[
  {
    "path": ".github/ISSUE_TEMPLATE/01-bugReport.yml",
    "content": "name: BUG提交 / BUG Report\ndescription: Report bugs to developers\nlabels: [\"BUG\"]\nbody:\n  - type: checkboxes\n    attributes:\n      label: 确认 / Assignments\n      description: 提交issue请确保完成以下前提，否则该issue可能被忽略 / Make sure you read checkboxs below\n      options:\n        - label: 搜索现有issues，不存在相似或相关的issue / No similar or related issues\n          required: true\n        - label: 最新[测试版](https://github.com/gedoor/legado/releases/beta)依然存在此问题 / Latest beta app does not work\n          required: true\n        - label: 此问题和Xposed、Lsposed、Magisk、手机主题、浏览器插件、无障碍服务等无关 / Make sure your machine is not touched by hook frameworks, plugins, accessibility etc\n          required: true\n\n  - type: textarea\n    attributes:\n      label: 问题描述 / Describe Bugs\n    validations:\n      required: true\n  - type: textarea\n    attributes:\n      label: 复现步骤 / How to reproduce\n    validations:\n      required: true\n  - type: checkboxes\n    attributes:\n      label: 确认 / Assignment\n      options:\n        - label: 已经提交复现所需要的附加资料 / Submit additions related with bugs\n          required: true\n  - type: textarea\n    attributes:\n      label: 其他信息 / Additions\n      description: |\n        反馈WEB书架前端问题时请提供浏览器版本信息，如Edge 129.0.2792.89\n      placeholder: \"请用```将提交的内容包裹\"\n\n  - type: textarea\n    attributes:\n      label: 日志提交 / Relevant log output\n      description: |\n        阅读日志位于我的-关于-崩溃日志、保存日志、书架-右上角-日志，或者自行使用log工具抓取日志\n        如果崩溃日志中包含`java.lang.OutOfMemoryError`，请安装最新测试版，在其他设置里打开记录堆转储，复现崩溃后去关于那里点保存日志，然后去备份目录里将heapDump文件夹里的文件打包压缩一下上传上来\n      placeholder: \"请用```将日志内容包裹\"\n\n  - type: input\n    attributes:\n      label: 阅读版本 / Legado version\n      placeholder: \"3.22.110823\"\n    validations:\n      required: true\n  - type: input\n    attributes:\n      label: Android版本 / Android version\n      placeholder: \"Android 12\"\n    validations:\n      required: true\n  - type: input\n    attributes:\n      label: 机型 / Model\n      placeholder: \"Redmi K30 Pro\"\n    validations:\n      required: true\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/02-featureRequest.yml",
    "content": "name: 功能请求 / Features\ndescription: Request new features\nlabels: [\"需求\"]\nbody:\n  - type: checkboxes\n    attributes:\n      label: 确认 / Assignments\n      description: 提交issue请确保完成以下前提，否则该issue可能被忽略 / Make sure you read checkbox below \n      options:\n        - label: 搜索现有issues，不存在相似或相关的issue / No related requests\n          required: true\n        - label: 最新[测试版](https://github.com/gedoor/legado/releases/beta)没有此功能 / Latest beta app does not this feature\n          required: true\n\n  - type: textarea\n    attributes:\n      label: 功能描述 / Features\n      placeholder: 请清晰的、详细的描述你想要的功能\n    validations:\n      required: true\n  - type: textarea\n    attributes:\n      label: 期望实现方式 / How to implement\n      placeholder: 阅读应该如何实现该功能\n    validations:\n      required: true\n  - type: input\n    attributes:\n      label: 阅读版本 / Legado version\n      placeholder: \"3.22.110823\"\n    validations:\n      required: true\n  - type: textarea\n    attributes:\n      label: 附加信息 / Additions\n      placeholder: 其他的与功能相关的附加信息\n  - type: textarea\n    attributes:\n      label: 效果演示 / Demo\n      placeholder: 可以手绘一些草图，或者提供可借鉴的图片\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/config.yml",
    "content": "blank_issues_enabled: false\ncontact_links:\n  - name: 简繁转化\n    url: https://github.com/liuyueyi/quick-chinese-transfer/issues/new\n    about: 简繁转化问题请优先到quick-chinese-transfer反馈\n  - name: 讨论 / Discussions\n    url: https://github.com/gedoor/legado/discussions\n    about: Please ask and answer questions here.\n  - name: 常见问题 / Wiki\n    url: https://github.com/gedoor/legado/wiki\n    about: Read wiki if your are new here.\n"
  },
  {
    "path": ".github/dependabot.yml",
    "content": "# To get started with Dependabot version updates, you'll need to specify which\n# package ecosystems to update and where the package manifests are located.\n# Please see the documentation for all configuration options:\n# https://docs.github.com/github/administering-a-repository/configuration-options-for-dependency-updates\n\nversion: 2\nregistries:\n  maven-google:\n    type: maven-repository\n    # url: https://maven.google.com\n    url: https://dl.google.com/dl/android/maven2/\n    password: dummy\n    username: dummy\n  maven-central:\n    type: maven-repository\n    url: https://repo1.maven.org/maven2/\n    password: dummy\n    username: dummy\n  maven-jitpack:\n    type: maven-repository\n    url: https://jitpack.io\n    password: dummy\n    username: dummy\n\nupdates:\n  - package-ecosystem: gradle\n    directory: \"/\"\n    schedule:\n      interval: \"weekly\"\n    registries: \"*\"\n    open-pull-requests-limit: 20\n    groups:\n      kotlin_KSP:\n        patterns:\n          - \"org.jetbrains.kotlin:*\"\n          - \"com.google.devtools.ksp\"\n  # Maintain dependencies for GitHub Actions\n  - package-ecosystem: \"github-actions\"\n    directory: \"/\"\n    schedule:\n      interval: \"weekly\"\n  - package-ecosystem: npm\n    directory: \"modules/web\"\n    schedule:\n      interval: \"weekly\"\n"
  },
  {
    "path": ".github/scripts/cronet.sh",
    "content": "#!/usr/bin/env bash\n\n#分支Stable Dev Beta\nbranch=$1\n#api 最大偏移\nmax_offset=$2\n\n[ -z $1 ] && branch=Stable\n[ -z $2 ] && max_offset=3\n[ -z $GITHUB_ENV ] && echo \"Error: Unexpected github workflow environment\" && exit\n\noffset=0\n\nfunction fetch_version() {\n    # 获取最新cronet版本\n    lastest_cronet_version=`curl -s \"https://chromiumdash.appspot.com/fetch_releases?channel=$branch&platform=Android&num=1&offset=$offset\" | jq .[0].version -r`\n    echo \"lastest_cronet_version: $lastest_cronet_version\"\n    #lastest_cronet_version=100.0.4845.0\n    lastest_cronet_main_version=${lastest_cronet_version%%\\.*}.0.0.0\n    check_version_exit\n}\nfunction check_version_exit() {\n    # 检查版本是否存在\n    local jar_url=\"https://storage.googleapis.com/chromium-cronet/android/$lastest_cronet_version/Release/cronet/cronet_api.jar\"\n    statusCode=$(curl -s -I -w %{http_code} \"$jar_url\" -o /dev/null)\n    if [ $statusCode == \"404\" ]; then\n        echo \"storage.googleapis.com return 404 for cronet $lastest_cronet_version\"\n        if [[ $max_offset > $offset ]]; then\n            offset=$(expr $offset + 1)\n            echo \"retry with offset $offset\"\n            fetch_version\n        else\n            exit\n        fi\n    fi\n}\nfunction version_compare() {\n    # 版本号比较 本地版本小于远程版本时返回0\n    local local_version=$1\n    local remote_version=$2\n    if [[ $local_version == $remote_version ]]; then\n        return 1\n    fi\n    if [[ $(printf '%s\\n' \"$1\" \"$2\" | sort -V | head -n1) == $remote_version ]]; then\n        return 1\n    else\n        return 0\n    fi\n}\n\n# 添加变量到github env\nfunction write_github_env_variable() {\n    echo \"$1=$2\" >> $GITHUB_ENV\n}\n\nfunction sync_proguard_rules() {\n    local raw_github_git=\"https://raw.githubusercontent.com/chromium/chromium/$lastest_cronet_version\"\n    local proguard_paths=(\n      components/cronet/android/cronet_combined_impl_native_proguard_golden.cfg\n    )\n    local proguard_rules_path=\"$GITHUB_WORKSPACE/app/cronet-proguard-rules.pro\"\n    rm -f $proguard_rules_path\n    echo \"fetch cronet proguard rules from upstream $raw_github_git\"\n    for path in ${proguard_paths[@]}\n    do\n        echo \"fetching $path ...\"\n        curl \"$raw_github_git/$path\" >> $proguard_rules_path\n    done\n}\n##########\n# 获取本地cronet版本\npath=$GITHUB_WORKSPACE/gradle.properties\ncurrent_cronet_version=`cat $path | grep CronetVersion | sed s/CronetVersion=//`\necho \"current_cronet_version: $current_cronet_version\"\n\necho \"fetch $branch release info from https://chromiumdash.appspot.com ...\"\nfetch_version\n\nif version_compare $current_cronet_version $lastest_cronet_version; then\n    # 更新gradle.properties\n    sed -i s/CronetVersion=.*/CronetVersion=$lastest_cronet_version/ $path\n    sed -i s/CronetMainVersion=.*/CronetMainVersion=$lastest_cronet_main_version/ $path\n    # 更新cronet_proguard_rules.pro\n    sync_proguard_rules\n    # 更新cronet版本\n    sed -i \"s/## cronet版本: .*/## cronet版本: $lastest_cronet_version/\" $GITHUB_WORKSPACE/app/src/main/assets/updateLog.md\n    # 生成pull request信息\n    write_github_env_variable PR_TITLE \"Bump cronet from $current_cronet_version to $lastest_cronet_version\"\n    write_github_env_variable PR_BODY \"Changes in the [Git log](https://chromium.googlesource.com/chromium/src/+log/$current_cronet_version..$lastest_cronet_version)\"\n    # 生成cronet flag\n    write_github_env_variable cronet ok\nfi\n"
  },
  {
    "path": ".github/scripts/lzy_web.py",
    "content": "import requests, os, datetime, sys\n\n# Cookie 中 phpdisk_info 的值\ncookie_phpdisk_info = os.environ.get('phpdisk_info')\n# Cookie 中 ylogin 的值\ncookie_ylogin = os.environ.get('ylogin')\n\n# 请求头\nheaders = {\n    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.72 Safari/537.36 Edg/89.0.774.45',\n    'Accept-Language': 'zh-CN,zh;q=0.9',\n    'Referer': 'https://pc.woozooo.com/account.php?action=login'\n}\n\n# 小饼干\ncookie = {\n    'ylogin': cookie_ylogin,\n    'phpdisk_info': cookie_phpdisk_info\n}\n\n\n# 日志打印\ndef log(msg):\n    utc_time = datetime.datetime.utcnow()\n    china_time = utc_time + datetime.timedelta(hours=8)\n    print(f\"[{china_time.strftime('%Y.%m.%d %H:%M:%S')}] {msg}\")\n\n\n# 检查是否已登录\ndef login_by_cookie():\n    url_account = \"https://pc.woozooo.com/account.php\"\n    if cookie['phpdisk_info'] is None:\n        log('ERROR: 请指定 Cookie 中 phpdisk_info 的值！')\n        return False\n    if cookie['ylogin'] is None:\n        log('ERROR: 请指定 Cookie 中 ylogin 的值！')\n        return False\n    res = requests.get(url_account, headers=headers, cookies=cookie, verify=True)\n    if '网盘用户登录' in res.text:\n        log('ERROR: 登录失败,请更新Cookie')\n        return False\n    else:\n        log('登录成功')\n        return True\n\n\n# 上传文件\ndef upload_file(file_dir, folder_id):\n    file_name = os.path.basename(file_dir)\n    url_upload = \"https://up.woozooo.com/fileup.php\"\n    headers['Referer'] = f'https://up.woozooo.com/mydisk.php?item=files&action=index&u={cookie_ylogin}'\n    post_data = {\n        \"task\": \"1\",\n        \"folder_id\": folder_id,\n        \"id\": \"WU_FILE_0\",\n        \"name\": file_name,\n    }\n    files = {'upload_file': (file_name, open(file_dir, \"rb\"), 'application/octet-stream')}\n    res = requests.post(url_upload, data=post_data, files=files, headers=headers, cookies=cookie, timeout=120).json()\n    log(f\"{file_dir} -> {res['info']}\")\n    return res['zt'] == 1\n\n\n# 上传文件夹内的文件\ndef upload_folder(folder_dir, folder_id):\n    file_list = sorted(os.listdir(folder_dir), reverse=True)\n    for file in file_list:\n        path = os.path.join(folder_dir, file)\n        if os.path.isfile(path):\n            upload_file(path, folder_id)\n        else:\n            upload_folder(path, folder_id)\n\n\n# 上传\ndef upload(dir, folder_id):\n    if dir is None:\n        log('ERROR: 请指定上传的文件路径')\n        return\n    if folder_id is None:\n        log('ERROR: 请指定蓝奏云的文件夹id')\n        return\n    if os.path.isfile(dir):\n        upload_file(dir, str(folder_id))\n    else:\n        upload_folder(dir, str(folder_id))\n\n\nif __name__ == '__main__':\n    argv = sys.argv[1:]\n    if len(argv) != 2:\n        log('ERROR: 参数错误,请以这种格式重新尝试\\npython lzy_web.py 需上传的路径 蓝奏云文件夹id')\n    # 需上传的路径\n    upload_path = argv[0]\n    # 蓝奏云文件夹id\n    lzy_folder_id = argv[1]\n    if login_by_cookie():\n        upload(upload_path, lzy_folder_id)\n"
  },
  {
    "path": ".github/scripts/tg_bot.py",
    "content": "import os, sys, telebot\n\n# 上传文件\ndef upload_file(tb, chat_id, file_dir):\n    doc = open(file_dir, 'rb')\n    tb.send_document(chat_id, doc)\n\n# 上传文件夹内的文件\ndef upload_folder(tb, chat_id, folder_dir):\n    file_list = sorted(os.listdir(folder_dir))\n    for file in file_list:\n        path = os.path.join(folder_dir, file)\n        if os.path.isfile(path):\n            upload_file(tb, chat_id, path)\n        else:\n            upload_folder(tb, chat_id, path)\n\n# 上传\ndef upload(tb, chat_id, dir):\n    if tb is None:\n        log('ERROR: 输入正确的token')\n        return\n    if chat_id is None:\n        log('ERROR: 输入正确的chat_id')\n        return\n    if dir is None:\n        log('ERROR: 请指定上传的文件路径')\n        return\n    if os.path.isfile(dir):\n        upload_file(tb, chat_id, dir)\n    else:\n        upload_folder(tb, chat_id, dir)\n\nif __name__ == '__main__':\n    argv = sys.argv[1:]\n    if len(argv) != 3:\n        log('ERROR: 参数错误,请以这种格式重新尝试\\npython tg_bot.py $token $chat_id 待上传的路径')\n    # Token\n    TOKEN = argv[0]\n    # chat_id\n    chat_id = argv[1]\n    # 待上传文件的路径\n    upload_path = argv[2]\n    #创建连接\n    tb = telebot.TeleBot(TOKEN)\n    #开始上传\n    upload(tb, chat_id, upload_path)\n"
  },
  {
    "path": ".github/workflows/autoupdatefork.yml",
    "content": "#更新fork\nname: update fork\n\non: \n  schedule:\n    - cron: '0 16 * * *' #0点定时任务\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n    if: ${{ github.event.repository.owner.id == github.event.sender.id && github.actor != 'gedoor' }}\n    steps:\n    - name: Checkout\n      uses: actions/checkout@v4\n    - name: Set env\n      run: |\n        git config --global user.email \"github-actions@github.com\"\n        git config --global user.name \"github-actions\"\n    - name: Update fork\n      run: |\n        git remote add upstream https://github.com/gedoor/legado.git\n        git remote -v\n        git fetch upstream\n        git checkout master\n        git merge upstream/master\n        git push origin master\n"
  },
  {
    "path": ".github/workflows/cronet.yml",
    "content": "name: Update Cronet\n\non:\n  schedule:\n  # 周一北京时间9点\n    - cron: 0 1 * * 1\n  workflow_dispatch:\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n    if: ${{ github.repository == 'gedoor/legado' }}\n    steps:\n      - uses: actions/checkout@v4\n\n      - name: Check Cronet Updates\n        run: source .github/scripts/cronet.sh\n\n      - name: Set up JDK 17\n        if: ${{ env.cronet == 'ok' }}\n        uses: actions/setup-java@v4\n        with:\n          distribution: 'temurin'\n          java-version: 17\n\n      - uses: gradle/actions/setup-gradle@v4\n        if: ${{ env.cronet == 'ok' }}\n\n      - name: Download Cronet\n        if: ${{ env.cronet == 'ok' }}\n        run: |\n          chmod +x gradlew\n          ./gradlew app:downloadCronet\n\n      - name: Create Pull Request\n        if: ${{ env.cronet == 'ok' }}\n        uses: peter-evans/create-pull-request@v7\n        continue-on-error: true\n        with:\n          token: ${{ secrets.ACTIONS_TOKEN }}\n          title: ${{ env.PR_TITLE }}\n          commit-message: |\n            ${{ env.PR_TITLE }}\n            - ${{ env.PR_BODY }}\n          body: ${{ env.PR_BODY }}\n          branch: cronet\n          delete-branch: true\n          add-paths: |\n            *cronet*jar\n            *cronet.json\n            *updateLog.md\n            *gradle.properties\n            *cronet-proguard-rules.pro\n"
  },
  {
    "path": ".github/workflows/release.yml",
    "content": "name: Release Build\n\non:\n#  push:\n#    branches:\n#      - master\n#    paths:\n#      - 'CHANGELOG.md'\n  workflow_dispatch:\n\njobs:\n  prepare:\n    runs-on: ubuntu-latest\n    if: github.event_name != 'pull_request' && github.ref == 'refs/heads/master'\n    outputs:\n      version: ${{ steps.set-ver.outputs.version }}\n      play: ${{ steps.check.outputs.play }}\n      sign: ${{ steps.check.outputs.sign }}\n    steps:\n      - id: set-ver\n        run: |\n          echo \"version=$(date -d \"8 hour\" -u +3.%y.%m%d%H)\" >> $GITHUB_OUTPUT\n      - id: check\n        run: |\n          if [ ! -z \"${{ secrets.RELEASE_KEY_STORE }}\" ]; then\n            echo \"sign=yes\" >> $GITHUB_OUTPUT\n          fi\n          if [ ! -z \"${{ secrets.SERVICE_ACCOUNT_JSON }}\" ]; then\n            echo \"play=yes\" >> $GITHUB_OUTPUT\n          fi\n\n  build:\n    needs: prepare\n    if: ${{ needs.prepare.outputs.sign }}\n    strategy:\n      matrix:\n        product: [ app, google ]\n      fail-fast: false\n    runs-on: ubuntu-latest\n    env:\n      product: ${{ matrix.product }}\n      VERSION: ${{ needs.prepare.outputs.version }}\n      play: ${{ needs.prepare.outputs.play }}\n    steps:\n      - uses: actions/checkout@v4\n\n      - name: Set up JDK 17\n        uses: actions/setup-java@v4\n        with:\n          distribution: 'temurin'\n          java-version: 17\n\n      - name: Release Apk Sign\n        run: |\n          # not use this output\n          # echo \"KeyStore=yes\" >> $GITHUB_OUTPUT\n          echo -e \"\\n\" >> gradle.properties\n          echo RELEASE_KEY_ALIAS='${{ secrets.RELEASE_KEY_ALIAS }}' >> gradle.properties\n          echo RELEASE_KEY_PASSWORD='${{ secrets.RELEASE_KEY_PASSWORD }}' >> gradle.properties\n          echo RELEASE_STORE_PASSWORD='${{ secrets.RELEASE_STORE_PASSWORD }}' >> gradle.properties\n          echo RELEASE_STORE_FILE='./key.jks' >> gradle.properties\n          echo ${{ secrets.RELEASE_KEY_STORE }} | base64 --decode > $GITHUB_WORKSPACE/app/key.jks\n\n      - name: Unify Version Name\n        run: |\n          echo \"统一版本号\"\n          sed \"/def version/c def version = \\\"${{ env.VERSION }}\\\"\" $GITHUB_WORKSPACE/app/build.gradle  -i\n\n      - name: Set up Gradle\n        uses: gradle/actions/setup-gradle@v4\n\n      - name: Build With Gradle\n        run: |\n          echo \"开始进行${{ env.product }}构建\"\n          chmod +x gradlew\n          ./gradlew assemble${{ env.product }}release --build-cache --parallel --daemon --warning-mode all\n\n      - name: Organize the Files\n        run: |\n          mkdir -p ${{ github.workspace }}/apk/\n          cp -rf ${{ github.workspace }}/app/build/outputs/apk/*/*/*.apk ${{ github.workspace }}/apk/\n          \n      - name: Upload App To Artifact\n        uses: actions/upload-artifact@v4\n        with:\n          name: legado_${{ env.product }}\n          path: ${{ github.workspace }}/apk/*.apk\n\n      - name: Release\n        if: ${{ env.product == 'app' }}\n        uses: softprops/action-gh-release@v2\n        with:\n          name: legado_app_${{ env.VERSION }}\n          tag_name: ${{ env.VERSION }}\n          body_path: ${{ github.workspace }}/CHANGELOG.md\n          draft: false\n          prerelease: false\n          files: ${{ github.workspace }}/apk/legado_app_*.apk\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n      \n      - name: Prepare For GooglePlay\n        if: env.product == 'google' && env.play == 'yes'\n        run: |\n          mkdir -p ReleaseNotes\n          ln -s ${{ github.workspace }}/CHANGELOG.md ReleaseNotes/whatsnew-en-US\n          ln -s ${{ github.workspace }}/CHANGELOG.md ReleaseNotes/whatsnew-zh-CN\n\n      - name: Release To GooglePlay\n        if: env.product == 'google' && env.play == 'yes'\n        uses: r0adkll/upload-google-play@v1\n        with:\n          serviceAccountJsonPlainText: ${{ secrets.SERVICE_ACCOUNT_JSON }}\n          packageName: io.legado.play\n          releaseFiles: ${{ github.workspace }}/apk/legado_google_*.apk\n          track: production\n          whatsNewDirectory: ${{ github.workspace }}/ReleaseNotes\n\n      - name: Push Assets To \"release\" Branch\n        if: ${{ github.actor == 'gedoor' }}\n        run: |\n          cd $GITHUB_WORKSPACE/apk/\n          git init\n          git checkout -b release\n          git config --global user.name \"github-actions[bot]\"\n          git config --global user.email \"41898282+github-actions[bot]@users.noreply.github.com\"\n          git remote add origin \"https://${{ github.actor }}:${{ secrets.ACTIONS_TOKEN }}@github.com/${{ github.actor }}/release\"\n          git add *.apk\n          git commit -m \"${{ env.VERSION }}\"\n          git push -f -u origin release\n          \n      - name: Purge Jsdelivr Cache\n        if: ${{ github.actor == 'gedoor' }}\n        run: |\n          result=$(curl -s https://purge.jsdelivr.net/gh/${{ github.actor }}/release@release/)\n          if echo $result |grep -q 'success.*true'; then\n            echo \"jsdelivr缓存更新成功\"\n          else\n            echo $result\n          fi\n"
  },
  {
    "path": ".github/workflows/stale.yml",
    "content": "# This workflow warns and then closes issues and PRs that have had no activity for a specified amount of time.\n#\n# You can adjust the behavior by modifying this file.\n# For more information, see:\n# https://github.com/actions/stale\nname: closeStaleIssue\n\non:\n  schedule:\n  # 每5天北京时间9点\n  - cron: '30 1 1/5 * *'\n  workflow_dispatch:\n\njobs:\n  stale:\n\n    runs-on: ubuntu-latest\n    if: ${{ github.repository == 'gedoor/legado' }}\n    permissions:\n      issues: write\n\n    steps:\n    - uses: actions/stale@v9\n      with:\n        repo-token: ${{ secrets.GITHUB_TOKEN }}\n        stale-issue-message: '由于长期没有状态更新，该问题将于5天后自动关闭。如有需要可重新打开。'\n        days-before-stale: 30\n        days-before-close: 5\n        operations-per-run: 100\n"
  },
  {
    "path": ".github/workflows/test.yml",
    "content": "name: Test Build\n\non:\n  push:\n    branches:\n      - master\n    paths:\n      - '**'\n      # - '!**/assets/**'\n      # - '!**.md'\n      - '!**/ISSUE_TEMPLATE/**'\n      - '!**/modules/web/**'\n  pull_request:\n    paths-ignore:\n      - '**/modules/web/**'\n  workflow_run:\n    workflows: [Build Web]\n    branches: [master]\n    types:\n      - completed\n  workflow_dispatch:\n\nconcurrency:\n  group: ${{ github.workflow }}-${{ github.ref }}\n  cancel-in-progress: true\n\njobs:\n\n  prepare:\n    runs-on: ubuntu-latest\n    if: ${{ !startsWith(github.event.head_commit.message, 'Merge pull request') }}\n    outputs:\n      version: ${{ steps.set-ver.outputs.version }}\n      versionL: ${{ steps.set-ver.outputs.versionL }}\n      lanzou: ${{ steps.check.outputs.lanzou }}\n      telegram: ${{ steps.check.outputs.telegram }}\n    steps:\n      - id: set-ver\n        run: |\n          echo \"version=$(date -d \"8 hour\" -u +3.%y.%m%d%H)\" >> $GITHUB_OUTPUT\n          echo \"versionL=$(date -d \"8 hour\" -u +3.%y.%m%d%H%M)\" >> $GITHUB_OUTPUT\n      - id: check\n        run: |\n          if [ ${{ secrets.LANZOU_ID }} ]; then\n            echo \"lanzou=yes\" >> $GITHUB_OUTPUT\n          fi\n          if [ ${{ secrets.BOT_TOKEN }} ]; then\n            echo \"telegram=yes\" >> $GITHUB_OUTPUT\n          fi\n\n  build:\n    needs: prepare\n    strategy:\n      matrix:\n        product: [ app ]\n        type: [ release, releaseA ]\n      fail-fast: false\n    runs-on: ubuntu-latest\n    env:\n      product: ${{ matrix.product }}\n      type: ${{ matrix.type }}\n      VERSION: ${{ needs.prepare.outputs.version }}\n      VERSIONL: ${{ needs.prepare.outputs.versionL }}\n    steps:\n      - uses: actions/checkout@v4\n        with:\n          fetch-depth: 0\n      - name: Set up JDK 17\n        uses: actions/setup-java@v4\n        with:\n          distribution: 'temurin'\n          java-version: 17\n      - name: Clear 18PlusList.txt\n        run: |\n          echo \"清空18PlusList.txt\"\n          echo \"\">$GITHUB_WORKSPACE/app/src/main/assets/18PlusList.txt\n      - name: Release Apk Sign\n        run: |\n          echo \"给apk增加签名\"\n          cp $GITHUB_WORKSPACE/.github/workflows/legado.jks $GITHUB_WORKSPACE/app/legado.jks\n          sed '$a\\RELEASE_STORE_FILE=./legado.jks'          $GITHUB_WORKSPACE/gradle.properties -i\n          sed '$a\\RELEASE_KEY_ALIAS=legado'                 $GITHUB_WORKSPACE/gradle.properties -i\n          sed '$a\\RELEASE_STORE_PASSWORD=gedoor_legado'     $GITHUB_WORKSPACE/gradle.properties -i\n          sed '$a\\RELEASE_KEY_PASSWORD=gedoor_legado'       $GITHUB_WORKSPACE/gradle.properties -i\n\n      - name: Set up Gradle\n        uses: gradle/actions/setup-gradle@v4\n\n      - name: Build With Gradle\n        continue-on-error: true\n        run: |\n          if [ ${{ env.type }} == 'release' ]; then\n            typeName=\"原包名\"\n          else\n            typeName=\"共存\"\n            sed \"s/'.release'/'.releaseA'/\" $GITHUB_WORKSPACE/app/build.gradle  -i\n            sed 's/.release/.releaseA/'     $GITHUB_WORKSPACE/app/google-services.json -i\n          fi\n          echo \"统一版本号\"\n          sed \"/def version/c def version = \\\"${{ env.VERSION }}\\\"\" $GITHUB_WORKSPACE/app/build.gradle  -i\n          echo \"开始${{ env.product }}$typeName构建\"\n          chmod +x gradlew\n          ./gradlew assemble${{ env.product }}release --build-cache --parallel --daemon --warning-mode all\n          echo \"修改APK文件名\"\n          mkdir -p ${{ github.workspace }}/apk/\n          for file in `ls ${{ github.workspace }}/app/build/outputs/apk/*/*/*.apk`; do\n            mv \"$file\" ${{ github.workspace }}/apk/legado_${{ env.product }}_${{ env.VERSIONL }}_$typeName.apk\n          done\n          echo \"移动mapping文件\"\n          mkdir -p ${{ github.workspace }}/mapping/\n          for file in `ls ${{ github.workspace }}/app/build/outputs/mapping/*/mapping.txt`; do\n            mv \"$file\" ${{ github.workspace }}/mapping/mapping.txt\n          done\n\n      - name: Move Missing Rules Files\n        run: |\n          echo \"移动missing_rules.txt文件\"\n          mkdir -p ${{ github.workspace }}/mapping/\n          for file in `ls ${{ github.workspace }}/app/build/outputs/mapping/*/missing_rules.txt`; do\n            mv \"$file\" ${{ github.workspace }}/mapping/missing_rules.txt\n          done\n\n      - name: Upload Missing Rules File To Artifact\n        uses: actions/upload-artifact@v4\n        with:\n          name: legado.${{ env.product }}.${{ env.type }}.mapping.missing_rules\n          if-no-files-found: ignore\n          path: ${{ github.workspace }}/mapping/missing_rules.txt\n\n      - name: Check Build production\n        run: |\n          if [ ! -d ${{ github.workspace }}/apk ]; then\n            echo \"Build production not found! Check gradle logs.\"\n            exit 1\n          fi\n          cd ${{ github.workspace }}/apk/\n          if [ ! -e legado_*.apk ]; then\n            echo \"Build production not found! Check gradle logs.\"\n            exit 1\n          fi\n\n      - name: Upload App To Artifact\n        uses: actions/upload-artifact@v4\n        with:\n          name: legado.${{ env.product }}.${{ env.type }}\n          if-no-files-found: ignore\n          path: ${{ github.workspace }}/apk/*.apk\n\n      - name: Upload Mapping File To Artifact\n        uses: actions/upload-artifact@v4\n        with:\n          name: legado.${{ env.product }}.${{ env.type }}.mapping\n          if-no-files-found: ignore\n          path: ${{ github.workspace }}/mapping/mapping.txt\n\n  prerelease:\n    needs: [ prepare, build ]\n    if: github.event_name != 'pull_request' && github.repository == 'gedoor/legado'\n    runs-on: ubuntu-latest\n    env: \n      VERSION: ${{ needs.prepare.outputs.version }}\n    steps:\n      - uses: actions/checkout@v4\n      - uses: actions/download-artifact@v4\n        with:\n          path: apk/\n\n      - working-directory: apk/\n        run: |\n          mv */*.apk .\n          rm -rf */\n\n          for file in `ls *.apk`; do\n            if [[ \"$file\" == *原包名* ]]; then\n              mv \"$file\" $(echo $file | sed s/原包名/release/)\n            else\n              mv \"$file\" $(echo $file | sed s/共存/releaseA/)\n            fi\n          done\n\n      - name: Delete Pre-Release\n        run: |\n          if gh release view beta &>/dev/null; then\n            gh release delete beta -y\n          fi\n        env:\n          GH_TOKEN: ${{ github.token }}\n      \n      - name: Create or update beta tag\n        uses: richardsimko/update-tag@v1\n        with:\n          tag_name: beta\n        env:\n          GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}\n\n      - name: Publish Pre-Release\n        uses: ncipollo/release-action@v1\n        with:\n          name: legado_app_${{ env.VERSION }}\n          tag: \"beta\"\n          body: |\n            此版本为测试版，签名与正式版不同，可能存在不稳定情况，升级前请务必备份好数据。\n            releaseA 为共存版本，可同时安装使用，功能没有区别。\n          prerelease: true\n          artifacts: ${{ github.workspace }}/apk/*.apk\n\n  lanzou:\n    needs: [ prepare, build ]\n    if: ${{ github.event_name != 'pull_request' && needs.prepare.outputs.lanzou == 'yes' }}\n    runs-on: ubuntu-latest\n    env:\n      # 登录蓝奏云后在控制台运行document.cookie\n      ylogin: ${{ secrets.LANZOU_ID }}\n      phpdisk_info: ${{ secrets.LANZOU_PSD }}\n      # 蓝奏云里的文件夹ID（阅读3测试版:2670621）\n      LANZOU_FOLDER_ID: ${{ secrets.LANZOU_FOLDER_ID }}\n      #蓝奏云分享链接\n      LANZOU_URL: ${{ secrets.LANZOU_URL }}\n    steps:\n      - uses: actions/checkout@v4\n      - uses: actions/download-artifact@v4\n        with:\n          path: apk/\n      - working-directory: apk/\n        run: mv */*.apk . ;rm -rf */\n      - name: Upload To Lanzou\n        continue-on-error: true\n        run: |\n          path=\"$GITHUB_WORKSPACE/apk/\"\n          python3 $GITHUB_WORKSPACE/.github/scripts/lzy_web.py \"$path\" \"$LANZOU_FOLDER_ID\"\n          echo \"[$(date -u -d '+8 hour' '+%Y.%m.%d %H:%M:%S')] 分享链接: $LANZOU_URL\"\n\n  test_Branch:\n    needs: [ prepare, build ]\n    runs-on: ubuntu-latest\n    if: ${{ github.event_name != 'pull_request' && github.actor == 'gedoor' }}\n    steps:\n      - uses: actions/checkout@v4\n      - uses: actions/download-artifact@v4\n        with:\n          path: apk/\n      - working-directory: apk/\n        run: mv */*.apk . ;rm -rf */\n      - name: Push To \"test\" Branch\n        run: |\n          cd $GITHUB_WORKSPACE/apk/\n          git init\n          git checkout -b test\n          git config --global user.name \"github-actions[bot]\"\n          git config --global user.email \"41898282+github-actions[bot]@users.noreply.github.com\"\n          git remote add origin \"https://${{ github.actor }}:${{ secrets.ACTIONS_TOKEN }}@github.com/${{ github.actor }}/release\"\n          git add *.apk\n          git commit -m \"${{ needs.prepare.outputs.versionL }}\"\n          git push -f -u origin test\n\n  telegram:\n    needs: [ prepare, build ]\n    if: ${{ github.event_name != 'pull_request' && needs.prepare.outputs.telegram == 'yes' }}\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions/checkout@v4\n      - uses: actions/download-artifact@v4\n        with:\n          path: apk/\n      - working-directory: apk/\n        run: |\n          for file in `ls */*.apk`; do\n            mv \"$file\" \"$(echo \"$file\"|sed -e 's#.*\\/##g' -e \"s/_/ /g\" -e 's/legado/阅读/')\"\n          done\n          rm -rf */\n      - name: Post to channel\n        uses: xireiki/channel-post@v1\n        with:\n          chat_id: ${{ secrets.CHANNEL_ID }}\n          bot_token: ${{ secrets.BOT_TOKEN }}\n          context: \"#阅读 #Legado #Beta ${{ needs.prepare.outputs.versionL }}\"\n          path: apk/*\n          method: sendFile\n"
  },
  {
    "path": ".github/workflows/web.yml",
    "content": "name: Build Web\n\non:\n  push:\n    branches:\n      - master\n    paths:\n      - '**/modules/web/**'\n  pull_request:\n    paths:\n      - '**/modules/web/**'\n  workflow_dispatch:\n\nenv:\n  UPSTREAM_REPOSITORY: gedoor/legado\n\njobs:\n  build:\n    runs-on: ubuntu-latest\n\n    steps:\n      - name: Checkout\n        uses: actions/checkout@v4\n\n      - name: Install Node.js\n        uses: actions/setup-node@v4\n        with:\n          node-version: 22\n\n      - uses: pnpm/action-setup@v4\n        name: Install pnpm\n        id: pnpm-install\n        with:\n          version: 9\n          run_install: false\n\n      - name: Get pnpm store directory\n        id: pnpm-cache\n        shell: bash\n        run: |\n          echo \"STORE_PATH=$(pnpm store path)\" >> $GITHUB_OUTPUT\n\n      - uses: actions/cache@v4\n        name: Setup pnpm cache\n        with:\n          path: ${{ steps.pnpm-cache.outputs.STORE_PATH }}\n          key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/web/package.json') }}\n          restore-keys: |\n            ${{ runner.os }}-pnpm-store-\n\n      - name: Build and move files\n        working-directory: modules/web\n        run: |\n          pnpm i\n          pnpm build\n          version=\"v$(date -d \"8 hour\" -u +3.%y.%m%d%H)\"\n          echo \"APP_VER=$version\" >> $GITHUB_ENV\n\n      - name: push changes\n        if: ${{ github.event_name != 'pull_request' && github.repository == env.UPSTREAM_REPOSITORY }}\n        uses: stefanzweifel/git-auto-commit-action@v5.1.0\n        with:\n          commit_message: Bump web ${{ env.APP_VER}}\n          file_pattern: app/src/main/assets/web/vue/\n\n"
  },
  {
    "path": ".gitignore",
    "content": "*.iml\n.gradle\nlocal.properties\n.DS_Store\n/build\nbuild/\n/captures\n.externalNativeBuild\n/release\n/tmp\nnode_modules/\n/app/app\n/app/google\n/app/gradle.properties\npackage-lock.json\n.idea/\n# Kotlin 2.0\n.kotlin/\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "**2022/10/02**\n\n* 更新cronet: 106.0.5249.79\n* 正文选择菜单朗读按钮长按可切换朗读选择内容和从选择开始处一直朗读\n* 源编辑输入框设置最大行数12,在行数特别多的时候更容易滚动到其它输入\n* 修复某些情况下无法搜索到标题的bug，净化规则较多的可能会降低搜索速度 by Xwite\n* 修复文件类书源换源后阅读bug by Xwite\n* Cronet 支持DnsHttpsSvcb by g2s20150909\n* 修复web进度同步问题 by 821938089\n* 启用混淆以减小app大小 有bug请带日志反馈\n* 其它一些优化\n"
  },
  {
    "path": "English.md",
    "content": "# [English](English.md) [中文](README.md)\n\n[![icon_android](https://github.com/gedoor/gedoor.github.io/blob/master/static/img/legado/icon_android.png)](https://play.google.com/store/apps/details?id=io.legado.play.release)\n<a href=\"https://jb.gg/OpenSourceSupport\" target=\"_blank\">\n<img width=\"24\" height=\"24\" src=\"https://resources.jetbrains.com/storage/products/company/brand/logos/jb_beam.svg?_gl=1*135yekd*_ga*OTY4Mjg4NDYzLjE2Mzk0NTE3MzQ.*_ga_9J976DJZ68*MTY2OTE2MzM5Ny4xMy4wLjE2NjkxNjMzOTcuNjAuMC4w&_ga=2.257292110.451256242.1669085120-968288463.1639451734\" alt=\"idea\"/>\n</a>\n<div align=\"center\">\n<img width=\"125\" height=\"125\" src=\"https://github.com/gedoor/legado/raw/master/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png\" alt=\"legado\"/>  \n  \nLegado / 开源阅读\n<br>\n<a href=\"https://gedoor.github.io\" target=\"_blank\">gedoor.github.io</a> / <a href=\"https://www.legado.top/\" target=\"_blank\">legado.top</a>\n<br>\nLegado is a free and open source novel reader for Android.\n</div>\n\n[![](https://img.shields.io/badge/-Contents:-696969.svg)](#contents) [![](https://img.shields.io/badge/-Function-F5F5F5.svg)](#Function-) [![](https://img.shields.io/badge/-Download-F5F5F5.svg)](#Download-) [![](https://img.shields.io/badge/-Community-F5F5F5.svg)](#Community-) [![](https://img.shields.io/badge/-API-F5F5F5.svg)](#API-) [![](https://img.shields.io/badge/-Other-F5F5F5.svg)](#Other-) [![](https://img.shields.io/badge/-Grateful-F5F5F5.svg)](#Grateful-) [![](https://img.shields.io/badge/-Interface-F5F5F5.svg)](#Interface-)\n\n>New user?\n>\n>The software does not provide content, you need to add it manually, such as importing book sources, etc. \n>Take a look at [official help documentation](https://www.yuque.com/legado/wiki)，Maybe there's an answer you need inside.\n\n# Function [![](https://img.shields.io/badge/-Function-F5F5F5.svg)](#Function-)\n\nYou can customize the book source, set your own rules, and capture web page data. The rules are simple and easy to understand. There are rules in the software. List bookshelf, grid bookshelf switch freely. The book source rules support search and discovery, and all the functions of finding books and reading books are all customized, making it easier to find books.\n* Custom ebook sources, set your own rules to capture web data, the rules are simple and easy to understand, the software has a rule description.\n* eBook sources rules support search and discovery, all find books and read books function all custom, find books more convenient.\n* Schedule updating your library for new chapters.\n* Online reading from web sources that can be imported in bulk\n* Local reading of Auto-download episodes.\n* Local reading of TXT or EPUB files\n* ebook Wishlist\n* Big text viewer. You can open eBook and txt in 1GB size\n* Automatic text replacement for removing ad in content\n* List bookshelf, grid bookshelf free to switch.\n* Subscription content, you can subscribe to any content you want to see, see what you want to see\n* A configurable reader with fonts, background, page transitions mode and other settings\n* Timer. Set interval time to listen ebook, time up, ebook  turn off completely.\n* TTS book reader. tts can optionally be install“smartvoice-4.1.0” or ”Speech Services by Google“  Give your baby a storybook to listen to and teach your baby to talk, \n* Dark mode and E-Ink mode support and Web service support\n* Create backups to local or WebDav server\n* Decentralization web3\n* Support replacement purification, it is very convenient to remove the content of advertisement replacement.\n* Support local TXT, EPUB reading, manual browsing, intelligent scanning.\n* Support highly customized reading interface, switch font, color, background, line spacing, paragraph spacing, bold, simplified and traditional conversion.\n* Support multiple page turning modes, covering, emulating, sliding, scrolling, etc.\n\n\n<a href=\"#readme\">\n    <img src=\"https://img.shields.io/badge/-Top-orange.svg\" alt=\"#\" align=\"right\">\n</a>\n\n# Download [![](https://img.shields.io/badge/-Download-F5F5F5.svg)](#Download-)\n\n#### Android\n\n* [Releases](https://github.com/gedoor/legado/releases/latest)\n* [Google play - $1.99](https://play.google.com/store/apps/details?id=io.legado.play.release)\n* [Coolapk](https://www.coolapk.com/apk/io.legado.app.release)\n* [\\#Beta](https://kunfei.lanzoui.com/b0f810h4b)\n* [IzzyOnDroid F-Droid Repository](https://apt.izzysoft.de/fdroid/index/apk/io.legado.app.release)\n\n\n#### IOS\n\n* Stopped(No release) - [Github](https://github.com/gedoor/YueDuFlutter)\n\n<a href=\"#readme\">\n    <img src=\"https://img.shields.io/badge/-Top-orange.svg\" alt=\"#\" align=\"right\">\n</a>\n\n# Community [![](https://img.shields.io/badge/-Community-F5F5F5.svg)](#Community-)\n\n#### Telegram\n\n[![Telegram-group](https://img.shields.io/badge/Telegram-group-blue)](https://t.me/yueduguanfang) [![Telegram-channel](https://img.shields.io/badge/Telegram-channel-blue)](https://t.me/legado_channels)\n\n#### Discord\n\n[![Discord](https://img.shields.io/discord/560731361414086666?color=%235865f2&label=Discord)](https://discord.gg/VtUfRyzRXn)\n\n#### Other\n\nhttps://www.yuque.com/legado/wiki/community\n\n<a href=\"#readme\">\n    <img src=\"https://img.shields.io/badge/-Top-orange.svg\" alt=\"#\" align=\"right\">\n</a>\n\n# API [![](https://img.shields.io/badge/-API-F5F5F5.svg)](#API-)\n\n* Legado 3.0 The API is provided in 2 ways: `Web way` and `Content Provider way`. You can call it yourself as needed in [here](api.md). \n* One-click import by url recall reading, url format: legado://import/{path}?src={url}\n* Path Type: bookSource,rssSource,replaceRule,textTocRule,httpTTS,theme,readConfig,dictRule,addToBookshelf\n* path type explanation: Book source, subscription source, replacement rules, local txt novel directory rules, online reading engine, theme, reading layout, [add to bookshelf](/app/src/main/java/io/legado/app/ui/association/AddToBookshelfDialog.kt)\n\n<a href=\"#readme\">\n    <img src=\"https://img.shields.io/badge/-Top-orange.svg\" alt=\"#\" align=\"right\">\n</a>\n\n# Other [![](https://img.shields.io/badge/-Other-F5F5F5.svg)](#Other-)\n\n##### Disclaimers\n\nhttps://gedoor.github.io/Disclaimer\n\n##### Legado 3.0\n\n* [eBook sources rules](https://mgz0227.github.io/The-tutorial-of-Legado/)\n* [Update Log](/app/src/main/assets/updateLog.md)\n* [Help Documentation](/app/src/main/assets/web/help/md/appHelp.md)\n* [web bookshelf](https://github.com/gedoor/legado_web_bookshelf)\n* [web source editor](https://github.com/gedoor/legado_web_source_editor)\n\n<a href=\"#readme\">\n    <img src=\"https://img.shields.io/badge/-Top-orange.svg\" alt=\"#\" align=\"right\">\n</a>\n\n# Grateful [![](https://img.shields.io/badge/-Grateful-F5F5F5.svg)](#Grateful-)\n\n> * org.jsoup:jsoup\n> * cn.wanghaomiao:JsoupXpath\n> * com.jayway.jsonpath:json-path\n> * com.github.gedoor:rhino-android\n> * com.squareup.okhttp3:okhttp\n> * com.github.bumptech.glide:glide\n> * org.nanohttpd:nanohttpd\n> * org.nanohttpd:nanohttpd-websocket\n> * cn.bingoogolapple:bga-qrcode-zxing\n> * com.jaredrummler:colorpicker\n> * org.apache.commons:commons-text\n> * io.noties.markwon:core\n> * io.noties.markwon:image-glide\n> * com.hankcs:hanlp\n> * com.positiondev.epublib:epublib-core\n\n<a href=\"#readme\">\n    <img src=\"https://img.shields.io/badge/-Top-orange.svg\" alt=\"#\" align=\"right\">\n</a>\n\n# Interface [![](https://img.shields.io/badge/-Interface-F5F5F5.svg)](#Interface-)\n\n<img src=\"https://github.com/gedoor/gedoor.github.io/blob/master/static/img/legado/%E9%98%85%E8%AF%BB%E7%AE%80%E4%BB%8B1.jpg\" width=\"270\"><img src=\"https://github.com/gedoor/gedoor.github.io/blob/master/static/img/legado/%E9%98%85%E8%AF%BB%E7%AE%80%E4%BB%8B2.jpg\" width=\"270\"><img src=\"https://github.com/gedoor/gedoor.github.io/blob/master/static/img/legado/%E9%98%85%E8%AF%BB%E7%AE%80%E4%BB%8B3.jpg\" width=\"270\">\n<img src=\"https://github.com/gedoor/gedoor.github.io/blob/master/static/img/legado/%E9%98%85%E8%AF%BB%E7%AE%80%E4%BB%8B4.jpg\" width=\"270\"><img src=\"https://github.com/gedoor/gedoor.github.io/blob/master/static/img/legado/%E9%98%85%E8%AF%BB%E7%AE%80%E4%BB%8B5.jpg\" width=\"270\"><img src=\"https://github.com/gedoor/gedoor.github.io/blob/master/static/img/legado/%E9%98%85%E8%AF%BB%E7%AE%80%E4%BB%8B6.jpg\" width=\"270\">\n\n<a href=\"#readme\">\n    <img src=\"https://img.shields.io/badge/-Top-orange.svg\" alt=\"#\" align=\"right\">\n</a>\n"
  },
  {
    "path": "LICENSE",
    "content": "                    GNU GENERAL PUBLIC LICENSE\n                       Version 3, 29 June 2007\n\n Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>\n Everyone is permitted to copy and distribute verbatim copies\n of this license document, but changing it is not allowed.\n\n                            Preamble\n\n  The GNU General Public License is a free, copyleft license for\nsoftware and other kinds of works.\n\n  The licenses for most software and other practical works are designed\nto take away your freedom to share and change the works.  By contrast,\nthe GNU General Public License is intended to guarantee your freedom to\nshare and change all versions of a program--to make sure it remains free\nsoftware for all its users.  We, the Free Software Foundation, use the\nGNU General Public License for most of our software; it applies also to\nany other work released this way by its authors.  You can apply it to\nyour programs, too.\n\n  When we speak of free software, we are referring to freedom, not\nprice.  Our General Public Licenses are designed to make sure that you\nhave the freedom to distribute copies of free software (and charge for\nthem if you wish), that you receive source code or can get it if you\nwant it, that you can change the software or use pieces of it in new\nfree programs, and that you know you can do these things.\n\n  To protect your rights, we need to prevent others from denying you\nthese rights or asking you to surrender the rights.  Therefore, you have\ncertain responsibilities if you distribute copies of the software, or if\nyou modify it: responsibilities to respect the freedom of others.\n\n  For example, if you distribute copies of such a program, whether\ngratis or for a fee, you must pass on to the recipients the same\nfreedoms that you received.  You must make sure that they, too, receive\nor can get the source code.  And you must show them these terms so they\nknow their rights.\n\n  Developers that use the GNU GPL protect your rights with two steps:\n(1) assert copyright on the software, and (2) offer you this License\ngiving you legal permission to copy, distribute and/or modify it.\n\n  For the developers' and authors' protection, the GPL clearly explains\nthat there is no warranty for this free software.  For both users' and\nauthors' sake, the GPL requires that modified versions be marked as\nchanged, so that their problems will not be attributed erroneously to\nauthors of previous versions.\n\n  Some devices are designed to deny users access to install or run\nmodified versions of the software inside them, although the manufacturer\ncan do so.  This is fundamentally incompatible with the aim of\nprotecting users' freedom to change the software.  The systematic\npattern of such abuse occurs in the area of products for individuals to\nuse, which is precisely where it is most unacceptable.  Therefore, we\nhave designed this version of the GPL to prohibit the practice for those\nproducts.  If such problems arise substantially in other domains, we\nstand ready to extend this provision to those domains in future versions\nof the GPL, as needed to protect the freedom of users.\n\n  Finally, every program is threatened constantly by software patents.\nStates should not allow patents to restrict development and use of\nsoftware on general-purpose computers, but in those that do, we wish to\navoid the special danger that patents applied to a free program could\nmake it effectively proprietary.  To prevent this, the GPL assures that\npatents cannot be used to render the program non-free.\n\n  The precise terms and conditions for copying, distribution and\nmodification follow.\n\n                       TERMS AND CONDITIONS\n\n  0. Definitions.\n\n  \"This License\" refers to version 3 of the GNU General Public License.\n\n  \"Copyright\" also means copyright-like laws that apply to other kinds of\nworks, such as semiconductor masks.\n\n  \"The Program\" refers to any copyrightable work licensed under this\nLicense.  Each licensee is addressed as \"you\".  \"Licensees\" and\n\"recipients\" may be individuals or organizations.\n\n  To \"modify\" a work means to copy from or adapt all or part of the work\nin a fashion requiring copyright permission, other than the making of an\nexact copy.  The resulting work is called a \"modified version\" of the\nearlier work or a work \"based on\" the earlier work.\n\n  A \"covered work\" means either the unmodified Program or a work based\non the Program.\n\n  To \"propagate\" a work means to do anything with it that, without\npermission, would make you directly or secondarily liable for\ninfringement under applicable copyright law, except executing it on a\ncomputer or modifying a private copy.  Propagation includes copying,\ndistribution (with or without modification), making available to the\npublic, and in some countries other activities as well.\n\n  To \"convey\" a work means any kind of propagation that enables other\nparties to make or receive copies.  Mere interaction with a user through\na computer network, with no transfer of a copy, is not conveying.\n\n  An interactive user interface displays \"Appropriate Legal Notices\"\nto the extent that it includes a convenient and prominently visible\nfeature that (1) displays an appropriate copyright notice, and (2)\ntells the user that there is no warranty for the work (except to the\nextent that warranties are provided), that licensees may convey the\nwork under this License, and how to view a copy of this License.  If\nthe interface presents a list of user commands or options, such as a\nmenu, a prominent item in the list meets this criterion.\n\n  1. Source Code.\n\n  The \"source code\" for a work means the preferred form of the work\nfor making modifications to it.  \"Object code\" means any non-source\nform of a work.\n\n  A \"Standard Interface\" means an interface that either is an official\nstandard defined by a recognized standards body, or, in the case of\ninterfaces specified for a particular programming language, one that\nis widely used among developers working in that language.\n\n  The \"System Libraries\" of an executable work include anything, other\nthan the work as a whole, that (a) is included in the normal form of\npackaging a Major Component, but which is not part of that Major\nComponent, and (b) serves only to enable use of the work with that\nMajor Component, or to implement a Standard Interface for which an\nimplementation is available to the public in source code form.  A\n\"Major Component\", in this context, means a major essential component\n(kernel, window system, and so on) of the specific operating system\n(if any) on which the executable work runs, or a compiler used to\nproduce the work, or an object code interpreter used to run it.\n\n  The \"Corresponding Source\" for a work in object code form means all\nthe source code needed to generate, install, and (for an executable\nwork) run the object code and to modify the work, including scripts to\ncontrol those activities.  However, it does not include the work's\nSystem Libraries, or general-purpose tools or generally available free\nprograms which are used unmodified in performing those activities but\nwhich are not part of the work.  For example, Corresponding Source\nincludes interface definition files associated with source files for\nthe work, and the source code for shared libraries and dynamically\nlinked subprograms that the work is specifically designed to require,\nsuch as by intimate data communication or control flow between those\nsubprograms and other parts of the work.\n\n  The Corresponding Source need not include anything that users\ncan regenerate automatically from other parts of the Corresponding\nSource.\n\n  The Corresponding Source for a work in source code form is that\nsame work.\n\n  2. Basic Permissions.\n\n  All rights granted under this License are granted for the term of\ncopyright on the Program, and are irrevocable provided the stated\nconditions are met.  This License explicitly affirms your unlimited\npermission to run the unmodified Program.  The output from running a\ncovered work is covered by this License only if the output, given its\ncontent, constitutes a covered work.  This License acknowledges your\nrights of fair use or other equivalent, as provided by copyright law.\n\n  You may make, run and propagate covered works that you do not\nconvey, without conditions so long as your license otherwise remains\nin force.  You may convey covered works to others for the sole purpose\nof having them make modifications exclusively for you, or provide you\nwith facilities for running those works, provided that you comply with\nthe terms of this License in conveying all material for which you do\nnot control copyright.  Those thus making or running the covered works\nfor you must do so exclusively on your behalf, under your direction\nand control, on terms that prohibit them from making any copies of\nyour copyrighted material outside their relationship with you.\n\n  Conveying under any other circumstances is permitted solely under\nthe conditions stated below.  Sublicensing is not allowed; section 10\nmakes it unnecessary.\n\n  3. Protecting Users' Legal Rights From Anti-Circumvention Law.\n\n  No covered work shall be deemed part of an effective technological\nmeasure under any applicable law fulfilling obligations under article\n11 of the WIPO copyright treaty adopted on 20 December 1996, or\nsimilar laws prohibiting or restricting circumvention of such\nmeasures.\n\n  When you convey a covered work, you waive any legal power to forbid\ncircumvention of technological measures to the extent such circumvention\nis effected by exercising rights under this License with respect to\nthe covered work, and you disclaim any intention to limit operation or\nmodification of the work as a means of enforcing, against the work's\nusers, your or third parties' legal rights to forbid circumvention of\ntechnological measures.\n\n  4. Conveying Verbatim Copies.\n\n  You may convey verbatim copies of the Program's source code as you\nreceive it, in any medium, provided that you conspicuously and\nappropriately publish on each copy an appropriate copyright notice;\nkeep intact all notices stating that this License and any\nnon-permissive terms added in accord with section 7 apply to the code;\nkeep intact all notices of the absence of any warranty; and give all\nrecipients a copy of this License along with the Program.\n\n  You may charge any price or no price for each copy that you convey,\nand you may offer support or warranty protection for a fee.\n\n  5. Conveying Modified Source Versions.\n\n  You may convey a work based on the Program, or the modifications to\nproduce it from the Program, in the form of source code under the\nterms of section 4, provided that you also meet all of these conditions:\n\n    a) The work must carry prominent notices stating that you modified\n    it, and giving a relevant date.\n\n    b) The work must carry prominent notices stating that it is\n    released under this License and any conditions added under section\n    7.  This requirement modifies the requirement in section 4 to\n    \"keep intact all notices\".\n\n    c) You must license the entire work, as a whole, under this\n    License to anyone who comes into possession of a copy.  This\n    License will therefore apply, along with any applicable section 7\n    additional terms, to the whole of the work, and all its parts,\n    regardless of how they are packaged.  This License gives no\n    permission to license the work in any other way, but it does not\n    invalidate such permission if you have separately received it.\n\n    d) If the work has interactive user interfaces, each must display\n    Appropriate Legal Notices; however, if the Program has interactive\n    interfaces that do not display Appropriate Legal Notices, your\n    work need not make them do so.\n\n  A compilation of a covered work with other separate and independent\nworks, which are not by their nature extensions of the covered work,\nand which are not combined with it such as to form a larger program,\nin or on a volume of a storage or distribution medium, is called an\n\"aggregate\" if the compilation and its resulting copyright are not\nused to limit the access or legal rights of the compilation's users\nbeyond what the individual works permit.  Inclusion of a covered work\nin an aggregate does not cause this License to apply to the other\nparts of the aggregate.\n\n  6. Conveying Non-Source Forms.\n\n  You may convey a covered work in object code form under the terms\nof sections 4 and 5, provided that you also convey the\nmachine-readable Corresponding Source under the terms of this License,\nin one of these ways:\n\n    a) Convey the object code in, or embodied in, a physical product\n    (including a physical distribution medium), accompanied by the\n    Corresponding Source fixed on a durable physical medium\n    customarily used for software interchange.\n\n    b) Convey the object code in, or embodied in, a physical product\n    (including a physical distribution medium), accompanied by a\n    written offer, valid for at least three years and valid for as\n    long as you offer spare parts or customer support for that product\n    model, to give anyone who possesses the object code either (1) a\n    copy of the Corresponding Source for all the software in the\n    product that is covered by this License, on a durable physical\n    medium customarily used for software interchange, for a price no\n    more than your reasonable cost of physically performing this\n    conveying of source, or (2) access to copy the\n    Corresponding Source from a network server at no charge.\n\n    c) Convey individual copies of the object code with a copy of the\n    written offer to provide the Corresponding Source.  This\n    alternative is allowed only occasionally and noncommercially, and\n    only if you received the object code with such an offer, in accord\n    with subsection 6b.\n\n    d) Convey the object code by offering access from a designated\n    place (gratis or for a charge), and offer equivalent access to the\n    Corresponding Source in the same way through the same place at no\n    further charge.  You need not require recipients to copy the\n    Corresponding Source along with the object code.  If the place to\n    copy the object code is a network server, the Corresponding Source\n    may be on a different server (operated by you or a third party)\n    that supports equivalent copying facilities, provided you maintain\n    clear directions next to the object code saying where to find the\n    Corresponding Source.  Regardless of what server hosts the\n    Corresponding Source, you remain obligated to ensure that it is\n    available for as long as needed to satisfy these requirements.\n\n    e) Convey the object code using peer-to-peer transmission, provided\n    you inform other peers where the object code and Corresponding\n    Source of the work are being offered to the general public at no\n    charge under subsection 6d.\n\n  A separable portion of the object code, whose source code is excluded\nfrom the Corresponding Source as a System Library, need not be\nincluded in conveying the object code work.\n\n  A \"User Product\" is either (1) a \"consumer product\", which means any\ntangible personal property which is normally used for personal, family,\nor household purposes, or (2) anything designed or sold for incorporation\ninto a dwelling.  In determining whether a product is a consumer product,\ndoubtful cases shall be resolved in favor of coverage.  For a particular\nproduct received by a particular user, \"normally used\" refers to a\ntypical or common use of that class of product, regardless of the status\nof the particular user or of the way in which the particular user\nactually uses, or expects or is expected to use, the product.  A product\nis a consumer product regardless of whether the product has substantial\ncommercial, industrial or non-consumer uses, unless such uses represent\nthe only significant mode of use of the product.\n\n  \"Installation Information\" for a User Product means any methods,\nprocedures, authorization keys, or other information required to install\nand execute modified versions of a covered work in that User Product from\na modified version of its Corresponding Source.  The information must\nsuffice to ensure that the continued functioning of the modified object\ncode is in no case prevented or interfered with solely because\nmodification has been made.\n\n  If you convey an object code work under this section in, or with, or\nspecifically for use in, a User Product, and the conveying occurs as\npart of a transaction in which the right of possession and use of the\nUser Product is transferred to the recipient in perpetuity or for a\nfixed term (regardless of how the transaction is characterized), the\nCorresponding Source conveyed under this section must be accompanied\nby the Installation Information.  But this requirement does not apply\nif neither you nor any third party retains the ability to install\nmodified object code on the User Product (for example, the work has\nbeen installed in ROM).\n\n  The requirement to provide Installation Information does not include a\nrequirement to continue to provide support service, warranty, or updates\nfor a work that has been modified or installed by the recipient, or for\nthe User Product in which it has been modified or installed.  Access to a\nnetwork may be denied when the modification itself materially and\nadversely affects the operation of the network or violates the rules and\nprotocols for communication across the network.\n\n  Corresponding Source conveyed, and Installation Information provided,\nin accord with this section must be in a format that is publicly\ndocumented (and with an implementation available to the public in\nsource code form), and must require no special password or key for\nunpacking, reading or copying.\n\n  7. Additional Terms.\n\n  \"Additional permissions\" are terms that supplement the terms of this\nLicense by making exceptions from one or more of its conditions.\nAdditional permissions that are applicable to the entire Program shall\nbe treated as though they were included in this License, to the extent\nthat they are valid under applicable law.  If additional permissions\napply only to part of the Program, that part may be used separately\nunder those permissions, but the entire Program remains governed by\nthis License without regard to the additional permissions.\n\n  When you convey a copy of a covered work, you may at your option\nremove any additional permissions from that copy, or from any part of\nit.  (Additional permissions may be written to require their own\nremoval in certain cases when you modify the work.)  You may place\nadditional permissions on material, added by you to a covered work,\nfor which you have or can give appropriate copyright permission.\n\n  Notwithstanding any other provision of this License, for material you\nadd to a covered work, you may (if authorized by the copyright holders of\nthat material) supplement the terms of this License with terms:\n\n    a) Disclaiming warranty or limiting liability differently from the\n    terms of sections 15 and 16 of this License; or\n\n    b) Requiring preservation of specified reasonable legal notices or\n    author attributions in that material or in the Appropriate Legal\n    Notices displayed by works containing it; or\n\n    c) Prohibiting misrepresentation of the origin of that material, or\n    requiring that modified versions of such material be marked in\n    reasonable ways as different from the original version; or\n\n    d) Limiting the use for publicity purposes of names of licensors or\n    authors of the material; or\n\n    e) Declining to grant rights under trademark law for use of some\n    trade names, trademarks, or service marks; or\n\n    f) Requiring indemnification of licensors and authors of that\n    material by anyone who conveys the material (or modified versions of\n    it) with contractual assumptions of liability to the recipient, for\n    any liability that these contractual assumptions directly impose on\n    those licensors and authors.\n\n  All other non-permissive additional terms are considered \"further\nrestrictions\" within the meaning of section 10.  If the Program as you\nreceived it, or any part of it, contains a notice stating that it is\ngoverned by this License along with a term that is a further\nrestriction, you may remove that term.  If a license document contains\na further restriction but permits relicensing or conveying under this\nLicense, you may add to a covered work material governed by the terms\nof that license document, provided that the further restriction does\nnot survive such relicensing or conveying.\n\n  If you add terms to a covered work in accord with this section, you\nmust place, in the relevant source files, a statement of the\nadditional terms that apply to those files, or a notice indicating\nwhere to find the applicable terms.\n\n  Additional terms, permissive or non-permissive, may be stated in the\nform of a separately written license, or stated as exceptions;\nthe above requirements apply either way.\n\n  8. Termination.\n\n  You may not propagate or modify a covered work except as expressly\nprovided under this License.  Any attempt otherwise to propagate or\nmodify it is void, and will automatically terminate your rights under\nthis License (including any patent licenses granted under the third\nparagraph of section 11).\n\n  However, if you cease all violation of this License, then your\nlicense from a particular copyright holder is reinstated (a)\nprovisionally, unless and until the copyright holder explicitly and\nfinally terminates your license, and (b) permanently, if the copyright\nholder fails to notify you of the violation by some reasonable means\nprior to 60 days after the cessation.\n\n  Moreover, your license from a particular copyright holder is\nreinstated permanently if the copyright holder notifies you of the\nviolation by some reasonable means, this is the first time you have\nreceived notice of violation of this License (for any work) from that\ncopyright holder, and you cure the violation prior to 30 days after\nyour receipt of the notice.\n\n  Termination of your rights under this section does not terminate the\nlicenses of parties who have received copies or rights from you under\nthis License.  If your rights have been terminated and not permanently\nreinstated, you do not qualify to receive new licenses for the same\nmaterial under section 10.\n\n  9. Acceptance Not Required for Having Copies.\n\n  You are not required to accept this License in order to receive or\nrun a copy of the Program.  Ancillary propagation of a covered work\noccurring solely as a consequence of using peer-to-peer transmission\nto receive a copy likewise does not require acceptance.  However,\nnothing other than this License grants you permission to propagate or\nmodify any covered work.  These actions infringe copyright if you do\nnot accept this License.  Therefore, by modifying or propagating a\ncovered work, you indicate your acceptance of this License to do so.\n\n  10. Automatic Licensing of Downstream Recipients.\n\n  Each time you convey a covered work, the recipient automatically\nreceives a license from the original licensors, to run, modify and\npropagate that work, subject to this License.  You are not responsible\nfor enforcing compliance by third parties with this License.\n\n  An \"entity transaction\" is a transaction transferring control of an\norganization, or substantially all assets of one, or subdividing an\norganization, or merging organizations.  If propagation of a covered\nwork results from an entity transaction, each party to that\ntransaction who receives a copy of the work also receives whatever\nlicenses to the work the party's predecessor in interest had or could\ngive under the previous paragraph, plus a right to possession of the\nCorresponding Source of the work from the predecessor in interest, if\nthe predecessor has it or can get it with reasonable efforts.\n\n  You may not impose any further restrictions on the exercise of the\nrights granted or affirmed under this License.  For example, you may\nnot impose a license fee, royalty, or other charge for exercise of\nrights granted under this License, and you may not initiate litigation\n(including a cross-claim or counterclaim in a lawsuit) alleging that\nany patent claim is infringed by making, using, selling, offering for\nsale, or importing the Program or any portion of it.\n\n  11. Patents.\n\n  A \"contributor\" is a copyright holder who authorizes use under this\nLicense of the Program or a work on which the Program is based.  The\nwork thus licensed is called the contributor's \"contributor version\".\n\n  A contributor's \"essential patent claims\" are all patent claims\nowned or controlled by the contributor, whether already acquired or\nhereafter acquired, that would be infringed by some manner, permitted\nby this License, of making, using, or selling its contributor version,\nbut do not include claims that would be infringed only as a\nconsequence of further modification of the contributor version.  For\npurposes of this definition, \"control\" includes the right to grant\npatent sublicenses in a manner consistent with the requirements of\nthis License.\n\n  Each contributor grants you a non-exclusive, worldwide, royalty-free\npatent license under the contributor's essential patent claims, to\nmake, use, sell, offer for sale, import and otherwise run, modify and\npropagate the contents of its contributor version.\n\n  In the following three paragraphs, a \"patent license\" is any express\nagreement or commitment, however denominated, not to enforce a patent\n(such as an express permission to practice a patent or covenant not to\nsue for patent infringement).  To \"grant\" such a patent license to a\nparty means to make such an agreement or commitment not to enforce a\npatent against the party.\n\n  If you convey a covered work, knowingly relying on a patent license,\nand the Corresponding Source of the work is not available for anyone\nto copy, free of charge and under the terms of this License, through a\npublicly available network server or other readily accessible means,\nthen you must either (1) cause the Corresponding Source to be so\navailable, or (2) arrange to deprive yourself of the benefit of the\npatent license for this particular work, or (3) arrange, in a manner\nconsistent with the requirements of this License, to extend the patent\nlicense to downstream recipients.  \"Knowingly relying\" means you have\nactual knowledge that, but for the patent license, your conveying the\ncovered work in a country, or your recipient's use of the covered work\nin a country, would infringe one or more identifiable patents in that\ncountry that you have reason to believe are valid.\n\n  If, pursuant to or in connection with a single transaction or\narrangement, you convey, or propagate by procuring conveyance of, a\ncovered work, and grant a patent license to some of the parties\nreceiving the covered work authorizing them to use, propagate, modify\nor convey a specific copy of the covered work, then the patent license\nyou grant is automatically extended to all recipients of the covered\nwork and works based on it.\n\n  A patent license is \"discriminatory\" if it does not include within\nthe scope of its coverage, prohibits the exercise of, or is\nconditioned on the non-exercise of one or more of the rights that are\nspecifically granted under this License.  You may not convey a covered\nwork if you are a party to an arrangement with a third party that is\nin the business of distributing software, under which you make payment\nto the third party based on the extent of your activity of conveying\nthe work, and under which the third party grants, to any of the\nparties who would receive the covered work from you, a discriminatory\npatent license (a) in connection with copies of the covered work\nconveyed by you (or copies made from those copies), or (b) primarily\nfor and in connection with specific products or compilations that\ncontain the covered work, unless you entered into that arrangement,\nor that patent license was granted, prior to 28 March 2007.\n\n  Nothing in this License shall be construed as excluding or limiting\nany implied license or other defenses to infringement that may\notherwise be available to you under applicable patent law.\n\n  12. No Surrender of Others' Freedom.\n\n  If conditions are imposed on you (whether by court order, agreement or\notherwise) that contradict the conditions of this License, they do not\nexcuse you from the conditions of this License.  If you cannot convey a\ncovered work so as to satisfy simultaneously your obligations under this\nLicense and any other pertinent obligations, then as a consequence you may\nnot convey it at all.  For example, if you agree to terms that obligate you\nto collect a royalty for further conveying from those to whom you convey\nthe Program, the only way you could satisfy both those terms and this\nLicense would be to refrain entirely from conveying the Program.\n\n  13. Use with the GNU Affero General Public License.\n\n  Notwithstanding any other provision of this License, you have\npermission to link or combine any covered work with a work licensed\nunder version 3 of the GNU Affero General Public License into a single\ncombined work, and to convey the resulting work.  The terms of this\nLicense will continue to apply to the part which is the covered work,\nbut the special requirements of the GNU Affero General Public License,\nsection 13, concerning interaction through a network will apply to the\ncombination as such.\n\n  14. Revised Versions of this License.\n\n  The Free Software Foundation may publish revised and/or new versions of\nthe GNU General Public License from time to time.  Such new versions will\nbe similar in spirit to the present version, but may differ in detail to\naddress new problems or concerns.\n\n  Each version is given a distinguishing version number.  If the\nProgram specifies that a certain numbered version of the GNU General\nPublic License \"or any later version\" applies to it, you have the\noption of following the terms and conditions either of that numbered\nversion or of any later version published by the Free Software\nFoundation.  If the Program does not specify a version number of the\nGNU General Public License, you may choose any version ever published\nby the Free Software Foundation.\n\n  If the Program specifies that a proxy can decide which future\nversions of the GNU General Public License can be used, that proxy's\npublic statement of acceptance of a version permanently authorizes you\nto choose that version for the Program.\n\n  Later license versions may give you additional or different\npermissions.  However, no additional obligations are imposed on any\nauthor or copyright holder as a result of your choosing to follow a\nlater version.\n\n  15. Disclaimer of Warranty.\n\n  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY\nAPPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT\nHOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM \"AS IS\" WITHOUT WARRANTY\nOF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,\nTHE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\nPURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM\nIS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF\nALL NECESSARY SERVICING, REPAIR OR CORRECTION.\n\n  16. Limitation of Liability.\n\n  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING\nWILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS\nTHE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY\nGENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE\nUSE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF\nDATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD\nPARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),\nEVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF\nSUCH DAMAGES.\n\n  17. Interpretation of Sections 15 and 16.\n\n  If the disclaimer of warranty and limitation of liability provided\nabove cannot be given local legal effect according to their terms,\nreviewing courts shall apply local law that most closely approximates\nan absolute waiver of all civil liability in connection with the\nProgram, unless a warranty or assumption of liability accompanies a\ncopy of the Program in return for a fee.\n\n                     END OF TERMS AND CONDITIONS\n\n            How to Apply These Terms to Your New Programs\n\n  If you develop a new program, and you want it to be of the greatest\npossible use to the public, the best way to achieve this is to make it\nfree software which everyone can redistribute and change under these terms.\n\n  To do so, attach the following notices to the program.  It is safest\nto attach them to the start of each source file to most effectively\nstate the exclusion of warranty; and each file should have at least\nthe \"copyright\" line and a pointer to where the full notice is found.\n\n    <one line to give the program's name and a brief idea of what it does.>\n    Copyright (C) <year>  <name of author>\n\n    This program is free software: you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation, either version 3 of the License, or\n    (at your option) any later version.\n\n    This program is distributed in the hope that it will be useful,\n    but WITHOUT ANY WARRANTY; without even the implied warranty of\n    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n    GNU General Public License for more details.\n\n    You should have received a copy of the GNU General Public License\n    along with this program.  If not, see <https://www.gnu.org/licenses/>.\n\nAlso add information on how to contact you by electronic and paper mail.\n\n  If the program does terminal interaction, make it output a short\nnotice like this when it starts in an interactive mode:\n\n    Legado  Copyright (C) 2019-2023  gedoor\n    This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.\n    This is free software, and you are welcome to redistribute it\n    under certain conditions; type `show c' for details.\n\nThe hypothetical commands `show w' and `show c' should show the appropriate\nparts of the General Public License.  Of course, your program's commands\nmight be different; for a GUI interface, you would use an \"about box\".\n\n  You should also get your employer (if you work as a programmer) or school,\nif any, to sign a \"copyright disclaimer\" for the program, if necessary.\nFor more information on this, and how to apply and follow the GNU GPL, see\n<https://www.gnu.org/licenses/>.\n\n  The GNU General Public License does not permit incorporating your program\ninto proprietary programs.  If your program is a subroutine library, you\nmay consider it more useful to permit linking proprietary applications with\nthe library.  If this is what you want to do, use the GNU Lesser General\nPublic License instead of this License.  But first, please read\n<https://www.gnu.org/licenses/why-not-lgpl.html>.\n"
  },
  {
    "path": "README.md",
    "content": "# [English](English.md) [中文](README.md)\n\n[![icon_android](https://github.com/gedoor/gedoor.github.io/blob/master/static/img/legado/icon_android.png)](https://play.google.com/store/apps/details?id=io.legado.play.release)\n<a href=\"https://jb.gg/OpenSourceSupport\" target=\"_blank\">\n<img width=\"24\" height=\"24\" src=\"https://resources.jetbrains.com/storage/products/company/brand/logos/jb_beam.svg?_gl=1*135yekd*_ga*OTY4Mjg4NDYzLjE2Mzk0NTE3MzQ.*_ga_9J976DJZ68*MTY2OTE2MzM5Ny4xMy4wLjE2NjkxNjMzOTcuNjAuMC4w&_ga=2.257292110.451256242.1669085120-968288463.1639451734\" alt=\"idea\"/>\n</a>\n\n<div align=\"center\">\n<img width=\"125\" height=\"125\" src=\"https://github.com/gedoor/legado/raw/master/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png\" alt=\"legado\"/>  \n  \nLegado / 开源阅读\n<br>\n<a href=\"https://gedoor.github.io\" target=\"_blank\">gedoor.github.io</a> / <a href=\"https://www.legado.top/\" target=\"_blank\">legado.top</a>\n<br>\nLegado is a free and open source novel reader for Android.\n</div>\n\n[![](https://img.shields.io/badge/-Contents:-696969.svg)](#contents) [![](https://img.shields.io/badge/-Function-F5F5F5.svg)](#Function-主要功能-) [![](https://img.shields.io/badge/-Community-F5F5F5.svg)](#Community-交流社区-) [![](https://img.shields.io/badge/-API-F5F5F5.svg)](#API-) [![](https://img.shields.io/badge/-Other-F5F5F5.svg)](#Other-其他-) [![](https://img.shields.io/badge/-Grateful-F5F5F5.svg)](#Grateful-感谢-) [![](https://img.shields.io/badge/-Interface-F5F5F5.svg)](#Interface-界面-)\n\n>新用户？\n>\n>软件不提供内容，需要您自己手动添加，例如导入书源等。\n>看看 [官方帮助文档](https://www.yuque.com/legado/wiki)，也许里面就有你要的答案。\n\n# Function-主要功能 [![](https://img.shields.io/badge/-Function-F5F5F5.svg)](#Function-主要功能-)\n[English](English.md)\n\n<details><summary>中文</summary>\n1.自定义书源，自己设置规则，抓取网页数据，规则简单易懂，软件内有规则说明。<br>\n2.列表书架，网格书架自由切换。<br>\n3.书源规则支持搜索及发现，所有找书看书功能全部自定义，找书更方便。<br>\n4.订阅内容,可以订阅想看的任何内容,看你想看<br>\n5.支持替换净化，去除广告替换内容很方便。<br>\n6.支持本地TXT、EPUB阅读，手动浏览，智能扫描。<br>\n7.支持高度自定义阅读界面，切换字体、颜色、背景、行距、段距、加粗、简繁转换等。<br>\n8.支持多种翻页模式，覆盖、仿真、滑动、滚动等。<br>\n9.软件开源，持续优化，无广告。\n</details>\n\n<a href=\"#readme\">\n    <img src=\"https://img.shields.io/badge/-返回顶部-orange.svg\" alt=\"#\" align=\"right\">\n</a>\n\n# Community-交流社区 [![](https://img.shields.io/badge/-Community-F5F5F5.svg)](#Community-交流社区-)\n\n#### Telegram\n[![Telegram-group](https://img.shields.io/badge/Telegram-%E7%BE%A4%E7%BB%84-blue)](https://t.me/yueduguanfang) [![Telegram-channel](https://img.shields.io/badge/Telegram-%E9%A2%91%E9%81%93-blue)](https://t.me/legado_channels)\n\n#### Discord\n[![Discord](https://img.shields.io/discord/560731361414086666?color=%235865f2&label=Discord)](https://discord.gg/VtUfRyzRXn)\n\n#### Other\nhttps://www.yuque.com/legado/wiki/community\n\n<a href=\"#readme\">\n    <img src=\"https://img.shields.io/badge/-返回顶部-orange.svg\" alt=\"#\" align=\"right\">\n</a>\n\n# API [![](https://img.shields.io/badge/-API-F5F5F5.svg)](#API-)\n* 阅读3.0 提供了2种方式的API：`Web方式`和`Content Provider方式`。您可以在[这里](api.md)根据需要自行调用。 \n* 可通过url唤起阅读进行一键导入,url格式: legado://import/{path}?src={url}\n* path类型: bookSource,rssSource,replaceRule,textTocRule,httpTTS,theme,readConfig,dictRule,[addToBookshelf](/app/src/main/java/io/legado/app/ui/association/AddToBookshelfDialog.kt)\n* path类型解释: 书源,订阅源,替换规则,本地txt小说目录规则,在线朗读引擎,主题,阅读排版,添加到书架\n\n<a href=\"#readme\">\n    <img src=\"https://img.shields.io/badge/-返回顶部-orange.svg\" alt=\"#\" align=\"right\">\n</a>\n\n# Other-其他 [![](https://img.shields.io/badge/-Other-F5F5F5.svg)](#Other-其他-)\n##### 免责声明\nhttps://gedoor.github.io/Disclaimer\n\n##### 阅读3.0\n* [书源规则](https://mgz0227.github.io/The-tutorial-of-Legado/)\n* [更新日志](/app/src/main/assets/updateLog.md)\n* [帮助文档](/app/src/main/assets/web/help/md/appHelp.md)\n* [web端书架](https://github.com/gedoor/legado_web_bookshelf)\n* [web端源编辑](https://github.com/gedoor/legado_web_source_editor)\n\n<a href=\"#readme\">\n    <img src=\"https://img.shields.io/badge/-返回顶部-orange.svg\" alt=\"#\" align=\"right\">\n</a>\n\n# Grateful-感谢 [![](https://img.shields.io/badge/-Grateful-F5F5F5.svg)](#Grateful-感谢-)\n> * org.jsoup:jsoup\n> * cn.wanghaomiao:JsoupXpath\n> * com.jayway.jsonpath:json-path\n> * com.github.gedoor:rhino-android\n> * com.squareup.okhttp3:okhttp\n> * com.github.bumptech.glide:glide\n> * org.nanohttpd:nanohttpd\n> * org.nanohttpd:nanohttpd-websocket\n> * cn.bingoogolapple:bga-qrcode-zxing\n> * com.jaredrummler:colorpicker\n> * org.apache.commons:commons-text\n> * io.noties.markwon:core\n> * io.noties.markwon:image-glide\n> * com.hankcs:hanlp\n> * com.positiondev.epublib:epublib-core\n<a href=\"#readme\">\n    <img src=\"https://img.shields.io/badge/-返回顶部-orange.svg\" alt=\"#\" align=\"right\">\n</a>\n\n# Interface-界面 [![](https://img.shields.io/badge/-Interface-F5F5F5.svg)](#Interface-界面-)\n<img src=\"https://github.com/gedoor/gedoor.github.io/blob/master/static/img/legado/%E9%98%85%E8%AF%BB%E7%AE%80%E4%BB%8B1.jpg\" width=\"270\"><img src=\"https://github.com/gedoor/gedoor.github.io/blob/master/static/img/legado/%E9%98%85%E8%AF%BB%E7%AE%80%E4%BB%8B2.jpg\" width=\"270\"><img src=\"https://github.com/gedoor/gedoor.github.io/blob/master/static/img/legado/%E9%98%85%E8%AF%BB%E7%AE%80%E4%BB%8B3.jpg\" width=\"270\">\n<img src=\"https://github.com/gedoor/gedoor.github.io/blob/master/static/img/legado/%E9%98%85%E8%AF%BB%E7%AE%80%E4%BB%8B4.jpg\" width=\"270\"><img src=\"https://github.com/gedoor/gedoor.github.io/blob/master/static/img/legado/%E9%98%85%E8%AF%BB%E7%AE%80%E4%BB%8B5.jpg\" width=\"270\"><img src=\"https://github.com/gedoor/gedoor.github.io/blob/master/static/img/legado/%E9%98%85%E8%AF%BB%E7%AE%80%E4%BB%8B6.jpg\" width=\"270\">\n\n<a href=\"#readme\">\n    <img src=\"https://img.shields.io/badge/-返回顶部-orange.svg\" alt=\"#\" align=\"right\">\n</a>\n"
  },
  {
    "path": "api.md",
    "content": "# 阅读[API](/app/src/main/java/io/legado/app/api/controller)\n\n## 对于[Web](/app/src/main/java/io/legado/app/web/)的配置\n\n您需要先在设置中启用\"Web 服务\"。\n\n## 使用\n\n### Web\n\n以下说明假设您的操作在本机进行，且开放端口为1234。  \n如果您要从远程计算机访问[阅读]()，请将`127.0.0.1`替换成手机IP。\n\n#### 插入单个书源\n\n请求BODY内容为`JSON`字符串，  \n格式参考[这个文件](/app/src/main/java/io/legado/app/data/entities/BookSource.kt)\n\n```\nURL = http://127.0.0.1:1234/saveBookSource\nMethod = POST\n```\n\n#### 插入多个书源or订阅源\n\n请求BODY内容为`JSON`字符串，  \n格式参考[这个文件](/app/src/main/java/io/legado/app/data/entities/BookSource.kt)，**为数组格式**。\n\n```\nURL = http://127.0.0.1:1234/saveBookSources\nURL = http://127.0.0.1:1234/saveRssSources\nMethod = POST\n```\n\n#### 获取书源\n\n```\nURL = http://127.0.0.1:1234/getBookSource?url=xxx\nURL = http://127.0.0.1:1234/getRssSource?url=xxx\nMethod = GET\n``` \n\n#### 获取所有书源or订阅源\n\n```\nURL = http://127.0.0.1:1234/getBookSources\nURL = http://127.0.0.1:1234/getRssSources\nMethod = GET\n```\n\n#### 删除多个书源or订阅源\n\n请求BODY内容为`JSON`字符串，  \n格式参考[这个文件](/app/src/main/java/io/legado/app/data/entities/BookSource.kt)，**为数组格式**。\n\n```\nURL = http://127.0.0.1:1234/deleteBookSources\nURL = http://127.0.0.1:1234/deleteRssSources\nMethod = POST\n```\n\n#### 调试源\n\nkey为书源搜索关键词，tag为源链接\n\n```\nURL = ws://127.0.0.1:1235/bookSourceDebug\nURL = ws://127.0.0.1:1235/rssSourceDebug\nMessage = { key: [String], tag: [String] }\n```\n\n#### 获取替换规则\n\n```\nURL = http://127.0.0.1:1234/getReplaceRules\nMethod = GET\n```\n\n#### 替换规则管理\n\n请求BODY内容为`JSON`字符串，  \n替换规则参考[这个文件](/app/src/main/java/io/legado/app/data/entities/ReplaceRule.kt)。\n\n##### 删除\n\n```\nURL = http://127.0.0.1:1234/deleteReplaceRule\nMethod = POST\nBody = [ReplaceRule]\n```\n##### 插入\n\n```\nURL = http://127.0.0.1:1234/saveReplaceRule\nMethod = POST\nBody = [ReplaceRule]\n```\n\n##### 测试\n\n返回测试文本text替换结果\n\n```\nURL = http://127.0.0.1:1234/testReplaceRule\nMethod = POST\nBody = { rule: [ReplaceRule], text: [String] }\n```\n\n#### 搜索在线书籍\n\n若想获取对应的书籍的目录正文 请先**插入书籍**以启用缓存，如果试读后决定不添加到书籍，请**删除书籍**\n\n```\nURL = ws://127.0.0.1:1235/searchBook\nMessage = { key: [String] }\n```\n\n#### 插入书籍\n\n请求BODY内容为`JSON`字符串，  \n格式参考[这个文件](/app/src/main/java/io/legado/app/data/entities/Book.kt)。\n\n```\nURL = http://127.0.0.1:1234/saveBook\nMethod = POST\n```\n\n#### 删除书籍\n\n```\nURL = http://127.0.0.1:1234/deleteBook\nMethod = POST\n```\n\n#### 获取所有书籍\n\n```\nURL = http://127.0.0.1:1234/getBookshelf\nMethod = GET\n```\n\n获取APP内的所有书籍。\n\n#### 获取书籍章节列表\n\n```\nURL = http://127.0.0.1:1234/getChapterList?url=xxx\nMethod = GET\n```\n\n获取指定图书的章节列表。\n\n#### 获取书籍内容\n\n```\nURL = http://127.0.0.1:1234/getBookContent?url=xxx&index=1\nMethod = GET\n```\n\n获取指定图书的第`index`章节的文本内容。\n\n#### 获取封面\n\n```\nURL = http://127.0.0.1:1234/cover?path=xxxxx\nMethod = GET\n```\n\n#### 获取正文图片\n\n```\nURL = http://127.0.0.1:1234/image?url=${bookUrl}&path=${picUrl}&width=${width}\nMethod = GET\n```\n\n#### 保存书籍进度\n\n请求BODY内容为`JSON`字符串，  \n格式参考[这个文件](/app/src/main/java/io/legado/app/data/entities/BookProgress.kt)。\n\n```\nURL = http://127.0.0.1:1234/saveBookProgress\nMethod = POST\n```\n\n### [Content Provider](/app/src/main/java/io/legado/app/api/ReaderProvider.kt)\n\n\n* 需声明`io.legado.READ_WRITE`权限\n* `providerHost`为`包名.readerProvider`, 如`io.legado.app.release.readerProvider`,不同包的地址不同,防止冲突安装失败\n* 以下出现的`providerHost`请自行替换\n\n#### 插入单个书源or订阅源\n\n创建`Key=\"json\"`的`ContentValues`，内容为`JSON`字符串，  \n格式参考[这个文件](/app/src/main/java/io/legado/app/data/entities/BookSource.kt)\n\n```\nURL = content://providerHost/bookSource/insert\nURL = content://providerHost/rssSource/insert\nMethod = insert\n```\n\n#### 插入多个书源or订阅源\n\n创建`Key=\"json\"`的`ContentValues`，内容为`JSON`字符串，  \n格式参考[这个文件](/app/src/main/java/io/legado/app/data/entities/BookSource.kt)，**为数组格式**。\n\n```\nURL = content://providerHost/bookSources/insert\nURL = content://providerHost/rssSources/insert\nMethod = insert\n```\n\n#### 获取书源or订阅源\n\n获取指定URL对应的书源信息。  \n用`Cursor.getString(0)`取出返回结果。\n\n```\nURL = content://providerHost/bookSource/query?url=xxx\nURL = content://providerHost/rssSource/query?url=xxx\nMethod = query\n```\n\n#### 获取所有书源or订阅源\n\n获取APP内的所有订阅源。  \n用`Cursor.getString(0)`取出返回结果。\n\n```\nURL = content://providerHost/bookSources/query\nURL = content://providerHost/rssSources/query\nMethod = query\n```\n\n#### 删除多个书源or订阅源\n\n创建`Key=\"json\"`的`ContentValues`，内容为`JSON`字符串，  \n格式参考[这个文件](/app/src/main/java/io/legado/app/data/entities/BookSource.kt)，**为数组格式**。\n\n```\nURL = content://providerHost/bookSources/delete\nURL = content://providerHost/rssSources/delete\nMethod = delete\n```\n\n#### 插入书籍\n\n创建`Key=\"json\"`的`ContentValues`，内容为`JSON`字符串，  \n格式参考[这个文件](/app/src/main/java/io/legado/app/data/entities/Book.kt)。\n\n```\nURL = content://providerHost/book/insert\nMethod = insert\n```\n\n#### 获取所有书籍\n\n获取APP内的所有书籍。  \n用`Cursor.getString(0)`取出返回结果。\n\n```\nURL = content://providerHost/books/query\nMethod = query\n```\n\n#### 获取书籍章节列表\n\n获取指定图书的章节列表。   \n用`Cursor.getString(0)`取出返回结果。\n\n```\nURL = content://providerHost/book/chapter/query?url=xxx\nMethod = query\n```\n\n#### 获取书籍内容\n\n获取指定图书的第`index`章节的文本内容。     \n用`Cursor.getString(0)`取出返回结果。\n\n```\nURL = content://providerHost/book/content/query?url=xxx&index=1\nMethod = query\n```\n\n#### 获取封面\n\n```\nURL = content://providerHost/book/cover/query?path=xxxx\nMethod = query\n```\n"
  },
  {
    "path": "app/.gitignore",
    "content": "/build\n/so"
  },
  {
    "path": "app/build.gradle",
    "content": "plugins {\n//    id \"com.android.application\"\n//    id 'org.jetbrains.kotlin.android'\n//    id 'kotlin-parcelize'\n//    //id 'kotlin-kapt'\n//    id 'com.google.devtools.ksp'\n//    id \"com.google.gms.google-services\"\n\n    alias libs.plugins.android.application\n    alias libs.plugins.kotlin.android\n    alias libs.plugins.kotlin.parcelize\n    alias libs.plugins.room\n    alias libs.plugins.ksp\n    alias libs.plugins.google.services\n}\napply from: 'download.gradle'\n\nstatic def releaseTime() {\n    return new Date().format(\"yy.MMddHH\", TimeZone.getTimeZone(\"GMT+8\"))\n}\n\ndef name = \"legado\"\ndef version = \"3.\" + releaseTime()\ndef gitCommits = Integer.parseInt('git rev-list HEAD --count'.execute().text.trim())\n\nandroid {\n    compileSdk = compile_sdk_version\n    namespace = 'io.legado.app'\n    kotlin {\n        jvmToolchain {\n            languageVersion.set(JavaLanguageVersion.of(17))\n        }\n    }\n\n    signingConfigs {\n        if (project.hasProperty(\"RELEASE_STORE_FILE\")) {\n            myConfig {\n                storeFile file(RELEASE_STORE_FILE)\n                storePassword RELEASE_STORE_PASSWORD\n                keyAlias RELEASE_KEY_ALIAS\n                keyPassword RELEASE_KEY_PASSWORD\n                enableV1Signing = true\n                enableV2Signing = true\n                enableV3Signing = true\n                enableV4Signing = true\n            }\n        }\n    }\n    defaultConfig {\n        applicationId \"io.legado.app\"\n        minSdk 21\n        targetSdk 36\n        versionCode 10000 + gitCommits\n        versionName version\n        testInstrumentationRunner \"androidx.test.runner.AndroidJUnitRunner\"\n        project.ext.set(\"archivesBaseName\", name + \"_\" + version)\n\n        buildConfigField \"String\", \"Cronet_Version\", \"\\\"$CronetVersion\\\"\"\n        buildConfigField \"String\", \"Cronet_Main_Version\", \"\\\"$CronetMainVersion\\\"\"\n\n        javaCompileOptions {\n            annotationProcessorOptions {\n                arguments += [\n                        \"room.incremental\"     : \"true\",\n                        \"room.expandProjection\": \"true\",\n                        \"room.schemaLocation\"  : \"$projectDir/schemas\".toString()\n                ]\n            }\n        }\n    }\n    buildFeatures {\n        buildConfig = true\n        viewBinding = true\n    }\n    buildTypes {\n        release {\n            if (project.hasProperty(\"RELEASE_STORE_FILE\")) {\n                signingConfig signingConfigs.myConfig\n            }\n            applicationIdSuffix '.release'\n            if (getApplicationIdSuffix() == '.releaseA') {\n                manifestPlaceholders.put(\"app_name\", \"@string/app_name_a\")\n            } else {\n                manifestPlaceholders.put(\"app_name\", \"@string/app_name\")\n            }\n\n            minifyEnabled true\n            shrinkResources = true\n            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro', 'cronet-proguard-rules.pro'\n        }\n        debug {\n            if (project.hasProperty(\"RELEASE_STORE_FILE\")) {\n                signingConfig signingConfigs.myConfig\n            }\n            manifestPlaceholders.put(\"app_name\", \"@string/app_name\")\n\n            applicationIdSuffix '.debug'\n            versionNameSuffix 'debug'\n            minifyEnabled false\n            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro', 'cronet-proguard-rules.pro'\n        }\n    }\n    //noinspection GrDeprecatedAPIUsage\n    flavorDimensions = ['mode']\n    productFlavors {\n        app {\n            dimension \"mode\"\n            manifestPlaceholders.put(\"APP_CHANNEL_VALUE\", \"app\")\n        }\n    }\n\n    android.applicationVariants.configureEach { variant ->\n        variant.outputs.configureEach {\n            def flavor = variant.productFlavors[0].name\n            outputFileName = \"${name}_${flavor}_${defaultConfig.versionName}.apk\"\n        }\n    }\n\n\n    room {\n        schemaDirectory \"$projectDir/schemas\"\n    }\n    // 设定Room的KSP参数\n    ksp {\n        arg(\"room.incremental\", \"true\")\n        arg(\"room.expandProjection\", \"true\")\n        arg(\"room.generateKotlin\", \"false\")\n        //arg(\"room.schemaLocation\", \"$projectDir/schemas\")\n\n    }\n\n    compileOptions {\n        // Flag to enable support for the new language APIs\n        coreLibraryDesugaringEnabled = true\n        // Sets Java compatibility\n        sourceCompatibility JavaVersion.VERSION_17\n        targetCompatibility JavaVersion.VERSION_17\n    }\n    packaging {\n        resources.excludes.add('META-INF/*')\n    }\n\n    sourceSets {\n        // Adds exported schema location as test app assets.\n        androidTest.assets.srcDirs += files(\"$projectDir/schemas\".toString())\n    }\n    lint {\n        checkDependencies = true\n    }\n    tasks.withType(JavaCompile).tap {\n        configureEach {\n            //options.compilerArgs << \"-Xlint:unchecked\"\n        }\n    }\n}\n\ndependencies {\n    //compileOnly \"com.android.tools.build:gradle:$agp_version\"\n    //noinspection GradleDependency,GradlePackageUpdate\n    //coreLibraryDesugaring('com.android.tools:desugar_jdk_libs_nio:2.0.4')\n    coreLibraryDesugaring(libs.desugar)\n    testImplementation(libs.junit)\n    androidTestImplementation(libs.bundles.androidTest)\n    //kotlin\n    //noinspection GradleDependency,DifferentStdlibGradleVersion\n    implementation(libs.kotlin.stdlib)\n    //Kotlin反射\n    //noinspection GradleDependency,DifferentStdlibGradleVersion\n    //implementation(libs.kotlin.reflect)\n\n\n    //协程\n    //def coroutines_version = '1.7.3'\n    implementation(libs.bundles.coroutines)\n\n\n    //图像处理库Toolkit\n    implementation(libs.renderscript.intrinsics.replacement.toolkit)\n\n    //androidX\n    implementation(libs.core.ktx)\n    implementation(libs.appcompat.appcompat)\n    implementation(libs.activity.ktx)\n    implementation(libs.fragment.ktx)\n    implementation(libs.preference.ktx)\n    implementation(libs.androidx.constraintlayout)\n    implementation(libs.androidx.swiperefreshlayout)\n    implementation(libs.androidx.recyclerview)\n    implementation(libs.androidx.viewpager2)\n    implementation(libs.androidx.webkit)\n    implementation(libs.androidx.documentfile)\n\n    //google\n    implementation(libs.material)\n    implementation(libs.flexbox)\n    implementation(libs.gson)\n\n    //lifecycle\n    implementation(libs.lifecycle.common.java8)\n    implementation(libs.lifecycle.service)\n\n    //media\n    implementation(libs.media.media)\n    // For media playback using ExoPlayer\n    implementation(libs.media3.exoplayer)\n    // For loading data using the OkHttp network stack\n    implementation(libs.media3.datasource.okhttp)\n    // For exposing and controlling media sessions\n    //implementation \"androidx.media3:media3-session:$media3_version\"\n\n    //Splitties\n    implementation(libs.splitties.appctx)\n    implementation(libs.splitties.systemservices)\n    implementation(libs.splitties.views)\n\n    //room sql语句不高亮解决方法https://issuetracker.google.com/issues/234612964#comment6\n    implementation(libs.room.runtime)\n    implementation(libs.room.ktx)\n    //kapt(\"androidx.room:room-compiler:$room_version\")\n    ksp(libs.room.compiler)\n    androidTestImplementation(libs.room.testing)\n\n    //liveEventBus\n    implementation(libs.liveeventbus)\n\n    //规则相关\n    implementation(libs.jsoup)\n    implementation(libs.json.path)\n    implementation(libs.jsoupxpath)\n    implementation(project(path: ':modules:book'))\n    implementation(project(path: ':modules:rhino'))\n\n    //JS rhino\n\n    //网络\n    implementation(libs.okhttp)\n    implementation(fileTree(dir: 'cronetlib', include: ['*.jar', '*.aar']))\n    implementation(libs.protobuf.javalite)\n\n    //Glide\n    implementation(libs.glide.glide)\n    implementation(libs.glide.okhttp)\n    ksp(libs.glide.ksp)\n\n    //Svg\n    implementation(libs.androidsvg)\n    //Glide svg plugin\n    implementation(libs.glide.svg)\n\n    //webServer\n    implementation(libs.nanohttpd.nanohttpd)\n    implementation(libs.nanohttpd.websocket)\n\n    //二维码\n    //noinspection GradleDependency\n    implementation(libs.zxing.lite)\n\n    //颜色选择\n    implementation(libs.colorpicker)\n\n    //压缩解压\n    implementation libs.libarchive\n\n    //apache\n    implementation(libs.commons.text)\n\n    //MarkDown\n    implementation(libs.markwon.core)\n    implementation(libs.markwon.image.glide)\n    implementation(libs.markwon.ext.tables)\n    implementation(libs.markwon.html)\n\n    //转换繁体\n    implementation(libs.quick.chinese.transfer.core)\n\n    //加解密类库,有些书源使用\n    //noinspection GradleDependency,GradlePackageUpdate\n    implementation(libs.hutool.crypto)\n\n    //firebase, 崩溃统计和性能统计\n    implementation platform(libs.firebase.bom)\n    implementation libs.firebase.analytics\n    implementation libs.firebase.perf\n\n    implementation libs.glide.recyclerview\n\n    //LeakCanary, 内存泄露检测\n    //debugImplementation('com.squareup.leakcanary:leakcanary-android:2.7')\n\n    //com.github.AmrDeveloper:CodeView代码编辑已集成到应用内\n    //epubLib集成到应用内\n}\n"
  },
  {
    "path": "app/cronet-proguard-rules.pro",
    "content": "# -------- Config Path: base/android/proguard/shared_with_cronet.flags --------\n# Copyright 2016 The Chromium Authors\n# Use of this source code is governed by a BSD-style license that can be\n# found in the LICENSE file.\n\n# Contains flags that we want to apply not only to Chromium APKs, but also to\n# third-party apps that bundle the Cronet library.\n\n# WARNING: rules in this file are applied to entire third-party APKs, not just\n# Chromium code. They MUST be scoped appropriately to avoid side effects on app\n# code that we do not own.\n\n# Keep all CREATOR fields within Parcelable that are kept.\n-keepclassmembers class !cr_allowunused,org.chromium.** implements android.os.Parcelable {\n  public static *** CREATOR;\n}\n\n# Don't obfuscate Parcelables as they might be marshalled outside Chrome.\n# If we annotated all Parcelables that get put into Bundles other than\n# for saveInstanceState (e.g. PendingIntents), then we could actually keep the\n# names of just those ones. For now, we'll just keep them all.\n-keepnames,allowaccessmodification class !cr_allowunused,org.chromium.** implements android.os.Parcelable {}\n\n# Keep all enum values and valueOf methods. See\n# http://proguard.sourceforge.net/index.html#manual/examples.html\n# for the reason for this. Also, see http://crbug.com/248037.\n-keepclassmembers enum !cr_allowunused,org.chromium.** {\n    public static **[] values();\n}\n\n# Required to remove fields until b/274802355 is resolved.\n-assumevalues class !cr_allowunused,** {\n  final org.chromium.base.ThreadUtils$ThreadChecker * return _NONNULL_;\n}\n# -------- Config Path: build/android/chromium_annotations.flags --------\n# Copyright 2022 The Chromium Authors\n# Use of this source code is governed by a BSD-style license that can be\n# found in the LICENSE file.\n\n# Contains flags related to annotations in //build/android that can be safely\n# shared with Cronet, and thus would be appropriate for third-party apps to\n# include.\n\n# Keep all annotation related attributes that can affect runtime\n-keepattributes RuntimeVisible*Annotations\n-keepattributes AnnotationDefault\n\n# Keeps for class level annotations.\n-keep,allowaccessmodification @org.chromium.build.annotations.UsedByReflection class ** {}\n\n# Keeps for method level annotations.\n-keepclasseswithmembers,allowaccessmodification class ** {\n  @org.chromium.build.annotations.UsedByReflection <methods>;\n}\n-keepclasseswithmembers,allowaccessmodification class ** {\n  @org.chromium.build.annotations.UsedByReflection <fields>;\n}\n\n# Never inline classes, methods, or fields with this annotation, but allow\n# shrinking and obfuscation.\n# Relevant to fields when they are needed to store strong references to objects\n# that are held as weak references by native code.\n-if @org.chromium.build.annotations.DoNotInline class * {\n    *** *(...);\n}\n-keep,allowobfuscation,allowaccessmodification class <1> {\n    *** <2>(...);\n}\n-keepclassmembers,allowobfuscation,allowaccessmodification class * {\n   @org.chromium.build.annotations.DoNotInline <methods>;\n}\n-keepclassmembers,allowobfuscation,allowaccessmodification class * {\n   @org.chromium.build.annotations.DoNotInline <fields>;\n}\n\n-alwaysinline class * {\n    @org.chromium.build.annotations.AlwaysInline *;\n}\n\n# Keep all logs (Log.VERBOSE = 2). R8 does not allow setting to 0.\n-maximumremovedandroidloglevel 1 class ** {\n   @org.chromium.build.annotations.DoNotStripLogs <methods>;\n}\n-maximumremovedandroidloglevel 1 @org.chromium.build.annotations.DoNotStripLogs class ** {\n   <methods>;\n}\n\n# Never merge classes horizontally or vertically with this annotation.\n# Relevant to classes being used as a key in maps or sets.\n-keep,allowaccessmodification,allowobfuscation,allowshrinking @org.chromium.build.annotations.DoNotClassMerge class *\n\n# Mark members annotated with IdentifierNameString as identifier name strings\n-identifiernamestring class * {\n    @org.chromium.build.annotations.IdentifierNameString *;\n}\n\n# Mark fields with this to help R8 figure out that they cannot be null.\n# Use assumevalues in addition to assumenosideeffects block because Google3 proguard cannot parse\n# assumenosideeffects blocks which overwrite return value.\n-assumevalues class ** {\n  @org.chromium.build.annotations.AssumeNonNull *** *(...) return _NONNULL_;\n}\n-assumenosideeffects class ** {\n  @org.chromium.build.annotations.AssumeNonNull *** *(...);\n}\n-assumevalues class ** {\n  @org.chromium.build.annotations.AssumeNonNull *** * return _NONNULL_;\n}\n-assumenosideeffects class ** {\n  @org.chromium.build.annotations.AssumeNonNull *** *;\n}\n# -------- Config Path: components/cronet/android/cronet_impl_common_proguard.cfg --------\n# Proguard config for apps that depend on cronet_impl_common_java.jar.\n\n# Used through reflection by the API code to figure out the version of the impl\n# code it's talking to.\n-keep public class org.chromium.net.impl.ImplVersion {\n  public *;\n}\n\n-dontwarn com.google.errorprone.annotations.DoNotMock\n# -------- Config Path: components/cronet/android/cronet_impl_native_proguard.cfg --------\n# Proguard config for apps that depend on cronet_impl_native_java.jar.\n\n# This constructor is called using the reflection from Cronet API (cronet_api.jar).\n-keep class org.chromium.net.impl.NativeCronetProvider {\n    public <init>(android.content.Context);\n}\n\n# While Chrome doesn't need to keep these with their version of R8, some cronet\n# users may be on other optimizers which still require the annotation to be\n# kept in order for the keep rules to work.\n-keep @interface org.chromium.build.annotations.DoNotInline\n-keep @interface org.chromium.build.annotations.UsedByReflection\n-keep @interface org.chromium.build.annotations.IdentifierNameString\n-keep @interface org.jni_zero.AccessedByNative\n-keep @interface org.jni_zero.CalledByNative\n-keep @interface org.jni_zero.CalledByNativeUnchecked\n\n# Suppress unnecessary warnings.\n-dontnote org.chromium.net.ProxyChangeListener$ProxyReceiver\n-dontnote org.chromium.net.AndroidKeyStore\n# Needs 'void setTextAppearance(int)' (API level 23).\n-dontwarn org.chromium.base.ApiCompatibilityUtils\n# Needs 'boolean onSearchRequested(android.view.SearchEvent)' (API level 23).\n-dontwarn org.chromium.base.WindowCallbackWrapper\n\n# Generated for chrome apk and not included into cronet.\n-dontwarn org.chromium.base.multidex.ChromiumMultiDexInstaller\n-dontwarn org.chromium.base.library_loader.LibraryLoader\n-dontwarn org.chromium.base.SysUtils\n-dontwarn org.chromium.build.NativeLibraries\n\n# Objects of this type are passed around by native code, but the class\n# is never used directly by native code. Since the class is not loaded, it does\n# not need to be preserved as an entry point.\n-dontnote org.chromium.net.UrlRequest$ResponseHeadersMap\n# https://android.googlesource.com/platform/sdk/+/marshmallow-mr1-release/files/proguard-android.txt#54\n-dontwarn android.support.**\n\n# This class should be explicitly kept to avoid failure if\n# class/merging/horizontal proguard optimization is enabled.\n-keep class org.chromium.base.CollectionUtil\n\n# Skip protobuf runtime check for isOnAndroidDevice().\n# A nice-to-have optimization shamelessly stolen from //third_party/protobuf/java/lite/proguard.pgcfg.\n-assumevalues class com.google.protobuf.Android {\n    static boolean ASSUME_ANDROID return true;\n}\n\n# See crbug.com/1440987. We must keep every native that we are manually\n# registering. If Cronet bumps its min-sdk past 21, we may be able to move to\n# automatic JNI registration.\n-keepclasseswithmembers,includedescriptorclasses,allowaccessmodification class org.chromium.**,J.N {\n  native <methods>;\n}\n\n# Protobuf builder uses reflection so make sure ProGuard leaves it alone. See\n# https://crbug.com/1395764.\n# Note that we can't simply use the rule from\n# //third_party/protobuf/java/lite/proguard.pgcfg, because some users who\n# consume our ProGuard rules do not want all their protos to be kept. Instead,\n# use a more specific rule that covers Chromium protos only.\n-keepclassmembers class org.chromium.** extends com.google.protobuf.GeneratedMessageLite {\n  <fields>;\n}\n# -------- Config Path: components/cronet/android/cronet_shared_proguard.cfg --------\n# Proguard config for apps that depend on cronet_shared_java.jar (which should\n# be all apps that depend on any part of Cronet)\n\n# Part of the Android System SDK, so ProGuard won't be able to resolve it if\n# running against the standard SDK.\n-dontwarn android.util.StatsEvent\n-dontwarn android.util.StatsEvent$*\n# There is also an undefined reference to android.util.StatsLog.write(), which\n# R8 appears to be fine with but other processors (e.g. internal Google\n# ProGuard) may not be. See b/315269496.\n-dontwarn android.util.StatsLog\n# -------- Config Path: third_party/androidx/androidx_annotations.flags --------\n# Copyright 2023 The Chromium Authors\n# Use of this source code is governed by a BSD-style license that can be\n# found in the LICENSE file.\n\n-keep @androidx.annotation.Keep class *\n-keepclasseswithmembers,allowaccessmodification class * {\n  @androidx.annotation.Keep <fields>;\n}\n-keepclasseswithmembers,allowaccessmodification class * {\n  @androidx.annotation.Keep <methods>;\n}\n# -------- Config Path: third_party/jni_zero/proguard.flags --------\n# Copyright 2023 The Chromium Authors\n# Use of this source code is governed by a BSD-style license that can be\n# found in the LICENSE file.\n\n# Keeps for method level annotations.\n-keepclasseswithmembers,allowaccessmodification class ** {\n  @org.jni_zero.AccessedByNative <fields>;\n}\n-keepclasseswithmembers,includedescriptorclasses,allowaccessmodification class ** {\n  @org.jni_zero.CalledByNative <methods>;\n}\n-keepclasseswithmembers,includedescriptorclasses,allowaccessmodification class ** {\n  @org.jni_zero.CalledByNativeUnchecked <methods>;\n}\n\n# Allow unused native methods to be removed, but prevent renaming on those that\n# are kept.\n# TODO(crbug.com/315973491): Restrict the broad scope of this rule.\n-keepclasseswithmembernames,includedescriptorclasses,allowaccessmodification class ** {\n  native <methods>;\n}\n"
  },
  {
    "path": "app/download.gradle",
    "content": "import java.security.MessageDigest\n\napply plugin: 'de.undercouch.download'\n\ndef BASE_PATH = \"https://storage.googleapis.com/chromium-cronet/android/\" + CronetVersion + \"/Release/cronet/\"\ndef assetsDir = projectDir.toString() + \"/src/main/assets\"\ndef libPath = projectDir.toString() + \"/cronetlib\"\ndef soPath = projectDir.toString() + \"/so\"\n\n/**\n * 从文件生成MD5\n * @param file\n * @return\n */\nstatic def generateMD5(final file) {\n    MessageDigest digest = MessageDigest.getInstance(\"MD5\")\n    file.withInputStream() { is ->\n        byte[] buffer = new byte[1024]\n        //noinspection GroovyUnusedAssignment\n        int numRead = 0\n        while ((numRead = is.read(buffer)) > 0) {\n            digest.update(buffer, 0, numRead)\n        }\n    }\n    return String.format(\"%032x\", new BigInteger(1, digest.digest())).toLowerCase()\n}\n\n/**\n * 下载Cronet相关的jar\n */\ntasks.register('downloadJar', Download) {\n    src([\n            BASE_PATH + \"cronet_api.jar\",\n            BASE_PATH + \"cronet_impl_common_java.jar\",\n            BASE_PATH + \"cronet_impl_native_java.jar\",\n            BASE_PATH + \"cronet_impl_platform_java.jar\",\n            BASE_PATH + \"cronet_shared_java.jar\"\n    ])\n    dest libPath\n    overwrite true\n    onlyIfModified false\n}\n/**\n * 下载Cronet的arm64-v8a so\n */\ntasks.register('downloadARM64', Download) {\n    src BASE_PATH + \"libs/arm64-v8a/libcronet.\" + CronetVersion + \".so\"\n    dest soPath + \"/arm64-v8a.so\"\n    overwrite true\n    onlyIfModified true\n}\n/**\n * 下载Cronet的armeabi-v7a so\n */\ntasks.register('downloadARMv7', Download) {\n    src BASE_PATH + \"libs/armeabi-v7a/libcronet.\" + CronetVersion + \".so\"\n    dest soPath + \"/armeabi-v7a.so\"\n    overwrite true\n    onlyIfModified true\n}\n/**\n * 下载Cronet的x86_64 so\n */\ntasks.register('downloadX86_64', Download) {\n    src BASE_PATH + \"libs/x86_64/libcronet.\" + CronetVersion + \".so\"\n    dest soPath + \"/x86_64.so\"\n    overwrite true\n    onlyIfModified true\n}\n/**\n * 下载Cronet的x86 so\n */\ntasks.register('downloadX86', Download) {\n    src BASE_PATH + \"libs/x86/libcronet.\" + CronetVersion + \".so\"\n    dest soPath + \"/x86.so\"\n    overwrite true\n    onlyIfModified true\n}\n\n/**\n * 更新Cronet版本时执行这个task\n * 先更改gradle.properties 里面的版本号，然后再执行\n * gradlew app:downloadCronet\n */\ntasks.register('downloadCronet') {\n    dependsOn downloadJar, downloadARM64, downloadARMv7, downloadX86_64, downloadX86\n\n    doLast {\n        StringBuilder sb = new StringBuilder(\"{\")\n        def files = new File(soPath).listFiles()\n        for (File file : files) {\n            println file.name.replace(\".so\", \"\")\n            sb.append(\"\\\"\").append(file.name.replace(\".so\", \"\")).append(\"\\\":\\\"\").append(generateMD5(file)).append(\"\\\",\")\n        }\n        sb.append(\"\\\"version\\\":\\\"\").append(CronetVersion).append(\"\\\"}\")\n\n        println sb.toString()\n\n        println assetsDir\n        def f1 = new File(assetsDir + \"/cronet.json\")\n        if (!f1.exists()) {\n            f1.parentFile.mkdirs()\n            f1.createNewFile()\n        }\n        f1.text = sb.toString()\n\n    }\n\n\n}\n\n\n"
  },
  {
    "path": "app/google-services.json",
    "content": "{\n  \"project_info\": {\n    \"project_number\": \"453392274790\",\n    \"firebase_url\": \"https://legado-fca69.firebaseio.com\",\n    \"project_id\": \"legado-fca69\",\n    \"storage_bucket\": \"legado-fca69.appspot.com\"\n  },\n  \"client\": [\n    {\n      \"client_info\": {\n        \"mobilesdk_app_id\": \"1:453392274790:android:c4eac14b1410eec5f624a7\",\n        \"android_client_info\": {\n          \"package_name\": \"io.legado.app.debug\"\n        }\n      },\n      \"oauth_client\": [\n        {\n          \"client_id\": \"453392274790-hnbpatpce9hbjiggj76hgo7queu86atq.apps.googleusercontent.com\",\n          \"client_type\": 3\n        }\n      ],\n      \"api_key\": [\n        {\n          \"current_key\": \"AIzaSyD90mfNLhA7cAzzI9SonpSz5mrF5BnmyJA\"\n        }\n      ],\n      \"services\": {\n        \"appinvite_service\": {\n          \"other_platform_oauth_client\": [\n            {\n              \"client_id\": \"453392274790-hnbpatpce9hbjiggj76hgo7queu86atq.apps.googleusercontent.com\",\n              \"client_type\": 3\n            }\n          ]\n        }\n      }\n    },\n    {\n      \"client_info\": {\n        \"mobilesdk_app_id\": \"1:453392274790:android:c1481c1c3d3f51eff624a7\",\n        \"android_client_info\": {\n          \"package_name\": \"io.legado.app.release\"\n        }\n      },\n      \"oauth_client\": [\n        {\n          \"client_id\": \"453392274790-trrgennt5njr1lhil1sgtf0ogcgd38fo.apps.googleusercontent.com\",\n          \"client_type\": 1,\n          \"android_info\": {\n            \"package_name\": \"io.legado.app.release\",\n            \"certificate_hash\": \"fd67dba87b7b761631266f19ddde249054aac5c1\"\n          }\n        },\n        {\n          \"client_id\": \"453392274790-hnbpatpce9hbjiggj76hgo7queu86atq.apps.googleusercontent.com\",\n          \"client_type\": 3\n        }\n      ],\n      \"api_key\": [\n        {\n          \"current_key\": \"AIzaSyD90mfNLhA7cAzzI9SonpSz5mrF5BnmyJA\"\n        }\n      ],\n      \"services\": {\n        \"appinvite_service\": {\n          \"other_platform_oauth_client\": [\n            {\n              \"client_id\": \"453392274790-hnbpatpce9hbjiggj76hgo7queu86atq.apps.googleusercontent.com\",\n              \"client_type\": 3\n            }\n          ]\n        }\n      }\n    },\n    {\n      \"client_info\": {\n        \"mobilesdk_app_id\": \"1:453392274790:android:b891abd2331577dff624a7\",\n        \"android_client_info\": {\n          \"package_name\": \"io.legado.play.release\"\n        }\n      },\n      \"oauth_client\": [\n        {\n          \"client_id\": \"453392274790-f8sjn6ohs72rg1dvp0pdvk42nkq54p0k.apps.googleusercontent.com\",\n          \"client_type\": 1,\n          \"android_info\": {\n            \"package_name\": \"io.legado.play.release\",\n            \"certificate_hash\": \"00819ace9891386e535967cbafd6a88f3797bd5b\"\n          }\n        },\n        {\n          \"client_id\": \"453392274790-hnbpatpce9hbjiggj76hgo7queu86atq.apps.googleusercontent.com\",\n          \"client_type\": 3\n        }\n      ],\n      \"api_key\": [\n        {\n          \"current_key\": \"AIzaSyD90mfNLhA7cAzzI9SonpSz5mrF5BnmyJA\"\n        }\n      ],\n      \"services\": {\n        \"appinvite_service\": {\n          \"other_platform_oauth_client\": [\n            {\n              \"client_id\": \"453392274790-hnbpatpce9hbjiggj76hgo7queu86atq.apps.googleusercontent.com\",\n              \"client_type\": 3\n            }\n          ]\n        }\n      }\n    },\n    {\n      \"client_info\": {\n        \"mobilesdk_app_id\": \"1:453392274790:android:b891abd2331577dff624a7\",\n        \"android_client_info\": {\n          \"package_name\": \"io.legado.play.debug\"\n        }\n      },\n      \"oauth_client\": [\n        {\n          \"client_id\": \"453392274790-f8sjn6ohs72rg1dvp0pdvk42nkq54p0k.apps.googleusercontent.com\",\n          \"client_type\": 1,\n          \"android_info\": {\n            \"package_name\": \"io.legado.play.debug\",\n            \"certificate_hash\": \"00819ace9891386e535967cbafd6a88f3797bd5b\"\n          }\n        },\n        {\n          \"client_id\": \"453392274790-hnbpatpce9hbjiggj76hgo7queu86atq.apps.googleusercontent.com\",\n          \"client_type\": 3\n        }\n      ],\n      \"api_key\": [\n        {\n          \"current_key\": \"AIzaSyD90mfNLhA7cAzzI9SonpSz5mrF5BnmyJA\"\n        }\n      ],\n      \"services\": {\n        \"appinvite_service\": {\n          \"other_platform_oauth_client\": [\n            {\n              \"client_id\": \"453392274790-hnbpatpce9hbjiggj76hgo7queu86atq.apps.googleusercontent.com\",\n              \"client_type\": 3\n            }\n          ]\n        }\n      }\n    }\n  ],\n  \"configuration_version\": \"1\"\n}"
  },
  {
    "path": "app/proguard-rules.pro",
    "content": "# Add project specific ProGuard rules here.\n# You can control the set of applied configuration files using the\n# proguardFiles setting in build.gradle.\n#\n# For more details, see\n#   http://developer.android.com/guide/developing/tools/proguard.html\n\n# If your project uses WebView with JS, uncomment the following\n# and specify the fully qualified class name to the JavaScript interface\n# class:\n#-keepclassmembers class fqcn.of.javascript.interface.for.webview {\n#   public *;\n#}\n\n# Uncomment this to preserve the line number information for\n# debugging stack traces.\n#-keepattributes SourceFile,LineNumberTable\n\n# If you keep the line number information, uncomment this to\n# hide the original source file name.\n#-renamesourcefileattribute SourceFile\n# 混合时不使用大小写混合，混合后的类名为小写\n-dontusemixedcaseclassnames\n\n# 这句话能够使我们的项目混淆后产生映射文件\n# 包含有类名->混淆后类名的映射关系\n-verbose\n\n# 保留Annotation不混淆\n-keepattributes *Annotation*,InnerClasses\n\n# 避免混淆泛型\n-keepattributes Signature\n\n# 指定混淆是采用的算法，后面的参数是一个过滤器\n# 这个过滤器是谷歌推荐的算法，一般不做更改\n-optimizations !code/simplification/cast,!field/*,!class/merging/*\n\n-flattenpackagehierarchy\n\n#############################################\n#\n# Android开发中一些需要保留的公共部分\n#\n#############################################\n# 屏蔽错误Unresolved class name\n#noinspection ShrinkerUnresolvedReference\n\n# 移除Log类打印各个等级日志的代码，打正式包的时候可以做为禁log使用，这里可以作为禁止log打印的功能使用\n# 记得proguard-android.txt中一定不要加-dontoptimize才起作用\n# 另外的一种实现方案是通过BuildConfig.DEBUG的变量来控制\n-assumenosideeffects class android.util.Log {\n    public static int v(...);\n    public static int i(...);\n    public static int w(...);\n    public static int d(...);\n    public static int e(...);\n}\n\n# 保持js引擎调用的java类\n-keep class * extends io.legado.app.help.JsExtensions{*;}\n# 数据类\n-keep class **.data.entities.**{*;}\n# hutool-core hutool-crypto\n-keep class\n!cn.hutool.core.util.RuntimeUtil,\n!cn.hutool.core.util.ClassLoaderUtil,\n!cn.hutool.core.util.ReflectUtil,\n!cn.hutool.core.util.SerializeUtil,\n!cn.hutool.core.util.ClassUtil,\ncn.hutool.core.codec.**,\ncn.hutool.core.util.**{*;}\n-keep class cn.hutool.crypto.**{*;}\n-dontwarn cn.hutool.**\n# 缓存 Cookie\n-keep class **.help.http.CookieStore{*;}\n-keep class **.help.CacheManager{*;}\n# StrResponse\n-keep class **.help.http.StrResponse{*;}\n\n# markwon\n-dontwarn org.commonmark.ext.gfm.**\n\n-keep class okhttp3.*{*;}\n-keep class okio.*{*;}\n-keep class com.jayway.jsonpath.*{*;}\n\n# LiveEventBus\n-keepclassmembers class androidx.lifecycle.LiveData {\n    *** mObservers;\n    *** mActiveCount;\n}\n-keepclassmembers class androidx.arch.core.internal.SafeIterableMap {\n    *** size();\n    *** putIfAbsent(...);\n}\n\n## ChangeBookSourceDialog initNavigationView\n-keepclassmembers class androidx.appcompat.widget.Toolbar {\n    *** mNavButtonView;\n}\n\n# MenuExtensions applyOpenTint\n-keepnames class androidx.appcompat.view.menu.SubMenuBuilder\n-keep class androidx.appcompat.view.menu.MenuBuilder {\n    *** setOptionalIconsVisible(...);\n    *** getNonActionItems();\n}\n\n# FileDocExtensions.kt treeDocumentFileConstructor\n-keep class androidx.documentfile.provider.TreeDocumentFile {\n    <init>(...);\n}\n\n# JsoupXpath\n-keep,allowobfuscation class * implements org.seimicrawler.xpath.core.AxisSelector{*;}\n-keep,allowobfuscation class * implements org.seimicrawler.xpath.core.NodeTest{*;}\n-keep,allowobfuscation class * implements org.seimicrawler.xpath.core.Function{*;}\n\n## JSOUP\n-keep class org.jsoup.**{*;}\n-dontwarn org.jspecify.annotations.NullMarked\n\n## ExoPlayer 反射设置ua 保证该私有变量不被混淆\n-keepclassmembers class androidx.media3.datasource.cache.CacheDataSource$Factory {\n    *** upstreamDataSourceFactory;\n}\n## ExoPlayer 如果还不能播放就取消注释这个\n# -keep class com.google.android.exoplayer2.** {*;}\n\n## 对外提供api\n-keep class io.legado.app.api.ReturnData{*;}\n\n# Cronet\n-keepclassmembers class org.chromium.net.X509Util {\n    *** sDefaultTrustManager;\n    *** sTestTrustManager;\n}\n\n# Throwable\n-keepnames class * extends java.lang.Throwable\n-keepclassmembernames,allowobfuscation class * extends java.lang.Throwable{*;}\n"
  },
  {
    "path": "app/schemas/io.legado.app.data.AppDatabase/1.json",
    "content": "{\n  \"formatVersion\": 1,\n  \"database\": {\n    \"version\": 1,\n    \"identityHash\": \"d9ed367fc7241a61e9f770d416c4f887\",\n    \"entities\": [\n      {\n        \"tableName\": \"books\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookUrl` TEXT NOT NULL, `tocUrl` TEXT NOT NULL, `origin` TEXT NOT NULL, `originName` TEXT NOT NULL, `name` TEXT NOT NULL, `author` TEXT NOT NULL, `kind` TEXT, `customTag` TEXT, `coverUrl` TEXT, `customCoverUrl` TEXT, `intro` TEXT, `customIntro` TEXT, `charset` TEXT, `type` INTEGER NOT NULL, `group` INTEGER NOT NULL, `latestChapterTitle` TEXT, `latestChapterTime` INTEGER NOT NULL, `lastCheckTime` INTEGER NOT NULL, `lastCheckCount` INTEGER NOT NULL, `totalChapterNum` INTEGER NOT NULL, `durChapterTitle` TEXT, `durChapterIndex` INTEGER NOT NULL, `durChapterPos` INTEGER NOT NULL, `durChapterTime` INTEGER NOT NULL, `wordCount` TEXT, `canUpdate` INTEGER NOT NULL, `order` INTEGER NOT NULL, `originOrder` INTEGER NOT NULL, `useReplaceRule` INTEGER NOT NULL, `variable` TEXT, PRIMARY KEY(`bookUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"tocUrl\",\n            \"columnName\": \"tocUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"originName\",\n            \"columnName\": \"originName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"author\",\n            \"columnName\": \"author\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"kind\",\n            \"columnName\": \"kind\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customTag\",\n            \"columnName\": \"customTag\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"coverUrl\",\n            \"columnName\": \"coverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customCoverUrl\",\n            \"columnName\": \"customCoverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"intro\",\n            \"columnName\": \"intro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customIntro\",\n            \"columnName\": \"customIntro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"charset\",\n            \"columnName\": \"charset\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"group\",\n            \"columnName\": \"group\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"latestChapterTitle\",\n            \"columnName\": \"latestChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"latestChapterTime\",\n            \"columnName\": \"latestChapterTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"lastCheckTime\",\n            \"columnName\": \"lastCheckTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"lastCheckCount\",\n            \"columnName\": \"lastCheckCount\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"totalChapterNum\",\n            \"columnName\": \"totalChapterNum\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"durChapterTitle\",\n            \"columnName\": \"durChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"durChapterIndex\",\n            \"columnName\": \"durChapterIndex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"durChapterPos\",\n            \"columnName\": \"durChapterPos\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"durChapterTime\",\n            \"columnName\": \"durChapterTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"wordCount\",\n            \"columnName\": \"wordCount\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"canUpdate\",\n            \"columnName\": \"canUpdate\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"originOrder\",\n            \"columnName\": \"originOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"useReplaceRule\",\n            \"columnName\": \"useReplaceRule\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"bookUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_books_bookUrl\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"bookUrl\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_books_bookUrl` ON `${TABLE_NAME}` (`bookUrl`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"book_groups\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`groupId` INTEGER NOT NULL, `groupName` TEXT NOT NULL, `order` INTEGER NOT NULL, PRIMARY KEY(`groupId`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"groupId\",\n            \"columnName\": \"groupId\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"groupName\",\n            \"columnName\": \"groupName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"groupId\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"book_sources\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookSourceName` TEXT NOT NULL, `bookSourceGroup` TEXT, `bookSourceUrl` TEXT NOT NULL, `bookSourceType` INTEGER NOT NULL, `bookUrlPattern` TEXT, `customOrder` INTEGER NOT NULL, `enabled` INTEGER NOT NULL, `enabledExplore` INTEGER NOT NULL, `header` TEXT, `loginUrl` TEXT, `lastUpdateTime` INTEGER NOT NULL, `weight` INTEGER NOT NULL, `exploreUrl` TEXT, `ruleExplore` TEXT, `searchUrl` TEXT, `ruleSearch` TEXT, `ruleBookInfo` TEXT, `ruleToc` TEXT, `ruleContent` TEXT, PRIMARY KEY(`bookSourceUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookSourceName\",\n            \"columnName\": \"bookSourceName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookSourceGroup\",\n            \"columnName\": \"bookSourceGroup\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"bookSourceUrl\",\n            \"columnName\": \"bookSourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookSourceType\",\n            \"columnName\": \"bookSourceType\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookUrlPattern\",\n            \"columnName\": \"bookUrlPattern\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enabled\",\n            \"columnName\": \"enabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enabledExplore\",\n            \"columnName\": \"enabledExplore\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"header\",\n            \"columnName\": \"header\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUrl\",\n            \"columnName\": \"loginUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"lastUpdateTime\",\n            \"columnName\": \"lastUpdateTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"weight\",\n            \"columnName\": \"weight\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"exploreUrl\",\n            \"columnName\": \"exploreUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleExplore\",\n            \"columnName\": \"ruleExplore\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"searchUrl\",\n            \"columnName\": \"searchUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleSearch\",\n            \"columnName\": \"ruleSearch\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleBookInfo\",\n            \"columnName\": \"ruleBookInfo\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleToc\",\n            \"columnName\": \"ruleToc\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleContent\",\n            \"columnName\": \"ruleContent\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"bookSourceUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_book_sources_bookSourceUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"bookSourceUrl\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_book_sources_bookSourceUrl` ON `${TABLE_NAME}` (`bookSourceUrl`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"chapters\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`url` TEXT NOT NULL, `title` TEXT NOT NULL, `bookUrl` TEXT NOT NULL, `index` INTEGER NOT NULL, `resourceUrl` TEXT, `tag` TEXT, `start` INTEGER, `end` INTEGER, `variable` TEXT, PRIMARY KEY(`url`, `bookUrl`), FOREIGN KEY(`bookUrl`) REFERENCES `books`(`bookUrl`) ON UPDATE NO ACTION ON DELETE CASCADE )\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"index\",\n            \"columnName\": \"index\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"resourceUrl\",\n            \"columnName\": \"resourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"tag\",\n            \"columnName\": \"tag\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"start\",\n            \"columnName\": \"start\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"end\",\n            \"columnName\": \"end\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"url\",\n            \"bookUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_chapters_bookUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"bookUrl\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_chapters_bookUrl` ON `${TABLE_NAME}` (`bookUrl`)\"\n          },\n          {\n            \"name\": \"index_chapters_bookUrl_index\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"bookUrl\",\n              \"index\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_chapters_bookUrl_index` ON `${TABLE_NAME}` (`bookUrl`, `index`)\"\n          }\n        ],\n        \"foreignKeys\": [\n          {\n            \"table\": \"books\",\n            \"onDelete\": \"CASCADE\",\n            \"onUpdate\": \"NO ACTION\",\n            \"columns\": [\n              \"bookUrl\"\n            ],\n            \"referencedColumns\": [\n              \"bookUrl\"\n            ]\n          }\n        ]\n      },\n      {\n        \"tableName\": \"replace_rules\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL, `group` TEXT, `pattern` TEXT NOT NULL, `replacement` TEXT NOT NULL, `scope` TEXT, `isEnabled` INTEGER NOT NULL, `isRegex` INTEGER NOT NULL, `sortOrder` INTEGER NOT NULL)\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"group\",\n            \"columnName\": \"group\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"pattern\",\n            \"columnName\": \"pattern\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"replacement\",\n            \"columnName\": \"replacement\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"scope\",\n            \"columnName\": \"scope\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"isEnabled\",\n            \"columnName\": \"isEnabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"isRegex\",\n            \"columnName\": \"isRegex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"sortOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"id\"\n          ],\n          \"autoGenerate\": true\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_replace_rules_id\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"id\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_replace_rules_id` ON `${TABLE_NAME}` (`id`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"searchBooks\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookUrl` TEXT NOT NULL, `origin` TEXT NOT NULL, `originName` TEXT NOT NULL, `type` INTEGER NOT NULL, `name` TEXT NOT NULL, `author` TEXT NOT NULL, `kind` TEXT, `coverUrl` TEXT, `intro` TEXT, `wordCount` TEXT, `latestChapterTitle` TEXT, `tocUrl` TEXT NOT NULL, `time` INTEGER NOT NULL, `variable` TEXT, `originOrder` INTEGER NOT NULL, PRIMARY KEY(`bookUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"originName\",\n            \"columnName\": \"originName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"author\",\n            \"columnName\": \"author\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"kind\",\n            \"columnName\": \"kind\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"coverUrl\",\n            \"columnName\": \"coverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"intro\",\n            \"columnName\": \"intro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"wordCount\",\n            \"columnName\": \"wordCount\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"latestChapterTitle\",\n            \"columnName\": \"latestChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"tocUrl\",\n            \"columnName\": \"tocUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"time\",\n            \"columnName\": \"time\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"originOrder\",\n            \"columnName\": \"originOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"bookUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_searchBooks_bookUrl\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"bookUrl\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_searchBooks_bookUrl` ON `${TABLE_NAME}` (`bookUrl`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"search_keywords\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`word` TEXT NOT NULL, `usage` INTEGER NOT NULL, `lastUseTime` INTEGER NOT NULL, PRIMARY KEY(`word`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"word\",\n            \"columnName\": \"word\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"usage\",\n            \"columnName\": \"usage\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"lastUseTime\",\n            \"columnName\": \"lastUseTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"word\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_search_keywords_word\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"word\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_search_keywords_word` ON `${TABLE_NAME}` (`word`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"cookies\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`url` TEXT NOT NULL, `cookie` TEXT NOT NULL, PRIMARY KEY(`url`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"cookie\",\n            \"columnName\": \"cookie\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"url\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_cookies_url\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"url\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_cookies_url` ON `${TABLE_NAME}` (`url`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssSources\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`sourceUrl` TEXT NOT NULL, `sourceName` TEXT NOT NULL, `sourceIcon` TEXT NOT NULL, `sourceGroup` TEXT, `enabled` INTEGER NOT NULL, `ruleArticles` TEXT, `ruleNextPage` TEXT, `ruleTitle` TEXT, `rulePubDate` TEXT, `ruleCategories` TEXT, `ruleDescription` TEXT, `ruleImage` TEXT, `ruleLink` TEXT, `ruleContent` TEXT, `enableJs` INTEGER NOT NULL, `loadWithBaseUrl` INTEGER NOT NULL, `customOrder` INTEGER NOT NULL, PRIMARY KEY(`sourceUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"sourceUrl\",\n            \"columnName\": \"sourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceName\",\n            \"columnName\": \"sourceName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceIcon\",\n            \"columnName\": \"sourceIcon\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceGroup\",\n            \"columnName\": \"sourceGroup\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enabled\",\n            \"columnName\": \"enabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"ruleArticles\",\n            \"columnName\": \"ruleArticles\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleNextPage\",\n            \"columnName\": \"ruleNextPage\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleTitle\",\n            \"columnName\": \"ruleTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"rulePubDate\",\n            \"columnName\": \"rulePubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleCategories\",\n            \"columnName\": \"ruleCategories\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleDescription\",\n            \"columnName\": \"ruleDescription\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleImage\",\n            \"columnName\": \"ruleImage\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleLink\",\n            \"columnName\": \"ruleLink\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleContent\",\n            \"columnName\": \"ruleContent\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enableJs\",\n            \"columnName\": \"enableJs\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"loadWithBaseUrl\",\n            \"columnName\": \"loadWithBaseUrl\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"sourceUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_rssSources_sourceUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"sourceUrl\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_rssSources_sourceUrl` ON `${TABLE_NAME}` (`sourceUrl`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"bookmarks\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`time` INTEGER NOT NULL, `bookUrl` TEXT NOT NULL, `bookName` TEXT NOT NULL, `chapterName` TEXT NOT NULL, `chapterIndex` INTEGER NOT NULL, `pageIndex` INTEGER NOT NULL, `content` TEXT NOT NULL, PRIMARY KEY(`time`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"time\",\n            \"columnName\": \"time\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookName\",\n            \"columnName\": \"bookName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterName\",\n            \"columnName\": \"chapterName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterIndex\",\n            \"columnName\": \"chapterIndex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"pageIndex\",\n            \"columnName\": \"pageIndex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"time\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_bookmarks_bookUrl\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"bookUrl\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_bookmarks_bookUrl` ON `${TABLE_NAME}` (`bookUrl`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssArticles\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`origin` TEXT NOT NULL, `title` TEXT NOT NULL, `order` INTEGER NOT NULL, `link` TEXT NOT NULL, `pubDate` TEXT, `description` TEXT, `content` TEXT, `image` TEXT, `categories` TEXT, `read` INTEGER NOT NULL, `star` INTEGER NOT NULL, PRIMARY KEY(`origin`, `link`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"link\",\n            \"columnName\": \"link\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"pubDate\",\n            \"columnName\": \"pubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"description\",\n            \"columnName\": \"description\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"image\",\n            \"columnName\": \"image\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"categories\",\n            \"columnName\": \"categories\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"read\",\n            \"columnName\": \"read\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"star\",\n            \"columnName\": \"star\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"origin\",\n            \"link\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      }\n    ],\n    \"views\": [],\n    \"setupQueries\": [\n      \"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)\",\n      \"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'd9ed367fc7241a61e9f770d416c4f887')\"\n    ]\n  }\n}"
  },
  {
    "path": "app/schemas/io.legado.app.data.AppDatabase/10.json",
    "content": "{\n  \"formatVersion\": 1,\n  \"database\": {\n    \"version\": 10,\n    \"identityHash\": \"a9744f575dad6d4774cccc433921973b\",\n    \"entities\": [\n      {\n        \"tableName\": \"books\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookUrl` TEXT NOT NULL, `tocUrl` TEXT NOT NULL, `origin` TEXT NOT NULL, `originName` TEXT NOT NULL, `name` TEXT NOT NULL, `author` TEXT NOT NULL, `kind` TEXT, `customTag` TEXT, `coverUrl` TEXT, `customCoverUrl` TEXT, `intro` TEXT, `customIntro` TEXT, `charset` TEXT, `type` INTEGER NOT NULL, `group` INTEGER NOT NULL, `latestChapterTitle` TEXT, `latestChapterTime` INTEGER NOT NULL, `lastCheckTime` INTEGER NOT NULL, `lastCheckCount` INTEGER NOT NULL, `totalChapterNum` INTEGER NOT NULL, `durChapterTitle` TEXT, `durChapterIndex` INTEGER NOT NULL, `durChapterPos` INTEGER NOT NULL, `durChapterTime` INTEGER NOT NULL, `wordCount` TEXT, `canUpdate` INTEGER NOT NULL, `order` INTEGER NOT NULL, `originOrder` INTEGER NOT NULL, `useReplaceRule` INTEGER NOT NULL, `variable` TEXT, PRIMARY KEY(`name`, `author`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"tocUrl\",\n            \"columnName\": \"tocUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"originName\",\n            \"columnName\": \"originName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"author\",\n            \"columnName\": \"author\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"kind\",\n            \"columnName\": \"kind\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customTag\",\n            \"columnName\": \"customTag\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"coverUrl\",\n            \"columnName\": \"coverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customCoverUrl\",\n            \"columnName\": \"customCoverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"intro\",\n            \"columnName\": \"intro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customIntro\",\n            \"columnName\": \"customIntro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"charset\",\n            \"columnName\": \"charset\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"group\",\n            \"columnName\": \"group\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"latestChapterTitle\",\n            \"columnName\": \"latestChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"latestChapterTime\",\n            \"columnName\": \"latestChapterTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"lastCheckTime\",\n            \"columnName\": \"lastCheckTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"lastCheckCount\",\n            \"columnName\": \"lastCheckCount\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"totalChapterNum\",\n            \"columnName\": \"totalChapterNum\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"durChapterTitle\",\n            \"columnName\": \"durChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"durChapterIndex\",\n            \"columnName\": \"durChapterIndex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"durChapterPos\",\n            \"columnName\": \"durChapterPos\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"durChapterTime\",\n            \"columnName\": \"durChapterTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"wordCount\",\n            \"columnName\": \"wordCount\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"canUpdate\",\n            \"columnName\": \"canUpdate\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"originOrder\",\n            \"columnName\": \"originOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"useReplaceRule\",\n            \"columnName\": \"useReplaceRule\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"name\",\n            \"author\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_books_bookUrl\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"bookUrl\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_books_bookUrl` ON `${TABLE_NAME}` (`bookUrl`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"book_groups\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`groupId` INTEGER NOT NULL, `groupName` TEXT NOT NULL, `order` INTEGER NOT NULL, PRIMARY KEY(`groupId`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"groupId\",\n            \"columnName\": \"groupId\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"groupName\",\n            \"columnName\": \"groupName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"groupId\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"book_sources\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookSourceName` TEXT NOT NULL, `bookSourceGroup` TEXT, `bookSourceUrl` TEXT NOT NULL, `bookSourceType` INTEGER NOT NULL, `bookUrlPattern` TEXT, `customOrder` INTEGER NOT NULL, `enabled` INTEGER NOT NULL, `enabledExplore` INTEGER NOT NULL, `header` TEXT, `loginUrl` TEXT, `lastUpdateTime` INTEGER NOT NULL, `weight` INTEGER NOT NULL, `exploreUrl` TEXT, `ruleExplore` TEXT, `searchUrl` TEXT, `ruleSearch` TEXT, `ruleBookInfo` TEXT, `ruleToc` TEXT, `ruleContent` TEXT, PRIMARY KEY(`bookSourceUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookSourceName\",\n            \"columnName\": \"bookSourceName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookSourceGroup\",\n            \"columnName\": \"bookSourceGroup\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"bookSourceUrl\",\n            \"columnName\": \"bookSourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookSourceType\",\n            \"columnName\": \"bookSourceType\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookUrlPattern\",\n            \"columnName\": \"bookUrlPattern\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enabled\",\n            \"columnName\": \"enabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enabledExplore\",\n            \"columnName\": \"enabledExplore\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"header\",\n            \"columnName\": \"header\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUrl\",\n            \"columnName\": \"loginUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"lastUpdateTime\",\n            \"columnName\": \"lastUpdateTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"weight\",\n            \"columnName\": \"weight\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"exploreUrl\",\n            \"columnName\": \"exploreUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleExplore\",\n            \"columnName\": \"ruleExplore\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"searchUrl\",\n            \"columnName\": \"searchUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleSearch\",\n            \"columnName\": \"ruleSearch\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleBookInfo\",\n            \"columnName\": \"ruleBookInfo\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleToc\",\n            \"columnName\": \"ruleToc\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleContent\",\n            \"columnName\": \"ruleContent\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"bookSourceUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_book_sources_bookSourceUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"bookSourceUrl\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_book_sources_bookSourceUrl` ON `${TABLE_NAME}` (`bookSourceUrl`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"chapters\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`url` TEXT NOT NULL, `title` TEXT NOT NULL, `bookUrl` TEXT NOT NULL, `index` INTEGER NOT NULL, `resourceUrl` TEXT, `tag` TEXT, `start` INTEGER, `end` INTEGER, `variable` TEXT, PRIMARY KEY(`url`, `bookUrl`), FOREIGN KEY(`bookUrl`) REFERENCES `books`(`bookUrl`) ON UPDATE NO ACTION ON DELETE CASCADE )\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"index\",\n            \"columnName\": \"index\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"resourceUrl\",\n            \"columnName\": \"resourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"tag\",\n            \"columnName\": \"tag\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"start\",\n            \"columnName\": \"start\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"end\",\n            \"columnName\": \"end\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"url\",\n            \"bookUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_chapters_bookUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"bookUrl\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_chapters_bookUrl` ON `${TABLE_NAME}` (`bookUrl`)\"\n          },\n          {\n            \"name\": \"index_chapters_bookUrl_index\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"bookUrl\",\n              \"index\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_chapters_bookUrl_index` ON `${TABLE_NAME}` (`bookUrl`, `index`)\"\n          }\n        ],\n        \"foreignKeys\": [\n          {\n            \"table\": \"books\",\n            \"onDelete\": \"CASCADE\",\n            \"onUpdate\": \"NO ACTION\",\n            \"columns\": [\n              \"bookUrl\"\n            ],\n            \"referencedColumns\": [\n              \"bookUrl\"\n            ]\n          }\n        ]\n      },\n      {\n        \"tableName\": \"replace_rules\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL, `group` TEXT, `pattern` TEXT NOT NULL, `replacement` TEXT NOT NULL, `scope` TEXT, `isEnabled` INTEGER NOT NULL, `isRegex` INTEGER NOT NULL, `sortOrder` INTEGER NOT NULL)\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"group\",\n            \"columnName\": \"group\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"pattern\",\n            \"columnName\": \"pattern\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"replacement\",\n            \"columnName\": \"replacement\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"scope\",\n            \"columnName\": \"scope\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"isEnabled\",\n            \"columnName\": \"isEnabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"isRegex\",\n            \"columnName\": \"isRegex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"sortOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"id\"\n          ],\n          \"autoGenerate\": true\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_replace_rules_id\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"id\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_replace_rules_id` ON `${TABLE_NAME}` (`id`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"searchBooks\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookUrl` TEXT NOT NULL, `origin` TEXT NOT NULL, `originName` TEXT NOT NULL, `type` INTEGER NOT NULL, `name` TEXT NOT NULL, `author` TEXT NOT NULL, `kind` TEXT, `coverUrl` TEXT, `intro` TEXT, `wordCount` TEXT, `latestChapterTitle` TEXT, `tocUrl` TEXT NOT NULL, `time` INTEGER NOT NULL, `variable` TEXT, `originOrder` INTEGER NOT NULL, PRIMARY KEY(`bookUrl`), FOREIGN KEY(`origin`) REFERENCES `book_sources`(`bookSourceUrl`) ON UPDATE NO ACTION ON DELETE CASCADE )\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"originName\",\n            \"columnName\": \"originName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"author\",\n            \"columnName\": \"author\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"kind\",\n            \"columnName\": \"kind\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"coverUrl\",\n            \"columnName\": \"coverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"intro\",\n            \"columnName\": \"intro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"wordCount\",\n            \"columnName\": \"wordCount\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"latestChapterTitle\",\n            \"columnName\": \"latestChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"tocUrl\",\n            \"columnName\": \"tocUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"time\",\n            \"columnName\": \"time\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"originOrder\",\n            \"columnName\": \"originOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"bookUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_searchBooks_bookUrl\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"bookUrl\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_searchBooks_bookUrl` ON `${TABLE_NAME}` (`bookUrl`)\"\n          },\n          {\n            \"name\": \"index_searchBooks_origin\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"origin\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_searchBooks_origin` ON `${TABLE_NAME}` (`origin`)\"\n          }\n        ],\n        \"foreignKeys\": [\n          {\n            \"table\": \"book_sources\",\n            \"onDelete\": \"CASCADE\",\n            \"onUpdate\": \"NO ACTION\",\n            \"columns\": [\n              \"origin\"\n            ],\n            \"referencedColumns\": [\n              \"bookSourceUrl\"\n            ]\n          }\n        ]\n      },\n      {\n        \"tableName\": \"search_keywords\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`word` TEXT NOT NULL, `usage` INTEGER NOT NULL, `lastUseTime` INTEGER NOT NULL, PRIMARY KEY(`word`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"word\",\n            \"columnName\": \"word\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"usage\",\n            \"columnName\": \"usage\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"lastUseTime\",\n            \"columnName\": \"lastUseTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"word\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_search_keywords_word\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"word\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_search_keywords_word` ON `${TABLE_NAME}` (`word`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"cookies\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`url` TEXT NOT NULL, `cookie` TEXT NOT NULL, PRIMARY KEY(`url`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"cookie\",\n            \"columnName\": \"cookie\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"url\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_cookies_url\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"url\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_cookies_url` ON `${TABLE_NAME}` (`url`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssSources\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`sourceUrl` TEXT NOT NULL, `sourceName` TEXT NOT NULL, `sourceIcon` TEXT NOT NULL, `sourceGroup` TEXT, `enabled` INTEGER NOT NULL, `sortUrl` TEXT, `ruleArticles` TEXT, `ruleNextPage` TEXT, `ruleTitle` TEXT, `rulePubDate` TEXT, `ruleDescription` TEXT, `ruleImage` TEXT, `ruleLink` TEXT, `ruleContent` TEXT, `header` TEXT, `enableJs` INTEGER NOT NULL, `loadWithBaseUrl` INTEGER NOT NULL, `customOrder` INTEGER NOT NULL, PRIMARY KEY(`sourceUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"sourceUrl\",\n            \"columnName\": \"sourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceName\",\n            \"columnName\": \"sourceName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceIcon\",\n            \"columnName\": \"sourceIcon\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceGroup\",\n            \"columnName\": \"sourceGroup\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enabled\",\n            \"columnName\": \"enabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sortUrl\",\n            \"columnName\": \"sortUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleArticles\",\n            \"columnName\": \"ruleArticles\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleNextPage\",\n            \"columnName\": \"ruleNextPage\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleTitle\",\n            \"columnName\": \"ruleTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"rulePubDate\",\n            \"columnName\": \"rulePubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleDescription\",\n            \"columnName\": \"ruleDescription\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleImage\",\n            \"columnName\": \"ruleImage\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleLink\",\n            \"columnName\": \"ruleLink\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleContent\",\n            \"columnName\": \"ruleContent\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"header\",\n            \"columnName\": \"header\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enableJs\",\n            \"columnName\": \"enableJs\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"loadWithBaseUrl\",\n            \"columnName\": \"loadWithBaseUrl\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"sourceUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_rssSources_sourceUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"sourceUrl\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_rssSources_sourceUrl` ON `${TABLE_NAME}` (`sourceUrl`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"bookmarks\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`time` INTEGER NOT NULL, `bookUrl` TEXT NOT NULL, `bookName` TEXT NOT NULL, `chapterIndex` INTEGER NOT NULL, `pageIndex` INTEGER NOT NULL, `chapterName` TEXT NOT NULL, `content` TEXT NOT NULL, PRIMARY KEY(`time`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"time\",\n            \"columnName\": \"time\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookName\",\n            \"columnName\": \"bookName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterIndex\",\n            \"columnName\": \"chapterIndex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"pageIndex\",\n            \"columnName\": \"pageIndex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterName\",\n            \"columnName\": \"chapterName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"time\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_bookmarks_time\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"time\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_bookmarks_time` ON `${TABLE_NAME}` (`time`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssArticles\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`origin` TEXT NOT NULL, `sort` TEXT NOT NULL, `title` TEXT NOT NULL, `order` INTEGER NOT NULL, `link` TEXT NOT NULL, `pubDate` TEXT, `description` TEXT, `content` TEXT, `image` TEXT, `read` INTEGER NOT NULL, PRIMARY KEY(`origin`, `link`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sort\",\n            \"columnName\": \"sort\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"link\",\n            \"columnName\": \"link\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"pubDate\",\n            \"columnName\": \"pubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"description\",\n            \"columnName\": \"description\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"image\",\n            \"columnName\": \"image\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"read\",\n            \"columnName\": \"read\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"origin\",\n            \"link\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssReadRecords\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`record` TEXT NOT NULL, `read` INTEGER NOT NULL, PRIMARY KEY(`record`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"record\",\n            \"columnName\": \"record\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"read\",\n            \"columnName\": \"read\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"record\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssStars\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`origin` TEXT NOT NULL, `sort` TEXT NOT NULL, `title` TEXT NOT NULL, `starTime` INTEGER NOT NULL, `link` TEXT NOT NULL, `pubDate` TEXT, `description` TEXT, `content` TEXT, `image` TEXT, PRIMARY KEY(`origin`, `link`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sort\",\n            \"columnName\": \"sort\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"starTime\",\n            \"columnName\": \"starTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"link\",\n            \"columnName\": \"link\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"pubDate\",\n            \"columnName\": \"pubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"description\",\n            \"columnName\": \"description\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"image\",\n            \"columnName\": \"image\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"origin\",\n            \"link\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"txtTocRules\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`name` TEXT NOT NULL, `rule` TEXT NOT NULL, `serialNumber` INTEGER NOT NULL, `enable` INTEGER NOT NULL, PRIMARY KEY(`name`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"rule\",\n            \"columnName\": \"rule\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"serialNumber\",\n            \"columnName\": \"serialNumber\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enable\",\n            \"columnName\": \"enable\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"name\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      }\n    ],\n    \"views\": [],\n    \"setupQueries\": [\n      \"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)\",\n      \"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'a9744f575dad6d4774cccc433921973b')\"\n    ]\n  }\n}"
  },
  {
    "path": "app/schemas/io.legado.app.data.AppDatabase/11.json",
    "content": "{\n  \"formatVersion\": 1,\n  \"database\": {\n    \"version\": 11,\n    \"identityHash\": \"d3019908fa3212a7ac8eb87ac2f33369\",\n    \"entities\": [\n      {\n        \"tableName\": \"books\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookUrl` TEXT NOT NULL, `tocUrl` TEXT NOT NULL, `origin` TEXT NOT NULL, `originName` TEXT NOT NULL, `name` TEXT NOT NULL, `author` TEXT NOT NULL, `kind` TEXT, `customTag` TEXT, `coverUrl` TEXT, `customCoverUrl` TEXT, `intro` TEXT, `customIntro` TEXT, `charset` TEXT, `type` INTEGER NOT NULL, `group` INTEGER NOT NULL, `latestChapterTitle` TEXT, `latestChapterTime` INTEGER NOT NULL, `lastCheckTime` INTEGER NOT NULL, `lastCheckCount` INTEGER NOT NULL, `totalChapterNum` INTEGER NOT NULL, `durChapterTitle` TEXT, `durChapterIndex` INTEGER NOT NULL, `durChapterPos` INTEGER NOT NULL, `durChapterTime` INTEGER NOT NULL, `wordCount` TEXT, `canUpdate` INTEGER NOT NULL, `order` INTEGER NOT NULL, `originOrder` INTEGER NOT NULL, `useReplaceRule` INTEGER NOT NULL, `variable` TEXT, PRIMARY KEY(`name`, `author`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"tocUrl\",\n            \"columnName\": \"tocUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"originName\",\n            \"columnName\": \"originName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"author\",\n            \"columnName\": \"author\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"kind\",\n            \"columnName\": \"kind\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customTag\",\n            \"columnName\": \"customTag\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"coverUrl\",\n            \"columnName\": \"coverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customCoverUrl\",\n            \"columnName\": \"customCoverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"intro\",\n            \"columnName\": \"intro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customIntro\",\n            \"columnName\": \"customIntro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"charset\",\n            \"columnName\": \"charset\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"group\",\n            \"columnName\": \"group\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"latestChapterTitle\",\n            \"columnName\": \"latestChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"latestChapterTime\",\n            \"columnName\": \"latestChapterTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"lastCheckTime\",\n            \"columnName\": \"lastCheckTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"lastCheckCount\",\n            \"columnName\": \"lastCheckCount\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"totalChapterNum\",\n            \"columnName\": \"totalChapterNum\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"durChapterTitle\",\n            \"columnName\": \"durChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"durChapterIndex\",\n            \"columnName\": \"durChapterIndex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"durChapterPos\",\n            \"columnName\": \"durChapterPos\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"durChapterTime\",\n            \"columnName\": \"durChapterTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"wordCount\",\n            \"columnName\": \"wordCount\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"canUpdate\",\n            \"columnName\": \"canUpdate\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"originOrder\",\n            \"columnName\": \"originOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"useReplaceRule\",\n            \"columnName\": \"useReplaceRule\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"name\",\n            \"author\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_books_bookUrl\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"bookUrl\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_books_bookUrl` ON `${TABLE_NAME}` (`bookUrl`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"book_groups\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`groupId` INTEGER NOT NULL, `groupName` TEXT NOT NULL, `order` INTEGER NOT NULL, PRIMARY KEY(`groupId`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"groupId\",\n            \"columnName\": \"groupId\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"groupName\",\n            \"columnName\": \"groupName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"groupId\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"book_sources\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookSourceName` TEXT NOT NULL, `bookSourceGroup` TEXT, `bookSourceUrl` TEXT NOT NULL, `bookSourceType` INTEGER NOT NULL, `bookUrlPattern` TEXT, `customOrder` INTEGER NOT NULL, `enabled` INTEGER NOT NULL, `enabledExplore` INTEGER NOT NULL, `header` TEXT, `loginUrl` TEXT, `lastUpdateTime` INTEGER NOT NULL, `weight` INTEGER NOT NULL, `exploreUrl` TEXT, `ruleExplore` TEXT, `searchUrl` TEXT, `ruleSearch` TEXT, `ruleBookInfo` TEXT, `ruleToc` TEXT, `ruleContent` TEXT, PRIMARY KEY(`bookSourceUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookSourceName\",\n            \"columnName\": \"bookSourceName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookSourceGroup\",\n            \"columnName\": \"bookSourceGroup\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"bookSourceUrl\",\n            \"columnName\": \"bookSourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookSourceType\",\n            \"columnName\": \"bookSourceType\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookUrlPattern\",\n            \"columnName\": \"bookUrlPattern\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enabled\",\n            \"columnName\": \"enabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enabledExplore\",\n            \"columnName\": \"enabledExplore\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"header\",\n            \"columnName\": \"header\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUrl\",\n            \"columnName\": \"loginUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"lastUpdateTime\",\n            \"columnName\": \"lastUpdateTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"weight\",\n            \"columnName\": \"weight\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"exploreUrl\",\n            \"columnName\": \"exploreUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleExplore\",\n            \"columnName\": \"ruleExplore\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"searchUrl\",\n            \"columnName\": \"searchUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleSearch\",\n            \"columnName\": \"ruleSearch\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleBookInfo\",\n            \"columnName\": \"ruleBookInfo\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleToc\",\n            \"columnName\": \"ruleToc\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleContent\",\n            \"columnName\": \"ruleContent\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"bookSourceUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_book_sources_bookSourceUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"bookSourceUrl\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_book_sources_bookSourceUrl` ON `${TABLE_NAME}` (`bookSourceUrl`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"chapters\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`url` TEXT NOT NULL, `title` TEXT NOT NULL, `bookUrl` TEXT NOT NULL, `index` INTEGER NOT NULL, `resourceUrl` TEXT, `tag` TEXT, `start` INTEGER, `end` INTEGER, `variable` TEXT, PRIMARY KEY(`url`, `bookUrl`), FOREIGN KEY(`bookUrl`) REFERENCES `books`(`bookUrl`) ON UPDATE NO ACTION ON DELETE CASCADE )\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"index\",\n            \"columnName\": \"index\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"resourceUrl\",\n            \"columnName\": \"resourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"tag\",\n            \"columnName\": \"tag\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"start\",\n            \"columnName\": \"start\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"end\",\n            \"columnName\": \"end\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"url\",\n            \"bookUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_chapters_bookUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"bookUrl\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_chapters_bookUrl` ON `${TABLE_NAME}` (`bookUrl`)\"\n          },\n          {\n            \"name\": \"index_chapters_bookUrl_index\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"bookUrl\",\n              \"index\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_chapters_bookUrl_index` ON `${TABLE_NAME}` (`bookUrl`, `index`)\"\n          }\n        ],\n        \"foreignKeys\": [\n          {\n            \"table\": \"books\",\n            \"onDelete\": \"CASCADE\",\n            \"onUpdate\": \"NO ACTION\",\n            \"columns\": [\n              \"bookUrl\"\n            ],\n            \"referencedColumns\": [\n              \"bookUrl\"\n            ]\n          }\n        ]\n      },\n      {\n        \"tableName\": \"replace_rules\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL, `group` TEXT, `pattern` TEXT NOT NULL, `replacement` TEXT NOT NULL, `scope` TEXT, `isEnabled` INTEGER NOT NULL, `isRegex` INTEGER NOT NULL, `sortOrder` INTEGER NOT NULL)\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"group\",\n            \"columnName\": \"group\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"pattern\",\n            \"columnName\": \"pattern\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"replacement\",\n            \"columnName\": \"replacement\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"scope\",\n            \"columnName\": \"scope\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"isEnabled\",\n            \"columnName\": \"isEnabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"isRegex\",\n            \"columnName\": \"isRegex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"sortOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"id\"\n          ],\n          \"autoGenerate\": true\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_replace_rules_id\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"id\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_replace_rules_id` ON `${TABLE_NAME}` (`id`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"searchBooks\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookUrl` TEXT NOT NULL, `origin` TEXT NOT NULL, `originName` TEXT NOT NULL, `type` INTEGER NOT NULL, `name` TEXT NOT NULL, `author` TEXT NOT NULL, `kind` TEXT, `coverUrl` TEXT, `intro` TEXT, `wordCount` TEXT, `latestChapterTitle` TEXT, `tocUrl` TEXT NOT NULL, `time` INTEGER NOT NULL, `variable` TEXT, `originOrder` INTEGER NOT NULL, PRIMARY KEY(`bookUrl`), FOREIGN KEY(`origin`) REFERENCES `book_sources`(`bookSourceUrl`) ON UPDATE NO ACTION ON DELETE CASCADE )\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"originName\",\n            \"columnName\": \"originName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"author\",\n            \"columnName\": \"author\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"kind\",\n            \"columnName\": \"kind\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"coverUrl\",\n            \"columnName\": \"coverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"intro\",\n            \"columnName\": \"intro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"wordCount\",\n            \"columnName\": \"wordCount\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"latestChapterTitle\",\n            \"columnName\": \"latestChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"tocUrl\",\n            \"columnName\": \"tocUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"time\",\n            \"columnName\": \"time\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"originOrder\",\n            \"columnName\": \"originOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"bookUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_searchBooks_bookUrl\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"bookUrl\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_searchBooks_bookUrl` ON `${TABLE_NAME}` (`bookUrl`)\"\n          },\n          {\n            \"name\": \"index_searchBooks_origin\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"origin\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_searchBooks_origin` ON `${TABLE_NAME}` (`origin`)\"\n          }\n        ],\n        \"foreignKeys\": [\n          {\n            \"table\": \"book_sources\",\n            \"onDelete\": \"CASCADE\",\n            \"onUpdate\": \"NO ACTION\",\n            \"columns\": [\n              \"origin\"\n            ],\n            \"referencedColumns\": [\n              \"bookSourceUrl\"\n            ]\n          }\n        ]\n      },\n      {\n        \"tableName\": \"search_keywords\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`word` TEXT NOT NULL, `usage` INTEGER NOT NULL, `lastUseTime` INTEGER NOT NULL, PRIMARY KEY(`word`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"word\",\n            \"columnName\": \"word\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"usage\",\n            \"columnName\": \"usage\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"lastUseTime\",\n            \"columnName\": \"lastUseTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"word\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_search_keywords_word\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"word\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_search_keywords_word` ON `${TABLE_NAME}` (`word`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"cookies\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`url` TEXT NOT NULL, `cookie` TEXT NOT NULL, PRIMARY KEY(`url`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"cookie\",\n            \"columnName\": \"cookie\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"url\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_cookies_url\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"url\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_cookies_url` ON `${TABLE_NAME}` (`url`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssSources\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`sourceUrl` TEXT NOT NULL, `sourceName` TEXT NOT NULL, `sourceIcon` TEXT NOT NULL, `sourceGroup` TEXT, `enabled` INTEGER NOT NULL, `sortUrl` TEXT, `ruleArticles` TEXT, `ruleNextPage` TEXT, `ruleTitle` TEXT, `rulePubDate` TEXT, `ruleDescription` TEXT, `ruleImage` TEXT, `ruleLink` TEXT, `ruleContent` TEXT, `header` TEXT, `enableJs` INTEGER NOT NULL, `loadWithBaseUrl` INTEGER NOT NULL, `customOrder` INTEGER NOT NULL, PRIMARY KEY(`sourceUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"sourceUrl\",\n            \"columnName\": \"sourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceName\",\n            \"columnName\": \"sourceName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceIcon\",\n            \"columnName\": \"sourceIcon\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceGroup\",\n            \"columnName\": \"sourceGroup\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enabled\",\n            \"columnName\": \"enabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sortUrl\",\n            \"columnName\": \"sortUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleArticles\",\n            \"columnName\": \"ruleArticles\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleNextPage\",\n            \"columnName\": \"ruleNextPage\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleTitle\",\n            \"columnName\": \"ruleTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"rulePubDate\",\n            \"columnName\": \"rulePubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleDescription\",\n            \"columnName\": \"ruleDescription\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleImage\",\n            \"columnName\": \"ruleImage\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleLink\",\n            \"columnName\": \"ruleLink\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleContent\",\n            \"columnName\": \"ruleContent\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"header\",\n            \"columnName\": \"header\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enableJs\",\n            \"columnName\": \"enableJs\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"loadWithBaseUrl\",\n            \"columnName\": \"loadWithBaseUrl\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"sourceUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_rssSources_sourceUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"sourceUrl\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_rssSources_sourceUrl` ON `${TABLE_NAME}` (`sourceUrl`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"bookmarks\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`time` INTEGER NOT NULL, `bookUrl` TEXT NOT NULL, `bookName` TEXT NOT NULL, `chapterIndex` INTEGER NOT NULL, `pageIndex` INTEGER NOT NULL, `chapterName` TEXT NOT NULL, `content` TEXT NOT NULL, PRIMARY KEY(`time`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"time\",\n            \"columnName\": \"time\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookName\",\n            \"columnName\": \"bookName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterIndex\",\n            \"columnName\": \"chapterIndex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"pageIndex\",\n            \"columnName\": \"pageIndex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterName\",\n            \"columnName\": \"chapterName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"time\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_bookmarks_time\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"time\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_bookmarks_time` ON `${TABLE_NAME}` (`time`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssArticles\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`origin` TEXT NOT NULL, `sort` TEXT NOT NULL, `title` TEXT NOT NULL, `order` INTEGER NOT NULL, `link` TEXT NOT NULL, `pubDate` TEXT, `description` TEXT, `content` TEXT, `image` TEXT, `read` INTEGER NOT NULL, PRIMARY KEY(`origin`, `link`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sort\",\n            \"columnName\": \"sort\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"link\",\n            \"columnName\": \"link\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"pubDate\",\n            \"columnName\": \"pubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"description\",\n            \"columnName\": \"description\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"image\",\n            \"columnName\": \"image\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"read\",\n            \"columnName\": \"read\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"origin\",\n            \"link\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssReadRecords\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`record` TEXT NOT NULL, `read` INTEGER NOT NULL, PRIMARY KEY(`record`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"record\",\n            \"columnName\": \"record\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"read\",\n            \"columnName\": \"read\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"record\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssStars\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`origin` TEXT NOT NULL, `sort` TEXT NOT NULL, `title` TEXT NOT NULL, `starTime` INTEGER NOT NULL, `link` TEXT NOT NULL, `pubDate` TEXT, `description` TEXT, `content` TEXT, `image` TEXT, PRIMARY KEY(`origin`, `link`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sort\",\n            \"columnName\": \"sort\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"starTime\",\n            \"columnName\": \"starTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"link\",\n            \"columnName\": \"link\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"pubDate\",\n            \"columnName\": \"pubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"description\",\n            \"columnName\": \"description\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"image\",\n            \"columnName\": \"image\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"origin\",\n            \"link\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"txtTocRules\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `rule` TEXT NOT NULL, `serialNumber` INTEGER NOT NULL, `enable` INTEGER NOT NULL, PRIMARY KEY(`id`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"rule\",\n            \"columnName\": \"rule\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"serialNumber\",\n            \"columnName\": \"serialNumber\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enable\",\n            \"columnName\": \"enable\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"id\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      }\n    ],\n    \"views\": [],\n    \"setupQueries\": [\n      \"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)\",\n      \"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'd3019908fa3212a7ac8eb87ac2f33369')\"\n    ]\n  }\n}"
  },
  {
    "path": "app/schemas/io.legado.app.data.AppDatabase/12.json",
    "content": "{\n  \"formatVersion\": 1,\n  \"database\": {\n    \"version\": 12,\n    \"identityHash\": \"fa238e7524c215177f66110c847d327d\",\n    \"entities\": [\n      {\n        \"tableName\": \"books\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookUrl` TEXT NOT NULL, `tocUrl` TEXT NOT NULL, `origin` TEXT NOT NULL, `originName` TEXT NOT NULL, `name` TEXT NOT NULL, `author` TEXT NOT NULL, `kind` TEXT, `customTag` TEXT, `coverUrl` TEXT, `customCoverUrl` TEXT, `intro` TEXT, `customIntro` TEXT, `charset` TEXT, `type` INTEGER NOT NULL, `group` INTEGER NOT NULL, `latestChapterTitle` TEXT, `latestChapterTime` INTEGER NOT NULL, `lastCheckTime` INTEGER NOT NULL, `lastCheckCount` INTEGER NOT NULL, `totalChapterNum` INTEGER NOT NULL, `durChapterTitle` TEXT, `durChapterIndex` INTEGER NOT NULL, `durChapterPos` INTEGER NOT NULL, `durChapterTime` INTEGER NOT NULL, `wordCount` TEXT, `canUpdate` INTEGER NOT NULL, `order` INTEGER NOT NULL, `originOrder` INTEGER NOT NULL, `useReplaceRule` INTEGER NOT NULL, `variable` TEXT, PRIMARY KEY(`name`, `author`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"tocUrl\",\n            \"columnName\": \"tocUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"originName\",\n            \"columnName\": \"originName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"author\",\n            \"columnName\": \"author\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"kind\",\n            \"columnName\": \"kind\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customTag\",\n            \"columnName\": \"customTag\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"coverUrl\",\n            \"columnName\": \"coverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customCoverUrl\",\n            \"columnName\": \"customCoverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"intro\",\n            \"columnName\": \"intro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customIntro\",\n            \"columnName\": \"customIntro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"charset\",\n            \"columnName\": \"charset\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"group\",\n            \"columnName\": \"group\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"latestChapterTitle\",\n            \"columnName\": \"latestChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"latestChapterTime\",\n            \"columnName\": \"latestChapterTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"lastCheckTime\",\n            \"columnName\": \"lastCheckTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"lastCheckCount\",\n            \"columnName\": \"lastCheckCount\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"totalChapterNum\",\n            \"columnName\": \"totalChapterNum\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"durChapterTitle\",\n            \"columnName\": \"durChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"durChapterIndex\",\n            \"columnName\": \"durChapterIndex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"durChapterPos\",\n            \"columnName\": \"durChapterPos\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"durChapterTime\",\n            \"columnName\": \"durChapterTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"wordCount\",\n            \"columnName\": \"wordCount\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"canUpdate\",\n            \"columnName\": \"canUpdate\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"originOrder\",\n            \"columnName\": \"originOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"useReplaceRule\",\n            \"columnName\": \"useReplaceRule\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"name\",\n            \"author\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_books_bookUrl\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"bookUrl\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_books_bookUrl` ON `${TABLE_NAME}` (`bookUrl`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"book_groups\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`groupId` INTEGER NOT NULL, `groupName` TEXT NOT NULL, `order` INTEGER NOT NULL, PRIMARY KEY(`groupId`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"groupId\",\n            \"columnName\": \"groupId\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"groupName\",\n            \"columnName\": \"groupName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"groupId\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"book_sources\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookSourceName` TEXT NOT NULL, `bookSourceGroup` TEXT, `bookSourceUrl` TEXT NOT NULL, `bookSourceType` INTEGER NOT NULL, `bookUrlPattern` TEXT, `customOrder` INTEGER NOT NULL, `enabled` INTEGER NOT NULL, `enabledExplore` INTEGER NOT NULL, `header` TEXT, `loginUrl` TEXT, `lastUpdateTime` INTEGER NOT NULL, `weight` INTEGER NOT NULL, `exploreUrl` TEXT, `ruleExplore` TEXT, `searchUrl` TEXT, `ruleSearch` TEXT, `ruleBookInfo` TEXT, `ruleToc` TEXT, `ruleContent` TEXT, PRIMARY KEY(`bookSourceUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookSourceName\",\n            \"columnName\": \"bookSourceName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookSourceGroup\",\n            \"columnName\": \"bookSourceGroup\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"bookSourceUrl\",\n            \"columnName\": \"bookSourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookSourceType\",\n            \"columnName\": \"bookSourceType\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookUrlPattern\",\n            \"columnName\": \"bookUrlPattern\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enabled\",\n            \"columnName\": \"enabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enabledExplore\",\n            \"columnName\": \"enabledExplore\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"header\",\n            \"columnName\": \"header\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUrl\",\n            \"columnName\": \"loginUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"lastUpdateTime\",\n            \"columnName\": \"lastUpdateTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"weight\",\n            \"columnName\": \"weight\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"exploreUrl\",\n            \"columnName\": \"exploreUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleExplore\",\n            \"columnName\": \"ruleExplore\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"searchUrl\",\n            \"columnName\": \"searchUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleSearch\",\n            \"columnName\": \"ruleSearch\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleBookInfo\",\n            \"columnName\": \"ruleBookInfo\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleToc\",\n            \"columnName\": \"ruleToc\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleContent\",\n            \"columnName\": \"ruleContent\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"bookSourceUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_book_sources_bookSourceUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"bookSourceUrl\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_book_sources_bookSourceUrl` ON `${TABLE_NAME}` (`bookSourceUrl`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"chapters\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`url` TEXT NOT NULL, `title` TEXT NOT NULL, `bookUrl` TEXT NOT NULL, `index` INTEGER NOT NULL, `resourceUrl` TEXT, `tag` TEXT, `start` INTEGER, `end` INTEGER, `variable` TEXT, PRIMARY KEY(`url`, `bookUrl`), FOREIGN KEY(`bookUrl`) REFERENCES `books`(`bookUrl`) ON UPDATE NO ACTION ON DELETE CASCADE )\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"index\",\n            \"columnName\": \"index\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"resourceUrl\",\n            \"columnName\": \"resourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"tag\",\n            \"columnName\": \"tag\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"start\",\n            \"columnName\": \"start\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"end\",\n            \"columnName\": \"end\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"url\",\n            \"bookUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_chapters_bookUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"bookUrl\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_chapters_bookUrl` ON `${TABLE_NAME}` (`bookUrl`)\"\n          },\n          {\n            \"name\": \"index_chapters_bookUrl_index\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"bookUrl\",\n              \"index\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_chapters_bookUrl_index` ON `${TABLE_NAME}` (`bookUrl`, `index`)\"\n          }\n        ],\n        \"foreignKeys\": [\n          {\n            \"table\": \"books\",\n            \"onDelete\": \"CASCADE\",\n            \"onUpdate\": \"NO ACTION\",\n            \"columns\": [\n              \"bookUrl\"\n            ],\n            \"referencedColumns\": [\n              \"bookUrl\"\n            ]\n          }\n        ]\n      },\n      {\n        \"tableName\": \"replace_rules\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL, `group` TEXT, `pattern` TEXT NOT NULL, `replacement` TEXT NOT NULL, `scope` TEXT, `isEnabled` INTEGER NOT NULL, `isRegex` INTEGER NOT NULL, `sortOrder` INTEGER NOT NULL)\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"group\",\n            \"columnName\": \"group\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"pattern\",\n            \"columnName\": \"pattern\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"replacement\",\n            \"columnName\": \"replacement\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"scope\",\n            \"columnName\": \"scope\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"isEnabled\",\n            \"columnName\": \"isEnabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"isRegex\",\n            \"columnName\": \"isRegex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"sortOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"id\"\n          ],\n          \"autoGenerate\": true\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_replace_rules_id\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"id\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_replace_rules_id` ON `${TABLE_NAME}` (`id`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"searchBooks\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookUrl` TEXT NOT NULL, `origin` TEXT NOT NULL, `originName` TEXT NOT NULL, `type` INTEGER NOT NULL, `name` TEXT NOT NULL, `author` TEXT NOT NULL, `kind` TEXT, `coverUrl` TEXT, `intro` TEXT, `wordCount` TEXT, `latestChapterTitle` TEXT, `tocUrl` TEXT NOT NULL, `time` INTEGER NOT NULL, `variable` TEXT, `originOrder` INTEGER NOT NULL, PRIMARY KEY(`bookUrl`), FOREIGN KEY(`origin`) REFERENCES `book_sources`(`bookSourceUrl`) ON UPDATE NO ACTION ON DELETE CASCADE )\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"originName\",\n            \"columnName\": \"originName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"author\",\n            \"columnName\": \"author\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"kind\",\n            \"columnName\": \"kind\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"coverUrl\",\n            \"columnName\": \"coverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"intro\",\n            \"columnName\": \"intro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"wordCount\",\n            \"columnName\": \"wordCount\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"latestChapterTitle\",\n            \"columnName\": \"latestChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"tocUrl\",\n            \"columnName\": \"tocUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"time\",\n            \"columnName\": \"time\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"originOrder\",\n            \"columnName\": \"originOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"bookUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_searchBooks_bookUrl\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"bookUrl\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_searchBooks_bookUrl` ON `${TABLE_NAME}` (`bookUrl`)\"\n          },\n          {\n            \"name\": \"index_searchBooks_origin\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"origin\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_searchBooks_origin` ON `${TABLE_NAME}` (`origin`)\"\n          }\n        ],\n        \"foreignKeys\": [\n          {\n            \"table\": \"book_sources\",\n            \"onDelete\": \"CASCADE\",\n            \"onUpdate\": \"NO ACTION\",\n            \"columns\": [\n              \"origin\"\n            ],\n            \"referencedColumns\": [\n              \"bookSourceUrl\"\n            ]\n          }\n        ]\n      },\n      {\n        \"tableName\": \"search_keywords\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`word` TEXT NOT NULL, `usage` INTEGER NOT NULL, `lastUseTime` INTEGER NOT NULL, PRIMARY KEY(`word`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"word\",\n            \"columnName\": \"word\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"usage\",\n            \"columnName\": \"usage\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"lastUseTime\",\n            \"columnName\": \"lastUseTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"word\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_search_keywords_word\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"word\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_search_keywords_word` ON `${TABLE_NAME}` (`word`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"cookies\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`url` TEXT NOT NULL, `cookie` TEXT NOT NULL, PRIMARY KEY(`url`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"cookie\",\n            \"columnName\": \"cookie\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"url\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_cookies_url\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"url\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_cookies_url` ON `${TABLE_NAME}` (`url`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssSources\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`sourceUrl` TEXT NOT NULL, `sourceName` TEXT NOT NULL, `sourceIcon` TEXT NOT NULL, `sourceGroup` TEXT, `enabled` INTEGER NOT NULL, `sortUrl` TEXT, `ruleArticles` TEXT, `ruleNextPage` TEXT, `ruleTitle` TEXT, `rulePubDate` TEXT, `ruleDescription` TEXT, `ruleImage` TEXT, `ruleLink` TEXT, `ruleContent` TEXT, `style` TEXT, `header` TEXT, `enableJs` INTEGER NOT NULL, `loadWithBaseUrl` INTEGER NOT NULL, `customOrder` INTEGER NOT NULL, PRIMARY KEY(`sourceUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"sourceUrl\",\n            \"columnName\": \"sourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceName\",\n            \"columnName\": \"sourceName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceIcon\",\n            \"columnName\": \"sourceIcon\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceGroup\",\n            \"columnName\": \"sourceGroup\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enabled\",\n            \"columnName\": \"enabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sortUrl\",\n            \"columnName\": \"sortUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleArticles\",\n            \"columnName\": \"ruleArticles\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleNextPage\",\n            \"columnName\": \"ruleNextPage\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleTitle\",\n            \"columnName\": \"ruleTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"rulePubDate\",\n            \"columnName\": \"rulePubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleDescription\",\n            \"columnName\": \"ruleDescription\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleImage\",\n            \"columnName\": \"ruleImage\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleLink\",\n            \"columnName\": \"ruleLink\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleContent\",\n            \"columnName\": \"ruleContent\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"style\",\n            \"columnName\": \"style\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"header\",\n            \"columnName\": \"header\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enableJs\",\n            \"columnName\": \"enableJs\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"loadWithBaseUrl\",\n            \"columnName\": \"loadWithBaseUrl\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"sourceUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_rssSources_sourceUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"sourceUrl\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_rssSources_sourceUrl` ON `${TABLE_NAME}` (`sourceUrl`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"bookmarks\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`time` INTEGER NOT NULL, `bookUrl` TEXT NOT NULL, `bookName` TEXT NOT NULL, `chapterIndex` INTEGER NOT NULL, `pageIndex` INTEGER NOT NULL, `chapterName` TEXT NOT NULL, `content` TEXT NOT NULL, PRIMARY KEY(`time`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"time\",\n            \"columnName\": \"time\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookName\",\n            \"columnName\": \"bookName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterIndex\",\n            \"columnName\": \"chapterIndex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"pageIndex\",\n            \"columnName\": \"pageIndex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterName\",\n            \"columnName\": \"chapterName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"time\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_bookmarks_time\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"time\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_bookmarks_time` ON `${TABLE_NAME}` (`time`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssArticles\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`origin` TEXT NOT NULL, `sort` TEXT NOT NULL, `title` TEXT NOT NULL, `order` INTEGER NOT NULL, `link` TEXT NOT NULL, `pubDate` TEXT, `description` TEXT, `content` TEXT, `image` TEXT, `read` INTEGER NOT NULL, PRIMARY KEY(`origin`, `link`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sort\",\n            \"columnName\": \"sort\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"link\",\n            \"columnName\": \"link\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"pubDate\",\n            \"columnName\": \"pubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"description\",\n            \"columnName\": \"description\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"image\",\n            \"columnName\": \"image\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"read\",\n            \"columnName\": \"read\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"origin\",\n            \"link\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssReadRecords\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`record` TEXT NOT NULL, `read` INTEGER NOT NULL, PRIMARY KEY(`record`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"record\",\n            \"columnName\": \"record\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"read\",\n            \"columnName\": \"read\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"record\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssStars\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`origin` TEXT NOT NULL, `sort` TEXT NOT NULL, `title` TEXT NOT NULL, `starTime` INTEGER NOT NULL, `link` TEXT NOT NULL, `pubDate` TEXT, `description` TEXT, `content` TEXT, `image` TEXT, PRIMARY KEY(`origin`, `link`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sort\",\n            \"columnName\": \"sort\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"starTime\",\n            \"columnName\": \"starTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"link\",\n            \"columnName\": \"link\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"pubDate\",\n            \"columnName\": \"pubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"description\",\n            \"columnName\": \"description\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"image\",\n            \"columnName\": \"image\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"origin\",\n            \"link\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"txtTocRules\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `rule` TEXT NOT NULL, `serialNumber` INTEGER NOT NULL, `enable` INTEGER NOT NULL, PRIMARY KEY(`id`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"rule\",\n            \"columnName\": \"rule\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"serialNumber\",\n            \"columnName\": \"serialNumber\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enable\",\n            \"columnName\": \"enable\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"id\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      }\n    ],\n    \"views\": [],\n    \"setupQueries\": [\n      \"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)\",\n      \"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'fa238e7524c215177f66110c847d327d')\"\n    ]\n  }\n}"
  },
  {
    "path": "app/schemas/io.legado.app.data.AppDatabase/13.json",
    "content": "{\n  \"formatVersion\": 1,\n  \"database\": {\n    \"version\": 13,\n    \"identityHash\": \"da04f8cb7f257482f105b1274a7a351b\",\n    \"entities\": [\n      {\n        \"tableName\": \"books\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookUrl` TEXT NOT NULL, `tocUrl` TEXT NOT NULL, `origin` TEXT NOT NULL, `originName` TEXT NOT NULL, `name` TEXT NOT NULL, `author` TEXT NOT NULL, `kind` TEXT, `customTag` TEXT, `coverUrl` TEXT, `customCoverUrl` TEXT, `intro` TEXT, `customIntro` TEXT, `charset` TEXT, `type` INTEGER NOT NULL, `group` INTEGER NOT NULL, `latestChapterTitle` TEXT, `latestChapterTime` INTEGER NOT NULL, `lastCheckTime` INTEGER NOT NULL, `lastCheckCount` INTEGER NOT NULL, `totalChapterNum` INTEGER NOT NULL, `durChapterTitle` TEXT, `durChapterIndex` INTEGER NOT NULL, `durChapterPos` INTEGER NOT NULL, `durChapterTime` INTEGER NOT NULL, `wordCount` TEXT, `canUpdate` INTEGER NOT NULL, `order` INTEGER NOT NULL, `originOrder` INTEGER NOT NULL, `useReplaceRule` INTEGER NOT NULL, `variable` TEXT, PRIMARY KEY(`name`, `author`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"tocUrl\",\n            \"columnName\": \"tocUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"originName\",\n            \"columnName\": \"originName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"author\",\n            \"columnName\": \"author\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"kind\",\n            \"columnName\": \"kind\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customTag\",\n            \"columnName\": \"customTag\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"coverUrl\",\n            \"columnName\": \"coverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customCoverUrl\",\n            \"columnName\": \"customCoverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"intro\",\n            \"columnName\": \"intro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customIntro\",\n            \"columnName\": \"customIntro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"charset\",\n            \"columnName\": \"charset\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"group\",\n            \"columnName\": \"group\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"latestChapterTitle\",\n            \"columnName\": \"latestChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"latestChapterTime\",\n            \"columnName\": \"latestChapterTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"lastCheckTime\",\n            \"columnName\": \"lastCheckTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"lastCheckCount\",\n            \"columnName\": \"lastCheckCount\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"totalChapterNum\",\n            \"columnName\": \"totalChapterNum\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"durChapterTitle\",\n            \"columnName\": \"durChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"durChapterIndex\",\n            \"columnName\": \"durChapterIndex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"durChapterPos\",\n            \"columnName\": \"durChapterPos\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"durChapterTime\",\n            \"columnName\": \"durChapterTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"wordCount\",\n            \"columnName\": \"wordCount\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"canUpdate\",\n            \"columnName\": \"canUpdate\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"originOrder\",\n            \"columnName\": \"originOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"useReplaceRule\",\n            \"columnName\": \"useReplaceRule\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"name\",\n            \"author\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_books_bookUrl\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"bookUrl\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_books_bookUrl` ON `${TABLE_NAME}` (`bookUrl`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"book_groups\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`groupId` INTEGER NOT NULL, `groupName` TEXT NOT NULL, `order` INTEGER NOT NULL, PRIMARY KEY(`groupId`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"groupId\",\n            \"columnName\": \"groupId\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"groupName\",\n            \"columnName\": \"groupName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"groupId\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"book_sources\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookSourceName` TEXT NOT NULL, `bookSourceGroup` TEXT, `bookSourceUrl` TEXT NOT NULL, `bookSourceType` INTEGER NOT NULL, `bookUrlPattern` TEXT, `customOrder` INTEGER NOT NULL, `enabled` INTEGER NOT NULL, `enabledExplore` INTEGER NOT NULL, `header` TEXT, `loginUrl` TEXT, `lastUpdateTime` INTEGER NOT NULL, `weight` INTEGER NOT NULL, `exploreUrl` TEXT, `ruleExplore` TEXT, `searchUrl` TEXT, `ruleSearch` TEXT, `ruleBookInfo` TEXT, `ruleToc` TEXT, `ruleContent` TEXT, PRIMARY KEY(`bookSourceUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookSourceName\",\n            \"columnName\": \"bookSourceName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookSourceGroup\",\n            \"columnName\": \"bookSourceGroup\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"bookSourceUrl\",\n            \"columnName\": \"bookSourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookSourceType\",\n            \"columnName\": \"bookSourceType\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookUrlPattern\",\n            \"columnName\": \"bookUrlPattern\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enabled\",\n            \"columnName\": \"enabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enabledExplore\",\n            \"columnName\": \"enabledExplore\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"header\",\n            \"columnName\": \"header\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUrl\",\n            \"columnName\": \"loginUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"lastUpdateTime\",\n            \"columnName\": \"lastUpdateTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"weight\",\n            \"columnName\": \"weight\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"exploreUrl\",\n            \"columnName\": \"exploreUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleExplore\",\n            \"columnName\": \"ruleExplore\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"searchUrl\",\n            \"columnName\": \"searchUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleSearch\",\n            \"columnName\": \"ruleSearch\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleBookInfo\",\n            \"columnName\": \"ruleBookInfo\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleToc\",\n            \"columnName\": \"ruleToc\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleContent\",\n            \"columnName\": \"ruleContent\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"bookSourceUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_book_sources_bookSourceUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"bookSourceUrl\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_book_sources_bookSourceUrl` ON `${TABLE_NAME}` (`bookSourceUrl`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"chapters\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`url` TEXT NOT NULL, `title` TEXT NOT NULL, `bookUrl` TEXT NOT NULL, `index` INTEGER NOT NULL, `resourceUrl` TEXT, `tag` TEXT, `start` INTEGER, `end` INTEGER, `variable` TEXT, PRIMARY KEY(`url`, `bookUrl`), FOREIGN KEY(`bookUrl`) REFERENCES `books`(`bookUrl`) ON UPDATE NO ACTION ON DELETE CASCADE )\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"index\",\n            \"columnName\": \"index\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"resourceUrl\",\n            \"columnName\": \"resourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"tag\",\n            \"columnName\": \"tag\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"start\",\n            \"columnName\": \"start\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"end\",\n            \"columnName\": \"end\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"url\",\n            \"bookUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_chapters_bookUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"bookUrl\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_chapters_bookUrl` ON `${TABLE_NAME}` (`bookUrl`)\"\n          },\n          {\n            \"name\": \"index_chapters_bookUrl_index\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"bookUrl\",\n              \"index\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_chapters_bookUrl_index` ON `${TABLE_NAME}` (`bookUrl`, `index`)\"\n          }\n        ],\n        \"foreignKeys\": [\n          {\n            \"table\": \"books\",\n            \"onDelete\": \"CASCADE\",\n            \"onUpdate\": \"NO ACTION\",\n            \"columns\": [\n              \"bookUrl\"\n            ],\n            \"referencedColumns\": [\n              \"bookUrl\"\n            ]\n          }\n        ]\n      },\n      {\n        \"tableName\": \"replace_rules\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL, `group` TEXT, `pattern` TEXT NOT NULL, `replacement` TEXT NOT NULL, `scope` TEXT, `isEnabled` INTEGER NOT NULL, `isRegex` INTEGER NOT NULL, `sortOrder` INTEGER NOT NULL)\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"group\",\n            \"columnName\": \"group\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"pattern\",\n            \"columnName\": \"pattern\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"replacement\",\n            \"columnName\": \"replacement\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"scope\",\n            \"columnName\": \"scope\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"isEnabled\",\n            \"columnName\": \"isEnabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"isRegex\",\n            \"columnName\": \"isRegex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"sortOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"id\"\n          ],\n          \"autoGenerate\": true\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_replace_rules_id\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"id\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_replace_rules_id` ON `${TABLE_NAME}` (`id`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"searchBooks\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookUrl` TEXT NOT NULL, `origin` TEXT NOT NULL, `originName` TEXT NOT NULL, `type` INTEGER NOT NULL, `name` TEXT NOT NULL, `author` TEXT NOT NULL, `kind` TEXT, `coverUrl` TEXT, `intro` TEXT, `wordCount` TEXT, `latestChapterTitle` TEXT, `tocUrl` TEXT NOT NULL, `time` INTEGER NOT NULL, `variable` TEXT, `originOrder` INTEGER NOT NULL, PRIMARY KEY(`bookUrl`), FOREIGN KEY(`origin`) REFERENCES `book_sources`(`bookSourceUrl`) ON UPDATE NO ACTION ON DELETE CASCADE )\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"originName\",\n            \"columnName\": \"originName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"author\",\n            \"columnName\": \"author\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"kind\",\n            \"columnName\": \"kind\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"coverUrl\",\n            \"columnName\": \"coverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"intro\",\n            \"columnName\": \"intro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"wordCount\",\n            \"columnName\": \"wordCount\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"latestChapterTitle\",\n            \"columnName\": \"latestChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"tocUrl\",\n            \"columnName\": \"tocUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"time\",\n            \"columnName\": \"time\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"originOrder\",\n            \"columnName\": \"originOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"bookUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_searchBooks_bookUrl\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"bookUrl\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_searchBooks_bookUrl` ON `${TABLE_NAME}` (`bookUrl`)\"\n          },\n          {\n            \"name\": \"index_searchBooks_origin\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"origin\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_searchBooks_origin` ON `${TABLE_NAME}` (`origin`)\"\n          }\n        ],\n        \"foreignKeys\": [\n          {\n            \"table\": \"book_sources\",\n            \"onDelete\": \"CASCADE\",\n            \"onUpdate\": \"NO ACTION\",\n            \"columns\": [\n              \"origin\"\n            ],\n            \"referencedColumns\": [\n              \"bookSourceUrl\"\n            ]\n          }\n        ]\n      },\n      {\n        \"tableName\": \"search_keywords\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`word` TEXT NOT NULL, `usage` INTEGER NOT NULL, `lastUseTime` INTEGER NOT NULL, PRIMARY KEY(`word`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"word\",\n            \"columnName\": \"word\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"usage\",\n            \"columnName\": \"usage\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"lastUseTime\",\n            \"columnName\": \"lastUseTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"word\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_search_keywords_word\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"word\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_search_keywords_word` ON `${TABLE_NAME}` (`word`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"cookies\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`url` TEXT NOT NULL, `cookie` TEXT NOT NULL, PRIMARY KEY(`url`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"cookie\",\n            \"columnName\": \"cookie\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"url\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_cookies_url\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"url\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_cookies_url` ON `${TABLE_NAME}` (`url`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssSources\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`sourceUrl` TEXT NOT NULL, `sourceName` TEXT NOT NULL, `sourceIcon` TEXT NOT NULL, `sourceGroup` TEXT, `enabled` INTEGER NOT NULL, `sortUrl` TEXT, `articleStyle` INTEGER NOT NULL, `ruleArticles` TEXT, `ruleNextPage` TEXT, `ruleTitle` TEXT, `rulePubDate` TEXT, `ruleDescription` TEXT, `ruleImage` TEXT, `ruleLink` TEXT, `ruleContent` TEXT, `style` TEXT, `header` TEXT, `enableJs` INTEGER NOT NULL, `loadWithBaseUrl` INTEGER NOT NULL, `customOrder` INTEGER NOT NULL, PRIMARY KEY(`sourceUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"sourceUrl\",\n            \"columnName\": \"sourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceName\",\n            \"columnName\": \"sourceName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceIcon\",\n            \"columnName\": \"sourceIcon\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceGroup\",\n            \"columnName\": \"sourceGroup\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enabled\",\n            \"columnName\": \"enabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sortUrl\",\n            \"columnName\": \"sortUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"articleStyle\",\n            \"columnName\": \"articleStyle\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"ruleArticles\",\n            \"columnName\": \"ruleArticles\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleNextPage\",\n            \"columnName\": \"ruleNextPage\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleTitle\",\n            \"columnName\": \"ruleTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"rulePubDate\",\n            \"columnName\": \"rulePubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleDescription\",\n            \"columnName\": \"ruleDescription\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleImage\",\n            \"columnName\": \"ruleImage\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleLink\",\n            \"columnName\": \"ruleLink\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleContent\",\n            \"columnName\": \"ruleContent\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"style\",\n            \"columnName\": \"style\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"header\",\n            \"columnName\": \"header\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enableJs\",\n            \"columnName\": \"enableJs\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"loadWithBaseUrl\",\n            \"columnName\": \"loadWithBaseUrl\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"sourceUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_rssSources_sourceUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"sourceUrl\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_rssSources_sourceUrl` ON `${TABLE_NAME}` (`sourceUrl`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"bookmarks\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`time` INTEGER NOT NULL, `bookUrl` TEXT NOT NULL, `bookName` TEXT NOT NULL, `chapterIndex` INTEGER NOT NULL, `pageIndex` INTEGER NOT NULL, `chapterName` TEXT NOT NULL, `content` TEXT NOT NULL, PRIMARY KEY(`time`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"time\",\n            \"columnName\": \"time\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookName\",\n            \"columnName\": \"bookName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterIndex\",\n            \"columnName\": \"chapterIndex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"pageIndex\",\n            \"columnName\": \"pageIndex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterName\",\n            \"columnName\": \"chapterName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"time\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_bookmarks_time\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"time\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_bookmarks_time` ON `${TABLE_NAME}` (`time`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssArticles\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`origin` TEXT NOT NULL, `sort` TEXT NOT NULL, `title` TEXT NOT NULL, `order` INTEGER NOT NULL, `link` TEXT NOT NULL, `pubDate` TEXT, `description` TEXT, `content` TEXT, `image` TEXT, `read` INTEGER NOT NULL, PRIMARY KEY(`origin`, `link`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sort\",\n            \"columnName\": \"sort\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"link\",\n            \"columnName\": \"link\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"pubDate\",\n            \"columnName\": \"pubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"description\",\n            \"columnName\": \"description\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"image\",\n            \"columnName\": \"image\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"read\",\n            \"columnName\": \"read\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"origin\",\n            \"link\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssReadRecords\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`record` TEXT NOT NULL, `read` INTEGER NOT NULL, PRIMARY KEY(`record`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"record\",\n            \"columnName\": \"record\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"read\",\n            \"columnName\": \"read\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"record\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssStars\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`origin` TEXT NOT NULL, `sort` TEXT NOT NULL, `title` TEXT NOT NULL, `starTime` INTEGER NOT NULL, `link` TEXT NOT NULL, `pubDate` TEXT, `description` TEXT, `content` TEXT, `image` TEXT, PRIMARY KEY(`origin`, `link`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sort\",\n            \"columnName\": \"sort\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"starTime\",\n            \"columnName\": \"starTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"link\",\n            \"columnName\": \"link\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"pubDate\",\n            \"columnName\": \"pubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"description\",\n            \"columnName\": \"description\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"image\",\n            \"columnName\": \"image\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"origin\",\n            \"link\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"txtTocRules\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `rule` TEXT NOT NULL, `serialNumber` INTEGER NOT NULL, `enable` INTEGER NOT NULL, PRIMARY KEY(`id`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"rule\",\n            \"columnName\": \"rule\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"serialNumber\",\n            \"columnName\": \"serialNumber\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enable\",\n            \"columnName\": \"enable\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"id\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      }\n    ],\n    \"views\": [],\n    \"setupQueries\": [\n      \"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)\",\n      \"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'da04f8cb7f257482f105b1274a7a351b')\"\n    ]\n  }\n}"
  },
  {
    "path": "app/schemas/io.legado.app.data.AppDatabase/14.json",
    "content": "{\n  \"formatVersion\": 1,\n  \"database\": {\n    \"version\": 14,\n    \"identityHash\": \"139ff0cc002ac7be67a60912cd26bac7\",\n    \"entities\": [\n      {\n        \"tableName\": \"books\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookUrl` TEXT NOT NULL, `tocUrl` TEXT NOT NULL, `origin` TEXT NOT NULL, `originName` TEXT NOT NULL, `name` TEXT NOT NULL, `author` TEXT NOT NULL, `kind` TEXT, `customTag` TEXT, `coverUrl` TEXT, `customCoverUrl` TEXT, `intro` TEXT, `customIntro` TEXT, `charset` TEXT, `type` INTEGER NOT NULL, `group` INTEGER NOT NULL, `latestChapterTitle` TEXT, `latestChapterTime` INTEGER NOT NULL, `lastCheckTime` INTEGER NOT NULL, `lastCheckCount` INTEGER NOT NULL, `totalChapterNum` INTEGER NOT NULL, `durChapterTitle` TEXT, `durChapterIndex` INTEGER NOT NULL, `durChapterPos` INTEGER NOT NULL, `durChapterTime` INTEGER NOT NULL, `wordCount` TEXT, `canUpdate` INTEGER NOT NULL, `order` INTEGER NOT NULL, `originOrder` INTEGER NOT NULL, `useReplaceRule` INTEGER NOT NULL, `variable` TEXT, PRIMARY KEY(`bookUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"tocUrl\",\n            \"columnName\": \"tocUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"originName\",\n            \"columnName\": \"originName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"author\",\n            \"columnName\": \"author\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"kind\",\n            \"columnName\": \"kind\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customTag\",\n            \"columnName\": \"customTag\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"coverUrl\",\n            \"columnName\": \"coverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customCoverUrl\",\n            \"columnName\": \"customCoverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"intro\",\n            \"columnName\": \"intro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customIntro\",\n            \"columnName\": \"customIntro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"charset\",\n            \"columnName\": \"charset\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"group\",\n            \"columnName\": \"group\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"latestChapterTitle\",\n            \"columnName\": \"latestChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"latestChapterTime\",\n            \"columnName\": \"latestChapterTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"lastCheckTime\",\n            \"columnName\": \"lastCheckTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"lastCheckCount\",\n            \"columnName\": \"lastCheckCount\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"totalChapterNum\",\n            \"columnName\": \"totalChapterNum\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"durChapterTitle\",\n            \"columnName\": \"durChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"durChapterIndex\",\n            \"columnName\": \"durChapterIndex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"durChapterPos\",\n            \"columnName\": \"durChapterPos\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"durChapterTime\",\n            \"columnName\": \"durChapterTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"wordCount\",\n            \"columnName\": \"wordCount\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"canUpdate\",\n            \"columnName\": \"canUpdate\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"originOrder\",\n            \"columnName\": \"originOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"useReplaceRule\",\n            \"columnName\": \"useReplaceRule\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"bookUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_books_name_author\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"name\",\n              \"author\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_books_name_author` ON `${TABLE_NAME}` (`name`, `author`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"book_groups\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`groupId` INTEGER NOT NULL, `groupName` TEXT NOT NULL, `order` INTEGER NOT NULL, PRIMARY KEY(`groupId`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"groupId\",\n            \"columnName\": \"groupId\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"groupName\",\n            \"columnName\": \"groupName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"groupId\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"book_sources\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookSourceName` TEXT NOT NULL, `bookSourceGroup` TEXT, `bookSourceUrl` TEXT NOT NULL, `bookSourceType` INTEGER NOT NULL, `bookUrlPattern` TEXT, `customOrder` INTEGER NOT NULL, `enabled` INTEGER NOT NULL, `enabledExplore` INTEGER NOT NULL, `header` TEXT, `loginUrl` TEXT, `lastUpdateTime` INTEGER NOT NULL, `weight` INTEGER NOT NULL, `exploreUrl` TEXT, `ruleExplore` TEXT, `searchUrl` TEXT, `ruleSearch` TEXT, `ruleBookInfo` TEXT, `ruleToc` TEXT, `ruleContent` TEXT, PRIMARY KEY(`bookSourceUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookSourceName\",\n            \"columnName\": \"bookSourceName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookSourceGroup\",\n            \"columnName\": \"bookSourceGroup\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"bookSourceUrl\",\n            \"columnName\": \"bookSourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookSourceType\",\n            \"columnName\": \"bookSourceType\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookUrlPattern\",\n            \"columnName\": \"bookUrlPattern\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enabled\",\n            \"columnName\": \"enabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enabledExplore\",\n            \"columnName\": \"enabledExplore\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"header\",\n            \"columnName\": \"header\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUrl\",\n            \"columnName\": \"loginUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"lastUpdateTime\",\n            \"columnName\": \"lastUpdateTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"weight\",\n            \"columnName\": \"weight\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"exploreUrl\",\n            \"columnName\": \"exploreUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleExplore\",\n            \"columnName\": \"ruleExplore\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"searchUrl\",\n            \"columnName\": \"searchUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleSearch\",\n            \"columnName\": \"ruleSearch\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleBookInfo\",\n            \"columnName\": \"ruleBookInfo\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleToc\",\n            \"columnName\": \"ruleToc\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleContent\",\n            \"columnName\": \"ruleContent\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"bookSourceUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_book_sources_bookSourceUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"bookSourceUrl\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_book_sources_bookSourceUrl` ON `${TABLE_NAME}` (`bookSourceUrl`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"chapters\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`url` TEXT NOT NULL, `title` TEXT NOT NULL, `bookUrl` TEXT NOT NULL, `index` INTEGER NOT NULL, `resourceUrl` TEXT, `tag` TEXT, `start` INTEGER, `end` INTEGER, `variable` TEXT, PRIMARY KEY(`url`, `bookUrl`), FOREIGN KEY(`bookUrl`) REFERENCES `books`(`bookUrl`) ON UPDATE NO ACTION ON DELETE CASCADE )\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"index\",\n            \"columnName\": \"index\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"resourceUrl\",\n            \"columnName\": \"resourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"tag\",\n            \"columnName\": \"tag\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"start\",\n            \"columnName\": \"start\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"end\",\n            \"columnName\": \"end\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"url\",\n            \"bookUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_chapters_bookUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"bookUrl\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_chapters_bookUrl` ON `${TABLE_NAME}` (`bookUrl`)\"\n          },\n          {\n            \"name\": \"index_chapters_bookUrl_index\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"bookUrl\",\n              \"index\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_chapters_bookUrl_index` ON `${TABLE_NAME}` (`bookUrl`, `index`)\"\n          }\n        ],\n        \"foreignKeys\": [\n          {\n            \"table\": \"books\",\n            \"onDelete\": \"CASCADE\",\n            \"onUpdate\": \"NO ACTION\",\n            \"columns\": [\n              \"bookUrl\"\n            ],\n            \"referencedColumns\": [\n              \"bookUrl\"\n            ]\n          }\n        ]\n      },\n      {\n        \"tableName\": \"replace_rules\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL, `group` TEXT, `pattern` TEXT NOT NULL, `replacement` TEXT NOT NULL, `scope` TEXT, `isEnabled` INTEGER NOT NULL, `isRegex` INTEGER NOT NULL, `sortOrder` INTEGER NOT NULL)\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"group\",\n            \"columnName\": \"group\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"pattern\",\n            \"columnName\": \"pattern\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"replacement\",\n            \"columnName\": \"replacement\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"scope\",\n            \"columnName\": \"scope\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"isEnabled\",\n            \"columnName\": \"isEnabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"isRegex\",\n            \"columnName\": \"isRegex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"sortOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"id\"\n          ],\n          \"autoGenerate\": true\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_replace_rules_id\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"id\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_replace_rules_id` ON `${TABLE_NAME}` (`id`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"searchBooks\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookUrl` TEXT NOT NULL, `origin` TEXT NOT NULL, `originName` TEXT NOT NULL, `type` INTEGER NOT NULL, `name` TEXT NOT NULL, `author` TEXT NOT NULL, `kind` TEXT, `coverUrl` TEXT, `intro` TEXT, `wordCount` TEXT, `latestChapterTitle` TEXT, `tocUrl` TEXT NOT NULL, `time` INTEGER NOT NULL, `variable` TEXT, `originOrder` INTEGER NOT NULL, PRIMARY KEY(`bookUrl`), FOREIGN KEY(`origin`) REFERENCES `book_sources`(`bookSourceUrl`) ON UPDATE NO ACTION ON DELETE CASCADE )\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"originName\",\n            \"columnName\": \"originName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"author\",\n            \"columnName\": \"author\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"kind\",\n            \"columnName\": \"kind\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"coverUrl\",\n            \"columnName\": \"coverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"intro\",\n            \"columnName\": \"intro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"wordCount\",\n            \"columnName\": \"wordCount\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"latestChapterTitle\",\n            \"columnName\": \"latestChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"tocUrl\",\n            \"columnName\": \"tocUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"time\",\n            \"columnName\": \"time\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"originOrder\",\n            \"columnName\": \"originOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"bookUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_searchBooks_bookUrl\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"bookUrl\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_searchBooks_bookUrl` ON `${TABLE_NAME}` (`bookUrl`)\"\n          },\n          {\n            \"name\": \"index_searchBooks_origin\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"origin\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_searchBooks_origin` ON `${TABLE_NAME}` (`origin`)\"\n          }\n        ],\n        \"foreignKeys\": [\n          {\n            \"table\": \"book_sources\",\n            \"onDelete\": \"CASCADE\",\n            \"onUpdate\": \"NO ACTION\",\n            \"columns\": [\n              \"origin\"\n            ],\n            \"referencedColumns\": [\n              \"bookSourceUrl\"\n            ]\n          }\n        ]\n      },\n      {\n        \"tableName\": \"search_keywords\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`word` TEXT NOT NULL, `usage` INTEGER NOT NULL, `lastUseTime` INTEGER NOT NULL, PRIMARY KEY(`word`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"word\",\n            \"columnName\": \"word\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"usage\",\n            \"columnName\": \"usage\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"lastUseTime\",\n            \"columnName\": \"lastUseTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"word\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_search_keywords_word\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"word\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_search_keywords_word` ON `${TABLE_NAME}` (`word`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"cookies\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`url` TEXT NOT NULL, `cookie` TEXT NOT NULL, PRIMARY KEY(`url`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"cookie\",\n            \"columnName\": \"cookie\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"url\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_cookies_url\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"url\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_cookies_url` ON `${TABLE_NAME}` (`url`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssSources\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`sourceUrl` TEXT NOT NULL, `sourceName` TEXT NOT NULL, `sourceIcon` TEXT NOT NULL, `sourceGroup` TEXT, `enabled` INTEGER NOT NULL, `sortUrl` TEXT, `articleStyle` INTEGER NOT NULL, `ruleArticles` TEXT, `ruleNextPage` TEXT, `ruleTitle` TEXT, `rulePubDate` TEXT, `ruleDescription` TEXT, `ruleImage` TEXT, `ruleLink` TEXT, `ruleContent` TEXT, `style` TEXT, `header` TEXT, `enableJs` INTEGER NOT NULL, `loadWithBaseUrl` INTEGER NOT NULL, `customOrder` INTEGER NOT NULL, PRIMARY KEY(`sourceUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"sourceUrl\",\n            \"columnName\": \"sourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceName\",\n            \"columnName\": \"sourceName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceIcon\",\n            \"columnName\": \"sourceIcon\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceGroup\",\n            \"columnName\": \"sourceGroup\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enabled\",\n            \"columnName\": \"enabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sortUrl\",\n            \"columnName\": \"sortUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"articleStyle\",\n            \"columnName\": \"articleStyle\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"ruleArticles\",\n            \"columnName\": \"ruleArticles\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleNextPage\",\n            \"columnName\": \"ruleNextPage\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleTitle\",\n            \"columnName\": \"ruleTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"rulePubDate\",\n            \"columnName\": \"rulePubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleDescription\",\n            \"columnName\": \"ruleDescription\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleImage\",\n            \"columnName\": \"ruleImage\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleLink\",\n            \"columnName\": \"ruleLink\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleContent\",\n            \"columnName\": \"ruleContent\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"style\",\n            \"columnName\": \"style\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"header\",\n            \"columnName\": \"header\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enableJs\",\n            \"columnName\": \"enableJs\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"loadWithBaseUrl\",\n            \"columnName\": \"loadWithBaseUrl\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"sourceUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_rssSources_sourceUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"sourceUrl\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_rssSources_sourceUrl` ON `${TABLE_NAME}` (`sourceUrl`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"bookmarks\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`time` INTEGER NOT NULL, `bookUrl` TEXT NOT NULL, `bookName` TEXT NOT NULL, `chapterIndex` INTEGER NOT NULL, `pageIndex` INTEGER NOT NULL, `chapterName` TEXT NOT NULL, `content` TEXT NOT NULL, PRIMARY KEY(`time`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"time\",\n            \"columnName\": \"time\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookName\",\n            \"columnName\": \"bookName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterIndex\",\n            \"columnName\": \"chapterIndex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"pageIndex\",\n            \"columnName\": \"pageIndex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterName\",\n            \"columnName\": \"chapterName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"time\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_bookmarks_time\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"time\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_bookmarks_time` ON `${TABLE_NAME}` (`time`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssArticles\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`origin` TEXT NOT NULL, `sort` TEXT NOT NULL, `title` TEXT NOT NULL, `order` INTEGER NOT NULL, `link` TEXT NOT NULL, `pubDate` TEXT, `description` TEXT, `content` TEXT, `image` TEXT, `read` INTEGER NOT NULL, PRIMARY KEY(`origin`, `link`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sort\",\n            \"columnName\": \"sort\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"link\",\n            \"columnName\": \"link\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"pubDate\",\n            \"columnName\": \"pubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"description\",\n            \"columnName\": \"description\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"image\",\n            \"columnName\": \"image\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"read\",\n            \"columnName\": \"read\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"origin\",\n            \"link\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssReadRecords\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`record` TEXT NOT NULL, `read` INTEGER NOT NULL, PRIMARY KEY(`record`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"record\",\n            \"columnName\": \"record\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"read\",\n            \"columnName\": \"read\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"record\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssStars\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`origin` TEXT NOT NULL, `sort` TEXT NOT NULL, `title` TEXT NOT NULL, `starTime` INTEGER NOT NULL, `link` TEXT NOT NULL, `pubDate` TEXT, `description` TEXT, `content` TEXT, `image` TEXT, PRIMARY KEY(`origin`, `link`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sort\",\n            \"columnName\": \"sort\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"starTime\",\n            \"columnName\": \"starTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"link\",\n            \"columnName\": \"link\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"pubDate\",\n            \"columnName\": \"pubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"description\",\n            \"columnName\": \"description\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"image\",\n            \"columnName\": \"image\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"origin\",\n            \"link\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"txtTocRules\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `rule` TEXT NOT NULL, `serialNumber` INTEGER NOT NULL, `enable` INTEGER NOT NULL, PRIMARY KEY(`id`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"rule\",\n            \"columnName\": \"rule\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"serialNumber\",\n            \"columnName\": \"serialNumber\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enable\",\n            \"columnName\": \"enable\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"id\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      }\n    ],\n    \"views\": [],\n    \"setupQueries\": [\n      \"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)\",\n      \"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '139ff0cc002ac7be67a60912cd26bac7')\"\n    ]\n  }\n}"
  },
  {
    "path": "app/schemas/io.legado.app.data.AppDatabase/15.json",
    "content": "{\n  \"formatVersion\": 1,\n  \"database\": {\n    \"version\": 15,\n    \"identityHash\": \"07a0976a08cbae60a16550af5663cde5\",\n    \"entities\": [\n      {\n        \"tableName\": \"books\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookUrl` TEXT NOT NULL, `tocUrl` TEXT NOT NULL, `origin` TEXT NOT NULL, `originName` TEXT NOT NULL, `name` TEXT NOT NULL, `author` TEXT NOT NULL, `kind` TEXT, `customTag` TEXT, `coverUrl` TEXT, `customCoverUrl` TEXT, `intro` TEXT, `customIntro` TEXT, `charset` TEXT, `type` INTEGER NOT NULL, `group` INTEGER NOT NULL, `latestChapterTitle` TEXT, `latestChapterTime` INTEGER NOT NULL, `lastCheckTime` INTEGER NOT NULL, `lastCheckCount` INTEGER NOT NULL, `totalChapterNum` INTEGER NOT NULL, `durChapterTitle` TEXT, `durChapterIndex` INTEGER NOT NULL, `durChapterPos` INTEGER NOT NULL, `durChapterTime` INTEGER NOT NULL, `wordCount` TEXT, `canUpdate` INTEGER NOT NULL, `order` INTEGER NOT NULL, `originOrder` INTEGER NOT NULL, `useReplaceRule` INTEGER NOT NULL, `variable` TEXT, PRIMARY KEY(`bookUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"tocUrl\",\n            \"columnName\": \"tocUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"originName\",\n            \"columnName\": \"originName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"author\",\n            \"columnName\": \"author\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"kind\",\n            \"columnName\": \"kind\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customTag\",\n            \"columnName\": \"customTag\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"coverUrl\",\n            \"columnName\": \"coverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customCoverUrl\",\n            \"columnName\": \"customCoverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"intro\",\n            \"columnName\": \"intro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customIntro\",\n            \"columnName\": \"customIntro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"charset\",\n            \"columnName\": \"charset\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"group\",\n            \"columnName\": \"group\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"latestChapterTitle\",\n            \"columnName\": \"latestChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"latestChapterTime\",\n            \"columnName\": \"latestChapterTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"lastCheckTime\",\n            \"columnName\": \"lastCheckTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"lastCheckCount\",\n            \"columnName\": \"lastCheckCount\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"totalChapterNum\",\n            \"columnName\": \"totalChapterNum\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"durChapterTitle\",\n            \"columnName\": \"durChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"durChapterIndex\",\n            \"columnName\": \"durChapterIndex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"durChapterPos\",\n            \"columnName\": \"durChapterPos\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"durChapterTime\",\n            \"columnName\": \"durChapterTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"wordCount\",\n            \"columnName\": \"wordCount\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"canUpdate\",\n            \"columnName\": \"canUpdate\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"originOrder\",\n            \"columnName\": \"originOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"useReplaceRule\",\n            \"columnName\": \"useReplaceRule\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"bookUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_books_name_author\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"name\",\n              \"author\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_books_name_author` ON `${TABLE_NAME}` (`name`, `author`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"book_groups\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`groupId` INTEGER NOT NULL, `groupName` TEXT NOT NULL, `order` INTEGER NOT NULL, PRIMARY KEY(`groupId`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"groupId\",\n            \"columnName\": \"groupId\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"groupName\",\n            \"columnName\": \"groupName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"groupId\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"book_sources\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookSourceName` TEXT NOT NULL, `bookSourceGroup` TEXT, `bookSourceUrl` TEXT NOT NULL, `bookSourceType` INTEGER NOT NULL, `bookUrlPattern` TEXT, `customOrder` INTEGER NOT NULL, `enabled` INTEGER NOT NULL, `enabledExplore` INTEGER NOT NULL, `header` TEXT, `loginUrl` TEXT, `lastUpdateTime` INTEGER NOT NULL, `weight` INTEGER NOT NULL, `exploreUrl` TEXT, `ruleExplore` TEXT, `searchUrl` TEXT, `ruleSearch` TEXT, `ruleBookInfo` TEXT, `ruleToc` TEXT, `ruleContent` TEXT, PRIMARY KEY(`bookSourceUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookSourceName\",\n            \"columnName\": \"bookSourceName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookSourceGroup\",\n            \"columnName\": \"bookSourceGroup\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"bookSourceUrl\",\n            \"columnName\": \"bookSourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookSourceType\",\n            \"columnName\": \"bookSourceType\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookUrlPattern\",\n            \"columnName\": \"bookUrlPattern\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enabled\",\n            \"columnName\": \"enabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enabledExplore\",\n            \"columnName\": \"enabledExplore\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"header\",\n            \"columnName\": \"header\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUrl\",\n            \"columnName\": \"loginUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"lastUpdateTime\",\n            \"columnName\": \"lastUpdateTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"weight\",\n            \"columnName\": \"weight\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"exploreUrl\",\n            \"columnName\": \"exploreUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleExplore\",\n            \"columnName\": \"ruleExplore\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"searchUrl\",\n            \"columnName\": \"searchUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleSearch\",\n            \"columnName\": \"ruleSearch\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleBookInfo\",\n            \"columnName\": \"ruleBookInfo\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleToc\",\n            \"columnName\": \"ruleToc\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleContent\",\n            \"columnName\": \"ruleContent\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"bookSourceUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_book_sources_bookSourceUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"bookSourceUrl\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_book_sources_bookSourceUrl` ON `${TABLE_NAME}` (`bookSourceUrl`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"chapters\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`url` TEXT NOT NULL, `title` TEXT NOT NULL, `bookUrl` TEXT NOT NULL, `index` INTEGER NOT NULL, `resourceUrl` TEXT, `tag` TEXT, `start` INTEGER, `end` INTEGER, `variable` TEXT, PRIMARY KEY(`url`, `bookUrl`), FOREIGN KEY(`bookUrl`) REFERENCES `books`(`bookUrl`) ON UPDATE NO ACTION ON DELETE CASCADE )\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"index\",\n            \"columnName\": \"index\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"resourceUrl\",\n            \"columnName\": \"resourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"tag\",\n            \"columnName\": \"tag\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"start\",\n            \"columnName\": \"start\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"end\",\n            \"columnName\": \"end\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"url\",\n            \"bookUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_chapters_bookUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"bookUrl\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_chapters_bookUrl` ON `${TABLE_NAME}` (`bookUrl`)\"\n          },\n          {\n            \"name\": \"index_chapters_bookUrl_index\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"bookUrl\",\n              \"index\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_chapters_bookUrl_index` ON `${TABLE_NAME}` (`bookUrl`, `index`)\"\n          }\n        ],\n        \"foreignKeys\": [\n          {\n            \"table\": \"books\",\n            \"onDelete\": \"CASCADE\",\n            \"onUpdate\": \"NO ACTION\",\n            \"columns\": [\n              \"bookUrl\"\n            ],\n            \"referencedColumns\": [\n              \"bookUrl\"\n            ]\n          }\n        ]\n      },\n      {\n        \"tableName\": \"replace_rules\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL, `group` TEXT, `pattern` TEXT NOT NULL, `replacement` TEXT NOT NULL, `scope` TEXT, `isEnabled` INTEGER NOT NULL, `isRegex` INTEGER NOT NULL, `sortOrder` INTEGER NOT NULL)\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"group\",\n            \"columnName\": \"group\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"pattern\",\n            \"columnName\": \"pattern\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"replacement\",\n            \"columnName\": \"replacement\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"scope\",\n            \"columnName\": \"scope\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"isEnabled\",\n            \"columnName\": \"isEnabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"isRegex\",\n            \"columnName\": \"isRegex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"sortOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"id\"\n          ],\n          \"autoGenerate\": true\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_replace_rules_id\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"id\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_replace_rules_id` ON `${TABLE_NAME}` (`id`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"searchBooks\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookUrl` TEXT NOT NULL, `origin` TEXT NOT NULL, `originName` TEXT NOT NULL, `type` INTEGER NOT NULL, `name` TEXT NOT NULL, `author` TEXT NOT NULL, `kind` TEXT, `coverUrl` TEXT, `intro` TEXT, `wordCount` TEXT, `latestChapterTitle` TEXT, `tocUrl` TEXT NOT NULL, `time` INTEGER NOT NULL, `variable` TEXT, `originOrder` INTEGER NOT NULL, PRIMARY KEY(`bookUrl`), FOREIGN KEY(`origin`) REFERENCES `book_sources`(`bookSourceUrl`) ON UPDATE NO ACTION ON DELETE CASCADE )\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"originName\",\n            \"columnName\": \"originName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"author\",\n            \"columnName\": \"author\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"kind\",\n            \"columnName\": \"kind\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"coverUrl\",\n            \"columnName\": \"coverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"intro\",\n            \"columnName\": \"intro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"wordCount\",\n            \"columnName\": \"wordCount\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"latestChapterTitle\",\n            \"columnName\": \"latestChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"tocUrl\",\n            \"columnName\": \"tocUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"time\",\n            \"columnName\": \"time\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"originOrder\",\n            \"columnName\": \"originOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"bookUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_searchBooks_bookUrl\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"bookUrl\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_searchBooks_bookUrl` ON `${TABLE_NAME}` (`bookUrl`)\"\n          },\n          {\n            \"name\": \"index_searchBooks_origin\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"origin\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_searchBooks_origin` ON `${TABLE_NAME}` (`origin`)\"\n          }\n        ],\n        \"foreignKeys\": [\n          {\n            \"table\": \"book_sources\",\n            \"onDelete\": \"CASCADE\",\n            \"onUpdate\": \"NO ACTION\",\n            \"columns\": [\n              \"origin\"\n            ],\n            \"referencedColumns\": [\n              \"bookSourceUrl\"\n            ]\n          }\n        ]\n      },\n      {\n        \"tableName\": \"search_keywords\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`word` TEXT NOT NULL, `usage` INTEGER NOT NULL, `lastUseTime` INTEGER NOT NULL, PRIMARY KEY(`word`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"word\",\n            \"columnName\": \"word\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"usage\",\n            \"columnName\": \"usage\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"lastUseTime\",\n            \"columnName\": \"lastUseTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"word\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_search_keywords_word\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"word\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_search_keywords_word` ON `${TABLE_NAME}` (`word`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"cookies\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`url` TEXT NOT NULL, `cookie` TEXT NOT NULL, PRIMARY KEY(`url`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"cookie\",\n            \"columnName\": \"cookie\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"url\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_cookies_url\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"url\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_cookies_url` ON `${TABLE_NAME}` (`url`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssSources\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`sourceUrl` TEXT NOT NULL, `sourceName` TEXT NOT NULL, `sourceIcon` TEXT NOT NULL, `sourceGroup` TEXT, `enabled` INTEGER NOT NULL, `sortUrl` TEXT, `articleStyle` INTEGER NOT NULL, `ruleArticles` TEXT, `ruleNextPage` TEXT, `ruleTitle` TEXT, `rulePubDate` TEXT, `ruleDescription` TEXT, `ruleImage` TEXT, `ruleLink` TEXT, `ruleContent` TEXT, `style` TEXT, `header` TEXT, `enableJs` INTEGER NOT NULL, `loadWithBaseUrl` INTEGER NOT NULL, `customOrder` INTEGER NOT NULL, PRIMARY KEY(`sourceUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"sourceUrl\",\n            \"columnName\": \"sourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceName\",\n            \"columnName\": \"sourceName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceIcon\",\n            \"columnName\": \"sourceIcon\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceGroup\",\n            \"columnName\": \"sourceGroup\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enabled\",\n            \"columnName\": \"enabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sortUrl\",\n            \"columnName\": \"sortUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"articleStyle\",\n            \"columnName\": \"articleStyle\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"ruleArticles\",\n            \"columnName\": \"ruleArticles\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleNextPage\",\n            \"columnName\": \"ruleNextPage\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleTitle\",\n            \"columnName\": \"ruleTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"rulePubDate\",\n            \"columnName\": \"rulePubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleDescription\",\n            \"columnName\": \"ruleDescription\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleImage\",\n            \"columnName\": \"ruleImage\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleLink\",\n            \"columnName\": \"ruleLink\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleContent\",\n            \"columnName\": \"ruleContent\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"style\",\n            \"columnName\": \"style\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"header\",\n            \"columnName\": \"header\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enableJs\",\n            \"columnName\": \"enableJs\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"loadWithBaseUrl\",\n            \"columnName\": \"loadWithBaseUrl\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"sourceUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_rssSources_sourceUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"sourceUrl\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_rssSources_sourceUrl` ON `${TABLE_NAME}` (`sourceUrl`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"bookmarks\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`time` INTEGER NOT NULL, `bookUrl` TEXT NOT NULL, `bookName` TEXT NOT NULL, `bookAuthor` TEXT NOT NULL, `chapterIndex` INTEGER NOT NULL, `pageIndex` INTEGER NOT NULL, `chapterName` TEXT NOT NULL, `content` TEXT NOT NULL, PRIMARY KEY(`time`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"time\",\n            \"columnName\": \"time\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookName\",\n            \"columnName\": \"bookName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookAuthor\",\n            \"columnName\": \"bookAuthor\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterIndex\",\n            \"columnName\": \"chapterIndex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"pageIndex\",\n            \"columnName\": \"pageIndex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterName\",\n            \"columnName\": \"chapterName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"time\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_bookmarks_time\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"time\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_bookmarks_time` ON `${TABLE_NAME}` (`time`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssArticles\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`origin` TEXT NOT NULL, `sort` TEXT NOT NULL, `title` TEXT NOT NULL, `order` INTEGER NOT NULL, `link` TEXT NOT NULL, `pubDate` TEXT, `description` TEXT, `content` TEXT, `image` TEXT, `read` INTEGER NOT NULL, PRIMARY KEY(`origin`, `link`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sort\",\n            \"columnName\": \"sort\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"link\",\n            \"columnName\": \"link\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"pubDate\",\n            \"columnName\": \"pubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"description\",\n            \"columnName\": \"description\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"image\",\n            \"columnName\": \"image\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"read\",\n            \"columnName\": \"read\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"origin\",\n            \"link\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssReadRecords\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`record` TEXT NOT NULL, `read` INTEGER NOT NULL, PRIMARY KEY(`record`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"record\",\n            \"columnName\": \"record\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"read\",\n            \"columnName\": \"read\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"record\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssStars\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`origin` TEXT NOT NULL, `sort` TEXT NOT NULL, `title` TEXT NOT NULL, `starTime` INTEGER NOT NULL, `link` TEXT NOT NULL, `pubDate` TEXT, `description` TEXT, `content` TEXT, `image` TEXT, PRIMARY KEY(`origin`, `link`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sort\",\n            \"columnName\": \"sort\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"starTime\",\n            \"columnName\": \"starTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"link\",\n            \"columnName\": \"link\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"pubDate\",\n            \"columnName\": \"pubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"description\",\n            \"columnName\": \"description\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"image\",\n            \"columnName\": \"image\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"origin\",\n            \"link\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"txtTocRules\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `rule` TEXT NOT NULL, `serialNumber` INTEGER NOT NULL, `enable` INTEGER NOT NULL, PRIMARY KEY(`id`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"rule\",\n            \"columnName\": \"rule\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"serialNumber\",\n            \"columnName\": \"serialNumber\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enable\",\n            \"columnName\": \"enable\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"id\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      }\n    ],\n    \"views\": [],\n    \"setupQueries\": [\n      \"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)\",\n      \"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '07a0976a08cbae60a16550af5663cde5')\"\n    ]\n  }\n}"
  },
  {
    "path": "app/schemas/io.legado.app.data.AppDatabase/16.json",
    "content": "{\n  \"formatVersion\": 1,\n  \"database\": {\n    \"version\": 16,\n    \"identityHash\": \"ce9320370930dec28d85e2a77fad95e2\",\n    \"entities\": [\n      {\n        \"tableName\": \"books\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookUrl` TEXT NOT NULL, `tocUrl` TEXT NOT NULL, `origin` TEXT NOT NULL, `originName` TEXT NOT NULL, `name` TEXT NOT NULL, `author` TEXT NOT NULL, `kind` TEXT, `customTag` TEXT, `coverUrl` TEXT, `customCoverUrl` TEXT, `intro` TEXT, `customIntro` TEXT, `charset` TEXT, `type` INTEGER NOT NULL, `group` INTEGER NOT NULL, `latestChapterTitle` TEXT, `latestChapterTime` INTEGER NOT NULL, `lastCheckTime` INTEGER NOT NULL, `lastCheckCount` INTEGER NOT NULL, `totalChapterNum` INTEGER NOT NULL, `durChapterTitle` TEXT, `durChapterIndex` INTEGER NOT NULL, `durChapterPos` INTEGER NOT NULL, `durChapterTime` INTEGER NOT NULL, `wordCount` TEXT, `canUpdate` INTEGER NOT NULL, `order` INTEGER NOT NULL, `originOrder` INTEGER NOT NULL, `useReplaceRule` INTEGER NOT NULL, `variable` TEXT, PRIMARY KEY(`bookUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"tocUrl\",\n            \"columnName\": \"tocUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"originName\",\n            \"columnName\": \"originName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"author\",\n            \"columnName\": \"author\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"kind\",\n            \"columnName\": \"kind\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customTag\",\n            \"columnName\": \"customTag\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"coverUrl\",\n            \"columnName\": \"coverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customCoverUrl\",\n            \"columnName\": \"customCoverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"intro\",\n            \"columnName\": \"intro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customIntro\",\n            \"columnName\": \"customIntro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"charset\",\n            \"columnName\": \"charset\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"group\",\n            \"columnName\": \"group\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"latestChapterTitle\",\n            \"columnName\": \"latestChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"latestChapterTime\",\n            \"columnName\": \"latestChapterTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"lastCheckTime\",\n            \"columnName\": \"lastCheckTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"lastCheckCount\",\n            \"columnName\": \"lastCheckCount\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"totalChapterNum\",\n            \"columnName\": \"totalChapterNum\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"durChapterTitle\",\n            \"columnName\": \"durChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"durChapterIndex\",\n            \"columnName\": \"durChapterIndex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"durChapterPos\",\n            \"columnName\": \"durChapterPos\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"durChapterTime\",\n            \"columnName\": \"durChapterTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"wordCount\",\n            \"columnName\": \"wordCount\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"canUpdate\",\n            \"columnName\": \"canUpdate\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"originOrder\",\n            \"columnName\": \"originOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"useReplaceRule\",\n            \"columnName\": \"useReplaceRule\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"bookUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_books_name_author\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"name\",\n              \"author\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_books_name_author` ON `${TABLE_NAME}` (`name`, `author`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"book_groups\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`groupId` INTEGER NOT NULL, `groupName` TEXT NOT NULL, `order` INTEGER NOT NULL, PRIMARY KEY(`groupId`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"groupId\",\n            \"columnName\": \"groupId\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"groupName\",\n            \"columnName\": \"groupName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"groupId\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"book_sources\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookSourceName` TEXT NOT NULL, `bookSourceGroup` TEXT, `bookSourceUrl` TEXT NOT NULL, `bookSourceType` INTEGER NOT NULL, `bookUrlPattern` TEXT, `customOrder` INTEGER NOT NULL, `enabled` INTEGER NOT NULL, `enabledExplore` INTEGER NOT NULL, `header` TEXT, `loginUrl` TEXT, `lastUpdateTime` INTEGER NOT NULL, `weight` INTEGER NOT NULL, `exploreUrl` TEXT, `ruleExplore` TEXT, `searchUrl` TEXT, `ruleSearch` TEXT, `ruleBookInfo` TEXT, `ruleToc` TEXT, `ruleContent` TEXT, PRIMARY KEY(`bookSourceUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookSourceName\",\n            \"columnName\": \"bookSourceName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookSourceGroup\",\n            \"columnName\": \"bookSourceGroup\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"bookSourceUrl\",\n            \"columnName\": \"bookSourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookSourceType\",\n            \"columnName\": \"bookSourceType\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookUrlPattern\",\n            \"columnName\": \"bookUrlPattern\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enabled\",\n            \"columnName\": \"enabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enabledExplore\",\n            \"columnName\": \"enabledExplore\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"header\",\n            \"columnName\": \"header\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUrl\",\n            \"columnName\": \"loginUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"lastUpdateTime\",\n            \"columnName\": \"lastUpdateTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"weight\",\n            \"columnName\": \"weight\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"exploreUrl\",\n            \"columnName\": \"exploreUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleExplore\",\n            \"columnName\": \"ruleExplore\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"searchUrl\",\n            \"columnName\": \"searchUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleSearch\",\n            \"columnName\": \"ruleSearch\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleBookInfo\",\n            \"columnName\": \"ruleBookInfo\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleToc\",\n            \"columnName\": \"ruleToc\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleContent\",\n            \"columnName\": \"ruleContent\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"bookSourceUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_book_sources_bookSourceUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"bookSourceUrl\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_book_sources_bookSourceUrl` ON `${TABLE_NAME}` (`bookSourceUrl`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"chapters\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`url` TEXT NOT NULL, `title` TEXT NOT NULL, `bookUrl` TEXT NOT NULL, `index` INTEGER NOT NULL, `resourceUrl` TEXT, `tag` TEXT, `start` INTEGER, `end` INTEGER, `variable` TEXT, PRIMARY KEY(`url`, `bookUrl`), FOREIGN KEY(`bookUrl`) REFERENCES `books`(`bookUrl`) ON UPDATE NO ACTION ON DELETE CASCADE )\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"index\",\n            \"columnName\": \"index\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"resourceUrl\",\n            \"columnName\": \"resourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"tag\",\n            \"columnName\": \"tag\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"start\",\n            \"columnName\": \"start\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"end\",\n            \"columnName\": \"end\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"url\",\n            \"bookUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_chapters_bookUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"bookUrl\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_chapters_bookUrl` ON `${TABLE_NAME}` (`bookUrl`)\"\n          },\n          {\n            \"name\": \"index_chapters_bookUrl_index\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"bookUrl\",\n              \"index\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_chapters_bookUrl_index` ON `${TABLE_NAME}` (`bookUrl`, `index`)\"\n          }\n        ],\n        \"foreignKeys\": [\n          {\n            \"table\": \"books\",\n            \"onDelete\": \"CASCADE\",\n            \"onUpdate\": \"NO ACTION\",\n            \"columns\": [\n              \"bookUrl\"\n            ],\n            \"referencedColumns\": [\n              \"bookUrl\"\n            ]\n          }\n        ]\n      },\n      {\n        \"tableName\": \"replace_rules\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL, `group` TEXT, `pattern` TEXT NOT NULL, `replacement` TEXT NOT NULL, `scope` TEXT, `isEnabled` INTEGER NOT NULL, `isRegex` INTEGER NOT NULL, `sortOrder` INTEGER NOT NULL)\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"group\",\n            \"columnName\": \"group\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"pattern\",\n            \"columnName\": \"pattern\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"replacement\",\n            \"columnName\": \"replacement\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"scope\",\n            \"columnName\": \"scope\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"isEnabled\",\n            \"columnName\": \"isEnabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"isRegex\",\n            \"columnName\": \"isRegex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"sortOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"id\"\n          ],\n          \"autoGenerate\": true\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_replace_rules_id\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"id\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_replace_rules_id` ON `${TABLE_NAME}` (`id`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"searchBooks\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookUrl` TEXT NOT NULL, `origin` TEXT NOT NULL, `originName` TEXT NOT NULL, `type` INTEGER NOT NULL, `name` TEXT NOT NULL, `author` TEXT NOT NULL, `kind` TEXT, `coverUrl` TEXT, `intro` TEXT, `wordCount` TEXT, `latestChapterTitle` TEXT, `tocUrl` TEXT NOT NULL, `time` INTEGER NOT NULL, `variable` TEXT, `originOrder` INTEGER NOT NULL, PRIMARY KEY(`bookUrl`), FOREIGN KEY(`origin`) REFERENCES `book_sources`(`bookSourceUrl`) ON UPDATE NO ACTION ON DELETE CASCADE )\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"originName\",\n            \"columnName\": \"originName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"author\",\n            \"columnName\": \"author\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"kind\",\n            \"columnName\": \"kind\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"coverUrl\",\n            \"columnName\": \"coverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"intro\",\n            \"columnName\": \"intro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"wordCount\",\n            \"columnName\": \"wordCount\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"latestChapterTitle\",\n            \"columnName\": \"latestChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"tocUrl\",\n            \"columnName\": \"tocUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"time\",\n            \"columnName\": \"time\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"originOrder\",\n            \"columnName\": \"originOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"bookUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_searchBooks_bookUrl\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"bookUrl\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_searchBooks_bookUrl` ON `${TABLE_NAME}` (`bookUrl`)\"\n          },\n          {\n            \"name\": \"index_searchBooks_origin\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"origin\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_searchBooks_origin` ON `${TABLE_NAME}` (`origin`)\"\n          }\n        ],\n        \"foreignKeys\": [\n          {\n            \"table\": \"book_sources\",\n            \"onDelete\": \"CASCADE\",\n            \"onUpdate\": \"NO ACTION\",\n            \"columns\": [\n              \"origin\"\n            ],\n            \"referencedColumns\": [\n              \"bookSourceUrl\"\n            ]\n          }\n        ]\n      },\n      {\n        \"tableName\": \"search_keywords\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`word` TEXT NOT NULL, `usage` INTEGER NOT NULL, `lastUseTime` INTEGER NOT NULL, PRIMARY KEY(`word`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"word\",\n            \"columnName\": \"word\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"usage\",\n            \"columnName\": \"usage\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"lastUseTime\",\n            \"columnName\": \"lastUseTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"word\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_search_keywords_word\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"word\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_search_keywords_word` ON `${TABLE_NAME}` (`word`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"cookies\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`url` TEXT NOT NULL, `cookie` TEXT NOT NULL, PRIMARY KEY(`url`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"cookie\",\n            \"columnName\": \"cookie\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"url\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_cookies_url\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"url\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_cookies_url` ON `${TABLE_NAME}` (`url`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssSources\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`sourceUrl` TEXT NOT NULL, `sourceName` TEXT NOT NULL, `sourceIcon` TEXT NOT NULL, `sourceGroup` TEXT, `enabled` INTEGER NOT NULL, `sortUrl` TEXT, `articleStyle` INTEGER NOT NULL, `ruleArticles` TEXT, `ruleNextPage` TEXT, `ruleTitle` TEXT, `rulePubDate` TEXT, `ruleDescription` TEXT, `ruleImage` TEXT, `ruleLink` TEXT, `ruleContent` TEXT, `style` TEXT, `header` TEXT, `enableJs` INTEGER NOT NULL, `loadWithBaseUrl` INTEGER NOT NULL, `customOrder` INTEGER NOT NULL, PRIMARY KEY(`sourceUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"sourceUrl\",\n            \"columnName\": \"sourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceName\",\n            \"columnName\": \"sourceName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceIcon\",\n            \"columnName\": \"sourceIcon\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceGroup\",\n            \"columnName\": \"sourceGroup\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enabled\",\n            \"columnName\": \"enabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sortUrl\",\n            \"columnName\": \"sortUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"articleStyle\",\n            \"columnName\": \"articleStyle\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"ruleArticles\",\n            \"columnName\": \"ruleArticles\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleNextPage\",\n            \"columnName\": \"ruleNextPage\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleTitle\",\n            \"columnName\": \"ruleTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"rulePubDate\",\n            \"columnName\": \"rulePubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleDescription\",\n            \"columnName\": \"ruleDescription\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleImage\",\n            \"columnName\": \"ruleImage\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleLink\",\n            \"columnName\": \"ruleLink\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleContent\",\n            \"columnName\": \"ruleContent\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"style\",\n            \"columnName\": \"style\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"header\",\n            \"columnName\": \"header\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enableJs\",\n            \"columnName\": \"enableJs\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"loadWithBaseUrl\",\n            \"columnName\": \"loadWithBaseUrl\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"sourceUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_rssSources_sourceUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"sourceUrl\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_rssSources_sourceUrl` ON `${TABLE_NAME}` (`sourceUrl`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"bookmarks\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`time` INTEGER NOT NULL, `bookUrl` TEXT NOT NULL, `bookName` TEXT NOT NULL, `bookAuthor` TEXT NOT NULL, `chapterIndex` INTEGER NOT NULL, `pageIndex` INTEGER NOT NULL, `chapterName` TEXT NOT NULL, `content` TEXT NOT NULL, PRIMARY KEY(`time`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"time\",\n            \"columnName\": \"time\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookName\",\n            \"columnName\": \"bookName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookAuthor\",\n            \"columnName\": \"bookAuthor\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterIndex\",\n            \"columnName\": \"chapterIndex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"pageIndex\",\n            \"columnName\": \"pageIndex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterName\",\n            \"columnName\": \"chapterName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"time\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_bookmarks_time\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"time\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_bookmarks_time` ON `${TABLE_NAME}` (`time`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssArticles\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`origin` TEXT NOT NULL, `sort` TEXT NOT NULL, `title` TEXT NOT NULL, `order` INTEGER NOT NULL, `link` TEXT NOT NULL, `pubDate` TEXT, `description` TEXT, `content` TEXT, `image` TEXT, `read` INTEGER NOT NULL, PRIMARY KEY(`origin`, `link`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sort\",\n            \"columnName\": \"sort\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"link\",\n            \"columnName\": \"link\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"pubDate\",\n            \"columnName\": \"pubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"description\",\n            \"columnName\": \"description\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"image\",\n            \"columnName\": \"image\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"read\",\n            \"columnName\": \"read\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"origin\",\n            \"link\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssReadRecords\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`record` TEXT NOT NULL, `read` INTEGER NOT NULL, PRIMARY KEY(`record`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"record\",\n            \"columnName\": \"record\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"read\",\n            \"columnName\": \"read\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"record\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssStars\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`origin` TEXT NOT NULL, `sort` TEXT NOT NULL, `title` TEXT NOT NULL, `starTime` INTEGER NOT NULL, `link` TEXT NOT NULL, `pubDate` TEXT, `description` TEXT, `content` TEXT, `image` TEXT, PRIMARY KEY(`origin`, `link`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sort\",\n            \"columnName\": \"sort\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"starTime\",\n            \"columnName\": \"starTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"link\",\n            \"columnName\": \"link\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"pubDate\",\n            \"columnName\": \"pubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"description\",\n            \"columnName\": \"description\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"image\",\n            \"columnName\": \"image\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"origin\",\n            \"link\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"txtTocRules\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `rule` TEXT NOT NULL, `serialNumber` INTEGER NOT NULL, `enable` INTEGER NOT NULL, PRIMARY KEY(`id`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"rule\",\n            \"columnName\": \"rule\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"serialNumber\",\n            \"columnName\": \"serialNumber\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enable\",\n            \"columnName\": \"enable\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"id\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"readRecord\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookName` TEXT NOT NULL, `readTime` INTEGER NOT NULL, PRIMARY KEY(`bookName`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookName\",\n            \"columnName\": \"bookName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"readTime\",\n            \"columnName\": \"readTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"bookName\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      }\n    ],\n    \"views\": [],\n    \"setupQueries\": [\n      \"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)\",\n      \"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'ce9320370930dec28d85e2a77fad95e2')\"\n    ]\n  }\n}"
  },
  {
    "path": "app/schemas/io.legado.app.data.AppDatabase/17.json",
    "content": "{\n  \"formatVersion\": 1,\n  \"database\": {\n    \"version\": 17,\n    \"identityHash\": \"ce9320370930dec28d85e2a77fad95e2\",\n    \"entities\": [\n      {\n        \"tableName\": \"books\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookUrl` TEXT NOT NULL, `tocUrl` TEXT NOT NULL, `origin` TEXT NOT NULL, `originName` TEXT NOT NULL, `name` TEXT NOT NULL, `author` TEXT NOT NULL, `kind` TEXT, `customTag` TEXT, `coverUrl` TEXT, `customCoverUrl` TEXT, `intro` TEXT, `customIntro` TEXT, `charset` TEXT, `type` INTEGER NOT NULL, `group` INTEGER NOT NULL, `latestChapterTitle` TEXT, `latestChapterTime` INTEGER NOT NULL, `lastCheckTime` INTEGER NOT NULL, `lastCheckCount` INTEGER NOT NULL, `totalChapterNum` INTEGER NOT NULL, `durChapterTitle` TEXT, `durChapterIndex` INTEGER NOT NULL, `durChapterPos` INTEGER NOT NULL, `durChapterTime` INTEGER NOT NULL, `wordCount` TEXT, `canUpdate` INTEGER NOT NULL, `order` INTEGER NOT NULL, `originOrder` INTEGER NOT NULL, `useReplaceRule` INTEGER NOT NULL, `variable` TEXT, PRIMARY KEY(`bookUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"tocUrl\",\n            \"columnName\": \"tocUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"originName\",\n            \"columnName\": \"originName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"author\",\n            \"columnName\": \"author\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"kind\",\n            \"columnName\": \"kind\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customTag\",\n            \"columnName\": \"customTag\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"coverUrl\",\n            \"columnName\": \"coverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customCoverUrl\",\n            \"columnName\": \"customCoverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"intro\",\n            \"columnName\": \"intro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customIntro\",\n            \"columnName\": \"customIntro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"charset\",\n            \"columnName\": \"charset\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"group\",\n            \"columnName\": \"group\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"latestChapterTitle\",\n            \"columnName\": \"latestChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"latestChapterTime\",\n            \"columnName\": \"latestChapterTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"lastCheckTime\",\n            \"columnName\": \"lastCheckTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"lastCheckCount\",\n            \"columnName\": \"lastCheckCount\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"totalChapterNum\",\n            \"columnName\": \"totalChapterNum\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"durChapterTitle\",\n            \"columnName\": \"durChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"durChapterIndex\",\n            \"columnName\": \"durChapterIndex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"durChapterPos\",\n            \"columnName\": \"durChapterPos\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"durChapterTime\",\n            \"columnName\": \"durChapterTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"wordCount\",\n            \"columnName\": \"wordCount\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"canUpdate\",\n            \"columnName\": \"canUpdate\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"originOrder\",\n            \"columnName\": \"originOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"useReplaceRule\",\n            \"columnName\": \"useReplaceRule\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"bookUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_books_name_author\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"name\",\n              \"author\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_books_name_author` ON `${TABLE_NAME}` (`name`, `author`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"book_groups\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`groupId` INTEGER NOT NULL, `groupName` TEXT NOT NULL, `order` INTEGER NOT NULL, PRIMARY KEY(`groupId`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"groupId\",\n            \"columnName\": \"groupId\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"groupName\",\n            \"columnName\": \"groupName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"groupId\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"book_sources\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookSourceName` TEXT NOT NULL, `bookSourceGroup` TEXT, `bookSourceUrl` TEXT NOT NULL, `bookSourceType` INTEGER NOT NULL, `bookUrlPattern` TEXT, `customOrder` INTEGER NOT NULL, `enabled` INTEGER NOT NULL, `enabledExplore` INTEGER NOT NULL, `header` TEXT, `loginUrl` TEXT, `lastUpdateTime` INTEGER NOT NULL, `weight` INTEGER NOT NULL, `exploreUrl` TEXT, `ruleExplore` TEXT, `searchUrl` TEXT, `ruleSearch` TEXT, `ruleBookInfo` TEXT, `ruleToc` TEXT, `ruleContent` TEXT, PRIMARY KEY(`bookSourceUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookSourceName\",\n            \"columnName\": \"bookSourceName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookSourceGroup\",\n            \"columnName\": \"bookSourceGroup\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"bookSourceUrl\",\n            \"columnName\": \"bookSourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookSourceType\",\n            \"columnName\": \"bookSourceType\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookUrlPattern\",\n            \"columnName\": \"bookUrlPattern\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enabled\",\n            \"columnName\": \"enabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enabledExplore\",\n            \"columnName\": \"enabledExplore\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"header\",\n            \"columnName\": \"header\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUrl\",\n            \"columnName\": \"loginUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"lastUpdateTime\",\n            \"columnName\": \"lastUpdateTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"weight\",\n            \"columnName\": \"weight\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"exploreUrl\",\n            \"columnName\": \"exploreUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleExplore\",\n            \"columnName\": \"ruleExplore\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"searchUrl\",\n            \"columnName\": \"searchUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleSearch\",\n            \"columnName\": \"ruleSearch\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleBookInfo\",\n            \"columnName\": \"ruleBookInfo\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleToc\",\n            \"columnName\": \"ruleToc\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleContent\",\n            \"columnName\": \"ruleContent\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"bookSourceUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_book_sources_bookSourceUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"bookSourceUrl\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_book_sources_bookSourceUrl` ON `${TABLE_NAME}` (`bookSourceUrl`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"chapters\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`url` TEXT NOT NULL, `title` TEXT NOT NULL, `bookUrl` TEXT NOT NULL, `index` INTEGER NOT NULL, `resourceUrl` TEXT, `tag` TEXT, `start` INTEGER, `end` INTEGER, `variable` TEXT, PRIMARY KEY(`url`, `bookUrl`), FOREIGN KEY(`bookUrl`) REFERENCES `books`(`bookUrl`) ON UPDATE NO ACTION ON DELETE CASCADE )\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"index\",\n            \"columnName\": \"index\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"resourceUrl\",\n            \"columnName\": \"resourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"tag\",\n            \"columnName\": \"tag\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"start\",\n            \"columnName\": \"start\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"end\",\n            \"columnName\": \"end\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"url\",\n            \"bookUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_chapters_bookUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"bookUrl\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_chapters_bookUrl` ON `${TABLE_NAME}` (`bookUrl`)\"\n          },\n          {\n            \"name\": \"index_chapters_bookUrl_index\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"bookUrl\",\n              \"index\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_chapters_bookUrl_index` ON `${TABLE_NAME}` (`bookUrl`, `index`)\"\n          }\n        ],\n        \"foreignKeys\": [\n          {\n            \"table\": \"books\",\n            \"onDelete\": \"CASCADE\",\n            \"onUpdate\": \"NO ACTION\",\n            \"columns\": [\n              \"bookUrl\"\n            ],\n            \"referencedColumns\": [\n              \"bookUrl\"\n            ]\n          }\n        ]\n      },\n      {\n        \"tableName\": \"replace_rules\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL, `group` TEXT, `pattern` TEXT NOT NULL, `replacement` TEXT NOT NULL, `scope` TEXT, `isEnabled` INTEGER NOT NULL, `isRegex` INTEGER NOT NULL, `sortOrder` INTEGER NOT NULL)\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"group\",\n            \"columnName\": \"group\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"pattern\",\n            \"columnName\": \"pattern\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"replacement\",\n            \"columnName\": \"replacement\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"scope\",\n            \"columnName\": \"scope\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"isEnabled\",\n            \"columnName\": \"isEnabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"isRegex\",\n            \"columnName\": \"isRegex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"sortOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"id\"\n          ],\n          \"autoGenerate\": true\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_replace_rules_id\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"id\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_replace_rules_id` ON `${TABLE_NAME}` (`id`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"searchBooks\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookUrl` TEXT NOT NULL, `origin` TEXT NOT NULL, `originName` TEXT NOT NULL, `type` INTEGER NOT NULL, `name` TEXT NOT NULL, `author` TEXT NOT NULL, `kind` TEXT, `coverUrl` TEXT, `intro` TEXT, `wordCount` TEXT, `latestChapterTitle` TEXT, `tocUrl` TEXT NOT NULL, `time` INTEGER NOT NULL, `variable` TEXT, `originOrder` INTEGER NOT NULL, PRIMARY KEY(`bookUrl`), FOREIGN KEY(`origin`) REFERENCES `book_sources`(`bookSourceUrl`) ON UPDATE NO ACTION ON DELETE CASCADE )\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"originName\",\n            \"columnName\": \"originName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"author\",\n            \"columnName\": \"author\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"kind\",\n            \"columnName\": \"kind\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"coverUrl\",\n            \"columnName\": \"coverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"intro\",\n            \"columnName\": \"intro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"wordCount\",\n            \"columnName\": \"wordCount\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"latestChapterTitle\",\n            \"columnName\": \"latestChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"tocUrl\",\n            \"columnName\": \"tocUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"time\",\n            \"columnName\": \"time\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"originOrder\",\n            \"columnName\": \"originOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"bookUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_searchBooks_bookUrl\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"bookUrl\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_searchBooks_bookUrl` ON `${TABLE_NAME}` (`bookUrl`)\"\n          },\n          {\n            \"name\": \"index_searchBooks_origin\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"origin\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_searchBooks_origin` ON `${TABLE_NAME}` (`origin`)\"\n          }\n        ],\n        \"foreignKeys\": [\n          {\n            \"table\": \"book_sources\",\n            \"onDelete\": \"CASCADE\",\n            \"onUpdate\": \"NO ACTION\",\n            \"columns\": [\n              \"origin\"\n            ],\n            \"referencedColumns\": [\n              \"bookSourceUrl\"\n            ]\n          }\n        ]\n      },\n      {\n        \"tableName\": \"search_keywords\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`word` TEXT NOT NULL, `usage` INTEGER NOT NULL, `lastUseTime` INTEGER NOT NULL, PRIMARY KEY(`word`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"word\",\n            \"columnName\": \"word\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"usage\",\n            \"columnName\": \"usage\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"lastUseTime\",\n            \"columnName\": \"lastUseTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"word\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_search_keywords_word\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"word\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_search_keywords_word` ON `${TABLE_NAME}` (`word`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"cookies\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`url` TEXT NOT NULL, `cookie` TEXT NOT NULL, PRIMARY KEY(`url`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"cookie\",\n            \"columnName\": \"cookie\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"url\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_cookies_url\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"url\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_cookies_url` ON `${TABLE_NAME}` (`url`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssSources\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`sourceUrl` TEXT NOT NULL, `sourceName` TEXT NOT NULL, `sourceIcon` TEXT NOT NULL, `sourceGroup` TEXT, `enabled` INTEGER NOT NULL, `sortUrl` TEXT, `articleStyle` INTEGER NOT NULL, `ruleArticles` TEXT, `ruleNextPage` TEXT, `ruleTitle` TEXT, `rulePubDate` TEXT, `ruleDescription` TEXT, `ruleImage` TEXT, `ruleLink` TEXT, `ruleContent` TEXT, `style` TEXT, `header` TEXT, `enableJs` INTEGER NOT NULL, `loadWithBaseUrl` INTEGER NOT NULL, `customOrder` INTEGER NOT NULL, PRIMARY KEY(`sourceUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"sourceUrl\",\n            \"columnName\": \"sourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceName\",\n            \"columnName\": \"sourceName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceIcon\",\n            \"columnName\": \"sourceIcon\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceGroup\",\n            \"columnName\": \"sourceGroup\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enabled\",\n            \"columnName\": \"enabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sortUrl\",\n            \"columnName\": \"sortUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"articleStyle\",\n            \"columnName\": \"articleStyle\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"ruleArticles\",\n            \"columnName\": \"ruleArticles\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleNextPage\",\n            \"columnName\": \"ruleNextPage\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleTitle\",\n            \"columnName\": \"ruleTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"rulePubDate\",\n            \"columnName\": \"rulePubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleDescription\",\n            \"columnName\": \"ruleDescription\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleImage\",\n            \"columnName\": \"ruleImage\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleLink\",\n            \"columnName\": \"ruleLink\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleContent\",\n            \"columnName\": \"ruleContent\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"style\",\n            \"columnName\": \"style\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"header\",\n            \"columnName\": \"header\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enableJs\",\n            \"columnName\": \"enableJs\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"loadWithBaseUrl\",\n            \"columnName\": \"loadWithBaseUrl\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"sourceUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_rssSources_sourceUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"sourceUrl\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_rssSources_sourceUrl` ON `${TABLE_NAME}` (`sourceUrl`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"bookmarks\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`time` INTEGER NOT NULL, `bookUrl` TEXT NOT NULL, `bookName` TEXT NOT NULL, `bookAuthor` TEXT NOT NULL, `chapterIndex` INTEGER NOT NULL, `pageIndex` INTEGER NOT NULL, `chapterName` TEXT NOT NULL, `content` TEXT NOT NULL, PRIMARY KEY(`time`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"time\",\n            \"columnName\": \"time\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookName\",\n            \"columnName\": \"bookName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookAuthor\",\n            \"columnName\": \"bookAuthor\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterIndex\",\n            \"columnName\": \"chapterIndex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"pageIndex\",\n            \"columnName\": \"pageIndex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterName\",\n            \"columnName\": \"chapterName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"time\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_bookmarks_time\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"time\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_bookmarks_time` ON `${TABLE_NAME}` (`time`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssArticles\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`origin` TEXT NOT NULL, `sort` TEXT NOT NULL, `title` TEXT NOT NULL, `order` INTEGER NOT NULL, `link` TEXT NOT NULL, `pubDate` TEXT, `description` TEXT, `content` TEXT, `image` TEXT, `read` INTEGER NOT NULL, PRIMARY KEY(`origin`, `link`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sort\",\n            \"columnName\": \"sort\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"link\",\n            \"columnName\": \"link\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"pubDate\",\n            \"columnName\": \"pubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"description\",\n            \"columnName\": \"description\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"image\",\n            \"columnName\": \"image\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"read\",\n            \"columnName\": \"read\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"origin\",\n            \"link\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssReadRecords\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`record` TEXT NOT NULL, `read` INTEGER NOT NULL, PRIMARY KEY(`record`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"record\",\n            \"columnName\": \"record\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"read\",\n            \"columnName\": \"read\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"record\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssStars\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`origin` TEXT NOT NULL, `sort` TEXT NOT NULL, `title` TEXT NOT NULL, `starTime` INTEGER NOT NULL, `link` TEXT NOT NULL, `pubDate` TEXT, `description` TEXT, `content` TEXT, `image` TEXT, PRIMARY KEY(`origin`, `link`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sort\",\n            \"columnName\": \"sort\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"starTime\",\n            \"columnName\": \"starTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"link\",\n            \"columnName\": \"link\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"pubDate\",\n            \"columnName\": \"pubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"description\",\n            \"columnName\": \"description\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"image\",\n            \"columnName\": \"image\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"origin\",\n            \"link\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"txtTocRules\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `rule` TEXT NOT NULL, `serialNumber` INTEGER NOT NULL, `enable` INTEGER NOT NULL, PRIMARY KEY(`id`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"rule\",\n            \"columnName\": \"rule\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"serialNumber\",\n            \"columnName\": \"serialNumber\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enable\",\n            \"columnName\": \"enable\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"id\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"readRecord\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookName` TEXT NOT NULL, `readTime` INTEGER NOT NULL, PRIMARY KEY(`bookName`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookName\",\n            \"columnName\": \"bookName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"readTime\",\n            \"columnName\": \"readTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"bookName\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      }\n    ],\n    \"views\": [],\n    \"setupQueries\": [\n      \"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)\",\n      \"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'ce9320370930dec28d85e2a77fad95e2')\"\n    ]\n  }\n}"
  },
  {
    "path": "app/schemas/io.legado.app.data.AppDatabase/18.json",
    "content": "{\n  \"formatVersion\": 1,\n  \"database\": {\n    \"version\": 18,\n    \"identityHash\": \"ec3badfc86fb8187260ab26fb78a2d3f\",\n    \"entities\": [\n      {\n        \"tableName\": \"books\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookUrl` TEXT NOT NULL, `tocUrl` TEXT NOT NULL, `origin` TEXT NOT NULL, `originName` TEXT NOT NULL, `name` TEXT NOT NULL, `author` TEXT NOT NULL, `kind` TEXT, `customTag` TEXT, `coverUrl` TEXT, `customCoverUrl` TEXT, `intro` TEXT, `customIntro` TEXT, `charset` TEXT, `type` INTEGER NOT NULL, `group` INTEGER NOT NULL, `latestChapterTitle` TEXT, `latestChapterTime` INTEGER NOT NULL, `lastCheckTime` INTEGER NOT NULL, `lastCheckCount` INTEGER NOT NULL, `totalChapterNum` INTEGER NOT NULL, `durChapterTitle` TEXT, `durChapterIndex` INTEGER NOT NULL, `durChapterPos` INTEGER NOT NULL, `durChapterTime` INTEGER NOT NULL, `wordCount` TEXT, `canUpdate` INTEGER NOT NULL, `order` INTEGER NOT NULL, `originOrder` INTEGER NOT NULL, `useReplaceRule` INTEGER NOT NULL, `variable` TEXT, PRIMARY KEY(`bookUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"tocUrl\",\n            \"columnName\": \"tocUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"originName\",\n            \"columnName\": \"originName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"author\",\n            \"columnName\": \"author\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"kind\",\n            \"columnName\": \"kind\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customTag\",\n            \"columnName\": \"customTag\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"coverUrl\",\n            \"columnName\": \"coverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customCoverUrl\",\n            \"columnName\": \"customCoverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"intro\",\n            \"columnName\": \"intro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customIntro\",\n            \"columnName\": \"customIntro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"charset\",\n            \"columnName\": \"charset\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"group\",\n            \"columnName\": \"group\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"latestChapterTitle\",\n            \"columnName\": \"latestChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"latestChapterTime\",\n            \"columnName\": \"latestChapterTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"lastCheckTime\",\n            \"columnName\": \"lastCheckTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"lastCheckCount\",\n            \"columnName\": \"lastCheckCount\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"totalChapterNum\",\n            \"columnName\": \"totalChapterNum\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"durChapterTitle\",\n            \"columnName\": \"durChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"durChapterIndex\",\n            \"columnName\": \"durChapterIndex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"durChapterPos\",\n            \"columnName\": \"durChapterPos\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"durChapterTime\",\n            \"columnName\": \"durChapterTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"wordCount\",\n            \"columnName\": \"wordCount\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"canUpdate\",\n            \"columnName\": \"canUpdate\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"originOrder\",\n            \"columnName\": \"originOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"useReplaceRule\",\n            \"columnName\": \"useReplaceRule\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"bookUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_books_name_author\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"name\",\n              \"author\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_books_name_author` ON `${TABLE_NAME}` (`name`, `author`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"book_groups\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`groupId` INTEGER NOT NULL, `groupName` TEXT NOT NULL, `order` INTEGER NOT NULL, PRIMARY KEY(`groupId`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"groupId\",\n            \"columnName\": \"groupId\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"groupName\",\n            \"columnName\": \"groupName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"groupId\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"book_sources\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookSourceName` TEXT NOT NULL, `bookSourceGroup` TEXT, `bookSourceUrl` TEXT NOT NULL, `bookSourceType` INTEGER NOT NULL, `bookUrlPattern` TEXT, `customOrder` INTEGER NOT NULL, `enabled` INTEGER NOT NULL, `enabledExplore` INTEGER NOT NULL, `header` TEXT, `loginUrl` TEXT, `lastUpdateTime` INTEGER NOT NULL, `weight` INTEGER NOT NULL, `exploreUrl` TEXT, `ruleExplore` TEXT, `searchUrl` TEXT, `ruleSearch` TEXT, `ruleBookInfo` TEXT, `ruleToc` TEXT, `ruleContent` TEXT, PRIMARY KEY(`bookSourceUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookSourceName\",\n            \"columnName\": \"bookSourceName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookSourceGroup\",\n            \"columnName\": \"bookSourceGroup\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"bookSourceUrl\",\n            \"columnName\": \"bookSourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookSourceType\",\n            \"columnName\": \"bookSourceType\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookUrlPattern\",\n            \"columnName\": \"bookUrlPattern\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enabled\",\n            \"columnName\": \"enabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enabledExplore\",\n            \"columnName\": \"enabledExplore\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"header\",\n            \"columnName\": \"header\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUrl\",\n            \"columnName\": \"loginUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"lastUpdateTime\",\n            \"columnName\": \"lastUpdateTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"weight\",\n            \"columnName\": \"weight\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"exploreUrl\",\n            \"columnName\": \"exploreUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleExplore\",\n            \"columnName\": \"ruleExplore\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"searchUrl\",\n            \"columnName\": \"searchUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleSearch\",\n            \"columnName\": \"ruleSearch\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleBookInfo\",\n            \"columnName\": \"ruleBookInfo\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleToc\",\n            \"columnName\": \"ruleToc\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleContent\",\n            \"columnName\": \"ruleContent\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"bookSourceUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_book_sources_bookSourceUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"bookSourceUrl\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_book_sources_bookSourceUrl` ON `${TABLE_NAME}` (`bookSourceUrl`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"chapters\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`url` TEXT NOT NULL, `title` TEXT NOT NULL, `bookUrl` TEXT NOT NULL, `index` INTEGER NOT NULL, `resourceUrl` TEXT, `tag` TEXT, `start` INTEGER, `end` INTEGER, `variable` TEXT, PRIMARY KEY(`url`, `bookUrl`), FOREIGN KEY(`bookUrl`) REFERENCES `books`(`bookUrl`) ON UPDATE NO ACTION ON DELETE CASCADE )\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"index\",\n            \"columnName\": \"index\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"resourceUrl\",\n            \"columnName\": \"resourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"tag\",\n            \"columnName\": \"tag\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"start\",\n            \"columnName\": \"start\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"end\",\n            \"columnName\": \"end\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"url\",\n            \"bookUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_chapters_bookUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"bookUrl\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_chapters_bookUrl` ON `${TABLE_NAME}` (`bookUrl`)\"\n          },\n          {\n            \"name\": \"index_chapters_bookUrl_index\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"bookUrl\",\n              \"index\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_chapters_bookUrl_index` ON `${TABLE_NAME}` (`bookUrl`, `index`)\"\n          }\n        ],\n        \"foreignKeys\": [\n          {\n            \"table\": \"books\",\n            \"onDelete\": \"CASCADE\",\n            \"onUpdate\": \"NO ACTION\",\n            \"columns\": [\n              \"bookUrl\"\n            ],\n            \"referencedColumns\": [\n              \"bookUrl\"\n            ]\n          }\n        ]\n      },\n      {\n        \"tableName\": \"replace_rules\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL, `group` TEXT, `pattern` TEXT NOT NULL, `replacement` TEXT NOT NULL, `scope` TEXT, `isEnabled` INTEGER NOT NULL, `isRegex` INTEGER NOT NULL, `sortOrder` INTEGER NOT NULL)\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"group\",\n            \"columnName\": \"group\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"pattern\",\n            \"columnName\": \"pattern\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"replacement\",\n            \"columnName\": \"replacement\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"scope\",\n            \"columnName\": \"scope\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"isEnabled\",\n            \"columnName\": \"isEnabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"isRegex\",\n            \"columnName\": \"isRegex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"sortOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"id\"\n          ],\n          \"autoGenerate\": true\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_replace_rules_id\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"id\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_replace_rules_id` ON `${TABLE_NAME}` (`id`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"searchBooks\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookUrl` TEXT NOT NULL, `origin` TEXT NOT NULL, `originName` TEXT NOT NULL, `type` INTEGER NOT NULL, `name` TEXT NOT NULL, `author` TEXT NOT NULL, `kind` TEXT, `coverUrl` TEXT, `intro` TEXT, `wordCount` TEXT, `latestChapterTitle` TEXT, `tocUrl` TEXT NOT NULL, `time` INTEGER NOT NULL, `variable` TEXT, `originOrder` INTEGER NOT NULL, PRIMARY KEY(`bookUrl`), FOREIGN KEY(`origin`) REFERENCES `book_sources`(`bookSourceUrl`) ON UPDATE NO ACTION ON DELETE CASCADE )\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"originName\",\n            \"columnName\": \"originName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"author\",\n            \"columnName\": \"author\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"kind\",\n            \"columnName\": \"kind\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"coverUrl\",\n            \"columnName\": \"coverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"intro\",\n            \"columnName\": \"intro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"wordCount\",\n            \"columnName\": \"wordCount\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"latestChapterTitle\",\n            \"columnName\": \"latestChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"tocUrl\",\n            \"columnName\": \"tocUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"time\",\n            \"columnName\": \"time\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"originOrder\",\n            \"columnName\": \"originOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"bookUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_searchBooks_bookUrl\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"bookUrl\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_searchBooks_bookUrl` ON `${TABLE_NAME}` (`bookUrl`)\"\n          },\n          {\n            \"name\": \"index_searchBooks_origin\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"origin\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_searchBooks_origin` ON `${TABLE_NAME}` (`origin`)\"\n          }\n        ],\n        \"foreignKeys\": [\n          {\n            \"table\": \"book_sources\",\n            \"onDelete\": \"CASCADE\",\n            \"onUpdate\": \"NO ACTION\",\n            \"columns\": [\n              \"origin\"\n            ],\n            \"referencedColumns\": [\n              \"bookSourceUrl\"\n            ]\n          }\n        ]\n      },\n      {\n        \"tableName\": \"search_keywords\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`word` TEXT NOT NULL, `usage` INTEGER NOT NULL, `lastUseTime` INTEGER NOT NULL, PRIMARY KEY(`word`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"word\",\n            \"columnName\": \"word\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"usage\",\n            \"columnName\": \"usage\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"lastUseTime\",\n            \"columnName\": \"lastUseTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"word\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_search_keywords_word\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"word\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_search_keywords_word` ON `${TABLE_NAME}` (`word`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"cookies\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`url` TEXT NOT NULL, `cookie` TEXT NOT NULL, PRIMARY KEY(`url`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"cookie\",\n            \"columnName\": \"cookie\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"url\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_cookies_url\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"url\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_cookies_url` ON `${TABLE_NAME}` (`url`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssSources\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`sourceUrl` TEXT NOT NULL, `sourceName` TEXT NOT NULL, `sourceIcon` TEXT NOT NULL, `sourceGroup` TEXT, `enabled` INTEGER NOT NULL, `sortUrl` TEXT, `articleStyle` INTEGER NOT NULL, `ruleArticles` TEXT, `ruleNextPage` TEXT, `ruleTitle` TEXT, `rulePubDate` TEXT, `ruleDescription` TEXT, `ruleImage` TEXT, `ruleLink` TEXT, `ruleContent` TEXT, `style` TEXT, `header` TEXT, `enableJs` INTEGER NOT NULL, `loadWithBaseUrl` INTEGER NOT NULL, `customOrder` INTEGER NOT NULL, PRIMARY KEY(`sourceUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"sourceUrl\",\n            \"columnName\": \"sourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceName\",\n            \"columnName\": \"sourceName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceIcon\",\n            \"columnName\": \"sourceIcon\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceGroup\",\n            \"columnName\": \"sourceGroup\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enabled\",\n            \"columnName\": \"enabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sortUrl\",\n            \"columnName\": \"sortUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"articleStyle\",\n            \"columnName\": \"articleStyle\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"ruleArticles\",\n            \"columnName\": \"ruleArticles\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleNextPage\",\n            \"columnName\": \"ruleNextPage\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleTitle\",\n            \"columnName\": \"ruleTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"rulePubDate\",\n            \"columnName\": \"rulePubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleDescription\",\n            \"columnName\": \"ruleDescription\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleImage\",\n            \"columnName\": \"ruleImage\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleLink\",\n            \"columnName\": \"ruleLink\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleContent\",\n            \"columnName\": \"ruleContent\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"style\",\n            \"columnName\": \"style\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"header\",\n            \"columnName\": \"header\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enableJs\",\n            \"columnName\": \"enableJs\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"loadWithBaseUrl\",\n            \"columnName\": \"loadWithBaseUrl\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"sourceUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_rssSources_sourceUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"sourceUrl\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_rssSources_sourceUrl` ON `${TABLE_NAME}` (`sourceUrl`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"bookmarks\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`time` INTEGER NOT NULL, `bookUrl` TEXT NOT NULL, `bookName` TEXT NOT NULL, `bookAuthor` TEXT NOT NULL, `chapterIndex` INTEGER NOT NULL, `pageIndex` INTEGER NOT NULL, `chapterName` TEXT NOT NULL, `content` TEXT NOT NULL, PRIMARY KEY(`time`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"time\",\n            \"columnName\": \"time\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookName\",\n            \"columnName\": \"bookName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookAuthor\",\n            \"columnName\": \"bookAuthor\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterIndex\",\n            \"columnName\": \"chapterIndex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"pageIndex\",\n            \"columnName\": \"pageIndex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterName\",\n            \"columnName\": \"chapterName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"time\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_bookmarks_time\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"time\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_bookmarks_time` ON `${TABLE_NAME}` (`time`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssArticles\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`origin` TEXT NOT NULL, `sort` TEXT NOT NULL, `title` TEXT NOT NULL, `order` INTEGER NOT NULL, `link` TEXT NOT NULL, `pubDate` TEXT, `description` TEXT, `content` TEXT, `image` TEXT, `read` INTEGER NOT NULL, PRIMARY KEY(`origin`, `link`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sort\",\n            \"columnName\": \"sort\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"link\",\n            \"columnName\": \"link\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"pubDate\",\n            \"columnName\": \"pubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"description\",\n            \"columnName\": \"description\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"image\",\n            \"columnName\": \"image\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"read\",\n            \"columnName\": \"read\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"origin\",\n            \"link\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssReadRecords\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`record` TEXT NOT NULL, `read` INTEGER NOT NULL, PRIMARY KEY(`record`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"record\",\n            \"columnName\": \"record\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"read\",\n            \"columnName\": \"read\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"record\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssStars\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`origin` TEXT NOT NULL, `sort` TEXT NOT NULL, `title` TEXT NOT NULL, `starTime` INTEGER NOT NULL, `link` TEXT NOT NULL, `pubDate` TEXT, `description` TEXT, `content` TEXT, `image` TEXT, PRIMARY KEY(`origin`, `link`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sort\",\n            \"columnName\": \"sort\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"starTime\",\n            \"columnName\": \"starTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"link\",\n            \"columnName\": \"link\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"pubDate\",\n            \"columnName\": \"pubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"description\",\n            \"columnName\": \"description\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"image\",\n            \"columnName\": \"image\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"origin\",\n            \"link\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"txtTocRules\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `rule` TEXT NOT NULL, `serialNumber` INTEGER NOT NULL, `enable` INTEGER NOT NULL, PRIMARY KEY(`id`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"rule\",\n            \"columnName\": \"rule\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"serialNumber\",\n            \"columnName\": \"serialNumber\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enable\",\n            \"columnName\": \"enable\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"id\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"readRecord\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookName` TEXT NOT NULL, `readTime` INTEGER NOT NULL, PRIMARY KEY(`bookName`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookName\",\n            \"columnName\": \"bookName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"readTime\",\n            \"columnName\": \"readTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"bookName\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"httpTTS\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `url` TEXT NOT NULL, PRIMARY KEY(`id`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"id\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      }\n    ],\n    \"views\": [],\n    \"setupQueries\": [\n      \"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)\",\n      \"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'ec3badfc86fb8187260ab26fb78a2d3f')\"\n    ]\n  }\n}"
  },
  {
    "path": "app/schemas/io.legado.app.data.AppDatabase/19.json",
    "content": "{\n  \"formatVersion\": 1,\n  \"database\": {\n    \"version\": 19,\n    \"identityHash\": \"c58916ed4a4aece6092e21acf99845a1\",\n    \"entities\": [\n      {\n        \"tableName\": \"books\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookUrl` TEXT NOT NULL, `tocUrl` TEXT NOT NULL, `origin` TEXT NOT NULL, `originName` TEXT NOT NULL, `name` TEXT NOT NULL, `author` TEXT NOT NULL, `kind` TEXT, `customTag` TEXT, `coverUrl` TEXT, `customCoverUrl` TEXT, `intro` TEXT, `customIntro` TEXT, `charset` TEXT, `type` INTEGER NOT NULL, `group` INTEGER NOT NULL, `latestChapterTitle` TEXT, `latestChapterTime` INTEGER NOT NULL, `lastCheckTime` INTEGER NOT NULL, `lastCheckCount` INTEGER NOT NULL, `totalChapterNum` INTEGER NOT NULL, `durChapterTitle` TEXT, `durChapterIndex` INTEGER NOT NULL, `durChapterPos` INTEGER NOT NULL, `durChapterTime` INTEGER NOT NULL, `wordCount` TEXT, `canUpdate` INTEGER NOT NULL, `order` INTEGER NOT NULL, `originOrder` INTEGER NOT NULL, `useReplaceRule` INTEGER NOT NULL, `variable` TEXT, PRIMARY KEY(`bookUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"tocUrl\",\n            \"columnName\": \"tocUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"originName\",\n            \"columnName\": \"originName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"author\",\n            \"columnName\": \"author\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"kind\",\n            \"columnName\": \"kind\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customTag\",\n            \"columnName\": \"customTag\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"coverUrl\",\n            \"columnName\": \"coverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customCoverUrl\",\n            \"columnName\": \"customCoverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"intro\",\n            \"columnName\": \"intro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customIntro\",\n            \"columnName\": \"customIntro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"charset\",\n            \"columnName\": \"charset\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"group\",\n            \"columnName\": \"group\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"latestChapterTitle\",\n            \"columnName\": \"latestChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"latestChapterTime\",\n            \"columnName\": \"latestChapterTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"lastCheckTime\",\n            \"columnName\": \"lastCheckTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"lastCheckCount\",\n            \"columnName\": \"lastCheckCount\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"totalChapterNum\",\n            \"columnName\": \"totalChapterNum\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"durChapterTitle\",\n            \"columnName\": \"durChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"durChapterIndex\",\n            \"columnName\": \"durChapterIndex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"durChapterPos\",\n            \"columnName\": \"durChapterPos\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"durChapterTime\",\n            \"columnName\": \"durChapterTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"wordCount\",\n            \"columnName\": \"wordCount\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"canUpdate\",\n            \"columnName\": \"canUpdate\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"originOrder\",\n            \"columnName\": \"originOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"useReplaceRule\",\n            \"columnName\": \"useReplaceRule\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"bookUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_books_name_author\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"name\",\n              \"author\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_books_name_author` ON `${TABLE_NAME}` (`name`, `author`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"book_groups\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`groupId` INTEGER NOT NULL, `groupName` TEXT NOT NULL, `order` INTEGER NOT NULL, PRIMARY KEY(`groupId`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"groupId\",\n            \"columnName\": \"groupId\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"groupName\",\n            \"columnName\": \"groupName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"groupId\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"book_sources\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookSourceName` TEXT NOT NULL, `bookSourceGroup` TEXT, `bookSourceUrl` TEXT NOT NULL, `bookSourceType` INTEGER NOT NULL, `bookUrlPattern` TEXT, `customOrder` INTEGER NOT NULL, `enabled` INTEGER NOT NULL, `enabledExplore` INTEGER NOT NULL, `header` TEXT, `loginUrl` TEXT, `lastUpdateTime` INTEGER NOT NULL, `weight` INTEGER NOT NULL, `exploreUrl` TEXT, `ruleExplore` TEXT, `searchUrl` TEXT, `ruleSearch` TEXT, `ruleBookInfo` TEXT, `ruleToc` TEXT, `ruleContent` TEXT, PRIMARY KEY(`bookSourceUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookSourceName\",\n            \"columnName\": \"bookSourceName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookSourceGroup\",\n            \"columnName\": \"bookSourceGroup\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"bookSourceUrl\",\n            \"columnName\": \"bookSourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookSourceType\",\n            \"columnName\": \"bookSourceType\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookUrlPattern\",\n            \"columnName\": \"bookUrlPattern\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enabled\",\n            \"columnName\": \"enabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enabledExplore\",\n            \"columnName\": \"enabledExplore\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"header\",\n            \"columnName\": \"header\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUrl\",\n            \"columnName\": \"loginUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"lastUpdateTime\",\n            \"columnName\": \"lastUpdateTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"weight\",\n            \"columnName\": \"weight\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"exploreUrl\",\n            \"columnName\": \"exploreUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleExplore\",\n            \"columnName\": \"ruleExplore\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"searchUrl\",\n            \"columnName\": \"searchUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleSearch\",\n            \"columnName\": \"ruleSearch\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleBookInfo\",\n            \"columnName\": \"ruleBookInfo\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleToc\",\n            \"columnName\": \"ruleToc\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleContent\",\n            \"columnName\": \"ruleContent\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"bookSourceUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_book_sources_bookSourceUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"bookSourceUrl\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_book_sources_bookSourceUrl` ON `${TABLE_NAME}` (`bookSourceUrl`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"chapters\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`url` TEXT NOT NULL, `title` TEXT NOT NULL, `bookUrl` TEXT NOT NULL, `index` INTEGER NOT NULL, `resourceUrl` TEXT, `tag` TEXT, `start` INTEGER, `end` INTEGER, `variable` TEXT, PRIMARY KEY(`url`, `bookUrl`), FOREIGN KEY(`bookUrl`) REFERENCES `books`(`bookUrl`) ON UPDATE NO ACTION ON DELETE CASCADE )\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"index\",\n            \"columnName\": \"index\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"resourceUrl\",\n            \"columnName\": \"resourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"tag\",\n            \"columnName\": \"tag\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"start\",\n            \"columnName\": \"start\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"end\",\n            \"columnName\": \"end\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"url\",\n            \"bookUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_chapters_bookUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"bookUrl\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_chapters_bookUrl` ON `${TABLE_NAME}` (`bookUrl`)\"\n          },\n          {\n            \"name\": \"index_chapters_bookUrl_index\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"bookUrl\",\n              \"index\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_chapters_bookUrl_index` ON `${TABLE_NAME}` (`bookUrl`, `index`)\"\n          }\n        ],\n        \"foreignKeys\": [\n          {\n            \"table\": \"books\",\n            \"onDelete\": \"CASCADE\",\n            \"onUpdate\": \"NO ACTION\",\n            \"columns\": [\n              \"bookUrl\"\n            ],\n            \"referencedColumns\": [\n              \"bookUrl\"\n            ]\n          }\n        ]\n      },\n      {\n        \"tableName\": \"replace_rules\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL, `group` TEXT, `pattern` TEXT NOT NULL, `replacement` TEXT NOT NULL, `scope` TEXT, `isEnabled` INTEGER NOT NULL, `isRegex` INTEGER NOT NULL, `sortOrder` INTEGER NOT NULL)\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"group\",\n            \"columnName\": \"group\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"pattern\",\n            \"columnName\": \"pattern\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"replacement\",\n            \"columnName\": \"replacement\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"scope\",\n            \"columnName\": \"scope\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"isEnabled\",\n            \"columnName\": \"isEnabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"isRegex\",\n            \"columnName\": \"isRegex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"sortOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"id\"\n          ],\n          \"autoGenerate\": true\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_replace_rules_id\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"id\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_replace_rules_id` ON `${TABLE_NAME}` (`id`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"searchBooks\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookUrl` TEXT NOT NULL, `origin` TEXT NOT NULL, `originName` TEXT NOT NULL, `type` INTEGER NOT NULL, `name` TEXT NOT NULL, `author` TEXT NOT NULL, `kind` TEXT, `coverUrl` TEXT, `intro` TEXT, `wordCount` TEXT, `latestChapterTitle` TEXT, `tocUrl` TEXT NOT NULL, `time` INTEGER NOT NULL, `variable` TEXT, `originOrder` INTEGER NOT NULL, PRIMARY KEY(`bookUrl`), FOREIGN KEY(`origin`) REFERENCES `book_sources`(`bookSourceUrl`) ON UPDATE NO ACTION ON DELETE CASCADE )\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"originName\",\n            \"columnName\": \"originName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"author\",\n            \"columnName\": \"author\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"kind\",\n            \"columnName\": \"kind\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"coverUrl\",\n            \"columnName\": \"coverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"intro\",\n            \"columnName\": \"intro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"wordCount\",\n            \"columnName\": \"wordCount\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"latestChapterTitle\",\n            \"columnName\": \"latestChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"tocUrl\",\n            \"columnName\": \"tocUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"time\",\n            \"columnName\": \"time\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"originOrder\",\n            \"columnName\": \"originOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"bookUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_searchBooks_bookUrl\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"bookUrl\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_searchBooks_bookUrl` ON `${TABLE_NAME}` (`bookUrl`)\"\n          },\n          {\n            \"name\": \"index_searchBooks_origin\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"origin\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_searchBooks_origin` ON `${TABLE_NAME}` (`origin`)\"\n          }\n        ],\n        \"foreignKeys\": [\n          {\n            \"table\": \"book_sources\",\n            \"onDelete\": \"CASCADE\",\n            \"onUpdate\": \"NO ACTION\",\n            \"columns\": [\n              \"origin\"\n            ],\n            \"referencedColumns\": [\n              \"bookSourceUrl\"\n            ]\n          }\n        ]\n      },\n      {\n        \"tableName\": \"search_keywords\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`word` TEXT NOT NULL, `usage` INTEGER NOT NULL, `lastUseTime` INTEGER NOT NULL, PRIMARY KEY(`word`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"word\",\n            \"columnName\": \"word\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"usage\",\n            \"columnName\": \"usage\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"lastUseTime\",\n            \"columnName\": \"lastUseTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"word\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_search_keywords_word\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"word\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_search_keywords_word` ON `${TABLE_NAME}` (`word`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"cookies\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`url` TEXT NOT NULL, `cookie` TEXT NOT NULL, PRIMARY KEY(`url`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"cookie\",\n            \"columnName\": \"cookie\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"url\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_cookies_url\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"url\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_cookies_url` ON `${TABLE_NAME}` (`url`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssSources\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`sourceUrl` TEXT NOT NULL, `sourceName` TEXT NOT NULL, `sourceIcon` TEXT NOT NULL, `sourceGroup` TEXT, `enabled` INTEGER NOT NULL, `sortUrl` TEXT, `articleStyle` INTEGER NOT NULL, `ruleArticles` TEXT, `ruleNextPage` TEXT, `ruleTitle` TEXT, `rulePubDate` TEXT, `ruleDescription` TEXT, `ruleImage` TEXT, `ruleLink` TEXT, `ruleContent` TEXT, `style` TEXT, `header` TEXT, `enableJs` INTEGER NOT NULL, `loadWithBaseUrl` INTEGER NOT NULL, `customOrder` INTEGER NOT NULL, PRIMARY KEY(`sourceUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"sourceUrl\",\n            \"columnName\": \"sourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceName\",\n            \"columnName\": \"sourceName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceIcon\",\n            \"columnName\": \"sourceIcon\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceGroup\",\n            \"columnName\": \"sourceGroup\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enabled\",\n            \"columnName\": \"enabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sortUrl\",\n            \"columnName\": \"sortUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"articleStyle\",\n            \"columnName\": \"articleStyle\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"ruleArticles\",\n            \"columnName\": \"ruleArticles\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleNextPage\",\n            \"columnName\": \"ruleNextPage\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleTitle\",\n            \"columnName\": \"ruleTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"rulePubDate\",\n            \"columnName\": \"rulePubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleDescription\",\n            \"columnName\": \"ruleDescription\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleImage\",\n            \"columnName\": \"ruleImage\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleLink\",\n            \"columnName\": \"ruleLink\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleContent\",\n            \"columnName\": \"ruleContent\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"style\",\n            \"columnName\": \"style\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"header\",\n            \"columnName\": \"header\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enableJs\",\n            \"columnName\": \"enableJs\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"loadWithBaseUrl\",\n            \"columnName\": \"loadWithBaseUrl\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"sourceUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_rssSources_sourceUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"sourceUrl\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_rssSources_sourceUrl` ON `${TABLE_NAME}` (`sourceUrl`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"bookmarks\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`time` INTEGER NOT NULL, `bookUrl` TEXT NOT NULL, `bookName` TEXT NOT NULL, `bookAuthor` TEXT NOT NULL, `chapterIndex` INTEGER NOT NULL, `pageIndex` INTEGER NOT NULL, `chapterName` TEXT NOT NULL, `content` TEXT NOT NULL, PRIMARY KEY(`time`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"time\",\n            \"columnName\": \"time\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookName\",\n            \"columnName\": \"bookName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookAuthor\",\n            \"columnName\": \"bookAuthor\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterIndex\",\n            \"columnName\": \"chapterIndex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"pageIndex\",\n            \"columnName\": \"pageIndex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterName\",\n            \"columnName\": \"chapterName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"time\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_bookmarks_time\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"time\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_bookmarks_time` ON `${TABLE_NAME}` (`time`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssArticles\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`origin` TEXT NOT NULL, `sort` TEXT NOT NULL, `title` TEXT NOT NULL, `order` INTEGER NOT NULL, `link` TEXT NOT NULL, `pubDate` TEXT, `description` TEXT, `content` TEXT, `image` TEXT, `read` INTEGER NOT NULL, PRIMARY KEY(`origin`, `link`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sort\",\n            \"columnName\": \"sort\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"link\",\n            \"columnName\": \"link\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"pubDate\",\n            \"columnName\": \"pubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"description\",\n            \"columnName\": \"description\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"image\",\n            \"columnName\": \"image\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"read\",\n            \"columnName\": \"read\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"origin\",\n            \"link\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssReadRecords\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`record` TEXT NOT NULL, `read` INTEGER NOT NULL, PRIMARY KEY(`record`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"record\",\n            \"columnName\": \"record\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"read\",\n            \"columnName\": \"read\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"record\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssStars\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`origin` TEXT NOT NULL, `sort` TEXT NOT NULL, `title` TEXT NOT NULL, `starTime` INTEGER NOT NULL, `link` TEXT NOT NULL, `pubDate` TEXT, `description` TEXT, `content` TEXT, `image` TEXT, PRIMARY KEY(`origin`, `link`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sort\",\n            \"columnName\": \"sort\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"starTime\",\n            \"columnName\": \"starTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"link\",\n            \"columnName\": \"link\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"pubDate\",\n            \"columnName\": \"pubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"description\",\n            \"columnName\": \"description\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"image\",\n            \"columnName\": \"image\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"origin\",\n            \"link\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"txtTocRules\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `rule` TEXT NOT NULL, `serialNumber` INTEGER NOT NULL, `enable` INTEGER NOT NULL, PRIMARY KEY(`id`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"rule\",\n            \"columnName\": \"rule\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"serialNumber\",\n            \"columnName\": \"serialNumber\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enable\",\n            \"columnName\": \"enable\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"id\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"readRecord\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`androidId` TEXT NOT NULL, `bookName` TEXT NOT NULL, `readTime` INTEGER NOT NULL, PRIMARY KEY(`androidId`, `bookName`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"androidId\",\n            \"columnName\": \"androidId\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookName\",\n            \"columnName\": \"bookName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"readTime\",\n            \"columnName\": \"readTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"androidId\",\n            \"bookName\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"httpTTS\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `url` TEXT NOT NULL, PRIMARY KEY(`id`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"id\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      }\n    ],\n    \"views\": [],\n    \"setupQueries\": [\n      \"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)\",\n      \"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'c58916ed4a4aece6092e21acf99845a1')\"\n    ]\n  }\n}"
  },
  {
    "path": "app/schemas/io.legado.app.data.AppDatabase/2.json",
    "content": "{\n  \"formatVersion\": 1,\n  \"database\": {\n    \"version\": 2,\n    \"identityHash\": \"8164f697c57c68c7b82ec8e427214a88\",\n    \"entities\": [\n      {\n        \"tableName\": \"books\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookUrl` TEXT NOT NULL, `tocUrl` TEXT NOT NULL, `origin` TEXT NOT NULL, `originName` TEXT NOT NULL, `name` TEXT NOT NULL, `author` TEXT NOT NULL, `kind` TEXT, `customTag` TEXT, `coverUrl` TEXT, `customCoverUrl` TEXT, `intro` TEXT, `customIntro` TEXT, `charset` TEXT, `type` INTEGER NOT NULL, `group` INTEGER NOT NULL, `latestChapterTitle` TEXT, `latestChapterTime` INTEGER NOT NULL, `lastCheckTime` INTEGER NOT NULL, `lastCheckCount` INTEGER NOT NULL, `totalChapterNum` INTEGER NOT NULL, `durChapterTitle` TEXT, `durChapterIndex` INTEGER NOT NULL, `durChapterPos` INTEGER NOT NULL, `durChapterTime` INTEGER NOT NULL, `wordCount` TEXT, `canUpdate` INTEGER NOT NULL, `order` INTEGER NOT NULL, `originOrder` INTEGER NOT NULL, `useReplaceRule` INTEGER NOT NULL, `variable` TEXT, PRIMARY KEY(`bookUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"tocUrl\",\n            \"columnName\": \"tocUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"originName\",\n            \"columnName\": \"originName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"author\",\n            \"columnName\": \"author\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"kind\",\n            \"columnName\": \"kind\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customTag\",\n            \"columnName\": \"customTag\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"coverUrl\",\n            \"columnName\": \"coverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customCoverUrl\",\n            \"columnName\": \"customCoverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"intro\",\n            \"columnName\": \"intro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customIntro\",\n            \"columnName\": \"customIntro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"charset\",\n            \"columnName\": \"charset\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"group\",\n            \"columnName\": \"group\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"latestChapterTitle\",\n            \"columnName\": \"latestChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"latestChapterTime\",\n            \"columnName\": \"latestChapterTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"lastCheckTime\",\n            \"columnName\": \"lastCheckTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"lastCheckCount\",\n            \"columnName\": \"lastCheckCount\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"totalChapterNum\",\n            \"columnName\": \"totalChapterNum\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"durChapterTitle\",\n            \"columnName\": \"durChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"durChapterIndex\",\n            \"columnName\": \"durChapterIndex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"durChapterPos\",\n            \"columnName\": \"durChapterPos\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"durChapterTime\",\n            \"columnName\": \"durChapterTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"wordCount\",\n            \"columnName\": \"wordCount\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"canUpdate\",\n            \"columnName\": \"canUpdate\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"originOrder\",\n            \"columnName\": \"originOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"useReplaceRule\",\n            \"columnName\": \"useReplaceRule\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"bookUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_books_bookUrl\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"bookUrl\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_books_bookUrl` ON `${TABLE_NAME}` (`bookUrl`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"book_groups\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`groupId` INTEGER NOT NULL, `groupName` TEXT NOT NULL, `order` INTEGER NOT NULL, PRIMARY KEY(`groupId`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"groupId\",\n            \"columnName\": \"groupId\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"groupName\",\n            \"columnName\": \"groupName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"groupId\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"book_sources\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookSourceName` TEXT NOT NULL, `bookSourceGroup` TEXT, `bookSourceUrl` TEXT NOT NULL, `bookSourceType` INTEGER NOT NULL, `bookUrlPattern` TEXT, `customOrder` INTEGER NOT NULL, `enabled` INTEGER NOT NULL, `enabledExplore` INTEGER NOT NULL, `header` TEXT, `loginUrl` TEXT, `lastUpdateTime` INTEGER NOT NULL, `weight` INTEGER NOT NULL, `exploreUrl` TEXT, `ruleExplore` TEXT, `searchUrl` TEXT, `ruleSearch` TEXT, `ruleBookInfo` TEXT, `ruleToc` TEXT, `ruleContent` TEXT, PRIMARY KEY(`bookSourceUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookSourceName\",\n            \"columnName\": \"bookSourceName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookSourceGroup\",\n            \"columnName\": \"bookSourceGroup\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"bookSourceUrl\",\n            \"columnName\": \"bookSourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookSourceType\",\n            \"columnName\": \"bookSourceType\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookUrlPattern\",\n            \"columnName\": \"bookUrlPattern\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enabled\",\n            \"columnName\": \"enabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enabledExplore\",\n            \"columnName\": \"enabledExplore\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"header\",\n            \"columnName\": \"header\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUrl\",\n            \"columnName\": \"loginUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"lastUpdateTime\",\n            \"columnName\": \"lastUpdateTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"weight\",\n            \"columnName\": \"weight\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"exploreUrl\",\n            \"columnName\": \"exploreUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleExplore\",\n            \"columnName\": \"ruleExplore\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"searchUrl\",\n            \"columnName\": \"searchUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleSearch\",\n            \"columnName\": \"ruleSearch\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleBookInfo\",\n            \"columnName\": \"ruleBookInfo\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleToc\",\n            \"columnName\": \"ruleToc\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleContent\",\n            \"columnName\": \"ruleContent\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"bookSourceUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_book_sources_bookSourceUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"bookSourceUrl\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_book_sources_bookSourceUrl` ON `${TABLE_NAME}` (`bookSourceUrl`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"chapters\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`url` TEXT NOT NULL, `title` TEXT NOT NULL, `bookUrl` TEXT NOT NULL, `index` INTEGER NOT NULL, `resourceUrl` TEXT, `tag` TEXT, `start` INTEGER, `end` INTEGER, `variable` TEXT, PRIMARY KEY(`url`, `bookUrl`), FOREIGN KEY(`bookUrl`) REFERENCES `books`(`bookUrl`) ON UPDATE NO ACTION ON DELETE CASCADE )\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"index\",\n            \"columnName\": \"index\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"resourceUrl\",\n            \"columnName\": \"resourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"tag\",\n            \"columnName\": \"tag\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"start\",\n            \"columnName\": \"start\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"end\",\n            \"columnName\": \"end\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"url\",\n            \"bookUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_chapters_bookUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"bookUrl\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_chapters_bookUrl` ON `${TABLE_NAME}` (`bookUrl`)\"\n          },\n          {\n            \"name\": \"index_chapters_bookUrl_index\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"bookUrl\",\n              \"index\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_chapters_bookUrl_index` ON `${TABLE_NAME}` (`bookUrl`, `index`)\"\n          }\n        ],\n        \"foreignKeys\": [\n          {\n            \"table\": \"books\",\n            \"onDelete\": \"CASCADE\",\n            \"onUpdate\": \"NO ACTION\",\n            \"columns\": [\n              \"bookUrl\"\n            ],\n            \"referencedColumns\": [\n              \"bookUrl\"\n            ]\n          }\n        ]\n      },\n      {\n        \"tableName\": \"replace_rules\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL, `group` TEXT, `pattern` TEXT NOT NULL, `replacement` TEXT NOT NULL, `scope` TEXT, `isEnabled` INTEGER NOT NULL, `isRegex` INTEGER NOT NULL, `sortOrder` INTEGER NOT NULL)\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"group\",\n            \"columnName\": \"group\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"pattern\",\n            \"columnName\": \"pattern\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"replacement\",\n            \"columnName\": \"replacement\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"scope\",\n            \"columnName\": \"scope\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"isEnabled\",\n            \"columnName\": \"isEnabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"isRegex\",\n            \"columnName\": \"isRegex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"sortOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"id\"\n          ],\n          \"autoGenerate\": true\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_replace_rules_id\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"id\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_replace_rules_id` ON `${TABLE_NAME}` (`id`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"searchBooks\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookUrl` TEXT NOT NULL, `origin` TEXT NOT NULL, `originName` TEXT NOT NULL, `type` INTEGER NOT NULL, `name` TEXT NOT NULL, `author` TEXT NOT NULL, `kind` TEXT, `coverUrl` TEXT, `intro` TEXT, `wordCount` TEXT, `latestChapterTitle` TEXT, `tocUrl` TEXT NOT NULL, `time` INTEGER NOT NULL, `variable` TEXT, `originOrder` INTEGER NOT NULL, PRIMARY KEY(`bookUrl`), FOREIGN KEY(`origin`) REFERENCES `book_sources`(`bookSourceUrl`) ON UPDATE NO ACTION ON DELETE CASCADE )\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"originName\",\n            \"columnName\": \"originName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"author\",\n            \"columnName\": \"author\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"kind\",\n            \"columnName\": \"kind\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"coverUrl\",\n            \"columnName\": \"coverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"intro\",\n            \"columnName\": \"intro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"wordCount\",\n            \"columnName\": \"wordCount\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"latestChapterTitle\",\n            \"columnName\": \"latestChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"tocUrl\",\n            \"columnName\": \"tocUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"time\",\n            \"columnName\": \"time\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"originOrder\",\n            \"columnName\": \"originOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"bookUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_searchBooks_bookUrl\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"bookUrl\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_searchBooks_bookUrl` ON `${TABLE_NAME}` (`bookUrl`)\"\n          }\n        ],\n        \"foreignKeys\": [\n          {\n            \"table\": \"book_sources\",\n            \"onDelete\": \"CASCADE\",\n            \"onUpdate\": \"NO ACTION\",\n            \"columns\": [\n              \"origin\"\n            ],\n            \"referencedColumns\": [\n              \"bookSourceUrl\"\n            ]\n          }\n        ]\n      },\n      {\n        \"tableName\": \"search_keywords\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`word` TEXT NOT NULL, `usage` INTEGER NOT NULL, `lastUseTime` INTEGER NOT NULL, PRIMARY KEY(`word`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"word\",\n            \"columnName\": \"word\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"usage\",\n            \"columnName\": \"usage\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"lastUseTime\",\n            \"columnName\": \"lastUseTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"word\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_search_keywords_word\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"word\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_search_keywords_word` ON `${TABLE_NAME}` (`word`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"cookies\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`url` TEXT NOT NULL, `cookie` TEXT NOT NULL, PRIMARY KEY(`url`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"cookie\",\n            \"columnName\": \"cookie\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"url\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_cookies_url\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"url\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_cookies_url` ON `${TABLE_NAME}` (`url`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssSources\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`sourceUrl` TEXT NOT NULL, `sourceName` TEXT NOT NULL, `sourceIcon` TEXT NOT NULL, `sourceGroup` TEXT, `enabled` INTEGER NOT NULL, `ruleArticles` TEXT, `ruleNextPage` TEXT, `ruleTitle` TEXT, `rulePubDate` TEXT, `ruleDescription` TEXT, `ruleImage` TEXT, `ruleLink` TEXT, `ruleContent` TEXT, `header` TEXT, `enableJs` INTEGER NOT NULL, `loadWithBaseUrl` INTEGER NOT NULL, `customOrder` INTEGER NOT NULL, PRIMARY KEY(`sourceUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"sourceUrl\",\n            \"columnName\": \"sourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceName\",\n            \"columnName\": \"sourceName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceIcon\",\n            \"columnName\": \"sourceIcon\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceGroup\",\n            \"columnName\": \"sourceGroup\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enabled\",\n            \"columnName\": \"enabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"ruleArticles\",\n            \"columnName\": \"ruleArticles\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleNextPage\",\n            \"columnName\": \"ruleNextPage\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleTitle\",\n            \"columnName\": \"ruleTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"rulePubDate\",\n            \"columnName\": \"rulePubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleDescription\",\n            \"columnName\": \"ruleDescription\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleImage\",\n            \"columnName\": \"ruleImage\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleLink\",\n            \"columnName\": \"ruleLink\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleContent\",\n            \"columnName\": \"ruleContent\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"header\",\n            \"columnName\": \"header\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enableJs\",\n            \"columnName\": \"enableJs\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"loadWithBaseUrl\",\n            \"columnName\": \"loadWithBaseUrl\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"sourceUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_rssSources_sourceUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"sourceUrl\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_rssSources_sourceUrl` ON `${TABLE_NAME}` (`sourceUrl`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"bookmarks\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`time` INTEGER NOT NULL, `bookUrl` TEXT NOT NULL, `bookName` TEXT NOT NULL, `chapterName` TEXT NOT NULL, `chapterIndex` INTEGER NOT NULL, `pageIndex` INTEGER NOT NULL, `content` TEXT NOT NULL, PRIMARY KEY(`time`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"time\",\n            \"columnName\": \"time\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookName\",\n            \"columnName\": \"bookName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterName\",\n            \"columnName\": \"chapterName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterIndex\",\n            \"columnName\": \"chapterIndex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"pageIndex\",\n            \"columnName\": \"pageIndex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"time\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_bookmarks_bookUrl\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"bookUrl\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_bookmarks_bookUrl` ON `${TABLE_NAME}` (`bookUrl`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssArticles\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`origin` TEXT NOT NULL, `title` TEXT NOT NULL, `order` INTEGER NOT NULL, `link` TEXT NOT NULL, `pubDate` TEXT, `description` TEXT, `content` TEXT, `image` TEXT, `read` INTEGER NOT NULL, `star` INTEGER NOT NULL, PRIMARY KEY(`origin`, `link`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"link\",\n            \"columnName\": \"link\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"pubDate\",\n            \"columnName\": \"pubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"description\",\n            \"columnName\": \"description\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"image\",\n            \"columnName\": \"image\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"read\",\n            \"columnName\": \"read\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"star\",\n            \"columnName\": \"star\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"origin\",\n            \"link\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      }\n    ],\n    \"views\": [],\n    \"setupQueries\": [\n      \"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)\",\n      \"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '8164f697c57c68c7b82ec8e427214a88')\"\n    ]\n  }\n}"
  },
  {
    "path": "app/schemas/io.legado.app.data.AppDatabase/20.json",
    "content": "{\n  \"formatVersion\": 1,\n  \"database\": {\n    \"version\": 20,\n    \"identityHash\": \"2c443ea987b87d8daf2a6161a98d2d5c\",\n    \"entities\": [\n      {\n        \"tableName\": \"books\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookUrl` TEXT NOT NULL, `tocUrl` TEXT NOT NULL, `origin` TEXT NOT NULL, `originName` TEXT NOT NULL, `name` TEXT NOT NULL, `author` TEXT NOT NULL, `kind` TEXT, `customTag` TEXT, `coverUrl` TEXT, `customCoverUrl` TEXT, `intro` TEXT, `customIntro` TEXT, `charset` TEXT, `type` INTEGER NOT NULL, `group` INTEGER NOT NULL, `latestChapterTitle` TEXT, `latestChapterTime` INTEGER NOT NULL, `lastCheckTime` INTEGER NOT NULL, `lastCheckCount` INTEGER NOT NULL, `totalChapterNum` INTEGER NOT NULL, `durChapterTitle` TEXT, `durChapterIndex` INTEGER NOT NULL, `durChapterPos` INTEGER NOT NULL, `durChapterTime` INTEGER NOT NULL, `wordCount` TEXT, `canUpdate` INTEGER NOT NULL, `order` INTEGER NOT NULL, `originOrder` INTEGER NOT NULL, `useReplaceRule` INTEGER NOT NULL, `variable` TEXT, PRIMARY KEY(`bookUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"tocUrl\",\n            \"columnName\": \"tocUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"originName\",\n            \"columnName\": \"originName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"author\",\n            \"columnName\": \"author\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"kind\",\n            \"columnName\": \"kind\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customTag\",\n            \"columnName\": \"customTag\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"coverUrl\",\n            \"columnName\": \"coverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customCoverUrl\",\n            \"columnName\": \"customCoverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"intro\",\n            \"columnName\": \"intro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customIntro\",\n            \"columnName\": \"customIntro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"charset\",\n            \"columnName\": \"charset\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"group\",\n            \"columnName\": \"group\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"latestChapterTitle\",\n            \"columnName\": \"latestChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"latestChapterTime\",\n            \"columnName\": \"latestChapterTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"lastCheckTime\",\n            \"columnName\": \"lastCheckTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"lastCheckCount\",\n            \"columnName\": \"lastCheckCount\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"totalChapterNum\",\n            \"columnName\": \"totalChapterNum\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"durChapterTitle\",\n            \"columnName\": \"durChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"durChapterIndex\",\n            \"columnName\": \"durChapterIndex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"durChapterPos\",\n            \"columnName\": \"durChapterPos\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"durChapterTime\",\n            \"columnName\": \"durChapterTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"wordCount\",\n            \"columnName\": \"wordCount\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"canUpdate\",\n            \"columnName\": \"canUpdate\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"originOrder\",\n            \"columnName\": \"originOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"useReplaceRule\",\n            \"columnName\": \"useReplaceRule\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"bookUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_books_name_author\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"name\",\n              \"author\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_books_name_author` ON `${TABLE_NAME}` (`name`, `author`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"book_groups\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`groupId` INTEGER NOT NULL, `groupName` TEXT NOT NULL, `order` INTEGER NOT NULL, PRIMARY KEY(`groupId`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"groupId\",\n            \"columnName\": \"groupId\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"groupName\",\n            \"columnName\": \"groupName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"groupId\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"book_sources\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookSourceName` TEXT NOT NULL, `bookSourceGroup` TEXT, `bookSourceUrl` TEXT NOT NULL, `bookSourceType` INTEGER NOT NULL, `bookUrlPattern` TEXT, `customOrder` INTEGER NOT NULL, `enabled` INTEGER NOT NULL, `enabledExplore` INTEGER NOT NULL, `header` TEXT, `loginUrl` TEXT, `bookSourceComment` TEXT, `lastUpdateTime` INTEGER NOT NULL, `weight` INTEGER NOT NULL, `exploreUrl` TEXT, `ruleExplore` TEXT, `searchUrl` TEXT, `ruleSearch` TEXT, `ruleBookInfo` TEXT, `ruleToc` TEXT, `ruleContent` TEXT, PRIMARY KEY(`bookSourceUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookSourceName\",\n            \"columnName\": \"bookSourceName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookSourceGroup\",\n            \"columnName\": \"bookSourceGroup\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"bookSourceUrl\",\n            \"columnName\": \"bookSourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookSourceType\",\n            \"columnName\": \"bookSourceType\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookUrlPattern\",\n            \"columnName\": \"bookUrlPattern\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enabled\",\n            \"columnName\": \"enabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enabledExplore\",\n            \"columnName\": \"enabledExplore\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"header\",\n            \"columnName\": \"header\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUrl\",\n            \"columnName\": \"loginUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"bookSourceComment\",\n            \"columnName\": \"bookSourceComment\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"lastUpdateTime\",\n            \"columnName\": \"lastUpdateTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"weight\",\n            \"columnName\": \"weight\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"exploreUrl\",\n            \"columnName\": \"exploreUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleExplore\",\n            \"columnName\": \"ruleExplore\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"searchUrl\",\n            \"columnName\": \"searchUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleSearch\",\n            \"columnName\": \"ruleSearch\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleBookInfo\",\n            \"columnName\": \"ruleBookInfo\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleToc\",\n            \"columnName\": \"ruleToc\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleContent\",\n            \"columnName\": \"ruleContent\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"bookSourceUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_book_sources_bookSourceUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"bookSourceUrl\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_book_sources_bookSourceUrl` ON `${TABLE_NAME}` (`bookSourceUrl`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"chapters\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`url` TEXT NOT NULL, `title` TEXT NOT NULL, `bookUrl` TEXT NOT NULL, `index` INTEGER NOT NULL, `resourceUrl` TEXT, `tag` TEXT, `start` INTEGER, `end` INTEGER, `variable` TEXT, PRIMARY KEY(`url`, `bookUrl`), FOREIGN KEY(`bookUrl`) REFERENCES `books`(`bookUrl`) ON UPDATE NO ACTION ON DELETE CASCADE )\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"index\",\n            \"columnName\": \"index\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"resourceUrl\",\n            \"columnName\": \"resourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"tag\",\n            \"columnName\": \"tag\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"start\",\n            \"columnName\": \"start\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"end\",\n            \"columnName\": \"end\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"url\",\n            \"bookUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_chapters_bookUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"bookUrl\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_chapters_bookUrl` ON `${TABLE_NAME}` (`bookUrl`)\"\n          },\n          {\n            \"name\": \"index_chapters_bookUrl_index\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"bookUrl\",\n              \"index\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_chapters_bookUrl_index` ON `${TABLE_NAME}` (`bookUrl`, `index`)\"\n          }\n        ],\n        \"foreignKeys\": [\n          {\n            \"table\": \"books\",\n            \"onDelete\": \"CASCADE\",\n            \"onUpdate\": \"NO ACTION\",\n            \"columns\": [\n              \"bookUrl\"\n            ],\n            \"referencedColumns\": [\n              \"bookUrl\"\n            ]\n          }\n        ]\n      },\n      {\n        \"tableName\": \"replace_rules\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL, `group` TEXT, `pattern` TEXT NOT NULL, `replacement` TEXT NOT NULL, `scope` TEXT, `isEnabled` INTEGER NOT NULL, `isRegex` INTEGER NOT NULL, `sortOrder` INTEGER NOT NULL)\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"group\",\n            \"columnName\": \"group\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"pattern\",\n            \"columnName\": \"pattern\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"replacement\",\n            \"columnName\": \"replacement\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"scope\",\n            \"columnName\": \"scope\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"isEnabled\",\n            \"columnName\": \"isEnabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"isRegex\",\n            \"columnName\": \"isRegex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"sortOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"id\"\n          ],\n          \"autoGenerate\": true\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_replace_rules_id\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"id\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_replace_rules_id` ON `${TABLE_NAME}` (`id`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"searchBooks\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookUrl` TEXT NOT NULL, `origin` TEXT NOT NULL, `originName` TEXT NOT NULL, `type` INTEGER NOT NULL, `name` TEXT NOT NULL, `author` TEXT NOT NULL, `kind` TEXT, `coverUrl` TEXT, `intro` TEXT, `wordCount` TEXT, `latestChapterTitle` TEXT, `tocUrl` TEXT NOT NULL, `time` INTEGER NOT NULL, `variable` TEXT, `originOrder` INTEGER NOT NULL, PRIMARY KEY(`bookUrl`), FOREIGN KEY(`origin`) REFERENCES `book_sources`(`bookSourceUrl`) ON UPDATE NO ACTION ON DELETE CASCADE )\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"originName\",\n            \"columnName\": \"originName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"author\",\n            \"columnName\": \"author\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"kind\",\n            \"columnName\": \"kind\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"coverUrl\",\n            \"columnName\": \"coverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"intro\",\n            \"columnName\": \"intro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"wordCount\",\n            \"columnName\": \"wordCount\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"latestChapterTitle\",\n            \"columnName\": \"latestChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"tocUrl\",\n            \"columnName\": \"tocUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"time\",\n            \"columnName\": \"time\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"originOrder\",\n            \"columnName\": \"originOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"bookUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_searchBooks_bookUrl\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"bookUrl\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_searchBooks_bookUrl` ON `${TABLE_NAME}` (`bookUrl`)\"\n          },\n          {\n            \"name\": \"index_searchBooks_origin\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"origin\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_searchBooks_origin` ON `${TABLE_NAME}` (`origin`)\"\n          }\n        ],\n        \"foreignKeys\": [\n          {\n            \"table\": \"book_sources\",\n            \"onDelete\": \"CASCADE\",\n            \"onUpdate\": \"NO ACTION\",\n            \"columns\": [\n              \"origin\"\n            ],\n            \"referencedColumns\": [\n              \"bookSourceUrl\"\n            ]\n          }\n        ]\n      },\n      {\n        \"tableName\": \"search_keywords\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`word` TEXT NOT NULL, `usage` INTEGER NOT NULL, `lastUseTime` INTEGER NOT NULL, PRIMARY KEY(`word`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"word\",\n            \"columnName\": \"word\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"usage\",\n            \"columnName\": \"usage\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"lastUseTime\",\n            \"columnName\": \"lastUseTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"word\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_search_keywords_word\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"word\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_search_keywords_word` ON `${TABLE_NAME}` (`word`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"cookies\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`url` TEXT NOT NULL, `cookie` TEXT NOT NULL, PRIMARY KEY(`url`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"cookie\",\n            \"columnName\": \"cookie\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"url\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_cookies_url\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"url\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_cookies_url` ON `${TABLE_NAME}` (`url`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssSources\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`sourceUrl` TEXT NOT NULL, `sourceName` TEXT NOT NULL, `sourceIcon` TEXT NOT NULL, `sourceGroup` TEXT, `enabled` INTEGER NOT NULL, `sortUrl` TEXT, `articleStyle` INTEGER NOT NULL, `ruleArticles` TEXT, `ruleNextPage` TEXT, `ruleTitle` TEXT, `rulePubDate` TEXT, `ruleDescription` TEXT, `ruleImage` TEXT, `ruleLink` TEXT, `ruleContent` TEXT, `style` TEXT, `header` TEXT, `enableJs` INTEGER NOT NULL, `loadWithBaseUrl` INTEGER NOT NULL, `customOrder` INTEGER NOT NULL, PRIMARY KEY(`sourceUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"sourceUrl\",\n            \"columnName\": \"sourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceName\",\n            \"columnName\": \"sourceName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceIcon\",\n            \"columnName\": \"sourceIcon\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceGroup\",\n            \"columnName\": \"sourceGroup\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enabled\",\n            \"columnName\": \"enabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sortUrl\",\n            \"columnName\": \"sortUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"articleStyle\",\n            \"columnName\": \"articleStyle\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"ruleArticles\",\n            \"columnName\": \"ruleArticles\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleNextPage\",\n            \"columnName\": \"ruleNextPage\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleTitle\",\n            \"columnName\": \"ruleTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"rulePubDate\",\n            \"columnName\": \"rulePubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleDescription\",\n            \"columnName\": \"ruleDescription\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleImage\",\n            \"columnName\": \"ruleImage\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleLink\",\n            \"columnName\": \"ruleLink\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleContent\",\n            \"columnName\": \"ruleContent\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"style\",\n            \"columnName\": \"style\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"header\",\n            \"columnName\": \"header\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enableJs\",\n            \"columnName\": \"enableJs\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"loadWithBaseUrl\",\n            \"columnName\": \"loadWithBaseUrl\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"sourceUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_rssSources_sourceUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"sourceUrl\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_rssSources_sourceUrl` ON `${TABLE_NAME}` (`sourceUrl`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"bookmarks\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`time` INTEGER NOT NULL, `bookUrl` TEXT NOT NULL, `bookName` TEXT NOT NULL, `bookAuthor` TEXT NOT NULL, `chapterIndex` INTEGER NOT NULL, `pageIndex` INTEGER NOT NULL, `chapterName` TEXT NOT NULL, `content` TEXT NOT NULL, PRIMARY KEY(`time`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"time\",\n            \"columnName\": \"time\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookName\",\n            \"columnName\": \"bookName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookAuthor\",\n            \"columnName\": \"bookAuthor\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterIndex\",\n            \"columnName\": \"chapterIndex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"pageIndex\",\n            \"columnName\": \"pageIndex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterName\",\n            \"columnName\": \"chapterName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"time\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_bookmarks_time\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"time\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_bookmarks_time` ON `${TABLE_NAME}` (`time`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssArticles\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`origin` TEXT NOT NULL, `sort` TEXT NOT NULL, `title` TEXT NOT NULL, `order` INTEGER NOT NULL, `link` TEXT NOT NULL, `pubDate` TEXT, `description` TEXT, `content` TEXT, `image` TEXT, `read` INTEGER NOT NULL, PRIMARY KEY(`origin`, `link`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sort\",\n            \"columnName\": \"sort\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"link\",\n            \"columnName\": \"link\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"pubDate\",\n            \"columnName\": \"pubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"description\",\n            \"columnName\": \"description\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"image\",\n            \"columnName\": \"image\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"read\",\n            \"columnName\": \"read\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"origin\",\n            \"link\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssReadRecords\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`record` TEXT NOT NULL, `read` INTEGER NOT NULL, PRIMARY KEY(`record`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"record\",\n            \"columnName\": \"record\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"read\",\n            \"columnName\": \"read\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"record\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssStars\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`origin` TEXT NOT NULL, `sort` TEXT NOT NULL, `title` TEXT NOT NULL, `starTime` INTEGER NOT NULL, `link` TEXT NOT NULL, `pubDate` TEXT, `description` TEXT, `content` TEXT, `image` TEXT, PRIMARY KEY(`origin`, `link`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sort\",\n            \"columnName\": \"sort\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"starTime\",\n            \"columnName\": \"starTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"link\",\n            \"columnName\": \"link\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"pubDate\",\n            \"columnName\": \"pubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"description\",\n            \"columnName\": \"description\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"image\",\n            \"columnName\": \"image\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"origin\",\n            \"link\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"txtTocRules\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `rule` TEXT NOT NULL, `serialNumber` INTEGER NOT NULL, `enable` INTEGER NOT NULL, PRIMARY KEY(`id`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"rule\",\n            \"columnName\": \"rule\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"serialNumber\",\n            \"columnName\": \"serialNumber\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enable\",\n            \"columnName\": \"enable\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"id\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"readRecord\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`androidId` TEXT NOT NULL, `bookName` TEXT NOT NULL, `readTime` INTEGER NOT NULL, PRIMARY KEY(`androidId`, `bookName`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"androidId\",\n            \"columnName\": \"androidId\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookName\",\n            \"columnName\": \"bookName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"readTime\",\n            \"columnName\": \"readTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"androidId\",\n            \"bookName\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"httpTTS\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `url` TEXT NOT NULL, PRIMARY KEY(`id`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"id\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      }\n    ],\n    \"views\": [],\n    \"setupQueries\": [\n      \"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)\",\n      \"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '2c443ea987b87d8daf2a6161a98d2d5c')\"\n    ]\n  }\n}"
  },
  {
    "path": "app/schemas/io.legado.app.data.AppDatabase/21.json",
    "content": "{\n  \"formatVersion\": 1,\n  \"database\": {\n    \"version\": 21,\n    \"identityHash\": \"eac4e5b7fdad840e82c1f607a3a8a46a\",\n    \"entities\": [\n      {\n        \"tableName\": \"books\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookUrl` TEXT NOT NULL, `tocUrl` TEXT NOT NULL, `origin` TEXT NOT NULL, `originName` TEXT NOT NULL, `name` TEXT NOT NULL, `author` TEXT NOT NULL, `kind` TEXT, `customTag` TEXT, `coverUrl` TEXT, `customCoverUrl` TEXT, `intro` TEXT, `customIntro` TEXT, `charset` TEXT, `type` INTEGER NOT NULL, `group` INTEGER NOT NULL, `latestChapterTitle` TEXT, `latestChapterTime` INTEGER NOT NULL, `lastCheckTime` INTEGER NOT NULL, `lastCheckCount` INTEGER NOT NULL, `totalChapterNum` INTEGER NOT NULL, `durChapterTitle` TEXT, `durChapterIndex` INTEGER NOT NULL, `durChapterPos` INTEGER NOT NULL, `durChapterTime` INTEGER NOT NULL, `wordCount` TEXT, `canUpdate` INTEGER NOT NULL, `order` INTEGER NOT NULL, `originOrder` INTEGER NOT NULL, `useReplaceRule` INTEGER NOT NULL, `variable` TEXT, `config` TEXT, PRIMARY KEY(`bookUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"tocUrl\",\n            \"columnName\": \"tocUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"originName\",\n            \"columnName\": \"originName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"author\",\n            \"columnName\": \"author\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"kind\",\n            \"columnName\": \"kind\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customTag\",\n            \"columnName\": \"customTag\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"coverUrl\",\n            \"columnName\": \"coverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customCoverUrl\",\n            \"columnName\": \"customCoverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"intro\",\n            \"columnName\": \"intro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customIntro\",\n            \"columnName\": \"customIntro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"charset\",\n            \"columnName\": \"charset\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"group\",\n            \"columnName\": \"group\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"latestChapterTitle\",\n            \"columnName\": \"latestChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"latestChapterTime\",\n            \"columnName\": \"latestChapterTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"lastCheckTime\",\n            \"columnName\": \"lastCheckTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"lastCheckCount\",\n            \"columnName\": \"lastCheckCount\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"totalChapterNum\",\n            \"columnName\": \"totalChapterNum\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"durChapterTitle\",\n            \"columnName\": \"durChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"durChapterIndex\",\n            \"columnName\": \"durChapterIndex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"durChapterPos\",\n            \"columnName\": \"durChapterPos\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"durChapterTime\",\n            \"columnName\": \"durChapterTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"wordCount\",\n            \"columnName\": \"wordCount\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"canUpdate\",\n            \"columnName\": \"canUpdate\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"originOrder\",\n            \"columnName\": \"originOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"useReplaceRule\",\n            \"columnName\": \"useReplaceRule\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"config\",\n            \"columnName\": \"config\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"bookUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_books_name_author\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"name\",\n              \"author\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_books_name_author` ON `${TABLE_NAME}` (`name`, `author`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"book_groups\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`groupId` INTEGER NOT NULL, `groupName` TEXT NOT NULL, `order` INTEGER NOT NULL, `show` INTEGER NOT NULL, PRIMARY KEY(`groupId`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"groupId\",\n            \"columnName\": \"groupId\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"groupName\",\n            \"columnName\": \"groupName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"show\",\n            \"columnName\": \"show\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"groupId\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"book_sources\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookSourceName` TEXT NOT NULL, `bookSourceGroup` TEXT, `bookSourceUrl` TEXT NOT NULL, `bookSourceType` INTEGER NOT NULL, `bookUrlPattern` TEXT, `customOrder` INTEGER NOT NULL, `enabled` INTEGER NOT NULL, `enabledExplore` INTEGER NOT NULL, `header` TEXT, `loginUrl` TEXT, `bookSourceComment` TEXT, `lastUpdateTime` INTEGER NOT NULL, `weight` INTEGER NOT NULL, `exploreUrl` TEXT, `ruleExplore` TEXT, `searchUrl` TEXT, `ruleSearch` TEXT, `ruleBookInfo` TEXT, `ruleToc` TEXT, `ruleContent` TEXT, PRIMARY KEY(`bookSourceUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookSourceName\",\n            \"columnName\": \"bookSourceName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookSourceGroup\",\n            \"columnName\": \"bookSourceGroup\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"bookSourceUrl\",\n            \"columnName\": \"bookSourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookSourceType\",\n            \"columnName\": \"bookSourceType\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookUrlPattern\",\n            \"columnName\": \"bookUrlPattern\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enabled\",\n            \"columnName\": \"enabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enabledExplore\",\n            \"columnName\": \"enabledExplore\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"header\",\n            \"columnName\": \"header\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUrl\",\n            \"columnName\": \"loginUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"bookSourceComment\",\n            \"columnName\": \"bookSourceComment\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"lastUpdateTime\",\n            \"columnName\": \"lastUpdateTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"weight\",\n            \"columnName\": \"weight\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"exploreUrl\",\n            \"columnName\": \"exploreUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleExplore\",\n            \"columnName\": \"ruleExplore\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"searchUrl\",\n            \"columnName\": \"searchUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleSearch\",\n            \"columnName\": \"ruleSearch\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleBookInfo\",\n            \"columnName\": \"ruleBookInfo\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleToc\",\n            \"columnName\": \"ruleToc\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleContent\",\n            \"columnName\": \"ruleContent\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"bookSourceUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_book_sources_bookSourceUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"bookSourceUrl\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_book_sources_bookSourceUrl` ON `${TABLE_NAME}` (`bookSourceUrl`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"chapters\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`url` TEXT NOT NULL, `title` TEXT NOT NULL, `bookUrl` TEXT NOT NULL, `index` INTEGER NOT NULL, `resourceUrl` TEXT, `tag` TEXT, `start` INTEGER, `end` INTEGER, `variable` TEXT, PRIMARY KEY(`url`, `bookUrl`), FOREIGN KEY(`bookUrl`) REFERENCES `books`(`bookUrl`) ON UPDATE NO ACTION ON DELETE CASCADE )\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"index\",\n            \"columnName\": \"index\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"resourceUrl\",\n            \"columnName\": \"resourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"tag\",\n            \"columnName\": \"tag\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"start\",\n            \"columnName\": \"start\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"end\",\n            \"columnName\": \"end\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"url\",\n            \"bookUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_chapters_bookUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"bookUrl\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_chapters_bookUrl` ON `${TABLE_NAME}` (`bookUrl`)\"\n          },\n          {\n            \"name\": \"index_chapters_bookUrl_index\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"bookUrl\",\n              \"index\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_chapters_bookUrl_index` ON `${TABLE_NAME}` (`bookUrl`, `index`)\"\n          }\n        ],\n        \"foreignKeys\": [\n          {\n            \"table\": \"books\",\n            \"onDelete\": \"CASCADE\",\n            \"onUpdate\": \"NO ACTION\",\n            \"columns\": [\n              \"bookUrl\"\n            ],\n            \"referencedColumns\": [\n              \"bookUrl\"\n            ]\n          }\n        ]\n      },\n      {\n        \"tableName\": \"replace_rules\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL, `group` TEXT, `pattern` TEXT NOT NULL, `replacement` TEXT NOT NULL, `scope` TEXT, `isEnabled` INTEGER NOT NULL, `isRegex` INTEGER NOT NULL, `sortOrder` INTEGER NOT NULL)\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"group\",\n            \"columnName\": \"group\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"pattern\",\n            \"columnName\": \"pattern\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"replacement\",\n            \"columnName\": \"replacement\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"scope\",\n            \"columnName\": \"scope\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"isEnabled\",\n            \"columnName\": \"isEnabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"isRegex\",\n            \"columnName\": \"isRegex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"sortOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"id\"\n          ],\n          \"autoGenerate\": true\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_replace_rules_id\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"id\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_replace_rules_id` ON `${TABLE_NAME}` (`id`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"searchBooks\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookUrl` TEXT NOT NULL, `origin` TEXT NOT NULL, `originName` TEXT NOT NULL, `type` INTEGER NOT NULL, `name` TEXT NOT NULL, `author` TEXT NOT NULL, `kind` TEXT, `coverUrl` TEXT, `intro` TEXT, `wordCount` TEXT, `latestChapterTitle` TEXT, `tocUrl` TEXT NOT NULL, `time` INTEGER NOT NULL, `variable` TEXT, `originOrder` INTEGER NOT NULL, PRIMARY KEY(`bookUrl`), FOREIGN KEY(`origin`) REFERENCES `book_sources`(`bookSourceUrl`) ON UPDATE NO ACTION ON DELETE CASCADE )\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"originName\",\n            \"columnName\": \"originName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"author\",\n            \"columnName\": \"author\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"kind\",\n            \"columnName\": \"kind\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"coverUrl\",\n            \"columnName\": \"coverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"intro\",\n            \"columnName\": \"intro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"wordCount\",\n            \"columnName\": \"wordCount\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"latestChapterTitle\",\n            \"columnName\": \"latestChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"tocUrl\",\n            \"columnName\": \"tocUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"time\",\n            \"columnName\": \"time\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"originOrder\",\n            \"columnName\": \"originOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"bookUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_searchBooks_bookUrl\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"bookUrl\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_searchBooks_bookUrl` ON `${TABLE_NAME}` (`bookUrl`)\"\n          },\n          {\n            \"name\": \"index_searchBooks_origin\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"origin\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_searchBooks_origin` ON `${TABLE_NAME}` (`origin`)\"\n          }\n        ],\n        \"foreignKeys\": [\n          {\n            \"table\": \"book_sources\",\n            \"onDelete\": \"CASCADE\",\n            \"onUpdate\": \"NO ACTION\",\n            \"columns\": [\n              \"origin\"\n            ],\n            \"referencedColumns\": [\n              \"bookSourceUrl\"\n            ]\n          }\n        ]\n      },\n      {\n        \"tableName\": \"search_keywords\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`word` TEXT NOT NULL, `usage` INTEGER NOT NULL, `lastUseTime` INTEGER NOT NULL, PRIMARY KEY(`word`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"word\",\n            \"columnName\": \"word\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"usage\",\n            \"columnName\": \"usage\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"lastUseTime\",\n            \"columnName\": \"lastUseTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"word\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_search_keywords_word\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"word\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_search_keywords_word` ON `${TABLE_NAME}` (`word`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"cookies\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`url` TEXT NOT NULL, `cookie` TEXT NOT NULL, PRIMARY KEY(`url`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"cookie\",\n            \"columnName\": \"cookie\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"url\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_cookies_url\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"url\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_cookies_url` ON `${TABLE_NAME}` (`url`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssSources\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`sourceUrl` TEXT NOT NULL, `sourceName` TEXT NOT NULL, `sourceIcon` TEXT NOT NULL, `sourceGroup` TEXT, `enabled` INTEGER NOT NULL, `sortUrl` TEXT, `articleStyle` INTEGER NOT NULL, `ruleArticles` TEXT, `ruleNextPage` TEXT, `ruleTitle` TEXT, `rulePubDate` TEXT, `ruleDescription` TEXT, `ruleImage` TEXT, `ruleLink` TEXT, `ruleContent` TEXT, `style` TEXT, `header` TEXT, `enableJs` INTEGER NOT NULL, `loadWithBaseUrl` INTEGER NOT NULL, `customOrder` INTEGER NOT NULL, PRIMARY KEY(`sourceUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"sourceUrl\",\n            \"columnName\": \"sourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceName\",\n            \"columnName\": \"sourceName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceIcon\",\n            \"columnName\": \"sourceIcon\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceGroup\",\n            \"columnName\": \"sourceGroup\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enabled\",\n            \"columnName\": \"enabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sortUrl\",\n            \"columnName\": \"sortUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"articleStyle\",\n            \"columnName\": \"articleStyle\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"ruleArticles\",\n            \"columnName\": \"ruleArticles\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleNextPage\",\n            \"columnName\": \"ruleNextPage\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleTitle\",\n            \"columnName\": \"ruleTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"rulePubDate\",\n            \"columnName\": \"rulePubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleDescription\",\n            \"columnName\": \"ruleDescription\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleImage\",\n            \"columnName\": \"ruleImage\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleLink\",\n            \"columnName\": \"ruleLink\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleContent\",\n            \"columnName\": \"ruleContent\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"style\",\n            \"columnName\": \"style\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"header\",\n            \"columnName\": \"header\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enableJs\",\n            \"columnName\": \"enableJs\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"loadWithBaseUrl\",\n            \"columnName\": \"loadWithBaseUrl\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"sourceUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_rssSources_sourceUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"sourceUrl\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_rssSources_sourceUrl` ON `${TABLE_NAME}` (`sourceUrl`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"bookmarks\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`time` INTEGER NOT NULL, `bookUrl` TEXT NOT NULL, `bookName` TEXT NOT NULL, `bookAuthor` TEXT NOT NULL, `chapterIndex` INTEGER NOT NULL, `pageIndex` INTEGER NOT NULL, `chapterName` TEXT NOT NULL, `content` TEXT NOT NULL, PRIMARY KEY(`time`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"time\",\n            \"columnName\": \"time\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookName\",\n            \"columnName\": \"bookName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookAuthor\",\n            \"columnName\": \"bookAuthor\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterIndex\",\n            \"columnName\": \"chapterIndex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"pageIndex\",\n            \"columnName\": \"pageIndex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterName\",\n            \"columnName\": \"chapterName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"time\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_bookmarks_time\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"time\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_bookmarks_time` ON `${TABLE_NAME}` (`time`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssArticles\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`origin` TEXT NOT NULL, `sort` TEXT NOT NULL, `title` TEXT NOT NULL, `order` INTEGER NOT NULL, `link` TEXT NOT NULL, `pubDate` TEXT, `description` TEXT, `content` TEXT, `image` TEXT, `read` INTEGER NOT NULL, PRIMARY KEY(`origin`, `link`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sort\",\n            \"columnName\": \"sort\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"link\",\n            \"columnName\": \"link\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"pubDate\",\n            \"columnName\": \"pubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"description\",\n            \"columnName\": \"description\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"image\",\n            \"columnName\": \"image\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"read\",\n            \"columnName\": \"read\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"origin\",\n            \"link\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssReadRecords\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`record` TEXT NOT NULL, `read` INTEGER NOT NULL, PRIMARY KEY(`record`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"record\",\n            \"columnName\": \"record\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"read\",\n            \"columnName\": \"read\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"record\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssStars\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`origin` TEXT NOT NULL, `sort` TEXT NOT NULL, `title` TEXT NOT NULL, `starTime` INTEGER NOT NULL, `link` TEXT NOT NULL, `pubDate` TEXT, `description` TEXT, `content` TEXT, `image` TEXT, PRIMARY KEY(`origin`, `link`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sort\",\n            \"columnName\": \"sort\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"starTime\",\n            \"columnName\": \"starTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"link\",\n            \"columnName\": \"link\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"pubDate\",\n            \"columnName\": \"pubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"description\",\n            \"columnName\": \"description\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"image\",\n            \"columnName\": \"image\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"origin\",\n            \"link\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"txtTocRules\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `rule` TEXT NOT NULL, `serialNumber` INTEGER NOT NULL, `enable` INTEGER NOT NULL, PRIMARY KEY(`id`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"rule\",\n            \"columnName\": \"rule\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"serialNumber\",\n            \"columnName\": \"serialNumber\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enable\",\n            \"columnName\": \"enable\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"id\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"readRecord\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`androidId` TEXT NOT NULL, `bookName` TEXT NOT NULL, `readTime` INTEGER NOT NULL, PRIMARY KEY(`androidId`, `bookName`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"androidId\",\n            \"columnName\": \"androidId\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookName\",\n            \"columnName\": \"bookName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"readTime\",\n            \"columnName\": \"readTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"androidId\",\n            \"bookName\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"httpTTS\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `url` TEXT NOT NULL, PRIMARY KEY(`id`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"id\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      }\n    ],\n    \"views\": [],\n    \"setupQueries\": [\n      \"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)\",\n      \"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'eac4e5b7fdad840e82c1f607a3a8a46a')\"\n    ]\n  }\n}"
  },
  {
    "path": "app/schemas/io.legado.app.data.AppDatabase/22.json",
    "content": "{\n  \"formatVersion\": 1,\n  \"database\": {\n    \"version\": 22,\n    \"identityHash\": \"9cf4f754700355578a8b8bf045c0e8e1\",\n    \"entities\": [\n      {\n        \"tableName\": \"books\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookUrl` TEXT NOT NULL, `tocUrl` TEXT NOT NULL, `origin` TEXT NOT NULL, `originName` TEXT NOT NULL, `name` TEXT NOT NULL, `author` TEXT NOT NULL, `kind` TEXT, `customTag` TEXT, `coverUrl` TEXT, `customCoverUrl` TEXT, `intro` TEXT, `customIntro` TEXT, `charset` TEXT, `type` INTEGER NOT NULL, `group` INTEGER NOT NULL, `latestChapterTitle` TEXT, `latestChapterTime` INTEGER NOT NULL, `lastCheckTime` INTEGER NOT NULL, `lastCheckCount` INTEGER NOT NULL, `totalChapterNum` INTEGER NOT NULL, `durChapterTitle` TEXT, `durChapterIndex` INTEGER NOT NULL, `durChapterPos` INTEGER NOT NULL, `durChapterTime` INTEGER NOT NULL, `wordCount` TEXT, `canUpdate` INTEGER NOT NULL, `order` INTEGER NOT NULL, `originOrder` INTEGER NOT NULL, `variable` TEXT, `readConfig` TEXT, PRIMARY KEY(`bookUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"tocUrl\",\n            \"columnName\": \"tocUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"originName\",\n            \"columnName\": \"originName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"author\",\n            \"columnName\": \"author\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"kind\",\n            \"columnName\": \"kind\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customTag\",\n            \"columnName\": \"customTag\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"coverUrl\",\n            \"columnName\": \"coverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customCoverUrl\",\n            \"columnName\": \"customCoverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"intro\",\n            \"columnName\": \"intro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customIntro\",\n            \"columnName\": \"customIntro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"charset\",\n            \"columnName\": \"charset\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"group\",\n            \"columnName\": \"group\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"latestChapterTitle\",\n            \"columnName\": \"latestChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"latestChapterTime\",\n            \"columnName\": \"latestChapterTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"lastCheckTime\",\n            \"columnName\": \"lastCheckTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"lastCheckCount\",\n            \"columnName\": \"lastCheckCount\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"totalChapterNum\",\n            \"columnName\": \"totalChapterNum\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"durChapterTitle\",\n            \"columnName\": \"durChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"durChapterIndex\",\n            \"columnName\": \"durChapterIndex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"durChapterPos\",\n            \"columnName\": \"durChapterPos\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"durChapterTime\",\n            \"columnName\": \"durChapterTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"wordCount\",\n            \"columnName\": \"wordCount\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"canUpdate\",\n            \"columnName\": \"canUpdate\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"originOrder\",\n            \"columnName\": \"originOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"readConfig\",\n            \"columnName\": \"readConfig\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"bookUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_books_name_author\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"name\",\n              \"author\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_books_name_author` ON `${TABLE_NAME}` (`name`, `author`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"book_groups\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`groupId` INTEGER NOT NULL, `groupName` TEXT NOT NULL, `order` INTEGER NOT NULL, `show` INTEGER NOT NULL, PRIMARY KEY(`groupId`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"groupId\",\n            \"columnName\": \"groupId\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"groupName\",\n            \"columnName\": \"groupName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"show\",\n            \"columnName\": \"show\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"groupId\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"book_sources\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookSourceName` TEXT NOT NULL, `bookSourceGroup` TEXT, `bookSourceUrl` TEXT NOT NULL, `bookSourceType` INTEGER NOT NULL, `bookUrlPattern` TEXT, `customOrder` INTEGER NOT NULL, `enabled` INTEGER NOT NULL, `enabledExplore` INTEGER NOT NULL, `header` TEXT, `loginUrl` TEXT, `bookSourceComment` TEXT, `lastUpdateTime` INTEGER NOT NULL, `weight` INTEGER NOT NULL, `exploreUrl` TEXT, `ruleExplore` TEXT, `searchUrl` TEXT, `ruleSearch` TEXT, `ruleBookInfo` TEXT, `ruleToc` TEXT, `ruleContent` TEXT, PRIMARY KEY(`bookSourceUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookSourceName\",\n            \"columnName\": \"bookSourceName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookSourceGroup\",\n            \"columnName\": \"bookSourceGroup\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"bookSourceUrl\",\n            \"columnName\": \"bookSourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookSourceType\",\n            \"columnName\": \"bookSourceType\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookUrlPattern\",\n            \"columnName\": \"bookUrlPattern\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enabled\",\n            \"columnName\": \"enabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enabledExplore\",\n            \"columnName\": \"enabledExplore\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"header\",\n            \"columnName\": \"header\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUrl\",\n            \"columnName\": \"loginUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"bookSourceComment\",\n            \"columnName\": \"bookSourceComment\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"lastUpdateTime\",\n            \"columnName\": \"lastUpdateTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"weight\",\n            \"columnName\": \"weight\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"exploreUrl\",\n            \"columnName\": \"exploreUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleExplore\",\n            \"columnName\": \"ruleExplore\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"searchUrl\",\n            \"columnName\": \"searchUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleSearch\",\n            \"columnName\": \"ruleSearch\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleBookInfo\",\n            \"columnName\": \"ruleBookInfo\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleToc\",\n            \"columnName\": \"ruleToc\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleContent\",\n            \"columnName\": \"ruleContent\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"bookSourceUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_book_sources_bookSourceUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"bookSourceUrl\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_book_sources_bookSourceUrl` ON `${TABLE_NAME}` (`bookSourceUrl`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"chapters\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`url` TEXT NOT NULL, `title` TEXT NOT NULL, `bookUrl` TEXT NOT NULL, `index` INTEGER NOT NULL, `resourceUrl` TEXT, `tag` TEXT, `start` INTEGER, `end` INTEGER, `variable` TEXT, PRIMARY KEY(`url`, `bookUrl`), FOREIGN KEY(`bookUrl`) REFERENCES `books`(`bookUrl`) ON UPDATE NO ACTION ON DELETE CASCADE )\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"index\",\n            \"columnName\": \"index\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"resourceUrl\",\n            \"columnName\": \"resourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"tag\",\n            \"columnName\": \"tag\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"start\",\n            \"columnName\": \"start\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"end\",\n            \"columnName\": \"end\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"url\",\n            \"bookUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_chapters_bookUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"bookUrl\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_chapters_bookUrl` ON `${TABLE_NAME}` (`bookUrl`)\"\n          },\n          {\n            \"name\": \"index_chapters_bookUrl_index\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"bookUrl\",\n              \"index\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_chapters_bookUrl_index` ON `${TABLE_NAME}` (`bookUrl`, `index`)\"\n          }\n        ],\n        \"foreignKeys\": [\n          {\n            \"table\": \"books\",\n            \"onDelete\": \"CASCADE\",\n            \"onUpdate\": \"NO ACTION\",\n            \"columns\": [\n              \"bookUrl\"\n            ],\n            \"referencedColumns\": [\n              \"bookUrl\"\n            ]\n          }\n        ]\n      },\n      {\n        \"tableName\": \"replace_rules\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL, `group` TEXT, `pattern` TEXT NOT NULL, `replacement` TEXT NOT NULL, `scope` TEXT, `isEnabled` INTEGER NOT NULL, `isRegex` INTEGER NOT NULL, `sortOrder` INTEGER NOT NULL)\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"group\",\n            \"columnName\": \"group\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"pattern\",\n            \"columnName\": \"pattern\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"replacement\",\n            \"columnName\": \"replacement\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"scope\",\n            \"columnName\": \"scope\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"isEnabled\",\n            \"columnName\": \"isEnabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"isRegex\",\n            \"columnName\": \"isRegex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"sortOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"id\"\n          ],\n          \"autoGenerate\": true\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_replace_rules_id\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"id\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_replace_rules_id` ON `${TABLE_NAME}` (`id`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"searchBooks\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookUrl` TEXT NOT NULL, `origin` TEXT NOT NULL, `originName` TEXT NOT NULL, `type` INTEGER NOT NULL, `name` TEXT NOT NULL, `author` TEXT NOT NULL, `kind` TEXT, `coverUrl` TEXT, `intro` TEXT, `wordCount` TEXT, `latestChapterTitle` TEXT, `tocUrl` TEXT NOT NULL, `time` INTEGER NOT NULL, `variable` TEXT, `originOrder` INTEGER NOT NULL, PRIMARY KEY(`bookUrl`), FOREIGN KEY(`origin`) REFERENCES `book_sources`(`bookSourceUrl`) ON UPDATE NO ACTION ON DELETE CASCADE )\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"originName\",\n            \"columnName\": \"originName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"author\",\n            \"columnName\": \"author\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"kind\",\n            \"columnName\": \"kind\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"coverUrl\",\n            \"columnName\": \"coverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"intro\",\n            \"columnName\": \"intro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"wordCount\",\n            \"columnName\": \"wordCount\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"latestChapterTitle\",\n            \"columnName\": \"latestChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"tocUrl\",\n            \"columnName\": \"tocUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"time\",\n            \"columnName\": \"time\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"originOrder\",\n            \"columnName\": \"originOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"bookUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_searchBooks_bookUrl\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"bookUrl\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_searchBooks_bookUrl` ON `${TABLE_NAME}` (`bookUrl`)\"\n          },\n          {\n            \"name\": \"index_searchBooks_origin\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"origin\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_searchBooks_origin` ON `${TABLE_NAME}` (`origin`)\"\n          }\n        ],\n        \"foreignKeys\": [\n          {\n            \"table\": \"book_sources\",\n            \"onDelete\": \"CASCADE\",\n            \"onUpdate\": \"NO ACTION\",\n            \"columns\": [\n              \"origin\"\n            ],\n            \"referencedColumns\": [\n              \"bookSourceUrl\"\n            ]\n          }\n        ]\n      },\n      {\n        \"tableName\": \"search_keywords\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`word` TEXT NOT NULL, `usage` INTEGER NOT NULL, `lastUseTime` INTEGER NOT NULL, PRIMARY KEY(`word`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"word\",\n            \"columnName\": \"word\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"usage\",\n            \"columnName\": \"usage\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"lastUseTime\",\n            \"columnName\": \"lastUseTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"word\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_search_keywords_word\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"word\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_search_keywords_word` ON `${TABLE_NAME}` (`word`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"cookies\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`url` TEXT NOT NULL, `cookie` TEXT NOT NULL, PRIMARY KEY(`url`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"cookie\",\n            \"columnName\": \"cookie\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"url\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_cookies_url\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"url\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_cookies_url` ON `${TABLE_NAME}` (`url`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssSources\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`sourceUrl` TEXT NOT NULL, `sourceName` TEXT NOT NULL, `sourceIcon` TEXT NOT NULL, `sourceGroup` TEXT, `enabled` INTEGER NOT NULL, `sortUrl` TEXT, `articleStyle` INTEGER NOT NULL, `ruleArticles` TEXT, `ruleNextPage` TEXT, `ruleTitle` TEXT, `rulePubDate` TEXT, `ruleDescription` TEXT, `ruleImage` TEXT, `ruleLink` TEXT, `ruleContent` TEXT, `style` TEXT, `header` TEXT, `enableJs` INTEGER NOT NULL, `loadWithBaseUrl` INTEGER NOT NULL, `customOrder` INTEGER NOT NULL, PRIMARY KEY(`sourceUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"sourceUrl\",\n            \"columnName\": \"sourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceName\",\n            \"columnName\": \"sourceName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceIcon\",\n            \"columnName\": \"sourceIcon\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceGroup\",\n            \"columnName\": \"sourceGroup\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enabled\",\n            \"columnName\": \"enabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sortUrl\",\n            \"columnName\": \"sortUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"articleStyle\",\n            \"columnName\": \"articleStyle\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"ruleArticles\",\n            \"columnName\": \"ruleArticles\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleNextPage\",\n            \"columnName\": \"ruleNextPage\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleTitle\",\n            \"columnName\": \"ruleTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"rulePubDate\",\n            \"columnName\": \"rulePubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleDescription\",\n            \"columnName\": \"ruleDescription\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleImage\",\n            \"columnName\": \"ruleImage\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleLink\",\n            \"columnName\": \"ruleLink\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleContent\",\n            \"columnName\": \"ruleContent\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"style\",\n            \"columnName\": \"style\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"header\",\n            \"columnName\": \"header\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enableJs\",\n            \"columnName\": \"enableJs\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"loadWithBaseUrl\",\n            \"columnName\": \"loadWithBaseUrl\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"sourceUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_rssSources_sourceUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"sourceUrl\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_rssSources_sourceUrl` ON `${TABLE_NAME}` (`sourceUrl`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"bookmarks\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`time` INTEGER NOT NULL, `bookUrl` TEXT NOT NULL, `bookName` TEXT NOT NULL, `bookAuthor` TEXT NOT NULL, `chapterIndex` INTEGER NOT NULL, `pageIndex` INTEGER NOT NULL, `chapterName` TEXT NOT NULL, `content` TEXT NOT NULL, PRIMARY KEY(`time`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"time\",\n            \"columnName\": \"time\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookName\",\n            \"columnName\": \"bookName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookAuthor\",\n            \"columnName\": \"bookAuthor\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterIndex\",\n            \"columnName\": \"chapterIndex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"pageIndex\",\n            \"columnName\": \"pageIndex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterName\",\n            \"columnName\": \"chapterName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"time\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_bookmarks_time\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"time\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_bookmarks_time` ON `${TABLE_NAME}` (`time`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssArticles\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`origin` TEXT NOT NULL, `sort` TEXT NOT NULL, `title` TEXT NOT NULL, `order` INTEGER NOT NULL, `link` TEXT NOT NULL, `pubDate` TEXT, `description` TEXT, `content` TEXT, `image` TEXT, `read` INTEGER NOT NULL, PRIMARY KEY(`origin`, `link`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sort\",\n            \"columnName\": \"sort\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"link\",\n            \"columnName\": \"link\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"pubDate\",\n            \"columnName\": \"pubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"description\",\n            \"columnName\": \"description\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"image\",\n            \"columnName\": \"image\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"read\",\n            \"columnName\": \"read\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"origin\",\n            \"link\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssReadRecords\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`record` TEXT NOT NULL, `read` INTEGER NOT NULL, PRIMARY KEY(`record`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"record\",\n            \"columnName\": \"record\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"read\",\n            \"columnName\": \"read\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"record\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssStars\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`origin` TEXT NOT NULL, `sort` TEXT NOT NULL, `title` TEXT NOT NULL, `starTime` INTEGER NOT NULL, `link` TEXT NOT NULL, `pubDate` TEXT, `description` TEXT, `content` TEXT, `image` TEXT, PRIMARY KEY(`origin`, `link`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sort\",\n            \"columnName\": \"sort\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"starTime\",\n            \"columnName\": \"starTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"link\",\n            \"columnName\": \"link\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"pubDate\",\n            \"columnName\": \"pubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"description\",\n            \"columnName\": \"description\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"image\",\n            \"columnName\": \"image\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"origin\",\n            \"link\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"txtTocRules\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `rule` TEXT NOT NULL, `serialNumber` INTEGER NOT NULL, `enable` INTEGER NOT NULL, PRIMARY KEY(`id`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"rule\",\n            \"columnName\": \"rule\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"serialNumber\",\n            \"columnName\": \"serialNumber\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enable\",\n            \"columnName\": \"enable\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"id\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"readRecord\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`androidId` TEXT NOT NULL, `bookName` TEXT NOT NULL, `readTime` INTEGER NOT NULL, PRIMARY KEY(`androidId`, `bookName`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"androidId\",\n            \"columnName\": \"androidId\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookName\",\n            \"columnName\": \"bookName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"readTime\",\n            \"columnName\": \"readTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"androidId\",\n            \"bookName\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"httpTTS\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `url` TEXT NOT NULL, PRIMARY KEY(`id`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"id\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      }\n    ],\n    \"views\": [],\n    \"setupQueries\": [\n      \"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)\",\n      \"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '9cf4f754700355578a8b8bf045c0e8e1')\"\n    ]\n  }\n}"
  },
  {
    "path": "app/schemas/io.legado.app.data.AppDatabase/23.json",
    "content": "{\n  \"formatVersion\": 1,\n  \"database\": {\n    \"version\": 23,\n    \"identityHash\": \"874aa30f88921306741b488dbad38536\",\n    \"entities\": [\n      {\n        \"tableName\": \"books\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookUrl` TEXT NOT NULL, `tocUrl` TEXT NOT NULL, `origin` TEXT NOT NULL, `originName` TEXT NOT NULL, `name` TEXT NOT NULL, `author` TEXT NOT NULL, `kind` TEXT, `customTag` TEXT, `coverUrl` TEXT, `customCoverUrl` TEXT, `intro` TEXT, `customIntro` TEXT, `charset` TEXT, `type` INTEGER NOT NULL, `group` INTEGER NOT NULL, `latestChapterTitle` TEXT, `latestChapterTime` INTEGER NOT NULL, `lastCheckTime` INTEGER NOT NULL, `lastCheckCount` INTEGER NOT NULL, `totalChapterNum` INTEGER NOT NULL, `durChapterTitle` TEXT, `durChapterIndex` INTEGER NOT NULL, `durChapterPos` INTEGER NOT NULL, `durChapterTime` INTEGER NOT NULL, `wordCount` TEXT, `canUpdate` INTEGER NOT NULL, `order` INTEGER NOT NULL, `originOrder` INTEGER NOT NULL, `variable` TEXT, `readConfig` TEXT, PRIMARY KEY(`bookUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"tocUrl\",\n            \"columnName\": \"tocUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"originName\",\n            \"columnName\": \"originName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"author\",\n            \"columnName\": \"author\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"kind\",\n            \"columnName\": \"kind\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customTag\",\n            \"columnName\": \"customTag\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"coverUrl\",\n            \"columnName\": \"coverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customCoverUrl\",\n            \"columnName\": \"customCoverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"intro\",\n            \"columnName\": \"intro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customIntro\",\n            \"columnName\": \"customIntro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"charset\",\n            \"columnName\": \"charset\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"group\",\n            \"columnName\": \"group\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"latestChapterTitle\",\n            \"columnName\": \"latestChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"latestChapterTime\",\n            \"columnName\": \"latestChapterTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"lastCheckTime\",\n            \"columnName\": \"lastCheckTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"lastCheckCount\",\n            \"columnName\": \"lastCheckCount\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"totalChapterNum\",\n            \"columnName\": \"totalChapterNum\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"durChapterTitle\",\n            \"columnName\": \"durChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"durChapterIndex\",\n            \"columnName\": \"durChapterIndex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"durChapterPos\",\n            \"columnName\": \"durChapterPos\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"durChapterTime\",\n            \"columnName\": \"durChapterTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"wordCount\",\n            \"columnName\": \"wordCount\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"canUpdate\",\n            \"columnName\": \"canUpdate\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"originOrder\",\n            \"columnName\": \"originOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"readConfig\",\n            \"columnName\": \"readConfig\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"bookUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_books_name_author\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"name\",\n              \"author\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_books_name_author` ON `${TABLE_NAME}` (`name`, `author`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"book_groups\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`groupId` INTEGER NOT NULL, `groupName` TEXT NOT NULL, `order` INTEGER NOT NULL, `show` INTEGER NOT NULL, PRIMARY KEY(`groupId`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"groupId\",\n            \"columnName\": \"groupId\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"groupName\",\n            \"columnName\": \"groupName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"show\",\n            \"columnName\": \"show\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"groupId\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"book_sources\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookSourceName` TEXT NOT NULL, `bookSourceGroup` TEXT, `bookSourceUrl` TEXT NOT NULL, `bookSourceType` INTEGER NOT NULL, `bookUrlPattern` TEXT, `customOrder` INTEGER NOT NULL, `enabled` INTEGER NOT NULL, `enabledExplore` INTEGER NOT NULL, `header` TEXT, `loginUrl` TEXT, `bookSourceComment` TEXT, `lastUpdateTime` INTEGER NOT NULL, `weight` INTEGER NOT NULL, `exploreUrl` TEXT, `ruleExplore` TEXT, `searchUrl` TEXT, `ruleSearch` TEXT, `ruleBookInfo` TEXT, `ruleToc` TEXT, `ruleContent` TEXT, PRIMARY KEY(`bookSourceUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookSourceName\",\n            \"columnName\": \"bookSourceName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookSourceGroup\",\n            \"columnName\": \"bookSourceGroup\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"bookSourceUrl\",\n            \"columnName\": \"bookSourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookSourceType\",\n            \"columnName\": \"bookSourceType\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookUrlPattern\",\n            \"columnName\": \"bookUrlPattern\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enabled\",\n            \"columnName\": \"enabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enabledExplore\",\n            \"columnName\": \"enabledExplore\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"header\",\n            \"columnName\": \"header\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUrl\",\n            \"columnName\": \"loginUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"bookSourceComment\",\n            \"columnName\": \"bookSourceComment\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"lastUpdateTime\",\n            \"columnName\": \"lastUpdateTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"weight\",\n            \"columnName\": \"weight\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"exploreUrl\",\n            \"columnName\": \"exploreUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleExplore\",\n            \"columnName\": \"ruleExplore\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"searchUrl\",\n            \"columnName\": \"searchUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleSearch\",\n            \"columnName\": \"ruleSearch\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleBookInfo\",\n            \"columnName\": \"ruleBookInfo\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleToc\",\n            \"columnName\": \"ruleToc\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleContent\",\n            \"columnName\": \"ruleContent\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"bookSourceUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_book_sources_bookSourceUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"bookSourceUrl\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_book_sources_bookSourceUrl` ON `${TABLE_NAME}` (`bookSourceUrl`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"chapters\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`url` TEXT NOT NULL, `title` TEXT NOT NULL, `baseUrl` TEXT NOT NULL, `bookUrl` TEXT NOT NULL, `index` INTEGER NOT NULL, `resourceUrl` TEXT, `tag` TEXT, `start` INTEGER, `end` INTEGER, `variable` TEXT, PRIMARY KEY(`url`, `bookUrl`), FOREIGN KEY(`bookUrl`) REFERENCES `books`(`bookUrl`) ON UPDATE NO ACTION ON DELETE CASCADE )\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"baseUrl\",\n            \"columnName\": \"baseUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"index\",\n            \"columnName\": \"index\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"resourceUrl\",\n            \"columnName\": \"resourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"tag\",\n            \"columnName\": \"tag\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"start\",\n            \"columnName\": \"start\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"end\",\n            \"columnName\": \"end\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"url\",\n            \"bookUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_chapters_bookUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"bookUrl\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_chapters_bookUrl` ON `${TABLE_NAME}` (`bookUrl`)\"\n          },\n          {\n            \"name\": \"index_chapters_bookUrl_index\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"bookUrl\",\n              \"index\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_chapters_bookUrl_index` ON `${TABLE_NAME}` (`bookUrl`, `index`)\"\n          }\n        ],\n        \"foreignKeys\": [\n          {\n            \"table\": \"books\",\n            \"onDelete\": \"CASCADE\",\n            \"onUpdate\": \"NO ACTION\",\n            \"columns\": [\n              \"bookUrl\"\n            ],\n            \"referencedColumns\": [\n              \"bookUrl\"\n            ]\n          }\n        ]\n      },\n      {\n        \"tableName\": \"replace_rules\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL, `group` TEXT, `pattern` TEXT NOT NULL, `replacement` TEXT NOT NULL, `scope` TEXT, `isEnabled` INTEGER NOT NULL, `isRegex` INTEGER NOT NULL, `sortOrder` INTEGER NOT NULL)\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"group\",\n            \"columnName\": \"group\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"pattern\",\n            \"columnName\": \"pattern\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"replacement\",\n            \"columnName\": \"replacement\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"scope\",\n            \"columnName\": \"scope\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"isEnabled\",\n            \"columnName\": \"isEnabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"isRegex\",\n            \"columnName\": \"isRegex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"sortOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"id\"\n          ],\n          \"autoGenerate\": true\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_replace_rules_id\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"id\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_replace_rules_id` ON `${TABLE_NAME}` (`id`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"searchBooks\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookUrl` TEXT NOT NULL, `origin` TEXT NOT NULL, `originName` TEXT NOT NULL, `type` INTEGER NOT NULL, `name` TEXT NOT NULL, `author` TEXT NOT NULL, `kind` TEXT, `coverUrl` TEXT, `intro` TEXT, `wordCount` TEXT, `latestChapterTitle` TEXT, `tocUrl` TEXT NOT NULL, `time` INTEGER NOT NULL, `variable` TEXT, `originOrder` INTEGER NOT NULL, PRIMARY KEY(`bookUrl`), FOREIGN KEY(`origin`) REFERENCES `book_sources`(`bookSourceUrl`) ON UPDATE NO ACTION ON DELETE CASCADE )\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"originName\",\n            \"columnName\": \"originName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"author\",\n            \"columnName\": \"author\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"kind\",\n            \"columnName\": \"kind\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"coverUrl\",\n            \"columnName\": \"coverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"intro\",\n            \"columnName\": \"intro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"wordCount\",\n            \"columnName\": \"wordCount\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"latestChapterTitle\",\n            \"columnName\": \"latestChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"tocUrl\",\n            \"columnName\": \"tocUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"time\",\n            \"columnName\": \"time\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"originOrder\",\n            \"columnName\": \"originOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"bookUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_searchBooks_bookUrl\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"bookUrl\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_searchBooks_bookUrl` ON `${TABLE_NAME}` (`bookUrl`)\"\n          },\n          {\n            \"name\": \"index_searchBooks_origin\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"origin\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_searchBooks_origin` ON `${TABLE_NAME}` (`origin`)\"\n          }\n        ],\n        \"foreignKeys\": [\n          {\n            \"table\": \"book_sources\",\n            \"onDelete\": \"CASCADE\",\n            \"onUpdate\": \"NO ACTION\",\n            \"columns\": [\n              \"origin\"\n            ],\n            \"referencedColumns\": [\n              \"bookSourceUrl\"\n            ]\n          }\n        ]\n      },\n      {\n        \"tableName\": \"search_keywords\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`word` TEXT NOT NULL, `usage` INTEGER NOT NULL, `lastUseTime` INTEGER NOT NULL, PRIMARY KEY(`word`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"word\",\n            \"columnName\": \"word\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"usage\",\n            \"columnName\": \"usage\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"lastUseTime\",\n            \"columnName\": \"lastUseTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"word\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_search_keywords_word\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"word\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_search_keywords_word` ON `${TABLE_NAME}` (`word`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"cookies\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`url` TEXT NOT NULL, `cookie` TEXT NOT NULL, PRIMARY KEY(`url`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"cookie\",\n            \"columnName\": \"cookie\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"url\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_cookies_url\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"url\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_cookies_url` ON `${TABLE_NAME}` (`url`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssSources\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`sourceUrl` TEXT NOT NULL, `sourceName` TEXT NOT NULL, `sourceIcon` TEXT NOT NULL, `sourceGroup` TEXT, `enabled` INTEGER NOT NULL, `sortUrl` TEXT, `articleStyle` INTEGER NOT NULL, `ruleArticles` TEXT, `ruleNextPage` TEXT, `ruleTitle` TEXT, `rulePubDate` TEXT, `ruleDescription` TEXT, `ruleImage` TEXT, `ruleLink` TEXT, `ruleContent` TEXT, `style` TEXT, `header` TEXT, `enableJs` INTEGER NOT NULL, `loadWithBaseUrl` INTEGER NOT NULL, `customOrder` INTEGER NOT NULL, PRIMARY KEY(`sourceUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"sourceUrl\",\n            \"columnName\": \"sourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceName\",\n            \"columnName\": \"sourceName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceIcon\",\n            \"columnName\": \"sourceIcon\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceGroup\",\n            \"columnName\": \"sourceGroup\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enabled\",\n            \"columnName\": \"enabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sortUrl\",\n            \"columnName\": \"sortUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"articleStyle\",\n            \"columnName\": \"articleStyle\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"ruleArticles\",\n            \"columnName\": \"ruleArticles\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleNextPage\",\n            \"columnName\": \"ruleNextPage\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleTitle\",\n            \"columnName\": \"ruleTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"rulePubDate\",\n            \"columnName\": \"rulePubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleDescription\",\n            \"columnName\": \"ruleDescription\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleImage\",\n            \"columnName\": \"ruleImage\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleLink\",\n            \"columnName\": \"ruleLink\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleContent\",\n            \"columnName\": \"ruleContent\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"style\",\n            \"columnName\": \"style\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"header\",\n            \"columnName\": \"header\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enableJs\",\n            \"columnName\": \"enableJs\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"loadWithBaseUrl\",\n            \"columnName\": \"loadWithBaseUrl\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"sourceUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_rssSources_sourceUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"sourceUrl\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_rssSources_sourceUrl` ON `${TABLE_NAME}` (`sourceUrl`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"bookmarks\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`time` INTEGER NOT NULL, `bookUrl` TEXT NOT NULL, `bookName` TEXT NOT NULL, `bookAuthor` TEXT NOT NULL, `chapterIndex` INTEGER NOT NULL, `pageIndex` INTEGER NOT NULL, `chapterName` TEXT NOT NULL, `content` TEXT NOT NULL, PRIMARY KEY(`time`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"time\",\n            \"columnName\": \"time\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookName\",\n            \"columnName\": \"bookName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookAuthor\",\n            \"columnName\": \"bookAuthor\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterIndex\",\n            \"columnName\": \"chapterIndex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"pageIndex\",\n            \"columnName\": \"pageIndex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterName\",\n            \"columnName\": \"chapterName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"time\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_bookmarks_time\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"time\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_bookmarks_time` ON `${TABLE_NAME}` (`time`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssArticles\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`origin` TEXT NOT NULL, `sort` TEXT NOT NULL, `title` TEXT NOT NULL, `order` INTEGER NOT NULL, `link` TEXT NOT NULL, `pubDate` TEXT, `description` TEXT, `content` TEXT, `image` TEXT, `read` INTEGER NOT NULL, PRIMARY KEY(`origin`, `link`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sort\",\n            \"columnName\": \"sort\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"link\",\n            \"columnName\": \"link\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"pubDate\",\n            \"columnName\": \"pubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"description\",\n            \"columnName\": \"description\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"image\",\n            \"columnName\": \"image\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"read\",\n            \"columnName\": \"read\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"origin\",\n            \"link\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssReadRecords\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`record` TEXT NOT NULL, `read` INTEGER NOT NULL, PRIMARY KEY(`record`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"record\",\n            \"columnName\": \"record\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"read\",\n            \"columnName\": \"read\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"record\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssStars\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`origin` TEXT NOT NULL, `sort` TEXT NOT NULL, `title` TEXT NOT NULL, `starTime` INTEGER NOT NULL, `link` TEXT NOT NULL, `pubDate` TEXT, `description` TEXT, `content` TEXT, `image` TEXT, PRIMARY KEY(`origin`, `link`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sort\",\n            \"columnName\": \"sort\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"starTime\",\n            \"columnName\": \"starTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"link\",\n            \"columnName\": \"link\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"pubDate\",\n            \"columnName\": \"pubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"description\",\n            \"columnName\": \"description\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"image\",\n            \"columnName\": \"image\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"origin\",\n            \"link\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"txtTocRules\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `rule` TEXT NOT NULL, `serialNumber` INTEGER NOT NULL, `enable` INTEGER NOT NULL, PRIMARY KEY(`id`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"rule\",\n            \"columnName\": \"rule\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"serialNumber\",\n            \"columnName\": \"serialNumber\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enable\",\n            \"columnName\": \"enable\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"id\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"readRecord\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`androidId` TEXT NOT NULL, `bookName` TEXT NOT NULL, `readTime` INTEGER NOT NULL, PRIMARY KEY(`androidId`, `bookName`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"androidId\",\n            \"columnName\": \"androidId\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookName\",\n            \"columnName\": \"bookName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"readTime\",\n            \"columnName\": \"readTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"androidId\",\n            \"bookName\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"httpTTS\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `url` TEXT NOT NULL, PRIMARY KEY(`id`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"id\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      }\n    ],\n    \"views\": [],\n    \"setupQueries\": [\n      \"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)\",\n      \"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '874aa30f88921306741b488dbad38536')\"\n    ]\n  }\n}"
  },
  {
    "path": "app/schemas/io.legado.app.data.AppDatabase/24.json",
    "content": "{\n  \"formatVersion\": 1,\n  \"database\": {\n    \"version\": 24,\n    \"identityHash\": \"55416d5a8a8530659ae3e7f948c0058b\",\n    \"entities\": [\n      {\n        \"tableName\": \"books\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookUrl` TEXT NOT NULL, `tocUrl` TEXT NOT NULL, `origin` TEXT NOT NULL, `originName` TEXT NOT NULL, `name` TEXT NOT NULL, `author` TEXT NOT NULL, `kind` TEXT, `customTag` TEXT, `coverUrl` TEXT, `customCoverUrl` TEXT, `intro` TEXT, `customIntro` TEXT, `charset` TEXT, `type` INTEGER NOT NULL, `group` INTEGER NOT NULL, `latestChapterTitle` TEXT, `latestChapterTime` INTEGER NOT NULL, `lastCheckTime` INTEGER NOT NULL, `lastCheckCount` INTEGER NOT NULL, `totalChapterNum` INTEGER NOT NULL, `durChapterTitle` TEXT, `durChapterIndex` INTEGER NOT NULL, `durChapterPos` INTEGER NOT NULL, `durChapterTime` INTEGER NOT NULL, `wordCount` TEXT, `canUpdate` INTEGER NOT NULL, `order` INTEGER NOT NULL, `originOrder` INTEGER NOT NULL, `variable` TEXT, `readConfig` TEXT, PRIMARY KEY(`bookUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"tocUrl\",\n            \"columnName\": \"tocUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"originName\",\n            \"columnName\": \"originName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"author\",\n            \"columnName\": \"author\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"kind\",\n            \"columnName\": \"kind\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customTag\",\n            \"columnName\": \"customTag\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"coverUrl\",\n            \"columnName\": \"coverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customCoverUrl\",\n            \"columnName\": \"customCoverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"intro\",\n            \"columnName\": \"intro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customIntro\",\n            \"columnName\": \"customIntro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"charset\",\n            \"columnName\": \"charset\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"group\",\n            \"columnName\": \"group\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"latestChapterTitle\",\n            \"columnName\": \"latestChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"latestChapterTime\",\n            \"columnName\": \"latestChapterTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"lastCheckTime\",\n            \"columnName\": \"lastCheckTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"lastCheckCount\",\n            \"columnName\": \"lastCheckCount\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"totalChapterNum\",\n            \"columnName\": \"totalChapterNum\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"durChapterTitle\",\n            \"columnName\": \"durChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"durChapterIndex\",\n            \"columnName\": \"durChapterIndex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"durChapterPos\",\n            \"columnName\": \"durChapterPos\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"durChapterTime\",\n            \"columnName\": \"durChapterTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"wordCount\",\n            \"columnName\": \"wordCount\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"canUpdate\",\n            \"columnName\": \"canUpdate\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"originOrder\",\n            \"columnName\": \"originOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"readConfig\",\n            \"columnName\": \"readConfig\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"bookUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_books_name_author\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"name\",\n              \"author\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_books_name_author` ON `${TABLE_NAME}` (`name`, `author`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"book_groups\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`groupId` INTEGER NOT NULL, `groupName` TEXT NOT NULL, `order` INTEGER NOT NULL, `show` INTEGER NOT NULL, PRIMARY KEY(`groupId`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"groupId\",\n            \"columnName\": \"groupId\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"groupName\",\n            \"columnName\": \"groupName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"show\",\n            \"columnName\": \"show\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"groupId\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"book_sources\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookSourceName` TEXT NOT NULL, `bookSourceGroup` TEXT, `bookSourceUrl` TEXT NOT NULL, `bookSourceType` INTEGER NOT NULL, `bookUrlPattern` TEXT, `customOrder` INTEGER NOT NULL, `enabled` INTEGER NOT NULL, `enabledExplore` INTEGER NOT NULL, `header` TEXT, `loginUrl` TEXT, `bookSourceComment` TEXT, `lastUpdateTime` INTEGER NOT NULL, `weight` INTEGER NOT NULL, `exploreUrl` TEXT, `ruleExplore` TEXT, `searchUrl` TEXT, `ruleSearch` TEXT, `ruleBookInfo` TEXT, `ruleToc` TEXT, `ruleContent` TEXT, PRIMARY KEY(`bookSourceUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookSourceName\",\n            \"columnName\": \"bookSourceName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookSourceGroup\",\n            \"columnName\": \"bookSourceGroup\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"bookSourceUrl\",\n            \"columnName\": \"bookSourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookSourceType\",\n            \"columnName\": \"bookSourceType\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookUrlPattern\",\n            \"columnName\": \"bookUrlPattern\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enabled\",\n            \"columnName\": \"enabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enabledExplore\",\n            \"columnName\": \"enabledExplore\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"header\",\n            \"columnName\": \"header\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUrl\",\n            \"columnName\": \"loginUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"bookSourceComment\",\n            \"columnName\": \"bookSourceComment\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"lastUpdateTime\",\n            \"columnName\": \"lastUpdateTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"weight\",\n            \"columnName\": \"weight\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"exploreUrl\",\n            \"columnName\": \"exploreUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleExplore\",\n            \"columnName\": \"ruleExplore\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"searchUrl\",\n            \"columnName\": \"searchUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleSearch\",\n            \"columnName\": \"ruleSearch\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleBookInfo\",\n            \"columnName\": \"ruleBookInfo\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleToc\",\n            \"columnName\": \"ruleToc\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleContent\",\n            \"columnName\": \"ruleContent\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"bookSourceUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_book_sources_bookSourceUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"bookSourceUrl\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_book_sources_bookSourceUrl` ON `${TABLE_NAME}` (`bookSourceUrl`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"chapters\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`url` TEXT NOT NULL, `title` TEXT NOT NULL, `baseUrl` TEXT NOT NULL, `bookUrl` TEXT NOT NULL, `index` INTEGER NOT NULL, `resourceUrl` TEXT, `tag` TEXT, `start` INTEGER, `end` INTEGER, `variable` TEXT, PRIMARY KEY(`url`, `bookUrl`), FOREIGN KEY(`bookUrl`) REFERENCES `books`(`bookUrl`) ON UPDATE NO ACTION ON DELETE CASCADE )\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"baseUrl\",\n            \"columnName\": \"baseUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"index\",\n            \"columnName\": \"index\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"resourceUrl\",\n            \"columnName\": \"resourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"tag\",\n            \"columnName\": \"tag\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"start\",\n            \"columnName\": \"start\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"end\",\n            \"columnName\": \"end\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"url\",\n            \"bookUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_chapters_bookUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"bookUrl\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_chapters_bookUrl` ON `${TABLE_NAME}` (`bookUrl`)\"\n          },\n          {\n            \"name\": \"index_chapters_bookUrl_index\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"bookUrl\",\n              \"index\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_chapters_bookUrl_index` ON `${TABLE_NAME}` (`bookUrl`, `index`)\"\n          }\n        ],\n        \"foreignKeys\": [\n          {\n            \"table\": \"books\",\n            \"onDelete\": \"CASCADE\",\n            \"onUpdate\": \"NO ACTION\",\n            \"columns\": [\n              \"bookUrl\"\n            ],\n            \"referencedColumns\": [\n              \"bookUrl\"\n            ]\n          }\n        ]\n      },\n      {\n        \"tableName\": \"replace_rules\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL, `group` TEXT, `pattern` TEXT NOT NULL, `replacement` TEXT NOT NULL, `scope` TEXT, `isEnabled` INTEGER NOT NULL, `isRegex` INTEGER NOT NULL, `sortOrder` INTEGER NOT NULL)\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"group\",\n            \"columnName\": \"group\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"pattern\",\n            \"columnName\": \"pattern\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"replacement\",\n            \"columnName\": \"replacement\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"scope\",\n            \"columnName\": \"scope\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"isEnabled\",\n            \"columnName\": \"isEnabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"isRegex\",\n            \"columnName\": \"isRegex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"sortOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"id\"\n          ],\n          \"autoGenerate\": true\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_replace_rules_id\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"id\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_replace_rules_id` ON `${TABLE_NAME}` (`id`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"searchBooks\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookUrl` TEXT NOT NULL, `origin` TEXT NOT NULL, `originName` TEXT NOT NULL, `type` INTEGER NOT NULL, `name` TEXT NOT NULL, `author` TEXT NOT NULL, `kind` TEXT, `coverUrl` TEXT, `intro` TEXT, `wordCount` TEXT, `latestChapterTitle` TEXT, `tocUrl` TEXT NOT NULL, `time` INTEGER NOT NULL, `variable` TEXT, `originOrder` INTEGER NOT NULL, PRIMARY KEY(`bookUrl`), FOREIGN KEY(`origin`) REFERENCES `book_sources`(`bookSourceUrl`) ON UPDATE NO ACTION ON DELETE CASCADE )\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"originName\",\n            \"columnName\": \"originName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"author\",\n            \"columnName\": \"author\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"kind\",\n            \"columnName\": \"kind\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"coverUrl\",\n            \"columnName\": \"coverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"intro\",\n            \"columnName\": \"intro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"wordCount\",\n            \"columnName\": \"wordCount\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"latestChapterTitle\",\n            \"columnName\": \"latestChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"tocUrl\",\n            \"columnName\": \"tocUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"time\",\n            \"columnName\": \"time\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"originOrder\",\n            \"columnName\": \"originOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"bookUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_searchBooks_bookUrl\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"bookUrl\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_searchBooks_bookUrl` ON `${TABLE_NAME}` (`bookUrl`)\"\n          },\n          {\n            \"name\": \"index_searchBooks_origin\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"origin\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_searchBooks_origin` ON `${TABLE_NAME}` (`origin`)\"\n          }\n        ],\n        \"foreignKeys\": [\n          {\n            \"table\": \"book_sources\",\n            \"onDelete\": \"CASCADE\",\n            \"onUpdate\": \"NO ACTION\",\n            \"columns\": [\n              \"origin\"\n            ],\n            \"referencedColumns\": [\n              \"bookSourceUrl\"\n            ]\n          }\n        ]\n      },\n      {\n        \"tableName\": \"search_keywords\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`word` TEXT NOT NULL, `usage` INTEGER NOT NULL, `lastUseTime` INTEGER NOT NULL, PRIMARY KEY(`word`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"word\",\n            \"columnName\": \"word\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"usage\",\n            \"columnName\": \"usage\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"lastUseTime\",\n            \"columnName\": \"lastUseTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"word\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_search_keywords_word\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"word\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_search_keywords_word` ON `${TABLE_NAME}` (`word`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"cookies\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`url` TEXT NOT NULL, `cookie` TEXT NOT NULL, PRIMARY KEY(`url`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"cookie\",\n            \"columnName\": \"cookie\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"url\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_cookies_url\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"url\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_cookies_url` ON `${TABLE_NAME}` (`url`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssSources\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`sourceUrl` TEXT NOT NULL, `sourceName` TEXT NOT NULL, `sourceIcon` TEXT NOT NULL, `sourceGroup` TEXT, `enabled` INTEGER NOT NULL, `sortUrl` TEXT, `articleStyle` INTEGER NOT NULL, `ruleArticles` TEXT, `ruleNextPage` TEXT, `ruleTitle` TEXT, `rulePubDate` TEXT, `ruleDescription` TEXT, `ruleImage` TEXT, `ruleLink` TEXT, `ruleContent` TEXT, `style` TEXT, `header` TEXT, `enableJs` INTEGER NOT NULL, `loadWithBaseUrl` INTEGER NOT NULL, `customOrder` INTEGER NOT NULL, PRIMARY KEY(`sourceUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"sourceUrl\",\n            \"columnName\": \"sourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceName\",\n            \"columnName\": \"sourceName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceIcon\",\n            \"columnName\": \"sourceIcon\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceGroup\",\n            \"columnName\": \"sourceGroup\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enabled\",\n            \"columnName\": \"enabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sortUrl\",\n            \"columnName\": \"sortUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"articleStyle\",\n            \"columnName\": \"articleStyle\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"ruleArticles\",\n            \"columnName\": \"ruleArticles\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleNextPage\",\n            \"columnName\": \"ruleNextPage\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleTitle\",\n            \"columnName\": \"ruleTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"rulePubDate\",\n            \"columnName\": \"rulePubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleDescription\",\n            \"columnName\": \"ruleDescription\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleImage\",\n            \"columnName\": \"ruleImage\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleLink\",\n            \"columnName\": \"ruleLink\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleContent\",\n            \"columnName\": \"ruleContent\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"style\",\n            \"columnName\": \"style\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"header\",\n            \"columnName\": \"header\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enableJs\",\n            \"columnName\": \"enableJs\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"loadWithBaseUrl\",\n            \"columnName\": \"loadWithBaseUrl\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"sourceUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_rssSources_sourceUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"sourceUrl\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_rssSources_sourceUrl` ON `${TABLE_NAME}` (`sourceUrl`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"bookmarks\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`time` INTEGER NOT NULL, `bookUrl` TEXT NOT NULL, `bookName` TEXT NOT NULL, `bookAuthor` TEXT NOT NULL, `chapterIndex` INTEGER NOT NULL, `pageIndex` INTEGER NOT NULL, `chapterName` TEXT NOT NULL, `content` TEXT NOT NULL, PRIMARY KEY(`time`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"time\",\n            \"columnName\": \"time\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookName\",\n            \"columnName\": \"bookName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookAuthor\",\n            \"columnName\": \"bookAuthor\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterIndex\",\n            \"columnName\": \"chapterIndex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"pageIndex\",\n            \"columnName\": \"pageIndex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterName\",\n            \"columnName\": \"chapterName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"time\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_bookmarks_time\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"time\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_bookmarks_time` ON `${TABLE_NAME}` (`time`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssArticles\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`origin` TEXT NOT NULL, `sort` TEXT NOT NULL, `title` TEXT NOT NULL, `order` INTEGER NOT NULL, `link` TEXT NOT NULL, `pubDate` TEXT, `description` TEXT, `content` TEXT, `image` TEXT, `read` INTEGER NOT NULL, PRIMARY KEY(`origin`, `link`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sort\",\n            \"columnName\": \"sort\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"link\",\n            \"columnName\": \"link\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"pubDate\",\n            \"columnName\": \"pubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"description\",\n            \"columnName\": \"description\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"image\",\n            \"columnName\": \"image\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"read\",\n            \"columnName\": \"read\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"origin\",\n            \"link\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssReadRecords\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`record` TEXT NOT NULL, `read` INTEGER NOT NULL, PRIMARY KEY(`record`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"record\",\n            \"columnName\": \"record\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"read\",\n            \"columnName\": \"read\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"record\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssStars\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`origin` TEXT NOT NULL, `sort` TEXT NOT NULL, `title` TEXT NOT NULL, `starTime` INTEGER NOT NULL, `link` TEXT NOT NULL, `pubDate` TEXT, `description` TEXT, `content` TEXT, `image` TEXT, PRIMARY KEY(`origin`, `link`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sort\",\n            \"columnName\": \"sort\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"starTime\",\n            \"columnName\": \"starTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"link\",\n            \"columnName\": \"link\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"pubDate\",\n            \"columnName\": \"pubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"description\",\n            \"columnName\": \"description\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"image\",\n            \"columnName\": \"image\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"origin\",\n            \"link\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"txtTocRules\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `rule` TEXT NOT NULL, `serialNumber` INTEGER NOT NULL, `enable` INTEGER NOT NULL, PRIMARY KEY(`id`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"rule\",\n            \"columnName\": \"rule\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"serialNumber\",\n            \"columnName\": \"serialNumber\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enable\",\n            \"columnName\": \"enable\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"id\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"readRecord\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`androidId` TEXT NOT NULL, `bookName` TEXT NOT NULL, `readTime` INTEGER NOT NULL, PRIMARY KEY(`androidId`, `bookName`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"androidId\",\n            \"columnName\": \"androidId\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookName\",\n            \"columnName\": \"bookName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"readTime\",\n            \"columnName\": \"readTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"androidId\",\n            \"bookName\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"httpTTS\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `url` TEXT NOT NULL, PRIMARY KEY(`id`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"id\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"caches\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`key` TEXT NOT NULL, `value` TEXT, `deadline` INTEGER NOT NULL, PRIMARY KEY(`key`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"key\",\n            \"columnName\": \"key\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"value\",\n            \"columnName\": \"value\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"deadline\",\n            \"columnName\": \"deadline\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"key\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_caches_key\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"key\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_caches_key` ON `${TABLE_NAME}` (`key`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      }\n    ],\n    \"views\": [],\n    \"setupQueries\": [\n      \"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)\",\n      \"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '55416d5a8a8530659ae3e7f948c0058b')\"\n    ]\n  }\n}"
  },
  {
    "path": "app/schemas/io.legado.app.data.AppDatabase/25.json",
    "content": "{\n  \"formatVersion\": 1,\n  \"database\": {\n    \"version\": 25,\n    \"identityHash\": \"469ee9861faf7f562d7c60bc15a4a58b\",\n    \"entities\": [\n      {\n        \"tableName\": \"books\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookUrl` TEXT NOT NULL, `tocUrl` TEXT NOT NULL, `origin` TEXT NOT NULL, `originName` TEXT NOT NULL, `name` TEXT NOT NULL, `author` TEXT NOT NULL, `kind` TEXT, `customTag` TEXT, `coverUrl` TEXT, `customCoverUrl` TEXT, `intro` TEXT, `customIntro` TEXT, `charset` TEXT, `type` INTEGER NOT NULL, `group` INTEGER NOT NULL, `latestChapterTitle` TEXT, `latestChapterTime` INTEGER NOT NULL, `lastCheckTime` INTEGER NOT NULL, `lastCheckCount` INTEGER NOT NULL, `totalChapterNum` INTEGER NOT NULL, `durChapterTitle` TEXT, `durChapterIndex` INTEGER NOT NULL, `durChapterPos` INTEGER NOT NULL, `durChapterTime` INTEGER NOT NULL, `wordCount` TEXT, `canUpdate` INTEGER NOT NULL, `order` INTEGER NOT NULL, `originOrder` INTEGER NOT NULL, `variable` TEXT, `readConfig` TEXT, PRIMARY KEY(`bookUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"tocUrl\",\n            \"columnName\": \"tocUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"originName\",\n            \"columnName\": \"originName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"author\",\n            \"columnName\": \"author\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"kind\",\n            \"columnName\": \"kind\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customTag\",\n            \"columnName\": \"customTag\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"coverUrl\",\n            \"columnName\": \"coverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customCoverUrl\",\n            \"columnName\": \"customCoverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"intro\",\n            \"columnName\": \"intro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customIntro\",\n            \"columnName\": \"customIntro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"charset\",\n            \"columnName\": \"charset\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"group\",\n            \"columnName\": \"group\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"latestChapterTitle\",\n            \"columnName\": \"latestChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"latestChapterTime\",\n            \"columnName\": \"latestChapterTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"lastCheckTime\",\n            \"columnName\": \"lastCheckTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"lastCheckCount\",\n            \"columnName\": \"lastCheckCount\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"totalChapterNum\",\n            \"columnName\": \"totalChapterNum\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"durChapterTitle\",\n            \"columnName\": \"durChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"durChapterIndex\",\n            \"columnName\": \"durChapterIndex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"durChapterPos\",\n            \"columnName\": \"durChapterPos\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"durChapterTime\",\n            \"columnName\": \"durChapterTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"wordCount\",\n            \"columnName\": \"wordCount\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"canUpdate\",\n            \"columnName\": \"canUpdate\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"originOrder\",\n            \"columnName\": \"originOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"readConfig\",\n            \"columnName\": \"readConfig\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"bookUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_books_name_author\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"name\",\n              \"author\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_books_name_author` ON `${TABLE_NAME}` (`name`, `author`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"book_groups\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`groupId` INTEGER NOT NULL, `groupName` TEXT NOT NULL, `order` INTEGER NOT NULL, `show` INTEGER NOT NULL, PRIMARY KEY(`groupId`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"groupId\",\n            \"columnName\": \"groupId\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"groupName\",\n            \"columnName\": \"groupName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"show\",\n            \"columnName\": \"show\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"groupId\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"book_sources\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookSourceName` TEXT NOT NULL, `bookSourceGroup` TEXT, `bookSourceUrl` TEXT NOT NULL, `bookSourceType` INTEGER NOT NULL, `bookUrlPattern` TEXT, `customOrder` INTEGER NOT NULL, `enabled` INTEGER NOT NULL, `enabledExplore` INTEGER NOT NULL, `header` TEXT, `loginUrl` TEXT, `bookSourceComment` TEXT, `lastUpdateTime` INTEGER NOT NULL, `weight` INTEGER NOT NULL, `exploreUrl` TEXT, `ruleExplore` TEXT, `searchUrl` TEXT, `ruleSearch` TEXT, `ruleBookInfo` TEXT, `ruleToc` TEXT, `ruleContent` TEXT, PRIMARY KEY(`bookSourceUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookSourceName\",\n            \"columnName\": \"bookSourceName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookSourceGroup\",\n            \"columnName\": \"bookSourceGroup\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"bookSourceUrl\",\n            \"columnName\": \"bookSourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookSourceType\",\n            \"columnName\": \"bookSourceType\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookUrlPattern\",\n            \"columnName\": \"bookUrlPattern\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enabled\",\n            \"columnName\": \"enabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enabledExplore\",\n            \"columnName\": \"enabledExplore\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"header\",\n            \"columnName\": \"header\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUrl\",\n            \"columnName\": \"loginUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"bookSourceComment\",\n            \"columnName\": \"bookSourceComment\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"lastUpdateTime\",\n            \"columnName\": \"lastUpdateTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"weight\",\n            \"columnName\": \"weight\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"exploreUrl\",\n            \"columnName\": \"exploreUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleExplore\",\n            \"columnName\": \"ruleExplore\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"searchUrl\",\n            \"columnName\": \"searchUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleSearch\",\n            \"columnName\": \"ruleSearch\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleBookInfo\",\n            \"columnName\": \"ruleBookInfo\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleToc\",\n            \"columnName\": \"ruleToc\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleContent\",\n            \"columnName\": \"ruleContent\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"bookSourceUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_book_sources_bookSourceUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"bookSourceUrl\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_book_sources_bookSourceUrl` ON `${TABLE_NAME}` (`bookSourceUrl`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"chapters\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`url` TEXT NOT NULL, `title` TEXT NOT NULL, `baseUrl` TEXT NOT NULL, `bookUrl` TEXT NOT NULL, `index` INTEGER NOT NULL, `resourceUrl` TEXT, `tag` TEXT, `start` INTEGER, `end` INTEGER, `variable` TEXT, PRIMARY KEY(`url`, `bookUrl`), FOREIGN KEY(`bookUrl`) REFERENCES `books`(`bookUrl`) ON UPDATE NO ACTION ON DELETE CASCADE )\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"baseUrl\",\n            \"columnName\": \"baseUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"index\",\n            \"columnName\": \"index\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"resourceUrl\",\n            \"columnName\": \"resourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"tag\",\n            \"columnName\": \"tag\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"start\",\n            \"columnName\": \"start\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"end\",\n            \"columnName\": \"end\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"url\",\n            \"bookUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_chapters_bookUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"bookUrl\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_chapters_bookUrl` ON `${TABLE_NAME}` (`bookUrl`)\"\n          },\n          {\n            \"name\": \"index_chapters_bookUrl_index\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"bookUrl\",\n              \"index\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_chapters_bookUrl_index` ON `${TABLE_NAME}` (`bookUrl`, `index`)\"\n          }\n        ],\n        \"foreignKeys\": [\n          {\n            \"table\": \"books\",\n            \"onDelete\": \"CASCADE\",\n            \"onUpdate\": \"NO ACTION\",\n            \"columns\": [\n              \"bookUrl\"\n            ],\n            \"referencedColumns\": [\n              \"bookUrl\"\n            ]\n          }\n        ]\n      },\n      {\n        \"tableName\": \"replace_rules\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL, `group` TEXT, `pattern` TEXT NOT NULL, `replacement` TEXT NOT NULL, `scope` TEXT, `isEnabled` INTEGER NOT NULL, `isRegex` INTEGER NOT NULL, `sortOrder` INTEGER NOT NULL)\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"group\",\n            \"columnName\": \"group\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"pattern\",\n            \"columnName\": \"pattern\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"replacement\",\n            \"columnName\": \"replacement\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"scope\",\n            \"columnName\": \"scope\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"isEnabled\",\n            \"columnName\": \"isEnabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"isRegex\",\n            \"columnName\": \"isRegex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"sortOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"id\"\n          ],\n          \"autoGenerate\": true\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_replace_rules_id\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"id\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_replace_rules_id` ON `${TABLE_NAME}` (`id`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"searchBooks\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookUrl` TEXT NOT NULL, `origin` TEXT NOT NULL, `originName` TEXT NOT NULL, `type` INTEGER NOT NULL, `name` TEXT NOT NULL, `author` TEXT NOT NULL, `kind` TEXT, `coverUrl` TEXT, `intro` TEXT, `wordCount` TEXT, `latestChapterTitle` TEXT, `tocUrl` TEXT NOT NULL, `time` INTEGER NOT NULL, `variable` TEXT, `originOrder` INTEGER NOT NULL, PRIMARY KEY(`bookUrl`), FOREIGN KEY(`origin`) REFERENCES `book_sources`(`bookSourceUrl`) ON UPDATE NO ACTION ON DELETE CASCADE )\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"originName\",\n            \"columnName\": \"originName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"author\",\n            \"columnName\": \"author\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"kind\",\n            \"columnName\": \"kind\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"coverUrl\",\n            \"columnName\": \"coverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"intro\",\n            \"columnName\": \"intro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"wordCount\",\n            \"columnName\": \"wordCount\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"latestChapterTitle\",\n            \"columnName\": \"latestChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"tocUrl\",\n            \"columnName\": \"tocUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"time\",\n            \"columnName\": \"time\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"originOrder\",\n            \"columnName\": \"originOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"bookUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_searchBooks_bookUrl\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"bookUrl\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_searchBooks_bookUrl` ON `${TABLE_NAME}` (`bookUrl`)\"\n          },\n          {\n            \"name\": \"index_searchBooks_origin\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"origin\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_searchBooks_origin` ON `${TABLE_NAME}` (`origin`)\"\n          }\n        ],\n        \"foreignKeys\": [\n          {\n            \"table\": \"book_sources\",\n            \"onDelete\": \"CASCADE\",\n            \"onUpdate\": \"NO ACTION\",\n            \"columns\": [\n              \"origin\"\n            ],\n            \"referencedColumns\": [\n              \"bookSourceUrl\"\n            ]\n          }\n        ]\n      },\n      {\n        \"tableName\": \"search_keywords\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`word` TEXT NOT NULL, `usage` INTEGER NOT NULL, `lastUseTime` INTEGER NOT NULL, PRIMARY KEY(`word`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"word\",\n            \"columnName\": \"word\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"usage\",\n            \"columnName\": \"usage\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"lastUseTime\",\n            \"columnName\": \"lastUseTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"word\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_search_keywords_word\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"word\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_search_keywords_word` ON `${TABLE_NAME}` (`word`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"cookies\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`url` TEXT NOT NULL, `cookie` TEXT NOT NULL, PRIMARY KEY(`url`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"cookie\",\n            \"columnName\": \"cookie\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"url\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_cookies_url\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"url\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_cookies_url` ON `${TABLE_NAME}` (`url`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssSources\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`sourceUrl` TEXT NOT NULL, `sourceName` TEXT NOT NULL, `sourceIcon` TEXT NOT NULL, `sourceGroup` TEXT, `enabled` INTEGER NOT NULL, `sortUrl` TEXT, `articleStyle` INTEGER NOT NULL, `ruleArticles` TEXT, `ruleNextPage` TEXT, `ruleTitle` TEXT, `rulePubDate` TEXT, `ruleDescription` TEXT, `ruleImage` TEXT, `ruleLink` TEXT, `ruleContent` TEXT, `style` TEXT, `header` TEXT, `enableJs` INTEGER NOT NULL, `loadWithBaseUrl` INTEGER NOT NULL, `customOrder` INTEGER NOT NULL, PRIMARY KEY(`sourceUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"sourceUrl\",\n            \"columnName\": \"sourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceName\",\n            \"columnName\": \"sourceName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceIcon\",\n            \"columnName\": \"sourceIcon\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceGroup\",\n            \"columnName\": \"sourceGroup\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enabled\",\n            \"columnName\": \"enabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sortUrl\",\n            \"columnName\": \"sortUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"articleStyle\",\n            \"columnName\": \"articleStyle\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"ruleArticles\",\n            \"columnName\": \"ruleArticles\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleNextPage\",\n            \"columnName\": \"ruleNextPage\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleTitle\",\n            \"columnName\": \"ruleTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"rulePubDate\",\n            \"columnName\": \"rulePubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleDescription\",\n            \"columnName\": \"ruleDescription\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleImage\",\n            \"columnName\": \"ruleImage\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleLink\",\n            \"columnName\": \"ruleLink\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleContent\",\n            \"columnName\": \"ruleContent\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"style\",\n            \"columnName\": \"style\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"header\",\n            \"columnName\": \"header\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enableJs\",\n            \"columnName\": \"enableJs\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"loadWithBaseUrl\",\n            \"columnName\": \"loadWithBaseUrl\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"sourceUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_rssSources_sourceUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"sourceUrl\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_rssSources_sourceUrl` ON `${TABLE_NAME}` (`sourceUrl`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"bookmarks\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`time` INTEGER NOT NULL, `bookUrl` TEXT NOT NULL, `bookName` TEXT NOT NULL, `bookAuthor` TEXT NOT NULL, `chapterIndex` INTEGER NOT NULL, `pageIndex` INTEGER NOT NULL, `chapterName` TEXT NOT NULL, `content` TEXT NOT NULL, PRIMARY KEY(`time`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"time\",\n            \"columnName\": \"time\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookName\",\n            \"columnName\": \"bookName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookAuthor\",\n            \"columnName\": \"bookAuthor\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterIndex\",\n            \"columnName\": \"chapterIndex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"pageIndex\",\n            \"columnName\": \"pageIndex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterName\",\n            \"columnName\": \"chapterName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"time\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_bookmarks_time\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"time\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_bookmarks_time` ON `${TABLE_NAME}` (`time`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssArticles\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`origin` TEXT NOT NULL, `sort` TEXT NOT NULL, `title` TEXT NOT NULL, `order` INTEGER NOT NULL, `link` TEXT NOT NULL, `pubDate` TEXT, `description` TEXT, `content` TEXT, `image` TEXT, `read` INTEGER NOT NULL, PRIMARY KEY(`origin`, `link`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sort\",\n            \"columnName\": \"sort\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"link\",\n            \"columnName\": \"link\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"pubDate\",\n            \"columnName\": \"pubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"description\",\n            \"columnName\": \"description\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"image\",\n            \"columnName\": \"image\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"read\",\n            \"columnName\": \"read\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"origin\",\n            \"link\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssReadRecords\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`record` TEXT NOT NULL, `read` INTEGER NOT NULL, PRIMARY KEY(`record`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"record\",\n            \"columnName\": \"record\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"read\",\n            \"columnName\": \"read\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"record\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssStars\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`origin` TEXT NOT NULL, `sort` TEXT NOT NULL, `title` TEXT NOT NULL, `starTime` INTEGER NOT NULL, `link` TEXT NOT NULL, `pubDate` TEXT, `description` TEXT, `content` TEXT, `image` TEXT, PRIMARY KEY(`origin`, `link`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sort\",\n            \"columnName\": \"sort\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"starTime\",\n            \"columnName\": \"starTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"link\",\n            \"columnName\": \"link\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"pubDate\",\n            \"columnName\": \"pubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"description\",\n            \"columnName\": \"description\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"image\",\n            \"columnName\": \"image\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"origin\",\n            \"link\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"txtTocRules\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `rule` TEXT NOT NULL, `serialNumber` INTEGER NOT NULL, `enable` INTEGER NOT NULL, PRIMARY KEY(`id`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"rule\",\n            \"columnName\": \"rule\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"serialNumber\",\n            \"columnName\": \"serialNumber\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enable\",\n            \"columnName\": \"enable\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"id\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"readRecord\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`androidId` TEXT NOT NULL, `bookName` TEXT NOT NULL, `readTime` INTEGER NOT NULL, PRIMARY KEY(`androidId`, `bookName`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"androidId\",\n            \"columnName\": \"androidId\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookName\",\n            \"columnName\": \"bookName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"readTime\",\n            \"columnName\": \"readTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"androidId\",\n            \"bookName\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"httpTTS\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `url` TEXT NOT NULL, PRIMARY KEY(`id`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"id\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"caches\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`key` TEXT NOT NULL, `value` TEXT, `deadline` INTEGER NOT NULL, PRIMARY KEY(`key`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"key\",\n            \"columnName\": \"key\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"value\",\n            \"columnName\": \"value\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"deadline\",\n            \"columnName\": \"deadline\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"key\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_caches_key\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"key\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_caches_key` ON `${TABLE_NAME}` (`key`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"sourceSubs\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `url` TEXT NOT NULL, `type` INTEGER NOT NULL, `customOrder` INTEGER NOT NULL, PRIMARY KEY(`id`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"id\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      }\n    ],\n    \"views\": [],\n    \"setupQueries\": [\n      \"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)\",\n      \"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '469ee9861faf7f562d7c60bc15a4a58b')\"\n    ]\n  }\n}"
  },
  {
    "path": "app/schemas/io.legado.app.data.AppDatabase/26.json",
    "content": "{\n  \"formatVersion\": 1,\n  \"database\": {\n    \"version\": 26,\n    \"identityHash\": \"e20aa63032efb23c9e9c269afd64e7d7\",\n    \"entities\": [\n      {\n        \"tableName\": \"books\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookUrl` TEXT NOT NULL, `tocUrl` TEXT NOT NULL, `origin` TEXT NOT NULL, `originName` TEXT NOT NULL, `name` TEXT NOT NULL, `author` TEXT NOT NULL, `kind` TEXT, `customTag` TEXT, `coverUrl` TEXT, `customCoverUrl` TEXT, `intro` TEXT, `customIntro` TEXT, `charset` TEXT, `type` INTEGER NOT NULL, `group` INTEGER NOT NULL, `latestChapterTitle` TEXT, `latestChapterTime` INTEGER NOT NULL, `lastCheckTime` INTEGER NOT NULL, `lastCheckCount` INTEGER NOT NULL, `totalChapterNum` INTEGER NOT NULL, `durChapterTitle` TEXT, `durChapterIndex` INTEGER NOT NULL, `durChapterPos` INTEGER NOT NULL, `durChapterTime` INTEGER NOT NULL, `wordCount` TEXT, `canUpdate` INTEGER NOT NULL, `order` INTEGER NOT NULL, `originOrder` INTEGER NOT NULL, `variable` TEXT, `readConfig` TEXT, PRIMARY KEY(`bookUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"tocUrl\",\n            \"columnName\": \"tocUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"originName\",\n            \"columnName\": \"originName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"author\",\n            \"columnName\": \"author\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"kind\",\n            \"columnName\": \"kind\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customTag\",\n            \"columnName\": \"customTag\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"coverUrl\",\n            \"columnName\": \"coverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customCoverUrl\",\n            \"columnName\": \"customCoverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"intro\",\n            \"columnName\": \"intro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customIntro\",\n            \"columnName\": \"customIntro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"charset\",\n            \"columnName\": \"charset\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"group\",\n            \"columnName\": \"group\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"latestChapterTitle\",\n            \"columnName\": \"latestChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"latestChapterTime\",\n            \"columnName\": \"latestChapterTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"lastCheckTime\",\n            \"columnName\": \"lastCheckTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"lastCheckCount\",\n            \"columnName\": \"lastCheckCount\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"totalChapterNum\",\n            \"columnName\": \"totalChapterNum\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"durChapterTitle\",\n            \"columnName\": \"durChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"durChapterIndex\",\n            \"columnName\": \"durChapterIndex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"durChapterPos\",\n            \"columnName\": \"durChapterPos\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"durChapterTime\",\n            \"columnName\": \"durChapterTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"wordCount\",\n            \"columnName\": \"wordCount\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"canUpdate\",\n            \"columnName\": \"canUpdate\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"originOrder\",\n            \"columnName\": \"originOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"readConfig\",\n            \"columnName\": \"readConfig\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"bookUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_books_name_author\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"name\",\n              \"author\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_books_name_author` ON `${TABLE_NAME}` (`name`, `author`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"book_groups\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`groupId` INTEGER NOT NULL, `groupName` TEXT NOT NULL, `order` INTEGER NOT NULL, `show` INTEGER NOT NULL, PRIMARY KEY(`groupId`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"groupId\",\n            \"columnName\": \"groupId\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"groupName\",\n            \"columnName\": \"groupName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"show\",\n            \"columnName\": \"show\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"groupId\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"book_sources\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookSourceName` TEXT NOT NULL, `bookSourceGroup` TEXT, `bookSourceUrl` TEXT NOT NULL, `bookSourceType` INTEGER NOT NULL, `bookUrlPattern` TEXT, `customOrder` INTEGER NOT NULL, `enabled` INTEGER NOT NULL, `enabledExplore` INTEGER NOT NULL, `header` TEXT, `loginUrl` TEXT, `bookSourceComment` TEXT, `lastUpdateTime` INTEGER NOT NULL, `weight` INTEGER NOT NULL, `exploreUrl` TEXT, `ruleExplore` TEXT, `searchUrl` TEXT, `ruleSearch` TEXT, `ruleBookInfo` TEXT, `ruleToc` TEXT, `ruleContent` TEXT, PRIMARY KEY(`bookSourceUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookSourceName\",\n            \"columnName\": \"bookSourceName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookSourceGroup\",\n            \"columnName\": \"bookSourceGroup\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"bookSourceUrl\",\n            \"columnName\": \"bookSourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookSourceType\",\n            \"columnName\": \"bookSourceType\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookUrlPattern\",\n            \"columnName\": \"bookUrlPattern\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enabled\",\n            \"columnName\": \"enabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enabledExplore\",\n            \"columnName\": \"enabledExplore\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"header\",\n            \"columnName\": \"header\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUrl\",\n            \"columnName\": \"loginUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"bookSourceComment\",\n            \"columnName\": \"bookSourceComment\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"lastUpdateTime\",\n            \"columnName\": \"lastUpdateTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"weight\",\n            \"columnName\": \"weight\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"exploreUrl\",\n            \"columnName\": \"exploreUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleExplore\",\n            \"columnName\": \"ruleExplore\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"searchUrl\",\n            \"columnName\": \"searchUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleSearch\",\n            \"columnName\": \"ruleSearch\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleBookInfo\",\n            \"columnName\": \"ruleBookInfo\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleToc\",\n            \"columnName\": \"ruleToc\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleContent\",\n            \"columnName\": \"ruleContent\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"bookSourceUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_book_sources_bookSourceUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"bookSourceUrl\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_book_sources_bookSourceUrl` ON `${TABLE_NAME}` (`bookSourceUrl`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"chapters\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`url` TEXT NOT NULL, `title` TEXT NOT NULL, `baseUrl` TEXT NOT NULL, `bookUrl` TEXT NOT NULL, `index` INTEGER NOT NULL, `resourceUrl` TEXT, `tag` TEXT, `start` INTEGER, `end` INTEGER, `variable` TEXT, PRIMARY KEY(`url`, `bookUrl`), FOREIGN KEY(`bookUrl`) REFERENCES `books`(`bookUrl`) ON UPDATE NO ACTION ON DELETE CASCADE )\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"baseUrl\",\n            \"columnName\": \"baseUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"index\",\n            \"columnName\": \"index\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"resourceUrl\",\n            \"columnName\": \"resourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"tag\",\n            \"columnName\": \"tag\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"start\",\n            \"columnName\": \"start\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"end\",\n            \"columnName\": \"end\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"url\",\n            \"bookUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_chapters_bookUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"bookUrl\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_chapters_bookUrl` ON `${TABLE_NAME}` (`bookUrl`)\"\n          },\n          {\n            \"name\": \"index_chapters_bookUrl_index\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"bookUrl\",\n              \"index\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_chapters_bookUrl_index` ON `${TABLE_NAME}` (`bookUrl`, `index`)\"\n          }\n        ],\n        \"foreignKeys\": [\n          {\n            \"table\": \"books\",\n            \"onDelete\": \"CASCADE\",\n            \"onUpdate\": \"NO ACTION\",\n            \"columns\": [\n              \"bookUrl\"\n            ],\n            \"referencedColumns\": [\n              \"bookUrl\"\n            ]\n          }\n        ]\n      },\n      {\n        \"tableName\": \"replace_rules\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL, `group` TEXT, `pattern` TEXT NOT NULL, `replacement` TEXT NOT NULL, `scope` TEXT, `isEnabled` INTEGER NOT NULL, `isRegex` INTEGER NOT NULL, `sortOrder` INTEGER NOT NULL)\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"group\",\n            \"columnName\": \"group\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"pattern\",\n            \"columnName\": \"pattern\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"replacement\",\n            \"columnName\": \"replacement\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"scope\",\n            \"columnName\": \"scope\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"isEnabled\",\n            \"columnName\": \"isEnabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"isRegex\",\n            \"columnName\": \"isRegex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"sortOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"id\"\n          ],\n          \"autoGenerate\": true\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_replace_rules_id\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"id\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_replace_rules_id` ON `${TABLE_NAME}` (`id`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"searchBooks\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookUrl` TEXT NOT NULL, `origin` TEXT NOT NULL, `originName` TEXT NOT NULL, `type` INTEGER NOT NULL, `name` TEXT NOT NULL, `author` TEXT NOT NULL, `kind` TEXT, `coverUrl` TEXT, `intro` TEXT, `wordCount` TEXT, `latestChapterTitle` TEXT, `tocUrl` TEXT NOT NULL, `time` INTEGER NOT NULL, `variable` TEXT, `originOrder` INTEGER NOT NULL, PRIMARY KEY(`bookUrl`), FOREIGN KEY(`origin`) REFERENCES `book_sources`(`bookSourceUrl`) ON UPDATE NO ACTION ON DELETE CASCADE )\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"originName\",\n            \"columnName\": \"originName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"author\",\n            \"columnName\": \"author\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"kind\",\n            \"columnName\": \"kind\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"coverUrl\",\n            \"columnName\": \"coverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"intro\",\n            \"columnName\": \"intro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"wordCount\",\n            \"columnName\": \"wordCount\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"latestChapterTitle\",\n            \"columnName\": \"latestChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"tocUrl\",\n            \"columnName\": \"tocUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"time\",\n            \"columnName\": \"time\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"originOrder\",\n            \"columnName\": \"originOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"bookUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_searchBooks_bookUrl\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"bookUrl\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_searchBooks_bookUrl` ON `${TABLE_NAME}` (`bookUrl`)\"\n          },\n          {\n            \"name\": \"index_searchBooks_origin\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"origin\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_searchBooks_origin` ON `${TABLE_NAME}` (`origin`)\"\n          }\n        ],\n        \"foreignKeys\": [\n          {\n            \"table\": \"book_sources\",\n            \"onDelete\": \"CASCADE\",\n            \"onUpdate\": \"NO ACTION\",\n            \"columns\": [\n              \"origin\"\n            ],\n            \"referencedColumns\": [\n              \"bookSourceUrl\"\n            ]\n          }\n        ]\n      },\n      {\n        \"tableName\": \"search_keywords\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`word` TEXT NOT NULL, `usage` INTEGER NOT NULL, `lastUseTime` INTEGER NOT NULL, PRIMARY KEY(`word`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"word\",\n            \"columnName\": \"word\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"usage\",\n            \"columnName\": \"usage\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"lastUseTime\",\n            \"columnName\": \"lastUseTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"word\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_search_keywords_word\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"word\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_search_keywords_word` ON `${TABLE_NAME}` (`word`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"cookies\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`url` TEXT NOT NULL, `cookie` TEXT NOT NULL, PRIMARY KEY(`url`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"cookie\",\n            \"columnName\": \"cookie\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"url\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_cookies_url\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"url\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_cookies_url` ON `${TABLE_NAME}` (`url`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssSources\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`sourceUrl` TEXT NOT NULL, `sourceName` TEXT NOT NULL, `sourceIcon` TEXT NOT NULL, `sourceGroup` TEXT, `enabled` INTEGER NOT NULL, `sortUrl` TEXT, `singleUrl` INTEGER NOT NULL, `articleStyle` INTEGER NOT NULL, `ruleArticles` TEXT, `ruleNextPage` TEXT, `ruleTitle` TEXT, `rulePubDate` TEXT, `ruleDescription` TEXT, `ruleImage` TEXT, `ruleLink` TEXT, `ruleContent` TEXT, `style` TEXT, `header` TEXT, `enableJs` INTEGER NOT NULL, `loadWithBaseUrl` INTEGER NOT NULL, `customOrder` INTEGER NOT NULL, PRIMARY KEY(`sourceUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"sourceUrl\",\n            \"columnName\": \"sourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceName\",\n            \"columnName\": \"sourceName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceIcon\",\n            \"columnName\": \"sourceIcon\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceGroup\",\n            \"columnName\": \"sourceGroup\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enabled\",\n            \"columnName\": \"enabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sortUrl\",\n            \"columnName\": \"sortUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"singleUrl\",\n            \"columnName\": \"singleUrl\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"articleStyle\",\n            \"columnName\": \"articleStyle\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"ruleArticles\",\n            \"columnName\": \"ruleArticles\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleNextPage\",\n            \"columnName\": \"ruleNextPage\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleTitle\",\n            \"columnName\": \"ruleTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"rulePubDate\",\n            \"columnName\": \"rulePubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleDescription\",\n            \"columnName\": \"ruleDescription\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleImage\",\n            \"columnName\": \"ruleImage\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleLink\",\n            \"columnName\": \"ruleLink\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleContent\",\n            \"columnName\": \"ruleContent\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"style\",\n            \"columnName\": \"style\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"header\",\n            \"columnName\": \"header\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enableJs\",\n            \"columnName\": \"enableJs\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"loadWithBaseUrl\",\n            \"columnName\": \"loadWithBaseUrl\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"sourceUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_rssSources_sourceUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"sourceUrl\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_rssSources_sourceUrl` ON `${TABLE_NAME}` (`sourceUrl`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"bookmarks\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`time` INTEGER NOT NULL, `bookUrl` TEXT NOT NULL, `bookName` TEXT NOT NULL, `bookAuthor` TEXT NOT NULL, `chapterIndex` INTEGER NOT NULL, `chapterPos` INTEGER NOT NULL, `chapterName` TEXT NOT NULL, `bookText` TEXT NOT NULL, `content` TEXT NOT NULL, PRIMARY KEY(`time`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"time\",\n            \"columnName\": \"time\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookName\",\n            \"columnName\": \"bookName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookAuthor\",\n            \"columnName\": \"bookAuthor\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterIndex\",\n            \"columnName\": \"chapterIndex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterPos\",\n            \"columnName\": \"chapterPos\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterName\",\n            \"columnName\": \"chapterName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookText\",\n            \"columnName\": \"bookText\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"time\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_bookmarks_time\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"time\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_bookmarks_time` ON `${TABLE_NAME}` (`time`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssArticles\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`origin` TEXT NOT NULL, `sort` TEXT NOT NULL, `title` TEXT NOT NULL, `order` INTEGER NOT NULL, `link` TEXT NOT NULL, `pubDate` TEXT, `description` TEXT, `content` TEXT, `image` TEXT, `read` INTEGER NOT NULL, PRIMARY KEY(`origin`, `link`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sort\",\n            \"columnName\": \"sort\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"link\",\n            \"columnName\": \"link\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"pubDate\",\n            \"columnName\": \"pubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"description\",\n            \"columnName\": \"description\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"image\",\n            \"columnName\": \"image\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"read\",\n            \"columnName\": \"read\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"origin\",\n            \"link\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssReadRecords\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`record` TEXT NOT NULL, `read` INTEGER NOT NULL, PRIMARY KEY(`record`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"record\",\n            \"columnName\": \"record\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"read\",\n            \"columnName\": \"read\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"record\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssStars\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`origin` TEXT NOT NULL, `sort` TEXT NOT NULL, `title` TEXT NOT NULL, `starTime` INTEGER NOT NULL, `link` TEXT NOT NULL, `pubDate` TEXT, `description` TEXT, `content` TEXT, `image` TEXT, PRIMARY KEY(`origin`, `link`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sort\",\n            \"columnName\": \"sort\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"starTime\",\n            \"columnName\": \"starTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"link\",\n            \"columnName\": \"link\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"pubDate\",\n            \"columnName\": \"pubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"description\",\n            \"columnName\": \"description\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"image\",\n            \"columnName\": \"image\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"origin\",\n            \"link\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"txtTocRules\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `rule` TEXT NOT NULL, `serialNumber` INTEGER NOT NULL, `enable` INTEGER NOT NULL, PRIMARY KEY(`id`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"rule\",\n            \"columnName\": \"rule\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"serialNumber\",\n            \"columnName\": \"serialNumber\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enable\",\n            \"columnName\": \"enable\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"id\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"readRecord\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`androidId` TEXT NOT NULL, `bookName` TEXT NOT NULL, `readTime` INTEGER NOT NULL, PRIMARY KEY(`androidId`, `bookName`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"androidId\",\n            \"columnName\": \"androidId\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookName\",\n            \"columnName\": \"bookName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"readTime\",\n            \"columnName\": \"readTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"androidId\",\n            \"bookName\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"httpTTS\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `url` TEXT NOT NULL, PRIMARY KEY(`id`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"id\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"caches\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`key` TEXT NOT NULL, `value` TEXT, `deadline` INTEGER NOT NULL, PRIMARY KEY(`key`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"key\",\n            \"columnName\": \"key\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"value\",\n            \"columnName\": \"value\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"deadline\",\n            \"columnName\": \"deadline\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"key\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_caches_key\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"key\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_caches_key` ON `${TABLE_NAME}` (`key`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"ruleSubs\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `url` TEXT NOT NULL, `type` INTEGER NOT NULL, `customOrder` INTEGER NOT NULL, `autoUpdate` INTEGER NOT NULL, `update` INTEGER NOT NULL, PRIMARY KEY(`id`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"autoUpdate\",\n            \"columnName\": \"autoUpdate\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"update\",\n            \"columnName\": \"update\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"id\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      }\n    ],\n    \"views\": [],\n    \"setupQueries\": [\n      \"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)\",\n      \"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'e20aa63032efb23c9e9c269afd64e7d7')\"\n    ]\n  }\n}"
  },
  {
    "path": "app/schemas/io.legado.app.data.AppDatabase/27.json",
    "content": "{\n  \"formatVersion\": 1,\n  \"database\": {\n    \"version\": 27,\n    \"identityHash\": \"e20aa63032efb23c9e9c269afd64e7d7\",\n    \"entities\": [\n      {\n        \"tableName\": \"books\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookUrl` TEXT NOT NULL, `tocUrl` TEXT NOT NULL, `origin` TEXT NOT NULL, `originName` TEXT NOT NULL, `name` TEXT NOT NULL, `author` TEXT NOT NULL, `kind` TEXT, `customTag` TEXT, `coverUrl` TEXT, `customCoverUrl` TEXT, `intro` TEXT, `customIntro` TEXT, `charset` TEXT, `type` INTEGER NOT NULL, `group` INTEGER NOT NULL, `latestChapterTitle` TEXT, `latestChapterTime` INTEGER NOT NULL, `lastCheckTime` INTEGER NOT NULL, `lastCheckCount` INTEGER NOT NULL, `totalChapterNum` INTEGER NOT NULL, `durChapterTitle` TEXT, `durChapterIndex` INTEGER NOT NULL, `durChapterPos` INTEGER NOT NULL, `durChapterTime` INTEGER NOT NULL, `wordCount` TEXT, `canUpdate` INTEGER NOT NULL, `order` INTEGER NOT NULL, `originOrder` INTEGER NOT NULL, `variable` TEXT, `readConfig` TEXT, PRIMARY KEY(`bookUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"tocUrl\",\n            \"columnName\": \"tocUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"originName\",\n            \"columnName\": \"originName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"author\",\n            \"columnName\": \"author\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"kind\",\n            \"columnName\": \"kind\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customTag\",\n            \"columnName\": \"customTag\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"coverUrl\",\n            \"columnName\": \"coverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customCoverUrl\",\n            \"columnName\": \"customCoverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"intro\",\n            \"columnName\": \"intro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customIntro\",\n            \"columnName\": \"customIntro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"charset\",\n            \"columnName\": \"charset\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"group\",\n            \"columnName\": \"group\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"latestChapterTitle\",\n            \"columnName\": \"latestChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"latestChapterTime\",\n            \"columnName\": \"latestChapterTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"lastCheckTime\",\n            \"columnName\": \"lastCheckTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"lastCheckCount\",\n            \"columnName\": \"lastCheckCount\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"totalChapterNum\",\n            \"columnName\": \"totalChapterNum\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"durChapterTitle\",\n            \"columnName\": \"durChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"durChapterIndex\",\n            \"columnName\": \"durChapterIndex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"durChapterPos\",\n            \"columnName\": \"durChapterPos\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"durChapterTime\",\n            \"columnName\": \"durChapterTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"wordCount\",\n            \"columnName\": \"wordCount\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"canUpdate\",\n            \"columnName\": \"canUpdate\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"originOrder\",\n            \"columnName\": \"originOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"readConfig\",\n            \"columnName\": \"readConfig\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"bookUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_books_name_author\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"name\",\n              \"author\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_books_name_author` ON `${TABLE_NAME}` (`name`, `author`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"book_groups\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`groupId` INTEGER NOT NULL, `groupName` TEXT NOT NULL, `order` INTEGER NOT NULL, `show` INTEGER NOT NULL, PRIMARY KEY(`groupId`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"groupId\",\n            \"columnName\": \"groupId\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"groupName\",\n            \"columnName\": \"groupName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"show\",\n            \"columnName\": \"show\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"groupId\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"book_sources\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookSourceName` TEXT NOT NULL, `bookSourceGroup` TEXT, `bookSourceUrl` TEXT NOT NULL, `bookSourceType` INTEGER NOT NULL, `bookUrlPattern` TEXT, `customOrder` INTEGER NOT NULL, `enabled` INTEGER NOT NULL, `enabledExplore` INTEGER NOT NULL, `header` TEXT, `loginUrl` TEXT, `bookSourceComment` TEXT, `lastUpdateTime` INTEGER NOT NULL, `weight` INTEGER NOT NULL, `exploreUrl` TEXT, `ruleExplore` TEXT, `searchUrl` TEXT, `ruleSearch` TEXT, `ruleBookInfo` TEXT, `ruleToc` TEXT, `ruleContent` TEXT, PRIMARY KEY(`bookSourceUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookSourceName\",\n            \"columnName\": \"bookSourceName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookSourceGroup\",\n            \"columnName\": \"bookSourceGroup\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"bookSourceUrl\",\n            \"columnName\": \"bookSourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookSourceType\",\n            \"columnName\": \"bookSourceType\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookUrlPattern\",\n            \"columnName\": \"bookUrlPattern\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enabled\",\n            \"columnName\": \"enabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enabledExplore\",\n            \"columnName\": \"enabledExplore\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"header\",\n            \"columnName\": \"header\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUrl\",\n            \"columnName\": \"loginUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"bookSourceComment\",\n            \"columnName\": \"bookSourceComment\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"lastUpdateTime\",\n            \"columnName\": \"lastUpdateTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"weight\",\n            \"columnName\": \"weight\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"exploreUrl\",\n            \"columnName\": \"exploreUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleExplore\",\n            \"columnName\": \"ruleExplore\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"searchUrl\",\n            \"columnName\": \"searchUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleSearch\",\n            \"columnName\": \"ruleSearch\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleBookInfo\",\n            \"columnName\": \"ruleBookInfo\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleToc\",\n            \"columnName\": \"ruleToc\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleContent\",\n            \"columnName\": \"ruleContent\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"bookSourceUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_book_sources_bookSourceUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"bookSourceUrl\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_book_sources_bookSourceUrl` ON `${TABLE_NAME}` (`bookSourceUrl`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"chapters\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`url` TEXT NOT NULL, `title` TEXT NOT NULL, `baseUrl` TEXT NOT NULL, `bookUrl` TEXT NOT NULL, `index` INTEGER NOT NULL, `resourceUrl` TEXT, `tag` TEXT, `start` INTEGER, `end` INTEGER, `variable` TEXT, PRIMARY KEY(`url`, `bookUrl`), FOREIGN KEY(`bookUrl`) REFERENCES `books`(`bookUrl`) ON UPDATE NO ACTION ON DELETE CASCADE )\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"baseUrl\",\n            \"columnName\": \"baseUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"index\",\n            \"columnName\": \"index\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"resourceUrl\",\n            \"columnName\": \"resourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"tag\",\n            \"columnName\": \"tag\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"start\",\n            \"columnName\": \"start\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"end\",\n            \"columnName\": \"end\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"url\",\n            \"bookUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_chapters_bookUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"bookUrl\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_chapters_bookUrl` ON `${TABLE_NAME}` (`bookUrl`)\"\n          },\n          {\n            \"name\": \"index_chapters_bookUrl_index\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"bookUrl\",\n              \"index\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_chapters_bookUrl_index` ON `${TABLE_NAME}` (`bookUrl`, `index`)\"\n          }\n        ],\n        \"foreignKeys\": [\n          {\n            \"table\": \"books\",\n            \"onDelete\": \"CASCADE\",\n            \"onUpdate\": \"NO ACTION\",\n            \"columns\": [\n              \"bookUrl\"\n            ],\n            \"referencedColumns\": [\n              \"bookUrl\"\n            ]\n          }\n        ]\n      },\n      {\n        \"tableName\": \"replace_rules\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL, `group` TEXT, `pattern` TEXT NOT NULL, `replacement` TEXT NOT NULL, `scope` TEXT, `isEnabled` INTEGER NOT NULL, `isRegex` INTEGER NOT NULL, `sortOrder` INTEGER NOT NULL)\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"group\",\n            \"columnName\": \"group\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"pattern\",\n            \"columnName\": \"pattern\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"replacement\",\n            \"columnName\": \"replacement\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"scope\",\n            \"columnName\": \"scope\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"isEnabled\",\n            \"columnName\": \"isEnabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"isRegex\",\n            \"columnName\": \"isRegex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"sortOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"id\"\n          ],\n          \"autoGenerate\": true\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_replace_rules_id\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"id\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_replace_rules_id` ON `${TABLE_NAME}` (`id`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"searchBooks\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookUrl` TEXT NOT NULL, `origin` TEXT NOT NULL, `originName` TEXT NOT NULL, `type` INTEGER NOT NULL, `name` TEXT NOT NULL, `author` TEXT NOT NULL, `kind` TEXT, `coverUrl` TEXT, `intro` TEXT, `wordCount` TEXT, `latestChapterTitle` TEXT, `tocUrl` TEXT NOT NULL, `time` INTEGER NOT NULL, `variable` TEXT, `originOrder` INTEGER NOT NULL, PRIMARY KEY(`bookUrl`), FOREIGN KEY(`origin`) REFERENCES `book_sources`(`bookSourceUrl`) ON UPDATE NO ACTION ON DELETE CASCADE )\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"originName\",\n            \"columnName\": \"originName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"author\",\n            \"columnName\": \"author\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"kind\",\n            \"columnName\": \"kind\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"coverUrl\",\n            \"columnName\": \"coverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"intro\",\n            \"columnName\": \"intro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"wordCount\",\n            \"columnName\": \"wordCount\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"latestChapterTitle\",\n            \"columnName\": \"latestChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"tocUrl\",\n            \"columnName\": \"tocUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"time\",\n            \"columnName\": \"time\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"originOrder\",\n            \"columnName\": \"originOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"bookUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_searchBooks_bookUrl\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"bookUrl\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_searchBooks_bookUrl` ON `${TABLE_NAME}` (`bookUrl`)\"\n          },\n          {\n            \"name\": \"index_searchBooks_origin\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"origin\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_searchBooks_origin` ON `${TABLE_NAME}` (`origin`)\"\n          }\n        ],\n        \"foreignKeys\": [\n          {\n            \"table\": \"book_sources\",\n            \"onDelete\": \"CASCADE\",\n            \"onUpdate\": \"NO ACTION\",\n            \"columns\": [\n              \"origin\"\n            ],\n            \"referencedColumns\": [\n              \"bookSourceUrl\"\n            ]\n          }\n        ]\n      },\n      {\n        \"tableName\": \"search_keywords\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`word` TEXT NOT NULL, `usage` INTEGER NOT NULL, `lastUseTime` INTEGER NOT NULL, PRIMARY KEY(`word`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"word\",\n            \"columnName\": \"word\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"usage\",\n            \"columnName\": \"usage\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"lastUseTime\",\n            \"columnName\": \"lastUseTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"word\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_search_keywords_word\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"word\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_search_keywords_word` ON `${TABLE_NAME}` (`word`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"cookies\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`url` TEXT NOT NULL, `cookie` TEXT NOT NULL, PRIMARY KEY(`url`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"cookie\",\n            \"columnName\": \"cookie\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"url\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_cookies_url\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"url\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_cookies_url` ON `${TABLE_NAME}` (`url`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssSources\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`sourceUrl` TEXT NOT NULL, `sourceName` TEXT NOT NULL, `sourceIcon` TEXT NOT NULL, `sourceGroup` TEXT, `enabled` INTEGER NOT NULL, `sortUrl` TEXT, `singleUrl` INTEGER NOT NULL, `articleStyle` INTEGER NOT NULL, `ruleArticles` TEXT, `ruleNextPage` TEXT, `ruleTitle` TEXT, `rulePubDate` TEXT, `ruleDescription` TEXT, `ruleImage` TEXT, `ruleLink` TEXT, `ruleContent` TEXT, `style` TEXT, `header` TEXT, `enableJs` INTEGER NOT NULL, `loadWithBaseUrl` INTEGER NOT NULL, `customOrder` INTEGER NOT NULL, PRIMARY KEY(`sourceUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"sourceUrl\",\n            \"columnName\": \"sourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceName\",\n            \"columnName\": \"sourceName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceIcon\",\n            \"columnName\": \"sourceIcon\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceGroup\",\n            \"columnName\": \"sourceGroup\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enabled\",\n            \"columnName\": \"enabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sortUrl\",\n            \"columnName\": \"sortUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"singleUrl\",\n            \"columnName\": \"singleUrl\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"articleStyle\",\n            \"columnName\": \"articleStyle\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"ruleArticles\",\n            \"columnName\": \"ruleArticles\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleNextPage\",\n            \"columnName\": \"ruleNextPage\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleTitle\",\n            \"columnName\": \"ruleTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"rulePubDate\",\n            \"columnName\": \"rulePubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleDescription\",\n            \"columnName\": \"ruleDescription\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleImage\",\n            \"columnName\": \"ruleImage\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleLink\",\n            \"columnName\": \"ruleLink\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleContent\",\n            \"columnName\": \"ruleContent\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"style\",\n            \"columnName\": \"style\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"header\",\n            \"columnName\": \"header\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enableJs\",\n            \"columnName\": \"enableJs\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"loadWithBaseUrl\",\n            \"columnName\": \"loadWithBaseUrl\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"sourceUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_rssSources_sourceUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"sourceUrl\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_rssSources_sourceUrl` ON `${TABLE_NAME}` (`sourceUrl`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"bookmarks\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`time` INTEGER NOT NULL, `bookUrl` TEXT NOT NULL, `bookName` TEXT NOT NULL, `bookAuthor` TEXT NOT NULL, `chapterIndex` INTEGER NOT NULL, `chapterPos` INTEGER NOT NULL, `chapterName` TEXT NOT NULL, `bookText` TEXT NOT NULL, `content` TEXT NOT NULL, PRIMARY KEY(`time`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"time\",\n            \"columnName\": \"time\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookName\",\n            \"columnName\": \"bookName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookAuthor\",\n            \"columnName\": \"bookAuthor\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterIndex\",\n            \"columnName\": \"chapterIndex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterPos\",\n            \"columnName\": \"chapterPos\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterName\",\n            \"columnName\": \"chapterName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookText\",\n            \"columnName\": \"bookText\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"time\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_bookmarks_time\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"time\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_bookmarks_time` ON `${TABLE_NAME}` (`time`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssArticles\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`origin` TEXT NOT NULL, `sort` TEXT NOT NULL, `title` TEXT NOT NULL, `order` INTEGER NOT NULL, `link` TEXT NOT NULL, `pubDate` TEXT, `description` TEXT, `content` TEXT, `image` TEXT, `read` INTEGER NOT NULL, PRIMARY KEY(`origin`, `link`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sort\",\n            \"columnName\": \"sort\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"link\",\n            \"columnName\": \"link\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"pubDate\",\n            \"columnName\": \"pubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"description\",\n            \"columnName\": \"description\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"image\",\n            \"columnName\": \"image\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"read\",\n            \"columnName\": \"read\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"origin\",\n            \"link\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssReadRecords\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`record` TEXT NOT NULL, `read` INTEGER NOT NULL, PRIMARY KEY(`record`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"record\",\n            \"columnName\": \"record\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"read\",\n            \"columnName\": \"read\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"record\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssStars\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`origin` TEXT NOT NULL, `sort` TEXT NOT NULL, `title` TEXT NOT NULL, `starTime` INTEGER NOT NULL, `link` TEXT NOT NULL, `pubDate` TEXT, `description` TEXT, `content` TEXT, `image` TEXT, PRIMARY KEY(`origin`, `link`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sort\",\n            \"columnName\": \"sort\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"starTime\",\n            \"columnName\": \"starTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"link\",\n            \"columnName\": \"link\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"pubDate\",\n            \"columnName\": \"pubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"description\",\n            \"columnName\": \"description\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"image\",\n            \"columnName\": \"image\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"origin\",\n            \"link\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"txtTocRules\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `rule` TEXT NOT NULL, `serialNumber` INTEGER NOT NULL, `enable` INTEGER NOT NULL, PRIMARY KEY(`id`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"rule\",\n            \"columnName\": \"rule\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"serialNumber\",\n            \"columnName\": \"serialNumber\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enable\",\n            \"columnName\": \"enable\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"id\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"readRecord\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`androidId` TEXT NOT NULL, `bookName` TEXT NOT NULL, `readTime` INTEGER NOT NULL, PRIMARY KEY(`androidId`, `bookName`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"androidId\",\n            \"columnName\": \"androidId\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookName\",\n            \"columnName\": \"bookName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"readTime\",\n            \"columnName\": \"readTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"androidId\",\n            \"bookName\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"httpTTS\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `url` TEXT NOT NULL, PRIMARY KEY(`id`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"id\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"caches\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`key` TEXT NOT NULL, `value` TEXT, `deadline` INTEGER NOT NULL, PRIMARY KEY(`key`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"key\",\n            \"columnName\": \"key\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"value\",\n            \"columnName\": \"value\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"deadline\",\n            \"columnName\": \"deadline\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"key\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_caches_key\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"key\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_caches_key` ON `${TABLE_NAME}` (`key`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"ruleSubs\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `url` TEXT NOT NULL, `type` INTEGER NOT NULL, `customOrder` INTEGER NOT NULL, `autoUpdate` INTEGER NOT NULL, `update` INTEGER NOT NULL, PRIMARY KEY(`id`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"autoUpdate\",\n            \"columnName\": \"autoUpdate\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"update\",\n            \"columnName\": \"update\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"id\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      }\n    ],\n    \"views\": [],\n    \"setupQueries\": [\n      \"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)\",\n      \"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'e20aa63032efb23c9e9c269afd64e7d7')\"\n    ]\n  }\n}"
  },
  {
    "path": "app/schemas/io.legado.app.data.AppDatabase/28.json",
    "content": "{\n  \"formatVersion\": 1,\n  \"database\": {\n    \"version\": 28,\n    \"identityHash\": \"f77119a40a8930665af834d03c8c5d25\",\n    \"entities\": [\n      {\n        \"tableName\": \"books\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookUrl` TEXT NOT NULL, `tocUrl` TEXT NOT NULL, `origin` TEXT NOT NULL, `originName` TEXT NOT NULL, `name` TEXT NOT NULL, `author` TEXT NOT NULL, `kind` TEXT, `customTag` TEXT, `coverUrl` TEXT, `customCoverUrl` TEXT, `intro` TEXT, `customIntro` TEXT, `charset` TEXT, `type` INTEGER NOT NULL, `group` INTEGER NOT NULL, `latestChapterTitle` TEXT, `latestChapterTime` INTEGER NOT NULL, `lastCheckTime` INTEGER NOT NULL, `lastCheckCount` INTEGER NOT NULL, `totalChapterNum` INTEGER NOT NULL, `durChapterTitle` TEXT, `durChapterIndex` INTEGER NOT NULL, `durChapterPos` INTEGER NOT NULL, `durChapterTime` INTEGER NOT NULL, `wordCount` TEXT, `canUpdate` INTEGER NOT NULL, `order` INTEGER NOT NULL, `originOrder` INTEGER NOT NULL, `variable` TEXT, `readConfig` TEXT, PRIMARY KEY(`bookUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"tocUrl\",\n            \"columnName\": \"tocUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"originName\",\n            \"columnName\": \"originName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"author\",\n            \"columnName\": \"author\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"kind\",\n            \"columnName\": \"kind\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customTag\",\n            \"columnName\": \"customTag\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"coverUrl\",\n            \"columnName\": \"coverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customCoverUrl\",\n            \"columnName\": \"customCoverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"intro\",\n            \"columnName\": \"intro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customIntro\",\n            \"columnName\": \"customIntro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"charset\",\n            \"columnName\": \"charset\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"group\",\n            \"columnName\": \"group\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"latestChapterTitle\",\n            \"columnName\": \"latestChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"latestChapterTime\",\n            \"columnName\": \"latestChapterTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"lastCheckTime\",\n            \"columnName\": \"lastCheckTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"lastCheckCount\",\n            \"columnName\": \"lastCheckCount\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"totalChapterNum\",\n            \"columnName\": \"totalChapterNum\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"durChapterTitle\",\n            \"columnName\": \"durChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"durChapterIndex\",\n            \"columnName\": \"durChapterIndex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"durChapterPos\",\n            \"columnName\": \"durChapterPos\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"durChapterTime\",\n            \"columnName\": \"durChapterTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"wordCount\",\n            \"columnName\": \"wordCount\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"canUpdate\",\n            \"columnName\": \"canUpdate\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"originOrder\",\n            \"columnName\": \"originOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"readConfig\",\n            \"columnName\": \"readConfig\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"bookUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_books_name_author\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"name\",\n              \"author\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_books_name_author` ON `${TABLE_NAME}` (`name`, `author`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"book_groups\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`groupId` INTEGER NOT NULL, `groupName` TEXT NOT NULL, `order` INTEGER NOT NULL, `show` INTEGER NOT NULL, PRIMARY KEY(`groupId`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"groupId\",\n            \"columnName\": \"groupId\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"groupName\",\n            \"columnName\": \"groupName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"show\",\n            \"columnName\": \"show\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"groupId\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"book_sources\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookSourceName` TEXT NOT NULL, `bookSourceGroup` TEXT, `bookSourceUrl` TEXT NOT NULL, `bookSourceType` INTEGER NOT NULL, `bookUrlPattern` TEXT, `customOrder` INTEGER NOT NULL, `enabled` INTEGER NOT NULL, `enabledExplore` INTEGER NOT NULL, `header` TEXT, `loginUrl` TEXT, `bookSourceComment` TEXT, `lastUpdateTime` INTEGER NOT NULL, `weight` INTEGER NOT NULL, `exploreUrl` TEXT, `ruleExplore` TEXT, `searchUrl` TEXT, `ruleSearch` TEXT, `ruleBookInfo` TEXT, `ruleToc` TEXT, `ruleContent` TEXT, PRIMARY KEY(`bookSourceUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookSourceName\",\n            \"columnName\": \"bookSourceName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookSourceGroup\",\n            \"columnName\": \"bookSourceGroup\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"bookSourceUrl\",\n            \"columnName\": \"bookSourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookSourceType\",\n            \"columnName\": \"bookSourceType\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookUrlPattern\",\n            \"columnName\": \"bookUrlPattern\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enabled\",\n            \"columnName\": \"enabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enabledExplore\",\n            \"columnName\": \"enabledExplore\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"header\",\n            \"columnName\": \"header\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUrl\",\n            \"columnName\": \"loginUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"bookSourceComment\",\n            \"columnName\": \"bookSourceComment\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"lastUpdateTime\",\n            \"columnName\": \"lastUpdateTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"weight\",\n            \"columnName\": \"weight\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"exploreUrl\",\n            \"columnName\": \"exploreUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleExplore\",\n            \"columnName\": \"ruleExplore\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"searchUrl\",\n            \"columnName\": \"searchUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleSearch\",\n            \"columnName\": \"ruleSearch\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleBookInfo\",\n            \"columnName\": \"ruleBookInfo\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleToc\",\n            \"columnName\": \"ruleToc\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleContent\",\n            \"columnName\": \"ruleContent\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"bookSourceUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_book_sources_bookSourceUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"bookSourceUrl\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_book_sources_bookSourceUrl` ON `${TABLE_NAME}` (`bookSourceUrl`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"chapters\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`url` TEXT NOT NULL, `title` TEXT NOT NULL, `baseUrl` TEXT NOT NULL, `bookUrl` TEXT NOT NULL, `index` INTEGER NOT NULL, `resourceUrl` TEXT, `tag` TEXT, `start` INTEGER, `end` INTEGER, `variable` TEXT, PRIMARY KEY(`url`, `bookUrl`), FOREIGN KEY(`bookUrl`) REFERENCES `books`(`bookUrl`) ON UPDATE NO ACTION ON DELETE CASCADE )\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"baseUrl\",\n            \"columnName\": \"baseUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"index\",\n            \"columnName\": \"index\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"resourceUrl\",\n            \"columnName\": \"resourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"tag\",\n            \"columnName\": \"tag\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"start\",\n            \"columnName\": \"start\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"end\",\n            \"columnName\": \"end\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"url\",\n            \"bookUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_chapters_bookUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"bookUrl\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_chapters_bookUrl` ON `${TABLE_NAME}` (`bookUrl`)\"\n          },\n          {\n            \"name\": \"index_chapters_bookUrl_index\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"bookUrl\",\n              \"index\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_chapters_bookUrl_index` ON `${TABLE_NAME}` (`bookUrl`, `index`)\"\n          }\n        ],\n        \"foreignKeys\": [\n          {\n            \"table\": \"books\",\n            \"onDelete\": \"CASCADE\",\n            \"onUpdate\": \"NO ACTION\",\n            \"columns\": [\n              \"bookUrl\"\n            ],\n            \"referencedColumns\": [\n              \"bookUrl\"\n            ]\n          }\n        ]\n      },\n      {\n        \"tableName\": \"replace_rules\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL, `group` TEXT, `pattern` TEXT NOT NULL, `replacement` TEXT NOT NULL, `scope` TEXT, `isEnabled` INTEGER NOT NULL, `isRegex` INTEGER NOT NULL, `sortOrder` INTEGER NOT NULL)\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"group\",\n            \"columnName\": \"group\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"pattern\",\n            \"columnName\": \"pattern\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"replacement\",\n            \"columnName\": \"replacement\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"scope\",\n            \"columnName\": \"scope\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"isEnabled\",\n            \"columnName\": \"isEnabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"isRegex\",\n            \"columnName\": \"isRegex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"sortOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"id\"\n          ],\n          \"autoGenerate\": true\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_replace_rules_id\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"id\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_replace_rules_id` ON `${TABLE_NAME}` (`id`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"searchBooks\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookUrl` TEXT NOT NULL, `origin` TEXT NOT NULL, `originName` TEXT NOT NULL, `type` INTEGER NOT NULL, `name` TEXT NOT NULL, `author` TEXT NOT NULL, `kind` TEXT, `coverUrl` TEXT, `intro` TEXT, `wordCount` TEXT, `latestChapterTitle` TEXT, `tocUrl` TEXT NOT NULL, `time` INTEGER NOT NULL, `variable` TEXT, `originOrder` INTEGER NOT NULL, PRIMARY KEY(`bookUrl`), FOREIGN KEY(`origin`) REFERENCES `book_sources`(`bookSourceUrl`) ON UPDATE NO ACTION ON DELETE CASCADE )\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"originName\",\n            \"columnName\": \"originName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"author\",\n            \"columnName\": \"author\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"kind\",\n            \"columnName\": \"kind\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"coverUrl\",\n            \"columnName\": \"coverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"intro\",\n            \"columnName\": \"intro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"wordCount\",\n            \"columnName\": \"wordCount\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"latestChapterTitle\",\n            \"columnName\": \"latestChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"tocUrl\",\n            \"columnName\": \"tocUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"time\",\n            \"columnName\": \"time\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"originOrder\",\n            \"columnName\": \"originOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"bookUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_searchBooks_bookUrl\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"bookUrl\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_searchBooks_bookUrl` ON `${TABLE_NAME}` (`bookUrl`)\"\n          },\n          {\n            \"name\": \"index_searchBooks_origin\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"origin\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_searchBooks_origin` ON `${TABLE_NAME}` (`origin`)\"\n          }\n        ],\n        \"foreignKeys\": [\n          {\n            \"table\": \"book_sources\",\n            \"onDelete\": \"CASCADE\",\n            \"onUpdate\": \"NO ACTION\",\n            \"columns\": [\n              \"origin\"\n            ],\n            \"referencedColumns\": [\n              \"bookSourceUrl\"\n            ]\n          }\n        ]\n      },\n      {\n        \"tableName\": \"search_keywords\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`word` TEXT NOT NULL, `usage` INTEGER NOT NULL, `lastUseTime` INTEGER NOT NULL, PRIMARY KEY(`word`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"word\",\n            \"columnName\": \"word\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"usage\",\n            \"columnName\": \"usage\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"lastUseTime\",\n            \"columnName\": \"lastUseTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"word\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_search_keywords_word\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"word\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_search_keywords_word` ON `${TABLE_NAME}` (`word`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"cookies\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`url` TEXT NOT NULL, `cookie` TEXT NOT NULL, PRIMARY KEY(`url`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"cookie\",\n            \"columnName\": \"cookie\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"url\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_cookies_url\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"url\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_cookies_url` ON `${TABLE_NAME}` (`url`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssSources\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`sourceUrl` TEXT NOT NULL, `sourceName` TEXT NOT NULL, `sourceIcon` TEXT NOT NULL, `sourceGroup` TEXT, `enabled` INTEGER NOT NULL, `sortUrl` TEXT, `singleUrl` INTEGER NOT NULL, `articleStyle` INTEGER NOT NULL, `ruleArticles` TEXT, `ruleNextPage` TEXT, `ruleTitle` TEXT, `rulePubDate` TEXT, `ruleDescription` TEXT, `ruleImage` TEXT, `ruleLink` TEXT, `ruleContent` TEXT, `style` TEXT, `header` TEXT, `enableJs` INTEGER NOT NULL, `loadWithBaseUrl` INTEGER NOT NULL, `customOrder` INTEGER NOT NULL, PRIMARY KEY(`sourceUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"sourceUrl\",\n            \"columnName\": \"sourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceName\",\n            \"columnName\": \"sourceName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceIcon\",\n            \"columnName\": \"sourceIcon\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceGroup\",\n            \"columnName\": \"sourceGroup\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enabled\",\n            \"columnName\": \"enabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sortUrl\",\n            \"columnName\": \"sortUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"singleUrl\",\n            \"columnName\": \"singleUrl\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"articleStyle\",\n            \"columnName\": \"articleStyle\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"ruleArticles\",\n            \"columnName\": \"ruleArticles\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleNextPage\",\n            \"columnName\": \"ruleNextPage\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleTitle\",\n            \"columnName\": \"ruleTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"rulePubDate\",\n            \"columnName\": \"rulePubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleDescription\",\n            \"columnName\": \"ruleDescription\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleImage\",\n            \"columnName\": \"ruleImage\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleLink\",\n            \"columnName\": \"ruleLink\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleContent\",\n            \"columnName\": \"ruleContent\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"style\",\n            \"columnName\": \"style\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"header\",\n            \"columnName\": \"header\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enableJs\",\n            \"columnName\": \"enableJs\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"loadWithBaseUrl\",\n            \"columnName\": \"loadWithBaseUrl\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"sourceUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_rssSources_sourceUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"sourceUrl\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_rssSources_sourceUrl` ON `${TABLE_NAME}` (`sourceUrl`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"bookmarks\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`time` INTEGER NOT NULL, `bookUrl` TEXT NOT NULL, `bookName` TEXT NOT NULL, `bookAuthor` TEXT NOT NULL, `chapterIndex` INTEGER NOT NULL, `chapterPos` INTEGER NOT NULL, `chapterName` TEXT NOT NULL, `bookText` TEXT NOT NULL, `content` TEXT NOT NULL, PRIMARY KEY(`time`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"time\",\n            \"columnName\": \"time\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookName\",\n            \"columnName\": \"bookName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookAuthor\",\n            \"columnName\": \"bookAuthor\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterIndex\",\n            \"columnName\": \"chapterIndex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterPos\",\n            \"columnName\": \"chapterPos\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterName\",\n            \"columnName\": \"chapterName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookText\",\n            \"columnName\": \"bookText\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"time\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_bookmarks_time\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"time\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_bookmarks_time` ON `${TABLE_NAME}` (`time`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssArticles\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`origin` TEXT NOT NULL, `sort` TEXT NOT NULL, `title` TEXT NOT NULL, `order` INTEGER NOT NULL, `link` TEXT NOT NULL, `pubDate` TEXT, `description` TEXT, `content` TEXT, `image` TEXT, `read` INTEGER NOT NULL, `variable` TEXT, PRIMARY KEY(`origin`, `link`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sort\",\n            \"columnName\": \"sort\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"link\",\n            \"columnName\": \"link\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"pubDate\",\n            \"columnName\": \"pubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"description\",\n            \"columnName\": \"description\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"image\",\n            \"columnName\": \"image\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"read\",\n            \"columnName\": \"read\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"origin\",\n            \"link\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssReadRecords\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`record` TEXT NOT NULL, `read` INTEGER NOT NULL, PRIMARY KEY(`record`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"record\",\n            \"columnName\": \"record\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"read\",\n            \"columnName\": \"read\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"record\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssStars\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`origin` TEXT NOT NULL, `sort` TEXT NOT NULL, `title` TEXT NOT NULL, `starTime` INTEGER NOT NULL, `link` TEXT NOT NULL, `pubDate` TEXT, `description` TEXT, `content` TEXT, `image` TEXT, `variable` TEXT, PRIMARY KEY(`origin`, `link`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sort\",\n            \"columnName\": \"sort\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"starTime\",\n            \"columnName\": \"starTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"link\",\n            \"columnName\": \"link\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"pubDate\",\n            \"columnName\": \"pubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"description\",\n            \"columnName\": \"description\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"image\",\n            \"columnName\": \"image\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"origin\",\n            \"link\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"txtTocRules\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `rule` TEXT NOT NULL, `serialNumber` INTEGER NOT NULL, `enable` INTEGER NOT NULL, PRIMARY KEY(`id`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"rule\",\n            \"columnName\": \"rule\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"serialNumber\",\n            \"columnName\": \"serialNumber\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enable\",\n            \"columnName\": \"enable\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"id\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"readRecord\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`androidId` TEXT NOT NULL, `bookName` TEXT NOT NULL, `readTime` INTEGER NOT NULL, PRIMARY KEY(`androidId`, `bookName`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"androidId\",\n            \"columnName\": \"androidId\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookName\",\n            \"columnName\": \"bookName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"readTime\",\n            \"columnName\": \"readTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"androidId\",\n            \"bookName\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"httpTTS\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `url` TEXT NOT NULL, PRIMARY KEY(`id`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"id\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"caches\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`key` TEXT NOT NULL, `value` TEXT, `deadline` INTEGER NOT NULL, PRIMARY KEY(`key`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"key\",\n            \"columnName\": \"key\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"value\",\n            \"columnName\": \"value\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"deadline\",\n            \"columnName\": \"deadline\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"key\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_caches_key\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"key\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_caches_key` ON `${TABLE_NAME}` (`key`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"ruleSubs\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `url` TEXT NOT NULL, `type` INTEGER NOT NULL, `customOrder` INTEGER NOT NULL, `autoUpdate` INTEGER NOT NULL, `update` INTEGER NOT NULL, PRIMARY KEY(`id`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"autoUpdate\",\n            \"columnName\": \"autoUpdate\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"update\",\n            \"columnName\": \"update\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"id\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      }\n    ],\n    \"views\": [],\n    \"setupQueries\": [\n      \"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)\",\n      \"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'f77119a40a8930665af834d03c8c5d25')\"\n    ]\n  }\n}"
  },
  {
    "path": "app/schemas/io.legado.app.data.AppDatabase/29.json",
    "content": "{\n  \"formatVersion\": 1,\n  \"database\": {\n    \"version\": 29,\n    \"identityHash\": \"85f1e7146f650af82aac6f9137eff815\",\n    \"entities\": [\n      {\n        \"tableName\": \"books\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookUrl` TEXT NOT NULL, `tocUrl` TEXT NOT NULL, `origin` TEXT NOT NULL, `originName` TEXT NOT NULL, `name` TEXT NOT NULL, `author` TEXT NOT NULL, `kind` TEXT, `customTag` TEXT, `coverUrl` TEXT, `customCoverUrl` TEXT, `intro` TEXT, `customIntro` TEXT, `charset` TEXT, `type` INTEGER NOT NULL, `group` INTEGER NOT NULL, `latestChapterTitle` TEXT, `latestChapterTime` INTEGER NOT NULL, `lastCheckTime` INTEGER NOT NULL, `lastCheckCount` INTEGER NOT NULL, `totalChapterNum` INTEGER NOT NULL, `durChapterTitle` TEXT, `durChapterIndex` INTEGER NOT NULL, `durChapterPos` INTEGER NOT NULL, `durChapterTime` INTEGER NOT NULL, `wordCount` TEXT, `canUpdate` INTEGER NOT NULL, `order` INTEGER NOT NULL, `originOrder` INTEGER NOT NULL, `variable` TEXT, `readConfig` TEXT, PRIMARY KEY(`bookUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"tocUrl\",\n            \"columnName\": \"tocUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"originName\",\n            \"columnName\": \"originName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"author\",\n            \"columnName\": \"author\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"kind\",\n            \"columnName\": \"kind\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customTag\",\n            \"columnName\": \"customTag\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"coverUrl\",\n            \"columnName\": \"coverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customCoverUrl\",\n            \"columnName\": \"customCoverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"intro\",\n            \"columnName\": \"intro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customIntro\",\n            \"columnName\": \"customIntro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"charset\",\n            \"columnName\": \"charset\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"group\",\n            \"columnName\": \"group\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"latestChapterTitle\",\n            \"columnName\": \"latestChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"latestChapterTime\",\n            \"columnName\": \"latestChapterTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"lastCheckTime\",\n            \"columnName\": \"lastCheckTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"lastCheckCount\",\n            \"columnName\": \"lastCheckCount\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"totalChapterNum\",\n            \"columnName\": \"totalChapterNum\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"durChapterTitle\",\n            \"columnName\": \"durChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"durChapterIndex\",\n            \"columnName\": \"durChapterIndex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"durChapterPos\",\n            \"columnName\": \"durChapterPos\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"durChapterTime\",\n            \"columnName\": \"durChapterTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"wordCount\",\n            \"columnName\": \"wordCount\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"canUpdate\",\n            \"columnName\": \"canUpdate\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"originOrder\",\n            \"columnName\": \"originOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"readConfig\",\n            \"columnName\": \"readConfig\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"bookUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_books_name_author\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"name\",\n              \"author\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_books_name_author` ON `${TABLE_NAME}` (`name`, `author`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"book_groups\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`groupId` INTEGER NOT NULL, `groupName` TEXT NOT NULL, `order` INTEGER NOT NULL, `show` INTEGER NOT NULL, PRIMARY KEY(`groupId`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"groupId\",\n            \"columnName\": \"groupId\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"groupName\",\n            \"columnName\": \"groupName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"show\",\n            \"columnName\": \"show\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"groupId\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"book_sources\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookSourceName` TEXT NOT NULL, `bookSourceGroup` TEXT, `bookSourceUrl` TEXT NOT NULL, `bookSourceType` INTEGER NOT NULL, `bookUrlPattern` TEXT, `customOrder` INTEGER NOT NULL, `enabled` INTEGER NOT NULL, `enabledExplore` INTEGER NOT NULL, `header` TEXT, `loginUrl` TEXT, `bookSourceComment` TEXT, `lastUpdateTime` INTEGER NOT NULL, `weight` INTEGER NOT NULL, `exploreUrl` TEXT, `ruleExplore` TEXT, `searchUrl` TEXT, `ruleSearch` TEXT, `ruleBookInfo` TEXT, `ruleToc` TEXT, `ruleContent` TEXT, PRIMARY KEY(`bookSourceUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookSourceName\",\n            \"columnName\": \"bookSourceName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookSourceGroup\",\n            \"columnName\": \"bookSourceGroup\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"bookSourceUrl\",\n            \"columnName\": \"bookSourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookSourceType\",\n            \"columnName\": \"bookSourceType\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookUrlPattern\",\n            \"columnName\": \"bookUrlPattern\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enabled\",\n            \"columnName\": \"enabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enabledExplore\",\n            \"columnName\": \"enabledExplore\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"header\",\n            \"columnName\": \"header\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUrl\",\n            \"columnName\": \"loginUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"bookSourceComment\",\n            \"columnName\": \"bookSourceComment\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"lastUpdateTime\",\n            \"columnName\": \"lastUpdateTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"weight\",\n            \"columnName\": \"weight\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"exploreUrl\",\n            \"columnName\": \"exploreUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleExplore\",\n            \"columnName\": \"ruleExplore\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"searchUrl\",\n            \"columnName\": \"searchUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleSearch\",\n            \"columnName\": \"ruleSearch\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleBookInfo\",\n            \"columnName\": \"ruleBookInfo\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleToc\",\n            \"columnName\": \"ruleToc\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleContent\",\n            \"columnName\": \"ruleContent\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"bookSourceUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_book_sources_bookSourceUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"bookSourceUrl\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_book_sources_bookSourceUrl` ON `${TABLE_NAME}` (`bookSourceUrl`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"chapters\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`url` TEXT NOT NULL, `title` TEXT NOT NULL, `baseUrl` TEXT NOT NULL, `bookUrl` TEXT NOT NULL, `index` INTEGER NOT NULL, `resourceUrl` TEXT, `tag` TEXT, `start` INTEGER, `end` INTEGER, `variable` TEXT, PRIMARY KEY(`url`, `bookUrl`), FOREIGN KEY(`bookUrl`) REFERENCES `books`(`bookUrl`) ON UPDATE NO ACTION ON DELETE CASCADE )\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"baseUrl\",\n            \"columnName\": \"baseUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"index\",\n            \"columnName\": \"index\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"resourceUrl\",\n            \"columnName\": \"resourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"tag\",\n            \"columnName\": \"tag\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"start\",\n            \"columnName\": \"start\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"end\",\n            \"columnName\": \"end\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"url\",\n            \"bookUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_chapters_bookUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"bookUrl\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_chapters_bookUrl` ON `${TABLE_NAME}` (`bookUrl`)\"\n          },\n          {\n            \"name\": \"index_chapters_bookUrl_index\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"bookUrl\",\n              \"index\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_chapters_bookUrl_index` ON `${TABLE_NAME}` (`bookUrl`, `index`)\"\n          }\n        ],\n        \"foreignKeys\": [\n          {\n            \"table\": \"books\",\n            \"onDelete\": \"CASCADE\",\n            \"onUpdate\": \"NO ACTION\",\n            \"columns\": [\n              \"bookUrl\"\n            ],\n            \"referencedColumns\": [\n              \"bookUrl\"\n            ]\n          }\n        ]\n      },\n      {\n        \"tableName\": \"replace_rules\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL, `group` TEXT, `pattern` TEXT NOT NULL, `replacement` TEXT NOT NULL, `scope` TEXT, `isEnabled` INTEGER NOT NULL, `isRegex` INTEGER NOT NULL, `sortOrder` INTEGER NOT NULL)\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"group\",\n            \"columnName\": \"group\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"pattern\",\n            \"columnName\": \"pattern\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"replacement\",\n            \"columnName\": \"replacement\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"scope\",\n            \"columnName\": \"scope\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"isEnabled\",\n            \"columnName\": \"isEnabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"isRegex\",\n            \"columnName\": \"isRegex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"sortOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"id\"\n          ],\n          \"autoGenerate\": true\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_replace_rules_id\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"id\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_replace_rules_id` ON `${TABLE_NAME}` (`id`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"searchBooks\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookUrl` TEXT NOT NULL, `origin` TEXT NOT NULL, `originName` TEXT NOT NULL, `type` INTEGER NOT NULL, `name` TEXT NOT NULL, `author` TEXT NOT NULL, `kind` TEXT, `coverUrl` TEXT, `intro` TEXT, `wordCount` TEXT, `latestChapterTitle` TEXT, `tocUrl` TEXT NOT NULL, `time` INTEGER NOT NULL, `variable` TEXT, `originOrder` INTEGER NOT NULL, PRIMARY KEY(`bookUrl`), FOREIGN KEY(`origin`) REFERENCES `book_sources`(`bookSourceUrl`) ON UPDATE NO ACTION ON DELETE CASCADE )\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"originName\",\n            \"columnName\": \"originName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"author\",\n            \"columnName\": \"author\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"kind\",\n            \"columnName\": \"kind\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"coverUrl\",\n            \"columnName\": \"coverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"intro\",\n            \"columnName\": \"intro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"wordCount\",\n            \"columnName\": \"wordCount\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"latestChapterTitle\",\n            \"columnName\": \"latestChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"tocUrl\",\n            \"columnName\": \"tocUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"time\",\n            \"columnName\": \"time\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"originOrder\",\n            \"columnName\": \"originOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"bookUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_searchBooks_bookUrl\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"bookUrl\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_searchBooks_bookUrl` ON `${TABLE_NAME}` (`bookUrl`)\"\n          },\n          {\n            \"name\": \"index_searchBooks_origin\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"origin\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_searchBooks_origin` ON `${TABLE_NAME}` (`origin`)\"\n          }\n        ],\n        \"foreignKeys\": [\n          {\n            \"table\": \"book_sources\",\n            \"onDelete\": \"CASCADE\",\n            \"onUpdate\": \"NO ACTION\",\n            \"columns\": [\n              \"origin\"\n            ],\n            \"referencedColumns\": [\n              \"bookSourceUrl\"\n            ]\n          }\n        ]\n      },\n      {\n        \"tableName\": \"search_keywords\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`word` TEXT NOT NULL, `usage` INTEGER NOT NULL, `lastUseTime` INTEGER NOT NULL, PRIMARY KEY(`word`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"word\",\n            \"columnName\": \"word\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"usage\",\n            \"columnName\": \"usage\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"lastUseTime\",\n            \"columnName\": \"lastUseTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"word\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_search_keywords_word\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"word\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_search_keywords_word` ON `${TABLE_NAME}` (`word`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"cookies\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`url` TEXT NOT NULL, `cookie` TEXT NOT NULL, PRIMARY KEY(`url`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"cookie\",\n            \"columnName\": \"cookie\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"url\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_cookies_url\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"url\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_cookies_url` ON `${TABLE_NAME}` (`url`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssSources\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`sourceUrl` TEXT NOT NULL, `sourceName` TEXT NOT NULL, `sourceIcon` TEXT NOT NULL, `sourceGroup` TEXT, `sourceComment` TEXT, `enabled` INTEGER NOT NULL, `sortUrl` TEXT, `singleUrl` INTEGER NOT NULL, `articleStyle` INTEGER NOT NULL, `ruleArticles` TEXT, `ruleNextPage` TEXT, `ruleTitle` TEXT, `rulePubDate` TEXT, `ruleDescription` TEXT, `ruleImage` TEXT, `ruleLink` TEXT, `ruleContent` TEXT, `style` TEXT, `header` TEXT, `enableJs` INTEGER NOT NULL, `loadWithBaseUrl` INTEGER NOT NULL, `customOrder` INTEGER NOT NULL, PRIMARY KEY(`sourceUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"sourceUrl\",\n            \"columnName\": \"sourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceName\",\n            \"columnName\": \"sourceName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceIcon\",\n            \"columnName\": \"sourceIcon\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceGroup\",\n            \"columnName\": \"sourceGroup\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"sourceComment\",\n            \"columnName\": \"sourceComment\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enabled\",\n            \"columnName\": \"enabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sortUrl\",\n            \"columnName\": \"sortUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"singleUrl\",\n            \"columnName\": \"singleUrl\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"articleStyle\",\n            \"columnName\": \"articleStyle\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"ruleArticles\",\n            \"columnName\": \"ruleArticles\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleNextPage\",\n            \"columnName\": \"ruleNextPage\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleTitle\",\n            \"columnName\": \"ruleTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"rulePubDate\",\n            \"columnName\": \"rulePubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleDescription\",\n            \"columnName\": \"ruleDescription\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleImage\",\n            \"columnName\": \"ruleImage\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleLink\",\n            \"columnName\": \"ruleLink\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleContent\",\n            \"columnName\": \"ruleContent\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"style\",\n            \"columnName\": \"style\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"header\",\n            \"columnName\": \"header\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enableJs\",\n            \"columnName\": \"enableJs\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"loadWithBaseUrl\",\n            \"columnName\": \"loadWithBaseUrl\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"sourceUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_rssSources_sourceUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"sourceUrl\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_rssSources_sourceUrl` ON `${TABLE_NAME}` (`sourceUrl`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"bookmarks\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`time` INTEGER NOT NULL, `bookUrl` TEXT NOT NULL, `bookName` TEXT NOT NULL, `bookAuthor` TEXT NOT NULL, `chapterIndex` INTEGER NOT NULL, `chapterPos` INTEGER NOT NULL, `chapterName` TEXT NOT NULL, `bookText` TEXT NOT NULL, `content` TEXT NOT NULL, PRIMARY KEY(`time`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"time\",\n            \"columnName\": \"time\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookName\",\n            \"columnName\": \"bookName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookAuthor\",\n            \"columnName\": \"bookAuthor\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterIndex\",\n            \"columnName\": \"chapterIndex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterPos\",\n            \"columnName\": \"chapterPos\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterName\",\n            \"columnName\": \"chapterName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookText\",\n            \"columnName\": \"bookText\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"time\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_bookmarks_time\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"time\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_bookmarks_time` ON `${TABLE_NAME}` (`time`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssArticles\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`origin` TEXT NOT NULL, `sort` TEXT NOT NULL, `title` TEXT NOT NULL, `order` INTEGER NOT NULL, `link` TEXT NOT NULL, `pubDate` TEXT, `description` TEXT, `content` TEXT, `image` TEXT, `read` INTEGER NOT NULL, `variable` TEXT, PRIMARY KEY(`origin`, `link`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sort\",\n            \"columnName\": \"sort\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"link\",\n            \"columnName\": \"link\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"pubDate\",\n            \"columnName\": \"pubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"description\",\n            \"columnName\": \"description\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"image\",\n            \"columnName\": \"image\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"read\",\n            \"columnName\": \"read\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"origin\",\n            \"link\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssReadRecords\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`record` TEXT NOT NULL, `read` INTEGER NOT NULL, PRIMARY KEY(`record`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"record\",\n            \"columnName\": \"record\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"read\",\n            \"columnName\": \"read\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"record\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssStars\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`origin` TEXT NOT NULL, `sort` TEXT NOT NULL, `title` TEXT NOT NULL, `starTime` INTEGER NOT NULL, `link` TEXT NOT NULL, `pubDate` TEXT, `description` TEXT, `content` TEXT, `image` TEXT, `variable` TEXT, PRIMARY KEY(`origin`, `link`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sort\",\n            \"columnName\": \"sort\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"starTime\",\n            \"columnName\": \"starTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"link\",\n            \"columnName\": \"link\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"pubDate\",\n            \"columnName\": \"pubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"description\",\n            \"columnName\": \"description\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"image\",\n            \"columnName\": \"image\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"origin\",\n            \"link\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"txtTocRules\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `rule` TEXT NOT NULL, `serialNumber` INTEGER NOT NULL, `enable` INTEGER NOT NULL, PRIMARY KEY(`id`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"rule\",\n            \"columnName\": \"rule\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"serialNumber\",\n            \"columnName\": \"serialNumber\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enable\",\n            \"columnName\": \"enable\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"id\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"readRecord\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`androidId` TEXT NOT NULL, `bookName` TEXT NOT NULL, `readTime` INTEGER NOT NULL, PRIMARY KEY(`androidId`, `bookName`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"androidId\",\n            \"columnName\": \"androidId\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookName\",\n            \"columnName\": \"bookName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"readTime\",\n            \"columnName\": \"readTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"androidId\",\n            \"bookName\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"httpTTS\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `url` TEXT NOT NULL, PRIMARY KEY(`id`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"id\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"caches\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`key` TEXT NOT NULL, `value` TEXT, `deadline` INTEGER NOT NULL, PRIMARY KEY(`key`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"key\",\n            \"columnName\": \"key\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"value\",\n            \"columnName\": \"value\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"deadline\",\n            \"columnName\": \"deadline\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"key\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_caches_key\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"key\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_caches_key` ON `${TABLE_NAME}` (`key`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"ruleSubs\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `url` TEXT NOT NULL, `type` INTEGER NOT NULL, `customOrder` INTEGER NOT NULL, `autoUpdate` INTEGER NOT NULL, `update` INTEGER NOT NULL, PRIMARY KEY(`id`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"autoUpdate\",\n            \"columnName\": \"autoUpdate\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"update\",\n            \"columnName\": \"update\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"id\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      }\n    ],\n    \"views\": [],\n    \"setupQueries\": [\n      \"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)\",\n      \"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '85f1e7146f650af82aac6f9137eff815')\"\n    ]\n  }\n}"
  },
  {
    "path": "app/schemas/io.legado.app.data.AppDatabase/3.json",
    "content": "{\n  \"formatVersion\": 1,\n  \"database\": {\n    \"version\": 3,\n    \"identityHash\": \"a3ccd8882307290a450c49e09a4435f6\",\n    \"entities\": [\n      {\n        \"tableName\": \"books\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookUrl` TEXT NOT NULL, `tocUrl` TEXT NOT NULL, `origin` TEXT NOT NULL, `originName` TEXT NOT NULL, `name` TEXT NOT NULL, `author` TEXT NOT NULL, `kind` TEXT, `customTag` TEXT, `coverUrl` TEXT, `customCoverUrl` TEXT, `intro` TEXT, `customIntro` TEXT, `charset` TEXT, `type` INTEGER NOT NULL, `group` INTEGER NOT NULL, `latestChapterTitle` TEXT, `latestChapterTime` INTEGER NOT NULL, `lastCheckTime` INTEGER NOT NULL, `lastCheckCount` INTEGER NOT NULL, `totalChapterNum` INTEGER NOT NULL, `durChapterTitle` TEXT, `durChapterIndex` INTEGER NOT NULL, `durChapterPos` INTEGER NOT NULL, `durChapterTime` INTEGER NOT NULL, `wordCount` TEXT, `canUpdate` INTEGER NOT NULL, `order` INTEGER NOT NULL, `originOrder` INTEGER NOT NULL, `useReplaceRule` INTEGER NOT NULL, `variable` TEXT, PRIMARY KEY(`bookUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"tocUrl\",\n            \"columnName\": \"tocUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"originName\",\n            \"columnName\": \"originName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"author\",\n            \"columnName\": \"author\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"kind\",\n            \"columnName\": \"kind\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customTag\",\n            \"columnName\": \"customTag\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"coverUrl\",\n            \"columnName\": \"coverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customCoverUrl\",\n            \"columnName\": \"customCoverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"intro\",\n            \"columnName\": \"intro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customIntro\",\n            \"columnName\": \"customIntro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"charset\",\n            \"columnName\": \"charset\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"group\",\n            \"columnName\": \"group\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"latestChapterTitle\",\n            \"columnName\": \"latestChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"latestChapterTime\",\n            \"columnName\": \"latestChapterTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"lastCheckTime\",\n            \"columnName\": \"lastCheckTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"lastCheckCount\",\n            \"columnName\": \"lastCheckCount\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"totalChapterNum\",\n            \"columnName\": \"totalChapterNum\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"durChapterTitle\",\n            \"columnName\": \"durChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"durChapterIndex\",\n            \"columnName\": \"durChapterIndex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"durChapterPos\",\n            \"columnName\": \"durChapterPos\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"durChapterTime\",\n            \"columnName\": \"durChapterTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"wordCount\",\n            \"columnName\": \"wordCount\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"canUpdate\",\n            \"columnName\": \"canUpdate\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"originOrder\",\n            \"columnName\": \"originOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"useReplaceRule\",\n            \"columnName\": \"useReplaceRule\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"bookUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_books_bookUrl\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"bookUrl\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_books_bookUrl` ON `${TABLE_NAME}` (`bookUrl`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"book_groups\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`groupId` INTEGER NOT NULL, `groupName` TEXT NOT NULL, `order` INTEGER NOT NULL, PRIMARY KEY(`groupId`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"groupId\",\n            \"columnName\": \"groupId\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"groupName\",\n            \"columnName\": \"groupName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"groupId\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"book_sources\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookSourceName` TEXT NOT NULL, `bookSourceGroup` TEXT, `bookSourceUrl` TEXT NOT NULL, `bookSourceType` INTEGER NOT NULL, `bookUrlPattern` TEXT, `customOrder` INTEGER NOT NULL, `enabled` INTEGER NOT NULL, `enabledExplore` INTEGER NOT NULL, `header` TEXT, `loginUrl` TEXT, `lastUpdateTime` INTEGER NOT NULL, `weight` INTEGER NOT NULL, `exploreUrl` TEXT, `ruleExplore` TEXT, `searchUrl` TEXT, `ruleSearch` TEXT, `ruleBookInfo` TEXT, `ruleToc` TEXT, `ruleContent` TEXT, PRIMARY KEY(`bookSourceUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookSourceName\",\n            \"columnName\": \"bookSourceName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookSourceGroup\",\n            \"columnName\": \"bookSourceGroup\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"bookSourceUrl\",\n            \"columnName\": \"bookSourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookSourceType\",\n            \"columnName\": \"bookSourceType\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookUrlPattern\",\n            \"columnName\": \"bookUrlPattern\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enabled\",\n            \"columnName\": \"enabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enabledExplore\",\n            \"columnName\": \"enabledExplore\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"header\",\n            \"columnName\": \"header\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUrl\",\n            \"columnName\": \"loginUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"lastUpdateTime\",\n            \"columnName\": \"lastUpdateTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"weight\",\n            \"columnName\": \"weight\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"exploreUrl\",\n            \"columnName\": \"exploreUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleExplore\",\n            \"columnName\": \"ruleExplore\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"searchUrl\",\n            \"columnName\": \"searchUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleSearch\",\n            \"columnName\": \"ruleSearch\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleBookInfo\",\n            \"columnName\": \"ruleBookInfo\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleToc\",\n            \"columnName\": \"ruleToc\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleContent\",\n            \"columnName\": \"ruleContent\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"bookSourceUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_book_sources_bookSourceUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"bookSourceUrl\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_book_sources_bookSourceUrl` ON `${TABLE_NAME}` (`bookSourceUrl`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"chapters\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`url` TEXT NOT NULL, `title` TEXT NOT NULL, `bookUrl` TEXT NOT NULL, `index` INTEGER NOT NULL, `resourceUrl` TEXT, `tag` TEXT, `start` INTEGER, `end` INTEGER, `variable` TEXT, PRIMARY KEY(`url`, `bookUrl`), FOREIGN KEY(`bookUrl`) REFERENCES `books`(`bookUrl`) ON UPDATE NO ACTION ON DELETE CASCADE )\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"index\",\n            \"columnName\": \"index\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"resourceUrl\",\n            \"columnName\": \"resourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"tag\",\n            \"columnName\": \"tag\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"start\",\n            \"columnName\": \"start\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"end\",\n            \"columnName\": \"end\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"url\",\n            \"bookUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_chapters_bookUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"bookUrl\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_chapters_bookUrl` ON `${TABLE_NAME}` (`bookUrl`)\"\n          },\n          {\n            \"name\": \"index_chapters_bookUrl_index\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"bookUrl\",\n              \"index\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_chapters_bookUrl_index` ON `${TABLE_NAME}` (`bookUrl`, `index`)\"\n          }\n        ],\n        \"foreignKeys\": [\n          {\n            \"table\": \"books\",\n            \"onDelete\": \"CASCADE\",\n            \"onUpdate\": \"NO ACTION\",\n            \"columns\": [\n              \"bookUrl\"\n            ],\n            \"referencedColumns\": [\n              \"bookUrl\"\n            ]\n          }\n        ]\n      },\n      {\n        \"tableName\": \"replace_rules\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL, `group` TEXT, `pattern` TEXT NOT NULL, `replacement` TEXT NOT NULL, `scope` TEXT, `isEnabled` INTEGER NOT NULL, `isRegex` INTEGER NOT NULL, `sortOrder` INTEGER NOT NULL)\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"group\",\n            \"columnName\": \"group\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"pattern\",\n            \"columnName\": \"pattern\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"replacement\",\n            \"columnName\": \"replacement\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"scope\",\n            \"columnName\": \"scope\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"isEnabled\",\n            \"columnName\": \"isEnabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"isRegex\",\n            \"columnName\": \"isRegex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"sortOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"id\"\n          ],\n          \"autoGenerate\": true\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_replace_rules_id\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"id\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_replace_rules_id` ON `${TABLE_NAME}` (`id`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"searchBooks\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookUrl` TEXT NOT NULL, `origin` TEXT NOT NULL, `originName` TEXT NOT NULL, `type` INTEGER NOT NULL, `name` TEXT NOT NULL, `author` TEXT NOT NULL, `kind` TEXT, `coverUrl` TEXT, `intro` TEXT, `wordCount` TEXT, `latestChapterTitle` TEXT, `tocUrl` TEXT NOT NULL, `time` INTEGER NOT NULL, `variable` TEXT, `originOrder` INTEGER NOT NULL, PRIMARY KEY(`bookUrl`), FOREIGN KEY(`origin`) REFERENCES `book_sources`(`bookSourceUrl`) ON UPDATE NO ACTION ON DELETE CASCADE )\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"originName\",\n            \"columnName\": \"originName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"author\",\n            \"columnName\": \"author\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"kind\",\n            \"columnName\": \"kind\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"coverUrl\",\n            \"columnName\": \"coverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"intro\",\n            \"columnName\": \"intro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"wordCount\",\n            \"columnName\": \"wordCount\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"latestChapterTitle\",\n            \"columnName\": \"latestChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"tocUrl\",\n            \"columnName\": \"tocUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"time\",\n            \"columnName\": \"time\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"originOrder\",\n            \"columnName\": \"originOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"bookUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_searchBooks_bookUrl\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"bookUrl\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_searchBooks_bookUrl` ON `${TABLE_NAME}` (`bookUrl`)\"\n          },\n          {\n            \"name\": \"index_searchBooks_origin\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"origin\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_searchBooks_origin` ON `${TABLE_NAME}` (`origin`)\"\n          }\n        ],\n        \"foreignKeys\": [\n          {\n            \"table\": \"book_sources\",\n            \"onDelete\": \"CASCADE\",\n            \"onUpdate\": \"NO ACTION\",\n            \"columns\": [\n              \"origin\"\n            ],\n            \"referencedColumns\": [\n              \"bookSourceUrl\"\n            ]\n          }\n        ]\n      },\n      {\n        \"tableName\": \"search_keywords\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`word` TEXT NOT NULL, `usage` INTEGER NOT NULL, `lastUseTime` INTEGER NOT NULL, PRIMARY KEY(`word`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"word\",\n            \"columnName\": \"word\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"usage\",\n            \"columnName\": \"usage\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"lastUseTime\",\n            \"columnName\": \"lastUseTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"word\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_search_keywords_word\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"word\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_search_keywords_word` ON `${TABLE_NAME}` (`word`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"cookies\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`url` TEXT NOT NULL, `cookie` TEXT NOT NULL, PRIMARY KEY(`url`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"cookie\",\n            \"columnName\": \"cookie\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"url\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_cookies_url\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"url\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_cookies_url` ON `${TABLE_NAME}` (`url`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssSources\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`sourceUrl` TEXT NOT NULL, `sourceName` TEXT NOT NULL, `sourceIcon` TEXT NOT NULL, `sourceGroup` TEXT, `enabled` INTEGER NOT NULL, `ruleArticles` TEXT, `ruleNextPage` TEXT, `ruleTitle` TEXT, `rulePubDate` TEXT, `ruleDescription` TEXT, `ruleImage` TEXT, `ruleLink` TEXT, `ruleContent` TEXT, `header` TEXT, `enableJs` INTEGER NOT NULL, `loadWithBaseUrl` INTEGER NOT NULL, `customOrder` INTEGER NOT NULL, PRIMARY KEY(`sourceUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"sourceUrl\",\n            \"columnName\": \"sourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceName\",\n            \"columnName\": \"sourceName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceIcon\",\n            \"columnName\": \"sourceIcon\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceGroup\",\n            \"columnName\": \"sourceGroup\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enabled\",\n            \"columnName\": \"enabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"ruleArticles\",\n            \"columnName\": \"ruleArticles\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleNextPage\",\n            \"columnName\": \"ruleNextPage\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleTitle\",\n            \"columnName\": \"ruleTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"rulePubDate\",\n            \"columnName\": \"rulePubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleDescription\",\n            \"columnName\": \"ruleDescription\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleImage\",\n            \"columnName\": \"ruleImage\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleLink\",\n            \"columnName\": \"ruleLink\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleContent\",\n            \"columnName\": \"ruleContent\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"header\",\n            \"columnName\": \"header\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enableJs\",\n            \"columnName\": \"enableJs\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"loadWithBaseUrl\",\n            \"columnName\": \"loadWithBaseUrl\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"sourceUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_rssSources_sourceUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"sourceUrl\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_rssSources_sourceUrl` ON `${TABLE_NAME}` (`sourceUrl`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"bookmarks\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`time` INTEGER NOT NULL, `bookUrl` TEXT NOT NULL, `bookName` TEXT NOT NULL, `chapterName` TEXT NOT NULL, `chapterIndex` INTEGER NOT NULL, `pageIndex` INTEGER NOT NULL, `content` TEXT NOT NULL, PRIMARY KEY(`time`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"time\",\n            \"columnName\": \"time\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookName\",\n            \"columnName\": \"bookName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterName\",\n            \"columnName\": \"chapterName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterIndex\",\n            \"columnName\": \"chapterIndex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"pageIndex\",\n            \"columnName\": \"pageIndex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"time\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_bookmarks_bookUrl\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"bookUrl\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_bookmarks_bookUrl` ON `${TABLE_NAME}` (`bookUrl`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssArticles\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`origin` TEXT NOT NULL, `title` TEXT NOT NULL, `order` INTEGER NOT NULL, `link` TEXT NOT NULL, `pubDate` TEXT, `description` TEXT, `content` TEXT, `image` TEXT, `read` INTEGER NOT NULL, `star` INTEGER NOT NULL, PRIMARY KEY(`origin`, `link`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"link\",\n            \"columnName\": \"link\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"pubDate\",\n            \"columnName\": \"pubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"description\",\n            \"columnName\": \"description\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"image\",\n            \"columnName\": \"image\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"read\",\n            \"columnName\": \"read\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"star\",\n            \"columnName\": \"star\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"origin\",\n            \"link\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      }\n    ],\n    \"views\": [],\n    \"setupQueries\": [\n      \"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)\",\n      \"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'a3ccd8882307290a450c49e09a4435f6')\"\n    ]\n  }\n}"
  },
  {
    "path": "app/schemas/io.legado.app.data.AppDatabase/30.json",
    "content": "{\n  \"formatVersion\": 1,\n  \"database\": {\n    \"version\": 30,\n    \"identityHash\": \"d9c8ef97ef4ffe0c1dbd57ca74bc4de4\",\n    \"entities\": [\n      {\n        \"tableName\": \"books\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookUrl` TEXT NOT NULL, `tocUrl` TEXT NOT NULL, `origin` TEXT NOT NULL, `originName` TEXT NOT NULL, `name` TEXT NOT NULL, `author` TEXT NOT NULL, `kind` TEXT, `customTag` TEXT, `coverUrl` TEXT, `customCoverUrl` TEXT, `intro` TEXT, `customIntro` TEXT, `charset` TEXT, `type` INTEGER NOT NULL, `group` INTEGER NOT NULL, `latestChapterTitle` TEXT, `latestChapterTime` INTEGER NOT NULL, `lastCheckTime` INTEGER NOT NULL, `lastCheckCount` INTEGER NOT NULL, `totalChapterNum` INTEGER NOT NULL, `durChapterTitle` TEXT, `durChapterIndex` INTEGER NOT NULL, `durChapterPos` INTEGER NOT NULL, `durChapterTime` INTEGER NOT NULL, `wordCount` TEXT, `canUpdate` INTEGER NOT NULL, `order` INTEGER NOT NULL, `originOrder` INTEGER NOT NULL, `variable` TEXT, `readConfig` TEXT, PRIMARY KEY(`bookUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"tocUrl\",\n            \"columnName\": \"tocUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"originName\",\n            \"columnName\": \"originName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"author\",\n            \"columnName\": \"author\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"kind\",\n            \"columnName\": \"kind\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customTag\",\n            \"columnName\": \"customTag\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"coverUrl\",\n            \"columnName\": \"coverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customCoverUrl\",\n            \"columnName\": \"customCoverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"intro\",\n            \"columnName\": \"intro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customIntro\",\n            \"columnName\": \"customIntro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"charset\",\n            \"columnName\": \"charset\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"group\",\n            \"columnName\": \"group\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"latestChapterTitle\",\n            \"columnName\": \"latestChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"latestChapterTime\",\n            \"columnName\": \"latestChapterTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"lastCheckTime\",\n            \"columnName\": \"lastCheckTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"lastCheckCount\",\n            \"columnName\": \"lastCheckCount\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"totalChapterNum\",\n            \"columnName\": \"totalChapterNum\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"durChapterTitle\",\n            \"columnName\": \"durChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"durChapterIndex\",\n            \"columnName\": \"durChapterIndex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"durChapterPos\",\n            \"columnName\": \"durChapterPos\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"durChapterTime\",\n            \"columnName\": \"durChapterTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"wordCount\",\n            \"columnName\": \"wordCount\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"canUpdate\",\n            \"columnName\": \"canUpdate\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"originOrder\",\n            \"columnName\": \"originOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"readConfig\",\n            \"columnName\": \"readConfig\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"bookUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_books_name_author\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"name\",\n              \"author\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_books_name_author` ON `${TABLE_NAME}` (`name`, `author`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"book_groups\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`groupId` INTEGER NOT NULL, `groupName` TEXT NOT NULL, `order` INTEGER NOT NULL, `show` INTEGER NOT NULL, PRIMARY KEY(`groupId`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"groupId\",\n            \"columnName\": \"groupId\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"groupName\",\n            \"columnName\": \"groupName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"show\",\n            \"columnName\": \"show\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"groupId\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"book_sources\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookSourceName` TEXT NOT NULL, `bookSourceGroup` TEXT, `bookSourceUrl` TEXT NOT NULL, `bookSourceType` INTEGER NOT NULL, `bookUrlPattern` TEXT, `customOrder` INTEGER NOT NULL, `enabled` INTEGER NOT NULL, `enabledExplore` INTEGER NOT NULL, `header` TEXT, `loginUrl` TEXT, `bookSourceComment` TEXT, `lastUpdateTime` INTEGER NOT NULL, `weight` INTEGER NOT NULL, `exploreUrl` TEXT, `ruleExplore` TEXT, `searchUrl` TEXT, `ruleSearch` TEXT, `ruleBookInfo` TEXT, `ruleToc` TEXT, `ruleContent` TEXT, PRIMARY KEY(`bookSourceUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookSourceName\",\n            \"columnName\": \"bookSourceName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookSourceGroup\",\n            \"columnName\": \"bookSourceGroup\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"bookSourceUrl\",\n            \"columnName\": \"bookSourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookSourceType\",\n            \"columnName\": \"bookSourceType\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookUrlPattern\",\n            \"columnName\": \"bookUrlPattern\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enabled\",\n            \"columnName\": \"enabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enabledExplore\",\n            \"columnName\": \"enabledExplore\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"header\",\n            \"columnName\": \"header\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUrl\",\n            \"columnName\": \"loginUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"bookSourceComment\",\n            \"columnName\": \"bookSourceComment\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"lastUpdateTime\",\n            \"columnName\": \"lastUpdateTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"weight\",\n            \"columnName\": \"weight\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"exploreUrl\",\n            \"columnName\": \"exploreUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleExplore\",\n            \"columnName\": \"ruleExplore\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"searchUrl\",\n            \"columnName\": \"searchUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleSearch\",\n            \"columnName\": \"ruleSearch\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleBookInfo\",\n            \"columnName\": \"ruleBookInfo\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleToc\",\n            \"columnName\": \"ruleToc\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleContent\",\n            \"columnName\": \"ruleContent\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"bookSourceUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_book_sources_bookSourceUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"bookSourceUrl\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_book_sources_bookSourceUrl` ON `${TABLE_NAME}` (`bookSourceUrl`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"chapters\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`url` TEXT NOT NULL, `title` TEXT NOT NULL, `baseUrl` TEXT NOT NULL, `bookUrl` TEXT NOT NULL, `index` INTEGER NOT NULL, `resourceUrl` TEXT, `tag` TEXT, `start` INTEGER, `end` INTEGER, `startFragmentId` TEXT, `endFragmentId` TEXT, `variable` TEXT, PRIMARY KEY(`url`, `bookUrl`), FOREIGN KEY(`bookUrl`) REFERENCES `books`(`bookUrl`) ON UPDATE NO ACTION ON DELETE CASCADE )\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"baseUrl\",\n            \"columnName\": \"baseUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"index\",\n            \"columnName\": \"index\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"resourceUrl\",\n            \"columnName\": \"resourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"tag\",\n            \"columnName\": \"tag\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"start\",\n            \"columnName\": \"start\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"end\",\n            \"columnName\": \"end\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"startFragmentId\",\n            \"columnName\": \"startFragmentId\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"endFragmentId\",\n            \"columnName\": \"endFragmentId\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"url\",\n            \"bookUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_chapters_bookUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"bookUrl\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_chapters_bookUrl` ON `${TABLE_NAME}` (`bookUrl`)\"\n          },\n          {\n            \"name\": \"index_chapters_bookUrl_index\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"bookUrl\",\n              \"index\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_chapters_bookUrl_index` ON `${TABLE_NAME}` (`bookUrl`, `index`)\"\n          }\n        ],\n        \"foreignKeys\": [\n          {\n            \"table\": \"books\",\n            \"onDelete\": \"CASCADE\",\n            \"onUpdate\": \"NO ACTION\",\n            \"columns\": [\n              \"bookUrl\"\n            ],\n            \"referencedColumns\": [\n              \"bookUrl\"\n            ]\n          }\n        ]\n      },\n      {\n        \"tableName\": \"replace_rules\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL, `group` TEXT, `pattern` TEXT NOT NULL, `replacement` TEXT NOT NULL, `scope` TEXT, `isEnabled` INTEGER NOT NULL, `isRegex` INTEGER NOT NULL, `sortOrder` INTEGER NOT NULL)\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"group\",\n            \"columnName\": \"group\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"pattern\",\n            \"columnName\": \"pattern\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"replacement\",\n            \"columnName\": \"replacement\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"scope\",\n            \"columnName\": \"scope\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"isEnabled\",\n            \"columnName\": \"isEnabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"isRegex\",\n            \"columnName\": \"isRegex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"sortOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"id\"\n          ],\n          \"autoGenerate\": true\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_replace_rules_id\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"id\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_replace_rules_id` ON `${TABLE_NAME}` (`id`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"searchBooks\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookUrl` TEXT NOT NULL, `origin` TEXT NOT NULL, `originName` TEXT NOT NULL, `type` INTEGER NOT NULL, `name` TEXT NOT NULL, `author` TEXT NOT NULL, `kind` TEXT, `coverUrl` TEXT, `intro` TEXT, `wordCount` TEXT, `latestChapterTitle` TEXT, `tocUrl` TEXT NOT NULL, `time` INTEGER NOT NULL, `variable` TEXT, `originOrder` INTEGER NOT NULL, PRIMARY KEY(`bookUrl`), FOREIGN KEY(`origin`) REFERENCES `book_sources`(`bookSourceUrl`) ON UPDATE NO ACTION ON DELETE CASCADE )\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"originName\",\n            \"columnName\": \"originName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"author\",\n            \"columnName\": \"author\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"kind\",\n            \"columnName\": \"kind\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"coverUrl\",\n            \"columnName\": \"coverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"intro\",\n            \"columnName\": \"intro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"wordCount\",\n            \"columnName\": \"wordCount\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"latestChapterTitle\",\n            \"columnName\": \"latestChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"tocUrl\",\n            \"columnName\": \"tocUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"time\",\n            \"columnName\": \"time\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"originOrder\",\n            \"columnName\": \"originOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"bookUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_searchBooks_bookUrl\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"bookUrl\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_searchBooks_bookUrl` ON `${TABLE_NAME}` (`bookUrl`)\"\n          },\n          {\n            \"name\": \"index_searchBooks_origin\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"origin\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_searchBooks_origin` ON `${TABLE_NAME}` (`origin`)\"\n          }\n        ],\n        \"foreignKeys\": [\n          {\n            \"table\": \"book_sources\",\n            \"onDelete\": \"CASCADE\",\n            \"onUpdate\": \"NO ACTION\",\n            \"columns\": [\n              \"origin\"\n            ],\n            \"referencedColumns\": [\n              \"bookSourceUrl\"\n            ]\n          }\n        ]\n      },\n      {\n        \"tableName\": \"search_keywords\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`word` TEXT NOT NULL, `usage` INTEGER NOT NULL, `lastUseTime` INTEGER NOT NULL, PRIMARY KEY(`word`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"word\",\n            \"columnName\": \"word\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"usage\",\n            \"columnName\": \"usage\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"lastUseTime\",\n            \"columnName\": \"lastUseTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"word\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_search_keywords_word\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"word\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_search_keywords_word` ON `${TABLE_NAME}` (`word`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"cookies\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`url` TEXT NOT NULL, `cookie` TEXT NOT NULL, PRIMARY KEY(`url`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"cookie\",\n            \"columnName\": \"cookie\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"url\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_cookies_url\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"url\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_cookies_url` ON `${TABLE_NAME}` (`url`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssSources\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`sourceUrl` TEXT NOT NULL, `sourceName` TEXT NOT NULL, `sourceIcon` TEXT NOT NULL, `sourceGroup` TEXT, `sourceComment` TEXT, `enabled` INTEGER NOT NULL, `sortUrl` TEXT, `singleUrl` INTEGER NOT NULL, `articleStyle` INTEGER NOT NULL, `ruleArticles` TEXT, `ruleNextPage` TEXT, `ruleTitle` TEXT, `rulePubDate` TEXT, `ruleDescription` TEXT, `ruleImage` TEXT, `ruleLink` TEXT, `ruleContent` TEXT, `style` TEXT, `header` TEXT, `enableJs` INTEGER NOT NULL, `loadWithBaseUrl` INTEGER NOT NULL, `customOrder` INTEGER NOT NULL, PRIMARY KEY(`sourceUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"sourceUrl\",\n            \"columnName\": \"sourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceName\",\n            \"columnName\": \"sourceName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceIcon\",\n            \"columnName\": \"sourceIcon\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceGroup\",\n            \"columnName\": \"sourceGroup\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"sourceComment\",\n            \"columnName\": \"sourceComment\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enabled\",\n            \"columnName\": \"enabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sortUrl\",\n            \"columnName\": \"sortUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"singleUrl\",\n            \"columnName\": \"singleUrl\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"articleStyle\",\n            \"columnName\": \"articleStyle\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"ruleArticles\",\n            \"columnName\": \"ruleArticles\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleNextPage\",\n            \"columnName\": \"ruleNextPage\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleTitle\",\n            \"columnName\": \"ruleTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"rulePubDate\",\n            \"columnName\": \"rulePubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleDescription\",\n            \"columnName\": \"ruleDescription\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleImage\",\n            \"columnName\": \"ruleImage\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleLink\",\n            \"columnName\": \"ruleLink\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleContent\",\n            \"columnName\": \"ruleContent\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"style\",\n            \"columnName\": \"style\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"header\",\n            \"columnName\": \"header\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enableJs\",\n            \"columnName\": \"enableJs\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"loadWithBaseUrl\",\n            \"columnName\": \"loadWithBaseUrl\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"sourceUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_rssSources_sourceUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"sourceUrl\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_rssSources_sourceUrl` ON `${TABLE_NAME}` (`sourceUrl`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"bookmarks\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`time` INTEGER NOT NULL, `bookUrl` TEXT NOT NULL, `bookName` TEXT NOT NULL, `bookAuthor` TEXT NOT NULL, `chapterIndex` INTEGER NOT NULL, `chapterPos` INTEGER NOT NULL, `chapterName` TEXT NOT NULL, `bookText` TEXT NOT NULL, `content` TEXT NOT NULL, PRIMARY KEY(`time`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"time\",\n            \"columnName\": \"time\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookName\",\n            \"columnName\": \"bookName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookAuthor\",\n            \"columnName\": \"bookAuthor\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterIndex\",\n            \"columnName\": \"chapterIndex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterPos\",\n            \"columnName\": \"chapterPos\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterName\",\n            \"columnName\": \"chapterName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookText\",\n            \"columnName\": \"bookText\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"time\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_bookmarks_time\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"time\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_bookmarks_time` ON `${TABLE_NAME}` (`time`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssArticles\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`origin` TEXT NOT NULL, `sort` TEXT NOT NULL, `title` TEXT NOT NULL, `order` INTEGER NOT NULL, `link` TEXT NOT NULL, `pubDate` TEXT, `description` TEXT, `content` TEXT, `image` TEXT, `read` INTEGER NOT NULL, `variable` TEXT, PRIMARY KEY(`origin`, `link`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sort\",\n            \"columnName\": \"sort\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"link\",\n            \"columnName\": \"link\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"pubDate\",\n            \"columnName\": \"pubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"description\",\n            \"columnName\": \"description\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"image\",\n            \"columnName\": \"image\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"read\",\n            \"columnName\": \"read\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"origin\",\n            \"link\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssReadRecords\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`record` TEXT NOT NULL, `read` INTEGER NOT NULL, PRIMARY KEY(`record`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"record\",\n            \"columnName\": \"record\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"read\",\n            \"columnName\": \"read\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"record\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssStars\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`origin` TEXT NOT NULL, `sort` TEXT NOT NULL, `title` TEXT NOT NULL, `starTime` INTEGER NOT NULL, `link` TEXT NOT NULL, `pubDate` TEXT, `description` TEXT, `content` TEXT, `image` TEXT, `variable` TEXT, PRIMARY KEY(`origin`, `link`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sort\",\n            \"columnName\": \"sort\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"starTime\",\n            \"columnName\": \"starTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"link\",\n            \"columnName\": \"link\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"pubDate\",\n            \"columnName\": \"pubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"description\",\n            \"columnName\": \"description\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"image\",\n            \"columnName\": \"image\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"origin\",\n            \"link\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"txtTocRules\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `rule` TEXT NOT NULL, `serialNumber` INTEGER NOT NULL, `enable` INTEGER NOT NULL, PRIMARY KEY(`id`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"rule\",\n            \"columnName\": \"rule\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"serialNumber\",\n            \"columnName\": \"serialNumber\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enable\",\n            \"columnName\": \"enable\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"id\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"readRecord\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`deviceId` TEXT NOT NULL, `bookName` TEXT NOT NULL, `readTime` INTEGER NOT NULL, PRIMARY KEY(`deviceId`, `bookName`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"deviceId\",\n            \"columnName\": \"deviceId\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookName\",\n            \"columnName\": \"bookName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"readTime\",\n            \"columnName\": \"readTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"deviceId\",\n            \"bookName\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"httpTTS\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `url` TEXT NOT NULL, PRIMARY KEY(`id`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"id\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"caches\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`key` TEXT NOT NULL, `value` TEXT, `deadline` INTEGER NOT NULL, PRIMARY KEY(`key`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"key\",\n            \"columnName\": \"key\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"value\",\n            \"columnName\": \"value\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"deadline\",\n            \"columnName\": \"deadline\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"key\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_caches_key\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"key\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_caches_key` ON `${TABLE_NAME}` (`key`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"ruleSubs\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `url` TEXT NOT NULL, `type` INTEGER NOT NULL, `customOrder` INTEGER NOT NULL, `autoUpdate` INTEGER NOT NULL, `update` INTEGER NOT NULL, PRIMARY KEY(`id`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"autoUpdate\",\n            \"columnName\": \"autoUpdate\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"update\",\n            \"columnName\": \"update\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"id\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"epubChapters\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookUrl` TEXT NOT NULL, `href` TEXT NOT NULL, `parentHref` TEXT, PRIMARY KEY(`bookUrl`, `href`), FOREIGN KEY(`bookUrl`) REFERENCES `books`(`bookUrl`) ON UPDATE NO ACTION ON DELETE CASCADE )\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"href\",\n            \"columnName\": \"href\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"parentHref\",\n            \"columnName\": \"parentHref\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"bookUrl\",\n            \"href\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_epubChapters_bookUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"bookUrl\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_epubChapters_bookUrl` ON `${TABLE_NAME}` (`bookUrl`)\"\n          },\n          {\n            \"name\": \"index_epubChapters_bookUrl_href\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"bookUrl\",\n              \"href\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_epubChapters_bookUrl_href` ON `${TABLE_NAME}` (`bookUrl`, `href`)\"\n          }\n        ],\n        \"foreignKeys\": [\n          {\n            \"table\": \"books\",\n            \"onDelete\": \"CASCADE\",\n            \"onUpdate\": \"NO ACTION\",\n            \"columns\": [\n              \"bookUrl\"\n            ],\n            \"referencedColumns\": [\n              \"bookUrl\"\n            ]\n          }\n        ]\n      }\n    ],\n    \"views\": [],\n    \"setupQueries\": [\n      \"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)\",\n      \"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'd9c8ef97ef4ffe0c1dbd57ca74bc4de4')\"\n    ]\n  }\n}"
  },
  {
    "path": "app/schemas/io.legado.app.data.AppDatabase/31.json",
    "content": "{\n  \"formatVersion\": 1,\n  \"database\": {\n    \"version\": 31,\n    \"identityHash\": \"d1c390e708a1e89c7d016cdd2e0b2e88\",\n    \"entities\": [\n      {\n        \"tableName\": \"books\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookUrl` TEXT NOT NULL, `tocUrl` TEXT NOT NULL, `origin` TEXT NOT NULL, `originName` TEXT NOT NULL, `name` TEXT NOT NULL, `author` TEXT NOT NULL, `kind` TEXT, `customTag` TEXT, `coverUrl` TEXT, `customCoverUrl` TEXT, `intro` TEXT, `customIntro` TEXT, `charset` TEXT, `type` INTEGER NOT NULL, `group` INTEGER NOT NULL, `latestChapterTitle` TEXT, `latestChapterTime` INTEGER NOT NULL, `lastCheckTime` INTEGER NOT NULL, `lastCheckCount` INTEGER NOT NULL, `totalChapterNum` INTEGER NOT NULL, `durChapterTitle` TEXT, `durChapterIndex` INTEGER NOT NULL, `durChapterPos` INTEGER NOT NULL, `durChapterTime` INTEGER NOT NULL, `wordCount` TEXT, `canUpdate` INTEGER NOT NULL, `order` INTEGER NOT NULL, `originOrder` INTEGER NOT NULL, `variable` TEXT, `readConfig` TEXT, PRIMARY KEY(`bookUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"tocUrl\",\n            \"columnName\": \"tocUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"originName\",\n            \"columnName\": \"originName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"author\",\n            \"columnName\": \"author\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"kind\",\n            \"columnName\": \"kind\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customTag\",\n            \"columnName\": \"customTag\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"coverUrl\",\n            \"columnName\": \"coverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customCoverUrl\",\n            \"columnName\": \"customCoverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"intro\",\n            \"columnName\": \"intro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customIntro\",\n            \"columnName\": \"customIntro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"charset\",\n            \"columnName\": \"charset\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"group\",\n            \"columnName\": \"group\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"latestChapterTitle\",\n            \"columnName\": \"latestChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"latestChapterTime\",\n            \"columnName\": \"latestChapterTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"lastCheckTime\",\n            \"columnName\": \"lastCheckTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"lastCheckCount\",\n            \"columnName\": \"lastCheckCount\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"totalChapterNum\",\n            \"columnName\": \"totalChapterNum\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"durChapterTitle\",\n            \"columnName\": \"durChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"durChapterIndex\",\n            \"columnName\": \"durChapterIndex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"durChapterPos\",\n            \"columnName\": \"durChapterPos\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"durChapterTime\",\n            \"columnName\": \"durChapterTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"wordCount\",\n            \"columnName\": \"wordCount\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"canUpdate\",\n            \"columnName\": \"canUpdate\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"originOrder\",\n            \"columnName\": \"originOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"readConfig\",\n            \"columnName\": \"readConfig\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"bookUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_books_name_author\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"name\",\n              \"author\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_books_name_author` ON `${TABLE_NAME}` (`name`, `author`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"book_groups\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`groupId` INTEGER NOT NULL, `groupName` TEXT NOT NULL, `order` INTEGER NOT NULL, `show` INTEGER NOT NULL, PRIMARY KEY(`groupId`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"groupId\",\n            \"columnName\": \"groupId\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"groupName\",\n            \"columnName\": \"groupName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"show\",\n            \"columnName\": \"show\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"groupId\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"book_sources\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookSourceName` TEXT NOT NULL, `bookSourceGroup` TEXT, `bookSourceUrl` TEXT NOT NULL, `bookSourceType` INTEGER NOT NULL, `bookUrlPattern` TEXT, `customOrder` INTEGER NOT NULL, `enabled` INTEGER NOT NULL, `enabledExplore` INTEGER NOT NULL, `header` TEXT, `loginUrl` TEXT, `bookSourceComment` TEXT, `lastUpdateTime` INTEGER NOT NULL, `weight` INTEGER NOT NULL, `exploreUrl` TEXT, `ruleExplore` TEXT, `searchUrl` TEXT, `ruleSearch` TEXT, `ruleBookInfo` TEXT, `ruleToc` TEXT, `ruleContent` TEXT, PRIMARY KEY(`bookSourceUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookSourceName\",\n            \"columnName\": \"bookSourceName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookSourceGroup\",\n            \"columnName\": \"bookSourceGroup\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"bookSourceUrl\",\n            \"columnName\": \"bookSourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookSourceType\",\n            \"columnName\": \"bookSourceType\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookUrlPattern\",\n            \"columnName\": \"bookUrlPattern\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enabled\",\n            \"columnName\": \"enabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enabledExplore\",\n            \"columnName\": \"enabledExplore\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"header\",\n            \"columnName\": \"header\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUrl\",\n            \"columnName\": \"loginUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"bookSourceComment\",\n            \"columnName\": \"bookSourceComment\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"lastUpdateTime\",\n            \"columnName\": \"lastUpdateTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"weight\",\n            \"columnName\": \"weight\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"exploreUrl\",\n            \"columnName\": \"exploreUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleExplore\",\n            \"columnName\": \"ruleExplore\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"searchUrl\",\n            \"columnName\": \"searchUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleSearch\",\n            \"columnName\": \"ruleSearch\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleBookInfo\",\n            \"columnName\": \"ruleBookInfo\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleToc\",\n            \"columnName\": \"ruleToc\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleContent\",\n            \"columnName\": \"ruleContent\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"bookSourceUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_book_sources_bookSourceUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"bookSourceUrl\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_book_sources_bookSourceUrl` ON `${TABLE_NAME}` (`bookSourceUrl`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"chapters\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`url` TEXT NOT NULL, `title` TEXT NOT NULL, `baseUrl` TEXT NOT NULL, `bookUrl` TEXT NOT NULL, `index` INTEGER NOT NULL, `resourceUrl` TEXT, `tag` TEXT, `start` INTEGER, `end` INTEGER, `startFragmentId` TEXT, `endFragmentId` TEXT, `variable` TEXT, PRIMARY KEY(`url`, `bookUrl`), FOREIGN KEY(`bookUrl`) REFERENCES `books`(`bookUrl`) ON UPDATE NO ACTION ON DELETE CASCADE )\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"baseUrl\",\n            \"columnName\": \"baseUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"index\",\n            \"columnName\": \"index\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"resourceUrl\",\n            \"columnName\": \"resourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"tag\",\n            \"columnName\": \"tag\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"start\",\n            \"columnName\": \"start\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"end\",\n            \"columnName\": \"end\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"startFragmentId\",\n            \"columnName\": \"startFragmentId\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"endFragmentId\",\n            \"columnName\": \"endFragmentId\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"url\",\n            \"bookUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_chapters_bookUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"bookUrl\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_chapters_bookUrl` ON `${TABLE_NAME}` (`bookUrl`)\"\n          },\n          {\n            \"name\": \"index_chapters_bookUrl_index\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"bookUrl\",\n              \"index\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_chapters_bookUrl_index` ON `${TABLE_NAME}` (`bookUrl`, `index`)\"\n          }\n        ],\n        \"foreignKeys\": [\n          {\n            \"table\": \"books\",\n            \"onDelete\": \"CASCADE\",\n            \"onUpdate\": \"NO ACTION\",\n            \"columns\": [\n              \"bookUrl\"\n            ],\n            \"referencedColumns\": [\n              \"bookUrl\"\n            ]\n          }\n        ]\n      },\n      {\n        \"tableName\": \"replace_rules\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL, `group` TEXT, `pattern` TEXT NOT NULL, `replacement` TEXT NOT NULL, `scope` TEXT, `isEnabled` INTEGER NOT NULL, `isRegex` INTEGER NOT NULL, `sortOrder` INTEGER NOT NULL)\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"group\",\n            \"columnName\": \"group\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"pattern\",\n            \"columnName\": \"pattern\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"replacement\",\n            \"columnName\": \"replacement\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"scope\",\n            \"columnName\": \"scope\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"isEnabled\",\n            \"columnName\": \"isEnabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"isRegex\",\n            \"columnName\": \"isRegex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"sortOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"id\"\n          ],\n          \"autoGenerate\": true\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_replace_rules_id\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"id\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_replace_rules_id` ON `${TABLE_NAME}` (`id`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"searchBooks\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookUrl` TEXT NOT NULL, `origin` TEXT NOT NULL, `originName` TEXT NOT NULL, `type` INTEGER NOT NULL, `name` TEXT NOT NULL, `author` TEXT NOT NULL, `kind` TEXT, `coverUrl` TEXT, `intro` TEXT, `wordCount` TEXT, `latestChapterTitle` TEXT, `tocUrl` TEXT NOT NULL, `time` INTEGER NOT NULL, `variable` TEXT, `originOrder` INTEGER NOT NULL, PRIMARY KEY(`bookUrl`), FOREIGN KEY(`origin`) REFERENCES `book_sources`(`bookSourceUrl`) ON UPDATE NO ACTION ON DELETE CASCADE )\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"originName\",\n            \"columnName\": \"originName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"author\",\n            \"columnName\": \"author\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"kind\",\n            \"columnName\": \"kind\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"coverUrl\",\n            \"columnName\": \"coverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"intro\",\n            \"columnName\": \"intro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"wordCount\",\n            \"columnName\": \"wordCount\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"latestChapterTitle\",\n            \"columnName\": \"latestChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"tocUrl\",\n            \"columnName\": \"tocUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"time\",\n            \"columnName\": \"time\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"originOrder\",\n            \"columnName\": \"originOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"bookUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_searchBooks_bookUrl\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"bookUrl\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_searchBooks_bookUrl` ON `${TABLE_NAME}` (`bookUrl`)\"\n          },\n          {\n            \"name\": \"index_searchBooks_origin\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"origin\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_searchBooks_origin` ON `${TABLE_NAME}` (`origin`)\"\n          }\n        ],\n        \"foreignKeys\": [\n          {\n            \"table\": \"book_sources\",\n            \"onDelete\": \"CASCADE\",\n            \"onUpdate\": \"NO ACTION\",\n            \"columns\": [\n              \"origin\"\n            ],\n            \"referencedColumns\": [\n              \"bookSourceUrl\"\n            ]\n          }\n        ]\n      },\n      {\n        \"tableName\": \"search_keywords\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`word` TEXT NOT NULL, `usage` INTEGER NOT NULL, `lastUseTime` INTEGER NOT NULL, PRIMARY KEY(`word`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"word\",\n            \"columnName\": \"word\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"usage\",\n            \"columnName\": \"usage\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"lastUseTime\",\n            \"columnName\": \"lastUseTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"word\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_search_keywords_word\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"word\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_search_keywords_word` ON `${TABLE_NAME}` (`word`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"cookies\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`url` TEXT NOT NULL, `cookie` TEXT NOT NULL, PRIMARY KEY(`url`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"cookie\",\n            \"columnName\": \"cookie\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"url\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_cookies_url\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"url\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_cookies_url` ON `${TABLE_NAME}` (`url`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssSources\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`sourceUrl` TEXT NOT NULL, `sourceName` TEXT NOT NULL, `sourceIcon` TEXT NOT NULL, `sourceGroup` TEXT, `sourceComment` TEXT, `enabled` INTEGER NOT NULL, `sortUrl` TEXT, `singleUrl` INTEGER NOT NULL, `articleStyle` INTEGER NOT NULL, `ruleArticles` TEXT, `ruleNextPage` TEXT, `ruleTitle` TEXT, `rulePubDate` TEXT, `ruleDescription` TEXT, `ruleImage` TEXT, `ruleLink` TEXT, `ruleContent` TEXT, `style` TEXT, `header` TEXT, `enableJs` INTEGER NOT NULL, `loadWithBaseUrl` INTEGER NOT NULL, `customOrder` INTEGER NOT NULL, PRIMARY KEY(`sourceUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"sourceUrl\",\n            \"columnName\": \"sourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceName\",\n            \"columnName\": \"sourceName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceIcon\",\n            \"columnName\": \"sourceIcon\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceGroup\",\n            \"columnName\": \"sourceGroup\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"sourceComment\",\n            \"columnName\": \"sourceComment\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enabled\",\n            \"columnName\": \"enabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sortUrl\",\n            \"columnName\": \"sortUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"singleUrl\",\n            \"columnName\": \"singleUrl\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"articleStyle\",\n            \"columnName\": \"articleStyle\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"ruleArticles\",\n            \"columnName\": \"ruleArticles\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleNextPage\",\n            \"columnName\": \"ruleNextPage\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleTitle\",\n            \"columnName\": \"ruleTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"rulePubDate\",\n            \"columnName\": \"rulePubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleDescription\",\n            \"columnName\": \"ruleDescription\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleImage\",\n            \"columnName\": \"ruleImage\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleLink\",\n            \"columnName\": \"ruleLink\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleContent\",\n            \"columnName\": \"ruleContent\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"style\",\n            \"columnName\": \"style\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"header\",\n            \"columnName\": \"header\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enableJs\",\n            \"columnName\": \"enableJs\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"loadWithBaseUrl\",\n            \"columnName\": \"loadWithBaseUrl\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"sourceUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_rssSources_sourceUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"sourceUrl\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_rssSources_sourceUrl` ON `${TABLE_NAME}` (`sourceUrl`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"bookmarks\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`time` INTEGER NOT NULL, `bookUrl` TEXT NOT NULL, `bookName` TEXT NOT NULL, `bookAuthor` TEXT NOT NULL, `chapterIndex` INTEGER NOT NULL, `chapterPos` INTEGER NOT NULL, `chapterName` TEXT NOT NULL, `bookText` TEXT NOT NULL, `content` TEXT NOT NULL, PRIMARY KEY(`time`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"time\",\n            \"columnName\": \"time\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookName\",\n            \"columnName\": \"bookName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookAuthor\",\n            \"columnName\": \"bookAuthor\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterIndex\",\n            \"columnName\": \"chapterIndex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterPos\",\n            \"columnName\": \"chapterPos\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterName\",\n            \"columnName\": \"chapterName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookText\",\n            \"columnName\": \"bookText\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"time\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_bookmarks_time\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"time\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_bookmarks_time` ON `${TABLE_NAME}` (`time`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssArticles\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`origin` TEXT NOT NULL, `sort` TEXT NOT NULL, `title` TEXT NOT NULL, `order` INTEGER NOT NULL, `link` TEXT NOT NULL, `pubDate` TEXT, `description` TEXT, `content` TEXT, `image` TEXT, `read` INTEGER NOT NULL, `variable` TEXT, PRIMARY KEY(`origin`, `link`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sort\",\n            \"columnName\": \"sort\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"link\",\n            \"columnName\": \"link\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"pubDate\",\n            \"columnName\": \"pubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"description\",\n            \"columnName\": \"description\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"image\",\n            \"columnName\": \"image\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"read\",\n            \"columnName\": \"read\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"origin\",\n            \"link\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssReadRecords\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`record` TEXT NOT NULL, `read` INTEGER NOT NULL, PRIMARY KEY(`record`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"record\",\n            \"columnName\": \"record\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"read\",\n            \"columnName\": \"read\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"record\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssStars\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`origin` TEXT NOT NULL, `sort` TEXT NOT NULL, `title` TEXT NOT NULL, `starTime` INTEGER NOT NULL, `link` TEXT NOT NULL, `pubDate` TEXT, `description` TEXT, `content` TEXT, `image` TEXT, `variable` TEXT, PRIMARY KEY(`origin`, `link`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sort\",\n            \"columnName\": \"sort\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"starTime\",\n            \"columnName\": \"starTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"link\",\n            \"columnName\": \"link\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"pubDate\",\n            \"columnName\": \"pubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"description\",\n            \"columnName\": \"description\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"image\",\n            \"columnName\": \"image\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"origin\",\n            \"link\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"txtTocRules\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `rule` TEXT NOT NULL, `serialNumber` INTEGER NOT NULL, `enable` INTEGER NOT NULL, PRIMARY KEY(`id`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"rule\",\n            \"columnName\": \"rule\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"serialNumber\",\n            \"columnName\": \"serialNumber\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enable\",\n            \"columnName\": \"enable\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"id\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"readRecord\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`deviceId` TEXT NOT NULL, `bookName` TEXT NOT NULL, `readTime` INTEGER NOT NULL, PRIMARY KEY(`deviceId`, `bookName`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"deviceId\",\n            \"columnName\": \"deviceId\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookName\",\n            \"columnName\": \"bookName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"readTime\",\n            \"columnName\": \"readTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"deviceId\",\n            \"bookName\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"httpTTS\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `url` TEXT NOT NULL, PRIMARY KEY(`id`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"id\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"caches\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`key` TEXT NOT NULL, `value` TEXT, `deadline` INTEGER NOT NULL, PRIMARY KEY(`key`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"key\",\n            \"columnName\": \"key\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"value\",\n            \"columnName\": \"value\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"deadline\",\n            \"columnName\": \"deadline\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"key\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_caches_key\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"key\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_caches_key` ON `${TABLE_NAME}` (`key`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"ruleSubs\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `url` TEXT NOT NULL, `type` INTEGER NOT NULL, `customOrder` INTEGER NOT NULL, `autoUpdate` INTEGER NOT NULL, `update` INTEGER NOT NULL, PRIMARY KEY(`id`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"autoUpdate\",\n            \"columnName\": \"autoUpdate\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"update\",\n            \"columnName\": \"update\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"id\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      }\n    ],\n    \"views\": [],\n    \"setupQueries\": [\n      \"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)\",\n      \"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'd1c390e708a1e89c7d016cdd2e0b2e88')\"\n    ]\n  }\n}"
  },
  {
    "path": "app/schemas/io.legado.app.data.AppDatabase/32.json",
    "content": "{\n  \"formatVersion\": 1,\n  \"database\": {\n    \"version\": 32,\n    \"identityHash\": \"d1c390e708a1e89c7d016cdd2e0b2e88\",\n    \"entities\": [\n      {\n        \"tableName\": \"books\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookUrl` TEXT NOT NULL, `tocUrl` TEXT NOT NULL, `origin` TEXT NOT NULL, `originName` TEXT NOT NULL, `name` TEXT NOT NULL, `author` TEXT NOT NULL, `kind` TEXT, `customTag` TEXT, `coverUrl` TEXT, `customCoverUrl` TEXT, `intro` TEXT, `customIntro` TEXT, `charset` TEXT, `type` INTEGER NOT NULL, `group` INTEGER NOT NULL, `latestChapterTitle` TEXT, `latestChapterTime` INTEGER NOT NULL, `lastCheckTime` INTEGER NOT NULL, `lastCheckCount` INTEGER NOT NULL, `totalChapterNum` INTEGER NOT NULL, `durChapterTitle` TEXT, `durChapterIndex` INTEGER NOT NULL, `durChapterPos` INTEGER NOT NULL, `durChapterTime` INTEGER NOT NULL, `wordCount` TEXT, `canUpdate` INTEGER NOT NULL, `order` INTEGER NOT NULL, `originOrder` INTEGER NOT NULL, `variable` TEXT, `readConfig` TEXT, PRIMARY KEY(`bookUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"tocUrl\",\n            \"columnName\": \"tocUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"originName\",\n            \"columnName\": \"originName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"author\",\n            \"columnName\": \"author\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"kind\",\n            \"columnName\": \"kind\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customTag\",\n            \"columnName\": \"customTag\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"coverUrl\",\n            \"columnName\": \"coverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customCoverUrl\",\n            \"columnName\": \"customCoverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"intro\",\n            \"columnName\": \"intro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customIntro\",\n            \"columnName\": \"customIntro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"charset\",\n            \"columnName\": \"charset\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"group\",\n            \"columnName\": \"group\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"latestChapterTitle\",\n            \"columnName\": \"latestChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"latestChapterTime\",\n            \"columnName\": \"latestChapterTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"lastCheckTime\",\n            \"columnName\": \"lastCheckTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"lastCheckCount\",\n            \"columnName\": \"lastCheckCount\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"totalChapterNum\",\n            \"columnName\": \"totalChapterNum\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"durChapterTitle\",\n            \"columnName\": \"durChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"durChapterIndex\",\n            \"columnName\": \"durChapterIndex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"durChapterPos\",\n            \"columnName\": \"durChapterPos\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"durChapterTime\",\n            \"columnName\": \"durChapterTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"wordCount\",\n            \"columnName\": \"wordCount\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"canUpdate\",\n            \"columnName\": \"canUpdate\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"originOrder\",\n            \"columnName\": \"originOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"readConfig\",\n            \"columnName\": \"readConfig\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"bookUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_books_name_author\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"name\",\n              \"author\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_books_name_author` ON `${TABLE_NAME}` (`name`, `author`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"book_groups\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`groupId` INTEGER NOT NULL, `groupName` TEXT NOT NULL, `order` INTEGER NOT NULL, `show` INTEGER NOT NULL, PRIMARY KEY(`groupId`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"groupId\",\n            \"columnName\": \"groupId\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"groupName\",\n            \"columnName\": \"groupName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"show\",\n            \"columnName\": \"show\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"groupId\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"book_sources\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookSourceName` TEXT NOT NULL, `bookSourceGroup` TEXT, `bookSourceUrl` TEXT NOT NULL, `bookSourceType` INTEGER NOT NULL, `bookUrlPattern` TEXT, `customOrder` INTEGER NOT NULL, `enabled` INTEGER NOT NULL, `enabledExplore` INTEGER NOT NULL, `header` TEXT, `loginUrl` TEXT, `bookSourceComment` TEXT, `lastUpdateTime` INTEGER NOT NULL, `weight` INTEGER NOT NULL, `exploreUrl` TEXT, `ruleExplore` TEXT, `searchUrl` TEXT, `ruleSearch` TEXT, `ruleBookInfo` TEXT, `ruleToc` TEXT, `ruleContent` TEXT, PRIMARY KEY(`bookSourceUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookSourceName\",\n            \"columnName\": \"bookSourceName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookSourceGroup\",\n            \"columnName\": \"bookSourceGroup\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"bookSourceUrl\",\n            \"columnName\": \"bookSourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookSourceType\",\n            \"columnName\": \"bookSourceType\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookUrlPattern\",\n            \"columnName\": \"bookUrlPattern\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enabled\",\n            \"columnName\": \"enabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enabledExplore\",\n            \"columnName\": \"enabledExplore\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"header\",\n            \"columnName\": \"header\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUrl\",\n            \"columnName\": \"loginUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"bookSourceComment\",\n            \"columnName\": \"bookSourceComment\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"lastUpdateTime\",\n            \"columnName\": \"lastUpdateTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"weight\",\n            \"columnName\": \"weight\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"exploreUrl\",\n            \"columnName\": \"exploreUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleExplore\",\n            \"columnName\": \"ruleExplore\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"searchUrl\",\n            \"columnName\": \"searchUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleSearch\",\n            \"columnName\": \"ruleSearch\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleBookInfo\",\n            \"columnName\": \"ruleBookInfo\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleToc\",\n            \"columnName\": \"ruleToc\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleContent\",\n            \"columnName\": \"ruleContent\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"bookSourceUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_book_sources_bookSourceUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"bookSourceUrl\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_book_sources_bookSourceUrl` ON `${TABLE_NAME}` (`bookSourceUrl`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"chapters\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`url` TEXT NOT NULL, `title` TEXT NOT NULL, `baseUrl` TEXT NOT NULL, `bookUrl` TEXT NOT NULL, `index` INTEGER NOT NULL, `resourceUrl` TEXT, `tag` TEXT, `start` INTEGER, `end` INTEGER, `startFragmentId` TEXT, `endFragmentId` TEXT, `variable` TEXT, PRIMARY KEY(`url`, `bookUrl`), FOREIGN KEY(`bookUrl`) REFERENCES `books`(`bookUrl`) ON UPDATE NO ACTION ON DELETE CASCADE )\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"baseUrl\",\n            \"columnName\": \"baseUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"index\",\n            \"columnName\": \"index\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"resourceUrl\",\n            \"columnName\": \"resourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"tag\",\n            \"columnName\": \"tag\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"start\",\n            \"columnName\": \"start\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"end\",\n            \"columnName\": \"end\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"startFragmentId\",\n            \"columnName\": \"startFragmentId\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"endFragmentId\",\n            \"columnName\": \"endFragmentId\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"url\",\n            \"bookUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_chapters_bookUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"bookUrl\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_chapters_bookUrl` ON `${TABLE_NAME}` (`bookUrl`)\"\n          },\n          {\n            \"name\": \"index_chapters_bookUrl_index\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"bookUrl\",\n              \"index\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_chapters_bookUrl_index` ON `${TABLE_NAME}` (`bookUrl`, `index`)\"\n          }\n        ],\n        \"foreignKeys\": [\n          {\n            \"table\": \"books\",\n            \"onDelete\": \"CASCADE\",\n            \"onUpdate\": \"NO ACTION\",\n            \"columns\": [\n              \"bookUrl\"\n            ],\n            \"referencedColumns\": [\n              \"bookUrl\"\n            ]\n          }\n        ]\n      },\n      {\n        \"tableName\": \"replace_rules\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL, `group` TEXT, `pattern` TEXT NOT NULL, `replacement` TEXT NOT NULL, `scope` TEXT, `isEnabled` INTEGER NOT NULL, `isRegex` INTEGER NOT NULL, `sortOrder` INTEGER NOT NULL)\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"group\",\n            \"columnName\": \"group\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"pattern\",\n            \"columnName\": \"pattern\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"replacement\",\n            \"columnName\": \"replacement\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"scope\",\n            \"columnName\": \"scope\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"isEnabled\",\n            \"columnName\": \"isEnabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"isRegex\",\n            \"columnName\": \"isRegex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"sortOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"id\"\n          ],\n          \"autoGenerate\": true\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_replace_rules_id\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"id\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_replace_rules_id` ON `${TABLE_NAME}` (`id`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"searchBooks\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookUrl` TEXT NOT NULL, `origin` TEXT NOT NULL, `originName` TEXT NOT NULL, `type` INTEGER NOT NULL, `name` TEXT NOT NULL, `author` TEXT NOT NULL, `kind` TEXT, `coverUrl` TEXT, `intro` TEXT, `wordCount` TEXT, `latestChapterTitle` TEXT, `tocUrl` TEXT NOT NULL, `time` INTEGER NOT NULL, `variable` TEXT, `originOrder` INTEGER NOT NULL, PRIMARY KEY(`bookUrl`), FOREIGN KEY(`origin`) REFERENCES `book_sources`(`bookSourceUrl`) ON UPDATE NO ACTION ON DELETE CASCADE )\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"originName\",\n            \"columnName\": \"originName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"author\",\n            \"columnName\": \"author\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"kind\",\n            \"columnName\": \"kind\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"coverUrl\",\n            \"columnName\": \"coverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"intro\",\n            \"columnName\": \"intro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"wordCount\",\n            \"columnName\": \"wordCount\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"latestChapterTitle\",\n            \"columnName\": \"latestChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"tocUrl\",\n            \"columnName\": \"tocUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"time\",\n            \"columnName\": \"time\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"originOrder\",\n            \"columnName\": \"originOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"bookUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_searchBooks_bookUrl\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"bookUrl\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_searchBooks_bookUrl` ON `${TABLE_NAME}` (`bookUrl`)\"\n          },\n          {\n            \"name\": \"index_searchBooks_origin\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"origin\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_searchBooks_origin` ON `${TABLE_NAME}` (`origin`)\"\n          }\n        ],\n        \"foreignKeys\": [\n          {\n            \"table\": \"book_sources\",\n            \"onDelete\": \"CASCADE\",\n            \"onUpdate\": \"NO ACTION\",\n            \"columns\": [\n              \"origin\"\n            ],\n            \"referencedColumns\": [\n              \"bookSourceUrl\"\n            ]\n          }\n        ]\n      },\n      {\n        \"tableName\": \"search_keywords\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`word` TEXT NOT NULL, `usage` INTEGER NOT NULL, `lastUseTime` INTEGER NOT NULL, PRIMARY KEY(`word`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"word\",\n            \"columnName\": \"word\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"usage\",\n            \"columnName\": \"usage\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"lastUseTime\",\n            \"columnName\": \"lastUseTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"word\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_search_keywords_word\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"word\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_search_keywords_word` ON `${TABLE_NAME}` (`word`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"cookies\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`url` TEXT NOT NULL, `cookie` TEXT NOT NULL, PRIMARY KEY(`url`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"cookie\",\n            \"columnName\": \"cookie\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"url\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_cookies_url\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"url\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_cookies_url` ON `${TABLE_NAME}` (`url`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssSources\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`sourceUrl` TEXT NOT NULL, `sourceName` TEXT NOT NULL, `sourceIcon` TEXT NOT NULL, `sourceGroup` TEXT, `sourceComment` TEXT, `enabled` INTEGER NOT NULL, `sortUrl` TEXT, `singleUrl` INTEGER NOT NULL, `articleStyle` INTEGER NOT NULL, `ruleArticles` TEXT, `ruleNextPage` TEXT, `ruleTitle` TEXT, `rulePubDate` TEXT, `ruleDescription` TEXT, `ruleImage` TEXT, `ruleLink` TEXT, `ruleContent` TEXT, `style` TEXT, `header` TEXT, `enableJs` INTEGER NOT NULL, `loadWithBaseUrl` INTEGER NOT NULL, `customOrder` INTEGER NOT NULL, PRIMARY KEY(`sourceUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"sourceUrl\",\n            \"columnName\": \"sourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceName\",\n            \"columnName\": \"sourceName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceIcon\",\n            \"columnName\": \"sourceIcon\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceGroup\",\n            \"columnName\": \"sourceGroup\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"sourceComment\",\n            \"columnName\": \"sourceComment\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enabled\",\n            \"columnName\": \"enabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sortUrl\",\n            \"columnName\": \"sortUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"singleUrl\",\n            \"columnName\": \"singleUrl\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"articleStyle\",\n            \"columnName\": \"articleStyle\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"ruleArticles\",\n            \"columnName\": \"ruleArticles\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleNextPage\",\n            \"columnName\": \"ruleNextPage\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleTitle\",\n            \"columnName\": \"ruleTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"rulePubDate\",\n            \"columnName\": \"rulePubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleDescription\",\n            \"columnName\": \"ruleDescription\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleImage\",\n            \"columnName\": \"ruleImage\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleLink\",\n            \"columnName\": \"ruleLink\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleContent\",\n            \"columnName\": \"ruleContent\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"style\",\n            \"columnName\": \"style\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"header\",\n            \"columnName\": \"header\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enableJs\",\n            \"columnName\": \"enableJs\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"loadWithBaseUrl\",\n            \"columnName\": \"loadWithBaseUrl\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"sourceUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_rssSources_sourceUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"sourceUrl\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_rssSources_sourceUrl` ON `${TABLE_NAME}` (`sourceUrl`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"bookmarks\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`time` INTEGER NOT NULL, `bookUrl` TEXT NOT NULL, `bookName` TEXT NOT NULL, `bookAuthor` TEXT NOT NULL, `chapterIndex` INTEGER NOT NULL, `chapterPos` INTEGER NOT NULL, `chapterName` TEXT NOT NULL, `bookText` TEXT NOT NULL, `content` TEXT NOT NULL, PRIMARY KEY(`time`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"time\",\n            \"columnName\": \"time\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookName\",\n            \"columnName\": \"bookName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookAuthor\",\n            \"columnName\": \"bookAuthor\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterIndex\",\n            \"columnName\": \"chapterIndex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterPos\",\n            \"columnName\": \"chapterPos\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterName\",\n            \"columnName\": \"chapterName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookText\",\n            \"columnName\": \"bookText\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"time\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_bookmarks_time\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"time\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_bookmarks_time` ON `${TABLE_NAME}` (`time`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssArticles\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`origin` TEXT NOT NULL, `sort` TEXT NOT NULL, `title` TEXT NOT NULL, `order` INTEGER NOT NULL, `link` TEXT NOT NULL, `pubDate` TEXT, `description` TEXT, `content` TEXT, `image` TEXT, `read` INTEGER NOT NULL, `variable` TEXT, PRIMARY KEY(`origin`, `link`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sort\",\n            \"columnName\": \"sort\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"link\",\n            \"columnName\": \"link\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"pubDate\",\n            \"columnName\": \"pubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"description\",\n            \"columnName\": \"description\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"image\",\n            \"columnName\": \"image\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"read\",\n            \"columnName\": \"read\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"origin\",\n            \"link\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssReadRecords\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`record` TEXT NOT NULL, `read` INTEGER NOT NULL, PRIMARY KEY(`record`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"record\",\n            \"columnName\": \"record\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"read\",\n            \"columnName\": \"read\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"record\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssStars\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`origin` TEXT NOT NULL, `sort` TEXT NOT NULL, `title` TEXT NOT NULL, `starTime` INTEGER NOT NULL, `link` TEXT NOT NULL, `pubDate` TEXT, `description` TEXT, `content` TEXT, `image` TEXT, `variable` TEXT, PRIMARY KEY(`origin`, `link`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sort\",\n            \"columnName\": \"sort\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"starTime\",\n            \"columnName\": \"starTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"link\",\n            \"columnName\": \"link\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"pubDate\",\n            \"columnName\": \"pubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"description\",\n            \"columnName\": \"description\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"image\",\n            \"columnName\": \"image\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"origin\",\n            \"link\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"txtTocRules\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `rule` TEXT NOT NULL, `serialNumber` INTEGER NOT NULL, `enable` INTEGER NOT NULL, PRIMARY KEY(`id`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"rule\",\n            \"columnName\": \"rule\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"serialNumber\",\n            \"columnName\": \"serialNumber\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enable\",\n            \"columnName\": \"enable\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"id\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"readRecord\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`deviceId` TEXT NOT NULL, `bookName` TEXT NOT NULL, `readTime` INTEGER NOT NULL, PRIMARY KEY(`deviceId`, `bookName`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"deviceId\",\n            \"columnName\": \"deviceId\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookName\",\n            \"columnName\": \"bookName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"readTime\",\n            \"columnName\": \"readTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"deviceId\",\n            \"bookName\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"httpTTS\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `url` TEXT NOT NULL, PRIMARY KEY(`id`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"id\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"caches\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`key` TEXT NOT NULL, `value` TEXT, `deadline` INTEGER NOT NULL, PRIMARY KEY(`key`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"key\",\n            \"columnName\": \"key\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"value\",\n            \"columnName\": \"value\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"deadline\",\n            \"columnName\": \"deadline\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"key\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_caches_key\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"key\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_caches_key` ON `${TABLE_NAME}` (`key`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"ruleSubs\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `url` TEXT NOT NULL, `type` INTEGER NOT NULL, `customOrder` INTEGER NOT NULL, `autoUpdate` INTEGER NOT NULL, `update` INTEGER NOT NULL, PRIMARY KEY(`id`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"autoUpdate\",\n            \"columnName\": \"autoUpdate\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"update\",\n            \"columnName\": \"update\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"id\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      }\n    ],\n    \"views\": [],\n    \"setupQueries\": [\n      \"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)\",\n      \"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'd1c390e708a1e89c7d016cdd2e0b2e88')\"\n    ]\n  }\n}"
  },
  {
    "path": "app/schemas/io.legado.app.data.AppDatabase/33.json",
    "content": "{\n  \"formatVersion\": 1,\n  \"database\": {\n    \"version\": 33,\n    \"identityHash\": \"6dad1518f359667b4d740fc6a1f44a21\",\n    \"entities\": [\n      {\n        \"tableName\": \"books\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookUrl` TEXT NOT NULL, `tocUrl` TEXT NOT NULL, `origin` TEXT NOT NULL, `originName` TEXT NOT NULL, `name` TEXT NOT NULL, `author` TEXT NOT NULL, `kind` TEXT, `customTag` TEXT, `coverUrl` TEXT, `customCoverUrl` TEXT, `intro` TEXT, `customIntro` TEXT, `charset` TEXT, `type` INTEGER NOT NULL, `group` INTEGER NOT NULL, `latestChapterTitle` TEXT, `latestChapterTime` INTEGER NOT NULL, `lastCheckTime` INTEGER NOT NULL, `lastCheckCount` INTEGER NOT NULL, `totalChapterNum` INTEGER NOT NULL, `durChapterTitle` TEXT, `durChapterIndex` INTEGER NOT NULL, `durChapterPos` INTEGER NOT NULL, `durChapterTime` INTEGER NOT NULL, `wordCount` TEXT, `canUpdate` INTEGER NOT NULL, `order` INTEGER NOT NULL, `originOrder` INTEGER NOT NULL, `variable` TEXT, `readConfig` TEXT, PRIMARY KEY(`bookUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"tocUrl\",\n            \"columnName\": \"tocUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"originName\",\n            \"columnName\": \"originName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"author\",\n            \"columnName\": \"author\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"kind\",\n            \"columnName\": \"kind\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customTag\",\n            \"columnName\": \"customTag\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"coverUrl\",\n            \"columnName\": \"coverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customCoverUrl\",\n            \"columnName\": \"customCoverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"intro\",\n            \"columnName\": \"intro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customIntro\",\n            \"columnName\": \"customIntro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"charset\",\n            \"columnName\": \"charset\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"group\",\n            \"columnName\": \"group\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"latestChapterTitle\",\n            \"columnName\": \"latestChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"latestChapterTime\",\n            \"columnName\": \"latestChapterTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"lastCheckTime\",\n            \"columnName\": \"lastCheckTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"lastCheckCount\",\n            \"columnName\": \"lastCheckCount\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"totalChapterNum\",\n            \"columnName\": \"totalChapterNum\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"durChapterTitle\",\n            \"columnName\": \"durChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"durChapterIndex\",\n            \"columnName\": \"durChapterIndex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"durChapterPos\",\n            \"columnName\": \"durChapterPos\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"durChapterTime\",\n            \"columnName\": \"durChapterTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"wordCount\",\n            \"columnName\": \"wordCount\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"canUpdate\",\n            \"columnName\": \"canUpdate\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"originOrder\",\n            \"columnName\": \"originOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"readConfig\",\n            \"columnName\": \"readConfig\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"bookUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_books_name_author\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"name\",\n              \"author\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_books_name_author` ON `${TABLE_NAME}` (`name`, `author`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"book_groups\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`groupId` INTEGER NOT NULL, `groupName` TEXT NOT NULL, `order` INTEGER NOT NULL, `show` INTEGER NOT NULL, PRIMARY KEY(`groupId`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"groupId\",\n            \"columnName\": \"groupId\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"groupName\",\n            \"columnName\": \"groupName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"show\",\n            \"columnName\": \"show\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"groupId\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"book_sources\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookSourceName` TEXT NOT NULL, `bookSourceGroup` TEXT, `bookSourceUrl` TEXT NOT NULL, `bookSourceType` INTEGER NOT NULL, `bookUrlPattern` TEXT, `customOrder` INTEGER NOT NULL, `enabled` INTEGER NOT NULL, `enabledExplore` INTEGER NOT NULL, `header` TEXT, `loginUrl` TEXT, `bookSourceComment` TEXT, `lastUpdateTime` INTEGER NOT NULL, `weight` INTEGER NOT NULL, `exploreUrl` TEXT, `ruleExplore` TEXT, `searchUrl` TEXT, `ruleSearch` TEXT, `ruleBookInfo` TEXT, `ruleToc` TEXT, `ruleContent` TEXT, PRIMARY KEY(`bookSourceUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookSourceName\",\n            \"columnName\": \"bookSourceName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookSourceGroup\",\n            \"columnName\": \"bookSourceGroup\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"bookSourceUrl\",\n            \"columnName\": \"bookSourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookSourceType\",\n            \"columnName\": \"bookSourceType\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookUrlPattern\",\n            \"columnName\": \"bookUrlPattern\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enabled\",\n            \"columnName\": \"enabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enabledExplore\",\n            \"columnName\": \"enabledExplore\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"header\",\n            \"columnName\": \"header\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUrl\",\n            \"columnName\": \"loginUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"bookSourceComment\",\n            \"columnName\": \"bookSourceComment\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"lastUpdateTime\",\n            \"columnName\": \"lastUpdateTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"weight\",\n            \"columnName\": \"weight\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"exploreUrl\",\n            \"columnName\": \"exploreUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleExplore\",\n            \"columnName\": \"ruleExplore\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"searchUrl\",\n            \"columnName\": \"searchUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleSearch\",\n            \"columnName\": \"ruleSearch\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleBookInfo\",\n            \"columnName\": \"ruleBookInfo\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleToc\",\n            \"columnName\": \"ruleToc\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleContent\",\n            \"columnName\": \"ruleContent\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"bookSourceUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_book_sources_bookSourceUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"bookSourceUrl\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_book_sources_bookSourceUrl` ON `${TABLE_NAME}` (`bookSourceUrl`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"chapters\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`url` TEXT NOT NULL, `title` TEXT NOT NULL, `baseUrl` TEXT NOT NULL, `bookUrl` TEXT NOT NULL, `index` INTEGER NOT NULL, `resourceUrl` TEXT, `tag` TEXT, `start` INTEGER, `end` INTEGER, `startFragmentId` TEXT, `endFragmentId` TEXT, `variable` TEXT, PRIMARY KEY(`url`, `bookUrl`), FOREIGN KEY(`bookUrl`) REFERENCES `books`(`bookUrl`) ON UPDATE NO ACTION ON DELETE CASCADE )\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"baseUrl\",\n            \"columnName\": \"baseUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"index\",\n            \"columnName\": \"index\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"resourceUrl\",\n            \"columnName\": \"resourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"tag\",\n            \"columnName\": \"tag\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"start\",\n            \"columnName\": \"start\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"end\",\n            \"columnName\": \"end\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"startFragmentId\",\n            \"columnName\": \"startFragmentId\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"endFragmentId\",\n            \"columnName\": \"endFragmentId\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"url\",\n            \"bookUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_chapters_bookUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"bookUrl\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_chapters_bookUrl` ON `${TABLE_NAME}` (`bookUrl`)\"\n          },\n          {\n            \"name\": \"index_chapters_bookUrl_index\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"bookUrl\",\n              \"index\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_chapters_bookUrl_index` ON `${TABLE_NAME}` (`bookUrl`, `index`)\"\n          }\n        ],\n        \"foreignKeys\": [\n          {\n            \"table\": \"books\",\n            \"onDelete\": \"CASCADE\",\n            \"onUpdate\": \"NO ACTION\",\n            \"columns\": [\n              \"bookUrl\"\n            ],\n            \"referencedColumns\": [\n              \"bookUrl\"\n            ]\n          }\n        ]\n      },\n      {\n        \"tableName\": \"replace_rules\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL, `group` TEXT, `pattern` TEXT NOT NULL, `replacement` TEXT NOT NULL, `scope` TEXT, `isEnabled` INTEGER NOT NULL, `isRegex` INTEGER NOT NULL, `sortOrder` INTEGER NOT NULL)\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"group\",\n            \"columnName\": \"group\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"pattern\",\n            \"columnName\": \"pattern\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"replacement\",\n            \"columnName\": \"replacement\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"scope\",\n            \"columnName\": \"scope\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"isEnabled\",\n            \"columnName\": \"isEnabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"isRegex\",\n            \"columnName\": \"isRegex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"sortOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"id\"\n          ],\n          \"autoGenerate\": true\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_replace_rules_id\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"id\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_replace_rules_id` ON `${TABLE_NAME}` (`id`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"searchBooks\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookUrl` TEXT NOT NULL, `origin` TEXT NOT NULL, `originName` TEXT NOT NULL, `type` INTEGER NOT NULL, `name` TEXT NOT NULL, `author` TEXT NOT NULL, `kind` TEXT, `coverUrl` TEXT, `intro` TEXT, `wordCount` TEXT, `latestChapterTitle` TEXT, `tocUrl` TEXT NOT NULL, `time` INTEGER NOT NULL, `variable` TEXT, `originOrder` INTEGER NOT NULL, PRIMARY KEY(`bookUrl`), FOREIGN KEY(`origin`) REFERENCES `book_sources`(`bookSourceUrl`) ON UPDATE NO ACTION ON DELETE CASCADE )\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"originName\",\n            \"columnName\": \"originName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"author\",\n            \"columnName\": \"author\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"kind\",\n            \"columnName\": \"kind\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"coverUrl\",\n            \"columnName\": \"coverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"intro\",\n            \"columnName\": \"intro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"wordCount\",\n            \"columnName\": \"wordCount\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"latestChapterTitle\",\n            \"columnName\": \"latestChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"tocUrl\",\n            \"columnName\": \"tocUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"time\",\n            \"columnName\": \"time\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"originOrder\",\n            \"columnName\": \"originOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"bookUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_searchBooks_bookUrl\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"bookUrl\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_searchBooks_bookUrl` ON `${TABLE_NAME}` (`bookUrl`)\"\n          },\n          {\n            \"name\": \"index_searchBooks_origin\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"origin\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_searchBooks_origin` ON `${TABLE_NAME}` (`origin`)\"\n          }\n        ],\n        \"foreignKeys\": [\n          {\n            \"table\": \"book_sources\",\n            \"onDelete\": \"CASCADE\",\n            \"onUpdate\": \"NO ACTION\",\n            \"columns\": [\n              \"origin\"\n            ],\n            \"referencedColumns\": [\n              \"bookSourceUrl\"\n            ]\n          }\n        ]\n      },\n      {\n        \"tableName\": \"search_keywords\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`word` TEXT NOT NULL, `usage` INTEGER NOT NULL, `lastUseTime` INTEGER NOT NULL, PRIMARY KEY(`word`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"word\",\n            \"columnName\": \"word\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"usage\",\n            \"columnName\": \"usage\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"lastUseTime\",\n            \"columnName\": \"lastUseTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"word\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_search_keywords_word\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"word\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_search_keywords_word` ON `${TABLE_NAME}` (`word`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"cookies\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`url` TEXT NOT NULL, `cookie` TEXT NOT NULL, PRIMARY KEY(`url`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"cookie\",\n            \"columnName\": \"cookie\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"url\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_cookies_url\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"url\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_cookies_url` ON `${TABLE_NAME}` (`url`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssSources\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`sourceUrl` TEXT NOT NULL, `sourceName` TEXT NOT NULL, `sourceIcon` TEXT NOT NULL, `sourceGroup` TEXT, `sourceComment` TEXT, `enabled` INTEGER NOT NULL, `sortUrl` TEXT, `singleUrl` INTEGER NOT NULL, `articleStyle` INTEGER NOT NULL, `ruleArticles` TEXT, `ruleNextPage` TEXT, `ruleTitle` TEXT, `rulePubDate` TEXT, `ruleDescription` TEXT, `ruleImage` TEXT, `ruleLink` TEXT, `ruleContent` TEXT, `style` TEXT, `header` TEXT, `enableJs` INTEGER NOT NULL, `loadWithBaseUrl` INTEGER NOT NULL, `customOrder` INTEGER NOT NULL, PRIMARY KEY(`sourceUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"sourceUrl\",\n            \"columnName\": \"sourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceName\",\n            \"columnName\": \"sourceName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceIcon\",\n            \"columnName\": \"sourceIcon\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceGroup\",\n            \"columnName\": \"sourceGroup\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"sourceComment\",\n            \"columnName\": \"sourceComment\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enabled\",\n            \"columnName\": \"enabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sortUrl\",\n            \"columnName\": \"sortUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"singleUrl\",\n            \"columnName\": \"singleUrl\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"articleStyle\",\n            \"columnName\": \"articleStyle\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"ruleArticles\",\n            \"columnName\": \"ruleArticles\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleNextPage\",\n            \"columnName\": \"ruleNextPage\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleTitle\",\n            \"columnName\": \"ruleTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"rulePubDate\",\n            \"columnName\": \"rulePubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleDescription\",\n            \"columnName\": \"ruleDescription\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleImage\",\n            \"columnName\": \"ruleImage\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleLink\",\n            \"columnName\": \"ruleLink\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleContent\",\n            \"columnName\": \"ruleContent\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"style\",\n            \"columnName\": \"style\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"header\",\n            \"columnName\": \"header\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enableJs\",\n            \"columnName\": \"enableJs\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"loadWithBaseUrl\",\n            \"columnName\": \"loadWithBaseUrl\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"sourceUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_rssSources_sourceUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"sourceUrl\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_rssSources_sourceUrl` ON `${TABLE_NAME}` (`sourceUrl`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"bookmarks\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`time` INTEGER NOT NULL, `bookName` TEXT NOT NULL, `bookAuthor` TEXT NOT NULL, `chapterIndex` INTEGER NOT NULL, `chapterPos` INTEGER NOT NULL, `chapterName` TEXT NOT NULL, `bookText` TEXT NOT NULL, `content` TEXT NOT NULL, PRIMARY KEY(`time`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"time\",\n            \"columnName\": \"time\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookName\",\n            \"columnName\": \"bookName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookAuthor\",\n            \"columnName\": \"bookAuthor\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterIndex\",\n            \"columnName\": \"chapterIndex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterPos\",\n            \"columnName\": \"chapterPos\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterName\",\n            \"columnName\": \"chapterName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookText\",\n            \"columnName\": \"bookText\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"time\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_bookmarks_bookName_bookAuthor\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"bookName\",\n              \"bookAuthor\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_bookmarks_bookName_bookAuthor` ON `${TABLE_NAME}` (`bookName`, `bookAuthor`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssArticles\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`origin` TEXT NOT NULL, `sort` TEXT NOT NULL, `title` TEXT NOT NULL, `order` INTEGER NOT NULL, `link` TEXT NOT NULL, `pubDate` TEXT, `description` TEXT, `content` TEXT, `image` TEXT, `read` INTEGER NOT NULL, `variable` TEXT, PRIMARY KEY(`origin`, `link`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sort\",\n            \"columnName\": \"sort\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"link\",\n            \"columnName\": \"link\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"pubDate\",\n            \"columnName\": \"pubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"description\",\n            \"columnName\": \"description\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"image\",\n            \"columnName\": \"image\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"read\",\n            \"columnName\": \"read\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"origin\",\n            \"link\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssReadRecords\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`record` TEXT NOT NULL, `read` INTEGER NOT NULL, PRIMARY KEY(`record`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"record\",\n            \"columnName\": \"record\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"read\",\n            \"columnName\": \"read\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"record\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssStars\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`origin` TEXT NOT NULL, `sort` TEXT NOT NULL, `title` TEXT NOT NULL, `starTime` INTEGER NOT NULL, `link` TEXT NOT NULL, `pubDate` TEXT, `description` TEXT, `content` TEXT, `image` TEXT, `variable` TEXT, PRIMARY KEY(`origin`, `link`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sort\",\n            \"columnName\": \"sort\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"starTime\",\n            \"columnName\": \"starTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"link\",\n            \"columnName\": \"link\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"pubDate\",\n            \"columnName\": \"pubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"description\",\n            \"columnName\": \"description\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"image\",\n            \"columnName\": \"image\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"origin\",\n            \"link\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"txtTocRules\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `rule` TEXT NOT NULL, `serialNumber` INTEGER NOT NULL, `enable` INTEGER NOT NULL, PRIMARY KEY(`id`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"rule\",\n            \"columnName\": \"rule\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"serialNumber\",\n            \"columnName\": \"serialNumber\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enable\",\n            \"columnName\": \"enable\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"id\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"readRecord\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`deviceId` TEXT NOT NULL, `bookName` TEXT NOT NULL, `readTime` INTEGER NOT NULL, PRIMARY KEY(`deviceId`, `bookName`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"deviceId\",\n            \"columnName\": \"deviceId\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookName\",\n            \"columnName\": \"bookName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"readTime\",\n            \"columnName\": \"readTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"deviceId\",\n            \"bookName\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"httpTTS\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `url` TEXT NOT NULL, PRIMARY KEY(`id`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"id\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"caches\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`key` TEXT NOT NULL, `value` TEXT, `deadline` INTEGER NOT NULL, PRIMARY KEY(`key`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"key\",\n            \"columnName\": \"key\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"value\",\n            \"columnName\": \"value\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"deadline\",\n            \"columnName\": \"deadline\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"key\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_caches_key\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"key\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_caches_key` ON `${TABLE_NAME}` (`key`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"ruleSubs\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `url` TEXT NOT NULL, `type` INTEGER NOT NULL, `customOrder` INTEGER NOT NULL, `autoUpdate` INTEGER NOT NULL, `update` INTEGER NOT NULL, PRIMARY KEY(`id`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"autoUpdate\",\n            \"columnName\": \"autoUpdate\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"update\",\n            \"columnName\": \"update\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"id\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      }\n    ],\n    \"views\": [],\n    \"setupQueries\": [\n      \"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)\",\n      \"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '6dad1518f359667b4d740fc6a1f44a21')\"\n    ]\n  }\n}"
  },
  {
    "path": "app/schemas/io.legado.app.data.AppDatabase/34.json",
    "content": "{\n  \"formatVersion\": 1,\n  \"database\": {\n    \"version\": 34,\n    \"identityHash\": \"2e519f1f67ca16091cbc3891c1b71c66\",\n    \"entities\": [\n      {\n        \"tableName\": \"books\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookUrl` TEXT NOT NULL, `tocUrl` TEXT NOT NULL, `origin` TEXT NOT NULL, `originName` TEXT NOT NULL, `name` TEXT NOT NULL, `author` TEXT NOT NULL, `kind` TEXT, `customTag` TEXT, `coverUrl` TEXT, `customCoverUrl` TEXT, `intro` TEXT, `customIntro` TEXT, `charset` TEXT, `type` INTEGER NOT NULL, `group` INTEGER NOT NULL, `latestChapterTitle` TEXT, `latestChapterTime` INTEGER NOT NULL, `lastCheckTime` INTEGER NOT NULL, `lastCheckCount` INTEGER NOT NULL, `totalChapterNum` INTEGER NOT NULL, `durChapterTitle` TEXT, `durChapterIndex` INTEGER NOT NULL, `durChapterPos` INTEGER NOT NULL, `durChapterTime` INTEGER NOT NULL, `wordCount` TEXT, `canUpdate` INTEGER NOT NULL, `order` INTEGER NOT NULL, `originOrder` INTEGER NOT NULL, `variable` TEXT, `readConfig` TEXT, PRIMARY KEY(`bookUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"tocUrl\",\n            \"columnName\": \"tocUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"originName\",\n            \"columnName\": \"originName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"author\",\n            \"columnName\": \"author\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"kind\",\n            \"columnName\": \"kind\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customTag\",\n            \"columnName\": \"customTag\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"coverUrl\",\n            \"columnName\": \"coverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customCoverUrl\",\n            \"columnName\": \"customCoverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"intro\",\n            \"columnName\": \"intro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customIntro\",\n            \"columnName\": \"customIntro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"charset\",\n            \"columnName\": \"charset\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"group\",\n            \"columnName\": \"group\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"latestChapterTitle\",\n            \"columnName\": \"latestChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"latestChapterTime\",\n            \"columnName\": \"latestChapterTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"lastCheckTime\",\n            \"columnName\": \"lastCheckTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"lastCheckCount\",\n            \"columnName\": \"lastCheckCount\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"totalChapterNum\",\n            \"columnName\": \"totalChapterNum\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"durChapterTitle\",\n            \"columnName\": \"durChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"durChapterIndex\",\n            \"columnName\": \"durChapterIndex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"durChapterPos\",\n            \"columnName\": \"durChapterPos\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"durChapterTime\",\n            \"columnName\": \"durChapterTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"wordCount\",\n            \"columnName\": \"wordCount\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"canUpdate\",\n            \"columnName\": \"canUpdate\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"originOrder\",\n            \"columnName\": \"originOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"readConfig\",\n            \"columnName\": \"readConfig\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"bookUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_books_name_author\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"name\",\n              \"author\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_books_name_author` ON `${TABLE_NAME}` (`name`, `author`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"book_groups\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`groupId` INTEGER NOT NULL, `groupName` TEXT NOT NULL, `cover` TEXT, `order` INTEGER NOT NULL, `show` INTEGER NOT NULL, PRIMARY KEY(`groupId`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"groupId\",\n            \"columnName\": \"groupId\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"groupName\",\n            \"columnName\": \"groupName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"cover\",\n            \"columnName\": \"cover\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"show\",\n            \"columnName\": \"show\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"groupId\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"book_sources\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookSourceName` TEXT NOT NULL, `bookSourceGroup` TEXT, `bookSourceUrl` TEXT NOT NULL, `bookSourceType` INTEGER NOT NULL, `bookUrlPattern` TEXT, `customOrder` INTEGER NOT NULL, `enabled` INTEGER NOT NULL, `enabledExplore` INTEGER NOT NULL, `header` TEXT, `loginUrl` TEXT, `bookSourceComment` TEXT, `lastUpdateTime` INTEGER NOT NULL, `weight` INTEGER NOT NULL, `exploreUrl` TEXT, `ruleExplore` TEXT, `searchUrl` TEXT, `ruleSearch` TEXT, `ruleBookInfo` TEXT, `ruleToc` TEXT, `ruleContent` TEXT, PRIMARY KEY(`bookSourceUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookSourceName\",\n            \"columnName\": \"bookSourceName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookSourceGroup\",\n            \"columnName\": \"bookSourceGroup\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"bookSourceUrl\",\n            \"columnName\": \"bookSourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookSourceType\",\n            \"columnName\": \"bookSourceType\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookUrlPattern\",\n            \"columnName\": \"bookUrlPattern\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enabled\",\n            \"columnName\": \"enabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enabledExplore\",\n            \"columnName\": \"enabledExplore\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"header\",\n            \"columnName\": \"header\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUrl\",\n            \"columnName\": \"loginUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"bookSourceComment\",\n            \"columnName\": \"bookSourceComment\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"lastUpdateTime\",\n            \"columnName\": \"lastUpdateTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"weight\",\n            \"columnName\": \"weight\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"exploreUrl\",\n            \"columnName\": \"exploreUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleExplore\",\n            \"columnName\": \"ruleExplore\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"searchUrl\",\n            \"columnName\": \"searchUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleSearch\",\n            \"columnName\": \"ruleSearch\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleBookInfo\",\n            \"columnName\": \"ruleBookInfo\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleToc\",\n            \"columnName\": \"ruleToc\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleContent\",\n            \"columnName\": \"ruleContent\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"bookSourceUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_book_sources_bookSourceUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"bookSourceUrl\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_book_sources_bookSourceUrl` ON `${TABLE_NAME}` (`bookSourceUrl`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"chapters\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`url` TEXT NOT NULL, `title` TEXT NOT NULL, `baseUrl` TEXT NOT NULL, `bookUrl` TEXT NOT NULL, `index` INTEGER NOT NULL, `resourceUrl` TEXT, `tag` TEXT, `start` INTEGER, `end` INTEGER, `startFragmentId` TEXT, `endFragmentId` TEXT, `variable` TEXT, PRIMARY KEY(`url`, `bookUrl`), FOREIGN KEY(`bookUrl`) REFERENCES `books`(`bookUrl`) ON UPDATE NO ACTION ON DELETE CASCADE )\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"baseUrl\",\n            \"columnName\": \"baseUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"index\",\n            \"columnName\": \"index\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"resourceUrl\",\n            \"columnName\": \"resourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"tag\",\n            \"columnName\": \"tag\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"start\",\n            \"columnName\": \"start\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"end\",\n            \"columnName\": \"end\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"startFragmentId\",\n            \"columnName\": \"startFragmentId\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"endFragmentId\",\n            \"columnName\": \"endFragmentId\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"url\",\n            \"bookUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_chapters_bookUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"bookUrl\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_chapters_bookUrl` ON `${TABLE_NAME}` (`bookUrl`)\"\n          },\n          {\n            \"name\": \"index_chapters_bookUrl_index\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"bookUrl\",\n              \"index\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_chapters_bookUrl_index` ON `${TABLE_NAME}` (`bookUrl`, `index`)\"\n          }\n        ],\n        \"foreignKeys\": [\n          {\n            \"table\": \"books\",\n            \"onDelete\": \"CASCADE\",\n            \"onUpdate\": \"NO ACTION\",\n            \"columns\": [\n              \"bookUrl\"\n            ],\n            \"referencedColumns\": [\n              \"bookUrl\"\n            ]\n          }\n        ]\n      },\n      {\n        \"tableName\": \"replace_rules\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL, `group` TEXT, `pattern` TEXT NOT NULL, `replacement` TEXT NOT NULL, `scope` TEXT, `isEnabled` INTEGER NOT NULL, `isRegex` INTEGER NOT NULL, `sortOrder` INTEGER NOT NULL)\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"group\",\n            \"columnName\": \"group\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"pattern\",\n            \"columnName\": \"pattern\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"replacement\",\n            \"columnName\": \"replacement\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"scope\",\n            \"columnName\": \"scope\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"isEnabled\",\n            \"columnName\": \"isEnabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"isRegex\",\n            \"columnName\": \"isRegex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"sortOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"id\"\n          ],\n          \"autoGenerate\": true\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_replace_rules_id\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"id\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_replace_rules_id` ON `${TABLE_NAME}` (`id`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"searchBooks\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookUrl` TEXT NOT NULL, `origin` TEXT NOT NULL, `originName` TEXT NOT NULL, `type` INTEGER NOT NULL, `name` TEXT NOT NULL, `author` TEXT NOT NULL, `kind` TEXT, `coverUrl` TEXT, `intro` TEXT, `wordCount` TEXT, `latestChapterTitle` TEXT, `tocUrl` TEXT NOT NULL, `time` INTEGER NOT NULL, `variable` TEXT, `originOrder` INTEGER NOT NULL, PRIMARY KEY(`bookUrl`), FOREIGN KEY(`origin`) REFERENCES `book_sources`(`bookSourceUrl`) ON UPDATE NO ACTION ON DELETE CASCADE )\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"originName\",\n            \"columnName\": \"originName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"author\",\n            \"columnName\": \"author\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"kind\",\n            \"columnName\": \"kind\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"coverUrl\",\n            \"columnName\": \"coverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"intro\",\n            \"columnName\": \"intro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"wordCount\",\n            \"columnName\": \"wordCount\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"latestChapterTitle\",\n            \"columnName\": \"latestChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"tocUrl\",\n            \"columnName\": \"tocUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"time\",\n            \"columnName\": \"time\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"originOrder\",\n            \"columnName\": \"originOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"bookUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_searchBooks_bookUrl\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"bookUrl\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_searchBooks_bookUrl` ON `${TABLE_NAME}` (`bookUrl`)\"\n          },\n          {\n            \"name\": \"index_searchBooks_origin\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"origin\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_searchBooks_origin` ON `${TABLE_NAME}` (`origin`)\"\n          }\n        ],\n        \"foreignKeys\": [\n          {\n            \"table\": \"book_sources\",\n            \"onDelete\": \"CASCADE\",\n            \"onUpdate\": \"NO ACTION\",\n            \"columns\": [\n              \"origin\"\n            ],\n            \"referencedColumns\": [\n              \"bookSourceUrl\"\n            ]\n          }\n        ]\n      },\n      {\n        \"tableName\": \"search_keywords\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`word` TEXT NOT NULL, `usage` INTEGER NOT NULL, `lastUseTime` INTEGER NOT NULL, PRIMARY KEY(`word`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"word\",\n            \"columnName\": \"word\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"usage\",\n            \"columnName\": \"usage\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"lastUseTime\",\n            \"columnName\": \"lastUseTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"word\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_search_keywords_word\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"word\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_search_keywords_word` ON `${TABLE_NAME}` (`word`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"cookies\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`url` TEXT NOT NULL, `cookie` TEXT NOT NULL, PRIMARY KEY(`url`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"cookie\",\n            \"columnName\": \"cookie\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"url\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_cookies_url\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"url\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_cookies_url` ON `${TABLE_NAME}` (`url`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssSources\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`sourceUrl` TEXT NOT NULL, `sourceName` TEXT NOT NULL, `sourceIcon` TEXT NOT NULL, `sourceGroup` TEXT, `sourceComment` TEXT, `enabled` INTEGER NOT NULL, `sortUrl` TEXT, `singleUrl` INTEGER NOT NULL, `articleStyle` INTEGER NOT NULL, `ruleArticles` TEXT, `ruleNextPage` TEXT, `ruleTitle` TEXT, `rulePubDate` TEXT, `ruleDescription` TEXT, `ruleImage` TEXT, `ruleLink` TEXT, `ruleContent` TEXT, `style` TEXT, `header` TEXT, `enableJs` INTEGER NOT NULL, `loadWithBaseUrl` INTEGER NOT NULL, `customOrder` INTEGER NOT NULL, PRIMARY KEY(`sourceUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"sourceUrl\",\n            \"columnName\": \"sourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceName\",\n            \"columnName\": \"sourceName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceIcon\",\n            \"columnName\": \"sourceIcon\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceGroup\",\n            \"columnName\": \"sourceGroup\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"sourceComment\",\n            \"columnName\": \"sourceComment\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enabled\",\n            \"columnName\": \"enabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sortUrl\",\n            \"columnName\": \"sortUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"singleUrl\",\n            \"columnName\": \"singleUrl\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"articleStyle\",\n            \"columnName\": \"articleStyle\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"ruleArticles\",\n            \"columnName\": \"ruleArticles\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleNextPage\",\n            \"columnName\": \"ruleNextPage\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleTitle\",\n            \"columnName\": \"ruleTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"rulePubDate\",\n            \"columnName\": \"rulePubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleDescription\",\n            \"columnName\": \"ruleDescription\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleImage\",\n            \"columnName\": \"ruleImage\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleLink\",\n            \"columnName\": \"ruleLink\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleContent\",\n            \"columnName\": \"ruleContent\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"style\",\n            \"columnName\": \"style\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"header\",\n            \"columnName\": \"header\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enableJs\",\n            \"columnName\": \"enableJs\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"loadWithBaseUrl\",\n            \"columnName\": \"loadWithBaseUrl\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"sourceUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_rssSources_sourceUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"sourceUrl\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_rssSources_sourceUrl` ON `${TABLE_NAME}` (`sourceUrl`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"bookmarks\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`time` INTEGER NOT NULL, `bookName` TEXT NOT NULL, `bookAuthor` TEXT NOT NULL, `chapterIndex` INTEGER NOT NULL, `chapterPos` INTEGER NOT NULL, `chapterName` TEXT NOT NULL, `bookText` TEXT NOT NULL, `content` TEXT NOT NULL, PRIMARY KEY(`time`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"time\",\n            \"columnName\": \"time\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookName\",\n            \"columnName\": \"bookName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookAuthor\",\n            \"columnName\": \"bookAuthor\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterIndex\",\n            \"columnName\": \"chapterIndex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterPos\",\n            \"columnName\": \"chapterPos\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterName\",\n            \"columnName\": \"chapterName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookText\",\n            \"columnName\": \"bookText\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"time\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_bookmarks_bookName_bookAuthor\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"bookName\",\n              \"bookAuthor\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_bookmarks_bookName_bookAuthor` ON `${TABLE_NAME}` (`bookName`, `bookAuthor`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssArticles\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`origin` TEXT NOT NULL, `sort` TEXT NOT NULL, `title` TEXT NOT NULL, `order` INTEGER NOT NULL, `link` TEXT NOT NULL, `pubDate` TEXT, `description` TEXT, `content` TEXT, `image` TEXT, `read` INTEGER NOT NULL, `variable` TEXT, PRIMARY KEY(`origin`, `link`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sort\",\n            \"columnName\": \"sort\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"link\",\n            \"columnName\": \"link\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"pubDate\",\n            \"columnName\": \"pubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"description\",\n            \"columnName\": \"description\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"image\",\n            \"columnName\": \"image\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"read\",\n            \"columnName\": \"read\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"origin\",\n            \"link\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssReadRecords\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`record` TEXT NOT NULL, `read` INTEGER NOT NULL, PRIMARY KEY(`record`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"record\",\n            \"columnName\": \"record\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"read\",\n            \"columnName\": \"read\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"record\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssStars\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`origin` TEXT NOT NULL, `sort` TEXT NOT NULL, `title` TEXT NOT NULL, `starTime` INTEGER NOT NULL, `link` TEXT NOT NULL, `pubDate` TEXT, `description` TEXT, `content` TEXT, `image` TEXT, `variable` TEXT, PRIMARY KEY(`origin`, `link`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sort\",\n            \"columnName\": \"sort\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"starTime\",\n            \"columnName\": \"starTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"link\",\n            \"columnName\": \"link\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"pubDate\",\n            \"columnName\": \"pubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"description\",\n            \"columnName\": \"description\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"image\",\n            \"columnName\": \"image\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"origin\",\n            \"link\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"txtTocRules\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `rule` TEXT NOT NULL, `serialNumber` INTEGER NOT NULL, `enable` INTEGER NOT NULL, PRIMARY KEY(`id`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"rule\",\n            \"columnName\": \"rule\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"serialNumber\",\n            \"columnName\": \"serialNumber\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enable\",\n            \"columnName\": \"enable\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"id\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"readRecord\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`deviceId` TEXT NOT NULL, `bookName` TEXT NOT NULL, `readTime` INTEGER NOT NULL, PRIMARY KEY(`deviceId`, `bookName`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"deviceId\",\n            \"columnName\": \"deviceId\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookName\",\n            \"columnName\": \"bookName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"readTime\",\n            \"columnName\": \"readTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"deviceId\",\n            \"bookName\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"httpTTS\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `url` TEXT NOT NULL, PRIMARY KEY(`id`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"id\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"caches\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`key` TEXT NOT NULL, `value` TEXT, `deadline` INTEGER NOT NULL, PRIMARY KEY(`key`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"key\",\n            \"columnName\": \"key\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"value\",\n            \"columnName\": \"value\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"deadline\",\n            \"columnName\": \"deadline\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"key\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_caches_key\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"key\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_caches_key` ON `${TABLE_NAME}` (`key`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"ruleSubs\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `url` TEXT NOT NULL, `type` INTEGER NOT NULL, `customOrder` INTEGER NOT NULL, `autoUpdate` INTEGER NOT NULL, `update` INTEGER NOT NULL, PRIMARY KEY(`id`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"autoUpdate\",\n            \"columnName\": \"autoUpdate\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"update\",\n            \"columnName\": \"update\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"id\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      }\n    ],\n    \"views\": [],\n    \"setupQueries\": [\n      \"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)\",\n      \"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '2e519f1f67ca16091cbc3891c1b71c66')\"\n    ]\n  }\n}"
  },
  {
    "path": "app/schemas/io.legado.app.data.AppDatabase/35.json",
    "content": "{\n  \"formatVersion\": 1,\n  \"database\": {\n    \"version\": 35,\n    \"identityHash\": \"25948a8defe4d091514bb725b4db7683\",\n    \"entities\": [\n      {\n        \"tableName\": \"books\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookUrl` TEXT NOT NULL, `tocUrl` TEXT NOT NULL, `origin` TEXT NOT NULL, `originName` TEXT NOT NULL, `name` TEXT NOT NULL, `author` TEXT NOT NULL, `kind` TEXT, `customTag` TEXT, `coverUrl` TEXT, `customCoverUrl` TEXT, `intro` TEXT, `customIntro` TEXT, `charset` TEXT, `type` INTEGER NOT NULL, `group` INTEGER NOT NULL, `latestChapterTitle` TEXT, `latestChapterTime` INTEGER NOT NULL, `lastCheckTime` INTEGER NOT NULL, `lastCheckCount` INTEGER NOT NULL, `totalChapterNum` INTEGER NOT NULL, `durChapterTitle` TEXT, `durChapterIndex` INTEGER NOT NULL, `durChapterPos` INTEGER NOT NULL, `durChapterTime` INTEGER NOT NULL, `wordCount` TEXT, `canUpdate` INTEGER NOT NULL, `order` INTEGER NOT NULL, `originOrder` INTEGER NOT NULL, `variable` TEXT, `readConfig` TEXT, PRIMARY KEY(`bookUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"tocUrl\",\n            \"columnName\": \"tocUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"originName\",\n            \"columnName\": \"originName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"author\",\n            \"columnName\": \"author\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"kind\",\n            \"columnName\": \"kind\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customTag\",\n            \"columnName\": \"customTag\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"coverUrl\",\n            \"columnName\": \"coverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customCoverUrl\",\n            \"columnName\": \"customCoverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"intro\",\n            \"columnName\": \"intro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customIntro\",\n            \"columnName\": \"customIntro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"charset\",\n            \"columnName\": \"charset\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"group\",\n            \"columnName\": \"group\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"latestChapterTitle\",\n            \"columnName\": \"latestChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"latestChapterTime\",\n            \"columnName\": \"latestChapterTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"lastCheckTime\",\n            \"columnName\": \"lastCheckTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"lastCheckCount\",\n            \"columnName\": \"lastCheckCount\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"totalChapterNum\",\n            \"columnName\": \"totalChapterNum\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"durChapterTitle\",\n            \"columnName\": \"durChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"durChapterIndex\",\n            \"columnName\": \"durChapterIndex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"durChapterPos\",\n            \"columnName\": \"durChapterPos\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"durChapterTime\",\n            \"columnName\": \"durChapterTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"wordCount\",\n            \"columnName\": \"wordCount\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"canUpdate\",\n            \"columnName\": \"canUpdate\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"originOrder\",\n            \"columnName\": \"originOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"readConfig\",\n            \"columnName\": \"readConfig\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"bookUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_books_name_author\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"name\",\n              \"author\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_books_name_author` ON `${TABLE_NAME}` (`name`, `author`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"book_groups\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`groupId` INTEGER NOT NULL, `groupName` TEXT NOT NULL, `cover` TEXT, `order` INTEGER NOT NULL, `show` INTEGER NOT NULL, PRIMARY KEY(`groupId`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"groupId\",\n            \"columnName\": \"groupId\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"groupName\",\n            \"columnName\": \"groupName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"cover\",\n            \"columnName\": \"cover\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"show\",\n            \"columnName\": \"show\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"groupId\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"book_sources\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookSourceName` TEXT NOT NULL, `bookSourceGroup` TEXT, `bookSourceUrl` TEXT NOT NULL, `bookSourceType` INTEGER NOT NULL, `bookUrlPattern` TEXT, `concurrentRate` TEXT, `customOrder` INTEGER NOT NULL, `enabled` INTEGER NOT NULL, `enabledExplore` INTEGER NOT NULL, `header` TEXT, `loginUrl` TEXT, `loginUi` TEXT, `loginCheckJs` TEXT, `bookSourceComment` TEXT, `lastUpdateTime` INTEGER NOT NULL, `weight` INTEGER NOT NULL, `exploreUrl` TEXT, `ruleExplore` TEXT, `searchUrl` TEXT, `ruleSearch` TEXT, `ruleBookInfo` TEXT, `ruleToc` TEXT, `ruleContent` TEXT, PRIMARY KEY(`bookSourceUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookSourceName\",\n            \"columnName\": \"bookSourceName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookSourceGroup\",\n            \"columnName\": \"bookSourceGroup\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"bookSourceUrl\",\n            \"columnName\": \"bookSourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookSourceType\",\n            \"columnName\": \"bookSourceType\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookUrlPattern\",\n            \"columnName\": \"bookUrlPattern\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"concurrentRate\",\n            \"columnName\": \"concurrentRate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enabled\",\n            \"columnName\": \"enabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enabledExplore\",\n            \"columnName\": \"enabledExplore\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"header\",\n            \"columnName\": \"header\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUrl\",\n            \"columnName\": \"loginUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUi\",\n            \"columnName\": \"loginUi\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginCheckJs\",\n            \"columnName\": \"loginCheckJs\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"bookSourceComment\",\n            \"columnName\": \"bookSourceComment\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"lastUpdateTime\",\n            \"columnName\": \"lastUpdateTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"weight\",\n            \"columnName\": \"weight\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"exploreUrl\",\n            \"columnName\": \"exploreUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleExplore\",\n            \"columnName\": \"ruleExplore\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"searchUrl\",\n            \"columnName\": \"searchUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleSearch\",\n            \"columnName\": \"ruleSearch\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleBookInfo\",\n            \"columnName\": \"ruleBookInfo\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleToc\",\n            \"columnName\": \"ruleToc\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleContent\",\n            \"columnName\": \"ruleContent\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"bookSourceUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_book_sources_bookSourceUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"bookSourceUrl\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_book_sources_bookSourceUrl` ON `${TABLE_NAME}` (`bookSourceUrl`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"chapters\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`url` TEXT NOT NULL, `title` TEXT NOT NULL, `baseUrl` TEXT NOT NULL, `bookUrl` TEXT NOT NULL, `index` INTEGER NOT NULL, `resourceUrl` TEXT, `tag` TEXT, `start` INTEGER, `end` INTEGER, `startFragmentId` TEXT, `endFragmentId` TEXT, `variable` TEXT, PRIMARY KEY(`url`, `bookUrl`), FOREIGN KEY(`bookUrl`) REFERENCES `books`(`bookUrl`) ON UPDATE NO ACTION ON DELETE CASCADE )\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"baseUrl\",\n            \"columnName\": \"baseUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"index\",\n            \"columnName\": \"index\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"resourceUrl\",\n            \"columnName\": \"resourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"tag\",\n            \"columnName\": \"tag\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"start\",\n            \"columnName\": \"start\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"end\",\n            \"columnName\": \"end\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"startFragmentId\",\n            \"columnName\": \"startFragmentId\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"endFragmentId\",\n            \"columnName\": \"endFragmentId\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"url\",\n            \"bookUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_chapters_bookUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"bookUrl\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_chapters_bookUrl` ON `${TABLE_NAME}` (`bookUrl`)\"\n          },\n          {\n            \"name\": \"index_chapters_bookUrl_index\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"bookUrl\",\n              \"index\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_chapters_bookUrl_index` ON `${TABLE_NAME}` (`bookUrl`, `index`)\"\n          }\n        ],\n        \"foreignKeys\": [\n          {\n            \"table\": \"books\",\n            \"onDelete\": \"CASCADE\",\n            \"onUpdate\": \"NO ACTION\",\n            \"columns\": [\n              \"bookUrl\"\n            ],\n            \"referencedColumns\": [\n              \"bookUrl\"\n            ]\n          }\n        ]\n      },\n      {\n        \"tableName\": \"replace_rules\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL, `group` TEXT, `pattern` TEXT NOT NULL, `replacement` TEXT NOT NULL, `scope` TEXT, `isEnabled` INTEGER NOT NULL, `isRegex` INTEGER NOT NULL, `sortOrder` INTEGER NOT NULL)\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"group\",\n            \"columnName\": \"group\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"pattern\",\n            \"columnName\": \"pattern\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"replacement\",\n            \"columnName\": \"replacement\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"scope\",\n            \"columnName\": \"scope\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"isEnabled\",\n            \"columnName\": \"isEnabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"isRegex\",\n            \"columnName\": \"isRegex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"sortOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"id\"\n          ],\n          \"autoGenerate\": true\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_replace_rules_id\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"id\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_replace_rules_id` ON `${TABLE_NAME}` (`id`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"searchBooks\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookUrl` TEXT NOT NULL, `origin` TEXT NOT NULL, `originName` TEXT NOT NULL, `type` INTEGER NOT NULL, `name` TEXT NOT NULL, `author` TEXT NOT NULL, `kind` TEXT, `coverUrl` TEXT, `intro` TEXT, `wordCount` TEXT, `latestChapterTitle` TEXT, `tocUrl` TEXT NOT NULL, `time` INTEGER NOT NULL, `variable` TEXT, `originOrder` INTEGER NOT NULL, PRIMARY KEY(`bookUrl`), FOREIGN KEY(`origin`) REFERENCES `book_sources`(`bookSourceUrl`) ON UPDATE NO ACTION ON DELETE CASCADE )\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"originName\",\n            \"columnName\": \"originName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"author\",\n            \"columnName\": \"author\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"kind\",\n            \"columnName\": \"kind\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"coverUrl\",\n            \"columnName\": \"coverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"intro\",\n            \"columnName\": \"intro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"wordCount\",\n            \"columnName\": \"wordCount\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"latestChapterTitle\",\n            \"columnName\": \"latestChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"tocUrl\",\n            \"columnName\": \"tocUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"time\",\n            \"columnName\": \"time\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"originOrder\",\n            \"columnName\": \"originOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"bookUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_searchBooks_bookUrl\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"bookUrl\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_searchBooks_bookUrl` ON `${TABLE_NAME}` (`bookUrl`)\"\n          },\n          {\n            \"name\": \"index_searchBooks_origin\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"origin\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_searchBooks_origin` ON `${TABLE_NAME}` (`origin`)\"\n          }\n        ],\n        \"foreignKeys\": [\n          {\n            \"table\": \"book_sources\",\n            \"onDelete\": \"CASCADE\",\n            \"onUpdate\": \"NO ACTION\",\n            \"columns\": [\n              \"origin\"\n            ],\n            \"referencedColumns\": [\n              \"bookSourceUrl\"\n            ]\n          }\n        ]\n      },\n      {\n        \"tableName\": \"search_keywords\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`word` TEXT NOT NULL, `usage` INTEGER NOT NULL, `lastUseTime` INTEGER NOT NULL, PRIMARY KEY(`word`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"word\",\n            \"columnName\": \"word\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"usage\",\n            \"columnName\": \"usage\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"lastUseTime\",\n            \"columnName\": \"lastUseTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"word\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_search_keywords_word\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"word\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_search_keywords_word` ON `${TABLE_NAME}` (`word`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"cookies\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`url` TEXT NOT NULL, `cookie` TEXT NOT NULL, PRIMARY KEY(`url`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"cookie\",\n            \"columnName\": \"cookie\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"url\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_cookies_url\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"url\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_cookies_url` ON `${TABLE_NAME}` (`url`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssSources\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`sourceUrl` TEXT NOT NULL, `sourceName` TEXT NOT NULL, `sourceIcon` TEXT NOT NULL, `sourceGroup` TEXT, `sourceComment` TEXT, `enabled` INTEGER NOT NULL, `sortUrl` TEXT, `singleUrl` INTEGER NOT NULL, `articleStyle` INTEGER NOT NULL, `ruleArticles` TEXT, `ruleNextPage` TEXT, `ruleTitle` TEXT, `rulePubDate` TEXT, `ruleDescription` TEXT, `ruleImage` TEXT, `ruleLink` TEXT, `ruleContent` TEXT, `style` TEXT, `header` TEXT, `enableJs` INTEGER NOT NULL, `loadWithBaseUrl` INTEGER NOT NULL, `customOrder` INTEGER NOT NULL, PRIMARY KEY(`sourceUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"sourceUrl\",\n            \"columnName\": \"sourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceName\",\n            \"columnName\": \"sourceName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceIcon\",\n            \"columnName\": \"sourceIcon\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceGroup\",\n            \"columnName\": \"sourceGroup\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"sourceComment\",\n            \"columnName\": \"sourceComment\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enabled\",\n            \"columnName\": \"enabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sortUrl\",\n            \"columnName\": \"sortUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"singleUrl\",\n            \"columnName\": \"singleUrl\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"articleStyle\",\n            \"columnName\": \"articleStyle\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"ruleArticles\",\n            \"columnName\": \"ruleArticles\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleNextPage\",\n            \"columnName\": \"ruleNextPage\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleTitle\",\n            \"columnName\": \"ruleTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"rulePubDate\",\n            \"columnName\": \"rulePubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleDescription\",\n            \"columnName\": \"ruleDescription\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleImage\",\n            \"columnName\": \"ruleImage\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleLink\",\n            \"columnName\": \"ruleLink\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleContent\",\n            \"columnName\": \"ruleContent\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"style\",\n            \"columnName\": \"style\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"header\",\n            \"columnName\": \"header\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enableJs\",\n            \"columnName\": \"enableJs\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"loadWithBaseUrl\",\n            \"columnName\": \"loadWithBaseUrl\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"sourceUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_rssSources_sourceUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"sourceUrl\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_rssSources_sourceUrl` ON `${TABLE_NAME}` (`sourceUrl`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"bookmarks\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`time` INTEGER NOT NULL, `bookName` TEXT NOT NULL, `bookAuthor` TEXT NOT NULL, `chapterIndex` INTEGER NOT NULL, `chapterPos` INTEGER NOT NULL, `chapterName` TEXT NOT NULL, `bookText` TEXT NOT NULL, `content` TEXT NOT NULL, PRIMARY KEY(`time`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"time\",\n            \"columnName\": \"time\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookName\",\n            \"columnName\": \"bookName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookAuthor\",\n            \"columnName\": \"bookAuthor\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterIndex\",\n            \"columnName\": \"chapterIndex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterPos\",\n            \"columnName\": \"chapterPos\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterName\",\n            \"columnName\": \"chapterName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookText\",\n            \"columnName\": \"bookText\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"time\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_bookmarks_bookName_bookAuthor\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"bookName\",\n              \"bookAuthor\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_bookmarks_bookName_bookAuthor` ON `${TABLE_NAME}` (`bookName`, `bookAuthor`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssArticles\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`origin` TEXT NOT NULL, `sort` TEXT NOT NULL, `title` TEXT NOT NULL, `order` INTEGER NOT NULL, `link` TEXT NOT NULL, `pubDate` TEXT, `description` TEXT, `content` TEXT, `image` TEXT, `read` INTEGER NOT NULL, `variable` TEXT, PRIMARY KEY(`origin`, `link`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sort\",\n            \"columnName\": \"sort\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"link\",\n            \"columnName\": \"link\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"pubDate\",\n            \"columnName\": \"pubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"description\",\n            \"columnName\": \"description\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"image\",\n            \"columnName\": \"image\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"read\",\n            \"columnName\": \"read\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"origin\",\n            \"link\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssReadRecords\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`record` TEXT NOT NULL, `read` INTEGER NOT NULL, PRIMARY KEY(`record`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"record\",\n            \"columnName\": \"record\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"read\",\n            \"columnName\": \"read\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"record\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssStars\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`origin` TEXT NOT NULL, `sort` TEXT NOT NULL, `title` TEXT NOT NULL, `starTime` INTEGER NOT NULL, `link` TEXT NOT NULL, `pubDate` TEXT, `description` TEXT, `content` TEXT, `image` TEXT, `variable` TEXT, PRIMARY KEY(`origin`, `link`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sort\",\n            \"columnName\": \"sort\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"starTime\",\n            \"columnName\": \"starTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"link\",\n            \"columnName\": \"link\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"pubDate\",\n            \"columnName\": \"pubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"description\",\n            \"columnName\": \"description\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"image\",\n            \"columnName\": \"image\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"origin\",\n            \"link\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"txtTocRules\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `rule` TEXT NOT NULL, `serialNumber` INTEGER NOT NULL, `enable` INTEGER NOT NULL, PRIMARY KEY(`id`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"rule\",\n            \"columnName\": \"rule\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"serialNumber\",\n            \"columnName\": \"serialNumber\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enable\",\n            \"columnName\": \"enable\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"id\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"readRecord\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`deviceId` TEXT NOT NULL, `bookName` TEXT NOT NULL, `readTime` INTEGER NOT NULL, PRIMARY KEY(`deviceId`, `bookName`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"deviceId\",\n            \"columnName\": \"deviceId\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookName\",\n            \"columnName\": \"bookName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"readTime\",\n            \"columnName\": \"readTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"deviceId\",\n            \"bookName\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"httpTTS\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `url` TEXT NOT NULL, PRIMARY KEY(`id`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"id\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"caches\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`key` TEXT NOT NULL, `value` TEXT, `deadline` INTEGER NOT NULL, PRIMARY KEY(`key`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"key\",\n            \"columnName\": \"key\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"value\",\n            \"columnName\": \"value\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"deadline\",\n            \"columnName\": \"deadline\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"key\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_caches_key\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"key\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_caches_key` ON `${TABLE_NAME}` (`key`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"ruleSubs\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `url` TEXT NOT NULL, `type` INTEGER NOT NULL, `customOrder` INTEGER NOT NULL, `autoUpdate` INTEGER NOT NULL, `update` INTEGER NOT NULL, PRIMARY KEY(`id`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"autoUpdate\",\n            \"columnName\": \"autoUpdate\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"update\",\n            \"columnName\": \"update\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"id\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      }\n    ],\n    \"views\": [],\n    \"setupQueries\": [\n      \"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)\",\n      \"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '25948a8defe4d091514bb725b4db7683')\"\n    ]\n  }\n}"
  },
  {
    "path": "app/schemas/io.legado.app.data.AppDatabase/36.json",
    "content": "{\n  \"formatVersion\": 1,\n  \"database\": {\n    \"version\": 36,\n    \"identityHash\": \"25948a8defe4d091514bb725b4db7683\",\n    \"entities\": [\n      {\n        \"tableName\": \"books\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookUrl` TEXT NOT NULL, `tocUrl` TEXT NOT NULL, `origin` TEXT NOT NULL, `originName` TEXT NOT NULL, `name` TEXT NOT NULL, `author` TEXT NOT NULL, `kind` TEXT, `customTag` TEXT, `coverUrl` TEXT, `customCoverUrl` TEXT, `intro` TEXT, `customIntro` TEXT, `charset` TEXT, `type` INTEGER NOT NULL, `group` INTEGER NOT NULL, `latestChapterTitle` TEXT, `latestChapterTime` INTEGER NOT NULL, `lastCheckTime` INTEGER NOT NULL, `lastCheckCount` INTEGER NOT NULL, `totalChapterNum` INTEGER NOT NULL, `durChapterTitle` TEXT, `durChapterIndex` INTEGER NOT NULL, `durChapterPos` INTEGER NOT NULL, `durChapterTime` INTEGER NOT NULL, `wordCount` TEXT, `canUpdate` INTEGER NOT NULL, `order` INTEGER NOT NULL, `originOrder` INTEGER NOT NULL, `variable` TEXT, `readConfig` TEXT, PRIMARY KEY(`bookUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"tocUrl\",\n            \"columnName\": \"tocUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"originName\",\n            \"columnName\": \"originName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"author\",\n            \"columnName\": \"author\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"kind\",\n            \"columnName\": \"kind\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customTag\",\n            \"columnName\": \"customTag\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"coverUrl\",\n            \"columnName\": \"coverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customCoverUrl\",\n            \"columnName\": \"customCoverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"intro\",\n            \"columnName\": \"intro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customIntro\",\n            \"columnName\": \"customIntro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"charset\",\n            \"columnName\": \"charset\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"group\",\n            \"columnName\": \"group\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"latestChapterTitle\",\n            \"columnName\": \"latestChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"latestChapterTime\",\n            \"columnName\": \"latestChapterTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"lastCheckTime\",\n            \"columnName\": \"lastCheckTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"lastCheckCount\",\n            \"columnName\": \"lastCheckCount\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"totalChapterNum\",\n            \"columnName\": \"totalChapterNum\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"durChapterTitle\",\n            \"columnName\": \"durChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"durChapterIndex\",\n            \"columnName\": \"durChapterIndex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"durChapterPos\",\n            \"columnName\": \"durChapterPos\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"durChapterTime\",\n            \"columnName\": \"durChapterTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"wordCount\",\n            \"columnName\": \"wordCount\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"canUpdate\",\n            \"columnName\": \"canUpdate\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"originOrder\",\n            \"columnName\": \"originOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"readConfig\",\n            \"columnName\": \"readConfig\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"bookUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_books_name_author\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"name\",\n              \"author\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_books_name_author` ON `${TABLE_NAME}` (`name`, `author`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"book_groups\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`groupId` INTEGER NOT NULL, `groupName` TEXT NOT NULL, `cover` TEXT, `order` INTEGER NOT NULL, `show` INTEGER NOT NULL, PRIMARY KEY(`groupId`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"groupId\",\n            \"columnName\": \"groupId\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"groupName\",\n            \"columnName\": \"groupName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"cover\",\n            \"columnName\": \"cover\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"show\",\n            \"columnName\": \"show\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"groupId\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"book_sources\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookSourceName` TEXT NOT NULL, `bookSourceGroup` TEXT, `bookSourceUrl` TEXT NOT NULL, `bookSourceType` INTEGER NOT NULL, `bookUrlPattern` TEXT, `concurrentRate` TEXT, `customOrder` INTEGER NOT NULL, `enabled` INTEGER NOT NULL, `enabledExplore` INTEGER NOT NULL, `header` TEXT, `loginUrl` TEXT, `loginUi` TEXT, `loginCheckJs` TEXT, `bookSourceComment` TEXT, `lastUpdateTime` INTEGER NOT NULL, `weight` INTEGER NOT NULL, `exploreUrl` TEXT, `ruleExplore` TEXT, `searchUrl` TEXT, `ruleSearch` TEXT, `ruleBookInfo` TEXT, `ruleToc` TEXT, `ruleContent` TEXT, PRIMARY KEY(`bookSourceUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookSourceName\",\n            \"columnName\": \"bookSourceName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookSourceGroup\",\n            \"columnName\": \"bookSourceGroup\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"bookSourceUrl\",\n            \"columnName\": \"bookSourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookSourceType\",\n            \"columnName\": \"bookSourceType\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookUrlPattern\",\n            \"columnName\": \"bookUrlPattern\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"concurrentRate\",\n            \"columnName\": \"concurrentRate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enabled\",\n            \"columnName\": \"enabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enabledExplore\",\n            \"columnName\": \"enabledExplore\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"header\",\n            \"columnName\": \"header\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUrl\",\n            \"columnName\": \"loginUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUi\",\n            \"columnName\": \"loginUi\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginCheckJs\",\n            \"columnName\": \"loginCheckJs\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"bookSourceComment\",\n            \"columnName\": \"bookSourceComment\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"lastUpdateTime\",\n            \"columnName\": \"lastUpdateTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"weight\",\n            \"columnName\": \"weight\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"exploreUrl\",\n            \"columnName\": \"exploreUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleExplore\",\n            \"columnName\": \"ruleExplore\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"searchUrl\",\n            \"columnName\": \"searchUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleSearch\",\n            \"columnName\": \"ruleSearch\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleBookInfo\",\n            \"columnName\": \"ruleBookInfo\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleToc\",\n            \"columnName\": \"ruleToc\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleContent\",\n            \"columnName\": \"ruleContent\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"bookSourceUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_book_sources_bookSourceUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"bookSourceUrl\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_book_sources_bookSourceUrl` ON `${TABLE_NAME}` (`bookSourceUrl`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"chapters\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`url` TEXT NOT NULL, `title` TEXT NOT NULL, `baseUrl` TEXT NOT NULL, `bookUrl` TEXT NOT NULL, `index` INTEGER NOT NULL, `resourceUrl` TEXT, `tag` TEXT, `start` INTEGER, `end` INTEGER, `startFragmentId` TEXT, `endFragmentId` TEXT, `variable` TEXT, PRIMARY KEY(`url`, `bookUrl`), FOREIGN KEY(`bookUrl`) REFERENCES `books`(`bookUrl`) ON UPDATE NO ACTION ON DELETE CASCADE )\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"baseUrl\",\n            \"columnName\": \"baseUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"index\",\n            \"columnName\": \"index\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"resourceUrl\",\n            \"columnName\": \"resourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"tag\",\n            \"columnName\": \"tag\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"start\",\n            \"columnName\": \"start\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"end\",\n            \"columnName\": \"end\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"startFragmentId\",\n            \"columnName\": \"startFragmentId\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"endFragmentId\",\n            \"columnName\": \"endFragmentId\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"url\",\n            \"bookUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_chapters_bookUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"bookUrl\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_chapters_bookUrl` ON `${TABLE_NAME}` (`bookUrl`)\"\n          },\n          {\n            \"name\": \"index_chapters_bookUrl_index\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"bookUrl\",\n              \"index\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_chapters_bookUrl_index` ON `${TABLE_NAME}` (`bookUrl`, `index`)\"\n          }\n        ],\n        \"foreignKeys\": [\n          {\n            \"table\": \"books\",\n            \"onDelete\": \"CASCADE\",\n            \"onUpdate\": \"NO ACTION\",\n            \"columns\": [\n              \"bookUrl\"\n            ],\n            \"referencedColumns\": [\n              \"bookUrl\"\n            ]\n          }\n        ]\n      },\n      {\n        \"tableName\": \"replace_rules\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL, `group` TEXT, `pattern` TEXT NOT NULL, `replacement` TEXT NOT NULL, `scope` TEXT, `isEnabled` INTEGER NOT NULL, `isRegex` INTEGER NOT NULL, `sortOrder` INTEGER NOT NULL)\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"group\",\n            \"columnName\": \"group\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"pattern\",\n            \"columnName\": \"pattern\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"replacement\",\n            \"columnName\": \"replacement\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"scope\",\n            \"columnName\": \"scope\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"isEnabled\",\n            \"columnName\": \"isEnabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"isRegex\",\n            \"columnName\": \"isRegex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"sortOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"id\"\n          ],\n          \"autoGenerate\": true\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_replace_rules_id\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"id\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_replace_rules_id` ON `${TABLE_NAME}` (`id`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"searchBooks\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookUrl` TEXT NOT NULL, `origin` TEXT NOT NULL, `originName` TEXT NOT NULL, `type` INTEGER NOT NULL, `name` TEXT NOT NULL, `author` TEXT NOT NULL, `kind` TEXT, `coverUrl` TEXT, `intro` TEXT, `wordCount` TEXT, `latestChapterTitle` TEXT, `tocUrl` TEXT NOT NULL, `time` INTEGER NOT NULL, `variable` TEXT, `originOrder` INTEGER NOT NULL, PRIMARY KEY(`bookUrl`), FOREIGN KEY(`origin`) REFERENCES `book_sources`(`bookSourceUrl`) ON UPDATE NO ACTION ON DELETE CASCADE )\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"originName\",\n            \"columnName\": \"originName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"author\",\n            \"columnName\": \"author\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"kind\",\n            \"columnName\": \"kind\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"coverUrl\",\n            \"columnName\": \"coverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"intro\",\n            \"columnName\": \"intro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"wordCount\",\n            \"columnName\": \"wordCount\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"latestChapterTitle\",\n            \"columnName\": \"latestChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"tocUrl\",\n            \"columnName\": \"tocUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"time\",\n            \"columnName\": \"time\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"originOrder\",\n            \"columnName\": \"originOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"bookUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_searchBooks_bookUrl\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"bookUrl\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_searchBooks_bookUrl` ON `${TABLE_NAME}` (`bookUrl`)\"\n          },\n          {\n            \"name\": \"index_searchBooks_origin\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"origin\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_searchBooks_origin` ON `${TABLE_NAME}` (`origin`)\"\n          }\n        ],\n        \"foreignKeys\": [\n          {\n            \"table\": \"book_sources\",\n            \"onDelete\": \"CASCADE\",\n            \"onUpdate\": \"NO ACTION\",\n            \"columns\": [\n              \"origin\"\n            ],\n            \"referencedColumns\": [\n              \"bookSourceUrl\"\n            ]\n          }\n        ]\n      },\n      {\n        \"tableName\": \"search_keywords\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`word` TEXT NOT NULL, `usage` INTEGER NOT NULL, `lastUseTime` INTEGER NOT NULL, PRIMARY KEY(`word`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"word\",\n            \"columnName\": \"word\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"usage\",\n            \"columnName\": \"usage\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"lastUseTime\",\n            \"columnName\": \"lastUseTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"word\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_search_keywords_word\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"word\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_search_keywords_word` ON `${TABLE_NAME}` (`word`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"cookies\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`url` TEXT NOT NULL, `cookie` TEXT NOT NULL, PRIMARY KEY(`url`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"cookie\",\n            \"columnName\": \"cookie\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"url\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_cookies_url\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"url\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_cookies_url` ON `${TABLE_NAME}` (`url`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssSources\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`sourceUrl` TEXT NOT NULL, `sourceName` TEXT NOT NULL, `sourceIcon` TEXT NOT NULL, `sourceGroup` TEXT, `sourceComment` TEXT, `enabled` INTEGER NOT NULL, `sortUrl` TEXT, `singleUrl` INTEGER NOT NULL, `articleStyle` INTEGER NOT NULL, `ruleArticles` TEXT, `ruleNextPage` TEXT, `ruleTitle` TEXT, `rulePubDate` TEXT, `ruleDescription` TEXT, `ruleImage` TEXT, `ruleLink` TEXT, `ruleContent` TEXT, `style` TEXT, `header` TEXT, `enableJs` INTEGER NOT NULL, `loadWithBaseUrl` INTEGER NOT NULL, `customOrder` INTEGER NOT NULL, PRIMARY KEY(`sourceUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"sourceUrl\",\n            \"columnName\": \"sourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceName\",\n            \"columnName\": \"sourceName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceIcon\",\n            \"columnName\": \"sourceIcon\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceGroup\",\n            \"columnName\": \"sourceGroup\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"sourceComment\",\n            \"columnName\": \"sourceComment\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enabled\",\n            \"columnName\": \"enabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sortUrl\",\n            \"columnName\": \"sortUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"singleUrl\",\n            \"columnName\": \"singleUrl\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"articleStyle\",\n            \"columnName\": \"articleStyle\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"ruleArticles\",\n            \"columnName\": \"ruleArticles\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleNextPage\",\n            \"columnName\": \"ruleNextPage\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleTitle\",\n            \"columnName\": \"ruleTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"rulePubDate\",\n            \"columnName\": \"rulePubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleDescription\",\n            \"columnName\": \"ruleDescription\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleImage\",\n            \"columnName\": \"ruleImage\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleLink\",\n            \"columnName\": \"ruleLink\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleContent\",\n            \"columnName\": \"ruleContent\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"style\",\n            \"columnName\": \"style\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"header\",\n            \"columnName\": \"header\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enableJs\",\n            \"columnName\": \"enableJs\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"loadWithBaseUrl\",\n            \"columnName\": \"loadWithBaseUrl\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"sourceUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_rssSources_sourceUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"sourceUrl\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_rssSources_sourceUrl` ON `${TABLE_NAME}` (`sourceUrl`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"bookmarks\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`time` INTEGER NOT NULL, `bookName` TEXT NOT NULL, `bookAuthor` TEXT NOT NULL, `chapterIndex` INTEGER NOT NULL, `chapterPos` INTEGER NOT NULL, `chapterName` TEXT NOT NULL, `bookText` TEXT NOT NULL, `content` TEXT NOT NULL, PRIMARY KEY(`time`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"time\",\n            \"columnName\": \"time\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookName\",\n            \"columnName\": \"bookName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookAuthor\",\n            \"columnName\": \"bookAuthor\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterIndex\",\n            \"columnName\": \"chapterIndex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterPos\",\n            \"columnName\": \"chapterPos\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterName\",\n            \"columnName\": \"chapterName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookText\",\n            \"columnName\": \"bookText\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"time\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_bookmarks_bookName_bookAuthor\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"bookName\",\n              \"bookAuthor\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_bookmarks_bookName_bookAuthor` ON `${TABLE_NAME}` (`bookName`, `bookAuthor`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssArticles\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`origin` TEXT NOT NULL, `sort` TEXT NOT NULL, `title` TEXT NOT NULL, `order` INTEGER NOT NULL, `link` TEXT NOT NULL, `pubDate` TEXT, `description` TEXT, `content` TEXT, `image` TEXT, `read` INTEGER NOT NULL, `variable` TEXT, PRIMARY KEY(`origin`, `link`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sort\",\n            \"columnName\": \"sort\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"link\",\n            \"columnName\": \"link\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"pubDate\",\n            \"columnName\": \"pubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"description\",\n            \"columnName\": \"description\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"image\",\n            \"columnName\": \"image\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"read\",\n            \"columnName\": \"read\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"origin\",\n            \"link\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssReadRecords\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`record` TEXT NOT NULL, `read` INTEGER NOT NULL, PRIMARY KEY(`record`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"record\",\n            \"columnName\": \"record\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"read\",\n            \"columnName\": \"read\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"record\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssStars\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`origin` TEXT NOT NULL, `sort` TEXT NOT NULL, `title` TEXT NOT NULL, `starTime` INTEGER NOT NULL, `link` TEXT NOT NULL, `pubDate` TEXT, `description` TEXT, `content` TEXT, `image` TEXT, `variable` TEXT, PRIMARY KEY(`origin`, `link`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sort\",\n            \"columnName\": \"sort\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"starTime\",\n            \"columnName\": \"starTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"link\",\n            \"columnName\": \"link\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"pubDate\",\n            \"columnName\": \"pubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"description\",\n            \"columnName\": \"description\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"image\",\n            \"columnName\": \"image\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"origin\",\n            \"link\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"txtTocRules\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `rule` TEXT NOT NULL, `serialNumber` INTEGER NOT NULL, `enable` INTEGER NOT NULL, PRIMARY KEY(`id`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"rule\",\n            \"columnName\": \"rule\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"serialNumber\",\n            \"columnName\": \"serialNumber\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enable\",\n            \"columnName\": \"enable\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"id\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"readRecord\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`deviceId` TEXT NOT NULL, `bookName` TEXT NOT NULL, `readTime` INTEGER NOT NULL, PRIMARY KEY(`deviceId`, `bookName`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"deviceId\",\n            \"columnName\": \"deviceId\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookName\",\n            \"columnName\": \"bookName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"readTime\",\n            \"columnName\": \"readTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"deviceId\",\n            \"bookName\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"httpTTS\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `url` TEXT NOT NULL, PRIMARY KEY(`id`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"id\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"caches\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`key` TEXT NOT NULL, `value` TEXT, `deadline` INTEGER NOT NULL, PRIMARY KEY(`key`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"key\",\n            \"columnName\": \"key\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"value\",\n            \"columnName\": \"value\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"deadline\",\n            \"columnName\": \"deadline\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"key\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_caches_key\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"key\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_caches_key` ON `${TABLE_NAME}` (`key`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"ruleSubs\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `url` TEXT NOT NULL, `type` INTEGER NOT NULL, `customOrder` INTEGER NOT NULL, `autoUpdate` INTEGER NOT NULL, `update` INTEGER NOT NULL, PRIMARY KEY(`id`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"autoUpdate\",\n            \"columnName\": \"autoUpdate\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"update\",\n            \"columnName\": \"update\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"id\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      }\n    ],\n    \"views\": [],\n    \"setupQueries\": [\n      \"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)\",\n      \"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '25948a8defe4d091514bb725b4db7683')\"\n    ]\n  }\n}"
  },
  {
    "path": "app/schemas/io.legado.app.data.AppDatabase/37.json",
    "content": "{\n  \"formatVersion\": 1,\n  \"database\": {\n    \"version\": 37,\n    \"identityHash\": \"11ebd6a72eb3f9ccd6ca46bc5535bca5\",\n    \"entities\": [\n      {\n        \"tableName\": \"books\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookUrl` TEXT NOT NULL, `tocUrl` TEXT NOT NULL, `origin` TEXT NOT NULL, `originName` TEXT NOT NULL, `name` TEXT NOT NULL, `author` TEXT NOT NULL, `kind` TEXT, `customTag` TEXT, `coverUrl` TEXT, `customCoverUrl` TEXT, `intro` TEXT, `customIntro` TEXT, `charset` TEXT, `type` INTEGER NOT NULL, `group` INTEGER NOT NULL, `latestChapterTitle` TEXT, `latestChapterTime` INTEGER NOT NULL, `lastCheckTime` INTEGER NOT NULL, `lastCheckCount` INTEGER NOT NULL, `totalChapterNum` INTEGER NOT NULL, `durChapterTitle` TEXT, `durChapterIndex` INTEGER NOT NULL, `durChapterPos` INTEGER NOT NULL, `durChapterTime` INTEGER NOT NULL, `wordCount` TEXT, `canUpdate` INTEGER NOT NULL, `order` INTEGER NOT NULL, `originOrder` INTEGER NOT NULL, `variable` TEXT, `readConfig` TEXT, PRIMARY KEY(`bookUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"tocUrl\",\n            \"columnName\": \"tocUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"originName\",\n            \"columnName\": \"originName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"author\",\n            \"columnName\": \"author\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"kind\",\n            \"columnName\": \"kind\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customTag\",\n            \"columnName\": \"customTag\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"coverUrl\",\n            \"columnName\": \"coverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customCoverUrl\",\n            \"columnName\": \"customCoverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"intro\",\n            \"columnName\": \"intro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customIntro\",\n            \"columnName\": \"customIntro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"charset\",\n            \"columnName\": \"charset\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"group\",\n            \"columnName\": \"group\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"latestChapterTitle\",\n            \"columnName\": \"latestChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"latestChapterTime\",\n            \"columnName\": \"latestChapterTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"lastCheckTime\",\n            \"columnName\": \"lastCheckTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"lastCheckCount\",\n            \"columnName\": \"lastCheckCount\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"totalChapterNum\",\n            \"columnName\": \"totalChapterNum\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"durChapterTitle\",\n            \"columnName\": \"durChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"durChapterIndex\",\n            \"columnName\": \"durChapterIndex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"durChapterPos\",\n            \"columnName\": \"durChapterPos\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"durChapterTime\",\n            \"columnName\": \"durChapterTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"wordCount\",\n            \"columnName\": \"wordCount\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"canUpdate\",\n            \"columnName\": \"canUpdate\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"originOrder\",\n            \"columnName\": \"originOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"readConfig\",\n            \"columnName\": \"readConfig\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"bookUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_books_name_author\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"name\",\n              \"author\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_books_name_author` ON `${TABLE_NAME}` (`name`, `author`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"book_groups\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`groupId` INTEGER NOT NULL, `groupName` TEXT NOT NULL, `cover` TEXT, `order` INTEGER NOT NULL, `show` INTEGER NOT NULL, PRIMARY KEY(`groupId`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"groupId\",\n            \"columnName\": \"groupId\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"groupName\",\n            \"columnName\": \"groupName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"cover\",\n            \"columnName\": \"cover\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"show\",\n            \"columnName\": \"show\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"groupId\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"book_sources\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookSourceName` TEXT NOT NULL, `bookSourceGroup` TEXT, `bookSourceUrl` TEXT NOT NULL, `bookSourceType` INTEGER NOT NULL, `bookUrlPattern` TEXT, `concurrentRate` TEXT, `customOrder` INTEGER NOT NULL, `enabled` INTEGER NOT NULL, `enabledExplore` INTEGER NOT NULL, `header` TEXT, `loginUrl` TEXT, `loginUi` TEXT, `loginCheckJs` TEXT, `bookSourceComment` TEXT, `lastUpdateTime` INTEGER NOT NULL, `weight` INTEGER NOT NULL, `exploreUrl` TEXT, `ruleExplore` TEXT, `searchUrl` TEXT, `ruleSearch` TEXT, `ruleBookInfo` TEXT, `ruleToc` TEXT, `ruleContent` TEXT, PRIMARY KEY(`bookSourceUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookSourceName\",\n            \"columnName\": \"bookSourceName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookSourceGroup\",\n            \"columnName\": \"bookSourceGroup\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"bookSourceUrl\",\n            \"columnName\": \"bookSourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookSourceType\",\n            \"columnName\": \"bookSourceType\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookUrlPattern\",\n            \"columnName\": \"bookUrlPattern\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"concurrentRate\",\n            \"columnName\": \"concurrentRate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enabled\",\n            \"columnName\": \"enabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enabledExplore\",\n            \"columnName\": \"enabledExplore\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"header\",\n            \"columnName\": \"header\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUrl\",\n            \"columnName\": \"loginUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUi\",\n            \"columnName\": \"loginUi\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginCheckJs\",\n            \"columnName\": \"loginCheckJs\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"bookSourceComment\",\n            \"columnName\": \"bookSourceComment\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"lastUpdateTime\",\n            \"columnName\": \"lastUpdateTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"weight\",\n            \"columnName\": \"weight\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"exploreUrl\",\n            \"columnName\": \"exploreUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleExplore\",\n            \"columnName\": \"ruleExplore\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"searchUrl\",\n            \"columnName\": \"searchUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleSearch\",\n            \"columnName\": \"ruleSearch\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleBookInfo\",\n            \"columnName\": \"ruleBookInfo\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleToc\",\n            \"columnName\": \"ruleToc\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleContent\",\n            \"columnName\": \"ruleContent\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"bookSourceUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_book_sources_bookSourceUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"bookSourceUrl\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_book_sources_bookSourceUrl` ON `${TABLE_NAME}` (`bookSourceUrl`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"chapters\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`url` TEXT NOT NULL, `title` TEXT NOT NULL, `baseUrl` TEXT NOT NULL, `bookUrl` TEXT NOT NULL, `index` INTEGER NOT NULL, `resourceUrl` TEXT, `tag` TEXT, `start` INTEGER, `end` INTEGER, `startFragmentId` TEXT, `endFragmentId` TEXT, `variable` TEXT, PRIMARY KEY(`url`, `bookUrl`), FOREIGN KEY(`bookUrl`) REFERENCES `books`(`bookUrl`) ON UPDATE NO ACTION ON DELETE CASCADE )\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"baseUrl\",\n            \"columnName\": \"baseUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"index\",\n            \"columnName\": \"index\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"resourceUrl\",\n            \"columnName\": \"resourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"tag\",\n            \"columnName\": \"tag\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"start\",\n            \"columnName\": \"start\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"end\",\n            \"columnName\": \"end\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"startFragmentId\",\n            \"columnName\": \"startFragmentId\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"endFragmentId\",\n            \"columnName\": \"endFragmentId\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"url\",\n            \"bookUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_chapters_bookUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"bookUrl\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_chapters_bookUrl` ON `${TABLE_NAME}` (`bookUrl`)\"\n          },\n          {\n            \"name\": \"index_chapters_bookUrl_index\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"bookUrl\",\n              \"index\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_chapters_bookUrl_index` ON `${TABLE_NAME}` (`bookUrl`, `index`)\"\n          }\n        ],\n        \"foreignKeys\": [\n          {\n            \"table\": \"books\",\n            \"onDelete\": \"CASCADE\",\n            \"onUpdate\": \"NO ACTION\",\n            \"columns\": [\n              \"bookUrl\"\n            ],\n            \"referencedColumns\": [\n              \"bookUrl\"\n            ]\n          }\n        ]\n      },\n      {\n        \"tableName\": \"replace_rules\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL, `group` TEXT, `pattern` TEXT NOT NULL, `replacement` TEXT NOT NULL, `scope` TEXT, `isEnabled` INTEGER NOT NULL, `isRegex` INTEGER NOT NULL, `sortOrder` INTEGER NOT NULL)\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"group\",\n            \"columnName\": \"group\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"pattern\",\n            \"columnName\": \"pattern\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"replacement\",\n            \"columnName\": \"replacement\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"scope\",\n            \"columnName\": \"scope\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"isEnabled\",\n            \"columnName\": \"isEnabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"isRegex\",\n            \"columnName\": \"isRegex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"sortOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"id\"\n          ],\n          \"autoGenerate\": true\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_replace_rules_id\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"id\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_replace_rules_id` ON `${TABLE_NAME}` (`id`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"searchBooks\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookUrl` TEXT NOT NULL, `origin` TEXT NOT NULL, `originName` TEXT NOT NULL, `type` INTEGER NOT NULL, `name` TEXT NOT NULL, `author` TEXT NOT NULL, `kind` TEXT, `coverUrl` TEXT, `intro` TEXT, `wordCount` TEXT, `latestChapterTitle` TEXT, `tocUrl` TEXT NOT NULL, `time` INTEGER NOT NULL, `variable` TEXT, `originOrder` INTEGER NOT NULL, PRIMARY KEY(`bookUrl`), FOREIGN KEY(`origin`) REFERENCES `book_sources`(`bookSourceUrl`) ON UPDATE NO ACTION ON DELETE CASCADE )\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"originName\",\n            \"columnName\": \"originName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"author\",\n            \"columnName\": \"author\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"kind\",\n            \"columnName\": \"kind\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"coverUrl\",\n            \"columnName\": \"coverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"intro\",\n            \"columnName\": \"intro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"wordCount\",\n            \"columnName\": \"wordCount\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"latestChapterTitle\",\n            \"columnName\": \"latestChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"tocUrl\",\n            \"columnName\": \"tocUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"time\",\n            \"columnName\": \"time\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"originOrder\",\n            \"columnName\": \"originOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"bookUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_searchBooks_bookUrl\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"bookUrl\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_searchBooks_bookUrl` ON `${TABLE_NAME}` (`bookUrl`)\"\n          },\n          {\n            \"name\": \"index_searchBooks_origin\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"origin\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_searchBooks_origin` ON `${TABLE_NAME}` (`origin`)\"\n          }\n        ],\n        \"foreignKeys\": [\n          {\n            \"table\": \"book_sources\",\n            \"onDelete\": \"CASCADE\",\n            \"onUpdate\": \"NO ACTION\",\n            \"columns\": [\n              \"origin\"\n            ],\n            \"referencedColumns\": [\n              \"bookSourceUrl\"\n            ]\n          }\n        ]\n      },\n      {\n        \"tableName\": \"search_keywords\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`word` TEXT NOT NULL, `usage` INTEGER NOT NULL, `lastUseTime` INTEGER NOT NULL, PRIMARY KEY(`word`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"word\",\n            \"columnName\": \"word\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"usage\",\n            \"columnName\": \"usage\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"lastUseTime\",\n            \"columnName\": \"lastUseTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"word\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_search_keywords_word\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"word\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_search_keywords_word` ON `${TABLE_NAME}` (`word`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"cookies\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`url` TEXT NOT NULL, `cookie` TEXT NOT NULL, PRIMARY KEY(`url`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"cookie\",\n            \"columnName\": \"cookie\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"url\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_cookies_url\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"url\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_cookies_url` ON `${TABLE_NAME}` (`url`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssSources\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`sourceUrl` TEXT NOT NULL, `sourceName` TEXT NOT NULL, `sourceIcon` TEXT NOT NULL, `sourceGroup` TEXT, `sourceComment` TEXT, `enabled` INTEGER NOT NULL, `header` TEXT, `loginUrl` TEXT, `loginUi` TEXT, `loginCheckJs` TEXT, `sortUrl` TEXT, `singleUrl` INTEGER NOT NULL, `articleStyle` INTEGER NOT NULL, `ruleArticles` TEXT, `ruleNextPage` TEXT, `ruleTitle` TEXT, `rulePubDate` TEXT, `ruleDescription` TEXT, `ruleImage` TEXT, `ruleLink` TEXT, `ruleContent` TEXT, `style` TEXT, `enableJs` INTEGER NOT NULL, `loadWithBaseUrl` INTEGER NOT NULL, `customOrder` INTEGER NOT NULL, PRIMARY KEY(`sourceUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"sourceUrl\",\n            \"columnName\": \"sourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceName\",\n            \"columnName\": \"sourceName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceIcon\",\n            \"columnName\": \"sourceIcon\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceGroup\",\n            \"columnName\": \"sourceGroup\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"sourceComment\",\n            \"columnName\": \"sourceComment\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enabled\",\n            \"columnName\": \"enabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"header\",\n            \"columnName\": \"header\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUrl\",\n            \"columnName\": \"loginUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUi\",\n            \"columnName\": \"loginUi\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginCheckJs\",\n            \"columnName\": \"loginCheckJs\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"sortUrl\",\n            \"columnName\": \"sortUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"singleUrl\",\n            \"columnName\": \"singleUrl\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"articleStyle\",\n            \"columnName\": \"articleStyle\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"ruleArticles\",\n            \"columnName\": \"ruleArticles\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleNextPage\",\n            \"columnName\": \"ruleNextPage\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleTitle\",\n            \"columnName\": \"ruleTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"rulePubDate\",\n            \"columnName\": \"rulePubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleDescription\",\n            \"columnName\": \"ruleDescription\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleImage\",\n            \"columnName\": \"ruleImage\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleLink\",\n            \"columnName\": \"ruleLink\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleContent\",\n            \"columnName\": \"ruleContent\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"style\",\n            \"columnName\": \"style\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enableJs\",\n            \"columnName\": \"enableJs\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"loadWithBaseUrl\",\n            \"columnName\": \"loadWithBaseUrl\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"sourceUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_rssSources_sourceUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"sourceUrl\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_rssSources_sourceUrl` ON `${TABLE_NAME}` (`sourceUrl`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"bookmarks\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`time` INTEGER NOT NULL, `bookName` TEXT NOT NULL, `bookAuthor` TEXT NOT NULL, `chapterIndex` INTEGER NOT NULL, `chapterPos` INTEGER NOT NULL, `chapterName` TEXT NOT NULL, `bookText` TEXT NOT NULL, `content` TEXT NOT NULL, PRIMARY KEY(`time`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"time\",\n            \"columnName\": \"time\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookName\",\n            \"columnName\": \"bookName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookAuthor\",\n            \"columnName\": \"bookAuthor\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterIndex\",\n            \"columnName\": \"chapterIndex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterPos\",\n            \"columnName\": \"chapterPos\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterName\",\n            \"columnName\": \"chapterName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookText\",\n            \"columnName\": \"bookText\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"time\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_bookmarks_bookName_bookAuthor\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"bookName\",\n              \"bookAuthor\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_bookmarks_bookName_bookAuthor` ON `${TABLE_NAME}` (`bookName`, `bookAuthor`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssArticles\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`origin` TEXT NOT NULL, `sort` TEXT NOT NULL, `title` TEXT NOT NULL, `order` INTEGER NOT NULL, `link` TEXT NOT NULL, `pubDate` TEXT, `description` TEXT, `content` TEXT, `image` TEXT, `read` INTEGER NOT NULL, `variable` TEXT, PRIMARY KEY(`origin`, `link`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sort\",\n            \"columnName\": \"sort\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"link\",\n            \"columnName\": \"link\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"pubDate\",\n            \"columnName\": \"pubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"description\",\n            \"columnName\": \"description\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"image\",\n            \"columnName\": \"image\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"read\",\n            \"columnName\": \"read\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"origin\",\n            \"link\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssReadRecords\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`record` TEXT NOT NULL, `read` INTEGER NOT NULL, PRIMARY KEY(`record`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"record\",\n            \"columnName\": \"record\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"read\",\n            \"columnName\": \"read\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"record\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssStars\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`origin` TEXT NOT NULL, `sort` TEXT NOT NULL, `title` TEXT NOT NULL, `starTime` INTEGER NOT NULL, `link` TEXT NOT NULL, `pubDate` TEXT, `description` TEXT, `content` TEXT, `image` TEXT, `variable` TEXT, PRIMARY KEY(`origin`, `link`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sort\",\n            \"columnName\": \"sort\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"starTime\",\n            \"columnName\": \"starTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"link\",\n            \"columnName\": \"link\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"pubDate\",\n            \"columnName\": \"pubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"description\",\n            \"columnName\": \"description\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"image\",\n            \"columnName\": \"image\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"origin\",\n            \"link\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"txtTocRules\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `rule` TEXT NOT NULL, `serialNumber` INTEGER NOT NULL, `enable` INTEGER NOT NULL, PRIMARY KEY(`id`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"rule\",\n            \"columnName\": \"rule\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"serialNumber\",\n            \"columnName\": \"serialNumber\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enable\",\n            \"columnName\": \"enable\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"id\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"readRecord\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`deviceId` TEXT NOT NULL, `bookName` TEXT NOT NULL, `readTime` INTEGER NOT NULL, PRIMARY KEY(`deviceId`, `bookName`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"deviceId\",\n            \"columnName\": \"deviceId\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookName\",\n            \"columnName\": \"bookName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"readTime\",\n            \"columnName\": \"readTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"deviceId\",\n            \"bookName\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"httpTTS\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `url` TEXT NOT NULL, PRIMARY KEY(`id`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"id\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"caches\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`key` TEXT NOT NULL, `value` TEXT, `deadline` INTEGER NOT NULL, PRIMARY KEY(`key`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"key\",\n            \"columnName\": \"key\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"value\",\n            \"columnName\": \"value\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"deadline\",\n            \"columnName\": \"deadline\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"key\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_caches_key\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"key\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_caches_key` ON `${TABLE_NAME}` (`key`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"ruleSubs\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `url` TEXT NOT NULL, `type` INTEGER NOT NULL, `customOrder` INTEGER NOT NULL, `autoUpdate` INTEGER NOT NULL, `update` INTEGER NOT NULL, PRIMARY KEY(`id`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"autoUpdate\",\n            \"columnName\": \"autoUpdate\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"update\",\n            \"columnName\": \"update\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"id\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      }\n    ],\n    \"views\": [],\n    \"setupQueries\": [\n      \"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)\",\n      \"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '11ebd6a72eb3f9ccd6ca46bc5535bca5')\"\n    ]\n  }\n}"
  },
  {
    "path": "app/schemas/io.legado.app.data.AppDatabase/38.json",
    "content": "{\n  \"formatVersion\": 1,\n  \"database\": {\n    \"version\": 38,\n    \"identityHash\": \"5211699415b40f58b06d4136d14173d1\",\n    \"entities\": [\n      {\n        \"tableName\": \"books\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookUrl` TEXT NOT NULL, `tocUrl` TEXT NOT NULL, `origin` TEXT NOT NULL, `originName` TEXT NOT NULL, `name` TEXT NOT NULL, `author` TEXT NOT NULL, `kind` TEXT, `customTag` TEXT, `coverUrl` TEXT, `customCoverUrl` TEXT, `intro` TEXT, `customIntro` TEXT, `charset` TEXT, `type` INTEGER NOT NULL, `group` INTEGER NOT NULL, `latestChapterTitle` TEXT, `latestChapterTime` INTEGER NOT NULL, `lastCheckTime` INTEGER NOT NULL, `lastCheckCount` INTEGER NOT NULL, `totalChapterNum` INTEGER NOT NULL, `durChapterTitle` TEXT, `durChapterIndex` INTEGER NOT NULL, `durChapterPos` INTEGER NOT NULL, `durChapterTime` INTEGER NOT NULL, `wordCount` TEXT, `canUpdate` INTEGER NOT NULL, `order` INTEGER NOT NULL, `originOrder` INTEGER NOT NULL, `variable` TEXT, `readConfig` TEXT, PRIMARY KEY(`bookUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"tocUrl\",\n            \"columnName\": \"tocUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"originName\",\n            \"columnName\": \"originName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"author\",\n            \"columnName\": \"author\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"kind\",\n            \"columnName\": \"kind\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customTag\",\n            \"columnName\": \"customTag\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"coverUrl\",\n            \"columnName\": \"coverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customCoverUrl\",\n            \"columnName\": \"customCoverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"intro\",\n            \"columnName\": \"intro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customIntro\",\n            \"columnName\": \"customIntro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"charset\",\n            \"columnName\": \"charset\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"group\",\n            \"columnName\": \"group\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"latestChapterTitle\",\n            \"columnName\": \"latestChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"latestChapterTime\",\n            \"columnName\": \"latestChapterTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"lastCheckTime\",\n            \"columnName\": \"lastCheckTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"lastCheckCount\",\n            \"columnName\": \"lastCheckCount\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"totalChapterNum\",\n            \"columnName\": \"totalChapterNum\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"durChapterTitle\",\n            \"columnName\": \"durChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"durChapterIndex\",\n            \"columnName\": \"durChapterIndex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"durChapterPos\",\n            \"columnName\": \"durChapterPos\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"durChapterTime\",\n            \"columnName\": \"durChapterTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"wordCount\",\n            \"columnName\": \"wordCount\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"canUpdate\",\n            \"columnName\": \"canUpdate\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"originOrder\",\n            \"columnName\": \"originOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"readConfig\",\n            \"columnName\": \"readConfig\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"bookUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_books_name_author\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"name\",\n              \"author\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_books_name_author` ON `${TABLE_NAME}` (`name`, `author`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"book_groups\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`groupId` INTEGER NOT NULL, `groupName` TEXT NOT NULL, `cover` TEXT, `order` INTEGER NOT NULL, `show` INTEGER NOT NULL, PRIMARY KEY(`groupId`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"groupId\",\n            \"columnName\": \"groupId\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"groupName\",\n            \"columnName\": \"groupName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"cover\",\n            \"columnName\": \"cover\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"show\",\n            \"columnName\": \"show\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"groupId\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"book_sources\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookSourceName` TEXT NOT NULL, `bookSourceGroup` TEXT, `bookSourceUrl` TEXT NOT NULL, `bookSourceType` INTEGER NOT NULL, `bookUrlPattern` TEXT, `concurrentRate` TEXT, `customOrder` INTEGER NOT NULL, `enabled` INTEGER NOT NULL, `enabledExplore` INTEGER NOT NULL, `header` TEXT, `loginUrl` TEXT, `loginUi` TEXT, `loginCheckJs` TEXT, `bookSourceComment` TEXT, `lastUpdateTime` INTEGER NOT NULL, `respondTime` INTEGER NOT NULL, `weight` INTEGER NOT NULL, `exploreUrl` TEXT, `ruleExplore` TEXT, `searchUrl` TEXT, `ruleSearch` TEXT, `ruleBookInfo` TEXT, `ruleToc` TEXT, `ruleContent` TEXT, PRIMARY KEY(`bookSourceUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookSourceName\",\n            \"columnName\": \"bookSourceName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookSourceGroup\",\n            \"columnName\": \"bookSourceGroup\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"bookSourceUrl\",\n            \"columnName\": \"bookSourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookSourceType\",\n            \"columnName\": \"bookSourceType\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookUrlPattern\",\n            \"columnName\": \"bookUrlPattern\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"concurrentRate\",\n            \"columnName\": \"concurrentRate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enabled\",\n            \"columnName\": \"enabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enabledExplore\",\n            \"columnName\": \"enabledExplore\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"header\",\n            \"columnName\": \"header\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUrl\",\n            \"columnName\": \"loginUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUi\",\n            \"columnName\": \"loginUi\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginCheckJs\",\n            \"columnName\": \"loginCheckJs\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"bookSourceComment\",\n            \"columnName\": \"bookSourceComment\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"lastUpdateTime\",\n            \"columnName\": \"lastUpdateTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"respondTime\",\n            \"columnName\": \"respondTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"weight\",\n            \"columnName\": \"weight\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"exploreUrl\",\n            \"columnName\": \"exploreUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleExplore\",\n            \"columnName\": \"ruleExplore\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"searchUrl\",\n            \"columnName\": \"searchUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleSearch\",\n            \"columnName\": \"ruleSearch\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleBookInfo\",\n            \"columnName\": \"ruleBookInfo\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleToc\",\n            \"columnName\": \"ruleToc\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleContent\",\n            \"columnName\": \"ruleContent\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"bookSourceUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_book_sources_bookSourceUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"bookSourceUrl\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_book_sources_bookSourceUrl` ON `${TABLE_NAME}` (`bookSourceUrl`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"chapters\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`url` TEXT NOT NULL, `title` TEXT NOT NULL, `baseUrl` TEXT NOT NULL, `bookUrl` TEXT NOT NULL, `index` INTEGER NOT NULL, `resourceUrl` TEXT, `tag` TEXT, `start` INTEGER, `end` INTEGER, `startFragmentId` TEXT, `endFragmentId` TEXT, `variable` TEXT, PRIMARY KEY(`url`, `bookUrl`), FOREIGN KEY(`bookUrl`) REFERENCES `books`(`bookUrl`) ON UPDATE NO ACTION ON DELETE CASCADE )\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"baseUrl\",\n            \"columnName\": \"baseUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"index\",\n            \"columnName\": \"index\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"resourceUrl\",\n            \"columnName\": \"resourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"tag\",\n            \"columnName\": \"tag\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"start\",\n            \"columnName\": \"start\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"end\",\n            \"columnName\": \"end\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"startFragmentId\",\n            \"columnName\": \"startFragmentId\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"endFragmentId\",\n            \"columnName\": \"endFragmentId\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"url\",\n            \"bookUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_chapters_bookUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"bookUrl\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_chapters_bookUrl` ON `${TABLE_NAME}` (`bookUrl`)\"\n          },\n          {\n            \"name\": \"index_chapters_bookUrl_index\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"bookUrl\",\n              \"index\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_chapters_bookUrl_index` ON `${TABLE_NAME}` (`bookUrl`, `index`)\"\n          }\n        ],\n        \"foreignKeys\": [\n          {\n            \"table\": \"books\",\n            \"onDelete\": \"CASCADE\",\n            \"onUpdate\": \"NO ACTION\",\n            \"columns\": [\n              \"bookUrl\"\n            ],\n            \"referencedColumns\": [\n              \"bookUrl\"\n            ]\n          }\n        ]\n      },\n      {\n        \"tableName\": \"replace_rules\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL, `group` TEXT, `pattern` TEXT NOT NULL, `replacement` TEXT NOT NULL, `scope` TEXT, `isEnabled` INTEGER NOT NULL, `isRegex` INTEGER NOT NULL, `sortOrder` INTEGER NOT NULL)\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"group\",\n            \"columnName\": \"group\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"pattern\",\n            \"columnName\": \"pattern\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"replacement\",\n            \"columnName\": \"replacement\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"scope\",\n            \"columnName\": \"scope\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"isEnabled\",\n            \"columnName\": \"isEnabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"isRegex\",\n            \"columnName\": \"isRegex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"sortOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"id\"\n          ],\n          \"autoGenerate\": true\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_replace_rules_id\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"id\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_replace_rules_id` ON `${TABLE_NAME}` (`id`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"searchBooks\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookUrl` TEXT NOT NULL, `origin` TEXT NOT NULL, `originName` TEXT NOT NULL, `type` INTEGER NOT NULL, `name` TEXT NOT NULL, `author` TEXT NOT NULL, `kind` TEXT, `coverUrl` TEXT, `intro` TEXT, `wordCount` TEXT, `latestChapterTitle` TEXT, `tocUrl` TEXT NOT NULL, `time` INTEGER NOT NULL, `variable` TEXT, `originOrder` INTEGER NOT NULL, PRIMARY KEY(`bookUrl`), FOREIGN KEY(`origin`) REFERENCES `book_sources`(`bookSourceUrl`) ON UPDATE NO ACTION ON DELETE CASCADE )\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"originName\",\n            \"columnName\": \"originName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"author\",\n            \"columnName\": \"author\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"kind\",\n            \"columnName\": \"kind\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"coverUrl\",\n            \"columnName\": \"coverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"intro\",\n            \"columnName\": \"intro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"wordCount\",\n            \"columnName\": \"wordCount\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"latestChapterTitle\",\n            \"columnName\": \"latestChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"tocUrl\",\n            \"columnName\": \"tocUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"time\",\n            \"columnName\": \"time\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"originOrder\",\n            \"columnName\": \"originOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"bookUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_searchBooks_bookUrl\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"bookUrl\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_searchBooks_bookUrl` ON `${TABLE_NAME}` (`bookUrl`)\"\n          },\n          {\n            \"name\": \"index_searchBooks_origin\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"origin\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_searchBooks_origin` ON `${TABLE_NAME}` (`origin`)\"\n          }\n        ],\n        \"foreignKeys\": [\n          {\n            \"table\": \"book_sources\",\n            \"onDelete\": \"CASCADE\",\n            \"onUpdate\": \"NO ACTION\",\n            \"columns\": [\n              \"origin\"\n            ],\n            \"referencedColumns\": [\n              \"bookSourceUrl\"\n            ]\n          }\n        ]\n      },\n      {\n        \"tableName\": \"search_keywords\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`word` TEXT NOT NULL, `usage` INTEGER NOT NULL, `lastUseTime` INTEGER NOT NULL, PRIMARY KEY(`word`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"word\",\n            \"columnName\": \"word\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"usage\",\n            \"columnName\": \"usage\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"lastUseTime\",\n            \"columnName\": \"lastUseTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"word\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_search_keywords_word\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"word\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_search_keywords_word` ON `${TABLE_NAME}` (`word`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"cookies\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`url` TEXT NOT NULL, `cookie` TEXT NOT NULL, PRIMARY KEY(`url`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"cookie\",\n            \"columnName\": \"cookie\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"url\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_cookies_url\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"url\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_cookies_url` ON `${TABLE_NAME}` (`url`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssSources\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`sourceUrl` TEXT NOT NULL, `sourceName` TEXT NOT NULL, `sourceIcon` TEXT NOT NULL, `sourceGroup` TEXT, `sourceComment` TEXT, `enabled` INTEGER NOT NULL, `header` TEXT, `loginUrl` TEXT, `loginUi` TEXT, `loginCheckJs` TEXT, `sortUrl` TEXT, `singleUrl` INTEGER NOT NULL, `articleStyle` INTEGER NOT NULL, `ruleArticles` TEXT, `ruleNextPage` TEXT, `ruleTitle` TEXT, `rulePubDate` TEXT, `ruleDescription` TEXT, `ruleImage` TEXT, `ruleLink` TEXT, `ruleContent` TEXT, `style` TEXT, `enableJs` INTEGER NOT NULL, `loadWithBaseUrl` INTEGER NOT NULL, `customOrder` INTEGER NOT NULL, PRIMARY KEY(`sourceUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"sourceUrl\",\n            \"columnName\": \"sourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceName\",\n            \"columnName\": \"sourceName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceIcon\",\n            \"columnName\": \"sourceIcon\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceGroup\",\n            \"columnName\": \"sourceGroup\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"sourceComment\",\n            \"columnName\": \"sourceComment\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enabled\",\n            \"columnName\": \"enabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"header\",\n            \"columnName\": \"header\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUrl\",\n            \"columnName\": \"loginUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUi\",\n            \"columnName\": \"loginUi\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginCheckJs\",\n            \"columnName\": \"loginCheckJs\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"sortUrl\",\n            \"columnName\": \"sortUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"singleUrl\",\n            \"columnName\": \"singleUrl\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"articleStyle\",\n            \"columnName\": \"articleStyle\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"ruleArticles\",\n            \"columnName\": \"ruleArticles\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleNextPage\",\n            \"columnName\": \"ruleNextPage\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleTitle\",\n            \"columnName\": \"ruleTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"rulePubDate\",\n            \"columnName\": \"rulePubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleDescription\",\n            \"columnName\": \"ruleDescription\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleImage\",\n            \"columnName\": \"ruleImage\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleLink\",\n            \"columnName\": \"ruleLink\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleContent\",\n            \"columnName\": \"ruleContent\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"style\",\n            \"columnName\": \"style\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enableJs\",\n            \"columnName\": \"enableJs\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"loadWithBaseUrl\",\n            \"columnName\": \"loadWithBaseUrl\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"sourceUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_rssSources_sourceUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"sourceUrl\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_rssSources_sourceUrl` ON `${TABLE_NAME}` (`sourceUrl`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"bookmarks\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`time` INTEGER NOT NULL, `bookName` TEXT NOT NULL, `bookAuthor` TEXT NOT NULL, `chapterIndex` INTEGER NOT NULL, `chapterPos` INTEGER NOT NULL, `chapterName` TEXT NOT NULL, `bookText` TEXT NOT NULL, `content` TEXT NOT NULL, PRIMARY KEY(`time`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"time\",\n            \"columnName\": \"time\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookName\",\n            \"columnName\": \"bookName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookAuthor\",\n            \"columnName\": \"bookAuthor\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterIndex\",\n            \"columnName\": \"chapterIndex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterPos\",\n            \"columnName\": \"chapterPos\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterName\",\n            \"columnName\": \"chapterName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookText\",\n            \"columnName\": \"bookText\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"time\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_bookmarks_bookName_bookAuthor\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"bookName\",\n              \"bookAuthor\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_bookmarks_bookName_bookAuthor` ON `${TABLE_NAME}` (`bookName`, `bookAuthor`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssArticles\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`origin` TEXT NOT NULL, `sort` TEXT NOT NULL, `title` TEXT NOT NULL, `order` INTEGER NOT NULL, `link` TEXT NOT NULL, `pubDate` TEXT, `description` TEXT, `content` TEXT, `image` TEXT, `read` INTEGER NOT NULL, `variable` TEXT, PRIMARY KEY(`origin`, `link`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sort\",\n            \"columnName\": \"sort\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"link\",\n            \"columnName\": \"link\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"pubDate\",\n            \"columnName\": \"pubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"description\",\n            \"columnName\": \"description\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"image\",\n            \"columnName\": \"image\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"read\",\n            \"columnName\": \"read\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"origin\",\n            \"link\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssReadRecords\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`record` TEXT NOT NULL, `read` INTEGER NOT NULL, PRIMARY KEY(`record`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"record\",\n            \"columnName\": \"record\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"read\",\n            \"columnName\": \"read\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"record\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssStars\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`origin` TEXT NOT NULL, `sort` TEXT NOT NULL, `title` TEXT NOT NULL, `starTime` INTEGER NOT NULL, `link` TEXT NOT NULL, `pubDate` TEXT, `description` TEXT, `content` TEXT, `image` TEXT, `variable` TEXT, PRIMARY KEY(`origin`, `link`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sort\",\n            \"columnName\": \"sort\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"starTime\",\n            \"columnName\": \"starTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"link\",\n            \"columnName\": \"link\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"pubDate\",\n            \"columnName\": \"pubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"description\",\n            \"columnName\": \"description\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"image\",\n            \"columnName\": \"image\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"origin\",\n            \"link\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"txtTocRules\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `rule` TEXT NOT NULL, `serialNumber` INTEGER NOT NULL, `enable` INTEGER NOT NULL, PRIMARY KEY(`id`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"rule\",\n            \"columnName\": \"rule\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"serialNumber\",\n            \"columnName\": \"serialNumber\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enable\",\n            \"columnName\": \"enable\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"id\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"readRecord\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`deviceId` TEXT NOT NULL, `bookName` TEXT NOT NULL, `readTime` INTEGER NOT NULL, PRIMARY KEY(`deviceId`, `bookName`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"deviceId\",\n            \"columnName\": \"deviceId\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookName\",\n            \"columnName\": \"bookName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"readTime\",\n            \"columnName\": \"readTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"deviceId\",\n            \"bookName\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"httpTTS\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `url` TEXT NOT NULL, PRIMARY KEY(`id`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"id\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"caches\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`key` TEXT NOT NULL, `value` TEXT, `deadline` INTEGER NOT NULL, PRIMARY KEY(`key`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"key\",\n            \"columnName\": \"key\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"value\",\n            \"columnName\": \"value\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"deadline\",\n            \"columnName\": \"deadline\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"key\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_caches_key\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"key\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_caches_key` ON `${TABLE_NAME}` (`key`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"ruleSubs\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `url` TEXT NOT NULL, `type` INTEGER NOT NULL, `customOrder` INTEGER NOT NULL, `autoUpdate` INTEGER NOT NULL, `update` INTEGER NOT NULL, PRIMARY KEY(`id`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"autoUpdate\",\n            \"columnName\": \"autoUpdate\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"update\",\n            \"columnName\": \"update\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"id\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      }\n    ],\n    \"views\": [],\n    \"setupQueries\": [\n      \"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)\",\n      \"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '5211699415b40f58b06d4136d14173d1')\"\n    ]\n  }\n}"
  },
  {
    "path": "app/schemas/io.legado.app.data.AppDatabase/39.json",
    "content": "{\n  \"formatVersion\": 1,\n  \"database\": {\n    \"version\": 39,\n    \"identityHash\": \"8cfa1fb6bb9a65c04bfe563680126a4f\",\n    \"entities\": [\n      {\n        \"tableName\": \"books\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookUrl` TEXT NOT NULL, `tocUrl` TEXT NOT NULL, `origin` TEXT NOT NULL, `originName` TEXT NOT NULL, `name` TEXT NOT NULL, `author` TEXT NOT NULL, `kind` TEXT, `customTag` TEXT, `coverUrl` TEXT, `customCoverUrl` TEXT, `intro` TEXT, `customIntro` TEXT, `charset` TEXT, `type` INTEGER NOT NULL, `group` INTEGER NOT NULL, `latestChapterTitle` TEXT, `latestChapterTime` INTEGER NOT NULL, `lastCheckTime` INTEGER NOT NULL, `lastCheckCount` INTEGER NOT NULL, `totalChapterNum` INTEGER NOT NULL, `durChapterTitle` TEXT, `durChapterIndex` INTEGER NOT NULL, `durChapterPos` INTEGER NOT NULL, `durChapterTime` INTEGER NOT NULL, `wordCount` TEXT, `canUpdate` INTEGER NOT NULL, `order` INTEGER NOT NULL, `originOrder` INTEGER NOT NULL, `variable` TEXT, `readConfig` TEXT, PRIMARY KEY(`bookUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"tocUrl\",\n            \"columnName\": \"tocUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"originName\",\n            \"columnName\": \"originName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"author\",\n            \"columnName\": \"author\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"kind\",\n            \"columnName\": \"kind\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customTag\",\n            \"columnName\": \"customTag\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"coverUrl\",\n            \"columnName\": \"coverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customCoverUrl\",\n            \"columnName\": \"customCoverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"intro\",\n            \"columnName\": \"intro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customIntro\",\n            \"columnName\": \"customIntro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"charset\",\n            \"columnName\": \"charset\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"group\",\n            \"columnName\": \"group\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"latestChapterTitle\",\n            \"columnName\": \"latestChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"latestChapterTime\",\n            \"columnName\": \"latestChapterTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"lastCheckTime\",\n            \"columnName\": \"lastCheckTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"lastCheckCount\",\n            \"columnName\": \"lastCheckCount\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"totalChapterNum\",\n            \"columnName\": \"totalChapterNum\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"durChapterTitle\",\n            \"columnName\": \"durChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"durChapterIndex\",\n            \"columnName\": \"durChapterIndex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"durChapterPos\",\n            \"columnName\": \"durChapterPos\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"durChapterTime\",\n            \"columnName\": \"durChapterTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"wordCount\",\n            \"columnName\": \"wordCount\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"canUpdate\",\n            \"columnName\": \"canUpdate\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"originOrder\",\n            \"columnName\": \"originOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"readConfig\",\n            \"columnName\": \"readConfig\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"bookUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_books_name_author\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"name\",\n              \"author\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_books_name_author` ON `${TABLE_NAME}` (`name`, `author`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"book_groups\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`groupId` INTEGER NOT NULL, `groupName` TEXT NOT NULL, `cover` TEXT, `order` INTEGER NOT NULL, `show` INTEGER NOT NULL, PRIMARY KEY(`groupId`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"groupId\",\n            \"columnName\": \"groupId\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"groupName\",\n            \"columnName\": \"groupName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"cover\",\n            \"columnName\": \"cover\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"show\",\n            \"columnName\": \"show\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"groupId\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"book_sources\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookSourceName` TEXT NOT NULL, `bookSourceGroup` TEXT, `bookSourceUrl` TEXT NOT NULL, `bookSourceType` INTEGER NOT NULL, `bookUrlPattern` TEXT, `customOrder` INTEGER NOT NULL, `enabled` INTEGER NOT NULL, `enabledExplore` INTEGER NOT NULL, `concurrentRate` TEXT, `header` TEXT, `loginUrl` TEXT, `loginUi` TEXT, `loginCheckJs` TEXT, `bookSourceComment` TEXT, `lastUpdateTime` INTEGER NOT NULL, `respondTime` INTEGER NOT NULL, `weight` INTEGER NOT NULL, `exploreUrl` TEXT, `ruleExplore` TEXT, `searchUrl` TEXT, `ruleSearch` TEXT, `ruleBookInfo` TEXT, `ruleToc` TEXT, `ruleContent` TEXT, PRIMARY KEY(`bookSourceUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookSourceName\",\n            \"columnName\": \"bookSourceName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookSourceGroup\",\n            \"columnName\": \"bookSourceGroup\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"bookSourceUrl\",\n            \"columnName\": \"bookSourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookSourceType\",\n            \"columnName\": \"bookSourceType\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookUrlPattern\",\n            \"columnName\": \"bookUrlPattern\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enabled\",\n            \"columnName\": \"enabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enabledExplore\",\n            \"columnName\": \"enabledExplore\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"concurrentRate\",\n            \"columnName\": \"concurrentRate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"header\",\n            \"columnName\": \"header\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUrl\",\n            \"columnName\": \"loginUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUi\",\n            \"columnName\": \"loginUi\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginCheckJs\",\n            \"columnName\": \"loginCheckJs\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"bookSourceComment\",\n            \"columnName\": \"bookSourceComment\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"lastUpdateTime\",\n            \"columnName\": \"lastUpdateTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"respondTime\",\n            \"columnName\": \"respondTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"weight\",\n            \"columnName\": \"weight\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"exploreUrl\",\n            \"columnName\": \"exploreUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleExplore\",\n            \"columnName\": \"ruleExplore\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"searchUrl\",\n            \"columnName\": \"searchUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleSearch\",\n            \"columnName\": \"ruleSearch\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleBookInfo\",\n            \"columnName\": \"ruleBookInfo\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleToc\",\n            \"columnName\": \"ruleToc\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleContent\",\n            \"columnName\": \"ruleContent\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"bookSourceUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_book_sources_bookSourceUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"bookSourceUrl\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_book_sources_bookSourceUrl` ON `${TABLE_NAME}` (`bookSourceUrl`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"chapters\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`url` TEXT NOT NULL, `title` TEXT NOT NULL, `baseUrl` TEXT NOT NULL, `bookUrl` TEXT NOT NULL, `index` INTEGER NOT NULL, `resourceUrl` TEXT, `tag` TEXT, `start` INTEGER, `end` INTEGER, `startFragmentId` TEXT, `endFragmentId` TEXT, `variable` TEXT, PRIMARY KEY(`url`, `bookUrl`), FOREIGN KEY(`bookUrl`) REFERENCES `books`(`bookUrl`) ON UPDATE NO ACTION ON DELETE CASCADE )\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"baseUrl\",\n            \"columnName\": \"baseUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"index\",\n            \"columnName\": \"index\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"resourceUrl\",\n            \"columnName\": \"resourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"tag\",\n            \"columnName\": \"tag\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"start\",\n            \"columnName\": \"start\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"end\",\n            \"columnName\": \"end\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"startFragmentId\",\n            \"columnName\": \"startFragmentId\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"endFragmentId\",\n            \"columnName\": \"endFragmentId\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"url\",\n            \"bookUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_chapters_bookUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"bookUrl\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_chapters_bookUrl` ON `${TABLE_NAME}` (`bookUrl`)\"\n          },\n          {\n            \"name\": \"index_chapters_bookUrl_index\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"bookUrl\",\n              \"index\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_chapters_bookUrl_index` ON `${TABLE_NAME}` (`bookUrl`, `index`)\"\n          }\n        ],\n        \"foreignKeys\": [\n          {\n            \"table\": \"books\",\n            \"onDelete\": \"CASCADE\",\n            \"onUpdate\": \"NO ACTION\",\n            \"columns\": [\n              \"bookUrl\"\n            ],\n            \"referencedColumns\": [\n              \"bookUrl\"\n            ]\n          }\n        ]\n      },\n      {\n        \"tableName\": \"replace_rules\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL, `group` TEXT, `pattern` TEXT NOT NULL, `replacement` TEXT NOT NULL, `scope` TEXT, `isEnabled` INTEGER NOT NULL, `isRegex` INTEGER NOT NULL, `sortOrder` INTEGER NOT NULL)\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"group\",\n            \"columnName\": \"group\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"pattern\",\n            \"columnName\": \"pattern\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"replacement\",\n            \"columnName\": \"replacement\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"scope\",\n            \"columnName\": \"scope\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"isEnabled\",\n            \"columnName\": \"isEnabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"isRegex\",\n            \"columnName\": \"isRegex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"sortOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"id\"\n          ],\n          \"autoGenerate\": true\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_replace_rules_id\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"id\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_replace_rules_id` ON `${TABLE_NAME}` (`id`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"searchBooks\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookUrl` TEXT NOT NULL, `origin` TEXT NOT NULL, `originName` TEXT NOT NULL, `type` INTEGER NOT NULL, `name` TEXT NOT NULL, `author` TEXT NOT NULL, `kind` TEXT, `coverUrl` TEXT, `intro` TEXT, `wordCount` TEXT, `latestChapterTitle` TEXT, `tocUrl` TEXT NOT NULL, `time` INTEGER NOT NULL, `variable` TEXT, `originOrder` INTEGER NOT NULL, PRIMARY KEY(`bookUrl`), FOREIGN KEY(`origin`) REFERENCES `book_sources`(`bookSourceUrl`) ON UPDATE NO ACTION ON DELETE CASCADE )\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"originName\",\n            \"columnName\": \"originName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"author\",\n            \"columnName\": \"author\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"kind\",\n            \"columnName\": \"kind\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"coverUrl\",\n            \"columnName\": \"coverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"intro\",\n            \"columnName\": \"intro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"wordCount\",\n            \"columnName\": \"wordCount\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"latestChapterTitle\",\n            \"columnName\": \"latestChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"tocUrl\",\n            \"columnName\": \"tocUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"time\",\n            \"columnName\": \"time\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"originOrder\",\n            \"columnName\": \"originOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"bookUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_searchBooks_bookUrl\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"bookUrl\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_searchBooks_bookUrl` ON `${TABLE_NAME}` (`bookUrl`)\"\n          },\n          {\n            \"name\": \"index_searchBooks_origin\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"origin\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_searchBooks_origin` ON `${TABLE_NAME}` (`origin`)\"\n          }\n        ],\n        \"foreignKeys\": [\n          {\n            \"table\": \"book_sources\",\n            \"onDelete\": \"CASCADE\",\n            \"onUpdate\": \"NO ACTION\",\n            \"columns\": [\n              \"origin\"\n            ],\n            \"referencedColumns\": [\n              \"bookSourceUrl\"\n            ]\n          }\n        ]\n      },\n      {\n        \"tableName\": \"search_keywords\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`word` TEXT NOT NULL, `usage` INTEGER NOT NULL, `lastUseTime` INTEGER NOT NULL, PRIMARY KEY(`word`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"word\",\n            \"columnName\": \"word\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"usage\",\n            \"columnName\": \"usage\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"lastUseTime\",\n            \"columnName\": \"lastUseTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"word\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_search_keywords_word\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"word\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_search_keywords_word` ON `${TABLE_NAME}` (`word`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"cookies\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`url` TEXT NOT NULL, `cookie` TEXT NOT NULL, PRIMARY KEY(`url`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"cookie\",\n            \"columnName\": \"cookie\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"url\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_cookies_url\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"url\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_cookies_url` ON `${TABLE_NAME}` (`url`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssSources\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`sourceUrl` TEXT NOT NULL, `sourceName` TEXT NOT NULL, `sourceIcon` TEXT NOT NULL, `sourceGroup` TEXT, `sourceComment` TEXT, `enabled` INTEGER NOT NULL, `concurrentRate` TEXT, `header` TEXT, `loginUrl` TEXT, `loginUi` TEXT, `loginCheckJs` TEXT, `sortUrl` TEXT, `singleUrl` INTEGER NOT NULL, `articleStyle` INTEGER NOT NULL, `ruleArticles` TEXT, `ruleNextPage` TEXT, `ruleTitle` TEXT, `rulePubDate` TEXT, `ruleDescription` TEXT, `ruleImage` TEXT, `ruleLink` TEXT, `ruleContent` TEXT, `style` TEXT, `enableJs` INTEGER NOT NULL, `loadWithBaseUrl` INTEGER NOT NULL, `customOrder` INTEGER NOT NULL, PRIMARY KEY(`sourceUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"sourceUrl\",\n            \"columnName\": \"sourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceName\",\n            \"columnName\": \"sourceName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceIcon\",\n            \"columnName\": \"sourceIcon\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceGroup\",\n            \"columnName\": \"sourceGroup\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"sourceComment\",\n            \"columnName\": \"sourceComment\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enabled\",\n            \"columnName\": \"enabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"concurrentRate\",\n            \"columnName\": \"concurrentRate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"header\",\n            \"columnName\": \"header\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUrl\",\n            \"columnName\": \"loginUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUi\",\n            \"columnName\": \"loginUi\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginCheckJs\",\n            \"columnName\": \"loginCheckJs\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"sortUrl\",\n            \"columnName\": \"sortUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"singleUrl\",\n            \"columnName\": \"singleUrl\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"articleStyle\",\n            \"columnName\": \"articleStyle\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"ruleArticles\",\n            \"columnName\": \"ruleArticles\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleNextPage\",\n            \"columnName\": \"ruleNextPage\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleTitle\",\n            \"columnName\": \"ruleTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"rulePubDate\",\n            \"columnName\": \"rulePubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleDescription\",\n            \"columnName\": \"ruleDescription\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleImage\",\n            \"columnName\": \"ruleImage\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleLink\",\n            \"columnName\": \"ruleLink\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleContent\",\n            \"columnName\": \"ruleContent\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"style\",\n            \"columnName\": \"style\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enableJs\",\n            \"columnName\": \"enableJs\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"loadWithBaseUrl\",\n            \"columnName\": \"loadWithBaseUrl\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"sourceUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_rssSources_sourceUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"sourceUrl\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_rssSources_sourceUrl` ON `${TABLE_NAME}` (`sourceUrl`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"bookmarks\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`time` INTEGER NOT NULL, `bookName` TEXT NOT NULL, `bookAuthor` TEXT NOT NULL, `chapterIndex` INTEGER NOT NULL, `chapterPos` INTEGER NOT NULL, `chapterName` TEXT NOT NULL, `bookText` TEXT NOT NULL, `content` TEXT NOT NULL, PRIMARY KEY(`time`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"time\",\n            \"columnName\": \"time\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookName\",\n            \"columnName\": \"bookName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookAuthor\",\n            \"columnName\": \"bookAuthor\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterIndex\",\n            \"columnName\": \"chapterIndex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterPos\",\n            \"columnName\": \"chapterPos\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterName\",\n            \"columnName\": \"chapterName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookText\",\n            \"columnName\": \"bookText\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"time\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_bookmarks_bookName_bookAuthor\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"bookName\",\n              \"bookAuthor\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_bookmarks_bookName_bookAuthor` ON `${TABLE_NAME}` (`bookName`, `bookAuthor`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssArticles\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`origin` TEXT NOT NULL, `sort` TEXT NOT NULL, `title` TEXT NOT NULL, `order` INTEGER NOT NULL, `link` TEXT NOT NULL, `pubDate` TEXT, `description` TEXT, `content` TEXT, `image` TEXT, `read` INTEGER NOT NULL, `variable` TEXT, PRIMARY KEY(`origin`, `link`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sort\",\n            \"columnName\": \"sort\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"link\",\n            \"columnName\": \"link\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"pubDate\",\n            \"columnName\": \"pubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"description\",\n            \"columnName\": \"description\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"image\",\n            \"columnName\": \"image\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"read\",\n            \"columnName\": \"read\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"origin\",\n            \"link\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssReadRecords\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`record` TEXT NOT NULL, `read` INTEGER NOT NULL, PRIMARY KEY(`record`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"record\",\n            \"columnName\": \"record\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"read\",\n            \"columnName\": \"read\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"record\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssStars\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`origin` TEXT NOT NULL, `sort` TEXT NOT NULL, `title` TEXT NOT NULL, `starTime` INTEGER NOT NULL, `link` TEXT NOT NULL, `pubDate` TEXT, `description` TEXT, `content` TEXT, `image` TEXT, `variable` TEXT, PRIMARY KEY(`origin`, `link`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sort\",\n            \"columnName\": \"sort\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"starTime\",\n            \"columnName\": \"starTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"link\",\n            \"columnName\": \"link\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"pubDate\",\n            \"columnName\": \"pubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"description\",\n            \"columnName\": \"description\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"image\",\n            \"columnName\": \"image\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"origin\",\n            \"link\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"txtTocRules\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `rule` TEXT NOT NULL, `serialNumber` INTEGER NOT NULL, `enable` INTEGER NOT NULL, PRIMARY KEY(`id`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"rule\",\n            \"columnName\": \"rule\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"serialNumber\",\n            \"columnName\": \"serialNumber\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enable\",\n            \"columnName\": \"enable\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"id\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"readRecord\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`deviceId` TEXT NOT NULL, `bookName` TEXT NOT NULL, `readTime` INTEGER NOT NULL, PRIMARY KEY(`deviceId`, `bookName`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"deviceId\",\n            \"columnName\": \"deviceId\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookName\",\n            \"columnName\": \"bookName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"readTime\",\n            \"columnName\": \"readTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"deviceId\",\n            \"bookName\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"httpTTS\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `url` TEXT NOT NULL, PRIMARY KEY(`id`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"id\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"caches\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`key` TEXT NOT NULL, `value` TEXT, `deadline` INTEGER NOT NULL, PRIMARY KEY(`key`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"key\",\n            \"columnName\": \"key\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"value\",\n            \"columnName\": \"value\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"deadline\",\n            \"columnName\": \"deadline\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"key\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_caches_key\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"key\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_caches_key` ON `${TABLE_NAME}` (`key`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"ruleSubs\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `url` TEXT NOT NULL, `type` INTEGER NOT NULL, `customOrder` INTEGER NOT NULL, `autoUpdate` INTEGER NOT NULL, `update` INTEGER NOT NULL, PRIMARY KEY(`id`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"autoUpdate\",\n            \"columnName\": \"autoUpdate\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"update\",\n            \"columnName\": \"update\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"id\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      }\n    ],\n    \"views\": [],\n    \"setupQueries\": [\n      \"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)\",\n      \"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '8cfa1fb6bb9a65c04bfe563680126a4f')\"\n    ]\n  }\n}"
  },
  {
    "path": "app/schemas/io.legado.app.data.AppDatabase/4.json",
    "content": "{\n  \"formatVersion\": 1,\n  \"database\": {\n    \"version\": 4,\n    \"identityHash\": \"3b81b2e900be34b8ceb48aaacc6b1004\",\n    \"entities\": [\n      {\n        \"tableName\": \"books\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookUrl` TEXT NOT NULL, `tocUrl` TEXT NOT NULL, `origin` TEXT NOT NULL, `originName` TEXT NOT NULL, `name` TEXT NOT NULL, `author` TEXT NOT NULL, `kind` TEXT, `customTag` TEXT, `coverUrl` TEXT, `customCoverUrl` TEXT, `intro` TEXT, `customIntro` TEXT, `charset` TEXT, `type` INTEGER NOT NULL, `group` INTEGER NOT NULL, `latestChapterTitle` TEXT, `latestChapterTime` INTEGER NOT NULL, `lastCheckTime` INTEGER NOT NULL, `lastCheckCount` INTEGER NOT NULL, `totalChapterNum` INTEGER NOT NULL, `durChapterTitle` TEXT, `durChapterIndex` INTEGER NOT NULL, `durChapterPos` INTEGER NOT NULL, `durChapterTime` INTEGER NOT NULL, `wordCount` TEXT, `canUpdate` INTEGER NOT NULL, `order` INTEGER NOT NULL, `originOrder` INTEGER NOT NULL, `useReplaceRule` INTEGER NOT NULL, `variable` TEXT, PRIMARY KEY(`bookUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"tocUrl\",\n            \"columnName\": \"tocUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"originName\",\n            \"columnName\": \"originName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"author\",\n            \"columnName\": \"author\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"kind\",\n            \"columnName\": \"kind\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customTag\",\n            \"columnName\": \"customTag\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"coverUrl\",\n            \"columnName\": \"coverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customCoverUrl\",\n            \"columnName\": \"customCoverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"intro\",\n            \"columnName\": \"intro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customIntro\",\n            \"columnName\": \"customIntro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"charset\",\n            \"columnName\": \"charset\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"group\",\n            \"columnName\": \"group\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"latestChapterTitle\",\n            \"columnName\": \"latestChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"latestChapterTime\",\n            \"columnName\": \"latestChapterTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"lastCheckTime\",\n            \"columnName\": \"lastCheckTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"lastCheckCount\",\n            \"columnName\": \"lastCheckCount\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"totalChapterNum\",\n            \"columnName\": \"totalChapterNum\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"durChapterTitle\",\n            \"columnName\": \"durChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"durChapterIndex\",\n            \"columnName\": \"durChapterIndex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"durChapterPos\",\n            \"columnName\": \"durChapterPos\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"durChapterTime\",\n            \"columnName\": \"durChapterTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"wordCount\",\n            \"columnName\": \"wordCount\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"canUpdate\",\n            \"columnName\": \"canUpdate\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"originOrder\",\n            \"columnName\": \"originOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"useReplaceRule\",\n            \"columnName\": \"useReplaceRule\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"bookUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_books_bookUrl\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"bookUrl\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_books_bookUrl` ON `${TABLE_NAME}` (`bookUrl`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"book_groups\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`groupId` INTEGER NOT NULL, `groupName` TEXT NOT NULL, `order` INTEGER NOT NULL, PRIMARY KEY(`groupId`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"groupId\",\n            \"columnName\": \"groupId\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"groupName\",\n            \"columnName\": \"groupName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"groupId\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"book_sources\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookSourceName` TEXT NOT NULL, `bookSourceGroup` TEXT, `bookSourceUrl` TEXT NOT NULL, `bookSourceType` INTEGER NOT NULL, `bookUrlPattern` TEXT, `customOrder` INTEGER NOT NULL, `enabled` INTEGER NOT NULL, `enabledExplore` INTEGER NOT NULL, `header` TEXT, `loginUrl` TEXT, `lastUpdateTime` INTEGER NOT NULL, `weight` INTEGER NOT NULL, `exploreUrl` TEXT, `ruleExplore` TEXT, `searchUrl` TEXT, `ruleSearch` TEXT, `ruleBookInfo` TEXT, `ruleToc` TEXT, `ruleContent` TEXT, PRIMARY KEY(`bookSourceUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookSourceName\",\n            \"columnName\": \"bookSourceName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookSourceGroup\",\n            \"columnName\": \"bookSourceGroup\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"bookSourceUrl\",\n            \"columnName\": \"bookSourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookSourceType\",\n            \"columnName\": \"bookSourceType\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookUrlPattern\",\n            \"columnName\": \"bookUrlPattern\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enabled\",\n            \"columnName\": \"enabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enabledExplore\",\n            \"columnName\": \"enabledExplore\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"header\",\n            \"columnName\": \"header\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUrl\",\n            \"columnName\": \"loginUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"lastUpdateTime\",\n            \"columnName\": \"lastUpdateTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"weight\",\n            \"columnName\": \"weight\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"exploreUrl\",\n            \"columnName\": \"exploreUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleExplore\",\n            \"columnName\": \"ruleExplore\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"searchUrl\",\n            \"columnName\": \"searchUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleSearch\",\n            \"columnName\": \"ruleSearch\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleBookInfo\",\n            \"columnName\": \"ruleBookInfo\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleToc\",\n            \"columnName\": \"ruleToc\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleContent\",\n            \"columnName\": \"ruleContent\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"bookSourceUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_book_sources_bookSourceUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"bookSourceUrl\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_book_sources_bookSourceUrl` ON `${TABLE_NAME}` (`bookSourceUrl`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"chapters\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`url` TEXT NOT NULL, `title` TEXT NOT NULL, `bookUrl` TEXT NOT NULL, `index` INTEGER NOT NULL, `resourceUrl` TEXT, `tag` TEXT, `start` INTEGER, `end` INTEGER, `variable` TEXT, PRIMARY KEY(`url`, `bookUrl`), FOREIGN KEY(`bookUrl`) REFERENCES `books`(`bookUrl`) ON UPDATE NO ACTION ON DELETE CASCADE )\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"index\",\n            \"columnName\": \"index\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"resourceUrl\",\n            \"columnName\": \"resourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"tag\",\n            \"columnName\": \"tag\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"start\",\n            \"columnName\": \"start\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"end\",\n            \"columnName\": \"end\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"url\",\n            \"bookUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_chapters_bookUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"bookUrl\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_chapters_bookUrl` ON `${TABLE_NAME}` (`bookUrl`)\"\n          },\n          {\n            \"name\": \"index_chapters_bookUrl_index\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"bookUrl\",\n              \"index\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_chapters_bookUrl_index` ON `${TABLE_NAME}` (`bookUrl`, `index`)\"\n          }\n        ],\n        \"foreignKeys\": [\n          {\n            \"table\": \"books\",\n            \"onDelete\": \"CASCADE\",\n            \"onUpdate\": \"NO ACTION\",\n            \"columns\": [\n              \"bookUrl\"\n            ],\n            \"referencedColumns\": [\n              \"bookUrl\"\n            ]\n          }\n        ]\n      },\n      {\n        \"tableName\": \"replace_rules\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL, `group` TEXT, `pattern` TEXT NOT NULL, `replacement` TEXT NOT NULL, `scope` TEXT, `isEnabled` INTEGER NOT NULL, `isRegex` INTEGER NOT NULL, `sortOrder` INTEGER NOT NULL)\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"group\",\n            \"columnName\": \"group\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"pattern\",\n            \"columnName\": \"pattern\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"replacement\",\n            \"columnName\": \"replacement\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"scope\",\n            \"columnName\": \"scope\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"isEnabled\",\n            \"columnName\": \"isEnabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"isRegex\",\n            \"columnName\": \"isRegex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"sortOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"id\"\n          ],\n          \"autoGenerate\": true\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_replace_rules_id\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"id\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_replace_rules_id` ON `${TABLE_NAME}` (`id`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"searchBooks\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookUrl` TEXT NOT NULL, `origin` TEXT NOT NULL, `originName` TEXT NOT NULL, `type` INTEGER NOT NULL, `name` TEXT NOT NULL, `author` TEXT NOT NULL, `kind` TEXT, `coverUrl` TEXT, `intro` TEXT, `wordCount` TEXT, `latestChapterTitle` TEXT, `tocUrl` TEXT NOT NULL, `time` INTEGER NOT NULL, `variable` TEXT, `originOrder` INTEGER NOT NULL, PRIMARY KEY(`bookUrl`), FOREIGN KEY(`origin`) REFERENCES `book_sources`(`bookSourceUrl`) ON UPDATE NO ACTION ON DELETE CASCADE )\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"originName\",\n            \"columnName\": \"originName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"author\",\n            \"columnName\": \"author\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"kind\",\n            \"columnName\": \"kind\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"coverUrl\",\n            \"columnName\": \"coverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"intro\",\n            \"columnName\": \"intro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"wordCount\",\n            \"columnName\": \"wordCount\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"latestChapterTitle\",\n            \"columnName\": \"latestChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"tocUrl\",\n            \"columnName\": \"tocUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"time\",\n            \"columnName\": \"time\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"originOrder\",\n            \"columnName\": \"originOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"bookUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_searchBooks_bookUrl\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"bookUrl\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_searchBooks_bookUrl` ON `${TABLE_NAME}` (`bookUrl`)\"\n          },\n          {\n            \"name\": \"index_searchBooks_origin\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"origin\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_searchBooks_origin` ON `${TABLE_NAME}` (`origin`)\"\n          }\n        ],\n        \"foreignKeys\": [\n          {\n            \"table\": \"book_sources\",\n            \"onDelete\": \"CASCADE\",\n            \"onUpdate\": \"NO ACTION\",\n            \"columns\": [\n              \"origin\"\n            ],\n            \"referencedColumns\": [\n              \"bookSourceUrl\"\n            ]\n          }\n        ]\n      },\n      {\n        \"tableName\": \"search_keywords\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`word` TEXT NOT NULL, `usage` INTEGER NOT NULL, `lastUseTime` INTEGER NOT NULL, PRIMARY KEY(`word`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"word\",\n            \"columnName\": \"word\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"usage\",\n            \"columnName\": \"usage\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"lastUseTime\",\n            \"columnName\": \"lastUseTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"word\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_search_keywords_word\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"word\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_search_keywords_word` ON `${TABLE_NAME}` (`word`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"cookies\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`url` TEXT NOT NULL, `cookie` TEXT NOT NULL, PRIMARY KEY(`url`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"cookie\",\n            \"columnName\": \"cookie\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"url\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_cookies_url\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"url\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_cookies_url` ON `${TABLE_NAME}` (`url`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssSources\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`sourceUrl` TEXT NOT NULL, `sourceName` TEXT NOT NULL, `sourceIcon` TEXT NOT NULL, `sourceGroup` TEXT, `enabled` INTEGER NOT NULL, `ruleArticles` TEXT, `ruleNextPage` TEXT, `ruleTitle` TEXT, `rulePubDate` TEXT, `ruleDescription` TEXT, `ruleImage` TEXT, `ruleLink` TEXT, `ruleContent` TEXT, `header` TEXT, `enableJs` INTEGER NOT NULL, `loadWithBaseUrl` INTEGER NOT NULL, `customOrder` INTEGER NOT NULL, PRIMARY KEY(`sourceUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"sourceUrl\",\n            \"columnName\": \"sourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceName\",\n            \"columnName\": \"sourceName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceIcon\",\n            \"columnName\": \"sourceIcon\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceGroup\",\n            \"columnName\": \"sourceGroup\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enabled\",\n            \"columnName\": \"enabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"ruleArticles\",\n            \"columnName\": \"ruleArticles\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleNextPage\",\n            \"columnName\": \"ruleNextPage\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleTitle\",\n            \"columnName\": \"ruleTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"rulePubDate\",\n            \"columnName\": \"rulePubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleDescription\",\n            \"columnName\": \"ruleDescription\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleImage\",\n            \"columnName\": \"ruleImage\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleLink\",\n            \"columnName\": \"ruleLink\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleContent\",\n            \"columnName\": \"ruleContent\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"header\",\n            \"columnName\": \"header\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enableJs\",\n            \"columnName\": \"enableJs\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"loadWithBaseUrl\",\n            \"columnName\": \"loadWithBaseUrl\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"sourceUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_rssSources_sourceUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"sourceUrl\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_rssSources_sourceUrl` ON `${TABLE_NAME}` (`sourceUrl`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"bookmarks\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`time` INTEGER NOT NULL, `bookUrl` TEXT NOT NULL, `bookName` TEXT NOT NULL, `chapterName` TEXT NOT NULL, `chapterIndex` INTEGER NOT NULL, `pageIndex` INTEGER NOT NULL, `content` TEXT NOT NULL, PRIMARY KEY(`time`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"time\",\n            \"columnName\": \"time\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookName\",\n            \"columnName\": \"bookName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterName\",\n            \"columnName\": \"chapterName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterIndex\",\n            \"columnName\": \"chapterIndex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"pageIndex\",\n            \"columnName\": \"pageIndex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"time\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_bookmarks_bookUrl\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"bookUrl\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_bookmarks_bookUrl` ON `${TABLE_NAME}` (`bookUrl`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssArticles\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`origin` TEXT NOT NULL, `title` TEXT NOT NULL, `order` INTEGER NOT NULL, `link` TEXT NOT NULL, `pubDate` TEXT, `description` TEXT, `content` TEXT, `image` TEXT, `read` INTEGER NOT NULL, PRIMARY KEY(`origin`, `link`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"link\",\n            \"columnName\": \"link\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"pubDate\",\n            \"columnName\": \"pubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"description\",\n            \"columnName\": \"description\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"image\",\n            \"columnName\": \"image\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"read\",\n            \"columnName\": \"read\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"origin\",\n            \"link\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssStars\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`origin` TEXT NOT NULL, `title` TEXT NOT NULL, `starTime` INTEGER NOT NULL, `link` TEXT NOT NULL, `pubDate` TEXT, `description` TEXT, `content` TEXT, `image` TEXT, PRIMARY KEY(`origin`, `link`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"starTime\",\n            \"columnName\": \"starTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"link\",\n            \"columnName\": \"link\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"pubDate\",\n            \"columnName\": \"pubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"description\",\n            \"columnName\": \"description\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"image\",\n            \"columnName\": \"image\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"origin\",\n            \"link\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      }\n    ],\n    \"views\": [],\n    \"setupQueries\": [\n      \"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)\",\n      \"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '3b81b2e900be34b8ceb48aaacc6b1004')\"\n    ]\n  }\n}"
  },
  {
    "path": "app/schemas/io.legado.app.data.AppDatabase/40.json",
    "content": "{\n  \"formatVersion\": 1,\n  \"database\": {\n    \"version\": 40,\n    \"identityHash\": \"09617e0520cd8ec1671d812a866b45a4\",\n    \"entities\": [\n      {\n        \"tableName\": \"books\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookUrl` TEXT NOT NULL, `tocUrl` TEXT NOT NULL, `origin` TEXT NOT NULL, `originName` TEXT NOT NULL, `name` TEXT NOT NULL, `author` TEXT NOT NULL, `kind` TEXT, `customTag` TEXT, `coverUrl` TEXT, `customCoverUrl` TEXT, `intro` TEXT, `customIntro` TEXT, `charset` TEXT, `type` INTEGER NOT NULL, `group` INTEGER NOT NULL, `latestChapterTitle` TEXT, `latestChapterTime` INTEGER NOT NULL, `lastCheckTime` INTEGER NOT NULL, `lastCheckCount` INTEGER NOT NULL, `totalChapterNum` INTEGER NOT NULL, `durChapterTitle` TEXT, `durChapterIndex` INTEGER NOT NULL, `durChapterPos` INTEGER NOT NULL, `durChapterTime` INTEGER NOT NULL, `wordCount` TEXT, `canUpdate` INTEGER NOT NULL, `order` INTEGER NOT NULL, `originOrder` INTEGER NOT NULL, `variable` TEXT, `readConfig` TEXT, PRIMARY KEY(`bookUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"tocUrl\",\n            \"columnName\": \"tocUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"originName\",\n            \"columnName\": \"originName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"author\",\n            \"columnName\": \"author\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"kind\",\n            \"columnName\": \"kind\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customTag\",\n            \"columnName\": \"customTag\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"coverUrl\",\n            \"columnName\": \"coverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customCoverUrl\",\n            \"columnName\": \"customCoverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"intro\",\n            \"columnName\": \"intro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customIntro\",\n            \"columnName\": \"customIntro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"charset\",\n            \"columnName\": \"charset\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"group\",\n            \"columnName\": \"group\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"latestChapterTitle\",\n            \"columnName\": \"latestChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"latestChapterTime\",\n            \"columnName\": \"latestChapterTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"lastCheckTime\",\n            \"columnName\": \"lastCheckTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"lastCheckCount\",\n            \"columnName\": \"lastCheckCount\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"totalChapterNum\",\n            \"columnName\": \"totalChapterNum\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"durChapterTitle\",\n            \"columnName\": \"durChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"durChapterIndex\",\n            \"columnName\": \"durChapterIndex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"durChapterPos\",\n            \"columnName\": \"durChapterPos\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"durChapterTime\",\n            \"columnName\": \"durChapterTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"wordCount\",\n            \"columnName\": \"wordCount\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"canUpdate\",\n            \"columnName\": \"canUpdate\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"originOrder\",\n            \"columnName\": \"originOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"readConfig\",\n            \"columnName\": \"readConfig\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"bookUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_books_name_author\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"name\",\n              \"author\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_books_name_author` ON `${TABLE_NAME}` (`name`, `author`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"book_groups\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`groupId` INTEGER NOT NULL, `groupName` TEXT NOT NULL, `cover` TEXT, `order` INTEGER NOT NULL, `show` INTEGER NOT NULL, PRIMARY KEY(`groupId`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"groupId\",\n            \"columnName\": \"groupId\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"groupName\",\n            \"columnName\": \"groupName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"cover\",\n            \"columnName\": \"cover\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"show\",\n            \"columnName\": \"show\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"groupId\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"book_sources\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookSourceName` TEXT NOT NULL, `bookSourceGroup` TEXT, `bookSourceUrl` TEXT NOT NULL, `bookSourceType` INTEGER NOT NULL, `bookUrlPattern` TEXT, `customOrder` INTEGER NOT NULL, `enabled` INTEGER NOT NULL, `enabledExplore` INTEGER NOT NULL, `concurrentRate` TEXT, `header` TEXT, `loginUrl` TEXT, `loginUi` TEXT, `loginCheckJs` TEXT, `bookSourceComment` TEXT, `lastUpdateTime` INTEGER NOT NULL, `respondTime` INTEGER NOT NULL, `weight` INTEGER NOT NULL, `exploreUrl` TEXT, `ruleExplore` TEXT, `searchUrl` TEXT, `ruleSearch` TEXT, `ruleBookInfo` TEXT, `ruleToc` TEXT, `ruleContent` TEXT, PRIMARY KEY(`bookSourceUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookSourceName\",\n            \"columnName\": \"bookSourceName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookSourceGroup\",\n            \"columnName\": \"bookSourceGroup\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"bookSourceUrl\",\n            \"columnName\": \"bookSourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookSourceType\",\n            \"columnName\": \"bookSourceType\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookUrlPattern\",\n            \"columnName\": \"bookUrlPattern\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enabled\",\n            \"columnName\": \"enabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enabledExplore\",\n            \"columnName\": \"enabledExplore\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"concurrentRate\",\n            \"columnName\": \"concurrentRate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"header\",\n            \"columnName\": \"header\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUrl\",\n            \"columnName\": \"loginUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUi\",\n            \"columnName\": \"loginUi\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginCheckJs\",\n            \"columnName\": \"loginCheckJs\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"bookSourceComment\",\n            \"columnName\": \"bookSourceComment\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"lastUpdateTime\",\n            \"columnName\": \"lastUpdateTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"respondTime\",\n            \"columnName\": \"respondTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"weight\",\n            \"columnName\": \"weight\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"exploreUrl\",\n            \"columnName\": \"exploreUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleExplore\",\n            \"columnName\": \"ruleExplore\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"searchUrl\",\n            \"columnName\": \"searchUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleSearch\",\n            \"columnName\": \"ruleSearch\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleBookInfo\",\n            \"columnName\": \"ruleBookInfo\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleToc\",\n            \"columnName\": \"ruleToc\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleContent\",\n            \"columnName\": \"ruleContent\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"bookSourceUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_book_sources_bookSourceUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"bookSourceUrl\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_book_sources_bookSourceUrl` ON `${TABLE_NAME}` (`bookSourceUrl`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"chapters\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`url` TEXT NOT NULL, `title` TEXT NOT NULL, `baseUrl` TEXT NOT NULL, `bookUrl` TEXT NOT NULL, `index` INTEGER NOT NULL, `isVip` INTEGER NOT NULL, `isPay` INTEGER NOT NULL, `resourceUrl` TEXT, `tag` TEXT, `start` INTEGER, `end` INTEGER, `startFragmentId` TEXT, `endFragmentId` TEXT, `variable` TEXT, PRIMARY KEY(`url`, `bookUrl`), FOREIGN KEY(`bookUrl`) REFERENCES `books`(`bookUrl`) ON UPDATE NO ACTION ON DELETE CASCADE )\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"baseUrl\",\n            \"columnName\": \"baseUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"index\",\n            \"columnName\": \"index\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"isVip\",\n            \"columnName\": \"isVip\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"isPay\",\n            \"columnName\": \"isPay\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"resourceUrl\",\n            \"columnName\": \"resourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"tag\",\n            \"columnName\": \"tag\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"start\",\n            \"columnName\": \"start\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"end\",\n            \"columnName\": \"end\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"startFragmentId\",\n            \"columnName\": \"startFragmentId\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"endFragmentId\",\n            \"columnName\": \"endFragmentId\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"url\",\n            \"bookUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_chapters_bookUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"bookUrl\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_chapters_bookUrl` ON `${TABLE_NAME}` (`bookUrl`)\"\n          },\n          {\n            \"name\": \"index_chapters_bookUrl_index\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"bookUrl\",\n              \"index\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_chapters_bookUrl_index` ON `${TABLE_NAME}` (`bookUrl`, `index`)\"\n          }\n        ],\n        \"foreignKeys\": [\n          {\n            \"table\": \"books\",\n            \"onDelete\": \"CASCADE\",\n            \"onUpdate\": \"NO ACTION\",\n            \"columns\": [\n              \"bookUrl\"\n            ],\n            \"referencedColumns\": [\n              \"bookUrl\"\n            ]\n          }\n        ]\n      },\n      {\n        \"tableName\": \"replace_rules\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL, `group` TEXT, `pattern` TEXT NOT NULL, `replacement` TEXT NOT NULL, `scope` TEXT, `isEnabled` INTEGER NOT NULL, `isRegex` INTEGER NOT NULL, `sortOrder` INTEGER NOT NULL)\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"group\",\n            \"columnName\": \"group\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"pattern\",\n            \"columnName\": \"pattern\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"replacement\",\n            \"columnName\": \"replacement\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"scope\",\n            \"columnName\": \"scope\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"isEnabled\",\n            \"columnName\": \"isEnabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"isRegex\",\n            \"columnName\": \"isRegex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"sortOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"id\"\n          ],\n          \"autoGenerate\": true\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_replace_rules_id\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"id\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_replace_rules_id` ON `${TABLE_NAME}` (`id`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"searchBooks\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookUrl` TEXT NOT NULL, `origin` TEXT NOT NULL, `originName` TEXT NOT NULL, `type` INTEGER NOT NULL, `name` TEXT NOT NULL, `author` TEXT NOT NULL, `kind` TEXT, `coverUrl` TEXT, `intro` TEXT, `wordCount` TEXT, `latestChapterTitle` TEXT, `tocUrl` TEXT NOT NULL, `time` INTEGER NOT NULL, `variable` TEXT, `originOrder` INTEGER NOT NULL, PRIMARY KEY(`bookUrl`), FOREIGN KEY(`origin`) REFERENCES `book_sources`(`bookSourceUrl`) ON UPDATE NO ACTION ON DELETE CASCADE )\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"originName\",\n            \"columnName\": \"originName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"author\",\n            \"columnName\": \"author\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"kind\",\n            \"columnName\": \"kind\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"coverUrl\",\n            \"columnName\": \"coverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"intro\",\n            \"columnName\": \"intro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"wordCount\",\n            \"columnName\": \"wordCount\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"latestChapterTitle\",\n            \"columnName\": \"latestChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"tocUrl\",\n            \"columnName\": \"tocUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"time\",\n            \"columnName\": \"time\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"originOrder\",\n            \"columnName\": \"originOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"bookUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_searchBooks_bookUrl\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"bookUrl\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_searchBooks_bookUrl` ON `${TABLE_NAME}` (`bookUrl`)\"\n          },\n          {\n            \"name\": \"index_searchBooks_origin\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"origin\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_searchBooks_origin` ON `${TABLE_NAME}` (`origin`)\"\n          }\n        ],\n        \"foreignKeys\": [\n          {\n            \"table\": \"book_sources\",\n            \"onDelete\": \"CASCADE\",\n            \"onUpdate\": \"NO ACTION\",\n            \"columns\": [\n              \"origin\"\n            ],\n            \"referencedColumns\": [\n              \"bookSourceUrl\"\n            ]\n          }\n        ]\n      },\n      {\n        \"tableName\": \"search_keywords\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`word` TEXT NOT NULL, `usage` INTEGER NOT NULL, `lastUseTime` INTEGER NOT NULL, PRIMARY KEY(`word`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"word\",\n            \"columnName\": \"word\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"usage\",\n            \"columnName\": \"usage\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"lastUseTime\",\n            \"columnName\": \"lastUseTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"word\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_search_keywords_word\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"word\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_search_keywords_word` ON `${TABLE_NAME}` (`word`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"cookies\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`url` TEXT NOT NULL, `cookie` TEXT NOT NULL, PRIMARY KEY(`url`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"cookie\",\n            \"columnName\": \"cookie\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"url\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_cookies_url\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"url\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_cookies_url` ON `${TABLE_NAME}` (`url`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssSources\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`sourceUrl` TEXT NOT NULL, `sourceName` TEXT NOT NULL, `sourceIcon` TEXT NOT NULL, `sourceGroup` TEXT, `sourceComment` TEXT, `enabled` INTEGER NOT NULL, `concurrentRate` TEXT, `header` TEXT, `loginUrl` TEXT, `loginUi` TEXT, `loginCheckJs` TEXT, `sortUrl` TEXT, `singleUrl` INTEGER NOT NULL, `articleStyle` INTEGER NOT NULL, `ruleArticles` TEXT, `ruleNextPage` TEXT, `ruleTitle` TEXT, `rulePubDate` TEXT, `ruleDescription` TEXT, `ruleImage` TEXT, `ruleLink` TEXT, `ruleContent` TEXT, `style` TEXT, `enableJs` INTEGER NOT NULL, `loadWithBaseUrl` INTEGER NOT NULL, `customOrder` INTEGER NOT NULL, PRIMARY KEY(`sourceUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"sourceUrl\",\n            \"columnName\": \"sourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceName\",\n            \"columnName\": \"sourceName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceIcon\",\n            \"columnName\": \"sourceIcon\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceGroup\",\n            \"columnName\": \"sourceGroup\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"sourceComment\",\n            \"columnName\": \"sourceComment\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enabled\",\n            \"columnName\": \"enabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"concurrentRate\",\n            \"columnName\": \"concurrentRate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"header\",\n            \"columnName\": \"header\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUrl\",\n            \"columnName\": \"loginUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUi\",\n            \"columnName\": \"loginUi\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginCheckJs\",\n            \"columnName\": \"loginCheckJs\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"sortUrl\",\n            \"columnName\": \"sortUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"singleUrl\",\n            \"columnName\": \"singleUrl\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"articleStyle\",\n            \"columnName\": \"articleStyle\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"ruleArticles\",\n            \"columnName\": \"ruleArticles\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleNextPage\",\n            \"columnName\": \"ruleNextPage\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleTitle\",\n            \"columnName\": \"ruleTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"rulePubDate\",\n            \"columnName\": \"rulePubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleDescription\",\n            \"columnName\": \"ruleDescription\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleImage\",\n            \"columnName\": \"ruleImage\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleLink\",\n            \"columnName\": \"ruleLink\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleContent\",\n            \"columnName\": \"ruleContent\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"style\",\n            \"columnName\": \"style\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enableJs\",\n            \"columnName\": \"enableJs\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"loadWithBaseUrl\",\n            \"columnName\": \"loadWithBaseUrl\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"sourceUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_rssSources_sourceUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"sourceUrl\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_rssSources_sourceUrl` ON `${TABLE_NAME}` (`sourceUrl`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"bookmarks\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`time` INTEGER NOT NULL, `bookName` TEXT NOT NULL, `bookAuthor` TEXT NOT NULL, `chapterIndex` INTEGER NOT NULL, `chapterPos` INTEGER NOT NULL, `chapterName` TEXT NOT NULL, `bookText` TEXT NOT NULL, `content` TEXT NOT NULL, PRIMARY KEY(`time`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"time\",\n            \"columnName\": \"time\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookName\",\n            \"columnName\": \"bookName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookAuthor\",\n            \"columnName\": \"bookAuthor\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterIndex\",\n            \"columnName\": \"chapterIndex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterPos\",\n            \"columnName\": \"chapterPos\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterName\",\n            \"columnName\": \"chapterName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookText\",\n            \"columnName\": \"bookText\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"time\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_bookmarks_bookName_bookAuthor\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"bookName\",\n              \"bookAuthor\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_bookmarks_bookName_bookAuthor` ON `${TABLE_NAME}` (`bookName`, `bookAuthor`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssArticles\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`origin` TEXT NOT NULL, `sort` TEXT NOT NULL, `title` TEXT NOT NULL, `order` INTEGER NOT NULL, `link` TEXT NOT NULL, `pubDate` TEXT, `description` TEXT, `content` TEXT, `image` TEXT, `read` INTEGER NOT NULL, `variable` TEXT, PRIMARY KEY(`origin`, `link`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sort\",\n            \"columnName\": \"sort\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"link\",\n            \"columnName\": \"link\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"pubDate\",\n            \"columnName\": \"pubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"description\",\n            \"columnName\": \"description\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"image\",\n            \"columnName\": \"image\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"read\",\n            \"columnName\": \"read\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"origin\",\n            \"link\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssReadRecords\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`record` TEXT NOT NULL, `read` INTEGER NOT NULL, PRIMARY KEY(`record`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"record\",\n            \"columnName\": \"record\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"read\",\n            \"columnName\": \"read\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"record\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssStars\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`origin` TEXT NOT NULL, `sort` TEXT NOT NULL, `title` TEXT NOT NULL, `starTime` INTEGER NOT NULL, `link` TEXT NOT NULL, `pubDate` TEXT, `description` TEXT, `content` TEXT, `image` TEXT, `variable` TEXT, PRIMARY KEY(`origin`, `link`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sort\",\n            \"columnName\": \"sort\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"starTime\",\n            \"columnName\": \"starTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"link\",\n            \"columnName\": \"link\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"pubDate\",\n            \"columnName\": \"pubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"description\",\n            \"columnName\": \"description\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"image\",\n            \"columnName\": \"image\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"origin\",\n            \"link\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"txtTocRules\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `rule` TEXT NOT NULL, `serialNumber` INTEGER NOT NULL, `enable` INTEGER NOT NULL, PRIMARY KEY(`id`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"rule\",\n            \"columnName\": \"rule\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"serialNumber\",\n            \"columnName\": \"serialNumber\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enable\",\n            \"columnName\": \"enable\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"id\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"readRecord\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`deviceId` TEXT NOT NULL, `bookName` TEXT NOT NULL, `readTime` INTEGER NOT NULL, PRIMARY KEY(`deviceId`, `bookName`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"deviceId\",\n            \"columnName\": \"deviceId\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookName\",\n            \"columnName\": \"bookName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"readTime\",\n            \"columnName\": \"readTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"deviceId\",\n            \"bookName\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"httpTTS\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `url` TEXT NOT NULL, PRIMARY KEY(`id`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"id\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"caches\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`key` TEXT NOT NULL, `value` TEXT, `deadline` INTEGER NOT NULL, PRIMARY KEY(`key`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"key\",\n            \"columnName\": \"key\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"value\",\n            \"columnName\": \"value\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"deadline\",\n            \"columnName\": \"deadline\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"key\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_caches_key\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"key\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_caches_key` ON `${TABLE_NAME}` (`key`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"ruleSubs\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `url` TEXT NOT NULL, `type` INTEGER NOT NULL, `customOrder` INTEGER NOT NULL, `autoUpdate` INTEGER NOT NULL, `update` INTEGER NOT NULL, PRIMARY KEY(`id`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"autoUpdate\",\n            \"columnName\": \"autoUpdate\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"update\",\n            \"columnName\": \"update\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"id\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      }\n    ],\n    \"views\": [],\n    \"setupQueries\": [\n      \"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)\",\n      \"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '09617e0520cd8ec1671d812a866b45a4')\"\n    ]\n  }\n}"
  },
  {
    "path": "app/schemas/io.legado.app.data.AppDatabase/41.json",
    "content": "{\n  \"formatVersion\": 1,\n  \"database\": {\n    \"version\": 41,\n    \"identityHash\": \"6fbd1d1b3918bcc6db113ad108e6b924\",\n    \"entities\": [\n      {\n        \"tableName\": \"books\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookUrl` TEXT NOT NULL, `tocUrl` TEXT NOT NULL, `origin` TEXT NOT NULL, `originName` TEXT NOT NULL, `name` TEXT NOT NULL, `author` TEXT NOT NULL, `kind` TEXT, `customTag` TEXT, `coverUrl` TEXT, `customCoverUrl` TEXT, `intro` TEXT, `customIntro` TEXT, `charset` TEXT, `type` INTEGER NOT NULL, `group` INTEGER NOT NULL, `latestChapterTitle` TEXT, `latestChapterTime` INTEGER NOT NULL, `lastCheckTime` INTEGER NOT NULL, `lastCheckCount` INTEGER NOT NULL, `totalChapterNum` INTEGER NOT NULL, `durChapterTitle` TEXT, `durChapterIndex` INTEGER NOT NULL, `durChapterPos` INTEGER NOT NULL, `durChapterTime` INTEGER NOT NULL, `wordCount` TEXT, `canUpdate` INTEGER NOT NULL, `order` INTEGER NOT NULL, `originOrder` INTEGER NOT NULL, `variable` TEXT, `readConfig` TEXT, PRIMARY KEY(`bookUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"tocUrl\",\n            \"columnName\": \"tocUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"originName\",\n            \"columnName\": \"originName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"author\",\n            \"columnName\": \"author\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"kind\",\n            \"columnName\": \"kind\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customTag\",\n            \"columnName\": \"customTag\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"coverUrl\",\n            \"columnName\": \"coverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customCoverUrl\",\n            \"columnName\": \"customCoverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"intro\",\n            \"columnName\": \"intro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customIntro\",\n            \"columnName\": \"customIntro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"charset\",\n            \"columnName\": \"charset\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"group\",\n            \"columnName\": \"group\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"latestChapterTitle\",\n            \"columnName\": \"latestChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"latestChapterTime\",\n            \"columnName\": \"latestChapterTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"lastCheckTime\",\n            \"columnName\": \"lastCheckTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"lastCheckCount\",\n            \"columnName\": \"lastCheckCount\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"totalChapterNum\",\n            \"columnName\": \"totalChapterNum\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"durChapterTitle\",\n            \"columnName\": \"durChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"durChapterIndex\",\n            \"columnName\": \"durChapterIndex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"durChapterPos\",\n            \"columnName\": \"durChapterPos\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"durChapterTime\",\n            \"columnName\": \"durChapterTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"wordCount\",\n            \"columnName\": \"wordCount\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"canUpdate\",\n            \"columnName\": \"canUpdate\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"originOrder\",\n            \"columnName\": \"originOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"readConfig\",\n            \"columnName\": \"readConfig\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"bookUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_books_name_author\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"name\",\n              \"author\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_books_name_author` ON `${TABLE_NAME}` (`name`, `author`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"book_groups\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`groupId` INTEGER NOT NULL, `groupName` TEXT NOT NULL, `cover` TEXT, `order` INTEGER NOT NULL, `show` INTEGER NOT NULL, PRIMARY KEY(`groupId`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"groupId\",\n            \"columnName\": \"groupId\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"groupName\",\n            \"columnName\": \"groupName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"cover\",\n            \"columnName\": \"cover\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"show\",\n            \"columnName\": \"show\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"groupId\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"book_sources\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookSourceName` TEXT NOT NULL, `bookSourceGroup` TEXT, `bookSourceUrl` TEXT NOT NULL, `bookSourceType` INTEGER NOT NULL, `bookUrlPattern` TEXT, `customOrder` INTEGER NOT NULL, `enabled` INTEGER NOT NULL, `enabledExplore` INTEGER NOT NULL, `concurrentRate` TEXT, `header` TEXT, `loginUrl` TEXT, `loginUi` TEXT, `loginCheckJs` TEXT, `bookSourceComment` TEXT, `lastUpdateTime` INTEGER NOT NULL, `respondTime` INTEGER NOT NULL, `weight` INTEGER NOT NULL, `exploreUrl` TEXT, `ruleExplore` TEXT, `searchUrl` TEXT, `ruleSearch` TEXT, `ruleBookInfo` TEXT, `ruleToc` TEXT, `ruleContent` TEXT, PRIMARY KEY(`bookSourceUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookSourceName\",\n            \"columnName\": \"bookSourceName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookSourceGroup\",\n            \"columnName\": \"bookSourceGroup\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"bookSourceUrl\",\n            \"columnName\": \"bookSourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookSourceType\",\n            \"columnName\": \"bookSourceType\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookUrlPattern\",\n            \"columnName\": \"bookUrlPattern\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enabled\",\n            \"columnName\": \"enabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enabledExplore\",\n            \"columnName\": \"enabledExplore\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"concurrentRate\",\n            \"columnName\": \"concurrentRate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"header\",\n            \"columnName\": \"header\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUrl\",\n            \"columnName\": \"loginUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUi\",\n            \"columnName\": \"loginUi\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginCheckJs\",\n            \"columnName\": \"loginCheckJs\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"bookSourceComment\",\n            \"columnName\": \"bookSourceComment\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"lastUpdateTime\",\n            \"columnName\": \"lastUpdateTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"respondTime\",\n            \"columnName\": \"respondTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"weight\",\n            \"columnName\": \"weight\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"exploreUrl\",\n            \"columnName\": \"exploreUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleExplore\",\n            \"columnName\": \"ruleExplore\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"searchUrl\",\n            \"columnName\": \"searchUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleSearch\",\n            \"columnName\": \"ruleSearch\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleBookInfo\",\n            \"columnName\": \"ruleBookInfo\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleToc\",\n            \"columnName\": \"ruleToc\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleContent\",\n            \"columnName\": \"ruleContent\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"bookSourceUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_book_sources_bookSourceUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"bookSourceUrl\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_book_sources_bookSourceUrl` ON `${TABLE_NAME}` (`bookSourceUrl`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"chapters\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`url` TEXT NOT NULL, `title` TEXT NOT NULL, `baseUrl` TEXT NOT NULL, `bookUrl` TEXT NOT NULL, `index` INTEGER NOT NULL, `isVip` INTEGER NOT NULL, `isPay` INTEGER NOT NULL, `resourceUrl` TEXT, `tag` TEXT, `start` INTEGER, `end` INTEGER, `startFragmentId` TEXT, `endFragmentId` TEXT, `variable` TEXT, PRIMARY KEY(`url`, `bookUrl`), FOREIGN KEY(`bookUrl`) REFERENCES `books`(`bookUrl`) ON UPDATE NO ACTION ON DELETE CASCADE )\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"baseUrl\",\n            \"columnName\": \"baseUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"index\",\n            \"columnName\": \"index\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"isVip\",\n            \"columnName\": \"isVip\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"isPay\",\n            \"columnName\": \"isPay\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"resourceUrl\",\n            \"columnName\": \"resourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"tag\",\n            \"columnName\": \"tag\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"start\",\n            \"columnName\": \"start\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"end\",\n            \"columnName\": \"end\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"startFragmentId\",\n            \"columnName\": \"startFragmentId\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"endFragmentId\",\n            \"columnName\": \"endFragmentId\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"url\",\n            \"bookUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_chapters_bookUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"bookUrl\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_chapters_bookUrl` ON `${TABLE_NAME}` (`bookUrl`)\"\n          },\n          {\n            \"name\": \"index_chapters_bookUrl_index\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"bookUrl\",\n              \"index\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_chapters_bookUrl_index` ON `${TABLE_NAME}` (`bookUrl`, `index`)\"\n          }\n        ],\n        \"foreignKeys\": [\n          {\n            \"table\": \"books\",\n            \"onDelete\": \"CASCADE\",\n            \"onUpdate\": \"NO ACTION\",\n            \"columns\": [\n              \"bookUrl\"\n            ],\n            \"referencedColumns\": [\n              \"bookUrl\"\n            ]\n          }\n        ]\n      },\n      {\n        \"tableName\": \"replace_rules\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL, `group` TEXT, `pattern` TEXT NOT NULL, `replacement` TEXT NOT NULL, `scope` TEXT, `isEnabled` INTEGER NOT NULL, `isRegex` INTEGER NOT NULL, `sortOrder` INTEGER NOT NULL)\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"group\",\n            \"columnName\": \"group\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"pattern\",\n            \"columnName\": \"pattern\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"replacement\",\n            \"columnName\": \"replacement\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"scope\",\n            \"columnName\": \"scope\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"isEnabled\",\n            \"columnName\": \"isEnabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"isRegex\",\n            \"columnName\": \"isRegex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"sortOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"id\"\n          ],\n          \"autoGenerate\": true\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_replace_rules_id\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"id\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_replace_rules_id` ON `${TABLE_NAME}` (`id`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"searchBooks\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookUrl` TEXT NOT NULL, `origin` TEXT NOT NULL, `originName` TEXT NOT NULL, `type` INTEGER NOT NULL, `name` TEXT NOT NULL, `author` TEXT NOT NULL, `kind` TEXT, `coverUrl` TEXT, `intro` TEXT, `wordCount` TEXT, `latestChapterTitle` TEXT, `tocUrl` TEXT NOT NULL, `time` INTEGER NOT NULL, `variable` TEXT, `originOrder` INTEGER NOT NULL, PRIMARY KEY(`bookUrl`), FOREIGN KEY(`origin`) REFERENCES `book_sources`(`bookSourceUrl`) ON UPDATE NO ACTION ON DELETE CASCADE )\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"originName\",\n            \"columnName\": \"originName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"author\",\n            \"columnName\": \"author\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"kind\",\n            \"columnName\": \"kind\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"coverUrl\",\n            \"columnName\": \"coverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"intro\",\n            \"columnName\": \"intro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"wordCount\",\n            \"columnName\": \"wordCount\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"latestChapterTitle\",\n            \"columnName\": \"latestChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"tocUrl\",\n            \"columnName\": \"tocUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"time\",\n            \"columnName\": \"time\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"originOrder\",\n            \"columnName\": \"originOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"bookUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_searchBooks_bookUrl\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"bookUrl\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_searchBooks_bookUrl` ON `${TABLE_NAME}` (`bookUrl`)\"\n          },\n          {\n            \"name\": \"index_searchBooks_origin\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"origin\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_searchBooks_origin` ON `${TABLE_NAME}` (`origin`)\"\n          }\n        ],\n        \"foreignKeys\": [\n          {\n            \"table\": \"book_sources\",\n            \"onDelete\": \"CASCADE\",\n            \"onUpdate\": \"NO ACTION\",\n            \"columns\": [\n              \"origin\"\n            ],\n            \"referencedColumns\": [\n              \"bookSourceUrl\"\n            ]\n          }\n        ]\n      },\n      {\n        \"tableName\": \"search_keywords\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`word` TEXT NOT NULL, `usage` INTEGER NOT NULL, `lastUseTime` INTEGER NOT NULL, PRIMARY KEY(`word`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"word\",\n            \"columnName\": \"word\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"usage\",\n            \"columnName\": \"usage\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"lastUseTime\",\n            \"columnName\": \"lastUseTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"word\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_search_keywords_word\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"word\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_search_keywords_word` ON `${TABLE_NAME}` (`word`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"cookies\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`url` TEXT NOT NULL, `cookie` TEXT NOT NULL, PRIMARY KEY(`url`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"cookie\",\n            \"columnName\": \"cookie\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"url\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_cookies_url\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"url\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_cookies_url` ON `${TABLE_NAME}` (`url`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssSources\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`sourceUrl` TEXT NOT NULL, `sourceName` TEXT NOT NULL, `sourceIcon` TEXT NOT NULL, `sourceGroup` TEXT, `sourceComment` TEXT, `enabled` INTEGER NOT NULL, `concurrentRate` TEXT, `header` TEXT, `loginUrl` TEXT, `loginUi` TEXT, `loginCheckJs` TEXT, `sortUrl` TEXT, `singleUrl` INTEGER NOT NULL, `articleStyle` INTEGER NOT NULL, `ruleArticles` TEXT, `ruleNextPage` TEXT, `ruleTitle` TEXT, `rulePubDate` TEXT, `ruleDescription` TEXT, `ruleImage` TEXT, `ruleLink` TEXT, `ruleContent` TEXT, `style` TEXT, `enableJs` INTEGER NOT NULL, `loadWithBaseUrl` INTEGER NOT NULL, `customOrder` INTEGER NOT NULL, PRIMARY KEY(`sourceUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"sourceUrl\",\n            \"columnName\": \"sourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceName\",\n            \"columnName\": \"sourceName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceIcon\",\n            \"columnName\": \"sourceIcon\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceGroup\",\n            \"columnName\": \"sourceGroup\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"sourceComment\",\n            \"columnName\": \"sourceComment\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enabled\",\n            \"columnName\": \"enabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"concurrentRate\",\n            \"columnName\": \"concurrentRate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"header\",\n            \"columnName\": \"header\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUrl\",\n            \"columnName\": \"loginUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUi\",\n            \"columnName\": \"loginUi\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginCheckJs\",\n            \"columnName\": \"loginCheckJs\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"sortUrl\",\n            \"columnName\": \"sortUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"singleUrl\",\n            \"columnName\": \"singleUrl\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"articleStyle\",\n            \"columnName\": \"articleStyle\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"ruleArticles\",\n            \"columnName\": \"ruleArticles\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleNextPage\",\n            \"columnName\": \"ruleNextPage\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleTitle\",\n            \"columnName\": \"ruleTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"rulePubDate\",\n            \"columnName\": \"rulePubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleDescription\",\n            \"columnName\": \"ruleDescription\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleImage\",\n            \"columnName\": \"ruleImage\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleLink\",\n            \"columnName\": \"ruleLink\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleContent\",\n            \"columnName\": \"ruleContent\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"style\",\n            \"columnName\": \"style\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enableJs\",\n            \"columnName\": \"enableJs\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"loadWithBaseUrl\",\n            \"columnName\": \"loadWithBaseUrl\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"sourceUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_rssSources_sourceUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"sourceUrl\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_rssSources_sourceUrl` ON `${TABLE_NAME}` (`sourceUrl`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"bookmarks\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`time` INTEGER NOT NULL, `bookName` TEXT NOT NULL, `bookAuthor` TEXT NOT NULL, `chapterIndex` INTEGER NOT NULL, `chapterPos` INTEGER NOT NULL, `chapterName` TEXT NOT NULL, `bookText` TEXT NOT NULL, `content` TEXT NOT NULL, PRIMARY KEY(`time`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"time\",\n            \"columnName\": \"time\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookName\",\n            \"columnName\": \"bookName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookAuthor\",\n            \"columnName\": \"bookAuthor\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterIndex\",\n            \"columnName\": \"chapterIndex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterPos\",\n            \"columnName\": \"chapterPos\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterName\",\n            \"columnName\": \"chapterName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookText\",\n            \"columnName\": \"bookText\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"time\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_bookmarks_bookName_bookAuthor\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"bookName\",\n              \"bookAuthor\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_bookmarks_bookName_bookAuthor` ON `${TABLE_NAME}` (`bookName`, `bookAuthor`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssArticles\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`origin` TEXT NOT NULL, `sort` TEXT NOT NULL, `title` TEXT NOT NULL, `order` INTEGER NOT NULL, `link` TEXT NOT NULL, `pubDate` TEXT, `description` TEXT, `content` TEXT, `image` TEXT, `read` INTEGER NOT NULL, `variable` TEXT, PRIMARY KEY(`origin`, `link`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sort\",\n            \"columnName\": \"sort\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"link\",\n            \"columnName\": \"link\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"pubDate\",\n            \"columnName\": \"pubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"description\",\n            \"columnName\": \"description\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"image\",\n            \"columnName\": \"image\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"read\",\n            \"columnName\": \"read\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"origin\",\n            \"link\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssReadRecords\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`record` TEXT NOT NULL, `read` INTEGER NOT NULL, PRIMARY KEY(`record`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"record\",\n            \"columnName\": \"record\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"read\",\n            \"columnName\": \"read\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"record\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssStars\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`origin` TEXT NOT NULL, `sort` TEXT NOT NULL, `title` TEXT NOT NULL, `starTime` INTEGER NOT NULL, `link` TEXT NOT NULL, `pubDate` TEXT, `description` TEXT, `content` TEXT, `image` TEXT, `variable` TEXT, PRIMARY KEY(`origin`, `link`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sort\",\n            \"columnName\": \"sort\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"starTime\",\n            \"columnName\": \"starTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"link\",\n            \"columnName\": \"link\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"pubDate\",\n            \"columnName\": \"pubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"description\",\n            \"columnName\": \"description\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"image\",\n            \"columnName\": \"image\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"origin\",\n            \"link\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"txtTocRules\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `rule` TEXT NOT NULL, `serialNumber` INTEGER NOT NULL, `enable` INTEGER NOT NULL, PRIMARY KEY(`id`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"rule\",\n            \"columnName\": \"rule\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"serialNumber\",\n            \"columnName\": \"serialNumber\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enable\",\n            \"columnName\": \"enable\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"id\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"readRecord\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`deviceId` TEXT NOT NULL, `bookName` TEXT NOT NULL, `readTime` INTEGER NOT NULL, PRIMARY KEY(`deviceId`, `bookName`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"deviceId\",\n            \"columnName\": \"deviceId\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookName\",\n            \"columnName\": \"bookName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"readTime\",\n            \"columnName\": \"readTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"deviceId\",\n            \"bookName\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"httpTTS\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `url` TEXT NOT NULL, `concurrentRate` TEXT, `loginUrl` TEXT, `loginUi` TEXT, `header` TEXT, `loginCheckJs` TEXT, PRIMARY KEY(`id`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"concurrentRate\",\n            \"columnName\": \"concurrentRate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUrl\",\n            \"columnName\": \"loginUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUi\",\n            \"columnName\": \"loginUi\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"header\",\n            \"columnName\": \"header\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginCheckJs\",\n            \"columnName\": \"loginCheckJs\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"id\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"caches\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`key` TEXT NOT NULL, `value` TEXT, `deadline` INTEGER NOT NULL, PRIMARY KEY(`key`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"key\",\n            \"columnName\": \"key\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"value\",\n            \"columnName\": \"value\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"deadline\",\n            \"columnName\": \"deadline\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"key\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_caches_key\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"key\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_caches_key` ON `${TABLE_NAME}` (`key`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"ruleSubs\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `url` TEXT NOT NULL, `type` INTEGER NOT NULL, `customOrder` INTEGER NOT NULL, `autoUpdate` INTEGER NOT NULL, `update` INTEGER NOT NULL, PRIMARY KEY(`id`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"autoUpdate\",\n            \"columnName\": \"autoUpdate\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"update\",\n            \"columnName\": \"update\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"id\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      }\n    ],\n    \"views\": [],\n    \"setupQueries\": [\n      \"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)\",\n      \"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '6fbd1d1b3918bcc6db113ad108e6b924')\"\n    ]\n  }\n}"
  },
  {
    "path": "app/schemas/io.legado.app.data.AppDatabase/42.json",
    "content": "{\n  \"formatVersion\": 1,\n  \"database\": {\n    \"version\": 42,\n    \"identityHash\": \"5bef05ac6abeaa4b82c3ff6e9e6bd7b3\",\n    \"entities\": [\n      {\n        \"tableName\": \"books\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookUrl` TEXT NOT NULL, `tocUrl` TEXT NOT NULL, `origin` TEXT NOT NULL, `originName` TEXT NOT NULL, `name` TEXT NOT NULL, `author` TEXT NOT NULL, `kind` TEXT, `customTag` TEXT, `coverUrl` TEXT, `customCoverUrl` TEXT, `intro` TEXT, `customIntro` TEXT, `charset` TEXT, `type` INTEGER NOT NULL, `group` INTEGER NOT NULL, `latestChapterTitle` TEXT, `latestChapterTime` INTEGER NOT NULL, `lastCheckTime` INTEGER NOT NULL, `lastCheckCount` INTEGER NOT NULL, `totalChapterNum` INTEGER NOT NULL, `durChapterTitle` TEXT, `durChapterIndex` INTEGER NOT NULL, `durChapterPos` INTEGER NOT NULL, `durChapterTime` INTEGER NOT NULL, `wordCount` TEXT, `canUpdate` INTEGER NOT NULL, `order` INTEGER NOT NULL, `originOrder` INTEGER NOT NULL, `variable` TEXT, `readConfig` TEXT, PRIMARY KEY(`bookUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"tocUrl\",\n            \"columnName\": \"tocUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"originName\",\n            \"columnName\": \"originName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"author\",\n            \"columnName\": \"author\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"kind\",\n            \"columnName\": \"kind\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customTag\",\n            \"columnName\": \"customTag\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"coverUrl\",\n            \"columnName\": \"coverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customCoverUrl\",\n            \"columnName\": \"customCoverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"intro\",\n            \"columnName\": \"intro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customIntro\",\n            \"columnName\": \"customIntro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"charset\",\n            \"columnName\": \"charset\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"group\",\n            \"columnName\": \"group\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"latestChapterTitle\",\n            \"columnName\": \"latestChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"latestChapterTime\",\n            \"columnName\": \"latestChapterTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"lastCheckTime\",\n            \"columnName\": \"lastCheckTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"lastCheckCount\",\n            \"columnName\": \"lastCheckCount\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"totalChapterNum\",\n            \"columnName\": \"totalChapterNum\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"durChapterTitle\",\n            \"columnName\": \"durChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"durChapterIndex\",\n            \"columnName\": \"durChapterIndex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"durChapterPos\",\n            \"columnName\": \"durChapterPos\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"durChapterTime\",\n            \"columnName\": \"durChapterTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"wordCount\",\n            \"columnName\": \"wordCount\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"canUpdate\",\n            \"columnName\": \"canUpdate\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"originOrder\",\n            \"columnName\": \"originOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"readConfig\",\n            \"columnName\": \"readConfig\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"bookUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_books_name_author\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"name\",\n              \"author\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_books_name_author` ON `${TABLE_NAME}` (`name`, `author`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"book_groups\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`groupId` INTEGER NOT NULL, `groupName` TEXT NOT NULL, `cover` TEXT, `order` INTEGER NOT NULL, `show` INTEGER NOT NULL, PRIMARY KEY(`groupId`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"groupId\",\n            \"columnName\": \"groupId\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"groupName\",\n            \"columnName\": \"groupName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"cover\",\n            \"columnName\": \"cover\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"show\",\n            \"columnName\": \"show\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"groupId\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"book_sources\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookSourceName` TEXT NOT NULL, `bookSourceGroup` TEXT, `bookSourceUrl` TEXT NOT NULL, `bookSourceType` INTEGER NOT NULL, `bookUrlPattern` TEXT, `customOrder` INTEGER NOT NULL, `enabled` INTEGER NOT NULL, `enabledExplore` INTEGER NOT NULL, `concurrentRate` TEXT, `header` TEXT, `loginUrl` TEXT, `loginUi` TEXT, `loginCheckJs` TEXT, `bookSourceComment` TEXT, `lastUpdateTime` INTEGER NOT NULL, `respondTime` INTEGER NOT NULL, `weight` INTEGER NOT NULL, `exploreUrl` TEXT, `ruleExplore` TEXT, `searchUrl` TEXT, `ruleSearch` TEXT, `ruleBookInfo` TEXT, `ruleToc` TEXT, `ruleContent` TEXT, PRIMARY KEY(`bookSourceUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookSourceName\",\n            \"columnName\": \"bookSourceName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookSourceGroup\",\n            \"columnName\": \"bookSourceGroup\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"bookSourceUrl\",\n            \"columnName\": \"bookSourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookSourceType\",\n            \"columnName\": \"bookSourceType\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookUrlPattern\",\n            \"columnName\": \"bookUrlPattern\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enabled\",\n            \"columnName\": \"enabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enabledExplore\",\n            \"columnName\": \"enabledExplore\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"concurrentRate\",\n            \"columnName\": \"concurrentRate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"header\",\n            \"columnName\": \"header\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUrl\",\n            \"columnName\": \"loginUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUi\",\n            \"columnName\": \"loginUi\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginCheckJs\",\n            \"columnName\": \"loginCheckJs\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"bookSourceComment\",\n            \"columnName\": \"bookSourceComment\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"lastUpdateTime\",\n            \"columnName\": \"lastUpdateTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"respondTime\",\n            \"columnName\": \"respondTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"weight\",\n            \"columnName\": \"weight\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"exploreUrl\",\n            \"columnName\": \"exploreUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleExplore\",\n            \"columnName\": \"ruleExplore\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"searchUrl\",\n            \"columnName\": \"searchUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleSearch\",\n            \"columnName\": \"ruleSearch\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleBookInfo\",\n            \"columnName\": \"ruleBookInfo\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleToc\",\n            \"columnName\": \"ruleToc\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleContent\",\n            \"columnName\": \"ruleContent\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"bookSourceUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_book_sources_bookSourceUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"bookSourceUrl\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_book_sources_bookSourceUrl` ON `${TABLE_NAME}` (`bookSourceUrl`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"chapters\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`url` TEXT NOT NULL, `title` TEXT NOT NULL, `baseUrl` TEXT NOT NULL, `bookUrl` TEXT NOT NULL, `index` INTEGER NOT NULL, `isVip` INTEGER NOT NULL, `isPay` INTEGER NOT NULL, `resourceUrl` TEXT, `tag` TEXT, `start` INTEGER, `end` INTEGER, `startFragmentId` TEXT, `endFragmentId` TEXT, `variable` TEXT, PRIMARY KEY(`url`, `bookUrl`), FOREIGN KEY(`bookUrl`) REFERENCES `books`(`bookUrl`) ON UPDATE NO ACTION ON DELETE CASCADE )\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"baseUrl\",\n            \"columnName\": \"baseUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"index\",\n            \"columnName\": \"index\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"isVip\",\n            \"columnName\": \"isVip\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"isPay\",\n            \"columnName\": \"isPay\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"resourceUrl\",\n            \"columnName\": \"resourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"tag\",\n            \"columnName\": \"tag\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"start\",\n            \"columnName\": \"start\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"end\",\n            \"columnName\": \"end\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"startFragmentId\",\n            \"columnName\": \"startFragmentId\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"endFragmentId\",\n            \"columnName\": \"endFragmentId\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"url\",\n            \"bookUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_chapters_bookUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"bookUrl\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_chapters_bookUrl` ON `${TABLE_NAME}` (`bookUrl`)\"\n          },\n          {\n            \"name\": \"index_chapters_bookUrl_index\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"bookUrl\",\n              \"index\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_chapters_bookUrl_index` ON `${TABLE_NAME}` (`bookUrl`, `index`)\"\n          }\n        ],\n        \"foreignKeys\": [\n          {\n            \"table\": \"books\",\n            \"onDelete\": \"CASCADE\",\n            \"onUpdate\": \"NO ACTION\",\n            \"columns\": [\n              \"bookUrl\"\n            ],\n            \"referencedColumns\": [\n              \"bookUrl\"\n            ]\n          }\n        ]\n      },\n      {\n        \"tableName\": \"replace_rules\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL, `group` TEXT, `pattern` TEXT NOT NULL, `replacement` TEXT NOT NULL, `scope` TEXT, `isEnabled` INTEGER NOT NULL, `isRegex` INTEGER NOT NULL, `sortOrder` INTEGER NOT NULL)\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"group\",\n            \"columnName\": \"group\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"pattern\",\n            \"columnName\": \"pattern\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"replacement\",\n            \"columnName\": \"replacement\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"scope\",\n            \"columnName\": \"scope\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"isEnabled\",\n            \"columnName\": \"isEnabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"isRegex\",\n            \"columnName\": \"isRegex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"sortOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"id\"\n          ],\n          \"autoGenerate\": true\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_replace_rules_id\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"id\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_replace_rules_id` ON `${TABLE_NAME}` (`id`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"searchBooks\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookUrl` TEXT NOT NULL, `origin` TEXT NOT NULL, `originName` TEXT NOT NULL, `type` INTEGER NOT NULL, `name` TEXT NOT NULL, `author` TEXT NOT NULL, `kind` TEXT, `coverUrl` TEXT, `intro` TEXT, `wordCount` TEXT, `latestChapterTitle` TEXT, `tocUrl` TEXT NOT NULL, `time` INTEGER NOT NULL, `variable` TEXT, `originOrder` INTEGER NOT NULL, PRIMARY KEY(`bookUrl`), FOREIGN KEY(`origin`) REFERENCES `book_sources`(`bookSourceUrl`) ON UPDATE NO ACTION ON DELETE CASCADE )\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"originName\",\n            \"columnName\": \"originName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"author\",\n            \"columnName\": \"author\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"kind\",\n            \"columnName\": \"kind\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"coverUrl\",\n            \"columnName\": \"coverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"intro\",\n            \"columnName\": \"intro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"wordCount\",\n            \"columnName\": \"wordCount\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"latestChapterTitle\",\n            \"columnName\": \"latestChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"tocUrl\",\n            \"columnName\": \"tocUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"time\",\n            \"columnName\": \"time\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"originOrder\",\n            \"columnName\": \"originOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"bookUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_searchBooks_bookUrl\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"bookUrl\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_searchBooks_bookUrl` ON `${TABLE_NAME}` (`bookUrl`)\"\n          },\n          {\n            \"name\": \"index_searchBooks_origin\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"origin\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_searchBooks_origin` ON `${TABLE_NAME}` (`origin`)\"\n          }\n        ],\n        \"foreignKeys\": [\n          {\n            \"table\": \"book_sources\",\n            \"onDelete\": \"CASCADE\",\n            \"onUpdate\": \"NO ACTION\",\n            \"columns\": [\n              \"origin\"\n            ],\n            \"referencedColumns\": [\n              \"bookSourceUrl\"\n            ]\n          }\n        ]\n      },\n      {\n        \"tableName\": \"search_keywords\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`word` TEXT NOT NULL, `usage` INTEGER NOT NULL, `lastUseTime` INTEGER NOT NULL, PRIMARY KEY(`word`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"word\",\n            \"columnName\": \"word\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"usage\",\n            \"columnName\": \"usage\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"lastUseTime\",\n            \"columnName\": \"lastUseTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"word\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_search_keywords_word\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"word\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_search_keywords_word` ON `${TABLE_NAME}` (`word`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"cookies\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`url` TEXT NOT NULL, `cookie` TEXT NOT NULL, PRIMARY KEY(`url`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"cookie\",\n            \"columnName\": \"cookie\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"url\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_cookies_url\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"url\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_cookies_url` ON `${TABLE_NAME}` (`url`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssSources\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`sourceUrl` TEXT NOT NULL, `sourceName` TEXT NOT NULL, `sourceIcon` TEXT NOT NULL, `sourceGroup` TEXT, `sourceComment` TEXT, `enabled` INTEGER NOT NULL, `concurrentRate` TEXT, `header` TEXT, `loginUrl` TEXT, `loginUi` TEXT, `loginCheckJs` TEXT, `sortUrl` TEXT, `singleUrl` INTEGER NOT NULL, `articleStyle` INTEGER NOT NULL, `ruleArticles` TEXT, `ruleNextPage` TEXT, `ruleTitle` TEXT, `rulePubDate` TEXT, `ruleDescription` TEXT, `ruleImage` TEXT, `ruleLink` TEXT, `ruleContent` TEXT, `style` TEXT, `enableJs` INTEGER NOT NULL, `loadWithBaseUrl` INTEGER NOT NULL, `customOrder` INTEGER NOT NULL, PRIMARY KEY(`sourceUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"sourceUrl\",\n            \"columnName\": \"sourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceName\",\n            \"columnName\": \"sourceName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceIcon\",\n            \"columnName\": \"sourceIcon\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceGroup\",\n            \"columnName\": \"sourceGroup\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"sourceComment\",\n            \"columnName\": \"sourceComment\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enabled\",\n            \"columnName\": \"enabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"concurrentRate\",\n            \"columnName\": \"concurrentRate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"header\",\n            \"columnName\": \"header\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUrl\",\n            \"columnName\": \"loginUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUi\",\n            \"columnName\": \"loginUi\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginCheckJs\",\n            \"columnName\": \"loginCheckJs\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"sortUrl\",\n            \"columnName\": \"sortUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"singleUrl\",\n            \"columnName\": \"singleUrl\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"articleStyle\",\n            \"columnName\": \"articleStyle\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"ruleArticles\",\n            \"columnName\": \"ruleArticles\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleNextPage\",\n            \"columnName\": \"ruleNextPage\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleTitle\",\n            \"columnName\": \"ruleTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"rulePubDate\",\n            \"columnName\": \"rulePubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleDescription\",\n            \"columnName\": \"ruleDescription\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleImage\",\n            \"columnName\": \"ruleImage\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleLink\",\n            \"columnName\": \"ruleLink\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleContent\",\n            \"columnName\": \"ruleContent\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"style\",\n            \"columnName\": \"style\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enableJs\",\n            \"columnName\": \"enableJs\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"loadWithBaseUrl\",\n            \"columnName\": \"loadWithBaseUrl\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"sourceUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_rssSources_sourceUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"sourceUrl\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_rssSources_sourceUrl` ON `${TABLE_NAME}` (`sourceUrl`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"bookmarks\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`time` INTEGER NOT NULL, `bookName` TEXT NOT NULL, `bookAuthor` TEXT NOT NULL, `chapterIndex` INTEGER NOT NULL, `chapterPos` INTEGER NOT NULL, `chapterName` TEXT NOT NULL, `bookText` TEXT NOT NULL, `content` TEXT NOT NULL, PRIMARY KEY(`time`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"time\",\n            \"columnName\": \"time\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookName\",\n            \"columnName\": \"bookName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookAuthor\",\n            \"columnName\": \"bookAuthor\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterIndex\",\n            \"columnName\": \"chapterIndex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterPos\",\n            \"columnName\": \"chapterPos\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterName\",\n            \"columnName\": \"chapterName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookText\",\n            \"columnName\": \"bookText\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"time\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_bookmarks_bookName_bookAuthor\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"bookName\",\n              \"bookAuthor\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_bookmarks_bookName_bookAuthor` ON `${TABLE_NAME}` (`bookName`, `bookAuthor`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssArticles\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`origin` TEXT NOT NULL, `sort` TEXT NOT NULL, `title` TEXT NOT NULL, `order` INTEGER NOT NULL, `link` TEXT NOT NULL, `pubDate` TEXT, `description` TEXT, `content` TEXT, `image` TEXT, `read` INTEGER NOT NULL, `variable` TEXT, PRIMARY KEY(`origin`, `link`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sort\",\n            \"columnName\": \"sort\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"link\",\n            \"columnName\": \"link\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"pubDate\",\n            \"columnName\": \"pubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"description\",\n            \"columnName\": \"description\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"image\",\n            \"columnName\": \"image\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"read\",\n            \"columnName\": \"read\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"origin\",\n            \"link\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssReadRecords\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`record` TEXT NOT NULL, `read` INTEGER NOT NULL, PRIMARY KEY(`record`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"record\",\n            \"columnName\": \"record\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"read\",\n            \"columnName\": \"read\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"record\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssStars\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`origin` TEXT NOT NULL, `sort` TEXT NOT NULL, `title` TEXT NOT NULL, `starTime` INTEGER NOT NULL, `link` TEXT NOT NULL, `pubDate` TEXT, `description` TEXT, `content` TEXT, `image` TEXT, `variable` TEXT, PRIMARY KEY(`origin`, `link`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sort\",\n            \"columnName\": \"sort\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"starTime\",\n            \"columnName\": \"starTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"link\",\n            \"columnName\": \"link\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"pubDate\",\n            \"columnName\": \"pubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"description\",\n            \"columnName\": \"description\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"image\",\n            \"columnName\": \"image\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"origin\",\n            \"link\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"txtTocRules\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `rule` TEXT NOT NULL, `serialNumber` INTEGER NOT NULL, `enable` INTEGER NOT NULL, PRIMARY KEY(`id`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"rule\",\n            \"columnName\": \"rule\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"serialNumber\",\n            \"columnName\": \"serialNumber\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enable\",\n            \"columnName\": \"enable\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"id\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"readRecord\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`deviceId` TEXT NOT NULL, `bookName` TEXT NOT NULL, `readTime` INTEGER NOT NULL, PRIMARY KEY(`deviceId`, `bookName`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"deviceId\",\n            \"columnName\": \"deviceId\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookName\",\n            \"columnName\": \"bookName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"readTime\",\n            \"columnName\": \"readTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"deviceId\",\n            \"bookName\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"httpTTS\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `url` TEXT NOT NULL, `contentType` TEXT, `concurrentRate` TEXT, `loginUrl` TEXT, `loginUi` TEXT, `header` TEXT, `loginCheckJs` TEXT, PRIMARY KEY(`id`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"contentType\",\n            \"columnName\": \"contentType\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"concurrentRate\",\n            \"columnName\": \"concurrentRate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUrl\",\n            \"columnName\": \"loginUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUi\",\n            \"columnName\": \"loginUi\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"header\",\n            \"columnName\": \"header\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginCheckJs\",\n            \"columnName\": \"loginCheckJs\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"id\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"caches\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`key` TEXT NOT NULL, `value` TEXT, `deadline` INTEGER NOT NULL, PRIMARY KEY(`key`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"key\",\n            \"columnName\": \"key\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"value\",\n            \"columnName\": \"value\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"deadline\",\n            \"columnName\": \"deadline\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"key\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_caches_key\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"key\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_caches_key` ON `${TABLE_NAME}` (`key`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"ruleSubs\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `url` TEXT NOT NULL, `type` INTEGER NOT NULL, `customOrder` INTEGER NOT NULL, `autoUpdate` INTEGER NOT NULL, `update` INTEGER NOT NULL, PRIMARY KEY(`id`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"autoUpdate\",\n            \"columnName\": \"autoUpdate\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"update\",\n            \"columnName\": \"update\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"id\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      }\n    ],\n    \"views\": [],\n    \"setupQueries\": [\n      \"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)\",\n      \"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '5bef05ac6abeaa4b82c3ff6e9e6bd7b3')\"\n    ]\n  }\n}"
  },
  {
    "path": "app/schemas/io.legado.app.data.AppDatabase/43.json",
    "content": "{\n  \"formatVersion\": 1,\n  \"database\": {\n    \"version\": 43,\n    \"identityHash\": \"b97eb5297faaacb44c2274233f52d250\",\n    \"entities\": [\n      {\n        \"tableName\": \"books\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookUrl` TEXT NOT NULL, `tocUrl` TEXT NOT NULL, `origin` TEXT NOT NULL, `originName` TEXT NOT NULL, `name` TEXT NOT NULL, `author` TEXT NOT NULL, `kind` TEXT, `customTag` TEXT, `coverUrl` TEXT, `customCoverUrl` TEXT, `intro` TEXT, `customIntro` TEXT, `charset` TEXT, `type` INTEGER NOT NULL, `group` INTEGER NOT NULL, `latestChapterTitle` TEXT, `latestChapterTime` INTEGER NOT NULL, `lastCheckTime` INTEGER NOT NULL, `lastCheckCount` INTEGER NOT NULL, `totalChapterNum` INTEGER NOT NULL, `durChapterTitle` TEXT, `durChapterIndex` INTEGER NOT NULL, `durChapterPos` INTEGER NOT NULL, `durChapterTime` INTEGER NOT NULL, `wordCount` TEXT, `canUpdate` INTEGER NOT NULL, `order` INTEGER NOT NULL, `originOrder` INTEGER NOT NULL, `variable` TEXT, `readConfig` TEXT, PRIMARY KEY(`bookUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"tocUrl\",\n            \"columnName\": \"tocUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"originName\",\n            \"columnName\": \"originName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"author\",\n            \"columnName\": \"author\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"kind\",\n            \"columnName\": \"kind\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customTag\",\n            \"columnName\": \"customTag\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"coverUrl\",\n            \"columnName\": \"coverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customCoverUrl\",\n            \"columnName\": \"customCoverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"intro\",\n            \"columnName\": \"intro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customIntro\",\n            \"columnName\": \"customIntro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"charset\",\n            \"columnName\": \"charset\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"group\",\n            \"columnName\": \"group\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"latestChapterTitle\",\n            \"columnName\": \"latestChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"latestChapterTime\",\n            \"columnName\": \"latestChapterTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"lastCheckTime\",\n            \"columnName\": \"lastCheckTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"lastCheckCount\",\n            \"columnName\": \"lastCheckCount\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"totalChapterNum\",\n            \"columnName\": \"totalChapterNum\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"durChapterTitle\",\n            \"columnName\": \"durChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"durChapterIndex\",\n            \"columnName\": \"durChapterIndex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"durChapterPos\",\n            \"columnName\": \"durChapterPos\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"durChapterTime\",\n            \"columnName\": \"durChapterTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"wordCount\",\n            \"columnName\": \"wordCount\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"canUpdate\",\n            \"columnName\": \"canUpdate\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"originOrder\",\n            \"columnName\": \"originOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"readConfig\",\n            \"columnName\": \"readConfig\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"bookUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_books_name_author\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"name\",\n              \"author\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_books_name_author` ON `${TABLE_NAME}` (`name`, `author`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"book_groups\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`groupId` INTEGER NOT NULL, `groupName` TEXT NOT NULL, `cover` TEXT, `order` INTEGER NOT NULL, `show` INTEGER NOT NULL, PRIMARY KEY(`groupId`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"groupId\",\n            \"columnName\": \"groupId\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"groupName\",\n            \"columnName\": \"groupName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"cover\",\n            \"columnName\": \"cover\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"show\",\n            \"columnName\": \"show\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"groupId\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"book_sources\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookSourceUrl` TEXT NOT NULL, `bookSourceName` TEXT NOT NULL, `bookSourceGroup` TEXT, `bookSourceType` INTEGER NOT NULL, `bookUrlPattern` TEXT, `customOrder` INTEGER NOT NULL, `enabled` INTEGER NOT NULL, `enabledExplore` INTEGER NOT NULL, `concurrentRate` TEXT, `header` TEXT, `loginUrl` TEXT, `loginUi` TEXT, `loginCheckJs` TEXT, `bookSourceComment` TEXT, `lastUpdateTime` INTEGER NOT NULL, `respondTime` INTEGER NOT NULL, `weight` INTEGER NOT NULL, `exploreUrl` TEXT, `ruleExplore` TEXT, `searchUrl` TEXT, `ruleSearch` TEXT, `ruleBookInfo` TEXT, `ruleToc` TEXT, `ruleContent` TEXT, PRIMARY KEY(`bookSourceUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookSourceUrl\",\n            \"columnName\": \"bookSourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookSourceName\",\n            \"columnName\": \"bookSourceName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookSourceGroup\",\n            \"columnName\": \"bookSourceGroup\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"bookSourceType\",\n            \"columnName\": \"bookSourceType\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookUrlPattern\",\n            \"columnName\": \"bookUrlPattern\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enabled\",\n            \"columnName\": \"enabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enabledExplore\",\n            \"columnName\": \"enabledExplore\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"concurrentRate\",\n            \"columnName\": \"concurrentRate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"header\",\n            \"columnName\": \"header\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUrl\",\n            \"columnName\": \"loginUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUi\",\n            \"columnName\": \"loginUi\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginCheckJs\",\n            \"columnName\": \"loginCheckJs\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"bookSourceComment\",\n            \"columnName\": \"bookSourceComment\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"lastUpdateTime\",\n            \"columnName\": \"lastUpdateTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"respondTime\",\n            \"columnName\": \"respondTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"weight\",\n            \"columnName\": \"weight\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"exploreUrl\",\n            \"columnName\": \"exploreUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleExplore\",\n            \"columnName\": \"ruleExplore\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"searchUrl\",\n            \"columnName\": \"searchUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleSearch\",\n            \"columnName\": \"ruleSearch\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleBookInfo\",\n            \"columnName\": \"ruleBookInfo\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleToc\",\n            \"columnName\": \"ruleToc\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleContent\",\n            \"columnName\": \"ruleContent\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"bookSourceUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_book_sources_bookSourceUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"bookSourceUrl\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_book_sources_bookSourceUrl` ON `${TABLE_NAME}` (`bookSourceUrl`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"chapters\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`url` TEXT NOT NULL, `title` TEXT NOT NULL, `isVolume` INTEGER NOT NULL, `baseUrl` TEXT NOT NULL, `bookUrl` TEXT NOT NULL, `index` INTEGER NOT NULL, `isVip` INTEGER NOT NULL, `isPay` INTEGER NOT NULL, `resourceUrl` TEXT, `tag` TEXT, `start` INTEGER, `end` INTEGER, `startFragmentId` TEXT, `endFragmentId` TEXT, `variable` TEXT, PRIMARY KEY(`url`, `bookUrl`), FOREIGN KEY(`bookUrl`) REFERENCES `books`(`bookUrl`) ON UPDATE NO ACTION ON DELETE CASCADE )\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"isVolume\",\n            \"columnName\": \"isVolume\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"baseUrl\",\n            \"columnName\": \"baseUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"index\",\n            \"columnName\": \"index\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"isVip\",\n            \"columnName\": \"isVip\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"isPay\",\n            \"columnName\": \"isPay\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"resourceUrl\",\n            \"columnName\": \"resourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"tag\",\n            \"columnName\": \"tag\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"start\",\n            \"columnName\": \"start\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"end\",\n            \"columnName\": \"end\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"startFragmentId\",\n            \"columnName\": \"startFragmentId\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"endFragmentId\",\n            \"columnName\": \"endFragmentId\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"url\",\n            \"bookUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_chapters_bookUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"bookUrl\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_chapters_bookUrl` ON `${TABLE_NAME}` (`bookUrl`)\"\n          },\n          {\n            \"name\": \"index_chapters_bookUrl_index\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"bookUrl\",\n              \"index\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_chapters_bookUrl_index` ON `${TABLE_NAME}` (`bookUrl`, `index`)\"\n          }\n        ],\n        \"foreignKeys\": [\n          {\n            \"table\": \"books\",\n            \"onDelete\": \"CASCADE\",\n            \"onUpdate\": \"NO ACTION\",\n            \"columns\": [\n              \"bookUrl\"\n            ],\n            \"referencedColumns\": [\n              \"bookUrl\"\n            ]\n          }\n        ]\n      },\n      {\n        \"tableName\": \"replace_rules\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL, `group` TEXT, `pattern` TEXT NOT NULL, `replacement` TEXT NOT NULL, `scope` TEXT, `isEnabled` INTEGER NOT NULL, `isRegex` INTEGER NOT NULL, `sortOrder` INTEGER NOT NULL)\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"group\",\n            \"columnName\": \"group\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"pattern\",\n            \"columnName\": \"pattern\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"replacement\",\n            \"columnName\": \"replacement\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"scope\",\n            \"columnName\": \"scope\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"isEnabled\",\n            \"columnName\": \"isEnabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"isRegex\",\n            \"columnName\": \"isRegex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"sortOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"id\"\n          ],\n          \"autoGenerate\": true\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_replace_rules_id\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"id\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_replace_rules_id` ON `${TABLE_NAME}` (`id`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"searchBooks\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookUrl` TEXT NOT NULL, `origin` TEXT NOT NULL, `originName` TEXT NOT NULL, `type` INTEGER NOT NULL, `name` TEXT NOT NULL, `author` TEXT NOT NULL, `kind` TEXT, `coverUrl` TEXT, `intro` TEXT, `wordCount` TEXT, `latestChapterTitle` TEXT, `tocUrl` TEXT NOT NULL, `time` INTEGER NOT NULL, `variable` TEXT, `originOrder` INTEGER NOT NULL, PRIMARY KEY(`bookUrl`), FOREIGN KEY(`origin`) REFERENCES `book_sources`(`bookSourceUrl`) ON UPDATE NO ACTION ON DELETE CASCADE )\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"originName\",\n            \"columnName\": \"originName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"author\",\n            \"columnName\": \"author\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"kind\",\n            \"columnName\": \"kind\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"coverUrl\",\n            \"columnName\": \"coverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"intro\",\n            \"columnName\": \"intro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"wordCount\",\n            \"columnName\": \"wordCount\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"latestChapterTitle\",\n            \"columnName\": \"latestChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"tocUrl\",\n            \"columnName\": \"tocUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"time\",\n            \"columnName\": \"time\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"originOrder\",\n            \"columnName\": \"originOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"bookUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_searchBooks_bookUrl\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"bookUrl\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_searchBooks_bookUrl` ON `${TABLE_NAME}` (`bookUrl`)\"\n          },\n          {\n            \"name\": \"index_searchBooks_origin\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"origin\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_searchBooks_origin` ON `${TABLE_NAME}` (`origin`)\"\n          }\n        ],\n        \"foreignKeys\": [\n          {\n            \"table\": \"book_sources\",\n            \"onDelete\": \"CASCADE\",\n            \"onUpdate\": \"NO ACTION\",\n            \"columns\": [\n              \"origin\"\n            ],\n            \"referencedColumns\": [\n              \"bookSourceUrl\"\n            ]\n          }\n        ]\n      },\n      {\n        \"tableName\": \"search_keywords\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`word` TEXT NOT NULL, `usage` INTEGER NOT NULL, `lastUseTime` INTEGER NOT NULL, PRIMARY KEY(`word`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"word\",\n            \"columnName\": \"word\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"usage\",\n            \"columnName\": \"usage\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"lastUseTime\",\n            \"columnName\": \"lastUseTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"word\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_search_keywords_word\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"word\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_search_keywords_word` ON `${TABLE_NAME}` (`word`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"cookies\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`url` TEXT NOT NULL, `cookie` TEXT NOT NULL, PRIMARY KEY(`url`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"cookie\",\n            \"columnName\": \"cookie\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"url\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_cookies_url\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"url\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_cookies_url` ON `${TABLE_NAME}` (`url`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssSources\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`sourceUrl` TEXT NOT NULL, `sourceName` TEXT NOT NULL, `sourceIcon` TEXT NOT NULL, `sourceGroup` TEXT, `sourceComment` TEXT, `enabled` INTEGER NOT NULL, `concurrentRate` TEXT, `header` TEXT, `loginUrl` TEXT, `loginUi` TEXT, `loginCheckJs` TEXT, `sortUrl` TEXT, `singleUrl` INTEGER NOT NULL, `articleStyle` INTEGER NOT NULL, `ruleArticles` TEXT, `ruleNextPage` TEXT, `ruleTitle` TEXT, `rulePubDate` TEXT, `ruleDescription` TEXT, `ruleImage` TEXT, `ruleLink` TEXT, `ruleContent` TEXT, `style` TEXT, `enableJs` INTEGER NOT NULL, `loadWithBaseUrl` INTEGER NOT NULL, `customOrder` INTEGER NOT NULL, PRIMARY KEY(`sourceUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"sourceUrl\",\n            \"columnName\": \"sourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceName\",\n            \"columnName\": \"sourceName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceIcon\",\n            \"columnName\": \"sourceIcon\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceGroup\",\n            \"columnName\": \"sourceGroup\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"sourceComment\",\n            \"columnName\": \"sourceComment\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enabled\",\n            \"columnName\": \"enabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"concurrentRate\",\n            \"columnName\": \"concurrentRate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"header\",\n            \"columnName\": \"header\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUrl\",\n            \"columnName\": \"loginUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUi\",\n            \"columnName\": \"loginUi\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginCheckJs\",\n            \"columnName\": \"loginCheckJs\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"sortUrl\",\n            \"columnName\": \"sortUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"singleUrl\",\n            \"columnName\": \"singleUrl\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"articleStyle\",\n            \"columnName\": \"articleStyle\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"ruleArticles\",\n            \"columnName\": \"ruleArticles\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleNextPage\",\n            \"columnName\": \"ruleNextPage\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleTitle\",\n            \"columnName\": \"ruleTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"rulePubDate\",\n            \"columnName\": \"rulePubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleDescription\",\n            \"columnName\": \"ruleDescription\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleImage\",\n            \"columnName\": \"ruleImage\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleLink\",\n            \"columnName\": \"ruleLink\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleContent\",\n            \"columnName\": \"ruleContent\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"style\",\n            \"columnName\": \"style\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enableJs\",\n            \"columnName\": \"enableJs\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"loadWithBaseUrl\",\n            \"columnName\": \"loadWithBaseUrl\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"sourceUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_rssSources_sourceUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"sourceUrl\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_rssSources_sourceUrl` ON `${TABLE_NAME}` (`sourceUrl`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"bookmarks\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`time` INTEGER NOT NULL, `bookName` TEXT NOT NULL, `bookAuthor` TEXT NOT NULL, `chapterIndex` INTEGER NOT NULL, `chapterPos` INTEGER NOT NULL, `chapterName` TEXT NOT NULL, `bookText` TEXT NOT NULL, `content` TEXT NOT NULL, PRIMARY KEY(`time`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"time\",\n            \"columnName\": \"time\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookName\",\n            \"columnName\": \"bookName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookAuthor\",\n            \"columnName\": \"bookAuthor\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterIndex\",\n            \"columnName\": \"chapterIndex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterPos\",\n            \"columnName\": \"chapterPos\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterName\",\n            \"columnName\": \"chapterName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookText\",\n            \"columnName\": \"bookText\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"time\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_bookmarks_bookName_bookAuthor\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"bookName\",\n              \"bookAuthor\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_bookmarks_bookName_bookAuthor` ON `${TABLE_NAME}` (`bookName`, `bookAuthor`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssArticles\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`origin` TEXT NOT NULL, `sort` TEXT NOT NULL, `title` TEXT NOT NULL, `order` INTEGER NOT NULL, `link` TEXT NOT NULL, `pubDate` TEXT, `description` TEXT, `content` TEXT, `image` TEXT, `read` INTEGER NOT NULL, `variable` TEXT, PRIMARY KEY(`origin`, `link`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sort\",\n            \"columnName\": \"sort\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"link\",\n            \"columnName\": \"link\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"pubDate\",\n            \"columnName\": \"pubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"description\",\n            \"columnName\": \"description\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"image\",\n            \"columnName\": \"image\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"read\",\n            \"columnName\": \"read\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"origin\",\n            \"link\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssReadRecords\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`record` TEXT NOT NULL, `read` INTEGER NOT NULL, PRIMARY KEY(`record`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"record\",\n            \"columnName\": \"record\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"read\",\n            \"columnName\": \"read\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"record\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssStars\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`origin` TEXT NOT NULL, `sort` TEXT NOT NULL, `title` TEXT NOT NULL, `starTime` INTEGER NOT NULL, `link` TEXT NOT NULL, `pubDate` TEXT, `description` TEXT, `content` TEXT, `image` TEXT, `variable` TEXT, PRIMARY KEY(`origin`, `link`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sort\",\n            \"columnName\": \"sort\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"starTime\",\n            \"columnName\": \"starTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"link\",\n            \"columnName\": \"link\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"pubDate\",\n            \"columnName\": \"pubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"description\",\n            \"columnName\": \"description\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"image\",\n            \"columnName\": \"image\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"origin\",\n            \"link\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"txtTocRules\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `rule` TEXT NOT NULL, `serialNumber` INTEGER NOT NULL, `enable` INTEGER NOT NULL, PRIMARY KEY(`id`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"rule\",\n            \"columnName\": \"rule\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"serialNumber\",\n            \"columnName\": \"serialNumber\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enable\",\n            \"columnName\": \"enable\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"id\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"readRecord\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`deviceId` TEXT NOT NULL, `bookName` TEXT NOT NULL, `readTime` INTEGER NOT NULL, PRIMARY KEY(`deviceId`, `bookName`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"deviceId\",\n            \"columnName\": \"deviceId\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookName\",\n            \"columnName\": \"bookName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"readTime\",\n            \"columnName\": \"readTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"deviceId\",\n            \"bookName\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"httpTTS\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `url` TEXT NOT NULL, `contentType` TEXT, `concurrentRate` TEXT, `loginUrl` TEXT, `loginUi` TEXT, `header` TEXT, `loginCheckJs` TEXT, PRIMARY KEY(`id`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"contentType\",\n            \"columnName\": \"contentType\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"concurrentRate\",\n            \"columnName\": \"concurrentRate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUrl\",\n            \"columnName\": \"loginUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUi\",\n            \"columnName\": \"loginUi\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"header\",\n            \"columnName\": \"header\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginCheckJs\",\n            \"columnName\": \"loginCheckJs\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"id\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"caches\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`key` TEXT NOT NULL, `value` TEXT, `deadline` INTEGER NOT NULL, PRIMARY KEY(`key`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"key\",\n            \"columnName\": \"key\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"value\",\n            \"columnName\": \"value\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"deadline\",\n            \"columnName\": \"deadline\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"key\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_caches_key\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"key\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_caches_key` ON `${TABLE_NAME}` (`key`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"ruleSubs\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `url` TEXT NOT NULL, `type` INTEGER NOT NULL, `customOrder` INTEGER NOT NULL, `autoUpdate` INTEGER NOT NULL, `update` INTEGER NOT NULL, PRIMARY KEY(`id`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"autoUpdate\",\n            \"columnName\": \"autoUpdate\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"update\",\n            \"columnName\": \"update\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"id\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      }\n    ],\n    \"views\": [],\n    \"setupQueries\": [\n      \"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)\",\n      \"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'b97eb5297faaacb44c2274233f52d250')\"\n    ]\n  }\n}"
  },
  {
    "path": "app/schemas/io.legado.app.data.AppDatabase/44.json",
    "content": "{\n  \"formatVersion\": 1,\n  \"database\": {\n    \"version\": 44,\n    \"identityHash\": \"fdfaa67979c13dd76db0a1b594bc5904\",\n    \"entities\": [\n      {\n        \"tableName\": \"books\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookUrl` TEXT NOT NULL, `tocUrl` TEXT NOT NULL, `origin` TEXT NOT NULL, `originName` TEXT NOT NULL, `name` TEXT NOT NULL, `author` TEXT NOT NULL, `kind` TEXT, `customTag` TEXT, `coverUrl` TEXT, `customCoverUrl` TEXT, `intro` TEXT, `customIntro` TEXT, `charset` TEXT, `type` INTEGER NOT NULL, `group` INTEGER NOT NULL, `latestChapterTitle` TEXT, `latestChapterTime` INTEGER NOT NULL, `lastCheckTime` INTEGER NOT NULL, `lastCheckCount` INTEGER NOT NULL, `totalChapterNum` INTEGER NOT NULL, `durChapterTitle` TEXT, `durChapterIndex` INTEGER NOT NULL, `durChapterPos` INTEGER NOT NULL, `durChapterTime` INTEGER NOT NULL, `wordCount` TEXT, `canUpdate` INTEGER NOT NULL, `order` INTEGER NOT NULL, `originOrder` INTEGER NOT NULL, `variable` TEXT, `readConfig` TEXT, PRIMARY KEY(`bookUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"tocUrl\",\n            \"columnName\": \"tocUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"originName\",\n            \"columnName\": \"originName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"author\",\n            \"columnName\": \"author\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"kind\",\n            \"columnName\": \"kind\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customTag\",\n            \"columnName\": \"customTag\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"coverUrl\",\n            \"columnName\": \"coverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customCoverUrl\",\n            \"columnName\": \"customCoverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"intro\",\n            \"columnName\": \"intro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customIntro\",\n            \"columnName\": \"customIntro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"charset\",\n            \"columnName\": \"charset\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"group\",\n            \"columnName\": \"group\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"latestChapterTitle\",\n            \"columnName\": \"latestChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"latestChapterTime\",\n            \"columnName\": \"latestChapterTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"lastCheckTime\",\n            \"columnName\": \"lastCheckTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"lastCheckCount\",\n            \"columnName\": \"lastCheckCount\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"totalChapterNum\",\n            \"columnName\": \"totalChapterNum\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"durChapterTitle\",\n            \"columnName\": \"durChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"durChapterIndex\",\n            \"columnName\": \"durChapterIndex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"durChapterPos\",\n            \"columnName\": \"durChapterPos\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"durChapterTime\",\n            \"columnName\": \"durChapterTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"wordCount\",\n            \"columnName\": \"wordCount\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"canUpdate\",\n            \"columnName\": \"canUpdate\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"originOrder\",\n            \"columnName\": \"originOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"readConfig\",\n            \"columnName\": \"readConfig\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"bookUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_books_name_author\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"name\",\n              \"author\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_books_name_author` ON `${TABLE_NAME}` (`name`, `author`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"book_groups\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`groupId` INTEGER NOT NULL, `groupName` TEXT NOT NULL, `cover` TEXT, `order` INTEGER NOT NULL, `show` INTEGER NOT NULL, PRIMARY KEY(`groupId`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"groupId\",\n            \"columnName\": \"groupId\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"groupName\",\n            \"columnName\": \"groupName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"cover\",\n            \"columnName\": \"cover\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"show\",\n            \"columnName\": \"show\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"groupId\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"book_sources\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookSourceUrl` TEXT NOT NULL, `bookSourceName` TEXT NOT NULL, `bookSourceGroup` TEXT, `bookSourceType` INTEGER NOT NULL, `bookUrlPattern` TEXT, `customOrder` INTEGER NOT NULL, `enabled` INTEGER NOT NULL, `enabledExplore` INTEGER NOT NULL, `concurrentRate` TEXT, `header` TEXT, `loginUrl` TEXT, `loginUi` TEXT, `loginCheckJs` TEXT, `bookSourceComment` TEXT, `lastUpdateTime` INTEGER NOT NULL, `respondTime` INTEGER NOT NULL, `weight` INTEGER NOT NULL, `exploreUrl` TEXT, `ruleExplore` TEXT, `searchUrl` TEXT, `ruleSearch` TEXT, `ruleBookInfo` TEXT, `ruleToc` TEXT, `ruleContent` TEXT, PRIMARY KEY(`bookSourceUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookSourceUrl\",\n            \"columnName\": \"bookSourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookSourceName\",\n            \"columnName\": \"bookSourceName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookSourceGroup\",\n            \"columnName\": \"bookSourceGroup\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"bookSourceType\",\n            \"columnName\": \"bookSourceType\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookUrlPattern\",\n            \"columnName\": \"bookUrlPattern\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enabled\",\n            \"columnName\": \"enabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enabledExplore\",\n            \"columnName\": \"enabledExplore\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"concurrentRate\",\n            \"columnName\": \"concurrentRate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"header\",\n            \"columnName\": \"header\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUrl\",\n            \"columnName\": \"loginUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUi\",\n            \"columnName\": \"loginUi\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginCheckJs\",\n            \"columnName\": \"loginCheckJs\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"bookSourceComment\",\n            \"columnName\": \"bookSourceComment\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"lastUpdateTime\",\n            \"columnName\": \"lastUpdateTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"respondTime\",\n            \"columnName\": \"respondTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"weight\",\n            \"columnName\": \"weight\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"exploreUrl\",\n            \"columnName\": \"exploreUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleExplore\",\n            \"columnName\": \"ruleExplore\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"searchUrl\",\n            \"columnName\": \"searchUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleSearch\",\n            \"columnName\": \"ruleSearch\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleBookInfo\",\n            \"columnName\": \"ruleBookInfo\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleToc\",\n            \"columnName\": \"ruleToc\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleContent\",\n            \"columnName\": \"ruleContent\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"bookSourceUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_book_sources_bookSourceUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"bookSourceUrl\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_book_sources_bookSourceUrl` ON `${TABLE_NAME}` (`bookSourceUrl`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"chapters\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`url` TEXT NOT NULL, `title` TEXT NOT NULL, `isVolume` INTEGER NOT NULL, `baseUrl` TEXT NOT NULL, `bookUrl` TEXT NOT NULL, `index` INTEGER NOT NULL, `isVip` INTEGER NOT NULL, `isPay` INTEGER NOT NULL, `resourceUrl` TEXT, `tag` TEXT, `start` INTEGER, `end` INTEGER, `startFragmentId` TEXT, `endFragmentId` TEXT, `variable` TEXT, PRIMARY KEY(`url`, `bookUrl`), FOREIGN KEY(`bookUrl`) REFERENCES `books`(`bookUrl`) ON UPDATE NO ACTION ON DELETE CASCADE )\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"isVolume\",\n            \"columnName\": \"isVolume\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"baseUrl\",\n            \"columnName\": \"baseUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"index\",\n            \"columnName\": \"index\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"isVip\",\n            \"columnName\": \"isVip\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"isPay\",\n            \"columnName\": \"isPay\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"resourceUrl\",\n            \"columnName\": \"resourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"tag\",\n            \"columnName\": \"tag\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"start\",\n            \"columnName\": \"start\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"end\",\n            \"columnName\": \"end\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"startFragmentId\",\n            \"columnName\": \"startFragmentId\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"endFragmentId\",\n            \"columnName\": \"endFragmentId\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"url\",\n            \"bookUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_chapters_bookUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"bookUrl\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_chapters_bookUrl` ON `${TABLE_NAME}` (`bookUrl`)\"\n          },\n          {\n            \"name\": \"index_chapters_bookUrl_index\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"bookUrl\",\n              \"index\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_chapters_bookUrl_index` ON `${TABLE_NAME}` (`bookUrl`, `index`)\"\n          }\n        ],\n        \"foreignKeys\": [\n          {\n            \"table\": \"books\",\n            \"onDelete\": \"CASCADE\",\n            \"onUpdate\": \"NO ACTION\",\n            \"columns\": [\n              \"bookUrl\"\n            ],\n            \"referencedColumns\": [\n              \"bookUrl\"\n            ]\n          }\n        ]\n      },\n      {\n        \"tableName\": \"replace_rules\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL DEFAULT '', `group` TEXT, `pattern` TEXT NOT NULL DEFAULT '', `replacement` TEXT NOT NULL DEFAULT '', `scope` TEXT, `scopeTitle` INTEGER NOT NULL DEFAULT 0, `scopeContent` INTEGER NOT NULL DEFAULT 1, `isEnabled` INTEGER NOT NULL DEFAULT 1, `isRegex` INTEGER NOT NULL DEFAULT 1, `sortOrder` INTEGER NOT NULL DEFAULT 0)\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"group\",\n            \"columnName\": \"group\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"pattern\",\n            \"columnName\": \"pattern\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"replacement\",\n            \"columnName\": \"replacement\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"scope\",\n            \"columnName\": \"scope\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"scopeTitle\",\n            \"columnName\": \"scopeTitle\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"scopeContent\",\n            \"columnName\": \"scopeContent\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"isEnabled\",\n            \"columnName\": \"isEnabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"isRegex\",\n            \"columnName\": \"isRegex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"sortOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"id\"\n          ],\n          \"autoGenerate\": true\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_replace_rules_id\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"id\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_replace_rules_id` ON `${TABLE_NAME}` (`id`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"searchBooks\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookUrl` TEXT NOT NULL, `origin` TEXT NOT NULL, `originName` TEXT NOT NULL, `type` INTEGER NOT NULL, `name` TEXT NOT NULL, `author` TEXT NOT NULL, `kind` TEXT, `coverUrl` TEXT, `intro` TEXT, `wordCount` TEXT, `latestChapterTitle` TEXT, `tocUrl` TEXT NOT NULL, `time` INTEGER NOT NULL, `variable` TEXT, `originOrder` INTEGER NOT NULL, PRIMARY KEY(`bookUrl`), FOREIGN KEY(`origin`) REFERENCES `book_sources`(`bookSourceUrl`) ON UPDATE NO ACTION ON DELETE CASCADE )\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"originName\",\n            \"columnName\": \"originName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"author\",\n            \"columnName\": \"author\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"kind\",\n            \"columnName\": \"kind\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"coverUrl\",\n            \"columnName\": \"coverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"intro\",\n            \"columnName\": \"intro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"wordCount\",\n            \"columnName\": \"wordCount\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"latestChapterTitle\",\n            \"columnName\": \"latestChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"tocUrl\",\n            \"columnName\": \"tocUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"time\",\n            \"columnName\": \"time\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"originOrder\",\n            \"columnName\": \"originOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"bookUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_searchBooks_bookUrl\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"bookUrl\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_searchBooks_bookUrl` ON `${TABLE_NAME}` (`bookUrl`)\"\n          },\n          {\n            \"name\": \"index_searchBooks_origin\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"origin\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_searchBooks_origin` ON `${TABLE_NAME}` (`origin`)\"\n          }\n        ],\n        \"foreignKeys\": [\n          {\n            \"table\": \"book_sources\",\n            \"onDelete\": \"CASCADE\",\n            \"onUpdate\": \"NO ACTION\",\n            \"columns\": [\n              \"origin\"\n            ],\n            \"referencedColumns\": [\n              \"bookSourceUrl\"\n            ]\n          }\n        ]\n      },\n      {\n        \"tableName\": \"search_keywords\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`word` TEXT NOT NULL, `usage` INTEGER NOT NULL, `lastUseTime` INTEGER NOT NULL, PRIMARY KEY(`word`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"word\",\n            \"columnName\": \"word\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"usage\",\n            \"columnName\": \"usage\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"lastUseTime\",\n            \"columnName\": \"lastUseTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"word\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_search_keywords_word\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"word\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_search_keywords_word` ON `${TABLE_NAME}` (`word`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"cookies\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`url` TEXT NOT NULL, `cookie` TEXT NOT NULL, PRIMARY KEY(`url`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"cookie\",\n            \"columnName\": \"cookie\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"url\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_cookies_url\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"url\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_cookies_url` ON `${TABLE_NAME}` (`url`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssSources\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`sourceUrl` TEXT NOT NULL, `sourceName` TEXT NOT NULL, `sourceIcon` TEXT NOT NULL, `sourceGroup` TEXT, `sourceComment` TEXT, `enabled` INTEGER NOT NULL, `concurrentRate` TEXT, `header` TEXT, `loginUrl` TEXT, `loginUi` TEXT, `loginCheckJs` TEXT, `sortUrl` TEXT, `singleUrl` INTEGER NOT NULL, `articleStyle` INTEGER NOT NULL, `ruleArticles` TEXT, `ruleNextPage` TEXT, `ruleTitle` TEXT, `rulePubDate` TEXT, `ruleDescription` TEXT, `ruleImage` TEXT, `ruleLink` TEXT, `ruleContent` TEXT, `style` TEXT, `enableJs` INTEGER NOT NULL, `loadWithBaseUrl` INTEGER NOT NULL, `customOrder` INTEGER NOT NULL, PRIMARY KEY(`sourceUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"sourceUrl\",\n            \"columnName\": \"sourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceName\",\n            \"columnName\": \"sourceName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceIcon\",\n            \"columnName\": \"sourceIcon\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceGroup\",\n            \"columnName\": \"sourceGroup\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"sourceComment\",\n            \"columnName\": \"sourceComment\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enabled\",\n            \"columnName\": \"enabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"concurrentRate\",\n            \"columnName\": \"concurrentRate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"header\",\n            \"columnName\": \"header\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUrl\",\n            \"columnName\": \"loginUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUi\",\n            \"columnName\": \"loginUi\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginCheckJs\",\n            \"columnName\": \"loginCheckJs\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"sortUrl\",\n            \"columnName\": \"sortUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"singleUrl\",\n            \"columnName\": \"singleUrl\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"articleStyle\",\n            \"columnName\": \"articleStyle\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"ruleArticles\",\n            \"columnName\": \"ruleArticles\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleNextPage\",\n            \"columnName\": \"ruleNextPage\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleTitle\",\n            \"columnName\": \"ruleTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"rulePubDate\",\n            \"columnName\": \"rulePubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleDescription\",\n            \"columnName\": \"ruleDescription\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleImage\",\n            \"columnName\": \"ruleImage\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleLink\",\n            \"columnName\": \"ruleLink\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleContent\",\n            \"columnName\": \"ruleContent\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"style\",\n            \"columnName\": \"style\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enableJs\",\n            \"columnName\": \"enableJs\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"loadWithBaseUrl\",\n            \"columnName\": \"loadWithBaseUrl\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"sourceUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_rssSources_sourceUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"sourceUrl\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_rssSources_sourceUrl` ON `${TABLE_NAME}` (`sourceUrl`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"bookmarks\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`time` INTEGER NOT NULL, `bookName` TEXT NOT NULL, `bookAuthor` TEXT NOT NULL, `chapterIndex` INTEGER NOT NULL, `chapterPos` INTEGER NOT NULL, `chapterName` TEXT NOT NULL, `bookText` TEXT NOT NULL, `content` TEXT NOT NULL, PRIMARY KEY(`time`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"time\",\n            \"columnName\": \"time\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookName\",\n            \"columnName\": \"bookName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookAuthor\",\n            \"columnName\": \"bookAuthor\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterIndex\",\n            \"columnName\": \"chapterIndex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterPos\",\n            \"columnName\": \"chapterPos\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterName\",\n            \"columnName\": \"chapterName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookText\",\n            \"columnName\": \"bookText\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"time\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_bookmarks_bookName_bookAuthor\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"bookName\",\n              \"bookAuthor\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_bookmarks_bookName_bookAuthor` ON `${TABLE_NAME}` (`bookName`, `bookAuthor`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssArticles\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`origin` TEXT NOT NULL, `sort` TEXT NOT NULL, `title` TEXT NOT NULL, `order` INTEGER NOT NULL, `link` TEXT NOT NULL, `pubDate` TEXT, `description` TEXT, `content` TEXT, `image` TEXT, `read` INTEGER NOT NULL, `variable` TEXT, PRIMARY KEY(`origin`, `link`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sort\",\n            \"columnName\": \"sort\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"link\",\n            \"columnName\": \"link\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"pubDate\",\n            \"columnName\": \"pubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"description\",\n            \"columnName\": \"description\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"image\",\n            \"columnName\": \"image\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"read\",\n            \"columnName\": \"read\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"origin\",\n            \"link\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssReadRecords\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`record` TEXT NOT NULL, `read` INTEGER NOT NULL, PRIMARY KEY(`record`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"record\",\n            \"columnName\": \"record\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"read\",\n            \"columnName\": \"read\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"record\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssStars\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`origin` TEXT NOT NULL, `sort` TEXT NOT NULL, `title` TEXT NOT NULL, `starTime` INTEGER NOT NULL, `link` TEXT NOT NULL, `pubDate` TEXT, `description` TEXT, `content` TEXT, `image` TEXT, `variable` TEXT, PRIMARY KEY(`origin`, `link`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sort\",\n            \"columnName\": \"sort\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"starTime\",\n            \"columnName\": \"starTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"link\",\n            \"columnName\": \"link\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"pubDate\",\n            \"columnName\": \"pubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"description\",\n            \"columnName\": \"description\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"image\",\n            \"columnName\": \"image\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"origin\",\n            \"link\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"txtTocRules\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `rule` TEXT NOT NULL, `serialNumber` INTEGER NOT NULL, `enable` INTEGER NOT NULL, PRIMARY KEY(`id`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"rule\",\n            \"columnName\": \"rule\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"serialNumber\",\n            \"columnName\": \"serialNumber\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enable\",\n            \"columnName\": \"enable\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"id\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"readRecord\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`deviceId` TEXT NOT NULL, `bookName` TEXT NOT NULL, `readTime` INTEGER NOT NULL, PRIMARY KEY(`deviceId`, `bookName`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"deviceId\",\n            \"columnName\": \"deviceId\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookName\",\n            \"columnName\": \"bookName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"readTime\",\n            \"columnName\": \"readTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"deviceId\",\n            \"bookName\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"httpTTS\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `url` TEXT NOT NULL, `contentType` TEXT, `concurrentRate` TEXT, `loginUrl` TEXT, `loginUi` TEXT, `header` TEXT, `loginCheckJs` TEXT, PRIMARY KEY(`id`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"contentType\",\n            \"columnName\": \"contentType\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"concurrentRate\",\n            \"columnName\": \"concurrentRate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUrl\",\n            \"columnName\": \"loginUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUi\",\n            \"columnName\": \"loginUi\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"header\",\n            \"columnName\": \"header\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginCheckJs\",\n            \"columnName\": \"loginCheckJs\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"id\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"caches\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`key` TEXT NOT NULL, `value` TEXT, `deadline` INTEGER NOT NULL, PRIMARY KEY(`key`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"key\",\n            \"columnName\": \"key\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"value\",\n            \"columnName\": \"value\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"deadline\",\n            \"columnName\": \"deadline\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"key\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_caches_key\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"key\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_caches_key` ON `${TABLE_NAME}` (`key`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"ruleSubs\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `url` TEXT NOT NULL, `type` INTEGER NOT NULL, `customOrder` INTEGER NOT NULL, `autoUpdate` INTEGER NOT NULL, `update` INTEGER NOT NULL, PRIMARY KEY(`id`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"autoUpdate\",\n            \"columnName\": \"autoUpdate\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"update\",\n            \"columnName\": \"update\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"id\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      }\n    ],\n    \"views\": [],\n    \"setupQueries\": [\n      \"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)\",\n      \"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'fdfaa67979c13dd76db0a1b594bc5904')\"\n    ]\n  }\n}"
  },
  {
    "path": "app/schemas/io.legado.app.data.AppDatabase/45.json",
    "content": "{\n  \"formatVersion\": 1,\n  \"database\": {\n    \"version\": 45,\n    \"identityHash\": \"63272539e04e405abfd79e27ea55db75\",\n    \"entities\": [\n      {\n        \"tableName\": \"books\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookUrl` TEXT NOT NULL DEFAULT '', `tocUrl` TEXT NOT NULL DEFAULT '', `origin` TEXT NOT NULL DEFAULT '', `originName` TEXT NOT NULL DEFAULT '', `name` TEXT NOT NULL DEFAULT '', `author` TEXT NOT NULL DEFAULT '', `kind` TEXT, `customTag` TEXT, `coverUrl` TEXT, `customCoverUrl` TEXT, `intro` TEXT, `customIntro` TEXT, `charset` TEXT, `type` INTEGER NOT NULL DEFAULT 0, `group` INTEGER NOT NULL DEFAULT 0, `latestChapterTitle` TEXT, `latestChapterTime` INTEGER NOT NULL DEFAULT 0, `lastCheckTime` INTEGER NOT NULL DEFAULT 0, `lastCheckCount` INTEGER NOT NULL DEFAULT 0, `totalChapterNum` INTEGER NOT NULL DEFAULT 0, `durChapterTitle` TEXT, `durChapterIndex` INTEGER NOT NULL DEFAULT 0, `durChapterPos` INTEGER NOT NULL DEFAULT 0, `durChapterTime` INTEGER NOT NULL DEFAULT 0, `wordCount` TEXT, `canUpdate` INTEGER NOT NULL DEFAULT 1, `order` INTEGER NOT NULL DEFAULT 0, `originOrder` INTEGER NOT NULL DEFAULT 0, `variable` TEXT, `readConfig` TEXT, PRIMARY KEY(`bookUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"tocUrl\",\n            \"columnName\": \"tocUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"originName\",\n            \"columnName\": \"originName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"author\",\n            \"columnName\": \"author\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"kind\",\n            \"columnName\": \"kind\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customTag\",\n            \"columnName\": \"customTag\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"coverUrl\",\n            \"columnName\": \"coverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customCoverUrl\",\n            \"columnName\": \"customCoverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"intro\",\n            \"columnName\": \"intro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customIntro\",\n            \"columnName\": \"customIntro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"charset\",\n            \"columnName\": \"charset\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"group\",\n            \"columnName\": \"group\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"latestChapterTitle\",\n            \"columnName\": \"latestChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"latestChapterTime\",\n            \"columnName\": \"latestChapterTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"lastCheckTime\",\n            \"columnName\": \"lastCheckTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"lastCheckCount\",\n            \"columnName\": \"lastCheckCount\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"totalChapterNum\",\n            \"columnName\": \"totalChapterNum\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"durChapterTitle\",\n            \"columnName\": \"durChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"durChapterIndex\",\n            \"columnName\": \"durChapterIndex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"durChapterPos\",\n            \"columnName\": \"durChapterPos\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"durChapterTime\",\n            \"columnName\": \"durChapterTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"wordCount\",\n            \"columnName\": \"wordCount\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"canUpdate\",\n            \"columnName\": \"canUpdate\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"originOrder\",\n            \"columnName\": \"originOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"readConfig\",\n            \"columnName\": \"readConfig\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"bookUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_books_name_author\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"name\",\n              \"author\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_books_name_author` ON `${TABLE_NAME}` (`name`, `author`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"book_groups\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`groupId` INTEGER NOT NULL, `groupName` TEXT NOT NULL, `cover` TEXT, `order` INTEGER NOT NULL, `show` INTEGER NOT NULL, PRIMARY KEY(`groupId`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"groupId\",\n            \"columnName\": \"groupId\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"groupName\",\n            \"columnName\": \"groupName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"cover\",\n            \"columnName\": \"cover\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"show\",\n            \"columnName\": \"show\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"groupId\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"book_sources\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookSourceUrl` TEXT NOT NULL, `bookSourceName` TEXT NOT NULL, `bookSourceGroup` TEXT, `bookSourceType` INTEGER NOT NULL, `bookUrlPattern` TEXT, `customOrder` INTEGER NOT NULL, `enabled` INTEGER NOT NULL, `enabledExplore` INTEGER NOT NULL, `concurrentRate` TEXT, `header` TEXT, `loginUrl` TEXT, `loginUi` TEXT, `loginCheckJs` TEXT, `bookSourceComment` TEXT, `lastUpdateTime` INTEGER NOT NULL, `respondTime` INTEGER NOT NULL, `weight` INTEGER NOT NULL, `exploreUrl` TEXT, `ruleExplore` TEXT, `searchUrl` TEXT, `ruleSearch` TEXT, `ruleBookInfo` TEXT, `ruleToc` TEXT, `ruleContent` TEXT, PRIMARY KEY(`bookSourceUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookSourceUrl\",\n            \"columnName\": \"bookSourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookSourceName\",\n            \"columnName\": \"bookSourceName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookSourceGroup\",\n            \"columnName\": \"bookSourceGroup\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"bookSourceType\",\n            \"columnName\": \"bookSourceType\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookUrlPattern\",\n            \"columnName\": \"bookUrlPattern\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enabled\",\n            \"columnName\": \"enabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enabledExplore\",\n            \"columnName\": \"enabledExplore\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"concurrentRate\",\n            \"columnName\": \"concurrentRate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"header\",\n            \"columnName\": \"header\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUrl\",\n            \"columnName\": \"loginUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUi\",\n            \"columnName\": \"loginUi\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginCheckJs\",\n            \"columnName\": \"loginCheckJs\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"bookSourceComment\",\n            \"columnName\": \"bookSourceComment\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"lastUpdateTime\",\n            \"columnName\": \"lastUpdateTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"respondTime\",\n            \"columnName\": \"respondTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"weight\",\n            \"columnName\": \"weight\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"exploreUrl\",\n            \"columnName\": \"exploreUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleExplore\",\n            \"columnName\": \"ruleExplore\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"searchUrl\",\n            \"columnName\": \"searchUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleSearch\",\n            \"columnName\": \"ruleSearch\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleBookInfo\",\n            \"columnName\": \"ruleBookInfo\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleToc\",\n            \"columnName\": \"ruleToc\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleContent\",\n            \"columnName\": \"ruleContent\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"bookSourceUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_book_sources_bookSourceUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"bookSourceUrl\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_book_sources_bookSourceUrl` ON `${TABLE_NAME}` (`bookSourceUrl`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"chapters\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`url` TEXT NOT NULL, `title` TEXT NOT NULL, `isVolume` INTEGER NOT NULL, `baseUrl` TEXT NOT NULL, `bookUrl` TEXT NOT NULL, `index` INTEGER NOT NULL, `isVip` INTEGER NOT NULL, `isPay` INTEGER NOT NULL, `resourceUrl` TEXT, `tag` TEXT, `start` INTEGER, `end` INTEGER, `startFragmentId` TEXT, `endFragmentId` TEXT, `variable` TEXT, PRIMARY KEY(`url`, `bookUrl`), FOREIGN KEY(`bookUrl`) REFERENCES `books`(`bookUrl`) ON UPDATE NO ACTION ON DELETE CASCADE )\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"isVolume\",\n            \"columnName\": \"isVolume\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"baseUrl\",\n            \"columnName\": \"baseUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"index\",\n            \"columnName\": \"index\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"isVip\",\n            \"columnName\": \"isVip\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"isPay\",\n            \"columnName\": \"isPay\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"resourceUrl\",\n            \"columnName\": \"resourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"tag\",\n            \"columnName\": \"tag\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"start\",\n            \"columnName\": \"start\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"end\",\n            \"columnName\": \"end\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"startFragmentId\",\n            \"columnName\": \"startFragmentId\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"endFragmentId\",\n            \"columnName\": \"endFragmentId\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"url\",\n            \"bookUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_chapters_bookUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"bookUrl\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_chapters_bookUrl` ON `${TABLE_NAME}` (`bookUrl`)\"\n          },\n          {\n            \"name\": \"index_chapters_bookUrl_index\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"bookUrl\",\n              \"index\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_chapters_bookUrl_index` ON `${TABLE_NAME}` (`bookUrl`, `index`)\"\n          }\n        ],\n        \"foreignKeys\": [\n          {\n            \"table\": \"books\",\n            \"onDelete\": \"CASCADE\",\n            \"onUpdate\": \"NO ACTION\",\n            \"columns\": [\n              \"bookUrl\"\n            ],\n            \"referencedColumns\": [\n              \"bookUrl\"\n            ]\n          }\n        ]\n      },\n      {\n        \"tableName\": \"replace_rules\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL DEFAULT '', `group` TEXT, `pattern` TEXT NOT NULL DEFAULT '', `replacement` TEXT NOT NULL DEFAULT '', `scope` TEXT, `scopeTitle` INTEGER NOT NULL DEFAULT 0, `scopeContent` INTEGER NOT NULL DEFAULT 1, `isEnabled` INTEGER NOT NULL DEFAULT 1, `isRegex` INTEGER NOT NULL DEFAULT 1, `sortOrder` INTEGER NOT NULL DEFAULT 0)\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"group\",\n            \"columnName\": \"group\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"pattern\",\n            \"columnName\": \"pattern\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"replacement\",\n            \"columnName\": \"replacement\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"scope\",\n            \"columnName\": \"scope\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"scopeTitle\",\n            \"columnName\": \"scopeTitle\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"scopeContent\",\n            \"columnName\": \"scopeContent\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"isEnabled\",\n            \"columnName\": \"isEnabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"isRegex\",\n            \"columnName\": \"isRegex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"sortOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"id\"\n          ],\n          \"autoGenerate\": true\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_replace_rules_id\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"id\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_replace_rules_id` ON `${TABLE_NAME}` (`id`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"searchBooks\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookUrl` TEXT NOT NULL, `origin` TEXT NOT NULL, `originName` TEXT NOT NULL, `type` INTEGER NOT NULL, `name` TEXT NOT NULL, `author` TEXT NOT NULL, `kind` TEXT, `coverUrl` TEXT, `intro` TEXT, `wordCount` TEXT, `latestChapterTitle` TEXT, `tocUrl` TEXT NOT NULL, `time` INTEGER NOT NULL, `variable` TEXT, `originOrder` INTEGER NOT NULL, PRIMARY KEY(`bookUrl`), FOREIGN KEY(`origin`) REFERENCES `book_sources`(`bookSourceUrl`) ON UPDATE NO ACTION ON DELETE CASCADE )\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"originName\",\n            \"columnName\": \"originName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"author\",\n            \"columnName\": \"author\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"kind\",\n            \"columnName\": \"kind\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"coverUrl\",\n            \"columnName\": \"coverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"intro\",\n            \"columnName\": \"intro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"wordCount\",\n            \"columnName\": \"wordCount\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"latestChapterTitle\",\n            \"columnName\": \"latestChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"tocUrl\",\n            \"columnName\": \"tocUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"time\",\n            \"columnName\": \"time\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"originOrder\",\n            \"columnName\": \"originOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"bookUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_searchBooks_bookUrl\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"bookUrl\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_searchBooks_bookUrl` ON `${TABLE_NAME}` (`bookUrl`)\"\n          },\n          {\n            \"name\": \"index_searchBooks_origin\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"origin\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_searchBooks_origin` ON `${TABLE_NAME}` (`origin`)\"\n          }\n        ],\n        \"foreignKeys\": [\n          {\n            \"table\": \"book_sources\",\n            \"onDelete\": \"CASCADE\",\n            \"onUpdate\": \"NO ACTION\",\n            \"columns\": [\n              \"origin\"\n            ],\n            \"referencedColumns\": [\n              \"bookSourceUrl\"\n            ]\n          }\n        ]\n      },\n      {\n        \"tableName\": \"search_keywords\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`word` TEXT NOT NULL, `usage` INTEGER NOT NULL, `lastUseTime` INTEGER NOT NULL, PRIMARY KEY(`word`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"word\",\n            \"columnName\": \"word\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"usage\",\n            \"columnName\": \"usage\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"lastUseTime\",\n            \"columnName\": \"lastUseTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"word\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_search_keywords_word\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"word\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_search_keywords_word` ON `${TABLE_NAME}` (`word`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"cookies\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`url` TEXT NOT NULL, `cookie` TEXT NOT NULL, PRIMARY KEY(`url`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"cookie\",\n            \"columnName\": \"cookie\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"url\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_cookies_url\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"url\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_cookies_url` ON `${TABLE_NAME}` (`url`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssSources\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`sourceUrl` TEXT NOT NULL, `sourceName` TEXT NOT NULL, `sourceIcon` TEXT NOT NULL, `sourceGroup` TEXT, `sourceComment` TEXT, `enabled` INTEGER NOT NULL, `concurrentRate` TEXT, `header` TEXT, `loginUrl` TEXT, `loginUi` TEXT, `loginCheckJs` TEXT, `sortUrl` TEXT, `singleUrl` INTEGER NOT NULL, `articleStyle` INTEGER NOT NULL, `ruleArticles` TEXT, `ruleNextPage` TEXT, `ruleTitle` TEXT, `rulePubDate` TEXT, `ruleDescription` TEXT, `ruleImage` TEXT, `ruleLink` TEXT, `ruleContent` TEXT, `style` TEXT, `enableJs` INTEGER NOT NULL, `loadWithBaseUrl` INTEGER NOT NULL, `customOrder` INTEGER NOT NULL, PRIMARY KEY(`sourceUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"sourceUrl\",\n            \"columnName\": \"sourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceName\",\n            \"columnName\": \"sourceName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceIcon\",\n            \"columnName\": \"sourceIcon\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceGroup\",\n            \"columnName\": \"sourceGroup\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"sourceComment\",\n            \"columnName\": \"sourceComment\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enabled\",\n            \"columnName\": \"enabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"concurrentRate\",\n            \"columnName\": \"concurrentRate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"header\",\n            \"columnName\": \"header\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUrl\",\n            \"columnName\": \"loginUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUi\",\n            \"columnName\": \"loginUi\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginCheckJs\",\n            \"columnName\": \"loginCheckJs\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"sortUrl\",\n            \"columnName\": \"sortUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"singleUrl\",\n            \"columnName\": \"singleUrl\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"articleStyle\",\n            \"columnName\": \"articleStyle\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"ruleArticles\",\n            \"columnName\": \"ruleArticles\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleNextPage\",\n            \"columnName\": \"ruleNextPage\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleTitle\",\n            \"columnName\": \"ruleTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"rulePubDate\",\n            \"columnName\": \"rulePubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleDescription\",\n            \"columnName\": \"ruleDescription\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleImage\",\n            \"columnName\": \"ruleImage\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleLink\",\n            \"columnName\": \"ruleLink\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleContent\",\n            \"columnName\": \"ruleContent\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"style\",\n            \"columnName\": \"style\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enableJs\",\n            \"columnName\": \"enableJs\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"loadWithBaseUrl\",\n            \"columnName\": \"loadWithBaseUrl\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"sourceUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_rssSources_sourceUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"sourceUrl\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_rssSources_sourceUrl` ON `${TABLE_NAME}` (`sourceUrl`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"bookmarks\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`time` INTEGER NOT NULL, `bookName` TEXT NOT NULL, `bookAuthor` TEXT NOT NULL, `chapterIndex` INTEGER NOT NULL, `chapterPos` INTEGER NOT NULL, `chapterName` TEXT NOT NULL, `bookText` TEXT NOT NULL, `content` TEXT NOT NULL, PRIMARY KEY(`time`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"time\",\n            \"columnName\": \"time\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookName\",\n            \"columnName\": \"bookName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookAuthor\",\n            \"columnName\": \"bookAuthor\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterIndex\",\n            \"columnName\": \"chapterIndex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterPos\",\n            \"columnName\": \"chapterPos\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterName\",\n            \"columnName\": \"chapterName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookText\",\n            \"columnName\": \"bookText\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"time\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_bookmarks_bookName_bookAuthor\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"bookName\",\n              \"bookAuthor\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_bookmarks_bookName_bookAuthor` ON `${TABLE_NAME}` (`bookName`, `bookAuthor`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssArticles\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`origin` TEXT NOT NULL, `sort` TEXT NOT NULL, `title` TEXT NOT NULL, `order` INTEGER NOT NULL, `link` TEXT NOT NULL, `pubDate` TEXT, `description` TEXT, `content` TEXT, `image` TEXT, `read` INTEGER NOT NULL, `variable` TEXT, PRIMARY KEY(`origin`, `link`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sort\",\n            \"columnName\": \"sort\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"link\",\n            \"columnName\": \"link\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"pubDate\",\n            \"columnName\": \"pubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"description\",\n            \"columnName\": \"description\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"image\",\n            \"columnName\": \"image\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"read\",\n            \"columnName\": \"read\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"origin\",\n            \"link\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssReadRecords\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`record` TEXT NOT NULL, `read` INTEGER NOT NULL, PRIMARY KEY(`record`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"record\",\n            \"columnName\": \"record\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"read\",\n            \"columnName\": \"read\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"record\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssStars\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`origin` TEXT NOT NULL, `sort` TEXT NOT NULL, `title` TEXT NOT NULL, `starTime` INTEGER NOT NULL, `link` TEXT NOT NULL, `pubDate` TEXT, `description` TEXT, `content` TEXT, `image` TEXT, `variable` TEXT, PRIMARY KEY(`origin`, `link`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sort\",\n            \"columnName\": \"sort\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"starTime\",\n            \"columnName\": \"starTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"link\",\n            \"columnName\": \"link\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"pubDate\",\n            \"columnName\": \"pubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"description\",\n            \"columnName\": \"description\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"image\",\n            \"columnName\": \"image\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"origin\",\n            \"link\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"txtTocRules\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `rule` TEXT NOT NULL, `serialNumber` INTEGER NOT NULL, `enable` INTEGER NOT NULL, PRIMARY KEY(`id`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"rule\",\n            \"columnName\": \"rule\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"serialNumber\",\n            \"columnName\": \"serialNumber\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enable\",\n            \"columnName\": \"enable\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"id\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"readRecord\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`deviceId` TEXT NOT NULL, `bookName` TEXT NOT NULL, `readTime` INTEGER NOT NULL, PRIMARY KEY(`deviceId`, `bookName`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"deviceId\",\n            \"columnName\": \"deviceId\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookName\",\n            \"columnName\": \"bookName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"readTime\",\n            \"columnName\": \"readTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"deviceId\",\n            \"bookName\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"httpTTS\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `url` TEXT NOT NULL, `contentType` TEXT, `concurrentRate` TEXT, `loginUrl` TEXT, `loginUi` TEXT, `header` TEXT, `loginCheckJs` TEXT, PRIMARY KEY(`id`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"contentType\",\n            \"columnName\": \"contentType\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"concurrentRate\",\n            \"columnName\": \"concurrentRate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUrl\",\n            \"columnName\": \"loginUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUi\",\n            \"columnName\": \"loginUi\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"header\",\n            \"columnName\": \"header\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginCheckJs\",\n            \"columnName\": \"loginCheckJs\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"id\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"caches\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`key` TEXT NOT NULL, `value` TEXT, `deadline` INTEGER NOT NULL, PRIMARY KEY(`key`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"key\",\n            \"columnName\": \"key\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"value\",\n            \"columnName\": \"value\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"deadline\",\n            \"columnName\": \"deadline\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"key\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_caches_key\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"key\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_caches_key` ON `${TABLE_NAME}` (`key`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"ruleSubs\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `url` TEXT NOT NULL, `type` INTEGER NOT NULL, `customOrder` INTEGER NOT NULL, `autoUpdate` INTEGER NOT NULL, `update` INTEGER NOT NULL, PRIMARY KEY(`id`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"autoUpdate\",\n            \"columnName\": \"autoUpdate\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"update\",\n            \"columnName\": \"update\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"id\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"keyboardAssists\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`type` INTEGER NOT NULL DEFAULT 0, `key` TEXT NOT NULL DEFAULT '', `value` TEXT NOT NULL DEFAULT '', `serialNo` INTEGER NOT NULL DEFAULT 0, PRIMARY KEY(`type`, `key`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"key\",\n            \"columnName\": \"key\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"value\",\n            \"columnName\": \"value\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"serialNo\",\n            \"columnName\": \"serialNo\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"type\",\n            \"key\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      }\n    ],\n    \"views\": [],\n    \"setupQueries\": [\n      \"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)\",\n      \"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '63272539e04e405abfd79e27ea55db75')\"\n    ]\n  }\n}"
  },
  {
    "path": "app/schemas/io.legado.app.data.AppDatabase/46.json",
    "content": "{\n  \"formatVersion\": 1,\n  \"database\": {\n    \"version\": 46,\n    \"identityHash\": \"63272539e04e405abfd79e27ea55db75\",\n    \"entities\": [\n      {\n        \"tableName\": \"books\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookUrl` TEXT NOT NULL DEFAULT '', `tocUrl` TEXT NOT NULL DEFAULT '', `origin` TEXT NOT NULL DEFAULT '', `originName` TEXT NOT NULL DEFAULT '', `name` TEXT NOT NULL DEFAULT '', `author` TEXT NOT NULL DEFAULT '', `kind` TEXT, `customTag` TEXT, `coverUrl` TEXT, `customCoverUrl` TEXT, `intro` TEXT, `customIntro` TEXT, `charset` TEXT, `type` INTEGER NOT NULL DEFAULT 0, `group` INTEGER NOT NULL DEFAULT 0, `latestChapterTitle` TEXT, `latestChapterTime` INTEGER NOT NULL DEFAULT 0, `lastCheckTime` INTEGER NOT NULL DEFAULT 0, `lastCheckCount` INTEGER NOT NULL DEFAULT 0, `totalChapterNum` INTEGER NOT NULL DEFAULT 0, `durChapterTitle` TEXT, `durChapterIndex` INTEGER NOT NULL DEFAULT 0, `durChapterPos` INTEGER NOT NULL DEFAULT 0, `durChapterTime` INTEGER NOT NULL DEFAULT 0, `wordCount` TEXT, `canUpdate` INTEGER NOT NULL DEFAULT 1, `order` INTEGER NOT NULL DEFAULT 0, `originOrder` INTEGER NOT NULL DEFAULT 0, `variable` TEXT, `readConfig` TEXT, PRIMARY KEY(`bookUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"tocUrl\",\n            \"columnName\": \"tocUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"originName\",\n            \"columnName\": \"originName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"author\",\n            \"columnName\": \"author\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"kind\",\n            \"columnName\": \"kind\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customTag\",\n            \"columnName\": \"customTag\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"coverUrl\",\n            \"columnName\": \"coverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customCoverUrl\",\n            \"columnName\": \"customCoverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"intro\",\n            \"columnName\": \"intro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customIntro\",\n            \"columnName\": \"customIntro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"charset\",\n            \"columnName\": \"charset\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"group\",\n            \"columnName\": \"group\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"latestChapterTitle\",\n            \"columnName\": \"latestChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"latestChapterTime\",\n            \"columnName\": \"latestChapterTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"lastCheckTime\",\n            \"columnName\": \"lastCheckTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"lastCheckCount\",\n            \"columnName\": \"lastCheckCount\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"totalChapterNum\",\n            \"columnName\": \"totalChapterNum\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"durChapterTitle\",\n            \"columnName\": \"durChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"durChapterIndex\",\n            \"columnName\": \"durChapterIndex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"durChapterPos\",\n            \"columnName\": \"durChapterPos\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"durChapterTime\",\n            \"columnName\": \"durChapterTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"wordCount\",\n            \"columnName\": \"wordCount\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"canUpdate\",\n            \"columnName\": \"canUpdate\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"originOrder\",\n            \"columnName\": \"originOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"readConfig\",\n            \"columnName\": \"readConfig\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"bookUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_books_name_author\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"name\",\n              \"author\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_books_name_author` ON `${TABLE_NAME}` (`name`, `author`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"book_groups\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`groupId` INTEGER NOT NULL, `groupName` TEXT NOT NULL, `cover` TEXT, `order` INTEGER NOT NULL, `show` INTEGER NOT NULL, PRIMARY KEY(`groupId`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"groupId\",\n            \"columnName\": \"groupId\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"groupName\",\n            \"columnName\": \"groupName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"cover\",\n            \"columnName\": \"cover\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"show\",\n            \"columnName\": \"show\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"groupId\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"book_sources\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookSourceUrl` TEXT NOT NULL, `bookSourceName` TEXT NOT NULL, `bookSourceGroup` TEXT, `bookSourceType` INTEGER NOT NULL, `bookUrlPattern` TEXT, `customOrder` INTEGER NOT NULL, `enabled` INTEGER NOT NULL, `enabledExplore` INTEGER NOT NULL, `concurrentRate` TEXT, `header` TEXT, `loginUrl` TEXT, `loginUi` TEXT, `loginCheckJs` TEXT, `bookSourceComment` TEXT, `lastUpdateTime` INTEGER NOT NULL, `respondTime` INTEGER NOT NULL, `weight` INTEGER NOT NULL, `exploreUrl` TEXT, `ruleExplore` TEXT, `searchUrl` TEXT, `ruleSearch` TEXT, `ruleBookInfo` TEXT, `ruleToc` TEXT, `ruleContent` TEXT, PRIMARY KEY(`bookSourceUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookSourceUrl\",\n            \"columnName\": \"bookSourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookSourceName\",\n            \"columnName\": \"bookSourceName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookSourceGroup\",\n            \"columnName\": \"bookSourceGroup\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"bookSourceType\",\n            \"columnName\": \"bookSourceType\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookUrlPattern\",\n            \"columnName\": \"bookUrlPattern\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enabled\",\n            \"columnName\": \"enabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enabledExplore\",\n            \"columnName\": \"enabledExplore\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"concurrentRate\",\n            \"columnName\": \"concurrentRate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"header\",\n            \"columnName\": \"header\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUrl\",\n            \"columnName\": \"loginUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUi\",\n            \"columnName\": \"loginUi\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginCheckJs\",\n            \"columnName\": \"loginCheckJs\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"bookSourceComment\",\n            \"columnName\": \"bookSourceComment\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"lastUpdateTime\",\n            \"columnName\": \"lastUpdateTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"respondTime\",\n            \"columnName\": \"respondTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"weight\",\n            \"columnName\": \"weight\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"exploreUrl\",\n            \"columnName\": \"exploreUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleExplore\",\n            \"columnName\": \"ruleExplore\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"searchUrl\",\n            \"columnName\": \"searchUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleSearch\",\n            \"columnName\": \"ruleSearch\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleBookInfo\",\n            \"columnName\": \"ruleBookInfo\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleToc\",\n            \"columnName\": \"ruleToc\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleContent\",\n            \"columnName\": \"ruleContent\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"bookSourceUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_book_sources_bookSourceUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"bookSourceUrl\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_book_sources_bookSourceUrl` ON `${TABLE_NAME}` (`bookSourceUrl`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"chapters\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`url` TEXT NOT NULL, `title` TEXT NOT NULL, `isVolume` INTEGER NOT NULL, `baseUrl` TEXT NOT NULL, `bookUrl` TEXT NOT NULL, `index` INTEGER NOT NULL, `isVip` INTEGER NOT NULL, `isPay` INTEGER NOT NULL, `resourceUrl` TEXT, `tag` TEXT, `start` INTEGER, `end` INTEGER, `startFragmentId` TEXT, `endFragmentId` TEXT, `variable` TEXT, PRIMARY KEY(`url`, `bookUrl`), FOREIGN KEY(`bookUrl`) REFERENCES `books`(`bookUrl`) ON UPDATE NO ACTION ON DELETE CASCADE )\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"isVolume\",\n            \"columnName\": \"isVolume\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"baseUrl\",\n            \"columnName\": \"baseUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"index\",\n            \"columnName\": \"index\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"isVip\",\n            \"columnName\": \"isVip\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"isPay\",\n            \"columnName\": \"isPay\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"resourceUrl\",\n            \"columnName\": \"resourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"tag\",\n            \"columnName\": \"tag\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"start\",\n            \"columnName\": \"start\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"end\",\n            \"columnName\": \"end\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"startFragmentId\",\n            \"columnName\": \"startFragmentId\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"endFragmentId\",\n            \"columnName\": \"endFragmentId\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"url\",\n            \"bookUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_chapters_bookUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"bookUrl\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_chapters_bookUrl` ON `${TABLE_NAME}` (`bookUrl`)\"\n          },\n          {\n            \"name\": \"index_chapters_bookUrl_index\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"bookUrl\",\n              \"index\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_chapters_bookUrl_index` ON `${TABLE_NAME}` (`bookUrl`, `index`)\"\n          }\n        ],\n        \"foreignKeys\": [\n          {\n            \"table\": \"books\",\n            \"onDelete\": \"CASCADE\",\n            \"onUpdate\": \"NO ACTION\",\n            \"columns\": [\n              \"bookUrl\"\n            ],\n            \"referencedColumns\": [\n              \"bookUrl\"\n            ]\n          }\n        ]\n      },\n      {\n        \"tableName\": \"replace_rules\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL DEFAULT '', `group` TEXT, `pattern` TEXT NOT NULL DEFAULT '', `replacement` TEXT NOT NULL DEFAULT '', `scope` TEXT, `scopeTitle` INTEGER NOT NULL DEFAULT 0, `scopeContent` INTEGER NOT NULL DEFAULT 1, `isEnabled` INTEGER NOT NULL DEFAULT 1, `isRegex` INTEGER NOT NULL DEFAULT 1, `sortOrder` INTEGER NOT NULL DEFAULT 0)\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"group\",\n            \"columnName\": \"group\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"pattern\",\n            \"columnName\": \"pattern\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"replacement\",\n            \"columnName\": \"replacement\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"scope\",\n            \"columnName\": \"scope\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"scopeTitle\",\n            \"columnName\": \"scopeTitle\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"scopeContent\",\n            \"columnName\": \"scopeContent\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"isEnabled\",\n            \"columnName\": \"isEnabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"isRegex\",\n            \"columnName\": \"isRegex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"sortOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"id\"\n          ],\n          \"autoGenerate\": true\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_replace_rules_id\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"id\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_replace_rules_id` ON `${TABLE_NAME}` (`id`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"searchBooks\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookUrl` TEXT NOT NULL, `origin` TEXT NOT NULL, `originName` TEXT NOT NULL, `type` INTEGER NOT NULL, `name` TEXT NOT NULL, `author` TEXT NOT NULL, `kind` TEXT, `coverUrl` TEXT, `intro` TEXT, `wordCount` TEXT, `latestChapterTitle` TEXT, `tocUrl` TEXT NOT NULL, `time` INTEGER NOT NULL, `variable` TEXT, `originOrder` INTEGER NOT NULL, PRIMARY KEY(`bookUrl`), FOREIGN KEY(`origin`) REFERENCES `book_sources`(`bookSourceUrl`) ON UPDATE NO ACTION ON DELETE CASCADE )\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"originName\",\n            \"columnName\": \"originName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"author\",\n            \"columnName\": \"author\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"kind\",\n            \"columnName\": \"kind\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"coverUrl\",\n            \"columnName\": \"coverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"intro\",\n            \"columnName\": \"intro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"wordCount\",\n            \"columnName\": \"wordCount\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"latestChapterTitle\",\n            \"columnName\": \"latestChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"tocUrl\",\n            \"columnName\": \"tocUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"time\",\n            \"columnName\": \"time\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"originOrder\",\n            \"columnName\": \"originOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"bookUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_searchBooks_bookUrl\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"bookUrl\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_searchBooks_bookUrl` ON `${TABLE_NAME}` (`bookUrl`)\"\n          },\n          {\n            \"name\": \"index_searchBooks_origin\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"origin\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_searchBooks_origin` ON `${TABLE_NAME}` (`origin`)\"\n          }\n        ],\n        \"foreignKeys\": [\n          {\n            \"table\": \"book_sources\",\n            \"onDelete\": \"CASCADE\",\n            \"onUpdate\": \"NO ACTION\",\n            \"columns\": [\n              \"origin\"\n            ],\n            \"referencedColumns\": [\n              \"bookSourceUrl\"\n            ]\n          }\n        ]\n      },\n      {\n        \"tableName\": \"search_keywords\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`word` TEXT NOT NULL, `usage` INTEGER NOT NULL, `lastUseTime` INTEGER NOT NULL, PRIMARY KEY(`word`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"word\",\n            \"columnName\": \"word\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"usage\",\n            \"columnName\": \"usage\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"lastUseTime\",\n            \"columnName\": \"lastUseTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"word\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_search_keywords_word\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"word\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_search_keywords_word` ON `${TABLE_NAME}` (`word`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"cookies\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`url` TEXT NOT NULL, `cookie` TEXT NOT NULL, PRIMARY KEY(`url`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"cookie\",\n            \"columnName\": \"cookie\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"url\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_cookies_url\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"url\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_cookies_url` ON `${TABLE_NAME}` (`url`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssSources\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`sourceUrl` TEXT NOT NULL, `sourceName` TEXT NOT NULL, `sourceIcon` TEXT NOT NULL, `sourceGroup` TEXT, `sourceComment` TEXT, `enabled` INTEGER NOT NULL, `concurrentRate` TEXT, `header` TEXT, `loginUrl` TEXT, `loginUi` TEXT, `loginCheckJs` TEXT, `sortUrl` TEXT, `singleUrl` INTEGER NOT NULL, `articleStyle` INTEGER NOT NULL, `ruleArticles` TEXT, `ruleNextPage` TEXT, `ruleTitle` TEXT, `rulePubDate` TEXT, `ruleDescription` TEXT, `ruleImage` TEXT, `ruleLink` TEXT, `ruleContent` TEXT, `style` TEXT, `enableJs` INTEGER NOT NULL, `loadWithBaseUrl` INTEGER NOT NULL, `customOrder` INTEGER NOT NULL, PRIMARY KEY(`sourceUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"sourceUrl\",\n            \"columnName\": \"sourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceName\",\n            \"columnName\": \"sourceName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceIcon\",\n            \"columnName\": \"sourceIcon\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceGroup\",\n            \"columnName\": \"sourceGroup\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"sourceComment\",\n            \"columnName\": \"sourceComment\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enabled\",\n            \"columnName\": \"enabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"concurrentRate\",\n            \"columnName\": \"concurrentRate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"header\",\n            \"columnName\": \"header\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUrl\",\n            \"columnName\": \"loginUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUi\",\n            \"columnName\": \"loginUi\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginCheckJs\",\n            \"columnName\": \"loginCheckJs\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"sortUrl\",\n            \"columnName\": \"sortUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"singleUrl\",\n            \"columnName\": \"singleUrl\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"articleStyle\",\n            \"columnName\": \"articleStyle\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"ruleArticles\",\n            \"columnName\": \"ruleArticles\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleNextPage\",\n            \"columnName\": \"ruleNextPage\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleTitle\",\n            \"columnName\": \"ruleTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"rulePubDate\",\n            \"columnName\": \"rulePubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleDescription\",\n            \"columnName\": \"ruleDescription\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleImage\",\n            \"columnName\": \"ruleImage\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleLink\",\n            \"columnName\": \"ruleLink\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleContent\",\n            \"columnName\": \"ruleContent\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"style\",\n            \"columnName\": \"style\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enableJs\",\n            \"columnName\": \"enableJs\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"loadWithBaseUrl\",\n            \"columnName\": \"loadWithBaseUrl\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"sourceUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_rssSources_sourceUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"sourceUrl\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_rssSources_sourceUrl` ON `${TABLE_NAME}` (`sourceUrl`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"bookmarks\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`time` INTEGER NOT NULL, `bookName` TEXT NOT NULL, `bookAuthor` TEXT NOT NULL, `chapterIndex` INTEGER NOT NULL, `chapterPos` INTEGER NOT NULL, `chapterName` TEXT NOT NULL, `bookText` TEXT NOT NULL, `content` TEXT NOT NULL, PRIMARY KEY(`time`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"time\",\n            \"columnName\": \"time\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookName\",\n            \"columnName\": \"bookName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookAuthor\",\n            \"columnName\": \"bookAuthor\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterIndex\",\n            \"columnName\": \"chapterIndex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterPos\",\n            \"columnName\": \"chapterPos\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterName\",\n            \"columnName\": \"chapterName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookText\",\n            \"columnName\": \"bookText\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"time\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_bookmarks_bookName_bookAuthor\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"bookName\",\n              \"bookAuthor\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_bookmarks_bookName_bookAuthor` ON `${TABLE_NAME}` (`bookName`, `bookAuthor`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssArticles\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`origin` TEXT NOT NULL, `sort` TEXT NOT NULL, `title` TEXT NOT NULL, `order` INTEGER NOT NULL, `link` TEXT NOT NULL, `pubDate` TEXT, `description` TEXT, `content` TEXT, `image` TEXT, `read` INTEGER NOT NULL, `variable` TEXT, PRIMARY KEY(`origin`, `link`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sort\",\n            \"columnName\": \"sort\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"link\",\n            \"columnName\": \"link\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"pubDate\",\n            \"columnName\": \"pubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"description\",\n            \"columnName\": \"description\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"image\",\n            \"columnName\": \"image\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"read\",\n            \"columnName\": \"read\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"origin\",\n            \"link\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssReadRecords\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`record` TEXT NOT NULL, `read` INTEGER NOT NULL, PRIMARY KEY(`record`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"record\",\n            \"columnName\": \"record\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"read\",\n            \"columnName\": \"read\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"record\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssStars\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`origin` TEXT NOT NULL, `sort` TEXT NOT NULL, `title` TEXT NOT NULL, `starTime` INTEGER NOT NULL, `link` TEXT NOT NULL, `pubDate` TEXT, `description` TEXT, `content` TEXT, `image` TEXT, `variable` TEXT, PRIMARY KEY(`origin`, `link`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sort\",\n            \"columnName\": \"sort\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"starTime\",\n            \"columnName\": \"starTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"link\",\n            \"columnName\": \"link\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"pubDate\",\n            \"columnName\": \"pubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"description\",\n            \"columnName\": \"description\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"image\",\n            \"columnName\": \"image\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"origin\",\n            \"link\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"txtTocRules\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `rule` TEXT NOT NULL, `serialNumber` INTEGER NOT NULL, `enable` INTEGER NOT NULL, PRIMARY KEY(`id`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"rule\",\n            \"columnName\": \"rule\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"serialNumber\",\n            \"columnName\": \"serialNumber\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enable\",\n            \"columnName\": \"enable\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"id\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"readRecord\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`deviceId` TEXT NOT NULL, `bookName` TEXT NOT NULL, `readTime` INTEGER NOT NULL, PRIMARY KEY(`deviceId`, `bookName`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"deviceId\",\n            \"columnName\": \"deviceId\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookName\",\n            \"columnName\": \"bookName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"readTime\",\n            \"columnName\": \"readTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"deviceId\",\n            \"bookName\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"httpTTS\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `url` TEXT NOT NULL, `contentType` TEXT, `concurrentRate` TEXT, `loginUrl` TEXT, `loginUi` TEXT, `header` TEXT, `loginCheckJs` TEXT, PRIMARY KEY(`id`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"contentType\",\n            \"columnName\": \"contentType\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"concurrentRate\",\n            \"columnName\": \"concurrentRate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUrl\",\n            \"columnName\": \"loginUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUi\",\n            \"columnName\": \"loginUi\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"header\",\n            \"columnName\": \"header\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginCheckJs\",\n            \"columnName\": \"loginCheckJs\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"id\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"caches\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`key` TEXT NOT NULL, `value` TEXT, `deadline` INTEGER NOT NULL, PRIMARY KEY(`key`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"key\",\n            \"columnName\": \"key\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"value\",\n            \"columnName\": \"value\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"deadline\",\n            \"columnName\": \"deadline\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"key\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_caches_key\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"key\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_caches_key` ON `${TABLE_NAME}` (`key`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"ruleSubs\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `url` TEXT NOT NULL, `type` INTEGER NOT NULL, `customOrder` INTEGER NOT NULL, `autoUpdate` INTEGER NOT NULL, `update` INTEGER NOT NULL, PRIMARY KEY(`id`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"autoUpdate\",\n            \"columnName\": \"autoUpdate\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"update\",\n            \"columnName\": \"update\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"id\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"keyboardAssists\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`type` INTEGER NOT NULL DEFAULT 0, `key` TEXT NOT NULL DEFAULT '', `value` TEXT NOT NULL DEFAULT '', `serialNo` INTEGER NOT NULL DEFAULT 0, PRIMARY KEY(`type`, `key`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"key\",\n            \"columnName\": \"key\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"value\",\n            \"columnName\": \"value\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"serialNo\",\n            \"columnName\": \"serialNo\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"type\",\n            \"key\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      }\n    ],\n    \"views\": [],\n    \"setupQueries\": [\n      \"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)\",\n      \"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '63272539e04e405abfd79e27ea55db75')\"\n    ]\n  }\n}"
  },
  {
    "path": "app/schemas/io.legado.app.data.AppDatabase/47.json",
    "content": "{\n  \"formatVersion\": 1,\n  \"database\": {\n    \"version\": 47,\n    \"identityHash\": \"985b7a8fbf3f49e6eb444e4f4472bdee\",\n    \"entities\": [\n      {\n        \"tableName\": \"books\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookUrl` TEXT NOT NULL DEFAULT '', `tocUrl` TEXT NOT NULL DEFAULT '', `origin` TEXT NOT NULL DEFAULT '', `originName` TEXT NOT NULL DEFAULT '', `name` TEXT NOT NULL DEFAULT '', `author` TEXT NOT NULL DEFAULT '', `kind` TEXT, `customTag` TEXT, `coverUrl` TEXT, `customCoverUrl` TEXT, `intro` TEXT, `customIntro` TEXT, `charset` TEXT, `type` INTEGER NOT NULL DEFAULT 0, `group` INTEGER NOT NULL DEFAULT 0, `latestChapterTitle` TEXT, `latestChapterTime` INTEGER NOT NULL DEFAULT 0, `lastCheckTime` INTEGER NOT NULL DEFAULT 0, `lastCheckCount` INTEGER NOT NULL DEFAULT 0, `totalChapterNum` INTEGER NOT NULL DEFAULT 0, `durChapterTitle` TEXT, `durChapterIndex` INTEGER NOT NULL DEFAULT 0, `durChapterPos` INTEGER NOT NULL DEFAULT 0, `durChapterTime` INTEGER NOT NULL DEFAULT 0, `wordCount` TEXT, `canUpdate` INTEGER NOT NULL DEFAULT 1, `order` INTEGER NOT NULL DEFAULT 0, `originOrder` INTEGER NOT NULL DEFAULT 0, `variable` TEXT, `readConfig` TEXT, PRIMARY KEY(`bookUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"tocUrl\",\n            \"columnName\": \"tocUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"originName\",\n            \"columnName\": \"originName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"author\",\n            \"columnName\": \"author\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"kind\",\n            \"columnName\": \"kind\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customTag\",\n            \"columnName\": \"customTag\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"coverUrl\",\n            \"columnName\": \"coverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customCoverUrl\",\n            \"columnName\": \"customCoverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"intro\",\n            \"columnName\": \"intro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customIntro\",\n            \"columnName\": \"customIntro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"charset\",\n            \"columnName\": \"charset\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"group\",\n            \"columnName\": \"group\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"latestChapterTitle\",\n            \"columnName\": \"latestChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"latestChapterTime\",\n            \"columnName\": \"latestChapterTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"lastCheckTime\",\n            \"columnName\": \"lastCheckTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"lastCheckCount\",\n            \"columnName\": \"lastCheckCount\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"totalChapterNum\",\n            \"columnName\": \"totalChapterNum\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"durChapterTitle\",\n            \"columnName\": \"durChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"durChapterIndex\",\n            \"columnName\": \"durChapterIndex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"durChapterPos\",\n            \"columnName\": \"durChapterPos\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"durChapterTime\",\n            \"columnName\": \"durChapterTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"wordCount\",\n            \"columnName\": \"wordCount\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"canUpdate\",\n            \"columnName\": \"canUpdate\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"originOrder\",\n            \"columnName\": \"originOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"readConfig\",\n            \"columnName\": \"readConfig\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"bookUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_books_name_author\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"name\",\n              \"author\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_books_name_author` ON `${TABLE_NAME}` (`name`, `author`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"book_groups\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`groupId` INTEGER NOT NULL, `groupName` TEXT NOT NULL, `cover` TEXT, `order` INTEGER NOT NULL, `show` INTEGER NOT NULL, PRIMARY KEY(`groupId`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"groupId\",\n            \"columnName\": \"groupId\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"groupName\",\n            \"columnName\": \"groupName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"cover\",\n            \"columnName\": \"cover\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"show\",\n            \"columnName\": \"show\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"groupId\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"book_sources\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookSourceUrl` TEXT NOT NULL, `bookSourceName` TEXT NOT NULL, `bookSourceGroup` TEXT, `bookSourceType` INTEGER NOT NULL, `bookUrlPattern` TEXT, `customOrder` INTEGER NOT NULL, `enabled` INTEGER NOT NULL, `enabledExplore` INTEGER NOT NULL, `concurrentRate` TEXT, `header` TEXT, `loginUrl` TEXT, `loginUi` TEXT, `loginCheckJs` TEXT, `bookSourceComment` TEXT, `lastUpdateTime` INTEGER NOT NULL, `respondTime` INTEGER NOT NULL, `weight` INTEGER NOT NULL, `exploreUrl` TEXT, `ruleExplore` TEXT, `searchUrl` TEXT, `ruleSearch` TEXT, `ruleBookInfo` TEXT, `ruleToc` TEXT, `ruleContent` TEXT, PRIMARY KEY(`bookSourceUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookSourceUrl\",\n            \"columnName\": \"bookSourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookSourceName\",\n            \"columnName\": \"bookSourceName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookSourceGroup\",\n            \"columnName\": \"bookSourceGroup\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"bookSourceType\",\n            \"columnName\": \"bookSourceType\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookUrlPattern\",\n            \"columnName\": \"bookUrlPattern\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enabled\",\n            \"columnName\": \"enabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enabledExplore\",\n            \"columnName\": \"enabledExplore\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"concurrentRate\",\n            \"columnName\": \"concurrentRate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"header\",\n            \"columnName\": \"header\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUrl\",\n            \"columnName\": \"loginUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUi\",\n            \"columnName\": \"loginUi\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginCheckJs\",\n            \"columnName\": \"loginCheckJs\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"bookSourceComment\",\n            \"columnName\": \"bookSourceComment\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"lastUpdateTime\",\n            \"columnName\": \"lastUpdateTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"respondTime\",\n            \"columnName\": \"respondTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"weight\",\n            \"columnName\": \"weight\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"exploreUrl\",\n            \"columnName\": \"exploreUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleExplore\",\n            \"columnName\": \"ruleExplore\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"searchUrl\",\n            \"columnName\": \"searchUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleSearch\",\n            \"columnName\": \"ruleSearch\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleBookInfo\",\n            \"columnName\": \"ruleBookInfo\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleToc\",\n            \"columnName\": \"ruleToc\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleContent\",\n            \"columnName\": \"ruleContent\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"bookSourceUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_book_sources_bookSourceUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"bookSourceUrl\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_book_sources_bookSourceUrl` ON `${TABLE_NAME}` (`bookSourceUrl`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"chapters\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`url` TEXT NOT NULL, `title` TEXT NOT NULL, `isVolume` INTEGER NOT NULL, `baseUrl` TEXT NOT NULL, `bookUrl` TEXT NOT NULL, `index` INTEGER NOT NULL, `isVip` INTEGER NOT NULL, `isPay` INTEGER NOT NULL, `resourceUrl` TEXT, `tag` TEXT, `start` INTEGER, `end` INTEGER, `startFragmentId` TEXT, `endFragmentId` TEXT, `variable` TEXT, PRIMARY KEY(`url`, `bookUrl`), FOREIGN KEY(`bookUrl`) REFERENCES `books`(`bookUrl`) ON UPDATE NO ACTION ON DELETE CASCADE )\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"isVolume\",\n            \"columnName\": \"isVolume\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"baseUrl\",\n            \"columnName\": \"baseUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"index\",\n            \"columnName\": \"index\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"isVip\",\n            \"columnName\": \"isVip\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"isPay\",\n            \"columnName\": \"isPay\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"resourceUrl\",\n            \"columnName\": \"resourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"tag\",\n            \"columnName\": \"tag\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"start\",\n            \"columnName\": \"start\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"end\",\n            \"columnName\": \"end\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"startFragmentId\",\n            \"columnName\": \"startFragmentId\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"endFragmentId\",\n            \"columnName\": \"endFragmentId\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"url\",\n            \"bookUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_chapters_bookUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"bookUrl\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_chapters_bookUrl` ON `${TABLE_NAME}` (`bookUrl`)\"\n          },\n          {\n            \"name\": \"index_chapters_bookUrl_index\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"bookUrl\",\n              \"index\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_chapters_bookUrl_index` ON `${TABLE_NAME}` (`bookUrl`, `index`)\"\n          }\n        ],\n        \"foreignKeys\": [\n          {\n            \"table\": \"books\",\n            \"onDelete\": \"CASCADE\",\n            \"onUpdate\": \"NO ACTION\",\n            \"columns\": [\n              \"bookUrl\"\n            ],\n            \"referencedColumns\": [\n              \"bookUrl\"\n            ]\n          }\n        ]\n      },\n      {\n        \"tableName\": \"replace_rules\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL DEFAULT '', `group` TEXT, `pattern` TEXT NOT NULL DEFAULT '', `replacement` TEXT NOT NULL DEFAULT '', `scope` TEXT, `scopeTitle` INTEGER NOT NULL DEFAULT 0, `scopeContent` INTEGER NOT NULL DEFAULT 1, `isEnabled` INTEGER NOT NULL DEFAULT 1, `isRegex` INTEGER NOT NULL DEFAULT 1, `timeoutMillisecond` INTEGER NOT NULL DEFAULT 3000, `sortOrder` INTEGER NOT NULL DEFAULT 0)\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"group\",\n            \"columnName\": \"group\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"pattern\",\n            \"columnName\": \"pattern\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"replacement\",\n            \"columnName\": \"replacement\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"scope\",\n            \"columnName\": \"scope\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"scopeTitle\",\n            \"columnName\": \"scopeTitle\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"scopeContent\",\n            \"columnName\": \"scopeContent\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"isEnabled\",\n            \"columnName\": \"isEnabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"isRegex\",\n            \"columnName\": \"isRegex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"timeoutMillisecond\",\n            \"columnName\": \"timeoutMillisecond\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"3000\"\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"sortOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"id\"\n          ],\n          \"autoGenerate\": true\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_replace_rules_id\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"id\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_replace_rules_id` ON `${TABLE_NAME}` (`id`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"searchBooks\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookUrl` TEXT NOT NULL, `origin` TEXT NOT NULL, `originName` TEXT NOT NULL, `type` INTEGER NOT NULL, `name` TEXT NOT NULL, `author` TEXT NOT NULL, `kind` TEXT, `coverUrl` TEXT, `intro` TEXT, `wordCount` TEXT, `latestChapterTitle` TEXT, `tocUrl` TEXT NOT NULL, `time` INTEGER NOT NULL, `variable` TEXT, `originOrder` INTEGER NOT NULL, PRIMARY KEY(`bookUrl`), FOREIGN KEY(`origin`) REFERENCES `book_sources`(`bookSourceUrl`) ON UPDATE NO ACTION ON DELETE CASCADE )\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"originName\",\n            \"columnName\": \"originName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"author\",\n            \"columnName\": \"author\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"kind\",\n            \"columnName\": \"kind\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"coverUrl\",\n            \"columnName\": \"coverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"intro\",\n            \"columnName\": \"intro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"wordCount\",\n            \"columnName\": \"wordCount\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"latestChapterTitle\",\n            \"columnName\": \"latestChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"tocUrl\",\n            \"columnName\": \"tocUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"time\",\n            \"columnName\": \"time\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"originOrder\",\n            \"columnName\": \"originOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"bookUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_searchBooks_bookUrl\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"bookUrl\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_searchBooks_bookUrl` ON `${TABLE_NAME}` (`bookUrl`)\"\n          },\n          {\n            \"name\": \"index_searchBooks_origin\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"origin\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_searchBooks_origin` ON `${TABLE_NAME}` (`origin`)\"\n          }\n        ],\n        \"foreignKeys\": [\n          {\n            \"table\": \"book_sources\",\n            \"onDelete\": \"CASCADE\",\n            \"onUpdate\": \"NO ACTION\",\n            \"columns\": [\n              \"origin\"\n            ],\n            \"referencedColumns\": [\n              \"bookSourceUrl\"\n            ]\n          }\n        ]\n      },\n      {\n        \"tableName\": \"search_keywords\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`word` TEXT NOT NULL, `usage` INTEGER NOT NULL, `lastUseTime` INTEGER NOT NULL, PRIMARY KEY(`word`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"word\",\n            \"columnName\": \"word\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"usage\",\n            \"columnName\": \"usage\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"lastUseTime\",\n            \"columnName\": \"lastUseTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"word\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_search_keywords_word\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"word\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_search_keywords_word` ON `${TABLE_NAME}` (`word`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"cookies\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`url` TEXT NOT NULL, `cookie` TEXT NOT NULL, PRIMARY KEY(`url`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"cookie\",\n            \"columnName\": \"cookie\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"url\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_cookies_url\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"url\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_cookies_url` ON `${TABLE_NAME}` (`url`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssSources\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`sourceUrl` TEXT NOT NULL, `sourceName` TEXT NOT NULL, `sourceIcon` TEXT NOT NULL, `sourceGroup` TEXT, `sourceComment` TEXT, `enabled` INTEGER NOT NULL, `concurrentRate` TEXT, `header` TEXT, `loginUrl` TEXT, `loginUi` TEXT, `loginCheckJs` TEXT, `sortUrl` TEXT, `singleUrl` INTEGER NOT NULL, `articleStyle` INTEGER NOT NULL, `ruleArticles` TEXT, `ruleNextPage` TEXT, `ruleTitle` TEXT, `rulePubDate` TEXT, `ruleDescription` TEXT, `ruleImage` TEXT, `ruleLink` TEXT, `ruleContent` TEXT, `style` TEXT, `enableJs` INTEGER NOT NULL, `loadWithBaseUrl` INTEGER NOT NULL, `customOrder` INTEGER NOT NULL, PRIMARY KEY(`sourceUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"sourceUrl\",\n            \"columnName\": \"sourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceName\",\n            \"columnName\": \"sourceName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceIcon\",\n            \"columnName\": \"sourceIcon\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceGroup\",\n            \"columnName\": \"sourceGroup\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"sourceComment\",\n            \"columnName\": \"sourceComment\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enabled\",\n            \"columnName\": \"enabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"concurrentRate\",\n            \"columnName\": \"concurrentRate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"header\",\n            \"columnName\": \"header\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUrl\",\n            \"columnName\": \"loginUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUi\",\n            \"columnName\": \"loginUi\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginCheckJs\",\n            \"columnName\": \"loginCheckJs\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"sortUrl\",\n            \"columnName\": \"sortUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"singleUrl\",\n            \"columnName\": \"singleUrl\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"articleStyle\",\n            \"columnName\": \"articleStyle\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"ruleArticles\",\n            \"columnName\": \"ruleArticles\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleNextPage\",\n            \"columnName\": \"ruleNextPage\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleTitle\",\n            \"columnName\": \"ruleTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"rulePubDate\",\n            \"columnName\": \"rulePubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleDescription\",\n            \"columnName\": \"ruleDescription\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleImage\",\n            \"columnName\": \"ruleImage\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleLink\",\n            \"columnName\": \"ruleLink\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleContent\",\n            \"columnName\": \"ruleContent\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"style\",\n            \"columnName\": \"style\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enableJs\",\n            \"columnName\": \"enableJs\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"loadWithBaseUrl\",\n            \"columnName\": \"loadWithBaseUrl\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"sourceUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_rssSources_sourceUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"sourceUrl\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_rssSources_sourceUrl` ON `${TABLE_NAME}` (`sourceUrl`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"bookmarks\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`time` INTEGER NOT NULL, `bookName` TEXT NOT NULL, `bookAuthor` TEXT NOT NULL, `chapterIndex` INTEGER NOT NULL, `chapterPos` INTEGER NOT NULL, `chapterName` TEXT NOT NULL, `bookText` TEXT NOT NULL, `content` TEXT NOT NULL, PRIMARY KEY(`time`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"time\",\n            \"columnName\": \"time\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookName\",\n            \"columnName\": \"bookName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookAuthor\",\n            \"columnName\": \"bookAuthor\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterIndex\",\n            \"columnName\": \"chapterIndex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterPos\",\n            \"columnName\": \"chapterPos\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterName\",\n            \"columnName\": \"chapterName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookText\",\n            \"columnName\": \"bookText\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"time\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_bookmarks_bookName_bookAuthor\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"bookName\",\n              \"bookAuthor\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_bookmarks_bookName_bookAuthor` ON `${TABLE_NAME}` (`bookName`, `bookAuthor`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssArticles\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`origin` TEXT NOT NULL, `sort` TEXT NOT NULL, `title` TEXT NOT NULL, `order` INTEGER NOT NULL, `link` TEXT NOT NULL, `pubDate` TEXT, `description` TEXT, `content` TEXT, `image` TEXT, `read` INTEGER NOT NULL, `variable` TEXT, PRIMARY KEY(`origin`, `link`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sort\",\n            \"columnName\": \"sort\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"link\",\n            \"columnName\": \"link\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"pubDate\",\n            \"columnName\": \"pubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"description\",\n            \"columnName\": \"description\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"image\",\n            \"columnName\": \"image\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"read\",\n            \"columnName\": \"read\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"origin\",\n            \"link\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssReadRecords\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`record` TEXT NOT NULL, `read` INTEGER NOT NULL, PRIMARY KEY(`record`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"record\",\n            \"columnName\": \"record\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"read\",\n            \"columnName\": \"read\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"record\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssStars\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`origin` TEXT NOT NULL, `sort` TEXT NOT NULL, `title` TEXT NOT NULL, `starTime` INTEGER NOT NULL, `link` TEXT NOT NULL, `pubDate` TEXT, `description` TEXT, `content` TEXT, `image` TEXT, `variable` TEXT, PRIMARY KEY(`origin`, `link`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sort\",\n            \"columnName\": \"sort\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"starTime\",\n            \"columnName\": \"starTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"link\",\n            \"columnName\": \"link\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"pubDate\",\n            \"columnName\": \"pubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"description\",\n            \"columnName\": \"description\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"image\",\n            \"columnName\": \"image\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"origin\",\n            \"link\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"txtTocRules\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `rule` TEXT NOT NULL, `serialNumber` INTEGER NOT NULL, `enable` INTEGER NOT NULL, PRIMARY KEY(`id`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"rule\",\n            \"columnName\": \"rule\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"serialNumber\",\n            \"columnName\": \"serialNumber\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enable\",\n            \"columnName\": \"enable\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"id\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"readRecord\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`deviceId` TEXT NOT NULL, `bookName` TEXT NOT NULL, `readTime` INTEGER NOT NULL, PRIMARY KEY(`deviceId`, `bookName`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"deviceId\",\n            \"columnName\": \"deviceId\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookName\",\n            \"columnName\": \"bookName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"readTime\",\n            \"columnName\": \"readTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"deviceId\",\n            \"bookName\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"httpTTS\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `url` TEXT NOT NULL, `contentType` TEXT, `concurrentRate` TEXT, `loginUrl` TEXT, `loginUi` TEXT, `header` TEXT, `loginCheckJs` TEXT, PRIMARY KEY(`id`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"contentType\",\n            \"columnName\": \"contentType\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"concurrentRate\",\n            \"columnName\": \"concurrentRate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUrl\",\n            \"columnName\": \"loginUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUi\",\n            \"columnName\": \"loginUi\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"header\",\n            \"columnName\": \"header\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginCheckJs\",\n            \"columnName\": \"loginCheckJs\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"id\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"caches\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`key` TEXT NOT NULL, `value` TEXT, `deadline` INTEGER NOT NULL, PRIMARY KEY(`key`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"key\",\n            \"columnName\": \"key\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"value\",\n            \"columnName\": \"value\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"deadline\",\n            \"columnName\": \"deadline\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"key\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_caches_key\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"key\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_caches_key` ON `${TABLE_NAME}` (`key`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"ruleSubs\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `url` TEXT NOT NULL, `type` INTEGER NOT NULL, `customOrder` INTEGER NOT NULL, `autoUpdate` INTEGER NOT NULL, `update` INTEGER NOT NULL, PRIMARY KEY(`id`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"autoUpdate\",\n            \"columnName\": \"autoUpdate\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"update\",\n            \"columnName\": \"update\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"id\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"keyboardAssists\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`type` INTEGER NOT NULL DEFAULT 0, `key` TEXT NOT NULL DEFAULT '', `value` TEXT NOT NULL DEFAULT '', `serialNo` INTEGER NOT NULL DEFAULT 0, PRIMARY KEY(`type`, `key`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"key\",\n            \"columnName\": \"key\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"value\",\n            \"columnName\": \"value\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"serialNo\",\n            \"columnName\": \"serialNo\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"type\",\n            \"key\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      }\n    ],\n    \"views\": [],\n    \"setupQueries\": [\n      \"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)\",\n      \"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '985b7a8fbf3f49e6eb444e4f4472bdee')\"\n    ]\n  }\n}"
  },
  {
    "path": "app/schemas/io.legado.app.data.AppDatabase/48.json",
    "content": "{\n  \"formatVersion\": 1,\n  \"database\": {\n    \"version\": 48,\n    \"identityHash\": \"6348b1307bb9fb4f5482fb94a723338a\",\n    \"entities\": [\n      {\n        \"tableName\": \"books\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookUrl` TEXT NOT NULL DEFAULT '', `tocUrl` TEXT NOT NULL DEFAULT '', `origin` TEXT NOT NULL DEFAULT '', `originName` TEXT NOT NULL DEFAULT '', `name` TEXT NOT NULL DEFAULT '', `author` TEXT NOT NULL DEFAULT '', `kind` TEXT, `customTag` TEXT, `coverUrl` TEXT, `customCoverUrl` TEXT, `intro` TEXT, `customIntro` TEXT, `charset` TEXT, `type` INTEGER NOT NULL DEFAULT 0, `group` INTEGER NOT NULL DEFAULT 0, `latestChapterTitle` TEXT, `latestChapterTime` INTEGER NOT NULL DEFAULT 0, `lastCheckTime` INTEGER NOT NULL DEFAULT 0, `lastCheckCount` INTEGER NOT NULL DEFAULT 0, `totalChapterNum` INTEGER NOT NULL DEFAULT 0, `durChapterTitle` TEXT, `durChapterIndex` INTEGER NOT NULL DEFAULT 0, `durChapterPos` INTEGER NOT NULL DEFAULT 0, `durChapterTime` INTEGER NOT NULL DEFAULT 0, `wordCount` TEXT, `canUpdate` INTEGER NOT NULL DEFAULT 1, `order` INTEGER NOT NULL DEFAULT 0, `originOrder` INTEGER NOT NULL DEFAULT 0, `variable` TEXT, `readConfig` TEXT, PRIMARY KEY(`bookUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"tocUrl\",\n            \"columnName\": \"tocUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"originName\",\n            \"columnName\": \"originName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"author\",\n            \"columnName\": \"author\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"kind\",\n            \"columnName\": \"kind\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customTag\",\n            \"columnName\": \"customTag\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"coverUrl\",\n            \"columnName\": \"coverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customCoverUrl\",\n            \"columnName\": \"customCoverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"intro\",\n            \"columnName\": \"intro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customIntro\",\n            \"columnName\": \"customIntro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"charset\",\n            \"columnName\": \"charset\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"group\",\n            \"columnName\": \"group\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"latestChapterTitle\",\n            \"columnName\": \"latestChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"latestChapterTime\",\n            \"columnName\": \"latestChapterTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"lastCheckTime\",\n            \"columnName\": \"lastCheckTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"lastCheckCount\",\n            \"columnName\": \"lastCheckCount\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"totalChapterNum\",\n            \"columnName\": \"totalChapterNum\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"durChapterTitle\",\n            \"columnName\": \"durChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"durChapterIndex\",\n            \"columnName\": \"durChapterIndex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"durChapterPos\",\n            \"columnName\": \"durChapterPos\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"durChapterTime\",\n            \"columnName\": \"durChapterTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"wordCount\",\n            \"columnName\": \"wordCount\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"canUpdate\",\n            \"columnName\": \"canUpdate\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"originOrder\",\n            \"columnName\": \"originOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"readConfig\",\n            \"columnName\": \"readConfig\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"bookUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_books_name_author\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"name\",\n              \"author\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_books_name_author` ON `${TABLE_NAME}` (`name`, `author`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"book_groups\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`groupId` INTEGER NOT NULL, `groupName` TEXT NOT NULL, `cover` TEXT, `order` INTEGER NOT NULL, `show` INTEGER NOT NULL, PRIMARY KEY(`groupId`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"groupId\",\n            \"columnName\": \"groupId\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"groupName\",\n            \"columnName\": \"groupName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"cover\",\n            \"columnName\": \"cover\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"show\",\n            \"columnName\": \"show\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"groupId\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"book_sources\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookSourceUrl` TEXT NOT NULL, `bookSourceName` TEXT NOT NULL, `bookSourceGroup` TEXT, `bookSourceType` INTEGER NOT NULL, `bookUrlPattern` TEXT, `customOrder` INTEGER NOT NULL, `enabled` INTEGER NOT NULL, `enabledExplore` INTEGER NOT NULL, `concurrentRate` TEXT, `header` TEXT, `loginUrl` TEXT, `loginUi` TEXT, `loginCheckJs` TEXT, `bookSourceComment` TEXT, `lastUpdateTime` INTEGER NOT NULL, `respondTime` INTEGER NOT NULL, `weight` INTEGER NOT NULL, `exploreUrl` TEXT, `ruleExplore` TEXT, `searchUrl` TEXT, `ruleSearch` TEXT, `ruleBookInfo` TEXT, `ruleToc` TEXT, `ruleContent` TEXT, PRIMARY KEY(`bookSourceUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookSourceUrl\",\n            \"columnName\": \"bookSourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookSourceName\",\n            \"columnName\": \"bookSourceName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookSourceGroup\",\n            \"columnName\": \"bookSourceGroup\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"bookSourceType\",\n            \"columnName\": \"bookSourceType\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookUrlPattern\",\n            \"columnName\": \"bookUrlPattern\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enabled\",\n            \"columnName\": \"enabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enabledExplore\",\n            \"columnName\": \"enabledExplore\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"concurrentRate\",\n            \"columnName\": \"concurrentRate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"header\",\n            \"columnName\": \"header\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUrl\",\n            \"columnName\": \"loginUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUi\",\n            \"columnName\": \"loginUi\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginCheckJs\",\n            \"columnName\": \"loginCheckJs\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"bookSourceComment\",\n            \"columnName\": \"bookSourceComment\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"lastUpdateTime\",\n            \"columnName\": \"lastUpdateTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"respondTime\",\n            \"columnName\": \"respondTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"weight\",\n            \"columnName\": \"weight\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"exploreUrl\",\n            \"columnName\": \"exploreUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleExplore\",\n            \"columnName\": \"ruleExplore\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"searchUrl\",\n            \"columnName\": \"searchUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleSearch\",\n            \"columnName\": \"ruleSearch\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleBookInfo\",\n            \"columnName\": \"ruleBookInfo\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleToc\",\n            \"columnName\": \"ruleToc\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleContent\",\n            \"columnName\": \"ruleContent\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"bookSourceUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_book_sources_bookSourceUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"bookSourceUrl\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_book_sources_bookSourceUrl` ON `${TABLE_NAME}` (`bookSourceUrl`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"chapters\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`url` TEXT NOT NULL, `title` TEXT NOT NULL, `isVolume` INTEGER NOT NULL, `baseUrl` TEXT NOT NULL, `bookUrl` TEXT NOT NULL, `index` INTEGER NOT NULL, `isVip` INTEGER NOT NULL, `isPay` INTEGER NOT NULL, `resourceUrl` TEXT, `tag` TEXT, `start` INTEGER, `end` INTEGER, `startFragmentId` TEXT, `endFragmentId` TEXT, `variable` TEXT, PRIMARY KEY(`url`, `bookUrl`), FOREIGN KEY(`bookUrl`) REFERENCES `books`(`bookUrl`) ON UPDATE NO ACTION ON DELETE CASCADE )\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"isVolume\",\n            \"columnName\": \"isVolume\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"baseUrl\",\n            \"columnName\": \"baseUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"index\",\n            \"columnName\": \"index\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"isVip\",\n            \"columnName\": \"isVip\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"isPay\",\n            \"columnName\": \"isPay\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"resourceUrl\",\n            \"columnName\": \"resourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"tag\",\n            \"columnName\": \"tag\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"start\",\n            \"columnName\": \"start\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"end\",\n            \"columnName\": \"end\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"startFragmentId\",\n            \"columnName\": \"startFragmentId\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"endFragmentId\",\n            \"columnName\": \"endFragmentId\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"url\",\n            \"bookUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_chapters_bookUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"bookUrl\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_chapters_bookUrl` ON `${TABLE_NAME}` (`bookUrl`)\"\n          },\n          {\n            \"name\": \"index_chapters_bookUrl_index\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"bookUrl\",\n              \"index\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_chapters_bookUrl_index` ON `${TABLE_NAME}` (`bookUrl`, `index`)\"\n          }\n        ],\n        \"foreignKeys\": [\n          {\n            \"table\": \"books\",\n            \"onDelete\": \"CASCADE\",\n            \"onUpdate\": \"NO ACTION\",\n            \"columns\": [\n              \"bookUrl\"\n            ],\n            \"referencedColumns\": [\n              \"bookUrl\"\n            ]\n          }\n        ]\n      },\n      {\n        \"tableName\": \"replace_rules\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL DEFAULT '', `group` TEXT, `pattern` TEXT NOT NULL DEFAULT '', `replacement` TEXT NOT NULL DEFAULT '', `scope` TEXT, `scopeTitle` INTEGER NOT NULL DEFAULT 0, `scopeContent` INTEGER NOT NULL DEFAULT 1, `isEnabled` INTEGER NOT NULL DEFAULT 1, `isRegex` INTEGER NOT NULL DEFAULT 1, `timeoutMillisecond` INTEGER NOT NULL DEFAULT 3000, `sortOrder` INTEGER NOT NULL DEFAULT 0)\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"group\",\n            \"columnName\": \"group\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"pattern\",\n            \"columnName\": \"pattern\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"replacement\",\n            \"columnName\": \"replacement\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"scope\",\n            \"columnName\": \"scope\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"scopeTitle\",\n            \"columnName\": \"scopeTitle\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"scopeContent\",\n            \"columnName\": \"scopeContent\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"isEnabled\",\n            \"columnName\": \"isEnabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"isRegex\",\n            \"columnName\": \"isRegex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"timeoutMillisecond\",\n            \"columnName\": \"timeoutMillisecond\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"3000\"\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"sortOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"id\"\n          ],\n          \"autoGenerate\": true\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_replace_rules_id\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"id\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_replace_rules_id` ON `${TABLE_NAME}` (`id`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"searchBooks\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookUrl` TEXT NOT NULL, `origin` TEXT NOT NULL, `originName` TEXT NOT NULL, `type` INTEGER NOT NULL, `name` TEXT NOT NULL, `author` TEXT NOT NULL, `kind` TEXT, `coverUrl` TEXT, `intro` TEXT, `wordCount` TEXT, `latestChapterTitle` TEXT, `tocUrl` TEXT NOT NULL, `time` INTEGER NOT NULL, `variable` TEXT, `originOrder` INTEGER NOT NULL, PRIMARY KEY(`bookUrl`), FOREIGN KEY(`origin`) REFERENCES `book_sources`(`bookSourceUrl`) ON UPDATE NO ACTION ON DELETE CASCADE )\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"originName\",\n            \"columnName\": \"originName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"author\",\n            \"columnName\": \"author\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"kind\",\n            \"columnName\": \"kind\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"coverUrl\",\n            \"columnName\": \"coverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"intro\",\n            \"columnName\": \"intro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"wordCount\",\n            \"columnName\": \"wordCount\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"latestChapterTitle\",\n            \"columnName\": \"latestChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"tocUrl\",\n            \"columnName\": \"tocUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"time\",\n            \"columnName\": \"time\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"originOrder\",\n            \"columnName\": \"originOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"bookUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_searchBooks_bookUrl\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"bookUrl\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_searchBooks_bookUrl` ON `${TABLE_NAME}` (`bookUrl`)\"\n          },\n          {\n            \"name\": \"index_searchBooks_origin\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"origin\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_searchBooks_origin` ON `${TABLE_NAME}` (`origin`)\"\n          }\n        ],\n        \"foreignKeys\": [\n          {\n            \"table\": \"book_sources\",\n            \"onDelete\": \"CASCADE\",\n            \"onUpdate\": \"NO ACTION\",\n            \"columns\": [\n              \"origin\"\n            ],\n            \"referencedColumns\": [\n              \"bookSourceUrl\"\n            ]\n          }\n        ]\n      },\n      {\n        \"tableName\": \"search_keywords\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`word` TEXT NOT NULL, `usage` INTEGER NOT NULL, `lastUseTime` INTEGER NOT NULL, PRIMARY KEY(`word`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"word\",\n            \"columnName\": \"word\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"usage\",\n            \"columnName\": \"usage\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"lastUseTime\",\n            \"columnName\": \"lastUseTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"word\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_search_keywords_word\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"word\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_search_keywords_word` ON `${TABLE_NAME}` (`word`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"cookies\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`url` TEXT NOT NULL, `cookie` TEXT NOT NULL, PRIMARY KEY(`url`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"cookie\",\n            \"columnName\": \"cookie\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"url\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_cookies_url\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"url\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_cookies_url` ON `${TABLE_NAME}` (`url`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssSources\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`sourceUrl` TEXT NOT NULL, `sourceName` TEXT NOT NULL, `sourceIcon` TEXT NOT NULL, `sourceGroup` TEXT, `sourceComment` TEXT, `enabled` INTEGER NOT NULL, `concurrentRate` TEXT, `header` TEXT, `loginUrl` TEXT, `loginUi` TEXT, `loginCheckJs` TEXT, `sortUrl` TEXT, `singleUrl` INTEGER NOT NULL, `articleStyle` INTEGER NOT NULL, `ruleArticles` TEXT, `ruleNextPage` TEXT, `ruleTitle` TEXT, `rulePubDate` TEXT, `ruleDescription` TEXT, `ruleImage` TEXT, `ruleLink` TEXT, `ruleContent` TEXT, `style` TEXT, `enableJs` INTEGER NOT NULL, `loadWithBaseUrl` INTEGER NOT NULL, `customOrder` INTEGER NOT NULL, PRIMARY KEY(`sourceUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"sourceUrl\",\n            \"columnName\": \"sourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceName\",\n            \"columnName\": \"sourceName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceIcon\",\n            \"columnName\": \"sourceIcon\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceGroup\",\n            \"columnName\": \"sourceGroup\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"sourceComment\",\n            \"columnName\": \"sourceComment\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enabled\",\n            \"columnName\": \"enabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"concurrentRate\",\n            \"columnName\": \"concurrentRate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"header\",\n            \"columnName\": \"header\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUrl\",\n            \"columnName\": \"loginUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUi\",\n            \"columnName\": \"loginUi\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginCheckJs\",\n            \"columnName\": \"loginCheckJs\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"sortUrl\",\n            \"columnName\": \"sortUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"singleUrl\",\n            \"columnName\": \"singleUrl\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"articleStyle\",\n            \"columnName\": \"articleStyle\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"ruleArticles\",\n            \"columnName\": \"ruleArticles\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleNextPage\",\n            \"columnName\": \"ruleNextPage\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleTitle\",\n            \"columnName\": \"ruleTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"rulePubDate\",\n            \"columnName\": \"rulePubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleDescription\",\n            \"columnName\": \"ruleDescription\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleImage\",\n            \"columnName\": \"ruleImage\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleLink\",\n            \"columnName\": \"ruleLink\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleContent\",\n            \"columnName\": \"ruleContent\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"style\",\n            \"columnName\": \"style\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enableJs\",\n            \"columnName\": \"enableJs\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"loadWithBaseUrl\",\n            \"columnName\": \"loadWithBaseUrl\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"sourceUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_rssSources_sourceUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"sourceUrl\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_rssSources_sourceUrl` ON `${TABLE_NAME}` (`sourceUrl`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"bookmarks\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`time` INTEGER NOT NULL, `bookName` TEXT NOT NULL, `bookAuthor` TEXT NOT NULL, `chapterIndex` INTEGER NOT NULL, `chapterPos` INTEGER NOT NULL, `chapterName` TEXT NOT NULL, `bookText` TEXT NOT NULL, `content` TEXT NOT NULL, PRIMARY KEY(`time`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"time\",\n            \"columnName\": \"time\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookName\",\n            \"columnName\": \"bookName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookAuthor\",\n            \"columnName\": \"bookAuthor\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterIndex\",\n            \"columnName\": \"chapterIndex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterPos\",\n            \"columnName\": \"chapterPos\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterName\",\n            \"columnName\": \"chapterName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookText\",\n            \"columnName\": \"bookText\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"time\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_bookmarks_bookName_bookAuthor\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"bookName\",\n              \"bookAuthor\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_bookmarks_bookName_bookAuthor` ON `${TABLE_NAME}` (`bookName`, `bookAuthor`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssArticles\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`origin` TEXT NOT NULL, `sort` TEXT NOT NULL, `title` TEXT NOT NULL, `order` INTEGER NOT NULL, `link` TEXT NOT NULL, `pubDate` TEXT, `description` TEXT, `content` TEXT, `image` TEXT, `read` INTEGER NOT NULL, `variable` TEXT, PRIMARY KEY(`origin`, `link`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sort\",\n            \"columnName\": \"sort\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"link\",\n            \"columnName\": \"link\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"pubDate\",\n            \"columnName\": \"pubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"description\",\n            \"columnName\": \"description\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"image\",\n            \"columnName\": \"image\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"read\",\n            \"columnName\": \"read\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"origin\",\n            \"link\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssReadRecords\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`record` TEXT NOT NULL, `read` INTEGER NOT NULL, PRIMARY KEY(`record`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"record\",\n            \"columnName\": \"record\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"read\",\n            \"columnName\": \"read\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"record\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssStars\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`origin` TEXT NOT NULL, `sort` TEXT NOT NULL, `title` TEXT NOT NULL, `starTime` INTEGER NOT NULL, `link` TEXT NOT NULL, `pubDate` TEXT, `description` TEXT, `content` TEXT, `image` TEXT, `variable` TEXT, PRIMARY KEY(`origin`, `link`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sort\",\n            \"columnName\": \"sort\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"starTime\",\n            \"columnName\": \"starTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"link\",\n            \"columnName\": \"link\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"pubDate\",\n            \"columnName\": \"pubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"description\",\n            \"columnName\": \"description\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"image\",\n            \"columnName\": \"image\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"origin\",\n            \"link\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"txtTocRules\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `rule` TEXT NOT NULL, `serialNumber` INTEGER NOT NULL, `enable` INTEGER NOT NULL, PRIMARY KEY(`id`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"rule\",\n            \"columnName\": \"rule\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"serialNumber\",\n            \"columnName\": \"serialNumber\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enable\",\n            \"columnName\": \"enable\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"id\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"readRecord\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`deviceId` TEXT NOT NULL, `bookName` TEXT NOT NULL, `readTime` INTEGER NOT NULL, PRIMARY KEY(`deviceId`, `bookName`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"deviceId\",\n            \"columnName\": \"deviceId\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookName\",\n            \"columnName\": \"bookName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"readTime\",\n            \"columnName\": \"readTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"deviceId\",\n            \"bookName\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"httpTTS\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `url` TEXT NOT NULL, `contentType` TEXT, `concurrentRate` TEXT DEFAULT '0', `loginUrl` TEXT, `loginUi` TEXT, `header` TEXT, `loginCheckJs` TEXT, `lastUpdateTime` INTEGER NOT NULL DEFAULT 0, PRIMARY KEY(`id`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"contentType\",\n            \"columnName\": \"contentType\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"concurrentRate\",\n            \"columnName\": \"concurrentRate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false,\n            \"defaultValue\": \"'0'\"\n          },\n          {\n            \"fieldPath\": \"loginUrl\",\n            \"columnName\": \"loginUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUi\",\n            \"columnName\": \"loginUi\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"header\",\n            \"columnName\": \"header\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginCheckJs\",\n            \"columnName\": \"loginCheckJs\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"lastUpdateTime\",\n            \"columnName\": \"lastUpdateTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"id\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"caches\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`key` TEXT NOT NULL, `value` TEXT, `deadline` INTEGER NOT NULL, PRIMARY KEY(`key`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"key\",\n            \"columnName\": \"key\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"value\",\n            \"columnName\": \"value\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"deadline\",\n            \"columnName\": \"deadline\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"key\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_caches_key\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"key\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_caches_key` ON `${TABLE_NAME}` (`key`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"ruleSubs\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `url` TEXT NOT NULL, `type` INTEGER NOT NULL, `customOrder` INTEGER NOT NULL, `autoUpdate` INTEGER NOT NULL, `update` INTEGER NOT NULL, PRIMARY KEY(`id`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"autoUpdate\",\n            \"columnName\": \"autoUpdate\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"update\",\n            \"columnName\": \"update\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"id\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"keyboardAssists\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`type` INTEGER NOT NULL DEFAULT 0, `key` TEXT NOT NULL DEFAULT '', `value` TEXT NOT NULL DEFAULT '', `serialNo` INTEGER NOT NULL DEFAULT 0, PRIMARY KEY(`type`, `key`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"key\",\n            \"columnName\": \"key\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"value\",\n            \"columnName\": \"value\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"serialNo\",\n            \"columnName\": \"serialNo\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"type\",\n            \"key\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      }\n    ],\n    \"views\": [],\n    \"setupQueries\": [\n      \"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)\",\n      \"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '6348b1307bb9fb4f5482fb94a723338a')\"\n    ]\n  }\n}"
  },
  {
    "path": "app/schemas/io.legado.app.data.AppDatabase/49.json",
    "content": "{\n  \"formatVersion\": 1,\n  \"database\": {\n    \"version\": 49,\n    \"identityHash\": \"2a6f5ee3d0ed9ac13f15183a04a4af45\",\n    \"entities\": [\n      {\n        \"tableName\": \"books\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookUrl` TEXT NOT NULL DEFAULT '', `tocUrl` TEXT NOT NULL DEFAULT '', `origin` TEXT NOT NULL DEFAULT '', `originName` TEXT NOT NULL DEFAULT '', `name` TEXT NOT NULL DEFAULT '', `author` TEXT NOT NULL DEFAULT '', `kind` TEXT, `customTag` TEXT, `coverUrl` TEXT, `customCoverUrl` TEXT, `intro` TEXT, `customIntro` TEXT, `charset` TEXT, `type` INTEGER NOT NULL DEFAULT 0, `group` INTEGER NOT NULL DEFAULT 0, `latestChapterTitle` TEXT, `latestChapterTime` INTEGER NOT NULL DEFAULT 0, `lastCheckTime` INTEGER NOT NULL DEFAULT 0, `lastCheckCount` INTEGER NOT NULL DEFAULT 0, `totalChapterNum` INTEGER NOT NULL DEFAULT 0, `durChapterTitle` TEXT, `durChapterIndex` INTEGER NOT NULL DEFAULT 0, `durChapterPos` INTEGER NOT NULL DEFAULT 0, `durChapterTime` INTEGER NOT NULL DEFAULT 0, `wordCount` TEXT, `canUpdate` INTEGER NOT NULL DEFAULT 1, `order` INTEGER NOT NULL DEFAULT 0, `originOrder` INTEGER NOT NULL DEFAULT 0, `variable` TEXT, `readConfig` TEXT, PRIMARY KEY(`bookUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"tocUrl\",\n            \"columnName\": \"tocUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"originName\",\n            \"columnName\": \"originName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"author\",\n            \"columnName\": \"author\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"kind\",\n            \"columnName\": \"kind\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customTag\",\n            \"columnName\": \"customTag\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"coverUrl\",\n            \"columnName\": \"coverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customCoverUrl\",\n            \"columnName\": \"customCoverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"intro\",\n            \"columnName\": \"intro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customIntro\",\n            \"columnName\": \"customIntro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"charset\",\n            \"columnName\": \"charset\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"group\",\n            \"columnName\": \"group\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"latestChapterTitle\",\n            \"columnName\": \"latestChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"latestChapterTime\",\n            \"columnName\": \"latestChapterTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"lastCheckTime\",\n            \"columnName\": \"lastCheckTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"lastCheckCount\",\n            \"columnName\": \"lastCheckCount\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"totalChapterNum\",\n            \"columnName\": \"totalChapterNum\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"durChapterTitle\",\n            \"columnName\": \"durChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"durChapterIndex\",\n            \"columnName\": \"durChapterIndex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"durChapterPos\",\n            \"columnName\": \"durChapterPos\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"durChapterTime\",\n            \"columnName\": \"durChapterTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"wordCount\",\n            \"columnName\": \"wordCount\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"canUpdate\",\n            \"columnName\": \"canUpdate\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"originOrder\",\n            \"columnName\": \"originOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"readConfig\",\n            \"columnName\": \"readConfig\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"bookUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_books_name_author\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"name\",\n              \"author\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_books_name_author` ON `${TABLE_NAME}` (`name`, `author`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"book_groups\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`groupId` INTEGER NOT NULL, `groupName` TEXT NOT NULL, `cover` TEXT, `order` INTEGER NOT NULL, `show` INTEGER NOT NULL, PRIMARY KEY(`groupId`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"groupId\",\n            \"columnName\": \"groupId\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"groupName\",\n            \"columnName\": \"groupName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"cover\",\n            \"columnName\": \"cover\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"show\",\n            \"columnName\": \"show\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"groupId\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"book_sources\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookSourceUrl` TEXT NOT NULL, `bookSourceName` TEXT NOT NULL, `bookSourceGroup` TEXT, `bookSourceType` INTEGER NOT NULL, `bookUrlPattern` TEXT, `customOrder` INTEGER NOT NULL, `enabled` INTEGER NOT NULL, `enabledExplore` INTEGER NOT NULL, `enabledCookieJar` INTEGER DEFAULT 0, `concurrentRate` TEXT, `header` TEXT, `loginUrl` TEXT, `loginUi` TEXT, `loginCheckJs` TEXT, `bookSourceComment` TEXT, `lastUpdateTime` INTEGER NOT NULL, `respondTime` INTEGER NOT NULL, `weight` INTEGER NOT NULL, `exploreUrl` TEXT, `ruleExplore` TEXT, `searchUrl` TEXT, `ruleSearch` TEXT, `ruleBookInfo` TEXT, `ruleToc` TEXT, `ruleContent` TEXT, PRIMARY KEY(`bookSourceUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookSourceUrl\",\n            \"columnName\": \"bookSourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookSourceName\",\n            \"columnName\": \"bookSourceName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookSourceGroup\",\n            \"columnName\": \"bookSourceGroup\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"bookSourceType\",\n            \"columnName\": \"bookSourceType\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookUrlPattern\",\n            \"columnName\": \"bookUrlPattern\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enabled\",\n            \"columnName\": \"enabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enabledExplore\",\n            \"columnName\": \"enabledExplore\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enabledCookieJar\",\n            \"columnName\": \"enabledCookieJar\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"concurrentRate\",\n            \"columnName\": \"concurrentRate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"header\",\n            \"columnName\": \"header\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUrl\",\n            \"columnName\": \"loginUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUi\",\n            \"columnName\": \"loginUi\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginCheckJs\",\n            \"columnName\": \"loginCheckJs\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"bookSourceComment\",\n            \"columnName\": \"bookSourceComment\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"lastUpdateTime\",\n            \"columnName\": \"lastUpdateTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"respondTime\",\n            \"columnName\": \"respondTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"weight\",\n            \"columnName\": \"weight\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"exploreUrl\",\n            \"columnName\": \"exploreUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleExplore\",\n            \"columnName\": \"ruleExplore\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"searchUrl\",\n            \"columnName\": \"searchUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleSearch\",\n            \"columnName\": \"ruleSearch\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleBookInfo\",\n            \"columnName\": \"ruleBookInfo\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleToc\",\n            \"columnName\": \"ruleToc\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleContent\",\n            \"columnName\": \"ruleContent\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"bookSourceUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_book_sources_bookSourceUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"bookSourceUrl\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_book_sources_bookSourceUrl` ON `${TABLE_NAME}` (`bookSourceUrl`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"chapters\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`url` TEXT NOT NULL, `title` TEXT NOT NULL, `isVolume` INTEGER NOT NULL, `baseUrl` TEXT NOT NULL, `bookUrl` TEXT NOT NULL, `index` INTEGER NOT NULL, `isVip` INTEGER NOT NULL, `isPay` INTEGER NOT NULL, `resourceUrl` TEXT, `tag` TEXT, `start` INTEGER, `end` INTEGER, `startFragmentId` TEXT, `endFragmentId` TEXT, `variable` TEXT, PRIMARY KEY(`url`, `bookUrl`), FOREIGN KEY(`bookUrl`) REFERENCES `books`(`bookUrl`) ON UPDATE NO ACTION ON DELETE CASCADE )\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"isVolume\",\n            \"columnName\": \"isVolume\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"baseUrl\",\n            \"columnName\": \"baseUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"index\",\n            \"columnName\": \"index\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"isVip\",\n            \"columnName\": \"isVip\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"isPay\",\n            \"columnName\": \"isPay\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"resourceUrl\",\n            \"columnName\": \"resourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"tag\",\n            \"columnName\": \"tag\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"start\",\n            \"columnName\": \"start\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"end\",\n            \"columnName\": \"end\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"startFragmentId\",\n            \"columnName\": \"startFragmentId\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"endFragmentId\",\n            \"columnName\": \"endFragmentId\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"url\",\n            \"bookUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_chapters_bookUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"bookUrl\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_chapters_bookUrl` ON `${TABLE_NAME}` (`bookUrl`)\"\n          },\n          {\n            \"name\": \"index_chapters_bookUrl_index\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"bookUrl\",\n              \"index\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_chapters_bookUrl_index` ON `${TABLE_NAME}` (`bookUrl`, `index`)\"\n          }\n        ],\n        \"foreignKeys\": [\n          {\n            \"table\": \"books\",\n            \"onDelete\": \"CASCADE\",\n            \"onUpdate\": \"NO ACTION\",\n            \"columns\": [\n              \"bookUrl\"\n            ],\n            \"referencedColumns\": [\n              \"bookUrl\"\n            ]\n          }\n        ]\n      },\n      {\n        \"tableName\": \"replace_rules\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL DEFAULT '', `group` TEXT, `pattern` TEXT NOT NULL DEFAULT '', `replacement` TEXT NOT NULL DEFAULT '', `scope` TEXT, `scopeTitle` INTEGER NOT NULL DEFAULT 0, `scopeContent` INTEGER NOT NULL DEFAULT 1, `isEnabled` INTEGER NOT NULL DEFAULT 1, `isRegex` INTEGER NOT NULL DEFAULT 1, `timeoutMillisecond` INTEGER NOT NULL DEFAULT 3000, `sortOrder` INTEGER NOT NULL DEFAULT 0)\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"group\",\n            \"columnName\": \"group\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"pattern\",\n            \"columnName\": \"pattern\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"replacement\",\n            \"columnName\": \"replacement\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"scope\",\n            \"columnName\": \"scope\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"scopeTitle\",\n            \"columnName\": \"scopeTitle\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"scopeContent\",\n            \"columnName\": \"scopeContent\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"isEnabled\",\n            \"columnName\": \"isEnabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"isRegex\",\n            \"columnName\": \"isRegex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"timeoutMillisecond\",\n            \"columnName\": \"timeoutMillisecond\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"3000\"\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"sortOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"id\"\n          ],\n          \"autoGenerate\": true\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_replace_rules_id\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"id\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_replace_rules_id` ON `${TABLE_NAME}` (`id`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"searchBooks\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookUrl` TEXT NOT NULL, `origin` TEXT NOT NULL, `originName` TEXT NOT NULL, `type` INTEGER NOT NULL, `name` TEXT NOT NULL, `author` TEXT NOT NULL, `kind` TEXT, `coverUrl` TEXT, `intro` TEXT, `wordCount` TEXT, `latestChapterTitle` TEXT, `tocUrl` TEXT NOT NULL, `time` INTEGER NOT NULL, `variable` TEXT, `originOrder` INTEGER NOT NULL, PRIMARY KEY(`bookUrl`), FOREIGN KEY(`origin`) REFERENCES `book_sources`(`bookSourceUrl`) ON UPDATE NO ACTION ON DELETE CASCADE )\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"originName\",\n            \"columnName\": \"originName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"author\",\n            \"columnName\": \"author\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"kind\",\n            \"columnName\": \"kind\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"coverUrl\",\n            \"columnName\": \"coverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"intro\",\n            \"columnName\": \"intro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"wordCount\",\n            \"columnName\": \"wordCount\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"latestChapterTitle\",\n            \"columnName\": \"latestChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"tocUrl\",\n            \"columnName\": \"tocUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"time\",\n            \"columnName\": \"time\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"originOrder\",\n            \"columnName\": \"originOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"bookUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_searchBooks_bookUrl\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"bookUrl\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_searchBooks_bookUrl` ON `${TABLE_NAME}` (`bookUrl`)\"\n          },\n          {\n            \"name\": \"index_searchBooks_origin\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"origin\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_searchBooks_origin` ON `${TABLE_NAME}` (`origin`)\"\n          }\n        ],\n        \"foreignKeys\": [\n          {\n            \"table\": \"book_sources\",\n            \"onDelete\": \"CASCADE\",\n            \"onUpdate\": \"NO ACTION\",\n            \"columns\": [\n              \"origin\"\n            ],\n            \"referencedColumns\": [\n              \"bookSourceUrl\"\n            ]\n          }\n        ]\n      },\n      {\n        \"tableName\": \"search_keywords\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`word` TEXT NOT NULL, `usage` INTEGER NOT NULL, `lastUseTime` INTEGER NOT NULL, PRIMARY KEY(`word`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"word\",\n            \"columnName\": \"word\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"usage\",\n            \"columnName\": \"usage\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"lastUseTime\",\n            \"columnName\": \"lastUseTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"word\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_search_keywords_word\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"word\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_search_keywords_word` ON `${TABLE_NAME}` (`word`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"cookies\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`url` TEXT NOT NULL, `cookie` TEXT NOT NULL, PRIMARY KEY(`url`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"cookie\",\n            \"columnName\": \"cookie\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"url\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_cookies_url\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"url\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_cookies_url` ON `${TABLE_NAME}` (`url`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssSources\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`sourceUrl` TEXT NOT NULL, `sourceName` TEXT NOT NULL, `sourceIcon` TEXT NOT NULL, `sourceGroup` TEXT, `sourceComment` TEXT, `enabled` INTEGER NOT NULL, `enabledCookieJar` INTEGER DEFAULT 0, `concurrentRate` TEXT, `header` TEXT, `loginUrl` TEXT, `loginUi` TEXT, `loginCheckJs` TEXT, `sortUrl` TEXT, `singleUrl` INTEGER NOT NULL, `articleStyle` INTEGER NOT NULL, `ruleArticles` TEXT, `ruleNextPage` TEXT, `ruleTitle` TEXT, `rulePubDate` TEXT, `ruleDescription` TEXT, `ruleImage` TEXT, `ruleLink` TEXT, `ruleContent` TEXT, `style` TEXT, `enableJs` INTEGER NOT NULL, `loadWithBaseUrl` INTEGER NOT NULL, `customOrder` INTEGER NOT NULL, PRIMARY KEY(`sourceUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"sourceUrl\",\n            \"columnName\": \"sourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceName\",\n            \"columnName\": \"sourceName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceIcon\",\n            \"columnName\": \"sourceIcon\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceGroup\",\n            \"columnName\": \"sourceGroup\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"sourceComment\",\n            \"columnName\": \"sourceComment\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enabled\",\n            \"columnName\": \"enabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enabledCookieJar\",\n            \"columnName\": \"enabledCookieJar\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"concurrentRate\",\n            \"columnName\": \"concurrentRate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"header\",\n            \"columnName\": \"header\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUrl\",\n            \"columnName\": \"loginUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUi\",\n            \"columnName\": \"loginUi\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginCheckJs\",\n            \"columnName\": \"loginCheckJs\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"sortUrl\",\n            \"columnName\": \"sortUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"singleUrl\",\n            \"columnName\": \"singleUrl\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"articleStyle\",\n            \"columnName\": \"articleStyle\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"ruleArticles\",\n            \"columnName\": \"ruleArticles\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleNextPage\",\n            \"columnName\": \"ruleNextPage\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleTitle\",\n            \"columnName\": \"ruleTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"rulePubDate\",\n            \"columnName\": \"rulePubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleDescription\",\n            \"columnName\": \"ruleDescription\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleImage\",\n            \"columnName\": \"ruleImage\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleLink\",\n            \"columnName\": \"ruleLink\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleContent\",\n            \"columnName\": \"ruleContent\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"style\",\n            \"columnName\": \"style\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enableJs\",\n            \"columnName\": \"enableJs\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"loadWithBaseUrl\",\n            \"columnName\": \"loadWithBaseUrl\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"sourceUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_rssSources_sourceUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"sourceUrl\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_rssSources_sourceUrl` ON `${TABLE_NAME}` (`sourceUrl`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"bookmarks\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`time` INTEGER NOT NULL, `bookName` TEXT NOT NULL, `bookAuthor` TEXT NOT NULL, `chapterIndex` INTEGER NOT NULL, `chapterPos` INTEGER NOT NULL, `chapterName` TEXT NOT NULL, `bookText` TEXT NOT NULL, `content` TEXT NOT NULL, PRIMARY KEY(`time`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"time\",\n            \"columnName\": \"time\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookName\",\n            \"columnName\": \"bookName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookAuthor\",\n            \"columnName\": \"bookAuthor\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterIndex\",\n            \"columnName\": \"chapterIndex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterPos\",\n            \"columnName\": \"chapterPos\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterName\",\n            \"columnName\": \"chapterName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookText\",\n            \"columnName\": \"bookText\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"time\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_bookmarks_bookName_bookAuthor\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"bookName\",\n              \"bookAuthor\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_bookmarks_bookName_bookAuthor` ON `${TABLE_NAME}` (`bookName`, `bookAuthor`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssArticles\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`origin` TEXT NOT NULL, `sort` TEXT NOT NULL, `title` TEXT NOT NULL, `order` INTEGER NOT NULL, `link` TEXT NOT NULL, `pubDate` TEXT, `description` TEXT, `content` TEXT, `image` TEXT, `read` INTEGER NOT NULL, `variable` TEXT, PRIMARY KEY(`origin`, `link`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sort\",\n            \"columnName\": \"sort\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"link\",\n            \"columnName\": \"link\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"pubDate\",\n            \"columnName\": \"pubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"description\",\n            \"columnName\": \"description\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"image\",\n            \"columnName\": \"image\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"read\",\n            \"columnName\": \"read\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"origin\",\n            \"link\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssReadRecords\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`record` TEXT NOT NULL, `read` INTEGER NOT NULL, PRIMARY KEY(`record`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"record\",\n            \"columnName\": \"record\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"read\",\n            \"columnName\": \"read\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"record\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssStars\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`origin` TEXT NOT NULL, `sort` TEXT NOT NULL, `title` TEXT NOT NULL, `starTime` INTEGER NOT NULL, `link` TEXT NOT NULL, `pubDate` TEXT, `description` TEXT, `content` TEXT, `image` TEXT, `variable` TEXT, PRIMARY KEY(`origin`, `link`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sort\",\n            \"columnName\": \"sort\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"starTime\",\n            \"columnName\": \"starTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"link\",\n            \"columnName\": \"link\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"pubDate\",\n            \"columnName\": \"pubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"description\",\n            \"columnName\": \"description\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"image\",\n            \"columnName\": \"image\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"origin\",\n            \"link\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"txtTocRules\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `rule` TEXT NOT NULL, `serialNumber` INTEGER NOT NULL, `enable` INTEGER NOT NULL, PRIMARY KEY(`id`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"rule\",\n            \"columnName\": \"rule\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"serialNumber\",\n            \"columnName\": \"serialNumber\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enable\",\n            \"columnName\": \"enable\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"id\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"readRecord\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`deviceId` TEXT NOT NULL, `bookName` TEXT NOT NULL, `readTime` INTEGER NOT NULL, PRIMARY KEY(`deviceId`, `bookName`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"deviceId\",\n            \"columnName\": \"deviceId\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookName\",\n            \"columnName\": \"bookName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"readTime\",\n            \"columnName\": \"readTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"deviceId\",\n            \"bookName\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"httpTTS\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `url` TEXT NOT NULL, `contentType` TEXT, `concurrentRate` TEXT DEFAULT '0', `loginUrl` TEXT, `loginUi` TEXT, `header` TEXT, `enabledCookieJar` INTEGER DEFAULT 0, `loginCheckJs` TEXT, `lastUpdateTime` INTEGER NOT NULL DEFAULT 0, PRIMARY KEY(`id`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"contentType\",\n            \"columnName\": \"contentType\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"concurrentRate\",\n            \"columnName\": \"concurrentRate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false,\n            \"defaultValue\": \"'0'\"\n          },\n          {\n            \"fieldPath\": \"loginUrl\",\n            \"columnName\": \"loginUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUi\",\n            \"columnName\": \"loginUi\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"header\",\n            \"columnName\": \"header\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enabledCookieJar\",\n            \"columnName\": \"enabledCookieJar\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"loginCheckJs\",\n            \"columnName\": \"loginCheckJs\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"lastUpdateTime\",\n            \"columnName\": \"lastUpdateTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"id\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"caches\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`key` TEXT NOT NULL, `value` TEXT, `deadline` INTEGER NOT NULL, PRIMARY KEY(`key`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"key\",\n            \"columnName\": \"key\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"value\",\n            \"columnName\": \"value\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"deadline\",\n            \"columnName\": \"deadline\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"key\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_caches_key\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"key\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_caches_key` ON `${TABLE_NAME}` (`key`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"ruleSubs\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `url` TEXT NOT NULL, `type` INTEGER NOT NULL, `customOrder` INTEGER NOT NULL, `autoUpdate` INTEGER NOT NULL, `update` INTEGER NOT NULL, PRIMARY KEY(`id`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"autoUpdate\",\n            \"columnName\": \"autoUpdate\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"update\",\n            \"columnName\": \"update\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"id\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"keyboardAssists\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`type` INTEGER NOT NULL DEFAULT 0, `key` TEXT NOT NULL DEFAULT '', `value` TEXT NOT NULL DEFAULT '', `serialNo` INTEGER NOT NULL DEFAULT 0, PRIMARY KEY(`type`, `key`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"key\",\n            \"columnName\": \"key\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"value\",\n            \"columnName\": \"value\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"serialNo\",\n            \"columnName\": \"serialNo\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"type\",\n            \"key\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      }\n    ],\n    \"views\": [],\n    \"setupQueries\": [\n      \"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)\",\n      \"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '2a6f5ee3d0ed9ac13f15183a04a4af45')\"\n    ]\n  }\n}"
  },
  {
    "path": "app/schemas/io.legado.app.data.AppDatabase/5.json",
    "content": "{\n  \"formatVersion\": 1,\n  \"database\": {\n    \"version\": 5,\n    \"identityHash\": \"a355f8e02ebe0b13464573b1420a7b90\",\n    \"entities\": [\n      {\n        \"tableName\": \"books\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookUrl` TEXT NOT NULL, `tocUrl` TEXT NOT NULL, `origin` TEXT NOT NULL, `originName` TEXT NOT NULL, `name` TEXT NOT NULL, `author` TEXT NOT NULL, `kind` TEXT, `customTag` TEXT, `coverUrl` TEXT, `customCoverUrl` TEXT, `intro` TEXT, `customIntro` TEXT, `charset` TEXT, `type` INTEGER NOT NULL, `group` INTEGER NOT NULL, `latestChapterTitle` TEXT, `latestChapterTime` INTEGER NOT NULL, `lastCheckTime` INTEGER NOT NULL, `lastCheckCount` INTEGER NOT NULL, `totalChapterNum` INTEGER NOT NULL, `durChapterTitle` TEXT, `durChapterIndex` INTEGER NOT NULL, `durChapterPos` INTEGER NOT NULL, `durChapterTime` INTEGER NOT NULL, `wordCount` TEXT, `canUpdate` INTEGER NOT NULL, `order` INTEGER NOT NULL, `originOrder` INTEGER NOT NULL, `useReplaceRule` INTEGER NOT NULL, `variable` TEXT, PRIMARY KEY(`bookUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"tocUrl\",\n            \"columnName\": \"tocUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"originName\",\n            \"columnName\": \"originName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"author\",\n            \"columnName\": \"author\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"kind\",\n            \"columnName\": \"kind\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customTag\",\n            \"columnName\": \"customTag\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"coverUrl\",\n            \"columnName\": \"coverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customCoverUrl\",\n            \"columnName\": \"customCoverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"intro\",\n            \"columnName\": \"intro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customIntro\",\n            \"columnName\": \"customIntro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"charset\",\n            \"columnName\": \"charset\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"group\",\n            \"columnName\": \"group\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"latestChapterTitle\",\n            \"columnName\": \"latestChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"latestChapterTime\",\n            \"columnName\": \"latestChapterTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"lastCheckTime\",\n            \"columnName\": \"lastCheckTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"lastCheckCount\",\n            \"columnName\": \"lastCheckCount\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"totalChapterNum\",\n            \"columnName\": \"totalChapterNum\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"durChapterTitle\",\n            \"columnName\": \"durChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"durChapterIndex\",\n            \"columnName\": \"durChapterIndex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"durChapterPos\",\n            \"columnName\": \"durChapterPos\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"durChapterTime\",\n            \"columnName\": \"durChapterTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"wordCount\",\n            \"columnName\": \"wordCount\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"canUpdate\",\n            \"columnName\": \"canUpdate\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"originOrder\",\n            \"columnName\": \"originOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"useReplaceRule\",\n            \"columnName\": \"useReplaceRule\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"bookUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_books_bookUrl\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"bookUrl\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_books_bookUrl` ON `${TABLE_NAME}` (`bookUrl`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"book_groups\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`groupId` INTEGER NOT NULL, `groupName` TEXT NOT NULL, `order` INTEGER NOT NULL, PRIMARY KEY(`groupId`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"groupId\",\n            \"columnName\": \"groupId\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"groupName\",\n            \"columnName\": \"groupName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"groupId\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"book_sources\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookSourceName` TEXT NOT NULL, `bookSourceGroup` TEXT, `bookSourceUrl` TEXT NOT NULL, `bookSourceType` INTEGER NOT NULL, `bookUrlPattern` TEXT, `customOrder` INTEGER NOT NULL, `enabled` INTEGER NOT NULL, `enabledExplore` INTEGER NOT NULL, `header` TEXT, `loginUrl` TEXT, `lastUpdateTime` INTEGER NOT NULL, `weight` INTEGER NOT NULL, `exploreUrl` TEXT, `ruleExplore` TEXT, `searchUrl` TEXT, `ruleSearch` TEXT, `ruleBookInfo` TEXT, `ruleToc` TEXT, `ruleContent` TEXT, PRIMARY KEY(`bookSourceUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookSourceName\",\n            \"columnName\": \"bookSourceName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookSourceGroup\",\n            \"columnName\": \"bookSourceGroup\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"bookSourceUrl\",\n            \"columnName\": \"bookSourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookSourceType\",\n            \"columnName\": \"bookSourceType\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookUrlPattern\",\n            \"columnName\": \"bookUrlPattern\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enabled\",\n            \"columnName\": \"enabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enabledExplore\",\n            \"columnName\": \"enabledExplore\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"header\",\n            \"columnName\": \"header\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUrl\",\n            \"columnName\": \"loginUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"lastUpdateTime\",\n            \"columnName\": \"lastUpdateTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"weight\",\n            \"columnName\": \"weight\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"exploreUrl\",\n            \"columnName\": \"exploreUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleExplore\",\n            \"columnName\": \"ruleExplore\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"searchUrl\",\n            \"columnName\": \"searchUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleSearch\",\n            \"columnName\": \"ruleSearch\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleBookInfo\",\n            \"columnName\": \"ruleBookInfo\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleToc\",\n            \"columnName\": \"ruleToc\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleContent\",\n            \"columnName\": \"ruleContent\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"bookSourceUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_book_sources_bookSourceUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"bookSourceUrl\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_book_sources_bookSourceUrl` ON `${TABLE_NAME}` (`bookSourceUrl`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"chapters\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`url` TEXT NOT NULL, `title` TEXT NOT NULL, `bookUrl` TEXT NOT NULL, `index` INTEGER NOT NULL, `resourceUrl` TEXT, `tag` TEXT, `start` INTEGER, `end` INTEGER, `variable` TEXT, PRIMARY KEY(`url`, `bookUrl`), FOREIGN KEY(`bookUrl`) REFERENCES `books`(`bookUrl`) ON UPDATE NO ACTION ON DELETE CASCADE )\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"index\",\n            \"columnName\": \"index\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"resourceUrl\",\n            \"columnName\": \"resourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"tag\",\n            \"columnName\": \"tag\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"start\",\n            \"columnName\": \"start\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"end\",\n            \"columnName\": \"end\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"url\",\n            \"bookUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_chapters_bookUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"bookUrl\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_chapters_bookUrl` ON `${TABLE_NAME}` (`bookUrl`)\"\n          },\n          {\n            \"name\": \"index_chapters_bookUrl_index\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"bookUrl\",\n              \"index\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_chapters_bookUrl_index` ON `${TABLE_NAME}` (`bookUrl`, `index`)\"\n          }\n        ],\n        \"foreignKeys\": [\n          {\n            \"table\": \"books\",\n            \"onDelete\": \"CASCADE\",\n            \"onUpdate\": \"NO ACTION\",\n            \"columns\": [\n              \"bookUrl\"\n            ],\n            \"referencedColumns\": [\n              \"bookUrl\"\n            ]\n          }\n        ]\n      },\n      {\n        \"tableName\": \"replace_rules\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL, `group` TEXT, `pattern` TEXT NOT NULL, `replacement` TEXT NOT NULL, `scope` TEXT, `isEnabled` INTEGER NOT NULL, `isRegex` INTEGER NOT NULL, `sortOrder` INTEGER NOT NULL)\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"group\",\n            \"columnName\": \"group\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"pattern\",\n            \"columnName\": \"pattern\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"replacement\",\n            \"columnName\": \"replacement\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"scope\",\n            \"columnName\": \"scope\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"isEnabled\",\n            \"columnName\": \"isEnabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"isRegex\",\n            \"columnName\": \"isRegex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"sortOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"id\"\n          ],\n          \"autoGenerate\": true\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_replace_rules_id\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"id\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_replace_rules_id` ON `${TABLE_NAME}` (`id`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"searchBooks\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookUrl` TEXT NOT NULL, `origin` TEXT NOT NULL, `originName` TEXT NOT NULL, `type` INTEGER NOT NULL, `name` TEXT NOT NULL, `author` TEXT NOT NULL, `kind` TEXT, `coverUrl` TEXT, `intro` TEXT, `wordCount` TEXT, `latestChapterTitle` TEXT, `tocUrl` TEXT NOT NULL, `time` INTEGER NOT NULL, `variable` TEXT, `originOrder` INTEGER NOT NULL, PRIMARY KEY(`bookUrl`), FOREIGN KEY(`origin`) REFERENCES `book_sources`(`bookSourceUrl`) ON UPDATE NO ACTION ON DELETE CASCADE )\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"originName\",\n            \"columnName\": \"originName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"author\",\n            \"columnName\": \"author\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"kind\",\n            \"columnName\": \"kind\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"coverUrl\",\n            \"columnName\": \"coverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"intro\",\n            \"columnName\": \"intro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"wordCount\",\n            \"columnName\": \"wordCount\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"latestChapterTitle\",\n            \"columnName\": \"latestChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"tocUrl\",\n            \"columnName\": \"tocUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"time\",\n            \"columnName\": \"time\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"originOrder\",\n            \"columnName\": \"originOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"bookUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_searchBooks_bookUrl\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"bookUrl\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_searchBooks_bookUrl` ON `${TABLE_NAME}` (`bookUrl`)\"\n          },\n          {\n            \"name\": \"index_searchBooks_origin\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"origin\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_searchBooks_origin` ON `${TABLE_NAME}` (`origin`)\"\n          }\n        ],\n        \"foreignKeys\": [\n          {\n            \"table\": \"book_sources\",\n            \"onDelete\": \"CASCADE\",\n            \"onUpdate\": \"NO ACTION\",\n            \"columns\": [\n              \"origin\"\n            ],\n            \"referencedColumns\": [\n              \"bookSourceUrl\"\n            ]\n          }\n        ]\n      },\n      {\n        \"tableName\": \"search_keywords\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`word` TEXT NOT NULL, `usage` INTEGER NOT NULL, `lastUseTime` INTEGER NOT NULL, PRIMARY KEY(`word`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"word\",\n            \"columnName\": \"word\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"usage\",\n            \"columnName\": \"usage\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"lastUseTime\",\n            \"columnName\": \"lastUseTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"word\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_search_keywords_word\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"word\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_search_keywords_word` ON `${TABLE_NAME}` (`word`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"cookies\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`url` TEXT NOT NULL, `cookie` TEXT NOT NULL, PRIMARY KEY(`url`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"cookie\",\n            \"columnName\": \"cookie\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"url\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_cookies_url\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"url\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_cookies_url` ON `${TABLE_NAME}` (`url`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssSources\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`sourceUrl` TEXT NOT NULL, `sourceName` TEXT NOT NULL, `sourceIcon` TEXT NOT NULL, `sourceGroup` TEXT, `enabled` INTEGER NOT NULL, `ruleArticles` TEXT, `ruleNextPage` TEXT, `ruleTitle` TEXT, `rulePubDate` TEXT, `ruleDescription` TEXT, `ruleImage` TEXT, `ruleLink` TEXT, `ruleContent` TEXT, `header` TEXT, `enableJs` INTEGER NOT NULL, `loadWithBaseUrl` INTEGER NOT NULL, `customOrder` INTEGER NOT NULL, PRIMARY KEY(`sourceUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"sourceUrl\",\n            \"columnName\": \"sourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceName\",\n            \"columnName\": \"sourceName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceIcon\",\n            \"columnName\": \"sourceIcon\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceGroup\",\n            \"columnName\": \"sourceGroup\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enabled\",\n            \"columnName\": \"enabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"ruleArticles\",\n            \"columnName\": \"ruleArticles\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleNextPage\",\n            \"columnName\": \"ruleNextPage\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleTitle\",\n            \"columnName\": \"ruleTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"rulePubDate\",\n            \"columnName\": \"rulePubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleDescription\",\n            \"columnName\": \"ruleDescription\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleImage\",\n            \"columnName\": \"ruleImage\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleLink\",\n            \"columnName\": \"ruleLink\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleContent\",\n            \"columnName\": \"ruleContent\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"header\",\n            \"columnName\": \"header\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enableJs\",\n            \"columnName\": \"enableJs\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"loadWithBaseUrl\",\n            \"columnName\": \"loadWithBaseUrl\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"sourceUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_rssSources_sourceUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"sourceUrl\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_rssSources_sourceUrl` ON `${TABLE_NAME}` (`sourceUrl`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"bookmarks\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`time` INTEGER NOT NULL, `bookUrl` TEXT NOT NULL, `bookName` TEXT NOT NULL, `chapterIndex` INTEGER NOT NULL, `pageIndex` INTEGER NOT NULL, `chapterName` TEXT NOT NULL, `content` TEXT NOT NULL, PRIMARY KEY(`time`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"time\",\n            \"columnName\": \"time\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookName\",\n            \"columnName\": \"bookName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterIndex\",\n            \"columnName\": \"chapterIndex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"pageIndex\",\n            \"columnName\": \"pageIndex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterName\",\n            \"columnName\": \"chapterName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"time\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_bookmarks_time\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"time\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_bookmarks_time` ON `${TABLE_NAME}` (`time`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssArticles\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`origin` TEXT NOT NULL, `title` TEXT NOT NULL, `order` INTEGER NOT NULL, `link` TEXT NOT NULL, `pubDate` TEXT, `description` TEXT, `content` TEXT, `image` TEXT, `read` INTEGER NOT NULL, PRIMARY KEY(`origin`, `link`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"link\",\n            \"columnName\": \"link\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"pubDate\",\n            \"columnName\": \"pubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"description\",\n            \"columnName\": \"description\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"image\",\n            \"columnName\": \"image\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"read\",\n            \"columnName\": \"read\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"origin\",\n            \"link\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssStars\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`origin` TEXT NOT NULL, `title` TEXT NOT NULL, `starTime` INTEGER NOT NULL, `link` TEXT NOT NULL, `pubDate` TEXT, `description` TEXT, `content` TEXT, `image` TEXT, PRIMARY KEY(`origin`, `link`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"starTime\",\n            \"columnName\": \"starTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"link\",\n            \"columnName\": \"link\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"pubDate\",\n            \"columnName\": \"pubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"description\",\n            \"columnName\": \"description\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"image\",\n            \"columnName\": \"image\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"origin\",\n            \"link\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      }\n    ],\n    \"views\": [],\n    \"setupQueries\": [\n      \"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)\",\n      \"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'a355f8e02ebe0b13464573b1420a7b90')\"\n    ]\n  }\n}"
  },
  {
    "path": "app/schemas/io.legado.app.data.AppDatabase/50.json",
    "content": "{\n  \"formatVersion\": 1,\n  \"database\": {\n    \"version\": 50,\n    \"identityHash\": \"524cf3564400bd897a49355afd984ac1\",\n    \"entities\": [\n      {\n        \"tableName\": \"books\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookUrl` TEXT NOT NULL DEFAULT '', `tocUrl` TEXT NOT NULL DEFAULT '', `origin` TEXT NOT NULL DEFAULT '', `originName` TEXT NOT NULL DEFAULT '', `name` TEXT NOT NULL DEFAULT '', `author` TEXT NOT NULL DEFAULT '', `kind` TEXT, `customTag` TEXT, `coverUrl` TEXT, `customCoverUrl` TEXT, `intro` TEXT, `customIntro` TEXT, `charset` TEXT, `type` INTEGER NOT NULL DEFAULT 0, `group` INTEGER NOT NULL DEFAULT 0, `latestChapterTitle` TEXT, `latestChapterTime` INTEGER NOT NULL DEFAULT 0, `lastCheckTime` INTEGER NOT NULL DEFAULT 0, `lastCheckCount` INTEGER NOT NULL DEFAULT 0, `totalChapterNum` INTEGER NOT NULL DEFAULT 0, `durChapterTitle` TEXT, `durChapterIndex` INTEGER NOT NULL DEFAULT 0, `durChapterPos` INTEGER NOT NULL DEFAULT 0, `durChapterTime` INTEGER NOT NULL DEFAULT 0, `wordCount` TEXT, `canUpdate` INTEGER NOT NULL DEFAULT 1, `order` INTEGER NOT NULL DEFAULT 0, `originOrder` INTEGER NOT NULL DEFAULT 0, `variable` TEXT, `readConfig` TEXT, PRIMARY KEY(`bookUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"tocUrl\",\n            \"columnName\": \"tocUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"originName\",\n            \"columnName\": \"originName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"author\",\n            \"columnName\": \"author\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"kind\",\n            \"columnName\": \"kind\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customTag\",\n            \"columnName\": \"customTag\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"coverUrl\",\n            \"columnName\": \"coverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customCoverUrl\",\n            \"columnName\": \"customCoverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"intro\",\n            \"columnName\": \"intro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customIntro\",\n            \"columnName\": \"customIntro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"charset\",\n            \"columnName\": \"charset\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"group\",\n            \"columnName\": \"group\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"latestChapterTitle\",\n            \"columnName\": \"latestChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"latestChapterTime\",\n            \"columnName\": \"latestChapterTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"lastCheckTime\",\n            \"columnName\": \"lastCheckTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"lastCheckCount\",\n            \"columnName\": \"lastCheckCount\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"totalChapterNum\",\n            \"columnName\": \"totalChapterNum\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"durChapterTitle\",\n            \"columnName\": \"durChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"durChapterIndex\",\n            \"columnName\": \"durChapterIndex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"durChapterPos\",\n            \"columnName\": \"durChapterPos\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"durChapterTime\",\n            \"columnName\": \"durChapterTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"wordCount\",\n            \"columnName\": \"wordCount\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"canUpdate\",\n            \"columnName\": \"canUpdate\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"originOrder\",\n            \"columnName\": \"originOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"readConfig\",\n            \"columnName\": \"readConfig\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"bookUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_books_name_author\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"name\",\n              \"author\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_books_name_author` ON `${TABLE_NAME}` (`name`, `author`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"book_groups\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`groupId` INTEGER NOT NULL, `groupName` TEXT NOT NULL, `cover` TEXT, `order` INTEGER NOT NULL, `show` INTEGER NOT NULL, PRIMARY KEY(`groupId`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"groupId\",\n            \"columnName\": \"groupId\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"groupName\",\n            \"columnName\": \"groupName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"cover\",\n            \"columnName\": \"cover\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"show\",\n            \"columnName\": \"show\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"groupId\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"book_sources\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookSourceUrl` TEXT NOT NULL, `bookSourceName` TEXT NOT NULL, `bookSourceGroup` TEXT, `bookSourceType` INTEGER NOT NULL, `bookUrlPattern` TEXT, `customOrder` INTEGER NOT NULL, `enabled` INTEGER NOT NULL, `enabledExplore` INTEGER NOT NULL, `enabledCookieJar` INTEGER DEFAULT 0, `concurrentRate` TEXT, `header` TEXT, `loginUrl` TEXT, `loginUi` TEXT, `loginCheckJs` TEXT, `bookSourceComment` TEXT, `variableComment` TEXT, `lastUpdateTime` INTEGER NOT NULL, `respondTime` INTEGER NOT NULL, `weight` INTEGER NOT NULL, `exploreUrl` TEXT, `ruleExplore` TEXT, `searchUrl` TEXT, `ruleSearch` TEXT, `ruleBookInfo` TEXT, `ruleToc` TEXT, `ruleContent` TEXT, PRIMARY KEY(`bookSourceUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookSourceUrl\",\n            \"columnName\": \"bookSourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookSourceName\",\n            \"columnName\": \"bookSourceName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookSourceGroup\",\n            \"columnName\": \"bookSourceGroup\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"bookSourceType\",\n            \"columnName\": \"bookSourceType\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookUrlPattern\",\n            \"columnName\": \"bookUrlPattern\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enabled\",\n            \"columnName\": \"enabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enabledExplore\",\n            \"columnName\": \"enabledExplore\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enabledCookieJar\",\n            \"columnName\": \"enabledCookieJar\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"concurrentRate\",\n            \"columnName\": \"concurrentRate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"header\",\n            \"columnName\": \"header\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUrl\",\n            \"columnName\": \"loginUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUi\",\n            \"columnName\": \"loginUi\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginCheckJs\",\n            \"columnName\": \"loginCheckJs\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"bookSourceComment\",\n            \"columnName\": \"bookSourceComment\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"variableComment\",\n            \"columnName\": \"variableComment\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"lastUpdateTime\",\n            \"columnName\": \"lastUpdateTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"respondTime\",\n            \"columnName\": \"respondTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"weight\",\n            \"columnName\": \"weight\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"exploreUrl\",\n            \"columnName\": \"exploreUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleExplore\",\n            \"columnName\": \"ruleExplore\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"searchUrl\",\n            \"columnName\": \"searchUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleSearch\",\n            \"columnName\": \"ruleSearch\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleBookInfo\",\n            \"columnName\": \"ruleBookInfo\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleToc\",\n            \"columnName\": \"ruleToc\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleContent\",\n            \"columnName\": \"ruleContent\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"bookSourceUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_book_sources_bookSourceUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"bookSourceUrl\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_book_sources_bookSourceUrl` ON `${TABLE_NAME}` (`bookSourceUrl`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"chapters\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`url` TEXT NOT NULL, `title` TEXT NOT NULL, `isVolume` INTEGER NOT NULL, `baseUrl` TEXT NOT NULL, `bookUrl` TEXT NOT NULL, `index` INTEGER NOT NULL, `isVip` INTEGER NOT NULL, `isPay` INTEGER NOT NULL, `resourceUrl` TEXT, `tag` TEXT, `start` INTEGER, `end` INTEGER, `startFragmentId` TEXT, `endFragmentId` TEXT, `variable` TEXT, PRIMARY KEY(`url`, `bookUrl`), FOREIGN KEY(`bookUrl`) REFERENCES `books`(`bookUrl`) ON UPDATE NO ACTION ON DELETE CASCADE )\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"isVolume\",\n            \"columnName\": \"isVolume\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"baseUrl\",\n            \"columnName\": \"baseUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"index\",\n            \"columnName\": \"index\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"isVip\",\n            \"columnName\": \"isVip\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"isPay\",\n            \"columnName\": \"isPay\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"resourceUrl\",\n            \"columnName\": \"resourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"tag\",\n            \"columnName\": \"tag\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"start\",\n            \"columnName\": \"start\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"end\",\n            \"columnName\": \"end\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"startFragmentId\",\n            \"columnName\": \"startFragmentId\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"endFragmentId\",\n            \"columnName\": \"endFragmentId\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"url\",\n            \"bookUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_chapters_bookUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"bookUrl\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_chapters_bookUrl` ON `${TABLE_NAME}` (`bookUrl`)\"\n          },\n          {\n            \"name\": \"index_chapters_bookUrl_index\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"bookUrl\",\n              \"index\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_chapters_bookUrl_index` ON `${TABLE_NAME}` (`bookUrl`, `index`)\"\n          }\n        ],\n        \"foreignKeys\": [\n          {\n            \"table\": \"books\",\n            \"onDelete\": \"CASCADE\",\n            \"onUpdate\": \"NO ACTION\",\n            \"columns\": [\n              \"bookUrl\"\n            ],\n            \"referencedColumns\": [\n              \"bookUrl\"\n            ]\n          }\n        ]\n      },\n      {\n        \"tableName\": \"replace_rules\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL DEFAULT '', `group` TEXT, `pattern` TEXT NOT NULL DEFAULT '', `replacement` TEXT NOT NULL DEFAULT '', `scope` TEXT, `scopeTitle` INTEGER NOT NULL DEFAULT 0, `scopeContent` INTEGER NOT NULL DEFAULT 1, `isEnabled` INTEGER NOT NULL DEFAULT 1, `isRegex` INTEGER NOT NULL DEFAULT 1, `timeoutMillisecond` INTEGER NOT NULL DEFAULT 3000, `sortOrder` INTEGER NOT NULL DEFAULT 0)\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"group\",\n            \"columnName\": \"group\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"pattern\",\n            \"columnName\": \"pattern\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"replacement\",\n            \"columnName\": \"replacement\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"scope\",\n            \"columnName\": \"scope\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"scopeTitle\",\n            \"columnName\": \"scopeTitle\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"scopeContent\",\n            \"columnName\": \"scopeContent\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"isEnabled\",\n            \"columnName\": \"isEnabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"isRegex\",\n            \"columnName\": \"isRegex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"timeoutMillisecond\",\n            \"columnName\": \"timeoutMillisecond\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"3000\"\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"sortOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"id\"\n          ],\n          \"autoGenerate\": true\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_replace_rules_id\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"id\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_replace_rules_id` ON `${TABLE_NAME}` (`id`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"searchBooks\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookUrl` TEXT NOT NULL, `origin` TEXT NOT NULL, `originName` TEXT NOT NULL, `type` INTEGER NOT NULL, `name` TEXT NOT NULL, `author` TEXT NOT NULL, `kind` TEXT, `coverUrl` TEXT, `intro` TEXT, `wordCount` TEXT, `latestChapterTitle` TEXT, `tocUrl` TEXT NOT NULL, `time` INTEGER NOT NULL, `variable` TEXT, `originOrder` INTEGER NOT NULL, PRIMARY KEY(`bookUrl`), FOREIGN KEY(`origin`) REFERENCES `book_sources`(`bookSourceUrl`) ON UPDATE NO ACTION ON DELETE CASCADE )\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"originName\",\n            \"columnName\": \"originName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"author\",\n            \"columnName\": \"author\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"kind\",\n            \"columnName\": \"kind\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"coverUrl\",\n            \"columnName\": \"coverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"intro\",\n            \"columnName\": \"intro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"wordCount\",\n            \"columnName\": \"wordCount\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"latestChapterTitle\",\n            \"columnName\": \"latestChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"tocUrl\",\n            \"columnName\": \"tocUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"time\",\n            \"columnName\": \"time\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"originOrder\",\n            \"columnName\": \"originOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"bookUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_searchBooks_bookUrl\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"bookUrl\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_searchBooks_bookUrl` ON `${TABLE_NAME}` (`bookUrl`)\"\n          },\n          {\n            \"name\": \"index_searchBooks_origin\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"origin\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_searchBooks_origin` ON `${TABLE_NAME}` (`origin`)\"\n          }\n        ],\n        \"foreignKeys\": [\n          {\n            \"table\": \"book_sources\",\n            \"onDelete\": \"CASCADE\",\n            \"onUpdate\": \"NO ACTION\",\n            \"columns\": [\n              \"origin\"\n            ],\n            \"referencedColumns\": [\n              \"bookSourceUrl\"\n            ]\n          }\n        ]\n      },\n      {\n        \"tableName\": \"search_keywords\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`word` TEXT NOT NULL, `usage` INTEGER NOT NULL, `lastUseTime` INTEGER NOT NULL, PRIMARY KEY(`word`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"word\",\n            \"columnName\": \"word\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"usage\",\n            \"columnName\": \"usage\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"lastUseTime\",\n            \"columnName\": \"lastUseTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"word\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_search_keywords_word\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"word\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_search_keywords_word` ON `${TABLE_NAME}` (`word`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"cookies\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`url` TEXT NOT NULL, `cookie` TEXT NOT NULL, PRIMARY KEY(`url`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"cookie\",\n            \"columnName\": \"cookie\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"url\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_cookies_url\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"url\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_cookies_url` ON `${TABLE_NAME}` (`url`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssSources\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`sourceUrl` TEXT NOT NULL, `sourceName` TEXT NOT NULL, `sourceIcon` TEXT NOT NULL, `sourceGroup` TEXT, `sourceComment` TEXT, `enabled` INTEGER NOT NULL, `variableComment` TEXT, `enabledCookieJar` INTEGER DEFAULT 0, `concurrentRate` TEXT, `header` TEXT, `loginUrl` TEXT, `loginUi` TEXT, `loginCheckJs` TEXT, `sortUrl` TEXT, `singleUrl` INTEGER NOT NULL, `articleStyle` INTEGER NOT NULL, `ruleArticles` TEXT, `ruleNextPage` TEXT, `ruleTitle` TEXT, `rulePubDate` TEXT, `ruleDescription` TEXT, `ruleImage` TEXT, `ruleLink` TEXT, `ruleContent` TEXT, `style` TEXT, `enableJs` INTEGER NOT NULL, `loadWithBaseUrl` INTEGER NOT NULL, `customOrder` INTEGER NOT NULL, PRIMARY KEY(`sourceUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"sourceUrl\",\n            \"columnName\": \"sourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceName\",\n            \"columnName\": \"sourceName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceIcon\",\n            \"columnName\": \"sourceIcon\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceGroup\",\n            \"columnName\": \"sourceGroup\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"sourceComment\",\n            \"columnName\": \"sourceComment\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enabled\",\n            \"columnName\": \"enabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"variableComment\",\n            \"columnName\": \"variableComment\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enabledCookieJar\",\n            \"columnName\": \"enabledCookieJar\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"concurrentRate\",\n            \"columnName\": \"concurrentRate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"header\",\n            \"columnName\": \"header\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUrl\",\n            \"columnName\": \"loginUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUi\",\n            \"columnName\": \"loginUi\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginCheckJs\",\n            \"columnName\": \"loginCheckJs\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"sortUrl\",\n            \"columnName\": \"sortUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"singleUrl\",\n            \"columnName\": \"singleUrl\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"articleStyle\",\n            \"columnName\": \"articleStyle\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"ruleArticles\",\n            \"columnName\": \"ruleArticles\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleNextPage\",\n            \"columnName\": \"ruleNextPage\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleTitle\",\n            \"columnName\": \"ruleTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"rulePubDate\",\n            \"columnName\": \"rulePubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleDescription\",\n            \"columnName\": \"ruleDescription\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleImage\",\n            \"columnName\": \"ruleImage\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleLink\",\n            \"columnName\": \"ruleLink\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleContent\",\n            \"columnName\": \"ruleContent\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"style\",\n            \"columnName\": \"style\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enableJs\",\n            \"columnName\": \"enableJs\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"loadWithBaseUrl\",\n            \"columnName\": \"loadWithBaseUrl\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"sourceUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_rssSources_sourceUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"sourceUrl\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_rssSources_sourceUrl` ON `${TABLE_NAME}` (`sourceUrl`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"bookmarks\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`time` INTEGER NOT NULL, `bookName` TEXT NOT NULL, `bookAuthor` TEXT NOT NULL, `chapterIndex` INTEGER NOT NULL, `chapterPos` INTEGER NOT NULL, `chapterName` TEXT NOT NULL, `bookText` TEXT NOT NULL, `content` TEXT NOT NULL, PRIMARY KEY(`time`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"time\",\n            \"columnName\": \"time\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookName\",\n            \"columnName\": \"bookName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookAuthor\",\n            \"columnName\": \"bookAuthor\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterIndex\",\n            \"columnName\": \"chapterIndex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterPos\",\n            \"columnName\": \"chapterPos\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterName\",\n            \"columnName\": \"chapterName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookText\",\n            \"columnName\": \"bookText\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"time\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_bookmarks_bookName_bookAuthor\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"bookName\",\n              \"bookAuthor\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_bookmarks_bookName_bookAuthor` ON `${TABLE_NAME}` (`bookName`, `bookAuthor`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssArticles\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`origin` TEXT NOT NULL, `sort` TEXT NOT NULL, `title` TEXT NOT NULL, `order` INTEGER NOT NULL, `link` TEXT NOT NULL, `pubDate` TEXT, `description` TEXT, `content` TEXT, `image` TEXT, `read` INTEGER NOT NULL, `variable` TEXT, PRIMARY KEY(`origin`, `link`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sort\",\n            \"columnName\": \"sort\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"link\",\n            \"columnName\": \"link\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"pubDate\",\n            \"columnName\": \"pubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"description\",\n            \"columnName\": \"description\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"image\",\n            \"columnName\": \"image\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"read\",\n            \"columnName\": \"read\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"origin\",\n            \"link\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssReadRecords\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`record` TEXT NOT NULL, `read` INTEGER NOT NULL, PRIMARY KEY(`record`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"record\",\n            \"columnName\": \"record\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"read\",\n            \"columnName\": \"read\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"record\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssStars\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`origin` TEXT NOT NULL, `sort` TEXT NOT NULL, `title` TEXT NOT NULL, `starTime` INTEGER NOT NULL, `link` TEXT NOT NULL, `pubDate` TEXT, `description` TEXT, `content` TEXT, `image` TEXT, `variable` TEXT, PRIMARY KEY(`origin`, `link`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sort\",\n            \"columnName\": \"sort\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"starTime\",\n            \"columnName\": \"starTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"link\",\n            \"columnName\": \"link\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"pubDate\",\n            \"columnName\": \"pubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"description\",\n            \"columnName\": \"description\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"image\",\n            \"columnName\": \"image\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"origin\",\n            \"link\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"txtTocRules\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `rule` TEXT NOT NULL, `serialNumber` INTEGER NOT NULL, `enable` INTEGER NOT NULL, PRIMARY KEY(`id`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"rule\",\n            \"columnName\": \"rule\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"serialNumber\",\n            \"columnName\": \"serialNumber\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enable\",\n            \"columnName\": \"enable\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"id\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"readRecord\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`deviceId` TEXT NOT NULL, `bookName` TEXT NOT NULL, `readTime` INTEGER NOT NULL, PRIMARY KEY(`deviceId`, `bookName`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"deviceId\",\n            \"columnName\": \"deviceId\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookName\",\n            \"columnName\": \"bookName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"readTime\",\n            \"columnName\": \"readTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"deviceId\",\n            \"bookName\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"httpTTS\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `url` TEXT NOT NULL, `contentType` TEXT, `concurrentRate` TEXT DEFAULT '0', `loginUrl` TEXT, `loginUi` TEXT, `header` TEXT, `enabledCookieJar` INTEGER DEFAULT 0, `loginCheckJs` TEXT, `lastUpdateTime` INTEGER NOT NULL DEFAULT 0, PRIMARY KEY(`id`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"contentType\",\n            \"columnName\": \"contentType\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"concurrentRate\",\n            \"columnName\": \"concurrentRate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false,\n            \"defaultValue\": \"'0'\"\n          },\n          {\n            \"fieldPath\": \"loginUrl\",\n            \"columnName\": \"loginUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUi\",\n            \"columnName\": \"loginUi\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"header\",\n            \"columnName\": \"header\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enabledCookieJar\",\n            \"columnName\": \"enabledCookieJar\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"loginCheckJs\",\n            \"columnName\": \"loginCheckJs\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"lastUpdateTime\",\n            \"columnName\": \"lastUpdateTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"id\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"caches\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`key` TEXT NOT NULL, `value` TEXT, `deadline` INTEGER NOT NULL, PRIMARY KEY(`key`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"key\",\n            \"columnName\": \"key\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"value\",\n            \"columnName\": \"value\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"deadline\",\n            \"columnName\": \"deadline\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"key\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_caches_key\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"key\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_caches_key` ON `${TABLE_NAME}` (`key`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"ruleSubs\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `url` TEXT NOT NULL, `type` INTEGER NOT NULL, `customOrder` INTEGER NOT NULL, `autoUpdate` INTEGER NOT NULL, `update` INTEGER NOT NULL, PRIMARY KEY(`id`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"autoUpdate\",\n            \"columnName\": \"autoUpdate\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"update\",\n            \"columnName\": \"update\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"id\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"keyboardAssists\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`type` INTEGER NOT NULL DEFAULT 0, `key` TEXT NOT NULL DEFAULT '', `value` TEXT NOT NULL DEFAULT '', `serialNo` INTEGER NOT NULL DEFAULT 0, PRIMARY KEY(`type`, `key`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"key\",\n            \"columnName\": \"key\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"value\",\n            \"columnName\": \"value\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"serialNo\",\n            \"columnName\": \"serialNo\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"type\",\n            \"key\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      }\n    ],\n    \"views\": [],\n    \"setupQueries\": [\n      \"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)\",\n      \"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '524cf3564400bd897a49355afd984ac1')\"\n    ]\n  }\n}"
  },
  {
    "path": "app/schemas/io.legado.app.data.AppDatabase/51.json",
    "content": "{\n  \"formatVersion\": 1,\n  \"database\": {\n    \"version\": 51,\n    \"identityHash\": \"028c189be828501449b61b32ab47a36f\",\n    \"entities\": [\n      {\n        \"tableName\": \"books\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookUrl` TEXT NOT NULL DEFAULT '', `tocUrl` TEXT NOT NULL DEFAULT '', `origin` TEXT NOT NULL DEFAULT '', `originName` TEXT NOT NULL DEFAULT '', `name` TEXT NOT NULL DEFAULT '', `author` TEXT NOT NULL DEFAULT '', `kind` TEXT, `customTag` TEXT, `coverUrl` TEXT, `customCoverUrl` TEXT, `intro` TEXT, `customIntro` TEXT, `charset` TEXT, `type` INTEGER NOT NULL DEFAULT 0, `group` INTEGER NOT NULL DEFAULT 0, `latestChapterTitle` TEXT, `latestChapterTime` INTEGER NOT NULL DEFAULT 0, `lastCheckTime` INTEGER NOT NULL DEFAULT 0, `lastCheckCount` INTEGER NOT NULL DEFAULT 0, `totalChapterNum` INTEGER NOT NULL DEFAULT 0, `durChapterTitle` TEXT, `durChapterIndex` INTEGER NOT NULL DEFAULT 0, `durChapterPos` INTEGER NOT NULL DEFAULT 0, `durChapterTime` INTEGER NOT NULL DEFAULT 0, `wordCount` TEXT, `canUpdate` INTEGER NOT NULL DEFAULT 1, `order` INTEGER NOT NULL DEFAULT 0, `originOrder` INTEGER NOT NULL DEFAULT 0, `variable` TEXT, `readConfig` TEXT, PRIMARY KEY(`bookUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"tocUrl\",\n            \"columnName\": \"tocUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"originName\",\n            \"columnName\": \"originName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"author\",\n            \"columnName\": \"author\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"kind\",\n            \"columnName\": \"kind\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customTag\",\n            \"columnName\": \"customTag\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"coverUrl\",\n            \"columnName\": \"coverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customCoverUrl\",\n            \"columnName\": \"customCoverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"intro\",\n            \"columnName\": \"intro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customIntro\",\n            \"columnName\": \"customIntro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"charset\",\n            \"columnName\": \"charset\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"group\",\n            \"columnName\": \"group\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"latestChapterTitle\",\n            \"columnName\": \"latestChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"latestChapterTime\",\n            \"columnName\": \"latestChapterTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"lastCheckTime\",\n            \"columnName\": \"lastCheckTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"lastCheckCount\",\n            \"columnName\": \"lastCheckCount\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"totalChapterNum\",\n            \"columnName\": \"totalChapterNum\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"durChapterTitle\",\n            \"columnName\": \"durChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"durChapterIndex\",\n            \"columnName\": \"durChapterIndex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"durChapterPos\",\n            \"columnName\": \"durChapterPos\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"durChapterTime\",\n            \"columnName\": \"durChapterTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"wordCount\",\n            \"columnName\": \"wordCount\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"canUpdate\",\n            \"columnName\": \"canUpdate\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"originOrder\",\n            \"columnName\": \"originOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"readConfig\",\n            \"columnName\": \"readConfig\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"bookUrl\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_books_name_author\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"name\",\n              \"author\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_books_name_author` ON `${TABLE_NAME}` (`name`, `author`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"book_groups\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`groupId` INTEGER NOT NULL, `groupName` TEXT NOT NULL, `cover` TEXT, `order` INTEGER NOT NULL, `show` INTEGER NOT NULL, PRIMARY KEY(`groupId`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"groupId\",\n            \"columnName\": \"groupId\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"groupName\",\n            \"columnName\": \"groupName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"cover\",\n            \"columnName\": \"cover\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"show\",\n            \"columnName\": \"show\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"groupId\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"book_sources\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookSourceUrl` TEXT NOT NULL, `bookSourceName` TEXT NOT NULL, `bookSourceGroup` TEXT, `bookSourceType` INTEGER NOT NULL, `bookUrlPattern` TEXT, `customOrder` INTEGER NOT NULL, `enabled` INTEGER NOT NULL, `enabledExplore` INTEGER NOT NULL, `enabledCookieJar` INTEGER DEFAULT 0, `concurrentRate` TEXT, `header` TEXT, `loginUrl` TEXT, `loginUi` TEXT, `loginCheckJs` TEXT, `bookSourceComment` TEXT, `variableComment` TEXT, `lastUpdateTime` INTEGER NOT NULL, `respondTime` INTEGER NOT NULL, `weight` INTEGER NOT NULL, `exploreUrl` TEXT, `ruleExplore` TEXT, `searchUrl` TEXT, `ruleSearch` TEXT, `ruleBookInfo` TEXT, `ruleToc` TEXT, `ruleContent` TEXT, PRIMARY KEY(`bookSourceUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookSourceUrl\",\n            \"columnName\": \"bookSourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookSourceName\",\n            \"columnName\": \"bookSourceName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookSourceGroup\",\n            \"columnName\": \"bookSourceGroup\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"bookSourceType\",\n            \"columnName\": \"bookSourceType\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookUrlPattern\",\n            \"columnName\": \"bookUrlPattern\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enabled\",\n            \"columnName\": \"enabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enabledExplore\",\n            \"columnName\": \"enabledExplore\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enabledCookieJar\",\n            \"columnName\": \"enabledCookieJar\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"concurrentRate\",\n            \"columnName\": \"concurrentRate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"header\",\n            \"columnName\": \"header\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUrl\",\n            \"columnName\": \"loginUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUi\",\n            \"columnName\": \"loginUi\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginCheckJs\",\n            \"columnName\": \"loginCheckJs\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"bookSourceComment\",\n            \"columnName\": \"bookSourceComment\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"variableComment\",\n            \"columnName\": \"variableComment\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"lastUpdateTime\",\n            \"columnName\": \"lastUpdateTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"respondTime\",\n            \"columnName\": \"respondTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"weight\",\n            \"columnName\": \"weight\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"exploreUrl\",\n            \"columnName\": \"exploreUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleExplore\",\n            \"columnName\": \"ruleExplore\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"searchUrl\",\n            \"columnName\": \"searchUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleSearch\",\n            \"columnName\": \"ruleSearch\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleBookInfo\",\n            \"columnName\": \"ruleBookInfo\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleToc\",\n            \"columnName\": \"ruleToc\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleContent\",\n            \"columnName\": \"ruleContent\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"bookSourceUrl\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_book_sources_bookSourceUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"bookSourceUrl\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_book_sources_bookSourceUrl` ON `${TABLE_NAME}` (`bookSourceUrl`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"chapters\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`url` TEXT NOT NULL, `title` TEXT NOT NULL, `isVolume` INTEGER NOT NULL, `baseUrl` TEXT NOT NULL, `bookUrl` TEXT NOT NULL, `index` INTEGER NOT NULL, `isVip` INTEGER NOT NULL, `isPay` INTEGER NOT NULL, `resourceUrl` TEXT, `tag` TEXT, `start` INTEGER, `end` INTEGER, `startFragmentId` TEXT, `endFragmentId` TEXT, `variable` TEXT, PRIMARY KEY(`url`, `bookUrl`), FOREIGN KEY(`bookUrl`) REFERENCES `books`(`bookUrl`) ON UPDATE NO ACTION ON DELETE CASCADE )\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"isVolume\",\n            \"columnName\": \"isVolume\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"baseUrl\",\n            \"columnName\": \"baseUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"index\",\n            \"columnName\": \"index\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"isVip\",\n            \"columnName\": \"isVip\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"isPay\",\n            \"columnName\": \"isPay\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"resourceUrl\",\n            \"columnName\": \"resourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"tag\",\n            \"columnName\": \"tag\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"start\",\n            \"columnName\": \"start\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"end\",\n            \"columnName\": \"end\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"startFragmentId\",\n            \"columnName\": \"startFragmentId\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"endFragmentId\",\n            \"columnName\": \"endFragmentId\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"url\",\n            \"bookUrl\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_chapters_bookUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"bookUrl\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_chapters_bookUrl` ON `${TABLE_NAME}` (`bookUrl`)\"\n          },\n          {\n            \"name\": \"index_chapters_bookUrl_index\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"bookUrl\",\n              \"index\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_chapters_bookUrl_index` ON `${TABLE_NAME}` (`bookUrl`, `index`)\"\n          }\n        ],\n        \"foreignKeys\": [\n          {\n            \"table\": \"books\",\n            \"onDelete\": \"CASCADE\",\n            \"onUpdate\": \"NO ACTION\",\n            \"columns\": [\n              \"bookUrl\"\n            ],\n            \"referencedColumns\": [\n              \"bookUrl\"\n            ]\n          }\n        ]\n      },\n      {\n        \"tableName\": \"replace_rules\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL DEFAULT '', `group` TEXT, `pattern` TEXT NOT NULL DEFAULT '', `replacement` TEXT NOT NULL DEFAULT '', `scope` TEXT, `scopeTitle` INTEGER NOT NULL DEFAULT 0, `scopeContent` INTEGER NOT NULL DEFAULT 1, `isEnabled` INTEGER NOT NULL DEFAULT 1, `isRegex` INTEGER NOT NULL DEFAULT 1, `timeoutMillisecond` INTEGER NOT NULL DEFAULT 3000, `sortOrder` INTEGER NOT NULL DEFAULT 0)\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"group\",\n            \"columnName\": \"group\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"pattern\",\n            \"columnName\": \"pattern\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"replacement\",\n            \"columnName\": \"replacement\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"scope\",\n            \"columnName\": \"scope\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"scopeTitle\",\n            \"columnName\": \"scopeTitle\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"scopeContent\",\n            \"columnName\": \"scopeContent\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"isEnabled\",\n            \"columnName\": \"isEnabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"isRegex\",\n            \"columnName\": \"isRegex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"timeoutMillisecond\",\n            \"columnName\": \"timeoutMillisecond\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"3000\"\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"sortOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": true,\n          \"columnNames\": [\n            \"id\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_replace_rules_id\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"id\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_replace_rules_id` ON `${TABLE_NAME}` (`id`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"searchBooks\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookUrl` TEXT NOT NULL, `origin` TEXT NOT NULL, `originName` TEXT NOT NULL, `type` INTEGER NOT NULL, `name` TEXT NOT NULL, `author` TEXT NOT NULL, `kind` TEXT, `coverUrl` TEXT, `intro` TEXT, `wordCount` TEXT, `latestChapterTitle` TEXT, `tocUrl` TEXT NOT NULL, `time` INTEGER NOT NULL, `variable` TEXT, `originOrder` INTEGER NOT NULL, PRIMARY KEY(`bookUrl`), FOREIGN KEY(`origin`) REFERENCES `book_sources`(`bookSourceUrl`) ON UPDATE NO ACTION ON DELETE CASCADE )\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"originName\",\n            \"columnName\": \"originName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"author\",\n            \"columnName\": \"author\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"kind\",\n            \"columnName\": \"kind\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"coverUrl\",\n            \"columnName\": \"coverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"intro\",\n            \"columnName\": \"intro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"wordCount\",\n            \"columnName\": \"wordCount\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"latestChapterTitle\",\n            \"columnName\": \"latestChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"tocUrl\",\n            \"columnName\": \"tocUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"time\",\n            \"columnName\": \"time\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"originOrder\",\n            \"columnName\": \"originOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"bookUrl\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_searchBooks_bookUrl\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"bookUrl\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_searchBooks_bookUrl` ON `${TABLE_NAME}` (`bookUrl`)\"\n          },\n          {\n            \"name\": \"index_searchBooks_origin\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"origin\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_searchBooks_origin` ON `${TABLE_NAME}` (`origin`)\"\n          }\n        ],\n        \"foreignKeys\": [\n          {\n            \"table\": \"book_sources\",\n            \"onDelete\": \"CASCADE\",\n            \"onUpdate\": \"NO ACTION\",\n            \"columns\": [\n              \"origin\"\n            ],\n            \"referencedColumns\": [\n              \"bookSourceUrl\"\n            ]\n          }\n        ]\n      },\n      {\n        \"tableName\": \"search_keywords\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`word` TEXT NOT NULL, `usage` INTEGER NOT NULL, `lastUseTime` INTEGER NOT NULL, PRIMARY KEY(`word`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"word\",\n            \"columnName\": \"word\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"usage\",\n            \"columnName\": \"usage\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"lastUseTime\",\n            \"columnName\": \"lastUseTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"word\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_search_keywords_word\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"word\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_search_keywords_word` ON `${TABLE_NAME}` (`word`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"cookies\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`url` TEXT NOT NULL, `cookie` TEXT NOT NULL, PRIMARY KEY(`url`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"cookie\",\n            \"columnName\": \"cookie\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"url\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_cookies_url\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"url\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_cookies_url` ON `${TABLE_NAME}` (`url`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssSources\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`sourceUrl` TEXT NOT NULL, `sourceName` TEXT NOT NULL, `sourceIcon` TEXT NOT NULL, `sourceGroup` TEXT, `sourceComment` TEXT, `enabled` INTEGER NOT NULL, `variableComment` TEXT, `enabledCookieJar` INTEGER DEFAULT 0, `concurrentRate` TEXT, `header` TEXT, `loginUrl` TEXT, `loginUi` TEXT, `loginCheckJs` TEXT, `sortUrl` TEXT, `singleUrl` INTEGER NOT NULL, `articleStyle` INTEGER NOT NULL, `ruleArticles` TEXT, `ruleNextPage` TEXT, `ruleTitle` TEXT, `rulePubDate` TEXT, `ruleDescription` TEXT, `ruleImage` TEXT, `ruleLink` TEXT, `ruleContent` TEXT, `style` TEXT, `enableJs` INTEGER NOT NULL, `loadWithBaseUrl` INTEGER NOT NULL, `customOrder` INTEGER NOT NULL, PRIMARY KEY(`sourceUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"sourceUrl\",\n            \"columnName\": \"sourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceName\",\n            \"columnName\": \"sourceName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceIcon\",\n            \"columnName\": \"sourceIcon\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceGroup\",\n            \"columnName\": \"sourceGroup\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"sourceComment\",\n            \"columnName\": \"sourceComment\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enabled\",\n            \"columnName\": \"enabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"variableComment\",\n            \"columnName\": \"variableComment\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enabledCookieJar\",\n            \"columnName\": \"enabledCookieJar\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"concurrentRate\",\n            \"columnName\": \"concurrentRate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"header\",\n            \"columnName\": \"header\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUrl\",\n            \"columnName\": \"loginUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUi\",\n            \"columnName\": \"loginUi\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginCheckJs\",\n            \"columnName\": \"loginCheckJs\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"sortUrl\",\n            \"columnName\": \"sortUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"singleUrl\",\n            \"columnName\": \"singleUrl\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"articleStyle\",\n            \"columnName\": \"articleStyle\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"ruleArticles\",\n            \"columnName\": \"ruleArticles\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleNextPage\",\n            \"columnName\": \"ruleNextPage\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleTitle\",\n            \"columnName\": \"ruleTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"rulePubDate\",\n            \"columnName\": \"rulePubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleDescription\",\n            \"columnName\": \"ruleDescription\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleImage\",\n            \"columnName\": \"ruleImage\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleLink\",\n            \"columnName\": \"ruleLink\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleContent\",\n            \"columnName\": \"ruleContent\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"style\",\n            \"columnName\": \"style\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enableJs\",\n            \"columnName\": \"enableJs\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"loadWithBaseUrl\",\n            \"columnName\": \"loadWithBaseUrl\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"sourceUrl\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_rssSources_sourceUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"sourceUrl\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_rssSources_sourceUrl` ON `${TABLE_NAME}` (`sourceUrl`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"bookmarks\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`time` INTEGER NOT NULL, `bookName` TEXT NOT NULL, `bookAuthor` TEXT NOT NULL, `chapterIndex` INTEGER NOT NULL, `chapterPos` INTEGER NOT NULL, `chapterName` TEXT NOT NULL, `bookText` TEXT NOT NULL, `content` TEXT NOT NULL, PRIMARY KEY(`time`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"time\",\n            \"columnName\": \"time\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookName\",\n            \"columnName\": \"bookName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookAuthor\",\n            \"columnName\": \"bookAuthor\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterIndex\",\n            \"columnName\": \"chapterIndex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterPos\",\n            \"columnName\": \"chapterPos\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterName\",\n            \"columnName\": \"chapterName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookText\",\n            \"columnName\": \"bookText\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"time\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_bookmarks_bookName_bookAuthor\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"bookName\",\n              \"bookAuthor\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_bookmarks_bookName_bookAuthor` ON `${TABLE_NAME}` (`bookName`, `bookAuthor`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssArticles\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`origin` TEXT NOT NULL, `sort` TEXT NOT NULL, `title` TEXT NOT NULL, `order` INTEGER NOT NULL, `link` TEXT NOT NULL, `pubDate` TEXT, `description` TEXT, `content` TEXT, `image` TEXT, `read` INTEGER NOT NULL, `variable` TEXT, PRIMARY KEY(`origin`, `link`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sort\",\n            \"columnName\": \"sort\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"link\",\n            \"columnName\": \"link\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"pubDate\",\n            \"columnName\": \"pubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"description\",\n            \"columnName\": \"description\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"image\",\n            \"columnName\": \"image\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"read\",\n            \"columnName\": \"read\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"origin\",\n            \"link\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssReadRecords\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`record` TEXT NOT NULL, `read` INTEGER NOT NULL, PRIMARY KEY(`record`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"record\",\n            \"columnName\": \"record\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"read\",\n            \"columnName\": \"read\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"record\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssStars\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`origin` TEXT NOT NULL, `sort` TEXT NOT NULL, `title` TEXT NOT NULL, `starTime` INTEGER NOT NULL, `link` TEXT NOT NULL, `pubDate` TEXT, `description` TEXT, `content` TEXT, `image` TEXT, `variable` TEXT, PRIMARY KEY(`origin`, `link`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sort\",\n            \"columnName\": \"sort\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"starTime\",\n            \"columnName\": \"starTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"link\",\n            \"columnName\": \"link\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"pubDate\",\n            \"columnName\": \"pubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"description\",\n            \"columnName\": \"description\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"image\",\n            \"columnName\": \"image\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"origin\",\n            \"link\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"txtTocRules\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `rule` TEXT NOT NULL, `serialNumber` INTEGER NOT NULL, `enable` INTEGER NOT NULL, PRIMARY KEY(`id`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"rule\",\n            \"columnName\": \"rule\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"serialNumber\",\n            \"columnName\": \"serialNumber\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enable\",\n            \"columnName\": \"enable\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"id\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"readRecord\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`deviceId` TEXT NOT NULL, `bookName` TEXT NOT NULL, `readTime` INTEGER NOT NULL DEFAULT 0, `lastRead` INTEGER NOT NULL DEFAULT 0, PRIMARY KEY(`deviceId`, `bookName`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"deviceId\",\n            \"columnName\": \"deviceId\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookName\",\n            \"columnName\": \"bookName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"readTime\",\n            \"columnName\": \"readTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"lastRead\",\n            \"columnName\": \"lastRead\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"deviceId\",\n            \"bookName\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"httpTTS\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `url` TEXT NOT NULL, `contentType` TEXT, `concurrentRate` TEXT DEFAULT '0', `loginUrl` TEXT, `loginUi` TEXT, `header` TEXT, `enabledCookieJar` INTEGER DEFAULT 0, `loginCheckJs` TEXT, `lastUpdateTime` INTEGER NOT NULL DEFAULT 0, PRIMARY KEY(`id`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"contentType\",\n            \"columnName\": \"contentType\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"concurrentRate\",\n            \"columnName\": \"concurrentRate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false,\n            \"defaultValue\": \"'0'\"\n          },\n          {\n            \"fieldPath\": \"loginUrl\",\n            \"columnName\": \"loginUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUi\",\n            \"columnName\": \"loginUi\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"header\",\n            \"columnName\": \"header\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enabledCookieJar\",\n            \"columnName\": \"enabledCookieJar\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"loginCheckJs\",\n            \"columnName\": \"loginCheckJs\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"lastUpdateTime\",\n            \"columnName\": \"lastUpdateTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"id\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"caches\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`key` TEXT NOT NULL, `value` TEXT, `deadline` INTEGER NOT NULL, PRIMARY KEY(`key`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"key\",\n            \"columnName\": \"key\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"value\",\n            \"columnName\": \"value\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"deadline\",\n            \"columnName\": \"deadline\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"key\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_caches_key\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"key\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_caches_key` ON `${TABLE_NAME}` (`key`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"ruleSubs\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `url` TEXT NOT NULL, `type` INTEGER NOT NULL, `customOrder` INTEGER NOT NULL, `autoUpdate` INTEGER NOT NULL, `update` INTEGER NOT NULL, PRIMARY KEY(`id`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"autoUpdate\",\n            \"columnName\": \"autoUpdate\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"update\",\n            \"columnName\": \"update\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"id\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"keyboardAssists\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`type` INTEGER NOT NULL DEFAULT 0, `key` TEXT NOT NULL DEFAULT '', `value` TEXT NOT NULL DEFAULT '', `serialNo` INTEGER NOT NULL DEFAULT 0, PRIMARY KEY(`type`, `key`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"key\",\n            \"columnName\": \"key\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"value\",\n            \"columnName\": \"value\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"serialNo\",\n            \"columnName\": \"serialNo\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"type\",\n            \"key\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      }\n    ],\n    \"views\": [],\n    \"setupQueries\": [\n      \"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)\",\n      \"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '028c189be828501449b61b32ab47a36f')\"\n    ]\n  }\n}"
  },
  {
    "path": "app/schemas/io.legado.app.data.AppDatabase/52.json",
    "content": "{\n  \"formatVersion\": 1,\n  \"database\": {\n    \"version\": 52,\n    \"identityHash\": \"0221c385ed7393f47afe9579d3106541\",\n    \"entities\": [\n      {\n        \"tableName\": \"books\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookUrl` TEXT NOT NULL DEFAULT '', `tocUrl` TEXT NOT NULL DEFAULT '', `origin` TEXT NOT NULL DEFAULT '', `originName` TEXT NOT NULL DEFAULT '', `name` TEXT NOT NULL DEFAULT '', `author` TEXT NOT NULL DEFAULT '', `kind` TEXT, `customTag` TEXT, `coverUrl` TEXT, `customCoverUrl` TEXT, `intro` TEXT, `customIntro` TEXT, `charset` TEXT, `type` INTEGER NOT NULL DEFAULT 0, `group` INTEGER NOT NULL DEFAULT 0, `latestChapterTitle` TEXT, `latestChapterTime` INTEGER NOT NULL DEFAULT 0, `lastCheckTime` INTEGER NOT NULL DEFAULT 0, `lastCheckCount` INTEGER NOT NULL DEFAULT 0, `totalChapterNum` INTEGER NOT NULL DEFAULT 0, `durChapterTitle` TEXT, `durChapterIndex` INTEGER NOT NULL DEFAULT 0, `durChapterPos` INTEGER NOT NULL DEFAULT 0, `durChapterTime` INTEGER NOT NULL DEFAULT 0, `wordCount` TEXT, `canUpdate` INTEGER NOT NULL DEFAULT 1, `order` INTEGER NOT NULL DEFAULT 0, `originOrder` INTEGER NOT NULL DEFAULT 0, `variable` TEXT, `readConfig` TEXT, PRIMARY KEY(`bookUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"tocUrl\",\n            \"columnName\": \"tocUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"originName\",\n            \"columnName\": \"originName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"author\",\n            \"columnName\": \"author\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"kind\",\n            \"columnName\": \"kind\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customTag\",\n            \"columnName\": \"customTag\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"coverUrl\",\n            \"columnName\": \"coverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customCoverUrl\",\n            \"columnName\": \"customCoverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"intro\",\n            \"columnName\": \"intro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customIntro\",\n            \"columnName\": \"customIntro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"charset\",\n            \"columnName\": \"charset\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"group\",\n            \"columnName\": \"group\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"latestChapterTitle\",\n            \"columnName\": \"latestChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"latestChapterTime\",\n            \"columnName\": \"latestChapterTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"lastCheckTime\",\n            \"columnName\": \"lastCheckTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"lastCheckCount\",\n            \"columnName\": \"lastCheckCount\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"totalChapterNum\",\n            \"columnName\": \"totalChapterNum\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"durChapterTitle\",\n            \"columnName\": \"durChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"durChapterIndex\",\n            \"columnName\": \"durChapterIndex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"durChapterPos\",\n            \"columnName\": \"durChapterPos\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"durChapterTime\",\n            \"columnName\": \"durChapterTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"wordCount\",\n            \"columnName\": \"wordCount\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"canUpdate\",\n            \"columnName\": \"canUpdate\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"originOrder\",\n            \"columnName\": \"originOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"readConfig\",\n            \"columnName\": \"readConfig\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"bookUrl\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_books_name_author\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"name\",\n              \"author\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_books_name_author` ON `${TABLE_NAME}` (`name`, `author`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"book_groups\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`groupId` INTEGER NOT NULL, `groupName` TEXT NOT NULL, `cover` TEXT, `order` INTEGER NOT NULL, `show` INTEGER NOT NULL, PRIMARY KEY(`groupId`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"groupId\",\n            \"columnName\": \"groupId\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"groupName\",\n            \"columnName\": \"groupName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"cover\",\n            \"columnName\": \"cover\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"show\",\n            \"columnName\": \"show\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"groupId\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"book_sources\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookSourceUrl` TEXT NOT NULL, `bookSourceName` TEXT NOT NULL, `bookSourceGroup` TEXT, `bookSourceType` INTEGER NOT NULL, `bookUrlPattern` TEXT, `customOrder` INTEGER NOT NULL, `enabled` INTEGER NOT NULL, `enabledExplore` INTEGER NOT NULL, `enabledCookieJar` INTEGER DEFAULT 0, `concurrentRate` TEXT, `header` TEXT, `loginUrl` TEXT, `loginUi` TEXT, `loginCheckJs` TEXT, `bookSourceComment` TEXT, `variableComment` TEXT, `lastUpdateTime` INTEGER NOT NULL, `respondTime` INTEGER NOT NULL, `weight` INTEGER NOT NULL, `exploreUrl` TEXT, `ruleExplore` TEXT, `searchUrl` TEXT, `ruleSearch` TEXT, `ruleBookInfo` TEXT, `ruleToc` TEXT, `ruleContent` TEXT, PRIMARY KEY(`bookSourceUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookSourceUrl\",\n            \"columnName\": \"bookSourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookSourceName\",\n            \"columnName\": \"bookSourceName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookSourceGroup\",\n            \"columnName\": \"bookSourceGroup\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"bookSourceType\",\n            \"columnName\": \"bookSourceType\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookUrlPattern\",\n            \"columnName\": \"bookUrlPattern\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enabled\",\n            \"columnName\": \"enabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enabledExplore\",\n            \"columnName\": \"enabledExplore\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enabledCookieJar\",\n            \"columnName\": \"enabledCookieJar\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"concurrentRate\",\n            \"columnName\": \"concurrentRate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"header\",\n            \"columnName\": \"header\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUrl\",\n            \"columnName\": \"loginUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUi\",\n            \"columnName\": \"loginUi\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginCheckJs\",\n            \"columnName\": \"loginCheckJs\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"bookSourceComment\",\n            \"columnName\": \"bookSourceComment\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"variableComment\",\n            \"columnName\": \"variableComment\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"lastUpdateTime\",\n            \"columnName\": \"lastUpdateTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"respondTime\",\n            \"columnName\": \"respondTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"weight\",\n            \"columnName\": \"weight\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"exploreUrl\",\n            \"columnName\": \"exploreUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleExplore\",\n            \"columnName\": \"ruleExplore\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"searchUrl\",\n            \"columnName\": \"searchUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleSearch\",\n            \"columnName\": \"ruleSearch\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleBookInfo\",\n            \"columnName\": \"ruleBookInfo\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleToc\",\n            \"columnName\": \"ruleToc\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleContent\",\n            \"columnName\": \"ruleContent\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"bookSourceUrl\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_book_sources_bookSourceUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"bookSourceUrl\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_book_sources_bookSourceUrl` ON `${TABLE_NAME}` (`bookSourceUrl`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"chapters\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`url` TEXT NOT NULL, `title` TEXT NOT NULL, `isVolume` INTEGER NOT NULL, `baseUrl` TEXT NOT NULL, `bookUrl` TEXT NOT NULL, `index` INTEGER NOT NULL, `isVip` INTEGER NOT NULL, `isPay` INTEGER NOT NULL, `resourceUrl` TEXT, `tag` TEXT, `start` INTEGER, `end` INTEGER, `startFragmentId` TEXT, `endFragmentId` TEXT, `variable` TEXT, PRIMARY KEY(`url`, `bookUrl`), FOREIGN KEY(`bookUrl`) REFERENCES `books`(`bookUrl`) ON UPDATE NO ACTION ON DELETE CASCADE )\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"isVolume\",\n            \"columnName\": \"isVolume\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"baseUrl\",\n            \"columnName\": \"baseUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"index\",\n            \"columnName\": \"index\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"isVip\",\n            \"columnName\": \"isVip\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"isPay\",\n            \"columnName\": \"isPay\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"resourceUrl\",\n            \"columnName\": \"resourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"tag\",\n            \"columnName\": \"tag\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"start\",\n            \"columnName\": \"start\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"end\",\n            \"columnName\": \"end\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"startFragmentId\",\n            \"columnName\": \"startFragmentId\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"endFragmentId\",\n            \"columnName\": \"endFragmentId\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"url\",\n            \"bookUrl\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_chapters_bookUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"bookUrl\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_chapters_bookUrl` ON `${TABLE_NAME}` (`bookUrl`)\"\n          },\n          {\n            \"name\": \"index_chapters_bookUrl_index\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"bookUrl\",\n              \"index\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_chapters_bookUrl_index` ON `${TABLE_NAME}` (`bookUrl`, `index`)\"\n          }\n        ],\n        \"foreignKeys\": [\n          {\n            \"table\": \"books\",\n            \"onDelete\": \"CASCADE\",\n            \"onUpdate\": \"NO ACTION\",\n            \"columns\": [\n              \"bookUrl\"\n            ],\n            \"referencedColumns\": [\n              \"bookUrl\"\n            ]\n          }\n        ]\n      },\n      {\n        \"tableName\": \"replace_rules\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL DEFAULT '', `group` TEXT, `pattern` TEXT NOT NULL DEFAULT '', `replacement` TEXT NOT NULL DEFAULT '', `scope` TEXT, `scopeTitle` INTEGER NOT NULL DEFAULT 0, `scopeContent` INTEGER NOT NULL DEFAULT 1, `isEnabled` INTEGER NOT NULL DEFAULT 1, `isRegex` INTEGER NOT NULL DEFAULT 1, `timeoutMillisecond` INTEGER NOT NULL DEFAULT 3000, `sortOrder` INTEGER NOT NULL DEFAULT 0)\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"group\",\n            \"columnName\": \"group\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"pattern\",\n            \"columnName\": \"pattern\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"replacement\",\n            \"columnName\": \"replacement\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"scope\",\n            \"columnName\": \"scope\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"scopeTitle\",\n            \"columnName\": \"scopeTitle\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"scopeContent\",\n            \"columnName\": \"scopeContent\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"isEnabled\",\n            \"columnName\": \"isEnabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"isRegex\",\n            \"columnName\": \"isRegex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"timeoutMillisecond\",\n            \"columnName\": \"timeoutMillisecond\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"3000\"\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"sortOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": true,\n          \"columnNames\": [\n            \"id\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_replace_rules_id\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"id\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_replace_rules_id` ON `${TABLE_NAME}` (`id`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"searchBooks\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookUrl` TEXT NOT NULL, `origin` TEXT NOT NULL, `originName` TEXT NOT NULL, `type` INTEGER NOT NULL, `name` TEXT NOT NULL, `author` TEXT NOT NULL, `kind` TEXT, `coverUrl` TEXT, `intro` TEXT, `wordCount` TEXT, `latestChapterTitle` TEXT, `tocUrl` TEXT NOT NULL, `time` INTEGER NOT NULL, `variable` TEXT, `originOrder` INTEGER NOT NULL, PRIMARY KEY(`bookUrl`), FOREIGN KEY(`origin`) REFERENCES `book_sources`(`bookSourceUrl`) ON UPDATE NO ACTION ON DELETE CASCADE )\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"originName\",\n            \"columnName\": \"originName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"author\",\n            \"columnName\": \"author\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"kind\",\n            \"columnName\": \"kind\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"coverUrl\",\n            \"columnName\": \"coverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"intro\",\n            \"columnName\": \"intro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"wordCount\",\n            \"columnName\": \"wordCount\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"latestChapterTitle\",\n            \"columnName\": \"latestChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"tocUrl\",\n            \"columnName\": \"tocUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"time\",\n            \"columnName\": \"time\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"originOrder\",\n            \"columnName\": \"originOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"bookUrl\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_searchBooks_bookUrl\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"bookUrl\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_searchBooks_bookUrl` ON `${TABLE_NAME}` (`bookUrl`)\"\n          },\n          {\n            \"name\": \"index_searchBooks_origin\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"origin\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_searchBooks_origin` ON `${TABLE_NAME}` (`origin`)\"\n          }\n        ],\n        \"foreignKeys\": [\n          {\n            \"table\": \"book_sources\",\n            \"onDelete\": \"CASCADE\",\n            \"onUpdate\": \"NO ACTION\",\n            \"columns\": [\n              \"origin\"\n            ],\n            \"referencedColumns\": [\n              \"bookSourceUrl\"\n            ]\n          }\n        ]\n      },\n      {\n        \"tableName\": \"search_keywords\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`word` TEXT NOT NULL, `usage` INTEGER NOT NULL, `lastUseTime` INTEGER NOT NULL, PRIMARY KEY(`word`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"word\",\n            \"columnName\": \"word\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"usage\",\n            \"columnName\": \"usage\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"lastUseTime\",\n            \"columnName\": \"lastUseTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"word\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_search_keywords_word\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"word\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_search_keywords_word` ON `${TABLE_NAME}` (`word`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"cookies\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`url` TEXT NOT NULL, `cookie` TEXT NOT NULL, PRIMARY KEY(`url`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"cookie\",\n            \"columnName\": \"cookie\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"url\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_cookies_url\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"url\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_cookies_url` ON `${TABLE_NAME}` (`url`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssSources\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`sourceUrl` TEXT NOT NULL, `sourceName` TEXT NOT NULL, `sourceIcon` TEXT NOT NULL, `sourceGroup` TEXT, `sourceComment` TEXT, `enabled` INTEGER NOT NULL, `variableComment` TEXT, `enabledCookieJar` INTEGER DEFAULT 0, `concurrentRate` TEXT, `header` TEXT, `loginUrl` TEXT, `loginUi` TEXT, `loginCheckJs` TEXT, `sortUrl` TEXT, `singleUrl` INTEGER NOT NULL, `articleStyle` INTEGER NOT NULL, `ruleArticles` TEXT, `ruleNextPage` TEXT, `ruleTitle` TEXT, `rulePubDate` TEXT, `ruleDescription` TEXT, `ruleImage` TEXT, `ruleLink` TEXT, `ruleContent` TEXT, `style` TEXT, `enableJs` INTEGER NOT NULL, `loadWithBaseUrl` INTEGER NOT NULL, `customOrder` INTEGER NOT NULL, PRIMARY KEY(`sourceUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"sourceUrl\",\n            \"columnName\": \"sourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceName\",\n            \"columnName\": \"sourceName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceIcon\",\n            \"columnName\": \"sourceIcon\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceGroup\",\n            \"columnName\": \"sourceGroup\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"sourceComment\",\n            \"columnName\": \"sourceComment\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enabled\",\n            \"columnName\": \"enabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"variableComment\",\n            \"columnName\": \"variableComment\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enabledCookieJar\",\n            \"columnName\": \"enabledCookieJar\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"concurrentRate\",\n            \"columnName\": \"concurrentRate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"header\",\n            \"columnName\": \"header\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUrl\",\n            \"columnName\": \"loginUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUi\",\n            \"columnName\": \"loginUi\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginCheckJs\",\n            \"columnName\": \"loginCheckJs\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"sortUrl\",\n            \"columnName\": \"sortUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"singleUrl\",\n            \"columnName\": \"singleUrl\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"articleStyle\",\n            \"columnName\": \"articleStyle\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"ruleArticles\",\n            \"columnName\": \"ruleArticles\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleNextPage\",\n            \"columnName\": \"ruleNextPage\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleTitle\",\n            \"columnName\": \"ruleTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"rulePubDate\",\n            \"columnName\": \"rulePubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleDescription\",\n            \"columnName\": \"ruleDescription\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleImage\",\n            \"columnName\": \"ruleImage\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleLink\",\n            \"columnName\": \"ruleLink\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleContent\",\n            \"columnName\": \"ruleContent\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"style\",\n            \"columnName\": \"style\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enableJs\",\n            \"columnName\": \"enableJs\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"loadWithBaseUrl\",\n            \"columnName\": \"loadWithBaseUrl\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"sourceUrl\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_rssSources_sourceUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"sourceUrl\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_rssSources_sourceUrl` ON `${TABLE_NAME}` (`sourceUrl`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"bookmarks\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`time` INTEGER NOT NULL, `bookName` TEXT NOT NULL, `bookAuthor` TEXT NOT NULL, `chapterIndex` INTEGER NOT NULL, `chapterPos` INTEGER NOT NULL, `chapterName` TEXT NOT NULL, `bookText` TEXT NOT NULL, `content` TEXT NOT NULL, PRIMARY KEY(`time`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"time\",\n            \"columnName\": \"time\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookName\",\n            \"columnName\": \"bookName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookAuthor\",\n            \"columnName\": \"bookAuthor\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterIndex\",\n            \"columnName\": \"chapterIndex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterPos\",\n            \"columnName\": \"chapterPos\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterName\",\n            \"columnName\": \"chapterName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookText\",\n            \"columnName\": \"bookText\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"time\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_bookmarks_bookName_bookAuthor\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"bookName\",\n              \"bookAuthor\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_bookmarks_bookName_bookAuthor` ON `${TABLE_NAME}` (`bookName`, `bookAuthor`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssArticles\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`origin` TEXT NOT NULL, `sort` TEXT NOT NULL, `title` TEXT NOT NULL, `order` INTEGER NOT NULL, `link` TEXT NOT NULL, `pubDate` TEXT, `description` TEXT, `content` TEXT, `image` TEXT, `read` INTEGER NOT NULL, `variable` TEXT, PRIMARY KEY(`origin`, `link`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sort\",\n            \"columnName\": \"sort\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"link\",\n            \"columnName\": \"link\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"pubDate\",\n            \"columnName\": \"pubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"description\",\n            \"columnName\": \"description\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"image\",\n            \"columnName\": \"image\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"read\",\n            \"columnName\": \"read\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"origin\",\n            \"link\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssReadRecords\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`record` TEXT NOT NULL, `read` INTEGER NOT NULL, PRIMARY KEY(`record`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"record\",\n            \"columnName\": \"record\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"read\",\n            \"columnName\": \"read\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"record\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssStars\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`origin` TEXT NOT NULL, `sort` TEXT NOT NULL, `title` TEXT NOT NULL, `starTime` INTEGER NOT NULL, `link` TEXT NOT NULL, `pubDate` TEXT, `description` TEXT, `content` TEXT, `image` TEXT, `variable` TEXT, PRIMARY KEY(`origin`, `link`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sort\",\n            \"columnName\": \"sort\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"starTime\",\n            \"columnName\": \"starTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"link\",\n            \"columnName\": \"link\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"pubDate\",\n            \"columnName\": \"pubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"description\",\n            \"columnName\": \"description\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"image\",\n            \"columnName\": \"image\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"origin\",\n            \"link\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"txtTocRules\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `rule` TEXT NOT NULL, `example` TEXT, `serialNumber` INTEGER NOT NULL, `enable` INTEGER NOT NULL, PRIMARY KEY(`id`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"rule\",\n            \"columnName\": \"rule\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"example\",\n            \"columnName\": \"example\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"serialNumber\",\n            \"columnName\": \"serialNumber\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enable\",\n            \"columnName\": \"enable\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"id\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"readRecord\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`deviceId` TEXT NOT NULL, `bookName` TEXT NOT NULL, `readTime` INTEGER NOT NULL DEFAULT 0, `lastRead` INTEGER NOT NULL DEFAULT 0, PRIMARY KEY(`deviceId`, `bookName`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"deviceId\",\n            \"columnName\": \"deviceId\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookName\",\n            \"columnName\": \"bookName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"readTime\",\n            \"columnName\": \"readTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"lastRead\",\n            \"columnName\": \"lastRead\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"deviceId\",\n            \"bookName\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"httpTTS\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `url` TEXT NOT NULL, `contentType` TEXT, `concurrentRate` TEXT DEFAULT '0', `loginUrl` TEXT, `loginUi` TEXT, `header` TEXT, `enabledCookieJar` INTEGER DEFAULT 0, `loginCheckJs` TEXT, `lastUpdateTime` INTEGER NOT NULL DEFAULT 0, PRIMARY KEY(`id`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"contentType\",\n            \"columnName\": \"contentType\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"concurrentRate\",\n            \"columnName\": \"concurrentRate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false,\n            \"defaultValue\": \"'0'\"\n          },\n          {\n            \"fieldPath\": \"loginUrl\",\n            \"columnName\": \"loginUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUi\",\n            \"columnName\": \"loginUi\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"header\",\n            \"columnName\": \"header\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enabledCookieJar\",\n            \"columnName\": \"enabledCookieJar\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"loginCheckJs\",\n            \"columnName\": \"loginCheckJs\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"lastUpdateTime\",\n            \"columnName\": \"lastUpdateTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"id\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"caches\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`key` TEXT NOT NULL, `value` TEXT, `deadline` INTEGER NOT NULL, PRIMARY KEY(`key`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"key\",\n            \"columnName\": \"key\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"value\",\n            \"columnName\": \"value\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"deadline\",\n            \"columnName\": \"deadline\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"key\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_caches_key\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"key\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_caches_key` ON `${TABLE_NAME}` (`key`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"ruleSubs\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `url` TEXT NOT NULL, `type` INTEGER NOT NULL, `customOrder` INTEGER NOT NULL, `autoUpdate` INTEGER NOT NULL, `update` INTEGER NOT NULL, PRIMARY KEY(`id`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"autoUpdate\",\n            \"columnName\": \"autoUpdate\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"update\",\n            \"columnName\": \"update\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"id\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"keyboardAssists\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`type` INTEGER NOT NULL DEFAULT 0, `key` TEXT NOT NULL DEFAULT '', `value` TEXT NOT NULL DEFAULT '', `serialNo` INTEGER NOT NULL DEFAULT 0, PRIMARY KEY(`type`, `key`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"key\",\n            \"columnName\": \"key\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"value\",\n            \"columnName\": \"value\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"serialNo\",\n            \"columnName\": \"serialNo\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"type\",\n            \"key\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      }\n    ],\n    \"views\": [],\n    \"setupQueries\": [\n      \"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)\",\n      \"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '0221c385ed7393f47afe9579d3106541')\"\n    ]\n  }\n}"
  },
  {
    "path": "app/schemas/io.legado.app.data.AppDatabase/53.json",
    "content": "{\n  \"formatVersion\": 1,\n  \"database\": {\n    \"version\": 53,\n    \"identityHash\": \"c55dac79aa9cc3dcdb72e91da4282005\",\n    \"entities\": [\n      {\n        \"tableName\": \"books\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookUrl` TEXT NOT NULL DEFAULT '', `tocUrl` TEXT NOT NULL DEFAULT '', `origin` TEXT NOT NULL DEFAULT '', `originName` TEXT NOT NULL DEFAULT '', `name` TEXT NOT NULL DEFAULT '', `author` TEXT NOT NULL DEFAULT '', `kind` TEXT, `customTag` TEXT, `coverUrl` TEXT, `customCoverUrl` TEXT, `intro` TEXT, `customIntro` TEXT, `charset` TEXT, `type` INTEGER NOT NULL DEFAULT 0, `group` INTEGER NOT NULL DEFAULT 0, `latestChapterTitle` TEXT, `latestChapterTime` INTEGER NOT NULL DEFAULT 0, `lastCheckTime` INTEGER NOT NULL DEFAULT 0, `lastCheckCount` INTEGER NOT NULL DEFAULT 0, `totalChapterNum` INTEGER NOT NULL DEFAULT 0, `durChapterTitle` TEXT, `durChapterIndex` INTEGER NOT NULL DEFAULT 0, `durChapterPos` INTEGER NOT NULL DEFAULT 0, `durChapterTime` INTEGER NOT NULL DEFAULT 0, `wordCount` TEXT, `canUpdate` INTEGER NOT NULL DEFAULT 1, `order` INTEGER NOT NULL DEFAULT 0, `originOrder` INTEGER NOT NULL DEFAULT 0, `variable` TEXT, `readConfig` TEXT, PRIMARY KEY(`bookUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"tocUrl\",\n            \"columnName\": \"tocUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"originName\",\n            \"columnName\": \"originName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"author\",\n            \"columnName\": \"author\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"kind\",\n            \"columnName\": \"kind\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customTag\",\n            \"columnName\": \"customTag\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"coverUrl\",\n            \"columnName\": \"coverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customCoverUrl\",\n            \"columnName\": \"customCoverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"intro\",\n            \"columnName\": \"intro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customIntro\",\n            \"columnName\": \"customIntro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"charset\",\n            \"columnName\": \"charset\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"group\",\n            \"columnName\": \"group\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"latestChapterTitle\",\n            \"columnName\": \"latestChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"latestChapterTime\",\n            \"columnName\": \"latestChapterTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"lastCheckTime\",\n            \"columnName\": \"lastCheckTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"lastCheckCount\",\n            \"columnName\": \"lastCheckCount\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"totalChapterNum\",\n            \"columnName\": \"totalChapterNum\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"durChapterTitle\",\n            \"columnName\": \"durChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"durChapterIndex\",\n            \"columnName\": \"durChapterIndex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"durChapterPos\",\n            \"columnName\": \"durChapterPos\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"durChapterTime\",\n            \"columnName\": \"durChapterTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"wordCount\",\n            \"columnName\": \"wordCount\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"canUpdate\",\n            \"columnName\": \"canUpdate\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"originOrder\",\n            \"columnName\": \"originOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"readConfig\",\n            \"columnName\": \"readConfig\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"bookUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_books_name_author\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"name\",\n              \"author\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_books_name_author` ON `${TABLE_NAME}` (`name`, `author`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"book_groups\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`groupId` INTEGER NOT NULL, `groupName` TEXT NOT NULL, `cover` TEXT, `order` INTEGER NOT NULL, `show` INTEGER NOT NULL, PRIMARY KEY(`groupId`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"groupId\",\n            \"columnName\": \"groupId\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"groupName\",\n            \"columnName\": \"groupName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"cover\",\n            \"columnName\": \"cover\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"show\",\n            \"columnName\": \"show\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"groupId\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"book_sources\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookSourceUrl` TEXT NOT NULL, `bookSourceName` TEXT NOT NULL, `bookSourceGroup` TEXT, `bookSourceType` INTEGER NOT NULL, `bookUrlPattern` TEXT, `customOrder` INTEGER NOT NULL, `enabled` INTEGER NOT NULL, `enabledExplore` INTEGER NOT NULL, `enabledReview` INTEGER, `enabledCookieJar` INTEGER DEFAULT 0, `concurrentRate` TEXT, `header` TEXT, `loginUrl` TEXT, `loginUi` TEXT, `loginCheckJs` TEXT, `bookSourceComment` TEXT, `variableComment` TEXT, `lastUpdateTime` INTEGER NOT NULL, `respondTime` INTEGER NOT NULL, `weight` INTEGER NOT NULL, `exploreUrl` TEXT, `ruleExplore` TEXT, `searchUrl` TEXT, `ruleSearch` TEXT, `ruleBookInfo` TEXT, `ruleToc` TEXT, `ruleContent` TEXT, `ruleReview` TEXT, PRIMARY KEY(`bookSourceUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookSourceUrl\",\n            \"columnName\": \"bookSourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookSourceName\",\n            \"columnName\": \"bookSourceName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookSourceGroup\",\n            \"columnName\": \"bookSourceGroup\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"bookSourceType\",\n            \"columnName\": \"bookSourceType\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookUrlPattern\",\n            \"columnName\": \"bookUrlPattern\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enabled\",\n            \"columnName\": \"enabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enabledExplore\",\n            \"columnName\": \"enabledExplore\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enabledReview\",\n            \"columnName\": \"enabledReview\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enabledCookieJar\",\n            \"columnName\": \"enabledCookieJar\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"concurrentRate\",\n            \"columnName\": \"concurrentRate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"header\",\n            \"columnName\": \"header\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUrl\",\n            \"columnName\": \"loginUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUi\",\n            \"columnName\": \"loginUi\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginCheckJs\",\n            \"columnName\": \"loginCheckJs\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"bookSourceComment\",\n            \"columnName\": \"bookSourceComment\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"variableComment\",\n            \"columnName\": \"variableComment\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"lastUpdateTime\",\n            \"columnName\": \"lastUpdateTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"respondTime\",\n            \"columnName\": \"respondTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"weight\",\n            \"columnName\": \"weight\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"exploreUrl\",\n            \"columnName\": \"exploreUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleExplore\",\n            \"columnName\": \"ruleExplore\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"searchUrl\",\n            \"columnName\": \"searchUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleSearch\",\n            \"columnName\": \"ruleSearch\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleBookInfo\",\n            \"columnName\": \"ruleBookInfo\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleToc\",\n            \"columnName\": \"ruleToc\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleContent\",\n            \"columnName\": \"ruleContent\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleReview\",\n            \"columnName\": \"ruleReview\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"bookSourceUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_book_sources_bookSourceUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"bookSourceUrl\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_book_sources_bookSourceUrl` ON `${TABLE_NAME}` (`bookSourceUrl`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"chapters\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`url` TEXT NOT NULL, `title` TEXT NOT NULL, `isVolume` INTEGER NOT NULL, `baseUrl` TEXT NOT NULL, `bookUrl` TEXT NOT NULL, `index` INTEGER NOT NULL, `isVip` INTEGER NOT NULL, `isPay` INTEGER NOT NULL, `resourceUrl` TEXT, `tag` TEXT, `start` INTEGER, `end` INTEGER, `startFragmentId` TEXT, `endFragmentId` TEXT, `variable` TEXT, PRIMARY KEY(`url`, `bookUrl`), FOREIGN KEY(`bookUrl`) REFERENCES `books`(`bookUrl`) ON UPDATE NO ACTION ON DELETE CASCADE )\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"isVolume\",\n            \"columnName\": \"isVolume\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"baseUrl\",\n            \"columnName\": \"baseUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"index\",\n            \"columnName\": \"index\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"isVip\",\n            \"columnName\": \"isVip\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"isPay\",\n            \"columnName\": \"isPay\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"resourceUrl\",\n            \"columnName\": \"resourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"tag\",\n            \"columnName\": \"tag\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"start\",\n            \"columnName\": \"start\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"end\",\n            \"columnName\": \"end\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"startFragmentId\",\n            \"columnName\": \"startFragmentId\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"endFragmentId\",\n            \"columnName\": \"endFragmentId\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"url\",\n            \"bookUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_chapters_bookUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"bookUrl\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_chapters_bookUrl` ON `${TABLE_NAME}` (`bookUrl`)\"\n          },\n          {\n            \"name\": \"index_chapters_bookUrl_index\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"bookUrl\",\n              \"index\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_chapters_bookUrl_index` ON `${TABLE_NAME}` (`bookUrl`, `index`)\"\n          }\n        ],\n        \"foreignKeys\": [\n          {\n            \"table\": \"books\",\n            \"onDelete\": \"CASCADE\",\n            \"onUpdate\": \"NO ACTION\",\n            \"columns\": [\n              \"bookUrl\"\n            ],\n            \"referencedColumns\": [\n              \"bookUrl\"\n            ]\n          }\n        ]\n      },\n      {\n        \"tableName\": \"replace_rules\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL DEFAULT '', `group` TEXT, `pattern` TEXT NOT NULL DEFAULT '', `replacement` TEXT NOT NULL DEFAULT '', `scope` TEXT, `scopeTitle` INTEGER NOT NULL DEFAULT 0, `scopeContent` INTEGER NOT NULL DEFAULT 1, `isEnabled` INTEGER NOT NULL DEFAULT 1, `isRegex` INTEGER NOT NULL DEFAULT 1, `timeoutMillisecond` INTEGER NOT NULL DEFAULT 3000, `sortOrder` INTEGER NOT NULL DEFAULT 0)\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"group\",\n            \"columnName\": \"group\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"pattern\",\n            \"columnName\": \"pattern\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"replacement\",\n            \"columnName\": \"replacement\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"scope\",\n            \"columnName\": \"scope\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"scopeTitle\",\n            \"columnName\": \"scopeTitle\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"scopeContent\",\n            \"columnName\": \"scopeContent\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"isEnabled\",\n            \"columnName\": \"isEnabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"isRegex\",\n            \"columnName\": \"isRegex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"timeoutMillisecond\",\n            \"columnName\": \"timeoutMillisecond\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"3000\"\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"sortOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"id\"\n          ],\n          \"autoGenerate\": true\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_replace_rules_id\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"id\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_replace_rules_id` ON `${TABLE_NAME}` (`id`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"searchBooks\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookUrl` TEXT NOT NULL, `origin` TEXT NOT NULL, `originName` TEXT NOT NULL, `type` INTEGER NOT NULL, `name` TEXT NOT NULL, `author` TEXT NOT NULL, `kind` TEXT, `coverUrl` TEXT, `intro` TEXT, `wordCount` TEXT, `latestChapterTitle` TEXT, `tocUrl` TEXT NOT NULL, `time` INTEGER NOT NULL, `variable` TEXT, `originOrder` INTEGER NOT NULL, PRIMARY KEY(`bookUrl`), FOREIGN KEY(`origin`) REFERENCES `book_sources`(`bookSourceUrl`) ON UPDATE NO ACTION ON DELETE CASCADE )\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"originName\",\n            \"columnName\": \"originName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"author\",\n            \"columnName\": \"author\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"kind\",\n            \"columnName\": \"kind\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"coverUrl\",\n            \"columnName\": \"coverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"intro\",\n            \"columnName\": \"intro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"wordCount\",\n            \"columnName\": \"wordCount\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"latestChapterTitle\",\n            \"columnName\": \"latestChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"tocUrl\",\n            \"columnName\": \"tocUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"time\",\n            \"columnName\": \"time\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"originOrder\",\n            \"columnName\": \"originOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"bookUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_searchBooks_bookUrl\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"bookUrl\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_searchBooks_bookUrl` ON `${TABLE_NAME}` (`bookUrl`)\"\n          },\n          {\n            \"name\": \"index_searchBooks_origin\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"origin\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_searchBooks_origin` ON `${TABLE_NAME}` (`origin`)\"\n          }\n        ],\n        \"foreignKeys\": [\n          {\n            \"table\": \"book_sources\",\n            \"onDelete\": \"CASCADE\",\n            \"onUpdate\": \"NO ACTION\",\n            \"columns\": [\n              \"origin\"\n            ],\n            \"referencedColumns\": [\n              \"bookSourceUrl\"\n            ]\n          }\n        ]\n      },\n      {\n        \"tableName\": \"search_keywords\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`word` TEXT NOT NULL, `usage` INTEGER NOT NULL, `lastUseTime` INTEGER NOT NULL, PRIMARY KEY(`word`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"word\",\n            \"columnName\": \"word\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"usage\",\n            \"columnName\": \"usage\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"lastUseTime\",\n            \"columnName\": \"lastUseTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"word\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_search_keywords_word\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"word\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_search_keywords_word` ON `${TABLE_NAME}` (`word`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"cookies\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`url` TEXT NOT NULL, `cookie` TEXT NOT NULL, PRIMARY KEY(`url`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"cookie\",\n            \"columnName\": \"cookie\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"url\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_cookies_url\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"url\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_cookies_url` ON `${TABLE_NAME}` (`url`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssSources\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`sourceUrl` TEXT NOT NULL, `sourceName` TEXT NOT NULL, `sourceIcon` TEXT NOT NULL, `sourceGroup` TEXT, `sourceComment` TEXT, `enabled` INTEGER NOT NULL, `variableComment` TEXT, `enabledCookieJar` INTEGER DEFAULT 0, `concurrentRate` TEXT, `header` TEXT, `loginUrl` TEXT, `loginUi` TEXT, `loginCheckJs` TEXT, `sortUrl` TEXT, `singleUrl` INTEGER NOT NULL, `articleStyle` INTEGER NOT NULL, `ruleArticles` TEXT, `ruleNextPage` TEXT, `ruleTitle` TEXT, `rulePubDate` TEXT, `ruleDescription` TEXT, `ruleImage` TEXT, `ruleLink` TEXT, `ruleContent` TEXT, `style` TEXT, `enableJs` INTEGER NOT NULL, `loadWithBaseUrl` INTEGER NOT NULL, `customOrder` INTEGER NOT NULL, PRIMARY KEY(`sourceUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"sourceUrl\",\n            \"columnName\": \"sourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceName\",\n            \"columnName\": \"sourceName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceIcon\",\n            \"columnName\": \"sourceIcon\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceGroup\",\n            \"columnName\": \"sourceGroup\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"sourceComment\",\n            \"columnName\": \"sourceComment\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enabled\",\n            \"columnName\": \"enabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"variableComment\",\n            \"columnName\": \"variableComment\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enabledCookieJar\",\n            \"columnName\": \"enabledCookieJar\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"concurrentRate\",\n            \"columnName\": \"concurrentRate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"header\",\n            \"columnName\": \"header\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUrl\",\n            \"columnName\": \"loginUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUi\",\n            \"columnName\": \"loginUi\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginCheckJs\",\n            \"columnName\": \"loginCheckJs\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"sortUrl\",\n            \"columnName\": \"sortUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"singleUrl\",\n            \"columnName\": \"singleUrl\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"articleStyle\",\n            \"columnName\": \"articleStyle\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"ruleArticles\",\n            \"columnName\": \"ruleArticles\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleNextPage\",\n            \"columnName\": \"ruleNextPage\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleTitle\",\n            \"columnName\": \"ruleTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"rulePubDate\",\n            \"columnName\": \"rulePubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleDescription\",\n            \"columnName\": \"ruleDescription\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleImage\",\n            \"columnName\": \"ruleImage\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleLink\",\n            \"columnName\": \"ruleLink\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleContent\",\n            \"columnName\": \"ruleContent\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"style\",\n            \"columnName\": \"style\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enableJs\",\n            \"columnName\": \"enableJs\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"loadWithBaseUrl\",\n            \"columnName\": \"loadWithBaseUrl\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"sourceUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_rssSources_sourceUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"sourceUrl\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_rssSources_sourceUrl` ON `${TABLE_NAME}` (`sourceUrl`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"bookmarks\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`time` INTEGER NOT NULL, `bookName` TEXT NOT NULL, `bookAuthor` TEXT NOT NULL, `chapterIndex` INTEGER NOT NULL, `chapterPos` INTEGER NOT NULL, `chapterName` TEXT NOT NULL, `bookText` TEXT NOT NULL, `content` TEXT NOT NULL, PRIMARY KEY(`time`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"time\",\n            \"columnName\": \"time\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookName\",\n            \"columnName\": \"bookName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookAuthor\",\n            \"columnName\": \"bookAuthor\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterIndex\",\n            \"columnName\": \"chapterIndex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterPos\",\n            \"columnName\": \"chapterPos\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterName\",\n            \"columnName\": \"chapterName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookText\",\n            \"columnName\": \"bookText\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"time\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_bookmarks_bookName_bookAuthor\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"bookName\",\n              \"bookAuthor\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_bookmarks_bookName_bookAuthor` ON `${TABLE_NAME}` (`bookName`, `bookAuthor`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssArticles\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`origin` TEXT NOT NULL, `sort` TEXT NOT NULL, `title` TEXT NOT NULL, `order` INTEGER NOT NULL, `link` TEXT NOT NULL, `pubDate` TEXT, `description` TEXT, `content` TEXT, `image` TEXT, `read` INTEGER NOT NULL, `variable` TEXT, PRIMARY KEY(`origin`, `link`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sort\",\n            \"columnName\": \"sort\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"link\",\n            \"columnName\": \"link\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"pubDate\",\n            \"columnName\": \"pubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"description\",\n            \"columnName\": \"description\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"image\",\n            \"columnName\": \"image\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"read\",\n            \"columnName\": \"read\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"origin\",\n            \"link\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssReadRecords\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`record` TEXT NOT NULL, `read` INTEGER NOT NULL, PRIMARY KEY(`record`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"record\",\n            \"columnName\": \"record\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"read\",\n            \"columnName\": \"read\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"record\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssStars\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`origin` TEXT NOT NULL, `sort` TEXT NOT NULL, `title` TEXT NOT NULL, `starTime` INTEGER NOT NULL, `link` TEXT NOT NULL, `pubDate` TEXT, `description` TEXT, `content` TEXT, `image` TEXT, `variable` TEXT, PRIMARY KEY(`origin`, `link`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sort\",\n            \"columnName\": \"sort\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"starTime\",\n            \"columnName\": \"starTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"link\",\n            \"columnName\": \"link\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"pubDate\",\n            \"columnName\": \"pubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"description\",\n            \"columnName\": \"description\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"image\",\n            \"columnName\": \"image\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"origin\",\n            \"link\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"txtTocRules\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `rule` TEXT NOT NULL, `example` TEXT, `serialNumber` INTEGER NOT NULL, `enable` INTEGER NOT NULL, PRIMARY KEY(`id`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"rule\",\n            \"columnName\": \"rule\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"example\",\n            \"columnName\": \"example\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"serialNumber\",\n            \"columnName\": \"serialNumber\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enable\",\n            \"columnName\": \"enable\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"id\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"readRecord\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`deviceId` TEXT NOT NULL, `bookName` TEXT NOT NULL, `readTime` INTEGER NOT NULL DEFAULT 0, `lastRead` INTEGER NOT NULL DEFAULT 0, PRIMARY KEY(`deviceId`, `bookName`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"deviceId\",\n            \"columnName\": \"deviceId\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookName\",\n            \"columnName\": \"bookName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"readTime\",\n            \"columnName\": \"readTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"lastRead\",\n            \"columnName\": \"lastRead\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"deviceId\",\n            \"bookName\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"httpTTS\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `url` TEXT NOT NULL, `contentType` TEXT, `concurrentRate` TEXT DEFAULT '0', `loginUrl` TEXT, `loginUi` TEXT, `header` TEXT, `enabledCookieJar` INTEGER DEFAULT 0, `loginCheckJs` TEXT, `lastUpdateTime` INTEGER NOT NULL DEFAULT 0, PRIMARY KEY(`id`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"contentType\",\n            \"columnName\": \"contentType\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"concurrentRate\",\n            \"columnName\": \"concurrentRate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false,\n            \"defaultValue\": \"'0'\"\n          },\n          {\n            \"fieldPath\": \"loginUrl\",\n            \"columnName\": \"loginUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUi\",\n            \"columnName\": \"loginUi\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"header\",\n            \"columnName\": \"header\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enabledCookieJar\",\n            \"columnName\": \"enabledCookieJar\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"loginCheckJs\",\n            \"columnName\": \"loginCheckJs\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"lastUpdateTime\",\n            \"columnName\": \"lastUpdateTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"id\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"caches\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`key` TEXT NOT NULL, `value` TEXT, `deadline` INTEGER NOT NULL, PRIMARY KEY(`key`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"key\",\n            \"columnName\": \"key\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"value\",\n            \"columnName\": \"value\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"deadline\",\n            \"columnName\": \"deadline\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"key\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_caches_key\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"key\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_caches_key` ON `${TABLE_NAME}` (`key`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"ruleSubs\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `url` TEXT NOT NULL, `type` INTEGER NOT NULL, `customOrder` INTEGER NOT NULL, `autoUpdate` INTEGER NOT NULL, `update` INTEGER NOT NULL, PRIMARY KEY(`id`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"autoUpdate\",\n            \"columnName\": \"autoUpdate\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"update\",\n            \"columnName\": \"update\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"id\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"keyboardAssists\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`type` INTEGER NOT NULL DEFAULT 0, `key` TEXT NOT NULL DEFAULT '', `value` TEXT NOT NULL DEFAULT '', `serialNo` INTEGER NOT NULL DEFAULT 0, PRIMARY KEY(`type`, `key`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"key\",\n            \"columnName\": \"key\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"value\",\n            \"columnName\": \"value\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"serialNo\",\n            \"columnName\": \"serialNo\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"type\",\n            \"key\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      }\n    ],\n    \"views\": [],\n    \"setupQueries\": [\n      \"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)\",\n      \"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'c55dac79aa9cc3dcdb72e91da4282005')\"\n    ]\n  }\n}"
  },
  {
    "path": "app/schemas/io.legado.app.data.AppDatabase/54.json",
    "content": "{\n  \"formatVersion\": 1,\n  \"database\": {\n    \"version\": 54,\n    \"identityHash\": \"2b32177325d903e84445cc80ad7cbce8\",\n    \"entities\": [\n      {\n        \"tableName\": \"books\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookUrl` TEXT NOT NULL DEFAULT '', `tocUrl` TEXT NOT NULL DEFAULT '', `origin` TEXT NOT NULL DEFAULT '', `originName` TEXT NOT NULL DEFAULT '', `name` TEXT NOT NULL DEFAULT '', `author` TEXT NOT NULL DEFAULT '', `kind` TEXT, `customTag` TEXT, `coverUrl` TEXT, `customCoverUrl` TEXT, `intro` TEXT, `customIntro` TEXT, `charset` TEXT, `type` INTEGER NOT NULL DEFAULT 1, `group` INTEGER NOT NULL DEFAULT 0, `latestChapterTitle` TEXT, `latestChapterTime` INTEGER NOT NULL DEFAULT 0, `lastCheckTime` INTEGER NOT NULL DEFAULT 0, `lastCheckCount` INTEGER NOT NULL DEFAULT 0, `totalChapterNum` INTEGER NOT NULL DEFAULT 0, `durChapterTitle` TEXT, `durChapterIndex` INTEGER NOT NULL DEFAULT 0, `durChapterPos` INTEGER NOT NULL DEFAULT 0, `durChapterTime` INTEGER NOT NULL DEFAULT 0, `wordCount` TEXT, `canUpdate` INTEGER NOT NULL DEFAULT 1, `order` INTEGER NOT NULL DEFAULT 0, `originOrder` INTEGER NOT NULL DEFAULT 0, `variable` TEXT, `readConfig` TEXT, PRIMARY KEY(`bookUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"tocUrl\",\n            \"columnName\": \"tocUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"originName\",\n            \"columnName\": \"originName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"author\",\n            \"columnName\": \"author\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"kind\",\n            \"columnName\": \"kind\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customTag\",\n            \"columnName\": \"customTag\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"coverUrl\",\n            \"columnName\": \"coverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customCoverUrl\",\n            \"columnName\": \"customCoverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"intro\",\n            \"columnName\": \"intro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customIntro\",\n            \"columnName\": \"customIntro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"charset\",\n            \"columnName\": \"charset\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"group\",\n            \"columnName\": \"group\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"latestChapterTitle\",\n            \"columnName\": \"latestChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"latestChapterTime\",\n            \"columnName\": \"latestChapterTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"lastCheckTime\",\n            \"columnName\": \"lastCheckTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"lastCheckCount\",\n            \"columnName\": \"lastCheckCount\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"totalChapterNum\",\n            \"columnName\": \"totalChapterNum\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"durChapterTitle\",\n            \"columnName\": \"durChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"durChapterIndex\",\n            \"columnName\": \"durChapterIndex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"durChapterPos\",\n            \"columnName\": \"durChapterPos\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"durChapterTime\",\n            \"columnName\": \"durChapterTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"wordCount\",\n            \"columnName\": \"wordCount\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"canUpdate\",\n            \"columnName\": \"canUpdate\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"originOrder\",\n            \"columnName\": \"originOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"readConfig\",\n            \"columnName\": \"readConfig\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"bookUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_books_name_author\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"name\",\n              \"author\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_books_name_author` ON `${TABLE_NAME}` (`name`, `author`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"book_groups\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`groupId` INTEGER NOT NULL, `groupName` TEXT NOT NULL, `cover` TEXT, `order` INTEGER NOT NULL, `show` INTEGER NOT NULL, PRIMARY KEY(`groupId`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"groupId\",\n            \"columnName\": \"groupId\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"groupName\",\n            \"columnName\": \"groupName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"cover\",\n            \"columnName\": \"cover\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"show\",\n            \"columnName\": \"show\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"groupId\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"book_sources\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookSourceUrl` TEXT NOT NULL, `bookSourceName` TEXT NOT NULL, `bookSourceGroup` TEXT, `bookSourceType` INTEGER NOT NULL, `bookUrlPattern` TEXT, `customOrder` INTEGER NOT NULL, `enabled` INTEGER NOT NULL, `enabledExplore` INTEGER NOT NULL, `enabledReview` INTEGER, `enabledCookieJar` INTEGER DEFAULT 0, `concurrentRate` TEXT, `header` TEXT, `loginUrl` TEXT, `loginUi` TEXT, `loginCheckJs` TEXT, `coverDecodeJs` TEXT, `bookSourceComment` TEXT, `variableComment` TEXT, `lastUpdateTime` INTEGER NOT NULL, `respondTime` INTEGER NOT NULL, `weight` INTEGER NOT NULL, `exploreUrl` TEXT, `ruleExplore` TEXT, `searchUrl` TEXT, `ruleSearch` TEXT, `ruleBookInfo` TEXT, `ruleToc` TEXT, `ruleContent` TEXT, `ruleReview` TEXT, PRIMARY KEY(`bookSourceUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookSourceUrl\",\n            \"columnName\": \"bookSourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookSourceName\",\n            \"columnName\": \"bookSourceName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookSourceGroup\",\n            \"columnName\": \"bookSourceGroup\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"bookSourceType\",\n            \"columnName\": \"bookSourceType\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookUrlPattern\",\n            \"columnName\": \"bookUrlPattern\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enabled\",\n            \"columnName\": \"enabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enabledExplore\",\n            \"columnName\": \"enabledExplore\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enabledReview\",\n            \"columnName\": \"enabledReview\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enabledCookieJar\",\n            \"columnName\": \"enabledCookieJar\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"concurrentRate\",\n            \"columnName\": \"concurrentRate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"header\",\n            \"columnName\": \"header\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUrl\",\n            \"columnName\": \"loginUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUi\",\n            \"columnName\": \"loginUi\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginCheckJs\",\n            \"columnName\": \"loginCheckJs\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"coverDecodeJs\",\n            \"columnName\": \"coverDecodeJs\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"bookSourceComment\",\n            \"columnName\": \"bookSourceComment\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"variableComment\",\n            \"columnName\": \"variableComment\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"lastUpdateTime\",\n            \"columnName\": \"lastUpdateTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"respondTime\",\n            \"columnName\": \"respondTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"weight\",\n            \"columnName\": \"weight\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"exploreUrl\",\n            \"columnName\": \"exploreUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleExplore\",\n            \"columnName\": \"ruleExplore\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"searchUrl\",\n            \"columnName\": \"searchUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleSearch\",\n            \"columnName\": \"ruleSearch\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleBookInfo\",\n            \"columnName\": \"ruleBookInfo\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleToc\",\n            \"columnName\": \"ruleToc\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleContent\",\n            \"columnName\": \"ruleContent\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleReview\",\n            \"columnName\": \"ruleReview\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"bookSourceUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_book_sources_bookSourceUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"bookSourceUrl\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_book_sources_bookSourceUrl` ON `${TABLE_NAME}` (`bookSourceUrl`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"chapters\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`url` TEXT NOT NULL, `title` TEXT NOT NULL, `isVolume` INTEGER NOT NULL, `baseUrl` TEXT NOT NULL, `bookUrl` TEXT NOT NULL, `index` INTEGER NOT NULL, `isVip` INTEGER NOT NULL, `isPay` INTEGER NOT NULL, `resourceUrl` TEXT, `tag` TEXT, `start` INTEGER, `end` INTEGER, `startFragmentId` TEXT, `endFragmentId` TEXT, `variable` TEXT, PRIMARY KEY(`url`, `bookUrl`), FOREIGN KEY(`bookUrl`) REFERENCES `books`(`bookUrl`) ON UPDATE NO ACTION ON DELETE CASCADE )\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"isVolume\",\n            \"columnName\": \"isVolume\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"baseUrl\",\n            \"columnName\": \"baseUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"index\",\n            \"columnName\": \"index\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"isVip\",\n            \"columnName\": \"isVip\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"isPay\",\n            \"columnName\": \"isPay\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"resourceUrl\",\n            \"columnName\": \"resourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"tag\",\n            \"columnName\": \"tag\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"start\",\n            \"columnName\": \"start\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"end\",\n            \"columnName\": \"end\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"startFragmentId\",\n            \"columnName\": \"startFragmentId\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"endFragmentId\",\n            \"columnName\": \"endFragmentId\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"url\",\n            \"bookUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_chapters_bookUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"bookUrl\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_chapters_bookUrl` ON `${TABLE_NAME}` (`bookUrl`)\"\n          },\n          {\n            \"name\": \"index_chapters_bookUrl_index\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"bookUrl\",\n              \"index\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_chapters_bookUrl_index` ON `${TABLE_NAME}` (`bookUrl`, `index`)\"\n          }\n        ],\n        \"foreignKeys\": [\n          {\n            \"table\": \"books\",\n            \"onDelete\": \"CASCADE\",\n            \"onUpdate\": \"NO ACTION\",\n            \"columns\": [\n              \"bookUrl\"\n            ],\n            \"referencedColumns\": [\n              \"bookUrl\"\n            ]\n          }\n        ]\n      },\n      {\n        \"tableName\": \"replace_rules\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL DEFAULT '', `group` TEXT, `pattern` TEXT NOT NULL DEFAULT '', `replacement` TEXT NOT NULL DEFAULT '', `scope` TEXT, `scopeTitle` INTEGER NOT NULL DEFAULT 0, `scopeContent` INTEGER NOT NULL DEFAULT 1, `isEnabled` INTEGER NOT NULL DEFAULT 1, `isRegex` INTEGER NOT NULL DEFAULT 1, `timeoutMillisecond` INTEGER NOT NULL DEFAULT 3000, `sortOrder` INTEGER NOT NULL DEFAULT 0)\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"group\",\n            \"columnName\": \"group\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"pattern\",\n            \"columnName\": \"pattern\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"replacement\",\n            \"columnName\": \"replacement\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"scope\",\n            \"columnName\": \"scope\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"scopeTitle\",\n            \"columnName\": \"scopeTitle\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"scopeContent\",\n            \"columnName\": \"scopeContent\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"isEnabled\",\n            \"columnName\": \"isEnabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"isRegex\",\n            \"columnName\": \"isRegex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"timeoutMillisecond\",\n            \"columnName\": \"timeoutMillisecond\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"3000\"\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"sortOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"id\"\n          ],\n          \"autoGenerate\": true\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_replace_rules_id\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"id\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_replace_rules_id` ON `${TABLE_NAME}` (`id`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"searchBooks\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookUrl` TEXT NOT NULL, `origin` TEXT NOT NULL, `originName` TEXT NOT NULL, `type` INTEGER NOT NULL, `name` TEXT NOT NULL, `author` TEXT NOT NULL, `kind` TEXT, `coverUrl` TEXT, `intro` TEXT, `wordCount` TEXT, `latestChapterTitle` TEXT, `tocUrl` TEXT NOT NULL, `time` INTEGER NOT NULL, `variable` TEXT, `originOrder` INTEGER NOT NULL, PRIMARY KEY(`bookUrl`), FOREIGN KEY(`origin`) REFERENCES `book_sources`(`bookSourceUrl`) ON UPDATE NO ACTION ON DELETE CASCADE )\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"originName\",\n            \"columnName\": \"originName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"author\",\n            \"columnName\": \"author\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"kind\",\n            \"columnName\": \"kind\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"coverUrl\",\n            \"columnName\": \"coverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"intro\",\n            \"columnName\": \"intro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"wordCount\",\n            \"columnName\": \"wordCount\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"latestChapterTitle\",\n            \"columnName\": \"latestChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"tocUrl\",\n            \"columnName\": \"tocUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"time\",\n            \"columnName\": \"time\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"originOrder\",\n            \"columnName\": \"originOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"bookUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_searchBooks_bookUrl\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"bookUrl\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_searchBooks_bookUrl` ON `${TABLE_NAME}` (`bookUrl`)\"\n          },\n          {\n            \"name\": \"index_searchBooks_origin\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"origin\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_searchBooks_origin` ON `${TABLE_NAME}` (`origin`)\"\n          }\n        ],\n        \"foreignKeys\": [\n          {\n            \"table\": \"book_sources\",\n            \"onDelete\": \"CASCADE\",\n            \"onUpdate\": \"NO ACTION\",\n            \"columns\": [\n              \"origin\"\n            ],\n            \"referencedColumns\": [\n              \"bookSourceUrl\"\n            ]\n          }\n        ]\n      },\n      {\n        \"tableName\": \"search_keywords\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`word` TEXT NOT NULL, `usage` INTEGER NOT NULL, `lastUseTime` INTEGER NOT NULL, PRIMARY KEY(`word`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"word\",\n            \"columnName\": \"word\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"usage\",\n            \"columnName\": \"usage\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"lastUseTime\",\n            \"columnName\": \"lastUseTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"word\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_search_keywords_word\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"word\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_search_keywords_word` ON `${TABLE_NAME}` (`word`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"cookies\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`url` TEXT NOT NULL, `cookie` TEXT NOT NULL, PRIMARY KEY(`url`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"cookie\",\n            \"columnName\": \"cookie\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"url\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_cookies_url\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"url\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_cookies_url` ON `${TABLE_NAME}` (`url`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssSources\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`sourceUrl` TEXT NOT NULL, `sourceName` TEXT NOT NULL, `sourceIcon` TEXT NOT NULL, `sourceGroup` TEXT, `sourceComment` TEXT, `enabled` INTEGER NOT NULL, `variableComment` TEXT, `enabledCookieJar` INTEGER DEFAULT 0, `concurrentRate` TEXT, `header` TEXT, `loginUrl` TEXT, `loginUi` TEXT, `loginCheckJs` TEXT, `coverDecodeJs` TEXT, `sortUrl` TEXT, `singleUrl` INTEGER NOT NULL, `articleStyle` INTEGER NOT NULL, `ruleArticles` TEXT, `ruleNextPage` TEXT, `ruleTitle` TEXT, `rulePubDate` TEXT, `ruleDescription` TEXT, `ruleImage` TEXT, `ruleLink` TEXT, `ruleContent` TEXT, `style` TEXT, `enableJs` INTEGER NOT NULL, `loadWithBaseUrl` INTEGER NOT NULL, `lastUpdateTime` INTEGER NOT NULL DEFAULT 0, `customOrder` INTEGER NOT NULL, PRIMARY KEY(`sourceUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"sourceUrl\",\n            \"columnName\": \"sourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceName\",\n            \"columnName\": \"sourceName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceIcon\",\n            \"columnName\": \"sourceIcon\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceGroup\",\n            \"columnName\": \"sourceGroup\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"sourceComment\",\n            \"columnName\": \"sourceComment\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enabled\",\n            \"columnName\": \"enabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"variableComment\",\n            \"columnName\": \"variableComment\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enabledCookieJar\",\n            \"columnName\": \"enabledCookieJar\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"concurrentRate\",\n            \"columnName\": \"concurrentRate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"header\",\n            \"columnName\": \"header\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUrl\",\n            \"columnName\": \"loginUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUi\",\n            \"columnName\": \"loginUi\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginCheckJs\",\n            \"columnName\": \"loginCheckJs\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"coverDecodeJs\",\n            \"columnName\": \"coverDecodeJs\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"sortUrl\",\n            \"columnName\": \"sortUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"singleUrl\",\n            \"columnName\": \"singleUrl\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"articleStyle\",\n            \"columnName\": \"articleStyle\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"ruleArticles\",\n            \"columnName\": \"ruleArticles\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleNextPage\",\n            \"columnName\": \"ruleNextPage\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleTitle\",\n            \"columnName\": \"ruleTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"rulePubDate\",\n            \"columnName\": \"rulePubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleDescription\",\n            \"columnName\": \"ruleDescription\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleImage\",\n            \"columnName\": \"ruleImage\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleLink\",\n            \"columnName\": \"ruleLink\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleContent\",\n            \"columnName\": \"ruleContent\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"style\",\n            \"columnName\": \"style\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enableJs\",\n            \"columnName\": \"enableJs\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"loadWithBaseUrl\",\n            \"columnName\": \"loadWithBaseUrl\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"lastUpdateTime\",\n            \"columnName\": \"lastUpdateTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"sourceUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_rssSources_sourceUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"sourceUrl\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_rssSources_sourceUrl` ON `${TABLE_NAME}` (`sourceUrl`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"bookmarks\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`time` INTEGER NOT NULL, `bookName` TEXT NOT NULL, `bookAuthor` TEXT NOT NULL, `chapterIndex` INTEGER NOT NULL, `chapterPos` INTEGER NOT NULL, `chapterName` TEXT NOT NULL, `bookText` TEXT NOT NULL, `content` TEXT NOT NULL, PRIMARY KEY(`time`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"time\",\n            \"columnName\": \"time\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookName\",\n            \"columnName\": \"bookName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookAuthor\",\n            \"columnName\": \"bookAuthor\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterIndex\",\n            \"columnName\": \"chapterIndex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterPos\",\n            \"columnName\": \"chapterPos\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterName\",\n            \"columnName\": \"chapterName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookText\",\n            \"columnName\": \"bookText\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"time\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_bookmarks_bookName_bookAuthor\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"bookName\",\n              \"bookAuthor\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_bookmarks_bookName_bookAuthor` ON `${TABLE_NAME}` (`bookName`, `bookAuthor`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssArticles\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`origin` TEXT NOT NULL, `sort` TEXT NOT NULL, `title` TEXT NOT NULL, `order` INTEGER NOT NULL, `link` TEXT NOT NULL, `pubDate` TEXT, `description` TEXT, `content` TEXT, `image` TEXT, `read` INTEGER NOT NULL, `variable` TEXT, PRIMARY KEY(`origin`, `link`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sort\",\n            \"columnName\": \"sort\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"link\",\n            \"columnName\": \"link\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"pubDate\",\n            \"columnName\": \"pubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"description\",\n            \"columnName\": \"description\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"image\",\n            \"columnName\": \"image\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"read\",\n            \"columnName\": \"read\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"origin\",\n            \"link\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssReadRecords\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`record` TEXT NOT NULL, `read` INTEGER NOT NULL, PRIMARY KEY(`record`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"record\",\n            \"columnName\": \"record\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"read\",\n            \"columnName\": \"read\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"record\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssStars\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`origin` TEXT NOT NULL, `sort` TEXT NOT NULL, `title` TEXT NOT NULL, `starTime` INTEGER NOT NULL, `link` TEXT NOT NULL, `pubDate` TEXT, `description` TEXT, `content` TEXT, `image` TEXT, `variable` TEXT, PRIMARY KEY(`origin`, `link`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sort\",\n            \"columnName\": \"sort\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"starTime\",\n            \"columnName\": \"starTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"link\",\n            \"columnName\": \"link\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"pubDate\",\n            \"columnName\": \"pubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"description\",\n            \"columnName\": \"description\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"image\",\n            \"columnName\": \"image\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"origin\",\n            \"link\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"txtTocRules\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `rule` TEXT NOT NULL, `example` TEXT, `serialNumber` INTEGER NOT NULL, `enable` INTEGER NOT NULL, PRIMARY KEY(`id`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"rule\",\n            \"columnName\": \"rule\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"example\",\n            \"columnName\": \"example\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"serialNumber\",\n            \"columnName\": \"serialNumber\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enable\",\n            \"columnName\": \"enable\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"id\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"readRecord\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`deviceId` TEXT NOT NULL, `bookName` TEXT NOT NULL, `readTime` INTEGER NOT NULL DEFAULT 0, `lastRead` INTEGER NOT NULL DEFAULT 0, PRIMARY KEY(`deviceId`, `bookName`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"deviceId\",\n            \"columnName\": \"deviceId\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookName\",\n            \"columnName\": \"bookName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"readTime\",\n            \"columnName\": \"readTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"lastRead\",\n            \"columnName\": \"lastRead\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"deviceId\",\n            \"bookName\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"httpTTS\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `url` TEXT NOT NULL, `contentType` TEXT, `concurrentRate` TEXT DEFAULT '0', `loginUrl` TEXT, `loginUi` TEXT, `header` TEXT, `enabledCookieJar` INTEGER DEFAULT 0, `loginCheckJs` TEXT, `lastUpdateTime` INTEGER NOT NULL DEFAULT 0, PRIMARY KEY(`id`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"contentType\",\n            \"columnName\": \"contentType\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"concurrentRate\",\n            \"columnName\": \"concurrentRate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false,\n            \"defaultValue\": \"'0'\"\n          },\n          {\n            \"fieldPath\": \"loginUrl\",\n            \"columnName\": \"loginUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUi\",\n            \"columnName\": \"loginUi\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"header\",\n            \"columnName\": \"header\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enabledCookieJar\",\n            \"columnName\": \"enabledCookieJar\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"loginCheckJs\",\n            \"columnName\": \"loginCheckJs\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"lastUpdateTime\",\n            \"columnName\": \"lastUpdateTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"id\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"caches\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`key` TEXT NOT NULL, `value` TEXT, `deadline` INTEGER NOT NULL, PRIMARY KEY(`key`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"key\",\n            \"columnName\": \"key\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"value\",\n            \"columnName\": \"value\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"deadline\",\n            \"columnName\": \"deadline\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"key\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_caches_key\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"key\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_caches_key` ON `${TABLE_NAME}` (`key`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"ruleSubs\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `url` TEXT NOT NULL, `type` INTEGER NOT NULL, `customOrder` INTEGER NOT NULL, `autoUpdate` INTEGER NOT NULL, `update` INTEGER NOT NULL, PRIMARY KEY(`id`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"autoUpdate\",\n            \"columnName\": \"autoUpdate\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"update\",\n            \"columnName\": \"update\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"id\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"keyboardAssists\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`type` INTEGER NOT NULL DEFAULT 0, `key` TEXT NOT NULL DEFAULT '', `value` TEXT NOT NULL DEFAULT '', `serialNo` INTEGER NOT NULL DEFAULT 0, PRIMARY KEY(`type`, `key`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"key\",\n            \"columnName\": \"key\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"value\",\n            \"columnName\": \"value\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"serialNo\",\n            \"columnName\": \"serialNo\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"type\",\n            \"key\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      }\n    ],\n    \"views\": [],\n    \"setupQueries\": [\n      \"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)\",\n      \"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '2b32177325d903e84445cc80ad7cbce8')\"\n    ]\n  }\n}"
  },
  {
    "path": "app/schemas/io.legado.app.data.AppDatabase/55.json",
    "content": "{\n  \"formatVersion\": 1,\n  \"database\": {\n    \"version\": 55,\n    \"identityHash\": \"7dc698b0bf395df06befb13d41df87b9\",\n    \"entities\": [\n      {\n        \"tableName\": \"books\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookUrl` TEXT NOT NULL DEFAULT '', `tocUrl` TEXT NOT NULL DEFAULT '', `origin` TEXT NOT NULL DEFAULT 'loc_book', `originName` TEXT NOT NULL DEFAULT '', `name` TEXT NOT NULL DEFAULT '', `author` TEXT NOT NULL DEFAULT '', `kind` TEXT, `customTag` TEXT, `coverUrl` TEXT, `customCoverUrl` TEXT, `intro` TEXT, `customIntro` TEXT, `charset` TEXT, `type` INTEGER NOT NULL DEFAULT 0, `group` INTEGER NOT NULL DEFAULT 0, `latestChapterTitle` TEXT, `latestChapterTime` INTEGER NOT NULL DEFAULT 0, `lastCheckTime` INTEGER NOT NULL DEFAULT 0, `lastCheckCount` INTEGER NOT NULL DEFAULT 0, `totalChapterNum` INTEGER NOT NULL DEFAULT 0, `durChapterTitle` TEXT, `durChapterIndex` INTEGER NOT NULL DEFAULT 0, `durChapterPos` INTEGER NOT NULL DEFAULT 0, `durChapterTime` INTEGER NOT NULL DEFAULT 0, `wordCount` TEXT, `canUpdate` INTEGER NOT NULL DEFAULT 1, `order` INTEGER NOT NULL DEFAULT 0, `originOrder` INTEGER NOT NULL DEFAULT 0, `variable` TEXT, `readConfig` TEXT, PRIMARY KEY(`bookUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"tocUrl\",\n            \"columnName\": \"tocUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"'loc_book'\"\n          },\n          {\n            \"fieldPath\": \"originName\",\n            \"columnName\": \"originName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"author\",\n            \"columnName\": \"author\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"kind\",\n            \"columnName\": \"kind\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customTag\",\n            \"columnName\": \"customTag\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"coverUrl\",\n            \"columnName\": \"coverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customCoverUrl\",\n            \"columnName\": \"customCoverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"intro\",\n            \"columnName\": \"intro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customIntro\",\n            \"columnName\": \"customIntro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"charset\",\n            \"columnName\": \"charset\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"group\",\n            \"columnName\": \"group\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"latestChapterTitle\",\n            \"columnName\": \"latestChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"latestChapterTime\",\n            \"columnName\": \"latestChapterTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"lastCheckTime\",\n            \"columnName\": \"lastCheckTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"lastCheckCount\",\n            \"columnName\": \"lastCheckCount\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"totalChapterNum\",\n            \"columnName\": \"totalChapterNum\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"durChapterTitle\",\n            \"columnName\": \"durChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"durChapterIndex\",\n            \"columnName\": \"durChapterIndex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"durChapterPos\",\n            \"columnName\": \"durChapterPos\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"durChapterTime\",\n            \"columnName\": \"durChapterTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"wordCount\",\n            \"columnName\": \"wordCount\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"canUpdate\",\n            \"columnName\": \"canUpdate\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"originOrder\",\n            \"columnName\": \"originOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"readConfig\",\n            \"columnName\": \"readConfig\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"bookUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_books_name_author\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"name\",\n              \"author\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_books_name_author` ON `${TABLE_NAME}` (`name`, `author`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"book_groups\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`groupId` INTEGER NOT NULL, `groupName` TEXT NOT NULL, `cover` TEXT, `order` INTEGER NOT NULL, `show` INTEGER NOT NULL, PRIMARY KEY(`groupId`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"groupId\",\n            \"columnName\": \"groupId\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"groupName\",\n            \"columnName\": \"groupName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"cover\",\n            \"columnName\": \"cover\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"show\",\n            \"columnName\": \"show\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"groupId\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"book_sources\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookSourceUrl` TEXT NOT NULL, `bookSourceName` TEXT NOT NULL, `bookSourceGroup` TEXT, `bookSourceType` INTEGER NOT NULL, `bookUrlPattern` TEXT, `customOrder` INTEGER NOT NULL, `enabled` INTEGER NOT NULL, `enabledExplore` INTEGER NOT NULL, `enabledReview` INTEGER, `enabledCookieJar` INTEGER DEFAULT 0, `concurrentRate` TEXT, `header` TEXT, `loginUrl` TEXT, `loginUi` TEXT, `loginCheckJs` TEXT, `coverDecodeJs` TEXT, `bookSourceComment` TEXT, `variableComment` TEXT, `lastUpdateTime` INTEGER NOT NULL, `respondTime` INTEGER NOT NULL, `weight` INTEGER NOT NULL, `exploreUrl` TEXT, `ruleExplore` TEXT, `searchUrl` TEXT, `ruleSearch` TEXT, `ruleBookInfo` TEXT, `ruleToc` TEXT, `ruleContent` TEXT, `ruleReview` TEXT, PRIMARY KEY(`bookSourceUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookSourceUrl\",\n            \"columnName\": \"bookSourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookSourceName\",\n            \"columnName\": \"bookSourceName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookSourceGroup\",\n            \"columnName\": \"bookSourceGroup\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"bookSourceType\",\n            \"columnName\": \"bookSourceType\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookUrlPattern\",\n            \"columnName\": \"bookUrlPattern\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enabled\",\n            \"columnName\": \"enabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enabledExplore\",\n            \"columnName\": \"enabledExplore\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enabledReview\",\n            \"columnName\": \"enabledReview\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enabledCookieJar\",\n            \"columnName\": \"enabledCookieJar\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"concurrentRate\",\n            \"columnName\": \"concurrentRate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"header\",\n            \"columnName\": \"header\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUrl\",\n            \"columnName\": \"loginUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUi\",\n            \"columnName\": \"loginUi\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginCheckJs\",\n            \"columnName\": \"loginCheckJs\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"coverDecodeJs\",\n            \"columnName\": \"coverDecodeJs\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"bookSourceComment\",\n            \"columnName\": \"bookSourceComment\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"variableComment\",\n            \"columnName\": \"variableComment\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"lastUpdateTime\",\n            \"columnName\": \"lastUpdateTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"respondTime\",\n            \"columnName\": \"respondTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"weight\",\n            \"columnName\": \"weight\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"exploreUrl\",\n            \"columnName\": \"exploreUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleExplore\",\n            \"columnName\": \"ruleExplore\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"searchUrl\",\n            \"columnName\": \"searchUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleSearch\",\n            \"columnName\": \"ruleSearch\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleBookInfo\",\n            \"columnName\": \"ruleBookInfo\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleToc\",\n            \"columnName\": \"ruleToc\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleContent\",\n            \"columnName\": \"ruleContent\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleReview\",\n            \"columnName\": \"ruleReview\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"bookSourceUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_book_sources_bookSourceUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"bookSourceUrl\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_book_sources_bookSourceUrl` ON `${TABLE_NAME}` (`bookSourceUrl`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"chapters\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`url` TEXT NOT NULL, `title` TEXT NOT NULL, `isVolume` INTEGER NOT NULL, `baseUrl` TEXT NOT NULL, `bookUrl` TEXT NOT NULL, `index` INTEGER NOT NULL, `isVip` INTEGER NOT NULL, `isPay` INTEGER NOT NULL, `resourceUrl` TEXT, `tag` TEXT, `start` INTEGER, `end` INTEGER, `startFragmentId` TEXT, `endFragmentId` TEXT, `variable` TEXT, PRIMARY KEY(`url`, `bookUrl`), FOREIGN KEY(`bookUrl`) REFERENCES `books`(`bookUrl`) ON UPDATE NO ACTION ON DELETE CASCADE )\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"isVolume\",\n            \"columnName\": \"isVolume\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"baseUrl\",\n            \"columnName\": \"baseUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"index\",\n            \"columnName\": \"index\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"isVip\",\n            \"columnName\": \"isVip\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"isPay\",\n            \"columnName\": \"isPay\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"resourceUrl\",\n            \"columnName\": \"resourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"tag\",\n            \"columnName\": \"tag\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"start\",\n            \"columnName\": \"start\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"end\",\n            \"columnName\": \"end\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"startFragmentId\",\n            \"columnName\": \"startFragmentId\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"endFragmentId\",\n            \"columnName\": \"endFragmentId\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"url\",\n            \"bookUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_chapters_bookUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"bookUrl\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_chapters_bookUrl` ON `${TABLE_NAME}` (`bookUrl`)\"\n          },\n          {\n            \"name\": \"index_chapters_bookUrl_index\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"bookUrl\",\n              \"index\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_chapters_bookUrl_index` ON `${TABLE_NAME}` (`bookUrl`, `index`)\"\n          }\n        ],\n        \"foreignKeys\": [\n          {\n            \"table\": \"books\",\n            \"onDelete\": \"CASCADE\",\n            \"onUpdate\": \"NO ACTION\",\n            \"columns\": [\n              \"bookUrl\"\n            ],\n            \"referencedColumns\": [\n              \"bookUrl\"\n            ]\n          }\n        ]\n      },\n      {\n        \"tableName\": \"replace_rules\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL DEFAULT '', `group` TEXT, `pattern` TEXT NOT NULL DEFAULT '', `replacement` TEXT NOT NULL DEFAULT '', `scope` TEXT, `scopeTitle` INTEGER NOT NULL DEFAULT 0, `scopeContent` INTEGER NOT NULL DEFAULT 1, `isEnabled` INTEGER NOT NULL DEFAULT 1, `isRegex` INTEGER NOT NULL DEFAULT 1, `timeoutMillisecond` INTEGER NOT NULL DEFAULT 3000, `sortOrder` INTEGER NOT NULL DEFAULT 0)\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"group\",\n            \"columnName\": \"group\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"pattern\",\n            \"columnName\": \"pattern\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"replacement\",\n            \"columnName\": \"replacement\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"scope\",\n            \"columnName\": \"scope\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"scopeTitle\",\n            \"columnName\": \"scopeTitle\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"scopeContent\",\n            \"columnName\": \"scopeContent\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"isEnabled\",\n            \"columnName\": \"isEnabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"isRegex\",\n            \"columnName\": \"isRegex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"timeoutMillisecond\",\n            \"columnName\": \"timeoutMillisecond\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"3000\"\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"sortOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"id\"\n          ],\n          \"autoGenerate\": true\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_replace_rules_id\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"id\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_replace_rules_id` ON `${TABLE_NAME}` (`id`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"searchBooks\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookUrl` TEXT NOT NULL, `origin` TEXT NOT NULL, `originName` TEXT NOT NULL, `type` INTEGER NOT NULL, `name` TEXT NOT NULL, `author` TEXT NOT NULL, `kind` TEXT, `coverUrl` TEXT, `intro` TEXT, `wordCount` TEXT, `latestChapterTitle` TEXT, `tocUrl` TEXT NOT NULL, `time` INTEGER NOT NULL, `variable` TEXT, `originOrder` INTEGER NOT NULL, PRIMARY KEY(`bookUrl`), FOREIGN KEY(`origin`) REFERENCES `book_sources`(`bookSourceUrl`) ON UPDATE NO ACTION ON DELETE CASCADE )\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"originName\",\n            \"columnName\": \"originName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"author\",\n            \"columnName\": \"author\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"kind\",\n            \"columnName\": \"kind\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"coverUrl\",\n            \"columnName\": \"coverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"intro\",\n            \"columnName\": \"intro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"wordCount\",\n            \"columnName\": \"wordCount\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"latestChapterTitle\",\n            \"columnName\": \"latestChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"tocUrl\",\n            \"columnName\": \"tocUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"time\",\n            \"columnName\": \"time\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"originOrder\",\n            \"columnName\": \"originOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"bookUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_searchBooks_bookUrl\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"bookUrl\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_searchBooks_bookUrl` ON `${TABLE_NAME}` (`bookUrl`)\"\n          },\n          {\n            \"name\": \"index_searchBooks_origin\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"origin\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_searchBooks_origin` ON `${TABLE_NAME}` (`origin`)\"\n          }\n        ],\n        \"foreignKeys\": [\n          {\n            \"table\": \"book_sources\",\n            \"onDelete\": \"CASCADE\",\n            \"onUpdate\": \"NO ACTION\",\n            \"columns\": [\n              \"origin\"\n            ],\n            \"referencedColumns\": [\n              \"bookSourceUrl\"\n            ]\n          }\n        ]\n      },\n      {\n        \"tableName\": \"search_keywords\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`word` TEXT NOT NULL, `usage` INTEGER NOT NULL, `lastUseTime` INTEGER NOT NULL, PRIMARY KEY(`word`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"word\",\n            \"columnName\": \"word\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"usage\",\n            \"columnName\": \"usage\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"lastUseTime\",\n            \"columnName\": \"lastUseTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"word\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_search_keywords_word\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"word\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_search_keywords_word` ON `${TABLE_NAME}` (`word`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"cookies\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`url` TEXT NOT NULL, `cookie` TEXT NOT NULL, PRIMARY KEY(`url`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"cookie\",\n            \"columnName\": \"cookie\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"url\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_cookies_url\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"url\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_cookies_url` ON `${TABLE_NAME}` (`url`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssSources\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`sourceUrl` TEXT NOT NULL, `sourceName` TEXT NOT NULL, `sourceIcon` TEXT NOT NULL, `sourceGroup` TEXT, `sourceComment` TEXT, `enabled` INTEGER NOT NULL, `variableComment` TEXT, `enabledCookieJar` INTEGER DEFAULT 0, `concurrentRate` TEXT, `header` TEXT, `loginUrl` TEXT, `loginUi` TEXT, `loginCheckJs` TEXT, `coverDecodeJs` TEXT, `sortUrl` TEXT, `singleUrl` INTEGER NOT NULL, `articleStyle` INTEGER NOT NULL, `ruleArticles` TEXT, `ruleNextPage` TEXT, `ruleTitle` TEXT, `rulePubDate` TEXT, `ruleDescription` TEXT, `ruleImage` TEXT, `ruleLink` TEXT, `ruleContent` TEXT, `style` TEXT, `enableJs` INTEGER NOT NULL, `loadWithBaseUrl` INTEGER NOT NULL, `lastUpdateTime` INTEGER NOT NULL DEFAULT 0, `customOrder` INTEGER NOT NULL, PRIMARY KEY(`sourceUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"sourceUrl\",\n            \"columnName\": \"sourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceName\",\n            \"columnName\": \"sourceName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceIcon\",\n            \"columnName\": \"sourceIcon\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceGroup\",\n            \"columnName\": \"sourceGroup\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"sourceComment\",\n            \"columnName\": \"sourceComment\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enabled\",\n            \"columnName\": \"enabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"variableComment\",\n            \"columnName\": \"variableComment\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enabledCookieJar\",\n            \"columnName\": \"enabledCookieJar\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"concurrentRate\",\n            \"columnName\": \"concurrentRate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"header\",\n            \"columnName\": \"header\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUrl\",\n            \"columnName\": \"loginUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUi\",\n            \"columnName\": \"loginUi\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginCheckJs\",\n            \"columnName\": \"loginCheckJs\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"coverDecodeJs\",\n            \"columnName\": \"coverDecodeJs\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"sortUrl\",\n            \"columnName\": \"sortUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"singleUrl\",\n            \"columnName\": \"singleUrl\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"articleStyle\",\n            \"columnName\": \"articleStyle\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"ruleArticles\",\n            \"columnName\": \"ruleArticles\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleNextPage\",\n            \"columnName\": \"ruleNextPage\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleTitle\",\n            \"columnName\": \"ruleTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"rulePubDate\",\n            \"columnName\": \"rulePubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleDescription\",\n            \"columnName\": \"ruleDescription\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleImage\",\n            \"columnName\": \"ruleImage\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleLink\",\n            \"columnName\": \"ruleLink\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleContent\",\n            \"columnName\": \"ruleContent\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"style\",\n            \"columnName\": \"style\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enableJs\",\n            \"columnName\": \"enableJs\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"loadWithBaseUrl\",\n            \"columnName\": \"loadWithBaseUrl\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"lastUpdateTime\",\n            \"columnName\": \"lastUpdateTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"sourceUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_rssSources_sourceUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"sourceUrl\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_rssSources_sourceUrl` ON `${TABLE_NAME}` (`sourceUrl`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"bookmarks\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`time` INTEGER NOT NULL, `bookName` TEXT NOT NULL, `bookAuthor` TEXT NOT NULL, `chapterIndex` INTEGER NOT NULL, `chapterPos` INTEGER NOT NULL, `chapterName` TEXT NOT NULL, `bookText` TEXT NOT NULL, `content` TEXT NOT NULL, PRIMARY KEY(`time`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"time\",\n            \"columnName\": \"time\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookName\",\n            \"columnName\": \"bookName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookAuthor\",\n            \"columnName\": \"bookAuthor\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterIndex\",\n            \"columnName\": \"chapterIndex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterPos\",\n            \"columnName\": \"chapterPos\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterName\",\n            \"columnName\": \"chapterName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookText\",\n            \"columnName\": \"bookText\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"time\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_bookmarks_bookName_bookAuthor\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"bookName\",\n              \"bookAuthor\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_bookmarks_bookName_bookAuthor` ON `${TABLE_NAME}` (`bookName`, `bookAuthor`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssArticles\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`origin` TEXT NOT NULL, `sort` TEXT NOT NULL, `title` TEXT NOT NULL, `order` INTEGER NOT NULL, `link` TEXT NOT NULL, `pubDate` TEXT, `description` TEXT, `content` TEXT, `image` TEXT, `read` INTEGER NOT NULL, `variable` TEXT, PRIMARY KEY(`origin`, `link`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sort\",\n            \"columnName\": \"sort\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"link\",\n            \"columnName\": \"link\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"pubDate\",\n            \"columnName\": \"pubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"description\",\n            \"columnName\": \"description\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"image\",\n            \"columnName\": \"image\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"read\",\n            \"columnName\": \"read\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"origin\",\n            \"link\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssReadRecords\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`record` TEXT NOT NULL, `read` INTEGER NOT NULL, PRIMARY KEY(`record`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"record\",\n            \"columnName\": \"record\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"read\",\n            \"columnName\": \"read\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"record\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssStars\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`origin` TEXT NOT NULL, `sort` TEXT NOT NULL, `title` TEXT NOT NULL, `starTime` INTEGER NOT NULL, `link` TEXT NOT NULL, `pubDate` TEXT, `description` TEXT, `content` TEXT, `image` TEXT, `variable` TEXT, PRIMARY KEY(`origin`, `link`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sort\",\n            \"columnName\": \"sort\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"starTime\",\n            \"columnName\": \"starTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"link\",\n            \"columnName\": \"link\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"pubDate\",\n            \"columnName\": \"pubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"description\",\n            \"columnName\": \"description\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"image\",\n            \"columnName\": \"image\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"origin\",\n            \"link\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"txtTocRules\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `rule` TEXT NOT NULL, `example` TEXT, `serialNumber` INTEGER NOT NULL, `enable` INTEGER NOT NULL, PRIMARY KEY(`id`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"rule\",\n            \"columnName\": \"rule\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"example\",\n            \"columnName\": \"example\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"serialNumber\",\n            \"columnName\": \"serialNumber\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enable\",\n            \"columnName\": \"enable\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"id\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"readRecord\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`deviceId` TEXT NOT NULL, `bookName` TEXT NOT NULL, `readTime` INTEGER NOT NULL DEFAULT 0, `lastRead` INTEGER NOT NULL DEFAULT 0, PRIMARY KEY(`deviceId`, `bookName`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"deviceId\",\n            \"columnName\": \"deviceId\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookName\",\n            \"columnName\": \"bookName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"readTime\",\n            \"columnName\": \"readTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"lastRead\",\n            \"columnName\": \"lastRead\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"deviceId\",\n            \"bookName\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"httpTTS\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `url` TEXT NOT NULL, `contentType` TEXT, `concurrentRate` TEXT DEFAULT '0', `loginUrl` TEXT, `loginUi` TEXT, `header` TEXT, `enabledCookieJar` INTEGER DEFAULT 0, `loginCheckJs` TEXT, `lastUpdateTime` INTEGER NOT NULL DEFAULT 0, PRIMARY KEY(`id`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"contentType\",\n            \"columnName\": \"contentType\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"concurrentRate\",\n            \"columnName\": \"concurrentRate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false,\n            \"defaultValue\": \"'0'\"\n          },\n          {\n            \"fieldPath\": \"loginUrl\",\n            \"columnName\": \"loginUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUi\",\n            \"columnName\": \"loginUi\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"header\",\n            \"columnName\": \"header\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enabledCookieJar\",\n            \"columnName\": \"enabledCookieJar\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"loginCheckJs\",\n            \"columnName\": \"loginCheckJs\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"lastUpdateTime\",\n            \"columnName\": \"lastUpdateTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"id\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"caches\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`key` TEXT NOT NULL, `value` TEXT, `deadline` INTEGER NOT NULL, PRIMARY KEY(`key`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"key\",\n            \"columnName\": \"key\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"value\",\n            \"columnName\": \"value\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"deadline\",\n            \"columnName\": \"deadline\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"key\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_caches_key\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"key\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_caches_key` ON `${TABLE_NAME}` (`key`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"ruleSubs\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `url` TEXT NOT NULL, `type` INTEGER NOT NULL, `customOrder` INTEGER NOT NULL, `autoUpdate` INTEGER NOT NULL, `update` INTEGER NOT NULL, PRIMARY KEY(`id`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"autoUpdate\",\n            \"columnName\": \"autoUpdate\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"update\",\n            \"columnName\": \"update\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"id\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"keyboardAssists\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`type` INTEGER NOT NULL DEFAULT 0, `key` TEXT NOT NULL DEFAULT '', `value` TEXT NOT NULL DEFAULT '', `serialNo` INTEGER NOT NULL DEFAULT 0, PRIMARY KEY(`type`, `key`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"key\",\n            \"columnName\": \"key\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"value\",\n            \"columnName\": \"value\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"serialNo\",\n            \"columnName\": \"serialNo\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"type\",\n            \"key\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      }\n    ],\n    \"views\": [],\n    \"setupQueries\": [\n      \"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)\",\n      \"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '7dc698b0bf395df06befb13d41df87b9')\"\n    ]\n  }\n}"
  },
  {
    "path": "app/schemas/io.legado.app.data.AppDatabase/56.json",
    "content": "{\n  \"formatVersion\": 1,\n  \"database\": {\n    \"version\": 56,\n    \"identityHash\": \"7dc698b0bf395df06befb13d41df87b9\",\n    \"entities\": [\n      {\n        \"tableName\": \"books\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookUrl` TEXT NOT NULL DEFAULT '', `tocUrl` TEXT NOT NULL DEFAULT '', `origin` TEXT NOT NULL DEFAULT 'loc_book', `originName` TEXT NOT NULL DEFAULT '', `name` TEXT NOT NULL DEFAULT '', `author` TEXT NOT NULL DEFAULT '', `kind` TEXT, `customTag` TEXT, `coverUrl` TEXT, `customCoverUrl` TEXT, `intro` TEXT, `customIntro` TEXT, `charset` TEXT, `type` INTEGER NOT NULL DEFAULT 0, `group` INTEGER NOT NULL DEFAULT 0, `latestChapterTitle` TEXT, `latestChapterTime` INTEGER NOT NULL DEFAULT 0, `lastCheckTime` INTEGER NOT NULL DEFAULT 0, `lastCheckCount` INTEGER NOT NULL DEFAULT 0, `totalChapterNum` INTEGER NOT NULL DEFAULT 0, `durChapterTitle` TEXT, `durChapterIndex` INTEGER NOT NULL DEFAULT 0, `durChapterPos` INTEGER NOT NULL DEFAULT 0, `durChapterTime` INTEGER NOT NULL DEFAULT 0, `wordCount` TEXT, `canUpdate` INTEGER NOT NULL DEFAULT 1, `order` INTEGER NOT NULL DEFAULT 0, `originOrder` INTEGER NOT NULL DEFAULT 0, `variable` TEXT, `readConfig` TEXT, PRIMARY KEY(`bookUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"tocUrl\",\n            \"columnName\": \"tocUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"'loc_book'\"\n          },\n          {\n            \"fieldPath\": \"originName\",\n            \"columnName\": \"originName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"author\",\n            \"columnName\": \"author\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"kind\",\n            \"columnName\": \"kind\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customTag\",\n            \"columnName\": \"customTag\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"coverUrl\",\n            \"columnName\": \"coverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customCoverUrl\",\n            \"columnName\": \"customCoverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"intro\",\n            \"columnName\": \"intro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customIntro\",\n            \"columnName\": \"customIntro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"charset\",\n            \"columnName\": \"charset\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"group\",\n            \"columnName\": \"group\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"latestChapterTitle\",\n            \"columnName\": \"latestChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"latestChapterTime\",\n            \"columnName\": \"latestChapterTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"lastCheckTime\",\n            \"columnName\": \"lastCheckTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"lastCheckCount\",\n            \"columnName\": \"lastCheckCount\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"totalChapterNum\",\n            \"columnName\": \"totalChapterNum\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"durChapterTitle\",\n            \"columnName\": \"durChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"durChapterIndex\",\n            \"columnName\": \"durChapterIndex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"durChapterPos\",\n            \"columnName\": \"durChapterPos\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"durChapterTime\",\n            \"columnName\": \"durChapterTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"wordCount\",\n            \"columnName\": \"wordCount\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"canUpdate\",\n            \"columnName\": \"canUpdate\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"originOrder\",\n            \"columnName\": \"originOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"readConfig\",\n            \"columnName\": \"readConfig\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"bookUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_books_name_author\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"name\",\n              \"author\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_books_name_author` ON `${TABLE_NAME}` (`name`, `author`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"book_groups\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`groupId` INTEGER NOT NULL, `groupName` TEXT NOT NULL, `cover` TEXT, `order` INTEGER NOT NULL, `show` INTEGER NOT NULL, PRIMARY KEY(`groupId`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"groupId\",\n            \"columnName\": \"groupId\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"groupName\",\n            \"columnName\": \"groupName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"cover\",\n            \"columnName\": \"cover\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"show\",\n            \"columnName\": \"show\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"groupId\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"book_sources\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookSourceUrl` TEXT NOT NULL, `bookSourceName` TEXT NOT NULL, `bookSourceGroup` TEXT, `bookSourceType` INTEGER NOT NULL, `bookUrlPattern` TEXT, `customOrder` INTEGER NOT NULL, `enabled` INTEGER NOT NULL, `enabledExplore` INTEGER NOT NULL, `enabledReview` INTEGER, `enabledCookieJar` INTEGER DEFAULT 0, `concurrentRate` TEXT, `header` TEXT, `loginUrl` TEXT, `loginUi` TEXT, `loginCheckJs` TEXT, `coverDecodeJs` TEXT, `bookSourceComment` TEXT, `variableComment` TEXT, `lastUpdateTime` INTEGER NOT NULL, `respondTime` INTEGER NOT NULL, `weight` INTEGER NOT NULL, `exploreUrl` TEXT, `ruleExplore` TEXT, `searchUrl` TEXT, `ruleSearch` TEXT, `ruleBookInfo` TEXT, `ruleToc` TEXT, `ruleContent` TEXT, `ruleReview` TEXT, PRIMARY KEY(`bookSourceUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookSourceUrl\",\n            \"columnName\": \"bookSourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookSourceName\",\n            \"columnName\": \"bookSourceName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookSourceGroup\",\n            \"columnName\": \"bookSourceGroup\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"bookSourceType\",\n            \"columnName\": \"bookSourceType\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookUrlPattern\",\n            \"columnName\": \"bookUrlPattern\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enabled\",\n            \"columnName\": \"enabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enabledExplore\",\n            \"columnName\": \"enabledExplore\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enabledReview\",\n            \"columnName\": \"enabledReview\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enabledCookieJar\",\n            \"columnName\": \"enabledCookieJar\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"concurrentRate\",\n            \"columnName\": \"concurrentRate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"header\",\n            \"columnName\": \"header\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUrl\",\n            \"columnName\": \"loginUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUi\",\n            \"columnName\": \"loginUi\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginCheckJs\",\n            \"columnName\": \"loginCheckJs\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"coverDecodeJs\",\n            \"columnName\": \"coverDecodeJs\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"bookSourceComment\",\n            \"columnName\": \"bookSourceComment\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"variableComment\",\n            \"columnName\": \"variableComment\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"lastUpdateTime\",\n            \"columnName\": \"lastUpdateTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"respondTime\",\n            \"columnName\": \"respondTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"weight\",\n            \"columnName\": \"weight\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"exploreUrl\",\n            \"columnName\": \"exploreUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleExplore\",\n            \"columnName\": \"ruleExplore\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"searchUrl\",\n            \"columnName\": \"searchUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleSearch\",\n            \"columnName\": \"ruleSearch\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleBookInfo\",\n            \"columnName\": \"ruleBookInfo\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleToc\",\n            \"columnName\": \"ruleToc\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleContent\",\n            \"columnName\": \"ruleContent\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleReview\",\n            \"columnName\": \"ruleReview\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"bookSourceUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_book_sources_bookSourceUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"bookSourceUrl\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_book_sources_bookSourceUrl` ON `${TABLE_NAME}` (`bookSourceUrl`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"chapters\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`url` TEXT NOT NULL, `title` TEXT NOT NULL, `isVolume` INTEGER NOT NULL, `baseUrl` TEXT NOT NULL, `bookUrl` TEXT NOT NULL, `index` INTEGER NOT NULL, `isVip` INTEGER NOT NULL, `isPay` INTEGER NOT NULL, `resourceUrl` TEXT, `tag` TEXT, `start` INTEGER, `end` INTEGER, `startFragmentId` TEXT, `endFragmentId` TEXT, `variable` TEXT, PRIMARY KEY(`url`, `bookUrl`), FOREIGN KEY(`bookUrl`) REFERENCES `books`(`bookUrl`) ON UPDATE NO ACTION ON DELETE CASCADE )\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"isVolume\",\n            \"columnName\": \"isVolume\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"baseUrl\",\n            \"columnName\": \"baseUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"index\",\n            \"columnName\": \"index\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"isVip\",\n            \"columnName\": \"isVip\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"isPay\",\n            \"columnName\": \"isPay\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"resourceUrl\",\n            \"columnName\": \"resourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"tag\",\n            \"columnName\": \"tag\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"start\",\n            \"columnName\": \"start\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"end\",\n            \"columnName\": \"end\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"startFragmentId\",\n            \"columnName\": \"startFragmentId\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"endFragmentId\",\n            \"columnName\": \"endFragmentId\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"url\",\n            \"bookUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_chapters_bookUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"bookUrl\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_chapters_bookUrl` ON `${TABLE_NAME}` (`bookUrl`)\"\n          },\n          {\n            \"name\": \"index_chapters_bookUrl_index\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"bookUrl\",\n              \"index\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_chapters_bookUrl_index` ON `${TABLE_NAME}` (`bookUrl`, `index`)\"\n          }\n        ],\n        \"foreignKeys\": [\n          {\n            \"table\": \"books\",\n            \"onDelete\": \"CASCADE\",\n            \"onUpdate\": \"NO ACTION\",\n            \"columns\": [\n              \"bookUrl\"\n            ],\n            \"referencedColumns\": [\n              \"bookUrl\"\n            ]\n          }\n        ]\n      },\n      {\n        \"tableName\": \"replace_rules\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL DEFAULT '', `group` TEXT, `pattern` TEXT NOT NULL DEFAULT '', `replacement` TEXT NOT NULL DEFAULT '', `scope` TEXT, `scopeTitle` INTEGER NOT NULL DEFAULT 0, `scopeContent` INTEGER NOT NULL DEFAULT 1, `isEnabled` INTEGER NOT NULL DEFAULT 1, `isRegex` INTEGER NOT NULL DEFAULT 1, `timeoutMillisecond` INTEGER NOT NULL DEFAULT 3000, `sortOrder` INTEGER NOT NULL DEFAULT 0)\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"group\",\n            \"columnName\": \"group\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"pattern\",\n            \"columnName\": \"pattern\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"replacement\",\n            \"columnName\": \"replacement\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"scope\",\n            \"columnName\": \"scope\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"scopeTitle\",\n            \"columnName\": \"scopeTitle\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"scopeContent\",\n            \"columnName\": \"scopeContent\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"isEnabled\",\n            \"columnName\": \"isEnabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"isRegex\",\n            \"columnName\": \"isRegex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"timeoutMillisecond\",\n            \"columnName\": \"timeoutMillisecond\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"3000\"\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"sortOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"id\"\n          ],\n          \"autoGenerate\": true\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_replace_rules_id\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"id\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_replace_rules_id` ON `${TABLE_NAME}` (`id`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"searchBooks\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookUrl` TEXT NOT NULL, `origin` TEXT NOT NULL, `originName` TEXT NOT NULL, `type` INTEGER NOT NULL, `name` TEXT NOT NULL, `author` TEXT NOT NULL, `kind` TEXT, `coverUrl` TEXT, `intro` TEXT, `wordCount` TEXT, `latestChapterTitle` TEXT, `tocUrl` TEXT NOT NULL, `time` INTEGER NOT NULL, `variable` TEXT, `originOrder` INTEGER NOT NULL, PRIMARY KEY(`bookUrl`), FOREIGN KEY(`origin`) REFERENCES `book_sources`(`bookSourceUrl`) ON UPDATE NO ACTION ON DELETE CASCADE )\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"originName\",\n            \"columnName\": \"originName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"author\",\n            \"columnName\": \"author\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"kind\",\n            \"columnName\": \"kind\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"coverUrl\",\n            \"columnName\": \"coverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"intro\",\n            \"columnName\": \"intro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"wordCount\",\n            \"columnName\": \"wordCount\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"latestChapterTitle\",\n            \"columnName\": \"latestChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"tocUrl\",\n            \"columnName\": \"tocUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"time\",\n            \"columnName\": \"time\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"originOrder\",\n            \"columnName\": \"originOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"bookUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_searchBooks_bookUrl\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"bookUrl\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_searchBooks_bookUrl` ON `${TABLE_NAME}` (`bookUrl`)\"\n          },\n          {\n            \"name\": \"index_searchBooks_origin\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"origin\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_searchBooks_origin` ON `${TABLE_NAME}` (`origin`)\"\n          }\n        ],\n        \"foreignKeys\": [\n          {\n            \"table\": \"book_sources\",\n            \"onDelete\": \"CASCADE\",\n            \"onUpdate\": \"NO ACTION\",\n            \"columns\": [\n              \"origin\"\n            ],\n            \"referencedColumns\": [\n              \"bookSourceUrl\"\n            ]\n          }\n        ]\n      },\n      {\n        \"tableName\": \"search_keywords\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`word` TEXT NOT NULL, `usage` INTEGER NOT NULL, `lastUseTime` INTEGER NOT NULL, PRIMARY KEY(`word`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"word\",\n            \"columnName\": \"word\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"usage\",\n            \"columnName\": \"usage\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"lastUseTime\",\n            \"columnName\": \"lastUseTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"word\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_search_keywords_word\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"word\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_search_keywords_word` ON `${TABLE_NAME}` (`word`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"cookies\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`url` TEXT NOT NULL, `cookie` TEXT NOT NULL, PRIMARY KEY(`url`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"cookie\",\n            \"columnName\": \"cookie\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"url\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_cookies_url\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"url\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_cookies_url` ON `${TABLE_NAME}` (`url`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssSources\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`sourceUrl` TEXT NOT NULL, `sourceName` TEXT NOT NULL, `sourceIcon` TEXT NOT NULL, `sourceGroup` TEXT, `sourceComment` TEXT, `enabled` INTEGER NOT NULL, `variableComment` TEXT, `enabledCookieJar` INTEGER DEFAULT 0, `concurrentRate` TEXT, `header` TEXT, `loginUrl` TEXT, `loginUi` TEXT, `loginCheckJs` TEXT, `coverDecodeJs` TEXT, `sortUrl` TEXT, `singleUrl` INTEGER NOT NULL, `articleStyle` INTEGER NOT NULL, `ruleArticles` TEXT, `ruleNextPage` TEXT, `ruleTitle` TEXT, `rulePubDate` TEXT, `ruleDescription` TEXT, `ruleImage` TEXT, `ruleLink` TEXT, `ruleContent` TEXT, `style` TEXT, `enableJs` INTEGER NOT NULL, `loadWithBaseUrl` INTEGER NOT NULL, `lastUpdateTime` INTEGER NOT NULL DEFAULT 0, `customOrder` INTEGER NOT NULL, PRIMARY KEY(`sourceUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"sourceUrl\",\n            \"columnName\": \"sourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceName\",\n            \"columnName\": \"sourceName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceIcon\",\n            \"columnName\": \"sourceIcon\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceGroup\",\n            \"columnName\": \"sourceGroup\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"sourceComment\",\n            \"columnName\": \"sourceComment\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enabled\",\n            \"columnName\": \"enabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"variableComment\",\n            \"columnName\": \"variableComment\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enabledCookieJar\",\n            \"columnName\": \"enabledCookieJar\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"concurrentRate\",\n            \"columnName\": \"concurrentRate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"header\",\n            \"columnName\": \"header\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUrl\",\n            \"columnName\": \"loginUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUi\",\n            \"columnName\": \"loginUi\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginCheckJs\",\n            \"columnName\": \"loginCheckJs\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"coverDecodeJs\",\n            \"columnName\": \"coverDecodeJs\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"sortUrl\",\n            \"columnName\": \"sortUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"singleUrl\",\n            \"columnName\": \"singleUrl\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"articleStyle\",\n            \"columnName\": \"articleStyle\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"ruleArticles\",\n            \"columnName\": \"ruleArticles\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleNextPage\",\n            \"columnName\": \"ruleNextPage\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleTitle\",\n            \"columnName\": \"ruleTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"rulePubDate\",\n            \"columnName\": \"rulePubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleDescription\",\n            \"columnName\": \"ruleDescription\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleImage\",\n            \"columnName\": \"ruleImage\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleLink\",\n            \"columnName\": \"ruleLink\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleContent\",\n            \"columnName\": \"ruleContent\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"style\",\n            \"columnName\": \"style\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enableJs\",\n            \"columnName\": \"enableJs\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"loadWithBaseUrl\",\n            \"columnName\": \"loadWithBaseUrl\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"lastUpdateTime\",\n            \"columnName\": \"lastUpdateTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"sourceUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_rssSources_sourceUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"sourceUrl\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_rssSources_sourceUrl` ON `${TABLE_NAME}` (`sourceUrl`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"bookmarks\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`time` INTEGER NOT NULL, `bookName` TEXT NOT NULL, `bookAuthor` TEXT NOT NULL, `chapterIndex` INTEGER NOT NULL, `chapterPos` INTEGER NOT NULL, `chapterName` TEXT NOT NULL, `bookText` TEXT NOT NULL, `content` TEXT NOT NULL, PRIMARY KEY(`time`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"time\",\n            \"columnName\": \"time\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookName\",\n            \"columnName\": \"bookName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookAuthor\",\n            \"columnName\": \"bookAuthor\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterIndex\",\n            \"columnName\": \"chapterIndex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterPos\",\n            \"columnName\": \"chapterPos\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterName\",\n            \"columnName\": \"chapterName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookText\",\n            \"columnName\": \"bookText\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"time\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_bookmarks_bookName_bookAuthor\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"bookName\",\n              \"bookAuthor\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_bookmarks_bookName_bookAuthor` ON `${TABLE_NAME}` (`bookName`, `bookAuthor`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssArticles\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`origin` TEXT NOT NULL, `sort` TEXT NOT NULL, `title` TEXT NOT NULL, `order` INTEGER NOT NULL, `link` TEXT NOT NULL, `pubDate` TEXT, `description` TEXT, `content` TEXT, `image` TEXT, `read` INTEGER NOT NULL, `variable` TEXT, PRIMARY KEY(`origin`, `link`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sort\",\n            \"columnName\": \"sort\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"link\",\n            \"columnName\": \"link\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"pubDate\",\n            \"columnName\": \"pubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"description\",\n            \"columnName\": \"description\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"image\",\n            \"columnName\": \"image\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"read\",\n            \"columnName\": \"read\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"origin\",\n            \"link\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssReadRecords\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`record` TEXT NOT NULL, `read` INTEGER NOT NULL, PRIMARY KEY(`record`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"record\",\n            \"columnName\": \"record\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"read\",\n            \"columnName\": \"read\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"record\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssStars\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`origin` TEXT NOT NULL, `sort` TEXT NOT NULL, `title` TEXT NOT NULL, `starTime` INTEGER NOT NULL, `link` TEXT NOT NULL, `pubDate` TEXT, `description` TEXT, `content` TEXT, `image` TEXT, `variable` TEXT, PRIMARY KEY(`origin`, `link`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sort\",\n            \"columnName\": \"sort\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"starTime\",\n            \"columnName\": \"starTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"link\",\n            \"columnName\": \"link\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"pubDate\",\n            \"columnName\": \"pubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"description\",\n            \"columnName\": \"description\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"image\",\n            \"columnName\": \"image\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"origin\",\n            \"link\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"txtTocRules\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `rule` TEXT NOT NULL, `example` TEXT, `serialNumber` INTEGER NOT NULL, `enable` INTEGER NOT NULL, PRIMARY KEY(`id`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"rule\",\n            \"columnName\": \"rule\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"example\",\n            \"columnName\": \"example\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"serialNumber\",\n            \"columnName\": \"serialNumber\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enable\",\n            \"columnName\": \"enable\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"id\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"readRecord\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`deviceId` TEXT NOT NULL, `bookName` TEXT NOT NULL, `readTime` INTEGER NOT NULL DEFAULT 0, `lastRead` INTEGER NOT NULL DEFAULT 0, PRIMARY KEY(`deviceId`, `bookName`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"deviceId\",\n            \"columnName\": \"deviceId\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookName\",\n            \"columnName\": \"bookName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"readTime\",\n            \"columnName\": \"readTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"lastRead\",\n            \"columnName\": \"lastRead\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"deviceId\",\n            \"bookName\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"httpTTS\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `url` TEXT NOT NULL, `contentType` TEXT, `concurrentRate` TEXT DEFAULT '0', `loginUrl` TEXT, `loginUi` TEXT, `header` TEXT, `enabledCookieJar` INTEGER DEFAULT 0, `loginCheckJs` TEXT, `lastUpdateTime` INTEGER NOT NULL DEFAULT 0, PRIMARY KEY(`id`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"contentType\",\n            \"columnName\": \"contentType\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"concurrentRate\",\n            \"columnName\": \"concurrentRate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false,\n            \"defaultValue\": \"'0'\"\n          },\n          {\n            \"fieldPath\": \"loginUrl\",\n            \"columnName\": \"loginUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUi\",\n            \"columnName\": \"loginUi\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"header\",\n            \"columnName\": \"header\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enabledCookieJar\",\n            \"columnName\": \"enabledCookieJar\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"loginCheckJs\",\n            \"columnName\": \"loginCheckJs\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"lastUpdateTime\",\n            \"columnName\": \"lastUpdateTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"id\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"caches\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`key` TEXT NOT NULL, `value` TEXT, `deadline` INTEGER NOT NULL, PRIMARY KEY(`key`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"key\",\n            \"columnName\": \"key\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"value\",\n            \"columnName\": \"value\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"deadline\",\n            \"columnName\": \"deadline\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"key\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_caches_key\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"key\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_caches_key` ON `${TABLE_NAME}` (`key`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"ruleSubs\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `url` TEXT NOT NULL, `type` INTEGER NOT NULL, `customOrder` INTEGER NOT NULL, `autoUpdate` INTEGER NOT NULL, `update` INTEGER NOT NULL, PRIMARY KEY(`id`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"autoUpdate\",\n            \"columnName\": \"autoUpdate\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"update\",\n            \"columnName\": \"update\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"id\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"keyboardAssists\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`type` INTEGER NOT NULL DEFAULT 0, `key` TEXT NOT NULL DEFAULT '', `value` TEXT NOT NULL DEFAULT '', `serialNo` INTEGER NOT NULL DEFAULT 0, PRIMARY KEY(`type`, `key`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"key\",\n            \"columnName\": \"key\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"value\",\n            \"columnName\": \"value\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"serialNo\",\n            \"columnName\": \"serialNo\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"type\",\n            \"key\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      }\n    ],\n    \"views\": [],\n    \"setupQueries\": [\n      \"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)\",\n      \"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '7dc698b0bf395df06befb13d41df87b9')\"\n    ]\n  }\n}"
  },
  {
    "path": "app/schemas/io.legado.app.data.AppDatabase/57.json",
    "content": "{\n  \"formatVersion\": 1,\n  \"database\": {\n    \"version\": 57,\n    \"identityHash\": \"ae968e35603fe39a0141d0983d993de1\",\n    \"entities\": [\n      {\n        \"tableName\": \"books\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookUrl` TEXT NOT NULL DEFAULT '', `tocUrl` TEXT NOT NULL DEFAULT '', `origin` TEXT NOT NULL DEFAULT 'loc_book', `originName` TEXT NOT NULL DEFAULT '', `name` TEXT NOT NULL DEFAULT '', `author` TEXT NOT NULL DEFAULT '', `kind` TEXT, `customTag` TEXT, `coverUrl` TEXT, `customCoverUrl` TEXT, `intro` TEXT, `customIntro` TEXT, `charset` TEXT, `type` INTEGER NOT NULL DEFAULT 0, `group` INTEGER NOT NULL DEFAULT 0, `latestChapterTitle` TEXT, `latestChapterTime` INTEGER NOT NULL DEFAULT 0, `lastCheckTime` INTEGER NOT NULL DEFAULT 0, `lastCheckCount` INTEGER NOT NULL DEFAULT 0, `totalChapterNum` INTEGER NOT NULL DEFAULT 0, `durChapterTitle` TEXT, `durChapterIndex` INTEGER NOT NULL DEFAULT 0, `durChapterPos` INTEGER NOT NULL DEFAULT 0, `durChapterTime` INTEGER NOT NULL DEFAULT 0, `wordCount` TEXT, `canUpdate` INTEGER NOT NULL DEFAULT 1, `order` INTEGER NOT NULL DEFAULT 0, `originOrder` INTEGER NOT NULL DEFAULT 0, `variable` TEXT, `readConfig` TEXT, PRIMARY KEY(`bookUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"tocUrl\",\n            \"columnName\": \"tocUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"'loc_book'\"\n          },\n          {\n            \"fieldPath\": \"originName\",\n            \"columnName\": \"originName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"author\",\n            \"columnName\": \"author\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"kind\",\n            \"columnName\": \"kind\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customTag\",\n            \"columnName\": \"customTag\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"coverUrl\",\n            \"columnName\": \"coverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customCoverUrl\",\n            \"columnName\": \"customCoverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"intro\",\n            \"columnName\": \"intro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customIntro\",\n            \"columnName\": \"customIntro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"charset\",\n            \"columnName\": \"charset\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"group\",\n            \"columnName\": \"group\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"latestChapterTitle\",\n            \"columnName\": \"latestChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"latestChapterTime\",\n            \"columnName\": \"latestChapterTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"lastCheckTime\",\n            \"columnName\": \"lastCheckTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"lastCheckCount\",\n            \"columnName\": \"lastCheckCount\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"totalChapterNum\",\n            \"columnName\": \"totalChapterNum\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"durChapterTitle\",\n            \"columnName\": \"durChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"durChapterIndex\",\n            \"columnName\": \"durChapterIndex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"durChapterPos\",\n            \"columnName\": \"durChapterPos\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"durChapterTime\",\n            \"columnName\": \"durChapterTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"wordCount\",\n            \"columnName\": \"wordCount\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"canUpdate\",\n            \"columnName\": \"canUpdate\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"originOrder\",\n            \"columnName\": \"originOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"readConfig\",\n            \"columnName\": \"readConfig\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"bookUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_books_name_author\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"name\",\n              \"author\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_books_name_author` ON `${TABLE_NAME}` (`name`, `author`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"book_groups\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`groupId` INTEGER NOT NULL, `groupName` TEXT NOT NULL, `cover` TEXT, `order` INTEGER NOT NULL, `show` INTEGER NOT NULL, PRIMARY KEY(`groupId`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"groupId\",\n            \"columnName\": \"groupId\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"groupName\",\n            \"columnName\": \"groupName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"cover\",\n            \"columnName\": \"cover\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"show\",\n            \"columnName\": \"show\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"groupId\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"book_sources\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookSourceUrl` TEXT NOT NULL, `bookSourceName` TEXT NOT NULL, `bookSourceGroup` TEXT, `bookSourceType` INTEGER NOT NULL, `bookUrlPattern` TEXT, `customOrder` INTEGER NOT NULL, `enabled` INTEGER NOT NULL, `enabledExplore` INTEGER NOT NULL, `enabledReview` INTEGER, `enabledCookieJar` INTEGER DEFAULT 0, `concurrentRate` TEXT, `header` TEXT, `loginUrl` TEXT, `loginUi` TEXT, `loginCheckJs` TEXT, `coverDecodeJs` TEXT, `bookSourceComment` TEXT, `variableComment` TEXT, `lastUpdateTime` INTEGER NOT NULL, `respondTime` INTEGER NOT NULL, `weight` INTEGER NOT NULL, `exploreUrl` TEXT, `ruleExplore` TEXT, `searchUrl` TEXT, `ruleSearch` TEXT, `ruleBookInfo` TEXT, `ruleToc` TEXT, `ruleContent` TEXT, `ruleReview` TEXT, PRIMARY KEY(`bookSourceUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookSourceUrl\",\n            \"columnName\": \"bookSourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookSourceName\",\n            \"columnName\": \"bookSourceName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookSourceGroup\",\n            \"columnName\": \"bookSourceGroup\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"bookSourceType\",\n            \"columnName\": \"bookSourceType\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookUrlPattern\",\n            \"columnName\": \"bookUrlPattern\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enabled\",\n            \"columnName\": \"enabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enabledExplore\",\n            \"columnName\": \"enabledExplore\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enabledReview\",\n            \"columnName\": \"enabledReview\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enabledCookieJar\",\n            \"columnName\": \"enabledCookieJar\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"concurrentRate\",\n            \"columnName\": \"concurrentRate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"header\",\n            \"columnName\": \"header\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUrl\",\n            \"columnName\": \"loginUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUi\",\n            \"columnName\": \"loginUi\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginCheckJs\",\n            \"columnName\": \"loginCheckJs\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"coverDecodeJs\",\n            \"columnName\": \"coverDecodeJs\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"bookSourceComment\",\n            \"columnName\": \"bookSourceComment\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"variableComment\",\n            \"columnName\": \"variableComment\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"lastUpdateTime\",\n            \"columnName\": \"lastUpdateTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"respondTime\",\n            \"columnName\": \"respondTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"weight\",\n            \"columnName\": \"weight\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"exploreUrl\",\n            \"columnName\": \"exploreUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleExplore\",\n            \"columnName\": \"ruleExplore\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"searchUrl\",\n            \"columnName\": \"searchUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleSearch\",\n            \"columnName\": \"ruleSearch\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleBookInfo\",\n            \"columnName\": \"ruleBookInfo\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleToc\",\n            \"columnName\": \"ruleToc\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleContent\",\n            \"columnName\": \"ruleContent\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleReview\",\n            \"columnName\": \"ruleReview\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"bookSourceUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_book_sources_bookSourceUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"bookSourceUrl\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_book_sources_bookSourceUrl` ON `${TABLE_NAME}` (`bookSourceUrl`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"chapters\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`url` TEXT NOT NULL, `title` TEXT NOT NULL, `isVolume` INTEGER NOT NULL, `baseUrl` TEXT NOT NULL, `bookUrl` TEXT NOT NULL, `index` INTEGER NOT NULL, `isVip` INTEGER NOT NULL, `isPay` INTEGER NOT NULL, `resourceUrl` TEXT, `tag` TEXT, `start` INTEGER, `end` INTEGER, `startFragmentId` TEXT, `endFragmentId` TEXT, `variable` TEXT, PRIMARY KEY(`url`, `bookUrl`), FOREIGN KEY(`bookUrl`) REFERENCES `books`(`bookUrl`) ON UPDATE NO ACTION ON DELETE CASCADE )\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"isVolume\",\n            \"columnName\": \"isVolume\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"baseUrl\",\n            \"columnName\": \"baseUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"index\",\n            \"columnName\": \"index\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"isVip\",\n            \"columnName\": \"isVip\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"isPay\",\n            \"columnName\": \"isPay\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"resourceUrl\",\n            \"columnName\": \"resourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"tag\",\n            \"columnName\": \"tag\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"start\",\n            \"columnName\": \"start\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"end\",\n            \"columnName\": \"end\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"startFragmentId\",\n            \"columnName\": \"startFragmentId\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"endFragmentId\",\n            \"columnName\": \"endFragmentId\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"url\",\n            \"bookUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_chapters_bookUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"bookUrl\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_chapters_bookUrl` ON `${TABLE_NAME}` (`bookUrl`)\"\n          },\n          {\n            \"name\": \"index_chapters_bookUrl_index\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"bookUrl\",\n              \"index\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_chapters_bookUrl_index` ON `${TABLE_NAME}` (`bookUrl`, `index`)\"\n          }\n        ],\n        \"foreignKeys\": [\n          {\n            \"table\": \"books\",\n            \"onDelete\": \"CASCADE\",\n            \"onUpdate\": \"NO ACTION\",\n            \"columns\": [\n              \"bookUrl\"\n            ],\n            \"referencedColumns\": [\n              \"bookUrl\"\n            ]\n          }\n        ]\n      },\n      {\n        \"tableName\": \"replace_rules\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL DEFAULT '', `group` TEXT, `pattern` TEXT NOT NULL DEFAULT '', `replacement` TEXT NOT NULL DEFAULT '', `scope` TEXT, `scopeTitle` INTEGER NOT NULL DEFAULT 0, `scopeContent` INTEGER NOT NULL DEFAULT 1, `isEnabled` INTEGER NOT NULL DEFAULT 1, `isRegex` INTEGER NOT NULL DEFAULT 1, `timeoutMillisecond` INTEGER NOT NULL DEFAULT 3000, `sortOrder` INTEGER NOT NULL DEFAULT 0)\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"group\",\n            \"columnName\": \"group\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"pattern\",\n            \"columnName\": \"pattern\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"replacement\",\n            \"columnName\": \"replacement\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"scope\",\n            \"columnName\": \"scope\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"scopeTitle\",\n            \"columnName\": \"scopeTitle\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"scopeContent\",\n            \"columnName\": \"scopeContent\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"isEnabled\",\n            \"columnName\": \"isEnabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"isRegex\",\n            \"columnName\": \"isRegex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"timeoutMillisecond\",\n            \"columnName\": \"timeoutMillisecond\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"3000\"\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"sortOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"id\"\n          ],\n          \"autoGenerate\": true\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_replace_rules_id\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"id\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_replace_rules_id` ON `${TABLE_NAME}` (`id`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"searchBooks\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookUrl` TEXT NOT NULL, `origin` TEXT NOT NULL, `originName` TEXT NOT NULL, `type` INTEGER NOT NULL, `name` TEXT NOT NULL, `author` TEXT NOT NULL, `kind` TEXT, `coverUrl` TEXT, `intro` TEXT, `wordCount` TEXT, `latestChapterTitle` TEXT, `tocUrl` TEXT NOT NULL, `time` INTEGER NOT NULL, `variable` TEXT, `originOrder` INTEGER NOT NULL, PRIMARY KEY(`bookUrl`), FOREIGN KEY(`origin`) REFERENCES `book_sources`(`bookSourceUrl`) ON UPDATE NO ACTION ON DELETE CASCADE )\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"originName\",\n            \"columnName\": \"originName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"author\",\n            \"columnName\": \"author\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"kind\",\n            \"columnName\": \"kind\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"coverUrl\",\n            \"columnName\": \"coverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"intro\",\n            \"columnName\": \"intro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"wordCount\",\n            \"columnName\": \"wordCount\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"latestChapterTitle\",\n            \"columnName\": \"latestChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"tocUrl\",\n            \"columnName\": \"tocUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"time\",\n            \"columnName\": \"time\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"originOrder\",\n            \"columnName\": \"originOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"bookUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_searchBooks_bookUrl\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"bookUrl\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_searchBooks_bookUrl` ON `${TABLE_NAME}` (`bookUrl`)\"\n          },\n          {\n            \"name\": \"index_searchBooks_origin\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"origin\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_searchBooks_origin` ON `${TABLE_NAME}` (`origin`)\"\n          }\n        ],\n        \"foreignKeys\": [\n          {\n            \"table\": \"book_sources\",\n            \"onDelete\": \"CASCADE\",\n            \"onUpdate\": \"NO ACTION\",\n            \"columns\": [\n              \"origin\"\n            ],\n            \"referencedColumns\": [\n              \"bookSourceUrl\"\n            ]\n          }\n        ]\n      },\n      {\n        \"tableName\": \"search_keywords\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`word` TEXT NOT NULL, `usage` INTEGER NOT NULL, `lastUseTime` INTEGER NOT NULL, PRIMARY KEY(`word`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"word\",\n            \"columnName\": \"word\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"usage\",\n            \"columnName\": \"usage\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"lastUseTime\",\n            \"columnName\": \"lastUseTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"word\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_search_keywords_word\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"word\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_search_keywords_word` ON `${TABLE_NAME}` (`word`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"cookies\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`url` TEXT NOT NULL, `cookie` TEXT NOT NULL, PRIMARY KEY(`url`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"cookie\",\n            \"columnName\": \"cookie\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"url\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_cookies_url\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"url\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_cookies_url` ON `${TABLE_NAME}` (`url`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssSources\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`sourceUrl` TEXT NOT NULL, `sourceName` TEXT NOT NULL, `sourceIcon` TEXT NOT NULL, `sourceGroup` TEXT, `sourceComment` TEXT, `enabled` INTEGER NOT NULL, `variableComment` TEXT, `enabledCookieJar` INTEGER DEFAULT 0, `concurrentRate` TEXT, `header` TEXT, `loginUrl` TEXT, `loginUi` TEXT, `loginCheckJs` TEXT, `coverDecodeJs` TEXT, `sortUrl` TEXT, `singleUrl` INTEGER NOT NULL, `articleStyle` INTEGER NOT NULL, `ruleArticles` TEXT, `ruleNextPage` TEXT, `ruleTitle` TEXT, `rulePubDate` TEXT, `ruleDescription` TEXT, `ruleImage` TEXT, `ruleLink` TEXT, `ruleContent` TEXT, `style` TEXT, `enableJs` INTEGER NOT NULL, `loadWithBaseUrl` INTEGER NOT NULL, `injectJs` TEXT, `lastUpdateTime` INTEGER NOT NULL DEFAULT 0, `customOrder` INTEGER NOT NULL, PRIMARY KEY(`sourceUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"sourceUrl\",\n            \"columnName\": \"sourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceName\",\n            \"columnName\": \"sourceName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceIcon\",\n            \"columnName\": \"sourceIcon\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceGroup\",\n            \"columnName\": \"sourceGroup\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"sourceComment\",\n            \"columnName\": \"sourceComment\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enabled\",\n            \"columnName\": \"enabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"variableComment\",\n            \"columnName\": \"variableComment\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enabledCookieJar\",\n            \"columnName\": \"enabledCookieJar\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"concurrentRate\",\n            \"columnName\": \"concurrentRate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"header\",\n            \"columnName\": \"header\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUrl\",\n            \"columnName\": \"loginUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUi\",\n            \"columnName\": \"loginUi\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginCheckJs\",\n            \"columnName\": \"loginCheckJs\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"coverDecodeJs\",\n            \"columnName\": \"coverDecodeJs\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"sortUrl\",\n            \"columnName\": \"sortUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"singleUrl\",\n            \"columnName\": \"singleUrl\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"articleStyle\",\n            \"columnName\": \"articleStyle\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"ruleArticles\",\n            \"columnName\": \"ruleArticles\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleNextPage\",\n            \"columnName\": \"ruleNextPage\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleTitle\",\n            \"columnName\": \"ruleTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"rulePubDate\",\n            \"columnName\": \"rulePubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleDescription\",\n            \"columnName\": \"ruleDescription\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleImage\",\n            \"columnName\": \"ruleImage\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleLink\",\n            \"columnName\": \"ruleLink\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleContent\",\n            \"columnName\": \"ruleContent\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"style\",\n            \"columnName\": \"style\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enableJs\",\n            \"columnName\": \"enableJs\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"loadWithBaseUrl\",\n            \"columnName\": \"loadWithBaseUrl\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"injectJs\",\n            \"columnName\": \"injectJs\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"lastUpdateTime\",\n            \"columnName\": \"lastUpdateTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"sourceUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_rssSources_sourceUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"sourceUrl\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_rssSources_sourceUrl` ON `${TABLE_NAME}` (`sourceUrl`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"bookmarks\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`time` INTEGER NOT NULL, `bookName` TEXT NOT NULL, `bookAuthor` TEXT NOT NULL, `chapterIndex` INTEGER NOT NULL, `chapterPos` INTEGER NOT NULL, `chapterName` TEXT NOT NULL, `bookText` TEXT NOT NULL, `content` TEXT NOT NULL, PRIMARY KEY(`time`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"time\",\n            \"columnName\": \"time\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookName\",\n            \"columnName\": \"bookName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookAuthor\",\n            \"columnName\": \"bookAuthor\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterIndex\",\n            \"columnName\": \"chapterIndex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterPos\",\n            \"columnName\": \"chapterPos\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterName\",\n            \"columnName\": \"chapterName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookText\",\n            \"columnName\": \"bookText\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"time\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_bookmarks_bookName_bookAuthor\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"bookName\",\n              \"bookAuthor\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_bookmarks_bookName_bookAuthor` ON `${TABLE_NAME}` (`bookName`, `bookAuthor`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssArticles\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`origin` TEXT NOT NULL, `sort` TEXT NOT NULL, `title` TEXT NOT NULL, `order` INTEGER NOT NULL, `link` TEXT NOT NULL, `pubDate` TEXT, `description` TEXT, `content` TEXT, `image` TEXT, `read` INTEGER NOT NULL, `variable` TEXT, PRIMARY KEY(`origin`, `link`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sort\",\n            \"columnName\": \"sort\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"link\",\n            \"columnName\": \"link\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"pubDate\",\n            \"columnName\": \"pubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"description\",\n            \"columnName\": \"description\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"image\",\n            \"columnName\": \"image\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"read\",\n            \"columnName\": \"read\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"origin\",\n            \"link\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssReadRecords\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`record` TEXT NOT NULL, `read` INTEGER NOT NULL, PRIMARY KEY(`record`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"record\",\n            \"columnName\": \"record\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"read\",\n            \"columnName\": \"read\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"record\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssStars\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`origin` TEXT NOT NULL, `sort` TEXT NOT NULL, `title` TEXT NOT NULL, `starTime` INTEGER NOT NULL, `link` TEXT NOT NULL, `pubDate` TEXT, `description` TEXT, `content` TEXT, `image` TEXT, `variable` TEXT, PRIMARY KEY(`origin`, `link`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sort\",\n            \"columnName\": \"sort\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"starTime\",\n            \"columnName\": \"starTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"link\",\n            \"columnName\": \"link\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"pubDate\",\n            \"columnName\": \"pubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"description\",\n            \"columnName\": \"description\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"image\",\n            \"columnName\": \"image\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"origin\",\n            \"link\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"txtTocRules\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `rule` TEXT NOT NULL, `example` TEXT, `serialNumber` INTEGER NOT NULL, `enable` INTEGER NOT NULL, PRIMARY KEY(`id`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"rule\",\n            \"columnName\": \"rule\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"example\",\n            \"columnName\": \"example\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"serialNumber\",\n            \"columnName\": \"serialNumber\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enable\",\n            \"columnName\": \"enable\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"id\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"readRecord\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`deviceId` TEXT NOT NULL, `bookName` TEXT NOT NULL, `readTime` INTEGER NOT NULL DEFAULT 0, `lastRead` INTEGER NOT NULL DEFAULT 0, PRIMARY KEY(`deviceId`, `bookName`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"deviceId\",\n            \"columnName\": \"deviceId\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookName\",\n            \"columnName\": \"bookName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"readTime\",\n            \"columnName\": \"readTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"lastRead\",\n            \"columnName\": \"lastRead\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"deviceId\",\n            \"bookName\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"httpTTS\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `url` TEXT NOT NULL, `contentType` TEXT, `concurrentRate` TEXT DEFAULT '0', `loginUrl` TEXT, `loginUi` TEXT, `header` TEXT, `enabledCookieJar` INTEGER DEFAULT 0, `loginCheckJs` TEXT, `lastUpdateTime` INTEGER NOT NULL DEFAULT 0, PRIMARY KEY(`id`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"contentType\",\n            \"columnName\": \"contentType\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"concurrentRate\",\n            \"columnName\": \"concurrentRate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false,\n            \"defaultValue\": \"'0'\"\n          },\n          {\n            \"fieldPath\": \"loginUrl\",\n            \"columnName\": \"loginUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUi\",\n            \"columnName\": \"loginUi\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"header\",\n            \"columnName\": \"header\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enabledCookieJar\",\n            \"columnName\": \"enabledCookieJar\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"loginCheckJs\",\n            \"columnName\": \"loginCheckJs\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"lastUpdateTime\",\n            \"columnName\": \"lastUpdateTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"id\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"caches\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`key` TEXT NOT NULL, `value` TEXT, `deadline` INTEGER NOT NULL, PRIMARY KEY(`key`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"key\",\n            \"columnName\": \"key\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"value\",\n            \"columnName\": \"value\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"deadline\",\n            \"columnName\": \"deadline\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"key\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_caches_key\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"key\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_caches_key` ON `${TABLE_NAME}` (`key`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"ruleSubs\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `url` TEXT NOT NULL, `type` INTEGER NOT NULL, `customOrder` INTEGER NOT NULL, `autoUpdate` INTEGER NOT NULL, `update` INTEGER NOT NULL, PRIMARY KEY(`id`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"autoUpdate\",\n            \"columnName\": \"autoUpdate\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"update\",\n            \"columnName\": \"update\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"id\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"keyboardAssists\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`type` INTEGER NOT NULL DEFAULT 0, `key` TEXT NOT NULL DEFAULT '', `value` TEXT NOT NULL DEFAULT '', `serialNo` INTEGER NOT NULL DEFAULT 0, PRIMARY KEY(`type`, `key`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"key\",\n            \"columnName\": \"key\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"value\",\n            \"columnName\": \"value\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"serialNo\",\n            \"columnName\": \"serialNo\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"type\",\n            \"key\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      }\n    ],\n    \"views\": [],\n    \"setupQueries\": [\n      \"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)\",\n      \"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'ae968e35603fe39a0141d0983d993de1')\"\n    ]\n  }\n}"
  },
  {
    "path": "app/schemas/io.legado.app.data.AppDatabase/58.json",
    "content": "{\n  \"formatVersion\": 1,\n  \"database\": {\n    \"version\": 58,\n    \"identityHash\": \"dce5917572677d2b19bbc43809efb40c\",\n    \"entities\": [\n      {\n        \"tableName\": \"books\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookUrl` TEXT NOT NULL DEFAULT '', `tocUrl` TEXT NOT NULL DEFAULT '', `origin` TEXT NOT NULL DEFAULT 'loc_book', `originName` TEXT NOT NULL DEFAULT '', `name` TEXT NOT NULL DEFAULT '', `author` TEXT NOT NULL DEFAULT '', `kind` TEXT, `customTag` TEXT, `coverUrl` TEXT, `customCoverUrl` TEXT, `intro` TEXT, `customIntro` TEXT, `charset` TEXT, `type` INTEGER NOT NULL DEFAULT 0, `group` INTEGER NOT NULL DEFAULT 0, `latestChapterTitle` TEXT, `latestChapterTime` INTEGER NOT NULL DEFAULT 0, `lastCheckTime` INTEGER NOT NULL DEFAULT 0, `lastCheckCount` INTEGER NOT NULL DEFAULT 0, `totalChapterNum` INTEGER NOT NULL DEFAULT 0, `durChapterTitle` TEXT, `durChapterIndex` INTEGER NOT NULL DEFAULT 0, `durChapterPos` INTEGER NOT NULL DEFAULT 0, `durChapterTime` INTEGER NOT NULL DEFAULT 0, `wordCount` TEXT, `canUpdate` INTEGER NOT NULL DEFAULT 1, `order` INTEGER NOT NULL DEFAULT 0, `originOrder` INTEGER NOT NULL DEFAULT 0, `variable` TEXT, `readConfig` TEXT, PRIMARY KEY(`bookUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"tocUrl\",\n            \"columnName\": \"tocUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"'loc_book'\"\n          },\n          {\n            \"fieldPath\": \"originName\",\n            \"columnName\": \"originName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"author\",\n            \"columnName\": \"author\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"kind\",\n            \"columnName\": \"kind\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customTag\",\n            \"columnName\": \"customTag\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"coverUrl\",\n            \"columnName\": \"coverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customCoverUrl\",\n            \"columnName\": \"customCoverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"intro\",\n            \"columnName\": \"intro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customIntro\",\n            \"columnName\": \"customIntro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"charset\",\n            \"columnName\": \"charset\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"group\",\n            \"columnName\": \"group\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"latestChapterTitle\",\n            \"columnName\": \"latestChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"latestChapterTime\",\n            \"columnName\": \"latestChapterTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"lastCheckTime\",\n            \"columnName\": \"lastCheckTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"lastCheckCount\",\n            \"columnName\": \"lastCheckCount\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"totalChapterNum\",\n            \"columnName\": \"totalChapterNum\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"durChapterTitle\",\n            \"columnName\": \"durChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"durChapterIndex\",\n            \"columnName\": \"durChapterIndex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"durChapterPos\",\n            \"columnName\": \"durChapterPos\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"durChapterTime\",\n            \"columnName\": \"durChapterTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"wordCount\",\n            \"columnName\": \"wordCount\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"canUpdate\",\n            \"columnName\": \"canUpdate\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"originOrder\",\n            \"columnName\": \"originOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"readConfig\",\n            \"columnName\": \"readConfig\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"bookUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_books_name_author\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"name\",\n              \"author\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_books_name_author` ON `${TABLE_NAME}` (`name`, `author`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"book_groups\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`groupId` INTEGER NOT NULL, `groupName` TEXT NOT NULL, `cover` TEXT, `order` INTEGER NOT NULL, `show` INTEGER NOT NULL, `bookSort` INTEGER NOT NULL DEFAULT -1, PRIMARY KEY(`groupId`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"groupId\",\n            \"columnName\": \"groupId\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"groupName\",\n            \"columnName\": \"groupName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"cover\",\n            \"columnName\": \"cover\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"show\",\n            \"columnName\": \"show\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookSort\",\n            \"columnName\": \"bookSort\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"-1\"\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"groupId\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"book_sources\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookSourceUrl` TEXT NOT NULL, `bookSourceName` TEXT NOT NULL, `bookSourceGroup` TEXT, `bookSourceType` INTEGER NOT NULL, `bookUrlPattern` TEXT, `customOrder` INTEGER NOT NULL, `enabled` INTEGER NOT NULL, `enabledExplore` INTEGER NOT NULL, `enabledReview` INTEGER, `enabledCookieJar` INTEGER DEFAULT 0, `concurrentRate` TEXT, `header` TEXT, `loginUrl` TEXT, `loginUi` TEXT, `loginCheckJs` TEXT, `coverDecodeJs` TEXT, `bookSourceComment` TEXT, `variableComment` TEXT, `lastUpdateTime` INTEGER NOT NULL, `respondTime` INTEGER NOT NULL, `weight` INTEGER NOT NULL, `exploreUrl` TEXT, `ruleExplore` TEXT, `searchUrl` TEXT, `ruleSearch` TEXT, `ruleBookInfo` TEXT, `ruleToc` TEXT, `ruleContent` TEXT, `ruleReview` TEXT, PRIMARY KEY(`bookSourceUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookSourceUrl\",\n            \"columnName\": \"bookSourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookSourceName\",\n            \"columnName\": \"bookSourceName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookSourceGroup\",\n            \"columnName\": \"bookSourceGroup\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"bookSourceType\",\n            \"columnName\": \"bookSourceType\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookUrlPattern\",\n            \"columnName\": \"bookUrlPattern\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enabled\",\n            \"columnName\": \"enabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enabledExplore\",\n            \"columnName\": \"enabledExplore\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enabledReview\",\n            \"columnName\": \"enabledReview\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enabledCookieJar\",\n            \"columnName\": \"enabledCookieJar\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"concurrentRate\",\n            \"columnName\": \"concurrentRate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"header\",\n            \"columnName\": \"header\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUrl\",\n            \"columnName\": \"loginUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUi\",\n            \"columnName\": \"loginUi\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginCheckJs\",\n            \"columnName\": \"loginCheckJs\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"coverDecodeJs\",\n            \"columnName\": \"coverDecodeJs\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"bookSourceComment\",\n            \"columnName\": \"bookSourceComment\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"variableComment\",\n            \"columnName\": \"variableComment\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"lastUpdateTime\",\n            \"columnName\": \"lastUpdateTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"respondTime\",\n            \"columnName\": \"respondTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"weight\",\n            \"columnName\": \"weight\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"exploreUrl\",\n            \"columnName\": \"exploreUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleExplore\",\n            \"columnName\": \"ruleExplore\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"searchUrl\",\n            \"columnName\": \"searchUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleSearch\",\n            \"columnName\": \"ruleSearch\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleBookInfo\",\n            \"columnName\": \"ruleBookInfo\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleToc\",\n            \"columnName\": \"ruleToc\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleContent\",\n            \"columnName\": \"ruleContent\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleReview\",\n            \"columnName\": \"ruleReview\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"bookSourceUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_book_sources_bookSourceUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"bookSourceUrl\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_book_sources_bookSourceUrl` ON `${TABLE_NAME}` (`bookSourceUrl`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"chapters\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`url` TEXT NOT NULL, `title` TEXT NOT NULL, `isVolume` INTEGER NOT NULL, `baseUrl` TEXT NOT NULL, `bookUrl` TEXT NOT NULL, `index` INTEGER NOT NULL, `isVip` INTEGER NOT NULL, `isPay` INTEGER NOT NULL, `resourceUrl` TEXT, `tag` TEXT, `start` INTEGER, `end` INTEGER, `startFragmentId` TEXT, `endFragmentId` TEXT, `variable` TEXT, PRIMARY KEY(`url`, `bookUrl`), FOREIGN KEY(`bookUrl`) REFERENCES `books`(`bookUrl`) ON UPDATE NO ACTION ON DELETE CASCADE )\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"isVolume\",\n            \"columnName\": \"isVolume\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"baseUrl\",\n            \"columnName\": \"baseUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"index\",\n            \"columnName\": \"index\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"isVip\",\n            \"columnName\": \"isVip\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"isPay\",\n            \"columnName\": \"isPay\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"resourceUrl\",\n            \"columnName\": \"resourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"tag\",\n            \"columnName\": \"tag\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"start\",\n            \"columnName\": \"start\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"end\",\n            \"columnName\": \"end\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"startFragmentId\",\n            \"columnName\": \"startFragmentId\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"endFragmentId\",\n            \"columnName\": \"endFragmentId\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"url\",\n            \"bookUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_chapters_bookUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"bookUrl\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_chapters_bookUrl` ON `${TABLE_NAME}` (`bookUrl`)\"\n          },\n          {\n            \"name\": \"index_chapters_bookUrl_index\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"bookUrl\",\n              \"index\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_chapters_bookUrl_index` ON `${TABLE_NAME}` (`bookUrl`, `index`)\"\n          }\n        ],\n        \"foreignKeys\": [\n          {\n            \"table\": \"books\",\n            \"onDelete\": \"CASCADE\",\n            \"onUpdate\": \"NO ACTION\",\n            \"columns\": [\n              \"bookUrl\"\n            ],\n            \"referencedColumns\": [\n              \"bookUrl\"\n            ]\n          }\n        ]\n      },\n      {\n        \"tableName\": \"replace_rules\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL DEFAULT '', `group` TEXT, `pattern` TEXT NOT NULL DEFAULT '', `replacement` TEXT NOT NULL DEFAULT '', `scope` TEXT, `scopeTitle` INTEGER NOT NULL DEFAULT 0, `scopeContent` INTEGER NOT NULL DEFAULT 1, `isEnabled` INTEGER NOT NULL DEFAULT 1, `isRegex` INTEGER NOT NULL DEFAULT 1, `timeoutMillisecond` INTEGER NOT NULL DEFAULT 3000, `sortOrder` INTEGER NOT NULL DEFAULT 0)\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"group\",\n            \"columnName\": \"group\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"pattern\",\n            \"columnName\": \"pattern\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"replacement\",\n            \"columnName\": \"replacement\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"scope\",\n            \"columnName\": \"scope\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"scopeTitle\",\n            \"columnName\": \"scopeTitle\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"scopeContent\",\n            \"columnName\": \"scopeContent\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"isEnabled\",\n            \"columnName\": \"isEnabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"isRegex\",\n            \"columnName\": \"isRegex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"timeoutMillisecond\",\n            \"columnName\": \"timeoutMillisecond\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"3000\"\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"sortOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"id\"\n          ],\n          \"autoGenerate\": true\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_replace_rules_id\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"id\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_replace_rules_id` ON `${TABLE_NAME}` (`id`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"searchBooks\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookUrl` TEXT NOT NULL, `origin` TEXT NOT NULL, `originName` TEXT NOT NULL, `type` INTEGER NOT NULL, `name` TEXT NOT NULL, `author` TEXT NOT NULL, `kind` TEXT, `coverUrl` TEXT, `intro` TEXT, `wordCount` TEXT, `latestChapterTitle` TEXT, `tocUrl` TEXT NOT NULL, `time` INTEGER NOT NULL, `variable` TEXT, `originOrder` INTEGER NOT NULL, PRIMARY KEY(`bookUrl`), FOREIGN KEY(`origin`) REFERENCES `book_sources`(`bookSourceUrl`) ON UPDATE NO ACTION ON DELETE CASCADE )\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"originName\",\n            \"columnName\": \"originName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"author\",\n            \"columnName\": \"author\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"kind\",\n            \"columnName\": \"kind\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"coverUrl\",\n            \"columnName\": \"coverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"intro\",\n            \"columnName\": \"intro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"wordCount\",\n            \"columnName\": \"wordCount\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"latestChapterTitle\",\n            \"columnName\": \"latestChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"tocUrl\",\n            \"columnName\": \"tocUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"time\",\n            \"columnName\": \"time\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"originOrder\",\n            \"columnName\": \"originOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"bookUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_searchBooks_bookUrl\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"bookUrl\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_searchBooks_bookUrl` ON `${TABLE_NAME}` (`bookUrl`)\"\n          },\n          {\n            \"name\": \"index_searchBooks_origin\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"origin\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_searchBooks_origin` ON `${TABLE_NAME}` (`origin`)\"\n          }\n        ],\n        \"foreignKeys\": [\n          {\n            \"table\": \"book_sources\",\n            \"onDelete\": \"CASCADE\",\n            \"onUpdate\": \"NO ACTION\",\n            \"columns\": [\n              \"origin\"\n            ],\n            \"referencedColumns\": [\n              \"bookSourceUrl\"\n            ]\n          }\n        ]\n      },\n      {\n        \"tableName\": \"search_keywords\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`word` TEXT NOT NULL, `usage` INTEGER NOT NULL, `lastUseTime` INTEGER NOT NULL, PRIMARY KEY(`word`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"word\",\n            \"columnName\": \"word\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"usage\",\n            \"columnName\": \"usage\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"lastUseTime\",\n            \"columnName\": \"lastUseTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"word\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_search_keywords_word\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"word\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_search_keywords_word` ON `${TABLE_NAME}` (`word`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"cookies\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`url` TEXT NOT NULL, `cookie` TEXT NOT NULL, PRIMARY KEY(`url`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"cookie\",\n            \"columnName\": \"cookie\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"url\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_cookies_url\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"url\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_cookies_url` ON `${TABLE_NAME}` (`url`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssSources\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`sourceUrl` TEXT NOT NULL, `sourceName` TEXT NOT NULL, `sourceIcon` TEXT NOT NULL, `sourceGroup` TEXT, `sourceComment` TEXT, `enabled` INTEGER NOT NULL, `variableComment` TEXT, `enabledCookieJar` INTEGER DEFAULT 0, `concurrentRate` TEXT, `header` TEXT, `loginUrl` TEXT, `loginUi` TEXT, `loginCheckJs` TEXT, `coverDecodeJs` TEXT, `sortUrl` TEXT, `singleUrl` INTEGER NOT NULL, `articleStyle` INTEGER NOT NULL, `ruleArticles` TEXT, `ruleNextPage` TEXT, `ruleTitle` TEXT, `rulePubDate` TEXT, `ruleDescription` TEXT, `ruleImage` TEXT, `ruleLink` TEXT, `ruleContent` TEXT, `style` TEXT, `enableJs` INTEGER NOT NULL, `loadWithBaseUrl` INTEGER NOT NULL, `injectJs` TEXT, `lastUpdateTime` INTEGER NOT NULL DEFAULT 0, `customOrder` INTEGER NOT NULL, PRIMARY KEY(`sourceUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"sourceUrl\",\n            \"columnName\": \"sourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceName\",\n            \"columnName\": \"sourceName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceIcon\",\n            \"columnName\": \"sourceIcon\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceGroup\",\n            \"columnName\": \"sourceGroup\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"sourceComment\",\n            \"columnName\": \"sourceComment\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enabled\",\n            \"columnName\": \"enabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"variableComment\",\n            \"columnName\": \"variableComment\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enabledCookieJar\",\n            \"columnName\": \"enabledCookieJar\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"concurrentRate\",\n            \"columnName\": \"concurrentRate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"header\",\n            \"columnName\": \"header\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUrl\",\n            \"columnName\": \"loginUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUi\",\n            \"columnName\": \"loginUi\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginCheckJs\",\n            \"columnName\": \"loginCheckJs\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"coverDecodeJs\",\n            \"columnName\": \"coverDecodeJs\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"sortUrl\",\n            \"columnName\": \"sortUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"singleUrl\",\n            \"columnName\": \"singleUrl\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"articleStyle\",\n            \"columnName\": \"articleStyle\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"ruleArticles\",\n            \"columnName\": \"ruleArticles\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleNextPage\",\n            \"columnName\": \"ruleNextPage\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleTitle\",\n            \"columnName\": \"ruleTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"rulePubDate\",\n            \"columnName\": \"rulePubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleDescription\",\n            \"columnName\": \"ruleDescription\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleImage\",\n            \"columnName\": \"ruleImage\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleLink\",\n            \"columnName\": \"ruleLink\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleContent\",\n            \"columnName\": \"ruleContent\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"style\",\n            \"columnName\": \"style\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enableJs\",\n            \"columnName\": \"enableJs\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"loadWithBaseUrl\",\n            \"columnName\": \"loadWithBaseUrl\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"injectJs\",\n            \"columnName\": \"injectJs\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"lastUpdateTime\",\n            \"columnName\": \"lastUpdateTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"sourceUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_rssSources_sourceUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"sourceUrl\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_rssSources_sourceUrl` ON `${TABLE_NAME}` (`sourceUrl`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"bookmarks\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`time` INTEGER NOT NULL, `bookName` TEXT NOT NULL, `bookAuthor` TEXT NOT NULL, `chapterIndex` INTEGER NOT NULL, `chapterPos` INTEGER NOT NULL, `chapterName` TEXT NOT NULL, `bookText` TEXT NOT NULL, `content` TEXT NOT NULL, PRIMARY KEY(`time`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"time\",\n            \"columnName\": \"time\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookName\",\n            \"columnName\": \"bookName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookAuthor\",\n            \"columnName\": \"bookAuthor\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterIndex\",\n            \"columnName\": \"chapterIndex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterPos\",\n            \"columnName\": \"chapterPos\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterName\",\n            \"columnName\": \"chapterName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookText\",\n            \"columnName\": \"bookText\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"time\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_bookmarks_bookName_bookAuthor\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"bookName\",\n              \"bookAuthor\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_bookmarks_bookName_bookAuthor` ON `${TABLE_NAME}` (`bookName`, `bookAuthor`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssArticles\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`origin` TEXT NOT NULL, `sort` TEXT NOT NULL, `title` TEXT NOT NULL, `order` INTEGER NOT NULL, `link` TEXT NOT NULL, `pubDate` TEXT, `description` TEXT, `content` TEXT, `image` TEXT, `read` INTEGER NOT NULL, `variable` TEXT, PRIMARY KEY(`origin`, `link`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sort\",\n            \"columnName\": \"sort\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"link\",\n            \"columnName\": \"link\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"pubDate\",\n            \"columnName\": \"pubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"description\",\n            \"columnName\": \"description\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"image\",\n            \"columnName\": \"image\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"read\",\n            \"columnName\": \"read\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"origin\",\n            \"link\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssReadRecords\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`record` TEXT NOT NULL, `read` INTEGER NOT NULL, PRIMARY KEY(`record`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"record\",\n            \"columnName\": \"record\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"read\",\n            \"columnName\": \"read\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"record\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssStars\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`origin` TEXT NOT NULL, `sort` TEXT NOT NULL, `title` TEXT NOT NULL, `starTime` INTEGER NOT NULL, `link` TEXT NOT NULL, `pubDate` TEXT, `description` TEXT, `content` TEXT, `image` TEXT, `variable` TEXT, PRIMARY KEY(`origin`, `link`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sort\",\n            \"columnName\": \"sort\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"starTime\",\n            \"columnName\": \"starTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"link\",\n            \"columnName\": \"link\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"pubDate\",\n            \"columnName\": \"pubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"description\",\n            \"columnName\": \"description\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"image\",\n            \"columnName\": \"image\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"origin\",\n            \"link\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"txtTocRules\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `rule` TEXT NOT NULL, `example` TEXT, `serialNumber` INTEGER NOT NULL, `enable` INTEGER NOT NULL, PRIMARY KEY(`id`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"rule\",\n            \"columnName\": \"rule\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"example\",\n            \"columnName\": \"example\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"serialNumber\",\n            \"columnName\": \"serialNumber\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enable\",\n            \"columnName\": \"enable\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"id\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"readRecord\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`deviceId` TEXT NOT NULL, `bookName` TEXT NOT NULL, `readTime` INTEGER NOT NULL DEFAULT 0, `lastRead` INTEGER NOT NULL DEFAULT 0, PRIMARY KEY(`deviceId`, `bookName`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"deviceId\",\n            \"columnName\": \"deviceId\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookName\",\n            \"columnName\": \"bookName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"readTime\",\n            \"columnName\": \"readTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"lastRead\",\n            \"columnName\": \"lastRead\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"deviceId\",\n            \"bookName\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"httpTTS\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `url` TEXT NOT NULL, `contentType` TEXT, `concurrentRate` TEXT DEFAULT '0', `loginUrl` TEXT, `loginUi` TEXT, `header` TEXT, `enabledCookieJar` INTEGER DEFAULT 0, `loginCheckJs` TEXT, `lastUpdateTime` INTEGER NOT NULL DEFAULT 0, PRIMARY KEY(`id`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"contentType\",\n            \"columnName\": \"contentType\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"concurrentRate\",\n            \"columnName\": \"concurrentRate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false,\n            \"defaultValue\": \"'0'\"\n          },\n          {\n            \"fieldPath\": \"loginUrl\",\n            \"columnName\": \"loginUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUi\",\n            \"columnName\": \"loginUi\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"header\",\n            \"columnName\": \"header\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enabledCookieJar\",\n            \"columnName\": \"enabledCookieJar\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"loginCheckJs\",\n            \"columnName\": \"loginCheckJs\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"lastUpdateTime\",\n            \"columnName\": \"lastUpdateTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"id\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"caches\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`key` TEXT NOT NULL, `value` TEXT, `deadline` INTEGER NOT NULL, PRIMARY KEY(`key`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"key\",\n            \"columnName\": \"key\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"value\",\n            \"columnName\": \"value\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"deadline\",\n            \"columnName\": \"deadline\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"key\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_caches_key\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"key\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_caches_key` ON `${TABLE_NAME}` (`key`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"ruleSubs\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `url` TEXT NOT NULL, `type` INTEGER NOT NULL, `customOrder` INTEGER NOT NULL, `autoUpdate` INTEGER NOT NULL, `update` INTEGER NOT NULL, PRIMARY KEY(`id`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"autoUpdate\",\n            \"columnName\": \"autoUpdate\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"update\",\n            \"columnName\": \"update\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"id\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"keyboardAssists\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`type` INTEGER NOT NULL DEFAULT 0, `key` TEXT NOT NULL DEFAULT '', `value` TEXT NOT NULL DEFAULT '', `serialNo` INTEGER NOT NULL DEFAULT 0, PRIMARY KEY(`type`, `key`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"key\",\n            \"columnName\": \"key\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"value\",\n            \"columnName\": \"value\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"serialNo\",\n            \"columnName\": \"serialNo\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"type\",\n            \"key\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      }\n    ],\n    \"views\": [],\n    \"setupQueries\": [\n      \"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)\",\n      \"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'dce5917572677d2b19bbc43809efb40c')\"\n    ]\n  }\n}"
  },
  {
    "path": "app/schemas/io.legado.app.data.AppDatabase/59.json",
    "content": "{\n  \"formatVersion\": 1,\n  \"database\": {\n    \"version\": 59,\n    \"identityHash\": \"ddae0fd3a6b7ec874a028754a8cdd528\",\n    \"entities\": [\n      {\n        \"tableName\": \"books\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookUrl` TEXT NOT NULL DEFAULT '', `tocUrl` TEXT NOT NULL DEFAULT '', `origin` TEXT NOT NULL DEFAULT 'loc_book', `originName` TEXT NOT NULL DEFAULT '', `name` TEXT NOT NULL DEFAULT '', `author` TEXT NOT NULL DEFAULT '', `kind` TEXT, `customTag` TEXT, `coverUrl` TEXT, `customCoverUrl` TEXT, `intro` TEXT, `customIntro` TEXT, `charset` TEXT, `type` INTEGER NOT NULL DEFAULT 0, `group` INTEGER NOT NULL DEFAULT 0, `latestChapterTitle` TEXT, `latestChapterTime` INTEGER NOT NULL DEFAULT 0, `lastCheckTime` INTEGER NOT NULL DEFAULT 0, `lastCheckCount` INTEGER NOT NULL DEFAULT 0, `totalChapterNum` INTEGER NOT NULL DEFAULT 0, `durChapterTitle` TEXT, `durChapterIndex` INTEGER NOT NULL DEFAULT 0, `durChapterPos` INTEGER NOT NULL DEFAULT 0, `durChapterTime` INTEGER NOT NULL DEFAULT 0, `wordCount` TEXT, `canUpdate` INTEGER NOT NULL DEFAULT 1, `order` INTEGER NOT NULL DEFAULT 0, `originOrder` INTEGER NOT NULL DEFAULT 0, `variable` TEXT, `readConfig` TEXT, PRIMARY KEY(`bookUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"tocUrl\",\n            \"columnName\": \"tocUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"'loc_book'\"\n          },\n          {\n            \"fieldPath\": \"originName\",\n            \"columnName\": \"originName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"author\",\n            \"columnName\": \"author\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"kind\",\n            \"columnName\": \"kind\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customTag\",\n            \"columnName\": \"customTag\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"coverUrl\",\n            \"columnName\": \"coverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customCoverUrl\",\n            \"columnName\": \"customCoverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"intro\",\n            \"columnName\": \"intro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customIntro\",\n            \"columnName\": \"customIntro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"charset\",\n            \"columnName\": \"charset\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"group\",\n            \"columnName\": \"group\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"latestChapterTitle\",\n            \"columnName\": \"latestChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"latestChapterTime\",\n            \"columnName\": \"latestChapterTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"lastCheckTime\",\n            \"columnName\": \"lastCheckTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"lastCheckCount\",\n            \"columnName\": \"lastCheckCount\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"totalChapterNum\",\n            \"columnName\": \"totalChapterNum\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"durChapterTitle\",\n            \"columnName\": \"durChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"durChapterIndex\",\n            \"columnName\": \"durChapterIndex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"durChapterPos\",\n            \"columnName\": \"durChapterPos\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"durChapterTime\",\n            \"columnName\": \"durChapterTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"wordCount\",\n            \"columnName\": \"wordCount\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"canUpdate\",\n            \"columnName\": \"canUpdate\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"originOrder\",\n            \"columnName\": \"originOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"readConfig\",\n            \"columnName\": \"readConfig\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"bookUrl\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_books_name_author\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"name\",\n              \"author\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_books_name_author` ON `${TABLE_NAME}` (`name`, `author`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"book_groups\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`groupId` INTEGER NOT NULL, `groupName` TEXT NOT NULL, `cover` TEXT, `order` INTEGER NOT NULL, `show` INTEGER NOT NULL, `bookSort` INTEGER NOT NULL DEFAULT -1, PRIMARY KEY(`groupId`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"groupId\",\n            \"columnName\": \"groupId\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"groupName\",\n            \"columnName\": \"groupName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"cover\",\n            \"columnName\": \"cover\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"show\",\n            \"columnName\": \"show\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookSort\",\n            \"columnName\": \"bookSort\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"-1\"\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"groupId\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"book_sources\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookSourceUrl` TEXT NOT NULL, `bookSourceName` TEXT NOT NULL, `bookSourceGroup` TEXT, `bookSourceType` INTEGER NOT NULL, `bookUrlPattern` TEXT, `customOrder` INTEGER NOT NULL, `enabled` INTEGER NOT NULL, `enabledExplore` INTEGER NOT NULL, `enabledReview` INTEGER, `enabledCookieJar` INTEGER DEFAULT 0, `concurrentRate` TEXT, `header` TEXT, `loginUrl` TEXT, `loginUi` TEXT, `loginCheckJs` TEXT, `coverDecodeJs` TEXT, `bookSourceComment` TEXT, `variableComment` TEXT, `lastUpdateTime` INTEGER NOT NULL, `respondTime` INTEGER NOT NULL, `weight` INTEGER NOT NULL, `exploreUrl` TEXT, `ruleExplore` TEXT, `searchUrl` TEXT, `ruleSearch` TEXT, `ruleBookInfo` TEXT, `ruleToc` TEXT, `ruleContent` TEXT, `ruleReview` TEXT, PRIMARY KEY(`bookSourceUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookSourceUrl\",\n            \"columnName\": \"bookSourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookSourceName\",\n            \"columnName\": \"bookSourceName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookSourceGroup\",\n            \"columnName\": \"bookSourceGroup\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"bookSourceType\",\n            \"columnName\": \"bookSourceType\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookUrlPattern\",\n            \"columnName\": \"bookUrlPattern\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enabled\",\n            \"columnName\": \"enabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enabledExplore\",\n            \"columnName\": \"enabledExplore\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enabledReview\",\n            \"columnName\": \"enabledReview\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enabledCookieJar\",\n            \"columnName\": \"enabledCookieJar\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"concurrentRate\",\n            \"columnName\": \"concurrentRate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"header\",\n            \"columnName\": \"header\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUrl\",\n            \"columnName\": \"loginUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUi\",\n            \"columnName\": \"loginUi\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginCheckJs\",\n            \"columnName\": \"loginCheckJs\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"coverDecodeJs\",\n            \"columnName\": \"coverDecodeJs\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"bookSourceComment\",\n            \"columnName\": \"bookSourceComment\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"variableComment\",\n            \"columnName\": \"variableComment\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"lastUpdateTime\",\n            \"columnName\": \"lastUpdateTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"respondTime\",\n            \"columnName\": \"respondTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"weight\",\n            \"columnName\": \"weight\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"exploreUrl\",\n            \"columnName\": \"exploreUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleExplore\",\n            \"columnName\": \"ruleExplore\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"searchUrl\",\n            \"columnName\": \"searchUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleSearch\",\n            \"columnName\": \"ruleSearch\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleBookInfo\",\n            \"columnName\": \"ruleBookInfo\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleToc\",\n            \"columnName\": \"ruleToc\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleContent\",\n            \"columnName\": \"ruleContent\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleReview\",\n            \"columnName\": \"ruleReview\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"bookSourceUrl\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_book_sources_bookSourceUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"bookSourceUrl\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_book_sources_bookSourceUrl` ON `${TABLE_NAME}` (`bookSourceUrl`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"chapters\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`url` TEXT NOT NULL, `title` TEXT NOT NULL, `isVolume` INTEGER NOT NULL, `baseUrl` TEXT NOT NULL, `bookUrl` TEXT NOT NULL, `index` INTEGER NOT NULL, `isVip` INTEGER NOT NULL, `isPay` INTEGER NOT NULL, `resourceUrl` TEXT, `tag` TEXT, `start` INTEGER, `end` INTEGER, `startFragmentId` TEXT, `endFragmentId` TEXT, `variable` TEXT, PRIMARY KEY(`url`, `bookUrl`), FOREIGN KEY(`bookUrl`) REFERENCES `books`(`bookUrl`) ON UPDATE NO ACTION ON DELETE CASCADE )\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"isVolume\",\n            \"columnName\": \"isVolume\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"baseUrl\",\n            \"columnName\": \"baseUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"index\",\n            \"columnName\": \"index\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"isVip\",\n            \"columnName\": \"isVip\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"isPay\",\n            \"columnName\": \"isPay\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"resourceUrl\",\n            \"columnName\": \"resourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"tag\",\n            \"columnName\": \"tag\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"start\",\n            \"columnName\": \"start\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"end\",\n            \"columnName\": \"end\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"startFragmentId\",\n            \"columnName\": \"startFragmentId\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"endFragmentId\",\n            \"columnName\": \"endFragmentId\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"url\",\n            \"bookUrl\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_chapters_bookUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"bookUrl\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_chapters_bookUrl` ON `${TABLE_NAME}` (`bookUrl`)\"\n          },\n          {\n            \"name\": \"index_chapters_bookUrl_index\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"bookUrl\",\n              \"index\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_chapters_bookUrl_index` ON `${TABLE_NAME}` (`bookUrl`, `index`)\"\n          }\n        ],\n        \"foreignKeys\": [\n          {\n            \"table\": \"books\",\n            \"onDelete\": \"CASCADE\",\n            \"onUpdate\": \"NO ACTION\",\n            \"columns\": [\n              \"bookUrl\"\n            ],\n            \"referencedColumns\": [\n              \"bookUrl\"\n            ]\n          }\n        ]\n      },\n      {\n        \"tableName\": \"replace_rules\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL DEFAULT '', `group` TEXT, `pattern` TEXT NOT NULL DEFAULT '', `replacement` TEXT NOT NULL DEFAULT '', `scope` TEXT, `scopeTitle` INTEGER NOT NULL DEFAULT 0, `scopeContent` INTEGER NOT NULL DEFAULT 1, `isEnabled` INTEGER NOT NULL DEFAULT 1, `isRegex` INTEGER NOT NULL DEFAULT 1, `timeoutMillisecond` INTEGER NOT NULL DEFAULT 3000, `sortOrder` INTEGER NOT NULL DEFAULT 0)\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"group\",\n            \"columnName\": \"group\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"pattern\",\n            \"columnName\": \"pattern\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"replacement\",\n            \"columnName\": \"replacement\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"scope\",\n            \"columnName\": \"scope\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"scopeTitle\",\n            \"columnName\": \"scopeTitle\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"scopeContent\",\n            \"columnName\": \"scopeContent\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"isEnabled\",\n            \"columnName\": \"isEnabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"isRegex\",\n            \"columnName\": \"isRegex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"timeoutMillisecond\",\n            \"columnName\": \"timeoutMillisecond\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"3000\"\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"sortOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": true,\n          \"columnNames\": [\n            \"id\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_replace_rules_id\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"id\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_replace_rules_id` ON `${TABLE_NAME}` (`id`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"searchBooks\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookUrl` TEXT NOT NULL, `origin` TEXT NOT NULL, `originName` TEXT NOT NULL, `type` INTEGER NOT NULL, `name` TEXT NOT NULL, `author` TEXT NOT NULL, `kind` TEXT, `coverUrl` TEXT, `intro` TEXT, `wordCount` TEXT, `latestChapterTitle` TEXT, `tocUrl` TEXT NOT NULL, `time` INTEGER NOT NULL, `variable` TEXT, `originOrder` INTEGER NOT NULL, PRIMARY KEY(`bookUrl`), FOREIGN KEY(`origin`) REFERENCES `book_sources`(`bookSourceUrl`) ON UPDATE NO ACTION ON DELETE CASCADE )\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"originName\",\n            \"columnName\": \"originName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"author\",\n            \"columnName\": \"author\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"kind\",\n            \"columnName\": \"kind\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"coverUrl\",\n            \"columnName\": \"coverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"intro\",\n            \"columnName\": \"intro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"wordCount\",\n            \"columnName\": \"wordCount\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"latestChapterTitle\",\n            \"columnName\": \"latestChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"tocUrl\",\n            \"columnName\": \"tocUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"time\",\n            \"columnName\": \"time\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"originOrder\",\n            \"columnName\": \"originOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"bookUrl\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_searchBooks_bookUrl\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"bookUrl\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_searchBooks_bookUrl` ON `${TABLE_NAME}` (`bookUrl`)\"\n          },\n          {\n            \"name\": \"index_searchBooks_origin\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"origin\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_searchBooks_origin` ON `${TABLE_NAME}` (`origin`)\"\n          }\n        ],\n        \"foreignKeys\": [\n          {\n            \"table\": \"book_sources\",\n            \"onDelete\": \"CASCADE\",\n            \"onUpdate\": \"NO ACTION\",\n            \"columns\": [\n              \"origin\"\n            ],\n            \"referencedColumns\": [\n              \"bookSourceUrl\"\n            ]\n          }\n        ]\n      },\n      {\n        \"tableName\": \"search_keywords\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`word` TEXT NOT NULL, `usage` INTEGER NOT NULL, `lastUseTime` INTEGER NOT NULL, PRIMARY KEY(`word`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"word\",\n            \"columnName\": \"word\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"usage\",\n            \"columnName\": \"usage\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"lastUseTime\",\n            \"columnName\": \"lastUseTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"word\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_search_keywords_word\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"word\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_search_keywords_word` ON `${TABLE_NAME}` (`word`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"cookies\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`url` TEXT NOT NULL, `cookie` TEXT NOT NULL, PRIMARY KEY(`url`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"cookie\",\n            \"columnName\": \"cookie\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"url\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_cookies_url\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"url\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_cookies_url` ON `${TABLE_NAME}` (`url`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssSources\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`sourceUrl` TEXT NOT NULL, `sourceName` TEXT NOT NULL, `sourceIcon` TEXT NOT NULL, `sourceGroup` TEXT, `sourceComment` TEXT, `enabled` INTEGER NOT NULL, `variableComment` TEXT, `enabledCookieJar` INTEGER DEFAULT 0, `concurrentRate` TEXT, `header` TEXT, `loginUrl` TEXT, `loginUi` TEXT, `loginCheckJs` TEXT, `coverDecodeJs` TEXT, `sortUrl` TEXT, `singleUrl` INTEGER NOT NULL, `articleStyle` INTEGER NOT NULL DEFAULT 0, `ruleArticles` TEXT, `ruleNextPage` TEXT, `ruleTitle` TEXT, `rulePubDate` TEXT, `ruleDescription` TEXT, `ruleImage` TEXT, `ruleLink` TEXT, `ruleContent` TEXT, `contentWhitelist` TEXT, `contentBlacklist` TEXT, `style` TEXT, `enableJs` INTEGER NOT NULL DEFAULT 1, `loadWithBaseUrl` INTEGER NOT NULL DEFAULT 1, `injectJs` TEXT, `lastUpdateTime` INTEGER NOT NULL DEFAULT 0, `customOrder` INTEGER NOT NULL DEFAULT 0, PRIMARY KEY(`sourceUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"sourceUrl\",\n            \"columnName\": \"sourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceName\",\n            \"columnName\": \"sourceName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceIcon\",\n            \"columnName\": \"sourceIcon\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceGroup\",\n            \"columnName\": \"sourceGroup\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"sourceComment\",\n            \"columnName\": \"sourceComment\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enabled\",\n            \"columnName\": \"enabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"variableComment\",\n            \"columnName\": \"variableComment\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enabledCookieJar\",\n            \"columnName\": \"enabledCookieJar\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"concurrentRate\",\n            \"columnName\": \"concurrentRate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"header\",\n            \"columnName\": \"header\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUrl\",\n            \"columnName\": \"loginUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUi\",\n            \"columnName\": \"loginUi\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginCheckJs\",\n            \"columnName\": \"loginCheckJs\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"coverDecodeJs\",\n            \"columnName\": \"coverDecodeJs\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"sortUrl\",\n            \"columnName\": \"sortUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"singleUrl\",\n            \"columnName\": \"singleUrl\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"articleStyle\",\n            \"columnName\": \"articleStyle\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"ruleArticles\",\n            \"columnName\": \"ruleArticles\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleNextPage\",\n            \"columnName\": \"ruleNextPage\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleTitle\",\n            \"columnName\": \"ruleTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"rulePubDate\",\n            \"columnName\": \"rulePubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleDescription\",\n            \"columnName\": \"ruleDescription\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleImage\",\n            \"columnName\": \"ruleImage\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleLink\",\n            \"columnName\": \"ruleLink\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleContent\",\n            \"columnName\": \"ruleContent\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"contentWhitelist\",\n            \"columnName\": \"contentWhitelist\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"contentBlacklist\",\n            \"columnName\": \"contentBlacklist\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"style\",\n            \"columnName\": \"style\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enableJs\",\n            \"columnName\": \"enableJs\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"loadWithBaseUrl\",\n            \"columnName\": \"loadWithBaseUrl\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"injectJs\",\n            \"columnName\": \"injectJs\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"lastUpdateTime\",\n            \"columnName\": \"lastUpdateTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"sourceUrl\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_rssSources_sourceUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"sourceUrl\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_rssSources_sourceUrl` ON `${TABLE_NAME}` (`sourceUrl`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"bookmarks\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`time` INTEGER NOT NULL, `bookName` TEXT NOT NULL, `bookAuthor` TEXT NOT NULL, `chapterIndex` INTEGER NOT NULL, `chapterPos` INTEGER NOT NULL, `chapterName` TEXT NOT NULL, `bookText` TEXT NOT NULL, `content` TEXT NOT NULL, PRIMARY KEY(`time`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"time\",\n            \"columnName\": \"time\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookName\",\n            \"columnName\": \"bookName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookAuthor\",\n            \"columnName\": \"bookAuthor\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterIndex\",\n            \"columnName\": \"chapterIndex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterPos\",\n            \"columnName\": \"chapterPos\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterName\",\n            \"columnName\": \"chapterName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookText\",\n            \"columnName\": \"bookText\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"time\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_bookmarks_bookName_bookAuthor\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"bookName\",\n              \"bookAuthor\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_bookmarks_bookName_bookAuthor` ON `${TABLE_NAME}` (`bookName`, `bookAuthor`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssArticles\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`origin` TEXT NOT NULL, `sort` TEXT NOT NULL, `title` TEXT NOT NULL, `order` INTEGER NOT NULL, `link` TEXT NOT NULL, `pubDate` TEXT, `description` TEXT, `content` TEXT, `image` TEXT, `read` INTEGER NOT NULL, `variable` TEXT, PRIMARY KEY(`origin`, `link`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sort\",\n            \"columnName\": \"sort\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"link\",\n            \"columnName\": \"link\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"pubDate\",\n            \"columnName\": \"pubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"description\",\n            \"columnName\": \"description\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"image\",\n            \"columnName\": \"image\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"read\",\n            \"columnName\": \"read\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"origin\",\n            \"link\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssReadRecords\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`record` TEXT NOT NULL, `read` INTEGER NOT NULL, PRIMARY KEY(`record`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"record\",\n            \"columnName\": \"record\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"read\",\n            \"columnName\": \"read\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"record\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssStars\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`origin` TEXT NOT NULL, `sort` TEXT NOT NULL, `title` TEXT NOT NULL, `starTime` INTEGER NOT NULL, `link` TEXT NOT NULL, `pubDate` TEXT, `description` TEXT, `content` TEXT, `image` TEXT, `variable` TEXT, PRIMARY KEY(`origin`, `link`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sort\",\n            \"columnName\": \"sort\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"starTime\",\n            \"columnName\": \"starTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"link\",\n            \"columnName\": \"link\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"pubDate\",\n            \"columnName\": \"pubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"description\",\n            \"columnName\": \"description\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"image\",\n            \"columnName\": \"image\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"origin\",\n            \"link\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"txtTocRules\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `rule` TEXT NOT NULL, `example` TEXT, `serialNumber` INTEGER NOT NULL, `enable` INTEGER NOT NULL, PRIMARY KEY(`id`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"rule\",\n            \"columnName\": \"rule\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"example\",\n            \"columnName\": \"example\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"serialNumber\",\n            \"columnName\": \"serialNumber\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enable\",\n            \"columnName\": \"enable\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"id\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"readRecord\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`deviceId` TEXT NOT NULL, `bookName` TEXT NOT NULL, `readTime` INTEGER NOT NULL DEFAULT 0, `lastRead` INTEGER NOT NULL DEFAULT 0, PRIMARY KEY(`deviceId`, `bookName`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"deviceId\",\n            \"columnName\": \"deviceId\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookName\",\n            \"columnName\": \"bookName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"readTime\",\n            \"columnName\": \"readTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"lastRead\",\n            \"columnName\": \"lastRead\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"deviceId\",\n            \"bookName\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"httpTTS\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `url` TEXT NOT NULL, `contentType` TEXT, `concurrentRate` TEXT DEFAULT '0', `loginUrl` TEXT, `loginUi` TEXT, `header` TEXT, `enabledCookieJar` INTEGER DEFAULT 0, `loginCheckJs` TEXT, `lastUpdateTime` INTEGER NOT NULL DEFAULT 0, PRIMARY KEY(`id`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"contentType\",\n            \"columnName\": \"contentType\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"concurrentRate\",\n            \"columnName\": \"concurrentRate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false,\n            \"defaultValue\": \"'0'\"\n          },\n          {\n            \"fieldPath\": \"loginUrl\",\n            \"columnName\": \"loginUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUi\",\n            \"columnName\": \"loginUi\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"header\",\n            \"columnName\": \"header\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enabledCookieJar\",\n            \"columnName\": \"enabledCookieJar\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"loginCheckJs\",\n            \"columnName\": \"loginCheckJs\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"lastUpdateTime\",\n            \"columnName\": \"lastUpdateTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"id\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"caches\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`key` TEXT NOT NULL, `value` TEXT, `deadline` INTEGER NOT NULL, PRIMARY KEY(`key`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"key\",\n            \"columnName\": \"key\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"value\",\n            \"columnName\": \"value\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"deadline\",\n            \"columnName\": \"deadline\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"key\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_caches_key\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"key\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_caches_key` ON `${TABLE_NAME}` (`key`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"ruleSubs\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `url` TEXT NOT NULL, `type` INTEGER NOT NULL, `customOrder` INTEGER NOT NULL, `autoUpdate` INTEGER NOT NULL, `update` INTEGER NOT NULL, PRIMARY KEY(`id`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"autoUpdate\",\n            \"columnName\": \"autoUpdate\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"update\",\n            \"columnName\": \"update\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"id\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"keyboardAssists\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`type` INTEGER NOT NULL DEFAULT 0, `key` TEXT NOT NULL DEFAULT '', `value` TEXT NOT NULL DEFAULT '', `serialNo` INTEGER NOT NULL DEFAULT 0, PRIMARY KEY(`type`, `key`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"key\",\n            \"columnName\": \"key\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"value\",\n            \"columnName\": \"value\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"serialNo\",\n            \"columnName\": \"serialNo\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"type\",\n            \"key\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      }\n    ],\n    \"views\": [],\n    \"setupQueries\": [\n      \"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)\",\n      \"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'ddae0fd3a6b7ec874a028754a8cdd528')\"\n    ]\n  }\n}"
  },
  {
    "path": "app/schemas/io.legado.app.data.AppDatabase/6.json",
    "content": "{\n  \"formatVersion\": 1,\n  \"database\": {\n    \"version\": 6,\n    \"identityHash\": \"af70ea583587e17c968d29f41bb3c0d6\",\n    \"entities\": [\n      {\n        \"tableName\": \"books\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookUrl` TEXT NOT NULL, `tocUrl` TEXT NOT NULL, `origin` TEXT NOT NULL, `originName` TEXT NOT NULL, `name` TEXT NOT NULL, `author` TEXT NOT NULL, `kind` TEXT, `customTag` TEXT, `coverUrl` TEXT, `customCoverUrl` TEXT, `intro` TEXT, `customIntro` TEXT, `charset` TEXT, `type` INTEGER NOT NULL, `group` INTEGER NOT NULL, `latestChapterTitle` TEXT, `latestChapterTime` INTEGER NOT NULL, `lastCheckTime` INTEGER NOT NULL, `lastCheckCount` INTEGER NOT NULL, `totalChapterNum` INTEGER NOT NULL, `durChapterTitle` TEXT, `durChapterIndex` INTEGER NOT NULL, `durChapterPos` INTEGER NOT NULL, `durChapterTime` INTEGER NOT NULL, `wordCount` TEXT, `canUpdate` INTEGER NOT NULL, `order` INTEGER NOT NULL, `originOrder` INTEGER NOT NULL, `useReplaceRule` INTEGER NOT NULL, `variable` TEXT, PRIMARY KEY(`bookUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"tocUrl\",\n            \"columnName\": \"tocUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"originName\",\n            \"columnName\": \"originName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"author\",\n            \"columnName\": \"author\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"kind\",\n            \"columnName\": \"kind\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customTag\",\n            \"columnName\": \"customTag\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"coverUrl\",\n            \"columnName\": \"coverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customCoverUrl\",\n            \"columnName\": \"customCoverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"intro\",\n            \"columnName\": \"intro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customIntro\",\n            \"columnName\": \"customIntro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"charset\",\n            \"columnName\": \"charset\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"group\",\n            \"columnName\": \"group\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"latestChapterTitle\",\n            \"columnName\": \"latestChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"latestChapterTime\",\n            \"columnName\": \"latestChapterTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"lastCheckTime\",\n            \"columnName\": \"lastCheckTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"lastCheckCount\",\n            \"columnName\": \"lastCheckCount\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"totalChapterNum\",\n            \"columnName\": \"totalChapterNum\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"durChapterTitle\",\n            \"columnName\": \"durChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"durChapterIndex\",\n            \"columnName\": \"durChapterIndex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"durChapterPos\",\n            \"columnName\": \"durChapterPos\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"durChapterTime\",\n            \"columnName\": \"durChapterTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"wordCount\",\n            \"columnName\": \"wordCount\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"canUpdate\",\n            \"columnName\": \"canUpdate\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"originOrder\",\n            \"columnName\": \"originOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"useReplaceRule\",\n            \"columnName\": \"useReplaceRule\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"bookUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_books_bookUrl\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"bookUrl\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_books_bookUrl` ON `${TABLE_NAME}` (`bookUrl`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"book_groups\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`groupId` INTEGER NOT NULL, `groupName` TEXT NOT NULL, `order` INTEGER NOT NULL, PRIMARY KEY(`groupId`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"groupId\",\n            \"columnName\": \"groupId\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"groupName\",\n            \"columnName\": \"groupName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"groupId\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"book_sources\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookSourceName` TEXT NOT NULL, `bookSourceGroup` TEXT, `bookSourceUrl` TEXT NOT NULL, `bookSourceType` INTEGER NOT NULL, `bookUrlPattern` TEXT, `customOrder` INTEGER NOT NULL, `enabled` INTEGER NOT NULL, `enabledExplore` INTEGER NOT NULL, `header` TEXT, `loginUrl` TEXT, `lastUpdateTime` INTEGER NOT NULL, `weight` INTEGER NOT NULL, `exploreUrl` TEXT, `ruleExplore` TEXT, `searchUrl` TEXT, `ruleSearch` TEXT, `ruleBookInfo` TEXT, `ruleToc` TEXT, `ruleContent` TEXT, PRIMARY KEY(`bookSourceUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookSourceName\",\n            \"columnName\": \"bookSourceName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookSourceGroup\",\n            \"columnName\": \"bookSourceGroup\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"bookSourceUrl\",\n            \"columnName\": \"bookSourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookSourceType\",\n            \"columnName\": \"bookSourceType\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookUrlPattern\",\n            \"columnName\": \"bookUrlPattern\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enabled\",\n            \"columnName\": \"enabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enabledExplore\",\n            \"columnName\": \"enabledExplore\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"header\",\n            \"columnName\": \"header\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUrl\",\n            \"columnName\": \"loginUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"lastUpdateTime\",\n            \"columnName\": \"lastUpdateTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"weight\",\n            \"columnName\": \"weight\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"exploreUrl\",\n            \"columnName\": \"exploreUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleExplore\",\n            \"columnName\": \"ruleExplore\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"searchUrl\",\n            \"columnName\": \"searchUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleSearch\",\n            \"columnName\": \"ruleSearch\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleBookInfo\",\n            \"columnName\": \"ruleBookInfo\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleToc\",\n            \"columnName\": \"ruleToc\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleContent\",\n            \"columnName\": \"ruleContent\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"bookSourceUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_book_sources_bookSourceUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"bookSourceUrl\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_book_sources_bookSourceUrl` ON `${TABLE_NAME}` (`bookSourceUrl`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"chapters\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`url` TEXT NOT NULL, `title` TEXT NOT NULL, `bookUrl` TEXT NOT NULL, `index` INTEGER NOT NULL, `resourceUrl` TEXT, `tag` TEXT, `start` INTEGER, `end` INTEGER, `variable` TEXT, PRIMARY KEY(`url`, `bookUrl`), FOREIGN KEY(`bookUrl`) REFERENCES `books`(`bookUrl`) ON UPDATE NO ACTION ON DELETE CASCADE )\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"index\",\n            \"columnName\": \"index\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"resourceUrl\",\n            \"columnName\": \"resourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"tag\",\n            \"columnName\": \"tag\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"start\",\n            \"columnName\": \"start\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"end\",\n            \"columnName\": \"end\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"url\",\n            \"bookUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_chapters_bookUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"bookUrl\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_chapters_bookUrl` ON `${TABLE_NAME}` (`bookUrl`)\"\n          },\n          {\n            \"name\": \"index_chapters_bookUrl_index\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"bookUrl\",\n              \"index\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_chapters_bookUrl_index` ON `${TABLE_NAME}` (`bookUrl`, `index`)\"\n          }\n        ],\n        \"foreignKeys\": [\n          {\n            \"table\": \"books\",\n            \"onDelete\": \"CASCADE\",\n            \"onUpdate\": \"NO ACTION\",\n            \"columns\": [\n              \"bookUrl\"\n            ],\n            \"referencedColumns\": [\n              \"bookUrl\"\n            ]\n          }\n        ]\n      },\n      {\n        \"tableName\": \"replace_rules\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL, `group` TEXT, `pattern` TEXT NOT NULL, `replacement` TEXT NOT NULL, `scope` TEXT, `isEnabled` INTEGER NOT NULL, `isRegex` INTEGER NOT NULL, `sortOrder` INTEGER NOT NULL)\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"group\",\n            \"columnName\": \"group\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"pattern\",\n            \"columnName\": \"pattern\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"replacement\",\n            \"columnName\": \"replacement\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"scope\",\n            \"columnName\": \"scope\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"isEnabled\",\n            \"columnName\": \"isEnabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"isRegex\",\n            \"columnName\": \"isRegex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"sortOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"id\"\n          ],\n          \"autoGenerate\": true\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_replace_rules_id\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"id\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_replace_rules_id` ON `${TABLE_NAME}` (`id`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"searchBooks\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookUrl` TEXT NOT NULL, `origin` TEXT NOT NULL, `originName` TEXT NOT NULL, `type` INTEGER NOT NULL, `name` TEXT NOT NULL, `author` TEXT NOT NULL, `kind` TEXT, `coverUrl` TEXT, `intro` TEXT, `wordCount` TEXT, `latestChapterTitle` TEXT, `tocUrl` TEXT NOT NULL, `time` INTEGER NOT NULL, `variable` TEXT, `originOrder` INTEGER NOT NULL, PRIMARY KEY(`bookUrl`), FOREIGN KEY(`origin`) REFERENCES `book_sources`(`bookSourceUrl`) ON UPDATE NO ACTION ON DELETE CASCADE )\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"originName\",\n            \"columnName\": \"originName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"author\",\n            \"columnName\": \"author\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"kind\",\n            \"columnName\": \"kind\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"coverUrl\",\n            \"columnName\": \"coverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"intro\",\n            \"columnName\": \"intro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"wordCount\",\n            \"columnName\": \"wordCount\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"latestChapterTitle\",\n            \"columnName\": \"latestChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"tocUrl\",\n            \"columnName\": \"tocUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"time\",\n            \"columnName\": \"time\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"originOrder\",\n            \"columnName\": \"originOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"bookUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_searchBooks_bookUrl\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"bookUrl\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_searchBooks_bookUrl` ON `${TABLE_NAME}` (`bookUrl`)\"\n          },\n          {\n            \"name\": \"index_searchBooks_origin\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"origin\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_searchBooks_origin` ON `${TABLE_NAME}` (`origin`)\"\n          }\n        ],\n        \"foreignKeys\": [\n          {\n            \"table\": \"book_sources\",\n            \"onDelete\": \"CASCADE\",\n            \"onUpdate\": \"NO ACTION\",\n            \"columns\": [\n              \"origin\"\n            ],\n            \"referencedColumns\": [\n              \"bookSourceUrl\"\n            ]\n          }\n        ]\n      },\n      {\n        \"tableName\": \"search_keywords\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`word` TEXT NOT NULL, `usage` INTEGER NOT NULL, `lastUseTime` INTEGER NOT NULL, PRIMARY KEY(`word`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"word\",\n            \"columnName\": \"word\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"usage\",\n            \"columnName\": \"usage\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"lastUseTime\",\n            \"columnName\": \"lastUseTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"word\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_search_keywords_word\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"word\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_search_keywords_word` ON `${TABLE_NAME}` (`word`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"cookies\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`url` TEXT NOT NULL, `cookie` TEXT NOT NULL, PRIMARY KEY(`url`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"cookie\",\n            \"columnName\": \"cookie\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"url\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_cookies_url\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"url\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_cookies_url` ON `${TABLE_NAME}` (`url`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssSources\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`sourceUrl` TEXT NOT NULL, `sourceName` TEXT NOT NULL, `sourceIcon` TEXT NOT NULL, `sourceGroup` TEXT, `enabled` INTEGER NOT NULL, `ruleArticles` TEXT, `ruleNextPage` TEXT, `ruleTitle` TEXT, `rulePubDate` TEXT, `ruleDescription` TEXT, `ruleImage` TEXT, `ruleLink` TEXT, `ruleContent` TEXT, `header` TEXT, `enableJs` INTEGER NOT NULL, `loadWithBaseUrl` INTEGER NOT NULL, `customOrder` INTEGER NOT NULL, PRIMARY KEY(`sourceUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"sourceUrl\",\n            \"columnName\": \"sourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceName\",\n            \"columnName\": \"sourceName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceIcon\",\n            \"columnName\": \"sourceIcon\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceGroup\",\n            \"columnName\": \"sourceGroup\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enabled\",\n            \"columnName\": \"enabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"ruleArticles\",\n            \"columnName\": \"ruleArticles\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleNextPage\",\n            \"columnName\": \"ruleNextPage\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleTitle\",\n            \"columnName\": \"ruleTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"rulePubDate\",\n            \"columnName\": \"rulePubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleDescription\",\n            \"columnName\": \"ruleDescription\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleImage\",\n            \"columnName\": \"ruleImage\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleLink\",\n            \"columnName\": \"ruleLink\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleContent\",\n            \"columnName\": \"ruleContent\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"header\",\n            \"columnName\": \"header\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enableJs\",\n            \"columnName\": \"enableJs\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"loadWithBaseUrl\",\n            \"columnName\": \"loadWithBaseUrl\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"sourceUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_rssSources_sourceUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"sourceUrl\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_rssSources_sourceUrl` ON `${TABLE_NAME}` (`sourceUrl`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"bookmarks\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`time` INTEGER NOT NULL, `bookUrl` TEXT NOT NULL, `bookName` TEXT NOT NULL, `chapterIndex` INTEGER NOT NULL, `pageIndex` INTEGER NOT NULL, `chapterName` TEXT NOT NULL, `content` TEXT NOT NULL, PRIMARY KEY(`time`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"time\",\n            \"columnName\": \"time\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookName\",\n            \"columnName\": \"bookName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterIndex\",\n            \"columnName\": \"chapterIndex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"pageIndex\",\n            \"columnName\": \"pageIndex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterName\",\n            \"columnName\": \"chapterName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"time\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_bookmarks_time\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"time\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_bookmarks_time` ON `${TABLE_NAME}` (`time`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssArticles\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`origin` TEXT NOT NULL, `title` TEXT NOT NULL, `order` INTEGER NOT NULL, `link` TEXT NOT NULL, `pubDate` TEXT, `description` TEXT, `content` TEXT, `image` TEXT, `read` INTEGER NOT NULL, PRIMARY KEY(`origin`, `link`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"link\",\n            \"columnName\": \"link\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"pubDate\",\n            \"columnName\": \"pubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"description\",\n            \"columnName\": \"description\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"image\",\n            \"columnName\": \"image\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"read\",\n            \"columnName\": \"read\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"origin\",\n            \"link\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssStars\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`origin` TEXT NOT NULL, `title` TEXT NOT NULL, `starTime` INTEGER NOT NULL, `link` TEXT NOT NULL, `pubDate` TEXT, `description` TEXT, `content` TEXT, `image` TEXT, PRIMARY KEY(`origin`, `link`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"starTime\",\n            \"columnName\": \"starTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"link\",\n            \"columnName\": \"link\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"pubDate\",\n            \"columnName\": \"pubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"description\",\n            \"columnName\": \"description\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"image\",\n            \"columnName\": \"image\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"origin\",\n            \"link\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"txtTocRules\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`name` TEXT NOT NULL, `rule` TEXT NOT NULL, `serialNumber` INTEGER NOT NULL, `enable` INTEGER NOT NULL, PRIMARY KEY(`name`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"rule\",\n            \"columnName\": \"rule\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"serialNumber\",\n            \"columnName\": \"serialNumber\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enable\",\n            \"columnName\": \"enable\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"name\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      }\n    ],\n    \"views\": [],\n    \"setupQueries\": [\n      \"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)\",\n      \"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'af70ea583587e17c968d29f41bb3c0d6')\"\n    ]\n  }\n}"
  },
  {
    "path": "app/schemas/io.legado.app.data.AppDatabase/60.json",
    "content": "{\n  \"formatVersion\": 1,\n  \"database\": {\n    \"version\": 60,\n    \"identityHash\": \"19e1f66252eb41afc0990f202fe4527a\",\n    \"entities\": [\n      {\n        \"tableName\": \"books\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookUrl` TEXT NOT NULL DEFAULT '', `tocUrl` TEXT NOT NULL DEFAULT '', `origin` TEXT NOT NULL DEFAULT 'loc_book', `originName` TEXT NOT NULL DEFAULT '', `name` TEXT NOT NULL DEFAULT '', `author` TEXT NOT NULL DEFAULT '', `kind` TEXT, `customTag` TEXT, `coverUrl` TEXT, `customCoverUrl` TEXT, `intro` TEXT, `customIntro` TEXT, `charset` TEXT, `type` INTEGER NOT NULL DEFAULT 0, `group` INTEGER NOT NULL DEFAULT 0, `latestChapterTitle` TEXT, `latestChapterTime` INTEGER NOT NULL DEFAULT 0, `lastCheckTime` INTEGER NOT NULL DEFAULT 0, `lastCheckCount` INTEGER NOT NULL DEFAULT 0, `totalChapterNum` INTEGER NOT NULL DEFAULT 0, `durChapterTitle` TEXT, `durChapterIndex` INTEGER NOT NULL DEFAULT 0, `durChapterPos` INTEGER NOT NULL DEFAULT 0, `durChapterTime` INTEGER NOT NULL DEFAULT 0, `wordCount` TEXT, `canUpdate` INTEGER NOT NULL DEFAULT 1, `order` INTEGER NOT NULL DEFAULT 0, `originOrder` INTEGER NOT NULL DEFAULT 0, `variable` TEXT, `readConfig` TEXT, PRIMARY KEY(`bookUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"tocUrl\",\n            \"columnName\": \"tocUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"'loc_book'\"\n          },\n          {\n            \"fieldPath\": \"originName\",\n            \"columnName\": \"originName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"author\",\n            \"columnName\": \"author\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"kind\",\n            \"columnName\": \"kind\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customTag\",\n            \"columnName\": \"customTag\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"coverUrl\",\n            \"columnName\": \"coverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customCoverUrl\",\n            \"columnName\": \"customCoverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"intro\",\n            \"columnName\": \"intro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customIntro\",\n            \"columnName\": \"customIntro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"charset\",\n            \"columnName\": \"charset\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"group\",\n            \"columnName\": \"group\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"latestChapterTitle\",\n            \"columnName\": \"latestChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"latestChapterTime\",\n            \"columnName\": \"latestChapterTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"lastCheckTime\",\n            \"columnName\": \"lastCheckTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"lastCheckCount\",\n            \"columnName\": \"lastCheckCount\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"totalChapterNum\",\n            \"columnName\": \"totalChapterNum\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"durChapterTitle\",\n            \"columnName\": \"durChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"durChapterIndex\",\n            \"columnName\": \"durChapterIndex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"durChapterPos\",\n            \"columnName\": \"durChapterPos\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"durChapterTime\",\n            \"columnName\": \"durChapterTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"wordCount\",\n            \"columnName\": \"wordCount\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"canUpdate\",\n            \"columnName\": \"canUpdate\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"originOrder\",\n            \"columnName\": \"originOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"readConfig\",\n            \"columnName\": \"readConfig\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"bookUrl\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_books_name_author\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"name\",\n              \"author\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_books_name_author` ON `${TABLE_NAME}` (`name`, `author`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"book_groups\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`groupId` INTEGER NOT NULL, `groupName` TEXT NOT NULL, `cover` TEXT, `order` INTEGER NOT NULL, `show` INTEGER NOT NULL, `bookSort` INTEGER NOT NULL DEFAULT -1, PRIMARY KEY(`groupId`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"groupId\",\n            \"columnName\": \"groupId\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"groupName\",\n            \"columnName\": \"groupName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"cover\",\n            \"columnName\": \"cover\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"show\",\n            \"columnName\": \"show\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookSort\",\n            \"columnName\": \"bookSort\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"-1\"\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"groupId\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"book_sources\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookSourceUrl` TEXT NOT NULL, `bookSourceName` TEXT NOT NULL, `bookSourceGroup` TEXT, `bookSourceType` INTEGER NOT NULL, `bookUrlPattern` TEXT, `customOrder` INTEGER NOT NULL, `enabled` INTEGER NOT NULL, `enabledExplore` INTEGER NOT NULL, `enabledReview` INTEGER, `enabledCookieJar` INTEGER DEFAULT 0, `concurrentRate` TEXT, `header` TEXT, `loginUrl` TEXT, `loginUi` TEXT, `loginCheckJs` TEXT, `coverDecodeJs` TEXT, `bookSourceComment` TEXT, `variableComment` TEXT, `lastUpdateTime` INTEGER NOT NULL, `respondTime` INTEGER NOT NULL, `weight` INTEGER NOT NULL, `exploreUrl` TEXT, `ruleExplore` TEXT, `searchUrl` TEXT, `ruleSearch` TEXT, `ruleBookInfo` TEXT, `ruleToc` TEXT, `ruleContent` TEXT, `ruleReview` TEXT, PRIMARY KEY(`bookSourceUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookSourceUrl\",\n            \"columnName\": \"bookSourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookSourceName\",\n            \"columnName\": \"bookSourceName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookSourceGroup\",\n            \"columnName\": \"bookSourceGroup\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"bookSourceType\",\n            \"columnName\": \"bookSourceType\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookUrlPattern\",\n            \"columnName\": \"bookUrlPattern\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enabled\",\n            \"columnName\": \"enabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enabledExplore\",\n            \"columnName\": \"enabledExplore\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enabledReview\",\n            \"columnName\": \"enabledReview\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enabledCookieJar\",\n            \"columnName\": \"enabledCookieJar\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"concurrentRate\",\n            \"columnName\": \"concurrentRate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"header\",\n            \"columnName\": \"header\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUrl\",\n            \"columnName\": \"loginUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUi\",\n            \"columnName\": \"loginUi\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginCheckJs\",\n            \"columnName\": \"loginCheckJs\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"coverDecodeJs\",\n            \"columnName\": \"coverDecodeJs\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"bookSourceComment\",\n            \"columnName\": \"bookSourceComment\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"variableComment\",\n            \"columnName\": \"variableComment\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"lastUpdateTime\",\n            \"columnName\": \"lastUpdateTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"respondTime\",\n            \"columnName\": \"respondTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"weight\",\n            \"columnName\": \"weight\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"exploreUrl\",\n            \"columnName\": \"exploreUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleExplore\",\n            \"columnName\": \"ruleExplore\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"searchUrl\",\n            \"columnName\": \"searchUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleSearch\",\n            \"columnName\": \"ruleSearch\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleBookInfo\",\n            \"columnName\": \"ruleBookInfo\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleToc\",\n            \"columnName\": \"ruleToc\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleContent\",\n            \"columnName\": \"ruleContent\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleReview\",\n            \"columnName\": \"ruleReview\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"bookSourceUrl\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_book_sources_bookSourceUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"bookSourceUrl\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_book_sources_bookSourceUrl` ON `${TABLE_NAME}` (`bookSourceUrl`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"chapters\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`url` TEXT NOT NULL, `title` TEXT NOT NULL, `isVolume` INTEGER NOT NULL, `baseUrl` TEXT NOT NULL, `bookUrl` TEXT NOT NULL, `index` INTEGER NOT NULL, `isVip` INTEGER NOT NULL, `isPay` INTEGER NOT NULL, `resourceUrl` TEXT, `tag` TEXT, `start` INTEGER, `end` INTEGER, `startFragmentId` TEXT, `endFragmentId` TEXT, `variable` TEXT, PRIMARY KEY(`url`, `bookUrl`), FOREIGN KEY(`bookUrl`) REFERENCES `books`(`bookUrl`) ON UPDATE NO ACTION ON DELETE CASCADE )\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"isVolume\",\n            \"columnName\": \"isVolume\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"baseUrl\",\n            \"columnName\": \"baseUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"index\",\n            \"columnName\": \"index\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"isVip\",\n            \"columnName\": \"isVip\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"isPay\",\n            \"columnName\": \"isPay\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"resourceUrl\",\n            \"columnName\": \"resourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"tag\",\n            \"columnName\": \"tag\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"start\",\n            \"columnName\": \"start\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"end\",\n            \"columnName\": \"end\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"startFragmentId\",\n            \"columnName\": \"startFragmentId\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"endFragmentId\",\n            \"columnName\": \"endFragmentId\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"url\",\n            \"bookUrl\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_chapters_bookUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"bookUrl\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_chapters_bookUrl` ON `${TABLE_NAME}` (`bookUrl`)\"\n          },\n          {\n            \"name\": \"index_chapters_bookUrl_index\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"bookUrl\",\n              \"index\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_chapters_bookUrl_index` ON `${TABLE_NAME}` (`bookUrl`, `index`)\"\n          }\n        ],\n        \"foreignKeys\": [\n          {\n            \"table\": \"books\",\n            \"onDelete\": \"CASCADE\",\n            \"onUpdate\": \"NO ACTION\",\n            \"columns\": [\n              \"bookUrl\"\n            ],\n            \"referencedColumns\": [\n              \"bookUrl\"\n            ]\n          }\n        ]\n      },\n      {\n        \"tableName\": \"replace_rules\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL DEFAULT '', `group` TEXT, `pattern` TEXT NOT NULL DEFAULT '', `replacement` TEXT NOT NULL DEFAULT '', `scope` TEXT, `scopeTitle` INTEGER NOT NULL DEFAULT 0, `scopeContent` INTEGER NOT NULL DEFAULT 1, `isEnabled` INTEGER NOT NULL DEFAULT 1, `isRegex` INTEGER NOT NULL DEFAULT 1, `timeoutMillisecond` INTEGER NOT NULL DEFAULT 3000, `sortOrder` INTEGER NOT NULL DEFAULT 0)\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"group\",\n            \"columnName\": \"group\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"pattern\",\n            \"columnName\": \"pattern\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"replacement\",\n            \"columnName\": \"replacement\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"scope\",\n            \"columnName\": \"scope\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"scopeTitle\",\n            \"columnName\": \"scopeTitle\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"scopeContent\",\n            \"columnName\": \"scopeContent\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"isEnabled\",\n            \"columnName\": \"isEnabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"isRegex\",\n            \"columnName\": \"isRegex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"timeoutMillisecond\",\n            \"columnName\": \"timeoutMillisecond\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"3000\"\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"sortOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": true,\n          \"columnNames\": [\n            \"id\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_replace_rules_id\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"id\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_replace_rules_id` ON `${TABLE_NAME}` (`id`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"searchBooks\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookUrl` TEXT NOT NULL, `origin` TEXT NOT NULL, `originName` TEXT NOT NULL, `type` INTEGER NOT NULL, `name` TEXT NOT NULL, `author` TEXT NOT NULL, `kind` TEXT, `coverUrl` TEXT, `intro` TEXT, `wordCount` TEXT, `latestChapterTitle` TEXT, `tocUrl` TEXT NOT NULL, `time` INTEGER NOT NULL, `variable` TEXT, `originOrder` INTEGER NOT NULL, PRIMARY KEY(`bookUrl`), FOREIGN KEY(`origin`) REFERENCES `book_sources`(`bookSourceUrl`) ON UPDATE NO ACTION ON DELETE CASCADE )\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"originName\",\n            \"columnName\": \"originName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"author\",\n            \"columnName\": \"author\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"kind\",\n            \"columnName\": \"kind\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"coverUrl\",\n            \"columnName\": \"coverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"intro\",\n            \"columnName\": \"intro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"wordCount\",\n            \"columnName\": \"wordCount\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"latestChapterTitle\",\n            \"columnName\": \"latestChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"tocUrl\",\n            \"columnName\": \"tocUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"time\",\n            \"columnName\": \"time\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"originOrder\",\n            \"columnName\": \"originOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"bookUrl\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_searchBooks_bookUrl\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"bookUrl\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_searchBooks_bookUrl` ON `${TABLE_NAME}` (`bookUrl`)\"\n          },\n          {\n            \"name\": \"index_searchBooks_origin\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"origin\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_searchBooks_origin` ON `${TABLE_NAME}` (`origin`)\"\n          }\n        ],\n        \"foreignKeys\": [\n          {\n            \"table\": \"book_sources\",\n            \"onDelete\": \"CASCADE\",\n            \"onUpdate\": \"NO ACTION\",\n            \"columns\": [\n              \"origin\"\n            ],\n            \"referencedColumns\": [\n              \"bookSourceUrl\"\n            ]\n          }\n        ]\n      },\n      {\n        \"tableName\": \"search_keywords\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`word` TEXT NOT NULL, `usage` INTEGER NOT NULL, `lastUseTime` INTEGER NOT NULL, PRIMARY KEY(`word`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"word\",\n            \"columnName\": \"word\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"usage\",\n            \"columnName\": \"usage\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"lastUseTime\",\n            \"columnName\": \"lastUseTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"word\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_search_keywords_word\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"word\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_search_keywords_word` ON `${TABLE_NAME}` (`word`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"cookies\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`url` TEXT NOT NULL, `cookie` TEXT NOT NULL, PRIMARY KEY(`url`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"cookie\",\n            \"columnName\": \"cookie\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"url\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_cookies_url\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"url\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_cookies_url` ON `${TABLE_NAME}` (`url`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssSources\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`sourceUrl` TEXT NOT NULL, `sourceName` TEXT NOT NULL, `sourceIcon` TEXT NOT NULL, `sourceGroup` TEXT, `sourceComment` TEXT, `enabled` INTEGER NOT NULL, `variableComment` TEXT, `enabledCookieJar` INTEGER DEFAULT 0, `concurrentRate` TEXT, `header` TEXT, `loginUrl` TEXT, `loginUi` TEXT, `loginCheckJs` TEXT, `coverDecodeJs` TEXT, `sortUrl` TEXT, `singleUrl` INTEGER NOT NULL, `articleStyle` INTEGER NOT NULL DEFAULT 0, `ruleArticles` TEXT, `ruleNextPage` TEXT, `ruleTitle` TEXT, `rulePubDate` TEXT, `ruleDescription` TEXT, `ruleImage` TEXT, `ruleLink` TEXT, `ruleContent` TEXT, `contentWhitelist` TEXT, `contentBlacklist` TEXT, `style` TEXT, `enableJs` INTEGER NOT NULL DEFAULT 1, `loadWithBaseUrl` INTEGER NOT NULL DEFAULT 1, `injectJs` TEXT, `lastUpdateTime` INTEGER NOT NULL DEFAULT 0, `customOrder` INTEGER NOT NULL DEFAULT 0, PRIMARY KEY(`sourceUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"sourceUrl\",\n            \"columnName\": \"sourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceName\",\n            \"columnName\": \"sourceName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceIcon\",\n            \"columnName\": \"sourceIcon\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceGroup\",\n            \"columnName\": \"sourceGroup\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"sourceComment\",\n            \"columnName\": \"sourceComment\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enabled\",\n            \"columnName\": \"enabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"variableComment\",\n            \"columnName\": \"variableComment\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enabledCookieJar\",\n            \"columnName\": \"enabledCookieJar\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"concurrentRate\",\n            \"columnName\": \"concurrentRate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"header\",\n            \"columnName\": \"header\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUrl\",\n            \"columnName\": \"loginUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUi\",\n            \"columnName\": \"loginUi\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginCheckJs\",\n            \"columnName\": \"loginCheckJs\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"coverDecodeJs\",\n            \"columnName\": \"coverDecodeJs\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"sortUrl\",\n            \"columnName\": \"sortUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"singleUrl\",\n            \"columnName\": \"singleUrl\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"articleStyle\",\n            \"columnName\": \"articleStyle\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"ruleArticles\",\n            \"columnName\": \"ruleArticles\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleNextPage\",\n            \"columnName\": \"ruleNextPage\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleTitle\",\n            \"columnName\": \"ruleTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"rulePubDate\",\n            \"columnName\": \"rulePubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleDescription\",\n            \"columnName\": \"ruleDescription\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleImage\",\n            \"columnName\": \"ruleImage\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleLink\",\n            \"columnName\": \"ruleLink\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleContent\",\n            \"columnName\": \"ruleContent\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"contentWhitelist\",\n            \"columnName\": \"contentWhitelist\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"contentBlacklist\",\n            \"columnName\": \"contentBlacklist\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"style\",\n            \"columnName\": \"style\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enableJs\",\n            \"columnName\": \"enableJs\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"loadWithBaseUrl\",\n            \"columnName\": \"loadWithBaseUrl\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"injectJs\",\n            \"columnName\": \"injectJs\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"lastUpdateTime\",\n            \"columnName\": \"lastUpdateTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"sourceUrl\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_rssSources_sourceUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"sourceUrl\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_rssSources_sourceUrl` ON `${TABLE_NAME}` (`sourceUrl`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"bookmarks\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`time` INTEGER NOT NULL, `bookName` TEXT NOT NULL, `bookAuthor` TEXT NOT NULL, `chapterIndex` INTEGER NOT NULL, `chapterPos` INTEGER NOT NULL, `chapterName` TEXT NOT NULL, `bookText` TEXT NOT NULL, `content` TEXT NOT NULL, PRIMARY KEY(`time`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"time\",\n            \"columnName\": \"time\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookName\",\n            \"columnName\": \"bookName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookAuthor\",\n            \"columnName\": \"bookAuthor\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterIndex\",\n            \"columnName\": \"chapterIndex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterPos\",\n            \"columnName\": \"chapterPos\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterName\",\n            \"columnName\": \"chapterName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookText\",\n            \"columnName\": \"bookText\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"time\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_bookmarks_bookName_bookAuthor\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"bookName\",\n              \"bookAuthor\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_bookmarks_bookName_bookAuthor` ON `${TABLE_NAME}` (`bookName`, `bookAuthor`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssArticles\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`origin` TEXT NOT NULL, `sort` TEXT NOT NULL, `title` TEXT NOT NULL, `order` INTEGER NOT NULL, `link` TEXT NOT NULL, `pubDate` TEXT, `description` TEXT, `content` TEXT, `image` TEXT, `read` INTEGER NOT NULL, `variable` TEXT, PRIMARY KEY(`origin`, `link`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sort\",\n            \"columnName\": \"sort\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"link\",\n            \"columnName\": \"link\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"pubDate\",\n            \"columnName\": \"pubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"description\",\n            \"columnName\": \"description\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"image\",\n            \"columnName\": \"image\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"read\",\n            \"columnName\": \"read\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"origin\",\n            \"link\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssReadRecords\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`record` TEXT NOT NULL, `read` INTEGER NOT NULL, PRIMARY KEY(`record`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"record\",\n            \"columnName\": \"record\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"read\",\n            \"columnName\": \"read\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"record\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssStars\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`origin` TEXT NOT NULL, `sort` TEXT NOT NULL, `title` TEXT NOT NULL, `starTime` INTEGER NOT NULL, `link` TEXT NOT NULL, `pubDate` TEXT, `description` TEXT, `content` TEXT, `image` TEXT, `variable` TEXT, PRIMARY KEY(`origin`, `link`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sort\",\n            \"columnName\": \"sort\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"starTime\",\n            \"columnName\": \"starTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"link\",\n            \"columnName\": \"link\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"pubDate\",\n            \"columnName\": \"pubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"description\",\n            \"columnName\": \"description\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"image\",\n            \"columnName\": \"image\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"origin\",\n            \"link\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"txtTocRules\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `rule` TEXT NOT NULL, `example` TEXT, `serialNumber` INTEGER NOT NULL, `enable` INTEGER NOT NULL, PRIMARY KEY(`id`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"rule\",\n            \"columnName\": \"rule\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"example\",\n            \"columnName\": \"example\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"serialNumber\",\n            \"columnName\": \"serialNumber\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enable\",\n            \"columnName\": \"enable\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"id\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"readRecord\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`deviceId` TEXT NOT NULL, `bookName` TEXT NOT NULL, `readTime` INTEGER NOT NULL DEFAULT 0, `lastRead` INTEGER NOT NULL DEFAULT 0, PRIMARY KEY(`deviceId`, `bookName`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"deviceId\",\n            \"columnName\": \"deviceId\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookName\",\n            \"columnName\": \"bookName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"readTime\",\n            \"columnName\": \"readTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"lastRead\",\n            \"columnName\": \"lastRead\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"deviceId\",\n            \"bookName\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"httpTTS\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `url` TEXT NOT NULL, `contentType` TEXT, `concurrentRate` TEXT DEFAULT '0', `loginUrl` TEXT, `loginUi` TEXT, `header` TEXT, `enabledCookieJar` INTEGER DEFAULT 0, `loginCheckJs` TEXT, `lastUpdateTime` INTEGER NOT NULL DEFAULT 0, PRIMARY KEY(`id`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"contentType\",\n            \"columnName\": \"contentType\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"concurrentRate\",\n            \"columnName\": \"concurrentRate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false,\n            \"defaultValue\": \"'0'\"\n          },\n          {\n            \"fieldPath\": \"loginUrl\",\n            \"columnName\": \"loginUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUi\",\n            \"columnName\": \"loginUi\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"header\",\n            \"columnName\": \"header\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enabledCookieJar\",\n            \"columnName\": \"enabledCookieJar\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"loginCheckJs\",\n            \"columnName\": \"loginCheckJs\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"lastUpdateTime\",\n            \"columnName\": \"lastUpdateTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"id\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"caches\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`key` TEXT NOT NULL, `value` TEXT, `deadline` INTEGER NOT NULL, PRIMARY KEY(`key`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"key\",\n            \"columnName\": \"key\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"value\",\n            \"columnName\": \"value\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"deadline\",\n            \"columnName\": \"deadline\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"key\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_caches_key\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"key\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_caches_key` ON `${TABLE_NAME}` (`key`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"ruleSubs\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `url` TEXT NOT NULL, `type` INTEGER NOT NULL, `customOrder` INTEGER NOT NULL, `autoUpdate` INTEGER NOT NULL, `update` INTEGER NOT NULL, PRIMARY KEY(`id`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"autoUpdate\",\n            \"columnName\": \"autoUpdate\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"update\",\n            \"columnName\": \"update\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"id\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"dictRules\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`name` TEXT NOT NULL, `urlRule` TEXT NOT NULL, `showRule` TEXT NOT NULL, `enabled` INTEGER NOT NULL, PRIMARY KEY(`name`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"urlRule\",\n            \"columnName\": \"urlRule\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"showRule\",\n            \"columnName\": \"showRule\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enabled\",\n            \"columnName\": \"enabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"name\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"keyboardAssists\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`type` INTEGER NOT NULL DEFAULT 0, `key` TEXT NOT NULL DEFAULT '', `value` TEXT NOT NULL DEFAULT '', `serialNo` INTEGER NOT NULL DEFAULT 0, PRIMARY KEY(`type`, `key`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"key\",\n            \"columnName\": \"key\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"value\",\n            \"columnName\": \"value\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"serialNo\",\n            \"columnName\": \"serialNo\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"type\",\n            \"key\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      }\n    ],\n    \"views\": [],\n    \"setupQueries\": [\n      \"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)\",\n      \"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '19e1f66252eb41afc0990f202fe4527a')\"\n    ]\n  }\n}"
  },
  {
    "path": "app/schemas/io.legado.app.data.AppDatabase/61.json",
    "content": "{\n  \"formatVersion\": 1,\n  \"database\": {\n    \"version\": 61,\n    \"identityHash\": \"9e424026dc2c4dba847b0d9a5f3f788e\",\n    \"entities\": [\n      {\n        \"tableName\": \"books\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookUrl` TEXT NOT NULL DEFAULT '', `tocUrl` TEXT NOT NULL DEFAULT '', `origin` TEXT NOT NULL DEFAULT 'loc_book', `originName` TEXT NOT NULL DEFAULT '', `name` TEXT NOT NULL DEFAULT '', `author` TEXT NOT NULL DEFAULT '', `kind` TEXT, `customTag` TEXT, `coverUrl` TEXT, `customCoverUrl` TEXT, `intro` TEXT, `customIntro` TEXT, `charset` TEXT, `type` INTEGER NOT NULL DEFAULT 0, `group` INTEGER NOT NULL DEFAULT 0, `latestChapterTitle` TEXT, `latestChapterTime` INTEGER NOT NULL DEFAULT 0, `lastCheckTime` INTEGER NOT NULL DEFAULT 0, `lastCheckCount` INTEGER NOT NULL DEFAULT 0, `totalChapterNum` INTEGER NOT NULL DEFAULT 0, `durChapterTitle` TEXT, `durChapterIndex` INTEGER NOT NULL DEFAULT 0, `durChapterPos` INTEGER NOT NULL DEFAULT 0, `durChapterTime` INTEGER NOT NULL DEFAULT 0, `wordCount` TEXT, `canUpdate` INTEGER NOT NULL DEFAULT 1, `order` INTEGER NOT NULL DEFAULT 0, `originOrder` INTEGER NOT NULL DEFAULT 0, `variable` TEXT, `readConfig` TEXT, PRIMARY KEY(`bookUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"tocUrl\",\n            \"columnName\": \"tocUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"'loc_book'\"\n          },\n          {\n            \"fieldPath\": \"originName\",\n            \"columnName\": \"originName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"author\",\n            \"columnName\": \"author\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"kind\",\n            \"columnName\": \"kind\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customTag\",\n            \"columnName\": \"customTag\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"coverUrl\",\n            \"columnName\": \"coverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customCoverUrl\",\n            \"columnName\": \"customCoverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"intro\",\n            \"columnName\": \"intro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customIntro\",\n            \"columnName\": \"customIntro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"charset\",\n            \"columnName\": \"charset\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"group\",\n            \"columnName\": \"group\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"latestChapterTitle\",\n            \"columnName\": \"latestChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"latestChapterTime\",\n            \"columnName\": \"latestChapterTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"lastCheckTime\",\n            \"columnName\": \"lastCheckTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"lastCheckCount\",\n            \"columnName\": \"lastCheckCount\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"totalChapterNum\",\n            \"columnName\": \"totalChapterNum\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"durChapterTitle\",\n            \"columnName\": \"durChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"durChapterIndex\",\n            \"columnName\": \"durChapterIndex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"durChapterPos\",\n            \"columnName\": \"durChapterPos\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"durChapterTime\",\n            \"columnName\": \"durChapterTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"wordCount\",\n            \"columnName\": \"wordCount\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"canUpdate\",\n            \"columnName\": \"canUpdate\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"originOrder\",\n            \"columnName\": \"originOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"readConfig\",\n            \"columnName\": \"readConfig\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"bookUrl\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_books_name_author\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"name\",\n              \"author\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_books_name_author` ON `${TABLE_NAME}` (`name`, `author`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"book_groups\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`groupId` INTEGER NOT NULL, `groupName` TEXT NOT NULL, `cover` TEXT, `order` INTEGER NOT NULL, `show` INTEGER NOT NULL, `bookSort` INTEGER NOT NULL DEFAULT -1, PRIMARY KEY(`groupId`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"groupId\",\n            \"columnName\": \"groupId\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"groupName\",\n            \"columnName\": \"groupName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"cover\",\n            \"columnName\": \"cover\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"show\",\n            \"columnName\": \"show\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookSort\",\n            \"columnName\": \"bookSort\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"-1\"\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"groupId\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"book_sources\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookSourceUrl` TEXT NOT NULL, `bookSourceName` TEXT NOT NULL, `bookSourceGroup` TEXT, `bookSourceType` INTEGER NOT NULL, `bookUrlPattern` TEXT, `customOrder` INTEGER NOT NULL, `enabled` INTEGER NOT NULL, `enabledExplore` INTEGER NOT NULL, `enabledReview` INTEGER, `enabledCookieJar` INTEGER DEFAULT 0, `concurrentRate` TEXT, `header` TEXT, `loginUrl` TEXT, `loginUi` TEXT, `loginCheckJs` TEXT, `coverDecodeJs` TEXT, `bookSourceComment` TEXT, `variableComment` TEXT, `lastUpdateTime` INTEGER NOT NULL, `respondTime` INTEGER NOT NULL, `weight` INTEGER NOT NULL, `exploreUrl` TEXT, `ruleExplore` TEXT, `searchUrl` TEXT, `ruleSearch` TEXT, `ruleBookInfo` TEXT, `ruleToc` TEXT, `ruleContent` TEXT, `ruleReview` TEXT, PRIMARY KEY(`bookSourceUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookSourceUrl\",\n            \"columnName\": \"bookSourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookSourceName\",\n            \"columnName\": \"bookSourceName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookSourceGroup\",\n            \"columnName\": \"bookSourceGroup\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"bookSourceType\",\n            \"columnName\": \"bookSourceType\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookUrlPattern\",\n            \"columnName\": \"bookUrlPattern\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enabled\",\n            \"columnName\": \"enabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enabledExplore\",\n            \"columnName\": \"enabledExplore\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enabledReview\",\n            \"columnName\": \"enabledReview\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enabledCookieJar\",\n            \"columnName\": \"enabledCookieJar\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"concurrentRate\",\n            \"columnName\": \"concurrentRate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"header\",\n            \"columnName\": \"header\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUrl\",\n            \"columnName\": \"loginUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUi\",\n            \"columnName\": \"loginUi\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginCheckJs\",\n            \"columnName\": \"loginCheckJs\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"coverDecodeJs\",\n            \"columnName\": \"coverDecodeJs\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"bookSourceComment\",\n            \"columnName\": \"bookSourceComment\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"variableComment\",\n            \"columnName\": \"variableComment\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"lastUpdateTime\",\n            \"columnName\": \"lastUpdateTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"respondTime\",\n            \"columnName\": \"respondTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"weight\",\n            \"columnName\": \"weight\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"exploreUrl\",\n            \"columnName\": \"exploreUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleExplore\",\n            \"columnName\": \"ruleExplore\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"searchUrl\",\n            \"columnName\": \"searchUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleSearch\",\n            \"columnName\": \"ruleSearch\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleBookInfo\",\n            \"columnName\": \"ruleBookInfo\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleToc\",\n            \"columnName\": \"ruleToc\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleContent\",\n            \"columnName\": \"ruleContent\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleReview\",\n            \"columnName\": \"ruleReview\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"bookSourceUrl\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_book_sources_bookSourceUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"bookSourceUrl\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_book_sources_bookSourceUrl` ON `${TABLE_NAME}` (`bookSourceUrl`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"chapters\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`url` TEXT NOT NULL, `title` TEXT NOT NULL, `isVolume` INTEGER NOT NULL, `baseUrl` TEXT NOT NULL, `bookUrl` TEXT NOT NULL, `index` INTEGER NOT NULL, `isVip` INTEGER NOT NULL, `isPay` INTEGER NOT NULL, `resourceUrl` TEXT, `tag` TEXT, `start` INTEGER, `end` INTEGER, `startFragmentId` TEXT, `endFragmentId` TEXT, `variable` TEXT, PRIMARY KEY(`url`, `bookUrl`), FOREIGN KEY(`bookUrl`) REFERENCES `books`(`bookUrl`) ON UPDATE NO ACTION ON DELETE CASCADE )\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"isVolume\",\n            \"columnName\": \"isVolume\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"baseUrl\",\n            \"columnName\": \"baseUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"index\",\n            \"columnName\": \"index\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"isVip\",\n            \"columnName\": \"isVip\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"isPay\",\n            \"columnName\": \"isPay\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"resourceUrl\",\n            \"columnName\": \"resourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"tag\",\n            \"columnName\": \"tag\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"start\",\n            \"columnName\": \"start\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"end\",\n            \"columnName\": \"end\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"startFragmentId\",\n            \"columnName\": \"startFragmentId\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"endFragmentId\",\n            \"columnName\": \"endFragmentId\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"url\",\n            \"bookUrl\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_chapters_bookUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"bookUrl\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_chapters_bookUrl` ON `${TABLE_NAME}` (`bookUrl`)\"\n          },\n          {\n            \"name\": \"index_chapters_bookUrl_index\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"bookUrl\",\n              \"index\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_chapters_bookUrl_index` ON `${TABLE_NAME}` (`bookUrl`, `index`)\"\n          }\n        ],\n        \"foreignKeys\": [\n          {\n            \"table\": \"books\",\n            \"onDelete\": \"CASCADE\",\n            \"onUpdate\": \"NO ACTION\",\n            \"columns\": [\n              \"bookUrl\"\n            ],\n            \"referencedColumns\": [\n              \"bookUrl\"\n            ]\n          }\n        ]\n      },\n      {\n        \"tableName\": \"replace_rules\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL DEFAULT '', `group` TEXT, `pattern` TEXT NOT NULL DEFAULT '', `replacement` TEXT NOT NULL DEFAULT '', `scope` TEXT, `scopeTitle` INTEGER NOT NULL DEFAULT 0, `scopeContent` INTEGER NOT NULL DEFAULT 1, `isEnabled` INTEGER NOT NULL DEFAULT 1, `isRegex` INTEGER NOT NULL DEFAULT 1, `timeoutMillisecond` INTEGER NOT NULL DEFAULT 3000, `sortOrder` INTEGER NOT NULL DEFAULT 0)\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"group\",\n            \"columnName\": \"group\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"pattern\",\n            \"columnName\": \"pattern\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"replacement\",\n            \"columnName\": \"replacement\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"scope\",\n            \"columnName\": \"scope\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"scopeTitle\",\n            \"columnName\": \"scopeTitle\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"scopeContent\",\n            \"columnName\": \"scopeContent\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"isEnabled\",\n            \"columnName\": \"isEnabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"isRegex\",\n            \"columnName\": \"isRegex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"timeoutMillisecond\",\n            \"columnName\": \"timeoutMillisecond\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"3000\"\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"sortOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": true,\n          \"columnNames\": [\n            \"id\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_replace_rules_id\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"id\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_replace_rules_id` ON `${TABLE_NAME}` (`id`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"searchBooks\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookUrl` TEXT NOT NULL, `origin` TEXT NOT NULL, `originName` TEXT NOT NULL, `type` INTEGER NOT NULL, `name` TEXT NOT NULL, `author` TEXT NOT NULL, `kind` TEXT, `coverUrl` TEXT, `intro` TEXT, `wordCount` TEXT, `latestChapterTitle` TEXT, `tocUrl` TEXT NOT NULL, `time` INTEGER NOT NULL, `variable` TEXT, `originOrder` INTEGER NOT NULL, PRIMARY KEY(`bookUrl`), FOREIGN KEY(`origin`) REFERENCES `book_sources`(`bookSourceUrl`) ON UPDATE NO ACTION ON DELETE CASCADE )\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"originName\",\n            \"columnName\": \"originName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"author\",\n            \"columnName\": \"author\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"kind\",\n            \"columnName\": \"kind\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"coverUrl\",\n            \"columnName\": \"coverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"intro\",\n            \"columnName\": \"intro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"wordCount\",\n            \"columnName\": \"wordCount\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"latestChapterTitle\",\n            \"columnName\": \"latestChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"tocUrl\",\n            \"columnName\": \"tocUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"time\",\n            \"columnName\": \"time\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"originOrder\",\n            \"columnName\": \"originOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"bookUrl\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_searchBooks_bookUrl\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"bookUrl\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_searchBooks_bookUrl` ON `${TABLE_NAME}` (`bookUrl`)\"\n          },\n          {\n            \"name\": \"index_searchBooks_origin\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"origin\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_searchBooks_origin` ON `${TABLE_NAME}` (`origin`)\"\n          }\n        ],\n        \"foreignKeys\": [\n          {\n            \"table\": \"book_sources\",\n            \"onDelete\": \"CASCADE\",\n            \"onUpdate\": \"NO ACTION\",\n            \"columns\": [\n              \"origin\"\n            ],\n            \"referencedColumns\": [\n              \"bookSourceUrl\"\n            ]\n          }\n        ]\n      },\n      {\n        \"tableName\": \"search_keywords\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`word` TEXT NOT NULL, `usage` INTEGER NOT NULL, `lastUseTime` INTEGER NOT NULL, PRIMARY KEY(`word`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"word\",\n            \"columnName\": \"word\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"usage\",\n            \"columnName\": \"usage\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"lastUseTime\",\n            \"columnName\": \"lastUseTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"word\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_search_keywords_word\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"word\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_search_keywords_word` ON `${TABLE_NAME}` (`word`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"cookies\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`url` TEXT NOT NULL, `cookie` TEXT NOT NULL, PRIMARY KEY(`url`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"cookie\",\n            \"columnName\": \"cookie\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"url\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_cookies_url\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"url\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_cookies_url` ON `${TABLE_NAME}` (`url`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssSources\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`sourceUrl` TEXT NOT NULL, `sourceName` TEXT NOT NULL, `sourceIcon` TEXT NOT NULL, `sourceGroup` TEXT, `sourceComment` TEXT, `enabled` INTEGER NOT NULL, `variableComment` TEXT, `enabledCookieJar` INTEGER DEFAULT 0, `concurrentRate` TEXT, `header` TEXT, `loginUrl` TEXT, `loginUi` TEXT, `loginCheckJs` TEXT, `coverDecodeJs` TEXT, `sortUrl` TEXT, `singleUrl` INTEGER NOT NULL, `articleStyle` INTEGER NOT NULL DEFAULT 0, `ruleArticles` TEXT, `ruleNextPage` TEXT, `ruleTitle` TEXT, `rulePubDate` TEXT, `ruleDescription` TEXT, `ruleImage` TEXT, `ruleLink` TEXT, `ruleContent` TEXT, `contentWhitelist` TEXT, `contentBlacklist` TEXT, `style` TEXT, `enableJs` INTEGER NOT NULL DEFAULT 1, `loadWithBaseUrl` INTEGER NOT NULL DEFAULT 1, `injectJs` TEXT, `lastUpdateTime` INTEGER NOT NULL DEFAULT 0, `customOrder` INTEGER NOT NULL DEFAULT 0, PRIMARY KEY(`sourceUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"sourceUrl\",\n            \"columnName\": \"sourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceName\",\n            \"columnName\": \"sourceName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceIcon\",\n            \"columnName\": \"sourceIcon\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceGroup\",\n            \"columnName\": \"sourceGroup\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"sourceComment\",\n            \"columnName\": \"sourceComment\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enabled\",\n            \"columnName\": \"enabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"variableComment\",\n            \"columnName\": \"variableComment\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enabledCookieJar\",\n            \"columnName\": \"enabledCookieJar\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"concurrentRate\",\n            \"columnName\": \"concurrentRate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"header\",\n            \"columnName\": \"header\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUrl\",\n            \"columnName\": \"loginUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUi\",\n            \"columnName\": \"loginUi\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginCheckJs\",\n            \"columnName\": \"loginCheckJs\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"coverDecodeJs\",\n            \"columnName\": \"coverDecodeJs\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"sortUrl\",\n            \"columnName\": \"sortUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"singleUrl\",\n            \"columnName\": \"singleUrl\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"articleStyle\",\n            \"columnName\": \"articleStyle\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"ruleArticles\",\n            \"columnName\": \"ruleArticles\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleNextPage\",\n            \"columnName\": \"ruleNextPage\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleTitle\",\n            \"columnName\": \"ruleTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"rulePubDate\",\n            \"columnName\": \"rulePubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleDescription\",\n            \"columnName\": \"ruleDescription\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleImage\",\n            \"columnName\": \"ruleImage\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleLink\",\n            \"columnName\": \"ruleLink\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleContent\",\n            \"columnName\": \"ruleContent\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"contentWhitelist\",\n            \"columnName\": \"contentWhitelist\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"contentBlacklist\",\n            \"columnName\": \"contentBlacklist\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"style\",\n            \"columnName\": \"style\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enableJs\",\n            \"columnName\": \"enableJs\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"loadWithBaseUrl\",\n            \"columnName\": \"loadWithBaseUrl\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"injectJs\",\n            \"columnName\": \"injectJs\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"lastUpdateTime\",\n            \"columnName\": \"lastUpdateTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"sourceUrl\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_rssSources_sourceUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"sourceUrl\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_rssSources_sourceUrl` ON `${TABLE_NAME}` (`sourceUrl`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"bookmarks\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`time` INTEGER NOT NULL, `bookName` TEXT NOT NULL, `bookAuthor` TEXT NOT NULL, `chapterIndex` INTEGER NOT NULL, `chapterPos` INTEGER NOT NULL, `chapterName` TEXT NOT NULL, `bookText` TEXT NOT NULL, `content` TEXT NOT NULL, PRIMARY KEY(`time`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"time\",\n            \"columnName\": \"time\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookName\",\n            \"columnName\": \"bookName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookAuthor\",\n            \"columnName\": \"bookAuthor\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterIndex\",\n            \"columnName\": \"chapterIndex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterPos\",\n            \"columnName\": \"chapterPos\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterName\",\n            \"columnName\": \"chapterName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookText\",\n            \"columnName\": \"bookText\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"time\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_bookmarks_bookName_bookAuthor\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"bookName\",\n              \"bookAuthor\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_bookmarks_bookName_bookAuthor` ON `${TABLE_NAME}` (`bookName`, `bookAuthor`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssArticles\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`origin` TEXT NOT NULL, `sort` TEXT NOT NULL, `title` TEXT NOT NULL, `order` INTEGER NOT NULL, `link` TEXT NOT NULL, `pubDate` TEXT, `description` TEXT, `content` TEXT, `image` TEXT, `read` INTEGER NOT NULL, `variable` TEXT, PRIMARY KEY(`origin`, `link`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sort\",\n            \"columnName\": \"sort\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"link\",\n            \"columnName\": \"link\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"pubDate\",\n            \"columnName\": \"pubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"description\",\n            \"columnName\": \"description\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"image\",\n            \"columnName\": \"image\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"read\",\n            \"columnName\": \"read\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"origin\",\n            \"link\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssReadRecords\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`record` TEXT NOT NULL, `read` INTEGER NOT NULL, PRIMARY KEY(`record`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"record\",\n            \"columnName\": \"record\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"read\",\n            \"columnName\": \"read\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"record\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssStars\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`origin` TEXT NOT NULL, `sort` TEXT NOT NULL, `title` TEXT NOT NULL, `starTime` INTEGER NOT NULL, `link` TEXT NOT NULL, `pubDate` TEXT, `description` TEXT, `content` TEXT, `image` TEXT, `variable` TEXT, PRIMARY KEY(`origin`, `link`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sort\",\n            \"columnName\": \"sort\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"starTime\",\n            \"columnName\": \"starTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"link\",\n            \"columnName\": \"link\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"pubDate\",\n            \"columnName\": \"pubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"description\",\n            \"columnName\": \"description\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"image\",\n            \"columnName\": \"image\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"origin\",\n            \"link\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"txtTocRules\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `rule` TEXT NOT NULL, `example` TEXT, `serialNumber` INTEGER NOT NULL, `enable` INTEGER NOT NULL, PRIMARY KEY(`id`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"rule\",\n            \"columnName\": \"rule\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"example\",\n            \"columnName\": \"example\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"serialNumber\",\n            \"columnName\": \"serialNumber\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enable\",\n            \"columnName\": \"enable\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"id\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"readRecord\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`deviceId` TEXT NOT NULL, `bookName` TEXT NOT NULL, `readTime` INTEGER NOT NULL DEFAULT 0, `lastRead` INTEGER NOT NULL DEFAULT 0, PRIMARY KEY(`deviceId`, `bookName`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"deviceId\",\n            \"columnName\": \"deviceId\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookName\",\n            \"columnName\": \"bookName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"readTime\",\n            \"columnName\": \"readTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"lastRead\",\n            \"columnName\": \"lastRead\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"deviceId\",\n            \"bookName\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"httpTTS\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `url` TEXT NOT NULL, `contentType` TEXT, `concurrentRate` TEXT DEFAULT '0', `loginUrl` TEXT, `loginUi` TEXT, `header` TEXT, `enabledCookieJar` INTEGER DEFAULT 0, `loginCheckJs` TEXT, `lastUpdateTime` INTEGER NOT NULL DEFAULT 0, PRIMARY KEY(`id`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"contentType\",\n            \"columnName\": \"contentType\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"concurrentRate\",\n            \"columnName\": \"concurrentRate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false,\n            \"defaultValue\": \"'0'\"\n          },\n          {\n            \"fieldPath\": \"loginUrl\",\n            \"columnName\": \"loginUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUi\",\n            \"columnName\": \"loginUi\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"header\",\n            \"columnName\": \"header\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enabledCookieJar\",\n            \"columnName\": \"enabledCookieJar\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"loginCheckJs\",\n            \"columnName\": \"loginCheckJs\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"lastUpdateTime\",\n            \"columnName\": \"lastUpdateTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"id\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"caches\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`key` TEXT NOT NULL, `value` TEXT, `deadline` INTEGER NOT NULL, PRIMARY KEY(`key`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"key\",\n            \"columnName\": \"key\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"value\",\n            \"columnName\": \"value\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"deadline\",\n            \"columnName\": \"deadline\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"key\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_caches_key\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"key\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_caches_key` ON `${TABLE_NAME}` (`key`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"ruleSubs\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `url` TEXT NOT NULL, `type` INTEGER NOT NULL, `customOrder` INTEGER NOT NULL, `autoUpdate` INTEGER NOT NULL, `update` INTEGER NOT NULL, PRIMARY KEY(`id`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"autoUpdate\",\n            \"columnName\": \"autoUpdate\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"update\",\n            \"columnName\": \"update\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"id\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"dictRules\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`name` TEXT NOT NULL, `urlRule` TEXT NOT NULL, `showRule` TEXT NOT NULL, `enabled` INTEGER NOT NULL DEFAULT 1, `sortNumber` INTEGER NOT NULL DEFAULT 0, PRIMARY KEY(`name`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"urlRule\",\n            \"columnName\": \"urlRule\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"showRule\",\n            \"columnName\": \"showRule\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enabled\",\n            \"columnName\": \"enabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"sortNumber\",\n            \"columnName\": \"sortNumber\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"name\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"keyboardAssists\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`type` INTEGER NOT NULL DEFAULT 0, `key` TEXT NOT NULL DEFAULT '', `value` TEXT NOT NULL DEFAULT '', `serialNo` INTEGER NOT NULL DEFAULT 0, PRIMARY KEY(`type`, `key`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"key\",\n            \"columnName\": \"key\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"value\",\n            \"columnName\": \"value\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"serialNo\",\n            \"columnName\": \"serialNo\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"type\",\n            \"key\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      }\n    ],\n    \"views\": [],\n    \"setupQueries\": [\n      \"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)\",\n      \"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '9e424026dc2c4dba847b0d9a5f3f788e')\"\n    ]\n  }\n}"
  },
  {
    "path": "app/schemas/io.legado.app.data.AppDatabase/62.json",
    "content": "{\n  \"formatVersion\": 1,\n  \"database\": {\n    \"version\": 62,\n    \"identityHash\": \"965c5f1d4c60d25b4b46efb17c575c85\",\n    \"entities\": [\n      {\n        \"tableName\": \"books\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookUrl` TEXT NOT NULL DEFAULT '', `tocUrl` TEXT NOT NULL DEFAULT '', `origin` TEXT NOT NULL DEFAULT 'loc_book', `originName` TEXT NOT NULL DEFAULT '', `name` TEXT NOT NULL DEFAULT '', `author` TEXT NOT NULL DEFAULT '', `kind` TEXT, `customTag` TEXT, `coverUrl` TEXT, `customCoverUrl` TEXT, `intro` TEXT, `customIntro` TEXT, `charset` TEXT, `type` INTEGER NOT NULL DEFAULT 0, `group` INTEGER NOT NULL DEFAULT 0, `latestChapterTitle` TEXT, `latestChapterTime` INTEGER NOT NULL DEFAULT 0, `lastCheckTime` INTEGER NOT NULL DEFAULT 0, `lastCheckCount` INTEGER NOT NULL DEFAULT 0, `totalChapterNum` INTEGER NOT NULL DEFAULT 0, `durChapterTitle` TEXT, `durChapterIndex` INTEGER NOT NULL DEFAULT 0, `durChapterPos` INTEGER NOT NULL DEFAULT 0, `durChapterTime` INTEGER NOT NULL DEFAULT 0, `wordCount` TEXT, `canUpdate` INTEGER NOT NULL DEFAULT 1, `order` INTEGER NOT NULL DEFAULT 0, `originOrder` INTEGER NOT NULL DEFAULT 0, `variable` TEXT, `readConfig` TEXT, PRIMARY KEY(`bookUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"tocUrl\",\n            \"columnName\": \"tocUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"'loc_book'\"\n          },\n          {\n            \"fieldPath\": \"originName\",\n            \"columnName\": \"originName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"author\",\n            \"columnName\": \"author\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"kind\",\n            \"columnName\": \"kind\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customTag\",\n            \"columnName\": \"customTag\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"coverUrl\",\n            \"columnName\": \"coverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customCoverUrl\",\n            \"columnName\": \"customCoverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"intro\",\n            \"columnName\": \"intro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customIntro\",\n            \"columnName\": \"customIntro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"charset\",\n            \"columnName\": \"charset\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"group\",\n            \"columnName\": \"group\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"latestChapterTitle\",\n            \"columnName\": \"latestChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"latestChapterTime\",\n            \"columnName\": \"latestChapterTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"lastCheckTime\",\n            \"columnName\": \"lastCheckTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"lastCheckCount\",\n            \"columnName\": \"lastCheckCount\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"totalChapterNum\",\n            \"columnName\": \"totalChapterNum\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"durChapterTitle\",\n            \"columnName\": \"durChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"durChapterIndex\",\n            \"columnName\": \"durChapterIndex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"durChapterPos\",\n            \"columnName\": \"durChapterPos\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"durChapterTime\",\n            \"columnName\": \"durChapterTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"wordCount\",\n            \"columnName\": \"wordCount\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"canUpdate\",\n            \"columnName\": \"canUpdate\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"originOrder\",\n            \"columnName\": \"originOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"readConfig\",\n            \"columnName\": \"readConfig\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"bookUrl\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_books_name_author\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"name\",\n              \"author\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_books_name_author` ON `${TABLE_NAME}` (`name`, `author`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"book_groups\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`groupId` INTEGER NOT NULL, `groupName` TEXT NOT NULL, `cover` TEXT, `order` INTEGER NOT NULL, `show` INTEGER NOT NULL, `bookSort` INTEGER NOT NULL DEFAULT -1, PRIMARY KEY(`groupId`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"groupId\",\n            \"columnName\": \"groupId\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"groupName\",\n            \"columnName\": \"groupName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"cover\",\n            \"columnName\": \"cover\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"show\",\n            \"columnName\": \"show\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookSort\",\n            \"columnName\": \"bookSort\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"-1\"\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"groupId\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"book_sources\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookSourceUrl` TEXT NOT NULL, `bookSourceName` TEXT NOT NULL, `bookSourceGroup` TEXT, `bookSourceType` INTEGER NOT NULL, `bookUrlPattern` TEXT, `customOrder` INTEGER NOT NULL, `enabled` INTEGER NOT NULL, `enabledExplore` INTEGER NOT NULL, `enabledReview` INTEGER, `enabledCookieJar` INTEGER DEFAULT 0, `concurrentRate` TEXT, `header` TEXT, `loginUrl` TEXT, `loginUi` TEXT, `loginCheckJs` TEXT, `coverDecodeJs` TEXT, `bookSourceComment` TEXT, `variableComment` TEXT, `lastUpdateTime` INTEGER NOT NULL, `respondTime` INTEGER NOT NULL, `weight` INTEGER NOT NULL, `exploreUrl` TEXT, `ruleExplore` TEXT, `searchUrl` TEXT, `ruleSearch` TEXT, `ruleBookInfo` TEXT, `ruleToc` TEXT, `ruleContent` TEXT, `ruleReview` TEXT, PRIMARY KEY(`bookSourceUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookSourceUrl\",\n            \"columnName\": \"bookSourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookSourceName\",\n            \"columnName\": \"bookSourceName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookSourceGroup\",\n            \"columnName\": \"bookSourceGroup\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"bookSourceType\",\n            \"columnName\": \"bookSourceType\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookUrlPattern\",\n            \"columnName\": \"bookUrlPattern\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enabled\",\n            \"columnName\": \"enabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enabledExplore\",\n            \"columnName\": \"enabledExplore\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enabledReview\",\n            \"columnName\": \"enabledReview\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enabledCookieJar\",\n            \"columnName\": \"enabledCookieJar\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"concurrentRate\",\n            \"columnName\": \"concurrentRate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"header\",\n            \"columnName\": \"header\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUrl\",\n            \"columnName\": \"loginUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUi\",\n            \"columnName\": \"loginUi\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginCheckJs\",\n            \"columnName\": \"loginCheckJs\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"coverDecodeJs\",\n            \"columnName\": \"coverDecodeJs\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"bookSourceComment\",\n            \"columnName\": \"bookSourceComment\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"variableComment\",\n            \"columnName\": \"variableComment\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"lastUpdateTime\",\n            \"columnName\": \"lastUpdateTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"respondTime\",\n            \"columnName\": \"respondTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"weight\",\n            \"columnName\": \"weight\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"exploreUrl\",\n            \"columnName\": \"exploreUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleExplore\",\n            \"columnName\": \"ruleExplore\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"searchUrl\",\n            \"columnName\": \"searchUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleSearch\",\n            \"columnName\": \"ruleSearch\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleBookInfo\",\n            \"columnName\": \"ruleBookInfo\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleToc\",\n            \"columnName\": \"ruleToc\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleContent\",\n            \"columnName\": \"ruleContent\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleReview\",\n            \"columnName\": \"ruleReview\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"bookSourceUrl\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_book_sources_bookSourceUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"bookSourceUrl\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_book_sources_bookSourceUrl` ON `${TABLE_NAME}` (`bookSourceUrl`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"chapters\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`url` TEXT NOT NULL, `title` TEXT NOT NULL, `isVolume` INTEGER NOT NULL, `baseUrl` TEXT NOT NULL, `bookUrl` TEXT NOT NULL, `index` INTEGER NOT NULL, `isVip` INTEGER NOT NULL, `isPay` INTEGER NOT NULL, `resourceUrl` TEXT, `tag` TEXT, `start` INTEGER, `end` INTEGER, `startFragmentId` TEXT, `endFragmentId` TEXT, `variable` TEXT, PRIMARY KEY(`url`, `bookUrl`), FOREIGN KEY(`bookUrl`) REFERENCES `books`(`bookUrl`) ON UPDATE NO ACTION ON DELETE CASCADE )\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"isVolume\",\n            \"columnName\": \"isVolume\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"baseUrl\",\n            \"columnName\": \"baseUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"index\",\n            \"columnName\": \"index\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"isVip\",\n            \"columnName\": \"isVip\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"isPay\",\n            \"columnName\": \"isPay\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"resourceUrl\",\n            \"columnName\": \"resourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"tag\",\n            \"columnName\": \"tag\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"start\",\n            \"columnName\": \"start\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"end\",\n            \"columnName\": \"end\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"startFragmentId\",\n            \"columnName\": \"startFragmentId\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"endFragmentId\",\n            \"columnName\": \"endFragmentId\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"url\",\n            \"bookUrl\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_chapters_bookUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"bookUrl\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_chapters_bookUrl` ON `${TABLE_NAME}` (`bookUrl`)\"\n          },\n          {\n            \"name\": \"index_chapters_bookUrl_index\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"bookUrl\",\n              \"index\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_chapters_bookUrl_index` ON `${TABLE_NAME}` (`bookUrl`, `index`)\"\n          }\n        ],\n        \"foreignKeys\": [\n          {\n            \"table\": \"books\",\n            \"onDelete\": \"CASCADE\",\n            \"onUpdate\": \"NO ACTION\",\n            \"columns\": [\n              \"bookUrl\"\n            ],\n            \"referencedColumns\": [\n              \"bookUrl\"\n            ]\n          }\n        ]\n      },\n      {\n        \"tableName\": \"replace_rules\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL DEFAULT '', `group` TEXT, `pattern` TEXT NOT NULL DEFAULT '', `replacement` TEXT NOT NULL DEFAULT '', `scope` TEXT, `scopeTitle` INTEGER NOT NULL DEFAULT 0, `scopeContent` INTEGER NOT NULL DEFAULT 1, `isEnabled` INTEGER NOT NULL DEFAULT 1, `isRegex` INTEGER NOT NULL DEFAULT 1, `timeoutMillisecond` INTEGER NOT NULL DEFAULT 3000, `sortOrder` INTEGER NOT NULL DEFAULT 0)\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"group\",\n            \"columnName\": \"group\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"pattern\",\n            \"columnName\": \"pattern\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"replacement\",\n            \"columnName\": \"replacement\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"scope\",\n            \"columnName\": \"scope\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"scopeTitle\",\n            \"columnName\": \"scopeTitle\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"scopeContent\",\n            \"columnName\": \"scopeContent\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"isEnabled\",\n            \"columnName\": \"isEnabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"isRegex\",\n            \"columnName\": \"isRegex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"timeoutMillisecond\",\n            \"columnName\": \"timeoutMillisecond\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"3000\"\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"sortOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": true,\n          \"columnNames\": [\n            \"id\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_replace_rules_id\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"id\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_replace_rules_id` ON `${TABLE_NAME}` (`id`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"searchBooks\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookUrl` TEXT NOT NULL, `origin` TEXT NOT NULL, `originName` TEXT NOT NULL, `type` INTEGER NOT NULL, `name` TEXT NOT NULL, `author` TEXT NOT NULL, `kind` TEXT, `coverUrl` TEXT, `intro` TEXT, `wordCount` TEXT, `latestChapterTitle` TEXT, `tocUrl` TEXT NOT NULL, `time` INTEGER NOT NULL, `variable` TEXT, `originOrder` INTEGER NOT NULL, PRIMARY KEY(`bookUrl`), FOREIGN KEY(`origin`) REFERENCES `book_sources`(`bookSourceUrl`) ON UPDATE NO ACTION ON DELETE CASCADE )\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"originName\",\n            \"columnName\": \"originName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"author\",\n            \"columnName\": \"author\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"kind\",\n            \"columnName\": \"kind\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"coverUrl\",\n            \"columnName\": \"coverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"intro\",\n            \"columnName\": \"intro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"wordCount\",\n            \"columnName\": \"wordCount\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"latestChapterTitle\",\n            \"columnName\": \"latestChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"tocUrl\",\n            \"columnName\": \"tocUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"time\",\n            \"columnName\": \"time\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"originOrder\",\n            \"columnName\": \"originOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"bookUrl\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_searchBooks_bookUrl\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"bookUrl\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_searchBooks_bookUrl` ON `${TABLE_NAME}` (`bookUrl`)\"\n          },\n          {\n            \"name\": \"index_searchBooks_origin\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"origin\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_searchBooks_origin` ON `${TABLE_NAME}` (`origin`)\"\n          }\n        ],\n        \"foreignKeys\": [\n          {\n            \"table\": \"book_sources\",\n            \"onDelete\": \"CASCADE\",\n            \"onUpdate\": \"NO ACTION\",\n            \"columns\": [\n              \"origin\"\n            ],\n            \"referencedColumns\": [\n              \"bookSourceUrl\"\n            ]\n          }\n        ]\n      },\n      {\n        \"tableName\": \"search_keywords\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`word` TEXT NOT NULL, `usage` INTEGER NOT NULL, `lastUseTime` INTEGER NOT NULL, PRIMARY KEY(`word`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"word\",\n            \"columnName\": \"word\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"usage\",\n            \"columnName\": \"usage\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"lastUseTime\",\n            \"columnName\": \"lastUseTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"word\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_search_keywords_word\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"word\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_search_keywords_word` ON `${TABLE_NAME}` (`word`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"cookies\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`url` TEXT NOT NULL, `cookie` TEXT NOT NULL, PRIMARY KEY(`url`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"cookie\",\n            \"columnName\": \"cookie\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"url\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_cookies_url\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"url\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_cookies_url` ON `${TABLE_NAME}` (`url`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssSources\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`sourceUrl` TEXT NOT NULL, `sourceName` TEXT NOT NULL, `sourceIcon` TEXT NOT NULL, `sourceGroup` TEXT, `sourceComment` TEXT, `enabled` INTEGER NOT NULL, `variableComment` TEXT, `enabledCookieJar` INTEGER DEFAULT 0, `concurrentRate` TEXT, `header` TEXT, `loginUrl` TEXT, `loginUi` TEXT, `loginCheckJs` TEXT, `coverDecodeJs` TEXT, `sortUrl` TEXT, `singleUrl` INTEGER NOT NULL, `articleStyle` INTEGER NOT NULL DEFAULT 0, `ruleArticles` TEXT, `ruleNextPage` TEXT, `ruleTitle` TEXT, `rulePubDate` TEXT, `ruleDescription` TEXT, `ruleImage` TEXT, `ruleLink` TEXT, `ruleContent` TEXT, `contentWhitelist` TEXT, `contentBlacklist` TEXT, `style` TEXT, `enableJs` INTEGER NOT NULL DEFAULT 1, `loadWithBaseUrl` INTEGER NOT NULL DEFAULT 1, `injectJs` TEXT, `lastUpdateTime` INTEGER NOT NULL DEFAULT 0, `customOrder` INTEGER NOT NULL DEFAULT 0, PRIMARY KEY(`sourceUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"sourceUrl\",\n            \"columnName\": \"sourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceName\",\n            \"columnName\": \"sourceName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceIcon\",\n            \"columnName\": \"sourceIcon\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceGroup\",\n            \"columnName\": \"sourceGroup\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"sourceComment\",\n            \"columnName\": \"sourceComment\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enabled\",\n            \"columnName\": \"enabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"variableComment\",\n            \"columnName\": \"variableComment\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enabledCookieJar\",\n            \"columnName\": \"enabledCookieJar\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"concurrentRate\",\n            \"columnName\": \"concurrentRate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"header\",\n            \"columnName\": \"header\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUrl\",\n            \"columnName\": \"loginUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUi\",\n            \"columnName\": \"loginUi\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginCheckJs\",\n            \"columnName\": \"loginCheckJs\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"coverDecodeJs\",\n            \"columnName\": \"coverDecodeJs\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"sortUrl\",\n            \"columnName\": \"sortUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"singleUrl\",\n            \"columnName\": \"singleUrl\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"articleStyle\",\n            \"columnName\": \"articleStyle\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"ruleArticles\",\n            \"columnName\": \"ruleArticles\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleNextPage\",\n            \"columnName\": \"ruleNextPage\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleTitle\",\n            \"columnName\": \"ruleTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"rulePubDate\",\n            \"columnName\": \"rulePubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleDescription\",\n            \"columnName\": \"ruleDescription\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleImage\",\n            \"columnName\": \"ruleImage\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleLink\",\n            \"columnName\": \"ruleLink\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleContent\",\n            \"columnName\": \"ruleContent\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"contentWhitelist\",\n            \"columnName\": \"contentWhitelist\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"contentBlacklist\",\n            \"columnName\": \"contentBlacklist\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"style\",\n            \"columnName\": \"style\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enableJs\",\n            \"columnName\": \"enableJs\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"loadWithBaseUrl\",\n            \"columnName\": \"loadWithBaseUrl\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"injectJs\",\n            \"columnName\": \"injectJs\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"lastUpdateTime\",\n            \"columnName\": \"lastUpdateTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"sourceUrl\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_rssSources_sourceUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"sourceUrl\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_rssSources_sourceUrl` ON `${TABLE_NAME}` (`sourceUrl`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"bookmarks\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`time` INTEGER NOT NULL, `bookName` TEXT NOT NULL, `bookAuthor` TEXT NOT NULL, `chapterIndex` INTEGER NOT NULL, `chapterPos` INTEGER NOT NULL, `chapterName` TEXT NOT NULL, `bookText` TEXT NOT NULL, `content` TEXT NOT NULL, PRIMARY KEY(`time`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"time\",\n            \"columnName\": \"time\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookName\",\n            \"columnName\": \"bookName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookAuthor\",\n            \"columnName\": \"bookAuthor\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterIndex\",\n            \"columnName\": \"chapterIndex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterPos\",\n            \"columnName\": \"chapterPos\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterName\",\n            \"columnName\": \"chapterName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookText\",\n            \"columnName\": \"bookText\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"time\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_bookmarks_bookName_bookAuthor\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"bookName\",\n              \"bookAuthor\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_bookmarks_bookName_bookAuthor` ON `${TABLE_NAME}` (`bookName`, `bookAuthor`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssArticles\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`origin` TEXT NOT NULL, `sort` TEXT NOT NULL, `title` TEXT NOT NULL, `order` INTEGER NOT NULL, `link` TEXT NOT NULL, `pubDate` TEXT, `description` TEXT, `content` TEXT, `image` TEXT, `read` INTEGER NOT NULL, `variable` TEXT, PRIMARY KEY(`origin`, `link`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sort\",\n            \"columnName\": \"sort\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"link\",\n            \"columnName\": \"link\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"pubDate\",\n            \"columnName\": \"pubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"description\",\n            \"columnName\": \"description\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"image\",\n            \"columnName\": \"image\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"read\",\n            \"columnName\": \"read\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"origin\",\n            \"link\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssReadRecords\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`record` TEXT NOT NULL, `read` INTEGER NOT NULL, PRIMARY KEY(`record`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"record\",\n            \"columnName\": \"record\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"read\",\n            \"columnName\": \"read\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"record\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssStars\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`origin` TEXT NOT NULL, `sort` TEXT NOT NULL, `title` TEXT NOT NULL, `starTime` INTEGER NOT NULL, `link` TEXT NOT NULL, `pubDate` TEXT, `description` TEXT, `content` TEXT, `image` TEXT, `variable` TEXT, PRIMARY KEY(`origin`, `link`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sort\",\n            \"columnName\": \"sort\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"starTime\",\n            \"columnName\": \"starTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"link\",\n            \"columnName\": \"link\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"pubDate\",\n            \"columnName\": \"pubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"description\",\n            \"columnName\": \"description\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"image\",\n            \"columnName\": \"image\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"origin\",\n            \"link\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"txtTocRules\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `rule` TEXT NOT NULL, `example` TEXT, `serialNumber` INTEGER NOT NULL, `enable` INTEGER NOT NULL, PRIMARY KEY(`id`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"rule\",\n            \"columnName\": \"rule\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"example\",\n            \"columnName\": \"example\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"serialNumber\",\n            \"columnName\": \"serialNumber\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enable\",\n            \"columnName\": \"enable\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"id\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"readRecord\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`deviceId` TEXT NOT NULL, `bookName` TEXT NOT NULL, `readTime` INTEGER NOT NULL DEFAULT 0, `lastRead` INTEGER NOT NULL DEFAULT 0, PRIMARY KEY(`deviceId`, `bookName`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"deviceId\",\n            \"columnName\": \"deviceId\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookName\",\n            \"columnName\": \"bookName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"readTime\",\n            \"columnName\": \"readTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"lastRead\",\n            \"columnName\": \"lastRead\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"deviceId\",\n            \"bookName\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"httpTTS\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `url` TEXT NOT NULL, `contentType` TEXT, `concurrentRate` TEXT DEFAULT '0', `loginUrl` TEXT, `loginUi` TEXT, `header` TEXT, `enabledCookieJar` INTEGER DEFAULT 0, `loginCheckJs` TEXT, `lastUpdateTime` INTEGER NOT NULL DEFAULT 0, PRIMARY KEY(`id`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"contentType\",\n            \"columnName\": \"contentType\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"concurrentRate\",\n            \"columnName\": \"concurrentRate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false,\n            \"defaultValue\": \"'0'\"\n          },\n          {\n            \"fieldPath\": \"loginUrl\",\n            \"columnName\": \"loginUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUi\",\n            \"columnName\": \"loginUi\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"header\",\n            \"columnName\": \"header\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enabledCookieJar\",\n            \"columnName\": \"enabledCookieJar\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"loginCheckJs\",\n            \"columnName\": \"loginCheckJs\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"lastUpdateTime\",\n            \"columnName\": \"lastUpdateTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"id\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"caches\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`key` TEXT NOT NULL, `value` TEXT, `deadline` INTEGER NOT NULL, PRIMARY KEY(`key`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"key\",\n            \"columnName\": \"key\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"value\",\n            \"columnName\": \"value\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"deadline\",\n            \"columnName\": \"deadline\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"key\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_caches_key\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"key\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_caches_key` ON `${TABLE_NAME}` (`key`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"ruleSubs\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `url` TEXT NOT NULL, `type` INTEGER NOT NULL, `customOrder` INTEGER NOT NULL, `autoUpdate` INTEGER NOT NULL, `update` INTEGER NOT NULL, PRIMARY KEY(`id`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"autoUpdate\",\n            \"columnName\": \"autoUpdate\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"update\",\n            \"columnName\": \"update\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"id\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"dictRules\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`name` TEXT NOT NULL, `urlRule` TEXT NOT NULL, `showRule` TEXT NOT NULL, `enabled` INTEGER NOT NULL DEFAULT 1, `sortNumber` INTEGER NOT NULL DEFAULT 0, PRIMARY KEY(`name`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"urlRule\",\n            \"columnName\": \"urlRule\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"showRule\",\n            \"columnName\": \"showRule\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enabled\",\n            \"columnName\": \"enabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"sortNumber\",\n            \"columnName\": \"sortNumber\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"name\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"keyboardAssists\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`type` INTEGER NOT NULL DEFAULT 0, `key` TEXT NOT NULL DEFAULT '', `value` TEXT NOT NULL DEFAULT '', `serialNo` INTEGER NOT NULL DEFAULT 0, PRIMARY KEY(`type`, `key`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"key\",\n            \"columnName\": \"key\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"value\",\n            \"columnName\": \"value\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"serialNo\",\n            \"columnName\": \"serialNo\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"type\",\n            \"key\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"servers\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `type` TEXT NOT NULL, `config` TEXT, `sortNumber` INTEGER NOT NULL, PRIMARY KEY(`id`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"config\",\n            \"columnName\": \"config\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"sortNumber\",\n            \"columnName\": \"sortNumber\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"id\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      }\n    ],\n    \"views\": [],\n    \"setupQueries\": [\n      \"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)\",\n      \"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '965c5f1d4c60d25b4b46efb17c575c85')\"\n    ]\n  }\n}"
  },
  {
    "path": "app/schemas/io.legado.app.data.AppDatabase/63.json",
    "content": "{\n  \"formatVersion\": 1,\n  \"database\": {\n    \"version\": 63,\n    \"identityHash\": \"5763792a4d08dc4cd7ebec9fb3215458\",\n    \"entities\": [\n      {\n        \"tableName\": \"books\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookUrl` TEXT NOT NULL DEFAULT '', `tocUrl` TEXT NOT NULL DEFAULT '', `origin` TEXT NOT NULL DEFAULT 'loc_book', `originName` TEXT NOT NULL DEFAULT '', `name` TEXT NOT NULL DEFAULT '', `author` TEXT NOT NULL DEFAULT '', `kind` TEXT, `customTag` TEXT, `coverUrl` TEXT, `customCoverUrl` TEXT, `intro` TEXT, `customIntro` TEXT, `charset` TEXT, `type` INTEGER NOT NULL DEFAULT 0, `group` INTEGER NOT NULL DEFAULT 0, `latestChapterTitle` TEXT, `latestChapterTime` INTEGER NOT NULL DEFAULT 0, `lastCheckTime` INTEGER NOT NULL DEFAULT 0, `lastCheckCount` INTEGER NOT NULL DEFAULT 0, `totalChapterNum` INTEGER NOT NULL DEFAULT 0, `durChapterTitle` TEXT, `durChapterIndex` INTEGER NOT NULL DEFAULT 0, `durChapterPos` INTEGER NOT NULL DEFAULT 0, `durChapterTime` INTEGER NOT NULL DEFAULT 0, `wordCount` TEXT, `canUpdate` INTEGER NOT NULL DEFAULT 1, `order` INTEGER NOT NULL DEFAULT 0, `originOrder` INTEGER NOT NULL DEFAULT 0, `variable` TEXT, `readConfig` TEXT, PRIMARY KEY(`bookUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"tocUrl\",\n            \"columnName\": \"tocUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"'loc_book'\"\n          },\n          {\n            \"fieldPath\": \"originName\",\n            \"columnName\": \"originName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"author\",\n            \"columnName\": \"author\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"kind\",\n            \"columnName\": \"kind\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customTag\",\n            \"columnName\": \"customTag\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"coverUrl\",\n            \"columnName\": \"coverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customCoverUrl\",\n            \"columnName\": \"customCoverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"intro\",\n            \"columnName\": \"intro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customIntro\",\n            \"columnName\": \"customIntro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"charset\",\n            \"columnName\": \"charset\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"group\",\n            \"columnName\": \"group\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"latestChapterTitle\",\n            \"columnName\": \"latestChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"latestChapterTime\",\n            \"columnName\": \"latestChapterTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"lastCheckTime\",\n            \"columnName\": \"lastCheckTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"lastCheckCount\",\n            \"columnName\": \"lastCheckCount\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"totalChapterNum\",\n            \"columnName\": \"totalChapterNum\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"durChapterTitle\",\n            \"columnName\": \"durChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"durChapterIndex\",\n            \"columnName\": \"durChapterIndex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"durChapterPos\",\n            \"columnName\": \"durChapterPos\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"durChapterTime\",\n            \"columnName\": \"durChapterTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"wordCount\",\n            \"columnName\": \"wordCount\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"canUpdate\",\n            \"columnName\": \"canUpdate\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"originOrder\",\n            \"columnName\": \"originOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"readConfig\",\n            \"columnName\": \"readConfig\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"bookUrl\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_books_name_author\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"name\",\n              \"author\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_books_name_author` ON `${TABLE_NAME}` (`name`, `author`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"book_groups\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`groupId` INTEGER NOT NULL, `groupName` TEXT NOT NULL, `cover` TEXT, `order` INTEGER NOT NULL, `enableRefresh` INTEGER NOT NULL DEFAULT 1, `show` INTEGER NOT NULL DEFAULT 1, `bookSort` INTEGER NOT NULL DEFAULT -1, PRIMARY KEY(`groupId`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"groupId\",\n            \"columnName\": \"groupId\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"groupName\",\n            \"columnName\": \"groupName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"cover\",\n            \"columnName\": \"cover\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enableRefresh\",\n            \"columnName\": \"enableRefresh\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"show\",\n            \"columnName\": \"show\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"bookSort\",\n            \"columnName\": \"bookSort\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"-1\"\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"groupId\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"book_sources\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookSourceUrl` TEXT NOT NULL, `bookSourceName` TEXT NOT NULL, `bookSourceGroup` TEXT, `bookSourceType` INTEGER NOT NULL, `bookUrlPattern` TEXT, `customOrder` INTEGER NOT NULL, `enabled` INTEGER NOT NULL, `enabledExplore` INTEGER NOT NULL, `enabledReview` INTEGER, `enabledCookieJar` INTEGER DEFAULT 0, `concurrentRate` TEXT, `header` TEXT, `loginUrl` TEXT, `loginUi` TEXT, `loginCheckJs` TEXT, `coverDecodeJs` TEXT, `bookSourceComment` TEXT, `variableComment` TEXT, `lastUpdateTime` INTEGER NOT NULL, `respondTime` INTEGER NOT NULL, `weight` INTEGER NOT NULL, `exploreUrl` TEXT, `ruleExplore` TEXT, `searchUrl` TEXT, `ruleSearch` TEXT, `ruleBookInfo` TEXT, `ruleToc` TEXT, `ruleContent` TEXT, `ruleReview` TEXT, PRIMARY KEY(`bookSourceUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookSourceUrl\",\n            \"columnName\": \"bookSourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookSourceName\",\n            \"columnName\": \"bookSourceName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookSourceGroup\",\n            \"columnName\": \"bookSourceGroup\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"bookSourceType\",\n            \"columnName\": \"bookSourceType\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookUrlPattern\",\n            \"columnName\": \"bookUrlPattern\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enabled\",\n            \"columnName\": \"enabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enabledExplore\",\n            \"columnName\": \"enabledExplore\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enabledReview\",\n            \"columnName\": \"enabledReview\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enabledCookieJar\",\n            \"columnName\": \"enabledCookieJar\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"concurrentRate\",\n            \"columnName\": \"concurrentRate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"header\",\n            \"columnName\": \"header\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUrl\",\n            \"columnName\": \"loginUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUi\",\n            \"columnName\": \"loginUi\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginCheckJs\",\n            \"columnName\": \"loginCheckJs\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"coverDecodeJs\",\n            \"columnName\": \"coverDecodeJs\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"bookSourceComment\",\n            \"columnName\": \"bookSourceComment\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"variableComment\",\n            \"columnName\": \"variableComment\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"lastUpdateTime\",\n            \"columnName\": \"lastUpdateTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"respondTime\",\n            \"columnName\": \"respondTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"weight\",\n            \"columnName\": \"weight\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"exploreUrl\",\n            \"columnName\": \"exploreUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleExplore\",\n            \"columnName\": \"ruleExplore\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"searchUrl\",\n            \"columnName\": \"searchUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleSearch\",\n            \"columnName\": \"ruleSearch\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleBookInfo\",\n            \"columnName\": \"ruleBookInfo\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleToc\",\n            \"columnName\": \"ruleToc\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleContent\",\n            \"columnName\": \"ruleContent\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleReview\",\n            \"columnName\": \"ruleReview\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"bookSourceUrl\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_book_sources_bookSourceUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"bookSourceUrl\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_book_sources_bookSourceUrl` ON `${TABLE_NAME}` (`bookSourceUrl`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"chapters\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`url` TEXT NOT NULL, `title` TEXT NOT NULL, `isVolume` INTEGER NOT NULL, `baseUrl` TEXT NOT NULL, `bookUrl` TEXT NOT NULL, `index` INTEGER NOT NULL, `isVip` INTEGER NOT NULL, `isPay` INTEGER NOT NULL, `resourceUrl` TEXT, `tag` TEXT, `start` INTEGER, `end` INTEGER, `startFragmentId` TEXT, `endFragmentId` TEXT, `variable` TEXT, PRIMARY KEY(`url`, `bookUrl`), FOREIGN KEY(`bookUrl`) REFERENCES `books`(`bookUrl`) ON UPDATE NO ACTION ON DELETE CASCADE )\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"isVolume\",\n            \"columnName\": \"isVolume\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"baseUrl\",\n            \"columnName\": \"baseUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"index\",\n            \"columnName\": \"index\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"isVip\",\n            \"columnName\": \"isVip\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"isPay\",\n            \"columnName\": \"isPay\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"resourceUrl\",\n            \"columnName\": \"resourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"tag\",\n            \"columnName\": \"tag\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"start\",\n            \"columnName\": \"start\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"end\",\n            \"columnName\": \"end\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"startFragmentId\",\n            \"columnName\": \"startFragmentId\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"endFragmentId\",\n            \"columnName\": \"endFragmentId\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"url\",\n            \"bookUrl\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_chapters_bookUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"bookUrl\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_chapters_bookUrl` ON `${TABLE_NAME}` (`bookUrl`)\"\n          },\n          {\n            \"name\": \"index_chapters_bookUrl_index\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"bookUrl\",\n              \"index\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_chapters_bookUrl_index` ON `${TABLE_NAME}` (`bookUrl`, `index`)\"\n          }\n        ],\n        \"foreignKeys\": [\n          {\n            \"table\": \"books\",\n            \"onDelete\": \"CASCADE\",\n            \"onUpdate\": \"NO ACTION\",\n            \"columns\": [\n              \"bookUrl\"\n            ],\n            \"referencedColumns\": [\n              \"bookUrl\"\n            ]\n          }\n        ]\n      },\n      {\n        \"tableName\": \"replace_rules\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL DEFAULT '', `group` TEXT, `pattern` TEXT NOT NULL DEFAULT '', `replacement` TEXT NOT NULL DEFAULT '', `scope` TEXT, `scopeTitle` INTEGER NOT NULL DEFAULT 0, `scopeContent` INTEGER NOT NULL DEFAULT 1, `isEnabled` INTEGER NOT NULL DEFAULT 1, `isRegex` INTEGER NOT NULL DEFAULT 1, `timeoutMillisecond` INTEGER NOT NULL DEFAULT 3000, `sortOrder` INTEGER NOT NULL DEFAULT 0)\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"group\",\n            \"columnName\": \"group\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"pattern\",\n            \"columnName\": \"pattern\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"replacement\",\n            \"columnName\": \"replacement\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"scope\",\n            \"columnName\": \"scope\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"scopeTitle\",\n            \"columnName\": \"scopeTitle\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"scopeContent\",\n            \"columnName\": \"scopeContent\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"isEnabled\",\n            \"columnName\": \"isEnabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"isRegex\",\n            \"columnName\": \"isRegex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"timeoutMillisecond\",\n            \"columnName\": \"timeoutMillisecond\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"3000\"\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"sortOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": true,\n          \"columnNames\": [\n            \"id\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_replace_rules_id\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"id\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_replace_rules_id` ON `${TABLE_NAME}` (`id`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"searchBooks\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookUrl` TEXT NOT NULL, `origin` TEXT NOT NULL, `originName` TEXT NOT NULL, `type` INTEGER NOT NULL, `name` TEXT NOT NULL, `author` TEXT NOT NULL, `kind` TEXT, `coverUrl` TEXT, `intro` TEXT, `wordCount` TEXT, `latestChapterTitle` TEXT, `tocUrl` TEXT NOT NULL, `time` INTEGER NOT NULL, `variable` TEXT, `originOrder` INTEGER NOT NULL, PRIMARY KEY(`bookUrl`), FOREIGN KEY(`origin`) REFERENCES `book_sources`(`bookSourceUrl`) ON UPDATE NO ACTION ON DELETE CASCADE )\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"originName\",\n            \"columnName\": \"originName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"author\",\n            \"columnName\": \"author\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"kind\",\n            \"columnName\": \"kind\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"coverUrl\",\n            \"columnName\": \"coverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"intro\",\n            \"columnName\": \"intro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"wordCount\",\n            \"columnName\": \"wordCount\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"latestChapterTitle\",\n            \"columnName\": \"latestChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"tocUrl\",\n            \"columnName\": \"tocUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"time\",\n            \"columnName\": \"time\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"originOrder\",\n            \"columnName\": \"originOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"bookUrl\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_searchBooks_bookUrl\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"bookUrl\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_searchBooks_bookUrl` ON `${TABLE_NAME}` (`bookUrl`)\"\n          },\n          {\n            \"name\": \"index_searchBooks_origin\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"origin\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_searchBooks_origin` ON `${TABLE_NAME}` (`origin`)\"\n          }\n        ],\n        \"foreignKeys\": [\n          {\n            \"table\": \"book_sources\",\n            \"onDelete\": \"CASCADE\",\n            \"onUpdate\": \"NO ACTION\",\n            \"columns\": [\n              \"origin\"\n            ],\n            \"referencedColumns\": [\n              \"bookSourceUrl\"\n            ]\n          }\n        ]\n      },\n      {\n        \"tableName\": \"search_keywords\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`word` TEXT NOT NULL, `usage` INTEGER NOT NULL, `lastUseTime` INTEGER NOT NULL, PRIMARY KEY(`word`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"word\",\n            \"columnName\": \"word\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"usage\",\n            \"columnName\": \"usage\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"lastUseTime\",\n            \"columnName\": \"lastUseTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"word\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_search_keywords_word\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"word\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_search_keywords_word` ON `${TABLE_NAME}` (`word`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"cookies\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`url` TEXT NOT NULL, `cookie` TEXT NOT NULL, PRIMARY KEY(`url`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"cookie\",\n            \"columnName\": \"cookie\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"url\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_cookies_url\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"url\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_cookies_url` ON `${TABLE_NAME}` (`url`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssSources\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`sourceUrl` TEXT NOT NULL, `sourceName` TEXT NOT NULL, `sourceIcon` TEXT NOT NULL, `sourceGroup` TEXT, `sourceComment` TEXT, `enabled` INTEGER NOT NULL, `variableComment` TEXT, `enabledCookieJar` INTEGER DEFAULT 0, `concurrentRate` TEXT, `header` TEXT, `loginUrl` TEXT, `loginUi` TEXT, `loginCheckJs` TEXT, `coverDecodeJs` TEXT, `sortUrl` TEXT, `singleUrl` INTEGER NOT NULL, `articleStyle` INTEGER NOT NULL DEFAULT 0, `ruleArticles` TEXT, `ruleNextPage` TEXT, `ruleTitle` TEXT, `rulePubDate` TEXT, `ruleDescription` TEXT, `ruleImage` TEXT, `ruleLink` TEXT, `ruleContent` TEXT, `contentWhitelist` TEXT, `contentBlacklist` TEXT, `style` TEXT, `enableJs` INTEGER NOT NULL DEFAULT 1, `loadWithBaseUrl` INTEGER NOT NULL DEFAULT 1, `injectJs` TEXT, `lastUpdateTime` INTEGER NOT NULL DEFAULT 0, `customOrder` INTEGER NOT NULL DEFAULT 0, PRIMARY KEY(`sourceUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"sourceUrl\",\n            \"columnName\": \"sourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceName\",\n            \"columnName\": \"sourceName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceIcon\",\n            \"columnName\": \"sourceIcon\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceGroup\",\n            \"columnName\": \"sourceGroup\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"sourceComment\",\n            \"columnName\": \"sourceComment\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enabled\",\n            \"columnName\": \"enabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"variableComment\",\n            \"columnName\": \"variableComment\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enabledCookieJar\",\n            \"columnName\": \"enabledCookieJar\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"concurrentRate\",\n            \"columnName\": \"concurrentRate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"header\",\n            \"columnName\": \"header\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUrl\",\n            \"columnName\": \"loginUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUi\",\n            \"columnName\": \"loginUi\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginCheckJs\",\n            \"columnName\": \"loginCheckJs\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"coverDecodeJs\",\n            \"columnName\": \"coverDecodeJs\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"sortUrl\",\n            \"columnName\": \"sortUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"singleUrl\",\n            \"columnName\": \"singleUrl\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"articleStyle\",\n            \"columnName\": \"articleStyle\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"ruleArticles\",\n            \"columnName\": \"ruleArticles\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleNextPage\",\n            \"columnName\": \"ruleNextPage\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleTitle\",\n            \"columnName\": \"ruleTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"rulePubDate\",\n            \"columnName\": \"rulePubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleDescription\",\n            \"columnName\": \"ruleDescription\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleImage\",\n            \"columnName\": \"ruleImage\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleLink\",\n            \"columnName\": \"ruleLink\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleContent\",\n            \"columnName\": \"ruleContent\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"contentWhitelist\",\n            \"columnName\": \"contentWhitelist\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"contentBlacklist\",\n            \"columnName\": \"contentBlacklist\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"style\",\n            \"columnName\": \"style\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enableJs\",\n            \"columnName\": \"enableJs\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"loadWithBaseUrl\",\n            \"columnName\": \"loadWithBaseUrl\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"injectJs\",\n            \"columnName\": \"injectJs\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"lastUpdateTime\",\n            \"columnName\": \"lastUpdateTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"sourceUrl\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_rssSources_sourceUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"sourceUrl\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_rssSources_sourceUrl` ON `${TABLE_NAME}` (`sourceUrl`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"bookmarks\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`time` INTEGER NOT NULL, `bookName` TEXT NOT NULL, `bookAuthor` TEXT NOT NULL, `chapterIndex` INTEGER NOT NULL, `chapterPos` INTEGER NOT NULL, `chapterName` TEXT NOT NULL, `bookText` TEXT NOT NULL, `content` TEXT NOT NULL, PRIMARY KEY(`time`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"time\",\n            \"columnName\": \"time\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookName\",\n            \"columnName\": \"bookName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookAuthor\",\n            \"columnName\": \"bookAuthor\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterIndex\",\n            \"columnName\": \"chapterIndex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterPos\",\n            \"columnName\": \"chapterPos\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterName\",\n            \"columnName\": \"chapterName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookText\",\n            \"columnName\": \"bookText\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"time\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_bookmarks_bookName_bookAuthor\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"bookName\",\n              \"bookAuthor\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_bookmarks_bookName_bookAuthor` ON `${TABLE_NAME}` (`bookName`, `bookAuthor`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssArticles\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`origin` TEXT NOT NULL, `sort` TEXT NOT NULL, `title` TEXT NOT NULL, `order` INTEGER NOT NULL, `link` TEXT NOT NULL, `pubDate` TEXT, `description` TEXT, `content` TEXT, `image` TEXT, `read` INTEGER NOT NULL, `variable` TEXT, PRIMARY KEY(`origin`, `link`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sort\",\n            \"columnName\": \"sort\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"link\",\n            \"columnName\": \"link\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"pubDate\",\n            \"columnName\": \"pubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"description\",\n            \"columnName\": \"description\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"image\",\n            \"columnName\": \"image\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"read\",\n            \"columnName\": \"read\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"origin\",\n            \"link\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssReadRecords\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`record` TEXT NOT NULL, `read` INTEGER NOT NULL, PRIMARY KEY(`record`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"record\",\n            \"columnName\": \"record\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"read\",\n            \"columnName\": \"read\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"record\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssStars\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`origin` TEXT NOT NULL, `sort` TEXT NOT NULL, `title` TEXT NOT NULL, `starTime` INTEGER NOT NULL, `link` TEXT NOT NULL, `pubDate` TEXT, `description` TEXT, `content` TEXT, `image` TEXT, `variable` TEXT, PRIMARY KEY(`origin`, `link`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sort\",\n            \"columnName\": \"sort\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"starTime\",\n            \"columnName\": \"starTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"link\",\n            \"columnName\": \"link\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"pubDate\",\n            \"columnName\": \"pubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"description\",\n            \"columnName\": \"description\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"image\",\n            \"columnName\": \"image\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"origin\",\n            \"link\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"txtTocRules\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `rule` TEXT NOT NULL, `example` TEXT, `serialNumber` INTEGER NOT NULL, `enable` INTEGER NOT NULL, PRIMARY KEY(`id`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"rule\",\n            \"columnName\": \"rule\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"example\",\n            \"columnName\": \"example\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"serialNumber\",\n            \"columnName\": \"serialNumber\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enable\",\n            \"columnName\": \"enable\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"id\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"readRecord\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`deviceId` TEXT NOT NULL, `bookName` TEXT NOT NULL, `readTime` INTEGER NOT NULL DEFAULT 0, `lastRead` INTEGER NOT NULL DEFAULT 0, PRIMARY KEY(`deviceId`, `bookName`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"deviceId\",\n            \"columnName\": \"deviceId\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookName\",\n            \"columnName\": \"bookName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"readTime\",\n            \"columnName\": \"readTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"lastRead\",\n            \"columnName\": \"lastRead\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"deviceId\",\n            \"bookName\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"httpTTS\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `url` TEXT NOT NULL, `contentType` TEXT, `concurrentRate` TEXT DEFAULT '0', `loginUrl` TEXT, `loginUi` TEXT, `header` TEXT, `enabledCookieJar` INTEGER DEFAULT 0, `loginCheckJs` TEXT, `lastUpdateTime` INTEGER NOT NULL DEFAULT 0, PRIMARY KEY(`id`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"contentType\",\n            \"columnName\": \"contentType\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"concurrentRate\",\n            \"columnName\": \"concurrentRate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false,\n            \"defaultValue\": \"'0'\"\n          },\n          {\n            \"fieldPath\": \"loginUrl\",\n            \"columnName\": \"loginUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUi\",\n            \"columnName\": \"loginUi\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"header\",\n            \"columnName\": \"header\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enabledCookieJar\",\n            \"columnName\": \"enabledCookieJar\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"loginCheckJs\",\n            \"columnName\": \"loginCheckJs\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"lastUpdateTime\",\n            \"columnName\": \"lastUpdateTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"id\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"caches\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`key` TEXT NOT NULL, `value` TEXT, `deadline` INTEGER NOT NULL, PRIMARY KEY(`key`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"key\",\n            \"columnName\": \"key\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"value\",\n            \"columnName\": \"value\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"deadline\",\n            \"columnName\": \"deadline\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"key\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_caches_key\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"key\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_caches_key` ON `${TABLE_NAME}` (`key`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"ruleSubs\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `url` TEXT NOT NULL, `type` INTEGER NOT NULL, `customOrder` INTEGER NOT NULL, `autoUpdate` INTEGER NOT NULL, `update` INTEGER NOT NULL, PRIMARY KEY(`id`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"autoUpdate\",\n            \"columnName\": \"autoUpdate\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"update\",\n            \"columnName\": \"update\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"id\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"dictRules\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`name` TEXT NOT NULL, `urlRule` TEXT NOT NULL, `showRule` TEXT NOT NULL, `enabled` INTEGER NOT NULL DEFAULT 1, `sortNumber` INTEGER NOT NULL DEFAULT 0, PRIMARY KEY(`name`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"urlRule\",\n            \"columnName\": \"urlRule\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"showRule\",\n            \"columnName\": \"showRule\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enabled\",\n            \"columnName\": \"enabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"sortNumber\",\n            \"columnName\": \"sortNumber\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"name\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"keyboardAssists\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`type` INTEGER NOT NULL DEFAULT 0, `key` TEXT NOT NULL DEFAULT '', `value` TEXT NOT NULL DEFAULT '', `serialNo` INTEGER NOT NULL DEFAULT 0, PRIMARY KEY(`type`, `key`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"key\",\n            \"columnName\": \"key\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"value\",\n            \"columnName\": \"value\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"serialNo\",\n            \"columnName\": \"serialNo\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"type\",\n            \"key\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"servers\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `type` TEXT NOT NULL, `config` TEXT, `sortNumber` INTEGER NOT NULL, PRIMARY KEY(`id`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"config\",\n            \"columnName\": \"config\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"sortNumber\",\n            \"columnName\": \"sortNumber\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"id\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      }\n    ],\n    \"views\": [],\n    \"setupQueries\": [\n      \"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)\",\n      \"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '5763792a4d08dc4cd7ebec9fb3215458')\"\n    ]\n  }\n}"
  },
  {
    "path": "app/schemas/io.legado.app.data.AppDatabase/64.json",
    "content": "{\n  \"formatVersion\": 1,\n  \"database\": {\n    \"version\": 64,\n    \"identityHash\": \"c9b9979578e5ae84c1b424b1aa590efa\",\n    \"entities\": [\n      {\n        \"tableName\": \"books\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookUrl` TEXT NOT NULL DEFAULT '', `tocUrl` TEXT NOT NULL DEFAULT '', `origin` TEXT NOT NULL DEFAULT 'loc_book', `originName` TEXT NOT NULL DEFAULT '', `name` TEXT NOT NULL DEFAULT '', `author` TEXT NOT NULL DEFAULT '', `kind` TEXT, `customTag` TEXT, `coverUrl` TEXT, `customCoverUrl` TEXT, `intro` TEXT, `customIntro` TEXT, `charset` TEXT, `type` INTEGER NOT NULL DEFAULT 0, `group` INTEGER NOT NULL DEFAULT 0, `latestChapterTitle` TEXT, `latestChapterTime` INTEGER NOT NULL DEFAULT 0, `lastCheckTime` INTEGER NOT NULL DEFAULT 0, `lastCheckCount` INTEGER NOT NULL DEFAULT 0, `totalChapterNum` INTEGER NOT NULL DEFAULT 0, `durChapterTitle` TEXT, `durChapterIndex` INTEGER NOT NULL DEFAULT 0, `durChapterPos` INTEGER NOT NULL DEFAULT 0, `durChapterTime` INTEGER NOT NULL DEFAULT 0, `wordCount` TEXT, `canUpdate` INTEGER NOT NULL DEFAULT 1, `order` INTEGER NOT NULL DEFAULT 0, `originOrder` INTEGER NOT NULL DEFAULT 0, `variable` TEXT, `readConfig` TEXT, PRIMARY KEY(`bookUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"tocUrl\",\n            \"columnName\": \"tocUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"'loc_book'\"\n          },\n          {\n            \"fieldPath\": \"originName\",\n            \"columnName\": \"originName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"author\",\n            \"columnName\": \"author\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"kind\",\n            \"columnName\": \"kind\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customTag\",\n            \"columnName\": \"customTag\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"coverUrl\",\n            \"columnName\": \"coverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customCoverUrl\",\n            \"columnName\": \"customCoverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"intro\",\n            \"columnName\": \"intro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customIntro\",\n            \"columnName\": \"customIntro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"charset\",\n            \"columnName\": \"charset\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"group\",\n            \"columnName\": \"group\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"latestChapterTitle\",\n            \"columnName\": \"latestChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"latestChapterTime\",\n            \"columnName\": \"latestChapterTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"lastCheckTime\",\n            \"columnName\": \"lastCheckTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"lastCheckCount\",\n            \"columnName\": \"lastCheckCount\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"totalChapterNum\",\n            \"columnName\": \"totalChapterNum\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"durChapterTitle\",\n            \"columnName\": \"durChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"durChapterIndex\",\n            \"columnName\": \"durChapterIndex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"durChapterPos\",\n            \"columnName\": \"durChapterPos\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"durChapterTime\",\n            \"columnName\": \"durChapterTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"wordCount\",\n            \"columnName\": \"wordCount\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"canUpdate\",\n            \"columnName\": \"canUpdate\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"originOrder\",\n            \"columnName\": \"originOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"readConfig\",\n            \"columnName\": \"readConfig\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"bookUrl\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_books_name_author\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"name\",\n              \"author\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_books_name_author` ON `${TABLE_NAME}` (`name`, `author`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"book_groups\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`groupId` INTEGER NOT NULL, `groupName` TEXT NOT NULL, `cover` TEXT, `order` INTEGER NOT NULL, `enableRefresh` INTEGER NOT NULL DEFAULT 1, `show` INTEGER NOT NULL DEFAULT 1, `bookSort` INTEGER NOT NULL DEFAULT -1, PRIMARY KEY(`groupId`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"groupId\",\n            \"columnName\": \"groupId\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"groupName\",\n            \"columnName\": \"groupName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"cover\",\n            \"columnName\": \"cover\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enableRefresh\",\n            \"columnName\": \"enableRefresh\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"show\",\n            \"columnName\": \"show\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"bookSort\",\n            \"columnName\": \"bookSort\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"-1\"\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"groupId\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"book_sources\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookSourceUrl` TEXT NOT NULL, `bookSourceName` TEXT NOT NULL, `bookSourceGroup` TEXT, `bookSourceType` INTEGER NOT NULL, `bookUrlPattern` TEXT, `customOrder` INTEGER NOT NULL DEFAULT 0, `enabled` INTEGER NOT NULL DEFAULT 1, `enabledExplore` INTEGER NOT NULL DEFAULT 1, `enabledReview` INTEGER, `enabledCookieJar` INTEGER DEFAULT 0, `concurrentRate` TEXT, `header` TEXT, `loginUrl` TEXT, `loginUi` TEXT, `loginCheckJs` TEXT, `coverDecodeJs` TEXT, `bookSourceComment` TEXT, `variableComment` TEXT, `lastUpdateTime` INTEGER NOT NULL, `respondTime` INTEGER NOT NULL, `weight` INTEGER NOT NULL, `exploreUrl` TEXT, `exploreScreen` TEXT, `ruleExplore` TEXT, `searchUrl` TEXT, `ruleSearch` TEXT, `ruleBookInfo` TEXT, `ruleToc` TEXT, `ruleContent` TEXT, `ruleReview` TEXT, PRIMARY KEY(`bookSourceUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookSourceUrl\",\n            \"columnName\": \"bookSourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookSourceName\",\n            \"columnName\": \"bookSourceName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookSourceGroup\",\n            \"columnName\": \"bookSourceGroup\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"bookSourceType\",\n            \"columnName\": \"bookSourceType\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookUrlPattern\",\n            \"columnName\": \"bookUrlPattern\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"enabled\",\n            \"columnName\": \"enabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"enabledExplore\",\n            \"columnName\": \"enabledExplore\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"enabledReview\",\n            \"columnName\": \"enabledReview\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enabledCookieJar\",\n            \"columnName\": \"enabledCookieJar\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"concurrentRate\",\n            \"columnName\": \"concurrentRate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"header\",\n            \"columnName\": \"header\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUrl\",\n            \"columnName\": \"loginUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUi\",\n            \"columnName\": \"loginUi\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginCheckJs\",\n            \"columnName\": \"loginCheckJs\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"coverDecodeJs\",\n            \"columnName\": \"coverDecodeJs\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"bookSourceComment\",\n            \"columnName\": \"bookSourceComment\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"variableComment\",\n            \"columnName\": \"variableComment\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"lastUpdateTime\",\n            \"columnName\": \"lastUpdateTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"respondTime\",\n            \"columnName\": \"respondTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"weight\",\n            \"columnName\": \"weight\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"exploreUrl\",\n            \"columnName\": \"exploreUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"exploreScreen\",\n            \"columnName\": \"exploreScreen\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleExplore\",\n            \"columnName\": \"ruleExplore\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"searchUrl\",\n            \"columnName\": \"searchUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleSearch\",\n            \"columnName\": \"ruleSearch\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleBookInfo\",\n            \"columnName\": \"ruleBookInfo\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleToc\",\n            \"columnName\": \"ruleToc\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleContent\",\n            \"columnName\": \"ruleContent\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleReview\",\n            \"columnName\": \"ruleReview\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"bookSourceUrl\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_book_sources_bookSourceUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"bookSourceUrl\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_book_sources_bookSourceUrl` ON `${TABLE_NAME}` (`bookSourceUrl`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"chapters\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`url` TEXT NOT NULL, `title` TEXT NOT NULL, `isVolume` INTEGER NOT NULL, `baseUrl` TEXT NOT NULL, `bookUrl` TEXT NOT NULL, `index` INTEGER NOT NULL, `isVip` INTEGER NOT NULL, `isPay` INTEGER NOT NULL, `resourceUrl` TEXT, `tag` TEXT, `start` INTEGER, `end` INTEGER, `startFragmentId` TEXT, `endFragmentId` TEXT, `variable` TEXT, PRIMARY KEY(`url`, `bookUrl`), FOREIGN KEY(`bookUrl`) REFERENCES `books`(`bookUrl`) ON UPDATE NO ACTION ON DELETE CASCADE )\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"isVolume\",\n            \"columnName\": \"isVolume\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"baseUrl\",\n            \"columnName\": \"baseUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"index\",\n            \"columnName\": \"index\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"isVip\",\n            \"columnName\": \"isVip\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"isPay\",\n            \"columnName\": \"isPay\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"resourceUrl\",\n            \"columnName\": \"resourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"tag\",\n            \"columnName\": \"tag\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"start\",\n            \"columnName\": \"start\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"end\",\n            \"columnName\": \"end\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"startFragmentId\",\n            \"columnName\": \"startFragmentId\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"endFragmentId\",\n            \"columnName\": \"endFragmentId\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"url\",\n            \"bookUrl\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_chapters_bookUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"bookUrl\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_chapters_bookUrl` ON `${TABLE_NAME}` (`bookUrl`)\"\n          },\n          {\n            \"name\": \"index_chapters_bookUrl_index\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"bookUrl\",\n              \"index\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_chapters_bookUrl_index` ON `${TABLE_NAME}` (`bookUrl`, `index`)\"\n          }\n        ],\n        \"foreignKeys\": [\n          {\n            \"table\": \"books\",\n            \"onDelete\": \"CASCADE\",\n            \"onUpdate\": \"NO ACTION\",\n            \"columns\": [\n              \"bookUrl\"\n            ],\n            \"referencedColumns\": [\n              \"bookUrl\"\n            ]\n          }\n        ]\n      },\n      {\n        \"tableName\": \"replace_rules\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL DEFAULT '', `group` TEXT, `pattern` TEXT NOT NULL DEFAULT '', `replacement` TEXT NOT NULL DEFAULT '', `scope` TEXT, `scopeTitle` INTEGER NOT NULL DEFAULT 0, `scopeContent` INTEGER NOT NULL DEFAULT 1, `isEnabled` INTEGER NOT NULL DEFAULT 1, `isRegex` INTEGER NOT NULL DEFAULT 1, `timeoutMillisecond` INTEGER NOT NULL DEFAULT 3000, `sortOrder` INTEGER NOT NULL DEFAULT 0)\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"group\",\n            \"columnName\": \"group\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"pattern\",\n            \"columnName\": \"pattern\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"replacement\",\n            \"columnName\": \"replacement\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"scope\",\n            \"columnName\": \"scope\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"scopeTitle\",\n            \"columnName\": \"scopeTitle\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"scopeContent\",\n            \"columnName\": \"scopeContent\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"isEnabled\",\n            \"columnName\": \"isEnabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"isRegex\",\n            \"columnName\": \"isRegex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"timeoutMillisecond\",\n            \"columnName\": \"timeoutMillisecond\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"3000\"\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"sortOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": true,\n          \"columnNames\": [\n            \"id\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_replace_rules_id\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"id\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_replace_rules_id` ON `${TABLE_NAME}` (`id`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"searchBooks\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookUrl` TEXT NOT NULL, `origin` TEXT NOT NULL, `originName` TEXT NOT NULL, `type` INTEGER NOT NULL, `name` TEXT NOT NULL, `author` TEXT NOT NULL, `kind` TEXT, `coverUrl` TEXT, `intro` TEXT, `wordCount` TEXT, `latestChapterTitle` TEXT, `tocUrl` TEXT NOT NULL, `time` INTEGER NOT NULL, `variable` TEXT, `originOrder` INTEGER NOT NULL, PRIMARY KEY(`bookUrl`), FOREIGN KEY(`origin`) REFERENCES `book_sources`(`bookSourceUrl`) ON UPDATE NO ACTION ON DELETE CASCADE )\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"originName\",\n            \"columnName\": \"originName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"author\",\n            \"columnName\": \"author\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"kind\",\n            \"columnName\": \"kind\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"coverUrl\",\n            \"columnName\": \"coverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"intro\",\n            \"columnName\": \"intro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"wordCount\",\n            \"columnName\": \"wordCount\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"latestChapterTitle\",\n            \"columnName\": \"latestChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"tocUrl\",\n            \"columnName\": \"tocUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"time\",\n            \"columnName\": \"time\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"originOrder\",\n            \"columnName\": \"originOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"bookUrl\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_searchBooks_bookUrl\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"bookUrl\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_searchBooks_bookUrl` ON `${TABLE_NAME}` (`bookUrl`)\"\n          },\n          {\n            \"name\": \"index_searchBooks_origin\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"origin\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_searchBooks_origin` ON `${TABLE_NAME}` (`origin`)\"\n          }\n        ],\n        \"foreignKeys\": [\n          {\n            \"table\": \"book_sources\",\n            \"onDelete\": \"CASCADE\",\n            \"onUpdate\": \"NO ACTION\",\n            \"columns\": [\n              \"origin\"\n            ],\n            \"referencedColumns\": [\n              \"bookSourceUrl\"\n            ]\n          }\n        ]\n      },\n      {\n        \"tableName\": \"search_keywords\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`word` TEXT NOT NULL, `usage` INTEGER NOT NULL, `lastUseTime` INTEGER NOT NULL, PRIMARY KEY(`word`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"word\",\n            \"columnName\": \"word\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"usage\",\n            \"columnName\": \"usage\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"lastUseTime\",\n            \"columnName\": \"lastUseTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"word\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_search_keywords_word\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"word\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_search_keywords_word` ON `${TABLE_NAME}` (`word`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"cookies\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`url` TEXT NOT NULL, `cookie` TEXT NOT NULL, PRIMARY KEY(`url`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"cookie\",\n            \"columnName\": \"cookie\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"url\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_cookies_url\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"url\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_cookies_url` ON `${TABLE_NAME}` (`url`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssSources\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`sourceUrl` TEXT NOT NULL, `sourceName` TEXT NOT NULL, `sourceIcon` TEXT NOT NULL, `sourceGroup` TEXT, `sourceComment` TEXT, `enabled` INTEGER NOT NULL, `variableComment` TEXT, `enabledCookieJar` INTEGER DEFAULT 0, `concurrentRate` TEXT, `header` TEXT, `loginUrl` TEXT, `loginUi` TEXT, `loginCheckJs` TEXT, `coverDecodeJs` TEXT, `sortUrl` TEXT, `singleUrl` INTEGER NOT NULL, `articleStyle` INTEGER NOT NULL DEFAULT 0, `ruleArticles` TEXT, `ruleNextPage` TEXT, `ruleTitle` TEXT, `rulePubDate` TEXT, `ruleDescription` TEXT, `ruleImage` TEXT, `ruleLink` TEXT, `ruleContent` TEXT, `contentWhitelist` TEXT, `contentBlacklist` TEXT, `style` TEXT, `enableJs` INTEGER NOT NULL DEFAULT 1, `loadWithBaseUrl` INTEGER NOT NULL DEFAULT 1, `injectJs` TEXT, `lastUpdateTime` INTEGER NOT NULL DEFAULT 0, `customOrder` INTEGER NOT NULL DEFAULT 0, PRIMARY KEY(`sourceUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"sourceUrl\",\n            \"columnName\": \"sourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceName\",\n            \"columnName\": \"sourceName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceIcon\",\n            \"columnName\": \"sourceIcon\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceGroup\",\n            \"columnName\": \"sourceGroup\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"sourceComment\",\n            \"columnName\": \"sourceComment\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enabled\",\n            \"columnName\": \"enabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"variableComment\",\n            \"columnName\": \"variableComment\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enabledCookieJar\",\n            \"columnName\": \"enabledCookieJar\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"concurrentRate\",\n            \"columnName\": \"concurrentRate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"header\",\n            \"columnName\": \"header\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUrl\",\n            \"columnName\": \"loginUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUi\",\n            \"columnName\": \"loginUi\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginCheckJs\",\n            \"columnName\": \"loginCheckJs\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"coverDecodeJs\",\n            \"columnName\": \"coverDecodeJs\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"sortUrl\",\n            \"columnName\": \"sortUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"singleUrl\",\n            \"columnName\": \"singleUrl\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"articleStyle\",\n            \"columnName\": \"articleStyle\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"ruleArticles\",\n            \"columnName\": \"ruleArticles\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleNextPage\",\n            \"columnName\": \"ruleNextPage\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleTitle\",\n            \"columnName\": \"ruleTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"rulePubDate\",\n            \"columnName\": \"rulePubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleDescription\",\n            \"columnName\": \"ruleDescription\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleImage\",\n            \"columnName\": \"ruleImage\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleLink\",\n            \"columnName\": \"ruleLink\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleContent\",\n            \"columnName\": \"ruleContent\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"contentWhitelist\",\n            \"columnName\": \"contentWhitelist\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"contentBlacklist\",\n            \"columnName\": \"contentBlacklist\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"style\",\n            \"columnName\": \"style\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enableJs\",\n            \"columnName\": \"enableJs\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"loadWithBaseUrl\",\n            \"columnName\": \"loadWithBaseUrl\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"injectJs\",\n            \"columnName\": \"injectJs\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"lastUpdateTime\",\n            \"columnName\": \"lastUpdateTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"sourceUrl\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_rssSources_sourceUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"sourceUrl\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_rssSources_sourceUrl` ON `${TABLE_NAME}` (`sourceUrl`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"bookmarks\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`time` INTEGER NOT NULL, `bookName` TEXT NOT NULL, `bookAuthor` TEXT NOT NULL, `chapterIndex` INTEGER NOT NULL, `chapterPos` INTEGER NOT NULL, `chapterName` TEXT NOT NULL, `bookText` TEXT NOT NULL, `content` TEXT NOT NULL, PRIMARY KEY(`time`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"time\",\n            \"columnName\": \"time\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookName\",\n            \"columnName\": \"bookName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookAuthor\",\n            \"columnName\": \"bookAuthor\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterIndex\",\n            \"columnName\": \"chapterIndex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterPos\",\n            \"columnName\": \"chapterPos\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterName\",\n            \"columnName\": \"chapterName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookText\",\n            \"columnName\": \"bookText\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"time\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_bookmarks_bookName_bookAuthor\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"bookName\",\n              \"bookAuthor\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_bookmarks_bookName_bookAuthor` ON `${TABLE_NAME}` (`bookName`, `bookAuthor`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssArticles\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`origin` TEXT NOT NULL, `sort` TEXT NOT NULL, `title` TEXT NOT NULL, `order` INTEGER NOT NULL, `link` TEXT NOT NULL, `pubDate` TEXT, `description` TEXT, `content` TEXT, `image` TEXT, `read` INTEGER NOT NULL, `variable` TEXT, PRIMARY KEY(`origin`, `link`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sort\",\n            \"columnName\": \"sort\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"link\",\n            \"columnName\": \"link\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"pubDate\",\n            \"columnName\": \"pubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"description\",\n            \"columnName\": \"description\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"image\",\n            \"columnName\": \"image\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"read\",\n            \"columnName\": \"read\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"origin\",\n            \"link\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssReadRecords\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`record` TEXT NOT NULL, `read` INTEGER NOT NULL, PRIMARY KEY(`record`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"record\",\n            \"columnName\": \"record\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"read\",\n            \"columnName\": \"read\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"record\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssStars\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`origin` TEXT NOT NULL, `sort` TEXT NOT NULL, `title` TEXT NOT NULL, `starTime` INTEGER NOT NULL, `link` TEXT NOT NULL, `pubDate` TEXT, `description` TEXT, `content` TEXT, `image` TEXT, `variable` TEXT, PRIMARY KEY(`origin`, `link`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sort\",\n            \"columnName\": \"sort\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"starTime\",\n            \"columnName\": \"starTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"link\",\n            \"columnName\": \"link\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"pubDate\",\n            \"columnName\": \"pubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"description\",\n            \"columnName\": \"description\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"image\",\n            \"columnName\": \"image\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"origin\",\n            \"link\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"txtTocRules\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `rule` TEXT NOT NULL, `example` TEXT, `serialNumber` INTEGER NOT NULL, `enable` INTEGER NOT NULL, PRIMARY KEY(`id`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"rule\",\n            \"columnName\": \"rule\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"example\",\n            \"columnName\": \"example\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"serialNumber\",\n            \"columnName\": \"serialNumber\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enable\",\n            \"columnName\": \"enable\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"id\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"readRecord\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`deviceId` TEXT NOT NULL, `bookName` TEXT NOT NULL, `readTime` INTEGER NOT NULL DEFAULT 0, `lastRead` INTEGER NOT NULL DEFAULT 0, PRIMARY KEY(`deviceId`, `bookName`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"deviceId\",\n            \"columnName\": \"deviceId\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookName\",\n            \"columnName\": \"bookName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"readTime\",\n            \"columnName\": \"readTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"lastRead\",\n            \"columnName\": \"lastRead\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"deviceId\",\n            \"bookName\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"httpTTS\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `url` TEXT NOT NULL, `contentType` TEXT, `concurrentRate` TEXT DEFAULT '0', `loginUrl` TEXT, `loginUi` TEXT, `header` TEXT, `enabledCookieJar` INTEGER DEFAULT 0, `loginCheckJs` TEXT, `lastUpdateTime` INTEGER NOT NULL DEFAULT 0, PRIMARY KEY(`id`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"contentType\",\n            \"columnName\": \"contentType\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"concurrentRate\",\n            \"columnName\": \"concurrentRate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false,\n            \"defaultValue\": \"'0'\"\n          },\n          {\n            \"fieldPath\": \"loginUrl\",\n            \"columnName\": \"loginUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUi\",\n            \"columnName\": \"loginUi\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"header\",\n            \"columnName\": \"header\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enabledCookieJar\",\n            \"columnName\": \"enabledCookieJar\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"loginCheckJs\",\n            \"columnName\": \"loginCheckJs\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"lastUpdateTime\",\n            \"columnName\": \"lastUpdateTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"id\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"caches\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`key` TEXT NOT NULL, `value` TEXT, `deadline` INTEGER NOT NULL, PRIMARY KEY(`key`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"key\",\n            \"columnName\": \"key\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"value\",\n            \"columnName\": \"value\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"deadline\",\n            \"columnName\": \"deadline\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"key\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_caches_key\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"key\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_caches_key` ON `${TABLE_NAME}` (`key`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"ruleSubs\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `url` TEXT NOT NULL, `type` INTEGER NOT NULL, `customOrder` INTEGER NOT NULL, `autoUpdate` INTEGER NOT NULL, `update` INTEGER NOT NULL, PRIMARY KEY(`id`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"autoUpdate\",\n            \"columnName\": \"autoUpdate\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"update\",\n            \"columnName\": \"update\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"id\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"dictRules\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`name` TEXT NOT NULL, `urlRule` TEXT NOT NULL, `showRule` TEXT NOT NULL, `enabled` INTEGER NOT NULL DEFAULT 1, `sortNumber` INTEGER NOT NULL DEFAULT 0, PRIMARY KEY(`name`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"urlRule\",\n            \"columnName\": \"urlRule\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"showRule\",\n            \"columnName\": \"showRule\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enabled\",\n            \"columnName\": \"enabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"sortNumber\",\n            \"columnName\": \"sortNumber\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"name\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"keyboardAssists\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`type` INTEGER NOT NULL DEFAULT 0, `key` TEXT NOT NULL DEFAULT '', `value` TEXT NOT NULL DEFAULT '', `serialNo` INTEGER NOT NULL DEFAULT 0, PRIMARY KEY(`type`, `key`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"key\",\n            \"columnName\": \"key\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"value\",\n            \"columnName\": \"value\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"serialNo\",\n            \"columnName\": \"serialNo\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"type\",\n            \"key\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"servers\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `type` TEXT NOT NULL, `config` TEXT, `sortNumber` INTEGER NOT NULL, PRIMARY KEY(`id`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"config\",\n            \"columnName\": \"config\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"sortNumber\",\n            \"columnName\": \"sortNumber\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"id\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      }\n    ],\n    \"views\": [],\n    \"setupQueries\": [\n      \"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)\",\n      \"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'c9b9979578e5ae84c1b424b1aa590efa')\"\n    ]\n  }\n}"
  },
  {
    "path": "app/schemas/io.legado.app.data.AppDatabase/65.json",
    "content": "{\n  \"formatVersion\": 1,\n  \"database\": {\n    \"version\": 65,\n    \"identityHash\": \"ccfe4ad59c8caef014b1c754b69e6d05\",\n    \"entities\": [\n      {\n        \"tableName\": \"books\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookUrl` TEXT NOT NULL DEFAULT '', `tocUrl` TEXT NOT NULL DEFAULT '', `origin` TEXT NOT NULL DEFAULT 'loc_book', `originName` TEXT NOT NULL DEFAULT '', `name` TEXT NOT NULL DEFAULT '', `author` TEXT NOT NULL DEFAULT '', `kind` TEXT, `customTag` TEXT, `coverUrl` TEXT, `customCoverUrl` TEXT, `intro` TEXT, `customIntro` TEXT, `charset` TEXT, `type` INTEGER NOT NULL DEFAULT 0, `group` INTEGER NOT NULL DEFAULT 0, `latestChapterTitle` TEXT, `latestChapterTime` INTEGER NOT NULL DEFAULT 0, `lastCheckTime` INTEGER NOT NULL DEFAULT 0, `lastCheckCount` INTEGER NOT NULL DEFAULT 0, `totalChapterNum` INTEGER NOT NULL DEFAULT 0, `durChapterTitle` TEXT, `durChapterIndex` INTEGER NOT NULL DEFAULT 0, `durChapterPos` INTEGER NOT NULL DEFAULT 0, `durChapterTime` INTEGER NOT NULL DEFAULT 0, `wordCount` TEXT, `canUpdate` INTEGER NOT NULL DEFAULT 1, `order` INTEGER NOT NULL DEFAULT 0, `originOrder` INTEGER NOT NULL DEFAULT 0, `variable` TEXT, `readConfig` TEXT, PRIMARY KEY(`bookUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"tocUrl\",\n            \"columnName\": \"tocUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"'loc_book'\"\n          },\n          {\n            \"fieldPath\": \"originName\",\n            \"columnName\": \"originName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"author\",\n            \"columnName\": \"author\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"kind\",\n            \"columnName\": \"kind\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customTag\",\n            \"columnName\": \"customTag\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"coverUrl\",\n            \"columnName\": \"coverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customCoverUrl\",\n            \"columnName\": \"customCoverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"intro\",\n            \"columnName\": \"intro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customIntro\",\n            \"columnName\": \"customIntro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"charset\",\n            \"columnName\": \"charset\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"group\",\n            \"columnName\": \"group\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"latestChapterTitle\",\n            \"columnName\": \"latestChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"latestChapterTime\",\n            \"columnName\": \"latestChapterTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"lastCheckTime\",\n            \"columnName\": \"lastCheckTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"lastCheckCount\",\n            \"columnName\": \"lastCheckCount\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"totalChapterNum\",\n            \"columnName\": \"totalChapterNum\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"durChapterTitle\",\n            \"columnName\": \"durChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"durChapterIndex\",\n            \"columnName\": \"durChapterIndex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"durChapterPos\",\n            \"columnName\": \"durChapterPos\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"durChapterTime\",\n            \"columnName\": \"durChapterTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"wordCount\",\n            \"columnName\": \"wordCount\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"canUpdate\",\n            \"columnName\": \"canUpdate\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"originOrder\",\n            \"columnName\": \"originOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"readConfig\",\n            \"columnName\": \"readConfig\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"bookUrl\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_books_name_author\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"name\",\n              \"author\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_books_name_author` ON `${TABLE_NAME}` (`name`, `author`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"book_groups\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`groupId` INTEGER NOT NULL, `groupName` TEXT NOT NULL, `cover` TEXT, `order` INTEGER NOT NULL, `enableRefresh` INTEGER NOT NULL DEFAULT 1, `show` INTEGER NOT NULL DEFAULT 1, `bookSort` INTEGER NOT NULL DEFAULT -1, PRIMARY KEY(`groupId`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"groupId\",\n            \"columnName\": \"groupId\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"groupName\",\n            \"columnName\": \"groupName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"cover\",\n            \"columnName\": \"cover\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enableRefresh\",\n            \"columnName\": \"enableRefresh\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"show\",\n            \"columnName\": \"show\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"bookSort\",\n            \"columnName\": \"bookSort\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"-1\"\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"groupId\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"book_sources\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookSourceUrl` TEXT NOT NULL, `bookSourceName` TEXT NOT NULL, `bookSourceGroup` TEXT, `bookSourceType` INTEGER NOT NULL, `bookUrlPattern` TEXT, `customOrder` INTEGER NOT NULL DEFAULT 0, `enabled` INTEGER NOT NULL DEFAULT 1, `enabledExplore` INTEGER NOT NULL DEFAULT 1, `jsLib` TEXT, `enabledCookieJar` INTEGER DEFAULT 0, `concurrentRate` TEXT, `header` TEXT, `loginUrl` TEXT, `loginUi` TEXT, `loginCheckJs` TEXT, `coverDecodeJs` TEXT, `bookSourceComment` TEXT, `variableComment` TEXT, `lastUpdateTime` INTEGER NOT NULL, `respondTime` INTEGER NOT NULL, `weight` INTEGER NOT NULL, `exploreUrl` TEXT, `exploreScreen` TEXT, `ruleExplore` TEXT, `searchUrl` TEXT, `ruleSearch` TEXT, `ruleBookInfo` TEXT, `ruleToc` TEXT, `ruleContent` TEXT, `ruleReview` TEXT, PRIMARY KEY(`bookSourceUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookSourceUrl\",\n            \"columnName\": \"bookSourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookSourceName\",\n            \"columnName\": \"bookSourceName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookSourceGroup\",\n            \"columnName\": \"bookSourceGroup\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"bookSourceType\",\n            \"columnName\": \"bookSourceType\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookUrlPattern\",\n            \"columnName\": \"bookUrlPattern\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"enabled\",\n            \"columnName\": \"enabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"enabledExplore\",\n            \"columnName\": \"enabledExplore\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"jsLib\",\n            \"columnName\": \"jsLib\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enabledCookieJar\",\n            \"columnName\": \"enabledCookieJar\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"concurrentRate\",\n            \"columnName\": \"concurrentRate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"header\",\n            \"columnName\": \"header\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUrl\",\n            \"columnName\": \"loginUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUi\",\n            \"columnName\": \"loginUi\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginCheckJs\",\n            \"columnName\": \"loginCheckJs\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"coverDecodeJs\",\n            \"columnName\": \"coverDecodeJs\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"bookSourceComment\",\n            \"columnName\": \"bookSourceComment\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"variableComment\",\n            \"columnName\": \"variableComment\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"lastUpdateTime\",\n            \"columnName\": \"lastUpdateTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"respondTime\",\n            \"columnName\": \"respondTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"weight\",\n            \"columnName\": \"weight\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"exploreUrl\",\n            \"columnName\": \"exploreUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"exploreScreen\",\n            \"columnName\": \"exploreScreen\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleExplore\",\n            \"columnName\": \"ruleExplore\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"searchUrl\",\n            \"columnName\": \"searchUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleSearch\",\n            \"columnName\": \"ruleSearch\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleBookInfo\",\n            \"columnName\": \"ruleBookInfo\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleToc\",\n            \"columnName\": \"ruleToc\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleContent\",\n            \"columnName\": \"ruleContent\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleReview\",\n            \"columnName\": \"ruleReview\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"bookSourceUrl\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_book_sources_bookSourceUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"bookSourceUrl\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_book_sources_bookSourceUrl` ON `${TABLE_NAME}` (`bookSourceUrl`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"chapters\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`url` TEXT NOT NULL, `title` TEXT NOT NULL, `isVolume` INTEGER NOT NULL, `baseUrl` TEXT NOT NULL, `bookUrl` TEXT NOT NULL, `index` INTEGER NOT NULL, `isVip` INTEGER NOT NULL, `isPay` INTEGER NOT NULL, `resourceUrl` TEXT, `tag` TEXT, `start` INTEGER, `end` INTEGER, `startFragmentId` TEXT, `endFragmentId` TEXT, `variable` TEXT, PRIMARY KEY(`url`, `bookUrl`), FOREIGN KEY(`bookUrl`) REFERENCES `books`(`bookUrl`) ON UPDATE NO ACTION ON DELETE CASCADE )\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"isVolume\",\n            \"columnName\": \"isVolume\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"baseUrl\",\n            \"columnName\": \"baseUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"index\",\n            \"columnName\": \"index\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"isVip\",\n            \"columnName\": \"isVip\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"isPay\",\n            \"columnName\": \"isPay\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"resourceUrl\",\n            \"columnName\": \"resourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"tag\",\n            \"columnName\": \"tag\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"start\",\n            \"columnName\": \"start\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"end\",\n            \"columnName\": \"end\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"startFragmentId\",\n            \"columnName\": \"startFragmentId\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"endFragmentId\",\n            \"columnName\": \"endFragmentId\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"url\",\n            \"bookUrl\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_chapters_bookUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"bookUrl\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_chapters_bookUrl` ON `${TABLE_NAME}` (`bookUrl`)\"\n          },\n          {\n            \"name\": \"index_chapters_bookUrl_index\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"bookUrl\",\n              \"index\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_chapters_bookUrl_index` ON `${TABLE_NAME}` (`bookUrl`, `index`)\"\n          }\n        ],\n        \"foreignKeys\": [\n          {\n            \"table\": \"books\",\n            \"onDelete\": \"CASCADE\",\n            \"onUpdate\": \"NO ACTION\",\n            \"columns\": [\n              \"bookUrl\"\n            ],\n            \"referencedColumns\": [\n              \"bookUrl\"\n            ]\n          }\n        ]\n      },\n      {\n        \"tableName\": \"replace_rules\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL DEFAULT '', `group` TEXT, `pattern` TEXT NOT NULL DEFAULT '', `replacement` TEXT NOT NULL DEFAULT '', `scope` TEXT, `scopeTitle` INTEGER NOT NULL DEFAULT 0, `scopeContent` INTEGER NOT NULL DEFAULT 1, `isEnabled` INTEGER NOT NULL DEFAULT 1, `isRegex` INTEGER NOT NULL DEFAULT 1, `timeoutMillisecond` INTEGER NOT NULL DEFAULT 3000, `sortOrder` INTEGER NOT NULL DEFAULT 0)\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"group\",\n            \"columnName\": \"group\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"pattern\",\n            \"columnName\": \"pattern\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"replacement\",\n            \"columnName\": \"replacement\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"scope\",\n            \"columnName\": \"scope\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"scopeTitle\",\n            \"columnName\": \"scopeTitle\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"scopeContent\",\n            \"columnName\": \"scopeContent\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"isEnabled\",\n            \"columnName\": \"isEnabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"isRegex\",\n            \"columnName\": \"isRegex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"timeoutMillisecond\",\n            \"columnName\": \"timeoutMillisecond\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"3000\"\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"sortOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": true,\n          \"columnNames\": [\n            \"id\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_replace_rules_id\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"id\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_replace_rules_id` ON `${TABLE_NAME}` (`id`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"searchBooks\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookUrl` TEXT NOT NULL, `origin` TEXT NOT NULL, `originName` TEXT NOT NULL, `type` INTEGER NOT NULL, `name` TEXT NOT NULL, `author` TEXT NOT NULL, `kind` TEXT, `coverUrl` TEXT, `intro` TEXT, `wordCount` TEXT, `latestChapterTitle` TEXT, `tocUrl` TEXT NOT NULL, `time` INTEGER NOT NULL, `variable` TEXT, `originOrder` INTEGER NOT NULL, PRIMARY KEY(`bookUrl`), FOREIGN KEY(`origin`) REFERENCES `book_sources`(`bookSourceUrl`) ON UPDATE NO ACTION ON DELETE CASCADE )\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"originName\",\n            \"columnName\": \"originName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"author\",\n            \"columnName\": \"author\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"kind\",\n            \"columnName\": \"kind\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"coverUrl\",\n            \"columnName\": \"coverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"intro\",\n            \"columnName\": \"intro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"wordCount\",\n            \"columnName\": \"wordCount\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"latestChapterTitle\",\n            \"columnName\": \"latestChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"tocUrl\",\n            \"columnName\": \"tocUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"time\",\n            \"columnName\": \"time\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"originOrder\",\n            \"columnName\": \"originOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"bookUrl\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_searchBooks_bookUrl\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"bookUrl\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_searchBooks_bookUrl` ON `${TABLE_NAME}` (`bookUrl`)\"\n          },\n          {\n            \"name\": \"index_searchBooks_origin\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"origin\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_searchBooks_origin` ON `${TABLE_NAME}` (`origin`)\"\n          }\n        ],\n        \"foreignKeys\": [\n          {\n            \"table\": \"book_sources\",\n            \"onDelete\": \"CASCADE\",\n            \"onUpdate\": \"NO ACTION\",\n            \"columns\": [\n              \"origin\"\n            ],\n            \"referencedColumns\": [\n              \"bookSourceUrl\"\n            ]\n          }\n        ]\n      },\n      {\n        \"tableName\": \"search_keywords\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`word` TEXT NOT NULL, `usage` INTEGER NOT NULL, `lastUseTime` INTEGER NOT NULL, PRIMARY KEY(`word`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"word\",\n            \"columnName\": \"word\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"usage\",\n            \"columnName\": \"usage\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"lastUseTime\",\n            \"columnName\": \"lastUseTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"word\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_search_keywords_word\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"word\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_search_keywords_word` ON `${TABLE_NAME}` (`word`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"cookies\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`url` TEXT NOT NULL, `cookie` TEXT NOT NULL, PRIMARY KEY(`url`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"cookie\",\n            \"columnName\": \"cookie\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"url\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_cookies_url\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"url\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_cookies_url` ON `${TABLE_NAME}` (`url`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssSources\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`sourceUrl` TEXT NOT NULL, `sourceName` TEXT NOT NULL, `sourceIcon` TEXT NOT NULL, `sourceGroup` TEXT, `sourceComment` TEXT, `enabled` INTEGER NOT NULL, `variableComment` TEXT, `jsLib` TEXT, `enabledCookieJar` INTEGER DEFAULT 0, `concurrentRate` TEXT, `header` TEXT, `loginUrl` TEXT, `loginUi` TEXT, `loginCheckJs` TEXT, `coverDecodeJs` TEXT, `sortUrl` TEXT, `singleUrl` INTEGER NOT NULL, `articleStyle` INTEGER NOT NULL DEFAULT 0, `ruleArticles` TEXT, `ruleNextPage` TEXT, `ruleTitle` TEXT, `rulePubDate` TEXT, `ruleDescription` TEXT, `ruleImage` TEXT, `ruleLink` TEXT, `ruleContent` TEXT, `contentWhitelist` TEXT, `contentBlacklist` TEXT, `style` TEXT, `enableJs` INTEGER NOT NULL DEFAULT 1, `loadWithBaseUrl` INTEGER NOT NULL DEFAULT 1, `injectJs` TEXT, `lastUpdateTime` INTEGER NOT NULL DEFAULT 0, `customOrder` INTEGER NOT NULL DEFAULT 0, PRIMARY KEY(`sourceUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"sourceUrl\",\n            \"columnName\": \"sourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceName\",\n            \"columnName\": \"sourceName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceIcon\",\n            \"columnName\": \"sourceIcon\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceGroup\",\n            \"columnName\": \"sourceGroup\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"sourceComment\",\n            \"columnName\": \"sourceComment\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enabled\",\n            \"columnName\": \"enabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"variableComment\",\n            \"columnName\": \"variableComment\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"jsLib\",\n            \"columnName\": \"jsLib\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enabledCookieJar\",\n            \"columnName\": \"enabledCookieJar\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"concurrentRate\",\n            \"columnName\": \"concurrentRate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"header\",\n            \"columnName\": \"header\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUrl\",\n            \"columnName\": \"loginUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUi\",\n            \"columnName\": \"loginUi\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginCheckJs\",\n            \"columnName\": \"loginCheckJs\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"coverDecodeJs\",\n            \"columnName\": \"coverDecodeJs\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"sortUrl\",\n            \"columnName\": \"sortUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"singleUrl\",\n            \"columnName\": \"singleUrl\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"articleStyle\",\n            \"columnName\": \"articleStyle\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"ruleArticles\",\n            \"columnName\": \"ruleArticles\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleNextPage\",\n            \"columnName\": \"ruleNextPage\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleTitle\",\n            \"columnName\": \"ruleTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"rulePubDate\",\n            \"columnName\": \"rulePubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleDescription\",\n            \"columnName\": \"ruleDescription\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleImage\",\n            \"columnName\": \"ruleImage\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleLink\",\n            \"columnName\": \"ruleLink\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleContent\",\n            \"columnName\": \"ruleContent\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"contentWhitelist\",\n            \"columnName\": \"contentWhitelist\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"contentBlacklist\",\n            \"columnName\": \"contentBlacklist\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"style\",\n            \"columnName\": \"style\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enableJs\",\n            \"columnName\": \"enableJs\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"loadWithBaseUrl\",\n            \"columnName\": \"loadWithBaseUrl\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"injectJs\",\n            \"columnName\": \"injectJs\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"lastUpdateTime\",\n            \"columnName\": \"lastUpdateTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"sourceUrl\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_rssSources_sourceUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"sourceUrl\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_rssSources_sourceUrl` ON `${TABLE_NAME}` (`sourceUrl`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"bookmarks\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`time` INTEGER NOT NULL, `bookName` TEXT NOT NULL, `bookAuthor` TEXT NOT NULL, `chapterIndex` INTEGER NOT NULL, `chapterPos` INTEGER NOT NULL, `chapterName` TEXT NOT NULL, `bookText` TEXT NOT NULL, `content` TEXT NOT NULL, PRIMARY KEY(`time`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"time\",\n            \"columnName\": \"time\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookName\",\n            \"columnName\": \"bookName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookAuthor\",\n            \"columnName\": \"bookAuthor\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterIndex\",\n            \"columnName\": \"chapterIndex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterPos\",\n            \"columnName\": \"chapterPos\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterName\",\n            \"columnName\": \"chapterName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookText\",\n            \"columnName\": \"bookText\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"time\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_bookmarks_bookName_bookAuthor\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"bookName\",\n              \"bookAuthor\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_bookmarks_bookName_bookAuthor` ON `${TABLE_NAME}` (`bookName`, `bookAuthor`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssArticles\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`origin` TEXT NOT NULL, `sort` TEXT NOT NULL, `title` TEXT NOT NULL, `order` INTEGER NOT NULL, `link` TEXT NOT NULL, `pubDate` TEXT, `description` TEXT, `content` TEXT, `image` TEXT, `read` INTEGER NOT NULL, `variable` TEXT, PRIMARY KEY(`origin`, `link`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sort\",\n            \"columnName\": \"sort\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"link\",\n            \"columnName\": \"link\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"pubDate\",\n            \"columnName\": \"pubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"description\",\n            \"columnName\": \"description\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"image\",\n            \"columnName\": \"image\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"read\",\n            \"columnName\": \"read\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"origin\",\n            \"link\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssReadRecords\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`record` TEXT NOT NULL, `read` INTEGER NOT NULL, PRIMARY KEY(`record`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"record\",\n            \"columnName\": \"record\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"read\",\n            \"columnName\": \"read\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"record\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssStars\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`origin` TEXT NOT NULL, `sort` TEXT NOT NULL, `title` TEXT NOT NULL, `starTime` INTEGER NOT NULL, `link` TEXT NOT NULL, `pubDate` TEXT, `description` TEXT, `content` TEXT, `image` TEXT, `variable` TEXT, PRIMARY KEY(`origin`, `link`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sort\",\n            \"columnName\": \"sort\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"starTime\",\n            \"columnName\": \"starTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"link\",\n            \"columnName\": \"link\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"pubDate\",\n            \"columnName\": \"pubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"description\",\n            \"columnName\": \"description\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"image\",\n            \"columnName\": \"image\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"origin\",\n            \"link\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"txtTocRules\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `rule` TEXT NOT NULL, `example` TEXT, `serialNumber` INTEGER NOT NULL, `enable` INTEGER NOT NULL, PRIMARY KEY(`id`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"rule\",\n            \"columnName\": \"rule\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"example\",\n            \"columnName\": \"example\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"serialNumber\",\n            \"columnName\": \"serialNumber\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enable\",\n            \"columnName\": \"enable\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"id\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"readRecord\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`deviceId` TEXT NOT NULL, `bookName` TEXT NOT NULL, `readTime` INTEGER NOT NULL DEFAULT 0, `lastRead` INTEGER NOT NULL DEFAULT 0, PRIMARY KEY(`deviceId`, `bookName`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"deviceId\",\n            \"columnName\": \"deviceId\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookName\",\n            \"columnName\": \"bookName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"readTime\",\n            \"columnName\": \"readTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"lastRead\",\n            \"columnName\": \"lastRead\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"deviceId\",\n            \"bookName\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"httpTTS\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `url` TEXT NOT NULL, `contentType` TEXT, `concurrentRate` TEXT DEFAULT '0', `loginUrl` TEXT, `loginUi` TEXT, `header` TEXT, `enabledCookieJar` INTEGER DEFAULT 0, `loginCheckJs` TEXT, `lastUpdateTime` INTEGER NOT NULL DEFAULT 0, PRIMARY KEY(`id`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"contentType\",\n            \"columnName\": \"contentType\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"concurrentRate\",\n            \"columnName\": \"concurrentRate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false,\n            \"defaultValue\": \"'0'\"\n          },\n          {\n            \"fieldPath\": \"loginUrl\",\n            \"columnName\": \"loginUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUi\",\n            \"columnName\": \"loginUi\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"header\",\n            \"columnName\": \"header\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enabledCookieJar\",\n            \"columnName\": \"enabledCookieJar\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"loginCheckJs\",\n            \"columnName\": \"loginCheckJs\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"lastUpdateTime\",\n            \"columnName\": \"lastUpdateTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"id\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"caches\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`key` TEXT NOT NULL, `value` TEXT, `deadline` INTEGER NOT NULL, PRIMARY KEY(`key`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"key\",\n            \"columnName\": \"key\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"value\",\n            \"columnName\": \"value\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"deadline\",\n            \"columnName\": \"deadline\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"key\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_caches_key\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"key\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_caches_key` ON `${TABLE_NAME}` (`key`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"ruleSubs\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `url` TEXT NOT NULL, `type` INTEGER NOT NULL, `customOrder` INTEGER NOT NULL, `autoUpdate` INTEGER NOT NULL, `update` INTEGER NOT NULL, PRIMARY KEY(`id`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"autoUpdate\",\n            \"columnName\": \"autoUpdate\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"update\",\n            \"columnName\": \"update\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"id\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"dictRules\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`name` TEXT NOT NULL, `urlRule` TEXT NOT NULL, `showRule` TEXT NOT NULL, `enabled` INTEGER NOT NULL DEFAULT 1, `sortNumber` INTEGER NOT NULL DEFAULT 0, PRIMARY KEY(`name`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"urlRule\",\n            \"columnName\": \"urlRule\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"showRule\",\n            \"columnName\": \"showRule\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enabled\",\n            \"columnName\": \"enabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"sortNumber\",\n            \"columnName\": \"sortNumber\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"name\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"keyboardAssists\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`type` INTEGER NOT NULL DEFAULT 0, `key` TEXT NOT NULL DEFAULT '', `value` TEXT NOT NULL DEFAULT '', `serialNo` INTEGER NOT NULL DEFAULT 0, PRIMARY KEY(`type`, `key`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"key\",\n            \"columnName\": \"key\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"value\",\n            \"columnName\": \"value\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"serialNo\",\n            \"columnName\": \"serialNo\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"type\",\n            \"key\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"servers\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `type` TEXT NOT NULL, `config` TEXT, `sortNumber` INTEGER NOT NULL, PRIMARY KEY(`id`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"config\",\n            \"columnName\": \"config\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"sortNumber\",\n            \"columnName\": \"sortNumber\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"id\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      }\n    ],\n    \"views\": [],\n    \"setupQueries\": [\n      \"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)\",\n      \"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'ccfe4ad59c8caef014b1c754b69e6d05')\"\n    ]\n  }\n}"
  },
  {
    "path": "app/schemas/io.legado.app.data.AppDatabase/66.json",
    "content": "{\n  \"formatVersion\": 1,\n  \"database\": {\n    \"version\": 66,\n    \"identityHash\": \"ef55cf538ab8076ccaf821cbc7f6582f\",\n    \"entities\": [\n      {\n        \"tableName\": \"books\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookUrl` TEXT NOT NULL DEFAULT '', `tocUrl` TEXT NOT NULL DEFAULT '', `origin` TEXT NOT NULL DEFAULT 'loc_book', `originName` TEXT NOT NULL DEFAULT '', `name` TEXT NOT NULL DEFAULT '', `author` TEXT NOT NULL DEFAULT '', `kind` TEXT, `customTag` TEXT, `coverUrl` TEXT, `customCoverUrl` TEXT, `intro` TEXT, `customIntro` TEXT, `charset` TEXT, `type` INTEGER NOT NULL DEFAULT 0, `group` INTEGER NOT NULL DEFAULT 0, `latestChapterTitle` TEXT, `latestChapterTime` INTEGER NOT NULL DEFAULT 0, `lastCheckTime` INTEGER NOT NULL DEFAULT 0, `lastCheckCount` INTEGER NOT NULL DEFAULT 0, `totalChapterNum` INTEGER NOT NULL DEFAULT 0, `durChapterTitle` TEXT, `durChapterIndex` INTEGER NOT NULL DEFAULT 0, `durChapterPos` INTEGER NOT NULL DEFAULT 0, `durChapterTime` INTEGER NOT NULL DEFAULT 0, `wordCount` TEXT, `canUpdate` INTEGER NOT NULL DEFAULT 1, `order` INTEGER NOT NULL DEFAULT 0, `originOrder` INTEGER NOT NULL DEFAULT 0, `variable` TEXT, `readConfig` TEXT, PRIMARY KEY(`bookUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"tocUrl\",\n            \"columnName\": \"tocUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"'loc_book'\"\n          },\n          {\n            \"fieldPath\": \"originName\",\n            \"columnName\": \"originName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"author\",\n            \"columnName\": \"author\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"kind\",\n            \"columnName\": \"kind\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customTag\",\n            \"columnName\": \"customTag\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"coverUrl\",\n            \"columnName\": \"coverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customCoverUrl\",\n            \"columnName\": \"customCoverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"intro\",\n            \"columnName\": \"intro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customIntro\",\n            \"columnName\": \"customIntro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"charset\",\n            \"columnName\": \"charset\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"group\",\n            \"columnName\": \"group\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"latestChapterTitle\",\n            \"columnName\": \"latestChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"latestChapterTime\",\n            \"columnName\": \"latestChapterTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"lastCheckTime\",\n            \"columnName\": \"lastCheckTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"lastCheckCount\",\n            \"columnName\": \"lastCheckCount\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"totalChapterNum\",\n            \"columnName\": \"totalChapterNum\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"durChapterTitle\",\n            \"columnName\": \"durChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"durChapterIndex\",\n            \"columnName\": \"durChapterIndex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"durChapterPos\",\n            \"columnName\": \"durChapterPos\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"durChapterTime\",\n            \"columnName\": \"durChapterTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"wordCount\",\n            \"columnName\": \"wordCount\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"canUpdate\",\n            \"columnName\": \"canUpdate\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"originOrder\",\n            \"columnName\": \"originOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"readConfig\",\n            \"columnName\": \"readConfig\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"bookUrl\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_books_name_author\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"name\",\n              \"author\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_books_name_author` ON `${TABLE_NAME}` (`name`, `author`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"book_groups\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`groupId` INTEGER NOT NULL, `groupName` TEXT NOT NULL, `cover` TEXT, `order` INTEGER NOT NULL, `enableRefresh` INTEGER NOT NULL DEFAULT 1, `show` INTEGER NOT NULL DEFAULT 1, `bookSort` INTEGER NOT NULL DEFAULT -1, PRIMARY KEY(`groupId`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"groupId\",\n            \"columnName\": \"groupId\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"groupName\",\n            \"columnName\": \"groupName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"cover\",\n            \"columnName\": \"cover\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enableRefresh\",\n            \"columnName\": \"enableRefresh\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"show\",\n            \"columnName\": \"show\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"bookSort\",\n            \"columnName\": \"bookSort\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"-1\"\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"groupId\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"book_sources\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookSourceUrl` TEXT NOT NULL, `bookSourceName` TEXT NOT NULL, `bookSourceGroup` TEXT, `bookSourceType` INTEGER NOT NULL, `bookUrlPattern` TEXT, `customOrder` INTEGER NOT NULL DEFAULT 0, `enabled` INTEGER NOT NULL DEFAULT 1, `enabledExplore` INTEGER NOT NULL DEFAULT 1, `jsLib` TEXT, `enabledCookieJar` INTEGER DEFAULT 0, `concurrentRate` TEXT, `header` TEXT, `loginUrl` TEXT, `loginUi` TEXT, `loginCheckJs` TEXT, `coverDecodeJs` TEXT, `bookSourceComment` TEXT, `variableComment` TEXT, `lastUpdateTime` INTEGER NOT NULL, `respondTime` INTEGER NOT NULL, `weight` INTEGER NOT NULL, `exploreUrl` TEXT, `exploreScreen` TEXT, `ruleExplore` TEXT, `searchUrl` TEXT, `ruleSearch` TEXT, `ruleBookInfo` TEXT, `ruleToc` TEXT, `ruleContent` TEXT, `ruleReview` TEXT, PRIMARY KEY(`bookSourceUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookSourceUrl\",\n            \"columnName\": \"bookSourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookSourceName\",\n            \"columnName\": \"bookSourceName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookSourceGroup\",\n            \"columnName\": \"bookSourceGroup\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"bookSourceType\",\n            \"columnName\": \"bookSourceType\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookUrlPattern\",\n            \"columnName\": \"bookUrlPattern\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"enabled\",\n            \"columnName\": \"enabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"enabledExplore\",\n            \"columnName\": \"enabledExplore\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"jsLib\",\n            \"columnName\": \"jsLib\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enabledCookieJar\",\n            \"columnName\": \"enabledCookieJar\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"concurrentRate\",\n            \"columnName\": \"concurrentRate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"header\",\n            \"columnName\": \"header\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUrl\",\n            \"columnName\": \"loginUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUi\",\n            \"columnName\": \"loginUi\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginCheckJs\",\n            \"columnName\": \"loginCheckJs\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"coverDecodeJs\",\n            \"columnName\": \"coverDecodeJs\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"bookSourceComment\",\n            \"columnName\": \"bookSourceComment\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"variableComment\",\n            \"columnName\": \"variableComment\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"lastUpdateTime\",\n            \"columnName\": \"lastUpdateTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"respondTime\",\n            \"columnName\": \"respondTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"weight\",\n            \"columnName\": \"weight\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"exploreUrl\",\n            \"columnName\": \"exploreUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"exploreScreen\",\n            \"columnName\": \"exploreScreen\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleExplore\",\n            \"columnName\": \"ruleExplore\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"searchUrl\",\n            \"columnName\": \"searchUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleSearch\",\n            \"columnName\": \"ruleSearch\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleBookInfo\",\n            \"columnName\": \"ruleBookInfo\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleToc\",\n            \"columnName\": \"ruleToc\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleContent\",\n            \"columnName\": \"ruleContent\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleReview\",\n            \"columnName\": \"ruleReview\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"bookSourceUrl\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_book_sources_bookSourceUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"bookSourceUrl\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_book_sources_bookSourceUrl` ON `${TABLE_NAME}` (`bookSourceUrl`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"chapters\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`url` TEXT NOT NULL, `title` TEXT NOT NULL, `isVolume` INTEGER NOT NULL, `baseUrl` TEXT NOT NULL, `bookUrl` TEXT NOT NULL, `index` INTEGER NOT NULL, `isVip` INTEGER NOT NULL, `isPay` INTEGER NOT NULL, `resourceUrl` TEXT, `tag` TEXT, `start` INTEGER, `end` INTEGER, `startFragmentId` TEXT, `endFragmentId` TEXT, `variable` TEXT, PRIMARY KEY(`url`, `bookUrl`), FOREIGN KEY(`bookUrl`) REFERENCES `books`(`bookUrl`) ON UPDATE NO ACTION ON DELETE CASCADE )\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"isVolume\",\n            \"columnName\": \"isVolume\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"baseUrl\",\n            \"columnName\": \"baseUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"index\",\n            \"columnName\": \"index\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"isVip\",\n            \"columnName\": \"isVip\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"isPay\",\n            \"columnName\": \"isPay\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"resourceUrl\",\n            \"columnName\": \"resourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"tag\",\n            \"columnName\": \"tag\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"start\",\n            \"columnName\": \"start\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"end\",\n            \"columnName\": \"end\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"startFragmentId\",\n            \"columnName\": \"startFragmentId\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"endFragmentId\",\n            \"columnName\": \"endFragmentId\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"url\",\n            \"bookUrl\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_chapters_bookUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"bookUrl\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_chapters_bookUrl` ON `${TABLE_NAME}` (`bookUrl`)\"\n          },\n          {\n            \"name\": \"index_chapters_bookUrl_index\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"bookUrl\",\n              \"index\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_chapters_bookUrl_index` ON `${TABLE_NAME}` (`bookUrl`, `index`)\"\n          }\n        ],\n        \"foreignKeys\": [\n          {\n            \"table\": \"books\",\n            \"onDelete\": \"CASCADE\",\n            \"onUpdate\": \"NO ACTION\",\n            \"columns\": [\n              \"bookUrl\"\n            ],\n            \"referencedColumns\": [\n              \"bookUrl\"\n            ]\n          }\n        ]\n      },\n      {\n        \"tableName\": \"replace_rules\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL DEFAULT '', `group` TEXT, `pattern` TEXT NOT NULL DEFAULT '', `replacement` TEXT NOT NULL DEFAULT '', `scope` TEXT, `scopeTitle` INTEGER NOT NULL DEFAULT 0, `scopeContent` INTEGER NOT NULL DEFAULT 1, `isEnabled` INTEGER NOT NULL DEFAULT 1, `isRegex` INTEGER NOT NULL DEFAULT 1, `timeoutMillisecond` INTEGER NOT NULL DEFAULT 3000, `sortOrder` INTEGER NOT NULL DEFAULT 0)\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"group\",\n            \"columnName\": \"group\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"pattern\",\n            \"columnName\": \"pattern\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"replacement\",\n            \"columnName\": \"replacement\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"scope\",\n            \"columnName\": \"scope\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"scopeTitle\",\n            \"columnName\": \"scopeTitle\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"scopeContent\",\n            \"columnName\": \"scopeContent\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"isEnabled\",\n            \"columnName\": \"isEnabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"isRegex\",\n            \"columnName\": \"isRegex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"timeoutMillisecond\",\n            \"columnName\": \"timeoutMillisecond\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"3000\"\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"sortOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": true,\n          \"columnNames\": [\n            \"id\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_replace_rules_id\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"id\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_replace_rules_id` ON `${TABLE_NAME}` (`id`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"searchBooks\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookUrl` TEXT NOT NULL, `origin` TEXT NOT NULL, `originName` TEXT NOT NULL, `type` INTEGER NOT NULL, `name` TEXT NOT NULL, `author` TEXT NOT NULL, `kind` TEXT, `coverUrl` TEXT, `intro` TEXT, `wordCount` TEXT, `latestChapterTitle` TEXT, `tocUrl` TEXT NOT NULL, `time` INTEGER NOT NULL, `variable` TEXT, `originOrder` INTEGER NOT NULL, PRIMARY KEY(`bookUrl`), FOREIGN KEY(`origin`) REFERENCES `book_sources`(`bookSourceUrl`) ON UPDATE NO ACTION ON DELETE CASCADE )\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"originName\",\n            \"columnName\": \"originName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"author\",\n            \"columnName\": \"author\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"kind\",\n            \"columnName\": \"kind\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"coverUrl\",\n            \"columnName\": \"coverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"intro\",\n            \"columnName\": \"intro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"wordCount\",\n            \"columnName\": \"wordCount\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"latestChapterTitle\",\n            \"columnName\": \"latestChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"tocUrl\",\n            \"columnName\": \"tocUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"time\",\n            \"columnName\": \"time\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"originOrder\",\n            \"columnName\": \"originOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"bookUrl\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_searchBooks_bookUrl\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"bookUrl\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_searchBooks_bookUrl` ON `${TABLE_NAME}` (`bookUrl`)\"\n          },\n          {\n            \"name\": \"index_searchBooks_origin\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"origin\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_searchBooks_origin` ON `${TABLE_NAME}` (`origin`)\"\n          }\n        ],\n        \"foreignKeys\": [\n          {\n            \"table\": \"book_sources\",\n            \"onDelete\": \"CASCADE\",\n            \"onUpdate\": \"NO ACTION\",\n            \"columns\": [\n              \"origin\"\n            ],\n            \"referencedColumns\": [\n              \"bookSourceUrl\"\n            ]\n          }\n        ]\n      },\n      {\n        \"tableName\": \"search_keywords\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`word` TEXT NOT NULL, `usage` INTEGER NOT NULL, `lastUseTime` INTEGER NOT NULL, PRIMARY KEY(`word`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"word\",\n            \"columnName\": \"word\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"usage\",\n            \"columnName\": \"usage\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"lastUseTime\",\n            \"columnName\": \"lastUseTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"word\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_search_keywords_word\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"word\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_search_keywords_word` ON `${TABLE_NAME}` (`word`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"cookies\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`url` TEXT NOT NULL, `cookie` TEXT NOT NULL, PRIMARY KEY(`url`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"cookie\",\n            \"columnName\": \"cookie\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"url\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_cookies_url\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"url\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_cookies_url` ON `${TABLE_NAME}` (`url`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssSources\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`sourceUrl` TEXT NOT NULL, `sourceName` TEXT NOT NULL, `sourceIcon` TEXT NOT NULL, `sourceGroup` TEXT, `sourceComment` TEXT, `enabled` INTEGER NOT NULL, `variableComment` TEXT, `jsLib` TEXT, `enabledCookieJar` INTEGER DEFAULT 0, `concurrentRate` TEXT, `header` TEXT, `loginUrl` TEXT, `loginUi` TEXT, `loginCheckJs` TEXT, `coverDecodeJs` TEXT, `sortUrl` TEXT, `singleUrl` INTEGER NOT NULL, `articleStyle` INTEGER NOT NULL DEFAULT 0, `ruleArticles` TEXT, `ruleNextPage` TEXT, `ruleTitle` TEXT, `rulePubDate` TEXT, `ruleDescription` TEXT, `ruleImage` TEXT, `ruleLink` TEXT, `ruleContent` TEXT, `contentWhitelist` TEXT, `contentBlacklist` TEXT, `style` TEXT, `enableJs` INTEGER NOT NULL DEFAULT 1, `loadWithBaseUrl` INTEGER NOT NULL DEFAULT 1, `injectJs` TEXT, `lastUpdateTime` INTEGER NOT NULL DEFAULT 0, `customOrder` INTEGER NOT NULL DEFAULT 0, PRIMARY KEY(`sourceUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"sourceUrl\",\n            \"columnName\": \"sourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceName\",\n            \"columnName\": \"sourceName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceIcon\",\n            \"columnName\": \"sourceIcon\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceGroup\",\n            \"columnName\": \"sourceGroup\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"sourceComment\",\n            \"columnName\": \"sourceComment\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enabled\",\n            \"columnName\": \"enabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"variableComment\",\n            \"columnName\": \"variableComment\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"jsLib\",\n            \"columnName\": \"jsLib\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enabledCookieJar\",\n            \"columnName\": \"enabledCookieJar\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"concurrentRate\",\n            \"columnName\": \"concurrentRate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"header\",\n            \"columnName\": \"header\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUrl\",\n            \"columnName\": \"loginUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUi\",\n            \"columnName\": \"loginUi\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginCheckJs\",\n            \"columnName\": \"loginCheckJs\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"coverDecodeJs\",\n            \"columnName\": \"coverDecodeJs\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"sortUrl\",\n            \"columnName\": \"sortUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"singleUrl\",\n            \"columnName\": \"singleUrl\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"articleStyle\",\n            \"columnName\": \"articleStyle\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"ruleArticles\",\n            \"columnName\": \"ruleArticles\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleNextPage\",\n            \"columnName\": \"ruleNextPage\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleTitle\",\n            \"columnName\": \"ruleTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"rulePubDate\",\n            \"columnName\": \"rulePubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleDescription\",\n            \"columnName\": \"ruleDescription\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleImage\",\n            \"columnName\": \"ruleImage\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleLink\",\n            \"columnName\": \"ruleLink\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleContent\",\n            \"columnName\": \"ruleContent\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"contentWhitelist\",\n            \"columnName\": \"contentWhitelist\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"contentBlacklist\",\n            \"columnName\": \"contentBlacklist\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"style\",\n            \"columnName\": \"style\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enableJs\",\n            \"columnName\": \"enableJs\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"loadWithBaseUrl\",\n            \"columnName\": \"loadWithBaseUrl\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"injectJs\",\n            \"columnName\": \"injectJs\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"lastUpdateTime\",\n            \"columnName\": \"lastUpdateTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"sourceUrl\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_rssSources_sourceUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"sourceUrl\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_rssSources_sourceUrl` ON `${TABLE_NAME}` (`sourceUrl`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"bookmarks\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`time` INTEGER NOT NULL, `bookName` TEXT NOT NULL, `bookAuthor` TEXT NOT NULL, `chapterIndex` INTEGER NOT NULL, `chapterPos` INTEGER NOT NULL, `chapterName` TEXT NOT NULL, `bookText` TEXT NOT NULL, `content` TEXT NOT NULL, PRIMARY KEY(`time`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"time\",\n            \"columnName\": \"time\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookName\",\n            \"columnName\": \"bookName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookAuthor\",\n            \"columnName\": \"bookAuthor\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterIndex\",\n            \"columnName\": \"chapterIndex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterPos\",\n            \"columnName\": \"chapterPos\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterName\",\n            \"columnName\": \"chapterName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookText\",\n            \"columnName\": \"bookText\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"time\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_bookmarks_bookName_bookAuthor\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"bookName\",\n              \"bookAuthor\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_bookmarks_bookName_bookAuthor` ON `${TABLE_NAME}` (`bookName`, `bookAuthor`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssArticles\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`origin` TEXT NOT NULL, `sort` TEXT NOT NULL, `title` TEXT NOT NULL, `order` INTEGER NOT NULL, `link` TEXT NOT NULL, `pubDate` TEXT, `description` TEXT, `content` TEXT, `image` TEXT, `read` INTEGER NOT NULL, `variable` TEXT, PRIMARY KEY(`origin`, `link`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sort\",\n            \"columnName\": \"sort\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"link\",\n            \"columnName\": \"link\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"pubDate\",\n            \"columnName\": \"pubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"description\",\n            \"columnName\": \"description\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"image\",\n            \"columnName\": \"image\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"read\",\n            \"columnName\": \"read\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"origin\",\n            \"link\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssReadRecords\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`record` TEXT NOT NULL, `read` INTEGER NOT NULL, PRIMARY KEY(`record`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"record\",\n            \"columnName\": \"record\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"read\",\n            \"columnName\": \"read\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"record\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssStars\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`origin` TEXT NOT NULL, `sort` TEXT NOT NULL, `title` TEXT NOT NULL, `starTime` INTEGER NOT NULL, `link` TEXT NOT NULL, `pubDate` TEXT, `description` TEXT, `content` TEXT, `image` TEXT, `variable` TEXT, PRIMARY KEY(`origin`, `link`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sort\",\n            \"columnName\": \"sort\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"starTime\",\n            \"columnName\": \"starTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"link\",\n            \"columnName\": \"link\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"pubDate\",\n            \"columnName\": \"pubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"description\",\n            \"columnName\": \"description\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"image\",\n            \"columnName\": \"image\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"origin\",\n            \"link\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"txtTocRules\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `rule` TEXT NOT NULL, `example` TEXT, `serialNumber` INTEGER NOT NULL, `enable` INTEGER NOT NULL, PRIMARY KEY(`id`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"rule\",\n            \"columnName\": \"rule\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"example\",\n            \"columnName\": \"example\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"serialNumber\",\n            \"columnName\": \"serialNumber\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enable\",\n            \"columnName\": \"enable\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"id\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"readRecord\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`deviceId` TEXT NOT NULL, `bookName` TEXT NOT NULL, `readTime` INTEGER NOT NULL DEFAULT 0, `lastRead` INTEGER NOT NULL DEFAULT 0, PRIMARY KEY(`deviceId`, `bookName`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"deviceId\",\n            \"columnName\": \"deviceId\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookName\",\n            \"columnName\": \"bookName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"readTime\",\n            \"columnName\": \"readTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"lastRead\",\n            \"columnName\": \"lastRead\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"deviceId\",\n            \"bookName\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"httpTTS\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `url` TEXT NOT NULL, `contentType` TEXT, `concurrentRate` TEXT DEFAULT '0', `loginUrl` TEXT, `loginUi` TEXT, `header` TEXT, `jsLib` TEXT, `enabledCookieJar` INTEGER DEFAULT 0, `loginCheckJs` TEXT, `lastUpdateTime` INTEGER NOT NULL DEFAULT 0, PRIMARY KEY(`id`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"contentType\",\n            \"columnName\": \"contentType\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"concurrentRate\",\n            \"columnName\": \"concurrentRate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false,\n            \"defaultValue\": \"'0'\"\n          },\n          {\n            \"fieldPath\": \"loginUrl\",\n            \"columnName\": \"loginUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUi\",\n            \"columnName\": \"loginUi\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"header\",\n            \"columnName\": \"header\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"jsLib\",\n            \"columnName\": \"jsLib\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enabledCookieJar\",\n            \"columnName\": \"enabledCookieJar\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"loginCheckJs\",\n            \"columnName\": \"loginCheckJs\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"lastUpdateTime\",\n            \"columnName\": \"lastUpdateTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"id\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"caches\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`key` TEXT NOT NULL, `value` TEXT, `deadline` INTEGER NOT NULL, PRIMARY KEY(`key`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"key\",\n            \"columnName\": \"key\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"value\",\n            \"columnName\": \"value\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"deadline\",\n            \"columnName\": \"deadline\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"key\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_caches_key\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"key\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_caches_key` ON `${TABLE_NAME}` (`key`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"ruleSubs\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `url` TEXT NOT NULL, `type` INTEGER NOT NULL, `customOrder` INTEGER NOT NULL, `autoUpdate` INTEGER NOT NULL, `update` INTEGER NOT NULL, PRIMARY KEY(`id`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"autoUpdate\",\n            \"columnName\": \"autoUpdate\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"update\",\n            \"columnName\": \"update\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"id\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"dictRules\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`name` TEXT NOT NULL, `urlRule` TEXT NOT NULL, `showRule` TEXT NOT NULL, `enabled` INTEGER NOT NULL DEFAULT 1, `sortNumber` INTEGER NOT NULL DEFAULT 0, PRIMARY KEY(`name`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"urlRule\",\n            \"columnName\": \"urlRule\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"showRule\",\n            \"columnName\": \"showRule\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enabled\",\n            \"columnName\": \"enabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"sortNumber\",\n            \"columnName\": \"sortNumber\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"name\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"keyboardAssists\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`type` INTEGER NOT NULL DEFAULT 0, `key` TEXT NOT NULL DEFAULT '', `value` TEXT NOT NULL DEFAULT '', `serialNo` INTEGER NOT NULL DEFAULT 0, PRIMARY KEY(`type`, `key`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"key\",\n            \"columnName\": \"key\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"value\",\n            \"columnName\": \"value\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"serialNo\",\n            \"columnName\": \"serialNo\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"type\",\n            \"key\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"servers\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `type` TEXT NOT NULL, `config` TEXT, `sortNumber` INTEGER NOT NULL, PRIMARY KEY(`id`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"config\",\n            \"columnName\": \"config\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"sortNumber\",\n            \"columnName\": \"sortNumber\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"id\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      }\n    ],\n    \"views\": [],\n    \"setupQueries\": [\n      \"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)\",\n      \"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'ef55cf538ab8076ccaf821cbc7f6582f')\"\n    ]\n  }\n}"
  },
  {
    "path": "app/schemas/io.legado.app.data.AppDatabase/67.json",
    "content": "{\n  \"formatVersion\": 1,\n  \"database\": {\n    \"version\": 67,\n    \"identityHash\": \"48855e5272b008e4c6b202a41654b575\",\n    \"entities\": [\n      {\n        \"tableName\": \"books\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookUrl` TEXT NOT NULL DEFAULT '', `tocUrl` TEXT NOT NULL DEFAULT '', `origin` TEXT NOT NULL DEFAULT 'loc_book', `originName` TEXT NOT NULL DEFAULT '', `name` TEXT NOT NULL DEFAULT '', `author` TEXT NOT NULL DEFAULT '', `kind` TEXT, `customTag` TEXT, `coverUrl` TEXT, `customCoverUrl` TEXT, `intro` TEXT, `customIntro` TEXT, `charset` TEXT, `type` INTEGER NOT NULL DEFAULT 0, `group` INTEGER NOT NULL DEFAULT 0, `latestChapterTitle` TEXT, `latestChapterTime` INTEGER NOT NULL DEFAULT 0, `lastCheckTime` INTEGER NOT NULL DEFAULT 0, `lastCheckCount` INTEGER NOT NULL DEFAULT 0, `totalChapterNum` INTEGER NOT NULL DEFAULT 0, `durChapterTitle` TEXT, `durChapterIndex` INTEGER NOT NULL DEFAULT 0, `durChapterPos` INTEGER NOT NULL DEFAULT 0, `durChapterTime` INTEGER NOT NULL DEFAULT 0, `wordCount` TEXT, `canUpdate` INTEGER NOT NULL DEFAULT 1, `order` INTEGER NOT NULL DEFAULT 0, `originOrder` INTEGER NOT NULL DEFAULT 0, `variable` TEXT, `readConfig` TEXT, PRIMARY KEY(`bookUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"tocUrl\",\n            \"columnName\": \"tocUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"'loc_book'\"\n          },\n          {\n            \"fieldPath\": \"originName\",\n            \"columnName\": \"originName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"author\",\n            \"columnName\": \"author\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"kind\",\n            \"columnName\": \"kind\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customTag\",\n            \"columnName\": \"customTag\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"coverUrl\",\n            \"columnName\": \"coverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customCoverUrl\",\n            \"columnName\": \"customCoverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"intro\",\n            \"columnName\": \"intro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customIntro\",\n            \"columnName\": \"customIntro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"charset\",\n            \"columnName\": \"charset\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"group\",\n            \"columnName\": \"group\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"latestChapterTitle\",\n            \"columnName\": \"latestChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"latestChapterTime\",\n            \"columnName\": \"latestChapterTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"lastCheckTime\",\n            \"columnName\": \"lastCheckTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"lastCheckCount\",\n            \"columnName\": \"lastCheckCount\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"totalChapterNum\",\n            \"columnName\": \"totalChapterNum\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"durChapterTitle\",\n            \"columnName\": \"durChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"durChapterIndex\",\n            \"columnName\": \"durChapterIndex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"durChapterPos\",\n            \"columnName\": \"durChapterPos\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"durChapterTime\",\n            \"columnName\": \"durChapterTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"wordCount\",\n            \"columnName\": \"wordCount\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"canUpdate\",\n            \"columnName\": \"canUpdate\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"originOrder\",\n            \"columnName\": \"originOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"readConfig\",\n            \"columnName\": \"readConfig\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"bookUrl\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_books_name_author\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"name\",\n              \"author\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_books_name_author` ON `${TABLE_NAME}` (`name`, `author`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"book_groups\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`groupId` INTEGER NOT NULL, `groupName` TEXT NOT NULL, `cover` TEXT, `order` INTEGER NOT NULL, `enableRefresh` INTEGER NOT NULL DEFAULT 1, `show` INTEGER NOT NULL DEFAULT 1, `bookSort` INTEGER NOT NULL DEFAULT -1, PRIMARY KEY(`groupId`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"groupId\",\n            \"columnName\": \"groupId\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"groupName\",\n            \"columnName\": \"groupName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"cover\",\n            \"columnName\": \"cover\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enableRefresh\",\n            \"columnName\": \"enableRefresh\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"show\",\n            \"columnName\": \"show\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"bookSort\",\n            \"columnName\": \"bookSort\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"-1\"\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"groupId\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"book_sources\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookSourceUrl` TEXT NOT NULL, `bookSourceName` TEXT NOT NULL, `bookSourceGroup` TEXT, `bookSourceType` INTEGER NOT NULL, `bookUrlPattern` TEXT, `customOrder` INTEGER NOT NULL DEFAULT 0, `enabled` INTEGER NOT NULL DEFAULT 1, `enabledExplore` INTEGER NOT NULL DEFAULT 1, `jsLib` TEXT, `enabledCookieJar` INTEGER DEFAULT 0, `concurrentRate` TEXT, `header` TEXT, `loginUrl` TEXT, `loginUi` TEXT, `loginCheckJs` TEXT, `coverDecodeJs` TEXT, `bookSourceComment` TEXT, `variableComment` TEXT, `lastUpdateTime` INTEGER NOT NULL, `respondTime` INTEGER NOT NULL, `weight` INTEGER NOT NULL, `exploreUrl` TEXT, `exploreScreen` TEXT, `ruleExplore` TEXT, `searchUrl` TEXT, `ruleSearch` TEXT, `ruleBookInfo` TEXT, `ruleToc` TEXT, `ruleContent` TEXT, `ruleReview` TEXT, PRIMARY KEY(`bookSourceUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookSourceUrl\",\n            \"columnName\": \"bookSourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookSourceName\",\n            \"columnName\": \"bookSourceName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookSourceGroup\",\n            \"columnName\": \"bookSourceGroup\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"bookSourceType\",\n            \"columnName\": \"bookSourceType\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookUrlPattern\",\n            \"columnName\": \"bookUrlPattern\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"enabled\",\n            \"columnName\": \"enabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"enabledExplore\",\n            \"columnName\": \"enabledExplore\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"jsLib\",\n            \"columnName\": \"jsLib\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enabledCookieJar\",\n            \"columnName\": \"enabledCookieJar\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"concurrentRate\",\n            \"columnName\": \"concurrentRate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"header\",\n            \"columnName\": \"header\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUrl\",\n            \"columnName\": \"loginUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUi\",\n            \"columnName\": \"loginUi\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginCheckJs\",\n            \"columnName\": \"loginCheckJs\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"coverDecodeJs\",\n            \"columnName\": \"coverDecodeJs\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"bookSourceComment\",\n            \"columnName\": \"bookSourceComment\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"variableComment\",\n            \"columnName\": \"variableComment\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"lastUpdateTime\",\n            \"columnName\": \"lastUpdateTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"respondTime\",\n            \"columnName\": \"respondTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"weight\",\n            \"columnName\": \"weight\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"exploreUrl\",\n            \"columnName\": \"exploreUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"exploreScreen\",\n            \"columnName\": \"exploreScreen\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleExplore\",\n            \"columnName\": \"ruleExplore\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"searchUrl\",\n            \"columnName\": \"searchUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleSearch\",\n            \"columnName\": \"ruleSearch\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleBookInfo\",\n            \"columnName\": \"ruleBookInfo\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleToc\",\n            \"columnName\": \"ruleToc\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleContent\",\n            \"columnName\": \"ruleContent\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleReview\",\n            \"columnName\": \"ruleReview\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"bookSourceUrl\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_book_sources_bookSourceUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"bookSourceUrl\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_book_sources_bookSourceUrl` ON `${TABLE_NAME}` (`bookSourceUrl`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"chapters\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`url` TEXT NOT NULL, `title` TEXT NOT NULL, `isVolume` INTEGER NOT NULL, `baseUrl` TEXT NOT NULL, `bookUrl` TEXT NOT NULL, `index` INTEGER NOT NULL, `isVip` INTEGER NOT NULL, `isPay` INTEGER NOT NULL, `resourceUrl` TEXT, `tag` TEXT, `start` INTEGER, `end` INTEGER, `startFragmentId` TEXT, `endFragmentId` TEXT, `variable` TEXT, PRIMARY KEY(`url`, `bookUrl`), FOREIGN KEY(`bookUrl`) REFERENCES `books`(`bookUrl`) ON UPDATE NO ACTION ON DELETE CASCADE )\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"isVolume\",\n            \"columnName\": \"isVolume\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"baseUrl\",\n            \"columnName\": \"baseUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"index\",\n            \"columnName\": \"index\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"isVip\",\n            \"columnName\": \"isVip\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"isPay\",\n            \"columnName\": \"isPay\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"resourceUrl\",\n            \"columnName\": \"resourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"tag\",\n            \"columnName\": \"tag\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"start\",\n            \"columnName\": \"start\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"end\",\n            \"columnName\": \"end\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"startFragmentId\",\n            \"columnName\": \"startFragmentId\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"endFragmentId\",\n            \"columnName\": \"endFragmentId\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"url\",\n            \"bookUrl\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_chapters_bookUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"bookUrl\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_chapters_bookUrl` ON `${TABLE_NAME}` (`bookUrl`)\"\n          },\n          {\n            \"name\": \"index_chapters_bookUrl_index\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"bookUrl\",\n              \"index\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_chapters_bookUrl_index` ON `${TABLE_NAME}` (`bookUrl`, `index`)\"\n          }\n        ],\n        \"foreignKeys\": [\n          {\n            \"table\": \"books\",\n            \"onDelete\": \"CASCADE\",\n            \"onUpdate\": \"NO ACTION\",\n            \"columns\": [\n              \"bookUrl\"\n            ],\n            \"referencedColumns\": [\n              \"bookUrl\"\n            ]\n          }\n        ]\n      },\n      {\n        \"tableName\": \"replace_rules\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL DEFAULT '', `group` TEXT, `pattern` TEXT NOT NULL DEFAULT '', `replacement` TEXT NOT NULL DEFAULT '', `scope` TEXT, `scopeTitle` INTEGER NOT NULL DEFAULT 0, `scopeContent` INTEGER NOT NULL DEFAULT 1, `isEnabled` INTEGER NOT NULL DEFAULT 1, `isRegex` INTEGER NOT NULL DEFAULT 1, `timeoutMillisecond` INTEGER NOT NULL DEFAULT 3000, `sortOrder` INTEGER NOT NULL DEFAULT 0)\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"group\",\n            \"columnName\": \"group\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"pattern\",\n            \"columnName\": \"pattern\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"replacement\",\n            \"columnName\": \"replacement\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"scope\",\n            \"columnName\": \"scope\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"scopeTitle\",\n            \"columnName\": \"scopeTitle\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"scopeContent\",\n            \"columnName\": \"scopeContent\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"isEnabled\",\n            \"columnName\": \"isEnabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"isRegex\",\n            \"columnName\": \"isRegex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"timeoutMillisecond\",\n            \"columnName\": \"timeoutMillisecond\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"3000\"\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"sortOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": true,\n          \"columnNames\": [\n            \"id\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_replace_rules_id\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"id\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_replace_rules_id` ON `${TABLE_NAME}` (`id`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"searchBooks\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookUrl` TEXT NOT NULL, `origin` TEXT NOT NULL, `originName` TEXT NOT NULL, `type` INTEGER NOT NULL, `name` TEXT NOT NULL, `author` TEXT NOT NULL, `kind` TEXT, `coverUrl` TEXT, `intro` TEXT, `wordCount` TEXT, `latestChapterTitle` TEXT, `tocUrl` TEXT NOT NULL, `time` INTEGER NOT NULL, `variable` TEXT, `originOrder` INTEGER NOT NULL, `chapterWordCountText` TEXT, `chapterWordCount` INTEGER NOT NULL DEFAULT -1, `respondTime` INTEGER NOT NULL DEFAULT -1, PRIMARY KEY(`bookUrl`), FOREIGN KEY(`origin`) REFERENCES `book_sources`(`bookSourceUrl`) ON UPDATE NO ACTION ON DELETE CASCADE )\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"originName\",\n            \"columnName\": \"originName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"author\",\n            \"columnName\": \"author\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"kind\",\n            \"columnName\": \"kind\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"coverUrl\",\n            \"columnName\": \"coverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"intro\",\n            \"columnName\": \"intro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"wordCount\",\n            \"columnName\": \"wordCount\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"latestChapterTitle\",\n            \"columnName\": \"latestChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"tocUrl\",\n            \"columnName\": \"tocUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"time\",\n            \"columnName\": \"time\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"originOrder\",\n            \"columnName\": \"originOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterWordCountText\",\n            \"columnName\": \"chapterWordCountText\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"chapterWordCount\",\n            \"columnName\": \"chapterWordCount\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"-1\"\n          },\n          {\n            \"fieldPath\": \"respondTime\",\n            \"columnName\": \"respondTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"-1\"\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"bookUrl\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_searchBooks_bookUrl\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"bookUrl\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_searchBooks_bookUrl` ON `${TABLE_NAME}` (`bookUrl`)\"\n          },\n          {\n            \"name\": \"index_searchBooks_origin\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"origin\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_searchBooks_origin` ON `${TABLE_NAME}` (`origin`)\"\n          }\n        ],\n        \"foreignKeys\": [\n          {\n            \"table\": \"book_sources\",\n            \"onDelete\": \"CASCADE\",\n            \"onUpdate\": \"NO ACTION\",\n            \"columns\": [\n              \"origin\"\n            ],\n            \"referencedColumns\": [\n              \"bookSourceUrl\"\n            ]\n          }\n        ]\n      },\n      {\n        \"tableName\": \"search_keywords\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`word` TEXT NOT NULL, `usage` INTEGER NOT NULL, `lastUseTime` INTEGER NOT NULL, PRIMARY KEY(`word`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"word\",\n            \"columnName\": \"word\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"usage\",\n            \"columnName\": \"usage\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"lastUseTime\",\n            \"columnName\": \"lastUseTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"word\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_search_keywords_word\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"word\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_search_keywords_word` ON `${TABLE_NAME}` (`word`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"cookies\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`url` TEXT NOT NULL, `cookie` TEXT NOT NULL, PRIMARY KEY(`url`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"cookie\",\n            \"columnName\": \"cookie\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"url\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_cookies_url\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"url\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_cookies_url` ON `${TABLE_NAME}` (`url`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssSources\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`sourceUrl` TEXT NOT NULL, `sourceName` TEXT NOT NULL, `sourceIcon` TEXT NOT NULL, `sourceGroup` TEXT, `sourceComment` TEXT, `enabled` INTEGER NOT NULL, `variableComment` TEXT, `jsLib` TEXT, `enabledCookieJar` INTEGER DEFAULT 0, `concurrentRate` TEXT, `header` TEXT, `loginUrl` TEXT, `loginUi` TEXT, `loginCheckJs` TEXT, `coverDecodeJs` TEXT, `sortUrl` TEXT, `singleUrl` INTEGER NOT NULL, `articleStyle` INTEGER NOT NULL DEFAULT 0, `ruleArticles` TEXT, `ruleNextPage` TEXT, `ruleTitle` TEXT, `rulePubDate` TEXT, `ruleDescription` TEXT, `ruleImage` TEXT, `ruleLink` TEXT, `ruleContent` TEXT, `contentWhitelist` TEXT, `contentBlacklist` TEXT, `style` TEXT, `enableJs` INTEGER NOT NULL DEFAULT 1, `loadWithBaseUrl` INTEGER NOT NULL DEFAULT 1, `injectJs` TEXT, `lastUpdateTime` INTEGER NOT NULL DEFAULT 0, `customOrder` INTEGER NOT NULL DEFAULT 0, PRIMARY KEY(`sourceUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"sourceUrl\",\n            \"columnName\": \"sourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceName\",\n            \"columnName\": \"sourceName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceIcon\",\n            \"columnName\": \"sourceIcon\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceGroup\",\n            \"columnName\": \"sourceGroup\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"sourceComment\",\n            \"columnName\": \"sourceComment\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enabled\",\n            \"columnName\": \"enabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"variableComment\",\n            \"columnName\": \"variableComment\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"jsLib\",\n            \"columnName\": \"jsLib\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enabledCookieJar\",\n            \"columnName\": \"enabledCookieJar\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"concurrentRate\",\n            \"columnName\": \"concurrentRate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"header\",\n            \"columnName\": \"header\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUrl\",\n            \"columnName\": \"loginUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUi\",\n            \"columnName\": \"loginUi\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginCheckJs\",\n            \"columnName\": \"loginCheckJs\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"coverDecodeJs\",\n            \"columnName\": \"coverDecodeJs\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"sortUrl\",\n            \"columnName\": \"sortUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"singleUrl\",\n            \"columnName\": \"singleUrl\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"articleStyle\",\n            \"columnName\": \"articleStyle\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"ruleArticles\",\n            \"columnName\": \"ruleArticles\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleNextPage\",\n            \"columnName\": \"ruleNextPage\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleTitle\",\n            \"columnName\": \"ruleTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"rulePubDate\",\n            \"columnName\": \"rulePubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleDescription\",\n            \"columnName\": \"ruleDescription\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleImage\",\n            \"columnName\": \"ruleImage\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleLink\",\n            \"columnName\": \"ruleLink\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleContent\",\n            \"columnName\": \"ruleContent\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"contentWhitelist\",\n            \"columnName\": \"contentWhitelist\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"contentBlacklist\",\n            \"columnName\": \"contentBlacklist\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"style\",\n            \"columnName\": \"style\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enableJs\",\n            \"columnName\": \"enableJs\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"loadWithBaseUrl\",\n            \"columnName\": \"loadWithBaseUrl\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"injectJs\",\n            \"columnName\": \"injectJs\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"lastUpdateTime\",\n            \"columnName\": \"lastUpdateTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"sourceUrl\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_rssSources_sourceUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"sourceUrl\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_rssSources_sourceUrl` ON `${TABLE_NAME}` (`sourceUrl`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"bookmarks\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`time` INTEGER NOT NULL, `bookName` TEXT NOT NULL, `bookAuthor` TEXT NOT NULL, `chapterIndex` INTEGER NOT NULL, `chapterPos` INTEGER NOT NULL, `chapterName` TEXT NOT NULL, `bookText` TEXT NOT NULL, `content` TEXT NOT NULL, PRIMARY KEY(`time`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"time\",\n            \"columnName\": \"time\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookName\",\n            \"columnName\": \"bookName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookAuthor\",\n            \"columnName\": \"bookAuthor\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterIndex\",\n            \"columnName\": \"chapterIndex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterPos\",\n            \"columnName\": \"chapterPos\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterName\",\n            \"columnName\": \"chapterName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookText\",\n            \"columnName\": \"bookText\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"time\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_bookmarks_bookName_bookAuthor\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"bookName\",\n              \"bookAuthor\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_bookmarks_bookName_bookAuthor` ON `${TABLE_NAME}` (`bookName`, `bookAuthor`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssArticles\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`origin` TEXT NOT NULL, `sort` TEXT NOT NULL, `title` TEXT NOT NULL, `order` INTEGER NOT NULL, `link` TEXT NOT NULL, `pubDate` TEXT, `description` TEXT, `content` TEXT, `image` TEXT, `read` INTEGER NOT NULL, `variable` TEXT, PRIMARY KEY(`origin`, `link`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sort\",\n            \"columnName\": \"sort\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"link\",\n            \"columnName\": \"link\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"pubDate\",\n            \"columnName\": \"pubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"description\",\n            \"columnName\": \"description\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"image\",\n            \"columnName\": \"image\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"read\",\n            \"columnName\": \"read\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"origin\",\n            \"link\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssReadRecords\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`record` TEXT NOT NULL, `read` INTEGER NOT NULL, PRIMARY KEY(`record`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"record\",\n            \"columnName\": \"record\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"read\",\n            \"columnName\": \"read\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"record\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssStars\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`origin` TEXT NOT NULL, `sort` TEXT NOT NULL, `title` TEXT NOT NULL, `starTime` INTEGER NOT NULL, `link` TEXT NOT NULL, `pubDate` TEXT, `description` TEXT, `content` TEXT, `image` TEXT, `variable` TEXT, PRIMARY KEY(`origin`, `link`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sort\",\n            \"columnName\": \"sort\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"starTime\",\n            \"columnName\": \"starTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"link\",\n            \"columnName\": \"link\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"pubDate\",\n            \"columnName\": \"pubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"description\",\n            \"columnName\": \"description\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"image\",\n            \"columnName\": \"image\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"origin\",\n            \"link\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"txtTocRules\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `rule` TEXT NOT NULL, `example` TEXT, `serialNumber` INTEGER NOT NULL, `enable` INTEGER NOT NULL, PRIMARY KEY(`id`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"rule\",\n            \"columnName\": \"rule\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"example\",\n            \"columnName\": \"example\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"serialNumber\",\n            \"columnName\": \"serialNumber\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enable\",\n            \"columnName\": \"enable\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"id\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"readRecord\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`deviceId` TEXT NOT NULL, `bookName` TEXT NOT NULL, `readTime` INTEGER NOT NULL DEFAULT 0, `lastRead` INTEGER NOT NULL DEFAULT 0, PRIMARY KEY(`deviceId`, `bookName`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"deviceId\",\n            \"columnName\": \"deviceId\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookName\",\n            \"columnName\": \"bookName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"readTime\",\n            \"columnName\": \"readTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"lastRead\",\n            \"columnName\": \"lastRead\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"deviceId\",\n            \"bookName\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"httpTTS\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `url` TEXT NOT NULL, `contentType` TEXT, `concurrentRate` TEXT DEFAULT '0', `loginUrl` TEXT, `loginUi` TEXT, `header` TEXT, `jsLib` TEXT, `enabledCookieJar` INTEGER DEFAULT 0, `loginCheckJs` TEXT, `lastUpdateTime` INTEGER NOT NULL DEFAULT 0, PRIMARY KEY(`id`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"contentType\",\n            \"columnName\": \"contentType\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"concurrentRate\",\n            \"columnName\": \"concurrentRate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false,\n            \"defaultValue\": \"'0'\"\n          },\n          {\n            \"fieldPath\": \"loginUrl\",\n            \"columnName\": \"loginUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUi\",\n            \"columnName\": \"loginUi\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"header\",\n            \"columnName\": \"header\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"jsLib\",\n            \"columnName\": \"jsLib\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enabledCookieJar\",\n            \"columnName\": \"enabledCookieJar\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"loginCheckJs\",\n            \"columnName\": \"loginCheckJs\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"lastUpdateTime\",\n            \"columnName\": \"lastUpdateTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"id\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"caches\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`key` TEXT NOT NULL, `value` TEXT, `deadline` INTEGER NOT NULL, PRIMARY KEY(`key`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"key\",\n            \"columnName\": \"key\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"value\",\n            \"columnName\": \"value\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"deadline\",\n            \"columnName\": \"deadline\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"key\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_caches_key\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"key\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_caches_key` ON `${TABLE_NAME}` (`key`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"ruleSubs\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `url` TEXT NOT NULL, `type` INTEGER NOT NULL, `customOrder` INTEGER NOT NULL, `autoUpdate` INTEGER NOT NULL, `update` INTEGER NOT NULL, PRIMARY KEY(`id`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"autoUpdate\",\n            \"columnName\": \"autoUpdate\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"update\",\n            \"columnName\": \"update\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"id\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"dictRules\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`name` TEXT NOT NULL, `urlRule` TEXT NOT NULL, `showRule` TEXT NOT NULL, `enabled` INTEGER NOT NULL DEFAULT 1, `sortNumber` INTEGER NOT NULL DEFAULT 0, PRIMARY KEY(`name`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"urlRule\",\n            \"columnName\": \"urlRule\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"showRule\",\n            \"columnName\": \"showRule\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enabled\",\n            \"columnName\": \"enabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"sortNumber\",\n            \"columnName\": \"sortNumber\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"name\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"keyboardAssists\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`type` INTEGER NOT NULL DEFAULT 0, `key` TEXT NOT NULL DEFAULT '', `value` TEXT NOT NULL DEFAULT '', `serialNo` INTEGER NOT NULL DEFAULT 0, PRIMARY KEY(`type`, `key`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"key\",\n            \"columnName\": \"key\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"value\",\n            \"columnName\": \"value\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"serialNo\",\n            \"columnName\": \"serialNo\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"type\",\n            \"key\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"servers\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `type` TEXT NOT NULL, `config` TEXT, `sortNumber` INTEGER NOT NULL, PRIMARY KEY(`id`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"config\",\n            \"columnName\": \"config\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"sortNumber\",\n            \"columnName\": \"sortNumber\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"id\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      }\n    ],\n    \"views\": [],\n    \"setupQueries\": [\n      \"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)\",\n      \"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '48855e5272b008e4c6b202a41654b575')\"\n    ]\n  }\n}"
  },
  {
    "path": "app/schemas/io.legado.app.data.AppDatabase/68.json",
    "content": "{\n  \"formatVersion\": 1,\n  \"database\": {\n    \"version\": 68,\n    \"identityHash\": \"9b6070f339f55e90e4cceaeb89b4a698\",\n    \"entities\": [\n      {\n        \"tableName\": \"books\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookUrl` TEXT NOT NULL DEFAULT '', `tocUrl` TEXT NOT NULL DEFAULT '', `origin` TEXT NOT NULL DEFAULT 'loc_book', `originName` TEXT NOT NULL DEFAULT '', `name` TEXT NOT NULL DEFAULT '', `author` TEXT NOT NULL DEFAULT '', `kind` TEXT, `customTag` TEXT, `coverUrl` TEXT, `customCoverUrl` TEXT, `intro` TEXT, `customIntro` TEXT, `charset` TEXT, `type` INTEGER NOT NULL DEFAULT 0, `group` INTEGER NOT NULL DEFAULT 0, `latestChapterTitle` TEXT, `latestChapterTime` INTEGER NOT NULL DEFAULT 0, `lastCheckTime` INTEGER NOT NULL DEFAULT 0, `lastCheckCount` INTEGER NOT NULL DEFAULT 0, `totalChapterNum` INTEGER NOT NULL DEFAULT 0, `durChapterTitle` TEXT, `durChapterIndex` INTEGER NOT NULL DEFAULT 0, `durChapterPos` INTEGER NOT NULL DEFAULT 0, `durChapterTime` INTEGER NOT NULL DEFAULT 0, `wordCount` TEXT, `canUpdate` INTEGER NOT NULL DEFAULT 1, `order` INTEGER NOT NULL DEFAULT 0, `originOrder` INTEGER NOT NULL DEFAULT 0, `variable` TEXT, `readConfig` TEXT, PRIMARY KEY(`bookUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"tocUrl\",\n            \"columnName\": \"tocUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"'loc_book'\"\n          },\n          {\n            \"fieldPath\": \"originName\",\n            \"columnName\": \"originName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"author\",\n            \"columnName\": \"author\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"kind\",\n            \"columnName\": \"kind\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customTag\",\n            \"columnName\": \"customTag\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"coverUrl\",\n            \"columnName\": \"coverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customCoverUrl\",\n            \"columnName\": \"customCoverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"intro\",\n            \"columnName\": \"intro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customIntro\",\n            \"columnName\": \"customIntro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"charset\",\n            \"columnName\": \"charset\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"group\",\n            \"columnName\": \"group\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"latestChapterTitle\",\n            \"columnName\": \"latestChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"latestChapterTime\",\n            \"columnName\": \"latestChapterTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"lastCheckTime\",\n            \"columnName\": \"lastCheckTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"lastCheckCount\",\n            \"columnName\": \"lastCheckCount\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"totalChapterNum\",\n            \"columnName\": \"totalChapterNum\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"durChapterTitle\",\n            \"columnName\": \"durChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"durChapterIndex\",\n            \"columnName\": \"durChapterIndex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"durChapterPos\",\n            \"columnName\": \"durChapterPos\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"durChapterTime\",\n            \"columnName\": \"durChapterTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"wordCount\",\n            \"columnName\": \"wordCount\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"canUpdate\",\n            \"columnName\": \"canUpdate\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"originOrder\",\n            \"columnName\": \"originOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"readConfig\",\n            \"columnName\": \"readConfig\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"bookUrl\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_books_name_author\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"name\",\n              \"author\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_books_name_author` ON `${TABLE_NAME}` (`name`, `author`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"book_groups\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`groupId` INTEGER NOT NULL, `groupName` TEXT NOT NULL, `cover` TEXT, `order` INTEGER NOT NULL, `enableRefresh` INTEGER NOT NULL DEFAULT 1, `show` INTEGER NOT NULL DEFAULT 1, `bookSort` INTEGER NOT NULL DEFAULT -1, PRIMARY KEY(`groupId`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"groupId\",\n            \"columnName\": \"groupId\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"groupName\",\n            \"columnName\": \"groupName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"cover\",\n            \"columnName\": \"cover\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enableRefresh\",\n            \"columnName\": \"enableRefresh\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"show\",\n            \"columnName\": \"show\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"bookSort\",\n            \"columnName\": \"bookSort\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"-1\"\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"groupId\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"book_sources\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookSourceUrl` TEXT NOT NULL, `bookSourceName` TEXT NOT NULL, `bookSourceGroup` TEXT, `bookSourceType` INTEGER NOT NULL, `bookUrlPattern` TEXT, `customOrder` INTEGER NOT NULL DEFAULT 0, `enabled` INTEGER NOT NULL DEFAULT 1, `enabledExplore` INTEGER NOT NULL DEFAULT 1, `jsLib` TEXT, `enabledCookieJar` INTEGER DEFAULT 0, `concurrentRate` TEXT, `header` TEXT, `loginUrl` TEXT, `loginUi` TEXT, `loginCheckJs` TEXT, `coverDecodeJs` TEXT, `bookSourceComment` TEXT, `variableComment` TEXT, `lastUpdateTime` INTEGER NOT NULL, `respondTime` INTEGER NOT NULL, `weight` INTEGER NOT NULL, `exploreUrl` TEXT, `exploreScreen` TEXT, `ruleExplore` TEXT, `searchUrl` TEXT, `ruleSearch` TEXT, `ruleBookInfo` TEXT, `ruleToc` TEXT, `ruleContent` TEXT, `ruleReview` TEXT, PRIMARY KEY(`bookSourceUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookSourceUrl\",\n            \"columnName\": \"bookSourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookSourceName\",\n            \"columnName\": \"bookSourceName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookSourceGroup\",\n            \"columnName\": \"bookSourceGroup\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"bookSourceType\",\n            \"columnName\": \"bookSourceType\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookUrlPattern\",\n            \"columnName\": \"bookUrlPattern\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"enabled\",\n            \"columnName\": \"enabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"enabledExplore\",\n            \"columnName\": \"enabledExplore\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"jsLib\",\n            \"columnName\": \"jsLib\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enabledCookieJar\",\n            \"columnName\": \"enabledCookieJar\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"concurrentRate\",\n            \"columnName\": \"concurrentRate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"header\",\n            \"columnName\": \"header\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUrl\",\n            \"columnName\": \"loginUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUi\",\n            \"columnName\": \"loginUi\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginCheckJs\",\n            \"columnName\": \"loginCheckJs\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"coverDecodeJs\",\n            \"columnName\": \"coverDecodeJs\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"bookSourceComment\",\n            \"columnName\": \"bookSourceComment\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"variableComment\",\n            \"columnName\": \"variableComment\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"lastUpdateTime\",\n            \"columnName\": \"lastUpdateTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"respondTime\",\n            \"columnName\": \"respondTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"weight\",\n            \"columnName\": \"weight\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"exploreUrl\",\n            \"columnName\": \"exploreUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"exploreScreen\",\n            \"columnName\": \"exploreScreen\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleExplore\",\n            \"columnName\": \"ruleExplore\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"searchUrl\",\n            \"columnName\": \"searchUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleSearch\",\n            \"columnName\": \"ruleSearch\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleBookInfo\",\n            \"columnName\": \"ruleBookInfo\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleToc\",\n            \"columnName\": \"ruleToc\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleContent\",\n            \"columnName\": \"ruleContent\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleReview\",\n            \"columnName\": \"ruleReview\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"bookSourceUrl\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_book_sources_bookSourceUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"bookSourceUrl\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_book_sources_bookSourceUrl` ON `${TABLE_NAME}` (`bookSourceUrl`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"chapters\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`url` TEXT NOT NULL, `title` TEXT NOT NULL, `isVolume` INTEGER NOT NULL, `baseUrl` TEXT NOT NULL, `bookUrl` TEXT NOT NULL, `index` INTEGER NOT NULL, `isVip` INTEGER NOT NULL, `isPay` INTEGER NOT NULL, `resourceUrl` TEXT, `tag` TEXT, `start` INTEGER, `end` INTEGER, `startFragmentId` TEXT, `endFragmentId` TEXT, `variable` TEXT, PRIMARY KEY(`url`, `bookUrl`), FOREIGN KEY(`bookUrl`) REFERENCES `books`(`bookUrl`) ON UPDATE NO ACTION ON DELETE CASCADE )\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"isVolume\",\n            \"columnName\": \"isVolume\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"baseUrl\",\n            \"columnName\": \"baseUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"index\",\n            \"columnName\": \"index\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"isVip\",\n            \"columnName\": \"isVip\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"isPay\",\n            \"columnName\": \"isPay\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"resourceUrl\",\n            \"columnName\": \"resourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"tag\",\n            \"columnName\": \"tag\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"start\",\n            \"columnName\": \"start\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"end\",\n            \"columnName\": \"end\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"startFragmentId\",\n            \"columnName\": \"startFragmentId\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"endFragmentId\",\n            \"columnName\": \"endFragmentId\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"url\",\n            \"bookUrl\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_chapters_bookUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"bookUrl\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_chapters_bookUrl` ON `${TABLE_NAME}` (`bookUrl`)\"\n          },\n          {\n            \"name\": \"index_chapters_bookUrl_index\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"bookUrl\",\n              \"index\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_chapters_bookUrl_index` ON `${TABLE_NAME}` (`bookUrl`, `index`)\"\n          }\n        ],\n        \"foreignKeys\": [\n          {\n            \"table\": \"books\",\n            \"onDelete\": \"CASCADE\",\n            \"onUpdate\": \"NO ACTION\",\n            \"columns\": [\n              \"bookUrl\"\n            ],\n            \"referencedColumns\": [\n              \"bookUrl\"\n            ]\n          }\n        ]\n      },\n      {\n        \"tableName\": \"replace_rules\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL DEFAULT '', `group` TEXT, `pattern` TEXT NOT NULL DEFAULT '', `replacement` TEXT NOT NULL DEFAULT '', `scope` TEXT, `scopeTitle` INTEGER NOT NULL DEFAULT 0, `scopeContent` INTEGER NOT NULL DEFAULT 1, `excludeScope` TEXT, `isEnabled` INTEGER NOT NULL DEFAULT 1, `isRegex` INTEGER NOT NULL DEFAULT 1, `timeoutMillisecond` INTEGER NOT NULL DEFAULT 3000, `sortOrder` INTEGER NOT NULL DEFAULT 0)\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"group\",\n            \"columnName\": \"group\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"pattern\",\n            \"columnName\": \"pattern\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"replacement\",\n            \"columnName\": \"replacement\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"scope\",\n            \"columnName\": \"scope\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"scopeTitle\",\n            \"columnName\": \"scopeTitle\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"scopeContent\",\n            \"columnName\": \"scopeContent\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"excludeScope\",\n            \"columnName\": \"excludeScope\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"isEnabled\",\n            \"columnName\": \"isEnabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"isRegex\",\n            \"columnName\": \"isRegex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"timeoutMillisecond\",\n            \"columnName\": \"timeoutMillisecond\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"3000\"\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"sortOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": true,\n          \"columnNames\": [\n            \"id\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_replace_rules_id\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"id\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_replace_rules_id` ON `${TABLE_NAME}` (`id`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"searchBooks\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookUrl` TEXT NOT NULL, `origin` TEXT NOT NULL, `originName` TEXT NOT NULL, `type` INTEGER NOT NULL, `name` TEXT NOT NULL, `author` TEXT NOT NULL, `kind` TEXT, `coverUrl` TEXT, `intro` TEXT, `wordCount` TEXT, `latestChapterTitle` TEXT, `tocUrl` TEXT NOT NULL, `time` INTEGER NOT NULL, `variable` TEXT, `originOrder` INTEGER NOT NULL, `chapterWordCountText` TEXT, `chapterWordCount` INTEGER NOT NULL DEFAULT -1, `respondTime` INTEGER NOT NULL DEFAULT -1, PRIMARY KEY(`bookUrl`), FOREIGN KEY(`origin`) REFERENCES `book_sources`(`bookSourceUrl`) ON UPDATE NO ACTION ON DELETE CASCADE )\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"originName\",\n            \"columnName\": \"originName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"author\",\n            \"columnName\": \"author\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"kind\",\n            \"columnName\": \"kind\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"coverUrl\",\n            \"columnName\": \"coverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"intro\",\n            \"columnName\": \"intro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"wordCount\",\n            \"columnName\": \"wordCount\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"latestChapterTitle\",\n            \"columnName\": \"latestChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"tocUrl\",\n            \"columnName\": \"tocUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"time\",\n            \"columnName\": \"time\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"originOrder\",\n            \"columnName\": \"originOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterWordCountText\",\n            \"columnName\": \"chapterWordCountText\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"chapterWordCount\",\n            \"columnName\": \"chapterWordCount\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"-1\"\n          },\n          {\n            \"fieldPath\": \"respondTime\",\n            \"columnName\": \"respondTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"-1\"\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"bookUrl\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_searchBooks_bookUrl\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"bookUrl\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_searchBooks_bookUrl` ON `${TABLE_NAME}` (`bookUrl`)\"\n          },\n          {\n            \"name\": \"index_searchBooks_origin\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"origin\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_searchBooks_origin` ON `${TABLE_NAME}` (`origin`)\"\n          }\n        ],\n        \"foreignKeys\": [\n          {\n            \"table\": \"book_sources\",\n            \"onDelete\": \"CASCADE\",\n            \"onUpdate\": \"NO ACTION\",\n            \"columns\": [\n              \"origin\"\n            ],\n            \"referencedColumns\": [\n              \"bookSourceUrl\"\n            ]\n          }\n        ]\n      },\n      {\n        \"tableName\": \"search_keywords\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`word` TEXT NOT NULL, `usage` INTEGER NOT NULL, `lastUseTime` INTEGER NOT NULL, PRIMARY KEY(`word`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"word\",\n            \"columnName\": \"word\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"usage\",\n            \"columnName\": \"usage\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"lastUseTime\",\n            \"columnName\": \"lastUseTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"word\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_search_keywords_word\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"word\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_search_keywords_word` ON `${TABLE_NAME}` (`word`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"cookies\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`url` TEXT NOT NULL, `cookie` TEXT NOT NULL, PRIMARY KEY(`url`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"cookie\",\n            \"columnName\": \"cookie\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"url\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_cookies_url\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"url\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_cookies_url` ON `${TABLE_NAME}` (`url`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssSources\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`sourceUrl` TEXT NOT NULL, `sourceName` TEXT NOT NULL, `sourceIcon` TEXT NOT NULL, `sourceGroup` TEXT, `sourceComment` TEXT, `enabled` INTEGER NOT NULL, `variableComment` TEXT, `jsLib` TEXT, `enabledCookieJar` INTEGER DEFAULT 0, `concurrentRate` TEXT, `header` TEXT, `loginUrl` TEXT, `loginUi` TEXT, `loginCheckJs` TEXT, `coverDecodeJs` TEXT, `sortUrl` TEXT, `singleUrl` INTEGER NOT NULL, `articleStyle` INTEGER NOT NULL DEFAULT 0, `ruleArticles` TEXT, `ruleNextPage` TEXT, `ruleTitle` TEXT, `rulePubDate` TEXT, `ruleDescription` TEXT, `ruleImage` TEXT, `ruleLink` TEXT, `ruleContent` TEXT, `contentWhitelist` TEXT, `contentBlacklist` TEXT, `style` TEXT, `enableJs` INTEGER NOT NULL DEFAULT 1, `loadWithBaseUrl` INTEGER NOT NULL DEFAULT 1, `injectJs` TEXT, `lastUpdateTime` INTEGER NOT NULL DEFAULT 0, `customOrder` INTEGER NOT NULL DEFAULT 0, PRIMARY KEY(`sourceUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"sourceUrl\",\n            \"columnName\": \"sourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceName\",\n            \"columnName\": \"sourceName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceIcon\",\n            \"columnName\": \"sourceIcon\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceGroup\",\n            \"columnName\": \"sourceGroup\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"sourceComment\",\n            \"columnName\": \"sourceComment\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enabled\",\n            \"columnName\": \"enabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"variableComment\",\n            \"columnName\": \"variableComment\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"jsLib\",\n            \"columnName\": \"jsLib\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enabledCookieJar\",\n            \"columnName\": \"enabledCookieJar\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"concurrentRate\",\n            \"columnName\": \"concurrentRate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"header\",\n            \"columnName\": \"header\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUrl\",\n            \"columnName\": \"loginUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUi\",\n            \"columnName\": \"loginUi\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginCheckJs\",\n            \"columnName\": \"loginCheckJs\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"coverDecodeJs\",\n            \"columnName\": \"coverDecodeJs\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"sortUrl\",\n            \"columnName\": \"sortUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"singleUrl\",\n            \"columnName\": \"singleUrl\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"articleStyle\",\n            \"columnName\": \"articleStyle\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"ruleArticles\",\n            \"columnName\": \"ruleArticles\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleNextPage\",\n            \"columnName\": \"ruleNextPage\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleTitle\",\n            \"columnName\": \"ruleTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"rulePubDate\",\n            \"columnName\": \"rulePubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleDescription\",\n            \"columnName\": \"ruleDescription\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleImage\",\n            \"columnName\": \"ruleImage\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleLink\",\n            \"columnName\": \"ruleLink\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleContent\",\n            \"columnName\": \"ruleContent\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"contentWhitelist\",\n            \"columnName\": \"contentWhitelist\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"contentBlacklist\",\n            \"columnName\": \"contentBlacklist\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"style\",\n            \"columnName\": \"style\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enableJs\",\n            \"columnName\": \"enableJs\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"loadWithBaseUrl\",\n            \"columnName\": \"loadWithBaseUrl\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"injectJs\",\n            \"columnName\": \"injectJs\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"lastUpdateTime\",\n            \"columnName\": \"lastUpdateTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"sourceUrl\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_rssSources_sourceUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"sourceUrl\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_rssSources_sourceUrl` ON `${TABLE_NAME}` (`sourceUrl`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"bookmarks\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`time` INTEGER NOT NULL, `bookName` TEXT NOT NULL, `bookAuthor` TEXT NOT NULL, `chapterIndex` INTEGER NOT NULL, `chapterPos` INTEGER NOT NULL, `chapterName` TEXT NOT NULL, `bookText` TEXT NOT NULL, `content` TEXT NOT NULL, PRIMARY KEY(`time`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"time\",\n            \"columnName\": \"time\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookName\",\n            \"columnName\": \"bookName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookAuthor\",\n            \"columnName\": \"bookAuthor\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterIndex\",\n            \"columnName\": \"chapterIndex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterPos\",\n            \"columnName\": \"chapterPos\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterName\",\n            \"columnName\": \"chapterName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookText\",\n            \"columnName\": \"bookText\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"time\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_bookmarks_bookName_bookAuthor\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"bookName\",\n              \"bookAuthor\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_bookmarks_bookName_bookAuthor` ON `${TABLE_NAME}` (`bookName`, `bookAuthor`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssArticles\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`origin` TEXT NOT NULL, `sort` TEXT NOT NULL, `title` TEXT NOT NULL, `order` INTEGER NOT NULL, `link` TEXT NOT NULL, `pubDate` TEXT, `description` TEXT, `content` TEXT, `image` TEXT, `read` INTEGER NOT NULL, `variable` TEXT, PRIMARY KEY(`origin`, `link`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sort\",\n            \"columnName\": \"sort\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"link\",\n            \"columnName\": \"link\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"pubDate\",\n            \"columnName\": \"pubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"description\",\n            \"columnName\": \"description\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"image\",\n            \"columnName\": \"image\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"read\",\n            \"columnName\": \"read\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"origin\",\n            \"link\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssReadRecords\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`record` TEXT NOT NULL, `read` INTEGER NOT NULL, PRIMARY KEY(`record`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"record\",\n            \"columnName\": \"record\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"read\",\n            \"columnName\": \"read\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"record\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssStars\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`origin` TEXT NOT NULL, `sort` TEXT NOT NULL, `title` TEXT NOT NULL, `starTime` INTEGER NOT NULL, `link` TEXT NOT NULL, `pubDate` TEXT, `description` TEXT, `content` TEXT, `image` TEXT, `variable` TEXT, PRIMARY KEY(`origin`, `link`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sort\",\n            \"columnName\": \"sort\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"starTime\",\n            \"columnName\": \"starTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"link\",\n            \"columnName\": \"link\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"pubDate\",\n            \"columnName\": \"pubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"description\",\n            \"columnName\": \"description\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"image\",\n            \"columnName\": \"image\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"origin\",\n            \"link\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"txtTocRules\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `rule` TEXT NOT NULL, `example` TEXT, `serialNumber` INTEGER NOT NULL, `enable` INTEGER NOT NULL, PRIMARY KEY(`id`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"rule\",\n            \"columnName\": \"rule\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"example\",\n            \"columnName\": \"example\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"serialNumber\",\n            \"columnName\": \"serialNumber\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enable\",\n            \"columnName\": \"enable\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"id\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"readRecord\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`deviceId` TEXT NOT NULL, `bookName` TEXT NOT NULL, `readTime` INTEGER NOT NULL DEFAULT 0, `lastRead` INTEGER NOT NULL DEFAULT 0, PRIMARY KEY(`deviceId`, `bookName`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"deviceId\",\n            \"columnName\": \"deviceId\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookName\",\n            \"columnName\": \"bookName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"readTime\",\n            \"columnName\": \"readTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"lastRead\",\n            \"columnName\": \"lastRead\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"deviceId\",\n            \"bookName\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"httpTTS\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `url` TEXT NOT NULL, `contentType` TEXT, `concurrentRate` TEXT DEFAULT '0', `loginUrl` TEXT, `loginUi` TEXT, `header` TEXT, `jsLib` TEXT, `enabledCookieJar` INTEGER DEFAULT 0, `loginCheckJs` TEXT, `lastUpdateTime` INTEGER NOT NULL DEFAULT 0, PRIMARY KEY(`id`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"contentType\",\n            \"columnName\": \"contentType\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"concurrentRate\",\n            \"columnName\": \"concurrentRate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false,\n            \"defaultValue\": \"'0'\"\n          },\n          {\n            \"fieldPath\": \"loginUrl\",\n            \"columnName\": \"loginUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUi\",\n            \"columnName\": \"loginUi\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"header\",\n            \"columnName\": \"header\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"jsLib\",\n            \"columnName\": \"jsLib\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enabledCookieJar\",\n            \"columnName\": \"enabledCookieJar\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"loginCheckJs\",\n            \"columnName\": \"loginCheckJs\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"lastUpdateTime\",\n            \"columnName\": \"lastUpdateTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"id\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"caches\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`key` TEXT NOT NULL, `value` TEXT, `deadline` INTEGER NOT NULL, PRIMARY KEY(`key`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"key\",\n            \"columnName\": \"key\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"value\",\n            \"columnName\": \"value\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"deadline\",\n            \"columnName\": \"deadline\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"key\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_caches_key\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"key\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_caches_key` ON `${TABLE_NAME}` (`key`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"ruleSubs\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `url` TEXT NOT NULL, `type` INTEGER NOT NULL, `customOrder` INTEGER NOT NULL, `autoUpdate` INTEGER NOT NULL, `update` INTEGER NOT NULL, PRIMARY KEY(`id`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"autoUpdate\",\n            \"columnName\": \"autoUpdate\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"update\",\n            \"columnName\": \"update\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"id\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"dictRules\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`name` TEXT NOT NULL, `urlRule` TEXT NOT NULL, `showRule` TEXT NOT NULL, `enabled` INTEGER NOT NULL DEFAULT 1, `sortNumber` INTEGER NOT NULL DEFAULT 0, PRIMARY KEY(`name`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"urlRule\",\n            \"columnName\": \"urlRule\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"showRule\",\n            \"columnName\": \"showRule\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enabled\",\n            \"columnName\": \"enabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"sortNumber\",\n            \"columnName\": \"sortNumber\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"name\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"keyboardAssists\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`type` INTEGER NOT NULL DEFAULT 0, `key` TEXT NOT NULL DEFAULT '', `value` TEXT NOT NULL DEFAULT '', `serialNo` INTEGER NOT NULL DEFAULT 0, PRIMARY KEY(`type`, `key`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"key\",\n            \"columnName\": \"key\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"value\",\n            \"columnName\": \"value\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"serialNo\",\n            \"columnName\": \"serialNo\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"type\",\n            \"key\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"servers\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `type` TEXT NOT NULL, `config` TEXT, `sortNumber` INTEGER NOT NULL, PRIMARY KEY(`id`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"config\",\n            \"columnName\": \"config\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"sortNumber\",\n            \"columnName\": \"sortNumber\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"id\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      }\n    ],\n    \"views\": [],\n    \"setupQueries\": [\n      \"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)\",\n      \"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '9b6070f339f55e90e4cceaeb89b4a698')\"\n    ]\n  }\n}"
  },
  {
    "path": "app/schemas/io.legado.app.data.AppDatabase/69.json",
    "content": "{\n  \"formatVersion\": 1,\n  \"database\": {\n    \"version\": 69,\n    \"identityHash\": \"9b6070f339f55e90e4cceaeb89b4a698\",\n    \"entities\": [\n      {\n        \"tableName\": \"books\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookUrl` TEXT NOT NULL DEFAULT '', `tocUrl` TEXT NOT NULL DEFAULT '', `origin` TEXT NOT NULL DEFAULT 'loc_book', `originName` TEXT NOT NULL DEFAULT '', `name` TEXT NOT NULL DEFAULT '', `author` TEXT NOT NULL DEFAULT '', `kind` TEXT, `customTag` TEXT, `coverUrl` TEXT, `customCoverUrl` TEXT, `intro` TEXT, `customIntro` TEXT, `charset` TEXT, `type` INTEGER NOT NULL DEFAULT 0, `group` INTEGER NOT NULL DEFAULT 0, `latestChapterTitle` TEXT, `latestChapterTime` INTEGER NOT NULL DEFAULT 0, `lastCheckTime` INTEGER NOT NULL DEFAULT 0, `lastCheckCount` INTEGER NOT NULL DEFAULT 0, `totalChapterNum` INTEGER NOT NULL DEFAULT 0, `durChapterTitle` TEXT, `durChapterIndex` INTEGER NOT NULL DEFAULT 0, `durChapterPos` INTEGER NOT NULL DEFAULT 0, `durChapterTime` INTEGER NOT NULL DEFAULT 0, `wordCount` TEXT, `canUpdate` INTEGER NOT NULL DEFAULT 1, `order` INTEGER NOT NULL DEFAULT 0, `originOrder` INTEGER NOT NULL DEFAULT 0, `variable` TEXT, `readConfig` TEXT, PRIMARY KEY(`bookUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"tocUrl\",\n            \"columnName\": \"tocUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"'loc_book'\"\n          },\n          {\n            \"fieldPath\": \"originName\",\n            \"columnName\": \"originName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"author\",\n            \"columnName\": \"author\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"kind\",\n            \"columnName\": \"kind\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customTag\",\n            \"columnName\": \"customTag\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"coverUrl\",\n            \"columnName\": \"coverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customCoverUrl\",\n            \"columnName\": \"customCoverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"intro\",\n            \"columnName\": \"intro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customIntro\",\n            \"columnName\": \"customIntro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"charset\",\n            \"columnName\": \"charset\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"group\",\n            \"columnName\": \"group\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"latestChapterTitle\",\n            \"columnName\": \"latestChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"latestChapterTime\",\n            \"columnName\": \"latestChapterTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"lastCheckTime\",\n            \"columnName\": \"lastCheckTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"lastCheckCount\",\n            \"columnName\": \"lastCheckCount\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"totalChapterNum\",\n            \"columnName\": \"totalChapterNum\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"durChapterTitle\",\n            \"columnName\": \"durChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"durChapterIndex\",\n            \"columnName\": \"durChapterIndex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"durChapterPos\",\n            \"columnName\": \"durChapterPos\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"durChapterTime\",\n            \"columnName\": \"durChapterTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"wordCount\",\n            \"columnName\": \"wordCount\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"canUpdate\",\n            \"columnName\": \"canUpdate\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"originOrder\",\n            \"columnName\": \"originOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"readConfig\",\n            \"columnName\": \"readConfig\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"bookUrl\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_books_name_author\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"name\",\n              \"author\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_books_name_author` ON `${TABLE_NAME}` (`name`, `author`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"book_groups\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`groupId` INTEGER NOT NULL, `groupName` TEXT NOT NULL, `cover` TEXT, `order` INTEGER NOT NULL, `enableRefresh` INTEGER NOT NULL DEFAULT 1, `show` INTEGER NOT NULL DEFAULT 1, `bookSort` INTEGER NOT NULL DEFAULT -1, PRIMARY KEY(`groupId`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"groupId\",\n            \"columnName\": \"groupId\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"groupName\",\n            \"columnName\": \"groupName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"cover\",\n            \"columnName\": \"cover\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enableRefresh\",\n            \"columnName\": \"enableRefresh\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"show\",\n            \"columnName\": \"show\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"bookSort\",\n            \"columnName\": \"bookSort\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"-1\"\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"groupId\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"book_sources\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookSourceUrl` TEXT NOT NULL, `bookSourceName` TEXT NOT NULL, `bookSourceGroup` TEXT, `bookSourceType` INTEGER NOT NULL, `bookUrlPattern` TEXT, `customOrder` INTEGER NOT NULL DEFAULT 0, `enabled` INTEGER NOT NULL DEFAULT 1, `enabledExplore` INTEGER NOT NULL DEFAULT 1, `jsLib` TEXT, `enabledCookieJar` INTEGER DEFAULT 0, `concurrentRate` TEXT, `header` TEXT, `loginUrl` TEXT, `loginUi` TEXT, `loginCheckJs` TEXT, `coverDecodeJs` TEXT, `bookSourceComment` TEXT, `variableComment` TEXT, `lastUpdateTime` INTEGER NOT NULL, `respondTime` INTEGER NOT NULL, `weight` INTEGER NOT NULL, `exploreUrl` TEXT, `exploreScreen` TEXT, `ruleExplore` TEXT, `searchUrl` TEXT, `ruleSearch` TEXT, `ruleBookInfo` TEXT, `ruleToc` TEXT, `ruleContent` TEXT, `ruleReview` TEXT, PRIMARY KEY(`bookSourceUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookSourceUrl\",\n            \"columnName\": \"bookSourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookSourceName\",\n            \"columnName\": \"bookSourceName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookSourceGroup\",\n            \"columnName\": \"bookSourceGroup\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"bookSourceType\",\n            \"columnName\": \"bookSourceType\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookUrlPattern\",\n            \"columnName\": \"bookUrlPattern\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"enabled\",\n            \"columnName\": \"enabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"enabledExplore\",\n            \"columnName\": \"enabledExplore\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"jsLib\",\n            \"columnName\": \"jsLib\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enabledCookieJar\",\n            \"columnName\": \"enabledCookieJar\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"concurrentRate\",\n            \"columnName\": \"concurrentRate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"header\",\n            \"columnName\": \"header\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUrl\",\n            \"columnName\": \"loginUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUi\",\n            \"columnName\": \"loginUi\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginCheckJs\",\n            \"columnName\": \"loginCheckJs\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"coverDecodeJs\",\n            \"columnName\": \"coverDecodeJs\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"bookSourceComment\",\n            \"columnName\": \"bookSourceComment\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"variableComment\",\n            \"columnName\": \"variableComment\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"lastUpdateTime\",\n            \"columnName\": \"lastUpdateTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"respondTime\",\n            \"columnName\": \"respondTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"weight\",\n            \"columnName\": \"weight\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"exploreUrl\",\n            \"columnName\": \"exploreUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"exploreScreen\",\n            \"columnName\": \"exploreScreen\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleExplore\",\n            \"columnName\": \"ruleExplore\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"searchUrl\",\n            \"columnName\": \"searchUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleSearch\",\n            \"columnName\": \"ruleSearch\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleBookInfo\",\n            \"columnName\": \"ruleBookInfo\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleToc\",\n            \"columnName\": \"ruleToc\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleContent\",\n            \"columnName\": \"ruleContent\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleReview\",\n            \"columnName\": \"ruleReview\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"bookSourceUrl\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_book_sources_bookSourceUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"bookSourceUrl\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_book_sources_bookSourceUrl` ON `${TABLE_NAME}` (`bookSourceUrl`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"chapters\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`url` TEXT NOT NULL, `title` TEXT NOT NULL, `isVolume` INTEGER NOT NULL, `baseUrl` TEXT NOT NULL, `bookUrl` TEXT NOT NULL, `index` INTEGER NOT NULL, `isVip` INTEGER NOT NULL, `isPay` INTEGER NOT NULL, `resourceUrl` TEXT, `tag` TEXT, `start` INTEGER, `end` INTEGER, `startFragmentId` TEXT, `endFragmentId` TEXT, `variable` TEXT, PRIMARY KEY(`url`, `bookUrl`), FOREIGN KEY(`bookUrl`) REFERENCES `books`(`bookUrl`) ON UPDATE NO ACTION ON DELETE CASCADE )\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"isVolume\",\n            \"columnName\": \"isVolume\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"baseUrl\",\n            \"columnName\": \"baseUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"index\",\n            \"columnName\": \"index\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"isVip\",\n            \"columnName\": \"isVip\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"isPay\",\n            \"columnName\": \"isPay\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"resourceUrl\",\n            \"columnName\": \"resourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"tag\",\n            \"columnName\": \"tag\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"start\",\n            \"columnName\": \"start\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"end\",\n            \"columnName\": \"end\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"startFragmentId\",\n            \"columnName\": \"startFragmentId\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"endFragmentId\",\n            \"columnName\": \"endFragmentId\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"url\",\n            \"bookUrl\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_chapters_bookUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"bookUrl\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_chapters_bookUrl` ON `${TABLE_NAME}` (`bookUrl`)\"\n          },\n          {\n            \"name\": \"index_chapters_bookUrl_index\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"bookUrl\",\n              \"index\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_chapters_bookUrl_index` ON `${TABLE_NAME}` (`bookUrl`, `index`)\"\n          }\n        ],\n        \"foreignKeys\": [\n          {\n            \"table\": \"books\",\n            \"onDelete\": \"CASCADE\",\n            \"onUpdate\": \"NO ACTION\",\n            \"columns\": [\n              \"bookUrl\"\n            ],\n            \"referencedColumns\": [\n              \"bookUrl\"\n            ]\n          }\n        ]\n      },\n      {\n        \"tableName\": \"replace_rules\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL DEFAULT '', `group` TEXT, `pattern` TEXT NOT NULL DEFAULT '', `replacement` TEXT NOT NULL DEFAULT '', `scope` TEXT, `scopeTitle` INTEGER NOT NULL DEFAULT 0, `scopeContent` INTEGER NOT NULL DEFAULT 1, `excludeScope` TEXT, `isEnabled` INTEGER NOT NULL DEFAULT 1, `isRegex` INTEGER NOT NULL DEFAULT 1, `timeoutMillisecond` INTEGER NOT NULL DEFAULT 3000, `sortOrder` INTEGER NOT NULL DEFAULT 0)\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"group\",\n            \"columnName\": \"group\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"pattern\",\n            \"columnName\": \"pattern\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"replacement\",\n            \"columnName\": \"replacement\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"scope\",\n            \"columnName\": \"scope\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"scopeTitle\",\n            \"columnName\": \"scopeTitle\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"scopeContent\",\n            \"columnName\": \"scopeContent\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"excludeScope\",\n            \"columnName\": \"excludeScope\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"isEnabled\",\n            \"columnName\": \"isEnabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"isRegex\",\n            \"columnName\": \"isRegex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"timeoutMillisecond\",\n            \"columnName\": \"timeoutMillisecond\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"3000\"\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"sortOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": true,\n          \"columnNames\": [\n            \"id\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_replace_rules_id\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"id\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_replace_rules_id` ON `${TABLE_NAME}` (`id`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"searchBooks\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookUrl` TEXT NOT NULL, `origin` TEXT NOT NULL, `originName` TEXT NOT NULL, `type` INTEGER NOT NULL, `name` TEXT NOT NULL, `author` TEXT NOT NULL, `kind` TEXT, `coverUrl` TEXT, `intro` TEXT, `wordCount` TEXT, `latestChapterTitle` TEXT, `tocUrl` TEXT NOT NULL, `time` INTEGER NOT NULL, `variable` TEXT, `originOrder` INTEGER NOT NULL, `chapterWordCountText` TEXT, `chapterWordCount` INTEGER NOT NULL DEFAULT -1, `respondTime` INTEGER NOT NULL DEFAULT -1, PRIMARY KEY(`bookUrl`), FOREIGN KEY(`origin`) REFERENCES `book_sources`(`bookSourceUrl`) ON UPDATE NO ACTION ON DELETE CASCADE )\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"originName\",\n            \"columnName\": \"originName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"author\",\n            \"columnName\": \"author\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"kind\",\n            \"columnName\": \"kind\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"coverUrl\",\n            \"columnName\": \"coverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"intro\",\n            \"columnName\": \"intro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"wordCount\",\n            \"columnName\": \"wordCount\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"latestChapterTitle\",\n            \"columnName\": \"latestChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"tocUrl\",\n            \"columnName\": \"tocUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"time\",\n            \"columnName\": \"time\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"originOrder\",\n            \"columnName\": \"originOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterWordCountText\",\n            \"columnName\": \"chapterWordCountText\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"chapterWordCount\",\n            \"columnName\": \"chapterWordCount\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"-1\"\n          },\n          {\n            \"fieldPath\": \"respondTime\",\n            \"columnName\": \"respondTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"-1\"\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"bookUrl\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_searchBooks_bookUrl\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"bookUrl\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_searchBooks_bookUrl` ON `${TABLE_NAME}` (`bookUrl`)\"\n          },\n          {\n            \"name\": \"index_searchBooks_origin\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"origin\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_searchBooks_origin` ON `${TABLE_NAME}` (`origin`)\"\n          }\n        ],\n        \"foreignKeys\": [\n          {\n            \"table\": \"book_sources\",\n            \"onDelete\": \"CASCADE\",\n            \"onUpdate\": \"NO ACTION\",\n            \"columns\": [\n              \"origin\"\n            ],\n            \"referencedColumns\": [\n              \"bookSourceUrl\"\n            ]\n          }\n        ]\n      },\n      {\n        \"tableName\": \"search_keywords\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`word` TEXT NOT NULL, `usage` INTEGER NOT NULL, `lastUseTime` INTEGER NOT NULL, PRIMARY KEY(`word`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"word\",\n            \"columnName\": \"word\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"usage\",\n            \"columnName\": \"usage\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"lastUseTime\",\n            \"columnName\": \"lastUseTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"word\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_search_keywords_word\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"word\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_search_keywords_word` ON `${TABLE_NAME}` (`word`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"cookies\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`url` TEXT NOT NULL, `cookie` TEXT NOT NULL, PRIMARY KEY(`url`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"cookie\",\n            \"columnName\": \"cookie\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"url\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_cookies_url\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"url\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_cookies_url` ON `${TABLE_NAME}` (`url`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssSources\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`sourceUrl` TEXT NOT NULL, `sourceName` TEXT NOT NULL, `sourceIcon` TEXT NOT NULL, `sourceGroup` TEXT, `sourceComment` TEXT, `enabled` INTEGER NOT NULL, `variableComment` TEXT, `jsLib` TEXT, `enabledCookieJar` INTEGER DEFAULT 0, `concurrentRate` TEXT, `header` TEXT, `loginUrl` TEXT, `loginUi` TEXT, `loginCheckJs` TEXT, `coverDecodeJs` TEXT, `sortUrl` TEXT, `singleUrl` INTEGER NOT NULL, `articleStyle` INTEGER NOT NULL DEFAULT 0, `ruleArticles` TEXT, `ruleNextPage` TEXT, `ruleTitle` TEXT, `rulePubDate` TEXT, `ruleDescription` TEXT, `ruleImage` TEXT, `ruleLink` TEXT, `ruleContent` TEXT, `contentWhitelist` TEXT, `contentBlacklist` TEXT, `style` TEXT, `enableJs` INTEGER NOT NULL DEFAULT 1, `loadWithBaseUrl` INTEGER NOT NULL DEFAULT 1, `injectJs` TEXT, `lastUpdateTime` INTEGER NOT NULL DEFAULT 0, `customOrder` INTEGER NOT NULL DEFAULT 0, PRIMARY KEY(`sourceUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"sourceUrl\",\n            \"columnName\": \"sourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceName\",\n            \"columnName\": \"sourceName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceIcon\",\n            \"columnName\": \"sourceIcon\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceGroup\",\n            \"columnName\": \"sourceGroup\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"sourceComment\",\n            \"columnName\": \"sourceComment\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enabled\",\n            \"columnName\": \"enabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"variableComment\",\n            \"columnName\": \"variableComment\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"jsLib\",\n            \"columnName\": \"jsLib\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enabledCookieJar\",\n            \"columnName\": \"enabledCookieJar\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"concurrentRate\",\n            \"columnName\": \"concurrentRate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"header\",\n            \"columnName\": \"header\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUrl\",\n            \"columnName\": \"loginUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUi\",\n            \"columnName\": \"loginUi\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginCheckJs\",\n            \"columnName\": \"loginCheckJs\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"coverDecodeJs\",\n            \"columnName\": \"coverDecodeJs\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"sortUrl\",\n            \"columnName\": \"sortUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"singleUrl\",\n            \"columnName\": \"singleUrl\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"articleStyle\",\n            \"columnName\": \"articleStyle\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"ruleArticles\",\n            \"columnName\": \"ruleArticles\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleNextPage\",\n            \"columnName\": \"ruleNextPage\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleTitle\",\n            \"columnName\": \"ruleTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"rulePubDate\",\n            \"columnName\": \"rulePubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleDescription\",\n            \"columnName\": \"ruleDescription\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleImage\",\n            \"columnName\": \"ruleImage\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleLink\",\n            \"columnName\": \"ruleLink\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleContent\",\n            \"columnName\": \"ruleContent\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"contentWhitelist\",\n            \"columnName\": \"contentWhitelist\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"contentBlacklist\",\n            \"columnName\": \"contentBlacklist\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"style\",\n            \"columnName\": \"style\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enableJs\",\n            \"columnName\": \"enableJs\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"loadWithBaseUrl\",\n            \"columnName\": \"loadWithBaseUrl\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"injectJs\",\n            \"columnName\": \"injectJs\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"lastUpdateTime\",\n            \"columnName\": \"lastUpdateTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"sourceUrl\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_rssSources_sourceUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"sourceUrl\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_rssSources_sourceUrl` ON `${TABLE_NAME}` (`sourceUrl`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"bookmarks\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`time` INTEGER NOT NULL, `bookName` TEXT NOT NULL, `bookAuthor` TEXT NOT NULL, `chapterIndex` INTEGER NOT NULL, `chapterPos` INTEGER NOT NULL, `chapterName` TEXT NOT NULL, `bookText` TEXT NOT NULL, `content` TEXT NOT NULL, PRIMARY KEY(`time`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"time\",\n            \"columnName\": \"time\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookName\",\n            \"columnName\": \"bookName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookAuthor\",\n            \"columnName\": \"bookAuthor\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterIndex\",\n            \"columnName\": \"chapterIndex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterPos\",\n            \"columnName\": \"chapterPos\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterName\",\n            \"columnName\": \"chapterName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookText\",\n            \"columnName\": \"bookText\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"time\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_bookmarks_bookName_bookAuthor\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"bookName\",\n              \"bookAuthor\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_bookmarks_bookName_bookAuthor` ON `${TABLE_NAME}` (`bookName`, `bookAuthor`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssArticles\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`origin` TEXT NOT NULL, `sort` TEXT NOT NULL, `title` TEXT NOT NULL, `order` INTEGER NOT NULL, `link` TEXT NOT NULL, `pubDate` TEXT, `description` TEXT, `content` TEXT, `image` TEXT, `read` INTEGER NOT NULL, `variable` TEXT, PRIMARY KEY(`origin`, `link`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sort\",\n            \"columnName\": \"sort\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"link\",\n            \"columnName\": \"link\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"pubDate\",\n            \"columnName\": \"pubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"description\",\n            \"columnName\": \"description\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"image\",\n            \"columnName\": \"image\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"read\",\n            \"columnName\": \"read\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"origin\",\n            \"link\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssReadRecords\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`record` TEXT NOT NULL, `read` INTEGER NOT NULL, PRIMARY KEY(`record`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"record\",\n            \"columnName\": \"record\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"read\",\n            \"columnName\": \"read\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"record\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssStars\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`origin` TEXT NOT NULL, `sort` TEXT NOT NULL, `title` TEXT NOT NULL, `starTime` INTEGER NOT NULL, `link` TEXT NOT NULL, `pubDate` TEXT, `description` TEXT, `content` TEXT, `image` TEXT, `variable` TEXT, PRIMARY KEY(`origin`, `link`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sort\",\n            \"columnName\": \"sort\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"starTime\",\n            \"columnName\": \"starTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"link\",\n            \"columnName\": \"link\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"pubDate\",\n            \"columnName\": \"pubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"description\",\n            \"columnName\": \"description\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"image\",\n            \"columnName\": \"image\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"origin\",\n            \"link\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"txtTocRules\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `rule` TEXT NOT NULL, `example` TEXT, `serialNumber` INTEGER NOT NULL, `enable` INTEGER NOT NULL, PRIMARY KEY(`id`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"rule\",\n            \"columnName\": \"rule\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"example\",\n            \"columnName\": \"example\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"serialNumber\",\n            \"columnName\": \"serialNumber\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enable\",\n            \"columnName\": \"enable\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"id\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"readRecord\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`deviceId` TEXT NOT NULL, `bookName` TEXT NOT NULL, `readTime` INTEGER NOT NULL DEFAULT 0, `lastRead` INTEGER NOT NULL DEFAULT 0, PRIMARY KEY(`deviceId`, `bookName`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"deviceId\",\n            \"columnName\": \"deviceId\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookName\",\n            \"columnName\": \"bookName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"readTime\",\n            \"columnName\": \"readTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"lastRead\",\n            \"columnName\": \"lastRead\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"deviceId\",\n            \"bookName\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"httpTTS\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `url` TEXT NOT NULL, `contentType` TEXT, `concurrentRate` TEXT DEFAULT '0', `loginUrl` TEXT, `loginUi` TEXT, `header` TEXT, `jsLib` TEXT, `enabledCookieJar` INTEGER DEFAULT 0, `loginCheckJs` TEXT, `lastUpdateTime` INTEGER NOT NULL DEFAULT 0, PRIMARY KEY(`id`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"contentType\",\n            \"columnName\": \"contentType\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"concurrentRate\",\n            \"columnName\": \"concurrentRate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false,\n            \"defaultValue\": \"'0'\"\n          },\n          {\n            \"fieldPath\": \"loginUrl\",\n            \"columnName\": \"loginUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUi\",\n            \"columnName\": \"loginUi\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"header\",\n            \"columnName\": \"header\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"jsLib\",\n            \"columnName\": \"jsLib\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enabledCookieJar\",\n            \"columnName\": \"enabledCookieJar\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"loginCheckJs\",\n            \"columnName\": \"loginCheckJs\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"lastUpdateTime\",\n            \"columnName\": \"lastUpdateTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"id\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"caches\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`key` TEXT NOT NULL, `value` TEXT, `deadline` INTEGER NOT NULL, PRIMARY KEY(`key`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"key\",\n            \"columnName\": \"key\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"value\",\n            \"columnName\": \"value\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"deadline\",\n            \"columnName\": \"deadline\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"key\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_caches_key\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"key\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_caches_key` ON `${TABLE_NAME}` (`key`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"ruleSubs\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `url` TEXT NOT NULL, `type` INTEGER NOT NULL, `customOrder` INTEGER NOT NULL, `autoUpdate` INTEGER NOT NULL, `update` INTEGER NOT NULL, PRIMARY KEY(`id`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"autoUpdate\",\n            \"columnName\": \"autoUpdate\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"update\",\n            \"columnName\": \"update\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"id\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"dictRules\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`name` TEXT NOT NULL, `urlRule` TEXT NOT NULL, `showRule` TEXT NOT NULL, `enabled` INTEGER NOT NULL DEFAULT 1, `sortNumber` INTEGER NOT NULL DEFAULT 0, PRIMARY KEY(`name`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"urlRule\",\n            \"columnName\": \"urlRule\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"showRule\",\n            \"columnName\": \"showRule\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enabled\",\n            \"columnName\": \"enabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"sortNumber\",\n            \"columnName\": \"sortNumber\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"name\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"keyboardAssists\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`type` INTEGER NOT NULL DEFAULT 0, `key` TEXT NOT NULL DEFAULT '', `value` TEXT NOT NULL DEFAULT '', `serialNo` INTEGER NOT NULL DEFAULT 0, PRIMARY KEY(`type`, `key`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"key\",\n            \"columnName\": \"key\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"value\",\n            \"columnName\": \"value\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"serialNo\",\n            \"columnName\": \"serialNo\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"type\",\n            \"key\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"servers\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `type` TEXT NOT NULL, `config` TEXT, `sortNumber` INTEGER NOT NULL, PRIMARY KEY(`id`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"config\",\n            \"columnName\": \"config\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"sortNumber\",\n            \"columnName\": \"sortNumber\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"id\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      }\n    ],\n    \"views\": [],\n    \"setupQueries\": [\n      \"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)\",\n      \"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '9b6070f339f55e90e4cceaeb89b4a698')\"\n    ]\n  }\n}"
  },
  {
    "path": "app/schemas/io.legado.app.data.AppDatabase/7.json",
    "content": "{\n  \"formatVersion\": 1,\n  \"database\": {\n    \"version\": 7,\n    \"identityHash\": \"af70ea583587e17c968d29f41bb3c0d6\",\n    \"entities\": [\n      {\n        \"tableName\": \"books\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookUrl` TEXT NOT NULL, `tocUrl` TEXT NOT NULL, `origin` TEXT NOT NULL, `originName` TEXT NOT NULL, `name` TEXT NOT NULL, `author` TEXT NOT NULL, `kind` TEXT, `customTag` TEXT, `coverUrl` TEXT, `customCoverUrl` TEXT, `intro` TEXT, `customIntro` TEXT, `charset` TEXT, `type` INTEGER NOT NULL, `group` INTEGER NOT NULL, `latestChapterTitle` TEXT, `latestChapterTime` INTEGER NOT NULL, `lastCheckTime` INTEGER NOT NULL, `lastCheckCount` INTEGER NOT NULL, `totalChapterNum` INTEGER NOT NULL, `durChapterTitle` TEXT, `durChapterIndex` INTEGER NOT NULL, `durChapterPos` INTEGER NOT NULL, `durChapterTime` INTEGER NOT NULL, `wordCount` TEXT, `canUpdate` INTEGER NOT NULL, `order` INTEGER NOT NULL, `originOrder` INTEGER NOT NULL, `useReplaceRule` INTEGER NOT NULL, `variable` TEXT, PRIMARY KEY(`bookUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"tocUrl\",\n            \"columnName\": \"tocUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"originName\",\n            \"columnName\": \"originName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"author\",\n            \"columnName\": \"author\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"kind\",\n            \"columnName\": \"kind\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customTag\",\n            \"columnName\": \"customTag\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"coverUrl\",\n            \"columnName\": \"coverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customCoverUrl\",\n            \"columnName\": \"customCoverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"intro\",\n            \"columnName\": \"intro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customIntro\",\n            \"columnName\": \"customIntro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"charset\",\n            \"columnName\": \"charset\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"group\",\n            \"columnName\": \"group\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"latestChapterTitle\",\n            \"columnName\": \"latestChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"latestChapterTime\",\n            \"columnName\": \"latestChapterTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"lastCheckTime\",\n            \"columnName\": \"lastCheckTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"lastCheckCount\",\n            \"columnName\": \"lastCheckCount\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"totalChapterNum\",\n            \"columnName\": \"totalChapterNum\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"durChapterTitle\",\n            \"columnName\": \"durChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"durChapterIndex\",\n            \"columnName\": \"durChapterIndex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"durChapterPos\",\n            \"columnName\": \"durChapterPos\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"durChapterTime\",\n            \"columnName\": \"durChapterTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"wordCount\",\n            \"columnName\": \"wordCount\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"canUpdate\",\n            \"columnName\": \"canUpdate\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"originOrder\",\n            \"columnName\": \"originOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"useReplaceRule\",\n            \"columnName\": \"useReplaceRule\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"bookUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_books_bookUrl\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"bookUrl\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_books_bookUrl` ON `${TABLE_NAME}` (`bookUrl`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"book_groups\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`groupId` INTEGER NOT NULL, `groupName` TEXT NOT NULL, `order` INTEGER NOT NULL, PRIMARY KEY(`groupId`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"groupId\",\n            \"columnName\": \"groupId\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"groupName\",\n            \"columnName\": \"groupName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"groupId\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"book_sources\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookSourceName` TEXT NOT NULL, `bookSourceGroup` TEXT, `bookSourceUrl` TEXT NOT NULL, `bookSourceType` INTEGER NOT NULL, `bookUrlPattern` TEXT, `customOrder` INTEGER NOT NULL, `enabled` INTEGER NOT NULL, `enabledExplore` INTEGER NOT NULL, `header` TEXT, `loginUrl` TEXT, `lastUpdateTime` INTEGER NOT NULL, `weight` INTEGER NOT NULL, `exploreUrl` TEXT, `ruleExplore` TEXT, `searchUrl` TEXT, `ruleSearch` TEXT, `ruleBookInfo` TEXT, `ruleToc` TEXT, `ruleContent` TEXT, PRIMARY KEY(`bookSourceUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookSourceName\",\n            \"columnName\": \"bookSourceName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookSourceGroup\",\n            \"columnName\": \"bookSourceGroup\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"bookSourceUrl\",\n            \"columnName\": \"bookSourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookSourceType\",\n            \"columnName\": \"bookSourceType\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookUrlPattern\",\n            \"columnName\": \"bookUrlPattern\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enabled\",\n            \"columnName\": \"enabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enabledExplore\",\n            \"columnName\": \"enabledExplore\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"header\",\n            \"columnName\": \"header\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUrl\",\n            \"columnName\": \"loginUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"lastUpdateTime\",\n            \"columnName\": \"lastUpdateTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"weight\",\n            \"columnName\": \"weight\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"exploreUrl\",\n            \"columnName\": \"exploreUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleExplore\",\n            \"columnName\": \"ruleExplore\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"searchUrl\",\n            \"columnName\": \"searchUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleSearch\",\n            \"columnName\": \"ruleSearch\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleBookInfo\",\n            \"columnName\": \"ruleBookInfo\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleToc\",\n            \"columnName\": \"ruleToc\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleContent\",\n            \"columnName\": \"ruleContent\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"bookSourceUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_book_sources_bookSourceUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"bookSourceUrl\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_book_sources_bookSourceUrl` ON `${TABLE_NAME}` (`bookSourceUrl`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"chapters\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`url` TEXT NOT NULL, `title` TEXT NOT NULL, `bookUrl` TEXT NOT NULL, `index` INTEGER NOT NULL, `resourceUrl` TEXT, `tag` TEXT, `start` INTEGER, `end` INTEGER, `variable` TEXT, PRIMARY KEY(`url`, `bookUrl`), FOREIGN KEY(`bookUrl`) REFERENCES `books`(`bookUrl`) ON UPDATE NO ACTION ON DELETE CASCADE )\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"index\",\n            \"columnName\": \"index\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"resourceUrl\",\n            \"columnName\": \"resourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"tag\",\n            \"columnName\": \"tag\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"start\",\n            \"columnName\": \"start\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"end\",\n            \"columnName\": \"end\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"url\",\n            \"bookUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_chapters_bookUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"bookUrl\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_chapters_bookUrl` ON `${TABLE_NAME}` (`bookUrl`)\"\n          },\n          {\n            \"name\": \"index_chapters_bookUrl_index\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"bookUrl\",\n              \"index\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_chapters_bookUrl_index` ON `${TABLE_NAME}` (`bookUrl`, `index`)\"\n          }\n        ],\n        \"foreignKeys\": [\n          {\n            \"table\": \"books\",\n            \"onDelete\": \"CASCADE\",\n            \"onUpdate\": \"NO ACTION\",\n            \"columns\": [\n              \"bookUrl\"\n            ],\n            \"referencedColumns\": [\n              \"bookUrl\"\n            ]\n          }\n        ]\n      },\n      {\n        \"tableName\": \"replace_rules\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL, `group` TEXT, `pattern` TEXT NOT NULL, `replacement` TEXT NOT NULL, `scope` TEXT, `isEnabled` INTEGER NOT NULL, `isRegex` INTEGER NOT NULL, `sortOrder` INTEGER NOT NULL)\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"group\",\n            \"columnName\": \"group\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"pattern\",\n            \"columnName\": \"pattern\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"replacement\",\n            \"columnName\": \"replacement\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"scope\",\n            \"columnName\": \"scope\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"isEnabled\",\n            \"columnName\": \"isEnabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"isRegex\",\n            \"columnName\": \"isRegex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"sortOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"id\"\n          ],\n          \"autoGenerate\": true\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_replace_rules_id\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"id\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_replace_rules_id` ON `${TABLE_NAME}` (`id`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"searchBooks\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookUrl` TEXT NOT NULL, `origin` TEXT NOT NULL, `originName` TEXT NOT NULL, `type` INTEGER NOT NULL, `name` TEXT NOT NULL, `author` TEXT NOT NULL, `kind` TEXT, `coverUrl` TEXT, `intro` TEXT, `wordCount` TEXT, `latestChapterTitle` TEXT, `tocUrl` TEXT NOT NULL, `time` INTEGER NOT NULL, `variable` TEXT, `originOrder` INTEGER NOT NULL, PRIMARY KEY(`bookUrl`), FOREIGN KEY(`origin`) REFERENCES `book_sources`(`bookSourceUrl`) ON UPDATE NO ACTION ON DELETE CASCADE )\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"originName\",\n            \"columnName\": \"originName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"author\",\n            \"columnName\": \"author\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"kind\",\n            \"columnName\": \"kind\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"coverUrl\",\n            \"columnName\": \"coverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"intro\",\n            \"columnName\": \"intro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"wordCount\",\n            \"columnName\": \"wordCount\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"latestChapterTitle\",\n            \"columnName\": \"latestChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"tocUrl\",\n            \"columnName\": \"tocUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"time\",\n            \"columnName\": \"time\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"originOrder\",\n            \"columnName\": \"originOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"bookUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_searchBooks_bookUrl\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"bookUrl\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_searchBooks_bookUrl` ON `${TABLE_NAME}` (`bookUrl`)\"\n          },\n          {\n            \"name\": \"index_searchBooks_origin\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"origin\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_searchBooks_origin` ON `${TABLE_NAME}` (`origin`)\"\n          }\n        ],\n        \"foreignKeys\": [\n          {\n            \"table\": \"book_sources\",\n            \"onDelete\": \"CASCADE\",\n            \"onUpdate\": \"NO ACTION\",\n            \"columns\": [\n              \"origin\"\n            ],\n            \"referencedColumns\": [\n              \"bookSourceUrl\"\n            ]\n          }\n        ]\n      },\n      {\n        \"tableName\": \"search_keywords\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`word` TEXT NOT NULL, `usage` INTEGER NOT NULL, `lastUseTime` INTEGER NOT NULL, PRIMARY KEY(`word`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"word\",\n            \"columnName\": \"word\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"usage\",\n            \"columnName\": \"usage\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"lastUseTime\",\n            \"columnName\": \"lastUseTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"word\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_search_keywords_word\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"word\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_search_keywords_word` ON `${TABLE_NAME}` (`word`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"cookies\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`url` TEXT NOT NULL, `cookie` TEXT NOT NULL, PRIMARY KEY(`url`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"cookie\",\n            \"columnName\": \"cookie\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"url\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_cookies_url\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"url\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_cookies_url` ON `${TABLE_NAME}` (`url`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssSources\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`sourceUrl` TEXT NOT NULL, `sourceName` TEXT NOT NULL, `sourceIcon` TEXT NOT NULL, `sourceGroup` TEXT, `enabled` INTEGER NOT NULL, `ruleArticles` TEXT, `ruleNextPage` TEXT, `ruleTitle` TEXT, `rulePubDate` TEXT, `ruleDescription` TEXT, `ruleImage` TEXT, `ruleLink` TEXT, `ruleContent` TEXT, `header` TEXT, `enableJs` INTEGER NOT NULL, `loadWithBaseUrl` INTEGER NOT NULL, `customOrder` INTEGER NOT NULL, PRIMARY KEY(`sourceUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"sourceUrl\",\n            \"columnName\": \"sourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceName\",\n            \"columnName\": \"sourceName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceIcon\",\n            \"columnName\": \"sourceIcon\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceGroup\",\n            \"columnName\": \"sourceGroup\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enabled\",\n            \"columnName\": \"enabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"ruleArticles\",\n            \"columnName\": \"ruleArticles\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleNextPage\",\n            \"columnName\": \"ruleNextPage\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleTitle\",\n            \"columnName\": \"ruleTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"rulePubDate\",\n            \"columnName\": \"rulePubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleDescription\",\n            \"columnName\": \"ruleDescription\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleImage\",\n            \"columnName\": \"ruleImage\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleLink\",\n            \"columnName\": \"ruleLink\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleContent\",\n            \"columnName\": \"ruleContent\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"header\",\n            \"columnName\": \"header\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enableJs\",\n            \"columnName\": \"enableJs\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"loadWithBaseUrl\",\n            \"columnName\": \"loadWithBaseUrl\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"sourceUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_rssSources_sourceUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"sourceUrl\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_rssSources_sourceUrl` ON `${TABLE_NAME}` (`sourceUrl`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"bookmarks\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`time` INTEGER NOT NULL, `bookUrl` TEXT NOT NULL, `bookName` TEXT NOT NULL, `chapterIndex` INTEGER NOT NULL, `pageIndex` INTEGER NOT NULL, `chapterName` TEXT NOT NULL, `content` TEXT NOT NULL, PRIMARY KEY(`time`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"time\",\n            \"columnName\": \"time\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookName\",\n            \"columnName\": \"bookName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterIndex\",\n            \"columnName\": \"chapterIndex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"pageIndex\",\n            \"columnName\": \"pageIndex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterName\",\n            \"columnName\": \"chapterName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"time\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_bookmarks_time\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"time\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_bookmarks_time` ON `${TABLE_NAME}` (`time`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssArticles\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`origin` TEXT NOT NULL, `title` TEXT NOT NULL, `order` INTEGER NOT NULL, `link` TEXT NOT NULL, `pubDate` TEXT, `description` TEXT, `content` TEXT, `image` TEXT, `read` INTEGER NOT NULL, PRIMARY KEY(`origin`, `link`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"link\",\n            \"columnName\": \"link\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"pubDate\",\n            \"columnName\": \"pubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"description\",\n            \"columnName\": \"description\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"image\",\n            \"columnName\": \"image\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"read\",\n            \"columnName\": \"read\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"origin\",\n            \"link\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssStars\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`origin` TEXT NOT NULL, `title` TEXT NOT NULL, `starTime` INTEGER NOT NULL, `link` TEXT NOT NULL, `pubDate` TEXT, `description` TEXT, `content` TEXT, `image` TEXT, PRIMARY KEY(`origin`, `link`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"starTime\",\n            \"columnName\": \"starTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"link\",\n            \"columnName\": \"link\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"pubDate\",\n            \"columnName\": \"pubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"description\",\n            \"columnName\": \"description\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"image\",\n            \"columnName\": \"image\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"origin\",\n            \"link\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"txtTocRules\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`name` TEXT NOT NULL, `rule` TEXT NOT NULL, `serialNumber` INTEGER NOT NULL, `enable` INTEGER NOT NULL, PRIMARY KEY(`name`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"rule\",\n            \"columnName\": \"rule\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"serialNumber\",\n            \"columnName\": \"serialNumber\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enable\",\n            \"columnName\": \"enable\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"name\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      }\n    ],\n    \"views\": [],\n    \"setupQueries\": [\n      \"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)\",\n      \"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'af70ea583587e17c968d29f41bb3c0d6')\"\n    ]\n  }\n}"
  },
  {
    "path": "app/schemas/io.legado.app.data.AppDatabase/70.json",
    "content": "{\n  \"formatVersion\": 1,\n  \"database\": {\n    \"version\": 70,\n    \"identityHash\": \"ab3b8675fe5c3152003e20a73f022b0c\",\n    \"entities\": [\n      {\n        \"tableName\": \"books\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookUrl` TEXT NOT NULL DEFAULT '', `tocUrl` TEXT NOT NULL DEFAULT '', `origin` TEXT NOT NULL DEFAULT 'loc_book', `originName` TEXT NOT NULL DEFAULT '', `name` TEXT NOT NULL DEFAULT '', `author` TEXT NOT NULL DEFAULT '', `kind` TEXT, `customTag` TEXT, `coverUrl` TEXT, `customCoverUrl` TEXT, `intro` TEXT, `customIntro` TEXT, `charset` TEXT, `type` INTEGER NOT NULL DEFAULT 0, `group` INTEGER NOT NULL DEFAULT 0, `latestChapterTitle` TEXT, `latestChapterTime` INTEGER NOT NULL DEFAULT 0, `lastCheckTime` INTEGER NOT NULL DEFAULT 0, `lastCheckCount` INTEGER NOT NULL DEFAULT 0, `totalChapterNum` INTEGER NOT NULL DEFAULT 0, `durChapterTitle` TEXT, `durChapterIndex` INTEGER NOT NULL DEFAULT 0, `durChapterPos` INTEGER NOT NULL DEFAULT 0, `durChapterTime` INTEGER NOT NULL DEFAULT 0, `wordCount` TEXT, `canUpdate` INTEGER NOT NULL DEFAULT 1, `order` INTEGER NOT NULL DEFAULT 0, `originOrder` INTEGER NOT NULL DEFAULT 0, `variable` TEXT, `readConfig` TEXT, PRIMARY KEY(`bookUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"tocUrl\",\n            \"columnName\": \"tocUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"'loc_book'\"\n          },\n          {\n            \"fieldPath\": \"originName\",\n            \"columnName\": \"originName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"author\",\n            \"columnName\": \"author\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"kind\",\n            \"columnName\": \"kind\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customTag\",\n            \"columnName\": \"customTag\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"coverUrl\",\n            \"columnName\": \"coverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customCoverUrl\",\n            \"columnName\": \"customCoverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"intro\",\n            \"columnName\": \"intro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customIntro\",\n            \"columnName\": \"customIntro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"charset\",\n            \"columnName\": \"charset\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"group\",\n            \"columnName\": \"group\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"latestChapterTitle\",\n            \"columnName\": \"latestChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"latestChapterTime\",\n            \"columnName\": \"latestChapterTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"lastCheckTime\",\n            \"columnName\": \"lastCheckTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"lastCheckCount\",\n            \"columnName\": \"lastCheckCount\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"totalChapterNum\",\n            \"columnName\": \"totalChapterNum\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"durChapterTitle\",\n            \"columnName\": \"durChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"durChapterIndex\",\n            \"columnName\": \"durChapterIndex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"durChapterPos\",\n            \"columnName\": \"durChapterPos\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"durChapterTime\",\n            \"columnName\": \"durChapterTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"wordCount\",\n            \"columnName\": \"wordCount\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"canUpdate\",\n            \"columnName\": \"canUpdate\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"originOrder\",\n            \"columnName\": \"originOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"readConfig\",\n            \"columnName\": \"readConfig\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"bookUrl\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_books_name_author\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"name\",\n              \"author\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_books_name_author` ON `${TABLE_NAME}` (`name`, `author`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"book_groups\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`groupId` INTEGER NOT NULL, `groupName` TEXT NOT NULL, `cover` TEXT, `order` INTEGER NOT NULL, `enableRefresh` INTEGER NOT NULL DEFAULT 1, `show` INTEGER NOT NULL DEFAULT 1, `bookSort` INTEGER NOT NULL DEFAULT -1, PRIMARY KEY(`groupId`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"groupId\",\n            \"columnName\": \"groupId\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"groupName\",\n            \"columnName\": \"groupName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"cover\",\n            \"columnName\": \"cover\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enableRefresh\",\n            \"columnName\": \"enableRefresh\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"show\",\n            \"columnName\": \"show\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"bookSort\",\n            \"columnName\": \"bookSort\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"-1\"\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"groupId\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"book_sources\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookSourceUrl` TEXT NOT NULL, `bookSourceName` TEXT NOT NULL, `bookSourceGroup` TEXT, `bookSourceType` INTEGER NOT NULL, `bookUrlPattern` TEXT, `customOrder` INTEGER NOT NULL DEFAULT 0, `enabled` INTEGER NOT NULL DEFAULT 1, `enabledExplore` INTEGER NOT NULL DEFAULT 1, `jsLib` TEXT, `enabledCookieJar` INTEGER DEFAULT 0, `concurrentRate` TEXT, `header` TEXT, `loginUrl` TEXT, `loginUi` TEXT, `loginCheckJs` TEXT, `coverDecodeJs` TEXT, `bookSourceComment` TEXT, `variableComment` TEXT, `lastUpdateTime` INTEGER NOT NULL, `respondTime` INTEGER NOT NULL, `weight` INTEGER NOT NULL, `exploreUrl` TEXT, `exploreScreen` TEXT, `ruleExplore` TEXT, `searchUrl` TEXT, `ruleSearch` TEXT, `ruleBookInfo` TEXT, `ruleToc` TEXT, `ruleContent` TEXT, `ruleReview` TEXT, PRIMARY KEY(`bookSourceUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookSourceUrl\",\n            \"columnName\": \"bookSourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookSourceName\",\n            \"columnName\": \"bookSourceName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookSourceGroup\",\n            \"columnName\": \"bookSourceGroup\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"bookSourceType\",\n            \"columnName\": \"bookSourceType\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookUrlPattern\",\n            \"columnName\": \"bookUrlPattern\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"enabled\",\n            \"columnName\": \"enabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"enabledExplore\",\n            \"columnName\": \"enabledExplore\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"jsLib\",\n            \"columnName\": \"jsLib\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enabledCookieJar\",\n            \"columnName\": \"enabledCookieJar\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"concurrentRate\",\n            \"columnName\": \"concurrentRate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"header\",\n            \"columnName\": \"header\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUrl\",\n            \"columnName\": \"loginUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUi\",\n            \"columnName\": \"loginUi\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginCheckJs\",\n            \"columnName\": \"loginCheckJs\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"coverDecodeJs\",\n            \"columnName\": \"coverDecodeJs\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"bookSourceComment\",\n            \"columnName\": \"bookSourceComment\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"variableComment\",\n            \"columnName\": \"variableComment\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"lastUpdateTime\",\n            \"columnName\": \"lastUpdateTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"respondTime\",\n            \"columnName\": \"respondTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"weight\",\n            \"columnName\": \"weight\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"exploreUrl\",\n            \"columnName\": \"exploreUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"exploreScreen\",\n            \"columnName\": \"exploreScreen\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleExplore\",\n            \"columnName\": \"ruleExplore\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"searchUrl\",\n            \"columnName\": \"searchUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleSearch\",\n            \"columnName\": \"ruleSearch\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleBookInfo\",\n            \"columnName\": \"ruleBookInfo\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleToc\",\n            \"columnName\": \"ruleToc\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleContent\",\n            \"columnName\": \"ruleContent\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleReview\",\n            \"columnName\": \"ruleReview\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"bookSourceUrl\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_book_sources_bookSourceUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"bookSourceUrl\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_book_sources_bookSourceUrl` ON `${TABLE_NAME}` (`bookSourceUrl`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"chapters\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`url` TEXT NOT NULL, `title` TEXT NOT NULL, `isVolume` INTEGER NOT NULL, `baseUrl` TEXT NOT NULL, `bookUrl` TEXT NOT NULL, `index` INTEGER NOT NULL, `isVip` INTEGER NOT NULL, `isPay` INTEGER NOT NULL, `resourceUrl` TEXT, `tag` TEXT, `start` INTEGER, `end` INTEGER, `startFragmentId` TEXT, `endFragmentId` TEXT, `variable` TEXT, PRIMARY KEY(`url`, `bookUrl`), FOREIGN KEY(`bookUrl`) REFERENCES `books`(`bookUrl`) ON UPDATE NO ACTION ON DELETE CASCADE )\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"isVolume\",\n            \"columnName\": \"isVolume\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"baseUrl\",\n            \"columnName\": \"baseUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"index\",\n            \"columnName\": \"index\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"isVip\",\n            \"columnName\": \"isVip\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"isPay\",\n            \"columnName\": \"isPay\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"resourceUrl\",\n            \"columnName\": \"resourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"tag\",\n            \"columnName\": \"tag\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"start\",\n            \"columnName\": \"start\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"end\",\n            \"columnName\": \"end\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"startFragmentId\",\n            \"columnName\": \"startFragmentId\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"endFragmentId\",\n            \"columnName\": \"endFragmentId\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"url\",\n            \"bookUrl\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_chapters_bookUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"bookUrl\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_chapters_bookUrl` ON `${TABLE_NAME}` (`bookUrl`)\"\n          },\n          {\n            \"name\": \"index_chapters_bookUrl_index\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"bookUrl\",\n              \"index\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_chapters_bookUrl_index` ON `${TABLE_NAME}` (`bookUrl`, `index`)\"\n          }\n        ],\n        \"foreignKeys\": [\n          {\n            \"table\": \"books\",\n            \"onDelete\": \"CASCADE\",\n            \"onUpdate\": \"NO ACTION\",\n            \"columns\": [\n              \"bookUrl\"\n            ],\n            \"referencedColumns\": [\n              \"bookUrl\"\n            ]\n          }\n        ]\n      },\n      {\n        \"tableName\": \"replace_rules\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL DEFAULT '', `group` TEXT, `pattern` TEXT NOT NULL DEFAULT '', `replacement` TEXT NOT NULL DEFAULT '', `scope` TEXT, `scopeTitle` INTEGER NOT NULL DEFAULT 0, `scopeContent` INTEGER NOT NULL DEFAULT 1, `excludeScope` TEXT, `isEnabled` INTEGER NOT NULL DEFAULT 1, `isRegex` INTEGER NOT NULL DEFAULT 1, `timeoutMillisecond` INTEGER NOT NULL DEFAULT 3000, `sortOrder` INTEGER NOT NULL DEFAULT 0)\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"group\",\n            \"columnName\": \"group\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"pattern\",\n            \"columnName\": \"pattern\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"replacement\",\n            \"columnName\": \"replacement\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"scope\",\n            \"columnName\": \"scope\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"scopeTitle\",\n            \"columnName\": \"scopeTitle\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"scopeContent\",\n            \"columnName\": \"scopeContent\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"excludeScope\",\n            \"columnName\": \"excludeScope\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"isEnabled\",\n            \"columnName\": \"isEnabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"isRegex\",\n            \"columnName\": \"isRegex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"timeoutMillisecond\",\n            \"columnName\": \"timeoutMillisecond\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"3000\"\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"sortOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": true,\n          \"columnNames\": [\n            \"id\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_replace_rules_id\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"id\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_replace_rules_id` ON `${TABLE_NAME}` (`id`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"searchBooks\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookUrl` TEXT NOT NULL, `origin` TEXT NOT NULL, `originName` TEXT NOT NULL, `type` INTEGER NOT NULL, `name` TEXT NOT NULL, `author` TEXT NOT NULL, `kind` TEXT, `coverUrl` TEXT, `intro` TEXT, `wordCount` TEXT, `latestChapterTitle` TEXT, `tocUrl` TEXT NOT NULL, `time` INTEGER NOT NULL, `variable` TEXT, `originOrder` INTEGER NOT NULL, `chapterWordCountText` TEXT, `chapterWordCount` INTEGER NOT NULL DEFAULT -1, `respondTime` INTEGER NOT NULL DEFAULT -1, PRIMARY KEY(`bookUrl`), FOREIGN KEY(`origin`) REFERENCES `book_sources`(`bookSourceUrl`) ON UPDATE NO ACTION ON DELETE CASCADE )\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"originName\",\n            \"columnName\": \"originName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"author\",\n            \"columnName\": \"author\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"kind\",\n            \"columnName\": \"kind\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"coverUrl\",\n            \"columnName\": \"coverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"intro\",\n            \"columnName\": \"intro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"wordCount\",\n            \"columnName\": \"wordCount\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"latestChapterTitle\",\n            \"columnName\": \"latestChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"tocUrl\",\n            \"columnName\": \"tocUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"time\",\n            \"columnName\": \"time\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"originOrder\",\n            \"columnName\": \"originOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterWordCountText\",\n            \"columnName\": \"chapterWordCountText\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"chapterWordCount\",\n            \"columnName\": \"chapterWordCount\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"-1\"\n          },\n          {\n            \"fieldPath\": \"respondTime\",\n            \"columnName\": \"respondTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"-1\"\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"bookUrl\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_searchBooks_bookUrl\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"bookUrl\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_searchBooks_bookUrl` ON `${TABLE_NAME}` (`bookUrl`)\"\n          },\n          {\n            \"name\": \"index_searchBooks_origin\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"origin\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_searchBooks_origin` ON `${TABLE_NAME}` (`origin`)\"\n          }\n        ],\n        \"foreignKeys\": [\n          {\n            \"table\": \"book_sources\",\n            \"onDelete\": \"CASCADE\",\n            \"onUpdate\": \"NO ACTION\",\n            \"columns\": [\n              \"origin\"\n            ],\n            \"referencedColumns\": [\n              \"bookSourceUrl\"\n            ]\n          }\n        ]\n      },\n      {\n        \"tableName\": \"search_keywords\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`word` TEXT NOT NULL, `usage` INTEGER NOT NULL, `lastUseTime` INTEGER NOT NULL, PRIMARY KEY(`word`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"word\",\n            \"columnName\": \"word\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"usage\",\n            \"columnName\": \"usage\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"lastUseTime\",\n            \"columnName\": \"lastUseTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"word\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_search_keywords_word\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"word\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_search_keywords_word` ON `${TABLE_NAME}` (`word`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"cookies\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`url` TEXT NOT NULL, `cookie` TEXT NOT NULL, PRIMARY KEY(`url`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"cookie\",\n            \"columnName\": \"cookie\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"url\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_cookies_url\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"url\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_cookies_url` ON `${TABLE_NAME}` (`url`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssSources\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`sourceUrl` TEXT NOT NULL, `sourceName` TEXT NOT NULL, `sourceIcon` TEXT NOT NULL, `sourceGroup` TEXT, `sourceComment` TEXT, `enabled` INTEGER NOT NULL, `variableComment` TEXT, `jsLib` TEXT, `enabledCookieJar` INTEGER DEFAULT 0, `concurrentRate` TEXT, `header` TEXT, `loginUrl` TEXT, `loginUi` TEXT, `loginCheckJs` TEXT, `coverDecodeJs` TEXT, `sortUrl` TEXT, `singleUrl` INTEGER NOT NULL, `articleStyle` INTEGER NOT NULL DEFAULT 0, `ruleArticles` TEXT, `ruleNextPage` TEXT, `ruleTitle` TEXT, `rulePubDate` TEXT, `ruleDescription` TEXT, `ruleImage` TEXT, `ruleLink` TEXT, `ruleContent` TEXT, `contentWhitelist` TEXT, `contentBlacklist` TEXT, `shouldOverrideUrlLoading` TEXT, `style` TEXT, `enableJs` INTEGER NOT NULL DEFAULT 1, `loadWithBaseUrl` INTEGER NOT NULL DEFAULT 1, `injectJs` TEXT, `lastUpdateTime` INTEGER NOT NULL DEFAULT 0, `customOrder` INTEGER NOT NULL DEFAULT 0, PRIMARY KEY(`sourceUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"sourceUrl\",\n            \"columnName\": \"sourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceName\",\n            \"columnName\": \"sourceName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceIcon\",\n            \"columnName\": \"sourceIcon\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceGroup\",\n            \"columnName\": \"sourceGroup\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"sourceComment\",\n            \"columnName\": \"sourceComment\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enabled\",\n            \"columnName\": \"enabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"variableComment\",\n            \"columnName\": \"variableComment\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"jsLib\",\n            \"columnName\": \"jsLib\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enabledCookieJar\",\n            \"columnName\": \"enabledCookieJar\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"concurrentRate\",\n            \"columnName\": \"concurrentRate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"header\",\n            \"columnName\": \"header\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUrl\",\n            \"columnName\": \"loginUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUi\",\n            \"columnName\": \"loginUi\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginCheckJs\",\n            \"columnName\": \"loginCheckJs\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"coverDecodeJs\",\n            \"columnName\": \"coverDecodeJs\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"sortUrl\",\n            \"columnName\": \"sortUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"singleUrl\",\n            \"columnName\": \"singleUrl\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"articleStyle\",\n            \"columnName\": \"articleStyle\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"ruleArticles\",\n            \"columnName\": \"ruleArticles\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleNextPage\",\n            \"columnName\": \"ruleNextPage\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleTitle\",\n            \"columnName\": \"ruleTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"rulePubDate\",\n            \"columnName\": \"rulePubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleDescription\",\n            \"columnName\": \"ruleDescription\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleImage\",\n            \"columnName\": \"ruleImage\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleLink\",\n            \"columnName\": \"ruleLink\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleContent\",\n            \"columnName\": \"ruleContent\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"contentWhitelist\",\n            \"columnName\": \"contentWhitelist\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"contentBlacklist\",\n            \"columnName\": \"contentBlacklist\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"shouldOverrideUrlLoading\",\n            \"columnName\": \"shouldOverrideUrlLoading\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"style\",\n            \"columnName\": \"style\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enableJs\",\n            \"columnName\": \"enableJs\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"loadWithBaseUrl\",\n            \"columnName\": \"loadWithBaseUrl\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"injectJs\",\n            \"columnName\": \"injectJs\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"lastUpdateTime\",\n            \"columnName\": \"lastUpdateTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"sourceUrl\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_rssSources_sourceUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"sourceUrl\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_rssSources_sourceUrl` ON `${TABLE_NAME}` (`sourceUrl`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"bookmarks\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`time` INTEGER NOT NULL, `bookName` TEXT NOT NULL, `bookAuthor` TEXT NOT NULL, `chapterIndex` INTEGER NOT NULL, `chapterPos` INTEGER NOT NULL, `chapterName` TEXT NOT NULL, `bookText` TEXT NOT NULL, `content` TEXT NOT NULL, PRIMARY KEY(`time`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"time\",\n            \"columnName\": \"time\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookName\",\n            \"columnName\": \"bookName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookAuthor\",\n            \"columnName\": \"bookAuthor\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterIndex\",\n            \"columnName\": \"chapterIndex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterPos\",\n            \"columnName\": \"chapterPos\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterName\",\n            \"columnName\": \"chapterName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookText\",\n            \"columnName\": \"bookText\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"time\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_bookmarks_bookName_bookAuthor\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"bookName\",\n              \"bookAuthor\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_bookmarks_bookName_bookAuthor` ON `${TABLE_NAME}` (`bookName`, `bookAuthor`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssArticles\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`origin` TEXT NOT NULL, `sort` TEXT NOT NULL, `title` TEXT NOT NULL, `order` INTEGER NOT NULL, `link` TEXT NOT NULL, `pubDate` TEXT, `description` TEXT, `content` TEXT, `image` TEXT, `read` INTEGER NOT NULL, `variable` TEXT, PRIMARY KEY(`origin`, `link`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sort\",\n            \"columnName\": \"sort\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"link\",\n            \"columnName\": \"link\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"pubDate\",\n            \"columnName\": \"pubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"description\",\n            \"columnName\": \"description\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"image\",\n            \"columnName\": \"image\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"read\",\n            \"columnName\": \"read\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"origin\",\n            \"link\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssReadRecords\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`record` TEXT NOT NULL, `read` INTEGER NOT NULL, PRIMARY KEY(`record`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"record\",\n            \"columnName\": \"record\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"read\",\n            \"columnName\": \"read\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"record\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssStars\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`origin` TEXT NOT NULL, `sort` TEXT NOT NULL, `title` TEXT NOT NULL, `starTime` INTEGER NOT NULL, `link` TEXT NOT NULL, `pubDate` TEXT, `description` TEXT, `content` TEXT, `image` TEXT, `variable` TEXT, PRIMARY KEY(`origin`, `link`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sort\",\n            \"columnName\": \"sort\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"starTime\",\n            \"columnName\": \"starTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"link\",\n            \"columnName\": \"link\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"pubDate\",\n            \"columnName\": \"pubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"description\",\n            \"columnName\": \"description\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"image\",\n            \"columnName\": \"image\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"origin\",\n            \"link\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"txtTocRules\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `rule` TEXT NOT NULL, `example` TEXT, `serialNumber` INTEGER NOT NULL, `enable` INTEGER NOT NULL, PRIMARY KEY(`id`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"rule\",\n            \"columnName\": \"rule\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"example\",\n            \"columnName\": \"example\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"serialNumber\",\n            \"columnName\": \"serialNumber\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enable\",\n            \"columnName\": \"enable\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"id\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"readRecord\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`deviceId` TEXT NOT NULL, `bookName` TEXT NOT NULL, `readTime` INTEGER NOT NULL DEFAULT 0, `lastRead` INTEGER NOT NULL DEFAULT 0, PRIMARY KEY(`deviceId`, `bookName`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"deviceId\",\n            \"columnName\": \"deviceId\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookName\",\n            \"columnName\": \"bookName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"readTime\",\n            \"columnName\": \"readTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"lastRead\",\n            \"columnName\": \"lastRead\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"deviceId\",\n            \"bookName\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"httpTTS\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `url` TEXT NOT NULL, `contentType` TEXT, `concurrentRate` TEXT DEFAULT '0', `loginUrl` TEXT, `loginUi` TEXT, `header` TEXT, `jsLib` TEXT, `enabledCookieJar` INTEGER DEFAULT 0, `loginCheckJs` TEXT, `lastUpdateTime` INTEGER NOT NULL DEFAULT 0, PRIMARY KEY(`id`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"contentType\",\n            \"columnName\": \"contentType\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"concurrentRate\",\n            \"columnName\": \"concurrentRate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false,\n            \"defaultValue\": \"'0'\"\n          },\n          {\n            \"fieldPath\": \"loginUrl\",\n            \"columnName\": \"loginUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUi\",\n            \"columnName\": \"loginUi\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"header\",\n            \"columnName\": \"header\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"jsLib\",\n            \"columnName\": \"jsLib\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enabledCookieJar\",\n            \"columnName\": \"enabledCookieJar\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"loginCheckJs\",\n            \"columnName\": \"loginCheckJs\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"lastUpdateTime\",\n            \"columnName\": \"lastUpdateTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"id\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"caches\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`key` TEXT NOT NULL, `value` TEXT, `deadline` INTEGER NOT NULL, PRIMARY KEY(`key`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"key\",\n            \"columnName\": \"key\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"value\",\n            \"columnName\": \"value\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"deadline\",\n            \"columnName\": \"deadline\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"key\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_caches_key\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"key\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_caches_key` ON `${TABLE_NAME}` (`key`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"ruleSubs\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `url` TEXT NOT NULL, `type` INTEGER NOT NULL, `customOrder` INTEGER NOT NULL, `autoUpdate` INTEGER NOT NULL, `update` INTEGER NOT NULL, PRIMARY KEY(`id`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"autoUpdate\",\n            \"columnName\": \"autoUpdate\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"update\",\n            \"columnName\": \"update\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"id\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"dictRules\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`name` TEXT NOT NULL, `urlRule` TEXT NOT NULL, `showRule` TEXT NOT NULL, `enabled` INTEGER NOT NULL DEFAULT 1, `sortNumber` INTEGER NOT NULL DEFAULT 0, PRIMARY KEY(`name`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"urlRule\",\n            \"columnName\": \"urlRule\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"showRule\",\n            \"columnName\": \"showRule\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enabled\",\n            \"columnName\": \"enabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"sortNumber\",\n            \"columnName\": \"sortNumber\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"name\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"keyboardAssists\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`type` INTEGER NOT NULL DEFAULT 0, `key` TEXT NOT NULL DEFAULT '', `value` TEXT NOT NULL DEFAULT '', `serialNo` INTEGER NOT NULL DEFAULT 0, PRIMARY KEY(`type`, `key`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"key\",\n            \"columnName\": \"key\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"value\",\n            \"columnName\": \"value\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"serialNo\",\n            \"columnName\": \"serialNo\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"type\",\n            \"key\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"servers\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `type` TEXT NOT NULL, `config` TEXT, `sortNumber` INTEGER NOT NULL, PRIMARY KEY(`id`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"config\",\n            \"columnName\": \"config\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"sortNumber\",\n            \"columnName\": \"sortNumber\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"id\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      }\n    ],\n    \"views\": [],\n    \"setupQueries\": [\n      \"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)\",\n      \"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'ab3b8675fe5c3152003e20a73f022b0c')\"\n    ]\n  }\n}"
  },
  {
    "path": "app/schemas/io.legado.app.data.AppDatabase/71.json",
    "content": "{\n  \"formatVersion\": 1,\n  \"database\": {\n    \"version\": 71,\n    \"identityHash\": \"6e17b5366286b868f2912c12d6d8e467\",\n    \"entities\": [\n      {\n        \"tableName\": \"books\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookUrl` TEXT NOT NULL DEFAULT '', `tocUrl` TEXT NOT NULL DEFAULT '', `origin` TEXT NOT NULL DEFAULT 'loc_book', `originName` TEXT NOT NULL DEFAULT '', `name` TEXT NOT NULL DEFAULT '', `author` TEXT NOT NULL DEFAULT '', `kind` TEXT, `customTag` TEXT, `coverUrl` TEXT, `customCoverUrl` TEXT, `intro` TEXT, `customIntro` TEXT, `charset` TEXT, `type` INTEGER NOT NULL DEFAULT 0, `group` INTEGER NOT NULL DEFAULT 0, `latestChapterTitle` TEXT, `latestChapterTime` INTEGER NOT NULL DEFAULT 0, `lastCheckTime` INTEGER NOT NULL DEFAULT 0, `lastCheckCount` INTEGER NOT NULL DEFAULT 0, `totalChapterNum` INTEGER NOT NULL DEFAULT 0, `durChapterTitle` TEXT, `durChapterIndex` INTEGER NOT NULL DEFAULT 0, `durChapterPos` INTEGER NOT NULL DEFAULT 0, `durChapterTime` INTEGER NOT NULL DEFAULT 0, `wordCount` TEXT, `canUpdate` INTEGER NOT NULL DEFAULT 1, `order` INTEGER NOT NULL DEFAULT 0, `originOrder` INTEGER NOT NULL DEFAULT 0, `variable` TEXT, `readConfig` TEXT, `syncTime` INTEGER NOT NULL DEFAULT 0, PRIMARY KEY(`bookUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"tocUrl\",\n            \"columnName\": \"tocUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"'loc_book'\"\n          },\n          {\n            \"fieldPath\": \"originName\",\n            \"columnName\": \"originName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"author\",\n            \"columnName\": \"author\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"kind\",\n            \"columnName\": \"kind\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customTag\",\n            \"columnName\": \"customTag\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"coverUrl\",\n            \"columnName\": \"coverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customCoverUrl\",\n            \"columnName\": \"customCoverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"intro\",\n            \"columnName\": \"intro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customIntro\",\n            \"columnName\": \"customIntro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"charset\",\n            \"columnName\": \"charset\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"group\",\n            \"columnName\": \"group\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"latestChapterTitle\",\n            \"columnName\": \"latestChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"latestChapterTime\",\n            \"columnName\": \"latestChapterTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"lastCheckTime\",\n            \"columnName\": \"lastCheckTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"lastCheckCount\",\n            \"columnName\": \"lastCheckCount\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"totalChapterNum\",\n            \"columnName\": \"totalChapterNum\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"durChapterTitle\",\n            \"columnName\": \"durChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"durChapterIndex\",\n            \"columnName\": \"durChapterIndex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"durChapterPos\",\n            \"columnName\": \"durChapterPos\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"durChapterTime\",\n            \"columnName\": \"durChapterTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"wordCount\",\n            \"columnName\": \"wordCount\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"canUpdate\",\n            \"columnName\": \"canUpdate\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"originOrder\",\n            \"columnName\": \"originOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"readConfig\",\n            \"columnName\": \"readConfig\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"syncTime\",\n            \"columnName\": \"syncTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"bookUrl\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_books_name_author\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"name\",\n              \"author\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_books_name_author` ON `${TABLE_NAME}` (`name`, `author`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"book_groups\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`groupId` INTEGER NOT NULL, `groupName` TEXT NOT NULL, `cover` TEXT, `order` INTEGER NOT NULL, `enableRefresh` INTEGER NOT NULL DEFAULT 1, `show` INTEGER NOT NULL DEFAULT 1, `bookSort` INTEGER NOT NULL DEFAULT -1, PRIMARY KEY(`groupId`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"groupId\",\n            \"columnName\": \"groupId\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"groupName\",\n            \"columnName\": \"groupName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"cover\",\n            \"columnName\": \"cover\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enableRefresh\",\n            \"columnName\": \"enableRefresh\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"show\",\n            \"columnName\": \"show\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"bookSort\",\n            \"columnName\": \"bookSort\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"-1\"\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"groupId\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"book_sources\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookSourceUrl` TEXT NOT NULL, `bookSourceName` TEXT NOT NULL, `bookSourceGroup` TEXT, `bookSourceType` INTEGER NOT NULL, `bookUrlPattern` TEXT, `customOrder` INTEGER NOT NULL DEFAULT 0, `enabled` INTEGER NOT NULL DEFAULT 1, `enabledExplore` INTEGER NOT NULL DEFAULT 1, `jsLib` TEXT, `enabledCookieJar` INTEGER DEFAULT 0, `concurrentRate` TEXT, `header` TEXT, `loginUrl` TEXT, `loginUi` TEXT, `loginCheckJs` TEXT, `coverDecodeJs` TEXT, `bookSourceComment` TEXT, `variableComment` TEXT, `lastUpdateTime` INTEGER NOT NULL, `respondTime` INTEGER NOT NULL, `weight` INTEGER NOT NULL, `exploreUrl` TEXT, `exploreScreen` TEXT, `ruleExplore` TEXT, `searchUrl` TEXT, `ruleSearch` TEXT, `ruleBookInfo` TEXT, `ruleToc` TEXT, `ruleContent` TEXT, `ruleReview` TEXT, PRIMARY KEY(`bookSourceUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookSourceUrl\",\n            \"columnName\": \"bookSourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookSourceName\",\n            \"columnName\": \"bookSourceName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookSourceGroup\",\n            \"columnName\": \"bookSourceGroup\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"bookSourceType\",\n            \"columnName\": \"bookSourceType\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookUrlPattern\",\n            \"columnName\": \"bookUrlPattern\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"enabled\",\n            \"columnName\": \"enabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"enabledExplore\",\n            \"columnName\": \"enabledExplore\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"jsLib\",\n            \"columnName\": \"jsLib\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enabledCookieJar\",\n            \"columnName\": \"enabledCookieJar\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"concurrentRate\",\n            \"columnName\": \"concurrentRate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"header\",\n            \"columnName\": \"header\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUrl\",\n            \"columnName\": \"loginUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUi\",\n            \"columnName\": \"loginUi\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginCheckJs\",\n            \"columnName\": \"loginCheckJs\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"coverDecodeJs\",\n            \"columnName\": \"coverDecodeJs\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"bookSourceComment\",\n            \"columnName\": \"bookSourceComment\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"variableComment\",\n            \"columnName\": \"variableComment\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"lastUpdateTime\",\n            \"columnName\": \"lastUpdateTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"respondTime\",\n            \"columnName\": \"respondTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"weight\",\n            \"columnName\": \"weight\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"exploreUrl\",\n            \"columnName\": \"exploreUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"exploreScreen\",\n            \"columnName\": \"exploreScreen\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleExplore\",\n            \"columnName\": \"ruleExplore\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"searchUrl\",\n            \"columnName\": \"searchUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleSearch\",\n            \"columnName\": \"ruleSearch\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleBookInfo\",\n            \"columnName\": \"ruleBookInfo\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleToc\",\n            \"columnName\": \"ruleToc\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleContent\",\n            \"columnName\": \"ruleContent\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleReview\",\n            \"columnName\": \"ruleReview\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"bookSourceUrl\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_book_sources_bookSourceUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"bookSourceUrl\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_book_sources_bookSourceUrl` ON `${TABLE_NAME}` (`bookSourceUrl`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"chapters\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`url` TEXT NOT NULL, `title` TEXT NOT NULL, `isVolume` INTEGER NOT NULL, `baseUrl` TEXT NOT NULL, `bookUrl` TEXT NOT NULL, `index` INTEGER NOT NULL, `isVip` INTEGER NOT NULL, `isPay` INTEGER NOT NULL, `resourceUrl` TEXT, `tag` TEXT, `start` INTEGER, `end` INTEGER, `startFragmentId` TEXT, `endFragmentId` TEXT, `variable` TEXT, PRIMARY KEY(`url`, `bookUrl`), FOREIGN KEY(`bookUrl`) REFERENCES `books`(`bookUrl`) ON UPDATE NO ACTION ON DELETE CASCADE )\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"isVolume\",\n            \"columnName\": \"isVolume\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"baseUrl\",\n            \"columnName\": \"baseUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"index\",\n            \"columnName\": \"index\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"isVip\",\n            \"columnName\": \"isVip\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"isPay\",\n            \"columnName\": \"isPay\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"resourceUrl\",\n            \"columnName\": \"resourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"tag\",\n            \"columnName\": \"tag\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"start\",\n            \"columnName\": \"start\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"end\",\n            \"columnName\": \"end\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"startFragmentId\",\n            \"columnName\": \"startFragmentId\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"endFragmentId\",\n            \"columnName\": \"endFragmentId\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"url\",\n            \"bookUrl\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_chapters_bookUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"bookUrl\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_chapters_bookUrl` ON `${TABLE_NAME}` (`bookUrl`)\"\n          },\n          {\n            \"name\": \"index_chapters_bookUrl_index\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"bookUrl\",\n              \"index\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_chapters_bookUrl_index` ON `${TABLE_NAME}` (`bookUrl`, `index`)\"\n          }\n        ],\n        \"foreignKeys\": [\n          {\n            \"table\": \"books\",\n            \"onDelete\": \"CASCADE\",\n            \"onUpdate\": \"NO ACTION\",\n            \"columns\": [\n              \"bookUrl\"\n            ],\n            \"referencedColumns\": [\n              \"bookUrl\"\n            ]\n          }\n        ]\n      },\n      {\n        \"tableName\": \"replace_rules\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL DEFAULT '', `group` TEXT, `pattern` TEXT NOT NULL DEFAULT '', `replacement` TEXT NOT NULL DEFAULT '', `scope` TEXT, `scopeTitle` INTEGER NOT NULL DEFAULT 0, `scopeContent` INTEGER NOT NULL DEFAULT 1, `excludeScope` TEXT, `isEnabled` INTEGER NOT NULL DEFAULT 1, `isRegex` INTEGER NOT NULL DEFAULT 1, `timeoutMillisecond` INTEGER NOT NULL DEFAULT 3000, `sortOrder` INTEGER NOT NULL DEFAULT 0)\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"group\",\n            \"columnName\": \"group\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"pattern\",\n            \"columnName\": \"pattern\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"replacement\",\n            \"columnName\": \"replacement\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"scope\",\n            \"columnName\": \"scope\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"scopeTitle\",\n            \"columnName\": \"scopeTitle\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"scopeContent\",\n            \"columnName\": \"scopeContent\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"excludeScope\",\n            \"columnName\": \"excludeScope\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"isEnabled\",\n            \"columnName\": \"isEnabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"isRegex\",\n            \"columnName\": \"isRegex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"timeoutMillisecond\",\n            \"columnName\": \"timeoutMillisecond\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"3000\"\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"sortOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": true,\n          \"columnNames\": [\n            \"id\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_replace_rules_id\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"id\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_replace_rules_id` ON `${TABLE_NAME}` (`id`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"searchBooks\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookUrl` TEXT NOT NULL, `origin` TEXT NOT NULL, `originName` TEXT NOT NULL, `type` INTEGER NOT NULL, `name` TEXT NOT NULL, `author` TEXT NOT NULL, `kind` TEXT, `coverUrl` TEXT, `intro` TEXT, `wordCount` TEXT, `latestChapterTitle` TEXT, `tocUrl` TEXT NOT NULL, `time` INTEGER NOT NULL, `variable` TEXT, `originOrder` INTEGER NOT NULL, `chapterWordCountText` TEXT, `chapterWordCount` INTEGER NOT NULL DEFAULT -1, `respondTime` INTEGER NOT NULL DEFAULT -1, PRIMARY KEY(`bookUrl`), FOREIGN KEY(`origin`) REFERENCES `book_sources`(`bookSourceUrl`) ON UPDATE NO ACTION ON DELETE CASCADE )\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"originName\",\n            \"columnName\": \"originName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"author\",\n            \"columnName\": \"author\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"kind\",\n            \"columnName\": \"kind\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"coverUrl\",\n            \"columnName\": \"coverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"intro\",\n            \"columnName\": \"intro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"wordCount\",\n            \"columnName\": \"wordCount\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"latestChapterTitle\",\n            \"columnName\": \"latestChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"tocUrl\",\n            \"columnName\": \"tocUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"time\",\n            \"columnName\": \"time\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"originOrder\",\n            \"columnName\": \"originOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterWordCountText\",\n            \"columnName\": \"chapterWordCountText\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"chapterWordCount\",\n            \"columnName\": \"chapterWordCount\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"-1\"\n          },\n          {\n            \"fieldPath\": \"respondTime\",\n            \"columnName\": \"respondTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"-1\"\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"bookUrl\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_searchBooks_bookUrl\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"bookUrl\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_searchBooks_bookUrl` ON `${TABLE_NAME}` (`bookUrl`)\"\n          },\n          {\n            \"name\": \"index_searchBooks_origin\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"origin\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_searchBooks_origin` ON `${TABLE_NAME}` (`origin`)\"\n          }\n        ],\n        \"foreignKeys\": [\n          {\n            \"table\": \"book_sources\",\n            \"onDelete\": \"CASCADE\",\n            \"onUpdate\": \"NO ACTION\",\n            \"columns\": [\n              \"origin\"\n            ],\n            \"referencedColumns\": [\n              \"bookSourceUrl\"\n            ]\n          }\n        ]\n      },\n      {\n        \"tableName\": \"search_keywords\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`word` TEXT NOT NULL, `usage` INTEGER NOT NULL, `lastUseTime` INTEGER NOT NULL, PRIMARY KEY(`word`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"word\",\n            \"columnName\": \"word\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"usage\",\n            \"columnName\": \"usage\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"lastUseTime\",\n            \"columnName\": \"lastUseTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"word\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_search_keywords_word\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"word\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_search_keywords_word` ON `${TABLE_NAME}` (`word`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"cookies\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`url` TEXT NOT NULL, `cookie` TEXT NOT NULL, PRIMARY KEY(`url`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"cookie\",\n            \"columnName\": \"cookie\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"url\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_cookies_url\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"url\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_cookies_url` ON `${TABLE_NAME}` (`url`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssSources\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`sourceUrl` TEXT NOT NULL, `sourceName` TEXT NOT NULL, `sourceIcon` TEXT NOT NULL, `sourceGroup` TEXT, `sourceComment` TEXT, `enabled` INTEGER NOT NULL, `variableComment` TEXT, `jsLib` TEXT, `enabledCookieJar` INTEGER DEFAULT 0, `concurrentRate` TEXT, `header` TEXT, `loginUrl` TEXT, `loginUi` TEXT, `loginCheckJs` TEXT, `coverDecodeJs` TEXT, `sortUrl` TEXT, `singleUrl` INTEGER NOT NULL, `articleStyle` INTEGER NOT NULL DEFAULT 0, `ruleArticles` TEXT, `ruleNextPage` TEXT, `ruleTitle` TEXT, `rulePubDate` TEXT, `ruleDescription` TEXT, `ruleImage` TEXT, `ruleLink` TEXT, `ruleContent` TEXT, `contentWhitelist` TEXT, `contentBlacklist` TEXT, `shouldOverrideUrlLoading` TEXT, `style` TEXT, `enableJs` INTEGER NOT NULL DEFAULT 1, `loadWithBaseUrl` INTEGER NOT NULL DEFAULT 1, `injectJs` TEXT, `lastUpdateTime` INTEGER NOT NULL DEFAULT 0, `customOrder` INTEGER NOT NULL DEFAULT 0, PRIMARY KEY(`sourceUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"sourceUrl\",\n            \"columnName\": \"sourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceName\",\n            \"columnName\": \"sourceName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceIcon\",\n            \"columnName\": \"sourceIcon\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceGroup\",\n            \"columnName\": \"sourceGroup\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"sourceComment\",\n            \"columnName\": \"sourceComment\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enabled\",\n            \"columnName\": \"enabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"variableComment\",\n            \"columnName\": \"variableComment\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"jsLib\",\n            \"columnName\": \"jsLib\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enabledCookieJar\",\n            \"columnName\": \"enabledCookieJar\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"concurrentRate\",\n            \"columnName\": \"concurrentRate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"header\",\n            \"columnName\": \"header\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUrl\",\n            \"columnName\": \"loginUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUi\",\n            \"columnName\": \"loginUi\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginCheckJs\",\n            \"columnName\": \"loginCheckJs\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"coverDecodeJs\",\n            \"columnName\": \"coverDecodeJs\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"sortUrl\",\n            \"columnName\": \"sortUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"singleUrl\",\n            \"columnName\": \"singleUrl\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"articleStyle\",\n            \"columnName\": \"articleStyle\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"ruleArticles\",\n            \"columnName\": \"ruleArticles\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleNextPage\",\n            \"columnName\": \"ruleNextPage\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleTitle\",\n            \"columnName\": \"ruleTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"rulePubDate\",\n            \"columnName\": \"rulePubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleDescription\",\n            \"columnName\": \"ruleDescription\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleImage\",\n            \"columnName\": \"ruleImage\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleLink\",\n            \"columnName\": \"ruleLink\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleContent\",\n            \"columnName\": \"ruleContent\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"contentWhitelist\",\n            \"columnName\": \"contentWhitelist\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"contentBlacklist\",\n            \"columnName\": \"contentBlacklist\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"shouldOverrideUrlLoading\",\n            \"columnName\": \"shouldOverrideUrlLoading\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"style\",\n            \"columnName\": \"style\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enableJs\",\n            \"columnName\": \"enableJs\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"loadWithBaseUrl\",\n            \"columnName\": \"loadWithBaseUrl\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"injectJs\",\n            \"columnName\": \"injectJs\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"lastUpdateTime\",\n            \"columnName\": \"lastUpdateTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"sourceUrl\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_rssSources_sourceUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"sourceUrl\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_rssSources_sourceUrl` ON `${TABLE_NAME}` (`sourceUrl`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"bookmarks\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`time` INTEGER NOT NULL, `bookName` TEXT NOT NULL, `bookAuthor` TEXT NOT NULL, `chapterIndex` INTEGER NOT NULL, `chapterPos` INTEGER NOT NULL, `chapterName` TEXT NOT NULL, `bookText` TEXT NOT NULL, `content` TEXT NOT NULL, PRIMARY KEY(`time`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"time\",\n            \"columnName\": \"time\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookName\",\n            \"columnName\": \"bookName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookAuthor\",\n            \"columnName\": \"bookAuthor\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterIndex\",\n            \"columnName\": \"chapterIndex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterPos\",\n            \"columnName\": \"chapterPos\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterName\",\n            \"columnName\": \"chapterName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookText\",\n            \"columnName\": \"bookText\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"time\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_bookmarks_bookName_bookAuthor\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"bookName\",\n              \"bookAuthor\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_bookmarks_bookName_bookAuthor` ON `${TABLE_NAME}` (`bookName`, `bookAuthor`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssArticles\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`origin` TEXT NOT NULL, `sort` TEXT NOT NULL, `title` TEXT NOT NULL, `order` INTEGER NOT NULL, `link` TEXT NOT NULL, `pubDate` TEXT, `description` TEXT, `content` TEXT, `image` TEXT, `read` INTEGER NOT NULL, `variable` TEXT, PRIMARY KEY(`origin`, `link`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sort\",\n            \"columnName\": \"sort\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"link\",\n            \"columnName\": \"link\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"pubDate\",\n            \"columnName\": \"pubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"description\",\n            \"columnName\": \"description\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"image\",\n            \"columnName\": \"image\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"read\",\n            \"columnName\": \"read\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"origin\",\n            \"link\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssReadRecords\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`record` TEXT NOT NULL, `read` INTEGER NOT NULL, PRIMARY KEY(`record`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"record\",\n            \"columnName\": \"record\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"read\",\n            \"columnName\": \"read\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"record\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssStars\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`origin` TEXT NOT NULL, `sort` TEXT NOT NULL, `title` TEXT NOT NULL, `starTime` INTEGER NOT NULL, `link` TEXT NOT NULL, `pubDate` TEXT, `description` TEXT, `content` TEXT, `image` TEXT, `variable` TEXT, PRIMARY KEY(`origin`, `link`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sort\",\n            \"columnName\": \"sort\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"starTime\",\n            \"columnName\": \"starTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"link\",\n            \"columnName\": \"link\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"pubDate\",\n            \"columnName\": \"pubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"description\",\n            \"columnName\": \"description\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"image\",\n            \"columnName\": \"image\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"origin\",\n            \"link\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"txtTocRules\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `rule` TEXT NOT NULL, `example` TEXT, `serialNumber` INTEGER NOT NULL, `enable` INTEGER NOT NULL, PRIMARY KEY(`id`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"rule\",\n            \"columnName\": \"rule\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"example\",\n            \"columnName\": \"example\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"serialNumber\",\n            \"columnName\": \"serialNumber\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enable\",\n            \"columnName\": \"enable\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"id\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"readRecord\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`deviceId` TEXT NOT NULL, `bookName` TEXT NOT NULL, `readTime` INTEGER NOT NULL DEFAULT 0, `lastRead` INTEGER NOT NULL DEFAULT 0, PRIMARY KEY(`deviceId`, `bookName`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"deviceId\",\n            \"columnName\": \"deviceId\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookName\",\n            \"columnName\": \"bookName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"readTime\",\n            \"columnName\": \"readTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"lastRead\",\n            \"columnName\": \"lastRead\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"deviceId\",\n            \"bookName\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"httpTTS\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `url` TEXT NOT NULL, `contentType` TEXT, `concurrentRate` TEXT DEFAULT '0', `loginUrl` TEXT, `loginUi` TEXT, `header` TEXT, `jsLib` TEXT, `enabledCookieJar` INTEGER DEFAULT 0, `loginCheckJs` TEXT, `lastUpdateTime` INTEGER NOT NULL DEFAULT 0, PRIMARY KEY(`id`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"contentType\",\n            \"columnName\": \"contentType\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"concurrentRate\",\n            \"columnName\": \"concurrentRate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false,\n            \"defaultValue\": \"'0'\"\n          },\n          {\n            \"fieldPath\": \"loginUrl\",\n            \"columnName\": \"loginUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUi\",\n            \"columnName\": \"loginUi\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"header\",\n            \"columnName\": \"header\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"jsLib\",\n            \"columnName\": \"jsLib\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enabledCookieJar\",\n            \"columnName\": \"enabledCookieJar\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"loginCheckJs\",\n            \"columnName\": \"loginCheckJs\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"lastUpdateTime\",\n            \"columnName\": \"lastUpdateTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"id\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"caches\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`key` TEXT NOT NULL, `value` TEXT, `deadline` INTEGER NOT NULL, PRIMARY KEY(`key`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"key\",\n            \"columnName\": \"key\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"value\",\n            \"columnName\": \"value\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"deadline\",\n            \"columnName\": \"deadline\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"key\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_caches_key\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"key\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_caches_key` ON `${TABLE_NAME}` (`key`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"ruleSubs\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `url` TEXT NOT NULL, `type` INTEGER NOT NULL, `customOrder` INTEGER NOT NULL, `autoUpdate` INTEGER NOT NULL, `update` INTEGER NOT NULL, PRIMARY KEY(`id`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"autoUpdate\",\n            \"columnName\": \"autoUpdate\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"update\",\n            \"columnName\": \"update\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"id\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"dictRules\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`name` TEXT NOT NULL, `urlRule` TEXT NOT NULL, `showRule` TEXT NOT NULL, `enabled` INTEGER NOT NULL DEFAULT 1, `sortNumber` INTEGER NOT NULL DEFAULT 0, PRIMARY KEY(`name`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"urlRule\",\n            \"columnName\": \"urlRule\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"showRule\",\n            \"columnName\": \"showRule\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enabled\",\n            \"columnName\": \"enabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"sortNumber\",\n            \"columnName\": \"sortNumber\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"name\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"keyboardAssists\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`type` INTEGER NOT NULL DEFAULT 0, `key` TEXT NOT NULL DEFAULT '', `value` TEXT NOT NULL DEFAULT '', `serialNo` INTEGER NOT NULL DEFAULT 0, PRIMARY KEY(`type`, `key`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"key\",\n            \"columnName\": \"key\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"value\",\n            \"columnName\": \"value\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"serialNo\",\n            \"columnName\": \"serialNo\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"type\",\n            \"key\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"servers\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `type` TEXT NOT NULL, `config` TEXT, `sortNumber` INTEGER NOT NULL, PRIMARY KEY(`id`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"config\",\n            \"columnName\": \"config\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"sortNumber\",\n            \"columnName\": \"sortNumber\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"id\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      }\n    ],\n    \"views\": [],\n    \"setupQueries\": [\n      \"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)\",\n      \"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '6e17b5366286b868f2912c12d6d8e467')\"\n    ]\n  }\n}"
  },
  {
    "path": "app/schemas/io.legado.app.data.AppDatabase/72.json",
    "content": "{\n  \"formatVersion\": 1,\n  \"database\": {\n    \"version\": 72,\n    \"identityHash\": \"53d960f1fdc04a6f157a25b269a0d93b\",\n    \"entities\": [\n      {\n        \"tableName\": \"books\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookUrl` TEXT NOT NULL DEFAULT '', `tocUrl` TEXT NOT NULL DEFAULT '', `origin` TEXT NOT NULL DEFAULT 'loc_book', `originName` TEXT NOT NULL DEFAULT '', `name` TEXT NOT NULL DEFAULT '', `author` TEXT NOT NULL DEFAULT '', `kind` TEXT, `customTag` TEXT, `coverUrl` TEXT, `customCoverUrl` TEXT, `intro` TEXT, `customIntro` TEXT, `charset` TEXT, `type` INTEGER NOT NULL DEFAULT 0, `group` INTEGER NOT NULL DEFAULT 0, `latestChapterTitle` TEXT, `latestChapterTime` INTEGER NOT NULL DEFAULT 0, `lastCheckTime` INTEGER NOT NULL DEFAULT 0, `lastCheckCount` INTEGER NOT NULL DEFAULT 0, `totalChapterNum` INTEGER NOT NULL DEFAULT 0, `durChapterTitle` TEXT, `durChapterIndex` INTEGER NOT NULL DEFAULT 0, `durChapterPos` INTEGER NOT NULL DEFAULT 0, `durChapterTime` INTEGER NOT NULL DEFAULT 0, `wordCount` TEXT, `canUpdate` INTEGER NOT NULL DEFAULT 1, `order` INTEGER NOT NULL DEFAULT 0, `originOrder` INTEGER NOT NULL DEFAULT 0, `variable` TEXT, `readConfig` TEXT, `syncTime` INTEGER NOT NULL DEFAULT 0, PRIMARY KEY(`bookUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"tocUrl\",\n            \"columnName\": \"tocUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"'loc_book'\"\n          },\n          {\n            \"fieldPath\": \"originName\",\n            \"columnName\": \"originName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"author\",\n            \"columnName\": \"author\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"kind\",\n            \"columnName\": \"kind\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customTag\",\n            \"columnName\": \"customTag\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"coverUrl\",\n            \"columnName\": \"coverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customCoverUrl\",\n            \"columnName\": \"customCoverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"intro\",\n            \"columnName\": \"intro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customIntro\",\n            \"columnName\": \"customIntro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"charset\",\n            \"columnName\": \"charset\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"group\",\n            \"columnName\": \"group\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"latestChapterTitle\",\n            \"columnName\": \"latestChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"latestChapterTime\",\n            \"columnName\": \"latestChapterTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"lastCheckTime\",\n            \"columnName\": \"lastCheckTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"lastCheckCount\",\n            \"columnName\": \"lastCheckCount\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"totalChapterNum\",\n            \"columnName\": \"totalChapterNum\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"durChapterTitle\",\n            \"columnName\": \"durChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"durChapterIndex\",\n            \"columnName\": \"durChapterIndex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"durChapterPos\",\n            \"columnName\": \"durChapterPos\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"durChapterTime\",\n            \"columnName\": \"durChapterTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"wordCount\",\n            \"columnName\": \"wordCount\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"canUpdate\",\n            \"columnName\": \"canUpdate\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"originOrder\",\n            \"columnName\": \"originOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"readConfig\",\n            \"columnName\": \"readConfig\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"syncTime\",\n            \"columnName\": \"syncTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"bookUrl\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_books_name_author\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"name\",\n              \"author\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_books_name_author` ON `${TABLE_NAME}` (`name`, `author`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"book_groups\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`groupId` INTEGER NOT NULL, `groupName` TEXT NOT NULL, `cover` TEXT, `order` INTEGER NOT NULL, `enableRefresh` INTEGER NOT NULL DEFAULT 1, `show` INTEGER NOT NULL DEFAULT 1, `bookSort` INTEGER NOT NULL DEFAULT -1, PRIMARY KEY(`groupId`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"groupId\",\n            \"columnName\": \"groupId\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"groupName\",\n            \"columnName\": \"groupName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"cover\",\n            \"columnName\": \"cover\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enableRefresh\",\n            \"columnName\": \"enableRefresh\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"show\",\n            \"columnName\": \"show\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"bookSort\",\n            \"columnName\": \"bookSort\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"-1\"\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"groupId\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"book_sources\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookSourceUrl` TEXT NOT NULL, `bookSourceName` TEXT NOT NULL, `bookSourceGroup` TEXT, `bookSourceType` INTEGER NOT NULL, `bookUrlPattern` TEXT, `customOrder` INTEGER NOT NULL DEFAULT 0, `enabled` INTEGER NOT NULL DEFAULT 1, `enabledExplore` INTEGER NOT NULL DEFAULT 1, `jsLib` TEXT, `enabledCookieJar` INTEGER DEFAULT 0, `concurrentRate` TEXT, `header` TEXT, `loginUrl` TEXT, `loginUi` TEXT, `loginCheckJs` TEXT, `coverDecodeJs` TEXT, `bookSourceComment` TEXT, `variableComment` TEXT, `lastUpdateTime` INTEGER NOT NULL, `respondTime` INTEGER NOT NULL, `weight` INTEGER NOT NULL, `exploreUrl` TEXT, `exploreScreen` TEXT, `ruleExplore` TEXT, `searchUrl` TEXT, `ruleSearch` TEXT, `ruleBookInfo` TEXT, `ruleToc` TEXT, `ruleContent` TEXT, `ruleReview` TEXT, PRIMARY KEY(`bookSourceUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookSourceUrl\",\n            \"columnName\": \"bookSourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookSourceName\",\n            \"columnName\": \"bookSourceName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookSourceGroup\",\n            \"columnName\": \"bookSourceGroup\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"bookSourceType\",\n            \"columnName\": \"bookSourceType\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookUrlPattern\",\n            \"columnName\": \"bookUrlPattern\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"enabled\",\n            \"columnName\": \"enabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"enabledExplore\",\n            \"columnName\": \"enabledExplore\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"jsLib\",\n            \"columnName\": \"jsLib\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enabledCookieJar\",\n            \"columnName\": \"enabledCookieJar\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"concurrentRate\",\n            \"columnName\": \"concurrentRate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"header\",\n            \"columnName\": \"header\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUrl\",\n            \"columnName\": \"loginUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUi\",\n            \"columnName\": \"loginUi\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginCheckJs\",\n            \"columnName\": \"loginCheckJs\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"coverDecodeJs\",\n            \"columnName\": \"coverDecodeJs\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"bookSourceComment\",\n            \"columnName\": \"bookSourceComment\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"variableComment\",\n            \"columnName\": \"variableComment\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"lastUpdateTime\",\n            \"columnName\": \"lastUpdateTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"respondTime\",\n            \"columnName\": \"respondTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"weight\",\n            \"columnName\": \"weight\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"exploreUrl\",\n            \"columnName\": \"exploreUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"exploreScreen\",\n            \"columnName\": \"exploreScreen\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleExplore\",\n            \"columnName\": \"ruleExplore\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"searchUrl\",\n            \"columnName\": \"searchUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleSearch\",\n            \"columnName\": \"ruleSearch\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleBookInfo\",\n            \"columnName\": \"ruleBookInfo\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleToc\",\n            \"columnName\": \"ruleToc\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleContent\",\n            \"columnName\": \"ruleContent\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleReview\",\n            \"columnName\": \"ruleReview\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"bookSourceUrl\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_book_sources_bookSourceUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"bookSourceUrl\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_book_sources_bookSourceUrl` ON `${TABLE_NAME}` (`bookSourceUrl`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"chapters\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`url` TEXT NOT NULL, `title` TEXT NOT NULL, `isVolume` INTEGER NOT NULL, `baseUrl` TEXT NOT NULL, `bookUrl` TEXT NOT NULL, `index` INTEGER NOT NULL, `isVip` INTEGER NOT NULL, `isPay` INTEGER NOT NULL, `resourceUrl` TEXT, `tag` TEXT, `start` INTEGER, `end` INTEGER, `startFragmentId` TEXT, `endFragmentId` TEXT, `variable` TEXT, PRIMARY KEY(`url`, `bookUrl`), FOREIGN KEY(`bookUrl`) REFERENCES `books`(`bookUrl`) ON UPDATE NO ACTION ON DELETE CASCADE )\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"isVolume\",\n            \"columnName\": \"isVolume\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"baseUrl\",\n            \"columnName\": \"baseUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"index\",\n            \"columnName\": \"index\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"isVip\",\n            \"columnName\": \"isVip\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"isPay\",\n            \"columnName\": \"isPay\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"resourceUrl\",\n            \"columnName\": \"resourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"tag\",\n            \"columnName\": \"tag\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"start\",\n            \"columnName\": \"start\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"end\",\n            \"columnName\": \"end\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"startFragmentId\",\n            \"columnName\": \"startFragmentId\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"endFragmentId\",\n            \"columnName\": \"endFragmentId\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"url\",\n            \"bookUrl\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_chapters_bookUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"bookUrl\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_chapters_bookUrl` ON `${TABLE_NAME}` (`bookUrl`)\"\n          },\n          {\n            \"name\": \"index_chapters_bookUrl_index\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"bookUrl\",\n              \"index\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_chapters_bookUrl_index` ON `${TABLE_NAME}` (`bookUrl`, `index`)\"\n          }\n        ],\n        \"foreignKeys\": [\n          {\n            \"table\": \"books\",\n            \"onDelete\": \"CASCADE\",\n            \"onUpdate\": \"NO ACTION\",\n            \"columns\": [\n              \"bookUrl\"\n            ],\n            \"referencedColumns\": [\n              \"bookUrl\"\n            ]\n          }\n        ]\n      },\n      {\n        \"tableName\": \"replace_rules\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL DEFAULT '', `group` TEXT, `pattern` TEXT NOT NULL DEFAULT '', `replacement` TEXT NOT NULL DEFAULT '', `scope` TEXT, `scopeTitle` INTEGER NOT NULL DEFAULT 0, `scopeContent` INTEGER NOT NULL DEFAULT 1, `excludeScope` TEXT, `isEnabled` INTEGER NOT NULL DEFAULT 1, `isRegex` INTEGER NOT NULL DEFAULT 1, `timeoutMillisecond` INTEGER NOT NULL DEFAULT 3000, `sortOrder` INTEGER NOT NULL DEFAULT 0)\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"group\",\n            \"columnName\": \"group\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"pattern\",\n            \"columnName\": \"pattern\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"replacement\",\n            \"columnName\": \"replacement\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"scope\",\n            \"columnName\": \"scope\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"scopeTitle\",\n            \"columnName\": \"scopeTitle\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"scopeContent\",\n            \"columnName\": \"scopeContent\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"excludeScope\",\n            \"columnName\": \"excludeScope\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"isEnabled\",\n            \"columnName\": \"isEnabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"isRegex\",\n            \"columnName\": \"isRegex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"timeoutMillisecond\",\n            \"columnName\": \"timeoutMillisecond\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"3000\"\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"sortOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": true,\n          \"columnNames\": [\n            \"id\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_replace_rules_id\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"id\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_replace_rules_id` ON `${TABLE_NAME}` (`id`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"searchBooks\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookUrl` TEXT NOT NULL, `origin` TEXT NOT NULL, `originName` TEXT NOT NULL, `type` INTEGER NOT NULL, `name` TEXT NOT NULL, `author` TEXT NOT NULL, `kind` TEXT, `coverUrl` TEXT, `intro` TEXT, `wordCount` TEXT, `latestChapterTitle` TEXT, `tocUrl` TEXT NOT NULL, `time` INTEGER NOT NULL, `variable` TEXT, `originOrder` INTEGER NOT NULL, `chapterWordCountText` TEXT, `chapterWordCount` INTEGER NOT NULL DEFAULT -1, `respondTime` INTEGER NOT NULL DEFAULT -1, PRIMARY KEY(`bookUrl`), FOREIGN KEY(`origin`) REFERENCES `book_sources`(`bookSourceUrl`) ON UPDATE NO ACTION ON DELETE CASCADE )\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"originName\",\n            \"columnName\": \"originName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"author\",\n            \"columnName\": \"author\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"kind\",\n            \"columnName\": \"kind\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"coverUrl\",\n            \"columnName\": \"coverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"intro\",\n            \"columnName\": \"intro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"wordCount\",\n            \"columnName\": \"wordCount\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"latestChapterTitle\",\n            \"columnName\": \"latestChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"tocUrl\",\n            \"columnName\": \"tocUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"time\",\n            \"columnName\": \"time\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"originOrder\",\n            \"columnName\": \"originOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterWordCountText\",\n            \"columnName\": \"chapterWordCountText\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"chapterWordCount\",\n            \"columnName\": \"chapterWordCount\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"-1\"\n          },\n          {\n            \"fieldPath\": \"respondTime\",\n            \"columnName\": \"respondTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"-1\"\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"bookUrl\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_searchBooks_bookUrl\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"bookUrl\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_searchBooks_bookUrl` ON `${TABLE_NAME}` (`bookUrl`)\"\n          },\n          {\n            \"name\": \"index_searchBooks_origin\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"origin\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_searchBooks_origin` ON `${TABLE_NAME}` (`origin`)\"\n          }\n        ],\n        \"foreignKeys\": [\n          {\n            \"table\": \"book_sources\",\n            \"onDelete\": \"CASCADE\",\n            \"onUpdate\": \"NO ACTION\",\n            \"columns\": [\n              \"origin\"\n            ],\n            \"referencedColumns\": [\n              \"bookSourceUrl\"\n            ]\n          }\n        ]\n      },\n      {\n        \"tableName\": \"search_keywords\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`word` TEXT NOT NULL, `usage` INTEGER NOT NULL, `lastUseTime` INTEGER NOT NULL, PRIMARY KEY(`word`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"word\",\n            \"columnName\": \"word\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"usage\",\n            \"columnName\": \"usage\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"lastUseTime\",\n            \"columnName\": \"lastUseTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"word\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_search_keywords_word\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"word\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_search_keywords_word` ON `${TABLE_NAME}` (`word`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"cookies\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`url` TEXT NOT NULL, `cookie` TEXT NOT NULL, PRIMARY KEY(`url`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"cookie\",\n            \"columnName\": \"cookie\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"url\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_cookies_url\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"url\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_cookies_url` ON `${TABLE_NAME}` (`url`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssSources\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`sourceUrl` TEXT NOT NULL, `sourceName` TEXT NOT NULL, `sourceIcon` TEXT NOT NULL, `sourceGroup` TEXT, `sourceComment` TEXT, `enabled` INTEGER NOT NULL, `variableComment` TEXT, `jsLib` TEXT, `enabledCookieJar` INTEGER DEFAULT 0, `concurrentRate` TEXT, `header` TEXT, `loginUrl` TEXT, `loginUi` TEXT, `loginCheckJs` TEXT, `coverDecodeJs` TEXT, `sortUrl` TEXT, `singleUrl` INTEGER NOT NULL, `articleStyle` INTEGER NOT NULL DEFAULT 0, `ruleArticles` TEXT, `ruleNextPage` TEXT, `ruleTitle` TEXT, `rulePubDate` TEXT, `ruleDescription` TEXT, `ruleImage` TEXT, `ruleLink` TEXT, `ruleContent` TEXT, `contentWhitelist` TEXT, `contentBlacklist` TEXT, `shouldOverrideUrlLoading` TEXT, `style` TEXT, `enableJs` INTEGER NOT NULL DEFAULT 1, `loadWithBaseUrl` INTEGER NOT NULL DEFAULT 1, `injectJs` TEXT, `lastUpdateTime` INTEGER NOT NULL DEFAULT 0, `customOrder` INTEGER NOT NULL DEFAULT 0, PRIMARY KEY(`sourceUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"sourceUrl\",\n            \"columnName\": \"sourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceName\",\n            \"columnName\": \"sourceName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceIcon\",\n            \"columnName\": \"sourceIcon\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceGroup\",\n            \"columnName\": \"sourceGroup\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"sourceComment\",\n            \"columnName\": \"sourceComment\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enabled\",\n            \"columnName\": \"enabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"variableComment\",\n            \"columnName\": \"variableComment\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"jsLib\",\n            \"columnName\": \"jsLib\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enabledCookieJar\",\n            \"columnName\": \"enabledCookieJar\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"concurrentRate\",\n            \"columnName\": \"concurrentRate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"header\",\n            \"columnName\": \"header\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUrl\",\n            \"columnName\": \"loginUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUi\",\n            \"columnName\": \"loginUi\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginCheckJs\",\n            \"columnName\": \"loginCheckJs\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"coverDecodeJs\",\n            \"columnName\": \"coverDecodeJs\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"sortUrl\",\n            \"columnName\": \"sortUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"singleUrl\",\n            \"columnName\": \"singleUrl\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"articleStyle\",\n            \"columnName\": \"articleStyle\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"ruleArticles\",\n            \"columnName\": \"ruleArticles\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleNextPage\",\n            \"columnName\": \"ruleNextPage\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleTitle\",\n            \"columnName\": \"ruleTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"rulePubDate\",\n            \"columnName\": \"rulePubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleDescription\",\n            \"columnName\": \"ruleDescription\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleImage\",\n            \"columnName\": \"ruleImage\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleLink\",\n            \"columnName\": \"ruleLink\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleContent\",\n            \"columnName\": \"ruleContent\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"contentWhitelist\",\n            \"columnName\": \"contentWhitelist\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"contentBlacklist\",\n            \"columnName\": \"contentBlacklist\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"shouldOverrideUrlLoading\",\n            \"columnName\": \"shouldOverrideUrlLoading\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"style\",\n            \"columnName\": \"style\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enableJs\",\n            \"columnName\": \"enableJs\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"loadWithBaseUrl\",\n            \"columnName\": \"loadWithBaseUrl\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"injectJs\",\n            \"columnName\": \"injectJs\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"lastUpdateTime\",\n            \"columnName\": \"lastUpdateTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"sourceUrl\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_rssSources_sourceUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"sourceUrl\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_rssSources_sourceUrl` ON `${TABLE_NAME}` (`sourceUrl`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"bookmarks\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`time` INTEGER NOT NULL, `bookName` TEXT NOT NULL, `bookAuthor` TEXT NOT NULL, `chapterIndex` INTEGER NOT NULL, `chapterPos` INTEGER NOT NULL, `chapterName` TEXT NOT NULL, `bookText` TEXT NOT NULL, `content` TEXT NOT NULL, PRIMARY KEY(`time`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"time\",\n            \"columnName\": \"time\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookName\",\n            \"columnName\": \"bookName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookAuthor\",\n            \"columnName\": \"bookAuthor\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterIndex\",\n            \"columnName\": \"chapterIndex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterPos\",\n            \"columnName\": \"chapterPos\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterName\",\n            \"columnName\": \"chapterName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookText\",\n            \"columnName\": \"bookText\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"time\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_bookmarks_bookName_bookAuthor\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"bookName\",\n              \"bookAuthor\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_bookmarks_bookName_bookAuthor` ON `${TABLE_NAME}` (`bookName`, `bookAuthor`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssArticles\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`origin` TEXT NOT NULL, `sort` TEXT NOT NULL, `title` TEXT NOT NULL, `order` INTEGER NOT NULL, `link` TEXT NOT NULL, `pubDate` TEXT, `description` TEXT, `content` TEXT, `image` TEXT, `group` TEXT NOT NULL DEFAULT '默认分组', `read` INTEGER NOT NULL, `variable` TEXT, PRIMARY KEY(`origin`, `link`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sort\",\n            \"columnName\": \"sort\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"link\",\n            \"columnName\": \"link\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"pubDate\",\n            \"columnName\": \"pubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"description\",\n            \"columnName\": \"description\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"image\",\n            \"columnName\": \"image\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"group\",\n            \"columnName\": \"group\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"'默认分组'\"\n          },\n          {\n            \"fieldPath\": \"read\",\n            \"columnName\": \"read\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"origin\",\n            \"link\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssReadRecords\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`record` TEXT NOT NULL, `read` INTEGER NOT NULL, PRIMARY KEY(`record`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"record\",\n            \"columnName\": \"record\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"read\",\n            \"columnName\": \"read\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"record\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssStars\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`origin` TEXT NOT NULL, `sort` TEXT NOT NULL, `title` TEXT NOT NULL, `starTime` INTEGER NOT NULL, `link` TEXT NOT NULL, `pubDate` TEXT, `description` TEXT, `content` TEXT, `image` TEXT, `group` TEXT NOT NULL DEFAULT '默认分组', `variable` TEXT, PRIMARY KEY(`origin`, `link`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sort\",\n            \"columnName\": \"sort\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"starTime\",\n            \"columnName\": \"starTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"link\",\n            \"columnName\": \"link\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"pubDate\",\n            \"columnName\": \"pubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"description\",\n            \"columnName\": \"description\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"image\",\n            \"columnName\": \"image\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"group\",\n            \"columnName\": \"group\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"'默认分组'\"\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"origin\",\n            \"link\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"txtTocRules\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `rule` TEXT NOT NULL, `example` TEXT, `serialNumber` INTEGER NOT NULL, `enable` INTEGER NOT NULL, PRIMARY KEY(`id`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"rule\",\n            \"columnName\": \"rule\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"example\",\n            \"columnName\": \"example\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"serialNumber\",\n            \"columnName\": \"serialNumber\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enable\",\n            \"columnName\": \"enable\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"id\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"readRecord\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`deviceId` TEXT NOT NULL, `bookName` TEXT NOT NULL, `readTime` INTEGER NOT NULL DEFAULT 0, `lastRead` INTEGER NOT NULL DEFAULT 0, PRIMARY KEY(`deviceId`, `bookName`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"deviceId\",\n            \"columnName\": \"deviceId\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookName\",\n            \"columnName\": \"bookName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"readTime\",\n            \"columnName\": \"readTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"lastRead\",\n            \"columnName\": \"lastRead\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"deviceId\",\n            \"bookName\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"httpTTS\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `url` TEXT NOT NULL, `contentType` TEXT, `concurrentRate` TEXT DEFAULT '0', `loginUrl` TEXT, `loginUi` TEXT, `header` TEXT, `jsLib` TEXT, `enabledCookieJar` INTEGER DEFAULT 0, `loginCheckJs` TEXT, `lastUpdateTime` INTEGER NOT NULL DEFAULT 0, PRIMARY KEY(`id`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"contentType\",\n            \"columnName\": \"contentType\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"concurrentRate\",\n            \"columnName\": \"concurrentRate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false,\n            \"defaultValue\": \"'0'\"\n          },\n          {\n            \"fieldPath\": \"loginUrl\",\n            \"columnName\": \"loginUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUi\",\n            \"columnName\": \"loginUi\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"header\",\n            \"columnName\": \"header\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"jsLib\",\n            \"columnName\": \"jsLib\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enabledCookieJar\",\n            \"columnName\": \"enabledCookieJar\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"loginCheckJs\",\n            \"columnName\": \"loginCheckJs\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"lastUpdateTime\",\n            \"columnName\": \"lastUpdateTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"id\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"caches\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`key` TEXT NOT NULL, `value` TEXT, `deadline` INTEGER NOT NULL, PRIMARY KEY(`key`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"key\",\n            \"columnName\": \"key\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"value\",\n            \"columnName\": \"value\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"deadline\",\n            \"columnName\": \"deadline\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"key\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_caches_key\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"key\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_caches_key` ON `${TABLE_NAME}` (`key`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"ruleSubs\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `url` TEXT NOT NULL, `type` INTEGER NOT NULL, `customOrder` INTEGER NOT NULL, `autoUpdate` INTEGER NOT NULL, `update` INTEGER NOT NULL, PRIMARY KEY(`id`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"autoUpdate\",\n            \"columnName\": \"autoUpdate\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"update\",\n            \"columnName\": \"update\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"id\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"dictRules\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`name` TEXT NOT NULL, `urlRule` TEXT NOT NULL, `showRule` TEXT NOT NULL, `enabled` INTEGER NOT NULL DEFAULT 1, `sortNumber` INTEGER NOT NULL DEFAULT 0, PRIMARY KEY(`name`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"urlRule\",\n            \"columnName\": \"urlRule\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"showRule\",\n            \"columnName\": \"showRule\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enabled\",\n            \"columnName\": \"enabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"sortNumber\",\n            \"columnName\": \"sortNumber\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"name\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"keyboardAssists\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`type` INTEGER NOT NULL DEFAULT 0, `key` TEXT NOT NULL DEFAULT '', `value` TEXT NOT NULL DEFAULT '', `serialNo` INTEGER NOT NULL DEFAULT 0, PRIMARY KEY(`type`, `key`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"key\",\n            \"columnName\": \"key\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"value\",\n            \"columnName\": \"value\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"serialNo\",\n            \"columnName\": \"serialNo\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"type\",\n            \"key\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"servers\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `type` TEXT NOT NULL, `config` TEXT, `sortNumber` INTEGER NOT NULL, PRIMARY KEY(`id`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"config\",\n            \"columnName\": \"config\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"sortNumber\",\n            \"columnName\": \"sortNumber\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"id\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      }\n    ],\n    \"views\": [],\n    \"setupQueries\": [\n      \"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)\",\n      \"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '53d960f1fdc04a6f157a25b269a0d93b')\"\n    ]\n  }\n}"
  },
  {
    "path": "app/schemas/io.legado.app.data.AppDatabase/73.json",
    "content": "{\n  \"formatVersion\": 1,\n  \"database\": {\n    \"version\": 73,\n    \"identityHash\": \"63617164079ebbdaf82176384c39bba5\",\n    \"entities\": [\n      {\n        \"tableName\": \"books\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookUrl` TEXT NOT NULL DEFAULT '', `tocUrl` TEXT NOT NULL DEFAULT '', `origin` TEXT NOT NULL DEFAULT 'loc_book', `originName` TEXT NOT NULL DEFAULT '', `name` TEXT NOT NULL DEFAULT '', `author` TEXT NOT NULL DEFAULT '', `kind` TEXT, `customTag` TEXT, `coverUrl` TEXT, `customCoverUrl` TEXT, `intro` TEXT, `customIntro` TEXT, `charset` TEXT, `type` INTEGER NOT NULL DEFAULT 0, `group` INTEGER NOT NULL DEFAULT 0, `latestChapterTitle` TEXT, `latestChapterTime` INTEGER NOT NULL DEFAULT 0, `lastCheckTime` INTEGER NOT NULL DEFAULT 0, `lastCheckCount` INTEGER NOT NULL DEFAULT 0, `totalChapterNum` INTEGER NOT NULL DEFAULT 0, `durChapterTitle` TEXT, `durChapterIndex` INTEGER NOT NULL DEFAULT 0, `durChapterPos` INTEGER NOT NULL DEFAULT 0, `durChapterTime` INTEGER NOT NULL DEFAULT 0, `wordCount` TEXT, `canUpdate` INTEGER NOT NULL DEFAULT 1, `order` INTEGER NOT NULL DEFAULT 0, `originOrder` INTEGER NOT NULL DEFAULT 0, `variable` TEXT, `readConfig` TEXT, `syncTime` INTEGER NOT NULL DEFAULT 0, PRIMARY KEY(`bookUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"tocUrl\",\n            \"columnName\": \"tocUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"'loc_book'\"\n          },\n          {\n            \"fieldPath\": \"originName\",\n            \"columnName\": \"originName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"author\",\n            \"columnName\": \"author\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"kind\",\n            \"columnName\": \"kind\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customTag\",\n            \"columnName\": \"customTag\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"coverUrl\",\n            \"columnName\": \"coverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customCoverUrl\",\n            \"columnName\": \"customCoverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"intro\",\n            \"columnName\": \"intro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customIntro\",\n            \"columnName\": \"customIntro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"charset\",\n            \"columnName\": \"charset\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"group\",\n            \"columnName\": \"group\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"latestChapterTitle\",\n            \"columnName\": \"latestChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"latestChapterTime\",\n            \"columnName\": \"latestChapterTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"lastCheckTime\",\n            \"columnName\": \"lastCheckTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"lastCheckCount\",\n            \"columnName\": \"lastCheckCount\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"totalChapterNum\",\n            \"columnName\": \"totalChapterNum\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"durChapterTitle\",\n            \"columnName\": \"durChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"durChapterIndex\",\n            \"columnName\": \"durChapterIndex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"durChapterPos\",\n            \"columnName\": \"durChapterPos\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"durChapterTime\",\n            \"columnName\": \"durChapterTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"wordCount\",\n            \"columnName\": \"wordCount\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"canUpdate\",\n            \"columnName\": \"canUpdate\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"originOrder\",\n            \"columnName\": \"originOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"readConfig\",\n            \"columnName\": \"readConfig\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"syncTime\",\n            \"columnName\": \"syncTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"bookUrl\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_books_name_author\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"name\",\n              \"author\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_books_name_author` ON `${TABLE_NAME}` (`name`, `author`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"book_groups\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`groupId` INTEGER NOT NULL, `groupName` TEXT NOT NULL, `cover` TEXT, `order` INTEGER NOT NULL, `enableRefresh` INTEGER NOT NULL DEFAULT 1, `show` INTEGER NOT NULL DEFAULT 1, `bookSort` INTEGER NOT NULL DEFAULT -1, PRIMARY KEY(`groupId`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"groupId\",\n            \"columnName\": \"groupId\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"groupName\",\n            \"columnName\": \"groupName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"cover\",\n            \"columnName\": \"cover\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enableRefresh\",\n            \"columnName\": \"enableRefresh\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"show\",\n            \"columnName\": \"show\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"bookSort\",\n            \"columnName\": \"bookSort\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"-1\"\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"groupId\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"book_sources\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookSourceUrl` TEXT NOT NULL, `bookSourceName` TEXT NOT NULL, `bookSourceGroup` TEXT, `bookSourceType` INTEGER NOT NULL, `bookUrlPattern` TEXT, `customOrder` INTEGER NOT NULL DEFAULT 0, `enabled` INTEGER NOT NULL DEFAULT 1, `enabledExplore` INTEGER NOT NULL DEFAULT 1, `jsLib` TEXT, `enabledCookieJar` INTEGER DEFAULT 0, `concurrentRate` TEXT, `header` TEXT, `loginUrl` TEXT, `loginUi` TEXT, `loginCheckJs` TEXT, `coverDecodeJs` TEXT, `bookSourceComment` TEXT, `variableComment` TEXT, `lastUpdateTime` INTEGER NOT NULL, `respondTime` INTEGER NOT NULL, `weight` INTEGER NOT NULL, `exploreUrl` TEXT, `exploreScreen` TEXT, `ruleExplore` TEXT, `searchUrl` TEXT, `ruleSearch` TEXT, `ruleBookInfo` TEXT, `ruleToc` TEXT, `ruleContent` TEXT, `ruleReview` TEXT, PRIMARY KEY(`bookSourceUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookSourceUrl\",\n            \"columnName\": \"bookSourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookSourceName\",\n            \"columnName\": \"bookSourceName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookSourceGroup\",\n            \"columnName\": \"bookSourceGroup\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"bookSourceType\",\n            \"columnName\": \"bookSourceType\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookUrlPattern\",\n            \"columnName\": \"bookUrlPattern\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"enabled\",\n            \"columnName\": \"enabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"enabledExplore\",\n            \"columnName\": \"enabledExplore\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"jsLib\",\n            \"columnName\": \"jsLib\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enabledCookieJar\",\n            \"columnName\": \"enabledCookieJar\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"concurrentRate\",\n            \"columnName\": \"concurrentRate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"header\",\n            \"columnName\": \"header\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUrl\",\n            \"columnName\": \"loginUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUi\",\n            \"columnName\": \"loginUi\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginCheckJs\",\n            \"columnName\": \"loginCheckJs\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"coverDecodeJs\",\n            \"columnName\": \"coverDecodeJs\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"bookSourceComment\",\n            \"columnName\": \"bookSourceComment\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"variableComment\",\n            \"columnName\": \"variableComment\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"lastUpdateTime\",\n            \"columnName\": \"lastUpdateTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"respondTime\",\n            \"columnName\": \"respondTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"weight\",\n            \"columnName\": \"weight\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"exploreUrl\",\n            \"columnName\": \"exploreUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"exploreScreen\",\n            \"columnName\": \"exploreScreen\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleExplore\",\n            \"columnName\": \"ruleExplore\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"searchUrl\",\n            \"columnName\": \"searchUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleSearch\",\n            \"columnName\": \"ruleSearch\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleBookInfo\",\n            \"columnName\": \"ruleBookInfo\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleToc\",\n            \"columnName\": \"ruleToc\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleContent\",\n            \"columnName\": \"ruleContent\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleReview\",\n            \"columnName\": \"ruleReview\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"bookSourceUrl\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_book_sources_bookSourceUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"bookSourceUrl\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_book_sources_bookSourceUrl` ON `${TABLE_NAME}` (`bookSourceUrl`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"chapters\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`url` TEXT NOT NULL, `title` TEXT NOT NULL, `isVolume` INTEGER NOT NULL, `baseUrl` TEXT NOT NULL, `bookUrl` TEXT NOT NULL, `index` INTEGER NOT NULL, `isVip` INTEGER NOT NULL, `isPay` INTEGER NOT NULL, `resourceUrl` TEXT, `tag` TEXT, `wordCount` TEXT, `start` INTEGER, `end` INTEGER, `startFragmentId` TEXT, `endFragmentId` TEXT, `variable` TEXT, PRIMARY KEY(`url`, `bookUrl`), FOREIGN KEY(`bookUrl`) REFERENCES `books`(`bookUrl`) ON UPDATE NO ACTION ON DELETE CASCADE )\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"isVolume\",\n            \"columnName\": \"isVolume\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"baseUrl\",\n            \"columnName\": \"baseUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"index\",\n            \"columnName\": \"index\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"isVip\",\n            \"columnName\": \"isVip\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"isPay\",\n            \"columnName\": \"isPay\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"resourceUrl\",\n            \"columnName\": \"resourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"tag\",\n            \"columnName\": \"tag\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"wordCount\",\n            \"columnName\": \"wordCount\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"start\",\n            \"columnName\": \"start\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"end\",\n            \"columnName\": \"end\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"startFragmentId\",\n            \"columnName\": \"startFragmentId\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"endFragmentId\",\n            \"columnName\": \"endFragmentId\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"url\",\n            \"bookUrl\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_chapters_bookUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"bookUrl\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_chapters_bookUrl` ON `${TABLE_NAME}` (`bookUrl`)\"\n          },\n          {\n            \"name\": \"index_chapters_bookUrl_index\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"bookUrl\",\n              \"index\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_chapters_bookUrl_index` ON `${TABLE_NAME}` (`bookUrl`, `index`)\"\n          }\n        ],\n        \"foreignKeys\": [\n          {\n            \"table\": \"books\",\n            \"onDelete\": \"CASCADE\",\n            \"onUpdate\": \"NO ACTION\",\n            \"columns\": [\n              \"bookUrl\"\n            ],\n            \"referencedColumns\": [\n              \"bookUrl\"\n            ]\n          }\n        ]\n      },\n      {\n        \"tableName\": \"replace_rules\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL DEFAULT '', `group` TEXT, `pattern` TEXT NOT NULL DEFAULT '', `replacement` TEXT NOT NULL DEFAULT '', `scope` TEXT, `scopeTitle` INTEGER NOT NULL DEFAULT 0, `scopeContent` INTEGER NOT NULL DEFAULT 1, `excludeScope` TEXT, `isEnabled` INTEGER NOT NULL DEFAULT 1, `isRegex` INTEGER NOT NULL DEFAULT 1, `timeoutMillisecond` INTEGER NOT NULL DEFAULT 3000, `sortOrder` INTEGER NOT NULL DEFAULT 0)\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"group\",\n            \"columnName\": \"group\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"pattern\",\n            \"columnName\": \"pattern\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"replacement\",\n            \"columnName\": \"replacement\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"scope\",\n            \"columnName\": \"scope\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"scopeTitle\",\n            \"columnName\": \"scopeTitle\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"scopeContent\",\n            \"columnName\": \"scopeContent\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"excludeScope\",\n            \"columnName\": \"excludeScope\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"isEnabled\",\n            \"columnName\": \"isEnabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"isRegex\",\n            \"columnName\": \"isRegex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"timeoutMillisecond\",\n            \"columnName\": \"timeoutMillisecond\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"3000\"\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"sortOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": true,\n          \"columnNames\": [\n            \"id\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_replace_rules_id\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"id\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_replace_rules_id` ON `${TABLE_NAME}` (`id`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"searchBooks\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookUrl` TEXT NOT NULL, `origin` TEXT NOT NULL, `originName` TEXT NOT NULL, `type` INTEGER NOT NULL, `name` TEXT NOT NULL, `author` TEXT NOT NULL, `kind` TEXT, `coverUrl` TEXT, `intro` TEXT, `wordCount` TEXT, `latestChapterTitle` TEXT, `tocUrl` TEXT NOT NULL, `time` INTEGER NOT NULL, `variable` TEXT, `originOrder` INTEGER NOT NULL, `chapterWordCountText` TEXT, `chapterWordCount` INTEGER NOT NULL DEFAULT -1, `respondTime` INTEGER NOT NULL DEFAULT -1, PRIMARY KEY(`bookUrl`), FOREIGN KEY(`origin`) REFERENCES `book_sources`(`bookSourceUrl`) ON UPDATE NO ACTION ON DELETE CASCADE )\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"originName\",\n            \"columnName\": \"originName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"author\",\n            \"columnName\": \"author\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"kind\",\n            \"columnName\": \"kind\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"coverUrl\",\n            \"columnName\": \"coverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"intro\",\n            \"columnName\": \"intro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"wordCount\",\n            \"columnName\": \"wordCount\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"latestChapterTitle\",\n            \"columnName\": \"latestChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"tocUrl\",\n            \"columnName\": \"tocUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"time\",\n            \"columnName\": \"time\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"originOrder\",\n            \"columnName\": \"originOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterWordCountText\",\n            \"columnName\": \"chapterWordCountText\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"chapterWordCount\",\n            \"columnName\": \"chapterWordCount\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"-1\"\n          },\n          {\n            \"fieldPath\": \"respondTime\",\n            \"columnName\": \"respondTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"-1\"\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"bookUrl\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_searchBooks_bookUrl\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"bookUrl\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_searchBooks_bookUrl` ON `${TABLE_NAME}` (`bookUrl`)\"\n          },\n          {\n            \"name\": \"index_searchBooks_origin\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"origin\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_searchBooks_origin` ON `${TABLE_NAME}` (`origin`)\"\n          }\n        ],\n        \"foreignKeys\": [\n          {\n            \"table\": \"book_sources\",\n            \"onDelete\": \"CASCADE\",\n            \"onUpdate\": \"NO ACTION\",\n            \"columns\": [\n              \"origin\"\n            ],\n            \"referencedColumns\": [\n              \"bookSourceUrl\"\n            ]\n          }\n        ]\n      },\n      {\n        \"tableName\": \"search_keywords\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`word` TEXT NOT NULL, `usage` INTEGER NOT NULL, `lastUseTime` INTEGER NOT NULL, PRIMARY KEY(`word`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"word\",\n            \"columnName\": \"word\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"usage\",\n            \"columnName\": \"usage\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"lastUseTime\",\n            \"columnName\": \"lastUseTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"word\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_search_keywords_word\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"word\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_search_keywords_word` ON `${TABLE_NAME}` (`word`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"cookies\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`url` TEXT NOT NULL, `cookie` TEXT NOT NULL, PRIMARY KEY(`url`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"cookie\",\n            \"columnName\": \"cookie\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"url\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_cookies_url\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"url\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_cookies_url` ON `${TABLE_NAME}` (`url`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssSources\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`sourceUrl` TEXT NOT NULL, `sourceName` TEXT NOT NULL, `sourceIcon` TEXT NOT NULL, `sourceGroup` TEXT, `sourceComment` TEXT, `enabled` INTEGER NOT NULL, `variableComment` TEXT, `jsLib` TEXT, `enabledCookieJar` INTEGER DEFAULT 0, `concurrentRate` TEXT, `header` TEXT, `loginUrl` TEXT, `loginUi` TEXT, `loginCheckJs` TEXT, `coverDecodeJs` TEXT, `sortUrl` TEXT, `singleUrl` INTEGER NOT NULL, `articleStyle` INTEGER NOT NULL DEFAULT 0, `ruleArticles` TEXT, `ruleNextPage` TEXT, `ruleTitle` TEXT, `rulePubDate` TEXT, `ruleDescription` TEXT, `ruleImage` TEXT, `ruleLink` TEXT, `ruleContent` TEXT, `contentWhitelist` TEXT, `contentBlacklist` TEXT, `shouldOverrideUrlLoading` TEXT, `style` TEXT, `enableJs` INTEGER NOT NULL DEFAULT 1, `loadWithBaseUrl` INTEGER NOT NULL DEFAULT 1, `injectJs` TEXT, `lastUpdateTime` INTEGER NOT NULL DEFAULT 0, `customOrder` INTEGER NOT NULL DEFAULT 0, PRIMARY KEY(`sourceUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"sourceUrl\",\n            \"columnName\": \"sourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceName\",\n            \"columnName\": \"sourceName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceIcon\",\n            \"columnName\": \"sourceIcon\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceGroup\",\n            \"columnName\": \"sourceGroup\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"sourceComment\",\n            \"columnName\": \"sourceComment\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enabled\",\n            \"columnName\": \"enabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"variableComment\",\n            \"columnName\": \"variableComment\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"jsLib\",\n            \"columnName\": \"jsLib\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enabledCookieJar\",\n            \"columnName\": \"enabledCookieJar\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"concurrentRate\",\n            \"columnName\": \"concurrentRate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"header\",\n            \"columnName\": \"header\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUrl\",\n            \"columnName\": \"loginUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUi\",\n            \"columnName\": \"loginUi\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginCheckJs\",\n            \"columnName\": \"loginCheckJs\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"coverDecodeJs\",\n            \"columnName\": \"coverDecodeJs\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"sortUrl\",\n            \"columnName\": \"sortUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"singleUrl\",\n            \"columnName\": \"singleUrl\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"articleStyle\",\n            \"columnName\": \"articleStyle\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"ruleArticles\",\n            \"columnName\": \"ruleArticles\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleNextPage\",\n            \"columnName\": \"ruleNextPage\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleTitle\",\n            \"columnName\": \"ruleTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"rulePubDate\",\n            \"columnName\": \"rulePubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleDescription\",\n            \"columnName\": \"ruleDescription\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleImage\",\n            \"columnName\": \"ruleImage\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleLink\",\n            \"columnName\": \"ruleLink\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleContent\",\n            \"columnName\": \"ruleContent\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"contentWhitelist\",\n            \"columnName\": \"contentWhitelist\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"contentBlacklist\",\n            \"columnName\": \"contentBlacklist\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"shouldOverrideUrlLoading\",\n            \"columnName\": \"shouldOverrideUrlLoading\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"style\",\n            \"columnName\": \"style\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enableJs\",\n            \"columnName\": \"enableJs\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"loadWithBaseUrl\",\n            \"columnName\": \"loadWithBaseUrl\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"injectJs\",\n            \"columnName\": \"injectJs\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"lastUpdateTime\",\n            \"columnName\": \"lastUpdateTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"sourceUrl\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_rssSources_sourceUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"sourceUrl\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_rssSources_sourceUrl` ON `${TABLE_NAME}` (`sourceUrl`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"bookmarks\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`time` INTEGER NOT NULL, `bookName` TEXT NOT NULL, `bookAuthor` TEXT NOT NULL, `chapterIndex` INTEGER NOT NULL, `chapterPos` INTEGER NOT NULL, `chapterName` TEXT NOT NULL, `bookText` TEXT NOT NULL, `content` TEXT NOT NULL, PRIMARY KEY(`time`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"time\",\n            \"columnName\": \"time\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookName\",\n            \"columnName\": \"bookName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookAuthor\",\n            \"columnName\": \"bookAuthor\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterIndex\",\n            \"columnName\": \"chapterIndex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterPos\",\n            \"columnName\": \"chapterPos\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterName\",\n            \"columnName\": \"chapterName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookText\",\n            \"columnName\": \"bookText\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"time\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_bookmarks_bookName_bookAuthor\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"bookName\",\n              \"bookAuthor\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_bookmarks_bookName_bookAuthor` ON `${TABLE_NAME}` (`bookName`, `bookAuthor`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssArticles\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`origin` TEXT NOT NULL, `sort` TEXT NOT NULL, `title` TEXT NOT NULL, `order` INTEGER NOT NULL, `link` TEXT NOT NULL, `pubDate` TEXT, `description` TEXT, `content` TEXT, `image` TEXT, `group` TEXT NOT NULL DEFAULT '默认分组', `read` INTEGER NOT NULL, `variable` TEXT, PRIMARY KEY(`origin`, `link`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sort\",\n            \"columnName\": \"sort\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"link\",\n            \"columnName\": \"link\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"pubDate\",\n            \"columnName\": \"pubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"description\",\n            \"columnName\": \"description\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"image\",\n            \"columnName\": \"image\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"group\",\n            \"columnName\": \"group\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"'默认分组'\"\n          },\n          {\n            \"fieldPath\": \"read\",\n            \"columnName\": \"read\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"origin\",\n            \"link\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssReadRecords\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`record` TEXT NOT NULL, `read` INTEGER NOT NULL, PRIMARY KEY(`record`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"record\",\n            \"columnName\": \"record\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"read\",\n            \"columnName\": \"read\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"record\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssStars\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`origin` TEXT NOT NULL, `sort` TEXT NOT NULL, `title` TEXT NOT NULL, `starTime` INTEGER NOT NULL, `link` TEXT NOT NULL, `pubDate` TEXT, `description` TEXT, `content` TEXT, `image` TEXT, `group` TEXT NOT NULL DEFAULT '默认分组', `variable` TEXT, PRIMARY KEY(`origin`, `link`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sort\",\n            \"columnName\": \"sort\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"starTime\",\n            \"columnName\": \"starTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"link\",\n            \"columnName\": \"link\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"pubDate\",\n            \"columnName\": \"pubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"description\",\n            \"columnName\": \"description\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"image\",\n            \"columnName\": \"image\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"group\",\n            \"columnName\": \"group\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"'默认分组'\"\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"origin\",\n            \"link\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"txtTocRules\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `rule` TEXT NOT NULL, `example` TEXT, `serialNumber` INTEGER NOT NULL, `enable` INTEGER NOT NULL, PRIMARY KEY(`id`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"rule\",\n            \"columnName\": \"rule\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"example\",\n            \"columnName\": \"example\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"serialNumber\",\n            \"columnName\": \"serialNumber\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enable\",\n            \"columnName\": \"enable\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"id\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"readRecord\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`deviceId` TEXT NOT NULL, `bookName` TEXT NOT NULL, `readTime` INTEGER NOT NULL DEFAULT 0, `lastRead` INTEGER NOT NULL DEFAULT 0, PRIMARY KEY(`deviceId`, `bookName`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"deviceId\",\n            \"columnName\": \"deviceId\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookName\",\n            \"columnName\": \"bookName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"readTime\",\n            \"columnName\": \"readTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"lastRead\",\n            \"columnName\": \"lastRead\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"deviceId\",\n            \"bookName\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"httpTTS\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `url` TEXT NOT NULL, `contentType` TEXT, `concurrentRate` TEXT DEFAULT '0', `loginUrl` TEXT, `loginUi` TEXT, `header` TEXT, `jsLib` TEXT, `enabledCookieJar` INTEGER DEFAULT 0, `loginCheckJs` TEXT, `lastUpdateTime` INTEGER NOT NULL DEFAULT 0, PRIMARY KEY(`id`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"contentType\",\n            \"columnName\": \"contentType\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"concurrentRate\",\n            \"columnName\": \"concurrentRate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false,\n            \"defaultValue\": \"'0'\"\n          },\n          {\n            \"fieldPath\": \"loginUrl\",\n            \"columnName\": \"loginUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUi\",\n            \"columnName\": \"loginUi\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"header\",\n            \"columnName\": \"header\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"jsLib\",\n            \"columnName\": \"jsLib\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enabledCookieJar\",\n            \"columnName\": \"enabledCookieJar\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"loginCheckJs\",\n            \"columnName\": \"loginCheckJs\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"lastUpdateTime\",\n            \"columnName\": \"lastUpdateTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"id\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"caches\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`key` TEXT NOT NULL, `value` TEXT, `deadline` INTEGER NOT NULL, PRIMARY KEY(`key`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"key\",\n            \"columnName\": \"key\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"value\",\n            \"columnName\": \"value\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"deadline\",\n            \"columnName\": \"deadline\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"key\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_caches_key\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"key\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_caches_key` ON `${TABLE_NAME}` (`key`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"ruleSubs\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `url` TEXT NOT NULL, `type` INTEGER NOT NULL, `customOrder` INTEGER NOT NULL, `autoUpdate` INTEGER NOT NULL, `update` INTEGER NOT NULL, PRIMARY KEY(`id`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"autoUpdate\",\n            \"columnName\": \"autoUpdate\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"update\",\n            \"columnName\": \"update\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"id\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"dictRules\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`name` TEXT NOT NULL, `urlRule` TEXT NOT NULL, `showRule` TEXT NOT NULL, `enabled` INTEGER NOT NULL DEFAULT 1, `sortNumber` INTEGER NOT NULL DEFAULT 0, PRIMARY KEY(`name`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"urlRule\",\n            \"columnName\": \"urlRule\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"showRule\",\n            \"columnName\": \"showRule\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enabled\",\n            \"columnName\": \"enabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"sortNumber\",\n            \"columnName\": \"sortNumber\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"name\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"keyboardAssists\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`type` INTEGER NOT NULL DEFAULT 0, `key` TEXT NOT NULL DEFAULT '', `value` TEXT NOT NULL DEFAULT '', `serialNo` INTEGER NOT NULL DEFAULT 0, PRIMARY KEY(`type`, `key`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"key\",\n            \"columnName\": \"key\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"value\",\n            \"columnName\": \"value\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"serialNo\",\n            \"columnName\": \"serialNo\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"type\",\n            \"key\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"servers\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `type` TEXT NOT NULL, `config` TEXT, `sortNumber` INTEGER NOT NULL, PRIMARY KEY(`id`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"config\",\n            \"columnName\": \"config\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"sortNumber\",\n            \"columnName\": \"sortNumber\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"id\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      }\n    ],\n    \"views\": [],\n    \"setupQueries\": [\n      \"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)\",\n      \"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '63617164079ebbdaf82176384c39bba5')\"\n    ]\n  }\n}"
  },
  {
    "path": "app/schemas/io.legado.app.data.AppDatabase/74.json",
    "content": "{\n  \"formatVersion\": 1,\n  \"database\": {\n    \"version\": 74,\n    \"identityHash\": \"c492bca5d9c4b4b34a0808d8cce9e7ba\",\n    \"entities\": [\n      {\n        \"tableName\": \"books\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookUrl` TEXT NOT NULL DEFAULT '', `tocUrl` TEXT NOT NULL DEFAULT '', `origin` TEXT NOT NULL DEFAULT 'loc_book', `originName` TEXT NOT NULL DEFAULT '', `name` TEXT NOT NULL DEFAULT '', `author` TEXT NOT NULL DEFAULT '', `kind` TEXT, `customTag` TEXT, `coverUrl` TEXT, `customCoverUrl` TEXT, `intro` TEXT, `customIntro` TEXT, `charset` TEXT, `type` INTEGER NOT NULL DEFAULT 0, `group` INTEGER NOT NULL DEFAULT 0, `latestChapterTitle` TEXT, `latestChapterTime` INTEGER NOT NULL DEFAULT 0, `lastCheckTime` INTEGER NOT NULL DEFAULT 0, `lastCheckCount` INTEGER NOT NULL DEFAULT 0, `totalChapterNum` INTEGER NOT NULL DEFAULT 0, `durChapterTitle` TEXT, `durChapterIndex` INTEGER NOT NULL DEFAULT 0, `durChapterPos` INTEGER NOT NULL DEFAULT 0, `durChapterTime` INTEGER NOT NULL DEFAULT 0, `wordCount` TEXT, `canUpdate` INTEGER NOT NULL DEFAULT 1, `order` INTEGER NOT NULL DEFAULT 0, `originOrder` INTEGER NOT NULL DEFAULT 0, `variable` TEXT, `readConfig` TEXT, `syncTime` INTEGER NOT NULL DEFAULT 0, PRIMARY KEY(`bookUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"tocUrl\",\n            \"columnName\": \"tocUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"'loc_book'\"\n          },\n          {\n            \"fieldPath\": \"originName\",\n            \"columnName\": \"originName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"author\",\n            \"columnName\": \"author\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"kind\",\n            \"columnName\": \"kind\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customTag\",\n            \"columnName\": \"customTag\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"coverUrl\",\n            \"columnName\": \"coverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customCoverUrl\",\n            \"columnName\": \"customCoverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"intro\",\n            \"columnName\": \"intro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customIntro\",\n            \"columnName\": \"customIntro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"charset\",\n            \"columnName\": \"charset\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"group\",\n            \"columnName\": \"group\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"latestChapterTitle\",\n            \"columnName\": \"latestChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"latestChapterTime\",\n            \"columnName\": \"latestChapterTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"lastCheckTime\",\n            \"columnName\": \"lastCheckTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"lastCheckCount\",\n            \"columnName\": \"lastCheckCount\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"totalChapterNum\",\n            \"columnName\": \"totalChapterNum\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"durChapterTitle\",\n            \"columnName\": \"durChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"durChapterIndex\",\n            \"columnName\": \"durChapterIndex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"durChapterPos\",\n            \"columnName\": \"durChapterPos\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"durChapterTime\",\n            \"columnName\": \"durChapterTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"wordCount\",\n            \"columnName\": \"wordCount\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"canUpdate\",\n            \"columnName\": \"canUpdate\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"originOrder\",\n            \"columnName\": \"originOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"readConfig\",\n            \"columnName\": \"readConfig\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"syncTime\",\n            \"columnName\": \"syncTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"bookUrl\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_books_name_author\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"name\",\n              \"author\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_books_name_author` ON `${TABLE_NAME}` (`name`, `author`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"book_groups\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`groupId` INTEGER NOT NULL, `groupName` TEXT NOT NULL, `cover` TEXT, `order` INTEGER NOT NULL, `enableRefresh` INTEGER NOT NULL DEFAULT 1, `show` INTEGER NOT NULL DEFAULT 1, `bookSort` INTEGER NOT NULL DEFAULT -1, PRIMARY KEY(`groupId`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"groupId\",\n            \"columnName\": \"groupId\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"groupName\",\n            \"columnName\": \"groupName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"cover\",\n            \"columnName\": \"cover\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enableRefresh\",\n            \"columnName\": \"enableRefresh\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"show\",\n            \"columnName\": \"show\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"bookSort\",\n            \"columnName\": \"bookSort\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"-1\"\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"groupId\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"book_sources\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookSourceUrl` TEXT NOT NULL, `bookSourceName` TEXT NOT NULL, `bookSourceGroup` TEXT, `bookSourceType` INTEGER NOT NULL, `bookUrlPattern` TEXT, `customOrder` INTEGER NOT NULL DEFAULT 0, `enabled` INTEGER NOT NULL DEFAULT 1, `enabledExplore` INTEGER NOT NULL DEFAULT 1, `jsLib` TEXT, `enabledCookieJar` INTEGER DEFAULT 0, `concurrentRate` TEXT, `header` TEXT, `loginUrl` TEXT, `loginUi` TEXT, `loginCheckJs` TEXT, `coverDecodeJs` TEXT, `bookSourceComment` TEXT, `variableComment` TEXT, `lastUpdateTime` INTEGER NOT NULL, `respondTime` INTEGER NOT NULL, `weight` INTEGER NOT NULL, `exploreUrl` TEXT, `exploreScreen` TEXT, `ruleExplore` TEXT, `searchUrl` TEXT, `ruleSearch` TEXT, `ruleBookInfo` TEXT, `ruleToc` TEXT, `ruleContent` TEXT, `ruleReview` TEXT, PRIMARY KEY(`bookSourceUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookSourceUrl\",\n            \"columnName\": \"bookSourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookSourceName\",\n            \"columnName\": \"bookSourceName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookSourceGroup\",\n            \"columnName\": \"bookSourceGroup\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"bookSourceType\",\n            \"columnName\": \"bookSourceType\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookUrlPattern\",\n            \"columnName\": \"bookUrlPattern\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"enabled\",\n            \"columnName\": \"enabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"enabledExplore\",\n            \"columnName\": \"enabledExplore\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"jsLib\",\n            \"columnName\": \"jsLib\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enabledCookieJar\",\n            \"columnName\": \"enabledCookieJar\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"concurrentRate\",\n            \"columnName\": \"concurrentRate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"header\",\n            \"columnName\": \"header\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUrl\",\n            \"columnName\": \"loginUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUi\",\n            \"columnName\": \"loginUi\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginCheckJs\",\n            \"columnName\": \"loginCheckJs\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"coverDecodeJs\",\n            \"columnName\": \"coverDecodeJs\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"bookSourceComment\",\n            \"columnName\": \"bookSourceComment\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"variableComment\",\n            \"columnName\": \"variableComment\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"lastUpdateTime\",\n            \"columnName\": \"lastUpdateTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"respondTime\",\n            \"columnName\": \"respondTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"weight\",\n            \"columnName\": \"weight\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"exploreUrl\",\n            \"columnName\": \"exploreUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"exploreScreen\",\n            \"columnName\": \"exploreScreen\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleExplore\",\n            \"columnName\": \"ruleExplore\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"searchUrl\",\n            \"columnName\": \"searchUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleSearch\",\n            \"columnName\": \"ruleSearch\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleBookInfo\",\n            \"columnName\": \"ruleBookInfo\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleToc\",\n            \"columnName\": \"ruleToc\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleContent\",\n            \"columnName\": \"ruleContent\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleReview\",\n            \"columnName\": \"ruleReview\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"bookSourceUrl\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_book_sources_bookSourceUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"bookSourceUrl\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_book_sources_bookSourceUrl` ON `${TABLE_NAME}` (`bookSourceUrl`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"chapters\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`url` TEXT NOT NULL, `title` TEXT NOT NULL, `isVolume` INTEGER NOT NULL, `baseUrl` TEXT NOT NULL, `bookUrl` TEXT NOT NULL, `index` INTEGER NOT NULL, `isVip` INTEGER NOT NULL, `isPay` INTEGER NOT NULL, `resourceUrl` TEXT, `tag` TEXT, `wordCount` TEXT, `start` INTEGER, `end` INTEGER, `startFragmentId` TEXT, `endFragmentId` TEXT, `variable` TEXT, PRIMARY KEY(`url`, `bookUrl`), FOREIGN KEY(`bookUrl`) REFERENCES `books`(`bookUrl`) ON UPDATE NO ACTION ON DELETE CASCADE )\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"isVolume\",\n            \"columnName\": \"isVolume\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"baseUrl\",\n            \"columnName\": \"baseUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"index\",\n            \"columnName\": \"index\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"isVip\",\n            \"columnName\": \"isVip\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"isPay\",\n            \"columnName\": \"isPay\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"resourceUrl\",\n            \"columnName\": \"resourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"tag\",\n            \"columnName\": \"tag\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"wordCount\",\n            \"columnName\": \"wordCount\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"start\",\n            \"columnName\": \"start\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"end\",\n            \"columnName\": \"end\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"startFragmentId\",\n            \"columnName\": \"startFragmentId\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"endFragmentId\",\n            \"columnName\": \"endFragmentId\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"url\",\n            \"bookUrl\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_chapters_bookUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"bookUrl\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_chapters_bookUrl` ON `${TABLE_NAME}` (`bookUrl`)\"\n          },\n          {\n            \"name\": \"index_chapters_bookUrl_index\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"bookUrl\",\n              \"index\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_chapters_bookUrl_index` ON `${TABLE_NAME}` (`bookUrl`, `index`)\"\n          }\n        ],\n        \"foreignKeys\": [\n          {\n            \"table\": \"books\",\n            \"onDelete\": \"CASCADE\",\n            \"onUpdate\": \"NO ACTION\",\n            \"columns\": [\n              \"bookUrl\"\n            ],\n            \"referencedColumns\": [\n              \"bookUrl\"\n            ]\n          }\n        ]\n      },\n      {\n        \"tableName\": \"replace_rules\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL DEFAULT '', `group` TEXT, `pattern` TEXT NOT NULL DEFAULT '', `replacement` TEXT NOT NULL DEFAULT '', `scope` TEXT, `scopeTitle` INTEGER NOT NULL DEFAULT 0, `scopeContent` INTEGER NOT NULL DEFAULT 1, `excludeScope` TEXT, `isEnabled` INTEGER NOT NULL DEFAULT 1, `isRegex` INTEGER NOT NULL DEFAULT 1, `timeoutMillisecond` INTEGER NOT NULL DEFAULT 3000, `sortOrder` INTEGER NOT NULL DEFAULT 0)\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"group\",\n            \"columnName\": \"group\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"pattern\",\n            \"columnName\": \"pattern\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"replacement\",\n            \"columnName\": \"replacement\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"scope\",\n            \"columnName\": \"scope\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"scopeTitle\",\n            \"columnName\": \"scopeTitle\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"scopeContent\",\n            \"columnName\": \"scopeContent\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"excludeScope\",\n            \"columnName\": \"excludeScope\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"isEnabled\",\n            \"columnName\": \"isEnabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"isRegex\",\n            \"columnName\": \"isRegex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"timeoutMillisecond\",\n            \"columnName\": \"timeoutMillisecond\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"3000\"\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"sortOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": true,\n          \"columnNames\": [\n            \"id\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_replace_rules_id\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"id\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_replace_rules_id` ON `${TABLE_NAME}` (`id`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"searchBooks\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookUrl` TEXT NOT NULL, `origin` TEXT NOT NULL, `originName` TEXT NOT NULL, `type` INTEGER NOT NULL, `name` TEXT NOT NULL, `author` TEXT NOT NULL, `kind` TEXT, `coverUrl` TEXT, `intro` TEXT, `wordCount` TEXT, `latestChapterTitle` TEXT, `tocUrl` TEXT NOT NULL, `time` INTEGER NOT NULL, `variable` TEXT, `originOrder` INTEGER NOT NULL, `chapterWordCountText` TEXT, `chapterWordCount` INTEGER NOT NULL DEFAULT -1, `respondTime` INTEGER NOT NULL DEFAULT -1, PRIMARY KEY(`bookUrl`), FOREIGN KEY(`origin`) REFERENCES `book_sources`(`bookSourceUrl`) ON UPDATE NO ACTION ON DELETE CASCADE )\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"originName\",\n            \"columnName\": \"originName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"author\",\n            \"columnName\": \"author\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"kind\",\n            \"columnName\": \"kind\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"coverUrl\",\n            \"columnName\": \"coverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"intro\",\n            \"columnName\": \"intro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"wordCount\",\n            \"columnName\": \"wordCount\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"latestChapterTitle\",\n            \"columnName\": \"latestChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"tocUrl\",\n            \"columnName\": \"tocUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"time\",\n            \"columnName\": \"time\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"originOrder\",\n            \"columnName\": \"originOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterWordCountText\",\n            \"columnName\": \"chapterWordCountText\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"chapterWordCount\",\n            \"columnName\": \"chapterWordCount\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"-1\"\n          },\n          {\n            \"fieldPath\": \"respondTime\",\n            \"columnName\": \"respondTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"-1\"\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"bookUrl\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_searchBooks_bookUrl\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"bookUrl\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_searchBooks_bookUrl` ON `${TABLE_NAME}` (`bookUrl`)\"\n          },\n          {\n            \"name\": \"index_searchBooks_origin\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"origin\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_searchBooks_origin` ON `${TABLE_NAME}` (`origin`)\"\n          }\n        ],\n        \"foreignKeys\": [\n          {\n            \"table\": \"book_sources\",\n            \"onDelete\": \"CASCADE\",\n            \"onUpdate\": \"NO ACTION\",\n            \"columns\": [\n              \"origin\"\n            ],\n            \"referencedColumns\": [\n              \"bookSourceUrl\"\n            ]\n          }\n        ]\n      },\n      {\n        \"tableName\": \"search_keywords\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`word` TEXT NOT NULL, `usage` INTEGER NOT NULL, `lastUseTime` INTEGER NOT NULL, PRIMARY KEY(`word`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"word\",\n            \"columnName\": \"word\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"usage\",\n            \"columnName\": \"usage\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"lastUseTime\",\n            \"columnName\": \"lastUseTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"word\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_search_keywords_word\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"word\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_search_keywords_word` ON `${TABLE_NAME}` (`word`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"cookies\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`url` TEXT NOT NULL, `cookie` TEXT NOT NULL, PRIMARY KEY(`url`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"cookie\",\n            \"columnName\": \"cookie\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"url\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_cookies_url\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"url\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_cookies_url` ON `${TABLE_NAME}` (`url`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssSources\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`sourceUrl` TEXT NOT NULL, `sourceName` TEXT NOT NULL, `sourceIcon` TEXT NOT NULL, `sourceGroup` TEXT, `sourceComment` TEXT, `enabled` INTEGER NOT NULL, `variableComment` TEXT, `jsLib` TEXT, `enabledCookieJar` INTEGER DEFAULT 0, `concurrentRate` TEXT, `header` TEXT, `loginUrl` TEXT, `loginUi` TEXT, `loginCheckJs` TEXT, `coverDecodeJs` TEXT, `sortUrl` TEXT, `singleUrl` INTEGER NOT NULL, `articleStyle` INTEGER NOT NULL DEFAULT 0, `ruleArticles` TEXT, `ruleNextPage` TEXT, `ruleTitle` TEXT, `rulePubDate` TEXT, `ruleDescription` TEXT, `ruleImage` TEXT, `ruleLink` TEXT, `ruleContent` TEXT, `contentWhitelist` TEXT, `contentBlacklist` TEXT, `shouldOverrideUrlLoading` TEXT, `style` TEXT, `enableJs` INTEGER NOT NULL DEFAULT 1, `loadWithBaseUrl` INTEGER NOT NULL DEFAULT 1, `injectJs` TEXT, `lastUpdateTime` INTEGER NOT NULL DEFAULT 0, `customOrder` INTEGER NOT NULL DEFAULT 0, PRIMARY KEY(`sourceUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"sourceUrl\",\n            \"columnName\": \"sourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceName\",\n            \"columnName\": \"sourceName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceIcon\",\n            \"columnName\": \"sourceIcon\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceGroup\",\n            \"columnName\": \"sourceGroup\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"sourceComment\",\n            \"columnName\": \"sourceComment\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enabled\",\n            \"columnName\": \"enabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"variableComment\",\n            \"columnName\": \"variableComment\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"jsLib\",\n            \"columnName\": \"jsLib\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enabledCookieJar\",\n            \"columnName\": \"enabledCookieJar\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"concurrentRate\",\n            \"columnName\": \"concurrentRate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"header\",\n            \"columnName\": \"header\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUrl\",\n            \"columnName\": \"loginUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUi\",\n            \"columnName\": \"loginUi\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginCheckJs\",\n            \"columnName\": \"loginCheckJs\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"coverDecodeJs\",\n            \"columnName\": \"coverDecodeJs\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"sortUrl\",\n            \"columnName\": \"sortUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"singleUrl\",\n            \"columnName\": \"singleUrl\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"articleStyle\",\n            \"columnName\": \"articleStyle\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"ruleArticles\",\n            \"columnName\": \"ruleArticles\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleNextPage\",\n            \"columnName\": \"ruleNextPage\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleTitle\",\n            \"columnName\": \"ruleTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"rulePubDate\",\n            \"columnName\": \"rulePubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleDescription\",\n            \"columnName\": \"ruleDescription\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleImage\",\n            \"columnName\": \"ruleImage\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleLink\",\n            \"columnName\": \"ruleLink\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleContent\",\n            \"columnName\": \"ruleContent\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"contentWhitelist\",\n            \"columnName\": \"contentWhitelist\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"contentBlacklist\",\n            \"columnName\": \"contentBlacklist\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"shouldOverrideUrlLoading\",\n            \"columnName\": \"shouldOverrideUrlLoading\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"style\",\n            \"columnName\": \"style\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enableJs\",\n            \"columnName\": \"enableJs\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"loadWithBaseUrl\",\n            \"columnName\": \"loadWithBaseUrl\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"injectJs\",\n            \"columnName\": \"injectJs\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"lastUpdateTime\",\n            \"columnName\": \"lastUpdateTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"sourceUrl\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_rssSources_sourceUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"sourceUrl\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_rssSources_sourceUrl` ON `${TABLE_NAME}` (`sourceUrl`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"bookmarks\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`time` INTEGER NOT NULL, `bookName` TEXT NOT NULL, `bookAuthor` TEXT NOT NULL, `chapterIndex` INTEGER NOT NULL, `chapterPos` INTEGER NOT NULL, `chapterName` TEXT NOT NULL, `bookText` TEXT NOT NULL, `content` TEXT NOT NULL, PRIMARY KEY(`time`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"time\",\n            \"columnName\": \"time\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookName\",\n            \"columnName\": \"bookName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookAuthor\",\n            \"columnName\": \"bookAuthor\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterIndex\",\n            \"columnName\": \"chapterIndex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterPos\",\n            \"columnName\": \"chapterPos\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterName\",\n            \"columnName\": \"chapterName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookText\",\n            \"columnName\": \"bookText\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"time\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_bookmarks_bookName_bookAuthor\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"bookName\",\n              \"bookAuthor\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_bookmarks_bookName_bookAuthor` ON `${TABLE_NAME}` (`bookName`, `bookAuthor`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssArticles\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`origin` TEXT NOT NULL, `sort` TEXT NOT NULL, `title` TEXT NOT NULL, `order` INTEGER NOT NULL, `link` TEXT NOT NULL, `pubDate` TEXT, `description` TEXT, `content` TEXT, `image` TEXT, `group` TEXT NOT NULL DEFAULT '默认分组', `read` INTEGER NOT NULL, `variable` TEXT, PRIMARY KEY(`origin`, `link`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sort\",\n            \"columnName\": \"sort\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"link\",\n            \"columnName\": \"link\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"pubDate\",\n            \"columnName\": \"pubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"description\",\n            \"columnName\": \"description\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"image\",\n            \"columnName\": \"image\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"group\",\n            \"columnName\": \"group\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"'默认分组'\"\n          },\n          {\n            \"fieldPath\": \"read\",\n            \"columnName\": \"read\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"origin\",\n            \"link\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssReadRecords\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`record` TEXT NOT NULL, `title` TEXT, `readTime` INTEGER, `read` INTEGER NOT NULL, PRIMARY KEY(`record`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"record\",\n            \"columnName\": \"record\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"readTime\",\n            \"columnName\": \"readTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"read\",\n            \"columnName\": \"read\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"record\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssStars\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`origin` TEXT NOT NULL, `sort` TEXT NOT NULL, `title` TEXT NOT NULL, `starTime` INTEGER NOT NULL, `link` TEXT NOT NULL, `pubDate` TEXT, `description` TEXT, `content` TEXT, `image` TEXT, `group` TEXT NOT NULL DEFAULT '默认分组', `variable` TEXT, PRIMARY KEY(`origin`, `link`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sort\",\n            \"columnName\": \"sort\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"starTime\",\n            \"columnName\": \"starTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"link\",\n            \"columnName\": \"link\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"pubDate\",\n            \"columnName\": \"pubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"description\",\n            \"columnName\": \"description\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"image\",\n            \"columnName\": \"image\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"group\",\n            \"columnName\": \"group\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"'默认分组'\"\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"origin\",\n            \"link\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"txtTocRules\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `rule` TEXT NOT NULL, `example` TEXT, `serialNumber` INTEGER NOT NULL, `enable` INTEGER NOT NULL, PRIMARY KEY(`id`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"rule\",\n            \"columnName\": \"rule\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"example\",\n            \"columnName\": \"example\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"serialNumber\",\n            \"columnName\": \"serialNumber\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enable\",\n            \"columnName\": \"enable\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"id\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"readRecord\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`deviceId` TEXT NOT NULL, `bookName` TEXT NOT NULL, `readTime` INTEGER NOT NULL DEFAULT 0, `lastRead` INTEGER NOT NULL DEFAULT 0, PRIMARY KEY(`deviceId`, `bookName`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"deviceId\",\n            \"columnName\": \"deviceId\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookName\",\n            \"columnName\": \"bookName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"readTime\",\n            \"columnName\": \"readTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"lastRead\",\n            \"columnName\": \"lastRead\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"deviceId\",\n            \"bookName\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"httpTTS\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `url` TEXT NOT NULL, `contentType` TEXT, `concurrentRate` TEXT DEFAULT '0', `loginUrl` TEXT, `loginUi` TEXT, `header` TEXT, `jsLib` TEXT, `enabledCookieJar` INTEGER DEFAULT 0, `loginCheckJs` TEXT, `lastUpdateTime` INTEGER NOT NULL DEFAULT 0, PRIMARY KEY(`id`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"contentType\",\n            \"columnName\": \"contentType\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"concurrentRate\",\n            \"columnName\": \"concurrentRate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false,\n            \"defaultValue\": \"'0'\"\n          },\n          {\n            \"fieldPath\": \"loginUrl\",\n            \"columnName\": \"loginUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUi\",\n            \"columnName\": \"loginUi\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"header\",\n            \"columnName\": \"header\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"jsLib\",\n            \"columnName\": \"jsLib\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enabledCookieJar\",\n            \"columnName\": \"enabledCookieJar\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"loginCheckJs\",\n            \"columnName\": \"loginCheckJs\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"lastUpdateTime\",\n            \"columnName\": \"lastUpdateTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"id\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"caches\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`key` TEXT NOT NULL, `value` TEXT, `deadline` INTEGER NOT NULL, PRIMARY KEY(`key`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"key\",\n            \"columnName\": \"key\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"value\",\n            \"columnName\": \"value\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"deadline\",\n            \"columnName\": \"deadline\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"key\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_caches_key\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"key\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_caches_key` ON `${TABLE_NAME}` (`key`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"ruleSubs\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `url` TEXT NOT NULL, `type` INTEGER NOT NULL, `customOrder` INTEGER NOT NULL, `autoUpdate` INTEGER NOT NULL, `update` INTEGER NOT NULL, PRIMARY KEY(`id`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"autoUpdate\",\n            \"columnName\": \"autoUpdate\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"update\",\n            \"columnName\": \"update\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"id\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"dictRules\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`name` TEXT NOT NULL, `urlRule` TEXT NOT NULL, `showRule` TEXT NOT NULL, `enabled` INTEGER NOT NULL DEFAULT 1, `sortNumber` INTEGER NOT NULL DEFAULT 0, PRIMARY KEY(`name`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"urlRule\",\n            \"columnName\": \"urlRule\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"showRule\",\n            \"columnName\": \"showRule\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enabled\",\n            \"columnName\": \"enabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"sortNumber\",\n            \"columnName\": \"sortNumber\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"name\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"keyboardAssists\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`type` INTEGER NOT NULL DEFAULT 0, `key` TEXT NOT NULL DEFAULT '', `value` TEXT NOT NULL DEFAULT '', `serialNo` INTEGER NOT NULL DEFAULT 0, PRIMARY KEY(`type`, `key`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"key\",\n            \"columnName\": \"key\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"value\",\n            \"columnName\": \"value\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"serialNo\",\n            \"columnName\": \"serialNo\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"type\",\n            \"key\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"servers\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `type` TEXT NOT NULL, `config` TEXT, `sortNumber` INTEGER NOT NULL, PRIMARY KEY(`id`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"config\",\n            \"columnName\": \"config\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"sortNumber\",\n            \"columnName\": \"sortNumber\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"id\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      }\n    ],\n    \"views\": [],\n    \"setupQueries\": [\n      \"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)\",\n      \"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'c492bca5d9c4b4b34a0808d8cce9e7ba')\"\n    ]\n  }\n}"
  },
  {
    "path": "app/schemas/io.legado.app.data.AppDatabase/75.json",
    "content": "{\n  \"formatVersion\": 1,\n  \"database\": {\n    \"version\": 75,\n    \"identityHash\": \"e22b36ff0280a224a30d08dd6e9bd7be\",\n    \"entities\": [\n      {\n        \"tableName\": \"books\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookUrl` TEXT NOT NULL DEFAULT '', `tocUrl` TEXT NOT NULL DEFAULT '', `origin` TEXT NOT NULL DEFAULT 'loc_book', `originName` TEXT NOT NULL DEFAULT '', `name` TEXT NOT NULL DEFAULT '', `author` TEXT NOT NULL DEFAULT '', `kind` TEXT, `customTag` TEXT, `coverUrl` TEXT, `customCoverUrl` TEXT, `intro` TEXT, `customIntro` TEXT, `charset` TEXT, `type` INTEGER NOT NULL DEFAULT 0, `group` INTEGER NOT NULL DEFAULT 0, `latestChapterTitle` TEXT, `latestChapterTime` INTEGER NOT NULL DEFAULT 0, `lastCheckTime` INTEGER NOT NULL DEFAULT 0, `lastCheckCount` INTEGER NOT NULL DEFAULT 0, `totalChapterNum` INTEGER NOT NULL DEFAULT 0, `durChapterTitle` TEXT, `durChapterIndex` INTEGER NOT NULL DEFAULT 0, `durChapterPos` INTEGER NOT NULL DEFAULT 0, `durChapterTime` INTEGER NOT NULL DEFAULT 0, `wordCount` TEXT, `canUpdate` INTEGER NOT NULL DEFAULT 1, `order` INTEGER NOT NULL DEFAULT 0, `originOrder` INTEGER NOT NULL DEFAULT 0, `variable` TEXT, `readConfig` TEXT, `syncTime` INTEGER NOT NULL DEFAULT 0, PRIMARY KEY(`bookUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"tocUrl\",\n            \"columnName\": \"tocUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"'loc_book'\"\n          },\n          {\n            \"fieldPath\": \"originName\",\n            \"columnName\": \"originName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"author\",\n            \"columnName\": \"author\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"kind\",\n            \"columnName\": \"kind\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customTag\",\n            \"columnName\": \"customTag\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"coverUrl\",\n            \"columnName\": \"coverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customCoverUrl\",\n            \"columnName\": \"customCoverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"intro\",\n            \"columnName\": \"intro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customIntro\",\n            \"columnName\": \"customIntro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"charset\",\n            \"columnName\": \"charset\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"group\",\n            \"columnName\": \"group\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"latestChapterTitle\",\n            \"columnName\": \"latestChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"latestChapterTime\",\n            \"columnName\": \"latestChapterTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"lastCheckTime\",\n            \"columnName\": \"lastCheckTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"lastCheckCount\",\n            \"columnName\": \"lastCheckCount\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"totalChapterNum\",\n            \"columnName\": \"totalChapterNum\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"durChapterTitle\",\n            \"columnName\": \"durChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"durChapterIndex\",\n            \"columnName\": \"durChapterIndex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"durChapterPos\",\n            \"columnName\": \"durChapterPos\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"durChapterTime\",\n            \"columnName\": \"durChapterTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"wordCount\",\n            \"columnName\": \"wordCount\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"canUpdate\",\n            \"columnName\": \"canUpdate\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"originOrder\",\n            \"columnName\": \"originOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"readConfig\",\n            \"columnName\": \"readConfig\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"syncTime\",\n            \"columnName\": \"syncTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"bookUrl\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_books_name_author\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"name\",\n              \"author\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_books_name_author` ON `${TABLE_NAME}` (`name`, `author`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"book_groups\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`groupId` INTEGER NOT NULL, `groupName` TEXT NOT NULL, `cover` TEXT, `order` INTEGER NOT NULL, `enableRefresh` INTEGER NOT NULL DEFAULT 1, `show` INTEGER NOT NULL DEFAULT 1, `bookSort` INTEGER NOT NULL DEFAULT -1, PRIMARY KEY(`groupId`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"groupId\",\n            \"columnName\": \"groupId\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"groupName\",\n            \"columnName\": \"groupName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"cover\",\n            \"columnName\": \"cover\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enableRefresh\",\n            \"columnName\": \"enableRefresh\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"show\",\n            \"columnName\": \"show\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"bookSort\",\n            \"columnName\": \"bookSort\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"-1\"\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"groupId\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"book_sources\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookSourceUrl` TEXT NOT NULL, `bookSourceName` TEXT NOT NULL, `bookSourceGroup` TEXT, `bookSourceType` INTEGER NOT NULL, `bookUrlPattern` TEXT, `customOrder` INTEGER NOT NULL DEFAULT 0, `enabled` INTEGER NOT NULL DEFAULT 1, `enabledExplore` INTEGER NOT NULL DEFAULT 1, `jsLib` TEXT, `enabledCookieJar` INTEGER DEFAULT 0, `concurrentRate` TEXT, `header` TEXT, `loginUrl` TEXT, `loginUi` TEXT, `loginCheckJs` TEXT, `coverDecodeJs` TEXT, `bookSourceComment` TEXT, `variableComment` TEXT, `lastUpdateTime` INTEGER NOT NULL, `respondTime` INTEGER NOT NULL, `weight` INTEGER NOT NULL, `exploreUrl` TEXT, `exploreScreen` TEXT, `ruleExplore` TEXT, `searchUrl` TEXT, `ruleSearch` TEXT, `ruleBookInfo` TEXT, `ruleToc` TEXT, `ruleContent` TEXT, `ruleReview` TEXT, PRIMARY KEY(`bookSourceUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookSourceUrl\",\n            \"columnName\": \"bookSourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookSourceName\",\n            \"columnName\": \"bookSourceName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookSourceGroup\",\n            \"columnName\": \"bookSourceGroup\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"bookSourceType\",\n            \"columnName\": \"bookSourceType\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookUrlPattern\",\n            \"columnName\": \"bookUrlPattern\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"enabled\",\n            \"columnName\": \"enabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"enabledExplore\",\n            \"columnName\": \"enabledExplore\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"jsLib\",\n            \"columnName\": \"jsLib\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enabledCookieJar\",\n            \"columnName\": \"enabledCookieJar\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"concurrentRate\",\n            \"columnName\": \"concurrentRate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"header\",\n            \"columnName\": \"header\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUrl\",\n            \"columnName\": \"loginUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUi\",\n            \"columnName\": \"loginUi\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginCheckJs\",\n            \"columnName\": \"loginCheckJs\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"coverDecodeJs\",\n            \"columnName\": \"coverDecodeJs\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"bookSourceComment\",\n            \"columnName\": \"bookSourceComment\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"variableComment\",\n            \"columnName\": \"variableComment\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"lastUpdateTime\",\n            \"columnName\": \"lastUpdateTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"respondTime\",\n            \"columnName\": \"respondTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"weight\",\n            \"columnName\": \"weight\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"exploreUrl\",\n            \"columnName\": \"exploreUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"exploreScreen\",\n            \"columnName\": \"exploreScreen\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleExplore\",\n            \"columnName\": \"ruleExplore\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"searchUrl\",\n            \"columnName\": \"searchUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleSearch\",\n            \"columnName\": \"ruleSearch\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleBookInfo\",\n            \"columnName\": \"ruleBookInfo\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleToc\",\n            \"columnName\": \"ruleToc\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleContent\",\n            \"columnName\": \"ruleContent\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleReview\",\n            \"columnName\": \"ruleReview\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"bookSourceUrl\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_book_sources_bookSourceUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"bookSourceUrl\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_book_sources_bookSourceUrl` ON `${TABLE_NAME}` (`bookSourceUrl`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"chapters\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`url` TEXT NOT NULL, `title` TEXT NOT NULL, `isVolume` INTEGER NOT NULL, `baseUrl` TEXT NOT NULL, `bookUrl` TEXT NOT NULL, `index` INTEGER NOT NULL, `isVip` INTEGER NOT NULL, `isPay` INTEGER NOT NULL, `resourceUrl` TEXT, `tag` TEXT, `wordCount` TEXT, `start` INTEGER, `end` INTEGER, `startFragmentId` TEXT, `endFragmentId` TEXT, `variable` TEXT, PRIMARY KEY(`url`, `bookUrl`), FOREIGN KEY(`bookUrl`) REFERENCES `books`(`bookUrl`) ON UPDATE NO ACTION ON DELETE CASCADE )\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"isVolume\",\n            \"columnName\": \"isVolume\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"baseUrl\",\n            \"columnName\": \"baseUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"index\",\n            \"columnName\": \"index\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"isVip\",\n            \"columnName\": \"isVip\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"isPay\",\n            \"columnName\": \"isPay\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"resourceUrl\",\n            \"columnName\": \"resourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"tag\",\n            \"columnName\": \"tag\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"wordCount\",\n            \"columnName\": \"wordCount\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"start\",\n            \"columnName\": \"start\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"end\",\n            \"columnName\": \"end\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"startFragmentId\",\n            \"columnName\": \"startFragmentId\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"endFragmentId\",\n            \"columnName\": \"endFragmentId\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"url\",\n            \"bookUrl\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_chapters_bookUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"bookUrl\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_chapters_bookUrl` ON `${TABLE_NAME}` (`bookUrl`)\"\n          },\n          {\n            \"name\": \"index_chapters_bookUrl_index\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"bookUrl\",\n              \"index\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_chapters_bookUrl_index` ON `${TABLE_NAME}` (`bookUrl`, `index`)\"\n          }\n        ],\n        \"foreignKeys\": [\n          {\n            \"table\": \"books\",\n            \"onDelete\": \"CASCADE\",\n            \"onUpdate\": \"NO ACTION\",\n            \"columns\": [\n              \"bookUrl\"\n            ],\n            \"referencedColumns\": [\n              \"bookUrl\"\n            ]\n          }\n        ]\n      },\n      {\n        \"tableName\": \"replace_rules\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL DEFAULT '', `group` TEXT, `pattern` TEXT NOT NULL DEFAULT '', `replacement` TEXT NOT NULL DEFAULT '', `scope` TEXT, `scopeTitle` INTEGER NOT NULL DEFAULT 0, `scopeContent` INTEGER NOT NULL DEFAULT 1, `excludeScope` TEXT, `isEnabled` INTEGER NOT NULL DEFAULT 1, `isRegex` INTEGER NOT NULL DEFAULT 1, `timeoutMillisecond` INTEGER NOT NULL DEFAULT 3000, `sortOrder` INTEGER NOT NULL DEFAULT 0)\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"group\",\n            \"columnName\": \"group\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"pattern\",\n            \"columnName\": \"pattern\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"replacement\",\n            \"columnName\": \"replacement\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"scope\",\n            \"columnName\": \"scope\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"scopeTitle\",\n            \"columnName\": \"scopeTitle\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"scopeContent\",\n            \"columnName\": \"scopeContent\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"excludeScope\",\n            \"columnName\": \"excludeScope\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"isEnabled\",\n            \"columnName\": \"isEnabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"isRegex\",\n            \"columnName\": \"isRegex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"timeoutMillisecond\",\n            \"columnName\": \"timeoutMillisecond\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"3000\"\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"sortOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": true,\n          \"columnNames\": [\n            \"id\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_replace_rules_id\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"id\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_replace_rules_id` ON `${TABLE_NAME}` (`id`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"searchBooks\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookUrl` TEXT NOT NULL, `origin` TEXT NOT NULL, `originName` TEXT NOT NULL, `type` INTEGER NOT NULL, `name` TEXT NOT NULL, `author` TEXT NOT NULL, `kind` TEXT, `coverUrl` TEXT, `intro` TEXT, `wordCount` TEXT, `latestChapterTitle` TEXT, `tocUrl` TEXT NOT NULL, `time` INTEGER NOT NULL, `variable` TEXT, `originOrder` INTEGER NOT NULL, `chapterWordCountText` TEXT, `chapterWordCount` INTEGER NOT NULL DEFAULT -1, `respondTime` INTEGER NOT NULL DEFAULT -1, PRIMARY KEY(`bookUrl`), FOREIGN KEY(`origin`) REFERENCES `book_sources`(`bookSourceUrl`) ON UPDATE NO ACTION ON DELETE CASCADE )\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"originName\",\n            \"columnName\": \"originName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"author\",\n            \"columnName\": \"author\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"kind\",\n            \"columnName\": \"kind\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"coverUrl\",\n            \"columnName\": \"coverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"intro\",\n            \"columnName\": \"intro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"wordCount\",\n            \"columnName\": \"wordCount\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"latestChapterTitle\",\n            \"columnName\": \"latestChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"tocUrl\",\n            \"columnName\": \"tocUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"time\",\n            \"columnName\": \"time\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"originOrder\",\n            \"columnName\": \"originOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterWordCountText\",\n            \"columnName\": \"chapterWordCountText\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"chapterWordCount\",\n            \"columnName\": \"chapterWordCount\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"-1\"\n          },\n          {\n            \"fieldPath\": \"respondTime\",\n            \"columnName\": \"respondTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"-1\"\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"bookUrl\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_searchBooks_bookUrl\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"bookUrl\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_searchBooks_bookUrl` ON `${TABLE_NAME}` (`bookUrl`)\"\n          },\n          {\n            \"name\": \"index_searchBooks_origin\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"origin\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_searchBooks_origin` ON `${TABLE_NAME}` (`origin`)\"\n          }\n        ],\n        \"foreignKeys\": [\n          {\n            \"table\": \"book_sources\",\n            \"onDelete\": \"CASCADE\",\n            \"onUpdate\": \"NO ACTION\",\n            \"columns\": [\n              \"origin\"\n            ],\n            \"referencedColumns\": [\n              \"bookSourceUrl\"\n            ]\n          }\n        ]\n      },\n      {\n        \"tableName\": \"search_keywords\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`word` TEXT NOT NULL, `usage` INTEGER NOT NULL, `lastUseTime` INTEGER NOT NULL, PRIMARY KEY(`word`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"word\",\n            \"columnName\": \"word\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"usage\",\n            \"columnName\": \"usage\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"lastUseTime\",\n            \"columnName\": \"lastUseTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"word\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_search_keywords_word\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"word\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_search_keywords_word` ON `${TABLE_NAME}` (`word`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"cookies\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`url` TEXT NOT NULL, `cookie` TEXT NOT NULL, PRIMARY KEY(`url`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"cookie\",\n            \"columnName\": \"cookie\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"url\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_cookies_url\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"url\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_cookies_url` ON `${TABLE_NAME}` (`url`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssSources\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`sourceUrl` TEXT NOT NULL, `sourceName` TEXT NOT NULL, `sourceIcon` TEXT NOT NULL, `sourceGroup` TEXT, `sourceComment` TEXT, `enabled` INTEGER NOT NULL, `variableComment` TEXT, `jsLib` TEXT, `enabledCookieJar` INTEGER DEFAULT 0, `concurrentRate` TEXT, `header` TEXT, `loginUrl` TEXT, `loginUi` TEXT, `loginCheckJs` TEXT, `coverDecodeJs` TEXT, `sortUrl` TEXT, `singleUrl` INTEGER NOT NULL, `articleStyle` INTEGER NOT NULL DEFAULT 0, `ruleArticles` TEXT, `ruleNextPage` TEXT, `ruleTitle` TEXT, `rulePubDate` TEXT, `ruleDescription` TEXT, `ruleImage` TEXT, `ruleLink` TEXT, `ruleContent` TEXT, `contentWhitelist` TEXT, `contentBlacklist` TEXT, `shouldOverrideUrlLoading` TEXT, `style` TEXT, `enableJs` INTEGER NOT NULL DEFAULT 1, `loadWithBaseUrl` INTEGER NOT NULL DEFAULT 1, `injectJs` TEXT, `lastUpdateTime` INTEGER NOT NULL DEFAULT 0, `customOrder` INTEGER NOT NULL DEFAULT 0, PRIMARY KEY(`sourceUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"sourceUrl\",\n            \"columnName\": \"sourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceName\",\n            \"columnName\": \"sourceName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceIcon\",\n            \"columnName\": \"sourceIcon\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceGroup\",\n            \"columnName\": \"sourceGroup\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"sourceComment\",\n            \"columnName\": \"sourceComment\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enabled\",\n            \"columnName\": \"enabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"variableComment\",\n            \"columnName\": \"variableComment\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"jsLib\",\n            \"columnName\": \"jsLib\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enabledCookieJar\",\n            \"columnName\": \"enabledCookieJar\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"concurrentRate\",\n            \"columnName\": \"concurrentRate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"header\",\n            \"columnName\": \"header\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUrl\",\n            \"columnName\": \"loginUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUi\",\n            \"columnName\": \"loginUi\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginCheckJs\",\n            \"columnName\": \"loginCheckJs\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"coverDecodeJs\",\n            \"columnName\": \"coverDecodeJs\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"sortUrl\",\n            \"columnName\": \"sortUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"singleUrl\",\n            \"columnName\": \"singleUrl\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"articleStyle\",\n            \"columnName\": \"articleStyle\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"ruleArticles\",\n            \"columnName\": \"ruleArticles\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleNextPage\",\n            \"columnName\": \"ruleNextPage\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleTitle\",\n            \"columnName\": \"ruleTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"rulePubDate\",\n            \"columnName\": \"rulePubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleDescription\",\n            \"columnName\": \"ruleDescription\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleImage\",\n            \"columnName\": \"ruleImage\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleLink\",\n            \"columnName\": \"ruleLink\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleContent\",\n            \"columnName\": \"ruleContent\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"contentWhitelist\",\n            \"columnName\": \"contentWhitelist\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"contentBlacklist\",\n            \"columnName\": \"contentBlacklist\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"shouldOverrideUrlLoading\",\n            \"columnName\": \"shouldOverrideUrlLoading\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"style\",\n            \"columnName\": \"style\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enableJs\",\n            \"columnName\": \"enableJs\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"loadWithBaseUrl\",\n            \"columnName\": \"loadWithBaseUrl\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"injectJs\",\n            \"columnName\": \"injectJs\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"lastUpdateTime\",\n            \"columnName\": \"lastUpdateTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"sourceUrl\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_rssSources_sourceUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"sourceUrl\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_rssSources_sourceUrl` ON `${TABLE_NAME}` (`sourceUrl`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"bookmarks\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`time` INTEGER NOT NULL, `bookName` TEXT NOT NULL, `bookAuthor` TEXT NOT NULL, `chapterIndex` INTEGER NOT NULL, `chapterPos` INTEGER NOT NULL, `chapterName` TEXT NOT NULL, `bookText` TEXT NOT NULL, `content` TEXT NOT NULL, PRIMARY KEY(`time`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"time\",\n            \"columnName\": \"time\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookName\",\n            \"columnName\": \"bookName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookAuthor\",\n            \"columnName\": \"bookAuthor\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterIndex\",\n            \"columnName\": \"chapterIndex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterPos\",\n            \"columnName\": \"chapterPos\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterName\",\n            \"columnName\": \"chapterName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookText\",\n            \"columnName\": \"bookText\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"time\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_bookmarks_bookName_bookAuthor\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"bookName\",\n              \"bookAuthor\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_bookmarks_bookName_bookAuthor` ON `${TABLE_NAME}` (`bookName`, `bookAuthor`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssArticles\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`origin` TEXT NOT NULL, `sort` TEXT NOT NULL, `title` TEXT NOT NULL, `order` INTEGER NOT NULL, `link` TEXT NOT NULL, `pubDate` TEXT, `description` TEXT, `content` TEXT, `image` TEXT, `group` TEXT NOT NULL DEFAULT '默认分组', `read` INTEGER NOT NULL, `variable` TEXT, PRIMARY KEY(`origin`, `link`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sort\",\n            \"columnName\": \"sort\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"link\",\n            \"columnName\": \"link\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"pubDate\",\n            \"columnName\": \"pubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"description\",\n            \"columnName\": \"description\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"image\",\n            \"columnName\": \"image\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"group\",\n            \"columnName\": \"group\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"'默认分组'\"\n          },\n          {\n            \"fieldPath\": \"read\",\n            \"columnName\": \"read\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"origin\",\n            \"link\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssReadRecords\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`record` TEXT NOT NULL, `title` TEXT, `readTime` INTEGER, `read` INTEGER NOT NULL, PRIMARY KEY(`record`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"record\",\n            \"columnName\": \"record\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"readTime\",\n            \"columnName\": \"readTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"read\",\n            \"columnName\": \"read\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"record\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssStars\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`origin` TEXT NOT NULL, `sort` TEXT NOT NULL, `title` TEXT NOT NULL, `starTime` INTEGER NOT NULL, `link` TEXT NOT NULL, `pubDate` TEXT, `description` TEXT, `content` TEXT, `image` TEXT, `group` TEXT NOT NULL DEFAULT '默认分组', `variable` TEXT, PRIMARY KEY(`origin`, `link`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sort\",\n            \"columnName\": \"sort\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"starTime\",\n            \"columnName\": \"starTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"link\",\n            \"columnName\": \"link\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"pubDate\",\n            \"columnName\": \"pubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"description\",\n            \"columnName\": \"description\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"image\",\n            \"columnName\": \"image\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"group\",\n            \"columnName\": \"group\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"'默认分组'\"\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"origin\",\n            \"link\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"txtTocRules\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `rule` TEXT NOT NULL, `example` TEXT, `serialNumber` INTEGER NOT NULL, `enable` INTEGER NOT NULL, PRIMARY KEY(`id`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"rule\",\n            \"columnName\": \"rule\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"example\",\n            \"columnName\": \"example\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"serialNumber\",\n            \"columnName\": \"serialNumber\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enable\",\n            \"columnName\": \"enable\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"id\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"readRecord\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`deviceId` TEXT NOT NULL, `bookName` TEXT NOT NULL, `readTime` INTEGER NOT NULL DEFAULT 0, `lastRead` INTEGER NOT NULL DEFAULT 0, PRIMARY KEY(`deviceId`, `bookName`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"deviceId\",\n            \"columnName\": \"deviceId\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookName\",\n            \"columnName\": \"bookName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"readTime\",\n            \"columnName\": \"readTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"lastRead\",\n            \"columnName\": \"lastRead\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"deviceId\",\n            \"bookName\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"httpTTS\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `url` TEXT NOT NULL, `contentType` TEXT, `concurrentRate` TEXT DEFAULT '0', `loginUrl` TEXT, `loginUi` TEXT, `header` TEXT, `jsLib` TEXT, `enabledCookieJar` INTEGER DEFAULT 0, `loginCheckJs` TEXT, `lastUpdateTime` INTEGER NOT NULL DEFAULT 0, PRIMARY KEY(`id`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"contentType\",\n            \"columnName\": \"contentType\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"concurrentRate\",\n            \"columnName\": \"concurrentRate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false,\n            \"defaultValue\": \"'0'\"\n          },\n          {\n            \"fieldPath\": \"loginUrl\",\n            \"columnName\": \"loginUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUi\",\n            \"columnName\": \"loginUi\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"header\",\n            \"columnName\": \"header\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"jsLib\",\n            \"columnName\": \"jsLib\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enabledCookieJar\",\n            \"columnName\": \"enabledCookieJar\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"loginCheckJs\",\n            \"columnName\": \"loginCheckJs\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"lastUpdateTime\",\n            \"columnName\": \"lastUpdateTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"id\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"caches\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`key` TEXT NOT NULL, `value` TEXT, `deadline` INTEGER NOT NULL, PRIMARY KEY(`key`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"key\",\n            \"columnName\": \"key\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"value\",\n            \"columnName\": \"value\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"deadline\",\n            \"columnName\": \"deadline\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"key\"\n          ]\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_caches_key\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"key\"\n            ],\n            \"orders\": [],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_caches_key` ON `${TABLE_NAME}` (`key`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"ruleSubs\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `url` TEXT NOT NULL, `type` INTEGER NOT NULL, `customOrder` INTEGER NOT NULL, `autoUpdate` INTEGER NOT NULL, `update` INTEGER NOT NULL, PRIMARY KEY(`id`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"autoUpdate\",\n            \"columnName\": \"autoUpdate\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"update\",\n            \"columnName\": \"update\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"id\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"dictRules\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`name` TEXT NOT NULL, `urlRule` TEXT NOT NULL, `showRule` TEXT NOT NULL, `enabled` INTEGER NOT NULL DEFAULT 1, `sortNumber` INTEGER NOT NULL DEFAULT 0, PRIMARY KEY(`name`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"urlRule\",\n            \"columnName\": \"urlRule\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"showRule\",\n            \"columnName\": \"showRule\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enabled\",\n            \"columnName\": \"enabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"1\"\n          },\n          {\n            \"fieldPath\": \"sortNumber\",\n            \"columnName\": \"sortNumber\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"name\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"keyboardAssists\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`type` INTEGER NOT NULL DEFAULT 0, `key` TEXT NOT NULL DEFAULT '', `value` TEXT NOT NULL DEFAULT '', `serialNo` INTEGER NOT NULL DEFAULT 0, PRIMARY KEY(`type`, `key`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          },\n          {\n            \"fieldPath\": \"key\",\n            \"columnName\": \"key\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"value\",\n            \"columnName\": \"value\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true,\n            \"defaultValue\": \"''\"\n          },\n          {\n            \"fieldPath\": \"serialNo\",\n            \"columnName\": \"serialNo\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true,\n            \"defaultValue\": \"0\"\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"type\",\n            \"key\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"servers\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `type` TEXT NOT NULL, `config` TEXT, `sortNumber` INTEGER NOT NULL, PRIMARY KEY(`id`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"config\",\n            \"columnName\": \"config\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"sortNumber\",\n            \"columnName\": \"sortNumber\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"autoGenerate\": false,\n          \"columnNames\": [\n            \"id\"\n          ]\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      }\n    ],\n    \"views\": [\n      {\n        \"viewName\": \"book_sources_part\",\n        \"createSql\": \"CREATE VIEW `${VIEW_NAME}` AS select bookSourceUrl, bookSourceName, bookSourceGroup, customOrder, enabled, enabledExplore, \\n    (loginUrl is not null and trim(loginUrl) <> '') hasLoginUrl, lastUpdateTime, respondTime, weight, \\n    (exploreUrl is not null and trim(exploreUrl) <> '') hasExploreUrl \\n    from book_sources\"\n      }\n    ],\n    \"setupQueries\": [\n      \"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)\",\n      \"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'e22b36ff0280a224a30d08dd6e9bd7be')\"\n    ]\n  }\n}"
  },
  {
    "path": "app/schemas/io.legado.app.data.AppDatabase/8.json",
    "content": "{\n  \"formatVersion\": 1,\n  \"database\": {\n    \"version\": 8,\n    \"identityHash\": \"0c09d0b5970a01069c4381648f793da7\",\n    \"entities\": [\n      {\n        \"tableName\": \"books\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookUrl` TEXT NOT NULL, `tocUrl` TEXT NOT NULL, `origin` TEXT NOT NULL, `originName` TEXT NOT NULL, `name` TEXT NOT NULL, `author` TEXT NOT NULL, `kind` TEXT, `customTag` TEXT, `coverUrl` TEXT, `customCoverUrl` TEXT, `intro` TEXT, `customIntro` TEXT, `charset` TEXT, `type` INTEGER NOT NULL, `group` INTEGER NOT NULL, `latestChapterTitle` TEXT, `latestChapterTime` INTEGER NOT NULL, `lastCheckTime` INTEGER NOT NULL, `lastCheckCount` INTEGER NOT NULL, `totalChapterNum` INTEGER NOT NULL, `durChapterTitle` TEXT, `durChapterIndex` INTEGER NOT NULL, `durChapterPos` INTEGER NOT NULL, `durChapterTime` INTEGER NOT NULL, `wordCount` TEXT, `canUpdate` INTEGER NOT NULL, `order` INTEGER NOT NULL, `originOrder` INTEGER NOT NULL, `useReplaceRule` INTEGER NOT NULL, `variable` TEXT, PRIMARY KEY(`bookUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"tocUrl\",\n            \"columnName\": \"tocUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"originName\",\n            \"columnName\": \"originName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"author\",\n            \"columnName\": \"author\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"kind\",\n            \"columnName\": \"kind\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customTag\",\n            \"columnName\": \"customTag\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"coverUrl\",\n            \"columnName\": \"coverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customCoverUrl\",\n            \"columnName\": \"customCoverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"intro\",\n            \"columnName\": \"intro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customIntro\",\n            \"columnName\": \"customIntro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"charset\",\n            \"columnName\": \"charset\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"group\",\n            \"columnName\": \"group\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"latestChapterTitle\",\n            \"columnName\": \"latestChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"latestChapterTime\",\n            \"columnName\": \"latestChapterTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"lastCheckTime\",\n            \"columnName\": \"lastCheckTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"lastCheckCount\",\n            \"columnName\": \"lastCheckCount\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"totalChapterNum\",\n            \"columnName\": \"totalChapterNum\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"durChapterTitle\",\n            \"columnName\": \"durChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"durChapterIndex\",\n            \"columnName\": \"durChapterIndex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"durChapterPos\",\n            \"columnName\": \"durChapterPos\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"durChapterTime\",\n            \"columnName\": \"durChapterTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"wordCount\",\n            \"columnName\": \"wordCount\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"canUpdate\",\n            \"columnName\": \"canUpdate\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"originOrder\",\n            \"columnName\": \"originOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"useReplaceRule\",\n            \"columnName\": \"useReplaceRule\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"bookUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_books_bookUrl\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"bookUrl\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_books_bookUrl` ON `${TABLE_NAME}` (`bookUrl`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"book_groups\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`groupId` INTEGER NOT NULL, `groupName` TEXT NOT NULL, `order` INTEGER NOT NULL, PRIMARY KEY(`groupId`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"groupId\",\n            \"columnName\": \"groupId\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"groupName\",\n            \"columnName\": \"groupName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"groupId\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"book_sources\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookSourceName` TEXT NOT NULL, `bookSourceGroup` TEXT, `bookSourceUrl` TEXT NOT NULL, `bookSourceType` INTEGER NOT NULL, `bookUrlPattern` TEXT, `customOrder` INTEGER NOT NULL, `enabled` INTEGER NOT NULL, `enabledExplore` INTEGER NOT NULL, `header` TEXT, `loginUrl` TEXT, `lastUpdateTime` INTEGER NOT NULL, `weight` INTEGER NOT NULL, `exploreUrl` TEXT, `ruleExplore` TEXT, `searchUrl` TEXT, `ruleSearch` TEXT, `ruleBookInfo` TEXT, `ruleToc` TEXT, `ruleContent` TEXT, PRIMARY KEY(`bookSourceUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookSourceName\",\n            \"columnName\": \"bookSourceName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookSourceGroup\",\n            \"columnName\": \"bookSourceGroup\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"bookSourceUrl\",\n            \"columnName\": \"bookSourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookSourceType\",\n            \"columnName\": \"bookSourceType\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookUrlPattern\",\n            \"columnName\": \"bookUrlPattern\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enabled\",\n            \"columnName\": \"enabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enabledExplore\",\n            \"columnName\": \"enabledExplore\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"header\",\n            \"columnName\": \"header\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUrl\",\n            \"columnName\": \"loginUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"lastUpdateTime\",\n            \"columnName\": \"lastUpdateTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"weight\",\n            \"columnName\": \"weight\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"exploreUrl\",\n            \"columnName\": \"exploreUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleExplore\",\n            \"columnName\": \"ruleExplore\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"searchUrl\",\n            \"columnName\": \"searchUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleSearch\",\n            \"columnName\": \"ruleSearch\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleBookInfo\",\n            \"columnName\": \"ruleBookInfo\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleToc\",\n            \"columnName\": \"ruleToc\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleContent\",\n            \"columnName\": \"ruleContent\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"bookSourceUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_book_sources_bookSourceUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"bookSourceUrl\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_book_sources_bookSourceUrl` ON `${TABLE_NAME}` (`bookSourceUrl`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"chapters\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`url` TEXT NOT NULL, `title` TEXT NOT NULL, `bookUrl` TEXT NOT NULL, `index` INTEGER NOT NULL, `resourceUrl` TEXT, `tag` TEXT, `start` INTEGER, `end` INTEGER, `variable` TEXT, PRIMARY KEY(`url`, `bookUrl`), FOREIGN KEY(`bookUrl`) REFERENCES `books`(`bookUrl`) ON UPDATE NO ACTION ON DELETE CASCADE )\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"index\",\n            \"columnName\": \"index\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"resourceUrl\",\n            \"columnName\": \"resourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"tag\",\n            \"columnName\": \"tag\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"start\",\n            \"columnName\": \"start\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"end\",\n            \"columnName\": \"end\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"url\",\n            \"bookUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_chapters_bookUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"bookUrl\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_chapters_bookUrl` ON `${TABLE_NAME}` (`bookUrl`)\"\n          },\n          {\n            \"name\": \"index_chapters_bookUrl_index\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"bookUrl\",\n              \"index\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_chapters_bookUrl_index` ON `${TABLE_NAME}` (`bookUrl`, `index`)\"\n          }\n        ],\n        \"foreignKeys\": [\n          {\n            \"table\": \"books\",\n            \"onDelete\": \"CASCADE\",\n            \"onUpdate\": \"NO ACTION\",\n            \"columns\": [\n              \"bookUrl\"\n            ],\n            \"referencedColumns\": [\n              \"bookUrl\"\n            ]\n          }\n        ]\n      },\n      {\n        \"tableName\": \"replace_rules\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL, `group` TEXT, `pattern` TEXT NOT NULL, `replacement` TEXT NOT NULL, `scope` TEXT, `isEnabled` INTEGER NOT NULL, `isRegex` INTEGER NOT NULL, `sortOrder` INTEGER NOT NULL)\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"group\",\n            \"columnName\": \"group\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"pattern\",\n            \"columnName\": \"pattern\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"replacement\",\n            \"columnName\": \"replacement\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"scope\",\n            \"columnName\": \"scope\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"isEnabled\",\n            \"columnName\": \"isEnabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"isRegex\",\n            \"columnName\": \"isRegex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"sortOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"id\"\n          ],\n          \"autoGenerate\": true\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_replace_rules_id\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"id\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_replace_rules_id` ON `${TABLE_NAME}` (`id`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"searchBooks\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookUrl` TEXT NOT NULL, `origin` TEXT NOT NULL, `originName` TEXT NOT NULL, `type` INTEGER NOT NULL, `name` TEXT NOT NULL, `author` TEXT NOT NULL, `kind` TEXT, `coverUrl` TEXT, `intro` TEXT, `wordCount` TEXT, `latestChapterTitle` TEXT, `tocUrl` TEXT NOT NULL, `time` INTEGER NOT NULL, `variable` TEXT, `originOrder` INTEGER NOT NULL, PRIMARY KEY(`bookUrl`), FOREIGN KEY(`origin`) REFERENCES `book_sources`(`bookSourceUrl`) ON UPDATE NO ACTION ON DELETE CASCADE )\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"originName\",\n            \"columnName\": \"originName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"author\",\n            \"columnName\": \"author\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"kind\",\n            \"columnName\": \"kind\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"coverUrl\",\n            \"columnName\": \"coverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"intro\",\n            \"columnName\": \"intro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"wordCount\",\n            \"columnName\": \"wordCount\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"latestChapterTitle\",\n            \"columnName\": \"latestChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"tocUrl\",\n            \"columnName\": \"tocUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"time\",\n            \"columnName\": \"time\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"originOrder\",\n            \"columnName\": \"originOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"bookUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_searchBooks_bookUrl\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"bookUrl\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_searchBooks_bookUrl` ON `${TABLE_NAME}` (`bookUrl`)\"\n          },\n          {\n            \"name\": \"index_searchBooks_origin\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"origin\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_searchBooks_origin` ON `${TABLE_NAME}` (`origin`)\"\n          }\n        ],\n        \"foreignKeys\": [\n          {\n            \"table\": \"book_sources\",\n            \"onDelete\": \"CASCADE\",\n            \"onUpdate\": \"NO ACTION\",\n            \"columns\": [\n              \"origin\"\n            ],\n            \"referencedColumns\": [\n              \"bookSourceUrl\"\n            ]\n          }\n        ]\n      },\n      {\n        \"tableName\": \"search_keywords\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`word` TEXT NOT NULL, `usage` INTEGER NOT NULL, `lastUseTime` INTEGER NOT NULL, PRIMARY KEY(`word`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"word\",\n            \"columnName\": \"word\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"usage\",\n            \"columnName\": \"usage\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"lastUseTime\",\n            \"columnName\": \"lastUseTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"word\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_search_keywords_word\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"word\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_search_keywords_word` ON `${TABLE_NAME}` (`word`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"cookies\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`url` TEXT NOT NULL, `cookie` TEXT NOT NULL, PRIMARY KEY(`url`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"cookie\",\n            \"columnName\": \"cookie\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"url\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_cookies_url\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"url\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_cookies_url` ON `${TABLE_NAME}` (`url`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssSources\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`sourceUrl` TEXT NOT NULL, `sourceName` TEXT NOT NULL, `sourceIcon` TEXT NOT NULL, `sourceGroup` TEXT, `enabled` INTEGER NOT NULL, `ruleArticles` TEXT, `ruleNextPage` TEXT, `ruleTitle` TEXT, `rulePubDate` TEXT, `ruleDescription` TEXT, `ruleImage` TEXT, `ruleLink` TEXT, `ruleContent` TEXT, `header` TEXT, `enableJs` INTEGER NOT NULL, `loadWithBaseUrl` INTEGER NOT NULL, `customOrder` INTEGER NOT NULL, PRIMARY KEY(`sourceUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"sourceUrl\",\n            \"columnName\": \"sourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceName\",\n            \"columnName\": \"sourceName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceIcon\",\n            \"columnName\": \"sourceIcon\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceGroup\",\n            \"columnName\": \"sourceGroup\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enabled\",\n            \"columnName\": \"enabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"ruleArticles\",\n            \"columnName\": \"ruleArticles\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleNextPage\",\n            \"columnName\": \"ruleNextPage\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleTitle\",\n            \"columnName\": \"ruleTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"rulePubDate\",\n            \"columnName\": \"rulePubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleDescription\",\n            \"columnName\": \"ruleDescription\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleImage\",\n            \"columnName\": \"ruleImage\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleLink\",\n            \"columnName\": \"ruleLink\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleContent\",\n            \"columnName\": \"ruleContent\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"header\",\n            \"columnName\": \"header\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enableJs\",\n            \"columnName\": \"enableJs\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"loadWithBaseUrl\",\n            \"columnName\": \"loadWithBaseUrl\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"sourceUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_rssSources_sourceUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"sourceUrl\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_rssSources_sourceUrl` ON `${TABLE_NAME}` (`sourceUrl`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"bookmarks\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`time` INTEGER NOT NULL, `bookUrl` TEXT NOT NULL, `bookName` TEXT NOT NULL, `chapterIndex` INTEGER NOT NULL, `pageIndex` INTEGER NOT NULL, `chapterName` TEXT NOT NULL, `content` TEXT NOT NULL, PRIMARY KEY(`time`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"time\",\n            \"columnName\": \"time\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookName\",\n            \"columnName\": \"bookName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterIndex\",\n            \"columnName\": \"chapterIndex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"pageIndex\",\n            \"columnName\": \"pageIndex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterName\",\n            \"columnName\": \"chapterName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"time\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_bookmarks_time\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"time\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_bookmarks_time` ON `${TABLE_NAME}` (`time`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssArticles\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`origin` TEXT NOT NULL, `title` TEXT NOT NULL, `order` INTEGER NOT NULL, `link` TEXT NOT NULL, `pubDate` TEXT, `description` TEXT, `content` TEXT, `image` TEXT, `read` INTEGER NOT NULL, PRIMARY KEY(`origin`, `link`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"link\",\n            \"columnName\": \"link\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"pubDate\",\n            \"columnName\": \"pubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"description\",\n            \"columnName\": \"description\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"image\",\n            \"columnName\": \"image\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"read\",\n            \"columnName\": \"read\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"origin\",\n            \"link\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssReadRecords\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`record` TEXT NOT NULL, `read` INTEGER NOT NULL, PRIMARY KEY(`record`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"record\",\n            \"columnName\": \"record\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"read\",\n            \"columnName\": \"read\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"record\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssStars\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`origin` TEXT NOT NULL, `title` TEXT NOT NULL, `starTime` INTEGER NOT NULL, `link` TEXT NOT NULL, `pubDate` TEXT, `description` TEXT, `content` TEXT, `image` TEXT, PRIMARY KEY(`origin`, `link`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"starTime\",\n            \"columnName\": \"starTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"link\",\n            \"columnName\": \"link\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"pubDate\",\n            \"columnName\": \"pubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"description\",\n            \"columnName\": \"description\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"image\",\n            \"columnName\": \"image\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"origin\",\n            \"link\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"txtTocRules\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`name` TEXT NOT NULL, `rule` TEXT NOT NULL, `serialNumber` INTEGER NOT NULL, `enable` INTEGER NOT NULL, PRIMARY KEY(`name`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"rule\",\n            \"columnName\": \"rule\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"serialNumber\",\n            \"columnName\": \"serialNumber\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enable\",\n            \"columnName\": \"enable\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"name\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      }\n    ],\n    \"views\": [],\n    \"setupQueries\": [\n      \"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)\",\n      \"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '0c09d0b5970a01069c4381648f793da7')\"\n    ]\n  }\n}"
  },
  {
    "path": "app/schemas/io.legado.app.data.AppDatabase/9.json",
    "content": "{\n  \"formatVersion\": 1,\n  \"database\": {\n    \"version\": 9,\n    \"identityHash\": \"8da976febbd44e9e028b951b42583f9a\",\n    \"entities\": [\n      {\n        \"tableName\": \"books\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookUrl` TEXT NOT NULL, `tocUrl` TEXT NOT NULL, `origin` TEXT NOT NULL, `originName` TEXT NOT NULL, `name` TEXT NOT NULL, `author` TEXT NOT NULL, `kind` TEXT, `customTag` TEXT, `coverUrl` TEXT, `customCoverUrl` TEXT, `intro` TEXT, `customIntro` TEXT, `charset` TEXT, `type` INTEGER NOT NULL, `group` INTEGER NOT NULL, `latestChapterTitle` TEXT, `latestChapterTime` INTEGER NOT NULL, `lastCheckTime` INTEGER NOT NULL, `lastCheckCount` INTEGER NOT NULL, `totalChapterNum` INTEGER NOT NULL, `durChapterTitle` TEXT, `durChapterIndex` INTEGER NOT NULL, `durChapterPos` INTEGER NOT NULL, `durChapterTime` INTEGER NOT NULL, `wordCount` TEXT, `canUpdate` INTEGER NOT NULL, `order` INTEGER NOT NULL, `originOrder` INTEGER NOT NULL, `useReplaceRule` INTEGER NOT NULL, `variable` TEXT, PRIMARY KEY(`name`, `author`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"tocUrl\",\n            \"columnName\": \"tocUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"originName\",\n            \"columnName\": \"originName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"author\",\n            \"columnName\": \"author\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"kind\",\n            \"columnName\": \"kind\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customTag\",\n            \"columnName\": \"customTag\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"coverUrl\",\n            \"columnName\": \"coverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customCoverUrl\",\n            \"columnName\": \"customCoverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"intro\",\n            \"columnName\": \"intro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customIntro\",\n            \"columnName\": \"customIntro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"charset\",\n            \"columnName\": \"charset\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"group\",\n            \"columnName\": \"group\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"latestChapterTitle\",\n            \"columnName\": \"latestChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"latestChapterTime\",\n            \"columnName\": \"latestChapterTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"lastCheckTime\",\n            \"columnName\": \"lastCheckTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"lastCheckCount\",\n            \"columnName\": \"lastCheckCount\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"totalChapterNum\",\n            \"columnName\": \"totalChapterNum\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"durChapterTitle\",\n            \"columnName\": \"durChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"durChapterIndex\",\n            \"columnName\": \"durChapterIndex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"durChapterPos\",\n            \"columnName\": \"durChapterPos\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"durChapterTime\",\n            \"columnName\": \"durChapterTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"wordCount\",\n            \"columnName\": \"wordCount\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"canUpdate\",\n            \"columnName\": \"canUpdate\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"originOrder\",\n            \"columnName\": \"originOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"useReplaceRule\",\n            \"columnName\": \"useReplaceRule\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"name\",\n            \"author\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_books_bookUrl\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"bookUrl\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_books_bookUrl` ON `${TABLE_NAME}` (`bookUrl`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"book_groups\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`groupId` INTEGER NOT NULL, `groupName` TEXT NOT NULL, `order` INTEGER NOT NULL, PRIMARY KEY(`groupId`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"groupId\",\n            \"columnName\": \"groupId\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"groupName\",\n            \"columnName\": \"groupName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"groupId\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"book_sources\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookSourceName` TEXT NOT NULL, `bookSourceGroup` TEXT, `bookSourceUrl` TEXT NOT NULL, `bookSourceType` INTEGER NOT NULL, `bookUrlPattern` TEXT, `customOrder` INTEGER NOT NULL, `enabled` INTEGER NOT NULL, `enabledExplore` INTEGER NOT NULL, `header` TEXT, `loginUrl` TEXT, `lastUpdateTime` INTEGER NOT NULL, `weight` INTEGER NOT NULL, `exploreUrl` TEXT, `ruleExplore` TEXT, `searchUrl` TEXT, `ruleSearch` TEXT, `ruleBookInfo` TEXT, `ruleToc` TEXT, `ruleContent` TEXT, PRIMARY KEY(`bookSourceUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookSourceName\",\n            \"columnName\": \"bookSourceName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookSourceGroup\",\n            \"columnName\": \"bookSourceGroup\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"bookSourceUrl\",\n            \"columnName\": \"bookSourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookSourceType\",\n            \"columnName\": \"bookSourceType\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookUrlPattern\",\n            \"columnName\": \"bookUrlPattern\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enabled\",\n            \"columnName\": \"enabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enabledExplore\",\n            \"columnName\": \"enabledExplore\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"header\",\n            \"columnName\": \"header\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"loginUrl\",\n            \"columnName\": \"loginUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"lastUpdateTime\",\n            \"columnName\": \"lastUpdateTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"weight\",\n            \"columnName\": \"weight\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"exploreUrl\",\n            \"columnName\": \"exploreUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleExplore\",\n            \"columnName\": \"ruleExplore\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"searchUrl\",\n            \"columnName\": \"searchUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleSearch\",\n            \"columnName\": \"ruleSearch\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleBookInfo\",\n            \"columnName\": \"ruleBookInfo\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleToc\",\n            \"columnName\": \"ruleToc\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleContent\",\n            \"columnName\": \"ruleContent\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"bookSourceUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_book_sources_bookSourceUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"bookSourceUrl\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_book_sources_bookSourceUrl` ON `${TABLE_NAME}` (`bookSourceUrl`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"chapters\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`url` TEXT NOT NULL, `title` TEXT NOT NULL, `bookUrl` TEXT NOT NULL, `index` INTEGER NOT NULL, `resourceUrl` TEXT, `tag` TEXT, `start` INTEGER, `end` INTEGER, `variable` TEXT, PRIMARY KEY(`url`, `bookUrl`), FOREIGN KEY(`bookUrl`) REFERENCES `books`(`bookUrl`) ON UPDATE NO ACTION ON DELETE CASCADE )\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"index\",\n            \"columnName\": \"index\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"resourceUrl\",\n            \"columnName\": \"resourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"tag\",\n            \"columnName\": \"tag\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"start\",\n            \"columnName\": \"start\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"end\",\n            \"columnName\": \"end\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"url\",\n            \"bookUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_chapters_bookUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"bookUrl\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_chapters_bookUrl` ON `${TABLE_NAME}` (`bookUrl`)\"\n          },\n          {\n            \"name\": \"index_chapters_bookUrl_index\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"bookUrl\",\n              \"index\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_chapters_bookUrl_index` ON `${TABLE_NAME}` (`bookUrl`, `index`)\"\n          }\n        ],\n        \"foreignKeys\": [\n          {\n            \"table\": \"books\",\n            \"onDelete\": \"CASCADE\",\n            \"onUpdate\": \"NO ACTION\",\n            \"columns\": [\n              \"bookUrl\"\n            ],\n            \"referencedColumns\": [\n              \"bookUrl\"\n            ]\n          }\n        ]\n      },\n      {\n        \"tableName\": \"replace_rules\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `name` TEXT NOT NULL, `group` TEXT, `pattern` TEXT NOT NULL, `replacement` TEXT NOT NULL, `scope` TEXT, `isEnabled` INTEGER NOT NULL, `isRegex` INTEGER NOT NULL, `sortOrder` INTEGER NOT NULL)\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"id\",\n            \"columnName\": \"id\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"group\",\n            \"columnName\": \"group\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"pattern\",\n            \"columnName\": \"pattern\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"replacement\",\n            \"columnName\": \"replacement\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"scope\",\n            \"columnName\": \"scope\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"isEnabled\",\n            \"columnName\": \"isEnabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"isRegex\",\n            \"columnName\": \"isRegex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"sortOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"id\"\n          ],\n          \"autoGenerate\": true\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_replace_rules_id\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"id\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_replace_rules_id` ON `${TABLE_NAME}` (`id`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"searchBooks\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`bookUrl` TEXT NOT NULL, `origin` TEXT NOT NULL, `originName` TEXT NOT NULL, `type` INTEGER NOT NULL, `name` TEXT NOT NULL, `author` TEXT NOT NULL, `kind` TEXT, `coverUrl` TEXT, `intro` TEXT, `wordCount` TEXT, `latestChapterTitle` TEXT, `tocUrl` TEXT NOT NULL, `time` INTEGER NOT NULL, `variable` TEXT, `originOrder` INTEGER NOT NULL, PRIMARY KEY(`bookUrl`), FOREIGN KEY(`origin`) REFERENCES `book_sources`(`bookSourceUrl`) ON UPDATE NO ACTION ON DELETE CASCADE )\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"originName\",\n            \"columnName\": \"originName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"type\",\n            \"columnName\": \"type\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"author\",\n            \"columnName\": \"author\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"kind\",\n            \"columnName\": \"kind\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"coverUrl\",\n            \"columnName\": \"coverUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"intro\",\n            \"columnName\": \"intro\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"wordCount\",\n            \"columnName\": \"wordCount\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"latestChapterTitle\",\n            \"columnName\": \"latestChapterTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"tocUrl\",\n            \"columnName\": \"tocUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"time\",\n            \"columnName\": \"time\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"variable\",\n            \"columnName\": \"variable\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"originOrder\",\n            \"columnName\": \"originOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"bookUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_searchBooks_bookUrl\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"bookUrl\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_searchBooks_bookUrl` ON `${TABLE_NAME}` (`bookUrl`)\"\n          },\n          {\n            \"name\": \"index_searchBooks_origin\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"origin\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_searchBooks_origin` ON `${TABLE_NAME}` (`origin`)\"\n          }\n        ],\n        \"foreignKeys\": [\n          {\n            \"table\": \"book_sources\",\n            \"onDelete\": \"CASCADE\",\n            \"onUpdate\": \"NO ACTION\",\n            \"columns\": [\n              \"origin\"\n            ],\n            \"referencedColumns\": [\n              \"bookSourceUrl\"\n            ]\n          }\n        ]\n      },\n      {\n        \"tableName\": \"search_keywords\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`word` TEXT NOT NULL, `usage` INTEGER NOT NULL, `lastUseTime` INTEGER NOT NULL, PRIMARY KEY(`word`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"word\",\n            \"columnName\": \"word\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"usage\",\n            \"columnName\": \"usage\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"lastUseTime\",\n            \"columnName\": \"lastUseTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"word\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_search_keywords_word\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"word\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_search_keywords_word` ON `${TABLE_NAME}` (`word`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"cookies\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`url` TEXT NOT NULL, `cookie` TEXT NOT NULL, PRIMARY KEY(`url`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"url\",\n            \"columnName\": \"url\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"cookie\",\n            \"columnName\": \"cookie\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"url\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_cookies_url\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"url\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_cookies_url` ON `${TABLE_NAME}` (`url`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssSources\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`sourceUrl` TEXT NOT NULL, `sourceName` TEXT NOT NULL, `sourceIcon` TEXT NOT NULL, `sourceGroup` TEXT, `enabled` INTEGER NOT NULL, `sortUrl` TEXT, `ruleArticles` TEXT, `ruleNextPage` TEXT, `ruleTitle` TEXT, `rulePubDate` TEXT, `ruleDescription` TEXT, `ruleImage` TEXT, `ruleLink` TEXT, `ruleContent` TEXT, `header` TEXT, `enableJs` INTEGER NOT NULL, `loadWithBaseUrl` INTEGER NOT NULL, `customOrder` INTEGER NOT NULL, PRIMARY KEY(`sourceUrl`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"sourceUrl\",\n            \"columnName\": \"sourceUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceName\",\n            \"columnName\": \"sourceName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceIcon\",\n            \"columnName\": \"sourceIcon\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sourceGroup\",\n            \"columnName\": \"sourceGroup\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enabled\",\n            \"columnName\": \"enabled\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"sortUrl\",\n            \"columnName\": \"sortUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleArticles\",\n            \"columnName\": \"ruleArticles\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleNextPage\",\n            \"columnName\": \"ruleNextPage\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleTitle\",\n            \"columnName\": \"ruleTitle\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"rulePubDate\",\n            \"columnName\": \"rulePubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleDescription\",\n            \"columnName\": \"ruleDescription\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleImage\",\n            \"columnName\": \"ruleImage\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleLink\",\n            \"columnName\": \"ruleLink\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"ruleContent\",\n            \"columnName\": \"ruleContent\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"header\",\n            \"columnName\": \"header\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"enableJs\",\n            \"columnName\": \"enableJs\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"loadWithBaseUrl\",\n            \"columnName\": \"loadWithBaseUrl\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"customOrder\",\n            \"columnName\": \"customOrder\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"sourceUrl\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_rssSources_sourceUrl\",\n            \"unique\": false,\n            \"columnNames\": [\n              \"sourceUrl\"\n            ],\n            \"createSql\": \"CREATE INDEX IF NOT EXISTS `index_rssSources_sourceUrl` ON `${TABLE_NAME}` (`sourceUrl`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"bookmarks\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`time` INTEGER NOT NULL, `bookUrl` TEXT NOT NULL, `bookName` TEXT NOT NULL, `chapterIndex` INTEGER NOT NULL, `pageIndex` INTEGER NOT NULL, `chapterName` TEXT NOT NULL, `content` TEXT NOT NULL, PRIMARY KEY(`time`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"time\",\n            \"columnName\": \"time\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookUrl\",\n            \"columnName\": \"bookUrl\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"bookName\",\n            \"columnName\": \"bookName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterIndex\",\n            \"columnName\": \"chapterIndex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"pageIndex\",\n            \"columnName\": \"pageIndex\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"chapterName\",\n            \"columnName\": \"chapterName\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"time\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [\n          {\n            \"name\": \"index_bookmarks_time\",\n            \"unique\": true,\n            \"columnNames\": [\n              \"time\"\n            ],\n            \"createSql\": \"CREATE UNIQUE INDEX IF NOT EXISTS `index_bookmarks_time` ON `${TABLE_NAME}` (`time`)\"\n          }\n        ],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssArticles\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`origin` TEXT NOT NULL, `title` TEXT NOT NULL, `order` INTEGER NOT NULL, `link` TEXT NOT NULL, `pubDate` TEXT, `description` TEXT, `content` TEXT, `image` TEXT, `read` INTEGER NOT NULL, PRIMARY KEY(`origin`, `link`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"order\",\n            \"columnName\": \"order\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"link\",\n            \"columnName\": \"link\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"pubDate\",\n            \"columnName\": \"pubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"description\",\n            \"columnName\": \"description\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"image\",\n            \"columnName\": \"image\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"read\",\n            \"columnName\": \"read\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"origin\",\n            \"link\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssReadRecords\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`record` TEXT NOT NULL, `read` INTEGER NOT NULL, PRIMARY KEY(`record`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"record\",\n            \"columnName\": \"record\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"read\",\n            \"columnName\": \"read\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"record\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"rssStars\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`origin` TEXT NOT NULL, `title` TEXT NOT NULL, `starTime` INTEGER NOT NULL, `link` TEXT NOT NULL, `pubDate` TEXT, `description` TEXT, `content` TEXT, `image` TEXT, PRIMARY KEY(`origin`, `link`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"origin\",\n            \"columnName\": \"origin\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"title\",\n            \"columnName\": \"title\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"starTime\",\n            \"columnName\": \"starTime\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"link\",\n            \"columnName\": \"link\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"pubDate\",\n            \"columnName\": \"pubDate\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"description\",\n            \"columnName\": \"description\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"content\",\n            \"columnName\": \"content\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          },\n          {\n            \"fieldPath\": \"image\",\n            \"columnName\": \"image\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": false\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"origin\",\n            \"link\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      },\n      {\n        \"tableName\": \"txtTocRules\",\n        \"createSql\": \"CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`name` TEXT NOT NULL, `rule` TEXT NOT NULL, `serialNumber` INTEGER NOT NULL, `enable` INTEGER NOT NULL, PRIMARY KEY(`name`))\",\n        \"fields\": [\n          {\n            \"fieldPath\": \"name\",\n            \"columnName\": \"name\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"rule\",\n            \"columnName\": \"rule\",\n            \"affinity\": \"TEXT\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"serialNumber\",\n            \"columnName\": \"serialNumber\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          },\n          {\n            \"fieldPath\": \"enable\",\n            \"columnName\": \"enable\",\n            \"affinity\": \"INTEGER\",\n            \"notNull\": true\n          }\n        ],\n        \"primaryKey\": {\n          \"columnNames\": [\n            \"name\"\n          ],\n          \"autoGenerate\": false\n        },\n        \"indices\": [],\n        \"foreignKeys\": []\n      }\n    ],\n    \"views\": [],\n    \"setupQueries\": [\n      \"CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)\",\n      \"INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '8da976febbd44e9e028b951b42583f9a')\"\n    ]\n  }\n}"
  },
  {
    "path": "app/src/androidTest/java/io/legado/app/AndroidJsTest.kt",
    "content": "package io.legado.app\n\nimport cn.hutool.core.lang.JarClassLoader\nimport com.script.ScriptBindings\nimport com.script.rhino.RhinoScriptEngine\nimport dalvik.system.DexClassLoader\nimport org.intellij.lang.annotations.Language\nimport org.junit.Assert\nimport org.junit.Test\nimport org.mozilla.javascript.DefiningClassLoader\nimport java.net.URLClassLoader\n\nclass AndroidJsTest {\n\n    @Test\n    fun testPackages() {\n        @Language(\"js\")\n        val js = \"\"\"\n            var accessKeyId = '1111';\n            var accessKeySecret = '2222';\n            var timestamp = '3333';\n            var aly = new JavaImporter(Packages.javax.crypto.Mac, Packages.javax.crypto.spec.SecretKeySpec, Packages.javax.xml.bind.DatatypeConverter, Packages.java.net.URLEncoder, Packages.java.lang.String, Packages.android.util.Base64);\n            with (aly) {\n                function percentEncode(value) {\n                    return URLEncoder.encode(value, \"UTF-8\").replace(\"+\", \"%20\")\n                        .replace(\"*\", \"%2A\").replace(\"%7E\", \"~\")\n                }\n            \n                function sign(stringToSign, accessKeySecret) {\n                    var mac = Mac.getInstance('HmacSHA1');\n                    mac.init(new SecretKeySpec(String(accessKeySecret + '&').getBytes(\"UTF-8\"), \"HmacSHA1\"));\n                    var signData = mac.doFinal(String(stringToSign).getBytes(\"UTF-8\"));\n                    var signBase64 = Base64.encodeToString(signData, Base64.NO_WRAP);\n                    var signUrlEncode = percentEncode(signBase64);\n                    return signUrlEncode;\n                }\n            }\n            var query = 'AccessKeyId=' + accessKeyId + '&Action=CreateToken&Format=JSON&RegionId=cn-shanghai&SignatureMethod=HMAC-SHA1&SignatureNonce=' + \"xxccrr\" + '&SignatureVersion=1.0&Timestamp=' + percentEncode(timestamp) + '&Version=2019-02-28';\n            var signStr = sign('GET&' + percentEncode('/') + '&' + percentEncode(query), accessKeySecret);\n            var queryStringWithSign = \"Signature=\" + signStr + \"&\" + query;\n            queryStringWithSign\n        \"\"\".trimIndent()\n        RhinoScriptEngine.eval(js)\n        @Language(\"js\")\n        val js1 = \"\"\"\n            var returnData = new Packages.io.legado.app.api.ReturnData()\n            returnData.getErrorMsg()\n        \"\"\".trimIndent()\n        val result1 = RhinoScriptEngine.eval(js1)\n        Assert.assertEquals(result1, \"未知错误,请联系开发者!\")\n    }\n\n    @Test\n    fun testPackages1() {\n        URLClassLoader.getSystemClassLoader()\n        DefiningClassLoader.getSystemClassLoader()\n        JarClassLoader.getSystemClassLoader()\n        DexClassLoader.getSystemClassLoader()\n        @Language(\"js\")\n        val js = \"\"\"\n            var ji = new JavaImporter(Packages.org.mozilla.javascript.DefiningClassLoader)\n            with(ji) {\n              let x = DefiningClassLoader.getSystemClassLoader()\n            }\n        \"\"\".trimIndent()\n        RhinoScriptEngine.eval(js)\n    }\n\n    @Test\n    fun testMap() {\n        val map = hashMapOf(\"id\" to \"3242532321\")\n        val bindings = ScriptBindings()\n        bindings[\"result\"] = map\n        @Language(\"js\")\n        val jsMap = \"$=result;id=$.id;id\"\n        val result = RhinoScriptEngine.eval(jsMap, bindings)\n        Assert.assertEquals(\"3242532321\", result)\n        @Language(\"js\")\n        val jsMap1 = \"\"\"result.get(\"id\")\"\"\"\n        val result1 = RhinoScriptEngine.eval(jsMap1, bindings)\n        Assert.assertEquals(\"3242532321\", result1)\n    }\n\n}"
  },
  {
    "path": "app/src/androidTest/java/io/legado/app/ExampleInstrumentedTest.kt",
    "content": "package io.legado.app\n\nimport android.content.Context\nimport android.net.Uri\nimport android.util.Log\nimport androidx.test.core.app.ApplicationProvider\nimport androidx.test.ext.junit.runners.AndroidJUnit4\nimport org.junit.Test\nimport org.junit.runner.RunWith\n\n/**\n * Instrumented test, which will execute on an Android device.\n *\n * See [testing documentation](http://d.android.com/tools/testing).\n */\n@RunWith(AndroidJUnit4::class)\nclass ExampleInstrumentedTest {\n    @Test\n    fun testContentProvider() {\n        // Context of the app under test.\n        val appContext = ApplicationProvider.getApplicationContext<Context>()\n        Log.d(\n            \"test\",\n            appContext.contentResolver.query(\n                Uri.parse(\"content://io.legado.app.api.ReaderProvider/sources/query\"),\n                null,\n                null,\n                null,\n                null\n            )\n            !!.getString(0)\n        )\n    }\n}\n"
  },
  {
    "path": "app/src/androidTest/java/io/legado/app/HttpTest.kt",
    "content": "package io.legado.app\n\nimport android.app.DownloadManager\nimport android.net.Uri\nimport android.os.Environment\nimport android.webkit.WebSettings\nimport android.webkit.WebView\nimport io.legado.app.help.config.AppConfig\nimport io.legado.app.utils.runOnUI\nimport kotlinx.coroutines.delay\nimport kotlinx.coroutines.runBlocking\nimport org.junit.Test\nimport splitties.init.appCtx\nimport splitties.systemservices.downloadManager\n\nclass HttpTest {\n\n    @Test\n    fun test() {\n        webViewDownloadTest()\n    }\n\n    private fun webViewDownloadTest() {\n        runOnUI {\n            val webView = WebView(appCtx)\n            val settings = webView.settings\n            settings.javaScriptEnabled = true\n            settings.domStorageEnabled = true\n            settings.blockNetworkImage = true\n            settings.userAgentString = AppConfig.userAgent\n            settings.mixedContentMode = WebSettings.MIXED_CONTENT_ALWAYS_ALLOW\n            webView.setDownloadListener { url, userAgent, contentDisposition, mimetype, contentLength ->\n                print(url)\n                webView.destroy()\n            }\n            webView.loadUrl(\"https://gj.legado.cc/legado/?url=https://miaogongzi.lanzout.com/iITmP0s7y26d&type=down\")\n        }\n    }\n\n    private fun downloadManagerTest() {\n        runBlocking {\n            val request =\n                DownloadManager.Request(Uri.parse(\"https://gj.legado.cc/legado/?url=https://miaogongzi.lanzout.com/iITmP0s7y26d&type=down\"))\n            // 设置通知\n            request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_HIDDEN)\n            // 设置下载文件保存的路径和文件名\n            request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, \"test.txt\")\n            // 添加一个下载任务\n            val downloadId = downloadManager.enqueue(request)\n            val query = DownloadManager.Query()\n            query.setFilterById(downloadId)\n            repeat(30) {\n                downloadManager.query(query).use { cursor ->\n                    if (cursor.moveToFirst()) {\n                        val progressIndex =\n                            cursor.getColumnIndex(DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR)\n                        val fileSizeIndex =\n                            cursor.getColumnIndex(DownloadManager.COLUMN_TOTAL_SIZE_BYTES)\n                        val statusIndex = cursor.getColumnIndex(DownloadManager.COLUMN_STATUS)\n                        val progress = cursor.getInt(progressIndex)\n                        val max = cursor.getInt(fileSizeIndex)\n                        val status = when (cursor.getInt(statusIndex)) {\n                            DownloadManager.STATUS_PAUSED -> appCtx.getString(R.string.pause)\n                            DownloadManager.STATUS_PENDING -> appCtx.getString(R.string.wait_download)\n                            DownloadManager.STATUS_RUNNING -> appCtx.getString(R.string.downloading)\n                            DownloadManager.STATUS_SUCCESSFUL -> {\n                                appCtx.getString(R.string.download_success)\n                            }\n\n                            DownloadManager.STATUS_FAILED -> appCtx.getString(R.string.download_error)\n                            else -> appCtx.getString(R.string.unknown_state)\n                        }\n                        print(status)\n                        delay(1000)\n                    } else {\n                        return@runBlocking\n                    }\n                }\n            }\n        }\n    }\n\n}"
  },
  {
    "path": "app/src/androidTest/java/io/legado/app/HttpTtsTest.kt",
    "content": "package io.legado.app\n\nimport io.legado.app.help.config.AppConfig\nimport io.legado.app.model.analyzeRule.AnalyzeUrl\nimport kotlinx.coroutines.runBlocking\nimport org.junit.Test\n\nclass HttpTtsTest {\n\n    @Test\n    fun test() {\n        val url = \"\"\"\n            http://tsn.baidu.com/text2audio,{\n                \"method\": \"POST\",\n                \"body\": \"tex={{java.encodeURI(java.encodeURI(speakText))}}&spd={{(speakSpeed + 5) / 10 + 4}}&per=4114&cuid=baidu_speech_demo&idx=1&cod=2&lan=zh&ctp=1&pdt=220&vol=5&aue=6&pit=5&_res_tag_=audio\"\n            }\n        \"\"\".trimIndent()\n        val analyzeUrl =\n            AnalyzeUrl(url, speakText = \"魔神\", speakSpeed = AppConfig.speechRatePlay + 5)\n        runBlocking {\n            val response = analyzeUrl.getResponseAwait()\n            response.headers\n        }\n    }\n\n}"
  },
  {
    "path": "app/src/androidTest/java/io/legado/app/MigrationTest.kt",
    "content": "package io.legado.app\n\nimport androidx.room.Room\nimport androidx.room.migration.Migration\nimport androidx.room.testing.MigrationTestHelper\nimport androidx.sqlite.db.framework.FrameworkSQLiteOpenHelperFactory\nimport androidx.test.ext.junit.runners.AndroidJUnit4\nimport androidx.test.platform.app.InstrumentationRegistry\nimport io.legado.app.data.AppDatabase\nimport org.junit.Rule\nimport org.junit.Test\nimport org.junit.runner.RunWith\nimport java.io.IOException\n\n@RunWith(AndroidJUnit4::class)\nclass MigrationTest {\n\n    private val TEST_DB = \"migration-test\"\n\n    private val ALL_MIGRATIONS = arrayOf<Migration>(\n\n    )\n\n    @get:Rule\n    val helper: MigrationTestHelper = MigrationTestHelper(\n        InstrumentationRegistry.getInstrumentation(),\n        AppDatabase::class.java.canonicalName,\n        FrameworkSQLiteOpenHelperFactory()\n    )\n\n    @Test\n    @Throws(IOException::class)\n    fun migrateAll() {\n        // Create earliest version of the database.\n        helper.createDatabase(TEST_DB, 50).apply {\n            close()\n        }\n\n        // Open latest version of the database. Room will validate the schema\n        // once all migrations execute.\n        Room.databaseBuilder(\n            InstrumentationRegistry.getInstrumentation().targetContext,\n            AppDatabase::class.java,\n            TEST_DB\n        ).addMigrations(*ALL_MIGRATIONS)\n            .build().apply {\n                openHelper.writableDatabase\n                close()\n            }\n    }\n}"
  },
  {
    "path": "app/src/androidTest/java/io/legado/app/UpdateTest.kt",
    "content": "package io.legado.app\n\nimport com.google.gson.Gson\nimport io.legado.app.exception.NoStackTraceException\nimport io.legado.app.help.http.okHttpClient\nimport io.legado.app.help.update.GithubRelease\nimport io.legado.app.utils.fromJsonObject\nimport okhttp3.Request\nimport org.junit.Assert.assertTrue\nimport org.junit.Test\n\nclass UpdateTest {\n\n    private val lastReleaseUrl =\n        \"https://api.github.com/repos/gedoor/legado/releases/latest\"\n\n    private val lastBetaReleaseUrl =\n        \"https://api.github.com/repos/gedoor/legado/releases/tags/beta\"\n\n    @Test\n    fun updateApp_beta() {\n        val body = okHttpClient.newCall(Request.Builder().url(lastBetaReleaseUrl).build()).execute()\n            .body!!.string()\n\n        val releaseList = Gson().fromJsonObject<GithubRelease>(body)\n            .getOrElse {\n                throw NoStackTraceException(\"获取新版本出错 \" + it.localizedMessage)\n            }\n            .gitReleaseToAppReleaseInfo()\n            .sortedByDescending { it.createdAt }\n\n        assertTrue(releaseList.size == 2)\n        assertTrue(releaseList.all { it.downloadUrl.isNotBlank() })\n        assertTrue(releaseList.all { it.versionName.isNotBlank() })\n    }\n\n    @Test\n    fun updateApp() {\n        val body = okHttpClient.newCall(Request.Builder().url(lastReleaseUrl).build()).execute()\n            .body!!.string()\n\n        val releaseList = Gson().fromJsonObject<GithubRelease>(body)\n            .getOrElse {\n                throw NoStackTraceException(\"获取新版本出错 \" + it.localizedMessage)\n            }\n            .gitReleaseToAppReleaseInfo()\n            .sortedByDescending { it.createdAt }\n\n        assertTrue(releaseList.size == 1)\n        assertTrue(releaseList.all { it.downloadUrl.isNotBlank() })\n        assertTrue(releaseList.all { it.versionName.isNotBlank() })\n    }\n\n}"
  },
  {
    "path": "app/src/debug/res/values/strings.xml",
    "content": "<resources>\n    <string name=\"app_name\">legado·D</string>\n    <string name=\"receiving_shared_label\">legado·D·search</string>\n</resources>"
  },
  {
    "path": "app/src/debug/res/values-zh/strings.xml",
    "content": "<resources>\n    <string name=\"app_name\">阅读·D</string>\n    <string name=\"receiving_shared_label\">阅读·D·搜索</string>\n</resources>"
  },
  {
    "path": "app/src/main/AndroidManifest.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\">\n\n    <uses-permission android:name=\"android.permission.INTERNET\" />\n    <uses-permission android:name=\"android.permission.WAKE_LOCK\" />\n    <uses-permission android:name=\"android.permission.READ_PHONE_STATE\" />\n    <uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\" />\n    <uses-permission android:name=\"android.permission.ACCESS_WIFI_STATE\" />\n    <uses-permission android:name=\"android.permission.FOREGROUND_SERVICE\" />\n    <uses-permission android:name=\"android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS\" />\n    <uses-permission android:name=\"android.permission.DOWNLOAD_WITHOUT_NOTIFICATION\" />\n    <uses-permission android:name=\"android.permission.MODIFY_AUDIO_SETTINGS\" />\n    <uses-permission android:name=\"android.permission.POST_NOTIFICATIONS\" />\n    <uses-permission android:name=\"android.permission.REQUEST_INSTALL_PACKAGES\" />\n    <uses-permission\n        android:name=\"android.permission.MANAGE_EXTERNAL_STORAGE\"\n        tools:ignore=\"ScopedStorage\" />\n    <uses-permission android:name=\"android.permission.READ_EXTERNAL_STORAGE\" />\n    <uses-permission android:name=\"android.permission.WRITE_EXTERNAL_STORAGE\" />\n    <uses-permission android:name=\"android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK\" />\n    <uses-permission android:name=\"android.permission.FOREGROUND_SERVICE_DATA_SYNC\" />\n\n    <application\n        android:name=\".App\"\n        android:allowBackup=\"true\"\n        android:icon=\"@mipmap/ic_launcher\"\n        android:label=\"${app_name}\"\n        android:networkSecurityConfig=\"@xml/network_security_config\"\n        android:requestLegacyExternalStorage=\"true\"\n        android:supportsRtl=\"true\"\n        android:theme=\"@style/AppTheme.Light\"\n        tools:ignore=\"AllowBackup,GoogleAppIndexingWarning,UnusedAttribute\">\n        <!-- 主入口 -->\n        <activity\n            android:name=\".ui.welcome.WelcomeActivity\"\n            android:exported=\"true\">\n            <intent-filter>\n                <action android:name=\"android.intent.action.MAIN\" />\n\n                <category android:name=\"android.intent.category.LAUNCHER\" />\n            </intent-filter>\n        </activity>\n        <!-- 图标1 -->\n        <activity\n            android:name=\".ui.welcome.Launcher1\"\n            android:enabled=\"false\"\n            android:exported=\"true\"\n            android:icon=\"@mipmap/launcher1\">\n            <intent-filter>\n                <action android:name=\"android.intent.action.MAIN\" />\n\n                <category android:name=\"android.intent.category.LAUNCHER\" />\n            </intent-filter>\n        </activity>\n        <!-- 图标2 -->\n        <activity\n            android:name=\".ui.welcome.Launcher2\"\n            android:enabled=\"false\"\n            android:exported=\"true\"\n            android:icon=\"@mipmap/launcher2\">\n            <intent-filter>\n                <action android:name=\"android.intent.action.MAIN\" />\n\n                <category android:name=\"android.intent.category.LAUNCHER\" />\n            </intent-filter>\n        </activity>\n        <!-- 图标3 -->\n        <activity\n            android:name=\".ui.welcome.Launcher3\"\n            android:enabled=\"false\"\n            android:exported=\"true\"\n            android:icon=\"@mipmap/launcher3\">\n            <intent-filter>\n                <action android:name=\"android.intent.action.MAIN\" />\n\n                <category android:name=\"android.intent.category.LAUNCHER\" />\n            </intent-filter>\n        </activity>\n        <!-- 图标4 -->\n        <activity\n            android:name=\".ui.welcome.Launcher4\"\n            android:enabled=\"false\"\n            android:exported=\"true\"\n            android:icon=\"@mipmap/launcher4\">\n            <intent-filter>\n                <action android:name=\"android.intent.action.MAIN\" />\n\n                <category android:name=\"android.intent.category.LAUNCHER\" />\n            </intent-filter>\n        </activity>\n        <!-- 图标5 -->\n        <activity\n            android:name=\".ui.welcome.Launcher5\"\n            android:enabled=\"false\"\n            android:exported=\"true\"\n            android:icon=\"@mipmap/launcher5\">\n            <intent-filter>\n                <action android:name=\"android.intent.action.MAIN\" />\n\n                <category android:name=\"android.intent.category.LAUNCHER\" />\n            </intent-filter>\n        </activity>\n        <!-- 图标6 -->\n        <activity\n            android:name=\".ui.welcome.Launcher6\"\n            android:enabled=\"false\"\n            android:exported=\"true\"\n            android:icon=\"@mipmap/launcher6\">\n            <intent-filter>\n                <action android:name=\"android.intent.action.MAIN\" />\n\n                <category android:name=\"android.intent.category.LAUNCHER\" />\n            </intent-filter>\n        </activity>\n        <!-- 主界面 -->\n        <activity\n            android:name=\".ui.main.MainActivity\"\n            android:alwaysRetainTaskState=\"true\"\n            android:configChanges=\"locale|keyboardHidden|orientation|screenSize|smallestScreenSize|screenLayout|uiMode\"\n            android:exported=\"true\"\n            android:launchMode=\"singleTask\" />\n        <!-- 阅读界面 -->\n        <activity\n            android:name=\".ui.book.read.ReadBookActivity\"\n            android:configChanges=\"locale|keyboardHidden|orientation|screenSize|smallestScreenSize|screenLayout\"\n            android:exported=\"true\"\n            android:launchMode=\"singleTask\">\n            <intent-filter>\n                <action android:name=\"com.samsung.android.support.REMOTE_ACTION\" />\n            </intent-filter>\n\n            <meta-data\n                android:name=\"com.samsung.android.support.REMOTE_ACTION\"\n                android:resource=\"@xml/spen_remote_actions\" />\n        </activity>\n\n        <!-- 阅读漫画界面 -->\n        <activity\n            android:name=\".ui.book.manga.ReadMangaActivity\"\n            android:exported=\"false\"\n            android:launchMode=\"singleTask\">\n            <intent-filter>\n                <action android:name=\"com.samsung.android.support.REMOTE_ACTION\" />\n            </intent-filter>\n\n            <meta-data\n                android:name=\"com.samsung.android.support.REMOTE_ACTION\"\n                android:resource=\"@xml/spen_remote_actions\" />\n        </activity>\n        <!-- 书籍详情页 -->\n        <activity\n            android:name=\".ui.book.info.BookInfoActivity\"\n            android:launchMode=\"singleTop\" />\n        <!-- 书籍信息编辑 -->\n        <activity\n            android:name=\".ui.book.info.edit.BookInfoEditActivity\"\n            android:launchMode=\"singleTask\" />\n        <!-- 音频播放界面 -->\n        <activity\n            android:name=\"io.legado.app.ui.book.audio.AudioPlayActivity\"\n            android:launchMode=\"singleTask\" />\n        <!-- 授权界面 -->\n        <activity\n            android:name=\"io.legado.app.lib.permission.PermissionActivity\"\n            android:theme=\"@style/Activity.Permission\" />\n        <!-- 二维码扫描 -->\n        <activity\n            android:name=\".ui.qrcode.QrCodeActivity\"\n            android:launchMode=\"singleTask\" />\n        <!-- 规则订阅 -->\n        <activity\n            android:name=\".ui.rss.subscription.RuleSubActivity\"\n            android:launchMode=\"singleTask\" />\n        <!-- 书源编辑 -->\n        <activity\n            android:name=\".ui.book.source.edit.BookSourceEditActivity\"\n            android:configChanges=\"locale|keyboardHidden|orientation|screenSize|smallestScreenSize|screenLayout\"\n            android:launchMode=\"singleTask\"\n            android:windowSoftInputMode=\"adjustResize|stateHidden\" />\n        <!-- 订阅源编辑 -->\n        <activity\n            android:name=\".ui.rss.source.edit.RssSourceEditActivity\"\n            android:configChanges=\"locale|keyboardHidden|orientation|screenSize|smallestScreenSize|screenLayout\"\n            android:launchMode=\"singleTask\"\n            android:windowSoftInputMode=\"adjustResize|stateHidden\" />\n        <!-- 替换规则编辑 -->\n        <activity\n            android:name=\".ui.replace.edit.ReplaceEditActivity\"\n            android:configChanges=\"locale|keyboardHidden|orientation|screenSize|smallestScreenSize|screenLayout\"\n            android:launchMode=\"singleTask\"\n            android:windowSoftInputMode=\"adjustResize|stateHidden\" />\n        <!-- 配置界面 -->\n        <activity\n            android:name=\".ui.config.ConfigActivity\"\n            android:launchMode=\"standard\" />\n        <!-- 搜索界面 -->\n        <activity\n            android:name=\".ui.book.search.SearchActivity\"\n            android:launchMode=\"standard\" />\n        <!-- 关于界面 -->\n        <activity\n            android:name=\".ui.about.AboutActivity\"\n            android:launchMode=\"singleTask\"\n            android:screenOrientation=\"behind\" />\n        <!-- 书源管理 -->\n        <activity\n            android:name=\".ui.book.source.manage.BookSourceActivity\"\n            android:launchMode=\"singleTop\"\n            android:screenOrientation=\"behind\" />\n        <!-- 订阅源管理 -->\n        <activity\n            android:name=\".ui.rss.source.manage.RssSourceActivity\"\n            android:launchMode=\"singleTop\"\n            android:screenOrientation=\"behind\" />\n        <!-- txt目录规则管理 -->\n        <activity\n            android:name=\".ui.book.toc.rule.TxtTocRuleActivity\"\n            android:launchMode=\"singleTop\"\n            android:screenOrientation=\"behind\" />\n        <!-- 替换规则界面 -->\n        <activity\n            android:name=\".ui.replace.ReplaceRuleActivity\"\n            android:launchMode=\"singleTop\"\n            android:screenOrientation=\"behind\" />\n        <!-- 书籍管理 -->\n        <activity\n            android:name=\".ui.book.manage.BookshelfManageActivity\"\n            android:launchMode=\"singleTop\"\n            android:screenOrientation=\"behind\" />\n        <!-- 书源调试 -->\n        <activity\n            android:name=\".ui.book.source.debug.BookSourceDebugActivity\"\n            android:launchMode=\"singleTop\"\n            android:screenOrientation=\"behind\" />\n        <!-- 目录 -->\n        <activity\n            android:name=\".ui.book.toc.TocActivity\"\n            android:launchMode=\"singleTop\"\n            android:screenOrientation=\"behind\" />\n        <!-- 正文搜索 -->\n        <activity\n            android:name=\".ui.book.searchContent.SearchContentActivity\"\n            android:launchMode=\"singleTop\"\n            android:screenOrientation=\"behind\" />\n        <!-- RSS条目 -->\n        <activity\n            android:name=\".ui.rss.article.RssSortActivity\"\n            android:launchMode=\"singleTop\" />\n        <!-- RSS阅读 -->\n        <activity\n            android:name=\".ui.rss.read.ReadRssActivity\"\n            android:configChanges=\"orientation|screenSize\"\n            android:hardwareAccelerated=\"true\"\n            android:launchMode=\"singleTop\" />\n        <!-- 导入书籍 -->\n        <activity\n            android:name=\".ui.book.import.local.ImportBookActivity\"\n            android:launchMode=\"singleTop\" />\n        <!-- 添加远程 -->\n        <activity\n            android:name=\".ui.book.import.remote.RemoteBookActivity\"\n            android:launchMode=\"singleTop\" />\n        <!-- 发现界面 -->\n        <activity\n            android:name=\".ui.book.explore.ExploreShowActivity\"\n            android:launchMode=\"singleTop\" />\n        <!-- 订阅源调试 -->\n        <activity\n            android:name=\".ui.rss.source.debug.RssSourceDebugActivity\"\n            android:launchMode=\"singleTop\" />\n        <!-- Rss收藏 -->\n        <activity\n            android:name=\".ui.rss.favorites.RssFavoritesActivity\"\n            android:launchMode=\"singleTop\" />\n        <!-- 书签 -->\n        <activity\n            android:name=\".ui.book.bookmark.AllBookmarkActivity\"\n            android:launchMode=\"singleTop\" />\n        <!-- 缓存界面 -->\n        <activity\n            android:name=\".ui.book.cache.CacheActivity\"\n            android:launchMode=\"singleTop\" />\n        <!-- WebView界面 -->\n        <activity\n            android:name=\".ui.browser.WebViewActivity\"\n            android:configChanges=\"locale|keyboardHidden|orientation|screenSize|smallestScreenSize|screenLayout\"\n            android:launchMode=\"standard\" />\n        <!-- 书源登录 -->\n        <activity\n            android:name=\"io.legado.app.ui.login.SourceLoginActivity\"\n            android:configChanges=\"orientation|screenSize\"\n            android:hardwareAccelerated=\"true\"\n            android:theme=\"@style/AppTheme.Transparent\" />\n        <!-- 阅读记录 -->\n        <activity\n            android:name=\".ui.about.ReadRecordActivity\"\n            android:configChanges=\"orientation|screenSize\"\n            android:hardwareAccelerated=\"true\" />\n        <!-- 字典管理 -->\n        <activity\n            android:name=\".ui.dict.rule.DictRuleActivity\"\n            android:configChanges=\"orientation|screenSize\"\n            android:hardwareAccelerated=\"true\" />\n        <!-- 文件管理 -->\n        <activity\n            android:name=\".ui.file.FileManageActivity\"\n            android:configChanges=\"orientation|screenSize\"\n            android:hardwareAccelerated=\"true\" />\n        <!-- 选择文件 -->\n        <activity\n            android:name=\".ui.file.HandleFileActivity\"\n            android:configChanges=\"orientation|screenSize\"\n            android:hardwareAccelerated=\"true\"\n            android:theme=\"@style/AppTheme.Transparent\" />\n        <!-- 文字处理 -->\n        <activity\n            android:name=\".receiver.SharedReceiverActivity\"\n            android:exported=\"true\"\n            android:label=\"@string/receiving_shared_label\"\n            android:theme=\"@style/AppTheme.Transparent\">\n            <intent-filter>\n                <action android:name=\"android.intent.action.PROCESS_TEXT\" />\n\n                <category android:name=\"android.intent.category.DEFAULT\" />\n\n                <data android:mimeType=\"text/plain\" />\n            </intent-filter>\n            <intent-filter>\n                <action android:name=\"android.intent.action.SEND\" />\n\n                <category android:name=\"android.intent.category.DEFAULT\" />\n\n                <data android:mimeType=\"text/plain\" />\n            </intent-filter>\n        </activity>\n        <!-- 一键导入 -->\n        <activity\n            android:name=\".ui.association.OnLineImportActivity\"\n            android:configChanges=\"locale|keyboardHidden|orientation|screenSize|smallestScreenSize|screenLayout\"\n            android:exported=\"true\"\n            android:theme=\"@style/AppTheme.Transparent\">\n            <intent-filter>\n                <action android:name=\"android.intent.action.VIEW\" />\n\n                <category android:name=\"android.intent.category.DEFAULT\" />\n                <category android:name=\"android.intent.category.BROWSABLE\" />\n\n                <data android:scheme=\"legado\" />\n                <data android:scheme=\"yuedu\" />\n            </intent-filter>\n        </activity>\n        <!-- 验证码输入 -->\n        <activity\n            android:name=\".ui.association.VerificationCodeActivity\"\n            android:configChanges=\"locale|keyboardHidden|orientation|screenSize|smallestScreenSize|screenLayout\"\n            android:theme=\"@style/AppTheme.Transparent\" />\n        <!-- 跳转确认 -->\n        <activity\n            android:name=\".ui.association.OpenUrlConfirmActivity\"\n            android:configChanges=\"locale|keyboardHidden|orientation|screenSize|smallestScreenSize|screenLayout\"\n            android:theme=\"@style/AppTheme.Transparent\" />\n        <!-- 打开文件 -->\n        <activity\n            android:name=\".ui.association.FileAssociationActivity\"\n            android:exported=\"true\"\n            android:launchMode=\"singleTop\"\n            android:theme=\"@style/AppTheme.Transparent\">\n            <!-- VIEW (Open with) action -->\n            <!-- Works when an app knows the media type of a file, e.g. Gmail or Chrome. -->\n            <intent-filter>\n                <action android:name=\"android.intent.action.VIEW\" />\n\n                <category android:name=\"android.intent.category.DEFAULT\" />\n                <category android:name=\"android.intent.category.BROWSABLE\" />\n\n                <data android:scheme=\"app\" />\n                <data android:scheme=\"content\" />\n                <data android:scheme=\"file\" />\n                <!-- text -->\n                <data android:mimeType=\"text/plain\" />\n                <!-- json -->\n                <data android:mimeType=\"application/json\" />\n                <!-- EPUB -->\n                <data android:mimeType=\"application/epub+zip\" />\n                <!-- pdf -->\n                <data android:mimeType=\"application/pdf\" />\n                <!-- mobi -->\n                <data android:mimeType=\"application/mobi\" />\n                <data android:mimeType=\"application/x-mobipocket-ebook\" />\n                <!-- azw/azw3 -->\n                <data android:mimeType=\"application/azw\" />\n                <data android:mimeType=\"application/azw3\" />\n                <data android:mimeType=\"application/x-mobi8-ebook\" />\n                <data android:mimeType=\"application/octet-stream\" />\n            </intent-filter>\n            <!-- Works when an app doesn't know the media type, e.g. Dropbox -->\n            <intent-filter>\n                <action android:name=\"android.intent.action.VIEW\" />\n\n                <category android:name=\"android.intent.category.DEFAULT\" />\n                <category android:name=\"android.intent.category.BROWSABLE\" />\n\n                <data android:host=\"*\" />\n\n                <data android:scheme=\"app\" />\n                <data android:scheme=\"content\" />\n                <data android:scheme=\"file\" />\n                <!-- This media type is necessary, otherwise it won't match on the file extension -->\n                <data android:mimeType=\"*/*\" />\n                <!--TXT-->\n                <data android:pathAdvancedPattern=\".*\\\\.[tT][xX][tT]\" />\n                <data android:pathPattern=\".*\\\\.txt\" />\n                <data android:pathPattern=\".*\\\\.TXT\" />\n                <!--JSON-->\n                <data android:pathAdvancedPattern=\".*\\\\.[jJ][sS][oO][nN]\" />\n                <data android:pathPattern=\".*\\\\.json\" />\n                <data android:pathPattern=\".*\\\\.JSON\" />\n                <!-- EPUB -->\n                <data android:pathAdvancedPattern=\".*\\\\.[eE][pP][uU][bB]\" />\n                <data android:pathPattern=\".*\\\\.epub\" />\n                <data android:pathPattern=\".*\\\\.EPUB\" />\n                <!-- pdf -->\n                <data android:pathAdvancedPattern=\".*\\\\.[pP][dD][fF]\" />\n                <data android:pathPattern=\".*\\\\.pdf\" />\n                <data android:pathPattern=\".*\\\\.PDF\" />\n                <!-- mobi -->\n                <data android:pathAdvancedPattern=\".*\\\\.[mM][oO][bB][iI]\" />\n                <data android:pathPattern=\".*\\\\.mobi\" />\n                <data android:pathPattern=\".*\\\\.MOBI\" />\n                <!-- azw -->\n                <data android:pathAdvancedPattern=\".*\\\\.[aA][zZ][wW]\" />\n                <data android:pathPattern=\".*\\\\.azw\" />\n                <data android:pathPattern=\".*\\\\.AZW\" />\n                <!-- azw3 -->\n                <data android:pathAdvancedPattern=\".*\\\\.[aA][zZ][wW]3\" />\n                <data android:pathPattern=\".*\\\\.azw3\" />\n                <data android:pathPattern=\".*\\\\.AZW3\" />\n                <!-- zip -->\n                <data android:pathAdvancedPattern=\".*\\\\.[zZ][iI][pP]\" />\n                <data android:pathPattern=\".*\\\\.zip\" />\n                <data android:pathPattern=\".*\\\\.ZIP\" />\n                <!-- rar -->\n                <data android:pathAdvancedPattern=\".*\\\\.[rR][aA][rR]\" />\n                <data android:pathPattern=\".*\\\\.rar\" />\n                <data android:pathPattern=\".*\\\\.RAR\" />\n                <!-- 7z -->\n                <data android:pathAdvancedPattern=\".*\\\\.7[zZ]\" />\n                <data android:pathPattern=\".*\\\\.7z\" />\n                <data android:pathPattern=\".*\\\\.7Z\" />\n            </intent-filter>\n        </activity>\n\n        <service\n            android:name=\".service.CheckSourceService\"\n            android:foregroundServiceType=\"dataSync\" />\n        <service\n            android:name=\".service.CacheBookService\"\n            android:foregroundServiceType=\"dataSync\" />\n        <service\n            android:name=\".service.ExportBookService\"\n            android:foregroundServiceType=\"dataSync\" />\n        <service\n            android:name=\".service.WebService\"\n            android:foregroundServiceType=\"dataSync\" />\n        <service\n            android:name=\".service.WebTileService\"\n            android:exported=\"true\"\n            android:foregroundServiceType=\"dataSync\"\n            android:icon=\"@drawable/ic_web_service_noti\"\n            android:label=\"legado Web Service\"\n            android:permission=\"android.permission.BIND_QUICK_SETTINGS_TILE\"\n            tools:targetApi=\"24\">\n            <intent-filter>\n                <action android:name=\"android.service.quicksettings.action.QS_TILE\" />\n            </intent-filter>\n        </service>\n        <service\n            android:name=\".service.TTSReadAloudService\"\n            android:foregroundServiceType=\"mediaPlayback\" />\n        <service\n            android:name=\".service.HttpReadAloudService\"\n            android:foregroundServiceType=\"mediaPlayback\" />\n        <service\n            android:name=\".service.AudioPlayService\"\n            android:foregroundServiceType=\"mediaPlayback\" />\n        <service\n            android:name=\".service.DownloadService\"\n            android:foregroundServiceType=\"dataSync\" />\n\n        <receiver\n            android:name=\".receiver.MediaButtonReceiver\"\n            android:exported=\"true\">\n            <intent-filter>\n                <action android:name=\"android.intent.action.MEDIA_BUTTON\" />\n            </intent-filter>\n        </receiver>\n\n        <provider\n            android:name=\".api.ReaderProvider\"\n            android:authorities=\"${applicationId}.readerProvider\"\n            android:enabled=\"true\"\n            android:exported=\"true\"\n            tools:ignore=\"ExportedContentProvider\" />\n        <provider\n            android:name=\"androidx.core.content.FileProvider\"\n            android:authorities=\"${applicationId}.fileProvider\"\n            android:exported=\"false\"\n            android:grantUriPermissions=\"true\">\n            <meta-data\n                android:name=\"android.support.FILE_PROVIDER_PATHS\"\n                android:resource=\"@xml/file_paths\" />\n        </provider>\n        <provider\n            android:name=\"androidx.startup.InitializationProvider\"\n            android:authorities=\"${applicationId}.androidx-startup\"\n            android:exported=\"false\"\n            tools:node=\"merge\">\n            <meta-data\n                android:name=\"androidx.emoji2.text.EmojiCompatInitializer\"\n                tools:node=\"remove\" />\n        </provider>\n\n        <meta-data\n            android:name=\"channel\"\n            android:value=\"${APP_CHANNEL_VALUE}\" />\n    </application>\n\n    <queries>\n        <intent>\n            <action android:name=\"android.intent.action.VIEW\" />\n            <data android:scheme=\"*\" />\n        </intent>\n        <intent>\n            <action android:name=\"android.intent.action.VIEW\" />\n            <data\n                android:mimeType=\"*/*\"\n                android:scheme=\"*\" />\n        </intent>\n        <intent>\n            <action android:name=\"android.intent.action.TTS_SERVICE\" />\n        </intent>\n        <intent>\n            <action android:name=\"android.intent.action.PROCESS_TEXT\" />\n            <data android:mimeType=\"text/plain\" />\n        </intent>\n    </queries>\n\n</manifest>"
  },
  {
    "path": "app/src/main/assets/18PlusList.txt",
    "content": "OGN5dS5jb20=\nc2cwMC54eXo=\naXRyYWZmaWNuZXQuY29t\neGlhb3FpYW5nNTIw\nMTIzeGlhb3FpYW5n\neGlhb3FpYW5neHM=\neGlhb3FpYW5nNTIw\nMzM1eHM=\neGN4czk=\neGN4czUyMA==\nc2h1YmFvYW4=\nc2h1YmFvd2FuZzEyMw==\nc2h1YmFvYW4=\naGFpdGFuZzEyMw==\neXV6aGFpd3VsYQ==\ncG8xOA==\nYmwtbm92ZWw=\nNXRucw==\nc2hhb3NodWdl\namluamlzaHV3dQ==\nNDJ3Zw==\neWlxdXNodQ==\nc2h1YmFvd2FuZzEyMw==\nM2hlYmFv\nMzNoZWJhbw==\nbHVvcWl1enc=\nbXlzaHVnZQ==\nc3NzeHN3\neWl0ZQ==\nY3Vpd2VpanV1\nY3Vpd2VpanV4cw==\nY3Vpd2VpanV4\neGlhb3FpYW5nd3g=\nYXN6dw==\nYXN6dzY=\nc2FuaGFveHM=\nODdzaHV3dQ==\nNDh3eA==\nbG9uZ3Rlbmcy\nNnF3eA==\nbG9uZ3Rlbmd4cw==\naGF4ZHU=\nM3R3eA==\naGF4d3g1\nNjZsZXdlbg==\neGJhbnpodQ==\naGR5cA==\nZHliejk=\nZGl5aWJhbnpodTk=\nZGl5aWJhbnpodQ==\nZGl5aWJhbnpodTc=\nYnoyMjI=\nd29kZWFwaTAwMQ==\ndGFuZ3poZWthbg==\nYmF4aWFueHM=\neGlhb3NodW9zaGVuemhhbg==\nZGFtb2tl\nemh3ZW5wZw==\neXV6aGFpZ2U=\nd21wOA==\nOXhpYW53ZW4=\nbmFucmVudmlw\ncmV5b28=\neWZ4aWFvc2h1b2U=\nc2Fuaml1enc=\nN3Fpbmc3\ncWR4aWFvc2h1bw==\nY2hpbmVzZXpq\nMzlzaHViYW8=\na3l4czU=\nNTZtcw==\nbml1c2hh\nbWt4czY=\nMjIyMjJ4cw==\nOTVkdXNodQ==\nYmFuemh1MjI=\nd3JsdHh0\ndHVkb3V0eHQ=\ncm5neHM=\nOTl3ZW5rdQ==\nbGFvc2lqaXhz\nZnVzaHV6aGFpMQ==\ncG8xOA==\nczUyMTc=\nc2FuaGFveHM=\nNTJrc2h1\nNDhyeA==\nZWNub3ZlbA==\nbGllaHVvenc=\neGlhb3FpYW5nd3g=\nNTJrc2h1\nNDh3eA==\nNTJrc2h1\nMDB1aQ==\nMDFieg==\nc2h1YmFvMQ==\nZG54aWFvc2h1b2E=\nam5zaHViYQ==\nMThzaHV3dQ==\nbGV4cw==\nMzM1eHM=\ndXB1\nZnVndW9kdQ==\nODB0eHQ=\nYWFyZWFk\neWlkdWR1MQ==\nYmFuemh1d2FuZw==\ncWloYW9xaWhhbw==\nOHhpYW54cw==\namluamlzaHV3dQ==\nd21wOA==\nZXl1c2h1d3U=\nNTB4c2Y=\naGF4d3g1\ncG93YW5qdWFu\nd2luMTBjaXR5\neWV5ZXhzdw==\nbXlzaHVnZQ==\neGlhbmd0eHN3\nY3Vpd2VpanV4\nMzY2eHN3\naHVheXVld2Vua3U=\neW91ZGlhbmxlbg==\nc291Nzg=\nbGFucm91Mg==\ncXFib29r\neW91d3V4cw==\ncnVpbGlzYWxl\nMzY1bXd3\nZnV3ZW5o\nbGVzYmw=\nYXd1Ym9vaw==\nbGl5dXhpYW5nMjAyMA==\nOTJwb3Bv\nZnVzaHV0dWFu\nODhkYW5tZWk=\nZG14cw==\neXVsaW56aGFueWU=\nM2hlYmFv\neGd1YWd1YXhz\nZGl5aWJhbnpodTY=\naXJlYWR4cw==\nc2h1YmFvOTY=\nZGl5aWJhbnpodTU1NQ==\nc2Fuaml1enc=\nN3Fpbmc3\nNjZsZXdlbg==\na3l4czU=\nMjIyMjJ4cw==\nc2hhb3NodWdl\namlsaW41NQ==\nbWt4czY=\namluc2h1bG91\neGlhbndhbmdz\neWlkdWR1\ncWR0eHQ=\nMTZib29rMQ==\nam1zaHV3dQ==\nMzY2eHN3\nZHliejk=\nc2hvdWRhOA==\nZnlxMTg=\neWlzaHVn\neXV6aGFpd3VsYQ==\nMTFiYW56aHU=\nMTIzeGlhb3FpYW5n\nZGl5aWJhbnpodTk=\nZGl5aWJhbnpodQ==\nMzY2eHN3\nODdzaHV3dQ==\nNnF3eA==\nemhlbmh1bnhpYW9zaHVv\nbG9uZ3Rlbmc1Mg==\neGlueGluZ3hpYW5nemhpZmE=\nZHliejk=\nZHVvemhla2Fu\nMTIzeGlhb3FpYW5n\nMzM1eHM=\nam1zaHV3dQ==\nc2hhb3NodWdl\nbGF3ZW54cw==\ncnVzaHV3dQ==\nMzY2eHN3\nNTB4c2Y=\nbGV3ZW41NQ==\naGFpdGFuZzEyMw==\naGViYW81MjA=\nbHVvcWl1enc=\nc3NzeHN3\nc2h1c2h1d3V4cw==\ncm5neHM=\ncWR4aWFvc2h1bw==\ndHl1ZQ==\nY2hlNDM=\nbG9uZ3Rlbmcy\namZ5eHNo\naGV0dTI=\nbGFvc2lqaXhz\nbG9uZ3Rlbmd4cw==\nbGllaHVvenc=\nc2h1YmFvYW4=\neHNodW9zaHVv\nNTIxZGFubWVp\nYmFuemh1MjI=\ncWtzaHU=\neWZ4aWFvc2h1b2U=\na3lnc28=\nc2h1bG91YmE=\nNXRucw==\nN3Fpbmc3\nbWlhb2R1NQ==\neXVzaHV3ZW4=\nYWFyZWFk\ncXRzaHU=\nMTdzaHV3dQ==\nc2h1YmFvMnM=\nYnowMDE=\nZGFtb2dl\nMTMxdGI=\naXhpYW9z\nbXlzaHVnZQ==\nOXhpYW53ZW4=\nZHVvemhla2Fu\nMTIwdw==\nc2h1c2h1d3U1MjA=\nc2h1YmFvMnM=\nYWd4c3c=\nOTR4c3c=\ncG8xOA==\neWFvY2hpeHM=\neGlhb3FpYW5neHM=\nYm9va2Js\nc2Fuaml1eHM=\nd29kZXNodWJhbw==\nem9uZ2NhaXhpYW9zaHVvMg==\nOWI4OTEzOTRkZjVi\nMThub3ZlbA==\nYWFib29r\nYjF0eHQ=\neXVjYWl6dw==\nYzl0eHQ=\nZGl5aWJhbnpodTU1NQ==\nMzBtYw==\neGlueXVzaHV3dQ==\nc2h1YmFvd2FuZzEyMw==\nYWd4cw==\nYmlxdWdlbmw=\nc2hpcWlzaHV3dQ==\nc2lsdWtl\nZGl5aWJhbnpodTg=\nZGl5aWJhbnpodTk=\naGV0dW54cw==\nOTl3ZW5rdQ==\naGFpdGFuZ3NodXd1\nOTd5ZA==\neXV6aGFpd3UxMQ==\nY3Vpd2VpanV4cw==\nY2JpcXU=\nNTIxZGFubWVp\nc2h1YmFvMzM=\nc2FuaGFvMQ==\ndGlhbm1lbmd3ZW5rdQ==\neXVzaHV3dTUyMA==\nc2h1YmFvMjIy\nc2h1YmFvd2FuZzEyMw==\neXVib29r\nY2JpcXU=\nMWxld2Vu\nMTV4c3c=\neG5jd3h3\nc2h1YmFvd2FuZzEyMw==\nc2FuaGFveHM=\neXV3YW5nc2hl\nYmlxdXRz\nbGFtZWl4cw==\neGJhbnpodQ==\ncWR4aWFvc2h1bw==\nbWh0bGE=\nOTl3ZW5rdQ==\neGlhb3FpYW5nNTIw\ndGlhbm1lbmd3ZW5rdQ==\nYWlmdXNodQ==\nbWlhb2R1NQ==\nbWlmZW5neHM="
  },
  {
    "path": "app/src/main/assets/LICENSE.md",
    "content": "GNU GENERAL PUBLIC LICENSE\nVersion 3, 29 June 2007\n\nCopyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>\nEveryone is permitted to copy and distribute verbatim copies of this license document,\nbut changing it is not allowed.\n\n## Preamble\n\nThe GNU General Public License is a free, copyleft license for\nsoftware and other kinds of works.\n\nThe licenses for most software and other practical works are designed\nto take away your freedom to share and change the works. By contrast,\nthe GNU General Public License is intended to guarantee your freedom to\nshare and change all versions of a program--to make sure it remains free\nsoftware for all its users. We, the Free Software Foundation, use the\nGNU General Public License for most of our software; it applies also to\nany other work released this way by its authors. You can apply it to\nyour programs, too.\n\nWhen we speak of free software, we are referring to freedom, not\nprice. Our General Public Licenses are designed to make sure that you\nhave the freedom to distribute copies of free software (and charge for\nthem if you wish), that you receive source code or can get it if you\nwant it, that you can change the software or use pieces of it in new\nfree programs, and that you know you can do these things.\n\nTo protect your rights, we need to prevent others from denying you\nthese rights or asking you to surrender the rights. Therefore, you have\ncertain responsibilities if you distribute copies of the software, or if\nyou modify it: responsibilities to respect the freedom of others.\n\nFor example, if you distribute copies of such a program, whether\ngratis or for a fee, you must pass on to the recipients the same\nfreedoms that you received. You must make sure that they, too, receive\nor can get the source code. And you must show them these terms so they\nknow their rights.\n\nDevelopers that use the GNU GPL protect your rights with two steps:\n(1) assert copyright on the software, and (2) offer you this License\ngiving you legal permission to copy, distribute and/or modify it.\n\nFor the developers' and authors' protection, the GPL clearly explains\nthat there is no warranty for this free software. For both users' and\nauthors' sake, the GPL requires that modified versions be marked as\nchanged, so that their problems will not be attributed erroneously to\nauthors of previous versions.\n\nSome devices are designed to deny users access to install or run\nmodified versions of the software inside them, although the manufacturer\ncan do so. This is fundamentally incompatible with the aim of\nprotecting users' freedom to change the software. The systematic\npattern of such abuse occurs in the area of products for individuals to\nuse, which is precisely where it is most unacceptable. Therefore, we\nhave designed this version of the GPL to prohibit the practice for those\nproducts. If such problems arise substantially in other domains, we\nstand ready to extend this provision to those domains in future versions\nof the GPL, as needed to protect the freedom of users.\n\nFinally, every program is threatened constantly by software patents.\nStates should not allow patents to restrict development and use of\nsoftware on general-purpose computers, but in those that do, we wish to\navoid the special danger that patents applied to a free program could\nmake it effectively proprietary. To prevent this, the GPL assures that\npatents cannot be used to render the program non-free.\n\nThe precise terms and conditions for copying, distribution and\nmodification follow.\n\n## TERMS AND CONDITIONS\n\n0. Definitions.\n\n\"This License\" refers to version 3 of the GNU General Public License.\n\n\"Copyright\" also means copyright-like laws that apply to other kinds of\nworks, such as semiconductor masks.\n\n\"The Program\" refers to any copyrightable work licensed under this\nLicense. Each licensee is addressed as \"you\".  \"Licensees\" and\n\"recipients\" may be individuals or organizations.\n\nTo \"modify\" a work means to copy from or adapt all or part of the work\nin a fashion requiring copyright permission, other than the making of an\nexact copy. The resulting work is called a \"modified version\" of the\nearlier work or a work \"based on\" the earlier work.\n\nA \"covered work\" means either the unmodified Program or a work based\non the Program.\n\nTo \"propagate\" a work means to do anything with it that, without\npermission, would make you directly or secondarily liable for\ninfringement under applicable copyright law, except executing it on a\ncomputer or modifying a private copy. Propagation includes copying,\ndistribution (with or without modification), making available to the\npublic, and in some countries other activities as well.\n\nTo \"convey\" a work means any kind of propagation that enables other\nparties to make or receive copies. Mere interaction with a user through\na computer network, with no transfer of a copy, is not conveying.\n\nAn interactive user interface displays \"Appropriate Legal Notices\"\nto the extent that it includes a convenient and prominently visible\nfeature that (1) displays an appropriate copyright notice, and (2)\ntells the user that there is no warranty for the work (except to the\nextent that warranties are provided), that licensees may convey the\nwork under this License, and how to view a copy of this License. If\nthe interface presents a list of user commands or options, such as a\nmenu, a prominent item in the list meets this criterion.\n\n1. Source Code.\n\nThe \"source code\" for a work means the preferred form of the work\nfor making modifications to it.  \"Object code\" means any non-source\nform of a work.\n\nA \"Standard Interface\" means an interface that either is an official\nstandard defined by a recognized standards body, or, in the case of\ninterfaces specified for a particular programming language, one that\nis widely used among developers working in that language.\n\nThe \"System Libraries\" of an executable work include anything, other\nthan the work as a whole, that (a) is included in the normal form of\npackaging a Major Component, but which is not part of that Major\nComponent, and (b) serves only to enable use of the work with that\nMajor Component, or to implement a Standard Interface for which an\nimplementation is available to the public in source code form. A\n\"Major Component\", in this context, means a major essential component\n(kernel, window system, and so on) of the specific operating system\n(if any) on which the executable work runs, or a compiler used to\nproduce the work, or an object code interpreter used to run it.\n\nThe \"Corresponding Source\" for a work in object code form means all\nthe source code needed to generate, install, and (for an executable\nwork) run the object code and to modify the work, including scripts to\ncontrol those activities. However, it does not include the work's\nSystem Libraries, or general-purpose tools or generally available free\nprograms which are used unmodified in performing those activities but\nwhich are not part of the work. For example, Corresponding Source\nincludes interface definition files associated with source files for\nthe work, and the source code for shared libraries and dynamically\nlinked subprograms that the work is specifically designed to require,\nsuch as by intimate data communication or control flow between those\nsubprograms and other parts of the work.\n\nThe Corresponding Source need not include anything that users\ncan regenerate automatically from other parts of the Corresponding\nSource.\n\nThe Corresponding Source for a work in source code form is that\nsame work.\n\n2. Basic Permissions.\n\nAll rights granted under this License are granted for the term of\ncopyright on the Program, and are irrevocable provided the stated\nconditions are met. This License explicitly affirms your unlimited\npermission to run the unmodified Program. The output from running a\ncovered work is covered by this License only if the output, given its\ncontent, constitutes a covered work. This License acknowledges your\nrights of fair use or other equivalent, as provided by copyright law.\n\nYou may make, run and propagate covered works that you do not\nconvey, without conditions so long as your license otherwise remains\nin force. You may convey covered works to others for the sole purpose\nof having them make modifications exclusively for you, or provide you\nwith facilities for running those works, provided that you comply with\nthe terms of this License in conveying all material for which you do\nnot control copyright. Those thus making or running the covered works\nfor you must do so exclusively on your behalf, under your direction\nand control, on terms that prohibit them from making any copies of\nyour copyrighted material outside their relationship with you.\n\nConveying under any other circumstances is permitted solely under\nthe conditions stated below. Sublicensing is not allowed; section 10\nmakes it unnecessary.\n\n3. Protecting Users' Legal Rights From Anti-Circumvention Law.\n\nNo covered work shall be deemed part of an effective technological\nmeasure under any applicable law fulfilling obligations under article\n11 of the WIPO copyright treaty adopted on 20 December 1996, or\nsimilar laws prohibiting or restricting circumvention of such\nmeasures.\n\nWhen you convey a covered work, you waive any legal power to forbid\ncircumvention of technological measures to the extent such circumvention\nis effected by exercising rights under this License with respect to\nthe covered work, and you disclaim any intention to limit operation or\nmodification of the work as a means of enforcing, against the work's\nusers, your or third parties' legal rights to forbid circumvention of\ntechnological measures.\n\n4. Conveying Verbatim Copies.\n\nYou may convey verbatim copies of the Program's source code as you\nreceive it, in any medium, provided that you conspicuously and\nappropriately publish on each copy an appropriate copyright notice;\nkeep intact all notices stating that this License and any\nnon-permissive terms added in accord with section 7 apply to the code;\nkeep intact all notices of the absence of any warranty; and give all\nrecipients a copy of this License along with the Program.\n\nYou may charge any price or no price for each copy that you convey,\nand you may offer support or warranty protection for a fee.\n\n5. Conveying Modified Source Versions.\n\nYou may convey a work based on the Program, or the modifications to\nproduce it from the Program, in the form of source code under the\nterms of section 4, provided that you also meet all of these conditions:\n\n    a) The work must carry prominent notices stating that you modified\n    it, and giving a relevant date.\n\n    b) The work must carry prominent notices stating that it is\n    released under this License and any conditions added under section\n    7.  This requirement modifies the requirement in section 4 to\n    \"keep intact all notices\".\n\n    c) You must license the entire work, as a whole, under this\n    License to anyone who comes into possession of a copy.  This\n    License will therefore apply, along with any applicable section 7\n    additional terms, to the whole of the work, and all its parts,\n    regardless of how they are packaged.  This License gives no\n    permission to license the work in any other way, but it does not\n    invalidate such permission if you have separately received it.\n\n    d) If the work has interactive user interfaces, each must display\n    Appropriate Legal Notices; however, if the Program has interactive\n    interfaces that do not display Appropriate Legal Notices, your\n    work need not make them do so.\n\nA compilation of a covered work with other separate and independent\nworks, which are not by their nature extensions of the covered work,\nand which are not combined with it such as to form a larger program,\nin or on a volume of a storage or distribution medium, is called an\n\"aggregate\" if the compilation and its resulting copyright are not\nused to limit the access or legal rights of the compilation's users\nbeyond what the individual works permit. Inclusion of a covered work\nin an aggregate does not cause this License to apply to the other\nparts of the aggregate.\n\n6. Conveying Non-Source Forms.\n\nYou may convey a covered work in object code form under the terms\nof sections 4 and 5, provided that you also convey the\nmachine-readable Corresponding Source under the terms of this License,\nin one of these ways:\n\n    a) Convey the object code in, or embodied in, a physical product\n    (including a physical distribution medium), accompanied by the\n    Corresponding Source fixed on a durable physical medium\n    customarily used for software interchange.\n\n    b) Convey the object code in, or embodied in, a physical product\n    (including a physical distribution medium), accompanied by a\n    written offer, valid for at least three years and valid for as\n    long as you offer spare parts or customer support for that product\n    model, to give anyone who possesses the object code either (1) a\n    copy of the Corresponding Source for all the software in the\n    product that is covered by this License, on a durable physical\n    medium customarily used for software interchange, for a price no\n    more than your reasonable cost of physically performing this\n    conveying of source, or (2) access to copy the\n    Corresponding Source from a network server at no charge.\n\n    c) Convey individual copies of the object code with a copy of the\n    written offer to provide the Corresponding Source.  This\n    alternative is allowed only occasionally and noncommercially, and\n    only if you received the object code with such an offer, in accord\n    with subsection 6b.\n\n    d) Convey the object code by offering access from a designated\n    place (gratis or for a charge), and offer equivalent access to the\n    Corresponding Source in the same way through the same place at no\n    further charge.  You need not require recipients to copy the\n    Corresponding Source along with the object code.  If the place to\n    copy the object code is a network server, the Corresponding Source\n    may be on a different server (operated by you or a third party)\n    that supports equivalent copying facilities, provided you maintain\n    clear directions next to the object code saying where to find the\n    Corresponding Source.  Regardless of what server hosts the\n    Corresponding Source, you remain obligated to ensure that it is\n    available for as long as needed to satisfy these requirements.\n\n    e) Convey the object code using peer-to-peer transmission, provided\n    you inform other peers where the object code and Corresponding\n    Source of the work are being offered to the general public at no\n    charge under subsection 6d.\n\nA separable portion of the object code, whose source code is excluded\nfrom the Corresponding Source as a System Library, need not be\nincluded in conveying the object code work.\n\nA \"User Product\" is either (1) a \"consumer product\", which means any\ntangible personal property which is normally used for personal, family,\nor household purposes, or (2) anything designed or sold for incorporation\ninto a dwelling. In determining whether a product is a consumer product,\ndoubtful cases shall be resolved in favor of coverage. For a particular\nproduct received by a particular user, \"normally used\" refers to a\ntypical or common use of that class of product, regardless of the status\nof the particular user or of the way in which the particular user\nactually uses, or expects or is expected to use, the product. A product\nis a consumer product regardless of whether the product has substantial\ncommercial, industrial or non-consumer uses, unless such uses represent\nthe only significant mode of use of the product.\n\n\"Installation Information\" for a User Product means any methods,\nprocedures, authorization keys, or other information required to install\nand execute modified versions of a covered work in that User Product from\na modified version of its Corresponding Source. The information must\nsuffice to ensure that the continued functioning of the modified object\ncode is in no case prevented or interfered with solely because\nmodification has been made.\n\nIf you convey an object code work under this section in, or with, or\nspecifically for use in, a User Product, and the conveying occurs as\npart of a transaction in which the right of possession and use of the\nUser Product is transferred to the recipient in perpetuity or for a\nfixed term (regardless of how the transaction is characterized), the\nCorresponding Source conveyed under this section must be accompanied\nby the Installation Information. But this requirement does not apply\nif neither you nor any third party retains the ability to install\nmodified object code on the User Product (for example, the work has\nbeen installed in ROM).\n\nThe requirement to provide Installation Information does not include a\nrequirement to continue to provide support service, warranty, or updates\nfor a work that has been modified or installed by the recipient, or for\nthe User Product in which it has been modified or installed. Access to a\nnetwork may be denied when the modification itself materially and\nadversely affects the operation of the network or violates the rules and\nprotocols for communication across the network.\n\nCorresponding Source conveyed, and Installation Information provided,\nin accord with this section must be in a format that is publicly\ndocumented (and with an implementation available to the public in\nsource code form), and must require no special password or key for\nunpacking, reading or copying.\n\n7. Additional Terms.\n\n\"Additional permissions\" are terms that supplement the terms of this\nLicense by making exceptions from one or more of its conditions.\nAdditional permissions that are applicable to the entire Program shall\nbe treated as though they were included in this License, to the extent\nthat they are valid under applicable law. If additional permissions\napply only to part of the Program, that part may be used separately\nunder those permissions, but the entire Program remains governed by\nthis License without regard to the additional permissions.\n\nWhen you convey a copy of a covered work, you may at your option\nremove any additional permissions from that copy, or from any part of\nit.  (Additional permissions may be written to require their own\nremoval in certain cases when you modify the work.)  You may place\nadditional permissions on material, added by you to a covered work,\nfor which you have or can give appropriate copyright permission.\n\nNotwithstanding any other provision of this License, for material you\nadd to a covered work, you may (if authorized by the copyright holders of\nthat material) supplement the terms of this License with terms:\n\n    a) Disclaiming warranty or limiting liability differently from the\n    terms of sections 15 and 16 of this License; or\n\n    b) Requiring preservation of specified reasonable legal notices or\n    author attributions in that material or in the Appropriate Legal\n    Notices displayed by works containing it; or\n\n    c) Prohibiting misrepresentation of the origin of that material, or\n    requiring that modified versions of such material be marked in\n    reasonable ways as different from the original version; or\n\n    d) Limiting the use for publicity purposes of names of licensors or\n    authors of the material; or\n\n    e) Declining to grant rights under trademark law for use of some\n    trade names, trademarks, or service marks; or\n\n    f) Requiring indemnification of licensors and authors of that\n    material by anyone who conveys the material (or modified versions of\n    it) with contractual assumptions of liability to the recipient, for\n    any liability that these contractual assumptions directly impose on\n    those licensors and authors.\n\nAll other non-permissive additional terms are considered \"further\nrestrictions\" within the meaning of section 10. If the Program as you\nreceived it, or any part of it, contains a notice stating that it is\ngoverned by this License along with a term that is a further\nrestriction, you may remove that term. If a license document contains\na further restriction but permits relicensing or conveying under this\nLicense, you may add to a covered work material governed by the terms\nof that license document, provided that the further restriction does\nnot survive such relicensing or conveying.\n\nIf you add terms to a covered work in accord with this section, you\nmust place, in the relevant source files, a statement of the\nadditional terms that apply to those files, or a notice indicating\nwhere to find the applicable terms.\n\nAdditional terms, permissive or non-permissive, may be stated in the\nform of a separately written license, or stated as exceptions;\nthe above requirements apply either way.\n\n8. Termination.\n\nYou may not propagate or modify a covered work except as expressly\nprovided under this License. Any attempt otherwise to propagate or\nmodify it is void, and will automatically terminate your rights under\nthis License (including any patent licenses granted under the third\nparagraph of section 11).\n\nHowever, if you cease all violation of this License, then your\nlicense from a particular copyright holder is reinstated (a)\nprovisionally, unless and until the copyright holder explicitly and\nfinally terminates your license, and (b) permanently, if the copyright\nholder fails to notify you of the violation by some reasonable means\nprior to 60 days after the cessation.\n\nMoreover, your license from a particular copyright holder is\nreinstated permanently if the copyright holder notifies you of the\nviolation by some reasonable means, this is the first time you have\nreceived notice of violation of this License (for any work) from that\ncopyright holder, and you cure the violation prior to 30 days after\nyour receipt of the notice.\n\nTermination of your rights under this section does not terminate the\nlicenses of parties who have received copies or rights from you under\nthis License. If your rights have been terminated and not permanently\nreinstated, you do not qualify to receive new licenses for the same\nmaterial under section 10.\n\n9. Acceptance Not Required for Having Copies.\n\nYou are not required to accept this License in order to receive or\nrun a copy of the Program. Ancillary propagation of a covered work\noccurring solely as a consequence of using peer-to-peer transmission\nto receive a copy likewise does not require acceptance. However,\nnothing other than this License grants you permission to propagate or\nmodify any covered work. These actions infringe copyright if you do\nnot accept this License. Therefore, by modifying or propagating a\ncovered work, you indicate your acceptance of this License to do so.\n\n10. Automatic Licensing of Downstream Recipients.\n\nEach time you convey a covered work, the recipient automatically\nreceives a license from the original licensors, to run, modify and\npropagate that work, subject to this License. You are not responsible\nfor enforcing compliance by third parties with this License.\n\nAn \"entity transaction\" is a transaction transferring control of an\norganization, or substantially all assets of one, or subdividing an\norganization, or merging organizations. If propagation of a covered\nwork results from an entity transaction, each party to that\ntransaction who receives a copy of the work also receives whatever\nlicenses to the work the party's predecessor in interest had or could\ngive under the previous paragraph, plus a right to possession of the\nCorresponding Source of the work from the predecessor in interest, if\nthe predecessor has it or can get it with reasonable efforts.\n\nYou may not impose any further restrictions on the exercise of the\nrights granted or affirmed under this License. For example, you may\nnot impose a license fee, royalty, or other charge for exercise of\nrights granted under this License, and you may not initiate litigation\n(including a cross-claim or counterclaim in a lawsuit) alleging that\nany patent claim is infringed by making, using, selling, offering for\nsale, or importing the Program or any portion of it.\n\n11. Patents.\n\nA \"contributor\" is a copyright holder who authorizes use under this\nLicense of the Program or a work on which the Program is based. The\nwork thus licensed is called the contributor's \"contributor version\".\n\nA contributor's \"essential patent claims\" are all patent claims\nowned or controlled by the contributor, whether already acquired or\nhereafter acquired, that would be infringed by some manner, permitted\nby this License, of making, using, or selling its contributor version,\nbut do not include claims that would be infringed only as a\nconsequence of further modification of the contributor version. For\npurposes of this definition, \"control\" includes the right to grant\npatent sublicenses in a manner consistent with the requirements of\nthis License.\n\nEach contributor grants you a non-exclusive, worldwide, royalty-free\npatent license under the contributor's essential patent claims, to\nmake, use, sell, offer for sale, import and otherwise run, modify and\npropagate the contents of its contributor version.\n\nIn the following three paragraphs, a \"patent license\" is any express\nagreement or commitment, however denominated, not to enforce a patent\n(such as an express permission to practice a patent or covenant not to\nsue for patent infringement). To \"grant\" such a patent license to a\nparty means to make such an agreement or commitment not to enforce a\npatent against the party.\n\nIf you convey a covered work, knowingly relying on a patent license,\nand the Corresponding Source of the work is not available for anyone\nto copy, free of charge and under the terms of this License, through a\npublicly available network server or other readily accessible means,\nthen you must either (1) cause the Corresponding Source to be so\navailable, or (2) arrange to deprive yourself of the benefit of the\npatent license for this particular work, or (3) arrange, in a manner\nconsistent with the requirements of this License, to extend the patent\nlicense to downstream recipients.  \"Knowingly relying\" means you have\nactual knowledge that, but for the patent license, your conveying the\ncovered work in a country, or your recipient's use of the covered work\nin a country, would infringe one or more identifiable patents in that\ncountry that you have reason to believe are valid.\n\nIf, pursuant to or in connection with a single transaction or\narrangement, you convey, or propagate by procuring conveyance of, a\ncovered work, and grant a patent license to some of the parties\nreceiving the covered work authorizing them to use, propagate, modify\nor convey a specific copy of the covered work, then the patent license\nyou grant is automatically extended to all recipients of the covered\nwork and works based on it.\n\nA patent license is \"discriminatory\" if it does not include within\nthe scope of its coverage, prohibits the exercise of, or is\nconditioned on the non-exercise of one or more of the rights that are\nspecifically granted under this License. You may not convey a covered\nwork if you are a party to an arrangement with a third party that is\nin the business of distributing software, under which you make payment\nto the third party based on the extent of your activity of conveying\nthe work, and under which the third party grants, to any of the\nparties who would receive the covered work from you, a discriminatory\npatent license (a) in connection with copies of the covered work\nconveyed by you (or copies made from those copies), or (b) primarily\nfor and in connection with specific products or compilations that\ncontain the covered work, unless you entered into that arrangement,\nor that patent license was granted, prior to 28 March 2007.\n\nNothing in this License shall be construed as excluding or limiting\nany implied license or other defenses to infringement that may\notherwise be available to you under applicable patent law.\n\n12. No Surrender of Others' Freedom.\n\nIf conditions are imposed on you (whether by court order, agreement or\notherwise) that contradict the conditions of this License, they do not\nexcuse you from the conditions of this License. If you cannot convey a\ncovered work so as to satisfy simultaneously your obligations under this\nLicense and any other pertinent obligations, then as a consequence you may\nnot convey it at all. For example, if you agree to terms that obligate you\nto collect a royalty for further conveying from those to whom you convey\nthe Program, the only way you could satisfy both those terms and this\nLicense would be to refrain entirely from conveying the Program.\n\n13. Use with the GNU Affero General Public License.\n\nNotwithstanding any other provision of this License, you have\npermission to link or combine any covered work with a work licensed\nunder version 3 of the GNU Affero General Public License into a single\ncombined work, and to convey the resulting work. The terms of this\nLicense will continue to apply to the part which is the covered work,\nbut the special requirements of the GNU Affero General Public License,\nsection 13, concerning interaction through a network will apply to the\ncombination as such.\n\n14. Revised Versions of this License.\n\nThe Free Software Foundation may publish revised and/or new versions of\nthe GNU General Public License from time to time. Such new versions will\nbe similar in spirit to the present version, but may differ in detail to\naddress new problems or concerns.\n\nEach version is given a distinguishing version number. If the\nProgram specifies that a certain numbered version of the GNU General\nPublic License \"or any later version\" applies to it, you have the\noption of following the terms and conditions either of that numbered\nversion or of any later version published by the Free Software\nFoundation. If the Program does not specify a version number of the\nGNU General Public License, you may choose any version ever published\nby the Free Software Foundation.\n\nIf the Program specifies that a proxy can decide which future\nversions of the GNU General Public License can be used, that proxy's\npublic statement of acceptance of a version permanently authorizes you\nto choose that version for the Program.\n\nLater license versions may give you additional or different\npermissions. However, no additional obligations are imposed on any\nauthor or copyright holder as a result of your choosing to follow a\nlater version.\n\n15. Disclaimer of Warranty.\n\nTHERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY\nAPPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT\nHOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM \"AS IS\" WITHOUT WARRANTY\nOF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,\nTHE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\nPURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM\nIS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF\nALL NECESSARY SERVICING, REPAIR OR CORRECTION.\n\n16. Limitation of Liability.\n\nIN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING\nWILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS\nTHE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY\nGENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE\nUSE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF\nDATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD\nPARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),\nEVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF\nSUCH DAMAGES.\n\n17. Interpretation of Sections 15 and 16.\n\nIf the disclaimer of warranty and limitation of liability provided\nabove cannot be given local legal effect according to their terms,\nreviewing courts shall apply local law that most closely approximates\nan absolute waiver of all civil liability in connection with the\nProgram, unless a warranty or assumption of liability accompanies a\ncopy of the Program in return for a fee.\n\n    END OF TERMS AND CONDITIONS\n\n    How to Apply These Terms to Your New Programs\n\nIf you develop a new program, and you want it to be of the greatest\npossible use to the public, the best way to achieve this is to make it\nfree software which everyone can redistribute and change under these terms.\n\nTo do so, attach the following notices to the program. It is safest\nto attach them to the start of each source file to most effectively\nstate the exclusion of warranty; and each file should have at least\nthe \"copyright\" line and a pointer to where the full notice is found.\n\n    <one line to give the program's name and a brief idea of what it does.>\n    Copyright (C) <year>  <name of author>\n\n    This program is free software: you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation, either version 3 of the License, or\n    (at your option) any later version.\n\n    This program is distributed in the hope that it will be useful,\n    but WITHOUT ANY WARRANTY; without even the implied warranty of\n    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n    GNU General Public License for more details.\n\n    You should have received a copy of the GNU General Public License\n    along with this program.  If not, see <https://www.gnu.org/licenses/>.\n\nAlso add information on how to contact you by electronic and paper mail.\n\nIf the program does terminal interaction, make it output a short\nnotice like this when it starts in an interactive mode:\n\n    Legado  Copyright (C) 2022  gedoor\n    This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.\n    This is free software, and you are welcome to redistribute it\n    under certain conditions; type `show c' for details.\n\nThe hypothetical commands `show w' and `show c' should show the appropriate\nparts of the General Public License. Of course, your program's commands\nmight be different; for a GUI interface, you would use an \"about box\".\n\nYou should also get your employer (if you work as a programmer) or school,\nif any, to sign a \"copyright disclaimer\" for the program, if necessary.\nFor more information on this, and how to apply and follow the GNU GPL, see\n<https://www.gnu.org/licenses/>.\n\nThe GNU General Public License does not permit incorporating your program\ninto proprietary programs. If your program is a subroutine library, you\nmay consider it more useful to permit linking proprietary applications with\nthe library. If this is what you want to do, use the GNU Lesser General\nPublic License instead of this License. But first, please read\n<https://www.gnu.org/licenses/why-not-lgpl.html>."
  },
  {
    "path": "app/src/main/assets/cronet.json",
    "content": "{\"x86\":\"2288814a4d9d4ee24154f52abf227fa0\",\"armeabi-v7a\":\"ac8190c5e795d1a754b7bf2d477119c5\",\"x86_64\":\"116f2d0f8e6363caacbd5c38bfc45b9f\",\"arm64-v8a\":\"61a96a20241e56ac7ab107cc3e4c3fda\",\"version\":\"128.0.6613.40\"}"
  },
  {
    "path": "app/src/main/assets/defaultData/bookSources.json",
    "content": "[\n  {\n    \"bookSourceComment\": \"\",\n    \"bookSourceGroup\": \"听书\",\n    \"bookSourceName\": \"消消乐听书\",\n    \"bookSourceType\": 1,\n    \"bookSourceUrl\": \"https://www.kaixin7days.com\",\n    \"customOrder\": 0,\n    \"enabled\": true,\n    \"enabledExplore\": true,\n    \"exploreUrl\": \"@js:var header = JSON.parsesource.getLoginHeader()\\nvar json = ''\\nvar j = null\\nif (header != null) {\\n    json = java.connect('https://www.kaixin7days.com/book-service/bookMgt/getBookCategroy,{\\\"method\\\":\\\"POST\\\",\\\"body\\\":{}}', header).body()\\n    j = JSON.parse(json)\\n}\\nif (j == null || j.statusCode != 200) {\\n    json = java.connect('https://www.kaixin7days.com/visitorLogin,{\\\"method\\\":\\\"POST\\\", \\\"body\\\":{} }').body()\\n    j = JSON.parse(json)\\n    var accessToken = {\\n        Authorization: 'Bearer ' + j.content.accessToken\\n    }\\n    header = JSON.stringify(accessToken)\\n    source.putLoginHeader(header)\\n    json = java.connect('https://www.kaixin7days.com/book-service/bookMgt/getBookCategroy,{\\\"method\\\":\\\"POST\\\",\\\"body\\\":{} }', header).body()\\n    j = JSON.parse(json)\\n}\\nvar fls = j.content\\nvar fx = []\\nfor (var i = 0; i < fls.length; i++) {\\n    fx.push({\\n        title: fls[i].categoryName,\\n        url: '/book-service/bookMgt/getAllBookByCategroyId,{\\\"method\\\":\\\"POST\\\",\\\"body\\\":{\\\"categoryIds\\\": \\\"' + fls[i].associationCategoryIDs + '\\\",\\\"pageNum\\\": {{page}},\\\"pageSize\\\": 100}}'\\n    })\\n}\\nJSON.stringify(fx)\",\n    \"searchUrl\": \"https://www.kaixin7days.com/book-service/bookMgt/findBookName,{\\\"method\\\":\\\"POST\\\",\\\"body\\\":{\\\"title\\\": \\\"searchKey\\\",\\\"pageNum\\\": {{searchPage}},\\\"pageSize\\\": 100}}\",\n    \"lastUpdateTime\": 1630656684531,\n    \"loginCheckJs\": \"var strRes = result\\nvar c = JSON.parse(result.body())\\nif (c.statusCode == 301) {\\n    var loginInfo = source.getLoginInfo()\\n    var dl = null\\n    if (loginInfo) {\\n        dl = java.connect('https://www.kaixin7days.com/login,{\\\"method\\\":\\\"POST\\\",\\\"body\\\":' + loginInfo + '}').body()\\n    } else {\\n        dl = java.connect('https://www.kaixin7days.com/visitorLogin,{\\\"method\\\":\\\"POST\\\",\\\"body\\\":{}}').body()\\n    }\\n    c = JSON.parse(dl)\\n    var accessToken = {\\n        Authorization: \\\"Bearer \\\" + c.content.accessToken\\n    }\\n    var header = JSON.stringify(accessToken)\\n    source.putLoginHeader(header)\\n    strRes = java.connect(url, header)\\n}\\nstrRes\",\n    \"loginUi\": \"[{\\\"name\\\": \\\"telephone\\\",\\\"type\\\": \\\"text\\\"},{\\\"name\\\": \\\"password\\\",\\\"type\\\": \\\"password\\\"},{\\\"name\\\": \\\"注册\\\",\\\"type\\\": \\\"button\\\",\\\"action\\\": \\\"http://www.yooike.com/xiaoshuo/#/register?title=%E6%B3%A8%E5%86%8C\\\"}]\",\n    \"loginUrl\": \"var loginInfo = source.getLoginInfo()\\nvar json = java.connect('https://www.kaixin7days.com/login,{\\\"method\\\":\\\"POST\\\",\\\"body\\\":' + loginInfo + '}').body()\\nvar loginRes = JSON.parse(json)\\nvar header = null\\nif (loginRes.statusCode == 200) {\\n    var accessToken = {\\n        Authorization: \\\"Bearer \\\" + loginRes.content.accessToken\\n    }\\n    header = JSON.stringify(accessToken)\\n    source.putLoginHeader(header)\\n}\\nheader\",\n    \"respondTime\": 180000,\n    \"ruleBookInfo\": {},\n    \"ruleContent\": {\n      \"content\": \"\",\n      \"payAction\": \"var header = JSON.parse(source.getLoginHeader()); var bookId = book.getVariableMap().get('bookId');var chapterId = java.get('chapterId');\\n'http://www.shuidi.online/?name=' + book.getName() + '&type=2&cover=' + book.getCoverUrl() + '&chapterId=' + chapterId + '&chapter=203&allNumber=' + book.getTotalChapterNum() + '&bookId=' + bookId + '&chapterIds=' + chapterId + '&number=' + chapter.getIndex() + '&accessToken=' + header.Authorization.substring(7) + '#/pay'\"\n    },\n    \"ruleExplore\": {\n      \"author\": \"$.author\",\n      \"bookList\": \"$.content.content\",\n      \"bookUrl\": \"$.id@js:java.put('bookId', result);'https://www.kaixin7days.com/book-service/bookMgt/getAllChapterByBookId,{ \\\"method\\\": \\\"POST\\\",\\\"body\\\": {\\\"bookId\\\": \\\"'+result+'\\\",\\\"pageNum\\\": \\\"1\\\",\\\"pageSize\\\": \\\"10000\\\"} }'\",\n      \"coverUrl\": \"$.cover@js:var cover = JSON.parse(result);'https://www.shuidi.online/fileMgt/getPicture?filePath='+cover.storeFilePath\",\n      \"intro\": \"$.desc\",\n      \"lastChapter\": \"$.newestChapter\",\n      \"name\": \"$.title\"\n    },\n    \"ruleSearch\": {\n      \"author\": \"$.author\",\n      \"bookList\": \"$.content.content\",\n      \"bookUrl\": \"$.id@js:java.put('bookId', result);'https://www.kaixin7days.com/book-service/bookMgt/getAllChapterByBookId,{ \\\"method\\\": \\\"POST\\\",\\\"body\\\": {\\\"bookId\\\": \\\"'+result+'\\\",\\\"pageNum\\\": \\\"1\\\",\\\"pageSize\\\": \\\"10000\\\"} }'\",\n      \"coverUrl\": \"$.cover@js:var cover = JSON.parse(result);'https://www.shuidi.online/fileMgt/getPicture?filePath='+cover.storeFilePath\",\n      \"intro\": \"$.desc\",\n      \"lastChapter\": \"$.newestChapter\",\n      \"name\": \"$.title\"\n    },\n    \"ruleToc\": {\n      \"chapterList\": \"$.content.content\",\n      \"chapterName\": \"$.chapterTitle\",\n      \"chapterUrl\": \"$.id@js:java.put('chapterId', result);'https://www.shuidi.online/fileMgt/getAudioByChapterId?bookId=' + java.get('bookId') + '&chapterId=' + result + \\\"&pageNum=1&pageSize=50&keyId={{var header = JSON.parse(source.getLoginHeader());var keyId = '1632746188011002';var ks = java.md5Encode(keyId + java.get('chapterId') + header.Authorization);keyId + '&keySecret=' + ks}\\\" + '}'\"\n    },\n    \"weight\": 0\n  }\n]"
  },
  {
    "path": "app/src/main/assets/defaultData/coverRule.json",
    "content": "{\n  \"enable\": false,\n  \"searchUrl\": \"\",\n  \"coverRule\": \"\"\n}"
  },
  {
    "path": "app/src/main/assets/defaultData/dictRules.json",
    "content": "[\n  {\n    \"name\": \"百度汉语\",\n    \"urlRule\": \"https://dict.baidu.com/s?wd={{key}}\",\n    \"showRule\": \"@js:var jsoup = org.jsoup.Jsoup.parse(result)\\njsoup.select(\\\"script,#search-bar,#right-panel,#copyright\\\").remove()\\njsoup.select(\\\"#poem-list-items\\\").html()\",\n    \"enabled\": true,\n    \"sortNumber\": 2\n  },\n  {\n    \"name\": \"海词英文\",\n    \"urlRule\": \"https://apii.dict.cn/mini.php?q={{key}}\",\n    \"showRule\": \"tag.body@all\",\n    \"enabled\": true,\n    \"sortNumber\": 1\n  },\n  {\n  \"name\": \"海词中文\",\n  \"urlRule\": \"https://hanyu.dict.cn/{{key}}\",\n  \"showRule\": \"@js:var jsoup = org.jsoup.Jsoup.parse(result)\\njsoup.select(\\\"script,#header,#footer,#page-share,.mslide,.title,#dictHcBtn,#dictHcBtnTop,#dictHc,#dictHc,#dictHcSettingArea,#dictHcClosetip\\\").remove()\\njsoup.select(\\\"#cy\\\").html()\",\n  \"enabled\": true,\n  \"sortNumber\": 0\n  }\n]\n"
  },
  {
    "path": "app/src/main/assets/defaultData/directLinkUpload.json",
    "content": "[\n  {\n    \"uploadUrl\": \"https://sy.nyasama.net/shuyuan,{\\\"method\\\":\\\"POST\\\",\\\"body\\\": {\\\"file\\\": \\\"fileRequest\\\"},\\\"type\\\": \\\"multipart/form-data\\\"}\",\n    \"downloadUrlRule\": \"$.data@js:if (result == '') \\n '' \\n else \\n 'https://shuyuan.nyasama.net/shuyuan/' + result\",\n    \"summary\": \"喵公子网盘①(有效期30天·香港G口)\",\n    \"compress\": false\n  },\n  {\n    \"uploadUrl\": \"https://sy.mgz6.com/shuyuan,{\\\"method\\\":\\\"POST\\\",\\\"body\\\": {\\\"file\\\": \\\"fileRequest\\\"},\\\"type\\\": \\\"multipart/form-data\\\"}\",\n    \"downloadUrlRule\": \"$.data@js:if (result == '') \\n '' \\n else \\n 'https://shuyuan.mgz6.com/shuyuan/' + result\",\n    \"summary\": \"喵公子网盘②(有效期7天)\",\n    \"compress\": false\n  },\n  {\n    \"uploadUrl\": \"http://v2.jt12.eu/up-v2.php,{\\\"method\\\":\\\"POST\\\",\\\"body\\\": {\\\"file\\\": \\\"fileRequest\\\"},\\\"type\\\": \\\"multipart/form-data\\\"}\",\n    \"downloadUrlRule\": \"$.msg\",\n    \"summary\": \"橘涂书源网盘2.0 Beta(永久有效)\",\n    \"compress\": false\n  }\n]"
  },
  {
    "path": "app/src/main/assets/defaultData/httpTTS.json",
    "content": "[\n  {\n    \"id\": -100,\n    \"name\": \"1.百度\",\n    \"url\": \"http://tts.baidu.com/text2audio,{\\n    \\\"method\\\": \\\"POST\\\",\\n    \\\"body\\\": \\\"tex={{java.encodeURI(java.encodeURI(speakText))}}&spd={{(speakSpeed + 5) / 10 + 4}}&per=3&cuid=baidu_speech_demo&idx=1&cod=2&lan=zh&ctp=1&pdt=160&vol=5&aue=6&pit=5&_res_tag_=audio\\\"\\n}\",\n    \"contentType\": \"audio/wav\"\n  },\n  {\n    \"id\": -29,\n    \"name\": \"2.阿里云语音\",\n    \"url\": \"https://nls-gateway.cn-shanghai.aliyuncs.com/stream/v1/tts,{\\\"method\\\": \\\"POST\\\",\\\"body\\\": {\\\"appkey\\\":\\\"{{source.getLoginInfoMap().get('AppKey')}}\\\",\\\"text\\\":\\\"{{speakText}}\\\",\\\"format\\\":\\\"mp3\\\",\\\"volume\\\":100,\\\"speech_rate\\\":{{String((speakSpeed) * 20 - 400)}} }}\",\n    \"contentType\": \"audio/mpeg\",\n    \"loginUrl\": \"function login(){var loginInfo = source.getLoginInfoMap();\\nvar accessKeyId = loginInfo.get('AccessKeyId');\\nvar accessKeySecret = loginInfo.get('AccessKeySecret');\\nvar timestamp = java.timeFormatUTC(new Date().getTime(), \\\"yyyy-MM-dd'T'HH:mm:ss'Z'\\\", 0);\\nvar aly = new JavaImporter(Packages.javax.crypto.Mac, Packages.javax.crypto.spec.SecretKeySpec, Packages.javax.xml.bind.DatatypeConverter, Packages.java.net.URLEncoder, Packages.java.lang.String, Packages.android.util.Base64);\\nwith (aly) {\\n    function percentEncode(value) {\\n        return URLEncoder.encode(value, \\\"UTF-8\\\").replace(\\\"+\\\", \\\"%20\\\")\\n            .replace(\\\"*\\\", \\\"%2A\\\").replace(\\\"%7E\\\", \\\"~\\\")\\n    }\\n\\n    function sign(stringToSign, accessKeySecret) {\\n        var mac = Mac.getInstance('HmacSHA1');\\n        mac.init(new SecretKeySpec(String(accessKeySecret + '&').getBytes(\\\"UTF-8\\\"), \\\"HmacSHA1\\\"));\\n        var signData = mac.doFinal(String(stringToSign).getBytes(\\\"UTF-8\\\"));\\n        var signBase64 = Base64.encodeToString(signData, Base64.NO_WRAP);\\n        var signUrlEncode = percentEncode(signBase64);\\n        return signUrlEncode;\\n    }\\n}\\nvar query = 'AccessKeyId=' + accessKeyId + '&Action=CreateToken&Format=JSON&RegionId=cn-shanghai&SignatureMethod=HMAC-SHA1&SignatureNonce=' + java.randomUUID() + '&SignatureVersion=1.0&Timestamp=' + percentEncode(timestamp) + '&Version=2019-02-28';\\nvar signStr = sign('GET&' + percentEncode('/') + '&' + percentEncode(query), accessKeySecret);\\nvar queryStringWithSign = \\\"Signature=\\\" + signStr + \\\"&\\\" + query;\\nvar body = java.ajax('http://nls-meta.cn-shanghai.aliyuncs.com/?' + queryStringWithSign)\\nvar res = JSON.parse(body)\\nif (res.Message) {\\n    throw new Error(res.Message)\\n}\\nvar header = { \\\"X-NLS-Token\\\": res.Token.Id };\\nsource.putLoginHeader(JSON.stringify(header))}\",\n    \"loginUi\": [\n      {\n        \"name\": \"AppKey\",\n        \"type\": \"text\"\n      },\n      {\n        \"name\": \"AccessKeyId\",\n        \"type\": \"text\"\n      },\n      {\n        \"name\": \"AccessKeySecret\",\n        \"type\": \"text\"\n      }\n    ],\n    \"loginCheckJs\": \"var response = result;\\nif (response.headers().get(\\\"Content-Type\\\") != \\\"audio/mpeg\\\") {\\n    var body = JSON.parse(response.body().string())\\n    if (body.status == 40000001) {\\n        source.login()\\n        java.getHeaderMap().putAll(source.getHeaderMap(true))\\n        response = java.getResponse()\\n    } else {\\n        throw body.message\\n    }\\n}\\nresponse\"\n  }\n]\n"
  },
  {
    "path": "app/src/main/assets/defaultData/keyboardAssists.json",
    "content": "[\n  {\n    \"key\": \"@css:\",\n    \"value\": \"@css:\",\n    \"serialNo\": 0\n  },\n  {\n    \"key\": \"<js>\",\n    \"value\": \"<js></js>\",\n    \"serialNo\": 1\n  },\n  {\n    \"key\": \"{{}}\",\n    \"value\": \"{{}}\",\n    \"serialNo\": 2\n  },\n  {\n    \"key\": \"##\",\n    \"value\": \"##\",\n    \"serialNo\": 3\n  },\n  {\n    \"key\": \"&&\",\n    \"value\": \"&&\",\n    \"serialNo\": 4\n  },\n  {\n    \"key\": \"%%\",\n    \"value\": \"%%\",\n    \"serialNo\": 5\n  },\n  {\n    \"key\": \"||\",\n    \"value\": \"||\",\n    \"serialNo\": 6\n  },\n  {\n    \"key\": \"//\",\n    \"value\": \"//\",\n    \"serialNo\": 7\n  },\n  {\n    \"key\": \"\\\\\",\n    \"value\": \"\\\\\",\n    \"serialNo\": 8\n  },\n  {\n    \"key\": \"$.\",\n    \"value\": \"$.\",\n    \"serialNo\": 9\n  },\n  {\n    \"key\": \"@\",\n    \"value\": \"@\",\n    \"serialNo\": 10\n  },\n  {\n    \"key\": \":\",\n    \"value\": \":\",\n    \"serialNo\": 11\n  },\n  {\n    \"key\": \"class\",\n    \"value\": \"class\",\n    \"serialNo\": 12\n  },\n  {\n    \"key\": \"text\",\n    \"value\": \"text\",\n    \"serialNo\": 13\n  },\n  {\n    \"key\": \"href\",\n    \"value\": \"href\",\n    \"serialNo\": 14\n  },\n  {\n    \"key\": \"textNodes\",\n    \"value\": \"textNodes\",\n    \"serialNo\": 15\n  },\n  {\n    \"key\": \"ownText\",\n    \"value\": \"ownText\",\n    \"serialNo\": 16\n  },\n  {\n    \"key\": \"all\",\n    \"value\": \"all\",\n    \"serialNo\": 17\n  },\n  {\n    \"key\": \"html\",\n    \"value\": \"html\",\n    \"serialNo\": 18\n  },\n  {\n    \"key\": \"[\",\n    \"value\": \"[\",\n    \"serialNo\": 19\n  },\n  {\n    \"key\": \"]\",\n    \"value\": \"]\",\n    \"serialNo\": 20\n  },\n  {\n    \"key\": \"<\",\n    \"value\": \"<\",\n    \"serialNo\": 21\n  },\n  {\n    \"key\": \">\",\n    \"value\": \">\",\n    \"serialNo\": 22\n  },\n  {\n    \"key\": \"#\",\n    \"value\": \"#\",\n    \"serialNo\": 23\n  },\n  {\n    \"key\": \"!\",\n    \"value\": \"!\",\n    \"serialNo\": 24\n  },\n  {\n    \"key\": \".\",\n    \"value\": \".\",\n    \"serialNo\": 25\n  },\n  {\n    \"key\": \"+\",\n    \"value\": \"+\",\n    \"serialNo\": 26\n  },\n  {\n    \"key\": \"-\",\n    \"value\": \"-\",\n    \"serialNo\": 27\n  },\n  {\n    \"key\": \"*\",\n    \"value\": \"*\",\n    \"serialNo\": 28\n  },\n  {\n    \"key\": \"/\",\n    \"value\": \"/\",\n    \"serialNo\": 29\n  },\n  {\n    \"key\": \"=\",\n    \"value\": \"=\",\n    \"serialNo\": 30\n  },\n  {\n    \"key\": \"useWebView\",\n    \"value\": \",{\\\"webView\\\": true}\",\n    \"serialNo\": 31\n  }\n]"
  },
  {
    "path": "app/src/main/assets/defaultData/readConfig.json",
    "content": "[\n  {\n    \"bgStr\": \"#ffc0edc6\",\n    \"bgStrEInk\": \"#FFFFFF\",\n    \"bgStrNight\": \"#000000\",\n    \"bgType\": 0,\n    \"bgTypeEInk\": 0,\n    \"bgTypeNight\": 0,\n    \"darkStatusIcon\": true,\n    \"darkStatusIconEInk\": true,\n    \"darkStatusIconNight\": false,\n    \"footerMode\": 0,\n    \"footerPaddingBottom\": 10,\n    \"footerPaddingLeft\": 13,\n    \"footerPaddingRight\": 17,\n    \"footerPaddingTop\": 0,\n    \"headerMode\": 0,\n    \"headerPaddingBottom\": 0,\n    \"headerPaddingLeft\": 19,\n    \"headerPaddingRight\": 16,\n    \"headerPaddingTop\": 10,\n    \"letterSpacing\": 0,\n    \"lineSpacingExtra\": 10,\n    \"name\": \"微信读书\",\n    \"paddingBottom\": 4,\n    \"paddingLeft\": 22,\n    \"paddingRight\": 22,\n    \"paddingTop\": 5,\n    \"paragraphIndent\": \"　　\",\n    \"paragraphSpacing\": 6,\n    \"showFooterLine\": true,\n    \"showHeaderLine\": true,\n    \"textBold\": 0,\n    \"textColor\": \"#ff0b0b0b\",\n    \"textColorEInk\": \"#000000\",\n    \"textColorNight\": \"#ADADAD\",\n    \"textSize\": 24,\n    \"tipColor\": -10461088,\n    \"tipFooterLeft\": 7,\n    \"tipFooterMiddle\": 0,\n    \"tipFooterRight\": 6,\n    \"tipHeaderLeft\": 1,\n    \"tipHeaderMiddle\": 0,\n    \"tipHeaderRight\": 2,\n    \"titleBottomSpacing\": 0,\n    \"titleMode\": 0,\n    \"titleSize\": 4,\n    \"titleTopSpacing\": 0\n  },\n  {\n    \"name\": \"预设1\",\n    \"bgStr\": \"#FFFFFF\",\n    \"bgStrNight\": \"#000000\",\n    \"textColor\": \"#000000\",\n    \"textColorNight\": \"#FFFFFF\",\n    \"bgType\": 0,\n    \"bgTypeNight\": 0,\n    \"darkStatusIcon\": true,\n    \"darkStatusIconNight\": false\n  },\n  {\n    \"name\": \"预设2\",\n    \"bgStr\": \"#DDC090\",\n    \"bgStrNight\": \"#3C3F43\",\n    \"textColor\": \"#3E3422\",\n    \"textColorNight\": \"#DCDFE1\",\n    \"bgType\": 0,\n    \"bgTypeNight\": 0,\n    \"darkStatusIcon\": true,\n    \"darkStatusIconNight\": false\n  },\n  {\n    \"name\": \"预设3\",\n    \"bgStr\": \"#C2D8AA\",\n    \"bgStrNight\": \"#3C3F43\",\n    \"textColor\": \"#596C44\",\n    \"textColorNight\": \"#88C16F\",\n    \"bgType\": 0,\n    \"bgTypeNight\": 0,\n    \"darkStatusIcon\": false,\n    \"darkStatusIconNight\": false\n  },\n  {\n    \"name\": \"预设4\",\n    \"bgStr\": \"#DBB8E2\",\n    \"bgStrNight\": \"#3C3F43\",\n    \"textColor\": \"#68516C\",\n    \"textColorNight\": \"#F6AEAE\",\n    \"bgType\": 0,\n    \"bgTypeNight\": 0,\n    \"darkStatusIcon\": false,\n    \"darkStatusIconNight\": false\n  },\n  {\n    \"name\": \"预设5\",\n    \"bgStr\": \"#ABCEE0\",\n    \"bgStrNight\": \"#3C3F43\",\n    \"textColor\": \"#3D4C54\",\n    \"textColorNight\": \"#90BFF5\",\n    \"bgType\": 0,\n    \"bgTypeNight\": 0,\n    \"darkStatusIcon\": false,\n    \"darkStatusIconNight\": false\n  }\n]"
  },
  {
    "path": "app/src/main/assets/defaultData/rssSources.json",
    "content": "[\n  {\n    \"customOrder\": 2,\n    \"enableJs\": true,\n    \"enabled\": true,\n    \"singleUrl\": true,\n    \"sourceGroup\": \"legado\",\n    \"sourceIcon\": \"https://cdn.jsdelivr.net/gh/gedoor/legado@master/app/src/main/res/mipmap-hdpi/ic_launcher.png\",\n    \"sourceName\": \"使用说明\",\n    \"sourceUrl\": \"https://www.yuque.com/legado\"\n  },\n  {\n    \"customOrder\": 3,\n    \"enableJs\": true,\n    \"enabled\": true,\n    \"singleUrl\": true,\n    \"sourceGroup\": \"legado\",\n    \"sourceIcon\": \"http://mmbiz.qpic.cn/mmbiz_png/hpfMV8hEuL2eS6vnCxvTzoOiaCAibV6exBzJWq9xMic9xDg3YXAick87tsfafic0icRwkQ5ibV0bJ84JtSuxhPuEDVquA/0?wx_fmt=png\",\n    \"sourceName\": \"小说拾遗\",\n    \"sourceUrl\": \"snssdk1128://user/profile/562564899806367\"\n  },\n  {\n    \"customOrder\": 4,\n    \"enableJs\": true,\n    \"enabled\": true,\n    \"singleUrl\": true,\n    \"sourceGroup\": \"legado\",\n    \"sourceIcon\": \"https://cdn.jsdelivr.net/gh/mgz0227/meowcloud/icon.png\",\n    \"sourceName\": \"Meow云\",\n    \"sourceUrl\": \"https://pan.miaogongzi.net\"\n  },\n  {\n    \"customOrder\": 5,\n    \"enableJs\": true,\n    \"enabled\": true,\n    \"singleUrl\": true,\n    \"sourceGroup\": \"legado\",\n    \"sourceIcon\": \"https://cdn.jsdelivr.net/gh/gedoor/legado@master/app/src/main/res/mipmap-hdpi/ic_launcher.png\",\n    \"sourceName\": \"烏雲净化\",\n    \"sourceUrl\": \"https://www.lanzoux.com/b0bw8jwoh\"\n  }\n]"
  },
  {
    "path": "app/src/main/assets/defaultData/themeConfig.json",
    "content": "[\n  {\n    \"themeName\": \"默认\",\n    \"isNightTheme\": false,\n    \"primaryColor\": \"#795548\",\n    \"accentColor\": \"#E53935\",\n    \"backgroundColor\": \"#F5F5F5\",\n    \"bottomBackground\": \"#EEEEEE\"\n  },\n  {\n    \"themeName\": \"典雅蓝\",\n    \"isNightTheme\": false,\n    \"primaryColor\": \"#03A9F4\",\n    \"accentColor\": \"#AD1457\",\n    \"backgroundColor\": \"#F5F5F5\",\n    \"bottomBackground\": \"#EEEEEE\"\n  },\n  {\n    \"themeName\": \"黑白\",\n    \"isNightTheme\": true,\n    \"primaryColor\": \"#303030\",\n    \"accentColor\": \"#E0E0E0\",\n    \"backgroundColor\": \"#424242\",\n    \"bottomBackground\": \"#424242\"\n  },\n  {\n    \"themeName\": \"A屏黑\",\n    \"isNightTheme\": true,\n    \"primaryColor\": \"#000000\",\n    \"accentColor\": \"#FFFFFF\",\n    \"backgroundColor\": \"#000000\",\n    \"bottomBackground\": \"#000000\"\n  }\n]"
  },
  {
    "path": "app/src/main/assets/defaultData/txtTocRule.json",
    "content": "[\n  {\n    \"id\": -1,\n    \"enable\": true,\n    \"name\": \"目录(去空白)\",\n    \"rule\": \"(?<=[　\\\\s])(?:序章|楔子|正文(?!完|结)|终章|后记|尾声|番外|第\\\\s{0,4}[\\\\d〇零一二两三四五六七八九十百千万壹贰叁肆伍陆柒捌玖拾佰仟]+?\\\\s{0,4}(?:章|节(?!课)|卷|集(?![合和]))).{0,30}$\",\n    \"example\": \"第一章 假装第一章前面有空白但我不要\",\n    \"serialNumber\": 0\n  },\n  {\n    \"id\": -2,\n    \"enable\": true,\n    \"name\": \"目录\",\n    \"rule\": \"^[ 　\\\\t]{0,4}(?:序章|楔子|正文(?!完|结)|终章|后记|尾声|番外|第\\\\s{0,4}[\\\\d〇零一二两三四五六七八九十百千万壹贰叁肆伍陆柒捌玖拾佰仟]+?\\\\s{0,4}(?:章|节(?!课)|卷|集(?![合和])|部(?![分赛游])|篇(?!张))).{0,30}$\",\n    \"example\": \"第一章 标准的粤语就是这样\",\n    \"serialNumber\": 1\n  },\n  {\n    \"id\": -3,\n    \"enable\": false,\n    \"name\": \"目录(匹配简介)\",\n    \"rule\": \"(?<=[　\\\\s])(?:(?:内容|文章)?简介|文案|前言|序章|楔子|正文(?!完|结)|终章|后记|尾声|番外|第\\\\s{0,4}[\\\\d〇零一二两三四五六七八九十百千万壹贰叁肆伍陆柒捌玖拾佰仟]+?\\\\s{0,4}(?:章|节(?!课)|卷|集(?![合和])|部(?![分赛游])|回(?![合来事去])|场(?![和合比电是])|篇(?!张))).{0,30}$\",\n    \"example\": \"简介 老夫诸葛村夫\",\n    \"serialNumber\": 2\n  },\n  {\n    \"id\": -4,\n    \"enable\": false,\n    \"name\": \"目录(古典、轻小说备用)\",\n    \"rule\": \"^[ 　\\\\t]{0,4}(?:序章|楔子|正文(?!完|结)|终章|后记|尾声|番外|第\\\\s{0,4}[\\\\d〇零一二两三四五六七八九十百千万壹贰叁肆伍陆柒捌玖拾佰仟]+?\\\\s{0,4}(?:章|节(?!课)|卷|集(?![合和])|部(?![分赛游])|回(?![合来事去])|场(?![和合比电是])|话|篇(?!张))).{0,30}$\",\n    \"example\": \"第一章 比上面只多了回和话\",\n    \"serialNumber\": 3\n  },\n  {\n    \"id\": -5,\n    \"enable\": false,\n    \"name\": \"数字(纯数字标题)\",\n    \"rule\": \"(?<=[　\\\\s])\\\\d+\\\\.?[ 　\\\\t]{0,4}$\",\n    \"example\": \"12\",\n    \"serialNumber\": 4\n  },\n  {\n    \"id\": -6,\n    \"enable\": false,\n    \"name\": \"大写数字(纯数字标题)\",\n    \"rule\": \"(?<=[　\\\\s])[零一二两三四五六七八九十百千万壹贰叁肆伍陆柒捌玖拾佰仟]{1,12}[ 　\\\\t]{0,4}$\",\n    \"example\": \"一百七十\",\n    \"serialNumber\": 5\n  },\n  {\n    \"id\": -7,\n    \"enable\": false,\n    \"name\": \"数字混合(纯数字标题)\",\n    \"rule\": \"(?<=[　\\\\s])[零一二两三四五六七八九十百千万壹贰叁肆伍陆柒捌玖拾佰仟\\\\d]{1,12}[ 　\\\\t]{0,4}$\",\n    \"example\": \"12\\n一百七十\",\n    \"serialNumber\": 6\n  },\n  {\n    \"id\": -8,\n    \"enable\": true,\n    \"name\": \"数字 分隔符 标题名称\",\n    \"rule\": \"^[ 　\\\\t]{0,4}\\\\d{1,5}[:：,.， 、_—\\\\-].{1,30}$\",\n    \"example\": \"1、这个就是标题\",\n    \"serialNumber\": 7\n  },\n  {\n    \"id\": -9,\n    \"enable\": true,\n    \"name\": \"大写数字 分隔符 标题名称\",\n    \"rule\": \"^[ 　\\\\t]{0,4}(?:序章|楔子|正文(?!完|结)|终章|后记|尾声|番外|[零一二两三四五六七八九十百千万壹贰叁肆伍陆柒捌玖拾佰仟]{1,8}章?)[ 、_—\\\\-].{1,30}$\",\n    \"example\": \"一、只有前面的数字有差别\\n二十四章 我瞎编的标题\",\n    \"serialNumber\": 8\n  },\n  {\n    \"id\": -10,\n    \"enable\": false,\n    \"name\": \"数字混合 分隔符 标题名称\",\n    \"rule\": \"^[ 　\\\\t]{0,4}(?:序章|楔子|正文(?!完|结)|终章|后记|尾声|番外|[零一二两三四五六七八九十百千万壹贰叁肆伍陆柒捌玖拾佰仟]{1,8}章?[ 、_—\\\\-]|\\\\d{1,5}章?[:：,.， 、_—\\\\-]).{0,30}$\",\n    \"example\": \"1、人参公鸡\\n二百二十章 boy next door\",\n    \"serialNumber\": 9\n  },\n  {\n    \"id\": -11,\n    \"enable\": true,\n    \"name\": \"正文 标题/序号\",\n    \"rule\": \"^[ 　\\\\t]{0,4}正文[ 　]{1,4}.{0,20}$\",\n    \"example\": \"正文 我奶常山赵子龙\",\n    \"serialNumber\": 10\n  },\n  {\n    \"id\": -12,\n    \"enable\": true,\n    \"name\": \"Chapter/Section/Part/Episode 序号 标题\",\n    \"rule\": \"^[ 　\\\\t]{0,4}(?:[Cc]hapter|[Ss]ection|[Pp]art|ＰＡＲＴ|[Nn][oO][.、]|[Ee]pisode|(?:内容|文章)?简介|文案|前言|序章|楔子|正文(?!完|结)|终章|后记|尾声|番外)\\\\s{0,4}\\\\d{1,4}.{0,30}$\",\n    \"example\": \"Chapter 1 MyGrandmaIsNB\",\n    \"serialNumber\": 11\n  },\n  {\n    \"id\": -13,\n    \"enable\": false,\n    \"name\": \"Chapter(去简介)\",\n    \"rule\": \"^[ 　\\\\t]{0,4}(?:[Cc]hapter|[Ss]ection|[Pp]art|ＰＡＲＴ|[Nn][Oo]\\\\.|[Ee]pisode)\\\\s{0,4}\\\\d{1,4}.{0,30}$\",\n    \"example\": \"Chapter 1 MyGrandmaIsNB\",\n    \"serialNumber\": 12\n  },\n  {\n    \"id\": -14,\n    \"enable\": true,\n    \"name\": \"特殊符号 序号 标题\",\n    \"rule\": \"(?<=[\\\\s　])[【〔〖「『〈［\\\\[](?:第|[Cc]hapter)[\\\\d零一二两三四五六七八九十百千万壹贰叁肆伍陆柒捌玖拾佰仟]{1,10}[章节].{0,20}$\",\n    \"example\": \"【第一章 后面的符号可以没有\",\n    \"serialNumber\": 13\n  },\n  {\n    \"id\": -15,\n    \"enable\": false,\n    \"name\": \"特殊符号 标题(成对)\",\n    \"rule\": \"(?<=[\\\\s　]{0,4})(?:[\\\\[〈「『〖〔《（【\\\\(].{1,30}[\\\\)】）》〕〗』」〉\\\\]]?|(?:内容|文章)?简介|文案|前言|序章|楔子|正文(?!完|结)|终章|后记|尾声|番外)[ 　]{0,4}$\",\n    \"example\": \"『加个直角引号更专业』\\n(11)我奶常山赵子聋\",\n    \"serialNumber\": 14\n  },\n  {\n    \"id\": -16,\n    \"enable\": true,\n    \"name\": \"特殊符号 标题(单个)\",\n    \"rule\": \"(?<=[\\\\s　]{0,4})(?:[☆★✦✧].{1,30}|(?:内容|文章)?简介|文案|前言|序章|楔子|正文(?!完|结)|终章|后记|尾声|番外)[ 　]{0,4}$\",\n    \"example\": \"☆、晋江作者最喜欢的格式\",\n    \"serialNumber\": 15\n  },\n  {\n    \"id\": -17,\n    \"enable\": true,\n    \"name\": \"章/卷 序号 标题\",\n    \"rule\": \"^[ \\\\t　]{0,4}(?:(?:内容|文章)?简介|文案|前言|序章|楔子|正文(?!完|结)|终章|后记|尾声|番外|[卷章][\\\\d零一二两三四五六七八九十百千万壹贰叁肆伍陆柒捌玖拾佰仟]{1,8})[ 　]{0,4}.{0,30}$\",\n    \"example\": \"卷五 开源盛世\",\n    \"serialNumber\": 16\n  },\n  {\n    \"id\": -18,\n    \"enable\": false,\n    \"name\": \"顶格标题\",\n    \"rule\": \"^\\\\S.{1,20}$\",\n    \"example\": \"20字以内顶格写的都是标题\",\n    \"serialNumber\": 17\n  },\n  {\n    \"id\": -19,\n    \"enable\": false,\n    \"name\": \"双标题(前向)\",\n    \"rule\": \"(?m)(?<=[ \\\\t　]{0,4})第[\\\\d〇零一二两三四五六七八九十百千万壹贰叁肆伍陆柒捌玖拾佰仟]{1,8}章.{0,30}$(?=[\\\\s　]{0,8}第[\\\\d零一二两三四五六七八九十百千万壹贰叁肆伍陆柒捌玖拾佰仟]{1,8}章)\",\n    \"example\": \"第一章 真正的标题\\n第一章 这个不要\",\n    \"serialNumber\": 18\n  },\n  {\n    \"id\": -20,\n    \"enable\": false,\n    \"name\": \"双标题(后向)\",\n    \"rule\": \"(?m)(?<=[ \\\\t　]{0,4}第[\\\\d〇零一二两三四五六七八九十百千万壹贰叁肆伍陆柒捌玖拾佰仟]{1,8}章.{0,30}$[\\\\s　]{0,8})第[\\\\d零一二两三四五六七八九十百千万壹贰叁肆伍陆柒捌玖拾佰仟]{1,8}章.{0,30}$\",\n    \"example\": \"第一章 这个标题不要\\n第一章真正的标题\",\n    \"serialNumber\": 19\n  },\n  {\n    \"id\": -21,\n    \"enable\": true,\n    \"name\": \"书名 括号 序号\",\n    \"rule\": \"^[一-龥]{1,20}[ 　\\\\t]{0,4}[(（][\\\\d〇零一二两三四五六七八九十百千万壹贰叁肆伍陆柒捌玖拾佰仟]{1,8}[)）][ 　\\\\t]{0,4}$\",\n    \"example\": \"标题后面数字有括号(12)\",\n    \"serialNumber\": 20\n  },\n  {\n    \"id\": -22,\n    \"enable\": true,\n    \"name\": \"书名 序号\",\n    \"rule\": \"^[一-龥]{1,20}[ 　\\\\t]{0,4}[\\\\d〇零一二两三四五六七八九十百千万壹贰叁肆伍陆柒捌玖拾佰仟]{1,8}[ 　\\\\t]{0,4}$\",\n    \"example\": \"标题后面数字没有括号124\",\n    \"serialNumber\": 21\n  },\n  {\n    \"id\": -23,\n    \"enable\": false,\n    \"name\": \"特定字符 标题 特定符号\",\n    \"rule\": \"(?<=\\\\={3,6}).{1,40}?(?=\\\\=)\",\n    \"example\": \"===起这种标题干什么===\",\n    \"serialNumber\": 22\n  },\n  {\n    \"id\": -24,\n    \"enable\": true,\n    \"name\": \"字数分割 分节阅读\",\n    \"rule\": \"(?<=[ 　\\\\t]{0,4})(?:.{0,15}分[页节章段]阅读[-_ ]|第\\\\s{0,4}[\\\\d零一二两三四五六七八九十百千万]{1,6}\\\\s{0,4}[页节]).{0,30}$\",\n    \"example\": \"分节|分页|分段阅读\\n第一页\",\n    \"serialNumber\": 23\n  },\n  {\n    \"id\": -25,\n    \"enable\": false,\n    \"name\": \"通用规则\",\n    \"rule\": \"(?im)^.{0,6}(?:[引楔]子|正文(?!完|结)|[引序前]言|[序终]章|扉页|[上中下][部篇卷]|卷首语|后记|尾声|番外|={2,4}|第\\\\s{0,4}[\\\\d〇零一二两三四五六七八九十百千万壹贰叁肆伍陆柒捌玖拾佰仟]+?\\\\s{0,4}(?:章|节(?!课)|卷|页[、 　]|集(?![合和])|部(?![分是门落])|篇(?!张))).{0,40}$|^.{0,6}[\\\\d〇零一二两三四五六七八九十百千万壹贰叁肆伍陆柒捌玖拾佰仟a-z]{1,8}[、. 　].{0,20}$\",\n    \"example\": \"激进规则,适配更多非常用格式\",\n    \"serialNumber\": 24\n  },\n  {\n    \"id\": -100,\n    \"enable\": false,\n    \"name\": \"默认分章规则\",\n    \"rule\": \"\",\n    \"example\": \"兜底规则，请勿改动此内容\",\n    \"serialNumber\": 99\n  }\n]\n"
  },
  {
    "path": "app/src/main/assets/disclaimer.md",
    "content": "# 免责声明（Disclaimer）\n\n* 阅读是一款解析指定规则并获取内容的工具，为广大网络文学爱好者提供一种方便、快捷舒适的试读体验。\n* 当您搜索一本书的时，阅读会您所使用的规则将该书的书名以关键词的形式提交到各个第三方网络文学网站。\n各第三方网站返回的内容与阅读无关，阅读对其概不负责，亦不承担任何法律责任。\n任何通过使用阅读而链接到的第三方网页均系他人制作或提供，您可能从第三方网页上获得其他服务，阅读对其合法性概不负责，亦不承担任何法律责任。\n第三方搜索引擎结果根据您提交的书名自动搜索获得并提供试读，不代表阅读赞成或被搜索链接到的第三方网页上的内容或立场。\n您应该对使用搜索引擎的结果自行承担风险。\n* 阅读不做任何形式的保证：不保证第三方搜索引擎的搜索结果满足您的要求，不保证搜索服务不中断，不保证搜索结果的安全性、正确性、及时性、合法性。\n因网络状况、通讯线路、第三方网站等任何原因而导致您不能正常使用阅读，阅读不承担任何法律责任。\n阅读尊重并保护所有使用阅读用户的个人隐私权，您注册的用户名、电子邮件地址等个人资料，非经您亲自许可或根据相关法律、法规的强制性规定，阅读不会主动地泄露给第三方。\n* 阅读致力于最大程度地减少网络文学阅读者在自行搜寻过程中的无意义的时间浪费，通过专业搜索展示不同网站中网络文学的最新章节。\n阅读在为广大小说爱好者提供方便、快捷舒适的试读体验的同时，也使优秀网络文学得以迅速、更广泛的传播，从而达到了在一定程度促进网络文学充分繁荣发展之目的。\n阅读鼓励广大小说爱好者通过阅读发现优秀网络小说及其提供商，并建议阅读正版图书。\n任何单位或个人认为通过阅读搜索链接到的第三方网页内容可能涉嫌侵犯其信息网络传播权，应该及时向阅读提出书面权力通知，并提供身份证明、权属证明及详细侵权情况证明。\n阅读在收到上述法律文件后，将会依法尽快断开相关链接内容。"
  },
  {
    "path": "app/src/main/assets/epub/chapter.html",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!DOCTYPE html>\n<html xmlns=\"http://www.w3.org/1999/xhtml\">\n<head>\n    <title>Chapter</title>\n    <link href=\"../Styles/fonts.css\" type=\"text/css\" rel=\"stylesheet\"/>\n    <link href=\"../Styles/main.css\" type=\"text/css\" rel=\"stylesheet\"/>\n</head>\n<body>\n<h2 class=\"head\">{title}</h2>\n{content}\n</body>\n</html>\n"
  },
  {
    "path": "app/src/main/assets/epub/cover.html",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!DOCTYPE html>\n<html xmlns=\"http://www.w3.org/1999/xhtml\">\n<head>\n    <title>Cover</title>\n    <style type=\"text/css\">\n\t\t.pic {\n\t\t\tmargin: 50% 30% 0 30%;\n\t\t\tpadding: 2px 2px;\n\t\t\tborder: 1px solid #f5f5dc;\n\t\t\tbackground-color: rgba(250,250,250, 0);\n\t\t\tborder-radius: 1px;\n\t\t}\n    </style>\n</head>\n<body style=\"text-align: center;\">\n<div class=\"pic\"><img src=\"../Images/cover.jpg\" style=\"width: 100%; height: auto;\"/></div>\n<h1 style=\"margin-top: 5%; font-size: 110%;\">{name}</h1>\n<div class=\"author\" style=\"margin-top: 0;\"><b>{author}</b> <span style=\"font-size: smaller;\">/ 著</span></div>\n</body>\n</html>"
  },
  {
    "path": "app/src/main/assets/epub/fonts.css",
    "content": "@charset \"utf-8\";\n/*---常用---*/\n\n@font-face {\n    font-family: \"zw\";\n    src:\n\tlocal(\"宋体\"),local(\"明体\"),local(\"明朝\"),\n\tlocal(\"Songti\"),local(\"Songti SC\"),local(\"Songti TC\"),\t\t\t/*iOS6+iBooks3*/\n\tlocal(\"Song S\"),local(\"Song T\"),local(\"STBShusong\"),local(\"TBMincho\"),local(\"HYMyeongJo\"),\t\t\t/*Kindle Paperwihite*/\n\tlocal(\"DK-SONGTI\"),\n\turl(../Fonts/zw.ttf),\n\turl(res:///opt/sony/ebook/FONT/zw.ttf),\n\turl(res:///Data/FONT/zw.ttf),\n\turl(res:///opt/sony/ebook/FONT/tt0011m_.ttf),\n\turl(res:///ebook/fonts/../../mnt/sdcard/fonts/zw.ttf),\n\turl(res:///ebook/fonts/../../mnt/extsd/fonts/zw.ttf),\n\turl(res:///ebook/fonts/zw.ttf),\n\turl(res:///ebook/fonts/DroidSansFallback.ttf),\n\turl(res:///fonts/ttf/zw.ttf),\n\turl(res:///../../media/mmcblk0p1/fonts/zw.ttf),\n\turl(file:///mnt/us/DK_System/system/fonts/zw.ttf),\t\t\t\t/*Duokan Old Path*/\n\turl(file:///mnt/us/DK_System/xKindle/res/userfonts/zw.ttf),\t\t/*Duokan 2012 Path*/\n\turl(res:///abook/fonts/zw.ttf),\n\turl(res:///system/fonts/zw.ttf),\n\turl(res:///system/media/sdcard/fonts/zw.ttf),\n\turl(res:///media/fonts/zw.ttf),\n\turl(res:///sdcard/fonts/zw.ttf),\n\turl(res:///system/fonts/DroidSansFallback.ttf),\n\turl(res:///mnt/MOVIFAT/font/zw.ttf),\n\turl(res:///media/flash/fonts/zw.ttf),\n\turl(res:///media/sd/fonts/zw.ttf),\n\turl(res:///opt/onyx/arm/lib/fonts/AdobeHeitiStd-Regular.otf),\n\turl(res:///../../fonts/zw.ttf),\n\turl(res:///../fonts/zw.ttf),\n\turl(../../../../../zw.ttf),\t\t\t\t\t\t\t\t\t\t/*EpubReaderI*/\n\turl(res:///mnt/sdcard/fonts/zw.ttf),\t\t\t\t\t\t\t/*Nook for Android: fonts in TF Card*/\n\turl(res:///fonts/zw.ttf),\t\t\t\t\t\t\t\t\t\t/*ADE1,8, 2.0 Program Path*/\n\turl(res:///../../../../Windows/fonts/zw.ttf);\n    /*ADE1,8, 2.0 Windows Path*/;\n}\n\n@font-face {\n    font-family: \"fs\";\n    src:\n\tlocal(\"amasis30\"),local(\"仿宋\"),local(\"仿宋_GB2312\"),\n\tlocal(\"Yuanti\"),local(\"Yuanti SC\"),local(\"Yuanti TC\"),\t\t\t/*iOS6+iBooks3*/\n\tlocal(\"DK-FANGSONG\"),\n\turl(../Fonts/fs.ttf),\n\turl(res:///opt/sony/ebook/FONT/fs.ttf),\n\turl(res:///Data/FONT/fs.ttf),\n\turl(res:///opt/sony/ebook/FONT/tt0011m_.ttf),\n\turl(res:///ebook/fonts/../../mnt/sdcard/fonts/fs.ttf),\n\turl(res:///ebook/fonts/../../mnt/extsd/fonts/fs.ttf),\n\turl(res:///ebook/fonts/fs.ttf),\n\turl(res:///ebook/fonts/DroidSansFallback.ttf),\n\turl(res:///fonts/ttf/fs.ttf),\n\turl(res:///../../media/mmcblk0p1/fonts/fs.ttf),\n\turl(file:///mnt/us/DK_System/system/fonts/fs.ttf),\t\t\t\t/*Duokan Old Path*/\n\turl(file:///mnt/us/DK_System/xKindle/res/userfonts/fs.ttf),\t\t/*Duokan 2012 Path*/\n\turl(res:///abook/fonts/fs.ttf),\n\turl(res:///system/fonts/fs.ttf),\n\turl(res:///system/media/sdcard/fonts/fs.ttf),\n\turl(res:///media/fonts/fs.ttf),\n\turl(res:///sdcard/fonts/fs.ttf),\n\turl(res:///system/fonts/DroidSansFallback.ttf),\n\turl(res:///mnt/MOVIFAT/font/fs.ttf),\n\turl(res:///media/flash/fonts/fs.ttf),\n\turl(res:///media/sd/fonts/fs.ttf),\n\turl(res:///opt/onyx/arm/lib/fonts/AdobeHeitiStd-Regular.otf),\n\turl(res:///../../fonts/fs.ttf),\n\turl(res:///../fonts/fs.ttf),\n\turl(../../../../../fs.ttf),\t\t\t\t\t\t\t\t\t\t/*EpubReaderI*/\n\turl(res:///mnt/sdcard/fonts/fs.ttf),\t\t\t\t\t\t\t/*Nook for Android: fonts in TF Card*/\n\turl(res:///fonts/fs.ttf),\t\t\t\t\t\t\t\t\t\t/*ADE1,8, 2.0 Program Path*/\n\turl(res:///../../../../Windows/fonts/fs.ttf);\n    /*ADE1,8, 2.0 Windows Path*/;\n}\n\n@font-face {\n    font-family: \"kt\";\n    src:\n\tlocal(\"Caecilia\"),local(\"楷体\"),local(\"楷体_GB2312\"),\n\tlocal(\"Kaiti\"),local(\"Kaiti SC\"),local(\"Kaiti TC\"),\t\t\t\t/*iOS6+iBooks3*/\n\tlocal(\"MKai PRC\"),local(\"MKaiGB18030C-Medium\"),local(\"MKaiGB18030C-Bold\"),\t\t\t/*Kindle Paperwihite*/\n\tlocal(\"DK-KAITI\"),\n\turl(../Fonts/kt.ttf),\n\turl(res:///opt/sony/ebook/FONT/kt.ttf),\n\turl(res:///Data/FONT/kt.ttf),\n\turl(res:///opt/sony/ebook/FONT/tt0011m_.ttf),\n\turl(res:///ebook/fonts/../../mnt/sdcard/fonts/kt.ttf),\n\turl(res:///ebook/fonts/../../mnt/extsd/fonts/kt.ttf),\n\turl(res:///ebook/fonts/kt.ttf),\n\turl(res:///ebook/fonts/DroidSansFallback.ttf),\n\turl(res:///fonts/ttf/kt.ttf),\n\turl(res:///../../media/mmcblk0p1/fonts/kt.ttf),\n\turl(file:///mnt/us/DK_System/system/fonts/kt.ttf),\t\t\t\t/*Duokan Old Path*/\n\turl(file:///mnt/us/DK_System/xKindle/res/userfonts/kt.ttf),\t\t/*Duokan 2012 Path*/\n\turl(res:///abook/fonts/kt.ttf),\n\turl(res:///system/fonts/kt.ttf),\n\turl(res:///system/media/sdcard/fonts/kt.ttf),\n\turl(res:///media/fonts/kt.ttf),\n\turl(res:///sdcard/fonts/kt.ttf),\n\turl(res:///system/fonts/DroidSansFallback.ttf),\n\turl(res:///mnt/MOVIFAT/font/kt.ttf),\n\turl(res:///media/flash/fonts/kt.ttf),\n\turl(res:///media/sd/fonts/kt.ttf),\n\turl(res:///opt/onyx/arm/lib/fonts/AdobeHeitiStd-Regular.otf),\n\turl(res:///../../fonts/kt.ttf),\n\turl(res:///../fonts/kt.ttf),\n\turl(../../../../../kt.ttf),\t\t\t\t\t\t\t\t\t\t/*EpubReaderI*/\n\turl(res:///mnt/sdcard/fonts/kt.ttf),\t\t\t\t\t\t\t/*Nook for Android: fonts in TF Card*/\n\turl(res:///fonts/kt.ttf),\t\t\t\t\t\t\t\t\t\t/*ADE1,8, 2.0 Program Path*/\n\turl(res:///../../../../Windows/fonts/kt.ttf);\n    /*ADE1,8, 2.0 Windows Path*/;\n}\n\n@font-face {\n    font-family: \"ht\";\n    src:\n\tlocal(\"黑体\"),local(\"微软雅黑\"),\n\tlocal(\"Heiti\"),local(\"Heiti SC\"),local(\"Heiti TC\"),\t\t\t\t/*iOS6+iBooks3*/\n\tlocal(\"MYing Hei S\"),local(\"MYing Hei T\"),local(\"TBGothic\"),\t\t\t\t\t\t/*Kindle Paperwihite*/\n\tlocal(\"DK-HEITI\"),\n\turl(../Fonts/ht.ttf),\n\turl(res:///opt/sony/ebook/FONT/ht.ttf),\n\turl(res:///Data/FONT/ht.ttf),\n\turl(res:///opt/sony/ebook/FONT/tt0011m_.ttf),\n\turl(res:///ebook/fonts/../../mnt/sdcard/fonts/ht.ttf),\n\turl(res:///ebook/fonts/../../mnt/extsd/fonts/ht.ttf),\n\turl(res:///ebook/fonts/ht.ttf),\n\turl(res:///ebook/fonts/DroidSansFallback.ttf),\n\turl(res:///fonts/ttf/ht.ttf),\n\turl(res:///../../media/mmcblk0p1/fonts/ht.ttf),\n\turl(file:///mnt/us/DK_System/system/fonts/ht.ttf),\t\t\t\t/*Duokan Old Path*/\n\turl(file:///mnt/us/DK_System/xKindle/res/userfonts/ht.ttf),\t\t/*Duokan 2012 Path*/\n\turl(res:///abook/fonts/ht.ttf),\n\turl(res:///system/fonts/ht.ttf),\n\turl(res:///system/media/sdcard/fonts/ht.ttf),\n\turl(res:///media/fonts/ht.ttf),\n\turl(res:///sdcard/fonts/ht.ttf),\n\turl(res:///system/fonts/DroidSansFallback.ttf),\n\turl(res:///mnt/MOVIFAT/font/ht.ttf),\n\turl(res:///media/flash/fonts/ht.ttf),\n\turl(res:///media/sd/fonts/ht.ttf),\n\turl(res:///opt/onyx/arm/lib/fonts/AdobeHeitiStd-Regular.otf),\n\turl(res:///../../fonts/ht.ttf),\n\turl(res:///../fonts/ht.ttf),\n\turl(../../../../../ht.ttf),\t\t\t\t\t\t\t\t\t\t/*EpubReaderI*/\n\turl(res:///mnt/sdcard/fonts/ht.ttf),\t\t\t\t\t\t\t/*Nook for Android: fonts in TF Card*/\n\turl(res:///fonts/ht.ttf),\t\t\t\t\t\t\t\t\t\t/*ADE1,8, 2.0 Program Path*/\n\turl(res:///../../../../Windows/fonts/ht.ttf);\n    /*ADE1,8, 2.0 Windows Path*/;\n}\n@font-face {\n\tfont-family:\"h1\";\n\tsrc:\n\tlocal(\"方正兰亭特黑长_GBK\"),local(\"方正兰亭特黑长简体\"),local(\"方正兰亭特黑长繁体\"),\n\tlocal(\"LantingTeheichang\"),\n\tlocal(\"Yuanti\"),local(\"Yuanti SC\"),local(\"Yuanti TC\"),\n\tlocal(\"DK-HEITI\"),\n\turl(../Fonts/h1.ttf),\n\turl(res:///opt/sony/ebook/FONT/h1.ttf),\n\turl(res:///Data/FONT/h1.ttf),\n\turl(res:///opt/sony/ebook/FONT/tt0011m_.ttf),\n\turl(res:///ebook/fonts/../../mnt/sdcard/fonts/h1.ttf),\n\turl(res:///ebook/fonts/../../mnt/extsd/fonts/h1.ttf),\n\turl(res:///ebook/fonts/h1.ttf),\n\turl(res:///ebook/fonts/DroidSansFallback.ttf),\n\turl(res:///fonts/ttf/h1.ttf),\n\turl(res:///../../media/mmcblk0p1/fonts/h1.ttf),\n\turl(file:///mnt/us/DK_System/system/fonts/h1.ttf),\t\t\t\t/*Duokan Old Path*/\n\turl(file:///mnt/us/DK_System/xKindle/res/userfonts/h1.ttf),\t\t/*Duokan 2012 Path*/\n\turl(res:///abook/fonts/h1.ttf),\n\turl(res:///system/fonts/h1.ttf),\n\turl(res:///system/media/sdcard/fonts/h1.ttf),\n\turl(res:///media/fonts/h1.ttf),\n\turl(res:///sdcard/fonts/h1.ttf),\n\turl(res:///system/fonts/DroidSansFallback.ttf),\n\turl(res:///mnt/MOVIFAT/font/h1.ttf),\n\turl(res:///media/flash/fonts/h1.ttf),\n\turl(res:///media/sd/fonts/h1.ttf),\n\turl(res:///opt/onyx/arm/lib/fonts/AdobeHeitiStd-Regular.otf),\n\turl(res:///../../fonts/h1.ttf),\n\turl(res:///../fonts/h1.ttf),\n\turl(../../../../../h1.ttf),\t\t\t\t\t\t\t\t\t\t/*EpubReaderI*/\n\turl(res:///mnt/sdcard/fonts/h1.ttf),\t\t\t\t\t\t\t/*Nook for Android: fonts in TF Card*/\n\turl(res:///fonts/h1.ttf),\t\t\t\t\t\t\t\t\t\t/*ADE1,8, 2.0 Program Path*/\n\turl(res:///../../../../Windows/fonts/h1.ttf);\t\t\t\t\t/*ADE1,8, 2.0 Windows Path*/\n}\n@font-face {\n\tfont-family:\"h2\";\n\tsrc:\n\tlocal(\"方正大标宋_GBK\"),local(\"方正大标宋简体\"),local(\"方正大标宋繁体\"),\n\tlocal(\"Dabiaosong\"),\n\tlocal(\"Heiti\"),local(\"Heiti SC\"),local(\"Heiti TC\"),\n\tlocal(\"DK-XIAOBIAOSONG\"),\n\turl(../Fonts/h2.ttf),\n\turl(res:///opt/sony/ebook/FONT/h2.ttf),\n\turl(res:///Data/FONT/h2.ttf),\n\turl(res:///opt/sony/ebook/FONT/tt0011m_.ttf),\n\turl(res:///ebook/fonts/../../mnt/sdcard/fonts/h2.ttf),\n\turl(res:///ebook/fonts/../../mnt/extsd/fonts/h2.ttf),\n\turl(res:///ebook/fonts/h2.ttf),\n\turl(res:///ebook/fonts/DroidSansFallback.ttf),\n\turl(res:///fonts/ttf/h2.ttf),\n\turl(res:///../../media/mmcblk0p1/fonts/h2.ttf),\n\turl(file:///mnt/us/DK_System/system/fonts/h2.ttf),\t\t\t\t/*Duokan Old Path*/\n\turl(file:///mnt/us/DK_System/xKindle/res/userfonts/h2.ttf),\t\t/*Duokan 2012 Path*/\n\turl(res:///abook/fonts/h2.ttf),\n\turl(res:///system/fonts/h2.ttf),\n\turl(res:///system/media/sdcard/fonts/h2.ttf),\n\turl(res:///media/fonts/h2.ttf),\n\turl(res:///sdcard/fonts/h2.ttf),\n\turl(res:///system/fonts/DroidSansFallback.ttf),\n\turl(res:///mnt/MOVIFAT/font/h2.ttf),\n\turl(res:///media/flash/fonts/h2.ttf),\n\turl(res:///media/sd/fonts/h2.ttf),\n\turl(res:///opt/onyx/arm/lib/fonts/AdobeHeitiStd-Regular.otf),\n\turl(res:///../../fonts/h2.ttf),\n\turl(res:///../fonts/h2.ttf),\n\turl(../../../../../h2.ttf),\t\t\t\t\t\t\t\t\t\t/*EpubReaderI*/\n\turl(res:///mnt/sdcard/fonts/h2.ttf),\t\t\t\t\t\t\t/*Nook for Android: fonts in TF Card*/\n\turl(res:///fonts/h2.ttf),\t\t\t\t\t\t\t\t\t\t/*ADE1,8, 2.0 Program Path*/\n\turl(res:///../../../../Windows/fonts/h2.ttf);\t\t\t\t\t/*ADE1,8, 2.0 Windows Path*/\n}\n\n@font-face {\n\tfont-family:\"h3\";\n\tsrc:\n\tlocal(\"方正华隶_GBK\"),local(\"方正行黑简体\"),local(\"方正行黑繁体\"),\n\tlocal(\"Yuanti\"),local(\"Yuanti SC\"),local(\"Yuanti TC\"),\n\tlocal(\"DK-FANGSONG\"),\n\turl(../Fonts/h3.ttf),\n\turl(res:///opt/sony/ebook/FONT/h3.ttf),\n\turl(res:///Data/FONT/h3.ttf),\n\turl(res:///opt/sony/ebook/FONT/tt0011m_.ttf),\n\turl(res:///ebook/fonts/../../mnt/sdcard/fonts/h3.ttf),\n\turl(res:///ebook/fonts/../../mnt/extsd/fonts/h3.ttf),\n\turl(res:///ebook/fonts/h3.ttf),\n\turl(res:///ebook/fonts/DroidSansFallback.ttf),\n\turl(res:///fonts/ttf/h3.ttf),\n\turl(res:///../../media/mmcblk0p1/fonts/h3.ttf),\n\turl(file:///mnt/us/DK_System/system/fonts/h3.ttf),\t\t\t\t/*Duokan Old Path*/\n\turl(file:///mnt/us/DK_System/xKindle/res/userfonts/h3.ttf),\t\t/*Duokan 2012 Path*/\n\turl(res:///abook/fonts/h3.ttf),\n\turl(res:///system/fonts/h3.ttf),\n\turl(res:///system/media/sdcard/fonts/h3.ttf),\n\turl(res:///media/fonts/h3.ttf),\n\turl(res:///sdcard/fonts/h3.ttf),\n\turl(res:///system/fonts/DroidSansFallback.ttf),\n\turl(res:///mnt/MOVIFAT/font/h3.ttf),\n\turl(res:///media/flash/fonts/h3.ttf),\n\turl(res:///media/sd/fonts/h3.ttf),\n\turl(res:///opt/onyx/arm/lib/fonts/AdobeHeitiStd-Regular.otf),\n\turl(res:///../../fonts/h3.ttf),\n\turl(res:///../fonts/h3.ttf),\n\turl(../../../../../h3.ttf),\t\t\t\t\t\t\t\t\t\t/*EpubReaderI*/\n\turl(res:///mnt/sdcard/fonts/h3.ttf),\t\t\t\t\t\t\t/*Nook for Android: fonts in TF Card*/\n\turl(res:///fonts/h3.ttf),\t\t\t\t\t\t\t\t\t\t/*ADE1,8, 2.0 Program Path*/\n\turl(res:///../../../../Windows/fonts/h3.ttf);\t\t\t\t\t/*ADE1,8, 2.0 Windows Path*/\n}\n\n@font-face {\n\tfont-family:\"luohua\";\n\tsrc:local(\"汉仪落花体\"),\n\t     url(\"../Fonts/hylh.ttf\");\n}"
  },
  {
    "path": "app/src/main/assets/epub/intro.html",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<!DOCTYPE html>\n<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"zh-CN\">\n<head>\n    <title>Intro</title>\n    <link href=\"../Styles/fonts.css\" type=\"text/css\" rel=\"stylesheet\" />\n    <link href=\"../Styles/main.css\" type=\"text/css\" rel=\"stylesheet\" />\n</head>\n<body>\n<h1 class=\"head\" style=\"margin-bottom:2em;\">内容简介</h1>{intro}</body>\n</html>\n"
  },
  {
    "path": "app/src/main/assets/epub/main.css",
    "content": "@charset \"utf-8\";\n@import url(\"../Styles/fonts.css\");\nbody {\n    padding: 0%;\n    margin-top: 0%;\n    margin-bottom: 0%;\n    margin-left: 0.5%;\n    margin-right: 0.5%;\n    line-height: 130%;\n    text-align: justify;\n    font-family: \"DK-SONGTI\",\"st\",\"宋体\",\"zw\",sans-serif;\n}\n\np {\n    text-align: justify;\n    text-indent: 2em;\n    line-height: 130%;\n    margin-right: 0.5%;\n    margin-left: 0.5%;\n    font-family: \"DK-SONGTI\",\"st\",\"宋体\",\"zw\",sans-serif;\n}\np.kaiti {\n    font-family: \"DK-KAITI\",\"kt\",\"楷体\",\"zw\",serif;\n}\n\np.fangsong {\n    font-family: \"DK-FANGSONG\",\"fs\",\"仿宋\",\"zw\",serif;\n}\n\nspan.xinli {\n    font-family: \"DK-KAITI\",\"kt\",\"楷体\",\"zw\",serif;\n    color: #4e753f;\n}\n/** 英文斜体字 **/\nspan.english{\n\tfont-style: italic;\n}\ndiv {\n    margin: 0px;\n    padding: 0px;\n    line-height: 120%;\n    text-align: justify;\n    font-family: \"zw\";\n}\ndiv.foot {\n    text-indent: 2em;\n    margin: 30% 5% 0 5%;\n    padding: 8px 0;\n}\np.foot {\n    font-family: \"DK-KAITI\",\"kt\",\"楷体\",\"zw\",serif;\n}\n\n/*扉页*/\n.booksubtitle {\n    padding: 10px 0 0px 0;\n    text-indent: 0em;\n    font-size: 75%;\n    font-family: \"ht\";    \n}\n\n.booktitle {\n   padding: 9% 0 0 0;\n   font-size: 1.3em;\n   font-family: \"方正小标宋_GBK\",\"DK-XIAOBIAOSONG\";\n   font-weight: normal;\n   text-indent: 0em;\n    color: #000;\n   text-align: center;\n   line-height: 1.6;\n}\n\n.booktitle0 {\n   font-size: 1.2em;\n   font-family: \"fs\";\n   text-indent: 0em;\n   text-align: center;\n   line-height: 1.8;\n}\n\n.booktitle1 {\n   padding: 0 0 0 0;\n   font-size: 0.85em;\n   font-family: \"fs\";\n   text-indent: 0em;\n   text-align: center;\n   line-height: 1.6;\n}\n\n.bookauthor {\n    font-family: \"DK-FANGSONG\",仿宋,\"fs\",\"fangsong\",sans-serif;\n    padding: 5% 5px 0px 5px;\n    text-indent: 0em;\n    text-align: center;  \n    color: #000;\n    font-size: 90%;\n    line-height: 1.3;\n}\n\n.booktranslator {\n    padding: 1% 5px 0px 5px;\n    text-indent: 0em;\n    text-align: center;   \n    font-size: 85%;\n    line-height: 1.3;\n}\n\n.bookpub {\n    font-family: \"DK-KAITI\",\"kt\",\"楷体\",\"楷体_gb2312\";\n    padding: 30% 5px 5px 5px;\n    text-indent: 0em;\n    color: #000;\n    text-align: center;  \n    font-size: 80%;\n}\n\n/*标题页*/\nbody.head {\n \tbackground-repeat:no-repeat no-repeat;\n\tbackground-size:160px 229px;\n\tbackground-position:bottom right;\n\tbackground-attachment:fixed;\n}\n\nbody.xhead {\n    background-color: #FDF5E6;\n}\n\nh1.head {\n    font-family: \"DK-HEITI\",黑体,sans-serif;\n    font-size: 1.2em;\n    font-weight: bold;\n    color: #311a02;\n    text-indent: 0em;\n    font-weight: normal;\n    duokan-text-indent: 0em;\n    padding: auto;\n    text-align: center;\n    margin-top: -8em;\n}\n\ndiv.head {\n    border: solid 2px #ffffff;\n    padding: 2px;\n    margin: 2em auto 0.7em auto;\n    text-align: center;\n    width: 1em;\n}\n\nh1.head b {\n    font-family: \"方正小标宋_GBK\",\"DK-XIAOBIAOSONG\";\n    font-weight: bold;\n    font-size: 1.2em;\n    text-align: center;\n    text-indent: 0em;\n    duokan-text-indent: 0em;\n    color: #311a02;\n    margin: 0.5em auto;\n    line-height: 140%;\n}\n\ndiv.back {\n    text-align: center;\n    text-indent: 0em;\n    duokan-text-indent: 0em;\n    margin: 4em auto;\n}\n\nimg.back {\n    width: 70%;\n}\nimg.back2 {\n    width: 40%;\n    margin: 2em 0 0 0;\n}\n/*正文*/\n/**楷体引文**/\n.titou {\n    font-family: \"DK-FANGSONG\",仿宋,\"fs\",\"fangsong\",sans-serif;\n}\n.yinwen {\n    font-family: \"DK-KAITI\",\"kt\",\"楷体\",\"zw\",serif;\n\tmargin-left: 2em;\n\ttext-indent: 0em;\n}\n.nicename {\n    font-family: \"DK-HEITI\",黑体,sans-serif;\n    font-weight: bold;\n    font-size: 0.9em;\n}\nbody.head3 {\n    background-color: #a7bdcc;\n    color: #354f66;\n}\n\nbody.head4 {\n    background-color: #bfd19b;\n    color: #4e753f;\n}\n\nh2.head {\n    font-family: \"小标宋\";\n    text-align: left;\n    font-weight: bold;\n    font-size: 1.1em;\n    margin: 1em 2em 2em 0;\n    color: #3f83e8;\n    line-height: 140%;\n}\n\nh2.head span {\n    font-family: \"仿宋\";\n    font-size: 0.7em;\n    background-color: #3f83e8;\n    border-radius: 9px;\n    padding: 4px;\n    color: #fff;\n}\n\n\ndiv.logo {\n    margin: -2em 0% 0 0;\n    text-align: right;\n}\n\nimg.logo {\n    width: 40%;\n}\n.imgl {\n    /*图片居右*/\n\tmargin: -8.8em 1em 4em 0em;\n    width: 80%;\n\ttext-align: right;\n}\n\nh1.head {\n\tline-height:130%;\n\tfont-size:1.4em;\n\ttext-align: center;\n\tcolor: #BA2213;\n\tfont-weight: bold;\n\tmargin-top: 2em;\n\tmargin-bottom: 1em;\n    font-family: \"方正小标宋_GBK\",\"DK-XIAOBIAOSONG\";\n\t\n}\nh3 {\n    font-family: \"DK-HEITI\",黑体,sans-serif;\n    font-size: 1.1em;\n    margin: 1em 0;\n    border-left: 1.2em solid #00a1e9;\n    line-height: 120%;\n    padding-left: 3px;\n\tcolor: #00a1e9;\n}\nh4 {\n    font-family: \"DK-HEITI\",黑体,sans-serif;\n    font-size: 1.1em;\n\ttext-align: center;\n    margin: 1em 0;\n    line-height: 120%;\n\tcolor: #000;\n}\nh1.post {\n    font-family: \"方正小标宋_GBK\",\"DK-XIAOBIAOSONG\";\n    text-align: center;\n    font-size: 1.3em;\n\tcolor: #026fca;\n    margin: 3em auto 2em auto;\n}\n.banquan {\n    font-family: \"DK-FANGSONG\",仿宋,\"fs\",\"fangsong\",sans-serif;\n    text-align: left;\n    color: #000;\n\tfont-size:1.1em;\n    margin-bottom:1em;\n    text-indent: 1em;\n    duokan-text-indent: 1em;\n}\np.post {\n    font-family: \"DK-FANGSONG\",仿宋,\"fs\",\"fangsong\",sans-serif;\n}\np.zy {\n    font-family: \"DK-FANGSONG\",仿宋,\"fs\",\"fangsong\",sans-serif;\n    margin: 1em 0 0 1em;\n    padding: 5px 0px 5px 10px;\n    text-indent: 0em;\n    border-left: 5px solid #a9b5c1;\n}\n.sign {\n    font-family: \"DK-KAITI\",\"kt\",\"楷体\",\"zw\",serif;\n    margin: 1em 2px 0 auto;\n    text-align: right;\n    font-size: 0.8em;\n    text-indent: 0em;\n    duokan-text-indent: 0em;\n}\n\n.mark {\n    font-family: \"DK-HEITI\",黑体,sans-serif;\n    font-size: 0.9em;\n    color: #fff;\n    text-indent: 0em;\n    duokan-text-indent: 0em;\n    background-color: maroon;\n    text-align: center;\n    padding: 0px;\n    margin: 2em 30%;\n}\n\n/*出版社*/\n.chubanshe img{\n\twidth:106px;\n\theight:28px;\n}\n.chubanshe {\n\tmargin-top:20px;\n}\n.cr {\n\tfont-size:0.9em;\n}\n\n/*多看画廊*/\ndiv.duokan-image-single {\n    text-align: center;\n    margin: 0.5em auto; /*插图盒子上下外边距为0.5em，左右设置auto是为了水平居中这个盒子*/\n}\nimg.picture-80 {\n    margin: 0; /*清除img元素的外边距*/\n    width: 80%; /*预览窗口的宽度*/\n    box-shadow: 3px 3px 10px #bfbfbf; /*给图片添加阴影效果*/\n}\np.duokan-image-maintitle {\n    margin: 1em 0 0; /*图片说明的段间距*/\n    font-family: \"楷体\"; /*图片说明使用的字体*/\n    font-size: 0.9em; /*字体大小*/\n    text-indent: 0; /*首行缩进为零，当你使用单标签p来指定首行缩进为2em时，记得在需要居中的文本中清除缩进，因为样式是叠加的*/\n    text-align: center; /*图片说明水平居中*/\n    color: #a52a2a; /*字体颜色*/\n    line-height: 1.25em; /*行高，防止有很长的图片说明*/\n}\n\n\n/*制作说明页*/\nbody.description {\n    background-image: url(../Images/001.png);\n    background-position: bottom center;\n    background-repeat: no-repeat;\n    background-size: cover;\n    padding: 25% 10% 0;\n    font-size: 0.9em;\n}\n\ndiv.description-body {\n    width: 55%;\n    padding: 2em 1.3em;\n    border-radius: 0.5em;\n    font-size: 0.9em;\n    border-style: solid;\n    border-color: #393939;\n    border-width: 0.3em;\n    border-radius: 5em;\n    background-color: #5a5a5a;\n    box-shadow: 2px 2px 3px #828281;\n}\n\nh1.description-title {\n    text-align: center;\n    font-family: \"黑体\";\n    font-size: 1.2em;\n    margin: 0 0 1em 0;\n    color: #FF9;\n    text-shadow: 1px 1px 0 black;\n}\n\np.description-text {\n    color: #f9ddd2;\n    font-family: \"准圆\";\n    margin: 0;\n    text-align: justify;\n    text-indent: 0;\n    duokan-text-indent: 0;\n}\n\nhr.description-hr {\n    margin: 0.5em -1em;\n    border-style: dotted;\n    border-color: #9C9;\n    border-width: 0.05em 0 0 0;\n}\n\np.tips {\n    text-align: justify;\n    text-indent: 0;\n    duokan-text-indent: 0;\n    font-family: \"楷体\";\n    font-size: 0.7em;\n    color: #FFC;\n    margin: 0;\n}\n\n/*版本说明页*/\n.ver {\n    font-family: \"DK-CODE\",\"DK-XIHEITI\",细黑体,\"xihei\",sans-serif;\n\tfont-weight: bold;\n    font-size: 100%;\n    color: #000;\n    margin: 1em 0 1em 0;\n    text-align: center;\n}\n\n.vertitle {\n    font-family: \"DK-FANGSONG\",仿宋,\"fs\",\"fangsong\",sans-serif;\n    font-size: 100%;\n    text-indent: 0em;\n    text-align: left;\n    duokan-text-indent: 0em;\n}\n\n.vertxt {\n    font-family: \"DK-FANGSONG\",仿宋,\"fs\",\"fangsong\",sans-serif;\n\tline-height: 100%;\n    font-size: 85%;\n    text-indent: 0em;\n    text-align: left;\n    duokan-text-indent: 0em;\n}\n.verchar {\n    font-family: \"DK-KAITI\",\"kt\",\"楷体\",\"楷体_gb2312\";\n    text-align: left;\n    text-indent: 1em;\n    duokan-text-indent: 1em;\n\tmargin-bottom: 1em;\n\tmargin-top: 1em;\n}\n.vernote {\n    font-family: \"DK-FANGSONG\",仿宋,\"fs\",\"fangsong\",sans-serif;\n    font-size: 75%;\n    color: #686d70;\n    text-indent: 0em;\n    text-align: left;\n    duokan-text-indent: 0em;\n    padding-bottom: 15px;\n}\n\n.line {\n    border: dotted #A2906A;\n    border-width: 1px 0 0 0;\n}\n\n.entry {\n    margin-left: 18px;\n    font-size: 83%;\n    color: #8fe0a3;\n    text-indent: 0em;\n    duokan-text-indent: 0em;\n}\n/*版权信息*/\n.vol {\n    text-indent: 0em;\n    text-align: center;\n    padding: 0.8em;\n    margin: 0 auto 3px auto;\n    color: #000;\n    font-family: \"方正小标宋_GBK\",\"DK-XIAOBIAOSONG\";\n    font-size: 130%;\n    text-shadow: none;\n}\n\n.cp {\n    font-family: \"DK-CODE\",\"DK-XIHEITI\",细黑体,\"xihei\",sans-serif;\n    color: #412938;\n    font-size: 70%;\n    text-align: left;\n    text-indent: 0em;\n    duokan-text-indent: 0em;\n}\n\n.xchar {\n    font-family: \"DK-KAITI\",\"kt\",\"楷体\",\"楷体_gb2312\";\n    text-indent: 0em;\n    duokan-text-indent: 0em;\n}\n/*多看弹注*/\nsup img {\n\tline-height: 100%;\n\twidth: auto;\n    height: 1.0em;\n    margin: 0em;\n    padding: 0em;\n\tvertical-align: text-top;\n}\n\nol {\n\tmargin-bottom:0;\n\tpadding:0 auto;\n\tlist-style-type: decimal;\n}\n.hr {\n\twidth:50%;\n\tmargin:2em 0 0 0.5em;\n\tpadding:0;\n\theight:2px;\n\tbackground-color: #F3221D;\n}\n\n.duokan-footnote-content{\n\tpadding:0 auto;\n\ttext-align: left;\n}\n\n.duokan-footnote-item {\n\tfont-family:\"DK-XIHEITI\",细黑体,\"xihei\",sans-serif;\n\ttext-align: left;\n\tfont-size: 80%;\n\tline-height: 100%;\n\tclear: both;\n\tcolor:#000;\n\tlist-style-type:decimal;\n}\n\nli.duokan-footnote-item a {\n\tfont-family:\"DK-HEITI\";\n\ttext-align: left;\n}\na{\n\ttext-decoration: none;\n\tcolor: #222;\n}\n\na:hover {background: #81caf9}\t\na:active {background: yellow}\n.duokan-image-maintitle {\n\tfont-family:\"DK-HEITI\",黑体,\"hei\",sans-serif;\n\ttext-align: center;\n\ttext-indent: 0em;\n\tduokan-text-indent: 0em;\n\tfont-size:90%;\n\tcolor: #1F4150;\n\tmargin-top: 1em;\n}\n\n.duokan-image-subtitle {\n\tfont-family:\"DK-XIHEITI\",细黑体,\"xihei\",sans-serif;\n\ttext-align: center;\n\ttext-indent: 0em;\n\tduokan-text-indent: 0em;\n\tfont-size:70%;\n\tcolor: #3A3348;\n\tmargin-top: 1em;\n}"
  },
  {
    "path": "app/src/main/assets/privacyPolicy.md",
    "content": "* 本应用没有服务端,不收集任何用户信息,只采用了Google Firebase收集崩溃报告和性能报告.\n* 本应用网络同步和备份采用webDav协议,由用户自己提供同步服务.\n* 存储权限用来打开本地文件和本地备份恢复.\n* 其它一些权限是Google Firebase需要.\n* 本应用为开源软件,内置js引擎,因书源调用js发生的任何问题由用户自行承担."
  },
  {
    "path": "app/src/main/assets/storageHelp.md",
    "content": "* 由于安卓的存储访问限制，阅读需要设置**公共目录下的子目录**来实现书籍拷贝、下载，例如Documents/Books、Download/Books\n* 如不设置，将无法正常使用本地书籍、webDav书籍的相关功能"
  },
  {
    "path": "app/src/main/assets/updateLog.md",
    "content": "# 更新日志\n\n* 关注公众号 **[开源阅读]** 菜单•软件下载 提前享受新版本。\n* 关注合作公众号 **[小说拾遗]** 获取好看的小说。\n\n## cronet版本: 128.0.6613.40\n\n## **必读**\n\n【温馨提醒】 *更新前一定要做好备份，以免数据丢失！*\n\n* 阅读只是一个转码工具，不提供内容，第一次安装app，需要自己手动导入书源，可以从公众号 **[开源阅读]**\n  、QQ群、酷安评论里获取由书友制作分享的书源。\n* 正文出现缺字漏字、内容缺失、排版错乱等情况，有可能是净化规则或简繁转换出现问题。\n* 漫画源看书显示乱码，**阅读与其他软件的源并不通用**，请导入阅读的支持的漫画源！\n\n**2025/03/26**\n* 目前阅读被恶意注册软著，并建立了多个公众号\n* 官方公众号仅有：开源阅读、开源阅读软件，其他公众号与本软件无关\n\n**2024/10/03**\n* web书架支持加载网络字体、试读非书架书籍后弹窗、自定义后端IP\n* rss收藏添加分组管理\n* 朗读功能添加流式播放音频、来电自动暂停播放功能\n* 其它bug修复\n\n**2024/02/27**\n* 添加备用URL导出\n* 更新书源制作的帮助文档链接\n\n**2024/02/20**\n* 更新cronet: 121.0.6167.180\n* 更新 kotlin->1.9.22 ksp->1.0.17\n* 界面绘制优化\n* Bug修复和其他优化\n\n----\n\n* [2023年日志](https://github.com/gedoor/legado/blob/record2023/app/src/main/assets/updateLog.md)\n* [2022年日志](https://github.com/gedoor/legado/blob/record2022/app/src/main/assets/updateLog.md)\n* [2021年日志](https://github.com/gedoor/legado/blob/record2021/app/src/main/assets/updateLog.md)\n"
  },
  {
    "path": "app/src/main/assets/web/assets/css/main.css",
    "content": " /*\n\tForty by HTML5 UP\n\thtml5up.net | @ajlkn\n\tFree for personal and commercial use under the CCA 3.0 license (html5up.net/license)\n*/\n\nhtml, body, div, span, applet, object,\niframe, h1, h2, h3, h4, h5, h6, p, blockquote,\npre, a, abbr, acronym, address, big, cite,\ncode, del, dfn, em, img, ins, kbd, q, s, samp,\nsmall, strike, strong, sub, sup, tt, var, b,\nu, i, center, dl, dt, dd, ol, ul, li, fieldset,\nform, label, legend, table, caption, tbody,\ntfoot, thead, tr, th, td, article, aside,\ncanvas, details, embed, figure, figcaption,\nfooter, header, hgroup, menu, nav, output, ruby,\nsection, summary, time, mark, audio, video {\n\tmargin: 0;\n\tpadding: 0;\n\tborder: 0;\n\tfont-size: 100%;\n\tfont: inherit;\n\tvertical-align: baseline;}\n\narticle, aside, details, figcaption, figure,\nfooter, header, hgroup, menu, nav, section {\n\tdisplay: block;}\n\nbody {\n\tline-height: 1;\n\tbackground: linear-gradient(rgba(0, 0, 0, 0.6), rgba(0, 0, 0, 0.6)),url(\"../../images/bg.jpg\") no-repeat center center fixed;\n\t-webkit-background-size: cover;\n\t-moz-background-size: cover;\n\t-o-background-size: cover;\n\tbackground-size: cover;\n}\n\nol, ul {\n\tlist-style: none;\n}\n\nblockquote, q {\n\tquotes: none;\n}\n\n\tblockquote:before, blockquote:after, q:before, q:after {\n\t\tcontent: '';\n\t\tcontent: none;\n\t}\n\ntable {\n\tborder-collapse: collapse;\n\tborder-spacing: 0;\n}\n\nbody {\n\t-webkit-text-size-adjust: none;\n}\n\nmark {\n\tbackground-color: transparent;\n\tcolor: inherit;\n}\n\ninput::-moz-focus-inner {\n\tborder: 0;\n\tpadding: 0;\n}\n\ninput, select, textarea {\n\t-moz-appearance: none;\n\t-webkit-appearance: none;\n\t-ms-appearance: none;\n\tappearance: none;\n}\n\n/* Basic */\n\n\t@-ms-viewport {\n\t\twidth: device-width;\n\t}\n\n\tbody {\n\t\t-ms-overflow-style: scrollbar;\n\t}\n\n\t@media screen and (max-width: 480px) {\n\n\t\thtml, body {\n\t\t\tmin-width: 320px;\n\t\t}\n\n\t}\n\n\thtml {\n\t\tbox-sizing: border-box;\n\t\theight: 100vh;\n\t}\n\n\t*, *:before, *:after {\n\t\tbox-sizing: inherit;\n\t}\n\n\tbody {\n\t\t/* background: #242943; */\n\t\theight: 100vh;\n\t}\n\n\t\tbody.is-preload *, body.is-preload *:before, body.is-preload *:after {\n\t\t\t-moz-animation: none !important;\n\t\t\t-webkit-animation: none !important;\n\t\t\t-ms-animation: none !important;\n\t\t\tanimation: none !important;\n\t\t\t-moz-transition: none !important;\n\t\t\t-webkit-transition: none !important;\n\t\t\t-ms-transition: none !important;\n\t\t\ttransition: none !important;\n\t\t}\n\n/* Type */\n\n\tbody, input, select, textarea {\n\t\tcolor: #ffffff;\n\t\tfont-family: \"Source Sans Pro\", Helvetica, sans-serif;\n\t\tfont-size: 17pt;\n\t\tfont-weight: 300;\n\t\tletter-spacing: 0.025em;\n\t\tline-height: 1.65;\n\t}\n\n\t\t@media screen and (max-width: 1680px) {\n\n\t\t\tbody, input, select, textarea {\n\t\t\t\tfont-size: 14pt;\n\t\t\t}\n\n\t\t}\n\n\t\t@media screen and (max-width: 1280px) {\n\n\t\t\tbody, input, select, textarea {\n\t\t\t\tfont-size: 12pt;\n\t\t\t}\n\n\t\t}\n\n\t\t@media screen and (max-width: 360px) {\n\n\t\t\tbody, input, select, textarea {\n\t\t\t\tfont-size: 11pt;\n\t\t\t}\n\n\t\t}\n\n\ta {\n\t\t-moz-transition: color 0.2s ease-in-out, border-bottom-color 0.2s ease-in-out;\n\t\t-webkit-transition: color 0.2s ease-in-out, border-bottom-color 0.2s ease-in-out;\n\t\t-ms-transition: color 0.2s ease-in-out, border-bottom-color 0.2s ease-in-out;\n\t\ttransition: color 0.2s ease-in-out, border-bottom-color 0.2s ease-in-out;\n\t\tborder-bottom: dotted 1px;\n\t\tcolor: inherit;\n\t\ttext-decoration: none;\n\t}\n\n\t\ta:hover {\n\t\t\tborder-bottom-color: transparent;\n\t\t\tcolor: #9bf1ff !important;\n\t\t}\n\n\t\ta:active {\n\t\t\tcolor: #53e3fb !important;\n\t\t}\n\n\tstrong, b {\n\t\tcolor: #ffffff;\n\t\tfont-weight: 600;\n\t}\n\n\tem, i {\n\t\tfont-style: italic;\n\t}\n\n\tp {\n\t\tmargin: 0 0 2em 0;\n\t}\n\n\th1, h2, h3, h4, h5, h6 {\n\t\tcolor: #ffffff;\n\t\tfont-weight: 600;\n\t\tline-height: 1.65;\n\t\tmargin: 0 0 1em 0;\n\t}\n\n\t\th1 a, h2 a, h3 a, h4 a, h5 a, h6 a {\n\t\t\tcolor: inherit;\n\t\t\tborder-bottom: 0;\n\t\t}\n\n\th1 {\n\t\tfont-size: 2.5em;\n\t}\n\n\th2 {\n\t\tfont-size: 1.75em;\n\t}\n\n\th3 {\n\t\tfont-size: 1.35em;\n\t}\n\n\th4 {\n\t\tfont-size: 1.1em;\n\t}\n\n\th5 {\n\t\tfont-size: 0.9em;\n\t}\n\n\th6 {\n\t\tfont-size: 0.7em;\n\t}\n\n\t@media screen and (max-width: 736px) {\n\n\t\th1 {\n\t\t\tfont-size: 2em;\n\t\t}\n\n\t\th2 {\n\t\t\tfont-size: 1.5em;\n\t\t}\n\n\t\th3 {\n\t\t\tfont-size: 1.25em;\n\t\t}\n\n\t}\n\n\tsub {\n\t\tfont-size: 0.8em;\n\t\tposition: relative;\n\t\ttop: 0.5em;\n\t}\n\n\tsup {\n\t\tfont-size: 0.8em;\n\t\tposition: relative;\n\t\ttop: -0.5em;\n\t}\n\n\tblockquote {\n\t\tborder-left: solid 4px rgba(212, 212, 255, 0.1);\n\t\tfont-style: italic;\n\t\tmargin: 0 0 2em 0;\n\t\tpadding: 0.5em 0 0.5em 2em;\n\t}\n\n\tcode {\n\t\tbackground: rgba(212, 212, 255, 0.035);\n\t\tfont-family: \"Courier New\", monospace;\n\t\tfont-size: 0.9em;\n\t\tmargin: 0 0.25em;\n\t\tpadding: 0.25em 0.65em;\n\t}\n\n\tpre {\n\t\t-webkit-overflow-scrolling: touch;\n\t\tfont-family: \"Courier New\", monospace;\n\t\tfont-size: 0.9em;\n\t\tmargin: 0 0 2em 0;\n\t}\n\n\t\tpre code {\n\t\t\tdisplay: block;\n\t\t\tline-height: 1.75;\n\t\t\tpadding: 1em 1.5em;\n\t\t\toverflow-x: auto;\n\t\t}\n\n\thr {\n\t\tborder: 0;\n\t\tborder-bottom: solid 1px rgba(212, 212, 255, 0.1);\n\t\tmargin: 2em 0;\n\t}\n\n\t\thr.major {\n\t\t\tmargin: 3em 0;\n\t\t}\n\n\t.align-left {\n\t\ttext-align: left;\n\t}\n\n\t.align-center {\n\t\ttext-align: center;\n\t}\n\n\t.align-right {\n\t\ttext-align: right;\n\t}\n\n/* Row */\n\n\t.row {\n\t\tdisplay: flex;\n\t\tflex-wrap: wrap;\n\t\tbox-sizing: border-box;\n\t\talign-items: stretch;\n\t}\n\n\t\t.row > * {\n\t\t\tbox-sizing: border-box;\n\t\t}\n\n\t\t.row.gtr-uniform > * > :last-child {\n\t\t\tmargin-bottom: 0;\n\t\t}\n\n\t\t.row.aln-left {\n\t\t\tjustify-content: flex-start;\n\t\t}\n\n\t\t.row.aln-center {\n\t\t\tjustify-content: center;\n\t\t}\n\n\t\t.row.aln-right {\n\t\t\tjustify-content: flex-end;\n\t\t}\n\n\t\t.row.aln-top {\n\t\t\talign-items: flex-start;\n\t\t}\n\n\t\t.row.aln-middle {\n\t\t\talign-items: center;\n\t\t}\n\n\t\t.row.aln-bottom {\n\t\t\talign-items: flex-end;\n\t\t}\n\n\t\t.row > .imp {\n\t\t\torder: -1;\n\t\t}\n\n\t\t.row > .col-1 {\n\t\t\twidth: 8.33333%;\n\t\t}\n\n\t\t.row > .off-1 {\n\t\t\tmargin-left: 8.33333%;\n\t\t}\n\n\t\t.row > .col-2 {\n\t\t\twidth: 16.66667%;\n\t\t}\n\n\t\t.row > .off-2 {\n\t\t\tmargin-left: 16.66667%;\n\t\t}\n\n\t\t.row > .col-3 {\n\t\t\twidth: 25%;\n\t\t}\n\n\t\t.row > .off-3 {\n\t\t\tmargin-left: 25%;\n\t\t}\n\n\t\t.row > .col-4 {\n\t\t\twidth: 33.33333%;\n\t\t}\n\n\t\t.row > .off-4 {\n\t\t\tmargin-left: 33.33333%;\n\t\t}\n\n\t\t.row > .col-5 {\n\t\t\twidth: 41.66667%;\n\t\t}\n\n\t\t.row > .off-5 {\n\t\t\tmargin-left: 41.66667%;\n\t\t}\n\n\t\t.row > .col-6 {\n\t\t\twidth: 50%;\n\t\t}\n\n\t\t.row > .off-6 {\n\t\t\tmargin-left: 50%;\n\t\t}\n\n\t\t.row > .col-7 {\n\t\t\twidth: 58.33333%;\n\t\t}\n\n\t\t.row > .off-7 {\n\t\t\tmargin-left: 58.33333%;\n\t\t}\n\n\t\t.row > .col-8 {\n\t\t\twidth: 66.66667%;\n\t\t}\n\n\t\t.row > .off-8 {\n\t\t\tmargin-left: 66.66667%;\n\t\t}\n\n\t\t.row > .col-9 {\n\t\t\twidth: 75%;\n\t\t}\n\n\t\t.row > .off-9 {\n\t\t\tmargin-left: 75%;\n\t\t}\n\n\t\t.row > .col-10 {\n\t\t\twidth: 83.33333%;\n\t\t}\n\n\t\t.row > .off-10 {\n\t\t\tmargin-left: 83.33333%;\n\t\t}\n\n\t\t.row > .col-11 {\n\t\t\twidth: 91.66667%;\n\t\t}\n\n\t\t.row > .off-11 {\n\t\t\tmargin-left: 91.66667%;\n\t\t}\n\n\t\t.row > .col-12 {\n\t\t\twidth: 100%;\n\t\t}\n\n\t\t.row > .off-12 {\n\t\t\tmargin-left: 100%;\n\t\t}\n\n\t\t.row.gtr-0 {\n\t\t\tmargin-top: 0;\n\t\t\tmargin-left: 0em;\n\t\t}\n\n\t\t\t.row.gtr-0 > * {\n\t\t\t\tpadding: 0 0 0 0em;\n\t\t\t}\n\n\t\t\t.row.gtr-0.gtr-uniform {\n\t\t\t\tmargin-top: 0em;\n\t\t\t}\n\n\t\t\t\t.row.gtr-0.gtr-uniform > * {\n\t\t\t\t\tpadding-top: 0em;\n\t\t\t\t}\n\n\t\t.row.gtr-25 {\n\t\t\tmargin-top: 0;\n\t\t\tmargin-left: -0.5em;\n\t\t}\n\n\t\t\t.row.gtr-25 > * {\n\t\t\t\tpadding: 0 0 0 0.5em;\n\t\t\t}\n\n\t\t\t.row.gtr-25.gtr-uniform {\n\t\t\t\tmargin-top: -0.5em;\n\t\t\t}\n\n\t\t\t\t.row.gtr-25.gtr-uniform > * {\n\t\t\t\t\tpadding-top: 0.5em;\n\t\t\t\t}\n\n\t\t.row.gtr-50 {\n\t\t\tmargin-top: 0;\n\t\t\tmargin-left: -1em;\n\t\t}\n\n\t\t\t.row.gtr-50 > * {\n\t\t\t\tpadding: 0 0 0 1em;\n\t\t\t}\n\n\t\t\t.row.gtr-50.gtr-uniform {\n\t\t\t\tmargin-top: -1em;\n\t\t\t}\n\n\t\t\t\t.row.gtr-50.gtr-uniform > * {\n\t\t\t\t\tpadding-top: 1em;\n\t\t\t\t}\n\n\t\t.row {\n\t\t\tmargin-top: 0;\n\t\t\tmargin-left: -2em;\n\t\t}\n\n\t\t\t.row > * {\n\t\t\t\tpadding: 0 0 0 2em;\n\t\t\t}\n\n\t\t\t.row.gtr-uniform {\n\t\t\t\tmargin-top: -2em;\n\t\t\t}\n\n\t\t\t\t.row.gtr-uniform > * {\n\t\t\t\t\tpadding-top: 2em;\n\t\t\t\t}\n\n\t\t.row.gtr-150 {\n\t\t\tmargin-top: 0;\n\t\t\tmargin-left: -3em;\n\t\t}\n\n\t\t\t.row.gtr-150 > * {\n\t\t\t\tpadding: 0 0 0 3em;\n\t\t\t}\n\n\t\t\t.row.gtr-150.gtr-uniform {\n\t\t\t\tmargin-top: -3em;\n\t\t\t}\n\n\t\t\t\t.row.gtr-150.gtr-uniform > * {\n\t\t\t\t\tpadding-top: 3em;\n\t\t\t\t}\n\n\t\t.row.gtr-200 {\n\t\t\tmargin-top: 0;\n\t\t\tmargin-left: -4em;\n\t\t}\n\n\t\t\t.row.gtr-200 > * {\n\t\t\t\tpadding: 0 0 0 4em;\n\t\t\t}\n\n\t\t\t.row.gtr-200.gtr-uniform {\n\t\t\t\tmargin-top: -4em;\n\t\t\t}\n\n\t\t\t\t.row.gtr-200.gtr-uniform > * {\n\t\t\t\t\tpadding-top: 4em;\n\t\t\t\t}\n\n\t\t@media screen and (max-width: 1680px) {\n\n\t\t\t.row {\n\t\t\t\tdisplay: flex;\n\t\t\t\tflex-wrap: wrap;\n\t\t\t\tbox-sizing: border-box;\n\t\t\t\talign-items: stretch;\n\t\t\t}\n\n\t\t\t\t.row > * {\n\t\t\t\t\tbox-sizing: border-box;\n\t\t\t\t}\n\n\t\t\t\t.row.gtr-uniform > * > :last-child {\n\t\t\t\t\tmargin-bottom: 0;\n\t\t\t\t}\n\n\t\t\t\t.row.aln-left {\n\t\t\t\t\tjustify-content: flex-start;\n\t\t\t\t}\n\n\t\t\t\t.row.aln-center {\n\t\t\t\t\tjustify-content: center;\n\t\t\t\t}\n\n\t\t\t\t.row.aln-right {\n\t\t\t\t\tjustify-content: flex-end;\n\t\t\t\t}\n\n\t\t\t\t.row.aln-top {\n\t\t\t\t\talign-items: flex-start;\n\t\t\t\t}\n\n\t\t\t\t.row.aln-middle {\n\t\t\t\t\talign-items: center;\n\t\t\t\t}\n\n\t\t\t\t.row.aln-bottom {\n\t\t\t\t\talign-items: flex-end;\n\t\t\t\t}\n\n\t\t\t\t.row > .imp-xlarge {\n\t\t\t\t\torder: -1;\n\t\t\t\t}\n\n\t\t\t\t.row > .col-1-xlarge {\n\t\t\t\t\twidth: 8.33333%;\n\t\t\t\t}\n\n\t\t\t\t.row > .off-1-xlarge {\n\t\t\t\t\tmargin-left: 8.33333%;\n\t\t\t\t}\n\n\t\t\t\t.row > .col-2-xlarge {\n\t\t\t\t\twidth: 16.66667%;\n\t\t\t\t}\n\n\t\t\t\t.row > .off-2-xlarge {\n\t\t\t\t\tmargin-left: 16.66667%;\n\t\t\t\t}\n\n\t\t\t\t.row > .col-3-xlarge {\n\t\t\t\t\twidth: 25%;\n\t\t\t\t}\n\n\t\t\t\t.row > .off-3-xlarge {\n\t\t\t\t\tmargin-left: 25%;\n\t\t\t\t}\n\n\t\t\t\t.row > .col-4-xlarge {\n\t\t\t\t\twidth: 33.33333%;\n\t\t\t\t}\n\n\t\t\t\t.row > .off-4-xlarge {\n\t\t\t\t\tmargin-left: 33.33333%;\n\t\t\t\t}\n\n\t\t\t\t.row > .col-5-xlarge {\n\t\t\t\t\twidth: 41.66667%;\n\t\t\t\t}\n\n\t\t\t\t.row > .off-5-xlarge {\n\t\t\t\t\tmargin-left: 41.66667%;\n\t\t\t\t}\n\n\t\t\t\t.row > .col-6-xlarge {\n\t\t\t\t\twidth: 50%;\n\t\t\t\t}\n\n\t\t\t\t.row > .off-6-xlarge {\n\t\t\t\t\tmargin-left: 50%;\n\t\t\t\t}\n\n\t\t\t\t.row > .col-7-xlarge {\n\t\t\t\t\twidth: 58.33333%;\n\t\t\t\t}\n\n\t\t\t\t.row > .off-7-xlarge {\n\t\t\t\t\tmargin-left: 58.33333%;\n\t\t\t\t}\n\n\t\t\t\t.row > .col-8-xlarge {\n\t\t\t\t\twidth: 66.66667%;\n\t\t\t\t}\n\n\t\t\t\t.row > .off-8-xlarge {\n\t\t\t\t\tmargin-left: 66.66667%;\n\t\t\t\t}\n\n\t\t\t\t.row > .col-9-xlarge {\n\t\t\t\t\twidth: 75%;\n\t\t\t\t}\n\n\t\t\t\t.row > .off-9-xlarge {\n\t\t\t\t\tmargin-left: 75%;\n\t\t\t\t}\n\n\t\t\t\t.row > .col-10-xlarge {\n\t\t\t\t\twidth: 83.33333%;\n\t\t\t\t}\n\n\t\t\t\t.row > .off-10-xlarge {\n\t\t\t\t\tmargin-left: 83.33333%;\n\t\t\t\t}\n\n\t\t\t\t.row > .col-11-xlarge {\n\t\t\t\t\twidth: 91.66667%;\n\t\t\t\t}\n\n\t\t\t\t.row > .off-11-xlarge {\n\t\t\t\t\tmargin-left: 91.66667%;\n\t\t\t\t}\n\n\t\t\t\t.row > .col-12-xlarge {\n\t\t\t\t\twidth: 100%;\n\t\t\t\t}\n\n\t\t\t\t.row > .off-12-xlarge {\n\t\t\t\t\tmargin-left: 100%;\n\t\t\t\t}\n\n\t\t\t\t.row.gtr-0 {\n\t\t\t\t\tmargin-top: 0;\n\t\t\t\t\tmargin-left: 0em;\n\t\t\t\t}\n\n\t\t\t\t\t.row.gtr-0 > * {\n\t\t\t\t\t\tpadding: 0 0 0 0em;\n\t\t\t\t\t}\n\n\t\t\t\t\t.row.gtr-0.gtr-uniform {\n\t\t\t\t\t\tmargin-top: 0em;\n\t\t\t\t\t}\n\n\t\t\t\t\t\t.row.gtr-0.gtr-uniform > * {\n\t\t\t\t\t\t\tpadding-top: 0em;\n\t\t\t\t\t\t}\n\n\t\t\t\t.row.gtr-25 {\n\t\t\t\t\tmargin-top: 0;\n\t\t\t\t\tmargin-left: -0.5em;\n\t\t\t\t}\n\n\t\t\t\t\t.row.gtr-25 > * {\n\t\t\t\t\t\tpadding: 0 0 0 0.5em;\n\t\t\t\t\t}\n\n\t\t\t\t\t.row.gtr-25.gtr-uniform {\n\t\t\t\t\t\tmargin-top: -0.5em;\n\t\t\t\t\t}\n\n\t\t\t\t\t\t.row.gtr-25.gtr-uniform > * {\n\t\t\t\t\t\t\tpadding-top: 0.5em;\n\t\t\t\t\t\t}\n\n\t\t\t\t.row.gtr-50 {\n\t\t\t\t\tmargin-top: 0;\n\t\t\t\t\tmargin-left: -1em;\n\t\t\t\t}\n\n\t\t\t\t\t.row.gtr-50 > * {\n\t\t\t\t\t\tpadding: 0 0 0 1em;\n\t\t\t\t\t}\n\n\t\t\t\t\t.row.gtr-50.gtr-uniform {\n\t\t\t\t\t\tmargin-top: -1em;\n\t\t\t\t\t}\n\n\t\t\t\t\t\t.row.gtr-50.gtr-uniform > * {\n\t\t\t\t\t\t\tpadding-top: 1em;\n\t\t\t\t\t\t}\n\n\t\t\t\t.row {\n\t\t\t\t\tmargin-top: 0;\n\t\t\t\t\tmargin-left: -2em;\n\t\t\t\t}\n\n\t\t\t\t\t.row > * {\n\t\t\t\t\t\tpadding: 0 0 0 2em;\n\t\t\t\t\t}\n\n\t\t\t\t\t.row.gtr-uniform {\n\t\t\t\t\t\tmargin-top: -2em;\n\t\t\t\t\t}\n\n\t\t\t\t\t\t.row.gtr-uniform > * {\n\t\t\t\t\t\t\tpadding-top: 2em;\n\t\t\t\t\t\t}\n\n\t\t\t\t.row.gtr-150 {\n\t\t\t\t\tmargin-top: 0;\n\t\t\t\t\tmargin-left: -3em;\n\t\t\t\t}\n\n\t\t\t\t\t.row.gtr-150 > * {\n\t\t\t\t\t\tpadding: 0 0 0 3em;\n\t\t\t\t\t}\n\n\t\t\t\t\t.row.gtr-150.gtr-uniform {\n\t\t\t\t\t\tmargin-top: -3em;\n\t\t\t\t\t}\n\n\t\t\t\t\t\t.row.gtr-150.gtr-uniform > * {\n\t\t\t\t\t\t\tpadding-top: 3em;\n\t\t\t\t\t\t}\n\n\t\t\t\t.row.gtr-200 {\n\t\t\t\t\tmargin-top: 0;\n\t\t\t\t\tmargin-left: -4em;\n\t\t\t\t}\n\n\t\t\t\t\t.row.gtr-200 > * {\n\t\t\t\t\t\tpadding: 0 0 0 4em;\n\t\t\t\t\t}\n\n\t\t\t\t\t.row.gtr-200.gtr-uniform {\n\t\t\t\t\t\tmargin-top: -4em;\n\t\t\t\t\t}\n\n\t\t\t\t\t\t.row.gtr-200.gtr-uniform > * {\n\t\t\t\t\t\t\tpadding-top: 4em;\n\t\t\t\t\t\t}\n\n\t\t}\n\n\t\t@media screen and (max-width: 1280px) {\n\n\t\t\t.row {\n\t\t\t\tdisplay: flex;\n\t\t\t\tflex-wrap: wrap;\n\t\t\t\tbox-sizing: border-box;\n\t\t\t\talign-items: stretch;\n\t\t\t}\n\n\t\t\t\t.row > * {\n\t\t\t\t\tbox-sizing: border-box;\n\t\t\t\t}\n\n\t\t\t\t.row.gtr-uniform > * > :last-child {\n\t\t\t\t\tmargin-bottom: 0;\n\t\t\t\t}\n\n\t\t\t\t.row.aln-left {\n\t\t\t\t\tjustify-content: flex-start;\n\t\t\t\t}\n\n\t\t\t\t.row.aln-center {\n\t\t\t\t\tjustify-content: center;\n\t\t\t\t}\n\n\t\t\t\t.row.aln-right {\n\t\t\t\t\tjustify-content: flex-end;\n\t\t\t\t}\n\n\t\t\t\t.row.aln-top {\n\t\t\t\t\talign-items: flex-start;\n\t\t\t\t}\n\n\t\t\t\t.row.aln-middle {\n\t\t\t\t\talign-items: center;\n\t\t\t\t}\n\n\t\t\t\t.row.aln-bottom {\n\t\t\t\t\talign-items: flex-end;\n\t\t\t\t}\n\n\t\t\t\t.row > .imp-large {\n\t\t\t\t\torder: -1;\n\t\t\t\t}\n\n\t\t\t\t.row > .col-1-large {\n\t\t\t\t\twidth: 8.33333%;\n\t\t\t\t}\n\n\t\t\t\t.row > .off-1-large {\n\t\t\t\t\tmargin-left: 8.33333%;\n\t\t\t\t}\n\n\t\t\t\t.row > .col-2-large {\n\t\t\t\t\twidth: 16.66667%;\n\t\t\t\t}\n\n\t\t\t\t.row > .off-2-large {\n\t\t\t\t\tmargin-left: 16.66667%;\n\t\t\t\t}\n\n\t\t\t\t.row > .col-3-large {\n\t\t\t\t\twidth: 25%;\n\t\t\t\t}\n\n\t\t\t\t.row > .off-3-large {\n\t\t\t\t\tmargin-left: 25%;\n\t\t\t\t}\n\n\t\t\t\t.row > .col-4-large {\n\t\t\t\t\twidth: 33.33333%;\n\t\t\t\t}\n\n\t\t\t\t.row > .off-4-large {\n\t\t\t\t\tmargin-left: 33.33333%;\n\t\t\t\t}\n\n\t\t\t\t.row > .col-5-large {\n\t\t\t\t\twidth: 41.66667%;\n\t\t\t\t}\n\n\t\t\t\t.row > .off-5-large {\n\t\t\t\t\tmargin-left: 41.66667%;\n\t\t\t\t}\n\n\t\t\t\t.row > .col-6-large {\n\t\t\t\t\twidth: 50%;\n\t\t\t\t}\n\n\t\t\t\t.row > .off-6-large {\n\t\t\t\t\tmargin-left: 50%;\n\t\t\t\t}\n\n\t\t\t\t.row > .col-7-large {\n\t\t\t\t\twidth: 58.33333%;\n\t\t\t\t}\n\n\t\t\t\t.row > .off-7-large {\n\t\t\t\t\tmargin-left: 58.33333%;\n\t\t\t\t}\n\n\t\t\t\t.row > .col-8-large {\n\t\t\t\t\twidth: 66.66667%;\n\t\t\t\t}\n\n\t\t\t\t.row > .off-8-large {\n\t\t\t\t\tmargin-left: 66.66667%;\n\t\t\t\t}\n\n\t\t\t\t.row > .col-9-large {\n\t\t\t\t\twidth: 75%;\n\t\t\t\t}\n\n\t\t\t\t.row > .off-9-large {\n\t\t\t\t\tmargin-left: 75%;\n\t\t\t\t}\n\n\t\t\t\t.row > .col-10-large {\n\t\t\t\t\twidth: 83.33333%;\n\t\t\t\t}\n\n\t\t\t\t.row > .off-10-large {\n\t\t\t\t\tmargin-left: 83.33333%;\n\t\t\t\t}\n\n\t\t\t\t.row > .col-11-large {\n\t\t\t\t\twidth: 91.66667%;\n\t\t\t\t}\n\n\t\t\t\t.row > .off-11-large {\n\t\t\t\t\tmargin-left: 91.66667%;\n\t\t\t\t}\n\n\t\t\t\t.row > .col-12-large {\n\t\t\t\t\twidth: 100%;\n\t\t\t\t}\n\n\t\t\t\t.row > .off-12-large {\n\t\t\t\t\tmargin-left: 100%;\n\t\t\t\t}\n\n\t\t\t\t.row.gtr-0 {\n\t\t\t\t\tmargin-top: 0;\n\t\t\t\t\tmargin-left: 0em;\n\t\t\t\t}\n\n\t\t\t\t\t.row.gtr-0 > * {\n\t\t\t\t\t\tpadding: 0 0 0 0em;\n\t\t\t\t\t}\n\n\t\t\t\t\t.row.gtr-0.gtr-uniform {\n\t\t\t\t\t\tmargin-top: 0em;\n\t\t\t\t\t}\n\n\t\t\t\t\t\t.row.gtr-0.gtr-uniform > * {\n\t\t\t\t\t\t\tpadding-top: 0em;\n\t\t\t\t\t\t}\n\n\t\t\t\t.row.gtr-25 {\n\t\t\t\t\tmargin-top: 0;\n\t\t\t\t\tmargin-left: -0.375em;\n\t\t\t\t}\n\n\t\t\t\t\t.row.gtr-25 > * {\n\t\t\t\t\t\tpadding: 0 0 0 0.375em;\n\t\t\t\t\t}\n\n\t\t\t\t\t.row.gtr-25.gtr-uniform {\n\t\t\t\t\t\tmargin-top: -0.375em;\n\t\t\t\t\t}\n\n\t\t\t\t\t\t.row.gtr-25.gtr-uniform > * {\n\t\t\t\t\t\t\tpadding-top: 0.375em;\n\t\t\t\t\t\t}\n\n\t\t\t\t.row.gtr-50 {\n\t\t\t\t\tmargin-top: 0;\n\t\t\t\t\tmargin-left: -0.75em;\n\t\t\t\t}\n\n\t\t\t\t\t.row.gtr-50 > * {\n\t\t\t\t\t\tpadding: 0 0 0 0.75em;\n\t\t\t\t\t}\n\n\t\t\t\t\t.row.gtr-50.gtr-uniform {\n\t\t\t\t\t\tmargin-top: -0.75em;\n\t\t\t\t\t}\n\n\t\t\t\t\t\t.row.gtr-50.gtr-uniform > * {\n\t\t\t\t\t\t\tpadding-top: 0.75em;\n\t\t\t\t\t\t}\n\n\t\t\t\t.row {\n\t\t\t\t\tmargin-top: 0;\n\t\t\t\t\tmargin-left: -1.5em;\n\t\t\t\t}\n\n\t\t\t\t\t.row > * {\n\t\t\t\t\t\tpadding: 0 0 0 1.5em;\n\t\t\t\t\t}\n\n\t\t\t\t\t.row.gtr-uniform {\n\t\t\t\t\t\tmargin-top: -1.5em;\n\t\t\t\t\t}\n\n\t\t\t\t\t\t.row.gtr-uniform > * {\n\t\t\t\t\t\t\tpadding-top: 1.5em;\n\t\t\t\t\t\t}\n\n\t\t\t\t.row.gtr-150 {\n\t\t\t\t\tmargin-top: 0;\n\t\t\t\t\tmargin-left: -2.25em;\n\t\t\t\t}\n\n\t\t\t\t\t.row.gtr-150 > * {\n\t\t\t\t\t\tpadding: 0 0 0 2.25em;\n\t\t\t\t\t}\n\n\t\t\t\t\t.row.gtr-150.gtr-uniform {\n\t\t\t\t\t\tmargin-top: -2.25em;\n\t\t\t\t\t}\n\n\t\t\t\t\t\t.row.gtr-150.gtr-uniform > * {\n\t\t\t\t\t\t\tpadding-top: 2.25em;\n\t\t\t\t\t\t}\n\n\t\t\t\t.row.gtr-200 {\n\t\t\t\t\tmargin-top: 0;\n\t\t\t\t\tmargin-left: -3em;\n\t\t\t\t}\n\n\t\t\t\t\t.row.gtr-200 > * {\n\t\t\t\t\t\tpadding: 0 0 0 3em;\n\t\t\t\t\t}\n\n\t\t\t\t\t.row.gtr-200.gtr-uniform {\n\t\t\t\t\t\tmargin-top: -3em;\n\t\t\t\t\t}\n\n\t\t\t\t\t\t.row.gtr-200.gtr-uniform > * {\n\t\t\t\t\t\t\tpadding-top: 3em;\n\t\t\t\t\t\t}\n\n\t\t}\n\n\t\t@media screen and (max-width: 980px) {\n\n\t\t\t.row {\n\t\t\t\tdisplay: flex;\n\t\t\t\tflex-wrap: wrap;\n\t\t\t\tbox-sizing: border-box;\n\t\t\t\talign-items: stretch;\n\t\t\t}\n\n\t\t\t\t.row > * {\n\t\t\t\t\tbox-sizing: border-box;\n\t\t\t\t}\n\n\t\t\t\t.row.gtr-uniform > * > :last-child {\n\t\t\t\t\tmargin-bottom: 0;\n\t\t\t\t}\n\n\t\t\t\t.row.aln-left {\n\t\t\t\t\tjustify-content: flex-start;\n\t\t\t\t}\n\n\t\t\t\t.row.aln-center {\n\t\t\t\t\tjustify-content: center;\n\t\t\t\t}\n\n\t\t\t\t.row.aln-right {\n\t\t\t\t\tjustify-content: flex-end;\n\t\t\t\t}\n\n\t\t\t\t.row.aln-top {\n\t\t\t\t\talign-items: flex-start;\n\t\t\t\t}\n\n\t\t\t\t.row.aln-middle {\n\t\t\t\t\talign-items: center;\n\t\t\t\t}\n\n\t\t\t\t.row.aln-bottom {\n\t\t\t\t\talign-items: flex-end;\n\t\t\t\t}\n\n\t\t\t\t.row > .imp-medium {\n\t\t\t\t\torder: -1;\n\t\t\t\t}\n\n\t\t\t\t.row > .col-1-medium {\n\t\t\t\t\twidth: 8.33333%;\n\t\t\t\t}\n\n\t\t\t\t.row > .off-1-medium {\n\t\t\t\t\tmargin-left: 8.33333%;\n\t\t\t\t}\n\n\t\t\t\t.row > .col-2-medium {\n\t\t\t\t\twidth: 16.66667%;\n\t\t\t\t}\n\n\t\t\t\t.row > .off-2-medium {\n\t\t\t\t\tmargin-left: 16.66667%;\n\t\t\t\t}\n\n\t\t\t\t.row > .col-3-medium {\n\t\t\t\t\twidth: 25%;\n\t\t\t\t}\n\n\t\t\t\t.row > .off-3-medium {\n\t\t\t\t\tmargin-left: 25%;\n\t\t\t\t}\n\n\t\t\t\t.row > .col-4-medium {\n\t\t\t\t\twidth: 33.33333%;\n\t\t\t\t}\n\n\t\t\t\t.row > .off-4-medium {\n\t\t\t\t\tmargin-left: 33.33333%;\n\t\t\t\t}\n\n\t\t\t\t.row > .col-5-medium {\n\t\t\t\t\twidth: 41.66667%;\n\t\t\t\t}\n\n\t\t\t\t.row > .off-5-medium {\n\t\t\t\t\tmargin-left: 41.66667%;\n\t\t\t\t}\n\n\t\t\t\t.row > .col-6-medium {\n\t\t\t\t\twidth: 50%;\n\t\t\t\t}\n\n\t\t\t\t.row > .off-6-medium {\n\t\t\t\t\tmargin-left: 50%;\n\t\t\t\t}\n\n\t\t\t\t.row > .col-7-medium {\n\t\t\t\t\twidth: 58.33333%;\n\t\t\t\t}\n\n\t\t\t\t.row > .off-7-medium {\n\t\t\t\t\tmargin-left: 58.33333%;\n\t\t\t\t}\n\n\t\t\t\t.row > .col-8-medium {\n\t\t\t\t\twidth: 66.66667%;\n\t\t\t\t}\n\n\t\t\t\t.row > .off-8-medium {\n\t\t\t\t\tmargin-left: 66.66667%;\n\t\t\t\t}\n\n\t\t\t\t.row > .col-9-medium {\n\t\t\t\t\twidth: 75%;\n\t\t\t\t}\n\n\t\t\t\t.row > .off-9-medium {\n\t\t\t\t\tmargin-left: 75%;\n\t\t\t\t}\n\n\t\t\t\t.row > .col-10-medium {\n\t\t\t\t\twidth: 83.33333%;\n\t\t\t\t}\n\n\t\t\t\t.row > .off-10-medium {\n\t\t\t\t\tmargin-left: 83.33333%;\n\t\t\t\t}\n\n\t\t\t\t.row > .col-11-medium {\n\t\t\t\t\twidth: 91.66667%;\n\t\t\t\t}\n\n\t\t\t\t.row > .off-11-medium {\n\t\t\t\t\tmargin-left: 91.66667%;\n\t\t\t\t}\n\n\t\t\t\t.row > .col-12-medium {\n\t\t\t\t\twidth: 100%;\n\t\t\t\t}\n\n\t\t\t\t.row > .off-12-medium {\n\t\t\t\t\tmargin-left: 100%;\n\t\t\t\t}\n\n\t\t\t\t.row.gtr-0 {\n\t\t\t\t\tmargin-top: 0;\n\t\t\t\t\tmargin-left: 0em;\n\t\t\t\t}\n\n\t\t\t\t\t.row.gtr-0 > * {\n\t\t\t\t\t\tpadding: 0 0 0 0em;\n\t\t\t\t\t}\n\n\t\t\t\t\t.row.gtr-0.gtr-uniform {\n\t\t\t\t\t\tmargin-top: 0em;\n\t\t\t\t\t}\n\n\t\t\t\t\t\t.row.gtr-0.gtr-uniform > * {\n\t\t\t\t\t\t\tpadding-top: 0em;\n\t\t\t\t\t\t}\n\n\t\t\t\t.row.gtr-25 {\n\t\t\t\t\tmargin-top: 0;\n\t\t\t\t\tmargin-left: -0.375em;\n\t\t\t\t}\n\n\t\t\t\t\t.row.gtr-25 > * {\n\t\t\t\t\t\tpadding: 0 0 0 0.375em;\n\t\t\t\t\t}\n\n\t\t\t\t\t.row.gtr-25.gtr-uniform {\n\t\t\t\t\t\tmargin-top: -0.375em;\n\t\t\t\t\t}\n\n\t\t\t\t\t\t.row.gtr-25.gtr-uniform > * {\n\t\t\t\t\t\t\tpadding-top: 0.375em;\n\t\t\t\t\t\t}\n\n\t\t\t\t.row.gtr-50 {\n\t\t\t\t\tmargin-top: 0;\n\t\t\t\t\tmargin-left: -0.75em;\n\t\t\t\t}\n\n\t\t\t\t\t.row.gtr-50 > * {\n\t\t\t\t\t\tpadding: 0 0 0 0.75em;\n\t\t\t\t\t}\n\n\t\t\t\t\t.row.gtr-50.gtr-uniform {\n\t\t\t\t\t\tmargin-top: -0.75em;\n\t\t\t\t\t}\n\n\t\t\t\t\t\t.row.gtr-50.gtr-uniform > * {\n\t\t\t\t\t\t\tpadding-top: 0.75em;\n\t\t\t\t\t\t}\n\n\t\t\t\t.row {\n\t\t\t\t\tmargin-top: 0;\n\t\t\t\t\tmargin-left: -1.5em;\n\t\t\t\t}\n\n\t\t\t\t\t.row > * {\n\t\t\t\t\t\tpadding: 0 0 0 1.5em;\n\t\t\t\t\t}\n\n\t\t\t\t\t.row.gtr-uniform {\n\t\t\t\t\t\tmargin-top: -1.5em;\n\t\t\t\t\t}\n\n\t\t\t\t\t\t.row.gtr-uniform > * {\n\t\t\t\t\t\t\tpadding-top: 1.5em;\n\t\t\t\t\t\t}\n\n\t\t\t\t.row.gtr-150 {\n\t\t\t\t\tmargin-top: 0;\n\t\t\t\t\tmargin-left: -2.25em;\n\t\t\t\t}\n\n\t\t\t\t\t.row.gtr-150 > * {\n\t\t\t\t\t\tpadding: 0 0 0 2.25em;\n\t\t\t\t\t}\n\n\t\t\t\t\t.row.gtr-150.gtr-uniform {\n\t\t\t\t\t\tmargin-top: -2.25em;\n\t\t\t\t\t}\n\n\t\t\t\t\t\t.row.gtr-150.gtr-uniform > * {\n\t\t\t\t\t\t\tpadding-top: 2.25em;\n\t\t\t\t\t\t}\n\n\t\t\t\t.row.gtr-200 {\n\t\t\t\t\tmargin-top: 0;\n\t\t\t\t\tmargin-left: -3em;\n\t\t\t\t}\n\n\t\t\t\t\t.row.gtr-200 > * {\n\t\t\t\t\t\tpadding: 0 0 0 3em;\n\t\t\t\t\t}\n\n\t\t\t\t\t.row.gtr-200.gtr-uniform {\n\t\t\t\t\t\tmargin-top: -3em;\n\t\t\t\t\t}\n\n\t\t\t\t\t\t.row.gtr-200.gtr-uniform > * {\n\t\t\t\t\t\t\tpadding-top: 3em;\n\t\t\t\t\t\t}\n\n\t\t}\n\n\t\t@media screen and (max-width: 736px) {\n\n\t\t\t.row {\n\t\t\t\tdisplay: flex;\n\t\t\t\tflex-wrap: wrap;\n\t\t\t\tbox-sizing: border-box;\n\t\t\t\talign-items: stretch;\n\t\t\t}\n\n\t\t\t\t.row > * {\n\t\t\t\t\tbox-sizing: border-box;\n\t\t\t\t}\n\n\t\t\t\t.row.gtr-uniform > * > :last-child {\n\t\t\t\t\tmargin-bottom: 0;\n\t\t\t\t}\n\n\t\t\t\t.row.aln-left {\n\t\t\t\t\tjustify-content: flex-start;\n\t\t\t\t}\n\n\t\t\t\t.row.aln-center {\n\t\t\t\t\tjustify-content: center;\n\t\t\t\t}\n\n\t\t\t\t.row.aln-right {\n\t\t\t\t\tjustify-content: flex-end;\n\t\t\t\t}\n\n\t\t\t\t.row.aln-top {\n\t\t\t\t\talign-items: flex-start;\n\t\t\t\t}\n\n\t\t\t\t.row.aln-middle {\n\t\t\t\t\talign-items: center;\n\t\t\t\t}\n\n\t\t\t\t.row.aln-bottom {\n\t\t\t\t\talign-items: flex-end;\n\t\t\t\t}\n\n\t\t\t\t.row > .imp-small {\n\t\t\t\t\torder: -1;\n\t\t\t\t}\n\n\t\t\t\t.row > .col-1-small {\n\t\t\t\t\twidth: 8.33333%;\n\t\t\t\t}\n\n\t\t\t\t.row > .off-1-small {\n\t\t\t\t\tmargin-left: 8.33333%;\n\t\t\t\t}\n\n\t\t\t\t.row > .col-2-small {\n\t\t\t\t\twidth: 16.66667%;\n\t\t\t\t}\n\n\t\t\t\t.row > .off-2-small {\n\t\t\t\t\tmargin-left: 16.66667%;\n\t\t\t\t}\n\n\t\t\t\t.row > .col-3-small {\n\t\t\t\t\twidth: 25%;\n\t\t\t\t}\n\n\t\t\t\t.row > .off-3-small {\n\t\t\t\t\tmargin-left: 25%;\n\t\t\t\t}\n\n\t\t\t\t.row > .col-4-small {\n\t\t\t\t\twidth: 33.33333%;\n\t\t\t\t}\n\n\t\t\t\t.row > .off-4-small {\n\t\t\t\t\tmargin-left: 33.33333%;\n\t\t\t\t}\n\n\t\t\t\t.row > .col-5-small {\n\t\t\t\t\twidth: 41.66667%;\n\t\t\t\t}\n\n\t\t\t\t.row > .off-5-small {\n\t\t\t\t\tmargin-left: 41.66667%;\n\t\t\t\t}\n\n\t\t\t\t.row > .col-6-small {\n\t\t\t\t\twidth: 50%;\n\t\t\t\t}\n\n\t\t\t\t.row > .off-6-small {\n\t\t\t\t\tmargin-left: 50%;\n\t\t\t\t}\n\n\t\t\t\t.row > .col-7-small {\n\t\t\t\t\twidth: 58.33333%;\n\t\t\t\t}\n\n\t\t\t\t.row > .off-7-small {\n\t\t\t\t\tmargin-left: 58.33333%;\n\t\t\t\t}\n\n\t\t\t\t.row > .col-8-small {\n\t\t\t\t\twidth: 66.66667%;\n\t\t\t\t}\n\n\t\t\t\t.row > .off-8-small {\n\t\t\t\t\tmargin-left: 66.66667%;\n\t\t\t\t}\n\n\t\t\t\t.row > .col-9-small {\n\t\t\t\t\twidth: 75%;\n\t\t\t\t}\n\n\t\t\t\t.row > .off-9-small {\n\t\t\t\t\tmargin-left: 75%;\n\t\t\t\t}\n\n\t\t\t\t.row > .col-10-small {\n\t\t\t\t\twidth: 83.33333%;\n\t\t\t\t}\n\n\t\t\t\t.row > .off-10-small {\n\t\t\t\t\tmargin-left: 83.33333%;\n\t\t\t\t}\n\n\t\t\t\t.row > .col-11-small {\n\t\t\t\t\twidth: 91.66667%;\n\t\t\t\t}\n\n\t\t\t\t.row > .off-11-small {\n\t\t\t\t\tmargin-left: 91.66667%;\n\t\t\t\t}\n\n\t\t\t\t.row > .col-12-small {\n\t\t\t\t\twidth: 100%;\n\t\t\t\t}\n\n\t\t\t\t.row > .off-12-small {\n\t\t\t\t\tmargin-left: 100%;\n\t\t\t\t}\n\n\t\t\t\t.row.gtr-0 {\n\t\t\t\t\tmargin-top: 0;\n\t\t\t\t\tmargin-left: 0em;\n\t\t\t\t}\n\n\t\t\t\t\t.row.gtr-0 > * {\n\t\t\t\t\t\tpadding: 0 0 0 0em;\n\t\t\t\t\t}\n\n\t\t\t\t\t.row.gtr-0.gtr-uniform {\n\t\t\t\t\t\tmargin-top: 0em;\n\t\t\t\t\t}\n\n\t\t\t\t\t\t.row.gtr-0.gtr-uniform > * {\n\t\t\t\t\t\t\tpadding-top: 0em;\n\t\t\t\t\t\t}\n\n\t\t\t\t.row.gtr-25 {\n\t\t\t\t\tmargin-top: 0;\n\t\t\t\t\tmargin-left: -0.3125em;\n\t\t\t\t}\n\n\t\t\t\t\t.row.gtr-25 > * {\n\t\t\t\t\t\tpadding: 0 0 0 0.3125em;\n\t\t\t\t\t}\n\n\t\t\t\t\t.row.gtr-25.gtr-uniform {\n\t\t\t\t\t\tmargin-top: -0.3125em;\n\t\t\t\t\t}\n\n\t\t\t\t\t\t.row.gtr-25.gtr-uniform > * {\n\t\t\t\t\t\t\tpadding-top: 0.3125em;\n\t\t\t\t\t\t}\n\n\t\t\t\t.row.gtr-50 {\n\t\t\t\t\tmargin-top: 0;\n\t\t\t\t\tmargin-left: -0.625em;\n\t\t\t\t}\n\n\t\t\t\t\t.row.gtr-50 > * {\n\t\t\t\t\t\tpadding: 0 0 0 0.625em;\n\t\t\t\t\t}\n\n\t\t\t\t\t.row.gtr-50.gtr-uniform {\n\t\t\t\t\t\tmargin-top: -0.625em;\n\t\t\t\t\t}\n\n\t\t\t\t\t\t.row.gtr-50.gtr-uniform > * {\n\t\t\t\t\t\t\tpadding-top: 0.625em;\n\t\t\t\t\t\t}\n\n\t\t\t\t.row {\n\t\t\t\t\tmargin-top: 0;\n\t\t\t\t\tmargin-left: -1.25em;\n\t\t\t\t}\n\n\t\t\t\t\t.row > * {\n\t\t\t\t\t\tpadding: 0 0 0 1.25em;\n\t\t\t\t\t}\n\n\t\t\t\t\t.row.gtr-uniform {\n\t\t\t\t\t\tmargin-top: -1.25em;\n\t\t\t\t\t}\n\n\t\t\t\t\t\t.row.gtr-uniform > * {\n\t\t\t\t\t\t\tpadding-top: 1.25em;\n\t\t\t\t\t\t}\n\n\t\t\t\t.row.gtr-150 {\n\t\t\t\t\tmargin-top: 0;\n\t\t\t\t\tmargin-left: -1.875em;\n\t\t\t\t}\n\n\t\t\t\t\t.row.gtr-150 > * {\n\t\t\t\t\t\tpadding: 0 0 0 1.875em;\n\t\t\t\t\t}\n\n\t\t\t\t\t.row.gtr-150.gtr-uniform {\n\t\t\t\t\t\tmargin-top: -1.875em;\n\t\t\t\t\t}\n\n\t\t\t\t\t\t.row.gtr-150.gtr-uniform > * {\n\t\t\t\t\t\t\tpadding-top: 1.875em;\n\t\t\t\t\t\t}\n\n\t\t\t\t.row.gtr-200 {\n\t\t\t\t\tmargin-top: 0;\n\t\t\t\t\tmargin-left: -2.5em;\n\t\t\t\t}\n\n\t\t\t\t\t.row.gtr-200 > * {\n\t\t\t\t\t\tpadding: 0 0 0 2.5em;\n\t\t\t\t\t}\n\n\t\t\t\t\t.row.gtr-200.gtr-uniform {\n\t\t\t\t\t\tmargin-top: -2.5em;\n\t\t\t\t\t}\n\n\t\t\t\t\t\t.row.gtr-200.gtr-uniform > * {\n\t\t\t\t\t\t\tpadding-top: 2.5em;\n\t\t\t\t\t\t}\n\n\t\t}\n\n\t\t@media screen and (max-width: 480px) {\n\n\t\t\t.row {\n\t\t\t\tdisplay: flex;\n\t\t\t\tflex-wrap: wrap;\n\t\t\t\tbox-sizing: border-box;\n\t\t\t\talign-items: stretch;\n\t\t\t}\n\n\t\t\t\t.row > * {\n\t\t\t\t\tbox-sizing: border-box;\n\t\t\t\t}\n\n\t\t\t\t.row.gtr-uniform > * > :last-child {\n\t\t\t\t\tmargin-bottom: 0;\n\t\t\t\t}\n\n\t\t\t\t.row.aln-left {\n\t\t\t\t\tjustify-content: flex-start;\n\t\t\t\t}\n\n\t\t\t\t.row.aln-center {\n\t\t\t\t\tjustify-content: center;\n\t\t\t\t}\n\n\t\t\t\t.row.aln-right {\n\t\t\t\t\tjustify-content: flex-end;\n\t\t\t\t}\n\n\t\t\t\t.row.aln-top {\n\t\t\t\t\talign-items: flex-start;\n\t\t\t\t}\n\n\t\t\t\t.row.aln-middle {\n\t\t\t\t\talign-items: center;\n\t\t\t\t}\n\n\t\t\t\t.row.aln-bottom {\n\t\t\t\t\talign-items: flex-end;\n\t\t\t\t}\n\n\t\t\t\t.row > .imp-xsmall {\n\t\t\t\t\torder: -1;\n\t\t\t\t}\n\n\t\t\t\t.row > .col-1-xsmall {\n\t\t\t\t\twidth: 8.33333%;\n\t\t\t\t}\n\n\t\t\t\t.row > .off-1-xsmall {\n\t\t\t\t\tmargin-left: 8.33333%;\n\t\t\t\t}\n\n\t\t\t\t.row > .col-2-xsmall {\n\t\t\t\t\twidth: 16.66667%;\n\t\t\t\t}\n\n\t\t\t\t.row > .off-2-xsmall {\n\t\t\t\t\tmargin-left: 16.66667%;\n\t\t\t\t}\n\n\t\t\t\t.row > .col-3-xsmall {\n\t\t\t\t\twidth: 25%;\n\t\t\t\t}\n\n\t\t\t\t.row > .off-3-xsmall {\n\t\t\t\t\tmargin-left: 25%;\n\t\t\t\t}\n\n\t\t\t\t.row > .col-4-xsmall {\n\t\t\t\t\twidth: 33.33333%;\n\t\t\t\t}\n\n\t\t\t\t.row > .off-4-xsmall {\n\t\t\t\t\tmargin-left: 33.33333%;\n\t\t\t\t}\n\n\t\t\t\t.row > .col-5-xsmall {\n\t\t\t\t\twidth: 41.66667%;\n\t\t\t\t}\n\n\t\t\t\t.row > .off-5-xsmall {\n\t\t\t\t\tmargin-left: 41.66667%;\n\t\t\t\t}\n\n\t\t\t\t.row > .col-6-xsmall {\n\t\t\t\t\twidth: 50%;\n\t\t\t\t}\n\n\t\t\t\t.row > .off-6-xsmall {\n\t\t\t\t\tmargin-left: 50%;\n\t\t\t\t}\n\n\t\t\t\t.row > .col-7-xsmall {\n\t\t\t\t\twidth: 58.33333%;\n\t\t\t\t}\n\n\t\t\t\t.row > .off-7-xsmall {\n\t\t\t\t\tmargin-left: 58.33333%;\n\t\t\t\t}\n\n\t\t\t\t.row > .col-8-xsmall {\n\t\t\t\t\twidth: 66.66667%;\n\t\t\t\t}\n\n\t\t\t\t.row > .off-8-xsmall {\n\t\t\t\t\tmargin-left: 66.66667%;\n\t\t\t\t}\n\n\t\t\t\t.row > .col-9-xsmall {\n\t\t\t\t\twidth: 75%;\n\t\t\t\t}\n\n\t\t\t\t.row > .off-9-xsmall {\n\t\t\t\t\tmargin-left: 75%;\n\t\t\t\t}\n\n\t\t\t\t.row > .col-10-xsmall {\n\t\t\t\t\twidth: 83.33333%;\n\t\t\t\t}\n\n\t\t\t\t.row > .off-10-xsmall {\n\t\t\t\t\tmargin-left: 83.33333%;\n\t\t\t\t}\n\n\t\t\t\t.row > .col-11-xsmall {\n\t\t\t\t\twidth: 91.66667%;\n\t\t\t\t}\n\n\t\t\t\t.row > .off-11-xsmall {\n\t\t\t\t\tmargin-left: 91.66667%;\n\t\t\t\t}\n\n\t\t\t\t.row > .col-12-xsmall {\n\t\t\t\t\twidth: 100%;\n\t\t\t\t}\n\n\t\t\t\t.row > .off-12-xsmall {\n\t\t\t\t\tmargin-left: 100%;\n\t\t\t\t}\n\n\t\t\t\t.row.gtr-0 {\n\t\t\t\t\tmargin-top: 0;\n\t\t\t\t\tmargin-left: 0em;\n\t\t\t\t}\n\n\t\t\t\t\t.row.gtr-0 > * {\n\t\t\t\t\t\tpadding: 0 0 0 0em;\n\t\t\t\t\t}\n\n\t\t\t\t\t.row.gtr-0.gtr-uniform {\n\t\t\t\t\t\tmargin-top: 0em;\n\t\t\t\t\t}\n\n\t\t\t\t\t\t.row.gtr-0.gtr-uniform > * {\n\t\t\t\t\t\t\tpadding-top: 0em;\n\t\t\t\t\t\t}\n\n\t\t\t\t.row.gtr-25 {\n\t\t\t\t\tmargin-top: 0;\n\t\t\t\t\tmargin-left: -0.3125em;\n\t\t\t\t}\n\n\t\t\t\t\t.row.gtr-25 > * {\n\t\t\t\t\t\tpadding: 0 0 0 0.3125em;\n\t\t\t\t\t}\n\n\t\t\t\t\t.row.gtr-25.gtr-uniform {\n\t\t\t\t\t\tmargin-top: -0.3125em;\n\t\t\t\t\t}\n\n\t\t\t\t\t\t.row.gtr-25.gtr-uniform > * {\n\t\t\t\t\t\t\tpadding-top: 0.3125em;\n\t\t\t\t\t\t}\n\n\t\t\t\t.row.gtr-50 {\n\t\t\t\t\tmargin-top: 0;\n\t\t\t\t\tmargin-left: -0.625em;\n\t\t\t\t}\n\n\t\t\t\t\t.row.gtr-50 > * {\n\t\t\t\t\t\tpadding: 0 0 0 0.625em;\n\t\t\t\t\t}\n\n\t\t\t\t\t.row.gtr-50.gtr-uniform {\n\t\t\t\t\t\tmargin-top: -0.625em;\n\t\t\t\t\t}\n\n\t\t\t\t\t\t.row.gtr-50.gtr-uniform > * {\n\t\t\t\t\t\t\tpadding-top: 0.625em;\n\t\t\t\t\t\t}\n\n\t\t\t\t.row {\n\t\t\t\t\tmargin-top: 0;\n\t\t\t\t\tmargin-left: -1.25em;\n\t\t\t\t}\n\n\t\t\t\t\t.row > * {\n\t\t\t\t\t\tpadding: 0 0 0 1.25em;\n\t\t\t\t\t}\n\n\t\t\t\t\t.row.gtr-uniform {\n\t\t\t\t\t\tmargin-top: -1.25em;\n\t\t\t\t\t}\n\n\t\t\t\t\t\t.row.gtr-uniform > * {\n\t\t\t\t\t\t\tpadding-top: 1.25em;\n\t\t\t\t\t\t}\n\n\t\t\t\t.row.gtr-150 {\n\t\t\t\t\tmargin-top: 0;\n\t\t\t\t\tmargin-left: -1.875em;\n\t\t\t\t}\n\n\t\t\t\t\t.row.gtr-150 > * {\n\t\t\t\t\t\tpadding: 0 0 0 1.875em;\n\t\t\t\t\t}\n\n\t\t\t\t\t.row.gtr-150.gtr-uniform {\n\t\t\t\t\t\tmargin-top: -1.875em;\n\t\t\t\t\t}\n\n\t\t\t\t\t\t.row.gtr-150.gtr-uniform > * {\n\t\t\t\t\t\t\tpadding-top: 1.875em;\n\t\t\t\t\t\t}\n\n\t\t\t\t.row.gtr-200 {\n\t\t\t\t\tmargin-top: 0;\n\t\t\t\t\tmargin-left: -2.5em;\n\t\t\t\t}\n\n\t\t\t\t\t.row.gtr-200 > * {\n\t\t\t\t\t\tpadding: 0 0 0 2.5em;\n\t\t\t\t\t}\n\n\t\t\t\t\t.row.gtr-200.gtr-uniform {\n\t\t\t\t\t\tmargin-top: -2.5em;\n\t\t\t\t\t}\n\n\t\t\t\t\t\t.row.gtr-200.gtr-uniform > * {\n\t\t\t\t\t\t\tpadding-top: 2.5em;\n\t\t\t\t\t\t}\n\n\t\t}\n\n/* Section/Article */\n\n\tsection.special, article.special {\n\t\ttext-align: center;\n\t}\n\n\theader.major {\n\t\twidth: -moz-max-content;\n\t\twidth: -webkit-max-content;\n\t\twidth: -ms-max-content;\n\t\twidth: max-content;\n\t\tmargin-bottom: 2em;\n\t}\n\n\t\theader.major > :first-child {\n\t\t\tmargin-bottom: 0;\n\t\t\twidth: calc(100% + 0.5em);\n\t\t}\n\n\t\t\theader.major > :first-child:after {\n\t\t\t\tcontent: '';\n\t\t\t\tbackground-color: #ffffff;\n\t\t\t\tdisplay: block;\n\t\t\t\theight: 2px;\n\t\t\t\tmargin: 0.325em 0 0.5em 0;\n\t\t\t\twidth: 100%;\n\t\t\t}\n\n\t\theader.major > p {\n\t\t\tfont-size: 0.7em;\n\t\t\tfont-weight: 600;\n\t\t\tletter-spacing: 0.25em;\n\t\t\tmargin-bottom: 0;\n\t\t\ttext-transform: uppercase;\n\t\t}\n\n\t\tbody.is-ie header.major > :first-child:after {\n\t\t\tmax-width: 9em;\n\t\t}\n\n\t\tbody.is-ie header.major > h1:after {\n\t\t\tmax-width: 100% !important;\n\t\t}\n\n\t@media screen and (max-width: 736px) {\n\n\t\theader.major > p br {\n\t\t\tdisplay: none;\n\t\t}\n\n\t}\n\n/* Form */\n\n\tform {\n\t\tmargin: 0 0 2em 0;\n\t}\n\n\t\tform > :last-child {\n\t\t\tmargin-bottom: 0;\n\t\t}\n\n\t\tform > .fields {\n\t\t\tdisplay: -moz-flex;\n\t\t\tdisplay: -webkit-flex;\n\t\t\tdisplay: -ms-flex;\n\t\t\tdisplay: flex;\n\t\t\t-moz-flex-wrap: wrap;\n\t\t\t-webkit-flex-wrap: wrap;\n\t\t\t-ms-flex-wrap: wrap;\n\t\t\tflex-wrap: wrap;\n\t\t\twidth: calc(100% + 3em);\n\t\t\tmargin: -1.5em 0 2em -1.5em;\n\t\t}\n\n\t\t\tform > .fields > .field {\n\t\t\t\t-moz-flex-grow: 0;\n\t\t\t\t-webkit-flex-grow: 0;\n\t\t\t\t-ms-flex-grow: 0;\n\t\t\t\tflex-grow: 0;\n\t\t\t\t-moz-flex-shrink: 0;\n\t\t\t\t-webkit-flex-shrink: 0;\n\t\t\t\t-ms-flex-shrink: 0;\n\t\t\t\tflex-shrink: 0;\n\t\t\t\tpadding: 1.5em 0 0 1.5em;\n\t\t\t\twidth: calc(100% - 1.5em);\n\t\t\t}\n\n\t\t\t\tform > .fields > .field.half {\n\t\t\t\t\twidth: calc(50% - 0.75em);\n\t\t\t\t}\n\n\t\t\t\tform > .fields > .field.third {\n\t\t\t\t\twidth: calc(100%/3 - 0.5em);\n\t\t\t\t}\n\n\t\t\t\tform > .fields > .field.quarter {\n\t\t\t\t\twidth: calc(25% - 0.375em);\n\t\t\t\t}\n\n\t\t@media screen and (max-width: 480px) {\n\n\t\t\tform > .fields {\n\t\t\t\twidth: calc(100% + 3em);\n\t\t\t\tmargin: -1.5em 0 2em -1.5em;\n\t\t\t}\n\n\t\t\t\tform > .fields > .field {\n\t\t\t\t\tpadding: 1.5em 0 0 1.5em;\n\t\t\t\t\twidth: calc(100% - 1.5em);\n\t\t\t\t}\n\n\t\t\t\t\tform > .fields > .field.half {\n\t\t\t\t\t\twidth: calc(100% - 1.5em);\n\t\t\t\t\t}\n\n\t\t\t\t\tform > .fields > .field.third {\n\t\t\t\t\t\twidth: calc(100% - 1.5em);\n\t\t\t\t\t}\n\n\t\t\t\t\tform > .fields > .field.quarter {\n\t\t\t\t\t\twidth: calc(100% - 1.5em);\n\t\t\t\t\t}\n\n\t\t}\n\n\tlabel {\n\t\tcolor: #ffffff;\n\t\tdisplay: block;\n\t\tfont-size: 0.8em;\n\t\tfont-weight: 600;\n\t\tletter-spacing: 0.25em;\n\t\tmargin: 0 0 1em 0;\n\t\ttext-transform: uppercase;\n\t}\n\n\tinput[type=\"text\"],\n\tinput[type=\"password\"],\n\tinput[type=\"email\"],\n\tinput[type=\"tel\"],\n\tinput[type=\"search\"],\n\tinput[type=\"url\"],\n\tselect,\n\ttextarea {\n\t\t-moz-appearance: none;\n\t\t-webkit-appearance: none;\n\t\t-ms-appearance: none;\n\t\tappearance: none;\n\t\tbackground: rgba(212, 212, 255, 0.035);\n\t\tborder: none;\n\t\tborder-radius: 0;\n\t\tcolor: inherit;\n\t\tdisplay: block;\n\t\toutline: 0;\n\t\tpadding: 0 1em;\n\t\ttext-decoration: none;\n\t\twidth: 100%;\n\t}\n\n\t\tinput[type=\"text\"]:invalid,\n\t\tinput[type=\"password\"]:invalid,\n\t\tinput[type=\"email\"]:invalid,\n\t\tinput[type=\"tel\"]:invalid,\n\t\tinput[type=\"search\"]:invalid,\n\t\tinput[type=\"url\"]:invalid,\n\t\tselect:invalid,\n\t\ttextarea:invalid {\n\t\t\tbox-shadow: none;\n\t\t}\n\n\t\tinput[type=\"text\"]:focus,\n\t\tinput[type=\"password\"]:focus,\n\t\tinput[type=\"email\"]:focus,\n\t\tinput[type=\"tel\"]:focus,\n\t\tinput[type=\"search\"]:focus,\n\t\tinput[type=\"url\"]:focus,\n\t\tselect:focus,\n\t\ttextarea:focus {\n\t\t\tborder-color: #9bf1ff;\n\t\t\tbox-shadow: 0 0 0 2px #9bf1ff;\n\t\t}\n\n\tselect {\n\t\tbackground-image: url(\"data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='40' height='40' preserveAspectRatio='none' viewBox='0 0 40 40'%3E%3Cpath d='M9.4,12.3l10.4,10.4l10.4-10.4c0.2-0.2,0.5-0.4,0.9-0.4c0.3,0,0.6,0.1,0.9,0.4l3.3,3.3c0.2,0.2,0.4,0.5,0.4,0.9 c0,0.4-0.1,0.6-0.4,0.9L20.7,31.9c-0.2,0.2-0.5,0.4-0.9,0.4c-0.3,0-0.6-0.1-0.9-0.4L4.3,17.3c-0.2-0.2-0.4-0.5-0.4-0.9 c0-0.4,0.1-0.6,0.4-0.9l3.3-3.3c0.2-0.2,0.5-0.4,0.9-0.4S9.1,12.1,9.4,12.3z' fill='rgba(212, 212, 255, 0.1)' /%3E%3C/svg%3E\");\n\t\tbackground-size: 1.25rem;\n\t\tbackground-repeat: no-repeat;\n\t\tbackground-position: calc(100% - 1rem) center;\n\t\theight: 2.75em;\n\t\tpadding-right: 2.75em;\n\t\ttext-overflow: ellipsis;\n\t}\n\n\t\tselect option {\n\t\t\tcolor: #ffffff;\n\t\t\tbackground: #242943;\n\t\t}\n\n\t\tselect:focus::-ms-value {\n\t\t\tbackground-color: transparent;\n\t\t}\n\n\t\tselect::-ms-expand {\n\t\t\tdisplay: none;\n\t\t}\n\n\tinput[type=\"text\"],\n\tinput[type=\"password\"],\n\tinput[type=\"email\"],\n\tinput[type=\"tel\"],\n\tinput[type=\"search\"],\n\tinput[type=\"url\"],\n\tselect {\n\t\theight: 2.75em;\n\t}\n\n\ttextarea {\n\t\tpadding: 0.75em 1em;\n\t}\n\n\tinput[type=\"checkbox\"],\n\tinput[type=\"radio\"] {\n\t\t-moz-appearance: none;\n\t\t-webkit-appearance: none;\n\t\t-ms-appearance: none;\n\t\tappearance: none;\n\t\tdisplay: block;\n\t\tfloat: left;\n\t\tmargin-right: -2em;\n\t\topacity: 0;\n\t\twidth: 1em;\n\t\tz-index: -1;\n\t}\n\n\t\tinput[type=\"checkbox\"] + label,\n\t\tinput[type=\"radio\"] + label {\n\t\t\ttext-decoration: none;\n\t\t\tcolor: #ffffff;\n\t\t\tcursor: pointer;\n\t\t\tdisplay: inline-block;\n\t\t\tfont-weight: 300;\n\t\t\tpadding-left: 2.65em;\n\t\t\tpadding-right: 0.75em;\n\t\t\tposition: relative;\n\t\t}\n\n\t\t\tinput[type=\"checkbox\"] + label:before,\n\t\t\tinput[type=\"radio\"] + label:before {\n\t\t\t\t-moz-osx-font-smoothing: grayscale;\n\t\t\t\t-webkit-font-smoothing: antialiased;\n\t\t\t\tdisplay: inline-block;\n\t\t\t\tfont-style: normal;\n\t\t\t\tfont-variant: normal;\n\t\t\t\ttext-rendering: auto;\n\t\t\t\tline-height: 1;\n\t\t\t\ttext-transform: none !important;\n\t\t\t\tfont-family: 'Font Awesome 5 Free';\n\t\t\t\tfont-weight: 900;\n\t\t\t}\n\n\t\t\tinput[type=\"checkbox\"] + label:before,\n\t\t\tinput[type=\"radio\"] + label:before {\n\t\t\t\tbackground: rgba(212, 212, 255, 0.035);\n\t\t\t\tcontent: '';\n\t\t\t\tdisplay: inline-block;\n\t\t\t\tfont-size: 0.8em;\n\t\t\t\theight: 2.0625em;\n\t\t\t\tleft: 0;\n\t\t\t\tletter-spacing: 0;\n\t\t\t\tline-height: 2.0625em;\n\t\t\t\tposition: absolute;\n\t\t\t\ttext-align: center;\n\t\t\t\ttop: 0;\n\t\t\t\twidth: 2.0625em;\n\t\t\t}\n\n\t\tinput[type=\"checkbox\"]:checked + label:before,\n\t\tinput[type=\"radio\"]:checked + label:before {\n\t\t\tbackground: #ffffff;\n\t\t\tborder-color: #9bf1ff;\n\t\t\tcontent: '\\f00c';\n\t\t\tcolor: #242943;\n\t\t}\n\n\t\tinput[type=\"checkbox\"]:focus + label:before,\n\t\tinput[type=\"radio\"]:focus + label:before {\n\t\t\tbox-shadow: 0 0 0 2px #9bf1ff;\n\t\t}\n\n\tinput[type=\"radio\"] + label:before {\n\t\tborder-radius: 100%;\n\t}\n\n\t::-webkit-input-placeholder {\n\t\tcolor: rgba(244, 244, 255, 0.2) !important;\n\t\topacity: 1.0;\n\t}\n\n\t:-moz-placeholder {\n\t\tcolor: rgba(244, 244, 255, 0.2) !important;\n\t\topacity: 1.0;\n\t}\n\n\t::-moz-placeholder {\n\t\tcolor: rgba(244, 244, 255, 0.2) !important;\n\t\topacity: 1.0;\n\t}\n\n\t:-ms-input-placeholder {\n\t\tcolor: rgba(244, 244, 255, 0.2) !important;\n\t\topacity: 1.0;\n\t}\n\n/* Box */\n\n\t.box {\n\t\tborder: solid 1px rgba(212, 212, 255, 0.1);\n\t\tmargin-bottom: 2em;\n\t\tpadding: 1.5em;\n\t}\n\n\t\t.box > :last-child,\n\t\t.box > :last-child > :last-child,\n\t\t.box > :last-child > :last-child > :last-child {\n\t\t\tmargin-bottom: 0;\n\t\t}\n\n\t\t.box.alt {\n\t\t\tborder: 0;\n\t\t\tborder-radius: 0;\n\t\t\tpadding: 0;\n\t\t}\n\n/* Icon */\n\n\t.icon {\n\t\ttext-decoration: none;\n\t\tborder-bottom: none;\n\t\tposition: relative;\n\t}\n\n\t\t.icon:before {\n\t\t\t-moz-osx-font-smoothing: grayscale;\n\t\t\t-webkit-font-smoothing: antialiased;\n\t\t\tdisplay: inline-block;\n\t\t\tfont-style: normal;\n\t\t\tfont-variant: normal;\n\t\t\ttext-rendering: auto;\n\t\t\tline-height: 1;\n\t\t\ttext-transform: none !important;\n\t\t\tfont-family: 'Font Awesome 5 Free';\n\t\t\tfont-weight: 400;\n\t\t}\n\n\t\t.icon > .label {\n\t\t\tdisplay: none;\n\t\t}\n\n\t\t.icon:before {\n\t\t\tline-height: inherit;\n\t\t}\n\n\t\t.icon.solid:before {\n\t\t\tfont-weight: 900;\n\t\t}\n\n\t\t.icon.brands:before {\n\t\t\tfont-family: 'Font Awesome 5 Brands';\n\t\t}\n\n\t\t.icon.alt:before {\n\t\t\tbackground-color: #ffffff;\n\t\t\tborder-radius: 100%;\n\t\t\tcolor: #242943;\n\t\t\tdisplay: inline-block;\n\t\t\theight: 2em;\n\t\t\tline-height: 2em;\n\t\t\ttext-align: center;\n\t\t\twidth: 2em;\n\t\t}\n\n\ta.icon.alt:before {\n\t\t-moz-transition: background-color 0.2s ease-in-out;\n\t\t-webkit-transition: background-color 0.2s ease-in-out;\n\t\t-ms-transition: background-color 0.2s ease-in-out;\n\t\ttransition: background-color 0.2s ease-in-out;\n\t}\n\n\ta.icon.alt:hover:before {\n\t\tbackground-color: #6fc3df;\n\t}\n\n\ta.icon.alt:active:before {\n\t\tbackground-color: #37a6cb;\n\t}\n\n/* Image */\n\n\t.image {\n\t\tborder: 0;\n\t\tdisplay: inline-block;\n\t\tposition: relative;\n\t}\n\n\t\t.image img {\n\t\t\tdisplay: block;\n\t\t}\n\n\t\t.image.left, .image.right {\n\t\t\tmax-width: 30%;\n\t\t}\n\n\t\t\t.image.left img, .image.right img {\n\t\t\t\twidth: 100%;\n\t\t\t}\n\n\t\t.image.left {\n\t\t\tfloat: left;\n\t\t\tmargin: 0 1.5em 1.25em 0;\n\t\t\ttop: 0.25em;\n\t\t}\n\n\t\t.image.right {\n\t\t\tfloat: right;\n\t\t\tmargin: 0 0 1.25em 1.5em;\n\t\t\ttop: 0.25em;\n\t\t}\n\n\t\t.image.fit {\n\t\t\tdisplay: block;\n\t\t\tmargin: 0 0 2em 0;\n\t\t\twidth: 100%;\n\t\t}\n\n\t\t\t.image.fit img {\n\t\t\t\twidth: 100%;\n\t\t\t}\n\n\t\t.image.main {\n\t\t\tdisplay: block;\n\t\t\tmargin: 2.5em 0;\n\t\t\twidth: 100%;\n\t\t}\n\n\t\t\t.image.main img {\n\t\t\t\twidth: 100%;\n\t\t\t}\n\n\t\t\t@media screen and (max-width: 736px) {\n\n\t\t\t\t.image.main {\n\t\t\t\t\tmargin: 1.5em 0;\n\t\t\t\t}\n\n\t\t\t}\n\n/* List */\n\n\tol {\n\t\tlist-style: decimal;\n\t\tmargin: 0 0 2em 0;\n\t\tpadding-left: 1.25em;\n\t}\n\n\t\tol li {\n\t\t\tpadding-left: 0.25em;\n\t\t}\n\n\tul {\n\t\tlist-style: disc;\n\t\tmargin: 0 0 2em 0;\n\t\tpadding-left: 1em;\n\t}\n\n\t\tul li {\n\t\t\tpadding-left: 0.5em;\n\t\t}\n\n\t\tul.alt {\n\t\t\tlist-style: none;\n\t\t\tpadding-left: 0;\n\t\t}\n\n\t\t\tul.alt li {\n\t\t\t\tborder-top: solid 1px rgba(212, 212, 255, 0.1);\n\t\t\t\tpadding: 0.5em 0;\n\t\t\t}\n\n\t\t\t\tul.alt li:first-child {\n\t\t\t\t\tborder-top: 0;\n\t\t\t\t\tpadding-top: 0;\n\t\t\t\t}\n\n\tdl {\n\t\tmargin: 0 0 2em 0;\n\t}\n\n\t\tdl dt {\n\t\t\tdisplay: block;\n\t\t\tfont-weight: 600;\n\t\t\tmargin: 0 0 1em 0;\n\t\t}\n\n\t\tdl dd {\n\t\t\tmargin-left: 2em;\n\t\t}\n\n/* Actions */\n\n\tul.actions {\n\t\tdisplay: -moz-flex;\n\t\tdisplay: -webkit-flex;\n\t\tdisplay: -ms-flex;\n\t\tdisplay: flex;\n\t\tcursor: default;\n\t\tlist-style: none;\n\t\tmargin-left: -1em;\n\t\tpadding-left: 0;\n\t}\n\n\t\tul.actions li {\n\t\t\tpadding: 0 0 0 1em;\n\t\t\tvertical-align: middle;\n\t\t}\n\n\t\tul.actions.special {\n\t\t\t-moz-justify-content: center;\n\t\t\t-webkit-justify-content: center;\n\t\t\t-ms-justify-content: center;\n\t\t\tjustify-content: center;\n\t\t\twidth: 100%;\n\t\t\tmargin-left: 0;\n\t\t}\n\n\t\t\tul.actions.special li:first-child {\n\t\t\t\tpadding-left: 0;\n\t\t\t}\n\n\t\tul.actions.stacked {\n\t\t\t-moz-flex-direction: column;\n\t\t\t-webkit-flex-direction: column;\n\t\t\t-ms-flex-direction: column;\n\t\t\tflex-direction: column;\n\t\t\tmargin-left: 0;\n\t\t}\n\n\t\t\tul.actions.stacked li {\n\t\t\t\tpadding: 1.3em 0 0 0;\n\t\t\t}\n\n\t\t\t\tul.actions.stacked li:first-child {\n\t\t\t\t\tpadding-top: 0;\n\t\t\t\t}\n\n\t\tul.actions.fit {\n\t\t\twidth: calc(100% + 1em);\n\t\t}\n\n\t\t\tul.actions.fit li {\n\t\t\t\t-moz-flex-grow: 1;\n\t\t\t\t-webkit-flex-grow: 1;\n\t\t\t\t-ms-flex-grow: 1;\n\t\t\t\tflex-grow: 1;\n\t\t\t\t-moz-flex-shrink: 1;\n\t\t\t\t-webkit-flex-shrink: 1;\n\t\t\t\t-ms-flex-shrink: 1;\n\t\t\t\tflex-shrink: 1;\n\t\t\t\twidth: 100%;\n\t\t\t}\n\n\t\t\t\tul.actions.fit li > * {\n\t\t\t\t\twidth: 100%;\n\t\t\t\t}\n\n\t\t\tul.actions.fit.stacked {\n\t\t\t\twidth: 100%;\n\t\t\t}\n\n/* Icons */\n\n\tul.icons {\n\t\tcursor: default;\n\t\tlist-style: none;\n\t\tpadding-left: 0;\n\t}\n\n\t\tul.icons li {\n\t\t\tdisplay: inline-block;\n\t\t\tpadding: 0 1em 0 0;\n\t\t}\n\n\t\t\tul.icons li:last-child {\n\t\t\t\tpadding-right: 0;\n\t\t\t}\n\n\t\t@media screen and (max-width: 736px) {\n\n\t\t\tul.icons li {\n\t\t\t\tpadding: 0 0.75em 0 0;\n\t\t\t}\n\n\t\t}\n\n/* Pagination */\n\n\tul.pagination {\n\t\tcursor: default;\n\t\tlist-style: none;\n\t\tpadding-left: 0;\n\t}\n\n\t\tul.pagination li {\n\t\t\tdisplay: inline-block;\n\t\t\tpadding-left: 0;\n\t\t\tvertical-align: middle;\n\t\t}\n\n\t\t\tul.pagination li > .page {\n\t\t\t\t-moz-transition: background-color 0.2s ease-in-out, color 0.2s ease-in-out;\n\t\t\t\t-webkit-transition: background-color 0.2s ease-in-out, color 0.2s ease-in-out;\n\t\t\t\t-ms-transition: background-color 0.2s ease-in-out, color 0.2s ease-in-out;\n\t\t\t\ttransition: background-color 0.2s ease-in-out, color 0.2s ease-in-out;\n\t\t\t\tborder-bottom: 0;\n\t\t\t\tdisplay: inline-block;\n\t\t\t\tfont-size: 0.8em;\n\t\t\t\tfont-weight: 600;\n\t\t\t\theight: 1.5em;\n\t\t\t\tline-height: 1.5em;\n\t\t\t\tmargin: 0 0.125em;\n\t\t\t\tmin-width: 1.5em;\n\t\t\t\tpadding: 0 0.5em;\n\t\t\t\ttext-align: center;\n\t\t\t}\n\n\t\t\t\tul.pagination li > .page.active {\n\t\t\t\t\tbackground-color: #ffffff;\n\t\t\t\t\tcolor: #242943;\n\t\t\t\t}\n\n\t\t\t\t\tul.pagination li > .page.active:hover {\n\t\t\t\t\t\tbackground-color: #9bf1ff;\n\t\t\t\t\t\tcolor: #242943 !important;\n\t\t\t\t\t}\n\n\t\t\t\t\tul.pagination li > .page.active:active {\n\t\t\t\t\t\tbackground-color: #53e3fb;\n\t\t\t\t\t}\n\n\t\t\tul.pagination li:first-child {\n\t\t\t\tpadding-right: 0.75em;\n\t\t\t}\n\n\t\t\tul.pagination li:last-child {\n\t\t\t\tpadding-left: 0.75em;\n\t\t\t}\n\n\t\t@media screen and (max-width: 480px) {\n\n\t\t\tul.pagination li:nth-child(n+2):nth-last-child(n+2) {\n\t\t\t\tdisplay: none;\n\t\t\t}\n\n\t\t\tul.pagination li:first-child {\n\t\t\t\tpadding-right: 0;\n\t\t\t}\n\n\t\t}\n\n/* Table */\n\n\t.table-wrapper {\n\t\t-webkit-overflow-scrolling: touch;\n\t\toverflow-x: auto;\n\t}\n\n\ttable {\n\t\tmargin: 0 0 2em 0;\n\t\twidth: 100%;\n\t}\n\n\t\ttable tbody tr {\n\t\t\tborder: solid 1px rgba(212, 212, 255, 0.1);\n\t\t\tborder-left: 0;\n\t\t\tborder-right: 0;\n\t\t}\n\n\t\t\ttable tbody tr:nth-child(2n + 1) {\n\t\t\t\tbackground-color: rgba(212, 212, 255, 0.035);\n\t\t\t}\n\n\t\ttable td {\n\t\t\tpadding: 0.75em 0.75em;\n\t\t}\n\n\t\ttable th {\n\t\t\tcolor: #ffffff;\n\t\t\tfont-size: 0.9em;\n\t\t\tfont-weight: 600;\n\t\t\tpadding: 0 0.75em 0.75em 0.75em;\n\t\t\ttext-align: left;\n\t\t}\n\n\t\ttable thead {\n\t\t\tborder-bottom: solid 2px rgba(212, 212, 255, 0.1);\n\t\t}\n\n\t\ttable tfoot {\n\t\t\tborder-top: solid 2px rgba(212, 212, 255, 0.1);\n\t\t}\n\n\t\ttable.alt {\n\t\t\tborder-collapse: separate;\n\t\t}\n\n\t\t\ttable.alt tbody tr td {\n\t\t\t\tborder: solid 1px rgba(212, 212, 255, 0.1);\n\t\t\t\tborder-left-width: 0;\n\t\t\t\tborder-top-width: 0;\n\t\t\t}\n\n\t\t\t\ttable.alt tbody tr td:first-child {\n\t\t\t\t\tborder-left-width: 1px;\n\t\t\t\t}\n\n\t\t\ttable.alt tbody tr:first-child td {\n\t\t\t\tborder-top-width: 1px;\n\t\t\t}\n\n\t\t\ttable.alt thead {\n\t\t\t\tborder-bottom: 0;\n\t\t\t}\n\n\t\t\ttable.alt tfoot {\n\t\t\t\tborder-top: 0;\n\t\t\t}\n\n/* Button */\n\n\tinput[type=\"submit\"],\n\tinput[type=\"reset\"],\n\tinput[type=\"button\"],\n\tbutton,\n\t.button {\n\t\t-moz-appearance: none;\n\t\t-webkit-appearance: none;\n\t\t-ms-appearance: none;\n\t\tappearance: none;\n\t\t-moz-transition: background-color 0.2s ease-in-out, box-shadow 0.2s ease-in-out, color 0.2s ease-in-out;\n\t\t-webkit-transition: background-color 0.2s ease-in-out, box-shadow 0.2s ease-in-out, color 0.2s ease-in-out;\n\t\t-ms-transition: background-color 0.2s ease-in-out, box-shadow 0.2s ease-in-out, color 0.2s ease-in-out;\n\t\ttransition: background-color 0.2s ease-in-out, box-shadow 0.2s ease-in-out, color 0.2s ease-in-out;\n\t\tbackground-color: transparent;\n\t\tborder: 0;\n\t\tborder-radius: 0;\n\t\tbox-shadow: inset 0 0 0 2px #ffffff;\n\t\tcolor: #ffffff;\n\t\tcursor: pointer;\n\t\tdisplay: inline-block;\n\t\tfont-size: 0.8em;\n\t\tfont-weight: 600;\n\t\theight: 3.5em;\n\t\tletter-spacing: 0.25em;\n\t\tline-height: 3.5em;\n\t\tpadding: 0 1.75em;\n\t\ttext-align: center;\n\t\ttext-decoration: none;\n\t\ttext-transform: uppercase;\n\t\twhite-space: nowrap;\n\t}\n\n\t\tinput[type=\"submit\"]:hover, input[type=\"submit\"]:active,\n\t\tinput[type=\"reset\"]:hover,\n\t\tinput[type=\"reset\"]:active,\n\t\tinput[type=\"button\"]:hover,\n\t\tinput[type=\"button\"]:active,\n\t\tbutton:hover,\n\t\tbutton:active,\n\t\t.button:hover,\n\t\t.button:active {\n\t\t\tbox-shadow: inset 0 0 0 2px #9bf1ff;\n\t\t\tcolor: #9bf1ff;\n\t\t}\n\n\t\tinput[type=\"submit\"]:active,\n\t\tinput[type=\"reset\"]:active,\n\t\tinput[type=\"button\"]:active,\n\t\tbutton:active,\n\t\t.button:active {\n\t\t\tbackground-color: rgba(155, 241, 255, 0.1);\n\t\t\tbox-shadow: inset 0 0 0 2px #53e3fb;\n\t\t\tcolor: #53e3fb;\n\t\t}\n\n\t\tinput[type=\"submit\"].icon:before,\n\t\tinput[type=\"reset\"].icon:before,\n\t\tinput[type=\"button\"].icon:before,\n\t\tbutton.icon:before,\n\t\t.button.icon:before {\n\t\t\tmargin-right: 0.5em;\n\t\t}\n\n\t\tinput[type=\"submit\"].fit,\n\t\tinput[type=\"reset\"].fit,\n\t\tinput[type=\"button\"].fit,\n\t\tbutton.fit,\n\t\t.button.fit {\n\t\t\twidth: 100%;\n\t\t}\n\n\t\tinput[type=\"submit\"].small,\n\t\tinput[type=\"reset\"].small,\n\t\tinput[type=\"button\"].small,\n\t\tbutton.small,\n\t\t.button.small {\n\t\t\tfont-size: 0.6em;\n\t\t}\n\n\t\tinput[type=\"submit\"].large,\n\t\tinput[type=\"reset\"].large,\n\t\tinput[type=\"button\"].large,\n\t\tbutton.large,\n\t\t.button.large {\n\t\t\tfont-size: 1.25em;\n\t\t\theight: 3em;\n\t\t\tline-height: 3em;\n\t\t}\n\n\t\tinput[type=\"submit\"].next,\n\t\tinput[type=\"reset\"].next,\n\t\tinput[type=\"button\"].next,\n\t\tbutton.next,\n\t\t.button.next {\n\t\t\tpadding-right: 4.5em;\n\t\t\tposition: relative;\n\t\t}\n\n\t\t\tinput[type=\"submit\"].next:before, input[type=\"submit\"].next:after,\n\t\t\tinput[type=\"reset\"].next:before,\n\t\t\tinput[type=\"reset\"].next:after,\n\t\t\tinput[type=\"button\"].next:before,\n\t\t\tinput[type=\"button\"].next:after,\n\t\t\tbutton.next:before,\n\t\t\tbutton.next:after,\n\t\t\t.button.next:before,\n\t\t\t.button.next:after {\n\t\t\t\t-moz-transition: opacity 0.2s ease-in-out;\n\t\t\t\t-webkit-transition: opacity 0.2s ease-in-out;\n\t\t\t\t-ms-transition: opacity 0.2s ease-in-out;\n\t\t\t\ttransition: opacity 0.2s ease-in-out;\n\t\t\t\tbackground-position: center right;\n\t\t\t\tbackground-repeat: no-repeat;\n\t\t\t\tbackground-size: 36px 24px;\n\t\t\t\tcontent: '';\n\t\t\t\tdisplay: block;\n\t\t\t\theight: 100%;\n\t\t\t\tposition: absolute;\n\t\t\t\tright: 1.5em;\n\t\t\t\ttop: 0;\n\t\t\t\tvertical-align: middle;\n\t\t\t\twidth: 36px;\n\t\t\t}\n\n\t\t\tinput[type=\"submit\"].next:before,\n\t\t\tinput[type=\"reset\"].next:before,\n\t\t\tinput[type=\"button\"].next:before,\n\t\t\tbutton.next:before,\n\t\t\t.button.next:before {\n\t\t\t\tbackground-image: url(\"data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' width='36px' height='24px' viewBox='0 0 36 24' zoomAndPan='disable'%3E%3Cstyle%3Eline %7B stroke: %23ffffff%3B stroke-width: 2px%3B %7D%3C/style%3E%3Cline x1='0' y1='12' x2='34' y2='12' /%3E%3Cline x1='25' y1='4' x2='34' y2='12.5' /%3E%3Cline x1='25' y1='20' x2='34' y2='11.5' /%3E%3C/svg%3E\");\n\t\t\t}\n\n\t\t\tinput[type=\"submit\"].next:after,\n\t\t\tinput[type=\"reset\"].next:after,\n\t\t\tinput[type=\"button\"].next:after,\n\t\t\tbutton.next:after,\n\t\t\t.button.next:after {\n\t\t\t\tbackground-image: url(\"data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' width='36px' height='24px' viewBox='0 0 36 24' zoomAndPan='disable'%3E%3Cstyle%3Eline %7B stroke: %239bf1ff%3B stroke-width: 2px%3B %7D%3C/style%3E%3Cline x1='0' y1='12' x2='34' y2='12' /%3E%3Cline x1='25' y1='4' x2='34' y2='12.5' /%3E%3Cline x1='25' y1='20' x2='34' y2='11.5' /%3E%3C/svg%3E\");\n\t\t\t\topacity: 0;\n\t\t\t\tz-index: 1;\n\t\t\t}\n\n\t\t\tinput[type=\"submit\"].next:hover:after, input[type=\"submit\"].next:active:after,\n\t\t\tinput[type=\"reset\"].next:hover:after,\n\t\t\tinput[type=\"reset\"].next:active:after,\n\t\t\tinput[type=\"button\"].next:hover:after,\n\t\t\tinput[type=\"button\"].next:active:after,\n\t\t\tbutton.next:hover:after,\n\t\t\tbutton.next:active:after,\n\t\t\t.button.next:hover:after,\n\t\t\t.button.next:active:after {\n\t\t\t\topacity: 1;\n\t\t\t}\n\n\t\t\t@media screen and (max-width: 1280px) {\n\n\t\t\t\tinput[type=\"submit\"].next,\n\t\t\t\tinput[type=\"reset\"].next,\n\t\t\t\tinput[type=\"button\"].next,\n\t\t\t\tbutton.next,\n\t\t\t\t.button.next {\n\t\t\t\t\tpadding-right: 5em;\n\t\t\t\t}\n\n\t\t\t}\n\n\t\tinput[type=\"submit\"].primary,\n\t\tinput[type=\"reset\"].primary,\n\t\tinput[type=\"button\"].primary,\n\t\tbutton.primary,\n\t\t.button.primary {\n\t\t\tbackground-color: #ffffff;\n\t\t\tbox-shadow: none;\n\t\t\tcolor: #242943;\n\t\t}\n\n\t\t\tinput[type=\"submit\"].primary:hover, input[type=\"submit\"].primary:active,\n\t\t\tinput[type=\"reset\"].primary:hover,\n\t\t\tinput[type=\"reset\"].primary:active,\n\t\t\tinput[type=\"button\"].primary:hover,\n\t\t\tinput[type=\"button\"].primary:active,\n\t\t\tbutton.primary:hover,\n\t\t\tbutton.primary:active,\n\t\t\t.button.primary:hover,\n\t\t\t.button.primary:active {\n\t\t\t\tbackground-color: #9bf1ff;\n\t\t\t\tcolor: #242943 !important;\n\t\t\t}\n\n\t\t\tinput[type=\"submit\"].primary:active,\n\t\t\tinput[type=\"reset\"].primary:active,\n\t\t\tinput[type=\"button\"].primary:active,\n\t\t\tbutton.primary:active,\n\t\t\t.button.primary:active {\n\t\t\t\tbackground-color: #53e3fb;\n\t\t\t}\n\n\t\tinput[type=\"submit\"].disabled, input[type=\"submit\"]:disabled,\n\t\tinput[type=\"reset\"].disabled,\n\t\tinput[type=\"reset\"]:disabled,\n\t\tinput[type=\"button\"].disabled,\n\t\tinput[type=\"button\"]:disabled,\n\t\tbutton.disabled,\n\t\tbutton:disabled,\n\t\t.button.disabled,\n\t\t.button:disabled {\n\t\t\tpointer-events: none;\n\t\t\tcursor: default;\n\t\t\topacity: 0.25;\n\t\t}\n\n/* Tiles */\n\n\t.tiles {\n\t\tdisplay: -moz-flex;\n\t\tdisplay: -webkit-flex;\n\t\tdisplay: -ms-flex;\n\t\tdisplay: flex;\n\t\t-moz-flex-wrap: wrap;\n\t\t-webkit-flex-wrap: wrap;\n\t\t-ms-flex-wrap: wrap;\n\t\tflex-wrap: wrap;\n\t\tborder-top: 0 !important;\n\t}\n\n\t\t.tiles + * {\n\t\t\tborder-top: 0 !important;\n\t\t}\n\n\t\t.tiles article {\n\t\t\t-moz-align-items: center;\n\t\t\t-webkit-align-items: center;\n\t\t\t-ms-align-items: center;\n\t\t\talign-items: center;\n\t\t\tdisplay: -moz-flex;\n\t\t\tdisplay: -webkit-flex;\n\t\t\tdisplay: -ms-flex;\n\t\t\tdisplay: flex;\n\t\t\t-moz-transition: -moz-transform 0.25s ease, opacity 0.25s ease, -moz-filter 1s ease, -webkit-filter 1s ease;\n\t\t\t-webkit-transition: -webkit-transform 0.25s ease, opacity 0.25s ease, -webkit-filter 1s ease, -webkit-filter 1s ease;\n\t\t\t-ms-transition: -ms-transform 0.25s ease, opacity 0.25s ease, -ms-filter 1s ease, -webkit-filter 1s ease;\n\t\t\ttransition: transform 0.25s ease, opacity 0.25s ease, filter 1s ease, -webkit-filter 1s ease;\n\t\t\tpadding: 4em 4em 2em 4em ;\n\t\t\tbackground-position: center;\n\t\t\tbackground-repeat: no-repeat;\n\t\t\tbackground-size: cover;\n\t\t\tcursor: default;\n\t\t\theight: 40vh;\n\t\t\tmax-height: 40em;\n\t\t\tmin-height: 23em;\n\t\t\toverflow: hidden;\n\t\t\tposition: relative;\n\t\t\twidth: 40%;\n\t\t}\n\n\t\t\t.tiles article .image {\n\t\t\t\tdisplay: none;\n\t\t\t}\n\n\t\t\t.tiles article header {\n\t\t\t\tposition: relative;\n\t\t\t\tz-index: 3;\n\t\t\t}\n\n\t\t\t.tiles article h3 {\n\t\t\t\tfont-size: 1.75em;\n\t\t\t}\n\n\t\t\t\t.tiles article h3 a:hover {\n\t\t\t\t\tcolor: inherit !important;\n\t\t\t\t}\n\n\t\t\t.tiles article .link.primary {\n\t\t\t\tborder: 0;\n\t\t\t\theight: 100%;\n\t\t\t\tleft: 0;\n\t\t\t\tposition: absolute;\n\t\t\t\ttop: 0;\n\t\t\t\twidth: 100%;\n\t\t\t\tz-index: 4;\n\t\t\t}\n\n\t\t\t.tiles article:before {\n\t\t\t\t-moz-transition: opacity 0.5s ease;\n\t\t\t\t-webkit-transition: opacity 0.5s ease;\n\t\t\t\t-ms-transition: opacity 0.5s ease;\n\t\t\t\ttransition: opacity 0.5s ease;\n\t\t\t\tbottom: 0;\n\t\t\t\tcontent: '';\n\t\t\t\tdisplay: block;\n\t\t\t\theight: 100%;\n\t\t\t\tleft: 0;\n\t\t\t\topacity: 0.85;\n\t\t\t\tposition: absolute;\n\t\t\t\twidth: 100%;\n\t\t\t\tz-index: 2;\n\t\t\t}\n\n\t\t\t.tiles article:after {\n\t\t\t\tbackground-color: rgba(36, 41, 67, 0.25);\n\t\t\t\tcontent: '';\n\t\t\t\tdisplay: block;\n\t\t\t\theight: 100%;\n\t\t\t\tleft: 0;\n\t\t\t\tposition: absolute;\n\t\t\t\ttop: 0;\n\t\t\t\twidth: 100%;\n\t\t\t\tz-index: 1;\n\t\t\t}\n\n\t\t\t.tiles article:hover:before {\n\t\t\t\topacity: 0;\n\t\t\t}\n\n\t\t\t.tiles article.is-transitioning {\n\t\t\t\t-moz-transform: scale(0.95);\n\t\t\t\t-webkit-transform: scale(0.95);\n\t\t\t\t-ms-transform: scale(0.95);\n\t\t\t\ttransform: scale(0.95);\n\t\t\t\t-moz-filter: blur(0.5em);\n\t\t\t\t-webkit-filter: blur(0.5em);\n\t\t\t\t-ms-filter: blur(0.5em);\n\t\t\t\tfilter: blur(0.5em);\n\t\t\t\topacity: 0;\n\t\t\t}\n\n\t\t\t.tiles article:nth-child(4n - 1), .tiles article:nth-child(4n - 2) {\n\t\t\t\twidth: 60%;\n\t\t\t}\n\n\t\t\t.tiles article:nth-child(6n - 5):before {\n\t\t\t\tbackground-color: #6fc3df;\n\t\t\t}\n\n\t\t\t.tiles article:nth-child(6n - 4):before {\n\t\t\t\tbackground-color: #8d82c4;\n\t\t\t}\n\n\t\t\t.tiles article:nth-child(6n - 3):before {\n\t\t\t\tbackground-color: #ec8d81;\n\t\t\t}\n\n\t\t\t.tiles article:nth-child(6n - 2):before {\n\t\t\t\tbackground-color: #e7b788;\n\t\t\t}\n\n\t\t\t.tiles article:nth-child(6n - 1):before {\n\t\t\t\tbackground-color: #8ea9e8;\n\t\t\t}\n\n\t\t\t.tiles article:nth-child(6n):before {\n\t\t\t\tbackground-color: #87c5a4;\n\t\t\t}\n\n\t\t@media screen and (max-width: 1280px) {\n\n\t\t\t.tiles article {\n\t\t\t\tpadding: 4em 3em 2em 3em ;\n\t\t\t\theight: 30vh;\n\t\t\t\tmax-height: 30em;\n\t\t\t\tmin-height: 20em;\n\t\t\t}\n\n\t\t}\n\n\t\t@media screen and (max-width: 980px) {\n\n\t\t\t.tiles article {\n\t\t\t\twidth: 50% !important;\n\t\t\t}\n\n\t\t}\n\n\t\t@media screen and (max-width: 736px) {\n\n\t\t\t.tiles article {\n\t\t\t\tpadding: 3em 1.5em 1em 1.5em ;\n\t\t\t\theight: 16em;\n\t\t\t\tmax-height: none;\n\t\t\t\tmin-height: 0;\n\t\t\t}\n\n\t\t\t\t.tiles article h3 {\n\t\t\t\t\tfont-size: 1.5em;\n\t\t\t\t}\n\n\t\t}\n\n\t\t@media screen and (max-width: 480px) {\n\n\t\t\t.tiles {\n\t\t\t\tdisplay: block;\n\t\t\t}\n\n\t\t\t\t.tiles article {\n\t\t\t\t\theight: 20em;\n\t\t\t\t\twidth: 100% !important;\n\t\t\t\t}\n\n\t\t}\n\n/* Contact Method */\n\n\t.contact-method {\n\t\tmargin: 0 0 2em 0;\n\t\tpadding-left: 3.25em;\n\t\tposition: relative;\n\t}\n\n\t\t.contact-method .icon {\n\t\t\tleft: 0;\n\t\t\tposition: absolute;\n\t\t\ttop: 0;\n\t\t}\n\n\t\t.contact-method h3 {\n\t\t\tmargin: 0 0 0.5em 0;\n\t\t}\n\n/* Spotlights */\n\n\t.spotlights {\n\t\tborder-top: 0 !important;\n\t}\n\n\t\t.spotlights + * {\n\t\t\tborder-top: 0 !important;\n\t\t}\n\n\t\t.spotlights > section {\n\t\t\tdisplay: -moz-flex;\n\t\t\tdisplay: -webkit-flex;\n\t\t\tdisplay: -ms-flex;\n\t\t\tdisplay: flex;\n\t\t\t-moz-flex-direction: row;\n\t\t\t-webkit-flex-direction: row;\n\t\t\t-ms-flex-direction: row;\n\t\t\tflex-direction: row;\n\t\t\tbackground-color: #2e3450;\n\t\t}\n\n\t\t\t.spotlights > section > .image {\n\t\t\t\tbackground-position: center center;\n\t\t\t\tbackground-size: cover;\n\t\t\t\tborder-radius: 0;\n\t\t\t\tdisplay: block;\n\t\t\t\tposition: relative;\n\t\t\t\twidth: 30%;\n\t\t\t}\n\n\t\t\t\t.spotlights > section > .image img {\n\t\t\t\t\tborder-radius: 0;\n\t\t\t\t\tdisplay: block;\n\t\t\t\t\twidth: 100%;\n\t\t\t\t}\n\n\t\t\t\t.spotlights > section > .image:before {\n\t\t\t\t\tbackground: rgba(36, 41, 67, 0.9);\n\t\t\t\t\tcontent: '';\n\t\t\t\t\tdisplay: block;\n\t\t\t\t\theight: 100%;\n\t\t\t\t\tleft: 0;\n\t\t\t\t\topacity: 0;\n\t\t\t\t\tposition: absolute;\n\t\t\t\t\ttop: 0;\n\t\t\t\t\twidth: 100%;\n\t\t\t\t}\n\n\t\t\t.spotlights > section > .content {\n\t\t\t\tdisplay: -moz-flex;\n\t\t\t\tdisplay: -webkit-flex;\n\t\t\t\tdisplay: -ms-flex;\n\t\t\t\tdisplay: flex;\n\t\t\t\t-moz-flex-direction: column;\n\t\t\t\t-webkit-flex-direction: column;\n\t\t\t\t-ms-flex-direction: column;\n\t\t\t\tflex-direction: column;\n\t\t\t\t-moz-justify-content: center;\n\t\t\t\t-webkit-justify-content: center;\n\t\t\t\t-ms-justify-content: center;\n\t\t\t\tjustify-content: center;\n\t\t\t\t-moz-align-items: center;\n\t\t\t\t-webkit-align-items: center;\n\t\t\t\t-ms-align-items: center;\n\t\t\t\talign-items: center;\n\t\t\t\tpadding: 2em 3em 0.1em 3em ;\n\t\t\t\twidth: 70%;\n\t\t\t}\n\n\t\t\t\t.spotlights > section > .content > .inner {\n\t\t\t\t\tmargin: 0 auto;\n\t\t\t\t\tmax-width: 100%;\n\t\t\t\t\twidth: 65em;\n\t\t\t\t}\n\n\t\t\t.spotlights > section:nth-child(2n) {\n\t\t\t\t-moz-flex-direction: row-reverse;\n\t\t\t\t-webkit-flex-direction: row-reverse;\n\t\t\t\t-ms-flex-direction: row-reverse;\n\t\t\t\tflex-direction: row-reverse;\n\t\t\t\tbackground-color: #333856;\n\t\t\t}\n\n\t\t\t\t.spotlights > section:nth-child(2n) > .content {\n\t\t\t\t\t-moz-align-items: -moz-flex-end;\n\t\t\t\t\t-webkit-align-items: -webkit-flex-end;\n\t\t\t\t\t-ms-align-items: -ms-flex-end;\n\t\t\t\t\talign-items: flex-end;\n\t\t\t\t}\n\n\t\t@media screen and (max-width: 1680px) {\n\n\t\t\t.spotlights > section > .image {\n\t\t\t\twidth: 40%;\n\t\t\t}\n\n\t\t\t.spotlights > section > .content {\n\t\t\t\twidth: 60%;\n\t\t\t}\n\n\t\t}\n\n\t\t@media screen and (max-width: 1280px) {\n\n\t\t\t.spotlights > section > .image {\n\t\t\t\twidth: 45%;\n\t\t\t}\n\n\t\t\t.spotlights > section > .content {\n\t\t\t\twidth: 55%;\n\t\t\t}\n\n\t\t}\n\n\t\t@media screen and (max-width: 980px) {\n\n\t\t\t.spotlights > section {\n\t\t\t\tdisplay: block;\n\t\t\t}\n\n\t\t\t\t.spotlights > section > .image {\n\t\t\t\t\twidth: 100%;\n\t\t\t\t}\n\n\t\t\t\t.spotlights > section > .content {\n\t\t\t\t\tpadding: 4em 3em 2em 3em ;\n\t\t\t\t\twidth: 100%;\n\t\t\t\t}\n\n\t\t}\n\n\t\t@media screen and (max-width: 736px) {\n\n\t\t\t.spotlights > section > .content {\n\t\t\t\tpadding: 3em 1.5em 1em 1.5em ;\n\t\t\t}\n\n\t\t}\n\n/* Header */\n\n\t@-moz-keyframes reveal-header {\n\t\t0% {\n\t\t\ttop: -4em;\n\t\t\topacity: 0;\n\t\t}\n\n\t\t100% {\n\t\t\ttop: 0;\n\t\t\topacity: 1;\n\t\t}\n\t}\n\n\t@-webkit-keyframes reveal-header {\n\t\t0% {\n\t\t\ttop: -4em;\n\t\t\topacity: 0;\n\t\t}\n\n\t\t100% {\n\t\t\ttop: 0;\n\t\t\topacity: 1;\n\t\t}\n\t}\n\n\t@-ms-keyframes reveal-header {\n\t\t0% {\n\t\t\ttop: -4em;\n\t\t\topacity: 0;\n\t\t}\n\n\t\t100% {\n\t\t\ttop: 0;\n\t\t\topacity: 1;\n\t\t}\n\t}\n\n\t@keyframes reveal-header {\n\t\t0% {\n\t\t\ttop: -4em;\n\t\t\topacity: 0;\n\t\t}\n\n\t\t100% {\n\t\t\ttop: 0;\n\t\t\topacity: 1;\n\t\t}\n\t}\n\n\t#header {\n\t\tdisplay: -moz-flex;\n\t\tdisplay: -webkit-flex;\n\t\tdisplay: -ms-flex;\n\t\tdisplay: flex;\n\t\tbackground-color: #2a2f4a;\n\t\tbox-shadow: 0 0 0.25em 0 rgba(0, 0, 0, 0.15);\n\t\tcursor: default;\n\t\tfont-weight: 600;\n\t\theight: 3.25em;\n\t\tleft: 0;\n\t\tletter-spacing: 0.25em;\n\t\tline-height: 3.25em;\n\t\tmargin: 0;\n\t\tposition: fixed;\n\t\ttext-transform: uppercase;\n\t\ttop: 0;\n\t\twidth: 100%;\n\t\tz-index: 10000;\n\t}\n\n\t\t#header .logo {\n\t\t\tborder: 0;\n\t\t\tdisplay: inline-block;\n\t\t\tfont-size: 0.8em;\n\t\t\theight: inherit;\n\t\t\tline-height: inherit;\n\t\t\tpadding: 0 1.5em;\n\t\t}\n\n\t\t\t#header .logo strong {\n\t\t\t\t-moz-transition: background-color 0.2s ease-in-out, color 0.2s ease-in-out;\n\t\t\t\t-webkit-transition: background-color 0.2s ease-in-out, color 0.2s ease-in-out;\n\t\t\t\t-ms-transition: background-color 0.2s ease-in-out, color 0.2s ease-in-out;\n\t\t\t\ttransition: background-color 0.2s ease-in-out, color 0.2s ease-in-out;\n\t\t\t\tbackground-color: #ffffff;\n\t\t\t\tcolor: #242943;\n\t\t\t\tdisplay: inline-block;\n\t\t\t\tline-height: 1.65em;\n\t\t\t\tmargin-right: 0.325em;\n\t\t\t\tpadding: 0 0.125em 0 0.375em;\n\t\t\t}\n\n\t\t\t#header .logo:hover strong {\n\t\t\t\tbackground-color: #9bf1ff;\n\t\t\t}\n\n\t\t\t#header .logo:active strong {\n\t\t\t\tbackground-color: #53e3fb;\n\t\t\t}\n\n\t\t#header nav {\n\t\t\tdisplay: -moz-flex;\n\t\t\tdisplay: -webkit-flex;\n\t\t\tdisplay: -ms-flex;\n\t\t\tdisplay: flex;\n\t\t\t-moz-justify-content: -moz-flex-end;\n\t\t\t-webkit-justify-content: -webkit-flex-end;\n\t\t\t-ms-justify-content: -ms-flex-end;\n\t\t\tjustify-content: flex-end;\n\t\t\t-moz-flex-grow: 1;\n\t\t\t-webkit-flex-grow: 1;\n\t\t\t-ms-flex-grow: 1;\n\t\t\tflex-grow: 1;\n\t\t\theight: inherit;\n\t\t\tline-height: inherit;\n\t\t}\n\n\t\t\t#header nav a {\n\t\t\t\tborder: 0;\n\t\t\t\tdisplay: block;\n\t\t\t\tfont-size: 0.8em;\n\t\t\t\theight: inherit;\n\t\t\t\tline-height: inherit;\n\t\t\t\tpadding: 0 0.75em;\n\t\t\t\tposition: relative;\n\t\t\t\tvertical-align: middle;\n\t\t\t}\n\n\t\t\t\t#header nav a:last-child {\n\t\t\t\t\tpadding-right: 1.5em;\n\t\t\t\t}\n\n\t\t\t\t#header nav a[href=\"#menu\"] {\n\t\t\t\t\tpadding-right: 3.325em !important;\n\t\t\t\t}\n\n\t\t\t\t\t#header nav a[href=\"#menu\"]:before, #header nav a[href=\"#menu\"]:after {\n\t\t\t\t\t\tbackground-image: url(\"data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='32' viewBox='0 0 24 32' preserveAspectRatio='none'%3E%3Cstyle%3Eline %7B stroke-width: 2px%3B stroke: %23ffffff%3B %7D%3C/style%3E%3Cline x1='0' y1='11' x2='24' y2='11' /%3E%3Cline x1='0' y1='21' x2='24' y2='21' /%3E%3Cline x1='0' y1='16' x2='24' y2='16' /%3E%3C/svg%3E\");\n\t\t\t\t\t\tbackground-position: center;\n\t\t\t\t\t\tbackground-repeat: no-repeat;\n\t\t\t\t\t\tbackground-size: 24px 32px;\n\t\t\t\t\t\tcontent: '';\n\t\t\t\t\t\tdisplay: block;\n\t\t\t\t\t\theight: 100%;\n\t\t\t\t\t\tposition: absolute;\n\t\t\t\t\t\tright: 1.5em;\n\t\t\t\t\t\ttop: 0;\n\t\t\t\t\t\tvertical-align: middle;\n\t\t\t\t\t\twidth: 24px;\n\t\t\t\t\t}\n\n\t\t\t\t\t#header nav a[href=\"#menu\"]:after {\n\t\t\t\t\t\t-moz-transition: opacity 0.2s ease-in-out;\n\t\t\t\t\t\t-webkit-transition: opacity 0.2s ease-in-out;\n\t\t\t\t\t\t-ms-transition: opacity 0.2s ease-in-out;\n\t\t\t\t\t\ttransition: opacity 0.2s ease-in-out;\n\t\t\t\t\t\tbackground-image: url(\"data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='24' height='32' viewBox='0 0 24 32' preserveAspectRatio='none'%3E%3Cstyle%3Eline %7B stroke-width: 2px%3B stroke: %239bf1ff%3B %7D%3C/style%3E%3Cline x1='0' y1='11' x2='24' y2='11' /%3E%3Cline x1='0' y1='21' x2='24' y2='21' /%3E%3Cline x1='0' y1='16' x2='24' y2='16' /%3E%3C/svg%3E\");\n\t\t\t\t\t\topacity: 0;\n\t\t\t\t\t\tz-index: 1;\n\t\t\t\t\t}\n\n\t\t\t\t\t#header nav a[href=\"#menu\"]:hover:after, #header nav a[href=\"#menu\"]:active:after {\n\t\t\t\t\t\topacity: 1;\n\t\t\t\t\t}\n\n\t\t\t\t\t#header nav a[href=\"#menu\"]:last-child {\n\t\t\t\t\t\tpadding-right: 3.875em !important;\n\t\t\t\t\t}\n\n\t\t\t\t\t\t#header nav a[href=\"#menu\"]:last-child:before, #header nav a[href=\"#menu\"]:last-child:after {\n\t\t\t\t\t\t\tright: 2em;\n\t\t\t\t\t\t}\n\n\t\t#header.reveal {\n\t\t\t-moz-animation: reveal-header 0.35s ease;\n\t\t\t-webkit-animation: reveal-header 0.35s ease;\n\t\t\t-ms-animation: reveal-header 0.35s ease;\n\t\t\tanimation: reveal-header 0.35s ease;\n\t\t}\n\n\t\t#header.alt {\n\t\t\t-moz-transition: opacity 2.5s ease;\n\t\t\t-webkit-transition: opacity 2.5s ease;\n\t\t\t-ms-transition: opacity 2.5s ease;\n\t\t\ttransition: opacity 2.5s ease;\n\t\t\t-moz-transition-delay: 0.75s;\n\t\t\t-webkit-transition-delay: 0.75s;\n\t\t\t-ms-transition-delay: 0.75s;\n\t\t\ttransition-delay: 0.75s;\n\t\t\t-moz-animation: none;\n\t\t\t-webkit-animation: none;\n\t\t\t-ms-animation: none;\n\t\t\tanimation: none;\n\t\t\tbackground-color: transparent;\n\t\t\tbox-shadow: none;\n\t\t\tposition: absolute;\n\t\t}\n\n\t\t\t#header.alt.style1 .logo strong {\n\t\t\t\tcolor: #6fc3df;\n\t\t\t}\n\n\t\t\t#header.alt.style2 .logo strong {\n\t\t\t\tcolor: #8d82c4;\n\t\t\t}\n\n\t\t\t#header.alt.style3 .logo strong {\n\t\t\t\tcolor: #ec8d81;\n\t\t\t}\n\n\t\t\t#header.alt.style4 .logo strong {\n\t\t\t\tcolor: #e7b788;\n\t\t\t}\n\n\t\t\t#header.alt.style5 .logo strong {\n\t\t\t\tcolor: #8ea9e8;\n\t\t\t}\n\n\t\t\t#header.alt.style6 .logo strong {\n\t\t\t\tcolor: #87c5a4;\n\t\t\t}\n\n\t\tbody.is-preload #header.alt {\n\t\t\topacity: 0;\n\t\t}\n\n\t\t@media screen and (max-width: 1680px) {\n\n\t\t\t#header nav a[href=\"#menu\"] {\n\t\t\t\tpadding-right: 3.75em !important;\n\t\t\t}\n\n\t\t\t\t#header nav a[href=\"#menu\"]:last-child {\n\t\t\t\t\tpadding-right: 4.25em !important;\n\t\t\t\t}\n\n\t\t}\n\n\t\t@media screen and (max-width: 1280px) {\n\n\t\t\t#header nav a[href=\"#menu\"] {\n\t\t\t\tpadding-right: 4em !important;\n\t\t\t}\n\n\t\t\t\t#header nav a[href=\"#menu\"]:last-child {\n\t\t\t\t\tpadding-right: 4.5em !important;\n\t\t\t\t}\n\n\t\t}\n\n\t\t@media screen and (max-width: 736px) {\n\n\t\t\t#header {\n\t\t\t\theight: 2.75em;\n\t\t\t\tline-height: 2.75em;\n\t\t\t}\n\n\t\t\t\t#header .logo {\n\t\t\t\t\tpadding: 0 1em;\n\t\t\t\t}\n\n\t\t\t\t#header nav a {\n\t\t\t\t\tpadding: 0 0.5em;\n\t\t\t\t}\n\n\t\t\t\t\t#header nav a:last-child {\n\t\t\t\t\t\tpadding-right: 1em;\n\t\t\t\t\t}\n\n\t\t\t\t\t#header nav a[href=\"#menu\"] {\n\t\t\t\t\t\tpadding-right: 3.25em !important;\n\t\t\t\t\t}\n\n\t\t\t\t\t\t#header nav a[href=\"#menu\"]:before, #header nav a[href=\"#menu\"]:after {\n\t\t\t\t\t\t\tright: 0.75em;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t#header nav a[href=\"#menu\"]:last-child {\n\t\t\t\t\t\t\tpadding-right: 4em !important;\n\t\t\t\t\t\t}\n\n\t\t\t\t\t\t\t#header nav a[href=\"#menu\"]:last-child:before, #header nav a[href=\"#menu\"]:last-child:after {\n\t\t\t\t\t\t\t\tright: 1.5em;\n\t\t\t\t\t\t\t}\n\n\t\t}\n\n\t\t@media screen and (max-width: 480px) {\n\n\t\t\t#header .logo span {\n\t\t\t\tdisplay: none;\n\t\t\t}\n\n\t\t\t#header nav a[href=\"#menu\"] {\n\t\t\t\toverflow: hidden;\n\t\t\t\tpadding-right: 0 !important;\n\t\t\t\ttext-indent: 5em;\n\t\t\t\twhite-space: nowrap;\n\t\t\t\twidth: 5em;\n\t\t\t}\n\n\t\t\t\t#header nav a[href=\"#menu\"]:before, #header nav a[href=\"#menu\"]:after {\n\t\t\t\t\tright: 0;\n\t\t\t\t\twidth: inherit;\n\t\t\t\t}\n\n\t\t\t\t#header nav a[href=\"#menu\"]:last-child:before, #header nav a[href=\"#menu\"]:last-child:after {\n\t\t\t\t\twidth: 4em;\n\t\t\t\t\tright: 0;\n\t\t\t\t}\n\n\t\t}\n\n/* Banner */\n\n\t#banner {\n\t\t-moz-align-items: center;\n\t\t-webkit-align-items: center;\n\t\t-ms-align-items: center;\n\t\talign-items: center; \n\t\tdisplay: -moz-flex;\n\t\tdisplay: -webkit-flex;\n\t\tdisplay: -ms-flex;\n\t\tdisplay: flex;\n\t\tpadding: 12em 0 2em 0 ;\n\t\tbackground-attachment: fixed;\n\t\tbackground-position: center;\n\t\tbackground-repeat: no-repeat;\n\t\tbackground-size: cover;\n\t\tborder-bottom: 0 !important;\n\t\tcursor: default;\n\t\theight: 60vh;\n\t\tmargin-bottom: -3.25em;\n\t\tmax-height: 32em;\n\t\tmin-height: 22em;\n\t\tposition: relative;\n\t\ttop: -3.25em;\n\t}\n\n\t\t#banner:after {\n\t\t\t-moz-transition: opacity 2.5s ease;\n\t\t\t-webkit-transition: opacity 2.5s ease;\n\t\t\t-ms-transition: opacity 2.5s ease;\n\t\t\ttransition: opacity 2.5s ease;\n\t\t\t-moz-transition-delay: 0.75s;\n\t\t\t-webkit-transition-delay: 0.75s;\n\t\t\t-ms-transition-delay: 0.75s;\n\t\t\ttransition-delay: 0.75s;\n\t\t\tpointer-events: none;\n\t\t\tbackground-color: transparent;\n\t\t\tcontent: '';\n\t\t\tdisplay: block;\n\t\t\theight: 100%;\n\t\t\tleft: 0;\n\t\t\topacity: 0.85;\n\t\t\tposition: absolute;\n\t\t\ttop: 0;\n\t\t\twidth: 100%;\n\t\t\tz-index: 1;\n\t\t}\n\n\t\t#banner h1 {\n\t\t\tfont-size: 2.5em;\n\t\t}\n\n\t\t#banner > .inner {\n\t\t\t-moz-transition: opacity 1.5s ease, -moz-transform 0.5s ease-out, -moz-filter 0.5s ease, -webkit-filter 0.5s ease;\n\t\t\t-webkit-transition: opacity 1.5s ease, -webkit-transform 0.5s ease-out, -webkit-filter 0.5s ease, -webkit-filter 0.5s ease;\n\t\t\t-ms-transition: opacity 1.5s ease, -ms-transform 0.5s ease-out, -ms-filter 0.5s ease, -webkit-filter 0.5s ease;\n\t\t\ttransition: opacity 1.5s ease, transform 0.5s ease-out, filter 0.5s ease, -webkit-filter 0.5s ease;\n\t\t\tpadding: 0 !important;\n\t\t\tposition: relative;\n\t\t\tz-index: 2;\n\t\t}\n\n\t\t\t#banner > .inner .image {\n\t\t\t\tdisplay: none;\n\t\t\t}\n\n\t\t\t#banner > .inner header {\n\t\t\t\twidth: auto;\n\t\t\t}\n\n\t\t\t\t#banner > .inner header > :first-child {\n\t\t\t\t\twidth: auto;\n\t\t\t\t}\n\n\t\t\t\t\t#banner > .inner header > :first-child:after {\n\t\t\t\t\t\tmax-width: 100%;\n\t\t\t\t\t}\n\n\t\t\t#banner > .inner .content {\n\t\t\t\tdisplay: -moz-flex;\n\t\t\t\tdisplay: -webkit-flex;\n\t\t\t\tdisplay: -ms-flex;\n\t\t\t\tdisplay: flex;\n\t\t\t\t-moz-align-items: center;\n\t\t\t\t-webkit-align-items: center;\n\t\t\t\t-ms-align-items: center;\n\t\t\t\talign-items: center;\n\t\t\t\tmargin: 0 0 2em 0;\n\t\t\t}\n\n\t\t\t\t#banner > .inner .content > * {\n\t\t\t\t\tmargin-right: 1.5em;\n\t\t\t\t\tmargin-bottom: 0;\n\t\t\t\t}\n\n\t\t\t\t#banner > .inner .content > :last-child {\n\t\t\t\t\tmargin-right: 0;\n\t\t\t\t}\n\n\t\t\t\t#banner > .inner .content p {\n\t\t\t\t\tfont-size: 0.7em;\n\t\t\t\t\tfont-weight: 600;\n\t\t\t\t\tletter-spacing: 0.25em;\n\t\t\t\t\ttext-transform: uppercase;\n\t\t\t\t}\n\n\t\t#banner.major {\n\t\t\theight: 75vh;\n\t\t\tmin-height: 30em;\n\t\t\tmax-height: 50em;\n\t\t}\n\n\t\t\t#banner.major.alt {\n\t\t\t\topacity: 0.75;\n\t\t\t}\n\n\t\t#banner.style1:after {\n\t\t\tbackground-color: #6fc3df;\n\t\t}\n\n\t\t#banner.style2:after {\n\t\t\tbackground-color: #8d82c4;\n\t\t}\n\n\t\t#banner.style3:after {\n\t\t\tbackground-color: #ec8d81;\n\t\t}\n\n\t\t#banner.style4:after {\n\t\t\tbackground-color: #e7b788;\n\t\t}\n\n\t\t#banner.style5:after {\n\t\t\tbackground-color: #8ea9e8;\n\t\t}\n\n\t\t#banner.style6:after {\n\t\t\tbackground-color: #87c5a4;\n\t\t}\n\n\t\tbody.is-preload #banner:after {\n\t\t\topacity: 1.0;\n\t\t}\n\n\t\tbody.is-preload #banner > .inner {\n\t\t\t-moz-filter: blur(0.125em);\n\t\t\t-webkit-filter: blur(0.125em);\n\t\t\t-ms-filter: blur(0.125em);\n\t\t\tfilter: blur(0.125em);\n\t\t\t-moz-transform: translateX(-0.5em);\n\t\t\t-webkit-transform: translateX(-0.5em);\n\t\t\t-ms-transform: translateX(-0.5em);\n\t\t\ttransform: translateX(-0.5em);\n\t\t\topacity: 0;\n\t\t}\n\n\t\t@media screen and (max-width: 1280px) {\n\n\t\t\t#banner {\n\t\t\t\tbackground-attachment: scroll;\n\t\t\t}\n\n\t\t}\n\n\t\t@media screen and (max-width: 736px) {\n\n\t\t\t#banner {\n\t\t\t\tpadding: 5em 0 1em 0 ;\n\t\t\t\theight: auto;\n\t\t\t\tmargin-bottom: -2.75em;\n\t\t\t\tmax-height: none;\n\t\t\t\tmin-height: 0;\n\t\t\t\ttop: -2.75em;\n\t\t\t}\n\n\t\t\t\t#banner h1 {\n\t\t\t\t\tfont-size: 2em;\n\t\t\t\t}\n\n\t\t\t\t#banner > .inner .content {\n\t\t\t\t\tdisplay: block;\n\t\t\t\t}\n\n\t\t\t\t\t#banner > .inner .content > * {\n\t\t\t\t\t\tmargin-right: 0;\n\t\t\t\t\t\tmargin-bottom: 2em;\n\t\t\t\t\t}\n\n\t\t\t\t#banner.major {\n\t\t\t\t\theight: 75vh;\n\t\t\t\t\tmin-height: 0;\n\t\t\t\t\tmax-height: none;\n\t\t\t\t}\n\n\t\t}\n\n\t\t@media screen and (max-width: 480px) {\n\n\t\t\t#banner {\n\t\t\t\tpadding: 6em 0 2em 0 ;\n\t\t\t}\n\n\t\t\t\t#banner > .inner .content p br {\n\t\t\t\t\tdisplay: none;\n\t\t\t\t}\n\n\t\t\t\t#banner.major {\n\t\t\t\t\tpadding: 8em 0 4em 0 ;\n\t\t\t\t}\n\n\t\t}\n\n/* Main */\n\n\t#main {\n\t\tbackground-color: #2a2f4a;\n\t}\n\n\t\t#main > * {\n\t\t\tborder-top: solid 1px rgba(212, 212, 255, 0.1);\n\t\t}\n\n\t\t\t#main > *:first-child {\n\t\t\t\tborder-top: 0;\n\t\t\t}\n\n\t\t\t#main > * > .inner {\n\t\t\t\tpadding: 4em 0 2em 0 ;\n\t\t\t\tmargin: 0 auto;\n\t\t\t\tmax-width: 65em;\n\t\t\t\twidth: calc(100% - 6em);\n\t\t\t}\n\n\t\t\t\t@media screen and (max-width: 736px) {\n\n\t\t\t\t\t#main > * > .inner {\n\t\t\t\t\t\tpadding: 3em 0 1em 0 ;\n\t\t\t\t\t\twidth: calc(100% - 3em);\n\t\t\t\t\t}\n\n\t\t\t\t}\n\n\t\t#main.alt {\n\t\t\tbackground-color: transparent;\n\t\t\tborder-bottom: solid 1px rgba(212, 212, 255, 0.1);\n\t\t}\n\n/* Contact */\n\n\t#contact {\n\t\tborder-bottom: solid 1px rgba(212, 212, 255, 0.1);\n\t\toverflow-x: hidden;\n\t}\n\n\t\t#contact > .inner {\n\t\t\tdisplay: -moz-flex;\n\t\t\tdisplay: -webkit-flex;\n\t\t\tdisplay: -ms-flex;\n\t\t\tdisplay: flex;\n\t\t\tpadding: 0 !important;\n\t\t}\n\n\t\t\t#contact > .inner > :nth-child(2n - 1) {\n\t\t\t\tpadding: 4em 3em 2em 0 ;\n\t\t\t\tborder-right: solid 1px rgba(212, 212, 255, 0.1);\n\t\t\t\twidth: 60%;\n\t\t\t}\n\n\t\t\t#contact > .inner > :nth-child(2n) {\n\t\t\t\tpadding-left: 3em;\n\t\t\t\twidth: 40%;\n\t\t\t}\n\n\t\t\t#contact > .inner > .split {\n\t\t\t\tpadding: 0;\n\t\t\t}\n\n\t\t\t\t#contact > .inner > .split > * {\n\t\t\t\t\tpadding: 3em 0 1em 3em ;\n\t\t\t\t\tposition: relative;\n\t\t\t\t}\n\n\t\t\t\t\t#contact > .inner > .split > *:before {\n\t\t\t\t\t\tborder-top: solid 1px rgba(212, 212, 255, 0.1);\n\t\t\t\t\t\tcontent: '';\n\t\t\t\t\t\tdisplay: block;\n\t\t\t\t\t\tmargin-left: -3em;\n\t\t\t\t\t\tposition: absolute;\n\t\t\t\t\t\ttop: 0;\n\t\t\t\t\t\twidth: calc(100vw + 3em);\n\t\t\t\t\t}\n\n\t\t\t\t#contact > .inner > .split > :first-child:before {\n\t\t\t\t\tdisplay: none;\n\t\t\t\t}\n\n\t\t@media screen and (max-width: 980px) {\n\n\t\t\t#contact > .inner {\n\t\t\t\tdisplay: block;\n\t\t\t}\n\n\t\t\t\t#contact > .inner > :nth-child(2n - 1) {\n\t\t\t\t\tpadding: 4em 0 2em 0 ;\n\t\t\t\t\tborder-right: 0;\n\t\t\t\t\twidth: 100%;\n\t\t\t\t}\n\n\t\t\t\t#contact > .inner > :nth-child(2n) {\n\t\t\t\t\tpadding-left: 0;\n\t\t\t\t\twidth: 100%;\n\t\t\t\t}\n\n\t\t\t\t#contact > .inner > .split > * {\n\t\t\t\t\tpadding: 3em 0 1em 0 ;\n\t\t\t\t}\n\n\t\t\t\t#contact > .inner > .split > :first-child:before {\n\t\t\t\t\tdisplay: block;\n\t\t\t\t}\n\n\t\t}\n\n\t\t@media screen and (max-width: 736px) {\n\n\t\t\t#contact > .inner > :nth-child(2n - 1) {\n\t\t\t\tpadding: 3em 0 1em 0 ;\n\t\t\t}\n\n\t\t}\n\n/* Footer */\n\n\t#footer .copyright {\n\t\tfont-size: 0.8em;\n\t\tlist-style: none;\n\t\tpadding-left: 0;\n\t}\n\n\t\t#footer .copyright li {\n\t\t\tborder-left: solid 1px rgba(212, 212, 255, 0.1);\n\t\t\tcolor: rgba(244, 244, 255, 0.2);\n\t\t\tdisplay: inline-block;\n\t\t\tline-height: 1;\n\t\t\tmargin-left: 1em;\n\t\t\tpadding-left: 1em;\n\t\t}\n\n\t\t\t#footer .copyright li:first-child {\n\t\t\t\tborder-left: 0;\n\t\t\t\tmargin-left: 0;\n\t\t\t\tpadding-left: 0;\n\t\t\t}\n\n\t@media screen and (max-width: 480px) {\n\n\t\t#footer .copyright li {\n\t\t\tdisplay: block;\n\t\t\tborder-left: 0;\n\t\t\tmargin-left: 0;\n\t\t\tpadding-left: 0;\n\t\t\tline-height: inherit;\n\t\t}\n\n\t}\n\n/* Wrapper */\n\n\t#wrapper {\n\t\t-moz-transition: -moz-filter 0.35s ease, -webkit-filter 0.35s ease, opacity 0.375s ease-out;\n\t\t-webkit-transition: -webkit-filter 0.35s ease, -webkit-filter 0.35s ease, opacity 0.375s ease-out;\n\t\t-ms-transition: -ms-filter 0.35s ease, -webkit-filter 0.35s ease, opacity 0.375s ease-out;\n\t\ttransition: filter 0.35s ease, -webkit-filter 0.35s ease, opacity 0.375s ease-out;\n\t\tpadding-top: 3.25em;\n\t\theight: 100vh;\n\t}\n\n\t\t#wrapper.is-transitioning {\n\t\t\topacity: 0;\n\t\t}\n\n\t\t#wrapper > * > .inner {\n\t\t\tpadding: 4em 0 2em 0 ;\n\t\t\tmargin: 0 auto;\n\t\t\tmax-width: 65em;\n\t\t\twidth: calc(100% - 6em);\n\t\t}\n\n\t\t\t@media screen and (max-width: 736px) {\n\n\t\t\t\t#wrapper > * > .inner {\n\t\t\t\t\tpadding: 3em 0 1em 0 ;\n\t\t\t\t\twidth: calc(100% - 3em);\n\t\t\t\t}\n\n\t\t\t}\n\n\t\t@media screen and (max-width: 736px) {\n\n\t\t\t#wrapper {\n\t\t\t\tpadding-top: 2.75em;\n\t\t\t}\n\n\t\t}\n\n/* Menu */\n\n\t#menu {\n\t\t-moz-transition: -moz-transform 0.35s ease, opacity 0.35s ease, visibility 0.35s;\n\t\t-webkit-transition: -webkit-transform 0.35s ease, opacity 0.35s ease, visibility 0.35s;\n\t\t-ms-transition: -ms-transform 0.35s ease, opacity 0.35s ease, visibility 0.35s;\n\t\ttransition: transform 0.35s ease, opacity 0.35s ease, visibility 0.35s;\n\t\t-moz-align-items: center;\n\t\t-webkit-align-items: center;\n\t\t-ms-align-items: center;\n\t\talign-items: center;\n\t\tdisplay: -moz-flex;\n\t\tdisplay: -webkit-flex;\n\t\tdisplay: -ms-flex;\n\t\tdisplay: flex;\n\t\t-moz-justify-content: center;\n\t\t-webkit-justify-content: center;\n\t\t-ms-justify-content: center;\n\t\tjustify-content: center;\n\t\tpointer-events: none;\n\t\tbackground: rgb(0 0 0 / 50%);;\n\t\tbox-shadow: none;\n\t\theight: 100%;\n\t\tleft: 0;\n\t\topacity: 0;\n\t\toverflow: hidden;\n\t\tpadding: 3em 2em;\n\t\tposition: fixed;\n\t\ttop: 0;\n\t\tvisibility: hidden;\n\t\twidth: 100%;\n\t\tz-index: 10002;\n\t}\n\n\t\t#menu .inner {\n\t\t\t-moz-transition: -moz-transform 0.35s ease-out, opacity 0.35s ease, visibility 0.35s;\n\t\t\t-webkit-transition: -webkit-transform 0.35s ease-out, opacity 0.35s ease, visibility 0.35s;\n\t\t\t-ms-transition: -ms-transform 0.35s ease-out, opacity 0.35s ease, visibility 0.35s;\n\t\t\ttransition: transform 0.35s ease-out, opacity 0.35s ease, visibility 0.35s;\n\t\t\t-moz-transform: rotateX(20deg);\n\t\t\t-webkit-transform: rotateX(20deg);\n\t\t\t-ms-transform: rotateX(20deg);\n\t\t\ttransform: rotateX(20deg);\n\t\t\t-webkit-overflow-scrolling: touch;\n\t\t\tmax-width: 100%;\n\t\t\tmax-height: 100vh;\n\t\t\topacity: 0;\n\t\t\toverflow: auto;\n\t\t\ttext-align: center;\n\t\t\tvisibility: hidden;\n\t\t\twidth: 18em;\n\t\t}\n\n\t\t\t#menu .inner > :first-child {\n\t\t\t\tmargin-top: 2em;\n\t\t\t}\n\n\t\t\t#menu .inner > :last-child {\n\t\t\t\tmargin-bottom: 3em;\n\t\t\t}\n\n\t\t#menu ul {\n\t\t\tmargin: 0 0 1em 0;\n\t\t}\n\n\t\t\t#menu ul.links {\n\t\t\t\tlist-style: none;\n\t\t\t\tpadding: 0;\n\t\t\t}\n\n\t\t\t\t#menu ul.links > li {\n\t\t\t\t\tpadding: 0;\n\t\t\t\t}\n\n\t\t\t\t\t#menu ul.links > li > a:not(.button) {\n\t\t\t\t\t\tborder: 0;\n\t\t\t\t\t\tborder-top: solid 1px rgba(212, 212, 255, 0.1);\n\t\t\t\t\t\tdisplay: block;\n\t\t\t\t\t\tfont-size: 0.8em;\n\t\t\t\t\t\tfont-weight: 600;\n\t\t\t\t\t\tletter-spacing: 0.25em;\n\t\t\t\t\t\tline-height: 4em;\n\t\t\t\t\t\ttext-decoration: none;\n\t\t\t\t\t\ttext-transform: uppercase;\n\t\t\t\t\t}\n\n\t\t\t\t\t#menu ul.links > li > .button {\n\t\t\t\t\t\tdisplay: block;\n\t\t\t\t\t\tmargin: 0.5em 0 0 0;\n\t\t\t\t\t}\n\n\t\t\t\t\t#menu ul.links > li:first-child > a:not(.button) {\n\t\t\t\t\t\tborder-top: 0 !important;\n\t\t\t\t\t}\n\n\t\t#menu .close {\n\t\t\t-moz-transition: color 0.2s ease-in-out;\n\t\t\t-webkit-transition: color 0.2s ease-in-out;\n\t\t\t-ms-transition: color 0.2s ease-in-out;\n\t\t\ttransition: color 0.2s ease-in-out;\n\t\t\t-webkit-tap-highlight-color: rgba(0, 0, 0, 0);\n\t\t\tborder: 0;\n\t\t\tcursor: pointer;\n\t\t\tdisplay: block;\n\t\t\theight: 4em;\n\t\t\tline-height: 4em;\n\t\t\toverflow: hidden;\n\t\t\tpadding-right: 1.25em;\n\t\t\tposition: absolute;\n\t\t\tright: 0;\n\t\t\ttext-align: right;\n\t\t\ttext-indent: 8em;\n\t\t\ttop: 0;\n\t\t\tvertical-align: middle;\n\t\t\twhite-space: nowrap;\n\t\t\twidth: 8em;\n\t\t}\n\n\t\t\t#menu .close:before, #menu .close:after {\n\t\t\t\t-moz-transition: opacity 0.2s ease-in-out;\n\t\t\t\t-webkit-transition: opacity 0.2s ease-in-out;\n\t\t\t\t-ms-transition: opacity 0.2s ease-in-out;\n\t\t\t\ttransition: opacity 0.2s ease-in-out;\n\t\t\t\tbackground-position: center;\n\t\t\t\tbackground-repeat: no-repeat;\n\t\t\t\tcontent: '';\n\t\t\t\tdisplay: block;\n\t\t\t\theight: 4em;\n\t\t\t\tposition: absolute;\n\t\t\t\tright: 0;\n\t\t\t\ttop: 0;\n\t\t\t\twidth: 4em;\n\t\t\t}\n\n\t\t\t#menu .close:before {\n\t\t\t\tbackground-image: url(\"data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' width='20px' height='20px' viewBox='0 0 20 20' zoomAndPan='disable'%3E%3Cstyle%3Eline %7B stroke: %23ffffff%3B stroke-width: 2%3B %7D%3C/style%3E%3Cline x1='0' y1='0' x2='20' y2='20' /%3E%3Cline x1='20' y1='0' x2='0' y2='20' /%3E%3C/svg%3E\");\n\t\t\t}\n\n\t\t\t#menu .close:after {\n\t\t\t\tbackground-image: url(\"data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' xmlns:xlink='http://www.w3.org/1999/xlink' width='20px' height='20px' viewBox='0 0 20 20' zoomAndPan='disable'%3E%3Cstyle%3Eline %7B stroke: %239bf1ff%3B stroke-width: 2%3B %7D%3C/style%3E%3Cline x1='0' y1='0' x2='20' y2='20' /%3E%3Cline x1='20' y1='0' x2='0' y2='20' /%3E%3C/svg%3E\");\n\t\t\t\topacity: 0;\n\t\t\t}\n\n\t\t\t#menu .close:hover:after, #menu .close:active:after {\n\t\t\t\topacity: 1;\n\t\t\t}\n\n\tbody.is-ie #menu {\n\t\tbackground: rgba(42, 47, 74, 0.975);\n\t}\n\n\tbody.is-menu-visible #wrapper {\n\t\t-moz-filter: blur(0.5em);\n\t\t-webkit-filter: blur(0.5em);\n\t\t-ms-filter: blur(0.5em);\n\t\tfilter: blur(0.5em);\n\t}\n\n\tbody.is-menu-visible #menu {\n\t\tpointer-events: auto;\n\t\topacity: 1;\n\t\tvisibility: visible;\n\t}\n\n\t\tbody.is-menu-visible #menu .inner {\n\t\t\t-moz-transform: none;\n\t\t\t-webkit-transform: none;\n\t\t\t-ms-transform: none;\n\t\t\ttransform: none;\n\t\t\topacity: 1;\n\t\t\tvisibility: visible;\n\t\t}"
  },
  {
    "path": "app/src/main/assets/web/assets/js/dist.js",
    "content": "/*!\n * Powered by uglifiyJS v2.6.1, Build by http://tool.uis.cc/jsmin/\n * build time: Sun Jul 25 2021 19:58:00 GMT+0800 (中国标准时间)\n*/\n!function(e,t){\"use strict\";\"object\"==typeof module&&\"object\"==typeof module.exports?module.exports=e.document?t(e,!0):function(e){if(!e.document)throw new Error(\"jQuery requires a window with a document\");return t(e)}:t(e)}(\"undefined\"!=typeof window?window:this,function(C,e){\"use strict\";function b(e,t,n){var r,i,o=(n=n||E).createElement(\"script\");if(o.text=e,t)for(r in c)(i=t[r]||t.getAttribute&&t.getAttribute(r))&&o.setAttribute(r,i);n.head.appendChild(o).parentNode.removeChild(o)}function w(e){return null==e?e+\"\":\"object\"==typeof e||\"function\"==typeof e?n[o.call(e)]||\"object\":typeof e}function d(e){var t=!!e&&\"length\"in e&&e.length,n=w(e);return!m(e)&&!x(e)&&(\"array\"===n||0===t||\"number\"==typeof t&&t>0&&t-1 in e)}function A(e,t){return e.nodeName&&e.nodeName.toLowerCase()===t.toLowerCase()}function j(e,n,r){return m(n)?k.grep(e,function(e,t){return!!n.call(e,t,e)!==r}):n.nodeType?k.grep(e,function(e){return e===n!==r}):\"string\"!=typeof n?k.grep(e,function(e){return-1<i.call(n,e)!==r}):k.filter(n,e,r)}function P(e,t){for(;(e=e[t])&&1!==e.nodeType;);return e}function M(e){return e}function I(e){throw e}function W(e,t,n,r){var i;try{e&&m(i=e.promise)?i.call(e).done(t).fail(n):e&&m(i=e.then)?i.call(e,t,n):t.apply(void 0,[e].slice(r))}catch(e){n.apply(void 0,[e])}}function B(){E.removeEventListener(\"DOMContentLoaded\",B),C.removeEventListener(\"load\",B),k.ready()}function X(e,t){return t.toUpperCase()}function V(e){return e.replace(z,\"ms-\").replace(U,X)}function Y(){this.expando=k.expando+Y.uid++}function ee(e,t,n){var r,i;if(void 0===n&&1===e.nodeType)if(r=\"data-\"+t.replace(Z,\"-$&\").toLowerCase(),\"string\"==typeof(n=e.getAttribute(r))){try{n=\"true\"===(i=n)||\"false\"!==i&&(\"null\"===i?null:i===+i+\"\"?+i:K.test(i)?JSON.parse(i):i)}catch(e){}J.set(e,t,n)}else n=void 0;return n}function le(e,t,n,r){var i,o,a=20,s=r?function(){return r.cur()}:function(){return k.css(e,t,\"\")},u=s(),l=n&&n[3]||(k.cssNumber[t]?\"\":\"px\"),c=e.nodeType&&(k.cssNumber[t]||\"px\"!==l&&+u)&&ne.exec(k.css(e,t));if(c&&c[3]!==l){for(u/=2,l=l||c[3],c=+u||1;a--;)k.style(e,t,c+l),(1-o)*(1-(o=s()/u||.5))<=0&&(a=0),c/=o;c*=2,k.style(e,t,c+l),n=n||[]}return n&&(c=+c||+u||0,i=n[1]?c+(n[1]+1)*n[2]:+n[2],r&&(r.unit=l,r.start=c,r.end=i)),i}function fe(e,t){for(var n,r,i,o,a,s,u,l=[],c=0,f=e.length;f>c;c++)(r=e[c]).style&&(n=r.style.display,t?(\"none\"===n&&(l[c]=Q.get(r,\"display\")||null,l[c]||(r.style.display=\"\")),\"\"===r.style.display&&se(r)&&(l[c]=(u=a=o=void 0,a=(i=r).ownerDocument,s=i.nodeName,(u=ce[s])||(o=a.body.appendChild(a.createElement(s)),u=k.css(o,\"display\"),o.parentNode.removeChild(o),\"none\"===u&&(u=\"block\"),ce[s]=u)))):\"none\"!==n&&(l[c]=\"none\",Q.set(r,\"display\",n)));for(c=0;f>c;c++)null!=l[c]&&(e[c].style.display=l[c]);return e}function ve(e,t){var n;return n=\"undefined\"!=typeof e.getElementsByTagName?e.getElementsByTagName(t||\"*\"):\"undefined\"!=typeof e.querySelectorAll?e.querySelectorAll(t||\"*\"):[],void 0===t||t&&A(e,t)?k.merge([e],n):n}function ye(e,t){for(var n=0,r=e.length;r>n;n++)Q.set(e[n],\"globalEval\",!t||Q.get(t[n],\"globalEval\"))}function we(e,t,n,r,i){for(var o,a,s,u,l,c,f=t.createDocumentFragment(),p=[],d=0,h=e.length;h>d;d++)if((o=e[d])||0===o)if(\"object\"===w(o))k.merge(p,o.nodeType?[o]:o);else if(be.test(o)){for(a=a||f.appendChild(t.createElement(\"div\")),s=(de.exec(o)||[\"\",\"\"])[1].toLowerCase(),u=ge[s]||ge._default,a.innerHTML=u[1]+k.htmlPrefilter(o)+u[2],c=u[0];c--;)a=a.lastChild;k.merge(p,a.childNodes),(a=f.firstChild).textContent=\"\"}else p.push(t.createTextNode(o));for(f.textContent=\"\",d=0;o=p[d++];)if(r&&-1<k.inArray(o,r))i&&i.push(o);else if(l=oe(o),a=ve(f.appendChild(o),\"script\"),l&&ye(a),n)for(c=0;o=a[c++];)he.test(o.type||\"\")&&n.push(o);return f}function ke(){return!0}function Se(){return!1}function Ne(e,t){return e===function(){try{return E.activeElement}catch(e){}}()==(\"focus\"===t)}function Ae(e,t,n,r,i,o){var a,s;if(\"object\"==typeof t){for(s in\"string\"!=typeof n&&(r=r||n,n=void 0),t)Ae(e,s,n,r,t[s],o);return e}if(null==r&&null==i?(i=n,r=n=void 0):null==i&&(\"string\"==typeof n?(i=r,r=void 0):(i=r,r=n,n=void 0)),!1===i)i=Se;else if(!i)return e;return 1===o&&(a=i,(i=function(e){return k().off(e),a.apply(this,arguments)}).guid=a.guid||(a.guid=k.guid++)),e.each(function(){k.event.add(this,t,i,r,n)})}function De(e,i,o){o?(Q.set(e,i,!1),k.event.add(e,i,{namespace:!1,handler:function(e){var t,n,r=Q.get(this,i);if(1&e.isTrigger&&this[i]){if(r.length)(k.event.special[i]||{}).delegateType&&e.stopPropagation();else if(r=s.call(arguments),Q.set(this,i,r),t=o(this,i),this[i](),r!==(n=Q.get(this,i))||t?Q.set(this,i,!1):n={},r!==n)return e.stopImmediatePropagation(),e.preventDefault(),n.value}else r.length&&(Q.set(this,i,{value:k.event.trigger(k.extend(r[0],k.Event.prototype),r.slice(1),this)}),e.stopImmediatePropagation())}})):void 0===Q.get(e,i)&&k.event.add(e,i,ke)}function Oe(e,t){return A(e,\"table\")&&A(11!==t.nodeType?t:t.firstChild,\"tr\")&&k(e).children(\"tbody\")[0]||e}function Pe(e){return e.type=(null!==e.getAttribute(\"type\"))+\"/\"+e.type,e}function Re(e){return\"true/\"===(e.type||\"\").slice(0,5)?e.type=e.type.slice(5):e.removeAttribute(\"type\"),e}function Me(e,t){var n,r,i,o,a,s,u,l;if(1===t.nodeType){if(Q.hasData(e)&&(o=Q.access(e),a=Q.set(t,o),l=o.events))for(i in delete a.handle,a.events={},l)for(n=0,r=l[i].length;r>n;n++)k.event.add(t,i,l[i][n]);J.hasData(e)&&(s=J.access(e),u=k.extend({},s),J.set(t,u))}}function Ie(n,r,i,o){r=g.apply([],r);var e,t,a,s,u,l,c=0,f=n.length,p=f-1,d=r[0],h=m(d);if(h||f>1&&\"string\"==typeof d&&!y.checkClone&&Le.test(d))return n.each(function(e){var t=n.eq(e);h&&(r[0]=d.call(this,e,t.html())),Ie(t,r,i,o)});if(f&&(t=(e=we(r,n[0].ownerDocument,!1,n,o)).firstChild,1===e.childNodes.length&&(e=t),t||o)){for(s=(a=k.map(ve(e,\"script\"),Pe)).length;f>c;c++)u=e,c!==p&&(u=k.clone(u,!0,!0),s&&k.merge(a,ve(u,\"script\"))),i.call(n[c],u,c);if(s)for(l=a[a.length-1].ownerDocument,k.map(a,Re),c=0;s>c;c++)u=a[c],he.test(u.type||\"\")&&!Q.access(u,\"globalEval\")&&k.contains(l,u)&&(u.src&&\"module\"!==(u.type||\"\").toLowerCase()?k._evalUrl&&!u.noModule&&k._evalUrl(u.src,{nonce:u.nonce||u.getAttribute(\"nonce\")}):b(u.textContent.replace(He,\"\"),u,l))}return n}function We(e,t,n){for(var r,i=t?k.filter(t,e):e,o=0;null!=(r=i[o]);o++)n||1!==r.nodeType||k.cleanData(ve(r)),r.parentNode&&(n&&oe(r)&&ye(ve(r,\"script\")),r.parentNode.removeChild(r));return e}function _e(e,t,n){var r,i,o,a,s=e.style;return(n=n||Fe(e))&&(\"\"!==(a=n.getPropertyValue(t)||n[t])||oe(e)||(a=k.style(e,t)),!y.pixelBoxStyles()&&$e.test(a)&&Be.test(t)&&(r=s.width,i=s.minWidth,o=s.maxWidth,s.minWidth=s.maxWidth=s.width=a,a=n.width,s.width=r,s.minWidth=i,s.maxWidth=o)),void 0!==a?a+\"\":a}function ze(e,t){return{get:function(){return e()?void delete this.get:(this.get=t).apply(this,arguments)}}}function Ge(e){var t=k.cssProps[e]||Ve[e];return t||(e in Xe?e:Ve[e]=function(e){for(var t=e[0].toUpperCase()+e.slice(1),n=Ue.length;n--;)if((e=Ue[n]+t)in Xe)return e}(e)||e)}function Ze(e,t,n){var r=ne.exec(t);return r?Math.max(0,r[2]-(n||0))+(r[3]||\"px\"):t}function et(e,t,n,r,i,o){var a=\"width\"===t?1:0,s=0,u=0;if(n===(r?\"border\":\"content\"))return 0;for(;4>a;a+=2)\"margin\"===n&&(u+=k.css(e,n+re[a],!0,i)),r?(\"content\"===n&&(u-=k.css(e,\"padding\"+re[a],!0,i)),\"margin\"!==n&&(u-=k.css(e,\"border\"+re[a]+\"Width\",!0,i))):(u+=k.css(e,\"padding\"+re[a],!0,i),\"padding\"!==n?u+=k.css(e,\"border\"+re[a]+\"Width\",!0,i):s+=k.css(e,\"border\"+re[a]+\"Width\",!0,i));return!r&&o>=0&&(u+=Math.max(0,Math.ceil(e[\"offset\"+t[0].toUpperCase()+t.slice(1)]-o-u-s-.5))||0),u}function tt(e,t,n){var r=Fe(e),i=(!y.boxSizingReliable()||n)&&\"border-box\"===k.css(e,\"boxSizing\",!1,r),o=i,a=_e(e,t,r),s=\"offset\"+t[0].toUpperCase()+t.slice(1);if($e.test(a)){if(!n)return a;a=\"auto\"}return(!y.boxSizingReliable()&&i||\"auto\"===a||!parseFloat(a)&&\"inline\"===k.css(e,\"display\",!1,r))&&e.getClientRects().length&&(i=\"border-box\"===k.css(e,\"boxSizing\",!1,r),(o=s in e)&&(a=e[s])),(a=parseFloat(a)||0)+et(e,t,n||(i?\"border\":\"content\"),o,r,a)+\"px\"}function nt(e,t,n,r,i){return new nt.prototype.init(e,t,n,r,i)}function lt(){it&&(!1===E.hidden&&C.requestAnimationFrame?C.requestAnimationFrame(lt):C.setTimeout(lt,k.fx.interval),k.fx.tick())}function ct(){return C.setTimeout(function(){rt=void 0}),rt=Date.now()}function ft(e,t){var n,r=0,i={height:e};for(t=t?1:0;4>r;r+=2-t)i[\"margin\"+(n=re[r])]=i[\"padding\"+n]=e;return t&&(i.opacity=i.width=e),i}function pt(e,t,n){for(var r,i=(dt.tweeners[t]||[]).concat(dt.tweeners[\"*\"]),o=0,a=i.length;a>o;o++)if(r=i[o].call(n,t,e))return r}function dt(o,e,t){var n,a,r=0,i=dt.prefilters.length,s=k.Deferred().always(function(){delete u.elem}),u=function(){if(a)return!1;for(var e=rt||ct(),t=Math.max(0,l.startTime+l.duration-e),n=1-(t/l.duration||0),r=0,i=l.tweens.length;i>r;r++)l.tweens[r].run(n);return s.notifyWith(o,[l,n,t]),1>n&&i?t:(i||s.notifyWith(o,[l,1,0]),s.resolveWith(o,[l]),!1)},l=s.promise({elem:o,props:k.extend({},e),opts:k.extend(!0,{specialEasing:{},easing:k.easing._default},t),originalProperties:e,originalOptions:t,startTime:rt||ct(),duration:t.duration,tweens:[],createTween:function(e,t){var n=k.Tween(o,l.opts,e,t,l.opts.specialEasing[e]||l.opts.easing);return l.tweens.push(n),n},stop:function(e){var t=0,n=e?l.tweens.length:0;if(a)return this;for(a=!0;n>t;t++)l.tweens[t].run(1);return e?(s.notifyWith(o,[l,1,0]),s.resolveWith(o,[l,e])):s.rejectWith(o,[l,e]),this}}),c=l.props;for((!function(e,t){var n,r,i,o,a;for(n in e)if(i=t[r=V(n)],o=e[n],Array.isArray(o)&&(i=o[1],o=e[n]=o[0]),n!==r&&(e[r]=o,delete e[n]),(a=k.cssHooks[r])&&\"expand\"in a)for(n in o=a.expand(o),delete e[r],o)n in e||(e[n]=o[n],t[n]=i);else t[r]=i}(c,l.opts.specialEasing));i>r;r++)if(n=dt.prefilters[r].call(l,o,c,l.opts))return m(n.stop)&&(k._queueHooks(l.elem,l.opts.queue).stop=n.stop.bind(n)),n;return k.map(c,pt,l),m(l.opts.start)&&l.opts.start.call(o,l),l.progress(l.opts.progress).done(l.opts.done,l.opts.complete).fail(l.opts.fail).always(l.opts.always),k.fx.timer(k.extend(u,{elem:o,anim:l,queue:l.opts.queue})),l}function mt(e){return(e.match(R)||[]).join(\" \")}function xt(e){return e.getAttribute&&e.getAttribute(\"class\")||\"\"}function bt(e){return Array.isArray(e)?e:\"string\"==typeof e&&e.match(R)||[]}function qt(n,e,r,i){var t;if(Array.isArray(e))k.each(e,function(e,t){r||Nt.test(n)?i(n,t):qt(n+\"[\"+(\"object\"==typeof t&&null!=t?e:\"\")+\"]\",t,r,i)});else if(r||\"object\"!==w(e))i(n,e);else for(t in e)qt(n+\"[\"+t+\"]\",e[t],r,i)}function Bt(o){return function(e,t){\"string\"!=typeof e&&(t=e,e=\"*\");var n,r=0,i=e.toLowerCase().match(R)||[];if(m(t))for(;n=i[r++];)\"+\"===n[0]?(n=n.slice(1)||\"*\",(o[n]=o[n]||[]).unshift(t)):(o[n]=o[n]||[]).push(t)}}function _t(t,i,o,a){function l(e){var r;return s[e]=!0,k.each(t[e]||[],function(e,t){var n=t(i,o,a);return\"string\"!=typeof n||u||s[n]?u?!(r=n):void 0:(i.dataTypes.unshift(n),l(n),!1)}),r}var s={},u=t===Wt;return l(i.dataTypes[0])||!s[\"*\"]&&l(\"*\")}function zt(e,t){var n,r,i=k.ajaxSettings.flatOptions||{};for(n in t)void 0!==t[n]&&((i[n]?e:r||(r={}))[n]=t[n]);return r&&k.extend(!0,e,r),e}var t=[],E=C.document,r=Object.getPrototypeOf,s=t.slice,g=t.concat,u=t.push,i=t.indexOf,n={},o=n.toString,v=n.hasOwnProperty,a=v.toString,l=a.call(Object),y={},m=function(e){return\"function\"==typeof e&&\"number\"!=typeof e.nodeType},x=function(e){return null!=e&&e===e.window},c={type:!0,src:!0,nonce:!0,noModule:!0},f=\"3.4.1\",k=function(e,t){return new k.fn.init(e,t)},p=/^[\\s\\uFEFF\\xA0]+|[\\s\\uFEFF\\xA0]+$/g;k.fn=k.prototype={jquery:f,constructor:k,length:0,toArray:function(){return s.call(this)},get:function(e){return null==e?s.call(this):0>e?this[e+this.length]:this[e]},pushStack:function(e){var t=k.merge(this.constructor(),e);return t.prevObject=this,t},each:function(e){return k.each(this,e)},map:function(n){return this.pushStack(k.map(this,function(e,t){return n.call(e,t,e)}))},slice:function(){return this.pushStack(s.apply(this,arguments))},first:function(){return this.eq(0)},last:function(){return this.eq(-1)},eq:function(e){var t=this.length,n=+e+(0>e?t:0);return this.pushStack(n>=0&&t>n?[this[n]]:[])},end:function(){return this.prevObject||this.constructor()},push:u,sort:t.sort,splice:t.splice},k.extend=k.fn.extend=function(){var e,t,n,r,i,o,a=arguments[0]||{},s=1,u=arguments.length,l=!1;for(\"boolean\"==typeof a&&(l=a,a=arguments[s]||{},s++),\"object\"==typeof a||m(a)||(a={}),s===u&&(a=this,s--);u>s;s++)if(null!=(e=arguments[s]))for(t in e)r=e[t],\"__proto__\"!==t&&a!==r&&(l&&r&&(k.isPlainObject(r)||(i=Array.isArray(r)))?(n=a[t],o=i&&!Array.isArray(n)?[]:i||k.isPlainObject(n)?n:{},i=!1,a[t]=k.extend(l,o,r)):void 0!==r&&(a[t]=r));return a},k.extend({expando:\"jQuery\"+(f+Math.random()).replace(/\\D/g,\"\"),isReady:!0,error:function(e){throw new Error(e)},noop:function(){},isPlainObject:function(e){var t,n;return!(!e||\"[object Object]\"!==o.call(e)||(t=r(e))&&(\"function\"!=typeof(n=v.call(t,\"constructor\")&&t.constructor)||a.call(n)!==l))},isEmptyObject:function(e){var t;for(t in e)return!1;return!0},globalEval:function(e,t){b(e,{nonce:t&&t.nonce})},each:function(e,t){var n,r=0;if(d(e))for(n=e.length;n>r&&!1!==t.call(e[r],r,e[r]);r++);else for(r in e)if(!1===t.call(e[r],r,e[r]))break;return e},trim:function(e){return null==e?\"\":(e+\"\").replace(p,\"\")},makeArray:function(e,t){var n=t||[];return null!=e&&(d(Object(e))?k.merge(n,\"string\"==typeof e?[e]:e):u.call(n,e)),n},inArray:function(e,t,n){return null==t?-1:i.call(t,e,n)},merge:function(e,t){for(var n=+t.length,r=0,i=e.length;n>r;r++)e[i++]=t[r];return e.length=i,e},grep:function(e,t,n){for(var r=[],i=0,o=e.length,a=!n;o>i;i++)!t(e[i],i)!==a&&r.push(e[i]);return r},map:function(e,t,n){var r,i,o=0,a=[];if(d(e))for(r=e.length;r>o;o++)null!=(i=t(e[o],o,n))&&a.push(i);else for(o in e)null!=(i=t(e[o],o,n))&&a.push(i);return g.apply([],a)},guid:1,support:y}),\"function\"==typeof Symbol&&(k.fn[Symbol.iterator]=t[Symbol.iterator]),k.each(\"Boolean Number String Function Array Date RegExp Object Error Symbol\".split(\" \"),function(e,t){n[\"[object \"+t+\"]\"]=t.toLowerCase()});var h=function(n){function se(t,e,n,r){var i,o,a,s,u,l,c,f=e&&e.ownerDocument,p=e?e.nodeType:9;if(n=n||[],\"string\"!=typeof t||!t||1!==p&&9!==p&&11!==p)return n;if(!r&&((e?e.ownerDocument||e:m)!==C&&T(e),e=e||C,E)){if(11!==p&&(u=Z.exec(t)))if(i=u[1]){if(9===p){if(!(a=e.getElementById(i)))return n;if(a.id===i)return n.push(a),n}else if(f&&(a=f.getElementById(i))&&y(e,a)&&a.id===i)return n.push(a),n}else{if(u[2])return H.apply(n,e.getElementsByTagName(t)),n;if((i=u[3])&&d.getElementsByClassName&&e.getElementsByClassName)return H.apply(n,e.getElementsByClassName(i)),n}if(d.qsa&&!A[t+\" \"]&&(!v||!v.test(t))&&(1!==p||\"object\"!==e.nodeName.toLowerCase())){if(c=t,f=e,1===p&&U.test(t)){for((s=e.getAttribute(\"id\"))?s=s.replace(re,ie):e.setAttribute(\"id\",s=k),o=(l=h(t)).length;o--;)l[o]=\"#\"+s+\" \"+xe(l[o]);c=l.join(\",\"),f=ee.test(t)&&ye(e.parentNode)||e}try{return H.apply(n,f.querySelectorAll(c)),n}catch(e){A(t,!0)}finally{s===k&&e.removeAttribute(\"id\")}}}return g(t.replace(B,\"$1\"),e,n,r)}function ue(){var r=[];return function e(t,n){return r.push(t+\" \")>b.cacheLength&&delete e[r.shift()],e[t+\" \"]=n}}function le(e){return e[k]=!0,e}function ce(e){var t=C.createElement(\"fieldset\");try{return!!e(t)}catch(e){return!1}finally{t.parentNode&&t.parentNode.removeChild(t),t=null}}function fe(e,t){for(var n=e.split(\"|\"),r=n.length;r--;)b.attrHandle[n[r]]=t}function pe(e,t){var n=t&&e,r=n&&1===e.nodeType&&1===t.nodeType&&e.sourceIndex-t.sourceIndex;if(r)return r;if(n)for(;n=n.nextSibling;)if(n===t)return-1;return e?1:-1}function de(t){return function(e){return\"input\"===e.nodeName.toLowerCase()&&e.type===t}}function he(n){return function(e){var t=e.nodeName.toLowerCase();return(\"input\"===t||\"button\"===t)&&e.type===n}}function ge(t){return function(e){return\"form\"in e?e.parentNode&&!1===e.disabled?\"label\"in e?\"label\"in e.parentNode?e.parentNode.disabled===t:e.disabled===t:e.isDisabled===t||e.isDisabled!==!t&&ae(e)===t:e.disabled===t:\"label\"in e&&e.disabled===t}}function ve(a){return le(function(o){return o=+o,le(function(e,t){for(var n,r=a([],e.length,o),i=r.length;i--;)e[n=r[i]]&&(e[n]=!(t[n]=e[n]))})})}function ye(e){return e&&\"undefined\"!=typeof e.getElementsByTagName&&e}function me(){}function xe(e){for(var t=0,n=e.length,r=\"\";n>t;t++)r+=e[t].value;return r}function be(s,e,t){var u=e.dir,l=e.next,c=l||u,f=t&&\"parentNode\"===c,p=r++;return e.first?function(e,t,n){for(;e=e[u];)if(1===e.nodeType||f)return s(e,t,n);return!1}:function(e,t,n){var r,i,o,a=[S,p];if(n){for(;e=e[u];)if((1===e.nodeType||f)&&s(e,t,n))return!0}else for(;e=e[u];)if(1===e.nodeType||f)if(i=(o=e[k]||(e[k]={}))[e.uniqueID]||(o[e.uniqueID]={}),l&&l===e.nodeName.toLowerCase())e=e[u]||e;else{if((r=i[c])&&r[0]===S&&r[1]===p)return a[2]=r[2];if((i[c]=a)[2]=s(e,t,n))return!0}return!1}}function we(i){return 1<i.length?function(e,t,n){for(var r=i.length;r--;)if(!i[r](e,t,n))return!1;return!0}:i[0]}function Te(e,t,n,r,i){for(var o,a=[],s=0,u=e.length,l=null!=t;u>s;s++)(o=e[s])&&(n&&!n(o,r,i)||(a.push(o),l&&t.push(s)));return a}function Ce(d,h,g,v,y,e){return v&&!v[k]&&(v=Ce(v)),y&&!y[k]&&(y=Ce(y,e)),le(function(e,t,n,r){var i,o,a,s=[],u=[],l=t.length,c=e||function(e,t,n){for(var r=0,i=t.length;i>r;r++)se(e,t[r],n);return n}(h||\"*\",n.nodeType?[n]:n,[]),f=!d||!e&&h?c:Te(c,s,d,n,r),p=g?y||(e?d:l||v)?[]:t:f;if(g&&g(f,p,n,r),v)for(i=Te(p,u),v(i,[],n,r),o=i.length;o--;)(a=i[o])&&(p[u[o]]=!(f[u[o]]=a));if(e){if(y||d){if(y){for(i=[],o=p.length;o--;)(a=p[o])&&i.push(f[o]=a);y(null,p=[],i,r)}for(o=p.length;o--;)(a=p[o])&&-1<(i=y?P(e,a):s[o])&&(e[i]=!(t[i]=a))}}else p=Te(p===t?p.splice(l,p.length):p),y?y(null,t,p,r):H.apply(t,p)})}function Ee(e){for(var i,t,n,r=e.length,o=b.relative[e[0].type],a=o||b.relative[\" \"],s=o?1:0,u=be(function(e){return e===i},a,!0),l=be(function(e){return-1<P(i,e)},a,!0),c=[function(e,t,n){var r=!o&&(n||t!==w)||((i=t).nodeType?u(e,t,n):l(e,t,n));return i=null,r}];r>s;s++)if(t=b.relative[e[s].type])c=[be(we(c),t)];else{if((t=b.filter[e[s].type].apply(null,e[s].matches))[k]){for(n=++s;r>n&&!b.relative[e[n].type];n++);return Ce(s>1&&we(c),s>1&&xe(e.slice(0,s-1).concat({value:\" \"===e[s-2].type?\"*\":\"\"})).replace(B,\"$1\"),t,n>s&&Ee(e.slice(s,n)),r>n&&Ee(e=e.slice(n)),r>n&&xe(e))}c.push(t)}return we(c)}var e,d,b,o,i,h,f,g,w,u,l,T,C,a,E,v,s,c,y,k=\"sizzle\"+1*new Date,m=n.document,S=0,r=0,p=ue(),x=ue(),N=ue(),A=ue(),D=function(e,t){return e===t&&(l=!0),0},j={}.hasOwnProperty,t=[],q=t.pop,L=t.push,H=t.push,O=t.slice,P=function(e,t){for(var n=0,r=e.length;r>n;n++)if(e[n]===t)return n;return-1},R=\"checked|selected|async|autofocus|autoplay|controls|defer|disabled|hidden|ismap|loop|multiple|open|readonly|required|scoped\",M=\"[\\\\x20\\\\t\\\\r\\\\n\\\\f]\",I=\"(?:\\\\\\\\.|[\\\\w-]|[^\\x00-\\\\xa0])+\",W=\"\\\\[\"+M+\"*(\"+I+\")(?:\"+M+\"*([*^$|!~]?=)\"+M+\"*(?:'((?:\\\\\\\\.|[^\\\\\\\\'])*)'|\\\"((?:\\\\\\\\.|[^\\\\\\\\\\\"])*)\\\"|(\"+I+\"))|)\"+M+\"*\\\\]\",$=\":(\"+I+\")(?:\\\\((('((?:\\\\\\\\.|[^\\\\\\\\'])*)'|\\\"((?:\\\\\\\\.|[^\\\\\\\\\\\"])*)\\\")|((?:\\\\\\\\.|[^\\\\\\\\()[\\\\]]|\"+W+\")*)|.*)\\\\)|)\",F=new RegExp(M+\"+\",\"g\"),B=new RegExp(\"^\"+M+\"+|((?:^|[^\\\\\\\\])(?:\\\\\\\\.)*)\"+M+\"+$\",\"g\"),_=new RegExp(\"^\"+M+\"*,\"+M+\"*\"),z=new RegExp(\"^\"+M+\"*([>+~]|\"+M+\")\"+M+\"*\"),U=new RegExp(M+\"|>\"),X=new RegExp($),V=new RegExp(\"^\"+I+\"$\"),G={ID:new RegExp(\"^#(\"+I+\")\"),CLASS:new RegExp(\"^\\\\.(\"+I+\")\"),TAG:new RegExp(\"^(\"+I+\"|[*])\"),ATTR:new RegExp(\"^\"+W),PSEUDO:new RegExp(\"^\"+$),CHILD:new RegExp(\"^:(only|first|last|nth|nth-last)-(child|of-type)(?:\\\\(\"+M+\"*(even|odd|(([+-]|)(\\\\d*)n|)\"+M+\"*(?:([+-]|)\"+M+\"*(\\\\d+)|))\"+M+\"*\\\\)|)\",\"i\"),bool:new RegExp(\"^(?:\"+R+\")$\",\"i\"),needsContext:new RegExp(\"^\"+M+\"*[>+~]|:(even|odd|eq|gt|lt|nth|first|last)(?:\\\\(\"+M+\"*((?:-\\\\d)?\\\\d*)\"+M+\"*\\\\)|)(?=[^-]|$)\",\"i\")},Y=/HTML$/i,Q=/^(?:input|select|textarea|button)$/i,J=/^h\\d$/i,K=/^[^{]+\\{\\s*\\[native \\w/,Z=/^(?:#([\\w-]+)|(\\w+)|\\.([\\w-]+))$/,ee=/[+~]/,te=new RegExp(\"\\\\\\\\([\\\\da-f]{1,6}\"+M+\"?|(\"+M+\")|.)\",\"ig\"),ne=function(e,t,n){var r=\"0x\"+t-65536;return r!=r||n?t:0>r?String.fromCharCode(r+65536):String.fromCharCode(r>>10|55296,1023&r|56320)},re=/([\\0-\\x1f\\x7f]|^-?\\d)|^-$|[^\\0-\\x1f\\x7f-\\uFFFF\\w-]/g,ie=function(e,t){return t?\"\\x00\"===e?\"\\ufffd\":e.slice(0,-1)+\"\\\\\"+e.charCodeAt(e.length-1).toString(16)+\" \":\"\\\\\"+e},oe=function(){T()},ae=be(function(e){return!0===e.disabled&&\"fieldset\"===e.nodeName.toLowerCase()},{dir:\"parentNode\",next:\"legend\"});try{H.apply(t=O.call(m.childNodes),m.childNodes),t[m.childNodes.length].nodeType}catch(e){H={apply:t.length?function(e,t){L.apply(e,O.call(t))}:function(e,t){for(var n=e.length,r=0;e[n++]=t[r++];);e.length=n-1}}}for(e in d=se.support={},i=se.isXML=function(e){var t=e.namespaceURI,n=(e.ownerDocument||e).documentElement;return!Y.test(t||n&&n.nodeName||\"HTML\")},T=se.setDocument=function(e){var t,n,r=e?e.ownerDocument||e:m;return r!==C&&9===r.nodeType&&r.documentElement&&(a=(C=r).documentElement,E=!i(C),m!==C&&(n=C.defaultView)&&n.top!==n&&(n.addEventListener?n.addEventListener(\"unload\",oe,!1):n.attachEvent&&n.attachEvent(\"onunload\",oe)),d.attributes=ce(function(e){return e.className=\"i\",!e.getAttribute(\"className\")}),d.getElementsByTagName=ce(function(e){return e.appendChild(C.createComment(\"\")),!e.getElementsByTagName(\"*\").length}),d.getElementsByClassName=K.test(C.getElementsByClassName),d.getById=ce(function(e){return a.appendChild(e).id=k,!C.getElementsByName||!C.getElementsByName(k).length}),d.getById?(b.filter.ID=function(e){var t=e.replace(te,ne);return function(e){return e.getAttribute(\"id\")===t}},b.find.ID=function(e,t){if(\"undefined\"!=typeof t.getElementById&&E){var n=t.getElementById(e);return n?[n]:[]}}):(b.filter.ID=function(e){var n=e.replace(te,ne);return function(e){var t=\"undefined\"!=typeof e.getAttributeNode&&e.getAttributeNode(\"id\");return t&&t.value===n}},b.find.ID=function(e,t){if(\"undefined\"!=typeof t.getElementById&&E){var n,r,i,o=t.getElementById(e);if(o){if((n=o.getAttributeNode(\"id\"))&&n.value===e)return[o];for(i=t.getElementsByName(e),r=0;o=i[r++];)if((n=o.getAttributeNode(\"id\"))&&n.value===e)return[o]}return[]}}),b.find.TAG=d.getElementsByTagName?function(e,t){return\"undefined\"!=typeof t.getElementsByTagName?t.getElementsByTagName(e):d.qsa?t.querySelectorAll(e):void 0}:function(e,t){var n,r=[],i=0,o=t.getElementsByTagName(e);if(\"*\"===e){for(;n=o[i++];)1===n.nodeType&&r.push(n);return r}return o},b.find.CLASS=d.getElementsByClassName&&function(e,t){return\"undefined\"!=typeof t.getElementsByClassName&&E?t.getElementsByClassName(e):void 0},s=[],v=[],(d.qsa=K.test(C.querySelectorAll))&&(ce(function(e){a.appendChild(e).innerHTML=\"<a id='\"+k+\"'></a><select id='\"+k+\"-\\r\\\\' msallowcapture=''><option selected=''></option></select>\",e.querySelectorAll(\"[msallowcapture^='']\").length&&v.push(\"[*^$]=\"+M+\"*(?:''|\\\"\\\")\"),e.querySelectorAll(\"[selected]\").length||v.push(\"\\\\[\"+M+\"*(?:value|\"+R+\")\"),e.querySelectorAll(\"[id~=\"+k+\"-]\").length||v.push(\"~=\"),e.querySelectorAll(\":checked\").length||v.push(\":checked\"),e.querySelectorAll(\"a#\"+k+\"+*\").length||v.push(\".#.+[+~]\")}),ce(function(e){e.innerHTML=\"<a href='' disabled='disabled'></a><select disabled='disabled'><option/></select>\";var t=C.createElement(\"input\");t.setAttribute(\"type\",\"hidden\"),e.appendChild(t).setAttribute(\"name\",\"D\"),e.querySelectorAll(\"[name=d]\").length&&v.push(\"name\"+M+\"*[*^$|!~]?=\"),2!==e.querySelectorAll(\":enabled\").length&&v.push(\":enabled\",\":disabled\"),a.appendChild(e).disabled=!0,2!==e.querySelectorAll(\":disabled\").length&&v.push(\":enabled\",\":disabled\"),e.querySelectorAll(\"*,:x\"),v.push(\",.*:\")})),(d.matchesSelector=K.test(c=a.matches||a.webkitMatchesSelector||a.mozMatchesSelector||a.oMatchesSelector||a.msMatchesSelector))&&ce(function(e){d.disconnectedMatch=c.call(e,\"*\"),c.call(e,\"[s!='']:x\"),s.push(\"!=\",$)}),v=v.length&&new RegExp(v.join(\"|\")),s=s.length&&new RegExp(s.join(\"|\")),t=K.test(a.compareDocumentPosition),y=t||K.test(a.contains)?function(e,t){var n=9===e.nodeType?e.documentElement:e,r=t&&t.parentNode;return e===r||!(!r||1!==r.nodeType||!(n.contains?n.contains(r):e.compareDocumentPosition&&16&e.compareDocumentPosition(r)))}:function(e,t){if(t)for(;t=t.parentNode;)if(t===e)return!0;return!1},D=t?function(e,t){if(e===t)return l=!0,0;var n=!e.compareDocumentPosition-!t.compareDocumentPosition;return n||(1&(n=(e.ownerDocument||e)===(t.ownerDocument||t)?e.compareDocumentPosition(t):1)||!d.sortDetached&&t.compareDocumentPosition(e)===n?e===C||e.ownerDocument===m&&y(m,e)?-1:t===C||t.ownerDocument===m&&y(m,t)?1:u?P(u,e)-P(u,t):0:4&n?-1:1)}:function(e,t){if(e===t)return l=!0,0;var n,r=0,i=e.parentNode,o=t.parentNode,a=[e],s=[t];if(!i||!o)return e===C?-1:t===C?1:i?-1:o?1:u?P(u,e)-P(u,t):0;if(i===o)return pe(e,t);for(n=e;n=n.parentNode;)a.unshift(n);for(n=t;n=n.parentNode;)s.unshift(n);for(;a[r]===s[r];)r++;return r?pe(a[r],s[r]):a[r]===m?-1:s[r]===m?1:0}),C},se.matches=function(e,t){return se(e,null,null,t)},se.matchesSelector=function(e,t){if((e.ownerDocument||e)!==C&&T(e),d.matchesSelector&&E&&!A[t+\" \"]&&(!s||!s.test(t))&&(!v||!v.test(t)))try{var n=c.call(e,t);if(n||d.disconnectedMatch||e.document&&11!==e.document.nodeType)return n}catch(e){A(t,!0)}return 0<se(t,C,null,[e]).length},se.contains=function(e,t){return(e.ownerDocument||e)!==C&&T(e),y(e,t)},se.attr=function(e,t){(e.ownerDocument||e)!==C&&T(e);var n=b.attrHandle[t.toLowerCase()],r=n&&j.call(b.attrHandle,t.toLowerCase())?n(e,t,!E):void 0;return void 0!==r?r:d.attributes||!E?e.getAttribute(t):(r=e.getAttributeNode(t))&&r.specified?r.value:null},se.escape=function(e){return(e+\"\").replace(re,ie)},se.error=function(e){throw new Error(\"Syntax error, unrecognized expression: \"+e)},se.uniqueSort=function(e){var t,n=[],r=0,i=0;if(l=!d.detectDuplicates,u=!d.sortStable&&e.slice(0),e.sort(D),l){for(;t=e[i++];)t===e[i]&&(r=n.push(i));for(;r--;)e.splice(n[r],1)}return u=null,e},o=se.getText=function(e){var t,n=\"\",r=0,i=e.nodeType;if(i){if(1===i||9===i||11===i){if(\"string\"==typeof e.textContent)return e.textContent;for(e=e.firstChild;e;e=e.nextSibling)n+=o(e)}else if(3===i||4===i)return e.nodeValue}else for(;t=e[r++];)n+=o(t);return n},(b=se.selectors={cacheLength:50,createPseudo:le,match:G,attrHandle:{},find:{},relative:{\">\":{dir:\"parentNode\",first:!0},\" \":{dir:\"parentNode\"},\"+\":{dir:\"previousSibling\",first:!0},\"~\":{dir:\"previousSibling\"}},preFilter:{ATTR:function(e){return e[1]=e[1].replace(te,ne),e[3]=(e[3]||e[4]||e[5]||\"\").replace(te,ne),\"~=\"===e[2]&&(e[3]=\" \"+e[3]+\" \"),e.slice(0,4)},CHILD:function(e){return e[1]=e[1].toLowerCase(),\"nth\"===e[1].slice(0,3)?(e[3]||se.error(e[0]),e[4]=+(e[4]?e[5]+(e[6]||1):2*(\"even\"===e[3]||\"odd\"===e[3])),e[5]=+(e[7]+e[8]||\"odd\"===e[3])):e[3]&&se.error(e[0]),e},PSEUDO:function(e){var t,n=!e[6]&&e[2];return G.CHILD.test(e[0])?null:(e[3]?e[2]=e[4]||e[5]||\"\":n&&X.test(n)&&(t=h(n,!0))&&(t=n.indexOf(\")\",n.length-t)-n.length)&&(e[0]=e[0].slice(0,t),e[2]=n.slice(0,t)),e.slice(0,3))}},filter:{TAG:function(e){var t=e.replace(te,ne).toLowerCase();return\"*\"===e?function(){return!0}:function(e){return e.nodeName&&e.nodeName.toLowerCase()===t}},CLASS:function(e){var t=p[e+\" \"];return t||(t=new RegExp(\"(^|\"+M+\")\"+e+\"(\"+M+\"|$)\"))&&p(e,function(e){return t.test(\"string\"==typeof e.className&&e.className||\"undefined\"!=typeof e.getAttribute&&e.getAttribute(\"class\")||\"\")})},ATTR:function(n,r,i){return function(e){var t=se.attr(e,n);return null==t?\"!=\"===r:!r||(t+=\"\",\"=\"===r?t===i:\"!=\"===r?t!==i:\"^=\"===r?i&&0===t.indexOf(i):\"*=\"===r?i&&-1<t.indexOf(i):\"$=\"===r?i&&t.slice(-i.length)===i:\"~=\"===r?-1<(\" \"+t.replace(F,\" \")+\" \").indexOf(i):\"|=\"===r&&(t===i||t.slice(0,i.length+1)===i+\"-\"))}},CHILD:function(h,e,t,g,v){var y=\"nth\"!==h.slice(0,3),m=\"last\"!==h.slice(-4),x=\"of-type\"===e;return 1===g&&0===v?function(e){return!!e.parentNode}:function(e,t,n){var r,i,o,a,s,u,l=y!==m?\"nextSibling\":\"previousSibling\",c=e.parentNode,f=x&&e.nodeName.toLowerCase(),p=!n&&!x,d=!1;if(c){if(y){for(;l;){for(a=e;a=a[l];)if(x?a.nodeName.toLowerCase()===f:1===a.nodeType)return!1;u=l=\"only\"===h&&!u&&\"nextSibling\"}return!0}if(u=[m?c.firstChild:c.lastChild],m&&p){for(d=(s=(r=(i=(o=(a=c)[k]||(a[k]={}))[a.uniqueID]||(o[a.uniqueID]={}))[h]||[])[0]===S&&r[1])&&r[2],a=s&&c.childNodes[s];a=++s&&a&&a[l]||(d=s=0)||u.pop();)if(1===a.nodeType&&++d&&a===e){i[h]=[S,s,d];break}}else if(p&&(d=s=(r=(i=(o=(a=e)[k]||(a[k]={}))[a.uniqueID]||(o[a.uniqueID]={}))[h]||[])[0]===S&&r[1]),!1===d)for(;(a=++s&&a&&a[l]||(d=s=0)||u.pop())&&((x?a.nodeName.toLowerCase()!==f:1!==a.nodeType)||!++d||(p&&((i=(o=a[k]||(a[k]={}))[a.uniqueID]||(o[a.uniqueID]={}))[h]=[S,d]),a!==e)););return(d-=v)===g||d%g==0&&d/g>=0}}},PSEUDO:function(e,o){var t,a=b.pseudos[e]||b.setFilters[e.toLowerCase()]||se.error(\"unsupported pseudo: \"+e);return a[k]?a(o):1<a.length?(t=[e,e,\"\",o],b.setFilters.hasOwnProperty(e.toLowerCase())?le(function(e,t){for(var n,r=a(e,o),i=r.length;i--;)e[n=P(e,r[i])]=!(t[n]=r[i])}):function(e){return a(e,0,t)}):a}},pseudos:{not:le(function(e){var r=[],i=[],s=f(e.replace(B,\"$1\"));return s[k]?le(function(e,t,n,r){for(var i,o=s(e,null,r,[]),a=e.length;a--;)(i=o[a])&&(e[a]=!(t[a]=i))}):function(e,t,n){return r[0]=e,s(r,null,n,i),r[0]=null,!i.pop()}}),has:le(function(t){return function(e){return 0<se(t,e).length}}),contains:le(function(t){return t=t.replace(te,ne),function(e){return-1<(e.textContent||o(e)).indexOf(t)}}),lang:le(function(n){return V.test(n||\"\")||se.error(\"unsupported lang: \"+n),n=n.replace(te,ne).toLowerCase(),function(e){var t;do if(t=E?e.lang:e.getAttribute(\"xml:lang\")||e.getAttribute(\"lang\"))return(t=t.toLowerCase())===n||0===t.indexOf(n+\"-\");while((e=e.parentNode)&&1===e.nodeType);return!1}}),target:function(e){var t=n.location&&n.location.hash;return t&&t.slice(1)===e.id},root:function(e){return e===a},focus:function(e){return e===C.activeElement&&(!C.hasFocus||C.hasFocus())&&!!(e.type||e.href||~e.tabIndex)},enabled:ge(!1),disabled:ge(!0),checked:function(e){var t=e.nodeName.toLowerCase();return\"input\"===t&&!!e.checked||\"option\"===t&&!!e.selected},selected:function(e){return e.parentNode&&e.parentNode.selectedIndex,!0===e.selected},empty:function(e){for(e=e.firstChild;e;e=e.nextSibling)if(e.nodeType<6)return!1;return!0},parent:function(e){return!b.pseudos.empty(e)},header:function(e){return J.test(e.nodeName)},input:function(e){return Q.test(e.nodeName)},button:function(e){var t=e.nodeName.toLowerCase();return\"input\"===t&&\"button\"===e.type||\"button\"===t},text:function(e){var t;return\"input\"===e.nodeName.toLowerCase()&&\"text\"===e.type&&(null==(t=e.getAttribute(\"type\"))||\"text\"===t.toLowerCase())},first:ve(function(){return[0]}),last:ve(function(e,t){return[t-1]}),eq:ve(function(e,t,n){return[0>n?n+t:n]}),even:ve(function(e,t){for(var n=0;t>n;n+=2)e.push(n);return e}),odd:ve(function(e,t){for(var n=1;t>n;n+=2)e.push(n);return e}),lt:ve(function(e,t,n){for(var r=0>n?n+t:n>t?t:n;0<=--r;)e.push(r);return e}),gt:ve(function(e,t,n){for(var r=0>n?n+t:n;++r<t;)e.push(r);return e})}}).pseudos.nth=b.pseudos.eq,{radio:!0,checkbox:!0,file:!0,password:!0,image:!0})b.pseudos[e]=de(e);for(e in{submit:!0,reset:!0})b.pseudos[e]=he(e);return me.prototype=b.filters=b.pseudos,b.setFilters=new me,h=se.tokenize=function(e,t){var n,r,i,o,a,s,u,l=x[e+\" \"];if(l)return t?0:l.slice(0);for(a=e,s=[],u=b.preFilter;a;){for(o in n&&!(r=_.exec(a))||(r&&(a=a.slice(r[0].length)||a),s.push(i=[])),n=!1,(r=z.exec(a))&&(n=r.shift(),i.push({value:n,type:r[0].replace(B,\" \")}),a=a.slice(n.length)),b.filter)!(r=G[o].exec(a))||u[o]&&!(r=u[o](r))||(n=r.shift(),i.push({value:n,type:o,matches:r}),a=a.slice(n.length));if(!n)break}return t?a.length:a?se.error(e):x(e,s).slice(0)},f=se.compile=function(e,t){var n,v,y,m,x,r,i=[],o=[],a=N[e+\" \"];if(!a){for(t||(t=h(e)),n=t.length;n--;)(a=Ee(t[n]))[k]?i.push(a):o.push(a);(a=N(e,(v=o,m=0<(y=i).length,x=0<v.length,r=function(e,t,n,r,i){var o,a,s,u=0,l=\"0\",c=e&&[],f=[],p=w,d=e||x&&b.find.TAG(\"*\",i),h=S+=null==p?1:Math.random()||.1,g=d.length;for(i&&(w=t===C||t||i);l!==g&&null!=(o=d[l]);l++){if(x&&o){for(a=0,t||o.ownerDocument===C||(T(o),n=!E);s=v[a++];)if(s(o,t||C,n)){r.push(o);break}i&&(S=h)}m&&((o=!s&&o)&&u--,e&&c.push(o))}if(u+=l,m&&l!==u){for(a=0;s=y[a++];)s(c,f,t,n);if(e){if(u>0)for(;l--;)c[l]||f[l]||(f[l]=q.call(r));f=Te(f)}H.apply(r,f),i&&!e&&0<f.length&&1<u+y.length&&se.uniqueSort(r)}return i&&(S=h,\nw=p),c},m?le(r):r))).selector=e}return a},g=se.select=function(e,t,n,r){var i,o,a,s,u,l=\"function\"==typeof e&&e,c=!r&&h(e=l.selector||e);if(n=n||[],1===c.length){if(2<(o=c[0]=c[0].slice(0)).length&&\"ID\"===(a=o[0]).type&&9===t.nodeType&&E&&b.relative[o[1].type]){if(!(t=(b.find.ID(a.matches[0].replace(te,ne),t)||[])[0]))return n;l&&(t=t.parentNode),e=e.slice(o.shift().value.length)}for(i=G.needsContext.test(e)?0:o.length;i--&&(a=o[i],!b.relative[s=a.type]);)if((u=b.find[s])&&(r=u(a.matches[0].replace(te,ne),ee.test(o[0].type)&&ye(t.parentNode)||t))){if(o.splice(i,1),!(e=r.length&&xe(o)))return H.apply(n,r),n;break}}return(l||f(e,c))(r,t,!E,n,!t||ee.test(e)&&ye(t.parentNode)||t),n},d.sortStable=k.split(\"\").sort(D).join(\"\")===k,d.detectDuplicates=!!l,T(),d.sortDetached=ce(function(e){return 1&e.compareDocumentPosition(C.createElement(\"fieldset\"))}),ce(function(e){return e.innerHTML=\"<a href='#'></a>\",\"#\"===e.firstChild.getAttribute(\"href\")})||fe(\"type|href|height|width\",function(e,t,n){return n?void 0:e.getAttribute(t,\"type\"===t.toLowerCase()?1:2)}),d.attributes&&ce(function(e){return e.innerHTML=\"<input/>\",e.firstChild.setAttribute(\"value\",\"\"),\"\"===e.firstChild.getAttribute(\"value\")})||fe(\"value\",function(e,t,n){return n||\"input\"!==e.nodeName.toLowerCase()?void 0:e.defaultValue}),ce(function(e){return null==e.getAttribute(\"disabled\")})||fe(R,function(e,t,n){var r;return n?void 0:!0===e[t]?t.toLowerCase():(r=e.getAttributeNode(t))&&r.specified?r.value:null}),se}(C);k.find=h,k.expr=h.selectors,k.expr[\":\"]=k.expr.pseudos,k.uniqueSort=k.unique=h.uniqueSort,k.text=h.getText,k.isXMLDoc=h.isXML,k.contains=h.contains,k.escapeSelector=h.escape;var T=function(e,t,n){for(var r=[],i=void 0!==n;(e=e[t])&&9!==e.nodeType;)if(1===e.nodeType){if(i&&k(e).is(n))break;r.push(e)}return r},S=function(e,t){for(var n=[];e;e=e.nextSibling)1===e.nodeType&&e!==t&&n.push(e);return n},N=k.expr.match.needsContext,D=/^<([a-z][^\\/\\0>:\\x20\\t\\r\\n\\f]*)[\\x20\\t\\r\\n\\f]*\\/?>(?:<\\/\\1>|)$/i;k.filter=function(e,t,n){var r=t[0];return n&&(e=\":not(\"+e+\")\"),1===t.length&&1===r.nodeType?k.find.matchesSelector(r,e)?[r]:[]:k.find.matches(e,k.grep(t,function(e){return 1===e.nodeType}))},k.fn.extend({find:function(e){var t,n,r=this.length,i=this;if(\"string\"!=typeof e)return this.pushStack(k(e).filter(function(){for(t=0;r>t;t++)if(k.contains(i[t],this))return!0}));for(n=this.pushStack([]),t=0;r>t;t++)k.find(e,i[t],n);return r>1?k.uniqueSort(n):n},filter:function(e){return this.pushStack(j(this,e||[],!1))},not:function(e){return this.pushStack(j(this,e||[],!0))},is:function(e){return!!j(this,\"string\"==typeof e&&N.test(e)?k(e):e||[],!1).length}});var q,L=/^(?:\\s*(<[\\w\\W]+>)[^>]*|#([\\w-]+))$/;(k.fn.init=function(e,t,n){var r,i;if(!e)return this;if(n=n||q,\"string\"==typeof e){if(!(r=\"<\"===e[0]&&\">\"===e[e.length-1]&&3<=e.length?[null,e,null]:L.exec(e))||!r[1]&&t)return!t||t.jquery?(t||n).find(e):this.constructor(t).find(e);if(r[1]){if(t=t instanceof k?t[0]:t,k.merge(this,k.parseHTML(r[1],t&&t.nodeType?t.ownerDocument||t:E,!0)),D.test(r[1])&&k.isPlainObject(t))for(r in t)m(this[r])?this[r](t[r]):this.attr(r,t[r]);return this}return(i=E.getElementById(r[2]))&&(this[0]=i,this.length=1),this}return e.nodeType?(this[0]=e,this.length=1,this):m(e)?void 0!==n.ready?n.ready(e):e(k):k.makeArray(e,this)}).prototype=k.fn,q=k(E);var H=/^(?:parents|prev(?:Until|All))/,O={children:!0,contents:!0,next:!0,prev:!0};k.fn.extend({has:function(e){var t=k(e,this),n=t.length;return this.filter(function(){for(var e=0;n>e;e++)if(k.contains(this,t[e]))return!0})},closest:function(e,t){var n,r=0,i=this.length,o=[],a=\"string\"!=typeof e&&k(e);if(!N.test(e))for(;i>r;r++)for(n=this[r];n&&n!==t;n=n.parentNode)if(n.nodeType<11&&(a?-1<a.index(n):1===n.nodeType&&k.find.matchesSelector(n,e))){o.push(n);break}return this.pushStack(1<o.length?k.uniqueSort(o):o)},index:function(e){return e?\"string\"==typeof e?i.call(k(e),this[0]):i.call(this,e.jquery?e[0]:e):this[0]&&this[0].parentNode?this.first().prevAll().length:-1},add:function(e,t){return this.pushStack(k.uniqueSort(k.merge(this.get(),k(e,t))))},addBack:function(e){return this.add(null==e?this.prevObject:this.prevObject.filter(e))}}),k.each({parent:function(e){var t=e.parentNode;return t&&11!==t.nodeType?t:null},parents:function(e){return T(e,\"parentNode\")},parentsUntil:function(e,t,n){return T(e,\"parentNode\",n)},next:function(e){return P(e,\"nextSibling\")},prev:function(e){return P(e,\"previousSibling\")},nextAll:function(e){return T(e,\"nextSibling\")},prevAll:function(e){return T(e,\"previousSibling\")},nextUntil:function(e,t,n){return T(e,\"nextSibling\",n)},prevUntil:function(e,t,n){return T(e,\"previousSibling\",n)},siblings:function(e){return S((e.parentNode||{}).firstChild,e)},children:function(e){return S(e.firstChild)},contents:function(e){return\"undefined\"!=typeof e.contentDocument?e.contentDocument:(A(e,\"template\")&&(e=e.content||e),k.merge([],e.childNodes))}},function(r,i){k.fn[r]=function(e,t){var n=k.map(this,i,e);return\"Until\"!==r.slice(-5)&&(t=e),t&&\"string\"==typeof t&&(n=k.filter(t,n)),1<this.length&&(O[r]||k.uniqueSort(n),H.test(r)&&n.reverse()),this.pushStack(n)}});var R=/[^\\x20\\t\\r\\n\\f]+/g;k.Callbacks=function(r){var e,n;r=\"string\"==typeof r?(e=r,n={},k.each(e.match(R)||[],function(e,t){n[t]=!0}),n):k.extend({},r);var i,t,o,a,s=[],u=[],l=-1,c=function(){for(a=a||r.once,o=i=!0;u.length;l=-1)for(t=u.shift();++l<s.length;)!1===s[l].apply(t[0],t[1])&&r.stopOnFalse&&(l=s.length,t=!1);r.memory||(t=!1),i=!1,a&&(s=t?[]:\"\")},f={add:function(){return s&&(t&&!i&&(l=s.length-1,u.push(t)),function n(e){k.each(e,function(e,t){m(t)?r.unique&&f.has(t)||s.push(t):t&&t.length&&\"string\"!==w(t)&&n(t)})}(arguments),t&&!i&&c()),this},remove:function(){return k.each(arguments,function(e,t){for(var n;-1<(n=k.inArray(t,s,n));)s.splice(n,1),l>=n&&l--}),this},has:function(e){return e?-1<k.inArray(e,s):0<s.length},empty:function(){return s&&(s=[]),this},disable:function(){return a=u=[],s=t=\"\",this},disabled:function(){return!s},lock:function(){return a=u=[],t||i||(s=t=\"\"),this},locked:function(){return!!a},fireWith:function(e,t){return a||(t=[e,(t=t||[]).slice?t.slice():t],u.push(t),i||c()),this},fire:function(){return f.fireWith(this,arguments),this},fired:function(){return!!o}};return f},k.extend({Deferred:function(e){var o=[[\"notify\",\"progress\",k.Callbacks(\"memory\"),k.Callbacks(\"memory\"),2],[\"resolve\",\"done\",k.Callbacks(\"once memory\"),k.Callbacks(\"once memory\"),0,\"resolved\"],[\"reject\",\"fail\",k.Callbacks(\"once memory\"),k.Callbacks(\"once memory\"),1,\"rejected\"]],i=\"pending\",a={state:function(){return i},always:function(){return s.done(arguments).fail(arguments),this},\"catch\":function(e){return a.then(null,e)},pipe:function(){var i=arguments;return k.Deferred(function(r){k.each(o,function(e,t){var n=m(i[t[4]])&&i[t[4]];s[t[1]](function(){var e=n&&n.apply(this,arguments);e&&m(e.promise)?e.promise().progress(r.notify).done(r.resolve).fail(r.reject):r[t[0]+\"With\"](this,n?[e]:arguments)})}),i=null}).promise()},then:function(t,n,r){function l(i,o,a,s){return function(){var n=this,r=arguments,e=function(){var e,t;if(!(u>i)){if((e=a.apply(n,r))===o.promise())throw new TypeError(\"Thenable self-resolution\");t=e&&(\"object\"==typeof e||\"function\"==typeof e)&&e.then,m(t)?s?t.call(e,l(u,o,M,s),l(u,o,I,s)):(u++,t.call(e,l(u,o,M,s),l(u,o,I,s),l(u,o,M,o.notifyWith))):(a!==M&&(n=void 0,r=[e]),(s||o.resolveWith)(n,r))}},t=s?e:function(){try{e()}catch(e){k.Deferred.exceptionHook&&k.Deferred.exceptionHook(e,t.stackTrace),i+1>=u&&(a!==I&&(n=void 0,r=[e]),o.rejectWith(n,r))}};i?t():(k.Deferred.getStackHook&&(t.stackTrace=k.Deferred.getStackHook()),C.setTimeout(t))}}var u=0;return k.Deferred(function(e){o[0][3].add(l(0,e,m(r)?r:M,e.notifyWith)),o[1][3].add(l(0,e,m(t)?t:M)),o[2][3].add(l(0,e,m(n)?n:I))}).promise()},promise:function(e){return null!=e?k.extend(e,a):a}},s={};return k.each(o,function(e,t){var n=t[2],r=t[5];a[t[1]]=n.add,r&&n.add(function(){i=r},o[3-e][2].disable,o[3-e][3].disable,o[0][2].lock,o[0][3].lock),n.add(t[3].fire),s[t[0]]=function(){return s[t[0]+\"With\"](this===s?void 0:this,arguments),this},s[t[0]+\"With\"]=n.fireWith}),a.promise(s),e&&e.call(s,s),s},when:function(e){var n=arguments.length,t=n,r=Array(t),i=s.call(arguments),o=k.Deferred(),a=function(t){return function(e){r[t]=this,i[t]=1<arguments.length?s.call(arguments):e,--n||o.resolveWith(r,i)}};if(1>=n&&(W(e,o.done(a(t)).resolve,o.reject,!n),\"pending\"===o.state()||m(i[t]&&i[t].then)))return o.then();for(;t--;)W(i[t],a(t),o.reject);return o.promise()}});var $=/^(Eval|Internal|Range|Reference|Syntax|Type|URI)Error$/;k.Deferred.exceptionHook=function(e,t){C.console&&C.console.warn&&e&&$.test(e.name)&&C.console.warn(\"jQuery.Deferred exception: \"+e.message,e.stack,t)},k.readyException=function(e){C.setTimeout(function(){throw e})};var F=k.Deferred();k.fn.ready=function(e){return F.then(e)[\"catch\"](function(e){k.readyException(e)}),this},k.extend({isReady:!1,readyWait:1,ready:function(e){(!0===e?--k.readyWait:k.isReady)||(k.isReady=!0)!==e&&0<--k.readyWait||F.resolveWith(E,[k])}}),k.ready.then=F.then,\"complete\"===E.readyState||\"loading\"!==E.readyState&&!E.documentElement.doScroll?C.setTimeout(k.ready):(E.addEventListener(\"DOMContentLoaded\",B),C.addEventListener(\"load\",B));var _=function(e,t,n,r,i,o,a){var s=0,u=e.length,l=null==n;if(\"object\"===w(n))for(s in i=!0,n)_(e,t,s,n[s],!0,o,a);else if(void 0!==r&&(i=!0,m(r)||(a=!0),l&&(a?(t.call(e,r),t=null):(l=t,t=function(e,t,n){return l.call(k(e),n)})),t))for(;u>s;s++)t(e[s],n,a?r:r.call(e[s],s,t(e[s],n)));return i?e:l?t.call(e):u?t(e[0],n):o},z=/^-ms-/,U=/-([a-z])/g,G=function(e){return 1===e.nodeType||9===e.nodeType||!+e.nodeType};Y.uid=1,Y.prototype={cache:function(e){var t=e[this.expando];return t||(t={},G(e)&&(e.nodeType?e[this.expando]=t:Object.defineProperty(e,this.expando,{value:t,configurable:!0}))),t},set:function(e,t,n){var r,i=this.cache(e);if(\"string\"==typeof t)i[V(t)]=n;else for(r in t)i[V(r)]=t[r];return i},get:function(e,t){return void 0===t?this.cache(e):e[this.expando]&&e[this.expando][V(t)]},access:function(e,t,n){return void 0===t||t&&\"string\"==typeof t&&void 0===n?this.get(e,t):(this.set(e,t,n),void 0!==n?n:t)},remove:function(e,t){var n,r=e[this.expando];if(void 0!==r){if(void 0!==t){n=(t=Array.isArray(t)?t.map(V):(t=V(t))in r?[t]:t.match(R)||[]).length;for(;n--;)delete r[t[n]]}(void 0===t||k.isEmptyObject(r))&&(e.nodeType?e[this.expando]=void 0:delete e[this.expando])}},hasData:function(e){var t=e[this.expando];return void 0!==t&&!k.isEmptyObject(t)}};var Q=new Y,J=new Y,K=/^(?:\\{[\\w\\W]*\\}|\\[[\\w\\W]*\\])$/,Z=/[A-Z]/g;k.extend({hasData:function(e){return J.hasData(e)||Q.hasData(e)},data:function(e,t,n){return J.access(e,t,n)},removeData:function(e,t){J.remove(e,t)},_data:function(e,t,n){return Q.access(e,t,n)},_removeData:function(e,t){Q.remove(e,t)}}),k.fn.extend({data:function(n,e){var t,r,i,o=this[0],a=o&&o.attributes;if(void 0===n){if(this.length&&(i=J.get(o),1===o.nodeType&&!Q.get(o,\"hasDataAttrs\"))){for(t=a.length;t--;)a[t]&&0===(r=a[t].name).indexOf(\"data-\")&&(r=V(r.slice(5)),ee(o,r,i[r]));Q.set(o,\"hasDataAttrs\",!0)}return i}return\"object\"==typeof n?this.each(function(){J.set(this,n)}):_(this,function(e){var t;return o&&void 0===e?void 0!==(t=J.get(o,n))?t:void 0!==(t=ee(o,n))?t:void 0:void this.each(function(){J.set(this,n,e)})},null,e,1<arguments.length,null,!0)},removeData:function(e){return this.each(function(){J.remove(this,e)})}}),k.extend({queue:function(e,t,n){var r;return e?(t=(t||\"fx\")+\"queue\",r=Q.get(e,t),n&&(!r||Array.isArray(n)?r=Q.access(e,t,k.makeArray(n)):r.push(n)),r||[]):void 0},dequeue:function(e,t){t=t||\"fx\";var n=k.queue(e,t),r=n.length,i=n.shift(),o=k._queueHooks(e,t);\"inprogress\"===i&&(i=n.shift(),r--),i&&(\"fx\"===t&&n.unshift(\"inprogress\"),delete o.stop,i.call(e,function(){k.dequeue(e,t)},o)),!r&&o&&o.empty.fire()},_queueHooks:function(e,t){var n=t+\"queueHooks\";return Q.get(e,n)||Q.access(e,n,{empty:k.Callbacks(\"once memory\").add(function(){Q.remove(e,[t+\"queue\",n])})})}}),k.fn.extend({queue:function(t,n){var e=2;return\"string\"!=typeof t&&(n=t,t=\"fx\",e--),arguments.length<e?k.queue(this[0],t):void 0===n?this:this.each(function(){var e=k.queue(this,t,n);k._queueHooks(this,t),\"fx\"===t&&\"inprogress\"!==e[0]&&k.dequeue(this,t)})},dequeue:function(e){return this.each(function(){k.dequeue(this,e)})},clearQueue:function(e){return this.queue(e||\"fx\",[])},promise:function(e,t){var n,r=1,i=k.Deferred(),o=this,a=this.length,s=function(){--r||i.resolveWith(o,[o])};for(\"string\"!=typeof e&&(t=e,e=void 0),e=e||\"fx\";a--;)(n=Q.get(o[a],e+\"queueHooks\"))&&n.empty&&(r++,n.empty.add(s));return s(),i.promise(t)}});var te=/[+-]?(?:\\d*\\.|)\\d+(?:[eE][+-]?\\d+|)/.source,ne=new RegExp(\"^(?:([+-])=|)(\"+te+\")([a-z%]*)$\",\"i\"),re=[\"Top\",\"Right\",\"Bottom\",\"Left\"],ie=E.documentElement,oe=function(e){return k.contains(e.ownerDocument,e)},ae={composed:!0};ie.getRootNode&&(oe=function(e){return k.contains(e.ownerDocument,e)||e.getRootNode(ae)===e.ownerDocument});var se=function(e,t){return\"none\"===(e=t||e).style.display||\"\"===e.style.display&&oe(e)&&\"none\"===k.css(e,\"display\")},ue=function(e,t,n,r){var i,o,a={};for(o in t)a[o]=e.style[o],e.style[o]=t[o];for(o in i=n.apply(e,r||[]),t)e.style[o]=a[o];return i},ce={};k.fn.extend({show:function(){return fe(this,!0)},hide:function(){return fe(this)},toggle:function(e){return\"boolean\"==typeof e?e?this.show():this.hide():this.each(function(){se(this)?k(this).show():k(this).hide()})}});var pe=/^(?:checkbox|radio)$/i,de=/<([a-z][^\\/\\0>\\x20\\t\\r\\n\\f]*)/i,he=/^$|^module$|\\/(?:java|ecma)script/i,ge={option:[1,\"<select multiple='multiple'>\",\"</select>\"],thead:[1,\"<table>\",\"</table>\"],col:[2,\"<table><colgroup>\",\"</colgroup></table>\"],tr:[2,\"<table><tbody>\",\"</tbody></table>\"],td:[3,\"<table><tbody><tr>\",\"</tr></tbody></table>\"],_default:[0,\"\",\"\"]};ge.optgroup=ge.option,ge.tbody=ge.tfoot=ge.colgroup=ge.caption=ge.thead,ge.th=ge.td;var me,xe,be=/<|&#?\\w+;/;me=E.createDocumentFragment().appendChild(E.createElement(\"div\")),(xe=E.createElement(\"input\")).setAttribute(\"type\",\"radio\"),xe.setAttribute(\"checked\",\"checked\"),xe.setAttribute(\"name\",\"t\"),me.appendChild(xe),y.checkClone=me.cloneNode(!0).cloneNode(!0).lastChild.checked,me.innerHTML=\"<textarea>x</textarea>\",y.noCloneChecked=!!me.cloneNode(!0).lastChild.defaultValue;var Te=/^key/,Ce=/^(?:mouse|pointer|contextmenu|drag|drop)|click/,Ee=/^([^.]*)(?:\\.(.+)|)/;k.event={global:{},add:function(t,e,n,r,i){var o,a,s,u,l,c,f,p,d,h,g,v=Q.get(t);if(v)for(n.handler&&(n=(o=n).handler,i=o.selector),i&&k.find.matchesSelector(ie,i),n.guid||(n.guid=k.guid++),(u=v.events)||(u=v.events={}),(a=v.handle)||(a=v.handle=function(e){return\"undefined\"!=typeof k&&k.event.triggered!==e.type?k.event.dispatch.apply(t,arguments):void 0}),l=(e=(e||\"\").match(R)||[\"\"]).length;l--;)d=g=(s=Ee.exec(e[l])||[])[1],h=(s[2]||\"\").split(\".\").sort(),d&&(f=k.event.special[d]||{},d=(i?f.delegateType:f.bindType)||d,f=k.event.special[d]||{},c=k.extend({type:d,origType:g,data:r,handler:n,guid:n.guid,selector:i,needsContext:i&&k.expr.match.needsContext.test(i),namespace:h.join(\".\")},o),(p=u[d])||((p=u[d]=[]).delegateCount=0,f.setup&&!1!==f.setup.call(t,r,h,a)||t.addEventListener&&t.addEventListener(d,a)),f.add&&(f.add.call(t,c),c.handler.guid||(c.handler.guid=n.guid)),i?p.splice(p.delegateCount++,0,c):p.push(c),k.event.global[d]=!0)},remove:function(e,t,n,r,i){var o,a,s,u,l,c,f,p,d,h,g,v=Q.hasData(e)&&Q.get(e);if(v&&(u=v.events)){for(l=(t=(t||\"\").match(R)||[\"\"]).length;l--;)if(d=g=(s=Ee.exec(t[l])||[])[1],h=(s[2]||\"\").split(\".\").sort(),d){for(f=k.event.special[d]||{},p=u[d=(r?f.delegateType:f.bindType)||d]||[],s=s[2]&&new RegExp(\"(^|\\\\.)\"+h.join(\"\\\\.(?:.*\\\\.|)\")+\"(\\\\.|$)\"),a=o=p.length;o--;)c=p[o],!i&&g!==c.origType||n&&n.guid!==c.guid||s&&!s.test(c.namespace)||r&&r!==c.selector&&(\"**\"!==r||!c.selector)||(p.splice(o,1),c.selector&&p.delegateCount--,f.remove&&f.remove.call(e,c));a&&!p.length&&(f.teardown&&!1!==f.teardown.call(e,h,v.handle)||k.removeEvent(e,d,v.handle),delete u[d])}else for(d in u)k.event.remove(e,d+t[l],n,r,!0);k.isEmptyObject(u)&&Q.remove(e,\"handle events\")}},dispatch:function(e){var t,n,r,i,o,a,s=k.event.fix(e),u=new Array(arguments.length),l=(Q.get(this,\"events\")||{})[s.type]||[],c=k.event.special[s.type]||{};for(u[0]=s,t=1;t<arguments.length;t++)u[t]=arguments[t];if(s.delegateTarget=this,!c.preDispatch||!1!==c.preDispatch.call(this,s)){for(a=k.event.handlers.call(this,s,l),t=0;(i=a[t++])&&!s.isPropagationStopped();)for(s.currentTarget=i.elem,n=0;(o=i.handlers[n++])&&!s.isImmediatePropagationStopped();)s.rnamespace&&!1!==o.namespace&&!s.rnamespace.test(o.namespace)||(s.handleObj=o,s.data=o.data,void 0!==(r=((k.event.special[o.origType]||{}).handle||o.handler).apply(i.elem,u))&&!1===(s.result=r)&&(s.preventDefault(),s.stopPropagation()));return c.postDispatch&&c.postDispatch.call(this,s),s.result}},handlers:function(e,t){var n,r,i,o,a,s=[],u=t.delegateCount,l=e.target;if(u&&l.nodeType&&!(\"click\"===e.type&&1<=e.button))for(;l!==this;l=l.parentNode||this)if(1===l.nodeType&&(\"click\"!==e.type||!0!==l.disabled)){for(o=[],a={},n=0;u>n;n++)void 0===a[i=(r=t[n]).selector+\" \"]&&(a[i]=r.needsContext?-1<k(i,this).index(l):k.find(i,this,null,[l]).length),a[i]&&o.push(r);o.length&&s.push({elem:l,handlers:o})}return l=this,u<t.length&&s.push({elem:l,handlers:t.slice(u)}),s},addProp:function(t,e){Object.defineProperty(k.Event.prototype,t,{enumerable:!0,configurable:!0,get:m(e)?function(){return this.originalEvent?e(this.originalEvent):void 0}:function(){return this.originalEvent?this.originalEvent[t]:void 0},set:function(e){Object.defineProperty(this,t,{enumerable:!0,configurable:!0,writable:!0,value:e})}})},fix:function(e){return e[k.expando]?e:new k.Event(e)},special:{load:{noBubble:!0},click:{setup:function(e){var t=this||e;return pe.test(t.type)&&t.click&&A(t,\"input\")&&De(t,\"click\",ke),!1},trigger:function(e){var t=this||e;return pe.test(t.type)&&t.click&&A(t,\"input\")&&De(t,\"click\"),!0},_default:function(e){var t=e.target;return pe.test(t.type)&&t.click&&A(t,\"input\")&&Q.get(t,\"click\")||A(t,\"a\")}},beforeunload:{postDispatch:function(e){void 0!==e.result&&e.originalEvent&&(e.originalEvent.returnValue=e.result)}}}},k.removeEvent=function(e,t,n){e.removeEventListener&&e.removeEventListener(t,n)},k.Event=function(e,t){return this instanceof k.Event?(e&&e.type?(this.originalEvent=e,this.type=e.type,this.isDefaultPrevented=e.defaultPrevented||void 0===e.defaultPrevented&&!1===e.returnValue?ke:Se,this.target=e.target&&3===e.target.nodeType?e.target.parentNode:e.target,this.currentTarget=e.currentTarget,this.relatedTarget=e.relatedTarget):this.type=e,t&&k.extend(this,t),this.timeStamp=e&&e.timeStamp||Date.now(),this[k.expando]=!0,void 0):new k.Event(e,t)},k.Event.prototype={constructor:k.Event,isDefaultPrevented:Se,isPropagationStopped:Se,isImmediatePropagationStopped:Se,isSimulated:!1,preventDefault:function(){var e=this.originalEvent;this.isDefaultPrevented=ke,e&&!this.isSimulated&&e.preventDefault()},stopPropagation:function(){var e=this.originalEvent;this.isPropagationStopped=ke,e&&!this.isSimulated&&e.stopPropagation()},stopImmediatePropagation:function(){var e=this.originalEvent;this.isImmediatePropagationStopped=ke,e&&!this.isSimulated&&e.stopImmediatePropagation(),this.stopPropagation()}},k.each({altKey:!0,bubbles:!0,cancelable:!0,changedTouches:!0,ctrlKey:!0,detail:!0,eventPhase:!0,metaKey:!0,pageX:!0,pageY:!0,shiftKey:!0,view:!0,\"char\":!0,code:!0,charCode:!0,key:!0,keyCode:!0,button:!0,buttons:!0,clientX:!0,clientY:!0,offsetX:!0,offsetY:!0,pointerId:!0,pointerType:!0,screenX:!0,screenY:!0,targetTouches:!0,toElement:!0,touches:!0,which:function(e){var t=e.button;return null==e.which&&Te.test(e.type)?null!=e.charCode?e.charCode:e.keyCode:!e.which&&void 0!==t&&Ce.test(e.type)?1&t?1:2&t?3:4&t?2:0:e.which}},k.event.addProp),k.each({focus:\"focusin\",blur:\"focusout\"},function(e,t){k.event.special[e]={setup:function(){return De(this,e,Ne),!1},trigger:function(){return De(this,e),!0},delegateType:t}}),k.each({mouseenter:\"mouseover\",mouseleave:\"mouseout\",pointerenter:\"pointerover\",pointerleave:\"pointerout\"},function(e,i){k.event.special[e]={delegateType:i,bindType:i,handle:function(e){var t,n=e.relatedTarget,r=e.handleObj;return n&&(n===this||k.contains(this,n))||(e.type=r.origType,t=r.handler.apply(this,arguments),e.type=i),t}}}),k.fn.extend({on:function(e,t,n,r){return Ae(this,e,t,n,r)},one:function(e,t,n,r){return Ae(this,e,t,n,r,1)},off:function(e,t,n){var r,i;if(e&&e.preventDefault&&e.handleObj)return r=e.handleObj,k(e.delegateTarget).off(r.namespace?r.origType+\".\"+r.namespace:r.origType,r.selector,r.handler),this;if(\"object\"==typeof e){for(i in e)this.off(i,t,e[i]);return this}return!1!==t&&\"function\"!=typeof t||(n=t,t=void 0),!1===n&&(n=Se),this.each(function(){k.event.remove(this,e,n,t)})}});var je=/<(?!area|br|col|embed|hr|img|input|link|meta|param)(([a-z][^\\/\\0>\\x20\\t\\r\\n\\f]*)[^>]*)\\/>/gi,qe=/<script|<style|<link/i,Le=/checked\\s*(?:[^=]|=\\s*.checked.)/i,He=/^\\s*<!(?:\\[CDATA\\[|--)|(?:\\]\\]|--)>\\s*$/g;k.extend({htmlPrefilter:function(e){return e.replace(je,\"<$1></$2>\")},clone:function(e,t,n){var r,i,o,a,s,u,l,c=e.cloneNode(!0),f=oe(e);if(!(y.noCloneChecked||1!==e.nodeType&&11!==e.nodeType||k.isXMLDoc(e)))for(a=ve(c),r=0,i=(o=ve(e)).length;i>r;r++)s=o[r],u=a[r],\"input\"===(l=u.nodeName.toLowerCase())&&pe.test(s.type)?u.checked=s.checked:\"input\"!==l&&\"textarea\"!==l||(u.defaultValue=s.defaultValue);if(t)if(n)for(o=o||ve(e),a=a||ve(c),r=0,i=o.length;i>r;r++)Me(o[r],a[r]);else Me(e,c);return 0<(a=ve(c,\"script\")).length&&ye(a,!f&&ve(e,\"script\")),c},cleanData:function(e){for(var t,n,r,i=k.event.special,o=0;void 0!==(n=e[o]);o++)if(G(n)){if(t=n[Q.expando]){if(t.events)for(r in t.events)i[r]?k.event.remove(n,r):k.removeEvent(n,r,t.handle);n[Q.expando]=void 0}n[J.expando]&&(n[J.expando]=void 0)}}}),k.fn.extend({detach:function(e){return We(this,e,!0)},remove:function(e){return We(this,e)},text:function(e){return _(this,function(e){return void 0===e?k.text(this):this.empty().each(function(){1!==this.nodeType&&11!==this.nodeType&&9!==this.nodeType||(this.textContent=e)})},null,e,arguments.length)},append:function(){return Ie(this,arguments,function(e){1!==this.nodeType&&11!==this.nodeType&&9!==this.nodeType||Oe(this,e).appendChild(e)})},prepend:function(){return Ie(this,arguments,function(e){if(1===this.nodeType||11===this.nodeType||9===this.nodeType){var t=Oe(this,e);t.insertBefore(e,t.firstChild)}})},before:function(){return Ie(this,arguments,function(e){this.parentNode&&this.parentNode.insertBefore(e,this)})},after:function(){return Ie(this,arguments,function(e){this.parentNode&&this.parentNode.insertBefore(e,this.nextSibling)})},empty:function(){for(var e,t=0;null!=(e=this[t]);t++)1===e.nodeType&&(k.cleanData(ve(e,!1)),e.textContent=\"\");return this},clone:function(e,t){return e=null!=e&&e,t=null==t?e:t,this.map(function(){return k.clone(this,e,t)})},html:function(e){return _(this,function(e){var t=this[0]||{},n=0,r=this.length;if(void 0===e&&1===t.nodeType)return t.innerHTML;if(\"string\"==typeof e&&!qe.test(e)&&!ge[(de.exec(e)||[\"\",\"\"])[1].toLowerCase()]){e=k.htmlPrefilter(e);try{for(;r>n;n++)1===(t=this[n]||{}).nodeType&&(k.cleanData(ve(t,!1)),t.innerHTML=e);t=0}catch(e){}}t&&this.empty().append(e)},null,e,arguments.length)},replaceWith:function(){var n=[];return Ie(this,arguments,function(e){var t=this.parentNode;k.inArray(this,n)<0&&(k.cleanData(ve(this)),t&&t.replaceChild(e,this))},n)}}),k.each({appendTo:\"append\",prependTo:\"prepend\",insertBefore:\"before\",insertAfter:\"after\",replaceAll:\"replaceWith\"},function(e,a){k.fn[e]=function(e){for(var t,n=[],r=k(e),i=r.length-1,o=0;i>=o;o++)t=o===i?this:this.clone(!0),k(r[o])[a](t),u.apply(n,t.get());return this.pushStack(n)}});var $e=new RegExp(\"^(\"+te+\")(?!px)[a-z%]+$\",\"i\"),Fe=function(e){var t=e.ownerDocument.defaultView;return t&&t.opener||(t=C),t.getComputedStyle(e)},Be=new RegExp(re.join(\"|\"),\"i\");!function(){function e(){if(u){s.style.cssText=\"position:absolute;left:-11111px;width:60px;margin-top:1px;padding:0;border:0\",u.style.cssText=\"position:relative;display:block;box-sizing:border-box;overflow:scroll;margin:auto;border:1px;padding:1px;width:60%;top:1%\",ie.appendChild(s).appendChild(u);var e=C.getComputedStyle(u);n=\"1%\"!==e.top,a=12===t(e.marginLeft),u.style.right=\"60%\",o=36===t(e.right),r=36===t(e.width),u.style.position=\"absolute\",i=12===t(u.offsetWidth/3),ie.removeChild(s),u=null}}function t(e){return Math.round(parseFloat(e))}var n,r,i,o,a,s=E.createElement(\"div\"),u=E.createElement(\"div\");u.style&&(u.style.backgroundClip=\"content-box\",u.cloneNode(!0).style.backgroundClip=\"\",y.clearCloneStyle=\"content-box\"===u.style.backgroundClip,k.extend(y,{boxSizingReliable:function(){return e(),r},pixelBoxStyles:function(){return e(),o},pixelPosition:function(){return e(),n},reliableMarginLeft:function(){return e(),a},scrollboxSize:function(){return e(),i}}))}();var Ue=[\"Webkit\",\"Moz\",\"ms\"],Xe=E.createElement(\"div\").style,Ve={},Ye=/^(none|table(?!-c[ea]).+)/,Qe=/^--/,Je={position:\"absolute\",visibility:\"hidden\",display:\"block\"},Ke={letterSpacing:\"0\",fontWeight:\"400\"};k.extend({cssHooks:{opacity:{get:function(e,t){if(t){var n=_e(e,\"opacity\");return\"\"===n?\"1\":n}}}},cssNumber:{animationIterationCount:!0,columnCount:!0,fillOpacity:!0,flexGrow:!0,flexShrink:!0,fontWeight:!0,gridArea:!0,gridColumn:!0,gridColumnEnd:!0,gridColumnStart:!0,gridRow:!0,gridRowEnd:!0,gridRowStart:!0,lineHeight:!0,opacity:!0,order:!0,orphans:!0,widows:!0,zIndex:!0,zoom:!0},cssProps:{},style:function(e,t,n,r){if(e&&3!==e.nodeType&&8!==e.nodeType&&e.style){var i,o,a,s=V(t),u=Qe.test(t),l=e.style;if(u||(t=Ge(s)),a=k.cssHooks[t]||k.cssHooks[s],void 0===n)return a&&\"get\"in a&&void 0!==(i=a.get(e,!1,r))?i:l[t];\"string\"==(o=typeof n)&&(i=ne.exec(n))&&i[1]&&(n=le(e,t,i),o=\"number\"),null!=n&&n==n&&(\"number\"!==o||u||(n+=i&&i[3]||(k.cssNumber[s]?\"\":\"px\")),y.clearCloneStyle||\"\"!==n||0!==t.indexOf(\"background\")||(l[t]=\"inherit\"),a&&\"set\"in a&&void 0===(n=a.set(e,n,r))||(u?l.setProperty(t,n):l[t]=n))}},css:function(e,t,n,r){var i,o,a,s=V(t);return Qe.test(t)||(t=Ge(s)),(a=k.cssHooks[t]||k.cssHooks[s])&&\"get\"in a&&(i=a.get(e,!0,n)),void 0===i&&(i=_e(e,t,r)),\"normal\"===i&&t in Ke&&(i=Ke[t]),\"\"===n||n?(o=parseFloat(i),!0===n||isFinite(o)?o||0:i):i}}),k.each([\"height\",\"width\"],function(e,u){k.cssHooks[u]={get:function(e,t,n){return t?!Ye.test(k.css(e,\"display\"))||e.getClientRects().length&&e.getBoundingClientRect().width?tt(e,u,n):ue(e,Je,function(){return tt(e,u,n)}):void 0},set:function(e,t,n){var r,i=Fe(e),o=!y.scrollboxSize()&&\"absolute\"===i.position,a=(o||n)&&\"border-box\"===k.css(e,\"boxSizing\",!1,i),s=n?et(e,u,n,a,i):0;return a&&o&&(s-=Math.ceil(e[\"offset\"+u[0].toUpperCase()+u.slice(1)]-parseFloat(i[u])-et(e,u,\"border\",!1,i)-.5)),s&&(r=ne.exec(t))&&\"px\"!==(r[3]||\"px\")&&(e.style[u]=t,t=k.css(e,u)),Ze(0,t,s)}}}),k.cssHooks.marginLeft=ze(y.reliableMarginLeft,function(e,t){return t?(parseFloat(_e(e,\"marginLeft\"))||e.getBoundingClientRect().left-ue(e,{marginLeft:0},function(){return e.getBoundingClientRect().left}))+\"px\":void 0}),k.each({margin:\"\",padding:\"\",border:\"Width\"},function(i,o){k.cssHooks[i+o]={expand:function(e){for(var t=0,n={},r=\"string\"==typeof e?e.split(\" \"):[e];4>t;t++)n[i+re[t]+o]=r[t]||r[t-2]||r[0];return n}},\"margin\"!==i&&(k.cssHooks[i+o].set=Ze)}),k.fn.extend({css:function(e,t){return _(this,function(e,t,n){var r,i,o={},a=0;if(Array.isArray(t)){for(r=Fe(e),i=t.length;i>a;a++)o[t[a]]=k.css(e,t[a],!1,r);return o}return void 0!==n?k.style(e,t,n):k.css(e,t)},e,t,1<arguments.length)}}),((k.Tween=nt).prototype={constructor:nt,init:function(e,t,n,r,i,o){this.elem=e,this.prop=n,this.easing=i||k.easing._default,this.options=t,this.start=this.now=this.cur(),this.end=r,this.unit=o||(k.cssNumber[n]?\"\":\"px\")},cur:function(){var e=nt.propHooks[this.prop];return e&&e.get?e.get(this):nt.propHooks._default.get(this)},run:function(e){var t,n=nt.propHooks[this.prop];return this.options.duration?this.pos=t=k.easing[this.easing](e,this.options.duration*e,0,1,this.options.duration):this.pos=t=e,this.now=(this.end-this.start)*t+this.start,this.options.step&&this.options.step.call(this.elem,this.now,this),n&&n.set?n.set(this):nt.propHooks._default.set(this),this}}).init.prototype=nt.prototype,(nt.propHooks={_default:{get:function(e){var t;return 1!==e.elem.nodeType||null!=e.elem[e.prop]&&null==e.elem.style[e.prop]?e.elem[e.prop]:(t=k.css(e.elem,e.prop,\"\"))&&\"auto\"!==t?t:0},set:function(e){k.fx.step[e.prop]?k.fx.step[e.prop](e):1!==e.elem.nodeType||!k.cssHooks[e.prop]&&null==e.elem.style[Ge(e.prop)]?e.elem[e.prop]=e.now:k.style(e.elem,e.prop,e.now+e.unit)}}}).scrollTop=nt.propHooks.scrollLeft={set:function(e){e.elem.nodeType&&e.elem.parentNode&&(e.elem[e.prop]=e.now)}},k.easing={linear:function(e){return e},swing:function(e){return.5-Math.cos(e*Math.PI)/2},_default:\"swing\"},k.fx=nt.prototype.init,k.fx.step={};var rt,it,ot,at,st=/^(?:toggle|show|hide)$/,ut=/queueHooks$/;k.Animation=k.extend(dt,{tweeners:{\"*\":[function(e,t){var n=this.createTween(e,t);return le(n.elem,e,ne.exec(t),n),n}]},tweener:function(e,t){m(e)?(t=e,e=[\"*\"]):e=e.match(R);for(var n,r=0,i=e.length;i>r;r++)n=e[r],dt.tweeners[n]=dt.tweeners[n]||[],dt.tweeners[n].unshift(t)},prefilters:[function(e,t,n){var r,i,o,a,s,u,l,c,f=\"width\"in t||\"height\"in t,p=this,d={},h=e.style,g=e.nodeType&&se(e),v=Q.get(e,\"fxshow\");for(r in n.queue||(null==(a=k._queueHooks(e,\"fx\")).unqueued&&(a.unqueued=0,s=a.empty.fire,a.empty.fire=function(){a.unqueued||s()}),a.unqueued++,p.always(function(){p.always(function(){a.unqueued--,k.queue(e,\"fx\").length||a.empty.fire()})})),t)if(i=t[r],st.test(i)){if(delete t[r],o=o||\"toggle\"===i,i===(g?\"hide\":\"show\")){if(\"show\"!==i||!v||void 0===v[r])continue;g=!0}d[r]=v&&v[r]||k.style(e,r)}if((u=!k.isEmptyObject(t))||!k.isEmptyObject(d))for(r in f&&1===e.nodeType&&(n.overflow=[h.overflow,h.overflowX,h.overflowY],null==(l=v&&v.display)&&(l=Q.get(e,\"display\")),\"none\"===(c=k.css(e,\"display\"))&&(l?c=l:(fe([e],!0),l=e.style.display||l,c=k.css(e,\"display\"),fe([e]))),(\"inline\"===c||\"inline-block\"===c&&null!=l)&&\"none\"===k.css(e,\"float\")&&(u||(p.done(function(){h.display=l}),null==l&&(c=h.display,l=\"none\"===c?\"\":c)),h.display=\"inline-block\")),n.overflow&&(h.overflow=\"hidden\",p.always(function(){h.overflow=n.overflow[0],h.overflowX=n.overflow[1],h.overflowY=n.overflow[2]})),u=!1,d)u||(v?\"hidden\"in v&&(g=v.hidden):v=Q.access(e,\"fxshow\",{display:l}),o&&(v.hidden=!g),g&&fe([e],!0),p.done(function(){for(r in g||fe([e]),Q.remove(e,\"fxshow\"),d)k.style(e,r,d[r])})),u=pt(g?v[r]:0,r,p),r in v||(v[r]=u.start,g&&(u.end=u.start,u.start=0))}],prefilter:function(e,t){t?dt.prefilters.unshift(e):dt.prefilters.push(e)}}),k.speed=function(e,t,n){var r=e&&\"object\"==typeof e?k.extend({},e):{complete:n||!n&&t||m(e)&&e,duration:e,easing:n&&t||t&&!m(t)&&t};return k.fx.off?r.duration=0:\"number\"!=typeof r.duration&&(r.duration in k.fx.speeds?r.duration=k.fx.speeds[r.duration]:r.duration=k.fx.speeds._default),null!=r.queue&&!0!==r.queue||(r.queue=\"fx\"),r.old=r.complete,r.complete=function(){m(r.old)&&r.old.call(this),r.queue&&k.dequeue(this,r.queue)},r},k.fn.extend({fadeTo:function(e,t,n,r){return this.filter(se).css(\"opacity\",0).show().end().animate({opacity:t},e,n,r)},animate:function(t,e,n,r){var i=k.isEmptyObject(t),o=k.speed(e,n,r),a=function(){var e=dt(this,k.extend({},t),o);(i||Q.get(this,\"finish\"))&&e.stop(!0)};return a.finish=a,i||!1===o.queue?this.each(a):this.queue(o.queue,a)},stop:function(i,e,o){var a=function(e){var t=e.stop;delete e.stop,t(o)};return\"string\"!=typeof i&&(o=e,e=i,i=void 0),e&&!1!==i&&this.queue(i||\"fx\",[]),this.each(function(){var e=!0,t=null!=i&&i+\"queueHooks\",n=k.timers,r=Q.get(this);if(t)r[t]&&r[t].stop&&a(r[t]);else for(t in r)r[t]&&r[t].stop&&ut.test(t)&&a(r[t]);for(t=n.length;t--;)n[t].elem!==this||null!=i&&n[t].queue!==i||(n[t].anim.stop(o),e=!1,n.splice(t,1));!e&&o||k.dequeue(this,i)})},finish:function(a){return!1!==a&&(a=a||\"fx\"),this.each(function(){var e,t=Q.get(this),n=t[a+\"queue\"],r=t[a+\"queueHooks\"],i=k.timers,o=n?n.length:0;for(t.finish=!0,\nk.queue(this,a,[]),r&&r.stop&&r.stop.call(this,!0),e=i.length;e--;)i[e].elem===this&&i[e].queue===a&&(i[e].anim.stop(!0),i.splice(e,1));for(e=0;o>e;e++)n[e]&&n[e].finish&&n[e].finish.call(this);delete t.finish})}}),k.each([\"toggle\",\"show\",\"hide\"],function(e,r){var i=k.fn[r];k.fn[r]=function(e,t,n){return null==e||\"boolean\"==typeof e?i.apply(this,arguments):this.animate(ft(r,!0),e,t,n)}}),k.each({slideDown:ft(\"show\"),slideUp:ft(\"hide\"),slideToggle:ft(\"toggle\"),fadeIn:{opacity:\"show\"},fadeOut:{opacity:\"hide\"},fadeToggle:{opacity:\"toggle\"}},function(e,r){k.fn[e]=function(e,t,n){return this.animate(r,e,t,n)}}),k.timers=[],k.fx.tick=function(){var e,t=0,n=k.timers;for(rt=Date.now();t<n.length;t++)(e=n[t])()||n[t]!==e||n.splice(t--,1);n.length||k.fx.stop(),rt=void 0},k.fx.timer=function(e){k.timers.push(e),k.fx.start()},k.fx.interval=13,k.fx.start=function(){it||(it=!0,lt())},k.fx.stop=function(){it=null},k.fx.speeds={slow:600,fast:200,_default:400},k.fn.delay=function(r,e){return r=k.fx&&k.fx.speeds[r]||r,e=e||\"fx\",this.queue(e,function(e,t){var n=C.setTimeout(e,r);t.stop=function(){C.clearTimeout(n)}})},ot=E.createElement(\"input\"),at=E.createElement(\"select\").appendChild(E.createElement(\"option\")),ot.type=\"checkbox\",y.checkOn=\"\"!==ot.value,y.optSelected=at.selected,(ot=E.createElement(\"input\")).value=\"t\",ot.type=\"radio\",y.radioValue=\"t\"===ot.value;var ht,gt=k.expr.attrHandle;k.fn.extend({attr:function(e,t){return _(this,k.attr,e,t,1<arguments.length)},removeAttr:function(e){return this.each(function(){k.removeAttr(this,e)})}}),k.extend({attr:function(e,t,n){var r,i,o=e.nodeType;return 3!==o&&8!==o&&2!==o?\"undefined\"==typeof e.getAttribute?k.prop(e,t,n):(1===o&&k.isXMLDoc(e)||(i=k.attrHooks[t.toLowerCase()]||(k.expr.match.bool.test(t)?ht:void 0)),void 0!==n?null===n?void k.removeAttr(e,t):i&&\"set\"in i&&void 0!==(r=i.set(e,n,t))?r:(e.setAttribute(t,n+\"\"),n):i&&\"get\"in i&&null!==(r=i.get(e,t))?r:null==(r=k.find.attr(e,t))?void 0:r):void 0},attrHooks:{type:{set:function(e,t){if(!y.radioValue&&\"radio\"===t&&A(e,\"input\")){var n=e.value;return e.setAttribute(\"type\",t),n&&(e.value=n),t}}}},removeAttr:function(e,t){var n,r=0,i=t&&t.match(R);if(i&&1===e.nodeType)for(;n=i[r++];)e.removeAttribute(n)}}),ht={set:function(e,t,n){return!1===t?k.removeAttr(e,n):e.setAttribute(n,n),n}},k.each(k.expr.match.bool.source.match(/\\w+/g),function(e,t){var a=gt[t]||k.find.attr;gt[t]=function(e,t,n){var r,i,o=t.toLowerCase();return n||(i=gt[o],gt[o]=r,r=null!=a(e,t,n)?o:null,gt[o]=i),r}});var vt=/^(?:input|select|textarea|button)$/i,yt=/^(?:a|area)$/i;k.fn.extend({prop:function(e,t){return _(this,k.prop,e,t,1<arguments.length)},removeProp:function(e){return this.each(function(){delete this[k.propFix[e]||e]})}}),k.extend({prop:function(e,t,n){var r,i,o=e.nodeType;return 3!==o&&8!==o&&2!==o?(1===o&&k.isXMLDoc(e)||(t=k.propFix[t]||t,i=k.propHooks[t]),void 0!==n?i&&\"set\"in i&&void 0!==(r=i.set(e,n,t))?r:e[t]=n:i&&\"get\"in i&&null!==(r=i.get(e,t))?r:e[t]):void 0},propHooks:{tabIndex:{get:function(e){var t=k.find.attr(e,\"tabindex\");return t?parseInt(t,10):vt.test(e.nodeName)||yt.test(e.nodeName)&&e.href?0:-1}}},propFix:{\"for\":\"htmlFor\",\"class\":\"className\"}}),y.optSelected||(k.propHooks.selected={get:function(e){var t=e.parentNode;return t&&t.parentNode&&t.parentNode.selectedIndex,null},set:function(e){var t=e.parentNode;t&&(t.selectedIndex,t.parentNode&&t.parentNode.selectedIndex)}}),k.each([\"tabIndex\",\"readOnly\",\"maxLength\",\"cellSpacing\",\"cellPadding\",\"rowSpan\",\"colSpan\",\"useMap\",\"frameBorder\",\"contentEditable\"],function(){k.propFix[this.toLowerCase()]=this}),k.fn.extend({addClass:function(t){var e,n,r,i,o,a,s,u=0;if(m(t))return this.each(function(e){k(this).addClass(t.call(this,e,xt(this)))});if((e=bt(t)).length)for(;n=this[u++];)if(i=xt(n),r=1===n.nodeType&&\" \"+mt(i)+\" \"){for(a=0;o=e[a++];)r.indexOf(\" \"+o+\" \")<0&&(r+=o+\" \");i!==(s=mt(r))&&n.setAttribute(\"class\",s)}return this},removeClass:function(t){var e,n,r,i,o,a,s,u=0;if(m(t))return this.each(function(e){k(this).removeClass(t.call(this,e,xt(this)))});if(!arguments.length)return this.attr(\"class\",\"\");if((e=bt(t)).length)for(;n=this[u++];)if(i=xt(n),r=1===n.nodeType&&\" \"+mt(i)+\" \"){for(a=0;o=e[a++];)for(;-1<r.indexOf(\" \"+o+\" \");)r=r.replace(\" \"+o+\" \",\" \");i!==(s=mt(r))&&n.setAttribute(\"class\",s)}return this},toggleClass:function(i,t){var o=typeof i,a=\"string\"===o||Array.isArray(i);return\"boolean\"==typeof t&&a?t?this.addClass(i):this.removeClass(i):m(i)?this.each(function(e){k(this).toggleClass(i.call(this,e,xt(this),t),t)}):this.each(function(){var e,t,n,r;if(a)for(t=0,n=k(this),r=bt(i);e=r[t++];)n.hasClass(e)?n.removeClass(e):n.addClass(e);else void 0!==i&&\"boolean\"!==o||((e=xt(this))&&Q.set(this,\"__className__\",e),this.setAttribute&&this.setAttribute(\"class\",e||!1===i?\"\":Q.get(this,\"__className__\")||\"\"))})},hasClass:function(e){var t,n,r=0;for(t=\" \"+e+\" \";n=this[r++];)if(1===n.nodeType&&-1<(\" \"+mt(xt(n))+\" \").indexOf(t))return!0;return!1}});var wt=/\\r/g;k.fn.extend({val:function(n){var r,e,i,t=this[0];return arguments.length?(i=m(n),this.each(function(e){var t;1===this.nodeType&&(null==(t=i?n.call(this,e,k(this).val()):n)?t=\"\":\"number\"==typeof t?t+=\"\":Array.isArray(t)&&(t=k.map(t,function(e){return null==e?\"\":e+\"\"})),(r=k.valHooks[this.type]||k.valHooks[this.nodeName.toLowerCase()])&&\"set\"in r&&void 0!==r.set(this,t,\"value\")||(this.value=t))})):t?(r=k.valHooks[t.type]||k.valHooks[t.nodeName.toLowerCase()])&&\"get\"in r&&void 0!==(e=r.get(t,\"value\"))?e:\"string\"==typeof(e=t.value)?e.replace(wt,\"\"):null==e?\"\":e:void 0}}),k.extend({valHooks:{option:{get:function(e){var t=k.find.attr(e,\"value\");return null!=t?t:mt(k.text(e))}},select:{get:function(e){var t,n,r,i=e.options,o=e.selectedIndex,a=\"select-one\"===e.type,s=a?null:[],u=a?o+1:i.length;for(r=0>o?u:a?o:0;u>r;r++)if(((n=i[r]).selected||r===o)&&!n.disabled&&(!n.parentNode.disabled||!A(n.parentNode,\"optgroup\"))){if(t=k(n).val(),a)return t;s.push(t)}return s},set:function(e,t){for(var n,r,i=e.options,o=k.makeArray(t),a=i.length;a--;)((r=i[a]).selected=-1<k.inArray(k.valHooks.option.get(r),o))&&(n=!0);return n||(e.selectedIndex=-1),o}}}}),k.each([\"radio\",\"checkbox\"],function(){k.valHooks[this]={set:function(e,t){return Array.isArray(t)?e.checked=-1<k.inArray(k(e).val(),t):void 0}},y.checkOn||(k.valHooks[this].get=function(e){return null===e.getAttribute(\"value\")?\"on\":e.value})}),y.focusin=\"onfocusin\"in C;var Tt=/^(?:focusinfocus|focusoutblur)$/,Ct=function(e){e.stopPropagation()};k.extend(k.event,{trigger:function(e,t,n,r){var i,o,a,s,u,l,c,f,p=[n||E],d=v.call(e,\"type\")?e.type:e,h=v.call(e,\"namespace\")?e.namespace.split(\".\"):[];if(o=f=a=n=n||E,3!==n.nodeType&&8!==n.nodeType&&!Tt.test(d+k.event.triggered)&&(-1<d.indexOf(\".\")&&(d=(h=d.split(\".\")).shift(),h.sort()),u=d.indexOf(\":\")<0&&\"on\"+d,(e=e[k.expando]?e:new k.Event(d,\"object\"==typeof e&&e)).isTrigger=r?2:3,e.namespace=h.join(\".\"),e.rnamespace=e.namespace?new RegExp(\"(^|\\\\.)\"+h.join(\"\\\\.(?:.*\\\\.|)\")+\"(\\\\.|$)\"):null,e.result=void 0,e.target||(e.target=n),t=null==t?[e]:k.makeArray(t,[e]),c=k.event.special[d]||{},r||!c.trigger||!1!==c.trigger.apply(n,t))){if(!r&&!c.noBubble&&!x(n)){for(s=c.delegateType||d,Tt.test(s+d)||(o=o.parentNode);o;o=o.parentNode)p.push(o),a=o;a===(n.ownerDocument||E)&&p.push(a.defaultView||a.parentWindow||C)}for(i=0;(o=p[i++])&&!e.isPropagationStopped();)f=o,e.type=i>1?s:c.bindType||d,(l=(Q.get(o,\"events\")||{})[e.type]&&Q.get(o,\"handle\"))&&l.apply(o,t),(l=u&&o[u])&&l.apply&&G(o)&&(e.result=l.apply(o,t),!1===e.result&&e.preventDefault());return e.type=d,r||e.isDefaultPrevented()||c._default&&!1!==c._default.apply(p.pop(),t)||!G(n)||u&&m(n[d])&&!x(n)&&((a=n[u])&&(n[u]=null),k.event.triggered=d,e.isPropagationStopped()&&f.addEventListener(d,Ct),n[d](),e.isPropagationStopped()&&f.removeEventListener(d,Ct),k.event.triggered=void 0,a&&(n[u]=a)),e.result}},simulate:function(e,t,n){var r=k.extend(new k.Event,n,{type:e,isSimulated:!0});k.event.trigger(r,null,t)}}),k.fn.extend({trigger:function(e,t){return this.each(function(){k.event.trigger(e,t,this)})},triggerHandler:function(e,t){var n=this[0];return n?k.event.trigger(e,t,n,!0):void 0}}),y.focusin||k.each({focus:\"focusin\",blur:\"focusout\"},function(n,r){var i=function(e){k.event.simulate(r,e.target,k.event.fix(e))};k.event.special[r]={setup:function(){var e=this.ownerDocument||this,t=Q.access(e,r);t||e.addEventListener(n,i,!0),Q.access(e,r,(t||0)+1)},teardown:function(){var e=this.ownerDocument||this,t=Q.access(e,r)-1;t?Q.access(e,r,t):(e.removeEventListener(n,i,!0),Q.remove(e,r))}}});var Et=C.location,kt=Date.now(),St=/\\?/;k.parseXML=function(e){var t;if(!e||\"string\"!=typeof e)return null;try{t=(new C.DOMParser).parseFromString(e,\"text/xml\")}catch(e){t=void 0}return t&&!t.getElementsByTagName(\"parsererror\").length||k.error(\"Invalid XML: \"+e),t};var Nt=/\\[\\]$/,At=/\\r?\\n/g,Dt=/^(?:submit|button|image|reset|file)$/i,jt=/^(?:input|select|textarea|keygen)/i;k.param=function(e,t){var n,r=[],i=function(e,t){var n=m(t)?t():t;r[r.length]=encodeURIComponent(e)+\"=\"+encodeURIComponent(null==n?\"\":n)};if(null==e)return\"\";if(Array.isArray(e)||e.jquery&&!k.isPlainObject(e))k.each(e,function(){i(this.name,this.value)});else for(n in e)qt(n,e[n],t,i);return r.join(\"&\")},k.fn.extend({serialize:function(){return k.param(this.serializeArray())},serializeArray:function(){return this.map(function(){var e=k.prop(this,\"elements\");return e?k.makeArray(e):this}).filter(function(){var e=this.type;return this.name&&!k(this).is(\":disabled\")&&jt.test(this.nodeName)&&!Dt.test(e)&&(this.checked||!pe.test(e))}).map(function(e,t){var n=k(this).val();return null==n?null:Array.isArray(n)?k.map(n,function(e){return{name:t.name,value:e.replace(At,\"\\r\\n\")}}):{name:t.name,value:n.replace(At,\"\\r\\n\")}}).get()}});var Lt=/%20/g,Ht=/#.*$/,Ot=/([?&])_=[^&]*/,Pt=/^(.*?):[ \\t]*([^\\r\\n]*)$/gm,Rt=/^(?:GET|HEAD)$/,Mt=/^\\/\\//,It={},Wt={},$t=\"*/\".concat(\"*\"),Ft=E.createElement(\"a\");Ft.href=Et.href,k.extend({active:0,lastModified:{},etag:{},ajaxSettings:{url:Et.href,type:\"GET\",isLocal:/^(?:about|app|app-storage|.+-extension|file|res|widget):$/.test(Et.protocol),global:!0,processData:!0,async:!0,contentType:\"application/x-www-form-urlencoded; charset=UTF-8\",accepts:{\"*\":$t,text:\"text/plain\",html:\"text/html\",xml:\"application/xml, text/xml\",json:\"application/json, text/javascript\"},contents:{xml:/\\bxml\\b/,html:/\\bhtml/,json:/\\bjson\\b/},responseFields:{xml:\"responseXML\",text:\"responseText\",json:\"responseJSON\"},converters:{\"* text\":String,\"text html\":!0,\"text json\":JSON.parse,\"text xml\":k.parseXML},flatOptions:{url:!0,context:!0}},ajaxSetup:function(e,t){return t?zt(zt(e,k.ajaxSettings),t):zt(k.ajaxSettings,e)},ajaxPrefilter:Bt(It),ajaxTransport:Bt(Wt),ajax:function(e,t){function l(e,t,n,r){var i,o,a,s,u,l=t;h||(h=!0,d&&C.clearTimeout(d),c=void 0,p=r||\"\",T.readyState=e>0?4:0,i=e>=200&&300>e||304===e,n&&(s=function(e,t,n){for(var r,i,o,a,s=e.contents,u=e.dataTypes;\"*\"===u[0];)u.shift(),void 0===r&&(r=e.mimeType||t.getResponseHeader(\"Content-Type\"));if(r)for(i in s)if(s[i]&&s[i].test(r)){u.unshift(i);break}if(u[0]in n)o=u[0];else{for(i in n){if(!u[0]||e.converters[i+\" \"+u[0]]){o=i;break}a||(a=i)}o=o||a}return o?(o!==u[0]&&u.unshift(o),n[o]):void 0}(v,T,n)),s=function(e,t,n,r){var i,o,a,s,u,l={},c=e.dataTypes.slice();if(c[1])for(a in e.converters)l[a.toLowerCase()]=e.converters[a];for(o=c.shift();o;)if(e.responseFields[o]&&(n[e.responseFields[o]]=t),!u&&r&&e.dataFilter&&(t=e.dataFilter(t,e.dataType)),u=o,o=c.shift())if(\"*\"===o)o=u;else if(\"*\"!==u&&u!==o){if(!(a=l[u+\" \"+o]||l[\"* \"+o]))for(i in l)if((s=i.split(\" \"))[1]===o&&(a=l[u+\" \"+s[0]]||l[\"* \"+s[0]])){!0===a?a=l[i]:!0!==l[i]&&(o=s[0],c.unshift(s[1]));break}if(!0!==a)if(a&&e[\"throws\"])t=a(t);else try{t=a(t)}catch(e){return{state:\"parsererror\",error:a?e:\"No conversion from \"+u+\" to \"+o}}}return{state:\"success\",data:t}}(v,s,T,i),i?(v.ifModified&&((u=T.getResponseHeader(\"Last-Modified\"))&&(k.lastModified[f]=u),(u=T.getResponseHeader(\"etag\"))&&(k.etag[f]=u)),204===e||\"HEAD\"===v.type?l=\"nocontent\":304===e?l=\"notmodified\":(l=s.state,o=s.data,i=!(a=s.error))):(a=l,!e&&l||(l=\"error\",0>e&&(e=0))),T.status=e,T.statusText=(t||l)+\"\",i?x.resolveWith(y,[o,l,T]):x.rejectWith(y,[T,l,a]),T.statusCode(w),w=void 0,g&&m.trigger(i?\"ajaxSuccess\":\"ajaxError\",[T,v,i?o:a]),b.fireWith(y,[T,l]),g&&(m.trigger(\"ajaxComplete\",[T,v]),--k.active||k.event.trigger(\"ajaxStop\")))}\"object\"==typeof e&&(t=e,e=void 0),t=t||{};var c,f,p,n,d,r,h,g,i,o,v=k.ajaxSetup({},t),y=v.context||v,m=v.context&&(y.nodeType||y.jquery)?k(y):k.event,x=k.Deferred(),b=k.Callbacks(\"once memory\"),w=v.statusCode||{},a={},s={},u=\"canceled\",T={readyState:0,getResponseHeader:function(e){var t;if(h){if(!n)for(n={};t=Pt.exec(p);)n[t[1].toLowerCase()+\" \"]=(n[t[1].toLowerCase()+\" \"]||[]).concat(t[2]);t=n[e.toLowerCase()+\" \"]}return null==t?null:t.join(\", \")},getAllResponseHeaders:function(){return h?p:null},setRequestHeader:function(e,t){return null==h&&(e=s[e.toLowerCase()]=s[e.toLowerCase()]||e,a[e]=t),this},overrideMimeType:function(e){return null==h&&(v.mimeType=e),this},statusCode:function(e){var t;if(e)if(h)T.always(e[T.status]);else for(t in e)w[t]=[w[t],e[t]];return this},abort:function(e){var t=e||u;return c&&c.abort(t),l(0,t),this}};if(x.promise(T),v.url=((e||v.url||Et.href)+\"\").replace(Mt,Et.protocol+\"//\"),v.type=t.method||t.type||v.method||v.type,v.dataTypes=(v.dataType||\"*\").toLowerCase().match(R)||[\"\"],null==v.crossDomain){r=E.createElement(\"a\");try{r.href=v.url,r.href=r.href,v.crossDomain=Ft.protocol+\"//\"+Ft.host!=r.protocol+\"//\"+r.host}catch(e){v.crossDomain=!0}}if(v.data&&v.processData&&\"string\"!=typeof v.data&&(v.data=k.param(v.data,v.traditional)),_t(It,v,t,T),h)return T;for(i in(g=k.event&&v.global)&&0==k.active++&&k.event.trigger(\"ajaxStart\"),v.type=v.type.toUpperCase(),v.hasContent=!Rt.test(v.type),f=v.url.replace(Ht,\"\"),v.hasContent?v.data&&v.processData&&0===(v.contentType||\"\").indexOf(\"application/x-www-form-urlencoded\")&&(v.data=v.data.replace(Lt,\"+\")):(o=v.url.slice(f.length),v.data&&(v.processData||\"string\"==typeof v.data)&&(f+=(St.test(f)?\"&\":\"?\")+v.data,delete v.data),!1===v.cache&&(f=f.replace(Ot,\"$1\"),o=(St.test(f)?\"&\":\"?\")+\"_=\"+kt++ +o),v.url=f+o),v.ifModified&&(k.lastModified[f]&&T.setRequestHeader(\"If-Modified-Since\",k.lastModified[f]),k.etag[f]&&T.setRequestHeader(\"If-None-Match\",k.etag[f])),(v.data&&v.hasContent&&!1!==v.contentType||t.contentType)&&T.setRequestHeader(\"Content-Type\",v.contentType),T.setRequestHeader(\"Accept\",v.dataTypes[0]&&v.accepts[v.dataTypes[0]]?v.accepts[v.dataTypes[0]]+(\"*\"!==v.dataTypes[0]?\", \"+$t+\"; q=0.01\":\"\"):v.accepts[\"*\"]),v.headers)T.setRequestHeader(i,v.headers[i]);if(v.beforeSend&&(!1===v.beforeSend.call(y,T,v)||h))return T.abort();if(u=\"abort\",b.add(v.complete),T.done(v.success),T.fail(v.error),c=_t(Wt,v,t,T)){if(T.readyState=1,g&&m.trigger(\"ajaxSend\",[T,v]),h)return T;v.async&&0<v.timeout&&(d=C.setTimeout(function(){T.abort(\"timeout\")},v.timeout));try{h=!1,c.send(a,l)}catch(e){if(h)throw e;l(-1,e)}}else l(-1,\"No Transport\");return T},getJSON:function(e,t,n){return k.get(e,t,n,\"json\")},getScript:function(e,t){return k.get(e,void 0,t,\"script\")}}),k.each([\"get\",\"post\"],function(e,i){k[i]=function(e,t,n,r){return m(t)&&(r=r||n,n=t,t=void 0),k.ajax(k.extend({url:e,type:i,dataType:r,data:t,success:n},k.isPlainObject(e)&&e))}}),k._evalUrl=function(e,t){return k.ajax({url:e,type:\"GET\",dataType:\"script\",cache:!0,async:!1,global:!1,converters:{\"text script\":function(){}},dataFilter:function(e){k.globalEval(e,t)}})},k.fn.extend({wrapAll:function(e){var t;return this[0]&&(m(e)&&(e=e.call(this[0])),t=k(e,this[0].ownerDocument).eq(0).clone(!0),this[0].parentNode&&t.insertBefore(this[0]),t.map(function(){for(var e=this;e.firstElementChild;)e=e.firstElementChild;return e}).append(this)),this},wrapInner:function(n){return m(n)?this.each(function(e){k(this).wrapInner(n.call(this,e))}):this.each(function(){var e=k(this),t=e.contents();t.length?t.wrapAll(n):e.append(n)})},wrap:function(t){var n=m(t);return this.each(function(e){k(this).wrapAll(n?t.call(this,e):t)})},unwrap:function(e){return this.parent(e).not(\"body\").each(function(){k(this).replaceWith(this.childNodes)}),this}}),k.expr.pseudos.hidden=function(e){return!k.expr.pseudos.visible(e)},k.expr.pseudos.visible=function(e){return!!(e.offsetWidth||e.offsetHeight||e.getClientRects().length)},k.ajaxSettings.xhr=function(){try{return new C.XMLHttpRequest}catch(e){}};var Ut={0:200,1223:204},Xt=k.ajaxSettings.xhr();y.cors=!!Xt&&\"withCredentials\"in Xt,y.ajax=Xt=!!Xt,k.ajaxTransport(function(i){var o,a;return y.cors||Xt&&!i.crossDomain?{send:function(e,t){var n,r=i.xhr();if(r.open(i.type,i.url,i.async,i.username,i.password),i.xhrFields)for(n in i.xhrFields)r[n]=i.xhrFields[n];for(n in i.mimeType&&r.overrideMimeType&&r.overrideMimeType(i.mimeType),i.crossDomain||e[\"X-Requested-With\"]||(e[\"X-Requested-With\"]=\"XMLHttpRequest\"),e)r.setRequestHeader(n,e[n]);o=function(e){return function(){o&&(o=a=r.onload=r.onerror=r.onabort=r.ontimeout=r.onreadystatechange=null,\"abort\"===e?r.abort():\"error\"===e?\"number\"!=typeof r.status?t(0,\"error\"):t(r.status,r.statusText):t(Ut[r.status]||r.status,r.statusText,\"text\"!==(r.responseType||\"text\")||\"string\"!=typeof r.responseText?{binary:r.response}:{text:r.responseText},r.getAllResponseHeaders()))}},r.onload=o(),a=r.onerror=r.ontimeout=o(\"error\"),void 0!==r.onabort?r.onabort=a:r.onreadystatechange=function(){4===r.readyState&&C.setTimeout(function(){o&&a()})},o=o(\"abort\");try{r.send(i.hasContent&&i.data||null)}catch(e){if(o)throw e}},abort:function(){o&&o()}}:void 0}),k.ajaxPrefilter(function(e){e.crossDomain&&(e.contents.script=!1)}),k.ajaxSetup({accepts:{script:\"text/javascript, application/javascript, application/ecmascript, application/x-ecmascript\"},contents:{script:/\\b(?:java|ecma)script\\b/},converters:{\"text script\":function(e){return k.globalEval(e),e}}}),k.ajaxPrefilter(\"script\",function(e){void 0===e.cache&&(e.cache=!1),e.crossDomain&&(e.type=\"GET\")}),k.ajaxTransport(\"script\",function(n){var r,i;return n.crossDomain||n.scriptAttrs?{send:function(e,t){r=k(\"<script>\").attr(n.scriptAttrs||{}).prop({charset:n.scriptCharset,src:n.url}).on(\"load error\",i=function(e){r.remove(),i=null,e&&t(\"error\"===e.type?404:200,e.type)}),E.head.appendChild(r[0])},abort:function(){i&&i()}}:void 0});var Vt,Gt=[],Yt=/(=)\\?(?=&|$)|\\?\\?/;k.ajaxSetup({jsonp:\"callback\",jsonpCallback:function(){var e=Gt.pop()||k.expando+\"_\"+kt++;return this[e]=!0,e}}),k.ajaxPrefilter(\"json jsonp\",function(e,t,n){var r,i,o,a=!1!==e.jsonp&&(Yt.test(e.url)?\"url\":\"string\"==typeof e.data&&0===(e.contentType||\"\").indexOf(\"application/x-www-form-urlencoded\")&&Yt.test(e.data)&&\"data\");return a||\"jsonp\"===e.dataTypes[0]?(r=e.jsonpCallback=m(e.jsonpCallback)?e.jsonpCallback():e.jsonpCallback,a?e[a]=e[a].replace(Yt,\"$1\"+r):!1!==e.jsonp&&(e.url+=(St.test(e.url)?\"&\":\"?\")+e.jsonp+\"=\"+r),e.converters[\"script json\"]=function(){return o||k.error(r+\" was not called\"),o[0]},e.dataTypes[0]=\"json\",i=C[r],C[r]=function(){o=arguments},n.always(function(){void 0===i?k(C).removeProp(r):C[r]=i,e[r]&&(e.jsonpCallback=t.jsonpCallback,Gt.push(r)),o&&m(i)&&i(o[0]),o=i=void 0}),\"script\"):void 0}),y.createHTMLDocument=((Vt=E.implementation.createHTMLDocument(\"\").body).innerHTML=\"<form></form><form></form>\",2===Vt.childNodes.length),k.parseHTML=function(e,t,n){return\"string\"!=typeof e?[]:(\"boolean\"==typeof t&&(n=t,t=!1),t||(y.createHTMLDocument?((r=(t=E.implementation.createHTMLDocument(\"\")).createElement(\"base\")).href=E.location.href,t.head.appendChild(r)):t=E),o=!n&&[],(i=D.exec(e))?[t.createElement(i[1])]:(i=we([e],t,o),o&&o.length&&k(o).remove(),k.merge([],i.childNodes)));var r,i,o},k.fn.load=function(e,t,n){var r,i,o,a=this,s=e.indexOf(\" \");return s>-1&&(r=mt(e.slice(s)),e=e.slice(0,s)),m(t)?(n=t,t=void 0):t&&\"object\"==typeof t&&(i=\"POST\"),0<a.length&&k.ajax({url:e,type:i||\"GET\",dataType:\"html\",data:t}).done(function(e){o=arguments,a.html(r?k(\"<div>\").append(k.parseHTML(e)).find(r):e)}).always(n&&function(e,t){a.each(function(){n.apply(this,o||[e.responseText,t,e])})}),this},k.each([\"ajaxStart\",\"ajaxStop\",\"ajaxComplete\",\"ajaxError\",\"ajaxSuccess\",\"ajaxSend\"],function(e,t){k.fn[t]=function(e){return this.on(t,e)}}),k.expr.pseudos.animated=function(t){return k.grep(k.timers,function(e){return t===e.elem}).length},k.offset={setOffset:function(e,t,n){var r,i,o,a,s,u,l=k.css(e,\"position\"),c=k(e),f={};\"static\"===l&&(e.style.position=\"relative\"),s=c.offset(),o=k.css(e,\"top\"),u=k.css(e,\"left\"),(\"absolute\"===l||\"fixed\"===l)&&-1<(o+u).indexOf(\"auto\")?(a=(r=c.position()).top,i=r.left):(a=parseFloat(o)||0,i=parseFloat(u)||0),m(t)&&(t=t.call(e,n,k.extend({},s))),null!=t.top&&(f.top=t.top-s.top+a),null!=t.left&&(f.left=t.left-s.left+i),\"using\"in t?t.using.call(e,f):c.css(f)}},k.fn.extend({offset:function(t){if(arguments.length)return void 0===t?this:this.each(function(e){k.offset.setOffset(this,t,e)});var e,n,r=this[0];return r?r.getClientRects().length?(e=r.getBoundingClientRect(),n=r.ownerDocument.defaultView,{top:e.top+n.pageYOffset,left:e.left+n.pageXOffset}):{top:0,left:0}:void 0},position:function(){if(this[0]){var e,t,n,r=this[0],i={top:0,left:0};if(\"fixed\"===k.css(r,\"position\"))t=r.getBoundingClientRect();else{for(t=this.offset(),n=r.ownerDocument,e=r.offsetParent||n.documentElement;e&&(e===n.body||e===n.documentElement)&&\"static\"===k.css(e,\"position\");)e=e.parentNode;e&&e!==r&&1===e.nodeType&&((i=k(e).offset()).top+=k.css(e,\"borderTopWidth\",!0),i.left+=k.css(e,\"borderLeftWidth\",!0))}return{top:t.top-i.top-k.css(r,\"marginTop\",!0),left:t.left-i.left-k.css(r,\"marginLeft\",!0)}}},offsetParent:function(){return this.map(function(){for(var e=this.offsetParent;e&&\"static\"===k.css(e,\"position\");)e=e.offsetParent;return e||ie})}}),k.each({scrollLeft:\"pageXOffset\",scrollTop:\"pageYOffset\"},function(t,i){var o=\"pageYOffset\"===i;k.fn[t]=function(e){return _(this,function(e,t,n){var r;return x(e)?r=e:9===e.nodeType&&(r=e.defaultView),void 0===n?r?r[i]:e[t]:void(r?r.scrollTo(o?r.pageXOffset:n,o?n:r.pageYOffset):e[t]=n)},t,e,arguments.length)}}),k.each([\"top\",\"left\"],function(e,n){k.cssHooks[n]=ze(y.pixelPosition,function(e,t){return t?(t=_e(e,n),$e.test(t)?k(e).position()[n]+\"px\":t):void 0})}),k.each({Height:\"height\",Width:\"width\"},function(a,s){k.each({padding:\"inner\"+a,content:s,\"\":\"outer\"+a},function(r,o){k.fn[o]=function(e,t){var n=arguments.length&&(r||\"boolean\"!=typeof e),i=r||(!0===e||!0===t?\"margin\":\"border\");return _(this,function(e,t,n){var r;return x(e)?0===o.indexOf(\"outer\")?e[\"inner\"+a]:e.document.documentElement[\"client\"+a]:9===e.nodeType?(r=e.documentElement,Math.max(e.body[\"scroll\"+a],r[\"scroll\"+a],e.body[\"offset\"+a],r[\"offset\"+a],r[\"client\"+a])):void 0===n?k.css(e,t,i):k.style(e,t,n,i)},s,n?e:void 0,n)}})}),k.each(\"blur focus focusin focusout resize scroll click dblclick mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave change select submit keydown keypress keyup contextmenu\".split(\" \"),function(e,n){k.fn[n]=function(e,t){return 0<arguments.length?this.on(n,null,e,t):this.trigger(n)}}),k.fn.extend({hover:function(e,t){return this.mouseenter(e).mouseleave(t||e)}}),k.fn.extend({bind:function(e,t,n){return this.on(e,null,t,n)},unbind:function(e,t){return this.off(e,null,t)},delegate:function(e,t,n,r){return this.on(t,e,n,r)},undelegate:function(e,t,n){return 1===arguments.length?this.off(e,\"**\"):this.off(t,e||\"**\",n)}}),k.proxy=function(e,t){var n,r,i;return\"string\"==typeof t&&(n=e[t],t=e,e=n),m(e)?(r=s.call(arguments,2),(i=function(){return e.apply(t||this,r.concat(s.call(arguments)))}).guid=e.guid=e.guid||k.guid++,i):void 0},k.holdReady=function(e){e?k.readyWait++:k.ready(!0)},k.isArray=Array.isArray,k.parseJSON=JSON.parse,k.nodeName=A,k.isFunction=m,k.isWindow=x,k.camelCase=V,k.type=w,k.now=Date.now,k.isNumeric=function(e){var t=k.type(e);return(\"number\"===t||\"string\"===t)&&!isNaN(e-parseFloat(e))},\"function\"==typeof define&&define.amd&&define(\"jquery\",[],function(){return k});var Qt=C.jQuery,Jt=C.$;return k.noConflict=function(e){return C.$===k&&(C.$=Jt),e&&C.jQuery===k&&(C.jQuery=Qt),k},e||(C.jQuery=C.$=k),k}),function(e){function u(s,o){var u,a,f;if(0==(u=e(s))[t])return n;switch(a=u[i]()[r],o.anchor){case\"middle\":f=a-(e(window).height()-u.outerHeight())/2;break;default:case r:f=Math.max(a,0)}return f-=\"function\"==typeof o[i]?o[i]():o[i]}var t=\"length\",n=null,r=\"top\",i=\"offset\",s=\"click.scrolly\";e(window);e.fn.scrolly=function(i){var o,a,f,l,c=e(this);if(0==this[t])return c;if(this[t]>1){for(o=0;o<this[t];o++)e(this[o]).scrolly(i);return c}return l=n,f=c.attr(\"href\"),\"#\"!=f.charAt(0)||f[t]<2?c:(a=jQuery.extend({anchor:r,easing:\"swing\",offset:0,parent:e(\"body,html\"),pollOnce:!1,speed:1e3},i),a.pollOnce&&(l=u(f,a)),c.off(s).on(s,function(e){var t=l!==n?l:u(f,a);t!==n&&(e.preventDefault(),a.parent.stop().animate({scrollTop:t},a.speed,a.easing))}),void 0)}}(jQuery),!function(t){function e(t,e,n){return\"string\"==typeof t&&(\"%\"==t.slice(-1)?t=parseInt(t.substring(0,t.length-1))/100*e:\"vh\"==t.slice(-2)?t=parseInt(t.substring(0,t.length-2))/100*n:\"px\"==t.slice(-2)&&(t=parseInt(t.substring(0,t.length-2)))),t}var n=t(window),i=1,o={};n.on(\"scroll\",function(){var e=n.scrollTop();t.map(o,function(t){window.clearTimeout(t.timeoutId),t.timeoutId=window.setTimeout(function(){t.handler(e)},t.options.delay)})}).on(\"load\",function(){n.trigger(\"scroll\")}),jQuery.fn.scrollex=function(l){var s=t(this);if(0==this.length)return s;if(this.length>1){for(var r=0;r<this.length;r++)t(this[r]).scrollex(l);return s}if(s.data(\"_scrollexId\"))return s;var a,u,h,c,p;switch(a=i++,u=jQuery.extend({top:0,bottom:0,delay:0,mode:\"default\",enter:null,leave:null,initialize:null,terminate:null,scroll:null},l),u.mode){case\"top\":h=function(t,e,n,i,o){return t>=i&&o>=t};break;case\"bottom\":h=function(t,e,n,i,o){return n>=i&&o>=n};break;case\"middle\":h=function(t,e,n,i,o){return e>=i&&o>=e};break;case\"top-only\":h=function(t,e,n,i,o){return i>=t&&n>=i};break;case\"bottom-only\":h=function(t,e,n,i,o){return n>=o&&o>=t};break;default:case\"default\":h=function(t,e,n,i,o){return n>=i&&o>=t}}return c=function(t){var i,o,l,s,r,a,u=this.state,h=!1,c=this.$element.offset();i=n.height(),o=t+i/2,l=t+i,s=this.$element.outerHeight(),r=c.top+e(this.options.top,s,i),a=c.top+s-e(this.options.bottom,s,i),h=this.test(t,o,l,r,a),h!=u&&(this.state=h,h?this.options.enter&&this.options.enter.apply(this.element):this.options.leave&&this.options.leave.apply(this.element)),this.options.scroll&&this.options.scroll.apply(this.element,[(o-r)/(a-r)])},p={id:a,options:u,test:h,handler:c,state:null,element:this,$element:s,timeoutId:null},o[a]=p,s.data(\"_scrollexId\",p.id),p.options.initialize&&p.options.initialize.apply(this),s},jQuery.fn.unscrollex=function(){var e=t(this);if(0==this.length)return e;if(this.length>1){for(var n=0;n<this.length;n++)t(this[n]).unscrollex();return e}var i,l;return(i=e.data(\"_scrollexId\"))?(l=o[i],window.clearTimeout(l.timeoutId),delete o[i],e.removeData(\"_scrollexId\"),l.options.terminate&&l.options.terminate.apply(this),e):e}}(jQuery);var browser=function(){\"use strict\";var e={name:null,version:null,os:null,osVersion:null,touch:null,mobile:null,_canUse:null,canUse:function(n){e._canUse||(e._canUse=document.createElement(\"div\"));var o=e._canUse.style,r=n.charAt(0).toUpperCase()+n.slice(1);return n in o||\"Moz\"+r in o||\"Webkit\"+r in o||\"O\"+r in o||\"ms\"+r in o},init:function(){var n,o,r,i,t=navigator.userAgent;for(n=\"other\",o=0,r=[[\"firefox\",/Firefox\\/([0-9\\.]+)/],[\"bb\",/BlackBerry.+Version\\/([0-9\\.]+)/],[\"bb\",/BB[0-9]+.+Version\\/([0-9\\.]+)/],[\"opera\",/OPR\\/([0-9\\.]+)/],[\"opera\",/Opera\\/([0-9\\.]+)/],[\"edge\",/Edge\\/([0-9\\.]+)/],[\"safari\",/Version\\/([0-9\\.]+).+Safari/],[\"chrome\",/Chrome\\/([0-9\\.]+)/],[\"ie\",/MSIE ([0-9]+)/],[\"ie\",/Trident\\/.+rv:([0-9]+)/]],i=0;i<r.length;i++)if(t.match(r[i][1])){n=r[i][0],o=parseFloat(RegExp.$1);break}for(e.name=n,e.version=o,n=\"other\",o=0,r=[[\"ios\",/([0-9_]+) like Mac OS X/,function(e){return e.replace(\"_\",\".\").replace(\"_\",\"\")}],[\"ios\",/CPU like Mac OS X/,function(e){return 0}],[\"wp\",/Windows Phone ([0-9\\.]+)/,null],[\"android\",/Android ([0-9\\.]+)/,null],[\"mac\",/Macintosh.+Mac OS X ([0-9_]+)/,function(e){return e.replace(\"_\",\".\").replace(\"_\",\"\")}],[\"windows\",/Windows NT ([0-9\\.]+)/,null],[\"bb\",/BlackBerry.+Version\\/([0-9\\.]+)/,null],[\"bb\",/BB[0-9]+.+Version\\/([0-9\\.]+)/,null],[\"linux\",/Linux/,null],[\"bsd\",/BSD/,null],[\"unix\",/X11/,null]],i=0;i<r.length;i++)if(t.match(r[i][1])){n=r[i][0],o=parseFloat(r[i][2]?r[i][2](RegExp.$1):RegExp.$1);break}e.os=n,e.osVersion=o,e.touch=\"wp\"==e.os?navigator.msMaxTouchPoints>0:!!(\"ontouchstart\"in window),e.mobile=\"wp\"==e.os||\"android\"==e.os||\"ios\"==e.os||\"bb\"==e.os}};return e.init(),e}();!function(e,n){\"function\"==typeof define&&define.amd?define([],n):\"object\"==typeof exports?module.exports=n():e.browser=n()}(this,function(){return browser});var breakpoints=function(){\"use strict\";function e(e){t.init(e)}var t={list:null,media:{},events:[],init:function(e){t.list=e,window.addEventListener(\"resize\",t.poll),window.addEventListener(\"orientationchange\",t.poll),window.addEventListener(\"load\",t.poll),window.addEventListener(\"fullscreenchange\",t.poll)},active:function(e){var n,a,s,i,r,d,c;if(!(e in t.media)){if(\">=\"==e.substr(0,2)?(a=\"gte\",n=e.substr(2)):\"<=\"==e.substr(0,2)?(a=\"lte\",n=e.substr(2)):\">\"==e.substr(0,1)?(a=\"gt\",n=e.substr(1)):\"<\"==e.substr(0,1)?(a=\"lt\",n=e.substr(1)):\"!\"==e.substr(0,1)?(a=\"not\",n=e.substr(1)):(a=\"eq\",n=e),n&&n in t.list)if(i=t.list[n],Array.isArray(i)){if(r=parseInt(i[0]),d=parseInt(i[1]),isNaN(r)){if(isNaN(d))return;c=i[1].substr(String(d).length)}else c=i[0].substr(String(r).length);if(isNaN(r))switch(a){case\"gte\":s=\"screen\";break;case\"lte\":s=\"screen and (max-width: \"+d+c+\")\";break;case\"gt\":s=\"screen and (min-width: \"+(d+1)+c+\")\";break;case\"lt\":s=\"screen and (max-width: -1px)\";break;case\"not\":s=\"screen and (min-width: \"+(d+1)+c+\")\";break;default:s=\"screen and (max-width: \"+d+c+\")\"}else if(isNaN(d))switch(a){case\"gte\":s=\"screen and (min-width: \"+r+c+\")\";break;case\"lte\":s=\"screen\";break;case\"gt\":s=\"screen and (max-width: -1px)\";break;case\"lt\":s=\"screen and (max-width: \"+(r-1)+c+\")\";break;case\"not\":s=\"screen and (max-width: \"+(r-1)+c+\")\";break;default:s=\"screen and (min-width: \"+r+c+\")\"}else switch(a){case\"gte\":s=\"screen and (min-width: \"+r+c+\")\";break;case\"lte\":s=\"screen and (max-width: \"+d+c+\")\";break;case\"gt\":s=\"screen and (min-width: \"+(d+1)+c+\")\";break;case\"lt\":s=\"screen and (max-width: \"+(r-1)+c+\")\";break;case\"not\":s=\"screen and (max-width: \"+(r-1)+c+\"), screen and (min-width: \"+(d+1)+c+\")\";break;default:s=\"screen and (min-width: \"+r+c+\") and (max-width: \"+d+c+\")\"}}else s=\"(\"==i.charAt(0)?\"screen and \"+i:i;t.media[e]=!!s&&s}return t.media[e]!==!1&&window.matchMedia(t.media[e]).matches},on:function(e,n){t.events.push({query:e,handler:n,state:!1}),t.active(e)&&n()},poll:function(){var e,n;for(e=0;e<t.events.length;e++)n=t.events[e],t.active(n.query)?n.state||(n.state=!0,n.handler()):n.state&&(n.state=!1)}};return e._=t,e.on=function(e,n){t.on(e,n)},e.active=function(e){return t.active(e)},e}();!function(e,t){\"function\"==typeof define&&define.amd?define([],t):\"object\"==typeof exports?module.exports=t():e.breakpoints=t()}(this,function(){return breakpoints}),function($){$.fn.navList=function(){var $this=$(this);return $a=$this.find(\"a\"),b=[],$a.each(function(){var $this=$(this),indent=Math.max(0,$this.parents(\"li\").length-1),href=$this.attr(\"href\"),target=$this.attr(\"target\");b.push('<a class=\"link depth-'+indent+'\"'+(\"undefined\"!=typeof target&&\"\"!=target?' target=\"'+target+'\"':\"\")+(\"undefined\"!=typeof href&&\"\"!=href?' href=\"'+href+'\"':\"\")+'><span class=\"indent-'+indent+'\"></span>'+$this.text()+\"</a>\")}),b.join(\"\")},$.fn.panel=function(userConfig){if(0==this.length)return $this;if(this.length>1){for(var i=0;i<this.length;i++)$(this[i]).panel(userConfig);return $this}var config,$this=$(this),$body=$(\"body\"),$window=$(window),id=$this.attr(\"id\");return config=$.extend({delay:0,hideOnClick:!1,hideOnEscape:!1,hideOnSwipe:!1,resetScroll:!1,resetForms:!1,side:null,target:$this,\nvisibleClass:\"visible\"},userConfig),\"jQuery\"!=typeof config.target&&(config.target=$(config.target)),$this._hide=function(event){config.target.hasClass(config.visibleClass)&&(event&&(event.preventDefault(),event.stopPropagation()),config.target.removeClass(config.visibleClass),window.setTimeout(function(){config.resetScroll&&$this.scrollTop(0),config.resetForms&&$this.find(\"form\").each(function(){this.reset()})},config.delay))},$this.css(\"-ms-overflow-style\",\"-ms-autohiding-scrollbar\").css(\"-webkit-overflow-scrolling\",\"touch\"),config.hideOnClick&&($this.find(\"a\").css(\"-webkit-tap-highlight-color\",\"rgba(0,0,0,0)\"),$this.on(\"click\",\"a\",function(event){var $a=$(this),href=$a.attr(\"href\"),target=$a.attr(\"target\");href&&\"#\"!=href&&\"\"!=href&&href!=\"#\"+id&&(event.preventDefault(),event.stopPropagation(),$this._hide(),window.setTimeout(function(){\"_blank\"==target?window.open(href):window.location.href=href},config.delay+10))})),$this.on(\"touchstart\",function(event){$this.touchPosX=event.originalEvent.touches[0].pageX,$this.touchPosY=event.originalEvent.touches[0].pageY}),$this.on(\"touchmove\",function(event){if(null!==$this.touchPosX&&null!==$this.touchPosY){var diffX=$this.touchPosX-event.originalEvent.touches[0].pageX,diffY=$this.touchPosY-event.originalEvent.touches[0].pageY,th=$this.outerHeight(),ts=$this.get(0).scrollHeight-$this.scrollTop();if(config.hideOnSwipe){var result=!1,boundary=20,delta=50;switch(config.side){case\"left\":result=boundary>diffY&&diffY>-1*boundary&&diffX>delta;break;case\"right\":result=boundary>diffY&&diffY>-1*boundary&&-1*delta>diffX;break;case\"top\":result=boundary>diffX&&diffX>-1*boundary&&diffY>delta;break;case\"bottom\":result=boundary>diffX&&diffX>-1*boundary&&-1*delta>diffY}if(result)return $this.touchPosX=null,$this.touchPosY=null,$this._hide(),!1}($this.scrollTop()<0&&0>diffY||ts>th-2&&th+2>ts&&diffY>0)&&(event.preventDefault(),event.stopPropagation())}}),$this.on(\"click touchend touchstart touchmove\",function(event){event.stopPropagation()}),$this.on(\"click\",'a[href=\"#'+id+'\"]',function(event){event.preventDefault(),event.stopPropagation(),config.target.removeClass(config.visibleClass)}),$body.on(\"click touchend\",function(event){$this._hide(event)}),$body.on(\"click\",'a[href=\"#'+id+'\"]',function(event){event.preventDefault(),event.stopPropagation(),config.target.toggleClass(config.visibleClass)}),config.hideOnEscape&&$window.on(\"keydown\",function(event){27==event.keyCode&&$this._hide(event)}),$this},$.fn.placeholder=function(){if(\"undefined\"!=typeof document.createElement(\"input\").placeholder)return $(this);if(0==this.length)return $this;if(this.length>1){for(var i=0;i<this.length;i++)$(this[i]).placeholder();return $this}var $this=$(this);return $this.find(\"input[type=text],textarea\").each(function(){var i=$(this);(\"\"==i.val()||i.val()==i.attr(\"placeholder\"))&&i.addClass(\"polyfill-placeholder\").val(i.attr(\"placeholder\"))}).on(\"blur\",function(){var i=$(this);i.attr(\"name\").match(/-polyfill-field$/)||\"\"==i.val()&&i.addClass(\"polyfill-placeholder\").val(i.attr(\"placeholder\"))}).on(\"focus\",function(){var i=$(this);i.attr(\"name\").match(/-polyfill-field$/)||i.val()==i.attr(\"placeholder\")&&i.removeClass(\"polyfill-placeholder\").val(\"\")}),$this.find(\"input[type=password]\").each(function(){var i=$(this),x=$($(\"<div>\").append(i.clone()).remove().html().replace(/type=\"password\"/i,'type=\"text\"').replace(/type=password/i,\"type=text\"));\"\"!=i.attr(\"id\")&&x.attr(\"id\",i.attr(\"id\")+\"-polyfill-field\"),\"\"!=i.attr(\"name\")&&x.attr(\"name\",i.attr(\"name\")+\"-polyfill-field\"),x.addClass(\"polyfill-placeholder\").val(x.attr(\"placeholder\")).insertAfter(i),\"\"==i.val()?i.hide():x.hide(),i.on(\"blur\",function(event){event.preventDefault();var x=i.parent().find(\"input[name=\"+i.attr(\"name\")+\"-polyfill-field]\");\"\"==i.val()&&(i.hide(),x.show())}),x.on(\"focus\",function(event){event.preventDefault();var i=x.parent().find(\"input[name=\"+x.attr(\"name\").replace(\"-polyfill-field\",\"\")+\"]\");x.hide(),i.show().focus()}).on(\"keypress\",function(event){event.preventDefault(),x.val(\"\")})}),$this.on(\"submit\",function(){$this.find(\"input[type=text],input[type=password],textarea\").each(function(event){var i=$(this);i.attr(\"name\").match(/-polyfill-field$/)&&i.attr(\"name\",\"\"),i.val()==i.attr(\"placeholder\")&&(i.removeClass(\"polyfill-placeholder\"),i.val(\"\"))})}).on(\"reset\",function(event){event.preventDefault(),$this.find(\"select\").val($(\"option:first\").val()),$this.find(\"input,textarea\").each(function(){var x,i=$(this);switch(i.removeClass(\"polyfill-placeholder\"),this.type){case\"submit\":case\"reset\":break;case\"password\":i.val(i.attr(\"defaultValue\")),x=i.parent().find(\"input[name=\"+i.attr(\"name\")+\"-polyfill-field]\"),\"\"==i.val()?(i.hide(),x.show()):(i.show(),x.hide());break;case\"checkbox\":case\"radio\":i.attr(\"checked\",i.attr(\"defaultValue\"));break;case\"text\":case\"textarea\":i.val(i.attr(\"defaultValue\")),\"\"==i.val()&&(i.addClass(\"polyfill-placeholder\"),i.val(i.attr(\"placeholder\")));break;default:i.val(i.attr(\"defaultValue\"))}})}),$this},$.prioritize=function($elements,condition){var key=\"__prioritize\";\"jQuery\"!=typeof $elements&&($elements=$($elements)),$elements.each(function(){var $p,$e=$(this),$parent=$e.parent();if(0!=$parent.length)if($e.data(key)){if(condition)return;$p=$e.data(key),$e.insertAfter($p),$e.removeData(key)}else{if(!condition)return;if($p=$e.prev(),0==$p.length)return;$e.prependTo($parent),$e.data(key,$p)}})}}(jQuery),function($){var $window=$(window),$body=$(\"body\"),$wrapper=$(\"#wrapper\"),$header=$(\"#header\"),$banner=$(\"#banner\");breakpoints({xlarge:[\"1281px\",\"1680px\"],large:[\"981px\",\"1280px\"],medium:[\"737px\",\"980px\"],small:[\"481px\",\"736px\"],xsmall:[\"361px\",\"480px\"],xxsmall:[null,\"360px\"]}),$.fn._parallax=\"ie\"==browser.name||\"edge\"==browser.name||browser.mobile?function(){return $(this)}:function(intensity){var $window=$(window),$this=$(this);if(0==this.length||0===intensity)return $this;if(this.length>1){for(var i=0;i<this.length;i++)$(this[i])._parallax(intensity);return $this}return intensity||(intensity=.25),$this.each(function(){var on,off,$t=$(this);on=function(){$t.css(\"background-position\",\"center 100%, center 100%, center 0px\"),$window.on(\"scroll._parallax\",function(){var pos=parseInt($window.scrollTop())-parseInt($t.position().top);$t.css(\"background-position\",\"center \"+pos*(-1*intensity)+\"px\")})},off=function(){$t.css(\"background-position\",\"\"),$window.off(\"scroll._parallax\")},breakpoints.on(\"<=medium\",off),breakpoints.on(\">medium\",on)}),$window.off(\"load._parallax resize._parallax\").on(\"load._parallax resize._parallax\",function(){$window.trigger(\"scroll\")}),$(this)},$window.on(\"load\",function(){window.setTimeout(function(){$body.removeClass(\"is-preload\")},100)}),$window.on(\"unload pagehide\",function(){window.setTimeout(function(){$(\".is-transitioning\").removeClass(\"is-transitioning\")},250)}),(\"ie\"==browser.name||\"edge\"==browser.name)&&$body.addClass(\"is-ie\"),$(\".scrolly\").scrolly({offset:function(){return $header.height()-2}});var $tiles=$(\".tiles > article\");$tiles.each(function(){var x,$this=$(this),$image=$this.find(\".image\"),$img=$image.find(\"img\"),$link=$this.find(\".link\");$this.css(\"background-image\",\"url(\"+$img.attr(\"src\")+\")\"),(x=$img.data(\"position\"))&&$image.css(\"background-position\",x),$image.hide(),$link.length>0&&($x=$link.clone().text(\"\").addClass(\"primary\").appendTo($this),$link=$link.add($x),$link.on(\"click\",function(event){var href=$link.attr(\"href\");event.stopPropagation(),event.preventDefault(),\"_blank\"==$link.attr(\"target\")?window.open(href):($this.addClass(\"is-transitioning\"),$wrapper.addClass(\"is-transitioning\"),window.setTimeout(function(){location.href=href},500))}))}),$banner.length>0&&$header.hasClass(\"alt\")&&($window.on(\"resize\",function(){$window.trigger(\"scroll\")}),$window.on(\"load\",function(){$banner.scrollex({bottom:$header.height()+10,terminate:function(){$header.removeClass(\"alt\")},enter:function(){$header.addClass(\"alt\")},leave:function(){$header.removeClass(\"alt\"),$header.addClass(\"reveal\")}}),window.setTimeout(function(){$window.triggerHandler(\"scroll\")},100)})),$banner.each(function(){var $this=$(this),$image=$this.find(\".image\"),$img=$image.find(\"img\");$this._parallax(.275),$image.length>0&&($this.css(\"background-image\",\"url(\"+$img.attr(\"src\")+\")\"),$image.hide())});var $menuInner,$menu=$(\"#menu\");$menu.wrapInner('<div class=\"inner\"></div>'),$menuInner=$menu.children(\".inner\"),$menu._locked=!1,$menu._lock=function(){return $menu._locked?!1:($menu._locked=!0,window.setTimeout(function(){$menu._locked=!1},350),!0)},$menu._show=function(){$menu._lock()&&$body.addClass(\"is-menu-visible\")},$menu._hide=function(){$menu._lock()&&$body.removeClass(\"is-menu-visible\")},$menu._toggle=function(){$menu._lock()&&$body.toggleClass(\"is-menu-visible\")},$menuInner.on(\"click\",function(event){event.stopPropagation()}).on(\"click\",\"a\",function(event){var href=$(this).attr(\"href\");event.preventDefault(),event.stopPropagation(),$menu._hide(),window.setTimeout(function(){window.location.href=href},250)}),$menu.appendTo($body).on(\"click\",function(event){event.stopPropagation(),event.preventDefault(),$body.removeClass(\"is-menu-visible\")}).append('<a class=\"close\" href=\"#menu\">Close</a>'),$body.on(\"click\",'a[href=\"#menu\"]',function(event){event.stopPropagation(),event.preventDefault(),$menu._toggle()}).on(\"click\",function(event){$menu._hide()}).on(\"keydown\",function(event){27==event.keyCode&&$menu._hide()})}(jQuery);"
  },
  {
    "path": "app/src/main/assets/web/assets/js/md5.js",
    "content": "/*\n * A JavaScript implementation of the RSA Data Security, Inc. MD5 Message\n * Digest Algorithm, as defined in RFC 1321.\n * Version 2.1 Copyright (C) Paul Johnston 1999 - 2002.\n * Other contributors: Greg Holt, Andrew Kepert, Ydnar, Lostinet\n * Distributed under the BSD License\n * See http://pajhome.org.uk/crypt/md5 for more info.\n */\n\n/*\n * Configurable variables. You may need to tweak these to be compatible with\n * the server-side, but the defaults work in most cases.\n */\nvar hexcase = 0;  /* hex output format. 0 - lowercase; 1 - uppercase        */\nvar b64pad  = \"\"; /* base-64 pad character. \"=\" for strict RFC compliance   */\nvar chrsz   = 8;  /* bits per input character. 8 - ASCII; 16 - Unicode      */\n\n/*\n * These are the functions you'll usually want to call\n * They take string arguments and return either hex or base-64 encoded strings\n */\nfunction hex_md5(s){ return binl2hex(core_md5(str2binl(s), s.length * chrsz));}\nfunction b64_md5(s){ return binl2b64(core_md5(str2binl(s), s.length * chrsz));}\nfunction str_md5(s){ return binl2str(core_md5(str2binl(s), s.length * chrsz));}\nfunction hex_hmac_md5(key, data) { return binl2hex(core_hmac_md5(key, data)); }\nfunction b64_hmac_md5(key, data) { return binl2b64(core_hmac_md5(key, data)); }\nfunction str_hmac_md5(key, data) { return binl2str(core_hmac_md5(key, data)); }\n\n/*\n * Perform a simple self-test to see if the VM is working\n */\nfunction md5_vm_test()\n{\n  return hex_md5(\"abc\") == \"900150983cd24fb0d6963f7d28e17f72\";\n}\n\n/*\n * Calculate the MD5 of an array of little-endian words, and a bit length\n */\nfunction core_md5(x, len)\n{\n  /* append padding */\n  x[len >> 5] |= 0x80 << ((len) % 32);\n  x[(((len + 64) >>> 9) << 4) + 14] = len;\n\n  var a =  1732584193;\n  var b = -271733879;\n  var c = -1732584194;\n  var d =  271733878;\n\n  for(var i = 0; i < x.length; i += 16)\n  {\n    var olda = a;\n    var oldb = b;\n    var oldc = c;\n    var oldd = d;\n\n    a = md5_ff(a, b, c, d, x[i+ 0], 7 , -680876936);\n    d = md5_ff(d, a, b, c, x[i+ 1], 12, -389564586);\n    c = md5_ff(c, d, a, b, x[i+ 2], 17,  606105819);\n    b = md5_ff(b, c, d, a, x[i+ 3], 22, -1044525330);\n    a = md5_ff(a, b, c, d, x[i+ 4], 7 , -176418897);\n    d = md5_ff(d, a, b, c, x[i+ 5], 12,  1200080426);\n    c = md5_ff(c, d, a, b, x[i+ 6], 17, -1473231341);\n    b = md5_ff(b, c, d, a, x[i+ 7], 22, -45705983);\n    a = md5_ff(a, b, c, d, x[i+ 8], 7 ,  1770035416);\n    d = md5_ff(d, a, b, c, x[i+ 9], 12, -1958414417);\n    c = md5_ff(c, d, a, b, x[i+10], 17, -42063);\n    b = md5_ff(b, c, d, a, x[i+11], 22, -1990404162);\n    a = md5_ff(a, b, c, d, x[i+12], 7 ,  1804603682);\n    d = md5_ff(d, a, b, c, x[i+13], 12, -40341101);\n    c = md5_ff(c, d, a, b, x[i+14], 17, -1502002290);\n    b = md5_ff(b, c, d, a, x[i+15], 22,  1236535329);\n\n    a = md5_gg(a, b, c, d, x[i+ 1], 5 , -165796510);\n    d = md5_gg(d, a, b, c, x[i+ 6], 9 , -1069501632);\n    c = md5_gg(c, d, a, b, x[i+11], 14,  643717713);\n    b = md5_gg(b, c, d, a, x[i+ 0], 20, -373897302);\n    a = md5_gg(a, b, c, d, x[i+ 5], 5 , -701558691);\n    d = md5_gg(d, a, b, c, x[i+10], 9 ,  38016083);\n    c = md5_gg(c, d, a, b, x[i+15], 14, -660478335);\n    b = md5_gg(b, c, d, a, x[i+ 4], 20, -405537848);\n    a = md5_gg(a, b, c, d, x[i+ 9], 5 ,  568446438);\n    d = md5_gg(d, a, b, c, x[i+14], 9 , -1019803690);\n    c = md5_gg(c, d, a, b, x[i+ 3], 14, -187363961);\n    b = md5_gg(b, c, d, a, x[i+ 8], 20,  1163531501);\n    a = md5_gg(a, b, c, d, x[i+13], 5 , -1444681467);\n    d = md5_gg(d, a, b, c, x[i+ 2], 9 , -51403784);\n    c = md5_gg(c, d, a, b, x[i+ 7], 14,  1735328473);\n    b = md5_gg(b, c, d, a, x[i+12], 20, -1926607734);\n\n    a = md5_hh(a, b, c, d, x[i+ 5], 4 , -378558);\n    d = md5_hh(d, a, b, c, x[i+ 8], 11, -2022574463);\n    c = md5_hh(c, d, a, b, x[i+11], 16,  1839030562);\n    b = md5_hh(b, c, d, a, x[i+14], 23, -35309556);\n    a = md5_hh(a, b, c, d, x[i+ 1], 4 , -1530992060);\n    d = md5_hh(d, a, b, c, x[i+ 4], 11,  1272893353);\n    c = md5_hh(c, d, a, b, x[i+ 7], 16, -155497632);\n    b = md5_hh(b, c, d, a, x[i+10], 23, -1094730640);\n    a = md5_hh(a, b, c, d, x[i+13], 4 ,  681279174);\n    d = md5_hh(d, a, b, c, x[i+ 0], 11, -358537222);\n    c = md5_hh(c, d, a, b, x[i+ 3], 16, -722521979);\n    b = md5_hh(b, c, d, a, x[i+ 6], 23,  76029189);\n    a = md5_hh(a, b, c, d, x[i+ 9], 4 , -640364487);\n    d = md5_hh(d, a, b, c, x[i+12], 11, -421815835);\n    c = md5_hh(c, d, a, b, x[i+15], 16,  530742520);\n    b = md5_hh(b, c, d, a, x[i+ 2], 23, -995338651);\n\n    a = md5_ii(a, b, c, d, x[i+ 0], 6 , -198630844);\n    d = md5_ii(d, a, b, c, x[i+ 7], 10,  1126891415);\n    c = md5_ii(c, d, a, b, x[i+14], 15, -1416354905);\n    b = md5_ii(b, c, d, a, x[i+ 5], 21, -57434055);\n    a = md5_ii(a, b, c, d, x[i+12], 6 ,  1700485571);\n    d = md5_ii(d, a, b, c, x[i+ 3], 10, -1894986606);\n    c = md5_ii(c, d, a, b, x[i+10], 15, -1051523);\n    b = md5_ii(b, c, d, a, x[i+ 1], 21, -2054922799);\n    a = md5_ii(a, b, c, d, x[i+ 8], 6 ,  1873313359);\n    d = md5_ii(d, a, b, c, x[i+15], 10, -30611744);\n    c = md5_ii(c, d, a, b, x[i+ 6], 15, -1560198380);\n    b = md5_ii(b, c, d, a, x[i+13], 21,  1309151649);\n    a = md5_ii(a, b, c, d, x[i+ 4], 6 , -145523070);\n    d = md5_ii(d, a, b, c, x[i+11], 10, -1120210379);\n    c = md5_ii(c, d, a, b, x[i+ 2], 15,  718787259);\n    b = md5_ii(b, c, d, a, x[i+ 9], 21, -343485551);\n\n    a = safe_add(a, olda);\n    b = safe_add(b, oldb);\n    c = safe_add(c, oldc);\n    d = safe_add(d, oldd);\n  }\n  return Array(a, b, c, d);\n\n}\n\n/*\n * These functions implement the four basic operations the algorithm uses.\n */\nfunction md5_cmn(q, a, b, x, s, t)\n{\n  return safe_add(bit_rol(safe_add(safe_add(a, q), safe_add(x, t)), s),b);\n}\nfunction md5_ff(a, b, c, d, x, s, t)\n{\n  return md5_cmn((b & c) | ((~b) & d), a, b, x, s, t);\n}\nfunction md5_gg(a, b, c, d, x, s, t)\n{\n  return md5_cmn((b & d) | (c & (~d)), a, b, x, s, t);\n}\nfunction md5_hh(a, b, c, d, x, s, t)\n{\n  return md5_cmn(b ^ c ^ d, a, b, x, s, t);\n}\nfunction md5_ii(a, b, c, d, x, s, t)\n{\n  return md5_cmn(c ^ (b | (~d)), a, b, x, s, t);\n}\n\n/*\n * Calculate the HMAC-MD5, of a key and some data\n */\nfunction core_hmac_md5(key, data)\n{\n  var bkey = str2binl(key);\n  if(bkey.length > 16) bkey = core_md5(bkey, key.length * chrsz);\n\n  var ipad = Array(16), opad = Array(16);\n  for(var i = 0; i < 16; i++)\n  {\n    ipad[i] = bkey[i] ^ 0x36363636;\n    opad[i] = bkey[i] ^ 0x5C5C5C5C;\n  }\n\n  var hash = core_md5(ipad.concat(str2binl(data)), 512 + data.length * chrsz);\n  return core_md5(opad.concat(hash), 512 + 128);\n}\n\n/*\n * Add integers, wrapping at 2^32. This uses 16-bit operations internally\n * to work around bugs in some JS interpreters.\n */\nfunction safe_add(x, y)\n{\n  var lsw = (x & 0xFFFF) + (y & 0xFFFF);\n  var msw = (x >> 16) + (y >> 16) + (lsw >> 16);\n  return (msw << 16) | (lsw & 0xFFFF);\n}\n\n/*\n * Bitwise rotate a 32-bit number to the left.\n */\nfunction bit_rol(num, cnt)\n{\n  return (num << cnt) | (num >>> (32 - cnt));\n}\n\n/*\n * Convert a string to an array of little-endian words\n * If chrsz is ASCII, characters >255 have their hi-byte silently ignored.\n */\nfunction str2binl(str)\n{\n  var bin = Array();\n  var mask = (1 << chrsz) - 1;\n  for(var i = 0; i < str.length * chrsz; i += chrsz)\n    bin[i>>5] |= (str.charCodeAt(i / chrsz) & mask) << (i%32);\n  return bin;\n}\n\n/*\n * Convert an array of little-endian words to a string\n */\nfunction binl2str(bin)\n{\n  var str = \"\";\n  var mask = (1 << chrsz) - 1;\n  for(var i = 0; i < bin.length * 32; i += chrsz)\n    str += String.fromCharCode((bin[i>>5] >>> (i % 32)) & mask);\n  return str;\n}\n\n/*\n * Convert an array of little-endian words to a hex string.\n */\nfunction binl2hex(binarray)\n{\n  var hex_tab = hexcase ? \"0123456789ABCDEF\" : \"0123456789abcdef\";\n  var str = \"\";\n  for(var i = 0; i < binarray.length * 4; i++)\n  {\n    str += hex_tab.charAt((binarray[i>>2] >> ((i%4)*8+4)) & 0xF) +\n           hex_tab.charAt((binarray[i>>2] >> ((i%4)*8  )) & 0xF);\n  }\n  return str;\n}\n\n/*\n * Convert an array of little-endian words to a base-64 string\n */\nfunction binl2b64(binarray)\n{\n  var tab = \"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/\";\n  var str = \"\";\n  for(var i = 0; i < binarray.length * 4; i += 3)\n  {\n    var triplet = (((binarray[i   >> 2] >> 8 * ( i   %4)) & 0xFF) << 16)\n                | (((binarray[i+1 >> 2] >> 8 * ((i+1)%4)) & 0xFF) << 8 )\n                |  ((binarray[i+2 >> 2] >> 8 * ((i+2)%4)) & 0xFF);\n    for(var j = 0; j < 4; j++)\n    {\n      if(i * 8 + j * 6 > binarray.length * 32) str += b64pad;\n      else str += tab.charAt((triplet >> 6*(3-j)) & 0x3F);\n    }\n  }\n  return str;\n}\n"
  },
  {
    "path": "app/src/main/assets/web/help/index.html",
    "content": "<!DOCTYPE html>\n<html>\n\t<head>\n\t\t<meta charset=\"utf-8\" />\n\t\t<title>help</title>\n\t\t<link rel=\"stylesheet\" href=\"css/github-markdown-light.min.css\" />\n\t\t<link rel=\"stylesheet\" href=\"css/highlight.min.css\" />\n\t\t<style>\n\t\t\tbody {\n\t\t\t\tmin-width: 200px;\n\t\t\t\tmax-width: 980px;\n\t\t\t\tmargin: 0 auto;\n\t\t\t\tpadding: 45px;\n\t\t\t}\n\t\t</style>\n\t\t<script src=\"js/require.js\" data-main=\"js/main\" defer async=\"true\"></script>\n\t</head>\n\t<body>\n\t\t<div id=\"mdviewer\" class=\"markdown-body\"></div>\n\t</body>\n</html>\n"
  },
  {
    "path": "app/src/main/assets/web/help/js/main.js",
    "content": "require.config({\n    baseUrl: 'js',\n    paths: {\n        marked: 'marked.min',\n        markedHighlight: 'marked-highlight.umd',\n        highlight: 'highlight.min',\n    },\n    shim: {\n        marked: {\n            exports: 'marked',\n        },\n        markedHighlight: {\n            exports: 'markedHighlight',\n        },\n        highlight: {\n            exports: 'hljs',\n        },\n    },\n});\n\nrequire(['marked', 'markedHighlight', 'highlight'], (marked, mdhl, hljs) => {\n    marked.use(\n        mdhl.markedHighlight({\n            langPrefix: 'theme-vs2015-min hljs language-',\n            highlight(code, lang) {\n                const language = hljs.getLanguage(lang) ? lang : 'txt';\n                const result = hljs.highlight(code, {language});\n                return result.value;\n            },\n        })\n    );\n\n    const path = '/help/md/';\n    const file = location.hash.slice(1).trim();\n    if (!file) return;\n    fetch(`${path}${file}.md`)\n        .then((response) => response.text())\n        .then((md_text) => {\n            document.getElementById('mdviewer').innerHTML = marked.parse(md_text);\n        });\n});\n"
  },
  {
    "path": "app/src/main/assets/web/help/js/marked-highlight.umd.js",
    "content": "(function(global,factory){typeof exports==='object'&&typeof module!=='undefined'?factory(exports):typeof define==='function'&&define.amd?define(['exports'],factory):(global=typeof globalThis!=='undefined'?globalThis:global||self,factory(global.markedHighlight={}))})(this,(function(exports){'use strict';function markedHighlight(options){if(typeof options==='function'){options={highlight:options}}if(!options||typeof options.highlight!=='function'){throw new Error('Must provide highlight function');}if(typeof options.langPrefix!=='string'){options.langPrefix='language-'}return{async:!!options.async,walkTokens(token){if(token.type!=='code'){return}const lang=getLang(token.lang);if(options.async){return Promise.resolve(options.highlight(token.text,lang,token.lang||'')).then(updateToken(token))}const code=options.highlight(token.text,lang,token.lang||'');if(code instanceof Promise){throw new Error('markedHighlight is not set to async but the highlight function is async. Set the async option to true on markedHighlight to await the async highlight function.');}updateToken(token)(code)},renderer:{code(code,infoString,escaped){const lang=getLang(infoString);const classAttr=`${options.langPrefix}${escape(lang||'txt')}`;code=code.replace(/\\n$/,'');return`\n<pre class=\"${classAttr}\"><span class=\"hljs\"><code>${escaped?code:escape(code,true)}\\n</code></span><small calss=\"lang-label\">${lang||'txt'}</small></pre>\n`}}}}function getLang(lang){return(lang||'').match(/\\S*/)[0]}function updateToken(token){return(code)=>{if(typeof code==='string'&&code!==token.text){token.escaped=true;token.text=code}}}const escapeTest=/[&<>\"']/;const escapeReplace=new RegExp(escapeTest.source,'g');const escapeTestNoEncode=/[<>\"']|&(?!(#\\d{1,7}|#[Xx][a-fA-F0-9]{1,6}|\\w+);)/;const escapeReplaceNoEncode=new RegExp(escapeTestNoEncode.source,'g');const escapeReplacements={'&':'&amp;','<':'&lt;','>':'&gt;','\"':'&quot;',\"'\":'&#39;'};const getEscapeReplacement=(ch)=>escapeReplacements[ch];function escape(html,encode){if(encode){if(escapeTest.test(html)){return html.replace(escapeReplace,getEscapeReplacement)}}else{if(escapeTestNoEncode.test(html)){return html.replace(escapeReplaceNoEncode,getEscapeReplacement)}}return html}exports.markedHighlight=markedHighlight}));"
  },
  {
    "path": "app/src/main/assets/web/help/js/require.js",
    "content": "/** vim: et:ts=4:sw=4:sts=4\n * @license RequireJS 2.3.6 Copyright jQuery Foundation and other contributors.\n * Released under MIT license, https://github.com/requirejs/requirejs/blob/master/LICENSE\n */\nvar requirejs,require,define;!function(global,setTimeout){var req,s,head,baseElement,dataMain,src,interactiveScript,currentlyAddingScript,mainScript,subPath,version=\"2.3.6\",commentRegExp=/\\/\\*[\\s\\S]*?\\*\\/|([^:\"'=]|^)\\/\\/.*$/gm,cjsRequireRegExp=/[^.]\\s*require\\s*\\(\\s*[\"']([^'\"\\s]+)[\"']\\s*\\)/g,jsSuffixRegExp=/\\.js$/,currDirRegExp=/^\\.\\//,op=Object.prototype,ostring=op.toString,hasOwn=op.hasOwnProperty,isBrowser=!(\"undefined\"==typeof window||\"undefined\"==typeof navigator||!window.document),isWebWorker=!isBrowser&&\"undefined\"!=typeof importScripts,readyRegExp=isBrowser&&\"PLAYSTATION 3\"===navigator.platform?/^complete$/:/^(complete|loaded)$/,defContextName=\"_\",isOpera=\"undefined\"!=typeof opera&&\"[object Opera]\"===opera.toString(),contexts={},cfg={},globalDefQueue=[],useInteractive=!1;function commentReplace(e,t){return t||\"\"}function isFunction(e){return\"[object Function]\"===ostring.call(e)}function isArray(e){return\"[object Array]\"===ostring.call(e)}function each(e,t){var i;if(e)for(i=0;i<e.length&&(!e[i]||!t(e[i],i,e));i+=1);}function eachReverse(e,t){var i;if(e)for(i=e.length-1;-1<i&&(!e[i]||!t(e[i],i,e));i-=1);}function hasProp(e,t){return hasOwn.call(e,t)}function getOwn(e,t){return hasProp(e,t)&&e[t]}function eachProp(e,t){var i;for(i in e)if(hasProp(e,i)&&t(e[i],i))break}function mixin(i,e,r,n){return e&&eachProp(e,function(e,t){!r&&hasProp(i,t)||(!n||\"object\"!=typeof e||!e||isArray(e)||isFunction(e)||e instanceof RegExp?i[t]=e:(i[t]||(i[t]={}),mixin(i[t],e,r,n)))}),i}function bind(e,t){return function(){return t.apply(e,arguments)}}function scripts(){return document.getElementsByTagName(\"script\")}function defaultOnError(e){throw e}function getGlobal(e){if(!e)return e;var t=global;return each(e.split(\".\"),function(e){t=t[e]}),t}function makeError(e,t,i,r){var n=new Error(t+\"\\nhttps://requirejs.org/docs/errors.html#\"+e);return n.requireType=e,n.requireModules=r,i&&(n.originalError=i),n}if(void 0===define){if(void 0!==requirejs){if(isFunction(requirejs))return;cfg=requirejs,requirejs=void 0}void 0===require||isFunction(require)||(cfg=require,require=void 0),req=requirejs=function(e,t,i,r){var n,o,a=defContextName;return isArray(e)||\"string\"==typeof e||(o=e,isArray(t)?(e=t,t=i,i=r):e=[]),o&&o.context&&(a=o.context),(n=getOwn(contexts,a))||(n=contexts[a]=req.s.newContext(a)),o&&n.configure(o),n.require(e,t,i)},req.config=function(e){return req(e)},req.nextTick=void 0!==setTimeout?function(e){setTimeout(e,4)}:function(e){e()},require||(require=req),req.version=version,req.jsExtRegExp=/^\\/|:|\\?|\\.js$/,req.isBrowser=isBrowser,s=req.s={contexts:contexts,newContext:newContext},req({}),each([\"toUrl\",\"undef\",\"defined\",\"specified\"],function(t){req[t]=function(){var e=contexts[defContextName];return e.require[t].apply(e,arguments)}}),isBrowser&&(head=s.head=document.getElementsByTagName(\"head\")[0],baseElement=document.getElementsByTagName(\"base\")[0],baseElement&&(head=s.head=baseElement.parentNode)),req.onError=defaultOnError,req.createNode=function(e,t,i){var r=e.xhtml?document.createElementNS(\"http://www.w3.org/1999/xhtml\",\"html:script\"):document.createElement(\"script\");return r.type=e.scriptType||\"text/javascript\",r.charset=\"utf-8\",r.async=!0,r},req.load=function(t,i,r){var e,n=t&&t.config||{};if(isBrowser)return(e=req.createNode(n,i,r)).setAttribute(\"data-requirecontext\",t.contextName),e.setAttribute(\"data-requiremodule\",i),!e.attachEvent||e.attachEvent.toString&&e.attachEvent.toString().indexOf(\"[native code\")<0||isOpera?(e.addEventListener(\"load\",t.onScriptLoad,!1),e.addEventListener(\"error\",t.onScriptError,!1)):(useInteractive=!0,e.attachEvent(\"onreadystatechange\",t.onScriptLoad)),e.src=r,n.onNodeCreated&&n.onNodeCreated(e,n,i,r),currentlyAddingScript=e,baseElement?head.insertBefore(e,baseElement):head.appendChild(e),currentlyAddingScript=null,e;if(isWebWorker)try{setTimeout(function(){},0),importScripts(r),t.completeLoad(i)}catch(e){t.onError(makeError(\"importscripts\",\"importScripts failed for \"+i+\" at \"+r,e,[i]))}},isBrowser&&!cfg.skipDataMain&&eachReverse(scripts(),function(e){if(head||(head=e.parentNode),dataMain=e.getAttribute(\"data-main\"))return mainScript=dataMain,cfg.baseUrl||-1!==mainScript.indexOf(\"!\")||(mainScript=(src=mainScript.split(\"/\")).pop(),subPath=src.length?src.join(\"/\")+\"/\":\"./\",cfg.baseUrl=subPath),mainScript=mainScript.replace(jsSuffixRegExp,\"\"),req.jsExtRegExp.test(mainScript)&&(mainScript=dataMain),cfg.deps=cfg.deps?cfg.deps.concat(mainScript):[mainScript],!0}),define=function(e,i,t){var r,n;\"string\"!=typeof e&&(t=i,i=e,e=null),isArray(i)||(t=i,i=null),!i&&isFunction(t)&&(i=[],t.length&&(t.toString().replace(commentRegExp,commentReplace).replace(cjsRequireRegExp,function(e,t){i.push(t)}),i=(1===t.length?[\"require\"]:[\"require\",\"exports\",\"module\"]).concat(i))),useInteractive&&(r=currentlyAddingScript||getInteractiveScript())&&(e||(e=r.getAttribute(\"data-requiremodule\")),n=contexts[r.getAttribute(\"data-requirecontext\")]),n?(n.defQueue.push([e,i,t]),n.defQueueMap[e]=!0):globalDefQueue.push([e,i,t])},define.amd={jQuery:!0},req.exec=function(text){return eval(text)},req(cfg)}function newContext(u){var i,e,l,c,d,g={waitSeconds:7,baseUrl:\"./\",paths:{},bundles:{},pkgs:{},shim:{},config:{}},p={},f={},r={},h=[],m={},n={},v={},x=1,b=1;function q(e,t,i){var r,n,o,a,s,u,c,d,p,f,l=t&&t.split(\"/\"),h=g.map,m=h&&h[\"*\"];if(e&&(u=(e=e.split(\"/\")).length-1,g.nodeIdCompat&&jsSuffixRegExp.test(e[u])&&(e[u]=e[u].replace(jsSuffixRegExp,\"\")),\".\"===e[0].charAt(0)&&l&&(e=l.slice(0,l.length-1).concat(e)),function(e){var t,i;for(t=0;t<e.length;t++)if(\".\"===(i=e[t]))e.splice(t,1),t-=1;else if(\"..\"===i){if(0===t||1===t&&\"..\"===e[2]||\"..\"===e[t-1])continue;0<t&&(e.splice(t-1,2),t-=2)}}(e),e=e.join(\"/\")),i&&h&&(l||m)){e:for(o=(n=e.split(\"/\")).length;0<o;o-=1){if(s=n.slice(0,o).join(\"/\"),l)for(a=l.length;0<a;a-=1)if((r=getOwn(h,l.slice(0,a).join(\"/\")))&&(r=getOwn(r,s))){c=r,d=o;break e}!p&&m&&getOwn(m,s)&&(p=getOwn(m,s),f=o)}!c&&p&&(c=p,d=f),c&&(n.splice(0,d,c),e=n.join(\"/\"))}return getOwn(g.pkgs,e)||e}function E(t){isBrowser&&each(scripts(),function(e){if(e.getAttribute(\"data-requiremodule\")===t&&e.getAttribute(\"data-requirecontext\")===l.contextName)return e.parentNode.removeChild(e),!0})}function w(e){var t=getOwn(g.paths,e);if(t&&isArray(t)&&1<t.length)return t.shift(),l.require.undef(e),l.makeRequire(null,{skipMap:!0})([e]),!0}function y(e){var t,i=e?e.indexOf(\"!\"):-1;return-1<i&&(t=e.substring(0,i),e=e.substring(i+1,e.length)),[t,e]}function S(e,t,i,r){var n,o,a,s,u=null,c=t?t.name:null,d=e,p=!0,f=\"\";return e||(p=!1,e=\"_@r\"+(x+=1)),u=(s=y(e))[0],e=s[1],u&&(u=q(u,c,r),o=getOwn(m,u)),e&&(u?f=i?e:o&&o.normalize?o.normalize(e,function(e){return q(e,c,r)}):-1===e.indexOf(\"!\")?q(e,c,r):e:(u=(s=y(f=q(e,c,r)))[0],f=s[1],i=!0,n=l.nameToUrl(f))),{prefix:u,name:f,parentMap:t,unnormalized:!!(a=!u||o||i?\"\":\"_unnormalized\"+(b+=1)),url:n,originalName:d,isDefine:p,id:(u?u+\"!\"+f:f)+a}}function k(e){var t=e.id,i=getOwn(p,t);return i||(i=p[t]=new l.Module(e)),i}function M(e,t,i){var r=e.id,n=getOwn(p,r);!hasProp(m,r)||n&&!n.defineEmitComplete?(n=k(e)).error&&\"error\"===t?i(n.error):n.on(t,i):\"defined\"===t&&i(m[r])}function O(i,e){var t=i.requireModules,r=!1;e?e(i):(each(t,function(e){var t=getOwn(p,e);t&&(t.error=i,t.events.error&&(r=!0,t.emit(\"error\",i)))}),r||req.onError(i))}function j(){globalDefQueue.length&&(each(globalDefQueue,function(e){var t=e[0];\"string\"==typeof t&&(l.defQueueMap[t]=!0),h.push(e)}),globalDefQueue=[])}function P(e){delete p[e],delete f[e]}function R(){var e,r,t=1e3*g.waitSeconds,n=t&&l.startTime+t<(new Date).getTime(),o=[],a=[],s=!1,u=!0;if(!i){if(i=!0,eachProp(f,function(e){var t=e.map,i=t.id;if(e.enabled&&(t.isDefine||a.push(e),!e.error))if(!e.inited&&n)w(i)?s=r=!0:(o.push(i),E(i));else if(!e.inited&&e.fetched&&t.isDefine&&(s=!0,!t.prefix))return u=!1}),n&&o.length)return(e=makeError(\"timeout\",\"Load timeout for modules: \"+o,null,o)).contextName=l.contextName,O(e);u&&each(a,function(e){!function n(o,a,s){var e=o.map.id;o.error?o.emit(\"error\",o.error):(a[e]=!0,each(o.depMaps,function(e,t){var i=e.id,r=getOwn(p,i);!r||o.depMatched[t]||s[i]||(getOwn(a,i)?(o.defineDep(t,m[i]),o.check()):n(r,a,s))}),s[e]=!0)}(e,{},{})}),n&&!r||!s||!isBrowser&&!isWebWorker||d||(d=setTimeout(function(){d=0,R()},50)),i=!1}}function a(e){hasProp(m,e[0])||k(S(e[0],null,!0)).init(e[1],e[2])}function o(e,t,i,r){e.detachEvent&&!isOpera?r&&e.detachEvent(r,t):e.removeEventListener(i,t,!1)}function s(e){var t=e.currentTarget||e.srcElement;return o(t,l.onScriptLoad,\"load\",\"onreadystatechange\"),o(t,l.onScriptError,\"error\"),{node:t,id:t&&t.getAttribute(\"data-requiremodule\")}}function T(){var e;for(j();h.length;){if(null===(e=h.shift())[0])return O(makeError(\"mismatch\",\"Mismatched anonymous define() module: \"+e[e.length-1]));a(e)}l.defQueueMap={}}return c={require:function(e){return e.require?e.require:e.require=l.makeRequire(e.map)},exports:function(e){if(e.usingExports=!0,e.map.isDefine)return e.exports?m[e.map.id]=e.exports:e.exports=m[e.map.id]={}},module:function(e){return e.module?e.module:e.module={id:e.map.id,uri:e.map.url,config:function(){return getOwn(g.config,e.map.id)||{}},exports:e.exports||(e.exports={})}}},(e=function(e){this.events=getOwn(r,e.id)||{},this.map=e,this.shim=getOwn(g.shim,e.id),this.depExports=[],this.depMaps=[],this.depMatched=[],this.pluginMaps={},this.depCount=0}).prototype={init:function(e,t,i,r){r=r||{},this.inited||(this.factory=t,i?this.on(\"error\",i):this.events.error&&(i=bind(this,function(e){this.emit(\"error\",e)})),this.depMaps=e&&e.slice(0),this.errback=i,this.inited=!0,this.ignore=r.ignore,r.enabled||this.enabled?this.enable():this.check())},defineDep:function(e,t){this.depMatched[e]||(this.depMatched[e]=!0,this.depCount-=1,this.depExports[e]=t)},fetch:function(){if(!this.fetched){this.fetched=!0,l.startTime=(new Date).getTime();var e=this.map;if(!this.shim)return e.prefix?this.callPlugin():this.load();l.makeRequire(this.map,{enableBuildCallback:!0})(this.shim.deps||[],bind(this,function(){return e.prefix?this.callPlugin():this.load()}))}},load:function(){var e=this.map.url;n[e]||(n[e]=!0,l.load(this.map.id,e))},check:function(){if(this.enabled&&!this.enabling){var t,e,i=this.map.id,r=this.depExports,n=this.exports,o=this.factory;if(this.inited){if(this.error)this.emit(\"error\",this.error);else if(!this.defining){if(this.defining=!0,this.depCount<1&&!this.defined){if(isFunction(o)){if(this.events.error&&this.map.isDefine||req.onError!==defaultOnError)try{n=l.execCb(i,o,r,n)}catch(e){t=e}else n=l.execCb(i,o,r,n);if(this.map.isDefine&&void 0===n&&((e=this.module)?n=e.exports:this.usingExports&&(n=this.exports)),t)return t.requireMap=this.map,t.requireModules=this.map.isDefine?[this.map.id]:null,t.requireType=this.map.isDefine?\"define\":\"require\",O(this.error=t)}else n=o;if(this.exports=n,this.map.isDefine&&!this.ignore&&(m[i]=n,req.onResourceLoad)){var a=[];each(this.depMaps,function(e){a.push(e.normalizedMap||e)}),req.onResourceLoad(l,this.map,a)}P(i),this.defined=!0}this.defining=!1,this.defined&&!this.defineEmitted&&(this.defineEmitted=!0,this.emit(\"defined\",this.exports),this.defineEmitComplete=!0)}}else hasProp(l.defQueueMap,i)||this.fetch()}},callPlugin:function(){var u=this.map,c=u.id,e=S(u.prefix);this.depMaps.push(e),M(e,\"defined\",bind(this,function(e){var o,t,i,r=getOwn(v,this.map.id),n=this.map.name,a=this.map.parentMap?this.map.parentMap.name:null,s=l.makeRequire(u.parentMap,{enableBuildCallback:!0});return this.map.unnormalized?(e.normalize&&(n=e.normalize(n,function(e){return q(e,a,!0)})||\"\"),M(t=S(u.prefix+\"!\"+n,this.map.parentMap,!0),\"defined\",bind(this,function(e){this.map.normalizedMap=t,this.init([],function(){return e},null,{enabled:!0,ignore:!0})})),void((i=getOwn(p,t.id))&&(this.depMaps.push(t),this.events.error&&i.on(\"error\",bind(this,function(e){this.emit(\"error\",e)})),i.enable()))):r?(this.map.url=l.nameToUrl(r),void this.load()):((o=bind(this,function(e){this.init([],function(){return e},null,{enabled:!0})})).error=bind(this,function(e){this.inited=!0,(this.error=e).requireModules=[c],eachProp(p,function(e){0===e.map.id.indexOf(c+\"_unnormalized\")&&P(e.map.id)}),O(e)}),o.fromText=bind(this,function(e,t){var i=u.name,r=S(i),n=useInteractive;t&&(e=t),n&&(useInteractive=!1),k(r),hasProp(g.config,c)&&(g.config[i]=g.config[c]);try{req.exec(e)}catch(e){return O(makeError(\"fromtexteval\",\"fromText eval for \"+c+\" failed: \"+e,e,[c]))}n&&(useInteractive=!0),this.depMaps.push(r),l.completeLoad(i),s([i],o)}),void e.load(u.name,s,o,g))})),l.enable(e,this),this.pluginMaps[e.id]=e},enable:function(){(f[this.map.id]=this).enabled=!0,this.enabling=!0,each(this.depMaps,bind(this,function(e,t){var i,r,n;if(\"string\"==typeof e){if(e=S(e,this.map.isDefine?this.map:this.map.parentMap,!1,!this.skipMap),this.depMaps[t]=e,n=getOwn(c,e.id))return void(this.depExports[t]=n(this));this.depCount+=1,M(e,\"defined\",bind(this,function(e){this.undefed||(this.defineDep(t,e),this.check())})),this.errback?M(e,\"error\",bind(this,this.errback)):this.events.error&&M(e,\"error\",bind(this,function(e){this.emit(\"error\",e)}))}i=e.id,r=p[i],hasProp(c,i)||!r||r.enabled||l.enable(e,this)})),eachProp(this.pluginMaps,bind(this,function(e){var t=getOwn(p,e.id);t&&!t.enabled&&l.enable(e,this)})),this.enabling=!1,this.check()},on:function(e,t){var i=this.events[e];i||(i=this.events[e]=[]),i.push(t)},emit:function(e,t){each(this.events[e],function(e){e(t)}),\"error\"===e&&delete this.events[e]}},(l={config:g,contextName:u,registry:p,defined:m,urlFetched:n,defQueue:h,defQueueMap:{},Module:e,makeModuleMap:S,nextTick:req.nextTick,onError:O,configure:function(e){if(e.baseUrl&&\"/\"!==e.baseUrl.charAt(e.baseUrl.length-1)&&(e.baseUrl+=\"/\"),\"string\"==typeof e.urlArgs){var i=e.urlArgs;e.urlArgs=function(e,t){return(-1===t.indexOf(\"?\")?\"?\":\"&\")+i}}var r=g.shim,n={paths:!0,bundles:!0,config:!0,map:!0};eachProp(e,function(e,t){n[t]?(g[t]||(g[t]={}),mixin(g[t],e,!0,!0)):g[t]=e}),e.bundles&&eachProp(e.bundles,function(e,t){each(e,function(e){e!==t&&(v[e]=t)})}),e.shim&&(eachProp(e.shim,function(e,t){isArray(e)&&(e={deps:e}),!e.exports&&!e.init||e.exportsFn||(e.exportsFn=l.makeShimExports(e)),r[t]=e}),g.shim=r),e.packages&&each(e.packages,function(e){var t;t=(e=\"string\"==typeof e?{name:e}:e).name,e.location&&(g.paths[t]=e.location),g.pkgs[t]=e.name+\"/\"+(e.main||\"main\").replace(currDirRegExp,\"\").replace(jsSuffixRegExp,\"\")}),eachProp(p,function(e,t){e.inited||e.map.unnormalized||(e.map=S(t,null,!0))}),(e.deps||e.callback)&&l.require(e.deps||[],e.callback)},makeShimExports:function(t){return function(){var e;return t.init&&(e=t.init.apply(global,arguments)),e||t.exports&&getGlobal(t.exports)}},makeRequire:function(o,a){function s(e,t,i){var r,n;return a.enableBuildCallback&&t&&isFunction(t)&&(t.__requireJsBuild=!0),\"string\"==typeof e?isFunction(t)?O(makeError(\"requireargs\",\"Invalid require call\"),i):o&&hasProp(c,e)?c[e](p[o.id]):req.get?req.get(l,e,o,s):(r=S(e,o,!1,!0).id,hasProp(m,r)?m[r]:O(makeError(\"notloaded\",'Module name \"'+r+'\" has not been loaded yet for context: '+u+(o?\"\":\". Use require([])\")))):(T(),l.nextTick(function(){T(),(n=k(S(null,o))).skipMap=a.skipMap,n.init(e,t,i,{enabled:!0}),R()}),s)}return a=a||{},mixin(s,{isBrowser:isBrowser,toUrl:function(e){var t,i=e.lastIndexOf(\".\"),r=e.split(\"/\")[0];return-1!==i&&(!(\".\"===r||\"..\"===r)||1<i)&&(t=e.substring(i,e.length),e=e.substring(0,i)),l.nameToUrl(q(e,o&&o.id,!0),t,!0)},defined:function(e){return hasProp(m,S(e,o,!1,!0).id)},specified:function(e){return e=S(e,o,!1,!0).id,hasProp(m,e)||hasProp(p,e)}}),o||(s.undef=function(i){j();var e=S(i,o,!0),t=getOwn(p,i);t.undefed=!0,E(i),delete m[i],delete n[e.url],delete r[i],eachReverse(h,function(e,t){e[0]===i&&h.splice(t,1)}),delete l.defQueueMap[i],t&&(t.events.defined&&(r[i]=t.events),P(i))}),s},enable:function(e){getOwn(p,e.id)&&k(e).enable()},completeLoad:function(e){var t,i,r,n=getOwn(g.shim,e)||{},o=n.exports;for(j();h.length;){if(null===(i=h.shift())[0]){if(i[0]=e,t)break;t=!0}else i[0]===e&&(t=!0);a(i)}if(l.defQueueMap={},r=getOwn(p,e),!t&&!hasProp(m,e)&&r&&!r.inited){if(!(!g.enforceDefine||o&&getGlobal(o)))return w(e)?void 0:O(makeError(\"nodefine\",\"No define call for \"+e,null,[e]));a([e,n.deps||[],n.exportsFn])}R()},nameToUrl:function(e,t,i){var r,n,o,a,s,u,c=getOwn(g.pkgs,e);if(c&&(e=c),u=getOwn(v,e))return l.nameToUrl(u,t,i);if(req.jsExtRegExp.test(e))a=e+(t||\"\");else{for(r=g.paths,o=(n=e.split(\"/\")).length;0<o;o-=1)if(s=getOwn(r,n.slice(0,o).join(\"/\"))){isArray(s)&&(s=s[0]),n.splice(0,o,s);break}a=n.join(\"/\"),a=(\"/\"===(a+=t||(/^data\\:|^blob\\:|\\?/.test(a)||i?\"\":\".js\")).charAt(0)||a.match(/^[\\w\\+\\.\\-]+:/)?\"\":g.baseUrl)+a}return g.urlArgs&&!/^blob\\:/.test(a)?a+g.urlArgs(e,a):a},load:function(e,t){req.load(l,e,t)},execCb:function(e,t,i,r){return t.apply(r,i)},onScriptLoad:function(e){if(\"load\"===e.type||readyRegExp.test((e.currentTarget||e.srcElement).readyState)){interactiveScript=null;var t=s(e);l.completeLoad(t.id)}},onScriptError:function(e){var i=s(e);if(!w(i.id)){var r=[];return eachProp(p,function(e,t){0!==t.indexOf(\"_@r\")&&each(e.depMaps,function(e){if(e.id===i.id)return r.push(t),!0})}),O(makeError(\"scripterror\",'Script error for \"'+i.id+(r.length?'\", needed by: '+r.join(\", \"):'\"'),e,[i.id]))}}}).require=l.makeRequire(),l}function getInteractiveScript(){return interactiveScript&&\"interactive\"===interactiveScript.readyState||eachReverse(scripts(),function(e){if(\"interactive\"===e.readyState)return interactiveScript=e}),interactiveScript}}(this,\"undefined\"==typeof setTimeout?void 0:setTimeout);"
  },
  {
    "path": "app/src/main/assets/web/help/md/ExtensionContentType.md",
    "content": "```java\npublic enum MimeTypeEnum {\n\n    AAC(\"acc\", \"AAC音频\", \"audio/aac\"),\n\n    ABW(\"abw\", \"AbiWord文件\", \"application/x-abiword\"),\n\n    ARC(\"arc\", \"存档文件\", \"application/x-freearc\"),\n\n    AVI(\"avi\", \"音频视频交错格式\", \"video/x-msvideo\"),\n\n    AZW(\"azw\", \"亚马逊Kindle电子书格式\", \"application/vnd.amazon.ebook\"),\n\n    BIN(\"bin\", \"任何类型的二进制数据\", \"application/octet-stream\"),\n\n    BMP(\"bmp\", \"Windows OS / 2位图图形\", \"image/bmp\"),\n\n    BZ(\"bz\", \"BZip存档\", \"application/x-bzip\"),\n\n    BZ2(\"bz2\", \"BZip2存档\", \"application/x-bzip2\"),\n\n    CSH(\"csh\", \"C-Shell脚本\", \"application/x-csh\"),\n\n    CSS(\"css\", \"级联样式表（CSS）\", \"text/css\"),\n\n    CSV(\"csv\", \"逗号分隔值（CSV）\", \"text/csv\"),\n\n    DOC(\"doc\", \"微软Word文件\", \"application/msword\"),\n\n    DOCX(\"docx\", \"Microsoft Word（OpenXML）\", \"application/vnd.openxmlformats-officedocument.wordprocessingml.document\"),\n\n    EOT(\"eot\", \"MS Embedded OpenType字体\", \"application/vnd.ms-fontobject\"),\n\n    EPUB(\"epub\", \"电子出版物（EPUB）\", \"application/epub+zip\"),\n\n    GZ(\"gz\", \"GZip压缩档案\", \"application/gzip\"),\n\n    GIF(\"gif\", \"图形交换格式（GIF）\", \"image/gif\"),\n\n    HTM(\"htm\", \"超文本标记语言（HTML）\", \"text/html\"),\n\n    HTML(\"html\", \"超文本标记语言（HTML）\", \"text/html\"),\n\n    ICO(\"ico\", \"图标格式\", \"image/vnd.microsoft.icon\"),\n\n    ICS(\"ics\", \"iCalendar格式\", \"text/calendar\"),\n\n    JAR(\"jar\", \"Java存档\", \"application/java-archive\"),\n\n    JPEG(\"jpeg\", \"JPEG图像\", \"image/jpeg\"),\n\n    JPG(\"jpg\", \"JPEG图像\", \"image/jpeg\"),\n\n    JS(\"js\", \"JavaScript\", \"text/javascript\"),\n\n    JSON(\"json\", \"JSON格式\", \"application/json\"),\n\n    JSONLD(\"jsonld\", \"JSON-LD格式\", \"application/ld+json\"),\n\n    MID(\"mid\", \"乐器数字接口（MIDI）\", \"audio/midi\"),\n\n    MIDI(\"midi\", \"乐器数字接口（MIDI）\", \"audio/midi\"),\n\n    MJS(\"mjs\", \"JavaScript模块\", \"text/javascript\"),\n\n    MP3(\"mp3\", \"MP3音频\", \"audio/mpeg\"),\n\n    MPEG(\"mpeg\", \"MPEG视频\", \"video/mpeg\"),\n\n    MPKG(\"mpkg\", \"苹果安装程序包\", \"application/vnd.apple.installer+xml\"),\n\n    ODP(\"odp\", \"OpenDocument演示文稿文档\", \"application/vnd.oasis.opendocument.presentation\"),\n\n    ODS(\"ods\", \"OpenDocument电子表格文档\", \"application/vnd.oasis.opendocument.spreadsheet\"),\n\n    ODT(\"odt\", \"OpenDocument文字文件\", \"application/vnd.oasis.opendocument.text\"),\n\n    OGA(\"oga\", \"OGG音讯\", \"audio/ogg\"),\n\n    OGV(\"ogv\", \"OGG视频\", \"video/ogg\"),\n\n    OGX(\"ogx\", \"OGG\", \"application/ogg\"),\n\n    OPUS(\"opus\", \"OPUS音频\", \"audio/opus\"),\n\n    OTF(\"otf\", \"otf字体\", \"font/otf\"),\n\n    PNG(\"png\", \"便携式网络图形\", \"image/png\"),\n\n    PDF(\"pdf\", \"Adobe 可移植文档格式（PDF）\", \"application/pdf\"),\n\n    PHP(\"php\", \"php\", \"application/x-httpd-php\"),\n\n    PPT(\"ppt\", \"Microsoft PowerPoint\", \"application/vnd.ms-powerpoint\"),\n\n    PPTX(\"pptx\", \"Microsoft PowerPoint（OpenXML）\", \"application/vnd.openxmlformats-officedocument.presentationml.presentation\"),\n\n    RAR(\"rar\", \"RAR档案\", \"application/vnd.rar\"),\n\n    RTF(\"rtf\", \"富文本格式\", \"application/rtf\"),\n\n    SH(\"sh\", \"Bourne Shell脚本\", \"application/x-sh\"),\n\n    SVG(\"svg\", \"可缩放矢量图形（SVG）\", \"image/svg+xml\"),\n\n    SWF(\"swf\", \"小型Web格式（SWF）或Adobe Flash文档\", \"application/x-shockwave-flash\"),\n\n    TAR(\"tar\", \"磁带存档（TAR）\", \"application/x-tar\"),\n\n    TIF(\"tif\", \"标记图像文件格式（TIFF）\", \"image/tiff\"),\n\n    TIFF(\"tiff\", \"标记图像文件格式（TIFF）\", \"image/tiff\"),\n\n    TS(\"ts\", \"MPEG传输流\", \"video/mp2t\"),\n\n    TTF(\"ttf\", \"ttf字体\", \"font/ttf\"),\n\n    TXT(\"txt\", \"文本（通常为ASCII或ISO 8859- n\", \"text/plain\"),\n\n    VSD(\"vsd\", \"微软Visio\", \"application/vnd.visio\"),\n\n    WAV(\"wav\", \"波形音频格式\", \"audio/wav\"),\n\n    WEBA(\"weba\", \"WEBM音频\", \"audio/webm\"),\n\n    WEBM(\"webm\", \"WEBM视频\", \"video/webm\"),\n\n    WEBP(\"webp\", \"WEBP图像\", \"image/webp\"),\n\n    WOFF(\"woff\", \"Web开放字体格式（WOFF）\", \"font/woff\"),\n\n    WOFF2(\"woff2\", \"Web开放字体格式（WOFF）\", \"font/woff2\"),\n\n    XHTML(\"xhtml\", \"XHTML\", \"application/xhtml+xml\"),\n\n    XLS(\"xls\", \"微软Excel\", \"application/vnd.ms-excel\"),\n\n    XLSX(\"xlsx\", \"微软Excel（OpenXML）\", \"application/vnd.openxmlformats-officedocument.spreadsheetml.sheet\"),\n\n    XML(\"xml\", \"XML\", \"application/xml\"),\n\n    XUL(\"xul\", \"XUL\", \"application/vnd.mozilla.xul+xml\"),\n\n    ZIP(\"zip\", \"ZIP\", \"application/zip\"),\n\n    MIME_3GP(\"3gp\", \"3GPP audio/video container\", \"video/3gpp\"),\n\n    MIME_3GP_WITHOUT_VIDEO(\"3gp\", \"3GPP audio/video container doesn't contain video\", \"audio/3gpp2\"),\n\n    MIME_3G2(\"3g2\", \"3GPP2 audio/video container\", \"video/3gpp2\"),\n\n    MIME_3G2_WITHOUT_VIDEO(\"3g2\", \"3GPP2 audio/video container  doesn't contain video\", \"audio/3gpp2\"),\n\n    MIME_7Z(\"7z\", \"7-zip存档\", \"application/x-7z-compressed\")\n}\n```"
  },
  {
    "path": "app/src/main/assets/web/help/md/SourceMBookHelp.md",
    "content": "# 书源管理界面帮助\n\n* 书源右上角标志\n  * 绿点表示书源有发现,且启用了发现\n  * 红点表示书源有发现,但是未启用\n  * 没有标志表示此书源没有发现\n* 右上角有分组菜单,可以按分组筛选书源\n* 右上角更多菜单里包含\n  * 新建书源\n  * 本地导入\n  * 网络导入\n  * 二维码导入\n  * 分享选中源\n* 选择源的更多操作在右下角的菜单里面,操作都是针对选择的书源\n  * 启用所选\n  * 禁用所选\n  * 添加分组\n  * 移除分组\n  * 启用发现\n  * 禁用发现\n  * 置顶所选\n  * 置底所选\n  * 导出所选\n  * 校验所选\n* 校验书源可批量校验书源，由于网络等原因结果仅限参考\n  * \"校验成功\"是指所选的校验项目全部通过\n  * 可正常识别搜索为空、发现为空、搜索(发现)目录为空、搜索(发现)正文为空、校验超时、js执行错误导致的失效，其余的原因视为网站失效\n  * 校验搜索优先使用书源填写的校验关键词，不存在时使用用户输入的关键词\n  * 校验结束后会自动筛选\"失效\"书源"
  },
  {
    "path": "app/src/main/assets/web/help/md/SourceMRssHelp.md",
    "content": "# 订阅源管理界面帮助\n\n* 订阅源可以通过规则订阅一些网络内容\n* 书源右上角标志\n  * 绿点表示书源有发现,且启用了发现\n  * 红点表示书源有发现,但是未启用\n  * 没有标志表示此书源没有发现\n* 右上角有分组菜单,可以按分组筛选书源\n* 右上角更多菜单里包含\n  * 新建订阅源\n  * 本地导入\n  * 网络导入\n  * 二维码导入\n  * 分享选中源\n* 选择源的更多操作在右下角的菜单里面,操作都是针对选择的书源\n  * 启用所选\n  * 禁用所选\n  * 置顶所选\n  * 置底所选\n  * 导出所选"
  },
  {
    "path": "app/src/main/assets/web/help/md/appHelp.md",
    "content": "# 帮助文档\n\n【温馨提醒】 *本帮助可以在“**我的**”——右上角帮助按钮再次打开，更新前一定要做好备份，以免数据丢失！*\n\n## 新人必读 \n\n### 1. 为什么第一次安装好之后什么东西都没有？\n阅读只是一个转码工具，不提供内容。\n\n### 2. 正文出现缺字漏字、内容缺失或排版错乱等情况，如何处理？\n有可能是净化规则出现问题，先关闭替换净化并刷新，再观察是否正常。如果正常说明净化规则存在误杀，如果关闭后仍然出现相关问题，请点击源链接查看原文与正文是否相同，如果不同，再进行反馈。\n\n### 3. 漫画源看书显示乱码，如何解决？\n【异次元】和【阅读】是两个不同的软件，**两个软件的源并不通用**，请导入【阅读】的支持的漫画源！\n\n## 书源相关\n\n### 1. 如何导入本地书源文件？\n以导入 QQ 接收到的书源文件为例：\n* 下载群文件里的书源文件；\n* 打开【阅读】软件；\n* 点击“**我的**”——“**书源管理**”；\n* 点击右上角选择“**本地导入**”；\n* 左下角选择书源文件所在的路径；\n* 点击书源文件导入；\n* 导入后返回书源管理界面；\n\n**【注】** \n1. *新版 QQ 文件下载路径：`Android/data/com.tencent.mobileqq/Tencent/QQfile_recv/`。*\n2. *书源格式后缀有 .txt 和 .json，其中 .json 文件在某些情况下可能无法导入，需要修改后缀为 .txt 才可导入。*\n\n![QQ 导入书源](https://cdn.jsdelivr.net/gh/gedoor/gedoor.github.io@master/images/importSource.jpg)\n\n### 2. 如何新建大佬发的单独书源？\n* 复制书源代码；\n* 打开阅读软件；\n* 点击“**我的**”——“**书源管理**”；\n* 右上角“**⁝**”——“**+ 新建书源**”；\n* 进入后点击右上角“**⁝**”——“**粘贴源**”；\n* 粘贴完成后点击上方保存“**🖫**”按钮；\n* 本次新建单独书源操作完成。\n\n**【注】** *如果书源有错误或者复制不全会显示格式错误，请重新复制。*\n\n### 3. 为什么导入 2.0 书源后无法阅读？\n部分 2.0 书源并不适用于 3.0 版本的阅读，建议导入后进行筛选。\n\n### 4. 【阅读】2.0 数据如何导入【阅读】3.0？\n先对【阅读】2.0 的数据进行备份，然后进入【阅读】3.0，点击“**我的**”，选择“**备份与恢复**”，再点击“**导入旧版本数据**”。\n\n### 5. 如何给朋友分享我的书源？\n* 打开【阅读】软件；\n* 点击备份；\n* 打开手机系统自带的文件管理；\n* 在手机内置存储根目录找到 `YueDu3.0` 文件夹；\n* 找到 `myBookSource.json`，长按选择分享；\n* 选择微信分享或者 QQ 分享；\n* 选择你要分享的好友点击发送；\n* 好友接收后在手机内置存储根目录找到 `myBookSource.json` 文件；\n* 复制该文件到手机内置存储根目录找到 `YueDu3.0` 文件夹（如已有该文件请先删除该文件或者备份到其他地方再复制到文件夹）；\n* 打开【阅读】软件点击恢复。\n\n**【注】**\n1. *备份路径如已修改过请在修改后的路径下查找书源文件。*\n2. *Android 10 及以下版本系统，新版 QQ 文件接收路径在 `Android/data/com.tencent.mobileqq/Tencent/QQfile_recv/`，旧版 QQ 文件接收路径则在 `Tencent/QQfile_recv/`；新版微信文件接收路径在 `Android/data/com.tencent.mobileqq/Tencent/MicroMsg/Download`，旧版微信文件接收路径则在 `Tencent/MicroMsg/Download`。*\n3. *Android 11 及以上系统版本用户，由于系统限制，无法访问 `Android/data` 目录。*\n\n### 6. 效验书源显示失效就说明书源不能用了吗？\n效验书源只是测试书源，可以做为参考，但失效了不代表书源不能用了。\n\n### 7. “发现”和正版书源能不能使用？\n发现和正版书源只能用来找书或看排行榜，不能用来看书，如需看书请切换书源。\n\n### 8. 为什么书源这么多，“发现”里却只有一点点？\n书源想要在发现界面里显示需要在书源里添加发现规则，并不是所有书源都有发现规则。\n\n## 本地/WebDav远程书籍相关\n\n### 1. 目前阅读支持哪些格式的本地书籍？\n目前支持 TXT 和 EPUB 格式。\n\n### 2. 如何导入本地/WebDav远程书籍？\n本地：在书架页面点击右上角“**⁝**”，选择“**添加本地**”，授予相关权限后即可导入本地书籍。也可在文件管理器中使用【阅读】打开相关书籍。\n\n远程：在主页面点击右上角 “**⁝**”，选择 **WebDav书籍**，正确配置好后即可看到上传的远程书籍，点击 **加入书架** 按钮导入即可。\n\n### 3. 如何上传本地书籍到 WebDav 远程？\n长按本地书籍，进入书籍详情页，点击右上角 “**⁝**”，选择 **上传WebDav**，等待几秒后即可上传到远程。\n\n或进入书籍缓存页面，点击右上角 “**⁝**”，选择 **导出到 WebDav**，在书籍导出时便可同时上传到远程。\n\n### 4. 导入 TXT 文件提示“LoadTocError”或“List is empty”是怎么回事？\n* 请先去应用详情中确认是否授予了【阅读】“读写手机存储”的权限。\n* 自动识别目录失败，可能是相关目录规则未开启，请点击右上角的换源按钮手动更换目录规则。\n\n如果尝试所有规则均无法识别，请在 GitHub 上提交 Issue 并附上相关 TXT 文件，也可以发送邮件至 i@qnmlgb.trade（标题：legado 本地文件章节无法识别；内容对其具体情况进行简要说明，附件上传相关 TXT 文件）。\n\n### 5. 如何下载书籍到本地？\n把在线书籍加入到书架后，在书架页面点击右上角，选择“**离线缓存**”即可。\n\n### 6. 如何自定义导出的 TXT 或 EPUB 文件名称？\n* 点击“**离线缓存**“——”**导出文件名**“\n* 使用方法:\n  - 导出文件名支持 js 语法\n  - 可用变量: name（书名）和 author（作者）\n  - 示例:\n  > name + \"作者:\" + author\n  - 导出文件名:\n  >  Legado 是最好的在线阅读软件 作者: kunfei\n\n**【注】** *name、author 等变量与字符串的拼接都需要在 JSON 上下文环境中进行，即必须使用 `{}` 将变量与字符串包裹起来。*\n\n### 7. 为什么我打开本地的 TXT 文件，显示内容却是乱码？\n部分编码在阅读上会识别错误，建议先用文本编辑器转换为常用的 UTF-8 格式。\n\n### 8. 阅读对部分把正文（如所有含引号的句子）识别成标题，如何解决？\n点击右上角更换目录规则即可。\n\n## 书籍界面相关\n\n### 1. 如何刷新书架？\n在书架界面下拉即可刷新。\n\n### 2. 书架界面书籍右上角的红色或者灰色背景小数字代表什么？\n红色代表书籍有更新，灰色代表无更新，数字代表未读章节。\n\n### 3. 如何查看书籍详情？\n长按书籍即可查看。\n\n### 4. 如何对书架上的书进行删除、切换书架的操作？\n书籍详情页操作即可。\n\n### 5. 如何禁止或允许某本书更新？\n书籍详情页，点击右上角——“**允许更新**”。\n\n### 6. 如何更换小说封面、名字、作者或简介？\n书籍详情页，点击右上角修改按钮。\n\n### 7. 怎么使用自定义字体？\n阅读界面——“**字体**”——点击右上角选择字体文件路径。\n\n### 8. 目前支持哪些格式的字体文件？\n目前支持 TTF 和 OTF 格式。\n\n### 9. 书籍经常“正在加载中”怎么办？\n在线书籍出现这个问题通常是由于源质量不好或不兼容引起的，可以换其它源多试试；本地书籍出现这个问题大概率是目录规则问题，手动切换规则可以解决。\n\n### 10. 书籍内容只有标题，正文内容是路径怎么办？\n通常是缓存路径引起的，更换缓存路径即可。\n\n### 11. 看书时如遇到“目录为空”、“加载失败”或长串英文等情况怎么办？\n在线书籍一般是书源问题，切换或更新书源即可。本地书籍请尝试手动更换目录规则。\n\n### 12. 为什么每一章的最后一页，阅读的文字和横线背景总是对不齐？\n请在“**设置**”——“**文字底部对齐**”选项中关闭底部对齐，再调整排版。\n\n### 13. 漫画源或图片章节只能看到第一页，如何解决？\n请先查看原网页是否正常，若正常，请在书籍阅读界面点击右上角的“**⁝**”按钮，在弹出的菜单中，选择“**翻页动画(本书)**”，将翻页动画更改为“**滚动**”。\n\n### 14. 阅读图片章节、漫画或 EPUB 插图时，图片被缩放到一页中，以至无法看清，如何处理？\n* 临时处理方案：长按图片可以进行双指缩放。图片章节请先参考 Q13 中的方案将翻页动画更改为“**滚动**”。\n* 3.0 旧版可以点击书籍界面的章节标题进入“**编辑书源**”界面，在“**正文**”——“**图片样式**”中填入 *`full`*，保存更改，刷新当前章节即可。\n* 3.0 新版可以直接在书籍阅读界面点击右上角的“**⁝**”按钮，选择“**图片样式**”——***`full`***。\n\n\n## 替换净化相关\n\n### 1. 替换净化是什么？\n替换净化可以去除书籍内容里的广告、错别字、屏蔽词等。\n\n### 2. 如何自己填写净化替换规则？\n* 第一行：替换规则名称。请根据自己需求对替换净化规则进行命名；\n* 第二行：分组。净化规则的分组组别；\n* 第三行：替换规则。填写需要被替换的内容；\n* 第四行：替换为。填写想替换成的内容（如不填则默认表示删除第三行里填写的内容）；\n* 第五行：替换范围，选填书名或者源名。填写此替换净化规则需要对哪本书籍或者哪个书源生效（如不填则对所有书籍和书源生效）。\n\n**【注】** *如常规去除方法去除不掉，则需要勾选“使用正则表达式”，同时第三行里的替换规则也需要按照正则表达式来填写（正则表达式填写方法可自行网上搜索学习）。*\n\n\n## 备份相关\n\n### 1. 云备份在哪？\n“**我的**”——“**备份与恢复**”——“**WebDav 设置**”。\n\n### 2. 如何操作进行云备份？\n* 侧栏设置，WebDav 设置；\n* 正确填写 WebDAV 服务器地址、账号和密码；\n* 无需操作，APP 默认每天自动云备份一次。\n\n作者在此诚挚推荐使用【坚果云】进行 WebDav 备份。\n\n如果直接在手机上注册，须下载【坚果云】APP，步骤较为繁琐。推荐在电脑上进行操作：\n1. 打开注册链接：https://www.jianguoyun.com/d/signup ；\n2. 注册后，进入坚果云；\n3. 点击右上角账户名处选择“**账户信息**”，然后选择“**安全选项**”；\n4. 在“**安全选项**”中找到“**第三方应用管理**”，并选择“**添加应用**”，输入名称（如“阅读”）后，会生成密码，选择完成；\n5. 其中 `https://dav.jianguoyun.com/dav/` 就是填入“**WebDAV 服务器地址**”的内容，“**使用情况**”后面的邮箱地址就是你的“**WebDAV 账号**”，点击“**显示密码**“后得到的密码就是你的“**WebDAV 密码**”。\n\n### 3. 关于云备份的相关说明\n\n在正确设置好云备份的情况下，APP 默认每天自动云备份一次，当日多次手动云备份会对当日的旧云备份文件进行覆盖，并不会覆盖之前及之后不同日期的备份文件，每天所自动云备份的文件会按照日期进行命名。\n\n### 4. 本地备份和云备份都能备份哪些东西？\n书架、看书进度、搜索记录、书源、替换和 APP 设置等都会备份，基本涵盖所有内容。\n\n### 5. 出现某些未知 Bug 怎么办？\n清除软件数据试试看，不行再进行反馈。\n\n\n## 其他\n\n### 1. 如何听书？\n可以使用手机自带的朗读引擎，也可使用第三方如 Google（谷歌）或小米等朗读引擎。\n\n【具体操作】*安装——系统设置——其他高级设置——辅助功能——TTS 输出——选择安装的朗读引擎（不同品牌手机的操作方法及步骤也不同，视情况而定）。*\n\n### 2. 如何设置屏幕方向、屏幕显示时长、显示/隐藏状态栏、显示/隐藏导航栏、音量键翻页、长按选择文本、点击总是翻下一页或自定义翻页按键？\n阅读界面——“**设置**”（可上划，下面还有其他设置）。\n\n### 3. 搜索的时候感觉手机卡顿，如何解决？\n“**我的**”——“**其他设置**”——调低“**更新和搜索线程数**”。\n"
  },
  {
    "path": "app/src/main/assets/web/help/md/debugHelp.md",
    "content": "# 书源调试\n\n* 调试搜索>>输入关键字，如：\n```\n系统\n```\n* 调试发现>>输入发现URL，如：\n```\n月票榜::https://www.qidian.com/rank/yuepiao?page={{page}}\n```\n* 调试详情页>>输入详情页URL，如：\n```\nhttps://m.qidian.com/book/1015609210\n```\n* 调试目录页>>输入目录页URL，如：\n```\n++https://www.zhaishuyuan.com/read/30394\n```\n* 调试正文页>>输入正文页URL，如：\n```\n--https://www.zhaishuyuan.com/chapter/30394/20940996\n```\n"
  },
  {
    "path": "app/src/main/assets/web/help/md/dictRuleHelp.md",
    "content": "## 字典规则说明\n\n* 字典规则是用在正文文字选择菜单字典里的规则,通常用来做翻译或者查找\n* urlRule\n    * 同书源的url规则\n* showRule\n    * 用来提取显示到对话框里面内容的规则"
  },
  {
    "path": "app/src/main/assets/web/help/md/httpTTSHelp.md",
    "content": "# 在线朗读规则说明\n\n* 在线朗读规则为url规则,同书源url\n* js参数\n```\nspeakText //朗读文本\nspeakSpeed //朗读速度,5-50\n```\n* 例:\n```\nhttp://tts.baidu.com/text2audio,{\n    \"method\": \"POST\",\n    \"body\": \"tex={{java.encodeURI(java.encodeURI(speakText))}}&spd={{String((speakSpeed + 5) / 10 + 4)}}&per=5003&cuid=baidu_speech_demo&idx=1&cod=2&lan=zh&ctp=1&pdt=1&vol=5&pit=5&_res_tag_=audio\"\n}\n```"
  },
  {
    "path": "app/src/main/assets/web/help/md/jsHelp.md",
    "content": "# js变量和函数\n> 阅读使用[Rhino v1.8.0](https://github.com/mozilla/rhino) 作为JavaScript引擎以便于[调用Java类和方法](https://m.jb51.net/article/92138.htm)，查看[ECMAScript兼容性表格](https://mozilla.github.io/rhino/compat/engines.html)\n\n> [Rhino运行时](https://github.com/mozilla/rhino/blob/master/rhino/src/main/java/org/mozilla/javascript/ScriptRuntime.java)懒加载导入的Java类和方法\n\n|构造函数|函数|对象|调用类|简要说明|\n|------|-----|------|----|------|\n|JavaImporter|importClass importPackage| |[ImporterTopLevel](https://github.com/mozilla/rhino/blob/master/rhino/src/main/java/org/mozilla/javascript/ImporterTopLevel.java)|导入Java类到JavaScript|\n||getClass|Packages java javax ...|[NativeJavaTopPackage](https://github.com/mozilla/rhino/blob/master/rhino/src/main/java/org/mozilla/javascript/NativeJavaTopPackage.java)|默认导入JavaScript中的Java类|\n|JavaAdapter|||[JavaAdapter](https://github.com/mozilla/rhino/blob/master/rhino/src/main/java//org/mozilla/javascript/JavaAdapter.java)|继承Java类|\n\n> 注意`java`变量指向已经被阅读修改，如果想要调用`java.*`下的包，请使用`Packages.java.*`\n\n> 在书源规则中使用`@js` `<js>` `{{}}`可使用JavaScript调用阅读部分内置的类和方法\n\n> 注意为了安全，阅读会屏蔽部分java类调用，见[RhinoClassShutter](https://github.com/gedoor/legado/blob/master/modules/rhino/src/main/java/com/script/rhino/RhinoClassShutter.kt)\n\n> 不同的书源规则中支持的调用的Java类和方法可能有所不同\n\n> 注意使用 `const` 声明的变量不支持块级作用域，在循环里使用会出现值不变的问题，请改用 `var` 声明\n\n|变量名|调用类|\n|------|-----|\n|java|当前类|\n|baseUrl|当前url,String  |\n|result|上一步的结果|\n|book|[书籍类](https://github.com/gedoor/legado/blob/master/app/src/main/java/io/legado/app/data/entities/Book.kt)|\n|rssArticle|[Article类](https://github.com/gedoor/legado/blob/master/app/src/main/java/io/legado/app/data/entities/RssArticle.kt)|\n|chapter|[章节类](https://github.com/gedoor/legado/blob/master/app/src/main/java/io/legado/app/data/entities/BookChapter.kt)|\n|source|[基础书源类](https://github.com/gedoor/legado/blob/master/app/src/main/java/io/legado/app/data/entities/BaseSource.kt)|\n|cookie|[cookie操作类](https://github.com/gedoor/legado/blob/master/app/src/main/java/io/legado/app/help/http/CookieStore.kt)| \n|cache|[缓存操作类](https://github.com/gedoor/legado/blob/master/app/src/main/java/io/legado/app/help/CacheManager.kt)|\n|title|章节当前标题 String|\n|src| 请求返回的源码|\n|nextChapterUrl|下一章节url|\n\n## 当前类对象的可使用的部分方法\n\n### [RssJsExtensions](https://github.com/gedoor/legado/blob/master/app/src/main/java/io/legado/app/ui/rss/read/RssJsExtensions.kt)\n> 只能在订阅源`shouldOverrideUrlLoading`规则中使用  \n> 订阅添加跳转url拦截, js, 返回true拦截,js变量url,可以通过js打开url  \n> url跳转拦截规则不能执行耗时操作\n> 例子https://github.com/gedoor/legado/discussions/3259\n\n* 调用阅读搜索\n\n```js\njava.searchBook(bookName: String)\n```\n\n* 添加书架\n\n```js\njava.addBook(bookUrl: String)\n```\n\n### [AnalyzeUrl](https://github.com/gedoor/legado/blob/master/app/src/main/java/io/legado/app/model/analyzeRule/AnalyzeUrl.kt) 部分函数\n> js中通过java.调用,只在`登录检查JS`规则中有效\n```js\ninitUrl() //重新解析url,可以用于登录检测js登录后重新解析url重新访问\ngetHeaderMap().putAll(source.getHeaderMap(true)) //重新设置登录头\ngetStrResponse( jsStr: String? = null, sourceRegex: String? = null) //返回访问结果,文本类型,书源内部重新登录后可调用此方法重新返回结果\ngetResponse(): Response //返回访问结果,网络朗读引擎采用的是这个,调用登录后在调用这方法可以重新访问,参考阿里云登录检测\n```\n\n### [AnalyzeRule](https://github.com/gedoor/legado/blob/master/app/src/main/java/io/legado/app/model/analyzeRule/AnalyzeRule.kt) 部分函数\n* 获取文本/文本列表\n> `mContent` 待解析源代码，默认为当前页面  \n> `isUrl` 链接标识，默认为`false`\n```js\njava.getString(ruleStr: String?, mContent: Any? = null, isUrl: Boolean = false)\njava.getStringList(ruleStr: String?, mContent: Any? = null, isUrl: Boolean = false)\n```\n* 设置解析内容\n\n```js\njava.setContent(content: Any?, baseUrl: String? = null):\n```\n\n* 获取Element/Element列表\n\n> 如果要改变解析源代码，请先使用`java.setContent`\n\n```js\njava.getElement(ruleStr: String)\njava.getElements(ruleStr: String)\n```\n\n* 重新搜索书籍/重新获取目录url\n\n> 只能在刷新目录之前使用,有些书源书籍地址和目录url会变\n\n```js\njava.reGetBook()\njava.refreshTocUrl()\n```\n* 变量存取\n\n```js\njava.get(key)\njava.put(key, value)\n```\n\n### [js扩展类](https://github.com/gedoor/legado/blob/master/app/src/main/java/io/legado/app/help/JsExtensions.kt) 部分函数\n\n* 链接解析[JsURL](https://github.com/gedoor/legado/blob/master/app/src/main/java/io/legado/app/utils/JsURL.kt)\n```js\njava.toURL(url): JsURL\njava.toURL(url, baseUrl): JsURL\n```\n* 获取SystemWebView User-Agent\n```js\njava.getWebViewUA(): String\n```\n* 网络请求\n```js\njava.ajax(urlStr): String\njava.ajaxAll(urlList: Array<String>): Array<StrResponse>\n//返回StrResponse 方法body() code() message() headers() raw() toString() \njava.connect(urlStr): StrResponse\n\njava.post(url: String, body: String, headerMap: Map<String, String>): Connection.Response\n\njava.get(url: String, headerMap: Map<String, String>): Connection.Response\n\njava.head(url: String, headerMap: Map<String, String>): Connection.Response\n\n* 使用webView访问网络\n* @param html 直接用webView载入的html, 如果html为空直接访问url\n* @param url html内如果有相对路径的资源不传入url访问不了\n* @param js 用来取返回值的js语句, 没有就返回整个源代码\n* @return 返回js获取的内容\njava.webView(html: String?, url: String?, js: String?): String?\n\n* 使用webView获取跳转url\njava.webViewGetOverrideUrl(html: String?, url: String?, js: String?, overrideUrlRegex: String): String?\n\n* 使用webView获取资源url\njava.webViewGetSource(html: String?, url: String?, js: String?, sourceRegex: String): String?\n\n* 使用内置浏览器打开链接，可用于获取验证码 手动验证网站防爬\n* @param url 要打开的链接\n* @param title 浏览器的标题\njava.startBrowser(url: String, title: String)\n\n* 使用内置浏览器打开链接，并等待网页结果 .body()获取网页内容\njava.startBrowserAwait(url: String, title: String, refetchAfterSuccess: Boolean? = true): StrResponse\n```\n* 调试\n```js\njava.log(msg)\njava.logType(var)\n```\n* 获取用户输入的验证码\n```js\njava.getVerificationCode(imageUrl)\n```\n* 弹窗提示\n```js\njava.longToast(msg: Any?)\njava.toast(msg: Any?)\n```\n* 从网络(由java.cacheFile实现)、本地读取JavaScript文件，导入上下文请手动`eval(String(...))`\n```js\njava.importScript(url)\n//相对路径支持android/data/{package}/cache\njava.importScript(relativePath)\njava.importScript(absolutePath)\n```\n* 缓存网络文件\n```js\n获取\njava.cacheFile(url)\njava.cacheFile(url,saveTime)\n执行内容\neval(String(java.cacheFile(url)))\n使缓存失效\ncache.delete(java.md5Encode16(url))\n```\n* 获取网络压缩文件里面指定路径的数据 *可替换Zip Rar 7Z\n```js\njava.get*StringContent(url: String, path: String): String\n\njava.get*StringContent(url: String, path: String, charsetName: String): String\n\njava.get*ByteArrayContent(url: String, path: String): ByteArray?\n\n```\n* URI编码\n```js\njava.encodeURI(str: String) //默认enc=\"UTF-8\"\njava.encodeURI(str: String, enc: String)\n```\n* base64\n> flags参数可省略，默认Base64.NO_WRAP，查看[flags参数说明](https://blog.csdn.net/zcmain/article/details/97051870)\n```js\njava.base64Decode(str: String)\njava.base64Decode(str: String, charset: String)\njava.base64DecodeToByteArray(str: String, flags: Int)\njava.base64Encode(str: String, flags: Int)\n```\n* ByteArray\n```js\nStr转Bytes\njava.strToBytes(str: String)\njava.strToBytes(str: String, charset: String)\nBytes转Str\njava.bytesToStr(bytes: ByteArray)\njava.bytesToStr(bytes: ByteArray, charset: String)\n```\n* Hex\n```js\nHexString 解码为字节数组\njava.hexDecodeToByteArray(hex: String)\nhexString 解码为utf8String\njava.hexDecodeToString(hex: String)\nutf8 编码为hexString\njava.hexEncodeToString(utf8: String)\n```\n* 标识id\n```js\njava.randomUUID()\njava.androidId()\n```\n* 繁简转换\n```js\n将文本转换为简体\njava.t2s(text: String): String\n将文本转换为繁体\njava.s2t(text: String): String\n```\n* 时间格式化\n```js\njava.timeFormatUTC(time: Long, format: String, sh: Int): String?\njava.timeFormat(time: Long): String\n```\n* html格式化\n```js\njava.htmlFormat(str: String): String\n```\n* 文件\n>  所有对于文件的读写删操作都是相对路径,只能操作阅读缓存/android/data/{package}/cache/内的文件\n```js\n//文件下载 url用于生成文件名，返回文件路径\ndownloadFile(url: String): String\n//文件解压,zipPath为压缩文件路径，返回解压路径\nunArchiveFile(zipPath: String): String\nunzipFile(zipPath: String): String\nunrarFile(zipPath: String): String\nun7zFile(zipPath: String): String\n//文件夹内所有文件读取\ngetTxtInFolder(unzipPath: String): String\n//读取文本文件\nreadTxtFile(path: String): String\n//删除文件\ndeleteFile(path: String) \n```\n\n### [js加解密类](https://github.com/gedoor/legado/blob/master/app/src/main/java/io/legado/app/help/JsEncodeUtils.kt) 部分函数\n\n> 提供在JavaScript环境中快捷调用crypto算法的函数，由[hutool-crypto](https://www.hutool.cn/docs/#/crypto/概述)实现  \n> 由于兼容性问题，hutool-crypto当前版本为5.8.22  \n\n> 注意：如果输入的参数不是Utf8String 可先调用`java.hexDecodeToByteArray java.base64DecodeToByteArray`转成ByteArray\n* 对称加密\n> 输入参数key iv 支持ByteArray|**Utf8String**\n```js\n// 创建Cipher\njava.createSymmetricCrypto(transformation, key, iv)\n```\n>解密加密参数 data支持ByteArray|Base64String|HexString|InputStream\n```js\n//解密为ByteArray String\ncipher.decrypt(data)\ncipher.decryptStr(data)\n//加密为ByteArray Base64字符 HEX字符\ncipher.encrypt(data)\ncipher.encryptBase64(data)\ncipher.encryptHex(data)\n```\n* 非对称加密\n> 输入参数 key支持ByteArray|**Utf8String**\n```js\n//创建cipher\njava.createAsymmetricCrypto(transformation)\n//设置密钥\n.setPublicKey(key)\n.setPrivateKey(key)\n\n```\n> 解密加密参数 data支持ByteArray|Base64String|HexString|InputStream  \n```js\n//解密为ByteArray String\ncipher.decrypt(data,  usePublicKey: Boolean? = true\n)\ncipher.decryptStr(data, usePublicKey: Boolean? = true\n)\n//加密为ByteArray Base64字符 HEX字符\ncipher.encrypt(data,  usePublicKey: Boolean? = true\n)\ncipher.encryptBase64(data,  usePublicKey: Boolean? = true\n)\ncipher.encryptHex(data,  usePublicKey: Boolean? = true\n)\n```\n* 签名\n> 输入参数 key 支持ByteArray|**Utf8String**\n```js\n//创建Sign\njava.createSign(algorithm)\n//设置密钥\n.setPublicKey(key)\n.setPrivateKey(key)\n```\n> 签名参数 data支持ByteArray|inputStream|String\n```js\n//签名输出 ByteArray HexString\nsign.sign(data)\nsign.signHex(data)\n```\n* 摘要\n```js\njava.digestHex(data: String, algorithm: String,): String?\n\njava.digestBase64Str(data: String, algorithm: String,): String?\n```\n* md5\n```js\njava.md5Encode(str)\njava.md5Encode16(str)\n```\n* HMac\n```js\njava.HMacHex(data: String, algorithm: String, key: String): String\n\njava.HMacBase64(data: String, algorithm: String, key: String): String\n```\n\n## book对象的可用属性\n### 属性\n> 使用方法: 在js中或{{}}中使用book.属性的方式即可获取.如在正文内容后加上 ##{{book.name+\"正文卷\"+title}} 可以净化 书名+正文卷+章节名称（如 我是大明星正文卷第二章我爸是豪门总裁） 这一类的字符.\n```js\nbookUrl // 详情页Url(本地书源存储完整文件路径)\ntocUrl // 目录页Url (toc=table of Contents)\norigin // 书源URL(默认BookType.local)\noriginName //书源名称 or 本地书籍文件名\nname // 书籍名称(书源获取)\nauthor // 作者名称(书源获取)\nkind // 分类信息(书源获取)\ncustomTag // 分类信息(用户修改)\ncoverUrl // 封面Url(书源获取)\ncustomCoverUrl // 封面Url(用户修改)\nintro // 简介内容(书源获取)\ncustomIntro // 简介内容(用户修改)\ncharset // 自定义字符集名称(仅适用于本地书籍)\ntype // 0:text 1:audio\ngroup // 自定义分组索引号\nlatestChapterTitle // 最新章节标题\nlatestChapterTime // 最新章节标题更新时间\nlastCheckTime // 最近一次更新书籍信息的时间\nlastCheckCount // 最近一次发现新章节的数量\ntotalChapterNum // 书籍目录总数\ndurChapterTitle // 当前章节名称\ndurChapterIndex // 当前章节索引\ndurChapterPos // 当前阅读的进度(首行字符的索引位置)\ndurChapterTime // 最近一次阅读书籍的时间(打开正文的时间)\ncanUpdate // 刷新书架时更新书籍信息\norder // 手动排序\noriginOrder //书源排序\nvariable // 自定义书籍变量信息(用于书源规则检索书籍信息)\n ```\n\n## chapter对象的部分可用属性\n> 使用方法: 在js中或{{}}中使用chapter.属性的方式即可获取.如在正文内容后加上 ##{{chapter.title+chapter.index}} 可以净化 章节标题+序号(如 第二章 天仙下凡2) 这一类的字符.\n ```js\n url // 章节地址\n title // 章节标题\n baseUrl //用来拼接相对url\n bookUrl // 书籍地址\n index // 章节序号\n resourceUrl // 音频真实URL\n tag //\n start // 章节起始位置\n end // 章节终止位置\n variable //变量\n ```\n \n## source对象的部分可用函数\n* 获取书源url\n```js\nsource.getKey()\n```\n* 书源变量存取\n```js\nsource.setVariable(variable: String?)\nsource.getVariable()\n```\n\n* 登录头操作\n```js\n获取登录头\nsource.getLoginHeader()\n获取登录头某一键值\nsource.getLoginHeaderMap().get(key: String)\n保存登录头\nsource.putLoginHeader(header: String)\n清除登录头\nsource.removeLoginHeader()\n```\n* 用户登录信息操作\n> 使用`登录UI`规则，并成功登录，阅读自动加密保存登录UI规则中除type为button的信息\n```js\nlogin函数获取登录信息\nsource.getLoginInfo()\nlogin函数获取登录信息键值\nsource.getLoginInfoMap().get(key: String)\n清除登录信息\nsource.removeLoginInfo()\n```\n## cookie对象的部分可用函数\n```js\n获取全部cookie\ncookie.getCookie(url)\n获取cookie某一键值\ncookie.getKey(url,key)\n设置cookie\ncookie.setCookie(url,cookie)\n替换cookie\ncookie.replaceCookie(url,cookie)\n删除cookie\ncookie.removeCookie(url)\n```\n\n## cache对象的部分可用函数\n> saveTime单位:秒，可省略  \n> 保存至数据库和缓存文件(50M)，保存的内容较大时请使用`getFile putFile`\n```js\n保存\ncache.put(key: String, value: String, saveTime: Int)\n读取数据库\ncache.get(key: String): String?\n删除\ncache.delete(key: String)\n缓存文件内容\ncache.putFile(key: String, value: String, saveTime: Int)\n读取文件内容\ncache.getFile(key: String): String?\n保存到内存\ncache.putMemory(key: String, value: Any)\n读取内存\ncache.getFromMemory(key: String): Any?\n删除内存\ncache.deleteMemory(key: String)\n```\n\n## 跳转外部链接/应用函数\n```js\n// 跳转外部链接，传入http链接或者scheme跳转到浏览器或其他应用\njava.openUrl(url:String)\n// 指定mimeType，可以跳转指定类型应用，例如（video/*）\njava.openUrl(url:String,mimeType:String)"
  },
  {
    "path": "app/src/main/assets/web/help/md/readMenuHelp.md",
    "content": "# 阅读界面帮助文档\n\n## 阅读界面主菜单\n* 顶部操作\n  * 章节名称:点击可编辑书源\n  * 章节url:点击可打开浏览器浏览\n  * 菜单:**不同类型的书籍显示的菜单不同**。详情请查看菜单文字,长按菜单图标可显示文字\n* 中间左侧-亮度调节\n  * 亮度调节的顶端有跟随系统亮度的开关,打开后亮度跟随系统,关闭后才可以调节亮度条\n* 底部操作\n  * 4个圆形按钮依次为 全文搜索✧自动翻页✧替换净化✧切换夜间模式\n  * 上一章✧下一章中间的进度条为页数进度,要快速跳转章节点击目录按钮进入目录快速跳转\n  * 目录->目录和书签界面\n  * 朗读->单击开始朗读,长按进入朗读设置界面\n  * 界面->所有排版设置都在里面\n  * 设置->其它一些设置,找不到的设置去这里看看,可滚动\n\n## 全文搜索\n搜索本地缓存或者本地文件，不能搜索在线内容  \n书籍字数、净化规则数量、简繁转化、文章分段都会影响到搜索速度，请酌情启用\n\n## 朗读设置界面\n* 后台->进入后台朗读,可以做一些其它事\n* 设置->朗读引擎设置,可以切换本地TTS和在线朗读,在线朗读可自定义\n\n## 排版设置界面\n* 白天模式和夜间模式背景不同布局相同\n* 共用布局->启用共用布局时所有背景使用同一布局,关闭共用布局则每个背景单独布局\n* 长按背景可进入文字颜色和背景设置界面\n\n## 其它设置界面\n* 屏幕方向\n* 屏幕超时\n* 隐藏状态栏\n* 扩展到刘海\n* 隐藏导航栏\n* 文字两端对齐\n* 文字底部对齐\n* 音量键翻页\n* 点击翻页\n* 朗读时音量键翻页\n* 自动换源->书源被删除时自动切换到其它书源\n* 长按选择文本\n* 显示亮度调节控件\n* 点击区域设置\n* 自定义翻页按键\n"
  },
  {
    "path": "app/src/main/assets/web/help/md/regexHelp.md",
    "content": "# 正则表达式学习\n\n- [基本匹配]\n- [元字符]\n  - [英文句号]\n  - [字符集]\n    - [否定字符集]\n  - [重复]\n    - [星号]\n    - [加号]\n    - [问号]\n  - [花括号]\n  - [字符组]\n  - [分支结构]\n  - [转义特殊字符]\n  - [定位符]\n    - [插入符号]\n    - [美元符号]\n- [简写字符集]\n- [断言]\n  - [正向先行断言]\n  - [负向先行断言]\n  - [正向后行断言]\n  - [负向后行断言]\n- [标记]\n  - [不区分大小写]\n  - [全局搜索]\n  - [多行匹配]\n- [常用正则表达式]\n\n## 1. 基本匹配\n\n正则表达式只是我们用于在文本中检索字母和数字的模式。例如正则表达式 `cat`，表示: 字母 `c` 后面跟着一个字母 `a`，再后面跟着一个字母 `t`。<pre>\"cat\" => The <a href=\"#learn-regex\"><strong>cat</strong></a> sat on the mat</pre>\n\n正则表达式 `123` 会匹配字符串 \"123\"。通过将正则表达式中的每个字符逐个与要匹配的字符串中的每个字符进行比较，来完成正则匹配。\n正则表达式通常区分大小写，因此正则表达式 `Cat` 与字符串 \"cat\" 不匹配。<pre>\"Cat\" => The cat sat on the <a href=\"#learn-regex\"><strong>Cat</strong></a></pre>\n\n## 2. 元字符\n\n元字符是正则表达式的基本组成元素。元字符在这里跟它通常表达的意思不一样，而是以某种特殊的含义去解释。有些元字符写在方括号内的时候有特殊含义。\n元字符如下:\n\n|元字符|描述|\n|:----:|----|\n|.|匹配除换行符以外的任意字符。|\n|[ ]|字符类，匹配方括号中包含的任意字符。|\n|[^ ]|否定字符类。匹配方括号中不包含的任意字符|\n|*|匹配前面的子表达式零次或多次|\n|+|匹配前面的子表达式一次或多次|\n|?|匹配前面的子表达式零次或一次，或指明一个非贪婪限定符。|\n|{n,m}|花括号，匹配前面字符至少 n 次，但是不超过 m 次。|\n|(xyz)|字符组，按照确切的顺序匹配字符xyz。|\n|&#124;|分支结构，匹配符号之前的字符或后面的字符。|\n|&#92;|转义符，它可以还原元字符原来的含义，允许你匹配保留字符 <code>[ ] ( ) { } . * + ? ^ $ \\ &#124;</code>|\n|^|匹配行的开始|\n|$|匹配行的结束|\n\n## 2.1 英文句号\n\n英文句号 `.` 是元字符的最简单的例子。元字符 `.` 可以匹配任意单个字符。它不会匹配换行符和新行的字符。例如正则表达式 `.ar`，表示: 任意字符后面跟着一个字母 `a`，\n再后面跟着一个字母 `r`。<pre>\".ar\" => The <a href=\"#learn-regex\"><strong>car</strong></a> <a href=\"#learn-regex\"><strong>par</strong></a>ked in the <a href=\"#learn-regex\"><strong>gar</strong></a>age.</pre>\n\n## 2.2 字符集\n\n字符集也称为字符类。方括号被用于指定字符集。使用字符集内的连字符来指定字符范围。方括号内的字符范围的顺序并不重要。\n例如正则表达式 `[Tt]he`，表示: 大写 `T` 或小写 `t` ，后跟字母 `h`，再后跟字母 `e`。<pre>\"[Tt]he\" => <a href=\"#learn-regex\"><strong>The</strong></a> car parked in <a href=\"#learn-regex\"><strong>the</strong></a> garage.</pre>\n\n然而，字符集中的英文句号表示它字面的含义。正则表达式 `ar[.]`，表示小写字母 `a`，后面跟着一个字母 `r`，再后面跟着一个英文句号 `.` 字符。<pre>\"ar[.]\" => A garage is a good place to park a c<a href=\"#learn-regex\"><strong>ar.</strong></a></pre>\n\n### 2.2.1 否定字符集\n\n一般来说插入字符 `^` 表示一个字符串的开始，但是当它在方括号内出现时，它会取消字符集。例如正则表达式 `[^c]ar`，表示: 除了字母 `c` 以外的任意字符，后面跟着字符 `a`，\n再后面跟着一个字母 `r`。<pre>\"[^c]ar\" => The car <a href=\"#learn-regex\"><strong>par</strong></a>ked in the <a href=\"#learn-regex\"><strong>gar</strong></a>age.</pre>\n\n## 2.3 重复\n\n以下元字符 `+`，`*` 或 `?` 用于指定子模式可以出现多少次。这些元字符在不同情况下的作用不同。\n\n### 2.3.1 星号\n\n该符号 `*` 表示匹配上一个匹配规则的零次或多次。正则表达式 `a*` 表示小写字母 `a` 可以重复零次或者多次。但是它如果出现在字符集或者字符类之后，它表示整个字符集的重复。\n例如正则表达式 `[a-z]*`，表示: 一行中可以包含任意数量的小写字母。<pre>\"[a-z]*\" => T<a href=\"#learn-regex\"><strong>he</strong></a> <a href=\"#learn-regex\"><strong>car</strong></a> <a href=\"#learn-regex\"><strong>parked</strong></a> <a href=\"#learn-regex\"><strong>in</strong></a> <a href=\"#learn-regex\"><strong>the</strong></a> <a href=\"#learn-regex\"><strong>garage</strong></a> #21.</pre>\n\n该 `*` 符号可以与元符号 `.` 用在一起，用来匹配任意字符串 `.*`。该 `*` 符号可以与空格符 `\\s` 一起使用，用来匹配一串空格字符。\n例如正则表达式 `\\s*cat\\s*`，表示: 零个或多个空格，后面跟小写字母 `c`，再后面跟小写字母 `a`，再再后面跟小写字母 `t`，后面再跟零个或多个空格。<pre>\"\\s*cat\\s*\" => The fat<a href=\"#learn-regex\"><strong> cat </strong></a>sat on the <a href=\"#learn-regex\"><strong>cat</strong></a>.</pre>\n\n### 2.3.2 加号\n\n该符号 `+` 匹配上一个字符的一次或多次。例如正则表达式 `c.+t`，表示: 一个小写字母 `c`，后跟任意数量的字符，后跟小写字母 `t`。<pre>\"c.+t\" => The fat <a href=\"#learn-regex\"><strong>cat sat on the mat</strong></a>.</pre>\n\n### 2.3.3 问号\n\n在正则表达式中，元字符 `?` 用来表示前一个字符是可选的。该符号匹配前一个字符的零次或一次。\n例如正则表达式 `[T]?he`，表示: 可选的大写字母 `T`，后面跟小写字母 `h`，后跟小写字母 `e`。<pre>\"[T]he\" => <a href=\"#learn-regex\"><strong>The</strong></a> car is parked in the garage.</pre><pre>\"[T]?he\" => <a href=\"#learn-regex\"><strong>The</strong></a> car is parked in t<a href=\"#learn-regex\"><strong>he</strong></a> garage.</pre>\n\n## 2.4 花括号\n\n在正则表达式中花括号(也被称为量词 ?)用于指定字符或一组字符可以重复的次数。例如正则表达式 `[0-9]{2,3}`，表示: 匹配至少2位数字但不超过3位(0到9范围内的字符)。<pre>\"[0-9]{2,3}\" => The number was 9.<a href=\"#learn-regex\"><strong>999</strong></a>7 but we rounded it off to <a href=\"#learn-regex\"><strong>10</strong></a>.0.</pre>\n\n我们可以省略第二个数字。例如正则表达式 `[0-9]{2,}`，表示: 匹配2个或更多个数字。如果我们也删除逗号，则正则表达式 `[0-9]{2}`，表示: 匹配正好为2位数的数字。<pre>\"[0-9]{2,}\" => The number was 9.<a href=\"#learn-regex\"><strong>9997</strong></a> but we rounded it off to <a href=\"#learn-regex\"><strong>10</strong></a>.0.</pre><pre>\"[0-9]{2}\" => The number was 9.<a href=\"#learn-regex\"><strong>99</strong></a><a href=\"#learn-regex\"><strong>97</strong></a> but we rounded it off to <a href=\"#learn-regex\"><strong>10</strong></a>.0.</pre>\n\n## 2.5 字符组\n\n字符组是一组写在圆括号内的子模式 `(...)`。正如我们在正则表达式中讨论的那样，如果我们把一个量词放在一个字符之后，它会重复前一个字符。\n但是，如果我们把量词放在一个字符组之后，它会重复整个字符组。\n例如正则表达式 `(ab)*` 表示匹配零个或多个的字符串 \"ab\"。我们还可以在字符组中使用元字符 `|`。例如正则表达式 `(c|g|p)ar`，表示: 小写字母 `c`、`g` 或 `p` 后面跟字母 `a`，后跟字母 `r`。<pre>\"(c|g|p)ar\" => The <a href=\"#learn-regex\"><strong>car</strong></a> is <a href=\"#learn-regex\"><strong>par</strong></a>ked in the <a href=\"#learn-regex\"><strong>gar</strong></a>age.</pre>\n\n## 2.6 分支结构\n\n在正则表达式中垂直条 `|` 用来定义分支结构，分支结构就像多个表达式之间的条件。现在你可能认为这个字符集和分支机构的工作方式一样。\n但是字符集和分支结构巨大的区别是字符集只在字符级别上有作用，然而分支结构在表达式级别上依然可以使用。\n例如正则表达式 `(T|t)he|car`，表示: 大写字母 `T` 或小写字母 `t`，后面跟小写字母 `h`，后跟小写字母 `e` 或小写字母 `c`，后跟小写字母 `a`，后跟小写字母 `r`。<pre>\"(T|t)he|car\" => <a href=\"#learn-regex\"><strong>The</strong></a> <a href=\"#learn-regex\"><strong>car</strong></a> is parked in <a href=\"#learn-regex\"><strong>the</strong></a> garage.</pre>\n\n## 2.7 转义特殊字符\n\n正则表达式中使用反斜杠 `\\` 来转义下一个字符。这将允许你使用保留字符来作为匹配字符 `{ } [ ] / \\ + * . $ ^ | ?`。在特殊字符前面加 `\\`，就可以使用它来做匹配字符。\n例如正则表达式 `.` 是用来匹配除了换行符以外的任意字符。现在要在输入字符串中匹配 `.` 字符，正则表达式 `(f|c|m)at\\.?`，表示: 小写字母 `f`、`c` 或者 `m` 后跟小写字母 `a`，后跟小写字母 `t`，后跟可选的 `.` 字符。<pre>\"(f|c|m)at\\.?\" => The <a href=\"#learn-regex\"><strong>fat</strong></a> <a href=\"#learn-regex\"><strong>cat</strong></a> sat on the <a href=\"#learn-regex\"><strong>mat.</strong></a></pre>\n\n## 2.8 定位符\n\n在正则表达式中，为了检查匹配符号是否是起始符号或结尾符号，我们使用定位符。\n定位符有两种类型: 第一种类型是 `^` 检查匹配字符是否是起始字符，第二种类型是 `$`，它检查匹配字符是否是输入字符串的最后一个字符。\n\n### 2.8.1 插入符号\n\n插入符号 `^` 符号用于检查匹配字符是否是输入字符串的第一个字符。如果我们使用正则表达式 `^a` (如果a是起始符号)匹配字符串 `abc`，它会匹配到 `a`。\n但是如果我们使用正则表达式 `^b`，它是匹配不到任何东西的，因为在字符串 `abc` 中 \"b\" 不是起始字符。\n让我们来看看另一个正则表达式 `^(T|t)he`，这表示: 大写字母 `T` 或小写字母 `t` 是输入字符串的起始符号，后面跟着小写字母 `h`，后跟小写字母 `e`。<pre>\"(T|t)he\" => <a href=\"#learn-regex\"><strong>The</strong></a> car is parked in <a href=\"#learn-regex\"><strong>the</strong></a> garage.</pre><pre>\"^(T|t)he\" => <a href=\"#learn-regex\"><strong>The</strong></a> car is parked in the garage.</pre>\n\n### 2.8.2 美元符号\n\n美元 `$` 符号用于检查匹配字符是否是输入字符串的最后一个字符。例如正则表达式 `(at\\.)$`，表示: 小写字母 `a`，后跟小写字母 `t`，后跟一个 `.` 字符，且这个匹配器必须是字符串的结尾。<pre>\"(at\\.)\" => The fat c<a href=\"#learn-regex\"><strong>at.</strong></a> s<a href=\"#learn-regex\"><strong>at.</strong></a> on the m<a href=\"#learn-regex\"><strong>at.</strong></a></pre><pre>\"(at\\.)$\" => The fat cat sat on the m<a href=\"#learn-regex\"><strong>at.</strong></a></pre>\n\n## 3. 简写字符集\n\n正则表达式为常用的字符集和常用的正则表达式提供了简写。简写字符集如下:\n\n|简写|描述|\n|:----:|----|\n|.|匹配除换行符以外的任意字符|\n|\\w|匹配所有字母和数字的字符: `[a-zA-Z0-9_]`|\n|\\W|匹配非字母和数字的字符: `[^\\w]`|\n|\\d|匹配数字: `[0-9]`|\n|\\D|匹配非数字: `[^\\d]`|\n|\\s|匹配空格符: `[\\t\\n\\f\\r\\p{Z}]`|\n|\\S|匹配非空格符: `[^\\s]`|\n\n## 4. 断言\n\n后行断言和先行断言有时候被称为断言，它们是特殊类型的 ***非捕获组*** (用于匹配模式，但不包括在匹配列表中)。当我们在一种特定模式之前或者之后有这种模式时，会优先使用断言。\n例如我们想获取输入字符串 `$4.44 and $10.88` 中带有前缀 `$` 的所有数字。我们可以使用这个正则表达式 `(?<=\\$)[0-9\\.]*`，表示: 获取包含 `.` 字符且前缀为 `$` 的所有数字。\n以下是正则表达式中使用的断言:\n\n|符号|描述|\n|:----:|----|\n|?=|正向先行断言|\n|?!|负向先行断言|\n|?<=|正向后行断言|\n|?<!|负向后行断言|\n\n### 4.1 正向先行断言\n\n正向先行断言认为第一部分的表达式必须是先行断言表达式。返回的匹配结果仅包含与第一部分表达式匹配的文本。\n要在一个括号内定义一个正向先行断言，在括号中问号和等号是这样使用的 `(?=...)`。先行断言表达式写在括号中的等号后面。\n例如正则表达式 `(T|t)he(?=\\sfat)`，表示: 匹配大写字母 `T` 或小写字母 `t`，后面跟字母 `h`，后跟字母 `e`。\n在括号中，我们定义了正向先行断言，它会引导正则表达式引擎匹配 `The` 或 `the` 后面跟着 `fat`。<pre>\"(T|t)he(?=\\sfat)\" => <a href=\"#learn-regex\"><strong>The</strong></a> fat cat sat on the mat.</pre>\n\n### 4.2 负向先行断言\n\n当我们需要从输入字符串中获取不匹配表达式的内容时，使用负向先行断言。负向先行断言的定义跟我们定义的正向先行断言一样，\n唯一的区别是不是等号 `=`，我们使用否定符号 `!`，例如 `(?!...)`。\n我们来看看下面的正则表达式 `(T|t)he(?!\\sfat)`，表示: 从输入字符串中获取全部 `The` 或者 `the` 且不匹配 `fat` 前面加上一个空格字符。<pre>\"(T|t)he(?!\\sfat)\" => The fat cat sat on <a href=\"#learn-regex\"><strong>the</strong></a> mat.</pre>\n\n### 4.3 正向后行断言\n\n正向后行断言是用于获取在特定模式之前的所有匹配内容。正向后行断言表示为 `(?<=...)`。例如正则表达式 `(?<=(T|t)he\\s)(fat|mat)`，表示: 从输入字符串中获取在单词 `The` 或 `the` 之后的所有 `fat` 和 `mat` 单词。<pre>\"(?<=(T|t)he\\s)(fat|mat)\" => The <a href=\"#learn-regex\"><strong>fat</strong></a> cat sat on the <a href=\"#learn-regex\"><strong>mat</strong></a>.</pre>\n\n### 4.4 负向后行断言\n\n负向后行断言是用于获取不在特定模式之前的所有匹配的内容。负向后行断言表示为 `(?<!...)`。例如正则表达式 `(?<!(T|t)he\\s)(cat)`，表示: 在输入字符中获取所有不在 `The` 或 `the` 之后的所有单词 `cat`。<pre>\"(?&lt;!(T|t)he\\s)(cat)\" => The cat sat on <a href=\"#learn-regex\"><strong>cat</strong></a>.</pre>\n\n## 5. 标记\n\n标记也称为修饰符，因为它会修改正则表达式的输出。这些标志可以以任意顺序或组合使用，并且是正则表达式的一部分。\n\n|标记|描述|\n|:----:|----|\n|i|不区分大小写: 将匹配设置为不区分大小写。|\n|g|全局搜索: 搜索整个输入字符串中的所有匹配。|\n|m|多行匹配: 会匹配输入字符串每一行。|\n\n* **数字**: `\\d+$`\n* **用户名**: `^[\\w\\d_.]{4,16}$`\n* **字母数字字符**: `^[a-zA-Z0-9]*$`\n* **带空格的字母数字字符**: `^[a-zA-Z0-9 ]*$`\n* **小写字母**: `[a-z]+$`\n* **大写字母**: `[A-Z]+$`\n* **网址**: `^(((http|https|ftp):\\/\\/)?([[a-zA-Z0-9]\\-\\.])+(\\.)([[a-zA-Z0-9]]){2,4}([[a-zA-Z0-9]\\/+=%&_\\.~?\\-]*))*$`\n* **日期 (MM/DD/YYYY)**: `^(0?[1-9]|1[012])[- /.](0?[1-9]|[12][0-9]|3[01])[- /.](19|20)?[0-9]{2}$`\n* **日期 (YYYY/MM/DD)**: `^(19|20)?[0-9]{2}[- /.](0?[1-9]|1[012])[- /.](0?[1-9]|[12][0-9]|3[01])$`\n* **求更求转发致谢**: `[\\(（【].*?[求更谢乐发推].*?[】）\\)]`\n* **查找最新章节**: `您可以.*?查找最新章节`\n* **ps/PS**: `(?i)ps\\b.*`\n* **Html标签**: `<[^>]+?>`\n"
  },
  {
    "path": "app/src/main/assets/web/help/md/replaceRuleHelp.md",
    "content": "# 替换管理界面帮助\n\n* 替换规则是用来替换正文内容的一种规则\n  * 菜单可以新建和导入规则\n  * 可以拖动排序\n  * 可以选择操作"
  },
  {
    "path": "app/src/main/assets/web/help/md/ruleHelp.md",
    "content": "# 源规则帮助\n\n* [阅读3.0(Legado)规则说明](https://mgz0227.github.io/The-tutorial-of-Legado/)\n* [书源帮助文档](https://mgz0227.github.io/The-tutorial-of-Legado/Rule/source.html)\n* [订阅源帮助文档](https://mgz0227.github.io/The-tutorial-of-Legado/Rule/rss.html)\n* 辅助键盘❓中可插入URL参数模板,打开帮助,js教程,正则教程,选择文件\n* 规则标志, {{......}}内使用规则必须有明显的规则标志,没有规则标志当作js执行\n```\n@@ 默认规则,直接写时可以省略@@\n@XPath: xpath规则,直接写时以//开头可省略@XPath\n@Json: json规则,直接写时以$.开头可省略@Json\n: regex规则,不可省略,只可以用在书籍列表和目录列表\n```\n* jsLib\n> 注入JavaScript到RhinoJs引擎中，支持两种格式，可实现[函数共用](https://github.com/gedoor/legado/wiki/JavaScript%E5%87%BD%E6%95%B0%E5%85%B1%E7%94%A8)\n\n> `JavaScript Code` 直接填写JavaScript片段  \n> `{\"example\":\"https://www.example.com/js/example.js\", ...}` 自动复用已经下载的js文件\n\n> 注意此处定义的函数可能会被多个线程同时调用，在函数里的全局变量内容将会共享使用，对其进行修改可能会出现竞争问题\n> 函数内不可声明全局变量，函数外的全局变量不可再赋值，否则会抛出 `无法修改密封对象的属性` 异常\n\n* 并发率\n> 并发限制，单位ms，可填写两种格式\n\n> `1000` 访问间隔1s  \n> `20/60000` 60s内访问次数20  \n\n* 书源类型: 文件\n> 对于类似知轩藏书提供文件整合下载的网站，可以在书源详情的下载URL规则获取文件链接\n\n> 通过截取下载链接或文件响应头头获取文件信息，获取失败会自动拼接`书名` `作者`和下载链接的`UrlOption`的`type`字段\n\n> 压缩文件解压缓存会在下次启动后自动清理，不会占用额外空间  \n\n* CookieJar\n> 启用后会自动保存每次返回头中的Set-Cookie中的值，适用于验证码图片一类需要session的网站\n\n* 登录UI\n> 不使用内置webView登录网站，需要使用`登录URL`规则实现登录逻辑，可使用`登录检查JS`检查登录结果  \n> 版本20221113重要更改：按钮支持调用`登录URL`规则里面的函数，必须实现`login`函数\n```\n规则填写示范\n[\n    {\n        \"name\": \"telephone\",\n        \"type\": \"text\"\n    },\n    {\n        \"name\": \"password\",\n        \"type\": \"password\"\n    },\n    {\n        \"name\": \"注册\",\n        \"type\": \"button\",\n        \"action\": \"http://www.yooike.com/xiaoshuo/#/register?title=%E6%B3%A8%E5%86%8C\"\n    },\n    {\n        \"name\": \"获取验证码\",\n        \"type\": \"button\",\n        \"action\": \"getVerificationCode()\",\n        \"style\": {\n            \"layout_flexGrow\": 0,\n            \"layout_flexShrink\": 1,\n            \"layout_alignSelf\": \"auto\",\n            \"layout_flexBasisPercent\": -1,\n            \"layout_wrapBefore\": false\n        }\n    }\n]\n```\n* 登录URL\n> 可填写登录链接或者实现登录UI的登录逻辑的JavaScript\n```\n示范填写\nfunction login() {\n    java.log(\"模拟登录请求\");\n    java.log(source.getLoginInfoMap());\n}\nfunction getVerificationCode() {\n    java.log(\"登录UI按钮：获取到手机号码\"+result.get(\"telephone\"))\n}\n\n登录按钮函数获取登录信息\nresult.get(\"telephone\")\nlogin函数获取登录信息\nsource.getLoginInfo()\nsource.getLoginInfoMap().get(\"telephone\")\nsource登录相关方法,可在js内通过source.调用,可以参考阿里云语音登录\nlogin()\ngetHeaderMap(hasLoginHeader: Boolean = false)\ngetLoginHeader(): String?\ngetLoginHeaderMap(): Map<String, String>?\nputLoginHeader(header: String)\nremoveLoginHeader()\nsetVariable(variable: String?)\ngetVariable(): String?\nAnalyzeUrl相关函数,js中通过java.调用\ninitUrl() //重新解析url,可以用于登录检测js登录后重新解析url重新访问\ngetHeaderMap().putAll(source.getHeaderMap(true)) //重新设置登录头\ngetStrResponse( jsStr: String? = null, sourceRegex: String? = null) //返回访问结果,文本类型,书源内部重新登录后可调用此方法重新返回结果\ngetResponse(): Response //返回访问结果,网络朗读引擎采用的是这个,调用登录后在调用这方法可以重新访问,参考阿里云登录检测\n```\n\n* 发现url格式\n```json\n[\n  {\n    \"title\": \"xxx\",\n    \"url\": \"\",\n    \"style\": {\n      \"layout_flexGrow\": 0,\n      \"layout_flexShrink\": 1,\n      \"layout_alignSelf\": \"auto\",\n      \"layout_flexBasisPercent\": -1,\n      \"layout_wrapBefore\": false\n    }\n  }\n]\n```\n\n* 请求头,支持http代理,socks4 socks5代理设置\n> 注意请求头的key是区分大小写的  \n> 正确格式 User-Agent Referer  \n> 错误格式 user-agent referer\n```\nsocks5代理\n{\n  \"proxy\":\"socks5://127.0.0.1:1080\"\n}\n不支持需要验证的socks代理\nhttp代理\n{\n  \"proxy\":\"http://127.0.0.1:1080\"\n}\n支持http代理服务器验证\n{\n  \"proxy\":\"http://127.0.0.1:1080@用户名@密码\"\n}\n注意:这些请求头是无意义的,会被忽略掉\n```\n\n* url添加js参数,解析url时执行,可在访问url时处理url,例\n```\nhttps://www.baidu.com,{\"js\":\"java.headerMap.put('xxx', 'yyy')\"}\nhttps://www.baidu.com,{\"js\":\"java.url=java.url+'yyyy'\"}\n```\n\n* 增加js方法，用于重定向拦截\n  * `java.get(urlStr: String, headers: Map<String, String>)`\n  * `java.post(urlStr: String, body: String, headers: Map<String, String>)`\n* 对于搜索重定向的源，可以使用此方法获得重定向后的url\n```\n(()=>{\n  if(page==1){\n    let url='https://www.yooread.net/e/search/index.php,'+JSON.stringify({\n    \"method\":\"POST\",\n    \"body\":\"show=title&tempid=1&keyboard=\"+key\n    });\n    return source.put('surl',String(java.connect(url).raw().request().url()));\n  } else {\n    return source.get('surl')+'&page='+(page-1)\n  }\n})()\n或者\n(()=>{\n  let base='https://www.yooread.net/e/search/';\n  if(page==1){\n    let url=base+'index.php';\n    let body='show=title&tempid=1&keyboard='+key;\n    return base+source.put('surl',java.post(url,body,{}).header(\"Location\"));\n  } else {\n    return base+source.get('surl')+'&page='+(page-1);\n  }\n})()\n```\n\n* 图片链接支持修改headers\n```\nlet options = {\n\"headers\": {\"User-Agent\": \"xxxx\",\"Referrer\":baseUrl,\"Cookie\":\"aaa=vbbb;\"}\n};\n'<img src=\"'+src+\",\"+JSON.stringify(options)+'\">'\n```\n\n* 字体解析使用\n> 使用方法,在正文替换规则中使用,原理根据f1字体的字形数据到f2中查找字形对应的编码\n```\n<js>\n(function(){\n  var b64=String(src).match(/ttf;base64,([^\\)]+)/);\n  if(b64){\n    var f1 = java.queryTTF(b64[1]);\n    var f2 = java.queryTTF(\"https://alanskycn.gitee.io/teachme/assets/font/Source Han Sans CN Regular.ttf\");\n    // return java.replaceFont(result, f1, f2);\n    return java.replaceFont(result, f1, f2, true); // 过滤掉f1中不存在的字形\n  }\n  return result;\n})()\n</js>\n```\n\n* 购买操作\n> 可直接填写链接或者JavaScript，如果执行结果是网络链接将会自动打开浏览器,js返回true自动刷新目录和当前章节\n\n* 图片解密\n> 适用于图片需要二次解密的情况，直接填写JavaScript，返回解密后的`ByteArray`  \n> 部分变量说明：java（仅支持[js扩展类](https://github.com/gedoor/legado/blob/master/app/src/main/java/io/legado/app/help/JsExtensions.kt)），result为待解密图片的`ByteArray`，src为图片链接\n\n```js\njava.createSymmetricCrypto(\"AES/CBC/PKCS5Padding\", key, iv).decrypt(result)\n```\n\n```js\nfunction decodeImage(data, key) {\n  var input = new Packages.java.io.ByteArrayInputStream(data)\n  var out = new Packages.java.io.ByteArrayOutputStream()\n  var byte\n  while ((byte = input.read()) != -1) {\n    out.write(byte ^ key)\n  }\n  return out.toByteArray()\n}\n\ndecodeImage(result, key)\n```\n\n* 封面解密\n> 同图片解密 其中result为待解密封面的`inputStream`\n\n```js\njava.createSymmetricCrypto(\"AES/CBC/PKCS5Padding\", key, iv).decrypt(result)\n```\n\n```js\nfunction decodeImage(data, key) {\n  var out = new Packages.java.io.ByteArrayOutputStream()\n  var byte\n  while ((byte = data.read()) != -1) {\n    out.write(byte ^ key)\n  }\n  return out.toByteArray()\n}\n\ndecodeImage(result, key)\n```\n"
  },
  {
    "path": "app/src/main/assets/web/help/md/txtTocRuleHelp.md",
    "content": "## Txt目录正则说明\n\n### 菜单区\n\n- 新增目录规则，当Legado自带的规则不能够满足需求时，用户可根据自己的情况自定义目录规则\n- 导入默认规则 在旧版本中，Legado自带的规则不会随着软件的更新而更新。用户需要使用最新规则或对自带规则修改后需要重置时，可点击 导入默认规则。**注意：导入默认规则不会重置用户自定义的规则，但如果您对自带的规则进行了修改，则修改的规则会被重置为默认规则。**\n- 网络导入 为了方便异步调试以及用户导入他人的目录规则，Legado增加目录规则的网络导入功能。点击 网络导入 的输入框，可以通过内置的链接导入在线规则。（在线规则优先比内置的规则更加激进，但适配了更多类型的标题格式，用户需根据自己的情况选择是否导入）\n- 拆分超长章节 根据目录正则分章时若单章超过三万字左右将自动分为多章\n\n### 操作区\n![Functions][example]\n- 按钮① 选中时表示当前书籍使用的目录正则 如果Legado的自动识别的目录情况不太理想，用户可以手动点击各个规则前面的按钮，临时对本书启用该规则，该按钮仅**针对当前书籍生效**。\n- 按钮组② 左边的开关被点亮时，表示该规则将会在自动识别目录时尝试进行匹配，该规则针对**所有TXT书籍生效**。开启后会对所有的TXT格式的书籍启用当前规则扫描符合条件的标题格式；中间的按钮表示编辑当前规则，当识别出的目录与你所期望的不一致时，可以修改当前规则以适应你所导入的书籍；右边的按钮表示删除当前规则，当用户不需要当前规则时可直接删除。（默认的规则删除后可点击 导入默认规则 按钮恢复）\n- 按钮组③ 在当前界面进行操作后，需要点击确认按钮使得选择生效。\n\n[example]:data:image/jpg;base64,/9j/4RZnRXhpZgAATU0AKgAAAAgADAEAAAMAAAABBDgAAAEBAAMAAAABCWAAAAECAAMAAAADAAAAngEGAAMAAAABAAIAAAESAAMAAAABAAEAAAEVAAMAAAABAAMAAAEaAAUAAAABAAAApAEbAAUAAAABAAAArAEoAAMAAAABAAIAAAExAAIAAAAiAAAAtAEyAAIAAAAUAAAA1odpAAQAAAABAAAA7AAAASQACAAIAAgACvyAAAAnEAAK/IAAACcQQWRvYmUgUGhvdG9zaG9wIENDIDIwMTkgKFdpbmRvd3MpADIwMjA6MTE6MjQgMTY6NDQ6MjIAAAAABJAAAAcAAAAEMDIyMaABAAMAAAAB//8AAKACAAQAAAABAAABLKADAAQAAAABAAACWAAAAAAAAAAGAQMAAwAAAAEABgAAARoABQAAAAEAAAFyARsABQAAAAEAAAF6ASgAAwAAAAEAAgAAAgEABAAAAAEAAAGCAgIABAAAAAEAABTdAAAAAAAAAEgAAAABAAAASAAAAAH/2P/tAAxBZG9iZV9DTQAB/+4ADkFkb2JlAGSAAAAAAf/bAIQADAgICAkIDAkJDBELCgsRFQ8MDA8VGBMTFRMTGBEMDAwMDAwRDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAENCwsNDg0QDg4QFA4ODhQUDg4ODhQRDAwMDAwREQwMDAwMDBEMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwM/8AAEQgAoABQAwEiAAIRAQMRAf/dAAQABf/EAT8AAAEFAQEBAQEBAAAAAAAAAAMAAQIEBQYHCAkKCwEAAQUBAQEBAQEAAAAAAAAAAQACAwQFBgcICQoLEAABBAEDAgQCBQcGCAUDDDMBAAIRAwQhEjEFQVFhEyJxgTIGFJGhsUIjJBVSwWIzNHKC0UMHJZJT8OHxY3M1FqKygyZEk1RkRcKjdDYX0lXiZfKzhMPTdePzRieUpIW0lcTU5PSltcXV5fVWZnaGlqa2xtbm9jdHV2d3h5ent8fX5/cRAAICAQIEBAMEBQYHBwYFNQEAAhEDITESBEFRYXEiEwUygZEUobFCI8FS0fAzJGLhcoKSQ1MVY3M08SUGFqKygwcmNcLSRJNUoxdkRVU2dGXi8rOEw9N14/NGlKSFtJXE1OT0pbXF1eX1VmZ2hpamtsbW5vYnN0dXZ3eHl6e3x//aAAwDAQACEQMRAD8AfHw7cisvqc0vD21Np929znhzqwz2+l7vSs+lb/3xFb0u91T7fUrAYz1IDt0j0vtf0mbm/wA0mdhZWO91TsjHpsYSHs+10tcHDcxzXt9b6bdz2Jw3MDS0Z2PtIII+2UwQQWO/w37rluy5rFZrmMP1nD+LzkPh/M0L5LmSe8cWXhl/zWTukZDHUB9jGfaZ2F27SNn0trHe39J9Nns9iiemX/ZnZQsrNbN8gF0n0ztdt9qkHdQa4OHUKQ5o2tP22mQPAfplEsy3NLTm45a4HcPtlEEHV/8Ahvzvz0BzOPS+Yw+PrguPw/mNa5LmdtP1WX/vWnqlKvY3Q+o5e/7IKcn049T0r6X7Znbv22+3dtR/+avX/wDuL/4LV/6UUw5jAdsuP/Hi1TyfNRNSwZYnsccwf+i5UqdFNmRcymuN1hDRJgamO60v+avX/wDuL/4LV/6USH1W+sAIIxYIMgi2uQR/1xA8xhrTLj/xoqHK57F4MhHX0S/71y3Nc1xa7QjnUH8WqRYWGskg72h+naSRB/zVpf8ANXr/AP3FH/blX/pROfqz9YBDn40hg0m2sw0e6B+k+ikeYw1/O4/8aKRyue/5nJvp6JP/0Oqqu62y9rcJpON9qy93tJbP2nI2y4Mcz32+zI9W2r0sf9LR+lV+/K+szbLxRiVWVi2tuOS4CaiX+vZZN/5rfSd9Fik/p+HXcaxn34773usbQ28Ml1j3WP8ATrI3e+170T9jO/7m5v8A29/5gmjiAArYVu2sxw5JGXEI2Sfk9XqPF6kV+V9Y2m70MStways0EkEusJ/WGbfXr9rWO/RbvT/mrP8ASVpOPXMg203VVV172uqfOhDDZZts/SWWena+nG3/AKP+Zyrf9Eifsj/u9mf9vf8AmCX7I/7vZn/b3/mCVy7fixiGIEEZNjfyFp9Fwbsbq+T6p2FmMwtra8vBF+TnZP6R7wHPsq/m2v8A+uf4Rbiq4mBXi2WWi226y1rGOfc7edtZe5jW6N/OusVpKAoV5q5nJ7mQyBvSIuq2j6v+e431kyPrHRi2v6LS2wtrB3BofYHbv0hrqe73vZX/ADTPSt3oPR8n6w3fV1t3Um2V55uaA4Vt9Y45trbZc/G2bPWbSb/8B9Bnqekt2yyuqt1tr2111gufY8hrWgfSc5zvotaoNy8V+P8Aa231uxiNwvD2msidu71Z9P6Scg5P1XB7cd/5yvX/AHeJBiPzndN3vB+2bbNgtaASQX/Z/VY30G+5vpb/AOj/APF0IPSbOpv6dc7qYIul+zc0Mds2B3uaza3+dNjG+yv2f6T+es0K3stY19Tg9jxLXtILSD3a5vtcomyuzHfZU5r2FrwHNIcJAc13ub+64JMT/9H0PIwX2vt22NbVkbBc0s3P9nt/RWbhs9v7zH+lZ+mq96qZP1aw8l97n33t+02susDCwasNjmt3bHO2/pf66uX5rqnWltQfVj7Te8vDXDcN36KrafV2sO76dX+jp9SxVL/rL0rHsurtc8Ox7a6Hw0GX2F7GBnu93uqckp1TqZSWVkfWXpmO6wWiwNr9OX7WwfVLNu39Ju/Rtursu9v6KtSq+sOBbbZUxtpfTYyl4DQfdZvj6D3/AEW1u9v85/omWWJKdNAyc7BxNv2vJqx98lvqvayY52+o5viiU2suqZaydrxInnw7bmrA+smPZkZrK6sd+RYcSwM9IgOYTbT+m2ufX6uz6bK/9Iz07fTostTZkgWGblsccmQRmSI0SSKG395h9YMb6t9bqey3q1FFpYGNcLqnNBY71GF1bnfvfTa17N6j0nH6H0voo6ZX1nFss9duSbnWVlvqMsrva1tHrfzX6Bjf53/hFtVX/s/posyWOa1rjFctc5jHvd6FRdu2OdVWWMdssf8AyPU+mj5OWKcYXtb6m91TK2aNJNz2U17i76Hut96XqrcfZ/6EvlmjwnFU5YwduOHTr/NObTm9BZgOwrep4trbBaLX+tWJ9YvfZ7X2Wf6X891n8tQ6Q/ouDi2YeL1DGyLb3OeBW+sOc41tpaxldb3ud7alp157X4LswsIbWLC5ggn9EXss2bvT+l6X6Pf6f9hZlvU2dU6bXe2l1OzOxqy15YdfVotDmuqc/wDMsYkeIC7H2f2rIDFOQiIzHEavjifw9t//0vSn42PZa26ypr7WRseQCRB3N/zXe5insZJO1snUmBJKpZTcs3v9MW7iGfZXMdFTT/hftTJ93u/nPUa/9B/RP1hVrx9Z/Wv+znH9M3V/Z98CKZf6+/2uc5+z0v8AydaSnVNdZkljTPPtH9ycNYDIa0EAAEAcDj8qycwfWl7b2YTsat7mVjHfaAQ14c12Q9+3duY9j7K6m+n+j9Fn+l/R2Mk9aN2I7HbU2rUZjHETMs2urPu/RbfV/wCESU3xAEAQBoAFUyun/aMhmSzIuxrmMdVupLNWOc2yHC+q/wDOr/NVK7/nQWA1egHGh0zE+sWv9Pc125v6N3pb9lnpKeI36yfaa35bqPs7q6/UrYBLbB6X2k7vpbbP1j0tn/BoEA7roTlA3Hfbvv4SS/svImf2llk+foEfccVNb0iy6s13dQyba3fSY8Y7mnv7mPxC1Wsv7UaCMT25EiC6NsT7pLw5n0VHOGYcKMfccjdVu9Eta4t31/avROR+jb+h9Xbu/wCrQ4R4/aV/vz7Q/wDC8f8A3rXr6TbUwV1dQyq62/RYwY7Wj4MbibVAdFZXWB9qvdVS4XCiKWVl9Z9Wve3Hx6XfzjG/nqzjjPPTS2yWZhbaGGwtc4GX/ZvUczfU/az0v+/oPS29UbgX/tRznXkvLN5rLgzY3l2M1lf876qXCPH7Sr359OEeUMYP28L/AP/T9JsyceuxtVjw17o0M6bjsr3ujbX6r/ZV6n86/wDm1M2Vjl7RBDTLhyfot/rIF2H6jrALCyrIgZFYAO7aNntefdV6lY9K3+R/Nejb+lVR31a6S+3Iuexzn5VzMi33R763Gxv0Wtds93vZu+gkp0jZWOXtEan3Didvj+8n3skje2QYI3DQ/urKyfqv0jJ3+ox82hrbCHD3BhqNQdLdv6P7OxTP1c6SbTd6bhYbWXbg4iHVt9JrWx7Wsc3fu2/6R6SnRZZW+dj2vjna4GP834qSo9N6Nh9Nn7Nv1Lz7yD/Oel6n0Ws/7j1K8kpi97GDc9wY3iXEAT8XJrrqcet1t9jaq2xue8hrRJ2t1P7znbWqGTjMyaXUWk+k4gkN0Jg7gN3uSyMVl2OMcOdUGmtzHMglpqcy2r+cD2u91XuSUzZfTZSL2WNdSQXCwEbYH0ju/kodeVi5WNZbi3V5FYD2l9Tg9sgGW7mF3uTVYVVeG7Dc51tbxYHufG53ql77Zgf8K5Cw+nVdPw7qanvs9Tc9z7ILidjahwP3KmJKf//U9DyM2yp9pa1hrxgw2tc6LHep9H0G/R/kU7v6Tkfq/wCi/nFUs+smOy3JqGLkWHFuZQ4sDHbnWONbXVtFnqbdzfz2rUdVW97bHMa59c7HloLmzzsd9JilJ8UlOVmfWHGw33NfRc/7O9tb3NAj3B79zTu+j+icms+sVFdVlr8XI21Xeho0Eklr7W2c+2t3p/8AgtS15PiluPikpx2fWbEfaK/RtAOT9l3nZtB949VxDvbX7P8AjH71c6Z1FnUsUZLKrKBuLTXcAHAgNd+aXfvbf+MVzc7xTSTykpxvrF9Y2dDpc847r3BjXNM7GS53pNDnw76P+EUOlfWX9o9CHVfs4qeLm4zq3v21lzrK8f1WX7Hfof03+i+n+hW25rXsdW8B7HgtcxwkEHQtc0/SUW0U11ChlbGUtG0VBoDAP3fTjZtSZDLH7fDwfrL/AJzi/R7cDXoz/W6c7OFc7W2n02O3Sai9hDHuFf0/S/wjGf8ACIPSuqHqnT78g1CksL69rX+oD+jbbua4spd9G3/R/wDgfvWg0BoDWgNDRAA0AA8IUXta2h7WgNaGO0Gg1B8EmN//1e/yqMp97nMa9zjs+z3Ns2tpj+c9SncPU93v9rLftH9Gt9Jijk43XLH2mjLZUw2tdSyBpWAWWUPs9F387/P+p/g/5n/hFatzMaqz03uIcNu4hri1u4wz1bGjZVu/lp7M3CqLhZk1MLHBrw6xrdrnasY/c72vfHtSU0aMX6w+ow5GcwsGS572ta0zjx7KP5ir9Jv9v0voe/1N7ErMb6xm1jq8yoMFxc5hHNMscyufSd72s3s/9Wfo7tmfgVBxtyqawx21++xrYdqdjtzvp+x/tSfn4FZeLMqlhqE2h1jRtE+nNnu9n6T9H/XSU0a8T6xNZQHZ9dhZY43ktAL65r2NBbX9Juy783/Df8Glg431jrsxnZuXVaxhf9qYIl4cGirY8Y9X808Pfs21/wDGrV0Oo1B4ISSU431kwvrBlY1g6Pkik+mAKw703ucHbrNlu32vsr9jH+pX6aD0fA6/R9XG43UbH2Z3rNeGi6LG0C2t9mMcxrvdZ6Tb/wDC/wCE9H1VrZ3U+ndPrdbnZDKGsbvO4+7bO321tl7/AHfR2qGP1jpuVgDqOPf6uKXbA9rXl28uFLavR2+v6rrHsb6fp70mYzyezw8A4OL5+H9L93jVi05remejY8syi2wNeXbywuL/AEP0j/V3ekx1f0/X/wCMvQek43UMfp11fULDba4vczc82FrCxvt9R0v/AJ31X/T/AO2/5mu7XlY9mOckPDaQHFz3ywN2SLfVFux1XpbXep6n82h0ZuJm4ttuJaLWNDmOIkEODd21zXhr2+1zXt/4P9J9BJhf/9b0a7Crte8myxrLY9appAY/b7ffLXP9zP0dnpvr9StCs6RgWPte5tm66xtth9V/02Eurc33ezY538239Eo5WfZTe9jXVN9PZtoeD6l2/wD0BDh/xTPZb+m/nPTrQ7es20Pu39PyX1Uuc0PpYXuO07QTXtr9trd11fpW3s9L+e9G2z0klJLuhdJufZY+gh9zxbY5j3sl43+/2OH+lsSs6H0i31N+M0m2sVPdLt20EPbtfO5r2vYx/qfT3qFfWXWOhvT8uHt31PLBsI2h7dz93s3v/RpmdatLXA9OyfVYCSA32OIn21XPFbrPa3d/Nf8AF70lOk0Na0NaIa0AADsBoE6zX9YvLHHH6bl2WAw1r2itrj+d7/0m3b/KYp09TtutbUMDJql7q3PtaGtDmbN7twLt1Wx1jqrv5u70vTYkpbq3Qum9XqfXmMdue0M9Wtxa4bTvY5v5jnVu+jvYodP+r+B07pg6biusbX6gvNpINhta5lzbXHZ6ftdTV7fS9PYgfWTruX0fFttx8I5G2sPFp3em0l2z9J6bT7Kfp2/pK0Lo/wBY8rP+ro6rfVXjWes2kvduFJaba6HZTZdv9Gv1Xf4X6dP86kzmOb2OIyPs8QFcX6X912KMOijE+yNl1MOadx1IeXOs1Zs2/wA476H0EPFwMfp+HbRj7trg55LjJLizZ4N/NY381Ni5tl/TTmNYLHhthYxsgP8ATL2M2x6231vT/N9f/rqD0jqV/UunXZF1TKnNL2N9NxLXDYLNw3tbt2+p6X/W/wDraTA//9f06DzHHfwSkqhlYVtt7ntYxxfs9LIc4h9G36XpMA/677P57+ZyP0KhdgdWL7rMbqPpOe5xY19fqM2k7mB1b3ba3VM/QM9DZvr/AEl2++z9GlOlJSk+KzGYHWpm3qctcw7621NHv2hvsuaGvZVuH5jPU+n/ANZgel9XNrXnqTtrMl9xYNwBqe5jm43OzbU1rtm+u1JTrSUtVVwsXJxwRdkOyPaAC6eQ6x+73fyHsZ/rUrSSlapCdAO2gAQMum++g1Uv9F5IItBIgA9vTLX+5Qz8e+/CNFZD7N1RO9xrDwx9dlrHvqa9zPWYx7foJKbRmdeVG2fTsnnY7n4FVsbFyGdNOLY8MuLbGh9bnEM3l5q9Owiqz9C17Nn0EDpWDlYHT7qcq31XuL3gh9lgaCxrdrX3/pPptfZ/bSU//9D05JJJJSkkkklKWc+/qV+fkY2LbTRXjNqP6Sl1rnGwOd9JuRj7du391aKyzdbh9Vy7XYt91d7aTW+lm8HYHteD7vbymy6dr1ryZsAv3KETIQ9AkIy9XuY/0Z+n5ONN6PXP+5mN/wCwr/8A3tWfm5/XsawNrfRewW10WWDHLQLLfT9KprTml253rV+/+aUxl5oM7c4/pfUj0D9Cd3o63FNmDp+be2/I6bnGxogFrHMkTu2v9Oxu7hMlVaE35ybGIT4v1mOJj4Y8P/oDbpHWb6a7683G2Wsa9s4jwYcN7f8Atb5qF13VMa+irIuouryvVrIZQ6tzdtVt24Pdk3/6P9xBdmZfqWOprzaqnNDaqRj+2uAG+yLWqWRkW5uViObi5FTcc3WWvtrDGgGi2rkv/wBI9qOmlE3Y6y7rRGZMuOEBDhyfoYYn5JcHqjH99//Z/+0eiFBob3Rvc2hvcCAzLjAAOEJJTQQEAAAAAAAHHAIAAAIAAAA4QklNBCUAAAAAABDo8VzzL8EYoaJ7Z63FZNW6OEJJTQQ6AAAAAADXAAAAEAAAAAEAAAAAAAtwcmludE91dHB1dAAAAAUAAAAAUHN0U2Jvb2wBAAAAAEludGVlbnVtAAAAAEludGUAAAAAQ2xybQAAAA9wcmludFNpeHRlZW5CaXRib29sAAAAAAtwcmludGVyTmFtZVRFWFQAAAABAAAAAAAPcHJpbnRQcm9vZlNldHVwT2JqYwAAAAVoIWg3i75/bgAAAAAACnByb29mU2V0dXAAAAABAAAAAEJsdG5lbnVtAAAADGJ1aWx0aW5Qcm9vZgAAAAlwcm9vZkNNWUsAOEJJTQQ7AAAAAAItAAAAEAAAAAEAAAAAABJwcmludE91dHB1dE9wdGlvbnMAAAAXAAAAAENwdG5ib29sAAAAAABDbGJyYm9vbAAAAAAAUmdzTWJvb2wAAAAAAENybkNib29sAAAAAABDbnRDYm9vbAAAAAAATGJsc2Jvb2wAAAAAAE5ndHZib29sAAAAAABFbWxEYm9vbAAAAAAASW50cmJvb2wAAAAAAEJja2dPYmpjAAAAAQAAAAAAAFJHQkMAAAADAAAAAFJkICBkb3ViQG/gAAAAAAAAAAAAR3JuIGRvdWJAb+AAAAAAAAAAAABCbCAgZG91YkBv4AAAAAAAAAAAAEJyZFRVbnRGI1JsdAAAAAAAAAAAAAAAAEJsZCBVbnRGI1JsdAAAAAAAAAAAAAAAAFJzbHRVbnRGI1B4bEBSAAAAAAAAAAAACnZlY3RvckRhdGFib29sAQAAAABQZ1BzZW51bQAAAABQZ1BzAAAAAFBnUEMAAAAATGVmdFVudEYjUmx0AAAAAAAAAAAAAAAAVG9wIFVudEYjUmx0AAAAAAAAAAAAAAAAU2NsIFVudEYjUHJjQFkAAAAAAAAAAAAQY3JvcFdoZW5QcmludGluZ2Jvb2wAAAAADmNyb3BSZWN0Qm90dG9tbG9uZwAAAAAAAAAMY3JvcFJlY3RMZWZ0bG9uZwAAAAAAAAANY3JvcFJlY3RSaWdodGxvbmcAAAAAAAAAC2Nyb3BSZWN0VG9wbG9uZwAAAAAAOEJJTQPtAAAAAAAQAEgAAAABAAEASAAAAAEAAThCSU0EJgAAAAAADgAAAAAAAAAAAAA/gAAAOEJJTQQNAAAAAAAEAAAAHjhCSU0EGQAAAAAABAAAAB44QklNA/MAAAAAAAkAAAAAAAAAAAEAOEJJTScQAAAAAAAKAAEAAAAAAAAAAThCSU0D9QAAAAAASAAvZmYAAQBsZmYABgAAAAAAAQAvZmYAAQChmZoABgAAAAAAAQAyAAAAAQBaAAAABgAAAAAAAQA1AAAAAQAtAAAABgAAAAAAAThCSU0D+AAAAAAAcAAA/////////////////////////////wPoAAAAAP////////////////////////////8D6AAAAAD/////////////////////////////A+gAAAAA/////////////////////////////wPoAAA4QklNBAAAAAAAAAIAADhCSU0EAgAAAAAADgAAAAAAAAAAAAAAAAAAOEJJTQQwAAAAAAAHAQEBAQEBAQA4QklNBC0AAAAAAAYAAQAAAAI4QklNBAgAAAAAABAAAAABAAACQAAAAkAAAAAAOEJJTQQeAAAAAAAEAAAAADhCSU0EGgAAAAADlwAAAAYAAAAAAAAAAAAAAlgAAAEsAAAAMQBTAGMAcgBlAGUAbgBzAGgAbwB0AF8AMgAwADIAMAAtADEAMQAtADIANAAtADEANgAtADMANAAtADQANQAtADUAMQA0AF8AaQBvAC4AbABlAGcAYQBkAG8ALgBhAHAAcAAuAAAAAQAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAEsAAACWAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAAAAAAAAAAAAAABAAAAABAAAAAAAAbnVsbAAAAAIAAAAGYm91bmRzT2JqYwAAAAEAAAAAAABSY3QxAAAABAAAAABUb3AgbG9uZwAAAAAAAAAATGVmdGxvbmcAAAAAAAAAAEJ0b21sb25nAAACWAAAAABSZ2h0bG9uZwAAASwAAAAGc2xpY2VzVmxMcwAAAAFPYmpjAAAAAQAAAAAABXNsaWNlAAAAEgAAAAdzbGljZUlEbG9uZwAAAAAAAAAHZ3JvdXBJRGxvbmcAAAAAAAAABm9yaWdpbmVudW0AAAAMRVNsaWNlT3JpZ2luAAAADWF1dG9HZW5lcmF0ZWQAAAAAVHlwZWVudW0AAAAKRVNsaWNlVHlwZQAAAABJbWcgAAAABmJvdW5kc09iamMAAAABAAAAAAAAUmN0MQAAAAQAAAAAVG9wIGxvbmcAAAAAAAAAAExlZnRsb25nAAAAAAAAAABCdG9tbG9uZwAAAlgAAAAAUmdodGxvbmcAAAEsAAAAA3VybFRFWFQAAAABAAAAAAAAbnVsbFRFWFQAAAABAAAAAAAATXNnZVRFWFQAAAABAAAAAAAGYWx0VGFnVEVYVAAAAAEAAAAAAA5jZWxsVGV4dElzSFRNTGJvb2wBAAAACGNlbGxUZXh0VEVYVAAAAAEAAAAAAAlob3J6QWxpZ25lbnVtAAAAD0VTbGljZUhvcnpBbGlnbgAAAAdkZWZhdWx0AAAACXZlcnRBbGlnbmVudW0AAAAPRVNsaWNlVmVydEFsaWduAAAAB2RlZmF1bHQAAAALYmdDb2xvclR5cGVlbnVtAAAAEUVTbGljZUJHQ29sb3JUeXBlAAAAAE5vbmUAAAAJdG9wT3V0c2V0bG9uZwAAAAAAAAAKbGVmdE91dHNldGxvbmcAAAAAAAAADGJvdHRvbU91dHNldGxvbmcAAAAAAAAAC3JpZ2h0T3V0c2V0bG9uZwAAAAAAOEJJTQQoAAAAAAAMAAAAAj/wAAAAAAAAOEJJTQQRAAAAAAABAQA4QklNBBQAAAAAAAQAAAAKOEJJTQQMAAAAABT5AAAAAQAAAFAAAACgAAAA8AAAlgAAABTdABgAAf/Y/+0ADEFkb2JlX0NNAAH/7gAOQWRvYmUAZIAAAAAB/9sAhAAMCAgICQgMCQkMEQsKCxEVDwwMDxUYExMVExMYEQwMDAwMDBEMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMAQ0LCw0ODRAODhAUDg4OFBQODg4OFBEMDAwMDBERDAwMDAwMEQwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAz/wAARCACgAFADASIAAhEBAxEB/90ABAAF/8QBPwAAAQUBAQEBAQEAAAAAAAAAAwABAgQFBgcICQoLAQABBQEBAQEBAQAAAAAAAAABAAIDBAUGBwgJCgsQAAEEAQMCBAIFBwYIBQMMMwEAAhEDBCESMQVBUWETInGBMgYUkaGxQiMkFVLBYjM0coLRQwclklPw4fFjczUWorKDJkSTVGRFwqN0NhfSVeJl8rOEw9N14/NGJ5SkhbSVxNTk9KW1xdXl9VZmdoaWprbG1ub2N0dXZ3eHl6e3x9fn9xEAAgIBAgQEAwQFBgcHBgU1AQACEQMhMRIEQVFhcSITBTKBkRShsUIjwVLR8DMkYuFygpJDUxVjczTxJQYWorKDByY1wtJEk1SjF2RFVTZ0ZeLys4TD03Xj80aUpIW0lcTU5PSltcXV5fVWZnaGlqa2xtbm9ic3R1dnd4eXp7fH/9oADAMBAAIRAxEAPwB8fDtyKy+pzS8PbU2n3b3OeHOrDPb6Xu9Kz6Vv/fEVvS73VPt9SsBjPUgO3SPS+1/SZub/ADSZ2FlY73VOyMemxhIez7XS1wcNzHNe31vpt3PYnDcwNLRnY+0ggj7ZTBBBY7/DfuuW7LmsVmuYw/WcP4vOQ+H8zQvkuZJ7xxZeGX/NZO6RkMdQH2MZ9pnYXbtI2fS2sd7f0n02ez2KJ6Zf9mdlCys1s3yAXSfTO1232qQd1Brg4dQpDmja0/baZA8B+mUSzLc0tObjlrgdw+2UQQdX/wCG/O/PQHM49L5jD4+uC4/D+Y1rkuZ20/VZf+9aeqUq9jdD6jl7/sgpyfTj1PSvpftmdu/bb7d21H/5q9f/AO4v/gtX/pRTDmMB2y4/8eLVPJ81E1LBliexxzB/6LlSp0U2ZFzKa43WENEmBqY7rS/5q9f/AO4v/gtX/pRIfVb6wAgjFggyCLa5BH/XEDzGGtMuP/GiocrnsXgyEdfRL/vXLc1zXFrtCOdQfxapFhYaySDvaH6dpJEH/NWl/wA1ev8A/cUf9uVf+lE5+rP1gEOfjSGDSbazDR7oH6T6KR5jDX87j/xopHK57/mcm+nok//Q6qq7rbL2twmk432rL3e0ls/acjbLgxzPfb7Mj1bavSx/0tH6VX78r6zNsvFGJVZWLa245LgJqJf69lk3/mt9J30WKT+n4ddxrGffjvve6xtDbwyXWPdY/wBOsjd77XvRP2M7/ubm/wDb3/mCaOIACthW7azHDkkZcQjZJ+T1eo8XqRX5X1jabvQxK3BrKzQSQS6wn9YZt9ev2tY79Fu9P+as/wBJWk49cyDbTdVVXXva6p86EMNlm2z9JZZ6dr6cbf8Ao/5nKt/0SJ+yP+72Z/29/wCYJfsj/u9mf9vf+YJXLt+LGIYgQRk2N/IWn0XBuxur5PqnYWYzC2try8EX5Odk/pHvAc+yr+ba/wD65/hFuKriYFeLZZaLbbrLWsY59zt521l7mNbo3866xWkoChXmrmcnuZDIG9Ii6raPq/57jfWTI+sdGLa/otLbC2sHcGh9gdu/SGup7ve9lf8ANM9K3eg9HyfrDd9XW3dSbZXnm5oDhW31jjm2ttlz8bZs9ZtJv/wH0Gep6S3bLK6q3W2vbXXWC59jyGtaB9JznO+i1qg3LxX4/wBrbfW7GI3C8PaayJ27vVn0/pJyDk/VcHtx3/nK9f8Ad4kGI/Od03e8H7Zts2C1oBJBf9n9VjfQb7m+lv8A6P8A8XQg9Js6m/p1zupgi6X7NzQx2zYHe5rNrf502Mb7K/Z/pP56zQrey1jX1OD2PEte0gtIPdrm+1yibK7Md9lTmvYWvAc0hwkBzXe5v7rgkxP/0fQ8jBfa+3bY1tWRsFzSzc/2e39FZuGz2/vMf6Vn6ar3qpk/VrDyX3uffe37Tay6wMLBqw2Oa3dsc7b+l/rq5fmuqdaW1B9WPtN7y8NcNw3foqtp9Xaw7vp1f6On1LFUv+svSsey6u1zw7HtrofDQZfYXsYGe73e6pySnVOplJZWR9ZemY7rBaLA2v05ftbB9Us27f0m79G26uy72/oq1Kr6w4FttlTG2l9NjKXgNB91m+PoPf8ARbW72/zn+iZZYkp00DJzsHE2/a8mrH3yW+q9rJjnb6jm+KJTay6plrJ2vEiefDtuasD6yY9mRmsrqx35FhxLAz0iA5hNtP6ba59fq7Ppsr/0jPTt9Oiy1NmSBYZuWxxyZBGZIjRJIobf3mH1gxvq31up7LerUUWlgY1wuqc0FjvUYXVud+99NrXs3qPScfofS+ijplfWcWyz125JudZWW+oyyu9rW0et/NfoGN/nf+EW1Vf+z+mizJY5rWuMVy1zmMe93oVF27Y51VZYx2yx/wDI9T6aPk5Ypxhe1vqb3VMrZo0k3PZTXuLvoe633peqtx9n/oS+WaPCcVTljB244dOv805tOb0FmA7Ct6ni2tsFotf61Yn1i99ntfZZ/pfz3Wfy1DpD+i4OLZh4vUMbItvc54Fb6w5zjW2lrGV1ve53tqWnXntfguzCwhtYsLmCCf0ReyzZu9P6Xpfo9/p/2FmW9TZ1Tptd7aXU7M7GrLXlh19Wi0Oa6pz/AMyxiR4gLsfZ/asgMU5CIjMcRq+OJ/D23//S9KfjY9lrbrKmvtZGx5AJEHc3/Nd7mKexkk7WydSYEkqllNyze/0xbuIZ9lcx0VNP+F+1Mn3e7+c9Rr/0H9E/WFWvH1n9a/7Ocf0zdX9n3wIpl/r7/a5zn7PS/wDJ1pKdU11mSWNM8+0f3Jw1gMhrQQAAQBwOPyrJzB9aXtvZhOxq3uZWMd9oBDXhzXZD37d25j2Psrqb6f6P0Wf6X9HYyT1o3YjsdtTatRmMcRMyza6s+79Ft9X/AIRJTfEAQBAGgAVTK6f9oyGZLMi7GuYx1W6ks1Y5zbIcL6r/AM6v81Urv+dBYDV6AcaHTMT6xa/09zXbm/o3elv2Wekp4jfrJ9prfluo+zurr9StgEtsHpfaTu+lts/WPS2f8GgQDuuhOUDcd9u+/hJL+y8iZ/aWWT5+gR9xxU1vSLLqzXd1DJtrd9Jjxjuae/uY/ELVay/tRoIxPbkSILo2xPukvDmfRUc4Zhwox9xyN1W70S1ri3fX9q9E5H6Nv6H1du7/AKtDhHj9pX+/PtD/AMLx/wDetevpNtTBXV1DKrrb9FjBjtaPgxuJtUB0VldYH2q91VLhcKIpZWX1n1a97cfHpd/OMb+erOOM89NLbJZmFtoYbC1zgZf9m9RzN9T9rPS/7+g9Lb1RuBf+1HOdeS8s3msuDNjeXYzWV/zvqpcI8ftKvfn04R5Qxg/bwv8A/9P0mzJx67G1WPDXujQzpuOyve6Ntfqv9lXqfzr/AObUzZWOXtEENMuHJ+i3+sgXYfqOsAsLKsiBkVgA7to2e1591XqVj0rf5H816Nv6VVHfVrpL7ci57HOflXMyLfdHvrcbG/Ra12z3e9m76CSnSNlY5e0RqfcOJ2+P7yfeySN7ZBgjcND+6srJ+q/SMnf6jHzaGtsIcPcGGo1B0t2/o/s7FM/VzpJtN3puFhtZduDiIdW30mtbHtaxzd+7b/pHpKdFllb52Pa+OdrgY/zfipKj03o2H02fs2/UvPvIP856XqfRaz/uPUrySmL3sYNz3BjeJcQBPxcmuupx63W32NqrbG57yGtEna3U/vOdtaoZOMzJpdRaT6TiCQ3QmDuA3e5LIxWXY4xw51Qaa3McyCWmpzLav5wPa73Ve5JTNl9NlIvZY11JBcLARtgfSO7+Sh15WLlY1luLdXkVgPaX1OD2yAZbuYXe5NVhVV4bsNznW1vFge58bneqXvtmB/wrkLD6dV0/Dupqe+z1Nz3PsguJ2NqHA/cqYkp//9T0PIzbKn2lrWGvGDDa1zosd6n0fQb9H+RTu/pOR+r/AKL+cVSz6yY7LcmoYuRYcW5lDiwMdudY41tdW0Wept3N/PatR1Vb3tscxrn1zseWgubPOx30mKUnxSU5WZ9YcbDfc19Fz/s721vc0CPcHv3NO76P6Jyaz6xUV1WWvxcjbVd6GjQSSWvtbZz7a3en/wCC1LXk+KW4+KSnHZ9ZsR9or9G0A5P2Xedm0H3j1XEO9tfs/wCMfvVzpnUWdSxRksqsoG4tNdwAcCA135pd+9t/4xXNzvFNJPKSnG+sX1jZ0OlzzjuvcGNc0zsZLnek0OfDvo/4RQ6V9Zf2j0IdV+zip4ubjOre/bWXOsrx/VZfsd+h/Tf6L6f6Fbbmtex1bwHseC1zHCQQdC1zT9JRbRTXUKGVsZS0bRUGgMA/d9ONm1JkMsft8PB+sv8AnOL9HtwNejP9bpzs4VztbafTY7dJqL2EMe4V/T9L/CMZ/wAIg9K6oeqdPvyDUKSwvr2tf6gP6Ntu5riyl30bf9H/AOB+9aDQGgNaA0NEADQADwhRe1raHtaA1oY7QaDUHwSY3//V7/Koyn3ucxr3OOz7Pc2za2mP5z1Kdw9T3e/2st+0f0a30mKOTjdcsfaaMtlTDa11LIGlYBZZQ+z0Xfzv8/6n+D/mf+EVq3MxqrPTe4hw27iGuLW7jDPVsaNlW7+WnszcKouFmTUwscGvDrGt2udqxj9zva98e1JTRoxfrD6jDkZzCwZLnva1rTOPHso/mKv0m/2/S+h7/U3sSsxvrGbWOrzKgwXFzmEc0yxzK59J3vazez/1Z+ju2Z+BUHG3KprDHbX77Gth2p2O3O+n7H+1J+fgVl4syqWGoTaHWNG0T6c2e72fpP0f9dJTRrxPrE1lAdn12FljjeS0AvrmvY0Ftf0m7Lvzf8N/waWDjfWOuzGdm5dVrGF/2pgiXhwaKtjxj1fzTw9+zbX/AMatXQ6jUHghJJTjfWTC+sGVjWDo+SKT6YArDvTe5wdus2W7fa+yv2Mf6lfpoPR8Dr9H1cbjdRsfZnes14aLosbQLa32YxzGu91npNv/AML/AIT0fVWtndT6d0+t1udkMoaxu87j7ts7fbW2Xv8Ad9HaoY/WOm5WAOo49/q4pdsD2teXby4Utq9Hb6/qusexvp+nvSZjPJ7PDwDg4vn4f0v3eNWLTmt6Z6NjyzKLbA15dvLC4v8AQ/SP9Xd6THV/T9f/AIy9B6TjdQx+nXV9QsNtri9zNzzYWsLG+31HS/8AnfVf9P8A7b/ma7teVj2Y5yQ8NpAcXPfLA3ZIt9UW7HVeltd6nqfzaHRm4mbi224lotY0OY4iQQ4N3bXNeGvb7XNe3/g/0n0EmF//1vRrsKu17ybLGstj1qmkBj9vt98tc/3M/R2em+v1K0KzpGBY+17m2brrG22H1X/TYS6tzfd7Njnfzbf0SjlZ9lN72NdU309m2h4PqXb/APQEOH/FM9lv6b+c9OtDt6zbQ+7f0/JfVS5zQ+lhe47TtBNe2v22t3XV+lbez0v570bbPSSUku6F0m59lj6CH3PFtjmPeyXjf7/Y4f6WxKzofSLfU34zSbaxU90u3bQQ9u187mva9jH+p9PeoV9ZdY6G9Py4e3fU8sGwjaHt3P3eze/9GmZ1q0tcD07J9VgJIDfY4ifbVc8Vus9rd381/wAXvSU6TQ1rQ1ohrQAAOwGgTrNf1i8sccfpuXZYDDWvaK2uP53v/Sbdv8pinT1O261tQwMmqXurc+1oa0OZs3u3Au3VbHWOqu/m7vS9NiSlurdC6b1ep9eYx257Qz1a3FrhtO9jm/mOdW76O9ih0/6v4HTumDpuK6xtfqC82kg2G1rmXNtcdnp+11NXt9L09iB9ZOu5fR8W23Hwjkbaw8Wnd6bSXbP0nptPsp+nb+krQuj/AFjys/6ujqt9VeNZ6zaS924UlptrodlNl2/0a/Vd/hfp0/zqTOY5vY4jI+zxAVxfpf3XYow6KMT7I2XUw5p3HUh5c6zVmzb/ADjvofQQ8XAx+n4dtGPu2uDnkuMkuLNng381jfzU2Lm2X9NOY1gseG2FjGyA/wBMvYzbHrbfW9P831/+uoPSOpX9S6ddkXVMqc0vY303EtcNgs3De1u3b6npf9b/AOtpMD//1/ToPMcd/BKSqGVhW23ue1jHF+z0shziH0bfpekwD/rvs/nv5nI/QqF2B1Yvusxuo+k57nFjX1+ozaTuYHVvdtrdUz9Az0Nm+v8ASXb77P0aU6UlKT4rMZgdambepy1zDvrbU0e/aG+y5oa9lW4fmM9T6f8A1mB6X1c2teepO2syX3Fg3AGp7mObjc7NtTWu2b67UlOtJS1VXCxcnHBF2Q7I9oALp5DrH7vd/Iexn+tStJKVqkJ0A7aABAy6b76DVS/0Xkgi0EiAD29Mtf7lDPx778I0VkPs3VE73GsPDH12Wse+pr3M9ZjHt+gkptGZ15UbZ9OyedjufgVWxsXIZ004tjwy4tsaH1ucQzeXmr07CKrP0LXs2fQQOlYOVgdPupyrfVe4veCH2WBoLGt2tff+k+m19n9tJT//0PTkkkklKSSSSUpZz7+pX5+RjYttNFeM2o/pKXWucbA530m5GPt27f3VorLN1uH1XLtdi33V3tpNb6Wbwdge14Pu9vKbLp2vWvJmwC/coRMhD0CQjL1e5j/Rn6fk403o9c/7mY3/ALCv/wDe1Z+bn9exrA2t9F7BbXRZYMctAst9P0qmtOaXbnetX7/5pTGXmgztzj+l9SPQP0J3ejrcU2YOn5t7b8jpucbGiAWscyRO7a/07G7uEyVVoTfnJsYhPi/WY4mPhjw/+gNukdZvprvrzcbZaxr2ziPBhw3t/wC1vmoXXdUxr6Ksi6i6vK9WshlDq3N21W3bg92Tf/o/3EF2Zl+pY6mvNqqc0NqpGP7a4Ab7ItapZGRbm5WI5uLkVNxzdZa+2sMaAaLauS//AEj2o6aUTdjrLutEZky44QEOHJ+hhifklweqMf33/9kAOEJJTQQhAAAAAABdAAAAAQEAAAAPAEEAZABvAGIAZQAgAFAAaABvAHQAbwBzAGgAbwBwAAAAFwBBAGQAbwBiAGUAIABQAGgAbwB0AG8AcwBoAG8AcAAgAEMAQwAgADIAMAAxADkAAAABADhCSU0EBgAAAAAABwABAAAAAQEA/+EOxWh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8APD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNS42LWMxNDUgNzkuMTYzNDk5LCAyMDE4LzA4LzEzLTE2OjQwOjIyICAgICAgICAiPiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RFdnQ9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZUV2ZW50IyIgeG1sbnM6ZGM9Imh0dHA6Ly9wdXJsLm9yZy9kYy9lbGVtZW50cy8xLjEvIiB4bWxuczpwaG90b3Nob3A9Imh0dHA6Ly9ucy5hZG9iZS5jb20vcGhvdG9zaG9wLzEuMC8iIHhtbG5zOnhtcD0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wLyIgeG1wTU06RG9jdW1lbnRJRD0iYWRvYmU6ZG9jaWQ6cGhvdG9zaG9wOjM2YTcwM2M3LWNiOWMtN2Q0Zi05MDZmLWZkYmEwNjEzYTc5ZSIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDpiMzIyZjg5NS1mNmJjLTZkNDAtYTY2MS1hYzNlZTY0Nzg5OWMiIHhtcE1NOk9yaWdpbmFsRG9jdW1lbnRJRD0iODU2NzdDMTcyNUEwMDVDMUJCODcwRDIxQTdGMTk2MTciIGRjOmZvcm1hdD0iaW1hZ2UvanBlZyIgcGhvdG9zaG9wOkNvbG9yTW9kZT0iMyIgcGhvdG9zaG9wOklDQ1Byb2ZpbGU9InNSR0IiIHhtcDpDcmVhdGVEYXRlPSIyMDIwLTExLTI0VDE2OjM4OjAyKzA4OjAwIiB4bXA6TW9kaWZ5RGF0ZT0iMjAyMC0xMS0yNFQxNjo0NDoyMiswODowMCIgeG1wOk1ldGFkYXRhRGF0ZT0iMjAyMC0xMS0yNFQxNjo0NDoyMiswODowMCI+IDx4bXBNTTpIaXN0b3J5PiA8cmRmOlNlcT4gPHJkZjpsaSBzdEV2dDphY3Rpb249InNhdmVkIiBzdEV2dDppbnN0YW5jZUlEPSJ4bXAuaWlkOjZkNWJiOTY2LTU5MTYtZTU0Mi1hMGM5LWM3Zjg1YzcxZTY2NCIgc3RFdnQ6d2hlbj0iMjAyMC0xMS0yNFQxNjo0NDoyMiswODowMCIgc3RFdnQ6c29mdHdhcmVBZ2VudD0iQWRvYmUgUGhvdG9zaG9wIENDIDIwMTkgKFdpbmRvd3MpIiBzdEV2dDpjaGFuZ2VkPSIvIi8+IDxyZGY6bGkgc3RFdnQ6YWN0aW9uPSJzYXZlZCIgc3RFdnQ6aW5zdGFuY2VJRD0ieG1wLmlpZDpiMzIyZjg5NS1mNmJjLTZkNDAtYTY2MS1hYzNlZTY0Nzg5OWMiIHN0RXZ0OndoZW49IjIwMjAtMTEtMjRUMTY6NDQ6MjIrMDg6MDAiIHN0RXZ0OnNvZnR3YXJlQWdlbnQ9IkFkb2JlIFBob3Rvc2hvcCBDQyAyMDE5IChXaW5kb3dzKSIgc3RFdnQ6Y2hhbmdlZD0iLyIvPiA8L3JkZjpTZXE+IDwveG1wTU06SGlzdG9yeT4gPHBob3Rvc2hvcDpUZXh0TGF5ZXJzPiA8cmRmOkJhZz4gPHJkZjpsaSBwaG90b3Nob3A6TGF5ZXJOYW1lPSLikaAiIHBob3Rvc2hvcDpMYXllclRleHQ9IuKRoCIvPiA8cmRmOmxpIHBob3Rvc2hvcDpMYXllck5hbWU9IuKRoSIgcGhvdG9zaG9wOkxheWVyVGV4dD0i4pGhIi8+IDxyZGY6bGkgcGhvdG9zaG9wOkxheWVyTmFtZT0i4pGiIiBwaG90b3Nob3A6TGF5ZXJUZXh0PSLikaIiLz4gPC9yZGY6QmFnPiA8L3Bob3Rvc2hvcDpUZXh0TGF5ZXJzPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgICA8P3hwYWNrZXQgZW5kPSJ3Ij8+/+4ADkFkb2JlAGSAAAAAAf/bAIQADAgICAkIDAkJDBELCgsRFQ8MDA8VGBMTFRMTGBEMDAwMDAwRDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAENCwsNDg0QDg4QFA4ODhQUDg4ODhQRDAwMDAwREQwMDAwMDBEMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwMDAwM/8AAEQgCWAEsAwEiAAIRAQMRAf/dAAQAE//EAT8AAAEFAQEBAQEBAAAAAAAAAAMAAQIEBQYHCAkKCwEAAQUBAQEBAQEAAAAAAAAAAQACAwQFBgcICQoLEAABBAEDAgQCBQcGCAUDDDMBAAIRAwQhEjEFQVFhEyJxgTIGFJGhsUIjJBVSwWIzNHKC0UMHJZJT8OHxY3M1FqKygyZEk1RkRcKjdDYX0lXiZfKzhMPTdePzRieUpIW0lcTU5PSltcXV5fVWZnaGlqa2xtbm9jdHV2d3h5ent8fX5/cRAAICAQIEBAMEBQYHBwYFNQEAAhEDITESBEFRYXEiEwUygZEUobFCI8FS0fAzJGLhcoKSQ1MVY3M08SUGFqKygwcmNcLSRJNUoxdkRVU2dGXi8rOEw9N14/NGlKSFtJXE1OT0pbXF1eX1VmZ2hpamtsbW5vYnN0dXZ3eHl6e3x//aAAwDAQACEQMRAD8ADJ8UpPimSXTvGryfFKT4pkklLyfFKT4pkklLyfFKT4pkklLyfFKT4pkklLyfFKT4pkklLyfFKT4pkklLyfFKT4pkklLyfFKT4pkklLyfFKT4pkklLyfFKT4pkklLyfFKT4pkklLyfFKT4pkklLyfFKT4pkklLyfFKT4pkklLyfFKT4pkklLyfFKT4pkklLyfFKT4pkklLyfFF3H7Lz+f/wB9QUX/ALTf2/8AvqZLeH97/uJL4fLk/u/93B//0AK5gM6S5tn7RtuqII9P0Wh0j87duCppHhdNIWKsx8Y7vHQlwyB4RLwl8ru9T6T0XBtfiMvybM0sBpZtbtc5/wDNtLg1ZDcLMdkHFbQ85LfpU7TvECdWrT+tgJ60QASTVUAByTHZa2X9pPS7KWOY7rzMdn23aD6px5cfTDvzr9uz1v8A1Wqkc0oY8ZJ4zlA+f9A/ven/ACTfny8MmXLER9sYSa4B/ODX0er/ACriU9BzLulvz212mwPa2uoMkPYRJuaf3WovSum4t/TLcy3FvzLWXioVY5IIaWh+8gNcj9Pt6jd9WclmK+6yyq+trG1ucXNr2jc1oafbUm6VZTX9XbnXWZFLPtjQHYn85Owact/RpTyZOGYMtRkjEcN8XCf7qseLFxYyI6SwymeOuHiHX1JcTpGDkZVVD+k59DLHQ617iGt0+k72LFx66P2j6Nrd9XqPZsdYKpjcG7r3fQW/0nIwXdTxm15XU3vL/ay6PTJh3877vorO6O+5vVrmMxnZVVj3i5jK22OA3O2vHrA1s2u/eQhOY9yyTUAQJGcf3v8AOJyY4H2aAF5CCYxhLpH9GCc9P6WAT9mq0/8ANjX/AORVHruFj4Oc2jGBax1NdhaXb4L53RZ+c1dGzGazHza78jBdex7K6LBRWRWXktYMtjW7Get9D+Qsj6zX5IfXh3NcTSAH3vpbUHubIb9ncwbvQa0/R3JuDLI5QASRrdyn2jLi9a7mcMY4CSADoRUcenqlHgPto20fVsmkOq6iwZBAre70w10nZuYfzmtcl1HG+rmHfkYgGY7IplrXTXsL49vg/YpZX9C+r39r/wA+sU/rD1Qfb87E+xY07iz7R6Z9Xhvv9Td/OJ8eMziAZkHjv1/5vJ7fEsmIRxyJjAEe3R4P87i9zhR9L6bhX4FNtuO/Iyb7rK2tbb6TQ2tnrE/Rd+amz+k4YY/IxS+qpuFTmCtx3km1+zY5/wDJarf1bsbZTTSw+/FfkXWl3tY1tlX2enda72fpLHIObm0VUX4lxNOQzp+PiGt4IJtrfusY395u33Ns+g9Azye8QDLQjTf0yn+7/cXCGI8uDKMdQaNAeqOP9/8Av8TR6/i0YnWMnGx2enTWW7GSTEsY4/Sn85yz1q/Wn/l/L+LP/PbFlKzgJOLGSbJhH/otLmQBnygCgJzAA/vKSSSUjEpJJJJSkkkklKSSSSUpJJJJSkkkklKV3pOPRk5FrLhuazHusaJI9zG7mH2qrVYarWWtDXFhDg143NMfvsP0mrd6P1e+7Jua6jFbtxr3gsoY0y1m7aSPzP32KHPKYhLhHTfi4aZ+Vhjlkjxnr8vDxCTz4MgJK7kdVuyaDS+nGY10e6qlrHCNfa9qpKSJkR6hw/XiYpiIPplxDvXCpJJJOWtzpo6Xuu/aO+Nn6H05+l57f/OFTWr0B3UGuyvsVFd5NUWeoYgfm7f3t3+j/PWUFHE+uYvbh63/AM39BlmP1WM1vxa8PD1/f/TUi/8Aab+3/wB9QkTYPS3yZ3Rtn2/R52/vIy3h/e/7iS2Hy5P7v/dwf//RAkkkuneNSOych1ovfa91zY22lxLht+hDz7vanbk5LLzkMusbeSSbg4h5J+l+knd7kJJDhHYdvonilvZ3vfr3S05OTQXGi59Rfo8scWyP5W0+5WMTrHUMHFdjYlnote8WOe0e+QNu3cfbs/sqkkhKEZaSiD5jsmOScTcZGNWND+9u6lP1l65Va2w5TrNhnY+C0+T9u13/AElQblZLHWursdV68+rsJbuBO7adv5qEkgMcBdRiL3odkyzZZVxTkaurPduWdUyX4A6eG1V4/t3+mwNc8t+i65/56H+0M4101Ove6vHeLKWuO4NcPolu79391V0kRjgP0RvxbfpHqo5ch/SO3Dv+iOjq/wDOnr+v62df5Ff/AKTSP1o68WlpyyQRB9lfB/62spJM+74f81D/ABYr/vXMf57J/jybBz8k4A6eCG4wcXua0QXk/wCld/hNn5iPV1vqDLGWucy91dYpZ6zA+GA7xz+fu/PVBJOOOB0MRqSdusvmWDNkFETOgA36R+VLlZN2XkWZOQ7fbaZe6I8uEJJJOAAAA0AWEkkkmydSSpJJJFSkkkklKSSSSUpJJJJSkkkklKSSSSUpTrttqcXVPLHOaWktMS12j2/2lBJAi91AkGxopJJJFSkkkklJKr7qdxpsdWXja7aSJB/NMIaSSFKs1XZSL/2m/t/99QlP1Gen6X5+7dEaRt/eTZbw/vf9xJfD5cn93/u4P//S6P8A5iu/7nD/ALb/APM0v+Yrv+5w/wC2/wDzNC+vWMzL6j0PFsJFeRc+p5boQHuxmO2zu93uRv8AxuOh/wCnyv8APr/9IKU/E+c4pRiRLhrX0R3HF+4vj8A+FjBhy5sksRziUowjDJlr25yxfN78P3Vv+Yrv+5w/7b/8zS/5iu/7nD/tv/zNP/43HQ/9Plf59f8A6QS/8bjof+nyv8+v/wBIJf6R57w+2H/qtH+hPgn+fn/4Tk/+CFv+Yrv+5w/7b/8AM0v+Yrv+5w/7b/8AM0//AI3HQ/8AT5X+fX/6QS/8bjof+nyv8+v/ANIJf6R57w+2H/qtX+hPgn+fn/4Tk/8Aghb/AJiu/wC5w/7b/wDM0v8AmK7/ALnD/tv/AMzT/wDjcdD/ANPlf59f/pBL/wAbjof+nyv8+v8A9IJf6R57w+2H/qtX+hPgn+fn/wCE5P8A4IW/5iu/7nD/ALb/APM0v+Yrv+5w/wC2/wDzNP8A+Nx0P/T5X+fX/wCkEv8AxuOh/wCnyv8APr/9IJf6R57w+2H/AKrV/oT4J/n5/wDhOT/4IW/5iu/7nD/tv/zNL/mK7/ucP+2//M0//jcdD/0+V/n1/wDpBL/xuOh/6fK/z6//AEgl/pHnvD7Yf+q1f6E+Cf5+f/hOT/4IW/5iu/7nD/tv/wAzS/5iu/7nD/tv/wAzT/8AjcdD/wBPlf59f/pBL/xuOh/6fK/z6/8A0gl/pHnvD7Yf+q1f6E+Cf5+f/hOT/wCCFv8AmK7/ALnD/tv/AMzTH6jwQDntBPE18/8Agiy8v6mYD+r19K6dbcXMZ62bda5rm1sOjGMayuvddZ/XVXN6F9W6ut4nR8W7IvstsDMmzeyGT+Y3bT7rP3v3E0/FecG9DXh/Q3/8LZh/xb+FyIEMs5XA5T+qyDgxx/Sn+v8ATxO//wAxXf8Ac4f9t/8AmaX/ADFd/wBzh/23/wCZrE630PA+rHVulZVVtr8d1wstFkOc0Uvqe4t9Ntf5r/3V6K1zXNDmmWuEgjggp8PiXNSMgZcJjXSEt/8AAavNfAuRxQw5Md5ceYSMZEZMX83LgkP5ybyv/MV3/c4f9t/+Zpf8xXf9zh/23/5murSUn3/mf3/+bD/vWp/ozlP83/zp/wDfPKf8xXf9zh/23/5ml/zFd/3OH/bf/ma6tJL7/wAz+/8A82H/AHqv9Gcp/m/+dP8A755T/mK7/ucP+2//ADNL/mK7/ucP+2//ADNdWkl9/wCZ/f8A+bD/AL1X+jOU/wA3/wA6f/fPKf8AMV3/AHOH/bf/AJml/wAxXf8Ac4f9t/8Ama6tJL7/AMz+/wD82H/eq/0Zyn+b/wCdP/vnlP8AmK7/ALnD/tv/AMzS/wCYrv8AucP+2/8AzNdWkl9/5n9//mw/71X+jOU/zf8Azp/988p/zFd/3OH/AG3/AOZpf8xXf9zh/wBt/wDma6tJL7/zP7//ADYf96r/AEZyn+b/AOdP/vnlP+Yrv+5w/wC2/wDzNL/mK7/ucP8Atv8A8zXVpJff+Z/f/wCbD/vVf6M5T/N/86f/AHzyn/MV3/c4f9t/+Zpf8xXf9zh/23/5murSS+/8z+//AM2H/eq/0Zyn+b/50/8AvnlP+Yrv+5w/7b/8zS/5iu/7nD/tv/zNdWkl9/5n9/8A5sP+9V/ozlP83/zp/wDfPKf8xXf9zh/23/5mg/8ANX9e/Zn2sbvT+0b9nb+b2+nv/wCmuxWdtb/zk3QN32ON3eN3CB57mDXr21+WH/epHw3lBdY9xR9U/wC9+8//0+k+t/8Ay59Xf/DR/wDPmKtbqvW3YmRV0/BxzndUvbvZjg7WsYNPtGTb/gqv+rWT9b/+XPq5/wCGj/58xUK6zLrH1tyMSf2nW9jGFur20Ctuw1/nfzfrPaosf85l84/9B1Z4xPleSvWseTQ6RufNSxjj/qep0Tl/XKnda/H6fmtr1sxcax7bgPBj7fY5/wDWWn0vqeL1XDbmYpdsJLHseNr2Pb/OU2s/NsYvJegX5lfWsN/T3OOU+5ohpJL2k/pW2fv17N3qb16T0gNZ9Z+vMo/mD9ne8DgXuY71f7bmbfUUxDFzfKjGCPTxCPGDEcGnFHGYyhcv3/RJ18jLxMUNOVfXQHaNNr2smP3fULdyjj5+DkvLMbJpveBJbVY15A8drHOXN/XBjn9Z6QGtc8+hl6MxRmnnG/7Tv+h/x39hVuh1PZ9a8Pex7JxMqN+A3B/OxvouZ/SP6n+DQc96qzq3SqrHVW5uPXYw7XsdcwOaR+a5rnbmozMnHfR9pZax+PBd6zXAs2t+k71Adm1q4LrHVX4edZ9tFdWU7KbTZjNuc1wa/wB32+qo4Vr7MFtbPVst/wCM/wAIrFAyq/qJm12Fow7cgNqyGbwbMfJyWHKu/Ssp3UOqyHsqvaxnr1fpfTSU9vTYy+pl1LhZVa0Pre3UOa4bmPb/ACXNUce+jJpF+PY26l0htjDLTtJY73D917dqxPrZb9lrryLMS23Cw2l7raMx2GWuJFbMcU0OrsybLPYzHZ/pP0dSx/qlg5uBZiYtmBcMrH/SXh3USRVVkPtfXbd01z3Vu9j/AKO3+erf/hUlPaX3047BZkPbUxzmsa55gFzyK6mSfzrHu2MRII7Lg87puHh9Ryr+sdJstw7r3ek6usZLrLciwDHttyrbaW1/pHbMfDpxtlH+FyLEWnAy+nfVv6wXuxjhGzEcK7GzS55qZYPUfhB97cXI9/uvx8r0sj/R46Sns2X0WXW49djX3UbTdWDLmbxur9Rv5u9o9qZ+RRXdXjvsa264ONVZMOeGQbdg/O9Pd7lxGX0OqvpuRaz6r3VP9BzzcM5s7gw7bHbcrc/YtXKc49C+rWVvLsluR041vP0nG1raMn3H3fpca271ElNropaOv9ca/S82UOPnX6cVf9+WZ1npuF0/r/QRiVCs3ZN1lruXOcTS6XOPu/OWt1jpuc3MZ1jpEHNqb6duO4w2+qd3pl35lrf8G9ZXUOo4+dndPy8rHzcO/pr3vOOcZ1m8u2e1lrHbf8F9NQz0HCRrxWD4cfE6eAmWQZYEmEsRx5IR+aOSGCWCHFj/AHJS+STU/wAZvHTf+v8A/ohdZ0NtrejYDbf5wY9QdPM7G8rjvrFlX9Y6v0ZmVg2YmFZfsrF8NssD30Nu31A/ovbsXfAACBoBwEMWuXJIf1R/zU86TDkOSwyGo92ZNiX+Vnp6f+cpJJJTuWpJJJJSkklX6i61uBkOpn1BW7bHPnH9lJIFkDuaR3dY6ZTYa33gvbo4NBdB8Jasnrf136Z0yoCgOy8p+rKYLAB/pLXuH0FjiIEcdlyXWXOd1TI3dnAN/qgDanAB0uX5HFKdSJIAsi/mejp/xl9WFu6/EofTOrGFzXR/JscX/wDSau36P1fD6xgszcMnY47Xsdo5jx9KuwfvLxldt/ixfb9o6jXr6OytxHbfLm/9QkQKZee5PDHCckI8EoVttIE8L3qwH/XCn7Tk0YnSupdQbiXPx7cjGqY6r1a9Lq2Osuqf+jd7PoLoG/SHxXPfU7+a6x/6eM7/AM+BNcdNhfWmrJ6hj9Pv6b1Dp1uXvGO/LqY2t7q2+s+vfVbdtf6bXP8Actpc31G/J6n1mjpOU63pnTX3OZWBubfnWUt+0vZVbX/RensYzc+3ey3L/mq10hMmUlKVDqXX+idKcWdRzqMWwM9UVWPAeWa+5lf03/R/MV9c/wBdrn60fVt4Alzs2ncQPzsc2t5/lUpKa+D/AIx/qxl3spssfgixpfXdlGtlZjXa59dtvo2bfzL/AE10eLl4ubjsysO5mRj2TsurIcx0Ha7a9v8AKC4GrrmbmdFabrvU9T6t5uZkghvvva5uMy58N+n/ADq7bolQo6L0+kCBXi0tj4VsSU3Vn/8ArRf+gn/floLP/wDWi/8AQT/vySn/1Ok+t/8Ay59Xf/DR/wDPmKtPqfSMx2czq/R7mY/UWM9O1loJpvrGrar9vuY9n+Duasz63/8ALn1d/wDDR/8APmKuqUWP+cy+cf8AoOjzEzDlORI/zeYEHUSH3jJoXmqqPrILHnD6R0/pmRbpZn722QD9J7Kqq2ve7+utjpHSqelYpoY911tjzbk5Nn07bXfTtf8A98arqSlac80pCqER14b9Vd5T4pNDqPRMDqWTj5OX6u/FbYyoVWvp0t2epvdjursd/NN/PUMX6vdNw8+vqGP6wvqrfU31L7Lm7bCxz/bkvt2u/RN/m1pJJMTks+q/SKbcd+Ox+PXjW/afQrdDbbvcWZGZY5rsnJsr3u9P1MhGZ0DpNeJmYVNHpY3UA77RSxzgyXjY91NRJrx3a7v0DGfpP0i0EklOdndBweoVYdeY++x3TyH0WttdW/1A30/XsdSWb79v+EQ6fq10ynOo6hW7J+148tZa/ItsJrd/OY9rb32NfQ936TZ/pfetVJJTkZn1Yws1zjk5Wc9rrBaKvtVgY17XerW6usH9H6Vg31fuIv7Bxjg5eDbkZd9OdWarfXvda5rSCx3oG3d6X0lpJJKcd/1S6I+l1JGVtcw1n9cyToRs/Ov2/RVsdIxR+zhLyzpQjGrJG0uFf2ZltzY99tVO/wBP/jFdSSUpJJJJTyv1v/5c+rv/AIaP/nzFXVLlfrf/AMufV3/w0f8Az5irqlFj/nMvnH/oN/m/9x8j/czf+7GRSSSSlaCkkkklKSSSSU5tv1f6bY8vAfXu1LWOhv8AZaQdqwPrH9RGZLftPTrHNyGiHNsMtcB29o9v9ZdikjZZoczmhISjM6d9Xyer6l/WCy0VmllYmC8vBA+TfcvQvq30GnoeB9nYd9th33WHQud/31rfzFrJJEkr8/O5s4EZkCO/DEVfmoaGVztf1c69h5GY7pXWKsbGzMm3LNN2ILnNfcd9rfW9evczf9D2LokkGs4GP9X+sv6rhdQ6t1WvNb042PopqxhRL7WHHLrLPWu9jWP+jtW+kkkpSz+s9Jd1JuNZRkHDzcG318TJDRYGuLXU2Mtpft9Wm2qx7Ht31/8AGLQSSU83d9V+rZdDsPK6hi14drDRc3EwhTaaHHdbi1XvvubRXb+f+iXRta1jWsYNrGANa3wAENCdJJSlmRV/znnT1fsXjrt3furTWdA/5xzGv2Tnv9JJT//V7nrv1cwuueh9rfaz7Pv2ek5onfs3bt7LP9Gsr/xueif6bK/z6/8A0guqSUcsOORuUQSW3h+I85hgMeLNKEI3wxGw4vUXlf8Axueif6bK/wA+v/0gl/43PRP9Nlf59f8A6QXVJIfd8X7gZP8AS/P/APiibyv/AI3PRP8ATZX+fX/6QS/8bnon+myv8+v/ANILqkkvu+L9wK/0vz//AIom8r/43PRP9Nlf59f/AKQS/wDG56J/psr/AD6//SC6pJL7vi/cCv8AS/P/APiibyv/AI3PRP8ATZX+fX/6QS/8bnon+myv8+v/ANILqkkvu+L9wK/0vz//AIom8r/43PRP9Nlf59f/AKQS/wDG56J/psr/AD6//SC6pJL7vi/cCv8AS/P/APiibyv/AI3PRP8ATZX+fX/6QS/8bnon+myv8+v/ANILqkkvu+L9wK/0vz//AIom8r/43PRP9Nlf59f/AKQS/wDG56J/psr/AD6//SC6pJL7vi/cCv8AS/P/APiibzWJ9Qej4mVTlV25Jsx7G2sDn1kEsIe3dFLfb7V0vySST4QjD5RVtbmOaz8wQc2Q5DEVHi6K+SXySSTmFXyS+SSSSlfJL5JJJKV8kvkkkkpXyS+SSSSlfJL5JJJKV8kvkkkkpXyS+SSSSlfJL5JJJKV8ln/+tF/6Cf8AfloLP/8AWi/9BP8AvySn/9b05U+o9VxOnNZ62591x20Y9Td9tjvCusf9Wrix+ltbkdc6rm2+67HsZiUT+ZUGNtOz931Xv9ySlO69mUt9XM6RlUYw1da0stLR+9ZTU71FqY+RRlUMyMexttNo3V2N1BCIDGoWP0ituL1jquBSNuMDVksYOGPuB9ZjR+buc31ElOwATwnII5C5X632sb1XpVV1tVdD6spzm5GVbh1Fzfs/pn1cX32Wt3P2Vqt0G2kfWjGpxbsd1b8TJdazFzr8xpLXY/puvZlw2nbuf6T2/wAtJT2cHwKbhcF1DJxrMu7KdTdWyzMGK9pbuc3IsIa2i3Z1enZ6jvz/AEaqdi3/AKl33u6Vbi3tf6mHk31Osc5r2n9I97aq3svy/wCj1uZVYx1z/Sf+i9R6SneSSSSUpJJJJSkkkklKSSSSUpJJJJSkkkklKSSSSUpJJAz734+FffX9OthLfj4/2UkgWQB10SPvorO2y1jHeDnAH7iVQ6t9YukdJxxfk3h5dpXVUQ97z/Ja0/8ATcuXPucXPO9ztXOdqSfNcn1qx1nU7p4rIrYPAAJwi6GDkIznUpGgLlT2lP8AjM6c60Nuwr6qif5wOa8jzNY2rrMTLxs3GrysSwXUWiWWN4P/AJk391eJLuP8WWVcXZ+ESTS0MuaOwcSa3x/X2tSIZOd5DFjxHJjuPBVi+LiB9L3axc365/VbAyrMPL6jVXkUnbbXD3bXfuudWxzdy22/SHxXP/UwkU9Yj/y4zv8Az4muU2em/Wz6t9Vyhh9Pz678lwLm1AOaSG6u2eoxm7a1ay5vqXULer9Zp6b0s102YNrhf1a5jXGqwNm/B6VVaP1nOfj/ANKs/mcTG/4RdIefBJSknENY6x5DWNBLnOMAAauc5xSXKfWnpPT876z9B+147bxkty6LWvkteK6Tk47bGtI3em9tiSnZ6d9Y+h9TyPs2BmMuvLS9rAHN3NH0n0+o1jbmt/4JaREaFeZiro2Z0mu49LxK32dCyupufW14Nd1JbTWMbda70atznexd39W8WrD+r/TsapuxrMaokeL3tbZa/X9+xznpKdFZ20f845/7qf8AflorP/8AWi/9BP8AvySn/9f05ZGbi5+F1B/Vem1/aW3ta3OwpDXP2fzeRjud7PXY32uY7+cWukkpyHdeybG+nh9KzH5J0DLmCqtp/wCFuc7bs/qKx0fp1uHVbblWC7OzH+rlWN0bujayqr/gqWexivyfFJJTmdU6PkZ2fiZuPmuwn4ld1Z2VssLxd6X/AHIbZWzb6P7iHi9DzKurUdSyeouzBj020tqfTVXHrGp29r8ZlX+g+jYtdJJTzeR9SsbJzaMnIv8Atm2z1cuzLYLr7Q3eK8Wt014uLifpffXXib1rdJ6U3pVBxKb7LcRkDFot2n0GAfzFdoa22yr9z1/UsZ++rySSlJJJJKUkkkkpSSSSSlJJJJKUkkkkpSSSSSlJJJJKUmc1rmlrhua4EOB4IKdJJTiW/VhheTRkbK+zXt3EeW4ELmPrN9Ss5jvtuG4ZJdpZU0bXafnM3H3L0JIwRB1CNlsY+czY5CQlddCPm83xerpHVrbPSrw7i/iC0tA+Lne1ekfU76vO6LgvdeQ7LySHWkcAD6Fbf6q3hVUDIYAfgpJE2yczz+TPHgIEI7kR/SUDBBXMYeJ9aej3dQqwsLEzcfLzb8yu6zJdS6L3ep6TqvQs91f9ddOkg03nGYn1j6j1vpeb1LExsKjpj7rSacg3OebanYzawz0adv09+/cujSSSUpZXXem5uTZgZ/TjWc3pl5urqvJbXax9b8a+k2sa91L/AE7P0Vnp2e9aqSSnjH/V3ql2G7p+J0jF6WLMR/ThluzX5HpYtr/VyGMxvRZ61u7d6e6xn/GemuxqqZTUylk7K2tY2eYaNoUkklKWf/60X/oJ/wB+Wgs//wBaL/0E/wC/JKf/0PTpSlJJJSpSlJJJSpSlJJJSpSlJJJSpSlJJJSpSlJJJSpSlJJJSpSlJJJSpSlJJJSpSlJJJTz/1o671Ppd+Bj9OqqutznPrDbQfpA1Nqa0iylrdzrfz1j5v1r+tuA7Zl42DS/nY6xu7/MGZvVj69svs6j0OvHf6V77ntqs/deXYwY/+y5Qd03Op6oOkdEYMd1bBbmdWyK/UsscdTtsta7d9P8xVZ8ZnOpSABiBw+MXd5YctDleXMsOGcpwyTySyie2PLOPFLJGX9z/JZJsML60fW/PJbh4uDe4CSxtjdwH9T7ZvVv8AaP8AjC/8q8X/ADh/72Kp0/Ev6pm53S+oOYOp9OIfjdToaK3gz7fU9MMa9uv0F0f1e6lfn4LhlANzMWx2PlAcepX+e3j6bTvRhEnfJPW+sf0d/wBFZzObHjsw5TlpCPDxDhyH05RxY8kZe7Hjxz/uQcf9o/4wv/KvF/zh/wC9iX7R/wAYX/lXi/5w/wDexdWkpPaP+cn9sf8AvWp/pCH/AIj5X/Ey/wDq55T9o/4wv/KvF/zh/wC9iX7R/wAYX/lXi/5w/wDexdWkl7R/zk/tj/3qv9IQ/wDEfK/4mX/1c8p+0f8AGF/5V4v+cP8A3sS/aP8AjC/8q8X/ADh/72Lq0kvaP+cn9sf+9V/pCH/iPlf8TL/6ueU/aP8AjC/8q8X/ADh/72JftH/GF/5V4v8AnD/3sXVpJe0f85P7Y/8Aeq/0hD/xHyv+Jl/9XPKftH/GF/5V4v8AnD/3sS/aP+ML/wAq8X/OH/vYurSS9o/5yf2x/wC9V/pCH/iPlf8AEy/+rnlP2j/jC/8AKvF/zh/72JftH/GF/wCVeL/nD/3sXVpJe0f85P7Y/wDeq/0hD/xHyv8AiZf/AFc8efrH9a8TqOBi9Uw8bHrzrm1AtlxILmMt27Mi3a5vq/nroP8A1ov/AEE/78sX63/8ufV3/wANH/z5ira/9aL/ANBP+/JsASckDOWhjUv0tuJk5nJjjDk+Yjy+IHJDIZ4qn7EuHJPFH08fH/44/wD/0fTkHLzMXCoORl2topboXvMCT+aP3nIyxcalnUuu5mVlD1K+mPbjYdTtWteWiy/J2/6V27YxJSRn1p6I54a+2ylrjDbbqrK6yT/wtjdi1gQQCDIOoI1BBTWsZdW6q5osreIcx4lpB7FpWR0NpwszP6M1xdj4hZbiTqWVXAu9CT+bVY32JKdhJYP1iz+p0dS6fiYL8lrMirIsubh102WE1GkV7vtv6NlX6V30EDpfUOsft/Gwsp+ace/HvsLc6nHrl9TqAz0H4Xu9vqv9RtiSnpUlxuZ9Yn/a7HUdUezGstLaZcKxM7fRY2zpVzt+72+n61tiNjfWPOs+qeZebHP6tRYcYOewtLXX3fZsCwzVjV27arar/ZSzekp6xJc99YMnqeFZh4eHnOx214WXfdkOZXa+w4raPT9U3tc33+o59uxVOhZ3WsjMH7R6uWVVswnml1FLPUfl1utdil4r3s942V7P0iSnrElxFf1k6ofrI7Ffk5DemG92PW4/ZZD2g7tzfS9Z1Ff09m71/S2K7hdY6hfg9Xtxuo/a6Meuh2J1DLqFDGmxrjkuGynGbbsbssq9uz1P8J6aSnqklwr+p/WMCrEp6hbZk1S4NNWP6rnBvswsqr7Q3fcyj1XZv/dr0n0foFtZP1jdi9Jw3485mbm0MtquyG+jQxtkBuV1Kyr9Fj1MdYxnoUb777P0VP8ApElPQJLAwM7P6VnDo/U7X9SDnM9PqFbNz2Ou3bKuo0Vbvs9VlrLfseS39B6X6G70/S9Sy3hZGRV17qPTrrDbWa6c3F3aljLd+PfQD/o2X4/qVf8AHJKdRJJJJSkkkklPJ/XFwb1v6uucYaMokk8AepirW+sXXqej4m4D1My72YtA1LnH84t/0bFjfXrGry+pdCxbZ9O+99b40O178Zjo/wA5Az/qx1V14dmYw641jRXTcMj7PaGCdrbRZ+ic7X6TVXMpCeXhHWOv+C7EMOGXL8ic0wIiOWXt+mHH+vn+nklDH/e9bt/VbpB6di2XZTxb1HNd62W+QSCdfT9v7m7/AD031ZeL7OqZtY/QZGY70XdnNY1tXqN/kvc1YuB9U+o+q51NA6LTcw1ZBFxyL3VuLXOrrc39DXv2fTXYYeJj4WNXiYzdlNLdrG+SfjB9OnCI/i1ubnAHIRk93JmMboR4ccIerh/VyyY/0YcHBP5Eyfa7wKb46juFzmY19GVbS179rXe33Hg+4d1K0Ho0lmdHzLbS/HtcX7RuY48xwWlaaSlIb8jHrdsstrY4ctc9oOvkSit5HxXH/V3oPROpu6xk9SwKMzI/a2az1bmB79rXhtbN7vdsY36DUlPWV30WktrtZY4CSGuDjH9kqa5LKwOi4XW8XH+rXTqB1zGJfa6oenRRVa30nv6m+n3P31/0XE/nfU/SrrTE6cJKUkkuY+s9GTf13pOGzOy8XF6jXlVZFONYKw401/aKy0lj9j7P0jLElPSMupsc5tdjHuZo8NcHEf1g0+1TXmGJ03o+PiUdQ6XZnYWSej5PUqbGX1/o207G/ZbNmMz7Qx73fSs/0a9A+r7bx0PAOTdZkX2UV223WmXl9jRa+SA32tc/bX/ISU4n1v8A+XPq7/4aP/nzFW1/60X/AKCf9+WL9b/+XPq7/wCGj/58xVtf+tF/6Cf9+UWP+cy+cf8AoN/m/wDcfI/3M3/uxkf/0vTli5Lrui9Tv6h6T7umZ212Uamlz6bmjZ65rb7n0Ws+ns+gtpIGOElOTZ9auhBk05P2qw/Qooa59rj+6Kw32/21PouJltOT1DPaK8zPe1zqQZ9Ktg2UUbv32t91i0msY1xc1jWuPLgAD94TpKef+svQsvquf0++inFvqxa722NyzYGg2+j6bq24/v3fo3oHRvq1m4HXqM9+PhY9DMe6p5wzbuLrHUur3tyd3s/RP+gunSSU8hnfVTq2bn0OyLwabsluXmHFe7GprdWIY6jHb6mTfnP9j/tbr62epXv9JXh9Xc/9gZ3R7L6rC8izDy2tc219rS3IZf1Le631Mj7VWz1bqnfpWf4Or6C6FJJTg9c6P1Dqz8DI9DHs9Oi2vNw77rK2E3ehbsF2NXZ61VduP+krf+jvrUW9I6nd1ejqWZ0/p/q1uZvtrycgkNr3tZYMb0K8a6+lltnoOt+h/pF0CSSnkT9VurOftLqxjuzrck/pnEhljr/0n2b0fS9XZcz/ALUKw/o/1iyOiZOBkmmuMCvCxseq1z2PsZ/OZdlllVPo+pDGMr/SLpkklPLZP1Xz6MqzqWK9uZaM5+dVhyaQTY11Wx+RZZZS30/U9R+3F3q5f0XOs6H0voc1+hWMdvUrpMhmP6dprxmR73ZN1Xpeo7+aqW6kkpoY+HfX13Pz3R6OVRjV1wfduqN5t3N/68xRwsLJHV+odRyg0esKsbEaDJFFIc/e7919+Rfa70/3GVrRSSUpJJJJSkkkklPK/W//AJc+rv8A4aP/AJ8xV1S5r63dN6vl5XS8rpdAyLMGx9pDnNaAQaX1bvUfVua70vzEL9o/4w//ACrxf85v/vYoBLgyZLjI2Y1wxMv0XVlgHMcpygjmwQljjljOOXLDFOPFmnKPpk9S4bmkDQngqNdgfpxYPpMPIP8A5Fcx+0f8Yf8A5V4v+c3/AN7FCzM+v1kb+k4jo4O4T9/2xO94fuT/AMSTD/o2f/ijlf8A2oxfxerfYysS8/Bo5J/daFjW9M6lfa+57WB1ji4jeNPBv9lqzWZX1+rMs6TiB3724E/ecxT/AGj/AIw//KvF/wA5v/vYl7w/cn/iSV/o2f8A4o5X/wBqMX8Xe6d0/wCyBz3uDrX6GOAB+aFcXK/tH/GH/wCVeL/nN/8AexL9o/4w/wDyrxf85v8A72Je8P3J/wCJJX+jZ/8Aijlf/ajF/F6saEFcj0zqGZ0K/qmJkdH6jlG/qOTlVXYlLbKnV3u9SrbY62v3bfpqf7R/xh/+VeL/AJzf/exL9o/4w/8Aysxf85v/AL2Je8P3J/4klf6Nn/4o5X/2oxfxY419+f8AWHp1mB0jO6Xj1X5GV1KzJqbTXa6yl1DHP9O2z17vUNa6xcr+0f8AGH/5WYv+c3/3sS/aP+MP/wAq8X/Ob/72Je8P3J/4klf6Nn/4o5X/ANqMX8Xqli/WLHyxldK6ti478s9Lvssux6o9V1V1NmLY6hryxtj697X+lu96z/2j/jD/APKvF/zm/wDvYl+0f8Yf/lXi/wCc3/3sS94fuT/xJK/0bP8A8Ucr/wC1GL+Ljt6c4YB6f0zp/VX5Lum3dIodl0Mppay9/rPyci4u9npfyf8AMXfY1IoxqaAZFNba58drQz+C5n9o/wCMP/ysxf8AOb/72JftH/GH/wCVeL/nN/8AexL3h+5P/Ekr/Rs//FHK/wDtRi/ir63/APLn1d/8NH/z5ira/wDWi/8AQT/vy5q/D+uHVOq9MyOo4FVNWDe2wuqez6JfU61zg7Itc7a2n8xdL/60X/oJ/wB+TYS1yT4ZUTGhw+r5eH5WXmcIMOT5cZsJnCGUTkMsPZhxZZ5Y8WX5Y+l//9P05JJJJSkkkklKSSSSUpJJJJSkkkklKSSSSUpJJJJSkkkklKSSSSUpJJJJSkkkklKSSSSUpJJJJSkkkklKSSSSUpJJJJSkkkklKSSSSUpZ/wD60X/oJ/35aCz/AP1ov/QT/vySn//U9OSJABcSABqSdAAksO2gdc6rkY+SSemdNLa3Y4JAuvcPUcb9v06aWn+bSU6lXUenX2+jTl0228bGWNLvulWFQyOg9FyKfQswqWsH0TWwMc3+VXZXtexyD0S7Jrsy+lZdhvtwHN9K930rKLBuodZ/wjP5t6SnVSWD9ZOv5HScrBxqXY9Qy23PdblC1zR6Xp/o2MxQX7n+r+cgdF+s+TndZr6dbZiZDLaLbi/FbexzDUamjf8Aa2ta5tnrfmJKelSXO5XXeqszLasYU2UtcfTe2iyyWfveozKYx/8AmJ6PrRZZ9V8jrDmVuy6HWUihkwbRacPFbYwufZV61vp72uekp6FJUL87Nw/Qpfg5HULTWPWvxRU2sPHts9uRfS5m53vb/I/PWb9XvrJ1HqWDjW5HS8nde97X5FYqFLQ22yoPLTkG79Gxn6X9Gkp6FJcf1z665uH1C3Ew6McNxjk12OvuDXOfTjty2PFW39GzdZ/6Ef8ABrZ+rfXX9ZxrHW1MquxzW2w1WC1jjZVXkbmPaG7f5z31f4NJTrpJcrlMX61dUvwczMubhYNVFxFV2TaSz05hlLm0/pbMm1jfWp9P+e3/AEElPVpLG6N18ZfRL+rZ5pqbjG43CkuJZXVJm+mz9NRe9jfU+zWfpEmfWGtvUMyvK/RYdLcH0Xljg4OzTYz9Z/0bPUbUz/gklOyksXrPXj07IyGCylzMbEdfdSPdkseXCvGuZRuYzIxrXO9N/vq2P/wvvQ+n9b6u/quLgdVwTg/acYlhOxwfk1APzNj67rHVY1bHN9DfX+m/60kp3klx/UPrtl4XULRfVTh4W2MVmRvdfY5p/S2vrwW5Lsbf7Ps2NkNq9Sv9N6v+Cr0rvrFlU/VXI61ZTQ3Mx6y92My4XMYdwawXW0/yD6lja0lO8kuFv+vmfS7LYMnpT/sdIvDh9oiwkWH0a/5f6L/wRdNZ1PJrzOkl7WjE6ox1bh+dXkGv7XR7/wA6uyuu+nZ+/wCmkp1EkjwkkpUpJc8rCyM3qNGRZSb3H03QDA1HLeySndSVDpnUH5QdVdHqsEhw03Djj95X0lKTwfBIakLj+ldJf127quXm9S6hW+rqWVjV14+VZTW2qlwrpYymv2N2tSU9gQRyEy5TIw6vq/1LAfi9Q6jnZd7nMb0m292Schjhs3n13ivEqxHfrD8t3/Frqzz4pKUkkue+sOb1yrq/TundOyqcWnqVeQDbZQbrK7KGetvr/TVNd6jHe1rm+z0/8Ikp6GCOUl55hu6rhVM6jh/WKzLc/p9/UGU5VFtld1NQbufc23Ld9nv9RzNnortuiX5mT0fCys5zHZWRSy601tLGA2D1QxrC6z+ba7Z9L3pKbqz/AP1o/wD0E/78tBZ//rRf+gn/AH5JT//V9OWGMivo3WskZZ9PC6q9ttGS76Dbw0V2Y9zv8Hv276luKNtVV1bqrmNtreIcx4DmkebXJKRZGdhYtByMjIrqpaJLy4RHl+8qHQ23ZN+Z1m2s0tzixuLW8Q/0Khtrssb+a65zt6LT9Xeg0Wi6rApbY3Vp2zB/ktdua1aPKSnnfrLh9Rv6t0y7Cqy7G1U5LbX4VldLml5x/TbZblfotj9n0PpoHS8HqrPrJiZGVTntorxshhszbqb2hz3Y+1tf2T+ae/Y7+c+mupSSU+f5/wBWupZWZj0jBrwq8nNbktrrpZknHZXvNjsnP2041ePY/wBO39mfrPq/zXrrUq6H1Fn1b6jhOxGVdRFjMhl1ZrNeS+lzMqn7OyllLsen9D6FeNdV+g/0l388usSSU879ZKb+ou6O9uDk5eK+yy3JxK3Gh4DqXGn7Q/1cdtXpW/mPu/nFT6Z0k4f1g6bZhdGyelYwGQMlz7hbUQWfomOZVkZLK/0vu97K11ySSnh7fqtn9U6rm2Omql1uS77VkYtbd7rq24fo0V+s7Itprobupy7fSZ6n+lWn9VOn9S6fX1BllJpyHNqNdFlbKqDYyr0K3VZeJZe26u1tVf2h3osuq/0K6VJJTXwMm7Kwqsi7Hfh3vafUxrYLmPBLHN3M9tle4fo7G/zlS4un6u53Tn9Ud9jcLbLajgu6XWWltjaPbay3Nve2vF9R/pWekz1PW/4P9Gu8SSU8z0zo93Uej/Y88ZePdZbj2dTfmNrc7I9IMfbjV21O/orrK/T3u/wf6NSxMN3W8nr1mZh34eH1KmjFrGS0MsJqbe226usOftax11bqbF0iSSnl+v8ATeom3Ge7HPVsZmA/CzQPbbc6x+PsFLans9C266lttuS9/oYlPqWKv0rpXU+nddxm5VGTn2imoDPtvstx6t1bm9Xex1tntvuvZS2pjqfez+2uwSSU+fv6R1PHzskYODfRU7qb3WHHqfWx+Ft+h+hy8Vl9O938yyln/Gq7T0fLd9Ucvp9eE6jK6jmuq0q9NzKTkD08q/c61/pY+K3dX6luR+YuzSSU8Z1vpHVabesWsyepZbcvEqx6PRZS82WOGU307Q2hmyirez1LP0Pp+t/OLYuxMq3K6FhOqIqwB9ryrR9Br6qTiUY7bPzrH25D3/8AFUrbSSUs4EtO3nskx4e3c3jv5H90p0N9LXO3tLq3nlzTE/1klMyWtaXOMNGpPgufyKc3IyLL/s9gFjpaC3Xbw1boobuDnudYRqNx0H9ke1ESU53ScG3H3XXDa942tZ3A5JctFJJJS45C4vof1m6D0W3rGF1XMZh5P7VzLRVaHB2yx4fVZo13ssZ7mLs08/6wkp4vD6x0TI+tmEfq/mfbbeo23v6s7WxwpZS441XqWs3Y2JTkbPTqrd/OLs08pklKXP8A1nczD6h0TrF4IwsDIuGXaAXCtl9FmO220M3ObV6pZvs/MXQJAkcJKfMWu6bj9L+y43VMfqOYOjZHSMfFxQ99tl+RZvrdUwN/m/T2scvSMKg4+Hj47tDTVXWR5ta1n8EaY4gfAQkkpSz/AP1ov/QT/vy0Fm7nf85duw7fsc75ETu+jt+kkp//1vTkkoCUBJSkkoCUBJSkkoCUBJSkkoCUBJSkkoCUBJSkkoCUBJSkkoCUBJSkkoCUBJSkkoCUBJSkkoCUBJSkkoCUBJSkkoCUBJSkkoCUBJSkkoCUBJSkkoCUBJSkkoCUBJSkkoCUBJSkkoCUBJSln/8ArRf+gn/floQFn/8ArRf+gn/fklP/1/TkkljZdmZ1TqVvTMW52Lh4gb9uyK9LHveNzcWh/wDg/Z7rbElO1tPgmWOfqtgMbuxLsnEyB9HIbc9zgfF7LXOZaj9GzsrIGRiZwaM/BeK73MENsa4bqcmtv5vqs/6aSnRSWN1v60YPRcmrHyWuc64sjaf3y5u1rRuc63azfXX/AIVNg/Wrp+X1D9nuDqbnubXj6PeLLPSblZNQtqrdjtdh7/Tu/TJKdpJYTvrZXYyu3p/Ts3Opfb6b7a6HhoYC6u3IqdH6b0ns/m/8Ijn6y4AwM/M2XVnpg/T491bqrNzmiyitrH/9yNzGVpKdZJZuZ1puE7Dpyaw3IyW+pkMDxtoY1o9W11kfpP1h9OHR/wByb7lWwfrTTmdTt6cMHMqdUamix9DwJuDn/pwW/q7WbfzvppKdtJYB+uXTR1R3TXNIdXu32bhta1rzW+5x+h6Hps9Tfv8Ap/oNinh/XDpWR0y7Pt9Sl2LXXbl4/p2F9Yu/ozPfVV6r7vzPTSU7iSxf+ctn2o1npPUfs/p7hf8AZ3z6k7XUejG76H6T1Urvrb0it3SoslnWHRQ5wc0taWuc17mbHfStayj0/wDhElO0kqFHUeoWZDKrOk5NDHO2uve+gsaP33Cu99m3+oxZtn1wqbm34gxjFL8isW2WtrYXYobZc619jfToq2v9tnqJKehSWJ0n6x2dXwcnKxMP9JQzdXjuuYbHP9+ym6uvdZiut2fovVb72P3rT6fm0dQwcfOxzNOTW21k8gOE7Xfy2fQekpsJJJJKUkkkkpSSSUJKUklHy81j29YzKbX1Prr3VktPP96SnYSVXAz25jXe3Zaz6TeRB/OarSSlJJASQFy2E/60dav6jfjdYr6dj4udfh1Y4xGXe2h3piw3WWMfus+k5JT1KS5u23rnQsjHy+sdapzOm2PNN1RxRTaXvEYwwm4xvtyb3XfTq/0PqWLpCIMJKUkksTrnXepdP6jidNwcCvKs6hXa7Hvtv9JjX0jfa21npWO2MY5j/p/pElO2kuIxOt/XPDH2zMswep4v2azKdQy+utzq6xvfk4D6KnOfRX9H9Nv/ADF1nSM23qHS8XPuo+yvyq23CgP9Ta1431/pNte5zq9v5iSm2s//ANaL/wBBP+/LQWf/AOtF/wCgn/fklP8A/9D05Y3T7GYfXuoYN52vznty8Rx0Fg2tquraf9JU5n0Fsqtn9Owuo0inMr9RrTuY4Etex379VjffW9JTZOgJdoBqSdAB5rH6LY3N6j1LqtX9FuNePjv7WCgEWXN/keo7YxOfqziWAMysrMyqB/2nuuJrI/dft2usb/WWrXXXWxtdbQytgDWMaIAA4a1oSU8b9drLG9VxvTY3NfXUba8NlduRc21m70LPQx76XYeK9z/Uv6hX+m/Q+j+kS+r78Svr9Tmbv2ZZ6ren5AbY6qzqFra/2o1uTc59z2/oHvouyP5+77Zsus9JdoNONJ5KUlJTwX1kws12Xj4eQOk/tDqVtVLRTVaMj0XOLbLa3Osd6bK62u9S+v8Amv5D1qZnSuoYX1TysN1WL6eEKbcajDbYJrxrGZVtdrsqy2y57mVO2e5dSkkp5f6wNw8zrHSsxjWZDWs9assxLsix7bDtpsZkY7PSrqqba+6uvIs2et+sf4NZWPi2dK+sNeBmZguOPZ0/7HSzHLbMgMquwmPa91z2Nrxm2Psy7P8Ag/8ABLvRoIGg8BoE8mInRJT5zk2Yh61m/tHZkYLrH0ZefW3IdW3Gh9j6b+pY979+bbe5mM3pj6X0VV1+lX/o1t/Vk9Qdg5+O9jKuul1T3VZtTgz7OGso6fY9lb/0m/Ho/S+nZ+hzfVru9NdUNBA0Hh2SkpKfPG9Gs6j184FFfSX/ALPZ9pvFFdzaxkC3ZXXmOrt9V9jNr7GYzrPT/PsqWx1/D6jZl9EvzLofS64eh09u3dd6ORdZ6Tcj1dzb6avsnpWf6e9dWlp93CSng+jdPx3dWN2Pg4978N+CzNqo3FlFrxf9p+y/pHNZbg2/ZXZf0/UqrVTJysZvVc67IFlePYLzW8srDr3et6W7Df6DGX+vXUxmR+sY/o7PVs9Rejp9zvFJTyv1Y6nj1/tLLzMmuy2ipluTbTZ6tXpMdk37m2O22epX6r6X1vZ/g6/R/nPZp/VTFuxfq7gU3tLLTWbHMPLfVc/IFZ/4ttuxazvcIdqPA6hJJSkkkklKSSSSUs+dpI1I1hOCHAOGoOoKSGantJNL9gOpY4S2fL91JSRc1mXsvy7bmGWOd7T4ge3d/aXQela/S54LO7GCAf6zvpIsDwH3BJTldExrGufkPBaxzdrJ0nWS7+qtVJJJS7fpD4rnPqhdSyvrAfY1p/bGdo5wB/nB+8uiWXl/Vb6t5uQ/Ky+mY12RaZstfWC5x/ecf3klOXfWzG+t3Tcm+5nUbuo25FOMXRGHSyl2Rsw62Ocz7Rc9n61mP/S2Vfo/0a6hZ2B9XOgdOyPtOB0/HxrwC0W11gOAP0od/KWikpSwPrCa6uv/AFbybnBlIycihz3GGzfjWsqZuP51ljPYt9CysTFzcd+NmUsycez6dVrQ9hjUSx37qSngLeldU6T9Xzf1KttDMP6vZWA5xsYYybrG+hSNrjudZU1m3/MXedOpNHTsShw2mqiphB5BaxrYVLH+qn1Zxb2ZFHS8Zl1ZDq3+mCWuH0Xt3bve395aqSlLOkf849s+77HMd43crRWb6bP+c3qbff8AY9u7y3TCSn//0fTkkoShJSkkoShJSkkoShJSkkoShJSkkoShJSkkoShJSkkoShJSkkoShJSkkoShJSkkoShJSkkoShJSkkoShJSkkoShJSkkoShJSkkoShJSkkoShJSkkoShJSkkoShJSln/APrRf+gn/floQs3eP+cvp+6fsczGn0v3klP/0vTkkllZ+fnW537K6VsbkMaLMvKtG5lDHfzbW1/4XIt/NYkp1UlkO6X12oGzG6w+64a+nk1sNLv5J9INsr/sq10nqX7QoebKzj5eO805WOTOywfuu/Orsb763JKbqSpZ/WOn9PsZVlWFjrNu32mIcSzfv+j7HD9J+emxut9NysqzEquAvrFZNb4Y4+swZFQZW8tt3ek5u9uxJTeSWRlfWz6uYrq22dQocbLhjkVva7Y525u66Hfo6mOZsss/wav4fUMDPY5+Dk1ZTGHa91Lw8NdG7a7Z9F21JTYSVe/OxqMrGw7HH7RmF4orAJJFbfUtsdH0Kq/9I7/CWV1/nodHV8C/pZ6s2wjDYx77HOaQ5gqLm3ssq+m22p7HsfWkpuJLJ/50dGFYsL7mtLnM91FrSHMO2ytwexvvYiZf1h6Vh4NOfk2Oqx8iwVVl7HNcSZ9/pOHqekxrfUfZ/ov0iSnSSWP/AM7vq99qbi/av0j2Me32Pg7yWMZ9Df6nt3ubs/m/0i0Oo5+N03DtzcsltNAl5Gp1O32j5pKbCSzcr6xdHxL3499zxZXG4NoueNRuG2yql9b/AOy5PnfWDpeBktxsqxzbHNY/RjnANtf6FLnuA9u+32JKdFJUMTreDl5b8Klt/r1HbcH0Wsaw7fUa222xja2epX76/wB9DzvrL0Xp+e3p+XktryCz1HCCQwH+bbc5v83Zd/ga/pvSU6aSz7Ov9Gr6ceqHKa/BDix19bX2AOB2uDm0se9u38/2of8Azm6G280X5bMV4pryCMk+gQy0vFYczI9Oxlv6Pe6tzPYxJTqJJmPY9jXscHscA5rmmQQdWua4fmuTpKUkkkSAJJgDUk8QkpSSyLfrLiMeW1VPuaPzwQ0H+ruWF9YPr9ZjN+y9Op2ZTgC+22HhjTxtY36Vjv5aNFnx8rmnIRjHfv8Ate0SXk1P1z+s9Vwt+3Ot1k12Na5h8tga3/or0P6tfWCnr2Ab2t9LIqOzJpmQ10S1zD/o7PzEiKX8xyOXBHilUo94/o+brJJASYXOU9b+svUL809KwcN2Lh5VuGHZORYyxzqDsss2V0WM2Pd9D3oNV6NJYdPVPrFjZVI61hYmPgW7hbl0ZDnCkhu6t2R9oqoZ6dz/ANCz3/zi3ElKSSWR1j6yY/SsurCdiZeVlZNbrMavGq3izZ/OVtsn6df+E/0e9JTrpLkcX65dcre6zq/QMnHw2sc+22mux5pLfdFotbW26vbu3XUrpem59PUsCjPoZYynJbvrba3Y/afoPLNfbY331/yElNlZvqs/5zelPv8Ase7b5boWks//ANaL/wBBP+/JKf/T9OWP0givrPWcazS99zMho7upcxrGOb/JY9uxbCo9S6TTnuqvbY/FzcefQy6o3tB+lW4H220u/wBG9JTeWR0pzbuu9XyaTNANNBcOHW1tPqx/U3bEndM6/eDTk9WDaDo449ArtcPD1XOf6f8AYatHCwsbBxmYuKwV01iGt5Mn6TnO/Oe785ySnj/r4zFb1DFvzqR9nqZ61VxsrpDradxGKx9lN32nKt3/AKHHv/V/S9ZLoOFhVfWwYljmWX4QuzGW2embXX5LKW3Yf6Fvpb+nN3/o6P5mi/GXXP6fgPyjmWY9b8ks9L1ntDnbNf0Y3Ttb7lGnpXTKK6aqMSmuvGebcdrWACux27fbX+5Y/wBR+5JTyHVR1jBqrbh9Py+nNvuZi49dWdV6LH3PP6RtDN26x73us/SP2ep/Oro/q9j5FFd32rDtx73Fgsyci6u+3I2t2Ntudi+xr62fo/oq7d0zp2Rm059+NXbl4w20XvEuYP8Ag5+j9JWUlPKZ2Jm0/WxjendRjM6jW+zLddUy92Ni1D9BXTLq/s+JbkuZWyrb+nu9a/1LPTVfFx7R0r6z5lmQbbGuyca1tbW10XPqZWftrsdu9teZ7vs99lVn6b0/0tfqrqXdK6a9uUx+LU5ueZzAWg+qQNrfW/f2t+ipM6fgV4R6fVj114RaWHHY0Nr2u+m3YyPp/nJKeCvutqtFrabS3H3UY9r6rXX3MybrcjGc/GdWdn2nKr9Kux9db/8AC/za0LK7T0To2JXj111/andMyMoEl1VJv+zW0UNu/Tb8/wBJrcj9yj1a1178LEsc976WOdaGNsJGpFZc6j/tlz3emoVdN6fTi0YlWOxmNiua+ioD2sew+oyxv8vf79ySnlms63+1ukn1asG3IzMoXVOwywl9VD63W7vtVnr120Us9C2p/wDN+l/ovSV//GDjWZX1Yyaa2Mc0uY6yx4D/AE2tcHb6q3/Tuf8AzLf+MW0OmdPHUD1P7PX9vLPT+0kS/Zxta4/R/souTjUZVD8fJrFtNgh9btQRM6pKeHyr+oV2s9DJy66Q7Mr+z4tgqqt+xVMdZ+zq302uxKK7xfTRVvv9T0v5zYp9avxMn6w9OppcMp19WIWj3m6ykG3MruewPq9VzLKPVu2eh+jt/SLsf2fg/bz1L0GHOLPS+0ke/Zz6bXfmtTZPTOm5YeMrEpvFrmvs9RjXS5o9Ot/uH02M9jUlPJ/VvHDOsY17KgwO9c15YYWsyvZ+surt2+pZ+s/pWfbG1epV+kxfUqVOjH6qeoZNWI7G6Jj473UZ2KzOeMjJfYK8r1r8y6m3a/8AS/02mv7b/gPX9L012mP0To2LeMnGwqab2/RtYwBwkbfpJWdF6NbdZkXYOPbde7dbbZUx7nEAMBc6xrvzGpKeW6tecv6h5eLg4oxBjW2YYGJc401spcfXyjewUPvpfXv/AEdjP099n6RCymZdb/s7cYepkHNrspLrn/an1YjG3OffkP8AtVtb7A+nCf8A4JdjX0vp1WJZg1Y1deJcXGzHa0Bji/Wz2N/fUv2fg/bv2h6DPtor9EZBHvFfPptd+a1JTPEdS/EofQw10uqYaq3DaWsLRsY5h+hsb7dqKkkkpSr9Qqsuwciqr+cfWQ0Duf3f7SsJJJBog9tXhR/qPBcl1hr29TyN35zg5vwI0Xr93T8C95stx63vPLiNT8YWP136ndM6nUDSwY17Po2V8/1XNd7XtThJ0eX5/HCdyiQCKPXhfLF23+LGu31+oXa+jtrZPYvBc/8A6hBq/wAW+abQLsoCqdSxnuj+07au36T0rF6ThsxMVu1jdSeSSfpOc785yRIpl57nsU8Rx4zxmdWaIEQPV+k3W/SHxXP/AFNBNPWI/wDLjO/8+Bb6wrvqdgPyb8ijN6hhfarHX204mU+qo2P1ttFTPz7Xe56a5DW6lRbf9ZMCnrgFuFkX2M6Xg1mag6mp2T9v6lvj7RkP2enj4+30Mb+XYumWLg/VXCw8+nPfl52bfjbvs4zMl97GOe30n2Vsf/hPTdsW0kpSwOusn6y/Vtx0Drcykn/jMV7v+qqW+qnU+lYfVMdtGUHj03i2m2p5rtqsbo26i6v312e5JTwOOM2vodb8iq2oYv1XzWONjXNAtNjamMdvH85sq3bf3F33Sa/R6Tg1f6PGpZ91bAs131Rw7YZmZ/Uc7HkF+LkZLnUvjUNurY2v1Gbv8H9BbqSlLP8A/Wi/9BP+/LQWbP8A2TRt/wC0f05E/S+jt+kkp//U9OSS1S1SUpJLVLVJSkktUtUlKSS1S1SUpJLVLVJSkktUtUlKSS1S1SUpJLVLVJSkktUtUlKSS1S1SUpJLVLVJSkktUtUlKSS1S1SUpJLVLVJSkktUtUlKSS1S1SUpJLVLVJSkktUtUlKWdtb/wA5N0Dd9kjdGsbvFaOqz/8A1ov/AEE/78kp/9X05JJZ3Ueqvx768HCo+2dRubvbTO1jGcevk2/4Ov8Ad/fSU6KSyH2fWuhvrOqw8to1dj0l9b48KrbPY9/9ZXundQx+o4rcrHkNJLXseIex7dH1Wt/NexJTZSSQ8e+jJpryMd7bqbQHV2MMtc08OaUlJElTd1no7HFr8/Fa5pILTdWCCNHA+9FpzsHIZ6lGTVdWHBhfXY1zd7tG17mu273bvoJKTpKuzqGDYKzXkVvFzg2ra4HcXB72NaP5TKrHf9bVmDBMccpKWSQ8e+jKx68rGsbdj2tDq7WGWuafzmuU3Oaxpe8hrGguc4mAANXOcUlLpIVOXi5BLaLmWkMZaQxwd+jsk026f4O3a703odnUum1WPqty6K7Ko9RjrWNc2fo+o1ztzN38pJTZSQMfOwcp5rxcmm94G4sqsa8gcbi1jne1Qb1Xpj6G5Lcul1D3NY2wPbtLnO9FjOfz7v0aSm0kma9j27mOD28S0gjTzCAeo9Pa1znZNTGtsdS4ue1oFjP5yr3lvvYkpsJIH2/A9A5P2mn7O07XXeo3YHfuGzds3aoT+s9HYAX5+K0OG5pddWAWzt3CX/R3NSU3EkK/LxcfGdl33MqxmN3uvc4BgaeHb/o+5FBBEgg/ApKUkkkkpSSpXda6ZTYa33S5ujtjS4A+G5qyOufXjpvTag3Ga7Ky36sqILGgf6S15/N/qo0WWGDLMgRgSTto9IkvOaf8ZXWG3brsbHsqnWtu5jo/k2bn/wDSau46P1jD6zgtzcQnaTtsrd9Jjx9KuxIghfn5TNhAlOPpP6QPEG8kksF31ua7IyacLpPUM9mJc/GsyMdlZrNtelzGerfW/wDRu9v0EGu7ySxcP6zjIz8fAyel53T7MveKLMljAxzq2+s+vdTbdsd6bXfTW0kpSSSzup/WHofSX+n1LNqxrdnqip7oe5mvurr+k/6P5qSnRSXLYP8AjJ+rWVe2q578FtjS6u7J9MMMfmWOqst9Cz+RcujxMzFzsZmVh2syMeyfTtrMtdB2u2u/rBJSZZu8f85dms/Y5mDH0v3lpLM3n/nT6cHb9h3T2nfH/UpKf//W9OWR0eD1jrbrP6QL6268ikVt9GP5H01rrN6h0vIfls6l021uPnsb6bxYJqurBkVZDW+72/4O1qSnSWR0wNH1g6y2qPSPoOsjj1iw+p/a2bNyY3fWu4ek3GxMNx0OSbTaG/yq6NrNzv66u9M6bT03G9CtzrXvcbL736vssd9O2xJTS+sRuxsW7qP7Uvwaaa9voUspeLLD7ams9em6x19z3MqZWxZ/1LxM9nT8ai7qV4v6X+rZvTCyjax7R7WOeyn1/ScxzLqbfW/S/wCkW5f0nCyOo09RyGutuxmxjse4mqt2v6xXj/zf2n3bfX+mlb0nBs6lV1TY5mbS0s9Wtzmb6z/gMprDsyamu99bLv5t/wDNpKeJss6Xi5+Z9jyMZuC57fsza78EhrWt/TusHUqr8ht1mU657/crr34NX1Z6nkOqtx3tymV51zdjnE1ip1V7K+mtrxrGsqspbVsZT6j/AOk2Lr8zFGVjvoFlmMXx+mxyK7Gwd3ss2u+kqVf1fw6umv6dVbkMrusNuRcLP01rnmbvXvcHbvW+hZs2fo/oJKeFPT7Mbp1TbLb7RVZZiNY71ab29TyamtwbXvD9v6Ftn2f9Ba+j9LvXRde6X00ZONVYMizJox68dj34mRnVbGlzt/6At/W7H/ztj7n2en+YtBv1M+rjG+kMX9WFbq2Yhe91LDZpbkVVOcfSynt9v2hn6RaN2D6nThgDJyKg1rWDJZZ+seyPc7IeH77Hx+ke76aSnjfqmzp2RmYucMZ2OPVcen/ZsK6gFsWUvdnXNN2D6Fvueymt/wCi/wAJb6v6NdB9cKcy3pNj67Kx0/Ha+/qWO9zq3ZFVY9T7IMpjbfs9Nm39P+i9S/8AmPUqr9RauBhY/TsKnBxQW4+O0MYCS4x+89zvpPc73OQuo9KxOpeg3M32U0PFv2fcRVY4Q6v7VUP59lT2+pXU/wDR+okp551nWbur9Gudj43TcvJof6LqnvtaaK215FvT87HdTj+z9J+gspt/Vb/+DsesvqltLepdTqBBycm+2moV7BYw78Ox1tzrSzbRdTX6LLN/5llda7Kjo2LV1J/U3WX35Lt4r9e11jKW2FptZiUu9lDbNjEx6F08vfZtdvtym5r3SJNjNvpsJj+Ybt/mklPMdCysO09TrLbW034kt+wubY4Bh9O6vGdS++9uVb9pqr/sfyFmtx8W3phsb0rG9S7Ev6gzHFnosY1rX4oqbR9nf9od01tdbv6R/TL/ALX+h9Vdz07oeD03J+0Y28FtRorrc4FjGOsdk2emxrW+625+97n70O36vYdnR6+jstvoxa9zZqeG2OY/d6tNlu0/orPU9+1JSvq8SzoGK+zGZiTSLHVUHcCC31DaPTrq/TXfzn0P5xcXg2ZOPgZ/rU1+qcrOyqas9js7d6DHX2Nf6ttX2B9LPToyL/032i7IrXo1VVdNTKamhldTQxjRwGtG1jf81ZeT9Wem5FGRQXW1fa7LH321PDbC253q5OL6u3c3EvcP0lSSnDyAbfq9ZV+h9SrOpmnGpbhhlzfTyGt2Pue2/wBf9F6VjbKfVrsrWPNX2Dp9wvdUHtutwHiysbbrftVt11uz1Ws2vuw8P9bf6FXr+n6nqrus3oHR86o05OMx9TnMdawaCw1sNFPrR7rPRrd+j/63+4qY+qHT2tc2vKzWNfWKSBkE/omgtZj7rGve6ljXfzb0lOR9bX32X42GMTKzsw1Ndg4fp1fYDexptufki1zPtbvTbs9P3/Z2f0b08j9Ii/VfFxcXrDn5eFZidWysdwr20Nx8VlNZrN9GIxlttllnq2VvuyMn9Pd/wVf6Jb2Z0PBzRhi91w/Z8+ia7XVuO5n2d3qW07LXfo/3XqOL9X+n4mczPpdkOurrfS0W32XN22Fjn+3IdZtd+ib9BJTpKv1E2twMg0z6grdtjnzj+yrCSSQaIPYvCiIEcdlyXWC49UyN3ZwDf6oA2r1a3oHTbbC/a+vdqWsdDZ8m67VgfWL6iVZTftHTnuZkNEOFh3NcPA7Rub/WTgQ6nLc7hjP1WOIVZHyvni7X/Fi+37R1Fgn0dlbj4b5cP+oWPV9SfrBZaK3V11idXl8gf2W+5eg/VzoNPQ8D7Ow77bDvusPLnf8AkW/mNRJFMnP83hOE44SE5Tr5fVw0eK3Wb9IfFc99Tv5rrH/p4zv/AD4F0A0MrnGfVrrmJkZb+k9bbiY2Zk2ZZosw2Xltlx33fpnXV7mb/oexMcZbqV2R1LrNHSsx1nTumWXOrprbLbs6ylv2qz9NV/ROn1tZ+/62UukJkysDG+r3V3dVwuo9W6u3PHTzY/HprxW4/vtYcdzrLG227mtre72LfSUpYHXGn/nR9W3jQudnU7vDfjGwf9Klb6z+sdJPUmY76ch2Hm4Vvr4mU1ofseWuqe2yl/ttqtqsex7PYkp4ynrufl9FZ62Q6zf9Ws3KyAY99zXNxmWv/l/zjV2/RavR6L0+mI9PFoZHwrYFkXfVfqmVQ7Ey+qUDDtYab2YuEyix1Ljutxq7zdd6Ndv5/wCjXRNa1jWsYNrGgNa0dgBDQkpdZ8n/AJxR2+yfjuWgs/8A9aL/ANBP+/JKf//X9OSS+SXySUpJL5JfJJSkkvkl8klKSS+SXySUpJL5JfJJSkkvkl8klKSS+SXySUpJL5JfJJSkkvkl8klKSS+SXySUpJL5JfJJSkkvkl8klKSS+SXySUpJL5JfJJSkkvkl8klKSS+SXySUpJL5JfJJSkkvkl8klKWf/wCtF/6Cf9+Wh8ln/wDrRf8AoJ/35JT/AP/Q9OSSVPqPVcXpwrFofbfedtGNUN9thH7jP3W/nPckpuJLId1zPob62X0fJqxxq+xjmWuaP3n01nf/AJi08bJoyqGZONYLaLRuZY3ghJSRJIAngJ4PgkpZJJJJSkkoTwR2SUskn2nwKaDMd0lKSSSSUpJPtd4FMkpSSSSSlJJJJKUkkkkpSSSSSlJIdmRj1O222sY791zgD9yz+rfWXpHSccXZF4sc7Sumoh73Efutn2/13pLo45yIEYmRO1B1ElxlP+M3BdaG3YN1dRP8417XkDx9P2/9Uutw83FzsavLxLBdRaJY9v5P5Lm/nNRor8vL5cVHJAxvr0/5qZJJYmZ9dPqtg5VuHldQazIodstYGWP2uHLC+qt7NzfzvcgxO2ksjpv1t+rnVMtuFgZzbsl4LmVFljC4NG5+z1q62v2t/NWukpSSSZzmsY6x7gxjAXPc4gAAauc4n81qSl0lmdO+svQuqZAxcHMbde5pexm17N7R9J9LrmVtua3/AIJafCSlLP8A/Wi/9BP+/LQWf/60X/oJ/wB+SU//0fTlj9Ka2/rfVsy3W6ixmJTP5lTWNthn7vqvfucthZObiZ+J1B3VemMGR6zWszcInabNn83fQ8+312N9vv8A5xJTrAxqFj9IY3F6z1bBpG3GBqyWMH0WPuB9ZrR+bv2+ond1zNsbsxOkZbsk6AXhtVbT42Xbnez+orHSOnW4VVtmTYLs7Lf62Xa3RpdG1tdQ/wBFSz2MSU1es14l3V+l4t7bic03VB9WTdQGCqt2VJqx31tudZt9P3rK+pprtNVltXUrchlmU37Zdba/FcK7bqWN/SXuY/8ARNbWz9B9Ni2OqdEys/qGNn09Rsw3YQccetlVT2h72upuscbmlz/Uqdt2IfSeg5/SxVTX1a23Dqe97sZ9NI3Gxz7rAbmM9Vv6a1z/AGJKcfq7MrH6g+rrfW8mvFxmt6j0y5lWO1zn07mZGM0/Z/02TX6lfpY//aijI/wq6D6v09ZZ06qzrOU/Jzb2NssrcytgpJEmhnoV17ts/pN+/wDSKrZ9Vq857reuZl3UrGknGa39Xqx9S6u3EpxzublV/wDcu2227/R+mr/TMLNwq7KsnPs6gzcPs77mMbaxgG307baQxuS7d/hfSY9JTylmbkWfWfrtRe7Jwy3GoDXOOJUHNc4en+06Gvuqsott9Gr/ALm2erV/gla6Vf8AaOl9TfRivw3PxLGknNuy7WOm7Hq3Yzg51O59d1jbanf4NbGV0Bt+dfmttDbH/Z7MdhaSxl2N6/pW3sY+v7Qz1Mr1fS3V/pK0Cj6qUU9MOJ9oecmzFsw7ssCA9l1jsm79XDtrffZc2j3/AKD1UlPI14xswLGW2PopNzd1oZY01HHrqoyG0us+xUstycr2Xfzn6L/hV0GVZkf82MHpdI+y5fVcsYX6DezYwXWPzb6hZZbdW37HjXP/AJ32eoiN+pTqyWVZzX0eq670snHbcXvdo12Y/wBWn7a6j/tP67P0a0sHoFeO7p9uRe7Iu6Wy5mMQNlYF+1v8251z91FLfs9H6b+aSU879Z8twz+qVW5b201jHqrwjdZVW+qyqx+UytmP/OXu/wCE/nP5tF+p/VK7H5F1WTZmVUUTh4bLrrLrK2EMfb9hzTtp/wAHXj/p/U/0i6BvS8ug9QuxMtteb1G4W+vZVvbWxjGUVUei2yr1PTrr+nv+m9E6X0fE6bRjMrHqX4uMzEGS76bq2HfH7rN1nv8Aakp8/wAamqzEc1rW5Li61hyrm2ttLw97Hmxn7Ur/AElT/Z9D/B+xegdDc89Ixa7G3NfRU2lzshobZYa2iv7RtbZd7L49Rn6VUesfVr7e5wxH4mFXZW5l27Crusc53NzLnPr2P2n85li18PFqw8SjDpn0saplNc6nbW0Vt3f5qSkqSSSSlJJJJKUkkkkpSBn3Px8K+6v6dbCW/Hx/so6ZzWuaWuEtcIcDwQeySQQCCddXhj7iXOO5x1c46knxJXJ9asdZ1O7dxWQxg8AAvS7fqxWXk05BYw8Mc3dHlukLmfrN9Sc1rvtuE4ZLiItqA2kx+cyT9JOBDrctzWETFyqxWulPGruP8WWVbvz8MmaQGXNHYPJNb4/rtauTq6L1i2z0mYV2/j3N2gfFzvavSPqf9XndFwXm4h2XkkOuI4AH0K2/yWIyIpl+I58XsGHEJSnXCAeLr8z0DfpD4rn/AKmkinrEaf5Yzv8Az4FvgwZXM4eF9aukXZ9eDi4WXjZebfmV225D6ngXuFnpPqbRZ/N/10xw2XUc+3q/WKem9NdXjuwrnNu6rcxpeyxrd1+D0iu4fpsx9H9Lv/mcXG/feukPPgudZhfWXqHW+l53U8fExKOlvus/QXPue821Oxm17X007Pp7925dEkpS5X60dK6fm/WjoBysavIGQMuixtglrgyk5OO2xv53pvY/auqWV13publPwM7pzqxndMvN1Vd5Irsa9j8a+l9lYe+lzqrP0duyz3/4NJTxAZ0fM6Sy13S8NjrehZXU3vZWQa7qi2mv7NL3ejXuc72Lu/q5iVYf1f6bjVMDGsxqiWj95zG2Wv8A6z7HOeufs+rnVbsN/T8bpWF0ttuK7p/2v7XbkGrFtf6uRXXj+jV61jne6vfaz3rr6q2VVMqZOytrWNnmGjaElMln/wDrRf8AoJ/35aCzf0n/ADl/N9P7H57p3f5u1JT/AP/S9OSSlKUlKkpJSlKSlJJSlKSlJJSlKSlJJSlKSlJJSlKSlJJSlKSlJJSlKSlJJSlKSlJJSlKSlJJSlKSlJJSlKSlJfFKUpSUxFdYMhgB8YUkpSlJSkkpSlJSkkpSlJSkkpSlJSkkpSlJSlmzZ/wA5Y2j0/sf0p1ndxtWlKz//AFov/QT/AL8kp//T9OSSQcvNxMGg5GZa2ilpje88k/mtH0nu/qpKTJLIb9aujFwFj7aGOMNtupsrrP8A117drVrgggOBBBEgjUEHuElKSSSSUpJJJJSkkkklKSSSSUpJJJJSkkkklKSSSSUpJJJJSkkkklKSSSSUpJPB8FXzc7D6fjuyc21uPS3QveY1/db+c938lqSQCTQFk9AnSXP0/Xz6s23Cr7Q+uTAssrc1n+f+b/aW+1zXtD2ODmOALXNMgg8Oa4JUuniyY644She3EOFdJJBszMOp5rtyKq3jlj3taRP8lzkliZJCqysW5xZTfXa4CS1j2uMeMNJRUlKSSSSUpJDqyca5zmU3V2ur+m1j2uLf64aTtRElKWf/AOtF/wCgn/floLN9Nn/OX1I9/wBj27vLckp//9T05YuJSzqPXs3LyR6jOmPbjYdTtWscWi2/I2/6V+7buW0sXJN/RupX9QbU+/pudtOWKhufTawbPtHpj3WU2s/nNn0ElOzY1trHV2tFlbxDmOEtIPZzSsjobPsWbn9Ha4uxsU13YgOuyu4F3oSfza7G+xSf9auhhn6C85dp0Zj0Mc6xx/d2bfZ/bU+i4mWw5PUM9oZmZ7w99QMiqtg2UY+785zW/wA5/LSU1+oZnVszqz+jdIvZg/ZqGZGZnPrFzm+q57MfHx6HuZXvf6Nj7bbFSzeode6Tn9Gx8q13VH3vy2PrxKm1OvDa2OxvVrsf6VTqnl/qW+rXQxaXUujZd2ezqvSssYOe2v0LfUr9am6oE2V15FO6p++mx36G+qz1P5ytRo6L1F2b0/P6j1BuXkYDslx2UippbkMbU2mtrXv2Mx9n07PVstSUs36yttwRlY3T8m60XPxsjHmmt1FtR2215F2RfVjf8T6dtnrITfrhiZAx29OwsvPtysd2UyqptbS1jHnHuZeb7amstqta+v8A4R/82gZP1QssuGRXkUWvGbk5goy6PWxyMoVtLX0+rXuyMb0v1fI3fn2ez9IqeD9W+t9K6ni0dMya2txun21OzLscupc63Kdk+i2iq6r0LKW2epW1tmxJTeyPrXa/K6IemYluVh9VdaLHAMa8emyzdRtuuqdVkY11X6zvbs9Ouz099isD62YJyxUKLjhuyfsQ6iPT9E5G70fT9P1ftfo+v+r/AGr7P9n9b/txMz6suxsbpTMHK2ZHSrrLvWuZ6guOQLW5vq1sfVsdc7Issr2P/RKtjfU4Yme2zHdh/Y25JymmzDZZmDc/134rc579vo+q79Hd6P2qpns9T/CJKbbPrRQ/puf1MYl9WH0+u53r3bGttfQ6yqyqlrLH2O/SVbfU2LIzuqfWDCq6dT1LqJ6VXZiMtyeqOxW3sdmPP6TCvgehhVUtc3Y97avX/wBN+jWo/wCrTndCq6KcgOp+1/aMp5af0lRyX9RfjNG7273OZTvVvqWJ127I9XpvUasal9fp2Y2Rj+uyZP6xU9lmPb6m07fSs9SlJSDJ+sDcI42GK3dVz7MduTb9j9NjPSkV/amnJuZVtyLP6NQy62yxRd9asS1mMel42R1SzKoGWKscNaWUElnqXOyX01se6xllddG/1bLKrFRf9R6aWYX2J+PbZh4ow3DqOOMqt7Gudcy5jA+l2PdXZbd9B/pem/0v8GrY+r3UMN9OR0nOqoym4zMTK9XGaaLW1lz6bq8XGfjNxraX3XbGV/off+kSUtd1/MH1gZ0z7HdXhWYDsp98Vh7DLP0vuu3NbQ1/o2V+l6v2n/gv0iej6yVDFw6sHFzer3Pw6suzaKvVbTY39FbmPssoo+15G1+zHo99myxWcnouRd1HH6g3KAezEswstrq5FrLNtnq1bXt+z2+szf8A4Vip0fVvqXTmY7ukdQrovbh0YOWb6DbXYMZvp4+XVW26p9ORW19ns9Symz+wkpK763YdjqK+nYmT1K3Jx/tdbMdrBFYeabBc7Isp9K2uxjmem7/Cfo02Z105eL0V3SLC13WsmvY4gB7cdgdlZ8seHta9tNP2f/jLUTo/1ap6Tm05NN7rGU4ZxCx49znuudm3ZTrAdv6W2x/6PYh9F+rL+mZGBZZkjIq6ZiWY2O3ZtPqXWC3IyfpO27qq6qWt/rpKcJ/1n6tVjX9UHVsWy2vMtoq6C6pnq2NbkOxK8eqyuz7X9ofV+krf6L/5f6NdI/6w1VdVZ07JxL8Zt1px8fKsNRY+yHuZ+hruflV13Nqf6F1tGyxS6L0DD6UxzvTquynX33HK9JrbIvsff6fqe6z9G2z0vprMr+pr2dSqyvtFBrozjntsOPOXYXGwux8nO9X311+rtq/RfzbK60lMc/65ZLvq6/rXTOnZHpl9TaLbxUGua+xtVlvpev6np/4Kt3+ksrs/mf0i0czqOZZ1bpHT8ZrsY5Aty86t2xz2Y9LdnoWbTdX+ly76Gb6rP8H/ADiH/wA2f+xNn1c+0w6utjGZQZ+fW9uRXZ6Jd9H1GN31+orWF0vIr6rf1XNuZfkW41GKz02FjWNr325Ba177P6Rk2ept3exjGJKdJBy8j7Ni25EbvSaXAeJ7IyjbUy6p9VgllgLXDyKSRVi9r1eMtysq95sute5559xA/stb9Fcl17LuyOoPre9zq8f2MaSSAY979fznLvrfq3nNeRS5ltf5rnHaY/lNhcl9Z/qz1TCudnGr1KbNXmo7y0j85zQPop4IdjlMuH3AOKIsVHpq86u+/wAWvUrrKcrplji6vH220T+aHktsrH8neN64FoL3BjAXvOga0En/ADQvSvqH0G/pmJdl5bdmRlx7Dy1jfotd/LdO5KWzL8SlAcuYyrikRwDrd/N/ivVN5HxXIfVzofRepHrGT1HAx8y/9r5rPVvqbY/a14DGb3gu2M/MauvGhBXI9Mzs/oV3U8W/o3UMv1+o5OXVfiMrfU6u93qVQ999Tt+36fsTHBVlYfRsXreNjfVnpuMOt4hL7rKm+lRj1Wt9J7upPx9jrX217vsuF/Ob/wBP9BdaYnThcpj25Of9YOm24fR8zpWPRfkZXUbMhldTLXW0Ox2vd6Ntvr3us9P6bV1aSlLmPrPj35HXukYgzcvHxeoV5VWRTj2+mHGmr7TVHtdte/8ASMsXTrF+sONmfauldVxMd2W7pd9ll2NWWi11V1VmLYaPUcxj7K/U3+lv/SJKeKxendGpw6M/ppzcLJPRsnqVNteQ39E2nYz7K7Zj1/aK3vd7n2/6Nd/9X23N6FgfaLrMi9+PXZbdcdz3PsaLXy791rn+z+QuOHTH/YD0/pfTeqnJf023pFL8yuqmltd9nrWZGRdv9vpfyG/9bXe49Iox6qAZFNbawfHa0M/gkpIszdZ/zn27P0X2Kd8/nb/ox/VWms//ANaL/wBBP+/JKf/V9OS44SSSUsGtaS5rQ0nkgAFOkkkpSSSSSlJJJJKUkkkkpSSSSSlJJJJKUkkkkpSSSSSlJJJJKUkkkkpSSSSSlJnMa8bXCQnSSU1mdOwmP3sqaHeIABVkAAQNAkkkpSSSSSlJJJJKUkkkkpSSSSSlLP8A/Wi/9BP+/LQWf/60X/oJ/wB+SU//1vTkkkzi1rS5xDWtElxMADzJSUukqtPVel32+jRmUW28bG2NJ+WqtJKUkkkkpSSSSSlJJJJKUkkkkpSSSSSlJJJJKUkkkkpSSSSSlJJJJKUkkkkpSSUpJKUkkkkpSSSeD4JKWSTkEchMkpSSSSSlJJEEchJJSln/APrRf+gn/floLP8A/Wi/9BP+/JKf/9f05YdlA651bJoyiXdM6a5tf2aSG3Xub6j33x9OqlrvZWtxYf2hnRes5Jyz6eB1R7bask/QZeG+nZRc7/B+pt31vSU38jonSMmj0LcOn0/zdjAxzf5Vb2bXscq/RLcmq3L6TlWG+zAc30b3fSfRYN1Pqf8ACV/zau5HUcDFoORkZNVdLRJeXA/5sfS/sqj0Nl2RfmdYurNIzyxuNU8Q8UVDbU+xv5rrnH1ElK6l1fOZ1BvSekYrMrPNQvuffYaqKKnFzKn3WVstsfZfYxzaqKmf8IqWV9Zc/pmR05nW6KcCvJfksyHVvdeHeixj8Z+Jsay39Ysf/R3UeurPUMHq2L1Z3Wej11ZTr6GY+bg3WejvFRe/Gvx8jZa1l1frPZZXY307K1BuF1rN6p0nqWfTRjHCdlGymqw2Fjbq2VY49RzK/Uu3b/V9P2JKbD/rR0FmHRnHKLsfKc5lJrqtseXMn1WHHqqfkVvq2/pG2VexNk/Wv6u4tVN12a3Zk1evR6bX2l9fHqsZQyx+1v8AhP8AR/4RZVvROv49j7MebcezqOVlXYlGS7FfYy4V/ZLHZVTWvb6G231sbf8ApN/5/prP6Tidc6F1bDxaMSrOzaumXerV65raG2Zz7mOZlWVWert3/pfUYx//AFCSnoM/63dJwszpmOX+tX1WXV31h72hm1zqrW+lXY231bG+lt3fo/536Ctf84uiftH9mfa2/bN/o7Nrtnqxv+zfadv2X7Tt/wC0/ressrH6B1TpuJ0M44qy8jpmRfdlVB5pYRli8X/ZXObZ7MV+V+ire39LUxVMT6rdQxs1mLZQcrBZmHLGU7OtZVtNxzW7+l1j+mVP+h/gLX/pbElOtnfWvpzen593TbBl5OG0NFYa/Z61j/slFT7XNbX/AEl36Wtr/UV/IyOpYlOO2vDf1O0jbkPpdVSA5oG5+zJtq9tjt2xlayH9E6gz6n3dOAa7Prtty6WtMhzxlP6lj17tPda3ZWo/Wvp1nVcfEuxulvyMnJaGWZEsbdiUuHqWbKbraa3Zfu2U/wChu/SWfuJKTfVjrnWOq4eNdl9NextxtFmY19IrGx9jG/oRa7I/M9P+b+mqmX9ZOvY+ZmA41LcetwZTS71DewkH0PWNDLaXfaodl+12yimr7Lb+tIPS+hMxOr4s/V442A0NFNzn1vtovZuf61xoyLHXU3/6R7P0WR/xqa76vdXy8ix1Vba6xl9RcfWcGAi92OcW1jXVZG9rvTt9+xJTpdC+sNuRiZd/VrsSluBtbfZWbKwCR/PWjLZV6dWQ39JS3/rah/zj6g1x6vbiOZ9Wz7G2bXfagOf2rZjfzn2B/wDN+hs+1V1frdlX+jD9XMfIqx8+/qGFdYyqjEqFVtYNl9mHVttfRRad1m+/+j2WbN71JvS+pN6V0ht1ROWeqNz8xjDu9IXWX5NrXO/7r+s2qx6SnSzurGl/S8vFsryOnZt7ca17IfPrgtxMim5h27G5DfTs/wCN/kLUWR1jDffZ0vAxqdmO3LZk3vY0CuuvG/WWs9sbX5GR6TK/+urXOuqSlJJJJKUkkkkpR4STOBLTHPZJjw9u5vHceB/dKSl+dCsLJzOoY+RZSchx2OgHTUct7Lcc5rWlzjDRqSsDIozsjIsv+zWAWOloI1DeG/8ARSU6PS89+Tuquj1WDcHDTcOOP3lfWf0rAtx911w2veNrWdwOfctBJS41IXH9I6QOuXdVy87OzxZV1LKxq20ZVtNbaqXCumtlNTmsbtauwHIXFdF+s3QuiXdYwerZQw8o9Vy7hVYyyTXY8Pps9jHt2WM9zElNnJw8foHU8B2DmdQzM65zmt6U/Iff69bhsNln2h/p4lGK/wDT/anf8WurPPiuMwus9FyvrXhf838o5dufbfZ1d+17neiyl/2as231tdRi037PSprfs3rs0lKXPfWHL63X1jp3T+n5lWJT1KvIa6x1HrPrfQz1/Ur/AEtTXeox30f8H6f+EXQrA+s7m4mf0XrFzXfY+n5Fwy7GtL/Trvosx222NrDn+l6pZvf+Ykp5rDHUcKlnUcH6w3ZD7OnX9RZVk0PsZbTUG7nZLbcx7ar/AFHN2eku36JdmZHR8LJzntsysill1rq27Gg2D1Qxte5/821/p/S968+a7plHS/smJ1OrqWYOjZHSMfFxmWOssuyLN9b6xt/m/T2tfuXpGHQcfDx8c801MrMeLGtZ/BJSZZ//AK0X/oJ/35aCz/8A1ov/AEE/78kp/9D05RsrrtrdXaxtlbxDmOALSPNrlJJJTn0fV/oePaLqcChljdWu2TB/k7vorQ5SSSUpJJJJSkkkklKSSSSUpJJJJSkkkklKSSSSUpJJJJSkkkklKSSSSUpDfS1zt4JY/u5hgn+t+8iJJKRihu4Oe51hHG8yB/Z+iiJJJKUkkkkpScOcNJTJJKXLnHQlMkkkpSQMcJJJKX3O8UySSSlLM9Vn/Of0Z9/2LdHlv2rTWf8A+tF/6Cf9+SU//9H06AlASSSUqAlASSSUqAlASSSUqAlASSSUqAlASSSUqAlASSSUqAlASSSUqAlASSSUqAlASSSUqAlASSSU0f250P8A8sMX/t6v/wAml+3Oh/8Alhi/9vV/+TXI/Ur6u9H6p0q3Iz8f1rW3ura7e9vtDKnbYqexv0nuXQf8yfqx/wBwv/Bbf/SqghPNOIkBCj3Mv4OpzPLfDuXzTwzycwZYzwyMYYuH6frG9+3Oh/8Alhi/9vV/+TS/bnQ//LDF/wC3q/8Ayao/8yfqx/3C/wDBbf8A0ql/zJ+rH/cL/wAFt/8ASqdebtD7Zf8AesXD8M/f5r/wvD/6tb37c6H/AOWGL/29X/5NL9udD/8ALDF/7er/APJqj/zJ+rH/AHC/8Ft/9Kpf8yfqx/3C/wDBbf8A0qlebtD7Zf8Aeq4fhn7/ADX/AIXh/wDVre/bnQ//ACwxf+3q/wDyaX7c6H/5YYv/AG9X/wCTVD/mV9V/+4f/AILb/wClVX6j9Wfqd03Efl5eLsqZ4W2kuJ+ixjfV9z3JE5hqRD7Zf96mMPhsiIxlzRkTQAx4f/Vrr/tzof8A5YYv/b1f/k0v250P/wAsMX/t6v8A8muGzOndNq22X4mH0muwbqqsq3JtyC0mGPfTjW/omu/lI2Jg9Aqay3qWDRdgvf6Y6jhX3uqY4/Rbk02W+rQme7kuqh9sv+9bJ+H8lw8QlzBvpGGE34Rl7vBOX9XHN7P9udD/APLDF/7er/8AJpftzof/AJYYv/b1f/k1QH1K+q5AIw5B1BFtv/pVP/zJ+rH/AHC/8Ft/9Kp95u0Ptl/3rV4fhn7/ADX/AIXh/wDVre/bnQ//ACwxf+3q/wDyaX7c6H/5YYv/AG9X/wCTVH/mT9WP+4X/AILb/wClUv8AmT9WP+4X/gtv/pVK83aH2y/71XD8M/f5r/wvD/6tb37c6H/5YYv/AG9X/wCTS/bnQ/8Aywxf+3q//Jqj/wAyfqx/3C/8Ft/9Kpf8yfqx/wBwv/Bbf/SqV5u0Ptl/3quH4Z+/zX/heH/1a62NlYmWw2Yt1eRWDtL6nB4BGu3cwu93uVSW/wDOPbI3fZJ2zrG7wWN/i4/5Dv8A/DT/APz3Qtna3/nJu2jd9jjdGsbuJQ90+z7la1dLvuEf9I/c+M8Pue3x/pP/0vTkkkklKSSSSUpJJJJSkkkklKSSSSUpJJJJSkkkklKSSSSUpJJJJSkkkklPK/4uP+Q7/wDw0/8A890Lqlyv+Lj/AJDv/wDDT/8Az3QuqUXL/wA1Dyb/AMX/AN38x/fUkme9lbS+xwYxurnOMAD+U4obcvEeCWX1OA5h7TH/AElK0EqSg66lu3dYxu8FzJcNQ0bnub+9sb9JMMjHLi0WsLm7ZEj/AAn81/25+YkpIsHPazN+teDhW+6rDx35oYfomwvGPU4/8V9Nq3lhdfbfg52L16lhtrxmupza2iXeg/3eq3/iH+9Mnt4AgnybHK/zhANSlCcYf7SUKj/jfIjzundO6TkZfXcii7qN2QWtFYY2w1iI/RtO39Hta1u5Z/1Jxac6nq9rmMbhZ1uwYYM7B753N/M9tn/QU/8AnB1nEz78mqmzrHSMoh2LZjwfT5Pp/omF25rv0b2XKt0zLyOkv6h1TLxxRm9VePsPTG/zjnS4hz6/pMbus973t/0iiuPGD0HFenf9Li/S4nQGPN93yQJEskxiGOQn+5KP6n2f8lLD/lJ/oO79UbLHdGbRY4vOJZZjBx7trcW1/wCbXtatpZ3QcB/TOkVUZDgbgHW5DyRG95Ntnu/k7tquOy8RoYXXVgWO9Nh3CC+N+wa/S2qaAIjEHs5vMyjLPklHWJnIgjrrulSUXW1MDS57WiwhrCSIc4/Ra395zkhZWbDUHD1GtDnM7hpJDXf2tqcwskkkklPK/wCLj/kO/wD8NP8A/PdC2v8A1ov/AEE/78sX/Fx/yHf/AOGn/wDnuhbX/rRf+gn/AH5Vf/Av+C7v/l//AOrf9y//0/TkkkklKSSSSUpJJJJSkkkklKSSSSUpJJJJSkkkklKSSSSUpJJJJSkkkklPK/4uP+Q7/wDw0/8A890Lqlxn1B6l07E6PdXlZVOPYclzgy2xrCQWUjdte5vt9q6X9u9D/wDLHF/7er/8mocEojFDUbd3T+K4Msue5gjHMgz3EZJepbf2bl742+hZM8fRd4rnmXYtbLszIYy5mI7HsuY70rHem6p2P/gWiv8AnHsW0/rXQLGFlmdiPY76TXW1kH4tLlW+0/VX1/X+2YkyCK/Wr9MODTW1/oh2zfsc5Sccf3h9rR+75v8ANT/xJMnUHB6fhVOexr6arC2osD/0orffvpeP5r0ff/wVlSzsS1jbcJ7LA59luO0s9Sp7QI9P9HQyljq9tdj2+x/6NX/tX1X9KuoZ2OK6q7Kqm/aGHay3Sxrdz/3fZX/o2Kb+ofV2w4+7qVG3F2musZLAwln82+xu/wB7mJccf3h9qvu+b/NT/wASTrJc6FUf270P/wAscX/t6v8A8ml+3eh/+WOL/wBvV/8Ak0uOP7w+1X3fN/mp/wCLJq3fVTotlzr66nY1j/pnHsfUD/Yrc1iP0/6v9J6bYbsWgC93NzyX2a/8JaXub/ZU/wBu9D/8scX/ALer/wDJpft3of8A5Y4v/b1f/k0P1YN+n8GU/fZR4T7xjVUfc2bGa+yvDvfUwW2NreWVmIcQDDXbvauVvAporZuYzZjmoBkNYZfQ2yxjH13fp7a7v0r10LuudCc0td1DFLXAgj1q+Dofz1Vbk/VRjXMZl4jGPa1m1t7AA1h3s2w/2+/3vd/hEeOP7w+1i+75v81P/Ekj9cN6Tim59ZwmZJotZYA5pqY9zKNtv6Pa6n02em/Z71Ypyw/r9rv8BbU3Grf431h2VZV/21d/nssSZ1D6ttpbSc/Gsax5ta6y+tzvUJL/AFtxd/O7ne1R+2fVf7IzD+24voVkPZ+sM3B7XeoLvV9T1PW9T3usS44/vD7Vfd83+an/AIknXSVH9vdEP/eji/8Ab1f/AJNL9u9D/wDLHF/7er/8mlxx/eH2q+75v81P/Ek4f+Lj/kO//wANP/8APdC2v/Wi/wDQT/vyxf8AFx/yHf8A+Gn/APnuhbG8f85dkOn7HO6Pb9L9795V/wDwL/gux/5f/wDq3/cv/9T05JfLiSSn6jSXy4kkp+o0l8uJJKfqNJfLiSSn6jSXy4kkp+o0l8uJJKfqNJfLiSSn6jSXy4kkp+o0l8uJJKfqNJfLiSSn6H/5k/Vj/uF/4Lb/AOlUv+ZP1Y/7hf8Agtv/AKVXzwkoP6N/q/8AmOp/w1/5W/8Atw/Q/wDzJ+rH/cL/AMFt/wDSqX/Mn6sf9wv/AAW3/wBKr54SS/o3+r/5iv8Ahr/yt/8Abh+h/wDmT9WP+4X/AILb/wClUv8AmT9WP+4X/gtv/pVfPCSX9G/1f/MV/wANf+Vv/tw/Q/8AzJ+rH/cL/wAFt/8ASqX/ADJ+rH/cL/wW3/0qvnhJL+jf6v8A5iv+Gv8Ayt/9uH6H/wCZP1Y/7hf+C2/+lUv+ZP1Y/wC4X/gtv/pVfPCSX9G/1f8AzFf8Nf8Alb/7cP0P/wAyfqx/3C/8Ft/9Kpf8yfqx/wBwv/Bbf/Sq+eEkv6N/q/8AmK/4a/8AK3/24fof/mT9WP8AuF/4Lb/6VS/5k/Vj/uF/4Lb/AOlV88JJf0b/AFf/ADFf8Nf+Vv8A7cP0P/zJ+rH/AHC/8Ft/9Kpf8yfqx/3C/wDBbf8A0qvnhJL+jf6v/mK/4a/8rf8A24fpnpvS8HpdDsfAq9GpzjY5u5zvcQ1u6bXPd9FjVW3n/nNsgbfsn0twndunZs+l9D3r5vSUno4P0eD/AJlNL+lfeP8AK/eeL+v944//AErxv//Z\n"
  },
  {
    "path": "app/src/main/assets/web/help/md/webDavBookHelp.md",
    "content": "# WebDav 书籍简明使用教程\n\n> 本帮助页会在第一次进入时弹出，后续则不再出现，如想查看，请点击右上角 “**⁝**” > 帮助 查看此页。\n\n虽然阅读主要是用来看网络小说的工具，但为了方便书友，也提供了一些本地书籍阅读的简单支持（epub、txt）\n\n但阅读本地书籍的一个难题就是如何在多设备上同步阅读进度以及书籍，假如换了设备之后，原来设备上的本地书籍也要再次手动导入，不太方便。\n\n阅读本身没有自己的服务器，没有类似多看、微信读书那种服务器存储的可能性，但是，阅读支持 WebDav 备份，那么我们也可以利用 WebDav 来同步书籍。\n\n### 前提条件\n1. 配置好书籍存储位置（WebDav书籍下载存储到的位置）：依次点击我的/其他设置/书籍存储位置，选择书籍保存位置即可。\n\n2. 配置好 WebDav 备份（WebDav书籍的保存位置）：我的/备份与恢复/WebDav设置。这里需要配置 WebDav 备份的服务器地址、账号、密码。详细的配置方案这里不赘述，请看这篇文章：[坚果云注册与配置 · 语雀 (yuque.com)](https://www.yuque.com/legado/wiki/fkx510) 或点击该页面右上角的帮助按钮，查看配置方法。\n\n### 上传书籍到 WebDav\n\n配置好 WebDav 后，从主界面进入 WebDav 书籍页没有任何书籍显示，这是很正常的，因为我们WebDav的服务器上还没有任何书籍。\n\n目前将书籍上传到 WebDav 的方式有三种：\n\n1. App 上传已导入的本地书籍。\n\n   长按已导入的本地书籍进入书籍详情 > 右上角 “**⁝**” 找到 **上传 WebDav** ，点击，等待几秒后即可上传成功。\n\n2. App 上传已缓存的网络书籍。\n\n   主界面右上角点击更多设置 > 点击缓存/导出，在此页面右上角 “**⁝**” 找到 **导出到 WebDav** 并勾选。那么在书籍导出的时候便会自动上传一份到 WebDav 服务器中。\n\n3. 使用坚果云客户端/自建WebDav服务客户端上传。\n\n   对于大部分用户来说，App上传足够了，但有些用户书籍数量可能比较大，那么我们不建议您一本一本通过 App 上传，更好的方式是使用您所使用的 WebDav 服务的客户端批量上传。\n\n   假设我们使用的坚果云的 WebDav 服务，进入 [坚果云官网](https://www.jianguoyun.com/d/home#/) ,下载对应平台的客户端安装运行，找到文件夹目录 legado/books ，这里就是存放书籍的位置，您可以批量将书籍上传到该文件夹下。\n\n**不管是使用上述的任何一种方式上传的书籍，为了确保上传无误，请您最好在上传书籍后进入 WebDav 书籍页 检查是否能看到已经上传的书籍。**\n\n### 下载 WebDav 书籍到本地\n\n与上传方式的多种多样不同，下载书籍到本地的方式比较朴素。\n\n在 **WebDav 书籍页** 浏览已经上传的书籍，找到自己要下载的书籍，点击 **加入书架** 按钮，软件则会自动下载该书籍到本地并加入到书架中。\n\n### 注意事项\n\n- 如果使用的是坚果云的 WebDav 服务，免费流量限额对于同步App设置等以及 **少量的书籍** 足够了。但是如果是频繁需要上传/下载书籍的用户流量可能就不太够用了，请注意个人的用量，避免出现超出限额影响 App 设置等的同步。\n\n### 常见问题\n\n- 进入 **WebDav书籍页** 提示 \"获取WebDav书籍出错 webDav 没有配置\"。\n\n  > 这是因为没有配置 WebDav 同步服务，按照上文 前提条件 中提到的配置 Webdav 同步的方法配置好就行了。\n\n- A 设备上传的本地书籍能否在 B 设备上看到，是否能够自动加到书架？\n\n  > 如果 A 设备和 B 设备配置了相同的 WebDav 服务，那么 B 在 **WebDav 书籍页** 就能看到 A 上传的书籍。但是无法直接在书架上看到该书籍，这个可能后续会想方案来做，目前必须自己在 **WebDav 书籍页** 找到该书籍手动点击 **加入书架** 导入才行。\n\n- 本地书籍的阅读进度/书签等是否同步？\n\n  > 可以同步。"
  },
  {
    "path": "app/src/main/assets/web/help/md/webDavHelp.md",
    "content": "# WebDav备份教程\n\n### 阅读支持云备份,采用WebDav协议,所有支持WebDav的云盘都可以,建议采用坚果云,每月免费1G流量,用来备份阅读足够了,下面就采用坚果云来讲解配置步骤.\n\n1. 打开坚果云网站 https://www.jianguoyun.com/d/home#/\n2. 如果没有注册过坚果云先注册一下\n3. 登录坚果云\n4. 右上角用户名点开点账户信息\n5. 点击安全选项\n6. 在第三方管理里添加应用\n7. 将应用示例里的服务器地址,用户名,和密码填到阅读的WebDav设置里\n8. 阅读的WebDav配置在我的-备份与恢复,创建子文件夹选项保持默认即可\n9. 设置完成后手动执行一下备份,看看是否成功\n10. 恢复时选择想要恢复的备份文件\n\n### 自动备份说明\n\n* 设置好备份之后每次退出App会自动进行备份\n* WebDav同一天的备份会覆盖,不同日期的备份不会覆盖\n"
  },
  {
    "path": "app/src/main/assets/web/help/md/xpathHelp.md",
    "content": "# xpath 路径表达式详解\n\n_注：本文所有代码均通过 Chrome(版本 123.0.6312.86) 验证_\n\n> XPath 规范中定义了 13 种不同的轴（axes）。  \n> 轴表示与元素的关系，并用于定位元素树上相对于该元素的元素。\n\n-   `namespace`（不支持）\n-   `attribute` 元素的属性。它可以缩写为 `@`\n-   `self` 表示元素本身。它可以缩写为 `.`\n-   `parent` 当前元素的父元素。它可以缩写为 `..`\n-   `child` 当前元素的子元素。\n-   `ancestor` 当前元素的所有直属祖先。\n-   `ancestor-or-self` 当前元素及其所有直属祖先。\n-   `descendant` 当前元素的所有递归子元素。\n-   `descendant-or-self` 当前元素及其所有递归子元素。\n-   `following` 当前元素之后出现的所有元素。无视元素层级，但不含直属后代。\n-   `following-sibling` 当前元素之后出现的所有同级元素。\n-   `preceding` 当前元素之前出现的所有元素。无视元素层级，但不含直属祖先。\n-   `preceding-sibling` 当前元素之前出现的所有同级元素。\n\n```js\n// 轴的用法-> 轴名::表达式\n// 例:\n> $x('//body/ancestor-or-self::*')\n< [body, html]\n```\n\n#### 一、xpath 表达式的基本格式\n\n> xpath 通过\"路径表达式\"（Path Expression）来选取元素。  \n> 在形式上，\"路径表达式\"与传统的文件系统非常类似。\n\n```txt\n# \"/\"斜杠作为路径内部的分割符。\n# 同一个元素有绝对路径和相对路径两种写法。\n# 绝对路径必须用\"/\"起首，后面紧跟根元素，比如/step/step/...。\n# 相对路径则是除了绝对路径以外的其他写法，比如 step/step，也就是不使用\"/\"起首。\n# \".\"表示当前元素。\n# \"..\"表示当前元素的父元素\n```\n\n### 二、选取元素的基本规则\n\n```txt\n- \"/\"：表示选取根元素\n- \"//\"：表示选取任意位置的某个元素\n- nodename：表示选指定名称的元素\n- \"@\"： 表示选取某个属性\n```\n\n### 三、选取元素的实例\n\n```html\n<!DOCTYPE html>\n<html>\n    <head>\n        <meta charset=\"utf-8\" />\n        <title>标题</title>\n        <meta property=author\" content=\"作者\" />\n    </head>\n    <body>\n        <div>\n            <title lang=\"eng\">Harry Potter</title>\n            <p>29.39</p>\n            <p>usd</p>\n        </div>\n        <div>\n            <title lang=\"cn\">Cpp高级编程</title>\n            <p>39.95</p>\n            <p>rmb</p>\n        </div>\n        <div id=\"list\">\n            <dl>\n                <dd><a href=\"/1\">一</a></dd>\n                <dd><a href=\"/2\">二</a></dd>\n                <dd><a href=\"/3\">三</a></dd>\n            </dl>\n        </div>\n    </body>\n</html>\n```\n\n```js\n// 例1\n> $x('/') // 选取根元素,返回包含被选中元素的数组。\n< [document]\n// 例2\n> $x('/html') // 选取根元素下的所有 html 子元素，这是绝对路径写法。\n< [html]\n// 例3\n> $x('html/head/meta') // 选取 head 元素下的所有 meta 元素，这是相对路径写法。\n< [meta, meta]  // <meta charset=\"utf-8\">, <meta property=\"author\" content=\"作者\">\n// 例4\n> $x('//p') // 选取所有 p 元素，不管它们在哪里\n< [p, p, p, p] // <p>29.39</p>, <p>usd</p>, <p>39.95</p>, <p>rmb</p>\n// 例5\n> $x('html/body//a') // 选取 body 元素下的所有 a 元素\n< [a, a, a] // <a href=\"/1\">一</a>, <a href=\"/2\">二</a>, <a href=\"/3\">三</a>\n// 例6\n> $x('//@lang') // 选取所有名为 lang 的属性。\n< [lang, lang] // lang=\"eng\", lang=\"cn\"\n> $x('html/head/meta/@content') // 选取 head 元素下所有 meta 元素的 content 属性。\n< [content] // content=\"作者\"\n// 例7\n> $x('//meta/..') // 选取所有 meta 元素的父元素。（相同的结果只会返回一个）\n< [head] // <head>...</head>\n```\n\n### 四、xpath 的谓语条件（Predicate）\n\n> 所谓\"谓语条件\"，就是对路径表达式的附加条件。  \n> 所有的附加条件，都写在方括号 `[]` 中，用于对元素进一步的筛选。\n> 方括号内的表达式结果为 true 的元素才会被选取。\n\n```js\n// 例8\n> $x('html/head/meta[1]') //  选取 head 元素下的第一个 meta 元素\n< [meta] // <meta charset=\"utf-8\">\n> $x('//p[1]') // 选取所有元素下的第一个 p 元素\n< [p, p] // <p>29.39</p>, <p>39.95</p>\n// 例9\n> $x('html/head/meta[last()]') // 选取 head 元素下的最后一个 meta 元素\n< [meta] // <meta property=\"author\" content=\"作者\">\n// 例10\n> $x('html/head/meta[last()-1]') // 选取 head 元素下的倒数第二个 meta 元素\n< [meta] // <meta charset=\"utf-8\">\n// 例11\n> $x('html/head/meta[position()>1]') // 选取 head 元素下的除了第一个元素外的所有 meta 元素\n< [meta] // <meta property=\"author\" content=\"作者\">\n// 例12\n> $x('//title[@lang]') // 选取所有具有lang属性的title元素。\n< [title, title] // <title lang=\"eng\">Harry Potter</title>, <title lang=\"cn\">Cpp高级编程</title>\n// 例13\n> $x('//title[@lang=\"eng\"]') // 选取所有lang属性的值等于\"eng\"的title元素。\n< [title] // <title lang=\"eng\">Harry Potter</title>\n// 例14\n> $x('/html/body/div[dl]') // 选择 body 的 div 子元素，且被选中 的 div 元素必须带有 dl 子元素。\n< [div] // <div id=\"list\"><dl id=\"list\">...</dl></div>\n// 例15\n> $x('/html/body/div[p>35.00]') // 选取 body 的 div 子元素，且被选中 div 元素的 p 子元素的值必须大于 35.00。\n< [div] // <div><title lang=\"cn\">Cpp高级编程</title><p>39.95</p><p>rmb</p></div>\n> $x('/html/body/div[p=\"rmb\"]') // 选取 body 的 div 子元素，且被选中 div 元素的 p 子元素的值必须等于 \"rmb\"。\n< [div] // <div><title lang=\"cn\">Cpp高级编程</title><p>39.95</p><p>rmb</p></div>\n// 例16\n> $x('/html/body/div[p=\"rmb\"]/title') // 在例14结果集中，选择title子元素。\n< [title] // <title lang=\"cn\">Cpp高级编程</title>\n// 例17\n> $x('/html/body/div/p[.>35.00]') // 选择值大于 35.00 的 \"/html/body/div\" 的 p 子元素。\n< [p] // <p>39.95</p>\n```\n\n### 五、通配符\n\n-   `\\*` 表示匹配任何元素。\n-   `@\\*` 表示匹配任何属性名。\n\n```js\n// 例18\n> $x('//*') // 选取所有元素，结果以递归顺序返回\n< [html, head, meta, title, meta, body, div, title, p, p, div, title, p, p, div, dl, dd, a, dd, a, dd, a]\n// 例19\n> $x('/*/*') // 选取所有第二层的元素\n< [head, body] // <head>...</head>, <body>...</body>\n// 例20\n> $x('//dl[@id=\"list\"]/*') // 选取 id=\"list\" 的 dl 元素的所有子元素。\n< [dd, dd, dd] // <dd><a href=\"/1\">一</a></dd>, <dd><a href=\"/2\">二</a></dd>, <dd><a href=\"/3\">三</a></dd>\n// 例21\n> $x('//title[@*]') // 选取所有带有属性的 title 元素。\n< [title, title] // <title lang=\"eng\">Harry Potter</title>, <title lang=\"cn\">Cpp高级编程</title>\n```\n\n### 六、选择多个路径\n\n-   用 `|` 合并多个表达式的选取结果。\n\n```js\n// 例22\n> $x('//title | //a') // 选取所有 title 和 a 元素。\n< [title, title, title, a, a, a]\n\n```\n\n### 七、xpath 的函数\n\n> xpath 函数的参数可以是静态字符串或表达式，且函数可以嵌套调用。  \n> xpath 的索引均从1开始，而不是从0开始。\n\n```js\n// boolean(expression) 将表达式选取的结果转换为布尔值。\n> $x('boolean(//title)')\n< true\n// number([object]) 将表达式选取的结果转换为数字。(HTML元素内容默认均为字符串)\n> $x('number(//p[1])')\n< 29.39\n// round(decimal) 将数字参数转换为整数并四舍五入。\n> $x('round(//p[1])')\n< 29\n// ceiling(number) 将数字参数转换为整数并向上取整。ceiling(5.2)=6\n> $x('ceiling(//p[1])') // 仅使用匹配表达式的第一个元素\n< 30\n// floor(number) 将数字参数转换为整数并向下取整。floor(5.8)=5\n> $x('floor(//p[1])')\n< 29\n// concat( string1, string2 [,stringn]* ) 字符串拼接，参数为静态字符串或表达式\n> $x('concat(\"cost:\", //p[1], //p[2])') // 仅使用匹配表达式的第一个元素\n< 'cost:29.39usd'\n// contains(haystack, needle) 判断 haystack 是否包含 needle，返回 boolean\n> $x('contains(//p[1], \"29.39\")') // 仅使用匹配表达式的第一个元素\n< true\n> $x('//title[contains(., \"Harry\")]') // 选取内容中包含 \"Harry\" 的 title 元素。\n< [title] // <title lang=\"eng\">Harry Potter</title>\n// count( node-set ) 统计表达式选取的元素个数。\n> $x('count(//p)')\n< 4\n// id(expression) 根据 id 属性选取元素，若参数为表达式，将获取表达式结果作为id查询。\n> $x('id(//dl/@id)') // 等效于 $x('id(\"list\")')\n< [dl#list] // <dl id=\"list\">...</dl>\n// last() 返回当前路径表达式匹配的同级元素集合的成员数量。\n> $x('//p[last()]')\n< [p, p] // <p>usd</p>, <p>rmb</p>\n// name([node-set]) 返回表达式选取集合的首个成员带命名空间的元素名，HTML中与local-name([node-set])等价。\n// local-name([node-set]) 返回表达式选取集合的首个成员本地元素名。\n> $x('local-name(//*[@id])') //\n< 'dl'\n// namespace-uri([node-set]) 获取选定节点集中第一个节点的命名空间URI。\n> $x('namespace-uri(//div)')\n< 'http://www.w3.org/1999/xhtml' // HTML通常都返回这个固定值\n// normalize-space([string]) 去文本内容中的前后空白以及将内部连续的空白替换为单个空格\n> $x('normalize-space(\"  test    string   \")')\n< 'test string'\n// not(expression) 返回表达式的布尔反值。\n> $x('//title[not(@lang)]')\n< [title] // <title>标题</title>\n// position() 返回选定元素处于路径表达式匹配的同级元素集合中的位置。\n> $x('//meta[position()=2]')\n< [meta] // <meta property=author\" content=\"作者\" />\n// starts-with(haystack, needle) 检查某个字符串 haystack 是否以另一个字符串 needle 开始。\n> $x('//title[starts-with(., \"Cpp\")]')\n< [title] // <title lang=\"cn\">Cpp高级编程</title]\n// string([object]) 将给定参数转换为字符串\n> $x('string(//p)')\n< '29.39'\n// string-length([string]) 返回给定字符串的字符数量\n> $x('string-length(string(//p))')\n< 5\n// substring(string, start[, length]) 截取字符串\n> $x('substring(string(//p), 1, 3)')\n< '29.'\n// substring-after(haystack, needle) 返回字符串 haystack 中第一个 needle 之后的字符串。\n> $x('substring-after(string(//p), \".\")')\n< '39'\n// substring-before(haystack, needle) 返回字符串 haystack 中第一个 needle 之前的字符串。\n> $x('substring-before(string(//p), \".\")')\n< '29'\n// sum([node-set]) 对给定集合的数字求和。若给定集合中存在非数字，则返回 NaN\n> $x('sum(//p[1])')\n< 69.34\n// translate(string, \"abc\", \"XYZ\") 依次替换 string 中出现的 a、b、c 为对应位置的 X、Y、Z。\n// 若第三个参数中的字符少于第二个参数，那么在第一个参数中相应的字符将被删除。\n> $x('translate(\"aabbcc112233\", \"ac2\", \"V8\")')\n< 'VVbb881133'\n// true() 表示函数中的 true 布尔值\n// false() 表示函数中的 false 布尔值\n```\n"
  },
  {
    "path": "app/src/main/assets/web/index.html",
    "content": "<!DOCTYPE HTML>\n<!--\n\tForty by HTML5 UP\n\thtml5up.net | @ajlkn\n\tFree for personal and commercial use under the CCA 3.0 license (html5up.net/license)\n-->\n<html>\n\n<head>\n    <title>Legado web 导航</title>\n    <meta charset=\"utf-8\"/>\n    <meta name=\"viewport\" content=\"width=device-width, initial-scale=1, user-scalable=no\"/>\n    <link rel=\"stylesheet\" href=\"assets/css/main.css\"/>\n</head>\n\n<body class=\"is-preload\">\n\n<!-- Wrapper -->\n<div id=\"wrapper\">\n\n    <!-- Header -->\n    <header id=\"header\" class=\"alt\">\n        <nav>\n            <a href=\"#menu\">Menu</a>\n        </nav>\n    </header>\n\n    <!-- Menu -->\n    <nav id=\"menu\">\n        <ul class=\"links\">\n            <li><a href=\"https://github.com/gedoor/legado\">Github</a></li>\n            <li><a href=\"https://github.com/zsakvo\">zsakvo</a></li>\n            <li><a href=\"#\">Design: HTML5 UP</a></li>\n        </ul>\n    </nav>\n\n    <!-- Banner -->\n    <section id=\"banner\" class=\"major\">\n        <div class=\"inner\">\n            <header class=\"major\">\n                <h1>昨日邻家乞新火，晓窗分与读书灯</h1>\n            </header>\n            <div class=\"content\">\n                <!-- <p>A responsive site template designed by HTML5 UP<br />\n                    and released under the Creative Commons.</p> -->\n                <ul class=\"actions\">\n                    <li><a href=\"vue/index.html\" class=\"button next scrolly\" target=\"_blank\">书架</a>\n                    </li>\n                </ul>\n                <ul class=\"actions\">\n                    <li><a href=\"vue/index.html#/bookSource\" class=\"button next scrolly\"\n                           target=\"_blank\">书源</a></li>\n                </ul>\n                <ul class=\"actions\">\n                    <li><a href=\"uploadBook/index.html\" class=\"button next scrolly\" target=\"_blank\">传书</a>\n                    </li>\n                </ul>\n                <ul class=\"actions\">\n                    <li><a href=\"vue/index.html#/rssSource\" class=\"button next scrolly\"\n                           target=\"_blank\">订阅源</a></li>\n                </ul>\n            </div>\n        </div>\n    </section>\n</div>\n\n<!-- Scripts -->\n<script src=\"assets/js/dist.js\"></script>\n\n</body>\n\n</html>"
  },
  {
    "path": "app/src/main/assets/web/uploadBook/css/wifi_send.css",
    "content": "@charset \"utf-8\";\n/*reset*/\nhtml{-webkit-text-size-adjust:100%}\nbody,p,blockquote,ul,ol,li,h1,h2,h3,h4,h5,h6,dl,dd,input,textarea,button{margin:0; padding:0;}\nbody {background:#f9f9f6;font-size:14px;color:#333;font-family:\"微软雅黑\",Arial,sans-serif,Tahoma,Geneva;line-height:150%;-webkit-tap-highlight-color:rgba(0,0,0,0);-webkit-touch-callout:none}\nh1, h2, h3, h4, h5,em,i { font-weight:normal}\nul, ol { list-style:none }\nem,i{ font-style: normal; }\nimg{border: 0;vertical-align: middle}\n\n/*common*/\n.pc .red{\n    color: #ea5449;\n}\n.pc .black{\n    color: #202020;\n}\n.pc .gray{\n    color: #999;\n}\n.pc .f_20{\n    font-size: 20px;\n}\n.pc .f_18{\n    font-size: 18px;\n}\n.pc .f_36{\n    font-size: 36px;\n}\n.pc .f_14{\n    font-size: 14px;\n}\n.pc .ml_25{\n    margin-left: 25px;\n}\n.pc body{\n    background: url(../img/background.png) repeat;\n}\n.pc .inline-block{\n    display: inline-block;\n}\n.pc .inline{\n    display: inline;\n}\n/*private*/\n.pc .title_wrap{\n    border-bottom: 1px solid #fff;\n    outline: 1px solid #f0efed;\n    width: 100%;\n}\n.pc .main_wrap{\n    width: 900px;\n    margin: 0 auto;\n}\n.pc .s_title{\n    width: 900px;\n    margin: 0 auto;\n    padding: 16px 17px;\n    box-sizing: border-box;\n}\n.pc .s_title .up{\n    vertical-align: 2px;\n}\n.pc .s_logo{\n    display: inline-block;\n    height: 40px;\n    width: 30px;\n    vertical-align: -3px;\n}\n.pc .top_cont{\n    position: relative;\n    padding: 22px 0 22px 20px;\n    overflow: hidden;\n}\n.pc .top_cont .status{\n    font-size: 12px;\n}\n.pc .top_cont .type{\n    font-size: 12px;\n    margin-top: 8px;\n}\n.pc .top_cont .select_btn{\n    position: absolute;\n    right: 0;\n    display: inline-block;\n    line-height: 50px;\n    height: 50px;\n    width: 180px;\n    text-align: center;\n    background-color: #ea5449;\n    color: #fff;\n    border-radius: 3px;\n}\n.pc .select_btn input {\n    position: absolute;\n    left: 0;\n    right: 0;\n    top: 0;\n    bottom: 0;\n    width: 100%;\n    height: 50px;\n    opacity: 0;\n    cursor: pointer;\n    filter:alpha(opacity=0);\n}\n.pc .s_table table{\n    width: 100%;\n    background-color: #fff;\n    border-collapse:collapse;\n    table-layout: fixed;\n}\n.pc .s_table table tr td span{\n    display: inline-block;\n\n}\n/*thead*/\n.pc .s_table thead tr{\n    height: 40px;\n    font-size: 12px;\n}\n.pc .s_table thead tr td:first-child + td{\n    width: 40%;\n    text-align: left;\n    padding-left: 15px;\n}\n.pc .s_table thead tr td{\n    text-align: center;\n    border-bottom: 1px solid #f5f5f5;\n    border-right: 1px solid #f5f5f5;\n}\n.pc .s_table thead tr td:first-child + td + td + td{\n    border-right: 0;\n}\n/*tbody*/\n.pc .s_table tbody tr:nth-child(odd){\n    background-color: #fbfaf8;\n}\n.pc .s_table tbody tr td{\n    text-align: center;\n    line-height: 20px;\n    padding: 11px 0;\n    font-size: 12px;\n    border-bottom: 1px solid #f5f5f5;\n    border-right: 1px solid #f5f5f5;\n}\n.pc .s_table tbody tr td:first-child{\n    padding: 0 15px;\n}\n.pc .s_table tbody tr td:first-child + td + td{\n    padding: 0 15px;\n}\n.pc .s_table tbody tr td:first-child + td + td + td{\n    padding: 0 15px;\n    border-right: 0;\n}\n.pc .s_table tbody tr td:first-child + td{\n    text-align: left;\n    padding-left: 15px;\n}\n.pc .last_row{\n    color: #999;\n    border-bottom: 0;\n    column-span: 4;\n    padding: 12px;\n}\n.pc .op_right{\n    height: 16px;\n    width: 16px;\n    display: inline-block;\n    background: url(\"../img/right.png\") center no-repeat;\n    background-size: 16px auto;\n    vertical-align: -2px;\n}\n.pc .op_wrong{\n    height: 12px;\n    width: 12px;\n    display: inline-block;\n    background: url(\"../img/wrong.png\") center no-repeat;\n    background-size: 12px auto;\n}\n.pc .main_wrap .active{\n    -webkit-box-shadow: 0 0 15px rgba(0,0,0,.1);\n    -moz-box-shadow: 0 0 15px rgba(0,0,0,.1);\n    box-shadow: 0 0 15px rgba(0,0,0,.1);\n}\n.pc .btn{\n    display: inline-block;\n    line-height: 0;\n    float: right;\n}\n.pc .warning{\n    display: inline-block;\n    line-height: 50px;\n    float: right;\n    color: red;\n}\n.pc .warning a{\n    color: #0078a5;\n}\n\n/*h5*/\n.h5 .red{\n    color: #ea5449;\n}\n.h5 .black{\n    color: #202020;\n}\n.h5 .gray{\n    color: #999;\n}\n.h5 .f_18{\n    font-size: 18px;\n}\n.h5 .f_15{\n    font-size: 15px;\n}\n.h5 .f_14{\n    font-size: 14px;\n}\n.h5 .ml_15{\n    margin-left: 15px;\n}\n/*private*/\n.h5 .s_title{\n    line-height: 60px;\n    border-bottom: 1px solid #f0efed;\n    background-color: #fdfdfd;\n    padding: 0 17px;\n}\n.h5 .s_title .up{\n    vertical-align: 2px;\n}\n.h5 .s_logo{\n    display: inline-block;\n    height: 24px;\n    width: 20px;\n    background: url(\"../img/logo.png\") center no-repeat;\n    background-size: 18px auto;\n    vertical-align: -3px;\n}\n.h5 .s_table{\n    margin-bottom: 75px;\n}\n.h5 .s_table table{\n    width: 100%;\n    border-collapse:collapse;\n    table-layout: fixed;\n}\n.h5 .s_table thead tr{\n    height: 56px;\n    font-size: 11px;\n}\n.h5 .s_table thead tr td:nth-child(2){\n    text-align: left;\n    width: 40%;\n}\n.h5 .s_table thead tr td{\n    text-align: center;\n    border-bottom: 1px solid #f0efed;\n}\n.h5 .s_table tbody tr td{\n    text-align: center;\n    line-height: 20px;\n    padding: 20px 0;\n    font-size: 12px;\n    border-bottom: 1px solid #f0efed;\n}\n.h5 .s_table tbody tr td:first-child{\n    padding: 0 10px;\n}\n.h5 .s_table tbody tr td:nth-child(3){\n    padding: 0 10px;\n}\n.h5 .s_table tbody tr td:last-child{\n    padding: 0 10px;\n}\n.h5 .s_table tbody tr td:nth-child(2){\n    text-align: left;\n    font-size: 14px;\n}\n.h5 .op_right{\n    height: 16px;\n    width: 16px;\n    display: inline-block;\n    background: url(\"../img/right.png\") center no-repeat;\n    background-size: 16px auto;\n    vertical-align: -2px;\n}\n.h5 .op_wrong{\n    height: 12px;\n    width: 12px;\n    display: inline-block;\n    background: url(\"../img/wrong.png\") center no-repeat;\n    background-size: 12px auto;\n}\n.h5 .bottom_c{\n    position:fixed;\n    bottom:0;\n    left:0;\n    right:0;\n    text-align: center;\n}\n.h5 .bottom_c .type{\n    line-height: 25px;\n    color: #999;\n    font-size: 10px;\n    border-top: 1px solid #eeedea;\n    background: #f9f9f6;\n}\n.h5 .bottom_btn_wrap{\n    height: 50px;\n    line-height:50px;\n    background-color:#e8554d;\n    font-size:17px;\n    color:#fff;\n}\n.h5 .bottom_btn_wrap input{\n    position: absolute;\n    left: 0;\n    right: 0;\n    top: 0;\n    bottom: 0;\n    width: 100%;\n    display:inline-block;\n    opacity: 0;\n}\n.emphasize{\n    color: #ea5449;\n    font-weight: bold;\n}\n.none{\n    display: none;\n}\n.mask{ width:100%; height:100%;position: fixed;top: 0; left: 0; right: 0; background: rgba(0,0,0,.7); z-index: 99; display: none;}\n.c_tc{ background:url(../img/notice01.png) no-repeat left top; -webkit-background-size:171px 43px;position: fixed;; top: 50%;left: 50%; margin-top: -22px; margin-left: -80px; width: 171px; height: 43px; z-index: 100}\n.t_tc{ background:url(../img/notice02.png) no-repeat left top; -webkit-background-size:216px 84px;position: fixed;; top: 10px;right: 20px;  width: 216px; height: 84px; z-index: 100}\n.safariWarn{background:url(../img/safari.png) no-repeat left top; -webkit-background-size:216px 84px;}\n.close{\n    position: fixed;\n    right: 15px;\n    top: 15px;\n    width: 30px;\n    height: 30px;\n    background: url(\"../img/close.png\") no-repeat center;\n    -webkit-background-size: 15px 15px;\n}\n\n\n\n\n\n\n\n\n\n\n"
  },
  {
    "path": "app/src/main/assets/web/uploadBook/index.html",
    "content": "<!DOCTYPE html>\n<html lang=\"en\"><head><meta http-equiv=\"Content-Type\" content=\"text/html; charset=UTF-8\">\n    \n    <meta name=\"viewport\" content=\"width=device-width,initial-scale=1.0,minimum-scale=1.0,maximum-scale=1.0,user-scalable=no\">\n    <title>WiFi 传输</title>\n    <link rel=\"stylesheet\" href=\"./css/wifi_send.css\">\n    <script src=\"./js/jquery-1.4.2.min.js\"></script>\n<style type=\"text/css\">#htmlToothbrush #bodyToothbrush .parentToothbrush .bilibili-player-video {margin:0 !important;}\n#htmlToothbrush, #bodyToothbrush {overflow:hidden !important;zoom:100% !important;}\n#htmlToothbrush #bodyToothbrush .parentToothbrush {overflow:visible !important;z-index:auto !important;transform:none !important;-webkit-transform-style:flat !important;transition:none !important;contain:none !important;}\n#htmlToothbrush #bodyToothbrush .absoluteToothbrush {position:absolute !important;}\n#htmlToothbrush #bodyToothbrush .playerToothbrush {position:fixed !important;top:0px !important;left:0px !important;width:100vw !important;height:100vh !important;max-width:none !important;max-height:none !important;min-width:0 !important;min-height:0 !important;margin:0 !important;padding:0 !important;z-index:2147483646 !important;border:none !important;background-color:#000 !important;transform:none !important;}\n#htmlToothbrush #bodyToothbrush .parentToothbrush video {object-fit:contain !important;}\n#htmlToothbrush #bodyToothbrush .parentToothbrush .videoToothbrush {width:100vw !important;height:100vh !important;}\n#playerControlBtn {text-shadow: none;visibility:hidden;opacity:0;display:none;transition: all 0.5s ease;cursor: pointer;font: 12px \"微软雅黑\";margin:0;width:64px;height:20px;line-height:20px;border:none;text-align: center;position: fixed;z-index:2147483647;background-color: #27A9D8;color: #FFF;} #playerControlBtn:hover {visibility:visible;opacity:1;background-color:#2774D8;}\n#picinpicBtn {text-shadow: none;visibility:hidden;opacity:0;display:none;transition: all 0.5s ease;cursor: pointer;font: 12px \"微软雅黑\";margin:0;width:53px;height:20px;line-height:20px;border:none;text-align: center;position: fixed;z-index:2147483647;background-color: #27A9D8;color: #FFF;} #picinpicBtn:hover {visibility:visible;opacity:1;background-color:#2774D8;}\n#leftFullStackButton{display:none;position:fixed;width:1px;height:100vh;top:0;left:0;z-index:2147483647;background:#000;}\n#rightFullStackButton{display:none;position:fixed;width:1px;height:100vh;top:0;right:0;z-index:2147483647;background:#000;}</style><style type=\"text/css\">/* Copyright 2014-present Evernote Corporation. All rights reserved. */\n@keyframes caretBlink {\n    from { opacity: 1.0; }\n    to { opacity: 0.0; }\n}\n\n@keyframes rotateSpinner {\n    from {\n        transform:rotate(0deg);\n    }\n    to {\n        transform:rotate(360deg);\n    }\n}\n\n#text-tool-caret {\n    animation-name: caretBlink;  \n    animation-iteration-count: infinite;  \n    animation-timing-function: cubic-bezier(1.0,0,0,1.0);\n    animation-duration: 1s; \n}\n\n#en-markup-loading-spinner {\n    position: absolute;\n    top: calc(50% - 16px);\n    left: calc(50% - 16px);\n    width: 32px;\n    height: 32px;\n}\n\n#en-markup-loading-spinner img {\n    position: relative;\n    top: 0px;\n    left: 0px;\n    animation-name: rotateSpinner;\n    animation-duration: 0.6s;\n    animation-iteration-count: infinite;\n    animation-timing-function: linear;\n}\n</style><style type=\"text/css\">/* Copyright 2014-present Evernote Corporation. All rights reserved. */\n.skitchToastBoxContainer {\n    position: absolute;\n    width: 100%;\n    text-align: center;\n    top: 30px;\n    -webkit-user-select: none;\n    -moz-user-select: none;\n    pointer-events: none;\n}\n\n.skitchToastBox {\n    width: 200px;\n    height: 16px;\n    padding: 12px;\n    background-color: rgba(47, 55, 61, 0.95);\n    border-radius: 4px;\n    color: white;\n    cursor: default;\n    font-size: 10pt;\n    text-shadow: 1px 1px 2px rgba(0, 0, 0, 0.32);\n    font-family: 'Soleil', Helvetica, Arial, sans-serif;\n    border: 2px rgba(255, 255, 255, 0.38) solid;\n}\n\n.lang-zh-cn .skitchToastBox {\n    font-family: '微软雅黑', 'Microsoft YaHei', SimSun,\n        '&#x30E1;&#x30A4;&#x30EA;&#x30AA;', Meiryo, 'MS PGothic', 'Soleil',\n        Helvetica, Arial, sans-serif;\n}\n\n.lang-ja-jp .skitchToastBox {\n    font-family: '&#x30E1;&#x30A4;&#x30EA;&#x30AA;', Meiryo, 'MS PGothic',\n        '微软雅黑', 'Microsoft YaHei', SimSun, 'Soleil', Helvetica, Arial,\n        sans-serif;\n}\n\n.skitchToast {\n    padding-left: 20px;\n    padding-right: 20px;\n    display: inline-block;\n    height: 10px;\n    color: #f1f5f8;\n    text-align: center;\n}\n\n.skitchVisible {\n    /* Don't remove this class it's a hack used by the Evernote Clipper */\n}\n</style><style type=\"text/css\">/* Copyright 2014-present Evernote Corporation. All rights reserved. */\n\n@font-face {\n\tfont-family: 'Soleil';\n\tsrc: url(data:application/font-woff2;base64,d09GMgABAAAAAQ5sABIAAAADuOQAAQ4EAAEAgwAAAAAAAAAAAAAAAAAAAAAAAAAAG4SpIhy4XhSHMgZgAJJmCF4JgnMREAqGskiFyggSgrVoATYCJAOdOAuOXgAEIAWPGAfGYAyDGFsKfLME/nOMvX+HnQoMqyRqK6tt2TW8K2BDX9Umu4PZkCXwtMjpcHYNTx6UUmu30UdwcxxnVMvcNgAim7rdXiPZ////////////C5dF2ObuXM7ZvQeSECARwueHlaLVtkJEIaLRAI3ulswSE13cSPdM15RCyYQuuS7A0mBqpQO9pYZWQQMqa9lQb0dpJzHxHpLyCHIi8uBdwF58nNH+YOA40OpJvEjJtaokKaUkx/N5UeSinp5TWoW1vSpFjimBgeFGeCF66U0n6ZUN/qSqC4XC11GKUNmzSy2d5Jxyqw5JzGUlJUnyi0lKkE8BlxadTZsyRm8c1/Cti2hEjzIHnuOZ0fU6OjKc8DbYCHncqnchZTOgK1VPZRW/YGuwBmwn7Zu9nsUDKh5Cf+FWtrJRPdVHvMS330QH2MJdhDcZZTcpF0Nk4/uQLvb9VMH9BIm7mNJnDvMIs7Kz8oegHuEHJOFz4xnnmPexMfyG7y+G7HrXb+LulK9WFjg1OI/rT/VepENU74ARXdTlRumtpFnXEk5cm/OvP1kvp37Fnco3fIeP3lSv8jdKcGDbqm2V1PwCE1/Hb1mq+5RasMwwM9waPesf3YO/Dbiw2Vb/I/yveWLoShvVic5jU/fQEyN8Dx2EiMJo1jLIfJBOJlU2C1nEtxiWpid99q42kCokIYkPeIN/5qFNS1Ui/pszOD0A24DXSN4D6Lh1KgiMEA3R8QiTnoOJWIiNKIRO1SV4nG8S//x7/f/XmCudxvEFrSd82d4Znp9b7/1Ys2LABtsYK0ZsjAkDERE9bkYNxJo4jMbDQs8zsKhDJAci2sdhFWLc7AbFoGq0pJNj9A9/L/9Va5/bAZtDxAzSYCELNFhPMEs0YCP0AccGNL+j2lL7AZrLiXSYPGGzFHQzWhWuSV0qtSxfOhn0hKG5WYZmfQi7nQIAywboT1iSHWADhXjMNnYA5qbQSi+qWMBgY4wVq2ZjGzFqZIoKCgZGNSpiBdbNK0b/2r+VV7xRP//N2U/a3MiBzkEOMjR7kGmzF5mVW0nl4mHAV6na54r71zOoCpZQE31m1Hz4dV9pQzpVaMEhHznztQihnP1n+2897J8vtXJXCmmkkEEuMpxDIdbIJI36izdCXraeoUluHi5JwNkgxaJgIN4/ccnyUx99wWmh8ARHvqrfXamwcz4Xf2yZC0hV2wA4ymlVJdkze1XqwDG9KPAbWmIPuLtLVifpKI6jzCiwwP7I3/jr2IFdDtACB5ZeyB0AIDDQ/N/v8wshKnRPnVy9ukD4dAoUc03M0pgxY1KYQgp/r6vyfSTpi1tqUOOo56Z35mYPePfMHCQOLzRW7siXOjIHGdBfuvp8krrFLeSRRisaxAX6QLt7QJyf7Si0g8wOAYPQlV8Sm+eNC/LdBSQnvPDBOlOxygq0ZSlexTIewbghomtEAE37m4iq5RuRGIfFJdlJ299UrG1g+WC1WlI5Mch0MMdrdXBOOwY9PV3hjsL6+wsVwAJQ2nnnPk/tTSKoE2p6ekoi6g9U5CRN+xkPmAdEA3SzSxfm38ojXurd8E1q7cJUvq68oDU8BA4P9RNrtdCYrp2ItxP9NbUzw0L5TGxkUqCxDBc5v7ZAnGC10zfftumsZpZFxpZswjX9plBeKL/6ojPkRQQTLgzpXLDyK/jc8mIyqW4brLBWJSatR7R+Set3OZ5h+mO66qqS8nXly+1yqsmJExgkyBAkVvTNH+H2vV9qBwSS1VmVKPD0vG2Z2vP0cc/v7N2Z35OmCRJMIdQQUw12aFvY59gp58AfaoduobTllTCe6m+A1cxx/PnxyspxQlx1nzOAg5HbevAoFKNAKQCBNuKReY3IBoPPaJy0qdIqpe0aYj2YOACkty7W9YBonUwAg/NK6ItBvgLEUmHxZ3s19iddkK9JfTV3nYsnO6bwFQankHjTBgTYsgqfz61rb8HmGgUSJHT6zzQegntV/7iXeUfZGI54OIax0lj6qdRSBSZhRVuuWLk8PF2m7s671MHFAI7wfcMDz/60DlaF0kXCiXAIS9UAUHw+nVWpfutqbe/2u2sPLGTr4wknyJqWoGcOotRSlVxdrlZrgDwarxeol72Ekkol27LdM7zDC3yIEWDIECWXXppJ7kN6+SVReFlwPP/9sv/Zu+pX1++BLOSRT46QRKm7Z/qHzfwQgSHlS36FGkcMlmw88hUKicRZjMSp5DX/v6n2tu8OMLsDhX8AaQPXkWMtjklHyaHnz/H039XvfFy992YAzJuHIRFIGYMRKYpUIBi0ALkBIDcQICkBYFAGKa42p5/igFQAN31xpZ9S0n7HVIUUq1z5uHL5j0tXVY5l4dZFl4veRf871/b/Vap2rWoArWqoAYI0ExOC7JDMBiWz0PbhIpl885BlPWXmFM8pz9fZuqp+d3V1dYMk0KTYIEhKpJwIlOUBteRpm3kNgMhAsPkew2ywNKtmVXyabReZbdds2+Eyl0u2+0jJ+P+vWlaK+x5YqB5pDYZNMVrDgxqXZCwjE/qTz9kkIT4e8YX6/IWeUkk6rVJ1rfGtVkdrPIEHskkA1XK9c9qui2Z8kK6N801DKVMkwPPQ5fu6m6GEcuyKrttO6muUUoo1KgBSukUgJvDpU9Au3ZmAArMuwTTA5EH8ewMQGBkpwuQAJDgECrUFtbU0Nal0sjv0vy/dCxawNxMUo+8oThtbnZPNjdY4dx4sfFhYQAtQ4etbmlQhQQcAjYhM2XXVn+0n7a5GThlgZzrHxkIHqf3eqdL0Sep+vAyejiLZ/mrGmrAbjc3koFMIK2AtxeTmf6CWRi1IF/n4b6S0LoIJy3KYxbnfizWxqjqk3SoAG1xbNGYwptAaIYQQIhGJEIkQwpim9rubB5rD8KXjVDoUeURCOOJ7fk9/Z/f3fvw8XmNURVVVxaiqiog6ohb+txC9941zLD2IplK2gUChEObrr6w0sSkJyPILpWWCZODuPluJg/+Me+F3boUhrDtLaKTifb6fa2x1C8Z1YaKEgFFf7fw2ph36Y22oDyhKJCEE7CWbNdzTFzHEQpASwZMVCteYVaUXPkY3pAmOGRUUBIWGbhz/79f0e5jrluOuv3Sj2FGxA2pAZ0D96n3dl9ONwgXDVrvIPvRFsWMCTiWOZbnnWBqNXpacxP+3uf5cYVrCg5U1gA0kNCvFgg78xX7y/FSsjX/hNkax9MHvBTCTkAqyxod/A/RLzoyLBXwYwGBTgDEJ3NV88Pv93i58Lw7V4vt0RcvvTDv+PdgNIiLy2wpTK0KvKLNi7DPn+Ljldok061OURVaIFdduiaQGBNgpSZps+Y7MTqGVAgSYGgrQWCVbf/0Nn5Q3MhKcglqgUBNANRAKKlEQS4ThLZ69GQGcNP6ht8e/9aez/nFUGS75zf9XKNN4E0w0yWRTTKd30CGHHXHSGeecB0FsEIKJBX5wOYABtQCTdiHdnH7aOG58Z0Ii6SZRk7zJjcmLKX86a1o/GzfPNK9yPn++YdGrRdbFTKacGRafiNuSpORQUlnNqYa5ftxFdXWDuMJ+xp0rf6a8oMSUA+WZspktmlcfU39Ta9TZ8qJ3GdCSaGpaNq2BNo12lQ6jM+g59Br6MiaBG8rlcI2CDUlNKb9S/5ToldFKjGa/5rSGA6n0icDFPQmfjFwZ18ldzG1xnz4NAQaAA/Jy4Vx77thclJvLHQSqQTaoyqvPPwp9gpqhfhgVAGAcTIG5sBjWFvgK9IJVhINwkL7Ct+jq4tm8I/IcUbf4lNomvq35aXNqPusvayfWThocw143tW7WpNUTTGN9W/2yxbZoVkXDxoaPttpukOyU/HQ8R+zkSN0yltsumyhDspJs0C11G91BT5SzPLV8rDwjn/Y6fKbP8ccrCorHAUEJC/iBUSlTepWmsqacDaqDHyFSQUKZClK1qJ6GvdGDWqK+HNXHqrgO3g//T241YzWLKSe1a4E1o3yZbjMfKnZFjn6+vlv/t4xKr8FpWG94XzlXOcbxxunKWh9qu0lift0Mtqu21/Kyi2psa3zc9fdez+m11iKrybrYOj+oBqfNbjs5WDZ+G7N9rlN1AufTvfdReDR6xB6Tp80DPAVP//HTsfnk6VF52jzY89Qz5N15c7zTfW2+lW+0b7u/3W/3S/zT/TH/or8+wA6QAvaAJPA8yAnOCV8IHwx/jvwYWRoZjJRG+gmeYBASIo/QEdOJGDFKlJNG6i0dy/xkf88T+BniCXGv+FiKl9bKF+WV8qAiUqTKMqVTpdWK+l9zNK6m1yTabK2mLWpduqKzdL2u0Nv0mD6ol+qN+qAhGiiDY8iMMkNiWIyxRsZYN4ZNz2SYOabPTJmDZqlFsHCW3YKtxVbJWrWaAQGQgALUAjeYCRKgBSbBd9ABATQgCQqhFkIQhm44FbqwBkdhObTAIaQgHOIjFeIhBbKjiQihEhpEpajRBrZjs2y9XWv7bGBP2vUYwSjMx1pcj614Pq7hXTzoKA7KYThVzmb3gGt2TdHgqDNqic6MdsYOxApj3hiKzcdnxbfGNxNYwpWwptipaZlMdn/WntVlt2e/51a5ttz+3P/8aX5VfjTfVmAXUgvmQkvhaTGs6Cvi4mIJKwlLZaX5penScDkrK8qSclv5eWVcpaUyWuVXO6pPa+NqK2tP66K6ug7Xp9db9d2GtFHR2Nr42ZRbNZ2ErhXd7d3ibmPP6V5+b0PvyUjLRyKxrrvc9SDIFKdrh8by+w7aE5BUjneBNDZLt3NUBpAZa5elbw9x3UQY2ckhuSd5sdHvMLwkV/L9xwrYCheKSKVAlFRNWtp8nfUf0XKzFdq069Cpq2Isvt6drASrrLbGWuust8FGm2pzssVW22y3w86+r9CDK/0qx3XczG1yF/fbwyNBARAM9RoiwJOnMFROwdu3X/kTzv3hV7+G8BYPnrzIleq0GGpKFgAKASPoWHjRXnF+8MtAhMSB50GgY+imIuZi0cjabTIOmutX1Do1CSNOJB8FvRBb3mUF2rTr0KmrxzrryrmfJ13EpVzF9Xbz+dBJXnUWnahKjKUaVnwMREgMhtAxlIliadYT7kG6L+jaHzp0pqvr38D96PNTfvntj7/t35FG4JJDWaGCVtnYHIUpci+GllBl2HIVKsOjqrCQMR3CzjaX58hJ/olcKsAGI5WDJt3Zbvm3QQySKkUTjhbQg1H3Rh6pog5XPounkIVAbuqVBaDQiJ+xeOSNOBi9vwcJCKBHRnwzIxcPcKOoFpYVbQPjGTKWnHFkx4CEBEiMJmpP6gRMAxrcTxRU0eEAPdgVnB4zF2tMvtfJLqBMtYQtpVaq1QhABDWorZH3gCDHknhtEh5IK38OyAob8j0FrNC+lPBDoIAQxDKcg4AODNzEbAyyhpk3j3LStYfrUOi65GoEWmSFipSrUNl4a2xpgCWXnXCTome6ayOyOVtkq22222Gn2+66v3tYLe6mhAgTjmxkOMiSEz6BICSODY7IDdcNMgSoaQiZQjbybJJur1yrz8eSLE5ADVm2EDPibdHU0QZ6w0gp+H46YYVYEXreHpBKSJtZIBc2+sswowxb2LkQRYiKJSqpRNeqhHt1a6IQDdRypbkvN1oRbdp16NRVsSBe3dlmW2y1zXY77Fzfl/TUrtRVwnU363Z21/32cJUQYcKRjcSRJUeREmUq8N6X7mgcregvGnn0uLnmUhqLNTazc3BycTc01Z8rDBV9SSn8iIkhR9JzKXliAVKje5GTlNc0iRIguBXDwp19yaazoBRArhX40ppJ3ehIK1oswIvlqb8LsUq8qMKiIsqgH4VlHQAJ09XivkSz1E8uAwU6OcP7NkL8PbgCz5JQEtNHv5w6CK31lwvyFB8WIWWgRk/WYdC7qSWtmGX29y6oojhmebAi2rTr0KmrYoX4Ijd7csm52uW6280TwiIThRPzODmlzjCQopSShzIVeOG7qiFQBCYkIlbTYRlEdAznZGimbBgH13obZB5lvgUWWmSxJZZaZnmtqLLSKqutsdY6622wcd2fLhRfWHVDFy6iyPgoqEVDt5CbblhtdofT1c3dw9PLG2+9894HH33y2Rdu9uylkmqpTK5QqtQarU5vMJrMFisAQjCCYjhBUjTDcnwFrGizOzg6Obt49ebdh5VItRgOCJKidQzL8VDQi5KsHKoLuyKvdhq0Or2hqSMBvDgAClZELjmUa6igVRqr6XEQRZ3k1QxcX33dYbemPCOL4VWxLF5hqFxDSFELnApiGy590+PfRXvZnQErTBEVcVGWyCCVFSQKAAp1K4udAXpKhhEFAUyqxUbS8qFTBHbHscZkdVQsfd1Z32Tpl8ISiaWIZevzx0zipmtP19neFNUbVGGB8kMwaT8fpCgKq1I0K2OTpvAcsw8IyQ1JgVWgIoCIWOG7cwAAgIZZHFwzH4bwi0mVm3CwfFSa/AHVe2n8zjZ0z0dsJ82TJD8U3FmHU0eQPr+dspa6hWZpG+mVDGTG2mXp20MrjyiM7OSQ3JO82Oin/6EE8tkc+uOjJ+JJAwxXrLTKQMUblUIV4YdNay1SK2aZ/a0Fnc3E2JrRpl2HTl0Va9/l9T8bK7NVVltjrXXW22DjblO7YIwxxkzUweyQw4446pjjTjhZp/AsCMHIRkYOuT3hSe4xxWJzuDx+Jfayw7ylyMpSqTW9bXST9BgoY5vGtCPNBje3sLSytrG1s3dwdHKuyzBJ94vHFwhFQCAd4yhirLiQIKmYHNpbcl8USo1Wb7baHf3C3V533tSYo8UKgBAccg4TEQXNsByfYNFm/4BiOz5Vge7g0SvoFw2m+OccIbUAAHB8Fv3fVha/3iW7xO29VVVVVVVVVSbII5MrlCq1RqvTG4wms8UKgBCMoBhOkBTNsBw/AQAAAMAbgQD3MvX/rIvQ+wMAQMPkVSmoaCCiF61CtdcX5UtUGJWh/r79bt6ZoAmwA6ri52/quF84dEGssQXMWGoBrcYszI6s7c24++6Q8KHhNJOZzEQaaqxDBgsSKPjC6tq6DrgsUQF4NSiAHSCMRMIg7ci5GCNBSjDcTDFAnox2bz3Zk1fHbZVsgedLiCVxe8qCbO+F5PFLDavTGgVrVRYHS8RSy2VtOh4uPfR/5YpODF6XU9gq7rsFAdk0TtPJZqD2qstm5UDue7X+Z4nVuOzMqh7FDBhbaSpsTURxy+pXbEs2yISAIALCEKUJAgKCCCIWRF+8lXywfkeePCUPnaXHF55468koZ0BlaJdhznCirVhJSlGeikkqs8mlnnqT1JKyhkGR2Nat81+ld2I9Zy+eHZwajUaj0WgkiAcgDkgHS4fisCOOOua4E07WqUL2gxCM/Dal4y4EnQxjwmSxOdx48BOcEiISnxsLPBMU+7b/S1VqzWZ4W12hzwBlbNOYdqSZVWWhs9LbmNiZOZjvZGFdBiXdLx5fIBQBgZPgoBvOh7m4gSjJUDHq6U12XrmfilR7atJab7Zm6+xx5XBuZXlens2QuJwzF5asBkAIDpmgYeAEuRmJRMOwHC+INns/RGEZKITEqbumAVJySurHYTlsKkRG+7hODp02vXOMxbQdp9wmR7nCxhlOLu7uLdN0ji+atWj9m/XZx53///sAoKBhYOGA8AiIFfg/Lkjw0iK8ty34r9krv3Pdo3DPZHt/cO8rH/Pq53Qd0r2qLcuyLGsifg5xABcEnBJXSBIgUJDgXYjEWxNooe9hgIklQaIkyQ1ensYtU0RuEpJDOzlMeLjkXGUmKqtVQCA1xSsdFBOg4EpZvUuok4J5Q9h+c+3msSBXMGOVJM5u5pkY8gLCCZMG9Wv9PjaxMdmuNWLvJvFEOZSwBRsPb30Yp71bTBJL2MDFsQsyOB0n8bIoQpORErj0+OyJ4EkoDMBwFKOUj45LAEeSIN3GRI8ID0KtBRtCvFy6s7AyZj1V3QfegEGBkYv0PCtyxRSLzeHy+JJd1vchIyPbjlZzWdgoVMMRSWhgOT/hhEEs5vD3kZvB2dfD9enrx68lMvOBEpOatFTvOsNrUzSP0aeyAiAEP06MchLBTsvHFF60v69kLNIwVNbhcL9madVutA7jdOk23oSPp/7P2LJtxy6CYlRa/Z+mnp/HUaCRWFyv74Nr/4wmSGVyhVKl1mh1eoPRZLZYARCCERTDCZKiGZbjJ5wlAAAAAAAAAACaVQCF1LRCdEFnt3P1H/B+2i+//fHXv/i1jgOoIGCXiCQggQgSLEQEclFgqWih72GAiSVBoiTJvy+NbjqnGbi9UNmgQ7sMw3BFeAQMqfVNn/pR/pUA7ob6uL7T2o4DuNuNez92R7hOrkPBBlb3PZW9H4AGCQhHXOpgUYpOcIjrRbtSFyBAYsAwKemZfB3zYQkABhZAenvD7qbh5mfuE903dWvje0tiUpSws+Sjo2LETLL3pc71PfqEWD6vTQoLaowI2puHIuX5eyt3THlwKCKxIZu73/GKjYf6APIty6QwMv8kOt0J7t6XQynGxJZhmbdGQVjcpMygGIzqgI7qlrwgaEahk3Zk57epxsQMoIhGM6v6xt5kmk7/YETVZ5cswGZy89ZE0SFLeMTBxp00X68eRtdIuEeL9yWRSbLRRGZdaApC3nMCE7szN7hn5s4DTHawPjGV7WR4pfKgSkMyY3Ex5CW5S963beRtbwvuvQc2SdiXG6yINu06dOradaMEhBBCaE+u2nU3+23krtxvD1cJESYc+fEU8DUzcoUjWXKqdjbbOuttsFGvTTbb0vpWTSSRyuQKpUqt0er0BqPJbLECIAQjKIYTJEUzLNcQ8pfEzDJIEMxBQ0CaBKlhjtNaUVspmCZiggxYsfyFvTV73xLmygQRhkDh/OCAFpUW0ipmBbNPsADmnCJaoA3t6EAnuk5LNyC42GxFmb6ayVuEVipXkeu4uec2yN+BJUhQOwAAAAABCUCCBAleD+6OEHmA3E1IKFh2gLsRflWLQBEUIRGxmidhsWcMMcQQYkITn2YtWrX1eZj5scBCiyy2xFLLLP94EnfcxQwUNAwsHBAeAfF9hD6PGAJCKKHZf0ZFVudf9JV/b8BEL1Iqsmgb/5CumOg317inHMNfdNyGgoKGguoPhmsN9onlgqJVXxk9GyemSbwW8fZe+UzzWwiBifU+hQ0kBjsUqtB9178Q6H3U/sxjK1rnxMTEpgXaAosAEQQECREIK3BiYmLT69JQGCQkngY33OBSwbMTd3eqTw43VibcyJzCJiaG7YKGYRiWzShrEY00swnTWVgCK0eENBz7fmqZpEraMGZmZmb+hmyYDxZFURRFURRFURRFMTMzMzMzMzMz83Hfnnz7ybFy8rWLdDHzG6YG9f9Ntjsum1NX7ljtkpWAEFwWuchN2eQBkXyUQ7mUR82ntFArtdFI2NAoGk1jqIPr3OAmt7iNCqussc4Gm2yxzQ672OEu97jPA5KgNqgd6oA6oS6oG+qBeqFNUB+4JG1kjXJH9ihMk7QjTuv3AJJF6C38fMbQk3W6J47SpMG0vFRnfZGPlM/w0+INIpS70VsNeyIdfSy0Mx64Bh8y6j0f04igB05AwwIRkASmydAGISGhyben0WGA7dRaBNmtnSEdovTAVG+Fi7xhg8YpODJlyZZjdJN+fsWeCV+LFFMzNVP7lfHJSz0fLiTRjAEAoKrqg/i9kIMpvvjqWyWlMrlCqVJrtDq9wWgyW6wACMEIiuEESdEMy/GLUwZi8A26s8Erg03fwz2n40qzNMJGGmW0McaujyPjpQle94Y3vZW3e+9413vebx9cvR3YaZfd9thr38Z8OSAHHXLYEUdzTI47sTFjTqmdvlobsZuDk4ubh1dT8113YY2PhVx21Almx2BvFZbXkB6zIPeHJ2ks3WrRz+m4AERERHRBRURENFmEiYYuaIVciYjIVEDExMRwfp9MZ4lSo0RcQRHy6dMkRpcknTnrkYNsdQqjpNhEdBAPAgWDhCLGcIKMOtFlsiU9LSzDJ5b3vQP0OlsjrU5vMJrMFqvNzjmcLrfH62ojL8WUVDJ36uFx40aQAACEBEyqa7sddtpl90N79sdVEhEREVHTmDNE7xsYGgHljW9skqkzl0WWsgIhAezMP+eyFkIwQjKYLDaHy+MLhCJxJXRZz70Ue8pSqTVppdMbKGObVml95tUcCzPp0nSECQXGRRJlmJbtuKtwIQ6jGEExSU+sItVSa9Njzkq2zh4nR85ye+q1nDtbrAAIwSETNAycIEthNMNyvCDa7MvVZMghh5zwYW8aTBabw+XxBcJL0Y56W3jwbXwbz5SJYmbRyLplNrfhbTjiKEc5o1mLVm3xn9r/qwcEChoGFg4Ij4AoUNAuOCV0XdN0Xdd1Xdc1msvlcrkTdQdnw6F17D3iNYqF7wTJiAmLzY134icoYSLEkqTXntGXK4WUKrUmLTq9gco4adPVo8XM3MLSyrpvvM9MYZEowLiQKWdgWtk4uVceeeWtFB0BFYeBl8AvKTqGJEhl+VAWf6FUqbU6vdFktlhtds5RJ/vLnQcumcwWKwAGASMohhOHkpuSTcOwXHyXIDHbye7olPPurQdCm1a7ysF1xgV3PPBqar4XeIX2ki1uyAXtkihoDvNnH58kL6WuInNhnJ+n3ykUgEDtYUBqEDC4hk6GZfg1uhQruVh6REm5tpxzNJwhLr1paWR38igwemXMxlj1cWz8acM484ezItq069Cp6103ia+iQN2Tg3bIYUcczTE77oSTdSo47Yyzzi0X7svEXfIX8tcuf4u/+4d/+pd/13+S//qf//erlWvluhtuurUE74ABA2ASLBABSaDgRQMGDADys3Qv3yWSyj0lozIo2IZuWEtGCBEN3vFgwURQBbkiPyhkRJKYmJjYdWETEBC0I3UpDYIUciihZh4nAxnIYHfDnrHA+rgNaEw5IM5yQdwXvCD8evw8BSYLPr+rG2/3Cbenur3N7dv7fr4DkxxZOdo75rgTTjrlweMPa7aRx5UnzlPyzHlOXjiv8Nob7zasIx/w0ad894e75c/w9///u0CgoDsGEMXScQAWrOAJgQ9LP95z639/8pHCZ/fCQ5AUzbwvB4m9JlcVzEtVarTpMZrMFqvN3p3wB+/hI3n2vNoRexQPJNsbm0MGM58ACM37fn9IvPyW6bsa2LS89jafpf02j7z16Pk7r7Fj/5J8qlGtVK97FSwSmWQmbnFS3yDVBIfWhK6eBFo18EkNJhbuohTT+dx6NSuIM8kMKq/TPQa/o1Gpg+VZyjJ/PuZRvauWkKytlJ3319sqEyaNuI/Kw7h0rg8gdBP1/OtfQO9v+AWBBPDgnwP9d7DEq/m2FIQBIBAM6kuNsW6BQiI8lvy05pkF5P859stIB0sJ4QFUPLkD3tO5b3y1T0SjDGqIyuDeAAAAAAAAgPFPgl2mKD74JD63aWOqtxmxeRf//gt7/FHqJTj50w//9/Jfszf+YN/daUreni1Z0kTTmiWhQC5czzJY97p2fJ27vcVOVfMso9lB/Ob2YG5D/TA8iilFdNXnBBZBjSMZywPDTNCSSDblXa3KjMybGWIEDwFw79nsMDOY4Sb7TgrkiBUbNBBaOnoGxpjmaaFB1CYK6ohn+Yk5vzVHszDiS2PkbJTro0k3MlY4BCYljo5p5tshbucdcrvI7Sa3B/QhC0oqUNAzz80RRx1zwkmn2unr6sBAPQ2yIpe9VLJpBOPwbpBNqor7EcBBC+zmtW5hoygCIYQQbnfnzm3dnoMLzanb0F3328Nt0QmphEk4cu+PMpJwkCXn/Rs6woza81DQi1Ly2XCGMmq6OAEBaTMLkGc7Vnp0I1fcnkVWQwEFABRlMLDGAADAAAAQGYhXPudMI7PcTLkPLYv11tBmXVlUa3QGk8XmvOp+HRVv7Iy+gaERUB7f2GR9OjNzC0srEBLAzvxz7k6GEIygGE6QDCaLzeHy+AKhSFxJl+VfJLPBAEVRFPABAACKoigTChHBCCrGcIKkaEbKyhUqtUar5waJbD2HsJCkVKk1Wp3eYOyTBeeyWAEQgkNAMZwgqa61GFiOF0SbfZHYhq7LkCECAABgxYoVK0aMGBwcHAAQmCRbz8wtLK2sbWztHJ2c/YNQxz4QhYKF7wS5sd7HdCyx4xLvxE+AMBEVey+RPpb5RXAsmLW5U6nTWJsuegO1Pvow7RzNzNxwZCAY2zl3xdMFFyVCIiMUajSiw2CeQ7iNZjO88l1UIBQlvvbEXaWVTHpyRfVdVOlElVqfbUwbZhPGNWHV1MzcIktbWdvY2tnXwUXFQqeTJgMjMwocmbIqWzYno8GVmzzkK3iyUMfQyYmUOmWV8i5Oy4rrpKr43GrQocDSnFwROdG82KNv8Pw1tvp2EYLlFdAFyOU3+MLwtF4hL4gZRnr+5p24U7DQjkZmBJjawRxG9EccAGF/uFlYa5R2LDSQLWIGoaZZJ6v9NmlEf70pqkFj3xzT+0q9kMTIUDrRBH8zrr9RaCJhqg+aGywKfILK7iDwNpMYxt8cResBwXLwONpAADIM7YZMOyVwpAyuCH4AQQhxcA87iJrjqvVD2sQ174s7ZAdNfzRAbv7RBAJkFlSgmTiJx9lmDZsVD3+3NSx69eClYfKsQZCLZrqNFRGtYDaLsns1qOK4hDZD3m5OTJAih1XsvQRINbv+jusDLFE+WuNI5Jgf5PGbxggw+MRPO2gXU7dAY6SBLhhRt5l8B9rboOo8ko/P8jzKC8tnqllnBSc0Ux1L21cAMzIyFXe4x/3mK48Ozs6fq1DANMc8LVIhkols5AnZr3+PMe5HBmIQBiMihiCj8Hicx3k0eR6pE9G1J6T9Wx0YKf40/9hMjbeoXUHK7aV16dwg6K/1fggoqZ4OB6VP0A8DjMJOltk+0g5F8JUCJBNAXK7fl2dExigbHnjggadqlKEClYiiCi7zvjhDfNQxhCtxt1ekxmBdsB4b1t8I7Odx4Iudhy7wuMiy5l7HowfjCsVoCFqBzrZIiQeehp1oKjEwsSIuiGdIoXZKD570Af3QHwMooteqZrUB0Y2hamighR5GmNEIO5xwG20dXGQ155zf7FAn51XRYYjWbxZSqdxD1JOknNQZeZgCysk/mfObyxpfC8JyeVtuZQ4Fg6dwLqD7I+E6eNntGAjFtiihbSwdkQ3uWPm+Qrh0V6+BeJ0sWNw2ngAIKKh5XrNKjeZusGK2TwxGc3BhY8VWXk+qWciKwVwhYijUo75JHlkZZ+5uCwtVq2NbFmuZHyzAQizCYizBUiwzlpcuFGEgg2JQ44mmECQIdDRKryWReHS4R9dqcR556aUAZbkSjhj2GcvLZ6gAIjAUow8drHt6SgLoQe7mhclUHGmMrfqUKoYwAAKMaUqbYR8ifWhiAgFZ8TTqG2wIE+cpt6RsTkZSzejLEv3e+3CBGKxsibiwT2PIBSg3fo5qAqJdjaMb2tra2tra2tq27cTq4iUUwLQDAhXYCwcBLZhpVxgYQcUYTpAUzUikMh9WrlCq1BqtTs87QbTZHRydnF28evPuw0qkWgwHBEnROobleCjoRUlGZZQt8b6BAw+QLIDeLxINqZgdxCKiTetWjAFZ4WP3Jx3apBMoCGRkeQZzJNmd+T0S9JCukJLQGRlGQzIaQwgCEWqggdbTw/6Qs0v9XaUEIX75dzJi89RHIUmSJEmSJEmSJEmSJE7najBqTr2dD+lsVOe1y7DIO/ppM73adsJZye/Ly67BGWHMJDxMxOkTsThv9xLqAixEIYZ1GakQx8nn1xG8cufTLleoNmeI2wgU9YEiCgqKKFMn53WlgEav6GJz7P6VGLI7/7l6H/p1Wj+C2HnTzu+V8RwzWTD39nKY4cbVXlljQBd6JvGiHApuw0lMmqmA/ynONEIxw7CncODAgZu4/ppspWIDowKAQIACAw4CiCCBrFqpR4Cokm+OLKsdp+1WjtR3qGmfUcw5K7/w/HtH/YNZ5F7AslpROxj8bj0ujC5TckK2wt+ElJBdzzgNx5GVWdrh/PzpXI2djHulnnn7Y3vW1fxHqXWrORXDtlWAUeetcK9NsUvYjT0iIUhEkmErFQAEAhQYcBBABMkgJ6AcZ8SXL6aGmaHbpBdeQYwrVK8hZNKlvwkHl7aCfDCw7bet20ldpxt5/zjdmds6FkmCX7G8tvbj7E3wFu+ND0kKsPr2g8S2CJJmsP5bzn2WmXm3Ma/Ou0fqV0oRM4re7bT4C2yE4Lfym/1/MoC2nRXxv223UPC2Fwp1827uMcvrS0TcUr+P+ZtZcWJ/1RBUyRy9TKXmZkXHHgSGI+hrqQ1VJakw1VDOpmu93S4tKZKhbPwvNapFZJfICcpQ5cj6lMQheIhMAQjmYqYodAh9cSYNGBatVGq4tV+oQVbp9VbqhEJTRzeI/NYIKs5O2XqdWgOqmvs2YKVAII3RWI5eHIwcNL+VHYAE0G5vwrxvRQIUmAk9MqMiLV2WayQUJH7SLzMIPAzw9nvlxM6ejp0vzUIgJbSBDvpQVv2SDSC8DA6oEUpcdUnQBKM31/LMC9TPaQStfqiFqf7H8IMZdG5ZzVnScEAzjSnWsdYjIJAKbaxw3n4QFDRhPZZICxAUfNsMLFdOsQg6X9ceDwAZbsVTEoH3RQ1ljYyHoCFUps4DEs10WEjFCMfU47Bvanq3ujVY36b6yBUnhcOlC0uejrPwiyhHgUhoAx30tZx3pfN6MyGWe4HW33a7mW3XVTXCINsvXxSV4KkiNFSkKovyygRnNuSNpW+wlJFeBoWruvXaWQRNVQOPkoCgteXaF6BsKM0K9pyXhsq5eAsDpv0tSYC9nZl7/Ut2IAE/GsGFhDhHSQXm6+vQOj1Ibducj1teFtt27CIoRm3aqokkUplcoVSpNVqd3mA0mS1WAIRgBMVwgqRohuX4VKVc48cHImoymPCFS+zgkG55hCARSAhBk+WxlawjmEiXdrVUcpu2OcgY3iRh0NHBYE92XVlzdYt4zIw1JURvdl8dGQKCCwTT6QIql/NF9qhEHKxHgwM0HgSOmiBs82MWYAk/8Qu/8Qd/8U/4FdfdIoieA4GGo+Mm3F5N5G4U9OhFoB+4NhNM8OMnWO9QcQyCIN5PgiOkCRlnqDSZuQfCBxPnW7fqUgSmQ6NaGBwmppsYvWa3BA2GhyhgE0qQseEJTJbEMOPTHee/89HhCfI3R9EZn8X1p+m8BfSIqYIz2+uz1SUaCD02MQBrHNUgo5s9RO7MEAbSUKqi2sKHQIbQEBcv5KbiAKkhIwJpolAuoRY0NWKSyVc6vauJAYgcEbhwYMOB4YI7ty5lavpdMQfrzqjZf8zwWMswcf57sJcWfeIGIDh+d0DyYKWoWqzBOevsK2DyDvbWzItZoymg899XGARxJpeeUTeJ7FCOhr3I/B8Twpb8SHmVV/fVM9yJVZm0NodBELQ8wi7IkAZAT6f5vsiKajt1azEewoi0u2VoLrMkq0P01/Pr0dPdCXNEQ0RTUFFRUfX99C1gQU4nuf/9iupiS8WlAU5/tbZ4uZvvKQ3rQcccKY3ayxMQNjRsMBgEC4zRKdO9/3RPONLXVE776OjnCZ1OjmaTIw2JhP6AUwcaiepS1sgO87IhRwhgcMCYHiLiY3eKmYymWtCdbbEl5ljbNYgTb56dlkmwXKYVTN2VDiq0yqmfxTmTxMZkSdLZgZxLeF4+LM3P3y/LcCFweWEIK6vLoZyypTQ1I9vraj3J0Xr2SK7Wq/+spLi+VVOuVUtT5kHzaX5FBHCFs4ABDlCgLXnCsig7cyTmdJSoImpbFdW7sjZpm9aOtR8dp9N2c7ot3aHuzog5ih3d6BGCdx/fvxuLx3HjDxPpZNPk2MQyZU6106hp0rR4+mIWPKud5zfPOO/ZfM3CyIXZCwsXNi4cWpS0OCEtyZuLgsJcfC2B4yeOUWVeeaOycvlcXR3ZGPQl7GNecLnm3IsFdh3BgpeQUG2gg955H5o6ZEYfRq68k5TKqB+USeUyoSu3amju9NUR95hRHXEkKkUZIcktszPWuvyS5Z9fGWtQxmakhPiWEY7nTEBLqkA34QsCTGdiGDatNGLAU5AVNd6a0D0C98x7H5v/WPEAlVghIUCgIMFC0DEwsRI1lSBRkmRDDTOcgPeTk/1O+W7/xoJ73QPOvfTPo97hUoVQ+0GpAsOBAwcOHDieY2YSlCWoAa3eyzeuGCxYFbw64EOUXcMVsXpRpTokHxULK9AZ+vPQvay0g+vwOhVAKKJa1O5nWL7WaSGc2eor9UN17tlStDU4mcOSUrPS7ntdqUXEOnkyyCGtKA/J6yuy5SK8zLdwS1tTEhDowiRCEDQ5VWPWX16/OoPF+n8smVNYJI4wtp0IAi3CGQQ3VNDesJGygSPTHnZvV1FP4thDeAX3MPADVy8lUQGJpJsqjFpK+ach70t9pD/jR+l4SEZ+/Q5+hnrQ07HtHsF7FrzHxz1fciF0lEhfS35YokFCAAIRhGCE1AaJoC/BEJhgySguA4HAKhiKYRgusuciB1rwxbK1wlcxW7avlkAsj4brP8K+cw/fXJthj+guMAzDf2k3W7yiHRDx2bzfByYy3QuQBjZq04JTIjPICowAoABR2qMscpKrVGGru6F9EyleDWX1hhPktyrGr9cEybId4j4eumCs4Ty3XggFPL+GOUg4rvO+HLpEKtjsz3YX1gPj1c3sqaE+E5z11WxOtHXLjFPwirJpJpOfPib9TvdZYWk9wXqaVmnorL7FVKovSJUOlI4GUjlccJat1e097rC7bk878BcDvfjbf2Ll0M8K3VPpStxX+SrMhdfigdoaxh+wLZugu+rzB17GyT/xGOinlLHGMD9K+HbAoyrTnOVlYhURUjd6PmsFefhn9I0qNAYJ8QgYzQxuryJD0PVrXs5sUPB5l7QV7UlG6M07ePQRW3sPH8t7XbeAlE30It9psk8kks0iTbK+svzY36IWapMYbhU1OUvbWpvNQZQurqSr29nyGVjPhxUZxfU9i7jKpREW1siEyLA6P9hblICyBWXVsyUjBcgSNbkJEJkCFSMQpasPllcn9V7pPggX3pGDbgZWL4yw/Ur4Zi/jmCZcIFCL2SwRj6wHIkWT7sssYVlSIsmyGhy2/eHrpEboGn/iOUh80Q98YZTOI8Gp8zHuJofrVlQhjKTKZaBqbQXss0ys4xqFs0IcWKk931okv3xxqyxYIPKNYq+KgEetHZho1Zi32q4z1GG+iN3Vy24hqzMpnuc8M27/VZVmVlCjAC0sHMQZjTpJtuqYwXLIN+5J/SIhSNGBL76yCyudXQYHE21nEiPxCGIzZ1xc0qs74kHVTDCKomJMWTuL976obT4XtspebSumxcEFtZnkYZ8OQMtIoI2lXVI89gwo4EI0uBD7RnHktXI6sQdbiLlI2QXsRWs057TUw8qpJHVwtKRFSLokGQsObEXt7xdLUhVRtnqJZjDkrWAwpDOw2KoAIWHHVxajLnXAsVjSjlKdj8lcnSBKKznLzHY60ZBV81LM07I8EThjktasnMyUjcIQZg4zEC4aXimx9lGRdDzAdtTTEM22CCkbxZ5UeH3mMClD+eDpt/S7xdU0UiMwZXi3opUotzlnpCuWpLLLuKzFd9YBRZNOWtFoawBEeZOk6LWve336MpHg+ee9Tw5WLvnuh0jaMKarMyE9QJjxXq042WEdEY7W4vws+65OBeDQK3UGyd+HMYrNy6Yjo4GJnqmYmAZVKJ3iky4khJ3KTQAUj0uw5Sk7Yd9++UQOnJDwCFGJuGGRcmfPgwcnXnxw+dIQ8OPHtb5+fSIjBXMT4jcyv9Pxcor/lZXOOU/lojfU3qukVxcExlCIgu3hkAHiY4Xl7+xjLzWHONibU1ykJYhAVq5xlZ1b3OQkiYQpRRRy84xSXj7xUZBvfB3MP/4OFZAdDrcruzSWlCRNDRcCmocjJNA+CsIA3SByEQECQozpMhgAVTAW5/L/tsKGjLBaoa6r0RlMWOvbHC6PX/YEZohIhArvvgCxREpklSWAXKEjHatpAGouAAK3FuOIKFn6QiCmtHWvC3hrfR4B6dEZAskaOa+eg7hzMX81CzbM8ebyYLUg3LAITIDUkEW0CAAA5BxFyO4tlQdw52jiDeuSuYZ046ZsrqW31paf4yhWB8AEpCsm7esi1rCU+WZ1eOVQ1E5BdZlVMaGxsEYsKYCguJ0xNFp/vIEx2sQUwAwCwJjDWBmoHvdN7QG0WnTiAbB123u7QrUTY2cIfkzIOlKvqUFli51j1TpRfTLIBWrNdy+lAGsZIzKWQ/pCIJnkdfoehVYQmgeEypAiO8b9FmU/z2W0ZDjTaDpm9QBcfYyYILe4nXYw2WFaUwDGFG8LSXQLaQHmnLnthjFlLkvdeGY3lxOv5QXnpHw7Rq97vLMqmKk302bE/XZirAPAbFmisgDDche4qDcXdZ7L39R1q3O1bClxJHXvuLriDuBKan2Z4xSA0HB5u55kYk5hSEAI5BQug2O0mARqgwsxASIfpsSUlAFn8qkrtEG2UQqq4XIEsi+BbxqL31c+a2e2a6LiL299QgEBN3/LQcu1ORfMxeIhi8K95K/ZR2WfUSDFybm3OeP7Q8eRizsS7B5JG6+byHrqNEkXg3STdCjjoSXafkkzY5zLhwkZBhldDPNdDVDg4A+0wQYPzv9U57qAjHHaajfsEoz/l7iHSIbTRjlHck3w0UMWhz8e+H43Up7OuTSQx2Xremb0nnP8QVLz1GaZ4+tDdoXDucgjnzEE0CDwrN5nUvRlAa5PVD/KUmDvYHQVOx+cm1mr0T5FTrwW7esc66zvMWthutqzH0jKYjkhayeb2BpVc7nEZJnmxNSBL0xO7bN9FGLthA1xumFTxTTS3ndnFCDJqiBeGOzXvXmvEgg4/jv4IAZosjJVCKXAGptc+c7NIl8/1fck4/4wQgM1/fok47JVbBRJzsV48kAezyh6t/DAXEcnVdrxdTq24Rn43R2M6YtZsrToa01Rz0g9TYNHQJv0bp0J7h2gB9LB0+nVlZ/+DuyTzffha4p3jehgq9U7G3AZSL++PxaFvmZybgfuCOnqtC+kwtvuDmESxlewS2RxGw/acpw1LhN7D+ZPE/kodWiyTEqch+po5/LNsnCMcZVkD1MjXYaODR/TdRzJbh1JGyDFU+nUEUl8LNQm7+DICtAPrJ6HrqRHslcfvi0Awu1dRayZOlvYpohrqBLQmNg5iO7wcwxHFWKqUcsxZAVwwwJ4CcM5j3BJs4L/xqJHBGMWi+/7iWaMmKFdMefNRNNW/xt+88cqqkrsaqrGutVXo/WzlMXGtVWXTftRP2xdb/XZtoGGgPhDRIkg9ZBRd3uPuvk7c4Kb0SWIZWaZBZptNsQcc6DmmgtjYICbZx6CKFGI5puPxMiInHShFEqhFGpLvT6K5lhj5zi6E2j+YacQXQ2G2jGTmMIqrMIsjJb19AR2/+Qce3oKu7C37FesgHUtkCoJtK4HVjcCr5tBqFtBrP+CVOYg152AdTeQuhdo3Q+sHgReD4NQj4JYj4NUT1aRs1AoDacnJKsMh1WtcsxI49QT3DUEb42rnLPTugzbtkG7gr+OM4J1frW71rFr1lrRdS+39cmeeAN/90AfT0hmQwsMGE4eW/v9RcLSMqVlZeuV8zcO/GmRouISdqUVnOpXcWvShFfTZvyatxTUoVNhnXUhqsuuxXXzpeijnkut1iartelVrvO67z6ePHwRumqT1dr05r8GPXg7I6h3Iq536+jN9/qAUx+WkXti9MfKyGEsiEGkkroGGcExRWwBx5JrxS+BtdBGxBfbSuxk9npChUDfQSl2IlFJNTKdnoHcsDsyNuWYWZh3HVs7rT3HQeu4MApvt87gOde56LouXRm7MXFr6g3Ym3RvsRnDHHRzQQaIeVBRMPMxGREsoRRHKYGSidJR9o7hODG2/4/GHOZR2C3j+hq+G3rJ2U1mWrfdE+j+g78HL43y0HMuXnjN1ZvHS27e+0Llq0qOqh681Rihlrc6juqvPTVoXbfbdHLSBbUf1Hrw9VLpw9dPZQDfIJWfnFk5+sXZEEf/czbMkW12pUDAcsEA/5AKFKIwiA1wArQRKoiQiARJyJCMAimokIoGaeygHTqkDxgElNTdrwovj58vKBAWioqAwAASKxwrGps4KqyCx4kEmZxDMlNb/TxIHQUbWB6jInZVFOpVDs8UV1zjeuKwRWGvi8s5Opydg4uo3PXi6Rm8mPZQC386NRAaEBoQEpAzRc4UOVOEBYUFhQWFBYUFhacMTxmeMjxlRGBEYERgRGBEYERgZNrItJFpI9NGpYtKFxWdPjp9dPoU170JO70lo5SX/UjdXKwG5Y9dhjNMkA2ZAr/REDpwlK+XUuzioIrY+p/c8t1S9rWryNqIVldzuu3CZQ2LAzoIgcIwjwh1m124uGFxQAchUBgmwsvKvam3SPW+PiAJHUlKQZoyQSNNqZTRpFzEuFPwIEmpcKVUsihIUipTpCmTppSKEk3K5Yg7BQ8acu4UKRcimt5jmEd6pDmxWVyV0WdNlZCzljxq68AcAkQgDLCXaRAU4IsAdADrG7gGRqSFzahdYItt624pbQ0UBARCJnWM2jxkp6ifZAqhIAgBQRgcAREJCoKIUBCEgDA4AiISDI6ACIchgYml+HyW4pn8u5Oy8JjOivR5cVBEBi9dBZGC7qXN7koKuv19TOEyzfHypWpIr5c3msTkSzNhSjT8rN3fKNHA9/fHwVJQDJklSM1QI7RkBnl3uNOS6Tich+i4nvQ+FHaSSLZGGdkKi/74YGTLRYa2NO8Hx1E5yWL5E8YqdlQOpzCrgCjeO8wWAk9LFKYpAJxTnbjanCOcCmMfvxXuG0mgc1MkSpDgNTeptxeC11B8QpH47HxVhHn96aMOP4u/YrEj6Xu6sosNBQqWOCl6tP3sKqUcyzZSjgJl+K24GNFqsBIqOhZOvvtjf2ywFTvZRufL24oDJWxwfp6BFVdLBmZg5W5bqq7K63DjTTXbQsutbTdX3TJrcDvtd9RpF11v7/KaBhj32EvvfV39/vqJAfcXFUQjCBaBsR2TzbtgEmToY5Dhstq8WoMEAUvwCNVTQDrTCqIB7bzaTDTd3J3FdV5zJay31W4HHR/Pfk9uKnjZTfc99drHtiN9g8jAn8Vf8VgkoSgtq6pu0OHjpOihH7aRbU6jsdd7zsguW55iFX3d8IFrMVqXHjPM65cMH7lVNuiz035HHp78Sc5U/HlX3fbQc2+7z51OV/ldCQBQoG4tra4xawhREqTpZYChI+f7alMJXEUqQGrJZgdOuheIGLOPKSLU0wVK+QL3ZW+wuqr9Dx88Tmx0TIrRAIIRNEbGmJhtzC7GxfgYFhNiDjHHmFOMiOExdvr5b1olT7MYLRNrk3hJ0uU5osgFJe544o1PKjXqSBh5VNEmJLpMy6wYsyyx2ZT4JCU9eTmSolxISe7kCbf3s3keWltskyQzsrOmVCPEvep0zG65oSSKc0Ta9waYHO/eTYQIf7nsm/mTW5Pvk6Epfxo0nTONm2ZOz0wfTb9Pe2cUimjmN5swM8xWz7bN0myHzNbCFklMCpV4oToGax6bahEICjNOmlTZMtWXrFjjvpz1idQOeqRr1qM75mojPLPaO3ScnVvv9NeGe5a/PO2NUW6ycA6ny+3xmi1WAIRgBMVwgqRohuV4ibF/CCQKjcHi8IT5sWQP7Db8srUsDbz5xBdCMIKKMZwgKZqRSGU+rFyhVKk1Wp3eL15/paRlZOXkFRSVQIcHJQHU7+mZSfT/MFhKw+w2B0HsP4msoqqmrqGppR9L9tfZSf0bfJLW+0+h0ugMJovNQQqMKqxim2dHnlPxPyk93i8PZAwWT0CgRJBonIysnLyCopKyCplCItKodAaTBYAQG+ZwRcXEJaQkpU9Y4Fxu2apa9Zo16txfCEZQDCdIBpPF5vL4AqFILJHK5AqlIG2ViC8QS3Rag42tnb2Do5Ozi6ubu4dvvnPZLKlMrlBrqPln0E9WvwwtKv/ZICiqphumZTuu59MZTFb+PwcOl8cXCEViiVSmJ1foK52o1BqtzsDQ2MTUzNzC0srbvtrY2tk7ODr1nrFzUzNzC9WPjl2eC5y1JwAQBIag0BgsdATzLzqiX37aCkCmGMgldQjSNt/tbmpUbrlum+v2peeAwClyR1GAScyTRpBxJolgsMAyq623WbwEqSMAlIZqxJVfCfyH0Q4XUKLAkQ79MHQAFFW7Cs0gv3bzH0qbTUB7BA7pP608QO5dDcKStH8ZgJNlyLjEvGnpGSwRazMIq2xIPtqBnPNfEWNGflAkdJzZ7SKjB3CNxkMtwd2hradoV+U69GByhEPOo0KBok7AYw3xP2CVAdqjlnE56rgSvPxwesrYDLME1EHOLG+g+CHN846mI4+3wdwE1kGObE5czKj5nIPdSeNQmEFgHeTEhGGhVHOheSpxTIKJAFgDDtfbNyP2HYo3T6sJfSnMN2AdZM1Kggaz5iWmZrwvhHkErIMsWcqxka55isHC+s0w14N1kBUzHvIlutoIaK5ezyaRP8mCBS4sRHngC/07hUw52AcdJhwhPX+lkwl7mgIB0YgGN7iHR4O7O0ZuuBEy6h913RMfoKPaVvh97jLDRq9Q+GTuKYSPilF4YT4pFWFUgELTfPlYxFE8CjfPV9QjjRag0DBfmRp5FIrCoPnK6YAyEqKeOxPpwQDsLF2zvuGL1JWHR8it0WyBdfrehEK6XvJm47XmgNUmRfbh05dvVIvhgCApWsewHA8FvSjJmPLGJkVtKqjzHGBXW5GQrhFeHxv6HG6bD9z1Cn+HJk+2v8V3KZBYqxlsljJCup1o+Zn8tqYvbE+yCKx1b6t1IEIiMiPGRLewNhuChowK2JQOaqJJBGYycrVULA8bxRshUbrRvcgL4/uQDyZUljITd7XMJr3ZsicSguqHepg8FI6BR0JJxYe6D0qCE4CCk0IbCvxAI0MbJdBvS2daIiLUCEwZsVRc871Me489mbmpMNk3dAYRCQUd48tz6lUCj5ovjOjrTytA4B0zW7DRQowRaux1AQgr1H1PsfjUswU2S56XOeYymCfKfEbRFlhokcWWyONO36OOOeGfM///ffjOex98VKrMJ+W++ua7CpWqVKtRq069Bo2aNGth0aZdh05duv3Qq98gqyHDAbAgBgmQBCmQBumQGfbrJSEAIWKcpKVypUqjM5gsNs7p8vhtbmkNAEFgCBQGRyBRaAwWhycQSWQKlUaHoFuyLfHJy35QLqXDHbd98xVxIyYhJSPnTsGDJy/eGIqyvylddMllV1xVrMQNN93yH7Pb7rjrnvseeOiRx5546pkXXnrltTfeeqdHnwE//fI/GwxEIQ6JkAyp0A4yICv2xBaBMIoRFKtQa/VGs9XucHvNLKw8AcFQOBKNxRPJVDqTzeULxVK5Uq3VG2ztQ9jjCynakLXz88el/oB8k98wQZ4vKFEVR8b5lFBgSI2DvLOPUB4c/ewZA0EpRra0eFBuHCtyB85dy9Dh5hBzyekml55lclnApbPMnw0301IbJZ6/PHNKzH1d1wSBPjzAYWqMcbgXArQ3ZLmkU4qqTCDi2M8SlL+g1ypMiNdoogjPtSReC/DtXUJShqxUQP8ohRfH/TTiomzdSAR+APbhrhAYSiCcHrOERByFJMmNv5YazGGb1wbwBj6Mo8BBXFZ2B5JlIAmol3sxyIYyuhhL7MVgE6hRzGMu8LkkpIxzVijQtGOZOJUT9sZsyHy0ohDSCegSczFCAQHvRtSXfs/EKxDLlTkNg7Hku0lQXBeclOfcbYjLtn67XsqiOgFSFReYDb0G6Z2D2/huCTkpBqtKLw4BwEVksk8BeqO6EMiJlcvnc6TZAi6Ho4aWMJlwoN5XqYBH1hck36dhBQreUAAK9nLgjT8Q/RGoeMwvIhEIBf7bY1PmXgCPw452DQCeBsjfygxYAw3Qbr6ms5pVoLMDtR5CUQAgf9wFUN11CJzx/hBPdPs5VuOaSSl7D9EwZPkrGhq7vM+HfEQXKTa1XvvhIspoHqzJ9NcfOBCx5+WGWx546rUP3vuoTKlPvvruWzuwtFqVp16jp0kLay/0Zv3bvhbjbTXXSae9s1+e/NKzN2+UeFZ9PuVt3llcbb6kIt9TkwYHFPRVOQsWZqWps6TX+eagQ06JdNEVT9JlvOCSv7xUgF9hfU6jH9mcfAscdqTd2aPIGccVluey/1xidlOG+e3ZAj4qv6+7F8mSHFPSkpqsZCczuclJCqwdA7zOTgTegAV8jDSGwQJbxHvSGtEm6I/+foNRZ2GWZ2N2JDs2ZOUKKHM9redVXhXN1E43c7u3WuLYIyKLlhE5i4QisUguUouCRGFaq78Ur7+VrnPm6CZ2k7tFmtGyzWQfrNRsYuJH3Iun4kXipcQOYmexUOwl9hNP8AW+1BM92R+Fq+Fl+NVf+4/m/2K/epsNLCCiFiTUPAttdcxTn33RqMmAn/HNoqzIpmTHWtxIHU+O/uLZOf74zEuLuCK+SOQY+bnjtEU7te/4GTYP/ofBBJYAT/RYtsR3exK8WFbzj4b/gnIZ+gDrAsOHjl6RanRD6AnfXtoGgbmjF2/IMHEInLs/dw+Ye9dL3bB2WDrsVvuf/J76aj6zZHW4OlQdrPbVXKq2a+iad2mvcZtvV9WqX+w9qtZ871ZHtS9FgN8+GX6SqzFAGz36BFTNVBZb2LbBN8xrVfeqW1VRzVuOGWDf378B8P2/AUB/5xgDEMCrAB8DfA7wNV+BrAO0FqmwBr071sm1DRvjyJ+Jv8v/zfOOOX4wAdW1J6vOZFWMQPHHIzFLd5aGLDZdbnsbXruDlTprf2nZh3IWoKXzzcJpZ8pMgQKHepD7eYjVDM9QuWfK+8zS7R1yDJmXqMx3zWfXffFca6nlePJATzSqvrSC9t/bWpEXeZW6drgdSkMaK7X8y778il3a4pTmH70cM8RZZqog3oLLT3AeSuJbbOZlSB698fyEAOYP3mjDAoysQclupIEEGyrg6UUrM1p1YFQ+WgWh1yrYu4BNvn4iH3HC5cD0E98VW+K7QNh7N0GGnn7aO6+kJCofjSyQVFOwpJZqNFndWQvyNfzAu69R6X25RydbiUC7ZR5+4pDC0fZpT3uMceyBzrXfAxhJ6kQ4OpyJqYm+zI4vkeyfEBudk02Tb2xrcYd2TtooOsxU4kObN/Qhk4GMhl5LYpt/n0ZfGId7MjgRoO0LbQ+51on+ENHactxRrO/42lhMlz5k38sAMTSahPVtspLZuQnWdkLvYPzCZQgd8y6ikqbneyvaYYYQFwANCYIJDjYNoL0BYHwK0P4E7PFX4Mh/ArD4DwDzrwBe8zHA+s1JCASckmk6RKL+fhAIogzjhwjRqsRqFASAgB9ICspMHwSj17UwlpxeUipWw7m47EFhjrKc+IcpLqo1tlGRPMsIEt8ObY0K1dt2xyvQ3UT5oo2wLyDRSpGw2obHo7SLNES7TJd+9dIfDYZEz+yhbRuXFpY5oIsYlzgP0hAGfrQIIhy8K4Po23BFOePij1D4UBROAXLVhVAQKE3V2gsngDGUke0Utos9U1ByVOWc7H0vD0vnY2CJbYE9ahvfS/zTI3giKPeSlBK4+CICf2vImIc52Higw1i+qmqx/j3sr88oK1GHG2kw5axEdKiugrKUI3zEL2pew1TypBagIH+GQiWElmAq0UOUlAF17nwdQ7mSOVwBQlo3SKLuRH7nv22HQBM7yimnt7GvpYlma7fOWH20tLTc6ZyDaZvCvJSFjFbrOVXlrirXZjqVPaU0O/rU51yVVZleTL2MVmkp6/rpiW9lQffZmss7QdyvYtjkjDnffXnatHN62Mw5m60TpdummbzN+jFZHnP2QjqxPorG8z6jsDeEZHW2kbxitpw7hcHYZJ7pnLI15dufKOlQvfh1VdLhZRrLnXnMdNfRjjpvad/frdKSzfe/Sa/TB+d7TWnM0zRSatBEbexlCFFTSm1WnTTbeieNbpQj2Z3Tmu2LIM69DytrBy99dFty6S0ea1Bzo9sb6SL0NhUuQuhF2zYLusGIcqt8qx2qa6OE95tavfH+zOsjmm3OkeDryY3T2uts99nJ0Y1VOfYKB/Mu7KYNm5RihF4rTvnab3yVhiqtRDuHVqQm6uhS5RLL9nDfN3UZoIStsPZ0h8QitV3bbOET43VoO6i50K1Bt1AQqtP0ll5yioIWdiBgqr4E2lsaDtumIChYIKVgaD6UkqS/7yGDqEQuKsn6qrM9+7uGonyX2VU/ybbnGchqCNZ+1hyCuMu7tpnIuyyEmU3SbNS2CVYfekHYeyU18Vhv3ox3kfaVlI8HHYwR86a0mzKtKKWc6v7b9Dj6IUayWdPdzeYsyVcC8+pFXUu5LJK1ACptJmvmt5xYBu82remM7z+dp3bOVrc41dVpZ0A7vb5r957L5rmmbXv6OGPfqV6NI2M5CgmC144mRPnjy6oggy9F5XFVjgxhS1YvuKh0FcgQwgNhj1aXoSWEAJ5w7AJ/PieELKwXAi0kiSGzmmPefgARQXcIjc/21Iut2xrhgIQZ0E97iDkosntO5aV/H0JQVJ1HnN+2muZGW4Gk1VjWVN6QsKACRgbHEFM/S+xMgtt+SobOsXNBzyUQtnBvhIM6njUwPl4GjQw+pgR5jOV5dFwD8kCYwcYoXgGy9IzLCoqhgG09wZtIzQgK6fM+PBDoI4GYCDLTdQTFWPDAhr1kZu/6dQ0HiOmXKy7OcuFLm+RCbQTs90ExI86QJHseQiEr8pBRaTbXbyNNOV+d4oQ84sh+JJgf72seQjT1VrBP4DYm23JDS8ru3W/ny6FCu9v1npyVv72WeIijyQRAieR1t4IIds0uIOJKMlTB18Ie+Ea/91LFW9gfRgGVOVAhl7jMJSmufkAMVmWZ5HKll4fFWY0SDRSRjJ0UBYomSr457vkV6b92k66WizxI68goYTH2Kc0Jc/BH8OTjVD4aFIrWYUyeDF7ptblkD3cuSp2e5b2tmvfxg8cCJcT5Rsu1xA3vBC4TFKLxuPImakTVIPrcyq5qnJ3bQEvm0+pbQTPrIrjlZ6aNvERlQgUPFrt1wuKgmAWZMhVd43H2vMgnjNEEKZ/nyBqUxJQlXHPM4Utj9YzFgSiIMI9QgcIDiPCa3gzp6K9ga1m0QcGejHZjmQEI7FYL0QsLQJKwXVj2wcuzDky4lwRUHn8xiK+OnG/dtasqoqHbuL4DkinqrWBkFL+ijNvdJ+lfc4ZcIjyAznbb8kL4UpAnpd1d1csKW83R82Gt+cCekm1oYCZ4q5+TClZrkmt2dikEwW8UQAwUUEjIiXF5319Wr8Xi5oHxMDy+KUfcuLJUHeogwCnIelyilalwN9808UUEAsKW7LWmIaAqtXNzyMEEMnhkKBUZ/GTPH6NT4pfXNit7lPKrIusau7z+/xpPOYLuwIau4YBChlHOp16OCv52xoHvrcIsBYEZaPeHejbKQlrIXGhuxCFdKLgztT2fpxCKO7rsTQp6a4NxCBHO0soNevTPbIKECFRerMQ01W8oeQadYufZ495JCbruPdtMdlAcCU+Jc4KWzEYvq3N2rQS1Vl0MDPUAItJLCEdgiqvqHWm7AspiwjBbvJPZoI+0CJSxmsSyEXb6hedZK0lGqCBmiNCcZqASg0EuE/Kp/PC8xa6/nGvLVfOL+Jjn9kQoYppwuLcxjIVyI8inAnRjprYGUkxhF71ixT1jtzouQedkTZAdwhNtluwN+CfFaseJiMtRxPWDTQsReU/FSvsmuiI89k4zjGAmZE6fkMeNbRqDJsL9zM/4usxqqhwDsB8eXE4CPCXndCVynbqbgq5qt1MjciE69LGYE0FXSgz5OqbLmAP3xmgZt/Ealxj3D/ppe/JBCVqqYn0aRAN7RgDUYjSr6oPvrxW47Xb52ytB2DCetq9remyb6t5R7eocVJP7giSjXbIIktMKI3wZD3ZbHDAQ2PAA3gYIOT0Jp7eHvM35T7efqlOcmYJ95lR0Tq3Q1tPfGQbmFGKxiqmK4rdFCiodEv7wSl6c2p8WtC5ClbZ+ae3Y35iVKQePnHS6lhRdk14KmxqwZDbo5DstcVZbPYBEL2Kgee5rspXfY1A3dW2s5ZlH0Q6nY+8i0aZEH/hhRYYfov6W36sAttzzzBQ+QhMQfhhcOn6hpvaiCqJOYoXJpFSqpjXTctoWCXnVu9IS1Xf+a7wFh+qluBRYi5E55Qt8naj3BuSlKVmnwHFLoB/pPSfTOGUxhYKtRhuvp9yGi04wA6zo4w0q4X5gPewoup7GEzx1QYSO5xBWtCSxOHWOzpYwUPS3QaXuvELH6HhE09q1jMYfNaN7YOkUUYA3JD1ctO90z9TfaxQGT2h9505rofZVsHO/Cz3FeKYCwgh/wuybrf/o1+E0s3MzC0hHkDXJa27rMLre/wZbAWUrYzUIITaVfl7NM3B5vdZCVRWG2NTyVs2zrCSjWmoOwx5vr6Clp+rqGw4oxMcBrzzZsWpgW7ZB+7gxpVgUV2iX37gxys41j27oto44/xSQDUXLrn46VxBBKx5yKdr0Gg4xbwkIuT0BhZA2tkWho92WXyQUNChx1HUi3RfIjDbaoo06wArBNCh0MW2OhL+AgSxv8t9AEPImFbEmcJM1bhSdcOb3+Bebz8nT/Al+m927JXsWF3WDdQu2Htfc/R2TKYbo5OlpPkXhW6pAo5vYUxOFSI0MbihEa8QyTNE2gwAH9E5uEqNbefTlbKafIlpMqS3YRuN7xMWZMhUd5Mpf2FQYP4oAlBUqiRKHftWjvf26oBCIjnh99yop4AoEX5EPMQT1mWjH82cxgjER1caEUfLFqRNXEL6zgMAS+B/dQv9yv26wN38rNPZ/E2dZm9OfFOj7uUAb2dagm9s8AZhyPqBywyffhmuLWSl88ibyWJwP33ghTfOER0umDvwndMmPyI2POdHDTKi6PujNUvt8T1iv5YJl5TM/+FA178jc5+f7DvJi5D9Tl/za7aFfba/6L7TTuEve+iVMTI+dG+bZIQf57KxNvYcB8bXgUlfkyuNWZROsyL21ykbFmXhG+ZpS67uVHjRY/U/l/+9Je+zb/9+rW7WqeNdnvf158cpt+bEKxLGxgE4/Sjihq9/PgQr9w+ENhd6xD71as/QTqI+deD2zpeowArVwl3UgQi+bHv0nU1ekHoXXIovvygCMl4M6B/n+R1uP35U/RD8MJLMOuKoTSwlG4KdYuIHlfS+8oZCFVm0tM5ZRpvjdtRDwbHUAZ+qEbdpCDjXKTruABgXutS2gtyJJGIyxS+o8mt+uGbCePo+jeqpUUNyB+pGW6MJV5DULbSyH61PKFxiZZgCT++y8PqBi6pcMjt1zDNXFDKSG70OZVr3BN8G9CkGdiv1gmqcDSfCqO8e/9j42mMAtPnYtfvF5iXQPGP8Qid10lpN9j69DO5u8qNOdSDGue3+kNNx0YqphYOS0CHAbtdWJMgG7HFASsjWl+xlsrsEWDJ5mpLj/jm5mdEG3Rgpo5BiKGh+6Vn9nyrOnwR/E4YNHGdS4qdzqirDxRu7VZPPRqtKTlJbUzKLFhtrfJY8xX7hQsWSjQ0cpdRUDZTrkihhvdvoeM5Iit/qaRrJ17qD4q4VzVnyWL0eBOUIz66EQGsXQKq4atH+4po2zSS7SbJUypZjyJ8fU37wmXejO/6y75tV1zWcrua/OcuYOPHAEJPBC1yEMuqDdPA87BI/bejkH7n2sXZeHJQTmdEtturG87LPiAAoMke/R0nawDt/1zCwtLqQ3VwFdN7qmaVSeQbeCmN/vDitlxosxWRGQzLNfKeDtDp9TV65ABdGO3bVeHrQQevzHULfvwgbujS7Bp1/TSwouspQW1zvxTGTa0gj7mSJUeSGkd8XerZGrt6QxYlmmi+zl1s2gWUYvECRqS/tqjZ9hY6fhJDdAe1PW6pBNDvuEWjnGULlWeWkNYWn8FE0X5KzhQy/fyZ5APXhn/hCRbwum45S/GAranXKoI5B4BvcCg3r3/kCRS/BpZ9bzmyXPIZzGObAKmhqcqjey6qKaN6rIb+Eu8MXZb9YIekQz1cbKv/SUXpJAfmHq5Of+JBpP8NxOsospjQeW6hTuoltaNcQafj366Gwln1bURKYDQcuO6z7l3i3nc3ZnU/eclp/ISZlb5P4mNonet0RDeBFtCXfrRv9WiFCQIduUIy2TNmzDlZfxcFDSbGdCJkk9skP3uoBemEhXhNDVYTKFqQZE+OAk1qcwDSB6C1lemhh5MKA58jJ2ahpehH9DTinJoJ507MjLZW0uWX1FbLX5A+mi+AJ4jjobgLCp7nbvVPqxGDt4o20N49tvuR6uMfPeXjnnq9coVllA4a47Qz+Igj50CbYlwZ7HZ2IKm51oZQMqy9XFIydUd9a5MvqtYLGbgqKgSPD0S12FehhWhd6KEkKuPezxLbBTbr+G2wM9Sot+h6doNNc+QEWd3nD9XfR8ifDmdfdorem9wGU4aycTHnsWCjsjgufUtNTLx/wLzsO3ZZ5rMSUurjkjrHNN0txBx9PcmeOT5h6vocPfJKZp6m9w1+LeXvQirCaqhyljN16ofmtFZrjVa5IziP3bzWDHg7nQMNyArnpSorK7SGxefExo5W0ATNUNCYKWlK/srBgDMW/Vq69UL+8fvmKLdcul3d15vgJbruzknpfwQtW9WpFkur7TCEOiLWS/MXJJwzXsaRwcxS+0HO5ALr6AAQxE0/RP43uH8eX1lyMzvbd5Ehc2gU4dHTeQjvjMme8PMzXu7ooYmmiPO14uqXf01oY3jIPJeyVCybZ9HdwvmwyPHi0+7GrbHCTQpzaueRZE6MFpnf6tp9Gt7dC1kBt6yyPbu9UeoN+vMdQb3HKRb37b1oJb8qtKbsmT/kghwz7YuN21pGDwZreqpbMczLpN6zvrZ+TZDepQHe2pV7IV91u9dBELyF6KhFC5vvMVX3rWejUj4GLSpXzmp1ycG3bJwLxmyQK3ToCHLKFWVzQ3hWzZapVGd0GBZsV2pDX/hbUcZfGO6yF/H5a6t375JJJb8vN0zMMwV7FE6Mkjx7AMPXX11cNDqfsLM+2G7LlCakH+JyGjBUQVrtZL8/HrYVZiHDtDwxe61+iOMh6JbS7dJ+sAT5fkFjVq53L/tg3onoveDy6VH1rWVb+Q7m5wFTzL358B1MwySekWoZ8Kb7z+3aAn26XY0qmiOBCd/88DmxWkjaBInptJjkt5QH/2paXtb2waenaLieihd/RomfzUQr9FWJ6TjWW9UZ/yqJlmx++SP7zQia4nsgXdsYexdm14Z+4odHdEt7/yHtZ3pdGj+b/LNm/nFDXljd57/kwUh6ndv1tkBsY5IqA2lTdHuCsz4L6yE38k2QXacUAJ5eD4OKdtVIOGvQGmntpppSYH9Gey+6kXjTR97NCQkfCncM/79Z//dfZYRwu4kwjz09akLBQWlHj+wHpXzI1sI7B+5NDR4ateeme4WhRo7MJ5yGClU0bjYPGIjObhjcFC8GpY9wbhbw8EJl4WlKaK5UBU/MVyNTUe7yAYbEplGWBbEOVPhV1g8QElEyh32YAGWpfPWaBu+CbIqX9btkc8BUYq+DC+WTzSHkSLMdyPorxzH+bM/v6RDn6jUwUbbqQ7s3oa+dx94Pxakz74m4RBH7BF4qxEcEwCoC+RmhKHasm1s/8cYE+sfFhB+vg9QVyoLy6yCxGgMAFzKIonqnRF+TiqVeRKajBU1fTVo5AYGq7hG4yIvhITEBNTMQlUTW7Ahh8Xp+ITEiq13JPMQwJ3eX2jCqIv700YyTHMyuhikTasev2NrGKLP5cFGOpEuD8P0zGC+gUhxTcppzXWAwnTTFtxb4NTBqthOgXiBqeqjekr9YHmQHppJrs8OZiejq9kjUvJqeYpd3l6plo7/ShMPXzpkRpNNMfIPXTl0UlsPlSHOc/xo/7hhMcXMH1ErhlEXP2MgV4/p4xnLWlwqWUOv3UmWWxFKp7JFivsetXcIAwYaTfSlAEcLFQb0/kpOq0bpacDdAm4b3DmRGl59PJDdRM5bjacYVVMafpItXomapErtmiJg+a57fwsGPTXnrZZeDtTvrfulNhLf1u9zZiiJYcy3AItRRa20Nup6uw3vR9L1NIcuoShpUU9F5cM3FmTFBL93xq3/iDeANKG+8/Mo3KrtkA4pm8TwwfFzV8tAdW2YP9hXqQvyo36Jn1BpkfsbOW8Ts099qK93swAw9acs3W5V1f0XTILDFu5U9fNDfZmO2oeAMYcZsiKvd1oc4cdN7eUle82Tv6mrR6ROf13MTKcO9QZFnQndISe0D27dv9ipLXUFotHJFvP+ajLb/5TNz+WxqS7ZOQuydXtrlusW6g7VLdhLIN114Vj+RbU7RzL32d1i2PJN9+70/Royd797SCQYTMkd+plUpDzenVPhufKSYGBVnbNkF3ZNOdsh4Bwx0EA4VZY3yVdStQXZG1fj+Dr65RIyOsHdtT00pI1Wqt7MUq+RhQf5fq2Dh3uRCcSoNhEu1ZNCDtdtbmKbMm7wUoMhhyMwYNlryBakBflvL5Pzsw5e01O3Z3xrJylQ1MPyPP14ck5ebF2RzJxifqjUJFh+RM0oE6UWOqaPr8C/SZVrNUgu5MqonsSHtz2Bp8HOtePa43H4Q7aTvd0VmgPdZ3yo/H0cydkS4qGL404xkZMLZIVfioOXOejsCXqoV79Y3ikZLMD+2laK6+fNUx0a+Fz/KqK6y1Sh3PVEfa1obX8BlQnK98hkqzi4hXc+bGqP273R82xs+NxG7MooD8hreJ2aES6etLJrSc8blNYwnYHim7GCXInWWh7wzGNDcmJHN63ri1PaEMn8vnBT3FTh7ialQ97FQJbb04yVLFkR7d1fodOWYTsRVkgcGUEDIsUELbx792zbpz5HQlZYQRVMyiMRdE2d/nBDpRVQXEVOGfc644QgxL6mMTQT2FcGoGTud79q5feUj6FG6FUmj3EnpLCbg3oaOgNxNW/OXducblDnJ6iAdeyarqxnXOGnd1n9cP4oQtdN5eS2/V9YqtmHz8JFsf3DZEge8ppK2f2t/7GAtXHhE7x1k/lFN41VPpKcGsTryTc7gUZToQKn+g278fsq4/0D+pKWRDXul3CmAAsHCJUXBnt2EOLLSrXWtqALcfp1c0RuTHmq2Fs+IHy0xBhbE0SwsbcGiqBo0Y6FTpnfHt+Ed02oRW15T9fXFjt5dHoVnEDEt1xGcmefQOviOnyNTz+MfkNnfllYXQNWsyEXvGRH5WVaPeGBLKxoA9f2Cyzp2bavhUZC7lInCfC7KYTY4dmXtbmh5qjBl8egUP+8Re3RWCcKLKo943GKlBFLzoLSJfG+HcrI36tDoQ2mx09H0+o7rcYM/cBbuhpwnCPO17+QESS3bLAKLj9Ay9YFr8VZPE7X48XPOSWM9jYmwD+qz7GtepbMYI9ElMIi0GrzA6oSsc2qjI9tHzSSrEkf2SyVqcfS4dK4dANtBZdUUrhgDA4hfzZAEpb1RENif2TWeFR/Nh6iLTdyQh42bONRhkLj4bc7Nhdm7cH+yFPru0FM9WC61saxfmNS7/ecKI0nn9jq8a5WfXDin3I+areQKuLIyeAa9px80cr5SigSsmDdWIfT4Tw/h8eaJSFy2o1+HYdVv38qeB7p6N1XUX1N7xRAUPCd/dYxSz+yhYrDuZ2on233WH6N2K9SSdk3OtTRJgj3tGTuJ7h2lCITyIVoZT7AWXt1yk/I2RsP8+WFcO2K1itStv2h5l1G0wHFrcF5gt+KraCeIFIK6Q4gyYCizvOUBnPonmMXltvVAKNoxlyeUt3aqWyjtyxirXvIXWHxoXVGs6dxUtQiKLiRqD3xtSY09niLItQ+XusUQknbGaNHh5bs6K0ZzMqNMFFwQ0kO8sB1T5Z48gazh27Npb3X/FzuSqSjW+98JNYYnhczupmdVfqDHOmafPrt0mQ4U0gOn5GL3i6WItUBL/6np/NYacdO1AI/DBtOpVDLR9+xQw6sn1Spi8UQ/kY6jP6shRoo1nAeA/iIZefUQLRrySn32b/q+/It9UvCACIHFRpr0KvusW/NiBu1VKaHKurp8S08TRPb+mB4qO+giAw7qEhOpuG/36kZD8f8mAMTeLiSFYQfqpg54N92G9hHibTyutbHxUHzGHPD5w7P3i9N0p4lq6lVOO8FKHCP2bIxQxxKdJ2dKjURK5pxqQhm+9KfTjCIJN5sPW2Cghj0BikvLpMb1rAtUSqryqtc1JBJEpKb/mZL/82RINSmt2Rsal8Y+M8+udEicabZ7fc1xY3085Ev4H3DyF71JeyyRjtyDXLdan0a+/J2Nu/tSnqkNo/LmAF0aroTBThym7IDNK+h9OsV11xbgtVMNX6df1SsjlxXKFshm9b1g99MJPFqBf7sB8b085CdjIwqGfQwqLUXaGrtTfc6Q/dt2PkafKj8EX9i1IKpRyp/4wgGA672wmBjTv/kY381P7wqUyY++lHzksg9D8k8j1amb0zMnJxZCQX878dO9/T4dDJXn0Ad8hmeBfExuqnvVf59VVYhFWgtV3/V1fzltW1sCNhVHVzPU/BcRBekc1Mmbwg7TBFzaZwRqthJOys9+wxDui4fDJrNQP06ERJUz7Wlqhko+q6i7F+gc6qrRnx+iAryuHP5GXWPV5SPLu/GQW3NSYOH+2KHN2QCLd1jZrg5n8NRBW9sYfaY7RN5U0ITSfpZVZQFIEVWV6CjL+vbLg16fhRInhoIB/KW5oU6QtlYbUODYrG1GAFl/CJCPm2ukMNxVcSyxZdM0a2eyLbayvLB/qqxNLs4Wj4guqKuEBJ4HMfbycs8p+vVIoPqIlWzahkX27SaI2WscXL37Y1lrGJPhe/RUNPMOjn0qLWr+4YOex9inSgv4OEz511l4pyKXGaJXT/uS9kwKQvjchUik8tKyJIU/NpmRpagUhEK8jU5NNTCQ8uEKFpUijSPmYDEHxqmgwKn/y0IE44j5E8rzx2wBk1sO3lqyZpkDdvYCO/AJCtIek5fK5mwSRah0TiVtflj+CuKDiVrJNp654ns/ThF5a+hrhxGuYqfSSb+7fSks13jxNcLMWJzD38klzxBIdZ3F3i7xGak3KRi1m6jxowNa2YptXRiiUyWolGU0yW2LVcUd7AYeUEUqVd0Z1foJhY5ZhC0uy66J64aNJUQzze0A53pyUWivIPrXOprqpcqucql+kIg+k/VOQKeRLiUh5juwAjEudluq2TBcV5qRNsEo+gbYqCpkzRlQsko4qpBWTFUjdP2iD1H/LTGmhjzJd/SD7n5G/bXb9K8xHGbLjp5s0EfsTWHPwvP7Nu4/8b68546iZey0yUkIubS2R6gOtivlG+NFu2SYuuE/4iLJJJlp6MzuEjqohz7MG2dH2umFKs0uUyUlMLrPFSTEzal4gMwm5/QXeq0dSVSpi4AWFYJmM3/MRERHkpZlnVPDeYWQ/zH7rCcIM1PJalJE1ilyurvmSTXp9fruT+kWdKGY/Lyz0fKG7JgQvE7FSJXpN/hlIYCwrOl5WBSIVZt65QtTT/ocs0J1iVyDIK2SjF77Mr2IveCU0ZssrnbmLfQHveWdFEC46RquIlQuvalGeNBtwlsoqXxrFrBC4ozdTKa1FmTDp6/C8Ekj1tDA69cOuE22mZ2NEsBg+vXWmAMDPKiR8riSUBf2C765hL0KpdP3bbrOmd29Y3+qc159bZbLl1zdP8khJn1EDtOXrQOXq5unlL8Jy4wJkB/1RQ8EJQ3tGBiqlxwdlx3p7ugqCCkedAJ0AjhxV9cY/6I/pYdOuox9z7U1uXkkOWkOv+mcVU92UVup4J9Hyl63SSbkzyv2o0l/gQdJbGrXlqiaMDQ/4HiDqkLCfiMsJQWI5MEhpQT0BGLtf77561QChl7k4U9PeaVBBFgb/V3v8mfYOLyhb6aHI1OT+FT8yR63KYIraNcF/oe+v1zLNQqEwZdo4FR+xc9ZoKGpgUMrHyOGmvBc4W5pBUKqo/LYWap1bn0VJoasg1ht5iQxK2XILLyHSE7H4vHn5CKiJG8YY8R241y068z/ujz6fc6KLgHKCB1uAFB+dZKRTW7zkWPLFj1eva8f9i26cuJvEN5Ao53VeWapT4aFI1Li8llZyvyPRSk3gOwgV8Fj8GBb9/EQNyzKpEITJIn+odC2/eBID2LYzpImRMsk5a51snsoq01BhFJPOtAC7JLkEjrXOG/VAV0URxM+4YwUQrJnBx205spGVudFPYyV6KQkXOFfKIPrkmh6Fke4rPO1ex1+bP2SYcYfSROxTQwKSgBd39qw85uILkTHzOHIBfFOr7Ro7QIXbMx7NMyZuMvrc+n3PIN+TNCvgCAt/hOetC5jVdl7GFqc1w3foz8bAd90SwWF/69BkPzL7xueWG4s9+W9s/eJRass2bB/ntN2m4fsSO+TiSjr07KzHUV/5A1py2TYEG1oreb7f6hnzGfc5EFkuDK0rKAf30sgUCOcYwB1gkDp3Z/9GuFzlnR/udkkNTqil5KSmMrZRDy0Ha/R1GgspWDRCJA6uQT7pdLFH/R2VrAe6dz3+i0F996RtdFHYjCJRtdsVzamP4FYfa/lVlsmR6fMPVeL8OQVlSZvEN+fSaGbHNCGITOvbTag6kLXVNP55xByRPIAp81FMfOGnkTGrcp9Vo6mTy2OUEysaTs7FMK/L2yl1r/x0BGAG9Q4k7swempUfBk2WMD+NL0F+UVmWbr22ataDfVljPru/vj/c/Rhz6WDK+K7i7Sx3REFIyNmfmx8KK8cHV4wOTpwq5Y4Nrxhb2cIbtBk2jDzxeR3X7vmz91ti6o2VHTWnFx3HtwePbi2DXLRjz5b2P/dC1z60FtLuj3lpL6SbPpkXXTON+jTOJhm+aVri2KNk/OMWgFxXjx1qtGgMvtlPIsga/R91Im+XzESZMV9oAC3oU48cZOVneaWAjsjYRbUIDE657pEDTI2PWWvyrItCBtvUYg4Bg//pNbBMUYNa3BdAR262WuphIDwX6EVzLquZTDAttrJCFFAO/mgXXppSt2yNQLaigtu3bV7FdYPj0FlTEKv9cOqZBSLRtMhGTAEqQGvG0YOqkCcYKsNYzreZWk9QO17op1BwKm5rb9vPRal6g96tvF9hXGZSMmNC0sGHk2iCTUAbwd+AgOig0kmOI8NtSAWtT3Ylp2ZgOq5WxxUuxVDI5JUuh7ejuFoYC//+TREsplS3a7PuZxOD0RUpx1oV0ZXpmqrphhQ20DgDBP6sEgc3nwNAcANk0NRNKpGphY/AyGoee/2dfUZOQ5sxf3P1+bVIFkMS4ziPv3mnBJRepNBw9LA8tp7FZBkdP/7cQNJdgPGFFsrFv2m+90tGUmVgq4qfBg4Z7crnoBHlxo3ojq+S5vtBUZixDFywvchZt9H2DFwiNyUaeEpa/XfkTFVqoq4DC6TpMAKdnJuKsS2x4Lvbd1L9v6el4OYGKIxFxqFSDQoxWZipJSWHuMG+oG+hqnFd23cpVpHNNl857WBo8HWlicbCWkUY8R+Rlpmtx+UkWwVR3fYtbiXBXsU0sNfEdwERBXR8CJlWqKd9mf6ddqd+QuQohc1FwZt1q9XxJh1jq2iCCRfHt6HU4NV0itH51sRRCCkpuMyA5HAPShpJThCyF66vQKqHj1OvsaJ4Y+U+iCAYXcR4ikQ85IjhMlPgPAgftuA2GgMF3OqHQzjtgMAR8u6NiLEdZt1Uw5dhWt8IkxMqUjD45BFEfZ3XkzSPYWckI1tSpWNTCZcXnohRIUTSb3PdDhijMlxU4I80pqYp1WaDF4QoYtLYMcICpQS/N4LLRmcpMTHOBLO9KGT92+fCj3g9ihy2tDJfOK0KUMnTHzUCyvUGHUMbriSNCqFR7OFGfAFfUG7OodgiEl9lXiUtJL8U60sTOIY/eMDyYSkUpk7c+DnQrYHa3zHPTTE9V4LKFPGyWSGViP/PK3IqQ5ufF17bT6hI9FTlFQi7a5tXhWDQT4A+KCTcGjdvfN0BETnJOehrsKhDq5h80VS2EBM+BVBw2zdNvlm3W97X4Wvou7+Fjw9JoFZFWs0MMbkkCmtd0x5K7ZBS31emJrom0OJ2ZhP/uEQ1ULvujZYSv3Zk1wnH7c871oTm+oVLeaY1qmnO80wE8agVW1rf61EUalNyFIdqNJcjpu/SHRoCiojiRh/S7Y7RyNd+Dq/PA4I4Kj8XX0Aassug1w4GxczxCszwHl1ZpT1q3krZZ3N/CfJpWTc8XChlbKZ+Wg0xPdpBuX4LLKBSaeuk26bZatHRGB/+f4YbpeKW2zxFPSDQlBH6OlJzh/vRczQwhOxf7D5RmWiZMwlMgDEqirmDfqpxVmhJnyfpz2tz8VXnOAw747DSjRC9SeepNQwDZTZOqCVhgu4i4Kdk+PHyy4dXo0GhzsrLY0JzOLxrhUNSao8/Q5kxQ2aQKzbWTCiLuhSKVp+BA4KgFd+Hu33aaa/7daJrrmBJITNBcZoIgF/6/+vXl2ScnySyhbm7Cfg4HL19sArIzqkkT8jMMGC7sIJgLwZ+4oLdCFaT97KRkw/GQXg492p0JIPNckCmtd14F4+H/QP/9Nu8xBvkfTZbgFBq1yaNzc0cna7WMLV6KackOgyaVXli1FjBSUxlbiV6YA7+YV6eIf/ATKqM9nYVomhouw6iYNIySp0bShW/on9MS+BnyXOEjLtH51knicqn6LcnJ5eIWiBWIb4nOo6T+t3dIpFdv+xv3tncvf6a8/W7brWUyb4xP+F+axE6BTijVFlrXA6dSpGh41feRTr2zUBGrcDvUY2Vy/YLmtpT2lOYFekXDpn+Y7o6RHW1M96z5rYb50zTTF33rrKlMU4nf8bh19VS8je08g06nIpcUmMlNX/1HQP7VDZvzxycH+KV+usAnBURCvx6i7A3BkZlfYQpvw4Q7G2h5fGZwyHauiltXZwaTFKTqkKLVqtV0VoZfvl4VHD4iShUUbFUFB48lRMITxeuCokaEzzqCp8xUzRQ0wYGNBByClXAcgTiewEIgPeowIDnY1IIV8UvMObu/0l/59WyRDjmHqBeIWBYNzw1mpFZeXIyGv6mz/Weve4PI1wSn9YrhjXAMOd4h3CgYiuBZ4fqhu5rpqn/OAv/pwQCBmJ5/gL/WBwMWAz4foOPTfEzZvLbfq9ei9yfL2pfyf2QTFRo6IMJXMwjfi+L+uNIWXzIygfaUAgodrIOqR0wtnBYLqkgA5ICjdWMuM3Jm+NNV2ZR6JceQ2jn3e21xbRdv3fGdLwiVgve0wDsR2nU6yX9IOdfPM2yUqy1qFv0QLSklHTbebLO0vSIeTu2Z0rRDNTMLqu8v38QYpqATx8mmOV7dMtsOUhxQ0IPk6fRAJvtIhxJh7ytElEYuIjEPLZuOi/R0Hzk0uR7FN4ig/eVn+Q8X5ED6X16WZUUL8NYDVgyHY0bNxRk4TKb60yNDiV1IcsrSXFie+CveFMLQK21y2qYbV4jhyNVaEPR3zkhkikKeqpA7dMPLXrz770K9KDjEAHd35XywZuMrPjBhJEchysdjXtk4DTJvLqB6J0tPvz8MjT6ZZX72Z5qfJe/C9Y1lOMK9rFSg2jAG0gavI+gnJkqWPCwfQVDSreHrFGgKKNpnuETKblewfiPsPICdOj4hK5AbqOF32j+yhnxDyviVnfauGzO5+OwMRR5VkuKmSVQ4DzcR7zhiIbEZdcf+sFqHpRY8SS0YA56CN5/XLhfmDeWtOPKQhutaP4mM944aSaAvzfhJRDdNvXK0kV0iAYn7IVzausB+wM4oSovMnmEYb8e9ItZqjiD+YQDEAUpiZsFh2G/iX2Pt5hceEq7vOb8wGZ9VMV0en44FuUGQ3A+x7+Dg0c9JMCbTMWkvbOJ1/5u0S4GdeC3RNrC808YxbCAqPhpnrtOu8/q8aq3aSFg8MjNDQsuP7Yhpy+UoTFkjZ8ZOaSbsYCq2PnCR4HhkuBYLrP4NpuLtQfR+VNkZqSq0V0B68HlcX0ao1+Ct9lc3MHEf3aH5c+sa6qb2z12nXbcZzh4xo1qr9vo6j57x5s5sbz8qQ9590G9yEBaPlGujyDQjbvA7t3Z/8OUzG+vJ/R+/MRbBWg4jfagEBsqHe0QeU0Pvx87ze5Z/y3U5+o9ksrNrlPrkcoarl9tnivvI30fG0cVSTF86F8dethCbvhGSZzcqjsmoFAMB92zxj2SBAq9jOQnFy7FNUBJ+XT1Sch3bivx66KsFyWEWt7sQz0qdTlckvjxMihXsiZQvFPLPnKIslwR/CEjk2to7Gn4NSoLKtvURSX1HCRV5ex+J2LfV0B2LGMNeJvLJLWJalUkOuL9473gMkq17C7j3iMpR9lIoV5vWe2NN0DL0aMPbXXJSNb9S7jKMWHKzxlIj98tH/SYJetGOXY8IlIs7Van10y7aNH4yazuJJQEgUtYvi+6JZ1aLN/M4FzciZqG2yxegF/EjaJ4js6BV2+PckAsPVd7lg636CeJHfOyIaOhfTtk+Bqqs7bSoM9TQ7v4fuFqKdJO19ef27og/nZr8GheEZAhyJD5DoQcuyU7cy+QEC5gs5oNutDbugtDAck5z5o+10dMCXOAYcys8/0rfWm5yXAfyhfeuxbiaOlOSdRnSJvN3fj3q0y79HcPpjedpVseB+OScsY/+RaXLskuWkbO+/uV0vBBxiuuxGEcgGYNJDjgwsYpY/GE87jUhFordZStgsflqmjpuLob4kEgcZlDwD5z/VRIwLW2lWEzpookY7ISFAQw20NqM7E0ub++V82NSb8j57b3lMfNl1tpWPCV9ooLOSZxrlZvySBGehCb5f25+h88VK5TMvIHQ4STvQiayO+JRfCw2JgIJDY8sQzRSryU8Wx4/ktpH98r5pAWlnTsUAmrGDhF+0eYpePzkzYue7t7Nk78tmrK5F68mB1EowWTiBwrlvyTb5fRdapeHHfit77eUmo+Nmbf+qMcalbXyRxq5t5dIUmt1zIiQdFobQq7aUF1QYKhS36Dqr8rVDE9Nxs2fxQr4aXHiXCm5l6jL3rs9BQR5DoE+QGiiYKXMI1DYRaYGjm6L9KfI+tZLwxQFvvfj5qiWAiGvEaBPygO7nzOQb9OEcES/77aTgM7NT0TRtWRw37pMGGI0Gn0MH4lbowXCqJsunE/TvROni0s1pf1bz61asKFraqFz9IkILqQcDd/5HgVHK1QgDF1DBRzZIADhxCi0Ax1/bmscOCkZGHpDavp1V2iUBv9gI08Eo5EoblIomjSySo/j4hwpCr1glvgRtGQNsDFqOwiLQOOShFFXIMw8ZiE9738ZSkrPpTjwmgO7M8IxrEXY3upjP6V8gzIjw6jkk34eq16EhVJjHqJR72OonTOQhQhEARJpQyDsiI3QaCg09nNbtNo4V/q58aYGektZNkna2SnlmuqObFWBTGZk//jzIsNvQ54aXCeN7GawK1LJk98rzHO2hiQA54FAS2GqqLCw4dMhkOlfkqCIgv8l4SPnit8r8uixBfUkNwDQC0uYo9i0Yx0ZdjiSCIFOFh/AYhFa5n0ERUEIWQLlQGEIRDsmfNiuCaCXYUUtLTD1jSRRkkflWbZ05/QCm2mkw9k2LpQOXNcYBak/HAFBsuKvIClywrBlCUEA1DU4/DcibmTfRUAGDhN0Qmx/veSjUvj9DAU++/4XLgL9/ioST6WS0Sy0nqni5DX/RzxFRF5HoH8jgAnS/2cGU7Oo2aSsf1/DKXai/t3e9Wn/cV4lLD+Ah1CQl1Hoy0gIBH92umQ8kiETJSejHAM5fnoa6CAYdA4kvwWAD0bsgP6Ew39Cob/g8F92TXOz9wGNdDMIEAjEASFADByII261/q3WNyu4pKQxqmtcf6mGXZSl66akoWPD0fZ0bzI2zVdS4+iDgkr6dHkbJsOHJ2/R6q2lOZ45jiZj8qlmFu1s3dFQytiajPTfYf/ut7nmA+aYez94kK/K23mpShILeWH2dCg0/+SEv3Yu9Xh8Rd7JOIHphPlL2dI7htqAeSncl8yh9oP6Wq/ZCF07dMY8d9VDM9vHM0Q0fzJnRGdFdFd4D9DcDPDbLeYD5v1Ln1ORMXFJc0avJZivbzQniHKvma9dNV9tsImRo03hqWZc1v9nkkBzP5k/nbp62qUMGVA9f37pvHlz5z5xqQeCN99SYGgIeQ27v6JtbMbSvGRCjjLTT8sQ5TLlWlKeQEDyypVeihARKUzJ3FRcyuzg9tERTC1Ae49Bgsm33yNTDq6owrNiDJrBLEuKboFZzm53540VOZ2MLV5CcvMWhw52v0ua90AlYmuhTiYPmZOm8TFkMh9DYJHEY0KcHG24z4M8qaswjGgqL6IHbbmWzTz56LH/8Tp+mXhdMJd1R1aha5jgl/AjOumuS3nti//wu6KcW6IRx4In1salzXc+k7DfxKG+t1UP1mw/Rn/+A2Jfy8G1QyPiI4Cq8WTLq0SdLjKY+v+W0jgOJlcGTMiTWL9blRWfjaT8jjUu27rC0cEHWwoT//kkXnL+LDHRLkjRcB8O8ez5ZYRIB8eepBZCTwy+OW1oA1zUHE0gnCKiFQIgSME3bwsjKVd9u2982J89F0wAuAcIUiUL5STooWy44WYgDqKNs1tNg0uyRc8Hsk7kPsjdfJOwCIe9OEHkf0rsz2L3Rw1vIe30cHaiwY/KQ2Ocl0XQR42h4ZE7wLsvkW9Yo8BfOvhYwZKKCZ6SxuLG8uoJ5JmP5q+bLJDHBOcUjtjF58eEMWrQG8OOfZcjlIOn37CP3zR6TdzYjJ2P64Up3ZyikekCkR30MfOXj1dnRv84I7pmpVyD9eg7PJCgIxxjLeBjFEaNJFwZjl+Dx/1DCId6PlLPeJLOwHy6b5JxK07O4uYd6jgxL+jkvDGH8rinZjWe0GmNOEP5nvt4ZWQyXPbuu/xPcuoOboqMzWaD+2OueWIV0cwQEARzhojEx2uUxDC7LHRpzWPX0JxXKN1iIqBtKjAaRVbZ0Ci7ioxCg1V298+B4huBG+4F+rjcQ6zcQ3H6Bb1ybnpcI3HE9uetPe0wMphS4r26Cyrj2ydwHEFXNwq24pMHPcY6ZmcWZCALkqIE49ojf+eKdlwJGecGdLR56p3E3/7b0Bos5W+MwM1v4Mn6zWxw5DZQst+Awjf9GNml3ym31+jMGQ/Gb1bwDt8Zmjc0/UQY6QwrE+4WJsNdrMzT5Nmdg3Cgjs0B6ocPzIwKDZg5AAfqOWygjj7QOZt8Oq3alSyEu18mf5Gi0r5Jl0kRVpwKu9wcQ6uxmq2IZE06ND1m0ojW9Na29LbPGejkMRKoBFK5+76O3cBC/B+J+5eIwDKStyriCQqRSjKQIQk5ioCMegaEoVsCX+1JpwAx6yquxcE+gcHxP3OvlSKU4UT5cc3O8CQ4LnuUl4DzVJWzQyCnz1Y/mgOBxuesAQB3VcTpUFMK5YSJS9pJhPYti4gp25POvUqg0BisZBJxLyNqwVcnouIfElhvZQUeRgBu1RyXZ6VATp2t+TgHDE3wrQUAd1bEmdJ3zP/II01q9xCIo5b24LGZJQ7scCD0H/4xiviIewWhdMwidGvVX+/mEaNR9xFoq8T6RfOFICEQhHclCSn/S6wd9Mlr67BEPej6lvqTCBKYSQwXwHHZIz14rKeiHI9ICuPL1bnNvUXEe1cDKM/okBtntS1tMBgSv39KL3aVZimAMFAHhky+cUFzXJOWWJOkSVIHVBoVKZxtG4qED4OqNEb4oqp8NO6172FozqQWWFDNZGd1R/Nut3l0jvWCqW+pGRni2k3JaVAYHP3ytqkUjV+8ZKHoTMCAHwwMXKWgVlUJgC22tuzL1xQ5Xxrs5FSlwKFeo1kJiD/YCYYsvnFBuHczsXdpO4HQvqSXSFy05P2zunTRyNWML1TKVzrohPcXkPqF0TTqSxZCM0cQD8MD92jOybOEGrJkfZt7b57THPwSCZhiu+wQE+iytrxtM28aYUBwnOJ0PL38MuNa9uaYhARZbHyw5smv3BgwIunzFAhhf/l+ABm4Ghj/X/aTt+N/gqHBp31gtIIvvd32FyxBSJdzp1Ml1Ah1hsYusY+NDg+/ExPV2yhVrLnTKxBO7dtPvP2JJF9yUX3N3C6QNAks6mv8h2LRoNkpMPGvBaVdUwmvBguv6j0CQ8qgNrBGUaAo8DQsqF/mywyoA56WJdWLxItuqAfNWwY+06oKqknmzEHJisB4ySNsx1+T05/hpbdjSuVpPBPSL5Eg/VyTXOLeuTqeLIc4kgRgB0kevwM4TLZjGFkOdgiSIA6yPH71Trdcws2NeM95WilQgc3oz4hwMjIJWywRgnFZtqwIV52UK41YxJ8knWTcPdKeDJKrV8KVJNbv/mET9dNJptDQ9XrtBS2ApE83Ki7IeZQPGNghuBCJW7LJi4r5kMR7Cot73BcGQGkQ0MZES8iJFu0gfBGaNHAMJiMTOrffI6MnpkBUmkB4uDOyaNYZMOR5vw2GMNzViVasU5JfvEXIaGSE7PfdcNof1L9pYNvWLzD418MWcMLoOwzAtRbtkIrF7EUTB4/WtReobPsFCrJZ7QPWs+Sv/sKbGAWBMHLP9aqpybSX/ZCrF3+APrhDIixcXE4YDor4LqKxFY8L91DfNmwHXNTGfkwSwfChOKJX6oVoIRQphY7uX7UaDyj7LDk+jnjuyAwyoePoeQpqkpCikice/XwFT7NAwvvmKCEYkG7vBzhDtDAGgSxccQYEubnVCkP47jZCINq0cUPaa6wxOPK3I4eJO7RbQNRLu+CI2K//CtPGObVOzQydVkeKSyoEReKGMLHZ2rpkljKdfL8YFJpfUndy50pXy9SxZ7I9M254tENaffe4kSHOPZfKePEYFM74zn8HR37Vv5WwQ7sFSD22FgY9F45Cxf7jpmyf13bp0TNzwRgpfUbONCOObJVfkvP3fLEnwEi78y0MhThgvBtENSef8903XJ9R4eHjTt4bnit8014RT9JOI5egjwbYh/cHxY17UYDZseonCZztT7ROFfDFasaznoNSAGh8AsBZ/n/GPhwUpaUPwozhL5LpkE8QUPGS/2MXxELhlOfTIJi5KumTyX+5AZimhmnwVUwpM8ykMOdIc+bEICb+jAPCpZgb2ru+F5RvP49LPzFO2umw2T4FWz9Z+DkPg6VDhkKHUf4uSPnOmuMwiYeCVUParF/jzC2WFm/n5tbeHGOdvs4zav2oreIpL59zxphGZlotb4Ntb82SfQsXSP/HzxxaKv1KkBIIpdGQwfgmYzdKQ6WJ3DLdTERpXv8hYdHC5ztRcJfgKk3fwXC0LgWWZmo05TXBZSxIaSolNY+2omU7egXlQCZKLPIVuaNBROotY6iyHa3bxWs8+ZWzI2tfignWl+OOBjUiFtUdKU0hq5B31LAXRtOWfGnQvE8kJ0pfH6X56cdzPL5iLU3XaT4T7C3s0vwsEDzmsR/alWbHUhGLbCcmo2nP53yl2bUrm9aRaRuiB049xKaa6nBgKAGHLWl1YbGu1hLsZfSokI1ZGIQAsT0wlILAFAWEGIwwUIRBG7PR0N9QOJAaEn7PD4H474VDwAaPRgv6csBpNTlVJzjTns9yPpsKMAE/43US1Px9xVdDzY/Nbl37x/Q93HOJDg+xFyPdhvyf+Cs4SqeSOJQZ8dOz/8sWg3Tb8IGuq/X4Q6ECFgcNXP90NlyCuK5uE66GrDoJYt7bLroFMyyYUmyuq6+ODIov56YOhjUrKp6UUQyyJSv28C0yV6Db1PTA46mHHXG1oIYduU/BXritEzQtmDqODIPH5ko/rgif1hmXfY5EPuuJY0vA0/qBJ7x6ROdIcdlgS33+KpqDvMBcpl12aZBIIpIIBDAPB7s4uFS7VGiEdYJEBE87KszBiwTZReKzVEWuxgeJJGGPlkybRjDlgwnaoGUrhmkTpmGmTfdrEoKGTVgRtKyVaxdMLTDV41dt2+LxQXZ8ehCRPjPh3QTg27OxgliR8ec932+TY21KnYxVRuxl2Nm/gNG758/fFTPtBt7LiKkUcVjz467u+QBoONUA+HkMDZi6aQLo3p+hA86+PfQFgD72c9L9Yc/VuPnz5MqYjHvAmF2icXf0YXfg+biMxppDJK3VuzGLB831KaKXS5eH7Zx3iJIWYw4hHK3APewMsyv7Ms5N7HK5a2oGsPWbx7PPtz/EPY7v6mq+x8dvvjL99qxunqt7vGjWAvGsyDEjjK/kxu4sYLdLO0tUPks8JhIyXz9HLTmIXqKITsBp2/zoItK3zV2C2rIEHa1IwffdRF0/kO3UtF8pdL9G46enpDC2Et2fg7RtYeEMPQYci2nE9ehqFhNn7DGZLEP7yBjbca2LW3H79Z30HrrzqW8SEnp+2A6vvVPqUxduFfu1cXfy1H9J7lrnmHgHTptodGh/uw8OMepMa3Ii7GysR6FHVlkc3+geA0ew/X0XQnI6W5jzPPcXmIpdhNUjpSPtmbl3e3LvjB/RHNQGUH119n1PuUknwPVAivNuTs+dnLGXrNBE1BW455lDmO0uzMxZlnO7aUSL+58597AwYfwRsAkJ3MntuZ2rXVWqJsvxtbrz26zu2m3m3l4ZUjZ86Xvp+447Odpt7l1twKh5cmffn0MOFGjPuZN2drr22Lr73impNTJQbpNPw7JuImued+aHDt+dpm/NarJGjdTb1sH7VWLcOX78TmNJCWOLl2IlpF1KsVe5vLl5udLrFVhgUcyL7VI8ZvHkQGCy2GwWWGBRzIztUkxKfpPL1cRXKgUWEr8pB5rkFEVnHHnNZNQMHxBLxem7nq7lYNeu9I7AwXv3Booo+PBi4YvhCw+D09fOaSZMbCJIKlhb4NK9uwjNZYSmCgm0cqR3puJG9uDaF1yHNhnpdOlIXE877voCaIsQy6AMDRMtG1DVKLQNptLzBlZ9iOhqO46c/K3HG3bsmpNJoCG1dBbB9KcDx80oFB/X0xN3UZCP+CYkwsx/hIij3Q6sWTi/yFm0LWcbqeS53m8qMZSgg+aFm/hqgZorpxbtUqLCC3W6v2A0DdqFMbDZeMtBB57P6Rc4ER95JoQ4QyNL6VGRkiLc4d4wN1DntAdnoE+8t4MjJMNOVNS19zQEPH3YCZvmdI2garCleD2TRTTut2DZLDO2l2Bk4Q23vY9oSq6Ra2AqIgwcBENOV+SlD4ey+XSQjtfssWI5HNb2uMr0JKHzloup5lAwikIdmsGAVogQhbN5ebluCZ3ab+CwwVoopHMwDAwOHeyEQGsHQ+OVMBr2CgJxBUuDweiwgR4IdmvaULUfuc7HsZT/jzOpAit40tWVYdWg0GpNpSQk/YmnxyAorFpdqftTirava9VEyJEGN6ulRRm333d1T0V5t0SlzJZPjJbCzWcO+r4Fez2i2UTt8JjsAQFteUIjroTH1/zDdkQyJjM30F6l+PpYxJMV1wBUloqoFqwWyjQUP5CmcyISZbV0hzGxWiVLrHRY6lhyphECoqtW1GE5JuxkrDmRj7G0mtCJ8vmidKwY7RUo1IyRx2/eUAQLnGKryBgihD9JH7wPt+WLFbzsp0+Rpx8xF6Uowk6r4ozZIq84zxayLITRlmfFtPvtO5+YZbuy6YpNaSycN1KkzRa4h1jLylDWC1fIU3ba6FyOEdeGtXD4aPsl7F3aPMny0Pfol+qXT+x7xXnlE5d5xT4xmWTWwelXpqjQC3x9qj3hvSzwpghmcC7EC3jN4XsXfVUrgTlVG0s2z/aLL+HMs77BA+c0+86e84knbxkI2jYwuWfXmT+94tHL9EHGbpTY8+cKs3LC7g0bY5E84apSfmkPSsqutFpr2DJ5NbvEC5eSGJZcVgM2+emJGHO5Hstm8wXLMebE+Q94RF5xBQufoPr2/RYZk2lI/HvNeMBlHODG5Ul4Nt12td6KRp3AC0LDi5T/wYMMQIb421mawa7oMZtLcZ5sfn/gdhmMrQ7nNKQ+67Zo+hiLJ0PnvLD7bWPvv8pHPjFzK/LRRE2kCHYA7csm51Wfq1tEZCaTwJWq2EX7s9dtoHFpynNqIEZSQB1cXiRD0UHlNwEw+LiJvN5F4YUFJNpghAxAElrRFXtjnr/wvZhlG2UToEdMqpvZ1lIzoyfRJ/LrfAqF6PSLfBJJk4ytOx5sDrJQXVnasCN41I7SZaLihq1BrVuL67cG35Pye8aIlt54LFd/HfoXvnHV3Qr1tzDXEKWnXhy11nW8C6ZvYRVHTK/VvVakbSE/aO/v8A6ObreoOK4Iu+p9Ym9r0TVkFGWbLa4bMGhhuyfknWOXXI8HmfDhfWFPoQrNrwW/jFFyLhj4Qg3GJ+wrd3rU1QmS6CXv+svGFTX6mu19Js/T5PZjkeH31Z/7om3MdA+Loox/21DfSBO15Gfy8Nbz7/L3JjeGvSzlJKJNfW9z53LJ3wLdPAZeBE/v2qIQzNxdhodLb8m7JfIvIUJmoBPCHE/mg9jXEukXioq5ZeK+5119O7j26D4/u4PFeXXdN2OVQWNw+BwcJu4jWeU12zi2qXtwrRRqe/bXuWKfKbsrkBvwdWE2seB7Huu2R3T7TV5H/jC03AmkXdmcXQtxPbV57Kjuh4IWhmhLUH3HY8JoHc3+0UtRHFQBLydV9mOA4LBGK+SdAn1OXeHRe4J8QaO+onMg94fRaJnghFa4iPCZ+XmCxIorgcj86Z0dRiu7uMcv4iE/UbXI3rguV6He+7eem6VPjPX9M8rqNE/k7i82x+W49vSHjCnp7XEPuU2rUPNTKAEVY1fjW8VZN7Tes7fUrxblJnmNBZ9Z/3rigU3Te/9lqFC4e3jyIXpUVnCIeNndA0TSuc3NSEoWebAyN2dToCBQdOZBRLIzIjmvuCB9PW0iSpfm4UqW3j1LeCBXEdCOsPs+A6srMWBZLK5uxugYjGAzcfMlWJ2djk4MPoFEngjGotHxlz+ocCxm1UKU2TxvnHbeOLtgavKspFnay6D7yrQSlsHADIjFzIDBWMJMk1dQDWp6gRj/9j2NAHlx8tltNBYsFGCQwNQUPLjrMlIzFqcuXh8eIz6iAbMxmgYzksvSY8uwRjYbayrT41hIdh/14U47CHQEMj0O5ydCvL1jhbFiVqnREGClpZWyjAZWqTiNFTAaSrUQsAnoxCycabIBzyIZPxlnYrmmGN0G/JT36OTe9CNQ0t7eNgJ+4YENBKbr/zMTYyhBdPq/NOQaJxhYmP05jyagZKLkv/sJki2tgbNE3MnruzFCh+iqBTK6AoU7jcN9Koz6bREJEPKrlTBdQJYGv396J4+rx7ek5dl0bwbDz7OtyIBMjgiwbefDtryy6dPyCa1RYVGtVWAyv/eFUTUiIJch5289H069a9aK83DNUR7Hm33qssKyTNpwR6Y9SPW+N6xfPgQTr+BKqChlSQtSQbVw6TllItQ7osMkbZU5JDf1ZbiOeokhIeVUthHDoi/Lhq8Ou8Q9K0oYl6i68JdUt6uHwNV8ZrFQkYqWEpSSKhmtUq2uHvjoqOaTsPqXxwcgbah+SZKII9SWTJ7Il+pT8VSWAijMkk+PCGSFB/LoUPNIV5IG+W2NGhkV6ff1hdk26n81z0QTmJDVmZm3cgKjicbjG2lJRoSPEUsyGZE+LFHkCMt6EJ71SuP0c8WeUO9DgzFOHVO6LTmtoro6rTy5gfL/w1CFaNpF8aps16+t28QS3ZpSg8rkq+1OI8jRuOUUXCzMC+DefLqHQP3/1mQCkeb587GpphrwX8EM8zHBx2ceNPE7CR3W5EBVsP9bgyff7G0jEnHykGN8M4f6u0MPY6T5RHPM1MsCDHT6swQQCP+sGhx7ZX4tZP4Pn9Q8sV9kynK7rK752Y/Pwpqbrf/md7+4DgrV8x6D4skKOBotp1JRSoUezUlzCQ5pGNFmPKT2TgiYS0+BNXD/4USYwiyhBgy9vCrKiK/zw/Td6ynUH3/AlBQqUpWgQtEFVlINys62sNpNvnLFGXMGUU6MRe3e34vjdCQmZuvbv8+XTsmYmTY54KQkd9Y+S66NttWLfmdS5eCvm0gUsjJhLdeYSI9p14CpVA24PYbO5RrWJpCVFNKmr3Iw5RMtjkh+w2C8IRPjqBbuAiqjm8frZlAX5LYxtUHkh2NjW1kjF+7Hvks5Rp+bvZAIiJj9HQSudMYtOuBeP59Kv/ft1AmbwgxyUJuXb9ZS6eD2p0QYRBq2qvXs/Gsn6eSWiYEPFoW1+NunnJRO4+sUrVGQg0x6Vr/NisL0Bf+qhiLiSCRmxCflE7c/TS+fz5a/VLgNVUuKUfkGpTQifngkgxjSepnyrwQP0iqzl7HLLPhTq+laS8Ht8x2DyykMhOLBu3euysY9xLVw6QyE+l8ZikpUJWwn63E/YHyaSQxosDQcumtzpBJHLYOWjfk3qPXfkhXQUarZwtnKuaWe0jnKWcJZY5tiagmJEZpMdRLIGZqgz0Kgv2hwGKXFOMITodLp0jEzz+N10XoQsT05QhuwyIPmkZGy3xVIKpWr45AyMjkYR6DfXykjiXeYzdnK4/Vz2Ee4D1VPri+uea/lqCGBDIW0CD6iUEClBYmSKUEoBUqFRqtRcAoaTRJ8R4WEJBSyNRSFCs2TkKiDDrmETEZjkpAIEAYNJubN4wQWcEal8EIKQSAIBCVSCig3CkH6mCCv6pC0allv4YCQVSE5IfbWCYV/RJkSVkS1MznYaWqamj+xi86hIO/5sI7mMGzXAJWLVoIEYX9iOSd014UXnrsn/Sj4c0jfR8wV2qwJ3R8+a0T7eLK0K5RYSONyn7bMqB9VH+G8wUSBIFFJHTMwvHqq7PHAH5hnVPIHoDYDEO0/RIecKjBcirQrHa6F8me3XJfmzq3br8LSVQXf88UfuVno1lSjgyAUMhaYviVlnbA+qTHmqAiZVqAx5YlVwjsem5wCNFozCaocrQY2spqVWa0ko4Lgu2HGFi/4bmedkWJExuYQCDmxdCRTZlg3WnqtvfyMlWFFW5l4C9X4arntmkjGp2zywSJsrcFamG9tQB29/FInUqcYgNSpBoyy0SDAuJTyMpo+hDw+/cbut1UwDJOgn9g6tYg21WiDW6DifFXAGsuO2bEqLPirSyOfuD8ahmUS00f1qmmbmAn8eMSbZc3yHv0wa4m9oVTjMUMaN8m6gtihS2VdhEGzcvQoSx7pIap4aR6+bHBjFVFT3imlrG2n9glZLvduiqsirrzpShkOQ6r5yPOYcdz78XUYWJ1w9/IuurPOT/7wZChkjyjDd29iyjT/IwUbvXsmyEMvPitE+fqj+UL1hqtGrOp939spSRo6GASalfLm2SyPfOdRHzwaj/nisQ+P++bx/xsnPMN68UQ0Gac+/gaMUtbTCmOWGxrr43Y0PkwYO35lknsoEAFcjRafYQbMo9A1cP8D8+UXWvgZVDMA/C9ySVIZZc/2QdolAzvd5rhuZdIt/lnfIQcSLXK7Eg9TKXAn7+lOtbrNQAnocqQfJV/eRNtvufUHLoqA3ribX2oo3chWuqVdWrcE9w5z/5mLu/ujM6U7PSrd/a4yD3QNvaCfnH9rlZvIOFyknyKSjxSmo8Xeoxayj/1rW+49pseFfLOMMyDSbxHJl4W9ZyKzbT8AQ/t9GMIX2yDv0WRfOWqoLGwu2ZglX6uE6eZ279S3WNNYyvCxGlOb3SDZTnMON+Lv0Ce7TdneXZm20CW7ISdLO8Wo+5k49fy+Q0b7PnGZedCqz9JKLp90ty6Rda7u1M8pV5SOZGrV86sDqaUZAu5z3GLHxMF6fMNlVafC922tuyWLrAt0p4Q2ZXJeSmQrRiRXqmq/SL1Zd+sWWVfM92ndZb2ar1s0gjuHoCfkl7V+sUzTFo5lpi+YRNf30jTbtB+QuhOsVff3NhYXCz3wf2pt/JReh7uoDUX/K+vhB8cVz2RnwuFbJIh9DlIAN53RCQs1/AECIQttP/B24WRZ6gKt6cuW098UmTcMXK05pFv5SygY7SB8XKtim0oPYiDluihM+tmsm+zRnd7GFFnuxqTQNW1Ucu1U7Reptw96dA+15/KKpNx2fRdSfGFDl8ZGucdDaGisRecVnLKeyxKlHBwDfXKkUbdKFMnbVztnU3Zc0afvJMVNK3byKexRhC7pZzr3miRT13NoqnTZ4mPrHe5pzKu6jMhwQ+melkwi9da+xX5D9i1/qPZw7i3kEVtD8d/qH95pzLrdsM5wXreFq9O90h0h+15/6AV9clUdzTL2M4nWczh5H/LmtjNIJ9J3Fd0niqwNReeZh6bsDNAepv1rnAPA/39SRFhQ+OMR66BDDndHGpdH4Zjj3YmmNedv515He6F9Y3uxfZ1LLrviqmLXupIDki7m0M/5EQMpeGEnANa6Xbw+NXg5a8/nt3lpN0ubqy9GANMAT+DX36xPtoWAngzA069joT3cCb70MX+24NSvbwIu5WiaH2uMLBKi5FyYUCZzRdV0w2qzO5yubu4enl7eeOud9z746JPPvvgq4tvp0b5AAJ4xLBGrSTh2zEeZ3xuvbLFzESwuSyr8Zf+T79fkvcyvV9/+0Ld9Z24i7TSRfrbIvkbknNfmHlNVtu9UiLSzDks/BVjv5Jy47FpRXbbvE0qk/Uykf41zvo4td8ct21FHtMA4lzfrNMU6XZHOdinmOOldHnwXDJ4Og6fAXGhP1xtrsu92I2cPgIos80xcNJ05cKbin14nV4tx10l3QG0Ym1dkOWg3n+EwWFMJ+r+dBSmUCIjMMk+chOk0kXU+Z5zuNQlcP4mj95icvzYLiodG2JImx73WJT2cJ5H9DZH59V2mr/1CkBpYD/1kyS00zKfp2qP9ql+ON+bYlDU7hpfHyXLlZWmWF9IcGc3b0uSwUZOeUjrTyQMml+9wzPNf1x40XF60J9eYOOPEruP47Y3T9+s6ETb1LiZharPTmfaAae7aIPdteJ4L/XiY7jsHcPrex/tY1Wzns7aJJaZpbUPYwn+wl96WP7x9Nr4urcz2HZMYZ+xf9MHeCz0aVbdAW/8QSesdl68PZy1vHOfQ+XzLsz0a9B/80lH9GcD+33+OiT9Fv626s+OfTcPRFw3cHhyyvf3T82djiBcKrP4vaOt6C0BAGQOw++Ue/sSuG9tsf/gRAPn33FcdeMcPb/7a//eB4WUArALlN7i9DVehUUB+X0yzQx1yrlUjJF8QswDqzP66LeBaUgGMYH0T3ME37PLCm5eBhoAptzoNpwQLqGVYUXIjUloIkFll6Q+V2VG3UYYJubRDILOCOxjZgPjQKEjbmNsXaKAOgsASiIkAFYMAI+8yJFPeluiqkrBgW80lOlqRUFeyL9mfGEDd1grFAnGLGJOaOfQD7QUTiC7ekFowWAFf3VF4KBvhGnqtTVqBznymbRF8ew/tlEOJBX5AjKMDGba/zRALojEGHkRIeyGVNrJaXAPia9EsdIdP+AR5bncELEYGUs3DAOBtVE8HMj9s64Qte+obCdTieq2YgmxNUCG+BjNvBUwrSsEiZIBMZMLgms8KyK5SbkyN2ShSYrxZJhjP7laIOfZhIzsLFteAGCOaxWOGNScKPsNuVwcemai5NKZSYBRpMV4sE2NEs3iMW0PwjzF4xCVUlRgvlokxYEa8xfWMW3wlnmwFQ4PY4kL1tbJr2zJ8kjev10OrtW2bmovE8lUEYq3e4rl240tVmfmAj54YkYA3xubLwfKRV5xbZR66Qi1HGh5ByGkJr1bgqspQdawA5I+tUFh81fYOrnQijctFIZraEoodtocdVEqDhRyJNrJvpwm7qcCR85hKVdOhUQ0KVasAhQpnYbTxEDzS9+KcB+FIszjL4Eikc+USpoSJprLX7S1N3TsLFteAeIwC8Vz1jPFwA7CMUjrMEfeUWi0B2ISe/mKroxSNLRw2XitxqlgkxotlYqbY5izMQHlwoF2JzATq8yqchfdJIztjVpa5JgRKSVXOAiKmgC06yWogr1ThjFlcZvHYS8pKtAWZUEWc7pSM0kJ09U6oI7j6eGnGupMjKsZB/k0dDnMDnDL9/UQYwkID3MxAzR0EYc2Pe38lzhreoKJiCME4irEFNObAMo+s4MBWTMtZEyQeYeg2gCF1xR20DlxDf3t3pZ6t4gH4Xh4MLxNTVhyg9SCl9OyiuV5zGMhqpRI6KgLARjtoKZa2Vgd8ljlA+9eKgpcmlbkPv5B5noLvd+Wc2Q2IrfcYj6v352Mu19sWLQ+sG5fRjgMlLJraE6OQRi2vDBuXCQCuGA6/1+RfvfZ47S/PEm7r0Wc80az+9GtomHsuxS/TUb+pWFmo9vnUZ+/ll1utROzzFVYP450Aavak+eYl7zvBVRJgTVFDvCvoMuY+GZU9XAyk06qA/KgKWwsUsMG/K98EkvzSFgNkUDaoNUCWe6x8+HaXFSJ9OL/YfdLePlivdwuDouXu4Hqm8XBX6Sn49WFClAur1ZsWTunishotcHsxXFje1bRwSvFEGmxh7xPM0pbGNfRZKzepjq9aLWiqg2oW+5sc2in1H5Ep0wPCHNopJcg4kB8qILsaq/jRxIBqwqioq1NKpO65eyxsNwvCpgpKwfiY0lG4b8/dY2qe2gJXJ4ZNAJ6h8+WWDpVrs31ZU8a4hp9Llm9okrcvCJ1Icygjd4LCkHRVNHo9NWGp8KOxDgImsDfcKQp+VIsbqsZgLGMrcmAZJFnXzaZVSIo7KS/1UWlYlfPj4oaJXmfUYQBtITqSco+UctXHmhazVAvJ5eVm6T7YEau2RbTKn7NADIa7D93dJLcOCRI+hdgs8bkV8aDhuDzjV1eLuOVR7q9w82soj0jz47c3+yJgN2bP9zGxaugZ7pdnHkiDuPLbDxeopZRy1N9+y7arfolQbXg+0BgL4vGiRmDz2U5wj3fZgimYz1sqk/h1n6hZPMD/UojMX0CBxE0NNfuVoGN8AwhUhwF2vj8lwTreUgpwPICtRlANQKYgVt8qFjQqVJK8aVYboy68rFSJQ2W8YwNlYxBw+XgHNrW8csPmGmIEyZVn5iDpYpbtn7gAMEeLQUELklOqojeLlra7cSDROZGhDGIUktUR7f4I92ew+JtvEsHAt0WUIZX2srAZf6YGaeEgFh6293GwUJBLwEtBEfl1ojK/dmAiYZYvJD4UKsIEpqlpIHXg1k+FXDQzFtxuU/tUj2sHOiwOgWB/k+0p1M4sNUG2zCkKxUbG9mabMKNPbWKf2DQjFSqWDsFSRXnrTKbjRt3mrTCfxrSb3lEd1tjx3AazOAim4VUI/Cf//wBl0cfhS8AzSiZfcrwxha/VE+T/ntofM66zH+twxhVX1HiaCZzxzdp5N2YWkR1yAfrJp1+W2Yw+0hRO4S1NfAKAUigMtv/m/xN0lA7db9c6ZZbRDs5x67xkk9y0IxISOM4gmJqGQnbKcoJFUZsPenxExRQCCBiQoL+PzJXCumVLhEIEAh70rkQZsC2GU+RYSZ8gO95CN2KjbHhTNsliuMgavnCuOFCUArIWBeNlrECXBE93smRACUkp6cKdJTOS05L0pv5C+ElY1VJNZOMZZqT2nNGvXn2fe7DD9TVPP90NQ9FhJFMYrJ5jtzBeXQhDhP2x+LxhdVs234iC1hTuxswmPIUXnc0APRT/vMV90Dw9U7foHJ7NjfeZQ4dgB1ky6ZUFqijJjBmXhDnIuCzDT5esYbWQ6OJu1jRWShEWjGKbT/5a+ZVasXJipJn5OrPGRM3Nj0dI45gepx+pCquzx0bSzKHCZAT8wwmBPDjnlWMEGuC42yFTXV3L04Vlg5Up2R4o+NBLC5GQwKrlYkVhO27cnMB96BZJzsm3eybrZaHKPulfABXCmMKGOWOMNgWBdJdeiL7X+RGBiJ7IhvoMlaUOykwmVsjaokhqR0VKnbyW2fAMHfzzraRtMz+ucGIqtHrKlRlq2tFO7hvDWMYtvZI6VMbkH91UYpCzv9PyKv0uWwB/FKZqKa3zLIzWpwJFnaAEVSVFAg3M6gWKXb0R8VLOtrS2k4goNV5KKIVp4mhwpwJYSw6Yln5Uz2vFVO6a1yy5kkXt1xJiNLL+73+uP4E3gXpXJCMw+vvzjiT0lot9/VxsndkdzdQ/qXfq//TAKjMYZzMU26kEOkwfaIutttGs8nbQpB9P1tHEaPgzE5MG28v2Bs/utnE3c2g63zTD2uYlTBnUqKKa6iAr+XsRc/Eoc8DxzryeZIhtydQUdToAWMSjM0rh1pOsuB/zus3KJRJZxkYzhg47NbWJaNlcXtA0PdEwHlTk4NSec8tWDKjcKZpkbWe4sW5snjUbQQcosV7zqsyKnFUlwkWgKLXb6oIiFs18oK4+q7cD13551xQQauwvpdru1Y3kAk8+yKlhwBC4BbvCbjZRfFI0D6tMnsjtV3SPe3W9tP+eh70ZMQTzfz3rd7M/zPC7ws93foG/8T+WzKpXz0NGsyFN/d7fd/7YNV0chWMU+XjYfS66aMpaZ15hVL9U964nM7VXaNWSznskIFSDDSwUYHWSQBH81rm192H+tIEMNEIbIiGx4Z53uERrwj1P9Imu87mdM4Omartt8Odb3JZTWKlbRcBrMt3sjQ487XSXBIXvmFaaryh3t6LxmF0BtkM/pwqvBl323g6QjjQeK0tTdKLLrYtuO8QAFUd00nkEaVamcAZ0e/O89YufNJoj4JkeoRtV+27V96osk9F5hzyngJbFxPYA6CleC6yW5+dAqjnznXwOwAmHjMYF+ceYVbtn8KVXHYWe67fhYhTlzt510XpJ5pM/lsB8FttRGgy5dD0Nhw+zjo9AuyhuGFCgY/5PtrQklaN4WkWN87He3dV9sT0QWWHHS/JMWcpnl1digempjRXin03w4uz84jBVGKLr34njSSOJTKdks8DaBQj/PJCpi1k2uIj21nTFVdkepAdW+JHiHhoJ/akIj50Ladl33F3zE3A5h3unlAt8G1l5nx+mgr85wZ9D1DuFWXzgrviYf5x+F9Zg0J/uR7grsqp0j5YkXta4zyAjQ0wwBbwAcJtkFtrjzJ6J/5jNDvi04K3PLM44c1rvaERT5d6gdw86bJf/A5uooTnm+Qta0So3ZuByChKmC+I6KMn6xGyw/hB/e5Kb27FUcwoxVh3L4EbU0MkLJ+yYoMqTsochYnrl9+aAP5XFXxj/PWXeNh2jLZ1b+xIGjNkedAlilWLeG2K8JJciLp/9f0g3LShGFsa7i/FT1fABdY7ZLMmnY1bqBSm05Uzf9G1z+w3TGtwHMyT3kaVTSYH+MMXg2TbVXepMbwdToBYRqI4YQmttVgdoqu2ZS4+xhjsVV3kBGX2whzCEAMn4LpS+zqSM+EcL0W46VZMDRwlfXSq5CDhKdJLoB3o+PXY5gd7V8wfrK1gmDjyUYxFhtqDstqjV9GC33fRbNxKL6HF4hxCFbJKywHP4Ph/WpFmJIyQ6xxUL5OPXMiXgnvXHli4q6xThayoglIYpJ+/RIyQyRUamRLzYCBeBsLw9MWgcfIEWOaEZjjlYAhexmTcjWWzVUqp75Ev/iy0OS7SrJHlDEa8Nq3RAsWHnwh4bcB5iK3HrJpaYPnOR++MZXezT1QENoh9P82Ex3XHtycSOCUGGkKWPFALSGShrUxjbsSgkgZh5FoR7ZwQGwdeUTQfay2DuD0iE+CWY8LpQkLVjDTErMjlNHCkz8ZlSn2cBkqwT6qq1Ccn/AqYFA3Cw+GWRYwPqpSFv3hW+bEMOvtJ8exIv/nT44KBEQU0GdOczBwmPZ8Dh9xn6bRyG5wZFvOX+ZEHyuJ7/ooZUUs2KAQbjwjg7mfEVZrs623KXHBR8HHDZIOR2qm5REQYedOLBvFFPG40wurnFZiStzrZot7ybLLR2ajWG87yltYHNhrKQFy88Lwictg470YyJQJyJPZVAC9Q0N+jCzJmyvCXid6SMs1MnMo40cxHxwvrd3EC4qZAsEGRgkA0Wj+RYCtQ5i772NoXGZncRAIr05DnjquAtaAhAkMAuyM2AALmXWhBVAAmfA6+h0oZB/96/vZe+qx/rJABZv/4wqDnTUqeW+609b4HAqPcYOmNOG8EAD2NDmFyuuJHmexWaEyyv9XKhWxTkpRHklpp3rAbjWxf61cA9Hf4rGHFBo53WDmfgIWTFPRAOdYTUDtVb3+pvpU/8BK51XAusMmHQ6OzJ70SjMNqonIY7pb9OMoJqzBKE7afpkhWOZRe3mdQpQzHu7isMKyb1KOp1GiP8ZAMpUPOAWsTl+gs0G7d0hud5n06nfU6xXjiNGE+y72a8hriZd61B8d91ojH++pz/TerxVsWjsUZy/WAx3BqDpUnZp2IrMCErptUcJekYCyrfGMFkI1EZJKhXUMgrJA0kc8OT5WjPSEGma936OnZL/KVsg/WBq3JoC878yEXnBw2gb4o3JbvdxuToNdUxExc2XrbHPDvnNE9NStery0Vi6GNFg6FKr9O9VgAReXGfTYHtMsgVHM8mLboXPlnpM0zSwLJniCepzEiFQN+vqBWUPT5T9IzuMPmbWfNoffW9M+YIAI2s9BnGi4nBtzCKWuBUGDM3jPgBRonbQDWOr36DTNfT13TWfhY8X06oWlRJeY3JQs89/Jy88AAvXiAF8AIv8hmPM7fJyqofR1MbTAt9K6F2XM9EsY/w9C/u04VrPjl+o6Ts6+e+EQeotmLXIF6ZTC4GSWAWsQcXuoT9F7G0ICvJxFxkboB+BQWm8zibX0xxBMYXureyuu6uuMelNrfT5/LIkJgpNyz4FzZj8jnPVDRZFPr8JxEQrsG/eoYin/3ViCP6/B84UNiBc3A36FAf2rVV+v5L59umpCOhrbW51WoBUo14YEYl8nJ/211aggrjiYNxWzT8adI+2dg2LNEOQOQSEMh2zLGbEUxc0214/sp+GjWujLgMPXkxCCEXvDfyK2wtehkBHctR5aRawtJIZTgVNgSCBfXH3SkgNEXx4KTCDs1cakj9ISVUHN838DiEy1mSCmWfSuOery4aUUFte6tFFOlDbrkIhqBlbSTRFpL6hMSSwx8em99kiTfH5A0iRlQS6SZIRx23s0kw17AMR1xYSCDNKCnR4d+Uofm2DCrsdsN9rWiTqKIAgWJmzrqE1sM5euYAHaOQlXy1RIdNGjzntDLYjfmBnKM6td5IhdcVuaC3bDtEW8F4tcwgqpvSWmtBDOjoMuJC7g5FxFi0Q4b4tDyRkPWFaMP4q9soS/Ep1yXsSUO9dzksaQAh1q9kuyqWCqUmRCmBESIuJFmlXUEAoeVGjWlipIWaRwb9GbXmFULRWHDV6CNNElXDJrolUSKX8R2dyaFGfegwNURgnOe+mz0P3Lxcax4MCkujR1HFFcGuDFjFmYaCvSI71z+KjbExFWsiATIptVBjqcPEm7BPZ+yzSntdVUbrdxRN0VKiUrUwgWakiMwszzx0jAvYXBHjXGWxQmT0wkm00rYWAoxy17Ht6wbTXdcdaV/3527fYdL4sHfrgOlxi+MXGXx7tAZrWoxQc75XGqmD0qy8R7XRzEvWDm431ytqeoqnzRpMs3C2/ylv9s4xNsF5hpfDOc4b1h9D9jFExb29vsqQ+snEUIVJsrJxvlgh7bA62uoLesvn+iYapJTDFImAJXuBqgGB+QdBs/3TlxBK+ieUvjIzkYd9nOn0bItSwra+70wrenYzKJv6ffrRBr4/oQTqBXUabnkw05+qrtlO7e09gy0k424iZO8YbKPCu7Qdbw1p/7R7Rfu8AtTATl3qoUAs6mjRVIwsM0qjAnsczlkA4LFiYd57mHWDKH5srLS79YijqpI7zBUJFksM+F05ml9Bbi4lVD+x5iroajW6FrcroHz14+ryYdzz6A8BCqHHGtrLXvwOEgjqrqiFkMHYcytkf3UBsuep+wBo4zYvmlsdCmttardTGv9V3Qa1uw14Al3CHgJdg/E9hpy1QzspRvy545EkSoPbSm2l7KVvyjOU9ExoqJbHbRtiAN0zEfEgsYnKCScuVytfwFytV6rTvWzACuNNHXf6Ot4Ob1TS+TOfK3lEXydZPrOp9Rg2FRlKkdYoulouOrx4PhmfcxSmyLFz5+bJlZkeusft/ap1rh5eah02JxrS7nE7BcqtMVXeLrKrz3bseO+cIyYCrBcDA5SiT2juf4VtPDjuztB6/5YiXeUMxtkHYwwkHOnpQgIQy1aeEvuBzmJisFv2BWI5boPLsHoDsozWkoU8ZnBOt4nAzS8yLmV5QgxRcwRAdgVM78p/J+nfPYXOJSUJuPTPC9I0eUqqc93YfwfvgBQmUT4uJuO8iSyIFvj/c4ybhpRtzEa/7IgjCCCkZcHqOWkj1oK6ZdkjuOjMW9x27zemz/izqpkWlfRJ/Am7ugYm++x8Rx5PvThTz19nuK1k/evefYbgOFsi5hC+W7xTtNje6Wq6mr4imAa6wIUOM5k6nCwGJzKkpBl9brblrn54QP3iHYSB7wMHX9zgr341iyNYwEUFH1PI3n05vJdIGsnpkosmAIlUwG5nVOaEdyHn1Otac1tyo5I+39TyjIYqNySMmNcTzxDUOiGkiAsWH06FZD4bBSf0WWadKCvAFXYoVtk5GqIePmkdurqC8K8+JQsczOOiOt1mYFHK6nxuw6/PO8E4gckxDuA4x9WULxdPFBap1nMhJ+CinjmMfFRoHBnNMFDT/Kz40DoJGkXhN0MdNBSCZPRsp+IQqxzVIPwFDD2WkCci+PsUbwRApnE9B1Pp0iKRVFIYTnhdyLo94SzSnJb7uQwnacjmuRg4+cQDxjxQTXPpnElHEtbw0QAPQDzOjqRvxLKpWlTFHWvNes5FxXXCctVgES95uZb8W7WLcmU04McqHrHp8zFAjELdSs8I8hI6X8y/J2MEPpuR4xvOHRwuk6lOiSmnCcBU+v2a1JTUUHV+cwEyH1ZJqwE7K3j18Gq9zKq+pmx/Vo2zduyYlwREKAhg1wtaNTepobodX0j4AEx8yDunR7kEj65+nGEvj+4KHaEwDNlKIiEL0f4+neEshdtemFzYOsMFnvwe+/F+/uZ0tNP8WxovxgecTVKavWDuPePiBpUvIM4b+aSyl+6OG8umlZHT2ymfbsIbLHF/58thuQfHkfYVS+JRcnRviRs8W1uLyWXXFDUnMxal6160XWQ2zk1KaZbCfRE7so5s0SE8JK/BvsBT906CFv0ZYIFIcj6hiUxsmcyQLHzhtcG14DKv8FHtxBBSLL+eXWNpGS7xnD/EFT8gZAstI4hxD044ol4y9KHn/pjJ8F7h7ZT03vrPFIYhZnyNBhtZGp1ak8R0beaLTJbZD09hXNq3ARDqWxQaZDhYDlG57BNU8KIi/tDCHi7W0vOWVY1o99syhOSbIIgoAUEAxIKyQOBAPl8fv1f3ED2QMHgUMKgnye0vbXItKMZgDsCnVD6LgQXK4j8/oNavFkVYf+l1CjY/Sc+GjKwLC7QDCnS++RAnach/npIBF+MtRlhzUV66Z22LAlsn58tCsgv6gwyqBQ0K0kMiupmFxMCeM162xxO5ScCwtA4MuuuyJIZtz/ruVHqghRo0du8bDOjpRVt8XIP8aLl2SyCa+BXB1hqs6jRX9wpCACxBUWfcjMnq97wBSMgSQn1Tj9YpRxVpZCF9cGxjopSYfsxdRMYcIY+LvcXQxLWFN9CbBHXK0GkHFdqpP5vZr2atq2KnU0xkF3zI/8V/ZBCsxodI7THhG2FhW9QYIetY+U26KhyQJMy0a55gAoCKlAULmLkBb5HYCwaAOab2nMZjVDrxK+PysCBI2oWbydYUS6hqpz8KTLI6BON/dfYf0+mi6ey3GIhHwTR2GMmTZuhUe1VW4BiAljNakOMKxvLTuRJDcurt4guz8M4UhgdZgrDwpJWG2HbhMXbO6WBQLokbDNUJzf4qs89VgThIfUzl9MNi1qHVVIYS7o2PHCRpR2LadEDgRdASu8DYyyfXdj8/Er8G4vF+ovnfs3bV5K0V30kFFSA8E3K4HocFTnQEKJa494ueUCL43nRFuJuucx+NhvHoWAfMrQYvRIYCAq3SjYPKei2mMOckRhIylGw2EFT/ia5i7RgrkDyqiiXf2MNCWWe23FaO5bOBFuknH2Eth5mq7Ge/vFola4Nyxy0bBOpZpdR1d3acra8s1MvtQFcuicc4W6ozrXi1ugdePaqXSD+Sqvvuqb5CnSDz45oqQY3mvs+ew7AHSb2lOzKtHuisBEAwh1xoyEMf9UBHd8mq1XvX9l1rwe0HuSswE+lFhgG4/LCBSmBMY+vqTKN+776vjO477v2Fh5b6pLVugxY3ZQkUtF25kRuhx3TJS4gD9nMHwler+9bv2lq1pqtor84QCwxpAcaV042EKVlUZbx3M3PJpRv7we3a2ELbflvP6aqHGto2HVQsV0BPxk/Y9OJZX7CDsVW2YMu7sjdzK6JSPHH8Gs4+0Q9EhE6nT+n1x7+u4hqVmTqr7EyKbLp9S7fsNYuIRJBtv2ABvd3xdneigt583JuGC+5yQtP7N+UQmUyNe15UdWMW0NMDn8JgHaTYeGEvaD8mreChOLB/Gg+wtsWf8p65ircccL9smS5ybTfe49Sk/juszf800J6H2KjULXdgfoerCRahBh7JW0jaqzklLgUBd4kBuit6oUuPKZRDMuXYKdQhvBetwxDT4OooLAjUf+yoC4HFYKN8/T5RQD+jPrNHtcaE5yELO0I78JZBuaokwPbqJoQJn2sv4gJ4f0sEpG5jT4MnHZBsmOi8zIFTb1TJs9VdmN2hYNRZ/Y1ZYDmR/wD/nv49RX+2d3GtO76/+H2fWqlG89r+caYPAwHjZHtZefd/XGxGe47KRZkvZgxblBgl4y3G8niieAO1oIjOuMCsBhyLrNE0YBPwfJepRuyT087XkQk4hOA2nlwo6iNFytYRSqh/wGmG/S9Cll4ZBvTRwf1t8i7dbd01+xt37RWBEHfFxhYll5x1D9TNJPVKfD3LchhUeq7WHyc5AOmXZ1Zou+YMcqFzpEC43bRwRdY6F+uc5SCB8TQz42R+bDj3CLagdwYwA0wDPnSXUyVJXDdVsF7yaPVTk835nBy8ONTD63/O7Ha1u0YI1EpsGsCM3JBMZEdp7xxVK5ukZXJIwhwSIAB/ADRSydOLfQdleIarMnWfeZHyBsqBr3On2MERcAITAbhKyd2xBGqLzdVS+cMTrLkspcv/MWHOXWyH1DCqSxytuV/8gyKGPAGDz8LmL2FQpbNDfz+Pp5G9i0O1z4OTOAUX8jSYnFtdOWCschqNr9U8L8mIWHbHDPa1OWs81ItxBiNwgPdBy8hPnTvNFI7wUcJQz2waXWLOBOIKYv+aSEjY8QrEd2POaKWTw7Goa2mQDnVcCYyhnaAJKUMcREcY0GzZCvHeN0UhRit3IAA8aDQsSwGek4tvN6T2LUPA79J9GqZgZezEiDj5CxGptp1ZmimGIEuP7OZzqX878XdGzU0HQgtE5amxkG0prPUHViQelKWdVYDjVHsYUCgxjprWhnV6rcdgYdRsj4EobkXaFK0/ymJZZU6duQhhgCnpLRZi48mF1JbKKHOvGFXriYdzhXCbTIj2SBONm7GbFgqIsIgD36ftuvVRSYNjfwZfevU0mWgqjjS6WwzZUQ02R3VUS6Smpw9GYYo3vOunxn27foAvOCMewcKoA5Wk0uN6rgABTQQOy8NFdYhzUJvAjoUD32SL52BClA4HrIwzkAMWM3JtBiVEtjRMYfMAy6OphZFR2kLIJ3miR1M1ykDHl3jWWWrUZF5Su9Rh+YkedFnoOrzGju/x7ku3mKzd2wWs27iH2rBFxmvIjtxwjiDXqLjIrA+ZjxXeZhJby0QMv5/TxCkJE1h9Z/U/nLGQHHGgG+ZIIdeiXYTZ/bvT0LmYuDOJwQFP7TZfqwV1ZQpH7INiRhZbNhTaxvwt0WDeZ8KzlhxtS1Zo4fJ+nfGejfqQtMJDqA8q96eWCs/Wp39lqsH+fm1qa34YQZLDeLkPF2rY8BI9NiH4isYLgqqzLjhmbCHxg3reqa3/scEyo3L109rGY26x6Hs5XG20gIKd8xTi2Ja2eDQHrQ+e30odqX4Ct9WFO57hcD+ftrVbM3dJH21A+kL/i1XplRNPoLP6bYdn1e8+MgrCAO/xsSyaCR65N3yMIZtF0y/hcJzlmQSBa2L99p6QPvAzwAI70gRjDARulaxF+DZpIGHCIGP20fiAzxw/TBrYaDvYGc7SzBZKtEA7r8KgDbEgmLO/O5gQoaWoZi4iEd1mMalhxxhEEMEUgNSZXBkWjRFRk2n5FnIHQMWgYQg6CHR1f0YUvEBBi5SGhTYAaJYHtiMPBUgWVyE0c0yfqwh6S9kUUzAbxNMqlQLuY0Bgl2DEdrA5ATMfgzyCIWhKyxzRurGBbVIbNCDAI5PGCJbv3QkosxcYCuGYtiMXGQEv5A5ohOp4Co11SipJ7+Pgl6DPN7gt3I88XMErHsW0xd6A7xAQIAh1zWlO6MsbX97/wuG2gZmFabafyHAIoIPXDEAceW/VDd1bfE3zyn0JXnQN2skos826uIWxvqyxFtNzHlT/asGYRAtROhDAY8BhFnQ8BZFsgIgRh6K0etaMzI+Rx4CKCd/5E0RuiQnok5MEMkmp8EklzBaQg7ZVlRe4zwkt+mAR93omdphowBiHEziZjpk1WQMNJJAKzgImmHACd8AF35zaFDKKaq7XcNIQKq8levbMuvJunK5FMHOgMgsS8thF8nP2BbbnggeT3xEeBDN4bjlG/E7Ogi5D6X734nv7ddDt4iEcOPD9GYuhe6ln8M7jVJiYnWHM3swZ2Vgj1iyYi43GxhJ4xtYJOHXx5l2v2PhSTAZy7ArcAxCq5aMwIdngX/FvI64veDh/25bwHGfxFpdRJmoulL+BuO42BUI7ktGDffg5f5RK9IHE8K8yozzOUSWONbYWOb4KY/O7ohff5FKq44zhVXjGdfW2RTDEIs1jNZxm+OSUGDEFFFTHK5IEuG1Yz6kTNxxi2TL0UyAwomn1kFN2ez1wbRAdaEwGDf4gTwdQtPCVGVk7mQ9xEj+WS4Q3eHIxE7yhyHCcjO2cOzkIOn7zXYhusbJ2saMeEElSf8HNRsnG9l51b+h2v9SgOw60P7hHjn6IRKTWLjKaSQCsC4GS2OFqGglFijnWLgiwHGMLsSHLxp/zHCMlBn0u0qBRiqindUQBKNDSkQUgFx49+DDf+GkRWOSGh879z7nAOboccsE6pePfHluxvXlYooDOTHlxm9VJQ7u5Zs/hvbedQ1V3ddel7+D78EPxeuMeEl+8tpvDr/mQOK+VxUjuNdGLtXNR87N6Q80bKcAPkYyz4dA11n7Ze25stEs85HcZ0B86dBLC82fUJHB5f9jGRqsgMfiEoTWYM+gjtGhzEPuJ1trXN43BorqsjRJSs7vITBlew+zfED4KBo4ItB0srF2ISkREQdVVo4ICTKzwgZnPwuzVCy/FXWKBMZIwCe7vzBsl83spYOa907EU4iV/sxJ0mrUvtkBVCQRUm35Ch6P6ZWP7g+pO0j6YoSvUVxNwoglBGVk8Z+qUr5HuZWgpaVb0dJMtLWkKj663PuDNiTPFDfaa2JTpMLYhFRb3XAipUAFCQuDdIwsjfqYGr3XI6yFET454b05IHi7TfhrV7jPWqyaf00apedemeqrG2rhahrX1p3O6zD9zSZs0x5EOiG80N0aj5E5RT65Gp4aDqGWI1qClR+XyrmaUx/Jd9b7g5XZFmW64KTupPu7otmPfHdHvvvfvR/88/t50mAiFP4jPmyeffsU5/URlhcMNpMbGbJr/vTdgDyBTsl8hQ18/Wpkk3uhqDR5shlXn/vtS1Y5a8xZhvYOTOqSsTtTY9EcJ5Lt9lNqqzI9iGhoI9iREk9ywr3vYcXmlwUxvhVVEoBQb4nUg1lZiBCHsMaCOyOod+R+WDk8E3dC2ZbIaLGdm6LAG71UZKoD6n0caO47oakg7tWMBMPvLHpbEFTmcS/stz5kcwWu2LujafeXce4INX5JFlRVw4m0nUomPQ6rMgvhcnDYb5+vHBIPT+R49oFmLcqJUj9Lv6TLQRGOKGZJWge0WXV57e2x/tMnVbgOuX/X4xTHNT3mxPEEX10+Xq6O41+TnpIF1n2ac9jMUT7AEZG3rQrqbGM9WTTXHzEF6mEFKg8ffyBJakTO6vK6j0kuua+45WCCi7z0CAqeyXzlba4rWGzSiwaIMhvpgscEtLOjCcaPwpELFKN1erYVskr0ke/URgCysvTRR0J+Q7DQnTrDmwmcPJZCtUL/6dFNth1KqHg/wl45OSM5YpNqno6+XX4+pOQ6w2Xtz8lzgGbzCK3qF6+YrS8YN3lggMWf61Uvw9XsAFJyjE7vk2vn6216JgLT/mXwfX52FRxlafxnQ6kFk2Qi40Bahw6Aeti5HKfSy1buq5YWgybXa2yslnfZ99oKQ63XL0y+2az0y+70k6yklo17G6JEet9vrKiI90Ha6Zj0o/EuZ9CZOqW/qx6rg+y6ZKSP5XmmRowaDIv0StqpDuZH3cWxUoLVbxYoSp1d7YpBc/OZWIRIuasaLruXwk+C4ymyoyOQVkx8GUxdJHr2Cw8TwQFNGyvqzLk2Rz8lJwb5Z4h0sk6cHdBXvhFIkDl6XZLXs6fIeW3U0KqKixq2mw5kaY7TE67tiicX8dKnR3rBmS50sWakr94XkYPHejPxqp+Elqws1XXTck2isv4pMVtyJwV38gsDRfd3HCt+vbJiv4GSy4hTSyLTRrEs/ZRk9LPikKTetfhkuqUs3cCwoL3dmg2VpzMQWfNxWZmKwNWu54dZwp00P38yoePCKN8oFBKM3ProBm5PgZhmwSE0EacGeICoSDV0cYQsEeBaEodZ2ZxoFmku7eTDkkzEoneiUE4yYjM1xORmZcRLCwu3UREDservJj5qHcoeLB+IvpFqc4N7MqBU/Iw5u3UPKjOiB8IR7DAwX41TnewE28Hrr/VGNnjVHIvRw7rD9WmQ0j236DqiIUXaTqtkZwzY3f1JVTn2dPkqHpaOlNZ9q4SjLVcCp9eqd7cKdGHG5Aw1Kpksw7G7mIzByFZ6fYwUso3jRbrGLHJf0OB8dfuPJpc/TyRZGvafj44UZwbF2XUETbs0SF1m5wQgXOEeGjnjbQxc46Q53qsd3gyXDVhqBFWQqF7tWm5vrQPjNt4u5CC39JQEqwOXoyri9MuFmIKYpIIjCc7Va/E2ERRjWPx3MmJUVfeOwfORwPiZXj47ltYgPS9czKuDadLGBet+T9eeSnqPnmqvOxIvnrDKDRdZj0vRlgT5aMNf5WOy/s36qY7NR8rGYyzDhGS/xjLcf9O2IaHSgg5zMLuK+ONAiPAnOzve9cnotdeVaxsa56FssKNpfrInq7pvWdNWEUS9d30sP1OVxB400ykqv7G9D54lDT25LbpW72RK9zjKKYRxZERZmII3dOsakJPNhUFwu4Xn7SrO9uRKqtJGgy9U/D4sJHjbWzA1l0twe80Lz9EzHwVGXm2nbZwvg8qnQAmXUYBR3JNgOro8Oto6v+x8Wh+l19FpzFkeffKKfQHpbcPMDO33CqxL7pgp//qmvNuL5+pHOV8CeXxecfmYgAbY6rn6myC/ggcr3fgbcAekKH/MRFjXfBg4/t/eh+X/+uIJzH/DsM7BZuPmNP6vFahOcNovRts44dfj/pmlQ7Qj48KMdFWEVhLjVUfXnSJOGOsXFzIEArElwYRms0CkoWADBrwihZ99UNYuB+8uwVZTN70gIiwm0BXCODuVi3emoehITIjuoyCAZO0BakDc9pK9dyqDdjY2uaG05jDKrnuSouok+iJVPNdu0+kYtGIra1Wsp1B0jsbqdwwSKFfr1gaNwJLTS1ccK3cFKCW43d5E/3Oer6PCwVjnbaQn1Ru5EbI3qa7jWZAUWbI4quqxx+24Xyco81KzurZ6ALBqJ58u85POk1cpOmI8SuaYUhViJut+KHTRMD+TgasQFiQkcosIYRFYwA4vRjyiW2Djx8MxsNU6SNUIx7QaYud5SHLfwwkCeBfBArLKaIcaTMSbzVQDC5ajBxJe128XM33HPS98uWRU8CYThUuKGL1O8wCgUgy4E1ecwaMal8SM/CJp+vd6/VrXd+5+KfxvYCLR/sf6xzwMSB+ecrmoxuYaDG1IV23KbkBzHugkaoEljx91RoZSL4VChrpw+DKPxsdjQXdxpdaqYqDrnfDag1AdtEFUTFlkYpYwVEzViQx6c0B7U57MUEONSAwLqbJ35wxVLoA6arD5q2ijDkQkr2zBRFAJMi08BcZ51m+UmivCF9VPnrv3lhEM/y4wjVf2q/Dzuk92/1vs8nvY6pt7v3rTGkznofWSRc/y++Ue5rmOpYZBDIQ3MEwNFAr7qCuqM7yDvEqaR2Ck8lA9FuEvVIGcHrOUk0L4NReM1Qds8ZBPkqZbsscIR5lujKCCnfdzEtYizWwUs1cF+KeTQqb53bKVlKJ1cDyoNQCInqFJjScO2mWzNNAdnQFNoz2ynuD7ZzLz89U9W05WqXaMtqq3YfXUsNgl7OCLrn9TQJJVcsZ4rBbutIXiMhWbDaD+WuO4u7rw5gEDoq9LtGaa1rs6wMFwq016YVu7YVqGyP61tNq3yew3xomNUhppdlqsHrTBb2Mn8oHvRURKdGdlaEBtPwlNW2uyb3Tdc7eKVUb0Sp6gf4Gt56I+bfR4vczOR/j5436HXO8sz3B8JLTujSCCJRCGwYFnI7Eb5PoA6tnsmmVRP3CtlQtq7LcRpR6Foc02Zh163HI0IUl/NtDHZcBMXfFthir/iXLXgVw8wClM7X0FpZhxlAsJ4owYBlp4VkrgrFWTfDJlBG0/MnrlZcW1ys/vxx7zAXEWsMEXPAsoFEMf5Qg85O9hyoelWaqYqAa8DBJ7aIqMKNxbh7szFnUg3znDBlap4eOBqVjO0yxIOx3RgIgNq1wxXe6WAnbRsqXhiTJFP7JeeurGiwyipOt8Ay4oaMN24K/R6m40SyxzDvL1WvDXeasZcRZ8HsmsVe3U6DEfxC3bTtjciLdPnh2wu582ONe/3rXJz9/IsWV1qzSrYe6h0K17l+QUT3nOZjHOSveKJcbWxO7ODU9sa72sPJqT73lmVxL6kWHTzmYCH/JfNlgpE9iSpbI/jpnBRCP7xEjreBaSOHW2YEPW5uZPP8HeQOgHPaPovV9muVY3iV2XIDLrx7q3O96SZb+79P2qMUSirtBSezYiTfnwzGnqodvOyQqtDm7YZFo5rtihPHW/I9Bt0xxsqXrJ0p0e3MQRcHhW1BLR67/s/+sKE1Tg3nFnty1KZwda9enz+fJskiVueFIAzVNXXrNpEB9l1r/eK6GrDTqDhZmuIIDWUX6bNqs6JvzmoBLZfLiR+30uoPmnRxk11YHXqmT91axBSDd8xRKGRuro5Z4nLznPUha6D6OJJA85JJrPMKuroqih+8OyWRyfPKenJomEGmDmx0ogQARPMHt5DzmrrsSg0PaBC5IgbFzUtNIJWB2xbGVqZoPUE26ymieK7ou5A6QtZVtXLwRmoWoeLjjj9Rc6+hzZt8Wr/aDzo6jWzJzIqDgAS7uPyQawmBRGiMRbhK/W6mEzBKZlDxp53jT7tuXqta8FeoHNhdFilys/IbchL5hAA+Sz1BShYs2xV97ZUXafdh3HVucSslyEQSt8wtLZ/QjLXJxVEbK/qU9wrIEhdQQvh3PRu9lYibJrCYXsdtetTGp6y5HS2LVZg9dA1gwscT8vHRU2Q9m/2zf5AEiq4CVsOTqyB8EC/BGcn0kci2NPGOljfKQbS24YCRqm085oZvoAd27u7mTsgRdKYEIkMl7Gzo77ksShRHlwK/guvd7Mr1C/liMLeO4ltWg5qGRbMImQ7if1clmwdnpGza5t45qxqnSfLZcDh9HYs4LGKW6IOzUy8ZtaspvPYOZsvrpgxpw8+q0qVLBl21t+fMgFXE3IYmwipmLzgY0i4CVcuf/mMVXYuUMzWWAxVuR09RsXHEradSIi5f1frrBb1foGvslfRyQjUFWZYZ3lflsoUbPnKO68QOz5tiUr8SQMB4pa+hKS/7twbtkKM0owyKWRI07tJrUTZNDWYIKZr1yUNTz9lNT/ij2YPbUQZm56IQcHxtOzFrfo8eUhyJ1Y3rSYbg9YvzD5EgJGWkZw5YoF/cAC6D9q9NNpaxzqSV+VCXUlsOTeTXuxIHW9EETLMiZXG4EWd0yJZ865kZ7X1WFRxeHkqPM4Rx0Wuxp4WOosj5LDaNkOzPapE2M8OHm3MNAuPd9hNgxQGaoyuioVPGL3iTOLdO2iJyr2ZSUm8Zvb0ERXXuo9UuKcP8pahNMZ7ILfGpqIQMlaP4YCLvwbqimSCrxCZp3Z8EzXdIHQbJqYXV3QTuoBNsneHQaxFvTd9jiSoQquZX2Ds0HfDcFni0ASrN4lS7NjxaUsg8fbPF2DEWTWU2d/i4rYAoqkr10KhNJ1MaiUQ0wGHbY8Te9rw2KzqZKzk8NBSoutpLmWOv6W7qWGq2fT2kPmYfKYIlZimKFapsNJiohu9O5x1GbQuNjbnjHUIVjk4vNZ6sm8su7VOGX2x900jMyLNQWNKQNCkOoTYF2ceZe/LHotGWl6eCtZwnybVa7Rm0BMeEnUSW48SZGjbYJX/B35pCzwj/pMQkyiwJAj/df1SeQbQwUCmkL3re0WCVdkEmpmUxuUOMRf5fE3EhEF5RoENjOQAcVWYsNvU+HwanrYL1dCUu6nSVMXL57reqeu5wPvn8IPFUFX6BAimj6ViO9EQc/+uf4jVcaJmckhHigKMHdrg+VJZ9zUSwVx61STPmphgYhp/Ul+BLdMuYPUEhVaSOuy6MWjpW51M7iWmZpghDDHNUuM5zUS3DPWTG1jFXZTJ6/jGvnnh+b2kV59U1dy16t/vH9QPn3MsyITmykaLjzRI0mJpvwRnXQaB1e2NTcpOLSLuWXZrnTK7mNDewkUQZU6sNJZGS5qAqSzuL85C3Jc9Fk3YHswF5IhJrRmQbdBL7OIIxTI022wAFlVZfZUCaZEAgMc77KadXRe/HLiBVz0k9ptKasp2clWjJeLNdU9vZnpnzeyZOyoOyNw42ecPYRVZio3AwcrhrUMqhcbdMBJFYGjHWJOrjgKdn5YNEcI6PbYZAuRCx6yMtVGQP8eafIyqg48ql9DDALM4tDJfEG9WCZDvCgzA8P/CwJa4xmUd0tte1SVuC4hRmlGhhclL17kjq2EaAB5jvjVGi6LO6JB+2VMRonQEdwcsPbmppw8joMX0/gMSa84DRVHL3HmJCZM1LT7aP5Vw/0gEneGlrTr4sH7dM/Kbm6JNVvVpAohqYzDmMAdkmywsjlkmKoqFO6edh6IFx8tzwX5xAAOtC4Wjy0SYbwpWnQZfDKgkLK78PwqSaL1daJaDagJBJEF6iwilN5T1zMSVKfa7VU1FDA2Oj2amd8kzKwfV64syqZTlI51a38ON23rvGoOJdZUIvWBAaqIbp2p0wBMhaIU2AjycCxqDsBiSqjkVZcmnk+0EIeb+3IW03Fmt9Liyv1BTha5OiVL3SMO+pkDyLc/B3QpZEUjs+LIhjlZWBVr6/iCih+q3IHWVnwpLQtfZlF7iMCsZKs68jmcNT1tWjRt5SWlBB3698D0ttwql6vPwkNROrP6Qzr2U4+CIkUuREAuXWFoEzroOot5LY5P0eQ1MQBeWc1FYqQ2NsOBkmX+xJ3SRKUQSTuzWxu3Ji6ZoO6u5H5yFpK94LNp4e7AU3KubbNZ64rnOTEyF0UkUR8hhtRQmQlUA8EsbkG6dXeHxQplQ4HLwocKcKOmdoHokLwC8+8K95/CoaB7sbNrASsJT8oQ084DkNaYTKlS8puWNNojNz3n9ede0jV4Tm3fkzTdwKlgdJtW3R9Broc54fWJdmHdOGB4TjHl1abrc0a84GIY1wA6tPAfrMAg2KbOmpgsJmwWc9o/9ooVVpGyvqm0nu7YwOlcqoLAlki6mUEmYOcCApFWu782BKYk+ZYQGHvpz6rBDeXxnnH7awiJktZL2kNT+ZP/SH5WXUUgkYZZWNeSmrFmctHjChaW7IO/Xli9FjY1NImPADdEVWcSQOZoQSS7OVKxljaXMak7b1ngKxKAo+XHLlgU0mOmrHosOFQ+WAnJEbAcrZ7irQ8S0EJfvWIZmGzbOKpwtcK5SeUt3Z9ebEc9BduvoLeKNQaQXbCkjDqvs9fxmjAvlBU4z03tqpjLLpHmLXTOlSLYeWC1ohCIMo9IxpoWdxnDEsa7h82Wx1+uTePPlM7BzIQ4WIRLkoKDDLwbbiYX+QlTcEwNKEis252NU3XZU+dxcmMMwLML2NTBlYTUunj5/vkkqCc8azIo4s73doTlIgegNTxuy51Gxh7CtCCeqATUgDKzh6QfIv/lPgmwyVnbkl/6kMs/fiSlQ9XnpIRmECjxsn6OEWVkVbNXeLZazm6dFRIgSRBoh6jLIWf14meYSrihmwSJpwiylhBPrVtVtImYW4Na5V0VOLUp+1+x+dBaTth6LHm5eDoWEEvFTBCn3ItsYF0OF/pAgDBv8KybKBeBTdDq7wuMd9Uo2pIja7zOT2cSIyPZObKZVIS/QfPCQo7LapHmL7bO0qecRPyA4NZ1ChAEeSQZ9JcciiTO5pi+wu5qRD2qjthKINe/Ra1Usa5D2bcP0lQwWZtKjmRpzf2USpcovXzdSxaa+lsDqyaD1LAHGNXM/tPLSgVkwV+lKsXNdX7ckPf6kfgKuNqx6fa+qySShVaJzZUYUbkucEsHqrnSYCw3TNJTlsud8G7JllFe/5M+pQ9rjpUNoDN+pIiYhEYpcfTKhDbeqP9//wz9njauLGfhuY+Q0ipRc/m4kbjctdhkEVtvMJRxyrFeUiBKzNhGxDslKjaNxsmomXBSNd8NcFEE8zcv5ZAR1VjuPRS/pXo4Fa7gPlohOJoMgkbF7N0ZU7fqyo9hmQBUobCeB3+z+nrUFqzTrdIchntXQRwbzxhLJIxquUrZFd+z1VHyJpNcpIbSZGby51Myeu0fF9du6Fd/iRzqtIpY8zBlx3xM8K2HyPxzYLGkmR8F69NbQMAofrvQ9trfOyn7pVsqpw2quG1OwbgPbDAFyM92+HSAVKrkRu76rtx9VhkwxH96c9Dg0LrweWoxRKu1y1FJJQdbNnEBG6ulSFEY9oDpcCiCaZtTUwpMT62qCSyqZi4wIZ9nEp033k1kd+lbMrSXqxxNd/sisrMKiwwwGal9kaM7op2+rp1lz0WtaZHICC3Qd9ElY1moDxYhqJ+D1oWq85tR0ziyUTtnQk0XjdjO3cNFYanx1IqMI89VL9ZOzEPrCY9FbhwexYFqHRo5YIWSVPo1sxje0lUwktJg2hiKGMqVaKuHxgXpSzfbancbb742t6hdsOlh2Vg0z3nGk177mBVoMvqTpM88Ad/fMrbmVPX0YZJCOIxjF6HKgHda0IDBw08GWv3h8mTln/UAoPW4FiyHp3zZMXsnN001GNFNn7u8366WpI05IrT2FAHPpvgpmNkUKyJ8cHJrNOFx4Ot2sQAbEn9QH4JGlvtnTuLtBdUyS9QGFHLAKLwdOqWB11wCYm40Ew5WSK4gk6cj/tD8ZeIiVHfn9Tk1WMTGVxNXn4UMydWL1h8Tqaa/mTnCPfSswm7bS0qLOiLsM8ptU2E+M1YmuejWsfO4PY3OaZu7y/kXZTHI2MWk8JediYQAaP1fmTBHme9R2HpwNzmrnsejLVg9SAUM6lIT9IiaKlVjFqaWvyhGUARyPQsOvUkFH2D0ooLRtRCXQJR3k5ynoUSCOkxdoOfjWjj3vNSo+/PbVez2QiMXO7ItwDGzJFWm3GGx8WRiHcolMZ3r3KMeGgPNiBcKOuIOamx/yixZ2kqFLfMFrVhYVdU4qPHXhDseoWutR5RKKGWCazETyK3hBjtEKcTGz4eZCPFuA/FAihl4o9coGLXwVFB2mcclA8wioB3Bs3M6zZSDeb0DnkEGUAscxkmeT1UyTaEtbaPUX+2Y/90hns1IIn+04KqxuvXamI01Qx+gyCMCSYDaXAHP1Sb6AV7YsXd4tblmzd+fayVazV3WvZm3oijZ+bcwuNxL1tY0+wFnI+prHom/bPUgF1YPTUHRhxxLiwUED6Su9rYVVvrIoLDaMYj6fPjBJVOLi8zPiP9EsPD5ACpHr/oxh6f3GMausIiSBOZmCbmn/F5gATmJf8Zq5+9kIQtxnNpfRrvDh5GwyNhQk9pcagrb1IpUezXim2iSno6liARdTyPuDWaSJsvkq+MGy/PUQd32l8CgwmdFMYy628DLE/KMXdMsnVls7qgz1qdmwWD/OvkZuj6Bx9ez5s405IpmUxTA3EcW4ovLIDaqLywFE04w6uvd3ZdUXlEw0bzHA0oEa08gUbNFt6M+MRZSz/fq6QG2pOf9O2UCqT5Jo213VidVv8axw77lWxtd+PTvukUcbU0taTEhSEtJlkN9UJnVnz9WjlmgJQrK5PLt2ed3yknb2QfNFWW28WynNwmhtW+P3LlL47Mzr1texYoibs9p6LPqZDw+gYGfZpqP4MqTbqsxpgM14l12cDG0hh8FVI3Fx91yAS5vVdETPGOHkgHNSWfe6ts1kzm6pdsGWGHFWAjO2VXYcFxl5gVboO14zd78OwjyVX/5Vj7r2G3y4yJYkamIYq08+TKLoxy54meNN/a6wMMZRii4j9KImk4oreSswWT3mYfmiAzup0HVx8L0gArxz0sQbyAr7GK32Lqgvc8O6CIXVp8TgQhZ4PQQ1pxLOuCWLZvFTeAjYei5YHiCsKhILn4imXnHUws9Gsx5Nl5JFZlFQz2DhzUyhn2DwL//z2pwoMdtJVoCRemc398/a0c70+yG9N3hOb02oHrfroLz62te0yADQyBW6DAJ0TcAIcObqm3fxojl3rnp5vnouiz1B3L96Kd3Rxp+HasWieDvWbd0I1jDume6x6JefHkDBzrKHNtngiwSOiqQJbgUH3YLVhRKDDq4th4vUEEBSnF3h8U77K/P9abeTC8LG495ZfdymIInA5XlegAyrNgZ/5g3v7X2y+57NbRt3/Ah0L06RvIXvh9Jtkwx4W6HClDZXOpZGGuBRC3o3N3KhkHZymN1mEXIuJSLldjnRdlKhO+RNXYZlY73GKdUxjqqcGF1WsB3FYlx91aQ3FVbSSG2dnzZkcZbQvJnpwkQAMDcokgUJQawKjmydlw7YtQlmNQPdSAOstKbXpAO1lb/LX0uVMDulYBWLA7qcyiWXejlq3lcOf09if8X/N5/Pjq9r6U2onff7Sr6Vbex5LJIugyERq2yuvqBkSAaKq3ypU0o1z7W0Ury2ziUHaON71DosTN62Nf46zRpNIvu57X6eUMKd085D0e9y9QALKg98gE25SNptorDQbeCN7mNLMKBqmQAJPOCPRAJ+s1mF/JiDZmmCC8Sz8u718u1+e9GHv0oZiV1NZuRGXRbE8gKnmenda+bur6sQncpLv/q17+uD1ffbCuRT1AjuPOmX+cqVKjNsaUJ4hruExl/li1lkcR3pgPvMtoCrBz0jXp3lJY8QwBvDAT4CwXsTc3LsMR8tvXpdtb5zNWwrxILqu3VhOHpZF9JEuNKGbJklcURmieOtTVQn6EqpqoV98KInM7Rks9myQSmNfrnQYu5ddR/6t2IVZad0q+4EdctaXI1btHva+36v58T3rY6u3K+Px8a+l3090uKLXnks7hxdBvlN2xI7trQGE+rHTaRX3LW27N6a1Cp7CcKbYLdG/FxbW81hlfxzk519VI/rfuwHYwKc09Zj0V/17mUsKlmspnUysg4TOF2dr7hUrNztVV2A6IjFsaw6mQ1nV5icWnAXBCSc3iI7/a51hSod8ecmzOPX8/xoi0Yej2IkYg7+LkfN7Pl9Y9X9np91HNuLfkNxHAOq+6+iYpnh8UknfnvlKgJH+suaE8BblomdSf4WkAmpIACLkHO1ZBZuV0q2Ewn9oVR4DSbGTfWeluS3zRhPC8chMxT7j2u9A3g9BALeebECOqahwZYcyTKZRbBbElhfqOwGxZiU5FvoDKxuHKsskkoCu47A7NkgMChZEN7vbQwb/K/9tc5OlB3SLVVa/mittFooVJ8nD0nqpP9Hryv1e1uHybi9njuXox7bmcc+rWBC2mUQBGHExVFXfWmNxjiueFrrXkpv0po7vsptbcLWiF9b75sF0LNp/HvPedCket6O088zLRnOaeeh6NoeHlBB5V8o6EsnrfS4GW4Cn1RoORzEAzu5C9MMiwilRaq5kJh8zY+DDYdVNWEUIP7w2wGv+WptBQn5TY81zYwi2S3iecHL/iynhA8dRguzVPQY5/6GkddvQ25ivslatKqJOeOrzLrwZ2ncKMJbh/GbKSxelvezuZkPweM3Rsyr6tw04KtCX2kQG9tOJHSKK292uIj3/m/Uh6eYA7ZW913kzOhdGvY1Fj00D4KgMaULTCcJ/c23uj6pr1tSJDMzFUSHFlIgmmZU08K5SdDBTC25xRxuMKQJqLJMoZqDZQLz/3fJqmZUk4RUlO7DqdJr9704VJ9U1apbqn+4f1iu9437s2+ryfp48zqknu3cr3kswgkdR9dBJUtcnKU1qCe7pgjf0tPb8FpHH73XMXrt0aCnrZm/9zF2c1g98t/Dvcnsej+v53nhJdM57TwU3frTy1z8D4gVfdmkI73cMh5GX1QZo7sVY9XPhnUUc8SRMcsOZrvD4wNPIZWKufn8q95e6BnfrW2slkaw16Nmx5FLJW3fkBf03mpmz5/PqPhq6/pcb8db/o3MeWdW0Zg6mml2wfc9sGgrXbrqwnaEWREpqoSexcIHHRaiCCkKRYxjBtyroc4Yb7JHNxc9nDy71upxjAUD/G4cJ1UPrYzITOX1YAV0TMOMW3JrVthsikucVW2ztprL5FLTo5NWtEvhumvQwUyU3GqOYjCmGZaU5Z/1Vo3BL3zhK+5aSpFuBf7KtQhQICWrhiCamB5+QPt9UlVaQ7EpvXskgowu5rE6a0uXQf4uC+wnxqo+aSk5fbE6pOZCKKWlElrfPQqN49DaWXjDWGo8n4xpvgKlMYcY4mco5UDI2tZj0b2/vMzFVmYlsKzF0Ep28jFQbllkMQESZrdW8bXhBy6FSDAtIweYAPMNIdD47btb1953nFHO5SzL8pkyhqCrk8ejGJLlBb13eeb2MpGnbNtru5/v5fXFacKpPZsXpV6/iNFehw5TKrbm3cyGJsFAs7c1N4BRQiQxjovIJDlmeiVhyWLSopnKKelyiDC7sxBTbqgyoW6YUT3Po8qkXuoQXNZE7dA8/Cugca2j4bAzdNyQYlkVsxupGtdnZA6zQQKmGTU0fzssxDWkgJKi5loMwbTg9cvsqvvu8VMG4+Db8de6WFHixWZV4GyrvY20/OUS5b7S7HtS+kf8Y//CfjyQbspZhm1d5oBlVMknEk8M/A8BbvAug8CqvRROm54u7tNWZUAKrc36gjFK6rV/W4m8N8abw2rj9eLcUERpqjHNY5QAZ7X1WJRV94DEUlB9cEaQ8xwEenVnFwLjLog8FswctGBu2EYRZcSMEjkx54MIpBweH0AKUb0/7Ha7HgEI1zGODtYKurPKnHwKUTcOEx2lRDR464+auXt/k1VPZfO+v9kf1w96PXumiiQn3VElo+2feW+rrmZU8zblJVO+wOaZ5725iSKG45NEYobws8IsJKiy3RDgffRMkAcIbcV2PEYnr1fUa1sN10WxfVnSiAV3q+kKZeqa+rQhNcdYDYcBt6F5ZmB5j5plMvDMTxzNClQQXUieXHR0ZFc1mAYMNAqwTKEfh/FuzYQgyk7p1hItS1rqaiuGpJqcXv4L1ycyXXvHiWvrEonMOod8B3msFuG2DmOZUOguCLorxaVwqYsxB0JxS9+0EXJzbVynjNXKDElRbIRgbTALiKVG7wBWLJpzSyUXYjkmRG7azkNR0bOXUXEhQmS6ZVc0cnziuVuW0XFE0H4BfG6YnhxHeBMgjXYQmgUEtxSiZH+YChlvTyGjtR9PpOH0PBeNN9svEfJ4nDQ6mFTBezvesSnOza7EDWOp8elLE/PFJAQZjHIgk5KnyEFOGmIonQSzdFCO4HPOh488HjOJCRaOMbAYukAjC5pwO8ywSRrNFJ/CigTlRc5hWAT96K5fyNxYjXHuRp50tHaDxVFMKF61dqR5wbJKKgwe3ph8ZFgkGKaiWGZP861mF8CoEfWKIVrIgV50NDmWtGxaYeDRwDeqqIx6DzqZ7zcUcBTl7JRuBb5TvWlplNEKX6pPMjTlyurI6rf2wnY2wXPq+7YlooousUyL//ydx+qX42OXQRCxFG4OLuALhQExClONFUpZ48y8dZ1jH4DIHKNz0cJxRGOehXDsw3npuZZKcAKc1dZjUTWLB7iguk7RzYd03xBH44AAT7csCo9jd8tpzj8qlwnL8uhRaPiBS5tVpQNNzjXNwmSywypW49NtDPWmyGSNCZIG6/gcvrPXM0ecDTOfIY/X2GBWFXef7xkhp7LZ+8XXdIBklBsgLdKVUxBYP+riDcePYRMjF6/JfDY3cwPxOpbTNGZTso51POFx9WRn2WYIkNMkKj3AngUr2Y8N4GpKgBNbhrKac1MQELf6xKaOaQjThgg2SyTUDO1ZlZfVMvVFF4IoZhyFEumgU0eETsLAUxqP15/nmmkIMHzKUBr8sP9zHbsoV8O0pFuB79ToWllljcJD9UkG1ixi+w/2L/1re4vdamMAGpZjz1RXU1PNY+PDTs0AKHYZXJvnwOoYzIj5dHHkS+Ew33Ps1kmtnfXWtvSFrzngrDZS8j6ZBYxEzC0L0qtFcZtLq43mhDEZSTsPRd2uXsZFEyYMjdxQHF47/HIyjQxrMk2fZglSlXHJQ5KYrUwdJKOzK6yBDcYUonR/2NUXezPvOXbWxg6WpBFgNb0sh6kXGb3P8oLeVjO3un7g+pQQ1tDynby+A/rCT96AgaTIqLNBWWptHUnuk1nEJy77+JdF56tTzifkJyAWIa4xohjdjnBikkUzJZcRQ9MVhff+bwzDvrD7CTSfM1B8Gu3DYrlI2L5Oeq+Z7LweQj51TB2ZkzyzlJiGYRXHByp6WVwxjPJucsUslUJNbNDJRJeS7qaXBpkMMlrCy2iMQue359Mhkqwoz67DaGd3spoM1Se5aOY2mhBW/6V/7++I31yKgsX1fhRqmm253UEeixODhUA3QcCZywXS0lWAf4EWUWKU3ksZ411wzvhwB17Hk0jOMptl0BVtrKuUQQ4A6EvtrTNCqIbaeiya3e5lUtwvJgyPHE7RyCgQWBA0MmxYBYV4kCcb9SpAHY9Cww9p1F7OAsNZ7kCahas/DqvYjKPRGDM2oslHaTq3DRJAz2P+RU5X9SHJsBuhEUF8sJleM3ffPgKlp9KN87PGXh7kD5VFhmB1qsVoO3Ch7m31ORWN2jHH+dvTPAClGHF6KX7KLzdnTZxcFHI27IAfXL102aqQabVzR+nloc7IWZpLzNJxPS5oQp5tdKwOq1mK1RAQzRZxrSOOEAq0IYrPUgktB9Ese17ml6qoVPEJbgxIwDSjnBZa5oPOJhpKhptBGRSlyfRXOi/NWUqSVfQZ1qpZ4CRKAFocUH4WV6u9cc5Mb6Yc70nYXU7fc2l73Csz3fXSh531Kcx5bJdB0BQcJsKEThp5kdJ1bU24mhrZELuzVnerSCkhFGPESMTEtikVH0Yh5q3NfWZkUCEITxR7LFrcsZZJcSGhHI8gWD3IKiGxpGjiZBTG+VqmUi9NGuY8JClVzhW4bIUnFElE+QBYcRgGyUkJml6LgnP5A7IQ9BK1wDL6xOhDXtA7y1u9f5qS0p7m+qRY1yU+8XltUcn71etTDDtDcdVkPPPrWEGkoxkQ0KEWarbcmxv3yuNFNCYALELczIgRfDsqqEkezZReRy0rVzJygPyK9I2XLZ+4xElovhTJ8Hl0bB6ro5QDPRkjq7u8gC4+6piGEm2JgiyjpgNieAur8zasPQSVrSjNKI9CL/wq1Fk6tWsUMaPmAhPUjcvSC0ddLSMw+OX859tORNkx3X6nttWZYL239Fp9Hjwkaf+rPSx/nx9oOHzJkuf9+Wzczn6uy7ATSARQ+Jtbugzyd/UcQMCl5hjQiE8oWX7NfvVRWxuvpi65mG4VlgXSy62qtcZYLUSoK9o4DqUSBwix7H2ZF04J1XBbj0Wrv3tAC+bVfyGZhDilzE5LSSTFE1BL0ZVWHg+c1VTnE5Zl5FFo+MFv6mT5lPOUkojy/qsCEItyeLIeehv+evS+zMiz31xgl492+YvN0MTjkHTYrcHg7JaaufvxRTB2Kt08v1Ze2iv9w3TRVeGTsPojDgFS8y9h2QQeALAWSbAph4FpNf2ocwLc0WCMwC5bvwOKGMVOINREHas3Ep+RE/qYlISqtV4EGerrFgzrWYbTKWEGR6pKYAUDho83REOWMwFKHIdbrpIT5qLBg5vCQOBSmCtcdTHxtWStZjIGw2mKuLy3nNXb8fU6OydZUTbvm7fBhWqrhhMzdA/8+pHGR6hFQTleXzp3S1ja+p9p2JkAASxdBsNlAV9qjhc0JatcQMAoJcbR+VR0KSsRDaJGayk1Y5CYiftd63yVlOsxr8tKzpQSxNp5KNrCwwNa1BhlOibPyacYqYhiGN3lGMZ07kaebbR2hYFGBqVrDECXpTzLWnIHzcLV2hAhUW786qxzDiPO4RRC7REtioIRwusRT1fgebwmBhe34ZnPr4Lzp5TyKFt/Y49sivwrPCU/JSjDibdnaFZs5FEAMYslsyWBBaith7w88wMAJpxTtsqDRcgFDpYZUcne0cF2AqEPMc+b8tG6pWF3VmpGl7TV1hTq2x4Nm6MMBDuZM3aVCrr4uCcZKrwlRjABmF4QC/HvqD8WWqYoVfYKmlER+bWJa1xDckXJVlsBV5T+SpcwKpSicebb7Z9wKysJECuFxHD22L2Lafle7FZ9nviOGXIdWS3+xNIztnpgfby9zuDXsPYtj839A2k3xmkijDgy6TII0JHwf4anxnypObbN4x6zHVPaJ8fkc4k+jVcSdatZ7zl3CzHuOzYeD2MK+yi13Zdt3YAlwDltPRbt8ekBK5hXHwhFUp2Ti7dKU80IEsyz6gyo3OZgWNAvChgRggKXglWJRFNIQ7wHKIVIr+MEee8PQATnGBu+6J2hONemnDGCzLLZMOwWePfV77nXN3kT0y9W67Pu8zv/I2uwXZPSe1yV0BZIcJc1nquLUCyIlVnxKRJLfm3tXBxeDaS80eNlQvA0gZfk2FL4qUD+4zHMGVfcdiJCF/AAfZzexRL0s03TcJSTtoKV5nvXgC8L+wFpWeFCYrUUTPNCbZBC60SSnmSo8YZYmQVuBoFd9hLWQ/erVukZkymHo7nSQTa2Lm6SEoFde4OZrcFJmuGlIaxs1KpvllGs/rgjBwl6oGSw062Qs+cRfMo7Rc9v1SelaMH11f92/7D9ffvM80tqTYv2fH9bIGxxm/dh5yBepmki/ma0LoP8XbMl7SpoWGqusdEZnYcZbOv5SMWFsDWlHEpNvzar6NaY51JmY5CEiefT2mpxwP5Y920X/Jrvbok59Vh0pFcv82KecUXqGCgSrEZ8SbKwZ0n/SEhq5ROWAQnYAn4DjEApzq40wUVJMwIhKEjCJMkp9XnVEdb1tOVRLMolZKSEyryg96iZu998VwCncmtrL+1YPhVjiqFnDSGUfLkpmaPRH9xz8wmSlNSqUhns/Qfmr10mPLxpgii+NUfxOeeczgR6ZagzHo0wD7uz3FnOei2Z5/BMZsP+YwuPBAHCAB7fmJpwCA2jFOJFXPB4t+4MErCrSiUt7LO86WrSW4uGxYEDrihRfromWjOv+vn4F46ixIkBgwOLZx99DjkFXU0fvsn7F15ec+9G9JfPj1WEPe3LsQ5FhIgQYBJ5bJdBkDa3hmg9cUa4dH1kpsRQcs050s6xD/Mt52WpdTFGDBPz5eVJWh/P7b4fQXZOOw9F9/zmAS+obEqGlWa1KTptmPmshiWHzp0JkHpfjbWKLxt+SKMdhGYpvP8aQ5riAzGipOQ8k8HSrSllQM6dfLDeMA+7EdPU4B7veKtvf7p9Nvf7+hX+cIx8aEYAirU0TtD0BBzefRb5Hk5VDcAVUSs9uvc2P6Cc6tlTB07ZqiwWIbWK/hbkPxYGDhpMymimcDFEsYyIX5BUXPF14/WsZ+uNaM0vixH4MnqcGbsVkEpO1srq7talThVdEBgasiFOZQWYSREv40rWgkaVnTEJHs5OMyrj4zoW3XV0mNUbQNNALH7E1mXvVreNX89/4a3nFhhFKRe0FMN36nqmUGLJEcbqkwot3i4A6Lf4+Ab1rcyzkf316+cm45GP9T6JYSegLboMhiEIvzXEP8MfQAHnL3V75upjrKWVkmrrfBkv18a6trYag6RMvL0515qMub8eD39Ows2lrceiZ3n3MhRLHJQ9IG0uUDV5Y5kFihWPQCmfa+UfxBJu1rAapRRsBS7VRfJQK9YMsgi4KAUjUoIjf39Ht0pbiFlSGDGOENm0K6I1ytQTDB7pgbvf/dJCnEp3nmfmx/ZNHjktQ3W/ejfKeslSGKZ951BkUYp63cwb7qGpFrcxzrEwZUitR3IbiPFpvV7Az7QAAyIATK8KdUY+iXUyrjBcCteu9U6M5dfVSjyMnteCvYXD/unWqBAuqcHrIdS0JxlauiVeZ6VwVAYyQ2B4v1schcOsjKurUqQW7qsadeLornPAbD6O3DXuG/NsR8uQUX4Ab/AkEUXJxM6PhfxVb9ccayoliZG0llDsP9rD9q/nd2gfZZmtmt++fd1lupfH9hh2csGNYYzAsMugJQSSdxxIH59pWpfQyRmlV4SUWu2ltuyFs7oPQiNj29b7ZowYJo33d++7WLzJ4+3+fDzZR1LckgjuseilfHgZiv8GCMWJMResmoJ13AlGFCRBqXjisi4vNjp2J1iVghGlODYOkuhZIIcE77+C4LTESNK+lGBGWykrHTLL1orzmH9xqVD3fNiNKeKaHrynZ83cvXwYKcVd5pXLc/8h/iiIIJfKIw+jSY73WMsSq6xaseX1ZMaRW8flSHw3VGFULJhELEJWGhZI8Y4OJlU0U3FKZLnZYQA+WvdLrrd2zmOmKNm28FYN+6NCaVHtHfLCUB8AT2y2gTBrkKiyQ1yKWwWtVX7BJJR4GzSjKvKPTY9xddZMtCDsQQ1BDUEYXjfUsjidZr/WbCIjkfjoE1F+p+63klqqNYmx+qRGy26qTqw+oNcP0T/ruji1vP/4fqj8qM/9eadomghjcqJg6CoIwKzdiH4ZnldFl4QySq+IOfc219ZyLjFt7Ps87+ag8fERwszntPbl/fHyfFHiVtDWY9Fr/eplUViGNbKvbpbHNWTBbBXQV36FIWAB609oxlrVJobaQmmrZAY3Q3eWtvRaN3ptp8w5STol9nqeU/M4ArEa4+YJBh/5Bcb62Xoqt67r5/py/OQai351/JLfUfF/DbE9rXZB8ZIG2viSmmpas2BmK4UwzMjR6Eus8f2qjBnpqFnkjCLPfMOrL404CC7o8Ita9OpQZ0wWuSP8SEqp6HZvl7z61vL77hQeRq/3hsOK1Aan55lJOFIrr4fQsJ5k6NiWBJNV0iyGJB1XvL9qq4Cwcl9BM5WmUHjuetSpo7uChjlHQ7K0oBbo2FhXP1mGTrVv7V9rxEGgBIismIuQZ5Fby2IyitEKyv0ns7i/n2L+2rbV6fXz54+7Ki/15XgZdr76QphgzBYo4NxVEODNQ1X7Vi621JyDXFAjRukVsZS5L623dVRLX3bvoaWDjeOY58MYMZIx8fkZwrKPc68fz9eXVy0Sf0TBgUrwWPTWvnlZFBcKqYFYx+pBJToPXnJqxLy985W3X1ytd2DYcJESCw8AXLaK5wR5JHn/VRmQwnKclnsySjibW9vZ1e1QJikNM6qP4fyWnaXgNz3L64S7t69WKbjb3Lq93n/JPyrk5lM+/nLGR62mYb3CGB7t4qQ5QCn3YdV40ji5YfqGLEJWOgFuJ51UaaKZylOyqkN5IW440pq4tF98XZiT1ePw0G8yQrH/+OSyYHGsCoCeZOjZlkQTk7AatMVwG4cqXLCAFdhyFF4OM+nU5GNJ1OYSOWD18Q3XdK562wJ0dvh7zxFSD5Ud8+PvxONecy+9FTlVX6XYh6TsxOq/8M0vOX/r++b19vXXz4cur+31eJ0mcpCa02fA1F3wQzHCjEcuZgkj0dzJGaX3SLUs89rnujyrddYcxlPjfl+WuzlofP0a44rF53j7fHl7fdNyhLu2Hove+3cvy8Kyr2WAOjdQpzX5AOEOcVs5mIPq2xkMm0XOPaqMKieJIU9dUs2j0A4pTncWR5beD+mDdXVDsNdzRi/TBBh2a3bwpb4jt393rafS3efG/v74LYVyD4C61r9E0tDylXbYctd9Gk92darYcqtGa2otfswPoolNx3XgjFOhPzsMQoJkSm43BHgffUckT8jtkd3W2v3uI09ebfT+6DitKGPl9LpyBb6UrtsKtxw+35Bks1qZzZJsMtdogY9325uoPDbQjOpYeL1rmcHkU0ky5poMxdOSuTd+ys3s+9CP+RAjTgZEovyrPh+tzHXuFVqdYX1Ab/+Vy/f52IPZv/3+9TT1rb/d3+/4NFF6Jg4YdnYZtIQRbr18N3zJ3gAMjFKSW9O8zUtbn9W23CZDdSvA47GuD2PESM7E9+8pbft4//7t9f3t3cjEH1GCYEp4LPqYf3ggi/+KksoK6vxAg7bsgwgKmJVVcd4/gvarlVVpjYEHfZOpQRq19S2GgtUVy4xZI5XngFWPf4zcvus83/m1gHsImgdIabxxZlYCYBSeEu/x2j5+8g+ntXvM6/eP5/9oaZaAy5MGjap5G75sufIu9jLr2VpIbk1mVzju1GTtQHH6tBvn0KOWZ7bqmUXI8mAhr6Sl8sqkjWaqrlNNPzCRUvjo+o1jUZeybdzr2uMRJHzOAATuj6vbBv4hkcEOXg+h4z3JMPAtSa6DykL2mpbT0VEVAyZJDM990hU7m8LbwyKdmTCVJGuumYNfAaag68ZxRGQZji77tfyLA33MiSipOllxTM6+nr0ueae5KlR90onW3Fr90f0P6r/e/afWn8v9CPb4/r/Hpr3P74+PaaITR3dBYFXqDnAmf1nk1ksykGS8kVJS2tKyL7x225e2GoQ0ns9te5pFWiny44fc+YwhfHx/+3j/MGoS2FMLj0Wfyy8vq0IqHWY+IHYkRBHnVNsdsHu/XsFfBPCosgJcCkhmWkCxDdIN3AbdWdqyLcuD3xpF16z5qZoNyGHYrbnBt/5ZM0vHT6/WU3l/x/Hz+Hz5f1yap/qIYigpJUztwcVsRK9TDnHUxSxuIvst6AlwenITPX3eLeLFUATwcUyP47jWeli/S1IJxbXSQdtOXOhi3c0TU3VBUrBC88ax6qHsOw+afz6jwdfRx2vBeVFbVmv7Dn+FYfe2ax0cTOuGETYku6zRZnek2rh+oLNPw9Fl5bWj4Ktdjrw/Hcpa7mBXtjC3YmhIK27xrG67vd/T6u/132+12sVceqNoGMPZN8+5rXmnpWlUfdKL1t1WnVh9SN//r7df6+Me7f3H/99U/WP5eH5OEzFw5M4QSeEug0JG2MjQCS3OrZdEIEbpvUrv27qva9+PO9j0KiTEy8u+vxiDBCZ+/sz5sLjm88f758en1Q19Tkje1mPR1/W3B7qgBw/OSebDlVfWS0wyacGc6hpALY1gDqoft2TYLQJ4FDI/YFVd1WVj4kVBCWkkLJQD6ZATrFfnBHAmgKon3OpnKDGacZQA2oDbfTHUmpEBgKTgH/Cjf73i/Vcw5lRq92ncfZxf/4/Xi1037PntV19myYzgv5N4rRngM79WFALwlX8UrRhUc7ceSzcSMIUACPCLuNA6WmZQfXwEnQUEerU3lFywduVv6D8vcBrQV0G/d8bulNAsdKxkvbKE1YR5RdkEJjXaSrZizWfiEQnYPoxaLHonBO9dVYeJrjN8JDvqciVMzBy69gdaL9I5GF4PbMLJ+B1o6Ty3kLJmnH1g2F/wtgleewNec0koJqbdepF94wkGqVMKp9rqxYwdQ9VNtT0qRIXOfqiOLsLWE7n1bGlf+yGvDBznmlYVUlVgZkFIhWs/oS6AoZqSuTThYXBx8t6KAdSS3Ww+YXJTAFgi1vW5VzRHQP9TjQgkc+ZcD9kcKXqEUhqBmkx6hEtFcQW9xpJrsIICzXcZBRpwhiBQQ4A4AoTFOWhYFNqheVhKcUSJOPFuxyKn8HGd3JYxAs5H+raTV0A4tuDn4vzrsD5lVKD/xA7RpYaQrMME656co/zT9wvShhI08Y3+WdvW80j7OvRzHuKwOMYSDlYi3Bh6X7DUOzwObgtcx8Fx8VzRW0fPxe0o82zlPO2jxliobYCA6Yat8xc7KoQnR7cF6hYwMD7zlf68ech2iqXPQ29H8TJV66HxOEU7VIOX9EtcDYrTcl8z49AAwsugRzjUB7V9ekAr6nSB0IuoLgBgvlKu4e2+x+jL0g9dZcz+2BGilDPvPWZhBdGixhHpGMNw6+x8KlTm1C2bh66N5pVSmlNLj7t0XtNaROuET4kaXVefaO0YkaI1jNaXYSmFR+T3cZMopQ7uUQUmpHmhHE3w1Utay3Kq9vzoae0+QbXI56Lz7xe7Yo6tIGqQbZ439LCejQQmQTLMBl8g1VQ8pYJORBkJV5UITsrvmGMIqM1qGk6jZpvCKSeEzOWmxxJuU2eSgvkF4arinRdhSuz8OeTuhD9uqCwdqdoUSDoI0K/FruMNCBiACTNAx5l1TdYWA7BKRG1dLmiBB1yRB1wY32l/nHFkjCxGFHA4+RUL/A3hKoM2AwoWCKUaFrI0ARFmgyMsQx5Ng3UPDLYLMAIFNsBCkIHhOfW1HWh7i35Wip6cz1lH/zygPDOozsOgi66dZVFC56WnUHqN43oNGEOc6BDwevxV44TxfEZKp2K5R4fjC8Eh3l9xBCti5xiPZnJtGtbKIkndRZ0cFwyan+I7fttXYgO1k3TfuA3RITQfXG3ga2yZGpOrruYW4Wlk3wbe+cHyEJgEmt8dBFxiMPtIOTSQbfzmBqab/Irn3jpsTWUw9bLgficEG+1JMUjSuZX4R7B7dD++37MCoW2Amqh0Ki5wmeGkhnI1gN6nBhSOkM7AQpi24mjiQwX6sximoW3S2lxHDHyIg2jYBxGwGCSwFUwwFyRozSeWIPjYWBLZvXRCM7glxS2Qg08zxTLOSTDrgZod+YTK6BdNeC+q+ITnEt2ifbwXJ+eEFeDUVjxTaJ4DIIZICEQR0uZktVwn1EMIkuMlI+jB22ZtzWgJ0bpG61vWRn3m2t+xpLwHSgkYphSwmJJRYOh24m/K5RsxajUVvRToevhIetYYKGQlZwNbkVL9v6NWku6TLJZ+AlgTYJQKlvqaT4W4PNS2nlIpjMYxFsNmI2BW2tb3wS0jDLst8NBxcFBeU+4o76mkGl7GMCuEANDQiuF8hglGGLZK3Id+GgDcNJcqmy45SykDUCbw0cPbNGwKQQGVAmwy7z6tQ4LS7KeYwm9PTerTd8QeyrqOATIJn7+ydDqcfbqe9jNd3pas5V9x6gnPnRawk6o9SmiBNDIlLgzHUELO2TJLfcpujY0Fv4jGVgeF7hcsoLtUhTyUr5wzk9XIowyUAmCHTe6l2VbeqyAMnD58x0fFcOSWPvMtlXxwM0SAZIOkQQazkxXnKt3zCqUFtcKMxQPKaUYPXVVANMzoGXeK6fyoCs1/HkGgvSFxRFYjggBmQIHvg+6ttTsDGU5QN28QzPEWIqO34VvKurO4yztiJ9nbIzB5xzwUeCcYHrIp4gfvzBw13nkcGumdzy7TvAvMbZ53oYCKvYsR0updgphutlS370Eo2JEB8KaG4YVodcGLoNctLypwz7wYWgNenPOhMQKi3EsUcTReEuFJYGTEE16KS+eel8rzWeulcX4KvHauPMVeutjXlnmZaKPSy7HQR9XjektqxL+A2E/CMXZq0HeUA1ixU3Qyn5plIYlR7kf5/ytjraqsZXCYn3uh2sOZZtDWTDT9ulS7cUc28F1WqvJxRmvddrXZhNVxIeHXtGIcatTw43IVrNgqmMv+9bh31JSnWc5Y+Td/xBm6FUFVoh6ajzGyV1A1e0GVdYsxieSMsTMTxNx6Au+tnG841lAsXdf136PmsloFLpB9PlTf28TeAYY5aqky+cF+kn+b7Za0M3lCKyT1kpyia7RhdvqHcJ2yJ09bVgN3ZfVmBqgelGZWZMYVqWzLJzHwtGNwC8AHL+bq4X1gmd7YvVqyBC4aI8u+wS7LKW+XWLaJP4Zh6dFEH5vbVb0Tm2zFyAbJoiRlk3LlLhrZEBwRaeNYHY3g9ptr9vj4YCtV/Uz/N985i8hvVahoYnTxHGOCiHLrd+GEdsjkq6IE5iFHnAbvIhInjrmn0bGs5m7dNlFExjodjmE525rXwMHEuq71X0NHzWW1CpzJWBZouAUSRSxf1HCNxveT7Q1ACiBQFDgCMjsMTCwCQmKYf5z4SL2QEbqFDIc05faaTi9chBkiKUS+PYcIRUVjtMAYbM2ijTXOwlAQkbdVYjCQIFGeonBIgERIgmRIgVRIg3aQDhmQGVbYsQ8nDnGMU7jhxTku4UcQYVwjilvEkUQaWeRxjyIe8bxMGfOJ+aaNGCDJXqnS/q4VnKZeIZ4S11/2xa55bLcHkp2Vkn+0rrhagPOee+qCRRYXKLORlnhpqUSK+LarxTIfFVVqX7RcVkEvi1JlVmjV7m+rrBTjD6vFNsqxgq2xztpPi77WzE3+ZLHZlt6Zt9pum+ONtkO8nXZp0+HWL4+RsYmpmXn/N1JLK+tsEAAQBIZAYXAEEoXGYHEN1jKCyYYis/HxQJ3BZLE5XB5f0GedSCyRyuQKpUqt0er01TaZbWzt7B0cnZxdXN3cPQhmuEsuF0EgUWgMFocn9MuQeg0BEplCpdFFb4ZGuMn2bGnsv06mWaMmC5u13XNfw+btqnI1wb5Jr6899vmt9V2Fal9UeYLEXH/dfujpD8VwgqRohuV4fIFQ5MzxB7BYIpXJFUqVWqPV6Q1Gk7leffozrTa7w1lnl7uTMpjkco0nttDluKFLvqN9qMCBPvr8HbTfv7IdcrMvMrlCqVJrtDq9gTKaIKkmkqwE0z5NN0zLdlzP54WCx3oRAKLmfH0/GE1mi9VmV8n5+p7GYHF4ApFkA/OybXV6g42tnb2DoxMvH7+AoJCwCAKJQo9zxxJJHDBM3QjGkme0tBbMa6eqpq6hqaVDpy7devTq08/V0dXTNzA0Anh8YxMcpQXmM1iBkAAWCNlZ58Y2Y/zfNipUm/uWynuVVlHRU/QQv8m5rCmmNjl2VhQGRyAfmcMT+kpkChX7hhnFLHY7l4d6Q2FvVscS0RurVIHjXLNWp8fIeasmkCg0BovDE4gkMoVKozOYLACE2DCHKyomLiEpJS0jKyevoKikrKKqpq6xHxk96jY98P6i+OZweD9sja9QF6b+2mspAX0x8eRxEFSB+XzR0hOs+uJ2VX9vG0n6l7MHlhI3BVjajHjd+5LVYGyJ6WZTl2WxrU2YcjFxBq6qMCe8SAo7TE0aN8pFuA4zovE0GmGUUjQGUiIatkdjIDVHKkI0xqykliU9swxUktIVjTAqieTECKGZhTZmRYpiYh6aOKGBiYWJSKHBjOQ1IuZQoGqyztDsAhmTYTTYnBmyWDm7332hmsc6VczFPVWFiVeVzdboVFRH0xXeJA5WayUU3XRJQaGPXnCjaeg7TOdQtW+Ghg5QO4lU0JTFCi1GMR4YQPD1m6ZAIFNP1QEfFpsVcaiY2MAhs/qqfJJ4V9Nglwx9cZSnAX2kQKijtI4hyfcl0eAArVWYSDwpF5Xk5EQO6MRIgL2PSomnaRD1eHva2mmrnm/hLDecF6C/+uLsIIzWZzV98syZ1kKdleRE/7k5O9/oDs9zJV1EF+XMG1/GYjH/P7pjmsHlxeyCnUR0GSe0lgOOmbKPt5d8WLb22YPrpNpb15bX18qXMDVl0Jhzz8a68I585WFeiZvjYVDfKFzpG7XG3bV/1o7tumgM9OA+fx2teyvqexV78JBAJYkPx5Am64GmrEnNT8f36ewDap21fHIq9Vk/ytm2+fF2ms7OwFQCXUxjim5U6fhNG+z4ZRzr5uCt0hdNZamX5kafyBjDE1PMZwWMk9qTpcbf8ok3BYu4B3Kc9+AlUnW2JL6kSOfaaxpqEeq1op4szXs+M8rm58BnbCw7+9oI4RnZqOfYy1+nsf75hCPg0S3WMQc0j26kwsc8BxJ7Ryjt3ZAke7lD9Amn+7Tpyyvibqp3eRSjvpJDJXckwCqwSNhKQAWD4M72tF+7sEYAiEDUIGBEDUwgAKIeVar3TaD7JMsyDw1GJGhUhDARTwQgFEREEdDjzww6W0rP6OTNOXKrWlM+oCozpm5DkVZbskv+3Ol0vE6rT9/AG3lNqJ+fF5jvz8GPT2x5v9lE+PYr2ZA2d5kdg3bCZj2mcjK/kh6fs4rMUaY6zlfxNsugVA3ki1ldeZnn4mRl15ituilqezxB7HrWG5aK6+UNOZ7BGcuLaqV8Bec/JjcktApcGcj6F5JUNM3teL6ezCAzX3nWSJi6ZjlPubinPfHVMk5xjRfy8p2vRJtIEzVZAtUchTkvraiYGD32teB6qfVq68fMSV7jomw5Klm6aWpMvnmKEC2N76sD+8lDbLDfmhD6PEDB+pRmjJrzEfl0NVf9djxhDAcESdE6huV4KOhFSVYMRpP5YqslT0i25haEE/YnEAlbCSyASAzWCRDta2gnL47hgCApWsewHA8FvSjJisFoMreAVHOqzru/rFQNXYITulajclmFC+waZ+ni2DWi8wSBFsMBQVK0jmE5Hgp6UZIVg9FkbmGcrpflzT6xqk3TfEVGGQjFejQ3v74xh4vtUhWGw+lOVRbTjMEzUggDBAt0q5ZSCM/IGHJ+YEQXySMATMCWtgknMnH2GXjwvEbCE/GkDXvURuj5oq/2VSkuQeBPP9J1KeIP8yTy9O/w38Q7/8/2Z6RxGn52lWhUCj2I1+u4LJa1d7TpLtTpJOGtjaRjrUOtJ3hkdIuuOqYCa3ywjmsVXahhHLbUSEcJ1ZiG6DXZnOG10XOSi11DP9SR5BBwjS4iSnAjIW3fx8OR6N5kFuLs/3msZiOPYydnF1c3d498dmkIUfmNsEYAiEDUIGBEDUwgIBDBmLhHYsM73vKWN7zjHW95z1ve8jFveMdBlR7IsbqqkFR2OJl5YFdc9J2Oot/kgwz0oVSQrppfqdt0j5bnF/kV2zYv+ZnA/pnITh+RQ3qIPO6y0ZNXguGAIClax7C5U86ep5MHv3LREvSiJCsGo8nc8k97o7IaJt09pM9vUGlnBpCR7fBhQ9opBAaGdvUyB8MbmaiGi286+pp18uCGkBEFUkC7DRJ1EqrZxta7AHI9CnzBJaIminuHg6Voo++/gQX1TYvEop2WvWtJ7dLDOV622p7r1DeEOHCw0rtAyDLisiDsFfIRMweKcvQOIeUcZKkAmk7AR/pRBPQGAVwlUabAyFw8BIRduboie3+gJXOAOrl+5fdjY3SWFaPr+vW51mRDXigJ2+bivh7kAwdbfvTwAHBQ6BRA6OPzPfFhNKzj67JvYhHh3hLMJ3052BTGvPiJRImp7nBzrFMrDvN+NfPloMUqpFTV8AtKyq91GnKBxf0cjP6qEgZJAG5h5zr1LmG3cGyiZDxkxhZ5xzdRZup1Khe2af8PMZ3AblGfBcJ0QxfUlrV5/TQumvHGtKBnU8a2u8Rr9TxBMrZk8D/xMOWdnbOVf1I2LaP3ZdFVw9csGnQ1BnbMbfzgRj3WjPytwZYWpPh3OtmsLZaNcxxqaGVqMVqFffFpU8bbs/0N1vG3POWfS7LUeB81TfHn+VbD7x/UnT3lXyFltHfdX6+X6Rzmg9d4dAISK7GlTj2zgME/JUwAETAECwABAUgUmAysrKI5shRwAchnFSMheQ8UoKlvnVbkW9mMeq2+bbO9EQ2q09Z9wzsADarYt4ftoamBDi8gmfbZKnl+lkZWyRx1VYVSIpVFXs9VXy9ytCyTNYt09YIAGKjqpDT22SgOdP5OsdAK/vOCXFnrpEwA/PPTJiopB7HxZV+MlaGCT/04K/LY3kjPYV/YR146Ggg+9sX6na/c2So+Epz3PXve3PKRylPIhNZASBUYVDIGhikiWc1jj/claeVCJvljX7JfvO+xhfTDIjSXBs4HV16Ol/BMfcuFQ4NRmDOWEh6diOI6NZmGl87N9J+r2JfpW2+5NhV/fGHLO57Yavbq7zaCewa/RDxp/aSH+luOPwAA);\n\tfont-weight: normal;\n\tfont-style: normal;\n}\n</style><style type=\"text/css\">/* Copyright 2019-present Evernote Corporation. All rights reserved. */\n\n#en-markup-disabled {\n    position: fixed;\n    z-index: 9999;\n    width: 100%;\n    height: 100%;\n    top: 0px;\n    left: 0px;\n    cursor: default;\n    -webkit-user-select: none;\n}\n\n#en-markup-alert-container {\n    position: absolute;\n    z-index: 9999;\n    width: 450px;\n    left: calc(50% - 225px);\n    top: calc(50% - 85px);\n    background-color: white;\n    box-shadow: 0 2px 7px 1px rgba(0,0,0,0.35);\n    -webkit-user-select: none;\n}\n\n#en-markup-alert-container .cell-1 {\n    position: relative;\n    height: 110px;\n    width: 105px;\n    float: left;\n    text-align: center;\n    background-image: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADAAAAAwCAIAAADYYG7QAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAJcEhZcwAADsMAAA7DAcdvqGQAABT3SURBVFhHvZj3V5PptsfvvzBzxhHP8TrjmjnjXHXUQXoJPYAESOg1CSSBhBZCSWgJaQTpRbp0kN6ki1KkgwiE3nsRLChVKbPuDkH0ztx1zy93nb02L0/evHn2J9+9n5b/OPlXdvz/Z0dHR2et4+Oz3v9i/wLoCDo5FvVyePQJGp8OP4kaR/APGl+uHw8/iq/nN8V3wOFh+Dh8Fj4IDndEL48OofP/FesMaO/jjm8BXsH38h2vbyQZ39z1uSDle0Ha73vZAAlwOeYlhUDwfyhxLitzriB4V1R4V9T4P6gGXQVXP73+ydX4V1V4VxHcK8rcK/BBBdY/5FmXoB85poR0gIS0//fQ/12fv0E4Jb/LEBoAxCQiICCl59rCewpsCfX7l5HhP9yL+ckg4ZpR8g2ztNvWmdLYXDm7PHmHQlVyibpLGZJaoU2rvOdVjaLXGDBqDX3q0D51GLgyakVOrzH0rjbwqkLRKvXcynXheXKxOqlA1T5PCZcjB71ZpEmaJN/GJN7Qf3BNO+InFf5lSfrfAEAsmAjo06dP0j4SilwJ7agfjZKuWWTcxuXJOJYouVZoeFTrMOpRAc8wnBYzQZt1WBc+uocQ2+fwoJ/woN8ubsA+YZCYKCQmD5FTh6hpQo/UQc+UQY/kfvfEfmp8n0tMLyWiixjWYSdos+G2WAQ8M4bePGp03B5rUcrUSIXK9rnypsm3VYOvyPj9HTDOgPb29u54f6MWetk45b9sc+8CCrVKk16PYjYa8dssQ7rx0X0OiYMuqaO0jHH3nEmP9HGHqH40p1Hdv0rd/7FGYK1mcDMq4aVt/rRn5XxQ1Xxo5VxI+WxwyXRQ0RQnb5KVO+6XNuKdLHSP66dE9BKCO2zYLaa+Tw29anVdKzRJBYqoB79AkQDGGdDu7q6U3zfIyB8sMm87lCq612j5PEVxnpuFdGKj+4hJQteMMa/8af/cGWrGJCFrnMJt0DBj3NXASKvpyqrqymoayuuYyWPIstQkjaIx37aNpPaNtNZXD5vXkp+txj9Zjq1ZjKiYFxTNsB9N+qWOeMQNOEX02Ae1WTGbjOj1ei7l6mZpt+TZEjs722dAOzs7Un5/uxf7Ey5filqlwWhAQYJCunCxL8kpw9ScSZ/iOXbxIj1+yDgw3ziq2cLQUUrqtry8rJKSAgKhrKqKUFdDaKohtFTVVLwfYJrXIps3Q1s2Ipo3Ilo2oR3VtBnTuBFTvxpRuRhcMBOYOUZPHHSN6iVADbAajTyqtXGPZBD8f7z78O4LkLT/BVT8PwmF8l51uqBnSKdtbB/54QgNvlPZAr982T9l3IoYLmftoJ/QaqekKquogFBRUVFTU1NXV9fS0tLW1tbR1dFB6hlaaFEzZFyzfqfmSNIKbnuXSTLrZUPatRKFJjkzxPIVxuNldvEsO2ucAUyRPQR+q6XPE5RDsZJm6NXNrY2vgAIuGCZeg+rxazAIareKekFIGaLmTvlVLPArVpkZE1jnWISMtAyFhs3qcVFWV1BBqJ6j6Orq6unp6evrG5yaPkYHXM8IqWeiqWeqjrJEGBCkMe53sHxJer5qmpBQvhBQPBOYMUqPe+kU0okLbDJxLVe/F/XPtTfLMNDOgGRYF9BJ1yhlCMhraBc+fsA5e5xRNs+rWPHPmsS7xWooKsmrKqvTWS6F/Qx1JCRJHYlEilEAwtDQEIPBGInMxMTY1NTE3OzUTY3NTdBmGEMzNMpEXw99T1/XMRD1aJRWvsjKn2I+HKJF9TjwW608qrQN468vbMx8AZILvGCSesO5Qo3bYgrypA3TimYCSxa808exbrGaCory6hpII2NTfqh/9QhPW09NS1NEg0KhzlFMTU3Nzc0tLCwsLS2tvjJ4CffNzMxMTEwxaCM9HYPwQkrNOrt0jpc95hPf7xzSjqPXoYyTfpteG/0KiP2dafoNaqWGoM0qvt8pZ8KncM4rd5pEi9WTkZZVRWiaGJs5Ep0eJIY3TITdM9DS0daFHKHRaECBYGIOa2trW1tb7P80uGNjYwNvicn09TC+90mNb4IeL/LzJ1kPhbSILoLfE0Pz1NuTq0Ow2J0BKXAlzDN/86jRCumyTRG6FUwHlC57lkx5hBbg4h+7ptYxsus4uTWhZU0JMdleUDf6qC80X6PY2dnZ29sTPhu0wfB4vJgMHjNGm7n54JpfCWpXQkpm+Bkj9NgXjsxGY8t06YkVoQgIVNre2VbkS1jl3KbX6YZ34yFfxbPMokXnpG5LMgdpjFM3wSKt7PVtSWhrOyO9e1A3KKAxMTGBbwxJAQHEKEQi0eHUHL8yeAn3AQ4egMcszK0c3WybVgTP1iMez4XkjPnF9zlxmkxtM6XHVwZEQIeHh++33ysHSVjn/u7ToBfdS8gepRfOe2ZN4syclG9ek5S+Kycvo6ikiFBVUddU14LSEWsjpoHvDQJAPAhMJpOdnJycnZ1dPhu0weA+kJFIJFDL1gZLcMQ2LgY3b0bAnP5oPCCp35XXYoHNljkD2tvfe/f+jbJAwibvrt9TVGwfMXfcF/KVNWyHUJOVuiunqKgIUw4McvGwgjElztQ5DQgA8SgUChC4ublRqVR3d3faqUEb7ojJAAug7fD2BBK+YTbo+ZvI2sXIgkl2yqB7UKsVNld2cm1IVNQ7uzsApHRfAp8vxWwyiO93zJvwr1pnFE1SFJRk5WRkEQiEqqqqpqamjo4ODCtxsqCKIVPnNCCMq6srhAcIT09Pr88GbTDgE2MBNIlIsifgHw+xOrYi65eiYbFLFdIErda4HJnp9WGRQgD0/sM7RIiEfZFUYDMmYYCSPxlQt+FXv8LQ0FGSkxYB/VUeKE8oCMjUOQ1Ehdje3t4MBsPns0GbTqcDmYeHB+ACE9mRYk+wL+727/4Q1bASWzLNSx/yDG63xuXKzL6aOFNo6/1bldBLhGIZdgsmcZBSMBVQv87s2OIbW2vJSMmK8wUjC+bAc3kgWVCkUBbiTIlpIDZA+Pn5+fv7BwQEMMECmNCEmwAqZnJ2cibYE/Na/Xq3o56uxJbO8NKHPe932OAfiYBECu3u7YJCqmESxFIZznNMspBSOMWsW2UPHIWTvY2lfpf7Ol8wB4qr51weKA5IBwSDkBBYzBEYGMjhsH19/ClOoAjJxdnVh+EDOkFCXV1cCfaknEa/FzuRT1celM5yASik08YuX3b21fhXQBGXSKUy3FYjMVDNCnv4OIob5wBAsILCmvWnfImrB+pUnCwIBjQgDIvFYrPZXC43kMXmhfp0TxRMrNamFgYDCp3OABWpblQiwTHriX/ffvjT1ZiyWV7miGfoKdCcOGUwykRAUZdIZbLcdqOkIUrBtH/NMg8Uyqj3h5T9CQhGu7icxfkCIJAHkuXr6wvygDZAw+cHsdjMrunst0e9bw/7dk5ehkQzvTxFxQT0JAI5q57Ztx/2dDW6bJabMeoR2mVjVyAz92pcBLR/sL/1YUst+hKpXI7XjkkaIhdM+9UsC3oPghvGw5SUlCBl4oqGZesc6LyAxPkCICgdkIfD4fD5fIFAcD+Un5kflZAWmpgpqG1JTckK9fZifAai5NQHvjwIfboaVTbHyRylhXZZ2xfKzG9MilImBlKPuehQIcPtACDH/GmfmqXg9ve8/vcJaBNdJSWVr4H+VNHnQP5+onyJgYKDg0NCQgCMy+EFsjhQV4FsJuT0a6AX8IVXIktn2Rkj7iFdVvgCqcXNaZFCBwcHAKQRe8mxUpZ3CpQ3Ta+GmfQNZ+r4IcXTUkFO6f9I2XlF0+neUM5QQDweLygoCEQCAzjIIIBCeX0Bsqfk1LH7DoKfrISVzLIyRqn3uy3tCqWWXp9uPz5+/AhLh9aDS+RKGW4nOnGI+GjK8/Ei79kGd+I4ISKdJiet8KeiPp8Sz2qI5uHpTaMySCIlROOLA0yAAgYNeAmgkFAxEIx8oh05u4794iCofvl+8Yx/2ohLcJcZvlBy+fWsCAgOH9vbHzTjQSEpbqd+gtA+Z4JaNs96ss4THoVVvghRVFASD3vYb/x12IsmISrNw5vq4m8BE5C4rkEn4ACDxrk84qkIFLXHO+TUM3sOuDVL/IJpxsNhiqDTBFv0+xcgUEgz4ZJD5V12h16cEJc57loy51u/EtSxzR7bzzC10ocyOl83YCMG+w1xGcHaJJqmXdy8GbToQtH0CDPh2ZR4atAW00CRidcQ+AJ2eGJ+C7Nrn125yM6b9koZdgzqMsIX/76wOXWWsg8f3iMTLzpUSQZ26MYO2qSPUwpmvKuXeY2brOnj5PwnwQpyigryCKSWNmxW/7qQUSjOdD/q5PtSVoirHY7kTvVg0H1P1YJp2h+mg3MayC9kmUggPn7Jbt0OqFhg5k65Jw0TeV0YfMnvcxunE+PBxwNQCIBIVXeYHdpRA+YpY6RH07THi8yGdX7nB/bicX7zcBonlGZhbQTDTVMDqauDMsKY2ljb2tvZw+bHDkcMiw84/mN4cbc2uzKYHeJJ83YiO4k2GySiI83dQ0wDyYJpHZ53cCQ1LwQ/e8conffNmnSJH8LzuvRxJbdmN8ZEQGdFnXyRWHWL2a4ROWCaPGIPz5XM02tXOE0bgp5t7uxx4qvjktn9svbJrOyK0OBYupsn0crWzNTMxAhjYm5p2j6U88cfY4d/DH086Xl/0rR4WDn6urh7Jje/Npzq5QhLBxgkF2Z2WMjIzoTON4K6TY/CWa/0cdIDoQ2nUw9XBkCjZwptbW9ppwDQb/5taqH96Phh24wJcsGcx+OlgIZ1Qevr8J6tMOFu6NRR9NJx8uZx/puT8rWTx9M7ZV2z2bXdCTAj7570bJ90vngdkTNsEd+lF9d9r3CcuHZY+emks30qheRIIJMp4q2jrTXWO8Cpa5tbueaeN0NNGSdEC81ZHdq40t8A6HQeOgVCpl60r77h06Z8v08/dsgyZYyQPe1SvOBdvcJ69iq49XV071bS4Hbq2G7a1P7D+U8Plw7TV48zNo/z3h0XvT8p2zpprJn3IKf/bOOrgHXXxVJRFCZmaC3j3UnD0lGRf5AzHgeDkgBlZ2ZiJYih9R7CUdg5e4qSOIqNHDBhdWjhym/MbJzuh85qKO2iXdWvjDYFfp9O5KBxwohNxiQpb45atuRTu8p+9gp0iuzeShj4kDa8nT2xmz+9XzR3ULL0sWzlU8XGUf7UXqJvkbw13pyIc3IgUPA2ZA9/4sRu6vpR2cpxniDe1cpCdASAAwhs8uPz6W0H3nlzDumThLhhq/B+dECHGrb8+uSrwbOlAxZXEVD1Na9WaW6PZugAiGSWPIbNnHI4ZWJUrzKfvgp6vhne8Tamdyux/0Pq0Hbm6E7O5G7B7H7R0mFy8zqDyNSwNhdv4y2sbSx9+Y7Te5nzBzlzx2lB8WRzU9giiE5CcDQrf8Gte0/JmiYkj9lEC42D+/R82xHYil/H1/thg3+mkE76RXz1zx7PpVg9asH9upFCo/hRy9QJu6wZh4J5aumid9VqQN06t3EjuOV1RMfb2J6thL73yYMfUoe306c+RpXPOpiTkRamNnB8SyryXTqomjsonfuYPbn/cOhjREAMnH5gyTGHEWBta9G0yitax6VO4uJHzcMHDYL6kJAZbMW10bXez0Af3mlnfo+r/tn9+Z2AbiV+H4iEih4yShizejiBA6a8OZeiBY+KZZ+aNWb9OufphqB5M6RVRBbd+y52cI9fNG1ngkdCOozRppENmKfvXBpfezx55V67SsmaMCIx9DEGprDs6OroezFJnQeB6XNmiWPmUUPo+/26nF51epsstvKX4dUuUcpgpoai1s36Hlt9ldpyy69Ljv1CTdCPDBOiokeM4sfMUyaxGTPE3DlK4QK1ZMkLsKpXA2rXYG3hNm4KWl4Hd237lc47GOHUDfWNMIYmxg6KVuE/4B/8SIj7hRB5w8ZXDmOERhuiDQ0MNdW0M+sC6ncpCRNG0SPoMOE9/ktNVg/Cs00KW/nzyGq3CAj+YC3TyQaFfnRtucnolGb2KHD7VIEpVKgXNYyOGzNLmrBOm8JnzZAAK3/epXDRvWTJs3yFXrXqW7vm3/DWrX7Tw9pFS0cLBQuwvh4adc8Qdc/g1A31dA1RKH1YdpBaOuZWmLbXIRnLmJgRgzChblC/JvuFin+3PK1N0rbyp8lXA6JhD39wcgUg25ofXVque3bc8e2WZvYqcPpUg/q1QoS6EcP6MaNGceOmiRNWD6ew6dP4zFlizpzDo3lKwaJL8SK1ZM2uacebn2qnIIvQ0YFtga4eHG7hIvIzQyK15WWV0spYDfu0yDGtMKGOYECD04cI6FGgd0m5Pb9pXv7Dyps5ONefne11cr6zqfpPl+cAdJvefVfE9EIhsE+Z168qGNQKFWGhokcNHoyJyJImLFMmrVOnbNNncFlz9jkLNgVrNu1vw315FFVVNQV5JTjpQnhFeQTspeRkFOVkFGB5DoljvPwU9WAWeV+oKRhQ57xUDuiVh0BenZLkpl/RxZfgePgFSDPzW6uqy84t193bb3h23PLukmT0SPn1iLDYfUrcfpWgAfXgQRAMGTZ8L3JEP2rEIHYUHTdunDBhnDxpnjyDzluz7N6LaByPy6oKis9hRaT4CmI8BTFe0EjK5zwbTeg9CEta0AsSIoIGVDh9IhqfHmlGt5Rnh6Rd/VXtnO8gUWdAu7u7mg+/Q5dcIjT87Pr8OrX9pnv7TVqHyD07b3l23vbulvTpBj7ZgF45Zp8SKMd5qcLtV+X3awQPat4XIkOHkGEjWpETmqmLmLxXuJLXpIo3zo/fuFa8cS17Qy7YsE9ZNLo/Ch9RZvcps14o+fcq+HbL0kGeDimnphsmZZcBADDOgPb29khpmlqZ36JLLlpVX7atvYKt/xFXfxX35Cr+yVW7Jz/ZN/xMePoL8dkvpGfXHJ796tgIfp3cdB36cm6+6dxy0+X5LbfWO+5tZ05rv+Pefpvadsut7aZrK7x7w7nlBjzv2PQrqfEa9AO92TX8hK+7al17xbj0klbWt/apKl9+FoaRP7c4a/FATi3pO2TOt7r534ldT+Tf3yu4gPrs+oUXP/slcINzLzpzQ/Di0+upG3x5/iKq8KwT6BC6FYfQzv0WgkLo2YWZLz+cw0Db399fXl7u7e1tbm5u/DcahIOgEBrkAYwzIDETAMJdSCTU+L/NIBwEhdBimpOTk/8GkPkddVxNk+cAAAAASUVORK5CYII=);\n    background-position: 65% 50%;\n    background-repeat: no-repeat;\n}\n\n#en-markup-alert-container .cell-2 {\n    position: relative;\n    float: left;\n    width: 345px;\n    margin-top: 29px;\n    margin-bottom: 20px;\n}\n\n#en-markup-alert-container .cell-2 .cell-2-title {\n    margin-bottom: 5px;\n    padding-right: 30px;\n    font-size: 12pt;\n    font-family: Tahoma, Arial;\n}\n\n#en-markup-alert-container .cell-2 .cell-2-message {\n    padding-right: 30px;\n    font-size: 9.5pt;\n    font-family: Tahoma, Arial;\n}\n\n#en-markup-alert-container .cell-3 {\n    position: relative;\n    width: 450px;\n    height: 60px;\n    float: left;\n    background-color: rgb(240,240,240);\n}\n\n#en-markup-alert-container .cell-3 button {\n    position: absolute;\n    top: 12px;\n    right: 15px;\n    width: 110px;\n    height: 36px;\n}\n\n#en-markup-alert-container .cell-3 button.alt-button {\n    position: absolute;\n    top: 12px;\n    right: 140px;\n    width: 110px;\n    height: 36px;\n}\n</style></head><remove-web-limits-iqxin id=\"rwl-iqxin\" class=\"rwl-exempt\" style=\"position: fixed; top: 0px; left: 0px;\"><qxinbutton type=\"qxinbutton\" id=\"rwl-setbtn\"> set </qxinbutton> <lalala style=\"cursor:move; font-size:12px;\">限制解除</lalala> <input type=\"checkbox\" name=\"\" id=\"black_node\"><style type=\"text/css\">#rwl-iqxin{position:fixed;transform:translate(-95%,0);width:85px;height:25px;font-size:12px;font-weight: 500;font-family:Verdana, Arial, '宋体';color:#fff;background:#333;z-index:2147483647;margin: 0;opacity:0.05;transition:0.3s;overflow:hidden;user-select:none;text-align:center;white-space:nowrap;line-height:25px;padding:0 16px;border:1px solid #ccc;border-width:1px 1px 1px 0;border-bottom-right-radius:5px;box-sizing: content-box;}#rwl-iqxin input{margin: 0;padding: 0;vertical-align:middle;-webkit-appearance:checkbox !important;-moz-appearance:checkbox;position: static;clip: auto;opacity: 1;cursor: pointer;}#rwl-iqxin.rwl-active-iqxin{left: 0px;transform:translate(0,0);opacity: 0.9;height: 32px;line-height: 32px}#rwl-iqxin label{margin:0;padding:0;font-weight:500;}#rwl-iqxin #rwl-setbtn{margin: 0 4px 0 0;padding: 0 0 0 4px;border: none;border-radius: 2px;cursor: pointer;background: #fff;color: #000;} </style></remove-web-limits-iqxin>\n<body>\n<div style=\"display: none\"><img src=\"./img/wrong.png\"></div>\n<div id=\"pc\" class=\"pc\" style=\"display: block;\">\n    <div class=\"title_wrap\">\n        <div class=\"s_title\">\n            <span class=\"f_36 black\">阅读</span>\n            <span class=\"gray ml_25 f_14 up\">|</span>\n            <span class=\"gray f_20 ml_25\">WiFi传输</span>\n        </div>\n    </div>\n    <div class=\"main_wrap\">\n        <div class=\"top_cont\">\n            <div class=\"inline-block\">\n                <!--<p class=\"status\">已与同局域网的电纸书<span id=\"status\"></span>连接</p>-->\n                <p class=\"status\">可支持文件格式： </p>\n                <p class=\"type\">TXT、EPUB、UMD、PDF、MOBI、AZW3、AZW</p>\n            </div>\n            <div class=\"inline\">\n                <span class=\"warning\" id=\"warning\" style=\"display: none\">请<a href=\"https://get.adobe.com/cn/flashplayer/\" target=\"_blank\">点击</a>安装flash插件</span>\n                <span class=\"btn\" id=\"flash_btn\" style=\"display: none;\"><span id=\"spanButtonPlaceHolder\"></span></span>\n                <form class=\"select_btn\" id=\"h5_btn\" style=\"display: inline-block;\">\n                    选择文件\n                    <input type=\"file\" id=\"click\" multiple=\"\">\n                </form>\n            </div>\n        </div>\n\n        <div class=\"s_table\" id=\"drag\">\n            <table>\n                <thead>\n                <tr>\n                    <td></td>\n                    <td><span>文件名</span></td>\n                    <td><span>大小</span></td>\n                    <td><span>操作</span></td>\n                </tr>\n                </thead>\n                <tbody><tr data-js=\"item\" data-status=\"init\"><td><span></span></td><td><span></span></td><td><span></span></td><td><span><i></i></span></td></tr><tr data-js=\"item\" data-status=\"init\"><td><span></span></td><td><span></span></td><td><span></span></td><td><span><i></i></span></td></tr><tr data-js=\"item\" data-status=\"init\"><td><span></span></td><td><span></span></td><td><span></span></td><td><span><i></i></span></td></tr><tr data-js=\"item\" data-status=\"init\"><td><span></span></td><td><span></span></td><td><span></span></td><td><span><i></i></span></td></tr><tr data-js=\"item\" data-status=\"init\"><td><span></span></td><td><span></span></td><td><span></span></td><td><span><i></i></span></td></tr><tr data-js=\"item\" data-status=\"init\"><td><span></span></td><td><span></span></td><td><span></span></td><td><span><i></i></span></td></tr><tr data-js=\"item\" data-status=\"init\"><td><span></span></td><td><span></span></td><td><span></span></td><td><span><i></i></span></td></tr><tr data-js=\"item\" data-status=\"init\"><td><span></span></td><td><span></span></td><td><span></span></td><td><span><i></i></span></td></tr>\n                <tr>\n                    <td colspan=\"4\"><span class=\"last_row\">请将图书或字体拖拽至此即可上传</span></td>\n                </tr>\n                </tbody>\n            </table>\n        </div>\n    </div>\n</div>\n\n<div id=\"h5\" class=\"h5\" style=\"display: none\">\n    <div class=\"s_title\">\n        <i class=\"s_logo\"></i>\n        <span class=\"f_18 black\">阅读</span>\n        <span class=\"gray ml_15 f_14 up\">|</span>\n        <span class=\"gray f_15 ml_15\">WiFi传输</span>\n    </div>\n    <div class=\"s_table\">\n        <table>\n            <thead>\n            <tr>\n                <td></td>\n                <td>文件名</td>\n                <td>大小</td>\n                <td>操作</td>\n            </tr>\n            </thead>\n            <tbody id=\"show\">\n\n            </tbody>\n        </table>\n    </div>\n    <div class=\"bottom_c\">\n        <div class=\"type\">TXT、EPUB、UMD</div>\n        <form class=\"bottom_btn_wrap\">\n            选择文件\n            <input type=\"file\" id=\"tap\">\n        </form>\n    </div>\n</div>\n<!-- ios 暂不支持提示 start -->\n<div class=\"mask\" id=\"mask_1\">\n    <div class=\"close\" id=\"close\"></div>\n    <div class=\"c_tc\"></div>\n</div>\n<!-- ios 暂不支持提示 start -->\n\n<!-- 浏览器打开提示 start -->\n<div class=\"mask\" id=\"mask_2\">\n    <div class=\"t_tc\" id=\"safari\"></div>\n</div>\n<!-- 浏览器打开提示 end -->\n\n<script>\n    //alert(navigator.userAgent)\n    var browser={\n        versions:function(){\n            var u = navigator.userAgent;\n            return {//移动终端浏览器版本信息\n                trident: u.indexOf('Trident') > -1, //IE内核\n                presto: u.indexOf('Presto') > -1, //opera内核\n                webKit: u.indexOf('AppleWebKit') > -1, //苹果、谷歌内核\n                gecko: u.indexOf('Gecko') > -1 && u.indexOf('KHTML') == -1, //火狐内核\n                mobile: !!u.match(/AppleWebKit.*Mobile.*/), //是否为移动终端\n                ios: !!u.match(/\\(i[^;]+;( U;)? CPU.+Mac OS X/), //ios终端\n                android: u.indexOf('Android') > -1 || u.indexOf('Linux') > -1, //android终端或者uc浏览器\n                iPhone: u.indexOf('iPhone') > -1 , //是否为iPhone或者QQHD浏览器\n                iPad: u.indexOf('iPad') > -1, //是否iPad\n                webApp: u.indexOf('Safari') == -1, //是否web应该程序，没有头部与底部\n                ie8: u.indexOf('MSIE 8.0') > -1, //是否是ie8\n                ie7: u.indexOf('MSIE 7.0') > -1  //是否是ie7\n            };\n        }()\n    };\n\n    var isH5 = browser.versions.mobile || browser.versions.ios || browser.versions.android\n            || browser.versions.iPhone || browser.versions.iPad;\n\n    var ie = browser.versions.ie7 || browser.versions.ie8;\n\n    var ios = browser.versions.ios;\n\n    var ua = navigator.userAgent.toLowerCase();\n\n    var src = [];\n\n\n    if(ie){\n        try{\n            var swf = new ActiveXObject('ShockwaveFlash.ShockwaveFlash');\n            document.getElementById('flash_btn').style.display = 'inline-block';\n        }\n        catch(e){\n            document.getElementById('warning').style.display = 'inline-block';\n        }\n\n    }else{\n        document.getElementById('h5_btn').style.display = 'inline-block';\n    }\n\n    function addScript(src){\n        var s;\n        for(s in src){\n            var myScript= document.createElement('script');\n            myScript.setAttribute('src', src[s]);\n            document.body.appendChild(myScript);\n        }\n    }\n    var maskDom = document.getElementById('mask_1');\n    var maskDom2 = document.getElementById('mask_2');\n    if(ios){\n        maskDom.style.display=\"block\";\n    }\n \n    if(ua.match(/MicroMessenger/i)==\"micromessenger\") {\n        maskDom2.style.display=\"block\";\n        if(ios){\n            maskDom.style.display=\"none\";\n            $(\"#safari\").addClass(\"safariWarn\");\n        }\n    }\n\n    document.getElementById(\"close\").addEventListener(\"click\", function () {\n        maskDom.style.display=\"none\"\n    })\n</script><script src=\"./js/html5_fun.js\"></script><script src=\"./js/common.js\"></script>\n<tbdiv id=\"picinpicBtn\"></tbdiv><tbdiv id=\"playerControlBtn\"></tbdiv><tbdiv id=\"leftFullStackButton\"></tbdiv><tbdiv id=\"rightFullStackButton\"></tbdiv></body></html>"
  },
  {
    "path": "app/src/main/assets/web/uploadBook/js/common.js",
    "content": "/**\n * 公共函数\n */\n//全局的配置文件 \nvar config = {\n\tfileTypes: \"txt|epub|umd|pdf|mobi|azw3|azw\", //允许上传的文件格式 \"txt|epub\" // |doc|docx|wps|xls|xlsx|et|ppt|pptx|dps\n\t//url : \"http://\"+location.host+\"?action=addBook\",//\"http://localhost/t/post.php\",//\n\turl: \"../addLocalBook\",\n\tfileLimitSize : 500 * 1024 *1024\n\n};\n\n//文件对应序号\nvar fileMap = {};\n\n/**\n * HTML5 和 flash 公用，所有文件对象集合\n * @var array\n */\nvar filesUpload\t= []; //\n\n//初始化表格\ninit();\n\nfunction init(){\n\t//判断浏览器的高度预留空表格\n\tvar tr_num = parseInt(Math.round(((window.innerHeight || document.documentElement.clientHeight) *.5)/43));\n\tvar item =  '<tr data-js=\"item\" data-status=\"init\">' +\n\t\t'<td><span></span></td>' +\n\t\t'<td><span></span></td>' +\n\t\t'<td><span></span></td>' +\n\t\t'<td><span><i></i></span></td>' +\n\t\t'</tr>';\n\tvar i = 0;\n\tvar HTML = '';\n\twhile(i < tr_num){\n\t\tHTML = HTML + item;\n\t\ti ++;\n\t}\n\t$('#drag table tbody').prepend(HTML);\n}\n\n//统计文件大小\nfunction countFileSize(fileSize)\n{\n\tvar KB  = 1024;\n\tvar MB = 1024 * 1024;\n\tif(KB >= fileSize){\n\t   return fileSize+\"B\";\n\t}else if(MB >= fileSize){\n\t\treturn (fileSize/KB).toFixed(2)+\"KB\";\n\t}else{\n\t\treturn (fileSize/MB).toFixed(2)+\"MB\";\n\t}\n}\n\n//如果文件太长进行截取\nfunction substr_string(name)\n{\n\tvar maxLen = 30;\n\tvar len = name.length;\n\tif(len < maxLen )return name;\n\n\tvar lastIndex = name.lastIndexOf(\".\");\n\tvar suffix    = name.substr(lastIndex);\n\tvar pre       = name.substr(0,lastIndex);\n\tvar preLen    = pre.length;\n\tvar preStart  = preLen - 20;\n\t//前面10个 + 后面5个\n\tvar fileName  =  pre.substr(0,20) + \"....\" + pre.substr( preStart > 4 ? -4 : -preStart , 4)+suffix;\n\treturn fileName\n}\n\n\nfunction checkFile(file) {\n\tif (!file.name || !file.name.toLowerCase().match('('+config.fileTypes+')$')) {\n\t\treturn \"格式不支持\";\n\t}\n\n\tvar len = filesUpload.length;\n\tfor(var i=0; i< len; i++){\n\t\tif(filesUpload[i].name == file.name)\t{\n\t\t\treturn \"文件已存在\";\n\t\t}\n\t}\n\treturn null;\n}\n\n/**\n * 添加文件时，回调的函数\n * @param object file 文件对象\n * @param int type 0 是swf 上传的，1 是html5上传的\n */\nfunction fileQueuedPC(file, type)\n{\n\tvar size=0 ,fid=file.id, name=\"\";\n\ttype = type || 0;\n\n\tif(file != undefined )\n\t{\n\t\t//计算文件大小 单位MB\n\t\tsize = countFileSize(file.size);\n\t\tname = substr_string(file.name);\n\n\t\t//如果没有找到这个节点，先创建\n\t\tif ($('#drag tbody tr:last-child').prev().attr('data-status') != 'init' ){\n\t\t\tvar HTML =  '<tr data-js=\"item\" data-status=\"init\">' +\n\t\t\t\t'<td><span></span></td>' +\n\t\t\t\t'<td><span></span></td>' +\n\t\t\t\t'<td><span></span></td>' +\n\t\t\t\t'<td><span><i></i></span></td>' +\n\t\t\t\t'</tr>';\n\n\t\t\t$(\"#drag tbody tr:last-child\").before(HTML);\n\t\t}\n\n\t\tvar i = $('#drag [data-status=init]').eq(0).index('#drag [data-js=item]');\n\n\t\t$('#drag [data-js=item]').eq(i).children().eq(0).find('span').html(i+1);\n\t\t$('#drag [data-js=item]').eq(i).children().eq(1).find('span').html(name);\n\t\t$('#drag [data-js=item]').eq(i).children().eq(2).find('span').html(size);\n\t\t$('#drag [data-js=item]').eq(i).children().eq(3).find('span i').addClass('red').html('0%');\n\t\t$('#drag [data-js=item]').eq(i).attr('data-status', 'ed');\n\n\t\tfileMap[file.name] = $('#drag [data-js=item]').eq(i).children().eq(3).find('span i');\n\n\t}\n}\n\n//上传时返回的状态\nfunction uploadProgress(file, bytesLoaded, bytesTotal)\n{\n\tfileMap[file.name].html(parseInt((bytesLoaded/bytesTotal)*100)+\"%\");\n}\n\n\n//上传成功\nfunction uploadSuccess(file, serverData, res)\n{\n\tfileMap[file.name].removeClass('red').addClass('op_right').html('');\n}\n\n/**\n * 查找在数组中的位置\n */\nfunction findObjectKey (object, fid){\n\tvar len = object.length; \n\tfor(var i=0; i<len; i++){\n\t\tif(object[i].id == fid){\n\t\t\treturn i;\n\t\t}\n\t}\n\treturn -1;\n}\n\n/**\n * 从全局的文件集合中移除文件，一般上传失败时使用\n * @param array files   文件对象集合  [{},{},{}]\n * @param int fid  要删除的文件id\n * @return 删除后的数组，  其实数组是引用类型可以不返回\n */\nfunction removeFileFromFilesUpload(files, fid){\n\t//console.log(currUploadfile);\n\n\tvar filesUploadKey = -1;\n\t\n\tfilesUploadKey = findObjectKey(files, fid);\n\t//从全局文件中移除\n\tif(filesUploadKey > -1)\n\t\t files.splice(filesUploadKey, 1);\n\n\treturn files;\n}"
  },
  {
    "path": "app/src/main/assets/web/uploadBook/js/html5_fun.js",
    "content": "/**\n * 处理拖拽上传\n */\n\tvar isDragOver\t\t= false;//拖拽触发点\n\tvar fileNumber\t\t= -1; //上传文件编号\n\tvar fileNumberPex\t= \"zyFileUpload_\"; //编号前缀\n\tvar currUploadfile  = {}; //当前上传的文件对象\n\t\n\tvar uploadQueue\t\t= [];//上传队列集合\n\tvar isUploading\t\t= false;//是否正在上传\n\t\n\tvar XHR;\n\ttry{ \n\t\tXHR = new XMLHttpRequest(); \n\t}catch(e){}\n\t\n(function(isSupportFileUpload){\n\t\n\t//不支持拖拽上传，或者 不支持FormData ，显示WiFi表示\n\tif(!isSupportFileUpload){\n\t\t$(\"#drag tbody tr:last-child td span\").html('您的浏览器不支持拖拽上传');\n\t\treturn;\n\t//更换样式\n\t}else{\n\t\t$(\"#drag tbody tr:last-child td span\").html('请将图书或字体拖拽至此即可上传');\n\t}\n\n\taddEvent();\n\t\n\t/**\n\t * 添加事件\n\t */\n\tfunction addEvent(){\n\t\tvar click = $('#click')[0];\n\t\tvar dropArea = $('#drag')[0];\n\t\tclick.addEventListener('change', handleDrop, false);\n\t\tdropArea.addEventListener('dragover', handleDragOver, false);\n\t\tdropArea.addEventListener('dragleave', handleDragLeave, false);\n\t\tdropArea.addEventListener('drop', handleDrop, false);\n\t} \n\n\t/**\n\t * 松开拖拽文件的处理，进行上传\n\t */\n\tfunction handleDrop(evt){\n\t\t\n\t\tevt.stopPropagation();\n\t\tevt.preventDefault();\n\n\t\t$('#drag').removeClass('active');\n\t\t\n\t\tisDragOver = false;\n\t\t\n\t\tvar file={};\n\t\tvar errorMsgs = [];\n\n\t\tvar len = 0;\n\t\tif(typeof (this.files) == 'object'){\n\t\t\tlen = this.files.length;\n\t\t}else{\n\t\t\tlen = evt.dataTransfer.files.length;\n\t\t}\n\n\n\t\tfor(var i=0; i < len; i++){\n\t\t\tfileNumber ++ ;\n\n\t\t\tif(typeof(this.files) == 'object'){\n\t\t\t\tfile = this.files[i];\n\t\t\t}else{\n\t\t\t\tfile = evt.dataTransfer.files[i];\n\t\t\t}\n\n\t\t\t//检测文件\n\t\t\tmsg = checkFile(file);\n\t\t\t//文件可以通过\n\t\t\tif(!msg){\n\t\t\t\tfile.id = fileNumberPex+fileNumber;\n\n\t\t\t\t//添加全局\n\t\t\t\tfilesUpload.push(file);\n\t\t\t\t//添加上传队列\n\t\t\t\tuploadQueue.push(file);\n\t\t\t\t//在页面进行展示\n\t\t\t\tfileQueuedPC(file, 1);\n\t\t\t}else{\n\t\t\t\terrorMsgs.push(msg)\n\t\t\t}\n\t\t}\n\n\t\tif(errorMsgs.length>0){\n\t\t\t//只选择做一个进行上传\n\t\t\tif(len==1){\n\t\t\t\talert(errorMsgs[0]);\n\t\t\t\t\n\t\t\t}else{\n\t\t\t\talert(\"你选择了\"+len+\"个文件，只能上传\"+(len - errorMsgs.length)+\"个文件。\\n请选择可支持文件格式且文件名不能重复。\");\n\t\t\t}\t\t\t\n\t\t}\n\n\t\t//拿出第一个，进行上传\n\t\tif(!isUploading && uploadQueue.length>0) uploadFiles(uploadQueue.shift());\n\n\t\t//清空input内容，防止两次上传文件一样，change事件不触发\n\t\tdocument.getElementById('click').value = '';\n\n\t}\n\n\tfunction handleDragOver(evt){\n\t\t\t\t\n\t\tevt.stopPropagation();\n\t\tevt.preventDefault();\n\t\t//防止多次DOM操作\n\t\tif (!isDragOver) {\n            $('#drag').addClass('active');\n\t\t\tisDragOver = true;\n\t\t}\n\t\t\n\t\t\n\t}\n\t\n\tfunction handleDragLeave(evt){\n\t\t\n\t\tevt.stopPropagation();\n\t\tevt.preventDefault();\n\t\tisDragOver = false;\n\t\t$('#drag').removeClass('active');\n\t}\n\n\n\n\tfunction uploadFiles(file){\n\t\tconsole.log(file);\n\t\t//正在上传\n\t\tisUploading = true;\n\t\t//设置上传的数据\n//        var reader = new FileReader();\n//        reader.readAsDataURL(file);\n//        reader.onload = function (e) {\n//            var data = e.target.result;\n            var fd = new FormData();\n            fd.append(\"fileName\", file.name);\n            fd.append(\"fileData\", file);\n            //设置当前的上传对象\n            currUploadfile = file;\n            if(XHR.readyState>0){\n                XHR = new XMLHttpRequest();\n            }\n\n            XHR.upload.addEventListener(\"progress\", progress, false);\n            XHR.upload.addEventListener(\"load\", requestLoad, false);\n            XHR.upload.addEventListener(\"error\", error, false);\n            XHR.upload.addEventListener(\"abort\", abort, false);\n            XHR.upload.addEventListener(\"loadend\", loadend, false);\n            XHR.upload.addEventListener(\"loadstart\", loadstart, false);\n            XHR.open(\"POST\", config.url);\n    //\t\tXHR.setRequestHeader(\"Content-Type\",\"application/octet-stream\");\n            XHR.send(fd);\n            XHR.onreadystatechange = function() {\n\n                //只要上传完成不管成功失败\n                if (XHR.readyState == 4 ){\n\n                    if(XHR.status == 200){\n                        uploadSuccess(currUploadfile, {}, XHR.status)\n                    }else{\n                        uploadError()\n                    }\n\n                    //进行下一个上传\n                    nextUpload()\n                }\n            };\n//\t\t};\n\t}\n\n\t//请求完成，无论失败或成功\n\tfunction loadend(evt){\n\t//\tconsole.log(\"loadend\",+new Date(),evt);\n\t}\n\t//请求开始\n\tfunction loadstart(evt){\n\t//\tconsole.log(\"loadstart\",evt);\n\t}\n\t\n\t//在请求发送或接收数据期间，在服务器指定的时间间隔触发。\n\tfunction progress(evt){\n\t\tuploadProgress(currUploadfile,  evt.loaded || evt.position , evt.total)\n\t}\n\t\n\t//在请求被取消时触发，例如，在调用 abort() 方法时。\n\tfunction abort(evt){\n\t//\tconsole.log(\"abort\",evt);\n\t}\n\t\n\t//在请求失败时触发。\n\tfunction error(evt){\n\t\t//终止ajax请求\n\t\tXHR.abort();\n\t\tuploadError();\n\t\tnextUpload();\n\t}\n\t\n\t//在请求成功完成时触发。\n\tfunction requestLoad(evt){\n\t//\tconsole.log(\"requestLoad\", +new Date(),evt);\n\t}\n\t\n\t//进行下一个上传\n\tfunction nextUpload(){\n\t\tisUploading = false;\n\t\tif(uploadQueue.length>0){\n\t\t\t uploadFiles(uploadQueue.shift());\t\t\n\t\t}else{\n\t\t\t //米有正在上传的了\n\t\t\t currUploadfile  = {}\n\t\t}\n\t}\n\t\n\t//上传出错误了，比如断网，\n\tfunction uploadError(){\n\t\t//移除全局变量中的，上传出错的\n\t\tremoveFileFromFilesUpload(filesUpload, currUploadfile.id);\n\t\tvar file = currUploadfile;\n\t\tfileMap[file.name].removeClass('red').addClass('op_wrong').html('');\n\t}\n\t\n\t\n\t//对外部注册的函数\n\tvar HTML5Funs = {\n\t\t/**\n\t\t * 取消上传\n\t\t * @param string fid 文件的Id \n\t\t */\n\t\tcancelUpload : function(fid){\n\t\t\t\n\t\t\tvar filesUploadKey = -1;\n\t\t\tvar uploadQueueKey = -1;\n\t\t\t\n\t\t\t\n\t\t\t//从全局中删除文件\n\t\t\tremoveFileFromFilesUpload(filesUpload, fid)\n\t\t\t\n\t\t\t//如果是正在上传的，AJAX取消\n\t\t\tif(currUploadfile.id == fid){\n\t\t\t\tXHR.abort();\n\t\t\t}else{\n\t\t\t\t//从上传队列中移除\n\t\t\t\tremoveFileFromFilesUpload(uploadQueue, fid)\n\t\t\t}\n\t\t}\n\t};\n\t\n\twindow.HTML5Funs = HTML5Funs;\n\t\n\n})(\"FormData\" in window && \"ondrop\" in document.body);"
  },
  {
    "path": "app/src/main/assets/web/vue/assets/BookChapter-BsiFtdIw.css",
    "content": "@charset \"UTF-8\";.title[data-v-d8efefe3]{margin-bottom:57px;font:24px/32px PingFangSC-Regular,HelveticaNeue-Light,Helvetica Neue Light,Microsoft YaHei,sans-serif}p[data-v-d8efefe3]{display:block;word-wrap:break-word;letter-spacing:calc(var(--v3af0ffa9) * 1em);line-height:calc(1 + var(--v316cdf92));margin:calc(var(--v3ab99b0b) * 1em) 0}p[data-v-d8efefe3] img{height:1em}.full[data-v-d8efefe3]{display:block;width:100%}@font-face{font-family:FZZCYSK;src:local(\"☺\"),url(./popfont-WaOB0hHG.ttf);font-style:normal;font-weight:400}@font-face{font-family:iconfont;src:url(./iconfont-PstzbNMW.woff) format(\"woff\")}[data-v-dd7cfcb2] .iconfont,[data-v-dd7cfcb2] .moon-icon{font-family:iconfont;font-style:normal}.settings-wrapper[data-v-dd7cfcb2]{-webkit-user-select:none;user-select:none;margin:-13px;text-align:left;padding:40px 0 40px 24px;background:#ede7da url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyBAMAAADsEZWCAAAAD1BMVEX48dr48Nf58tv379X17NJtIBxUAAACFUlEQVQ4y1XRUZakMAgF0Af2AiDWApDZgHZqAV1nZv9rGh7Rj7Y8McUFEg1wvcMESMNVD/neU8Xcaz7nYYkYlYO6Ti82PBI4BvIEg1aj3wKwRvIMgZsUy5LdhCawPFh1sZs4SrlyN9fQKpv8s5dgZ2eLyqqJiu+WkCmUEybXkm3INS01WAiv0PapJ0CZc0SJQUzcWnZYbOOY20iFD8Bk+/j2A3wNxH7GdShFYS5ff237kXh9I9zSkQmIAhOsOSVfJ6DIXTMDaPnzkRJ92S1BQQmXl5LdirgRLLDdcYqcGPwe3QN4xCBiGNbrqq9wpW1XCecChwaQdVOsRDpPCpeoolPdxeXp3WNB9PHVzWBHlygy4NJCCrFHREv6bDt0VGwJZASkpONmm1UseGeFKAQexgaAkrfYWl3AGxWOLL2AIMBNbCXpktmS3k3vHeYjGCPBa43wJTurO3ZFVpQSJdAZGLoHTyk1upkjxMEaIxum3iIARcCa5kSkFAW5fi1mUlL9eyOsaanFmOMruwvEdE3ZYzsRSzo5ewRLXyVPPEvknt8ij4DvCg2O7xOgBCUprEzV4z1WekSpUgI8DT2mrnSOXKRfQavwuKA1F+tFnMKdJSUpMA7wQAifWRkMgjUKKZE4lBl6MCM4B1pq1P4uIjDE6Pq6rL0FnW1nIFmta5vrSvq/Ch4tpqG/ZNyyWa5jZPktq81eYv8Bt5s4iFITOp4AAAAASUVORK5CYII=) repeat}.settings-wrapper .settings-title[data-v-dd7cfcb2]{font-size:18px;line-height:22px;margin-bottom:28px;font-family:FZZCYSK;font-weight:400}.settings-wrapper .setting-list[data-v-dd7cfcb2]{max-height:calc(70vh - 50px);overflow:auto}.settings-wrapper .setting-list ul[data-v-dd7cfcb2]{list-style:none outside none;margin:0;padding:0}.settings-wrapper .setting-list ul li[data-v-dd7cfcb2]{list-style:none outside none}.settings-wrapper .setting-list ul li i[data-v-dd7cfcb2]{font:12px/16px PingFangSC-Regular,-apple-system,Simsun;display:inline-block;min-width:48px;margin-right:16px;vertical-align:middle;color:#666}.settings-wrapper .setting-list ul li .theme-item[data-v-dd7cfcb2]{line-height:32px;width:34px;height:34px;margin-right:16px;margin-top:5px;border-radius:100%;display:inline-block;cursor:pointer;text-align:center;vertical-align:middle}.settings-wrapper .setting-list ul li .theme-item .iconfont[data-v-dd7cfcb2]{display:none}.settings-wrapper .setting-list ul li .selected[data-v-dd7cfcb2]{color:#ed4259}.settings-wrapper .setting-list ul li .selected .iconfont[data-v-dd7cfcb2]{display:inline}.settings-wrapper .setting-list ul .font-list[data-v-dd7cfcb2],.settings-wrapper .setting-list ul .infinite-loading[data-v-dd7cfcb2]{margin-top:28px}.settings-wrapper .setting-list ul .font-list .font-item[data-v-dd7cfcb2],.settings-wrapper .setting-list ul .font-list .infinite-loading-item[data-v-dd7cfcb2],.settings-wrapper .setting-list ul .infinite-loading .font-item[data-v-dd7cfcb2],.settings-wrapper .setting-list ul .infinite-loading .infinite-loading-item[data-v-dd7cfcb2]{width:78px;height:34px;cursor:pointer;margin-right:16px;border-radius:2px;text-align:center;vertical-align:middle;display:inline-block;font:14px/34px PingFangSC-Regular,HelveticaNeue-Light,Helvetica Neue Light,Microsoft YaHei,sans-serif}.settings-wrapper .setting-list ul .font-list .font-item-input[data-v-dd7cfcb2],.settings-wrapper .setting-list ul .infinite-loading .font-item-input[data-v-dd7cfcb2]{width:168px;color:#000}.settings-wrapper .setting-list ul .font-list .selected[data-v-dd7cfcb2],.settings-wrapper .setting-list ul .infinite-loading .selected[data-v-dd7cfcb2]{color:#ed4259;border:1px solid #ed4259}.settings-wrapper .setting-list ul .font-list .font-item[data-v-dd7cfcb2]:hover,.settings-wrapper .setting-list ul .font-list .infinite-loading-item[data-v-dd7cfcb2]:hover,.settings-wrapper .setting-list ul .infinite-loading .font-item[data-v-dd7cfcb2]:hover,.settings-wrapper .setting-list ul .infinite-loading .infinite-loading-item[data-v-dd7cfcb2]:hover{border:1px solid #ed4259;color:#ed4259}.settings-wrapper .setting-list ul .font-size[data-v-dd7cfcb2],.settings-wrapper .setting-list ul .read-width[data-v-dd7cfcb2],.settings-wrapper .setting-list ul .letter-spacing[data-v-dd7cfcb2],.settings-wrapper .setting-list ul .line-spacing[data-v-dd7cfcb2],.settings-wrapper .setting-list ul .paragraph-spacing[data-v-dd7cfcb2]{margin-top:28px}.settings-wrapper .setting-list ul .font-size .resize[data-v-dd7cfcb2],.settings-wrapper .setting-list ul .read-width .resize[data-v-dd7cfcb2],.settings-wrapper .setting-list ul .letter-spacing .resize[data-v-dd7cfcb2],.settings-wrapper .setting-list ul .line-spacing .resize[data-v-dd7cfcb2],.settings-wrapper .setting-list ul .paragraph-spacing .resize[data-v-dd7cfcb2]{display:inline-block;width:274px;height:34px;vertical-align:middle;border-radius:2px}.settings-wrapper .setting-list ul .font-size .resize span[data-v-dd7cfcb2],.settings-wrapper .setting-list ul .read-width .resize span[data-v-dd7cfcb2],.settings-wrapper .setting-list ul .letter-spacing .resize span[data-v-dd7cfcb2],.settings-wrapper .setting-list ul .line-spacing .resize span[data-v-dd7cfcb2],.settings-wrapper .setting-list ul .paragraph-spacing .resize span[data-v-dd7cfcb2]{width:89px;height:34px;line-height:34px;display:inline-block;cursor:pointer;text-align:center;vertical-align:middle}.settings-wrapper .setting-list ul .font-size .resize span em[data-v-dd7cfcb2],.settings-wrapper .setting-list ul .read-width .resize span em[data-v-dd7cfcb2],.settings-wrapper .setting-list ul .letter-spacing .resize span em[data-v-dd7cfcb2],.settings-wrapper .setting-list ul .line-spacing .resize span em[data-v-dd7cfcb2],.settings-wrapper .setting-list ul .paragraph-spacing .resize span em[data-v-dd7cfcb2]{font-style:normal}.settings-wrapper .setting-list ul .font-size .resize .less[data-v-dd7cfcb2]:hover,.settings-wrapper .setting-list ul .font-size .resize .more[data-v-dd7cfcb2]:hover,.settings-wrapper .setting-list ul .read-width .resize .less[data-v-dd7cfcb2]:hover,.settings-wrapper .setting-list ul .read-width .resize .more[data-v-dd7cfcb2]:hover,.settings-wrapper .setting-list ul .letter-spacing .resize .less[data-v-dd7cfcb2]:hover,.settings-wrapper .setting-list ul .letter-spacing .resize .more[data-v-dd7cfcb2]:hover,.settings-wrapper .setting-list ul .line-spacing .resize .less[data-v-dd7cfcb2]:hover,.settings-wrapper .setting-list ul .line-spacing .resize .more[data-v-dd7cfcb2]:hover,.settings-wrapper .setting-list ul .paragraph-spacing .resize .less[data-v-dd7cfcb2]:hover,.settings-wrapper .setting-list ul .paragraph-spacing .resize .more[data-v-dd7cfcb2]:hover{color:#ed4259}.settings-wrapper .setting-list ul .font-size .resize .lang[data-v-dd7cfcb2],.settings-wrapper .setting-list ul .read-width .resize .lang[data-v-dd7cfcb2],.settings-wrapper .setting-list ul .letter-spacing .resize .lang[data-v-dd7cfcb2],.settings-wrapper .setting-list ul .line-spacing .resize .lang[data-v-dd7cfcb2],.settings-wrapper .setting-list ul .paragraph-spacing .resize .lang[data-v-dd7cfcb2]{color:#a6a6a6;font-weight:400;font-family:FZZCYSK}.settings-wrapper .setting-list ul .font-size .resize b[data-v-dd7cfcb2],.settings-wrapper .setting-list ul .read-width .resize b[data-v-dd7cfcb2],.settings-wrapper .setting-list ul .letter-spacing .resize b[data-v-dd7cfcb2],.settings-wrapper .setting-list ul .line-spacing .resize b[data-v-dd7cfcb2],.settings-wrapper .setting-list ul .paragraph-spacing .resize b[data-v-dd7cfcb2]{display:inline-block;height:20px;vertical-align:middle}.night[data-v-dd7cfcb2] .theme-item,.night[data-v-dd7cfcb2] .selected{border:1px solid #666}.night[data-v-dd7cfcb2] .moon-icon{color:#ed4259}.night[data-v-dd7cfcb2] .font-list .font-item,.night[data-v-dd7cfcb2] .font-list .infinite-loading-item,.night .infinite-loading .font-item[data-v-dd7cfcb2],.night .infinite-loading .infinite-loading-item[data-v-dd7cfcb2],.night[data-v-dd7cfcb2] .resize{border:1px solid #666;background:#2d2d2d80}.night[data-v-dd7cfcb2] .resize b{border-right:1px solid #666}.day[data-v-dd7cfcb2] .theme-item{border:1px solid #e5e5e5}.day[data-v-dd7cfcb2] .selected{border:1px solid #ed4259}.day[data-v-dd7cfcb2] .moon-icon{display:inline;color:#fff3}.day[data-v-dd7cfcb2] .font-list .font-item,.day[data-v-dd7cfcb2] .font-list .infinite-loading-item,.day .infinite-loading .font-item[data-v-dd7cfcb2],.day .infinite-loading .infinite-loading-item[data-v-dd7cfcb2]{background:#ffffff80;border:1px solid rgba(0,0,0,.1)}.day[data-v-dd7cfcb2] .resize{border:1px solid #e5e5e5;background:#ffffff80}.day[data-v-dd7cfcb2] .resize b{border-right:1px solid #e5e5e5}@media screen and (max-width: 500px){.settings-wrapper i[data-v-dd7cfcb2]{display:flex!important;flex-wrap:wrap;padding-bottom:5px!important}}.selected[data-v-a892cd6d]{color:#eb4259}.wrapper[data-v-a892cd6d]{display:flex}.wrapper .cata-text[data-v-a892cd6d]{width:100%;margin-right:26px;overflow:hidden;white-space:nowrap;text-overflow:ellipsis}.cata-wrapper[data-v-6cab38af]{margin:-16px;padding:18px 0 24px 25px}.cata-wrapper .title[data-v-6cab38af]{font-size:18px;font-weight:400;font-family:FZZCYSK;margin:0 0 20px;color:#ed4259;width:fit-content;border-bottom:1px solid #ed4259}.cata-wrapper[data-v-6cab38af] .data-wrapper .cata{height:40px;cursor:pointer;font:16px/40px PingFangSC-Regular,HelveticaNeue-Light,Helvetica Neue Light,Microsoft YaHei,sans-serif}.cata-wrapper .night[data-v-6cab38af] .cata{border-bottom:1px solid #666}.cata-wrapper .day[data-v-6cab38af] .cata{border-bottom:1px solid #f2f2f2}[data-v-fff9fad7] .pop-setting{margin-left:68px;top:0}[data-v-fff9fad7] .pop-cata{margin-left:10px}.chapter-wrapper[data-v-fff9fad7]{padding:0 4%;overflow-x:hidden}.chapter-wrapper[data-v-fff9fad7] .no-point{pointer-events:none}.chapter-wrapper .tool-bar[data-v-fff9fad7]{position:fixed;top:0;left:50%;z-index:100}.chapter-wrapper .tool-bar .tools[data-v-fff9fad7]{display:flex;flex-direction:column}.chapter-wrapper .tool-bar .tools .tool-icon[data-v-fff9fad7]{font-size:18px;width:58px;height:48px;text-align:center;padding-top:12px;cursor:pointer;outline:none}.chapter-wrapper .tool-bar .tools .tool-icon .iconfont[data-v-fff9fad7]{font-family:iconfont;width:16px;height:16px;font-size:16px;margin:0 auto 6px}.chapter-wrapper .tool-bar .tools .tool-icon .icon-text[data-v-fff9fad7]{font-size:12px}.chapter-wrapper .read-bar[data-v-fff9fad7]{position:fixed;bottom:0;right:50%;z-index:100}.chapter-wrapper .read-bar .tools[data-v-fff9fad7]{display:flex;flex-direction:column}.chapter-wrapper .read-bar .tools .tool-icon[data-v-fff9fad7]{font-size:18px;width:42px;height:31px;padding-top:12px;text-align:center;align-items:center;cursor:pointer;outline:none;margin-top:-1px}.chapter-wrapper .read-bar .tools .tool-icon .iconfont[data-v-fff9fad7]{font-family:iconfont;width:16px;height:16px;font-size:16px;margin:0 auto 6px}.chapter-wrapper .chapter[data-v-fff9fad7]{font-family:Microsoft YaHei,PingFangSC-Regular,HelveticaNeue-Light,Helvetica Neue Light,sans-serif;text-align:left;padding:0 65px;min-height:100vh;width:670px;margin:0 auto}.chapter-wrapper .chapter .content[data-v-fff9fad7]{font-size:18px;line-height:1.8;font-family:Microsoft YaHei,PingFangSC-Regular,HelveticaNeue-Light,Helvetica Neue Light,sans-serif}.chapter-wrapper .chapter .content .bottom-bar[data-v-fff9fad7],.chapter-wrapper .chapter .content .top-bar[data-v-fff9fad7]{height:64px}.day[data-v-fff9fad7] .popup{box-shadow:0 2px 4px #0000001f,0 0 6px #0000000a}.day[data-v-fff9fad7] .tool-icon{border:1px solid rgba(0,0,0,.1);margin-top:-1px;color:#000}.day[data-v-fff9fad7] .tool-icon .icon-text{color:#0006}.day[data-v-fff9fad7] .chapter{border:1px solid #d8d8d8;color:#262626}.night[data-v-fff9fad7] .popup{box-shadow:0 2px 4px #0000007a,0 0 6px #00000029}.night[data-v-fff9fad7] .tool-icon{border:1px solid #444;margin-top:-1px;color:#666}.night[data-v-fff9fad7] .tool-icon .icon-text{color:#666}.night[data-v-fff9fad7] .chapter{border:1px solid #444;color:#666}.night[data-v-fff9fad7] .popper__arrow{background:#666}@media screen and (max-width: 776px){.chapter-wrapper[data-v-fff9fad7]{padding:0}.chapter-wrapper .tool-bar[data-v-fff9fad7]{left:0;width:100vw;margin-left:0!important}.chapter-wrapper .tool-bar .tools[data-v-fff9fad7]{flex-direction:row;justify-content:space-between}.chapter-wrapper .tool-bar .tools .tool-icon[data-v-fff9fad7]{border:none}.chapter-wrapper .read-bar[data-v-fff9fad7]{right:0;width:100vw;margin-right:0!important}.chapter-wrapper .read-bar .tools[data-v-fff9fad7]{flex-direction:row;justify-content:space-between;padding:0 15px}.chapter-wrapper .read-bar .tools .tool-icon[data-v-fff9fad7]{border:none;width:auto}.chapter-wrapper .read-bar .tools .tool-icon .iconfont[data-v-fff9fad7]{display:inline-block}.chapter-wrapper .chapter[data-v-fff9fad7]{width:100vw!important;padding:0 20px;box-sizing:border-box}}\n"
  },
  {
    "path": "app/src/main/assets/web/vue/assets/BookChapter-Cs3stH93.js",
    "content": "import{d as ce,a4 as Je,U as Fe,a9 as Pe,o as g,e as p,F as se,h as t,z as K,R as le,u as a,aa as Z,C as l,M as D,ab as Me,N as be,y as Q,f as H,ac as Ge,w as z,ad as qe,ae as Ze,H as fe,af as Le,A as Ye,g as q,P as pe,a8 as Re,n as R,ag as je,a6 as Ke,ah as Xe,V as _e,Q as De,a7 as $e,ai as et,aj as tt,c as ot}from\"./vendor-KSDcS24u.js\";import{u as me,i as Qe,A as ne,_ as Ae,c as Ve}from\"./index-Wr40-hHf.js\";import{u as nt}from\"./loading-C4J6hIxs.js\";const st=(c,s,A,f)=>(c/=f/2,c<1?A/2*c*c+s:(c--,-A/2*(c*(c-2)-1)+s)),at=()=>{let c,s,A,f,n,w,m,y,h,F,P,x,S;function V(){let u=c.scrollTop||c.scrollY||c.pageYOffset;return u=typeof u>\"u\"?0:u,u}function U(u){const i=u.getBoundingClientRect().top,d=c.getBoundingClientRect?c.getBoundingClientRect().top:0;return i-d+A}function C(u){c.scrollTo?c.scrollTo(0,u):c.scrollTop=u}function I(u){F||(F=u),P=u-F,x=w(P,A,y,h),C(x),P<h?requestAnimationFrame(I):b()}function b(){C(A+y),s&&m&&(s.setAttribute(\"tabindex\",\"-1\"),s.focus()),typeof S==\"function\"&&S(),F=!1}function v(u,i={}){switch(h=i.duration||1e3,n=i.offset||0,S=i.callback,w=i.easing||st,m=i.a11y||!1,typeof i.container){case\"object\":c=i.container;break;case\"string\":c=document.querySelector(i.container);break;default:c=window}switch(A=V(),typeof u){case\"number\":s=void 0,m=!1,f=A+u;break;case\"object\":s=u,f=U(s);break;case\"string\":s=document.querySelector(u),f=U(s);break}switch(y=f-A+n,typeof i.duration){case\"number\":h=i.duration;break;case\"function\":h=i.duration(y);break}requestAnimationFrame(I)}return v},te=at(),it=[\"data-chapterpos\"],rt=[\"src\"],lt=[\"innerHTML\"],ct=ce({__name:\"ChapterContent\",props:{chapterIndex:{},contents:{},title:{},spacing:{},fontFamily:{},fontSize:{}},emits:[\"readedLengthChange\"],setup(c,{expose:s,emit:A}){Je(i=>({v3af0ffa9:y.spacing.letter,v316cdf92:y.spacing.line,v3ab99b0b:y.spacing.paragraph}));const f=me(),n=l(()=>f.config.readWidth),w=l(()=>f.config.fontSize),m=l(()=>f.readingBook.bookUrl),y=c,h=/<img[^>]*src=['\"]([^'\"]*(?:['\"][^>]+\\})?)['\"][^>]*>/g,F=i=>i.replace(h,(d,B)=>{if(Qe(B)){const W=ne.getProxyImageUrl(m.value,B,w.value*2);return d.replace(B,W)}return d}),P=i=>{const d=/<img[^>]*src=['\"]([^'\"]*(?:['\"][^>]+\\})?)['\"][^>]*>/,B=i.match(d)[1];return Qe(B)?ne.getProxyImageUrl(m.value,B,n.value):B},x=i=>{var B;const d=(B=i.target)==null?void 0:B.getAttribute(\"src\");d!=null&&d.length>0&&(i.target.src=ne.getProxyImageUrl(m.value,d,n.value))},S=i=>{var d;((d=i.target)==null?void 0:d.tagName)===\"IMG\"&&x(i)},V=i=>i.replace(h,\" \").length,U=l(()=>{let i=-1;return Array.from(y.contents,d=>(i+=V(d)+1,i))}),C=D(),I=D();s({scrollToReadedLength:i=>{if(i===0)return;const d=U.value.findIndex(B=>B>=i);d!==-1&&Me(()=>{te(I.value[d],{duration:0})})}});let v=null;const u=A;return Fe(()=>{v=new IntersectionObserver(i=>{for(const{target:d,isIntersecting:B}of i)B&&u(\"readedLengthChange\",y.chapterIndex,parseInt(d.dataset.chapterpos))},{rootMargin:`0px 0px -${window.innerHeight-24}px 0px`}),v.observe(C.value),I.value.forEach(i=>{v.observe(i)})}),Pe(()=>{v==null||v.disconnect(),v=null}),(i,d)=>(g(),p(se,null,[t(\"div\",{class:\"title\",\"data-chapterpos\":\"0\",ref_key:\"titleRef\",ref:C},K(c.title),513),(g(!0),p(se,null,le(c.contents,(B,W)=>(g(),p(\"div\",{key:W,ref_for:!0,ref_key:\"paragraphRef\",ref:I,\"data-chapterpos\":a(U)[W]},[/^\\s*<img[^>]*src[^>]+>$/.test(String(B))?(g(),p(\"img\",{key:0,class:\"full\",src:P(B),onErrorOnce:x,loading:\"lazy\"},null,40,rt)):(g(),p(\"p\",{key:1,style:Z({fontFamily:c.fontFamily,fontSize:a(w)}),innerHTML:F(B),onErrorCapture:S},null,44,lt))],8,it))),128))],64))}}),At=Ae(ct,[[\"__scopeId\",\"data-v-d8efefe3\"]]),dt=\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyAgMAAABjUWAiAAAADFBMVEXr5djn4dTp49bt59rT6LKxAAACnElEQVQozw3NUUwScRzA8d8R6MF8YMIx8uk47hDSJbj14IPzOGc7jPLvwTGg5uAYDbe2tt56cLtznvEnS6yDqCcEaWi91DvrbLJZz7b1aFtz1aO+2OZWvn+/+4CHeB6BMYaqBLfjPNRY6RFT2JJYby+uAk4WUTrtlmJ4hgPYb2q1XGDQjaK8pgJHvqNaAX+KyuIkDXpgQinb46nOulnn4b5laUHTxLfseeArAoNOeJlOIjdoal0n1FA7tKFv5roK+YaHOqP3P0XyKHPHY+MhTRe5uCZnKhtJKw2eSrSoBDPLtpZuNcFNJcFyiCMxOaaHIfXz1e8HQbWLySrBQ4x0x1qlhnHlnz2HQEC6TNb0gTHXa7IKhcaHqkE015hk9whA0YeWiLIXf7Fa2CZo3DjqjB4tTuF8jIcbfcEx5z/w4sXpQhXW+ju0cqh7icTFmRMaG+v6CIvTjcSpHcH8JEsF3EPh3fRthYdVLLgI2fWXm85/pGFE4l046s70L+yKCcirGFR+jbpy3kMmiCGHrSezVONsn1RBixncyk2PcVWk7DlgxHo8iZwDyq5uAUD854dZhdIFYzKoQig2haUKi1lVufz2RZUZPZ41n/hrOQB6h0Hhg8I367FNoEHgeM/KY7szSeQwD8q2WE3HM35ZLl0K1MJiOtHIkBclRQUwZnyOWcNsRQQgVLj1PSqkjF9DsoOSaSg3iinKzvfmgsNFFfpP/2T3GLGvL4fHEfwIX1sVvXcPqLztehWGcfn9nI2U9nTfCgJPe/jFPLZwgVEzimBgAm0VIyK2tt1cE/AzQdLK+SxLSQ4aDCZnnId94OG2S1XwvnTbNk/ZnhyRCQT+sZM6z9g6LXL1BOBe+zJySiFkHAINCtnQokbCJ/apCv0foqPiZVfhpywAAAAASUVORK5CYII=\",ut=\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyAgMAAABjUWAiAAAACVBMVEX28ef48+n69esoK7jYAAAB4UlEQVQozw2OsW4bQQxEhwLXkDrysGdEqRRgVShfQQq8wOr2jD0jSpXCLvwXbtKfADlFqgSwC/9ljqweZgYzQFnb/QGepYhA9jzmTc1WaSEtQpbFgjWATI00ZZtIckXx8q2Oe5yEByBy+RHOTcM+VVTadULsvxvRC/q8WTwgcWGD+Mnaqa0oy2gw2pKFzK+PzEsus5hP9AHojKslVynLlioVTBEN8cjDNnZoR1uMGTiZAAN47HxMtEkGUE9b8HWzkqNX5Lpk0yVziAJOs46rK1pG/xNuXLjz95fSDoJE5IqG23MAYPtWoeWPvfVtIV/Ng9oH3W0gGMPIOqd4MK4QZ55dV61gOb8Zxp7I9qayaGxp6Q91cmC0ZRdBwEQVHWzSAanlZwVWc9yljeTCeaHjBVvlPSLeyeBUT2rPdJegQI103jVS3uYkyIx1il6mslMDedZuOkwzolsagvPuQAfp7cYg7k9V1NOxfq64PNSvMdwONV4VYEmqlbpZy5OAakRKkjPnL4CBv5/OZRgoWHBmNbxB0LgB1I4vXFj93UoF2/0TPEsWwV9EhbIiTPqYoTHYoMn3enTDjmrFeDTIzaL1bUC/PBIMuF+vSSYSaxoVt90EO3Gu1zrMuMRGUk7Ffv3L+A931Gsb/yBoIgAAAABJRU5ErkJggg==\",gt=\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyAgMAAABjUWAiAAAADFBMVEX6+fP8+/X+/ff///kbczPAAAACeElEQVQozxXHQUgUUQAG4P8936yzs6VvZNZmN9QxVxiF9OLBoOjtOC6rQq6ygXjI2fCQBdXBg4egtzFGdqkoI+zgBFbqkm3hQSxhFYLotOcubeKhOnVYoqQy+m4f5g5TvpX0xHLbLY9j8SMhJp+Jk4LfAUS2kVRIjILmnwGBTX42PhCVlDJQkIiy2nWAvaJ1h+oFIpJ0hMSYVbyyrgDWshcMpMyL1brPDQKWmduO+KTJ6XeXAMK9Yc3FpD7atyNwg6kt5XgFpLPhjUTFSYVn2abDiugGShwD8JTVRJVo/2ecuKtRb/qc4BK+9TboFfokog4T2Fn6Oqdnsjk90NMS76Rji6E0NmwkPBAZ4Xbkw8KoDAkAbEhkc78e9omxxgxg6qa5HvMv+UZbCV0qmHnSHKl5TxeA2XTCGWekR581mwC5crBH81PznASqB9va3TbkYAjJPLfg5uBfXaJgIgIBv9eessRIhxe7PA7kj6uUMeMaQ/OEQOYRaaHlqH2Gxwsl6E/pwVY5FH7uCypBZPKvDQyVziYBrAkMURe2MOOOxG/eQpp5PF+bFzUV5HtPj9GeiVSNZDELleifYTp9NAjsoiXg4cW+4ZORkdSMB/B74aAdjhsVakhgkugsbmqcDSLEoWp8zRjrux3tli6Q5uM3E+maT99Wy0RiP7tboiuRZle2c6CYeL2kcUc1KvPtQKucogMadKVTQOJYCeyCYlhQQ/Q7Etfd/vBygy9iqy+LyHeF46saCYvW6ingsbA9RBWtdi8GgUXW+oQx9/wP6bAAX1TWeV+CbShZDlQ9xT6SoSxZmKRAkmXb60kzEzkRF+Ccb94BGspGJoN/UzmyR4wjXHAAAAAASUVORK5CYII=\",pt=\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyCAMAAAAp4XiDAAAATlBMVEXdzaHh0KPgz6LdzKDezqLczJ7ezZ/fz6Dcy5zi0aXdzZ3fz6Tfz57h0KDg0aLcyZrg0KXi0qPfzZ3j06bh0qbdyJbfzJrhz5/cxpLZwo0vDconAAAFn0lEQVRIxxyPW5LjMAwDAT5FybLl2JnM3P+i6+wXWVC1GoQGaD0h4XM3Q5o4T0HgABHBi6pZ4CDXXcUOFd6VhqC3Kch4EI8w9oMXwvU6m5LOOvcxKMOhuu8i5+5cMjcgb0t4F2uvOoeI3/MlT4IqsbtM9UG2AGSXUOsxzPevnXzK1CSHytZLvx7VdQmUcJsJCxJh2nmHW12Qod1qPjt8pih47uQ9aGpoNWF+yElCt60oH7vdIU/MnlRPSBLC/VwqxcKR8PFqnADN9ih5ufqnTlG9KwCofvs7kKYqOPHTNMQ93j9qNImFw9vjHPZ0F1m8hUUVB/Q/TrRYDMXr9++APMFARAt6sPh6wVAXzxUGhZsFUwCNfPZ8/72TAHebAhvuOuT3gO1Vn5d9Jd5sBRkg0p2seL9B7ulkjFJFIt9HPpLzdSzzMP3UcodAfMqC6pBuET2heHK1itZf1GZ1bi0BwOSxiCS8f/JBHMPMM4XCu3Mt1uz9lJbDJRqsKDZuikzkvskQEz6hanfDfO494azY5JpqPqOF1RhxD9XYEdaNxiqWqakKgmPfmrsta8KAiwF4HBxGVUJAgeSqQaiRRZJ7D2jedhw5t1CIAKxag0CBA60BpoBE6DcUi8O5AuM4pLfN0kHLmeu2B4e6HofqbgxsTWUw3PAODqa1oDtyzgXBlusi1KFdclMPE8O3jvLJ8RNi5/RxDQVzVmXA233XQ4KummunfxvLOZo+iH37964YjP06995CTdu9hsvErqJNzmf4wTrZ5DL7+qW9EoLnadrx67b8dUtrJnBXaT1N1uvPaYRKpWkq52xNsMN7vv4Sdryt/f4MhQoMCKnvVxikai1CQ6ZsnwJDc8+3Y/z8HcfvYQNq66pnAu1Hwa+3KNSwbNu8h3nDPqTl9fl7tx8fBhFfdS0o0F3JUKEZtZG9b/LZEM95lzaR30OnWPzroMxyZYdBIMoMnpN0J+m7/40+/P4soFSUjgzE7yY5zrMJuoZv0CmpVguYx1pprfb5HOviRVhHUVi/352shxCYrYBZxGtVaxiAz/MsaGSIsB7R1t4zJXH//n7RTTQQwxqcGEqEvklFHUgiO2GvJV+jAIPR+N29usWDoiSOVrN3XuqT1egQJAAU9EwslVJC8u0rGcy+WPqktJhjfMpatIG6CDAb0v5H34MGKqiVRue7GGLZ9Otxtt4JIrAhxBDwDuqI9JavcO0A7GlqFt219tH/bln9jBXzaKWAEqJV0CBxs5TwM8EvUPHaa8S86vN303MVWOsl3goDBHPWSoQ9c0kQmCKljfsKNH1+ofEOHW8a9a7glZGS8fPieL/SRSs0LAhI4FDTnXs1QYtubv2+IXPZpHB4bhivRexBkYKsSrYXNjvMUbVXpVJ+N6haV72c1k2zrnv5IYBMJBYTSZx0KTkoM3vY93rU/qs7zHplc/3d2ACadhFWByrn9LUk2IWb5JywvawTQc3F0iz+lgsBmInAIemBJtft2plKIlAFOgcroigrG2XlDsAzywQECNyaI8yr2ogoh7D4qJOYmZBzQgoZAM1PAcB8sDrr1uE5CDMR+nWSSVUGUCHAs8Vd21HOE0FzNj37pX0sLp9p3K8k++xxpkmzDxK64rmTSJnDUuIgTeslui6lg92jonZXI4jqNiUuzN4IagcKMjCniMGCODoo8T4tGDprn2hRww+NrnYiCwokd9iiWrkmbRfXYGLAoZrjO1lVQKExjUy5fIkgJURmz2uGFdASwwlWx5gDVTMK7hP6ISRVsFbYNmqtZL9MQtio285PaekyzDhZmtdexCYB0SZcTmBdhvdbmAEonk8hwcHQuZN1kVqrhyKoHHsnQhQAjF7SG533Da2S4LGjx1LoZqp7XeKQLDUBmYmydG0NQHpMeR5lRIRQc1PQ2ASMQflF4YBDMt0/GFlEHeRwCcEAAAAASUVORK5CYII=\",ft=\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyBAMAAADsEZWCAAAALVBMVEXx58b168ny6Mjz6sn06sf27Mvw5sTz6cbw5cLy58T37svv47/168v37s7t4Ltrv0//AAAEjUlEQVQ4yw2Ty2sTURxGf3dmOqmPxb0zmaStCnfmZpL6gpmbxIpUSMZGrSJkxsZiVZimNVaqMklrUnWTRq2KIDFWWx+IFrIRFxXEB4KIgqu6EBdu7M6FIPg32PW3+DhwDmBaYrK56KP4HGIsvg/uvOV0wK+qgBMlO9BujuH4DSJlOseqV5a/BEF97gt0ChyIPqBhXI9BtqtIB8vJB/LdCQ3OVjaLNX0g7+OmoI4e7nkemAqX6o8vg0yyQAyQS7IfgvFbI+6QyI3R4KELxw7kwM2ooQfyQigYnwY5MZbMlHI1DvnQVCoVcrt+R+bO7vPDif3ybNajwqAAe443dpfDsPt379VMWZzGRuqM79mQF+DUz9nt74bQ8J/O80MtVR51U02JKKmTCvTzLVf+vuxP/aHnPo9+2bW+zVsJ0Y630/CrfzX+b+UL+7O68Rczv+7lrMh5etfKXvhc2rk6KforxuoO2xB2tcxKfeXHt18rHOiHI/0RRjW/YGRDkHiwo3nzqL60o58C/bgRuaj7vk+QOwOhpnFNdjuWpKMCGP8Yapu9Ty5FTHKQLGSEFikjd9ADwP9ciaNNjc5qMH6w50AF/LKOsOYqsOG9GjKgc7ZXolqntm6fysJ6Ma6ll2CiqmOgE6O7x1wXExklbeqMYcwsmJmOoigt8SBg2WfilDSsAZJcBxDcrqtBXzFQJqZNHfscyIhoZlygAtyYAceah+elrFbI+46gEHDGiW878Kj7JpWyfhg6iyRMymV1MKBSeVpfgLHIohyTojI6sRyK1VpcqzVZeEBLOnA9unhGKUXPJDYtV9Dxuz4iA5xSkSWhCJdAiJR9PHlvfvbntbrR14FDqUNRAYDJmSnv3oKxuz5+7fiblgVJyYLTbgUM05P7LESkoXvyWNfb0aUU6FZizgQIa25VqKQZqFrk6v6BsqqIHlQmkQ9KrBhkC20/DrFsAFEEYLjM+lj2wYHXCwnNvZQR42XJ2iVK+UBXnI+OBE6oXpUUHiQ1yg0MhA03iwGbnOdQYc1CMiPIPQrCQJFH4L4BMFktAtKd9PN5gnU2Gra4KuK+V+mjtBRpAGIqDVe4wnSnajiFGO5d7smvhVQEMEYwqshrENIEaY7YeblJYtsb3QhAHWZCEKK67swwPMKw0If1Ta+6DgHmlgPzcUTSbi3rrv1Y64/BYEMPQ5SDHUOR022B4QRF6xLUPAaPX/V4IDI5N2BMwx4LqO1uO4j6uW7NvM7lATqGAxY/ZHVgoGZbu7SvkNR75x6qGSB23FdouENVwN7sCbewTdsXGrrnQ5ZZKOCOFtMTIzxlPu6eYmtL+nMFmoK7OeXajn86r9sqWbfmvHC4IagE5qfCPGZvLSq5F55hHIxJFa4/vRxHBlz0og4TojU1l/MOHJX17lybdF0mQhFO44JYUNt3UA473IXw/iPfDWtKG5oFSXIF5iU/VnyDSjxxeDk3jAXRyVyGTNB9FxH9qcFDNJpVbt2y9LytUXkK7Py6+z1RezHQqnoY8XcLimmd8dCnBhQCuaGpJCq3SoIlmYvLz8UkWhJw7T8k+Db/DYEKwgAAAABJRU5ErkJggg==\",mt=\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyBAMAAADsEZWCAAAAD1BMVEX48dr48Nf58tv379X17NJtIBxUAAACFUlEQVQ4y1XRUZakMAgF0Af2AiDWApDZgHZqAV1nZv9rGh7Rj7Y8McUFEg1wvcMESMNVD/neU8Xcaz7nYYkYlYO6Ti82PBI4BvIEg1aj3wKwRvIMgZsUy5LdhCawPFh1sZs4SrlyN9fQKpv8s5dgZ2eLyqqJiu+WkCmUEybXkm3INS01WAiv0PapJ0CZc0SJQUzcWnZYbOOY20iFD8Bk+/j2A3wNxH7GdShFYS5ff237kXh9I9zSkQmIAhOsOSVfJ6DIXTMDaPnzkRJ92S1BQQmXl5LdirgRLLDdcYqcGPwe3QN4xCBiGNbrqq9wpW1XCecChwaQdVOsRDpPCpeoolPdxeXp3WNB9PHVzWBHlygy4NJCCrFHREv6bDt0VGwJZASkpONmm1UseGeFKAQexgaAkrfYWl3AGxWOLL2AIMBNbCXpktmS3k3vHeYjGCPBa43wJTurO3ZFVpQSJdAZGLoHTyk1upkjxMEaIxum3iIARcCa5kSkFAW5fi1mUlL9eyOsaanFmOMruwvEdE3ZYzsRSzo5ewRLXyVPPEvknt8ij4DvCg2O7xOgBCUprEzV4z1WekSpUgI8DT2mrnSOXKRfQavwuKA1F+tFnMKdJSUpMA7wQAifWRkMgjUKKZE4lBl6MCM4B1pq1P4uIjDE6Pq6rL0FnW1nIFmta5vrSvq/Ch4tpqG/ZNyyWa5jZPktq81eYv8Bt5s4iFITOp4AAAAASUVORK5CYII=\",vt=\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyAgMAAABjUWAiAAAADFBMVEXN383Q4tDP4c/R5NEInCCXAAACVElEQVQozw3Hv2sTYRwH4M/79pJ7bZL2bXqtERJ97zjUpbZDhg6pfC8qibi8hLR0EaJ0EFxaCSWDxjfpj1zrYBcRBKE6SAfBJWsx9i8IQfdQxDlKtA6t2OnhQfN3lbG7ytYRywF8rVoPCNO0X2sQOKDpAnSDK2VwkHgmh5yLGT8qASt+2KofnNt2Xg1gf1UF8AoM6052cRMNaloLZb7RKQGrKKji2OefsZF+VqIvos5ZLVIZCX61JcwUdk56wASVkgQvzPfvmT2twTSwyYaC/Pl/UhAHorFhBgZtL6XdAZRp1tkPwC1NLa9CWs5prLhI85NBQsLdXvjDymG3/EbYfQhVNYqc3TtktQhWLY3ko0QsdMbSEp+64v0NfxyqLbIGdh6M2xHHlLBGqKTyQo4E/nebBgBfe1GpdeywYXc8CT7D3cKXuMXkBy4xN6o5OuKamYp3DVI6uccO9lxgd2CAlJgI2BGgaAgIJV/TYwKqu3WFccjbMuA+bVkWgS2bfnlRbD1Eb1sDyWMmjKYIBgGAWbqKRicfvzBkBIz3V5AKnguWdglQEysQsSuVzOg6ALy1pitA5ykGCsc857BRYcgCSZyFOdvoOigSGoPc5Ta73mgxshIcQE5sHMHd9D7yqITw7JO+GHVMxjhzYLcKPSEgmz3fU+BRy3iYNtiXLaBssCW8KguReqkQOTb3MStV0Ugt4U1eIs1RZWRII6Ww8xeNNItyGGQI4ZMlpg/3lQtkl2JFnBp1imRyFe0kK2Id3PCslMgiQNMS77gvFeDhG3cSkYvheeg/e7ClIh5oh+IAAAAASUVORK5CYII=\",Bt=\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyAgMAAABjUWAiAAAADFBMVEXh7eHl8eXj7+Pn8+eTbH1KAAACPElEQVQozxWPQWrbQABF/0xn3JFKQRTZOIuUsbCCbOgdRoYEOauxkYPcTRyTlPQWIxEltrsRwQ6hK9nEQek6F+gNTE/Q3qLLusv34cN7SH3mFicdYW4gNIhJWXPBRVXzjcFD0IqeU4o4PRbAIVjyico0vJpIifqPfL80QN9DAQY5ucRHE/hpHxBldXe9GilaHKcKMlj6pho2zXgkNdBl0oJ8kiF1DSiJF1ZHBJkQr0Dbux/5I42Zp4cFahJDFGeW6/QjBwmFY/Q7vZ2SnoOdW2parv/Cnm81+m0xrEfiVXQ3W4nOXIqVYi3l6AAQBwMFkViVBANMto4enXHPNTkHBB0oVj4r5vHzCWayrgBvxtygDlDB2CNDjd80ZInY69aKVYZcfJ8DW+fWuc+syEODALx+ojqoafHsthTI+ZW27PGpIeo/cR6YKcbqIuIFhHmBrzAovzIOOJk1ucvcDzrMRYGVBH2yvcAOf0KiKwfRovBI3tm/kW1eemtfNWwIIXE2mJNhvoszfmMBfRCv0OPwd2321uDW3nx2q/BDxFVeoN1g7a6Im8yRnoawa8kbdXnU0cHeTMxKfZGlJgvLb3sKsxgglQnDdAfvj9LUnqWRDo0GiUmPwyU7TAsD7wHeIW3Nfy1qVGKoE9NgJCdYCAexNRob9yCn4DAQmXtQuUtera6bEmTTXhZy6h856xi4mnEl6BI9mfISkLbtJyZIMJIAUd5ZOBEu88KRAk71yxfItj/hpIB0Errv4gO1os4/UICf+o3kkqwAAAAASUVORK5CYII=\",kt=\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyBAMAAADsEZWCAAAAD1BMVEX0/PTx+fH2/vbz+/P4//htSO9OAAAC5UlEQVQ4yyWT0QGjMAxDZTsDWKQDmJQBYrgBUsr+M517x0+LRWw9CyA+pC1YzndrMgHaNXVKQ+di13Of1qbur48nWhuRjj8i6ON8e7pNm7zyag/DBTfS9Z4Hup1fUuXMKY4HEE8QOHCByXkIkl7lDT239RtL9quO4JItmmhOAHXg45QuYKrQFLyGJcRvaTw6kQqZy6mkR6JAPFH/XqsQjEDRmUOA+MNLHGyMUT7AHApoAhjgjIJmCxy6XHdf648AWCdGe57IUDazCeTImQOY4/z+eVYVX2IjOw9RydeAeJwl79iGi4HpgQgHEchWraUZLtayu8scq0lHHHUKMY3Ml8hB7CS1jOckDLG9ccgNeX3124phOcjL9fPnWJhTXpLHeG9DRmHnTxHEaHakS2J51lwAJcUraNbuU7q4gMTDQj3Eripc/x+qFM5VEKAB1roQfAkX5/PxqnS2QpOrxfK1Zft0/omV5T+xCSBUAIbEIwUQgvAfxFE1O8dnk233+1UZiqJ1mAbsue6Yt8tF+yOrxC/YrUhzC4qPlE3EbR5hGKhhHdlrg7J9WunV7L7BcYQwAeE59u2tnN1c6gfVYrQiLSZ9OxZdWDXQq0+r0Pbarh3UqGCwauVvbiXuDsNxCtLDdW9rTF8oQYN4EoXXdfmwNguQP26n/tRjDeo+F2W7PjWtfSr6Bn/z+cXOLp4NnMV4RytvSW4B68m+XN9XfZTFGhO/S+cHTuTqZDC21ccA0N7QsePALaDQC3D1f94U9CWo+aq6BjB3v0rxIimBM12296M3aKPHjXLQE9KQKH4By8RHraJ3AgVto2r4xdFqlaPaiAHLl1ZF4P2pI6cYc+K8UZdcmxy7lqGc1IoPxLmIFuIeEZ6j2sQT88muEg1zwrEDTIX5U/ZmcsqfgVlBumiBLF4sAyhf9BFlXOPKLZ4H0iFb3VoHrGhtHTldKrOvP2/reu2zfV8CXMPqzRdlgd0a5eI7WwB/AYcgavcqxXWEAAAAAElFTkSuQmCC\",ht=\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyAgMAAABjUWAiAAAADFBMVEXM2t7O3ODQ3uLR4OTDp25yAAACdUlEQVQozw3P70sTcQDH8c/3/M7NG+j35mnHwjwh4hRy/QFK3zvPNbeIG1koPZmxfj2IDAwihL53zj0JYisfmEHcZJZOiBUG60lZiI8T/ANusuftgQ+kCPIPeMP7hS5mUrV9c1g6MQCAEZ8tDLHwofImAGRlX+SZK3Vu9rRRPuO4PK6/9nA4GIATsxlODS+rdCMhkAZivpYV0LWoQHSLSA4NfUg+6mY+7BKL2++F9LvnrBDYm6JO9i/YO3i/HJTGQ4pdIV82TbEDFG6vGYCd4wZchgK5J2CrKTLE+Tx0v+YGlIbdWJFcQl4ptBN8fUJQN1MCJLcZLYwUVVo+famGGty8EXJF5ofOEDzcodT3/Fb0I5sHmc1ZG7CcSl8COgxlXx09jT05OafjCZLIHJhGIaU6wDZHsuMQ41wbdjmQXbhKnMq1zlXSYrjCnyZblqexA7fC8RxS74tq2P3OxSQwTuJSApH8OZLzBBp1pOe0i3rdyDUA47GySZ31YmC4EQYSXvFSvieORGBxXF9aeVtUWKGS9WMC4Z9Y2uXnJ2nCUXVMbPOYqNYNmGWWQ7Evr+BWC+a0JAMTImcq/S4Z5INdQMeuOqDIMa9beilxfA60iC6sP1INcPDpmHBW8drZHNmqwyddJtVje9q8WGUgWAOzmbU4FCQBFi8B2Wk6pickBnYhJMenmJGuRmtt2IoKq9NuFGbNFR99sHnvrnLsLysKANDIsxbp6RNMAsoDSKuRpMwZbAAzI68QatIjmZ0aImyM3O8/4e2MNlOHZomFsa/fLDsysliHS+nlYLQJMnynxrH8QO4PaAV2Li8B/+52UgeGIVNFYf8B1XG/kFSmLcUAAAAASUVORK5CYII=\",Ct=\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyAgMAAABjUWAiAAAADFBMVEXh7vLf7PDj8PTm8/ecW+lZAAACZElEQVQozw2RsU8TUQCHfz3fw7MS87jeI7DdmSMpDEoHE+P0HqGkvRR8vb5XC4NpN2RQZqcK9xJkwtriekcggerC4OZADDiT+A+goxv/gfwB3zd8H/T6vYF/pTZkCSmDNd3CBEtmZJP4N+CvvhecDvmntKsvwB17rpbIRTLOEoYkj9KZzRUuJsuBQFwgptyJ3Y7EL4V+ud5LO1UnMeQSSObqisiISZkbQBlliP3qWSk3GPQXjxv6VF2BTDO4ySx1zhuJXbA2wBNJF4t5vH9keg6wu5NvUpLtXrZ3OHC9ZsgVcZdOl38PM1y/L6m8GRiErj4AqezUjHGatGGIgs5NJDHh8Ua1IuB4035haVT6SaYWMoQ0eJ3rB/Gpnr3fB49YAy1Wa21YKqAHOmAveVw6CCMGMZh5bGtVI7jnZaiQNbta1Z+285oSoKoRbta1KZ/1bBdKH/RIxv2pRVpkoCmvpr097RWoo0CpMlTWllIenSjECU8mV43mHx2fIRfH/pncrJm3+58BWdbSqCS07/yiQnvHiCG4ZPGRFeAtfreoOubyctzHvLNHhjNvIhukxQzjU5O6QdOEzUp1Ef4d98Pxz+IPYX0bcpnT52dbedfz8y7C4R89RV+MjJkuCCx7mWDt4eyK/62lQB55xXGJK7p8u6bgRv4hVHylelYGGFs64W94tng8sAIVqSRJBpqRA9rFvAysS+9ak8s7557pz5HR4qhCRmWgplpTRJ+bhYfSAMO8/YBucWPuSdmFFtOnuWqvV2NbF6CJnbhNDzEZ/T0XSDrUydzkZCG1z/oIEyUFYxW/KPXNfwopuHDcO04UAAAAAElFTkSuQmCC\",yt=\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyAgMAAABjUWAiAAAADFBMVEXm9PXq+Pno9vfs+vttWKBGAAACPElEQVQozw3RQWrbQACF4TfCMjPqZgIj4RRaxsZKE0PuMBZ2cLKaCI9RDAXFmJJknUWWI1O1UlamOMHJSjGkuFn3AD2Cr9CepDrAg+/xIxK4QwIqHHQkUhQ/WuphInVIFBojl8QXc012Tgq4RTtVHWVLZVFh1tEoI91uiN4joCqde8Ukn/zGM1B2W4ari2PtTwyw55Ld+Wways54qhGPyS6FzbIT3lIY8WwWdCq56Yolx6KmSKzoqrsCB5heAp4TGNQWJ1Pc6XlE5jQD5OlIX9I47A9uiUQcPQxcury/ToyxWJG/za6ki88crxKPocKS59Sl3EtBG7C89fCGflpfqoSzCeC4crioJA7F0V5+8MaSIk4qSCdwzpogmbqzEirVpGiS2dOVJvUuuqFEmhHao06KEpq+8lvHI14NJk3Qrmi9vBuRLwAz0qZB4hsDXQFXgtnlpDX3C6ug9BquSw/CYtwAzuTz5vuQNdr/YibhR68378ehZH30FSpjh71LpQkrsj+Q062h5WwZ5wlRoD6uQJy1DqvSYuCUapMBqT5YA4ZFw4KlWapxoUGlKWrx0eDQvmigu4WMYt97ruru98fYL8/0lG6CTOFcFWBhFK5gKw19h2JN808nh7xhkU6sWKLXdtkqBL6h+lULK5k19wFB/FldnGYf3LDeuf6IC2/MzJOSOP0qPxLqzaGIqtBcFIItrstkazONOkrc1D1czjuwEGESB4JJnjgSMN7PXAu7fZQpl1C236C+9mM4Af8P98Ch4R2TRl8AAAAASUVORK5CYII=\",It=\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyAgMAAABjUWAiAAAADFBMVEXPz8/R0dHT09PU1NToNyAhAAACdElEQVQozw3NP0xTQQDH8d9d7sFrG+QeKVgQ4aoFCwFkYERyLY//0UB8GNGg1WAC0RBGJrzW4mCXQmpgvCYOwEAYiulSpYtza2KiW7s5FgNJFSV2/CzfL7RwpoJ20iadmgA8owOyaxmusKE44scBeb4vIv00dqYgmf6jzWcr7W6INbDQeZbQL9ytXeYgtFfzmW1Fek5msxJlwhyt6qDDxOLQzpVPompYrMPnEnhvLm7M5BxY5nowAj3zkydAkpC0FIG6g7AK+Ub25ybyNWVYwtpseP2rfrQwiGRpfqrnMuPeuvr2dA0p2YsHF2XghkrXKtZ8tLBjR7S2qIaYbKmyLd/QP+EogLjqqwNw5Lq1pDlMLkM5+gNoSvdq+Pxmz9/61EFq6GYM6GqaGvlN95zy3gsmEWI8K3k8OP9OmRLEPO6DP3Wv3g42COinJTZ33dcIvs4ESp6opMTjDs6mcYTEbFeUifuxh989yZrIx4lkpuixxz0nHLCekKbE17suKhYkMGhoYhTZtVBvg4bfq/1L1Im0AGMVpBFwumM0zwyuKiCMi5dqR4Flx47AGyF2xTbxqUdTwCH94BT3DozpLV5WuAL/N8rGtHKjotBOOuOtCJ9E21uqsyBoLOzaXbHPrK5PQBP+fBfeidvJAeMIAmzVt5IkJJ9DBWaZDAepYUhlQqHt0h72SJ3j8TZHom64f516xx9T5evgMPgwG82jZdJaJIDyWp6LAjOCclVyzNA3iTKzIULlBQEPaTXlPHok5gISclmyaWZlqY2aTHdRHpJOwTdDEQ3ZfKtbpclcNhyVClagmY+fIfyKukntPqBgnx5QvZHk/D/MK8JMClrSigAAAABJRU5ErkJggg==\",bt=\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyAgMAAABjUWAiAAAADFBMVEXe3t7a2trc3Nzg4OCXP9lCAAACoklEQVQozwXBzU/TYBwA4N+QEr4CNbSFwcFuowSqMRvEAwShHWAYNsu7dS0dLnGUSWT4kZB4lGzE4VtcwgIDJqcOWLJxcv4BOoQZuCPxSNSD4WSWLJGL8XmAIiyo2RgJ4A1pxQQlOxRAszLTdnPu2oQGb05RC5slJld7ZAIfo4O44Bn1ud59F0BcjnYOa17Jhwc6EdiKettncsXjT1f8KUBZUW41pK0Jc1Az4dEV3rkkPBtDSZ83Blyt0kSf2PRjzIykoBwINisPbPPtljdVE9iAXRfUPkXLVIgYrCccp5g687NdZbcJ+xa5VE/HhTtT23IKsN5jj/pcUd0dTZNAqCVw72n4gOwnTOC0vvHfaauT8d9zAoRRfPpISZRVyUiw8ELzOG1b2DZpFzkSrHLhq52twDEdyZHwvp2j4uv/bjvOf23/AcEtTuJbY5Cp4YcAer1IGkUzOo2rn8LQOKjFJw3NTw24nprQXY5aF4wxcqcSdbFQ00H4xFl8Drx4X4CikvAM1tuR8bKIBCBoLnKN10KJG4zKAsc7c9WEB9gnCi6BhVjqoco6t20ILAJuVctvaEZK732cRHDRmGfuihOam0o2CHByUZ/epCcVlRs2wmCnMqsd6aSim3ibBJtm1LGyXW3Bb7tJCPlFtUG+SvPdeEUAB60lNdo+VQbLcwRNVtT68FsLcr1+NotgNihlpExS1V2SFgNbeC8bEhgm8sM17wSi6Us2gxVWJU/5GKBpandvfyYbU1yHCLpCgWGbbPXn40rehEsUXKIJr9DMKgICfjc4bl1YfvUhE/YIECGRqjCxSM9hrybAIkND5OeWfFZsXkxB+qDzb7pUQ3EfQ3Ml6EChEt3D+iS01VqC7EQ/Z/DuPQcz4yChoFQJce2Qr+NNAv0HxofmpXGqgHkAAAAASUVORK5CYII=\",wt=\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyBAMAAADsEZWCAAAAD1BMVEXm5ubo6Ojp6enr6+vt7e1FnZagAAACrklEQVQ4yx1SixUbMQgT3AKAFwDcAfzpBN1/qMrJS5w7bCQhC6IGSUGYQJd6Ox9ZPXi1AGJBavhUTT0JjYPGAab9WcDYIxsmlnxkayX8mhxCmKHA75az5cfRbWybEExiu08xDSgGym0mwuf3j4SvHeQxDJJzh2zp4iOlrD8iOb4SXyC1wiOLRTcnrje+nGamFeXVKWkmzbFIPChkmJ6Fg7mBpV8n+JGOVCd4jv1thThkjeQGNeafpeV3rsEWLfyWc8tC9jOv6FQ8rRzHOOVB+jCYEUAJpDvh8xHNFm/Tm5p5lw94Pp3NhtKEfQsGvnXhowdZE73hPwxKvjDd4i4PCdd0fe3W5fO8ktAsUAacLgstpUw60JCiPLg2XpkgiqPIYYXJd9ksGIT3q+LlevypzItvO+s0F1dBzVr2QDMUkYmuyGcrIS44mVJ7JVKwQXjYuBYp0Uetecbswzsikzu3gUR8bJC/C8Gd/NAzI/xdUGOYQQHDZ8X2d5XuzGRUiXAi9si5CRgoiToRZPtzLJkd0FUHRHZwJf0BHT1sE7gcnh0jmKKlSSF4/GBirGk5+K9NKlGDCfc9JtPhg78JdabH0YQRKNZnJ8tFnPfXHJb4xum1TTCeEmyEdbyEJLjznMLHuFD2Y9NEkSleIBs7SiCbblhgctVi9ch++kDYnn1C9DA5TvdPsToXM55wI6k+8eKT1blwPTqWb5CFJ+7dTBmab+KHy+xwNtItXhZNSpHD2fxnynrxG3ZBKRe8KBpXk11AnadlccEhr9w1nBBvBylNkv7A8eqpGBCDqhitmWQXBjjdS6idr/QjXWLDeMzMbVDoJuM8zN7WenMZWXgZ2vX3F01J3jHZbwk1LRP+DWEvDJtOUoh/AIaBUz5VpWyhuyx4QtgL/NmgC6kM/JvNe+R/C/5aL7BKIbYAAAAASUVORK5CYII=\",St=\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyBAMAAADsEZWCAAAAElBMVEUQERMODxESFBYWGBkaHB0eICLm6ozJAAACkUlEQVQ4yyWTUdLbMAiEASfvoOkBkBy/O5keIE0v8E/uf5h+68qZWALELgu2MG9PP9qyvCzTVhrrsPGOCjvTfXQZvtp/W3Gy6LCITqs4q/DZ+KYl76zKzHVYpY2wNY27nqN1sbLGcrLH3/ENH4oWlGctsDu8AO+HzTLlsYdh8MzP1m6YDMz0ACfcimvakBj+mwO/+5Uta5teOD379sxK1fUxmUhv8MU3jUT5gs26PMephFznkLcpQZ6/dPL9C/GWHcCxDN6oZhD5xBm5qoYBPA+PFE/H1tXDWcWl8TW7rS+4dUzAVy0BIrvC4/HcqW2TkG1HO8q9dC23INAg7NA4AFRFkDTM2lfELPyFzi1VddcpX2z0KjHBUDmdLNJ6dDps4ytrX+FPsZwE31wSL+6OWfHOAJ3+Y0Rk/MiKfmWNPg7oVP/U3Ck9FoCkC2gBpALOiqbMNTkOe8P4FWkTD2Y9Q3+5VmV0uLUJBl68U5uAK2Kl6QDXvLxbwweOL2sixW78uU8p0ysfc7cWrF1j6B1sPJ4WgclYSnJN1bzozrhEcFHmRzBkbJWqqdG+EYJXRFmT5jnLXPUNF6WBdoFbTxYsmDXVLU/WA7MExNc93sJS5hIXDeLxzMScHzdhKvEkibr6cQXYPrmtmTA7JcInISrTzRDvShTdka0uVGrsJAAR6tSn1sKziZtfKVjAxPrJsYgZO0bye+vKTZ/DgoAoLGNO6jYHimZYTL/3pLJHawquJukjBpfz8WOGVSVIWx9ywUfS5iENutidRM4NzkAmxgUSQ68xgNOU+ZLalr4TS2V+D2xqukZig+Z9DilR7Nouzwp1cp/3E5q6Rdlf08obKvAM4qZ6pMr+w3PmQALSSBfjyZn5DwrNRVbywBQiAAAAAElFTkSuQmCC\",Et=\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyAgMAAABjUWAiAAAADFBMVEUWGBkYGhsdHyAfISI1t/v6AAAB5ElEQVQozxXQsYoTURSA4f/EeycZsDgDdySDjihk38Hy3GWi2J2BCaziQhaiaB+tt9AFu1kwvYUPsIXNPoB9BAUfwAfwEUzKv/v4odGrroyp9/rUaC6rZ5skv5F8qPsfYYP+yKUMymmAEEeW55oUR4o8jr05KNzJ07yvB7w0KKfLwcQUSjfmMU0PJfPHFoEVU+ohNrcKMEzMQ23FDnVSI2dqtYWI7KlLu6vE4UnyvKc3SJuL7lBbeEEl42ItpGLjzIT8PRJCmkRjVpVpsbJFVN0687okJNZiHAr5Z7MV0BnGIDc+THM1zlbieBc1Fq+tH5BH+OpnbWkj40hSqC8Lw2TvFuF0SUFJCk2IytXbjeqcRAt6NHpnrUkUU4KRzZs8RCK8N/Akn2W04LwxMU/V7XK0bDyN2RxfDyx7I4h5vjZby72V8UnOWumZL3qtYc+8DTE0siSBMXGhywx2dMYPnQHbxdFZ7deiNGxCCtD/QWnbwDoGhRYPDzUdUA3krjpnkvdAgDN4ddLkEQSov9qjd42HaDjI34gEqS9TUueAk+sc4qg5ws407KQYKs8G1jv4xBlqBVk6cb4dISZIwVi1Jzu4+HLk6lyfUxkXvwy+1Q+4WVdHIhwfybZ6CWVhxMEhShOgsP/HOW0MvZJeFwAAAABJRU5ErkJggg==\",xt=\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAADIAAAAyAgMAAABjUWAiAAAADFBMVEUWGBkYGhsdHyAfISI1t/v6AAAB5ElEQVQozxXQsYoTURSA4f/EeycZsDgDdySDjihk38Hy3GWi2J2BCaziQhaiaB+tt9AFu1kwvYUPsIXNPoB9BAUfwAfwEUzKv/v4odGrroyp9/rUaC6rZ5skv5F8qPsfYYP+yKUMymmAEEeW55oUR4o8jr05KNzJ07yvB7w0KKfLwcQUSjfmMU0PJfPHFoEVU+ohNrcKMEzMQ23FDnVSI2dqtYWI7KlLu6vE4UnyvKc3SJuL7lBbeEEl42ItpGLjzIT8PRJCmkRjVpVpsbJFVN0687okJNZiHAr5Z7MV0BnGIDc+THM1zlbieBc1Fq+tH5BH+OpnbWkj40hSqC8Lw2TvFuF0SUFJCk2IytXbjeqcRAt6NHpnrUkUU4KRzZs8RCK8N/Akn2W04LwxMU/V7XK0bDyN2RxfDyx7I4h5vjZby72V8UnOWumZL3qtYc+8DTE0siSBMXGhywx2dMYPnQHbxdFZ7deiNGxCCtD/QWnbwDoGhRYPDzUdUA3krjpnkvdAgDN4ddLkEQSov9qjd42HaDjI34gEqS9TUueAk+sc4qg5ws407KQYKs8G1jv4xBlqBVk6cb4dISZIwVi1Jzu4+HLk6lyfUxkXvwy+1Q+4WVdHIhwfybZ6CWVhxMEhShOgsP/HOW0MvZJeFwAAAABJRU5ErkJggg==\",oe={themes:[{body:\"#ede7da url(\"+dt+\") repeat\",content:\"#ede7da url(\"+ut+\") repeat\",popup:\"#ede7da url(\"+gt+\") repeat\"},{body:\"#ede7da url(\"+pt+\") repeat\",content:\"#ede7da url(\"+ft+\") repeat\",popup:\"#ede7da url(\"+mt+\") repeat\"},{body:\"#ede7da url(\"+vt+\") repeat\",content:\"#ede7da url(\"+Bt+\") repeat\",popup:\"#ede7da url(\"+kt+\") repeat\"},{body:\"#ede7da url(\"+ht+\") repeat\",content:\"#ede7da url(\"+Ct+\") repeat\",popup:\"#ede7da url(\"+yt+\") repeat\"},{body:\"#ebcece repeat\",content:\"#f5e4e4 repeat\",popup:\"#faeceb repeat\"},{body:\"#ede7da url(\"+It+\") repeat\",content:\"#ede7da url(\"+bt+\") repeat\",popup:\"#ede7da url(\"+wt+\") repeat\"},{body:\"#ede7da url(\"+St+\") repeat\",content:\"#ede7da url(\"+Et+\") repeat\",popup:\"#ede7da url(\"+xt+\") repeat\"}],fonts:[\"Microsoft YaHei, PingFangSC-Regular, HelveticaNeue-Light, Helvetica Neue Light, sans-serif\",\"PingFangSC-Regular, -apple-system, Simsun\",\"Kaiti\"]},Ut={class:\"setting-list\"},Dt={class:\"theme-list\"},Qt=[\"onClick\"],Vt={key:0,class:\"iconfont\"},Ft={key:1,class:\"moon-icon\"},Pt={class:\"font-list\"},Mt=[\"onClick\"],Lt={class:\"font-list\"},Rt={style:{\"text-align\":\"right\",margin:\"0\"}},Kt={class:\"font-size\"},zt={class:\"resize\"},Ht={class:\"lang\"},Ot={class:\"letter-spacing\"},Wt={class:\"resize\"},Nt={class:\"lang\"},Tt={class:\"line-spacing\"},Jt={class:\"resize\"},Gt={class:\"lang\"},qt={class:\"paragraph-spacing\"},Zt={class:\"resize\"},Yt={class:\"resize\"},jt={class:\"lang\"},Xt={key:0,class:\"read-width\"},_t={class:\"resize\"},$t={class:\"lang\"},eo={class:\"paragraph-spacing\"},to={class:\"resize\"},oo={class:\"resize\"},no={class:\"lang\"},so={class:\"infinite-loading\"},ao=ce({__name:\"ReadSettings\",setup(c){const s=me(),A=je(()=>ne.saveReadConfig(s.config),500);be(()=>s.config,()=>{A()},{deep:2});const f=l(()=>s.theme),n=l(()=>s.isNight),w=l(()=>f.value==6?\"\":\"\"),m=[{background:\"rgba(250, 245, 235, 0.8)\"},{background:\"rgba(245, 234, 204, 0.8)\"},{background:\"rgba(230, 242, 230, 0.8)\"},{background:\"rgba(228, 241, 245, 0.8)\"},{background:\"rgba(245, 228, 228, 0.8)\"},{background:\"rgba(224, 224, 224, 0.8)\"},{background:\"rgba(0, 0, 0, 0.5)\"}],y=l(()=>({background:oe.themes[f.value].popup})),h=M=>{s.config.theme=M},F=D([\"雅黑\",\"宋体\",\"楷书\"]),P=M=>{s.config.font=M},x=l(()=>s.config.font),S=D(s.config.customFontName),V=D(!1),U=()=>{V.value=!1,s.config.font=-1,s.config.customFontName=S.value},C=()=>{V.value=!1,Re.prompt(\"请输入 字体网络链接\",\"提示\",{confirmButtonText:\"确定\",cancelButtonText:\"取消\",inputPattern:/^https?:.+$/,inputErrorMessage:\"url 形式不正确\",beforeClose:(M,e,N)=>{if(M===\"confirm\"){e.confirmButtonLoading=!0,e.confirmButtonText=\"下载中……\";const Y=e.inputValue;if(typeof FontFace!=\"function\")return R.error(\"浏览器不支持FontFace\"),N();const _=new FontFace(S.value,`url(\"${Y}\")`);document.fonts.add(_),_.load().then(function(){e.confirmButtonLoading=!1,R.info(\"字体加载成功！\"),U(),N()}).catch(function(E){throw e.confirmButtonLoading=!1,e.confirmButtonText=\"确定\",R.error(\"下载失败，请检查您输入的 url\"),E})}else N()}})},I=l(()=>s.config.fontSize),b=()=>{s.config.fontSize<48&&(s.config.fontSize+=2)},v=()=>{s.config.fontSize>12&&(s.config.fontSize-=2)},u=l(()=>s.config.spacing),i=()=>{s.config.spacing.letter-=.01},d=()=>{s.config.spacing.letter+=.01},B=()=>{s.config.spacing.line-=.1},W=()=>{s.config.spacing.line+=.1},ve=()=>{s.config.spacing.paragraph-=.1},de=()=>{s.config.spacing.paragraph+=.1},Be=l(()=>s.config.readWidth),ue=()=>{s.config.readWidth+160+2*68>window.innerWidth||(s.config.readWidth+=160)},ke=()=>{s.config.readWidth>640&&(s.config.readWidth-=160)},he=l(()=>s.config.jumpDuration),j=()=>{s.config.jumpDuration+=100},Ce=()=>{s.config.jumpDuration!==0&&(s.config.jumpDuration-=100)},ge=l(()=>s.config.infiniteLoading),X=M=>{s.config.infiniteLoading=M};return(M,e)=>{const N=Ge,Y=Ye,_=Le;return g(),p(\"div\",{class:Q([\"settings-wrapper\",{night:a(n),day:!a(n)}]),style:Z(a(y))},[e[51]||(e[51]=t(\"div\",{class:\"settings-title\"},\"设置\",-1)),t(\"div\",Ut,[t(\"ul\",null,[t(\"li\",Dt,[e[7]||(e[7]=t(\"i\",null,\"阅读主题\",-1)),(g(),p(se,null,le(m,(E,L)=>t(\"span\",{class:Q([\"theme-item\",{selected:a(f)==L}]),key:L,style:Z(E),ref_for:!0,ref:\"themes\",onClick:T=>h(L)},[L<6?(g(),p(\"em\",Vt,\"\")):(g(),p(\"em\",Ft,K(a(w)),1))],14,Qt)),64))]),t(\"li\",Pt,[e[8]||(e[8]=t(\"i\",null,\"正文字体\",-1)),(g(!0),p(se,null,le(a(F),(E,L)=>(g(),p(\"span\",{class:Q([\"font-item\",{selected:a(x)==L}]),key:L,onClick:T=>P(L)},K(E),11,Mt))),128))]),t(\"li\",Lt,[e[14]||(e[14]=t(\"i\",null,\"自定字体\",-1)),H(N,{effect:\"dark\",content:\"自定义的字体名称\",placement:\"top\"},{default:z(()=>[qe(t(\"input\",{type:\"text\",class:\"font-item font-item-input\",\"onUpdate:modelValue\":e[0]||(e[0]=E=>fe(S)?S.value=E:null),placeholder:\"请输入自定义的字体名称\"},null,512),[[Ze,a(S)]])]),_:1}),H(_,{placement:\"top\",width:\"270\",trigger:\"click\",visible:a(V),\"onUpdate:visible\":e[4]||(e[4]=E=>fe(V)?V.value=E:null)},{reference:z(()=>[...e[12]||(e[12]=[t(\"span\",{type:\"text\",class:\"font-item\"},\"保存\",-1)])]),default:z(()=>[e[13]||(e[13]=t(\"p\",null,\" 已经安装在您的设备上的字体请确认输入的字体名称完整无误，或者从网络下载字体。 \",-1)),t(\"div\",Rt,[H(Y,{size:\"small\",plain:\"\",onClick:e[1]||(e[1]=E=>V.value=!1)},{default:z(()=>[...e[9]||(e[9]=[q(\"取消\",-1)])]),_:1}),H(Y,{type:\"primary\",size:\"small\",onClick:e[2]||(e[2]=E=>U())},{default:z(()=>[...e[10]||(e[10]=[q(\"确定\",-1)])]),_:1}),H(Y,{type:\"primary\",size:\"small\",onClick:e[3]||(e[3]=E=>C())},{default:z(()=>[...e[11]||(e[11]=[q(\"网络下载\",-1)])]),_:1})])]),_:1},8,[\"visible\"])]),t(\"li\",Kt,[e[20]||(e[20]=t(\"i\",null,\"字体大小\",-1)),t(\"div\",zt,[t(\"span\",{class:\"less\",onClick:v},[...e[15]||(e[15]=[t(\"em\",{class:\"iconfont\"},\"\",-1)])]),e[17]||(e[17]=t(\"b\",null,null,-1)),e[18]||(e[18]=q()),t(\"span\",Ht,K(a(I)),1),e[19]||(e[19]=t(\"b\",null,null,-1)),t(\"span\",{class:\"more\",onClick:b},[...e[16]||(e[16]=[t(\"em\",{class:\"iconfont\"},\"\",-1)])])])]),t(\"li\",Ot,[e[26]||(e[26]=t(\"i\",null,\"字距\",-1)),t(\"div\",Wt,[t(\"span\",{class:\"less\",onClick:i},[...e[21]||(e[21]=[t(\"em\",{class:\"iconfont\"},\"\",-1)])]),e[23]||(e[23]=t(\"b\",null,null,-1)),e[24]||(e[24]=q()),t(\"span\",Nt,K(a(u).letter.toFixed(2)),1),e[25]||(e[25]=t(\"b\",null,null,-1)),t(\"span\",{class:\"more\",onClick:d},[...e[22]||(e[22]=[t(\"em\",{class:\"iconfont\"},\"\",-1)])])])]),t(\"li\",Tt,[e[32]||(e[32]=t(\"i\",null,\"行距\",-1)),t(\"div\",Jt,[t(\"span\",{class:\"less\",onClick:B},[...e[27]||(e[27]=[t(\"em\",{class:\"iconfont\"},\"\",-1)])]),e[29]||(e[29]=t(\"b\",null,null,-1)),e[30]||(e[30]=q()),t(\"span\",Gt,K(a(u).line.toFixed(1)),1),e[31]||(e[31]=t(\"b\",null,null,-1)),t(\"span\",{class:\"more\",onClick:W},[...e[28]||(e[28]=[t(\"em\",{class:\"iconfont\"},\"\",-1)])])])]),t(\"li\",qt,[e[37]||(e[37]=t(\"i\",null,\"段距\",-1)),t(\"div\",Zt,[t(\"div\",Yt,[t(\"span\",{class:\"less\",onClick:ve},[...e[33]||(e[33]=[t(\"em\",{class:\"iconfont\"},\"\",-1)])]),e[35]||(e[35]=t(\"b\",null,null,-1)),t(\"span\",jt,K(a(u).paragraph.toFixed(1)),1),e[36]||(e[36]=t(\"b\",null,null,-1)),t(\"span\",{class:\"more\",onClick:de},[...e[34]||(e[34]=[t(\"em\",{class:\"iconfont\"},\"\",-1)])])])])]),a(s).miniInterface?pe(\"\",!0):(g(),p(\"li\",Xt,[e[43]||(e[43]=t(\"i\",null,\"页面宽度\",-1)),t(\"div\",_t,[t(\"span\",{class:\"less\",onClick:ke},[...e[38]||(e[38]=[t(\"em\",{class:\"iconfont\"},\"\",-1)])]),e[40]||(e[40]=t(\"b\",null,null,-1)),e[41]||(e[41]=q()),t(\"span\",$t,K(a(Be)),1),e[42]||(e[42]=t(\"b\",null,null,-1)),t(\"span\",{class:\"more\",onClick:ue},[...e[39]||(e[39]=[t(\"em\",{class:\"iconfont\"},\"\",-1)])])])])),t(\"li\",eo,[e[49]||(e[49]=t(\"i\",null,\"翻页速度\",-1)),t(\"div\",to,[t(\"div\",oo,[t(\"span\",{class:\"less\",onClick:Ce},[...e[44]||(e[44]=[t(\"em\",{class:\"iconfont\"},\"\",-1)])]),e[46]||(e[46]=t(\"b\",null,null,-1)),e[47]||(e[47]=q()),t(\"span\",no,K(a(he)),1),e[48]||(e[48]=t(\"b\",null,null,-1)),t(\"span\",{class:\"more\",onClick:j},[...e[45]||(e[45]=[t(\"em\",{class:\"iconfont\"},\"\",-1)])])])])]),t(\"li\",so,[e[50]||(e[50]=t(\"i\",null,\"无限加载\",-1)),(g(),p(\"span\",{class:Q([\"infinite-loading-item\",{selected:a(ge)==!1}]),key:0,onClick:e[5]||(e[5]=E=>X(!1))},\"关闭\",2)),(g(),p(\"span\",{class:Q([\"infinite-loading-item\",{selected:a(ge)==!0}]),key:1,onClick:e[6]||(e[6]=E=>X(!0))},\"开启\",2))])])])],6)}}}),io=Ae(ao,[[\"__scopeId\",\"data-v-dd7cfcb2\"]]),ro={class:\"wrapper\"},lo=[\"onClick\"],co=ce({__name:\"CatalogItem\",props:{index:{},source:{},gotoChapter:{type:Function},currentChapterIndex:{}},setup(c){const s=c,A=n=>n==s.currentChapterIndex,f=l(()=>{const n=s.source;return\"catas\"in n?n.catas:[s.source]});return(n,w)=>(g(),p(\"div\",ro,[(g(!0),p(se,null,le(a(f),m=>(g(),p(\"div\",{class:Q([\"cata-text\",{selected:A(m.index)}]),key:m.url,onClick:y=>c.gotoChapter(m)},K(m.title),11,lo))),128))]))}}),Ao=Ae(co,[[\"__scopeId\",\"data-v-a892cd6d\"]]),uo=ce({__name:\"PopCatalog\",emits:[\"getContent\"],setup(c,{emit:s}){const A=me(),{catalog:f,popCataVisible:n,miniInterface:w}=Ke(A),m=l(()=>A.theme),y=l(()=>A.theme),h=l(()=>({background:oe.themes[y.value].popup})),F=l(()=>{const C=f.value;if(w.value)return C;const I=Math.ceil(C.length/2),b=new Array(I);let v=0;for(;v<I;)b[v]={index:v,catas:C.slice(2*v,2*v+2)},v++;return b}),P=D(),x=l({get:()=>A.readingBook.chapterIndex,set:C=>A.readingBook.chapterIndex=C}),S=l(()=>{const C=x.value;return w.value?C:Math.floor(C/2)});Xe(()=>{n.value&&P.value.scrollToIndex(S.value)});const V=s,U=C=>{const I=f.value.indexOf(C);x.value=I,A.setPopCataVisible(!1),A.setContentLoading(!0),A.saveBookProgress(),V(\"getContent\",I)};return(C,I)=>(g(),p(\"div\",{class:Q({\"cata-wrapper\":!0,visible:a(n)}),style:Z(a(h))},[I[0]||(I[0]=t(\"div\",{class:\"title\"},\"目录\",-1)),H(a(_e),{style:{height:\"300px\",overflow:\"auto\"},class:Q({night:a(m),day:!a(m)}),ref_key:\"virtualListRef\",ref:P,\"data-key\":\"index\",\"wrap-class\":\"data-wrapper\",\"item-class\":\"cata\",\"data-sources\":a(F),\"data-component\":Ao,\"estimate-size\":40,\"extra-props\":{gotoChapter:U,currentChapterIndex:a(x)}},null,8,[\"class\",\"data-sources\",\"extra-props\"])],6))}}),go=Ae(uo,[[\"__scopeId\",\"data-v-6cab38af\"]]),po={class:\"tools\"},fo={class:\"tools\"},mo={key:0},vo={key:0},Bo={class:\"content\"},ko=[\"chapterIndex\"],ho=ce({__name:\"BookChapter\",setup(c){const s=D(),{isLoading:A,loadingWrapper:f}=nt(s,\"正在获取信息\"),n=me(),{catalog:w,popCataVisible:m,readSettingsVisible:y,miniInterface:h,showContent:F,bookProgress:P,theme:x,isNight:S}=Ke(n),V=l({get:()=>n.readingBook.chapterPos,set:o=>n.readingBook.chapterPos=o}),U=l({get:()=>n.readingBook.chapterIndex,set:o=>n.readingBook.chapterIndex=o}),C=l({get:()=>n.readingBook.isSeachBook,set:o=>n.readingBook.isSeachBook=o});be(()=>n.readingBook,o=>{localStorage.setItem(\"readingRecent\",JSON.stringify(o)),sessionStorage.setItem(\"chapterIndex\",o.chapterIndex.toString()),sessionStorage.setItem(\"chapterPos\",o.chapterPos.toString())},{deep:1});const I=l(()=>n.config.infiniteLoading);let b;const v=D();De(()=>{I.value?b==null||b.observe(v.value):b==null||b.disconnect()});const u=()=>{const o=T.value.slice(-1)[0].index;w.value.length-1>o&&(ae(o+1,!1),n.saveBookProgress())},i=o=>{if(!A.value)for(const{isIntersecting:r}of o){if(!r)return;u()}},d=l(()=>n.config.font>=0?oe.fonts[n.config.font]:n.config.customFontName),B=l(()=>n.config.fontSize+\"px\"),W=l(()=>oe.themes[x.value].body),ve=l(()=>oe.themes[x.value].content),de=l(()=>oe.themes[x.value].popup),Be=l(()=>h.value?window.innerWidth+\"px\":n.config.readWidth-130+\"px\"),ue=l(()=>h.value?window.innerWidth-33:n.config.readWidth-33),ke=l(()=>({background:W.value})),he=l(()=>({background:ve.value,width:Be.value})),j=D(!1),Ce=l(()=>({background:de.value,marginLeft:h.value?0:-(n.config.readWidth/2+68)+\"px\",display:h.value&&!j.value?\"none\":\"block\"})),ge=l(()=>({background:de.value,marginRight:h.value?0:-(n.config.readWidth/2+52)+\"px\",display:h.value&&!j.value?\"none\":\"block\"})),X=()=>{n.setMiniInterface(window.innerWidth<776);const o=n.config.readWidth;M(o)},M=o=>{n.miniInterface||(o<640&&(n.config.readWidth=640),o+2*68>window.innerWidth&&(n.config.readWidth-=160))};be(()=>n.config.readWidth,o=>M(o));const e=D(),N=D(),Y=()=>{te(e.value)},_=()=>{te(N.value)},E=$e(),L=()=>{E.push(\"/\")},T=D([]),$=D(!0),ae=(o,r=!0,O=0)=>{r&&(n.setShowContent(!1),te(e.value,{duration:0}),we(o,O),T.value=[]);const J=n.readingBook.bookUrl,{title:G,index:re}=w.value[o];f(ne.getBookContent(J,re).then(k=>{if(k.data.isSuccess){const Te=k.data.data.split(/\\n+/);T.value.push({index:o,content:Te,title:G}),r&&He(O)}else{R({message:k.data.errorMsg,type:\"error\"});const ee=[k.data.errorMsg];T.value.push({index:o,content:ee,title:G})}if(n.setContentLoading(!0),$.value=!1,n.setShowContent(!0),!k.data.isSuccess)throw k.data},k=>{const ee=[\"获取章节内容失败！\"];throw T.value.push({index:o,content:ee,title:G}),n.setShowContent(!0),k}))},ze=D(),ye=D(),He=o=>{Me(()=>{ye.value.length===1&&ye.value[0].scrollToReadedLength(o)})},Oe=et(()=>n.saveBookProgress(),6e4),We=(o,r)=>{we(o,r),Oe()};De(()=>{var o;document.title=((o=w.value[U.value])==null?void 0:o.title)||document.title});const we=(o,r)=>{U.value=o,V.value=r},Se=()=>{const o=P.value;document.visibilityState==\"hidden\"&&o&&n.saveBookProgress()},Ee=()=>{n.setContentLoading(!0);const o=U.value+1;typeof w.value[o]<\"u\"?(R({message:\"下一章\",type:\"info\"}),ae(o),n.saveBookProgress()):R({message:\"本章是最后一章\",type:\"error\"})},xe=()=>{n.setContentLoading(!0);const o=U.value-1;typeof w.value[o]<\"u\"?(R({message:\"上一章\",type:\"info\"}),ae(o),n.saveBookProgress()):R({message:\"本章是第一章\",type:\"error\"})};let ie=!0;const Ie=o=>{if(ie)switch(o.key){case\"ArrowLeft\":o.stopPropagation(),o.preventDefault(),xe();break;case\"ArrowRight\":o.stopPropagation(),o.preventDefault(),Ee();break;case\"ArrowUp\":o.stopPropagation(),o.preventDefault(),document.documentElement.scrollTop===0?R.warning(\"已到达页面顶部\"):(ie=!1,te(0-document.documentElement.clientHeight+100,{duration:n.config.jumpDuration,callback:()=>ie=!0}));break;case\"ArrowDown\":o.stopPropagation(),o.preventDefault(),document.documentElement.clientHeight+document.documentElement.scrollTop===document.documentElement.scrollHeight?R.warning(\"已到达页面底部\"):(ie=!1,te(document.documentElement.clientHeight-100,{duration:n.config.jumpDuration,callback:()=>ie=!0}));break}},Ue=o=>{(o.key===\"ArrowUp\"||o.key===\"ArrowDown\")&&(o.preventDefault(),o.stopPropagation())};Fe(async()=>{await n.loadWebConfig();const o=sessionStorage.getItem(\"bookUrl\"),r=sessionStorage.getItem(\"bookName\"),O=sessionStorage.getItem(\"bookAuthor\"),J=Number(sessionStorage.getItem(\"chapterIndex\")||0),G=Number(sessionStorage.getItem(\"chapterPos\")||0),re=sessionStorage.getItem(\"isSeachBook\")===\"true\";if(Ve(o)||Ve(r)||O===null)return R.warning(\"书籍信息为空，即将自动返回书架页面...\"),setTimeout(L,500);const k={bookUrl:o,name:r,author:O,chapterIndex:J,chapterPos:G,isSeachBook:re};X(),window.addEventListener(\"resize\",X),f(n.loadWebCatalog(k).then(ee=>{n.setReadingBook(k),ae(J,!0,G),window.addEventListener(\"keyup\",Ie),window.addEventListener(\"keydown\",Ue),document.addEventListener(\"visibilitychange\",Se),b=new IntersectionObserver(i,{rootMargin:\"-100% 0% 20% 0%\"}),I.value===!0&&b.observe(v.value),document.title=\"...\",document.title=r+\" | \"+ee[J].title}))}),Pe(()=>{window.removeEventListener(\"keyup\",Ie),window.removeEventListener(\"keydown\",Ue),window.removeEventListener(\"resize\",X),document.removeEventListener(\"visibilitychange\",Se),y.value=!1,m.value=!1,b==null||b.disconnect(),b=null});const Ne=async()=>{const o=n.readingBook;o.isSeachBook===!0&&await Re.confirm(`是否将《${o.name}》放入书架？`,\"放入书架\",{confirmButtonText:\"确认\",cancelButtonText:\"否\",type:\"info\",closeOnHashChange:!1}).then(()=>{C.value=!1}).catch(async()=>{await ne.deleteBook(o)}).finally(()=>sessionStorage.removeItem(\"isSeachBook\"))};return tt(async(o,r,O)=>{window.removeEventListener(\"keyup\",Ie),await Ne(),O()}),(o,r)=>{const O=go,J=Le,G=io,re=At;return g(),p(\"div\",{class:Q([\"chapter-wrapper\",{night:a(S),day:!a(S)}]),style:Z(a(ke)),onClick:r[2]||(r[2]=k=>j.value=!a(j))},[t(\"div\",{class:\"tool-bar\",style:Z(a(Ce))},[t(\"div\",po,[H(J,{placement:\"right\",width:a(ue),trigger:\"click\",\"show-arrow\":!1,visible:a(m),\"onUpdate:visible\":r[0]||(r[0]=k=>fe(m)?m.value=k:null),\"popper-class\":\"pop-cata\"},{reference:z(()=>[t(\"div\",{class:Q([\"tool-icon\",{\"no-point\":!1}])},[...r[3]||(r[3]=[t(\"div\",{class:\"iconfont\"},\"\",-1),t(\"div\",{class:\"icon-text\"},\"目录\",-1)])])]),default:z(()=>[H(O,{onGetContent:ae,class:\"popup\"})]),_:1},8,[\"width\",\"visible\"]),H(J,{placement:\"right\",width:a(ue),trigger:\"click\",\"show-arrow\":!1,visible:a(y),\"onUpdate:visible\":r[1]||(r[1]=k=>fe(y)?y.value=k:null),\"popper-class\":\"pop-setting\"},{reference:z(()=>[t(\"div\",{class:Q([\"tool-icon\",{\"no-point\":a($)}])},[...r[4]||(r[4]=[t(\"div\",{class:\"iconfont\"},\"\",-1),t(\"div\",{class:\"icon-text\"},\"设置\",-1)])],2)]),default:z(()=>[H(G,{class:\"popup\"})]),_:1},8,[\"width\",\"visible\"]),t(\"div\",{class:\"tool-icon\",onClick:L},[...r[5]||(r[5]=[t(\"div\",{class:\"iconfont\"},\"\",-1),t(\"div\",{class:\"icon-text\"},\"书架\",-1)])]),t(\"div\",{class:Q([\"tool-icon\",{\"no-point\":a($)}]),onClick:Y},[...r[6]||(r[6]=[t(\"div\",{class:\"iconfont\"},\"\",-1),t(\"div\",{class:\"icon-text\"},\"顶部\",-1)])],2),t(\"div\",{class:Q([\"tool-icon\",{\"no-point\":a($)}]),onClick:_},[...r[7]||(r[7]=[t(\"div\",{class:\"iconfont\"},\"\",-1),t(\"div\",{class:\"icon-text\"},\"底部\",-1)])],2)])],4),t(\"div\",{class:\"read-bar\",style:Z(a(ge))},[t(\"div\",fo,[t(\"div\",{class:Q([\"tool-icon\",{\"no-point\":a($)}]),onClick:xe},[r[8]||(r[8]=t(\"div\",{class:\"iconfont\"},\"\",-1)),a(h)?(g(),p(\"span\",mo,\"上一章\")):pe(\"\",!0)],2),t(\"div\",{class:Q([\"tool-icon\",{\"no-point\":a($)}]),onClick:Ee},[a(h)?(g(),p(\"span\",vo,\"下一章\")):pe(\"\",!0),r[9]||(r[9]=t(\"div\",{class:\"iconfont\"},\"\",-1))],2)])],4),r[10]||(r[10]=t(\"div\",{class:\"chapter-bar\"},null,-1)),t(\"div\",{class:\"chapter\",ref_key:\"content\",ref:s,style:Z(a(he))},[t(\"div\",Bo,[t(\"div\",{class:\"top-bar\",ref_key:\"top\",ref:e},null,512),(g(!0),p(se,null,le(a(T),k=>(g(),p(\"div\",{key:k.index,chapterIndex:k.index,ref_for:!0,ref_key:\"chapter\",ref:ze},[a(F)?(g(),ot(re,{key:0,ref_for:!0,ref_key:\"chapterRef\",ref:ye,chapterIndex:k.index,contents:k.content,title:k.title,spacing:a(n).config.spacing,fontSize:a(B),fontFamily:a(d),onReadedLengthChange:We},null,8,[\"chapterIndex\",\"contents\",\"title\",\"spacing\",\"fontSize\",\"fontFamily\"])):pe(\"\",!0)],8,ko))),128)),t(\"div\",{class:\"loading\",ref_key:\"loading\",ref:v},null,512),t(\"div\",{class:\"bottom-bar\",ref_key:\"bottom\",ref:N},null,512)])],4)],6)}}}),bo=Ae(ho,[[\"__scopeId\",\"data-v-fff9fad7\"]]);export{bo as default};\n"
  },
  {
    "path": "app/src/main/assets/web/vue/assets/BookShelf-00b2QCsd.css",
    "content": "@charset \"UTF-8\";.books-wrapper[data-v-0f5f0160]{overflow:auto}.books-wrapper .wrapper[data-v-0f5f0160]{display:grid;grid-template-columns:repeat(auto-fill,380px);justify-content:space-around;grid-gap:10px}.books-wrapper .wrapper .book[data-v-0f5f0160]{-webkit-user-select:none;user-select:none;display:flex;cursor:pointer;margin-bottom:18px;padding:24px;width:360px;flex-direction:row;justify-content:space-around}.books-wrapper .wrapper .book .cover-img[data-v-0f5f0160],.books-wrapper .wrapper .book .cover-img .cover[data-v-0f5f0160]{width:84px;height:112px}.books-wrapper .wrapper .book .info[data-v-0f5f0160]{display:flex;flex-direction:column;justify-content:space-around;align-items:left;height:112px;margin-left:20px;flex:1;overflow:hidden}.books-wrapper .wrapper .book .info .name[data-v-0f5f0160]{width:fit-content;font-size:16px;font-weight:700;color:#33373d}.books-wrapper .wrapper .book .info .sub[data-v-0f5f0160]{display:flex;flex-direction:row;align-items:baseline;justify-content:var(--v2a51eeb0);font-size:12px;font-weight:600;color:#6b6b6b}.books-wrapper .wrapper .book .info .sub .tags[data-v-0f5f0160] .el-tag{margin-right:.5em}.books-wrapper .wrapper .book .info .sub .update-info[data-v-0f5f0160]{display:flex}.books-wrapper .wrapper .book .info .sub .update-info .dot[data-v-0f5f0160]{margin:0 7px}.books-wrapper .wrapper .book .info .intro[data-v-0f5f0160],.books-wrapper .wrapper .book .info .dur-chapter[data-v-0f5f0160],.books-wrapper .wrapper .book .info .last-chapter[data-v-0f5f0160]{color:#969ba3;font-size:13px;margin-top:3px;font-weight:500;word-wrap:break-word;overflow:hidden;text-overflow:ellipsis;display:-webkit-box;-webkit-box-orient:vertical;-webkit-line-clamp:1;line-clamp:1;text-align:left}.books-wrapper .wrapper .book[data-v-0f5f0160]:hover{background:#0000001a;transition-duration:.5s}.books-wrapper .wrapper[data-v-0f5f0160]:last-child{margin-right:auto}.books-wrapper[data-v-0f5f0160]::-webkit-scrollbar{width:0!important}@media screen and (max-width: 750px){.books-wrapper .wrapper[data-v-0f5f0160]{display:flex;flex-direction:column}.books-wrapper .wrapper .book[data-v-0f5f0160]{box-sizing:border-box;width:100%;margin-bottom:0;padding:10px 20px}}body{padding:0;margin:0;height:100vh}#app{font-family:Avenir,Helvetica,Arial,sans-serif;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;color:#2c3e50;margin:0;height:100%}@font-face{font-family:FZZCYSK;src:local(\"☺\"),url(./shelffont-D-W4UqG-.ttf);font-style:normal;font-weight:400}.index-wrapper[data-v-5061e9c0]{height:100%;width:100%;display:flex;flex-direction:row}.index-wrapper .navigation-wrapper[data-v-5061e9c0]{width:260px;min-width:260px;padding:48px 36px;background-color:#f7f7f7}.index-wrapper .navigation-wrapper .navigation-title[data-v-5061e9c0]{font-size:24px;font-weight:500;font-family:FZZCYSK}.index-wrapper .navigation-wrapper .navigation-sub-title[data-v-5061e9c0]{font-size:16px;font-weight:300;font-family:FZZCYSK;margin-top:16px;color:#b1b1b1}.index-wrapper .navigation-wrapper .search-wrapper .search-input[data-v-5061e9c0]{border-radius:50%;margin-top:24px}.index-wrapper .navigation-wrapper .search-wrapper .search-input[data-v-5061e9c0] .el-input__wrapper{border-radius:50px;border-color:#e3e3e3}.index-wrapper .navigation-wrapper .bottom-wrapper[data-v-5061e9c0]{display:flex;flex-direction:column}.index-wrapper .navigation-wrapper .recent-wrapper[data-v-5061e9c0]{margin-top:36px}.index-wrapper .navigation-wrapper .recent-wrapper .recent-title[data-v-5061e9c0]{font-size:14px;color:#b1b1b1;font-family:FZZCYSK}.index-wrapper .navigation-wrapper .recent-wrapper .reading-recent[data-v-5061e9c0]{margin:18px 0}.index-wrapper .navigation-wrapper .recent-wrapper .reading-recent .recent-book[data-v-5061e9c0]{font-size:10px;cursor:pointer}.index-wrapper .navigation-wrapper .setting-wrapper[data-v-5061e9c0]{margin-top:36px}.index-wrapper .navigation-wrapper .setting-wrapper .setting-title[data-v-5061e9c0]{font-size:14px;color:#b1b1b1;font-family:FZZCYSK}.index-wrapper .navigation-wrapper .setting-wrapper .no-point[data-v-5061e9c0]{pointer-events:none}.index-wrapper .navigation-wrapper .setting-wrapper .setting-connect[data-v-5061e9c0]{font-size:8px;margin-top:16px;cursor:pointer}.index-wrapper .navigation-wrapper .bottom-icons[data-v-5061e9c0]{position:fixed;bottom:0;height:120px;width:260px;align-items:center;display:flex;flex-direction:row}.index-wrapper .shelf-wrapper[data-v-5061e9c0]{padding:48px;width:100%;display:flex;flex-direction:column;box-sizing:border-box;overflow:hidden}@media screen and (max-width: 750px){.index-wrapper[data-v-5061e9c0]{overflow-x:hidden;flex-direction:column}.index-wrapper .navigation-wrapper[data-v-5061e9c0]{padding:20px 24px;box-sizing:border-box;width:100%}.index-wrapper .navigation-wrapper .navigation-title-wrapper[data-v-5061e9c0]{white-space:nowrap;display:flex;justify-content:space-between;align-items:flex-end}.index-wrapper .navigation-wrapper .bottom-wrapper[data-v-5061e9c0]{flex-direction:row}.index-wrapper .navigation-wrapper .bottom-wrapper[data-v-5061e9c0]>*{flex-grow:1;margin-top:18px}.index-wrapper .navigation-wrapper .bottom-wrapper>* .reading-recent[data-v-5061e9c0],.index-wrapper .navigation-wrapper .bottom-wrapper>* .setting-item[data-v-5061e9c0]{margin-bottom:0}.index-wrapper .navigation-wrapper .bottom-icons[data-v-5061e9c0]{display:none}.index-wrapper .shelf-wrapper[data-v-5061e9c0]{padding:0;flex-grow:1}.index-wrapper .shelf-wrapper[data-v-5061e9c0] .el-loading-spinner{display:none}}.night .navigation-wrapper[data-v-5061e9c0]{background-color:#454545}.night .navigation-wrapper .navigation-title[data-v-5061e9c0]{color:#aeaeae}.night .navigation-wrapper .search-wrapper .search-input .el-input__wrapper[data-v-5061e9c0]{background-color:#454545}.night .navigation-wrapper .search-wrapper .search-input .el-input__inner[data-v-5061e9c0]{color:#b1b1b1}.night[data-v-5061e9c0] .shelf-wrapper{background-color:#161819}\n"
  },
  {
    "path": "app/src/main/assets/web/vue/assets/BookShelf-DIQtBULC.js",
    "content": "import{d as H,a4 as j,o as d,e as h,h as e,F as N,R as W,z as p,c as $,w as M,g as P,a5 as J,P as x,u as o,C as V,Q as ee,a6 as te,a7 as se,U as oe,f as I,O as ae,G as ne,H as re,D as ie,y as b,M as C,s as le,n as z,a8 as ce}from\"./vendor-KSDcS24u.js\";import{d as de,A as B,i as ue,_ as O,u as he,a as pe,l as ge,s as ve,p as me,b as D,v as fe}from\"./index-Wr40-hHf.js\";import{u as _e}from\"./loading-C4J6hIxs.js\";const we={class:\"books-wrapper\"},Be={class:\"wrapper\"},ke=[\"onClick\"],ye={class:\"cover-img\"},Ae=[\"src\"],Se={class:\"info\"},xe={class:\"name\"},Ie={class:\"sub\"},Ce={class:\"author\"},Re={key:0,class:\"tags\"},Ee={key:1,class:\"update-info\"},Le={class:\"size\"},be={class:\"date\"},ze={key:0,class:\"intro\"},Me={key:1,class:\"dur-chapter\"},Pe={class:\"last-chapter\"},Ve=H({__name:\"BookItems\",props:{books:{},isSearch:{type:Boolean}},emits:[\"bookClick\"],setup(_,{emit:i}){j(g=>({v2a51eeb0:o(A)}));const k=_,R=i,a=g=>R(\"bookClick\",g),y=({bookUrl:g,coverUrl:r})=>r===void 0?B.getProxyCoverUrl(g):ue(r)?B.getProxyCoverUrl(r):r,E=g=>{const r=g.target;r.src=B.getProxyCoverUrl(r.src)},A=V(()=>k.isSearch?\"space-between\":\"flex-start\");return(g,r)=>{const v=J;return d(),h(\"div\",we,[e(\"div\",Be,[(d(!0),h(N,null,W(_.books,n=>{var l;return d(),h(\"div\",{class:\"book\",key:n.bookUrl,onClick:f=>a(n)},[e(\"div\",ye,[(d(),h(\"img\",{class:\"cover\",src:y(n),key:n.coverUrl,onErrorOnce:E,alt:\"\",loading:\"lazy\"},null,40,Ae))]),e(\"div\",Se,[e(\"div\",xe,p(n.name),1),e(\"div\",Ie,[e(\"div\",Ce,p(n.author),1),_.isSearch?(d(),h(\"div\",Re,[(d(!0),h(N,null,W((l=n.kind)==null?void 0:l.split(\",\").slice(0,2),f=>(d(),$(v,{key:f},{default:M(()=>[P(p(f),1)]),_:2},1024))),128))])):x(\"\",!0),_.isSearch?x(\"\",!0):(d(),h(\"div\",Ee,[r[0]||(r[0]=e(\"div\",{class:\"dot\"},\"•\",-1)),e(\"div\",Le,\"共\"+p(n.totalChapterNum)+\"章\",1),r[1]||(r[1]=e(\"div\",{class:\"dot\"},\"•\",-1)),e(\"div\",be,p(o(de)(n.lastCheckTime)),1)]))]),_.isSearch?(d(),h(\"div\",ze,p(n.intro),1)):x(\"\",!0),_.isSearch?x(\"\",!0):(d(),h(\"div\",Me,\" 已读：\"+p(n.durChapterTitle),1)),e(\"div\",Pe,\"最新：\"+p(n.latestChapterTitle),1)])],8,ke)}),128))])])}}}),Te=O(Ve,[[\"__scopeId\",\"data-v-0f5f0160\"]]),Ue=\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAECUlEQVRYR7WXTYhcRRDHq3pY9yKrYBQ8KBsjgvHgwRhiQBTjYZm4Xe8NusawhwS/o9GLoKhgBGPAgJd1NdGIXwtZTbRf9Rqzl6gHTVyDeIkIgnEOghAM6oKHzTJd0sO8Zaa338zb7NjwmJn++Ndv+lVVVyOsoM3Ozl69sLBAiHiDc26NUuoKv9w5d14p9aeI/DI4OMgjIyN/lJXFMhOttQ8BgBaR0TLzEXEGAKzW+lCv+V0BmLmGiLtF5M5eQrFxRPxaRCaI6LOi9YUAzPwGADxxMYYjayaJ6MkoZKyTmU8AwF19Mp7LfElEW0LNZTvAzIcBYFufjedy00T0QLt2B4AxZo9S6qX/yXhT1jn3cpqme3IbSwDM/DgAvNlu3Dm3Uyl1HAA2IOJ2EdleEu5Io9H4EBHPVCqVLSISRsMuInrLazUBpqamhoaGhr4TkRsDgLVpmtbzPmPMLQBwOwD4vvzxw8P5IyJztVrtVL4my7L1iPhTx7Yj/jw/P79pfHx8vgmQZdkLiPhK+O8GBgauqVarv5f819FpxpjLlVJ/hYMi8mKSJHubAMz8KwBcF1EYI6IjqwRIlFImonGWiNZhlmVVRDxWYGTVAMx8HwB8EtMXka1orT0gIo9GJrxNRLH+FW8IMx8EgEeW5QDEgx5gTkQ2Bk7yr9b60hVb6rKAmc8BwJWBne+x4P3XiWhtPwGstV9FzpSzHuBvALgsMHaaiDp2ZbUwWZZNIuKuQOcfD7AAAJeEcaq1Xr9ao+3rmdknnscCzQse4LdWEukYazQaa2q12vl+QTDztwCwOdCr+zA8iYi3RQwREdl+ADDz9QDwIwB0OLaInPJRcEhEHoyEyAmt9d39ALDW2lg1hYjv+lfgC4WJgkTxcJIkPcuqbpC+qgKATwvm7PYAGwDgdBeRZ4notYvZCWPMDqXUe13W3to8C6y10yJyv//u6zj/2R6ziPiRiBwt6xPMrBExFZEdRcYR8WOt9bb8MNoKAJ+3Jvtwed05d4dSKtz+c4h4VGsdrRWttZMici8AXFVix+4homNLBUmWZQcQMc/9x4mommXZ84i4t11MKbV5dHR06bxvH5uZmbnZOfdN6O0RmMNE1CxulgCstdeKyBcAcFPrVTyltZ4wxiSVSuXplkhda72zh9P1rClFZFOSJHMdAP5Hq3rxR6eH+IGIvIOuqFlr94nIc10WdRzxy6riAMJnr2nn3JlcME3TppMWNWvtfhF5pmB8WX0RvZgEEEtaYUUbM2KtfUdE/FUubNHipvBmZIxZp5TaDwBprlQGIHLqzSHiPq01x4B7Xk6Z2d8TfDwPlwFozfd1f90598Hi4uKrY2NjFwrzQVkP81nNi/byAWOMv8gOp2n6fhnt/wDqJrRWLmhIrwAAAABJRU5ErkJggg==\",Ne={class:\"navigation-wrapper\"},We={class:\"search-wrapper\"},De={class:\"bottom-wrapper\"},He={class:\"recent-wrapper\"},Je={class:\"reading-recent\"},Oe={class:\"setting-wrapper\"},Ze={class:\"setting-item\"},Fe={class:\"bottom-icons\"},Ke={href:\"https://github.com/gedoor/legado_web_bookshelf\",target:\"_blank\"},Ye={class:\"bottom-icon\"},Qe=[\"src\"],qe=H({__name:\"BookShelf\",setup(_){const i=he(),k=V(()=>i.isNight),R=s=>{try{s!==void 0&&i.setConfig(s)}catch{z.info(\"阅读界面配置解析错误\")}},a=C({name:\"尚无阅读记录\",author:\"\",bookUrl:\"\",chapterIndex:0,chapterPos:0,isSeachBook:!1}),y=C(),{showLoading:E,closeLoading:A,loadingWrapper:g,isLoading:r}=_e(y,\"正在获取书籍信息\"),v=le([]),n=V(()=>i.shelf),l=C(\"\"),f=C(!1);ee(()=>{if(!(f.value&&l.value!=\"\")){if(f.value=!1,v.value=[],l.value==\"\"){v.value=n.value;return}v.value=n.value.filter(s=>s.name.includes(l.value)||s.author.includes(l.value))}});const T=()=>{l.value!=\"\"&&(v.value=[],i.clearSearchBooks(),E(),f.value=!0,B.search(l.value,s=>{r&&A();try{i.setSearchBooks(s),v.value=i.searchBooks}catch(t){throw z.error(\"后端数据错误\"),t}},()=>{A(),v.value.length==0&&z.info(\"搜索结果为空\")}))},S=pe(),{connectStatus:Z,connectType:F,newConnect:K}=te(S),Y=()=>{ce.prompt(\"请输入 后端地址 ( 如：http://127.0.0.1:9527 或者通过内网穿透的地址)\",\"提示\",{confirmButtonText:\"确定\",cancelButtonText:\"取消\",inputPlaceholder:ge,inputValidator:s=>fe(s),inputErrorMessage:\"输入的格式不对\",beforeClose:(s,t,m)=>{if(s===\"confirm\"){S.setNewConnect(!0),t.confirmButtonLoading=!0,t.confirmButtonText=\"校验中……\";const c=new URL(t.inputValue).toString();B.getReadConfig(c).then(function(u){S.setNewConnect(!1),R(u),t.confirmButtonLoading=!1,i.clearSearchBooks(),ve(...me(c)),c===location.origin?localStorage.removeItem(D):localStorage.setItem(D,c),i.loadBookShelf(),m()}).catch(function(u){throw S.setNewConnect(!1),t.confirmButtonLoading=!1,t.confirmButtonText=\"确定\",u})}else m()}})},Q=se(),q=async s=>{const t=\"respondTime\"in s;t&&await B.saveBook(s);const{bookUrl:m,name:c,author:u,durChapterIndex:w=0,durChapterPos:L=0}=s;U(m,c,u,w,L,t)},U=(s,t,m,c,u,w=!1,L=!1)=>{if(t!==\"尚无阅读记录\"){if(L&&n.value.every(X=>X.bookUrl!==s)){l.value=t,T();return}sessionStorage.setItem(\"bookUrl\",s),sessionStorage.setItem(\"bookName\",t),sessionStorage.setItem(\"bookAuthor\",m),sessionStorage.setItem(\"chapterIndex\",String(c)),sessionStorage.setItem(\"chapterPos\",String(u)),sessionStorage.setItem(\"isSeachBook\",String(w)),a.value={name:t,author:m,bookUrl:s,chapterIndex:c,chapterPos:u,isSeachBook:w},localStorage.setItem(\"readingRecent\",JSON.stringify(a.value)),Q.push({path:\"/chapter\"})}},G=async()=>{await i.loadWebConfig(),await i.saveBookProgress(),await i.loadBookShelf()};return oe(()=>{const s=localStorage.getItem(\"readingRecent\");s!=null&&(a.value=JSON.parse(s),typeof a.value.chapterIndex>\"u\"&&(a.value.chapterIndex=0)),g(G())}),(s,t)=>{const m=ie,c=J,u=Te;return d(),h(\"div\",{class:b({\"index-wrapper\":!0,night:o(k),day:!o(k)})},[e(\"div\",Ne,[t[4]||(t[4]=e(\"div\",{class:\"navigation-title-wrapper\"},[e(\"div\",{class:\"navigation-title\"},\"阅读\"),e(\"div\",{class:\"navigation-sub-title\"},\"清风不识字，何故乱翻书\")],-1)),e(\"div\",We,[I(m,{placeholder:\"搜索书籍，在线书籍自动加入书架\",modelValue:o(l),\"onUpdate:modelValue\":t[0]||(t[0]=w=>re(l)?l.value=w:null),class:\"search-input\",\"prefix-icon\":o(ne),onKeyup:ae(T,[\"enter\"])},null,8,[\"modelValue\",\"prefix-icon\"])]),e(\"div\",De,[e(\"div\",He,[t[2]||(t[2]=e(\"div\",{class:\"recent-title\"},\"最近阅读\",-1)),e(\"div\",Je,[I(c,{type:o(a).name==\"尚无阅读记录\"?\"warning\":\"primary\",class:b([\"recent-book\",{\"no-point\":o(a).bookUrl==\"\"}]),size:\"large\",onClick:t[1]||(t[1]=w=>U(o(a).bookUrl,o(a).name,o(a).author,o(a).chapterIndex,o(a).chapterPos,o(a).isSeachBook,!0))},{default:M(()=>[P(p(o(a).name),1)]),_:1},8,[\"type\",\"class\"])])]),e(\"div\",Oe,[t[3]||(t[3]=e(\"div\",{class:\"setting-title\"},\"基本设定\",-1)),e(\"div\",Ze,[I(c,{type:o(F),size:\"large\",class:b([\"setting-connect\",{\"no-point\":o(K)}]),onClick:Y},{default:M(()=>[P(p(o(Z)),1)]),_:1},8,[\"type\",\"class\"])])])]),e(\"div\",Fe,[e(\"a\",Ke,[e(\"div\",Ye,[e(\"img\",{src:o(Ue),alt:\"\"},null,8,Qe)])])])]),e(\"div\",{class:\"shelf-wrapper\",ref_key:\"shelfWrapper\",ref:y},[I(u,{books:o(v),onBookClick:q,isSearch:o(f)},null,8,[\"books\",\"isSearch\"])],512)],2)}}}),$e=O(qe,[[\"__scopeId\",\"data-v-5061e9c0\"]]);export{$e as default};\n"
  },
  {
    "path": "app/src/main/assets/web/vue/assets/index-CrxHVQK7.css",
    "content": ".el-link[data-v-085627fb]{padding:4px}.el-text[data-v-085627fb]{padding-top:20px}[data-v-d8dae8d3] .el-checkbox__label{flex:1;display:flex;justify-content:space-between;align-items:center}.error[data-v-d8dae8d3]{border-color:var(--el-color-error)!important;color:var(--el-color-error)!important;--el-checkbox-checked-text-color: var(--el-color-error);--el-checkbox-checked-bg-color: var(--el-color-error);--el-checkbox-checked-input-border-color: var(--el-color-error)}.edit[data-v-d8dae8d3]{border-color:var(--el-color-dark)!important}.tool[data-v-258cd99b]{display:flex;margin:4px 0;justify-content:center}#source-list[data-v-258cd99b]{margin-top:6px;height:calc(100vh - 119px)}#source-list[data-v-258cd99b] .el-checkbox{margin-bottom:4px;width:100%}[data-v-3ac68c8a] #debug-text{height:calc(100vh - 86px)}[data-v-f62d9369] .el-input{width:100%}[data-v-f62d9369] #source-json{height:calc(100vh - 50px)}[data-v-fd81540f] .el-tabs__header{margin-bottom:5px}.flex-space-between[data-v-096adf6d]{display:flex;justify-content:space-between;align-items:baseline}.flex-column-center[data-v-096adf6d]{display:flex;flex-direction:column;justify-content:center}.menu>.el-button[data-v-096adf6d]{margin:4px;padding:1em;width:6em}.hotkeys-item .title[data-v-096adf6d]{width:5em;display:flex;justify-content:flex-end;margin-right:1em}.hotkeys-item .hotkeys-item__content[data-v-096adf6d]{display:flex;flex-wrap:wrap;flex:1}.hotkeys-item .hotkeys-item__content div[data-v-096adf6d]{margin-bottom:1em}.hotkeys-item .hotkeys-item__content span[data-v-096adf6d]{margin:.5em}[data-v-c07c5146] .el-tab-pane{height:calc(100vh - 55px);padding-top:15px;padding-right:5px;overflow-y:auto}[data-v-c07c5146] .el-tabs__header{margin:0}kbd{align-items:center;background:#7d7d7d1a;border-radius:3px;border:0;padding:4px 5px;font-weight:700;box-shadow:inset 0 -2px #cdcde6,inset 0 0 1px 1px #fff,0 1px 2px 1px #1e235a66}code{border-radius:4px;padding:.15rem .5rem;background-color:var(--el-fill-color-light);transition:color .25s,background-color .5s;font-size:14px}body{padding:0;margin:0}.el-tabs__header{position:sticky;top:0;z-index:2}.editor[data-v-f2c47af3]{display:flex;height:100vh;overflow:hidden}.editor .left[data-v-f2c47af3]{flex:1;margin-left:20px}.editor .right[data-v-f2c47af3]{flex:1;width:360px;margin-right:20px}\n"
  },
  {
    "path": "app/src/main/assets/web/vue/assets/index-Wr40-hHf.js",
    "content": "const __vite__mapDeps=(i,m=__vite__mapDeps,d=(m.f||(m.f=[\"./BookShelf-DIQtBULC.js\",\"./vendor-KSDcS24u.js\",\"./vendor-CXe1BRiH.css\",\"./loading-C4J6hIxs.js\",\"./loading-DkQYEuap.css\",\"./BookShelf-00b2QCsd.css\",\"./BookChapter-Cs3stH93.js\",\"./BookChapter-BsiFtdIw.css\"])))=>i.map(i=>d[i]);\nimport{r as Ke,o as g,c as C,a as le,b as ae,d as K,e as E,F as N,f as h,E as We,u as i,l as V,w as p,g as y,h as f,i as Ce,j as He,k as Me,m as ce,t as Ue,n as k,p as me,s as Se,q as Fe,v as Be,x as qe,y as Ee,z as ee,A as ue,B as xe,C as A,D as te,G as Le,H as M,I as ze,J as Ge,K as fe,L as Ye,V as Qe,M as J,N as de,O as Xe,P as I,Q as Ze,R as W,S as Re,T as Ie,U as et,W as tt,X as ot,Y as z,Z as nt,_ as rt,$ as st,a0 as it,a1 as lt,a2 as at,a3 as ct}from\"./vendor-KSDcS24u.js\";(function(){const o=document.createElement(\"link\").relList;if(o&&o.supports&&o.supports(\"modulepreload\"))return;for(const r of document.querySelectorAll('link[rel=\"modulepreload\"]'))n(r);new MutationObserver(r=>{for(const s of r)if(s.type===\"childList\")for(const c of s.addedNodes)c.tagName===\"LINK\"&&c.rel===\"modulepreload\"&&n(c)}).observe(document,{childList:!0,subtree:!0});function t(r){const s={};return r.integrity&&(s.integrity=r.integrity),r.referrerPolicy&&(s.referrerPolicy=r.referrerPolicy),r.crossOrigin===\"use-credentials\"?s.credentials=\"include\":r.crossOrigin===\"anonymous\"?s.credentials=\"omit\":s.credentials=\"same-origin\",s}function n(r){if(r.ep)return;r.ep=!0;const s=t(r);fetch(r.href,s)}})();const j=(e,o)=>{const t=e.__vccOpts||e;for(const[n,r]of o)t[n]=r;return t},ut={};function dt(e,o){const t=Ke(\"router-view\");return g(),C(t)}const Te=j(ut,[[\"render\",dt]]),pt=\"modulepreload\",gt=function(e,o){return new URL(e,o).href},ye={},be=function(o,t,n){let r=Promise.resolve();if(t&&t.length>0){const c=document.getElementsByTagName(\"link\"),a=document.querySelector(\"meta[property=csp-nonce]\"),_=(a==null?void 0:a.nonce)||(a==null?void 0:a.getAttribute(\"nonce\"));r=Promise.allSettled(t.map(w=>{if(w=gt(w,n),w in ye)return;ye[w]=!0;const U=w.endsWith(\".css\"),x=U?'[rel=\"stylesheet\"]':\"\";if(!!n)for(let b=c.length-1;b>=0;b--){const v=c[b];if(v.href===w&&(!U||v.rel===\"stylesheet\"))return}else if(document.querySelector(`link[href=\"${w}\"]${x}`))return;const l=document.createElement(\"link\");if(l.rel=U?\"stylesheet\":pt,U||(l.as=\"script\"),l.crossOrigin=\"\",l.href=w,_&&l.setAttribute(\"nonce\",_),document.head.appendChild(l),U)return new Promise((b,v)=>{l.addEventListener(\"load\",b),l.addEventListener(\"error\",()=>v(new Error(`Unable to preload CSS for ${w}`)))})}))}function s(c){const a=new Event(\"vite:preloadError\",{cancelable:!0});if(a.payload=c,window.dispatchEvent(a),!a.defaultPrevented)throw c}return r.then(c=>{for(const a of c||[])a.status===\"rejected\"&&s(a.reason);return o().catch(s)})},Ve=[{path:\"/\",name:\"shelf\",component:()=>be(()=>import(\"./BookShelf-DIQtBULC.js\"),__vite__mapDeps([0,1,2,3,4,5]),import.meta.url)},{path:\"/chapter\",name:\"chapter\",component:()=>be(()=>import(\"./BookChapter-Cs3stH93.js\"),__vite__mapDeps([6,1,2,3,4,7]),import.meta.url)}];le({history:ae(),routes:Ve});const ht={style:{\"margin-top\":\"20px\"}},mt=K({__name:\"SourceHelp\",setup(e){return(o,t)=>{const n=We,r=Ce;return g(),E(N,null,[h(n,{icon:i(V),href:\"/help/#appHelp\",target:\"_blank\"},{default:p(()=>[...t[0]||(t[0]=[y(\"APP帮助文档\",-1)])]),_:1},8,[\"icon\"]),t[19]||(t[19]=f(\"br\",null,null,-1)),h(n,{icon:i(V),href:\"/help/#ruleHelp\",target:\"_blank\"},{default:p(()=>[...t[1]||(t[1]=[y(\"书源制作教程\",-1)])]),_:1},8,[\"icon\"]),t[20]||(t[20]=f(\"br\",null,null,-1)),h(n,{icon:i(V),href:\"/help/#jsHelp\",target:\"_blank\"},{default:p(()=>[...t[2]||(t[2]=[y(\"js变量和函数\",-1)])]),_:1},8,[\"icon\"]),t[21]||(t[21]=f(\"br\",null,null,-1)),h(n,{icon:i(V),href:\"/help/#xpathHelp\",target:\"_blank\"},{default:p(()=>[...t[3]||(t[3]=[y(\"xpath语法教程\",-1)])]),_:1},8,[\"icon\"]),t[22]||(t[22]=f(\"br\",null,null,-1)),h(n,{icon:i(V),href:\"/help/#regexHelp\",target:\"_blank\"},{default:p(()=>[...t[4]||(t[4]=[y(\"正则表达式教程\",-1)])]),_:1},8,[\"icon\"]),t[23]||(t[23]=f(\"br\",null,null,-1)),h(n,{icon:i(V),href:\"/help/#txtTocRuleHelp\",target:\"_blank\"},{default:p(()=>[...t[5]||(t[5]=[y(\"txt目录正则说明\",-1)])]),_:1},8,[\"icon\"]),t[24]||(t[24]=f(\"br\",null,null,-1)),h(n,{icon:i(V),href:\"/help/#debugHelp\",target:\"_blank\"},{default:p(()=>[...t[6]||(t[6]=[y(\"书源调试说明\",-1)])]),_:1},8,[\"icon\"]),t[25]||(t[25]=f(\"br\",null,null,-1)),h(n,{icon:i(V),href:\"/help/#httpTTSHelp\",target:\"_blank\"},{default:p(()=>[...t[7]||(t[7]=[y(\"在线朗读规则\",-1)])]),_:1},8,[\"icon\"]),t[26]||(t[26]=f(\"br\",null,null,-1)),h(n,{icon:i(V),href:\"/help/#webDavBookHelp\",target:\"_blank\"},{default:p(()=>[...t[8]||(t[8]=[y(\" WebDav书籍简明使用教程\",-1)])]),_:1},8,[\"icon\"]),t[27]||(t[27]=f(\"br\",null,null,-1)),h(n,{icon:i(V),href:\"/help/#webDavHelp\",target:\"_blank\"},{default:p(()=>[...t[9]||(t[9]=[y(\" WebDav备份教程\",-1)])]),_:1},8,[\"icon\"]),t[28]||(t[28]=f(\"br\",null,null,-1)),h(n,{icon:i(V),href:\"https://regexr-cn.com/\",target:\"_blank\"},{default:p(()=>[...t[10]||(t[10]=[y(\"正则表达式在线验证工具\",-1)])]),_:1},8,[\"icon\"]),t[29]||(t[29]=f(\"br\",null,null,-1)),f(\"div\",ht,[f(\"span\",null,[h(r,null,{default:p(()=>[...t[11]||(t[11]=[f(\"code\",null,\"^$()[]{}.?+*|\",-1),y(\" 这些是Java正则特殊符号,匹配需转义\",-1)])]),_:1})]),t[15]||(t[15]=f(\"br\",null,null,-1)),f(\"span\",null,[h(r,null,{default:p(()=>[...t[12]||(t[12]=[f(\"code\",null,\"(?s)\",-1),y(\" 前缀表示跨行解析\",-1)])]),_:1})]),t[16]||(t[16]=f(\"br\",null,null,-1)),f(\"span\",null,[h(r,null,{default:p(()=>[...t[13]||(t[13]=[f(\"code\",null,\"(?m)\",-1),y(\" 前缀表示逐行匹配\",-1)])]),_:1})]),t[17]||(t[17]=f(\"br\",null,null,-1)),f(\"span\",null,[h(r,null,{default:p(()=>[...t[14]||(t[14]=[f(\"code\",null,\"(?i)\",-1),y(\" 前缀表示忽略大小写\",-1)])]),_:1})]),t[18]||(t[18]=f(\"br\",null,null,-1))])],64)}}}),St=j(mt,[[\"__scopeId\",\"data-v-085627fb\"]]),ft=\"remoteUrl\",yt=1e3,B=He.create({baseURL:localStorage.getItem(ft)||location.origin,timeout:120*yt});let $=\"\",pe=\"\",ge=()=>{},H=()=>{};const bt=e=>H=e,_t=e=>{ge=e},kt=(e,o)=>{$=new URL(e).toString(),pe=new URL(o).toString(),B.defaults.baseURL=$},vt=async(e=$)=>{const{data:o}=await B.get(\"getReadConfig\",{baseURL:e.toString(),timeout:3e3});if(o.isSuccess)try{return JSON.parse(o.data)}catch{}},wt=e=>B.post(\"saveReadConfig\",e),Ct=e=>B.post(\"saveBookProgress\",e),Ut=e=>{e&&navigator.sendBeacon(new URL(\"saveBookProgress\",$),JSON.stringify(e))},Bt=()=>B.get(\"getBookshelf\"),Et=e=>B.get(\"getChapterList?url=\"+encodeURIComponent(e)),xt=(e,o)=>B.get(\"getBookContent?url=\"+encodeURIComponent(e)+\"&index=\"+o),Lt=(e,o,t)=>{const n=new WebSocket(new URL(\"searchBook\",pe));n.onerror=ge,n.onopen=()=>{n.send(`{\"key\":\"${e}\"}`)},n.onmessage=r=>{try{o(JSON.parse(r.data)),H==null||H.call(n,r)}catch{t()}},n.onclose=()=>{t()}},Rt=e=>B.post(\"saveBook\",e),It=e=>B.post(\"deleteBook\",e),Z=/bookSource/i.test(location.href),Tt=()=>Z?B.get(\"getBookSources\"):B.get(\"getRssSources\"),Vt=e=>Z?B.post(\"saveBookSource\",e):B.post(\"saveRssSource\",e),Nt=e=>Z?B.post(\"saveBookSources\",e):B.post(\"saveRssSources\",e),Ot=e=>Z?B.post(\"deleteBookSources\",e):B.post(\"deleteRssSources\",e),Dt=(e,o,t,n)=>{const r=new URL(`${Z?\"bookSource\":\"rssSource\"}Debug`,pe),s=new WebSocket(r);s.onerror=ge,s.onopen=()=>{s.send(JSON.stringify({tag:e,key:o}))},s.onmessage=c=>{t(c.data),H==null||H.call(s,c)},s.onclose=()=>{n()}},Jt=e=>e.startsWith($)?e:new URL(\"cover?path=\"+encodeURIComponent(e),$).toString(),Pt=(e,o,t)=>o.startsWith($)?o:new URL(\"image?path=\"+encodeURIComponent(o)+\"&url=\"+encodeURIComponent(e)+\"&width=\"+t,$).toString(),P={getReadConfig:vt,saveReadConfig:wt,saveBookProgress:Ct,saveBookProgressWithBeacon:Ut,getBookShelf:Bt,getChapterList:Et,getBookContent:xt,search:Lt,saveBook:Rt,deleteBook:It,getSources:Tt,saveSources:Nt,saveSource:Vt,deleteSource:Ot,debug:Dt,getProxyCoverUrl:Jt,getProxyImageUrl:Pt},X=e=>e==null||e.length===0||/^\\s+$/.test(e),Uo=e=>/,\\s*\\{/.test(e)||!(e.startsWith(\"http\")||e.startsWith(\"data:\")||e.startsWith(\"blob:\")),$t=(e,o=[\"https:\",\"http:\"])=>{try{const t=new URL(e),{protocol:n}=t;if(!o.includes(n))throw new Error(`Expected protocol ${o.join(\"/\")}, but ${n}`);return!0}catch{return!1}},Bo=e=>{const o=new Date().getTime(),t=Math.floor((o-e)/1e3);let n=\"\";return t<=30?n=\"刚刚\":t<60?n=t+\"秒前\":t<3600?n=Math.floor(t/60)+\"分钟前\":t<86400?n=Math.floor(t/3600)+\"小时前\":t<2592e3?n=Math.floor(t/86400)+\"天前\":n=Me(new Date(e),\"YYYY-MM-DD\"),n},jt={theme:0,font:0,fontSize:18,readWidth:800,infiniteLoading:!1,customFontName:\"\",jumpDuration:1e3,spacing:{paragraph:1,line:.8,letter:0}};let _e;const At=ce(\"book\",{state:()=>({searchBooks:[],shelf:[],catalog:[],readingBook:{chapterPos:0,chapterIndex:0},popCataVisible:!1,contentLoading:!0,showContent:!1,config:jt,miniInterface:!1,readSettingsVisible:!1}),getters:{bookProgress:e=>{var c;if(e.catalog.length==0)return;const{chapterIndex:o,chapterPos:t,name:n,author:r}=e.readingBook,s=(c=e.catalog[o])==null?void 0:c.title;if(s)return{name:n,author:r,durChapterIndex:o,durChapterPos:t,durChapterTime:new Date().getTime(),durChapterTitle:s}},theme:e=>e.config.theme,isNight:e=>e.config.theme==6},actions:{async loadBookShelf(){const e=P.getBookShelf().then(o=>{const{isSuccess:t,data:n,errorMsg:r}=o.data;if(t===!0)this.shelf.length!==n.length&&this.shelf.length>0&&n.length>0&&k.info(\"书架数据已更新\"),this.shelf=n.sort((s,c)=>{const a=s.durChapterTime||0;return(c.durChapterTime||0)-a});else{if(r.includes(\"还没有添加小说\")&&this.shelf.length>0)return k.info(\"当前书架上的书籍已经被删除\"),this.shelf=[];k.error(r??\"后端返回格式错误！\")}return this.shelf});return this.shelf.length>0?this.shelf:await e},async loadWebCatalog(e){const{bookUrl:o,name:t,chapterIndex:n}=e,r=P.getChapterList(o).then(s=>{const{isSuccess:c,data:a,errorMsg:_}=s.data;if(c===!1)throw k.error(_),new Error;return o===this.readingBook.bookUrl&&a.length!==this.catalog.length&&a.length>0&&this.catalog.length>0&&k.info(`书籍${t}: 章节目录已更新`),this.catalog=a,this.catalog});return o===this.readingBook.bookUrl&&this.catalog.length>0&&this.catalog.length-1>=n?this.catalog:await r},setPopCataVisible(e){this.popCataVisible=e},setContentLoading(e){this.contentLoading=e},setReadingBook(e){this.readingBook=e},async loadWebConfig(){if(_e===void 0){const e=await P.getReadConfig();return _e=new Date,this.setConfig(e)}},setConfig(e){this.config=Object.assign({},this.config,e)},setReadSettingsVisible(e){this.readSettingsVisible=e},setShowContent(e){this.showContent=e},setMiniInterface(e){this.miniInterface=e},async setSearchBooks(e){e.forEach(o=>{this.shelf.every(n=>n.bookUrl!==o.bookUrl)===!0&&this.searchBooks.push(o)})},clearSearchBooks(){this.searchBooks=[]},async saveBookProgress(){if(!this.bookProgress)return Promise.resolve();const{bookUrl:e}=this.readingBook,o=Ue(this.shelf),t=o.findIndex(n=>n.bookUrl===e);return t>-1&&(this.shelf[t]=Object.assign({},o[t],this.bookProgress)),P.saveBookProgressWithBeacon(this.bookProgress)}}}),oe=e=>\"bookSourceName\"in e,Kt=e=>oe(e)?!X(e.bookSourceName)&&!X(e.bookSourceUrl)&&!X(e.bookSourceType):!X(e.sourceName)&&!X(e.sourceUrl),ne=e=>oe(e)?e.bookSourceUrl:e.sourceUrl,he=e=>oe(e)?e.bookSourceName:e.sourceName,Wt=(e,o)=>{var t,n,r,s;return oe(e)?(e.bookSourceName.includes(o)||e.bookSourceUrl.includes(o)||((t=e.bookSourceGroup)==null?void 0:t.includes(o))||((n=e.bookSourceComment)==null?void 0:n.includes(o)))??!1:(e.sourceName.includes(o)||e.sourceUrl.includes(o)||((r=e.sourceGroup)==null?void 0:r.includes(o))||((s=e.sourceComment)==null?void 0:s.includes(o)))??!1},ie=e=>{const o=new Map;return e.forEach(t=>o.set(ne(t),t)),o},Ne=e=>{for(const o in e){const t=e[o];t===\"\"||t===null||typeof t==\"string\"&&!t.trim()?delete e[o]:t instanceof Object&&Ne(t)}},Ht={ruleSearch:{},ruleBookInfo:{},ruleToc:{},ruleContent:{},ruleExplore:{}},Mt={},G=/bookSource/i.test(location.href),ke=G?Ht:Mt,F=ce(\"source\",{state:()=>({bookSources:Se([]),rssSources:Se([]),savedSources:[],currentSource:JSON.parse(JSON.stringify(ke)),currentTab:localStorage.getItem(\"tabName\")||\"editTab\",editTabSource:{},isDebuging:!1}),getters:{sources:e=>G?e.bookSources:e.rssSources,sourcesMap:function(){return ie(this.sources)},savedSourcesMap:e=>ie(e.savedSources),currentSourceUrl:e=>G?e.currentSource.bookSourceUrl:e.currentSource.sourceUrl,searchKey:e=>{var o,t;return G?((t=(o=e.currentSource)==null?void 0:o.ruleSearch)==null?void 0:t.checkKeyWord)||\"我的\":\"\"}},actions:{startDebug(){this.currentTab=\"editDebug\",this.isDebuging=!0},debugFinish(){this.isDebuging=!1},saveSources(e){G?this.bookSources=me(e):this.rssSources=me(e)},setPushReturnSources(e){this.savedSources=e},deleteSources(e){const o=G?this.bookSources:this.rssSources;e.forEach(t=>{const n=o.indexOf(t);n>-1&&o.splice(n,1)})},saveCurrentSource(){const e=this.currentSource,o=this.sourcesMap;o.set(ne(e),JSON.parse(JSON.stringify(e))),this.saveSources(Array.from(o.values()))},changeCurrentSource(e){this.currentSource=JSON.parse(JSON.stringify(e))},changeTabName(e){this.currentTab=e,localStorage.setItem(\"tabName\",e)},changeEditTabSource(e){this.editTabSource=JSON.parse(JSON.stringify(e))},editHistory(e){let o;if(localStorage.getItem(\"history\"))o=JSON.parse(localStorage.getItem(\"history\")),o.new.push(e),o.new.length>50&&o.new.shift(),o.old.length>50&&o.old.shift(),localStorage.setItem(\"history\",JSON.stringify(o));else{const t={new:[e],old:[]};localStorage.setItem(\"history\",JSON.stringify(t))}},editHistoryUndo(){if(localStorage.getItem(\"history\")){const e=JSON.parse(localStorage.getItem(\"history\"));e.old.push(this.currentSource),e.new.length&&(this.currentSource=e.new.pop()),localStorage.setItem(\"history\",JSON.stringify(e))}},clearAllHistory(){localStorage.setItem(\"history\",JSON.stringify({new:[],old:[]}))},clearEdit(){this.editTabSource={},this.currentSource=JSON.parse(JSON.stringify(ke))},clearAllSource(){this.bookSources=[],this.rssSources=[],this.savedSources=[]}}}),Ft=ce(\"connection\",{state:()=>({connectStatus:\"正在连接后端服务器……\",connectType:\"primary\",newConnect:!1}),actions:{setConnectStatus(e){this.newConnect!==!0&&(this.connectStatus=e)},setConnectType(e){this.newConnect!==!0&&(this.connectType=e)},setNewConnect(e){this.newConnect=e}}}),Oe=Fe();Be(Te).use(Oe);const Y=Ft(),ve=Array.of(\"isSuccess\",\"errorMsg\"),De=k,qt=e=>{let o=!0;try{const t=e.data;for(const n of ve)n in t||(o=!1,ve.length=0);t.isSuccess===!0&&(\"data\"in t||(o=!1))}catch{o=!1}if(o===!1)throw De.warning({message:\"后端返回内容格式错误\",grouping:!0}),new Error;return Y.setConnectType(\"primary\"),Y.setConnectStatus(\"已连接 \"+$),e},Je=e=>{throw De.error({message:\"后端连接失败，请检查阅读WEB服务或者设置其它可用链接\",grouping:!0}),Y.setConnectType(\"danger\"),Y.setConnectStatus(\"连接异常\"),e};B.interceptors.response.use(qt,Je);_t(Je);bt(()=>{Y.setConnectType(\"primary\"),Y.setConnectStatus(\"已连接 \"+$)});const zt=e=>{let o=new URL(location.origin);$t(e)&&(o=new URL(e));const{protocol:t,port:n}=o;let r;n!==\"\"?r=String(Number(n)+1):r=t.startsWith(\"https:\")?\"444\":\"81\";const s=t.startsWith(\"https:\")?\"wss://\":\"ws://\",c=o.toString();o.protocol=s,o.port=r;const a=o.toString();return[c,a]};kt(...zt(B.defaults.baseURL));const Gt=K({__name:\"SourceItem\",props:{source:{}},setup(e){const o=e,t=F(),n=A(()=>t.currentSourceUrl),r=A(()=>ne(o.source)),s=a=>{t.changeCurrentSource(a)},c=A(()=>{const a=t.savedSourcesMap;return a.size==0?!1:!a.has(r.value)});return(a,_)=>{const w=ue,U=qe;return g(),C(U,{size:\"large\",border:\"\",value:i(r),class:Ee({error:i(c),edit:i(r)==i(n)})},{default:p(()=>[y(ee(i(he)(e.source))+\" \",1),h(w,{text:\"\",icon:i(xe),onClick:_[0]||(_[0]=x=>s(e.source))},null,8,[\"icon\"])]),_:1},8,[\"value\",\"class\"])}}}),Yt=j(Gt,[[\"__scopeId\",\"data-v-d8dae8d3\"]]),Qt={class:\"tool\"},Xt=K({__name:\"SourceList\",setup(e){const o=F(),t=J([]),n=J(\"\"),r=A(()=>o.sources),s=A(()=>{const d=n.value;return d===\"\"?r.value:r.value.filter(l=>Wt(l,d))}),c=A(()=>{const d=t.value;if(d.length==0)return[];const l=n.value==\"\"?o.sourcesMap:ie(s.value);return d.reduce((b,v)=>{const L=l.get(v);return L&&b.push(L),b},[])}),a=()=>{const d=c.value;P.deleteSource(d).then(({data:l})=>{if(!l.isSuccess)return k.error(l.errorMsg);o.deleteSources(d);const b=Ue(t.value);d.forEach(v=>{const L=b.indexOf(ne(v));L>-1&&b.splice(L,1)}),t.value=b})},_=()=>{o.clearAllSource(),t.value=[]},w=()=>{const d=document.createElement(\"input\");d.type=\"file\",d.accept=\".json,.txt\",d.addEventListener(\"change\",()=>{const l=d.files;if(l===null)return k.info(\"未选择文件\");const b=new FileReader;b.readAsText(l[0]),b.onload=()=>{try{const v=JSON.parse(b.result);o.saveSources(v)}catch(v){k.error(\"上传的源格式错误: \"+v.message)}}}),d.click()},U=/bookSource/i.test(window.location.href),x=()=>{const d=document.createElement(\"a\"),l=t.value.length===0?s.value:c.value,b=U?\"BookSource\":\"RssSource\";d.download=`${b}_${Date().replace(/.*?\\s(\\d+)\\s(\\d+)\\s(\\d+:\\d+:\\d+).*/,\"$2$1$3\").replace(/:/g,\"\")}.json`;const v=new Blob([JSON.stringify(l,null,4)],{type:\"application/json\"});d.href=window.URL.createObjectURL(v),d.click(),window.URL.revokeObjectURL(d.href)};return(d,l)=>{const b=te,v=ue,L=Ye;return g(),E(N,null,[h(b,{modelValue:i(n),\"onUpdate:modelValue\":l[0]||(l[0]=O=>M(n)?n.value=O:null),class:\"search\",\"prefix-icon\":i(Le),placeholder:\"筛选源\"},null,8,[\"modelValue\",\"prefix-icon\"]),f(\"div\",Qt,[h(v,{onClick:w,icon:i(ze)},{default:p(()=>[...l[2]||(l[2]=[y(\"打开\",-1)])]),_:1},8,[\"icon\"]),h(v,{disabled:i(s).length===0,onClick:x,icon:i(Ge)},{default:p(()=>[...l[3]||(l[3]=[y(\" 导出\",-1)])]),_:1},8,[\"disabled\",\"icon\"]),h(v,{type:\"danger\",icon:i(fe),onClick:a,disabled:i(c).length===0},{default:p(()=>[...l[4]||(l[4]=[y(\"删除\",-1)])]),_:1},8,[\"icon\",\"disabled\"]),h(v,{type:\"danger\",icon:i(fe),onClick:_,disabled:i(r).length===0},{default:p(()=>[...l[5]||(l[5]=[y(\"清空\",-1)])]),_:1},8,[\"icon\",\"disabled\"])]),h(L,{id:\"source-list\",modelValue:i(t),\"onUpdate:modelValue\":l[1]||(l[1]=O=>M(t)?t.value=O:null)},{default:p(()=>[h(i(Qe),{style:{height:\"100%\",\"overflow-y\":\"auto\",\"overflow-x\":\"hidden\"},\"data-key\":O=>i(he)(O),\"data-sources\":i(s),\"data-component\":Yt,\"estimate-size\":45},null,8,[\"data-key\",\"data-sources\"])]),_:1},8,[\"modelValue\"])],64)}}}),Zt=j(Xt,[[\"__scopeId\",\"data-v-258cd99b\"]]),eo=K({__name:\"SourceDebug\",setup(e){const o=F(),t=J(\"\"),n=J(\"\");de(()=>o.isDebuging,()=>{o.isDebuging&&s()});const r=a=>{const _=document.querySelector(\"#debug-text\");_.scrollTop=_.scrollHeight,t.value+=a+`\n`},s=async()=>{t.value=\"\";try{await P.saveSource(o.currentSource)}catch(a){throw o.debugFinish(),a}P.debug(o.currentSourceUrl,n.value||o.searchKey,r,o.debugFinish)},c=A(()=>/bookSource/i.test(window.location.href));return(a,_)=>{const w=te;return g(),E(N,null,[i(c)?(g(),C(w,{key:0,id:\"debug-key\",modelValue:i(n),\"onUpdate:modelValue\":_[0]||(_[0]=U=>M(n)?n.value=U:null),placeholder:\"搜索书名、作者\",\"prefix-icon\":i(Le),style:{\"padding-bottom\":\"4px\"},onKeydown:Xe(s,[\"enter\"])},null,8,[\"modelValue\",\"prefix-icon\"])):I(\"\",!0),h(w,{id:\"debug-text\",modelValue:i(t),\"onUpdate:modelValue\":_[1]||(_[1]=U=>M(t)?t.value=U:null),type:\"textarea\",readonly:\"\",rows:29,placeholder:\"这里用于输出调试信息\"},null,8,[\"modelValue\"])],64)}}}),to=j(eo,[[\"__scopeId\",\"data-v-3ac68c8a\"]]),oo=K({__name:\"SourceJson\",setup(e){const o=F(),t=J(\"\"),n=async r=>{try{o.changeEditTabSource(JSON.parse(r))}catch{k({message:\"粘贴的源格式错误\",type:\"error\"})}};return Ze(async()=>{const r=o.editTabSource;Object.keys(r).length>0?t.value=JSON.stringify(r,null,4):t.value=\"\"}),(r,s)=>{const c=te;return g(),C(c,{id:\"source-json\",modelValue:i(t),\"onUpdate:modelValue\":s[0]||(s[0]=a=>M(t)?t.value=a:null),type:\"textarea\",placeholder:\"这里输出序列化的JSON数据,可直接导入'阅读'APP\",rows:30,onChange:n,style:{\"margin-bottom\":\"4px\"}},null,8,[\"modelValue\"])}}}),no=j(oo,[[\"__scopeId\",\"data-v-f62d9369\"]]),ro=K({__name:\"SourceTabTools\",setup(e){const o=F(),t=A({get:()=>o.currentTab,set:r=>o.currentTab=r}),n=J([[\"editTab\",\"编辑源\"],[\"editDebug\",\"调试源\"],[\"editList\",\"源列表\"],[\"editHelp\",\"帮助信息\"]]);return(r,s)=>{const c=no,a=to,_=Zt,w=St,U=Re,x=Ie;return g(),C(x,{modelValue:i(t),\"onUpdate:modelValue\":s[0]||(s[0]=d=>M(t)?t.value=d:null)},{default:p(()=>[(g(!0),E(N,null,W(i(n),(d,l)=>(g(),C(U,{key:d[0],name:d[0],label:d[1]},{default:p(()=>[l==0?(g(),C(c,{key:0})):I(\"\",!0),l==1?(g(),C(a,{key:1})):I(\"\",!0),l==2?(g(),C(_,{key:2})):I(\"\",!0),l==3?(g(),C(w,{key:3})):I(\"\",!0)]),_:2},1032,[\"name\",\"label\"]))),128))]),_:1},8,[\"modelValue\"])}}}),so=j(ro,[[\"__scopeId\",\"data-v-fd81540f\"]]),io={class:\"menu flex-column-center\"},lo={class:\"hotkeys-header flex-space-between\"},ao=[\"id\"],co={key:0},uo={class:\"hotkeys-settings flex-column-center\"},po={class:\"title\"},go={class:\"hotkeys-item__content\"},ho={key:0},mo={key:0},So=K({__name:\"ToolBar\",setup(e){const o=F(),t=()=>{const S=k({message:\"加载中……\",showClose:!0,duration:0});P.getSources().then(({data:u})=>{u.isSuccess?(o.changeTabName(\"editList\"),o.saveSources(u.data),k({message:`成功拉取${u.data.length}条源`,type:\"success\"})):k({message:u.errorMsg??\"后端错误\",type:\"error\"})}).finally(()=>S.close())},n=()=>{const S=o.sources;if(o.changeTabName(\"editList\"),S.length===0)return k({message:\"空空如也\",type:\"info\"});k({message:\"正在推送中\",type:\"info\"}),P.saveSources(S).then(({data:u})=>{if(u.isSuccess){const m=u.data;if(Array.isArray(m)){let D=\"\";S.length>m.length&&(D=`\n推送失败的源将用红色字体标注!`,o.setPushReturnSources(m)),k({message:`批量推送源到「阅读3.0APP」\n共计: ${S.length} 条\n成功: ${m.length} 条\n失败: ${S.length-m.length} 条${D}`,type:\"success\"})}}else k({message:`批量推送源失败!\nErrorMsg: ${u.errorMsg}`,type:\"error\"})})},r=()=>{o.changeTabName(\"editTab\"),o.changeEditTabSource(o.currentSource)},s=()=>{o.changeCurrentSource(o.editTabSource)},c=()=>{o.editHistoryUndo()},a=()=>{o.clearEdit(),k({message:\"已清除\",type:\"success\"})},_=()=>{o.clearEdit(),o.clearAllHistory(),k({message:\"已清除所有历史记录\",type:\"success\"})},w=()=>{const S=o.currentSource;Kt(S)?(Ne(S),P.saveSource(S).then(({data:u})=>{const m=he(S);u.isSuccess?(k({message:`源《${m}》已成功保存到「阅读3.0APP」`,type:\"success\"}),o.saveCurrentSource()):k({message:`源《${m}》保存失败!\nErrorMsg: ${u.errorMsg}`,type:\"error\"})})):k({message:\"请检查<必填>项是否全部填写\",type:\"error\"})},U=()=>{o.startDebug()},x=J(Array.of({name:\"⇈推送源\",hotKeys:[],action:n},{name:\"⇊拉取源\",hotKeys:[],action:t},{name:\"⋙生成源\",hotKeys:[],action:r},{name:\"⋘编辑源\",hotKeys:[],action:s},{name:\"✗清空表单\",hotKeys:[],action:a},{name:\"↶撤销操作\",hotKeys:[],action:c},{name:\"↷重做操作\",hotKeys:[],action:_},{name:\"⇏调试源\",hotKeys:[],action:U},{name:\"✓保存源\",hotKeys:[],action:w})),d=J(!0),l=J(!1),b=J(-1),v=()=>{l.value||(d.value=!1),l.value=!1};de(d,S=>{if(!S){z.unbind(\"*\"),Q(),q();return}Q(),z.unbind(),z(\"*\",u=>{u.preventDefault();const m=z.getPressedKeyString();m.length==1&&m[0]==\"esc\"||l.value&&b.value>-1&&(x.value[b.value].hotKeys=m)})},{immediate:!0});const L=S=>{l.value=!0,k({message:\"按ESC键或者点击空白处结束录入\",type:\"info\"}),x.value[S].hotKeys=[],b.value=S},O=()=>{const S=[];x.value.forEach(({hotKeys:u})=>{S.push(u)}),T(S),d.value=!1},q=()=>{z.filter=()=>!0,x.value.forEach(({hotKeys:S,action:u})=>{S.length!=0&&z(S.join(\"+\"),m=>{m.preventDefault(),u.call(null)})})},T=S=>{localStorage.setItem(\"legado_web_hotkeys\",JSON.stringify(S))};function Q(){try{const S=localStorage.getItem(\"legado_web_hotkeys\");if(S===null)return!1;const u=JSON.parse(S);return!Array.isArray(u)||u.length==0?!1:(x.value.forEach((m,D)=>m.hotKeys=u[D]),!0)}catch{k({message:\"快捷键配置错误\",type:\"error\"}),localStorage.removeItem(\"legado_web_hotkeys\")}return!1}return et(()=>{Q()&&(d.value=!1)}),(S,u)=>{const m=ue,D=Ce,je=tt;return g(),E(N,null,[f(\"div\",io,[(g(!0),E(N,null,W(i(x),R=>(g(),C(m,{size:\"large\",key:R.name,onClick:R.action},{default:p(()=>[y(ee(R.name),1)]),_:2},1032,[\"onClick\"]))),128)),h(m,{size:\"large\",onClick:u[0]||(u[0]=()=>d.value=!0)},{default:p(()=>[...u[2]||(u[2]=[y(\"快捷键\",-1)])]),_:1})]),h(je,{modelValue:i(d),\"onUpdate:modelValue\":u[1]||(u[1]=R=>M(d)?d.value=R:null),\"show-close\":!1,\"before-close\":v},{header:p(({titleClass:R,titleId:re})=>[f(\"div\",lo,[f(\"div\",{id:re,class:Ee(R)},[u[4]||(u[4]=y(\" 快捷键设置 \",-1)),i(l)?(g(),E(\"span\",co,[h(D,null,{default:p(()=>[...u[3]||(u[3]=[y(\" / 录入中 \",-1)])]),_:1})])):I(\"\",!0)],10,ao),h(m,{disabled:i(l),onClick:O,icon:i(ot)},{default:p(()=>[...u[5]||(u[5]=[y(\"保存\",-1)])]),_:1},8,[\"disabled\",\"icon\"])])]),default:p(()=>[f(\"div\",uo,[(g(!0),E(N,null,W(i(x),(R,re)=>(g(),E(\"div\",{key:R.name,class:\"hotkeys-item flex-space-between\"},[f(\"span\",po,[h(D,null,{default:p(()=>[y(ee(R.name),1)]),_:2},1024)]),f(\"div\",go,[(g(!0),E(N,null,W(R.hotKeys,(se,Ae)=>(g(),E(\"div\",{key:se},[f(\"kbd\",null,ee(se),1),Ae+1<R.hotKeys.length?(g(),E(\"span\",ho,[h(D,null,{default:p(()=>[...u[6]||(u[6]=[y(\"+\",-1)])]),_:1})])):I(\"\",!0)]))),128)),R.hotKeys.length==0?(g(),E(\"span\",mo,\"未设置\")):I(\"\",!0)]),h(m,{disabled:i(l),text:\"\",icon:i(xe),onClick:se=>L(re)},{default:p(()=>[...u[7]||(u[7]=[y(\"编辑\",-1)])]),_:1},8,[\"disabled\",\"icon\",\"onClick\"])]))),128))])]),_:1},8,[\"modelValue\"])],64)}}}),fo=j(So,[[\"__scopeId\",\"data-v-096adf6d\"]]),yo=K({__name:\"SourceTabForm\",props:{config:{}},setup(e){const o=F(),t=A(()=>o.currentSource);return(n,r)=>{const s=te,c=st,a=it,_=at,w=lt,U=rt,x=nt,d=Re,l=Ie;return g(),C(l,{id:\"source-edit\"},{default:p(()=>[(g(!0),E(N,null,W(Object.values(e.config),({name:b,children:v})=>(g(),C(d,{label:b,key:b},{default:p(()=>[h(x,{\"label-position\":\"right\",\"label-width\":\"auto\"},{default:p(()=>[(g(!0),E(N,null,W(v,({type:L,title:O,namespace:q,id:T,array:Q,hint:S,required:u=!1})=>(g(),C(U,{label:O,key:O,required:u},{default:p(()=>[L==\"String\"&&typeof q>\"u\"?(g(),C(s,{key:0,type:\"textarea\",modelValue:i(t)[T],\"onUpdate:modelValue\":m=>i(t)[T]=m,placeholder:S,autosize:\"\"},null,8,[\"modelValue\",\"onUpdate:modelValue\",\"placeholder\"])):I(\"\",!0),L==\"String\"&&typeof q<\"u\"?(g(),C(s,{key:1,type:\"textarea\",modelValue:i(t)[q][T],\"onUpdate:modelValue\":m=>i(t)[q][T]=m,placeholder:S,autosize:\"\"},null,8,[\"modelValue\",\"onUpdate:modelValue\",\"placeholder\"])):I(\"\",!0),L===\"Boolean\"?(g(),C(c,{key:2,modelValue:i(t)[T],\"onUpdate:modelValue\":m=>i(t)[T]=m},null,8,[\"modelValue\",\"onUpdate:modelValue\"])):I(\"\",!0),L===\"Number\"?(g(),C(a,{key:3,modelValue:i(t)[T],\"onUpdate:modelValue\":m=>i(t)[T]=m,min:0},null,8,[\"modelValue\",\"onUpdate:modelValue\"])):I(\"\",!0),L===\"Array\"?(g(),C(w,{key:4,modelValue:i(t)[T],\"onUpdate:modelValue\":m=>i(t)[T]=m},{default:p(()=>[(g(!0),E(N,null,W(Q,(m,D)=>(g(),C(_,{value:D,key:m,label:m},null,8,[\"value\",\"label\"]))),128))]),_:2},1032,[\"modelValue\",\"onUpdate:modelValue\"])):I(\"\",!0)]),_:2},1032,[\"label\",\"required\"]))),128))]),_:2},1024)]),_:2},1032,[\"label\"]))),128))]),_:1})}}}),bo=j(yo,[[\"__scopeId\",\"data-v-c07c5146\"]]),_o={base:{name:\"基础\",children:[{title:\"源类型\",id:\"bookSourceType\",type:\"Array\",array:[\"文本\",\"音频\",\"图片\",\"文件\"],required:!0},{title:\"源域名\",id:\"bookSourceUrl\",type:\"String\",hint:\"通常填写网站主页,例: https://www.qidian.com\",required:!0},{title:\"源名称\",id:\"bookSourceName\",type:\"String\",hint:\"会显示在源列表\",required:!0},{title:\"源分组\",id:\"bookSourceGroup\",type:\"String\",hint:\"描述源的特征信息\"},{title:\"源注释\",id:\"bookSourceComment\",type:\"String\",hint:\"描述源作者和状态\"},{title:\"登录地址\",id:\"loginUrl\",type:\"String\",hint:\"填写网站登录网址,仅在需要登录的源有用\"},{title:\"登录界面\",id:\"loginUi\",type:\"String\",hint:\"自定义登录界面\"},{title:\"登录检测\",id:\"loginCheckJs\",type:\"String\",hint:\"登录检测js\"},{title:\"封面解密\",id:\"coverDecodeJs\",type:\"String\",hint:\"封面解密js\"},{title:\"链接验证\",id:\"bookUrlPattern\",type:\"String\",hint:\"书籍URL正则，当详情页URL与源URL的域名不一致时有效，用于添加网址\"},{title:\"请求头\",id:\"header\",type:\"String\",hint:\"客户端标识\"},{title:\"变量说明\",id:\"variableComment\",type:\"String\",hint:\"书源变量说明\"},{title:\"并发率\",id:\"concurrentRate\",type:\"String\",hint:\"并发率，如1000(访问间隔1000ms)或者1/1000(1000ms内访问1次)\"},{title:\"js库\",id:\"jsLib\",type:\"String\",hint:\"js库, 可填写js或者key-value object获取在线js文件\"}]},search:{name:\"搜索\",children:[{title:\"搜索地址\",id:\"searchUrl\",type:\"String\",hint:\"[域名可省略]/search.php@kw={{key}}\"},{title:\"校验文字\",namespace:\"ruleSearch\",id:\"checkKeyWord\",type:\"String\",hint:\"校验关键字，强烈建议填写\"},{title:\"列表规则\",namespace:\"ruleSearch\",id:\"bookList\",type:\"String\",hint:\"选择书籍节点 (规则结果为List<Element>)\"},{title:\"书名规则\",namespace:\"ruleSearch\",id:\"name\",type:\"String\",hint:\"选择节点书名 (规则结果为String)\"},{title:\"作者规则\",namespace:\"ruleSearch\",id:\"author\",type:\"String\",hint:\"选择节点作者 (规则结果为String)\"},{title:\"分类规则\",namespace:\"ruleSearch\",id:\"kind\",type:\"String\",hint:\"选择节点分类信息 (规则结果为String)\"},{title:\"字数规则\",namespace:\"ruleSearch\",id:\"wordCount\",type:\"String\",hint:\"选择节点字数信息 (规则结果为String)\"},{title:\"最新章节\",namespace:\"ruleSearch\",id:\"lastChapter\",type:\"String\",hint:\"选择节点最新章节 (规则结果为String)\"},{title:\"简介规则\",namespace:\"ruleSearch\",id:\"intro\",type:\"String\",hint:\"选择节点书籍简介 (规则结果为String)\"},{title:\"封面规则\",namespace:\"ruleSearch\",id:\"coverUrl\",type:\"String\",hint:\"选择节点书籍封面 (规则结果为String类型的url)\"},{title:\"详情地址\",namespace:\"ruleSearch\",id:\"bookUrl\",type:\"String\",hint:\"选择书籍详情页网址 (规则结果为String类型的url)\"}]},find:{name:\"发现\",children:[{title:\"发现地址\",id:\"exploreUrl\",type:\"String\",hint:\"单个发现格式<name>::<url>或者{url:<url>,title:<name>,style:...}；前者用换行符或者&&连接，后者放在数组内；可用js动态生成\"},{title:\"列表规则\",namespace:\"ruleExplore\",id:\"bookList\",type:\"String\",hint:\"选择书籍节点 (规则结果为List<Element>)\"},{title:\"书名规则\",namespace:\"ruleExplore\",id:\"name\",type:\"String\",hint:\"选择节点书名 (规则结果为String)\"},{title:\"作者规则\",namespace:\"ruleExplore\",id:\"author\",type:\"String\",hint:\"选择节点作者 (规则结果为String)\"},{title:\"分类规则\",namespace:\"ruleExplore\",id:\"kind\",type:\"String\",hint:\"选择节点分类信息 (规则结果为String)\"},{title:\"字数规则\",namespace:\"ruleExplore\",id:\"wordCount\",type:\"String\",hint:\"选择节点字数信息 (规则结果为String)\"},{title:\"最新章节\",namespace:\"ruleExplore\",id:\"lastChapter\",type:\"String\",hint:\"选择节点最新章节 (规则结果为String)\"},{title:\"简介规则\",namespace:\"ruleExplore\",id:\"intro\",type:\"String\",hint:\"选择节点书籍简介 (规则结果为String)\"},{title:\"封面规则\",namespace:\"ruleExplore\",id:\"coverUrl\",type:\"String\",hint:\"选择节点书籍封面 (规则结果为String类型的url)\"},{title:\"详情地址\",namespace:\"ruleExplore\",id:\"bookUrl\",type:\"String\",hint:\"选择书籍详情页网址 (规则结果为String类型的url)\"}]},detail:{name:\"详情\",children:[{title:\"预处理\",namespace:\"ruleBookInfo\",id:\"init\",type:\"String\",hint:\"用于加速详情信息检索，只支持AllInOne规则\"},{title:\"书名规则\",namespace:\"ruleBookInfo\",id:\"name\",type:\"String\",hint:\"选择节点书名 (规则结果为String)\"},{title:\"作者规则\",namespace:\"ruleBookInfo\",id:\"author\",type:\"String\",hint:\"选择节点作者 (规则结果为String)\"},{title:\"分类规则\",namespace:\"ruleBookInfo\",id:\"kind\",type:\"String\",hint:\"选择节点分类信息 (规则结果为String)\"},{title:\"字数规则\",namespace:\"ruleBookInfo\",id:\"wordCount\",type:\"String\",hint:\"选择节点字数信息 (规则结果为String)\"},{title:\"最新章节\",namespace:\"ruleBookInfo\",id:\"lastChapter\",type:\"String\",hint:\"选择节点最新章节 (规则结果为String)\"},{title:\"简介规则\",namespace:\"ruleBookInfo\",id:\"intro\",type:\"String\",hint:\"选择节点书籍简介 (规则结果为String)\"},{title:\"封面规则\",namespace:\"ruleBookInfo\",id:\"coverUrl\",type:\"String\",hint:\"选择节点书籍封面 (规则结果为String类型的url)\"},{title:\"目录地址\",namespace:\"ruleBookInfo\",id:\"tocUrl\",type:\"String\",hint:\"选择书籍详情页网址 (规则结果为String类型的url, 与详情页相同时可省略)\"},{title:\"修改书籍\",namespace:\"ruleBookInfo\",id:\"canReName\",type:\"String\",hint:\"允许修改书名作者(规则结果为String类型, 默认不允许)\"},{title:\"下载URL\",namespace:\"ruleBookInfo\",id:\"downloadUrls\",type:\"String\",hint:\"文件类书源下载地址 (规则结果为String类型的url, 多个链接返回数组)\"}]},directory:{name:\"目录\",children:[{title:\"更新前JS\",namespace:\"ruleToc\",id:\"preUpdateJs\",type:\"String\",hint:\"更新目录前调用JS 动态更新目录链接\"},{title:\"列表规则\",namespace:\"ruleToc\",id:\"chapterList\",type:\"String\",hint:\"选择目录列表的章节节点 (规则结果为List<Element>)\"},{title:\"章节名称\",namespace:\"ruleToc\",id:\"chapterName\",type:\"String\",hint:\"选择章节名称 (规则结果为String)\"},{title:\"章节地址\",namespace:\"ruleToc\",id:\"chapterUrl\",type:\"String\",hint:\"选择章节链接 (规则结果为String类型的Url)\"},{title:\"标题处理\",namespace:\"ruleToc\",id:\"formatJs\",type:\"String\",hint:\"遍历去重后的章节列表的回调，提供index(章节序号从1开始)、title(章节标题)变量，额外提供gInt(初始值0)，返回值作为新的标题\"},{title:\"卷名标识\",namespace:\"ruleToc\",id:\"isVolume\",type:\"String\",hint:\"章节名称是否是卷名 (规则结果为Bool)\"},{title:\"章节信息\",namespace:\"ruleToc\",id:\"updateTime\",type:\"String\",hint:\"选择章节信息（如更新时间） (规则结果为String)\"},{title:\"收费标识\",namespace:\"ruleToc\",id:\"isVip\",type:\"String\",hint:\"章节是否为VIP章节 (规则结果为Bool)\"},{title:\"购买标识\",namespace:\"ruleToc\",id:\"isPay\",type:\"String\",hint:\"章节是否为已购买 (规则结果为Bool)\"},{title:\"翻页规则\",namespace:\"ruleToc\",id:\"nextTocUrl\",type:\"String\",hint:\"选择目录下一页链接 (规则结果为List<String>或String)\"}]},content:{name:\"正文\",children:[{title:\"正文规则\",namespace:\"ruleContent\",id:\"content\",type:\"String\",hint:\"选择正文内容 (规则结果为String)\"},{title:\"标题规则\",namespace:\"ruleContent\",id:\"title\",type:\"String\",hint:\"获取结果将会覆盖章节标题 (规则结果为String)\"},{title:\"翻页规则\",namespace:\"ruleContent\",id:\"nextContentUrl\",type:\"String\",hint:\"选择下一分页(不是下一章)链接 (规则结果为String类型的Url)\"},{title:\"脚本注入\",namespace:\"ruleContent\",id:\"webJs\",type:\"String\",hint:\"注入javascript，用于模拟鼠标点击等，必须有返回值，一般为String类型\"},{title:\"资源正则\",namespace:\"ruleContent\",id:\"sourceRegex\",type:\"String\",hint:\"匹配资源的url特征，用于嗅探\"},{title:\"替换规则\",namespace:\"ruleContent\",id:\"replaceRegex\",type:\"String\",hint:\"多页内容合并后替换，用于正文净化\"},{title:\"图片样式\",namespace:\"ruleContent\",id:\"imageStyle\",type:\"String\",hint:\"FULL:铺满 不填:默认样式\"},{title:\"图片解密\",namespace:\"ruleContent\",id:\"imageDecode\",type:\"String\",hint:\"填写JavaScript 返回解密图片的bytes \"},{title:\"购买操作\",namespace:\"ruleContent\",id:\"payAction\",type:\"String\",hint:\"填写JavaScript 返回购买链接或者调用购买接口\"}]},other:{name:\"其他\",children:[{title:\"启用搜索\",id:\"enabled\",type:\"Boolean\"},{title:\"启用发现\",id:\"enabledExplore\",type:\"Boolean\"},{title:\"CookieJar\",id:\"enabledCookieJar\",type:\"Boolean\"},{title:\"搜索权重\",id:\"weight\",type:\"Number\"},{title:\"排序编号\",id:\"customOrder\",type:\"Number\"}]}},ko={base:{name:\"基础\",children:[{title:\"源域名\",id:\"sourceUrl\",type:\"String\",hint:\"通常填写网站主页,例: https://www.qidian.com\",required:!0},{title:\"图标\",id:\"sourceIcon\",type:\"String\",hint:\"填写图片网络链接\"},{title:\"源名称\",id:\"sourceName\",type:\"String\",hint:\"会显示在源列表\",required:!0},{title:\"源分组\",id:\"sourceGroup\",type:\"String\",hint:\"描述源的特征信息\"},{title:\"源注释\",id:\"sourceComment\",type:\"String\",hint:\"描述源作者和状态\"},{title:\"分类地址\",id:\"sortUrl\",type:\"String\",hint:`名称1::链接1\n名称2::链接2`},{title:\"登录地址\",id:\"loginUrl\",type:\"String\",hint:\"填写网站登录网址,仅在需要登录的源有用\"},{title:\"登录界面\",id:\"loginUi\",type:\"String\",hint:\"自定义登录界面\"},{title:\"登录检测\",id:\"loginCheckJs\",type:\"String\",hint:\"登录检测js\"},{title:\"封面解密\",id:\"coverDecodeJs\",type:\"String\",hint:\"封面解密js\"},{title:\"请求头\",id:\"header\",type:\"String\",hint:\"客户端标识\"},{title:\"变量说明\",id:\"variableComment\",type:\"String\",hint:\"源变量说明\"},{title:\"并发率\",id:\"concurrentRate\",type:\"String\",hint:\"并发率\"},{title:\"js库\",id:\"jsLib\",type:\"String\",hint:\"js库, 可填写js或者key-value object获取在线js文件\"}]},list:{name:\"列表\",children:[{title:\"列表规则\",id:\"ruleArticles\",type:\"String\",hint:\"规则结果为List<Element>\"},{title:\"翻页规则\",id:\"ruleNextPage\",type:\"String\",hint:\"下一页链接 规则结果为List<String>或String\"},{title:\"标题规则\",id:\"ruleTitle\",type:\"String\",hint:\"文章标题 规则结果为String\"},{title:\"时间规则\",id:\"rulePubDate\",type:\"String\",hint:\"文章发布时间 规则结果为String\"},{title:\"描述规则\",id:\"ruleDescription\",type:\"String\",hint:\"文章简要描述 规则结果为String\"},{title:\"图片规则\",id:\"ruleImage\",type:\"String\",hint:\"文章图片链接 规则结果为String\"},{title:\"链接规则\",id:\"ruleLink\",type:\"String\",hint:\"文章链接 规则结果为String\"}]},webView:{name:\"WebView\",children:[{title:\"内容规则\",id:\"ruleContent\",type:\"String\",hint:\"文章正文\"},{title:\"样式规则\",id:\"style\",type:\"String\",hint:\"文章正文样式 填写css\"},{title:\"注入规则\",id:\"injectJs\",type:\"String\",hint:\"注入网页的JavaScript\"},{title:\"黑名单\",id:\"contentBlacklist\",type:\"String\",hint:\"webView链接加载黑名单，英文逗号隔开\"},{title:\"白名单\",id:\"contentWhitelist\",type:\"String\",hint:\"webView链接加载白名单，英文逗号隔开\"},{title:\"链接拦截\",id:\"shouldOverrideUrlLoading\",type:\"String\",hint:\"填写js，变量url为当前资源链接，返回true拦截\"}]},other:{name:\"其他\",children:[{title:\"列表样式\",id:\"articleStyle\",type:\"Array\",array:[\"默认\",\"大图\",\"双列\"]},{title:\"加载地址\",id:\"loadWithBaseUrl\",type:\"Boolean\"},{title:\"启用JS\",id:\"enableJs\",type:\"Boolean\"},{title:\"启用\",id:\"enabled\",type:\"Boolean\"},{title:\"Cookie\",id:\"enabledCookieJar\",type:\"Boolean\"},{title:\"单URL\",id:\"singleUrl\",type:\"Boolean\"},{title:\"排序编号\",id:\"customOrder\",type:\"Number\"}]}},vo={class:\"editor\"},wo=K({__name:\"SourceEditor\",setup(e){ct();let o;return/bookSource/i.test(location.href)?(o=_o,document.title=\"书源管理\"):(o=ko,document.title=\"订阅源管理\"),(t,n)=>{const r=bo,s=fo,c=so;return g(),E(\"div\",vo,[h(r,{class:\"left\",config:i(o)},null,8,[\"config\"]),h(s),h(c,{class:\"right\"})])}}}),we=j(wo,[[\"__scopeId\",\"data-v-f2c47af3\"]]),Pe=[{path:\"/bookSource\",name:\"book-home\",component:we},{path:\"/rssSource\",name:\"rss-home\",component:we}];le({history:ae(),routes:Pe});const $e=le({history:ae(),routes:[Ve,Pe].flat()});$e.afterEach(e=>{e.name==\"shelf\"&&(document.title=\"书架\")});Be(Te).use(Oe).use($e).mount(\"#app\");de(()=>At().isNight,e=>{e?document.documentElement.classList.add(\"dark\"):document.documentElement.classList.remove(\"dark\")});window.addEventListener(\"vite:preloadError\",e=>{e.preventDefault()});export{P as A,j as _,Ft as a,ft as b,X as c,Bo as d,Uo as i,$ as l,zt as p,kt as s,At as u,$t as v};\n"
  },
  {
    "path": "app/src/main/assets/web/vue/assets/loading-C4J6hIxs.js",
    "content": "import{ak as i,N as g,a9 as c,M as d,al as f,u as m}from\"./vendor-KSDcS24u.js\";const L=(s,t,l=i)=>{const a=d(!1);let r=null;const o=()=>a.value=!1,n=()=>a.value=!0;g(a,e=>{if(!e)return r==null?void 0:r.close();r=f.service({target:m(s),spinner:l,text:t,lock:!0,background:\"rgba(0, 0, 0, 0)\"})});const u=e=>{if(!(e instanceof Promise))throw TypeError(\"loadingWrapper argument must be Promise\");return n(),e.finally(o)};return c(()=>{o()}),{isLoading:a,showLoading:n,closeLoading:o,loadingWrapper:u}};export{L as u};\n"
  },
  {
    "path": "app/src/main/assets/web/vue/assets/loading-DkQYEuap.css",
    "content": ".el-loading-spinner{font-size:36px;color:#b5b5b5}.el-loading-text{font-weight:500;color:#b5b5b5!important}\n"
  },
  {
    "path": "app/src/main/assets/web/vue/assets/vendor-CXe1BRiH.css",
    "content": "@charset \"UTF-8\";:root{--el-color-white:#ffffff;--el-color-black:#000000;--el-color-primary-rgb:64,158,255;--el-color-success-rgb:103,194,58;--el-color-warning-rgb:230,162,60;--el-color-danger-rgb:245,108,108;--el-color-error-rgb:245,108,108;--el-color-info-rgb:144,147,153;--el-font-size-extra-large:20px;--el-font-size-large:18px;--el-font-size-medium:16px;--el-font-size-base:14px;--el-font-size-small:13px;--el-font-size-extra-small:12px;--el-font-family:\"Helvetica Neue\",Helvetica,\"PingFang SC\",\"Hiragino Sans GB\",\"Microsoft YaHei\",\"微软雅黑\",Arial,sans-serif;--el-font-weight-primary:500;--el-font-line-height-primary:24px;--el-index-normal:1;--el-index-top:1000;--el-index-popper:2000;--el-border-radius-base:4px;--el-border-radius-small:2px;--el-border-radius-round:20px;--el-border-radius-circle:100%;--el-transition-duration:.3s;--el-transition-duration-fast:.2s;--el-transition-function-ease-in-out-bezier:cubic-bezier(.645,.045,.355,1);--el-transition-function-fast-bezier:cubic-bezier(.23,1,.32,1);--el-transition-all:all var(--el-transition-duration) var(--el-transition-function-ease-in-out-bezier);--el-transition-fade:opacity var(--el-transition-duration) var(--el-transition-function-fast-bezier);--el-transition-md-fade:transform var(--el-transition-duration) var(--el-transition-function-fast-bezier),opacity var(--el-transition-duration) var(--el-transition-function-fast-bezier);--el-transition-fade-linear:opacity var(--el-transition-duration-fast) linear;--el-transition-border:border-color var(--el-transition-duration-fast) var(--el-transition-function-ease-in-out-bezier);--el-transition-box-shadow:box-shadow var(--el-transition-duration-fast) var(--el-transition-function-ease-in-out-bezier);--el-transition-color:color var(--el-transition-duration-fast) var(--el-transition-function-ease-in-out-bezier);--el-component-size-large:40px;--el-component-size:32px;--el-component-size-small:24px;color-scheme:light;--el-color-primary:#409eff;--el-color-primary-light-3:rgb(121.3,187.1,255);--el-color-primary-light-5:rgb(159.5,206.5,255);--el-color-primary-light-7:rgb(197.7,225.9,255);--el-color-primary-light-8:rgb(216.8,235.6,255);--el-color-primary-light-9:rgb(235.9,245.3,255);--el-color-primary-dark-2:rgb(51.2,126.4,204);--el-color-success:#67c23a;--el-color-success-light-3:rgb(148.6,212.3,117.1);--el-color-success-light-5:rgb(179,224.5,156.5);--el-color-success-light-7:rgb(209.4,236.7,195.9);--el-color-success-light-8:rgb(224.6,242.8,215.6);--el-color-success-light-9:rgb(239.8,248.9,235.3);--el-color-success-dark-2:rgb(82.4,155.2,46.4);--el-color-warning:#e6a23c;--el-color-warning-light-3:rgb(237.5,189.9,118.5);--el-color-warning-light-5:rgb(242.5,208.5,157.5);--el-color-warning-light-7:rgb(247.5,227.1,196.5);--el-color-warning-light-8:rgb(250,236.4,216);--el-color-warning-light-9:rgb(252.5,245.7,235.5);--el-color-warning-dark-2:rgb(184,129.6,48);--el-color-danger:#f56c6c;--el-color-danger-light-3:rgb(248,152.1,152.1);--el-color-danger-light-5:rgb(250,181.5,181.5);--el-color-danger-light-7:rgb(252,210.9,210.9);--el-color-danger-light-8:rgb(253,225.6,225.6);--el-color-danger-light-9:rgb(254,240.3,240.3);--el-color-danger-dark-2:rgb(196,86.4,86.4);--el-color-error:#f56c6c;--el-color-error-light-3:rgb(248,152.1,152.1);--el-color-error-light-5:rgb(250,181.5,181.5);--el-color-error-light-7:rgb(252,210.9,210.9);--el-color-error-light-8:rgb(253,225.6,225.6);--el-color-error-light-9:rgb(254,240.3,240.3);--el-color-error-dark-2:rgb(196,86.4,86.4);--el-color-info:#909399;--el-color-info-light-3:rgb(177.3,179.4,183.6);--el-color-info-light-5:rgb(199.5,201,204);--el-color-info-light-7:rgb(221.7,222.6,224.4);--el-color-info-light-8:rgb(232.8,233.4,234.6);--el-color-info-light-9:rgb(243.9,244.2,244.8);--el-color-info-dark-2:rgb(115.2,117.6,122.4);--el-bg-color:#ffffff;--el-bg-color-page:#f2f3f5;--el-bg-color-overlay:#ffffff;--el-text-color-primary:#303133;--el-text-color-regular:#606266;--el-text-color-secondary:#909399;--el-text-color-placeholder:#a8abb2;--el-text-color-disabled:#c0c4cc;--el-border-color:#dcdfe6;--el-border-color-light:#e4e7ed;--el-border-color-lighter:#ebeef5;--el-border-color-extra-light:#f2f6fc;--el-border-color-dark:#d4d7de;--el-border-color-darker:#cdd0d6;--el-fill-color:#f0f2f5;--el-fill-color-light:#f5f7fa;--el-fill-color-lighter:#fafafa;--el-fill-color-extra-light:#fafcff;--el-fill-color-dark:#ebedf0;--el-fill-color-darker:#e6e8eb;--el-fill-color-blank:#ffffff;--el-box-shadow:0px 12px 32px 4px rgba(0,0,0,.04),0px 8px 20px rgba(0,0,0,.08);--el-box-shadow-light:0px 0px 12px rgba(0,0,0,.12);--el-box-shadow-lighter:0px 0px 6px rgba(0,0,0,.12);--el-box-shadow-dark:0px 16px 48px 16px rgba(0,0,0,.08),0px 12px 32px rgba(0,0,0,.12),0px 8px 16px -8px rgba(0,0,0,.16);--el-disabled-bg-color:var(--el-fill-color-light);--el-disabled-text-color:var(--el-text-color-placeholder);--el-disabled-border-color:var(--el-border-color-light);--el-overlay-color:rgba(0,0,0,.8);--el-overlay-color-light:rgba(0,0,0,.7);--el-overlay-color-lighter:rgba(0,0,0,.5);--el-mask-color:rgba(255,255,255,.9);--el-mask-color-extra-light:rgba(255,255,255,.3);--el-border-width:1px;--el-border-style:solid;--el-border-color-hover:var(--el-text-color-disabled);--el-border:var(--el-border-width) var(--el-border-style) var(--el-border-color);--el-svg-monochrome-grey:var(--el-border-color)}.fade-in-linear-enter-active,.fade-in-linear-leave-active{transition:var(--el-transition-fade-linear)}.fade-in-linear-enter-from,.fade-in-linear-leave-to{opacity:0}.el-fade-in-linear-enter-active,.el-fade-in-linear-leave-active{transition:var(--el-transition-fade-linear)}.el-fade-in-linear-enter-from,.el-fade-in-linear-leave-to{opacity:0}.el-fade-in-enter-active,.el-fade-in-leave-active{transition:all var(--el-transition-duration) cubic-bezier(.55,0,.1,1)}.el-fade-in-enter-from,.el-fade-in-leave-active{opacity:0}.el-zoom-in-center-enter-active,.el-zoom-in-center-leave-active{transition:all var(--el-transition-duration) cubic-bezier(.55,0,.1,1)}.el-zoom-in-center-enter-from,.el-zoom-in-center-leave-active{opacity:0;transform:scaleX(0)}.el-zoom-in-top-enter-active,.el-zoom-in-top-leave-active{opacity:1;transform:scaleY(1);transform-origin:center top;transition:var(--el-transition-md-fade)}.el-zoom-in-top-enter-active[data-popper-placement^=top],.el-zoom-in-top-leave-active[data-popper-placement^=top]{transform-origin:center bottom}.el-zoom-in-top-enter-from,.el-zoom-in-top-leave-active{opacity:0;transform:scaleY(0)}.el-zoom-in-bottom-enter-active,.el-zoom-in-bottom-leave-active{opacity:1;transform:scaleY(1);transform-origin:center bottom;transition:var(--el-transition-md-fade)}.el-zoom-in-bottom-enter-from,.el-zoom-in-bottom-leave-active{opacity:0;transform:scaleY(0)}.el-zoom-in-left-enter-active,.el-zoom-in-left-leave-active{opacity:1;transform:scale(1);transform-origin:top left;transition:var(--el-transition-md-fade)}.el-zoom-in-left-enter-from,.el-zoom-in-left-leave-active{opacity:0;transform:scale(.45)}.collapse-transition{transition:var(--el-transition-duration) height ease-in-out,var(--el-transition-duration) padding-top ease-in-out,var(--el-transition-duration) padding-bottom ease-in-out}.el-collapse-transition-enter-active,.el-collapse-transition-leave-active{transition:var(--el-transition-duration) max-height ease-in-out,var(--el-transition-duration) padding-top ease-in-out,var(--el-transition-duration) padding-bottom ease-in-out}.horizontal-collapse-transition{transition:var(--el-transition-duration) width ease-in-out,var(--el-transition-duration) padding-left ease-in-out,var(--el-transition-duration) padding-right ease-in-out}.el-list-enter-active,.el-list-leave-active{transition:all 1s}.el-list-enter-from,.el-list-leave-to{opacity:0;transform:translateY(-30px)}.el-list-leave-active{position:absolute!important}.el-opacity-transition{transition:opacity var(--el-transition-duration) cubic-bezier(.55,0,.1,1)}.el-icon-loading{animation:rotating 2s linear infinite}.el-icon--right{margin-left:5px}.el-icon--left{margin-right:5px}@keyframes rotating{0%{transform:rotate(0)}to{transform:rotate(1turn)}}.el-icon{--color:inherit;align-items:center;display:inline-flex;height:1em;justify-content:center;line-height:1em;position:relative;width:1em;fill:currentColor;color:var(--color);font-size:inherit}.el-icon.is-loading{animation:rotating 2s linear infinite}.el-icon svg{height:1em;width:1em}.el-tabs{--el-tabs-header-height:40px;display:flex}.el-tabs__header{align-items:center;display:flex;justify-content:space-between;margin:0 0 15px;padding:0;position:relative}.el-tabs__header-vertical{flex-direction:column}.el-tabs__active-bar{background-color:var(--el-color-primary);bottom:0;height:2px;left:0;list-style:none;position:absolute;transition:width var(--el-transition-duration) var(--el-transition-function-ease-in-out-bezier),transform var(--el-transition-duration) var(--el-transition-function-ease-in-out-bezier);z-index:1}.el-tabs__new-tab{align-items:center;border:1px solid var(--el-border-color);border-radius:3px;color:var(--el-text-color-primary);cursor:pointer;display:flex;font-size:12px;height:20px;justify-content:center;line-height:20px;margin:10px 0 10px 10px;text-align:center;transition:all .15s;width:20px}.el-tabs__new-tab .is-icon-plus{height:inherit;transform:scale(.8);width:inherit}.el-tabs__new-tab .is-icon-plus svg{vertical-align:middle}.el-tabs__new-tab:hover{color:var(--el-color-primary)}.el-tabs__new-tab-vertical{margin-left:0}.el-tabs__nav-wrap{flex:1 auto;margin-bottom:-1px;overflow:hidden;position:relative}.el-tabs__nav-wrap:after{background-color:var(--el-border-color-light);bottom:0;content:\"\";height:2px;left:0;position:absolute;width:100%;z-index:var(--el-index-normal)}.el-tabs__nav-wrap.is-scrollable{box-sizing:border-box;padding:0 20px}.el-tabs__nav-scroll{overflow:hidden}.el-tabs__nav-next,.el-tabs__nav-prev{color:var(--el-text-color-secondary);cursor:pointer;font-size:12px;line-height:44px;position:absolute;text-align:center;width:20px}.el-tabs__nav-next{right:0}.el-tabs__nav-prev{left:0}.el-tabs__nav{display:flex;float:left;position:relative;transition:transform var(--el-transition-duration);white-space:nowrap;z-index:calc(var(--el-index-normal) + 1)}.el-tabs__nav.is-stretch{display:flex;min-width:100%}.el-tabs__nav.is-stretch>*{flex:1;text-align:center}.el-tabs__item{align-items:center;box-sizing:border-box;color:var(--el-text-color-primary);display:flex;font-size:var(--el-font-size-base);font-weight:500;height:var(--el-tabs-header-height);justify-content:center;list-style:none;padding:0 20px;position:relative}.el-tabs__item:focus,.el-tabs__item:focus:active{outline:none}.el-tabs__item:focus-visible{border-radius:3px;box-shadow:0 0 2px 2px var(--el-color-primary) inset}.el-tabs__item .is-icon-close{border-radius:50%;margin-left:5px;text-align:center;transition:all var(--el-transition-duration) var(--el-transition-function-ease-in-out-bezier)}.el-tabs__item .is-icon-close:before{display:inline-block;transform:scale(.9)}.el-tabs__item .is-icon-close:hover{background-color:var(--el-text-color-placeholder);color:#fff}.el-tabs__item.is-active,.el-tabs__item:hover{color:var(--el-color-primary)}.el-tabs__item:hover{cursor:pointer}.el-tabs__item.is-disabled{color:var(--el-disabled-text-color);cursor:not-allowed}.el-tabs__content{flex-grow:1;overflow:hidden;position:relative}.el-tabs--bottom>.el-tabs__header .el-tabs__item:nth-child(2),.el-tabs--top>.el-tabs__header .el-tabs__item:nth-child(2){padding-left:0}.el-tabs--bottom>.el-tabs__header .el-tabs__item:last-child,.el-tabs--top>.el-tabs__header .el-tabs__item:last-child{padding-right:0}.el-tabs--bottom.el-tabs--border-card>.el-tabs__header .el-tabs__item:nth-child(2),.el-tabs--bottom.el-tabs--card>.el-tabs__header .el-tabs__item:nth-child(2),.el-tabs--top.el-tabs--border-card>.el-tabs__header .el-tabs__item:nth-child(2),.el-tabs--top.el-tabs--card>.el-tabs__header .el-tabs__item:nth-child(2){padding-left:20px}.el-tabs--bottom.el-tabs--border-card>.el-tabs__header .el-tabs__item:last-child,.el-tabs--bottom.el-tabs--card>.el-tabs__header .el-tabs__item:last-child,.el-tabs--top.el-tabs--border-card>.el-tabs__header .el-tabs__item:last-child,.el-tabs--top.el-tabs--card>.el-tabs__header .el-tabs__item:last-child{padding-right:20px}.el-tabs--card>.el-tabs__header{border-bottom:1px solid var(--el-border-color-light);height:var(--el-tabs-header-height)}.el-tabs--card>.el-tabs__header .el-tabs__nav-wrap:after{content:none}.el-tabs--card>.el-tabs__header .el-tabs__nav{border:1px solid var(--el-border-color-light);border-bottom:none;border-radius:4px 4px 0 0;box-sizing:border-box}.el-tabs--card>.el-tabs__header .el-tabs__active-bar{display:none}.el-tabs--card>.el-tabs__header .el-tabs__item .is-icon-close{font-size:12px;height:14px;overflow:hidden;position:relative;right:-2px;transform-origin:100% 50%;width:0}.el-tabs--card>.el-tabs__header .el-tabs__item{border-bottom:1px solid transparent;border-left:1px solid var(--el-border-color-light);transition:color var(--el-transition-duration) var(--el-transition-function-ease-in-out-bezier),padding var(--el-transition-duration) var(--el-transition-function-ease-in-out-bezier)}.el-tabs--card>.el-tabs__header .el-tabs__item:first-child{border-left:none}.el-tabs--card>.el-tabs__header .el-tabs__item.is-closable:hover{padding-left:13px;padding-right:13px}.el-tabs--card>.el-tabs__header .el-tabs__item.is-closable:hover .is-icon-close{width:14px}.el-tabs--card>.el-tabs__header .el-tabs__item.is-active{border-bottom-color:var(--el-bg-color)}.el-tabs--card>.el-tabs__header .el-tabs__item.is-active.is-closable{padding-left:20px;padding-right:20px}.el-tabs--card>.el-tabs__header .el-tabs__item.is-active.is-closable .is-icon-close{width:14px}.el-tabs--border-card{background:var(--el-bg-color-overlay);border:1px solid var(--el-border-color)}.el-tabs--border-card>.el-tabs__content{padding:15px}.el-tabs--border-card>.el-tabs__header{background-color:var(--el-fill-color-light);border-bottom:1px solid var(--el-border-color-light);margin:0}.el-tabs--border-card>.el-tabs__header .el-tabs__nav-wrap:after{content:none}.el-tabs--border-card>.el-tabs__header .el-tabs__item{border:1px solid transparent;color:var(--el-text-color-secondary);margin-top:-1px;transition:all var(--el-transition-duration) var(--el-transition-function-ease-in-out-bezier)}.el-tabs--border-card>.el-tabs__header .el-tabs__item+.el-tabs__item,.el-tabs--border-card>.el-tabs__header .el-tabs__item:first-child{margin-left:-1px}.el-tabs--border-card>.el-tabs__header .el-tabs__item.is-active{background-color:var(--el-bg-color-overlay);border-left-color:var(--el-border-color);border-right-color:var(--el-border-color);color:var(--el-color-primary)}.el-tabs--border-card>.el-tabs__header .el-tabs__item:not(.is-disabled):hover{color:var(--el-color-primary)}.el-tabs--border-card>.el-tabs__header .el-tabs__item.is-disabled{color:var(--el-disabled-text-color)}.el-tabs--border-card>.el-tabs__header .is-scrollable .el-tabs__item:first-child{margin-left:0}.el-tabs--bottom{flex-direction:column}.el-tabs--bottom .el-tabs__header.is-bottom{margin-bottom:0;margin-top:10px}.el-tabs--bottom.el-tabs--border-card .el-tabs__header.is-bottom{border-bottom:0;border-top:1px solid var(--el-border-color)}.el-tabs--bottom.el-tabs--border-card .el-tabs__nav-wrap.is-bottom{margin-bottom:0;margin-top:-1px}.el-tabs--bottom.el-tabs--border-card .el-tabs__item.is-bottom:not(.is-active){border:1px solid transparent}.el-tabs--bottom.el-tabs--border-card .el-tabs__item.is-bottom{margin:0 -1px -1px}.el-tabs--left,.el-tabs--right{overflow:hidden}.el-tabs--left .el-tabs__header.is-left,.el-tabs--left .el-tabs__header.is-right,.el-tabs--left .el-tabs__nav-scroll,.el-tabs--left .el-tabs__nav-wrap.is-left,.el-tabs--left .el-tabs__nav-wrap.is-right,.el-tabs--right .el-tabs__header.is-left,.el-tabs--right .el-tabs__header.is-right,.el-tabs--right .el-tabs__nav-scroll,.el-tabs--right .el-tabs__nav-wrap.is-left,.el-tabs--right .el-tabs__nav-wrap.is-right{height:100%}.el-tabs--left .el-tabs__active-bar.is-left,.el-tabs--left .el-tabs__active-bar.is-right,.el-tabs--right .el-tabs__active-bar.is-left,.el-tabs--right .el-tabs__active-bar.is-right{bottom:auto;height:auto;top:0;width:2px}.el-tabs--left .el-tabs__nav-wrap.is-left,.el-tabs--left .el-tabs__nav-wrap.is-right,.el-tabs--right .el-tabs__nav-wrap.is-left,.el-tabs--right .el-tabs__nav-wrap.is-right{margin-bottom:0}.el-tabs--left .el-tabs__nav-wrap.is-left>.el-tabs__nav-next,.el-tabs--left .el-tabs__nav-wrap.is-left>.el-tabs__nav-prev,.el-tabs--left .el-tabs__nav-wrap.is-right>.el-tabs__nav-next,.el-tabs--left .el-tabs__nav-wrap.is-right>.el-tabs__nav-prev,.el-tabs--right .el-tabs__nav-wrap.is-left>.el-tabs__nav-next,.el-tabs--right .el-tabs__nav-wrap.is-left>.el-tabs__nav-prev,.el-tabs--right .el-tabs__nav-wrap.is-right>.el-tabs__nav-next,.el-tabs--right .el-tabs__nav-wrap.is-right>.el-tabs__nav-prev{cursor:pointer;height:30px;line-height:30px;text-align:center;width:100%}.el-tabs--left .el-tabs__nav-wrap.is-left>.el-tabs__nav-next i,.el-tabs--left .el-tabs__nav-wrap.is-left>.el-tabs__nav-prev i,.el-tabs--left .el-tabs__nav-wrap.is-right>.el-tabs__nav-next i,.el-tabs--left .el-tabs__nav-wrap.is-right>.el-tabs__nav-prev i,.el-tabs--right .el-tabs__nav-wrap.is-left>.el-tabs__nav-next i,.el-tabs--right .el-tabs__nav-wrap.is-left>.el-tabs__nav-prev i,.el-tabs--right .el-tabs__nav-wrap.is-right>.el-tabs__nav-next i,.el-tabs--right .el-tabs__nav-wrap.is-right>.el-tabs__nav-prev i{transform:rotate(90deg)}.el-tabs--left .el-tabs__nav-wrap.is-left>.el-tabs__nav-prev,.el-tabs--left .el-tabs__nav-wrap.is-right>.el-tabs__nav-prev,.el-tabs--right .el-tabs__nav-wrap.is-left>.el-tabs__nav-prev,.el-tabs--right .el-tabs__nav-wrap.is-right>.el-tabs__nav-prev{left:auto;top:0}.el-tabs--left .el-tabs__nav-wrap.is-left>.el-tabs__nav-next,.el-tabs--left .el-tabs__nav-wrap.is-right>.el-tabs__nav-next,.el-tabs--right .el-tabs__nav-wrap.is-left>.el-tabs__nav-next,.el-tabs--right .el-tabs__nav-wrap.is-right>.el-tabs__nav-next{bottom:0;right:auto}.el-tabs--left .el-tabs__nav-wrap.is-left.is-scrollable,.el-tabs--left .el-tabs__nav-wrap.is-right.is-scrollable,.el-tabs--right .el-tabs__nav-wrap.is-left.is-scrollable,.el-tabs--right .el-tabs__nav-wrap.is-right.is-scrollable{padding:30px 0}.el-tabs--left .el-tabs__nav-wrap.is-left:after,.el-tabs--left .el-tabs__nav-wrap.is-right:after,.el-tabs--right .el-tabs__nav-wrap.is-left:after,.el-tabs--right .el-tabs__nav-wrap.is-right:after{bottom:auto;height:100%;top:0;width:2px}.el-tabs--left .el-tabs__nav.is-left,.el-tabs--left .el-tabs__nav.is-right,.el-tabs--right .el-tabs__nav.is-left,.el-tabs--right .el-tabs__nav.is-right{flex-direction:column}.el-tabs--left .el-tabs__item.is-left,.el-tabs--right .el-tabs__item.is-left{justify-content:flex-end}.el-tabs--left .el-tabs__item.is-right,.el-tabs--right .el-tabs__item.is-right{justify-content:flex-start}.el-tabs--left{flex-direction:row-reverse}.el-tabs--left .el-tabs__header.is-left{margin-bottom:0;margin-right:10px}.el-tabs--left .el-tabs__nav-wrap.is-left{margin-right:-1px}.el-tabs--left .el-tabs__active-bar.is-left,.el-tabs--left .el-tabs__nav-wrap.is-left:after{left:auto;right:0}.el-tabs--left .el-tabs__item.is-left{text-align:right}.el-tabs--left.el-tabs--card .el-tabs__active-bar.is-left{display:none}.el-tabs--left.el-tabs--card .el-tabs__item.is-left{border-bottom:none;border-left:none;border-right:1px solid var(--el-border-color-light);border-top:1px solid var(--el-border-color-light);text-align:left}.el-tabs--left.el-tabs--card .el-tabs__item.is-left:first-child{border-right:1px solid var(--el-border-color-light);border-top:none}.el-tabs--left.el-tabs--card .el-tabs__item.is-left.is-active{border:1px solid var(--el-border-color-light);border-bottom:none;border-left:none;border-right:1px solid #fff}.el-tabs--left.el-tabs--card .el-tabs__item.is-left.is-active:first-child{border-top:none}.el-tabs--left.el-tabs--card .el-tabs__item.is-left.is-active:last-child{border-bottom:none}.el-tabs--left.el-tabs--card .el-tabs__nav{border-bottom:1px solid var(--el-border-color-light);border-radius:4px 0 0 4px;border-right:none}.el-tabs--left.el-tabs--card .el-tabs__new-tab{float:none}.el-tabs--left.el-tabs--border-card .el-tabs__header.is-left{border-right:1px solid var(--el-border-color)}.el-tabs--left.el-tabs--border-card .el-tabs__item.is-left{border:1px solid transparent;margin:-1px 0 -1px -1px}.el-tabs--left.el-tabs--border-card .el-tabs__item.is-left.is-active{border-color:rgb(209,219,229) transparent}.el-tabs--right .el-tabs__header.is-right{margin-bottom:0;margin-left:10px}.el-tabs--right .el-tabs__nav-wrap.is-right{margin-left:-1px}.el-tabs--right .el-tabs__nav-wrap.is-right:after{left:0;right:auto}.el-tabs--right .el-tabs__active-bar.is-right{left:0}.el-tabs--right.el-tabs--card .el-tabs__active-bar.is-right{display:none}.el-tabs--right.el-tabs--card .el-tabs__item.is-right{border-bottom:none;border-top:1px solid var(--el-border-color-light)}.el-tabs--right.el-tabs--card .el-tabs__item.is-right:first-child{border-left:1px solid var(--el-border-color-light);border-top:none}.el-tabs--right.el-tabs--card .el-tabs__item.is-right.is-active{border:1px solid var(--el-border-color-light);border-bottom:none;border-left:1px solid #fff;border-right:none}.el-tabs--right.el-tabs--card .el-tabs__item.is-right.is-active:first-child{border-top:none}.el-tabs--right.el-tabs--card .el-tabs__item.is-right.is-active:last-child{border-bottom:none}.el-tabs--right.el-tabs--card .el-tabs__nav{border-bottom:1px solid var(--el-border-color-light);border-left:none;border-radius:0 4px 4px 0}.el-tabs--right.el-tabs--border-card .el-tabs__header.is-right{border-left:1px solid var(--el-border-color)}.el-tabs--right.el-tabs--border-card .el-tabs__item.is-right{border:1px solid transparent;margin:-1px -1px -1px 0}.el-tabs--right.el-tabs--border-card .el-tabs__item.is-right.is-active{border-color:rgb(209,219,229) transparent}.el-tabs--top{flex-direction:column-reverse}.slideInLeft-transition,.slideInRight-transition{display:inline-block}.slideInRight-enter{animation:slideInRight-enter var(--el-transition-duration)}.slideInRight-leave{animation:slideInRight-leave var(--el-transition-duration);left:0;position:absolute;right:0}.slideInLeft-enter{animation:slideInLeft-enter var(--el-transition-duration)}.slideInLeft-leave{animation:slideInLeft-leave var(--el-transition-duration);left:0;position:absolute;right:0}@keyframes slideInRight-enter{0%{opacity:0;transform:translate(100%);transform-origin:0 0}to{opacity:1;transform:translate(0);transform-origin:0 0}}@keyframes slideInRight-leave{0%{opacity:1;transform:translate(0);transform-origin:0 0}to{opacity:0;transform:translate(100%);transform-origin:0 0}}@keyframes slideInLeft-enter{0%{opacity:0;transform:translate(-100%);transform-origin:0 0}to{opacity:1;transform:translate(0);transform-origin:0 0}}@keyframes slideInLeft-leave{0%{opacity:1;transform:translate(0);transform-origin:0 0}to{opacity:0;transform:translate(-100%);transform-origin:0 0}}.el-text{--el-text-font-size:var(--el-font-size-base);--el-text-color:var(--el-text-color-regular);align-self:center;color:var(--el-text-color);font-size:var(--el-text-font-size);margin:0;overflow-wrap:break-word;padding:0}.el-text.is-truncated{display:inline-block;max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.el-text.is-line-clamp{display:-webkit-inline-box;-webkit-box-orient:vertical;overflow:hidden}.el-text--large{--el-text-font-size:var(--el-font-size-medium)}.el-text--default{--el-text-font-size:var(--el-font-size-base)}.el-text--small{--el-text-font-size:var(--el-font-size-extra-small)}.el-text.el-text--primary{--el-text-color:var(--el-color-primary)}.el-text.el-text--success{--el-text-color:var(--el-color-success)}.el-text.el-text--warning{--el-text-color:var(--el-color-warning)}.el-text.el-text--danger{--el-text-color:var(--el-color-danger)}.el-text.el-text--error{--el-text-color:var(--el-color-error)}.el-text.el-text--info{--el-text-color:var(--el-color-info)}.el-text>.el-icon{vertical-align:-2px}.el-link{--el-link-font-size:var(--el-font-size-base);--el-link-font-weight:var(--el-font-weight-primary);--el-link-text-color:var(--el-text-color-regular);--el-link-hover-text-color:var(--el-color-primary);--el-link-disabled-text-color:var(--el-text-color-placeholder);align-items:center;color:var(--el-link-text-color);cursor:pointer;display:inline-flex;flex-direction:row;font-size:var(--el-link-font-size);font-weight:var(--el-link-font-weight);justify-content:center;outline:none;padding:0;position:relative;text-decoration:none;vertical-align:middle}.el-link:hover{color:var(--el-link-hover-text-color)}.el-link.is-underline:hover:after{border-bottom:1px solid var(--el-link-hover-text-color);bottom:0;content:\"\";height:0;left:0;position:absolute;right:0}.el-link.is-disabled{color:var(--el-link-disabled-text-color);cursor:not-allowed}.el-link [class*=el-icon-]+span{margin-left:5px}.el-link.el-link--default:after{border-color:var(--el-link-hover-text-color)}.el-link__inner{align-items:center;display:inline-flex;justify-content:center}.el-link.el-link--primary{--el-link-text-color:var(--el-color-primary);--el-link-hover-text-color:var(--el-color-primary-light-3);--el-link-disabled-text-color:var(--el-color-primary-light-5)}.el-link.el-link--primary.is-underline:hover:after,.el-link.el-link--primary:after{border-color:var(--el-link-text-color)}.el-link.el-link--success{--el-link-text-color:var(--el-color-success);--el-link-hover-text-color:var(--el-color-success-light-3);--el-link-disabled-text-color:var(--el-color-success-light-5)}.el-link.el-link--success.is-underline:hover:after,.el-link.el-link--success:after{border-color:var(--el-link-text-color)}.el-link.el-link--warning{--el-link-text-color:var(--el-color-warning);--el-link-hover-text-color:var(--el-color-warning-light-3);--el-link-disabled-text-color:var(--el-color-warning-light-5)}.el-link.el-link--warning.is-underline:hover:after,.el-link.el-link--warning:after{border-color:var(--el-link-text-color)}.el-link.el-link--danger{--el-link-text-color:var(--el-color-danger);--el-link-hover-text-color:var(--el-color-danger-light-3);--el-link-disabled-text-color:var(--el-color-danger-light-5)}.el-link.el-link--danger.is-underline:hover:after,.el-link.el-link--danger:after{border-color:var(--el-link-text-color)}.el-link.el-link--error{--el-link-text-color:var(--el-color-error);--el-link-hover-text-color:var(--el-color-error-light-3);--el-link-disabled-text-color:var(--el-color-error-light-5)}.el-link.el-link--error.is-underline:hover:after,.el-link.el-link--error:after{border-color:var(--el-link-text-color)}.el-link.el-link--info{--el-link-text-color:var(--el-color-info);--el-link-hover-text-color:var(--el-color-info-light-3);--el-link-disabled-text-color:var(--el-color-info-light-5)}.el-link.el-link--info.is-underline:hover:after,.el-link.el-link--info:after{border-color:var(--el-link-text-color)}.el-checkbox-group{font-size:0;line-height:0}.el-button{--el-button-font-weight:var(--el-font-weight-primary);--el-button-border-color:var(--el-border-color);--el-button-bg-color:var(--el-fill-color-blank);--el-button-text-color:var(--el-text-color-regular);--el-button-disabled-text-color:var(--el-disabled-text-color);--el-button-disabled-bg-color:var(--el-fill-color-blank);--el-button-disabled-border-color:var(--el-border-color-light);--el-button-divide-border-color:rgba(255,255,255,.5);--el-button-hover-text-color:var(--el-color-primary);--el-button-hover-bg-color:var(--el-color-primary-light-9);--el-button-hover-border-color:var(--el-color-primary-light-7);--el-button-active-text-color:var(--el-button-hover-text-color);--el-button-active-border-color:var(--el-color-primary);--el-button-active-bg-color:var(--el-button-hover-bg-color);--el-button-outline-color:var(--el-color-primary-light-5);--el-button-hover-link-text-color:var(--el-color-info);--el-button-active-color:var(--el-text-color-primary);align-items:center;-webkit-appearance:none;background-color:var(--el-button-bg-color);border:var(--el-border);border-color:var(--el-button-border-color);box-sizing:border-box;color:var(--el-button-text-color);cursor:pointer;display:inline-flex;font-weight:var(--el-button-font-weight);height:32px;justify-content:center;line-height:1;outline:none;text-align:center;transition:.1s;-webkit-user-select:none;-moz-user-select:none;user-select:none;vertical-align:middle;white-space:nowrap}.el-button:hover{background-color:var(--el-button-hover-bg-color);border-color:var(--el-button-hover-border-color);color:var(--el-button-hover-text-color);outline:none}.el-button:active{background-color:var(--el-button-active-bg-color);border-color:var(--el-button-active-border-color);color:var(--el-button-active-text-color);outline:none}.el-button:focus-visible{outline:2px solid var(--el-button-outline-color);outline-offset:1px;transition:outline-offset 0s,outline 0s}.el-button>span{align-items:center;display:inline-flex}.el-button+.el-button{margin-left:12px}.el-button{border-radius:var(--el-border-radius-base);font-size:var(--el-font-size-base)}.el-button,.el-button.is-round{padding:8px 15px}.el-button::-moz-focus-inner{border:0}.el-button [class*=el-icon]+span{margin-left:6px}.el-button [class*=el-icon] svg{vertical-align:bottom}.el-button.is-plain{--el-button-hover-text-color:var(--el-color-primary);--el-button-hover-bg-color:var(--el-fill-color-blank);--el-button-hover-border-color:var(--el-color-primary)}.el-button.is-active{background-color:var(--el-button-active-bg-color);border-color:var(--el-button-active-border-color);color:var(--el-button-active-text-color);outline:none}.el-button.is-disabled,.el-button.is-disabled:hover{background-color:var(--el-button-disabled-bg-color);background-image:none;border-color:var(--el-button-disabled-border-color);color:var(--el-button-disabled-text-color);cursor:not-allowed}.el-button.is-loading{pointer-events:none;position:relative}.el-button.is-loading:before{background-color:var(--el-mask-color-extra-light);border-radius:inherit;bottom:-1px;content:\"\";left:-1px;pointer-events:none;position:absolute;right:-1px;top:-1px;z-index:1}.el-button.is-round{border-radius:var(--el-border-radius-round)}.el-button.is-circle{border-radius:50%;padding:8px;width:32px}.el-button.is-text{background-color:transparent;border:0 solid transparent;color:var(--el-button-text-color)}.el-button.is-text.is-disabled{background-color:transparent!important;color:var(--el-button-disabled-text-color)}.el-button.is-text:not(.is-disabled):hover{background-color:var(--el-fill-color-light)}.el-button.is-text:not(.is-disabled):focus-visible{outline:2px solid var(--el-button-outline-color);outline-offset:1px;transition:outline-offset 0s,outline 0s}.el-button.is-text:not(.is-disabled):active{background-color:var(--el-fill-color)}.el-button.is-text:not(.is-disabled).is-has-bg{background-color:var(--el-fill-color-light)}.el-button.is-text:not(.is-disabled).is-has-bg:hover{background-color:var(--el-fill-color)}.el-button.is-text:not(.is-disabled).is-has-bg:active{background-color:var(--el-fill-color-dark)}.el-button__text--expand{letter-spacing:.3em;margin-right:-.3em}.el-button.is-link{background:transparent;border-color:transparent;color:var(--el-button-text-color);height:auto;padding:2px}.el-button.is-link:hover{color:var(--el-button-hover-link-text-color)}.el-button.is-link.is-disabled{background-color:transparent!important;border-color:transparent!important;color:var(--el-button-disabled-text-color)}.el-button.is-link:not(.is-disabled):active,.el-button.is-link:not(.is-disabled):hover{background-color:transparent;border-color:transparent}.el-button.is-link:not(.is-disabled):active{color:var(--el-button-active-color)}.el-button--text{background:transparent;border-color:transparent;color:var(--el-color-primary);padding-left:0;padding-right:0}.el-button--text.is-disabled{background-color:transparent!important;border-color:transparent!important;color:var(--el-button-disabled-text-color)}.el-button--text:not(.is-disabled):hover{background-color:transparent;border-color:transparent;color:var(--el-color-primary-light-3)}.el-button--text:not(.is-disabled):active{background-color:transparent;border-color:transparent;color:var(--el-color-primary-dark-2)}.el-button__link--expand{letter-spacing:.3em;margin-right:-.3em}.el-button--primary{--el-button-text-color:var(--el-color-white);--el-button-bg-color:var(--el-color-primary);--el-button-border-color:var(--el-color-primary);--el-button-outline-color:var(--el-color-primary-light-5);--el-button-active-color:var(--el-color-primary-dark-2);--el-button-hover-text-color:var(--el-color-white);--el-button-hover-link-text-color:var(--el-color-primary-light-5);--el-button-hover-bg-color:var(--el-color-primary-light-3);--el-button-hover-border-color:var(--el-color-primary-light-3);--el-button-active-bg-color:var(--el-color-primary-dark-2);--el-button-active-border-color:var(--el-color-primary-dark-2);--el-button-disabled-text-color:var(--el-color-white);--el-button-disabled-bg-color:var(--el-color-primary-light-5);--el-button-disabled-border-color:var(--el-color-primary-light-5)}.el-button--primary.is-link,.el-button--primary.is-plain,.el-button--primary.is-text{--el-button-text-color:var(--el-color-primary);--el-button-bg-color:var(--el-color-primary-light-9);--el-button-border-color:var(--el-color-primary-light-5);--el-button-hover-text-color:var(--el-color-white);--el-button-hover-bg-color:var(--el-color-primary);--el-button-hover-border-color:var(--el-color-primary);--el-button-active-text-color:var(--el-color-white)}.el-button--primary.is-link.is-disabled,.el-button--primary.is-link.is-disabled:active,.el-button--primary.is-link.is-disabled:focus,.el-button--primary.is-link.is-disabled:hover,.el-button--primary.is-plain.is-disabled,.el-button--primary.is-plain.is-disabled:active,.el-button--primary.is-plain.is-disabled:focus,.el-button--primary.is-plain.is-disabled:hover,.el-button--primary.is-text.is-disabled,.el-button--primary.is-text.is-disabled:active,.el-button--primary.is-text.is-disabled:focus,.el-button--primary.is-text.is-disabled:hover{background-color:var(--el-color-primary-light-9);border-color:var(--el-color-primary-light-8);color:var(--el-color-primary-light-5)}.el-button--success{--el-button-text-color:var(--el-color-white);--el-button-bg-color:var(--el-color-success);--el-button-border-color:var(--el-color-success);--el-button-outline-color:var(--el-color-success-light-5);--el-button-active-color:var(--el-color-success-dark-2);--el-button-hover-text-color:var(--el-color-white);--el-button-hover-link-text-color:var(--el-color-success-light-5);--el-button-hover-bg-color:var(--el-color-success-light-3);--el-button-hover-border-color:var(--el-color-success-light-3);--el-button-active-bg-color:var(--el-color-success-dark-2);--el-button-active-border-color:var(--el-color-success-dark-2);--el-button-disabled-text-color:var(--el-color-white);--el-button-disabled-bg-color:var(--el-color-success-light-5);--el-button-disabled-border-color:var(--el-color-success-light-5)}.el-button--success.is-link,.el-button--success.is-plain,.el-button--success.is-text{--el-button-text-color:var(--el-color-success);--el-button-bg-color:var(--el-color-success-light-9);--el-button-border-color:var(--el-color-success-light-5);--el-button-hover-text-color:var(--el-color-white);--el-button-hover-bg-color:var(--el-color-success);--el-button-hover-border-color:var(--el-color-success);--el-button-active-text-color:var(--el-color-white)}.el-button--success.is-link.is-disabled,.el-button--success.is-link.is-disabled:active,.el-button--success.is-link.is-disabled:focus,.el-button--success.is-link.is-disabled:hover,.el-button--success.is-plain.is-disabled,.el-button--success.is-plain.is-disabled:active,.el-button--success.is-plain.is-disabled:focus,.el-button--success.is-plain.is-disabled:hover,.el-button--success.is-text.is-disabled,.el-button--success.is-text.is-disabled:active,.el-button--success.is-text.is-disabled:focus,.el-button--success.is-text.is-disabled:hover{background-color:var(--el-color-success-light-9);border-color:var(--el-color-success-light-8);color:var(--el-color-success-light-5)}.el-button--warning{--el-button-text-color:var(--el-color-white);--el-button-bg-color:var(--el-color-warning);--el-button-border-color:var(--el-color-warning);--el-button-outline-color:var(--el-color-warning-light-5);--el-button-active-color:var(--el-color-warning-dark-2);--el-button-hover-text-color:var(--el-color-white);--el-button-hover-link-text-color:var(--el-color-warning-light-5);--el-button-hover-bg-color:var(--el-color-warning-light-3);--el-button-hover-border-color:var(--el-color-warning-light-3);--el-button-active-bg-color:var(--el-color-warning-dark-2);--el-button-active-border-color:var(--el-color-warning-dark-2);--el-button-disabled-text-color:var(--el-color-white);--el-button-disabled-bg-color:var(--el-color-warning-light-5);--el-button-disabled-border-color:var(--el-color-warning-light-5)}.el-button--warning.is-link,.el-button--warning.is-plain,.el-button--warning.is-text{--el-button-text-color:var(--el-color-warning);--el-button-bg-color:var(--el-color-warning-light-9);--el-button-border-color:var(--el-color-warning-light-5);--el-button-hover-text-color:var(--el-color-white);--el-button-hover-bg-color:var(--el-color-warning);--el-button-hover-border-color:var(--el-color-warning);--el-button-active-text-color:var(--el-color-white)}.el-button--warning.is-link.is-disabled,.el-button--warning.is-link.is-disabled:active,.el-button--warning.is-link.is-disabled:focus,.el-button--warning.is-link.is-disabled:hover,.el-button--warning.is-plain.is-disabled,.el-button--warning.is-plain.is-disabled:active,.el-button--warning.is-plain.is-disabled:focus,.el-button--warning.is-plain.is-disabled:hover,.el-button--warning.is-text.is-disabled,.el-button--warning.is-text.is-disabled:active,.el-button--warning.is-text.is-disabled:focus,.el-button--warning.is-text.is-disabled:hover{background-color:var(--el-color-warning-light-9);border-color:var(--el-color-warning-light-8);color:var(--el-color-warning-light-5)}.el-button--danger{--el-button-text-color:var(--el-color-white);--el-button-bg-color:var(--el-color-danger);--el-button-border-color:var(--el-color-danger);--el-button-outline-color:var(--el-color-danger-light-5);--el-button-active-color:var(--el-color-danger-dark-2);--el-button-hover-text-color:var(--el-color-white);--el-button-hover-link-text-color:var(--el-color-danger-light-5);--el-button-hover-bg-color:var(--el-color-danger-light-3);--el-button-hover-border-color:var(--el-color-danger-light-3);--el-button-active-bg-color:var(--el-color-danger-dark-2);--el-button-active-border-color:var(--el-color-danger-dark-2);--el-button-disabled-text-color:var(--el-color-white);--el-button-disabled-bg-color:var(--el-color-danger-light-5);--el-button-disabled-border-color:var(--el-color-danger-light-5)}.el-button--danger.is-link,.el-button--danger.is-plain,.el-button--danger.is-text{--el-button-text-color:var(--el-color-danger);--el-button-bg-color:var(--el-color-danger-light-9);--el-button-border-color:var(--el-color-danger-light-5);--el-button-hover-text-color:var(--el-color-white);--el-button-hover-bg-color:var(--el-color-danger);--el-button-hover-border-color:var(--el-color-danger);--el-button-active-text-color:var(--el-color-white)}.el-button--danger.is-link.is-disabled,.el-button--danger.is-link.is-disabled:active,.el-button--danger.is-link.is-disabled:focus,.el-button--danger.is-link.is-disabled:hover,.el-button--danger.is-plain.is-disabled,.el-button--danger.is-plain.is-disabled:active,.el-button--danger.is-plain.is-disabled:focus,.el-button--danger.is-plain.is-disabled:hover,.el-button--danger.is-text.is-disabled,.el-button--danger.is-text.is-disabled:active,.el-button--danger.is-text.is-disabled:focus,.el-button--danger.is-text.is-disabled:hover{background-color:var(--el-color-danger-light-9);border-color:var(--el-color-danger-light-8);color:var(--el-color-danger-light-5)}.el-button--info{--el-button-text-color:var(--el-color-white);--el-button-bg-color:var(--el-color-info);--el-button-border-color:var(--el-color-info);--el-button-outline-color:var(--el-color-info-light-5);--el-button-active-color:var(--el-color-info-dark-2);--el-button-hover-text-color:var(--el-color-white);--el-button-hover-link-text-color:var(--el-color-info-light-5);--el-button-hover-bg-color:var(--el-color-info-light-3);--el-button-hover-border-color:var(--el-color-info-light-3);--el-button-active-bg-color:var(--el-color-info-dark-2);--el-button-active-border-color:var(--el-color-info-dark-2);--el-button-disabled-text-color:var(--el-color-white);--el-button-disabled-bg-color:var(--el-color-info-light-5);--el-button-disabled-border-color:var(--el-color-info-light-5)}.el-button--info.is-link,.el-button--info.is-plain,.el-button--info.is-text{--el-button-text-color:var(--el-color-info);--el-button-bg-color:var(--el-color-info-light-9);--el-button-border-color:var(--el-color-info-light-5);--el-button-hover-text-color:var(--el-color-white);--el-button-hover-bg-color:var(--el-color-info);--el-button-hover-border-color:var(--el-color-info);--el-button-active-text-color:var(--el-color-white)}.el-button--info.is-link.is-disabled,.el-button--info.is-link.is-disabled:active,.el-button--info.is-link.is-disabled:focus,.el-button--info.is-link.is-disabled:hover,.el-button--info.is-plain.is-disabled,.el-button--info.is-plain.is-disabled:active,.el-button--info.is-plain.is-disabled:focus,.el-button--info.is-plain.is-disabled:hover,.el-button--info.is-text.is-disabled,.el-button--info.is-text.is-disabled:active,.el-button--info.is-text.is-disabled:focus,.el-button--info.is-text.is-disabled:hover{background-color:var(--el-color-info-light-9);border-color:var(--el-color-info-light-8);color:var(--el-color-info-light-5)}.el-button--large{--el-button-size:40px;height:var(--el-button-size)}.el-button--large [class*=el-icon]+span{margin-left:8px}.el-button--large{border-radius:var(--el-border-radius-base);font-size:var(--el-font-size-base);padding:12px 19px}.el-button--large.is-round{padding:12px 19px}.el-button--large.is-circle{padding:12px;width:var(--el-button-size)}.el-button--small{--el-button-size:24px;height:var(--el-button-size)}.el-button--small [class*=el-icon]+span{margin-left:4px}.el-button--small{border-radius:calc(var(--el-border-radius-base) - 1px);font-size:12px;padding:5px 11px}.el-button--small.is-round{padding:5px 11px}.el-button--small.is-circle{padding:5px;width:var(--el-button-size)}.el-textarea{--el-input-text-color:var(--el-text-color-regular);--el-input-border:var(--el-border);--el-input-hover-border:var(--el-border-color-hover);--el-input-focus-border:var(--el-color-primary);--el-input-transparent-border:0 0 0 1px transparent inset;--el-input-border-color:var(--el-border-color);--el-input-border-radius:var(--el-border-radius-base);--el-input-bg-color:var(--el-fill-color-blank);--el-input-icon-color:var(--el-text-color-placeholder);--el-input-placeholder-color:var(--el-text-color-placeholder);--el-input-hover-border-color:var(--el-border-color-hover);--el-input-clear-hover-color:var(--el-text-color-secondary);--el-input-focus-border-color:var(--el-color-primary);--el-input-width:100%;display:inline-block;font-size:var(--el-font-size-base);position:relative;vertical-align:bottom;width:100%}.el-textarea__inner{-webkit-appearance:none;background-color:var(--el-input-bg-color,var(--el-fill-color-blank));background-image:none;border:none;border-radius:var(--el-input-border-radius,var(--el-border-radius-base));box-shadow:0 0 0 1px var(--el-input-border-color,var(--el-border-color)) inset;box-sizing:border-box;color:var(--el-input-text-color,var(--el-text-color-regular));display:block;font-family:inherit;font-size:inherit;line-height:1.5;padding:5px 11px;position:relative;resize:vertical;transition:var(--el-transition-box-shadow);width:100%}.el-textarea__inner::-moz-placeholder{color:var(--el-input-placeholder-color,var(--el-text-color-placeholder))}.el-textarea__inner::placeholder{color:var(--el-input-placeholder-color,var(--el-text-color-placeholder))}.el-textarea__inner:hover{box-shadow:0 0 0 1px var(--el-input-hover-border-color) inset}.el-textarea__inner:focus{box-shadow:0 0 0 1px var(--el-input-focus-border-color) inset;outline:none}.el-textarea .el-input__count{background:var(--el-fill-color-blank);bottom:5px;color:var(--el-color-info);font-size:12px;line-height:14px;position:absolute;right:10px}.el-textarea.is-disabled .el-textarea__inner{background-color:var(--el-disabled-bg-color);box-shadow:0 0 0 1px var(--el-disabled-border-color) inset;color:var(--el-disabled-text-color);cursor:not-allowed}.el-textarea.is-disabled .el-textarea__inner::-moz-placeholder{color:var(--el-text-color-placeholder)}.el-textarea.is-disabled .el-textarea__inner::placeholder{color:var(--el-text-color-placeholder)}.el-textarea.is-exceed .el-textarea__inner{box-shadow:0 0 0 1px var(--el-color-danger) inset}.el-textarea.is-exceed .el-input__count{color:var(--el-color-danger)}.el-input{--el-input-text-color:var(--el-text-color-regular);--el-input-border:var(--el-border);--el-input-hover-border:var(--el-border-color-hover);--el-input-focus-border:var(--el-color-primary);--el-input-transparent-border:0 0 0 1px transparent inset;--el-input-border-color:var(--el-border-color);--el-input-border-radius:var(--el-border-radius-base);--el-input-bg-color:var(--el-fill-color-blank);--el-input-icon-color:var(--el-text-color-placeholder);--el-input-placeholder-color:var(--el-text-color-placeholder);--el-input-hover-border-color:var(--el-border-color-hover);--el-input-clear-hover-color:var(--el-text-color-secondary);--el-input-focus-border-color:var(--el-color-primary);--el-input-width:100%;--el-input-height:var(--el-component-size);box-sizing:border-box;display:inline-flex;font-size:var(--el-font-size-base);line-height:var(--el-input-height);position:relative;vertical-align:middle;width:var(--el-input-width)}.el-input::-webkit-scrollbar{width:6px;z-index:11}.el-input::-webkit-scrollbar:horizontal{height:6px}.el-input::-webkit-scrollbar-thumb{background:var(--el-text-color-disabled);border-radius:5px;width:6px}.el-input::-webkit-scrollbar-corner,.el-input::-webkit-scrollbar-track{background:var(--el-fill-color-blank)}.el-input::-webkit-scrollbar-track-piece{background:var(--el-fill-color-blank);width:6px}.el-input .el-input__clear,.el-input .el-input__password{color:var(--el-input-icon-color);cursor:pointer;font-size:14px}.el-input .el-input__clear:hover,.el-input .el-input__password:hover{color:var(--el-input-clear-hover-color)}.el-input .el-input__count{align-items:center;color:var(--el-color-info);display:inline-flex;font-size:12px;height:100%}.el-input .el-input__count .el-input__count-inner{background:var(--el-fill-color-blank);display:inline-block;line-height:normal;padding-left:8px}.el-input__wrapper{align-items:center;background-color:var(--el-input-bg-color,var(--el-fill-color-blank));background-image:none;border-radius:var(--el-input-border-radius,var(--el-border-radius-base));box-shadow:0 0 0 1px var(--el-input-border-color,var(--el-border-color)) inset;cursor:text;display:inline-flex;flex-grow:1;justify-content:center;padding:1px 11px;transform:translateZ(0);transition:var(--el-transition-box-shadow)}.el-input__wrapper:hover{box-shadow:0 0 0 1px var(--el-input-hover-border-color) inset}.el-input__wrapper.is-focus{box-shadow:0 0 0 1px var(--el-input-focus-border-color) inset}.el-input__inner{--el-input-inner-height:calc(var(--el-input-height, 32px) - 2px);-webkit-appearance:none;background:none;border:none;box-sizing:border-box;color:var(--el-input-text-color,var(--el-text-color-regular));flex-grow:1;font-size:inherit;height:var(--el-input-inner-height);line-height:var(--el-input-inner-height);outline:none;padding:0;width:100%}.el-input__inner:focus{outline:none}.el-input__inner::-moz-placeholder{color:var(--el-input-placeholder-color,var(--el-text-color-placeholder))}.el-input__inner::placeholder{color:var(--el-input-placeholder-color,var(--el-text-color-placeholder))}.el-input__inner[type=password]::-ms-reveal{display:none}.el-input__inner[type=number]{line-height:1}.el-input__prefix{color:var(--el-input-icon-color,var(--el-text-color-placeholder));display:inline-flex;flex-shrink:0;flex-wrap:nowrap;height:100%;pointer-events:none;text-align:center;transition:all var(--el-transition-duration);white-space:nowrap}.el-input__prefix-inner{align-items:center;display:inline-flex;justify-content:center;pointer-events:all}.el-input__prefix-inner>:last-child{margin-right:8px}.el-input__prefix-inner>:first-child,.el-input__prefix-inner>:first-child.el-input__icon{margin-left:0}.el-input__suffix{color:var(--el-input-icon-color,var(--el-text-color-placeholder));display:inline-flex;flex-shrink:0;flex-wrap:nowrap;height:100%;pointer-events:none;text-align:center;transition:all var(--el-transition-duration);white-space:nowrap}.el-input__suffix-inner{align-items:center;display:inline-flex;justify-content:center;pointer-events:all}.el-input__suffix-inner>:first-child{margin-left:8px}.el-input .el-input__icon{align-items:center;display:flex;height:inherit;justify-content:center;line-height:inherit;margin-left:8px;transition:all var(--el-transition-duration)}.el-input__validateIcon{pointer-events:none}.el-input.is-active .el-input__wrapper{box-shadow:0 0 0 1px var(--el-input-focus-color, ) inset}.el-input.is-disabled{cursor:not-allowed}.el-input.is-disabled .el-input__wrapper{background-color:var(--el-disabled-bg-color);box-shadow:0 0 0 1px var(--el-disabled-border-color) inset}.el-input.is-disabled .el-input__inner{color:var(--el-disabled-text-color);-webkit-text-fill-color:var(--el-disabled-text-color);cursor:not-allowed}.el-input.is-disabled .el-input__inner::-moz-placeholder{color:var(--el-text-color-placeholder)}.el-input.is-disabled .el-input__inner::placeholder{color:var(--el-text-color-placeholder)}.el-input.is-disabled .el-input__icon{cursor:not-allowed}.el-input.is-exceed .el-input__wrapper{box-shadow:0 0 0 1px var(--el-color-danger) inset}.el-input.is-exceed .el-input__suffix .el-input__count{color:var(--el-color-danger)}.el-input--large{--el-input-height:var(--el-component-size-large);font-size:14px}.el-input--large .el-input__wrapper{padding:1px 15px}.el-input--large .el-input__inner{--el-input-inner-height:calc(var(--el-input-height, 40px) - 2px)}.el-input--small{--el-input-height:var(--el-component-size-small);font-size:12px}.el-input--small .el-input__wrapper{padding:1px 7px}.el-input--small .el-input__inner{--el-input-inner-height:calc(var(--el-input-height, 24px) - 2px)}.el-input-group{align-items:stretch;display:inline-flex;width:100%}.el-input-group__append,.el-input-group__prepend{align-items:center;background-color:var(--el-fill-color-light);border-radius:var(--el-input-border-radius);color:var(--el-color-info);display:inline-flex;justify-content:center;min-height:100%;padding:0 20px;position:relative;white-space:nowrap}.el-input-group__append:focus,.el-input-group__prepend:focus{outline:none}.el-input-group__append .el-button,.el-input-group__append .el-select,.el-input-group__prepend .el-button,.el-input-group__prepend .el-select{display:inline-block;margin:0 -20px}.el-input-group__append button.el-button,.el-input-group__append button.el-button:hover,.el-input-group__append div.el-select .el-select__wrapper,.el-input-group__append div.el-select:hover .el-select__wrapper,.el-input-group__prepend button.el-button,.el-input-group__prepend button.el-button:hover,.el-input-group__prepend div.el-select .el-select__wrapper,.el-input-group__prepend div.el-select:hover .el-select__wrapper{background-color:transparent;border-color:transparent;color:inherit}.el-input-group__append .el-button,.el-input-group__append .el-input,.el-input-group__prepend .el-button,.el-input-group__prepend .el-input{font-size:inherit}.el-input-group__prepend{border-bottom-right-radius:0;border-right:0;border-top-right-radius:0;box-shadow:1px 0 0 0 var(--el-input-border-color) inset,0 1px 0 0 var(--el-input-border-color) inset,0 -1px 0 0 var(--el-input-border-color) inset}.el-input-group__append{border-left:0;box-shadow:0 1px 0 0 var(--el-input-border-color) inset,0 -1px 0 0 var(--el-input-border-color) inset,-1px 0 0 0 var(--el-input-border-color) inset}.el-input-group--prepend>.el-input__wrapper,.el-input-group__append{border-bottom-left-radius:0;border-top-left-radius:0}.el-input-group--prepend .el-input-group__prepend .el-select .el-select__wrapper{border-bottom-right-radius:0;border-top-right-radius:0;box-shadow:1px 0 0 0 var(--el-input-border-color) inset,0 1px 0 0 var(--el-input-border-color) inset,0 -1px 0 0 var(--el-input-border-color) inset}.el-input-group--append>.el-input__wrapper{border-bottom-right-radius:0;border-top-right-radius:0}.el-input-group--append .el-input-group__append .el-select .el-select__wrapper{border-bottom-left-radius:0;border-top-left-radius:0;box-shadow:0 1px 0 0 var(--el-input-border-color) inset,0 -1px 0 0 var(--el-input-border-color) inset,-1px 0 0 0 var(--el-input-border-color) inset}.el-input-hidden{display:none!important}.el-badge{--el-badge-bg-color:var(--el-color-danger);--el-badge-radius:10px;--el-badge-font-size:12px;--el-badge-padding:6px;--el-badge-size:18px;display:inline-block;position:relative;vertical-align:middle;width:-moz-fit-content;width:fit-content}.el-badge__content{align-items:center;background-color:var(--el-badge-bg-color);border:1px solid var(--el-bg-color);border-radius:var(--el-badge-radius);color:var(--el-color-white);display:inline-flex;font-size:var(--el-badge-font-size);height:var(--el-badge-size);justify-content:center;padding:0 var(--el-badge-padding);white-space:nowrap}.el-badge__content.is-fixed{position:absolute;right:calc(1px + var(--el-badge-size)/2);top:0;transform:translateY(-50%) translate(100%);z-index:var(--el-index-normal)}.el-badge__content.is-fixed.is-dot{right:5px}.el-badge__content.is-dot{border-radius:50%;height:8px;padding:0;right:0;width:8px}.el-badge__content.is-hide-zero{display:none}.el-badge__content--primary{background-color:var(--el-color-primary)}.el-badge__content--success{background-color:var(--el-color-success)}.el-badge__content--warning{background-color:var(--el-color-warning)}.el-badge__content--info{background-color:var(--el-color-info)}.el-badge__content--danger{background-color:var(--el-color-danger)}.el-message{--el-message-bg-color:var(--el-color-info-light-9);--el-message-border-color:var(--el-border-color-lighter);--el-message-padding:11px 15px;--el-message-close-size:16px;--el-message-close-icon-color:var(--el-text-color-placeholder);--el-message-close-hover-color:var(--el-text-color-secondary);align-items:center;background-color:var(--el-message-bg-color);border-color:var(--el-message-border-color);border-radius:var(--el-border-radius-base);border-style:var(--el-border-style);border-width:var(--el-border-width);box-sizing:border-box;display:flex;gap:8px;left:50%;max-width:calc(100% - 32px);padding:var(--el-message-padding);position:fixed;top:20px;transform:translate(-50%);transition:opacity var(--el-transition-duration),transform .4s,top .4s;width:-moz-fit-content;width:fit-content}.el-message.is-center{justify-content:center}.el-message.is-plain{background-color:var(--el-bg-color-overlay);border-color:var(--el-bg-color-overlay);box-shadow:var(--el-box-shadow-light)}.el-message p{margin:0}.el-message--success{--el-message-bg-color:var(--el-color-success-light-9);--el-message-border-color:var(--el-color-success-light-8);--el-message-text-color:var(--el-color-success)}.el-message--success .el-message__content{color:var(--el-message-text-color);overflow-wrap:break-word}.el-message .el-message-icon--success{color:var(--el-message-text-color)}.el-message--info{--el-message-bg-color:var(--el-color-info-light-9);--el-message-border-color:var(--el-color-info-light-8);--el-message-text-color:var(--el-color-info)}.el-message--info .el-message__content{color:var(--el-message-text-color);overflow-wrap:break-word}.el-message .el-message-icon--info{color:var(--el-message-text-color)}.el-message--warning{--el-message-bg-color:var(--el-color-warning-light-9);--el-message-border-color:var(--el-color-warning-light-8);--el-message-text-color:var(--el-color-warning)}.el-message--warning .el-message__content{color:var(--el-message-text-color);overflow-wrap:break-word}.el-message .el-message-icon--warning{color:var(--el-message-text-color)}.el-message--error{--el-message-bg-color:var(--el-color-error-light-9);--el-message-border-color:var(--el-color-error-light-8);--el-message-text-color:var(--el-color-error)}.el-message--error .el-message__content{color:var(--el-message-text-color);overflow-wrap:break-word}.el-message .el-message-icon--error{color:var(--el-message-text-color)}.el-message .el-message__badge{position:absolute;right:-8px;top:-8px}.el-message__content{font-size:14px;line-height:1;padding:0}.el-message__content:focus{outline-width:0}.el-message .el-message__closeBtn{color:var(--el-message-close-icon-color);cursor:pointer;font-size:var(--el-message-close-size)}.el-message .el-message__closeBtn:focus{outline-width:0}.el-message .el-message__closeBtn:hover{color:var(--el-message-close-hover-color)}.el-message-fade-enter-from,.el-message-fade-leave-to{opacity:0;transform:translate(-50%,-100%)}.el-checkbox{--el-checkbox-font-size:14px;--el-checkbox-font-weight:var(--el-font-weight-primary);--el-checkbox-text-color:var(--el-text-color-regular);--el-checkbox-input-height:14px;--el-checkbox-input-width:14px;--el-checkbox-border-radius:var(--el-border-radius-small);--el-checkbox-bg-color:var(--el-fill-color-blank);--el-checkbox-input-border:var(--el-border);--el-checkbox-disabled-border-color:var(--el-border-color);--el-checkbox-disabled-input-fill:var(--el-fill-color-light);--el-checkbox-disabled-icon-color:var(--el-text-color-placeholder);--el-checkbox-disabled-checked-input-fill:var(--el-border-color-extra-light);--el-checkbox-disabled-checked-input-border-color:var(--el-border-color);--el-checkbox-disabled-checked-icon-color:var(--el-text-color-placeholder);--el-checkbox-checked-text-color:var(--el-color-primary);--el-checkbox-checked-input-border-color:var(--el-color-primary);--el-checkbox-checked-bg-color:var(--el-color-primary);--el-checkbox-checked-icon-color:var(--el-color-white);--el-checkbox-input-border-color-hover:var(--el-color-primary);align-items:center;color:var(--el-checkbox-text-color);cursor:pointer;display:inline-flex;font-size:var(--el-font-size-base);font-weight:var(--el-checkbox-font-weight);height:var(--el-checkbox-height,32px);margin-right:30px;position:relative;-webkit-user-select:none;-moz-user-select:none;user-select:none;white-space:nowrap}.el-checkbox.is-disabled{cursor:not-allowed}.el-checkbox.is-bordered{border:var(--el-border);border-radius:var(--el-border-radius-base);box-sizing:border-box;padding:0 15px 0 9px}.el-checkbox.is-bordered.is-checked{border-color:var(--el-color-primary)}.el-checkbox.is-bordered.is-disabled{border-color:var(--el-border-color-lighter)}.el-checkbox.is-bordered.el-checkbox--large{border-radius:var(--el-border-radius-base);padding:0 19px 0 11px}.el-checkbox.is-bordered.el-checkbox--large .el-checkbox__label{font-size:var(--el-font-size-base)}.el-checkbox.is-bordered.el-checkbox--large .el-checkbox__inner{height:14px;width:14px}.el-checkbox.is-bordered.el-checkbox--small{border-radius:calc(var(--el-border-radius-base) - 1px);padding:0 11px 0 7px}.el-checkbox.is-bordered.el-checkbox--small .el-checkbox__label{font-size:12px}.el-checkbox.is-bordered.el-checkbox--small .el-checkbox__inner{height:12px;width:12px}.el-checkbox.is-bordered.el-checkbox--small .el-checkbox__inner:after{height:6px;width:2px}.el-checkbox input:focus-visible+.el-checkbox__inner{border-radius:var(--el-checkbox-border-radius);outline:2px solid var(--el-checkbox-input-border-color-hover);outline-offset:1px}.el-checkbox__input{cursor:pointer;display:inline-flex;outline:none;position:relative;white-space:nowrap}.el-checkbox__input.is-disabled .el-checkbox__inner{background-color:var(--el-checkbox-disabled-input-fill);border-color:var(--el-checkbox-disabled-border-color);cursor:not-allowed}.el-checkbox__input.is-disabled .el-checkbox__inner:after{border-color:var(--el-checkbox-disabled-icon-color);cursor:not-allowed}.el-checkbox__input.is-disabled.is-checked .el-checkbox__inner{background-color:var(--el-checkbox-disabled-checked-input-fill);border-color:var(--el-checkbox-disabled-checked-input-border-color)}.el-checkbox__input.is-disabled.is-checked .el-checkbox__inner:after{border-color:var(--el-checkbox-disabled-checked-icon-color)}.el-checkbox__input.is-disabled.is-indeterminate .el-checkbox__inner{background-color:var(--el-checkbox-disabled-checked-input-fill);border-color:var(--el-checkbox-disabled-checked-input-border-color)}.el-checkbox__input.is-disabled.is-indeterminate .el-checkbox__inner:before{background-color:var(--el-checkbox-disabled-checked-icon-color);border-color:var(--el-checkbox-disabled-checked-icon-color)}.el-checkbox__input.is-disabled+span.el-checkbox__label{color:var(--el-disabled-text-color);cursor:not-allowed}.el-checkbox__input.is-checked .el-checkbox__inner{background-color:var(--el-checkbox-checked-bg-color);border-color:var(--el-checkbox-checked-input-border-color)}.el-checkbox__input.is-checked .el-checkbox__inner:after{border-color:var(--el-checkbox-checked-icon-color);transform:rotate(45deg) scaleY(1)}.el-checkbox__input.is-checked+.el-checkbox__label{color:var(--el-checkbox-checked-text-color)}.el-checkbox__input.is-focus:not(.is-checked) .el-checkbox__original:not(:focus-visible){border-color:var(--el-checkbox-input-border-color-hover)}.el-checkbox__input.is-indeterminate .el-checkbox__inner{background-color:var(--el-checkbox-checked-bg-color);border-color:var(--el-checkbox-checked-input-border-color)}.el-checkbox__input.is-indeterminate .el-checkbox__inner:before{background-color:var(--el-checkbox-checked-icon-color);content:\"\";display:block;height:2px;left:0;position:absolute;right:0;top:5px;transform:scale(.5)}.el-checkbox__input.is-indeterminate .el-checkbox__inner:after{display:none}.el-checkbox__inner{background-color:var(--el-checkbox-bg-color);border:var(--el-checkbox-input-border);border-radius:var(--el-checkbox-border-radius);box-sizing:border-box;display:inline-block;height:var(--el-checkbox-input-height);position:relative;transition:border-color .25s cubic-bezier(.71,-.46,.29,1.46),background-color .25s cubic-bezier(.71,-.46,.29,1.46),outline .25s cubic-bezier(.71,-.46,.29,1.46);width:var(--el-checkbox-input-width);z-index:var(--el-index-normal)}.el-checkbox__inner:hover{border-color:var(--el-checkbox-input-border-color-hover)}.el-checkbox__inner:after{border:1px solid transparent;border-left:0;border-top:0;box-sizing:content-box;content:\"\";height:7px;left:4px;position:absolute;top:1px;transform:rotate(45deg) scaleY(0);transform-origin:center;transition:transform .15s ease-in .05s;width:3px}.el-checkbox__original{height:0;margin:0;opacity:0;outline:none;position:absolute;width:0;z-index:-1}.el-checkbox__label{display:inline-block;font-size:var(--el-checkbox-font-size);line-height:1;padding-left:8px}.el-checkbox.el-checkbox--large{height:40px}.el-checkbox.el-checkbox--large .el-checkbox__label{font-size:14px}.el-checkbox.el-checkbox--large .el-checkbox__inner{height:14px;width:14px}.el-checkbox.el-checkbox--small{height:24px}.el-checkbox.el-checkbox--small .el-checkbox__label{font-size:12px}.el-checkbox.el-checkbox--small .el-checkbox__inner{height:12px;width:12px}.el-checkbox.el-checkbox--small .el-checkbox__input.is-indeterminate .el-checkbox__inner:before{top:4px}.el-checkbox.el-checkbox--small .el-checkbox__inner:after{height:6px;width:2px}.el-checkbox:last-of-type{margin-right:0}.el-dialog{--el-dialog-width:50%;--el-dialog-margin-top:15vh;--el-dialog-bg-color:var(--el-bg-color);--el-dialog-box-shadow:var(--el-box-shadow);--el-dialog-title-font-size:var(--el-font-size-large);--el-dialog-content-font-size:14px;--el-dialog-font-line-height:var(--el-font-line-height-primary);--el-dialog-padding-primary:16px;--el-dialog-border-radius:var(--el-border-radius-base);background:var(--el-dialog-bg-color);border-radius:var(--el-dialog-border-radius);box-shadow:var(--el-dialog-box-shadow);box-sizing:border-box;margin:var(--el-dialog-margin-top,15vh) auto 50px;overflow-wrap:break-word;padding:var(--el-dialog-padding-primary);position:relative;width:var(--el-dialog-width,50%)}.el-dialog:focus{outline:none!important}.el-dialog.is-align-center{margin:auto}.el-dialog.is-fullscreen{--el-dialog-width:100%;--el-dialog-margin-top:0;height:100%;margin-bottom:0;overflow:auto}.el-dialog__wrapper{bottom:0;left:0;margin:0;overflow:auto;position:fixed;right:0;top:0}.el-dialog.is-draggable .el-dialog__header{cursor:move;-webkit-user-select:none;-moz-user-select:none;user-select:none}.el-dialog__header{padding-bottom:var(--el-dialog-padding-primary)}.el-dialog__header.show-close{padding-right:calc(var(--el-dialog-padding-primary) + var(--el-message-close-size, 16px))}.el-dialog__headerbtn{background:transparent;border:none;cursor:pointer;font-size:var(--el-message-close-size,16px);height:48px;outline:none;padding:0;position:absolute;right:0;top:0;width:48px}.el-dialog__headerbtn .el-dialog__close{color:var(--el-color-info);font-size:inherit}.el-dialog__headerbtn:focus .el-dialog__close,.el-dialog__headerbtn:hover .el-dialog__close{color:var(--el-color-primary)}.el-dialog__title{color:var(--el-text-color-primary);font-size:var(--el-dialog-title-font-size);line-height:var(--el-dialog-font-line-height)}.el-dialog__body{color:var(--el-text-color-regular);font-size:var(--el-dialog-content-font-size)}.el-dialog__footer{box-sizing:border-box;padding-top:var(--el-dialog-padding-primary);text-align:right}.el-dialog--center{text-align:center}.el-dialog--center .el-dialog__body{text-align:initial}.el-dialog--center .el-dialog__footer{text-align:inherit}.el-overlay-dialog{bottom:0;left:0;overflow:auto;position:fixed;right:0;top:0}.dialog-fade-enter-active{animation:modal-fade-in var(--el-transition-duration)}.dialog-fade-enter-active .el-overlay-dialog{animation:dialog-fade-in var(--el-transition-duration)}.dialog-fade-leave-active{animation:modal-fade-out var(--el-transition-duration)}.dialog-fade-leave-active .el-overlay-dialog{animation:dialog-fade-out var(--el-transition-duration)}@keyframes dialog-fade-in{0%{opacity:0;transform:translate3d(0,-20px,0)}to{opacity:1;transform:translateZ(0)}}@keyframes dialog-fade-out{0%{opacity:1;transform:translateZ(0)}to{opacity:0;transform:translate3d(0,-20px,0)}}@keyframes modal-fade-in{0%{opacity:0}to{opacity:1}}@keyframes modal-fade-out{0%{opacity:1}to{opacity:0}}.el-overlay{background-color:var(--el-overlay-color-lighter);bottom:0;height:100%;left:0;overflow:auto;position:fixed;right:0;top:0;z-index:2000}.el-overlay .el-overlay-root{height:0}.el-form{--el-form-label-font-size:var(--el-font-size-base);--el-form-inline-content-width:220px}.el-form--inline .el-form-item{display:inline-flex;margin-right:32px;vertical-align:middle}.el-form--inline.el-form--label-top{display:flex;flex-wrap:wrap}.el-form--inline.el-form--label-top .el-form-item{display:block}.el-form-item{display:flex;--font-size:14px;margin-bottom:18px}.el-form-item .el-form-item{margin-bottom:0}.el-form-item .el-input__validateIcon{display:none}.el-form-item--large{--font-size:14px;--el-form-label-font-size:var(--font-size);margin-bottom:22px}.el-form-item--large .el-form-item__label{height:40px;line-height:40px}.el-form-item--large .el-form-item__content{line-height:40px}.el-form-item--large .el-form-item__error{padding-top:4px}.el-form-item--default{--font-size:14px;--el-form-label-font-size:var(--font-size);margin-bottom:18px}.el-form-item--default .el-form-item__label{height:32px;line-height:32px}.el-form-item--default .el-form-item__content{line-height:32px}.el-form-item--default .el-form-item__error{padding-top:2px}.el-form-item--small{--font-size:12px;--el-form-label-font-size:var(--font-size);margin-bottom:18px}.el-form-item--small .el-form-item__label{height:24px;line-height:24px}.el-form-item--small .el-form-item__content{line-height:24px}.el-form-item--small .el-form-item__error{padding-top:2px}.el-form-item--label-left .el-form-item__label{justify-content:flex-start}.el-form-item--label-top{display:block}.el-form-item--label-top .el-form-item__label{display:inline-block;height:auto;line-height:22px;margin-bottom:8px;text-align:left;vertical-align:middle}.el-form-item__label-wrap{display:flex}.el-form-item__label{align-items:flex-start;box-sizing:border-box;color:var(--el-text-color-regular);display:inline-flex;flex:0 0 auto;font-size:var(--el-form-label-font-size);height:32px;justify-content:flex-end;line-height:32px;padding:0 12px 0 0}.el-form-item__content{align-items:center;display:flex;flex:1;flex-wrap:wrap;font-size:var(--font-size);line-height:32px;min-width:0;position:relative}.el-form-item__content .el-input-group{vertical-align:top}.el-form-item__error{color:var(--el-color-danger);font-size:12px;left:0;line-height:1;padding-top:2px;position:absolute;top:100%}.el-form-item__error--inline{display:inline-block;left:auto;margin-left:10px;position:relative;top:auto}.el-form-item.is-required:not(.is-no-asterisk).asterisk-left>.el-form-item__label-wrap>.el-form-item__label:before,.el-form-item.is-required:not(.is-no-asterisk).asterisk-left>.el-form-item__label:before{color:var(--el-color-danger);content:\"*\";margin-right:4px}.el-form-item.is-required:not(.is-no-asterisk).asterisk-right>.el-form-item__label-wrap>.el-form-item__label:after,.el-form-item.is-required:not(.is-no-asterisk).asterisk-right>.el-form-item__label:after{color:var(--el-color-danger);content:\"*\";margin-left:4px}.el-form-item.is-error .el-input__wrapper,.el-form-item.is-error .el-input__wrapper.is-focus,.el-form-item.is-error .el-input__wrapper:focus,.el-form-item.is-error .el-input__wrapper:hover,.el-form-item.is-error .el-select__wrapper,.el-form-item.is-error .el-select__wrapper.is-focus,.el-form-item.is-error .el-select__wrapper:focus,.el-form-item.is-error .el-select__wrapper:hover,.el-form-item.is-error .el-textarea__inner,.el-form-item.is-error .el-textarea__inner.is-focus,.el-form-item.is-error .el-textarea__inner:focus,.el-form-item.is-error .el-textarea__inner:hover{box-shadow:0 0 0 1px var(--el-color-danger) inset}.el-form-item.is-error .el-input-group__append .el-input__wrapper,.el-form-item.is-error .el-input-group__prepend .el-input__wrapper{box-shadow:inset 0 0 0 1px transparent}.el-form-item.is-error .el-input-group__append .el-input__validateIcon,.el-form-item.is-error .el-input-group__prepend .el-input__validateIcon{display:none}.el-form-item.is-error .el-input__validateIcon{color:var(--el-color-danger)}.el-form-item--feedback .el-input__validateIcon{display:inline-flex}.el-tag{--el-tag-font-size:12px;--el-tag-border-radius:4px;--el-tag-border-radius-rounded:9999px;align-items:center;background-color:var(--el-tag-bg-color);border-color:var(--el-tag-border-color);border-radius:var(--el-tag-border-radius);border-style:solid;border-width:1px;box-sizing:border-box;color:var(--el-tag-text-color);display:inline-flex;font-size:var(--el-tag-font-size);height:24px;justify-content:center;line-height:1;padding:0 9px;vertical-align:middle;white-space:nowrap;--el-icon-size:14px}.el-tag,.el-tag.el-tag--primary{--el-tag-bg-color:var(--el-color-primary-light-9);--el-tag-border-color:var(--el-color-primary-light-8);--el-tag-hover-color:var(--el-color-primary)}.el-tag.el-tag--success{--el-tag-bg-color:var(--el-color-success-light-9);--el-tag-border-color:var(--el-color-success-light-8);--el-tag-hover-color:var(--el-color-success)}.el-tag.el-tag--warning{--el-tag-bg-color:var(--el-color-warning-light-9);--el-tag-border-color:var(--el-color-warning-light-8);--el-tag-hover-color:var(--el-color-warning)}.el-tag.el-tag--danger{--el-tag-bg-color:var(--el-color-danger-light-9);--el-tag-border-color:var(--el-color-danger-light-8);--el-tag-hover-color:var(--el-color-danger)}.el-tag.el-tag--error{--el-tag-bg-color:var(--el-color-error-light-9);--el-tag-border-color:var(--el-color-error-light-8);--el-tag-hover-color:var(--el-color-error)}.el-tag.el-tag--info{--el-tag-bg-color:var(--el-color-info-light-9);--el-tag-border-color:var(--el-color-info-light-8);--el-tag-hover-color:var(--el-color-info)}.el-tag.is-hit{border-color:var(--el-color-primary)}.el-tag.is-round{border-radius:var(--el-tag-border-radius-rounded)}.el-tag .el-tag__close{color:var(--el-tag-text-color);flex-shrink:0}.el-tag .el-tag__close:hover{background-color:var(--el-tag-hover-color);color:var(--el-color-white)}.el-tag.el-tag--primary{--el-tag-text-color:var(--el-color-primary)}.el-tag.el-tag--success{--el-tag-text-color:var(--el-color-success)}.el-tag.el-tag--warning{--el-tag-text-color:var(--el-color-warning)}.el-tag.el-tag--danger{--el-tag-text-color:var(--el-color-danger)}.el-tag.el-tag--error{--el-tag-text-color:var(--el-color-error)}.el-tag.el-tag--info{--el-tag-text-color:var(--el-color-info)}.el-tag .el-icon{border-radius:50%;cursor:pointer;font-size:calc(var(--el-icon-size) - 2px);height:var(--el-icon-size);width:var(--el-icon-size)}.el-tag .el-tag__close{margin-left:6px}.el-tag--dark{--el-tag-text-color:var(--el-color-white)}.el-tag--dark,.el-tag--dark.el-tag--primary{--el-tag-bg-color:var(--el-color-primary);--el-tag-border-color:var(--el-color-primary);--el-tag-hover-color:var(--el-color-primary-light-3)}.el-tag--dark.el-tag--success{--el-tag-bg-color:var(--el-color-success);--el-tag-border-color:var(--el-color-success);--el-tag-hover-color:var(--el-color-success-light-3)}.el-tag--dark.el-tag--warning{--el-tag-bg-color:var(--el-color-warning);--el-tag-border-color:var(--el-color-warning);--el-tag-hover-color:var(--el-color-warning-light-3)}.el-tag--dark.el-tag--danger{--el-tag-bg-color:var(--el-color-danger);--el-tag-border-color:var(--el-color-danger);--el-tag-hover-color:var(--el-color-danger-light-3)}.el-tag--dark.el-tag--error{--el-tag-bg-color:var(--el-color-error);--el-tag-border-color:var(--el-color-error);--el-tag-hover-color:var(--el-color-error-light-3)}.el-tag--dark.el-tag--info{--el-tag-bg-color:var(--el-color-info);--el-tag-border-color:var(--el-color-info);--el-tag-hover-color:var(--el-color-info-light-3)}.el-tag--dark.el-tag--danger,.el-tag--dark.el-tag--error,.el-tag--dark.el-tag--info,.el-tag--dark.el-tag--primary,.el-tag--dark.el-tag--success,.el-tag--dark.el-tag--warning{--el-tag-text-color:var(--el-color-white)}.el-tag--plain,.el-tag--plain.el-tag--primary{--el-tag-bg-color:var(--el-fill-color-blank);--el-tag-border-color:var(--el-color-primary-light-5);--el-tag-hover-color:var(--el-color-primary)}.el-tag--plain.el-tag--success{--el-tag-bg-color:var(--el-fill-color-blank);--el-tag-border-color:var(--el-color-success-light-5);--el-tag-hover-color:var(--el-color-success)}.el-tag--plain.el-tag--warning{--el-tag-bg-color:var(--el-fill-color-blank);--el-tag-border-color:var(--el-color-warning-light-5);--el-tag-hover-color:var(--el-color-warning)}.el-tag--plain.el-tag--danger{--el-tag-bg-color:var(--el-fill-color-blank);--el-tag-border-color:var(--el-color-danger-light-5);--el-tag-hover-color:var(--el-color-danger)}.el-tag--plain.el-tag--error{--el-tag-bg-color:var(--el-fill-color-blank);--el-tag-border-color:var(--el-color-error-light-5);--el-tag-hover-color:var(--el-color-error)}.el-tag--plain.el-tag--info{--el-tag-bg-color:var(--el-fill-color-blank);--el-tag-border-color:var(--el-color-info-light-5);--el-tag-hover-color:var(--el-color-info)}.el-tag.is-closable{padding-right:5px}.el-tag--large{height:32px;padding:0 11px;--el-icon-size:16px}.el-tag--large .el-tag__close{margin-left:8px}.el-tag--large.is-closable{padding-right:7px}.el-tag--small{height:20px;padding:0 7px;--el-icon-size:12px}.el-tag--small .el-tag__close{margin-left:4px}.el-tag--small.is-closable{padding-right:3px}.el-tag--small .el-icon-close{transform:scale(.8)}.el-tag.el-tag--primary.is-hit{border-color:var(--el-color-primary)}.el-tag.el-tag--success.is-hit{border-color:var(--el-color-success)}.el-tag.el-tag--warning.is-hit{border-color:var(--el-color-warning)}.el-tag.el-tag--danger.is-hit{border-color:var(--el-color-danger)}.el-tag.el-tag--error.is-hit{border-color:var(--el-color-error)}.el-tag.el-tag--info.is-hit{border-color:var(--el-color-info)}.el-select-dropdown.is-multiple .el-select-dropdown__item.is-selected:after{background-color:var(--el-color-primary);background-position:50%;background-repeat:no-repeat;border-right:none;border-top:none;content:\"\";height:12px;mask:url(\"data:image/svg+xml;utf8,%3Csvg class='icon' width='200' height='200' viewBox='0 0 1024 1024' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill='currentColor' d='M406.656 706.944L195.84 496.256a32 32 0 10-45.248 45.248l256 256 512-512a32 32 0 00-45.248-45.248L406.592 706.944z'%3E%3C/path%3E%3C/svg%3E\") no-repeat;mask-size:100% 100%;-webkit-mask:url(\"data:image/svg+xml;utf8,%3Csvg class='icon' width='200' height='200' viewBox='0 0 1024 1024' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill='currentColor' d='M406.656 706.944L195.84 496.256a32 32 0 10-45.248 45.248l256 256 512-512a32 32 0 00-45.248-45.248L406.592 706.944z'%3E%3C/path%3E%3C/svg%3E\") no-repeat;-webkit-mask-size:100% 100%;position:absolute;right:20px;top:50%;transform:translateY(-50%);width:12px}.el-scrollbar{--el-scrollbar-opacity:.3;--el-scrollbar-bg-color:var(--el-text-color-secondary);--el-scrollbar-hover-opacity:.5;--el-scrollbar-hover-bg-color:var(--el-text-color-secondary);height:100%;overflow:hidden;position:relative}.el-scrollbar__wrap{height:100%;overflow:auto}.el-scrollbar__wrap--hidden-default{scrollbar-width:none}.el-scrollbar__wrap--hidden-default::-webkit-scrollbar{display:none}.el-scrollbar__thumb{background-color:var(--el-scrollbar-bg-color,var(--el-text-color-secondary));border-radius:inherit;cursor:pointer;display:block;height:0;opacity:var(--el-scrollbar-opacity,.3);position:relative;transition:var(--el-transition-duration) background-color;width:0}.el-scrollbar__thumb:hover{background-color:var(--el-scrollbar-hover-bg-color,var(--el-text-color-secondary));opacity:var(--el-scrollbar-hover-opacity,.5)}.el-scrollbar__bar{border-radius:4px;bottom:2px;position:absolute;right:2px;z-index:1}.el-scrollbar__bar.is-vertical{top:2px;width:6px}.el-scrollbar__bar.is-vertical>div{width:100%}.el-scrollbar__bar.is-horizontal{height:6px;left:2px}.el-scrollbar__bar.is-horizontal>div{height:100%}.el-scrollbar-fade-enter-active{transition:opacity .34s ease-out}.el-scrollbar-fade-leave-active{transition:opacity .12s ease-out}.el-scrollbar-fade-enter-from,.el-scrollbar-fade-leave-active{opacity:0}.el-popper{--el-popper-border-radius:var(--el-popover-border-radius,4px);border-radius:var(--el-popper-border-radius);font-size:12px;line-height:20px;min-width:10px;overflow-wrap:break-word;padding:5px 11px;position:absolute;visibility:visible;z-index:2000}.el-popper.is-dark{color:var(--el-bg-color)}.el-popper.is-dark,.el-popper.is-dark>.el-popper__arrow:before{background:var(--el-text-color-primary);border:1px solid var(--el-text-color-primary)}.el-popper.is-dark>.el-popper__arrow:before{right:0}.el-popper.is-light,.el-popper.is-light>.el-popper__arrow:before{background:var(--el-bg-color-overlay);border:1px solid var(--el-border-color-light)}.el-popper.is-light>.el-popper__arrow:before{right:0}.el-popper.is-pure{padding:0}.el-popper__arrow,.el-popper__arrow:before{height:10px;position:absolute;width:10px;z-index:-1}.el-popper__arrow:before{background:var(--el-text-color-primary);box-sizing:border-box;content:\" \";transform:rotate(45deg)}.el-popper[data-popper-placement^=top]>.el-popper__arrow{bottom:-5px}.el-popper[data-popper-placement^=top]>.el-popper__arrow:before{border-bottom-right-radius:2px}.el-popper[data-popper-placement^=bottom]>.el-popper__arrow{top:-5px}.el-popper[data-popper-placement^=bottom]>.el-popper__arrow:before{border-top-left-radius:2px}.el-popper[data-popper-placement^=left]>.el-popper__arrow{right:-5px}.el-popper[data-popper-placement^=left]>.el-popper__arrow:before{border-top-right-radius:2px}.el-popper[data-popper-placement^=right]>.el-popper__arrow{left:-5px}.el-popper[data-popper-placement^=right]>.el-popper__arrow:before{border-bottom-left-radius:2px}.el-popper[data-popper-placement^=top] .el-popper__arrow:before{border-left-color:transparent!important;border-top-color:transparent!important}.el-popper[data-popper-placement^=bottom] .el-popper__arrow:before{border-bottom-color:transparent!important;border-right-color:transparent!important}.el-popper[data-popper-placement^=left] .el-popper__arrow:before{border-bottom-color:transparent!important;border-left-color:transparent!important}.el-popper[data-popper-placement^=right] .el-popper__arrow:before{border-right-color:transparent!important;border-top-color:transparent!important}.el-select-dropdown{border-radius:var(--el-border-radius-base);box-sizing:border-box;z-index:calc(var(--el-index-top) + 1)}.el-select-dropdown .el-scrollbar.is-empty .el-select-dropdown__list{padding:0}.el-select-dropdown__empty,.el-select-dropdown__loading{color:var(--el-text-color-secondary);font-size:var(--el-select-font-size);margin:0;padding:10px 0;text-align:center}.el-select-dropdown__wrap{max-height:274px}.el-select-dropdown__list{box-sizing:border-box;list-style:none;margin:0;padding:6px 0}.el-select-dropdown__list.el-vl__window{margin:6px 0;padding:0}.el-select-dropdown__header{border-bottom:1px solid var(--el-border-color-light);padding:10px}.el-select-dropdown__footer{border-top:1px solid var(--el-border-color-light);padding:10px}.el-select-dropdown__item{box-sizing:border-box;color:var(--el-text-color-regular);cursor:pointer;font-size:var(--el-font-size-base);height:34px;line-height:34px;overflow:hidden;padding:0 32px 0 20px;position:relative;text-overflow:ellipsis;white-space:nowrap}.el-select-dropdown__item.is-hovering{background-color:var(--el-fill-color-light)}.el-select-dropdown__item.is-selected{color:var(--el-color-primary);font-weight:700}.el-select-dropdown__item.is-disabled{background-color:unset;color:var(--el-text-color-placeholder);cursor:not-allowed}.el-select-dropdown.is-multiple .el-select-dropdown__item.is-selected:after{background-color:var(--el-color-primary);background-position:50%;background-repeat:no-repeat;border-right:none;border-top:none;content:\"\";height:12px;mask:url(\"data:image/svg+xml;utf8,%3Csvg class='icon' width='200' height='200' viewBox='0 0 1024 1024' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill='currentColor' d='M406.656 706.944L195.84 496.256a32 32 0 10-45.248 45.248l256 256 512-512a32 32 0 00-45.248-45.248L406.592 706.944z'%3E%3C/path%3E%3C/svg%3E\") no-repeat;mask-size:100% 100%;-webkit-mask:url(\"data:image/svg+xml;utf8,%3Csvg class='icon' width='200' height='200' viewBox='0 0 1024 1024' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill='currentColor' d='M406.656 706.944L195.84 496.256a32 32 0 10-45.248 45.248l256 256 512-512a32 32 0 00-45.248-45.248L406.592 706.944z'%3E%3C/path%3E%3C/svg%3E\") no-repeat;-webkit-mask-size:100% 100%;position:absolute;right:20px;top:50%;transform:translateY(-50%);width:12px}.el-select-dropdown.is-multiple .el-select-dropdown__item.is-disabled:after{background-color:var(--el-text-color-placeholder)}.el-select-group{margin:0;padding:0}.el-select-group__wrap{list-style:none;margin:0;padding:0;position:relative}.el-select-group__title{color:var(--el-color-info);font-size:12px;line-height:34px;padding-left:20px}.el-select-group .el-select-dropdown__item{padding-left:20px}.el-select{--el-select-border-color-hover:var(--el-border-color-hover);--el-select-disabled-color:var(--el-disabled-text-color);--el-select-disabled-border:var(--el-disabled-border-color);--el-select-font-size:var(--el-font-size-base);--el-select-close-hover-color:var(--el-text-color-secondary);--el-select-input-color:var(--el-text-color-placeholder);--el-select-multiple-input-color:var(--el-text-color-regular);--el-select-input-focus-border-color:var(--el-color-primary);--el-select-input-font-size:14px;--el-select-width:100%;display:inline-block;position:relative;vertical-align:middle;width:var(--el-select-width)}.el-select__wrapper{align-items:center;background-color:var(--el-fill-color-blank);border-radius:var(--el-border-radius-base);box-shadow:0 0 0 1px var(--el-border-color) inset;box-sizing:border-box;cursor:pointer;display:flex;font-size:14px;gap:6px;line-height:24px;min-height:32px;padding:4px 12px;position:relative;text-align:left;transform:translateZ(0);transition:var(--el-transition-duration)}.el-select__wrapper.is-filterable{cursor:text}.el-select__wrapper.is-focused{box-shadow:0 0 0 1px var(--el-color-primary) inset}.el-select__wrapper.is-hovering:not(.is-focused){box-shadow:0 0 0 1px var(--el-border-color-hover) inset}.el-select__wrapper.is-disabled{background-color:var(--el-fill-color-light);color:var(--el-text-color-placeholder);cursor:not-allowed}.el-select__wrapper.is-disabled,.el-select__wrapper.is-disabled:hover{box-shadow:0 0 0 1px var(--el-select-disabled-border) inset}.el-select__wrapper.is-disabled.is-focus{box-shadow:0 0 0 1px var(--el-input-focus-border-color) inset}.el-select__wrapper.is-disabled .el-select__selected-item{color:var(--el-select-disabled-color)}.el-select__wrapper.is-disabled .el-select__caret,.el-select__wrapper.is-disabled .el-tag{cursor:not-allowed}.el-select__prefix,.el-select__suffix{align-items:center;color:var(--el-input-icon-color,var(--el-text-color-placeholder));display:flex;flex-shrink:0;gap:6px}.el-select__caret{color:var(--el-select-input-color);cursor:pointer;font-size:var(--el-select-input-font-size);transform:rotate(0);transition:var(--el-transition-duration)}.el-select__caret.is-reverse{transform:rotate(180deg)}.el-select__selection{align-items:center;display:flex;flex:1;flex-wrap:wrap;gap:6px;min-width:0;position:relative}.el-select__selection.is-near{margin-left:-8px}.el-select__selection .el-tag{border-color:transparent;cursor:pointer}.el-select__selection .el-tag.el-tag--plain{border-color:var(--el-tag-border-color)}.el-select__selection .el-tag .el-tag__content{min-width:0}.el-select__selected-item{display:flex;flex-wrap:wrap;-webkit-user-select:none;-moz-user-select:none;user-select:none}.el-select__tags-text{line-height:normal}.el-select__placeholder,.el-select__tags-text{display:block;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.el-select__placeholder{color:var(--el-input-text-color,var(--el-text-color-regular));position:absolute;top:50%;transform:translateY(-50%);width:100%}.el-select__placeholder.is-transparent{color:var(--el-text-color-placeholder);-webkit-user-select:none;-moz-user-select:none;user-select:none}.el-select__popper.el-popper{background:var(--el-bg-color-overlay);box-shadow:var(--el-box-shadow-light)}.el-select__popper.el-popper,.el-select__popper.el-popper .el-popper__arrow:before{border:1px solid var(--el-border-color-light)}.el-select__popper.el-popper[data-popper-placement^=top] .el-popper__arrow:before{border-left-color:transparent;border-top-color:transparent}.el-select__popper.el-popper[data-popper-placement^=bottom] .el-popper__arrow:before{border-bottom-color:transparent;border-right-color:transparent}.el-select__popper.el-popper[data-popper-placement^=left] .el-popper__arrow:before{border-bottom-color:transparent;border-left-color:transparent}.el-select__popper.el-popper[data-popper-placement^=right] .el-popper__arrow:before{border-right-color:transparent;border-top-color:transparent}.el-select__input-wrapper{max-width:100%}.el-select__input-wrapper.is-hidden{opacity:0;position:absolute}.el-select__input{-webkit-appearance:none;-moz-appearance:none;appearance:none;background-color:transparent;border:none;color:var(--el-select-multiple-input-color);font-family:inherit;font-size:inherit;height:24px;max-width:100%;outline:none;padding:0}.el-select__input.is-disabled{cursor:not-allowed}.el-select__input-calculator{left:0;max-width:100%;overflow:hidden;position:absolute;top:0;visibility:hidden;white-space:pre}.el-select--large .el-select__wrapper{font-size:14px;gap:6px;line-height:24px;min-height:40px;padding:8px 16px}.el-select--large .el-select__selection{gap:6px}.el-select--large .el-select__selection.is-near{margin-left:-8px}.el-select--large .el-select__prefix,.el-select--large .el-select__suffix{gap:6px}.el-select--large .el-select__input{height:24px}.el-select--small .el-select__wrapper{font-size:12px;gap:4px;line-height:20px;min-height:24px;padding:2px 8px}.el-select--small .el-select__selection{gap:4px}.el-select--small .el-select__selection.is-near{margin-left:-6px}.el-select--small .el-select__prefix,.el-select--small .el-select__suffix{gap:4px}.el-select--small .el-select__input{height:20px}.el-input-number{display:inline-flex;line-height:30px;position:relative;vertical-align:middle;width:150px}.el-input-number .el-input__wrapper{padding-left:42px;padding-right:42px}.el-input-number .el-input__inner{-webkit-appearance:none;-moz-appearance:textfield;line-height:1;text-align:center}.el-input-number .el-input__inner::-webkit-inner-spin-button,.el-input-number .el-input__inner::-webkit-outer-spin-button{-webkit-appearance:none;margin:0}.el-input-number__decrease,.el-input-number__increase{align-items:center;background:var(--el-fill-color-light);bottom:1px;color:var(--el-text-color-regular);cursor:pointer;display:flex;font-size:13px;height:auto;justify-content:center;position:absolute;top:1px;-webkit-user-select:none;-moz-user-select:none;user-select:none;width:32px;z-index:1}.el-input-number__decrease:hover,.el-input-number__increase:hover{color:var(--el-color-primary)}.el-input-number__decrease:hover~.el-input:not(.is-disabled) .el-input__wrapper,.el-input-number__increase:hover~.el-input:not(.is-disabled) .el-input__wrapper{box-shadow:0 0 0 1px var(--el-input-focus-border-color,var(--el-color-primary)) inset}.el-input-number__decrease.is-disabled,.el-input-number__increase.is-disabled{color:var(--el-disabled-text-color);cursor:not-allowed}.el-input-number__increase{border-left:var(--el-border);border-radius:0 var(--el-border-radius-base) var(--el-border-radius-base) 0;right:1px}.el-input-number__decrease{border-radius:var(--el-border-radius-base) 0 0 var(--el-border-radius-base);border-right:var(--el-border);left:1px}.el-input-number.is-disabled .el-input-number__decrease,.el-input-number.is-disabled .el-input-number__increase{border-color:var(--el-disabled-border-color);color:var(--el-disabled-border-color)}.el-input-number.is-disabled .el-input-number__decrease:hover,.el-input-number.is-disabled .el-input-number__increase:hover{color:var(--el-disabled-border-color);cursor:not-allowed}.el-input-number--large{line-height:38px;width:180px}.el-input-number--large .el-input-number__decrease,.el-input-number--large .el-input-number__increase{font-size:14px;width:40px}.el-input-number--large .el-input--large .el-input__wrapper{padding-left:47px;padding-right:47px}.el-input-number--small{line-height:22px;width:120px}.el-input-number--small .el-input-number__decrease,.el-input-number--small .el-input-number__increase{font-size:12px;width:24px}.el-input-number--small .el-input--small .el-input__wrapper{padding-left:31px;padding-right:31px}.el-input-number--small .el-input-number__decrease [class*=el-icon],.el-input-number--small .el-input-number__increase [class*=el-icon]{transform:scale(.9)}.el-input-number.is-without-controls .el-input__wrapper{padding-left:15px;padding-right:15px}.el-input-number.is-controls-right .el-input__wrapper{padding-left:15px;padding-right:42px}.el-input-number.is-controls-right .el-input-number__decrease,.el-input-number.is-controls-right .el-input-number__increase{--el-input-number-controls-height:15px;height:var(--el-input-number-controls-height);line-height:var(--el-input-number-controls-height)}.el-input-number.is-controls-right .el-input-number__decrease [class*=el-icon],.el-input-number.is-controls-right .el-input-number__increase [class*=el-icon]{transform:scale(.8)}.el-input-number.is-controls-right .el-input-number__increase{border-bottom:var(--el-border);border-radius:0 var(--el-border-radius-base) 0 0;bottom:auto;left:auto}.el-input-number.is-controls-right .el-input-number__decrease{border-left:var(--el-border);border-radius:0 0 var(--el-border-radius-base) 0;border-right:none;left:auto;right:1px;top:auto}.el-input-number.is-controls-right[class*=large] [class*=decrease],.el-input-number.is-controls-right[class*=large] [class*=increase]{--el-input-number-controls-height:19px}.el-input-number.is-controls-right[class*=small] [class*=decrease],.el-input-number.is-controls-right[class*=small] [class*=increase]{--el-input-number-controls-height:11px}.el-switch{--el-switch-on-color:var(--el-color-primary);--el-switch-off-color:var(--el-border-color);align-items:center;display:inline-flex;font-size:14px;height:32px;line-height:20px;position:relative;vertical-align:middle}.el-switch.is-disabled .el-switch__core,.el-switch.is-disabled .el-switch__label{cursor:not-allowed}.el-switch__label{color:var(--el-text-color-primary);cursor:pointer;display:inline-block;font-size:14px;font-weight:500;height:20px;transition:var(--el-transition-duration-fast);vertical-align:middle}.el-switch__label.is-active{color:var(--el-color-primary)}.el-switch__label--left{margin-right:10px}.el-switch__label--right{margin-left:10px}.el-switch__label *{display:inline-block;font-size:14px;line-height:1}.el-switch__label .el-icon{height:inherit}.el-switch__label .el-icon svg{vertical-align:middle}.el-switch__input{height:0;margin:0;opacity:0;position:absolute;width:0}.el-switch__input:focus-visible~.el-switch__core{outline:2px solid var(--el-switch-on-color);outline-offset:1px}.el-switch__core{align-items:center;background:var(--el-switch-off-color);border:1px solid var(--el-switch-border-color,var(--el-switch-off-color));border-radius:10px;box-sizing:border-box;cursor:pointer;display:inline-flex;height:20px;min-width:40px;outline:none;position:relative;transition:border-color var(--el-transition-duration),background-color var(--el-transition-duration)}.el-switch__core .el-switch__inner{align-items:center;display:flex;height:16px;justify-content:center;overflow:hidden;padding:0 4px 0 18px;transition:all var(--el-transition-duration);width:100%}.el-switch__core .el-switch__inner .is-icon,.el-switch__core .el-switch__inner .is-text{color:var(--el-color-white);font-size:12px;overflow:hidden;text-overflow:ellipsis;-webkit-user-select:none;-moz-user-select:none;user-select:none;white-space:nowrap}.el-switch__core .el-switch__action{align-items:center;background-color:var(--el-color-white);border-radius:var(--el-border-radius-circle);color:var(--el-switch-off-color);display:flex;height:16px;justify-content:center;left:1px;position:absolute;transition:all var(--el-transition-duration);width:16px}.el-switch.is-checked .el-switch__core{background-color:var(--el-switch-on-color);border-color:var(--el-switch-border-color,var(--el-switch-on-color))}.el-switch.is-checked .el-switch__core .el-switch__action{color:var(--el-switch-on-color);left:calc(100% - 17px)}.el-switch.is-checked .el-switch__core .el-switch__inner{padding:0 18px 0 4px}.el-switch.is-disabled{opacity:.6}.el-switch--wide .el-switch__label.el-switch__label--left span{left:10px}.el-switch--wide .el-switch__label.el-switch__label--right span{right:10px}.el-switch .label-fade-enter-from,.el-switch .label-fade-leave-active{opacity:0}.el-switch--large{font-size:14px;height:40px;line-height:24px}.el-switch--large .el-switch__label{font-size:14px;height:24px}.el-switch--large .el-switch__label *{font-size:14px}.el-switch--large .el-switch__core{border-radius:12px;height:24px;min-width:50px}.el-switch--large .el-switch__core .el-switch__inner{height:20px;padding:0 6px 0 22px}.el-switch--large .el-switch__core .el-switch__action{height:20px;width:20px}.el-switch--large.is-checked .el-switch__core .el-switch__action{left:calc(100% - 21px)}.el-switch--large.is-checked .el-switch__core .el-switch__inner{padding:0 22px 0 6px}.el-switch--small{font-size:12px;height:24px;line-height:16px}.el-switch--small .el-switch__label{font-size:12px;height:16px}.el-switch--small .el-switch__label *{font-size:12px}.el-switch--small .el-switch__core{border-radius:8px;height:16px;min-width:30px}.el-switch--small .el-switch__core .el-switch__inner{height:12px;padding:0 2px 0 14px}.el-switch--small .el-switch__core .el-switch__action{height:12px;width:12px}.el-switch--small.is-checked .el-switch__core .el-switch__action{left:calc(100% - 13px)}.el-switch--small.is-checked .el-switch__core .el-switch__inner{padding:0 14px 0 2px}html.dark{color-scheme:dark;--el-color-primary:#409eff;--el-color-primary-light-3:rgb(50.8,116.6,184.5);--el-color-primary-light-5:rgb(42,89,137.5);--el-color-primary-light-7:rgb(33.2,61.4,90.5);--el-color-primary-light-8:rgb(28.8,47.6,67);--el-color-primary-light-9:rgb(24.4,33.8,43.5);--el-color-primary-dark-2:rgb(102.2,177.4,255);--el-color-success:#67c23a;--el-color-success-light-3:rgb(78.1,141.8,46.6);--el-color-success-light-5:rgb(61.5,107,39);--el-color-success-light-7:rgb(44.9,72.2,31.4);--el-color-success-light-8:rgb(36.6,54.8,27.6);--el-color-success-light-9:rgb(28.3,37.4,23.8);--el-color-success-dark-2:rgb(133.4,206.2,97.4);--el-color-warning:#e6a23c;--el-color-warning-light-3:rgb(167,119.4,48);--el-color-warning-light-5:#7d5b28;--el-color-warning-light-7:rgb(83,62.6,32);--el-color-warning-light-8:rgb(62,48.4,28);--el-color-warning-light-9:rgb(41,34.2,24);--el-color-warning-dark-2:rgb(235,180.6,99);--el-color-danger:#f56c6c;--el-color-danger-light-3:rgb(177.5,81.6,81.6);--el-color-danger-light-5:rgb(132.5,64,64);--el-color-danger-light-7:rgb(87.5,46.4,46.4);--el-color-danger-light-8:rgb(65,37.6,37.6);--el-color-danger-light-9:rgb(42.5,28.8,28.8);--el-color-danger-dark-2:rgb(247,137.4,137.4);--el-color-error:#f56c6c;--el-color-error-light-3:rgb(177.5,81.6,81.6);--el-color-error-light-5:rgb(132.5,64,64);--el-color-error-light-7:rgb(87.5,46.4,46.4);--el-color-error-light-8:rgb(65,37.6,37.6);--el-color-error-light-9:rgb(42.5,28.8,28.8);--el-color-error-dark-2:rgb(247,137.4,137.4);--el-color-info:#909399;--el-color-info-light-3:rgb(106.8,108.9,113.1);--el-color-info-light-5:rgb(82,83.5,86.5);--el-color-info-light-7:rgb(57.2,58.1,59.9);--el-color-info-light-8:rgb(44.8,45.4,46.6);--el-color-info-light-9:rgb(32.4,32.7,33.3);--el-color-info-dark-2:rgb(166.2,168.6,173.4);--el-box-shadow:0px 12px 32px 4px rgba(0,0,0,.36),0px 8px 20px rgba(0,0,0,.72);--el-box-shadow-light:0px 0px 12px rgba(0,0,0,.72);--el-box-shadow-lighter:0px 0px 6px rgba(0,0,0,.72);--el-box-shadow-dark:0px 16px 48px 16px rgba(0,0,0,.72),0px 12px 32px #000000,0px 8px 16px -8px #000000;--el-bg-color-page:#0a0a0a;--el-bg-color:#141414;--el-bg-color-overlay:#1d1e1f;--el-text-color-primary:#E5EAF3;--el-text-color-regular:#CFD3DC;--el-text-color-secondary:#A3A6AD;--el-text-color-placeholder:#8D9095;--el-text-color-disabled:#6C6E72;--el-border-color-darker:#636466;--el-border-color-dark:#58585B;--el-border-color:#4C4D4F;--el-border-color-light:#414243;--el-border-color-lighter:#363637;--el-border-color-extra-light:#2B2B2C;--el-fill-color-darker:#424243;--el-fill-color-dark:#39393A;--el-fill-color:#303030;--el-fill-color-light:#262727;--el-fill-color-lighter:#1D1D1D;--el-fill-color-extra-light:#191919;--el-fill-color-blank:transparent;--el-mask-color:rgba(0,0,0,.8);--el-mask-color-extra-light:rgba(0,0,0,.3)}html.dark .el-button{--el-button-disabled-text-color:rgba(255,255,255,.5)}html.dark .el-card{--el-card-bg-color:var(--el-bg-color-overlay)}html.dark .el-empty{--el-empty-fill-color-0:var(--el-color-black);--el-empty-fill-color-1:#4b4b52;--el-empty-fill-color-2:#36383d;--el-empty-fill-color-3:#1e1e20;--el-empty-fill-color-4:#262629;--el-empty-fill-color-5:#202124;--el-empty-fill-color-6:#212224;--el-empty-fill-color-7:#1b1c1f;--el-empty-fill-color-8:#1c1d1f;--el-empty-fill-color-9:#18181a}:root{--el-loading-spinner-size:42px;--el-loading-fullscreen-spinner-size:50px}.el-loading-parent--relative{position:relative!important}.el-loading-parent--hidden{overflow:hidden!important}.el-loading-mask{background-color:var(--el-mask-color);bottom:0;left:0;margin:0;position:absolute;right:0;top:0;transition:opacity var(--el-transition-duration);z-index:2000}.el-loading-mask.is-fullscreen{position:fixed}.el-loading-mask.is-fullscreen .el-loading-spinner{margin-top:calc((0px - var(--el-loading-fullscreen-spinner-size))/2)}.el-loading-mask.is-fullscreen .el-loading-spinner .circular{height:var(--el-loading-fullscreen-spinner-size);width:var(--el-loading-fullscreen-spinner-size)}.el-loading-spinner{margin-top:calc((0px - var(--el-loading-spinner-size))/2);position:absolute;text-align:center;top:50%;width:100%}.el-loading-spinner .el-loading-text{color:var(--el-color-primary);font-size:14px;margin:3px 0}.el-loading-spinner .circular{animation:loading-rotate 2s linear infinite;display:inline;height:var(--el-loading-spinner-size);width:var(--el-loading-spinner-size)}.el-loading-spinner .path{animation:loading-dash 1.5s ease-in-out infinite;stroke-dasharray:90,150;stroke-dashoffset:0;stroke-width:2;stroke:var(--el-color-primary);stroke-linecap:round}.el-loading-spinner i{color:var(--el-color-primary)}.el-loading-fade-enter-from,.el-loading-fade-leave-to{opacity:0}@keyframes loading-rotate{to{transform:rotate(1turn)}}@keyframes loading-dash{0%{stroke-dasharray:1,200;stroke-dashoffset:0}50%{stroke-dasharray:90,150;stroke-dashoffset:-40px}to{stroke-dasharray:90,150;stroke-dashoffset:-120px}}:root{--el-popup-modal-bg-color:var(--el-color-black);--el-popup-modal-opacity:.5}.v-modal-enter{animation:v-modal-in var(--el-transition-duration-fast) ease}.v-modal-leave{animation:v-modal-out var(--el-transition-duration-fast) ease forwards}@keyframes v-modal-in{0%{opacity:0}}@keyframes v-modal-out{to{opacity:0}}.v-modal{background:var(--el-popup-modal-bg-color);height:100%;left:0;opacity:var(--el-popup-modal-opacity);position:fixed;top:0;width:100%}.el-popup-parent--hidden{overflow:hidden}.el-message-box{--el-messagebox-title-color:var(--el-text-color-primary);--el-messagebox-width:420px;--el-messagebox-border-radius:4px;--el-messagebox-box-shadow:var(--el-box-shadow);--el-messagebox-font-size:var(--el-font-size-large);--el-messagebox-content-font-size:var(--el-font-size-base);--el-messagebox-content-color:var(--el-text-color-regular);--el-messagebox-error-font-size:12px;--el-messagebox-padding-primary:12px;--el-messagebox-font-line-height:var(--el-font-line-height-primary);backface-visibility:hidden;background-color:var(--el-bg-color);border-radius:var(--el-messagebox-border-radius);box-shadow:var(--el-messagebox-box-shadow);box-sizing:border-box;display:inline-block;font-size:var(--el-messagebox-font-size);max-width:var(--el-messagebox-width);overflow:hidden;overflow-wrap:break-word;padding:var(--el-messagebox-padding-primary);position:relative;text-align:left;vertical-align:middle;width:100%}.el-message-box:focus{outline:none!important}.el-overlay.is-message-box .el-overlay-message-box{bottom:0;left:0;overflow:auto;padding:16px;position:fixed;right:0;text-align:center;top:0}.el-overlay.is-message-box .el-overlay-message-box:after{content:\"\";display:inline-block;height:100%;vertical-align:middle;width:0}.el-message-box.is-draggable .el-message-box__header{cursor:move;-webkit-user-select:none;-moz-user-select:none;user-select:none}.el-message-box__header{padding-bottom:var(--el-messagebox-padding-primary)}.el-message-box__header.show-close{padding-right:calc(var(--el-messagebox-padding-primary) + var(--el-message-close-size, 16px))}.el-message-box__title{color:var(--el-messagebox-title-color);font-size:var(--el-messagebox-font-size);line-height:var(--el-messagebox-font-line-height)}.el-message-box__headerbtn{background:transparent;border:none;cursor:pointer;font-size:var(--el-message-close-size,16px);height:40px;outline:none;padding:0;position:absolute;right:0;top:0;width:40px}.el-message-box__headerbtn .el-message-box__close{color:var(--el-color-info);font-size:inherit}.el-message-box__headerbtn:focus .el-message-box__close,.el-message-box__headerbtn:hover .el-message-box__close{color:var(--el-color-primary)}.el-message-box__content{color:var(--el-messagebox-content-color);font-size:var(--el-messagebox-content-font-size)}.el-message-box__container{align-items:center;display:flex;gap:12px}.el-message-box__input{padding-top:12px}.el-message-box__input div.invalid>input,.el-message-box__input div.invalid>input:focus{border-color:var(--el-color-error)}.el-message-box__status{font-size:24px}.el-message-box__status.el-message-box-icon--success{--el-messagebox-color:var(--el-color-success);color:var(--el-messagebox-color)}.el-message-box__status.el-message-box-icon--info{--el-messagebox-color:var(--el-color-info);color:var(--el-messagebox-color)}.el-message-box__status.el-message-box-icon--warning{--el-messagebox-color:var(--el-color-warning);color:var(--el-messagebox-color)}.el-message-box__status.el-message-box-icon--error{--el-messagebox-color:var(--el-color-error);color:var(--el-messagebox-color)}.el-message-box__message{margin:0}.el-message-box__message p{line-height:var(--el-messagebox-font-line-height);margin:0}.el-message-box__errormsg{color:var(--el-color-error);font-size:var(--el-messagebox-error-font-size);line-height:var(--el-messagebox-font-line-height)}.el-message-box__btns{align-items:center;display:flex;flex-wrap:wrap;justify-content:flex-end;padding-top:var(--el-messagebox-padding-primary)}.el-message-box--center .el-message-box__title{align-items:center;display:flex;gap:6px;justify-content:center}.el-message-box--center .el-message-box__status{font-size:inherit}.el-message-box--center .el-message-box__btns,.el-message-box--center .el-message-box__container{justify-content:center}.fade-in-linear-enter-active .el-overlay-message-box{animation:msgbox-fade-in var(--el-transition-duration)}.fade-in-linear-leave-active .el-overlay-message-box{animation:msgbox-fade-in var(--el-transition-duration) reverse}@keyframes msgbox-fade-in{0%{opacity:0;transform:translate3d(0,-20px,0)}to{opacity:1;transform:translateZ(0)}}.el-popover{--el-popover-bg-color:var(--el-bg-color-overlay);--el-popover-font-size:var(--el-font-size-base);--el-popover-border-color:var(--el-border-color-lighter);--el-popover-padding:12px;--el-popover-padding-large:18px 20px;--el-popover-title-font-size:16px;--el-popover-title-text-color:var(--el-text-color-primary);--el-popover-border-radius:4px}.el-popover.el-popper{background:var(--el-popover-bg-color);border:1px solid var(--el-popover-border-color);border-radius:var(--el-popover-border-radius);box-shadow:var(--el-box-shadow-light);box-sizing:border-box;color:var(--el-text-color-regular);font-size:var(--el-popover-font-size);line-height:1.4;min-width:150px;overflow-wrap:break-word;padding:var(--el-popover-padding);z-index:var(--el-index-popper)}.el-popover.el-popper--plain{padding:var(--el-popover-padding-large)}.el-popover__title{color:var(--el-popover-title-text-color);font-size:var(--el-popover-title-font-size);line-height:1;margin-bottom:12px}.el-popover__reference:focus:hover,.el-popover__reference:focus:not(.focusing){outline-width:0}.el-popover.el-popper.is-dark{--el-popover-bg-color:var(--el-text-color-primary);--el-popover-border-color:var(--el-text-color-primary);--el-popover-title-text-color:var(--el-bg-color);color:var(--el-bg-color)}.el-popover.el-popper:focus,.el-popover.el-popper:focus:active{outline-width:0}\n"
  },
  {
    "path": "app/src/main/assets/web/vue/assets/vendor-KSDcS24u.js",
    "content": "/**\n* @vue/shared v3.5.30\n* (c) 2018-present Yuxi (Evan) You and Vue contributors\n* @license MIT\n**/function Ku(e){const t=Object.create(null);for(const n of e.split(\",\"))t[n]=1;return n=>n in t}const Ye={},Mo=[],ht=()=>{},mh=()=>!1,Na=e=>e.charCodeAt(0)===111&&e.charCodeAt(1)===110&&(e.charCodeAt(2)>122||e.charCodeAt(2)<97),qu=e=>e.startsWith(\"onUpdate:\"),vt=Object.assign,Wu=(e,t)=>{const n=e.indexOf(t);n>-1&&e.splice(n,1)},Wb=Object.prototype.hasOwnProperty,Ve=(e,t)=>Wb.call(e,t),pe=Array.isArray,$o=e=>oi(e)===\"[object Map]\",La=e=>oi(e)===\"[object Set]\",nf=e=>oi(e)===\"[object Date]\",ve=e=>typeof e==\"function\",Ae=e=>typeof e==\"string\",Tn=e=>typeof e==\"symbol\",Oe=e=>e!==null&&typeof e==\"object\",ia=e=>(Oe(e)||ve(e))&&ve(e.then)&&ve(e.catch),gh=Object.prototype.toString,oi=e=>gh.call(e),Ui=e=>oi(e).slice(8,-1),bh=e=>oi(e)===\"[object Object]\",Ma=e=>Ae(e)&&e!==\"NaN\"&&e[0]!==\"-\"&&\"\"+parseInt(e,10)===e,ws=Ku(\",key,ref,ref_for,ref_key,onVnodeBeforeMount,onVnodeMounted,onVnodeBeforeUpdate,onVnodeUpdated,onVnodeBeforeUnmount,onVnodeUnmounted\"),$a=e=>{const t=Object.create(null);return n=>t[n]||(t[n]=e(n))},Gb=/-\\w/g,Mt=$a(e=>e.replace(Gb,t=>t.slice(1).toUpperCase())),Yb=/\\B([A-Z])/g,hr=$a(e=>e.replace(Yb,\"-$1\").toLowerCase()),si=$a(e=>e.charAt(0).toUpperCase()+e.slice(1)),Ki=$a(e=>e?`on${si(e)}`:\"\"),jn=(e,t)=>!Object.is(e,t),qi=(e,...t)=>{for(let n=0;n<e.length;n++)e[n](...t)},yh=(e,t,n,r=!1)=>{Object.defineProperty(e,t,{configurable:!0,enumerable:!1,writable:r,value:n})},Gu=e=>{const t=parseFloat(e);return isNaN(t)?e:t},Jb=e=>{const t=Ae(e)?Number(e):NaN;return isNaN(t)?e:t};let rf;const ka=()=>rf||(rf=typeof globalThis<\"u\"?globalThis:typeof self<\"u\"?self:typeof window<\"u\"?window:typeof global<\"u\"?global:{});function ot(e){if(pe(e)){const t={};for(let n=0;n<e.length;n++){const r=e[n],o=Ae(r)?ey(r):ot(r);if(o)for(const s in o)t[s]=o[s]}return t}else if(Ae(e)||Oe(e))return e}const Xb=/;(?![^(]*\\))/g,Zb=/:([^]+)/,Qb=/\\/\\*[^]*?\\*\\//g;function ey(e){const t={};return e.replace(Qb,\"\").split(Xb).forEach(n=>{if(n){const r=n.split(Zb);r.length>1&&(t[r[0].trim()]=r[1].trim())}}),t}function U(e){let t=\"\";if(Ae(e))t=e;else if(pe(e))for(let n=0;n<e.length;n++){const r=U(e[n]);r&&(t+=r+\" \")}else if(Oe(e))for(const n in e)e[n]&&(t+=n+\" \");return t.trim()}const ty=\"itemscope,allowfullscreen,formnovalidate,ismap,nomodule,novalidate,readonly\",ny=Ku(ty);function wh(e){return!!e||e===\"\"}function ry(e,t){if(e.length!==t.length)return!1;let n=!0;for(let r=0;n&&r<e.length;r++)n=ii(e[r],t[r]);return n}function ii(e,t){if(e===t)return!0;let n=nf(e),r=nf(t);if(n||r)return n&&r?e.getTime()===t.getTime():!1;if(n=Tn(e),r=Tn(t),n||r)return e===t;if(n=pe(e),r=pe(t),n||r)return n&&r?ry(e,t):!1;if(n=Oe(e),r=Oe(t),n||r){if(!n||!r)return!1;const o=Object.keys(e).length,s=Object.keys(t).length;if(o!==s)return!1;for(const i in e){const a=e.hasOwnProperty(i),l=t.hasOwnProperty(i);if(a&&!l||!a&&l||!ii(e[i],t[i]))return!1}}return String(e)===String(t)}function Sh(e,t){return e.findIndex(n=>ii(n,t))}const Eh=e=>!!(e&&e.__v_isRef===!0),He=e=>Ae(e)?e:e==null?\"\":pe(e)||Oe(e)&&(e.toString===gh||!ve(e.toString))?Eh(e)?He(e.value):JSON.stringify(e,_h,2):String(e),_h=(e,t)=>Eh(t)?_h(e,t.value):$o(t)?{[`Map(${t.size})`]:[...t.entries()].reduce((n,[r,o],s)=>(n[yl(r,s)+\" =>\"]=o,n),{})}:La(t)?{[`Set(${t.size})`]:[...t.values()].map(n=>yl(n))}:Tn(t)?yl(t):Oe(t)&&!pe(t)&&!bh(t)?String(t):t,yl=(e,t=\"\")=>{var n;return Tn(e)?`Symbol(${(n=e.description)!=null?n:t})`:e};function oy(e){return e==null?\"initial\":typeof e==\"string\"?e===\"\"?\" \":e:String(e)}/**\n* @vue/reactivity v3.5.30\n* (c) 2018-present Yuxi (Evan) You and Vue contributors\n* @license MIT\n**/let xt;class Ch{constructor(t=!1){this.detached=t,this._active=!0,this._on=0,this.effects=[],this.cleanups=[],this._isPaused=!1,this.__v_skip=!0,this.parent=xt,!t&&xt&&(this.index=(xt.scopes||(xt.scopes=[])).push(this)-1)}get active(){return this._active}pause(){if(this._active){this._isPaused=!0;let t,n;if(this.scopes)for(t=0,n=this.scopes.length;t<n;t++)this.scopes[t].pause();for(t=0,n=this.effects.length;t<n;t++)this.effects[t].pause()}}resume(){if(this._active&&this._isPaused){this._isPaused=!1;let t,n;if(this.scopes)for(t=0,n=this.scopes.length;t<n;t++)this.scopes[t].resume();for(t=0,n=this.effects.length;t<n;t++)this.effects[t].resume()}}run(t){if(this._active){const n=xt;try{return xt=this,t()}finally{xt=n}}}on(){++this._on===1&&(this.prevScope=xt,xt=this)}off(){this._on>0&&--this._on===0&&(xt=this.prevScope,this.prevScope=void 0)}stop(t){if(this._active){this._active=!1;let n,r;for(n=0,r=this.effects.length;n<r;n++)this.effects[n].stop();for(this.effects.length=0,n=0,r=this.cleanups.length;n<r;n++)this.cleanups[n]();if(this.cleanups.length=0,this.scopes){for(n=0,r=this.scopes.length;n<r;n++)this.scopes[n].stop(!0);this.scopes.length=0}if(!this.detached&&this.parent&&!t){const o=this.parent.scopes.pop();o&&o!==this&&(this.parent.scopes[this.index]=o,o.index=this.index)}this.parent=void 0}}}function Th(e){return new Ch(e)}function Fa(){return xt}function Ba(e,t=!1){xt&&xt.cleanups.push(e)}let et;const wl=new WeakSet;class Oh{constructor(t){this.fn=t,this.deps=void 0,this.depsTail=void 0,this.flags=5,this.next=void 0,this.cleanup=void 0,this.scheduler=void 0,xt&&xt.active&&xt.effects.push(this)}pause(){this.flags|=64}resume(){this.flags&64&&(this.flags&=-65,wl.has(this)&&(wl.delete(this),this.trigger()))}notify(){this.flags&2&&!(this.flags&32)||this.flags&8||Rh(this)}run(){if(!(this.flags&1))return this.fn();this.flags|=2,of(this),xh(this);const t=et,n=Sn;et=this,Sn=!0;try{return this.fn()}finally{Ih(this),et=t,Sn=n,this.flags&=-3}}stop(){if(this.flags&1){for(let t=this.deps;t;t=t.nextDep)Xu(t);this.deps=this.depsTail=void 0,of(this),this.onStop&&this.onStop(),this.flags&=-2}}trigger(){this.flags&64?wl.add(this):this.scheduler?this.scheduler():this.runIfDirty()}runIfDirty(){eu(this)&&this.run()}get dirty(){return eu(this)}}let Ah=0,Ss,Es;function Rh(e,t=!1){if(e.flags|=8,t){e.next=Es,Es=e;return}e.next=Ss,Ss=e}function Yu(){Ah++}function Ju(){if(--Ah>0)return;if(Es){let t=Es;for(Es=void 0;t;){const n=t.next;t.next=void 0,t.flags&=-9,t=n}}let e;for(;Ss;){let t=Ss;for(Ss=void 0;t;){const n=t.next;if(t.next=void 0,t.flags&=-9,t.flags&1)try{t.trigger()}catch(r){e||(e=r)}t=n}}if(e)throw e}function xh(e){for(let t=e.deps;t;t=t.nextDep)t.version=-1,t.prevActiveLink=t.dep.activeLink,t.dep.activeLink=t}function Ih(e){let t,n=e.depsTail,r=n;for(;r;){const o=r.prevDep;r.version===-1?(r===n&&(n=o),Xu(r),sy(r)):t=r,r.dep.activeLink=r.prevActiveLink,r.prevActiveLink=void 0,r=o}e.deps=t,e.depsTail=n}function eu(e){for(let t=e.deps;t;t=t.nextDep)if(t.dep.version!==t.version||t.dep.computed&&(Ph(t.dep.computed)||t.dep.version!==t.version))return!0;return!!e._dirty}function Ph(e){if(e.flags&4&&!(e.flags&16)||(e.flags&=-17,e.globalVersion===Fs)||(e.globalVersion=Fs,!e.isSSR&&e.flags&128&&(!e.deps&&!e._dirty||!eu(e))))return;e.flags|=2;const t=e.dep,n=et,r=Sn;et=e,Sn=!0;try{xh(e);const o=e.fn(e._value);(t.version===0||jn(o,e._value))&&(e.flags|=128,e._value=o,t.version++)}catch(o){throw t.version++,o}finally{et=n,Sn=r,Ih(e),e.flags&=-3}}function Xu(e,t=!1){const{dep:n,prevSub:r,nextSub:o}=e;if(r&&(r.nextSub=o,e.prevSub=void 0),o&&(o.prevSub=r,e.nextSub=void 0),n.subs===e&&(n.subs=r,!r&&n.computed)){n.computed.flags&=-5;for(let s=n.computed.deps;s;s=s.nextDep)Xu(s,!0)}!t&&!--n.sc&&n.map&&n.map.delete(n.key)}function sy(e){const{prevDep:t,nextDep:n}=e;t&&(t.nextDep=n,e.prevDep=void 0),n&&(n.prevDep=t,e.nextDep=void 0)}let Sn=!0;const Nh=[];function lr(){Nh.push(Sn),Sn=!1}function ur(){const e=Nh.pop();Sn=e===void 0?!0:e}function of(e){const{cleanup:t}=e;if(e.cleanup=void 0,t){const n=et;et=void 0;try{t()}finally{et=n}}}let Fs=0,iy=class{constructor(t,n){this.sub=t,this.dep=n,this.version=n.version,this.nextDep=this.prevDep=this.nextSub=this.prevSub=this.prevActiveLink=void 0}};class Da{constructor(t){this.computed=t,this.version=0,this.activeLink=void 0,this.subs=void 0,this.map=void 0,this.key=void 0,this.sc=0,this.__v_skip=!0}track(t){if(!et||!Sn||et===this.computed)return;let n=this.activeLink;if(n===void 0||n.sub!==et)n=this.activeLink=new iy(et,this),et.deps?(n.prevDep=et.depsTail,et.depsTail.nextDep=n,et.depsTail=n):et.deps=et.depsTail=n,Lh(n);else if(n.version===-1&&(n.version=this.version,n.nextDep)){const r=n.nextDep;r.prevDep=n.prevDep,n.prevDep&&(n.prevDep.nextDep=r),n.prevDep=et.depsTail,n.nextDep=void 0,et.depsTail.nextDep=n,et.depsTail=n,et.deps===n&&(et.deps=r)}return n}trigger(t){this.version++,Fs++,this.notify(t)}notify(t){Yu();try{for(let n=this.subs;n;n=n.prevSub)n.sub.notify()&&n.sub.dep.notify()}finally{Ju()}}}function Lh(e){if(e.dep.sc++,e.sub.flags&4){const t=e.dep.computed;if(t&&!e.dep.subs){t.flags|=20;for(let r=t.deps;r;r=r.nextDep)Lh(r)}const n=e.dep.subs;n!==e&&(e.prevSub=n,n&&(n.nextSub=e)),e.dep.subs=e}}const aa=new WeakMap,oo=Symbol(\"\"),tu=Symbol(\"\"),Bs=Symbol(\"\");function It(e,t,n){if(Sn&&et){let r=aa.get(e);r||aa.set(e,r=new Map);let o=r.get(n);o||(r.set(n,o=new Da),o.map=r,o.key=n),o.track()}}function nr(e,t,n,r,o,s){const i=aa.get(e);if(!i){Fs++;return}const a=l=>{l&&l.trigger()};if(Yu(),t===\"clear\")i.forEach(a);else{const l=pe(e),u=l&&Ma(n);if(l&&n===\"length\"){const c=Number(r);i.forEach((f,d)=>{(d===\"length\"||d===Bs||!Tn(d)&&d>=c)&&a(f)})}else switch((n!==void 0||i.has(void 0))&&a(i.get(n)),u&&a(i.get(Bs)),t){case\"add\":l?u&&a(i.get(\"length\")):(a(i.get(oo)),$o(e)&&a(i.get(tu)));break;case\"delete\":l||(a(i.get(oo)),$o(e)&&a(i.get(tu)));break;case\"set\":$o(e)&&a(i.get(oo));break}}Ju()}function ay(e,t){const n=aa.get(e);return n&&n.get(t)}function _o(e){const t=Me(e);return t===e?t:(It(t,\"iterate\",Bs),Qt(e)?t:t.map(On))}function Va(e){return It(e=Me(e),\"iterate\",Bs),e}function Dn(e,t){return cr(e)?Do(Hn(e)?On(t):t):On(t)}const ly={__proto__:null,[Symbol.iterator](){return Sl(this,Symbol.iterator,e=>Dn(this,e))},concat(...e){return _o(this).concat(...e.map(t=>pe(t)?_o(t):t))},entries(){return Sl(this,\"entries\",e=>(e[1]=Dn(this,e[1]),e))},every(e,t){return Yn(this,\"every\",e,t,void 0,arguments)},filter(e,t){return Yn(this,\"filter\",e,t,n=>n.map(r=>Dn(this,r)),arguments)},find(e,t){return Yn(this,\"find\",e,t,n=>Dn(this,n),arguments)},findIndex(e,t){return Yn(this,\"findIndex\",e,t,void 0,arguments)},findLast(e,t){return Yn(this,\"findLast\",e,t,n=>Dn(this,n),arguments)},findLastIndex(e,t){return Yn(this,\"findLastIndex\",e,t,void 0,arguments)},forEach(e,t){return Yn(this,\"forEach\",e,t,void 0,arguments)},includes(...e){return El(this,\"includes\",e)},indexOf(...e){return El(this,\"indexOf\",e)},join(e){return _o(this).join(e)},lastIndexOf(...e){return El(this,\"lastIndexOf\",e)},map(e,t){return Yn(this,\"map\",e,t,void 0,arguments)},pop(){return ls(this,\"pop\")},push(...e){return ls(this,\"push\",e)},reduce(e,...t){return sf(this,\"reduce\",e,t)},reduceRight(e,...t){return sf(this,\"reduceRight\",e,t)},shift(){return ls(this,\"shift\")},some(e,t){return Yn(this,\"some\",e,t,void 0,arguments)},splice(...e){return ls(this,\"splice\",e)},toReversed(){return _o(this).toReversed()},toSorted(e){return _o(this).toSorted(e)},toSpliced(...e){return _o(this).toSpliced(...e)},unshift(...e){return ls(this,\"unshift\",e)},values(){return Sl(this,\"values\",e=>Dn(this,e))}};function Sl(e,t,n){const r=Va(e),o=r[t]();return r!==e&&!Qt(e)&&(o._next=o.next,o.next=()=>{const s=o._next();return s.done||(s.value=n(s.value)),s}),o}const uy=Array.prototype;function Yn(e,t,n,r,o,s){const i=Va(e),a=i!==e&&!Qt(e),l=i[t];if(l!==uy[t]){const f=l.apply(e,s);return a?On(f):f}let u=n;i!==e&&(a?u=function(f,d){return n.call(this,Dn(e,f),d,e)}:n.length>2&&(u=function(f,d){return n.call(this,f,d,e)}));const c=l.call(i,u,r);return a&&o?o(c):c}function sf(e,t,n,r){const o=Va(e),s=o!==e&&!Qt(e);let i=n,a=!1;o!==e&&(s?(a=r.length===0,i=function(u,c,f){return a&&(a=!1,u=Dn(e,u)),n.call(this,u,Dn(e,c),f,e)}):n.length>3&&(i=function(u,c,f){return n.call(this,u,c,f,e)}));const l=o[t](i,...r);return a?Dn(e,l):l}function El(e,t,n){const r=Me(e);It(r,\"iterate\",Bs);const o=r[t](...n);return(o===-1||o===!1)&&ja(n[0])?(n[0]=Me(n[0]),r[t](...n)):o}function ls(e,t,n=[]){lr(),Yu();const r=Me(e)[t].apply(e,n);return Ju(),ur(),r}const cy=Ku(\"__proto__,__v_isRef,__isVue\"),Mh=new Set(Object.getOwnPropertyNames(Symbol).filter(e=>e!==\"arguments\"&&e!==\"caller\").map(e=>Symbol[e]).filter(Tn));function fy(e){Tn(e)||(e=String(e));const t=Me(this);return It(t,\"has\",e),t.hasOwnProperty(e)}class $h{constructor(t=!1,n=!1){this._isReadonly=t,this._isShallow=n}get(t,n,r){if(n===\"__v_skip\")return t.__v_skip;const o=this._isReadonly,s=this._isShallow;if(n===\"__v_isReactive\")return!o;if(n===\"__v_isReadonly\")return o;if(n===\"__v_isShallow\")return s;if(n===\"__v_raw\")return r===(o?s?Sy:Dh:s?Bh:Fh).get(t)||Object.getPrototypeOf(t)===Object.getPrototypeOf(r)?t:void 0;const i=pe(t);if(!o){let l;if(i&&(l=ly[n]))return l;if(n===\"hasOwnProperty\")return fy}const a=Reflect.get(t,n,Ue(t)?t:r);if((Tn(n)?Mh.has(n):cy(n))||(o||It(t,\"get\",n),s))return a;if(Ue(a)){const l=i&&Ma(n)?a:a.value;return o&&Oe(l)?Mr(l):l}return Oe(a)?o?Mr(a):wt(a):a}}class kh extends $h{constructor(t=!1){super(!1,t)}set(t,n,r,o){let s=t[n];const i=pe(t)&&Ma(n);if(!this._isShallow){const u=cr(s);if(!Qt(r)&&!cr(r)&&(s=Me(s),r=Me(r)),!i&&Ue(s)&&!Ue(r))return u||(s.value=r),!0}const a=i?Number(n)<t.length:Ve(t,n),l=Reflect.set(t,n,r,Ue(t)?t:o);return t===Me(o)&&(a?jn(r,s)&&nr(t,\"set\",n,r):nr(t,\"add\",n,r)),l}deleteProperty(t,n){const r=Ve(t,n);t[n];const o=Reflect.deleteProperty(t,n);return o&&r&&nr(t,\"delete\",n,void 0),o}has(t,n){const r=Reflect.has(t,n);return(!Tn(n)||!Mh.has(n))&&It(t,\"has\",n),r}ownKeys(t){return It(t,\"iterate\",pe(t)?\"length\":oo),Reflect.ownKeys(t)}}class dy extends $h{constructor(t=!1){super(!0,t)}set(t,n){return!0}deleteProperty(t,n){return!0}}const py=new kh,hy=new dy,vy=new kh(!0);const nu=e=>e,Ci=e=>Reflect.getPrototypeOf(e);function my(e,t,n){return function(...r){const o=this.__v_raw,s=Me(o),i=$o(s),a=e===\"entries\"||e===Symbol.iterator&&i,l=e===\"keys\"&&i,u=o[e](...r),c=n?nu:t?Do:On;return!t&&It(s,\"iterate\",l?tu:oo),vt(Object.create(u),{next(){const{value:f,done:d}=u.next();return d?{value:f,done:d}:{value:a?[c(f[0]),c(f[1])]:c(f),done:d}}})}}function Ti(e){return function(...t){return e===\"delete\"?!1:e===\"clear\"?void 0:this}}function gy(e,t){const n={get(o){const s=this.__v_raw,i=Me(s),a=Me(o);e||(jn(o,a)&&It(i,\"get\",o),It(i,\"get\",a));const{has:l}=Ci(i),u=t?nu:e?Do:On;if(l.call(i,o))return u(s.get(o));if(l.call(i,a))return u(s.get(a));s!==i&&s.get(o)},get size(){const o=this.__v_raw;return!e&&It(Me(o),\"iterate\",oo),o.size},has(o){const s=this.__v_raw,i=Me(s),a=Me(o);return e||(jn(o,a)&&It(i,\"has\",o),It(i,\"has\",a)),o===a?s.has(o):s.has(o)||s.has(a)},forEach(o,s){const i=this,a=i.__v_raw,l=Me(a),u=t?nu:e?Do:On;return!e&&It(l,\"iterate\",oo),a.forEach((c,f)=>o.call(s,u(c),u(f),i))}};return vt(n,e?{add:Ti(\"add\"),set:Ti(\"set\"),delete:Ti(\"delete\"),clear:Ti(\"clear\")}:{add(o){const s=Me(this),i=Ci(s),a=Me(o),l=!t&&!Qt(o)&&!cr(o)?a:o;return i.has.call(s,l)||jn(o,l)&&i.has.call(s,o)||jn(a,l)&&i.has.call(s,a)||(s.add(l),nr(s,\"add\",l,l)),this},set(o,s){!t&&!Qt(s)&&!cr(s)&&(s=Me(s));const i=Me(this),{has:a,get:l}=Ci(i);let u=a.call(i,o);u||(o=Me(o),u=a.call(i,o));const c=l.call(i,o);return i.set(o,s),u?jn(s,c)&&nr(i,\"set\",o,s):nr(i,\"add\",o,s),this},delete(o){const s=Me(this),{has:i,get:a}=Ci(s);let l=i.call(s,o);l||(o=Me(o),l=i.call(s,o)),a&&a.call(s,o);const u=s.delete(o);return l&&nr(s,\"delete\",o,void 0),u},clear(){const o=Me(this),s=o.size!==0,i=o.clear();return s&&nr(o,\"clear\",void 0,void 0),i}}),[\"keys\",\"values\",\"entries\",Symbol.iterator].forEach(o=>{n[o]=my(o,e,t)}),n}function Zu(e,t){const n=gy(e,t);return(r,o,s)=>o===\"__v_isReactive\"?!e:o===\"__v_isReadonly\"?e:o===\"__v_raw\"?r:Reflect.get(Ve(n,o)&&o in r?n:r,o,s)}const by={get:Zu(!1,!1)},yy={get:Zu(!1,!0)},wy={get:Zu(!0,!1)};const Fh=new WeakMap,Bh=new WeakMap,Dh=new WeakMap,Sy=new WeakMap;function Ey(e){switch(e){case\"Object\":case\"Array\":return 1;case\"Map\":case\"Set\":case\"WeakMap\":case\"WeakSet\":return 2;default:return 0}}function _y(e){return e.__v_skip||!Object.isExtensible(e)?0:Ey(Ui(e))}function wt(e){return cr(e)?e:ec(e,!1,py,by,Fh)}function Qu(e){return ec(e,!1,vy,yy,Bh)}function Mr(e){return ec(e,!0,hy,wy,Dh)}function ec(e,t,n,r,o){if(!Oe(e)||e.__v_raw&&!(t&&e.__v_isReactive))return e;const s=_y(e);if(s===0)return e;const i=o.get(e);if(i)return i;const a=new Proxy(e,s===2?r:n);return o.set(e,a),a}function Hn(e){return cr(e)?Hn(e.__v_raw):!!(e&&e.__v_isReactive)}function cr(e){return!!(e&&e.__v_isReadonly)}function Qt(e){return!!(e&&e.__v_isShallow)}function ja(e){return e?!!e.__v_raw:!1}function Me(e){const t=e&&e.__v_raw;return t?Me(t):e}function Ds(e){return!Ve(e,\"__v_skip\")&&Object.isExtensible(e)&&yh(e,\"__v_skip\",!0),e}const On=e=>Oe(e)?wt(e):e,Do=e=>Oe(e)?Mr(e):e;function Ue(e){return e?e.__v_isRef===!0:!1}function V(e){return Vh(e,!1)}function ir(e){return Vh(e,!0)}function Vh(e,t){return Ue(e)?e:new Cy(e,t)}class Cy{constructor(t,n){this.dep=new Da,this.__v_isRef=!0,this.__v_isShallow=!1,this._rawValue=n?t:Me(t),this._value=n?t:On(t),this.__v_isShallow=n}get value(){return this.dep.track(),this._value}set value(t){const n=this._rawValue,r=this.__v_isShallow||Qt(t)||cr(t);t=r?t:Me(t),jn(t,n)&&(this._rawValue=t,this._value=r?t:On(t),this.dep.trigger())}}function g(e){return Ue(e)?e.value:e}const Ty={get:(e,t,n)=>t===\"__v_raw\"?e:g(Reflect.get(e,t,n)),set:(e,t,n,r)=>{const o=e[t];return Ue(o)&&!Ue(n)?(o.value=n,!0):Reflect.set(e,t,n,r)}};function jh(e){return Hn(e)?e:new Proxy(e,Ty)}class Oy{constructor(t){this.__v_isRef=!0,this._value=void 0;const n=this.dep=new Da,{get:r,set:o}=t(n.track.bind(n),n.trigger.bind(n));this._get=r,this._set=o}get value(){return this._value=this._get()}set value(t){this._set(t)}}function Ay(e){return new Oy(e)}function vr(e){const t=pe(e)?new Array(e.length):{};for(const n in e)t[n]=zh(e,n);return t}class Ry{constructor(t,n,r){this._object=t,this._key=n,this._defaultValue=r,this.__v_isRef=!0,this._value=void 0,this._raw=Me(t);let o=!0,s=t;if(!pe(t)||!Ma(String(n)))do o=!ja(s)||Qt(s);while(o&&(s=s.__v_raw));this._shallow=o}get value(){let t=this._object[this._key];return this._shallow&&(t=g(t)),this._value=t===void 0?this._defaultValue:t}set value(t){if(this._shallow&&Ue(this._raw[this._key])){const n=this._object[this._key];if(Ue(n)){n.value=t;return}}this._object[this._key]=t}get dep(){return ay(this._raw,this._key)}}class xy{constructor(t){this._getter=t,this.__v_isRef=!0,this.__v_isReadonly=!0,this._value=void 0}get value(){return this._value=this._getter()}}function Jt(e,t,n){return Ue(e)?e:ve(e)?new xy(e):Oe(e)&&arguments.length>1?zh(e,t,n):V(e)}function zh(e,t,n){return new Ry(e,t,n)}class Iy{constructor(t,n,r){this.fn=t,this.setter=n,this._value=void 0,this.dep=new Da(this),this.__v_isRef=!0,this.deps=void 0,this.depsTail=void 0,this.flags=16,this.globalVersion=Fs-1,this.next=void 0,this.effect=this,this.__v_isReadonly=!n,this.isSSR=r}notify(){if(this.flags|=16,!(this.flags&8)&&et!==this)return Rh(this,!0),!0}get value(){const t=this.dep.track();return Ph(this),t&&(t.version=this.dep.version),this._value}set value(t){this.setter&&this.setter(t)}}function Py(e,t,n=!1){let r,o;return ve(e)?r=e:(r=e.get,o=e.set),new Iy(r,o,n)}const Oi={},la=new WeakMap;let Jr;function Ny(e,t=!1,n=Jr){if(n){let r=la.get(n);r||la.set(n,r=[]),r.push(e)}}function Ly(e,t,n=Ye){const{immediate:r,deep:o,once:s,scheduler:i,augmentJob:a,call:l}=n,u=w=>o?w:Qt(w)||o===!1||o===0?rr(w,1):rr(w);let c,f,d,v,p=!1,h=!1;if(Ue(e)?(f=()=>e.value,p=Qt(e)):Hn(e)?(f=()=>u(e),p=!0):pe(e)?(h=!0,p=e.some(w=>Hn(w)||Qt(w)),f=()=>e.map(w=>{if(Ue(w))return w.value;if(Hn(w))return u(w);if(ve(w))return l?l(w,2):w()})):ve(e)?t?f=l?()=>l(e,2):e:f=()=>{if(d){lr();try{d()}finally{ur()}}const w=Jr;Jr=c;try{return l?l(e,3,[v]):e(v)}finally{Jr=w}}:f=ht,t&&o){const w=f,y=o===!0?1/0:o;f=()=>rr(w(),y)}const b=Fa(),m=()=>{c.stop(),b&&b.active&&Wu(b.effects,c)};if(s&&t){const w=t;t=(...y)=>{w(...y),m()}}let S=h?new Array(e.length).fill(Oi):Oi;const _=w=>{if(!(!(c.flags&1)||!c.dirty&&!w))if(t){const y=c.run();if(o||p||(h?y.some((A,R)=>jn(A,S[R])):jn(y,S))){d&&d();const A=Jr;Jr=c;try{const R=[y,S===Oi?void 0:h&&S[0]===Oi?[]:S,v];S=y,l?l(t,3,R):t(...R)}finally{Jr=A}}}else c.run()};return a&&a(_),c=new Oh(f),c.scheduler=i?()=>i(_,!1):_,v=w=>Ny(w,!1,c),d=c.onStop=()=>{const w=la.get(c);if(w){if(l)l(w,4);else for(const y of w)y();la.delete(c)}},t?r?_(!0):S=c.run():i?i(_.bind(null,!0),!0):c.run(),m.pause=c.pause.bind(c),m.resume=c.resume.bind(c),m.stop=m,m}function rr(e,t=1/0,n){if(t<=0||!Oe(e)||e.__v_skip||(n=n||new Map,(n.get(e)||0)>=t))return e;if(n.set(e,t),t--,Ue(e))rr(e.value,t,n);else if(pe(e))for(let r=0;r<e.length;r++)rr(e[r],t,n);else if(La(e)||$o(e))e.forEach(r=>{rr(r,t,n)});else if(bh(e)){for(const r in e)rr(e[r],t,n);for(const r of Object.getOwnPropertySymbols(e))Object.prototype.propertyIsEnumerable.call(e,r)&&rr(e[r],t,n)}return e}/**\n* @vue/runtime-core v3.5.30\n* (c) 2018-present Yuxi (Evan) You and Vue contributors\n* @license MIT\n**/function ai(e,t,n,r){try{return r?e(...r):e()}catch(o){za(o,t,n)}}function An(e,t,n,r){if(ve(e)){const o=ai(e,t,n,r);return o&&ia(o)&&o.catch(s=>{za(s,t,n)}),o}if(pe(e)){const o=[];for(let s=0;s<e.length;s++)o.push(An(e[s],t,n,r));return o}}function za(e,t,n,r=!0){const o=t?t.vnode:null,{errorHandler:s,throwUnhandledErrorInProduction:i}=t&&t.appContext.config||Ye;if(t){let a=t.parent;const l=t.proxy,u=`https://vuejs.org/error-reference/#runtime-${n}`;for(;a;){const c=a.ec;if(c){for(let f=0;f<c.length;f++)if(c[f](e,l,u)===!1)return}a=a.parent}if(s){lr(),ai(s,null,10,[e,l,u]),ur();return}}My(e,n,o,r,i)}function My(e,t,n,r=!0,o=!1){if(o)throw e}const Ft=[];let kn=-1;const ko=[];let xr=null,xo=0;const Hh=Promise.resolve();let ua=null;function Ie(e){const t=ua||Hh;return e?t.then(this?e.bind(this):e):t}function $y(e){let t=kn+1,n=Ft.length;for(;t<n;){const r=t+n>>>1,o=Ft[r],s=Vs(o);s<e||s===e&&o.flags&2?t=r+1:n=r}return t}function tc(e){if(!(e.flags&1)){const t=Vs(e),n=Ft[Ft.length-1];!n||!(e.flags&2)&&t>=Vs(n)?Ft.push(e):Ft.splice($y(t),0,e),e.flags|=1,Uh()}}function Uh(){ua||(ua=Hh.then(Wh))}function Kh(e){pe(e)?ko.push(...e):xr&&e.id===-1?xr.splice(xo+1,0,e):e.flags&1||(ko.push(e),e.flags|=1),Uh()}function af(e,t,n=kn+1){for(;n<Ft.length;n++){const r=Ft[n];if(r&&r.flags&2){if(e&&r.id!==e.uid)continue;Ft.splice(n,1),n--,r.flags&4&&(r.flags&=-2),r(),r.flags&4||(r.flags&=-2)}}}function qh(e){if(ko.length){const t=[...new Set(ko)].sort((n,r)=>Vs(n)-Vs(r));if(ko.length=0,xr){xr.push(...t);return}for(xr=t,xo=0;xo<xr.length;xo++){const n=xr[xo];n.flags&4&&(n.flags&=-2),n.flags&8||n(),n.flags&=-2}xr=null,xo=0}}const Vs=e=>e.id==null?e.flags&2?-1:1/0:e.id;function Wh(e){try{for(kn=0;kn<Ft.length;kn++){const t=Ft[kn];t&&!(t.flags&8)&&(t.flags&4&&(t.flags&=-2),ai(t,t.i,t.i?15:14),t.flags&4||(t.flags&=-2))}}finally{for(;kn<Ft.length;kn++){const t=Ft[kn];t&&(t.flags&=-2)}kn=-1,Ft.length=0,qh(),ua=null,(Ft.length||ko.length)&&Wh()}}let Ct=null,Gh=null;function ca(e){const t=Ct;return Ct=e,Gh=e&&e.type.__scopeId||null,t}function ce(e,t=Ct,n){if(!t||e._n)return e;const r=(...o)=>{r._d&&pa(-1);const s=ca(t);let i;try{i=e(...o)}finally{ca(s),r._d&&pa(1)}return i};return r._n=!0,r._c=!0,r._d=!0,r}function ct(e,t){if(Ct===null)return e;const n=Ga(Ct),r=e.dirs||(e.dirs=[]);for(let o=0;o<t.length;o++){let[s,i,a,l=Ye]=t[o];s&&(ve(s)&&(s={mounted:s,updated:s}),s.deep&&rr(i),r.push({dir:s,instance:n,value:i,oldValue:void 0,arg:a,modifiers:l}))}return e}function Ur(e,t,n,r){const o=e.dirs,s=t&&t.dirs;for(let i=0;i<o.length;i++){const a=o[i];s&&(a.oldValue=s[i].value);let l=a.dir[r];l&&(lr(),An(l,n,8,[e.el,a,e,t]),ur())}}function ft(e,t){if(Pt){let n=Pt.provides;const r=Pt.parent&&Pt.parent.provides;r===n&&(n=Pt.provides=Object.create(r)),n[e]=t}}function Ee(e,t,n=!1){const r=Ge();if(r||so){let o=so?so._context.provides:r?r.parent==null||r.ce?r.vnode.appContext&&r.vnode.appContext.provides:r.parent.provides:void 0;if(o&&e in o)return o[e];if(arguments.length>1)return n&&ve(t)?t.call(r&&r.proxy):t}}function ky(){return!!(Ge()||so)}const Fy=Symbol.for(\"v-scx\"),By=()=>Ee(Fy);function Ha(e,t){return nc(e,null,t)}function he(e,t,n){return nc(e,t,n)}function nc(e,t,n=Ye){const{immediate:r,deep:o,flush:s,once:i}=n,a=vt({},n),l=t&&r||!t&&s!==\"post\";let u;if(Hs){if(s===\"sync\"){const v=By();u=v.__watcherHandles||(v.__watcherHandles=[])}else if(!l){const v=()=>{};return v.stop=ht,v.resume=ht,v.pause=ht,v}}const c=Pt;a.call=(v,p,h)=>An(v,c,p,h);let f=!1;s===\"post\"?a.scheduler=v=>{Rt(v,c&&c.suspense)}:s!==\"sync\"&&(f=!0,a.scheduler=(v,p)=>{p?v():tc(v)}),a.augmentJob=v=>{t&&(v.flags|=4),f&&(v.flags|=2,c&&(v.id=c.uid,v.i=c))};const d=Ly(e,t,a);return Hs&&(u?u.push(d):l&&d()),d}function Dy(e,t,n){const r=this.proxy,o=Ae(e)?e.includes(\".\")?Yh(r,e):()=>r[e]:e.bind(r,r);let s;ve(t)?s=t:(s=t.handler,n=t);const i=li(this),a=nc(o,s.bind(r),n);return i(),a}function Yh(e,t){const n=t.split(\".\");return()=>{let r=e;for(let o=0;o<n.length&&r;o++)r=r[n[o]];return r}}const Jh=Symbol(\"_vte\"),Xh=e=>e.__isTeleport,_s=e=>e&&(e.disabled||e.disabled===\"\"),lf=e=>e&&(e.defer||e.defer===\"\"),uf=e=>typeof SVGElement<\"u\"&&e instanceof SVGElement,cf=e=>typeof MathMLElement==\"function\"&&e instanceof MathMLElement,ru=(e,t)=>{const n=e&&e.to;return Ae(n)?t?t(n):null:n},Zh={name:\"Teleport\",__isTeleport:!0,process(e,t,n,r,o,s,i,a,l,u){const{mc:c,pc:f,pbc:d,o:{insert:v,querySelector:p,createText:h,createComment:b}}=u,m=_s(t.props);let{shapeFlag:S,children:_,dynamicChildren:w}=t;if(e==null){const y=t.el=h(\"\"),A=t.anchor=h(\"\");v(y,n,r),v(A,n,r);const R=(I,x)=>{S&16&&c(_,I,x,o,s,i,a,l)},N=()=>{const I=t.target=ru(t.props,p),x=ou(I,t,h,v);I&&(i!==\"svg\"&&uf(I)?i=\"svg\":i!==\"mathml\"&&cf(I)&&(i=\"mathml\"),o&&o.isCE&&(o.ce._teleportTargets||(o.ce._teleportTargets=new Set)).add(I),m||(R(I,x),Wi(t,!1)))};m&&(R(n,A),Wi(t,!0)),lf(t.props)?(t.el.__isMounted=!1,Rt(()=>{N(),delete t.el.__isMounted},s)):N()}else{if(lf(t.props)&&e.el.__isMounted===!1){Rt(()=>{Zh.process(e,t,n,r,o,s,i,a,l,u)},s);return}t.el=e.el,t.targetStart=e.targetStart;const y=t.anchor=e.anchor,A=t.target=e.target,R=t.targetAnchor=e.targetAnchor,N=_s(e.props),I=N?n:A,x=N?y:R;if(i===\"svg\"||uf(A)?i=\"svg\":(i===\"mathml\"||cf(A))&&(i=\"mathml\"),w?(d(e.dynamicChildren,w,I,o,s,i,a),cc(e,t,!0)):l||f(e,t,I,x,o,s,i,a,!1),m)N?t.props&&e.props&&t.props.to!==e.props.to&&(t.props.to=e.props.to):Ai(t,n,y,u,1);else if((t.props&&t.props.to)!==(e.props&&e.props.to)){const k=t.target=ru(t.props,p);k&&Ai(t,k,null,u,0)}else N&&Ai(t,A,R,u,1);Wi(t,m)}},remove(e,t,n,{um:r,o:{remove:o}},s){const{shapeFlag:i,children:a,anchor:l,targetStart:u,targetAnchor:c,target:f,props:d}=e;if(f&&(o(u),o(c)),s&&o(l),i&16){const v=s||!_s(d);for(let p=0;p<a.length;p++){const h=a[p];r(h,t,n,v,!!h.dynamicChildren)}}},move:Ai,hydrate:Vy};function Ai(e,t,n,{o:{insert:r},m:o},s=2){s===0&&r(e.targetAnchor,t,n);const{el:i,anchor:a,shapeFlag:l,children:u,props:c}=e,f=s===2;if(f&&r(i,t,n),(!f||_s(c))&&l&16)for(let d=0;d<u.length;d++)o(u[d],t,n,2);f&&r(a,t,n)}function Vy(e,t,n,r,o,s,{o:{nextSibling:i,parentNode:a,querySelector:l,insert:u,createText:c}},f){function d(b,m){let S=m;for(;S;){if(S&&S.nodeType===8){if(S.data===\"teleport start anchor\")t.targetStart=S;else if(S.data===\"teleport anchor\"){t.targetAnchor=S,b._lpa=t.targetAnchor&&i(t.targetAnchor);break}}S=i(S)}}function v(b,m){m.anchor=f(i(b),m,a(b),n,r,o,s)}const p=t.target=ru(t.props,l),h=_s(t.props);if(p){const b=p._lpa||p.firstChild;t.shapeFlag&16&&(h?(v(e,t),d(p,b),t.targetAnchor||ou(p,t,c,u,a(e)===p?e:null)):(t.anchor=i(e),d(p,b),t.targetAnchor||ou(p,t,c,u),f(b&&i(b),t,p,n,r,o,s))),Wi(t,h)}else h&&t.shapeFlag&16&&(v(e,t),t.targetStart=e,t.targetAnchor=i(e));return t.anchor&&i(t.anchor)}const jy=Zh;function Wi(e,t){const n=e.ctx;if(n&&n.ut){let r,o;for(t?(r=e.el,o=e.anchor):(r=e.targetStart,o=e.targetAnchor);r&&r!==o;)r.nodeType===1&&r.setAttribute(\"data-v-owner\",n.uid),r=r.nextSibling;n.ut()}}function ou(e,t,n,r,o=null){const s=t.targetStart=n(\"\"),i=t.targetAnchor=n(\"\");return s[Jh]=i,e&&(r(s,e,o),r(i,e,o)),i}const Bn=Symbol(\"_leaveCb\"),us=Symbol(\"_enterCb\");function Qh(){const e={isMounted:!1,isLeaving:!1,isUnmounting:!1,leavingVNodes:new Map};return Ke(()=>{e.isMounted=!0}),St(()=>{e.isUnmounting=!0}),e}const sn=[Function,Array],ev={mode:String,appear:Boolean,persisted:Boolean,onBeforeEnter:sn,onEnter:sn,onAfterEnter:sn,onEnterCancelled:sn,onBeforeLeave:sn,onLeave:sn,onAfterLeave:sn,onLeaveCancelled:sn,onBeforeAppear:sn,onAppear:sn,onAfterAppear:sn,onAppearCancelled:sn},tv=e=>{const t=e.subTree;return t.component?tv(t.component):t},zy={name:\"BaseTransition\",props:ev,setup(e,{slots:t}){const n=Ge(),r=Qh();return()=>{const o=t.default&&rc(t.default(),!0);if(!o||!o.length)return;const s=nv(o),i=Me(e),{mode:a}=i;if(r.isLeaving)return _l(s);const l=ff(s);if(!l)return _l(s);let u=js(l,i,r,n,f=>u=f);l.type!==_t&&uo(l,u);let c=n.subTree&&ff(n.subTree);if(c&&c.type!==_t&&!Xr(c,l)&&tv(n).type!==_t){let f=js(c,i,r,n);if(uo(c,f),a===\"out-in\"&&l.type!==_t)return r.isLeaving=!0,f.afterLeave=()=>{r.isLeaving=!1,n.job.flags&8||n.update(),delete f.afterLeave,c=void 0},_l(s);a===\"in-out\"&&l.type!==_t?f.delayLeave=(d,v,p)=>{const h=rv(r,c);h[String(c.key)]=c,d[Bn]=()=>{v(),d[Bn]=void 0,delete u.delayedLeave,c=void 0},u.delayedLeave=()=>{p(),delete u.delayedLeave,c=void 0}}:c=void 0}else c&&(c=void 0);return s}}};function nv(e){let t=e[0];if(e.length>1){for(const n of e)if(n.type!==_t){t=n;break}}return t}const Hy=zy;function rv(e,t){const{leavingVNodes:n}=e;let r=n.get(t.type);return r||(r=Object.create(null),n.set(t.type,r)),r}function js(e,t,n,r,o){const{appear:s,mode:i,persisted:a=!1,onBeforeEnter:l,onEnter:u,onAfterEnter:c,onEnterCancelled:f,onBeforeLeave:d,onLeave:v,onAfterLeave:p,onLeaveCancelled:h,onBeforeAppear:b,onAppear:m,onAfterAppear:S,onAppearCancelled:_}=t,w=String(e.key),y=rv(n,e),A=(I,x)=>{I&&An(I,r,9,x)},R=(I,x)=>{const k=x[1];A(I,x),pe(I)?I.every($=>$.length<=1)&&k():I.length<=1&&k()},N={mode:i,persisted:a,beforeEnter(I){let x=l;if(!n.isMounted)if(s)x=b||l;else return;I[Bn]&&I[Bn](!0);const k=y[w];k&&Xr(e,k)&&k.el[Bn]&&k.el[Bn](),A(x,[I])},enter(I){if(y[w]===e)return;let x=u,k=c,$=f;if(!n.isMounted)if(s)x=m||u,k=S||c,$=_||f;else return;let F=!1;I[us]=P=>{F||(F=!0,P?A($,[I]):A(k,[I]),N.delayedLeave&&N.delayedLeave(),I[us]=void 0)};const Y=I[us].bind(null,!1);x?R(x,[I,Y]):Y()},leave(I,x){const k=String(e.key);if(I[us]&&I[us](!0),n.isUnmounting)return x();A(d,[I]);let $=!1;I[Bn]=Y=>{$||($=!0,x(),Y?A(h,[I]):A(p,[I]),I[Bn]=void 0,y[k]===e&&delete y[k])};const F=I[Bn].bind(null,!1);y[k]=e,v?R(v,[I,F]):F()},clone(I){const x=js(I,t,n,r,o);return o&&o(x),x}};return N}function _l(e){if(Ua(e))return e=fr(e),e.children=null,e}function ff(e){if(!Ua(e))return Xh(e.type)&&e.children?nv(e.children):e;if(e.component)return e.component.subTree;const{shapeFlag:t,children:n}=e;if(n){if(t&16)return n[0];if(t&32&&ve(n.default))return n.default()}}function uo(e,t){e.shapeFlag&6&&e.component?(e.transition=t,uo(e.component.subTree,t)):e.shapeFlag&128?(e.ssContent.transition=t.clone(e.ssContent),e.ssFallback.transition=t.clone(e.ssFallback)):e.transition=t}function rc(e,t=!1,n){let r=[],o=0;for(let s=0;s<e.length;s++){let i=e[s];const a=n==null?i.key:String(n)+String(i.key!=null?i.key:s);i.type===nt?(i.patchFlag&128&&o++,r=r.concat(rc(i.children,t,a))):(t||i.type!==_t)&&r.push(a!=null?fr(i,{key:a}):i)}if(o>1)for(let s=0;s<r.length;s++)r[s].patchFlag=-2;return r}function Z(e,t){return ve(e)?vt({name:e.name},t,{setup:e}):e}function ov(e){e.ids=[e.ids[0]+e.ids[2]+++\"-\",0,0]}function df(e,t){let n;return!!((n=Object.getOwnPropertyDescriptor(e,t))&&!n.configurable)}const fa=new WeakMap;function Cs(e,t,n,r,o=!1){if(pe(e)){e.forEach((h,b)=>Cs(h,t&&(pe(t)?t[b]:t),n,r,o));return}if(Fo(r)&&!o){r.shapeFlag&512&&r.type.__asyncResolved&&r.component.subTree.component&&Cs(e,t,n,r.component.subTree);return}const s=r.shapeFlag&4?Ga(r.component):r.el,i=o?null:s,{i:a,r:l}=e,u=t&&t.r,c=a.refs===Ye?a.refs={}:a.refs,f=a.setupState,d=Me(f),v=f===Ye?mh:h=>df(c,h)?!1:Ve(d,h),p=(h,b)=>!(b&&df(c,b));if(u!=null&&u!==l){if(pf(t),Ae(u))c[u]=null,v(u)&&(f[u]=null);else if(Ue(u)){const h=t;p(u,h.k)&&(u.value=null),h.k&&(c[h.k]=null)}}if(ve(l))ai(l,a,12,[i,c]);else{const h=Ae(l),b=Ue(l);if(h||b){const m=()=>{if(e.f){const S=h?v(l)?f[l]:c[l]:p()||!e.k?l.value:c[e.k];if(o)pe(S)&&Wu(S,s);else if(pe(S))S.includes(s)||S.push(s);else if(h)c[l]=[s],v(l)&&(f[l]=c[l]);else{const _=[s];p(l,e.k)&&(l.value=_),e.k&&(c[e.k]=_)}}else h?(c[l]=i,v(l)&&(f[l]=i)):b&&(p(l,e.k)&&(l.value=i),e.k&&(c[e.k]=i))};if(i){const S=()=>{m(),fa.delete(e)};S.id=-1,fa.set(e,S),Rt(S,n)}else pf(e),m()}}}function pf(e){const t=fa.get(e);t&&(t.flags|=8,fa.delete(e))}ka().requestIdleCallback;ka().cancelIdleCallback;const Fo=e=>!!e.type.__asyncLoader,Ua=e=>e.type.__isKeepAlive;function Ka(e,t){sv(e,\"a\",t)}function oc(e,t){sv(e,\"da\",t)}function sv(e,t,n=Pt){const r=e.__wdc||(e.__wdc=()=>{let o=n;for(;o;){if(o.isDeactivated)return;o=o.parent}return e()});if(qa(t,r,n),n){let o=n.parent;for(;o&&o.parent;)Ua(o.parent.vnode)&&Uy(r,t,n,o),o=o.parent}}function Uy(e,t,n,r){const o=qa(t,e,r,!0);kr(()=>{Wu(r[t],o)},n)}function qa(e,t,n=Pt,r=!1){if(n){const o=n[e]||(n[e]=[]),s=t.__weh||(t.__weh=(...i)=>{lr();const a=li(n),l=An(t,n,e,i);return a(),ur(),l});return r?o.unshift(s):o.push(s),s}}const mr=e=>(t,n=Pt)=>{(!Hs||e===\"sp\")&&qa(e,(...r)=>t(...r),n)},sc=mr(\"bm\"),Ke=mr(\"m\"),iv=mr(\"bu\"),mo=mr(\"u\"),St=mr(\"bum\"),kr=mr(\"um\"),Ky=mr(\"sp\"),qy=mr(\"rtg\"),Wy=mr(\"rtc\");function Gy(e,t=Pt){qa(\"ec\",e,t)}const ic=\"components\",Yy=\"directives\";function Yt(e,t){return ac(ic,e,!0,t)||e}const av=Symbol.for(\"v-ndc\");function Xe(e){return Ae(e)?ac(ic,e,!1)||e:e||av}function Jy(e){return ac(Yy,e)}function ac(e,t,n=!0,r=!1){const o=Ct||Pt;if(o){const s=o.type;if(e===ic){const a=L0(s,!1);if(a&&(a===t||a===Mt(t)||a===si(Mt(t))))return s}const i=hf(o[e]||s[e],t)||hf(o.appContext[e],t);return!i&&r?s:i}}function hf(e,t){return e&&(e[t]||e[Mt(t)]||e[si(Mt(t))])}function vf(e,t,n,r){let o;const s=n,i=pe(e);if(i||Ae(e)){const a=i&&Hn(e);let l=!1,u=!1;a&&(l=!Qt(e),u=cr(e),e=Va(e)),o=new Array(e.length);for(let c=0,f=e.length;c<f;c++)o[c]=t(l?u?Do(On(e[c])):On(e[c]):e[c],c,void 0,s)}else if(typeof e==\"number\"){o=new Array(e);for(let a=0;a<e;a++)o[a]=t(a+1,a,void 0,s)}else if(Oe(e))if(e[Symbol.iterator])o=Array.from(e,(a,l)=>t(a,l,void 0,s));else{const a=Object.keys(e);o=new Array(a.length);for(let l=0,u=a.length;l<u;l++){const c=a[l];o[l]=t(e[c],c,l,s)}}else o=[];return o}function lv(e,t){for(let n=0;n<t.length;n++){const r=t[n];if(pe(r))for(let o=0;o<r.length;o++)e[r[o].name]=r[o].fn;else r&&(e[r.name]=r.key?(...o)=>{const s=r.fn(...o);return s&&(s.key=r.key),s}:r.fn)}return e}function de(e,t,n={},r,o){if(Ct.ce||Ct.parent&&Fo(Ct.parent)&&Ct.parent.ce){const u=Object.keys(n).length>0;return t!==\"default\"&&(n.name=t),L(),fe(nt,null,[re(\"slot\",n,r&&r())],u?-2:64)}let s=e[t];s&&s._c&&(s._d=!1),L();const i=s&&uv(s(n)),a=n.key||i&&i.key,l=fe(nt,{key:(a&&!Tn(a)?a:`_${t}`)+(!i&&r?\"_fb\":\"\")},i||(r?r():[]),i&&e._===1?64:-2);return l.scopeId&&(l.slotScopeIds=[l.scopeId+\"-s\"]),s&&s._c&&(s._d=!0),l}function uv(e){return e.some(t=>cn(t)?!(t.type===_t||t.type===nt&&!uv(t.children)):!0)?e:null}const su=e=>e?xv(e)?Ga(e):su(e.parent):null,Ts=vt(Object.create(null),{$:e=>e,$el:e=>e.vnode.el,$data:e=>e.data,$props:e=>e.props,$attrs:e=>e.attrs,$slots:e=>e.slots,$refs:e=>e.refs,$parent:e=>su(e.parent),$root:e=>su(e.root),$host:e=>e.ce,$emit:e=>e.emit,$options:e=>dv(e),$forceUpdate:e=>e.f||(e.f=()=>{tc(e.update)}),$nextTick:e=>e.n||(e.n=Ie.bind(e.proxy)),$watch:e=>Dy.bind(e)}),Cl=(e,t)=>e!==Ye&&!e.__isScriptSetup&&Ve(e,t),Xy={get({_:e},t){if(t===\"__v_skip\")return!0;const{ctx:n,setupState:r,data:o,props:s,accessCache:i,type:a,appContext:l}=e;if(t[0]!==\"$\"){const d=i[t];if(d!==void 0)switch(d){case 1:return r[t];case 2:return o[t];case 4:return n[t];case 3:return s[t]}else{if(Cl(r,t))return i[t]=1,r[t];if(o!==Ye&&Ve(o,t))return i[t]=2,o[t];if(Ve(s,t))return i[t]=3,s[t];if(n!==Ye&&Ve(n,t))return i[t]=4,n[t];iu&&(i[t]=0)}}const u=Ts[t];let c,f;if(u)return t===\"$attrs\"&&It(e.attrs,\"get\",\"\"),u(e);if((c=a.__cssModules)&&(c=c[t]))return c;if(n!==Ye&&Ve(n,t))return i[t]=4,n[t];if(f=l.config.globalProperties,Ve(f,t))return f[t]},set({_:e},t,n){const{data:r,setupState:o,ctx:s}=e;return Cl(o,t)?(o[t]=n,!0):r!==Ye&&Ve(r,t)?(r[t]=n,!0):Ve(e.props,t)||t[0]===\"$\"&&t.slice(1)in e?!1:(s[t]=n,!0)},has({_:{data:e,setupState:t,accessCache:n,ctx:r,appContext:o,props:s,type:i}},a){let l;return!!(n[a]||e!==Ye&&a[0]!==\"$\"&&Ve(e,a)||Cl(t,a)||Ve(s,a)||Ve(r,a)||Ve(Ts,a)||Ve(o.config.globalProperties,a)||(l=i.__cssModules)&&l[a])},defineProperty(e,t,n){return n.get!=null?e._.accessCache[t]=0:Ve(n,\"value\")&&this.set(e,t,n.value,null),Reflect.defineProperty(e,t,n)}};function go(){return cv().slots}function Zy(){return cv().attrs}function cv(e){const t=Ge();return t.setupContext||(t.setupContext=Pv(t))}function mf(e){return pe(e)?e.reduce((t,n)=>(t[n]=null,t),{}):e}let iu=!0;function Qy(e){const t=dv(e),n=e.proxy,r=e.ctx;iu=!1,t.beforeCreate&&gf(t.beforeCreate,e,\"bc\");const{data:o,computed:s,methods:i,watch:a,provide:l,inject:u,created:c,beforeMount:f,mounted:d,beforeUpdate:v,updated:p,activated:h,deactivated:b,beforeDestroy:m,beforeUnmount:S,destroyed:_,unmounted:w,render:y,renderTracked:A,renderTriggered:R,errorCaptured:N,serverPrefetch:I,expose:x,inheritAttrs:k,components:$,directives:F,filters:Y}=t;if(u&&e0(u,r,null),i)for(const j in i){const Q=i[j];ve(Q)&&(r[j]=Q.bind(n))}if(o){const j=o.call(n,n);Oe(j)&&(e.data=wt(j))}if(iu=!0,s)for(const j in s){const Q=s[j],me=ve(Q)?Q.bind(n,n):ve(Q.get)?Q.get.bind(n,n):ht,Pe=!ve(Q)&&ve(Q.set)?Q.set.bind(n):ht,Re=C({get:me,set:Pe});Object.defineProperty(r,j,{enumerable:!0,configurable:!0,get:()=>Re.value,set:Ce=>Re.value=Ce})}if(a)for(const j in a)fv(a[j],r,n,j);if(l){const j=ve(l)?l.call(n):l;Reflect.ownKeys(j).forEach(Q=>{ft(Q,j[Q])})}c&&gf(c,e,\"c\");function O(j,Q){pe(Q)?Q.forEach(me=>j(me.bind(n))):Q&&j(Q.bind(n))}if(O(sc,f),O(Ke,d),O(iv,v),O(mo,p),O(Ka,h),O(oc,b),O(Gy,N),O(Wy,A),O(qy,R),O(St,S),O(kr,w),O(Ky,I),pe(x))if(x.length){const j=e.exposed||(e.exposed={});x.forEach(Q=>{Object.defineProperty(j,Q,{get:()=>n[Q],set:me=>n[Q]=me,enumerable:!0})})}else e.exposed||(e.exposed={});y&&e.render===ht&&(e.render=y),k!=null&&(e.inheritAttrs=k),$&&(e.components=$),F&&(e.directives=F),I&&ov(e)}function e0(e,t,n=ht){pe(e)&&(e=au(e));for(const r in e){const o=e[r];let s;Oe(o)?\"default\"in o?s=Ee(o.from||r,o.default,!0):s=Ee(o.from||r):s=Ee(o),Ue(s)?Object.defineProperty(t,r,{enumerable:!0,configurable:!0,get:()=>s.value,set:i=>s.value=i}):t[r]=s}}function gf(e,t,n){An(pe(e)?e.map(r=>r.bind(t.proxy)):e.bind(t.proxy),t,n)}function fv(e,t,n,r){let o=r.includes(\".\")?Yh(n,r):()=>n[r];if(Ae(e)){const s=t[e];ve(s)&&he(o,s)}else if(ve(e))he(o,e.bind(n));else if(Oe(e))if(pe(e))e.forEach(s=>fv(s,t,n,r));else{const s=ve(e.handler)?e.handler.bind(n):t[e.handler];ve(s)&&he(o,s,e)}}function dv(e){const t=e.type,{mixins:n,extends:r}=t,{mixins:o,optionsCache:s,config:{optionMergeStrategies:i}}=e.appContext,a=s.get(t);let l;return a?l=a:!o.length&&!n&&!r?l=t:(l={},o.length&&o.forEach(u=>da(l,u,i,!0)),da(l,t,i)),Oe(t)&&s.set(t,l),l}function da(e,t,n,r=!1){const{mixins:o,extends:s}=t;s&&da(e,s,n,!0),o&&o.forEach(i=>da(e,i,n,!0));for(const i in t)if(!(r&&i===\"expose\")){const a=t0[i]||n&&n[i];e[i]=a?a(e[i],t[i]):t[i]}return e}const t0={data:bf,props:yf,emits:yf,methods:gs,computed:gs,beforeCreate:$t,created:$t,beforeMount:$t,mounted:$t,beforeUpdate:$t,updated:$t,beforeDestroy:$t,beforeUnmount:$t,destroyed:$t,unmounted:$t,activated:$t,deactivated:$t,errorCaptured:$t,serverPrefetch:$t,components:gs,directives:gs,watch:r0,provide:bf,inject:n0};function bf(e,t){return t?e?function(){return vt(ve(e)?e.call(this,this):e,ve(t)?t.call(this,this):t)}:t:e}function n0(e,t){return gs(au(e),au(t))}function au(e){if(pe(e)){const t={};for(let n=0;n<e.length;n++)t[e[n]]=e[n];return t}return e}function $t(e,t){return e?[...new Set([].concat(e,t))]:t}function gs(e,t){return e?vt(Object.create(null),e,t):t}function yf(e,t){return e?pe(e)&&pe(t)?[...new Set([...e,...t])]:vt(Object.create(null),mf(e),mf(t??{})):t}function r0(e,t){if(!e)return t;if(!t)return e;const n=vt(Object.create(null),e);for(const r in t)n[r]=$t(e[r],t[r]);return n}function pv(){return{app:null,config:{isNativeTag:mh,performance:!1,globalProperties:{},optionMergeStrategies:{},errorHandler:void 0,warnHandler:void 0,compilerOptions:{}},mixins:[],components:{},directives:{},provides:Object.create(null),optionsCache:new WeakMap,propsCache:new WeakMap,emitsCache:new WeakMap}}let o0=0;function s0(e,t){return function(r,o=null){ve(r)||(r=vt({},r)),o!=null&&!Oe(o)&&(o=null);const s=pv(),i=new WeakSet,a=[];let l=!1;const u=s.app={_uid:o0++,_component:r,_props:o,_container:null,_context:s,_instance:null,version:$0,get config(){return s.config},set config(c){},use(c,...f){return i.has(c)||(c&&ve(c.install)?(i.add(c),c.install(u,...f)):ve(c)&&(i.add(c),c(u,...f))),u},mixin(c){return s.mixins.includes(c)||s.mixins.push(c),u},component(c,f){return f?(s.components[c]=f,u):s.components[c]},directive(c,f){return f?(s.directives[c]=f,u):s.directives[c]},mount(c,f,d){if(!l){const v=u._ceVNode||re(r,o);return v.appContext=s,d===!0?d=\"svg\":d===!1&&(d=void 0),e(v,c,d),l=!0,u._container=c,c.__vue_app__=u,Ga(v.component)}},onUnmount(c){a.push(c)},unmount(){l&&(An(a,u._instance,16),e(null,u._container),delete u._container.__vue_app__)},provide(c,f){return s.provides[c]=f,u},runWithContext(c){const f=so;so=u;try{return c()}finally{so=f}}};return u}}let so=null;const i0=(e,t)=>t===\"modelValue\"||t===\"model-value\"?e.modelModifiers:e[`${t}Modifiers`]||e[`${Mt(t)}Modifiers`]||e[`${hr(t)}Modifiers`];function a0(e,t,...n){if(e.isUnmounted)return;const r=e.vnode.props||Ye;let o=n;const s=t.startsWith(\"update:\"),i=s&&i0(r,t.slice(7));i&&(i.trim&&(o=n.map(c=>Ae(c)?c.trim():c)),i.number&&(o=n.map(Gu)));let a,l=r[a=Ki(t)]||r[a=Ki(Mt(t))];!l&&s&&(l=r[a=Ki(hr(t))]),l&&An(l,e,6,o);const u=r[a+\"Once\"];if(u){if(!e.emitted)e.emitted={};else if(e.emitted[a])return;e.emitted[a]=!0,An(u,e,6,o)}}const l0=new WeakMap;function hv(e,t,n=!1){const r=n?l0:t.emitsCache,o=r.get(e);if(o!==void 0)return o;const s=e.emits;let i={},a=!1;if(!ve(e)){const l=u=>{const c=hv(u,t,!0);c&&(a=!0,vt(i,c))};!n&&t.mixins.length&&t.mixins.forEach(l),e.extends&&l(e.extends),e.mixins&&e.mixins.forEach(l)}return!s&&!a?(Oe(e)&&r.set(e,null),null):(pe(s)?s.forEach(l=>i[l]=null):vt(i,s),Oe(e)&&r.set(e,i),i)}function Wa(e,t){return!e||!Na(t)?!1:(t=t.slice(2).replace(/Once$/,\"\"),Ve(e,t[0].toLowerCase()+t.slice(1))||Ve(e,hr(t))||Ve(e,t))}function wf(e){const{type:t,vnode:n,proxy:r,withProxy:o,propsOptions:[s],slots:i,attrs:a,emit:l,render:u,renderCache:c,props:f,data:d,setupState:v,ctx:p,inheritAttrs:h}=e,b=ca(e);let m,S;try{if(n.shapeFlag&4){const w=o||r,y=w;m=Vn(u.call(y,w,c,f,v,d,p)),S=a}else{const w=t;m=Vn(w.length>1?w(f,{attrs:a,slots:i,emit:l}):w(f,null)),S=t.props?a:u0(a)}}catch(w){Os.length=0,za(w,e,1),m=re(_t)}let _=m;if(S&&h!==!1){const w=Object.keys(S),{shapeFlag:y}=_;w.length&&y&7&&(s&&w.some(qu)&&(S=c0(S,s)),_=fr(_,S,!1,!0))}return n.dirs&&(_=fr(_,null,!1,!0),_.dirs=_.dirs?_.dirs.concat(n.dirs):n.dirs),n.transition&&uo(_,n.transition),m=_,ca(b),m}const u0=e=>{let t;for(const n in e)(n===\"class\"||n===\"style\"||Na(n))&&((t||(t={}))[n]=e[n]);return t},c0=(e,t)=>{const n={};for(const r in e)(!qu(r)||!(r.slice(9)in t))&&(n[r]=e[r]);return n};function f0(e,t,n){const{props:r,children:o,component:s}=e,{props:i,children:a,patchFlag:l}=t,u=s.emitsOptions;if(t.dirs||t.transition)return!0;if(n&&l>=0){if(l&1024)return!0;if(l&16)return r?Sf(r,i,u):!!i;if(l&8){const c=t.dynamicProps;for(let f=0;f<c.length;f++){const d=c[f];if(vv(i,r,d)&&!Wa(u,d))return!0}}}else return(o||a)&&(!a||!a.$stable)?!0:r===i?!1:r?i?Sf(r,i,u):!0:!!i;return!1}function Sf(e,t,n){const r=Object.keys(t);if(r.length!==Object.keys(e).length)return!0;for(let o=0;o<r.length;o++){const s=r[o];if(vv(t,e,s)&&!Wa(n,s))return!0}return!1}function vv(e,t,n){const r=e[n],o=t[n];return n===\"style\"&&Oe(r)&&Oe(o)?!ii(r,o):r!==o}function d0({vnode:e,parent:t},n){for(;t;){const r=t.subTree;if(r.suspense&&r.suspense.activeBranch===e&&(r.el=e.el),r===e)(e=t.vnode).el=n,t=t.parent;else break}}const mv={},gv=()=>Object.create(mv),bv=e=>Object.getPrototypeOf(e)===mv;function p0(e,t,n,r=!1){const o={},s=gv();e.propsDefaults=Object.create(null),yv(e,t,o,s);for(const i in e.propsOptions[0])i in o||(o[i]=void 0);n?e.props=r?o:Qu(o):e.type.props?e.props=o:e.props=s,e.attrs=s}function h0(e,t,n,r){const{props:o,attrs:s,vnode:{patchFlag:i}}=e,a=Me(o),[l]=e.propsOptions;let u=!1;if((r||i>0)&&!(i&16)){if(i&8){const c=e.vnode.dynamicProps;for(let f=0;f<c.length;f++){let d=c[f];if(Wa(e.emitsOptions,d))continue;const v=t[d];if(l)if(Ve(s,d))v!==s[d]&&(s[d]=v,u=!0);else{const p=Mt(d);o[p]=lu(l,a,p,v,e,!1)}else v!==s[d]&&(s[d]=v,u=!0)}}}else{yv(e,t,o,s)&&(u=!0);let c;for(const f in a)(!t||!Ve(t,f)&&((c=hr(f))===f||!Ve(t,c)))&&(l?n&&(n[f]!==void 0||n[c]!==void 0)&&(o[f]=lu(l,a,f,void 0,e,!0)):delete o[f]);if(s!==a)for(const f in s)(!t||!Ve(t,f))&&(delete s[f],u=!0)}u&&nr(e.attrs,\"set\",\"\")}function yv(e,t,n,r){const[o,s]=e.propsOptions;let i=!1,a;if(t)for(let l in t){if(ws(l))continue;const u=t[l];let c;o&&Ve(o,c=Mt(l))?!s||!s.includes(c)?n[c]=u:(a||(a={}))[c]=u:Wa(e.emitsOptions,l)||(!(l in r)||u!==r[l])&&(r[l]=u,i=!0)}if(s){const l=Me(n),u=a||Ye;for(let c=0;c<s.length;c++){const f=s[c];n[f]=lu(o,l,f,u[f],e,!Ve(u,f))}}return i}function lu(e,t,n,r,o,s){const i=e[n];if(i!=null){const a=Ve(i,\"default\");if(a&&r===void 0){const l=i.default;if(i.type!==Function&&!i.skipFactory&&ve(l)){const{propsDefaults:u}=o;if(n in u)r=u[n];else{const c=li(o);r=u[n]=l.call(null,t),c()}}else r=l;o.ce&&o.ce._setProp(n,r)}i[0]&&(s&&!a?r=!1:i[1]&&(r===\"\"||r===hr(n))&&(r=!0))}return r}const v0=new WeakMap;function wv(e,t,n=!1){const r=n?v0:t.propsCache,o=r.get(e);if(o)return o;const s=e.props,i={},a=[];let l=!1;if(!ve(e)){const c=f=>{l=!0;const[d,v]=wv(f,t,!0);vt(i,d),v&&a.push(...v)};!n&&t.mixins.length&&t.mixins.forEach(c),e.extends&&c(e.extends),e.mixins&&e.mixins.forEach(c)}if(!s&&!l)return Oe(e)&&r.set(e,Mo),Mo;if(pe(s))for(let c=0;c<s.length;c++){const f=Mt(s[c]);Ef(f)&&(i[f]=Ye)}else if(s)for(const c in s){const f=Mt(c);if(Ef(f)){const d=s[c],v=i[f]=pe(d)||ve(d)?{type:d}:vt({},d),p=v.type;let h=!1,b=!0;if(pe(p))for(let m=0;m<p.length;++m){const S=p[m],_=ve(S)&&S.name;if(_===\"Boolean\"){h=!0;break}else _===\"String\"&&(b=!1)}else h=ve(p)&&p.name===\"Boolean\";v[0]=h,v[1]=b,(h||Ve(v,\"default\"))&&a.push(f)}}const u=[i,a];return Oe(e)&&r.set(e,u),u}function Ef(e){return e[0]!==\"$\"&&!ws(e)}const lc=e=>e===\"_\"||e===\"_ctx\"||e===\"$stable\",uc=e=>pe(e)?e.map(Vn):[Vn(e)],m0=(e,t,n)=>{if(t._n)return t;const r=ce((...o)=>uc(t(...o)),n);return r._c=!1,r},Sv=(e,t,n)=>{const r=e._ctx;for(const o in e){if(lc(o))continue;const s=e[o];if(ve(s))t[o]=m0(o,s,r);else if(s!=null){const i=uc(s);t[o]=()=>i}}},Ev=(e,t)=>{const n=uc(t);e.slots.default=()=>n},_v=(e,t,n)=>{for(const r in t)(n||!lc(r))&&(e[r]=t[r])},g0=(e,t,n)=>{const r=e.slots=gv();if(e.vnode.shapeFlag&32){const o=t._;o?(_v(r,t,n),n&&yh(r,\"_\",o,!0)):Sv(t,r)}else t&&Ev(e,t)},b0=(e,t,n)=>{const{vnode:r,slots:o}=e;let s=!0,i=Ye;if(r.shapeFlag&32){const a=t._;a?n&&a===1?s=!1:_v(o,t,n):(s=!t.$stable,Sv(t,o)),i=t}else t&&(Ev(e,t),i={default:1});if(s)for(const a in o)!lc(a)&&i[a]==null&&delete o[a]},Rt=_0;function y0(e){return w0(e)}function w0(e,t){const n=ka();n.__VUE__=!0;const{insert:r,remove:o,patchProp:s,createElement:i,createText:a,createComment:l,setText:u,setElementText:c,parentNode:f,nextSibling:d,setScopeId:v=ht,insertStaticContent:p}=e,h=(E,T,M,W=null,G=null,q=null,se=void 0,ne=null,te=!!T.dynamicChildren)=>{if(E===T)return;E&&!Xr(E,T)&&(W=B(E),Ce(E,G,q,!0),E=null),T.patchFlag===-2&&(te=!1,T.dynamicChildren=null);const{type:X,ref:Se,shapeFlag:ue}=T;switch(X){case Zo:b(E,T,M,W);break;case _t:m(E,T,M,W);break;case Gi:E==null&&S(T,M,W,se);break;case nt:$(E,T,M,W,G,q,se,ne,te);break;default:ue&1?y(E,T,M,W,G,q,se,ne,te):ue&6?F(E,T,M,W,G,q,se,ne,te):(ue&64||ue&128)&&X.process(E,T,M,W,G,q,se,ne,te,oe)}Se!=null&&G?Cs(Se,E&&E.ref,q,T||E,!T):Se==null&&E&&E.ref!=null&&Cs(E.ref,null,q,E,!0)},b=(E,T,M,W)=>{if(E==null)r(T.el=a(T.children),M,W);else{const G=T.el=E.el;T.children!==E.children&&u(G,T.children)}},m=(E,T,M,W)=>{E==null?r(T.el=l(T.children||\"\"),M,W):T.el=E.el},S=(E,T,M,W)=>{[E.el,E.anchor]=p(E.children,T,M,W,E.el,E.anchor)},_=({el:E,anchor:T},M,W)=>{let G;for(;E&&E!==T;)G=d(E),r(E,M,W),E=G;r(T,M,W)},w=({el:E,anchor:T})=>{let M;for(;E&&E!==T;)M=d(E),o(E),E=M;o(T)},y=(E,T,M,W,G,q,se,ne,te)=>{if(T.type===\"svg\"?se=\"svg\":T.type===\"math\"&&(se=\"mathml\"),E==null)A(T,M,W,G,q,se,ne,te);else{const X=E.el&&E.el._isVueCE?E.el:null;try{X&&X._beginPatch(),I(E,T,G,q,se,ne,te)}finally{X&&X._endPatch()}}},A=(E,T,M,W,G,q,se,ne)=>{let te,X;const{props:Se,shapeFlag:ue,transition:ye,dirs:z}=E;if(te=E.el=i(E.type,q,Se&&Se.is,Se),ue&8?c(te,E.children):ue&16&&N(E.children,te,null,W,G,Tl(E,q),se,ne),z&&Ur(E,null,W,\"created\"),R(te,E,E.scopeId,se,W),Se){for(const Le in Se)Le!==\"value\"&&!ws(Le)&&s(te,Le,null,Se[Le],q,W);\"value\"in Se&&s(te,\"value\",null,Se.value,q),(X=Se.onVnodeBeforeMount)&&Mn(X,W,E)}z&&Ur(E,null,W,\"beforeMount\");const be=S0(G,ye);be&&ye.beforeEnter(te),r(te,T,M),((X=Se&&Se.onVnodeMounted)||be||z)&&Rt(()=>{X&&Mn(X,W,E),be&&ye.enter(te),z&&Ur(E,null,W,\"mounted\")},G)},R=(E,T,M,W,G)=>{if(M&&v(E,M),W)for(let q=0;q<W.length;q++)v(E,W[q]);if(G){let q=G.subTree;if(T===q||Ov(q.type)&&(q.ssContent===T||q.ssFallback===T)){const se=G.vnode;R(E,se,se.scopeId,se.slotScopeIds,G.parent)}}},N=(E,T,M,W,G,q,se,ne,te=0)=>{for(let X=te;X<E.length;X++){const Se=E[X]=ne?tr(E[X]):Vn(E[X]);h(null,Se,T,M,W,G,q,se,ne)}},I=(E,T,M,W,G,q,se)=>{const ne=T.el=E.el;let{patchFlag:te,dynamicChildren:X,dirs:Se}=T;te|=E.patchFlag&16;const ue=E.props||Ye,ye=T.props||Ye;let z;if(M&&Kr(M,!1),(z=ye.onVnodeBeforeUpdate)&&Mn(z,M,T,E),Se&&Ur(T,E,M,\"beforeUpdate\"),M&&Kr(M,!0),(ue.innerHTML&&ye.innerHTML==null||ue.textContent&&ye.textContent==null)&&c(ne,\"\"),X?x(E.dynamicChildren,X,ne,M,W,Tl(T,G),q):se||Q(E,T,ne,null,M,W,Tl(T,G),q,!1),te>0){if(te&16)k(ne,ue,ye,M,G);else if(te&2&&ue.class!==ye.class&&s(ne,\"class\",null,ye.class,G),te&4&&s(ne,\"style\",ue.style,ye.style,G),te&8){const be=T.dynamicProps;for(let Le=0;Le<be.length;Le++){const ke=be[Le],dt=ue[ke],pt=ye[ke];(pt!==dt||ke===\"value\")&&s(ne,ke,dt,pt,G,M)}}te&1&&E.children!==T.children&&c(ne,T.children)}else!se&&X==null&&k(ne,ue,ye,M,G);((z=ye.onVnodeUpdated)||Se)&&Rt(()=>{z&&Mn(z,M,T,E),Se&&Ur(T,E,M,\"updated\")},W)},x=(E,T,M,W,G,q,se)=>{for(let ne=0;ne<T.length;ne++){const te=E[ne],X=T[ne],Se=te.el&&(te.type===nt||!Xr(te,X)||te.shapeFlag&198)?f(te.el):M;h(te,X,Se,null,W,G,q,se,!0)}},k=(E,T,M,W,G)=>{if(T!==M){if(T!==Ye)for(const q in T)!ws(q)&&!(q in M)&&s(E,q,T[q],null,G,W);for(const q in M){if(ws(q))continue;const se=M[q],ne=T[q];se!==ne&&q!==\"value\"&&s(E,q,ne,se,G,W)}\"value\"in M&&s(E,\"value\",T.value,M.value,G)}},$=(E,T,M,W,G,q,se,ne,te)=>{const X=T.el=E?E.el:a(\"\"),Se=T.anchor=E?E.anchor:a(\"\");let{patchFlag:ue,dynamicChildren:ye,slotScopeIds:z}=T;z&&(ne=ne?ne.concat(z):z),E==null?(r(X,M,W),r(Se,M,W),N(T.children||[],M,Se,G,q,se,ne,te)):ue>0&&ue&64&&ye&&E.dynamicChildren&&E.dynamicChildren.length===ye.length?(x(E.dynamicChildren,ye,M,G,q,se,ne),(T.key!=null||G&&T===G.subTree)&&cc(E,T,!0)):Q(E,T,M,Se,G,q,se,ne,te)},F=(E,T,M,W,G,q,se,ne,te)=>{T.slotScopeIds=ne,E==null?T.shapeFlag&512?G.ctx.activate(T,M,W,se,te):Y(T,M,W,G,q,se,te):P(E,T,te)},Y=(E,T,M,W,G,q,se)=>{const ne=E.component=x0(E,W,G);if(Ua(E)&&(ne.ctx.renderer=oe),I0(ne,!1,se),ne.asyncDep){if(G&&G.registerDep(ne,O,se),!E.el){const te=ne.subTree=re(_t);m(null,te,T,M),E.placeholder=te.el}}else O(ne,E,T,M,G,q,se)},P=(E,T,M)=>{const W=T.component=E.component;if(f0(E,T,M))if(W.asyncDep&&!W.asyncResolved){j(W,T,M);return}else W.next=T,W.update();else T.el=E.el,W.vnode=T},O=(E,T,M,W,G,q,se)=>{const ne=()=>{if(E.isMounted){let{next:ue,bu:ye,u:z,parent:be,vnode:Le}=E;{const on=Cv(E);if(on){ue&&(ue.el=Le.el,j(E,ue,se)),on.asyncDep.then(()=>{Rt(()=>{E.isUnmounted||X()},G)});return}}let ke=ue,dt;Kr(E,!1),ue?(ue.el=Le.el,j(E,ue,se)):ue=Le,ye&&qi(ye),(dt=ue.props&&ue.props.onVnodeBeforeUpdate)&&Mn(dt,be,ue,Le),Kr(E,!0);const pt=wf(E),rn=E.subTree;E.subTree=pt,h(rn,pt,f(rn.el),B(rn),E,G,q),ue.el=pt.el,ke===null&&d0(E,pt.el),z&&Rt(z,G),(dt=ue.props&&ue.props.onVnodeUpdated)&&Rt(()=>Mn(dt,be,ue,Le),G)}else{let ue;const{el:ye,props:z}=T,{bm:be,m:Le,parent:ke,root:dt,type:pt}=E,rn=Fo(T);Kr(E,!1),be&&qi(be),!rn&&(ue=z&&z.onVnodeBeforeMount)&&Mn(ue,ke,T),Kr(E,!0);{dt.ce&&dt.ce._hasShadowRoot()&&dt.ce._injectChildStyle(pt,E.parent?E.parent.type:void 0);const on=E.subTree=wf(E);h(null,on,M,W,E,G,q),T.el=on.el}if(Le&&Rt(Le,G),!rn&&(ue=z&&z.onVnodeMounted)){const on=T;Rt(()=>Mn(ue,ke,on),G)}(T.shapeFlag&256||ke&&Fo(ke.vnode)&&ke.vnode.shapeFlag&256)&&E.a&&Rt(E.a,G),E.isMounted=!0,T=M=W=null}};E.scope.on();const te=E.effect=new Oh(ne);E.scope.off();const X=E.update=te.run.bind(te),Se=E.job=te.runIfDirty.bind(te);Se.i=E,Se.id=E.uid,te.scheduler=()=>tc(Se),Kr(E,!0),X()},j=(E,T,M)=>{T.component=E;const W=E.vnode.props;E.vnode=T,E.next=null,h0(E,T.props,W,M),b0(E,T.children,M),lr(),af(E),ur()},Q=(E,T,M,W,G,q,se,ne,te=!1)=>{const X=E&&E.children,Se=E?E.shapeFlag:0,ue=T.children,{patchFlag:ye,shapeFlag:z}=T;if(ye>0){if(ye&128){Pe(X,ue,M,W,G,q,se,ne,te);return}else if(ye&256){me(X,ue,M,W,G,q,se,ne,te);return}}z&8?(Se&16&&De(X,G,q),ue!==X&&c(M,ue)):Se&16?z&16?Pe(X,ue,M,W,G,q,se,ne,te):De(X,G,q,!0):(Se&8&&c(M,\"\"),z&16&&N(ue,M,W,G,q,se,ne,te))},me=(E,T,M,W,G,q,se,ne,te)=>{E=E||Mo,T=T||Mo;const X=E.length,Se=T.length,ue=Math.min(X,Se);let ye;for(ye=0;ye<ue;ye++){const z=T[ye]=te?tr(T[ye]):Vn(T[ye]);h(E[ye],z,M,null,G,q,se,ne,te)}X>Se?De(E,G,q,!0,!1,ue):N(T,M,W,G,q,se,ne,te,ue)},Pe=(E,T,M,W,G,q,se,ne,te)=>{let X=0;const Se=T.length;let ue=E.length-1,ye=Se-1;for(;X<=ue&&X<=ye;){const z=E[X],be=T[X]=te?tr(T[X]):Vn(T[X]);if(Xr(z,be))h(z,be,M,null,G,q,se,ne,te);else break;X++}for(;X<=ue&&X<=ye;){const z=E[ue],be=T[ye]=te?tr(T[ye]):Vn(T[ye]);if(Xr(z,be))h(z,be,M,null,G,q,se,ne,te);else break;ue--,ye--}if(X>ue){if(X<=ye){const z=ye+1,be=z<Se?T[z].el:W;for(;X<=ye;)h(null,T[X]=te?tr(T[X]):Vn(T[X]),M,be,G,q,se,ne,te),X++}}else if(X>ye)for(;X<=ue;)Ce(E[X],G,q,!0),X++;else{const z=X,be=X,Le=new Map;for(X=be;X<=ye;X++){const Ot=T[X]=te?tr(T[X]):Vn(T[X]);Ot.key!=null&&Le.set(Ot.key,X)}let ke,dt=0;const pt=ye-be+1;let rn=!1,on=0;const jr=new Array(pt);for(X=0;X<pt;X++)jr[X]=0;for(X=z;X<=ue;X++){const Ot=E[X];if(dt>=pt){Ce(Ot,G,q,!0);continue}let qt;if(Ot.key!=null)qt=Le.get(Ot.key);else for(ke=be;ke<=ye;ke++)if(jr[ke-be]===0&&Xr(Ot,T[ke])){qt=ke;break}qt===void 0?Ce(Ot,G,q,!0):(jr[qt-be]=X+1,qt>=on?on=qt:rn=!0,h(Ot,T[qt],M,null,G,q,se,ne,te),dt++)}const as=rn?E0(jr):Mo;for(ke=as.length-1,X=pt-1;X>=0;X--){const Ot=be+X,qt=T[Ot],zr=T[Ot+1],Si=Ot+1<Se?zr.el||Tv(zr):W;jr[X]===0?h(null,qt,M,Si,G,q,se,ne,te):rn&&(ke<0||X!==as[ke]?Re(qt,M,Si,2):ke--)}}},Re=(E,T,M,W,G=null)=>{const{el:q,type:se,transition:ne,children:te,shapeFlag:X}=E;if(X&6){Re(E.component.subTree,T,M,W);return}if(X&128){E.suspense.move(T,M,W);return}if(X&64){se.move(E,T,M,oe);return}if(se===nt){r(q,T,M);for(let ue=0;ue<te.length;ue++)Re(te[ue],T,M,W);r(E.anchor,T,M);return}if(se===Gi){_(E,T,M);return}if(W!==2&&X&1&&ne)if(W===0)ne.beforeEnter(q),r(q,T,M),Rt(()=>ne.enter(q),G);else{const{leave:ue,delayLeave:ye,afterLeave:z}=ne,be=()=>{E.ctx.isUnmounted?o(q):r(q,T,M)},Le=()=>{q._isLeaving&&q[Bn](!0),ue(q,()=>{be(),z&&z()})};ye?ye(q,be,Le):Le()}else r(q,T,M)},Ce=(E,T,M,W=!1,G=!1)=>{const{type:q,props:se,ref:ne,children:te,dynamicChildren:X,shapeFlag:Se,patchFlag:ue,dirs:ye,cacheIndex:z}=E;if(ue===-2&&(G=!1),ne!=null&&(lr(),Cs(ne,null,M,E,!0),ur()),z!=null&&(T.renderCache[z]=void 0),Se&256){T.ctx.deactivate(E);return}const be=Se&1&&ye,Le=!Fo(E);let ke;if(Le&&(ke=se&&se.onVnodeBeforeUnmount)&&Mn(ke,T,E),Se&6)ze(E.component,M,W);else{if(Se&128){E.suspense.unmount(M,W);return}be&&Ur(E,null,T,\"beforeUnmount\"),Se&64?E.type.remove(E,T,M,oe,W):X&&!X.hasOnce&&(q!==nt||ue>0&&ue&64)?De(X,T,M,!1,!0):(q===nt&&ue&384||!G&&Se&16)&&De(te,T,M),W&&_e(E)}(Le&&(ke=se&&se.onVnodeUnmounted)||be)&&Rt(()=>{ke&&Mn(ke,T,E),be&&Ur(E,null,T,\"unmounted\")},M)},_e=E=>{const{type:T,el:M,anchor:W,transition:G}=E;if(T===nt){qe(M,W);return}if(T===Gi){w(E);return}const q=()=>{o(M),G&&!G.persisted&&G.afterLeave&&G.afterLeave()};if(E.shapeFlag&1&&G&&!G.persisted){const{leave:se,delayLeave:ne}=G,te=()=>se(M,q);ne?ne(E.el,q,te):te()}else q()},qe=(E,T)=>{let M;for(;E!==T;)M=d(E),o(E),E=M;o(T)},ze=(E,T,M)=>{const{bum:W,scope:G,job:q,subTree:se,um:ne,m:te,a:X}=E;_f(te),_f(X),W&&qi(W),G.stop(),q&&(q.flags|=8,Ce(se,E,T,M)),ne&&Rt(ne,T),Rt(()=>{E.isUnmounted=!0},T)},De=(E,T,M,W=!1,G=!1,q=0)=>{for(let se=q;se<E.length;se++)Ce(E[se],T,M,W,G)},B=E=>{if(E.shapeFlag&6)return B(E.component.subTree);if(E.shapeFlag&128)return E.suspense.next();const T=d(E.anchor||E.el),M=T&&T[Jh];return M?d(M):T};let K=!1;const J=(E,T,M)=>{let W;E==null?T._vnode&&(Ce(T._vnode,null,null,!0),W=T._vnode.component):h(T._vnode||null,E,T,null,null,null,M),T._vnode=E,K||(K=!0,af(W),qh(),K=!1)},oe={p:h,um:Ce,m:Re,r:_e,mt:Y,mc:N,pc:Q,pbc:x,n:B,o:e};return{render:J,hydrate:void 0,createApp:s0(J)}}function Tl({type:e,props:t},n){return n===\"svg\"&&e===\"foreignObject\"||n===\"mathml\"&&e===\"annotation-xml\"&&t&&t.encoding&&t.encoding.includes(\"html\")?void 0:n}function Kr({effect:e,job:t},n){n?(e.flags|=32,t.flags|=4):(e.flags&=-33,t.flags&=-5)}function S0(e,t){return(!e||e&&!e.pendingBranch)&&t&&!t.persisted}function cc(e,t,n=!1){const r=e.children,o=t.children;if(pe(r)&&pe(o))for(let s=0;s<r.length;s++){const i=r[s];let a=o[s];a.shapeFlag&1&&!a.dynamicChildren&&((a.patchFlag<=0||a.patchFlag===32)&&(a=o[s]=tr(o[s]),a.el=i.el),!n&&a.patchFlag!==-2&&cc(i,a)),a.type===Zo&&(a.patchFlag===-1&&(a=o[s]=tr(a)),a.el=i.el),a.type===_t&&!a.el&&(a.el=i.el)}}function E0(e){const t=e.slice(),n=[0];let r,o,s,i,a;const l=e.length;for(r=0;r<l;r++){const u=e[r];if(u!==0){if(o=n[n.length-1],e[o]<u){t[r]=o,n.push(r);continue}for(s=0,i=n.length-1;s<i;)a=s+i>>1,e[n[a]]<u?s=a+1:i=a;u<e[n[s]]&&(s>0&&(t[r]=n[s-1]),n[s]=r)}}for(s=n.length,i=n[s-1];s-- >0;)n[s]=i,i=t[i];return n}function Cv(e){const t=e.subTree.component;if(t)return t.asyncDep&&!t.asyncResolved?t:Cv(t)}function _f(e){if(e)for(let t=0;t<e.length;t++)e[t].flags|=8}function Tv(e){if(e.placeholder)return e.placeholder;const t=e.component;return t?Tv(t.subTree):null}const Ov=e=>e.__isSuspense;function _0(e,t){t&&t.pendingBranch?pe(e)?t.effects.push(...e):t.effects.push(e):Kh(e)}const nt=Symbol.for(\"v-fgt\"),Zo=Symbol.for(\"v-txt\"),_t=Symbol.for(\"v-cmt\"),Gi=Symbol.for(\"v-stc\"),Os=[];let Xt=null;function L(e=!1){Os.push(Xt=e?null:[])}function C0(){Os.pop(),Xt=Os[Os.length-1]||null}let zs=1;function pa(e,t=!1){zs+=e,e<0&&Xt&&t&&(Xt.hasOnce=!0)}function Av(e){return e.dynamicChildren=zs>0?Xt||Mo:null,C0(),zs>0&&Xt&&Xt.push(e),e}function ee(e,t,n,r,o,s){return Av(le(e,t,n,r,o,s,!0))}function fe(e,t,n,r,o){return Av(re(e,t,n,r,o,!0))}function cn(e){return e?e.__v_isVNode===!0:!1}function Xr(e,t){return e.type===t.type&&e.key===t.key}const Rv=({key:e})=>e??null,Yi=({ref:e,ref_key:t,ref_for:n})=>(typeof e==\"number\"&&(e=\"\"+e),e!=null?Ae(e)||Ue(e)||ve(e)?{i:Ct,r:e,k:t,f:!!n}:e:null);function le(e,t=null,n=null,r=0,o=null,s=e===nt?0:1,i=!1,a=!1){const l={__v_isVNode:!0,__v_skip:!0,type:e,props:t,key:t&&Rv(t),ref:t&&Yi(t),scopeId:Gh,slotScopeIds:null,children:n,component:null,suspense:null,ssContent:null,ssFallback:null,dirs:null,transition:null,el:null,anchor:null,target:null,targetStart:null,targetAnchor:null,staticCount:0,shapeFlag:s,patchFlag:r,dynamicProps:o,dynamicChildren:null,appContext:null,ctx:Ct};return a?(fc(l,n),s&128&&e.normalize(l)):n&&(l.shapeFlag|=Ae(n)?8:16),zs>0&&!i&&Xt&&(l.patchFlag>0||s&6)&&l.patchFlag!==32&&Xt.push(l),l}const re=T0;function T0(e,t=null,n=null,r=0,o=null,s=!1){if((!e||e===av)&&(e=_t),cn(e)){const a=fr(e,t,!0);return n&&fc(a,n),zs>0&&!s&&Xt&&(a.shapeFlag&6?Xt[Xt.indexOf(e)]=a:Xt.push(a)),a.patchFlag=-2,a}if(M0(e)&&(e=e.__vccOpts),t){t=O0(t);let{class:a,style:l}=t;a&&!Ae(a)&&(t.class=U(a)),Oe(l)&&(ja(l)&&!pe(l)&&(l=vt({},l)),t.style=ot(l))}const i=Ae(e)?1:Ov(e)?128:Xh(e)?64:Oe(e)?4:ve(e)?2:0;return le(e,t,n,r,o,i,s,!0)}function O0(e){return e?ja(e)||bv(e)?vt({},e):e:null}function fr(e,t,n=!1,r=!1){const{props:o,ref:s,patchFlag:i,children:a,transition:l}=e,u=t?En(o||{},t):o,c={__v_isVNode:!0,__v_skip:!0,type:e.type,props:u,key:u&&Rv(u),ref:t&&t.ref?n&&s?pe(s)?s.concat(Yi(t)):[s,Yi(t)]:Yi(t):s,scopeId:e.scopeId,slotScopeIds:e.slotScopeIds,children:a,target:e.target,targetStart:e.targetStart,targetAnchor:e.targetAnchor,staticCount:e.staticCount,shapeFlag:e.shapeFlag,patchFlag:t&&e.type!==nt?i===-1?16:i|16:i,dynamicProps:e.dynamicProps,dynamicChildren:e.dynamicChildren,appContext:e.appContext,dirs:e.dirs,transition:l,component:e.component,suspense:e.suspense,ssContent:e.ssContent&&fr(e.ssContent),ssFallback:e.ssFallback&&fr(e.ssFallback),placeholder:e.placeholder,el:e.el,anchor:e.anchor,ctx:e.ctx,ce:e.ce};return l&&r&&uo(c,l.clone(c)),c}function Un(e=\" \",t=0){return re(Zo,null,e,t)}function ae(e=\"\",t=!1){return t?(L(),fe(_t,null,e)):re(_t,null,e)}function Vn(e){return e==null||typeof e==\"boolean\"?re(_t):pe(e)?re(nt,null,e.slice()):cn(e)?tr(e):re(Zo,null,String(e))}function tr(e){return e.el===null&&e.patchFlag!==-1||e.memo?e:fr(e)}function fc(e,t){let n=0;const{shapeFlag:r}=e;if(t==null)t=null;else if(pe(t))n=16;else if(typeof t==\"object\")if(r&65){const o=t.default;o&&(o._c&&(o._d=!1),fc(e,o()),o._c&&(o._d=!0));return}else{n=32;const o=t._;!o&&!bv(t)?t._ctx=Ct:o===3&&Ct&&(Ct.slots._===1?t._=1:(t._=2,e.patchFlag|=1024))}else ve(t)?(t={default:t,_ctx:Ct},n=32):(t=String(t),r&64?(n=16,t=[Un(t)]):n=8);e.children=t,e.shapeFlag|=n}function En(...e){const t={};for(let n=0;n<e.length;n++){const r=e[n];for(const o in r)if(o===\"class\")t.class!==r.class&&(t.class=U([t.class,r.class]));else if(o===\"style\")t.style=ot([t.style,r.style]);else if(Na(o)){const s=t[o],i=r[o];i&&s!==i&&!(pe(s)&&s.includes(i))&&(t[o]=s?[].concat(s,i):i)}else o!==\"\"&&(t[o]=r[o])}return t}function Mn(e,t,n,r=null){An(e,t,7,[n,r])}const A0=pv();let R0=0;function x0(e,t,n){const r=e.type,o=(t?t.appContext:e.appContext)||A0,s={uid:R0++,vnode:e,type:r,parent:t,appContext:o,root:null,next:null,subTree:null,effect:null,update:null,job:null,scope:new Ch(!0),render:null,proxy:null,exposed:null,exposeProxy:null,withProxy:null,provides:t?t.provides:Object.create(o.provides),ids:t?t.ids:[\"\",0,0],accessCache:null,renderCache:[],components:null,directives:null,propsOptions:wv(r,o),emitsOptions:hv(r,o),emit:null,emitted:null,propsDefaults:Ye,inheritAttrs:r.inheritAttrs,ctx:Ye,data:Ye,props:Ye,attrs:Ye,slots:Ye,refs:Ye,setupState:Ye,setupContext:null,suspense:n,suspenseId:n?n.pendingId:0,asyncDep:null,asyncResolved:!1,isMounted:!1,isUnmounted:!1,isDeactivated:!1,bc:null,c:null,bm:null,m:null,bu:null,u:null,um:null,bum:null,da:null,a:null,rtg:null,rtc:null,ec:null,sp:null};return s.ctx={_:s},s.root=t?t.root:s,s.emit=a0.bind(null,s),e.ce&&e.ce(s),s}let Pt=null;const Ge=()=>Pt||Ct;let ha,uu;{const e=ka(),t=(n,r)=>{let o;return(o=e[n])||(o=e[n]=[]),o.push(r),s=>{o.length>1?o.forEach(i=>i(s)):o[0](s)}};ha=t(\"__VUE_INSTANCE_SETTERS__\",n=>Pt=n),uu=t(\"__VUE_SSR_SETTERS__\",n=>Hs=n)}const li=e=>{const t=Pt;return ha(e),e.scope.on(),()=>{e.scope.off(),ha(t)}},Cf=()=>{Pt&&Pt.scope.off(),ha(null)};function xv(e){return e.vnode.shapeFlag&4}let Hs=!1;function I0(e,t=!1,n=!1){t&&uu(t);const{props:r,children:o}=e.vnode,s=xv(e);p0(e,r,s,t),g0(e,o,n||t);const i=s?P0(e,t):void 0;return t&&uu(!1),i}function P0(e,t){const n=e.type;e.accessCache=Object.create(null),e.proxy=new Proxy(e.ctx,Xy);const{setup:r}=n;if(r){lr();const o=e.setupContext=r.length>1?Pv(e):null,s=li(e),i=ai(r,e,0,[e.props,o]),a=ia(i);if(ur(),s(),(a||e.sp)&&!Fo(e)&&ov(e),a){if(i.then(Cf,Cf),t)return i.then(l=>{Tf(e,l)}).catch(l=>{za(l,e,0)});e.asyncDep=i}else Tf(e,i)}else Iv(e)}function Tf(e,t,n){ve(t)?e.type.__ssrInlineRender?e.ssrRender=t:e.render=t:Oe(t)&&(e.setupState=jh(t)),Iv(e)}function Iv(e,t,n){const r=e.type;e.render||(e.render=r.render||ht);{const o=li(e);lr();try{Qy(e)}finally{ur(),o()}}}const N0={get(e,t){return It(e,\"get\",\"\"),e[t]}};function Pv(e){const t=n=>{e.exposed=n||{}};return{attrs:new Proxy(e.attrs,N0),slots:e.slots,emit:e.emit,expose:t}}function Ga(e){return e.exposed?e.exposeProxy||(e.exposeProxy=new Proxy(jh(Ds(e.exposed)),{get(t,n){if(n in t)return t[n];if(n in Ts)return Ts[n](e)},has(t,n){return n in t||n in Ts}})):e.proxy}function L0(e,t=!0){return ve(e)?e.displayName||e.name:e.name||t&&e.__name}function M0(e){return ve(e)&&\"__vccOpts\"in e}const C=(e,t)=>Py(e,t,Hs);function or(e,t,n){try{pa(-1);const r=arguments.length;return r===2?Oe(t)&&!pe(t)?cn(t)?re(e,null,[t]):re(e,t):re(e,null,t):(r>3?n=Array.prototype.slice.call(arguments,2):r===3&&cn(n)&&(n=[n]),re(e,t,n))}finally{pa(1)}}const $0=\"3.5.30\",k0=ht;/**\n* @vue/runtime-dom v3.5.30\n* (c) 2018-present Yuxi (Evan) You and Vue contributors\n* @license MIT\n**/let cu;const Of=typeof window<\"u\"&&window.trustedTypes;if(Of)try{cu=Of.createPolicy(\"vue\",{createHTML:e=>e})}catch{}const Nv=cu?e=>cu.createHTML(e):e=>e,F0=\"http://www.w3.org/2000/svg\",B0=\"http://www.w3.org/1998/Math/MathML\",Zn=typeof document<\"u\"?document:null,Af=Zn&&Zn.createElement(\"template\"),D0={insert:(e,t,n)=>{t.insertBefore(e,n||null)},remove:e=>{const t=e.parentNode;t&&t.removeChild(e)},createElement:(e,t,n,r)=>{const o=t===\"svg\"?Zn.createElementNS(F0,e):t===\"mathml\"?Zn.createElementNS(B0,e):n?Zn.createElement(e,{is:n}):Zn.createElement(e);return e===\"select\"&&r&&r.multiple!=null&&o.setAttribute(\"multiple\",r.multiple),o},createText:e=>Zn.createTextNode(e),createComment:e=>Zn.createComment(e),setText:(e,t)=>{e.nodeValue=t},setElementText:(e,t)=>{e.textContent=t},parentNode:e=>e.parentNode,nextSibling:e=>e.nextSibling,querySelector:e=>Zn.querySelector(e),setScopeId(e,t){e.setAttribute(t,\"\")},insertStaticContent(e,t,n,r,o,s){const i=n?n.previousSibling:t.lastChild;if(o&&(o===s||o.nextSibling))for(;t.insertBefore(o.cloneNode(!0),n),!(o===s||!(o=o.nextSibling)););else{Af.innerHTML=Nv(r===\"svg\"?`<svg>${e}</svg>`:r===\"mathml\"?`<math>${e}</math>`:e);const a=Af.content;if(r===\"svg\"||r===\"mathml\"){const l=a.firstChild;for(;l.firstChild;)a.appendChild(l.firstChild);a.removeChild(l)}t.insertBefore(a,n)}return[i?i.nextSibling:t.firstChild,n?n.previousSibling:t.lastChild]}},Sr=\"transition\",cs=\"animation\",Vo=Symbol(\"_vtc\"),Lv={name:String,type:String,css:{type:Boolean,default:!0},duration:[String,Number,Object],enterFromClass:String,enterActiveClass:String,enterToClass:String,appearFromClass:String,appearActiveClass:String,appearToClass:String,leaveFromClass:String,leaveActiveClass:String,leaveToClass:String},Mv=vt({},ev,Lv),V0=e=>(e.displayName=\"Transition\",e.props=Mv,e),Fr=V0((e,{slots:t})=>or(Hy,$v(e),t)),qr=(e,t=[])=>{pe(e)?e.forEach(n=>n(...t)):e&&e(...t)},Rf=e=>e?pe(e)?e.some(t=>t.length>1):e.length>1:!1;function $v(e){const t={};for(const $ in e)$ in Lv||(t[$]=e[$]);if(e.css===!1)return t;const{name:n=\"v\",type:r,duration:o,enterFromClass:s=`${n}-enter-from`,enterActiveClass:i=`${n}-enter-active`,enterToClass:a=`${n}-enter-to`,appearFromClass:l=s,appearActiveClass:u=i,appearToClass:c=a,leaveFromClass:f=`${n}-leave-from`,leaveActiveClass:d=`${n}-leave-active`,leaveToClass:v=`${n}-leave-to`}=e,p=j0(o),h=p&&p[0],b=p&&p[1],{onBeforeEnter:m,onEnter:S,onEnterCancelled:_,onLeave:w,onLeaveCancelled:y,onBeforeAppear:A=m,onAppear:R=S,onAppearCancelled:N=_}=t,I=($,F,Y,P)=>{$._enterCancelled=P,Cr($,F?c:a),Cr($,F?u:i),Y&&Y()},x=($,F)=>{$._isLeaving=!1,Cr($,f),Cr($,v),Cr($,d),F&&F()},k=$=>(F,Y)=>{const P=$?R:S,O=()=>I(F,$,Y);qr(P,[F,O]),xf(()=>{Cr(F,$?l:s),$n(F,$?c:a),Rf(P)||If(F,r,h,O)})};return vt(t,{onBeforeEnter($){qr(m,[$]),$n($,s),$n($,i)},onBeforeAppear($){qr(A,[$]),$n($,l),$n($,u)},onEnter:k(!1),onAppear:k(!0),onLeave($,F){$._isLeaving=!0;const Y=()=>x($,F);$n($,f),$._enterCancelled?($n($,d),fu($)):(fu($),$n($,d)),xf(()=>{$._isLeaving&&(Cr($,f),$n($,v),Rf(w)||If($,r,b,Y))}),qr(w,[$,Y])},onEnterCancelled($){I($,!1,void 0,!0),qr(_,[$])},onAppearCancelled($){I($,!0,void 0,!0),qr(N,[$])},onLeaveCancelled($){x($),qr(y,[$])}})}function j0(e){if(e==null)return null;if(Oe(e))return[Ol(e.enter),Ol(e.leave)];{const t=Ol(e);return[t,t]}}function Ol(e){return Jb(e)}function $n(e,t){t.split(/\\s+/).forEach(n=>n&&e.classList.add(n)),(e[Vo]||(e[Vo]=new Set)).add(t)}function Cr(e,t){t.split(/\\s+/).forEach(r=>r&&e.classList.remove(r));const n=e[Vo];n&&(n.delete(t),n.size||(e[Vo]=void 0))}function xf(e){requestAnimationFrame(()=>{requestAnimationFrame(e)})}let z0=0;function If(e,t,n,r){const o=e._endId=++z0,s=()=>{o===e._endId&&r()};if(n!=null)return setTimeout(s,n);const{type:i,timeout:a,propCount:l}=kv(e,t);if(!i)return r();const u=i+\"end\";let c=0;const f=()=>{e.removeEventListener(u,d),s()},d=v=>{v.target===e&&++c>=l&&f()};setTimeout(()=>{c<l&&f()},a+1),e.addEventListener(u,d)}function kv(e,t){const n=window.getComputedStyle(e),r=p=>(n[p]||\"\").split(\", \"),o=r(`${Sr}Delay`),s=r(`${Sr}Duration`),i=Pf(o,s),a=r(`${cs}Delay`),l=r(`${cs}Duration`),u=Pf(a,l);let c=null,f=0,d=0;t===Sr?i>0&&(c=Sr,f=i,d=s.length):t===cs?u>0&&(c=cs,f=u,d=l.length):(f=Math.max(i,u),c=f>0?i>u?Sr:cs:null,d=c?c===Sr?s.length:l.length:0);const v=c===Sr&&/\\b(?:transform|all)(?:,|$)/.test(r(`${Sr}Property`).toString());return{type:c,timeout:f,propCount:d,hasTransform:v}}function Pf(e,t){for(;e.length<t.length;)e=e.concat(e);return Math.max(...t.map((n,r)=>Nf(n)+Nf(e[r])))}function Nf(e){return e===\"auto\"?0:Number(e.slice(0,-1).replace(\",\",\".\"))*1e3}function fu(e){return(e?e.ownerDocument:document).body.offsetHeight}function H0(e,t,n){const r=e[Vo];r&&(t=(t?[t,...r]:[...r]).join(\" \")),t==null?e.removeAttribute(\"class\"):n?e.setAttribute(\"class\",t):e.className=t}const va=Symbol(\"_vod\"),Fv=Symbol(\"_vsh\"),en={name:\"show\",beforeMount(e,{value:t},{transition:n}){e[va]=e.style.display===\"none\"?\"\":e.style.display,n&&t?n.beforeEnter(e):fs(e,t)},mounted(e,{value:t},{transition:n}){n&&t&&n.enter(e)},updated(e,{value:t,oldValue:n},{transition:r}){!t!=!n&&(r?t?(r.beforeEnter(e),fs(e,!0),r.enter(e)):r.leave(e,()=>{fs(e,!1)}):fs(e,t))},beforeUnmount(e,{value:t}){fs(e,t)}};function fs(e,t){e.style.display=t?e[va]:\"none\",e[Fv]=!t}const Bv=Symbol(\"\");function bN(e){const t=Ge();if(!t)return;const n=t.ut=(o=e(t.proxy))=>{Array.from(document.querySelectorAll(`[data-v-owner=\"${t.uid}\"]`)).forEach(s=>ma(s,o))},r=()=>{const o=e(t.proxy);t.ce?ma(t.ce,o):du(t.subTree,o),n(o)};iv(()=>{Kh(r)}),Ke(()=>{he(r,ht,{flush:\"post\"});const o=new MutationObserver(r);o.observe(t.subTree.el.parentNode,{childList:!0}),kr(()=>o.disconnect())})}function du(e,t){if(e.shapeFlag&128){const n=e.suspense;e=n.activeBranch,n.pendingBranch&&!n.isHydrating&&n.effects.push(()=>{du(n.activeBranch,t)})}for(;e.component;)e=e.component.subTree;if(e.shapeFlag&1&&e.el)ma(e.el,t);else if(e.type===nt)e.children.forEach(n=>du(n,t));else if(e.type===Gi){let{el:n,anchor:r}=e;for(;n&&(ma(n,t),n!==r);)n=n.nextSibling}}function ma(e,t){if(e.nodeType===1){const n=e.style;let r=\"\";for(const o in t){const s=oy(t[o]);n.setProperty(`--${o}`,s),r+=`--${o}: ${s};`}n[Bv]=r}}const U0=/(?:^|;)\\s*display\\s*:/;function K0(e,t,n){const r=e.style,o=Ae(n);let s=!1;if(n&&!o){if(t)if(Ae(t))for(const i of t.split(\";\")){const a=i.slice(0,i.indexOf(\":\")).trim();n[a]==null&&Ji(r,a,\"\")}else for(const i in t)n[i]==null&&Ji(r,i,\"\");for(const i in n)i===\"display\"&&(s=!0),Ji(r,i,n[i])}else if(o){if(t!==n){const i=r[Bv];i&&(n+=\";\"+i),r.cssText=n,s=U0.test(n)}}else t&&e.removeAttribute(\"style\");va in e&&(e[va]=s?r.display:\"\",e[Fv]&&(r.display=\"none\"))}const Lf=/\\s*!important$/;function Ji(e,t,n){if(pe(n))n.forEach(r=>Ji(e,t,r));else if(n==null&&(n=\"\"),t.startsWith(\"--\"))e.setProperty(t,n);else{const r=q0(e,t);Lf.test(n)?e.setProperty(hr(r),n.replace(Lf,\"\"),\"important\"):e[r]=n}}const Mf=[\"Webkit\",\"Moz\",\"ms\"],Al={};function q0(e,t){const n=Al[t];if(n)return n;let r=Mt(t);if(r!==\"filter\"&&r in e)return Al[t]=r;r=si(r);for(let o=0;o<Mf.length;o++){const s=Mf[o]+r;if(s in e)return Al[t]=s}return t}const $f=\"http://www.w3.org/1999/xlink\";function kf(e,t,n,r,o,s=ny(t)){r&&t.startsWith(\"xlink:\")?n==null?e.removeAttributeNS($f,t.slice(6,t.length)):e.setAttributeNS($f,t,n):n==null||s&&!wh(n)?e.removeAttribute(t):e.setAttribute(t,s?\"\":Tn(n)?String(n):n)}function Ff(e,t,n,r,o){if(t===\"innerHTML\"||t===\"textContent\"){n!=null&&(e[t]=t===\"innerHTML\"?Nv(n):n);return}const s=e.tagName;if(t===\"value\"&&s!==\"PROGRESS\"&&!s.includes(\"-\")){const a=s===\"OPTION\"?e.getAttribute(\"value\")||\"\":e.value,l=n==null?e.type===\"checkbox\"?\"on\":\"\":String(n);(a!==l||!(\"_value\"in e))&&(e.value=l),n==null&&e.removeAttribute(t),e._value=n;return}let i=!1;if(n===\"\"||n==null){const a=typeof e[t];a===\"boolean\"?n=wh(n):n==null&&a===\"string\"?(n=\"\",i=!0):a===\"number\"&&(n=0,i=!0)}try{e[t]=n}catch{}i&&e.removeAttribute(o||t)}function Zr(e,t,n,r){e.addEventListener(t,n,r)}function W0(e,t,n,r){e.removeEventListener(t,n,r)}const Bf=Symbol(\"_vei\");function G0(e,t,n,r,o=null){const s=e[Bf]||(e[Bf]={}),i=s[t];if(r&&i)i.value=r;else{const[a,l]=Y0(t);if(r){const u=s[t]=Z0(r,o);Zr(e,a,u,l)}else i&&(W0(e,a,i,l),s[t]=void 0)}}const Df=/(?:Once|Passive|Capture)$/;function Y0(e){let t;if(Df.test(e)){t={};let r;for(;r=e.match(Df);)e=e.slice(0,e.length-r[0].length),t[r[0].toLowerCase()]=!0}return[e[2]===\":\"?e.slice(3):hr(e.slice(2)),t]}let Rl=0;const J0=Promise.resolve(),X0=()=>Rl||(J0.then(()=>Rl=0),Rl=Date.now());function Z0(e,t){const n=r=>{if(!r._vts)r._vts=Date.now();else if(r._vts<=n.attached)return;An(Q0(r,n.value),t,5,[r])};return n.value=e,n.attached=X0(),n}function Q0(e,t){if(pe(t)){const n=e.stopImmediatePropagation;return e.stopImmediatePropagation=()=>{n.call(e),e._stopped=!0},t.map(r=>o=>!o._stopped&&r&&r(o))}else return t}const Vf=e=>e.charCodeAt(0)===111&&e.charCodeAt(1)===110&&e.charCodeAt(2)>96&&e.charCodeAt(2)<123,ew=(e,t,n,r,o,s)=>{const i=o===\"svg\";t===\"class\"?H0(e,r,i):t===\"style\"?K0(e,n,r):Na(t)?qu(t)||G0(e,t,n,r,s):(t[0]===\".\"?(t=t.slice(1),!0):t[0]===\"^\"?(t=t.slice(1),!1):tw(e,t,r,i))?(Ff(e,t,r),!e.tagName.includes(\"-\")&&(t===\"value\"||t===\"checked\"||t===\"selected\")&&kf(e,t,r,i,s,t!==\"value\")):e._isVueCE&&(nw(e,t)||e._def.__asyncLoader&&(/[A-Z]/.test(t)||!Ae(r)))?Ff(e,Mt(t),r,s,t):(t===\"true-value\"?e._trueValue=r:t===\"false-value\"&&(e._falseValue=r),kf(e,t,r,i))};function tw(e,t,n,r){if(r)return!!(t===\"innerHTML\"||t===\"textContent\"||t in e&&Vf(t)&&ve(n));if(t===\"spellcheck\"||t===\"draggable\"||t===\"translate\"||t===\"autocorrect\"||t===\"sandbox\"&&e.tagName===\"IFRAME\"||t===\"form\"||t===\"list\"&&e.tagName===\"INPUT\"||t===\"type\"&&e.tagName===\"TEXTAREA\")return!1;if(t===\"width\"||t===\"height\"){const o=e.tagName;if(o===\"IMG\"||o===\"VIDEO\"||o===\"CANVAS\"||o===\"SOURCE\")return!1}return Vf(t)&&Ae(n)?!1:t in e}function nw(e,t){const n=e._def.props;if(!n)return!1;const r=Mt(t);return Array.isArray(n)?n.some(o=>Mt(o)===r):Object.keys(n).some(o=>Mt(o)===r)}const Dv=new WeakMap,Vv=new WeakMap,ga=Symbol(\"_moveCb\"),jf=Symbol(\"_enterCb\"),rw=e=>(delete e.props.mode,e),ow=rw({name:\"TransitionGroup\",props:vt({},Mv,{tag:String,moveClass:String}),setup(e,{slots:t}){const n=Ge(),r=Qh();let o,s;return mo(()=>{if(!o.length)return;const i=e.moveClass||`${e.name||\"v\"}-move`;if(!uw(o[0].el,n.vnode.el,i)){o=[];return}o.forEach(iw),o.forEach(aw);const a=o.filter(lw);fu(n.vnode.el),a.forEach(l=>{const u=l.el,c=u.style;$n(u,i),c.transform=c.webkitTransform=c.transitionDuration=\"\";const f=u[ga]=d=>{d&&d.target!==u||(!d||d.propertyName.endsWith(\"transform\"))&&(u.removeEventListener(\"transitionend\",f),u[ga]=null,Cr(u,i))};u.addEventListener(\"transitionend\",f)}),o=[]}),()=>{const i=Me(e),a=$v(i);let l=i.tag||nt;if(o=[],s)for(let u=0;u<s.length;u++){const c=s[u];c.el&&c.el instanceof Element&&(o.push(c),uo(c,js(c,a,r,n)),Dv.set(c,jv(c.el)))}s=t.default?rc(t.default()):[];for(let u=0;u<s.length;u++){const c=s[u];c.key!=null&&uo(c,js(c,a,r,n))}return re(l,null,s)}}}),sw=ow;function iw(e){const t=e.el;t[ga]&&t[ga](),t[jf]&&t[jf]()}function aw(e){Vv.set(e,jv(e.el))}function lw(e){const t=Dv.get(e),n=Vv.get(e),r=t.left-n.left,o=t.top-n.top;if(r||o){const s=e.el,i=s.style,a=s.getBoundingClientRect();let l=1,u=1;return s.offsetWidth&&(l=a.width/s.offsetWidth),s.offsetHeight&&(u=a.height/s.offsetHeight),(!Number.isFinite(l)||l===0)&&(l=1),(!Number.isFinite(u)||u===0)&&(u=1),Math.abs(l-1)<.01&&(l=1),Math.abs(u-1)<.01&&(u=1),i.transform=i.webkitTransform=`translate(${r/l}px,${o/u}px)`,i.transitionDuration=\"0s\",e}}function jv(e){const t=e.getBoundingClientRect();return{left:t.left,top:t.top}}function uw(e,t,n){const r=e.cloneNode(),o=e[Vo];o&&o.forEach(a=>{a.split(/\\s+/).forEach(l=>l&&r.classList.remove(l))}),n.split(/\\s+/).forEach(a=>a&&r.classList.add(a)),r.style.display=\"none\";const s=t.nodeType===1?t:t.parentNode;s.appendChild(r);const{hasTransform:i}=kv(r);return s.removeChild(r),i}const ba=e=>{const t=e.props[\"onUpdate:modelValue\"]||!1;return pe(t)?n=>qi(t,n):t};function cw(e){e.target.composing=!0}function zf(e){const t=e.target;t.composing&&(t.composing=!1,t.dispatchEvent(new Event(\"input\")))}const Bo=Symbol(\"_assign\");function Hf(e,t,n){return t&&(e=e.trim()),n&&(e=Gu(e)),e}const fw={created(e,{modifiers:{lazy:t,trim:n,number:r}},o){e[Bo]=ba(o);const s=r||o.props&&o.props.type===\"number\";Zr(e,t?\"change\":\"input\",i=>{i.target.composing||e[Bo](Hf(e.value,n,s))}),(n||s)&&Zr(e,\"change\",()=>{e.value=Hf(e.value,n,s)}),t||(Zr(e,\"compositionstart\",cw),Zr(e,\"compositionend\",zf),Zr(e,\"change\",zf))},mounted(e,{value:t}){e.value=t??\"\"},beforeUpdate(e,{value:t,oldValue:n,modifiers:{lazy:r,trim:o,number:s}},i){if(e[Bo]=ba(i),e.composing)return;const a=(s||e.type===\"number\")&&!/^0\\d/.test(e.value)?Gu(e.value):e.value,l=t??\"\";a!==l&&(document.activeElement===e&&e.type!==\"range\"&&(r&&t===n||o&&e.value.trim()===l)||(e.value=l))}},ya={deep:!0,created(e,t,n){e[Bo]=ba(n),Zr(e,\"change\",()=>{const r=e._modelValue,o=dw(e),s=e.checked,i=e[Bo];if(pe(r)){const a=Sh(r,o),l=a!==-1;if(s&&!l)i(r.concat(o));else if(!s&&l){const u=[...r];u.splice(a,1),i(u)}}else if(La(r)){const a=new Set(r);s?a.add(o):a.delete(o),i(a)}else i(zv(e,s))})},mounted:Uf,beforeUpdate(e,t,n){e[Bo]=ba(n),Uf(e,t,n)}};function Uf(e,{value:t,oldValue:n},r){e._modelValue=t;let o;if(pe(t))o=Sh(t,r.props.value)>-1;else if(La(t))o=t.has(r.props.value);else{if(t===n)return;o=ii(t,zv(e,!0))}e.checked!==o&&(e.checked=o)}function dw(e){return\"_value\"in e?e._value:e.value}function zv(e,t){const n=t?\"_trueValue\":\"_falseValue\";return n in e?e[n]:t}const pw=[\"ctrl\",\"shift\",\"alt\",\"meta\"],hw={stop:e=>e.stopPropagation(),prevent:e=>e.preventDefault(),self:e=>e.target!==e.currentTarget,ctrl:e=>!e.ctrlKey,shift:e=>!e.shiftKey,alt:e=>!e.altKey,meta:e=>!e.metaKey,left:e=>\"button\"in e&&e.button!==0,middle:e=>\"button\"in e&&e.button!==1,right:e=>\"button\"in e&&e.button!==2,exact:(e,t)=>pw.some(n=>e[`${n}Key`]&&!t.includes(n))},tt=(e,t)=>{if(!e)return e;const n=e._withMods||(e._withMods={}),r=t.join(\".\");return n[r]||(n[r]=(o,...s)=>{for(let i=0;i<t.length;i++){const a=hw[t[i]];if(a&&a(o,t))return}return e(o,...s)})},vw={esc:\"escape\",space:\" \",up:\"arrow-up\",left:\"arrow-left\",right:\"arrow-right\",down:\"arrow-down\",delete:\"backspace\"},Vt=(e,t)=>{const n=e._withKeys||(e._withKeys={}),r=t.join(\".\");return n[r]||(n[r]=o=>{if(!(\"key\"in o))return;const s=hr(o.key);if(t.some(i=>i===s||vw[i]===s))return e(o)})},mw=vt({patchProp:ew},D0);let Kf;function Hv(){return Kf||(Kf=y0(mw))}const wa=(...e)=>{Hv().render(...e)},gw=(...e)=>{const t=Hv().createApp(...e),{mount:n}=t;return t.mount=r=>{const o=yw(r);if(!o)return;const s=t._component;!ve(s)&&!s.render&&!s.template&&(s.template=o.innerHTML),o.nodeType===1&&(o.textContent=\"\");const i=n(o,!1,bw(o));return o instanceof Element&&(o.removeAttribute(\"v-cloak\"),o.setAttribute(\"data-v-app\",\"\")),i},t};function bw(e){if(e instanceof SVGElement)return\"svg\";if(typeof MathMLElement==\"function\"&&e instanceof MathMLElement)return\"mathml\"}function yw(e){return Ae(e)?document.querySelector(e):e}/*!\n * vue-router v4.6.4\n * (c) 2025 Eduardo San Martin Morote\n * @license MIT\n */const Io=typeof document<\"u\";function Uv(e){return typeof e==\"object\"||\"displayName\"in e||\"props\"in e||\"__vccOpts\"in e}function ww(e){return e.__esModule||e[Symbol.toStringTag]===\"Module\"||e.default&&Uv(e.default)}const We=Object.assign;function xl(e,t){const n={};for(const r in t){const o=t[r];n[r]=Rn(o)?o.map(e):e(o)}return n}const As=()=>{},Rn=Array.isArray;function qf(e,t){const n={};for(const r in e)n[r]=r in t?t[r]:e[r];return n}const Kv=/#/g,Sw=/&/g,Ew=/\\//g,_w=/=/g,Cw=/\\?/g,qv=/\\+/g,Tw=/%5B/g,Ow=/%5D/g,Wv=/%5E/g,Aw=/%60/g,Gv=/%7B/g,Rw=/%7C/g,Yv=/%7D/g,xw=/%20/g;function dc(e){return e==null?\"\":encodeURI(\"\"+e).replace(Rw,\"|\").replace(Tw,\"[\").replace(Ow,\"]\")}function Iw(e){return dc(e).replace(Gv,\"{\").replace(Yv,\"}\").replace(Wv,\"^\")}function pu(e){return dc(e).replace(qv,\"%2B\").replace(xw,\"+\").replace(Kv,\"%23\").replace(Sw,\"%26\").replace(Aw,\"`\").replace(Gv,\"{\").replace(Yv,\"}\").replace(Wv,\"^\")}function Pw(e){return pu(e).replace(_w,\"%3D\")}function Nw(e){return dc(e).replace(Kv,\"%23\").replace(Cw,\"%3F\")}function Lw(e){return Nw(e).replace(Ew,\"%2F\")}function Us(e){if(e==null)return null;try{return decodeURIComponent(\"\"+e)}catch{}return\"\"+e}const Mw=/\\/$/,$w=e=>e.replace(Mw,\"\");function Il(e,t,n=\"/\"){let r,o={},s=\"\",i=\"\";const a=t.indexOf(\"#\");let l=t.indexOf(\"?\");return l=a>=0&&l>a?-1:l,l>=0&&(r=t.slice(0,l),s=t.slice(l,a>0?a:t.length),o=e(s.slice(1))),a>=0&&(r=r||t.slice(0,a),i=t.slice(a,t.length)),r=Dw(r??t,n),{fullPath:r+s+i,path:r,query:o,hash:Us(i)}}function kw(e,t){const n=t.query?e(t.query):\"\";return t.path+(n&&\"?\")+n+(t.hash||\"\")}function Wf(e,t){return!t||!e.toLowerCase().startsWith(t.toLowerCase())?e:e.slice(t.length)||\"/\"}function Fw(e,t,n){const r=t.matched.length-1,o=n.matched.length-1;return r>-1&&r===o&&jo(t.matched[r],n.matched[o])&&Jv(t.params,n.params)&&e(t.query)===e(n.query)&&t.hash===n.hash}function jo(e,t){return(e.aliasOf||e)===(t.aliasOf||t)}function Jv(e,t){if(Object.keys(e).length!==Object.keys(t).length)return!1;for(var n in e)if(!Bw(e[n],t[n]))return!1;return!0}function Bw(e,t){return Rn(e)?Gf(e,t):Rn(t)?Gf(t,e):(e==null?void 0:e.valueOf())===(t==null?void 0:t.valueOf())}function Gf(e,t){return Rn(t)?e.length===t.length&&e.every((n,r)=>n===t[r]):e.length===1&&e[0]===t}function Dw(e,t){if(e.startsWith(\"/\"))return e;if(!e)return t;const n=t.split(\"/\"),r=e.split(\"/\"),o=r[r.length-1];(o===\"..\"||o===\".\")&&r.push(\"\");let s=n.length-1,i,a;for(i=0;i<r.length;i++)if(a=r[i],a!==\".\")if(a===\"..\")s>1&&s--;else break;return n.slice(0,s).join(\"/\")+\"/\"+r.slice(i).join(\"/\")}const Er={path:\"/\",name:void 0,params:{},query:{},hash:\"\",fullPath:\"/\",matched:[],meta:{},redirectedFrom:void 0};let hu=function(e){return e.pop=\"pop\",e.push=\"push\",e}({}),Pl=function(e){return e.back=\"back\",e.forward=\"forward\",e.unknown=\"\",e}({});function Vw(e){if(!e)if(Io){const t=document.querySelector(\"base\");e=t&&t.getAttribute(\"href\")||\"/\",e=e.replace(/^\\w+:\\/\\/[^\\/]+/,\"\")}else e=\"/\";return e[0]!==\"/\"&&e[0]!==\"#\"&&(e=\"/\"+e),$w(e)}const jw=/^[^#]+#/;function zw(e,t){return e.replace(jw,\"#\")+t}function Hw(e,t){const n=document.documentElement.getBoundingClientRect(),r=e.getBoundingClientRect();return{behavior:t.behavior,left:r.left-n.left-(t.left||0),top:r.top-n.top-(t.top||0)}}const Ya=()=>({left:window.scrollX,top:window.scrollY});function Uw(e){let t;if(\"el\"in e){const n=e.el,r=typeof n==\"string\"&&n.startsWith(\"#\"),o=typeof n==\"string\"?r?document.getElementById(n.slice(1)):document.querySelector(n):n;if(!o)return;t=Hw(o,e)}else t=e;\"scrollBehavior\"in document.documentElement.style?window.scrollTo(t):window.scrollTo(t.left!=null?t.left:window.scrollX,t.top!=null?t.top:window.scrollY)}function Yf(e,t){return(history.state?history.state.position-t:-1)+e}const vu=new Map;function Kw(e,t){vu.set(e,t)}function qw(e){const t=vu.get(e);return vu.delete(e),t}function Ww(e){return typeof e==\"string\"||e&&typeof e==\"object\"}function Xv(e){return typeof e==\"string\"||typeof e==\"symbol\"}let ut=function(e){return e[e.MATCHER_NOT_FOUND=1]=\"MATCHER_NOT_FOUND\",e[e.NAVIGATION_GUARD_REDIRECT=2]=\"NAVIGATION_GUARD_REDIRECT\",e[e.NAVIGATION_ABORTED=4]=\"NAVIGATION_ABORTED\",e[e.NAVIGATION_CANCELLED=8]=\"NAVIGATION_CANCELLED\",e[e.NAVIGATION_DUPLICATED=16]=\"NAVIGATION_DUPLICATED\",e}({});const Zv=Symbol(\"\");ut.MATCHER_NOT_FOUND+\"\",ut.NAVIGATION_GUARD_REDIRECT+\"\",ut.NAVIGATION_ABORTED+\"\",ut.NAVIGATION_CANCELLED+\"\",ut.NAVIGATION_DUPLICATED+\"\";function zo(e,t){return We(new Error,{type:e,[Zv]:!0},t)}function Jn(e,t){return e instanceof Error&&Zv in e&&(t==null||!!(e.type&t))}const Gw=[\"params\",\"query\",\"hash\"];function Yw(e){if(typeof e==\"string\")return e;if(e.path!=null)return e.path;const t={};for(const n of Gw)n in e&&(t[n]=e[n]);return JSON.stringify(t,null,2)}function Jw(e){const t={};if(e===\"\"||e===\"?\")return t;const n=(e[0]===\"?\"?e.slice(1):e).split(\"&\");for(let r=0;r<n.length;++r){const o=n[r].replace(qv,\" \"),s=o.indexOf(\"=\"),i=Us(s<0?o:o.slice(0,s)),a=s<0?null:Us(o.slice(s+1));if(i in t){let l=t[i];Rn(l)||(l=t[i]=[l]),l.push(a)}else t[i]=a}return t}function Jf(e){let t=\"\";for(let n in e){const r=e[n];if(n=Pw(n),r==null){r!==void 0&&(t+=(t.length?\"&\":\"\")+n);continue}(Rn(r)?r.map(o=>o&&pu(o)):[r&&pu(r)]).forEach(o=>{o!==void 0&&(t+=(t.length?\"&\":\"\")+n,o!=null&&(t+=\"=\"+o))})}return t}function Xw(e){const t={};for(const n in e){const r=e[n];r!==void 0&&(t[n]=Rn(r)?r.map(o=>o==null?null:\"\"+o):r==null?r:\"\"+r)}return t}const Qv=Symbol(\"\"),Xf=Symbol(\"\"),Ja=Symbol(\"\"),em=Symbol(\"\"),mu=Symbol(\"\");function ds(){let e=[];function t(r){return e.push(r),()=>{const o=e.indexOf(r);o>-1&&e.splice(o,1)}}function n(){e=[]}return{add:t,list:()=>e.slice(),reset:n}}function Zw(e,t,n){const r=()=>{e[t].delete(n)};kr(r),oc(r),Ka(()=>{e[t].add(n)}),e[t].add(n)}function yN(e){const t=Ee(Qv,{}).value;t&&Zw(t,\"leaveGuards\",e)}function Ir(e,t,n,r,o,s=i=>i()){const i=r&&(r.enterCallbacks[o]=r.enterCallbacks[o]||[]);return()=>new Promise((a,l)=>{const u=d=>{d===!1?l(zo(ut.NAVIGATION_ABORTED,{from:n,to:t})):d instanceof Error?l(d):Ww(d)?l(zo(ut.NAVIGATION_GUARD_REDIRECT,{from:t,to:d})):(i&&r.enterCallbacks[o]===i&&typeof d==\"function\"&&i.push(d),a())},c=s(()=>e.call(r&&r.instances[o],t,n,u));let f=Promise.resolve(c);e.length<3&&(f=f.then(u)),f.catch(d=>l(d))})}function Nl(e,t,n,r,o=s=>s()){const s=[];for(const i of e)for(const a in i.components){let l=i.components[a];if(!(t!==\"beforeRouteEnter\"&&!i.instances[a]))if(Uv(l)){const u=(l.__vccOpts||l)[t];u&&s.push(Ir(u,n,r,i,a,o))}else{let u=l();s.push(()=>u.then(c=>{if(!c)throw new Error(`Couldn't resolve component \"${a}\" at \"${i.path}\"`);const f=ww(c)?c.default:c;i.mods[a]=c,i.components[a]=f;const d=(f.__vccOpts||f)[t];return d&&Ir(d,n,r,i,a,o)()}))}}return s}function Qw(e,t){const n=[],r=[],o=[],s=Math.max(t.matched.length,e.matched.length);for(let i=0;i<s;i++){const a=t.matched[i];a&&(e.matched.find(u=>jo(u,a))?r.push(a):n.push(a));const l=e.matched[i];l&&(t.matched.find(u=>jo(u,l))||o.push(l))}return[n,r,o]}/*!\n * vue-router v4.6.4\n * (c) 2025 Eduardo San Martin Morote\n * @license MIT\n */let e1=()=>location.protocol+\"//\"+location.host;function tm(e,t){const{pathname:n,search:r,hash:o}=t,s=e.indexOf(\"#\");if(s>-1){let i=o.includes(e.slice(s))?e.slice(s).length:1,a=o.slice(i);return a[0]!==\"/\"&&(a=\"/\"+a),Wf(a,\"\")}return Wf(n,e)+r+o}function t1(e,t,n,r){let o=[],s=[],i=null;const a=({state:d})=>{const v=tm(e,location),p=n.value,h=t.value;let b=0;if(d){if(n.value=v,t.value=d,i&&i===p){i=null;return}b=h?d.position-h.position:0}else r(v);o.forEach(m=>{m(n.value,p,{delta:b,type:hu.pop,direction:b?b>0?Pl.forward:Pl.back:Pl.unknown})})};function l(){i=n.value}function u(d){o.push(d);const v=()=>{const p=o.indexOf(d);p>-1&&o.splice(p,1)};return s.push(v),v}function c(){if(document.visibilityState===\"hidden\"){const{history:d}=window;if(!d.state)return;d.replaceState(We({},d.state,{scroll:Ya()}),\"\")}}function f(){for(const d of s)d();s=[],window.removeEventListener(\"popstate\",a),window.removeEventListener(\"pagehide\",c),document.removeEventListener(\"visibilitychange\",c)}return window.addEventListener(\"popstate\",a),window.addEventListener(\"pagehide\",c),document.addEventListener(\"visibilitychange\",c),{pauseListeners:l,listen:u,destroy:f}}function Zf(e,t,n,r=!1,o=!1){return{back:e,current:t,forward:n,replaced:r,position:window.history.length,scroll:o?Ya():null}}function n1(e){const{history:t,location:n}=window,r={value:tm(e,n)},o={value:t.state};o.value||s(r.value,{back:null,current:r.value,forward:null,position:t.length-1,replaced:!0,scroll:null},!0);function s(l,u,c){const f=e.indexOf(\"#\"),d=f>-1?(n.host&&document.querySelector(\"base\")?e:e.slice(f))+l:e1()+e+l;try{t[c?\"replaceState\":\"pushState\"](u,\"\",d),o.value=u}catch{n[c?\"replace\":\"assign\"](d)}}function i(l,u){s(l,We({},t.state,Zf(o.value.back,l,o.value.forward,!0),u,{position:o.value.position}),!0),r.value=l}function a(l,u){const c=We({},o.value,t.state,{forward:l,scroll:Ya()});s(c.current,c,!0),s(l,We({},Zf(r.value,l,null),{position:c.position+1},u),!1),r.value=l}return{location:r,state:o,push:a,replace:i}}function r1(e){e=Vw(e);const t=n1(e),n=t1(e,t.state,t.location,t.replace);function r(s,i=!0){i||n.pauseListeners(),history.go(s)}const o=We({location:\"\",base:e,go:r,createHref:zw.bind(null,e)},t,n);return Object.defineProperty(o,\"location\",{enumerable:!0,get:()=>t.location.value}),Object.defineProperty(o,\"state\",{enumerable:!0,get:()=>t.state.value}),o}function wN(e){return e=location.host?e||location.pathname+location.search:\"\",e.includes(\"#\")||(e+=\"#\"),r1(e)}let Qr=function(e){return e[e.Static=0]=\"Static\",e[e.Param=1]=\"Param\",e[e.Group=2]=\"Group\",e}({});var gt=function(e){return e[e.Static=0]=\"Static\",e[e.Param=1]=\"Param\",e[e.ParamRegExp=2]=\"ParamRegExp\",e[e.ParamRegExpEnd=3]=\"ParamRegExpEnd\",e[e.EscapeNext=4]=\"EscapeNext\",e}(gt||{});const o1={type:Qr.Static,value:\"\"},s1=/[a-zA-Z0-9_]/;function i1(e){if(!e)return[[]];if(e===\"/\")return[[o1]];if(!e.startsWith(\"/\"))throw new Error(`Invalid path \"${e}\"`);function t(v){throw new Error(`ERR (${n})/\"${u}\": ${v}`)}let n=gt.Static,r=n;const o=[];let s;function i(){s&&o.push(s),s=[]}let a=0,l,u=\"\",c=\"\";function f(){u&&(n===gt.Static?s.push({type:Qr.Static,value:u}):n===gt.Param||n===gt.ParamRegExp||n===gt.ParamRegExpEnd?(s.length>1&&(l===\"*\"||l===\"+\")&&t(`A repeatable param (${u}) must be alone in its segment. eg: '/:ids+.`),s.push({type:Qr.Param,value:u,regexp:c,repeatable:l===\"*\"||l===\"+\",optional:l===\"*\"||l===\"?\"})):t(\"Invalid state to consume buffer\"),u=\"\")}function d(){u+=l}for(;a<e.length;){if(l=e[a++],l===\"\\\\\"&&n!==gt.ParamRegExp){r=n,n=gt.EscapeNext;continue}switch(n){case gt.Static:l===\"/\"?(u&&f(),i()):l===\":\"?(f(),n=gt.Param):d();break;case gt.EscapeNext:d(),n=r;break;case gt.Param:l===\"(\"?n=gt.ParamRegExp:s1.test(l)?d():(f(),n=gt.Static,l!==\"*\"&&l!==\"?\"&&l!==\"+\"&&a--);break;case gt.ParamRegExp:l===\")\"?c[c.length-1]==\"\\\\\"?c=c.slice(0,-1)+l:n=gt.ParamRegExpEnd:c+=l;break;case gt.ParamRegExpEnd:f(),n=gt.Static,l!==\"*\"&&l!==\"?\"&&l!==\"+\"&&a--,c=\"\";break;default:t(\"Unknown state\");break}}return n===gt.ParamRegExp&&t(`Unfinished custom RegExp for param \"${u}\"`),f(),i(),o}const Qf=\"[^/]+?\",a1={sensitive:!1,strict:!1,start:!0,end:!0};var kt=function(e){return e[e._multiplier=10]=\"_multiplier\",e[e.Root=90]=\"Root\",e[e.Segment=40]=\"Segment\",e[e.SubSegment=30]=\"SubSegment\",e[e.Static=40]=\"Static\",e[e.Dynamic=20]=\"Dynamic\",e[e.BonusCustomRegExp=10]=\"BonusCustomRegExp\",e[e.BonusWildcard=-50]=\"BonusWildcard\",e[e.BonusRepeatable=-20]=\"BonusRepeatable\",e[e.BonusOptional=-8]=\"BonusOptional\",e[e.BonusStrict=.7000000000000001]=\"BonusStrict\",e[e.BonusCaseSensitive=.25]=\"BonusCaseSensitive\",e}(kt||{});const l1=/[.+*?^${}()[\\]/\\\\]/g;function u1(e,t){const n=We({},a1,t),r=[];let o=n.start?\"^\":\"\";const s=[];for(const u of e){const c=u.length?[]:[kt.Root];n.strict&&!u.length&&(o+=\"/\");for(let f=0;f<u.length;f++){const d=u[f];let v=kt.Segment+(n.sensitive?kt.BonusCaseSensitive:0);if(d.type===Qr.Static)f||(o+=\"/\"),o+=d.value.replace(l1,\"\\\\$&\"),v+=kt.Static;else if(d.type===Qr.Param){const{value:p,repeatable:h,optional:b,regexp:m}=d;s.push({name:p,repeatable:h,optional:b});const S=m||Qf;if(S!==Qf){v+=kt.BonusCustomRegExp;try{`${S}`}catch(w){throw new Error(`Invalid custom RegExp for param \"${p}\" (${S}): `+w.message)}}let _=h?`((?:${S})(?:/(?:${S}))*)`:`(${S})`;f||(_=b&&u.length<2?`(?:/${_})`:\"/\"+_),b&&(_+=\"?\"),o+=_,v+=kt.Dynamic,b&&(v+=kt.BonusOptional),h&&(v+=kt.BonusRepeatable),S===\".*\"&&(v+=kt.BonusWildcard)}c.push(v)}r.push(c)}if(n.strict&&n.end){const u=r.length-1;r[u][r[u].length-1]+=kt.BonusStrict}n.strict||(o+=\"/?\"),n.end?o+=\"$\":n.strict&&!o.endsWith(\"/\")&&(o+=\"(?:/|$)\");const i=new RegExp(o,n.sensitive?\"\":\"i\");function a(u){const c=u.match(i),f={};if(!c)return null;for(let d=1;d<c.length;d++){const v=c[d]||\"\",p=s[d-1];f[p.name]=v&&p.repeatable?v.split(\"/\"):v}return f}function l(u){let c=\"\",f=!1;for(const d of e){(!f||!c.endsWith(\"/\"))&&(c+=\"/\"),f=!1;for(const v of d)if(v.type===Qr.Static)c+=v.value;else if(v.type===Qr.Param){const{value:p,repeatable:h,optional:b}=v,m=p in u?u[p]:\"\";if(Rn(m)&&!h)throw new Error(`Provided param \"${p}\" is an array but it is not repeatable (* or + modifiers)`);const S=Rn(m)?m.join(\"/\"):m;if(!S)if(b)d.length<2&&(c.endsWith(\"/\")?c=c.slice(0,-1):f=!0);else throw new Error(`Missing required param \"${p}\"`);c+=S}}return c||\"/\"}return{re:i,score:r,keys:s,parse:a,stringify:l}}function c1(e,t){let n=0;for(;n<e.length&&n<t.length;){const r=t[n]-e[n];if(r)return r;n++}return e.length<t.length?e.length===1&&e[0]===kt.Static+kt.Segment?-1:1:e.length>t.length?t.length===1&&t[0]===kt.Static+kt.Segment?1:-1:0}function nm(e,t){let n=0;const r=e.score,o=t.score;for(;n<r.length&&n<o.length;){const s=c1(r[n],o[n]);if(s)return s;n++}if(Math.abs(o.length-r.length)===1){if(ed(r))return 1;if(ed(o))return-1}return o.length-r.length}function ed(e){const t=e[e.length-1];return e.length>0&&t[t.length-1]<0}const f1={strict:!1,end:!0,sensitive:!1};function d1(e,t,n){const r=u1(i1(e.path),n),o=We(r,{record:e,parent:t,children:[],alias:[]});return t&&!o.record.aliasOf==!t.record.aliasOf&&t.children.push(o),o}function p1(e,t){const n=[],r=new Map;t=qf(f1,t);function o(f){return r.get(f)}function s(f,d,v){const p=!v,h=nd(f);h.aliasOf=v&&v.record;const b=qf(t,f),m=[h];if(\"alias\"in f){const w=typeof f.alias==\"string\"?[f.alias]:f.alias;for(const y of w)m.push(nd(We({},h,{components:v?v.record.components:h.components,path:y,aliasOf:v?v.record:h})))}let S,_;for(const w of m){const{path:y}=w;if(d&&y[0]!==\"/\"){const A=d.record.path,R=A[A.length-1]===\"/\"?\"\":\"/\";w.path=d.record.path+(y&&R+y)}if(S=d1(w,d,b),v?v.alias.push(S):(_=_||S,_!==S&&_.alias.push(S),p&&f.name&&!rd(S)&&i(f.name)),rm(S)&&l(S),h.children){const A=h.children;for(let R=0;R<A.length;R++)s(A[R],S,v&&v.children[R])}v=v||S}return _?()=>{i(_)}:As}function i(f){if(Xv(f)){const d=r.get(f);d&&(r.delete(f),n.splice(n.indexOf(d),1),d.children.forEach(i),d.alias.forEach(i))}else{const d=n.indexOf(f);d>-1&&(n.splice(d,1),f.record.name&&r.delete(f.record.name),f.children.forEach(i),f.alias.forEach(i))}}function a(){return n}function l(f){const d=m1(f,n);n.splice(d,0,f),f.record.name&&!rd(f)&&r.set(f.record.name,f)}function u(f,d){let v,p={},h,b;if(\"name\"in f&&f.name){if(v=r.get(f.name),!v)throw zo(ut.MATCHER_NOT_FOUND,{location:f});b=v.record.name,p=We(td(d.params,v.keys.filter(_=>!_.optional).concat(v.parent?v.parent.keys.filter(_=>_.optional):[]).map(_=>_.name)),f.params&&td(f.params,v.keys.map(_=>_.name))),h=v.stringify(p)}else if(f.path!=null)h=f.path,v=n.find(_=>_.re.test(h)),v&&(p=v.parse(h),b=v.record.name);else{if(v=d.name?r.get(d.name):n.find(_=>_.re.test(d.path)),!v)throw zo(ut.MATCHER_NOT_FOUND,{location:f,currentLocation:d});b=v.record.name,p=We({},d.params,f.params),h=v.stringify(p)}const m=[];let S=v;for(;S;)m.unshift(S.record),S=S.parent;return{name:b,path:h,params:p,matched:m,meta:v1(m)}}e.forEach(f=>s(f));function c(){n.length=0,r.clear()}return{addRoute:s,resolve:u,removeRoute:i,clearRoutes:c,getRoutes:a,getRecordMatcher:o}}function td(e,t){const n={};for(const r of t)r in e&&(n[r]=e[r]);return n}function nd(e){const t={path:e.path,redirect:e.redirect,name:e.name,meta:e.meta||{},aliasOf:e.aliasOf,beforeEnter:e.beforeEnter,props:h1(e),children:e.children||[],instances:{},leaveGuards:new Set,updateGuards:new Set,enterCallbacks:{},components:\"components\"in e?e.components||null:e.component&&{default:e.component}};return Object.defineProperty(t,\"mods\",{value:{}}),t}function h1(e){const t={},n=e.props||!1;if(\"component\"in e)t.default=n;else for(const r in e.components)t[r]=typeof n==\"object\"?n[r]:n;return t}function rd(e){for(;e;){if(e.record.aliasOf)return!0;e=e.parent}return!1}function v1(e){return e.reduce((t,n)=>We(t,n.meta),{})}function m1(e,t){let n=0,r=t.length;for(;n!==r;){const s=n+r>>1;nm(e,t[s])<0?r=s:n=s+1}const o=g1(e);return o&&(r=t.lastIndexOf(o,r-1)),r}function g1(e){let t=e;for(;t=t.parent;)if(rm(t)&&nm(e,t)===0)return t}function rm({record:e}){return!!(e.name||e.components&&Object.keys(e.components).length||e.redirect)}function od(e){const t=Ee(Ja),n=Ee(em),r=C(()=>{const l=g(e.to);return t.resolve(l)}),o=C(()=>{const{matched:l}=r.value,{length:u}=l,c=l[u-1],f=n.matched;if(!c||!f.length)return-1;const d=f.findIndex(jo.bind(null,c));if(d>-1)return d;const v=sd(l[u-2]);return u>1&&sd(c)===v&&f[f.length-1].path!==v?f.findIndex(jo.bind(null,l[u-2])):d}),s=C(()=>o.value>-1&&E1(n.params,r.value.params)),i=C(()=>o.value>-1&&o.value===n.matched.length-1&&Jv(n.params,r.value.params));function a(l={}){if(S1(l)){const u=t[g(e.replace)?\"replace\":\"push\"](g(e.to)).catch(As);return e.viewTransition&&typeof document<\"u\"&&\"startViewTransition\"in document&&document.startViewTransition(()=>u),u}return Promise.resolve()}return{route:r,href:C(()=>r.value.href),isActive:s,isExactActive:i,navigate:a}}function b1(e){return e.length===1?e[0]:e}const y1=Z({name:\"RouterLink\",compatConfig:{MODE:3},props:{to:{type:[String,Object],required:!0},replace:Boolean,activeClass:String,exactActiveClass:String,custom:Boolean,ariaCurrentValue:{type:String,default:\"page\"},viewTransition:Boolean},useLink:od,setup(e,{slots:t}){const n=wt(od(e)),{options:r}=Ee(Ja),o=C(()=>({[id(e.activeClass,r.linkActiveClass,\"router-link-active\")]:n.isActive,[id(e.exactActiveClass,r.linkExactActiveClass,\"router-link-exact-active\")]:n.isExactActive}));return()=>{const s=t.default&&b1(t.default(n));return e.custom?s:or(\"a\",{\"aria-current\":n.isExactActive?e.ariaCurrentValue:null,href:n.href,onClick:n.navigate,class:o.value},s)}}}),w1=y1;function S1(e){if(!(e.metaKey||e.altKey||e.ctrlKey||e.shiftKey)&&!e.defaultPrevented&&!(e.button!==void 0&&e.button!==0)){if(e.currentTarget&&e.currentTarget.getAttribute){const t=e.currentTarget.getAttribute(\"target\");if(/\\b_blank\\b/i.test(t))return}return e.preventDefault&&e.preventDefault(),!0}}function E1(e,t){for(const n in t){const r=t[n],o=e[n];if(typeof r==\"string\"){if(r!==o)return!1}else if(!Rn(o)||o.length!==r.length||r.some((s,i)=>s.valueOf()!==o[i].valueOf()))return!1}return!0}function sd(e){return e?e.aliasOf?e.aliasOf.path:e.path:\"\"}const id=(e,t,n)=>e??t??n,_1=Z({name:\"RouterView\",inheritAttrs:!1,props:{name:{type:String,default:\"default\"},route:Object},compatConfig:{MODE:3},setup(e,{attrs:t,slots:n}){const r=Ee(mu),o=C(()=>e.route||r.value),s=Ee(Xf,0),i=C(()=>{let u=g(s);const{matched:c}=o.value;let f;for(;(f=c[u])&&!f.components;)u++;return u}),a=C(()=>o.value.matched[i.value]);ft(Xf,C(()=>i.value+1)),ft(Qv,a),ft(mu,o);const l=V();return he(()=>[l.value,a.value,e.name],([u,c,f],[d,v,p])=>{c&&(c.instances[f]=u,v&&v!==c&&u&&u===d&&(c.leaveGuards.size||(c.leaveGuards=v.leaveGuards),c.updateGuards.size||(c.updateGuards=v.updateGuards))),u&&c&&(!v||!jo(c,v)||!d)&&(c.enterCallbacks[f]||[]).forEach(h=>h(u))},{flush:\"post\"}),()=>{const u=o.value,c=e.name,f=a.value,d=f&&f.components[c];if(!d)return ad(n.default,{Component:d,route:u});const v=f.props[c],p=v?v===!0?u.params:typeof v==\"function\"?v(u):v:null,b=or(d,We({},p,t,{onVnodeUnmounted:m=>{m.component.isUnmounted&&(f.instances[c]=null)},ref:l}));return ad(n.default,{Component:b,route:u})||b}}});function ad(e,t){if(!e)return null;const n=e(t);return n.length===1?n[0]:n}const C1=_1;function SN(e){const t=p1(e.routes,e),n=e.parseQuery||Jw,r=e.stringifyQuery||Jf,o=e.history,s=ds(),i=ds(),a=ds(),l=ir(Er);let u=Er;Io&&e.scrollBehavior&&\"scrollRestoration\"in history&&(history.scrollRestoration=\"manual\");const c=xl.bind(null,B=>\"\"+B),f=xl.bind(null,Lw),d=xl.bind(null,Us);function v(B,K){let J,oe;return Xv(B)?(J=t.getRecordMatcher(B),oe=K):oe=B,t.addRoute(oe,J)}function p(B){const K=t.getRecordMatcher(B);K&&t.removeRoute(K)}function h(){return t.getRoutes().map(B=>B.record)}function b(B){return!!t.getRecordMatcher(B)}function m(B,K){if(K=We({},K||l.value),typeof B==\"string\"){const M=Il(n,B,K.path),W=t.resolve({path:M.path},K),G=o.createHref(M.fullPath);return We(M,W,{params:d(W.params),hash:Us(M.hash),redirectedFrom:void 0,href:G})}let J;if(B.path!=null)J=We({},B,{path:Il(n,B.path,K.path).path});else{const M=We({},B.params);for(const W in M)M[W]==null&&delete M[W];J=We({},B,{params:f(M)}),K.params=f(K.params)}const oe=t.resolve(J,K),ge=B.hash||\"\";oe.params=c(d(oe.params));const E=kw(r,We({},B,{hash:Iw(ge),path:oe.path})),T=o.createHref(E);return We({fullPath:E,hash:ge,query:r===Jf?Xw(B.query):B.query||{}},oe,{redirectedFrom:void 0,href:T})}function S(B){return typeof B==\"string\"?Il(n,B,l.value.path):We({},B)}function _(B,K){if(u!==B)return zo(ut.NAVIGATION_CANCELLED,{from:K,to:B})}function w(B){return R(B)}function y(B){return w(We(S(B),{replace:!0}))}function A(B,K){const J=B.matched[B.matched.length-1];if(J&&J.redirect){const{redirect:oe}=J;let ge=typeof oe==\"function\"?oe(B,K):oe;return typeof ge==\"string\"&&(ge=ge.includes(\"?\")||ge.includes(\"#\")?ge=S(ge):{path:ge},ge.params={}),We({query:B.query,hash:B.hash,params:ge.path!=null?{}:B.params},ge)}}function R(B,K){const J=u=m(B),oe=l.value,ge=B.state,E=B.force,T=B.replace===!0,M=A(J,oe);if(M)return R(We(S(M),{state:typeof M==\"object\"?We({},ge,M.state):ge,force:E,replace:T}),K||J);const W=J;W.redirectedFrom=K;let G;return!E&&Fw(r,oe,J)&&(G=zo(ut.NAVIGATION_DUPLICATED,{to:W,from:oe}),Re(oe,oe,!0,!1)),(G?Promise.resolve(G):x(W,oe)).catch(q=>Jn(q)?Jn(q,ut.NAVIGATION_GUARD_REDIRECT)?q:Pe(q):Q(q,W,oe)).then(q=>{if(q){if(Jn(q,ut.NAVIGATION_GUARD_REDIRECT))return R(We({replace:T},S(q.to),{state:typeof q.to==\"object\"?We({},ge,q.to.state):ge,force:E}),K||W)}else q=$(W,oe,!0,T,ge);return k(W,oe,q),q})}function N(B,K){const J=_(B,K);return J?Promise.reject(J):Promise.resolve()}function I(B){const K=qe.values().next().value;return K&&typeof K.runWithContext==\"function\"?K.runWithContext(B):B()}function x(B,K){let J;const[oe,ge,E]=Qw(B,K);J=Nl(oe.reverse(),\"beforeRouteLeave\",B,K);for(const M of oe)M.leaveGuards.forEach(W=>{J.push(Ir(W,B,K))});const T=N.bind(null,B,K);return J.push(T),De(J).then(()=>{J=[];for(const M of s.list())J.push(Ir(M,B,K));return J.push(T),De(J)}).then(()=>{J=Nl(ge,\"beforeRouteUpdate\",B,K);for(const M of ge)M.updateGuards.forEach(W=>{J.push(Ir(W,B,K))});return J.push(T),De(J)}).then(()=>{J=[];for(const M of E)if(M.beforeEnter)if(Rn(M.beforeEnter))for(const W of M.beforeEnter)J.push(Ir(W,B,K));else J.push(Ir(M.beforeEnter,B,K));return J.push(T),De(J)}).then(()=>(B.matched.forEach(M=>M.enterCallbacks={}),J=Nl(E,\"beforeRouteEnter\",B,K,I),J.push(T),De(J))).then(()=>{J=[];for(const M of i.list())J.push(Ir(M,B,K));return J.push(T),De(J)}).catch(M=>Jn(M,ut.NAVIGATION_CANCELLED)?M:Promise.reject(M))}function k(B,K,J){a.list().forEach(oe=>I(()=>oe(B,K,J)))}function $(B,K,J,oe,ge){const E=_(B,K);if(E)return E;const T=K===Er,M=Io?history.state:{};J&&(oe||T?o.replace(B.fullPath,We({scroll:T&&M&&M.scroll},ge)):o.push(B.fullPath,ge)),l.value=B,Re(B,K,J,T),Pe()}let F;function Y(){F||(F=o.listen((B,K,J)=>{if(!ze.listening)return;const oe=m(B),ge=A(oe,ze.currentRoute.value);if(ge){R(We(ge,{replace:!0,force:!0}),oe).catch(As);return}u=oe;const E=l.value;Io&&Kw(Yf(E.fullPath,J.delta),Ya()),x(oe,E).catch(T=>Jn(T,ut.NAVIGATION_ABORTED|ut.NAVIGATION_CANCELLED)?T:Jn(T,ut.NAVIGATION_GUARD_REDIRECT)?(R(We(S(T.to),{force:!0}),oe).then(M=>{Jn(M,ut.NAVIGATION_ABORTED|ut.NAVIGATION_DUPLICATED)&&!J.delta&&J.type===hu.pop&&o.go(-1,!1)}).catch(As),Promise.reject()):(J.delta&&o.go(-J.delta,!1),Q(T,oe,E))).then(T=>{T=T||$(oe,E,!1),T&&(J.delta&&!Jn(T,ut.NAVIGATION_CANCELLED)?o.go(-J.delta,!1):J.type===hu.pop&&Jn(T,ut.NAVIGATION_ABORTED|ut.NAVIGATION_DUPLICATED)&&o.go(-1,!1)),k(oe,E,T)}).catch(As)}))}let P=ds(),O=ds(),j;function Q(B,K,J){Pe(B);const oe=O.list();return oe.length&&oe.forEach(ge=>ge(B,K,J)),Promise.reject(B)}function me(){return j&&l.value!==Er?Promise.resolve():new Promise((B,K)=>{P.add([B,K])})}function Pe(B){return j||(j=!B,Y(),P.list().forEach(([K,J])=>B?J(B):K()),P.reset()),B}function Re(B,K,J,oe){const{scrollBehavior:ge}=e;if(!Io||!ge)return Promise.resolve();const E=!J&&qw(Yf(B.fullPath,0))||(oe||!J)&&history.state&&history.state.scroll||null;return Ie().then(()=>ge(B,K,E)).then(T=>T&&Uw(T)).catch(T=>Q(T,B,K))}const Ce=B=>o.go(B);let _e;const qe=new Set,ze={currentRoute:l,listening:!0,addRoute:v,removeRoute:p,clearRoutes:t.clearRoutes,hasRoute:b,getRoutes:h,resolve:m,options:e,push:w,replace:y,go:Ce,back:()=>Ce(-1),forward:()=>Ce(1),beforeEach:s.add,beforeResolve:i.add,afterEach:a.add,onError:O.add,isReady:me,install(B){B.component(\"RouterLink\",w1),B.component(\"RouterView\",C1),B.config.globalProperties.$router=ze,Object.defineProperty(B.config.globalProperties,\"$route\",{enumerable:!0,get:()=>g(l)}),Io&&!_e&&l.value===Er&&(_e=!0,w(o.location).catch(oe=>{}));const K={};for(const oe in Er)Object.defineProperty(K,oe,{get:()=>l.value[oe],enumerable:!0});B.provide(Ja,ze),B.provide(em,Qu(K)),B.provide(mu,l);const J=B.unmount;qe.add(B),B.unmount=function(){qe.delete(B),qe.size<1&&(u=Er,F&&F(),F=null,l.value=Er,_e=!1,j=!1),J()}}};function De(B){return B.reduce((K,J)=>K.then(()=>I(J)),Promise.resolve())}return ze}function EN(){return Ee(Ja)}const T1='a[href],button:not([disabled]),button:not([hidden]),:not([tabindex=\"-1\"]),input:not([disabled]),input:not([type=\"hidden\"]),select:not([disabled]),textarea:not([disabled])',O1=e=>getComputedStyle(e).position===\"fixed\"?!1:e.offsetParent!==null,ld=e=>Array.from(e.querySelectorAll(T1)).filter(t=>A1(t)&&O1(t)),A1=e=>{if(e.tabIndex>0||e.tabIndex===0&&e.getAttribute(\"tabIndex\")!==null)return!0;if(e.disabled)return!1;switch(e.nodeName){case\"A\":return!!e.href&&e.rel!==\"ignore\";case\"INPUT\":return!(e.type===\"hidden\"||e.type===\"file\");case\"BUTTON\":case\"SELECT\":case\"TEXTAREA\":return!0;default:return!1}},Qn=(e,t,{checkForDefaultPrevented:n=!0}={})=>o=>{const s=e==null?void 0:e(o);if(n===!1||!s)return t==null?void 0:t(o)};var R1=Object.defineProperty,x1=Object.defineProperties,I1=Object.getOwnPropertyDescriptors,ud=Object.getOwnPropertySymbols,P1=Object.prototype.hasOwnProperty,N1=Object.prototype.propertyIsEnumerable,cd=(e,t,n)=>t in e?R1(e,t,{enumerable:!0,configurable:!0,writable:!0,value:n}):e[t]=n,L1=(e,t)=>{for(var n in t||(t={}))P1.call(t,n)&&cd(e,n,t[n]);if(ud)for(var n of ud(t))N1.call(t,n)&&cd(e,n,t[n]);return e},M1=(e,t)=>x1(e,I1(t));function fd(e,t){var n;const r=ir();return Ha(()=>{r.value=e()},M1(L1({},t),{flush:(n=void 0)!=null?n:\"sync\"})),Mr(r)}var dd;const st=typeof window<\"u\",$1=e=>typeof e==\"string\",Sa=()=>{},gu=st&&((dd=window==null?void 0:window.navigator)==null?void 0:dd.userAgent)&&/iP(ad|hone|od)/.test(window.navigator.userAgent);function Ks(e){return typeof e==\"function\"?e():g(e)}function k1(e,t){function n(...r){return new Promise((o,s)=>{Promise.resolve(e(()=>t.apply(this,r),{fn:t,thisArg:this,args:r})).then(o).catch(s)})}return n}function F1(e,t={}){let n,r,o=Sa;const s=a=>{clearTimeout(a),o(),o=Sa};return a=>{const l=Ks(e),u=Ks(t.maxWait);return n&&s(n),l<=0||u!==void 0&&u<=0?(r&&(s(r),r=null),Promise.resolve(a())):new Promise((c,f)=>{o=t.rejectOnCancel?f:c,u&&!r&&(r=setTimeout(()=>{n&&s(n),r=null,c(a())},u)),n=setTimeout(()=>{r&&s(r),r=null,c(a())},l)})}}function B1(e){return e}function ui(e){return Fa()?(Ba(e),!0):!1}function D1(e,t=200,n={}){return k1(F1(t,n),e)}function V1(e,t=200,n={}){const r=V(e.value),o=D1(()=>{r.value=e.value},t,n);return he(e,()=>o()),r}function j1(e,t=!0){Ge()?Ke(e):t?e():Ie(e)}function bu(e,t,n={}){const{immediate:r=!0}=n,o=V(!1);let s=null;function i(){s&&(clearTimeout(s),s=null)}function a(){o.value=!1,i()}function l(...u){i(),o.value=!0,s=setTimeout(()=>{o.value=!1,s=null,e(...u)},Ks(t))}return r&&(o.value=!0,st&&l()),ui(a),{isPending:Mr(o),start:l,stop:a}}function sr(e){var t;const n=Ks(e);return(t=n==null?void 0:n.$el)!=null?t:n}const ci=st?window:void 0,z1=st?window.document:void 0;function tn(...e){let t,n,r,o;if($1(e[0])||Array.isArray(e[0])?([n,r,o]=e,t=ci):[t,n,r,o]=e,!t)return Sa;Array.isArray(n)||(n=[n]),Array.isArray(r)||(r=[r]);const s=[],i=()=>{s.forEach(c=>c()),s.length=0},a=(c,f,d,v)=>(c.addEventListener(f,d,v),()=>c.removeEventListener(f,d,v)),l=he(()=>[sr(t),Ks(o)],([c,f])=>{i(),c&&s.push(...n.flatMap(d=>r.map(v=>a(c,d,v,f))))},{immediate:!0,flush:\"post\"}),u=()=>{l(),i()};return ui(u),u}let pd=!1;function H1(e,t,n={}){const{window:r=ci,ignore:o=[],capture:s=!0,detectIframe:i=!1}=n;if(!r)return;gu&&!pd&&(pd=!0,Array.from(r.document.body.children).forEach(d=>d.addEventListener(\"click\",Sa)));let a=!0;const l=d=>o.some(v=>{if(typeof v==\"string\")return Array.from(r.document.querySelectorAll(v)).some(p=>p===d.target||d.composedPath().includes(p));{const p=sr(v);return p&&(d.target===p||d.composedPath().includes(p))}}),c=[tn(r,\"click\",d=>{const v=sr(e);if(!(!v||v===d.target||d.composedPath().includes(v))){if(d.detail===0&&(a=!l(d)),!a){a=!0;return}t(d)}},{passive:!0,capture:s}),tn(r,\"pointerdown\",d=>{const v=sr(e);v&&(a=!d.composedPath().includes(v)&&!l(d))},{passive:!0}),i&&tn(r,\"blur\",d=>{var v;const p=sr(e);((v=r.document.activeElement)==null?void 0:v.tagName)===\"IFRAME\"&&!(p!=null&&p.contains(r.document.activeElement))&&t(d)})].filter(Boolean);return()=>c.forEach(d=>d())}function om(e,t=!1){const n=V(),r=()=>n.value=!!e();return r(),j1(r,t),n}const hd=typeof globalThis<\"u\"?globalThis:typeof window<\"u\"?window:typeof global<\"u\"?global:typeof self<\"u\"?self:{},vd=\"__vueuse_ssr_handlers__\";hd[vd]=hd[vd]||{};function U1({document:e=z1}={}){if(!e)return V(\"visible\");const t=V(e.visibilityState);return tn(e,\"visibilitychange\",()=>{t.value=e.visibilityState}),t}var md=Object.getOwnPropertySymbols,K1=Object.prototype.hasOwnProperty,q1=Object.prototype.propertyIsEnumerable,W1=(e,t)=>{var n={};for(var r in e)K1.call(e,r)&&t.indexOf(r)<0&&(n[r]=e[r]);if(e!=null&&md)for(var r of md(e))t.indexOf(r)<0&&q1.call(e,r)&&(n[r]=e[r]);return n};function Dt(e,t,n={}){const r=n,{window:o=ci}=r,s=W1(r,[\"window\"]);let i;const a=om(()=>o&&\"ResizeObserver\"in o),l=()=>{i&&(i.disconnect(),i=void 0)},u=he(()=>sr(e),f=>{l(),a.value&&o&&f&&(i=new ResizeObserver(t),i.observe(f,s))},{immediate:!0,flush:\"post\"}),c=()=>{l(),u()};return ui(c),{isSupported:a,stop:c}}var gd=Object.getOwnPropertySymbols,G1=Object.prototype.hasOwnProperty,Y1=Object.prototype.propertyIsEnumerable,J1=(e,t)=>{var n={};for(var r in e)G1.call(e,r)&&t.indexOf(r)<0&&(n[r]=e[r]);if(e!=null&&gd)for(var r of gd(e))t.indexOf(r)<0&&Y1.call(e,r)&&(n[r]=e[r]);return n};function X1(e,t,n={}){const r=n,{window:o=ci}=r,s=J1(r,[\"window\"]);let i;const a=om(()=>o&&\"MutationObserver\"in o),l=()=>{i&&(i.disconnect(),i=void 0)},u=he(()=>sr(e),f=>{l(),a.value&&o&&f&&(i=new MutationObserver(t),i.observe(f,s))},{immediate:!0}),c=()=>{l(),u()};return ui(c),{isSupported:a,stop:c}}var bd;(function(e){e.UP=\"UP\",e.RIGHT=\"RIGHT\",e.DOWN=\"DOWN\",e.LEFT=\"LEFT\",e.NONE=\"NONE\"})(bd||(bd={}));var Z1=Object.defineProperty,yd=Object.getOwnPropertySymbols,Q1=Object.prototype.hasOwnProperty,eS=Object.prototype.propertyIsEnumerable,wd=(e,t,n)=>t in e?Z1(e,t,{enumerable:!0,configurable:!0,writable:!0,value:n}):e[t]=n,tS=(e,t)=>{for(var n in t||(t={}))Q1.call(t,n)&&wd(e,n,t[n]);if(yd)for(var n of yd(t))eS.call(t,n)&&wd(e,n,t[n]);return e};const nS={easeInSine:[.12,0,.39,0],easeOutSine:[.61,1,.88,1],easeInOutSine:[.37,0,.63,1],easeInQuad:[.11,0,.5,0],easeOutQuad:[.5,1,.89,1],easeInOutQuad:[.45,0,.55,1],easeInCubic:[.32,0,.67,0],easeOutCubic:[.33,1,.68,1],easeInOutCubic:[.65,0,.35,1],easeInQuart:[.5,0,.75,0],easeOutQuart:[.25,1,.5,1],easeInOutQuart:[.76,0,.24,1],easeInQuint:[.64,0,.78,0],easeOutQuint:[.22,1,.36,1],easeInOutQuint:[.83,0,.17,1],easeInExpo:[.7,0,.84,0],easeOutExpo:[.16,1,.3,1],easeInOutExpo:[.87,0,.13,1],easeInCirc:[.55,0,1,.45],easeOutCirc:[0,.55,.45,1],easeInOutCirc:[.85,0,.15,1],easeInBack:[.36,0,.66,-.56],easeOutBack:[.34,1.56,.64,1],easeInOutBack:[.68,-.6,.32,1.6]};tS({linear:B1},nS);function rS({window:e=ci}={}){if(!e)return V(!1);const t=V(e.document.hasFocus());return tn(e,\"blur\",()=>{t.value=!1}),tn(e,\"focus\",()=>{t.value=!0}),t}const oS=()=>st&&/firefox/i.test(window.navigator.userAgent);var sm=typeof global==\"object\"&&global&&global.Object===Object&&global,sS=typeof self==\"object\"&&self&&self.Object===Object&&self,Pn=sm||sS||Function(\"return this\")(),fn=Pn.Symbol,im=Object.prototype,iS=im.hasOwnProperty,aS=im.toString,ps=fn?fn.toStringTag:void 0;function lS(e){var t=iS.call(e,ps),n=e[ps];try{e[ps]=void 0;var r=!0}catch{}var o=aS.call(e);return r&&(t?e[ps]=n:delete e[ps]),o}var uS=Object.prototype,cS=uS.toString;function fS(e){return cS.call(e)}var dS=\"[object Null]\",pS=\"[object Undefined]\",Sd=fn?fn.toStringTag:void 0;function Qo(e){return e==null?e===void 0?pS:dS:Sd&&Sd in Object(e)?lS(e):fS(e)}function $r(e){return e!=null&&typeof e==\"object\"}var hS=\"[object Symbol]\";function Xa(e){return typeof e==\"symbol\"||$r(e)&&Qo(e)==hS}function vS(e,t){for(var n=-1,r=e==null?0:e.length,o=Array(r);++n<r;)o[n]=t(e[n],n,e);return o}var dn=Array.isArray,Ed=fn?fn.prototype:void 0,_d=Ed?Ed.toString:void 0;function am(e){if(typeof e==\"string\")return e;if(dn(e))return vS(e,am)+\"\";if(Xa(e))return _d?_d.call(e):\"\";var t=e+\"\";return t==\"0\"&&1/e==-1/0?\"-0\":t}var mS=/\\s/;function gS(e){for(var t=e.length;t--&&mS.test(e.charAt(t)););return t}var bS=/^\\s+/;function yS(e){return e&&e.slice(0,gS(e)+1).replace(bS,\"\")}function xn(e){var t=typeof e;return e!=null&&(t==\"object\"||t==\"function\")}var Cd=NaN,wS=/^[-+]0x[0-9a-f]+$/i,SS=/^0b[01]+$/i,ES=/^0o[0-7]+$/i,_S=parseInt;function Td(e){if(typeof e==\"number\")return e;if(Xa(e))return Cd;if(xn(e)){var t=typeof e.valueOf==\"function\"?e.valueOf():e;e=xn(t)?t+\"\":t}if(typeof e!=\"string\")return e===0?e:+e;e=yS(e);var n=SS.test(e);return n||ES.test(e)?_S(e.slice(2),n?2:8):wS.test(e)?Cd:+e}function lm(e){return e}var CS=\"[object AsyncFunction]\",TS=\"[object Function]\",OS=\"[object GeneratorFunction]\",AS=\"[object Proxy]\";function um(e){if(!xn(e))return!1;var t=Qo(e);return t==TS||t==OS||t==CS||t==AS}var Ll=Pn[\"__core-js_shared__\"],Od=function(){var e=/[^.]+$/.exec(Ll&&Ll.keys&&Ll.keys.IE_PROTO||\"\");return e?\"Symbol(src)_1.\"+e:\"\"}();function RS(e){return!!Od&&Od in e}var xS=Function.prototype,IS=xS.toString;function bo(e){if(e!=null){try{return IS.call(e)}catch{}try{return e+\"\"}catch{}}return\"\"}var PS=/[\\\\^$.*+?()[\\]{}|]/g,NS=/^\\[object .+?Constructor\\]$/,LS=Function.prototype,MS=Object.prototype,$S=LS.toString,kS=MS.hasOwnProperty,FS=RegExp(\"^\"+$S.call(kS).replace(PS,\"\\\\$&\").replace(/hasOwnProperty|(function).*?(?=\\\\\\()| for .+?(?=\\\\\\])/g,\"$1.*?\")+\"$\");function BS(e){if(!xn(e)||RS(e))return!1;var t=um(e)?FS:NS;return t.test(bo(e))}function DS(e,t){return e==null?void 0:e[t]}function yo(e,t){var n=DS(e,t);return BS(n)?n:void 0}var yu=yo(Pn,\"WeakMap\"),Ad=Object.create,VS=function(){function e(){}return function(t){if(!xn(t))return{};if(Ad)return Ad(t);e.prototype=t;var n=new e;return e.prototype=void 0,n}}();function jS(e,t,n){switch(n.length){case 0:return e.call(t);case 1:return e.call(t,n[0]);case 2:return e.call(t,n[0],n[1]);case 3:return e.call(t,n[0],n[1],n[2])}return e.apply(t,n)}function zS(e,t){var n=-1,r=e.length;for(t||(t=Array(r));++n<r;)t[n]=e[n];return t}var HS=800,US=16,KS=Date.now;function qS(e){var t=0,n=0;return function(){var r=KS(),o=US-(r-n);if(n=r,o>0){if(++t>=HS)return arguments[0]}else t=0;return e.apply(void 0,arguments)}}function WS(e){return function(){return e}}var Ea=function(){try{var e=yo(Object,\"defineProperty\");return e({},\"\",{}),e}catch{}}(),GS=Ea?function(e,t){return Ea(e,\"toString\",{configurable:!0,enumerable:!1,value:WS(t),writable:!0})}:lm,YS=qS(GS);function JS(e,t){for(var n=-1,r=e==null?0:e.length;++n<r&&t(e[n],n,e)!==!1;);return e}function XS(e,t,n,r){e.length;for(var o=n+1;o--;)if(t(e[o],o,e))return o;return-1}var ZS=9007199254740991,QS=/^(?:0|[1-9]\\d*)$/;function pc(e,t){var n=typeof e;return t=t??ZS,!!t&&(n==\"number\"||n!=\"symbol\"&&QS.test(e))&&e>-1&&e%1==0&&e<t}function cm(e,t,n){t==\"__proto__\"&&Ea?Ea(e,t,{configurable:!0,enumerable:!0,value:n,writable:!0}):e[t]=n}function hc(e,t){return e===t||e!==e&&t!==t}var eE=Object.prototype,tE=eE.hasOwnProperty;function vc(e,t,n){var r=e[t];(!(tE.call(e,t)&&hc(r,n))||n===void 0&&!(t in e))&&cm(e,t,n)}function Za(e,t,n,r){var o=!n;n||(n={});for(var s=-1,i=t.length;++s<i;){var a=t[s],l=void 0;l===void 0&&(l=e[a]),o?cm(n,a,l):vc(n,a,l)}return n}var Rd=Math.max;function nE(e,t,n){return t=Rd(t===void 0?e.length-1:t,0),function(){for(var r=arguments,o=-1,s=Rd(r.length-t,0),i=Array(s);++o<s;)i[o]=r[t+o];o=-1;for(var a=Array(t+1);++o<t;)a[o]=r[o];return a[t]=n(i),jS(e,this,a)}}var rE=9007199254740991;function mc(e){return typeof e==\"number\"&&e>-1&&e%1==0&&e<=rE}function fm(e){return e!=null&&mc(e.length)&&!um(e)}var oE=Object.prototype;function gc(e){var t=e&&e.constructor,n=typeof t==\"function\"&&t.prototype||oE;return e===n}function sE(e,t){for(var n=-1,r=Array(e);++n<e;)r[n]=t(n);return r}var iE=\"[object Arguments]\";function xd(e){return $r(e)&&Qo(e)==iE}var dm=Object.prototype,aE=dm.hasOwnProperty,lE=dm.propertyIsEnumerable,bc=xd(function(){return arguments}())?xd:function(e){return $r(e)&&aE.call(e,\"callee\")&&!lE.call(e,\"callee\")};function uE(){return!1}var pm=typeof exports==\"object\"&&exports&&!exports.nodeType&&exports,Id=pm&&typeof module==\"object\"&&module&&!module.nodeType&&module,cE=Id&&Id.exports===pm,Pd=cE?Pn.Buffer:void 0,fE=Pd?Pd.isBuffer:void 0,_a=fE||uE,dE=\"[object Arguments]\",pE=\"[object Array]\",hE=\"[object Boolean]\",vE=\"[object Date]\",mE=\"[object Error]\",gE=\"[object Function]\",bE=\"[object Map]\",yE=\"[object Number]\",wE=\"[object Object]\",SE=\"[object RegExp]\",EE=\"[object Set]\",_E=\"[object String]\",CE=\"[object WeakMap]\",TE=\"[object ArrayBuffer]\",OE=\"[object DataView]\",AE=\"[object Float32Array]\",RE=\"[object Float64Array]\",xE=\"[object Int8Array]\",IE=\"[object Int16Array]\",PE=\"[object Int32Array]\",NE=\"[object Uint8Array]\",LE=\"[object Uint8ClampedArray]\",ME=\"[object Uint16Array]\",$E=\"[object Uint32Array]\",it={};it[AE]=it[RE]=it[xE]=it[IE]=it[PE]=it[NE]=it[LE]=it[ME]=it[$E]=!0;it[dE]=it[pE]=it[TE]=it[hE]=it[OE]=it[vE]=it[mE]=it[gE]=it[bE]=it[yE]=it[wE]=it[SE]=it[EE]=it[_E]=it[CE]=!1;function kE(e){return $r(e)&&mc(e.length)&&!!it[Qo(e)]}function yc(e){return function(t){return e(t)}}var hm=typeof exports==\"object\"&&exports&&!exports.nodeType&&exports,Rs=hm&&typeof module==\"object\"&&module&&!module.nodeType&&module,FE=Rs&&Rs.exports===hm,Ml=FE&&sm.process,Ho=function(){try{var e=Rs&&Rs.require&&Rs.require(\"util\").types;return e||Ml&&Ml.binding&&Ml.binding(\"util\")}catch{}}(),Nd=Ho&&Ho.isTypedArray,vm=Nd?yc(Nd):kE,BE=Object.prototype,DE=BE.hasOwnProperty;function mm(e,t){var n=dn(e),r=!n&&bc(e),o=!n&&!r&&_a(e),s=!n&&!r&&!o&&vm(e),i=n||r||o||s,a=i?sE(e.length,String):[],l=a.length;for(var u in e)(t||DE.call(e,u))&&!(i&&(u==\"length\"||o&&(u==\"offset\"||u==\"parent\")||s&&(u==\"buffer\"||u==\"byteLength\"||u==\"byteOffset\")||pc(u,l)))&&a.push(u);return a}function gm(e,t){return function(n){return e(t(n))}}var VE=gm(Object.keys,Object),jE=Object.prototype,zE=jE.hasOwnProperty;function HE(e){if(!gc(e))return VE(e);var t=[];for(var n in Object(e))zE.call(e,n)&&n!=\"constructor\"&&t.push(n);return t}function wc(e){return fm(e)?mm(e):HE(e)}function UE(e){var t=[];if(e!=null)for(var n in Object(e))t.push(n);return t}var KE=Object.prototype,qE=KE.hasOwnProperty;function WE(e){if(!xn(e))return UE(e);var t=gc(e),n=[];for(var r in e)r==\"constructor\"&&(t||!qE.call(e,r))||n.push(r);return n}function GE(e){return fm(e)?mm(e,!0):WE(e)}var YE=/\\.|\\[(?:[^[\\]]*|([\"'])(?:(?!\\1)[^\\\\]|\\\\.)*?\\1)\\]/,JE=/^\\w*$/;function Sc(e,t){if(dn(e))return!1;var n=typeof e;return n==\"number\"||n==\"symbol\"||n==\"boolean\"||e==null||Xa(e)?!0:JE.test(e)||!YE.test(e)||t!=null&&e in Object(t)}var qs=yo(Object,\"create\");function XE(){this.__data__=qs?qs(null):{},this.size=0}function ZE(e){var t=this.has(e)&&delete this.__data__[e];return this.size-=t?1:0,t}var QE=\"__lodash_hash_undefined__\",e2=Object.prototype,t2=e2.hasOwnProperty;function n2(e){var t=this.__data__;if(qs){var n=t[e];return n===QE?void 0:n}return t2.call(t,e)?t[e]:void 0}var r2=Object.prototype,o2=r2.hasOwnProperty;function s2(e){var t=this.__data__;return qs?t[e]!==void 0:o2.call(t,e)}var i2=\"__lodash_hash_undefined__\";function a2(e,t){var n=this.__data__;return this.size+=this.has(e)?0:1,n[e]=qs&&t===void 0?i2:t,this}function co(e){var t=-1,n=e==null?0:e.length;for(this.clear();++t<n;){var r=e[t];this.set(r[0],r[1])}}co.prototype.clear=XE;co.prototype.delete=ZE;co.prototype.get=n2;co.prototype.has=s2;co.prototype.set=a2;function l2(){this.__data__=[],this.size=0}function Qa(e,t){for(var n=e.length;n--;)if(hc(e[n][0],t))return n;return-1}var u2=Array.prototype,c2=u2.splice;function f2(e){var t=this.__data__,n=Qa(t,e);if(n<0)return!1;var r=t.length-1;return n==r?t.pop():c2.call(t,n,1),--this.size,!0}function d2(e){var t=this.__data__,n=Qa(t,e);return n<0?void 0:t[n][1]}function p2(e){return Qa(this.__data__,e)>-1}function h2(e,t){var n=this.__data__,r=Qa(n,e);return r<0?(++this.size,n.push([e,t])):n[r][1]=t,this}function gr(e){var t=-1,n=e==null?0:e.length;for(this.clear();++t<n;){var r=e[t];this.set(r[0],r[1])}}gr.prototype.clear=l2;gr.prototype.delete=f2;gr.prototype.get=d2;gr.prototype.has=p2;gr.prototype.set=h2;var Ws=yo(Pn,\"Map\");function v2(){this.size=0,this.__data__={hash:new co,map:new(Ws||gr),string:new co}}function m2(e){var t=typeof e;return t==\"string\"||t==\"number\"||t==\"symbol\"||t==\"boolean\"?e!==\"__proto__\":e===null}function el(e,t){var n=e.__data__;return m2(t)?n[typeof t==\"string\"?\"string\":\"hash\"]:n.map}function g2(e){var t=el(this,e).delete(e);return this.size-=t?1:0,t}function b2(e){return el(this,e).get(e)}function y2(e){return el(this,e).has(e)}function w2(e,t){var n=el(this,e),r=n.size;return n.set(e,t),this.size+=n.size==r?0:1,this}function br(e){var t=-1,n=e==null?0:e.length;for(this.clear();++t<n;){var r=e[t];this.set(r[0],r[1])}}br.prototype.clear=v2;br.prototype.delete=g2;br.prototype.get=b2;br.prototype.has=y2;br.prototype.set=w2;var S2=\"Expected a function\";function Ec(e,t){if(typeof e!=\"function\"||t!=null&&typeof t!=\"function\")throw new TypeError(S2);var n=function(){var r=arguments,o=t?t.apply(this,r):r[0],s=n.cache;if(s.has(o))return s.get(o);var i=e.apply(this,r);return n.cache=s.set(o,i)||s,i};return n.cache=new(Ec.Cache||br),n}Ec.Cache=br;var E2=500;function _2(e){var t=Ec(e,function(r){return n.size===E2&&n.clear(),r}),n=t.cache;return t}var C2=/[^.[\\]]+|\\[(?:(-?\\d+(?:\\.\\d+)?)|([\"'])((?:(?!\\2)[^\\\\]|\\\\.)*?)\\2)\\]|(?=(?:\\.|\\[\\])(?:\\.|\\[\\]|$))/g,T2=/\\\\(\\\\)?/g,O2=_2(function(e){var t=[];return e.charCodeAt(0)===46&&t.push(\"\"),e.replace(C2,function(n,r,o,s){t.push(o?s.replace(T2,\"$1\"):r||n)}),t});function A2(e){return e==null?\"\":am(e)}function tl(e,t){return dn(e)?e:Sc(e,t)?[e]:O2(A2(e))}function fi(e){if(typeof e==\"string\"||Xa(e))return e;var t=e+\"\";return t==\"0\"&&1/e==-1/0?\"-0\":t}function _c(e,t){t=tl(t,e);for(var n=0,r=t.length;e!=null&&n<r;)e=e[fi(t[n++])];return n&&n==r?e:void 0}function zn(e,t,n){var r=e==null?void 0:_c(e,t);return r===void 0?n:r}function Cc(e,t){for(var n=-1,r=t.length,o=e.length;++n<r;)e[o+n]=t[n];return e}var Ld=fn?fn.isConcatSpreadable:void 0;function R2(e){return dn(e)||bc(e)||!!(Ld&&e&&e[Ld])}function x2(e,t,n,r,o){var s=-1,i=e.length;for(n||(n=R2),o||(o=[]);++s<i;){var a=e[s];n(a)?Cc(o,a):o[o.length]=a}return o}function I2(e){var t=e==null?0:e.length;return t?x2(e):[]}function P2(e){return YS(nE(e,void 0,I2),e+\"\")}var bm=gm(Object.getPrototypeOf,Object);function yn(){if(!arguments.length)return[];var e=arguments[0];return dn(e)?e:[e]}function N2(){this.__data__=new gr,this.size=0}function L2(e){var t=this.__data__,n=t.delete(e);return this.size=t.size,n}function M2(e){return this.__data__.get(e)}function $2(e){return this.__data__.has(e)}var k2=200;function F2(e,t){var n=this.__data__;if(n instanceof gr){var r=n.__data__;if(!Ws||r.length<k2-1)return r.push([e,t]),this.size=++n.size,this;n=this.__data__=new br(r)}return n.set(e,t),this.size=n.size,this}function Kn(e){var t=this.__data__=new gr(e);this.size=t.size}Kn.prototype.clear=N2;Kn.prototype.delete=L2;Kn.prototype.get=M2;Kn.prototype.has=$2;Kn.prototype.set=F2;function B2(e,t){return e&&Za(t,wc(t),e)}function D2(e,t){return e&&Za(t,GE(t),e)}var ym=typeof exports==\"object\"&&exports&&!exports.nodeType&&exports,Md=ym&&typeof module==\"object\"&&module&&!module.nodeType&&module,V2=Md&&Md.exports===ym,$d=V2?Pn.Buffer:void 0,kd=$d?$d.allocUnsafe:void 0;function j2(e,t){var n=e.length,r=kd?kd(n):new e.constructor(n);return e.copy(r),r}function z2(e,t){for(var n=-1,r=e==null?0:e.length,o=0,s=[];++n<r;){var i=e[n];t(i,n,e)&&(s[o++]=i)}return s}function wm(){return[]}var H2=Object.prototype,U2=H2.propertyIsEnumerable,Fd=Object.getOwnPropertySymbols,Tc=Fd?function(e){return e==null?[]:(e=Object(e),z2(Fd(e),function(t){return U2.call(e,t)}))}:wm;function K2(e,t){return Za(e,Tc(e),t)}var q2=Object.getOwnPropertySymbols,W2=q2?function(e){for(var t=[];e;)Cc(t,Tc(e)),e=bm(e);return t}:wm;function G2(e,t){return Za(e,W2(e),t)}function Y2(e,t,n){var r=t(e);return dn(e)?r:Cc(r,n(e))}function wu(e){return Y2(e,wc,Tc)}var Su=yo(Pn,\"DataView\"),Eu=yo(Pn,\"Promise\"),_u=yo(Pn,\"Set\"),Bd=\"[object Map]\",J2=\"[object Object]\",Dd=\"[object Promise]\",Vd=\"[object Set]\",jd=\"[object WeakMap]\",zd=\"[object DataView]\",X2=bo(Su),Z2=bo(Ws),Q2=bo(Eu),e_=bo(_u),t_=bo(yu),bn=Qo;(Su&&bn(new Su(new ArrayBuffer(1)))!=zd||Ws&&bn(new Ws)!=Bd||Eu&&bn(Eu.resolve())!=Dd||_u&&bn(new _u)!=Vd||yu&&bn(new yu)!=jd)&&(bn=function(e){var t=Qo(e),n=t==J2?e.constructor:void 0,r=n?bo(n):\"\";if(r)switch(r){case X2:return zd;case Z2:return Bd;case Q2:return Dd;case e_:return Vd;case t_:return jd}return t});var n_=Object.prototype,r_=n_.hasOwnProperty;function o_(e){var t=e.length,n=new e.constructor(t);return t&&typeof e[0]==\"string\"&&r_.call(e,\"index\")&&(n.index=e.index,n.input=e.input),n}var Ca=Pn.Uint8Array;function s_(e){var t=new e.constructor(e.byteLength);return new Ca(t).set(new Ca(e)),t}function i_(e,t){var n=e.buffer;return new e.constructor(n,e.byteOffset,e.byteLength)}var a_=/\\w*$/;function l_(e){var t=new e.constructor(e.source,a_.exec(e));return t.lastIndex=e.lastIndex,t}var Hd=fn?fn.prototype:void 0,Ud=Hd?Hd.valueOf:void 0;function u_(e){return Ud?Object(Ud.call(e)):{}}function c_(e,t){var n=e.buffer;return new e.constructor(n,e.byteOffset,e.length)}var f_=\"[object Boolean]\",d_=\"[object Date]\",p_=\"[object Map]\",h_=\"[object Number]\",v_=\"[object RegExp]\",m_=\"[object Set]\",g_=\"[object String]\",b_=\"[object Symbol]\",y_=\"[object ArrayBuffer]\",w_=\"[object DataView]\",S_=\"[object Float32Array]\",E_=\"[object Float64Array]\",__=\"[object Int8Array]\",C_=\"[object Int16Array]\",T_=\"[object Int32Array]\",O_=\"[object Uint8Array]\",A_=\"[object Uint8ClampedArray]\",R_=\"[object Uint16Array]\",x_=\"[object Uint32Array]\";function I_(e,t,n){var r=e.constructor;switch(t){case y_:return s_(e);case f_:case d_:return new r(+e);case w_:return i_(e);case S_:case E_:case __:case C_:case T_:case O_:case A_:case R_:case x_:return c_(e);case p_:return new r;case h_:case g_:return new r(e);case v_:return l_(e);case m_:return new r;case b_:return u_(e)}}function P_(e){return typeof e.constructor==\"function\"&&!gc(e)?VS(bm(e)):{}}var N_=\"[object Map]\";function L_(e){return $r(e)&&bn(e)==N_}var Kd=Ho&&Ho.isMap,M_=Kd?yc(Kd):L_,$_=\"[object Set]\";function k_(e){return $r(e)&&bn(e)==$_}var qd=Ho&&Ho.isSet,F_=qd?yc(qd):k_,B_=2,Sm=\"[object Arguments]\",D_=\"[object Array]\",V_=\"[object Boolean]\",j_=\"[object Date]\",z_=\"[object Error]\",Em=\"[object Function]\",H_=\"[object GeneratorFunction]\",U_=\"[object Map]\",K_=\"[object Number]\",_m=\"[object Object]\",q_=\"[object RegExp]\",W_=\"[object Set]\",G_=\"[object String]\",Y_=\"[object Symbol]\",J_=\"[object WeakMap]\",X_=\"[object ArrayBuffer]\",Z_=\"[object DataView]\",Q_=\"[object Float32Array]\",eC=\"[object Float64Array]\",tC=\"[object Int8Array]\",nC=\"[object Int16Array]\",rC=\"[object Int32Array]\",oC=\"[object Uint8Array]\",sC=\"[object Uint8ClampedArray]\",iC=\"[object Uint16Array]\",aC=\"[object Uint32Array]\",Qe={};Qe[Sm]=Qe[D_]=Qe[X_]=Qe[Z_]=Qe[V_]=Qe[j_]=Qe[Q_]=Qe[eC]=Qe[tC]=Qe[nC]=Qe[rC]=Qe[U_]=Qe[K_]=Qe[_m]=Qe[q_]=Qe[W_]=Qe[G_]=Qe[Y_]=Qe[oC]=Qe[sC]=Qe[iC]=Qe[aC]=!0;Qe[z_]=Qe[Em]=Qe[J_]=!1;function Xi(e,t,n,r,o,s){var i,a=t&B_;if(i!==void 0)return i;if(!xn(e))return e;var l=dn(e);if(l)return i=o_(e),zS(e,i);var u=bn(e),c=u==Em||u==H_;if(_a(e))return j2(e);if(u==_m||u==Sm||c&&!o)return i=c?{}:P_(e),a?G2(e,D2(i,e)):K2(e,B2(i,e));if(!Qe[u])return o?e:{};i=I_(e,u),s||(s=new Kn);var f=s.get(e);if(f)return f;s.set(e,i),F_(e)?e.forEach(function(p){i.add(Xi(p,t,n,p,e,s))}):M_(e)&&e.forEach(function(p,h){i.set(h,Xi(p,t,n,h,e,s))});var d=wu,v=l?void 0:d(e);return JS(v||e,function(p,h){v&&(h=p,p=e[h]),vc(i,h,Xi(p,t,n,h,e,s))}),i}var lC=4;function Wd(e){return Xi(e,lC)}var uC=\"__lodash_hash_undefined__\";function cC(e){return this.__data__.set(e,uC),this}function fC(e){return this.__data__.has(e)}function Ta(e){var t=-1,n=e==null?0:e.length;for(this.__data__=new br;++t<n;)this.add(e[t])}Ta.prototype.add=Ta.prototype.push=cC;Ta.prototype.has=fC;function dC(e,t){for(var n=-1,r=e==null?0:e.length;++n<r;)if(t(e[n],n,e))return!0;return!1}function pC(e,t){return e.has(t)}var hC=1,vC=2;function Cm(e,t,n,r,o,s){var i=n&hC,a=e.length,l=t.length;if(a!=l&&!(i&&l>a))return!1;var u=s.get(e),c=s.get(t);if(u&&c)return u==t&&c==e;var f=-1,d=!0,v=n&vC?new Ta:void 0;for(s.set(e,t),s.set(t,e);++f<a;){var p=e[f],h=t[f];if(r)var b=i?r(h,p,f,t,e,s):r(p,h,f,e,t,s);if(b!==void 0){if(b)continue;d=!1;break}if(v){if(!dC(t,function(m,S){if(!pC(v,S)&&(p===m||o(p,m,n,r,s)))return v.push(S)})){d=!1;break}}else if(!(p===h||o(p,h,n,r,s))){d=!1;break}}return s.delete(e),s.delete(t),d}function mC(e){var t=-1,n=Array(e.size);return e.forEach(function(r,o){n[++t]=[o,r]}),n}function gC(e){var t=-1,n=Array(e.size);return e.forEach(function(r){n[++t]=r}),n}var bC=1,yC=2,wC=\"[object Boolean]\",SC=\"[object Date]\",EC=\"[object Error]\",_C=\"[object Map]\",CC=\"[object Number]\",TC=\"[object RegExp]\",OC=\"[object Set]\",AC=\"[object String]\",RC=\"[object Symbol]\",xC=\"[object ArrayBuffer]\",IC=\"[object DataView]\",Gd=fn?fn.prototype:void 0,$l=Gd?Gd.valueOf:void 0;function PC(e,t,n,r,o,s,i){switch(n){case IC:if(e.byteLength!=t.byteLength||e.byteOffset!=t.byteOffset)return!1;e=e.buffer,t=t.buffer;case xC:return!(e.byteLength!=t.byteLength||!s(new Ca(e),new Ca(t)));case wC:case SC:case CC:return hc(+e,+t);case EC:return e.name==t.name&&e.message==t.message;case TC:case AC:return e==t+\"\";case _C:var a=mC;case OC:var l=r&bC;if(a||(a=gC),e.size!=t.size&&!l)return!1;var u=i.get(e);if(u)return u==t;r|=yC,i.set(e,t);var c=Cm(a(e),a(t),r,o,s,i);return i.delete(e),c;case RC:if($l)return $l.call(e)==$l.call(t)}return!1}var NC=1,LC=Object.prototype,MC=LC.hasOwnProperty;function $C(e,t,n,r,o,s){var i=n&NC,a=wu(e),l=a.length,u=wu(t),c=u.length;if(l!=c&&!i)return!1;for(var f=l;f--;){var d=a[f];if(!(i?d in t:MC.call(t,d)))return!1}var v=s.get(e),p=s.get(t);if(v&&p)return v==t&&p==e;var h=!0;s.set(e,t),s.set(t,e);for(var b=i;++f<l;){d=a[f];var m=e[d],S=t[d];if(r)var _=i?r(S,m,d,t,e,s):r(m,S,d,e,t,s);if(!(_===void 0?m===S||o(m,S,n,r,s):_)){h=!1;break}b||(b=d==\"constructor\")}if(h&&!b){var w=e.constructor,y=t.constructor;w!=y&&\"constructor\"in e&&\"constructor\"in t&&!(typeof w==\"function\"&&w instanceof w&&typeof y==\"function\"&&y instanceof y)&&(h=!1)}return s.delete(e),s.delete(t),h}var kC=1,Yd=\"[object Arguments]\",Jd=\"[object Array]\",Ri=\"[object Object]\",FC=Object.prototype,Xd=FC.hasOwnProperty;function BC(e,t,n,r,o,s){var i=dn(e),a=dn(t),l=i?Jd:bn(e),u=a?Jd:bn(t);l=l==Yd?Ri:l,u=u==Yd?Ri:u;var c=l==Ri,f=u==Ri,d=l==u;if(d&&_a(e)){if(!_a(t))return!1;i=!0,c=!1}if(d&&!c)return s||(s=new Kn),i||vm(e)?Cm(e,t,n,r,o,s):PC(e,t,l,n,r,o,s);if(!(n&kC)){var v=c&&Xd.call(e,\"__wrapped__\"),p=f&&Xd.call(t,\"__wrapped__\");if(v||p){var h=v?e.value():e,b=p?t.value():t;return s||(s=new Kn),o(h,b,n,r,s)}}return d?(s||(s=new Kn),$C(e,t,n,r,o,s)):!1}function nl(e,t,n,r,o){return e===t?!0:e==null||t==null||!$r(e)&&!$r(t)?e!==e&&t!==t:BC(e,t,n,r,nl,o)}var DC=1,VC=2;function jC(e,t,n,r){var o=n.length,s=o;if(e==null)return!s;for(e=Object(e);o--;){var i=n[o];if(i[2]?i[1]!==e[i[0]]:!(i[0]in e))return!1}for(;++o<s;){i=n[o];var a=i[0],l=e[a],u=i[1];if(i[2]){if(l===void 0&&!(a in e))return!1}else{var c=new Kn,f;if(!(f===void 0?nl(u,l,DC|VC,r,c):f))return!1}}return!0}function Tm(e){return e===e&&!xn(e)}function zC(e){for(var t=wc(e),n=t.length;n--;){var r=t[n],o=e[r];t[n]=[r,o,Tm(o)]}return t}function Om(e,t){return function(n){return n==null?!1:n[e]===t&&(t!==void 0||e in Object(n))}}function HC(e){var t=zC(e);return t.length==1&&t[0][2]?Om(t[0][0],t[0][1]):function(n){return n===e||jC(n,e,t)}}function UC(e,t){return e!=null&&t in Object(e)}function KC(e,t,n){t=tl(t,e);for(var r=-1,o=t.length,s=!1;++r<o;){var i=fi(t[r]);if(!(s=e!=null&&n(e,i)))break;e=e[i]}return s||++r!=o?s:(o=e==null?0:e.length,!!o&&mc(o)&&pc(i,o)&&(dn(e)||bc(e)))}function Am(e,t){return e!=null&&KC(e,t,UC)}var qC=1,WC=2;function GC(e,t){return Sc(e)&&Tm(t)?Om(fi(e),t):function(n){var r=zn(n,e);return r===void 0&&r===t?Am(n,e):nl(t,r,qC|WC)}}function YC(e){return function(t){return t==null?void 0:t[e]}}function JC(e){return function(t){return _c(t,e)}}function XC(e){return Sc(e)?YC(fi(e)):JC(e)}function ZC(e){return typeof e==\"function\"?e:e==null?lm:typeof e==\"object\"?dn(e)?GC(e[0],e[1]):HC(e):XC(e)}var kl=function(){return Pn.Date.now()},QC=\"Expected a function\",eT=Math.max,tT=Math.min;function nT(e,t,n){var r,o,s,i,a,l,u=0,c=!1,f=!1,d=!0;if(typeof e!=\"function\")throw new TypeError(QC);t=Td(t)||0,xn(n)&&(c=!!n.leading,f=\"maxWait\"in n,s=f?eT(Td(n.maxWait)||0,t):s,d=\"trailing\"in n?!!n.trailing:d);function v(A){var R=r,N=o;return r=o=void 0,u=A,i=e.apply(N,R),i}function p(A){return u=A,a=setTimeout(m,t),c?v(A):i}function h(A){var R=A-l,N=A-u,I=t-R;return f?tT(I,s-N):I}function b(A){var R=A-l,N=A-u;return l===void 0||R>=t||R<0||f&&N>=s}function m(){var A=kl();if(b(A))return S(A);a=setTimeout(m,h(A))}function S(A){return a=void 0,d&&r?v(A):(r=o=void 0,i)}function _(){a!==void 0&&clearTimeout(a),u=0,r=l=o=a=void 0}function w(){return a===void 0?i:S(kl())}function y(){var A=kl(),R=b(A);if(r=arguments,o=this,l=A,R){if(a===void 0)return p(l);if(f)return clearTimeout(a),a=setTimeout(m,t),v(l)}return a===void 0&&(a=setTimeout(m,t)),i}return y.cancel=_,y.flush=w,y}function rT(e,t,n){var r=e==null?0:e.length;if(!r)return-1;var o=r-1;return XS(e,ZC(t),o)}function Oa(e){for(var t=-1,n=e==null?0:e.length,r={};++t<n;){var o=e[t];r[o[0]]=o[1]}return r}function Aa(e,t){return nl(e,t)}function qn(e){return e==null}function Rm(e){return e===void 0}function xm(e,t,n,r){if(!xn(e))return e;t=tl(t,e);for(var o=-1,s=t.length,i=s-1,a=e;a!=null&&++o<s;){var l=fi(t[o]),u=n;if(l===\"__proto__\"||l===\"constructor\"||l===\"prototype\")return e;if(o!=i){var c=a[l];u=void 0,u===void 0&&(u=xn(c)?c:pc(t[o+1])?[]:{})}vc(a,l,u),a=a[l]}return e}function oT(e,t,n){for(var r=-1,o=t.length,s={};++r<o;){var i=t[r],a=_c(e,i);n(a,i)&&xm(s,tl(i,e),a)}return s}function sT(e,t){return oT(e,t,function(n,r){return Am(e,r)})}var Im=P2(function(e,t){return e==null?{}:sT(e,t)});function iT(e,t,n){return e==null?e:xm(e,t,n)}const Lt=e=>e===void 0,Bt=e=>typeof e==\"boolean\",je=e=>typeof e==\"number\",ar=e=>typeof Element>\"u\"?!1:e instanceof Element,Cu=e=>qn(e),aT=e=>Ae(e)?!Number.isNaN(Number(e)):!1,lT=(e=\"\")=>e.replace(/[|\\\\{}()[\\]^$+*?.]/g,\"\\\\$&\").replace(/-/g,\"\\\\x2d\"),Pr=e=>si(e),Zd=e=>Object.keys(e),Fl=(e,t,n)=>({get value(){return zn(e,t,n)},set value(r){iT(e,t,r)}});class uT extends Error{constructor(t){super(t),this.name=\"ElementPlusError\"}}function Br(e,t){throw new uT(`[${e}] ${t}`)}const Pm=(e=\"\")=>e.split(\" \").filter(t=>!!t.trim()),Qd=(e,t)=>{if(!e||!t)return!1;if(t.includes(\" \"))throw new Error(\"className should not contain space.\");return e.classList.contains(t)},Tu=(e,t)=>{!e||!t.trim()||e.classList.add(...Pm(t))},Gs=(e,t)=>{!e||!t.trim()||e.classList.remove(...Pm(t))},Po=(e,t)=>{var n;if(!st||!e||!t)return\"\";let r=Mt(t);r===\"float\"&&(r=\"cssFloat\");try{const o=e.style[r];if(o)return o;const s=(n=document.defaultView)==null?void 0:n.getComputedStyle(e,\"\");return s?s[r]:\"\"}catch{return e.style[r]}};function pn(e,t=\"px\"){if(!e)return\"\";if(je(e)||aT(e))return`${e}${t}`;if(Ae(e))return e}let xi;const cT=e=>{var t;if(!st)return 0;if(xi!==void 0)return xi;const n=document.createElement(\"div\");n.className=`${e}-scrollbar__wrap`,n.style.visibility=\"hidden\",n.style.width=\"100px\",n.style.position=\"absolute\",n.style.top=\"-9999px\",document.body.appendChild(n);const r=n.offsetWidth;n.style.overflow=\"scroll\";const o=document.createElement(\"div\");o.style.width=\"100%\",n.appendChild(o);const s=o.offsetWidth;return(t=n.parentNode)==null||t.removeChild(n),xi=r-s,xi};function fT(e,t){if(!st)return;if(!t){e.scrollTop=0;return}const n=[];let r=t.offsetParent;for(;r!==null&&e!==r&&e.contains(r);)n.push(r),r=r.offsetParent;const o=t.offsetTop+n.reduce((l,u)=>l+u.offsetTop,0),s=o+t.offsetHeight,i=e.scrollTop,a=i+e.clientHeight;o<i?e.scrollTop=o:s>a&&(e.scrollTop=s-e.clientHeight)}/*! Element Plus Icons Vue v2.3.2 */var dT=Z({name:\"ArrowDown\",__name:\"arrow-down\",setup(e){return(t,n)=>(L(),ee(\"svg\",{xmlns:\"http://www.w3.org/2000/svg\",viewBox:\"0 0 1024 1024\"},[le(\"path\",{fill:\"currentColor\",d:\"M831.872 340.864 512 652.672 192.128 340.864a30.59 30.59 0 0 0-42.752 0 29.12 29.12 0 0 0 0 41.6L489.664 714.24a32 32 0 0 0 44.672 0l340.288-331.712a29.12 29.12 0 0 0 0-41.728 30.59 30.59 0 0 0-42.752 0z\"})]))}}),Nm=dT,pT=Z({name:\"ArrowLeft\",__name:\"arrow-left\",setup(e){return(t,n)=>(L(),ee(\"svg\",{xmlns:\"http://www.w3.org/2000/svg\",viewBox:\"0 0 1024 1024\"},[le(\"path\",{fill:\"currentColor\",d:\"M609.408 149.376 277.76 489.6a32 32 0 0 0 0 44.672l331.648 340.352a29.12 29.12 0 0 0 41.728 0 30.59 30.59 0 0 0 0-42.752L339.264 511.936l311.872-319.872a30.59 30.59 0 0 0 0-42.688 29.12 29.12 0 0 0-41.728 0\"})]))}}),hT=pT,vT=Z({name:\"ArrowRight\",__name:\"arrow-right\",setup(e){return(t,n)=>(L(),ee(\"svg\",{xmlns:\"http://www.w3.org/2000/svg\",viewBox:\"0 0 1024 1024\"},[le(\"path\",{fill:\"currentColor\",d:\"M340.864 149.312a30.59 30.59 0 0 0 0 42.752L652.736 512 340.864 831.872a30.59 30.59 0 0 0 0 42.752 29.12 29.12 0 0 0 41.728 0L714.24 534.336a32 32 0 0 0 0-44.672L382.592 149.376a29.12 29.12 0 0 0-41.728 0z\"})]))}}),mT=vT,gT=Z({name:\"ArrowUp\",__name:\"arrow-up\",setup(e){return(t,n)=>(L(),ee(\"svg\",{xmlns:\"http://www.w3.org/2000/svg\",viewBox:\"0 0 1024 1024\"},[le(\"path\",{fill:\"currentColor\",d:\"m488.832 344.32-339.84 356.672a32 32 0 0 0 0 44.16l.384.384a29.44 29.44 0 0 0 42.688 0l320-335.872 319.872 335.872a29.44 29.44 0 0 0 42.688 0l.384-.384a32 32 0 0 0 0-44.16L535.168 344.32a32 32 0 0 0-46.336 0\"})]))}}),bT=gT,yT=Z({name:\"CircleCheckFilled\",__name:\"circle-check-filled\",setup(e){return(t,n)=>(L(),ee(\"svg\",{xmlns:\"http://www.w3.org/2000/svg\",viewBox:\"0 0 1024 1024\"},[le(\"path\",{fill:\"currentColor\",d:\"M512 64a448 448 0 1 1 0 896 448 448 0 0 1 0-896m-55.808 536.384-99.52-99.584a38.4 38.4 0 1 0-54.336 54.336l126.72 126.72a38.27 38.27 0 0 0 54.336 0l262.4-262.464a38.4 38.4 0 1 0-54.272-54.336z\"})]))}}),_N=yT,wT=Z({name:\"CircleCheck\",__name:\"circle-check\",setup(e){return(t,n)=>(L(),ee(\"svg\",{xmlns:\"http://www.w3.org/2000/svg\",viewBox:\"0 0 1024 1024\"},[le(\"path\",{fill:\"currentColor\",d:\"M512 896a384 384 0 1 0 0-768 384 384 0 0 0 0 768m0 64a448 448 0 1 1 0-896 448 448 0 0 1 0 896\"}),le(\"path\",{fill:\"currentColor\",d:\"M745.344 361.344a32 32 0 0 1 45.312 45.312l-288 288a32 32 0 0 1-45.312 0l-160-160a32 32 0 1 1 45.312-45.312L480 626.752z\"})]))}}),ST=wT,ET=Z({name:\"CircleCloseFilled\",__name:\"circle-close-filled\",setup(e){return(t,n)=>(L(),ee(\"svg\",{xmlns:\"http://www.w3.org/2000/svg\",viewBox:\"0 0 1024 1024\"},[le(\"path\",{fill:\"currentColor\",d:\"M512 64a448 448 0 1 1 0 896 448 448 0 0 1 0-896m0 393.664L407.936 353.6a38.4 38.4 0 1 0-54.336 54.336L457.664 512 353.6 616.064a38.4 38.4 0 1 0 54.336 54.336L512 566.336 616.064 670.4a38.4 38.4 0 1 0 54.336-54.336L566.336 512 670.4 407.936a38.4 38.4 0 1 0-54.336-54.336z\"})]))}}),Lm=ET,_T=Z({name:\"CircleClose\",__name:\"circle-close\",setup(e){return(t,n)=>(L(),ee(\"svg\",{xmlns:\"http://www.w3.org/2000/svg\",viewBox:\"0 0 1024 1024\"},[le(\"path\",{fill:\"currentColor\",d:\"m466.752 512-90.496-90.496a32 32 0 0 1 45.248-45.248L512 466.752l90.496-90.496a32 32 0 1 1 45.248 45.248L557.248 512l90.496 90.496a32 32 0 1 1-45.248 45.248L512 557.248l-90.496 90.496a32 32 0 0 1-45.248-45.248z\"}),le(\"path\",{fill:\"currentColor\",d:\"M512 896a384 384 0 1 0 0-768 384 384 0 0 0 0 768m0 64a448 448 0 1 1 0-896 448 448 0 0 1 0 896\"})]))}}),Oc=_T,CT=Z({name:\"Close\",__name:\"close\",setup(e){return(t,n)=>(L(),ee(\"svg\",{xmlns:\"http://www.w3.org/2000/svg\",viewBox:\"0 0 1024 1024\"},[le(\"path\",{fill:\"currentColor\",d:\"M764.288 214.592 512 466.88 259.712 214.592a31.936 31.936 0 0 0-45.12 45.12L466.752 512 214.528 764.224a31.936 31.936 0 1 0 45.12 45.184L512 557.184l252.288 252.288a31.936 31.936 0 0 0 45.12-45.12L557.12 512.064l252.288-252.352a31.936 31.936 0 1 0-45.12-45.184z\"})]))}}),Ys=CT,TT=Z({name:\"Delete\",__name:\"delete\",setup(e){return(t,n)=>(L(),ee(\"svg\",{xmlns:\"http://www.w3.org/2000/svg\",viewBox:\"0 0 1024 1024\"},[le(\"path\",{fill:\"currentColor\",d:\"M160 256H96a32 32 0 0 1 0-64h256V95.936a32 32 0 0 1 32-32h256a32 32 0 0 1 32 32V192h256a32 32 0 1 1 0 64h-64v672a32 32 0 0 1-32 32H192a32 32 0 0 1-32-32zm448-64v-64H416v64zM224 896h576V256H224zm192-128a32 32 0 0 1-32-32V416a32 32 0 0 1 64 0v320a32 32 0 0 1-32 32m192 0a32 32 0 0 1-32-32V416a32 32 0 0 1 64 0v320a32 32 0 0 1-32 32\"})]))}}),CN=TT,OT=Z({name:\"Download\",__name:\"download\",setup(e){return(t,n)=>(L(),ee(\"svg\",{xmlns:\"http://www.w3.org/2000/svg\",viewBox:\"0 0 1024 1024\"},[le(\"path\",{fill:\"currentColor\",d:\"M160 832h704a32 32 0 1 1 0 64H160a32 32 0 1 1 0-64m384-253.696 236.288-236.352 45.248 45.248L508.8 704 192 387.2l45.248-45.248L480 584.704V128h64z\"})]))}}),TN=OT,AT=Z({name:\"Edit\",__name:\"edit\",setup(e){return(t,n)=>(L(),ee(\"svg\",{xmlns:\"http://www.w3.org/2000/svg\",viewBox:\"0 0 1024 1024\"},[le(\"path\",{fill:\"currentColor\",d:\"M832 512a32 32 0 1 1 64 0v352a32 32 0 0 1-32 32H160a32 32 0 0 1-32-32V160a32 32 0 0 1 32-32h352a32 32 0 0 1 0 64H192v640h640z\"}),le(\"path\",{fill:\"currentColor\",d:\"m469.952 554.24 52.8-7.552L847.104 222.4a32 32 0 1 0-45.248-45.248L477.44 501.44l-7.552 52.8zm422.4-422.4a96 96 0 0 1 0 135.808l-331.84 331.84a32 32 0 0 1-18.112 9.088L436.8 623.68a32 32 0 0 1-36.224-36.224l15.104-105.6a32 32 0 0 1 9.024-18.112l331.904-331.84a96 96 0 0 1 135.744 0z\"})]))}}),ON=AT,RT=Z({name:\"Folder\",__name:\"folder\",setup(e){return(t,n)=>(L(),ee(\"svg\",{xmlns:\"http://www.w3.org/2000/svg\",viewBox:\"0 0 1024 1024\"},[le(\"path\",{fill:\"currentColor\",d:\"M128 192v640h768V320H485.76L357.504 192zm-32-64h287.872l128.384 128H928a32 32 0 0 1 32 32v576a32 32 0 0 1-32 32H96a32 32 0 0 1-32-32V160a32 32 0 0 1 32-32\"})]))}}),AN=RT,xT=Z({name:\"Hide\",__name:\"hide\",setup(e){return(t,n)=>(L(),ee(\"svg\",{xmlns:\"http://www.w3.org/2000/svg\",viewBox:\"0 0 1024 1024\"},[le(\"path\",{fill:\"currentColor\",d:\"M876.8 156.8c0-9.6-3.2-16-9.6-22.4s-12.8-9.6-22.4-9.6-16 3.2-22.4 9.6L736 220.8c-64-32-137.6-51.2-224-60.8-160 16-288 73.6-377.6 176S0 496 0 512s48 73.6 134.4 176c22.4 25.6 44.8 48 73.6 67.2l-86.4 89.6c-6.4 6.4-9.6 12.8-9.6 22.4s3.2 16 9.6 22.4 12.8 9.6 22.4 9.6 16-3.2 22.4-9.6l704-710.4c3.2-6.4 6.4-12.8 6.4-22.4m-646.4 528Q115.2 579.2 76.8 512q43.2-72 153.6-172.8C304 272 400 230.4 512 224c64 3.2 124.8 19.2 176 44.8l-54.4 54.4C598.4 300.8 560 288 512 288c-64 0-115.2 22.4-160 64s-64 96-64 160c0 48 12.8 89.6 35.2 124.8L256 707.2c-9.6-6.4-19.2-16-25.6-22.4m140.8-96Q352 555.2 352 512c0-44.8 16-83.2 48-112s67.2-48 112-48c28.8 0 54.4 6.4 73.6 19.2zM889.599 336c-12.8-16-28.8-28.8-41.6-41.6l-48 48c73.6 67.2 124.8 124.8 150.4 169.6q-43.2 72-153.6 172.8c-73.6 67.2-172.8 108.8-284.8 115.2-51.2-3.2-99.2-12.8-140.8-28.8l-48 48c57.6 22.4 118.4 38.4 188.8 44.8 160-16 288-73.6 377.6-176S1024 528 1024 512s-48.001-73.6-134.401-176\"}),le(\"path\",{fill:\"currentColor\",d:\"M511.998 672c-12.8 0-25.6-3.2-38.4-6.4l-51.2 51.2c28.8 12.8 57.6 19.2 89.6 19.2 64 0 115.2-22.4 160-64 41.6-41.6 64-96 64-160 0-32-6.4-64-19.2-89.6l-51.2 51.2c3.2 12.8 6.4 25.6 6.4 38.4 0 44.8-16 83.2-48 112s-67.2 48-112 48\"})]))}}),IT=xT,PT=Z({name:\"InfoFilled\",__name:\"info-filled\",setup(e){return(t,n)=>(L(),ee(\"svg\",{xmlns:\"http://www.w3.org/2000/svg\",viewBox:\"0 0 1024 1024\"},[le(\"path\",{fill:\"currentColor\",d:\"M512 64a448 448 0 1 1 0 896.064A448 448 0 0 1 512 64m67.2 275.072c33.28 0 60.288-23.104 60.288-57.344s-27.072-57.344-60.288-57.344c-33.28 0-60.16 23.104-60.16 57.344s26.88 57.344 60.16 57.344M590.912 699.2c0-6.848 2.368-24.64 1.024-34.752l-52.608 60.544c-10.88 11.456-24.512 19.392-30.912 17.28a12.99 12.99 0 0 1-8.256-14.72l87.68-276.992c7.168-35.136-12.544-67.2-54.336-71.296-44.096 0-108.992 44.736-148.48 101.504 0 6.784-1.28 23.68.064 33.792l52.544-60.608c10.88-11.328 23.552-19.328 29.952-17.152a12.8 12.8 0 0 1 7.808 16.128L388.48 728.576c-10.048 32.256 8.96 63.872 55.04 71.04 67.84 0 107.904-43.648 147.456-100.416z\"})]))}}),Mm=PT,NT=Z({name:\"Link\",__name:\"link\",setup(e){return(t,n)=>(L(),ee(\"svg\",{xmlns:\"http://www.w3.org/2000/svg\",viewBox:\"0 0 1024 1024\"},[le(\"path\",{fill:\"currentColor\",d:\"M715.648 625.152 670.4 579.904l90.496-90.56c75.008-74.944 85.12-186.368 22.656-248.896-62.528-62.464-173.952-52.352-248.96 22.656L444.16 353.6l-45.248-45.248 90.496-90.496c100.032-99.968 251.968-110.08 339.456-22.656 87.488 87.488 77.312 239.424-22.656 339.456l-90.496 90.496zm-90.496 90.496-90.496 90.496C434.624 906.112 282.688 916.224 195.2 828.8c-87.488-87.488-77.312-239.424 22.656-339.456l90.496-90.496 45.248 45.248-90.496 90.56c-75.008 74.944-85.12 186.368-22.656 248.896 62.528 62.464 173.952 52.352 248.96-22.656l90.496-90.496zm0-362.048 45.248 45.248L398.848 670.4 353.6 625.152z\"})]))}}),RN=NT,LT=Z({name:\"Loading\",__name:\"loading\",setup(e){return(t,n)=>(L(),ee(\"svg\",{xmlns:\"http://www.w3.org/2000/svg\",viewBox:\"0 0 1024 1024\"},[le(\"path\",{fill:\"currentColor\",d:\"M512 64a32 32 0 0 1 32 32v192a32 32 0 0 1-64 0V96a32 32 0 0 1 32-32m0 640a32 32 0 0 1 32 32v192a32 32 0 1 1-64 0V736a32 32 0 0 1 32-32m448-192a32 32 0 0 1-32 32H736a32 32 0 1 1 0-64h192a32 32 0 0 1 32 32m-640 0a32 32 0 0 1-32 32H96a32 32 0 0 1 0-64h192a32 32 0 0 1 32 32M195.2 195.2a32 32 0 0 1 45.248 0L376.32 331.008a32 32 0 0 1-45.248 45.248L195.2 240.448a32 32 0 0 1 0-45.248m452.544 452.544a32 32 0 0 1 45.248 0L828.8 783.552a32 32 0 0 1-45.248 45.248L647.744 692.992a32 32 0 0 1 0-45.248M828.8 195.264a32 32 0 0 1 0 45.184L692.992 376.32a32 32 0 0 1-45.248-45.248l135.808-135.808a32 32 0 0 1 45.248 0m-452.544 452.48a32 32 0 0 1 0 45.248L240.448 828.8a32 32 0 0 1-45.248-45.248l135.808-135.808a32 32 0 0 1 45.248 0\"})]))}}),Js=LT,MT=Z({name:\"Minus\",__name:\"minus\",setup(e){return(t,n)=>(L(),ee(\"svg\",{xmlns:\"http://www.w3.org/2000/svg\",viewBox:\"0 0 1024 1024\"},[le(\"path\",{fill:\"currentColor\",d:\"M128 544h768a32 32 0 1 0 0-64H128a32 32 0 0 0 0 64\"})]))}}),$T=MT,kT=Z({name:\"Plus\",__name:\"plus\",setup(e){return(t,n)=>(L(),ee(\"svg\",{xmlns:\"http://www.w3.org/2000/svg\",viewBox:\"0 0 1024 1024\"},[le(\"path\",{fill:\"currentColor\",d:\"M480 480V128a32 32 0 0 1 64 0v352h352a32 32 0 1 1 0 64H544v352a32 32 0 1 1-64 0V544H128a32 32 0 0 1 0-64z\"})]))}}),$m=kT,FT=Z({name:\"Search\",__name:\"search\",setup(e){return(t,n)=>(L(),ee(\"svg\",{xmlns:\"http://www.w3.org/2000/svg\",viewBox:\"0 0 1024 1024\"},[le(\"path\",{fill:\"currentColor\",d:\"m795.904 750.72 124.992 124.928a32 32 0 0 1-45.248 45.248L750.656 795.904a416 416 0 1 1 45.248-45.248zM480 832a352 352 0 1 0 0-704 352 352 0 0 0 0 704\"})]))}}),xN=FT,BT=Z({name:\"SuccessFilled\",__name:\"success-filled\",setup(e){return(t,n)=>(L(),ee(\"svg\",{xmlns:\"http://www.w3.org/2000/svg\",viewBox:\"0 0 1024 1024\"},[le(\"path\",{fill:\"currentColor\",d:\"M512 64a448 448 0 1 1 0 896 448 448 0 0 1 0-896m-55.808 536.384-99.52-99.584a38.4 38.4 0 1 0-54.336 54.336l126.72 126.72a38.27 38.27 0 0 0 54.336 0l262.4-262.464a38.4 38.4 0 1 0-54.272-54.336z\"})]))}}),km=BT,DT=Z({name:\"View\",__name:\"view\",setup(e){return(t,n)=>(L(),ee(\"svg\",{xmlns:\"http://www.w3.org/2000/svg\",viewBox:\"0 0 1024 1024\"},[le(\"path\",{fill:\"currentColor\",d:\"M512 160c320 0 512 352 512 352S832 864 512 864 0 512 0 512s192-352 512-352m0 64c-225.28 0-384.128 208.064-436.8 288 52.608 79.872 211.456 288 436.8 288 225.28 0 384.128-208.064 436.8-288-52.608-79.872-211.456-288-436.8-288m0 64a224 224 0 1 1 0 448 224 224 0 0 1 0-448m0 64a160.19 160.19 0 0 0-160 160c0 88.192 71.744 160 160 160s160-71.808 160-160-71.744-160-160-160\"})]))}}),VT=DT,jT=Z({name:\"WarningFilled\",__name:\"warning-filled\",setup(e){return(t,n)=>(L(),ee(\"svg\",{xmlns:\"http://www.w3.org/2000/svg\",viewBox:\"0 0 1024 1024\"},[le(\"path\",{fill:\"currentColor\",d:\"M512 64a448 448 0 1 1 0 896 448 448 0 0 1 0-896m0 192a58.43 58.43 0 0 0-58.24 63.744l23.36 256.384a35.072 35.072 0 0 0 69.76 0l23.296-256.384A58.43 58.43 0 0 0 512 256m0 512a51.2 51.2 0 1 0 0-102.4 51.2 51.2 0 0 0 0 102.4\"})]))}}),Fm=jT;const Bm=\"__epPropKey\",we=e=>e,zT=e=>Oe(e)&&!!e[Bm],rl=(e,t)=>{if(!Oe(e)||zT(e))return e;const{values:n,required:r,default:o,type:s,validator:i}=e,l={type:s,required:!!r,validator:n||i?u=>{let c=!1,f=[];if(n&&(f=Array.from(n),Ve(e,\"default\")&&f.push(o),c||(c=f.includes(u))),i&&(c||(c=i(u))),!c&&f.length>0){const d=[...new Set(f)].map(v=>JSON.stringify(v)).join(\", \");k0(`Invalid prop: validation failed${t?` for prop \"${t}\"`:\"\"}. Expected one of [${d}], got value ${JSON.stringify(u)}.`)}return c}:void 0,[Bm]:!0};return Ve(e,\"default\")&&(l.default=o),l},xe=e=>Oa(Object.entries(e).map(([t,n])=>[t,rl(n,t)])),jt=we([String,Object,Function]),HT={Close:Ys},Dm={Close:Ys,SuccessFilled:km,InfoFilled:Mm,WarningFilled:Fm,CircleCloseFilled:Lm},Ra={success:km,warning:Fm,error:Lm,info:Mm},Vm={validating:Js,success:ST,error:Oc},yt=(e,t)=>{if(e.install=n=>{for(const r of[e,...Object.values(t??{})])n.component(r.name,r)},t)for(const[n,r]of Object.entries(t))e[n]=r;return e},UT=(e,t)=>(e.install=n=>{e._context=n._context,n.config.globalProperties[t]=e},e),KT=(e,t)=>(e.install=n=>{n.directive(t,e)},e),wo=e=>(e.install=ht,e),qT=(...e)=>t=>{e.forEach(n=>{ve(n)?n(t):n.value=t})},_n={tab:\"Tab\",enter:\"Enter\",space:\"Space\",left:\"ArrowLeft\",up:\"ArrowUp\",right:\"ArrowRight\",down:\"ArrowDown\",esc:\"Escape\",delete:\"Delete\",backspace:\"Backspace\"},rt=\"update:modelValue\",fo=\"change\",io=\"input\",es=[\"\",\"default\",\"small\",\"large\"],jm=e=>[\"\",...es].includes(e);var Zi=(e=>(e[e.TEXT=1]=\"TEXT\",e[e.CLASS=2]=\"CLASS\",e[e.STYLE=4]=\"STYLE\",e[e.PROPS=8]=\"PROPS\",e[e.FULL_PROPS=16]=\"FULL_PROPS\",e[e.HYDRATE_EVENTS=32]=\"HYDRATE_EVENTS\",e[e.STABLE_FRAGMENT=64]=\"STABLE_FRAGMENT\",e[e.KEYED_FRAGMENT=128]=\"KEYED_FRAGMENT\",e[e.UNKEYED_FRAGMENT=256]=\"UNKEYED_FRAGMENT\",e[e.NEED_PATCH=512]=\"NEED_PATCH\",e[e.DYNAMIC_SLOTS=1024]=\"DYNAMIC_SLOTS\",e[e.HOISTED=-1]=\"HOISTED\",e[e.BAIL=-2]=\"BAIL\",e))(Zi||{});const Qi=e=>{const t=pe(e)?e:[e],n=[];return t.forEach(r=>{var o;pe(r)?n.push(...Qi(r)):cn(r)&&pe(r.children)?n.push(...Qi(r.children)):(n.push(r),cn(r)&&((o=r.component)!=null&&o.subTree)&&n.push(...Qi(r.component.subTree)))}),n},WT=e=>/([\\uAC00-\\uD7AF\\u3130-\\u318F])+/gi.test(e),ol=e=>e,GT=[\"class\",\"style\"],YT=/^on[A-Z]/,JT=(e={})=>{const{excludeListeners:t=!1,excludeKeys:n}=e,r=C(()=>((n==null?void 0:n.value)||[]).concat(GT)),o=Ge();return C(o?()=>{var s;return Oa(Object.entries((s=o.proxy)==null?void 0:s.$attrs).filter(([i])=>!r.value.includes(i)&&!(t&&YT.test(i))))}:()=>({}))},xs=({from:e,replacement:t,scope:n,version:r,ref:o,type:s=\"API\"},i)=>{he(()=>g(i),a=>{},{immediate:!0})},zm=(e,t,n,r)=>{let o={offsetX:0,offsetY:0};const s=u=>{const c=u.clientX,f=u.clientY,{offsetX:d,offsetY:v}=o,p=e.value.getBoundingClientRect(),h=p.left,b=p.top,m=p.width,S=p.height,_=document.documentElement.clientWidth,w=document.documentElement.clientHeight,y=-h+d,A=-b+v,R=_-h-m+d,N=w-b-S+v,I=k=>{let $=d+k.clientX-c,F=v+k.clientY-f;r!=null&&r.value||($=Math.min(Math.max($,y),R),F=Math.min(Math.max(F,A),N)),o={offsetX:$,offsetY:F},e.value&&(e.value.style.transform=`translate(${pn($)}, ${pn(F)})`)},x=()=>{document.removeEventListener(\"mousemove\",I),document.removeEventListener(\"mouseup\",x)};document.addEventListener(\"mousemove\",I),document.addEventListener(\"mouseup\",x)},i=()=>{t.value&&e.value&&t.value.addEventListener(\"mousedown\",s)},a=()=>{t.value&&e.value&&t.value.removeEventListener(\"mousedown\",s)},l=()=>{o={offsetX:0,offsetY:0},e.value&&(e.value.style.transform=\"none\")};return Ke(()=>{Ha(()=>{n.value?i():a()})}),St(()=>{a()}),{resetPosition:l}};var XT={name:\"en\",el:{breadcrumb:{label:\"Breadcrumb\"},colorpicker:{confirm:\"OK\",clear:\"Clear\",defaultLabel:\"color picker\",description:\"current color is {color}. press enter to select a new color.\",alphaLabel:\"pick alpha value\"},datepicker:{now:\"Now\",today:\"Today\",cancel:\"Cancel\",clear:\"Clear\",confirm:\"OK\",dateTablePrompt:\"Use the arrow keys and enter to select the day of the month\",monthTablePrompt:\"Use the arrow keys and enter to select the month\",yearTablePrompt:\"Use the arrow keys and enter to select the year\",selectedDate:\"Selected date\",selectDate:\"Select date\",selectTime:\"Select time\",startDate:\"Start Date\",startTime:\"Start Time\",endDate:\"End Date\",endTime:\"End Time\",prevYear:\"Previous Year\",nextYear:\"Next Year\",prevMonth:\"Previous Month\",nextMonth:\"Next Month\",year:\"\",month1:\"January\",month2:\"February\",month3:\"March\",month4:\"April\",month5:\"May\",month6:\"June\",month7:\"July\",month8:\"August\",month9:\"September\",month10:\"October\",month11:\"November\",month12:\"December\",week:\"week\",weeks:{sun:\"Sun\",mon:\"Mon\",tue:\"Tue\",wed:\"Wed\",thu:\"Thu\",fri:\"Fri\",sat:\"Sat\"},weeksFull:{sun:\"Sunday\",mon:\"Monday\",tue:\"Tuesday\",wed:\"Wednesday\",thu:\"Thursday\",fri:\"Friday\",sat:\"Saturday\"},months:{jan:\"Jan\",feb:\"Feb\",mar:\"Mar\",apr:\"Apr\",may:\"May\",jun:\"Jun\",jul:\"Jul\",aug:\"Aug\",sep:\"Sep\",oct:\"Oct\",nov:\"Nov\",dec:\"Dec\"}},inputNumber:{decrease:\"decrease number\",increase:\"increase number\"},select:{loading:\"Loading\",noMatch:\"No matching data\",noData:\"No data\",placeholder:\"Select\"},mention:{loading:\"Loading\"},dropdown:{toggleDropdown:\"Toggle Dropdown\"},cascader:{noMatch:\"No matching data\",loading:\"Loading\",placeholder:\"Select\",noData:\"No data\"},pagination:{goto:\"Go to\",pagesize:\"/page\",total:\"Total {total}\",pageClassifier:\"\",page:\"Page\",prev:\"Go to previous page\",next:\"Go to next page\",currentPage:\"page {pager}\",prevPages:\"Previous {pager} pages\",nextPages:\"Next {pager} pages\",deprecationWarning:\"Deprecated usages detected, please refer to the el-pagination documentation for more details\"},dialog:{close:\"Close this dialog\"},drawer:{close:\"Close this dialog\"},messagebox:{title:\"Message\",confirm:\"OK\",cancel:\"Cancel\",error:\"Illegal input\",close:\"Close this dialog\"},upload:{deleteTip:\"press delete to remove\",delete:\"Delete\",preview:\"Preview\",continue:\"Continue\"},slider:{defaultLabel:\"slider between {min} and {max}\",defaultRangeStartLabel:\"pick start value\",defaultRangeEndLabel:\"pick end value\"},table:{emptyText:\"No Data\",confirmFilter:\"Confirm\",resetFilter:\"Reset\",clearFilter:\"All\",sumText:\"Sum\"},tour:{next:\"Next\",previous:\"Previous\",finish:\"Finish\"},tree:{emptyText:\"No Data\"},transfer:{noMatch:\"No matching data\",noData:\"No data\",titles:[\"List 1\",\"List 2\"],filterPlaceholder:\"Enter keyword\",noCheckedFormat:\"{total} items\",hasCheckedFormat:\"{checked}/{total} checked\"},image:{error:\"FAILED\"},pageHeader:{title:\"Back\"},popconfirm:{confirmButtonText:\"Yes\",cancelButtonText:\"No\"},carousel:{leftArrow:\"Carousel arrow left\",rightArrow:\"Carousel arrow right\",indicator:\"Carousel switch to index {index}\"}}};const ZT=e=>(t,n)=>QT(t,n,g(e)),QT=(e,t,n)=>zn(n,e,e).replace(/\\{(\\w+)\\}/g,(r,o)=>{var s;return`${(s=t==null?void 0:t[o])!=null?s:`{${o}}`}`}),eO=e=>({lang:C(()=>g(e).name),locale:Ue(e)?e:V(e),t:ZT(e)}),Hm=Symbol(\"localeContextKey\"),sl=e=>{const t=e||Ee(Hm,V());return eO(C(()=>t.value||XT))},Is=\"el\",tO=\"is-\",Wr=(e,t,n,r,o)=>{let s=`${e}-${t}`;return n&&(s+=`-${n}`),r&&(s+=`__${r}`),o&&(s+=`--${o}`),s},Um=Symbol(\"namespaceContextKey\"),Ac=e=>{const t=e||(Ge()?Ee(Um,V(Is)):V(Is));return C(()=>g(t)||Is)},$e=(e,t)=>{const n=Ac(t);return{namespace:n,b:(h=\"\")=>Wr(n.value,e,h,\"\",\"\"),e:h=>h?Wr(n.value,e,\"\",h,\"\"):\"\",m:h=>h?Wr(n.value,e,\"\",\"\",h):\"\",be:(h,b)=>h&&b?Wr(n.value,e,h,b,\"\"):\"\",em:(h,b)=>h&&b?Wr(n.value,e,\"\",h,b):\"\",bm:(h,b)=>h&&b?Wr(n.value,e,h,\"\",b):\"\",bem:(h,b,m)=>h&&b&&m?Wr(n.value,e,h,b,m):\"\",is:(h,...b)=>{const m=b.length>=1?b[0]:!0;return h&&m?`${tO}${h}`:\"\"},cssVar:h=>{const b={};for(const m in h)h[m]&&(b[`--${n.value}-${m}`]=h[m]);return b},cssVarName:h=>`--${n.value}-${h}`,cssVarBlock:h=>{const b={};for(const m in h)h[m]&&(b[`--${n.value}-${e}-${m}`]=h[m]);return b},cssVarBlockName:h=>`--${n.value}-${e}-${h}`}},Km=(e,t={})=>{Ue(e)||Br(\"[useLockscreen]\",\"You need to pass a ref param to this function\");const n=t.ns||$e(\"popup\"),r=C(()=>n.bm(\"parent\",\"hidden\"));if(!st||Qd(document.body,r.value))return;let o=0,s=!1,i=\"0\";const a=()=>{setTimeout(()=>{typeof document>\"u\"||(Gs(document==null?void 0:document.body,r.value),s&&document&&(document.body.style.width=i))},200)};he(e,l=>{if(!l){a();return}s=!Qd(document.body,r.value),s&&(i=document.body.style.width),o=cT(n.namespace.value);const u=document.documentElement.clientHeight<document.body.scrollHeight,c=Po(document.body,\"overflowY\");o>0&&(u||c===\"scroll\")&&s&&(document.body.style.width=`calc(100% - ${o}px)`),Tu(document.body,r.value)}),Ba(()=>a())},nO=rl({type:we(Boolean),default:null}),rO=rl({type:we(Function)}),oO=e=>{const t=`update:${e}`,n=`onUpdate:${e}`,r=[t],o={[e]:nO,[n]:rO};return{useModelToggle:({indicator:i,toggleReason:a,shouldHideWhenRouteChanges:l,shouldProceed:u,onShow:c,onHide:f})=>{const d=Ge(),{emit:v}=d,p=d.props,h=C(()=>ve(p[n])),b=C(()=>p[e]===null),m=R=>{i.value!==!0&&(i.value=!0,a&&(a.value=R),ve(c)&&c(R))},S=R=>{i.value!==!1&&(i.value=!1,a&&(a.value=R),ve(f)&&f(R))},_=R=>{if(p.disabled===!0||ve(u)&&!u())return;const N=h.value&&st;N&&v(t,!0),(b.value||!N)&&m(R)},w=R=>{if(p.disabled===!0||!st)return;const N=h.value&&st;N&&v(t,!1),(b.value||!N)&&S(R)},y=R=>{Bt(R)&&(p.disabled&&R?h.value&&v(t,!1):i.value!==R&&(R?m():S()))},A=()=>{i.value?w():_()};return he(()=>p[e],y),l&&d.appContext.config.globalProperties.$route!==void 0&&he(()=>({...d.proxy.$route}),()=>{l.value&&i.value&&w()}),Ke(()=>{y(p[e])}),{hide:w,show:_,toggle:A,hasUpdateHandler:h}},useModelToggleProps:o,useModelToggleEmits:r}},qm=e=>{const t=Ge();return C(()=>{var n,r;return(r=(n=t==null?void 0:t.proxy)==null?void 0:n.$props)==null?void 0:r[e]})};var zt=\"top\",hn=\"bottom\",vn=\"right\",Ht=\"left\",Rc=\"auto\",di=[zt,hn,vn,Ht],Uo=\"start\",Xs=\"end\",sO=\"clippingParents\",Wm=\"viewport\",hs=\"popper\",iO=\"reference\",ep=di.reduce(function(e,t){return e.concat([t+\"-\"+Uo,t+\"-\"+Xs])},[]),il=[].concat(di,[Rc]).reduce(function(e,t){return e.concat([t,t+\"-\"+Uo,t+\"-\"+Xs])},[]),aO=\"beforeRead\",lO=\"read\",uO=\"afterRead\",cO=\"beforeMain\",fO=\"main\",dO=\"afterMain\",pO=\"beforeWrite\",hO=\"write\",vO=\"afterWrite\",mO=[aO,lO,uO,cO,fO,dO,pO,hO,vO];function Gn(e){return e?(e.nodeName||\"\").toLowerCase():null}function nn(e){if(e==null)return window;if(e.toString()!==\"[object Window]\"){var t=e.ownerDocument;return t&&t.defaultView||window}return e}function po(e){var t=nn(e).Element;return e instanceof t||e instanceof Element}function un(e){var t=nn(e).HTMLElement;return e instanceof t||e instanceof HTMLElement}function xc(e){if(typeof ShadowRoot>\"u\")return!1;var t=nn(e).ShadowRoot;return e instanceof t||e instanceof ShadowRoot}function gO(e){var t=e.state;Object.keys(t.elements).forEach(function(n){var r=t.styles[n]||{},o=t.attributes[n]||{},s=t.elements[n];!un(s)||!Gn(s)||(Object.assign(s.style,r),Object.keys(o).forEach(function(i){var a=o[i];a===!1?s.removeAttribute(i):s.setAttribute(i,a===!0?\"\":a)}))})}function bO(e){var t=e.state,n={popper:{position:t.options.strategy,left:\"0\",top:\"0\",margin:\"0\"},arrow:{position:\"absolute\"},reference:{}};return Object.assign(t.elements.popper.style,n.popper),t.styles=n,t.elements.arrow&&Object.assign(t.elements.arrow.style,n.arrow),function(){Object.keys(t.elements).forEach(function(r){var o=t.elements[r],s=t.attributes[r]||{},i=Object.keys(t.styles.hasOwnProperty(r)?t.styles[r]:n[r]),a=i.reduce(function(l,u){return l[u]=\"\",l},{});!un(o)||!Gn(o)||(Object.assign(o.style,a),Object.keys(s).forEach(function(l){o.removeAttribute(l)}))})}}var Gm={name:\"applyStyles\",enabled:!0,phase:\"write\",fn:gO,effect:bO,requires:[\"computeStyles\"]};function Wn(e){return e.split(\"-\")[0]}var ao=Math.max,xa=Math.min,Ko=Math.round;function Ou(){var e=navigator.userAgentData;return e!=null&&e.brands&&Array.isArray(e.brands)?e.brands.map(function(t){return t.brand+\"/\"+t.version}).join(\" \"):navigator.userAgent}function Ym(){return!/^((?!chrome|android).)*safari/i.test(Ou())}function qo(e,t,n){t===void 0&&(t=!1),n===void 0&&(n=!1);var r=e.getBoundingClientRect(),o=1,s=1;t&&un(e)&&(o=e.offsetWidth>0&&Ko(r.width)/e.offsetWidth||1,s=e.offsetHeight>0&&Ko(r.height)/e.offsetHeight||1);var i=po(e)?nn(e):window,a=i.visualViewport,l=!Ym()&&n,u=(r.left+(l&&a?a.offsetLeft:0))/o,c=(r.top+(l&&a?a.offsetTop:0))/s,f=r.width/o,d=r.height/s;return{width:f,height:d,top:c,right:u+f,bottom:c+d,left:u,x:u,y:c}}function Ic(e){var t=qo(e),n=e.offsetWidth,r=e.offsetHeight;return Math.abs(t.width-n)<=1&&(n=t.width),Math.abs(t.height-r)<=1&&(r=t.height),{x:e.offsetLeft,y:e.offsetTop,width:n,height:r}}function Jm(e,t){var n=t.getRootNode&&t.getRootNode();if(e.contains(t))return!0;if(n&&xc(n)){var r=t;do{if(r&&e.isSameNode(r))return!0;r=r.parentNode||r.host}while(r)}return!1}function dr(e){return nn(e).getComputedStyle(e)}function yO(e){return[\"table\",\"td\",\"th\"].indexOf(Gn(e))>=0}function Dr(e){return((po(e)?e.ownerDocument:e.document)||window.document).documentElement}function al(e){return Gn(e)===\"html\"?e:e.assignedSlot||e.parentNode||(xc(e)?e.host:null)||Dr(e)}function tp(e){return!un(e)||dr(e).position===\"fixed\"?null:e.offsetParent}function wO(e){var t=/firefox/i.test(Ou()),n=/Trident/i.test(Ou());if(n&&un(e)){var r=dr(e);if(r.position===\"fixed\")return null}var o=al(e);for(xc(o)&&(o=o.host);un(o)&&[\"html\",\"body\"].indexOf(Gn(o))<0;){var s=dr(o);if(s.transform!==\"none\"||s.perspective!==\"none\"||s.contain===\"paint\"||[\"transform\",\"perspective\"].indexOf(s.willChange)!==-1||t&&s.willChange===\"filter\"||t&&s.filter&&s.filter!==\"none\")return o;o=o.parentNode}return null}function pi(e){for(var t=nn(e),n=tp(e);n&&yO(n)&&dr(n).position===\"static\";)n=tp(n);return n&&(Gn(n)===\"html\"||Gn(n)===\"body\"&&dr(n).position===\"static\")?t:n||wO(e)||t}function Pc(e){return[\"top\",\"bottom\"].indexOf(e)>=0?\"x\":\"y\"}function Ps(e,t,n){return ao(e,xa(t,n))}function SO(e,t,n){var r=Ps(e,t,n);return r>n?n:r}function Xm(){return{top:0,right:0,bottom:0,left:0}}function Zm(e){return Object.assign({},Xm(),e)}function Qm(e,t){return t.reduce(function(n,r){return n[r]=e,n},{})}var EO=function(e,t){return e=typeof e==\"function\"?e(Object.assign({},t.rects,{placement:t.placement})):e,Zm(typeof e!=\"number\"?e:Qm(e,di))};function _O(e){var t,n=e.state,r=e.name,o=e.options,s=n.elements.arrow,i=n.modifiersData.popperOffsets,a=Wn(n.placement),l=Pc(a),u=[Ht,vn].indexOf(a)>=0,c=u?\"height\":\"width\";if(!(!s||!i)){var f=EO(o.padding,n),d=Ic(s),v=l===\"y\"?zt:Ht,p=l===\"y\"?hn:vn,h=n.rects.reference[c]+n.rects.reference[l]-i[l]-n.rects.popper[c],b=i[l]-n.rects.reference[l],m=pi(s),S=m?l===\"y\"?m.clientHeight||0:m.clientWidth||0:0,_=h/2-b/2,w=f[v],y=S-d[c]-f[p],A=S/2-d[c]/2+_,R=Ps(w,A,y),N=l;n.modifiersData[r]=(t={},t[N]=R,t.centerOffset=R-A,t)}}function CO(e){var t=e.state,n=e.options,r=n.element,o=r===void 0?\"[data-popper-arrow]\":r;o!=null&&(typeof o==\"string\"&&(o=t.elements.popper.querySelector(o),!o)||Jm(t.elements.popper,o)&&(t.elements.arrow=o))}var TO={name:\"arrow\",enabled:!0,phase:\"main\",fn:_O,effect:CO,requires:[\"popperOffsets\"],requiresIfExists:[\"preventOverflow\"]};function Wo(e){return e.split(\"-\")[1]}var OO={top:\"auto\",right:\"auto\",bottom:\"auto\",left:\"auto\"};function AO(e,t){var n=e.x,r=e.y,o=t.devicePixelRatio||1;return{x:Ko(n*o)/o||0,y:Ko(r*o)/o||0}}function np(e){var t,n=e.popper,r=e.popperRect,o=e.placement,s=e.variation,i=e.offsets,a=e.position,l=e.gpuAcceleration,u=e.adaptive,c=e.roundOffsets,f=e.isFixed,d=i.x,v=d===void 0?0:d,p=i.y,h=p===void 0?0:p,b=typeof c==\"function\"?c({x:v,y:h}):{x:v,y:h};v=b.x,h=b.y;var m=i.hasOwnProperty(\"x\"),S=i.hasOwnProperty(\"y\"),_=Ht,w=zt,y=window;if(u){var A=pi(n),R=\"clientHeight\",N=\"clientWidth\";if(A===nn(n)&&(A=Dr(n),dr(A).position!==\"static\"&&a===\"absolute\"&&(R=\"scrollHeight\",N=\"scrollWidth\")),A=A,o===zt||(o===Ht||o===vn)&&s===Xs){w=hn;var I=f&&A===y&&y.visualViewport?y.visualViewport.height:A[R];h-=I-r.height,h*=l?1:-1}if(o===Ht||(o===zt||o===hn)&&s===Xs){_=vn;var x=f&&A===y&&y.visualViewport?y.visualViewport.width:A[N];v-=x-r.width,v*=l?1:-1}}var k=Object.assign({position:a},u&&OO),$=c===!0?AO({x:v,y:h},nn(n)):{x:v,y:h};if(v=$.x,h=$.y,l){var F;return Object.assign({},k,(F={},F[w]=S?\"0\":\"\",F[_]=m?\"0\":\"\",F.transform=(y.devicePixelRatio||1)<=1?\"translate(\"+v+\"px, \"+h+\"px)\":\"translate3d(\"+v+\"px, \"+h+\"px, 0)\",F))}return Object.assign({},k,(t={},t[w]=S?h+\"px\":\"\",t[_]=m?v+\"px\":\"\",t.transform=\"\",t))}function RO(e){var t=e.state,n=e.options,r=n.gpuAcceleration,o=r===void 0?!0:r,s=n.adaptive,i=s===void 0?!0:s,a=n.roundOffsets,l=a===void 0?!0:a,u={placement:Wn(t.placement),variation:Wo(t.placement),popper:t.elements.popper,popperRect:t.rects.popper,gpuAcceleration:o,isFixed:t.options.strategy===\"fixed\"};t.modifiersData.popperOffsets!=null&&(t.styles.popper=Object.assign({},t.styles.popper,np(Object.assign({},u,{offsets:t.modifiersData.popperOffsets,position:t.options.strategy,adaptive:i,roundOffsets:l})))),t.modifiersData.arrow!=null&&(t.styles.arrow=Object.assign({},t.styles.arrow,np(Object.assign({},u,{offsets:t.modifiersData.arrow,position:\"absolute\",adaptive:!1,roundOffsets:l})))),t.attributes.popper=Object.assign({},t.attributes.popper,{\"data-popper-placement\":t.placement})}var eg={name:\"computeStyles\",enabled:!0,phase:\"beforeWrite\",fn:RO,data:{}},Ii={passive:!0};function xO(e){var t=e.state,n=e.instance,r=e.options,o=r.scroll,s=o===void 0?!0:o,i=r.resize,a=i===void 0?!0:i,l=nn(t.elements.popper),u=[].concat(t.scrollParents.reference,t.scrollParents.popper);return s&&u.forEach(function(c){c.addEventListener(\"scroll\",n.update,Ii)}),a&&l.addEventListener(\"resize\",n.update,Ii),function(){s&&u.forEach(function(c){c.removeEventListener(\"scroll\",n.update,Ii)}),a&&l.removeEventListener(\"resize\",n.update,Ii)}}var tg={name:\"eventListeners\",enabled:!0,phase:\"write\",fn:function(){},effect:xO,data:{}},IO={left:\"right\",right:\"left\",bottom:\"top\",top:\"bottom\"};function ea(e){return e.replace(/left|right|bottom|top/g,function(t){return IO[t]})}var PO={start:\"end\",end:\"start\"};function rp(e){return e.replace(/start|end/g,function(t){return PO[t]})}function Nc(e){var t=nn(e),n=t.pageXOffset,r=t.pageYOffset;return{scrollLeft:n,scrollTop:r}}function Lc(e){return qo(Dr(e)).left+Nc(e).scrollLeft}function NO(e,t){var n=nn(e),r=Dr(e),o=n.visualViewport,s=r.clientWidth,i=r.clientHeight,a=0,l=0;if(o){s=o.width,i=o.height;var u=Ym();(u||!u&&t===\"fixed\")&&(a=o.offsetLeft,l=o.offsetTop)}return{width:s,height:i,x:a+Lc(e),y:l}}function LO(e){var t,n=Dr(e),r=Nc(e),o=(t=e.ownerDocument)==null?void 0:t.body,s=ao(n.scrollWidth,n.clientWidth,o?o.scrollWidth:0,o?o.clientWidth:0),i=ao(n.scrollHeight,n.clientHeight,o?o.scrollHeight:0,o?o.clientHeight:0),a=-r.scrollLeft+Lc(e),l=-r.scrollTop;return dr(o||n).direction===\"rtl\"&&(a+=ao(n.clientWidth,o?o.clientWidth:0)-s),{width:s,height:i,x:a,y:l}}function Mc(e){var t=dr(e),n=t.overflow,r=t.overflowX,o=t.overflowY;return/auto|scroll|overlay|hidden/.test(n+o+r)}function ng(e){return[\"html\",\"body\",\"#document\"].indexOf(Gn(e))>=0?e.ownerDocument.body:un(e)&&Mc(e)?e:ng(al(e))}function Ns(e,t){var n;t===void 0&&(t=[]);var r=ng(e),o=r===((n=e.ownerDocument)==null?void 0:n.body),s=nn(r),i=o?[s].concat(s.visualViewport||[],Mc(r)?r:[]):r,a=t.concat(i);return o?a:a.concat(Ns(al(i)))}function Au(e){return Object.assign({},e,{left:e.x,top:e.y,right:e.x+e.width,bottom:e.y+e.height})}function MO(e,t){var n=qo(e,!1,t===\"fixed\");return n.top=n.top+e.clientTop,n.left=n.left+e.clientLeft,n.bottom=n.top+e.clientHeight,n.right=n.left+e.clientWidth,n.width=e.clientWidth,n.height=e.clientHeight,n.x=n.left,n.y=n.top,n}function op(e,t,n){return t===Wm?Au(NO(e,n)):po(t)?MO(t,n):Au(LO(Dr(e)))}function $O(e){var t=Ns(al(e)),n=[\"absolute\",\"fixed\"].indexOf(dr(e).position)>=0,r=n&&un(e)?pi(e):e;return po(r)?t.filter(function(o){return po(o)&&Jm(o,r)&&Gn(o)!==\"body\"}):[]}function kO(e,t,n,r){var o=t===\"clippingParents\"?$O(e):[].concat(t),s=[].concat(o,[n]),i=s[0],a=s.reduce(function(l,u){var c=op(e,u,r);return l.top=ao(c.top,l.top),l.right=xa(c.right,l.right),l.bottom=xa(c.bottom,l.bottom),l.left=ao(c.left,l.left),l},op(e,i,r));return a.width=a.right-a.left,a.height=a.bottom-a.top,a.x=a.left,a.y=a.top,a}function rg(e){var t=e.reference,n=e.element,r=e.placement,o=r?Wn(r):null,s=r?Wo(r):null,i=t.x+t.width/2-n.width/2,a=t.y+t.height/2-n.height/2,l;switch(o){case zt:l={x:i,y:t.y-n.height};break;case hn:l={x:i,y:t.y+t.height};break;case vn:l={x:t.x+t.width,y:a};break;case Ht:l={x:t.x-n.width,y:a};break;default:l={x:t.x,y:t.y}}var u=o?Pc(o):null;if(u!=null){var c=u===\"y\"?\"height\":\"width\";switch(s){case Uo:l[u]=l[u]-(t[c]/2-n[c]/2);break;case Xs:l[u]=l[u]+(t[c]/2-n[c]/2);break}}return l}function Zs(e,t){t===void 0&&(t={});var n=t,r=n.placement,o=r===void 0?e.placement:r,s=n.strategy,i=s===void 0?e.strategy:s,a=n.boundary,l=a===void 0?sO:a,u=n.rootBoundary,c=u===void 0?Wm:u,f=n.elementContext,d=f===void 0?hs:f,v=n.altBoundary,p=v===void 0?!1:v,h=n.padding,b=h===void 0?0:h,m=Zm(typeof b!=\"number\"?b:Qm(b,di)),S=d===hs?iO:hs,_=e.rects.popper,w=e.elements[p?S:d],y=kO(po(w)?w:w.contextElement||Dr(e.elements.popper),l,c,i),A=qo(e.elements.reference),R=rg({reference:A,element:_,placement:o}),N=Au(Object.assign({},_,R)),I=d===hs?N:A,x={top:y.top-I.top+m.top,bottom:I.bottom-y.bottom+m.bottom,left:y.left-I.left+m.left,right:I.right-y.right+m.right},k=e.modifiersData.offset;if(d===hs&&k){var $=k[o];Object.keys(x).forEach(function(F){var Y=[vn,hn].indexOf(F)>=0?1:-1,P=[zt,hn].indexOf(F)>=0?\"y\":\"x\";x[F]+=$[P]*Y})}return x}function FO(e,t){t===void 0&&(t={});var n=t,r=n.placement,o=n.boundary,s=n.rootBoundary,i=n.padding,a=n.flipVariations,l=n.allowedAutoPlacements,u=l===void 0?il:l,c=Wo(r),f=c?a?ep:ep.filter(function(p){return Wo(p)===c}):di,d=f.filter(function(p){return u.indexOf(p)>=0});d.length===0&&(d=f);var v=d.reduce(function(p,h){return p[h]=Zs(e,{placement:h,boundary:o,rootBoundary:s,padding:i})[Wn(h)],p},{});return Object.keys(v).sort(function(p,h){return v[p]-v[h]})}function BO(e){if(Wn(e)===Rc)return[];var t=ea(e);return[rp(e),t,rp(t)]}function DO(e){var t=e.state,n=e.options,r=e.name;if(!t.modifiersData[r]._skip){for(var o=n.mainAxis,s=o===void 0?!0:o,i=n.altAxis,a=i===void 0?!0:i,l=n.fallbackPlacements,u=n.padding,c=n.boundary,f=n.rootBoundary,d=n.altBoundary,v=n.flipVariations,p=v===void 0?!0:v,h=n.allowedAutoPlacements,b=t.options.placement,m=Wn(b),S=m===b,_=l||(S||!p?[ea(b)]:BO(b)),w=[b].concat(_).reduce(function(qe,ze){return qe.concat(Wn(ze)===Rc?FO(t,{placement:ze,boundary:c,rootBoundary:f,padding:u,flipVariations:p,allowedAutoPlacements:h}):ze)},[]),y=t.rects.reference,A=t.rects.popper,R=new Map,N=!0,I=w[0],x=0;x<w.length;x++){var k=w[x],$=Wn(k),F=Wo(k)===Uo,Y=[zt,hn].indexOf($)>=0,P=Y?\"width\":\"height\",O=Zs(t,{placement:k,boundary:c,rootBoundary:f,altBoundary:d,padding:u}),j=Y?F?vn:Ht:F?hn:zt;y[P]>A[P]&&(j=ea(j));var Q=ea(j),me=[];if(s&&me.push(O[$]<=0),a&&me.push(O[j]<=0,O[Q]<=0),me.every(function(qe){return qe})){I=k,N=!1;break}R.set(k,me)}if(N)for(var Pe=p?3:1,Re=function(qe){var ze=w.find(function(De){var B=R.get(De);if(B)return B.slice(0,qe).every(function(K){return K})});if(ze)return I=ze,\"break\"},Ce=Pe;Ce>0;Ce--){var _e=Re(Ce);if(_e===\"break\")break}t.placement!==I&&(t.modifiersData[r]._skip=!0,t.placement=I,t.reset=!0)}}var VO={name:\"flip\",enabled:!0,phase:\"main\",fn:DO,requiresIfExists:[\"offset\"],data:{_skip:!1}};function sp(e,t,n){return n===void 0&&(n={x:0,y:0}),{top:e.top-t.height-n.y,right:e.right-t.width+n.x,bottom:e.bottom-t.height+n.y,left:e.left-t.width-n.x}}function ip(e){return[zt,vn,hn,Ht].some(function(t){return e[t]>=0})}function jO(e){var t=e.state,n=e.name,r=t.rects.reference,o=t.rects.popper,s=t.modifiersData.preventOverflow,i=Zs(t,{elementContext:\"reference\"}),a=Zs(t,{altBoundary:!0}),l=sp(i,r),u=sp(a,o,s),c=ip(l),f=ip(u);t.modifiersData[n]={referenceClippingOffsets:l,popperEscapeOffsets:u,isReferenceHidden:c,hasPopperEscaped:f},t.attributes.popper=Object.assign({},t.attributes.popper,{\"data-popper-reference-hidden\":c,\"data-popper-escaped\":f})}var zO={name:\"hide\",enabled:!0,phase:\"main\",requiresIfExists:[\"preventOverflow\"],fn:jO};function HO(e,t,n){var r=Wn(e),o=[Ht,zt].indexOf(r)>=0?-1:1,s=typeof n==\"function\"?n(Object.assign({},t,{placement:e})):n,i=s[0],a=s[1];return i=i||0,a=(a||0)*o,[Ht,vn].indexOf(r)>=0?{x:a,y:i}:{x:i,y:a}}function UO(e){var t=e.state,n=e.options,r=e.name,o=n.offset,s=o===void 0?[0,0]:o,i=il.reduce(function(c,f){return c[f]=HO(f,t.rects,s),c},{}),a=i[t.placement],l=a.x,u=a.y;t.modifiersData.popperOffsets!=null&&(t.modifiersData.popperOffsets.x+=l,t.modifiersData.popperOffsets.y+=u),t.modifiersData[r]=i}var KO={name:\"offset\",enabled:!0,phase:\"main\",requires:[\"popperOffsets\"],fn:UO};function qO(e){var t=e.state,n=e.name;t.modifiersData[n]=rg({reference:t.rects.reference,element:t.rects.popper,placement:t.placement})}var og={name:\"popperOffsets\",enabled:!0,phase:\"read\",fn:qO,data:{}};function WO(e){return e===\"x\"?\"y\":\"x\"}function GO(e){var t=e.state,n=e.options,r=e.name,o=n.mainAxis,s=o===void 0?!0:o,i=n.altAxis,a=i===void 0?!1:i,l=n.boundary,u=n.rootBoundary,c=n.altBoundary,f=n.padding,d=n.tether,v=d===void 0?!0:d,p=n.tetherOffset,h=p===void 0?0:p,b=Zs(t,{boundary:l,rootBoundary:u,padding:f,altBoundary:c}),m=Wn(t.placement),S=Wo(t.placement),_=!S,w=Pc(m),y=WO(w),A=t.modifiersData.popperOffsets,R=t.rects.reference,N=t.rects.popper,I=typeof h==\"function\"?h(Object.assign({},t.rects,{placement:t.placement})):h,x=typeof I==\"number\"?{mainAxis:I,altAxis:I}:Object.assign({mainAxis:0,altAxis:0},I),k=t.modifiersData.offset?t.modifiersData.offset[t.placement]:null,$={x:0,y:0};if(A){if(s){var F,Y=w===\"y\"?zt:Ht,P=w===\"y\"?hn:vn,O=w===\"y\"?\"height\":\"width\",j=A[w],Q=j+b[Y],me=j-b[P],Pe=v?-N[O]/2:0,Re=S===Uo?R[O]:N[O],Ce=S===Uo?-N[O]:-R[O],_e=t.elements.arrow,qe=v&&_e?Ic(_e):{width:0,height:0},ze=t.modifiersData[\"arrow#persistent\"]?t.modifiersData[\"arrow#persistent\"].padding:Xm(),De=ze[Y],B=ze[P],K=Ps(0,R[O],qe[O]),J=_?R[O]/2-Pe-K-De-x.mainAxis:Re-K-De-x.mainAxis,oe=_?-R[O]/2+Pe+K+B+x.mainAxis:Ce+K+B+x.mainAxis,ge=t.elements.arrow&&pi(t.elements.arrow),E=ge?w===\"y\"?ge.clientTop||0:ge.clientLeft||0:0,T=(F=k==null?void 0:k[w])!=null?F:0,M=j+J-T-E,W=j+oe-T,G=Ps(v?xa(Q,M):Q,j,v?ao(me,W):me);A[w]=G,$[w]=G-j}if(a){var q,se=w===\"x\"?zt:Ht,ne=w===\"x\"?hn:vn,te=A[y],X=y===\"y\"?\"height\":\"width\",Se=te+b[se],ue=te-b[ne],ye=[zt,Ht].indexOf(m)!==-1,z=(q=k==null?void 0:k[y])!=null?q:0,be=ye?Se:te-R[X]-N[X]-z+x.altAxis,Le=ye?te+R[X]+N[X]-z-x.altAxis:ue,ke=v&&ye?SO(be,te,Le):Ps(v?be:Se,te,v?Le:ue);A[y]=ke,$[y]=ke-te}t.modifiersData[r]=$}}var YO={name:\"preventOverflow\",enabled:!0,phase:\"main\",fn:GO,requiresIfExists:[\"offset\"]};function JO(e){return{scrollLeft:e.scrollLeft,scrollTop:e.scrollTop}}function XO(e){return e===nn(e)||!un(e)?Nc(e):JO(e)}function ZO(e){var t=e.getBoundingClientRect(),n=Ko(t.width)/e.offsetWidth||1,r=Ko(t.height)/e.offsetHeight||1;return n!==1||r!==1}function QO(e,t,n){n===void 0&&(n=!1);var r=un(t),o=un(t)&&ZO(t),s=Dr(t),i=qo(e,o,n),a={scrollLeft:0,scrollTop:0},l={x:0,y:0};return(r||!r&&!n)&&((Gn(t)!==\"body\"||Mc(s))&&(a=XO(t)),un(t)?(l=qo(t,!0),l.x+=t.clientLeft,l.y+=t.clientTop):s&&(l.x=Lc(s))),{x:i.left+a.scrollLeft-l.x,y:i.top+a.scrollTop-l.y,width:i.width,height:i.height}}function eA(e){var t=new Map,n=new Set,r=[];e.forEach(function(s){t.set(s.name,s)});function o(s){n.add(s.name);var i=[].concat(s.requires||[],s.requiresIfExists||[]);i.forEach(function(a){if(!n.has(a)){var l=t.get(a);l&&o(l)}}),r.push(s)}return e.forEach(function(s){n.has(s.name)||o(s)}),r}function tA(e){var t=eA(e);return mO.reduce(function(n,r){return n.concat(t.filter(function(o){return o.phase===r}))},[])}function nA(e){var t;return function(){return t||(t=new Promise(function(n){Promise.resolve().then(function(){t=void 0,n(e())})})),t}}function rA(e){var t=e.reduce(function(n,r){var o=n[r.name];return n[r.name]=o?Object.assign({},o,r,{options:Object.assign({},o.options,r.options),data:Object.assign({},o.data,r.data)}):r,n},{});return Object.keys(t).map(function(n){return t[n]})}var ap={placement:\"bottom\",modifiers:[],strategy:\"absolute\"};function lp(){for(var e=arguments.length,t=new Array(e),n=0;n<e;n++)t[n]=arguments[n];return!t.some(function(r){return!(r&&typeof r.getBoundingClientRect==\"function\")})}function $c(e){e===void 0&&(e={});var t=e,n=t.defaultModifiers,r=n===void 0?[]:n,o=t.defaultOptions,s=o===void 0?ap:o;return function(i,a,l){l===void 0&&(l=s);var u={placement:\"bottom\",orderedModifiers:[],options:Object.assign({},ap,s),modifiersData:{},elements:{reference:i,popper:a},attributes:{},styles:{}},c=[],f=!1,d={state:u,setOptions:function(h){var b=typeof h==\"function\"?h(u.options):h;p(),u.options=Object.assign({},s,u.options,b),u.scrollParents={reference:po(i)?Ns(i):i.contextElement?Ns(i.contextElement):[],popper:Ns(a)};var m=tA(rA([].concat(r,u.options.modifiers)));return u.orderedModifiers=m.filter(function(S){return S.enabled}),v(),d.update()},forceUpdate:function(){if(!f){var h=u.elements,b=h.reference,m=h.popper;if(lp(b,m)){u.rects={reference:QO(b,pi(m),u.options.strategy===\"fixed\"),popper:Ic(m)},u.reset=!1,u.placement=u.options.placement,u.orderedModifiers.forEach(function(N){return u.modifiersData[N.name]=Object.assign({},N.data)});for(var S=0;S<u.orderedModifiers.length;S++){if(u.reset===!0){u.reset=!1,S=-1;continue}var _=u.orderedModifiers[S],w=_.fn,y=_.options,A=y===void 0?{}:y,R=_.name;typeof w==\"function\"&&(u=w({state:u,options:A,name:R,instance:d})||u)}}}},update:nA(function(){return new Promise(function(h){d.forceUpdate(),h(u)})}),destroy:function(){p(),f=!0}};if(!lp(i,a))return d;d.setOptions(l).then(function(h){!f&&l.onFirstUpdate&&l.onFirstUpdate(h)});function v(){u.orderedModifiers.forEach(function(h){var b=h.name,m=h.options,S=m===void 0?{}:m,_=h.effect;if(typeof _==\"function\"){var w=_({state:u,name:b,instance:d,options:S}),y=function(){};c.push(w||y)}})}function p(){c.forEach(function(h){return h()}),c=[]}return d}}$c();var oA=[tg,og,eg,Gm];$c({defaultModifiers:oA});var sA=[tg,og,eg,Gm,KO,VO,YO,TO,zO],iA=$c({defaultModifiers:sA});const aA=(e,t,n={})=>{const r={name:\"updateState\",enabled:!0,phase:\"write\",fn:({state:l})=>{const u=lA(l);Object.assign(i.value,u)},requires:[\"computeStyles\"]},o=C(()=>{const{onFirstUpdate:l,placement:u,strategy:c,modifiers:f}=g(n);return{onFirstUpdate:l,placement:u||\"bottom\",strategy:c||\"absolute\",modifiers:[...f||[],r,{name:\"applyStyles\",enabled:!1}]}}),s=ir(),i=V({styles:{popper:{position:g(o).strategy,left:\"0\",top:\"0\"},arrow:{position:\"absolute\"}},attributes:{}}),a=()=>{s.value&&(s.value.destroy(),s.value=void 0)};return he(o,l=>{const u=g(s);u&&u.setOptions(l)},{deep:!0}),he([e,t],([l,u])=>{a(),!(!l||!u)&&(s.value=iA(l,u,g(o)))}),St(()=>{a()}),{state:C(()=>{var l;return{...((l=g(s))==null?void 0:l.state)||{}}}),styles:C(()=>g(i).styles),attributes:C(()=>g(i).attributes),update:()=>{var l;return(l=g(s))==null?void 0:l.update()},forceUpdate:()=>{var l;return(l=g(s))==null?void 0:l.forceUpdate()},instanceRef:C(()=>g(s))}};function lA(e){const t=Object.keys(e.elements),n=Oa(t.map(o=>[o,e.styles[o]||{}])),r=Oa(t.map(o=>[o,e.attributes[o]]));return{styles:n,attributes:r}}const kc=e=>{if(!e)return{onClick:ht,onMousedown:ht,onMouseup:ht};let t=!1,n=!1;return{onClick:i=>{t&&n&&e(i),t=n=!1},onMousedown:i=>{t=i.target===i.currentTarget},onMouseup:i=>{n=i.target===i.currentTarget}}};function up(){let e;const t=(r,o)=>{n(),e=window.setTimeout(r,o)},n=()=>window.clearTimeout(e);return ui(()=>n()),{registerTimeout:t,cancelTimeout:n}}const cp={prefix:Math.floor(Math.random()*1e4),current:0},uA=Symbol(\"elIdInjection\"),sg=()=>Ge()?Ee(uA,cp):cp,pr=e=>{const t=sg(),n=Ac();return C(()=>g(e)||`${n.value}-id-${t.prefix}-${t.current++}`)};let No=[];const fp=e=>{const t=e;t.key===_n.esc&&No.forEach(n=>n(t))},cA=e=>{Ke(()=>{No.length===0&&document.addEventListener(\"keydown\",fp),st&&No.push(e)}),St(()=>{No=No.filter(t=>t!==e),No.length===0&&st&&document.removeEventListener(\"keydown\",fp)})},ig=()=>{const e=Ac(),t=sg(),n=C(()=>`${e.value}-popper-container-${t.prefix}`),r=C(()=>`#${n.value}`);return{id:n,selector:r}},fA=e=>{const t=document.createElement(\"div\");return t.id=e,document.body.appendChild(t),t},dA=()=>{const{id:e,selector:t}=ig();return sc(()=>{st&&(document.body.querySelector(t.value)||fA(e.value))}),{id:e,selector:t}},pA=xe({showAfter:{type:Number,default:0},hideAfter:{type:Number,default:200},autoClose:{type:Number,default:0}}),hA=({showAfter:e,hideAfter:t,autoClose:n,open:r,close:o})=>{const{registerTimeout:s}=up(),{registerTimeout:i,cancelTimeout:a}=up();return{onOpen:c=>{s(()=>{r(c);const f=g(n);je(f)&&f>0&&i(()=>{o(c)},f)},g(e))},onClose:c=>{a(),s(()=>{o(c)},g(t))}}},ag=Symbol(\"elForwardRef\"),vA=e=>{ft(ag,{setForwardRef:n=>{e.value=n}})},mA=e=>({mounted(t){e(t)},updated(t){e(t)},unmounted(){e(null)}}),dp={current:0},pp=V(0),lg=2e3,hp=Symbol(\"elZIndexContextKey\"),ug=Symbol(\"zIndexContextKey\"),Fc=e=>{const t=Ge()?Ee(hp,dp):dp,n=e||(Ge()?Ee(ug,void 0):void 0),r=C(()=>{const i=g(n);return je(i)?i:lg}),o=C(()=>r.value+pp.value),s=()=>(t.current++,pp.value=t.current,o.value);return!st&&Ee(hp),{initialZIndex:r,currentZIndex:o,nextZIndex:s}};function gA(e){let t;function n(){if(e.value==null)return;const{selectionStart:o,selectionEnd:s,value:i}=e.value;if(o==null||s==null)return;const a=i.slice(0,Math.max(0,o)),l=i.slice(Math.max(0,s));t={selectionStart:o,selectionEnd:s,value:i,beforeTxt:a,afterTxt:l}}function r(){if(e.value==null||t==null)return;const{value:o}=e.value,{beforeTxt:s,afterTxt:i,selectionStart:a}=t;if(s==null||i==null||a==null)return;let l=o.length;if(o.endsWith(i))l=o.length-i.length;else if(o.startsWith(s))l=s.length;else{const u=s[a-1],c=o.indexOf(u,a-1);c!==-1&&(l=c+1)}e.value.setSelectionRange(l,l)}return[n,r]}const bA=(e,t,n)=>Qi(e.subTree).filter(s=>{var i;return cn(s)&&((i=s.type)==null?void 0:i.name)===t&&!!s.component}).map(s=>s.component.uid).map(s=>n[s]).filter(s=>!!s),yA=(e,t)=>{const n={},r=ir([]);return{children:r,addChild:i=>{n[i.uid]=i,r.value=bA(e,t,n)},removeChild:i=>{delete n[i],r.value=r.value.filter(a=>a.uid!==i)}}},So=rl({type:String,values:es,required:!1}),cg=Symbol(\"size\"),wA=()=>{const e=Ee(cg,{});return C(()=>g(e.size)||\"\")};function fg(e,{beforeFocus:t,afterFocus:n,beforeBlur:r,afterBlur:o}={}){const s=Ge(),{emit:i}=s,a=ir(),l=V(!1),u=d=>{ve(t)&&t(d)||l.value||(l.value=!0,i(\"focus\",d),n==null||n())},c=d=>{var v;ve(r)&&r(d)||d.relatedTarget&&((v=a.value)!=null&&v.contains(d.relatedTarget))||(l.value=!1,i(\"blur\",d),o==null||o())},f=()=>{var d,v;(d=a.value)!=null&&d.contains(document.activeElement)&&a.value!==document.activeElement||(v=e.value)==null||v.focus()};return he(a,d=>{d&&d.setAttribute(\"tabindex\",\"-1\")}),tn(a,\"focus\",u,!0),tn(a,\"blur\",c,!0),tn(a,\"click\",f,!0),{isFocused:l,wrapperRef:a,handleFocus:u,handleBlur:c}}function dg({afterComposition:e,emit:t}){const n=V(!1),r=a=>{t==null||t(\"compositionstart\",a),n.value=!0},o=a=>{var l;t==null||t(\"compositionupdate\",a);const u=(l=a.target)==null?void 0:l.value,c=u[u.length-1]||\"\";n.value=!WT(c)},s=a=>{t==null||t(\"compositionend\",a),n.value&&(n.value=!1,Ie(()=>e(a)))};return{isComposing:n,handleComposition:a=>{a.type===\"compositionend\"?s(a):o(a)},handleCompositionStart:r,handleCompositionUpdate:o,handleCompositionEnd:s}}const pg=Symbol(\"emptyValuesContextKey\"),SA=[\"\",void 0,null],EA=void 0,hg=xe({emptyValues:Array,valueOnClear:{type:[String,Number,Boolean,Function],default:void 0,validator:e=>ve(e)?!e():!e}}),_A=(e,t)=>{const n=Ge()?Ee(pg,V({})):V({}),r=C(()=>e.emptyValues||n.value.emptyValues||SA),o=C(()=>ve(e.valueOnClear)?e.valueOnClear():e.valueOnClear!==void 0?e.valueOnClear:ve(n.value.valueOnClear)?n.value.valueOnClear():n.value.valueOnClear!==void 0?n.value.valueOnClear:EA),s=i=>r.value.includes(i);return r.value.includes(o.value),{emptyValues:r,valueOnClear:o,isEmptyValue:s}},CA=xe({ariaLabel:String,ariaOrientation:{type:String,values:[\"horizontal\",\"vertical\",\"undefined\"]},ariaControls:String}),yr=e=>Im(CA,e),vg=Symbol(),Ia=V();function ll(e,t=void 0){const n=Ge()?Ee(vg,Ia):Ia;return e?C(()=>{var r,o;return(o=(r=n.value)==null?void 0:r[e])!=null?o:t}):n}function Bc(e,t){const n=ll(),r=$e(e,C(()=>{var a;return((a=n.value)==null?void 0:a.namespace)||Is})),o=sl(C(()=>{var a;return(a=n.value)==null?void 0:a.locale})),s=Fc(C(()=>{var a;return((a=n.value)==null?void 0:a.zIndex)||lg})),i=C(()=>{var a;return g(t)||((a=n.value)==null?void 0:a.size)||\"\"});return TA(C(()=>g(n)||{})),{ns:r,locale:o,zIndex:s,size:i}}const TA=(e,t,n=!1)=>{var r;const o=!!Ge(),s=o?ll():void 0,i=(r=void 0)!=null?r:o?ft:void 0;if(!i)return;const a=C(()=>{const l=g(e);return s!=null&&s.value?OA(s.value,l):l});return i(vg,a),i(Hm,C(()=>a.value.locale)),i(Um,C(()=>a.value.namespace)),i(ug,C(()=>a.value.zIndex)),i(cg,{size:C(()=>a.value.size||\"\")}),i(pg,C(()=>({emptyValues:a.value.emptyValues,valueOnClear:a.value.valueOnClear}))),(n||!Ia.value)&&(Ia.value=a.value),a},OA=(e,t)=>{const n=[...new Set([...Zd(e),...Zd(t)])],r={};for(const o of n)r[o]=t[o]!==void 0?t[o]:e[o];return r};xe({a11y:{type:Boolean,default:!0},locale:{type:we(Object)},size:So,button:{type:we(Object)},experimentalFeatures:{type:we(Object)},keyboardNavigation:{type:Boolean,default:!0},message:{type:we(Object)},zIndex:Number,namespace:{type:String,default:\"el\"},...hg});const Fn={};var Ne=(e,t)=>{const n=e.__vccOpts||e;for(const[r,o]of t)n[r]=o;return n};const AA=xe({size:{type:we([Number,String])},color:{type:String}}),RA=Z({name:\"ElIcon\",inheritAttrs:!1}),xA=Z({...RA,props:AA,setup(e){const t=e,n=$e(\"icon\"),r=C(()=>{const{size:o,color:s}=t;return!o&&!s?{}:{fontSize:Lt(o)?void 0:pn(o),\"--color\":s}});return(o,s)=>(L(),ee(\"i\",En({class:g(n).b(),style:g(r)},o.$attrs),[de(o.$slots,\"default\")],16))}});var IA=Ne(xA,[[\"__file\",\"icon.vue\"]]);const Je=yt(IA),ts=Symbol(\"formContextKey\"),ho=Symbol(\"formItemContextKey\"),In=(e,t={})=>{const n=V(void 0),r=t.prop?n:qm(\"size\"),o=t.global?n:wA(),s=t.form?{size:void 0}:Ee(ts,void 0),i=t.formItem?{size:void 0}:Ee(ho,void 0);return C(()=>r.value||g(e)||(i==null?void 0:i.size)||(s==null?void 0:s.size)||o.value||\"\")},ns=e=>{const t=qm(\"disabled\"),n=Ee(ts,void 0);return C(()=>t.value||g(e)||(n==null?void 0:n.disabled)||!1)},Vr=()=>{const e=Ee(ts,void 0),t=Ee(ho,void 0);return{form:e,formItem:t}},hi=(e,{formItemContext:t,disableIdGeneration:n,disableIdManagement:r})=>{n||(n=V(!1)),r||(r=V(!1));const o=V();let s;const i=C(()=>{var a;return!!(!(e.label||e.ariaLabel)&&t&&t.inputIds&&((a=t.inputIds)==null?void 0:a.length)<=1)});return Ke(()=>{s=he([Jt(e,\"id\"),n],([a,l])=>{const u=a??(l?void 0:pr().value);u!==o.value&&(t!=null&&t.removeInputId&&(o.value&&t.removeInputId(o.value),!(r!=null&&r.value)&&!l&&u&&t.addInputId(u)),o.value=u)},{immediate:!0})}),kr(()=>{s&&s(),t!=null&&t.removeInputId&&o.value&&t.removeInputId(o.value)}),{isLabeledByFormItem:i,inputId:o}},PA=xe({size:{type:String,values:es},disabled:Boolean}),NA=xe({...PA,model:Object,rules:{type:we(Object)},labelPosition:{type:String,values:[\"left\",\"right\",\"top\"],default:\"right\"},requireAsteriskPosition:{type:String,values:[\"left\",\"right\"],default:\"left\"},labelWidth:{type:[String,Number],default:\"\"},labelSuffix:{type:String,default:\"\"},inline:Boolean,inlineMessage:Boolean,statusIcon:Boolean,showMessage:{type:Boolean,default:!0},validateOnRuleChange:{type:Boolean,default:!0},hideRequiredAsterisk:Boolean,scrollToError:Boolean,scrollIntoViewOptions:{type:[Object,Boolean]}}),LA={validate:(e,t,n)=>(pe(e)||Ae(e))&&Bt(t)&&Ae(n)};function MA(){const e=V([]),t=C(()=>{if(!e.value.length)return\"0\";const s=Math.max(...e.value);return s?`${s}px`:\"\"});function n(s){const i=e.value.indexOf(s);return i===-1&&t.value,i}function r(s,i){if(s&&i){const a=n(i);e.value.splice(a,1,s)}else s&&e.value.push(s)}function o(s){const i=n(s);i>-1&&e.value.splice(i,1)}return{autoLabelWidth:t,registerLabelWidth:r,deregisterLabelWidth:o}}const Pi=(e,t)=>{const n=yn(t);return n.length>0?e.filter(r=>r.prop&&n.includes(r.prop)):e},$A=\"ElForm\",kA=Z({name:$A}),FA=Z({...kA,props:NA,emits:LA,setup(e,{expose:t,emit:n}){const r=e,o=[],s=In(),i=$e(\"form\"),a=C(()=>{const{labelPosition:_,inline:w}=r;return[i.b(),i.m(s.value||\"default\"),{[i.m(`label-${_}`)]:_,[i.m(\"inline\")]:w}]}),l=_=>o.find(w=>w.prop===_),u=_=>{o.push(_)},c=_=>{_.prop&&o.splice(o.indexOf(_),1)},f=(_=[])=>{r.model&&Pi(o,_).forEach(w=>w.resetField())},d=(_=[])=>{Pi(o,_).forEach(w=>w.clearValidate())},v=C(()=>!!r.model),p=_=>{if(o.length===0)return[];const w=Pi(o,_);return w.length?w:[]},h=async _=>m(void 0,_),b=async(_=[])=>{if(!v.value)return!1;const w=p(_);if(w.length===0)return!0;let y={};for(const A of w)try{await A.validate(\"\")}catch(R){y={...y,...R}}return Object.keys(y).length===0?!0:Promise.reject(y)},m=async(_=[],w)=>{const y=!ve(w);try{const A=await b(_);return A===!0&&await(w==null?void 0:w(A)),A}catch(A){if(A instanceof Error)throw A;const R=A;return r.scrollToError&&S(Object.keys(R)[0]),await(w==null?void 0:w(!1,R)),y&&Promise.reject(R)}},S=_=>{var w;const y=Pi(o,_)[0];y&&((w=y.$el)==null||w.scrollIntoView(r.scrollIntoViewOptions))};return he(()=>r.rules,()=>{r.validateOnRuleChange&&h().catch(_=>void 0)},{deep:!0}),ft(ts,wt({...vr(r),emit:n,resetFields:f,clearValidate:d,validateField:m,getField:l,addField:u,removeField:c,...MA()})),t({validate:h,validateField:m,resetFields:f,clearValidate:d,scrollToField:S,fields:o}),(_,w)=>(L(),ee(\"form\",{class:U(g(a))},[de(_.$slots,\"default\")],2))}});var BA=Ne(FA,[[\"__file\",\"form.vue\"]]);function eo(){return eo=Object.assign?Object.assign.bind():function(e){for(var t=1;t<arguments.length;t++){var n=arguments[t];for(var r in n)Object.prototype.hasOwnProperty.call(n,r)&&(e[r]=n[r])}return e},eo.apply(this,arguments)}function DA(e,t){e.prototype=Object.create(t.prototype),e.prototype.constructor=e,Qs(e,t)}function Ru(e){return Ru=Object.setPrototypeOf?Object.getPrototypeOf.bind():function(n){return n.__proto__||Object.getPrototypeOf(n)},Ru(e)}function Qs(e,t){return Qs=Object.setPrototypeOf?Object.setPrototypeOf.bind():function(r,o){return r.__proto__=o,r},Qs(e,t)}function VA(){if(typeof Reflect>\"u\"||!Reflect.construct||Reflect.construct.sham)return!1;if(typeof Proxy==\"function\")return!0;try{return Boolean.prototype.valueOf.call(Reflect.construct(Boolean,[],function(){})),!0}catch{return!1}}function ta(e,t,n){return VA()?ta=Reflect.construct.bind():ta=function(o,s,i){var a=[null];a.push.apply(a,s);var l=Function.bind.apply(o,a),u=new l;return i&&Qs(u,i.prototype),u},ta.apply(null,arguments)}function jA(e){return Function.toString.call(e).indexOf(\"[native code]\")!==-1}function xu(e){var t=typeof Map==\"function\"?new Map:void 0;return xu=function(r){if(r===null||!jA(r))return r;if(typeof r!=\"function\")throw new TypeError(\"Super expression must either be null or a function\");if(typeof t<\"u\"){if(t.has(r))return t.get(r);t.set(r,o)}function o(){return ta(r,arguments,Ru(this).constructor)}return o.prototype=Object.create(r.prototype,{constructor:{value:o,enumerable:!1,writable:!0,configurable:!0}}),Qs(o,r)},xu(e)}var zA=/%[sdj%]/g,HA=function(){};function Iu(e){if(!e||!e.length)return null;var t={};return e.forEach(function(n){var r=n.field;t[r]=t[r]||[],t[r].push(n)}),t}function Zt(e){for(var t=arguments.length,n=new Array(t>1?t-1:0),r=1;r<t;r++)n[r-1]=arguments[r];var o=0,s=n.length;if(typeof e==\"function\")return e.apply(null,n);if(typeof e==\"string\"){var i=e.replace(zA,function(a){if(a===\"%%\")return\"%\";if(o>=s)return a;switch(a){case\"%s\":return String(n[o++]);case\"%d\":return Number(n[o++]);case\"%j\":try{return JSON.stringify(n[o++])}catch{return\"[Circular]\"}break;default:return a}});return i}return e}function UA(e){return e===\"string\"||e===\"url\"||e===\"hex\"||e===\"email\"||e===\"date\"||e===\"pattern\"}function bt(e,t){return!!(e==null||t===\"array\"&&Array.isArray(e)&&!e.length||UA(t)&&typeof e==\"string\"&&!e)}function KA(e,t,n){var r=[],o=0,s=e.length;function i(a){r.push.apply(r,a||[]),o++,o===s&&n(r)}e.forEach(function(a){t(a,i)})}function vp(e,t,n){var r=0,o=e.length;function s(i){if(i&&i.length){n(i);return}var a=r;r=r+1,a<o?t(e[a],s):n([])}s([])}function qA(e){var t=[];return Object.keys(e).forEach(function(n){t.push.apply(t,e[n]||[])}),t}var mp=function(e){DA(t,e);function t(n,r){var o;return o=e.call(this,\"Async Validation Error\")||this,o.errors=n,o.fields=r,o}return t}(xu(Error));function WA(e,t,n,r,o){if(t.first){var s=new Promise(function(d,v){var p=function(m){return r(m),m.length?v(new mp(m,Iu(m))):d(o)},h=qA(e);vp(h,n,p)});return s.catch(function(d){return d}),s}var i=t.firstFields===!0?Object.keys(e):t.firstFields||[],a=Object.keys(e),l=a.length,u=0,c=[],f=new Promise(function(d,v){var p=function(b){if(c.push.apply(c,b),u++,u===l)return r(c),c.length?v(new mp(c,Iu(c))):d(o)};a.length||(r(c),d(o)),a.forEach(function(h){var b=e[h];i.indexOf(h)!==-1?vp(b,n,p):KA(b,n,p)})});return f.catch(function(d){return d}),f}function GA(e){return!!(e&&e.message!==void 0)}function YA(e,t){for(var n=e,r=0;r<t.length;r++){if(n==null)return n;n=n[t[r]]}return n}function gp(e,t){return function(n){var r;return e.fullFields?r=YA(t,e.fullFields):r=t[n.field||e.fullField],GA(n)?(n.field=n.field||e.fullField,n.fieldValue=r,n):{message:typeof n==\"function\"?n():n,fieldValue:r,field:n.field||e.fullField}}}function bp(e,t){if(t){for(var n in t)if(t.hasOwnProperty(n)){var r=t[n];typeof r==\"object\"&&typeof e[n]==\"object\"?e[n]=eo({},e[n],r):e[n]=r}}return e}var mg=function(t,n,r,o,s,i){t.required&&(!r.hasOwnProperty(t.field)||bt(n,i||t.type))&&o.push(Zt(s.messages.required,t.fullField))},JA=function(t,n,r,o,s){(/^\\s+$/.test(n)||n===\"\")&&o.push(Zt(s.messages.whitespace,t.fullField))},Ni,XA=function(){if(Ni)return Ni;var e=\"[a-fA-F\\\\d:]\",t=function(w){return w&&w.includeBoundaries?\"(?:(?<=\\\\s|^)(?=\"+e+\")|(?<=\"+e+\")(?=\\\\s|$))\":\"\"},n=\"(?:25[0-5]|2[0-4]\\\\d|1\\\\d\\\\d|[1-9]\\\\d|\\\\d)(?:\\\\.(?:25[0-5]|2[0-4]\\\\d|1\\\\d\\\\d|[1-9]\\\\d|\\\\d)){3}\",r=\"[a-fA-F\\\\d]{1,4}\",o=(`\n(?:\n(?:`+r+\":){7}(?:\"+r+`|:)|                                    // 1:2:3:4:5:6:7::  1:2:3:4:5:6:7:8\n(?:`+r+\":){6}(?:\"+n+\"|:\"+r+`|:)|                             // 1:2:3:4:5:6::    1:2:3:4:5:6::8   1:2:3:4:5:6::8  1:2:3:4:5:6::1.2.3.4\n(?:`+r+\":){5}(?::\"+n+\"|(?::\"+r+`){1,2}|:)|                   // 1:2:3:4:5::      1:2:3:4:5::7:8   1:2:3:4:5::8    1:2:3:4:5::7:1.2.3.4\n(?:`+r+\":){4}(?:(?::\"+r+\"){0,1}:\"+n+\"|(?::\"+r+`){1,3}|:)| // 1:2:3:4::        1:2:3:4::6:7:8   1:2:3:4::8      1:2:3:4::6:7:1.2.3.4\n(?:`+r+\":){3}(?:(?::\"+r+\"){0,2}:\"+n+\"|(?::\"+r+`){1,4}|:)| // 1:2:3::          1:2:3::5:6:7:8   1:2:3::8        1:2:3::5:6:7:1.2.3.4\n(?:`+r+\":){2}(?:(?::\"+r+\"){0,3}:\"+n+\"|(?::\"+r+`){1,5}|:)| // 1:2::            1:2::4:5:6:7:8   1:2::8          1:2::4:5:6:7:1.2.3.4\n(?:`+r+\":){1}(?:(?::\"+r+\"){0,4}:\"+n+\"|(?::\"+r+`){1,6}|:)| // 1::              1::3:4:5:6:7:8   1::8            1::3:4:5:6:7:1.2.3.4\n(?::(?:(?::`+r+\"){0,5}:\"+n+\"|(?::\"+r+`){1,7}|:))             // ::2:3:4:5:6:7:8  ::2:3:4:5:6:7:8  ::8             ::1.2.3.4\n)(?:%[0-9a-zA-Z]{1,})?                                             // %eth0            %1\n`).replace(/\\s*\\/\\/.*$/gm,\"\").replace(/\\n/g,\"\").trim(),s=new RegExp(\"(?:^\"+n+\"$)|(?:^\"+o+\"$)\"),i=new RegExp(\"^\"+n+\"$\"),a=new RegExp(\"^\"+o+\"$\"),l=function(w){return w&&w.exact?s:new RegExp(\"(?:\"+t(w)+n+t(w)+\")|(?:\"+t(w)+o+t(w)+\")\",\"g\")};l.v4=function(_){return _&&_.exact?i:new RegExp(\"\"+t(_)+n+t(_),\"g\")},l.v6=function(_){return _&&_.exact?a:new RegExp(\"\"+t(_)+o+t(_),\"g\")};var u=\"(?:(?:[a-z]+:)?//)\",c=\"(?:\\\\S+(?::\\\\S*)?@)?\",f=l.v4().source,d=l.v6().source,v=\"(?:(?:[a-z\\\\u00a1-\\\\uffff0-9][-_]*)*[a-z\\\\u00a1-\\\\uffff0-9]+)\",p=\"(?:\\\\.(?:[a-z\\\\u00a1-\\\\uffff0-9]-*)*[a-z\\\\u00a1-\\\\uffff0-9]+)*\",h=\"(?:\\\\.(?:[a-z\\\\u00a1-\\\\uffff]{2,}))\",b=\"(?::\\\\d{2,5})?\",m='(?:[/?#][^\\\\s\"]*)?',S=\"(?:\"+u+\"|www\\\\.)\"+c+\"(?:localhost|\"+f+\"|\"+d+\"|\"+v+p+h+\")\"+b+m;return Ni=new RegExp(\"(?:^\"+S+\"$)\",\"i\"),Ni},yp={email:/^(([^<>()\\[\\]\\\\.,;:\\s@\"]+(\\.[^<>()\\[\\]\\\\.,;:\\s@\"]+)*)|(\".+\"))@((\\[[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}])|(([a-zA-Z\\-0-9\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF]+\\.)+[a-zA-Z\\u00A0-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFEF]{2,}))$/,hex:/^#?([a-f0-9]{6}|[a-f0-9]{3})$/i},bs={integer:function(t){return bs.number(t)&&parseInt(t,10)===t},float:function(t){return bs.number(t)&&!bs.integer(t)},array:function(t){return Array.isArray(t)},regexp:function(t){if(t instanceof RegExp)return!0;try{return!!new RegExp(t)}catch{return!1}},date:function(t){return typeof t.getTime==\"function\"&&typeof t.getMonth==\"function\"&&typeof t.getYear==\"function\"&&!isNaN(t.getTime())},number:function(t){return isNaN(t)?!1:typeof t==\"number\"},object:function(t){return typeof t==\"object\"&&!bs.array(t)},method:function(t){return typeof t==\"function\"},email:function(t){return typeof t==\"string\"&&t.length<=320&&!!t.match(yp.email)},url:function(t){return typeof t==\"string\"&&t.length<=2048&&!!t.match(XA())},hex:function(t){return typeof t==\"string\"&&!!t.match(yp.hex)}},ZA=function(t,n,r,o,s){if(t.required&&n===void 0){mg(t,n,r,o,s);return}var i=[\"integer\",\"float\",\"array\",\"regexp\",\"object\",\"method\",\"email\",\"number\",\"date\",\"url\",\"hex\"],a=t.type;i.indexOf(a)>-1?bs[a](n)||o.push(Zt(s.messages.types[a],t.fullField,t.type)):a&&typeof n!==t.type&&o.push(Zt(s.messages.types[a],t.fullField,t.type))},QA=function(t,n,r,o,s){var i=typeof t.len==\"number\",a=typeof t.min==\"number\",l=typeof t.max==\"number\",u=/[\\uD800-\\uDBFF][\\uDC00-\\uDFFF]/g,c=n,f=null,d=typeof n==\"number\",v=typeof n==\"string\",p=Array.isArray(n);if(d?f=\"number\":v?f=\"string\":p&&(f=\"array\"),!f)return!1;p&&(c=n.length),v&&(c=n.replace(u,\"_\").length),i?c!==t.len&&o.push(Zt(s.messages[f].len,t.fullField,t.len)):a&&!l&&c<t.min?o.push(Zt(s.messages[f].min,t.fullField,t.min)):l&&!a&&c>t.max?o.push(Zt(s.messages[f].max,t.fullField,t.max)):a&&l&&(c<t.min||c>t.max)&&o.push(Zt(s.messages[f].range,t.fullField,t.min,t.max))},Co=\"enum\",e4=function(t,n,r,o,s){t[Co]=Array.isArray(t[Co])?t[Co]:[],t[Co].indexOf(n)===-1&&o.push(Zt(s.messages[Co],t.fullField,t[Co].join(\", \")))},t4=function(t,n,r,o,s){if(t.pattern){if(t.pattern instanceof RegExp)t.pattern.lastIndex=0,t.pattern.test(n)||o.push(Zt(s.messages.pattern.mismatch,t.fullField,n,t.pattern));else if(typeof t.pattern==\"string\"){var i=new RegExp(t.pattern);i.test(n)||o.push(Zt(s.messages.pattern.mismatch,t.fullField,n,t.pattern))}}},Be={required:mg,whitespace:JA,type:ZA,range:QA,enum:e4,pattern:t4},n4=function(t,n,r,o,s){var i=[],a=t.required||!t.required&&o.hasOwnProperty(t.field);if(a){if(bt(n,\"string\")&&!t.required)return r();Be.required(t,n,o,i,s,\"string\"),bt(n,\"string\")||(Be.type(t,n,o,i,s),Be.range(t,n,o,i,s),Be.pattern(t,n,o,i,s),t.whitespace===!0&&Be.whitespace(t,n,o,i,s))}r(i)},r4=function(t,n,r,o,s){var i=[],a=t.required||!t.required&&o.hasOwnProperty(t.field);if(a){if(bt(n)&&!t.required)return r();Be.required(t,n,o,i,s),n!==void 0&&Be.type(t,n,o,i,s)}r(i)},o4=function(t,n,r,o,s){var i=[],a=t.required||!t.required&&o.hasOwnProperty(t.field);if(a){if(n===\"\"&&(n=void 0),bt(n)&&!t.required)return r();Be.required(t,n,o,i,s),n!==void 0&&(Be.type(t,n,o,i,s),Be.range(t,n,o,i,s))}r(i)},s4=function(t,n,r,o,s){var i=[],a=t.required||!t.required&&o.hasOwnProperty(t.field);if(a){if(bt(n)&&!t.required)return r();Be.required(t,n,o,i,s),n!==void 0&&Be.type(t,n,o,i,s)}r(i)},i4=function(t,n,r,o,s){var i=[],a=t.required||!t.required&&o.hasOwnProperty(t.field);if(a){if(bt(n)&&!t.required)return r();Be.required(t,n,o,i,s),bt(n)||Be.type(t,n,o,i,s)}r(i)},a4=function(t,n,r,o,s){var i=[],a=t.required||!t.required&&o.hasOwnProperty(t.field);if(a){if(bt(n)&&!t.required)return r();Be.required(t,n,o,i,s),n!==void 0&&(Be.type(t,n,o,i,s),Be.range(t,n,o,i,s))}r(i)},l4=function(t,n,r,o,s){var i=[],a=t.required||!t.required&&o.hasOwnProperty(t.field);if(a){if(bt(n)&&!t.required)return r();Be.required(t,n,o,i,s),n!==void 0&&(Be.type(t,n,o,i,s),Be.range(t,n,o,i,s))}r(i)},u4=function(t,n,r,o,s){var i=[],a=t.required||!t.required&&o.hasOwnProperty(t.field);if(a){if(n==null&&!t.required)return r();Be.required(t,n,o,i,s,\"array\"),n!=null&&(Be.type(t,n,o,i,s),Be.range(t,n,o,i,s))}r(i)},c4=function(t,n,r,o,s){var i=[],a=t.required||!t.required&&o.hasOwnProperty(t.field);if(a){if(bt(n)&&!t.required)return r();Be.required(t,n,o,i,s),n!==void 0&&Be.type(t,n,o,i,s)}r(i)},f4=\"enum\",d4=function(t,n,r,o,s){var i=[],a=t.required||!t.required&&o.hasOwnProperty(t.field);if(a){if(bt(n)&&!t.required)return r();Be.required(t,n,o,i,s),n!==void 0&&Be[f4](t,n,o,i,s)}r(i)},p4=function(t,n,r,o,s){var i=[],a=t.required||!t.required&&o.hasOwnProperty(t.field);if(a){if(bt(n,\"string\")&&!t.required)return r();Be.required(t,n,o,i,s),bt(n,\"string\")||Be.pattern(t,n,o,i,s)}r(i)},h4=function(t,n,r,o,s){var i=[],a=t.required||!t.required&&o.hasOwnProperty(t.field);if(a){if(bt(n,\"date\")&&!t.required)return r();if(Be.required(t,n,o,i,s),!bt(n,\"date\")){var l;n instanceof Date?l=n:l=new Date(n),Be.type(t,l,o,i,s),l&&Be.range(t,l.getTime(),o,i,s)}}r(i)},v4=function(t,n,r,o,s){var i=[],a=Array.isArray(n)?\"array\":typeof n;Be.required(t,n,o,i,s,a),r(i)},Bl=function(t,n,r,o,s){var i=t.type,a=[],l=t.required||!t.required&&o.hasOwnProperty(t.field);if(l){if(bt(n,i)&&!t.required)return r();Be.required(t,n,o,a,s,i),bt(n,i)||Be.type(t,n,o,a,s)}r(a)},m4=function(t,n,r,o,s){var i=[],a=t.required||!t.required&&o.hasOwnProperty(t.field);if(a){if(bt(n)&&!t.required)return r();Be.required(t,n,o,i,s)}r(i)},Ls={string:n4,method:r4,number:o4,boolean:s4,regexp:i4,integer:a4,float:l4,array:u4,object:c4,enum:d4,pattern:p4,date:h4,url:Bl,hex:Bl,email:Bl,required:v4,any:m4};function Pu(){return{default:\"Validation error on field %s\",required:\"%s is required\",enum:\"%s must be one of %s\",whitespace:\"%s cannot be empty\",date:{format:\"%s date %s is invalid for format %s\",parse:\"%s date could not be parsed, %s is invalid \",invalid:\"%s date %s is invalid\"},types:{string:\"%s is not a %s\",method:\"%s is not a %s (function)\",array:\"%s is not an %s\",object:\"%s is not an %s\",number:\"%s is not a %s\",date:\"%s is not a %s\",boolean:\"%s is not a %s\",integer:\"%s is not an %s\",float:\"%s is not a %s\",regexp:\"%s is not a valid %s\",email:\"%s is not a valid %s\",url:\"%s is not a valid %s\",hex:\"%s is not a valid %s\"},string:{len:\"%s must be exactly %s characters\",min:\"%s must be at least %s characters\",max:\"%s cannot be longer than %s characters\",range:\"%s must be between %s and %s characters\"},number:{len:\"%s must equal %s\",min:\"%s cannot be less than %s\",max:\"%s cannot be greater than %s\",range:\"%s must be between %s and %s\"},array:{len:\"%s must be exactly %s in length\",min:\"%s cannot be less than %s in length\",max:\"%s cannot be greater than %s in length\",range:\"%s must be between %s and %s in length\"},pattern:{mismatch:\"%s value %s does not match pattern %s\"},clone:function(){var t=JSON.parse(JSON.stringify(this));return t.clone=this.clone,t}}}var Nu=Pu(),vi=function(){function e(n){this.rules=null,this._messages=Nu,this.define(n)}var t=e.prototype;return t.define=function(r){var o=this;if(!r)throw new Error(\"Cannot configure a schema with no rules\");if(typeof r!=\"object\"||Array.isArray(r))throw new Error(\"Rules must be an object\");this.rules={},Object.keys(r).forEach(function(s){var i=r[s];o.rules[s]=Array.isArray(i)?i:[i]})},t.messages=function(r){return r&&(this._messages=bp(Pu(),r)),this._messages},t.validate=function(r,o,s){var i=this;o===void 0&&(o={}),s===void 0&&(s=function(){});var a=r,l=o,u=s;if(typeof l==\"function\"&&(u=l,l={}),!this.rules||Object.keys(this.rules).length===0)return u&&u(null,a),Promise.resolve(a);function c(h){var b=[],m={};function S(w){if(Array.isArray(w)){var y;b=(y=b).concat.apply(y,w)}else b.push(w)}for(var _=0;_<h.length;_++)S(h[_]);b.length?(m=Iu(b),u(b,m)):u(null,a)}if(l.messages){var f=this.messages();f===Nu&&(f=Pu()),bp(f,l.messages),l.messages=f}else l.messages=this.messages();var d={},v=l.keys||Object.keys(this.rules);v.forEach(function(h){var b=i.rules[h],m=a[h];b.forEach(function(S){var _=S;typeof _.transform==\"function\"&&(a===r&&(a=eo({},a)),m=a[h]=_.transform(m)),typeof _==\"function\"?_={validator:_}:_=eo({},_),_.validator=i.getValidationMethod(_),_.validator&&(_.field=h,_.fullField=_.fullField||h,_.type=i.getType(_),d[h]=d[h]||[],d[h].push({rule:_,value:m,source:a,field:h}))})});var p={};return WA(d,l,function(h,b){var m=h.rule,S=(m.type===\"object\"||m.type===\"array\")&&(typeof m.fields==\"object\"||typeof m.defaultField==\"object\");S=S&&(m.required||!m.required&&h.value),m.field=h.field;function _(A,R){return eo({},R,{fullField:m.fullField+\".\"+A,fullFields:m.fullFields?[].concat(m.fullFields,[A]):[A]})}function w(A){A===void 0&&(A=[]);var R=Array.isArray(A)?A:[A];!l.suppressWarning&&R.length&&e.warning(\"async-validator:\",R),R.length&&m.message!==void 0&&(R=[].concat(m.message));var N=R.map(gp(m,a));if(l.first&&N.length)return p[m.field]=1,b(N);if(!S)b(N);else{if(m.required&&!h.value)return m.message!==void 0?N=[].concat(m.message).map(gp(m,a)):l.error&&(N=[l.error(m,Zt(l.messages.required,m.field))]),b(N);var I={};m.defaultField&&Object.keys(h.value).map(function($){I[$]=m.defaultField}),I=eo({},I,h.rule.fields);var x={};Object.keys(I).forEach(function($){var F=I[$],Y=Array.isArray(F)?F:[F];x[$]=Y.map(_.bind(null,$))});var k=new e(x);k.messages(l.messages),h.rule.options&&(h.rule.options.messages=l.messages,h.rule.options.error=l.error),k.validate(h.value,h.rule.options||l,function($){var F=[];N&&N.length&&F.push.apply(F,N),$&&$.length&&F.push.apply(F,$),b(F.length?F:null)})}}var y;if(m.asyncValidator)y=m.asyncValidator(m,h.value,w,h.source,l);else if(m.validator){try{y=m.validator(m,h.value,w,h.source,l)}catch(A){console.error==null,l.suppressValidatorError||setTimeout(function(){throw A},0),w(A.message)}y===!0?w():y===!1?w(typeof m.message==\"function\"?m.message(m.fullField||m.field):m.message||(m.fullField||m.field)+\" fails\"):y instanceof Array?w(y):y instanceof Error&&w(y.message)}y&&y.then&&y.then(function(){return w()},function(A){return w(A)})},function(h){c(h)},a)},t.getType=function(r){if(r.type===void 0&&r.pattern instanceof RegExp&&(r.type=\"pattern\"),typeof r.validator!=\"function\"&&r.type&&!Ls.hasOwnProperty(r.type))throw new Error(Zt(\"Unknown rule type %s\",r.type));return r.type||\"string\"},t.getValidationMethod=function(r){if(typeof r.validator==\"function\")return r.validator;var o=Object.keys(r),s=o.indexOf(\"message\");return s!==-1&&o.splice(s,1),o.length===1&&o[0]===\"required\"?Ls.required:Ls[this.getType(r)]||void 0},e}();vi.register=function(t,n){if(typeof n!=\"function\")throw new Error(\"Cannot register a validator by type, validator is not a function\");Ls[t]=n};vi.warning=HA;vi.messages=Nu;vi.validators=Ls;const g4=[\"\",\"error\",\"validating\",\"success\"],b4=xe({label:String,labelWidth:{type:[String,Number],default:\"\"},labelPosition:{type:String,values:[\"left\",\"right\",\"top\",\"\"],default:\"\"},prop:{type:we([String,Array])},required:{type:Boolean,default:void 0},rules:{type:we([Object,Array])},error:String,validateStatus:{type:String,values:g4},for:String,inlineMessage:{type:[String,Boolean],default:\"\"},showMessage:{type:Boolean,default:!0},size:{type:String,values:es}}),wp=\"ElLabelWrap\";var y4=Z({name:wp,props:{isAutoWidth:Boolean,updateAll:Boolean},setup(e,{slots:t}){const n=Ee(ts,void 0),r=Ee(ho);r||Br(wp,\"usage: <el-form-item><label-wrap /></el-form-item>\");const o=$e(\"form\"),s=V(),i=V(0),a=()=>{var c;if((c=s.value)!=null&&c.firstElementChild){const f=window.getComputedStyle(s.value.firstElementChild).width;return Math.ceil(Number.parseFloat(f))}else return 0},l=(c=\"update\")=>{Ie(()=>{t.default&&e.isAutoWidth&&(c===\"update\"?i.value=a():c===\"remove\"&&(n==null||n.deregisterLabelWidth(i.value)))})},u=()=>l(\"update\");return Ke(()=>{u()}),St(()=>{l(\"remove\")}),mo(()=>u()),he(i,(c,f)=>{e.updateAll&&(n==null||n.registerLabelWidth(c,f))}),Dt(C(()=>{var c,f;return(f=(c=s.value)==null?void 0:c.firstElementChild)!=null?f:null}),u),()=>{var c,f;if(!t)return null;const{isAutoWidth:d}=e;if(d){const v=n==null?void 0:n.autoLabelWidth,p=r==null?void 0:r.hasLabel,h={};if(p&&v&&v!==\"auto\"){const b=Math.max(0,Number.parseInt(v,10)-i.value),S=(r.labelPosition||n.labelPosition)===\"left\"?\"marginRight\":\"marginLeft\";b&&(h[S]=`${b}px`)}return re(\"div\",{ref:s,class:[o.be(\"item\",\"label-wrap\")],style:h},[(c=t.default)==null?void 0:c.call(t)])}else return re(nt,{ref:s},[(f=t.default)==null?void 0:f.call(t)])}}});const w4=Z({name:\"ElFormItem\"}),S4=Z({...w4,props:b4,setup(e,{expose:t}){const n=e,r=go(),o=Ee(ts,void 0),s=Ee(ho,void 0),i=In(void 0,{formItem:!1}),a=$e(\"form-item\"),l=pr().value,u=V([]),c=V(\"\"),f=V1(c,100),d=V(\"\"),v=V();let p,h=!1;const b=C(()=>n.labelPosition||(o==null?void 0:o.labelPosition)),m=C(()=>{if(b.value===\"top\")return{};const K=pn(n.labelWidth||(o==null?void 0:o.labelWidth)||\"\");return K?{width:K}:{}}),S=C(()=>{if(b.value===\"top\"||o!=null&&o.inline)return{};if(!n.label&&!n.labelWidth&&x)return{};const K=pn(n.labelWidth||(o==null?void 0:o.labelWidth)||\"\");return!n.label&&!r.label?{marginLeft:K}:{}}),_=C(()=>[a.b(),a.m(i.value),a.is(\"error\",c.value===\"error\"),a.is(\"validating\",c.value===\"validating\"),a.is(\"success\",c.value===\"success\"),a.is(\"required\",P.value||n.required),a.is(\"no-asterisk\",o==null?void 0:o.hideRequiredAsterisk),(o==null?void 0:o.requireAsteriskPosition)===\"right\"?\"asterisk-right\":\"asterisk-left\",{[a.m(\"feedback\")]:o==null?void 0:o.statusIcon,[a.m(`label-${b.value}`)]:b.value}]),w=C(()=>Bt(n.inlineMessage)?n.inlineMessage:(o==null?void 0:o.inlineMessage)||!1),y=C(()=>[a.e(\"error\"),{[a.em(\"error\",\"inline\")]:w.value}]),A=C(()=>n.prop?Ae(n.prop)?n.prop:n.prop.join(\".\"):\"\"),R=C(()=>!!(n.label||r.label)),N=C(()=>n.for||(u.value.length===1?u.value[0]:void 0)),I=C(()=>!N.value&&R.value),x=!!s,k=C(()=>{const K=o==null?void 0:o.model;if(!(!K||!n.prop))return Fl(K,n.prop).value}),$=C(()=>{const{required:K}=n,J=[];n.rules&&J.push(...yn(n.rules));const oe=o==null?void 0:o.rules;if(oe&&n.prop){const ge=Fl(oe,n.prop).value;ge&&J.push(...yn(ge))}if(K!==void 0){const ge=J.map((E,T)=>[E,T]).filter(([E])=>Object.keys(E).includes(\"required\"));if(ge.length>0)for(const[E,T]of ge)E.required!==K&&(J[T]={...E,required:K});else J.push({required:K})}return J}),F=C(()=>$.value.length>0),Y=K=>$.value.filter(oe=>!oe.trigger||!K?!0:Array.isArray(oe.trigger)?oe.trigger.includes(K):oe.trigger===K).map(({trigger:oe,...ge})=>ge),P=C(()=>$.value.some(K=>K.required)),O=C(()=>{var K;return f.value===\"error\"&&n.showMessage&&((K=o==null?void 0:o.showMessage)!=null?K:!0)}),j=C(()=>`${n.label||\"\"}${(o==null?void 0:o.labelSuffix)||\"\"}`),Q=K=>{c.value=K},me=K=>{var J,oe;const{errors:ge,fields:E}=K;Q(\"error\"),d.value=ge?(oe=(J=ge==null?void 0:ge[0])==null?void 0:J.message)!=null?oe:`${n.prop} is required`:\"\",o==null||o.emit(\"validate\",n.prop,!1,d.value)},Pe=()=>{Q(\"success\"),o==null||o.emit(\"validate\",n.prop,!0,\"\")},Re=async K=>{const J=A.value;return new vi({[J]:K}).validate({[J]:k.value},{firstFields:!0}).then(()=>(Pe(),!0)).catch(ge=>(me(ge),Promise.reject(ge)))},Ce=async(K,J)=>{if(h||!n.prop)return!1;const oe=ve(J);if(!F.value)return J==null||J(!1),!1;const ge=Y(K);return ge.length===0?(J==null||J(!0),!0):(Q(\"validating\"),Re(ge).then(()=>(J==null||J(!0),!0)).catch(E=>{const{fields:T}=E;return J==null||J(!1,T),oe?!1:Promise.reject(T)}))},_e=()=>{Q(\"\"),d.value=\"\",h=!1},qe=async()=>{const K=o==null?void 0:o.model;if(!K||!n.prop)return;const J=Fl(K,n.prop);h=!0,J.value=Wd(p),await Ie(),_e(),h=!1},ze=K=>{u.value.includes(K)||u.value.push(K)},De=K=>{u.value=u.value.filter(J=>J!==K)};he(()=>n.error,K=>{d.value=K||\"\",Q(K?\"error\":\"\")},{immediate:!0}),he(()=>n.validateStatus,K=>Q(K||\"\"));const B=wt({...vr(n),$el:v,size:i,validateState:c,labelId:l,inputIds:u,isGroup:I,hasLabel:R,fieldValue:k,addInputId:ze,removeInputId:De,resetField:qe,clearValidate:_e,validate:Ce});return ft(ho,B),Ke(()=>{n.prop&&(o==null||o.addField(B),p=Wd(k.value))}),St(()=>{o==null||o.removeField(B)}),t({size:i,validateMessage:d,validateState:c,validate:Ce,clearValidate:_e,resetField:qe}),(K,J)=>{var oe;return L(),ee(\"div\",{ref_key:\"formItemRef\",ref:v,class:U(g(_)),role:g(I)?\"group\":void 0,\"aria-labelledby\":g(I)?g(l):void 0},[re(g(y4),{\"is-auto-width\":g(m).width===\"auto\",\"update-all\":((oe=g(o))==null?void 0:oe.labelWidth)===\"auto\"},{default:ce(()=>[g(R)?(L(),fe(Xe(g(N)?\"label\":\"div\"),{key:0,id:g(l),for:g(N),class:U(g(a).e(\"label\")),style:ot(g(m))},{default:ce(()=>[de(K.$slots,\"label\",{label:g(j)},()=>[Un(He(g(j)),1)])]),_:3},8,[\"id\",\"for\",\"class\",\"style\"])):ae(\"v-if\",!0)]),_:3},8,[\"is-auto-width\",\"update-all\"]),le(\"div\",{class:U(g(a).e(\"content\")),style:ot(g(S))},[de(K.$slots,\"default\"),re(sw,{name:`${g(a).namespace.value}-zoom-in-top`},{default:ce(()=>[g(O)?de(K.$slots,\"error\",{key:0,error:d.value},()=>[le(\"div\",{class:U(g(y))},He(d.value),3)]):ae(\"v-if\",!0)]),_:3},8,[\"name\"])],6)],10,[\"role\",\"aria-labelledby\"])}}});var gg=Ne(S4,[[\"__file\",\"form-item.vue\"]]);const IN=yt(BA,{FormItem:gg}),PN=wo(gg);let mn;const E4=`\n  height:0 !important;\n  visibility:hidden !important;\n  ${oS()?\"\":\"overflow:hidden !important;\"}\n  position:absolute !important;\n  z-index:-1000 !important;\n  top:0 !important;\n  right:0 !important;\n`,_4=[\"letter-spacing\",\"line-height\",\"padding-top\",\"padding-bottom\",\"font-family\",\"font-weight\",\"font-size\",\"text-rendering\",\"text-transform\",\"width\",\"text-indent\",\"padding-left\",\"padding-right\",\"border-width\",\"box-sizing\"];function C4(e){const t=window.getComputedStyle(e),n=t.getPropertyValue(\"box-sizing\"),r=Number.parseFloat(t.getPropertyValue(\"padding-bottom\"))+Number.parseFloat(t.getPropertyValue(\"padding-top\")),o=Number.parseFloat(t.getPropertyValue(\"border-bottom-width\"))+Number.parseFloat(t.getPropertyValue(\"border-top-width\"));return{contextStyle:_4.map(i=>`${i}:${t.getPropertyValue(i)}`).join(\";\"),paddingSize:r,borderSize:o,boxSizing:n}}function Sp(e,t=1,n){var r;mn||(mn=document.createElement(\"textarea\"),document.body.appendChild(mn));const{paddingSize:o,borderSize:s,boxSizing:i,contextStyle:a}=C4(e);mn.setAttribute(\"style\",`${a};${E4}`),mn.value=e.value||e.placeholder||\"\";let l=mn.scrollHeight;const u={};i===\"border-box\"?l=l+s:i===\"content-box\"&&(l=l-o),mn.value=\"\";const c=mn.scrollHeight-o;if(je(t)){let f=c*t;i===\"border-box\"&&(f=f+o+s),l=Math.max(f,l),u.minHeight=`${f}px`}if(je(n)){let f=c*n;i===\"border-box\"&&(f=f+o+s),l=Math.min(f,l)}return u.height=`${l}px`,(r=mn.parentNode)==null||r.removeChild(mn),mn=void 0,u}const T4=xe({id:{type:String,default:void 0},size:So,disabled:Boolean,modelValue:{type:we([String,Number,Object]),default:\"\"},maxlength:{type:[String,Number]},minlength:{type:[String,Number]},type:{type:String,default:\"text\"},resize:{type:String,values:[\"none\",\"both\",\"horizontal\",\"vertical\"]},autosize:{type:we([Boolean,Object]),default:!1},autocomplete:{type:String,default:\"off\"},formatter:{type:Function},parser:{type:Function},placeholder:{type:String},form:{type:String},readonly:Boolean,clearable:Boolean,showPassword:Boolean,showWordLimit:Boolean,suffixIcon:{type:jt},prefixIcon:{type:jt},containerRole:{type:String,default:void 0},tabindex:{type:[String,Number],default:0},validateEvent:{type:Boolean,default:!0},inputStyle:{type:we([Object,Array,String]),default:()=>ol({})},autofocus:Boolean,rows:{type:Number,default:2},...yr([\"ariaLabel\"])}),O4={[rt]:e=>Ae(e),input:e=>Ae(e),change:e=>Ae(e),focus:e=>e instanceof FocusEvent,blur:e=>e instanceof FocusEvent,clear:()=>!0,mouseleave:e=>e instanceof MouseEvent,mouseenter:e=>e instanceof MouseEvent,keydown:e=>e instanceof Event,compositionstart:e=>e instanceof CompositionEvent,compositionupdate:e=>e instanceof CompositionEvent,compositionend:e=>e instanceof CompositionEvent},A4=Z({name:\"ElInput\",inheritAttrs:!1}),R4=Z({...A4,props:T4,emits:O4,setup(e,{expose:t,emit:n}){const r=e,o=Zy(),s=go(),i=C(()=>{const z={};return r.containerRole===\"combobox\"&&(z[\"aria-haspopup\"]=o[\"aria-haspopup\"],z[\"aria-owns\"]=o[\"aria-owns\"],z[\"aria-expanded\"]=o[\"aria-expanded\"]),z}),a=C(()=>[r.type===\"textarea\"?b.b():h.b(),h.m(v.value),h.is(\"disabled\",p.value),h.is(\"exceed\",_e.value),{[h.b(\"group\")]:s.prepend||s.append,[h.m(\"prefix\")]:s.prefix||r.prefixIcon,[h.m(\"suffix\")]:s.suffix||r.suffixIcon||r.clearable||r.showPassword,[h.bm(\"suffix\",\"password-clear\")]:me.value&&Pe.value,[h.b(\"hidden\")]:r.type===\"hidden\"},o.class]),l=C(()=>[h.e(\"wrapper\"),h.is(\"focus\",I.value)]),u=JT({excludeKeys:C(()=>Object.keys(i.value))}),{form:c,formItem:f}=Vr(),{inputId:d}=hi(r,{formItemContext:f}),v=In(),p=ns(),h=$e(\"input\"),b=$e(\"textarea\"),m=ir(),S=ir(),_=V(!1),w=V(!1),y=V(),A=ir(r.inputStyle),R=C(()=>m.value||S.value),{wrapperRef:N,isFocused:I,handleFocus:x,handleBlur:k}=fg(R,{beforeFocus(){return p.value},afterBlur(){var z;r.validateEvent&&((z=f==null?void 0:f.validate)==null||z.call(f,\"blur\").catch(be=>void 0))}}),$=C(()=>{var z;return(z=c==null?void 0:c.statusIcon)!=null?z:!1}),F=C(()=>(f==null?void 0:f.validateState)||\"\"),Y=C(()=>F.value&&Vm[F.value]),P=C(()=>w.value?VT:IT),O=C(()=>[o.style]),j=C(()=>[r.inputStyle,A.value,{resize:r.resize}]),Q=C(()=>qn(r.modelValue)?\"\":String(r.modelValue)),me=C(()=>r.clearable&&!p.value&&!r.readonly&&!!Q.value&&(I.value||_.value)),Pe=C(()=>r.showPassword&&!p.value&&!r.readonly&&!!Q.value&&(!!Q.value||I.value)),Re=C(()=>r.showWordLimit&&!!r.maxlength&&(r.type===\"text\"||r.type===\"textarea\")&&!p.value&&!r.readonly&&!r.showPassword),Ce=C(()=>Q.value.length),_e=C(()=>!!Re.value&&Ce.value>Number(r.maxlength)),qe=C(()=>!!s.suffix||!!r.suffixIcon||me.value||r.showPassword||Re.value||!!F.value&&$.value),[ze,De]=gA(m);Dt(S,z=>{if(J(),!Re.value||r.resize!==\"both\")return;const be=z[0],{width:Le}=be.contentRect;y.value={right:`calc(100% - ${Le+15+6}px)`}});const B=()=>{const{type:z,autosize:be}=r;if(!(!st||z!==\"textarea\"||!S.value))if(be){const Le=Oe(be)?be.minRows:void 0,ke=Oe(be)?be.maxRows:void 0,dt=Sp(S.value,Le,ke);A.value={overflowY:\"hidden\",...dt},Ie(()=>{S.value.offsetHeight,A.value=dt})}else A.value={minHeight:Sp(S.value).minHeight}},J=(z=>{let be=!1;return()=>{var Le;if(be||!r.autosize)return;((Le=S.value)==null?void 0:Le.offsetParent)===null||(z(),be=!0)}})(B),oe=()=>{const z=R.value,be=r.formatter?r.formatter(Q.value):Q.value;!z||z.value===be||(z.value=be)},ge=async z=>{ze();let{value:be}=z.target;if(r.formatter&&(be=r.parser?r.parser(be):be),!T.value){if(be===Q.value){oe();return}n(rt,be),n(\"input\",be),await Ie(),oe(),De()}},E=z=>{n(\"change\",z.target.value)},{isComposing:T,handleCompositionStart:M,handleCompositionUpdate:W,handleCompositionEnd:G}=dg({emit:n,afterComposition:ge}),q=()=>{w.value=!w.value,se()},se=async()=>{var z;await Ie(),(z=R.value)==null||z.focus()},ne=()=>{var z;return(z=R.value)==null?void 0:z.blur()},te=z=>{_.value=!1,n(\"mouseleave\",z)},X=z=>{_.value=!0,n(\"mouseenter\",z)},Se=z=>{n(\"keydown\",z)},ue=()=>{var z;(z=R.value)==null||z.select()},ye=()=>{n(rt,\"\"),n(\"change\",\"\"),n(\"clear\"),n(\"input\",\"\")};return he(()=>r.modelValue,()=>{var z;Ie(()=>B()),r.validateEvent&&((z=f==null?void 0:f.validate)==null||z.call(f,\"change\").catch(be=>void 0))}),he(Q,()=>oe()),he(()=>r.type,async()=>{await Ie(),oe(),B()}),Ke(()=>{!r.formatter&&r.parser,oe(),Ie(B)}),t({input:m,textarea:S,ref:R,textareaStyle:j,autosize:Jt(r,\"autosize\"),isComposing:T,focus:se,blur:ne,select:ue,clear:ye,resizeTextarea:B}),(z,be)=>(L(),ee(\"div\",En(g(i),{class:[g(a),{[g(h).bm(\"group\",\"append\")]:z.$slots.append,[g(h).bm(\"group\",\"prepend\")]:z.$slots.prepend}],style:g(O),role:z.containerRole,onMouseenter:X,onMouseleave:te}),[ae(\" input \"),z.type!==\"textarea\"?(L(),ee(nt,{key:0},[ae(\" prepend slot \"),z.$slots.prepend?(L(),ee(\"div\",{key:0,class:U(g(h).be(\"group\",\"prepend\"))},[de(z.$slots,\"prepend\")],2)):ae(\"v-if\",!0),le(\"div\",{ref_key:\"wrapperRef\",ref:N,class:U(g(l))},[ae(\" prefix slot \"),z.$slots.prefix||z.prefixIcon?(L(),ee(\"span\",{key:0,class:U(g(h).e(\"prefix\"))},[le(\"span\",{class:U(g(h).e(\"prefix-inner\"))},[de(z.$slots,\"prefix\"),z.prefixIcon?(L(),fe(g(Je),{key:0,class:U(g(h).e(\"icon\"))},{default:ce(()=>[(L(),fe(Xe(z.prefixIcon)))]),_:1},8,[\"class\"])):ae(\"v-if\",!0)],2)],2)):ae(\"v-if\",!0),le(\"input\",En({id:g(d),ref_key:\"input\",ref:m,class:g(h).e(\"inner\")},g(u),{minlength:z.minlength,maxlength:z.maxlength,type:z.showPassword?w.value?\"text\":\"password\":z.type,disabled:g(p),readonly:z.readonly,autocomplete:z.autocomplete,tabindex:z.tabindex,\"aria-label\":z.ariaLabel,placeholder:z.placeholder,style:z.inputStyle,form:z.form,autofocus:z.autofocus,onCompositionstart:g(M),onCompositionupdate:g(W),onCompositionend:g(G),onInput:ge,onChange:E,onKeydown:Se}),null,16,[\"id\",\"minlength\",\"maxlength\",\"type\",\"disabled\",\"readonly\",\"autocomplete\",\"tabindex\",\"aria-label\",\"placeholder\",\"form\",\"autofocus\",\"onCompositionstart\",\"onCompositionupdate\",\"onCompositionend\"]),ae(\" suffix slot \"),g(qe)?(L(),ee(\"span\",{key:1,class:U(g(h).e(\"suffix\"))},[le(\"span\",{class:U(g(h).e(\"suffix-inner\"))},[!g(me)||!g(Pe)||!g(Re)?(L(),ee(nt,{key:0},[de(z.$slots,\"suffix\"),z.suffixIcon?(L(),fe(g(Je),{key:0,class:U(g(h).e(\"icon\"))},{default:ce(()=>[(L(),fe(Xe(z.suffixIcon)))]),_:1},8,[\"class\"])):ae(\"v-if\",!0)],64)):ae(\"v-if\",!0),g(me)?(L(),fe(g(Je),{key:1,class:U([g(h).e(\"icon\"),g(h).e(\"clear\")]),onMousedown:tt(g(ht),[\"prevent\"]),onClick:ye},{default:ce(()=>[re(g(Oc))]),_:1},8,[\"class\",\"onMousedown\"])):ae(\"v-if\",!0),g(Pe)?(L(),fe(g(Je),{key:2,class:U([g(h).e(\"icon\"),g(h).e(\"password\")]),onClick:q},{default:ce(()=>[(L(),fe(Xe(g(P))))]),_:1},8,[\"class\"])):ae(\"v-if\",!0),g(Re)?(L(),ee(\"span\",{key:3,class:U(g(h).e(\"count\"))},[le(\"span\",{class:U(g(h).e(\"count-inner\"))},He(g(Ce))+\" / \"+He(z.maxlength),3)],2)):ae(\"v-if\",!0),g(F)&&g(Y)&&g($)?(L(),fe(g(Je),{key:4,class:U([g(h).e(\"icon\"),g(h).e(\"validateIcon\"),g(h).is(\"loading\",g(F)===\"validating\")])},{default:ce(()=>[(L(),fe(Xe(g(Y))))]),_:1},8,[\"class\"])):ae(\"v-if\",!0)],2)],2)):ae(\"v-if\",!0)],2),ae(\" append slot \"),z.$slots.append?(L(),ee(\"div\",{key:1,class:U(g(h).be(\"group\",\"append\"))},[de(z.$slots,\"append\")],2)):ae(\"v-if\",!0)],64)):(L(),ee(nt,{key:1},[ae(\" textarea \"),le(\"textarea\",En({id:g(d),ref_key:\"textarea\",ref:S,class:[g(b).e(\"inner\"),g(h).is(\"focus\",g(I))]},g(u),{minlength:z.minlength,maxlength:z.maxlength,tabindex:z.tabindex,disabled:g(p),readonly:z.readonly,autocomplete:z.autocomplete,style:g(j),\"aria-label\":z.ariaLabel,placeholder:z.placeholder,form:z.form,autofocus:z.autofocus,rows:z.rows,onCompositionstart:g(M),onCompositionupdate:g(W),onCompositionend:g(G),onInput:ge,onFocus:g(x),onBlur:g(k),onChange:E,onKeydown:Se}),null,16,[\"id\",\"minlength\",\"maxlength\",\"tabindex\",\"disabled\",\"readonly\",\"autocomplete\",\"aria-label\",\"placeholder\",\"form\",\"autofocus\",\"rows\",\"onCompositionstart\",\"onCompositionupdate\",\"onCompositionend\",\"onFocus\",\"onBlur\"]),g(Re)?(L(),ee(\"span\",{key:0,style:ot(y.value),class:U(g(h).e(\"count\"))},He(g(Ce))+\" / \"+He(z.maxlength),7)):ae(\"v-if\",!0)],64))],16,[\"role\"]))}});var x4=Ne(R4,[[\"__file\",\"input.vue\"]]);const bg=yt(x4),To=4,I4={vertical:{offset:\"offsetHeight\",scroll:\"scrollTop\",scrollSize:\"scrollHeight\",size:\"height\",key:\"vertical\",axis:\"Y\",client:\"clientY\",direction:\"top\"},horizontal:{offset:\"offsetWidth\",scroll:\"scrollLeft\",scrollSize:\"scrollWidth\",size:\"width\",key:\"horizontal\",axis:\"X\",client:\"clientX\",direction:\"left\"}},P4=({move:e,size:t,bar:n})=>({[n.size]:t,transform:`translate${n.axis}(${e}%)`}),Dc=Symbol(\"scrollbarContextKey\"),N4=xe({vertical:Boolean,size:String,move:Number,ratio:{type:Number,required:!0},always:Boolean}),L4=\"Thumb\",M4=Z({__name:\"thumb\",props:N4,setup(e){const t=e,n=Ee(Dc),r=$e(\"scrollbar\");n||Br(L4,\"can not inject scrollbar context\");const o=V(),s=V(),i=V({}),a=V(!1);let l=!1,u=!1,c=st?document.onselectstart:null;const f=C(()=>I4[t.vertical?\"vertical\":\"horizontal\"]),d=C(()=>P4({size:t.size,move:t.move,bar:f.value})),v=C(()=>o.value[f.value.offset]**2/n.wrapElement[f.value.scrollSize]/t.ratio/s.value[f.value.offset]),p=A=>{var R;if(A.stopPropagation(),A.ctrlKey||[1,2].includes(A.button))return;(R=window.getSelection())==null||R.removeAllRanges(),b(A);const N=A.currentTarget;N&&(i.value[f.value.axis]=N[f.value.offset]-(A[f.value.client]-N.getBoundingClientRect()[f.value.direction]))},h=A=>{if(!s.value||!o.value||!n.wrapElement)return;const R=Math.abs(A.target.getBoundingClientRect()[f.value.direction]-A[f.value.client]),N=s.value[f.value.offset]/2,I=(R-N)*100*v.value/o.value[f.value.offset];n.wrapElement[f.value.scroll]=I*n.wrapElement[f.value.scrollSize]/100},b=A=>{A.stopImmediatePropagation(),l=!0,document.addEventListener(\"mousemove\",m),document.addEventListener(\"mouseup\",S),c=document.onselectstart,document.onselectstart=()=>!1},m=A=>{if(!o.value||!s.value||l===!1)return;const R=i.value[f.value.axis];if(!R)return;const N=(o.value.getBoundingClientRect()[f.value.direction]-A[f.value.client])*-1,I=s.value[f.value.offset]-R,x=(N-I)*100*v.value/o.value[f.value.offset];n.wrapElement[f.value.scroll]=x*n.wrapElement[f.value.scrollSize]/100},S=()=>{l=!1,i.value[f.value.axis]=0,document.removeEventListener(\"mousemove\",m),document.removeEventListener(\"mouseup\",S),y(),u&&(a.value=!1)},_=()=>{u=!1,a.value=!!t.size},w=()=>{u=!0,a.value=l};St(()=>{y(),document.removeEventListener(\"mouseup\",S)});const y=()=>{document.onselectstart!==c&&(document.onselectstart=c)};return tn(Jt(n,\"scrollbarElement\"),\"mousemove\",_),tn(Jt(n,\"scrollbarElement\"),\"mouseleave\",w),(A,R)=>(L(),fe(Fr,{name:g(r).b(\"fade\"),persisted:\"\"},{default:ce(()=>[ct(le(\"div\",{ref_key:\"instance\",ref:o,class:U([g(r).e(\"bar\"),g(r).is(g(f).key)]),onMousedown:h},[le(\"div\",{ref_key:\"thumb\",ref:s,class:U(g(r).e(\"thumb\")),style:ot(g(d)),onMousedown:p},null,38)],34),[[en,A.always||a.value]])]),_:1},8,[\"name\"]))}});var Ep=Ne(M4,[[\"__file\",\"thumb.vue\"]]);const $4=xe({always:{type:Boolean,default:!0},minSize:{type:Number,required:!0}}),k4=Z({__name:\"bar\",props:$4,setup(e,{expose:t}){const n=e,r=Ee(Dc),o=V(0),s=V(0),i=V(\"\"),a=V(\"\"),l=V(1),u=V(1);return t({handleScroll:d=>{if(d){const v=d.offsetHeight-To,p=d.offsetWidth-To;s.value=d.scrollTop*100/v*l.value,o.value=d.scrollLeft*100/p*u.value}},update:()=>{const d=r==null?void 0:r.wrapElement;if(!d)return;const v=d.offsetHeight-To,p=d.offsetWidth-To,h=v**2/d.scrollHeight,b=p**2/d.scrollWidth,m=Math.max(h,n.minSize),S=Math.max(b,n.minSize);l.value=h/(v-h)/(m/(v-m)),u.value=b/(p-b)/(S/(p-S)),a.value=m+To<v?`${m}px`:\"\",i.value=S+To<p?`${S}px`:\"\"}}),(d,v)=>(L(),ee(nt,null,[re(Ep,{move:o.value,ratio:u.value,size:i.value,always:d.always},null,8,[\"move\",\"ratio\",\"size\",\"always\"]),re(Ep,{move:s.value,ratio:l.value,size:a.value,vertical:\"\",always:d.always},null,8,[\"move\",\"ratio\",\"size\",\"always\"])],64))}});var F4=Ne(k4,[[\"__file\",\"bar.vue\"]]);const B4=xe({height:{type:[String,Number],default:\"\"},maxHeight:{type:[String,Number],default:\"\"},native:{type:Boolean,default:!1},wrapStyle:{type:we([String,Object,Array]),default:\"\"},wrapClass:{type:[String,Array],default:\"\"},viewClass:{type:[String,Array],default:\"\"},viewStyle:{type:[String,Array,Object],default:\"\"},noresize:Boolean,tag:{type:String,default:\"div\"},always:Boolean,minSize:{type:Number,default:20},tabindex:{type:[String,Number],default:void 0},id:String,role:String,...yr([\"ariaLabel\",\"ariaOrientation\"])}),D4={scroll:({scrollTop:e,scrollLeft:t})=>[e,t].every(je)},V4=\"ElScrollbar\",j4=Z({name:V4}),z4=Z({...j4,props:B4,emits:D4,setup(e,{expose:t,emit:n}){const r=e,o=$e(\"scrollbar\");let s,i,a=0,l=0;const u=V(),c=V(),f=V(),d=V(),v=C(()=>{const y={};return r.height&&(y.height=pn(r.height)),r.maxHeight&&(y.maxHeight=pn(r.maxHeight)),[r.wrapStyle,y]}),p=C(()=>[r.wrapClass,o.e(\"wrap\"),{[o.em(\"wrap\",\"hidden-default\")]:!r.native}]),h=C(()=>[o.e(\"view\"),r.viewClass]),b=()=>{var y;c.value&&((y=d.value)==null||y.handleScroll(c.value),a=c.value.scrollTop,l=c.value.scrollLeft,n(\"scroll\",{scrollTop:c.value.scrollTop,scrollLeft:c.value.scrollLeft}))};function m(y,A){Oe(y)?c.value.scrollTo(y):je(y)&&je(A)&&c.value.scrollTo(y,A)}const S=y=>{je(y)&&(c.value.scrollTop=y)},_=y=>{je(y)&&(c.value.scrollLeft=y)},w=()=>{var y;(y=d.value)==null||y.update()};return he(()=>r.noresize,y=>{y?(s==null||s(),i==null||i()):({stop:s}=Dt(f,w),i=tn(\"resize\",w))},{immediate:!0}),he(()=>[r.maxHeight,r.height],()=>{r.native||Ie(()=>{var y;w(),c.value&&((y=d.value)==null||y.handleScroll(c.value))})}),ft(Dc,wt({scrollbarElement:u,wrapElement:c})),Ka(()=>{c.value&&(c.value.scrollTop=a,c.value.scrollLeft=l)}),Ke(()=>{r.native||Ie(()=>{w()})}),mo(()=>w()),t({wrapRef:c,update:w,scrollTo:m,setScrollTop:S,setScrollLeft:_,handleScroll:b}),(y,A)=>(L(),ee(\"div\",{ref_key:\"scrollbarRef\",ref:u,class:U(g(o).b())},[le(\"div\",{ref_key:\"wrapRef\",ref:c,class:U(g(p)),style:ot(g(v)),tabindex:y.tabindex,onScroll:b},[(L(),fe(Xe(y.tag),{id:y.id,ref_key:\"resizeRef\",ref:f,class:U(g(h)),style:ot(y.viewStyle),role:y.role,\"aria-label\":y.ariaLabel,\"aria-orientation\":y.ariaOrientation},{default:ce(()=>[de(y.$slots,\"default\")]),_:3},8,[\"id\",\"class\",\"style\",\"role\",\"aria-label\",\"aria-orientation\"]))],46,[\"tabindex\"]),y.native?ae(\"v-if\",!0):(L(),fe(F4,{key:0,ref_key:\"barRef\",ref:d,always:y.always,\"min-size\":y.minSize},null,8,[\"always\",\"min-size\"]))],2))}});var H4=Ne(z4,[[\"__file\",\"scrollbar.vue\"]]);const U4=yt(H4),Vc=Symbol(\"popper\"),yg=Symbol(\"popperContent\"),K4=[\"dialog\",\"grid\",\"group\",\"listbox\",\"menu\",\"navigation\",\"tooltip\",\"tree\"],wg=xe({role:{type:String,values:K4,default:\"tooltip\"}}),q4=Z({name:\"ElPopper\",inheritAttrs:!1}),W4=Z({...q4,props:wg,setup(e,{expose:t}){const n=e,r=V(),o=V(),s=V(),i=V(),a=C(()=>n.role),l={triggerRef:r,popperInstanceRef:o,contentRef:s,referenceRef:i,role:a};return t(l),ft(Vc,l),(u,c)=>de(u.$slots,\"default\")}});var G4=Ne(W4,[[\"__file\",\"popper.vue\"]]);const Sg=xe({arrowOffset:{type:Number,default:5}}),Y4=Z({name:\"ElPopperArrow\",inheritAttrs:!1}),J4=Z({...Y4,props:Sg,setup(e,{expose:t}){const n=e,r=$e(\"popper\"),{arrowOffset:o,arrowRef:s,arrowStyle:i}=Ee(yg,void 0);return he(()=>n.arrowOffset,a=>{o.value=a}),St(()=>{s.value=void 0}),t({arrowRef:s}),(a,l)=>(L(),ee(\"span\",{ref_key:\"arrowRef\",ref:s,class:U(g(r).e(\"arrow\")),style:ot(g(i)),\"data-popper-arrow\":\"\"},null,6))}});var X4=Ne(J4,[[\"__file\",\"arrow.vue\"]]);const Z4=\"ElOnlyChild\",Q4=Z({name:Z4,setup(e,{slots:t,attrs:n}){var r;const o=Ee(ag),s=mA((r=o==null?void 0:o.setForwardRef)!=null?r:ht);return()=>{var i;const a=(i=t.default)==null?void 0:i.call(t,n);if(!a||a.length>1)return null;const l=Eg(a);return l?ct(fr(l,n),[[s]]):null}}});function Eg(e){if(!e)return null;const t=e;for(const n of t){if(Oe(n))switch(n.type){case _t:continue;case Zo:case\"svg\":return _p(n);case nt:return Eg(n.children);default:return n}return _p(n)}return null}function _p(e){const t=$e(\"only-child\");return re(\"span\",{class:t.e(\"content\")},[e])}const _g=xe({virtualRef:{type:we(Object)},virtualTriggering:Boolean,onMouseenter:{type:we(Function)},onMouseleave:{type:we(Function)},onClick:{type:we(Function)},onKeydown:{type:we(Function)},onFocus:{type:we(Function)},onBlur:{type:we(Function)},onContextmenu:{type:we(Function)},id:String,open:Boolean}),eR=Z({name:\"ElPopperTrigger\",inheritAttrs:!1}),tR=Z({...eR,props:_g,setup(e,{expose:t}){const n=e,{role:r,triggerRef:o}=Ee(Vc,void 0);vA(o);const s=C(()=>a.value?n.id:void 0),i=C(()=>{if(r&&r.value===\"tooltip\")return n.open&&n.id?n.id:void 0}),a=C(()=>{if(r&&r.value!==\"tooltip\")return r.value}),l=C(()=>a.value?`${n.open}`:void 0);let u;const c=[\"onMouseenter\",\"onMouseleave\",\"onClick\",\"onKeydown\",\"onFocus\",\"onBlur\",\"onContextmenu\"];return Ke(()=>{he(()=>n.virtualRef,f=>{f&&(o.value=sr(f))},{immediate:!0}),he(o,(f,d)=>{u==null||u(),u=void 0,ar(f)&&(c.forEach(v=>{var p;const h=n[v];h&&(f.addEventListener(v.slice(2).toLowerCase(),h),(p=d==null?void 0:d.removeEventListener)==null||p.call(d,v.slice(2).toLowerCase(),h))}),u=he([s,i,a,l],v=>{[\"aria-controls\",\"aria-describedby\",\"aria-haspopup\",\"aria-expanded\"].forEach((p,h)=>{qn(v[h])?f.removeAttribute(p):f.setAttribute(p,v[h])})},{immediate:!0})),ar(d)&&[\"aria-controls\",\"aria-describedby\",\"aria-haspopup\",\"aria-expanded\"].forEach(v=>d.removeAttribute(v))},{immediate:!0})}),St(()=>{if(u==null||u(),u=void 0,o.value&&ar(o.value)){const f=o.value;c.forEach(d=>{const v=n[d];v&&f.removeEventListener(d.slice(2).toLowerCase(),v)}),o.value=void 0}}),t({triggerRef:o}),(f,d)=>f.virtualTriggering?ae(\"v-if\",!0):(L(),fe(g(Q4),En({key:0},f.$attrs,{\"aria-controls\":g(s),\"aria-describedby\":g(i),\"aria-expanded\":g(l),\"aria-haspopup\":g(a)}),{default:ce(()=>[de(f.$slots,\"default\")]),_:3},16,[\"aria-controls\",\"aria-describedby\",\"aria-expanded\",\"aria-haspopup\"]))}});var nR=Ne(tR,[[\"__file\",\"trigger.vue\"]]);const Dl=\"focus-trap.focus-after-trapped\",Vl=\"focus-trap.focus-after-released\",rR=\"focus-trap.focusout-prevented\",Cp={cancelable:!0,bubbles:!1},oR={cancelable:!0,bubbles:!1},Tp=\"focusAfterTrapped\",Op=\"focusAfterReleased\",Cg=Symbol(\"elFocusTrap\"),jc=V(),ul=V(0),zc=V(0);let Li=0;const Tg=e=>{const t=[],n=document.createTreeWalker(e,NodeFilter.SHOW_ELEMENT,{acceptNode:r=>{const o=r.tagName===\"INPUT\"&&r.type===\"hidden\";return r.disabled||r.hidden||o?NodeFilter.FILTER_SKIP:r.tabIndex>=0||r===document.activeElement?NodeFilter.FILTER_ACCEPT:NodeFilter.FILTER_SKIP}});for(;n.nextNode();)t.push(n.currentNode);return t},Ap=(e,t)=>{for(const n of e)if(!sR(n,t))return n},sR=(e,t)=>{if(getComputedStyle(e).visibility===\"hidden\")return!0;for(;e;){if(t&&e===t)return!1;if(getComputedStyle(e).display===\"none\")return!0;e=e.parentElement}return!1},iR=e=>{const t=Tg(e),n=Ap(t,e),r=Ap(t.reverse(),e);return[n,r]},aR=e=>e instanceof HTMLInputElement&&\"select\"in e,Tr=(e,t)=>{if(e&&e.focus){const n=document.activeElement;e.focus({preventScroll:!0}),zc.value=window.performance.now(),e!==n&&aR(e)&&t&&e.select()}};function Rp(e,t){const n=[...e],r=e.indexOf(t);return r!==-1&&n.splice(r,1),n}const lR=()=>{let e=[];return{push:r=>{const o=e[0];o&&r!==o&&o.pause(),e=Rp(e,r),e.unshift(r)},remove:r=>{var o,s;e=Rp(e,r),(s=(o=e[0])==null?void 0:o.resume)==null||s.call(o)}}},uR=(e,t=!1)=>{const n=document.activeElement;for(const r of e)if(Tr(r,t),document.activeElement!==n)return},xp=lR(),cR=()=>ul.value>zc.value,Mi=()=>{jc.value=\"pointer\",ul.value=window.performance.now()},Ip=()=>{jc.value=\"keyboard\",ul.value=window.performance.now()},fR=()=>(Ke(()=>{Li===0&&(document.addEventListener(\"mousedown\",Mi),document.addEventListener(\"touchstart\",Mi),document.addEventListener(\"keydown\",Ip)),Li++}),St(()=>{Li--,Li<=0&&(document.removeEventListener(\"mousedown\",Mi),document.removeEventListener(\"touchstart\",Mi),document.removeEventListener(\"keydown\",Ip))}),{focusReason:jc,lastUserFocusTimestamp:ul,lastAutomatedFocusTimestamp:zc}),$i=e=>new CustomEvent(rR,{...oR,detail:e}),dR=Z({name:\"ElFocusTrap\",inheritAttrs:!1,props:{loop:Boolean,trapped:Boolean,focusTrapEl:Object,focusStartEl:{type:[Object,String],default:\"first\"}},emits:[Tp,Op,\"focusin\",\"focusout\",\"focusout-prevented\",\"release-requested\"],setup(e,{emit:t}){const n=V();let r,o;const{focusReason:s}=fR();cA(p=>{e.trapped&&!i.paused&&t(\"release-requested\",p)});const i={paused:!1,pause(){this.paused=!0},resume(){this.paused=!1}},a=p=>{if(!e.loop&&!e.trapped||i.paused)return;const{key:h,altKey:b,ctrlKey:m,metaKey:S,currentTarget:_,shiftKey:w}=p,{loop:y}=e,A=h===_n.tab&&!b&&!m&&!S,R=document.activeElement;if(A&&R){const N=_,[I,x]=iR(N);if(I&&x){if(!w&&R===x){const $=$i({focusReason:s.value});t(\"focusout-prevented\",$),$.defaultPrevented||(p.preventDefault(),y&&Tr(I,!0))}else if(w&&[I,N].includes(R)){const $=$i({focusReason:s.value});t(\"focusout-prevented\",$),$.defaultPrevented||(p.preventDefault(),y&&Tr(x,!0))}}else if(R===N){const $=$i({focusReason:s.value});t(\"focusout-prevented\",$),$.defaultPrevented||p.preventDefault()}}};ft(Cg,{focusTrapRef:n,onKeydown:a}),he(()=>e.focusTrapEl,p=>{p&&(n.value=p)},{immediate:!0}),he([n],([p],[h])=>{p&&(p.addEventListener(\"keydown\",a),p.addEventListener(\"focusin\",c),p.addEventListener(\"focusout\",f)),h&&(h.removeEventListener(\"keydown\",a),h.removeEventListener(\"focusin\",c),h.removeEventListener(\"focusout\",f))});const l=p=>{t(Tp,p)},u=p=>t(Op,p),c=p=>{const h=g(n);if(!h)return;const b=p.target,m=p.relatedTarget,S=b&&h.contains(b);e.trapped||m&&h.contains(m)||(r=m),S&&t(\"focusin\",p),!i.paused&&e.trapped&&(S?o=b:Tr(o,!0))},f=p=>{const h=g(n);if(!(i.paused||!h))if(e.trapped){const b=p.relatedTarget;!qn(b)&&!h.contains(b)&&setTimeout(()=>{if(!i.paused&&e.trapped){const m=$i({focusReason:s.value});t(\"focusout-prevented\",m),m.defaultPrevented||Tr(o,!0)}},0)}else{const b=p.target;b&&h.contains(b)||t(\"focusout\",p)}};async function d(){await Ie();const p=g(n);if(p){xp.push(i);const h=p.contains(document.activeElement)?r:document.activeElement;if(r=h,!p.contains(h)){const m=new Event(Dl,Cp);p.addEventListener(Dl,l),p.dispatchEvent(m),m.defaultPrevented||Ie(()=>{let S=e.focusStartEl;Ae(S)||(Tr(S),document.activeElement!==S&&(S=\"first\")),S===\"first\"&&uR(Tg(p),!0),(document.activeElement===h||S===\"container\")&&Tr(p)})}}}function v(){const p=g(n);if(p){p.removeEventListener(Dl,l);const h=new CustomEvent(Vl,{...Cp,detail:{focusReason:s.value}});p.addEventListener(Vl,u),p.dispatchEvent(h),!h.defaultPrevented&&(s.value==\"keyboard\"||!cR()||p.contains(document.activeElement))&&Tr(r??document.body),p.removeEventListener(Vl,u),xp.remove(i)}}return Ke(()=>{e.trapped&&d(),he(()=>e.trapped,p=>{p?d():v()})}),St(()=>{e.trapped&&v(),n.value&&(n.value.removeEventListener(\"keydown\",a),n.value.removeEventListener(\"focusin\",c),n.value.removeEventListener(\"focusout\",f),n.value=void 0)}),{onKeydown:a}}});function pR(e,t,n,r,o,s){return de(e.$slots,\"default\",{handleKeydown:e.onKeydown})}var Hc=Ne(dR,[[\"render\",pR],[\"__file\",\"focus-trap.vue\"]]);const hR=[\"fixed\",\"absolute\"],vR=xe({boundariesPadding:{type:Number,default:0},fallbackPlacements:{type:we(Array),default:void 0},gpuAcceleration:{type:Boolean,default:!0},offset:{type:Number,default:12},placement:{type:String,values:il,default:\"bottom\"},popperOptions:{type:we(Object),default:()=>({})},strategy:{type:String,values:hR,default:\"absolute\"}}),Og=xe({...vR,id:String,style:{type:we([String,Array,Object])},className:{type:we([String,Array,Object])},effect:{type:we(String),default:\"dark\"},visible:Boolean,enterable:{type:Boolean,default:!0},pure:Boolean,focusOnShow:{type:Boolean,default:!1},trapping:{type:Boolean,default:!1},popperClass:{type:we([String,Array,Object])},popperStyle:{type:we([String,Array,Object])},referenceEl:{type:we(Object)},triggerTargetEl:{type:we(Object)},stopPopperMouseEvent:{type:Boolean,default:!0},virtualTriggering:Boolean,zIndex:Number,...yr([\"ariaLabel\"])}),mR={mouseenter:e=>e instanceof MouseEvent,mouseleave:e=>e instanceof MouseEvent,focus:()=>!0,blur:()=>!0,close:()=>!0},gR=(e,t=[])=>{const{placement:n,strategy:r,popperOptions:o}=e,s={placement:n,strategy:r,...o,modifiers:[...yR(e),...t]};return wR(s,o==null?void 0:o.modifiers),s},bR=e=>{if(st)return sr(e)};function yR(e){const{offset:t,gpuAcceleration:n,fallbackPlacements:r}=e;return[{name:\"offset\",options:{offset:[0,t??12]}},{name:\"preventOverflow\",options:{padding:{top:2,bottom:2,left:5,right:5}}},{name:\"flip\",options:{padding:5,fallbackPlacements:r}},{name:\"computeStyles\",options:{gpuAcceleration:n}}]}function wR(e,t){t&&(e.modifiers=[...e.modifiers,...t??[]])}const SR=0,ER=e=>{const{popperInstanceRef:t,contentRef:n,triggerRef:r,role:o}=Ee(Vc,void 0),s=V(),i=V(),a=C(()=>({name:\"eventListeners\",enabled:!!e.visible})),l=C(()=>{var m;const S=g(s),_=(m=g(i))!=null?m:SR;return{name:\"arrow\",enabled:!Rm(S),options:{element:S,padding:_}}}),u=C(()=>({onFirstUpdate:()=>{p()},...gR(e,[g(l),g(a)])})),c=C(()=>bR(e.referenceEl)||g(r)),{attributes:f,state:d,styles:v,update:p,forceUpdate:h,instanceRef:b}=aA(c,n,u);return he(b,m=>t.value=m),Ke(()=>{he(()=>{var m;return(m=g(c))==null?void 0:m.getBoundingClientRect()},()=>{p()})}),{attributes:f,arrowRef:s,contentRef:n,instanceRef:b,state:d,styles:v,role:o,forceUpdate:h,update:p}},_R=(e,{attributes:t,styles:n,role:r})=>{const{nextZIndex:o}=Fc(),s=$e(\"popper\"),i=C(()=>g(t).popper),a=V(je(e.zIndex)?e.zIndex:o()),l=C(()=>[s.b(),s.is(\"pure\",e.pure),s.is(e.effect),e.popperClass]),u=C(()=>[{zIndex:g(a)},g(n).popper,e.popperStyle||{}]),c=C(()=>r.value===\"dialog\"?\"false\":void 0),f=C(()=>g(n).arrow||{});return{ariaModal:c,arrowStyle:f,contentAttrs:i,contentClass:l,contentStyle:u,contentZIndex:a,updateZIndex:()=>{a.value=je(e.zIndex)?e.zIndex:o()}}},CR=(e,t)=>{const n=V(!1),r=V();return{focusStartRef:r,trapped:n,onFocusAfterReleased:u=>{var c;((c=u.detail)==null?void 0:c.focusReason)!==\"pointer\"&&(r.value=\"first\",t(\"blur\"))},onFocusAfterTrapped:()=>{t(\"focus\")},onFocusInTrap:u=>{e.visible&&!n.value&&(u.target&&(r.value=u.target),n.value=!0)},onFocusoutPrevented:u=>{e.trapping||(u.detail.focusReason===\"pointer\"&&u.preventDefault(),n.value=!1)},onReleaseRequested:()=>{n.value=!1,t(\"close\")}}},TR=Z({name:\"ElPopperContent\"}),OR=Z({...TR,props:Og,emits:mR,setup(e,{expose:t,emit:n}){const r=e,{focusStartRef:o,trapped:s,onFocusAfterReleased:i,onFocusAfterTrapped:a,onFocusInTrap:l,onFocusoutPrevented:u,onReleaseRequested:c}=CR(r,n),{attributes:f,arrowRef:d,contentRef:v,styles:p,instanceRef:h,role:b,update:m}=ER(r),{ariaModal:S,arrowStyle:_,contentAttrs:w,contentClass:y,contentStyle:A,updateZIndex:R}=_R(r,{styles:p,attributes:f,role:b}),N=Ee(ho,void 0);ft(yg,{arrowStyle:_,arrowRef:d,arrowOffset:V()}),N&&ft(ho,{...N,addInputId:ht,removeInputId:ht});let x;const k=(F=!0)=>{m(),F&&R()},$=()=>{k(!1),r.visible&&r.focusOnShow?s.value=!0:r.visible===!1&&(s.value=!1)};return Ke(()=>{he(()=>r.triggerTargetEl,(F,Y)=>{x==null||x(),x=void 0;const P=g(F||v.value),O=g(Y||v.value);ar(P)&&(x=he([b,()=>r.ariaLabel,S,()=>r.id],j=>{[\"role\",\"aria-label\",\"aria-modal\",\"id\"].forEach((Q,me)=>{qn(j[me])?P.removeAttribute(Q):P.setAttribute(Q,j[me])})},{immediate:!0})),O!==P&&ar(O)&&[\"role\",\"aria-label\",\"aria-modal\",\"id\"].forEach(j=>{O.removeAttribute(j)})},{immediate:!0}),he(()=>r.visible,$,{immediate:!0})}),St(()=>{x==null||x(),x=void 0}),t({popperContentRef:v,popperInstanceRef:h,updatePopper:k,contentStyle:A}),(F,Y)=>(L(),ee(\"div\",En({ref_key:\"contentRef\",ref:v},g(w),{style:g(A),class:g(y),tabindex:\"-1\",onMouseenter:P=>F.$emit(\"mouseenter\",P),onMouseleave:P=>F.$emit(\"mouseleave\",P)}),[re(g(Hc),{trapped:g(s),\"trap-on-focus-in\":!0,\"focus-trap-el\":g(v),\"focus-start-el\":g(o),onFocusAfterTrapped:g(a),onFocusAfterReleased:g(i),onFocusin:g(l),onFocusoutPrevented:g(u),onReleaseRequested:g(c)},{default:ce(()=>[de(F.$slots,\"default\")]),_:3},8,[\"trapped\",\"focus-trap-el\",\"focus-start-el\",\"onFocusAfterTrapped\",\"onFocusAfterReleased\",\"onFocusin\",\"onFocusoutPrevented\",\"onReleaseRequested\"])],16,[\"onMouseenter\",\"onMouseleave\"]))}});var AR=Ne(OR,[[\"__file\",\"content.vue\"]]);const RR=yt(G4),Uc=Symbol(\"elTooltip\"),Gt=xe({...pA,...Og,appendTo:{type:we([String,Object])},content:{type:String,default:\"\"},rawContent:Boolean,persistent:Boolean,visible:{type:we(Boolean),default:null},transition:String,teleported:{type:Boolean,default:!0},disabled:Boolean,...yr([\"ariaLabel\"])}),ei=xe({..._g,disabled:Boolean,trigger:{type:we([String,Array]),default:\"hover\"},triggerKeys:{type:we(Array),default:()=>[_n.enter,_n.space]}}),{useModelToggleProps:xR,useModelToggleEmits:IR,useModelToggle:PR}=oO(\"visible\"),NR=xe({...wg,...xR,...Gt,...ei,...Sg,showArrow:{type:Boolean,default:!0}}),LR=[...IR,\"before-show\",\"before-hide\",\"show\",\"hide\",\"open\",\"close\"],MR=(e,t)=>pe(e)?e.includes(t):e===t,Oo=(e,t,n)=>r=>{MR(g(e),t)&&n(r)},$R=Z({name:\"ElTooltipTrigger\"}),kR=Z({...$R,props:ei,setup(e,{expose:t}){const n=e,r=$e(\"tooltip\"),{controlled:o,id:s,open:i,onOpen:a,onClose:l,onToggle:u}=Ee(Uc,void 0),c=V(null),f=()=>{if(g(o)||n.disabled)return!0},d=Jt(n,\"trigger\"),v=Qn(f,Oo(d,\"hover\",a)),p=Qn(f,Oo(d,\"hover\",l)),h=Qn(f,Oo(d,\"click\",w=>{w.button===0&&u(w)})),b=Qn(f,Oo(d,\"focus\",a)),m=Qn(f,Oo(d,\"focus\",l)),S=Qn(f,Oo(d,\"contextmenu\",w=>{w.preventDefault(),u(w)})),_=Qn(f,w=>{const{code:y}=w;n.triggerKeys.includes(y)&&(w.preventDefault(),u(w))});return t({triggerRef:c}),(w,y)=>(L(),fe(g(nR),{id:g(s),\"virtual-ref\":w.virtualRef,open:g(i),\"virtual-triggering\":w.virtualTriggering,class:U(g(r).e(\"trigger\")),onBlur:g(m),onClick:g(h),onContextmenu:g(S),onFocus:g(b),onMouseenter:g(v),onMouseleave:g(p),onKeydown:g(_)},{default:ce(()=>[de(w.$slots,\"default\")]),_:3},8,[\"id\",\"virtual-ref\",\"open\",\"virtual-triggering\",\"class\",\"onBlur\",\"onClick\",\"onContextmenu\",\"onFocus\",\"onMouseenter\",\"onMouseleave\",\"onKeydown\"]))}});var FR=Ne(kR,[[\"__file\",\"trigger.vue\"]]);const BR=xe({to:{type:we([String,Object]),required:!0},disabled:Boolean}),DR=Z({__name:\"teleport\",props:BR,setup(e){return(t,n)=>t.disabled?de(t.$slots,\"default\",{key:0}):(L(),fe(jy,{key:1,to:t.to},[de(t.$slots,\"default\")],8,[\"to\"]))}});var VR=Ne(DR,[[\"__file\",\"teleport.vue\"]]);const Ag=yt(VR),jR=Z({name:\"ElTooltipContent\",inheritAttrs:!1}),zR=Z({...jR,props:Gt,setup(e,{expose:t}){const n=e,{selector:r}=ig(),o=$e(\"tooltip\"),s=V(null);let i;const{controlled:a,id:l,open:u,trigger:c,onClose:f,onOpen:d,onShow:v,onHide:p,onBeforeShow:h,onBeforeHide:b}=Ee(Uc,void 0),m=C(()=>n.transition||`${o.namespace.value}-fade-in-linear`),S=C(()=>n.persistent);St(()=>{i==null||i()});const _=C(()=>g(S)?!0:g(u)),w=C(()=>n.disabled?!1:g(u)),y=C(()=>n.appendTo||r.value),A=C(()=>{var O;return(O=n.style)!=null?O:{}}),R=V(!0),N=()=>{p(),R.value=!0},I=()=>{if(g(a))return!0},x=Qn(I,()=>{n.enterable&&g(c)===\"hover\"&&d()}),k=Qn(I,()=>{g(c)===\"hover\"&&f()}),$=()=>{var O,j;(j=(O=s.value)==null?void 0:O.updatePopper)==null||j.call(O),h==null||h()},F=()=>{b==null||b()},Y=()=>{v(),i=H1(C(()=>{var O;return(O=s.value)==null?void 0:O.popperContentRef}),()=>{if(g(a))return;g(c)!==\"hover\"&&f()})},P=()=>{n.virtualTriggering||f()};return he(()=>g(u),O=>{O?R.value=!1:i==null||i()},{flush:\"post\"}),he(()=>n.content,()=>{var O,j;(j=(O=s.value)==null?void 0:O.updatePopper)==null||j.call(O)}),t({contentRef:s}),(O,j)=>(L(),fe(g(Ag),{disabled:!O.teleported,to:g(y)},{default:ce(()=>[re(Fr,{name:g(m),onAfterLeave:N,onBeforeEnter:$,onAfterEnter:Y,onBeforeLeave:F},{default:ce(()=>[g(_)?ct((L(),fe(g(AR),En({key:0,id:g(l),ref_key:\"contentRef\",ref:s},O.$attrs,{\"aria-label\":O.ariaLabel,\"aria-hidden\":R.value,\"boundaries-padding\":O.boundariesPadding,\"fallback-placements\":O.fallbackPlacements,\"gpu-acceleration\":O.gpuAcceleration,offset:O.offset,placement:O.placement,\"popper-options\":O.popperOptions,strategy:O.strategy,effect:O.effect,enterable:O.enterable,pure:O.pure,\"popper-class\":O.popperClass,\"popper-style\":[O.popperStyle,g(A)],\"reference-el\":O.referenceEl,\"trigger-target-el\":O.triggerTargetEl,visible:g(w),\"z-index\":O.zIndex,onMouseenter:g(x),onMouseleave:g(k),onBlur:P,onClose:g(f)}),{default:ce(()=>[de(O.$slots,\"default\")]),_:3},16,[\"id\",\"aria-label\",\"aria-hidden\",\"boundaries-padding\",\"fallback-placements\",\"gpu-acceleration\",\"offset\",\"placement\",\"popper-options\",\"strategy\",\"effect\",\"enterable\",\"pure\",\"popper-class\",\"popper-style\",\"reference-el\",\"trigger-target-el\",\"visible\",\"z-index\",\"onMouseenter\",\"onMouseleave\",\"onClose\"])),[[en,g(w)]]):ae(\"v-if\",!0)]),_:3},8,[\"name\"])]),_:3},8,[\"disabled\",\"to\"]))}});var HR=Ne(zR,[[\"__file\",\"content.vue\"]]);const UR=Z({name:\"ElTooltip\"}),KR=Z({...UR,props:NR,emits:LR,setup(e,{expose:t,emit:n}){const r=e;dA();const o=pr(),s=V(),i=V(),a=()=>{var m;const S=g(s);S&&((m=S.popperInstanceRef)==null||m.update())},l=V(!1),u=V(),{show:c,hide:f,hasUpdateHandler:d}=PR({indicator:l,toggleReason:u}),{onOpen:v,onClose:p}=hA({showAfter:Jt(r,\"showAfter\"),hideAfter:Jt(r,\"hideAfter\"),autoClose:Jt(r,\"autoClose\"),open:c,close:f}),h=C(()=>Bt(r.visible)&&!d.value);ft(Uc,{controlled:h,id:o,open:Mr(l),trigger:Jt(r,\"trigger\"),onOpen:m=>{v(m)},onClose:m=>{p(m)},onToggle:m=>{g(l)?p(m):v(m)},onShow:()=>{n(\"show\",u.value)},onHide:()=>{n(\"hide\",u.value)},onBeforeShow:()=>{n(\"before-show\",u.value)},onBeforeHide:()=>{n(\"before-hide\",u.value)},updatePopper:a}),he(()=>r.disabled,m=>{m&&l.value&&(l.value=!1)});const b=m=>{var S,_;const w=(_=(S=i.value)==null?void 0:S.contentRef)==null?void 0:_.popperContentRef,y=(m==null?void 0:m.relatedTarget)||document.activeElement;return w&&w.contains(y)};return oc(()=>l.value&&f()),t({popperRef:s,contentRef:i,isFocusInsideContent:b,updatePopper:a,onOpen:v,onClose:p,hide:f}),(m,S)=>(L(),fe(g(RR),{ref_key:\"popperRef\",ref:s,role:m.role},{default:ce(()=>[re(FR,{disabled:m.disabled,trigger:m.trigger,\"trigger-keys\":m.triggerKeys,\"virtual-ref\":m.virtualRef,\"virtual-triggering\":m.virtualTriggering},{default:ce(()=>[m.$slots.default?de(m.$slots,\"default\",{key:0}):ae(\"v-if\",!0)]),_:3},8,[\"disabled\",\"trigger\",\"trigger-keys\",\"virtual-ref\",\"virtual-triggering\"]),re(HR,{ref_key:\"contentRef\",ref:i,\"aria-label\":m.ariaLabel,\"boundaries-padding\":m.boundariesPadding,content:m.content,disabled:m.disabled,effect:m.effect,enterable:m.enterable,\"fallback-placements\":m.fallbackPlacements,\"hide-after\":m.hideAfter,\"gpu-acceleration\":m.gpuAcceleration,offset:m.offset,persistent:m.persistent,\"popper-class\":m.popperClass,\"popper-style\":m.popperStyle,placement:m.placement,\"popper-options\":m.popperOptions,pure:m.pure,\"raw-content\":m.rawContent,\"reference-el\":m.referenceEl,\"trigger-target-el\":m.triggerTargetEl,\"show-after\":m.showAfter,strategy:m.strategy,teleported:m.teleported,transition:m.transition,\"virtual-triggering\":m.virtualTriggering,\"z-index\":m.zIndex,\"append-to\":m.appendTo},{default:ce(()=>[de(m.$slots,\"content\",{},()=>[m.rawContent?(L(),ee(\"span\",{key:0,innerHTML:m.content},null,8,[\"innerHTML\"])):(L(),ee(\"span\",{key:1},He(m.content),1))]),m.showArrow?(L(),fe(g(X4),{key:0,\"arrow-offset\":m.arrowOffset},null,8,[\"arrow-offset\"])):ae(\"v-if\",!0)]),_:3},8,[\"aria-label\",\"boundaries-padding\",\"content\",\"disabled\",\"effect\",\"enterable\",\"fallback-placements\",\"hide-after\",\"gpu-acceleration\",\"offset\",\"persistent\",\"popper-class\",\"popper-style\",\"placement\",\"popper-options\",\"pure\",\"raw-content\",\"reference-el\",\"trigger-target-el\",\"show-after\",\"strategy\",\"teleported\",\"transition\",\"virtual-triggering\",\"z-index\",\"append-to\"])]),_:3},8,[\"role\"]))}});var qR=Ne(KR,[[\"__file\",\"tooltip.vue\"]]);const Rg=yt(qR),WR=xe({value:{type:[String,Number],default:\"\"},max:{type:Number,default:99},isDot:Boolean,hidden:Boolean,type:{type:String,values:[\"primary\",\"success\",\"warning\",\"info\",\"danger\"],default:\"danger\"},showZero:{type:Boolean,default:!0},color:String,badgeStyle:{type:we([String,Object,Array])},offset:{type:we(Array),default:[0,0]},badgeClass:{type:String}}),GR=Z({name:\"ElBadge\"}),YR=Z({...GR,props:WR,setup(e,{expose:t}){const n=e,r=$e(\"badge\"),o=C(()=>n.isDot?\"\":je(n.value)&&je(n.max)?n.max<n.value?`${n.max}+`:`${n.value}`:`${n.value}`),s=C(()=>{var i,a,l,u,c;return[{backgroundColor:n.color,marginRight:pn(-((a=(i=n.offset)==null?void 0:i[0])!=null?a:0)),marginTop:pn((u=(l=n.offset)==null?void 0:l[1])!=null?u:0)},(c=n.badgeStyle)!=null?c:{}]});return t({content:o}),(i,a)=>(L(),ee(\"div\",{class:U(g(r).b())},[de(i.$slots,\"default\"),re(Fr,{name:`${g(r).namespace.value}-zoom-in-center`,persisted:\"\"},{default:ce(()=>[ct(le(\"sup\",{class:U([g(r).e(\"content\"),g(r).em(\"content\",i.type),g(r).is(\"fixed\",!!i.$slots.default),g(r).is(\"dot\",i.isDot),g(r).is(\"hide-zero\",!i.showZero&&n.value===0),i.badgeClass]),style:ot(g(s)),textContent:He(g(o))},null,14,[\"textContent\"]),[[en,!i.hidden&&(g(o)||i.isDot)]])]),_:1},8,[\"name\"])],2))}});var JR=Ne(YR,[[\"__file\",\"badge.vue\"]]);const XR=yt(JR),xg=Symbol(\"buttonGroupContextKey\"),ZR=(e,t)=>{xs({from:\"type.text\",replacement:\"link\",version:\"3.0.0\",scope:\"props\",ref:\"https://element-plus.org/en-US/component/button.html#button-attributes\"},C(()=>e.type===\"text\"));const n=Ee(xg,void 0),r=ll(\"button\"),{form:o}=Vr(),s=In(C(()=>n==null?void 0:n.size)),i=ns(),a=V(),l=go(),u=C(()=>e.type||(n==null?void 0:n.type)||\"\"),c=C(()=>{var p,h,b;return(b=(h=e.autoInsertSpace)!=null?h:(p=r.value)==null?void 0:p.autoInsertSpace)!=null?b:!1}),f=C(()=>e.tag===\"button\"?{ariaDisabled:i.value||e.loading,disabled:i.value||e.loading,autofocus:e.autofocus,type:e.nativeType}:{}),d=C(()=>{var p;const h=(p=l.default)==null?void 0:p.call(l);if(c.value&&(h==null?void 0:h.length)===1){const b=h[0];if((b==null?void 0:b.type)===Zo){const m=b.children;return new RegExp(\"^\\\\p{Unified_Ideograph}{2}$\",\"u\").test(m.trim())}}return!1});return{_disabled:i,_size:s,_type:u,_ref:a,_props:f,shouldAddSpace:d,handleClick:p=>{if(i.value||e.loading){p.stopPropagation();return}e.nativeType===\"reset\"&&(o==null||o.resetFields()),t(\"click\",p)}}},QR=[\"default\",\"primary\",\"success\",\"warning\",\"info\",\"danger\",\"text\",\"\"],ex=[\"button\",\"submit\",\"reset\"],Lu=xe({size:So,disabled:Boolean,type:{type:String,values:QR,default:\"\"},icon:{type:jt},nativeType:{type:String,values:ex,default:\"button\"},loading:Boolean,loadingIcon:{type:jt,default:()=>Js},plain:Boolean,text:Boolean,link:Boolean,bg:Boolean,autofocus:Boolean,round:Boolean,circle:Boolean,color:String,dark:Boolean,autoInsertSpace:{type:Boolean,default:void 0},tag:{type:we([String,Object]),default:\"button\"}}),tx={click:e=>e instanceof MouseEvent};function Tt(e,t){nx(e)&&(e=\"100%\");var n=rx(e);return e=t===360?e:Math.min(t,Math.max(0,parseFloat(e))),n&&(e=parseInt(String(e*t),10)/100),Math.abs(e-t)<1e-6?1:(t===360?e=(e<0?e%t+t:e%t)/parseFloat(String(t)):e=e%t/parseFloat(String(t)),e)}function ki(e){return Math.min(1,Math.max(0,e))}function nx(e){return typeof e==\"string\"&&e.indexOf(\".\")!==-1&&parseFloat(e)===1}function rx(e){return typeof e==\"string\"&&e.indexOf(\"%\")!==-1}function Ig(e){return e=parseFloat(e),(isNaN(e)||e<0||e>1)&&(e=1),e}function Fi(e){return e<=1?\"\".concat(Number(e)*100,\"%\"):e}function to(e){return e.length===1?\"0\"+e:String(e)}function ox(e,t,n){return{r:Tt(e,255)*255,g:Tt(t,255)*255,b:Tt(n,255)*255}}function Pp(e,t,n){e=Tt(e,255),t=Tt(t,255),n=Tt(n,255);var r=Math.max(e,t,n),o=Math.min(e,t,n),s=0,i=0,a=(r+o)/2;if(r===o)i=0,s=0;else{var l=r-o;switch(i=a>.5?l/(2-r-o):l/(r+o),r){case e:s=(t-n)/l+(t<n?6:0);break;case t:s=(n-e)/l+2;break;case n:s=(e-t)/l+4;break}s/=6}return{h:s,s:i,l:a}}function jl(e,t,n){return n<0&&(n+=1),n>1&&(n-=1),n<1/6?e+(t-e)*(6*n):n<1/2?t:n<2/3?e+(t-e)*(2/3-n)*6:e}function sx(e,t,n){var r,o,s;if(e=Tt(e,360),t=Tt(t,100),n=Tt(n,100),t===0)o=n,s=n,r=n;else{var i=n<.5?n*(1+t):n+t-n*t,a=2*n-i;r=jl(a,i,e+1/3),o=jl(a,i,e),s=jl(a,i,e-1/3)}return{r:r*255,g:o*255,b:s*255}}function Np(e,t,n){e=Tt(e,255),t=Tt(t,255),n=Tt(n,255);var r=Math.max(e,t,n),o=Math.min(e,t,n),s=0,i=r,a=r-o,l=r===0?0:a/r;if(r===o)s=0;else{switch(r){case e:s=(t-n)/a+(t<n?6:0);break;case t:s=(n-e)/a+2;break;case n:s=(e-t)/a+4;break}s/=6}return{h:s,s:l,v:i}}function ix(e,t,n){e=Tt(e,360)*6,t=Tt(t,100),n=Tt(n,100);var r=Math.floor(e),o=e-r,s=n*(1-t),i=n*(1-o*t),a=n*(1-(1-o)*t),l=r%6,u=[n,i,s,s,a,n][l],c=[a,n,n,i,s,s][l],f=[s,s,a,n,n,i][l];return{r:u*255,g:c*255,b:f*255}}function Lp(e,t,n,r){var o=[to(Math.round(e).toString(16)),to(Math.round(t).toString(16)),to(Math.round(n).toString(16))];return r&&o[0].startsWith(o[0].charAt(1))&&o[1].startsWith(o[1].charAt(1))&&o[2].startsWith(o[2].charAt(1))?o[0].charAt(0)+o[1].charAt(0)+o[2].charAt(0):o.join(\"\")}function ax(e,t,n,r,o){var s=[to(Math.round(e).toString(16)),to(Math.round(t).toString(16)),to(Math.round(n).toString(16)),to(lx(r))];return o&&s[0].startsWith(s[0].charAt(1))&&s[1].startsWith(s[1].charAt(1))&&s[2].startsWith(s[2].charAt(1))&&s[3].startsWith(s[3].charAt(1))?s[0].charAt(0)+s[1].charAt(0)+s[2].charAt(0)+s[3].charAt(0):s.join(\"\")}function lx(e){return Math.round(parseFloat(e)*255).toString(16)}function Mp(e){return Wt(e)/255}function Wt(e){return parseInt(e,16)}function ux(e){return{r:e>>16,g:(e&65280)>>8,b:e&255}}var Mu={aliceblue:\"#f0f8ff\",antiquewhite:\"#faebd7\",aqua:\"#00ffff\",aquamarine:\"#7fffd4\",azure:\"#f0ffff\",beige:\"#f5f5dc\",bisque:\"#ffe4c4\",black:\"#000000\",blanchedalmond:\"#ffebcd\",blue:\"#0000ff\",blueviolet:\"#8a2be2\",brown:\"#a52a2a\",burlywood:\"#deb887\",cadetblue:\"#5f9ea0\",chartreuse:\"#7fff00\",chocolate:\"#d2691e\",coral:\"#ff7f50\",cornflowerblue:\"#6495ed\",cornsilk:\"#fff8dc\",crimson:\"#dc143c\",cyan:\"#00ffff\",darkblue:\"#00008b\",darkcyan:\"#008b8b\",darkgoldenrod:\"#b8860b\",darkgray:\"#a9a9a9\",darkgreen:\"#006400\",darkgrey:\"#a9a9a9\",darkkhaki:\"#bdb76b\",darkmagenta:\"#8b008b\",darkolivegreen:\"#556b2f\",darkorange:\"#ff8c00\",darkorchid:\"#9932cc\",darkred:\"#8b0000\",darksalmon:\"#e9967a\",darkseagreen:\"#8fbc8f\",darkslateblue:\"#483d8b\",darkslategray:\"#2f4f4f\",darkslategrey:\"#2f4f4f\",darkturquoise:\"#00ced1\",darkviolet:\"#9400d3\",deeppink:\"#ff1493\",deepskyblue:\"#00bfff\",dimgray:\"#696969\",dimgrey:\"#696969\",dodgerblue:\"#1e90ff\",firebrick:\"#b22222\",floralwhite:\"#fffaf0\",forestgreen:\"#228b22\",fuchsia:\"#ff00ff\",gainsboro:\"#dcdcdc\",ghostwhite:\"#f8f8ff\",goldenrod:\"#daa520\",gold:\"#ffd700\",gray:\"#808080\",green:\"#008000\",greenyellow:\"#adff2f\",grey:\"#808080\",honeydew:\"#f0fff0\",hotpink:\"#ff69b4\",indianred:\"#cd5c5c\",indigo:\"#4b0082\",ivory:\"#fffff0\",khaki:\"#f0e68c\",lavenderblush:\"#fff0f5\",lavender:\"#e6e6fa\",lawngreen:\"#7cfc00\",lemonchiffon:\"#fffacd\",lightblue:\"#add8e6\",lightcoral:\"#f08080\",lightcyan:\"#e0ffff\",lightgoldenrodyellow:\"#fafad2\",lightgray:\"#d3d3d3\",lightgreen:\"#90ee90\",lightgrey:\"#d3d3d3\",lightpink:\"#ffb6c1\",lightsalmon:\"#ffa07a\",lightseagreen:\"#20b2aa\",lightskyblue:\"#87cefa\",lightslategray:\"#778899\",lightslategrey:\"#778899\",lightsteelblue:\"#b0c4de\",lightyellow:\"#ffffe0\",lime:\"#00ff00\",limegreen:\"#32cd32\",linen:\"#faf0e6\",magenta:\"#ff00ff\",maroon:\"#800000\",mediumaquamarine:\"#66cdaa\",mediumblue:\"#0000cd\",mediumorchid:\"#ba55d3\",mediumpurple:\"#9370db\",mediumseagreen:\"#3cb371\",mediumslateblue:\"#7b68ee\",mediumspringgreen:\"#00fa9a\",mediumturquoise:\"#48d1cc\",mediumvioletred:\"#c71585\",midnightblue:\"#191970\",mintcream:\"#f5fffa\",mistyrose:\"#ffe4e1\",moccasin:\"#ffe4b5\",navajowhite:\"#ffdead\",navy:\"#000080\",oldlace:\"#fdf5e6\",olive:\"#808000\",olivedrab:\"#6b8e23\",orange:\"#ffa500\",orangered:\"#ff4500\",orchid:\"#da70d6\",palegoldenrod:\"#eee8aa\",palegreen:\"#98fb98\",paleturquoise:\"#afeeee\",palevioletred:\"#db7093\",papayawhip:\"#ffefd5\",peachpuff:\"#ffdab9\",peru:\"#cd853f\",pink:\"#ffc0cb\",plum:\"#dda0dd\",powderblue:\"#b0e0e6\",purple:\"#800080\",rebeccapurple:\"#663399\",red:\"#ff0000\",rosybrown:\"#bc8f8f\",royalblue:\"#4169e1\",saddlebrown:\"#8b4513\",salmon:\"#fa8072\",sandybrown:\"#f4a460\",seagreen:\"#2e8b57\",seashell:\"#fff5ee\",sienna:\"#a0522d\",silver:\"#c0c0c0\",skyblue:\"#87ceeb\",slateblue:\"#6a5acd\",slategray:\"#708090\",slategrey:\"#708090\",snow:\"#fffafa\",springgreen:\"#00ff7f\",steelblue:\"#4682b4\",tan:\"#d2b48c\",teal:\"#008080\",thistle:\"#d8bfd8\",tomato:\"#ff6347\",turquoise:\"#40e0d0\",violet:\"#ee82ee\",wheat:\"#f5deb3\",white:\"#ffffff\",whitesmoke:\"#f5f5f5\",yellow:\"#ffff00\",yellowgreen:\"#9acd32\"};function cx(e){var t={r:0,g:0,b:0},n=1,r=null,o=null,s=null,i=!1,a=!1;return typeof e==\"string\"&&(e=px(e)),typeof e==\"object\"&&(Xn(e.r)&&Xn(e.g)&&Xn(e.b)?(t=ox(e.r,e.g,e.b),i=!0,a=String(e.r).substr(-1)===\"%\"?\"prgb\":\"rgb\"):Xn(e.h)&&Xn(e.s)&&Xn(e.v)?(r=Fi(e.s),o=Fi(e.v),t=ix(e.h,r,o),i=!0,a=\"hsv\"):Xn(e.h)&&Xn(e.s)&&Xn(e.l)&&(r=Fi(e.s),s=Fi(e.l),t=sx(e.h,r,s),i=!0,a=\"hsl\"),Object.prototype.hasOwnProperty.call(e,\"a\")&&(n=e.a)),n=Ig(n),{ok:i,format:e.format||a,r:Math.min(255,Math.max(t.r,0)),g:Math.min(255,Math.max(t.g,0)),b:Math.min(255,Math.max(t.b,0)),a:n}}var fx=\"[-\\\\+]?\\\\d+%?\",dx=\"[-\\\\+]?\\\\d*\\\\.\\\\d+%?\",Nr=\"(?:\".concat(dx,\")|(?:\").concat(fx,\")\"),zl=\"[\\\\s|\\\\(]+(\".concat(Nr,\")[,|\\\\s]+(\").concat(Nr,\")[,|\\\\s]+(\").concat(Nr,\")\\\\s*\\\\)?\"),Hl=\"[\\\\s|\\\\(]+(\".concat(Nr,\")[,|\\\\s]+(\").concat(Nr,\")[,|\\\\s]+(\").concat(Nr,\")[,|\\\\s]+(\").concat(Nr,\")\\\\s*\\\\)?\"),gn={CSS_UNIT:new RegExp(Nr),rgb:new RegExp(\"rgb\"+zl),rgba:new RegExp(\"rgba\"+Hl),hsl:new RegExp(\"hsl\"+zl),hsla:new RegExp(\"hsla\"+Hl),hsv:new RegExp(\"hsv\"+zl),hsva:new RegExp(\"hsva\"+Hl),hex3:/^#?([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})$/,hex6:/^#?([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/,hex4:/^#?([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})([0-9a-fA-F]{1})$/,hex8:/^#?([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})$/};function px(e){if(e=e.trim().toLowerCase(),e.length===0)return!1;var t=!1;if(Mu[e])e=Mu[e],t=!0;else if(e===\"transparent\")return{r:0,g:0,b:0,a:0,format:\"name\"};var n=gn.rgb.exec(e);return n?{r:n[1],g:n[2],b:n[3]}:(n=gn.rgba.exec(e),n?{r:n[1],g:n[2],b:n[3],a:n[4]}:(n=gn.hsl.exec(e),n?{h:n[1],s:n[2],l:n[3]}:(n=gn.hsla.exec(e),n?{h:n[1],s:n[2],l:n[3],a:n[4]}:(n=gn.hsv.exec(e),n?{h:n[1],s:n[2],v:n[3]}:(n=gn.hsva.exec(e),n?{h:n[1],s:n[2],v:n[3],a:n[4]}:(n=gn.hex8.exec(e),n?{r:Wt(n[1]),g:Wt(n[2]),b:Wt(n[3]),a:Mp(n[4]),format:t?\"name\":\"hex8\"}:(n=gn.hex6.exec(e),n?{r:Wt(n[1]),g:Wt(n[2]),b:Wt(n[3]),format:t?\"name\":\"hex\"}:(n=gn.hex4.exec(e),n?{r:Wt(n[1]+n[1]),g:Wt(n[2]+n[2]),b:Wt(n[3]+n[3]),a:Mp(n[4]+n[4]),format:t?\"name\":\"hex8\"}:(n=gn.hex3.exec(e),n?{r:Wt(n[1]+n[1]),g:Wt(n[2]+n[2]),b:Wt(n[3]+n[3]),format:t?\"name\":\"hex\"}:!1)))))))))}function Xn(e){return!!gn.CSS_UNIT.exec(String(e))}var hx=function(){function e(t,n){t===void 0&&(t=\"\"),n===void 0&&(n={});var r;if(t instanceof e)return t;typeof t==\"number\"&&(t=ux(t)),this.originalInput=t;var o=cx(t);this.originalInput=t,this.r=o.r,this.g=o.g,this.b=o.b,this.a=o.a,this.roundA=Math.round(100*this.a)/100,this.format=(r=n.format)!==null&&r!==void 0?r:o.format,this.gradientType=n.gradientType,this.r<1&&(this.r=Math.round(this.r)),this.g<1&&(this.g=Math.round(this.g)),this.b<1&&(this.b=Math.round(this.b)),this.isValid=o.ok}return e.prototype.isDark=function(){return this.getBrightness()<128},e.prototype.isLight=function(){return!this.isDark()},e.prototype.getBrightness=function(){var t=this.toRgb();return(t.r*299+t.g*587+t.b*114)/1e3},e.prototype.getLuminance=function(){var t=this.toRgb(),n,r,o,s=t.r/255,i=t.g/255,a=t.b/255;return s<=.03928?n=s/12.92:n=Math.pow((s+.055)/1.055,2.4),i<=.03928?r=i/12.92:r=Math.pow((i+.055)/1.055,2.4),a<=.03928?o=a/12.92:o=Math.pow((a+.055)/1.055,2.4),.2126*n+.7152*r+.0722*o},e.prototype.getAlpha=function(){return this.a},e.prototype.setAlpha=function(t){return this.a=Ig(t),this.roundA=Math.round(100*this.a)/100,this},e.prototype.isMonochrome=function(){var t=this.toHsl().s;return t===0},e.prototype.toHsv=function(){var t=Np(this.r,this.g,this.b);return{h:t.h*360,s:t.s,v:t.v,a:this.a}},e.prototype.toHsvString=function(){var t=Np(this.r,this.g,this.b),n=Math.round(t.h*360),r=Math.round(t.s*100),o=Math.round(t.v*100);return this.a===1?\"hsv(\".concat(n,\", \").concat(r,\"%, \").concat(o,\"%)\"):\"hsva(\".concat(n,\", \").concat(r,\"%, \").concat(o,\"%, \").concat(this.roundA,\")\")},e.prototype.toHsl=function(){var t=Pp(this.r,this.g,this.b);return{h:t.h*360,s:t.s,l:t.l,a:this.a}},e.prototype.toHslString=function(){var t=Pp(this.r,this.g,this.b),n=Math.round(t.h*360),r=Math.round(t.s*100),o=Math.round(t.l*100);return this.a===1?\"hsl(\".concat(n,\", \").concat(r,\"%, \").concat(o,\"%)\"):\"hsla(\".concat(n,\", \").concat(r,\"%, \").concat(o,\"%, \").concat(this.roundA,\")\")},e.prototype.toHex=function(t){return t===void 0&&(t=!1),Lp(this.r,this.g,this.b,t)},e.prototype.toHexString=function(t){return t===void 0&&(t=!1),\"#\"+this.toHex(t)},e.prototype.toHex8=function(t){return t===void 0&&(t=!1),ax(this.r,this.g,this.b,this.a,t)},e.prototype.toHex8String=function(t){return t===void 0&&(t=!1),\"#\"+this.toHex8(t)},e.prototype.toHexShortString=function(t){return t===void 0&&(t=!1),this.a===1?this.toHexString(t):this.toHex8String(t)},e.prototype.toRgb=function(){return{r:Math.round(this.r),g:Math.round(this.g),b:Math.round(this.b),a:this.a}},e.prototype.toRgbString=function(){var t=Math.round(this.r),n=Math.round(this.g),r=Math.round(this.b);return this.a===1?\"rgb(\".concat(t,\", \").concat(n,\", \").concat(r,\")\"):\"rgba(\".concat(t,\", \").concat(n,\", \").concat(r,\", \").concat(this.roundA,\")\")},e.prototype.toPercentageRgb=function(){var t=function(n){return\"\".concat(Math.round(Tt(n,255)*100),\"%\")};return{r:t(this.r),g:t(this.g),b:t(this.b),a:this.a}},e.prototype.toPercentageRgbString=function(){var t=function(n){return Math.round(Tt(n,255)*100)};return this.a===1?\"rgb(\".concat(t(this.r),\"%, \").concat(t(this.g),\"%, \").concat(t(this.b),\"%)\"):\"rgba(\".concat(t(this.r),\"%, \").concat(t(this.g),\"%, \").concat(t(this.b),\"%, \").concat(this.roundA,\")\")},e.prototype.toName=function(){if(this.a===0)return\"transparent\";if(this.a<1)return!1;for(var t=\"#\"+Lp(this.r,this.g,this.b,!1),n=0,r=Object.entries(Mu);n<r.length;n++){var o=r[n],s=o[0],i=o[1];if(t===i)return s}return!1},e.prototype.toString=function(t){var n=!!t;t=t??this.format;var r=!1,o=this.a<1&&this.a>=0,s=!n&&o&&(t.startsWith(\"hex\")||t===\"name\");return s?t===\"name\"&&this.a===0?this.toName():this.toRgbString():(t===\"rgb\"&&(r=this.toRgbString()),t===\"prgb\"&&(r=this.toPercentageRgbString()),(t===\"hex\"||t===\"hex6\")&&(r=this.toHexString()),t===\"hex3\"&&(r=this.toHexString(!0)),t===\"hex4\"&&(r=this.toHex8String(!0)),t===\"hex8\"&&(r=this.toHex8String()),t===\"name\"&&(r=this.toName()),t===\"hsl\"&&(r=this.toHslString()),t===\"hsv\"&&(r=this.toHsvString()),r||this.toHexString())},e.prototype.toNumber=function(){return(Math.round(this.r)<<16)+(Math.round(this.g)<<8)+Math.round(this.b)},e.prototype.clone=function(){return new e(this.toString())},e.prototype.lighten=function(t){t===void 0&&(t=10);var n=this.toHsl();return n.l+=t/100,n.l=ki(n.l),new e(n)},e.prototype.brighten=function(t){t===void 0&&(t=10);var n=this.toRgb();return n.r=Math.max(0,Math.min(255,n.r-Math.round(255*-(t/100)))),n.g=Math.max(0,Math.min(255,n.g-Math.round(255*-(t/100)))),n.b=Math.max(0,Math.min(255,n.b-Math.round(255*-(t/100)))),new e(n)},e.prototype.darken=function(t){t===void 0&&(t=10);var n=this.toHsl();return n.l-=t/100,n.l=ki(n.l),new e(n)},e.prototype.tint=function(t){return t===void 0&&(t=10),this.mix(\"white\",t)},e.prototype.shade=function(t){return t===void 0&&(t=10),this.mix(\"black\",t)},e.prototype.desaturate=function(t){t===void 0&&(t=10);var n=this.toHsl();return n.s-=t/100,n.s=ki(n.s),new e(n)},e.prototype.saturate=function(t){t===void 0&&(t=10);var n=this.toHsl();return n.s+=t/100,n.s=ki(n.s),new e(n)},e.prototype.greyscale=function(){return this.desaturate(100)},e.prototype.spin=function(t){var n=this.toHsl(),r=(n.h+t)%360;return n.h=r<0?360+r:r,new e(n)},e.prototype.mix=function(t,n){n===void 0&&(n=50);var r=this.toRgb(),o=new e(t).toRgb(),s=n/100,i={r:(o.r-r.r)*s+r.r,g:(o.g-r.g)*s+r.g,b:(o.b-r.b)*s+r.b,a:(o.a-r.a)*s+r.a};return new e(i)},e.prototype.analogous=function(t,n){t===void 0&&(t=6),n===void 0&&(n=30);var r=this.toHsl(),o=360/n,s=[this];for(r.h=(r.h-(o*t>>1)+720)%360;--t;)r.h=(r.h+o)%360,s.push(new e(r));return s},e.prototype.complement=function(){var t=this.toHsl();return t.h=(t.h+180)%360,new e(t)},e.prototype.monochromatic=function(t){t===void 0&&(t=6);for(var n=this.toHsv(),r=n.h,o=n.s,s=n.v,i=[],a=1/t;t--;)i.push(new e({h:r,s:o,v:s})),s=(s+a)%1;return i},e.prototype.splitcomplement=function(){var t=this.toHsl(),n=t.h;return[this,new e({h:(n+72)%360,s:t.s,l:t.l}),new e({h:(n+216)%360,s:t.s,l:t.l})]},e.prototype.onBackground=function(t){var n=this.toRgb(),r=new e(t).toRgb(),o=n.a+r.a*(1-n.a);return new e({r:(n.r*n.a+r.r*r.a*(1-n.a))/o,g:(n.g*n.a+r.g*r.a*(1-n.a))/o,b:(n.b*n.a+r.b*r.a*(1-n.a))/o,a:o})},e.prototype.triad=function(){return this.polyad(3)},e.prototype.tetrad=function(){return this.polyad(4)},e.prototype.polyad=function(t){for(var n=this.toHsl(),r=n.h,o=[this],s=360/t,i=1;i<t;i++)o.push(new e({h:(r+i*s)%360,s:n.s,l:n.l}));return o},e.prototype.equals=function(t){return this.toRgbString()===new e(t).toRgbString()},e}();function _r(e,t=20){return e.mix(\"#141414\",t).toString()}function vx(e){const t=ns(),n=$e(\"button\");return C(()=>{let r={},o=e.color;if(o){const s=o.match(/var\\((.*?)\\)/);s&&(o=window.getComputedStyle(window.document.documentElement).getPropertyValue(s[1]));const i=new hx(o),a=e.dark?i.tint(20).toString():_r(i,20);if(e.plain)r=n.cssVarBlock({\"bg-color\":e.dark?_r(i,90):i.tint(90).toString(),\"text-color\":o,\"border-color\":e.dark?_r(i,50):i.tint(50).toString(),\"hover-text-color\":`var(${n.cssVarName(\"color-white\")})`,\"hover-bg-color\":o,\"hover-border-color\":o,\"active-bg-color\":a,\"active-text-color\":`var(${n.cssVarName(\"color-white\")})`,\"active-border-color\":a}),t.value&&(r[n.cssVarBlockName(\"disabled-bg-color\")]=e.dark?_r(i,90):i.tint(90).toString(),r[n.cssVarBlockName(\"disabled-text-color\")]=e.dark?_r(i,50):i.tint(50).toString(),r[n.cssVarBlockName(\"disabled-border-color\")]=e.dark?_r(i,80):i.tint(80).toString());else{const l=e.dark?_r(i,30):i.tint(30).toString(),u=i.isDark()?`var(${n.cssVarName(\"color-white\")})`:`var(${n.cssVarName(\"color-black\")})`;if(r=n.cssVarBlock({\"bg-color\":o,\"text-color\":u,\"border-color\":o,\"hover-bg-color\":l,\"hover-text-color\":u,\"hover-border-color\":l,\"active-bg-color\":a,\"active-border-color\":a}),t.value){const c=e.dark?_r(i,50):i.tint(50).toString();r[n.cssVarBlockName(\"disabled-bg-color\")]=c,r[n.cssVarBlockName(\"disabled-text-color\")]=e.dark?\"rgba(255, 255, 255, 0.5)\":`var(${n.cssVarName(\"color-white\")})`,r[n.cssVarBlockName(\"disabled-border-color\")]=c}}}return r})}const mx=Z({name:\"ElButton\"}),gx=Z({...mx,props:Lu,emits:tx,setup(e,{expose:t,emit:n}){const r=e,o=vx(r),s=$e(\"button\"),{_ref:i,_size:a,_type:l,_disabled:u,_props:c,shouldAddSpace:f,handleClick:d}=ZR(r,n),v=C(()=>[s.b(),s.m(l.value),s.m(a.value),s.is(\"disabled\",u.value),s.is(\"loading\",r.loading),s.is(\"plain\",r.plain),s.is(\"round\",r.round),s.is(\"circle\",r.circle),s.is(\"text\",r.text),s.is(\"link\",r.link),s.is(\"has-bg\",r.bg)]);return t({ref:i,size:a,type:l,disabled:u,shouldAddSpace:f}),(p,h)=>(L(),fe(Xe(p.tag),En({ref_key:\"_ref\",ref:i},g(c),{class:g(v),style:g(o),onClick:g(d)}),{default:ce(()=>[p.loading?(L(),ee(nt,{key:0},[p.$slots.loading?de(p.$slots,\"loading\",{key:0}):(L(),fe(g(Je),{key:1,class:U(g(s).is(\"loading\"))},{default:ce(()=>[(L(),fe(Xe(p.loadingIcon)))]),_:1},8,[\"class\"]))],64)):p.icon||p.$slots.icon?(L(),fe(g(Je),{key:1},{default:ce(()=>[p.icon?(L(),fe(Xe(p.icon),{key:0})):de(p.$slots,\"icon\",{key:1})]),_:3})):ae(\"v-if\",!0),p.$slots.default?(L(),ee(\"span\",{key:2,class:U({[g(s).em(\"text\",\"expand\")]:g(f)})},[de(p.$slots,\"default\")],2)):ae(\"v-if\",!0)]),_:3},16,[\"class\",\"style\",\"onClick\"]))}});var bx=Ne(gx,[[\"__file\",\"button.vue\"]]);const yx={size:Lu.size,type:Lu.type},wx=Z({name:\"ElButtonGroup\"}),Sx=Z({...wx,props:yx,setup(e){const t=e;ft(xg,wt({size:Jt(t,\"size\"),type:Jt(t,\"type\")}));const n=$e(\"button\");return(r,o)=>(L(),ee(\"div\",{class:U(g(n).b(\"group\"))},[de(r.$slots,\"default\")],2))}});var Pg=Ne(Sx,[[\"__file\",\"button-group.vue\"]]);const Ex=yt(bx,{ButtonGroup:Pg});wo(Pg);const Or=new Map;if(st){let e;document.addEventListener(\"mousedown\",t=>e=t),document.addEventListener(\"mouseup\",t=>{if(e){for(const n of Or.values())for(const{documentHandler:r}of n)r(t,e);e=void 0}})}function $p(e,t){let n=[];return Array.isArray(t.arg)?n=t.arg:ar(t.arg)&&n.push(t.arg),function(r,o){const s=t.instance.popperRef,i=r.target,a=o==null?void 0:o.target,l=!t||!t.instance,u=!i||!a,c=e.contains(i)||e.contains(a),f=e===i,d=n.length&&n.some(p=>p==null?void 0:p.contains(i))||n.length&&n.includes(a),v=s&&(s.contains(i)||s.contains(a));l||u||c||f||d||v||t.value(r,o)}}const _x={beforeMount(e,t){Or.has(e)||Or.set(e,[]),Or.get(e).push({documentHandler:$p(e,t),bindingFn:t.value})},updated(e,t){Or.has(e)||Or.set(e,[]);const n=Or.get(e),r=n.findIndex(s=>s.bindingFn===t.oldValue),o={documentHandler:$p(e,t),bindingFn:t.value};r>=0?n.splice(r,1,o):n.push(o)},unmounted(e){Or.delete(e)}},Cx=100,Tx=600,kp={beforeMount(e,t){const n=t.value,{interval:r=Cx,delay:o=Tx}=ve(n)?{}:n;let s,i;const a=()=>ve(n)?n():n.handler(),l=()=>{i&&(clearTimeout(i),i=void 0),s&&(clearInterval(s),s=void 0)};e.addEventListener(\"mousedown\",u=>{u.button===0&&(l(),a(),document.addEventListener(\"mouseup\",()=>l(),{once:!0}),i=setTimeout(()=>{s=setInterval(()=>{a()},r)},o))})}},$u=\"_trap-focus-children\",no=[],Fp=e=>{if(no.length===0)return;const t=no[no.length-1][$u];if(t.length>0&&e.code===_n.tab){if(t.length===1){e.preventDefault(),document.activeElement!==t[0]&&t[0].focus();return}const n=e.shiftKey,r=e.target===t[0],o=e.target===t[t.length-1];r&&n&&(e.preventDefault(),t[t.length-1].focus()),o&&!n&&(e.preventDefault(),t[0].focus())}},Ox={beforeMount(e){e[$u]=ld(e),no.push(e),no.length<=1&&document.addEventListener(\"keydown\",Fp)},updated(e){Ie(()=>{e[$u]=ld(e)})},unmounted(){no.shift(),no.length===0&&document.removeEventListener(\"keydown\",Fp)}},Ng={modelValue:{type:[Number,String,Boolean],default:void 0},label:{type:[String,Boolean,Number,Object],default:void 0},value:{type:[String,Boolean,Number,Object],default:void 0},indeterminate:Boolean,disabled:Boolean,checked:Boolean,name:{type:String,default:void 0},trueValue:{type:[String,Number],default:void 0},falseValue:{type:[String,Number],default:void 0},trueLabel:{type:[String,Number],default:void 0},falseLabel:{type:[String,Number],default:void 0},id:{type:String,default:void 0},border:Boolean,size:So,tabindex:[String,Number],validateEvent:{type:Boolean,default:!0},...yr([\"ariaControls\"])},Lg={[rt]:e=>Ae(e)||je(e)||Bt(e),change:e=>Ae(e)||je(e)||Bt(e)},rs=Symbol(\"checkboxGroupContextKey\"),Ax=({model:e,isChecked:t})=>{const n=Ee(rs,void 0),r=C(()=>{var s,i;const a=(s=n==null?void 0:n.max)==null?void 0:s.value,l=(i=n==null?void 0:n.min)==null?void 0:i.value;return!Lt(a)&&e.value.length>=a&&!t.value||!Lt(l)&&e.value.length<=l&&t.value});return{isDisabled:ns(C(()=>(n==null?void 0:n.disabled.value)||r.value)),isLimitDisabled:r}},Rx=(e,{model:t,isLimitExceeded:n,hasOwnLabel:r,isDisabled:o,isLabeledByFormItem:s})=>{const i=Ee(rs,void 0),{formItem:a}=Vr(),{emit:l}=Ge();function u(p){var h,b,m,S;return[!0,e.trueValue,e.trueLabel].includes(p)?(b=(h=e.trueValue)!=null?h:e.trueLabel)!=null?b:!0:(S=(m=e.falseValue)!=null?m:e.falseLabel)!=null?S:!1}function c(p,h){l(\"change\",u(p),h)}function f(p){if(n.value)return;const h=p.target;l(\"change\",u(h.checked),p)}async function d(p){n.value||!r.value&&!o.value&&s.value&&(p.composedPath().some(m=>m.tagName===\"LABEL\")||(t.value=u([!1,e.falseValue,e.falseLabel].includes(t.value)),await Ie(),c(t.value,p)))}const v=C(()=>(i==null?void 0:i.validateEvent)||e.validateEvent);return he(()=>e.modelValue,()=>{v.value&&(a==null||a.validate(\"change\").catch(p=>void 0))}),{handleChange:f,onClickRoot:d}},xx=e=>{const t=V(!1),{emit:n}=Ge(),r=Ee(rs,void 0),o=C(()=>Lt(r)===!1),s=V(!1),i=C({get(){var a,l;return o.value?(a=r==null?void 0:r.modelValue)==null?void 0:a.value:(l=e.modelValue)!=null?l:t.value},set(a){var l,u;o.value&&pe(a)?(s.value=((l=r==null?void 0:r.max)==null?void 0:l.value)!==void 0&&a.length>(r==null?void 0:r.max.value)&&a.length>i.value.length,s.value===!1&&((u=r==null?void 0:r.changeEvent)==null||u.call(r,a))):(n(rt,a),t.value=a)}});return{model:i,isGroup:o,isLimitExceeded:s}},Ix=(e,t,{model:n})=>{const r=Ee(rs,void 0),o=V(!1),s=C(()=>Cu(e.value)?e.label:e.value),i=C(()=>{const c=n.value;return Bt(c)?c:pe(c)?Oe(s.value)?c.map(Me).some(f=>Aa(f,s.value)):c.map(Me).includes(s.value):c!=null?c===e.trueValue||c===e.trueLabel:!!c}),a=In(C(()=>{var c;return(c=r==null?void 0:r.size)==null?void 0:c.value}),{prop:!0}),l=In(C(()=>{var c;return(c=r==null?void 0:r.size)==null?void 0:c.value})),u=C(()=>!!t.default||!Cu(s.value));return{checkboxButtonSize:a,isChecked:i,isFocused:o,checkboxSize:l,hasOwnLabel:u,actualValue:s}},Mg=(e,t)=>{const{formItem:n}=Vr(),{model:r,isGroup:o,isLimitExceeded:s}=xx(e),{isFocused:i,isChecked:a,checkboxButtonSize:l,checkboxSize:u,hasOwnLabel:c,actualValue:f}=Ix(e,t,{model:r}),{isDisabled:d}=Ax({model:r,isChecked:a}),{inputId:v,isLabeledByFormItem:p}=hi(e,{formItemContext:n,disableIdGeneration:c,disableIdManagement:o}),{handleChange:h,onClickRoot:b}=Rx(e,{model:r,isLimitExceeded:s,hasOwnLabel:c,isDisabled:d,isLabeledByFormItem:p});return(()=>{function S(){var _,w;pe(r.value)&&!r.value.includes(f.value)?r.value.push(f.value):r.value=(w=(_=e.trueValue)!=null?_:e.trueLabel)!=null?w:!0}e.checked&&S()})(),xs({from:\"label act as value\",replacement:\"value\",version:\"3.0.0\",scope:\"el-checkbox\",ref:\"https://element-plus.org/en-US/component/checkbox.html\"},C(()=>o.value&&Cu(e.value))),xs({from:\"true-label\",replacement:\"true-value\",version:\"3.0.0\",scope:\"el-checkbox\",ref:\"https://element-plus.org/en-US/component/checkbox.html\"},C(()=>!!e.trueLabel)),xs({from:\"false-label\",replacement:\"false-value\",version:\"3.0.0\",scope:\"el-checkbox\",ref:\"https://element-plus.org/en-US/component/checkbox.html\"},C(()=>!!e.falseLabel)),{inputId:v,isLabeledByFormItem:p,isChecked:a,isDisabled:d,isFocused:i,checkboxButtonSize:l,checkboxSize:u,hasOwnLabel:c,model:r,actualValue:f,handleChange:h,onClickRoot:b}},Px=Z({name:\"ElCheckbox\"}),Nx=Z({...Px,props:Ng,emits:Lg,setup(e){const t=e,n=go(),{inputId:r,isLabeledByFormItem:o,isChecked:s,isDisabled:i,isFocused:a,checkboxSize:l,hasOwnLabel:u,model:c,actualValue:f,handleChange:d,onClickRoot:v}=Mg(t,n),p=$e(\"checkbox\"),h=C(()=>[p.b(),p.m(l.value),p.is(\"disabled\",i.value),p.is(\"bordered\",t.border),p.is(\"checked\",s.value)]),b=C(()=>[p.e(\"input\"),p.is(\"disabled\",i.value),p.is(\"checked\",s.value),p.is(\"indeterminate\",t.indeterminate),p.is(\"focus\",a.value)]);return(m,S)=>(L(),fe(Xe(!g(u)&&g(o)?\"span\":\"label\"),{class:U(g(h)),\"aria-controls\":m.indeterminate?m.ariaControls:null,onClick:g(v)},{default:ce(()=>{var _,w,y,A;return[le(\"span\",{class:U(g(b))},[m.trueValue||m.falseValue||m.trueLabel||m.falseLabel?ct((L(),ee(\"input\",{key:0,id:g(r),\"onUpdate:modelValue\":R=>Ue(c)?c.value=R:null,class:U(g(p).e(\"original\")),type:\"checkbox\",indeterminate:m.indeterminate,name:m.name,tabindex:m.tabindex,disabled:g(i),\"true-value\":(w=(_=m.trueValue)!=null?_:m.trueLabel)!=null?w:!0,\"false-value\":(A=(y=m.falseValue)!=null?y:m.falseLabel)!=null?A:!1,onChange:g(d),onFocus:R=>a.value=!0,onBlur:R=>a.value=!1,onClick:tt(()=>{},[\"stop\"])},null,42,[\"id\",\"onUpdate:modelValue\",\"indeterminate\",\"name\",\"tabindex\",\"disabled\",\"true-value\",\"false-value\",\"onChange\",\"onFocus\",\"onBlur\",\"onClick\"])),[[ya,g(c)]]):ct((L(),ee(\"input\",{key:1,id:g(r),\"onUpdate:modelValue\":R=>Ue(c)?c.value=R:null,class:U(g(p).e(\"original\")),type:\"checkbox\",indeterminate:m.indeterminate,disabled:g(i),value:g(f),name:m.name,tabindex:m.tabindex,onChange:g(d),onFocus:R=>a.value=!0,onBlur:R=>a.value=!1,onClick:tt(()=>{},[\"stop\"])},null,42,[\"id\",\"onUpdate:modelValue\",\"indeterminate\",\"disabled\",\"value\",\"name\",\"tabindex\",\"onChange\",\"onFocus\",\"onBlur\",\"onClick\"])),[[ya,g(c)]]),le(\"span\",{class:U(g(p).e(\"inner\"))},null,2)],2),g(u)?(L(),ee(\"span\",{key:0,class:U(g(p).e(\"label\"))},[de(m.$slots,\"default\"),m.$slots.default?ae(\"v-if\",!0):(L(),ee(nt,{key:0},[Un(He(m.label),1)],64))],2)):ae(\"v-if\",!0)]}),_:3},8,[\"class\",\"aria-controls\",\"onClick\"]))}});var Lx=Ne(Nx,[[\"__file\",\"checkbox.vue\"]]);const Mx=Z({name:\"ElCheckboxButton\"}),$x=Z({...Mx,props:Ng,emits:Lg,setup(e){const t=e,n=go(),{isFocused:r,isChecked:o,isDisabled:s,checkboxButtonSize:i,model:a,actualValue:l,handleChange:u}=Mg(t,n),c=Ee(rs,void 0),f=$e(\"checkbox\"),d=C(()=>{var p,h,b,m;const S=(h=(p=c==null?void 0:c.fill)==null?void 0:p.value)!=null?h:\"\";return{backgroundColor:S,borderColor:S,color:(m=(b=c==null?void 0:c.textColor)==null?void 0:b.value)!=null?m:\"\",boxShadow:S?`-1px 0 0 0 ${S}`:void 0}}),v=C(()=>[f.b(\"button\"),f.bm(\"button\",i.value),f.is(\"disabled\",s.value),f.is(\"checked\",o.value),f.is(\"focus\",r.value)]);return(p,h)=>{var b,m,S,_;return L(),ee(\"label\",{class:U(g(v))},[p.trueValue||p.falseValue||p.trueLabel||p.falseLabel?ct((L(),ee(\"input\",{key:0,\"onUpdate:modelValue\":w=>Ue(a)?a.value=w:null,class:U(g(f).be(\"button\",\"original\")),type:\"checkbox\",name:p.name,tabindex:p.tabindex,disabled:g(s),\"true-value\":(m=(b=p.trueValue)!=null?b:p.trueLabel)!=null?m:!0,\"false-value\":(_=(S=p.falseValue)!=null?S:p.falseLabel)!=null?_:!1,onChange:g(u),onFocus:w=>r.value=!0,onBlur:w=>r.value=!1,onClick:tt(()=>{},[\"stop\"])},null,42,[\"onUpdate:modelValue\",\"name\",\"tabindex\",\"disabled\",\"true-value\",\"false-value\",\"onChange\",\"onFocus\",\"onBlur\",\"onClick\"])),[[ya,g(a)]]):ct((L(),ee(\"input\",{key:1,\"onUpdate:modelValue\":w=>Ue(a)?a.value=w:null,class:U(g(f).be(\"button\",\"original\")),type:\"checkbox\",name:p.name,tabindex:p.tabindex,disabled:g(s),value:g(l),onChange:g(u),onFocus:w=>r.value=!0,onBlur:w=>r.value=!1,onClick:tt(()=>{},[\"stop\"])},null,42,[\"onUpdate:modelValue\",\"name\",\"tabindex\",\"disabled\",\"value\",\"onChange\",\"onFocus\",\"onBlur\",\"onClick\"])),[[ya,g(a)]]),p.$slots.default||p.label?(L(),ee(\"span\",{key:2,class:U(g(f).be(\"button\",\"inner\")),style:ot(g(o)?g(d):void 0)},[de(p.$slots,\"default\",{},()=>[Un(He(p.label),1)])],6)):ae(\"v-if\",!0)],2)}}});var $g=Ne($x,[[\"__file\",\"checkbox-button.vue\"]]);const kx=xe({modelValue:{type:we(Array),default:()=>[]},disabled:Boolean,min:Number,max:Number,size:So,fill:String,textColor:String,tag:{type:String,default:\"div\"},validateEvent:{type:Boolean,default:!0},...yr([\"ariaLabel\"])}),Fx={[rt]:e=>pe(e),change:e=>pe(e)},Bx=Z({name:\"ElCheckboxGroup\"}),Dx=Z({...Bx,props:kx,emits:Fx,setup(e,{emit:t}){const n=e,r=$e(\"checkbox\"),{formItem:o}=Vr(),{inputId:s,isLabeledByFormItem:i}=hi(n,{formItemContext:o}),a=async u=>{t(rt,u),await Ie(),t(\"change\",u)},l=C({get(){return n.modelValue},set(u){a(u)}});return ft(rs,{...Im(vr(n),[\"size\",\"min\",\"max\",\"disabled\",\"validateEvent\",\"fill\",\"textColor\"]),modelValue:l,changeEvent:a}),he(()=>n.modelValue,()=>{n.validateEvent&&(o==null||o.validate(\"change\").catch(u=>void 0))}),(u,c)=>{var f;return L(),fe(Xe(u.tag),{id:g(s),class:U(g(r).b(\"group\")),role:\"group\",\"aria-label\":g(i)?void 0:u.ariaLabel||\"checkbox-group\",\"aria-labelledby\":g(i)?(f=g(o))==null?void 0:f.labelId:void 0},{default:ce(()=>[de(u.$slots,\"default\")]),_:3},8,[\"id\",\"class\",\"aria-label\",\"aria-labelledby\"])}}});var kg=Ne(Dx,[[\"__file\",\"checkbox-group.vue\"]]);const NN=yt(Lx,{CheckboxButton:$g,CheckboxGroup:kg});wo($g);const LN=wo(kg),ku=xe({type:{type:String,values:[\"primary\",\"success\",\"info\",\"warning\",\"danger\"],default:\"primary\"},closable:Boolean,disableTransitions:Boolean,hit:Boolean,color:String,size:{type:String,values:es},effect:{type:String,values:[\"dark\",\"light\",\"plain\"],default:\"light\"},round:Boolean}),Vx={close:e=>e instanceof MouseEvent,click:e=>e instanceof MouseEvent},jx=Z({name:\"ElTag\"}),zx=Z({...jx,props:ku,emits:Vx,setup(e,{emit:t}){const n=e,r=In(),o=$e(\"tag\"),s=C(()=>{const{type:u,hit:c,effect:f,closable:d,round:v}=n;return[o.b(),o.is(\"closable\",d),o.m(u||\"primary\"),o.m(r.value),o.m(f),o.is(\"hit\",c),o.is(\"round\",v)]}),i=u=>{t(\"close\",u)},a=u=>{t(\"click\",u)},l=u=>{u.component.subTree.component.bum=null};return(u,c)=>u.disableTransitions?(L(),ee(\"span\",{key:0,class:U(g(s)),style:ot({backgroundColor:u.color}),onClick:a},[le(\"span\",{class:U(g(o).e(\"content\"))},[de(u.$slots,\"default\")],2),u.closable?(L(),fe(g(Je),{key:0,class:U(g(o).e(\"close\")),onClick:tt(i,[\"stop\"])},{default:ce(()=>[re(g(Ys))]),_:1},8,[\"class\",\"onClick\"])):ae(\"v-if\",!0)],6)):(L(),fe(Fr,{key:1,name:`${g(o).namespace.value}-zoom-in-center`,appear:\"\",onVnodeMounted:l},{default:ce(()=>[le(\"span\",{class:U(g(s)),style:ot({backgroundColor:u.color}),onClick:a},[le(\"span\",{class:U(g(o).e(\"content\"))},[de(u.$slots,\"default\")],2),u.closable?(L(),fe(g(Je),{key:0,class:U(g(o).e(\"close\")),onClick:tt(i,[\"stop\"])},{default:ce(()=>[re(g(Ys))]),_:1},8,[\"class\",\"onClick\"])):ae(\"v-if\",!0)],6)]),_:3},8,[\"name\"]))}});var Hx=Ne(zx,[[\"__file\",\"tag.vue\"]]);const Ux=yt(Hx),Kx=xe({mask:{type:Boolean,default:!0},customMaskEvent:Boolean,overlayClass:{type:we([String,Array,Object])},zIndex:{type:we([String,Number])}}),qx={click:e=>e instanceof MouseEvent},Wx=\"overlay\";var Gx=Z({name:\"ElOverlay\",props:Kx,emits:qx,setup(e,{slots:t,emit:n}){const r=$e(Wx),o=l=>{n(\"click\",l)},{onClick:s,onMousedown:i,onMouseup:a}=kc(e.customMaskEvent?void 0:o);return()=>e.mask?re(\"div\",{class:[r.b(),e.overlayClass],style:{zIndex:e.zIndex},onClick:s,onMousedown:i,onMouseup:a},[de(t,\"default\")],Zi.STYLE|Zi.CLASS|Zi.PROPS,[\"onClick\",\"onMouseup\",\"onMousedown\"]):or(\"div\",{class:e.overlayClass,style:{zIndex:e.zIndex,position:\"fixed\",top:\"0px\",right:\"0px\",bottom:\"0px\",left:\"0px\"}},[de(t,\"default\")])}});const Fg=Gx,Bg=Symbol(\"dialogInjectionKey\"),Dg=xe({center:Boolean,alignCenter:Boolean,closeIcon:{type:jt},draggable:Boolean,overflow:Boolean,fullscreen:Boolean,showClose:{type:Boolean,default:!0},title:{type:String,default:\"\"},ariaLevel:{type:String,default:\"2\"}}),Yx={close:()=>!0},Jx=Z({name:\"ElDialogContent\"}),Xx=Z({...Jx,props:Dg,emits:Yx,setup(e,{expose:t}){const n=e,{t:r}=sl(),{Close:o}=HT,{dialogRef:s,headerRef:i,bodyId:a,ns:l,style:u}=Ee(Bg),{focusTrapRef:c}=Ee(Cg),f=C(()=>[l.b(),l.is(\"fullscreen\",n.fullscreen),l.is(\"draggable\",n.draggable),l.is(\"align-center\",n.alignCenter),{[l.m(\"center\")]:n.center}]),d=qT(c,s),v=C(()=>n.draggable),p=C(()=>n.overflow),{resetPosition:h}=zm(s,i,v,p);return t({resetPosition:h}),(b,m)=>(L(),ee(\"div\",{ref:g(d),class:U(g(f)),style:ot(g(u)),tabindex:\"-1\"},[le(\"header\",{ref_key:\"headerRef\",ref:i,class:U([g(l).e(\"header\"),{\"show-close\":b.showClose}])},[de(b.$slots,\"header\",{},()=>[le(\"span\",{role:\"heading\",\"aria-level\":b.ariaLevel,class:U(g(l).e(\"title\"))},He(b.title),11,[\"aria-level\"])]),b.showClose?(L(),ee(\"button\",{key:0,\"aria-label\":g(r)(\"el.dialog.close\"),class:U(g(l).e(\"headerbtn\")),type:\"button\",onClick:S=>b.$emit(\"close\")},[re(g(Je),{class:U(g(l).e(\"close\"))},{default:ce(()=>[(L(),fe(Xe(b.closeIcon||g(o))))]),_:1},8,[\"class\"])],10,[\"aria-label\",\"onClick\"])):ae(\"v-if\",!0)],2),le(\"div\",{id:g(a),class:U(g(l).e(\"body\"))},[de(b.$slots,\"default\")],10,[\"id\"]),b.$slots.footer?(L(),ee(\"footer\",{key:0,class:U(g(l).e(\"footer\"))},[de(b.$slots,\"footer\")],2)):ae(\"v-if\",!0)],6))}});var Zx=Ne(Xx,[[\"__file\",\"dialog-content.vue\"]]);const Qx=xe({...Dg,appendToBody:Boolean,appendTo:{type:we([String,Object]),default:\"body\"},beforeClose:{type:we(Function)},destroyOnClose:Boolean,closeOnClickModal:{type:Boolean,default:!0},closeOnPressEscape:{type:Boolean,default:!0},lockScroll:{type:Boolean,default:!0},modal:{type:Boolean,default:!0},openDelay:{type:Number,default:0},closeDelay:{type:Number,default:0},top:{type:String},modelValue:Boolean,modalClass:String,width:{type:[String,Number]},zIndex:{type:Number},trapFocus:Boolean,headerAriaLevel:{type:String,default:\"2\"}}),e3={open:()=>!0,opened:()=>!0,close:()=>!0,closed:()=>!0,[rt]:e=>Bt(e),openAutoFocus:()=>!0,closeAutoFocus:()=>!0},t3=(e,t)=>{var n;const o=Ge().emit,{nextZIndex:s}=Fc();let i=\"\";const a=pr(),l=pr(),u=V(!1),c=V(!1),f=V(!1),d=V((n=e.zIndex)!=null?n:s());let v,p;const h=ll(\"namespace\",Is),b=C(()=>{const P={},O=`--${h.value}-dialog`;return e.fullscreen||(e.top&&(P[`${O}-margin-top`]=e.top),e.width&&(P[`${O}-width`]=pn(e.width))),P}),m=C(()=>e.alignCenter?{display:\"flex\"}:{});function S(){o(\"opened\")}function _(){o(\"closed\"),o(rt,!1),e.destroyOnClose&&(f.value=!1)}function w(){o(\"close\")}function y(){p==null||p(),v==null||v(),e.openDelay&&e.openDelay>0?{stop:v}=bu(()=>I(),e.openDelay):I()}function A(){v==null||v(),p==null||p(),e.closeDelay&&e.closeDelay>0?{stop:p}=bu(()=>x(),e.closeDelay):x()}function R(){function P(O){O||(c.value=!0,u.value=!1)}e.beforeClose?e.beforeClose(P):A()}function N(){e.closeOnClickModal&&R()}function I(){st&&(u.value=!0)}function x(){u.value=!1}function k(){o(\"openAutoFocus\")}function $(){o(\"closeAutoFocus\")}function F(P){var O;((O=P.detail)==null?void 0:O.focusReason)===\"pointer\"&&P.preventDefault()}e.lockScroll&&Km(u);function Y(){e.closeOnPressEscape&&R()}return he(()=>e.modelValue,P=>{P?(c.value=!1,y(),f.value=!0,d.value=Rm(e.zIndex)?s():d.value++,Ie(()=>{o(\"open\"),t.value&&(t.value.scrollTop=0)})):u.value&&A()}),he(()=>e.fullscreen,P=>{t.value&&(P?(i=t.value.style.transform,t.value.style.transform=\"\"):t.value.style.transform=i)}),Ke(()=>{e.modelValue&&(u.value=!0,f.value=!0,y())}),{afterEnter:S,afterLeave:_,beforeLeave:w,handleClose:R,onModalClick:N,close:A,doClose:x,onOpenAutoFocus:k,onCloseAutoFocus:$,onCloseRequested:Y,onFocusoutPrevented:F,titleId:a,bodyId:l,closed:c,style:b,overlayDialogStyle:m,rendered:f,visible:u,zIndex:d}},n3=Z({name:\"ElDialog\",inheritAttrs:!1}),r3=Z({...n3,props:Qx,emits:e3,setup(e,{expose:t}){const n=e,r=go();xs({scope:\"el-dialog\",from:\"the title slot\",replacement:\"the header slot\",version:\"3.0.0\",ref:\"https://element-plus.org/en-US/component/dialog.html#slots\"},C(()=>!!r.title));const o=$e(\"dialog\"),s=V(),i=V(),a=V(),{visible:l,titleId:u,bodyId:c,style:f,overlayDialogStyle:d,rendered:v,zIndex:p,afterEnter:h,afterLeave:b,beforeLeave:m,handleClose:S,onModalClick:_,onOpenAutoFocus:w,onCloseAutoFocus:y,onCloseRequested:A,onFocusoutPrevented:R}=t3(n,s);ft(Bg,{dialogRef:s,headerRef:i,bodyId:c,ns:o,rendered:v,style:f});const N=kc(_),I=C(()=>n.draggable&&!n.fullscreen);return t({visible:l,dialogContentRef:a,resetPosition:()=>{var k;(k=a.value)==null||k.resetPosition()}}),(k,$)=>(L(),fe(g(Ag),{to:k.appendTo,disabled:k.appendTo!==\"body\"?!1:!k.appendToBody},{default:ce(()=>[re(Fr,{name:\"dialog-fade\",onAfterEnter:g(h),onAfterLeave:g(b),onBeforeLeave:g(m),persisted:\"\"},{default:ce(()=>[ct(re(g(Fg),{\"custom-mask-event\":\"\",mask:k.modal,\"overlay-class\":k.modalClass,\"z-index\":g(p)},{default:ce(()=>[le(\"div\",{role:\"dialog\",\"aria-modal\":\"true\",\"aria-label\":k.title||void 0,\"aria-labelledby\":k.title?void 0:g(u),\"aria-describedby\":g(c),class:U(`${g(o).namespace.value}-overlay-dialog`),style:ot(g(d)),onClick:g(N).onClick,onMousedown:g(N).onMousedown,onMouseup:g(N).onMouseup},[re(g(Hc),{loop:\"\",trapped:g(l),\"focus-start-el\":\"container\",onFocusAfterTrapped:g(w),onFocusAfterReleased:g(y),onFocusoutPrevented:g(R),onReleaseRequested:g(A)},{default:ce(()=>[g(v)?(L(),fe(Zx,En({key:0,ref_key:\"dialogContentRef\",ref:a},k.$attrs,{center:k.center,\"align-center\":k.alignCenter,\"close-icon\":k.closeIcon,draggable:g(I),overflow:k.overflow,fullscreen:k.fullscreen,\"show-close\":k.showClose,title:k.title,\"aria-level\":k.headerAriaLevel,onClose:g(S)}),lv({header:ce(()=>[k.$slots.title?de(k.$slots,\"title\",{key:1}):de(k.$slots,\"header\",{key:0,close:g(S),titleId:g(u),titleClass:g(o).e(\"title\")})]),default:ce(()=>[de(k.$slots,\"default\")]),_:2},[k.$slots.footer?{name:\"footer\",fn:ce(()=>[de(k.$slots,\"footer\")])}:void 0]),1040,[\"center\",\"align-center\",\"close-icon\",\"draggable\",\"overflow\",\"fullscreen\",\"show-close\",\"title\",\"aria-level\",\"onClose\"])):ae(\"v-if\",!0)]),_:3},8,[\"trapped\",\"onFocusAfterTrapped\",\"onFocusAfterReleased\",\"onFocusoutPrevented\",\"onReleaseRequested\"])],46,[\"aria-label\",\"aria-labelledby\",\"aria-describedby\",\"onClick\",\"onMousedown\",\"onMouseup\"])]),_:3},8,[\"mask\",\"overlay-class\",\"z-index\"]),[[en,g(l)]])]),_:3},8,[\"onAfterEnter\",\"onAfterLeave\",\"onBeforeLeave\"])]),_:3},8,[\"to\",\"disabled\"]))}});var o3=Ne(r3,[[\"__file\",\"dialog.vue\"]]);const MN=yt(o3),s3=Z({inheritAttrs:!1});function i3(e,t,n,r,o,s){return de(e.$slots,\"default\")}var a3=Ne(s3,[[\"render\",i3],[\"__file\",\"collection.vue\"]]);const l3=Z({name:\"ElCollectionItem\",inheritAttrs:!1});function u3(e,t,n,r,o,s){return de(e.$slots,\"default\")}var c3=Ne(l3,[[\"render\",u3],[\"__file\",\"collection-item.vue\"]]);const f3=\"data-el-collection-item\",d3=e=>{const t=`El${e}Collection`,n=`${t}Item`,r=Symbol(t),o=Symbol(n),s={...a3,name:t,setup(){const a=V(null),l=new Map;ft(r,{itemMap:l,getItems:()=>{const c=g(a);if(!c)return[];const f=Array.from(c.querySelectorAll(`[${f3}]`));return[...l.values()].sort((v,p)=>f.indexOf(v.ref)-f.indexOf(p.ref))},collectionRef:a})}},i={...c3,name:n,setup(a,{attrs:l}){const u=V(null),c=Ee(r,void 0);ft(o,{collectionItemRef:u}),Ke(()=>{const f=g(u);f&&c.itemMap.set(f,{ref:f,...l})}),St(()=>{const f=g(u);c.itemMap.delete(f)})}};return{COLLECTION_INJECTION_KEY:r,COLLECTION_ITEM_INJECTION_KEY:o,ElCollection:s,ElCollectionItem:i}},Ul=xe({trigger:ei.trigger,effect:{...Gt.effect,default:\"light\"},type:{type:we(String)},placement:{type:we(String),default:\"bottom\"},popperOptions:{type:we(Object),default:()=>({})},id:String,size:{type:String,default:\"\"},splitButton:Boolean,hideOnClick:{type:Boolean,default:!0},loop:{type:Boolean,default:!0},showTimeout:{type:Number,default:150},hideTimeout:{type:Number,default:150},tabindex:{type:we([Number,String]),default:0},maxHeight:{type:we([Number,String]),default:\"\"},popperClass:{type:String,default:\"\"},disabled:Boolean,role:{type:String,default:\"menu\"},buttonProps:{type:we(Object)},teleported:Gt.teleported});xe({command:{type:[Object,String,Number],default:()=>({})},disabled:Boolean,divided:Boolean,textValue:String,icon:{type:jt}});xe({onKeydown:{type:we(Function)}});d3(\"Dropdown\");const p3=xe({id:{type:String,default:void 0},step:{type:Number,default:1},stepStrictly:Boolean,max:{type:Number,default:Number.POSITIVE_INFINITY},min:{type:Number,default:Number.NEGATIVE_INFINITY},modelValue:Number,readonly:Boolean,disabled:Boolean,size:So,controls:{type:Boolean,default:!0},controlsPosition:{type:String,default:\"\",values:[\"\",\"right\"]},valueOnClear:{type:[String,Number,null],validator:e=>e===null||je(e)||[\"min\",\"max\"].includes(e),default:null},name:String,placeholder:String,precision:{type:Number,validator:e=>e>=0&&e===Number.parseInt(`${e}`,10)},validateEvent:{type:Boolean,default:!0},...yr([\"ariaLabel\"])}),h3={[fo]:(e,t)=>t!==e,blur:e=>e instanceof FocusEvent,focus:e=>e instanceof FocusEvent,[io]:e=>je(e)||qn(e),[rt]:e=>je(e)||qn(e)},v3=Z({name:\"ElInputNumber\"}),m3=Z({...v3,props:p3,emits:h3,setup(e,{expose:t,emit:n}){const r=e,{t:o}=sl(),s=$e(\"input-number\"),i=V(),a=wt({currentValue:r.modelValue,userInput:null}),{formItem:l}=Vr(),u=C(()=>je(r.modelValue)&&r.modelValue<=r.min),c=C(()=>je(r.modelValue)&&r.modelValue>=r.max),f=C(()=>{const P=m(r.step);return Lt(r.precision)?Math.max(m(r.modelValue),P):(P>r.precision,r.precision)}),d=C(()=>r.controls&&r.controlsPosition===\"right\"),v=In(),p=ns(),h=C(()=>{if(a.userInput!==null)return a.userInput;let P=a.currentValue;if(qn(P))return\"\";if(je(P)){if(Number.isNaN(P))return\"\";Lt(r.precision)||(P=P.toFixed(r.precision))}return P}),b=(P,O)=>{if(Lt(O)&&(O=f.value),O===0)return Math.round(P);let j=String(P);const Q=j.indexOf(\".\");if(Q===-1||!j.replace(\".\",\"\").split(\"\")[Q+O])return P;const Re=j.length;return j.charAt(Re-1)===\"5\"&&(j=`${j.slice(0,Math.max(0,Re-1))}6`),Number.parseFloat(Number(j).toFixed(O))},m=P=>{if(qn(P))return 0;const O=P.toString(),j=O.indexOf(\".\");let Q=0;return j!==-1&&(Q=O.length-j-1),Q},S=(P,O=1)=>je(P)?b(P+r.step*O):a.currentValue,_=()=>{if(r.readonly||p.value||c.value)return;const P=Number(h.value)||0,O=S(P);A(O),n(io,a.currentValue),F()},w=()=>{if(r.readonly||p.value||u.value)return;const P=Number(h.value)||0,O=S(P,-1);A(O),n(io,a.currentValue),F()},y=(P,O)=>{const{max:j,min:Q,step:me,precision:Pe,stepStrictly:Re,valueOnClear:Ce}=r;j<Q&&Br(\"InputNumber\",\"min should not be greater than max.\");let _e=Number(P);if(qn(P)||Number.isNaN(_e))return null;if(P===\"\"){if(Ce===null)return null;_e=Ae(Ce)?{min:Q,max:j}[Ce]:Ce}return Re&&(_e=b(Math.round(_e/me)*me,Pe),_e!==P&&O&&n(rt,_e)),Lt(Pe)||(_e=b(_e,Pe)),(_e>j||_e<Q)&&(_e=_e>j?j:Q,O&&n(rt,_e)),_e},A=(P,O=!0)=>{var j;const Q=a.currentValue,me=y(P);if(!O){n(rt,me);return}Q===me&&P||(a.userInput=null,n(rt,me),Q!==me&&n(fo,me,Q),r.validateEvent&&((j=l==null?void 0:l.validate)==null||j.call(l,\"change\").catch(Pe=>void 0)),a.currentValue=me)},R=P=>{a.userInput=P;const O=P===\"\"?null:Number(P);n(io,O),A(O,!1)},N=P=>{const O=P!==\"\"?Number(P):\"\";(je(O)&&!Number.isNaN(O)||P===\"\")&&A(O),F(),a.userInput=null},I=()=>{var P,O;(O=(P=i.value)==null?void 0:P.focus)==null||O.call(P)},x=()=>{var P,O;(O=(P=i.value)==null?void 0:P.blur)==null||O.call(P)},k=P=>{n(\"focus\",P)},$=P=>{var O;a.userInput=null,n(\"blur\",P),r.validateEvent&&((O=l==null?void 0:l.validate)==null||O.call(l,\"blur\").catch(j=>void 0))},F=()=>{a.currentValue!==r.modelValue&&(a.currentValue=r.modelValue)},Y=P=>{document.activeElement===P.target&&P.preventDefault()};return he(()=>r.modelValue,(P,O)=>{const j=y(P,!0);a.userInput===null&&j!==O&&(a.currentValue=j)},{immediate:!0}),Ke(()=>{var P;const{min:O,max:j,modelValue:Q}=r,me=(P=i.value)==null?void 0:P.input;if(me.setAttribute(\"role\",\"spinbutton\"),Number.isFinite(j)?me.setAttribute(\"aria-valuemax\",String(j)):me.removeAttribute(\"aria-valuemax\"),Number.isFinite(O)?me.setAttribute(\"aria-valuemin\",String(O)):me.removeAttribute(\"aria-valuemin\"),me.setAttribute(\"aria-valuenow\",a.currentValue||a.currentValue===0?String(a.currentValue):\"\"),me.setAttribute(\"aria-disabled\",String(p.value)),!je(Q)&&Q!=null){let Pe=Number(Q);Number.isNaN(Pe)&&(Pe=null),n(rt,Pe)}me.addEventListener(\"wheel\",Y,{passive:!1})}),mo(()=>{var P,O;const j=(P=i.value)==null?void 0:P.input;j==null||j.setAttribute(\"aria-valuenow\",`${(O=a.currentValue)!=null?O:\"\"}`)}),t({focus:I,blur:x}),(P,O)=>(L(),ee(\"div\",{class:U([g(s).b(),g(s).m(g(v)),g(s).is(\"disabled\",g(p)),g(s).is(\"without-controls\",!P.controls),g(s).is(\"controls-right\",g(d))]),onDragstart:tt(()=>{},[\"prevent\"])},[P.controls?ct((L(),ee(\"span\",{key:0,role:\"button\",\"aria-label\":g(o)(\"el.inputNumber.decrease\"),class:U([g(s).e(\"decrease\"),g(s).is(\"disabled\",g(u))]),onKeydown:Vt(w,[\"enter\"])},[de(P.$slots,\"decrease-icon\",{},()=>[re(g(Je),null,{default:ce(()=>[g(d)?(L(),fe(g(Nm),{key:0})):(L(),fe(g($T),{key:1}))]),_:1})])],42,[\"aria-label\",\"onKeydown\"])),[[g(kp),w]]):ae(\"v-if\",!0),P.controls?ct((L(),ee(\"span\",{key:1,role:\"button\",\"aria-label\":g(o)(\"el.inputNumber.increase\"),class:U([g(s).e(\"increase\"),g(s).is(\"disabled\",g(c))]),onKeydown:Vt(_,[\"enter\"])},[de(P.$slots,\"increase-icon\",{},()=>[re(g(Je),null,{default:ce(()=>[g(d)?(L(),fe(g(bT),{key:0})):(L(),fe(g($m),{key:1}))]),_:1})])],42,[\"aria-label\",\"onKeydown\"])),[[g(kp),_]]):ae(\"v-if\",!0),re(g(bg),{id:P.id,ref_key:\"input\",ref:i,type:\"number\",step:P.step,\"model-value\":g(h),placeholder:P.placeholder,readonly:P.readonly,disabled:g(p),size:g(v),max:P.max,min:P.min,name:P.name,\"aria-label\":P.ariaLabel,\"validate-event\":!1,onKeydown:[Vt(tt(_,[\"prevent\"]),[\"up\"]),Vt(tt(w,[\"prevent\"]),[\"down\"])],onBlur:$,onFocus:k,onInput:R,onChange:N},lv({_:2},[P.$slots.prefix?{name:\"prefix\",fn:ce(()=>[de(P.$slots,\"prefix\")])}:void 0,P.$slots.suffix?{name:\"suffix\",fn:ce(()=>[de(P.$slots,\"suffix\")])}:void 0]),1032,[\"id\",\"step\",\"model-value\",\"placeholder\",\"readonly\",\"disabled\",\"size\",\"max\",\"min\",\"name\",\"aria-label\",\"onKeydown\"])],42,[\"onDragstart\"]))}});var g3=Ne(m3,[[\"__file\",\"input-number.vue\"]]);const $N=yt(g3),b3=xe({type:{type:String,values:[\"primary\",\"success\",\"warning\",\"info\",\"danger\",\"default\"],default:\"default\"},underline:{type:Boolean,default:!0},disabled:Boolean,href:{type:String,default:\"\"},target:{type:String,default:\"_self\"},icon:{type:jt}}),y3={click:e=>e instanceof MouseEvent},w3=Z({name:\"ElLink\"}),S3=Z({...w3,props:b3,emits:y3,setup(e,{emit:t}){const n=e,r=$e(\"link\"),o=C(()=>[r.b(),r.m(n.type),r.is(\"disabled\",n.disabled),r.is(\"underline\",n.underline&&!n.disabled)]);function s(i){n.disabled||t(\"click\",i)}return(i,a)=>(L(),ee(\"a\",{class:U(g(o)),href:i.disabled||!i.href?void 0:i.href,target:i.disabled||!i.href?void 0:i.target,onClick:s},[i.icon?(L(),fe(g(Je),{key:0},{default:ce(()=>[(L(),fe(Xe(i.icon)))]),_:1})):ae(\"v-if\",!0),i.$slots.default?(L(),ee(\"span\",{key:1,class:U(g(r).e(\"inner\"))},[de(i.$slots,\"default\")],2)):ae(\"v-if\",!0),i.$slots.icon?de(i.$slots,\"icon\",{key:2}):ae(\"v-if\",!0)],10,[\"href\",\"target\"]))}});var E3=Ne(S3,[[\"__file\",\"link.vue\"]]);const kN=yt(E3),Vg=Symbol(\"ElSelectGroup\"),cl=Symbol(\"ElSelect\");function _3(e,t){const n=Ee(cl),r=Ee(Vg,{disabled:!1}),o=C(()=>c(yn(n.props.modelValue),e.value)),s=C(()=>{var v;if(n.props.multiple){const p=yn((v=n.props.modelValue)!=null?v:[]);return!o.value&&p.length>=n.props.multipleLimit&&n.props.multipleLimit>0}else return!1}),i=C(()=>e.label||(Oe(e.value)?\"\":e.value)),a=C(()=>e.value||e.label||\"\"),l=C(()=>e.disabled||t.groupDisabled||s.value),u=Ge(),c=(v=[],p)=>{if(Oe(e.value)){const h=n.props.valueKey;return v&&v.some(b=>Me(zn(b,h))===zn(p,h))}else return v&&v.includes(p)},f=()=>{!e.disabled&&!r.disabled&&(n.states.hoveringIndex=n.optionsArray.indexOf(u.proxy))},d=v=>{const p=new RegExp(lT(v),\"i\");t.visible=p.test(i.value)||e.created};return he(()=>i.value,()=>{!e.created&&!n.props.remote&&n.setSelected()}),he(()=>e.value,(v,p)=>{const{remote:h,valueKey:b}=n.props;if(v!==p&&(n.onOptionDestroy(p,u.proxy),n.onOptionCreate(u.proxy)),!e.created&&!h){if(b&&Oe(v)&&Oe(p)&&v[b]===p[b])return;n.setSelected()}}),he(()=>r.disabled,()=>{t.groupDisabled=r.disabled},{immediate:!0}),{select:n,currentLabel:i,currentValue:a,itemSelected:o,isDisabled:l,hoverItem:f,updateOption:d}}const C3=Z({name:\"ElOption\",componentName:\"ElOption\",props:{value:{required:!0,type:[String,Number,Boolean,Object]},label:[String,Number],created:Boolean,disabled:Boolean},setup(e){const t=$e(\"select\"),n=pr(),r=C(()=>[t.be(\"dropdown\",\"item\"),t.is(\"disabled\",g(a)),t.is(\"selected\",g(i)),t.is(\"hovering\",g(d))]),o=wt({index:-1,groupDisabled:!1,visible:!0,hover:!1}),{currentLabel:s,itemSelected:i,isDisabled:a,select:l,hoverItem:u,updateOption:c}=_3(e,o),{visible:f,hover:d}=vr(o),v=Ge().proxy;l.onOptionCreate(v),St(()=>{const h=v.value,{selected:b}=l.states,S=(l.props.multiple?b:[b]).some(_=>_.value===v.value);Ie(()=>{l.states.cachedOptions.get(h)===v&&!S&&l.states.cachedOptions.delete(h)}),l.onOptionDestroy(h,v)});function p(){a.value||l.handleOptionSelect(v)}return{ns:t,id:n,containerKls:r,currentLabel:s,itemSelected:i,isDisabled:a,select:l,hoverItem:u,updateOption:c,visible:f,hover:d,selectOptionClick:p,states:o}}});function T3(e,t,n,r,o,s){return ct((L(),ee(\"li\",{id:e.id,class:U(e.containerKls),role:\"option\",\"aria-disabled\":e.isDisabled||void 0,\"aria-selected\":e.itemSelected,onMouseenter:e.hoverItem,onClick:tt(e.selectOptionClick,[\"stop\"])},[de(e.$slots,\"default\",{},()=>[le(\"span\",null,He(e.currentLabel),1)])],42,[\"id\",\"aria-disabled\",\"aria-selected\",\"onMouseenter\",\"onClick\"])),[[en,e.visible]])}var Kc=Ne(C3,[[\"render\",T3],[\"__file\",\"option.vue\"]]);const O3=Z({name:\"ElSelectDropdown\",componentName:\"ElSelectDropdown\",setup(){const e=Ee(cl),t=$e(\"select\"),n=C(()=>e.props.popperClass),r=C(()=>e.props.multiple),o=C(()=>e.props.fitInputWidth),s=V(\"\");function i(){var a;s.value=`${(a=e.selectRef)==null?void 0:a.offsetWidth}px`}return Ke(()=>{i(),Dt(e.selectRef,i)}),{ns:t,minWidth:s,popperClass:n,isMultiple:r,isFitInputWidth:o}}});function A3(e,t,n,r,o,s){return L(),ee(\"div\",{class:U([e.ns.b(\"dropdown\"),e.ns.is(\"multiple\",e.isMultiple),e.popperClass]),style:ot({[e.isFitInputWidth?\"width\":\"minWidth\"]:e.minWidth})},[e.$slots.header?(L(),ee(\"div\",{key:0,class:U(e.ns.be(\"dropdown\",\"header\"))},[de(e.$slots,\"header\")],2)):ae(\"v-if\",!0),de(e.$slots,\"default\"),e.$slots.footer?(L(),ee(\"div\",{key:1,class:U(e.ns.be(\"dropdown\",\"footer\"))},[de(e.$slots,\"footer\")],2)):ae(\"v-if\",!0)],6)}var R3=Ne(O3,[[\"render\",A3],[\"__file\",\"select-dropdown.vue\"]]);const x3=11,I3=(e,t)=>{const{t:n}=sl(),r=pr(),o=$e(\"select\"),s=$e(\"input\"),i=wt({inputValue:\"\",options:new Map,cachedOptions:new Map,disabledOptions:new Map,optionValues:[],selected:[],selectionWidth:0,calculatorWidth:0,collapseItemWidth:0,selectedLabel:\"\",hoveringIndex:-1,previousQuery:null,inputHovering:!1,menuVisibleOnFocus:!1,isBeforeHide:!1}),a=V(null),l=V(null),u=V(null),c=V(null),f=V(null),d=V(null),v=V(null),p=V(null),h=V(null),b=V(null),m=V(null),S=V(null),{isComposing:_,handleCompositionStart:w,handleCompositionUpdate:y,handleCompositionEnd:A}=dg({afterComposition:H=>ke(H)}),{wrapperRef:R,isFocused:N,handleBlur:I}=fg(f,{beforeFocus(){return j.value},afterFocus(){e.automaticDropdown&&!x.value&&(x.value=!0,i.menuVisibleOnFocus=!0)},beforeBlur(H){var ie,Fe;return((ie=u.value)==null?void 0:ie.isFocusInsideContent(H))||((Fe=c.value)==null?void 0:Fe.isFocusInsideContent(H))},afterBlur(){x.value=!1,i.menuVisibleOnFocus=!1}}),x=V(!1),k=V(),{form:$,formItem:F}=Vr(),{inputId:Y}=hi(e,{formItemContext:F}),{valueOnClear:P,isEmptyValue:O}=_A(e),j=C(()=>e.disabled||($==null?void 0:$.disabled)),Q=C(()=>pe(e.modelValue)?e.modelValue.length>0:!O(e.modelValue)),me=C(()=>e.clearable&&!j.value&&i.inputHovering&&Q.value),Pe=C(()=>e.remote&&e.filterable&&!e.remoteShowSuffix?\"\":e.suffixIcon),Re=C(()=>o.is(\"reverse\",Pe.value&&x.value)),Ce=C(()=>(F==null?void 0:F.validateState)||\"\"),_e=C(()=>Vm[Ce.value]),qe=C(()=>e.remote?300:0),ze=C(()=>e.loading?e.loadingText||n(\"el.select.loading\"):e.remote&&!i.inputValue&&i.options.size===0?!1:e.filterable&&i.inputValue&&i.options.size>0&&De.value===0?e.noMatchText||n(\"el.select.noMatch\"):i.options.size===0?e.noDataText||n(\"el.select.noData\"):null),De=C(()=>B.value.filter(H=>H.visible).length),B=C(()=>{const H=Array.from(i.options.values()),ie=[];return i.optionValues.forEach(Fe=>{const Ze=H.findIndex(wr=>wr.value===Fe);Ze>-1&&ie.push(H[Ze])}),ie.length>=H.length?ie:H}),K=C(()=>Array.from(i.cachedOptions.values())),J=C(()=>{const H=B.value.filter(ie=>!ie.created).some(ie=>ie.currentLabel===i.inputValue);return e.filterable&&e.allowCreate&&i.inputValue!==\"\"&&!H}),oe=()=>{e.filterable&&ve(e.filterMethod)||e.filterable&&e.remote&&ve(e.remoteMethod)||B.value.forEach(H=>{var ie;(ie=H.updateOption)==null||ie.call(H,i.inputValue)})},ge=In(),E=C(()=>[\"small\"].includes(ge.value)?\"small\":\"default\"),T=C({get(){return x.value&&ze.value!==!1},set(H){x.value=H}}),M=C(()=>{if(e.multiple&&!Lt(e.modelValue))return yn(e.modelValue).length===0&&!i.inputValue;const H=pe(e.modelValue)?e.modelValue[0]:e.modelValue;return e.filterable||Lt(H)?!i.inputValue:!0}),W=C(()=>{var H;const ie=(H=e.placeholder)!=null?H:n(\"el.select.placeholder\");return e.multiple||!Q.value?ie:i.selectedLabel}),G=C(()=>gu?null:\"mouseenter\");he(()=>e.modelValue,(H,ie)=>{e.multiple&&e.filterable&&!e.reserveKeyword&&(i.inputValue=\"\",q(\"\")),ne(),!Aa(H,ie)&&e.validateEvent&&(F==null||F.validate(\"change\").catch(Fe=>void 0))},{flush:\"post\",deep:!0}),he(()=>x.value,H=>{H?q(i.inputValue):(i.inputValue=\"\",i.previousQuery=null,i.isBeforeHide=!0),t(\"visible-change\",H)}),he(()=>i.options.entries(),()=>{var H;if(!st)return;const ie=((H=a.value)==null?void 0:H.querySelectorAll(\"input\"))||[];(!e.filterable&&!e.defaultFirstOption&&!Lt(e.modelValue)||!Array.from(ie).includes(document.activeElement))&&ne(),e.defaultFirstOption&&(e.filterable||e.remote)&&De.value&&se()},{flush:\"post\"}),he(()=>i.hoveringIndex,H=>{je(H)&&H>-1?k.value=B.value[H]||{}:k.value={},B.value.forEach(ie=>{ie.hover=k.value===ie})}),Ha(()=>{i.isBeforeHide||oe()});const q=H=>{i.previousQuery===H||_.value||(i.previousQuery=H,e.filterable&&ve(e.filterMethod)?e.filterMethod(H):e.filterable&&e.remote&&ve(e.remoteMethod)&&e.remoteMethod(H),e.defaultFirstOption&&(e.filterable||e.remote)&&De.value?Ie(se):Ie(X))},se=()=>{const H=B.value.filter(Ze=>Ze.visible&&!Ze.disabled&&!Ze.states.groupDisabled),ie=H.find(Ze=>Ze.created),Fe=H[0];i.hoveringIndex=qt(B.value,ie||Fe)},ne=()=>{if(e.multiple)i.selectedLabel=\"\";else{const ie=pe(e.modelValue)?e.modelValue[0]:e.modelValue,Fe=te(ie);i.selectedLabel=Fe.currentLabel,i.selected=[Fe];return}const H=[];Lt(e.modelValue)||yn(e.modelValue).forEach(ie=>{H.push(te(ie))}),i.selected=H},te=H=>{let ie;const Fe=Ui(H).toLowerCase()===\"object\",Ze=Ui(H).toLowerCase()===\"null\",wr=Ui(H).toLowerCase()===\"undefined\";for(let Hr=i.cachedOptions.size-1;Hr>=0;Hr--){const Ln=K.value[Hr];if(Fe?zn(Ln.value,e.valueKey)===zn(H,e.valueKey):Ln.value===H){ie={value:H,currentLabel:Ln.currentLabel,get isDisabled(){return Ln.isDisabled}};break}}if(ie)return ie;const Eo=Fe?H.label:!Ze&&!wr?H:\"\";return{value:H,currentLabel:Eo}},X=()=>{i.hoveringIndex=B.value.findIndex(H=>i.selected.some(ie=>bl(ie)===bl(H)))},Se=()=>{i.selectionWidth=l.value.getBoundingClientRect().width},ue=()=>{i.calculatorWidth=d.value.getBoundingClientRect().width},ye=()=>{i.collapseItemWidth=m.value.getBoundingClientRect().width},z=()=>{var H,ie;(ie=(H=u.value)==null?void 0:H.updatePopper)==null||ie.call(H)},be=()=>{var H,ie;(ie=(H=c.value)==null?void 0:H.updatePopper)==null||ie.call(H)},Le=()=>{i.inputValue.length>0&&!x.value&&(x.value=!0),q(i.inputValue)},ke=H=>{if(i.inputValue=H.target.value,e.remote)dt();else return Le()},dt=nT(()=>{Le()},qe.value),pt=H=>{Aa(e.modelValue,H)||t(fo,H)},rn=H=>rT(H,ie=>!i.disabledOptions.has(ie)),on=H=>{if(e.multiple&&H.code!==_n.delete&&H.target.value.length<=0){const ie=yn(e.modelValue).slice(),Fe=rn(ie);if(Fe<0)return;const Ze=ie[Fe];ie.splice(Fe,1),t(rt,ie),pt(ie),t(\"remove-tag\",Ze)}},jr=(H,ie)=>{const Fe=i.selected.indexOf(ie);if(Fe>-1&&!j.value){const Ze=yn(e.modelValue).slice();Ze.splice(Fe,1),t(rt,Ze),pt(Ze),t(\"remove-tag\",ie.value)}H.stopPropagation(),Ei()},as=H=>{H.stopPropagation();const ie=e.multiple?[]:P.value;if(e.multiple)for(const Fe of i.selected)Fe.isDisabled&&ie.push(Fe.value);t(rt,ie),pt(ie),i.hoveringIndex=-1,x.value=!1,t(\"clear\"),Ei()},Ot=H=>{var ie;if(e.multiple){const Fe=yn((ie=e.modelValue)!=null?ie:[]).slice(),Ze=qt(Fe,H.value);Ze>-1?Fe.splice(Ze,1):(e.multipleLimit<=0||Fe.length<e.multipleLimit)&&Fe.push(H.value),t(rt,Fe),pt(Fe),H.created&&q(\"\"),e.filterable&&!e.reserveKeyword&&(i.inputValue=\"\")}else t(rt,H.value),pt(H.value),x.value=!1;Ei(),!x.value&&Ie(()=>{zr(H)})},qt=(H=[],ie)=>{if(!Oe(ie))return H.indexOf(ie);const Fe=e.valueKey;let Ze=-1;return H.some((wr,Eo)=>Me(zn(wr,Fe))===zn(ie,Fe)?(Ze=Eo,!0):!1),Ze},zr=H=>{var ie,Fe,Ze,wr,Eo;const _i=pe(H)?H[0]:H;let Hr=null;if(_i!=null&&_i.value){const Ln=B.value.filter(tf=>tf.value===_i.value);Ln.length>0&&(Hr=Ln[0].$el)}if(u.value&&Hr){const Ln=(wr=(Ze=(Fe=(ie=u.value)==null?void 0:ie.popperRef)==null?void 0:Fe.contentRef)==null?void 0:Ze.querySelector)==null?void 0:wr.call(Ze,`.${o.be(\"dropdown\",\"wrap\")}`);Ln&&fT(Ln,Hr)}(Eo=S.value)==null||Eo.handleScroll()},Si=H=>{i.options.set(H.value,H),i.cachedOptions.set(H.value,H),H.disabled&&i.disabledOptions.set(H.value,H)},Nb=(H,ie)=>{i.options.get(H)===ie&&i.options.delete(H)},Lb=C(()=>{var H,ie;return(ie=(H=u.value)==null?void 0:H.popperRef)==null?void 0:ie.contentRef}),Mb=()=>{i.isBeforeHide=!1,Ie(()=>zr(i.selected))},Ei=()=>{var H;(H=f.value)==null||H.focus()},$b=()=>{var H;if(x.value){x.value=!1,Ie(()=>{var ie;return(ie=f.value)==null?void 0:ie.blur()});return}(H=f.value)==null||H.blur()},kb=H=>{as(H)},Fb=H=>{if(x.value=!1,N.value){const ie=new FocusEvent(\"focus\",H);Ie(()=>I(ie))}},Bb=()=>{i.inputValue.length>0?i.inputValue=\"\":x.value=!1},Qc=()=>{j.value||(gu&&(i.inputHovering=!0),i.menuVisibleOnFocus?i.menuVisibleOnFocus=!1:x.value=!x.value)},Db=()=>{x.value?B.value[i.hoveringIndex]&&Ot(B.value[i.hoveringIndex]):Qc()},bl=H=>Oe(H.value)?zn(H.value,e.valueKey):H.value,Vb=C(()=>B.value.filter(H=>H.visible).every(H=>H.disabled)),jb=C(()=>e.multiple?e.collapseTags?i.selected.slice(0,e.maxCollapseTags):i.selected:[]),zb=C(()=>e.multiple?e.collapseTags?i.selected.slice(e.maxCollapseTags):[]:[]),ef=H=>{if(!x.value){x.value=!0;return}if(!(i.options.size===0||i.filteredOptionsCount===0||_.value)&&!Vb.value){H===\"next\"?(i.hoveringIndex++,i.hoveringIndex===i.options.size&&(i.hoveringIndex=0)):H===\"prev\"&&(i.hoveringIndex--,i.hoveringIndex<0&&(i.hoveringIndex=i.options.size-1));const ie=B.value[i.hoveringIndex];(ie.disabled===!0||ie.states.groupDisabled===!0||!ie.visible)&&ef(H),Ie(()=>zr(k.value))}},Hb=()=>{if(!l.value)return 0;const H=window.getComputedStyle(l.value);return Number.parseFloat(H.gap||\"6px\")},Ub=C(()=>{const H=Hb();return{maxWidth:`${m.value&&e.maxCollapseTags===1?i.selectionWidth-i.collapseItemWidth-H:i.selectionWidth}px`}}),Kb=C(()=>({maxWidth:`${i.selectionWidth}px`})),qb=C(()=>({width:`${Math.max(i.calculatorWidth,x3)}px`}));return Dt(l,Se),Dt(d,ue),Dt(h,z),Dt(R,z),Dt(b,be),Dt(m,ye),Ke(()=>{ne()}),{inputId:Y,contentId:r,nsSelect:o,nsInput:s,states:i,isFocused:N,expanded:x,optionsArray:B,hoverOption:k,selectSize:ge,filteredOptionsCount:De,resetCalculatorWidth:ue,updateTooltip:z,updateTagTooltip:be,debouncedOnInputChange:dt,onInput:ke,deletePrevTag:on,deleteTag:jr,deleteSelected:as,handleOptionSelect:Ot,scrollToOption:zr,hasModelValue:Q,shouldShowPlaceholder:M,currentPlaceholder:W,mouseEnterEventName:G,showClose:me,iconComponent:Pe,iconReverse:Re,validateState:Ce,validateIcon:_e,showNewOption:J,updateOptions:oe,collapseTagSize:E,setSelected:ne,selectDisabled:j,emptyText:ze,handleCompositionStart:w,handleCompositionUpdate:y,handleCompositionEnd:A,onOptionCreate:Si,onOptionDestroy:Nb,handleMenuEnter:Mb,focus:Ei,blur:$b,handleClearClick:kb,handleClickOutside:Fb,handleEsc:Bb,toggleMenu:Qc,selectOption:Db,getValueKey:bl,navigateOptions:ef,dropdownMenuVisible:T,showTagList:jb,collapseTagList:zb,tagStyle:Ub,collapseTagStyle:Kb,inputStyle:qb,popperRef:Lb,inputRef:f,tooltipRef:u,tagTooltipRef:c,calculatorRef:d,prefixRef:v,suffixRef:p,selectRef:a,wrapperRef:R,selectionRef:l,scrollbarRef:S,menuRef:h,tagMenuRef:b,collapseItemRef:m}};var P3=Z({name:\"ElOptions\",setup(e,{slots:t}){const n=Ee(cl);let r=[];return()=>{var o,s;const i=(o=t.default)==null?void 0:o.call(t),a=[];function l(u){pe(u)&&u.forEach(c=>{var f,d,v,p;const h=(f=(c==null?void 0:c.type)||{})==null?void 0:f.name;h===\"ElOptionGroup\"?l(!Ae(c.children)&&!pe(c.children)&&ve((d=c.children)==null?void 0:d.default)?(v=c.children)==null?void 0:v.default():c.children):h===\"ElOption\"?a.push((p=c.props)==null?void 0:p.value):pe(c.children)&&l(c.children)})}return i.length&&l((s=i[0])==null?void 0:s.children),Aa(a,r)||(r=a,n&&(n.states.optionValues=a)),i}}});const N3=xe({name:String,id:String,modelValue:{type:[Array,String,Number,Boolean,Object],default:void 0},autocomplete:{type:String,default:\"off\"},automaticDropdown:Boolean,size:So,effect:{type:we(String),default:\"light\"},disabled:Boolean,clearable:Boolean,filterable:Boolean,allowCreate:Boolean,loading:Boolean,popperClass:{type:String,default:\"\"},popperOptions:{type:we(Object),default:()=>({})},remote:Boolean,loadingText:String,noMatchText:String,noDataText:String,remoteMethod:Function,filterMethod:Function,multiple:Boolean,multipleLimit:{type:Number,default:0},placeholder:{type:String},defaultFirstOption:Boolean,reserveKeyword:{type:Boolean,default:!0},valueKey:{type:String,default:\"value\"},collapseTags:Boolean,collapseTagsTooltip:Boolean,maxCollapseTags:{type:Number,default:1},teleported:Gt.teleported,persistent:{type:Boolean,default:!0},clearIcon:{type:jt,default:Oc},fitInputWidth:Boolean,suffixIcon:{type:jt,default:Nm},tagType:{...ku.type,default:\"info\"},tagEffect:{...ku.effect,default:\"light\"},validateEvent:{type:Boolean,default:!0},remoteShowSuffix:Boolean,placement:{type:we(String),values:il,default:\"bottom-start\"},fallbackPlacements:{type:we(Array),default:[\"bottom-start\",\"top-start\",\"right\",\"left\"]},appendTo:String,...hg,...yr([\"ariaLabel\"])}),Bp=\"ElSelect\",L3=Z({name:Bp,componentName:Bp,components:{ElSelectMenu:R3,ElOption:Kc,ElOptions:P3,ElTag:Ux,ElScrollbar:U4,ElTooltip:Rg,ElIcon:Je},directives:{ClickOutside:_x},props:N3,emits:[rt,fo,\"remove-tag\",\"clear\",\"visible-change\",\"focus\",\"blur\"],setup(e,{emit:t}){const n=C(()=>{const{modelValue:i,multiple:a}=e,l=a?[]:void 0;return pe(i)?a?i:l:a?l:i}),r=wt({...vr(e),modelValue:n}),o=I3(r,t);ft(cl,wt({props:r,states:o.states,optionsArray:o.optionsArray,handleOptionSelect:o.handleOptionSelect,onOptionCreate:o.onOptionCreate,onOptionDestroy:o.onOptionDestroy,selectRef:o.selectRef,setSelected:o.setSelected}));const s=C(()=>e.multiple?o.states.selected.map(i=>i.currentLabel):o.states.selectedLabel);return{...o,modelValue:n,selectedLabel:s}}});function M3(e,t,n,r,o,s){const i=Yt(\"el-tag\"),a=Yt(\"el-tooltip\"),l=Yt(\"el-icon\"),u=Yt(\"el-option\"),c=Yt(\"el-options\"),f=Yt(\"el-scrollbar\"),d=Yt(\"el-select-menu\"),v=Jy(\"click-outside\");return ct((L(),ee(\"div\",{ref:\"selectRef\",class:U([e.nsSelect.b(),e.nsSelect.m(e.selectSize)]),[Ki(e.mouseEnterEventName)]:p=>e.states.inputHovering=!0,onMouseleave:p=>e.states.inputHovering=!1},[re(a,{ref:\"tooltipRef\",visible:e.dropdownMenuVisible,placement:e.placement,teleported:e.teleported,\"popper-class\":[e.nsSelect.e(\"popper\"),e.popperClass],\"popper-options\":e.popperOptions,\"fallback-placements\":e.fallbackPlacements,effect:e.effect,pure:\"\",trigger:\"click\",transition:`${e.nsSelect.namespace.value}-zoom-in-top`,\"stop-popper-mouse-event\":!1,\"gpu-acceleration\":!1,persistent:e.persistent,\"append-to\":e.appendTo,onBeforeShow:e.handleMenuEnter,onHide:p=>e.states.isBeforeHide=!1},{default:ce(()=>{var p;return[le(\"div\",{ref:\"wrapperRef\",class:U([e.nsSelect.e(\"wrapper\"),e.nsSelect.is(\"focused\",e.isFocused),e.nsSelect.is(\"hovering\",e.states.inputHovering),e.nsSelect.is(\"filterable\",e.filterable),e.nsSelect.is(\"disabled\",e.selectDisabled)]),onClick:tt(e.toggleMenu,[\"prevent\"])},[e.$slots.prefix?(L(),ee(\"div\",{key:0,ref:\"prefixRef\",class:U(e.nsSelect.e(\"prefix\"))},[de(e.$slots,\"prefix\")],2)):ae(\"v-if\",!0),le(\"div\",{ref:\"selectionRef\",class:U([e.nsSelect.e(\"selection\"),e.nsSelect.is(\"near\",e.multiple&&!e.$slots.prefix&&!!e.states.selected.length)])},[e.multiple?de(e.$slots,\"tag\",{key:0},()=>[(L(!0),ee(nt,null,vf(e.showTagList,h=>(L(),ee(\"div\",{key:e.getValueKey(h),class:U(e.nsSelect.e(\"selected-item\"))},[re(i,{closable:!e.selectDisabled&&!h.isDisabled,size:e.collapseTagSize,type:e.tagType,effect:e.tagEffect,\"disable-transitions\":\"\",style:ot(e.tagStyle),onClose:b=>e.deleteTag(b,h)},{default:ce(()=>[le(\"span\",{class:U(e.nsSelect.e(\"tags-text\"))},[de(e.$slots,\"label\",{label:h.currentLabel,value:h.value},()=>[Un(He(h.currentLabel),1)])],2)]),_:2},1032,[\"closable\",\"size\",\"type\",\"effect\",\"style\",\"onClose\"])],2))),128)),e.collapseTags&&e.states.selected.length>e.maxCollapseTags?(L(),fe(a,{key:0,ref:\"tagTooltipRef\",disabled:e.dropdownMenuVisible||!e.collapseTagsTooltip,\"fallback-placements\":[\"bottom\",\"top\",\"right\",\"left\"],effect:e.effect,placement:\"bottom\",teleported:e.teleported},{default:ce(()=>[le(\"div\",{ref:\"collapseItemRef\",class:U(e.nsSelect.e(\"selected-item\"))},[re(i,{closable:!1,size:e.collapseTagSize,type:e.tagType,effect:e.tagEffect,\"disable-transitions\":\"\",style:ot(e.collapseTagStyle)},{default:ce(()=>[le(\"span\",{class:U(e.nsSelect.e(\"tags-text\"))},\" + \"+He(e.states.selected.length-e.maxCollapseTags),3)]),_:1},8,[\"size\",\"type\",\"effect\",\"style\"])],2)]),content:ce(()=>[le(\"div\",{ref:\"tagMenuRef\",class:U(e.nsSelect.e(\"selection\"))},[(L(!0),ee(nt,null,vf(e.collapseTagList,h=>(L(),ee(\"div\",{key:e.getValueKey(h),class:U(e.nsSelect.e(\"selected-item\"))},[re(i,{class:\"in-tooltip\",closable:!e.selectDisabled&&!h.isDisabled,size:e.collapseTagSize,type:e.tagType,effect:e.tagEffect,\"disable-transitions\":\"\",onClose:b=>e.deleteTag(b,h)},{default:ce(()=>[le(\"span\",{class:U(e.nsSelect.e(\"tags-text\"))},[de(e.$slots,\"label\",{label:h.currentLabel,value:h.value},()=>[Un(He(h.currentLabel),1)])],2)]),_:2},1032,[\"closable\",\"size\",\"type\",\"effect\",\"onClose\"])],2))),128))],2)]),_:3},8,[\"disabled\",\"effect\",\"teleported\"])):ae(\"v-if\",!0)]):ae(\"v-if\",!0),e.selectDisabled?ae(\"v-if\",!0):(L(),ee(\"div\",{key:1,class:U([e.nsSelect.e(\"selected-item\"),e.nsSelect.e(\"input-wrapper\"),e.nsSelect.is(\"hidden\",!e.filterable)])},[ct(le(\"input\",{id:e.inputId,ref:\"inputRef\",\"onUpdate:modelValue\":h=>e.states.inputValue=h,type:\"text\",name:e.name,class:U([e.nsSelect.e(\"input\"),e.nsSelect.is(e.selectSize)]),disabled:e.selectDisabled,autocomplete:e.autocomplete,style:ot(e.inputStyle),role:\"combobox\",readonly:!e.filterable,spellcheck:\"false\",\"aria-activedescendant\":((p=e.hoverOption)==null?void 0:p.id)||\"\",\"aria-controls\":e.contentId,\"aria-expanded\":e.dropdownMenuVisible,\"aria-label\":e.ariaLabel,\"aria-autocomplete\":\"none\",\"aria-haspopup\":\"listbox\",onKeydown:[Vt(tt(h=>e.navigateOptions(\"next\"),[\"stop\",\"prevent\"]),[\"down\"]),Vt(tt(h=>e.navigateOptions(\"prev\"),[\"stop\",\"prevent\"]),[\"up\"]),Vt(tt(e.handleEsc,[\"stop\",\"prevent\"]),[\"esc\"]),Vt(tt(e.selectOption,[\"stop\",\"prevent\"]),[\"enter\"]),Vt(tt(e.deletePrevTag,[\"stop\"]),[\"delete\"])],onCompositionstart:e.handleCompositionStart,onCompositionupdate:e.handleCompositionUpdate,onCompositionend:e.handleCompositionEnd,onInput:e.onInput,onClick:tt(e.toggleMenu,[\"stop\"])},null,46,[\"id\",\"onUpdate:modelValue\",\"name\",\"disabled\",\"autocomplete\",\"readonly\",\"aria-activedescendant\",\"aria-controls\",\"aria-expanded\",\"aria-label\",\"onKeydown\",\"onCompositionstart\",\"onCompositionupdate\",\"onCompositionend\",\"onInput\",\"onClick\"]),[[fw,e.states.inputValue]]),e.filterable?(L(),ee(\"span\",{key:0,ref:\"calculatorRef\",\"aria-hidden\":\"true\",class:U(e.nsSelect.e(\"input-calculator\")),textContent:He(e.states.inputValue)},null,10,[\"textContent\"])):ae(\"v-if\",!0)],2)),e.shouldShowPlaceholder?(L(),ee(\"div\",{key:2,class:U([e.nsSelect.e(\"selected-item\"),e.nsSelect.e(\"placeholder\"),e.nsSelect.is(\"transparent\",!e.hasModelValue||e.expanded&&!e.states.inputValue)])},[e.hasModelValue?de(e.$slots,\"label\",{key:0,label:e.currentPlaceholder,value:e.modelValue},()=>[le(\"span\",null,He(e.currentPlaceholder),1)]):(L(),ee(\"span\",{key:1},He(e.currentPlaceholder),1))],2)):ae(\"v-if\",!0)],2),le(\"div\",{ref:\"suffixRef\",class:U(e.nsSelect.e(\"suffix\"))},[e.iconComponent&&!e.showClose?(L(),fe(l,{key:0,class:U([e.nsSelect.e(\"caret\"),e.nsSelect.e(\"icon\"),e.iconReverse])},{default:ce(()=>[(L(),fe(Xe(e.iconComponent)))]),_:1},8,[\"class\"])):ae(\"v-if\",!0),e.showClose&&e.clearIcon?(L(),fe(l,{key:1,class:U([e.nsSelect.e(\"caret\"),e.nsSelect.e(\"icon\"),e.nsSelect.e(\"clear\")]),onClick:e.handleClearClick},{default:ce(()=>[(L(),fe(Xe(e.clearIcon)))]),_:1},8,[\"class\",\"onClick\"])):ae(\"v-if\",!0),e.validateState&&e.validateIcon?(L(),fe(l,{key:2,class:U([e.nsInput.e(\"icon\"),e.nsInput.e(\"validateIcon\")])},{default:ce(()=>[(L(),fe(Xe(e.validateIcon)))]),_:1},8,[\"class\"])):ae(\"v-if\",!0)],2)],10,[\"onClick\"])]}),content:ce(()=>[re(d,{ref:\"menuRef\"},{default:ce(()=>[e.$slots.header?(L(),ee(\"div\",{key:0,class:U(e.nsSelect.be(\"dropdown\",\"header\")),onClick:tt(()=>{},[\"stop\"])},[de(e.$slots,\"header\")],10,[\"onClick\"])):ae(\"v-if\",!0),ct(re(f,{id:e.contentId,ref:\"scrollbarRef\",tag:\"ul\",\"wrap-class\":e.nsSelect.be(\"dropdown\",\"wrap\"),\"view-class\":e.nsSelect.be(\"dropdown\",\"list\"),class:U([e.nsSelect.is(\"empty\",e.filteredOptionsCount===0)]),role:\"listbox\",\"aria-label\":e.ariaLabel,\"aria-orientation\":\"vertical\"},{default:ce(()=>[e.showNewOption?(L(),fe(u,{key:0,value:e.states.inputValue,created:!0},null,8,[\"value\"])):ae(\"v-if\",!0),re(c,null,{default:ce(()=>[de(e.$slots,\"default\")]),_:3})]),_:3},8,[\"id\",\"wrap-class\",\"view-class\",\"class\",\"aria-label\"]),[[en,e.states.options.size>0&&!e.loading]]),e.$slots.loading&&e.loading?(L(),ee(\"div\",{key:1,class:U(e.nsSelect.be(\"dropdown\",\"loading\"))},[de(e.$slots,\"loading\")],2)):e.loading||e.filteredOptionsCount===0?(L(),ee(\"div\",{key:2,class:U(e.nsSelect.be(\"dropdown\",\"empty\"))},[de(e.$slots,\"empty\",{},()=>[le(\"span\",null,He(e.emptyText),1)])],2)):ae(\"v-if\",!0),e.$slots.footer?(L(),ee(\"div\",{key:3,class:U(e.nsSelect.be(\"dropdown\",\"footer\")),onClick:tt(()=>{},[\"stop\"])},[de(e.$slots,\"footer\")],10,[\"onClick\"])):ae(\"v-if\",!0)]),_:3},512)]),_:3},8,[\"visible\",\"placement\",\"teleported\",\"popper-class\",\"popper-options\",\"fallback-placements\",\"effect\",\"transition\",\"persistent\",\"append-to\",\"onBeforeShow\",\"onHide\"])],16,[\"onMouseleave\"])),[[v,e.handleClickOutside,e.popperRef]])}var $3=Ne(L3,[[\"render\",M3],[\"__file\",\"select.vue\"]]);const k3=Z({name:\"ElOptionGroup\",componentName:\"ElOptionGroup\",props:{label:String,disabled:Boolean},setup(e){const t=$e(\"select\"),n=V(null),r=Ge(),o=V([]);ft(Vg,wt({...vr(e)}));const s=C(()=>o.value.some(u=>u.visible===!0)),i=u=>{var c,f;return((c=u.type)==null?void 0:c.name)===\"ElOption\"&&!!((f=u.component)!=null&&f.proxy)},a=u=>{const c=yn(u),f=[];return c.forEach(d=>{var v,p;i(d)?f.push(d.component.proxy):(v=d.children)!=null&&v.length?f.push(...a(d.children)):(p=d.component)!=null&&p.subTree&&f.push(...a(d.component.subTree))}),f},l=()=>{o.value=a(r.subTree)};return Ke(()=>{l()}),X1(n,l,{attributes:!0,subtree:!0,childList:!0}),{groupRef:n,visible:s,ns:t}}});function F3(e,t,n,r,o,s){return ct((L(),ee(\"ul\",{ref:\"groupRef\",class:U(e.ns.be(\"group\",\"wrap\"))},[le(\"li\",{class:U(e.ns.be(\"group\",\"title\"))},He(e.label),3),le(\"li\",null,[le(\"ul\",{class:U(e.ns.b(\"group\"))},[de(e.$slots,\"default\")],2)])],2)),[[en,e.visible]])}var jg=Ne(k3,[[\"render\",F3],[\"__file\",\"option-group.vue\"]]);const FN=yt($3,{Option:Kc,OptionGroup:jg}),BN=wo(Kc);wo(jg);const B3=xe({trigger:ei.trigger,placement:Ul.placement,disabled:ei.disabled,visible:Gt.visible,transition:Gt.transition,popperOptions:Ul.popperOptions,tabindex:Ul.tabindex,content:Gt.content,popperStyle:Gt.popperStyle,popperClass:Gt.popperClass,enterable:{...Gt.enterable,default:!0},effect:{...Gt.effect,default:\"light\"},teleported:Gt.teleported,title:String,width:{type:[String,Number],default:150},offset:{type:Number,default:void 0},showAfter:{type:Number,default:0},hideAfter:{type:Number,default:200},autoClose:{type:Number,default:0},showArrow:{type:Boolean,default:!0},persistent:{type:Boolean,default:!0},\"onUpdate:visible\":{type:Function}}),D3={\"update:visible\":e=>Bt(e),\"before-enter\":()=>!0,\"before-leave\":()=>!0,\"after-enter\":()=>!0,\"after-leave\":()=>!0},V3=\"onUpdate:visible\",j3=Z({name:\"ElPopover\"}),z3=Z({...j3,props:B3,emits:D3,setup(e,{expose:t,emit:n}){const r=e,o=C(()=>r[V3]),s=$e(\"popover\"),i=V(),a=C(()=>{var b;return(b=g(i))==null?void 0:b.popperRef}),l=C(()=>[{width:pn(r.width)},r.popperStyle]),u=C(()=>[s.b(),r.popperClass,{[s.m(\"plain\")]:!!r.content}]),c=C(()=>r.transition===`${s.namespace.value}-fade-in-linear`),f=()=>{var b;(b=i.value)==null||b.hide()},d=()=>{n(\"before-enter\")},v=()=>{n(\"before-leave\")},p=()=>{n(\"after-enter\")},h=()=>{n(\"update:visible\",!1),n(\"after-leave\")};return t({popperRef:a,hide:f}),(b,m)=>(L(),fe(g(Rg),En({ref_key:\"tooltipRef\",ref:i},b.$attrs,{trigger:b.trigger,placement:b.placement,disabled:b.disabled,visible:b.visible,transition:b.transition,\"popper-options\":b.popperOptions,tabindex:b.tabindex,content:b.content,offset:b.offset,\"show-after\":b.showAfter,\"hide-after\":b.hideAfter,\"auto-close\":b.autoClose,\"show-arrow\":b.showArrow,\"aria-label\":b.title,effect:b.effect,enterable:b.enterable,\"popper-class\":g(u),\"popper-style\":g(l),teleported:b.teleported,persistent:b.persistent,\"gpu-acceleration\":g(c),\"onUpdate:visible\":g(o),onBeforeShow:d,onBeforeHide:v,onShow:p,onHide:h}),{content:ce(()=>[b.title?(L(),ee(\"div\",{key:0,class:U(g(s).e(\"title\")),role:\"title\"},He(b.title),3)):ae(\"v-if\",!0),de(b.$slots,\"default\",{},()=>[Un(He(b.content),1)])]),default:ce(()=>[b.$slots.reference?de(b.$slots,\"reference\",{key:0}):ae(\"v-if\",!0)]),_:3},16,[\"trigger\",\"placement\",\"disabled\",\"visible\",\"transition\",\"popper-options\",\"tabindex\",\"content\",\"offset\",\"show-after\",\"hide-after\",\"auto-close\",\"show-arrow\",\"aria-label\",\"effect\",\"enterable\",\"popper-class\",\"popper-style\",\"teleported\",\"persistent\",\"gpu-acceleration\",\"onUpdate:visible\"]))}});var H3=Ne(z3,[[\"__file\",\"popover.vue\"]]);const Dp=(e,t)=>{const n=t.arg||t.value,r=n==null?void 0:n.popperRef;r&&(r.triggerRef=e)};var U3={mounted(e,t){Dp(e,t)},updated(e,t){Dp(e,t)}};const K3=\"popover\",q3=KT(U3,K3),DN=yt(H3,{directive:q3}),W3=xe({modelValue:{type:[Boolean,String,Number],default:!1},disabled:Boolean,loading:Boolean,size:{type:String,validator:jm},width:{type:[String,Number],default:\"\"},inlinePrompt:Boolean,inactiveActionIcon:{type:jt},activeActionIcon:{type:jt},activeIcon:{type:jt},inactiveIcon:{type:jt},activeText:{type:String,default:\"\"},inactiveText:{type:String,default:\"\"},activeValue:{type:[Boolean,String,Number],default:!0},inactiveValue:{type:[Boolean,String,Number],default:!1},name:{type:String,default:\"\"},validateEvent:{type:Boolean,default:!0},beforeChange:{type:we(Function)},id:String,tabindex:{type:[String,Number]},...yr([\"ariaLabel\"])}),G3={[rt]:e=>Bt(e)||Ae(e)||je(e),[fo]:e=>Bt(e)||Ae(e)||je(e),[io]:e=>Bt(e)||Ae(e)||je(e)},zg=\"ElSwitch\",Y3=Z({name:zg}),J3=Z({...Y3,props:W3,emits:G3,setup(e,{expose:t,emit:n}){const r=e,{formItem:o}=Vr(),s=In(),i=$e(\"switch\"),{inputId:a}=hi(r,{formItemContext:o}),l=ns(C(()=>r.loading)),u=V(r.modelValue!==!1),c=V(),f=V(),d=C(()=>[i.b(),i.m(s.value),i.is(\"disabled\",l.value),i.is(\"checked\",m.value)]),v=C(()=>[i.e(\"label\"),i.em(\"label\",\"left\"),i.is(\"active\",!m.value)]),p=C(()=>[i.e(\"label\"),i.em(\"label\",\"right\"),i.is(\"active\",m.value)]),h=C(()=>({width:pn(r.width)}));he(()=>r.modelValue,()=>{u.value=!0});const b=C(()=>u.value?r.modelValue:!1),m=C(()=>b.value===r.activeValue);[r.activeValue,r.inactiveValue].includes(b.value)||(n(rt,r.inactiveValue),n(fo,r.inactiveValue),n(io,r.inactiveValue)),he(m,y=>{var A;c.value.checked=y,r.validateEvent&&((A=o==null?void 0:o.validate)==null||A.call(o,\"change\").catch(R=>void 0))});const S=()=>{const y=m.value?r.inactiveValue:r.activeValue;n(rt,y),n(fo,y),n(io,y),Ie(()=>{c.value.checked=m.value})},_=()=>{if(l.value)return;const{beforeChange:y}=r;if(!y){S();return}const A=y();[ia(A),Bt(A)].includes(!0)||Br(zg,\"beforeChange must return type `Promise<boolean>` or `boolean`\"),ia(A)?A.then(N=>{N&&S()}).catch(N=>{}):A&&S()},w=()=>{var y,A;(A=(y=c.value)==null?void 0:y.focus)==null||A.call(y)};return Ke(()=>{c.value.checked=m.value}),t({focus:w,checked:m}),(y,A)=>(L(),ee(\"div\",{class:U(g(d)),onClick:tt(_,[\"prevent\"])},[le(\"input\",{id:g(a),ref_key:\"input\",ref:c,class:U(g(i).e(\"input\")),type:\"checkbox\",role:\"switch\",\"aria-checked\":g(m),\"aria-disabled\":g(l),\"aria-label\":y.ariaLabel,name:y.name,\"true-value\":y.activeValue,\"false-value\":y.inactiveValue,disabled:g(l),tabindex:y.tabindex,onChange:S,onKeydown:Vt(_,[\"enter\"])},null,42,[\"id\",\"aria-checked\",\"aria-disabled\",\"aria-label\",\"name\",\"true-value\",\"false-value\",\"disabled\",\"tabindex\",\"onKeydown\"]),!y.inlinePrompt&&(y.inactiveIcon||y.inactiveText)?(L(),ee(\"span\",{key:0,class:U(g(v))},[y.inactiveIcon?(L(),fe(g(Je),{key:0},{default:ce(()=>[(L(),fe(Xe(y.inactiveIcon)))]),_:1})):ae(\"v-if\",!0),!y.inactiveIcon&&y.inactiveText?(L(),ee(\"span\",{key:1,\"aria-hidden\":g(m)},He(y.inactiveText),9,[\"aria-hidden\"])):ae(\"v-if\",!0)],2)):ae(\"v-if\",!0),le(\"span\",{ref_key:\"core\",ref:f,class:U(g(i).e(\"core\")),style:ot(g(h))},[y.inlinePrompt?(L(),ee(\"div\",{key:0,class:U(g(i).e(\"inner\"))},[y.activeIcon||y.inactiveIcon?(L(),fe(g(Je),{key:0,class:U(g(i).is(\"icon\"))},{default:ce(()=>[(L(),fe(Xe(g(m)?y.activeIcon:y.inactiveIcon)))]),_:1},8,[\"class\"])):y.activeText||y.inactiveText?(L(),ee(\"span\",{key:1,class:U(g(i).is(\"text\")),\"aria-hidden\":!g(m)},He(g(m)?y.activeText:y.inactiveText),11,[\"aria-hidden\"])):ae(\"v-if\",!0)],2)):ae(\"v-if\",!0),le(\"div\",{class:U(g(i).e(\"action\"))},[y.loading?(L(),fe(g(Je),{key:0,class:U(g(i).is(\"loading\"))},{default:ce(()=>[re(g(Js))]),_:1},8,[\"class\"])):g(m)?de(y.$slots,\"active-action\",{key:1},()=>[y.activeActionIcon?(L(),fe(g(Je),{key:0},{default:ce(()=>[(L(),fe(Xe(y.activeActionIcon)))]),_:1})):ae(\"v-if\",!0)]):g(m)?ae(\"v-if\",!0):de(y.$slots,\"inactive-action\",{key:2},()=>[y.inactiveActionIcon?(L(),fe(g(Je),{key:0},{default:ce(()=>[(L(),fe(Xe(y.inactiveActionIcon)))]),_:1})):ae(\"v-if\",!0)])],2)],6),!y.inlinePrompt&&(y.activeIcon||y.activeText)?(L(),ee(\"span\",{key:1,class:U(g(p))},[y.activeIcon?(L(),fe(g(Je),{key:0},{default:ce(()=>[(L(),fe(Xe(y.activeIcon)))]),_:1})):ae(\"v-if\",!0),!y.activeIcon&&y.activeText?(L(),ee(\"span\",{key:1,\"aria-hidden\":!g(m)},He(y.activeText),9,[\"aria-hidden\"])):ae(\"v-if\",!0)],2)):ae(\"v-if\",!0)],10,[\"onClick\"]))}});var X3=Ne(J3,[[\"__file\",\"switch.vue\"]]);const VN=yt(X3),fl=Symbol(\"tabsRootContextKey\"),Z3=xe({tabs:{type:we(Array),default:()=>ol([])}}),Hg=\"ElTabBar\",Q3=Z({name:Hg}),eI=Z({...Q3,props:Z3,setup(e,{expose:t}){const n=e,r=Ge(),o=Ee(fl);o||Br(Hg,\"<el-tabs><el-tab-bar /></el-tabs>\");const s=$e(\"tabs\"),i=V(),a=V(),l=()=>{let v=0,p=0;const h=[\"top\",\"bottom\"].includes(o.props.tabPosition)?\"width\":\"height\",b=h===\"width\"?\"x\":\"y\",m=b===\"x\"?\"left\":\"top\";return n.tabs.every(S=>{var _,w;const y=(w=(_=r.parent)==null?void 0:_.refs)==null?void 0:w[`tab-${S.uid}`];if(!y)return!1;if(!S.active)return!0;v=y[`offset${Pr(m)}`],p=y[`client${Pr(h)}`];const A=window.getComputedStyle(y);return h===\"width\"&&(p-=Number.parseFloat(A.paddingLeft)+Number.parseFloat(A.paddingRight),v+=Number.parseFloat(A.paddingLeft)),!1}),{[h]:`${p}px`,transform:`translate${Pr(b)}(${v}px)`}},u=()=>a.value=l(),c=[],f=()=>{var v;c.forEach(h=>h.stop()),c.length=0;const p=(v=r.parent)==null?void 0:v.refs;if(p){for(const h in p)if(h.startsWith(\"tab-\")){const b=p[h];b&&c.push(Dt(b,u))}}};he(()=>n.tabs,async()=>{await Ie(),u(),f()},{immediate:!0});const d=Dt(i,()=>u());return St(()=>{c.forEach(v=>v.stop()),c.length=0,d.stop()}),t({ref:i,update:u}),(v,p)=>(L(),ee(\"div\",{ref_key:\"barRef\",ref:i,class:U([g(s).e(\"active-bar\"),g(s).is(g(o).props.tabPosition)]),style:ot(a.value)},null,6))}});var tI=Ne(eI,[[\"__file\",\"tab-bar.vue\"]]);const nI=xe({panes:{type:we(Array),default:()=>ol([])},currentName:{type:[String,Number],default:\"\"},editable:Boolean,type:{type:String,values:[\"card\",\"border-card\",\"\"],default:\"\"},stretch:Boolean}),rI={tabClick:(e,t,n)=>n instanceof Event,tabRemove:(e,t)=>t instanceof Event},Vp=\"ElTabNav\",oI=Z({name:Vp,props:nI,emits:rI,setup(e,{expose:t,emit:n}){const r=Ee(fl);r||Br(Vp,\"<el-tabs><tab-nav /></el-tabs>\");const o=$e(\"tabs\"),s=U1(),i=rS(),a=V(),l=V(),u=V(),c=V(),f=V(!1),d=V(0),v=V(!1),p=V(!0),h=C(()=>[\"top\",\"bottom\"].includes(r.props.tabPosition)?\"width\":\"height\"),b=C(()=>({transform:`translate${h.value===\"width\"?\"X\":\"Y\"}(-${d.value}px)`})),m=()=>{if(!a.value)return;const N=a.value[`offset${Pr(h.value)}`],I=d.value;if(!I)return;const x=I>N?I-N:0;d.value=x},S=()=>{if(!a.value||!l.value)return;const N=l.value[`offset${Pr(h.value)}`],I=a.value[`offset${Pr(h.value)}`],x=d.value;if(N-x<=I)return;const k=N-x>I*2?x+I:N-I;d.value=k},_=async()=>{const N=l.value;if(!f.value||!u.value||!a.value||!N)return;await Ie();const I=u.value.querySelector(\".is-active\");if(!I)return;const x=a.value,k=[\"top\",\"bottom\"].includes(r.props.tabPosition),$=I.getBoundingClientRect(),F=x.getBoundingClientRect(),Y=k?N.offsetWidth-F.width:N.offsetHeight-F.height,P=d.value;let O=P;k?($.left<F.left&&(O=P-(F.left-$.left)),$.right>F.right&&(O=P+$.right-F.right)):($.top<F.top&&(O=P-(F.top-$.top)),$.bottom>F.bottom&&(O=P+($.bottom-F.bottom))),O=Math.max(O,0),d.value=Math.min(O,Y)},w=()=>{var N;if(!l.value||!a.value)return;e.stretch&&((N=c.value)==null||N.update());const I=l.value[`offset${Pr(h.value)}`],x=a.value[`offset${Pr(h.value)}`],k=d.value;x<I?(f.value=f.value||{},f.value.prev=k,f.value.next=k+x<I,I-k<x&&(d.value=I-x)):(f.value=!1,k>0&&(d.value=0))},y=N=>{const I=N.code,{up:x,down:k,left:$,right:F}=_n;if(![x,k,$,F].includes(I))return;const Y=Array.from(N.currentTarget.querySelectorAll(\"[role=tab]:not(.is-disabled)\")),P=Y.indexOf(N.target);let O;I===$||I===x?P===0?O=Y.length-1:O=P-1:P<Y.length-1?O=P+1:O=0,Y[O].focus({preventScroll:!0}),Y[O].click(),A()},A=()=>{p.value&&(v.value=!0)},R=()=>v.value=!1;return he(s,N=>{N===\"hidden\"?p.value=!1:N===\"visible\"&&setTimeout(()=>p.value=!0,50)}),he(i,N=>{N?setTimeout(()=>p.value=!0,50):p.value=!1}),Dt(u,w),Ke(()=>setTimeout(()=>_(),0)),mo(()=>w()),t({scrollToActiveTab:_,removeFocus:R}),()=>{const N=f.value?[re(\"span\",{class:[o.e(\"nav-prev\"),o.is(\"disabled\",!f.value.prev)],onClick:m},[re(Je,null,{default:()=>[re(hT,null,null)]})]),re(\"span\",{class:[o.e(\"nav-next\"),o.is(\"disabled\",!f.value.next)],onClick:S},[re(Je,null,{default:()=>[re(mT,null,null)]})])]:null,I=e.panes.map((x,k)=>{var $,F,Y,P;const O=x.uid,j=x.props.disabled,Q=(F=($=x.props.name)!=null?$:x.index)!=null?F:`${k}`,me=!j&&(x.isClosable||e.editable);x.index=`${k}`;const Pe=me?re(Je,{class:\"is-icon-close\",onClick:_e=>n(\"tabRemove\",x,_e)},{default:()=>[re(Ys,null,null)]}):null,Re=((P=(Y=x.slots).label)==null?void 0:P.call(Y))||x.props.label,Ce=!j&&x.active?0:-1;return re(\"div\",{ref:`tab-${O}`,class:[o.e(\"item\"),o.is(r.props.tabPosition),o.is(\"active\",x.active),o.is(\"disabled\",j),o.is(\"closable\",me),o.is(\"focus\",v.value)],id:`tab-${Q}`,key:`tab-${O}`,\"aria-controls\":`pane-${Q}`,role:\"tab\",\"aria-selected\":x.active,tabindex:Ce,onFocus:()=>A(),onBlur:()=>R(),onClick:_e=>{R(),n(\"tabClick\",x,Q,_e)},onKeydown:_e=>{me&&(_e.code===_n.delete||_e.code===_n.backspace)&&n(\"tabRemove\",x,_e)}},[Re,Pe])});return re(\"div\",{ref:u,class:[o.e(\"nav-wrap\"),o.is(\"scrollable\",!!f.value),o.is(r.props.tabPosition)]},[N,re(\"div\",{class:o.e(\"nav-scroll\"),ref:a},[re(\"div\",{class:[o.e(\"nav\"),o.is(r.props.tabPosition),o.is(\"stretch\",e.stretch&&[\"top\",\"bottom\"].includes(r.props.tabPosition))],ref:l,style:b.value,role:\"tablist\",onKeydown:y},[e.type?null:re(tI,{ref:c,tabs:[...e.panes]},null),I])])])}}}),sI=xe({type:{type:String,values:[\"card\",\"border-card\",\"\"],default:\"\"},closable:Boolean,addable:Boolean,modelValue:{type:[String,Number]},editable:Boolean,tabPosition:{type:String,values:[\"top\",\"right\",\"bottom\",\"left\"],default:\"top\"},beforeLeave:{type:we(Function),default:()=>!0},stretch:Boolean}),Kl=e=>Ae(e)||je(e),iI={[rt]:e=>Kl(e),tabClick:(e,t)=>t instanceof Event,tabChange:e=>Kl(e),edit:(e,t)=>[\"remove\",\"add\"].includes(t),tabRemove:e=>Kl(e),tabAdd:()=>!0},aI=Z({name:\"ElTabs\",props:sI,emits:iI,setup(e,{emit:t,slots:n,expose:r}){var o;const s=$e(\"tabs\"),i=C(()=>[\"left\",\"right\"].includes(e.tabPosition)),{children:a,addChild:l,removeChild:u}=yA(Ge(),\"ElTabPane\"),c=V(),f=V((o=e.modelValue)!=null?o:\"0\"),d=async(m,S=!1)=>{var _,w,y;if(!(f.value===m||Lt(m)))try{await((_=e.beforeLeave)==null?void 0:_.call(e,m,f.value))!==!1&&(f.value=m,S&&(t(rt,m),t(\"tabChange\",m)),(y=(w=c.value)==null?void 0:w.removeFocus)==null||y.call(w))}catch{}},v=(m,S,_)=>{m.props.disabled||(d(S,!0),t(\"tabClick\",m,_))},p=(m,S)=>{m.props.disabled||Lt(m.props.name)||(S.stopPropagation(),t(\"edit\",m.props.name,\"remove\"),t(\"tabRemove\",m.props.name))},h=()=>{t(\"edit\",void 0,\"add\"),t(\"tabAdd\")};he(()=>e.modelValue,m=>d(m)),he(f,async()=>{var m;await Ie(),(m=c.value)==null||m.scrollToActiveTab()}),ft(fl,{props:e,currentName:f,registerPane:m=>{a.value.push(m)},sortPane:l,unregisterPane:u}),r({currentName:f});const b=({render:m})=>m();return()=>{const m=n[\"add-icon\"],S=e.editable||e.addable?re(\"div\",{class:[s.e(\"new-tab\"),i.value&&s.e(\"new-tab-vertical\")],tabindex:\"0\",onClick:h,onKeydown:y=>{y.code===_n.enter&&h()}},[m?de(n,\"add-icon\"):re(Je,{class:s.is(\"icon-plus\")},{default:()=>[re($m,null,null)]})]):null,_=re(\"div\",{class:[s.e(\"header\"),i.value&&s.e(\"header-vertical\"),s.is(e.tabPosition)]},[re(b,{render:()=>{const y=a.value.some(A=>A.slots.label);return re(oI,{ref:c,currentName:f.value,editable:e.editable,type:e.type,panes:a.value,stretch:e.stretch,onTabClick:v,onTabRemove:p},{$stable:!y})}},null),S]),w=re(\"div\",{class:s.e(\"content\")},[de(n,\"default\")]);return re(\"div\",{class:[s.b(),s.m(e.tabPosition),{[s.m(\"card\")]:e.type===\"card\",[s.m(\"border-card\")]:e.type===\"border-card\"}]},[w,_])}}}),lI=xe({label:{type:String,default:\"\"},name:{type:[String,Number]},closable:Boolean,disabled:Boolean,lazy:Boolean}),Ug=\"ElTabPane\",uI=Z({name:Ug}),cI=Z({...uI,props:lI,setup(e){const t=e,n=Ge(),r=go(),o=Ee(fl);o||Br(Ug,\"usage: <el-tabs><el-tab-pane /></el-tabs/>\");const s=$e(\"tab-pane\"),i=V(),a=C(()=>t.closable||o.props.closable),l=fd(()=>{var v;return o.currentName.value===((v=t.name)!=null?v:i.value)}),u=V(l.value),c=C(()=>{var v;return(v=t.name)!=null?v:i.value}),f=fd(()=>!t.lazy||u.value||l.value);he(l,v=>{v&&(u.value=!0)});const d=wt({uid:n.uid,slots:r,props:t,paneName:c,active:l,index:i,isClosable:a});return o.registerPane(d),Ke(()=>{o.sortPane(d)}),kr(()=>{o.unregisterPane(d.uid)}),(v,p)=>g(f)?ct((L(),ee(\"div\",{key:0,id:`pane-${g(c)}`,class:U(g(s).b()),role:\"tabpanel\",\"aria-hidden\":!g(l),\"aria-labelledby\":`tab-${g(c)}`},[de(v.$slots,\"default\")],10,[\"id\",\"aria-hidden\",\"aria-labelledby\"])),[[en,g(l)]]):ae(\"v-if\",!0)}});var Kg=Ne(cI,[[\"__file\",\"tab-pane.vue\"]]);const jN=yt(aI,{TabPane:Kg}),zN=wo(Kg),fI=xe({type:{type:String,values:[\"primary\",\"success\",\"info\",\"warning\",\"danger\",\"\"],default:\"\"},size:{type:String,values:es,default:\"\"},truncated:Boolean,lineClamp:{type:[String,Number]},tag:{type:String,default:\"span\"}}),dI=Z({name:\"ElText\"}),pI=Z({...dI,props:fI,setup(e){const t=e,n=In(),r=$e(\"text\"),o=C(()=>[r.b(),r.m(t.type),r.m(n.value),r.is(\"truncated\",t.truncated),r.is(\"line-clamp\",!Lt(t.lineClamp))]);return(s,i)=>(L(),fe(Xe(s.tag),{class:U(g(o)),style:ot({\"-webkit-line-clamp\":s.lineClamp})},{default:ce(()=>[de(s.$slots,\"default\")]),_:3},8,[\"class\",\"style\"]))}});var hI=Ne(pI,[[\"__file\",\"text.vue\"]]);const HN=yt(hI);function vI(e){let t;const n=V(!1),r=wt({...e,originalPosition:\"\",originalOverflow:\"\",visible:!1});function o(d){r.text=d}function s(){const d=r.parent,v=f.ns;if(!d.vLoadingAddClassList){let p=d.getAttribute(\"loading-number\");p=Number.parseInt(p)-1,p?d.setAttribute(\"loading-number\",p.toString()):(Gs(d,v.bm(\"parent\",\"relative\")),d.removeAttribute(\"loading-number\")),Gs(d,v.bm(\"parent\",\"hidden\"))}i(),c.unmount()}function i(){var d,v;(v=(d=f.$el)==null?void 0:d.parentNode)==null||v.removeChild(f.$el)}function a(){var d;e.beforeClose&&!e.beforeClose()||(n.value=!0,clearTimeout(t),t=setTimeout(l,400),r.visible=!1,(d=e.closed)==null||d.call(e))}function l(){if(!n.value)return;const d=r.parent;n.value=!1,d.vLoadingAddClassList=void 0,s()}const c=gw(Z({name:\"ElLoading\",setup(d,{expose:v}){const{ns:p,zIndex:h}=Bc(\"loading\");return v({ns:p,zIndex:h}),()=>{const b=r.spinner||r.svg,m=or(\"svg\",{class:\"circular\",viewBox:r.svgViewBox?r.svgViewBox:\"0 0 50 50\",...b?{innerHTML:b}:{}},[or(\"circle\",{class:\"path\",cx:\"25\",cy:\"25\",r:\"20\",fill:\"none\"})]),S=r.text?or(\"p\",{class:p.b(\"text\")},[r.text]):void 0;return or(Fr,{name:p.b(\"fade\"),onAfterLeave:l},{default:ce(()=>[ct(re(\"div\",{style:{backgroundColor:r.background||\"\"},class:[p.b(\"mask\"),r.customClass,r.fullscreen?\"is-fullscreen\":\"\"]},[or(\"div\",{class:p.b(\"spinner\")},[m,S])]),[[en,r.visible]])])})}}})),f=c.mount(document.createElement(\"div\"));return{...vr(r),setText:o,removeElLoadingChild:i,close:a,handleAfterLeave:l,vm:f,get $el(){return f.$el}}}let Bi;const Fu=function(e={}){if(!st)return;const t=mI(e);if(t.fullscreen&&Bi)return Bi;const n=vI({...t,closed:()=>{var o;(o=t.closed)==null||o.call(t),t.fullscreen&&(Bi=void 0)}});gI(t,t.parent,n),jp(t,t.parent,n),t.parent.vLoadingAddClassList=()=>jp(t,t.parent,n);let r=t.parent.getAttribute(\"loading-number\");return r?r=`${Number.parseInt(r)+1}`:r=\"1\",t.parent.setAttribute(\"loading-number\",r),t.parent.appendChild(n.$el),Ie(()=>n.visible.value=t.visible),t.fullscreen&&(Bi=n),n},mI=e=>{var t,n,r,o;let s;return Ae(e.target)?s=(t=document.querySelector(e.target))!=null?t:document.body:s=e.target||document.body,{parent:s===document.body||e.body?document.body:s,background:e.background||\"\",svg:e.svg||\"\",svgViewBox:e.svgViewBox||\"\",spinner:e.spinner||!1,text:e.text||\"\",fullscreen:s===document.body&&((n=e.fullscreen)!=null?n:!0),lock:(r=e.lock)!=null?r:!1,customClass:e.customClass||\"\",visible:(o=e.visible)!=null?o:!0,beforeClose:e.beforeClose,closed:e.closed,target:s}},gI=async(e,t,n)=>{const{nextZIndex:r}=n.vm.zIndex||n.vm._.exposed.zIndex,o={};if(e.fullscreen)n.originalPosition.value=Po(document.body,\"position\"),n.originalOverflow.value=Po(document.body,\"overflow\"),o.zIndex=r();else if(e.parent===document.body){n.originalPosition.value=Po(document.body,\"position\"),await Ie();for(const s of[\"top\",\"left\"]){const i=s===\"top\"?\"scrollTop\":\"scrollLeft\";o[s]=`${e.target.getBoundingClientRect()[s]+document.body[i]+document.documentElement[i]-Number.parseInt(Po(document.body,`margin-${s}`),10)}px`}for(const s of[\"height\",\"width\"])o[s]=`${e.target.getBoundingClientRect()[s]}px`}else n.originalPosition.value=Po(t,\"position\");for(const[s,i]of Object.entries(o))n.$el.style[s]=i},jp=(e,t,n)=>{const r=n.vm.ns||n.vm._.exposed.ns;[\"absolute\",\"fixed\",\"sticky\"].includes(n.originalPosition.value)?Gs(t,r.bm(\"parent\",\"relative\")):Tu(t,r.bm(\"parent\",\"relative\")),e.fullscreen&&e.lock?Tu(t,r.bm(\"parent\",\"hidden\")):Gs(t,r.bm(\"parent\",\"hidden\"))},na=Symbol(\"ElLoading\"),zp=(e,t)=>{var n,r,o,s;const i=t.instance,a=d=>Oe(t.value)?t.value[d]:void 0,l=d=>{const v=Ae(d)&&(i==null?void 0:i[d])||d;return v&&V(v)},u=d=>l(a(d)||e.getAttribute(`element-loading-${hr(d)}`)),c=(n=a(\"fullscreen\"))!=null?n:t.modifiers.fullscreen,f={text:u(\"text\"),svg:u(\"svg\"),svgViewBox:u(\"svgViewBox\"),spinner:u(\"spinner\"),background:u(\"background\"),customClass:u(\"customClass\"),fullscreen:c,target:(r=a(\"target\"))!=null?r:c?void 0:e,body:(o=a(\"body\"))!=null?o:t.modifiers.body,lock:(s=a(\"lock\"))!=null?s:t.modifiers.lock};e[na]={options:f,instance:Fu(f)}},bI=(e,t)=>{for(const n of Object.keys(t))Ue(t[n])&&(t[n].value=e[n])},Hp={mounted(e,t){t.value&&zp(e,t)},updated(e,t){const n=e[na];t.oldValue!==t.value&&(t.value&&!t.oldValue?zp(e,t):t.value&&t.oldValue?Oe(t.value)&&bI(t.value,n.options):n==null||n.instance.close())},unmounted(e){var t;(t=e[na])==null||t.instance.close(),e[na]=null}},UN={install(e){e.directive(\"loading\",Hp),e.config.globalProperties.$loading=Fu},directive:Hp,service:Fu},qg=[\"success\",\"info\",\"warning\",\"error\"],At=ol({customClass:\"\",center:!1,dangerouslyUseHTMLString:!1,duration:3e3,icon:void 0,id:\"\",message:\"\",onClose:void 0,showClose:!1,type:\"info\",plain:!1,offset:16,zIndex:0,grouping:!1,repeatNum:1,appendTo:st?document.body:void 0}),yI=xe({customClass:{type:String,default:At.customClass},center:{type:Boolean,default:At.center},dangerouslyUseHTMLString:{type:Boolean,default:At.dangerouslyUseHTMLString},duration:{type:Number,default:At.duration},icon:{type:jt,default:At.icon},id:{type:String,default:At.id},message:{type:we([String,Object,Function]),default:At.message},onClose:{type:we(Function),default:At.onClose},showClose:{type:Boolean,default:At.showClose},type:{type:String,values:qg,default:At.type},plain:{type:Boolean,default:At.plain},offset:{type:Number,default:At.offset},zIndex:{type:Number,default:At.zIndex},grouping:{type:Boolean,default:At.grouping},repeatNum:{type:Number,default:At.repeatNum}}),wI={destroy:()=>!0},wn=Qu([]),SI=e=>{const t=wn.findIndex(o=>o.id===e),n=wn[t];let r;return t>0&&(r=wn[t-1]),{current:n,prev:r}},EI=e=>{const{prev:t}=SI(e);return t?t.vm.exposed.bottom.value:0},_I=(e,t)=>wn.findIndex(r=>r.id===e)>0?16:t,CI=Z({name:\"ElMessage\"}),TI=Z({...CI,props:yI,emits:wI,setup(e,{expose:t}){const n=e,{Close:r}=Dm,{ns:o,zIndex:s}=Bc(\"message\"),{currentZIndex:i,nextZIndex:a}=s,l=V(),u=V(!1),c=V(0);let f;const d=C(()=>n.type?n.type===\"error\"?\"danger\":n.type:\"info\"),v=C(()=>{const R=n.type;return{[o.bm(\"icon\",R)]:R&&Ra[R]}}),p=C(()=>n.icon||Ra[n.type]||\"\"),h=C(()=>EI(n.id)),b=C(()=>_I(n.id,n.offset)+h.value),m=C(()=>c.value+b.value),S=C(()=>({top:`${b.value}px`,zIndex:i.value}));function _(){n.duration!==0&&({stop:f}=bu(()=>{y()},n.duration))}function w(){f==null||f()}function y(){u.value=!1}function A({code:R}){R===_n.esc&&y()}return Ke(()=>{_(),a(),u.value=!0}),he(()=>n.repeatNum,()=>{w(),_()}),tn(document,\"keydown\",A),Dt(l,()=>{c.value=l.value.getBoundingClientRect().height}),t({visible:u,bottom:m,close:y}),(R,N)=>(L(),fe(Fr,{name:g(o).b(\"fade\"),onBeforeLeave:R.onClose,onAfterLeave:I=>R.$emit(\"destroy\"),persisted:\"\"},{default:ce(()=>[ct(le(\"div\",{id:R.id,ref_key:\"messageRef\",ref:l,class:U([g(o).b(),{[g(o).m(R.type)]:R.type},g(o).is(\"center\",R.center),g(o).is(\"closable\",R.showClose),g(o).is(\"plain\",R.plain),R.customClass]),style:ot(g(S)),role:\"alert\",onMouseenter:w,onMouseleave:_},[R.repeatNum>1?(L(),fe(g(XR),{key:0,value:R.repeatNum,type:g(d),class:U(g(o).e(\"badge\"))},null,8,[\"value\",\"type\",\"class\"])):ae(\"v-if\",!0),g(p)?(L(),fe(g(Je),{key:1,class:U([g(o).e(\"icon\"),g(v)])},{default:ce(()=>[(L(),fe(Xe(g(p))))]),_:1},8,[\"class\"])):ae(\"v-if\",!0),de(R.$slots,\"default\",{},()=>[R.dangerouslyUseHTMLString?(L(),ee(nt,{key:1},[ae(\" Caution here, message could've been compromised, never use user's input as message \"),le(\"p\",{class:U(g(o).e(\"content\")),innerHTML:R.message},null,10,[\"innerHTML\"])],2112)):(L(),ee(\"p\",{key:0,class:U(g(o).e(\"content\"))},He(R.message),3))]),R.showClose?(L(),fe(g(Je),{key:2,class:U(g(o).e(\"closeBtn\")),onClick:tt(y,[\"stop\"])},{default:ce(()=>[re(g(r))]),_:1},8,[\"class\",\"onClick\"])):ae(\"v-if\",!0)],46,[\"id\"]),[[en,u.value]])]),_:3},8,[\"name\",\"onBeforeLeave\",\"onAfterLeave\"]))}});var OI=Ne(TI,[[\"__file\",\"message.vue\"]]);let AI=1;const Wg=e=>{const t=!e||Ae(e)||cn(e)||ve(e)?{message:e}:e,n={...At,...t};if(!n.appendTo)n.appendTo=document.body;else if(Ae(n.appendTo)){let r=document.querySelector(n.appendTo);ar(r)||(r=document.body),n.appendTo=r}return Bt(Fn.grouping)&&!n.grouping&&(n.grouping=Fn.grouping),je(Fn.duration)&&n.duration===3e3&&(n.duration=Fn.duration),je(Fn.offset)&&n.offset===16&&(n.offset=Fn.offset),Bt(Fn.showClose)&&!n.showClose&&(n.showClose=Fn.showClose),n},RI=e=>{const t=wn.indexOf(e);if(t===-1)return;wn.splice(t,1);const{handler:n}=e;n.close()},xI=({appendTo:e,...t},n)=>{const r=`message_${AI++}`,o=t.onClose,s=document.createElement(\"div\"),i={...t,id:r,onClose:()=>{o==null||o(),RI(c)},onDestroy:()=>{wa(null,s)}},a=re(OI,i,ve(i.message)||cn(i.message)?{default:ve(i.message)?i.message:()=>i.message}:null);a.appContext=n||Go._context,wa(a,s),e.appendChild(s.firstElementChild);const l=a.component,c={id:r,vnode:a,vm:l,handler:{close:()=>{l.exposed.visible.value=!1}},props:a.component.props};return c},Go=(e={},t)=>{if(!st)return{close:()=>{}};const n=Wg(e);if(n.grouping&&wn.length){const o=wn.find(({vnode:s})=>{var i;return((i=s.props)==null?void 0:i.message)===n.message});if(o)return o.props.repeatNum+=1,o.props.type=n.type,o.handler}if(je(Fn.max)&&wn.length>=Fn.max)return{close:()=>{}};const r=xI(n,t);return wn.push(r),r.handler};qg.forEach(e=>{Go[e]=(t={},n)=>{const r=Wg(t);return Go({...r,type:e},n)}});function II(e){for(const t of wn)(!e||e===t.props.type)&&t.handler.close()}Go.closeAll=II;Go._context=null;const KN=UT(Go,\"$message\"),PI=Z({name:\"ElMessageBox\",directives:{TrapFocus:Ox},components:{ElButton:Ex,ElFocusTrap:Hc,ElInput:bg,ElOverlay:Fg,ElIcon:Je,...Dm},inheritAttrs:!1,props:{buttonSize:{type:String,validator:jm},modal:{type:Boolean,default:!0},lockScroll:{type:Boolean,default:!0},showClose:{type:Boolean,default:!0},closeOnClickModal:{type:Boolean,default:!0},closeOnPressEscape:{type:Boolean,default:!0},closeOnHashChange:{type:Boolean,default:!0},center:Boolean,draggable:Boolean,overflow:Boolean,roundButton:{default:!1,type:Boolean},container:{type:String,default:\"body\"},boxType:{type:String,default:\"\"}},emits:[\"vanish\",\"action\"],setup(e,{emit:t}){const{locale:n,zIndex:r,ns:o,size:s}=Bc(\"message-box\",C(()=>e.buttonSize)),{t:i}=n,{nextZIndex:a}=r,l=V(!1),u=wt({autofocus:!0,beforeClose:null,callback:null,cancelButtonText:\"\",cancelButtonClass:\"\",confirmButtonText:\"\",confirmButtonClass:\"\",customClass:\"\",customStyle:{},dangerouslyUseHTMLString:!1,distinguishCancelAndClose:!1,icon:\"\",inputPattern:null,inputPlaceholder:\"\",inputType:\"text\",inputValue:null,inputValidator:null,inputErrorMessage:\"\",message:null,modalFade:!0,modalClass:\"\",showCancelButton:!1,showConfirmButton:!0,type:\"\",title:void 0,showInput:!1,action:\"\",confirmButtonLoading:!1,cancelButtonLoading:!1,confirmButtonLoadingIcon:Ds(Js),cancelButtonLoadingIcon:Ds(Js),confirmButtonDisabled:!1,editorErrorMessage:\"\",validateError:!1,zIndex:a()}),c=C(()=>{const O=u.type;return{[o.bm(\"icon\",O)]:O&&Ra[O]}}),f=pr(),d=pr(),v=C(()=>u.icon||Ra[u.type]||\"\"),p=C(()=>!!u.message),h=V(),b=V(),m=V(),S=V(),_=V(),w=C(()=>u.confirmButtonClass);he(()=>u.inputValue,async O=>{await Ie(),e.boxType===\"prompt\"&&O!==null&&$()},{immediate:!0}),he(()=>l.value,O=>{var j,Q;O&&(e.boxType!==\"prompt\"&&(u.autofocus?m.value=(Q=(j=_.value)==null?void 0:j.$el)!=null?Q:h.value:m.value=h.value),u.zIndex=a()),e.boxType===\"prompt\"&&(O?Ie().then(()=>{var me;S.value&&S.value.$el&&(u.autofocus?m.value=(me=F())!=null?me:h.value:m.value=h.value)}):(u.editorErrorMessage=\"\",u.validateError=!1))});const y=C(()=>e.draggable),A=C(()=>e.overflow);zm(h,b,y,A),Ke(async()=>{await Ie(),e.closeOnHashChange&&window.addEventListener(\"hashchange\",R)}),St(()=>{e.closeOnHashChange&&window.removeEventListener(\"hashchange\",R)});function R(){l.value&&(l.value=!1,Ie(()=>{u.action&&t(\"action\",u.action)}))}const N=()=>{e.closeOnClickModal&&k(u.distinguishCancelAndClose?\"close\":\"cancel\")},I=kc(N),x=O=>{if(u.inputType!==\"textarea\")return O.preventDefault(),k(\"confirm\")},k=O=>{var j;e.boxType===\"prompt\"&&O===\"confirm\"&&!$()||(u.action=O,u.beforeClose?(j=u.beforeClose)==null||j.call(u,O,u,R):R())},$=()=>{if(e.boxType===\"prompt\"){const O=u.inputPattern;if(O&&!O.test(u.inputValue||\"\"))return u.editorErrorMessage=u.inputErrorMessage||i(\"el.messagebox.error\"),u.validateError=!0,!1;const j=u.inputValidator;if(typeof j==\"function\"){const Q=j(u.inputValue);if(Q===!1)return u.editorErrorMessage=u.inputErrorMessage||i(\"el.messagebox.error\"),u.validateError=!0,!1;if(typeof Q==\"string\")return u.editorErrorMessage=Q,u.validateError=!0,!1}}return u.editorErrorMessage=\"\",u.validateError=!1,!0},F=()=>{const O=S.value.$refs;return O.input||O.textarea},Y=()=>{k(\"close\")},P=()=>{e.closeOnPressEscape&&Y()};return e.lockScroll&&Km(l),{...vr(u),ns:o,overlayEvent:I,visible:l,hasMessage:p,typeClass:c,contentId:f,inputId:d,btnSize:s,iconComponent:v,confirmButtonClasses:w,rootRef:h,focusStartRef:m,headerRef:b,inputRef:S,confirmRef:_,doClose:R,handleClose:Y,onCloseRequested:P,handleWrapperClick:N,handleInputEnter:x,handleAction:k,t:i}}});function NI(e,t,n,r,o,s){const i=Yt(\"el-icon\"),a=Yt(\"close\"),l=Yt(\"el-input\"),u=Yt(\"el-button\"),c=Yt(\"el-focus-trap\"),f=Yt(\"el-overlay\");return L(),fe(Fr,{name:\"fade-in-linear\",onAfterLeave:d=>e.$emit(\"vanish\"),persisted:\"\"},{default:ce(()=>[ct(re(f,{\"z-index\":e.zIndex,\"overlay-class\":[e.ns.is(\"message-box\"),e.modalClass],mask:e.modal},{default:ce(()=>[le(\"div\",{role:\"dialog\",\"aria-label\":e.title,\"aria-modal\":\"true\",\"aria-describedby\":e.showInput?void 0:e.contentId,class:U(`${e.ns.namespace.value}-overlay-message-box`),onClick:e.overlayEvent.onClick,onMousedown:e.overlayEvent.onMousedown,onMouseup:e.overlayEvent.onMouseup},[re(c,{loop:\"\",trapped:e.visible,\"focus-trap-el\":e.rootRef,\"focus-start-el\":e.focusStartRef,onReleaseRequested:e.onCloseRequested},{default:ce(()=>[le(\"div\",{ref:\"rootRef\",class:U([e.ns.b(),e.customClass,e.ns.is(\"draggable\",e.draggable),{[e.ns.m(\"center\")]:e.center}]),style:ot(e.customStyle),tabindex:\"-1\",onClick:tt(()=>{},[\"stop\"])},[e.title!==null&&e.title!==void 0?(L(),ee(\"div\",{key:0,ref:\"headerRef\",class:U([e.ns.e(\"header\"),{\"show-close\":e.showClose}])},[le(\"div\",{class:U(e.ns.e(\"title\"))},[e.iconComponent&&e.center?(L(),fe(i,{key:0,class:U([e.ns.e(\"status\"),e.typeClass])},{default:ce(()=>[(L(),fe(Xe(e.iconComponent)))]),_:1},8,[\"class\"])):ae(\"v-if\",!0),le(\"span\",null,He(e.title),1)],2),e.showClose?(L(),ee(\"button\",{key:0,type:\"button\",class:U(e.ns.e(\"headerbtn\")),\"aria-label\":e.t(\"el.messagebox.close\"),onClick:d=>e.handleAction(e.distinguishCancelAndClose?\"close\":\"cancel\"),onKeydown:Vt(tt(d=>e.handleAction(e.distinguishCancelAndClose?\"close\":\"cancel\"),[\"prevent\"]),[\"enter\"])},[re(i,{class:U(e.ns.e(\"close\"))},{default:ce(()=>[re(a)]),_:1},8,[\"class\"])],42,[\"aria-label\",\"onClick\",\"onKeydown\"])):ae(\"v-if\",!0)],2)):ae(\"v-if\",!0),le(\"div\",{id:e.contentId,class:U(e.ns.e(\"content\"))},[le(\"div\",{class:U(e.ns.e(\"container\"))},[e.iconComponent&&!e.center&&e.hasMessage?(L(),fe(i,{key:0,class:U([e.ns.e(\"status\"),e.typeClass])},{default:ce(()=>[(L(),fe(Xe(e.iconComponent)))]),_:1},8,[\"class\"])):ae(\"v-if\",!0),e.hasMessage?(L(),ee(\"div\",{key:1,class:U(e.ns.e(\"message\"))},[de(e.$slots,\"default\",{},()=>[e.dangerouslyUseHTMLString?(L(),fe(Xe(e.showInput?\"label\":\"p\"),{key:1,for:e.showInput?e.inputId:void 0,innerHTML:e.message},null,8,[\"for\",\"innerHTML\"])):(L(),fe(Xe(e.showInput?\"label\":\"p\"),{key:0,for:e.showInput?e.inputId:void 0},{default:ce(()=>[Un(He(e.dangerouslyUseHTMLString?\"\":e.message),1)]),_:1},8,[\"for\"]))])],2)):ae(\"v-if\",!0)],2),ct(le(\"div\",{class:U(e.ns.e(\"input\"))},[re(l,{id:e.inputId,ref:\"inputRef\",modelValue:e.inputValue,\"onUpdate:modelValue\":d=>e.inputValue=d,type:e.inputType,placeholder:e.inputPlaceholder,\"aria-invalid\":e.validateError,class:U({invalid:e.validateError}),onKeydown:Vt(e.handleInputEnter,[\"enter\"])},null,8,[\"id\",\"modelValue\",\"onUpdate:modelValue\",\"type\",\"placeholder\",\"aria-invalid\",\"class\",\"onKeydown\"]),le(\"div\",{class:U(e.ns.e(\"errormsg\")),style:ot({visibility:e.editorErrorMessage?\"visible\":\"hidden\"})},He(e.editorErrorMessage),7)],2),[[en,e.showInput]])],10,[\"id\"]),le(\"div\",{class:U(e.ns.e(\"btns\"))},[e.showCancelButton?(L(),fe(u,{key:0,loading:e.cancelButtonLoading,\"loading-icon\":e.cancelButtonLoadingIcon,class:U([e.cancelButtonClass]),round:e.roundButton,size:e.btnSize,onClick:d=>e.handleAction(\"cancel\"),onKeydown:Vt(tt(d=>e.handleAction(\"cancel\"),[\"prevent\"]),[\"enter\"])},{default:ce(()=>[Un(He(e.cancelButtonText||e.t(\"el.messagebox.cancel\")),1)]),_:1},8,[\"loading\",\"loading-icon\",\"class\",\"round\",\"size\",\"onClick\",\"onKeydown\"])):ae(\"v-if\",!0),ct(re(u,{ref:\"confirmRef\",type:\"primary\",loading:e.confirmButtonLoading,\"loading-icon\":e.confirmButtonLoadingIcon,class:U([e.confirmButtonClasses]),round:e.roundButton,disabled:e.confirmButtonDisabled,size:e.btnSize,onClick:d=>e.handleAction(\"confirm\"),onKeydown:Vt(tt(d=>e.handleAction(\"confirm\"),[\"prevent\"]),[\"enter\"])},{default:ce(()=>[Un(He(e.confirmButtonText||e.t(\"el.messagebox.confirm\")),1)]),_:1},8,[\"loading\",\"loading-icon\",\"class\",\"round\",\"disabled\",\"size\",\"onClick\",\"onKeydown\"]),[[en,e.showConfirmButton]])],2)],14,[\"onClick\"])]),_:3},8,[\"trapped\",\"focus-trap-el\",\"focus-start-el\",\"onReleaseRequested\"])],42,[\"aria-label\",\"aria-describedby\",\"onClick\",\"onMousedown\",\"onMouseup\"])]),_:3},8,[\"z-index\",\"overlay-class\",\"mask\"]),[[en,e.visible]])]),_:3},8,[\"onAfterLeave\"])}var LI=Ne(PI,[[\"render\",NI],[\"__file\",\"index.vue\"]]);const ti=new Map,MI=e=>{let t=document.body;return e.appendTo&&(Ae(e.appendTo)&&(t=document.querySelector(e.appendTo)),ar(e.appendTo)&&(t=e.appendTo),ar(t)||(t=document.body)),t},$I=(e,t,n=null)=>{const r=re(LI,e,ve(e.message)||cn(e.message)?{default:ve(e.message)?e.message:()=>e.message}:null);return r.appContext=n,wa(r,t),MI(e).appendChild(t.firstElementChild),r.component},kI=()=>document.createElement(\"div\"),FI=(e,t)=>{const n=kI();e.onVanish=()=>{wa(null,n),ti.delete(o)},e.onAction=s=>{const i=ti.get(o);let a;e.showInput?a={value:o.inputValue,action:s}:a=s,e.callback?e.callback(a,r.proxy):s===\"cancel\"||s===\"close\"?e.distinguishCancelAndClose&&s!==\"cancel\"?i.reject(\"close\"):i.reject(\"cancel\"):i.resolve(a)};const r=$I(e,n,t),o=r.proxy;for(const s in e)Ve(e,s)&&!Ve(o.$props,s)&&(o[s]=e[s]);return o.visible=!0,o};function os(e,t=null){if(!st)return Promise.reject();let n;return Ae(e)||cn(e)?e={message:e}:n=e.callback,new Promise((r,o)=>{const s=FI(e,t??os._context);ti.set(s,{options:e,callback:n,resolve:r,reject:o})})}const BI=[\"alert\",\"confirm\",\"prompt\"],DI={alert:{closeOnPressEscape:!1,closeOnClickModal:!1},confirm:{showCancelButton:!0},prompt:{showCancelButton:!0,showInput:!0}};BI.forEach(e=>{os[e]=VI(e)});function VI(e){return(t,n,r,o)=>{let s=\"\";return Oe(n)?(r=n,s=\"\"):Lt(n)?s=\"\":s=n,os(Object.assign({title:s,message:t,type:\"\",...DI[e]},r,{boxType:e}),o)}}os.close=()=>{ti.forEach((e,t)=>{t.doClose()}),ti.clear()};os._context=null;const Ar=os;Ar.install=e=>{Ar._context=e._context,e.config.globalProperties.$msgbox=Ar,e.config.globalProperties.$messageBox=Ar,e.config.globalProperties.$alert=Ar.alert,e.config.globalProperties.$confirm=Ar.confirm,e.config.globalProperties.$prompt=Ar.prompt};const qN=Ar;function Gg(e,t){return function(){return e.apply(t,arguments)}}const{toString:jI}=Object.prototype,{getPrototypeOf:qc}=Object,{iterator:dl,toStringTag:Yg}=Symbol,pl=(e=>t=>{const n=jI.call(t);return e[n]||(e[n]=n.slice(8,-1).toLowerCase())})(Object.create(null)),Nn=e=>(e=e.toLowerCase(),t=>pl(t)===e),hl=e=>t=>typeof t===e,{isArray:ss}=Array,Yo=hl(\"undefined\");function mi(e){return e!==null&&!Yo(e)&&e.constructor!==null&&!Yo(e.constructor)&&Ut(e.constructor.isBuffer)&&e.constructor.isBuffer(e)}const Jg=Nn(\"ArrayBuffer\");function zI(e){let t;return typeof ArrayBuffer<\"u\"&&ArrayBuffer.isView?t=ArrayBuffer.isView(e):t=e&&e.buffer&&Jg(e.buffer),t}const HI=hl(\"string\"),Ut=hl(\"function\"),Xg=hl(\"number\"),gi=e=>e!==null&&typeof e==\"object\",UI=e=>e===!0||e===!1,ra=e=>{if(pl(e)!==\"object\")return!1;const t=qc(e);return(t===null||t===Object.prototype||Object.getPrototypeOf(t)===null)&&!(Yg in e)&&!(dl in e)},KI=e=>{if(!gi(e)||mi(e))return!1;try{return Object.keys(e).length===0&&Object.getPrototypeOf(e)===Object.prototype}catch{return!1}},qI=Nn(\"Date\"),WI=Nn(\"File\"),GI=e=>!!(e&&typeof e.uri<\"u\"),YI=e=>e&&typeof e.getParts<\"u\",JI=Nn(\"Blob\"),XI=Nn(\"FileList\"),ZI=e=>gi(e)&&Ut(e.pipe);function QI(){return typeof globalThis<\"u\"?globalThis:typeof self<\"u\"?self:typeof window<\"u\"?window:typeof global<\"u\"?global:{}}const Up=QI(),Kp=typeof Up.FormData<\"u\"?Up.FormData:void 0,eP=e=>{let t;return e&&(Kp&&e instanceof Kp||Ut(e.append)&&((t=pl(e))===\"formdata\"||t===\"object\"&&Ut(e.toString)&&e.toString()===\"[object FormData]\"))},tP=Nn(\"URLSearchParams\"),[nP,rP,oP,sP]=[\"ReadableStream\",\"Request\",\"Response\",\"Headers\"].map(Nn),iP=e=>e.trim?e.trim():e.replace(/^[\\s\\uFEFF\\xA0]+|[\\s\\uFEFF\\xA0]+$/g,\"\");function bi(e,t,{allOwnKeys:n=!1}={}){if(e===null||typeof e>\"u\")return;let r,o;if(typeof e!=\"object\"&&(e=[e]),ss(e))for(r=0,o=e.length;r<o;r++)t.call(null,e[r],r,e);else{if(mi(e))return;const s=n?Object.getOwnPropertyNames(e):Object.keys(e),i=s.length;let a;for(r=0;r<i;r++)a=s[r],t.call(null,e[a],a,e)}}function Zg(e,t){if(mi(e))return null;t=t.toLowerCase();const n=Object.keys(e);let r=n.length,o;for(;r-- >0;)if(o=n[r],t===o.toLowerCase())return o;return null}const ro=typeof globalThis<\"u\"?globalThis:typeof self<\"u\"?self:typeof window<\"u\"?window:global,Qg=e=>!Yo(e)&&e!==ro;function Bu(){const{caseless:e,skipUndefined:t}=Qg(this)&&this||{},n={},r=(o,s)=>{if(s===\"__proto__\"||s===\"constructor\"||s===\"prototype\")return;const i=e&&Zg(n,s)||s;ra(n[i])&&ra(o)?n[i]=Bu(n[i],o):ra(o)?n[i]=Bu({},o):ss(o)?n[i]=o.slice():(!t||!Yo(o))&&(n[i]=o)};for(let o=0,s=arguments.length;o<s;o++)arguments[o]&&bi(arguments[o],r);return n}const aP=(e,t,n,{allOwnKeys:r}={})=>(bi(t,(o,s)=>{n&&Ut(o)?Object.defineProperty(e,s,{value:Gg(o,n),writable:!0,enumerable:!0,configurable:!0}):Object.defineProperty(e,s,{value:o,writable:!0,enumerable:!0,configurable:!0})},{allOwnKeys:r}),e),lP=e=>(e.charCodeAt(0)===65279&&(e=e.slice(1)),e),uP=(e,t,n,r)=>{e.prototype=Object.create(t.prototype,r),Object.defineProperty(e.prototype,\"constructor\",{value:e,writable:!0,enumerable:!1,configurable:!0}),Object.defineProperty(e,\"super\",{value:t.prototype}),n&&Object.assign(e.prototype,n)},cP=(e,t,n,r)=>{let o,s,i;const a={};if(t=t||{},e==null)return t;do{for(o=Object.getOwnPropertyNames(e),s=o.length;s-- >0;)i=o[s],(!r||r(i,e,t))&&!a[i]&&(t[i]=e[i],a[i]=!0);e=n!==!1&&qc(e)}while(e&&(!n||n(e,t))&&e!==Object.prototype);return t},fP=(e,t,n)=>{e=String(e),(n===void 0||n>e.length)&&(n=e.length),n-=t.length;const r=e.indexOf(t,n);return r!==-1&&r===n},dP=e=>{if(!e)return null;if(ss(e))return e;let t=e.length;if(!Xg(t))return null;const n=new Array(t);for(;t-- >0;)n[t]=e[t];return n},pP=(e=>t=>e&&t instanceof e)(typeof Uint8Array<\"u\"&&qc(Uint8Array)),hP=(e,t)=>{const r=(e&&e[dl]).call(e);let o;for(;(o=r.next())&&!o.done;){const s=o.value;t.call(e,s[0],s[1])}},vP=(e,t)=>{let n;const r=[];for(;(n=e.exec(t))!==null;)r.push(n);return r},mP=Nn(\"HTMLFormElement\"),gP=e=>e.toLowerCase().replace(/[-_\\s]([a-z\\d])(\\w*)/g,function(n,r,o){return r.toUpperCase()+o}),qp=(({hasOwnProperty:e})=>(t,n)=>e.call(t,n))(Object.prototype),bP=Nn(\"RegExp\"),eb=(e,t)=>{const n=Object.getOwnPropertyDescriptors(e),r={};bi(n,(o,s)=>{let i;(i=t(o,s,e))!==!1&&(r[s]=i||o)}),Object.defineProperties(e,r)},yP=e=>{eb(e,(t,n)=>{if(Ut(e)&&[\"arguments\",\"caller\",\"callee\"].indexOf(n)!==-1)return!1;const r=e[n];if(Ut(r)){if(t.enumerable=!1,\"writable\"in t){t.writable=!1;return}t.set||(t.set=()=>{throw Error(\"Can not rewrite read-only method '\"+n+\"'\")})}})},wP=(e,t)=>{const n={},r=o=>{o.forEach(s=>{n[s]=!0})};return ss(e)?r(e):r(String(e).split(t)),n},SP=()=>{},EP=(e,t)=>e!=null&&Number.isFinite(e=+e)?e:t;function _P(e){return!!(e&&Ut(e.append)&&e[Yg]===\"FormData\"&&e[dl])}const CP=e=>{const t=new Array(10),n=(r,o)=>{if(gi(r)){if(t.indexOf(r)>=0)return;if(mi(r))return r;if(!(\"toJSON\"in r)){t[o]=r;const s=ss(r)?[]:{};return bi(r,(i,a)=>{const l=n(i,o+1);!Yo(l)&&(s[a]=l)}),t[o]=void 0,s}}return r};return n(e,0)},TP=Nn(\"AsyncFunction\"),OP=e=>e&&(gi(e)||Ut(e))&&Ut(e.then)&&Ut(e.catch),tb=((e,t)=>e?setImmediate:t?((n,r)=>(ro.addEventListener(\"message\",({source:o,data:s})=>{o===ro&&s===n&&r.length&&r.shift()()},!1),o=>{r.push(o),ro.postMessage(n,\"*\")}))(`axios@${Math.random()}`,[]):n=>setTimeout(n))(typeof setImmediate==\"function\",Ut(ro.postMessage)),AP=typeof queueMicrotask<\"u\"?queueMicrotask.bind(ro):typeof process<\"u\"&&process.nextTick||tb,RP=e=>e!=null&&Ut(e[dl]),D={isArray:ss,isArrayBuffer:Jg,isBuffer:mi,isFormData:eP,isArrayBufferView:zI,isString:HI,isNumber:Xg,isBoolean:UI,isObject:gi,isPlainObject:ra,isEmptyObject:KI,isReadableStream:nP,isRequest:rP,isResponse:oP,isHeaders:sP,isUndefined:Yo,isDate:qI,isFile:WI,isReactNativeBlob:GI,isReactNative:YI,isBlob:JI,isRegExp:bP,isFunction:Ut,isStream:ZI,isURLSearchParams:tP,isTypedArray:pP,isFileList:XI,forEach:bi,merge:Bu,extend:aP,trim:iP,stripBOM:lP,inherits:uP,toFlatObject:cP,kindOf:pl,kindOfTest:Nn,endsWith:fP,toArray:dP,forEachEntry:hP,matchAll:vP,isHTMLForm:mP,hasOwnProperty:qp,hasOwnProp:qp,reduceDescriptors:eb,freezeMethods:yP,toObjectSet:wP,toCamelCase:gP,noop:SP,toFiniteNumber:EP,findKey:Zg,global:ro,isContextDefined:Qg,isSpecCompliantForm:_P,toJSONObject:CP,isAsyncFn:TP,isThenable:OP,setImmediate:tb,asap:AP,isIterable:RP};let Te=class nb extends Error{static from(t,n,r,o,s,i){const a=new nb(t.message,n||t.code,r,o,s);return a.cause=t,a.name=t.name,t.status!=null&&a.status==null&&(a.status=t.status),i&&Object.assign(a,i),a}constructor(t,n,r,o,s){super(t),Object.defineProperty(this,\"message\",{value:t,enumerable:!0,writable:!0,configurable:!0}),this.name=\"AxiosError\",this.isAxiosError=!0,n&&(this.code=n),r&&(this.config=r),o&&(this.request=o),s&&(this.response=s,this.status=s.status)}toJSON(){return{message:this.message,name:this.name,description:this.description,number:this.number,fileName:this.fileName,lineNumber:this.lineNumber,columnNumber:this.columnNumber,stack:this.stack,config:D.toJSONObject(this.config),code:this.code,status:this.status}}};Te.ERR_BAD_OPTION_VALUE=\"ERR_BAD_OPTION_VALUE\";Te.ERR_BAD_OPTION=\"ERR_BAD_OPTION\";Te.ECONNABORTED=\"ECONNABORTED\";Te.ETIMEDOUT=\"ETIMEDOUT\";Te.ERR_NETWORK=\"ERR_NETWORK\";Te.ERR_FR_TOO_MANY_REDIRECTS=\"ERR_FR_TOO_MANY_REDIRECTS\";Te.ERR_DEPRECATED=\"ERR_DEPRECATED\";Te.ERR_BAD_RESPONSE=\"ERR_BAD_RESPONSE\";Te.ERR_BAD_REQUEST=\"ERR_BAD_REQUEST\";Te.ERR_CANCELED=\"ERR_CANCELED\";Te.ERR_NOT_SUPPORT=\"ERR_NOT_SUPPORT\";Te.ERR_INVALID_URL=\"ERR_INVALID_URL\";const xP=null;function Du(e){return D.isPlainObject(e)||D.isArray(e)}function rb(e){return D.endsWith(e,\"[]\")?e.slice(0,-2):e}function ql(e,t,n){return e?e.concat(t).map(function(o,s){return o=rb(o),!n&&s?\"[\"+o+\"]\":o}).join(n?\".\":\"\"):t}function IP(e){return D.isArray(e)&&!e.some(Du)}const PP=D.toFlatObject(D,{},null,function(t){return/^is[A-Z]/.test(t)});function vl(e,t,n){if(!D.isObject(e))throw new TypeError(\"target must be an object\");t=t||new FormData,n=D.toFlatObject(n,{metaTokens:!0,dots:!1,indexes:!1},!1,function(h,b){return!D.isUndefined(b[h])});const r=n.metaTokens,o=n.visitor||c,s=n.dots,i=n.indexes,l=(n.Blob||typeof Blob<\"u\"&&Blob)&&D.isSpecCompliantForm(t);if(!D.isFunction(o))throw new TypeError(\"visitor must be a function\");function u(p){if(p===null)return\"\";if(D.isDate(p))return p.toISOString();if(D.isBoolean(p))return p.toString();if(!l&&D.isBlob(p))throw new Te(\"Blob is not supported. Use a Buffer instead.\");return D.isArrayBuffer(p)||D.isTypedArray(p)?l&&typeof Blob==\"function\"?new Blob([p]):Buffer.from(p):p}function c(p,h,b){let m=p;if(D.isReactNative(t)&&D.isReactNativeBlob(p))return t.append(ql(b,h,s),u(p)),!1;if(p&&!b&&typeof p==\"object\"){if(D.endsWith(h,\"{}\"))h=r?h:h.slice(0,-2),p=JSON.stringify(p);else if(D.isArray(p)&&IP(p)||(D.isFileList(p)||D.endsWith(h,\"[]\"))&&(m=D.toArray(p)))return h=rb(h),m.forEach(function(_,w){!(D.isUndefined(_)||_===null)&&t.append(i===!0?ql([h],w,s):i===null?h:h+\"[]\",u(_))}),!1}return Du(p)?!0:(t.append(ql(b,h,s),u(p)),!1)}const f=[],d=Object.assign(PP,{defaultVisitor:c,convertValue:u,isVisitable:Du});function v(p,h){if(!D.isUndefined(p)){if(f.indexOf(p)!==-1)throw Error(\"Circular reference detected in \"+h.join(\".\"));f.push(p),D.forEach(p,function(m,S){(!(D.isUndefined(m)||m===null)&&o.call(t,m,D.isString(S)?S.trim():S,h,d))===!0&&v(m,h?h.concat(S):[S])}),f.pop()}}if(!D.isObject(e))throw new TypeError(\"data must be an object\");return v(e),t}function Wp(e){const t={\"!\":\"%21\",\"'\":\"%27\",\"(\":\"%28\",\")\":\"%29\",\"~\":\"%7E\",\"%20\":\"+\",\"%00\":\"\\0\"};return encodeURIComponent(e).replace(/[!'()~]|%20|%00/g,function(r){return t[r]})}function Wc(e,t){this._pairs=[],e&&vl(e,this,t)}const ob=Wc.prototype;ob.append=function(t,n){this._pairs.push([t,n])};ob.toString=function(t){const n=t?function(r){return t.call(this,r,Wp)}:Wp;return this._pairs.map(function(o){return n(o[0])+\"=\"+n(o[1])},\"\").join(\"&\")};function NP(e){return encodeURIComponent(e).replace(/%3A/gi,\":\").replace(/%24/g,\"$\").replace(/%2C/gi,\",\").replace(/%20/g,\"+\")}function sb(e,t,n){if(!t)return e;const r=n&&n.encode||NP,o=D.isFunction(n)?{serialize:n}:n,s=o&&o.serialize;let i;if(s?i=s(t,o):i=D.isURLSearchParams(t)?t.toString():new Wc(t,o).toString(r),i){const a=e.indexOf(\"#\");a!==-1&&(e=e.slice(0,a)),e+=(e.indexOf(\"?\")===-1?\"?\":\"&\")+i}return e}class Gp{constructor(){this.handlers=[]}use(t,n,r){return this.handlers.push({fulfilled:t,rejected:n,synchronous:r?r.synchronous:!1,runWhen:r?r.runWhen:null}),this.handlers.length-1}eject(t){this.handlers[t]&&(this.handlers[t]=null)}clear(){this.handlers&&(this.handlers=[])}forEach(t){D.forEach(this.handlers,function(r){r!==null&&t(r)})}}const Gc={silentJSONParsing:!0,forcedJSONParsing:!0,clarifyTimeoutError:!1,legacyInterceptorReqResOrdering:!0},LP=typeof URLSearchParams<\"u\"?URLSearchParams:Wc,MP=typeof FormData<\"u\"?FormData:null,$P=typeof Blob<\"u\"?Blob:null,kP={isBrowser:!0,classes:{URLSearchParams:LP,FormData:MP,Blob:$P},protocols:[\"http\",\"https\",\"file\",\"blob\",\"url\",\"data\"]},Yc=typeof window<\"u\"&&typeof document<\"u\",Vu=typeof navigator==\"object\"&&navigator||void 0,FP=Yc&&(!Vu||[\"ReactNative\",\"NativeScript\",\"NS\"].indexOf(Vu.product)<0),BP=typeof WorkerGlobalScope<\"u\"&&self instanceof WorkerGlobalScope&&typeof self.importScripts==\"function\",DP=Yc&&window.location.href||\"http://localhost\",VP=Object.freeze(Object.defineProperty({__proto__:null,hasBrowserEnv:Yc,hasStandardBrowserEnv:FP,hasStandardBrowserWebWorkerEnv:BP,navigator:Vu,origin:DP},Symbol.toStringTag,{value:\"Module\"})),Nt={...VP,...kP};function jP(e,t){return vl(e,new Nt.classes.URLSearchParams,{visitor:function(n,r,o,s){return Nt.isNode&&D.isBuffer(n)?(this.append(r,n.toString(\"base64\")),!1):s.defaultVisitor.apply(this,arguments)},...t})}function zP(e){return D.matchAll(/\\w+|\\[(\\w*)]/g,e).map(t=>t[0]===\"[]\"?\"\":t[1]||t[0])}function HP(e){const t={},n=Object.keys(e);let r;const o=n.length;let s;for(r=0;r<o;r++)s=n[r],t[s]=e[s];return t}function ib(e){function t(n,r,o,s){let i=n[s++];if(i===\"__proto__\")return!0;const a=Number.isFinite(+i),l=s>=n.length;return i=!i&&D.isArray(o)?o.length:i,l?(D.hasOwnProp(o,i)?o[i]=[o[i],r]:o[i]=r,!a):((!o[i]||!D.isObject(o[i]))&&(o[i]=[]),t(n,r,o[i],s)&&D.isArray(o[i])&&(o[i]=HP(o[i])),!a)}if(D.isFormData(e)&&D.isFunction(e.entries)){const n={};return D.forEachEntry(e,(r,o)=>{t(zP(r),o,n,0)}),n}return null}function UP(e,t,n){if(D.isString(e))try{return(t||JSON.parse)(e),D.trim(e)}catch(r){if(r.name!==\"SyntaxError\")throw r}return(n||JSON.stringify)(e)}const yi={transitional:Gc,adapter:[\"xhr\",\"http\",\"fetch\"],transformRequest:[function(t,n){const r=n.getContentType()||\"\",o=r.indexOf(\"application/json\")>-1,s=D.isObject(t);if(s&&D.isHTMLForm(t)&&(t=new FormData(t)),D.isFormData(t))return o?JSON.stringify(ib(t)):t;if(D.isArrayBuffer(t)||D.isBuffer(t)||D.isStream(t)||D.isFile(t)||D.isBlob(t)||D.isReadableStream(t))return t;if(D.isArrayBufferView(t))return t.buffer;if(D.isURLSearchParams(t))return n.setContentType(\"application/x-www-form-urlencoded;charset=utf-8\",!1),t.toString();let a;if(s){if(r.indexOf(\"application/x-www-form-urlencoded\")>-1)return jP(t,this.formSerializer).toString();if((a=D.isFileList(t))||r.indexOf(\"multipart/form-data\")>-1){const l=this.env&&this.env.FormData;return vl(a?{\"files[]\":t}:t,l&&new l,this.formSerializer)}}return s||o?(n.setContentType(\"application/json\",!1),UP(t)):t}],transformResponse:[function(t){const n=this.transitional||yi.transitional,r=n&&n.forcedJSONParsing,o=this.responseType===\"json\";if(D.isResponse(t)||D.isReadableStream(t))return t;if(t&&D.isString(t)&&(r&&!this.responseType||o)){const i=!(n&&n.silentJSONParsing)&&o;try{return JSON.parse(t,this.parseReviver)}catch(a){if(i)throw a.name===\"SyntaxError\"?Te.from(a,Te.ERR_BAD_RESPONSE,this,null,this.response):a}}return t}],timeout:0,xsrfCookieName:\"XSRF-TOKEN\",xsrfHeaderName:\"X-XSRF-TOKEN\",maxContentLength:-1,maxBodyLength:-1,env:{FormData:Nt.classes.FormData,Blob:Nt.classes.Blob},validateStatus:function(t){return t>=200&&t<300},headers:{common:{Accept:\"application/json, text/plain, */*\",\"Content-Type\":void 0}}};D.forEach([\"delete\",\"get\",\"head\",\"post\",\"put\",\"patch\"],e=>{yi.headers[e]={}});const KP=D.toObjectSet([\"age\",\"authorization\",\"content-length\",\"content-type\",\"etag\",\"expires\",\"from\",\"host\",\"if-modified-since\",\"if-unmodified-since\",\"last-modified\",\"location\",\"max-forwards\",\"proxy-authorization\",\"referer\",\"retry-after\",\"user-agent\"]),qP=e=>{const t={};let n,r,o;return e&&e.split(`\n`).forEach(function(i){o=i.indexOf(\":\"),n=i.substring(0,o).trim().toLowerCase(),r=i.substring(o+1).trim(),!(!n||t[n]&&KP[n])&&(n===\"set-cookie\"?t[n]?t[n].push(r):t[n]=[r]:t[n]=t[n]?t[n]+\", \"+r:r)}),t},Yp=Symbol(\"internals\");function vs(e){return e&&String(e).trim().toLowerCase()}function oa(e){return e===!1||e==null?e:D.isArray(e)?e.map(oa):String(e)}function WP(e){const t=Object.create(null),n=/([^\\s,;=]+)\\s*(?:=\\s*([^,;]+))?/g;let r;for(;r=n.exec(e);)t[r[1]]=r[2];return t}const GP=e=>/^[-_a-zA-Z0-9^`|~,!#$%&'*+.]+$/.test(e.trim());function Wl(e,t,n,r,o){if(D.isFunction(r))return r.call(this,t,n);if(o&&(t=n),!!D.isString(t)){if(D.isString(r))return t.indexOf(r)!==-1;if(D.isRegExp(r))return r.test(t)}}function YP(e){return e.trim().toLowerCase().replace(/([a-z\\d])(\\w*)/g,(t,n,r)=>n.toUpperCase()+r)}function JP(e,t){const n=D.toCamelCase(\" \"+t);[\"get\",\"set\",\"has\"].forEach(r=>{Object.defineProperty(e,r+n,{value:function(o,s,i){return this[r].call(this,t,o,s,i)},configurable:!0})})}let Kt=class{constructor(t){t&&this.set(t)}set(t,n,r){const o=this;function s(a,l,u){const c=vs(l);if(!c)throw new Error(\"header name must be a non-empty string\");const f=D.findKey(o,c);(!f||o[f]===void 0||u===!0||u===void 0&&o[f]!==!1)&&(o[f||l]=oa(a))}const i=(a,l)=>D.forEach(a,(u,c)=>s(u,c,l));if(D.isPlainObject(t)||t instanceof this.constructor)i(t,n);else if(D.isString(t)&&(t=t.trim())&&!GP(t))i(qP(t),n);else if(D.isObject(t)&&D.isIterable(t)){let a={},l,u;for(const c of t){if(!D.isArray(c))throw TypeError(\"Object iterator must return a key-value pair\");a[u=c[0]]=(l=a[u])?D.isArray(l)?[...l,c[1]]:[l,c[1]]:c[1]}i(a,n)}else t!=null&&s(n,t,r);return this}get(t,n){if(t=vs(t),t){const r=D.findKey(this,t);if(r){const o=this[r];if(!n)return o;if(n===!0)return WP(o);if(D.isFunction(n))return n.call(this,o,r);if(D.isRegExp(n))return n.exec(o);throw new TypeError(\"parser must be boolean|regexp|function\")}}}has(t,n){if(t=vs(t),t){const r=D.findKey(this,t);return!!(r&&this[r]!==void 0&&(!n||Wl(this,this[r],r,n)))}return!1}delete(t,n){const r=this;let o=!1;function s(i){if(i=vs(i),i){const a=D.findKey(r,i);a&&(!n||Wl(r,r[a],a,n))&&(delete r[a],o=!0)}}return D.isArray(t)?t.forEach(s):s(t),o}clear(t){const n=Object.keys(this);let r=n.length,o=!1;for(;r--;){const s=n[r];(!t||Wl(this,this[s],s,t,!0))&&(delete this[s],o=!0)}return o}normalize(t){const n=this,r={};return D.forEach(this,(o,s)=>{const i=D.findKey(r,s);if(i){n[i]=oa(o),delete n[s];return}const a=t?YP(s):String(s).trim();a!==s&&delete n[s],n[a]=oa(o),r[a]=!0}),this}concat(...t){return this.constructor.concat(this,...t)}toJSON(t){const n=Object.create(null);return D.forEach(this,(r,o)=>{r!=null&&r!==!1&&(n[o]=t&&D.isArray(r)?r.join(\", \"):r)}),n}[Symbol.iterator](){return Object.entries(this.toJSON())[Symbol.iterator]()}toString(){return Object.entries(this.toJSON()).map(([t,n])=>t+\": \"+n).join(`\n`)}getSetCookie(){return this.get(\"set-cookie\")||[]}get[Symbol.toStringTag](){return\"AxiosHeaders\"}static from(t){return t instanceof this?t:new this(t)}static concat(t,...n){const r=new this(t);return n.forEach(o=>r.set(o)),r}static accessor(t){const r=(this[Yp]=this[Yp]={accessors:{}}).accessors,o=this.prototype;function s(i){const a=vs(i);r[a]||(JP(o,i),r[a]=!0)}return D.isArray(t)?t.forEach(s):s(t),this}};Kt.accessor([\"Content-Type\",\"Content-Length\",\"Accept\",\"Accept-Encoding\",\"User-Agent\",\"Authorization\"]);D.reduceDescriptors(Kt.prototype,({value:e},t)=>{let n=t[0].toUpperCase()+t.slice(1);return{get:()=>e,set(r){this[n]=r}}});D.freezeMethods(Kt);function Gl(e,t){const n=this||yi,r=t||n,o=Kt.from(r.headers);let s=r.data;return D.forEach(e,function(a){s=a.call(n,s,o.normalize(),t?t.status:void 0)}),o.normalize(),s}function ab(e){return!!(e&&e.__CANCEL__)}let wi=class extends Te{constructor(t,n,r){super(t??\"canceled\",Te.ERR_CANCELED,n,r),this.name=\"CanceledError\",this.__CANCEL__=!0}};function lb(e,t,n){const r=n.config.validateStatus;!n.status||!r||r(n.status)?e(n):t(new Te(\"Request failed with status code \"+n.status,[Te.ERR_BAD_REQUEST,Te.ERR_BAD_RESPONSE][Math.floor(n.status/100)-4],n.config,n.request,n))}function XP(e){const t=/^([-+\\w]{1,25})(:?\\/\\/|:)/.exec(e);return t&&t[1]||\"\"}function ZP(e,t){e=e||10;const n=new Array(e),r=new Array(e);let o=0,s=0,i;return t=t!==void 0?t:1e3,function(l){const u=Date.now(),c=r[s];i||(i=u),n[o]=l,r[o]=u;let f=s,d=0;for(;f!==o;)d+=n[f++],f=f%e;if(o=(o+1)%e,o===s&&(s=(s+1)%e),u-i<t)return;const v=c&&u-c;return v?Math.round(d*1e3/v):void 0}}function QP(e,t){let n=0,r=1e3/t,o,s;const i=(u,c=Date.now())=>{n=c,o=null,s&&(clearTimeout(s),s=null),e(...u)};return[(...u)=>{const c=Date.now(),f=c-n;f>=r?i(u,c):(o=u,s||(s=setTimeout(()=>{s=null,i(o)},r-f)))},()=>o&&i(o)]}const Pa=(e,t,n=3)=>{let r=0;const o=ZP(50,250);return QP(s=>{const i=s.loaded,a=s.lengthComputable?s.total:void 0,l=i-r,u=o(l),c=i<=a;r=i;const f={loaded:i,total:a,progress:a?i/a:void 0,bytes:l,rate:u||void 0,estimated:u&&a&&c?(a-i)/u:void 0,event:s,lengthComputable:a!=null,[t?\"download\":\"upload\"]:!0};e(f)},n)},Jp=(e,t)=>{const n=e!=null;return[r=>t[0]({lengthComputable:n,total:e,loaded:r}),t[1]]},Xp=e=>(...t)=>D.asap(()=>e(...t)),e8=Nt.hasStandardBrowserEnv?((e,t)=>n=>(n=new URL(n,Nt.origin),e.protocol===n.protocol&&e.host===n.host&&(t||e.port===n.port)))(new URL(Nt.origin),Nt.navigator&&/(msie|trident)/i.test(Nt.navigator.userAgent)):()=>!0,t8=Nt.hasStandardBrowserEnv?{write(e,t,n,r,o,s,i){if(typeof document>\"u\")return;const a=[`${e}=${encodeURIComponent(t)}`];D.isNumber(n)&&a.push(`expires=${new Date(n).toUTCString()}`),D.isString(r)&&a.push(`path=${r}`),D.isString(o)&&a.push(`domain=${o}`),s===!0&&a.push(\"secure\"),D.isString(i)&&a.push(`SameSite=${i}`),document.cookie=a.join(\"; \")},read(e){if(typeof document>\"u\")return null;const t=document.cookie.match(new RegExp(\"(?:^|; )\"+e+\"=([^;]*)\"));return t?decodeURIComponent(t[1]):null},remove(e){this.write(e,\"\",Date.now()-864e5,\"/\")}}:{write(){},read(){return null},remove(){}};function n8(e){return typeof e!=\"string\"?!1:/^([a-z][a-z\\d+\\-.]*:)?\\/\\//i.test(e)}function r8(e,t){return t?e.replace(/\\/?\\/$/,\"\")+\"/\"+t.replace(/^\\/+/,\"\"):e}function ub(e,t,n){let r=!n8(t);return e&&(r||n==!1)?r8(e,t):t}const Zp=e=>e instanceof Kt?{...e}:e;function vo(e,t){t=t||{};const n={};function r(u,c,f,d){return D.isPlainObject(u)&&D.isPlainObject(c)?D.merge.call({caseless:d},u,c):D.isPlainObject(c)?D.merge({},c):D.isArray(c)?c.slice():c}function o(u,c,f,d){if(D.isUndefined(c)){if(!D.isUndefined(u))return r(void 0,u,f,d)}else return r(u,c,f,d)}function s(u,c){if(!D.isUndefined(c))return r(void 0,c)}function i(u,c){if(D.isUndefined(c)){if(!D.isUndefined(u))return r(void 0,u)}else return r(void 0,c)}function a(u,c,f){if(f in t)return r(u,c);if(f in e)return r(void 0,u)}const l={url:s,method:s,data:s,baseURL:i,transformRequest:i,transformResponse:i,paramsSerializer:i,timeout:i,timeoutMessage:i,withCredentials:i,withXSRFToken:i,adapter:i,responseType:i,xsrfCookieName:i,xsrfHeaderName:i,onUploadProgress:i,onDownloadProgress:i,decompress:i,maxContentLength:i,maxBodyLength:i,beforeRedirect:i,transport:i,httpAgent:i,httpsAgent:i,cancelToken:i,socketPath:i,responseEncoding:i,validateStatus:a,headers:(u,c,f)=>o(Zp(u),Zp(c),f,!0)};return D.forEach(Object.keys({...e,...t}),function(c){if(c===\"__proto__\"||c===\"constructor\"||c===\"prototype\")return;const f=D.hasOwnProp(l,c)?l[c]:o,d=f(e[c],t[c],c);D.isUndefined(d)&&f!==a||(n[c]=d)}),n}const cb=e=>{const t=vo({},e);let{data:n,withXSRFToken:r,xsrfHeaderName:o,xsrfCookieName:s,headers:i,auth:a}=t;if(t.headers=i=Kt.from(i),t.url=sb(ub(t.baseURL,t.url,t.allowAbsoluteUrls),e.params,e.paramsSerializer),a&&i.set(\"Authorization\",\"Basic \"+btoa((a.username||\"\")+\":\"+(a.password?unescape(encodeURIComponent(a.password)):\"\"))),D.isFormData(n)){if(Nt.hasStandardBrowserEnv||Nt.hasStandardBrowserWebWorkerEnv)i.setContentType(void 0);else if(D.isFunction(n.getHeaders)){const l=n.getHeaders(),u=[\"content-type\",\"content-length\"];Object.entries(l).forEach(([c,f])=>{u.includes(c.toLowerCase())&&i.set(c,f)})}}if(Nt.hasStandardBrowserEnv&&(r&&D.isFunction(r)&&(r=r(t)),r||r!==!1&&e8(t.url))){const l=o&&s&&t8.read(s);l&&i.set(o,l)}return t},o8=typeof XMLHttpRequest<\"u\",s8=o8&&function(e){return new Promise(function(n,r){const o=cb(e);let s=o.data;const i=Kt.from(o.headers).normalize();let{responseType:a,onUploadProgress:l,onDownloadProgress:u}=o,c,f,d,v,p;function h(){v&&v(),p&&p(),o.cancelToken&&o.cancelToken.unsubscribe(c),o.signal&&o.signal.removeEventListener(\"abort\",c)}let b=new XMLHttpRequest;b.open(o.method.toUpperCase(),o.url,!0),b.timeout=o.timeout;function m(){if(!b)return;const _=Kt.from(\"getAllResponseHeaders\"in b&&b.getAllResponseHeaders()),y={data:!a||a===\"text\"||a===\"json\"?b.responseText:b.response,status:b.status,statusText:b.statusText,headers:_,config:e,request:b};lb(function(R){n(R),h()},function(R){r(R),h()},y),b=null}\"onloadend\"in b?b.onloadend=m:b.onreadystatechange=function(){!b||b.readyState!==4||b.status===0&&!(b.responseURL&&b.responseURL.indexOf(\"file:\")===0)||setTimeout(m)},b.onabort=function(){b&&(r(new Te(\"Request aborted\",Te.ECONNABORTED,e,b)),b=null)},b.onerror=function(w){const y=w&&w.message?w.message:\"Network Error\",A=new Te(y,Te.ERR_NETWORK,e,b);A.event=w||null,r(A),b=null},b.ontimeout=function(){let w=o.timeout?\"timeout of \"+o.timeout+\"ms exceeded\":\"timeout exceeded\";const y=o.transitional||Gc;o.timeoutErrorMessage&&(w=o.timeoutErrorMessage),r(new Te(w,y.clarifyTimeoutError?Te.ETIMEDOUT:Te.ECONNABORTED,e,b)),b=null},s===void 0&&i.setContentType(null),\"setRequestHeader\"in b&&D.forEach(i.toJSON(),function(w,y){b.setRequestHeader(y,w)}),D.isUndefined(o.withCredentials)||(b.withCredentials=!!o.withCredentials),a&&a!==\"json\"&&(b.responseType=o.responseType),u&&([d,p]=Pa(u,!0),b.addEventListener(\"progress\",d)),l&&b.upload&&([f,v]=Pa(l),b.upload.addEventListener(\"progress\",f),b.upload.addEventListener(\"loadend\",v)),(o.cancelToken||o.signal)&&(c=_=>{b&&(r(!_||_.type?new wi(null,e,b):_),b.abort(),b=null)},o.cancelToken&&o.cancelToken.subscribe(c),o.signal&&(o.signal.aborted?c():o.signal.addEventListener(\"abort\",c)));const S=XP(o.url);if(S&&Nt.protocols.indexOf(S)===-1){r(new Te(\"Unsupported protocol \"+S+\":\",Te.ERR_BAD_REQUEST,e));return}b.send(s||null)})},i8=(e,t)=>{const{length:n}=e=e?e.filter(Boolean):[];if(t||n){let r=new AbortController,o;const s=function(u){if(!o){o=!0,a();const c=u instanceof Error?u:this.reason;r.abort(c instanceof Te?c:new wi(c instanceof Error?c.message:c))}};let i=t&&setTimeout(()=>{i=null,s(new Te(`timeout of ${t}ms exceeded`,Te.ETIMEDOUT))},t);const a=()=>{e&&(i&&clearTimeout(i),i=null,e.forEach(u=>{u.unsubscribe?u.unsubscribe(s):u.removeEventListener(\"abort\",s)}),e=null)};e.forEach(u=>u.addEventListener(\"abort\",s));const{signal:l}=r;return l.unsubscribe=()=>D.asap(a),l}},a8=function*(e,t){let n=e.byteLength;if(n<t){yield e;return}let r=0,o;for(;r<n;)o=r+t,yield e.slice(r,o),r=o},l8=async function*(e,t){for await(const n of u8(e))yield*a8(n,t)},u8=async function*(e){if(e[Symbol.asyncIterator]){yield*e;return}const t=e.getReader();try{for(;;){const{done:n,value:r}=await t.read();if(n)break;yield r}}finally{await t.cancel()}},Qp=(e,t,n,r)=>{const o=l8(e,t);let s=0,i,a=l=>{i||(i=!0,r&&r(l))};return new ReadableStream({async pull(l){try{const{done:u,value:c}=await o.next();if(u){a(),l.close();return}let f=c.byteLength;if(n){let d=s+=f;n(d)}l.enqueue(new Uint8Array(c))}catch(u){throw a(u),u}},cancel(l){return a(l),o.return()}},{highWaterMark:2})},eh=64*1024,{isFunction:Di}=D,c8=(({Request:e,Response:t})=>({Request:e,Response:t}))(D.global),{ReadableStream:th,TextEncoder:nh}=D.global,rh=(e,...t)=>{try{return!!e(...t)}catch{return!1}},f8=e=>{e=D.merge.call({skipUndefined:!0},c8,e);const{fetch:t,Request:n,Response:r}=e,o=t?Di(t):typeof fetch==\"function\",s=Di(n),i=Di(r);if(!o)return!1;const a=o&&Di(th),l=o&&(typeof nh==\"function\"?(p=>h=>p.encode(h))(new nh):async p=>new Uint8Array(await new n(p).arrayBuffer())),u=s&&a&&rh(()=>{let p=!1;const h=new n(Nt.origin,{body:new th,method:\"POST\",get duplex(){return p=!0,\"half\"}}).headers.has(\"Content-Type\");return p&&!h}),c=i&&a&&rh(()=>D.isReadableStream(new r(\"\").body)),f={stream:c&&(p=>p.body)};o&&[\"text\",\"arrayBuffer\",\"blob\",\"formData\",\"stream\"].forEach(p=>{!f[p]&&(f[p]=(h,b)=>{let m=h&&h[p];if(m)return m.call(h);throw new Te(`Response type '${p}' is not supported`,Te.ERR_NOT_SUPPORT,b)})});const d=async p=>{if(p==null)return 0;if(D.isBlob(p))return p.size;if(D.isSpecCompliantForm(p))return(await new n(Nt.origin,{method:\"POST\",body:p}).arrayBuffer()).byteLength;if(D.isArrayBufferView(p)||D.isArrayBuffer(p))return p.byteLength;if(D.isURLSearchParams(p)&&(p=p+\"\"),D.isString(p))return(await l(p)).byteLength},v=async(p,h)=>{const b=D.toFiniteNumber(p.getContentLength());return b??d(h)};return async p=>{let{url:h,method:b,data:m,signal:S,cancelToken:_,timeout:w,onDownloadProgress:y,onUploadProgress:A,responseType:R,headers:N,withCredentials:I=\"same-origin\",fetchOptions:x}=cb(p),k=t||fetch;R=R?(R+\"\").toLowerCase():\"text\";let $=i8([S,_&&_.toAbortSignal()],w),F=null;const Y=$&&$.unsubscribe&&(()=>{$.unsubscribe()});let P;try{if(A&&u&&b!==\"get\"&&b!==\"head\"&&(P=await v(N,m))!==0){let Re=new n(h,{method:\"POST\",body:m,duplex:\"half\"}),Ce;if(D.isFormData(m)&&(Ce=Re.headers.get(\"content-type\"))&&N.setContentType(Ce),Re.body){const[_e,qe]=Jp(P,Pa(Xp(A)));m=Qp(Re.body,eh,_e,qe)}}D.isString(I)||(I=I?\"include\":\"omit\");const O=s&&\"credentials\"in n.prototype,j={...x,signal:$,method:b.toUpperCase(),headers:N.normalize().toJSON(),body:m,duplex:\"half\",credentials:O?I:void 0};F=s&&new n(h,j);let Q=await(s?k(F,x):k(h,j));const me=c&&(R===\"stream\"||R===\"response\");if(c&&(y||me&&Y)){const Re={};[\"status\",\"statusText\",\"headers\"].forEach(ze=>{Re[ze]=Q[ze]});const Ce=D.toFiniteNumber(Q.headers.get(\"content-length\")),[_e,qe]=y&&Jp(Ce,Pa(Xp(y),!0))||[];Q=new r(Qp(Q.body,eh,_e,()=>{qe&&qe(),Y&&Y()}),Re)}R=R||\"text\";let Pe=await f[D.findKey(f,R)||\"text\"](Q,p);return!me&&Y&&Y(),await new Promise((Re,Ce)=>{lb(Re,Ce,{data:Pe,headers:Kt.from(Q.headers),status:Q.status,statusText:Q.statusText,config:p,request:F})})}catch(O){throw Y&&Y(),O&&O.name===\"TypeError\"&&/Load failed|fetch/i.test(O.message)?Object.assign(new Te(\"Network Error\",Te.ERR_NETWORK,p,F,O&&O.response),{cause:O.cause||O}):Te.from(O,O&&O.code,p,F,O&&O.response)}}},d8=new Map,fb=e=>{let t=e&&e.env||{};const{fetch:n,Request:r,Response:o}=t,s=[r,o,n];let i=s.length,a=i,l,u,c=d8;for(;a--;)l=s[a],u=c.get(l),u===void 0&&c.set(l,u=a?new Map:f8(t)),c=u;return u};fb();const Jc={http:xP,xhr:s8,fetch:{get:fb}};D.forEach(Jc,(e,t)=>{if(e){try{Object.defineProperty(e,\"name\",{value:t})}catch{}Object.defineProperty(e,\"adapterName\",{value:t})}});const oh=e=>`- ${e}`,p8=e=>D.isFunction(e)||e===null||e===!1;function h8(e,t){e=D.isArray(e)?e:[e];const{length:n}=e;let r,o;const s={};for(let i=0;i<n;i++){r=e[i];let a;if(o=r,!p8(r)&&(o=Jc[(a=String(r)).toLowerCase()],o===void 0))throw new Te(`Unknown adapter '${a}'`);if(o&&(D.isFunction(o)||(o=o.get(t))))break;s[a||\"#\"+i]=o}if(!o){const i=Object.entries(s).map(([l,u])=>`adapter ${l} `+(u===!1?\"is not supported by the environment\":\"is not available in the build\"));let a=n?i.length>1?`since :\n`+i.map(oh).join(`\n`):\" \"+oh(i[0]):\"as no adapter specified\";throw new Te(\"There is no suitable adapter to dispatch the request \"+a,\"ERR_NOT_SUPPORT\")}return o}const db={getAdapter:h8,adapters:Jc};function Yl(e){if(e.cancelToken&&e.cancelToken.throwIfRequested(),e.signal&&e.signal.aborted)throw new wi(null,e)}function sh(e){return Yl(e),e.headers=Kt.from(e.headers),e.data=Gl.call(e,e.transformRequest),[\"post\",\"put\",\"patch\"].indexOf(e.method)!==-1&&e.headers.setContentType(\"application/x-www-form-urlencoded\",!1),db.getAdapter(e.adapter||yi.adapter,e)(e).then(function(r){return Yl(e),r.data=Gl.call(e,e.transformResponse,r),r.headers=Kt.from(r.headers),r},function(r){return ab(r)||(Yl(e),r&&r.response&&(r.response.data=Gl.call(e,e.transformResponse,r.response),r.response.headers=Kt.from(r.response.headers))),Promise.reject(r)})}const pb=\"1.13.6\",ml={};[\"object\",\"boolean\",\"number\",\"function\",\"string\",\"symbol\"].forEach((e,t)=>{ml[e]=function(r){return typeof r===e||\"a\"+(t<1?\"n \":\" \")+e}});const ih={};ml.transitional=function(t,n,r){function o(s,i){return\"[Axios v\"+pb+\"] Transitional option '\"+s+\"'\"+i+(r?\". \"+r:\"\")}return(s,i,a)=>{if(t===!1)throw new Te(o(i,\" has been removed\"+(n?\" in \"+n:\"\")),Te.ERR_DEPRECATED);return n&&!ih[i]&&(ih[i]=!0),t?t(s,i,a):!0}};ml.spelling=function(t){return(n,r)=>!0};function v8(e,t,n){if(typeof e!=\"object\")throw new Te(\"options must be an object\",Te.ERR_BAD_OPTION_VALUE);const r=Object.keys(e);let o=r.length;for(;o-- >0;){const s=r[o],i=t[s];if(i){const a=e[s],l=a===void 0||i(a,s,e);if(l!==!0)throw new Te(\"option \"+s+\" must be \"+l,Te.ERR_BAD_OPTION_VALUE);continue}if(n!==!0)throw new Te(\"Unknown option \"+s,Te.ERR_BAD_OPTION)}}const sa={assertOptions:v8,validators:ml},an=sa.validators;let lo=class{constructor(t){this.defaults=t||{},this.interceptors={request:new Gp,response:new Gp}}async request(t,n){try{return await this._request(t,n)}catch(r){if(r instanceof Error){let o={};Error.captureStackTrace?Error.captureStackTrace(o):o=new Error;const s=o.stack?o.stack.replace(/^.+\\n/,\"\"):\"\";try{r.stack?s&&!String(r.stack).endsWith(s.replace(/^.+\\n.+\\n/,\"\"))&&(r.stack+=`\n`+s):r.stack=s}catch{}}throw r}}_request(t,n){typeof t==\"string\"?(n=n||{},n.url=t):n=t||{},n=vo(this.defaults,n);const{transitional:r,paramsSerializer:o,headers:s}=n;r!==void 0&&sa.assertOptions(r,{silentJSONParsing:an.transitional(an.boolean),forcedJSONParsing:an.transitional(an.boolean),clarifyTimeoutError:an.transitional(an.boolean),legacyInterceptorReqResOrdering:an.transitional(an.boolean)},!1),o!=null&&(D.isFunction(o)?n.paramsSerializer={serialize:o}:sa.assertOptions(o,{encode:an.function,serialize:an.function},!0)),n.allowAbsoluteUrls!==void 0||(this.defaults.allowAbsoluteUrls!==void 0?n.allowAbsoluteUrls=this.defaults.allowAbsoluteUrls:n.allowAbsoluteUrls=!0),sa.assertOptions(n,{baseUrl:an.spelling(\"baseURL\"),withXsrfToken:an.spelling(\"withXSRFToken\")},!0),n.method=(n.method||this.defaults.method||\"get\").toLowerCase();let i=s&&D.merge(s.common,s[n.method]);s&&D.forEach([\"delete\",\"get\",\"head\",\"post\",\"put\",\"patch\",\"common\"],p=>{delete s[p]}),n.headers=Kt.concat(i,s);const a=[];let l=!0;this.interceptors.request.forEach(function(h){if(typeof h.runWhen==\"function\"&&h.runWhen(n)===!1)return;l=l&&h.synchronous;const b=n.transitional||Gc;b&&b.legacyInterceptorReqResOrdering?a.unshift(h.fulfilled,h.rejected):a.push(h.fulfilled,h.rejected)});const u=[];this.interceptors.response.forEach(function(h){u.push(h.fulfilled,h.rejected)});let c,f=0,d;if(!l){const p=[sh.bind(this),void 0];for(p.unshift(...a),p.push(...u),d=p.length,c=Promise.resolve(n);f<d;)c=c.then(p[f++],p[f++]);return c}d=a.length;let v=n;for(;f<d;){const p=a[f++],h=a[f++];try{v=p(v)}catch(b){h.call(this,b);break}}try{c=sh.call(this,v)}catch(p){return Promise.reject(p)}for(f=0,d=u.length;f<d;)c=c.then(u[f++],u[f++]);return c}getUri(t){t=vo(this.defaults,t);const n=ub(t.baseURL,t.url,t.allowAbsoluteUrls);return sb(n,t.params,t.paramsSerializer)}};D.forEach([\"delete\",\"get\",\"head\",\"options\"],function(t){lo.prototype[t]=function(n,r){return this.request(vo(r||{},{method:t,url:n,data:(r||{}).data}))}});D.forEach([\"post\",\"put\",\"patch\"],function(t){function n(r){return function(s,i,a){return this.request(vo(a||{},{method:t,headers:r?{\"Content-Type\":\"multipart/form-data\"}:{},url:s,data:i}))}}lo.prototype[t]=n(),lo.prototype[t+\"Form\"]=n(!0)});let m8=class hb{constructor(t){if(typeof t!=\"function\")throw new TypeError(\"executor must be a function.\");let n;this.promise=new Promise(function(s){n=s});const r=this;this.promise.then(o=>{if(!r._listeners)return;let s=r._listeners.length;for(;s-- >0;)r._listeners[s](o);r._listeners=null}),this.promise.then=o=>{let s;const i=new Promise(a=>{r.subscribe(a),s=a}).then(o);return i.cancel=function(){r.unsubscribe(s)},i},t(function(s,i,a){r.reason||(r.reason=new wi(s,i,a),n(r.reason))})}throwIfRequested(){if(this.reason)throw this.reason}subscribe(t){if(this.reason){t(this.reason);return}this._listeners?this._listeners.push(t):this._listeners=[t]}unsubscribe(t){if(!this._listeners)return;const n=this._listeners.indexOf(t);n!==-1&&this._listeners.splice(n,1)}toAbortSignal(){const t=new AbortController,n=r=>{t.abort(r)};return this.subscribe(n),t.signal.unsubscribe=()=>this.unsubscribe(n),t.signal}static source(){let t;return{token:new hb(function(o){t=o}),cancel:t}}};function g8(e){return function(n){return e.apply(null,n)}}function b8(e){return D.isObject(e)&&e.isAxiosError===!0}const ju={Continue:100,SwitchingProtocols:101,Processing:102,EarlyHints:103,Ok:200,Created:201,Accepted:202,NonAuthoritativeInformation:203,NoContent:204,ResetContent:205,PartialContent:206,MultiStatus:207,AlreadyReported:208,ImUsed:226,MultipleChoices:300,MovedPermanently:301,Found:302,SeeOther:303,NotModified:304,UseProxy:305,Unused:306,TemporaryRedirect:307,PermanentRedirect:308,BadRequest:400,Unauthorized:401,PaymentRequired:402,Forbidden:403,NotFound:404,MethodNotAllowed:405,NotAcceptable:406,ProxyAuthenticationRequired:407,RequestTimeout:408,Conflict:409,Gone:410,LengthRequired:411,PreconditionFailed:412,PayloadTooLarge:413,UriTooLong:414,UnsupportedMediaType:415,RangeNotSatisfiable:416,ExpectationFailed:417,ImATeapot:418,MisdirectedRequest:421,UnprocessableEntity:422,Locked:423,FailedDependency:424,TooEarly:425,UpgradeRequired:426,PreconditionRequired:428,TooManyRequests:429,RequestHeaderFieldsTooLarge:431,UnavailableForLegalReasons:451,InternalServerError:500,NotImplemented:501,BadGateway:502,ServiceUnavailable:503,GatewayTimeout:504,HttpVersionNotSupported:505,VariantAlsoNegotiates:506,InsufficientStorage:507,LoopDetected:508,NotExtended:510,NetworkAuthenticationRequired:511,WebServerIsDown:521,ConnectionTimedOut:522,OriginIsUnreachable:523,TimeoutOccurred:524,SslHandshakeFailed:525,InvalidSslCertificate:526};Object.entries(ju).forEach(([e,t])=>{ju[t]=e});function vb(e){const t=new lo(e),n=Gg(lo.prototype.request,t);return D.extend(n,lo.prototype,t,{allOwnKeys:!0}),D.extend(n,t,null,{allOwnKeys:!0}),n.create=function(o){return vb(vo(e,o))},n}const mt=vb(yi);mt.Axios=lo;mt.CanceledError=wi;mt.CancelToken=m8;mt.isCancel=ab;mt.VERSION=pb;mt.toFormData=vl;mt.AxiosError=Te;mt.Cancel=mt.CanceledError;mt.all=function(t){return Promise.all(t)};mt.spread=g8;mt.isAxiosError=b8;mt.mergeConfig=vo;mt.AxiosHeaders=Kt;mt.formToJSON=e=>ib(D.isHTMLForm(e)?new FormData(e):e);mt.getAdapter=db.getAdapter;mt.HttpStatusCode=ju;mt.default=mt;const{Axios:JN,AxiosError:XN,CanceledError:ZN,isCancel:QN,CancelToken:eL,VERSION:tL,all:nL,Cancel:rL,isAxiosError:oL,spread:sL,toFormData:iL,AxiosHeaders:aL,HttpStatusCode:lL,formToJSON:uL,getAdapter:cL,mergeConfig:fL}=mt;function mb(e){return Fa()?(Ba(e),!0):!1}function ln(e){return typeof e==\"function\"?e():g(e)}const y8=typeof window<\"u\"&&typeof document<\"u\";typeof WorkerGlobalScope<\"u\"&&globalThis instanceof WorkerGlobalScope;const w8=Object.prototype.toString,S8=e=>w8.call(e)===\"[object Object]\",Jo=()=>{};function Xc(e,t){function n(...r){return new Promise((o,s)=>{Promise.resolve(e(()=>t.apply(this,r),{fn:t,thisArg:this,args:r})).then(o).catch(s)})}return n}const gb=e=>e();function E8(e,t={}){let n,r,o=Jo;const s=a=>{clearTimeout(a),o(),o=Jo};return a=>{const l=ln(e),u=ln(t.maxWait);return n&&s(n),l<=0||u!==void 0&&u<=0?(r&&(s(r),r=null),Promise.resolve(a())):new Promise((c,f)=>{o=t.rejectOnCancel?f:c,u&&!r&&(r=setTimeout(()=>{n&&s(n),r=null,c(a())},u)),n=setTimeout(()=>{r&&s(r),r=null,c(a())},l)})}}function _8(...e){let t=0,n,r=!0,o=Jo,s,i,a,l,u;!Ue(e[0])&&typeof e[0]==\"object\"?{delay:i,trailing:a=!0,leading:l=!0,rejectOnCancel:u=!1}=e[0]:[i,a=!0,l=!0,u=!1]=e;const c=()=>{n&&(clearTimeout(n),n=void 0,o(),o=Jo)};return d=>{const v=ln(i),p=Date.now()-t,h=()=>s=d();return c(),v<=0?(t=Date.now(),h()):(p>v&&(l||!r)?(t=Date.now(),h()):a&&(s=new Promise((b,m)=>{o=u?m:b,n=setTimeout(()=>{t=Date.now(),r=!0,b(h()),c()},Math.max(0,v-p))})),!l&&!n&&(n=setTimeout(()=>r=!0,v)),r=!1,s)}}function C8(e=gb){const t=V(!0);function n(){t.value=!1}function r(){t.value=!0}return{isActive:Mr(t),pause:n,resume:r,eventFilter:(...s)=>{t.value&&e(...s)}}}function T8(e){return Ge()}function O8(...e){if(e.length!==1)return Jt(...e);const t=e[0];return typeof t==\"function\"?Mr(Ay(()=>({get:t,set:Jo}))):V(t)}function dL(e,t=200,n={}){return Xc(E8(t,n),e)}function pL(e,t=200,n=!1,r=!0,o=!1){return Xc(_8(t,n,r,o),e)}function A8(e,t,n={}){const{eventFilter:r=gb,...o}=n;return he(e,Xc(r,t),o)}function R8(e,t,n={}){const{eventFilter:r,...o}=n,{eventFilter:s,pause:i,resume:a,isActive:l}=C8(r);return{stop:A8(e,t,{...o,eventFilter:s}),pause:i,resume:a,isActive:l}}function bb(e,t=!0,n){T8()?Ke(e,n):t?e():Ie(e)}const x8=/[YMDHhms]o|\\[([^\\]]+)\\]|Y{1,4}|M{1,4}|D{1,2}|d{1,4}|H{1,2}|h{1,2}|a{1,2}|A{1,2}|m{1,2}|s{1,2}|Z{1,2}|SSS/g;function I8(e,t,n,r){let o=e<12?\"AM\":\"PM\";return r&&(o=o.split(\"\").reduce((s,i)=>s+=`${i}.`,\"\")),n?o.toLowerCase():o}function Gr(e){const t=[\"th\",\"st\",\"nd\",\"rd\"],n=e%100;return e+(t[(n-20)%10]||t[n]||t[0])}function hL(e,t,n={}){var r;const o=e.getFullYear(),s=e.getMonth(),i=e.getDate(),a=e.getHours(),l=e.getMinutes(),u=e.getSeconds(),c=e.getMilliseconds(),f=e.getDay(),d=(r=n.customMeridiem)!=null?r:I8,v={Yo:()=>Gr(o),YY:()=>String(o).slice(-2),YYYY:()=>o,M:()=>s+1,Mo:()=>Gr(s+1),MM:()=>`${s+1}`.padStart(2,\"0\"),MMM:()=>e.toLocaleDateString(ln(n.locales),{month:\"short\"}),MMMM:()=>e.toLocaleDateString(ln(n.locales),{month:\"long\"}),D:()=>String(i),Do:()=>Gr(i),DD:()=>`${i}`.padStart(2,\"0\"),H:()=>String(a),Ho:()=>Gr(a),HH:()=>`${a}`.padStart(2,\"0\"),h:()=>`${a%12||12}`.padStart(1,\"0\"),ho:()=>Gr(a%12||12),hh:()=>`${a%12||12}`.padStart(2,\"0\"),m:()=>String(l),mo:()=>Gr(l),mm:()=>`${l}`.padStart(2,\"0\"),s:()=>String(u),so:()=>Gr(u),ss:()=>`${u}`.padStart(2,\"0\"),SSS:()=>`${c}`.padStart(3,\"0\"),d:()=>f,dd:()=>e.toLocaleDateString(ln(n.locales),{weekday:\"narrow\"}),ddd:()=>e.toLocaleDateString(ln(n.locales),{weekday:\"short\"}),dddd:()=>e.toLocaleDateString(ln(n.locales),{weekday:\"long\"}),A:()=>d(a,l),AA:()=>d(a,l,!1,!0),a:()=>d(a,l,!0),aa:()=>d(a,l,!0,!0)};return t.replace(x8,(p,h)=>{var b,m;return(m=h??((b=v[p])==null?void 0:b.call(v)))!=null?m:p})}/*!\n * pinia v2.3.1\n * (c) 2025 Eduardo San Martin Morote\n * @license MIT\n */let yb;const gl=e=>yb=e,wb=Symbol();function zu(e){return e&&typeof e==\"object\"&&Object.prototype.toString.call(e)===\"[object Object]\"&&typeof e.toJSON!=\"function\"}var Ms;(function(e){e.direct=\"direct\",e.patchObject=\"patch object\",e.patchFunction=\"patch function\"})(Ms||(Ms={}));function vL(){const e=Th(!0),t=e.run(()=>V({}));let n=[],r=[];const o=Ds({install(s){gl(o),o._a=s,s.provide(wb,o),s.config.globalProperties.$pinia=o,r.forEach(i=>n.push(i)),r=[]},use(s){return this._a?n.push(s):r.push(s),this},_p:n,_a:null,_e:e,_s:new Map,state:t});return o}const Sb=()=>{};function ah(e,t,n,r=Sb){e.push(t);const o=()=>{const s=e.indexOf(t);s>-1&&(e.splice(s,1),r())};return!n&&Fa()&&Ba(o),o}function Ao(e,...t){e.slice().forEach(n=>{n(...t)})}const P8=e=>e(),lh=Symbol(),Jl=Symbol();function Hu(e,t){e instanceof Map&&t instanceof Map?t.forEach((n,r)=>e.set(r,n)):e instanceof Set&&t instanceof Set&&t.forEach(e.add,e);for(const n in t){if(!t.hasOwnProperty(n))continue;const r=t[n],o=e[n];zu(o)&&zu(r)&&e.hasOwnProperty(n)&&!Ue(r)&&!Hn(r)?e[n]=Hu(o,r):e[n]=r}return e}const N8=Symbol();function L8(e){return!zu(e)||!e.hasOwnProperty(N8)}const{assign:Rr}=Object;function M8(e){return!!(Ue(e)&&e.effect)}function $8(e,t,n,r){const{state:o,actions:s,getters:i}=t,a=n.state.value[e];let l;function u(){a||(n.state.value[e]=o?o():{});const c=vr(n.state.value[e]);return Rr(c,s,Object.keys(i||{}).reduce((f,d)=>(f[d]=Ds(C(()=>{gl(n);const v=n._s.get(e);return i[d].call(v,v)})),f),{}))}return l=Eb(e,u,t,n,r,!0),l}function Eb(e,t,n={},r,o,s){let i;const a=Rr({actions:{}},n),l={deep:!0};let u,c,f=[],d=[],v;const p=r.state.value[e];!s&&!p&&(r.state.value[e]={});let h;function b(N){let I;u=c=!1,typeof N==\"function\"?(N(r.state.value[e]),I={type:Ms.patchFunction,storeId:e,events:v}):(Hu(r.state.value[e],N),I={type:Ms.patchObject,payload:N,storeId:e,events:v});const x=h=Symbol();Ie().then(()=>{h===x&&(u=!0)}),c=!0,Ao(f,I,r.state.value[e])}const m=s?function(){const{state:I}=n,x=I?I():{};this.$patch(k=>{Rr(k,x)})}:Sb;function S(){i.stop(),f=[],d=[],r._s.delete(e)}const _=(N,I=\"\")=>{if(lh in N)return N[Jl]=I,N;const x=function(){gl(r);const k=Array.from(arguments),$=[],F=[];function Y(j){$.push(j)}function P(j){F.push(j)}Ao(d,{args:k,name:x[Jl],store:y,after:Y,onError:P});let O;try{O=N.apply(this&&this.$id===e?this:y,k)}catch(j){throw Ao(F,j),j}return O instanceof Promise?O.then(j=>(Ao($,j),j)).catch(j=>(Ao(F,j),Promise.reject(j))):(Ao($,O),O)};return x[lh]=!0,x[Jl]=I,x},w={_p:r,$id:e,$onAction:ah.bind(null,d),$patch:b,$reset:m,$subscribe(N,I={}){const x=ah(f,N,I.detached,()=>k()),k=i.run(()=>he(()=>r.state.value[e],$=>{(I.flush===\"sync\"?c:u)&&N({storeId:e,type:Ms.direct,events:v},$)},Rr({},l,I)));return x},$dispose:S},y=wt(w);r._s.set(e,y);const R=(r._a&&r._a.runWithContext||P8)(()=>r._e.run(()=>(i=Th()).run(()=>t({action:_}))));for(const N in R){const I=R[N];if(Ue(I)&&!M8(I)||Hn(I))s||(p&&L8(I)&&(Ue(I)?I.value=p[N]:Hu(I,p[N])),r.state.value[e][N]=I);else if(typeof I==\"function\"){const x=_(I,N);R[N]=x,a.actions[N]=I}}return Rr(y,R),Rr(Me(y),R),Object.defineProperty(y,\"$state\",{get:()=>r.state.value[e],set:N=>{b(I=>{Rr(I,N)})}}),r._p.forEach(N=>{Rr(y,i.run(()=>N({store:y,app:r._a,pinia:r,options:a})))}),p&&s&&n.hydrate&&n.hydrate(y.$state,p),u=!0,c=!0,y}/*! #__NO_SIDE_EFFECTS__ */function mL(e,t,n){let r,o;const s=typeof t==\"function\";typeof e==\"string\"?(r=e,o=s?n:t):(o=e,r=e.id);function i(a,l){const u=ky();return a=a||(u?Ee(wb,null):null),a&&gl(a),a=yb,a._s.has(r)||(s?Eb(r,t,o,a):$8(r,o,a)),a._s.get(r)}return i.$id=r,i}function gL(e){{const t=Me(e),n={};for(const r in t){const o=t[r];o.effect?n[r]=C({get:()=>e[r],set(s){e[r]=s}}):(Ue(o)||Hn(o))&&(n[r]=Jt(e,r))}return n}}function uh(e,t){var n=Object.keys(e);if(Object.getOwnPropertySymbols){var r=Object.getOwnPropertySymbols(e);t&&(r=r.filter(function(o){return Object.getOwnPropertyDescriptor(e,o).enumerable})),n.push.apply(n,r)}return n}function Vi(e){for(var t=1;t<arguments.length;t++){var n=arguments[t]!=null?arguments[t]:{};t%2?uh(Object(n),!0).forEach(function(r){D8(e,r,n[r])}):Object.getOwnPropertyDescriptors?Object.defineProperties(e,Object.getOwnPropertyDescriptors(n)):uh(Object(n)).forEach(function(r){Object.defineProperty(e,r,Object.getOwnPropertyDescriptor(n,r))})}return e}function k8(e,t){if(!(e instanceof t))throw new TypeError(\"Cannot call a class as a function\")}function F8(e,t){for(var n=0;n<t.length;n++){var r=t[n];r.enumerable=r.enumerable||!1,r.configurable=!0,\"value\"in r&&(r.writable=!0),Object.defineProperty(e,r.key,r)}}function B8(e,t,n){return t&&F8(e.prototype,t),e}function D8(e,t,n){return t in e?Object.defineProperty(e,t,{value:n,enumerable:!0,configurable:!0,writable:!0}):e[t]=n,e}function V8(e){return j8(e)||z8(e)||H8(e)||U8()}function j8(e){if(Array.isArray(e))return Uu(e)}function z8(e){if(typeof Symbol<\"u\"&&e[Symbol.iterator]!=null||e[\"@@iterator\"]!=null)return Array.from(e)}function H8(e,t){if(e){if(typeof e==\"string\")return Uu(e,t);var n=Object.prototype.toString.call(e).slice(8,-1);if(n===\"Object\"&&e.constructor&&(n=e.constructor.name),n===\"Map\"||n===\"Set\")return Array.from(e);if(n===\"Arguments\"||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n))return Uu(e,t)}}function Uu(e,t){(t==null||t>e.length)&&(t=e.length);for(var n=0,r=new Array(t);n<t;n++)r[n]=e[n];return r}function U8(){throw new TypeError(`Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.`)}var Ro={FRONT:\"FRONT\",BEHIND:\"BEHIND\"},Yr={INIT:\"INIT\",FIXED:\"FIXED\",DYNAMIC:\"DYNAMIC\"},ch=2,K8=function(){function e(t,n){k8(this,e),this.init(t,n)}return B8(e,[{key:\"init\",value:function(n,r){this.param=n,this.callUpdate=r,this.sizes=new Map,this.firstRangeTotalSize=0,this.firstRangeAverageSize=0,this.lastCalcIndex=0,this.fixedSizeValue=0,this.calcType=Yr.INIT,this.offset=0,this.direction=\"\",this.range=Object.create(null),n&&this.checkRange(0,n.keeps-1)}},{key:\"destroy\",value:function(){this.init(null,null)}},{key:\"getRange\",value:function(){var n=Object.create(null);return n.start=this.range.start,n.end=this.range.end,n.padFront=this.range.padFront,n.padBehind=this.range.padBehind,n}},{key:\"isBehind\",value:function(){return this.direction===Ro.BEHIND}},{key:\"isFront\",value:function(){return this.direction===Ro.FRONT}},{key:\"getOffset\",value:function(n){return(n<1?0:this.getIndexOffset(n))+this.param.slotHeaderSize}},{key:\"updateParam\",value:function(n,r){var o=this;this.param&&n in this.param&&(n===\"uniqueIds\"&&this.sizes.forEach(function(s,i){r.includes(i)||o.sizes.delete(i)}),this.param[n]=r)}},{key:\"saveSize\",value:function(n,r){this.sizes.set(n,r),this.calcType===Yr.INIT?(this.fixedSizeValue=r,this.calcType=Yr.FIXED):this.calcType===Yr.FIXED&&this.fixedSizeValue!==r&&(this.calcType=Yr.DYNAMIC,delete this.fixedSizeValue),this.calcType!==Yr.FIXED&&typeof this.firstRangeTotalSize<\"u\"&&(this.sizes.size<Math.min(this.param.keeps,this.param.uniqueIds.length)?(this.firstRangeTotalSize=V8(this.sizes.values()).reduce(function(o,s){return o+s},0),this.firstRangeAverageSize=Math.round(this.firstRangeTotalSize/this.sizes.size)):delete this.firstRangeTotalSize)}},{key:\"handleDataSourcesChange\",value:function(){var n=this.range.start;this.isFront()?n=n-ch:this.isBehind()&&(n=n+ch),n=Math.max(n,0),this.updateRange(this.range.start,this.getEndByStart(n))}},{key:\"handleSlotSizeChange\",value:function(){this.handleDataSourcesChange()}},{key:\"handleScroll\",value:function(n){this.direction=n<this.offset?Ro.FRONT:Ro.BEHIND,this.offset=n,this.param&&(this.direction===Ro.FRONT?this.handleFront():this.direction===Ro.BEHIND&&this.handleBehind())}},{key:\"handleFront\",value:function(){var n=this.getScrollOvers();if(!(n>this.range.start)){var r=Math.max(n-this.param.buffer,0);this.checkRange(r,this.getEndByStart(r))}}},{key:\"handleBehind\",value:function(){var n=this.getScrollOvers();n<this.range.start+this.param.buffer||this.checkRange(n,this.getEndByStart(n))}},{key:\"getScrollOvers\",value:function(){var n=this.offset-this.param.slotHeaderSize;if(n<=0)return 0;if(this.isFixedType())return Math.floor(n/this.fixedSizeValue);for(var r=0,o=0,s=0,i=this.param.uniqueIds.length;r<=i;){if(o=r+Math.floor((i-r)/2),s=this.getIndexOffset(o),s===n)return o;s<n?r=o+1:s>n&&(i=o-1)}return r>0?--r:0}},{key:\"getIndexOffset\",value:function(n){if(!n)return 0;for(var r=0,o=0,s=0;s<n;s++)o=this.sizes.get(this.param.uniqueIds[s]),r=r+(typeof o==\"number\"?o:this.getEstimateSize());return this.lastCalcIndex=Math.max(this.lastCalcIndex,n-1),this.lastCalcIndex=Math.min(this.lastCalcIndex,this.getLastIndex()),r}},{key:\"isFixedType\",value:function(){return this.calcType===Yr.FIXED}},{key:\"getLastIndex\",value:function(){return this.param.uniqueIds.length-1}},{key:\"checkRange\",value:function(n,r){var o=this.param.keeps,s=this.param.uniqueIds.length;s<=o?(n=0,r=this.getLastIndex()):r-n<o-1&&(n=r-o+1),this.range.start!==n&&this.updateRange(n,r)}},{key:\"updateRange\",value:function(n,r){this.range.start=n,this.range.end=r,this.range.padFront=this.getPadFront(),this.range.padBehind=this.getPadBehind(),this.callUpdate(this.getRange())}},{key:\"getEndByStart\",value:function(n){var r=n+this.param.keeps-1,o=Math.min(r,this.getLastIndex());return o}},{key:\"getPadFront\",value:function(){return this.isFixedType()?this.fixedSizeValue*this.range.start:this.getIndexOffset(this.range.start)}},{key:\"getPadBehind\",value:function(){var n=this.range.end,r=this.getLastIndex();return this.isFixedType()?(r-n)*this.fixedSizeValue:this.lastCalcIndex===r?this.getIndexOffset(r)-this.getIndexOffset(n):(r-n)*this.getEstimateSize()}},{key:\"getEstimateSize\",value:function(){return this.isFixedType()?this.fixedSizeValue:this.firstRangeAverageSize||this.param.estimateSize}}]),e}(),q8={dataKey:{type:[String,Function],required:!0},dataSources:{type:Array,required:!0,default:function(){return[]}},dataComponent:{type:[Object,Function],required:!0},keeps:{type:Number,default:30},extraProps:{type:Object},estimateSize:{type:Number,default:50},direction:{type:String,default:\"vertical\"},start:{type:Number,default:0},offset:{type:Number,default:0},topThreshold:{type:Number,default:0},bottomThreshold:{type:Number,default:0},pageMode:{type:Boolean,default:!1},rootTag:{type:String,default:\"div\"},wrapTag:{type:String,default:\"div\"},wrapClass:{type:String,default:\"wrap\"},wrapStyle:{type:Object},itemTag:{type:String,default:\"div\"},itemClass:{type:String,default:\"\"},itemClassAdd:{type:Function},itemStyle:{type:Object},headerTag:{type:String,default:\"div\"},headerClass:{type:String,default:\"\"},headerStyle:{type:Object},footerTag:{type:String,default:\"div\"},footerClass:{type:String,default:\"\"},footerStyle:{type:Object},itemScopedSlots:{type:Object}},W8={index:{type:Number},event:{type:String},tag:{type:String},horizontal:{type:Boolean},source:{type:Object},component:{type:[Object,Function]},uniqueKey:{type:[String,Number]},extraProps:{type:Object},scopedSlots:{type:Object}},G8={event:{type:String},uniqueKey:{type:String},tag:{type:String},horizontal:{type:Boolean}},_b=function(t,n,r){var o=null,s=C(function(){return t.horizontal?\"offsetWidth\":\"offsetHeight\"}),i=function(){return n.value?n.value[s.value]:0},a=function(){var u=t.event,c=t.uniqueKey,f=t.hasInitial;r(u,c,i(),f)};Ke(function(){typeof ResizeObserver<\"u\"&&(o=new ResizeObserver(function(){a()}),n.value&&o.observe(n.value))}),mo(function(){a()}),kr(function(){o&&(o.disconnect(),o=null)})},Y8=Z({name:\"VirtualListItem\",props:W8,emits:[\"itemResize\"],setup:function(t,n){var r=n.emit,o=V(null);return _b(t,o,r),function(){var s=t.tag,i=t.component,a=t.extraProps,l=a===void 0?{}:a,u=t.index,c=t.source,f=t.scopedSlots,d=f===void 0?{}:f,v=t.uniqueKey,p=Vi(Vi({},l),{},{source:c,index:u});return re(s,{key:v,ref:o},{default:function(){return[re(i,Vi(Vi({},p),{},{scopedSlots:d}),null)]}})}}}),fh=Z({name:\"VirtualListSlot\",props:G8,emits:[\"slotResize\"],setup:function(t,n){var r=n.slots,o=n.emit,s=V(null);return _b(t,s,o),function(){var i,a=t.tag,l=t.uniqueKey;return re(a,{ref:s,key:l},{default:function(){return[(i=r.default)===null||i===void 0?void 0:i.call(r)]}})}}}),$s;(function(e){e.ITEM=\"itemResize\",e.SLOT=\"slotResize\"})($s||($s={}));var Lo;(function(e){e.HEADER=\"thead\",e.FOOTER=\"tfoot\"})(Lo||(Lo={}));var bL=Z({name:\"VirtualList\",props:q8,setup:function(t,n){var r=n.emit,o=n.slots,s=n.expose,i=t.direction===\"horizontal\",a=i?\"scrollLeft\":\"scrollTop\",l=V(null),u=V(),c=V(null),f;he(function(){return t.dataSources.length},function(){f.updateParam(\"uniqueIds\",S()),f.handleDataSourcesChange()}),he(function(){return t.keeps},function(F){f.updateParam(\"keeps\",F),f.handleSlotSizeChange()}),he(function(){return t.start},function(F){y(F)}),he(function(){return t.offset},function(F){return A(F)});var d=function(Y){return f.sizes.get(Y)},v=function(){return t.pageMode?document.documentElement[a]||document.body[a]:u.value?Math.ceil(u.value[a]):0},p=function(){var Y=i?\"clientWidth\":\"clientHeight\";return t.pageMode?document.documentElement[Y]||document.body[Y]:u.value?Math.ceil(u.value[Y]):0},h=function(){var Y=i?\"scrollWidth\":\"scrollHeight\";return t.pageMode?document.documentElement[Y]||document.body[Y]:u.value?Math.ceil(u.value[Y]):0},b=function(Y,P,O,j){r(\"scroll\",j,f.getRange()),f.isFront()&&t.dataSources.length&&Y-t.topThreshold<=0?r(\"totop\"):f.isBehind()&&Y+P+t.bottomThreshold>=O&&r(\"tobottom\")},m=function(Y){var P=v(),O=p(),j=h();P<0||P+O>j+1||!j||(f.handleScroll(P),b(P,O,j,Y))},S=function(){var Y=t.dataKey,P=t.dataSources,O=P===void 0?[]:P;return O.map(function(j){return typeof Y==\"function\"?Y(j):j[Y]})},_=function(Y){l.value=Y},w=function(){f=new K8({slotHeaderSize:0,slotFooterSize:0,keeps:t.keeps,estimateSize:t.estimateSize,buffer:Math.round(t.keeps/3),uniqueIds:S()},_),l.value=f.getRange()},y=function(Y){if(Y>=t.dataSources.length-1)x();else{var P=f.getOffset(Y);A(P)}},A=function(Y){t.pageMode?(document.body[a]=Y,document.documentElement[a]=Y):u.value&&(u.value[a]=Y)},R=function(){for(var Y=[],P=l.value,O=P.start,j=P.end,Q=t.dataSources,me=t.dataKey,Pe=t.itemClass,Re=t.itemTag,Ce=t.itemStyle,_e=t.extraProps,qe=t.dataComponent,ze=t.itemScopedSlots,De=O;De<=j;De++){var B=Q[De];if(B){var K=typeof me==\"function\"?me(B):B[me];(typeof K==\"string\"||typeof K==\"number\")&&Y.push(re(Y8,{index:De,tag:Re,event:$s.ITEM,horizontal:i,uniqueKey:K,source:B,extraProps:_e,component:qe,scopedSlots:ze,style:Ce,class:\"\".concat(Pe).concat(t.itemClassAdd?\" \"+t.itemClassAdd(De):\"\"),onItemResize:N},null))}}return Y},N=function(Y,P){f.saveSize(Y,P),r(\"resized\",Y,P)},I=function(Y,P,O){Y===Lo.HEADER?f.updateParam(\"slotHeaderSize\",P):Y===Lo.FOOTER&&f.updateParam(\"slotFooterSize\",P),O&&f.handleSlotSizeChange()},x=function F(){if(c.value){var Y=c.value[i?\"offsetLeft\":\"offsetTop\"];A(Y),setTimeout(function(){v()+p()<h()&&F()},3)}},k=function(){if(u.value){var Y=u.value.getBoundingClientRect(),P=u.value.ownerDocument.defaultView,O=i?Y.left+P.pageXOffset:Y.top+P.pageYOffset;f.updateParam(\"slotHeaderSize\",O)}},$=function(){return f.sizes.size};return sc(function(){w()}),Ka(function(){A(f.offset)}),Ke(function(){t.start?y(t.start):t.offset&&A(t.offset),t.pageMode&&(k(),document.addEventListener(\"scroll\",m,{passive:!1}))}),kr(function(){f.destroy(),t.pageMode&&document.removeEventListener(\"scroll\",m)}),s({scrollToBottom:x,getSizes:$,getSize:d,getOffset:v,getScrollSize:h,getClientSize:p,scrollToOffset:A,scrollToIndex:y}),function(){var F=t.pageMode,Y=t.rootTag,P=t.wrapTag,O=t.wrapClass,j=t.wrapStyle,Q=t.headerTag,me=t.headerClass,Pe=t.headerStyle,Re=t.footerTag,Ce=t.footerClass,_e=t.footerStyle,qe=l.value,ze=qe.padFront,De=qe.padBehind,B={padding:i?\"0px \".concat(De,\"px 0px \").concat(ze,\"px\"):\"\".concat(ze,\"px 0px \").concat(De,\"px\")},K=j?Object.assign({},j,B):B,J=o.header,oe=o.footer;return re(Y,{ref:u,onScroll:!F&&m},{default:function(){return[J&&re(fh,{class:me,style:Pe,tag:Q,event:$s.SLOT,uniqueKey:Lo.HEADER,onSlotResize:I},{default:function(){return[J()]}}),re(P,{class:O,style:K},{default:function(){return[R()]}}),oe&&re(fh,{class:Ce,style:_e,tag:Re,event:$s.SLOT,uniqueKey:Lo.FOOTER,onSlotResize:I},{default:function(){return[oe()]}}),re(\"div\",{ref:c,style:{width:i?\"0px\":\"100%\",height:i?\"100%\":\"0px\"}},null)]}})}}});const Xl=typeof navigator<\"u\"?navigator.userAgent.toLowerCase().indexOf(\"firefox\")>0:!1;function Zl(e,t,n,r){e.addEventListener?e.addEventListener(t,n,r):e.attachEvent&&e.attachEvent(\"on\".concat(t),n)}function ms(e,t,n,r){e.removeEventListener?e.removeEventListener(t,n,r):e.detachEvent&&e.detachEvent(\"on\".concat(t),n)}function Cb(e,t){const n=t.slice(0,t.length-1);for(let r=0;r<n.length;r++)n[r]=e[n[r].toLowerCase()];return n}function Tb(e){typeof e!=\"string\"&&(e=\"\"),e=e.replace(/\\s/g,\"\");const t=e.split(\",\");let n=t.lastIndexOf(\"\");for(;n>=0;)t[n-1]+=\",\",t.splice(n,1),n=t.lastIndexOf(\"\");return t}function J8(e,t){const n=e.length>=t.length?e:t,r=e.length>=t.length?t:e;let o=!0;for(let s=0;s<n.length;s++)r.indexOf(n[s])===-1&&(o=!1);return o}const ni={backspace:8,\"⌫\":8,tab:9,clear:12,enter:13,\"↩\":13,return:13,esc:27,escape:27,space:32,left:37,up:38,right:39,down:40,arrowup:38,arrowdown:40,arrowleft:37,arrowright:39,del:46,delete:46,ins:45,insert:45,home:36,end:35,pageup:33,pagedown:34,capslock:20,num_0:96,num_1:97,num_2:98,num_3:99,num_4:100,num_5:101,num_6:102,num_7:103,num_8:104,num_9:105,num_multiply:106,num_add:107,num_enter:108,num_subtract:109,num_decimal:110,num_divide:111,\"⇪\":20,\",\":188,\".\":190,\"/\":191,\"`\":192,\"-\":Xl?173:189,\"=\":Xl?61:187,\";\":Xl?59:186,\"'\":222,\"{\":219,\"}\":221,\"[\":219,\"]\":221,\"\\\\\":220},Cn={\"⇧\":16,shift:16,\"⌥\":18,alt:18,option:18,\"⌃\":17,ctrl:17,control:17,\"⌘\":91,cmd:91,meta:91,command:91},ys={16:\"shiftKey\",18:\"altKey\",17:\"ctrlKey\",91:\"metaKey\",shiftKey:16,ctrlKey:17,altKey:18,metaKey:91},Et={16:!1,18:!1,17:!1,91:!1},lt={};for(let e=1;e<20;e++)ni[\"f\".concat(e)]=111+e;let at=[],ks=null,Ob=\"all\";const er=new Map,is=e=>ni[e.toLowerCase()]||Cn[e.toLowerCase()]||e.toUpperCase().charCodeAt(0),X8=e=>Object.keys(ni).find(t=>ni[t]===e),Z8=e=>Object.keys(Cn).find(t=>Cn[t]===e);function Ab(e){Ob=e||\"all\"}function ri(){return Ob||\"all\"}function Q8(){return at.slice(0)}function eN(){return at.map(e=>X8(e)||Z8(e)||String.fromCharCode(e))}function tN(){const e=[];return Object.keys(lt).forEach(t=>{lt[t].forEach(n=>{let{key:r,scope:o,mods:s,shortcut:i}=n;e.push({scope:o,shortcut:i,mods:s,keys:r.split(\"+\").map(a=>is(a))})})}),e}function nN(e){const t=e.target||e.srcElement,{tagName:n}=t;let r=!0;const o=n===\"INPUT\"&&![\"checkbox\",\"radio\",\"range\",\"button\",\"file\",\"reset\",\"submit\",\"color\"].includes(t.type);return(t.isContentEditable||(o||n===\"TEXTAREA\"||n===\"SELECT\")&&!t.readOnly)&&(r=!1),r}function rN(e){return typeof e==\"string\"&&(e=is(e)),at.indexOf(e)!==-1}function oN(e,t){let n,r;e||(e=ri());for(const o in lt)if(Object.prototype.hasOwnProperty.call(lt,o))for(n=lt[o],r=0;r<n.length;)n[r].scope===e?n.splice(r,1).forEach(i=>{let{element:a}=i;return Zc(a)}):r++;ri()===e&&Ab(t||\"all\")}function sN(e){let t=e.keyCode||e.which||e.charCode;e.key&&e.key.toLowerCase()===\"capslock\"&&(t=is(e.key));const n=at.indexOf(t);if(n>=0&&at.splice(n,1),e.key&&e.key.toLowerCase()===\"meta\"&&at.splice(0,at.length),(t===93||t===224)&&(t=91),t in Et){Et[t]=!1;for(const r in Cn)Cn[r]===t&&(Lr[r]=!1)}}function Rb(e){if(typeof e>\"u\")Object.keys(lt).forEach(o=>{Array.isArray(lt[o])&&lt[o].forEach(s=>ji(s)),delete lt[o]}),Zc(null);else if(Array.isArray(e))e.forEach(o=>{o.key&&ji(o)});else if(typeof e==\"object\")e.key&&ji(e);else if(typeof e==\"string\"){for(var t=arguments.length,n=new Array(t>1?t-1:0),r=1;r<t;r++)n[r-1]=arguments[r];let[o,s]=n;typeof o==\"function\"&&(s=o,o=\"\"),ji({key:e,scope:o,method:s,splitKey:\"+\"})}}const ji=e=>{let{key:t,scope:n,method:r,splitKey:o=\"+\"}=e;Tb(t).forEach(i=>{const a=i.split(o),l=a.length,u=a[l-1],c=u===\"*\"?\"*\":is(u);if(!lt[c])return;n||(n=ri());const f=l>1?Cb(Cn,a):[],d=[];lt[c]=lt[c].filter(v=>{const h=(r?v.method===r:!0)&&v.scope===n&&J8(v.mods,f);return h&&d.push(v.element),!h}),d.forEach(v=>Zc(v))})};function dh(e,t,n,r){if(t.element!==r)return;let o;if(t.scope===n||t.scope===\"all\"){o=t.mods.length>0;for(const s in Et)Object.prototype.hasOwnProperty.call(Et,s)&&(!Et[s]&&t.mods.indexOf(+s)>-1||Et[s]&&t.mods.indexOf(+s)===-1)&&(o=!1);(t.mods.length===0&&!Et[16]&&!Et[18]&&!Et[17]&&!Et[91]||o||t.shortcut===\"*\")&&(t.keys=[],t.keys=t.keys.concat(at),t.method(e,t)===!1&&(e.preventDefault?e.preventDefault():e.returnValue=!1,e.stopPropagation&&e.stopPropagation(),e.cancelBubble&&(e.cancelBubble=!0)))}}function ph(e,t){const n=lt[\"*\"];let r=e.keyCode||e.which||e.charCode;if(e.key&&e.key.toLowerCase()===\"capslock\"||!Lr.filter.call(this,e))return;if((r===93||r===224)&&(r=91),at.indexOf(r)===-1&&r!==229&&at.push(r),[\"metaKey\",\"ctrlKey\",\"altKey\",\"shiftKey\"].forEach(a=>{const l=ys[a];e[a]&&at.indexOf(l)===-1?at.push(l):!e[a]&&at.indexOf(l)>-1?at.splice(at.indexOf(l),1):a===\"metaKey\"&&e[a]&&(at=at.filter(u=>u in ys||u===r))}),r in Et){Et[r]=!0;for(const a in Cn)if(Object.prototype.hasOwnProperty.call(Cn,a)){const l=ys[Cn[a]];Lr[a]=e[l]}if(!n)return}for(const a in Et)Object.prototype.hasOwnProperty.call(Et,a)&&(Et[a]=e[ys[a]]);e.getModifierState&&!(e.altKey&&!e.ctrlKey)&&e.getModifierState(\"AltGraph\")&&(at.indexOf(17)===-1&&at.push(17),at.indexOf(18)===-1&&at.push(18),Et[17]=!0,Et[18]=!0);const o=ri();if(n)for(let a=0;a<n.length;a++)n[a].scope===o&&(e.type===\"keydown\"&&n[a].keydown||e.type===\"keyup\"&&n[a].keyup)&&dh(e,n[a],o,t);if(!(r in lt))return;const s=lt[r],i=s.length;for(let a=0;a<i;a++)if((e.type===\"keydown\"&&s[a].keydown||e.type===\"keyup\"&&s[a].keyup)&&s[a].key){const l=s[a],{splitKey:u}=l,c=l.key.split(u),f=[];for(let d=0;d<c.length;d++)f.push(is(c[d]));f.sort().join(\"\")===at.sort().join(\"\")&&dh(e,l,o,t)}}function Lr(e,t,n){at=[];const r=Tb(e);let o=[],s=\"all\",i=document,a=0,l=!1,u=!0,c=\"+\",f=!1,d=!1;for(n===void 0&&typeof t==\"function\"&&(n=t),Object.prototype.toString.call(t)===\"[object Object]\"&&(t.scope&&(s=t.scope),t.element&&(i=t.element),t.keyup&&(l=t.keyup),t.keydown!==void 0&&(u=t.keydown),t.capture!==void 0&&(f=t.capture),typeof t.splitKey==\"string\"&&(c=t.splitKey),t.single===!0&&(d=!0)),typeof t==\"string\"&&(s=t),d&&Rb(e,s);a<r.length;a++)e=r[a].split(c),o=[],e.length>1&&(o=Cb(Cn,e)),e=e[e.length-1],e=e===\"*\"?\"*\":is(e),e in lt||(lt[e]=[]),lt[e].push({keyup:l,keydown:u,scope:s,mods:o,shortcut:r[a],method:n,key:r[a],splitKey:c,element:i});if(typeof i<\"u\"&&window){if(!er.has(i)){const v=function(){let h=arguments.length>0&&arguments[0]!==void 0?arguments[0]:window.event;return ph(h,i)},p=function(){let h=arguments.length>0&&arguments[0]!==void 0?arguments[0]:window.event;ph(h,i),sN(h)};er.set(i,{keydownListener:v,keyupListenr:p,capture:f}),Zl(i,\"keydown\",v,f),Zl(i,\"keyup\",p,f)}if(!ks){const v=()=>{at=[]};ks={listener:v,capture:f},Zl(window,\"focus\",v,f)}}}function iN(e){let t=arguments.length>1&&arguments[1]!==void 0?arguments[1]:\"all\";Object.keys(lt).forEach(n=>{lt[n].filter(o=>o.scope===t&&o.shortcut===e).forEach(o=>{o&&o.method&&o.method()})})}function Zc(e){const t=Object.values(lt).flat();if(t.findIndex(r=>{let{element:o}=r;return o===e})<0){const{keydownListener:r,keyupListenr:o,capture:s}=er.get(e)||{};r&&o&&(ms(e,\"keyup\",o,s),ms(e,\"keydown\",r,s),er.delete(e))}if((t.length<=0||er.size<=0)&&(Object.keys(er).forEach(o=>{const{keydownListener:s,keyupListenr:i,capture:a}=er.get(o)||{};s&&i&&(ms(o,\"keyup\",i,a),ms(o,\"keydown\",s,a),er.delete(o))}),er.clear(),Object.keys(lt).forEach(o=>delete lt[o]),ks)){const{listener:o,capture:s}=ks;ms(window,\"focus\",o,s),ks=null}}const Ql={getPressedKeyString:eN,setScope:Ab,getScope:ri,deleteScope:oN,getPressedKeyCodes:Q8,getAllKeyCodes:tN,isPressed:rN,filter:nN,trigger:iN,unbind:Rb,keyMap:ni,modifier:Cn,modifierMap:ys};for(const e in Ql)Object.prototype.hasOwnProperty.call(Ql,e)&&(Lr[e]=Ql[e]);if(typeof window<\"u\"){const e=window.hotkeys;Lr.noConflict=t=>(t&&window.hotkeys===Lr&&(window.hotkeys=e),Lr),window.hotkeys=Lr}const Xo=y8?window:void 0;function xb(e){var t;const n=ln(e);return(t=n==null?void 0:n.$el)!=null?t:n}function hh(...e){let t,n,r,o;if(typeof e[0]==\"string\"||Array.isArray(e[0])?([n,r,o]=e,t=Xo):[t,n,r,o]=e,!t)return Jo;Array.isArray(n)||(n=[n]),Array.isArray(r)||(r=[r]);const s=[],i=()=>{s.forEach(c=>c()),s.length=0},a=(c,f,d,v)=>(c.addEventListener(f,d,v),()=>c.removeEventListener(f,d,v)),l=he(()=>[xb(t),ln(o)],([c,f])=>{if(i(),!c)return;const d=S8(f)?{...f}:f;s.push(...n.flatMap(v=>r.map(p=>a(c,v,p,d))))},{immediate:!0,flush:\"post\"}),u=()=>{l(),i()};return mb(u),u}function aN(){const e=V(!1),t=Ge();return t&&Ke(()=>{e.value=!0},t),e}function lN(e){const t=aN();return C(()=>(t.value,!!e()))}function uN(e,t={}){const{window:n=Xo}=t,r=lN(()=>n&&\"matchMedia\"in n&&typeof n.matchMedia==\"function\");let o;const s=V(!1),i=u=>{s.value=u.matches},a=()=>{o&&(\"removeEventListener\"in o?o.removeEventListener(\"change\",i):o.removeListener(i))},l=Ha(()=>{r.value&&(a(),o=n.matchMedia(ln(e)),\"addEventListener\"in o?o.addEventListener(\"change\",i):o.addListener(i),s.value=o.matches)});return mb(()=>{l(),a(),o=void 0}),s}const zi=typeof globalThis<\"u\"?globalThis:typeof window<\"u\"?window:typeof global<\"u\"?global:typeof self<\"u\"?self:{},Hi=\"__vueuse_ssr_handlers__\",cN=fN();function fN(){return Hi in zi||(zi[Hi]=zi[Hi]||{}),zi[Hi]}function Ib(e,t){return cN[e]||t}function Pb(e){return uN(\"(prefers-color-scheme: dark)\",e)}function dN(e){return e==null?\"any\":e instanceof Set?\"set\":e instanceof Map?\"map\":e instanceof Date?\"date\":typeof e==\"boolean\"?\"boolean\":typeof e==\"string\"?\"string\":typeof e==\"object\"?\"object\":Number.isNaN(e)?\"any\":\"number\"}const pN={boolean:{read:e=>e===\"true\",write:e=>String(e)},object:{read:e=>JSON.parse(e),write:e=>JSON.stringify(e)},number:{read:e=>Number.parseFloat(e),write:e=>String(e)},any:{read:e=>e,write:e=>String(e)},string:{read:e=>e,write:e=>String(e)},map:{read:e=>new Map(JSON.parse(e)),write:e=>JSON.stringify(Array.from(e.entries()))},set:{read:e=>new Set(JSON.parse(e)),write:e=>JSON.stringify(Array.from(e))},date:{read:e=>new Date(e),write:e=>e.toISOString()}},vh=\"vueuse-storage\";function hN(e,t,n,r={}){var o;const{flush:s=\"pre\",deep:i=!0,listenToStorageChanges:a=!0,writeDefaults:l=!0,mergeDefaults:u=!1,shallow:c,window:f=Xo,eventFilter:d,onError:v=x=>{},initOnMounted:p}=r,h=(c?ir:V)(typeof t==\"function\"?t():t);if(!n)try{n=Ib(\"getDefaultStorage\",()=>{var x;return(x=Xo)==null?void 0:x.localStorage})()}catch(x){v(x)}if(!n)return h;const b=ln(t),m=dN(b),S=(o=r.serializer)!=null?o:pN[m],{pause:_,resume:w}=R8(h,()=>A(h.value),{flush:s,deep:i,eventFilter:d});f&&a&&bb(()=>{n instanceof Storage?hh(f,\"storage\",N):hh(f,vh,I),p&&N()}),p||N();function y(x,k){if(f){const $={key:e,oldValue:x,newValue:k,storageArea:n};f.dispatchEvent(n instanceof Storage?new StorageEvent(\"storage\",$):new CustomEvent(vh,{detail:$}))}}function A(x){try{const k=n.getItem(e);if(x==null)y(k,null),n.removeItem(e);else{const $=S.write(x);k!==$&&(n.setItem(e,$),y(k,$))}}catch(k){v(k)}}function R(x){const k=x?x.newValue:n.getItem(e);if(k==null)return l&&b!=null&&n.setItem(e,S.write(b)),b;if(!x&&u){const $=S.read(k);return typeof u==\"function\"?u($,b):m===\"object\"&&!Array.isArray($)?{...b,...$}:$}else return typeof k!=\"string\"?k:S.read(k)}function N(x){if(!(x&&x.storageArea!==n)){if(x&&x.key==null){h.value=b;return}if(!(x&&x.key!==e)){_();try{(x==null?void 0:x.newValue)!==S.write(h.value)&&(h.value=R(x))}catch(k){v(k)}finally{x?Ie(w):w()}}}}function I(x){N(x.detail)}return h}const vN=\"*,*::before,*::after{-webkit-transition:none!important;-moz-transition:none!important;-o-transition:none!important;-ms-transition:none!important;transition:none!important}\";function mN(e={}){const{selector:t=\"html\",attribute:n=\"class\",initialValue:r=\"auto\",window:o=Xo,storage:s,storageKey:i=\"vueuse-color-scheme\",listenToStorageChanges:a=!0,storageRef:l,emitAuto:u,disableTransition:c=!0}=e,f={auto:\"\",light:\"light\",dark:\"dark\",...e.modes||{}},d=Pb({window:o}),v=C(()=>d.value?\"dark\":\"light\"),p=l||(i==null?O8(r):hN(i,r,s,{window:o,listenToStorageChanges:a})),h=C(()=>p.value===\"auto\"?v.value:p.value),b=Ib(\"updateHTMLAttrs\",(w,y,A)=>{const R=typeof w==\"string\"?o==null?void 0:o.document.querySelector(w):xb(w);if(!R)return;const N=new Set,I=new Set;let x=null;if(y===\"class\"){const $=A.split(/\\s/g);Object.values(f).flatMap(F=>(F||\"\").split(/\\s/g)).filter(Boolean).forEach(F=>{$.includes(F)?N.add(F):I.add(F)})}else x={key:y,value:A};if(N.size===0&&I.size===0&&x===null)return;let k;c&&(k=o.document.createElement(\"style\"),k.appendChild(document.createTextNode(vN)),o.document.head.appendChild(k));for(const $ of N)R.classList.add($);for(const $ of I)R.classList.remove($);x&&R.setAttribute(x.key,x.value),c&&(o.getComputedStyle(k).opacity,document.head.removeChild(k))});function m(w){var y;b(t,n,(y=f[w])!=null?y:w)}function S(w){e.onChanged?e.onChanged(w,m):m(w)}he(h,S,{flush:\"post\",immediate:!0}),bb(()=>S(h.value));const _=C({get(){return u?p.value:h.value},set(w){p.value=w}});try{return Object.assign(_,{store:p,system:v,state:h})}catch{return _}}function yL(e={}){const{valueDark:t=\"dark\",valueLight:n=\"\",window:r=Xo}=e,o=mN({...e,onChanged:(a,l)=>{var u;e.onChanged?(u=e.onChanged)==null||u.call(e,a===\"dark\",l,a):l(a)},modes:{dark:t,light:n}}),s=C(()=>o.system?o.system.value:Pb({window:r}).value?\"dark\":\"light\");return C({get(){return o.value===\"dark\"},set(a){const l=a?\"dark\":\"light\";s.value===l?o.value=\"auto\":o.value=l}})}const wL='<svg xmlns=\"http://www.w3.org/2000/svg\" viewBox=\"0 0 1024 1024\"><path fill=\"currentColor\" d=\"M512 64a32 32 0 0 1 32 32v192a32 32 0 0 1-64 0V96a32 32 0 0 1 32-32m0 640a32 32 0 0 1 32 32v192a32 32 0 1 1-64 0V736a32 32 0 0 1 32-32m448-192a32 32 0 0 1-32 32H736a32 32 0 1 1 0-64h192a32 32 0 0 1 32 32m-640 0a32 32 0 0 1-32 32H96a32 32 0 0 1 0-64h192a32 32 0 0 1 32 32M195.2 195.2a32 32 0 0 1 45.248 0L376.32 331.008a32 32 0 0 1-45.248 45.248L195.2 240.448a32 32 0 0 1 0-45.248m452.544 452.544a32 32 0 0 1 45.248 0L828.8 783.552a32 32 0 0 1-45.248 45.248L647.744 692.992a32 32 0 0 1 0-45.248M828.8 195.264a32 32 0 0 1 0 45.184L692.992 376.32a32 32 0 0 1-45.248-45.248l135.808-135.808a32 32 0 0 1 45.248 0m-452.544 452.48a32 32 0 0 1 0 45.248L240.448 828.8a32 32 0 0 1-45.248-45.248l135.808-135.808a32 32 0 0 1 45.248 0\"/></svg>';export{VN as $,Ex as A,ON as B,C,bg as D,kN as E,nt as F,xN as G,Ue as H,AN as I,TN as J,CN as K,LN as L,V as M,he as N,Vt as O,ae as P,Ha as Q,vf as R,zN as S,jN as T,Ke as U,bL as V,MN as W,_N as X,Lr as Y,IN as Z,PN as _,SN as a,$N as a0,FN as a1,BN as a2,yL as a3,bN as a4,Ux as a5,gL as a6,EN as a7,qN as a8,kr as a9,ot as aa,Ie as ab,Rg as ac,ct as ad,fw as ae,DN as af,dL as ag,mo as ah,pL as ai,yN as aj,wL as ak,UN as al,wN as b,fe as c,Z as d,ee as e,re as f,Un as g,le as h,HN as i,mt as j,hL as k,RN as l,mL as m,KN as n,L as o,Ds as p,vL as q,Yt as r,ir as s,Me as t,g as u,gw as v,ce as w,NN as x,U as y,He as z};\n"
  },
  {
    "path": "app/src/main/assets/web/vue/index.html",
    "content": "<!DOCTYPE html>\r\n<html lang=\"zh\" class=\"\">\r\n    <head>\r\n        <meta charset=\"UTF-8\" />\r\n        <link rel=\"icon\" href=\"./favicon.ico\" />\r\n    <meta name=\"viewport\" content=\"width=device-width,initial-scale=1.0\" />\r\n      <script type=\"module\" crossorigin src=\"./assets/index-Wr40-hHf.js\"></script>\n      <link rel=\"modulepreload\" crossorigin href=\"./assets/vendor-KSDcS24u.js\">\n      <link rel=\"stylesheet\" crossorigin href=\"./assets/vendor-CXe1BRiH.css\">\n      <link rel=\"stylesheet\" crossorigin href=\"./assets/index-CrxHVQK7.css\">\n    </head>\r\n\r\n    <body>\r\n        <div id=\"app\"></div>\r\r\n    </body>\r\n</html>"
  },
  {
    "path": "app/src/main/java/io/legado/app/App.kt",
    "content": "package io.legado.app\n\nimport android.app.Application\nimport android.app.Notification\nimport android.app.NotificationChannel\nimport android.app.NotificationManager\nimport android.content.Context\nimport android.content.pm.ActivityInfo\nimport android.content.pm.ApplicationInfo\nimport android.content.res.Configuration\nimport android.os.Build\nimport com.github.liuyueyi.quick.transfer.constants.TransType\nimport com.jeremyliao.liveeventbus.LiveEventBus\nimport com.jeremyliao.liveeventbus.logger.DefaultLogger\nimport com.script.rhino.ReadOnlyJavaObject\nimport com.script.rhino.RhinoScriptEngine\nimport com.script.rhino.RhinoWrapFactory\nimport io.legado.app.base.AppContextWrapper\nimport io.legado.app.constant.AppConst.channelIdDownload\nimport io.legado.app.constant.AppConst.channelIdReadAloud\nimport io.legado.app.constant.AppConst.channelIdWeb\nimport io.legado.app.constant.PreferKey\nimport io.legado.app.data.appDb\nimport io.legado.app.data.entities.Book\nimport io.legado.app.data.entities.BookChapter\nimport io.legado.app.data.entities.BookSource\nimport io.legado.app.data.entities.HttpTTS\nimport io.legado.app.data.entities.RssSource\nimport io.legado.app.data.entities.rule.BookInfoRule\nimport io.legado.app.data.entities.rule.ContentRule\nimport io.legado.app.data.entities.rule.ExploreRule\nimport io.legado.app.data.entities.rule.SearchRule\nimport io.legado.app.help.AppFreezeMonitor\nimport io.legado.app.help.AppWebDav\nimport io.legado.app.help.CrashHandler\nimport io.legado.app.help.DefaultData\nimport io.legado.app.help.DispatchersMonitor\nimport io.legado.app.help.LifecycleHelp\nimport io.legado.app.help.RuleBigDataHelp\nimport io.legado.app.help.book.BookHelp\nimport io.legado.app.help.config.AppConfig\nimport io.legado.app.help.config.ReadBookConfig\nimport io.legado.app.help.config.ThemeConfig\nimport io.legado.app.help.config.ThemeConfig.applyDayNight\nimport io.legado.app.help.config.ThemeConfig.applyDayNightInit\nimport io.legado.app.help.coroutine.Coroutine\nimport io.legado.app.help.http.Cronet\nimport io.legado.app.help.http.ObsoleteUrlFactory\nimport io.legado.app.help.http.okHttpClient\nimport io.legado.app.help.rhino.NativeBaseSource\nimport io.legado.app.help.source.SourceHelp\nimport io.legado.app.help.storage.Backup\nimport io.legado.app.model.BookCover\nimport io.legado.app.utils.ChineseUtils\nimport io.legado.app.utils.LogUtils\nimport io.legado.app.utils.defaultSharedPreferences\nimport io.legado.app.utils.getPrefBoolean\nimport io.legado.app.utils.isDebuggable\nimport kotlinx.coroutines.launch\nimport org.chromium.base.ThreadUtils\nimport splitties.init.appCtx\nimport splitties.systemservices.notificationManager\nimport java.net.URL\nimport java.util.concurrent.TimeUnit\nimport java.util.logging.Level\n\nclass App : Application() {\n\n    private lateinit var oldConfig: Configuration\n\n    override fun onCreate() {\n        super.onCreate()\n        CrashHandler(this)\n        if (isDebuggable) {\n            ThreadUtils.setThreadAssertsDisabledForTesting(true)\n        }\n        oldConfig = Configuration(resources.configuration)\n        applyDayNightInit(this)\n        registerActivityLifecycleCallbacks(LifecycleHelp)\n        defaultSharedPreferences.registerOnSharedPreferenceChangeListener(AppConfig)\n        Coroutine.async {\n            LogUtils.init(this@App)\n            LogUtils.d(\"App\", \"onCreate\")\n            LogUtils.logDeviceInfo()\n            //预下载Cronet so\n            Cronet.preDownload()\n            createNotificationChannels()\n            LiveEventBus.config()\n                .lifecycleObserverAlwaysActive(true)\n                .autoClear(false)\n                .enableLogger(BuildConfig.DEBUG || AppConfig.recordLog)\n                .setLogger(EventLogger())\n            DefaultData.upVersion()\n            AppFreezeMonitor.init(this@App)\n            DispatchersMonitor.init()\n            URL.setURLStreamHandlerFactory(ObsoleteUrlFactory(okHttpClient))\n            launch { installGmsTlsProvider(appCtx) }\n            initRhino()\n            //初始化封面\n            BookCover.toString()\n            //清除过期数据\n            appDb.cacheDao.clearDeadline(System.currentTimeMillis())\n            if (getPrefBoolean(PreferKey.autoClearExpired, true)) {\n                val clearTime = System.currentTimeMillis() - TimeUnit.DAYS.toMillis(1)\n                appDb.searchBookDao.clearExpired(clearTime)\n            }\n            RuleBigDataHelp.clearInvalid()\n            BookHelp.clearInvalidCache()\n            Backup.clearCache()\n            ReadBookConfig.clearBgAndCache()\n            ThemeConfig.clearBg()\n            //初始化简繁转换引擎\n            when (AppConfig.chineseConverterType) {\n                1 -> {\n                    ChineseUtils.fixT2sDict()\n                    ChineseUtils.preLoad(true, TransType.TRADITIONAL_TO_SIMPLE)\n                }\n\n                2 -> ChineseUtils.preLoad(true, TransType.SIMPLE_TO_TRADITIONAL)\n            }\n            //调整排序序号\n            SourceHelp.adjustSortNumber()\n            //同步阅读记录\n            if (AppConfig.syncBookProgress) {\n                AppWebDav.downloadAllBookProgress()\n            }\n        }\n    }\n\n    override fun attachBaseContext(base: Context) {\n        super.attachBaseContext(AppContextWrapper.wrap(base))\n    }\n\n    override fun onConfigurationChanged(newConfig: Configuration) {\n        super.onConfigurationChanged(newConfig)\n        val diff = newConfig.diff(oldConfig)\n        if ((diff and ActivityInfo.CONFIG_UI_MODE) != 0) {\n            applyDayNight(this)\n        }\n        oldConfig = Configuration(newConfig)\n    }\n\n    /**\n     * 尝试在安装了GMS的设备上(GMS或者MicroG)使用GMS内置的Conscrypt\n     * 作为首选JCE提供程序，而使Okhttp在低版本Android上\n     * 能够启用TLSv1.3\n     * https://f-droid.org/zh_Hans/2020/05/29/android-updates-and-tls-connections.html\n     * https://developer.android.google.cn/reference/javax/net/ssl/SSLSocket\n     *\n     * @param context\n     * @return\n     */\n    private fun installGmsTlsProvider(context: Context) {\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {\n            return\n        }\n        try {\n            val gmsPackageName = \"com.google.android.gms\"\n            val appInfo = packageManager.getApplicationInfo(gmsPackageName, 0)\n            if ((appInfo.flags and ApplicationInfo.FLAG_SYSTEM) == 0) {\n                return\n            }\n            val gms = context.createPackageContext(\n                gmsPackageName,\n                CONTEXT_INCLUDE_CODE or CONTEXT_IGNORE_SECURITY\n            )\n            gms.classLoader\n                .loadClass(\"com.google.android.gms.common.security.ProviderInstallerImpl\")\n                .getMethod(\"insertProvider\", Context::class.java)\n                .invoke(null, gms)\n        } catch (e: java.lang.Exception) {\n            e.printStackTrace()\n        }\n    }\n\n    /**\n     * 创建通知ID\n     */\n    private fun createNotificationChannels() {\n        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return\n        val downloadChannel = NotificationChannel(\n            channelIdDownload,\n            getString(R.string.action_download),\n            NotificationManager.IMPORTANCE_DEFAULT\n        ).apply {\n            enableLights(false)\n            enableVibration(false)\n            setSound(null, null)\n            lockscreenVisibility = Notification.VISIBILITY_PUBLIC\n        }\n\n        val readAloudChannel = NotificationChannel(\n            channelIdReadAloud,\n            getString(R.string.read_aloud),\n            NotificationManager.IMPORTANCE_DEFAULT\n        ).apply {\n            enableLights(false)\n            enableVibration(false)\n            setSound(null, null)\n            lockscreenVisibility = Notification.VISIBILITY_PUBLIC\n        }\n\n        val webChannel = NotificationChannel(\n            channelIdWeb,\n            getString(R.string.web_service),\n            NotificationManager.IMPORTANCE_DEFAULT\n        ).apply {\n            enableLights(false)\n            enableVibration(false)\n            setSound(null, null)\n            lockscreenVisibility = Notification.VISIBILITY_PUBLIC\n        }\n\n        //向notification manager 提交channel\n        notificationManager.createNotificationChannels(\n            listOf(\n                downloadChannel,\n                readAloudChannel,\n                webChannel\n            )\n        )\n    }\n\n    private fun initRhino() {\n        RhinoScriptEngine\n        RhinoWrapFactory.register(BookSource::class.java, NativeBaseSource.factory)\n        RhinoWrapFactory.register(RssSource::class.java, NativeBaseSource.factory)\n        RhinoWrapFactory.register(HttpTTS::class.java, NativeBaseSource.factory)\n        RhinoWrapFactory.register(ExploreRule::class.java, ReadOnlyJavaObject.factory)\n        RhinoWrapFactory.register(SearchRule::class.java, ReadOnlyJavaObject.factory)\n        RhinoWrapFactory.register(BookInfoRule::class.java, ReadOnlyJavaObject.factory)\n        RhinoWrapFactory.register(ContentRule::class.java, ReadOnlyJavaObject.factory)\n        RhinoWrapFactory.register(BookChapter::class.java, ReadOnlyJavaObject.factory)\n        RhinoWrapFactory.register(Book.ReadConfig::class.java, ReadOnlyJavaObject.factory)\n    }\n\n    class EventLogger : DefaultLogger() {\n\n        override fun log(level: Level, msg: String) {\n            super.log(level, msg)\n            LogUtils.d(TAG, msg)\n        }\n\n        override fun log(level: Level, msg: String, th: Throwable?) {\n            super.log(level, msg, th)\n            LogUtils.d(TAG, \"$msg\\n${th?.stackTraceToString()}\")\n        }\n\n        companion object {\n            private const val TAG = \"[LiveEventBus]\"\n        }\n    }\n\n    companion object {\n        init {\n            if (BuildConfig.DEBUG) {\n                System.setProperty(\"kotlinx.coroutines.debug\", \"on\")\n            }\n        }\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/README.md",
    "content": "# 文件结构介绍\n\n* api 提供的接口\n* base 基类\n* constant 常量\n* data 数据\n* exception 错误类型\n* help 帮助\n* lib 库\n* model 解析\n* receiver 广播侦听\n* service 服务\n* ui 界面\n* utils 辅助类\n* web web服务"
  },
  {
    "path": "app/src/main/java/io/legado/app/api/ReaderProvider.kt",
    "content": "/*\n * Copyright (C) 2020 w568w\n */\npackage io.legado.app.api\n\nimport android.content.ContentProvider\nimport android.content.ContentValues\nimport android.content.UriMatcher\nimport android.database.Cursor\nimport android.database.MatrixCursor\nimport android.net.Uri\nimport com.google.gson.Gson\nimport io.legado.app.api.controller.BookController\nimport io.legado.app.api.controller.BookSourceController\nimport io.legado.app.api.controller.RssSourceController\nimport kotlinx.coroutines.runBlocking\n\n/**\n * Export book data to other app.\n */\nclass ReaderProvider : ContentProvider() {\n    private enum class RequestCode {\n        SaveBookSource, SaveBookSources, DeleteBookSources, GetBookSource, GetBookSources,\n        SaveRssSource, SaveRssSources, DeleteRssSources, GetRssSource, GetRssSources,\n        SaveBook, GetBookshelf, RefreshToc, GetChapterList, GetBookContent, GetBookCover,\n        SaveBookProgress\n    }\n\n    private val postBodyKey = \"json\"\n    private val sMatcher by lazy {\n        UriMatcher(UriMatcher.NO_MATCH).apply {\n            \"${context?.applicationInfo?.packageName}.readerProvider\".also { authority ->\n                addURI(authority, \"bookSource/insert\", RequestCode.SaveBookSource.ordinal)\n                addURI(authority, \"bookSources/insert\", RequestCode.SaveBookSources.ordinal)\n                addURI(authority, \"bookSources/delete\", RequestCode.DeleteBookSources.ordinal)\n                addURI(authority, \"bookSource/query\", RequestCode.GetBookSource.ordinal)\n                addURI(authority, \"bookSources/query\", RequestCode.GetBookSources.ordinal)\n                addURI(authority, \"rssSource/insert\", RequestCode.SaveBookSource.ordinal)\n                addURI(authority, \"rssSources/insert\", RequestCode.SaveBookSources.ordinal)\n                addURI(authority, \"rssSources/delete\", RequestCode.DeleteBookSources.ordinal)\n                addURI(authority, \"rssSource/query\", RequestCode.GetBookSource.ordinal)\n                addURI(authority, \"rssSources/query\", RequestCode.GetBookSources.ordinal)\n                addURI(authority, \"book/insert\", RequestCode.SaveBook.ordinal)\n                addURI(authority, \"books/query\", RequestCode.GetBookshelf.ordinal)\n                addURI(authority, \"book/refreshToc/query\", RequestCode.RefreshToc.ordinal)\n                addURI(authority, \"book/chapter/query\", RequestCode.GetChapterList.ordinal)\n                addURI(authority, \"book/content/query\", RequestCode.GetBookContent.ordinal)\n                addURI(authority, \"book/cover/query\", RequestCode.GetBookCover.ordinal)\n            }\n        }\n    }\n\n    override fun onCreate(): Boolean {\n        context?.let { context ->\n            ShortCuts.buildShortCuts(context)\n        }\n        return false\n    }\n\n    override fun delete(\n        uri: Uri,\n        selection: String?,\n        selectionArgs: Array<String>?\n    ): Int {\n        if (sMatcher.match(uri) < 0) return -1\n        when (RequestCode.entries[sMatcher.match(uri)]) {\n            RequestCode.DeleteBookSources -> BookSourceController.deleteSources(selection)\n            RequestCode.DeleteRssSources -> BookSourceController.deleteSources(selection)\n            else -> throw IllegalStateException(\n                \"Unexpected value: \" + RequestCode.entries[sMatcher.match(uri)].name\n            )\n        }\n        return 0\n    }\n\n    override fun getType(uri: Uri) = throw UnsupportedOperationException(\"Not yet implemented\")\n\n    override fun insert(uri: Uri, values: ContentValues?): Uri? {\n        if (sMatcher.match(uri) < 0) return null\n        runBlocking {\n            when (RequestCode.entries[sMatcher.match(uri)]) {\n                RequestCode.SaveBookSource -> values?.let {\n                    BookSourceController.saveSource(values.getAsString(postBodyKey))\n                }\n\n                RequestCode.SaveBookSources -> values?.let {\n                    BookSourceController.saveSources(values.getAsString(postBodyKey))\n                }\n\n                RequestCode.SaveRssSource -> values?.let {\n                    RssSourceController.saveSource(values.getAsString(postBodyKey))\n                }\n\n                RequestCode.SaveRssSources -> values?.let {\n                    RssSourceController.saveSources(values.getAsString(postBodyKey))\n                }\n\n                RequestCode.SaveBook -> values?.let {\n                    BookController.saveBook(values.getAsString(postBodyKey))\n                }\n\n                RequestCode.SaveBookProgress -> values?.let {\n                    BookController.saveBookProgress(values.getAsString(postBodyKey))\n                }\n\n                else -> throw IllegalStateException(\n                    \"Unexpected value: \" + RequestCode.entries[sMatcher.match(uri)].name\n                )\n            }\n        }\n        return null\n    }\n\n    override fun query(\n        uri: Uri, projection: Array<String>?, selection: String?,\n        selectionArgs: Array<String>?, sortOrder: String?\n    ): Cursor? {\n        val map: MutableMap<String, ArrayList<String>> = HashMap()\n        uri.getQueryParameter(\"url\")?.let {\n            map[\"url\"] = arrayListOf(it)\n        }\n        uri.getQueryParameter(\"index\")?.let {\n            map[\"index\"] = arrayListOf(it)\n        }\n        uri.getQueryParameter(\"path\")?.let {\n            map[\"path\"] = arrayListOf(it)\n        }\n        return if (sMatcher.match(uri) < 0) null else when (RequestCode.entries[sMatcher.match(uri)]) {\n            RequestCode.GetBookSource -> SimpleCursor(BookSourceController.getSource(map))\n            RequestCode.GetBookSources -> SimpleCursor(BookSourceController.sources)\n            RequestCode.GetRssSource -> SimpleCursor(RssSourceController.getSource(map))\n            RequestCode.GetRssSources -> SimpleCursor(RssSourceController.sources)\n            RequestCode.GetBookshelf -> SimpleCursor(BookController.bookshelf)\n            RequestCode.GetBookContent -> SimpleCursor(BookController.getBookContent(map))\n            RequestCode.RefreshToc -> SimpleCursor(BookController.refreshToc(map))\n            RequestCode.GetChapterList -> SimpleCursor(BookController.getChapterList(map))\n            RequestCode.GetBookCover -> SimpleCursor(BookController.getCover(map))\n            else -> throw IllegalStateException(\n                \"Unexpected value: \" + RequestCode.entries[sMatcher.match(uri)].name\n            )\n        }\n    }\n\n    override fun update(\n        uri: Uri, values: ContentValues?, selection: String?,\n        selectionArgs: Array<String>?\n    ) = throw UnsupportedOperationException(\"Not yet implemented\")\n\n\n    /**\n     * Simple inner class to deliver json callback data.\n     *\n     * Only getString() makes sense.\n     */\n    private class SimpleCursor(data: ReturnData?) : MatrixCursor(arrayOf(\"result\"), 1) {\n\n        private val mData: String = Gson().toJson(data)\n\n        init {\n            addRow(arrayOf(mData))\n        }\n\n    }\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/api/ReturnData.kt",
    "content": "package io.legado.app.api\n\nimport androidx.annotation.Keep\n\n@Keep\nclass ReturnData {\n\n    var isSuccess: Boolean = false\n        private set\n\n    var errorMsg: String = \"未知错误,请联系开发者!\"\n        private set\n\n    var data: Any? = null\n        private set\n\n    fun setErrorMsg(errorMsg: String): ReturnData {\n        this.isSuccess = false\n        this.errorMsg = errorMsg\n        return this\n    }\n\n    fun setData(data: Any): ReturnData {\n        this.isSuccess = true\n        this.errorMsg = \"\"\n        this.data = data\n        return this\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/api/ShortCuts.kt",
    "content": "package io.legado.app.api\n\nimport android.content.Context\nimport android.content.Intent\nimport androidx.core.content.pm.ShortcutInfoCompat\nimport androidx.core.content.pm.ShortcutManagerCompat\nimport androidx.core.graphics.drawable.IconCompat\nimport io.legado.app.R\nimport io.legado.app.receiver.SharedReceiverActivity\nimport io.legado.app.ui.book.read.ReadBookActivity\nimport io.legado.app.ui.main.MainActivity\n\nobject ShortCuts {\n\n    private inline fun <reified T> buildIntent(context: Context): Intent {\n        val intent = Intent(context, T::class.java)\n        intent.action = Intent.ACTION_VIEW\n        return intent\n    }\n\n    private fun buildBookShelfShortCutInfo(context: Context): ShortcutInfoCompat {\n        val bookShelfIntent = buildIntent<MainActivity>(context)\n        return ShortcutInfoCompat.Builder(context, \"bookshelf\")\n            .setShortLabel(context.getString(R.string.bookshelf))\n            .setLongLabel(context.getString(R.string.bookshelf))\n            .setIcon(IconCompat.createWithResource(context, R.drawable.icon_read_book))\n            .setIntent(bookShelfIntent)\n            .build()\n    }\n\n    private fun buildReadBookShortCutInfo(context: Context): ShortcutInfoCompat {\n        val bookShelfIntent = buildIntent<MainActivity>(context)\n        val readBookIntent = buildIntent<ReadBookActivity>(context)\n        return ShortcutInfoCompat.Builder(context, \"lastRead\")\n            .setShortLabel(context.getString(R.string.last_read))\n            .setLongLabel(context.getString(R.string.last_read))\n            .setIcon(IconCompat.createWithResource(context, R.drawable.icon_read_book))\n            .setIntents(arrayOf(bookShelfIntent, readBookIntent))\n            .build()\n    }\n\n    private fun buildReadAloudShortCutInfo(context: Context): ShortcutInfoCompat {\n        val readAloudIntent = buildIntent<SharedReceiverActivity>(context)\n        readAloudIntent.putExtra(\"action\", \"readAloud\")\n        return ShortcutInfoCompat.Builder(context, \"readAloud\")\n            .setShortLabel(context.getString(R.string.read_aloud))\n            .setLongLabel(context.getString(R.string.read_aloud))\n            .setIcon(IconCompat.createWithResource(context, R.drawable.icon_read_book))\n            .setIntent(readAloudIntent)\n            .build()\n    }\n\n    fun buildShortCuts(context: Context) {\n        ShortcutManagerCompat.setDynamicShortcuts(\n            context, listOf(\n                buildReadBookShortCutInfo(context),\n                buildReadAloudShortCutInfo(context),\n                buildBookShelfShortCutInfo(context)\n            )\n        )\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/api/controller/BookController.kt",
    "content": "package io.legado.app.api.controller\n\nimport android.graphics.Bitmap\nimport android.graphics.drawable.Drawable\nimport androidx.core.graphics.drawable.toBitmap\nimport com.bumptech.glide.Glide\nimport io.legado.app.api.ReturnData\nimport io.legado.app.data.appDb\nimport io.legado.app.data.entities.Book\nimport io.legado.app.data.entities.BookProgress\nimport io.legado.app.data.entities.BookSource\nimport io.legado.app.help.AppWebDav\nimport io.legado.app.help.CacheManager\nimport io.legado.app.help.book.BookHelp\nimport io.legado.app.help.book.ContentProcessor\nimport io.legado.app.help.book.isLocal\nimport io.legado.app.help.config.AppConfig\nimport io.legado.app.help.glide.ImageLoader\nimport io.legado.app.model.BookCover\nimport io.legado.app.model.ImageProvider\nimport io.legado.app.model.ReadBook\nimport io.legado.app.model.localBook.LocalBook\nimport io.legado.app.model.webBook.WebBook\nimport io.legado.app.utils.GSON\nimport io.legado.app.utils.cnCompare\nimport io.legado.app.utils.fromJsonObject\nimport io.legado.app.utils.printOnDebug\nimport io.legado.app.utils.stackTraceStr\nimport kotlinx.coroutines.delay\nimport kotlinx.coroutines.runBlocking\nimport splitties.init.appCtx\nimport java.io.File\nimport java.util.WeakHashMap\nimport java.util.concurrent.TimeUnit\n\nobject BookController {\n\n    private lateinit var book: Book\n    private var bookSource: BookSource? = null\n    private var bookUrl: String = \"\"\n    private val defaultCoverCache by lazy { WeakHashMap<Drawable, Bitmap>() }\n\n    /**\n     * 书架所有书籍\n     */\n    val bookshelf: ReturnData\n        get() {\n            val books = appDb.bookDao.all\n            val returnData = ReturnData()\n            return if (books.isEmpty()) {\n                returnData.setErrorMsg(\"还没有添加小说\")\n            } else {\n                val data = when (AppConfig.bookshelfSort) {\n                    1 -> books.sortedByDescending { it.latestChapterTime }\n                    2 -> books.sortedWith { o1, o2 ->\n                        o1.name.cnCompare(o2.name)\n                    }\n\n                    3 -> books.sortedBy { it.order }\n                    else -> books.sortedByDescending { it.durChapterTime }\n                }\n                returnData.setData(data)\n            }\n        }\n\n    /**\n     * 获取封面\n     */\n    fun getCover(parameters: Map<String, List<String>>): ReturnData {\n        val returnData = ReturnData()\n        val coverPath = parameters[\"path\"]?.firstOrNull()\n        val ftBitmap = ImageLoader.loadBitmap(appCtx, coverPath)\n            .override(84, 112)\n            .centerCrop()\n            .submit()\n        return try {\n            returnData.setData(ftBitmap.get(3, TimeUnit.SECONDS))\n        } catch (e: Exception) {\n            try {\n                val defaultBitmap = defaultCoverCache.getOrPut(BookCover.defaultDrawable) {\n                    Glide.with(appCtx)\n                        .asBitmap()\n                        .load(BookCover.defaultDrawable.toBitmap())\n                        .override(84, 112)\n                        .centerCrop()\n                        .submit()\n                        .get()\n                }\n                returnData.setData(defaultBitmap)\n            } catch (e: Exception) {\n                returnData.setErrorMsg(e.localizedMessage ?: \"getCover error\")\n            }\n        }\n    }\n\n    /**\n     * 获取正文图片\n     */\n    fun getImg(parameters: Map<String, List<String>>): ReturnData {\n        val returnData = ReturnData()\n        val bookUrl = parameters[\"url\"]?.firstOrNull()\n            ?: return returnData.setErrorMsg(\"bookUrl为空\")\n        val src = parameters[\"path\"]?.firstOrNull()\n            ?: return returnData.setErrorMsg(\"图片链接为空\")\n        val width = parameters[\"width\"]?.firstOrNull()?.toInt() ?: 640\n        if (this.bookUrl != bookUrl) {\n            this.book = appDb.bookDao.getBook(bookUrl)\n                ?: return returnData.setErrorMsg(\"bookUrl不对\")\n            this.bookSource = appDb.bookSourceDao.getBookSource(book.origin)\n        }\n        this.bookUrl = bookUrl\n        val bitmap = runBlocking {\n            ImageProvider.cacheImage(book, src, bookSource)\n            ImageProvider.getImage(book, src, width)\n        }\n        return returnData.setData(bitmap)\n    }\n\n    /**\n     * 更新目录\n     */\n    fun refreshToc(parameters: Map<String, List<String>>): ReturnData {\n        val returnData = ReturnData()\n        try {\n            val bookUrl = parameters[\"url\"]?.firstOrNull()\n            if (bookUrl.isNullOrEmpty()) {\n                return returnData.setErrorMsg(\"参数url不能为空，请指定书籍地址\")\n            }\n            val book = appDb.bookDao.getBook(bookUrl)\n                ?: return returnData.setErrorMsg(\"未在数据库找到对应书籍，请先添加\")\n            if (book.isLocal) {\n                val toc = LocalBook.getChapterList(book)\n                appDb.bookChapterDao.delByBook(book.bookUrl)\n                appDb.bookChapterDao.insert(*toc.toTypedArray())\n                appDb.bookDao.update(book)\n                return returnData.setData(toc)\n            } else {\n                val bookSource = appDb.bookSourceDao.getBookSource(book.origin)\n                    ?: return returnData.setErrorMsg(\"未找到对应书源,请换源\")\n                val toc = runBlocking {\n                    if (book.tocUrl.isBlank()) {\n                        WebBook.getBookInfoAwait(bookSource, book)\n                    }\n                    WebBook.getChapterListAwait(bookSource, book).getOrThrow()\n                }\n                appDb.bookChapterDao.delByBook(book.bookUrl)\n                appDb.bookChapterDao.insert(*toc.toTypedArray())\n                appDb.bookDao.update(book)\n                return returnData.setData(toc)\n            }\n        } catch (e: Exception) {\n            return returnData.setErrorMsg(e.localizedMessage ?: \"refresh toc error\")\n        }\n    }\n\n    /**\n     * 获取目录\n     */\n    fun getChapterList(parameters: Map<String, List<String>>): ReturnData {\n        val bookUrl = parameters[\"url\"]?.firstOrNull()\n        val returnData = ReturnData()\n        if (bookUrl.isNullOrEmpty()) {\n            return returnData.setErrorMsg(\"参数url不能为空，请指定书籍地址\")\n        }\n        val chapterList = appDb.bookChapterDao.getChapterList(bookUrl)\n        if (chapterList.isEmpty()) {\n            return refreshToc(parameters)\n        }\n        return returnData.setData(chapterList)\n    }\n\n    /**\n     * 获取正文\n     */\n    fun getBookContent(parameters: Map<String, List<String>>): ReturnData {\n        val bookUrl = parameters[\"url\"]?.firstOrNull()\n        val index = parameters[\"index\"]?.firstOrNull()?.toInt()\n        val returnData = ReturnData()\n        if (bookUrl.isNullOrEmpty()) {\n            return returnData.setErrorMsg(\"参数url不能为空，请指定书籍地址\")\n        }\n        if (index == null) {\n            return returnData.setErrorMsg(\"参数index不能为空, 请指定目录序号\")\n        }\n        val book = appDb.bookDao.getBook(bookUrl)\n        val chapter = runBlocking {\n            var chapter = appDb.bookChapterDao.getChapter(bookUrl, index)\n            var wait = 0\n            while (chapter == null && wait < 30) {\n                delay(1000)\n                chapter = appDb.bookChapterDao.getChapter(bookUrl, index)\n                wait++\n            }\n            chapter\n        }\n        if (book == null || chapter == null) {\n            return returnData.setErrorMsg(\"未找到\")\n        }\n        var content: String? = BookHelp.getContent(book, chapter)\n        if (content != null) {\n            val contentProcessor = ContentProcessor.get(book.name, book.origin)\n            content = runBlocking {\n                contentProcessor.getContent(book, chapter, content, includeTitle = false)\n                    .toString()\n            }\n            return returnData.setData(content)\n        }\n        val bookSource = appDb.bookSourceDao.getBookSource(book.origin)\n            ?: return returnData.setErrorMsg(\"未找到书源\")\n        try {\n            content = runBlocking {\n                WebBook.getContentAwait(bookSource, book, chapter).let {\n                    val contentProcessor = ContentProcessor.get(book.name, book.origin)\n                    contentProcessor.getContent(book, chapter, it, includeTitle = false)\n                        .toString()\n                }\n            }\n            returnData.setData(content)\n        } catch (e: Exception) {\n            returnData.setErrorMsg(e.stackTraceStr)\n        }\n        return returnData\n    }\n\n    /**\n     * 保存书籍\n     */\n    suspend fun saveBook(postData: String?): ReturnData {\n        val returnData = ReturnData()\n        GSON.fromJsonObject<Book>(postData).getOrNull()?.let { book ->\n            AppWebDav.uploadBookProgress(book)\n            book.save()\n            return returnData.setData(\"\")\n        }\n        return returnData.setErrorMsg(\"格式不对\")\n    }\n\n    /**\n     * 删除书籍\n     */\n    fun deleteBook(postData: String?): ReturnData {\n        val returnData = ReturnData()\n        GSON.fromJsonObject<Book>(postData).getOrNull()?.let { book ->\n            book.delete()\n            return returnData.setData(\"\")\n        }\n        return returnData.setErrorMsg(\"格式不对\")\n    }\n\n    /**\n     * 保存进度\n     */\n    suspend fun saveBookProgress(postData: String?): ReturnData {\n        val returnData = ReturnData()\n        GSON.fromJsonObject<BookProgress>(postData)\n            .onFailure { it.printOnDebug() }\n            .getOrNull()?.let { bookProgress ->\n                appDb.bookDao.getBook(bookProgress.name, bookProgress.author)?.let { book ->\n                    book.durChapterIndex = bookProgress.durChapterIndex\n                    book.durChapterPos = bookProgress.durChapterPos\n                    book.durChapterTitle = bookProgress.durChapterTitle\n                    book.durChapterTime = bookProgress.durChapterTime\n                    AppWebDav.uploadBookProgress(bookProgress) {\n                        book.syncTime = System.currentTimeMillis()\n                    }\n                    appDb.bookDao.update(book)\n                    ReadBook.book?.let {\n                        if (it.name == bookProgress.name &&\n                            it.author == bookProgress.author\n                        ) {\n                            ReadBook.webBookProgress = bookProgress\n                        }\n                    }\n                    return returnData.setData(\"\")\n                }\n            }\n        return returnData.setErrorMsg(\"格式不对\")\n    }\n\n    /**\n     * 添加本地书籍\n     */\n    fun addLocalBook(\n        parameters: Map<String, List<String>>,\n        files: Map<String, String>\n    ): ReturnData {\n        val returnData = ReturnData()\n        val fileName = parameters[\"fileName\"]?.firstOrNull()\n            ?: return returnData.setErrorMsg(\"fileName 不能为空\")\n        val fileData = files[\"fileData\"]\n            ?: return returnData.setErrorMsg(\"fileData 不能为空\")\n        kotlin.runCatching {\n            val uri = LocalBook.saveBookFile(File(fileData).inputStream(), fileName)\n            LocalBook.importFile(uri)\n        }.onFailure {\n            return when (it) {\n                is SecurityException -> returnData.setErrorMsg(\"需重新设置书籍保存位置!\")\n                else -> returnData.setErrorMsg(\"保存书籍错误\\n${it.localizedMessage}\")\n            }\n        }\n        return returnData.setData(true)\n    }\n\n    /**\n     * 保存web阅读界面配置\n     */\n    fun saveWebReadConfig(postData: String?): ReturnData {\n        val returnData = ReturnData()\n        postData?.let {\n            CacheManager.put(\"webReadConfig\", postData)\n        } ?: CacheManager.delete(\"webReadConfig\")\n        return returnData.setData(\"\")\n    }\n\n    /**\n     * 获取web阅读界面配置\n     */\n    fun getWebReadConfig(): ReturnData {\n        val returnData = ReturnData()\n        val data = CacheManager.get(\"webReadConfig\")\n            ?: return returnData.setErrorMsg(\"没有配置\")\n        return returnData.setData(data)\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/api/controller/BookSourceController.kt",
    "content": "package io.legado.app.api.controller\n\n\nimport android.text.TextUtils\nimport io.legado.app.api.ReturnData\nimport io.legado.app.data.appDb\nimport io.legado.app.data.entities.BookSource\nimport io.legado.app.help.source.SourceHelp\nimport io.legado.app.utils.GSON\nimport io.legado.app.utils.fromJsonArray\nimport io.legado.app.utils.fromJsonObject\n\nobject BookSourceController {\n\n    val sources: ReturnData\n        get() {\n            val bookSources = appDb.bookSourceDao.all\n            val returnData = ReturnData()\n            return if (bookSources.isEmpty()) {\n                returnData.setErrorMsg(\"设备源列表为空\")\n            } else returnData.setData(bookSources)\n        }\n\n    fun saveSource(postData: String?): ReturnData {\n        val returnData = ReturnData()\n        postData ?: return returnData.setErrorMsg(\"数据不能为空\")\n        val bookSource = GSON.fromJsonObject<BookSource>(postData).getOrNull()\n        if (bookSource != null) {\n            if (TextUtils.isEmpty(bookSource.bookSourceName) || TextUtils.isEmpty(bookSource.bookSourceUrl)) {\n                returnData.setErrorMsg(\"源名称和URL不能为空\")\n            } else {\n                appDb.bookSourceDao.insert(bookSource)\n                returnData.setData(\"\")\n            }\n        } else {\n            returnData.setErrorMsg(\"转换源失败\")\n        }\n        return returnData\n    }\n\n    fun saveSources(postData: String?): ReturnData {\n        postData ?: return ReturnData().setErrorMsg(\"数据为空\")\n        val okSources = arrayListOf<BookSource>()\n        val bookSources = GSON.fromJsonArray<BookSource>(postData).getOrNull()\n        if (bookSources.isNullOrEmpty()) {\n            return ReturnData().setErrorMsg(\"转换源失败\")\n        }\n        bookSources.forEach { bookSource ->\n            if (bookSource.bookSourceName.isNotBlank()\n                && bookSource.bookSourceUrl.isNotBlank()\n            ) {\n                appDb.bookSourceDao.insert(bookSource)\n                okSources.add(bookSource)\n            }\n        }\n        return ReturnData().setData(okSources)\n    }\n\n    fun getSource(parameters: Map<String, List<String>>): ReturnData {\n        val url = parameters[\"url\"]?.firstOrNull()\n        val returnData = ReturnData()\n        if (url.isNullOrEmpty()) {\n            return returnData.setErrorMsg(\"参数url不能为空，请指定源地址\")\n        }\n        val bookSource = appDb.bookSourceDao.getBookSource(url)\n            ?: return returnData.setErrorMsg(\"未找到源，请检查书源地址\")\n        return returnData.setData(bookSource)\n    }\n\n    fun deleteSources(postData: String?): ReturnData {\n        kotlin.runCatching {\n            GSON.fromJsonArray<BookSource>(postData).getOrThrow().let {\n                SourceHelp.deleteBookSources(it)\n            }\n        }.onFailure {\n            return ReturnData().setErrorMsg(it.localizedMessage ?: \"数据格式错误\")\n        }\n        return ReturnData().setData(\"已执行\"/*okSources*/)\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/api/controller/ReplaceRuleController.kt",
    "content": "package io.legado.app.api.controller\n\nimport io.legado.app.api.ReturnData\nimport io.legado.app.data.appDb\nimport io.legado.app.data.entities.ReplaceRule\nimport io.legado.app.utils.GSON\nimport io.legado.app.utils.fromJsonObject\nimport io.legado.app.utils.replace\nimport io.legado.app.utils.stackTraceStr\n\nobject ReplaceRuleController {\n\n    val allRules: ReturnData\n        get() {\n            val rules = appDb.replaceRuleDao.all\n            val returnData = ReturnData()\n            returnData.setData(GSON.toJson(rules))\n            return returnData\n        }\n\n\n    fun saveRule(postData: String?): ReturnData {\n        val returnData = ReturnData()\n        postData ?: return returnData.setErrorMsg(\"数据不能为空\")\n        val rule = GSON.fromJsonObject<ReplaceRule>(postData).getOrNull()\n        if (rule == null) {\n            returnData.setErrorMsg(\"格式不对\")\n        } else {\n            if (rule.order == Int.MIN_VALUE) {\n                rule.order = appDb.replaceRuleDao.maxOrder + 1\n            }\n            appDb.replaceRuleDao.insert(rule)\n        }\n        return returnData\n    }\n\n\n    fun delete(postData: String?): ReturnData {\n        val returnData = ReturnData()\n        postData ?: return returnData.setErrorMsg(\"数据不能为空\")\n        val rule = GSON.fromJsonObject<ReplaceRule>(postData).getOrNull()\n        if (rule == null) {\n            returnData.setErrorMsg(\"格式不对\")\n        } else {\n            appDb.replaceRuleDao.delete(rule)\n        }\n        return returnData\n    }\n\n    /**\n     * 传入测试数据格式\n     * {\n     *  rule: Replace,\n     *  text: \"xxx\"\n     * }\n     */\n    fun testRule(postData: String?): ReturnData {\n        val returnData = ReturnData()\n        postData ?: return returnData.setErrorMsg(\"数据不能为空\")\n        val map = GSON.fromJsonObject<Map<String, *>>(postData).getOrNull()\n        if (map == null) {\n            returnData.setErrorMsg(\"格式不对\")\n        } else {\n            val rule = map[\"rule\"]?.let {\n                if (it is String) {\n                    GSON.fromJsonObject<ReplaceRule>(it).getOrNull()\n                } else {\n                    GSON.fromJsonObject<ReplaceRule>(GSON.toJson(it)).getOrNull()\n                }\n            }\n            if (rule == null) {\n                returnData.setErrorMsg(\"格式不对\")\n                return returnData\n            }\n            if (rule.pattern.isEmpty()) {\n                returnData.setErrorMsg(\"替换规则不能为空\")\n            }\n            val text = map[\"text\"] as String\n            val content = try {\n                if (rule.isRegex) {\n                    text.replace(\n                        rule.pattern.toRegex(),\n                        rule.replacement,\n                        rule.getValidTimeoutMillisecond()\n                    )\n                } else {\n                    text.replace(rule.pattern, rule.replacement)\n                }\n            } catch (e: Exception) {\n                e.stackTraceStr\n            }\n            returnData.setData(content)\n        }\n        return returnData\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/api/controller/RssSourceController.kt",
    "content": "package io.legado.app.api.controller\n\n\nimport android.text.TextUtils\nimport io.legado.app.api.ReturnData\nimport io.legado.app.data.appDb\nimport io.legado.app.data.entities.RssSource\nimport io.legado.app.help.source.SourceHelp\nimport io.legado.app.utils.GSON\nimport io.legado.app.utils.fromJsonArray\nimport io.legado.app.utils.fromJsonObject\n\nobject RssSourceController {\n\n    val sources: ReturnData\n        get() {\n            val source = appDb.rssSourceDao.all\n            val returnData = ReturnData()\n            return if (source.isEmpty()) {\n                returnData.setErrorMsg(\"源列表为空\")\n            } else returnData.setData(source)\n        }\n\n    fun saveSource(postData: String?): ReturnData {\n        val returnData = ReturnData()\n        postData ?: return returnData.setErrorMsg(\"数据不能为空\")\n        GSON.fromJsonObject<RssSource>(postData).onFailure {\n            returnData.setErrorMsg(\"转换源失败${it.localizedMessage}\")\n        }.onSuccess { source ->\n            if (TextUtils.isEmpty(source.sourceName) || TextUtils.isEmpty(source.sourceUrl)) {\n                returnData.setErrorMsg(\"源名称和URL不能为空\")\n            } else {\n                appDb.rssSourceDao.insert(source)\n                returnData.setData(\"\")\n            }\n        }\n        return returnData\n    }\n\n    fun saveSources(postData: String?): ReturnData {\n        postData ?: return ReturnData().setErrorMsg(\"数据不能为空\")\n        val okSources = arrayListOf<RssSource>()\n        val source = GSON.fromJsonArray<RssSource>(postData).getOrNull()\n        if (source.isNullOrEmpty()) {\n            return ReturnData().setErrorMsg(\"转换源失败\")\n        }\n        for (rssSource in source) {\n            if (rssSource.sourceName.isBlank() || rssSource.sourceUrl.isBlank()) {\n                continue\n            }\n            appDb.rssSourceDao.insert(rssSource)\n            okSources.add(rssSource)\n        }\n        return ReturnData().setData(okSources)\n    }\n\n    fun getSource(parameters: Map<String, List<String>>): ReturnData {\n        val url = parameters[\"url\"]?.firstOrNull()\n        val returnData = ReturnData()\n        if (url.isNullOrEmpty()) {\n            return returnData.setErrorMsg(\"参数url不能为空，请指定书源地址\")\n        }\n        val source = appDb.rssSourceDao.getByKey(url)\n            ?: return returnData.setErrorMsg(\"未找到源，请检查源地址\")\n        return returnData.setData(source)\n    }\n\n    fun deleteSources(postData: String?): ReturnData {\n        postData ?: return ReturnData().setErrorMsg(\"没有传递数据\")\n        GSON.fromJsonArray<RssSource>(postData).onFailure {\n            return ReturnData().setErrorMsg(\"格式不对\")\n        }.onSuccess {\n            SourceHelp.deleteRssSources(it)\n        }\n        return ReturnData().setData(\"已执行\"/*okSources*/)\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/base/AppContextWrapper.kt",
    "content": "package io.legado.app.base\n\nimport android.annotation.SuppressLint\nimport android.content.Context\nimport android.content.res.Configuration\nimport android.content.res.Resources\nimport android.os.Build\nimport android.os.LocaleList\nimport io.legado.app.constant.PreferKey\nimport io.legado.app.utils.getPrefInt\nimport io.legado.app.utils.getPrefString\nimport io.legado.app.utils.sysConfiguration\nimport java.util.*\n\n\n@Suppress(\"unused\")\nobject AppContextWrapper {\n\n    @SuppressLint(\"ObsoleteSdkInt\")\n    fun wrap(context: Context): Context {\n        val resources: Resources = context.resources\n        val configuration: Configuration = resources.configuration\n        val targetLocale = getSetLocale(context)\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {\n            configuration.setLocale(targetLocale)\n            configuration.setLocales(LocaleList(targetLocale))\n        } else {\n            @Suppress(\"DEPRECATION\")\n            configuration.locale = targetLocale\n        }\n        configuration.fontScale = getFontScale(context)\n        return context.createConfigurationContext(configuration)\n    }\n\n    fun getFontScale(context: Context): Float {\n        var fontScale = context.getPrefInt(PreferKey.fontScale) / 10f\n        if (fontScale !in 0.8f..1.6f) {\n            fontScale = sysConfiguration.fontScale\n        }\n        return fontScale\n    }\n\n    /**\n     * 当前系统语言\n     */\n    @SuppressLint(\"ObsoleteSdkInt\")\n    private fun getSystemLocale(): Locale {\n        val locale: Locale\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { //7.0有多语言设置获取顶部的语言\n            locale = sysConfiguration.locales.get(0)\n        } else {\n            @Suppress(\"DEPRECATION\")\n            locale = sysConfiguration.locale\n        }\n        return locale\n    }\n\n    /**\n     * 当前App语言\n     */\n    @SuppressLint(\"ObsoleteSdkInt\")\n    private fun getAppLocale(context: Context): Locale {\n        val locale: Locale\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {\n            locale = context.resources.configuration.locales[0]\n        } else {\n            @Suppress(\"DEPRECATION\")\n            locale = context.resources.configuration.locale\n        }\n        return locale\n\n    }\n\n    /**\n     * 当前设置语言\n     */\n    private fun getSetLocale(context: Context): Locale {\n        return when (context.getPrefString(PreferKey.language)) {\n            \"zh\" -> Locale.SIMPLIFIED_CHINESE\n            \"tw\" -> Locale.TRADITIONAL_CHINESE\n            \"en\" -> Locale.ENGLISH\n            else -> getSystemLocale()\n        }\n    }\n\n    /**\n     * 判断App语言和设置语言是否相同\n     */\n    fun isSameWithSetting(context: Context): Boolean {\n        val locale = getAppLocale(context)\n        val language = locale.language\n        val country = locale.country\n        val pfLocale = getSetLocale(context)\n        val pfLanguage = pfLocale.language\n        val pfCountry = pfLocale.country\n        return language == pfLanguage && country == pfCountry\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/base/BaseActivity.kt",
    "content": "package io.legado.app.base\n\nimport android.annotation.SuppressLint\nimport android.content.Context\nimport android.content.res.Configuration\nimport android.graphics.drawable.BitmapDrawable\nimport android.os.Build\nimport android.os.Bundle\nimport android.util.AttributeSet\nimport android.view.Menu\nimport android.view.MenuItem\nimport android.view.MotionEvent\nimport android.view.View\nimport android.widget.FrameLayout\nimport androidx.activity.addCallback\nimport androidx.annotation.RequiresApi\nimport androidx.appcompat.app.AppCompatActivity\nimport androidx.viewbinding.ViewBinding\nimport io.legado.app.R\nimport io.legado.app.constant.AppConst\nimport io.legado.app.constant.AppLog\nimport io.legado.app.constant.Theme\nimport io.legado.app.help.config.AppConfig\nimport io.legado.app.help.config.ThemeConfig\nimport io.legado.app.lib.theme.ThemeStore\nimport io.legado.app.lib.theme.backgroundColor\nimport io.legado.app.lib.theme.primaryColor\nimport io.legado.app.ui.widget.TitleBar\nimport io.legado.app.utils.ColorUtils\nimport io.legado.app.utils.applyBackgroundTint\nimport io.legado.app.utils.applyOpenTint\nimport io.legado.app.utils.applyTint\nimport io.legado.app.utils.disableAutoFill\nimport io.legado.app.utils.fullScreen\nimport io.legado.app.utils.hideSoftInput\nimport io.legado.app.utils.setLightStatusBar\nimport io.legado.app.utils.setNavigationBarColorAuto\nimport io.legado.app.utils.setStatusBarColorAuto\nimport io.legado.app.utils.toastOnUi\nimport io.legado.app.utils.windowSize\n\n\nabstract class BaseActivity<VB : ViewBinding>(\n    val fullScreen: Boolean = true,\n    private val theme: Theme = Theme.Auto,\n    private val toolBarTheme: Theme = Theme.Auto,\n    private val transparent: Boolean = false,\n    private val imageBg: Boolean = true\n) : AppCompatActivity() {\n\n    protected abstract val binding: VB\n\n    val isInMultiWindow: Boolean\n        @SuppressLint(\"ObsoleteSdkInt\")\n        get() {\n            return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {\n                isInMultiWindowMode\n            } else {\n                false\n            }\n        }\n\n    override fun attachBaseContext(newBase: Context) {\n        super.attachBaseContext(AppContextWrapper.wrap(newBase))\n    }\n\n    override fun onCreateView(\n        parent: View?,\n        name: String,\n        context: Context,\n        attrs: AttributeSet\n    ): View? {\n        if (AppConst.menuViewNames.contains(name) && parent?.parent is FrameLayout) {\n            (parent.parent as View).setBackgroundColor(backgroundColor)\n        }\n        return super.onCreateView(parent, name, context, attrs)\n    }\n\n    @SuppressLint(\"ObsoleteSdkInt\")\n    override fun onCreate(savedInstanceState: Bundle?) {\n        window.decorView.disableAutoFill()\n        initTheme()\n        super.onCreate(savedInstanceState)\n        setupSystemBar()\n        setContentView(binding.root)\n        upBackgroundImage()\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {\n            findViewById<TitleBar>(R.id.title_bar)\n                ?.onMultiWindowModeChanged(isInMultiWindowMode, fullScreen)\n        }\n        onBackPressedDispatcher.addCallback(this) {\n            finish()\n        }\n        observeLiveBus()\n        onActivityCreated(savedInstanceState)\n    }\n\n    @RequiresApi(Build.VERSION_CODES.O)\n    override fun onMultiWindowModeChanged(isInMultiWindowMode: Boolean, newConfig: Configuration) {\n        super.onMultiWindowModeChanged(isInMultiWindowMode, newConfig)\n        findViewById<TitleBar>(R.id.title_bar)\n            ?.onMultiWindowModeChanged(isInMultiWindowMode, fullScreen)\n        setupSystemBar()\n    }\n\n    override fun onConfigurationChanged(newConfig: Configuration) {\n        super.onConfigurationChanged(newConfig)\n        findViewById<TitleBar>(R.id.title_bar)\n            ?.onMultiWindowModeChanged(isInMultiWindow, fullScreen)\n        setupSystemBar()\n    }\n\n    abstract fun onActivityCreated(savedInstanceState: Bundle?)\n\n    final override fun onCreateOptionsMenu(menu: Menu): Boolean {\n        val bool = onCompatCreateOptionsMenu(menu)\n        menu.applyTint(this, toolBarTheme)\n        return bool\n    }\n\n    override fun onMenuOpened(featureId: Int, menu: Menu): Boolean {\n        menu.applyOpenTint(this)\n        return super.onMenuOpened(featureId, menu)\n    }\n\n    open fun onCompatCreateOptionsMenu(menu: Menu) = super.onCreateOptionsMenu(menu)\n\n    final override fun onOptionsItemSelected(item: MenuItem): Boolean {\n        if (item.itemId == android.R.id.home) {\n            supportFinishAfterTransition()\n            return true\n        }\n        return onCompatOptionsItemSelected(item)\n    }\n\n    open fun onCompatOptionsItemSelected(item: MenuItem) = super.onOptionsItemSelected(item)\n\n    open fun initTheme() {\n        when (theme) {\n            Theme.Transparent -> setTheme(R.style.AppTheme_Transparent)\n            Theme.Dark -> {\n                setTheme(R.style.AppTheme_Dark)\n                window.decorView.applyBackgroundTint(backgroundColor)\n            }\n\n            Theme.Light -> {\n                setTheme(R.style.AppTheme_Light)\n                window.decorView.applyBackgroundTint(backgroundColor)\n            }\n\n            else -> {\n                if (ColorUtils.isColorLight(primaryColor)) {\n                    setTheme(R.style.AppTheme_Light)\n                } else {\n                    setTheme(R.style.AppTheme_Dark)\n                }\n                window.decorView.applyBackgroundTint(backgroundColor)\n            }\n        }\n    }\n\n    open fun upBackgroundImage() {\n        if (imageBg) {\n            try {\n                ThemeConfig.getBgImage(this, windowManager.windowSize)?.let {\n                    window.decorView.background = BitmapDrawable(resources, it)\n                }\n            } catch (e: OutOfMemoryError) {\n                toastOnUi(\"背景图片太大,内存溢出\")\n            } catch (e: Exception) {\n                AppLog.put(\"加载背景出错\\n${e.localizedMessage}\", e)\n            }\n        }\n    }\n\n    open fun setupSystemBar() {\n        if (fullScreen && !isInMultiWindow) {\n            fullScreen()\n        }\n        val isTransparentStatusBar = AppConfig.isTransparentStatusBar\n        val statusBarColor = ThemeStore.statusBarColor(this, isTransparentStatusBar)\n        setStatusBarColorAuto(statusBarColor, isTransparentStatusBar, fullScreen)\n        if (toolBarTheme == Theme.Dark) {\n            setLightStatusBar(false)\n        } else if (toolBarTheme == Theme.Light) {\n            setLightStatusBar(true)\n        }\n        upNavigationBarColor()\n    }\n\n    open fun upNavigationBarColor() {\n        if (AppConfig.immNavigationBar) {\n            setNavigationBarColorAuto(ThemeStore.navigationBarColor(this))\n        } else {\n            val nbColor = ColorUtils.darkenColor(ThemeStore.navigationBarColor(this))\n            setNavigationBarColorAuto(nbColor)\n        }\n    }\n\n    open fun observeLiveBus() {\n    }\n\n    override fun dispatchTouchEvent(ev: MotionEvent): Boolean {\n        return try {\n            super.dispatchTouchEvent(ev)\n        } catch (e: IllegalArgumentException) {\n            e.printStackTrace()\n            false\n        }\n    }\n\n    override fun finish() {\n        currentFocus?.hideSoftInput()\n        super.finish()\n    }\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/base/BaseDialogFragment.kt",
    "content": "package io.legado.app.base\n\nimport android.content.DialogInterface\nimport android.content.DialogInterface.OnDismissListener\nimport android.os.Build\nimport android.os.Bundle\nimport android.view.Gravity\nimport android.view.View\nimport android.view.WindowManager\nimport androidx.annotation.LayoutRes\nimport androidx.fragment.app.DialogFragment\nimport androidx.fragment.app.FragmentManager\nimport androidx.lifecycle.Lifecycle\nimport androidx.lifecycle.LifecycleEventObserver\nimport androidx.lifecycle.lifecycleScope\nimport io.legado.app.R\nimport io.legado.app.constant.AppLog\nimport io.legado.app.help.config.AppConfig\nimport io.legado.app.help.coroutine.Coroutine\nimport io.legado.app.lib.theme.ThemeStore\nimport io.legado.app.utils.dpToPx\nimport io.legado.app.utils.setBackgroundKeepPadding\nimport kotlinx.coroutines.CoroutineScope\nimport kotlinx.coroutines.Dispatchers\nimport kotlin.coroutines.CoroutineContext\n\n\nabstract class BaseDialogFragment(\n    @LayoutRes layoutID: Int,\n    private val adaptationSoftKeyboard: Boolean = false\n) : DialogFragment(layoutID) {\n\n    private var onDismissListener: OnDismissListener? = null\n\n    fun setOnDismissListener(onDismissListener: OnDismissListener?) {\n        this.onDismissListener = onDismissListener\n    }\n\n    override fun onStart() {\n        super.onStart()\n        if (adaptationSoftKeyboard) {\n            dialog?.window?.setBackgroundDrawableResource(R.color.transparent)\n        } else if (AppConfig.isEInkMode) {\n            dialog?.window?.let {\n                it.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND)\n                val attr = it.attributes\n                attr.dimAmount = 0.0f\n                attr.windowAnimations = 0\n                it.attributes = attr\n                it.decorView.setBackgroundKeepPadding(R.color.transparent)\n            }\n            // 修改gravity的时机一般在子类的onStart方法中, 因此需要在onStart之后执行.\n            lifecycle.addObserver(LifecycleEventObserver { _, event ->\n                if (event == Lifecycle.Event.ON_START) {\n                    when (dialog?.window?.attributes?.gravity) {\n                        Gravity.TOP -> view?.setBackgroundResource(R.drawable.bg_eink_border_bottom)\n                        Gravity.BOTTOM -> view?.setBackgroundResource(R.drawable.bg_eink_border_top)\n                        else -> {\n                            val padding = 2.dpToPx();\n                            view?.setPadding(padding, padding, padding, padding)\n                            view?.setBackgroundResource(R.drawable.bg_eink_border_dialog)\n                        }\n                    }\n                }\n            })\n        }\n    }\n\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {\n            //不加这个android 5.0对话框顶部会有空白\n            setStyle(STYLE_NO_TITLE, 0)\n        }\n    }\n\n    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n        super.onViewCreated(view, savedInstanceState)\n        if (adaptationSoftKeyboard) {\n            view.findViewById<View>(R.id.vw_bg)?.setOnClickListener(null)\n            view.setOnClickListener { dismiss() }\n        } else if (!AppConfig.isEInkMode) {\n            view.setBackgroundColor(ThemeStore.backgroundColor())\n        }\n        onFragmentCreated(view, savedInstanceState)\n        observeLiveBus()\n    }\n\n    abstract fun onFragmentCreated(view: View, savedInstanceState: Bundle?)\n\n    override fun show(manager: FragmentManager, tag: String?) {\n        kotlin.runCatching {\n            //在每个add事务前增加一个remove事务，防止连续的add\n            manager.beginTransaction().remove(this).commit()\n            super.show(manager, tag)\n        }.onFailure {\n            AppLog.put(\"显示对话框失败 tag:$tag\", it)\n        }\n    }\n\n    override fun onDismiss(dialog: DialogInterface) {\n        super.onDismiss(dialog)\n        onDismissListener?.onDismiss(dialog)\n    }\n\n    fun <T> execute(\n        scope: CoroutineScope = lifecycleScope,\n        context: CoroutineContext = Dispatchers.IO,\n        block: suspend CoroutineScope.() -> T\n    ) = Coroutine.async(scope, context) { block() }\n\n    open fun observeLiveBus() {\n    }\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/base/BaseFragment.kt",
    "content": "package io.legado.app.base\n\nimport android.annotation.SuppressLint\nimport android.content.res.Configuration\nimport android.os.Bundle\nimport android.view.Menu\nimport android.view.MenuInflater\nimport android.view.MenuItem\nimport android.view.View\nimport androidx.annotation.LayoutRes\nimport androidx.appcompat.view.SupportMenuInflater\nimport androidx.appcompat.widget.Toolbar\nimport androidx.fragment.app.Fragment\nimport io.legado.app.R\nimport io.legado.app.ui.widget.TitleBar\nimport io.legado.app.utils.applyTint\n\n@Suppress(\"MemberVisibilityCanBePrivate\")\nabstract class BaseFragment(@LayoutRes layoutID: Int) : Fragment(layoutID) {\n\n    var supportToolbar: Toolbar? = null\n        private set\n\n    val menuInflater: MenuInflater\n        @SuppressLint(\"RestrictedApi\")\n        get() = SupportMenuInflater(requireContext())\n\n    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n        super.onViewCreated(view, savedInstanceState)\n        onMultiWindowModeChanged()\n        observeLiveBus()\n        onFragmentCreated(view, savedInstanceState)\n    }\n\n    abstract fun onFragmentCreated(view: View, savedInstanceState: Bundle?)\n\n    override fun onMultiWindowModeChanged(isInMultiWindowMode: Boolean) {\n        super.onMultiWindowModeChanged(isInMultiWindowMode)\n        onMultiWindowModeChanged()\n    }\n\n    override fun onConfigurationChanged(newConfig: Configuration) {\n        super.onConfigurationChanged(newConfig)\n        onMultiWindowModeChanged()\n    }\n\n    private fun onMultiWindowModeChanged() {\n        (activity as? BaseActivity<*>)?.let {\n            view?.findViewById<TitleBar>(R.id.title_bar)\n                ?.onMultiWindowModeChanged(it.isInMultiWindow, it.fullScreen)\n        }\n    }\n\n    fun setSupportToolbar(toolbar: Toolbar) {\n        supportToolbar = toolbar\n        supportToolbar?.let {\n            it.menu.apply {\n                onCompatCreateOptionsMenu(this)\n                applyTint(requireContext())\n            }\n\n            it.setOnMenuItemClickListener { item ->\n                onCompatOptionsItemSelected(item)\n                true\n            }\n        }\n    }\n\n    open fun observeLiveBus() {\n    }\n\n    open fun onCompatCreateOptionsMenu(menu: Menu) {\n    }\n\n    open fun onCompatOptionsItemSelected(item: MenuItem) {\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/base/BasePrefDialogFragment.kt",
    "content": "package io.legado.app.base\n\nimport android.view.Gravity\nimport android.view.WindowManager\nimport androidx.fragment.app.DialogFragment\nimport androidx.lifecycle.Lifecycle\nimport androidx.lifecycle.LifecycleEventObserver\nimport io.legado.app.R\nimport io.legado.app.help.config.AppConfig\nimport io.legado.app.utils.dpToPx\n\n\nabstract class BasePrefDialogFragment(\n) : DialogFragment() {\n\n    override fun onStart() {\n        super.onStart()\n        if (AppConfig.isEInkMode) {\n            dialog?.window?.let {\n                it.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND)\n                val attr = it.attributes\n                attr.dimAmount = 0.0f\n                attr.windowAnimations = 0\n                it.attributes = attr\n                it.setBackgroundDrawableResource(R.color.transparent)\n            }\n\n            // 修改gravity的时机一般在子类的onStart方法中, 因此需要在onStart之后执行.\n            lifecycle.addObserver(LifecycleEventObserver { _, event ->\n                if (event == Lifecycle.Event.ON_START) {\n                    when (dialog?.window?.attributes?.gravity) {\n                        Gravity.TOP -> view?.setBackgroundResource(R.drawable.bg_eink_border_bottom)\n                        Gravity.BOTTOM -> view?.setBackgroundResource(R.drawable.bg_eink_border_top)\n                        else -> {\n                            val padding = 2.dpToPx();\n                            view?.setPadding(padding, padding, padding, padding)\n                            view?.setBackgroundResource(R.drawable.bg_eink_border_dialog)\n                        }\n                    }\n                }\n            })\n        }\n    }\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/base/BaseService.kt",
    "content": "package io.legado.app.base\n\nimport android.content.Intent\nimport android.os.Build\nimport android.os.IBinder\nimport androidx.annotation.CallSuper\nimport androidx.lifecycle.LifecycleService\nimport androidx.lifecycle.lifecycleScope\nimport io.legado.app.R\nimport io.legado.app.help.LifecycleHelp\nimport io.legado.app.help.coroutine.Coroutine\nimport io.legado.app.lib.permission.Permissions\nimport io.legado.app.lib.permission.PermissionsCompat\nimport io.legado.app.utils.LogUtils\nimport kotlinx.coroutines.CoroutineScope\nimport kotlinx.coroutines.CoroutineStart\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.isActive\nimport kotlinx.coroutines.sync.Semaphore\nimport kotlin.coroutines.CoroutineContext\n\nabstract class BaseService : LifecycleService() {\n\n    private val simpleName = this::class.simpleName.toString()\n    private var isForeground = false\n\n    fun <T> execute(\n        scope: CoroutineScope = lifecycleScope,\n        context: CoroutineContext = Dispatchers.IO,\n        start: CoroutineStart = CoroutineStart.DEFAULT,\n        executeContext: CoroutineContext = Dispatchers.Main,\n        semaphore: Semaphore? = null,\n        block: suspend CoroutineScope.() -> T\n    ) = Coroutine.async(scope, context, start, executeContext, semaphore, block)\n\n    @CallSuper\n    override fun onCreate() {\n        super.onCreate()\n        LifecycleHelp.onServiceCreate(this)\n        checkPermission()\n    }\n\n    @CallSuper\n    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {\n        LogUtils.d(simpleName) {\n            \"onStartCommand $intent ${intent?.toUri(0)}\"\n        }\n        if (!isForeground) {\n            startForegroundNotification()\n            isForeground = true\n        }\n        return super.onStartCommand(intent, flags, startId)\n    }\n\n    @CallSuper\n    override fun onTaskRemoved(rootIntent: Intent?) {\n        LogUtils.d(simpleName, \"onTaskRemoved\")\n        super.onTaskRemoved(rootIntent)\n        stopSelf()\n    }\n\n    override fun onBind(intent: Intent): IBinder? {\n        super.onBind(intent)\n        return null\n    }\n\n    @CallSuper\n    override fun onDestroy() {\n        super.onDestroy()\n        LifecycleHelp.onServiceDestroy(this)\n    }\n\n    @CallSuper\n    override fun onTimeout(startId: Int, fgsType: Int) {\n        super.onTimeout(startId, fgsType)\n        LogUtils.d(simpleName, \"onTimeout startId:$startId fgsType:$fgsType\")\n        stopSelf()\n    }\n\n    /**\n     * 开启前台服务并发送通知\n     */\n    open fun startForegroundNotification() {\n\n    }\n\n    /**\n     * 检测通知权限和后台权限\n     */\n    private fun checkPermission() {\n        PermissionsCompat.Builder()\n            .addPermissions(Permissions.POST_NOTIFICATIONS)\n            .rationale(R.string.notification_permission_rationale)\n            .onGranted {\n                if (lifecycleScope.isActive) {\n                    startForegroundNotification()\n                }\n            }\n            .request()\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {\n            PermissionsCompat.Builder()\n                .addPermissions(Permissions.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS)\n                .rationale(R.string.ignore_battery_permission_rationale)\n                .request()\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/base/BaseViewModel.kt",
    "content": "package io.legado.app.base\n\nimport android.app.Application\nimport android.content.Context\nimport androidx.lifecycle.AndroidViewModel\nimport androidx.lifecycle.viewModelScope\nimport io.legado.app.App\nimport io.legado.app.help.coroutine.Coroutine\nimport kotlinx.coroutines.CoroutineScope\nimport kotlinx.coroutines.CoroutineStart\nimport kotlinx.coroutines.Deferred\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.sync.Semaphore\nimport kotlin.coroutines.CoroutineContext\n\n@Suppress(\"unused\")\nopen class BaseViewModel(application: Application) : AndroidViewModel(application) {\n\n    val context: Context by lazy { this.getApplication<App>() }\n\n    fun <T> execute(\n        scope: CoroutineScope = viewModelScope,\n        context: CoroutineContext = Dispatchers.IO,\n        start: CoroutineStart = CoroutineStart.DEFAULT,\n        executeContext: CoroutineContext = Dispatchers.Main,\n        semaphore: Semaphore? = null,\n        block: suspend CoroutineScope.() -> T\n    ): Coroutine<T> {\n        return Coroutine.async(scope, context, start, executeContext, semaphore, block)\n    }\n\n    fun <T> executeLazy(\n        scope: CoroutineScope = viewModelScope,\n        context: CoroutineContext = Dispatchers.IO,\n        executeContext: CoroutineContext = Dispatchers.Main,\n        semaphore: Semaphore? = null,\n        block: suspend CoroutineScope.() -> T\n    ): Coroutine<T> {\n        return Coroutine.async(\n            scope, context, CoroutineStart.LAZY, executeContext, semaphore, block\n        )\n    }\n\n    fun <R> submit(\n        scope: CoroutineScope = viewModelScope,\n        context: CoroutineContext = Dispatchers.IO,\n        block: suspend CoroutineScope.() -> Deferred<R>\n    ): Coroutine<R> {\n        return Coroutine.async(scope, context) { block().await() }\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/base/README.md",
    "content": "# 基类"
  },
  {
    "path": "app/src/main/java/io/legado/app/base/VMBaseActivity.kt",
    "content": "package io.legado.app.base\n\nimport androidx.lifecycle.ViewModel\nimport androidx.viewbinding.ViewBinding\nimport io.legado.app.constant.Theme\n\nabstract class VMBaseActivity<VB : ViewBinding, VM : ViewModel>(\n    fullScreen: Boolean = true,\n    theme: Theme = Theme.Auto,\n    toolBarTheme: Theme = Theme.Auto,\n    transparent: Boolean = false,\n    imageBg: Boolean = true\n) : BaseActivity<VB>(fullScreen, theme, toolBarTheme, transparent, imageBg) {\n\n    protected abstract val viewModel: VM\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/base/VMBaseFragment.kt",
    "content": "package io.legado.app.base\n\nimport androidx.lifecycle.ViewModel\n\nabstract class VMBaseFragment<VM : ViewModel>(layoutID: Int) : BaseFragment(layoutID) {\n\n    protected abstract val viewModel: VM\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/base/adapter/DiffRecyclerAdapter.kt",
    "content": "package io.legado.app.base.adapter\n\nimport android.content.Context\nimport android.os.Parcelable\nimport android.view.LayoutInflater\nimport android.view.ViewGroup\nimport androidx.recyclerview.widget.AsyncListDiffer\nimport androidx.recyclerview.widget.DiffUtil\nimport androidx.recyclerview.widget.GridLayoutManager\nimport androidx.recyclerview.widget.RecyclerView\nimport androidx.viewbinding.ViewBinding\nimport splitties.views.onLongClick\n\n/**\n * Created by Invincible on 2017/12/15.\n */\n@Suppress(\"unused\", \"MemberVisibilityCanBePrivate\")\nabstract class DiffRecyclerAdapter<ITEM, VB : ViewBinding>(protected val context: Context) :\n    RecyclerView.Adapter<ItemViewHolder>() {\n\n    val inflater: LayoutInflater = LayoutInflater.from(context)\n\n    private val asyncListDiffer: AsyncListDiffer<ITEM> by lazy {\n        AsyncListDiffer(this, diffItemCallback).apply {\n            addListListener { _, _ ->\n                onCurrentListChanged()\n                if (keepScrollPosition) {\n                    layoutManager?.onRestoreInstanceState(layoutState)\n                    layoutState = null\n                }\n            }\n        }\n    }\n\n    private var itemClickListener: ((holder: ItemViewHolder, item: ITEM) -> Unit)? = null\n    private var itemLongClickListener: ((holder: ItemViewHolder, item: ITEM) -> Boolean)? = null\n\n    private var layoutManager: RecyclerView.LayoutManager? = null\n    private var layoutState: Parcelable? = null\n\n    var itemAnimation: ItemAnimation? = null\n\n    abstract val diffItemCallback: DiffUtil.ItemCallback<ITEM>\n\n    open val keepScrollPosition = false\n\n    fun setOnItemClickListener(listener: (holder: ItemViewHolder, item: ITEM) -> Unit) {\n        itemClickListener = listener\n    }\n\n    fun setOnItemLongClickListener(listener: (holder: ItemViewHolder, item: ITEM) -> Boolean) {\n        itemLongClickListener = listener\n    }\n\n    fun bindToRecyclerView(recyclerView: RecyclerView) {\n        recyclerView.adapter = this\n    }\n\n    fun setItems(items: List<ITEM>?) {\n        kotlin.runCatching {\n            if (keepScrollPosition) {\n                layoutState = layoutManager?.onSaveInstanceState()\n            }\n            asyncListDiffer.submitList(items?.toMutableList())\n        }\n    }\n\n    fun setItem(position: Int, item: ITEM) {\n        kotlin.runCatching {\n            asyncListDiffer.currentList[position] = item\n            notifyItemChanged(position)\n        }\n    }\n\n    fun updateItem(item: ITEM) {\n        kotlin.runCatching {\n            val index = asyncListDiffer.currentList.indexOf(item)\n            if (index >= 0) {\n                asyncListDiffer.currentList[index] = item\n                notifyItemChanged(index)\n            }\n        }\n    }\n\n    fun updateItem(position: Int, payload: Any) {\n        kotlin.runCatching {\n            val size = itemCount\n            if (position in 0 until size) {\n                notifyItemChanged(position, payload)\n            }\n        }\n    }\n\n    fun updateItems(fromPosition: Int, toPosition: Int, payloads: Any) {\n        kotlin.runCatching {\n            val size = itemCount\n            if (fromPosition in 0 until size && toPosition in 0 until size) {\n                notifyItemRangeChanged(\n                    fromPosition,\n                    toPosition - fromPosition + 1,\n                    payloads\n                )\n            }\n        }\n    }\n\n    fun isEmpty() = asyncListDiffer.currentList.isEmpty()\n\n    fun isNotEmpty() = asyncListDiffer.currentList.isNotEmpty()\n\n    fun getItem(position: Int): ITEM? = asyncListDiffer.currentList.getOrNull(position)\n\n    fun getItems(): List<ITEM> = asyncListDiffer.currentList\n\n    /**\n     * grid 模式下使用\n     */\n    protected open fun getSpanSize(viewType: Int, position: Int) = 1\n\n    final override fun getItemCount() = getItems().size\n\n    final override fun getItemViewType(position: Int): Int {\n        return 0\n    }\n\n    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ItemViewHolder {\n        return ItemViewHolder(getViewBinding(parent))\n    }\n\n    protected abstract fun getViewBinding(parent: ViewGroup): VB\n\n    final override fun onBindViewHolder(holder: ItemViewHolder, position: Int) {}\n\n    open fun onCurrentListChanged() {\n        //可继承\n    }\n\n    @Suppress(\"UNCHECKED_CAST\")\n    final override fun onBindViewHolder(\n        holder: ItemViewHolder,\n        position: Int,\n        payloads: MutableList<Any>\n    ) {\n        registerListener(holder, (holder.binding as VB))\n        registerItemListener(holder)\n        getItem(holder.layoutPosition)?.let {\n            convert(holder, holder.binding, it, payloads)\n        }\n    }\n\n    private fun registerItemListener(holder: ItemViewHolder) {\n        if (itemClickListener != null) {\n            holder.itemView.setOnClickListener {\n                getItem(holder.layoutPosition)?.let {\n                    itemClickListener?.invoke(holder, it)\n                }\n            }\n        }\n\n        if (itemLongClickListener != null) {\n            holder.itemView.onLongClick {\n                getItem(holder.layoutPosition)?.let {\n                    itemLongClickListener?.invoke(holder, it)\n                }\n            }\n        }\n    }\n\n    override fun onViewAttachedToWindow(holder: ItemViewHolder) {\n        super.onViewAttachedToWindow(holder)\n        addAnimation(holder)\n    }\n\n    override fun onAttachedToRecyclerView(recyclerView: RecyclerView) {\n        super.onAttachedToRecyclerView(recyclerView)\n        val manager = recyclerView.layoutManager\n        layoutManager = manager\n        if (manager is GridLayoutManager) {\n            manager.spanSizeLookup = object : GridLayoutManager.SpanSizeLookup() {\n                override fun getSpanSize(position: Int): Int {\n                    return getSpanSize(getItemViewType(position), position)\n                }\n            }\n        }\n    }\n\n    private fun addAnimation(holder: ItemViewHolder) {\n        itemAnimation?.let {\n            if (it.itemAnimEnabled) {\n                if (!it.itemAnimFirstOnly || holder.layoutPosition > it.itemAnimStartPosition) {\n                    startAnimation(holder, it)\n                    it.itemAnimStartPosition = holder.layoutPosition\n                }\n            }\n        }\n    }\n\n    protected open fun startAnimation(holder: ItemViewHolder, item: ItemAnimation) {\n        item.itemAnimation?.let {\n            for (anim in it.getAnimators(holder.itemView)) {\n                anim.setDuration(item.itemAnimDuration).start()\n                anim.interpolator = item.itemAnimInterpolator\n            }\n        }\n    }\n\n    /**\n     * 如果使用了事件回调,回调里不要直接使用item,会出现不更新的问题,\n     * 使用getItem(holder.layoutPosition)来获取item\n     */\n    abstract fun convert(\n        holder: ItemViewHolder,\n        binding: VB,\n        item: ITEM,\n        payloads: MutableList<Any>\n    )\n\n    /**\n     * 注册事件\n     */\n    abstract fun registerListener(holder: ItemViewHolder, binding: VB)\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/base/adapter/ItemAnimation.kt",
    "content": "package io.legado.app.base.adapter\n\nimport android.view.animation.Interpolator\nimport android.view.animation.LinearInterpolator\nimport io.legado.app.base.adapter.animations.*\n\n/**\n * Created by Invincible on 2017/12/15.\n */\n@Suppress(\"unused\")\nclass ItemAnimation private constructor() {\n\n    var itemAnimEnabled = false\n    var itemAnimFirstOnly = true\n    var itemAnimation: BaseAnimation? = null\n    var itemAnimInterpolator: Interpolator = LinearInterpolator()\n    var itemAnimDuration: Long = 300L\n    var itemAnimStartPosition: Int = -1\n\n    fun interpolator(interpolator: Interpolator) = apply {\n        itemAnimInterpolator = interpolator\n    }\n\n    fun duration(duration: Long) = apply {\n        itemAnimDuration = duration\n    }\n\n    fun startPosition(startPos: Int) = apply {\n        itemAnimStartPosition = startPos\n    }\n\n    fun animation(animationType: Int = NONE, animation: BaseAnimation? = null) = apply {\n        if (animation != null) {\n            itemAnimation = animation\n        } else {\n            when (animationType) {\n                FADE_IN -> itemAnimation = AlphaInAnimation()\n                SCALE_IN -> itemAnimation = ScaleInAnimation()\n                BOTTOM_SLIDE_IN -> itemAnimation = SlideInBottomAnimation()\n                LEFT_SLIDE_IN -> itemAnimation = SlideInLeftAnimation()\n                RIGHT_SLIDE_IN -> itemAnimation = SlideInRightAnimation()\n            }\n        }\n    }\n\n    fun enabled(enabled: Boolean) = apply {\n        itemAnimEnabled = enabled\n    }\n\n    fun firstOnly(firstOnly: Boolean) = apply {\n        itemAnimFirstOnly = firstOnly\n    }\n\n    companion object {\n        const val NONE: Int = 0x00000000\n\n        /**\n         * Use with [.openLoadAnimation]\n         */\n        const val FADE_IN: Int = 0x00000001\n\n        /**\n         * Use with [.openLoadAnimation]\n         */\n        const val SCALE_IN: Int = 0x00000002\n\n        /**\n         * Use with [.openLoadAnimation]\n         */\n        const val BOTTOM_SLIDE_IN: Int = 0x00000003\n\n        /**\n         * Use with [.openLoadAnimation]\n         */\n        const val LEFT_SLIDE_IN: Int = 0x00000004\n\n        /**\n         * Use with [.openLoadAnimation]\n         */\n        const val RIGHT_SLIDE_IN: Int = 0x00000005\n\n        fun create() = ItemAnimation()\n\n    }\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/base/adapter/ItemViewHolder.kt",
    "content": "package io.legado.app.base.adapter\n\nimport androidx.recyclerview.widget.RecyclerView\nimport androidx.viewbinding.ViewBinding\n\n/**\n * Created by Invincible on 2017/11/28.\n */\n@Suppress(\"MemberVisibilityCanBePrivate\")\nclass ItemViewHolder(val binding: ViewBinding) : RecyclerView.ViewHolder(binding.root)"
  },
  {
    "path": "app/src/main/java/io/legado/app/base/adapter/RecyclerAdapter.kt",
    "content": "package io.legado.app.base.adapter\n\nimport android.annotation.SuppressLint\nimport android.content.Context\nimport android.util.SparseArray\nimport android.view.LayoutInflater\nimport android.view.ViewGroup\nimport androidx.recyclerview.widget.DiffUtil\nimport androidx.recyclerview.widget.GridLayoutManager\nimport androidx.recyclerview.widget.RecyclerView\nimport androidx.viewbinding.ViewBinding\nimport io.legado.app.help.coroutine.Coroutine\nimport io.legado.app.utils.buildMainHandler\nimport io.legado.app.utils.withTimeoutOrNullAsync\nimport kotlinx.coroutines.ensureActive\nimport splitties.views.onLongClick\nimport java.util.Collections\n\n/**\n * Created by Invincible on 2017/11/24.\n *\n * 通用的adapter 可添加header，footer，以及不同类型item\n */\n@Suppress(\"unused\", \"MemberVisibilityCanBePrivate\")\nabstract class RecyclerAdapter<ITEM, VB : ViewBinding>(protected val context: Context) :\n    RecyclerView.Adapter<ItemViewHolder>() {\n\n    val inflater: LayoutInflater = LayoutInflater.from(context)\n\n    private val headerItems: SparseArray<(parent: ViewGroup) -> ViewBinding> by lazy { SparseArray() }\n    private val footerItems: SparseArray<(parent: ViewGroup) -> ViewBinding> by lazy { SparseArray() }\n\n    private val items: MutableList<ITEM> = mutableListOf()\n\n    private var itemClickListener: ((holder: ItemViewHolder, item: ITEM) -> Unit)? = null\n    private var itemLongClickListener: ((holder: ItemViewHolder, item: ITEM) -> Boolean)? = null\n\n    private var diffJob: Coroutine<*>? = null\n\n    var itemAnimation: ItemAnimation? = null\n\n    fun setOnItemClickListener(listener: (holder: ItemViewHolder, item: ITEM) -> Unit) {\n        itemClickListener = listener\n    }\n\n    fun setOnItemLongClickListener(listener: (holder: ItemViewHolder, item: ITEM) -> Boolean) {\n        itemLongClickListener = listener\n    }\n\n    fun bindToRecyclerView(recyclerView: RecyclerView) {\n        recyclerView.adapter = this\n    }\n\n    @Synchronized\n    fun addHeaderView(header: ((parent: ViewGroup) -> ViewBinding)) {\n        kotlin.runCatching {\n            val index = headerItems.size()\n            headerItems.put(TYPE_HEADER_VIEW + headerItems.size(), header)\n            notifyItemInserted(index)\n        }\n    }\n\n    @Synchronized\n    fun addFooterView(footer: ((parent: ViewGroup) -> ViewBinding)) {\n        kotlin.runCatching {\n            val index = getActualItemCount() + footerItems.size()\n            footerItems.put(TYPE_FOOTER_VIEW + footerItems.size(), footer)\n            notifyItemInserted(index)\n        }\n    }\n\n    @Synchronized\n    fun removeHeaderView(header: ((parent: ViewGroup) -> ViewBinding)) {\n        kotlin.runCatching {\n            val index = headerItems.indexOfValue(header)\n            if (index >= 0) {\n                headerItems.remove(index)\n                notifyItemRemoved(index)\n            }\n        }\n    }\n\n    @Synchronized\n    fun removeFooterView(footer: ((parent: ViewGroup) -> ViewBinding)) {\n        kotlin.runCatching {\n            val index = footerItems.indexOfValue(footer)\n            if (index >= 0) {\n                footerItems.remove(index)\n                notifyItemRemoved(getActualItemCount() + index - 2)\n            }\n        }\n    }\n\n    @SuppressLint(\"NotifyDataSetChanged\")\n    @Synchronized\n    fun setItems(items: List<ITEM>?) {\n        kotlin.runCatching {\n            if (this.items.isNotEmpty()) {\n                this.items.clear()\n            }\n            if (items != null) {\n                this.items.addAll(items)\n            }\n            notifyDataSetChanged()\n            onCurrentListChanged()\n        }\n    }\n\n    @Synchronized\n    fun setItems(\n        items: List<ITEM>?,\n        itemCallback: DiffUtil.ItemCallback<ITEM>,\n        skipDiff: Boolean = false\n    ) {\n        kotlin.runCatching {\n            val oldItems = this.items.toList()\n            val itemsSize = items?.size ?: 0\n            val headerCount = getHeaderCount()\n            val footerCount = getFooterCount()\n            val callback = object : DiffUtil.Callback() {\n                override fun getOldListSize(): Int {\n                    return itemCount\n                }\n\n                override fun getNewListSize(): Int {\n                    return itemsSize + headerCount + footerCount\n                }\n\n                override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {\n                    val oldItem = oldItems.getOrNull(oldItemPosition - headerCount)\n                        ?: return true\n                    val newItem = items?.getOrNull(newItemPosition - headerCount)\n                        ?: return true\n                    return itemCallback.areItemsTheSame(oldItem, newItem)\n                }\n\n                override fun areContentsTheSame(\n                    oldItemPosition: Int,\n                    newItemPosition: Int\n                ): Boolean {\n                    val oldItem = oldItems.getOrNull(oldItemPosition - headerCount)\n                        ?: return true\n                    val newItem = items?.getOrNull(newItemPosition - headerCount)\n                        ?: return true\n                    return itemCallback.areContentsTheSame(oldItem, newItem)\n                }\n\n                override fun getChangePayload(oldItemPosition: Int, newItemPosition: Int): Any? {\n                    val oldItem = oldItems.getOrNull(oldItemPosition - headerCount)\n                        ?: return null\n                    val newItem = items?.getOrNull(newItemPosition - headerCount)\n                        ?: return null\n                    return itemCallback.getChangePayload(oldItem, newItem)\n                }\n            }\n            diffJob?.cancel()\n            diffJob = Coroutine.async {\n                val diffResult = if (skipDiff) withTimeoutOrNullAsync(500L) {\n                    DiffUtil.calculateDiff(callback, itemsSize < 2000)\n                } else {\n                    DiffUtil.calculateDiff(callback, itemsSize < 2000)\n                }\n                ensureActive()\n                handler.post {\n                    if (diffResult == null) {\n                        setItems(items)\n                        return@post\n                    }\n                    if (this@RecyclerAdapter.items.isNotEmpty()) {\n                        this@RecyclerAdapter.items.clear()\n                    }\n                    if (items != null) {\n                        this@RecyclerAdapter.items.addAll(items)\n                    }\n                    diffResult.dispatchUpdatesTo(this@RecyclerAdapter)\n                    onCurrentListChanged()\n                }\n            }\n        }\n    }\n\n    @Synchronized\n    fun setItem(position: Int, item: ITEM) {\n        kotlin.runCatching {\n            val oldSize = getActualItemCount()\n            if (position in 0 until oldSize) {\n                this.items[position] = item\n                notifyItemChanged(position + getHeaderCount())\n            }\n            onCurrentListChanged()\n        }\n    }\n\n    @Synchronized\n    fun addItem(item: ITEM) {\n        kotlin.runCatching {\n            val oldSize = getActualItemCount()\n            if (this.items.add(item)) {\n                notifyItemInserted(oldSize + getHeaderCount())\n            }\n            onCurrentListChanged()\n        }\n    }\n\n    @Synchronized\n    fun addItems(position: Int, newItems: List<ITEM>) {\n        kotlin.runCatching {\n            if (this.items.addAll(position, newItems)) {\n                notifyItemRangeInserted(position + getHeaderCount(), newItems.size)\n            }\n            onCurrentListChanged()\n        }\n    }\n\n    @SuppressLint(\"NotifyDataSetChanged\")\n    @Synchronized\n    fun addItems(newItems: List<ITEM>) {\n        kotlin.runCatching {\n            val oldSize = getActualItemCount()\n            if (this.items.addAll(newItems)) {\n                if (oldSize == 0 && getHeaderCount() == 0) {\n                    notifyDataSetChanged()\n                } else {\n                    notifyItemRangeInserted(oldSize + getHeaderCount(), newItems.size)\n                }\n            }\n            onCurrentListChanged()\n        }\n    }\n\n    @Synchronized\n    fun removeItem(position: Int) {\n        kotlin.runCatching {\n            if (this.items.removeAt(position) != null) {\n                notifyItemRemoved(position + getHeaderCount())\n            }\n            onCurrentListChanged()\n        }\n    }\n\n    @Synchronized\n    fun removeItem(item: ITEM) {\n        kotlin.runCatching {\n            if (this.items.remove(item)) {\n                notifyItemRemoved(this.items.indexOf(item) + getHeaderCount())\n            }\n            onCurrentListChanged()\n        }\n    }\n\n    @SuppressLint(\"NotifyDataSetChanged\")\n    @Synchronized\n    fun removeItems(items: List<ITEM>) {\n        kotlin.runCatching {\n            if (this.items.removeAll(items)) {\n                notifyDataSetChanged()\n            }\n            onCurrentListChanged()\n        }\n    }\n\n    @Synchronized\n    fun swapItem(oldPosition: Int, newPosition: Int) {\n        kotlin.runCatching {\n            val size = getActualItemCount()\n            if (oldPosition in 0 until size && newPosition in 0 until size) {\n                val srcPosition = oldPosition + getHeaderCount()\n                val targetPosition = newPosition + getHeaderCount()\n                Collections.swap(this.items, srcPosition, targetPosition)\n                notifyItemMoved(srcPosition, targetPosition)\n            }\n            onCurrentListChanged()\n        }\n    }\n\n    @Synchronized\n    fun updateItem(item: ITEM) {\n        kotlin.runCatching {\n            val index = this.items.indexOf(item)\n            if (index >= 0) {\n                this.items[index] = item\n                notifyItemChanged(index)\n            }\n            onCurrentListChanged()\n        }\n    }\n\n    @Synchronized\n    fun updateItem(position: Int, payload: Any) {\n        kotlin.runCatching {\n            val size = getActualItemCount()\n            if (position in 0 until size) {\n                notifyItemChanged(position + getHeaderCount(), payload)\n            }\n        }\n    }\n\n    @Synchronized\n    fun updateItems(fromPosition: Int, toPosition: Int, payloads: Any) {\n        kotlin.runCatching {\n            val size = getActualItemCount()\n            if (fromPosition in 0 until size && toPosition in 0 until size) {\n                notifyItemRangeChanged(\n                    fromPosition + getHeaderCount(),\n                    toPosition - fromPosition + 1,\n                    payloads\n                )\n            }\n        }\n    }\n\n    @SuppressLint(\"NotifyDataSetChanged\")\n    @Synchronized\n    fun clearItems() {\n        kotlin.runCatching {\n            this.items.clear()\n            notifyDataSetChanged()\n            onCurrentListChanged()\n        }\n    }\n\n    fun isEmpty() = items.isEmpty()\n\n    fun isNotEmpty() = items.isNotEmpty()\n\n    /**\n     * 除去header和footer\n     */\n    fun getActualItemCount() = items.size\n\n\n    fun getHeaderCount() = headerItems.size()\n\n\n    fun getFooterCount() = footerItems.size()\n\n    fun getItem(position: Int): ITEM? = items.getOrNull(position)\n\n    fun getItemByLayoutPosition(position: Int) = items.getOrNull(getActualPosition(position))\n\n    fun getItems(): List<ITEM> = items.toList()\n\n    protected open fun getItemViewType(item: ITEM, position: Int) = 0\n\n    /**\n     * grid 模式下使用\n     */\n    protected open fun getSpanSize(viewType: Int, position: Int) = 1\n\n    final override fun getItemCount() = getActualItemCount() + getHeaderCount() + getFooterCount()\n\n    final override fun getItemViewType(position: Int) = when {\n        isHeader(position) -> TYPE_HEADER_VIEW + position\n        isFooter(position) -> TYPE_FOOTER_VIEW + position - getActualItemCount() - getHeaderCount()\n        else -> getItemByLayoutPosition(position)?.let {\n            getItemViewType(it, getActualPosition(position))\n        } ?: 0\n    }\n\n    open fun onCurrentListChanged() {\n\n    }\n\n    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = when {\n        viewType < TYPE_HEADER_VIEW + getHeaderCount() -> {\n            ItemViewHolder(headerItems.get(viewType).invoke(parent))\n        }\n\n        viewType >= TYPE_FOOTER_VIEW -> {\n            ItemViewHolder(footerItems.get(viewType).invoke(parent))\n        }\n\n        else -> {\n            ItemViewHolder(getViewBinding(parent))\n        }\n    }\n\n    protected abstract fun getViewBinding(parent: ViewGroup): VB\n\n    final override fun onBindViewHolder(holder: ItemViewHolder, position: Int) {}\n\n    @Suppress(\"UNCHECKED_CAST\")\n    final override fun onBindViewHolder(\n        holder: ItemViewHolder,\n        position: Int,\n        payloads: MutableList<Any>\n    ) {\n        if (!isHeader(holder.layoutPosition) && !isFooter(holder.layoutPosition)) {\n            registerListener(holder, (holder.binding as VB))\n            registerItemListener(holder)\n            getItemByLayoutPosition(holder.layoutPosition)?.let { item ->\n                convert(holder, holder.binding, item, payloads)\n            }\n        }\n    }\n\n    private fun registerItemListener(holder: ItemViewHolder) {\n        if (itemClickListener != null) {\n            holder.itemView.setOnClickListener {\n                getItemByLayoutPosition(holder.layoutPosition)?.let {\n                    itemClickListener?.invoke(holder, it)\n                }\n            }\n        }\n\n        if (itemLongClickListener != null) {\n            holder.itemView.onLongClick {\n                getItemByLayoutPosition(holder.layoutPosition)?.let {\n                    itemLongClickListener?.invoke(holder, it)\n                }\n            }\n        }\n    }\n\n    override fun onViewAttachedToWindow(holder: ItemViewHolder) {\n        super.onViewAttachedToWindow(holder)\n        if (!isHeader(holder.layoutPosition) && !isFooter(holder.layoutPosition)) {\n            addAnimation(holder)\n        }\n    }\n\n    override fun onAttachedToRecyclerView(recyclerView: RecyclerView) {\n        super.onAttachedToRecyclerView(recyclerView)\n        val manager = recyclerView.layoutManager\n        if (manager is GridLayoutManager) {\n            manager.spanSizeLookup = object : GridLayoutManager.SpanSizeLookup() {\n                override fun getSpanSize(position: Int): Int {\n                    return getSpanSize(getItemViewType(position), position)\n                }\n            }\n        }\n    }\n\n    private fun isHeader(position: Int) = position < getHeaderCount()\n\n    private fun isFooter(position: Int) = position >= getActualItemCount() + getHeaderCount()\n\n    private fun getActualPosition(position: Int) = position - getHeaderCount()\n\n    private fun addAnimation(holder: ItemViewHolder) {\n        itemAnimation?.let {\n            if (it.itemAnimEnabled) {\n                if (!it.itemAnimFirstOnly || holder.layoutPosition > it.itemAnimStartPosition) {\n                    startAnimation(holder, it)\n                    it.itemAnimStartPosition = holder.layoutPosition\n                }\n            }\n        }\n    }\n\n    protected open fun startAnimation(holder: ItemViewHolder, item: ItemAnimation) {\n        item.itemAnimation?.let {\n            for (anim in it.getAnimators(holder.itemView)) {\n                anim.setDuration(item.itemAnimDuration).start()\n                anim.interpolator = item.itemAnimInterpolator\n            }\n        }\n    }\n\n    /**\n     * 如果使用了事件回调,回调里不要直接使用item,会出现不更新的问题,\n     * 使用getItem(holder.layoutPosition)来获取item\n     */\n    abstract fun convert(\n        holder: ItemViewHolder,\n        binding: VB,\n        item: ITEM,\n        payloads: MutableList<Any>\n    )\n\n    /**\n     * 注册事件\n     */\n    abstract fun registerListener(holder: ItemViewHolder, binding: VB)\n\n    companion object {\n        private const val TYPE_HEADER_VIEW = Int.MIN_VALUE\n        const val TYPE_FOOTER_VIEW = Int.MAX_VALUE - 999\n        private val handler by lazy { buildMainHandler() }\n    }\n\n}\n\n\n\n\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/base/adapter/animations/AlphaInAnimation.kt",
    "content": "package io.legado.app.base.adapter.animations\n\nimport android.animation.Animator\nimport android.animation.ObjectAnimator\nimport android.view.View\n\n\nclass AlphaInAnimation @JvmOverloads constructor(private val mFrom: Float = DEFAULT_ALPHA_FROM) :\n    BaseAnimation {\n\n    override fun getAnimators(view: View): Array<Animator> =\n        arrayOf(ObjectAnimator.ofFloat(view, \"alpha\", mFrom, 1f))\n\n    companion object {\n\n        private const val DEFAULT_ALPHA_FROM = 0f\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/base/adapter/animations/BaseAnimation.kt",
    "content": "package io.legado.app.base.adapter.animations\n\nimport android.animation.Animator\nimport android.view.View\n\n/**\n * adapter item 动画\n */\ninterface BaseAnimation {\n\n    fun getAnimators(view: View): Array<Animator>\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/base/adapter/animations/ScaleInAnimation.kt",
    "content": "package io.legado.app.base.adapter.animations\n\nimport android.animation.Animator\nimport android.animation.ObjectAnimator\nimport android.view.View\n\n\nclass ScaleInAnimation @JvmOverloads constructor(private val mFrom: Float = DEFAULT_SCALE_FROM) :\n    BaseAnimation {\n\n    override fun getAnimators(view: View): Array<Animator> {\n        val scaleX = ObjectAnimator.ofFloat(view, \"scaleX\", mFrom, 1f)\n        val scaleY = ObjectAnimator.ofFloat(view, \"scaleY\", mFrom, 1f)\n        return arrayOf(scaleX, scaleY)\n    }\n\n    companion object {\n\n        private const val DEFAULT_SCALE_FROM = .5f\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/base/adapter/animations/SlideInBottomAnimation.kt",
    "content": "package io.legado.app.base.adapter.animations\n\nimport android.animation.Animator\nimport android.animation.ObjectAnimator\nimport android.view.View\n\nclass SlideInBottomAnimation : BaseAnimation {\n\n\n    override fun getAnimators(view: View): Array<Animator> =\n        arrayOf(ObjectAnimator.ofFloat(view, \"translationY\", view.measuredHeight.toFloat(), 0f))\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/base/adapter/animations/SlideInLeftAnimation.kt",
    "content": "package io.legado.app.base.adapter.animations\n\nimport android.animation.Animator\nimport android.animation.ObjectAnimator\nimport android.view.View\n\n\nclass SlideInLeftAnimation : BaseAnimation {\n\n\n    override fun getAnimators(view: View): Array<Animator> =\n        arrayOf(ObjectAnimator.ofFloat(view, \"translationX\", -view.rootView.width.toFloat(), 0f))\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/base/adapter/animations/SlideInRightAnimation.kt",
    "content": "package io.legado.app.base.adapter.animations\n\nimport android.animation.Animator\nimport android.animation.ObjectAnimator\nimport android.view.View\n\n\nclass SlideInRightAnimation : BaseAnimation {\n\n\n    override fun getAnimators(view: View): Array<Animator> =\n        arrayOf(ObjectAnimator.ofFloat(view, \"translationX\", view.rootView.width.toFloat(), 0f))\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/constant/AppConst.kt",
    "content": "package io.legado.app.constant\n\nimport android.annotation.SuppressLint\nimport android.content.pm.PackageManager\nimport android.provider.Settings\nimport androidx.annotation.Keep\nimport cn.hutool.crypto.digest.DigestUtil\nimport io.legado.app.BuildConfig\nimport io.legado.app.help.update.AppVariant\nimport org.apache.commons.lang3.time.FastDateFormat\nimport splitties.init.appCtx\n\n@Suppress(\"ConstPropertyName\")\n@SuppressLint(\"SimpleDateFormat\")\nobject AppConst {\n\n    const val APP_TAG = \"Legado\"\n\n    const val channelIdDownload = \"channel_download\"\n    const val channelIdReadAloud = \"channel_read_aloud\"\n    const val channelIdWeb = \"channel_web\"\n\n    const val UA_NAME = \"User-Agent\"\n\n    const val MAX_THREAD = 9\n\n    const val DEFAULT_WEBDAV_ID = -1L\n\n    private const val OFFICIAL_SIGNATURE =\n        \"8DACBF25EC667C9B1374DB1450C1A866C2AAA1173016E80BF6AD2F06FABDDC08\"\n    private const val BETA_SIGNATURE =\n        \"93A28468B0F69E8D14C8A99AB45841CEF902BBBA3761BBFEE02E67CBA801563E\"\n\n    val timeFormat: FastDateFormat by lazy {\n        FastDateFormat.getInstance(\"HH:mm\")\n    }\n\n    val dateFormat: FastDateFormat by lazy {\n        FastDateFormat.getInstance(\"yyyy/MM/dd HH:mm\")\n    }\n\n    val fileNameFormat: FastDateFormat by lazy {\n        FastDateFormat.getInstance(\"yy-MM-dd-HH-mm-ss\")\n    }\n\n    const val imagePathKey = \"imagePath\"\n\n    val menuViewNames = arrayOf(\n        \"com.android.internal.view.menu.ListMenuItemView\",\n        \"androidx.appcompat.view.menu.ListMenuItemView\"\n    )\n\n    @SuppressLint(\"PrivateResource\")\n    val sysElevation = appCtx.resources\n        .getDimension(com.google.android.material.R.dimen.design_appbar_elevation)\n        .toInt()\n\n    val androidId: String by lazy {\n        Settings.System.getString(appCtx.contentResolver, Settings.Secure.ANDROID_ID) ?: \"null\"\n    }\n\n    val appInfo: AppInfo by lazy {\n        val appInfo = AppInfo()\n        @Suppress(\"DEPRECATION\")\n        appCtx.packageManager.getPackageInfo(appCtx.packageName, PackageManager.GET_ACTIVITIES)\n            ?.let {\n                appInfo.versionName = it.versionName!!\n                appInfo.appVariant = when {\n                    it.packageName.contains(\"releaseA\") -> AppVariant.BETA_RELEASEA\n                    isBeta -> AppVariant.BETA_RELEASE\n                    isOfficial -> AppVariant.OFFICIAL\n                    else -> AppVariant.UNKNOWN\n                }\n\n                if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.P) {\n                    appInfo.versionCode = it.longVersionCode\n                } else {\n                    @Suppress(\"DEPRECATION\")\n                    appInfo.versionCode = it.versionCode.toLong()\n                }\n            }\n        appInfo\n    }\n\n    @Suppress(\"DEPRECATION\")\n    private val sha256Signature: String by lazy {\n        val packageInfo =\n            appCtx.packageManager.getPackageInfo(appCtx.packageName, PackageManager.GET_SIGNATURES)\n        DigestUtil.sha256Hex(packageInfo.signatures!![0].toByteArray()).uppercase()\n    }\n\n    private val isOfficial = sha256Signature == OFFICIAL_SIGNATURE\n\n    private val isBeta = sha256Signature == BETA_SIGNATURE || BuildConfig.DEBUG\n\n    val charsets =\n        arrayListOf(\"UTF-8\", \"GB2312\", \"GB18030\", \"GBK\", \"Unicode\", \"UTF-16\", \"UTF-16LE\", \"ASCII\")\n\n    @Keep\n    data class AppInfo(\n        var versionCode: Long = 0L,\n        var versionName: String = \"\",\n        var appVariant: AppVariant = AppVariant.UNKNOWN\n    )\n\n    /**\n     * The authority of a FileProvider defined in a <provider> element in your app's manifest.\n     */\n    const val authority = BuildConfig.APPLICATION_ID + \".fileProvider\"\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/constant/AppLog.kt",
    "content": "package io.legado.app.constant\n\nimport android.util.Log\nimport io.legado.app.BuildConfig\nimport io.legado.app.help.config.AppConfig\nimport io.legado.app.utils.LogUtils\nimport io.legado.app.utils.toastOnUi\nimport splitties.init.appCtx\n\nobject AppLog {\n\n    private val mLogs = arrayListOf<Triple<Long, String, Throwable?>>()\n\n    val logs get() = mLogs.toList()\n\n    @Synchronized\n    fun put(message: String?, throwable: Throwable? = null, toast: Boolean = false) {\n        message ?: return\n        if (toast) {\n            appCtx.toastOnUi(message)\n        }\n        if (mLogs.size > 100) {\n            mLogs.removeLastOrNull()\n        }\n        if (throwable == null) {\n            LogUtils.d(\"AppLog\", message)\n        } else {\n            LogUtils.d(\"AppLog\", \"$message\\n${throwable.stackTraceToString()}\")\n        }\n        mLogs.add(0, Triple(System.currentTimeMillis(), message, throwable))\n        if (BuildConfig.DEBUG) {\n            val stackTrace = Thread.currentThread().stackTrace\n            Log.e(stackTrace[3].className, message, throwable)\n        }\n    }\n\n    @Synchronized\n    fun putNotSave(message: String?, throwable: Throwable? = null, toast: Boolean = false) {\n        message ?: return\n        if (toast) {\n            appCtx.toastOnUi(message)\n        }\n        if (mLogs.size > 100) {\n            mLogs.removeLastOrNull()\n        }\n        mLogs.add(0, Triple(System.currentTimeMillis(), message, throwable))\n        if (BuildConfig.DEBUG) {\n            val stackTrace = Thread.currentThread().stackTrace\n            Log.e(stackTrace[3].className, message, throwable)\n        }\n    }\n\n    @Synchronized\n    fun clear() {\n        mLogs.clear()\n    }\n\n    fun putDebug(message: String?, throwable: Throwable? = null) {\n        if (AppConfig.recordLog) {\n            put(message, throwable)\n        }\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/constant/AppPattern.kt",
    "content": "package io.legado.app.constant\n\nimport java.util.regex.Pattern\n\n@Suppress(\"RegExpRedundantEscape\", \"unused\")\nobject AppPattern {\n    val JS_PATTERN: Pattern =\n        Pattern.compile(\"<js>([\\\\w\\\\W]*?)</js>|@js:([\\\\w\\\\W]*)\", Pattern.CASE_INSENSITIVE)\n    val EXP_PATTERN: Pattern = Pattern.compile(\"\\\\{\\\\{([\\\\w\\\\W]*?)\\\\}\\\\}\")\n\n    //匹配格式化后的图片格式\n    val imgPattern: Pattern = Pattern.compile(\"<img[^>]*src=['\\\"]([^'\\\"]*(?:['\\\"][^>]+\\\\})?)['\\\"][^>]*>\")\n\n    //dataURL图片类型\n    val dataUriRegex = Regex(\"^data:.*?;base64,(.*)\")\n\n    val nameRegex = Regex(\"\\\\s+作\\\\s*者.*|\\\\s+\\\\S+\\\\s+著\")\n    val authorRegex = Regex(\"^\\\\s*作\\\\s*者[:：\\\\s]+|\\\\s+著\")\n    val fileNameRegex = Regex(\"[\\\\\\\\/:*?\\\"<>|.]\")\n    val fileNameRegex2 = Regex(\"[\\\\\\\\/:*?\\\"<>|]\")\n    val splitGroupRegex = Regex(\"[,;，；]\")\n    val titleNumPattern: Pattern = Pattern.compile(\"(第)(.+?)(章)\")\n\n    //书源调试信息中的各种符号\n    val debugMessageSymbolRegex = Regex(\"[⇒◇┌└≡]\")\n\n    //本地书籍支持类型\n    val bookFileRegex = Regex(\".*\\\\.(txt|epub|umd|pdf|mobi|azw3|azw)\", RegexOption.IGNORE_CASE)\n    //压缩文件支持类型\n    val archiveFileRegex = Regex(\".*\\\\.(zip|rar|7z)$\", RegexOption.IGNORE_CASE)\n\n    /**\n     * 所有标点\n     */\n    val bdRegex = Regex(\"(\\\\p{P})+\")\n\n    /**\n     * 换行\n     */\n    val rnRegex = Regex(\"[\\\\r\\\\n]\")\n\n    /**\n     * 不发音段落判断\n     */\n    val notReadAloudRegex = Regex(\"^(\\\\s|\\\\p{C}|\\\\p{P}|\\\\p{Z}|\\\\p{S})+$\")\n\n    val xmlContentTypeRegex = \"(application|text)/\\\\w*\\\\+?xml.*\".toRegex()\n\n    val semicolonRegex = \";\".toRegex()\n\n    val equalsRegex = \"=\".toRegex()\n\n    val spaceRegex = \"\\\\s+\".toRegex()\n\n    val regexCharRegex = \"[{}()\\\\[\\\\].+*?^$\\\\\\\\|]\".toRegex()\n\n    val LFRegex = \"\\n\".toRegex()\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/constant/BookSourceType.kt",
    "content": "package io.legado.app.constant\n\nimport androidx.annotation.IntDef\n\n@Suppress(\"ConstPropertyName\")\nobject BookSourceType {\n\n    const val default = 0           // 0 文本\n    const val audio = 1             // 1 音频\n    const val image = 2            // 2 图片\n    const val file = 3               // 3 只提供下载服务的网站\n\n    @Target(AnnotationTarget.VALUE_PARAMETER)\n    @Retention(AnnotationRetention.SOURCE)\n    @IntDef(default, audio, image, file)\n    annotation class Type\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/constant/BookType.kt",
    "content": "package io.legado.app.constant\n\nimport androidx.annotation.IntDef\n\n/**\n * 以二进制位来区分,可能一本书籍包含多个类型,每一位代表一个类型,数值为2的n次方\n * 以二进制位来区分,数据库查询更高效, 数值>=8和老版本类型区分开\n */\n@Suppress(\"ConstPropertyName\")\nobject BookType {\n    /**\n     * 8 文本\n     */\n    const val text = 0b1000\n\n    /**\n     * 16 更新失败\n     */\n    const val updateError = 0b10000\n\n    /**\n     * 32 音频\n     */\n    const val audio = 0b100000\n\n    /**\n     * 64 图片\n     */\n    const val image = 0b1000000\n\n    /**\n     * 128 只提供下载服务的网站\n     */\n    const val webFile = 0b10000000\n\n    /**\n     * 256 本地\n     */\n    const val local = 0b100000000\n\n    /**\n     * 512 压缩包 表明书籍文件是从压缩包内解压来的\n     */\n    const val archive = 0b1000000000\n\n    /**\n     * 1024 未正式加入到书架的临时阅读书籍\n     */\n    const val notShelf = 0b100_0000_0000\n\n    @Target(AnnotationTarget.VALUE_PARAMETER)\n    @Retention(AnnotationRetention.SOURCE)\n    @IntDef(text, updateError, audio, image, webFile, local, archive, notShelf)\n    annotation class Type\n\n    /**\n     * 所有可以从书源转换的书籍类型\n     */\n    const val allBookType = text or image or audio or webFile\n\n    const val allBookTypeLocal = text or image or audio or webFile or local\n\n    /**\n     * 本地书籍书源标志\n     */\n    const val localTag = \"loc_book\"\n\n    /**\n     * 书源已webDav::开头的书籍,可以从webDav更新或重新下载\n     */\n    const val webDavTag = \"webDav::\"\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/constant/EventBus.kt",
    "content": "package io.legado.app.constant\n\nobject EventBus {\n    const val MEDIA_BUTTON = \"mediaButton\"\n    const val RECREATE = \"RECREATE\"\n    const val UP_BOOKSHELF = \"upBookToc\"\n    const val BOOKSHELF_REFRESH = \"bookshelfRefresh\"\n    const val ALOUD_STATE = \"aloud_state\"\n    const val TTS_PROGRESS = \"ttsStart\"\n    const val AUDIO_DS = \"audioDs\"\n    const val READ_ALOUD_DS = \"readAloudDs\"\n    const val BATTERY_CHANGED = \"batteryChanged\"\n    const val TIME_CHANGED = \"timeChanged\"\n    const val UP_CONFIG = \"upConfig\"\n    const val AUDIO_SUB_TITLE = \"audioSubTitle\"\n    const val AUDIO_STATE = \"audioState\"\n    const val AUDIO_PROGRESS = \"audioProgress\"\n    const val AUDIO_BUFFER_PROGRESS = \"audioBufferProgress\"\n    const val AUDIO_SIZE = \"audioSize\"\n    const val AUDIO_SPEED = \"audioSpeed\"\n    const val NOTIFY_MAIN = \"notifyMain\"\n    const val WEB_SERVICE = \"webService\"\n    const val UP_DOWNLOAD = \"upDownload\"\n    const val UP_DOWNLOAD_STATE = \"upDownloadState\"\n    const val SAVE_CONTENT = \"saveContent\"\n    const val CHECK_SOURCE = \"checkSource\"\n    const val CHECK_SOURCE_DONE = \"checkSourceDone\"\n    const val TIP_COLOR = \"tipColor\"\n    const val SOURCE_CHANGED = \"sourceChanged\"\n    const val SEARCH_RESULT = \"searchResult\"\n    const val UPDATE_READ_ACTION_BAR = \"updateReadActionBar\"\n    const val UP_SEEK_BAR = \"upSeekBar\"\n    const val READ_ALOUD_PLAY = \"readAloudPlay\"\n    const val EXPORT_BOOK = \"exportBook\"\n    const val UP_MANGA_CONFIG = \"upMangaConfig\"\n    const val PLAY_MODE_CHANGED = \"playModeChanged\"\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/constant/IntentAction.kt",
    "content": "package io.legado.app.constant\n\n@Suppress(\"ConstPropertyName\")\nobject IntentAction {\n    const val start = \"start\"\n    const val play = \"play\"\n    const val playNew = \"playNew\"\n    const val stop = \"stop\"\n    const val resume = \"resume\"\n    const val pause = \"pause\"\n    const val addTimer = \"addTimer\"\n    const val setTimer = \"setTimer\"\n    const val prevParagraph = \"prevParagraph\"\n    const val nextParagraph = \"nextParagraph\"\n    const val upTtsSpeechRate = \"upTtsSpeechRate\"\n    const val upTtsProgress = \"upTtsProgress\"\n    const val adjustProgress = \"adjustProgress\"\n    const val adjustSpeed = \"adjustSpeed\"\n    const val prev = \"prev\"\n    const val next = \"next\"\n    const val moveTo = \"moveTo\"\n    const val init = \"init\"\n    const val remove = \"remove\"\n    const val stopPlay = \"stopPlay\"\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/constant/NotificationId.kt",
    "content": "package io.legado.app.constant\n\n/**\n * 通知ID不能重复,统一规划通知ID\n */\n@Suppress(\"ConstPropertyName\")\nobject NotificationId {\n\n    const val ReadAloudService = 101\n    const val AudioPlayService = 102\n    const val CacheBookService = 103\n    const val ExportBookService = 104\n    const val WebService = 105\n    const val DownloadService = 106\n    const val CheckSourceService = 107\n    const val Download = 10000\n    const val ExportBook = 201\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/constant/PageAnim.kt",
    "content": "package io.legado.app.constant\n\nimport androidx.annotation.IntDef\n\n@Suppress(\"ConstPropertyName\")\nobject PageAnim {\n\n    const val coverPageAnim = 0\n\n    const val slidePageAnim = 1\n\n    const val simulationPageAnim = 2\n\n    const val scrollPageAnim = 3\n\n    const val noAnim = 4\n\n    @Target(AnnotationTarget.VALUE_PARAMETER)\n    @Retention(AnnotationRetention.SOURCE)\n    @IntDef(coverPageAnim, slidePageAnim, simulationPageAnim, scrollPageAnim, noAnim)\n    annotation class Anim\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/constant/PreferKey.kt",
    "content": "package io.legado.app.constant\n\n@Suppress(\"ConstPropertyName\")\nobject PreferKey {\n    const val language = \"language\"\n    const val fontScale = \"fontScale\"\n    const val themeMode = \"themeMode\"\n    const val userAgent = \"userAgent\"\n    const val showUnread = \"showUnread\"\n    const val bookGroupStyle = \"bookGroupStyle\"\n    const val useDefaultCover = \"useDefaultCover\"\n    const val loadCoverOnlyWifi = \"loadCoverOnlyWifi\"\n    const val coverShowName = \"coverShowName\"\n    const val coverShowAuthor = \"coverShowAuthor\"\n    const val coverShowNameN = \"coverShowNameN\"\n    const val coverShowAuthorN = \"coverShowAuthorN\"\n    const val remoteServerId = \"remoteServerId\"\n    const val hideStatusBar = \"hideStatusBar\"\n    const val clickActionTL = \"clickActionTopLeft\"\n    const val clickActionTC = \"clickActionTopCenter\"\n    const val clickActionTR = \"clickActionTopRight\"\n    const val clickActionML = \"clickActionMiddleLeft\"\n    const val clickActionMC = \"clickActionMiddleCenter\"\n    const val clickActionMR = \"clickActionMiddleRight\"\n    const val clickActionBL = \"clickActionBottomLeft\"\n    const val clickActionBC = \"clickActionBottomCenter\"\n    const val clickActionBR = \"clickActionBottomRight\"\n    const val hideNavigationBar = \"hideNavigationBar\"\n    const val precisionSearch = \"precisionSearch\"\n    const val readAloudByPage = \"readAloudByPage\"\n    const val ttsEngine = \"appTtsEngine\"\n    const val ttsFollowSys = \"ttsFollowSys\"\n    const val ttsSpeechRate = \"ttsSpeechRate\"\n    const val prevKeys = \"prevKeyCodes\"\n    const val nextKeys = \"nextKeyCodes\"\n    const val showDiscovery = \"showDiscovery\"\n    const val enableReview = \"enableReview\"\n    const val showRss = \"showRss\"\n    const val bookshelfLayout = \"bookshelfLayout\"\n    const val bookshelfSort = \"bookshelfSort\"\n    const val bookExportFileName = \"bookExportFileName\"\n    const val bookImportFileName = \"bookImportFileName\"\n    const val episodeExportFileName = \"episodeExportFileName\"\n    const val recordLog = \"recordLog\"\n    const val processText = \"process_text\"\n    const val cleanCache = \"cleanCache\"\n    const val saveTabPosition = \"saveTabPosition\"\n    const val fontFolder = \"fontFolder\"\n    const val backupPath = \"backupUri\"\n    const val restoreIgnore = \"restoreIgnore\"\n    const val threadCount = \"threadCount\"\n    const val webPort = \"webPort\"\n    const val keepLight = \"keep_light\"\n    const val webService = \"webService\"\n    const val webDavUrl = \"web_dav_url\"\n    const val webDavAccount = \"web_dav_account\"\n    const val webDavPassword = \"web_dav_password\"\n    const val webDavDir = \"webDavDir\"\n    const val enableCustomExport = \"enableCustomExport\"\n    const val exportToWebDav = \"webDavCacheBackup\"\n    const val exportNoChapterName = \"exportNoChapterName\"\n    const val exportType = \"exportType\"\n    const val exportPictureFile = \"exportPictureFile\"\n    const val changeSourceCheckAuthor = \"changeSourceCheckAuthor\"\n    const val changeSourceLoadToc = \"changeSourceLoadToc\"\n    const val changeSourceLoadInfo = \"changeSourceLoadInfo\"\n    const val changeSourceLoadWordCount = \"changeSourceLoadWordCount\"\n    const val chineseConverterType = \"chineseConverterType\"\n    const val launcherIcon = \"launcherIcon\"\n    const val textSelectAble = \"selectText\"\n    const val shareLayout = \"shareLayout\"\n    const val comicStyleSelect = \"comicStyleSelect\"\n    const val readStyleSelect = \"readStyleSelect\"\n    const val systemTypefaces = \"system_typefaces\"\n    const val readBodyToLh = \"readBodyToLh\"\n    const val textFullJustify = \"textFullJustify\"\n    const val textBottomJustify = \"textBottomJustify\"\n    const val autoReadSpeed = \"autoReadSpeed\"\n    const val barElevation = \"barElevation\"\n    const val transparentStatusBar = \"transparentStatusBar\"\n    const val immNavigationBar = \"immNavigationBar\"\n    const val defaultCover = \"defaultCover\"\n    const val defaultCoverDark = \"defaultCoverDark\"\n    const val replaceEnableDefault = \"replaceEnableDefault\"\n    const val showBrightnessView = \"showBrightnessView\"\n    const val autoClearExpired = \"autoClearExpired\"\n    const val autoChangeSource = \"autoChangeSource\"\n    const val importKeepName = \"importKeepName\"\n    const val importKeepGroup = \"importKeepGroup\"\n    const val screenOrientation = \"screenOrientation\"\n    const val syncBookProgress = \"syncBookProgress\"\n    const val syncBookProgressPlus = \"syncBookProgressPlus\"\n    const val cronet = \"Cronet\"\n    const val antiAlias = \"antiAlias\"\n    const val bitmapCacheSize = \"bitmapCacheSize\"\n    const val imageRetainNum = \"imageRetainNum\"\n    const val preDownloadNum = \"preDownloadNum\"\n    const val mangaPreDownloadNum = \"mangaPreDownloadNum\"\n    const val mangaAutoPageSpeed = \"mangaAutoPageSpeed\"\n    const val mangaFooterConfig = \"mangaFooterConfig\"\n    const val disableClickScroll = \"disableClickScroll\"\n    const val enableMangaHorizontalScroll = \"enableMangaHorizontalScroll\"\n    const val hideMangaTitle = \"hideMangaTitle\"\n    const val mangaColorFilter = \"mangaColorFilter\"\n    const val enableMangaEInk = \"enableMangaEInk\"\n    const val mangaEInkThreshold = \"mangaEInkThreshold\"\n    const val disableHorizontalPageSnap = \"disableHorizontalPageSnap\"\n    const val enableMangaGray = \"enableMangaGray\"\n    const val autoRefresh = \"auto_refresh\"\n    const val defaultToRead = \"defaultToRead\"\n    const val exportCharset = \"exportCharset\"\n    const val exportUseReplace = \"exportUseReplace\"\n    const val useZhLayout = \"useZhLayout\"\n    const val brightness = \"brightness\"\n    const val nightBrightness = \"nightBrightness\"\n    const val expandTextMenu = \"expandTextMenu\"\n    const val doublePageHorizontal = \"doubleHorizontalPage\"\n    const val readUrlOpenInBrowser = \"readUrlInBrowser\"\n    const val defaultBookTreeUri = \"defaultBookTreeUri\"\n    const val checkSource = \"checkSource\"\n    const val uploadRule = \"uploadRule\"\n    const val tocUiUseReplace = \"tocUiUseReplace\"\n    const val tocCountWords = \"tocCountWords\"\n    const val enableReadRecord = \"enableReadRecord\"\n    const val localBookImportSort = \"localBookImportSort\"\n    const val customWelcome = \"customWelcome\"\n    const val welcomeImage = \"welcomeImagePath\"\n    const val welcomeImageDark = \"welcomeImagePathDark\"\n    const val welcomeShowText = \"welcomeShowText\"\n    const val welcomeShowTextDark = \"welcomeShowTextDark\"\n    const val welcomeShowIcon = \"welcomeShowIcon\"\n    const val welcomeShowIconDark = \"welcomeShowIconDark\"\n    const val pageTouchSlop = \"pageTouchSlop\"\n    const val showAddToShelfAlert = \"showAddToShelfAlert\"\n    const val ignoreAudioFocus = \"ignoreAudioFocus\"\n    const val parallelExportBook = \"parallelExportBook\"\n    const val progressBarBehavior = \"progressBarBehavior\"\n    const val sourceEditMaxLine = \"sourceEditMaxLine\"\n    const val ttsTimer = \"ttsTimer\"\n    const val noAnimScrollPage = \"noAnimScrollPage\"\n    const val webDavDeviceName = \"webDavDeviceName\"\n    const val webServiceWakeLock = \"webServiceWakeLock\"\n    const val audioPlayWakeLock = \"audioPlayWakeLock\"\n    const val readAloudWakeLock = \"readAloudWakeLock\"\n    const val showLastUpdateTime = \"showLastUpdateTime\"\n    const val showWaitUpCount = \"showWaitUpCount\"\n    const val clearWebViewData = \"clearWebViewData\"\n    const val onlyLatestBackup = \"onlyLatestBackup\"\n    const val brightnessVwPos = \"brightnessVwPos\"\n    const val shrinkDatabase = \"shrinkDatabase\"\n    const val batchChangeSourceDelay = \"batchChangeSourceDelay\"\n    const val openBookInfoByClickTitle = \"openBookInfoByClickTitle\"\n    const val defaultHomePage = \"defaultHomePage\"\n    const val showBookshelfFastScroller = \"showBookshelfFastScroller\"\n    const val importKeepEnable = \"importKeepEnable\"\n    const val previewImageByClick = \"previewImageByClick\"\n    const val keyPageOnLongPress = \"keyPageOnLongPress\"\n    const val volumeKeyPage = \"volumeKeyPage\"\n    const val volumeKeyPageOnPlay = \"volumeKeyPageOnPlay\"\n    const val mouseWheelPage = \"mouseWheelPage\"\n    const val recordHeapDump = \"recordHeapDump\"\n    const val optimizeRender = \"optimizeRender\"\n    const val updateToVariant = \"updateToVariant\"\n    const val streamReadAloudAudio = \"streamReadAloudAudio\"\n    const val pauseReadAloudWhilePhoneCalls = \"pauseReadAloudWhilePhoneCalls\"\n    const val readAloudByMediaButton = \"readAloudByMediaButton\"\n    const val showMangaUi = \"showMangaUi\"\n    const val disableMangaScale = \"disableMangaScale\"\n    const val disableMangaPageAnim = \"disableMangaPageAnim\"\n    const val paddingDisplayCutouts = \"paddingDisplayCutouts\"\n    const val autoCheckNewBackup = \"autoCheckNewBackup\"\n\n    const val cPrimary = \"colorPrimary\"\n    const val cAccent = \"colorAccent\"\n    const val cBackground = \"colorBackground\"\n    const val cBBackground = \"colorBottomBackground\"\n    const val bgImage = \"backgroundImage\"\n    const val bgImageBlurring = \"backgroundImageBlurring\"\n\n    const val cNPrimary = \"colorPrimaryNight\"\n    const val cNAccent = \"colorAccentNight\"\n    const val cNBackground = \"colorBackgroundNight\"\n    const val cNBBackground = \"colorBottomBackgroundNight\"\n    const val bgImageN = \"backgroundImageNight\"\n    const val bgImageNBlurring = \"backgroundImageNightBlurring\"\n    const val showReadTitleAddition = \"showReadTitleAddition\"\n    const val readBarStyleFollowPage = \"readBarStyleFollowPage\"\n    const val contentSelectSpeakMod = \"contentReadAloudMod\"\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/constant/SourceType.kt",
    "content": "package io.legado.app.constant\n\nimport androidx.annotation.IntDef\n\n@Suppress(\"ConstPropertyName\")\nobject SourceType {\n\n    const val book = 0\n    const val rss = 1\n\n    @Target(AnnotationTarget.VALUE_PARAMETER)\n    @Retention(AnnotationRetention.SOURCE)\n    @IntDef(book, rss)\n    annotation class Type\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/constant/Status.kt",
    "content": "package io.legado.app.constant\n\nobject Status {\n    const val STOP = 0\n    const val PLAY = 1\n    const val PAUSE = 3\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/constant/Theme.kt",
    "content": "package io.legado.app.constant\n\nenum class Theme {\n    Dark, Light, Auto, Transparent, EInk;\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/data/AppDatabase.kt",
    "content": "package io.legado.app.data\n\nimport android.content.ContentValues\nimport android.database.sqlite.SQLiteDatabase\nimport android.os.Build\nimport android.util.Log\nimport androidx.room.AutoMigration\nimport androidx.room.Database\nimport androidx.room.Room\nimport androidx.room.RoomDatabase\nimport androidx.sqlite.db.SupportSQLiteDatabase\nimport io.legado.app.data.dao.BookChapterDao\nimport io.legado.app.data.dao.BookDao\nimport io.legado.app.data.dao.BookGroupDao\nimport io.legado.app.data.dao.BookSourceDao\nimport io.legado.app.data.dao.BookmarkDao\nimport io.legado.app.data.dao.CacheDao\nimport io.legado.app.data.dao.CookieDao\nimport io.legado.app.data.dao.DictRuleDao\nimport io.legado.app.data.dao.HttpTTSDao\nimport io.legado.app.data.dao.KeyboardAssistsDao\nimport io.legado.app.data.dao.ReadRecordDao\nimport io.legado.app.data.dao.ReplaceRuleDao\nimport io.legado.app.data.dao.RssArticleDao\nimport io.legado.app.data.dao.RssReadRecordDao\nimport io.legado.app.data.dao.RssSourceDao\nimport io.legado.app.data.dao.RssStarDao\nimport io.legado.app.data.dao.RuleSubDao\nimport io.legado.app.data.dao.SearchBookDao\nimport io.legado.app.data.dao.SearchKeywordDao\nimport io.legado.app.data.dao.ServerDao\nimport io.legado.app.data.dao.TxtTocRuleDao\nimport io.legado.app.data.entities.Book\nimport io.legado.app.data.entities.BookChapter\nimport io.legado.app.data.entities.BookGroup\nimport io.legado.app.data.entities.BookSource\nimport io.legado.app.data.entities.BookSourcePart\nimport io.legado.app.data.entities.Bookmark\nimport io.legado.app.data.entities.Cache\nimport io.legado.app.data.entities.Cookie\nimport io.legado.app.data.entities.DictRule\nimport io.legado.app.data.entities.HttpTTS\nimport io.legado.app.data.entities.KeyboardAssist\nimport io.legado.app.data.entities.ReadRecord\nimport io.legado.app.data.entities.ReplaceRule\nimport io.legado.app.data.entities.RssArticle\nimport io.legado.app.data.entities.RssReadRecord\nimport io.legado.app.data.entities.RssSource\nimport io.legado.app.data.entities.RssStar\nimport io.legado.app.data.entities.RuleSub\nimport io.legado.app.data.entities.SearchBook\nimport io.legado.app.data.entities.SearchKeyword\nimport io.legado.app.data.entities.Server\nimport io.legado.app.data.entities.TxtTocRule\nimport io.legado.app.help.DefaultData\nimport org.intellij.lang.annotations.Language\nimport splitties.init.appCtx\nimport java.util.Locale\n\nval appDb by lazy {\n    Room.databaseBuilder(appCtx, AppDatabase::class.java, AppDatabase.DATABASE_NAME)\n        .fallbackToDestructiveMigrationFrom(false, 1, 2, 3, 4, 5, 6, 7, 8, 9)\n        .addMigrations(*DatabaseMigrations.migrations)\n        .allowMainThreadQueries()\n        .addCallback(AppDatabase.dbCallback)\n        .build()\n}\n\n@Database(\n    version = 75,\n    exportSchema = true,\n    entities = [Book::class, BookGroup::class, BookSource::class, BookChapter::class,\n        ReplaceRule::class, SearchBook::class, SearchKeyword::class, Cookie::class,\n        RssSource::class, Bookmark::class, RssArticle::class, RssReadRecord::class,\n        RssStar::class, TxtTocRule::class, ReadRecord::class, HttpTTS::class, Cache::class,\n        RuleSub::class, DictRule::class, KeyboardAssist::class, Server::class],\n    views = [BookSourcePart::class],\n    autoMigrations = [\n        AutoMigration(from = 43, to = 44),\n        AutoMigration(from = 44, to = 45),\n        AutoMigration(from = 45, to = 46),\n        AutoMigration(from = 46, to = 47),\n        AutoMigration(from = 47, to = 48),\n        AutoMigration(from = 48, to = 49),\n        AutoMigration(from = 49, to = 50),\n        AutoMigration(from = 50, to = 51),\n        AutoMigration(from = 51, to = 52),\n        AutoMigration(from = 52, to = 53),\n        AutoMigration(from = 53, to = 54),\n        AutoMigration(from = 54, to = 55, spec = DatabaseMigrations.Migration_54_55::class),\n        AutoMigration(from = 55, to = 56),\n        AutoMigration(from = 56, to = 57),\n        AutoMigration(from = 57, to = 58),\n        AutoMigration(from = 58, to = 59),\n        AutoMigration(from = 59, to = 60),\n        AutoMigration(from = 60, to = 61),\n        AutoMigration(from = 61, to = 62),\n        AutoMigration(from = 62, to = 63),\n        AutoMigration(from = 63, to = 64),\n        AutoMigration(from = 64, to = 65, spec = DatabaseMigrations.Migration_64_65::class),\n        AutoMigration(from = 65, to = 66),\n        AutoMigration(from = 66, to = 67),\n        AutoMigration(from = 67, to = 68),\n        AutoMigration(from = 68, to = 69),\n        AutoMigration(from = 69, to = 70),\n        AutoMigration(from = 70, to = 71),\n        AutoMigration(from = 71, to = 72),\n        AutoMigration(from = 72, to = 73),\n        AutoMigration(from = 73, to = 74),\n        AutoMigration(from = 74, to = 75),\n    ]\n)\nabstract class AppDatabase : RoomDatabase() {\n\n    abstract val bookDao: BookDao\n    abstract val bookGroupDao: BookGroupDao\n    abstract val bookSourceDao: BookSourceDao\n    abstract val bookChapterDao: BookChapterDao\n    abstract val replaceRuleDao: ReplaceRuleDao\n    abstract val searchBookDao: SearchBookDao\n    abstract val searchKeywordDao: SearchKeywordDao\n    abstract val rssSourceDao: RssSourceDao\n    abstract val bookmarkDao: BookmarkDao\n    abstract val rssArticleDao: RssArticleDao\n    abstract val rssStarDao: RssStarDao\n    abstract val rssReadRecordDao: RssReadRecordDao\n    abstract val cookieDao: CookieDao\n    abstract val txtTocRuleDao: TxtTocRuleDao\n    abstract val readRecordDao: ReadRecordDao\n    abstract val httpTTSDao: HttpTTSDao\n    abstract val cacheDao: CacheDao\n    abstract val ruleSubDao: RuleSubDao\n    abstract val dictRuleDao: DictRuleDao\n    abstract val keyboardAssistsDao: KeyboardAssistsDao\n    abstract val serverDao: ServerDao\n\n    companion object {\n\n        const val DATABASE_NAME = \"legado.db\"\n\n        const val BOOK_TABLE_NAME = \"books\"\n        const val BOOK_SOURCE_TABLE_NAME = \"book_sources\"\n        const val RSS_SOURCE_TABLE_NAME = \"rssSources\"\n\n        val dbCallback = object : Callback() {\n\n            override fun onCreate(db: SupportSQLiteDatabase) {\n                // 只在 API 级别 23 (Marshmallow) 及以上版本尝试设置区域设置\n                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {\n                    try {\n                        Log.d(\"AppDatabaseCallback\", \"准备 设置 locale for API ${Build.VERSION.SDK_INT}...\")\n                        db.setLocale(Locale.CHINESE)\n                        // 在 21 上报错，但无法拦截\n                        Log.d(\"AppDatabaseCallback\", \"成功 设置 locale for API ${Build.VERSION.SDK_INT}.\")\n                    } catch (e: Exception) {\n                        Log.e(\"AppDatabaseCallback\", \"错误 设置 locale in onCreate for API ${Build.VERSION.SDK_INT}\", e)\n                    }\n                } else {\n                    Log.i(\"AppDatabaseCallback\", \"跳过 setLocale for API ${Build.VERSION.SDK_INT} (below M).\")\n                }\n            }\n\n            override fun onOpen(db: SupportSQLiteDatabase) {\n                @Language(\"sql\")\n                val insertBookGroupAllSql = \"\"\"\n                    insert into book_groups(groupId, groupName, 'order', show) \n                    select ${BookGroup.IdAll}, '全部', -10, 1\n                    where not exists (select * from book_groups where groupId = ${BookGroup.IdAll})\n                \"\"\".trimIndent()\n                db.execSQL(insertBookGroupAllSql)\n                @Language(\"sql\")\n                val insertBookGroupLocalSql = \"\"\"\n                    insert into book_groups(groupId, groupName, 'order', enableRefresh, show) \n                    select ${BookGroup.IdLocal}, '本地', -9, 0, 1\n                    where not exists (select * from book_groups where groupId = ${BookGroup.IdLocal})\n                \"\"\".trimIndent()\n                db.execSQL(insertBookGroupLocalSql)\n                @Language(\"sql\")\n                val insertBookGroupMusicSql = \"\"\"\n                    insert into book_groups(groupId, groupName, 'order', show) \n                    select ${BookGroup.IdAudio}, '音频', -8, 1\n                    where not exists (select * from book_groups where groupId = ${BookGroup.IdAudio})\n                \"\"\".trimIndent()\n                db.execSQL(insertBookGroupMusicSql)\n                @Language(\"sql\")\n                val insertBookGroupNetNoneGroupSql = \"\"\"\n                    insert into book_groups(groupId, groupName, 'order', show) \n                    select ${BookGroup.IdNetNone}, '网络未分组', -7, 1\n                    where not exists (select * from book_groups where groupId = ${BookGroup.IdNetNone})\n                \"\"\".trimIndent()\n                db.execSQL(insertBookGroupNetNoneGroupSql)\n                @Language(\"sql\")\n                val insertBookGroupLocalNoneGroupSql = \"\"\"\n                    insert into book_groups(groupId, groupName, 'order', show) \n                    select ${BookGroup.IdLocalNone}, '本地未分组', -6, 0\n                    where not exists (select * from book_groups where groupId = ${BookGroup.IdLocalNone})\n                \"\"\".trimIndent()\n                db.execSQL(insertBookGroupLocalNoneGroupSql)\n                @Language(\"sql\")\n                val insertBookGroupErrorSql = \"\"\"\n                    insert into book_groups(groupId, groupName, 'order', show) \n                    select ${BookGroup.IdError}, '更新失败', -1, 1\n                    where not exists (select * from book_groups where groupId = ${BookGroup.IdError})\n                \"\"\".trimIndent()\n                db.execSQL(insertBookGroupErrorSql)\n                @Language(\"sql\")\n                val upBookSourceLoginUiSql =\n                    \"update book_sources set loginUi = null where loginUi = 'null'\"\n                db.execSQL(upBookSourceLoginUiSql)\n                @Language(\"sql\")\n                val upRssSourceLoginUiSql =\n                    \"update rssSources set loginUi = null where loginUi = 'null'\"\n                db.execSQL(upRssSourceLoginUiSql)\n                @Language(\"sql\")\n                val upHttpTtsLoginUiSql =\n                    \"update httpTTS set loginUi = null where loginUi = 'null'\"\n                db.execSQL(upHttpTtsLoginUiSql)\n                @Language(\"sql\")\n                val upHttpTtsConcurrentRateSql =\n                    \"update httpTTS set concurrentRate = '0' where concurrentRate is null\"\n                db.execSQL(upHttpTtsConcurrentRateSql)\n                db.query(\"select * from keyboardAssists order by serialNo\").use {\n                    if (it.count == 0) {\n                        DefaultData.keyboardAssists.forEach { keyboardAssist ->\n                            val contentValues = ContentValues().apply {\n                                put(\"type\", keyboardAssist.type)\n                                put(\"key\", keyboardAssist.key)\n                                put(\"value\", keyboardAssist.value)\n                                put(\"serialNo\", keyboardAssist.serialNo)\n                            }\n                            db.insert(\n                                \"keyboardAssists\",\n                                SQLiteDatabase.CONFLICT_REPLACE,\n                                contentValues\n                            )\n                        }\n                    }\n                }\n            }\n        }\n\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/data/DatabaseMigrations.kt",
    "content": "package io.legado.app.data\n\nimport androidx.room.DeleteColumn\nimport androidx.room.migration.AutoMigrationSpec\nimport androidx.room.migration.Migration\nimport androidx.sqlite.db.SupportSQLiteDatabase\nimport io.legado.app.constant.AppConst\nimport io.legado.app.constant.BookSourceType\nimport io.legado.app.constant.BookType\n\nobject DatabaseMigrations {\n\n    val migrations: Array<Migration> by lazy {\n        arrayOf(\n            migration_10_11, migration_11_12, migration_12_13, migration_13_14,\n            migration_14_15, migration_15_17, migration_17_18, migration_18_19,\n            migration_19_20, migration_20_21, migration_21_22, migration_22_23,\n            migration_23_24, migration_24_25, migration_25_26, migration_26_27,\n            migration_27_28, migration_28_29, migration_29_30, migration_30_31,\n            migration_31_32, migration_32_33, migration_33_34, migration_34_35,\n            migration_35_36, migration_36_37, migration_37_38, migration_38_39,\n            migration_39_40, migration_40_41, migration_41_42, migration_42_43,\n        )\n    }\n\n    private val migration_10_11 = object : Migration(10, 11) {\n        override fun migrate(db: SupportSQLiteDatabase) {\n            db.execSQL(\"DROP TABLE txtTocRules\")\n            db.execSQL(\n                \"\"\"CREATE TABLE txtTocRules(id INTEGER NOT NULL, \n                    name TEXT NOT NULL, rule TEXT NOT NULL, serialNumber INTEGER NOT NULL, \n                    enable INTEGER NOT NULL, PRIMARY KEY (id))\"\"\"\n            )\n        }\n    }\n\n    private val migration_11_12 = object : Migration(11, 12) {\n        override fun migrate(db: SupportSQLiteDatabase) {\n            db.execSQL(\"ALTER TABLE rssSources ADD style TEXT \")\n        }\n    }\n\n    private val migration_12_13 = object : Migration(12, 13) {\n        override fun migrate(db: SupportSQLiteDatabase) {\n            db.execSQL(\"ALTER TABLE rssSources ADD articleStyle INTEGER NOT NULL DEFAULT 0 \")\n        }\n    }\n\n    private val migration_13_14 = object : Migration(13, 14) {\n        override fun migrate(db: SupportSQLiteDatabase) {\n            db.execSQL(\n                \"\"\"CREATE TABLE IF NOT EXISTS `books_new` (`bookUrl` TEXT NOT NULL, `tocUrl` TEXT NOT NULL, `origin` TEXT NOT NULL,\n                    `originName` TEXT NOT NULL, `name` TEXT NOT NULL, `author` TEXT NOT NULL, `kind` TEXT, `customTag` TEXT, `coverUrl` TEXT, \n                    `customCoverUrl` TEXT, `intro` TEXT, `customIntro` TEXT, `charset` TEXT, `type` INTEGER NOT NULL, `group` INTEGER NOT NULL, \n                    `latestChapterTitle` TEXT, `latestChapterTime` INTEGER NOT NULL, `lastCheckTime` INTEGER NOT NULL, `lastCheckCount` INTEGER NOT NULL, \n                    `totalChapterNum` INTEGER NOT NULL, `durChapterTitle` TEXT, `durChapterIndex` INTEGER NOT NULL, `durChapterPos` INTEGER NOT NULL, \n                    `durChapterTime` INTEGER NOT NULL, `wordCount` TEXT, `canUpdate` INTEGER NOT NULL, `order` INTEGER NOT NULL, \n                    `originOrder` INTEGER NOT NULL, `useReplaceRule` INTEGER NOT NULL, `variable` TEXT, PRIMARY KEY(`bookUrl`))\"\"\"\n            )\n            db.execSQL(\"INSERT INTO books_new select * from books \")\n            db.execSQL(\"DROP TABLE books\")\n            db.execSQL(\"ALTER TABLE books_new RENAME TO books\")\n            db.execSQL(\"CREATE UNIQUE INDEX IF NOT EXISTS `index_books_name_author` ON `books` (`name`, `author`) \")\n        }\n    }\n\n    private val migration_14_15 = object : Migration(14, 15) {\n        override fun migrate(db: SupportSQLiteDatabase) {\n            db.execSQL(\"ALTER TABLE bookmarks ADD bookAuthor TEXT NOT NULL DEFAULT ''\")\n        }\n    }\n\n    private val migration_15_17 = object : Migration(15, 17) {\n        override fun migrate(db: SupportSQLiteDatabase) {\n            db.execSQL(\"CREATE TABLE IF NOT EXISTS `readRecord` (`bookName` TEXT NOT NULL, `readTime` INTEGER NOT NULL, PRIMARY KEY(`bookName`))\")\n        }\n    }\n\n    private val migration_17_18 = object : Migration(17, 18) {\n        override fun migrate(db: SupportSQLiteDatabase) {\n            db.execSQL(\"CREATE TABLE IF NOT EXISTS `httpTTS` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `url` TEXT NOT NULL, PRIMARY KEY(`id`))\")\n        }\n    }\n\n    private val migration_18_19 = object : Migration(18, 19) {\n        override fun migrate(db: SupportSQLiteDatabase) {\n            db.execSQL(\n                \"\"\"CREATE TABLE IF NOT EXISTS `readRecordNew` (`androidId` TEXT NOT NULL, `bookName` TEXT NOT NULL, `readTime` INTEGER NOT NULL, \n                    PRIMARY KEY(`androidId`, `bookName`))\"\"\"\n            )\n            db.execSQL(\"INSERT INTO readRecordNew(androidId, bookName, readTime) select '${AppConst.androidId}' as androidId, bookName, readTime from readRecord\")\n            db.execSQL(\"DROP TABLE readRecord\")\n            db.execSQL(\"ALTER TABLE readRecordNew RENAME TO readRecord\")\n        }\n    }\n    private val migration_19_20 = object : Migration(19, 20) {\n        override fun migrate(db: SupportSQLiteDatabase) {\n            db.execSQL(\"ALTER TABLE book_sources ADD bookSourceComment TEXT\")\n        }\n    }\n\n    private val migration_20_21 = object : Migration(20, 21) {\n        override fun migrate(db: SupportSQLiteDatabase) {\n            db.execSQL(\"ALTER TABLE book_groups ADD show INTEGER NOT NULL DEFAULT 1\")\n        }\n    }\n\n    private val migration_21_22 = object : Migration(21, 22) {\n        override fun migrate(db: SupportSQLiteDatabase) {\n            db.execSQL(\n                \"\"\"CREATE TABLE IF NOT EXISTS `books_new` (`bookUrl` TEXT NOT NULL, `tocUrl` TEXT NOT NULL, `origin` TEXT NOT NULL, \n                    `originName` TEXT NOT NULL, `name` TEXT NOT NULL, `author` TEXT NOT NULL, `kind` TEXT, `customTag` TEXT, \n                    `coverUrl` TEXT, `customCoverUrl` TEXT, `intro` TEXT, `customIntro` TEXT, `charset` TEXT, `type` INTEGER NOT NULL, \n                    `group` INTEGER NOT NULL, `latestChapterTitle` TEXT, `latestChapterTime` INTEGER NOT NULL, `lastCheckTime` INTEGER NOT NULL, \n                    `lastCheckCount` INTEGER NOT NULL, `totalChapterNum` INTEGER NOT NULL, `durChapterTitle` TEXT, `durChapterIndex` INTEGER NOT NULL, \n                    `durChapterPos` INTEGER NOT NULL, `durChapterTime` INTEGER NOT NULL, `wordCount` TEXT, `canUpdate` INTEGER NOT NULL, \n                    `order` INTEGER NOT NULL, `originOrder` INTEGER NOT NULL, `variable` TEXT, `readConfig` TEXT, PRIMARY KEY(`bookUrl`))\"\"\"\n            )\n            db.execSQL(\n                \"\"\"INSERT INTO books_new select `bookUrl`, `tocUrl`, `origin`, `originName`, `name`, `author`, `kind`, `customTag`, `coverUrl`, \n                    `customCoverUrl`, `intro`, `customIntro`, `charset`, `type`, `group`, `latestChapterTitle`, `latestChapterTime`, `lastCheckTime`, \n                    `lastCheckCount`, `totalChapterNum`, `durChapterTitle`, `durChapterIndex`, `durChapterPos`, `durChapterTime`, `wordCount`, `canUpdate`, \n                    `order`, `originOrder`, `variable`, null\n                    from books\"\"\"\n            )\n            db.execSQL(\"DROP TABLE books\")\n            db.execSQL(\"ALTER TABLE books_new RENAME TO books\")\n            db.execSQL(\"CREATE UNIQUE INDEX IF NOT EXISTS `index_books_name_author` ON `books` (`name`, `author`) \")\n        }\n    }\n\n    private val migration_22_23 = object : Migration(22, 23) {\n        override fun migrate(db: SupportSQLiteDatabase) {\n            db.execSQL(\"ALTER TABLE chapters ADD baseUrl TEXT NOT NULL DEFAULT ''\")\n        }\n    }\n\n    private val migration_23_24 = object : Migration(23, 24) {\n        override fun migrate(db: SupportSQLiteDatabase) {\n            db.execSQL(\"CREATE TABLE IF NOT EXISTS `caches` (`key` TEXT NOT NULL, `value` TEXT, `deadline` INTEGER NOT NULL, PRIMARY KEY(`key`))\")\n            db.execSQL(\"CREATE UNIQUE INDEX IF NOT EXISTS `index_caches_key` ON `caches` (`key`)\")\n        }\n    }\n\n    private val migration_24_25 = object : Migration(24, 25) {\n        override fun migrate(db: SupportSQLiteDatabase) {\n            db.execSQL(\n                \"\"\"CREATE TABLE IF NOT EXISTS `sourceSubs` \n                    (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `url` TEXT NOT NULL, `type` INTEGER NOT NULL, `customOrder` INTEGER NOT NULL, \n                    PRIMARY KEY(`id`))\"\"\"\n            )\n        }\n    }\n\n    private val migration_25_26 = object : Migration(25, 26) {\n        override fun migrate(db: SupportSQLiteDatabase) {\n            db.execSQL(\n                \"\"\"CREATE TABLE IF NOT EXISTS `ruleSubs` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `url` TEXT NOT NULL, `type` INTEGER NOT NULL, \n                    `customOrder` INTEGER NOT NULL, `autoUpdate` INTEGER NOT NULL, `update` INTEGER NOT NULL, PRIMARY KEY(`id`))\"\"\"\n            )\n            db.execSQL(\" insert into `ruleSubs` select *, 0, 0 from `sourceSubs` \")\n            db.execSQL(\"DROP TABLE `sourceSubs`\")\n        }\n    }\n\n    private val migration_26_27 = object : Migration(26, 27) {\n        override fun migrate(db: SupportSQLiteDatabase) {\n            db.execSQL(\" ALTER TABLE rssSources ADD singleUrl INTEGER NOT NULL DEFAULT 0 \")\n            db.execSQL(\n                \"\"\"CREATE TABLE IF NOT EXISTS `bookmarks1` (`time` INTEGER NOT NULL, `bookUrl` TEXT NOT NULL, `bookName` TEXT NOT NULL, \n                        `bookAuthor` TEXT NOT NULL, `chapterIndex` INTEGER NOT NULL, `chapterPos` INTEGER NOT NULL, `chapterName` TEXT NOT NULL, \n                        `bookText` TEXT NOT NULL, `content` TEXT NOT NULL, PRIMARY KEY(`time`))\"\"\"\n            )\n            db.execSQL(\n                \"\"\"insert into `bookmarks1` \n                        select `time`, `bookUrl`, `bookName`, `bookAuthor`, `chapterIndex`, `pageIndex`, `chapterName`, '', `content` \n                        from bookmarks\"\"\"\n            )\n            db.execSQL(\" DROP TABLE `bookmarks` \")\n            db.execSQL(\" ALTER TABLE bookmarks1 RENAME TO bookmarks \")\n            db.execSQL(\"CREATE UNIQUE INDEX IF NOT EXISTS `index_bookmarks_time` ON `bookmarks` (`time`)\")\n        }\n    }\n\n    private val migration_27_28 = object : Migration(27, 28) {\n        override fun migrate(db: SupportSQLiteDatabase) {\n            db.execSQL(\"ALTER TABLE rssArticles ADD variable TEXT\")\n            db.execSQL(\"ALTER TABLE rssStars ADD variable TEXT\")\n        }\n    }\n\n    private val migration_28_29 = object : Migration(28, 29) {\n        override fun migrate(db: SupportSQLiteDatabase) {\n            db.execSQL(\"ALTER TABLE rssSources ADD sourceComment TEXT\")\n        }\n    }\n\n    private val migration_29_30 = object : Migration(29, 30) {\n        override fun migrate(db: SupportSQLiteDatabase) {\n            db.execSQL(\"ALTER TABLE chapters ADD `startFragmentId` TEXT\")\n            db.execSQL(\"ALTER TABLE chapters ADD `endFragmentId` TEXT\")\n            db.execSQL(\n                \"\"\"\n                    CREATE TABLE IF NOT EXISTS `epubChapters` \n                    (`bookUrl` TEXT NOT NULL, `href` TEXT NOT NULL, `parentHref` TEXT, \n                    PRIMARY KEY(`bookUrl`, `href`), FOREIGN KEY(`bookUrl`) REFERENCES `books`(`bookUrl`) ON UPDATE NO ACTION ON DELETE CASCADE )\n                \"\"\"\n            )\n            db.execSQL(\"CREATE INDEX IF NOT EXISTS `index_epubChapters_bookUrl` ON `epubChapters` (`bookUrl`)\")\n            db.execSQL(\"CREATE UNIQUE INDEX IF NOT EXISTS `index_epubChapters_bookUrl_href` ON `epubChapters` (`bookUrl`, `href`)\")\n        }\n    }\n\n    private val migration_30_31 = object : Migration(30, 31) {\n        override fun migrate(db: SupportSQLiteDatabase) {\n            db.execSQL(\"ALTER TABLE readRecord RENAME TO readRecord1\")\n            db.execSQL(\n                \"\"\"\n                    CREATE TABLE IF NOT EXISTS `readRecord` (`deviceId` TEXT NOT NULL, `bookName` TEXT NOT NULL, `readTime` INTEGER NOT NULL, PRIMARY KEY(`deviceId`, `bookName`))\n                \"\"\"\n            )\n            db.execSQL(\"insert into readRecord (deviceId, bookName, readTime) select androidId, bookName, readTime from readRecord1\")\n        }\n    }\n\n    private val migration_31_32 = object : Migration(31, 32) {\n        override fun migrate(db: SupportSQLiteDatabase) {\n            db.execSQL(\"DROP TABLE `epubChapters`\")\n        }\n    }\n\n    private val migration_32_33 = object : Migration(32, 33) {\n        override fun migrate(db: SupportSQLiteDatabase) {\n            db.execSQL(\"ALTER TABLE bookmarks RENAME TO bookmarks_old\")\n            db.execSQL(\n                \"\"\"\n                    CREATE TABLE IF NOT EXISTS `bookmarks` (`time` INTEGER NOT NULL,\n                    `bookName` TEXT NOT NULL, `bookAuthor` TEXT NOT NULL, `chapterIndex` INTEGER NOT NULL, \n                    `chapterPos` INTEGER NOT NULL, `chapterName` TEXT NOT NULL, `bookText` TEXT NOT NULL, \n                    `content` TEXT NOT NULL, PRIMARY KEY(`time`))\n                \"\"\"\n            )\n            db.execSQL(\n                \"\"\"\n                    CREATE INDEX IF NOT EXISTS `index_bookmarks_bookName_bookAuthor` ON `bookmarks` (`bookName`, `bookAuthor`)\n                \"\"\"\n            )\n            db.execSQL(\n                \"\"\"\n                    insert into bookmarks (time, bookName, bookAuthor, chapterIndex, chapterPos, chapterName, bookText, content)\n                    select time, ifNull(b.name, bookName) bookName, ifNull(b.author, bookAuthor) bookAuthor, \n                    chapterIndex, chapterPos, chapterName, bookText, content from bookmarks_old o\n                    left join books b on o.bookUrl = b.bookUrl\n                \"\"\"\n            )\n        }\n    }\n\n    private val migration_33_34 = object : Migration(33, 34) {\n        override fun migrate(db: SupportSQLiteDatabase) {\n            db.execSQL(\"ALTER TABLE `book_groups` ADD `cover` TEXT\")\n        }\n    }\n\n    private val migration_34_35 = object : Migration(34, 35) {\n        override fun migrate(db: SupportSQLiteDatabase) {\n            db.execSQL(\"ALTER TABLE `book_sources` ADD `concurrentRate` TEXT\")\n        }\n    }\n\n    private val migration_35_36 = object : Migration(35, 36) {\n        override fun migrate(db: SupportSQLiteDatabase) {\n            db.execSQL(\"ALTER TABLE `book_sources` ADD `loginUi` TEXT\")\n            db.execSQL(\"ALTER TABLE `book_sources` ADD`loginCheckJs` TEXT\")\n        }\n    }\n\n    private val migration_36_37 = object : Migration(36, 37) {\n        override fun migrate(db: SupportSQLiteDatabase) {\n            db.execSQL(\"ALTER TABLE `rssSources` ADD `loginUrl` TEXT\")\n            db.execSQL(\"ALTER TABLE `rssSources` ADD `loginUi` TEXT\")\n            db.execSQL(\"ALTER TABLE `rssSources` ADD `loginCheckJs` TEXT\")\n        }\n    }\n\n    private val migration_37_38 = object : Migration(37, 38) {\n        override fun migrate(db: SupportSQLiteDatabase) {\n            db.execSQL(\"ALTER TABLE `book_sources` ADD `respondTime` INTEGER NOT NULL DEFAULT 180000\")\n        }\n    }\n\n    private val migration_38_39 = object : Migration(38, 39) {\n        override fun migrate(db: SupportSQLiteDatabase) {\n            db.execSQL(\"ALTER TABLE `rssSources` ADD `concurrentRate` TEXT\")\n        }\n    }\n\n    private val migration_39_40 = object : Migration(39, 40) {\n        override fun migrate(db: SupportSQLiteDatabase) {\n            db.execSQL(\"ALTER TABLE `chapters` ADD `isVip` INTEGER NOT NULL DEFAULT 0\")\n            db.execSQL(\"ALTER TABLE `chapters` ADD `isPay` INTEGER NOT NULL DEFAULT 0\")\n        }\n    }\n\n    private val migration_40_41 = object : Migration(40, 41) {\n        override fun migrate(db: SupportSQLiteDatabase) {\n            db.execSQL(\"ALTER TABLE `httpTTS` ADD `loginUrl` TEXT\")\n            db.execSQL(\"ALTER TABLE `httpTTS` ADD `loginUi` TEXT\")\n            db.execSQL(\"ALTER TABLE `httpTTS` ADD `loginCheckJs` TEXT\")\n            db.execSQL(\"ALTER TABLE `httpTTS` ADD `header` TEXT\")\n            db.execSQL(\"ALTER TABLE `httpTTS` ADD `concurrentRate` TEXT\")\n        }\n    }\n\n    private val migration_41_42 = object : Migration(41, 42) {\n        override fun migrate(db: SupportSQLiteDatabase) {\n            db.execSQL(\"ALTER TABLE 'httpTTS' ADD `contentType` TEXT\")\n        }\n    }\n\n    private val migration_42_43 = object : Migration(42, 43) {\n        override fun migrate(db: SupportSQLiteDatabase) {\n            db.execSQL(\"ALTER TABLE `chapters` ADD `isVolume` INTEGER NOT NULL DEFAULT 0\")\n        }\n    }\n\n\n    @Suppress(\"ClassName\")\n    class Migration_54_55 : AutoMigrationSpec {\n\n        override fun onPostMigrate(db: SupportSQLiteDatabase) {\n            db.execSQL(\n                \"\"\"\n                update books set type = ${BookType.audio}\n                where type = ${BookSourceType.audio}\n            \"\"\".trimIndent()\n            )\n            db.execSQL(\n                \"\"\"\n                update books set type = ${BookType.image}\n                where type = ${BookSourceType.image}\n            \"\"\".trimIndent()\n            )\n            db.execSQL(\n                \"\"\"\n                update books set type = ${BookType.webFile}\n                where type = ${BookSourceType.file}\n            \"\"\".trimIndent()\n            )\n            db.execSQL(\n                \"\"\"\n                update books set type = ${BookType.text}\n                where type = ${BookSourceType.default}\n            \"\"\".trimIndent()\n            )\n            db.execSQL(\n                \"\"\"\n                update books set type = type | ${BookType.local}\n                where origin like '${BookType.localTag}%' or origin like '${BookType.webDavTag}%'\n            \"\"\".trimIndent()\n            )\n        }\n\n    }\n\n\n    @Suppress(\"ClassName\")\n    @DeleteColumn(\n        tableName = \"book_sources\",\n        columnName = \"enabledReview\"\n    )\n    class Migration_64_65 : AutoMigrationSpec\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/data/README.md",
    "content": "# 存储数据用\n\n* dao 数据操作\n* entities 数据模型\n* \\Book 书籍信息\n* \\BookChapter 目录信息\n* \\BookGroup 书籍分组\n* \\Bookmark 书签\n* \\BookSource 书源\n* \\Cookie http cookie\n* \\ReplaceRule 替换规则\n* \\RssArticle rss条目\n* \\RssReadRecord rss阅读记录\n* \\RssSource rss源\n* \\RssStar rss收藏\n* \\SearchBook 搜索结果\n* \\SearchKeyword 搜索关键字\n* \\TxtTocRule txt文件目录规则\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/data/dao/BookChapterDao.kt",
    "content": "package io.legado.app.data.dao\n\nimport androidx.room.Dao\nimport androidx.room.Insert\nimport androidx.room.OnConflictStrategy\nimport androidx.room.Query\nimport androidx.room.Update\nimport io.legado.app.data.entities.BookChapter\n\n@Dao\ninterface BookChapterDao {\n\n    @Query(\"SELECT * FROM chapters where bookUrl = :bookUrl and title like '%'||:key||'%' order by `index`\")\n    fun search(bookUrl: String, key: String): List<BookChapter>\n\n    @Query(\"SELECT * FROM chapters where bookUrl = :bookUrl and `index` >= :start and `index` <= :end and title like '%'||:key||'%' order by `index`\")\n    fun search(bookUrl: String, key: String, start: Int, end: Int): List<BookChapter>\n\n    @Query(\"select * from chapters where bookUrl = :bookUrl order by `index`\")\n    fun getChapterList(bookUrl: String): List<BookChapter>\n\n    @Query(\"select * from chapters where bookUrl = :bookUrl and `index` >= :start and `index` <= :end order by `index`\")\n    fun getChapterList(bookUrl: String, start: Int, end: Int): List<BookChapter>\n\n    @Query(\"select * from chapters where bookUrl = :bookUrl and `index` = :index\")\n    fun getChapter(bookUrl: String, index: Int): BookChapter?\n\n    @Query(\"select * from chapters where bookUrl = :bookUrl and `title` = :title\")\n    fun getChapter(bookUrl: String, title: String): BookChapter?\n\n    @Query(\"select count(url) from chapters where bookUrl = :bookUrl\")\n    fun getChapterCount(bookUrl: String): Int\n\n    @Insert(onConflict = OnConflictStrategy.REPLACE)\n    fun insert(vararg bookChapter: BookChapter)\n\n    @Update\n    fun update(vararg bookChapter: BookChapter)\n\n    @Query(\"delete from chapters where bookUrl = :bookUrl\")\n    fun delByBook(bookUrl: String)\n\n    @Query(\"update chapters set wordCount = :wordCount where bookUrl = :bookUrl and url = :url\")\n    fun upWordCount(bookUrl: String, url: String, wordCount: String)\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/data/dao/BookDao.kt",
    "content": "package io.legado.app.data.dao\n\nimport androidx.room.Dao\nimport androidx.room.Delete\nimport androidx.room.Insert\nimport androidx.room.OnConflictStrategy\nimport androidx.room.Query\nimport androidx.room.Transaction\nimport androidx.room.Update\nimport io.legado.app.constant.BookType\nimport io.legado.app.data.entities.Book\nimport io.legado.app.data.entities.BookGroup\nimport io.legado.app.data.entities.BookSource\nimport io.legado.app.help.book.isNotShelf\nimport kotlinx.coroutines.flow.Flow\nimport kotlinx.coroutines.flow.map\n\n@Dao\ninterface BookDao {\n\n    fun flowByGroup(groupId: Long): Flow<List<Book>> {\n        return when (groupId) {\n            BookGroup.IdRoot -> flowRoot()\n            BookGroup.IdAll -> flowAll()\n            BookGroup.IdLocal -> flowLocal()\n            BookGroup.IdAudio -> flowAudio()\n            BookGroup.IdNetNone -> flowNetNoGroup()\n            BookGroup.IdLocalNone -> flowLocalNoGroup()\n            BookGroup.IdError -> flowUpdateError()\n            else -> flowByUserGroup(groupId)\n        }.map { list ->\n            list.filterNot { it.isNotShelf }\n        }\n    }\n\n    @Query(\n        \"\"\"\n        select * from books where type & ${BookType.text} > 0\n        and type & ${BookType.local} = 0\n        and ((SELECT sum(groupId) FROM book_groups where groupId > 0) & `group`) = 0\n        and (select show from book_groups where groupId = ${BookGroup.IdNetNone}) != 1\n        \"\"\"\n    )\n    fun flowRoot(): Flow<List<Book>>\n\n    @Query(\"SELECT * FROM books order by durChapterTime desc\")\n    fun flowAll(): Flow<List<Book>>\n\n    @Query(\"SELECT * FROM books WHERE type & ${BookType.audio} > 0\")\n    fun flowAudio(): Flow<List<Book>>\n\n    @Query(\"SELECT * FROM books WHERE type & ${BookType.local} > 0\")\n    fun flowLocal(): Flow<List<Book>>\n\n    @Query(\n        \"\"\"\n        select * from books where type & ${BookType.audio} = 0 and type & ${BookType.local} = 0\n        and ((SELECT sum(groupId) FROM book_groups where groupId > 0) & `group`) = 0\n        \"\"\"\n    )\n    fun flowNetNoGroup(): Flow<List<Book>>\n\n    @Query(\n        \"\"\"\n        select * from books where type & ${BookType.local} > 0\n        and ((SELECT sum(groupId) FROM book_groups where groupId > 0) & `group`) = 0\n        \"\"\"\n    )\n    fun flowLocalNoGroup(): Flow<List<Book>>\n\n    @Query(\"SELECT * FROM books WHERE (`group` & :group) > 0\")\n    fun flowByUserGroup(group: Long): Flow<List<Book>>\n\n    @Query(\"SELECT * FROM books WHERE name like '%'||:key||'%' or author like '%'||:key||'%'\")\n    fun flowSearch(key: String): Flow<List<Book>>\n\n    @Query(\"SELECT * FROM books where type & ${BookType.updateError} > 0 order by durChapterTime desc\")\n    fun flowUpdateError(): Flow<List<Book>>\n\n    @Query(\"SELECT * FROM books WHERE (`group` & :group) > 0\")\n    fun getBooksByGroup(group: Long): List<Book>\n\n    @Query(\"SELECT * FROM books WHERE `name` in (:names)\")\n    fun findByName(vararg names: String): List<Book>\n\n    @Query(\"select * from books where originName = :fileName\")\n    fun getBookByFileName(fileName: String): Book?\n\n    @Query(\"SELECT * FROM books WHERE bookUrl = :bookUrl\")\n    fun getBook(bookUrl: String): Book?\n\n    @Query(\"SELECT * FROM books WHERE name = :name and author = :author\")\n    fun getBook(name: String, author: String): Book?\n\n    @Query(\"\"\"select distinct bs.* from books, book_sources bs \n        where origin == bookSourceUrl and origin not like '${BookType.localTag}%' \n        and origin not like '${BookType.webDavTag}%'\"\"\")\n    fun getAllUseBookSource(): List<BookSource>\n\n    @Query(\"SELECT * FROM books WHERE name = :name and origin = :origin\")\n    fun getBookByOrigin(name: String, origin: String): Book?\n\n    @get:Query(\"select count(bookUrl) from books where (SELECT sum(groupId) FROM book_groups)\")\n    val noGroupSize: Int\n\n    @get:Query(\"SELECT * FROM books where type & ${BookType.local} = 0\")\n    val webBooks: List<Book>\n\n    @get:Query(\"SELECT * FROM books where type & ${BookType.local} = 0 and canUpdate = 1\")\n    val hasUpdateBooks: List<Book>\n\n    @get:Query(\"SELECT * FROM books\")\n    val all: List<Book>\n\n    @Query(\"SELECT * FROM books where type & :type > 0 and type & ${BookType.local} = 0\")\n    fun getByTypeOnLine(type: Int): List<Book>\n\n    @get:Query(\"SELECT * FROM books where type & ${BookType.text} > 0 ORDER BY durChapterTime DESC limit 1\")\n    val lastReadBook: Book?\n\n    @get:Query(\"SELECT bookUrl FROM books\")\n    val allBookUrls: List<String>\n\n    @get:Query(\"SELECT COUNT(*) FROM books\")\n    val allBookCount: Int\n\n    @get:Query(\"select min(`order`) from books\")\n    val minOrder: Int\n\n    @get:Query(\"select max(`order`) from books\")\n    val maxOrder: Int\n\n    @Query(\"select exists(select 1 from books where bookUrl = :bookUrl)\")\n    fun has(bookUrl: String): Boolean\n\n    @Query(\"select exists(select 1 from books where name = :name and author = :author)\")\n    fun has(name: String, author: String): Boolean\n\n    @Query(\n        \"\"\"select exists(select 1 from books where type & ${BookType.local} > 0 \n        and (originName = :fileName or (origin != '${BookType.localTag}' and origin like '%' || :fileName)))\"\"\"\n    )\n    fun hasFile(fileName: String): Boolean\n\n    @Insert(onConflict = OnConflictStrategy.REPLACE)\n    fun insert(vararg book: Book)\n\n    @Update\n    fun update(vararg book: Book)\n\n    @Delete\n    fun delete(vararg book: Book)\n\n    @Transaction\n    fun replace(oldBook: Book, newBook: Book) {\n        delete(oldBook)\n        insert(newBook)\n    }\n\n    @Query(\"update books set durChapterPos = :pos where bookUrl = :bookUrl\")\n    fun upProgress(bookUrl: String, pos: Int)\n\n    @Query(\"update books set `group` = :newGroupId where `group` = :oldGroupId\")\n    fun upGroup(oldGroupId: Long, newGroupId: Long)\n\n    @Query(\"update books set `group` = `group` - :group where `group` & :group > 0\")\n    fun removeGroup(group: Long)\n\n    @Query(\"delete from books where type & ${BookType.notShelf} > 0\")\n    fun deleteNotShelfBook()\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/data/dao/BookGroupDao.kt",
    "content": "package io.legado.app.data.dao\n\nimport androidx.lifecycle.LiveData\nimport androidx.room.Dao\nimport androidx.room.Delete\nimport androidx.room.Insert\nimport androidx.room.OnConflictStrategy\nimport androidx.room.Query\nimport androidx.room.Update\nimport io.legado.app.constant.BookType\nimport io.legado.app.data.entities.BookGroup\nimport kotlinx.coroutines.flow.Flow\n\n@Dao\ninterface BookGroupDao {\n\n    @Query(\"select * from book_groups where groupId = :id\")\n    fun getByID(id: Long): BookGroup?\n\n    @Query(\"select * from book_groups where groupName = :groupName\")\n    fun getByName(groupName: String): BookGroup?\n\n    @Query(\"SELECT * FROM book_groups ORDER BY `order`\")\n    fun flowAll(): Flow<List<BookGroup>>\n\n    @get:Query(\n        \"\"\"\n        with const as (SELECT sum(groupId) sumGroupId FROM book_groups where groupId > 0)\n        SELECT book_groups.* FROM book_groups join const \n        where show > 0 \n        and (\n            (groupId >= 0  and exists (select 1 from books where `group` & book_groups.groupId > 0))\n            or groupId = -1\n            or (groupId = -2 and exists (select 1 from books where type & ${BookType.local} > 0))\n            or (groupId = -3 and exists (select 1 from books where type & ${BookType.audio} > 0))\n            or (groupId = -11 and exists (select 1 from books where type & ${BookType.updateError} > 0))\n            or (groupId = -4 \n                and exists (\n                    select 1 from books \n                    where type & ${BookType.audio} = 0\n                    and type & ${BookType.local} = 0\n                    and const.sumGroupId & `group` = 0\n                )\n            )\n            or (groupId = -5\n                and exists (\n                    select 1 from books \n                    where type & ${BookType.audio} = 0\n                    and type & ${BookType.local} > 0\n                    and const.sumGroupId & `group` = 0\n                )\n            )\n        )\n        ORDER BY `order`\"\"\"\n    )\n    val show: LiveData<List<BookGroup>>\n\n    @Query(\"SELECT * FROM book_groups where groupId >= 0 ORDER BY `order`\")\n    fun flowSelect(): Flow<List<BookGroup>>\n\n    @get:Query(\"SELECT sum(groupId) FROM book_groups where groupId >= 0\")\n    val idsSum: Long\n\n    @get:Query(\"SELECT MAX(`order`) FROM book_groups where groupId >= 0\")\n    val maxOrder: Int\n\n    @get:Query(\"SELECT * FROM book_groups ORDER BY `order`\")\n    val all: List<BookGroup>\n\n    @get:Query(\"select count(*) < 64 from book_groups where groupId >= 0 or groupId == ${Long.MIN_VALUE}\")\n    val canAddGroup: Boolean\n\n    @Query(\"update book_groups set show = 1 where groupId = :groupId\")\n    fun enableGroup(groupId: Long)\n\n    @Query(\"select groupName from book_groups where groupId > 0 and (groupId & :id) > 0\")\n    fun getGroupNames(id: Long): List<String>\n\n    @Insert(onConflict = OnConflictStrategy.REPLACE)\n    fun insert(vararg bookGroup: BookGroup)\n\n    @Update\n    fun update(vararg bookGroup: BookGroup)\n\n    @Delete\n    fun delete(vararg bookGroup: BookGroup)\n\n    fun isInRules(id: Long): Boolean {\n        if (id < 0) {\n            return true\n        }\n        return id and (id - 1) == 0L\n    }\n\n    fun getUnusedId(): Long {\n        var id = 1L\n        val idsSum = idsSum\n        while (id and idsSum != 0L) {\n            id = id.shl(1)\n        }\n        return id\n    }\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/data/dao/BookSourceDao.kt",
    "content": "package io.legado.app.data.dao\n\nimport androidx.room.Dao\nimport androidx.room.Delete\nimport androidx.room.Insert\nimport androidx.room.OnConflictStrategy\nimport androidx.room.Query\nimport androidx.room.Transaction\nimport androidx.room.Update\nimport io.legado.app.constant.AppPattern\nimport io.legado.app.data.entities.BookSource\nimport io.legado.app.data.entities.BookSourcePart\nimport io.legado.app.utils.cnCompare\nimport io.legado.app.utils.splitNotBlank\nimport kotlinx.coroutines.Dispatchers.IO\nimport kotlinx.coroutines.flow.Flow\nimport kotlinx.coroutines.flow.flowOn\nimport kotlinx.coroutines.flow.map\n\n@Dao\ninterface BookSourceDao {\n\n    @Query(\"select * from book_sources_part order by customOrder asc\")\n    fun flowAll(): Flow<List<BookSourcePart>>\n\n    @Query(\n        \"\"\"select bp.*\n        from book_sources b join book_sources_part bp on b.bookSourceUrl = bp.bookSourceUrl \n        where b.bookSourceName like '%' || :searchKey || '%'\n        or b.bookSourceGroup like '%' || :searchKey || '%'\n        or b.bookSourceUrl like '%' || :searchKey || '%'\n        or b.bookSourceComment like '%' || :searchKey || '%' \n        order by b.customOrder asc\"\"\"\n    )\n    fun flowSearch(searchKey: String): Flow<List<BookSourcePart>>\n\n    @Query(\n        \"\"\"select * from book_sources \n        where bookSourceName like '%' || :searchKey || '%'\n        or bookSourceGroup like '%' || :searchKey || '%'\n        or bookSourceUrl like '%' || :searchKey || '%'\n        or bookSourceComment like '%' || :searchKey || '%' \n        order by customOrder asc\"\"\"\n    )\n    fun search(searchKey: String): List<BookSource>\n\n    @Query(\n        \"\"\"select bp.*\n        from book_sources b join book_sources_part bp on b.bookSourceUrl = bp.bookSourceUrl \n        where b.enabled = 1 \n        and (b.bookSourceName like '%' || :searchKey || '%' \n        or b.bookSourceGroup like '%' || :searchKey || '%' \n        or b.bookSourceUrl like '%' || :searchKey || '%'  \n        or b.bookSourceComment like '%' || :searchKey || '%')\n        order by b.customOrder asc\"\"\"\n    )\n    fun flowSearchEnabled(searchKey: String): Flow<List<BookSourcePart>>\n\n    @Query(\n        \"\"\"select * from book_sources_part \n        where bookSourceGroup = :searchKey\n        or bookSourceGroup like :searchKey || ',%' \n        or bookSourceGroup like  '%,' || :searchKey\n        or bookSourceGroup like  '%,' || :searchKey || ',%' \n        order by customOrder asc\"\"\"\n    )\n    fun flowGroupSearch(searchKey: String): Flow<List<BookSourcePart>>\n\n    @Query(\n        \"\"\"select * from book_sources \n        where bookSourceGroup = :searchKey\n        or bookSourceGroup like :searchKey || ',%' \n        or bookSourceGroup like  '%,' || :searchKey\n        or bookSourceGroup like  '%,' || :searchKey || ',%' \n        order by customOrder asc\"\"\"\n    )\n    fun groupSearch(searchKey: String): List<BookSource>\n\n    @Query(\"select * from book_sources_part where enabled = 1 order by customOrder asc\")\n    fun flowEnabled(): Flow<List<BookSourcePart>>\n\n    @Query(\"select * from book_sources_part where enabled = 0 order by customOrder asc\")\n    fun flowDisabled(): Flow<List<BookSourcePart>>\n\n    @Query(\n        \"\"\"select * from book_sources_part \n        where enabledExplore = 1 and hasExploreUrl = 1 order by customOrder asc\"\"\"\n    )\n    fun flowExplore(): Flow<List<BookSourcePart>>\n\n    @Query(\"select * from book_sources_part where hasLoginUrl = 1 order by customOrder asc\")\n    fun flowLogin(): Flow<List<BookSourcePart>>\n\n    @Query(\n        \"\"\"select * from book_sources_part \n        where bookSourceGroup is null or bookSourceGroup = '' or bookSourceGroup like '%未分组%'\n        order by customOrder asc\"\"\"\n    )\n    fun flowNoGroup(): Flow<List<BookSourcePart>>\n\n    @Query(\"select * from book_sources_part where enabledExplore = 1 order by customOrder asc\")\n    fun flowEnabledExplore(): Flow<List<BookSourcePart>>\n\n    @Query(\"select * from book_sources_part where enabledExplore = 0 order by customOrder asc\")\n    fun flowDisabledExplore(): Flow<List<BookSourcePart>>\n\n    @Query(\n        \"\"\"select * from book_sources_part \n        where enabledExplore = 1 \n        and hasExploreUrl = 1 \n        and (bookSourceGroup like '%' || :key || '%' \n            or bookSourceName like '%' || :key || '%') \n        order by customOrder asc\"\"\"\n    )\n    fun flowExplore(key: String): Flow<List<BookSourcePart>>\n\n    @Query(\n        \"\"\"select * from book_sources_part \n        where enabledExplore = 1 \n        and hasExploreUrl = 1 \n        and (bookSourceGroup = :key\n            or bookSourceGroup like :key || ',%' \n            or bookSourceGroup like  '%,' || :key\n            or bookSourceGroup like  '%,' || :key || ',%') \n        order by customOrder asc\"\"\"\n    )\n    fun flowGroupExplore(key: String): Flow<List<BookSourcePart>>\n\n    @Query(\"select distinct bookSourceGroup from book_sources where trim(bookSourceGroup) <> ''\")\n    fun flowGroupsUnProcessed(): Flow<List<String>>\n\n    @Query(\n        \"\"\"select distinct bookSourceGroup from book_sources \n        where enabled = 1 and trim(bookSourceGroup) <> ''\"\"\"\n    )\n    fun flowEnabledGroupsUnProcessed(): Flow<List<String>>\n\n    @Query(\n        \"\"\"select distinct bookSourceGroup from book_sources \n        where enabledExplore = 1 \n        and trim(exploreUrl) <> '' \n        and trim(bookSourceGroup) <> ''\n        order by customOrder\"\"\"\n    )\n    fun flowExploreGroupsUnProcessed(): Flow<List<String>>\n\n    @Query(\n        \"\"\"select * from book_sources \n        where bookSourceGroup like '%' || :group || '%' order by customOrder asc\"\"\"\n    )\n    fun getByGroup(group: String): List<BookSource>\n\n    @Query(\n        \"\"\"select * from book_sources \n        where enabled = 1 \n        and (bookSourceGroup = :group\n            or bookSourceGroup like :group || ',%' \n            or bookSourceGroup like  '%,' || :group\n            or bookSourceGroup like  '%,' || :group || ',%')\n        order by customOrder asc\"\"\"\n    )\n    fun getEnabledByGroup(group: String): List<BookSource>\n\n    @Query(\n        \"\"\"select * from book_sources_part \n        where enabled = 1 \n        and (bookSourceGroup = :group\n            or bookSourceGroup like :group || ',%' \n            or bookSourceGroup like  '%,' || :group\n            or bookSourceGroup like  '%,' || :group || ',%')\n        order by customOrder asc\"\"\"\n    )\n    fun getEnabledPartByGroup(group: String): List<BookSourcePart>\n\n    @Query(\n        \"\"\"select * from book_sources \n        where bookUrlPattern != 'NONE' and bookSourceType = :type order by customOrder asc\"\"\"\n    )\n    fun getEnabledByType(type: Int): List<BookSource>\n\n    @Query(\"select * from book_sources where enabled = 1 and bookSourceUrl = :baseUrl\")\n    fun getBookSourceAddBook(baseUrl: String): BookSource?\n\n    @get:Query(\n        \"\"\"select bp.* \n        from book_sources b join book_sources_part bp on b.bookSourceUrl = bp.bookSourceUrl\n        where b.enabled = 1 \n        and trim(b.bookUrlPattern) <> '' \n        and trim(b.bookUrlPattern) <> 'NONE' \n        order by b.customOrder\"\"\"\n    )\n    val hasBookUrlPattern: List<BookSourcePart>\n\n    @get:Query(\"select * from book_sources where bookSourceGroup is null or bookSourceGroup = ''\")\n    val noGroup: List<BookSource>\n\n    @get:Query(\"select * from book_sources order by customOrder asc\")\n    val all: List<BookSource>\n\n    @get:Query(\"select * from book_sources_part order by customOrder asc\")\n    val allPart: List<BookSourcePart>\n\n    @get:Query(\"select * from book_sources where enabled = 1 order by customOrder\")\n    val allEnabled: List<BookSource>\n\n    @get:Query(\"select * from book_sources_part where enabled = 1 order by customOrder asc\")\n    val allEnabledPart: List<BookSourcePart>\n\n    @get:Query(\"select * from book_sources where enabled = 0 order by customOrder\")\n    val allDisabled: List<BookSource>\n\n    @get:Query(\n        \"\"\"select * from book_sources \n        where bookSourceGroup is null or bookSourceGroup = '' or bookSourceGroup like '%未分组%'\"\"\"\n    )\n    val allNoGroup: List<BookSource>\n\n    @get:Query(\"select * from book_sources where enabledExplore = 1 order by customOrder\")\n    val allEnabledExplore: List<BookSource>\n\n    @get:Query(\"select * from book_sources where enabledExplore = 0 order by customOrder\")\n    val allDisabledExplore: List<BookSource>\n\n    @get:Query(\"select * from book_sources where loginUrl is not null and loginUrl != ''\")\n    val allLogin: List<BookSource>\n\n    @get:Query(\n        \"\"\"select bp.*\n        from book_sources b join book_sources_part bp on b.bookSourceUrl = bp.bookSourceUrl \n        where b.enabled = 1 and b.bookSourceType = 0 order by b.customOrder\"\"\"\n    )\n    val allTextEnabledPart: List<BookSourcePart>\n\n    @get:Query(\n        \"\"\"select distinct bookSourceGroup from book_sources \n        where trim(bookSourceGroup) <> ''\"\"\"\n    )\n    val allGroupsUnProcessed: List<String>\n\n    @get:Query(\n        \"\"\"select distinct bookSourceGroup from book_sources \n        where enabled = 1 and trim(bookSourceGroup) <> ''\"\"\"\n    )\n    val allEnabledGroupsUnProcessed: List<String>\n\n    @Query(\"select * from book_sources where bookSourceUrl = :key\")\n    fun getBookSource(key: String): BookSource?\n\n    @Query(\"select * from book_sources_part where bookSourceUrl = :key\")\n    fun getBookSourcePart(key: String): BookSourcePart?\n\n    @Query(\"select count(*) from book_sources\")\n    fun allCount(): Int\n\n    @Query(\"SELECT EXISTS(select 1 from book_sources where bookSourceUrl = :key)\")\n    fun has(key: String): Boolean\n\n    @Insert(onConflict = OnConflictStrategy.REPLACE)\n    fun insert(vararg bookSource: BookSource)\n\n    @Update\n    fun update(vararg bookSource: BookSource)\n\n    @Delete\n    fun delete(vararg bookSource: BookSource)\n\n    @Query(\"delete from book_sources where bookSourceUrl = :key\")\n    fun delete(key: String)\n\n    @Transaction\n    fun delete(bookSources: List<BookSourcePart>) {\n        for (bs in bookSources) {\n            delete(bs.bookSourceUrl)\n        }\n    }\n\n    @get:Query(\"select min(customOrder) from book_sources\")\n    val minOrder: Int\n\n    @get:Query(\"select max(customOrder) from book_sources\")\n    val maxOrder: Int\n\n    @get:Query(\n        \"\"\"select exists (select 1 \n        from book_sources group by customOrder having count(customOrder) > 1)\"\"\"\n    )\n    val hasDuplicateOrder: Boolean\n\n    @Query(\"update book_sources set enabled = :enable where bookSourceUrl = :bookSourceUrl\")\n    fun enable(bookSourceUrl: String, enable: Boolean)\n\n    @Transaction\n    fun enable(enable: Boolean, bookSources: List<BookSourcePart>) {\n        for (bs in bookSources) {\n            enable(bs.bookSourceUrl, enable)\n        }\n    }\n\n    @Query(\"update book_sources set enabledExplore = :enable where bookSourceUrl = :bookSourceUrl\")\n    fun enableExplore(bookSourceUrl: String, enable: Boolean)\n\n    @Transaction\n    fun enableExplore(enable: Boolean, bookSources: List<BookSourcePart>) {\n        for (bs in bookSources) {\n            enableExplore(bs.bookSourceUrl, enable)\n        }\n    }\n\n    @Query(\n        \"\"\"update book_sources \n        set customOrder = :customOrder where bookSourceUrl = :bookSourceUrl\"\"\"\n    )\n    fun upOrder(bookSourceUrl: String, customOrder: Int)\n\n    @Transaction\n    fun upOrder(bookSources: List<BookSourcePart>) {\n        for (bs in bookSources) {\n            upOrder(bs.bookSourceUrl, bs.customOrder)\n        }\n    }\n\n    fun upOrder(bookSource: BookSourcePart) {\n        upOrder(bookSource.bookSourceUrl, bookSource.customOrder)\n    }\n\n    @Query(\n        \"\"\"update book_sources \n        set bookSourceGroup = :bookSourceGroup where bookSourceUrl = :bookSourceUrl\"\"\"\n    )\n    fun upGroup(bookSourceUrl: String, bookSourceGroup: String)\n\n    @Transaction\n    fun upGroup(bookSources: List<BookSourcePart>) {\n        for (bs in bookSources) {\n            bs.bookSourceGroup?.let { upGroup(bs.bookSourceUrl, it) }\n        }\n    }\n\n    private fun dealGroups(list: List<String>): List<String> {\n        val groups = linkedSetOf<String>()\n        list.forEach {\n            it.splitNotBlank(AppPattern.splitGroupRegex).forEach { group ->\n                groups.add(group)\n            }\n        }\n        return groups.sortedWith { o1, o2 ->\n            o1.cnCompare(o2)\n        }\n    }\n\n    fun allGroups(): List<String> = dealGroups(allGroupsUnProcessed)\n\n    fun allEnabledGroups(): List<String> = dealGroups(allEnabledGroupsUnProcessed)\n\n    fun flowGroups(): Flow<List<String>> {\n        return flowGroupsUnProcessed().map { list ->\n            dealGroups(list)\n        }.flowOn(IO)\n    }\n\n    fun flowExploreGroups(): Flow<List<String>> {\n        return flowExploreGroupsUnProcessed().map { list ->\n            dealGroups(list)\n        }.flowOn(IO)\n    }\n\n    fun flowEnabledGroups(): Flow<List<String>> {\n        return flowEnabledGroupsUnProcessed().map { list ->\n            dealGroups(list)\n        }.flowOn(IO)\n    }\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/data/dao/BookmarkDao.kt",
    "content": "package io.legado.app.data.dao\n\nimport androidx.room.*\nimport io.legado.app.data.entities.Bookmark\nimport kotlinx.coroutines.flow.Flow\n\n\n@Dao\ninterface BookmarkDao {\n\n    @get:Query(\n        \"\"\"\n        select * from bookmarks order by bookName collate localized, bookAuthor collate localized, chapterIndex, chapterPos\n    \"\"\"\n    )\n    val all: List<Bookmark>\n\n    @Query(\"select * from bookmarks order by bookName collate localized, bookAuthor collate localized, chapterIndex, chapterPos\")\n    fun flowAll(): Flow<List<Bookmark>>\n\n    @Query(\n        \"\"\"select * from bookmarks \n        where bookName = :bookName and bookAuthor = :bookAuthor \n        order by chapterIndex\"\"\"\n    )\n    fun flowByBook(bookName: String, bookAuthor: String): Flow<List<Bookmark>>\n\n    @Query(\n        \"\"\"SELECT * FROM bookmarks \n        where bookName = :bookName and bookAuthor = :bookAuthor \n        and chapterName like '%'||:key||'%' or content like '%'||:key||'%'\n        order by chapterIndex\"\"\"\n    )\n    fun flowSearch(bookName: String, bookAuthor: String, key: String): Flow<List<Bookmark>>\n\n    @Query(\n        \"\"\"select * from bookmarks \n        where bookName = :bookName and bookAuthor = :bookAuthor \n        order by chapterIndex\"\"\"\n    )\n    fun getByBook(bookName: String, bookAuthor: String): List<Bookmark>\n\n    @Query(\n        \"\"\"SELECT * FROM bookmarks \n        where bookName = :bookName and bookAuthor = :bookAuthor \n        and chapterName like '%'||:key||'%' or content like '%'||:key||'%'\n        order by chapterIndex\"\"\"\n    )\n    fun search(bookName: String, bookAuthor: String, key: String): List<Bookmark>\n\n    @Insert(onConflict = OnConflictStrategy.REPLACE)\n    fun insert(vararg bookmark: Bookmark)\n\n    @Update\n    fun update(bookmark: Bookmark)\n\n    @Delete\n    fun delete(vararg bookmark: Bookmark)\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/data/dao/CacheDao.kt",
    "content": "package io.legado.app.data.dao\n\nimport androidx.room.Dao\nimport androidx.room.Insert\nimport androidx.room.OnConflictStrategy\nimport androidx.room.Query\nimport io.legado.app.data.entities.Cache\n\n@Dao\ninterface CacheDao {\n\n    @Query(\"select * from caches where `key` = :key\")\n    fun get(key: String): Cache?\n\n    @Query(\"select value from caches where `key` = :key and (deadline = 0 or deadline > :now)\")\n    fun get(key: String, now: Long): String?\n\n    @Insert(onConflict = OnConflictStrategy.REPLACE)\n    fun insert(vararg cache: Cache)\n\n    @Query(\"delete from caches where `key` = :key\")\n    fun delete(key: String)\n\n    @Query(\n        \"\"\"delete from caches where `key` like 'v_' || :key || '_%'\n        or `key` = 'userInfo_' || :key\n        or `key` = 'loginHeader_' || :key\n        or `key` = 'sourceVariable_' || :key\"\"\"\n    )\n    fun deleteSourceVariables(key: String)\n\n    @Query(\"delete from caches where deadline > 0 and deadline < :now\")\n    fun clearDeadline(now: Long)\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/data/dao/CookieDao.kt",
    "content": "package io.legado.app.data.dao\n\nimport androidx.room.*\nimport io.legado.app.data.entities.Cookie\n\n@Dao\ninterface CookieDao {\n\n    @Query(\"SELECT * FROM cookies Where url = :url\")\n    fun get(url: String): Cookie?\n\n    @Query(\"select * from cookies where url like '%|%'\")\n    fun getOkHttpCookies(): List<Cookie>\n\n    @Insert(onConflict = OnConflictStrategy.REPLACE)\n    fun insert(vararg cookie: Cookie)\n\n    @Update\n    fun update(vararg cookie: Cookie)\n\n    @Query(\"delete from cookies where url = :url\")\n    fun delete(url: String)\n\n    @Query(\"delete from cookies where url like '%|%'\")\n    fun deleteOkHttp()\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/data/dao/DictRuleDao.kt",
    "content": "package io.legado.app.data.dao\n\nimport androidx.room.*\nimport io.legado.app.data.entities.DictRule\nimport kotlinx.coroutines.flow.Flow\n\n\n@Dao\ninterface DictRuleDao {\n\n    @get:Query(\"select * from dictRules order by sortNumber\")\n    val all: List<DictRule>\n\n    @get:Query(\"select * from dictRules where enabled = 1 order by sortNumber\")\n    val enabled: List<DictRule>\n\n    @Query(\"select * from dictRules order by sortNumber\")\n    fun flowAll(): Flow<List<DictRule>>\n\n    @Query(\"select * from dictRules where name = :name\")\n    fun getByName(name: String): DictRule?\n\n    @Insert(onConflict = OnConflictStrategy.REPLACE)\n    fun insert(vararg dictRule: DictRule)\n\n    @Update\n    fun update(vararg dictRule: DictRule)\n\n    @Delete\n    fun delete(vararg dictRule: DictRule)\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/data/dao/HttpTTSDao.kt",
    "content": "package io.legado.app.data.dao\n\nimport androidx.room.*\nimport io.legado.app.data.entities.HttpTTS\nimport kotlinx.coroutines.flow.Flow\n\n@Dao\ninterface HttpTTSDao {\n\n    @get:Query(\"select * from httpTTS order by name\")\n    val all: List<HttpTTS>\n\n    @Query(\"select * from httpTTS order by name\")\n    fun flowAll(): Flow<List<HttpTTS>>\n\n    @get:Query(\"select count(*) from httpTTS\")\n    val count: Int\n\n    @Query(\"select * from httpTTS where id = :id\")\n    fun get(id: Long): HttpTTS?\n\n    @Query(\"select name from httpTTS where id = :id\")\n    fun getName(id: Long): String?\n\n    @Insert(onConflict = OnConflictStrategy.REPLACE)\n    fun insert(vararg httpTTS: HttpTTS)\n\n    @Delete\n    fun delete(vararg httpTTS: HttpTTS)\n\n    @Update\n    fun update(vararg httpTTS: HttpTTS)\n\n    @Query(\"delete from httpTTS where id < 0\")\n    fun deleteDefault()\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/data/dao/KeyboardAssistsDao.kt",
    "content": "package io.legado.app.data.dao\n\nimport androidx.room.*\nimport io.legado.app.data.entities.KeyboardAssist\nimport kotlinx.coroutines.flow.Flow\n\n@Dao\ninterface KeyboardAssistsDao {\n\n    @get:Query(\"select * from keyboardAssists order by serialNo\")\n    val all: List<KeyboardAssist>\n\n    @Query(\"select * from keyboardAssists where type = :type order by serialNo\")\n    fun getByType(type: Int): List<KeyboardAssist>\n\n    @get:Query(\"select * from keyboardAssists order by serialNo\")\n    val flowAll: Flow<List<KeyboardAssist>>\n\n    @Query(\"select * from keyboardAssists where type = :type order by serialNo\")\n    fun flowByType(type: Int): Flow<List<KeyboardAssist>>\n\n    @get:Query(\"select max(serialNo) from keyboardAssists order by serialNo\")\n    val maxSerialNo: Int\n\n    @Insert(onConflict = OnConflictStrategy.REPLACE)\n    fun insert(vararg keyboardAssist: KeyboardAssist)\n\n    @Update\n    fun update(vararg keyboardAssist: KeyboardAssist)\n\n    @Delete\n    fun delete(vararg keyboardAssist: KeyboardAssist)\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/data/dao/ReadRecordDao.kt",
    "content": "package io.legado.app.data.dao\n\nimport androidx.room.*\nimport io.legado.app.data.entities.ReadRecord\nimport io.legado.app.data.entities.ReadRecordShow\n\n@Dao\ninterface ReadRecordDao {\n\n    @get:Query(\"select * from readRecord\")\n    val all: List<ReadRecord>\n\n    @get:Query(\n        \"\"\"\n        select bookName, sum(readTime) as readTime, max(lastRead) as lastRead \n        from readRecord \n        group by bookName \n        order by bookName collate localized\"\"\"\n    )\n    val allShow: List<ReadRecordShow>\n\n    @get:Query(\"select sum(readTime) from readRecord\")\n    val allTime: Long\n\n    @Query(\n        \"\"\"\n        select bookName, sum(readTime) as readTime, max(lastRead) as lastRead \n        from readRecord \n        where bookName like '%' || :searchKey || '%'\n        group by bookName \n        order by bookName collate localized\"\"\"\n    )\n    fun search(searchKey: String): List<ReadRecordShow>\n\n    @Query(\"select sum(readTime) from readRecord where bookName = :bookName\")\n    fun getReadTime(bookName: String): Long?\n\n    @Query(\"select readTime from readRecord where deviceId = :androidId and bookName = :bookName\")\n    fun getReadTime(androidId: String, bookName: String): Long?\n\n    @Insert(onConflict = OnConflictStrategy.REPLACE)\n    fun insert(vararg readRecord: ReadRecord)\n\n    @Update\n    fun update(vararg record: ReadRecord)\n\n    @Delete\n    fun delete(vararg record: ReadRecord)\n\n    @Query(\"delete from readRecord\")\n    fun clear()\n\n    @Query(\"delete from readRecord where bookName = :bookName\")\n    fun deleteByName(bookName: String)\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/data/dao/ReplaceRuleDao.kt",
    "content": "package io.legado.app.data.dao\n\nimport androidx.room.Dao\nimport androidx.room.Delete\nimport androidx.room.Insert\nimport androidx.room.OnConflictStrategy\nimport androidx.room.Query\nimport androidx.room.Update\nimport io.legado.app.constant.AppPattern\nimport io.legado.app.data.entities.ReplaceRule\nimport io.legado.app.utils.cnCompare\nimport io.legado.app.utils.splitNotBlank\nimport kotlinx.coroutines.Dispatchers.IO\nimport kotlinx.coroutines.flow.Flow\nimport kotlinx.coroutines.flow.flowOn\nimport kotlinx.coroutines.flow.map\n\n\n@Dao\ninterface ReplaceRuleDao {\n\n    @Query(\"SELECT * FROM replace_rules ORDER BY sortOrder ASC\")\n    fun flowAll(): Flow<List<ReplaceRule>>\n\n    @Query(\"SELECT * FROM replace_rules where `group` like :key or name like :key ORDER BY sortOrder ASC\")\n    fun flowSearch(key: String): Flow<List<ReplaceRule>>\n\n    @Query(\"SELECT * FROM replace_rules where `group` like :key ORDER BY sortOrder ASC\")\n    fun flowGroupSearch(key: String): Flow<List<ReplaceRule>>\n\n    @Query(\"select `group` from replace_rules where `group` is not null and `group` <> ''\")\n    fun flowGroupsUnProcessed(): Flow<List<String>>\n\n    @Query(\"select * from replace_rules where `group` is null or trim(`group`) = '' or trim(`group`) like '%未分组%'\")\n    fun flowNoGroup(): Flow<List<ReplaceRule>>\n\n    @get:Query(\"SELECT MIN(sortOrder) FROM replace_rules\")\n    val minOrder: Int\n\n    @get:Query(\"SELECT MAX(sortOrder) FROM replace_rules\")\n    val maxOrder: Int\n\n    @get:Query(\"SELECT * FROM replace_rules ORDER BY sortOrder ASC\")\n    val all: List<ReplaceRule>\n\n    @get:Query(\"select distinct `group` from replace_rules where trim(`group`) <> ''\")\n    val allGroupsUnProcessed: List<String>\n\n    @get:Query(\"SELECT * FROM replace_rules WHERE isEnabled = 1 ORDER BY sortOrder ASC\")\n    val allEnabled: List<ReplaceRule>\n\n    @Query(\"SELECT * FROM replace_rules WHERE id = :id\")\n    fun findById(id: Long): ReplaceRule?\n\n    @Query(\"SELECT * FROM replace_rules WHERE id in (:ids)\")\n    fun findByIds(vararg ids: Long): List<ReplaceRule>\n\n    @Query(\n        \"\"\"SELECT * FROM replace_rules WHERE isEnabled = 1 and scopeContent = 1\n        AND (scope LIKE '%' || :name || '%' or scope LIKE '%' || :origin || '%' or scope is null or scope = '')\n        and (excludeScope is null or (excludeScope not LIKE '%' || :name || '%' and excludeScope not LIKE '%' || :origin || '%'))\n        order by sortOrder\"\"\"\n    )\n    fun findEnabledByContentScope(name: String, origin: String): List<ReplaceRule>\n\n    @Query(\n        \"\"\"SELECT * FROM replace_rules WHERE isEnabled = 1 and scopeTitle = 1\n        AND (scope LIKE '%' || :name || '%' or scope LIKE '%' || :origin || '%' or scope is null or scope = '')\n        and (excludeScope is null or (excludeScope not LIKE '%' || :name || '%' and excludeScope not LIKE '%' || :origin || '%'))\n        order by sortOrder\"\"\"\n    )\n    fun findEnabledByTitleScope(name: String, origin: String): List<ReplaceRule>\n\n    @Query(\"select * from replace_rules where `group` like '%' || :group || '%'\")\n    fun getByGroup(group: String): List<ReplaceRule>\n\n    @get:Query(\"select * from replace_rules where `group` is null or `group` = ''\")\n    val noGroup: List<ReplaceRule>\n\n    @get:Query(\"SELECT COUNT(*) - SUM(isEnabled) FROM replace_rules\")\n    val summary: Int\n\n    @Query(\"UPDATE replace_rules SET isEnabled = :enable\")\n    fun enableAll(enable: Boolean)\n\n    @Insert(onConflict = OnConflictStrategy.REPLACE)\n    fun insert(vararg replaceRule: ReplaceRule): List<Long>\n\n    @Update\n    fun update(vararg replaceRules: ReplaceRule)\n\n    @Delete\n    fun delete(vararg replaceRules: ReplaceRule)\n\n    private fun dealGroups(list: List<String>): List<String> {\n        val groups = linkedSetOf<String>()\n        list.forEach {\n            it.splitNotBlank(AppPattern.splitGroupRegex).forEach { group ->\n                groups.add(group)\n            }\n        }\n        return groups.sortedWith { o1, o2 ->\n            o1.cnCompare(o2)\n        }\n    }\n\n    fun allGroups(): List<String> = dealGroups(allGroupsUnProcessed)\n\n    fun flowGroups(): Flow<List<String>> {\n        return flowGroupsUnProcessed().map { list ->\n            dealGroups(list)\n        }.flowOn(IO)\n    }\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/data/dao/RssArticleDao.kt",
    "content": "package io.legado.app.data.dao\n\nimport androidx.room.*\nimport io.legado.app.data.entities.RssArticle\nimport kotlinx.coroutines.flow.Flow\n\n@Dao\ninterface RssArticleDao {\n\n    @Query(\"select * from rssArticles where origin = :origin and link = :link\")\n    fun get(origin: String, link: String): RssArticle?\n\n    @Query(\n        \"\"\"select t1.link, t1.sort, t1.origin, t1.`order`, t1.title, t1.content, \n            t1.description, t1.image, t1.`group`, t1.pubDate, t1.variable, ifNull(t2.read, 0) as read\n        from rssArticles as t1 left join rssReadRecords as t2\n        on t1.link = t2.record  where origin = :origin and sort = :sort\n        order by `order` desc\"\"\"\n    )\n    fun flowByOriginSort(origin: String, sort: String): Flow<List<RssArticle>>\n\n    @Insert(onConflict = OnConflictStrategy.REPLACE)\n    fun insert(vararg rssArticle: RssArticle)\n\n    @Insert(onConflict = OnConflictStrategy.IGNORE)\n    fun append(vararg rssArticle: RssArticle)\n\n    @Query(\"delete from rssArticles where origin = :origin and sort = :sort and `order` < :order\")\n    fun clearOld(origin: String, sort: String, order: Long)\n\n    @Update\n    fun update(vararg rssArticle: RssArticle)\n\n    @Query(\"update rssArticles set origin = :origin where origin = :oldOrigin\")\n    fun updateOrigin(origin: String, oldOrigin: String)\n\n    @Query(\"delete from rssArticles where origin = :origin\")\n    fun delete(origin: String)\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/data/dao/RssReadRecordDao.kt",
    "content": "package io.legado.app.data.dao\n\nimport androidx.room.*\nimport io.legado.app.data.entities.RssReadRecord\n\n@Dao\ninterface RssReadRecordDao {\n\n    @Insert(onConflict = OnConflictStrategy.IGNORE)\n    fun insertRecord(vararg rssReadRecord: RssReadRecord)\n\n    @Query(\"select * from rssReadRecords order by readTime desc\")\n    fun getRecords(): List<RssReadRecord>\n\n    @get:Query(\"select count(1) from rssReadRecords\")\n    val countRecords: Int\n\n    @Query(\"delete from rssReadRecords\")\n    fun deleteAllRecord()\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/data/dao/RssSourceDao.kt",
    "content": "package io.legado.app.data.dao\n\nimport androidx.room.Dao\nimport androidx.room.Delete\nimport androidx.room.Insert\nimport androidx.room.OnConflictStrategy\nimport androidx.room.Query\nimport androidx.room.Update\nimport io.legado.app.constant.AppPattern\nimport io.legado.app.data.entities.RssSource\nimport io.legado.app.utils.cnCompare\nimport io.legado.app.utils.splitNotBlank\nimport kotlinx.coroutines.Dispatchers.IO\nimport kotlinx.coroutines.flow.Flow\nimport kotlinx.coroutines.flow.flowOn\nimport kotlinx.coroutines.flow.map\n\n@Dao\ninterface RssSourceDao {\n\n    @Query(\"select * from rssSources where sourceUrl = :key\")\n    fun getByKey(key: String): RssSource?\n\n    @Query(\"select * from rssSources where sourceUrl in (:sourceUrls)\")\n    fun getRssSources(vararg sourceUrls: String): List<RssSource>\n\n    @get:Query(\"SELECT * FROM rssSources order by customOrder\")\n    val all: List<RssSource>\n\n    @get:Query(\"select count(sourceUrl) from rssSources\")\n    val size: Int\n\n    @Query(\"SELECT * FROM rssSources order by customOrder\")\n    fun flowAll(): Flow<List<RssSource>>\n\n    @Query(\n        \"\"\"SELECT * FROM rssSources\n        where sourceName like '%' || :key || '%' \n        or sourceUrl like '%' || :key || '%' \n        or sourceGroup like '%' || :key || '%'\n        or sourceComment like '%' || :key || '%'\n        order by customOrder\"\"\"\n    )\n    fun flowSearch(key: String): Flow<List<RssSource>>\n\n    @Query(\n        \"\"\"SELECT * FROM rssSources \n        where (sourceGroup = :key\n        or sourceGroup like :key || ',%' \n        or sourceGroup like  '%,' || :key\n        or sourceGroup like  '%,' || :key || ',%')\n        order by customOrder\"\"\"\n    )\n    fun flowGroupSearch(key: String): Flow<List<RssSource>>\n\n    @Query(\"SELECT * FROM rssSources where enabled = 1 order by customOrder\")\n    fun flowEnabled(): Flow<List<RssSource>>\n\n    @Query(\"SELECT * FROM rssSources where enabled = 0 order by customOrder\")\n    fun flowDisabled(): Flow<List<RssSource>>\n\n    @Query(\"select * from rssSources where loginUrl is not null and loginUrl != ''\")\n    fun flowLogin(): Flow<List<RssSource>>\n\n    @Query(\"select * from rssSources where sourceGroup is null or sourceGroup = '' or sourceGroup like '%未分组%'\")\n    fun flowNoGroup(): Flow<List<RssSource>>\n\n    @Query(\n        \"\"\"SELECT * FROM rssSources \n        where enabled = 1 \n        and (sourceName like '%' || :searchKey || '%' \n            or sourceGroup like '%' || :searchKey || '%' \n            or sourceUrl like '%' || :searchKey || '%'\n            or sourceComment like '%' || :searchKey || '%') \n        order by customOrder\"\"\"\n    )\n    fun flowEnabled(searchKey: String): Flow<List<RssSource>>\n\n    @Query(\n        \"\"\"SELECT * FROM rssSources \n        where enabled = 1 and (sourceGroup = :searchKey\n        or sourceGroup like :searchKey || ',%' \n        or sourceGroup like  '%,' || :searchKey\n        or sourceGroup like  '%,' || :searchKey || ',%') \n        order by customOrder\"\"\"\n    )\n    fun flowEnabledByGroup(searchKey: String): Flow<List<RssSource>>\n\n    @Query(\"select distinct sourceGroup from rssSources where trim(sourceGroup) <> ''\")\n    fun flowGroupsUnProcessed(): Flow<List<String>>\n\n    @Query(\"select distinct sourceGroup from rssSources where trim(sourceGroup) <> '' and enabled = 1\")\n    fun flowEnabledGroupsUnProcessed(): Flow<List<String>>\n\n    @get:Query(\"select distinct sourceGroup from rssSources where trim(sourceGroup) <> ''\")\n    val allGroupsUnProcessed: List<String>\n\n    @get:Query(\"select min(customOrder) from rssSources\")\n    val minOrder: Int\n\n    @get:Query(\"select max(customOrder) from rssSources\")\n    val maxOrder: Int\n\n    @Insert(onConflict = OnConflictStrategy.REPLACE)\n    fun insert(vararg rssSource: RssSource)\n\n    @Update\n    fun update(vararg rssSource: RssSource)\n\n    @Delete\n    fun delete(vararg rssSource: RssSource)\n\n    @Query(\"delete from rssSources where sourceUrl = :sourceUrl\")\n    fun delete(sourceUrl: String)\n\n    @Query(\"delete from rssSources where sourceGroup like 'legado'\")\n    fun deleteDefault()\n\n    @get:Query(\"select * from rssSources where sourceGroup is null or sourceGroup = ''\")\n    val noGroup: List<RssSource>\n\n    @Query(\"select * from rssSources where sourceGroup like '%' || :group || '%'\")\n    fun getByGroup(group: String): List<RssSource>\n\n    @Query(\"select exists(select 1 from rssSources where sourceUrl = :key)\")\n    fun has(key: String): Boolean\n\n    @Query(\"update rssSources set enabled = :enable where sourceUrl = :sourceUrl\")\n    fun enable(sourceUrl: String, enable: Boolean)\n\n    private fun dealGroups(list: List<String>): List<String> {\n        val groups = linkedSetOf<String>()\n        list.forEach {\n            it.splitNotBlank(AppPattern.splitGroupRegex).forEach { group ->\n                groups.add(group)\n            }\n        }\n        return groups.sortedWith { o1, o2 ->\n            o1.cnCompare(o2)\n        }\n    }\n\n    fun allGroups(): List<String> = dealGroups(allGroupsUnProcessed)\n\n    fun flowGroups(): Flow<List<String>> {\n        return flowGroupsUnProcessed().map { list ->\n            dealGroups(list)\n        }.flowOn(IO)\n    }\n\n    fun flowEnabledGroups(): Flow<List<String>> {\n        return flowEnabledGroupsUnProcessed().map { list ->\n            dealGroups(list)\n        }.flowOn(IO)\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/data/dao/RssStarDao.kt",
    "content": "package io.legado.app.data.dao\n\nimport androidx.room.*\nimport io.legado.app.data.entities.RssStar\nimport kotlinx.coroutines.flow.Flow\n\n@Dao\ninterface RssStarDao {\n\n    @get:Query(\"select * from rssStars order by starTime desc\")\n    val all: List<RssStar>\n\n    @Query(\"select `group` from rssStars group by `group` order by `group`\")\n    fun flowGroups(): Flow<List<String>>\n\n    @Query(\"select * from rssStars where `group` = :group order by starTime desc\")\n    fun flowByGroup(group: String): Flow<List<RssStar>>\n\n    @Query(\"select * from rssStars where origin = :origin and link = :link\")\n    fun get(origin: String, link: String): RssStar?\n\n    @Query(\"select * from rssStars order by starTime desc\")\n    fun liveAll(): Flow<List<RssStar>>\n\n    @Insert(onConflict = OnConflictStrategy.REPLACE)\n    fun insert(vararg rssStar: RssStar)\n\n    @Update\n    fun update(vararg rssStar: RssStar)\n\n    @Query(\"update rssStars set origin = :origin where origin = :oldOrigin\")\n    fun updateOrigin(origin: String, oldOrigin: String)\n\n    @Query(\"delete from rssStars where origin = :origin\")\n    fun delete(origin: String)\n\n    @Query(\"delete from rssStars where origin = :origin and link = :link\")\n    fun delete(origin: String, link: String)\n\n    @Query(\"delete from rssStars where `group` = :group\")\n    fun deleteByGroup(group: String)\n\n    @Query(\"delete from rssStars\")\n    fun deleteAll()\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/data/dao/RuleSubDao.kt",
    "content": "package io.legado.app.data.dao\n\nimport androidx.room.*\nimport io.legado.app.data.entities.RuleSub\nimport kotlinx.coroutines.flow.Flow\n\n@Dao\ninterface RuleSubDao {\n\n    @get:Query(\"select * from ruleSubs order by customOrder\")\n    val all: List<RuleSub>\n\n    @Query(\"select * from ruleSubs order by customOrder\")\n    fun flowAll(): Flow<List<RuleSub>>\n\n    @get:Query(\"select customOrder from ruleSubs order by customOrder limit 0,1\")\n    val maxOrder: Int\n\n    @Query(\"select * from ruleSubs where url = :url\")\n    fun findByUrl(url: String): RuleSub?\n\n    @Insert(onConflict = OnConflictStrategy.REPLACE)\n    fun insert(vararg ruleSub: RuleSub)\n\n    @Delete\n    fun delete(vararg ruleSub: RuleSub)\n\n    @Update\n    fun update(vararg ruleSub: RuleSub)\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/data/dao/SearchBookDao.kt",
    "content": "package io.legado.app.data.dao\n\nimport androidx.room.*\nimport io.legado.app.data.entities.SearchBook\n\n@Dao\ninterface SearchBookDao {\n\n    @Query(\"select * from searchBooks where bookUrl = :bookUrl\")\n    fun getSearchBook(bookUrl: String): SearchBook?\n\n    @Query(\"select * from searchBooks where name = :name and author = :author and origin in (select bookSourceUrl from book_sources) order by originOrder limit 1\")\n    fun getFirstByNameAuthor(name: String, author: String): SearchBook?\n\n    @Query(\n        \"\"\"select t1.name, t1.author, t1.origin, t1.originName, t1.coverUrl, t1.bookUrl, \n        t1.type, t1.time, t1.intro, t1.kind, t1.latestChapterTitle, t1.tocUrl, t1.variable, \n        t1.wordCount, t2.customOrder as originOrder, t1.chapterWordCountText, t1.respondTime, t1.chapterWordCount\n        from searchBooks as t1 inner join book_sources as t2 \n        on t1.origin = t2.bookSourceUrl \n        where t1.name = :name and t1.author like '%'||:author||'%' \n        and t2.enabled = 1 and t2.bookSourceGroup like '%'||:sourceGroup||'%'\n        order by t2.customOrder\"\"\"\n    )\n    fun changeSourceByGroup(name: String, author: String, sourceGroup: String): List<SearchBook>\n\n    @Query(\n        \"\"\"select t1.name, t1.author, t1.origin, t1.originName, t1.coverUrl, t1.bookUrl, \n        t1.type, t1.time, t1.intro, t1.kind, t1.latestChapterTitle, t1.tocUrl, t1.variable, \n        t1.wordCount, t2.customOrder as originOrder, t1.chapterWordCountText, t1.respondTime, t1.chapterWordCount\n        from searchBooks as t1 inner join book_sources as t2 \n        on t1.origin = t2.bookSourceUrl \n        where t1.name = :name and t1.author like '%'||:author||'%'\n        and t2.bookSourceGroup like '%'||:sourceGroup||'%'\n        and (originName like '%'||:key||'%' or t1.latestChapterTitle like '%'||:key||'%')\n        and t2.enabled = 1 \n        order by t2.customOrder\"\"\"\n    )\n    fun changeSourceSearch(\n        name: String,\n        author: String,\n        key: String,\n        sourceGroup: String\n    ): List<SearchBook>\n\n    @Query(\n        \"\"\"\n        select t1.name, t1.author, t1.origin, t1.originName, t1.coverUrl, t1.bookUrl, \n        t1.type, t1.time, t1.intro, t1.kind, t1.latestChapterTitle, t1.tocUrl, t1.variable, \n        t1.wordCount, t2.customOrder as originOrder, t1.chapterWordCountText, t1.respondTime, t1.chapterWordCount\n        from searchBooks as t1 inner join book_sources as t2 \n        on t1.origin = t2.bookSourceUrl \n        where t1.name = :name and t1.author = :author and t1.coverUrl is not null and t1.coverUrl <> '' and t2.enabled = 1\n        order by t2.customOrder\n        \"\"\"\n    )\n    fun getEnableHasCover(name: String, author: String): List<SearchBook>\n\n    @Insert(onConflict = OnConflictStrategy.REPLACE)\n    fun insert(vararg searchBook: SearchBook): List<Long>\n\n    @Query(\"delete from searchBooks where name = :name and author = :author\")\n    fun clear(name: String, author: String)\n\n    @Query(\"delete from searchBooks where time < :time\")\n    fun clearExpired(time: Long)\n\n    @Update\n    fun update(vararg searchBook: SearchBook)\n\n    @Delete\n    fun delete(vararg searchBook: SearchBook)\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/data/dao/SearchKeywordDao.kt",
    "content": "package io.legado.app.data.dao\n\nimport androidx.room.*\nimport io.legado.app.data.entities.SearchKeyword\nimport kotlinx.coroutines.flow.Flow\n\n\n@Dao\ninterface SearchKeywordDao {\n\n    @get:Query(\"SELECT * FROM search_keywords\")\n    val all: List<SearchKeyword>\n\n    @Query(\"SELECT * FROM search_keywords ORDER BY usage DESC\")\n    fun flowByUsage(): Flow<List<SearchKeyword>>\n\n    @Query(\"SELECT * FROM search_keywords ORDER BY lastUseTime DESC\")\n    fun flowByTime(): Flow<List<SearchKeyword>>\n\n    @Query(\"SELECT * FROM search_keywords where word like '%'||:key||'%' ORDER BY usage DESC\")\n    fun flowSearch(key: String): Flow<List<SearchKeyword>>\n\n    @Query(\"select * from search_keywords where word = :key\")\n    fun get(key: String): SearchKeyword?\n\n    @Insert(onConflict = OnConflictStrategy.REPLACE)\n    fun insert(vararg keywords: SearchKeyword)\n\n    @Update\n    fun update(vararg keywords: SearchKeyword)\n\n    @Delete\n    fun delete(vararg keywords: SearchKeyword)\n\n    @Query(\"DELETE FROM search_keywords\")\n    fun deleteAll()\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/data/dao/ServerDao.kt",
    "content": "package io.legado.app.data.dao\n\nimport androidx.room.*\nimport io.legado.app.data.entities.Server\nimport kotlinx.coroutines.flow.Flow\n\n@Dao\ninterface ServerDao {\n\n    @Query(\"select * from servers order by sortNumber\")\n    fun observeAll(): Flow<List<Server>>\n\n    @get:Query(\"select * from servers order by sortNumber\")\n    val all: List<Server>\n\n    @Query(\"select * from servers where id = :id\")\n    fun get(id: Long): Server?\n\n    @Insert(onConflict = OnConflictStrategy.REPLACE)\n    fun insert(vararg server: Server)\n\n    @Update(onConflict = OnConflictStrategy.REPLACE)\n    fun update(vararg server: Server)\n\n    @Delete\n    fun delete(vararg server: Server)\n\n    @Query(\"delete from servers where id = :id\")\n    fun delete(id: Long)\n\n    @Query(\"delete from servers where id < 0\")\n    fun deleteDefault()\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/data/dao/TxtTocRuleDao.kt",
    "content": "package io.legado.app.data.dao\n\nimport androidx.room.*\nimport io.legado.app.data.entities.TxtTocRule\nimport kotlinx.coroutines.flow.Flow\n\n@Dao\ninterface TxtTocRuleDao {\n\n    @Query(\"select * from txtTocRules order by serialNumber\")\n    fun observeAll(): Flow<List<TxtTocRule>>\n\n    @get:Query(\"select * from txtTocRules order by serialNumber\")\n    val all: List<TxtTocRule>\n\n    @get:Query(\"select * from txtTocRules where enable = 1 order by serialNumber\")\n    val enabled: List<TxtTocRule>\n\n    @get:Query(\"select * from txtTocRules where enable != 1 order by serialNumber\")\n    val disabled: List<TxtTocRule>\n\n    @get:Query(\"select count(*) from txtTocRules\")\n    val count: Int\n\n    @Query(\"select * from txtTocRules where id = :id\")\n    fun get(id: Long): TxtTocRule?\n\n    @get:Query(\"select ifNull(min(serialNumber), 0) from txtTocRules\")\n    val minOrder: Int\n\n    @get:Query(\"select ifNull(max(serialNumber), 0) from txtTocRules\")\n    val maxOrder: Int\n\n    @Insert(onConflict = OnConflictStrategy.REPLACE)\n    fun insert(vararg rule: TxtTocRule)\n\n    @Update(onConflict = OnConflictStrategy.REPLACE)\n    fun update(vararg rule: TxtTocRule)\n\n    @Delete\n    fun delete(vararg rule: TxtTocRule)\n\n    @Query(\"delete from txtTocRules where id < 0\")\n    fun deleteDefault()\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/data/entities/BaseBook.kt",
    "content": "package io.legado.app.data.entities\n\nimport io.legado.app.help.RuleBigDataHelp\nimport io.legado.app.model.analyzeRule.RuleDataInterface\nimport io.legado.app.utils.GSON\nimport io.legado.app.utils.splitNotBlank\n\ninterface BaseBook : RuleDataInterface {\n    var name: String\n    var author: String\n    var bookUrl: String\n    var kind: String?\n    var wordCount: String?\n    var variable: String?\n\n    var infoHtml: String?\n    var tocHtml: String?\n\n    override fun putVariable(key: String, value: String?): Boolean {\n        if (super.putVariable(key, value)) {\n            variable = GSON.toJson(variableMap)\n        }\n        return true\n    }\n\n    fun putCustomVariable(value: String?) {\n        putVariable(\"custom\", value)\n    }\n\n    fun getCustomVariable(): String {\n        return getVariable(\"custom\")\n    }\n\n    override fun putBigVariable(key: String, value: String?) {\n        RuleBigDataHelp.putBookVariable(bookUrl, key, value)\n    }\n\n    override fun getBigVariable(key: String): String? {\n        return RuleBigDataHelp.getBookVariable(bookUrl, key)\n    }\n\n    fun getKindList(): List<String> {\n        val kindList = arrayListOf<String>()\n        wordCount?.let {\n            if (it.isNotBlank()) kindList.add(it)\n        }\n        kind?.let {\n            val kinds = it.splitNotBlank(\",\", \"\\n\")\n            kindList.addAll(kinds)\n        }\n        return kindList\n    }\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/data/entities/BaseRssArticle.kt",
    "content": "package io.legado.app.data.entities\n\nimport io.legado.app.help.RuleBigDataHelp\nimport io.legado.app.model.analyzeRule.RuleDataInterface\nimport io.legado.app.utils.GSON\n\ninterface BaseRssArticle : RuleDataInterface {\n\n    var origin: String\n    var link: String\n\n    var variable: String?\n\n    override fun putVariable(key: String, value: String?): Boolean {\n        if (super.putVariable(key, value)) {\n            variable = GSON.toJson(variableMap)\n        }\n        return true\n    }\n\n    override fun putBigVariable(key: String, value: String?) {\n        RuleBigDataHelp.putRssVariable(origin, link, key, value)\n    }\n\n    override fun getBigVariable(key: String): String? {\n        return RuleBigDataHelp.getRssVariable(origin, link, key)\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/data/entities/BaseSource.kt",
    "content": "package io.legado.app.data.entities\n\nimport cn.hutool.crypto.symmetric.AES\nimport com.script.ScriptBindings\nimport com.script.buildScriptBindings\nimport com.script.rhino.RhinoScriptEngine\nimport io.legado.app.constant.AppConst\nimport io.legado.app.constant.AppLog\nimport io.legado.app.data.entities.rule.RowUi\nimport io.legado.app.help.CacheManager\nimport io.legado.app.help.JsExtensions\nimport io.legado.app.help.config.AppConfig\nimport io.legado.app.help.crypto.SymmetricCryptoAndroid\nimport io.legado.app.help.http.CookieStore\nimport io.legado.app.help.source.getShareScope\nimport io.legado.app.utils.GSON\nimport io.legado.app.utils.GSONStrict\nimport io.legado.app.utils.fromJsonArray\nimport io.legado.app.utils.fromJsonObject\nimport io.legado.app.utils.has\nimport io.legado.app.utils.printOnDebug\nimport org.intellij.lang.annotations.Language\n\n/**\n * 可在js里调用,source.xxx()\n */\n@Suppress(\"unused\")\ninterface BaseSource : JsExtensions {\n    /**\n     * 并发率\n     */\n    var concurrentRate: String?\n\n    /**\n     * 登录地址\n     */\n    var loginUrl: String?\n\n    /**\n     * 登录UI\n     */\n    var loginUi: String?\n\n    /**\n     * 请求头\n     */\n    var header: String?\n\n    /**\n     * 启用cookieJar\n     */\n    var enabledCookieJar: Boolean?\n\n    /**\n     * js库\n     */\n    var jsLib: String?\n\n    fun getTag(): String\n\n    fun getKey(): String\n\n    override fun getSource(): BaseSource? {\n        return this\n    }\n\n    fun loginUi(): List<RowUi>? {\n        return GSON.fromJsonArray<RowUi>(loginUi).onFailure {\n            it.printOnDebug()\n        }.getOrNull()\n    }\n\n    fun getLoginJs(): String? {\n        val loginJs = loginUrl\n        return when {\n            loginJs == null -> null\n            loginJs.startsWith(\"@js:\") -> loginJs.substring(4)\n            loginJs.startsWith(\"<js>\") -> loginJs.substring(4, loginJs.lastIndexOf(\"<\"))\n            else -> loginJs\n        }\n    }\n\n    /**\n     * 调用login函数 实现登录请求\n     */\n    fun login() {\n        val loginJs = getLoginJs()\n        if (!loginJs.isNullOrBlank()) {\n            @Language(\"js\")\n            val js = \"\"\"$loginJs\n                if(typeof login=='function'){\n                    login.apply(this);\n                } else {\n                    throw('Function login not implements!!!')\n                }\n            \"\"\".trimIndent()\n            evalJS(js)\n        }\n    }\n\n    /**\n     * 解析header规则\n     */\n    fun getHeaderMap(hasLoginHeader: Boolean = false) = HashMap<String, String>().apply {\n        header?.let {\n            try {\n                val json = when {\n                    it.startsWith(\"@js:\", true) -> evalJS(it.substring(4)).toString()\n                    it.startsWith(\"<js>\", true) -> evalJS(\n                        it.substring(4, it.lastIndexOf(\"<\"))\n                    ).toString()\n\n                    else -> it\n                }\n                GSONStrict.fromJsonObject<Map<String, String>>(json).getOrNull()?.let { map ->\n                    putAll(map)\n                } ?: GSON.fromJsonObject<Map<String, String>>(json).getOrNull()?.let { map ->\n                    log(\"请求头规则 JSON 格式不规范，请改为规范格式\")\n                    putAll(map)\n                }\n            } catch (e: Exception) {\n                AppLog.put(\"执行请求头规则出错\\n$e\", e)\n            }\n        }\n        if (!has(AppConst.UA_NAME, true)) {\n            put(AppConst.UA_NAME, AppConfig.userAgent)\n        }\n        if (hasLoginHeader) {\n            getLoginHeaderMap()?.let {\n                putAll(it)\n            }\n        }\n    }\n\n    /**\n     * 获取用于登录的头部信息\n     */\n    fun getLoginHeader(): String? {\n        return CacheManager.get(\"loginHeader_${getKey()}\")\n    }\n\n    fun getLoginHeaderMap(): Map<String, String>? {\n        val cache = getLoginHeader() ?: return null\n        return GSON.fromJsonObject<Map<String, String>>(cache).getOrNull()\n    }\n\n    /**\n     * 保存登录头部信息,map格式,访问时自动添加\n     */\n    fun putLoginHeader(header: String) {\n        val headerMap = GSON.fromJsonObject<Map<String, String>>(header).getOrNull()\n        val cookie = headerMap?.get(\"Cookie\") ?: headerMap?.get(\"cookie\")\n        cookie?.let {\n            CookieStore.replaceCookie(getKey(), it)\n        }\n        CacheManager.put(\"loginHeader_${getKey()}\", header)\n    }\n\n    fun removeLoginHeader() {\n        CacheManager.delete(\"loginHeader_${getKey()}\")\n        CookieStore.removeCookie(getKey())\n    }\n\n    /**\n     * 获取用户信息,可以用来登录\n     * 用户信息采用aes加密存储\n     */\n    fun getLoginInfo(): String? {\n        try {\n            val key = AppConst.androidId.encodeToByteArray(0, 16)\n            val cache = CacheManager.get(\"userInfo_${getKey()}\") ?: return null\n            return AES(key).decryptStr(cache)\n        } catch (e: Exception) {\n            AppLog.put(\"获取登陆信息出错\", e)\n            return null\n        }\n    }\n\n    fun getLoginInfoMap(): Map<String, String>? {\n        return GSON.fromJsonObject<Map<String, String>>(getLoginInfo()).getOrNull()\n    }\n\n    /**\n     * 保存用户信息,aes加密\n     */\n    fun putLoginInfo(info: String): Boolean {\n        return try {\n            val key = (AppConst.androidId).encodeToByteArray(0, 16)\n            val encodeStr = SymmetricCryptoAndroid(\"AES\", key).encryptBase64(info)\n            CacheManager.put(\"userInfo_${getKey()}\", encodeStr)\n            true\n        } catch (e: Exception) {\n            AppLog.put(\"保存登陆信息出错\", e)\n            false\n        }\n    }\n\n    fun removeLoginInfo() {\n        CacheManager.delete(\"userInfo_${getKey()}\")\n    }\n\n    /**\n     * 设置自定义变量\n     * @param variable 变量内容\n     */\n    fun setVariable(variable: String?) {\n        if (variable != null) {\n            CacheManager.put(\"sourceVariable_${getKey()}\", variable)\n        } else {\n            CacheManager.delete(\"sourceVariable_${getKey()}\")\n        }\n    }\n\n    /**\n     * 获取自定义变量\n     */\n    fun getVariable(): String {\n        return CacheManager.get(\"sourceVariable_${getKey()}\") ?: \"\"\n    }\n\n    /**\n     * 保存数据\n     */\n    fun put(key: String, value: String): String {\n        CacheManager.put(\"v_${getKey()}_${key}\", value)\n        return value\n    }\n\n    /**\n     * 获取保存的数据\n     */\n    fun get(key: String): String {\n        return CacheManager.get(\"v_${getKey()}_${key}\") ?: \"\"\n    }\n\n    /**\n     * 执行JS\n     */\n    @Throws(Exception::class)\n    fun evalJS(jsStr: String, bindingsConfig: ScriptBindings.() -> Unit = {}): Any? {\n        val bindings = buildScriptBindings { bindings ->\n            bindings[\"java\"] = this\n            bindings[\"source\"] = this\n            bindings[\"baseUrl\"] = getKey()\n            bindings[\"cookie\"] = CookieStore\n            bindings[\"cache\"] = CacheManager\n            bindings.apply(bindingsConfig)\n        }\n        val sharedScope = getShareScope()\n        val scope = if (sharedScope == null) {\n            RhinoScriptEngine.getRuntimeScope(bindings)\n        } else {\n            bindings.apply {\n                prototype = sharedScope\n            }\n        }\n        return RhinoScriptEngine.eval(jsStr, scope)\n    }\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/data/entities/Book.kt",
    "content": "package io.legado.app.data.entities\n\nimport android.os.Parcelable\nimport androidx.room.ColumnInfo\nimport androidx.room.Entity\nimport androidx.room.Ignore\nimport androidx.room.Index\nimport androidx.room.PrimaryKey\nimport androidx.room.TypeConverter\nimport androidx.room.TypeConverters\nimport io.legado.app.constant.AppPattern\nimport io.legado.app.constant.BookType\nimport io.legado.app.constant.PageAnim\nimport io.legado.app.data.appDb\nimport io.legado.app.help.book.BookHelp\nimport io.legado.app.help.book.ContentProcessor\nimport io.legado.app.help.book.getFolderNameNoCache\nimport io.legado.app.help.book.isEpub\nimport io.legado.app.help.book.isImage\nimport io.legado.app.help.book.simulatedTotalChapterNum\nimport io.legado.app.help.config.AppConfig\nimport io.legado.app.help.config.ReadBookConfig\nimport io.legado.app.model.ReadBook\nimport io.legado.app.utils.GSON\nimport io.legado.app.utils.fromJsonObject\nimport kotlinx.parcelize.IgnoredOnParcel\nimport kotlinx.parcelize.Parcelize\nimport java.nio.charset.Charset\nimport java.time.LocalDate\nimport kotlin.math.max\n\n@Parcelize\n@TypeConverters(Book.Converters::class)\n@Entity(\n    tableName = \"books\",\n    indices = [Index(value = [\"name\", \"author\"], unique = true)]\n)\ndata class Book(\n    // 详情页Url(本地书源存储完整文件路径)\n    @PrimaryKey\n    @ColumnInfo(defaultValue = \"\")\n    override var bookUrl: String = \"\",\n    // 目录页Url (toc=table of Contents)\n    @ColumnInfo(defaultValue = \"\")\n    var tocUrl: String = \"\",\n    // 书源URL(默认BookType.local)\n    @ColumnInfo(defaultValue = BookType.localTag)\n    var origin: String = BookType.localTag,\n    //书源名称 or 本地书籍文件名\n    @ColumnInfo(defaultValue = \"\")\n    var originName: String = \"\",\n    // 书籍名称(书源获取)\n    @ColumnInfo(defaultValue = \"\")\n    override var name: String = \"\",\n    // 作者名称(书源获取)\n    @ColumnInfo(defaultValue = \"\")\n    override var author: String = \"\",\n    // 分类信息(书源获取)\n    override var kind: String? = null,\n    // 分类信息(用户修改)\n    var customTag: String? = null,\n    // 封面Url(书源获取)\n    var coverUrl: String? = null,\n    // 封面Url(用户修改)\n    var customCoverUrl: String? = null,\n    // 简介内容(书源获取)\n    var intro: String? = null,\n    // 简介内容(用户修改)\n    var customIntro: String? = null,\n    // 自定义字符集名称(仅适用于本地书籍)\n    var charset: String? = null,\n    // 类型,详见BookType\n    @ColumnInfo(defaultValue = \"0\")\n    var type: Int = BookType.text,\n    // 自定义分组索引号\n    @ColumnInfo(defaultValue = \"0\")\n    var group: Long = 0,\n    // 最新章节标题\n    var latestChapterTitle: String? = null,\n    // 最新章节标题更新时间\n    @ColumnInfo(defaultValue = \"0\")\n    var latestChapterTime: Long = System.currentTimeMillis(),\n    // 最近一次更新书籍信息的时间\n    @ColumnInfo(defaultValue = \"0\")\n    var lastCheckTime: Long = System.currentTimeMillis(),\n    // 最近一次发现新章节的数量\n    @ColumnInfo(defaultValue = \"0\")\n    var lastCheckCount: Int = 0,\n    // 书籍目录总数\n    @ColumnInfo(defaultValue = \"0\")\n    var totalChapterNum: Int = 0,\n    // 当前章节名称\n    var durChapterTitle: String? = null,\n    // 当前章节索引\n    @ColumnInfo(defaultValue = \"0\")\n    var durChapterIndex: Int = 0,\n    // 当前阅读的进度(首行字符的索引位置)\n    @ColumnInfo(defaultValue = \"0\")\n    var durChapterPos: Int = 0,\n    // 最近一次阅读书籍的时间(打开正文的时间)\n    @ColumnInfo(defaultValue = \"0\")\n    var durChapterTime: Long = System.currentTimeMillis(),\n    //字数\n    override var wordCount: String? = null,\n    // 刷新书架时更新书籍信息\n    @ColumnInfo(defaultValue = \"1\")\n    var canUpdate: Boolean = true,\n    // 手动排序\n    @ColumnInfo(defaultValue = \"0\")\n    var order: Int = 0,\n    //书源排序\n    @ColumnInfo(defaultValue = \"0\")\n    var originOrder: Int = 0,\n    // 自定义书籍变量信息(用于书源规则检索书籍信息)\n    override var variable: String? = null,\n    //阅读设置\n    var readConfig: ReadConfig? = null,\n    //同步时间\n    @ColumnInfo(defaultValue = \"0\")\n    var syncTime: Long = 0L\n) : Parcelable, BaseBook {\n\n    override fun equals(other: Any?): Boolean {\n        if (other is Book) {\n            return other.bookUrl == bookUrl\n        }\n        return false\n    }\n\n    override fun hashCode(): Int {\n        return bookUrl.hashCode()\n    }\n\n    @delegate:Transient\n    @delegate:Ignore\n    @IgnoredOnParcel\n    override val variableMap: HashMap<String, String> by lazy {\n        GSON.fromJsonObject<HashMap<String, String>>(variable).getOrNull() ?: hashMapOf()\n    }\n\n    @Ignore\n    @IgnoredOnParcel\n    override var infoHtml: String? = null\n\n    @Ignore\n    @IgnoredOnParcel\n    override var tocHtml: String? = null\n\n    @Ignore\n    @IgnoredOnParcel\n    var downloadUrls: List<String>? = null\n\n    @Ignore\n    @IgnoredOnParcel\n    private var folderName: String? = null\n\n    @get:Ignore\n    @IgnoredOnParcel\n    val lastChapterIndex get() = totalChapterNum - 1\n\n    fun getRealAuthor() = author.replace(AppPattern.authorRegex, \"\")\n\n    fun getUnreadChapterNum() = max(simulatedTotalChapterNum() - durChapterIndex - 1, 0)\n\n    fun getDisplayCover() = if (customCoverUrl.isNullOrEmpty()) coverUrl else customCoverUrl\n\n    fun getDisplayIntro() = if (customIntro.isNullOrEmpty()) intro else customIntro\n\n    //自定义简介有自动更新的需求时，可通过更新intro再调用upCustomIntro()完成\n    @Suppress(\"unused\")\n    fun upCustomIntro() {\n        customIntro = intro\n    }\n\n    fun fileCharset(): Charset {\n        return charset(charset ?: \"UTF-8\")\n    }\n\n    @IgnoredOnParcel\n    val config: ReadConfig\n        get() {\n            if (readConfig == null) {\n                readConfig = ReadConfig()\n            }\n            return readConfig!!\n        }\n\n    fun setReverseToc(reverseToc: Boolean) {\n        config.reverseToc = reverseToc\n    }\n\n    fun getReverseToc(): Boolean {\n        return config.reverseToc\n    }\n\n    fun setUseReplaceRule(useReplaceRule: Boolean) {\n        config.useReplaceRule = useReplaceRule\n    }\n\n    fun getUseReplaceRule(): Boolean {\n        val useReplaceRule = config.useReplaceRule\n        if (useReplaceRule != null) {\n            return useReplaceRule\n        }\n        //图片类书源 epub本地 默认关闭净化\n        if (isImage || isEpub) {\n            return false\n        }\n        return AppConfig.replaceEnableDefault\n    }\n\n    fun setReSegment(reSegment: Boolean) {\n        config.reSegment = reSegment\n    }\n\n    fun getReSegment(): Boolean {\n        return config.reSegment\n    }\n\n    fun setPageAnim(pageAnim: Int?) {\n        config.pageAnim = pageAnim\n    }\n\n    fun getPageAnim(): Int {\n        var pageAnim = config.pageAnim\n            ?: if (isImage) PageAnim.scrollPageAnim else ReadBookConfig.pageAnim\n        if (pageAnim < 0) {\n            pageAnim = ReadBookConfig.pageAnim\n        }\n        return pageAnim\n    }\n\n    fun setImageStyle(imageStyle: String?) {\n        config.imageStyle = imageStyle\n    }\n\n    fun getImageStyle(): String? {\n        return config.imageStyle\n    }\n\n    fun setTtsEngine(ttsEngine: String?) {\n        config.ttsEngine = ttsEngine\n    }\n\n    fun getTtsEngine(): String? {\n        return config.ttsEngine\n    }\n\n    fun setSplitLongChapter(limitLongContent: Boolean) {\n        config.splitLongChapter = limitLongContent\n    }\n\n    fun getSplitLongChapter(): Boolean {\n        return config.splitLongChapter\n    }\n\n    // readSimulating 的 setter 和 getter\n    fun setReadSimulating(readSimulating: Boolean) {\n        config.readSimulating = readSimulating\n    }\n\n    fun getReadSimulating(): Boolean {\n        return config.readSimulating\n    }\n\n    // startDate 的 setter 和 getter\n    fun setStartDate(startDate: LocalDate?) {\n        config.startDate = startDate\n    }\n\n    fun getStartDate(): LocalDate? {\n        if (!config.readSimulating || config.startDate == null) {\n            return LocalDate.now()\n        }\n        return config.startDate\n    }\n\n    // startChapter 的 setter 和 getter\n    fun setStartChapter(startChapter: Int) {\n        config.startChapter = startChapter\n    }\n\n    fun getStartChapter(): Int {\n        if (config.readSimulating) return config.startChapter ?: 0\n        return this.durChapterIndex\n    }\n\n    // dailyChapters 的 setter 和 getter\n    fun setDailyChapters(dailyChapters: Int) {\n        config.dailyChapters = dailyChapters\n    }\n\n    fun getDailyChapters(): Int {\n        return config.dailyChapters\n    }\n\n    fun getDelTag(tag: Long): Boolean {\n        return config.delTag and tag == tag\n    }\n\n    fun addDelTag(tag: Long) {\n        config.delTag = config.delTag and tag\n    }\n\n    fun removeDelTag(tag: Long) {\n        config.delTag = config.delTag and tag.inv()\n    }\n\n    fun getFolderName(): String {\n        folderName?.let {\n            return it\n        }\n        //防止书名过长,只取9位\n        folderName = getFolderNameNoCache()\n        return folderName!!\n    }\n\n    fun toSearchBook() = SearchBook(\n        name = name,\n        author = author,\n        kind = kind,\n        bookUrl = bookUrl,\n        origin = origin,\n        originName = originName,\n        type = type,\n        wordCount = wordCount,\n        latestChapterTitle = latestChapterTitle,\n        coverUrl = coverUrl,\n        intro = intro,\n        tocUrl = tocUrl,\n        originOrder = originOrder,\n        variable = variable\n    ).apply {\n        this.infoHtml = this@Book.infoHtml\n        this.tocHtml = this@Book.tocHtml\n    }\n\n    /**\n     * 迁移旧的书籍的一些信息到新的书籍中\n     */\n    fun migrateTo(newBook: Book, toc: List<BookChapter>): Book {\n        newBook.durChapterIndex = BookHelp\n            .getDurChapter(durChapterIndex, durChapterTitle, toc, totalChapterNum)\n        newBook.durChapterTitle = toc[newBook.durChapterIndex].getDisplayTitle(\n            ContentProcessor.get(newBook.name, newBook.origin).getTitleReplaceRules(),\n            getUseReplaceRule()\n        )\n        newBook.durChapterPos = durChapterPos\n        newBook.durChapterTime = durChapterTime\n        newBook.group = group\n        newBook.order = order\n        newBook.customCoverUrl = customCoverUrl\n        newBook.customIntro = customIntro\n        newBook.customTag = customTag\n        newBook.canUpdate = canUpdate\n        newBook.readConfig = readConfig\n        return newBook\n    }\n\n    fun createBookMark(): Bookmark {\n        return Bookmark(\n            bookName = name,\n            bookAuthor = author,\n        )\n    }\n\n    fun save() {\n        if (appDb.bookDao.has(bookUrl)) {\n            appDb.bookDao.update(this)\n        } else {\n            appDb.bookDao.insert(this)\n        }\n    }\n\n    fun delete() {\n        if (ReadBook.book?.bookUrl == bookUrl) {\n            ReadBook.book = null\n        }\n        appDb.bookDao.delete(this)\n    }\n\n    @Suppress(\"ConstPropertyName\")\n    companion object {\n        const val hTag = 2L\n        const val rubyTag = 4L\n        const val imgStyleDefault = \"DEFAULT\"\n        const val imgStyleFull = \"FULL\"\n        const val imgStyleText = \"TEXT\"\n        const val imgStyleSingle = \"SINGLE\"\n    }\n\n    @Parcelize\n    data class ReadConfig(\n        var reverseToc: Boolean = false,\n        var pageAnim: Int? = null,\n        var reSegment: Boolean = false,\n        var imageStyle: String? = null,\n        var useReplaceRule: Boolean? = null,// 正文使用净化替换规则\n        var delTag: Long = 0L,//去除标签\n        var ttsEngine: String? = null,\n        var splitLongChapter: Boolean = true,\n        var readSimulating: Boolean = false,\n        var startDate: LocalDate? = null,\n        var startChapter: Int? = null,     // 用户设置的起始章节\n        var dailyChapters: Int = 3    // 用户设置的每日更新章节数\n    ) : Parcelable\n\n    class Converters {\n\n        @TypeConverter\n        fun readConfigToString(config: ReadConfig?): String = GSON.toJson(config)\n\n        @TypeConverter\n        fun stringToReadConfig(json: String?) = GSON.fromJsonObject<ReadConfig>(json).getOrNull()\n    }\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/data/entities/BookChapter.kt",
    "content": "package io.legado.app.data.entities\n\nimport android.annotation.SuppressLint\nimport android.os.Parcelable\nimport androidx.room.Entity\nimport androidx.room.ForeignKey\nimport androidx.room.Ignore\nimport androidx.room.Index\nimport io.legado.app.constant.AppLog\nimport io.legado.app.constant.AppPattern\nimport io.legado.app.data.appDb\nimport io.legado.app.exception.RegexTimeoutException\nimport io.legado.app.help.RuleBigDataHelp\nimport io.legado.app.help.config.AppConfig\nimport io.legado.app.model.analyzeRule.AnalyzeUrl\nimport io.legado.app.model.analyzeRule.RuleDataInterface\nimport io.legado.app.utils.ChineseUtils\nimport io.legado.app.utils.GSON\nimport io.legado.app.utils.MD5Utils\nimport io.legado.app.utils.NetworkUtils\nimport io.legado.app.utils.fromJsonObject\nimport io.legado.app.utils.replace\nimport io.legado.app.utils.toastOnUi\nimport kotlinx.coroutines.CancellationException\nimport kotlinx.parcelize.IgnoredOnParcel\nimport kotlinx.parcelize.Parcelize\nimport splitties.init.appCtx\n\n@Parcelize\n@Entity(\n    tableName = \"chapters\",\n    primaryKeys = [\"url\", \"bookUrl\"],\n    indices = [(Index(value = [\"bookUrl\"], unique = false)),\n        (Index(value = [\"bookUrl\", \"index\"], unique = true))],\n    foreignKeys = [(ForeignKey(\n        entity = Book::class,\n        parentColumns = [\"bookUrl\"],\n        childColumns = [\"bookUrl\"],\n        onDelete = ForeignKey.CASCADE\n    ))]\n)    // 删除书籍时自动删除章节\ndata class BookChapter(\n    var url: String = \"\",               // 章节地址\n    var title: String = \"\",             // 章节标题\n    var isVolume: Boolean = false,      // 是否是卷名\n    var baseUrl: String = \"\",           // 用来拼接相对url\n    var bookUrl: String = \"\",           // 书籍地址\n    var index: Int = 0,                 // 章节序号\n    var isVip: Boolean = false,         // 是否VIP\n    var isPay: Boolean = false,         // 是否已购买\n    var resourceUrl: String? = null,    // 音频真实URL\n    var tag: String? = null,            // 更新时间或其他章节附加信息\n    var wordCount: String? = null,      // 本章节字数\n    var start: Long? = null,            // 章节起始位置\n    var end: Long? = null,              // 章节终止位置\n    var startFragmentId: String? = null,  //EPUB书籍当前章节的fragmentId\n    var endFragmentId: String? = null,    //EPUB书籍下一章节的fragmentId\n    var variable: String? = null        //变量\n) : Parcelable, RuleDataInterface {\n\n    @delegate:Transient\n    @delegate:Ignore\n    @IgnoredOnParcel\n    override val variableMap: HashMap<String, String> by lazy {\n        GSON.fromJsonObject<HashMap<String, String>>(variable).getOrNull() ?: hashMapOf()\n    }\n\n    @Ignore\n    @IgnoredOnParcel\n    var titleMD5: String? = null\n\n    override fun putVariable(key: String, value: String?): Boolean {\n        if (super.putVariable(key, value)) {\n            variable = GSON.toJson(variableMap)\n        }\n        return true\n    }\n\n    override fun putBigVariable(key: String, value: String?) {\n        RuleBigDataHelp.putChapterVariable(bookUrl, url, key, value)\n    }\n\n    override fun getBigVariable(key: String): String? {\n        return RuleBigDataHelp.getChapterVariable(bookUrl, url, key)\n    }\n\n    override fun hashCode() = url.hashCode()\n\n    override fun equals(other: Any?): Boolean {\n        if (other is BookChapter) {\n            return other.url == url\n        }\n        return false\n    }\n\n    fun primaryStr(): String {\n        return bookUrl + url\n    }\n\n    fun getDisplayTitle(\n        replaceRules: List<ReplaceRule>? = null,\n        useReplace: Boolean = true,\n        chineseConvert: Boolean = true,\n    ): String {\n        var displayTitle = title.replace(AppPattern.rnRegex, \"\")\n        if (chineseConvert) {\n            when (AppConfig.chineseConverterType) {\n                1 -> displayTitle = ChineseUtils.t2s(displayTitle)\n                2 -> displayTitle = ChineseUtils.s2t(displayTitle)\n            }\n        }\n        if (useReplace && replaceRules != null) kotlin.run {\n            replaceRules.forEach { item ->\n                if (item.pattern.isNotEmpty()) {\n                    try {\n                        val mDisplayTitle = if (item.isRegex) {\n                            displayTitle.replace(\n                                item.regex,\n                                item.replacement,\n                                item.getValidTimeoutMillisecond()\n                            )\n                        } else {\n                            displayTitle.replace(item.pattern, item.replacement)\n                        }\n                        if (mDisplayTitle.isNotBlank()) {\n                            displayTitle = mDisplayTitle\n                        }\n                    } catch (e: RegexTimeoutException) {\n                        item.isEnabled = false\n                        appDb.replaceRuleDao.update(item)\n                    } catch (e: CancellationException) {\n                        return@run\n                    } catch (e: Exception) {\n                        AppLog.put(\"${item.name}替换出错\\n替换内容\\n${displayTitle}\", e)\n                        appCtx.toastOnUi(\"${item.name}替换出错\")\n                    }\n                }\n            }\n        }\n        return displayTitle\n    }\n\n    fun getAbsoluteURL(): String {\n        //二级目录解析的卷链接为空 返回目录页的链接\n        if (url.startsWith(title) && isVolume) return baseUrl\n        val urlMatcher = AnalyzeUrl.paramPattern.matcher(url)\n        val urlBefore = if (urlMatcher.find()) url.substring(0, urlMatcher.start()) else url\n        val urlAbsoluteBefore = NetworkUtils.getAbsoluteURL(baseUrl, urlBefore)\n        return if (urlBefore.length == url.length) {\n            urlAbsoluteBefore\n        } else {\n            \"$urlAbsoluteBefore,\" + url.substring(urlMatcher.end())\n        }\n    }\n\n    private fun ensureTitleMD5Init() {\n        if (titleMD5 == null) {\n            titleMD5 = MD5Utils.md5Encode16(title)\n        }\n    }\n\n    @SuppressLint(\"DefaultLocale\")\n    @Suppress(\"unused\")\n    fun getFileName(suffix: String = \"nb\"): String {\n        ensureTitleMD5Init()\n        return String.format(\"%05d-%s.%s\", index, titleMD5, suffix)\n    }\n\n    @SuppressLint(\"DefaultLocale\")\n    @Suppress(\"unused\")\n    fun getFontName(): String {\n        ensureTitleMD5Init()\n        return String.format(\"%05d-%s.ttf\", index, titleMD5)\n    }\n}\n\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/data/entities/BookChapterReview.kt",
    "content": "package io.legado.app.data.entities\n\nimport android.os.Parcelable\nimport androidx.room.ColumnInfo\nimport kotlinx.parcelize.Parcelize\n\n@Parcelize\nclass BookChapterReview(\n    @ColumnInfo(defaultValue = \"0\")\n    var bookId: Long = 0,\n    var chapterId: Long = 0,\n    var summaryUrl: String = \"\",\n): Parcelable {\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/data/entities/BookGroup.kt",
    "content": "package io.legado.app.data.entities\n\nimport android.content.Context\nimport android.os.Parcelable\nimport androidx.room.ColumnInfo\nimport androidx.room.Entity\nimport androidx.room.PrimaryKey\nimport io.legado.app.R\nimport io.legado.app.help.config.AppConfig\nimport kotlinx.parcelize.Parcelize\n\n@Suppress(\"ConstPropertyName\")\n@Parcelize\n@Entity(tableName = \"book_groups\")\ndata class BookGroup(\n    @PrimaryKey\n    val groupId: Long = 0b1,\n    var groupName: String = \"\",\n    var cover: String? = null,\n    var order: Int = 0,\n    @ColumnInfo(defaultValue = \"1\")\n    var enableRefresh: Boolean = true,\n    @ColumnInfo(defaultValue = \"1\")\n    var show: Boolean = true,\n    @ColumnInfo(defaultValue = \"-1\")\n    var bookSort: Int = -1\n) : Parcelable {\n\n    companion object {\n        const val IdRoot = -100L\n        const val IdAll = -1L\n        const val IdLocal = -2L\n        const val IdAudio = -3L\n        const val IdNetNone = -4L\n        const val IdLocalNone = -5L\n        const val IdError = -11L\n    }\n\n    fun getManageName(context: Context): String {\n        return when (groupId) {\n            IdAll -> \"$groupName(${context.getString(R.string.all)})\"\n            IdAudio -> \"$groupName(${context.getString(R.string.audio)})\"\n            IdLocal -> \"$groupName(${context.getString(R.string.local)})\"\n            IdNetNone -> \"$groupName(${context.getString(R.string.net_no_group)})\"\n            IdLocalNone -> \"$groupName(${context.getString(R.string.local_no_group)})\"\n            IdError -> \"$groupName(${context.getString(R.string.update_book_fail)})\"\n            else -> groupName\n        }\n    }\n\n    fun getRealBookSort(): Int {\n        if (bookSort < 0) {\n            return AppConfig.bookshelfSort\n        }\n        return bookSort\n    }\n\n    override fun hashCode(): Int {\n        return groupId.hashCode()\n    }\n\n    override fun equals(other: Any?): Boolean {\n        if (other is BookGroup) {\n            return other.groupId == groupId\n                    && other.groupName == groupName\n                    && other.cover == cover\n                    && other.bookSort == bookSort\n                    && other.enableRefresh == enableRefresh\n                    && other.show == show\n                    && other.order == order\n        }\n        return false\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/data/entities/BookProgress.kt",
    "content": "package io.legado.app.data.entities\n\ndata class BookProgress(\n    val name: String,\n    val author: String,\n    val durChapterIndex: Int,\n    val durChapterPos: Int,\n    val durChapterTime: Long,\n    val durChapterTitle: String?\n) {\n\n    constructor(book: Book) : this(\n        name = book.name,\n        author = book.author,\n        durChapterIndex = book.durChapterIndex,\n        durChapterPos = book.durChapterPos,\n        durChapterTime = book.durChapterTime,\n        durChapterTitle = book.durChapterTitle\n    )\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/data/entities/BookSource.kt",
    "content": "package io.legado.app.data.entities\n\nimport android.os.Parcelable\nimport android.text.TextUtils\nimport androidx.room.ColumnInfo\nimport androidx.room.Entity\nimport androidx.room.Index\nimport androidx.room.PrimaryKey\nimport androidx.room.TypeConverter\nimport androidx.room.TypeConverters\nimport io.legado.app.constant.AppPattern\nimport io.legado.app.constant.BookSourceType\nimport io.legado.app.data.entities.rule.BookInfoRule\nimport io.legado.app.data.entities.rule.ContentRule\nimport io.legado.app.data.entities.rule.ExploreRule\nimport io.legado.app.data.entities.rule.ReviewRule\nimport io.legado.app.data.entities.rule.SearchRule\nimport io.legado.app.data.entities.rule.TocRule\nimport io.legado.app.utils.GSON\nimport io.legado.app.utils.fromJsonObject\nimport io.legado.app.utils.splitNotBlank\nimport kotlinx.parcelize.Parcelize\n\n@Suppress(\"unused\")\n@Parcelize\n@TypeConverters(BookSource.Converters::class)\n@Entity(\n    tableName = \"book_sources\",\n    indices = [(Index(value = [\"bookSourceUrl\"], unique = false))]\n)\ndata class BookSource(\n    // 地址，包括 http/https\n    @PrimaryKey\n    var bookSourceUrl: String = \"\",\n    // 名称\n    var bookSourceName: String = \"\",\n    // 分组\n    var bookSourceGroup: String? = null,\n    // 类型，0 文本，1 音频, 2 图片, 3 文件（指的是类似知轩藏书只提供下载的网站）\n    @BookSourceType.Type\n    var bookSourceType: Int = 0,\n    // 详情页url正则\n    var bookUrlPattern: String? = null,\n    // 手动排序编号\n    @ColumnInfo(defaultValue = \"0\")\n    var customOrder: Int = 0,\n    // 是否启用\n    @ColumnInfo(defaultValue = \"1\")\n    var enabled: Boolean = true,\n    // 启用发现\n    @ColumnInfo(defaultValue = \"1\")\n    var enabledExplore: Boolean = true,\n    // js库\n    override var jsLib: String? = null,\n    // 启用okhttp CookieJAr 自动保存每次请求的cookie\n    @ColumnInfo(defaultValue = \"0\")\n    override var enabledCookieJar: Boolean? = true,\n    // 并发率\n    override var concurrentRate: String? = null,\n    // 请求头\n    override var header: String? = null,\n    // 登录地址\n    override var loginUrl: String? = null,\n    // 登录UI\n    override var loginUi: String? = null,\n    // 登录检测js\n    var loginCheckJs: String? = null,\n    // 封面解密js\n    var coverDecodeJs: String? = null,\n    // 注释\n    var bookSourceComment: String? = null,\n    // 自定义变量说明\n    var variableComment: String? = null,\n    // 最后更新时间，用于排序\n    var lastUpdateTime: Long = 0,\n    // 响应时间，用于排序\n    var respondTime: Long = 180000L,\n    // 智能排序的权重\n    var weight: Int = 0,\n    // 发现url\n    var exploreUrl: String? = null,\n    // 发现筛选规则\n    var exploreScreen: String? = null,\n    // 发现规则\n    var ruleExplore: ExploreRule? = null,\n    // 搜索url\n    var searchUrl: String? = null,\n    // 搜索规则\n    var ruleSearch: SearchRule? = null,\n    // 书籍信息页规则\n    var ruleBookInfo: BookInfoRule? = null,\n    // 目录页规则\n    var ruleToc: TocRule? = null,\n    // 正文页规则\n    var ruleContent: ContentRule? = null,\n    // 段评规则\n    var ruleReview: ReviewRule? = null\n) : Parcelable, BaseSource {\n\n    override fun getTag(): String {\n        return bookSourceName\n    }\n\n    override fun getKey(): String {\n        return bookSourceUrl\n    }\n\n    override fun hashCode(): Int {\n        return bookSourceUrl.hashCode()\n    }\n\n    override fun equals(other: Any?): Boolean {\n        return if (other is BookSource) other.bookSourceUrl == bookSourceUrl else false\n    }\n\n    fun getSearchRule(): SearchRule {\n        ruleSearch?.let { return it }\n        val rule = SearchRule()\n        ruleSearch = rule\n        return rule\n    }\n\n    fun getExploreRule(): ExploreRule {\n        ruleExplore?.let { return it }\n        val rule = ExploreRule()\n        ruleExplore = rule\n        return rule\n    }\n\n    fun getBookInfoRule(): BookInfoRule {\n        ruleBookInfo?.let { return it }\n        val rule = BookInfoRule()\n        ruleBookInfo = rule\n        return rule\n    }\n\n    fun getTocRule(): TocRule {\n        ruleToc?.let { return it }\n        val rule = TocRule()\n        ruleToc = rule\n        return rule\n    }\n\n    fun getContentRule(): ContentRule {\n        ruleContent?.let { return it }\n        val rule = ContentRule()\n        ruleContent = rule\n        return rule\n    }\n\n//    fun getReviewRule(): ReviewRule {\n//        ruleReview?.let { return it }\n//        val rule = ReviewRule()\n//        ruleReview = rule\n//        return rule\n//    }\n\n    fun getDisPlayNameGroup(): String {\n        return if (bookSourceGroup.isNullOrBlank()) {\n            bookSourceName\n        } else {\n            String.format(\"%s (%s)\", bookSourceName, bookSourceGroup)\n        }\n    }\n\n    fun addGroup(groups: String): BookSource {\n        bookSourceGroup?.splitNotBlank(AppPattern.splitGroupRegex)?.toHashSet()?.let {\n            it.addAll(groups.splitNotBlank(AppPattern.splitGroupRegex))\n            bookSourceGroup = TextUtils.join(\",\", it)\n        }\n        if (bookSourceGroup.isNullOrBlank()) bookSourceGroup = groups\n        return this\n    }\n\n    fun removeGroup(groups: String): BookSource {\n        bookSourceGroup?.splitNotBlank(AppPattern.splitGroupRegex)?.toHashSet()?.let {\n            it.removeAll(groups.splitNotBlank(AppPattern.splitGroupRegex).toSet())\n            bookSourceGroup = TextUtils.join(\",\", it)\n        }\n        return this\n    }\n\n    fun hasGroup(group: String): Boolean {\n        bookSourceGroup?.splitNotBlank(AppPattern.splitGroupRegex)?.toHashSet()?.let {\n            return it.indexOf(group) != -1\n        }\n        return false\n    }\n\n    fun removeInvalidGroups() {\n        removeGroup(getInvalidGroupNames())\n    }\n\n    fun removeErrorComment() {\n        bookSourceComment = bookSourceComment\n            ?.split(\"\\n\\n\")\n            ?.filterNot {\n                it.startsWith(\"// Error: \")\n            }?.joinToString(\"\\n\")\n    }\n\n    fun addErrorComment(e: Throwable) {\n        bookSourceComment =\n            \"// Error: ${e.localizedMessage}\" + if (bookSourceComment.isNullOrBlank())\n                \"\" else \"\\n\\n${bookSourceComment}\"\n    }\n\n    fun getCheckKeyword(default: String): String {\n        ruleSearch?.checkKeyWord?.let {\n            if (it.isNotBlank()) {\n                return it\n            }\n        }\n        return default\n    }\n\n    fun getInvalidGroupNames(): String {\n        return bookSourceGroup?.splitNotBlank(AppPattern.splitGroupRegex)?.toHashSet()?.filter {\n            \"失效\" in it || it == \"校验超时\"\n        }?.joinToString() ?: \"\"\n    }\n\n    fun getDisplayVariableComment(otherComment: String): String {\n        return if (variableComment.isNullOrBlank()) {\n            otherComment\n        } else {\n            \"${variableComment}\\n$otherComment\"\n        }\n    }\n\n    fun equal(source: BookSource): Boolean {\n        return equal(bookSourceName, source.bookSourceName)\n                && equal(bookSourceUrl, source.bookSourceUrl)\n                && equal(bookSourceGroup, source.bookSourceGroup)\n                && bookSourceType == source.bookSourceType\n                && equal(bookUrlPattern, source.bookUrlPattern)\n                && equal(bookSourceComment, source.bookSourceComment)\n                && customOrder == source.customOrder\n                && enabled == source.enabled\n                && enabledExplore == source.enabledExplore\n                && enabledCookieJar == source.enabledCookieJar\n                && equal(variableComment, source.variableComment)\n                && equal(concurrentRate, source.concurrentRate)\n                && equal(jsLib, source.jsLib)\n                && equal(header, source.header)\n                && equal(loginUrl, source.loginUrl)\n                && equal(loginUi, source.loginUi)\n                && equal(loginCheckJs, source.loginCheckJs)\n                && equal(coverDecodeJs, source.coverDecodeJs)\n                && equal(exploreUrl, source.exploreUrl)\n                && equal(searchUrl, source.searchUrl)\n                && getSearchRule() == source.getSearchRule()\n                && getExploreRule() == source.getExploreRule()\n                && getBookInfoRule() == source.getBookInfoRule()\n                && getTocRule() == source.getTocRule()\n                && getContentRule() == source.getContentRule()\n    }\n\n    private fun equal(a: String?, b: String?) = a == b || (a.isNullOrEmpty() && b.isNullOrEmpty())\n\n    class Converters {\n\n        @TypeConverter\n        fun exploreRuleToString(exploreRule: ExploreRule?): String =\n            GSON.toJson(exploreRule)\n\n        @TypeConverter\n        fun stringToExploreRule(json: String?) =\n            GSON.fromJsonObject<ExploreRule>(json).getOrNull()\n\n        @TypeConverter\n        fun searchRuleToString(searchRule: SearchRule?): String =\n            GSON.toJson(searchRule)\n\n        @TypeConverter\n        fun stringToSearchRule(json: String?) =\n            GSON.fromJsonObject<SearchRule>(json).getOrNull()\n\n        @TypeConverter\n        fun bookInfoRuleToString(bookInfoRule: BookInfoRule?): String =\n            GSON.toJson(bookInfoRule)\n\n        @TypeConverter\n        fun stringToBookInfoRule(json: String?) =\n            GSON.fromJsonObject<BookInfoRule>(json).getOrNull()\n\n        @TypeConverter\n        fun tocRuleToString(tocRule: TocRule?): String =\n            GSON.toJson(tocRule)\n\n        @TypeConverter\n        fun stringToTocRule(json: String?) =\n            GSON.fromJsonObject<TocRule>(json).getOrNull()\n\n        @TypeConverter\n        fun contentRuleToString(contentRule: ContentRule?): String =\n            GSON.toJson(contentRule)\n\n        @TypeConverter\n        fun stringToContentRule(json: String?) =\n            GSON.fromJsonObject<ContentRule>(json).getOrNull()\n\n        @TypeConverter\n        fun stringToReviewRule(json: String?): ReviewRule? = null\n\n        @TypeConverter\n        fun reviewRuleToString(reviewRule: ReviewRule?): String = \"null\"\n\n    }\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/data/entities/BookSourcePart.kt",
    "content": "package io.legado.app.data.entities\n\nimport android.text.TextUtils\nimport androidx.room.DatabaseView\nimport io.legado.app.constant.AppPattern\nimport io.legado.app.data.appDb\nimport io.legado.app.utils.splitNotBlank\n\n\n@DatabaseView(\n    \"\"\"select bookSourceUrl, bookSourceName, bookSourceGroup, customOrder, enabled, enabledExplore, \n    (loginUrl is not null and trim(loginUrl) <> '') hasLoginUrl, lastUpdateTime, respondTime, weight, \n    (exploreUrl is not null and trim(exploreUrl) <> '') hasExploreUrl \n    from book_sources\"\"\",\n    viewName = \"book_sources_part\"\n)\ndata class BookSourcePart(\n    // 地址，包括 http/https\n    var bookSourceUrl: String = \"\",\n    // 名称\n    var bookSourceName: String = \"\",\n    // 分组\n    var bookSourceGroup: String? = null,\n    // 手动排序编号\n    var customOrder: Int = 0,\n    // 是否启用\n    var enabled: Boolean = true,\n    // 启用发现\n    var enabledExplore: Boolean = true,\n    // 是否有登录地址\n    var hasLoginUrl: Boolean = false,\n    // 最后更新时间，用于排序\n    var lastUpdateTime: Long = 0,\n    // 响应时间，用于排序\n    var respondTime: Long = 180000L,\n    // 智能排序的权重\n    var weight: Int = 0,\n    // 是否有发现url\n    var hasExploreUrl: Boolean = false\n) {\n\n    override fun hashCode(): Int {\n        return bookSourceUrl.hashCode()\n    }\n\n    override fun equals(other: Any?): Boolean {\n        return if (other is BookSourcePart) other.bookSourceUrl == bookSourceUrl else false\n    }\n\n    fun getDisPlayNameGroup(): String {\n        return if (bookSourceGroup.isNullOrBlank()) {\n            bookSourceName\n        } else {\n            String.format(\"%s (%s)\", bookSourceName, bookSourceGroup)\n        }\n    }\n\n    fun getBookSource(): BookSource? {\n        return appDb.bookSourceDao.getBookSource(bookSourceUrl)\n    }\n\n    fun addGroup(groups: String) {\n        bookSourceGroup?.splitNotBlank(AppPattern.splitGroupRegex)?.toHashSet()?.let {\n            it.addAll(groups.splitNotBlank(AppPattern.splitGroupRegex))\n            bookSourceGroup = TextUtils.join(\",\", it)\n        }\n        if (bookSourceGroup.isNullOrBlank()) bookSourceGroup = groups\n    }\n\n    fun removeGroup(groups: String) {\n        bookSourceGroup?.splitNotBlank(AppPattern.splitGroupRegex)?.toHashSet()?.let {\n            it.removeAll(groups.splitNotBlank(AppPattern.splitGroupRegex).toSet())\n            bookSourceGroup = TextUtils.join(\",\", it)\n        }\n    }\n\n}\n\nfun List<BookSourcePart>.toBookSource(): List<BookSource> {\n    return mapNotNull { it.getBookSource() }\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/data/entities/Bookmark.kt",
    "content": "package io.legado.app.data.entities\n\nimport android.os.Parcelable\nimport androidx.room.Entity\nimport androidx.room.Index\nimport androidx.room.PrimaryKey\nimport kotlinx.parcelize.Parcelize\n\n@Parcelize\n@Entity(\n    tableName = \"bookmarks\",\n    indices = [(Index(value = [\"bookName\", \"bookAuthor\"], unique = false))]\n)\ndata class Bookmark(\n    @PrimaryKey\n    val time: Long = System.currentTimeMillis(),\n    val bookName: String = \"\",\n    val bookAuthor: String = \"\",\n    var chapterIndex: Int = 0,\n    var chapterPos: Int = 0,\n    var chapterName: String = \"\",\n    var bookText: String = \"\",\n    var content: String = \"\"\n) : Parcelable"
  },
  {
    "path": "app/src/main/java/io/legado/app/data/entities/Cache.kt",
    "content": "package io.legado.app.data.entities\n\nimport androidx.room.Entity\nimport androidx.room.Index\nimport androidx.room.PrimaryKey\n\n@Entity(tableName = \"caches\", indices = [(Index(value = [\"key\"], unique = true))])\ndata class Cache(\n    @PrimaryKey\n    val key: String = \"\",\n    var value: String? = null,\n    var deadline: Long = 0L\n)"
  },
  {
    "path": "app/src/main/java/io/legado/app/data/entities/Cookie.kt",
    "content": "package io.legado.app.data.entities\n\nimport androidx.room.Entity\nimport androidx.room.Index\nimport androidx.room.PrimaryKey\n\n@Entity(tableName = \"cookies\", indices = [(Index(value = [\"url\"], unique = true))])\ndata class Cookie(\n    @PrimaryKey\n    var url: String = \"\",\n    var cookie: String = \"\"\n)"
  },
  {
    "path": "app/src/main/java/io/legado/app/data/entities/DictRule.kt",
    "content": "package io.legado.app.data.entities\n\nimport androidx.room.ColumnInfo\nimport androidx.room.Entity\nimport androidx.room.PrimaryKey\nimport io.legado.app.model.analyzeRule.AnalyzeRule\nimport io.legado.app.model.analyzeRule.AnalyzeRule.Companion.setCoroutineContext\nimport io.legado.app.model.analyzeRule.AnalyzeUrl\nimport kotlin.coroutines.coroutineContext\n\n/**\n * 字典规则\n */\n@Entity(tableName = \"dictRules\")\ndata class DictRule(\n    @PrimaryKey\n    var name: String = \"\",\n    var urlRule: String = \"\",\n    var showRule: String = \"\",\n    @ColumnInfo(defaultValue = \"1\")\n    var enabled: Boolean = true,\n    @ColumnInfo(defaultValue = \"0\")\n    var sortNumber: Int = 0\n) {\n\n    override fun hashCode(): Int {\n        return name.hashCode()\n    }\n\n    override fun equals(other: Any?): Boolean {\n        if (other is DictRule) {\n            return name == other.name\n        }\n        return false\n    }\n\n    /**\n     * 搜索字典\n     */\n    suspend fun search(word: String): String {\n        val analyzeUrl = AnalyzeUrl(urlRule, key = word, coroutineContext = coroutineContext)\n        val body = analyzeUrl.getStrResponseAwait().body\n        if (showRule.isBlank()) {\n            return body!!\n        }\n        val analyzeRule = AnalyzeRule().setCoroutineContext(coroutineContext)\n        return analyzeRule.getString(showRule, mContent = body)\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/data/entities/HttpTTS.kt",
    "content": "package io.legado.app.data.entities\n\nimport androidx.room.ColumnInfo\nimport androidx.room.Entity\nimport androidx.room.PrimaryKey\nimport com.jayway.jsonpath.DocumentContext\nimport io.legado.app.utils.GSON\nimport io.legado.app.utils.jsonPath\nimport io.legado.app.utils.readLong\nimport io.legado.app.utils.readString\n\n/**\n * 在线朗读引擎\n */\n@Entity(tableName = \"httpTTS\")\ndata class HttpTTS(\n    @PrimaryKey\n    val id: Long = System.currentTimeMillis(),\n    var name: String = \"\",\n    var url: String = \"\",\n    var contentType: String? = null,\n    @ColumnInfo(defaultValue = \"0\")\n    override var concurrentRate: String? = \"0\",\n    override var loginUrl: String? = null,\n    override var loginUi: String? = null,\n    override var header: String? = null,\n    override var jsLib: String? = null,\n    @ColumnInfo(defaultValue = \"0\")\n    override var enabledCookieJar: Boolean? = false,\n    var loginCheckJs: String? = null,\n    @ColumnInfo(defaultValue = \"0\")\n    var lastUpdateTime: Long = System.currentTimeMillis()\n) : BaseSource {\n\n    override fun getTag(): String {\n        return name\n    }\n\n    override fun getKey(): String {\n        return \"httpTts:$id\"\n    }\n\n    @Suppress(\"MemberVisibilityCanBePrivate\")\n    companion object {\n\n        fun fromJsonDoc(doc: DocumentContext): Result<HttpTTS> {\n            return kotlin.runCatching {\n                val loginUi = doc.read<Any>(\"$.loginUi\")\n                HttpTTS(\n                    id = doc.readLong(\"$.id\") ?: System.currentTimeMillis(),\n                    name = doc.readString(\"$.name\")!!,\n                    url = doc.readString(\"$.url\")!!,\n                    contentType = doc.readString(\"$.contentType\"),\n                    concurrentRate = doc.readString(\"$.concurrentRate\"),\n                    loginUrl = doc.readString(\"$.loginUrl\"),\n                    loginUi = if (loginUi is List<*>) GSON.toJson(loginUi) else loginUi?.toString(),\n                    header = doc.readString(\"$.header\"),\n                    loginCheckJs = doc.readString(\"$.loginCheckJs\"),\n                    lastUpdateTime = doc.readLong(\"$.lastUpdateTime\") ?: System.currentTimeMillis()\n                )\n            }\n        }\n\n        fun fromJson(json: String): Result<HttpTTS> {\n            return fromJsonDoc(jsonPath.parse(json))\n        }\n\n        fun fromJsonArray(jsonArray: String): Result<ArrayList<HttpTTS>> {\n            return kotlin.runCatching {\n                val sources = arrayListOf<HttpTTS>()\n                val doc = jsonPath.parse(jsonArray).read<List<*>>(\"$\")\n                doc.forEach {\n                    val jsonItem = jsonPath.parse(it)\n                    fromJsonDoc(jsonItem).getOrThrow().let { source ->\n                        sources.add(source)\n                    }\n                }\n                return@runCatching sources\n            }\n        }\n\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/data/entities/KeyboardAssist.kt",
    "content": "package io.legado.app.data.entities\n\nimport android.os.Parcelable\nimport androidx.room.ColumnInfo\nimport androidx.room.Entity\nimport kotlinx.parcelize.Parcelize\n\n\n@Parcelize\n@Entity(tableName = \"keyboardAssists\", primaryKeys = [\"type\", \"key\"])\ndata class KeyboardAssist(\n    @ColumnInfo(defaultValue = \"0\")\n    var type: Int = 0,\n    @ColumnInfo(defaultValue = \"\")\n    var key: String,\n    @ColumnInfo(defaultValue = \"\")\n    var value: String,\n    @ColumnInfo(defaultValue = \"0\")\n    var serialNo: Int = 0\n) : Parcelable"
  },
  {
    "path": "app/src/main/java/io/legado/app/data/entities/ReadRecord.kt",
    "content": "package io.legado.app.data.entities\n\nimport androidx.room.ColumnInfo\nimport androidx.room.Entity\n\n@Entity(tableName = \"readRecord\", primaryKeys = [\"deviceId\", \"bookName\"])\ndata class ReadRecord(\n    var deviceId: String = \"\",\n    var bookName: String = \"\",\n    @ColumnInfo(defaultValue = \"0\")\n    var readTime: Long = 0L,\n    @ColumnInfo(defaultValue = \"0\")\n    var lastRead: Long = System.currentTimeMillis()\n)"
  },
  {
    "path": "app/src/main/java/io/legado/app/data/entities/ReadRecordShow.kt",
    "content": "package io.legado.app.data.entities\n\ndata class ReadRecordShow(\n    var bookName: String,\n    var readTime: Long,\n    var lastRead: Long\n)"
  },
  {
    "path": "app/src/main/java/io/legado/app/data/entities/ReplaceRule.kt",
    "content": "package io.legado.app.data.entities\n\nimport android.os.Parcelable\nimport android.text.TextUtils\nimport androidx.room.ColumnInfo\nimport androidx.room.Entity\nimport androidx.room.Ignore\nimport androidx.room.Index\nimport androidx.room.PrimaryKey\nimport io.legado.app.R\nimport io.legado.app.constant.AppLog\nimport io.legado.app.exception.NoStackTraceException\nimport kotlinx.parcelize.IgnoredOnParcel\nimport kotlinx.parcelize.Parcelize\nimport splitties.init.appCtx\nimport java.util.regex.Pattern\nimport java.util.regex.PatternSyntaxException\n\n@Parcelize\n@Entity(\n    tableName = \"replace_rules\",\n    indices = [(Index(value = [\"id\"]))]\n)\ndata class ReplaceRule(\n    @PrimaryKey(autoGenerate = true)\n    var id: Long = System.currentTimeMillis(),\n    //名称\n    @ColumnInfo(defaultValue = \"\")\n    var name: String = \"\",\n    //分组\n    var group: String? = null,\n    //替换内容\n    @ColumnInfo(defaultValue = \"\")\n    var pattern: String = \"\",\n    //替换为\n    @ColumnInfo(defaultValue = \"\")\n    var replacement: String = \"\",\n    //作用范围\n    var scope: String? = null,\n    //作用于标题\n    @ColumnInfo(defaultValue = \"0\")\n    var scopeTitle: Boolean = false,\n    //作用于正文\n    @ColumnInfo(defaultValue = \"1\")\n    var scopeContent: Boolean = true,\n    //排除范围\n    var excludeScope: String? = null,\n    //是否启用\n    @ColumnInfo(defaultValue = \"1\")\n    var isEnabled: Boolean = true,\n    //是否正则\n    @ColumnInfo(defaultValue = \"1\")\n    var isRegex: Boolean = true,\n    //超时时间\n    @ColumnInfo(defaultValue = \"3000\")\n    var timeoutMillisecond: Long = 3000L,\n    //排序\n    @ColumnInfo(name = \"sortOrder\", defaultValue = \"0\")\n    var order: Int = Int.MIN_VALUE\n) : Parcelable {\n\n    override fun equals(other: Any?): Boolean {\n        if (other is ReplaceRule) {\n            return other.id == id\n        }\n        return super.equals(other)\n    }\n\n    override fun hashCode(): Int {\n        return id.hashCode()\n    }\n\n    @delegate:Transient\n    @delegate:Ignore\n    @IgnoredOnParcel\n    val regex: Regex by lazy {\n        pattern.toRegex()\n    }\n\n    fun getDisplayNameGroup(): String {\n        return if (group.isNullOrBlank()) {\n            name\n        } else {\n            String.format(\"%s (%s)\", name, group)\n        }\n    }\n\n    fun isValid(): Boolean {\n        if (TextUtils.isEmpty(pattern)) {\n            return false\n        }\n        //判断正则表达式是否正确\n        if (isRegex) {\n            try {\n                Pattern.compile(pattern)\n            } catch (ex: PatternSyntaxException) {\n                AppLog.put(\"正则语法错误或不支持：${ex.localizedMessage}\", ex)\n                return false\n            }\n            // Pattern.compile测试通过，但是部分情况下会替换超时，报错，一般发生在修改表达式时漏删了\n            if (pattern.endsWith('|') && !pattern.endsWith(\"\\\\|\")) {\n                return false\n            }\n        }\n        return true\n    }\n\n    @Throws(NoStackTraceException::class)\n    fun checkValid() {\n        if (!isValid()) {\n            throw NoStackTraceException(appCtx.getString(R.string.replace_rule_invalid))\n        }\n    }\n\n    fun getValidTimeoutMillisecond(): Long {\n        if (timeoutMillisecond <= 0) {\n            return 3000L\n        }\n        return timeoutMillisecond\n    }\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/data/entities/RssArticle.kt",
    "content": "package io.legado.app.data.entities\n\nimport androidx.room.ColumnInfo\nimport androidx.room.Entity\nimport androidx.room.Ignore\nimport io.legado.app.utils.GSON\nimport io.legado.app.utils.fromJsonObject\nimport kotlinx.parcelize.IgnoredOnParcel\n\n@Entity(\n    tableName = \"rssArticles\",\n    primaryKeys = [\"origin\", \"link\"]\n)\ndata class RssArticle(\n    override var origin: String = \"\",\n    var sort: String = \"\",\n    var title: String = \"\",\n    var order: Long = 0,\n    override var link: String = \"\",\n    var pubDate: String? = null,\n    var description: String? = null,\n    var content: String? = null,\n    var image: String? = null,\n    @ColumnInfo(defaultValue = \"默认分组\")\n    var group: String = \"默认分组\",\n    var read: Boolean = false,\n    override var variable: String? = null\n) : BaseRssArticle {\n\n    override fun hashCode() = link.hashCode()\n\n    override fun equals(other: Any?): Boolean {\n        other ?: return false\n        return if (other is RssArticle) origin == other.origin && link == other.link else false\n    }\n\n    @delegate:Transient\n    @delegate:Ignore\n    @IgnoredOnParcel\n    override val variableMap: HashMap<String, String> by lazy {\n        GSON.fromJsonObject<HashMap<String, String>>(variable).getOrNull() ?: hashMapOf()\n    }\n\n    fun toStar() = RssStar(\n        origin = origin,\n        sort = sort,\n        title = title,\n        starTime = System.currentTimeMillis(),\n        link = link,\n        pubDate = pubDate,\n        description = description,\n        content = content,\n        image = image,\n        group = group,\n        variable = variable\n    )\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/data/entities/RssReadRecord.kt",
    "content": "package io.legado.app.data.entities\n\nimport androidx.room.Entity\nimport androidx.room.PrimaryKey\n\n@Entity(tableName = \"rssReadRecords\")\ndata class RssReadRecord(\n    @PrimaryKey\n    val record: String,\n    val title: String? = null,\n    val readTime: Long? = null,\n    val read: Boolean = true\n)"
  },
  {
    "path": "app/src/main/java/io/legado/app/data/entities/RssSource.kt",
    "content": "package io.legado.app.data.entities\n\nimport android.os.Parcelable\nimport android.text.TextUtils\nimport androidx.room.ColumnInfo\nimport androidx.room.Entity\nimport androidx.room.Index\nimport androidx.room.PrimaryKey\nimport io.legado.app.constant.AppPattern\nimport io.legado.app.utils.splitNotBlank\nimport kotlinx.parcelize.Parcelize\n\n@Parcelize\n@Entity(tableName = \"rssSources\", indices = [(Index(value = [\"sourceUrl\"], unique = false))])\ndata class RssSource(\n    @PrimaryKey\n    var sourceUrl: String = \"\",\n    // 名称\n    var sourceName: String = \"\",\n    // 图标\n    var sourceIcon: String = \"\",\n    // 分组\n    var sourceGroup: String? = null,\n    // 注释\n    var sourceComment: String? = null,\n    // 是否启用\n    var enabled: Boolean = true,\n    // 自定义变量说明\n    var variableComment: String? = null,\n    // js库\n    override var jsLib: String? = null,\n    // 启用okhttp CookieJAr 自动保存每次请求的cookie\n    @ColumnInfo(defaultValue = \"0\")\n    override var enabledCookieJar: Boolean? = true,\n    /**并发率**/\n    override var concurrentRate: String? = null,\n    /**请求头**/\n    override var header: String? = null,\n    /**登录地址**/\n    override var loginUrl: String? = null,\n    /**登录Ui**/\n    override var loginUi: String? = null,\n    /**登录检测js**/\n    var loginCheckJs: String? = null,\n    /**封面解密js**/\n    var coverDecodeJs: String? = null,\n    /**分类Url**/\n    var sortUrl: String? = null,\n    /**是否单url源**/\n    var singleUrl: Boolean = false,\n    /*列表规则*/\n    /**列表样式,0,1,2**/\n    @ColumnInfo(defaultValue = \"0\")\n    var articleStyle: Int = 0,\n    /**列表规则**/\n    var ruleArticles: String? = null,\n    /**下一页规则**/\n    var ruleNextPage: String? = null,\n    /**标题规则**/\n    var ruleTitle: String? = null,\n    /**发布日期规则**/\n    var rulePubDate: String? = null,\n    /*webView规则*/\n    /**描述规则**/\n    var ruleDescription: String? = null,\n    /**图片规则**/\n    var ruleImage: String? = null,\n    /**链接规则**/\n    var ruleLink: String? = null,\n    /**正文规则**/\n    var ruleContent: String? = null,\n    /**正文url白名单**/\n    var contentWhitelist: String? = null,\n    /**正文url黑名单**/\n    var contentBlacklist: String? = null,\n    /**\n     * 跳转url拦截,\n     * js, 返回true拦截,js变量url,可以通过js打开url,比如调用阅读搜索,添加书架等,简化规则写法,不用webView js注入\n     * **/\n    var shouldOverrideUrlLoading: String? = null,\n    /**webView样式**/\n    var style: String? = null,\n    @ColumnInfo(defaultValue = \"1\")\n    var enableJs: Boolean = true,\n    @ColumnInfo(defaultValue = \"1\")\n    var loadWithBaseUrl: Boolean = true,\n    /**注入js**/\n    var injectJs: String? = null,\n    /*其它规则*/\n    /**最后更新时间，用于排序**/\n    @ColumnInfo(defaultValue = \"0\")\n    var lastUpdateTime: Long = 0,\n    @ColumnInfo(defaultValue = \"0\")\n    var customOrder: Int = 0\n) : Parcelable, BaseSource {\n\n    override fun getTag(): String {\n        return sourceName\n    }\n\n    override fun getKey(): String {\n        return sourceUrl\n    }\n\n    override fun equals(other: Any?): Boolean {\n        if (other is RssSource) {\n            return other.sourceUrl == sourceUrl\n        }\n        return false\n    }\n\n    override fun hashCode() = sourceUrl.hashCode()\n\n    fun equal(source: RssSource): Boolean {\n        return equal(sourceUrl, source.sourceUrl)\n                && equal(sourceName, source.sourceName)\n                && equal(sourceIcon, source.sourceIcon)\n                && enabled == source.enabled\n                && equal(sourceGroup, source.sourceGroup)\n                && enabledCookieJar == source.enabledCookieJar\n                && equal(sourceComment, source.sourceComment)\n                && equal(concurrentRate, source.concurrentRate)\n                && equal(header, source.header)\n                && equal(loginUrl, source.loginUrl)\n                && equal(loginUi, source.loginUi)\n                && equal(loginCheckJs, source.loginCheckJs)\n                && equal(coverDecodeJs, source.coverDecodeJs)\n                && equal(sortUrl, source.sortUrl)\n                && singleUrl == source.singleUrl\n                && articleStyle == source.articleStyle\n                && equal(ruleArticles, source.ruleArticles)\n                && equal(ruleNextPage, source.ruleNextPage)\n                && equal(ruleTitle, source.ruleTitle)\n                && equal(rulePubDate, source.rulePubDate)\n                && equal(ruleDescription, source.ruleDescription)\n                && equal(ruleLink, source.ruleLink)\n                && equal(ruleContent, source.ruleContent)\n                && enableJs == source.enableJs\n                && loadWithBaseUrl == source.loadWithBaseUrl\n                && equal(variableComment, source.variableComment)\n                && equal(style, source.style)\n                && equal(injectJs, source.injectJs)\n    }\n\n    private fun equal(a: String?, b: String?): Boolean {\n        return a == b || (a.isNullOrEmpty() && b.isNullOrEmpty())\n    }\n\n    fun getDisplayNameGroup(): String {\n        return if (sourceGroup.isNullOrBlank()) {\n            sourceName\n        } else {\n            String.format(\"%s (%s)\", sourceName, sourceGroup)\n        }\n    }\n\n    fun addGroup(groups: String): RssSource {\n        sourceGroup?.splitNotBlank(AppPattern.splitGroupRegex)?.toHashSet()?.let {\n            it.addAll(groups.splitNotBlank(AppPattern.splitGroupRegex))\n            sourceGroup = TextUtils.join(\",\", it)\n        }\n        if (sourceGroup.isNullOrBlank()) sourceGroup = groups\n        return this\n    }\n\n    fun removeGroup(groups: String): RssSource {\n        sourceGroup?.splitNotBlank(AppPattern.splitGroupRegex)?.toHashSet()?.let {\n            it.removeAll(groups.splitNotBlank(AppPattern.splitGroupRegex).toSet())\n            sourceGroup = TextUtils.join(\",\", it)\n        }\n        return this\n    }\n\n    fun getDisplayVariableComment(otherComment: String): String {\n        return if (variableComment.isNullOrBlank()) {\n            otherComment\n        } else {\n            \"${variableComment}\\n$otherComment\"\n        }\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/data/entities/RssStar.kt",
    "content": "package io.legado.app.data.entities\n\nimport androidx.room.ColumnInfo\nimport androidx.room.Entity\nimport androidx.room.Ignore\nimport io.legado.app.utils.GSON\nimport io.legado.app.utils.fromJsonObject\nimport kotlinx.parcelize.IgnoredOnParcel\n\n\n@Entity(\n    tableName = \"rssStars\",\n    primaryKeys = [\"origin\", \"link\"]\n)\ndata class RssStar(\n    override var origin: String = \"\",\n    var sort: String = \"\",\n    var title: String = \"\",\n    var starTime: Long = 0,\n    override var link: String = \"\",\n    var pubDate: String? = null,\n    var description: String? = null,\n    var content: String? = null,\n    var image: String? = null,\n    @ColumnInfo(defaultValue = \"默认分组\")\n    var group: String = \"默认分组\",\n    override var variable: String? = null\n) : BaseRssArticle {\n\n    @delegate:Transient\n    @delegate:Ignore\n    @IgnoredOnParcel\n    override val variableMap by lazy {\n        GSON.fromJsonObject<HashMap<String, String>>(variable).getOrNull() ?: hashMapOf()\n    }\n\n    fun toRssArticle() = RssArticle(\n        origin = origin,\n        sort = sort,\n        title = title,\n        link = link,\n        pubDate = pubDate,\n        description = description,\n        content = content,\n        image = image,\n        group = group,\n        variable = variable\n    )\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/data/entities/RuleSub.kt",
    "content": "package io.legado.app.data.entities\n\nimport androidx.room.Entity\nimport androidx.room.PrimaryKey\n\n@Entity(tableName = \"ruleSubs\")\ndata class RuleSub(\n    @PrimaryKey\n    val id: Long = System.currentTimeMillis(),\n    var name: String = \"\",\n    var url: String = \"\",\n    var type: Int = 0,\n    var customOrder: Int = 0,\n    var autoUpdate: Boolean = false,\n    var update: Long = System.currentTimeMillis()\n)\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/data/entities/SearchBook.kt",
    "content": "package io.legado.app.data.entities\n\nimport android.content.Context\nimport android.os.Parcelable\nimport androidx.room.ColumnInfo\nimport androidx.room.Entity\nimport androidx.room.ForeignKey\nimport androidx.room.Ignore\nimport androidx.room.Index\nimport androidx.room.PrimaryKey\nimport io.legado.app.R\nimport io.legado.app.constant.BookType\nimport io.legado.app.utils.GSON\nimport io.legado.app.utils.fromJsonObject\nimport kotlinx.parcelize.IgnoredOnParcel\nimport kotlinx.parcelize.Parcelize\n\n@Parcelize\n@Entity(\n    tableName = \"searchBooks\",\n    indices = [(Index(value = [\"bookUrl\"], unique = true)),\n        (Index(value = [\"origin\"], unique = false))],\n    foreignKeys = [(ForeignKey(\n        entity = BookSource::class,\n        parentColumns = [\"bookSourceUrl\"],\n        childColumns = [\"origin\"],\n        onDelete = ForeignKey.CASCADE\n    ))]\n)\ndata class SearchBook(\n    @PrimaryKey\n    override var bookUrl: String = \"\",\n    /** 书源 */\n    var origin: String = \"\",\n    var originName: String = \"\",\n    /** BookType */\n    var type: Int = BookType.text,\n    override var name: String = \"\",\n    override var author: String = \"\",\n    override var kind: String? = null,\n    var coverUrl: String? = null,\n    var intro: String? = null,\n    override var wordCount: String? = null,\n    var latestChapterTitle: String? = null,\n    /** 目录页Url (toc=table of Contents) */\n    var tocUrl: String = \"\",\n    var time: Long = System.currentTimeMillis(),\n    override var variable: String? = null,\n    var originOrder: Int = 0,\n    var chapterWordCountText: String? = null,\n    @ColumnInfo(defaultValue = \"-1\")\n    var chapterWordCount: Int = -1,\n    @ColumnInfo(defaultValue = \"-1\")\n    var respondTime: Int = -1\n) : Parcelable, BaseBook, Comparable<SearchBook> {\n\n    @Ignore\n    @IgnoredOnParcel\n    override var infoHtml: String? = null\n\n    @Ignore\n    @IgnoredOnParcel\n    override var tocHtml: String? = null\n\n    override fun equals(other: Any?) = other is SearchBook && other.bookUrl == bookUrl\n\n    override fun hashCode() = bookUrl.hashCode()\n\n    override fun compareTo(other: SearchBook): Int {\n        return other.originOrder - this.originOrder\n    }\n\n    @delegate:Transient\n    @delegate:Ignore\n    @IgnoredOnParcel\n    override val variableMap: HashMap<String, String> by lazy {\n        GSON.fromJsonObject<HashMap<String, String>>(variable).getOrNull() ?: HashMap()\n    }\n\n    @delegate:Transient\n    @delegate:Ignore\n    @IgnoredOnParcel\n    val origins: LinkedHashSet<String> by lazy { linkedSetOf(origin) }\n\n    fun addOrigin(origin: String) {\n        origins.add(origin)\n    }\n\n    fun getDisplayLastChapterTitle(): String {\n        latestChapterTitle?.let {\n            if (it.isNotEmpty()) {\n                return it\n            }\n        }\n        return \"无最新章节\"\n    }\n\n    fun trimIntro(context: Context): String {\n        val trimIntro = intro?.trim()\n        return if (trimIntro.isNullOrEmpty()) {\n            context.getString(R.string.intro_show_null)\n        } else {\n            context.getString(R.string.intro_show, trimIntro)\n        }\n    }\n\n    fun releaseHtmlData() {\n        infoHtml = null\n        tocHtml = null\n    }\n\n    fun primaryStr(): String {\n        return origin + bookUrl\n    }\n\n    fun sameBookTypeLocal(bookType: Int): Boolean {\n        return type and BookType.allBookTypeLocal == bookType and BookType.allBookTypeLocal\n    }\n\n    fun toBook() = Book(\n        name = name,\n        author = author,\n        kind = kind,\n        bookUrl = bookUrl,\n        origin = origin,\n        originName = originName,\n        type = type,\n        wordCount = wordCount,\n        latestChapterTitle = latestChapterTitle,\n        coverUrl = coverUrl,\n        intro = intro,\n        tocUrl = tocUrl,\n        originOrder = originOrder,\n        variable = variable\n    ).apply {\n        this.infoHtml = this@SearchBook.infoHtml\n        this.tocHtml = this@SearchBook.tocHtml\n    }\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/data/entities/SearchKeyword.kt",
    "content": "package io.legado.app.data.entities\n\nimport android.os.Parcelable\nimport androidx.room.Entity\nimport androidx.room.Index\nimport androidx.room.PrimaryKey\nimport kotlinx.parcelize.Parcelize\n\n\n@Parcelize\n@Entity(tableName = \"search_keywords\", indices = [(Index(value = [\"word\"], unique = true))])\ndata class SearchKeyword(\n    /** 搜索关键词 */\n    @PrimaryKey\n    var word: String = \"\",\n    /** 使用次数 */\n    var usage: Int = 1,\n    /** 最后一次使用时间 */\n    var lastUseTime: Long = System.currentTimeMillis()\n) : Parcelable\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/data/entities/Server.kt",
    "content": "package io.legado.app.data.entities\n\nimport android.os.Parcelable\nimport androidx.room.Entity\nimport androidx.room.PrimaryKey\nimport io.legado.app.utils.GSON\nimport io.legado.app.utils.fromJsonObject\nimport kotlinx.parcelize.Parcelize\nimport org.json.JSONObject\n\n/**\n * 服务器\n */\n@Parcelize\n@Entity(tableName = \"servers\")\ndata class Server(\n    @PrimaryKey\n    var id: Long = System.currentTimeMillis(),\n    var name: String = \"\",\n    var type: TYPE = TYPE.WEBDAV,\n    var config: String? = null,\n    var sortNumber: Int = 0\n) : Parcelable {\n\n    enum class TYPE {\n        WEBDAV\n    }\n\n    override fun hashCode(): Int {\n        return id.hashCode()\n    }\n\n    override fun equals(other: Any?): Boolean {\n        if (other is Server) {\n            return id == other.id\n        }\n        return false\n    }\n\n    fun getConfigJsonObject(): JSONObject? {\n        val json = config\n        json ?: return null\n        return JSONObject(json)\n    }\n\n    fun getWebDavConfig(): WebDavConfig? {\n        return if (type == TYPE.WEBDAV) GSON.fromJsonObject<WebDavConfig>(config).getOrNull() else null\n    }\n\n    @Parcelize\n    data class WebDavConfig(\n        var url: String,\n        var username: String,\n        var password: String\n    ) : Parcelable\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/data/entities/TxtTocRule.kt",
    "content": "package io.legado.app.data.entities\n\nimport androidx.room.Entity\nimport androidx.room.PrimaryKey\n\n\n@Entity(tableName = \"txtTocRules\")\ndata class TxtTocRule(\n    @PrimaryKey\n    var id: Long = System.currentTimeMillis(),\n    var name: String = \"\",\n    var rule: String = \"\",\n    var example: String? = null,\n    var serialNumber: Int = -1,\n    var enable: Boolean = true\n) {\n\n    override fun hashCode(): Int {\n        return id.hashCode()\n    }\n\n    override fun equals(other: Any?): Boolean {\n        if (other is TxtTocRule) {\n            return id == other.id\n        }\n        return false\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/data/entities/rule/BookInfoRule.kt",
    "content": "package io.legado.app.data.entities.rule\n\nimport android.os.Parcelable\nimport com.google.gson.JsonDeserializer\nimport io.legado.app.utils.INITIAL_GSON\nimport kotlinx.parcelize.Parcelize\n\n/**\n * 书籍详情页规则\n */\n@Parcelize\ndata class BookInfoRule(\n    var init: String? = null,\n    var name: String? = null,\n    var author: String? = null,\n    var intro: String? = null,\n    var kind: String? = null,\n    var lastChapter: String? = null,\n    var updateTime: String? = null,\n    var coverUrl: String? = null,\n    var tocUrl: String? = null,\n    var wordCount: String? = null,\n    var canReName: String? = null,\n    var downloadUrls: String? = null\n) : Parcelable {\n\n    companion object {\n\n        val jsonDeserializer = JsonDeserializer<BookInfoRule?> { json, _, _ ->\n            when {\n                json.isJsonObject -> INITIAL_GSON.fromJson(json, BookInfoRule::class.java)\n                json.isJsonPrimitive -> INITIAL_GSON.fromJson(\n                    json.asString,\n                    BookInfoRule::class.java\n                )\n                else -> null\n            }\n        }\n\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/data/entities/rule/BookListRule.kt",
    "content": "package io.legado.app.data.entities.rule\n\n/**\n * 书籍列表规则\n */\ninterface BookListRule {\n    var bookList: String?\n    var name: String?\n    var author: String?\n    var intro: String?\n    var kind: String?\n    var lastChapter: String?\n    var updateTime: String?\n    var bookUrl: String?\n    var coverUrl: String?\n    var wordCount: String?\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/data/entities/rule/ContentRule.kt",
    "content": "package io.legado.app.data.entities.rule\n\nimport android.os.Parcelable\nimport com.google.gson.JsonDeserializer\nimport io.legado.app.utils.INITIAL_GSON\nimport kotlinx.parcelize.Parcelize\n\n/**\n * 正文处理规则\n */\n@Parcelize\ndata class ContentRule(\n    var content: String? = null,\n    var title: String? = null, //有些网站只能在正文中获取标题\n    var nextContentUrl: String? = null,\n    var webJs: String? = null,\n    var sourceRegex: String? = null,\n    var replaceRegex: String? = null, //替换规则\n    var imageStyle: String? = null,   //默认大小居中,FULL最大宽度\n    var imageDecode: String? = null, //图片bytes二次解密js, 返回解密后的bytes\n    var payAction: String? = null,    //购买操作,js或者包含{{js}}的url\n) : Parcelable {\n\n\n    companion object {\n\n        val jsonDeserializer = JsonDeserializer<ContentRule?> { json, _, _ ->\n            when {\n                json.isJsonObject -> INITIAL_GSON.fromJson(json, ContentRule::class.java)\n                json.isJsonPrimitive -> INITIAL_GSON.fromJson(\n                    json.asString,\n                    ContentRule::class.java\n                )\n                else -> null\n            }\n        }\n\n    }\n\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/data/entities/rule/ExploreKind.kt",
    "content": "package io.legado.app.data.entities.rule\n\n/**\n * 发现分类\n */\ndata class ExploreKind(\n    val title: String = \"\",\n    val url: String? = null,\n    val style: FlexChildStyle? = null\n) {\n\n    fun style(): FlexChildStyle {\n        return style ?: FlexChildStyle.defaultStyle\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/data/entities/rule/ExploreRule.kt",
    "content": "package io.legado.app.data.entities.rule\n\nimport android.os.Parcelable\nimport com.google.gson.JsonDeserializer\nimport io.legado.app.utils.INITIAL_GSON\nimport kotlinx.parcelize.Parcelize\n\n/**\n * 发现结果规则\n */\n@Parcelize\ndata class ExploreRule(\n    override var bookList: String? = null,\n    override var name: String? = null,\n    override var author: String? = null,\n    override var intro: String? = null,\n    override var kind: String? = null,\n    override var lastChapter: String? = null,\n    override var updateTime: String? = null,\n    override var bookUrl: String? = null,\n    override var coverUrl: String? = null,\n    override var wordCount: String? = null\n) : BookListRule, Parcelable {\n\n    companion object {\n\n        val jsonDeserializer = JsonDeserializer<ExploreRule?> { json, _, _ ->\n            when {\n                json.isJsonObject -> INITIAL_GSON.fromJson(json, ExploreRule::class.java)\n                json.isJsonPrimitive -> INITIAL_GSON.fromJson(\n                    json.asString,\n                    ExploreRule::class.java\n                )\n                else -> null\n            }\n        }\n\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/data/entities/rule/FlexChildStyle.kt",
    "content": "package io.legado.app.data.entities.rule\n\nimport android.view.View\nimport com.google.android.flexbox.FlexboxLayout\n\ndata class FlexChildStyle(\n    val layout_flexGrow: Float = 0F,\n    val layout_flexShrink: Float = 1F,\n    val layout_alignSelf: String = \"auto\",\n    val layout_flexBasisPercent: Float = -1F,\n    val layout_wrapBefore: Boolean = false,\n) {\n\n    fun alignSelf(): Int {\n        return when (layout_alignSelf) {\n            \"auto\" -> -1\n            \"flex_start\" -> 0\n            \"flex_end\" -> 1\n            \"center\" -> 2\n            \"baseline\" -> 3\n            \"stretch\" -> 4\n            else -> -1\n        }\n    }\n\n    fun apply(view: View) {\n        val lp = view.layoutParams as FlexboxLayout.LayoutParams\n        lp.flexGrow = layout_flexGrow\n        lp.flexShrink = layout_flexShrink\n        lp.alignSelf = alignSelf()\n        lp.flexBasisPercent = layout_flexBasisPercent\n        lp.isWrapBefore = layout_wrapBefore\n    }\n\n    companion object {\n        val defaultStyle = FlexChildStyle()\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/data/entities/rule/ReviewRule.kt",
    "content": "package io.legado.app.data.entities.rule\r\n\r\nimport android.os.Parcelable\r\nimport com.google.gson.JsonDeserializer\r\nimport io.legado.app.utils.INITIAL_GSON\r\nimport kotlinx.parcelize.Parcelize\r\n\r\n@Parcelize\r\ndata class ReviewRule(\r\n    var reviewUrl: String? = null,          // 段评URL\r\n    var avatarRule: String? = null,         // 段评发布者头像\r\n    var contentRule: String? = null,        // 段评内容\r\n    var postTimeRule: String? = null,       // 段评发布时间\r\n    var reviewQuoteUrl: String? = null,     // 获取段评回复URL\r\n\r\n    // 这些功能将在以上功能完成以后实现\r\n    var voteUpUrl: String? = null,          // 点赞URL\r\n    var voteDownUrl: String? = null,        // 点踩URL\r\n    var postReviewUrl: String? = null,      // 发送回复URL\r\n    var postQuoteUrl: String? = null,       // 发送回复段评URL\r\n    var deleteUrl: String? = null,          // 删除段评URL\r\n) : Parcelable {\r\n\r\n    companion object {\r\n\r\n        val jsonDeserializer = JsonDeserializer<ReviewRule?> { json, _, _ ->\r\n            when {\r\n                json.isJsonObject -> INITIAL_GSON.fromJson(json, ReviewRule::class.java)\r\n                json.isJsonPrimitive -> INITIAL_GSON.fromJson(json.asString, ReviewRule::class.java)\r\n                else -> null\r\n            }\r\n        }\r\n\r\n    }\r\n\r\n}\r\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/data/entities/rule/RowUi.kt",
    "content": "package io.legado.app.data.entities.rule\n\ndata class RowUi(\n    var name: String = \"\",\n    var type: String = \"text\",\n    var action: String? = null,\n    var style: FlexChildStyle? = null\n) {\n\n    @Suppress(\"ConstPropertyName\")\n    object Type {\n\n        const val text = \"text\"\n        const val password = \"password\"\n        const val button = \"button\"\n\n    }\n\n    fun style(): FlexChildStyle {\n        return style ?: FlexChildStyle.defaultStyle\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/data/entities/rule/SearchRule.kt",
    "content": "package io.legado.app.data.entities.rule\n\nimport android.os.Parcelable\nimport com.google.gson.JsonDeserializer\nimport io.legado.app.utils.INITIAL_GSON\nimport kotlinx.parcelize.Parcelize\n\n/**\n * 搜索结果处理规则\n */\n@Parcelize\ndata class SearchRule(\n    /**校验关键字**/\n    var checkKeyWord: String? = null,\n    override var bookList: String? = null,\n    override var name: String? = null,\n    override var author: String? = null,\n    override var intro: String? = null,\n    override var kind: String? = null,\n    override var lastChapter: String? = null,\n    override var updateTime: String? = null,\n    override var bookUrl: String? = null,\n    override var coverUrl: String? = null,\n    override var wordCount: String? = null\n) : BookListRule, Parcelable {\n\n    companion object {\n\n        val jsonDeserializer = JsonDeserializer<SearchRule?> { json, _, _ ->\n            when {\n                json.isJsonObject -> INITIAL_GSON.fromJson(json, SearchRule::class.java)\n                json.isJsonPrimitive -> INITIAL_GSON.fromJson(json.asString, SearchRule::class.java)\n                else -> null\n            }\n        }\n\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/data/entities/rule/TocRule.kt",
    "content": "package io.legado.app.data.entities.rule\n\nimport android.os.Parcelable\nimport com.google.gson.JsonDeserializer\nimport io.legado.app.utils.INITIAL_GSON\nimport kotlinx.parcelize.Parcelize\n\n@Parcelize\ndata class TocRule(\n    var preUpdateJs: String? = null,\n    var chapterList: String? = null,\n    var chapterName: String? = null,\n    var chapterUrl: String? = null,\n    var formatJs: String? = null,\n    var isVolume: String? = null,\n    var isVip: String? = null,\n    var isPay: String? = null,\n    var updateTime: String? = null,\n    var nextTocUrl: String? = null\n) : Parcelable {\n\n    companion object {\n\n        val jsonDeserializer = JsonDeserializer<TocRule?> { json, _, _ ->\n            when {\n                json.isJsonObject -> INITIAL_GSON.fromJson(json, TocRule::class.java)\n                json.isJsonPrimitive -> INITIAL_GSON.fromJson(json.asString, TocRule::class.java)\n                else -> null\n            }\n        }\n\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/exception/ConcurrentException.kt",
    "content": "@file:Suppress(\"unused\")\n\npackage io.legado.app.exception\n\n/**\n * 并发限制\n */\nclass ConcurrentException(msg: String, val waitTime: Int) : NoStackTraceException(msg)"
  },
  {
    "path": "app/src/main/java/io/legado/app/exception/ContentEmptyException.kt",
    "content": "package io.legado.app.exception\n\n/**\n * 内容为空\n */\nclass ContentEmptyException(msg: String) : NoStackTraceException(msg)"
  },
  {
    "path": "app/src/main/java/io/legado/app/exception/EmptyFileException.kt",
    "content": "package io.legado.app.exception\n\n/**\n * 文件为空\n */\nclass EmptyFileException(msg: String) : NoStackTraceException(msg)"
  },
  {
    "path": "app/src/main/java/io/legado/app/exception/InvalidBooksDirException.kt",
    "content": "package io.legado.app.exception\n\nclass InvalidBooksDirException(msg: String) : NoStackTraceException(msg)\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/exception/NoBooksDirException.kt",
    "content": "package io.legado.app.exception\n\nimport io.legado.app.R\nimport splitties.init.appCtx\n\nclass NoBooksDirException: NoStackTraceException(appCtx.getString(R.string.no_books_dir))"
  },
  {
    "path": "app/src/main/java/io/legado/app/exception/NoStackTraceException.kt",
    "content": "package io.legado.app.exception\n\n/**\n * 不记录错误堆栈的报错\n */\nopen class NoStackTraceException(msg: String) : Exception(msg) {\n\n    override fun fillInStackTrace(): Throwable {\n        stackTrace = emptyStackTrace\n        return this\n    }\n\n    companion object {\n        private val emptyStackTrace = emptyArray<StackTraceElement>()\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/exception/RegexTimeoutException.kt",
    "content": "package io.legado.app.exception\n\nclass RegexTimeoutException(msg: String) : NoStackTraceException(msg)"
  },
  {
    "path": "app/src/main/java/io/legado/app/exception/TocEmptyException.kt",
    "content": "package io.legado.app.exception\n\n/**\n * 目录为空\n */\nclass TocEmptyException(msg: String) : NoStackTraceException(msg)"
  },
  {
    "path": "app/src/main/java/io/legado/app/help/AppFreezeMonitor.kt",
    "content": "package io.legado.app.help\n\nimport android.annotation.SuppressLint\nimport android.content.BroadcastReceiver\nimport android.content.Context\nimport android.content.Intent\nimport android.content.IntentFilter\nimport android.os.Handler\nimport android.os.HandlerThread\nimport android.os.SystemClock\nimport io.legado.app.help.config.AppConfig\nimport io.legado.app.utils.LogUtils\n\nobject AppFreezeMonitor {\n\n    private const val TAG = \"AppFreezeMonitor\"\n\n    val handler by lazy {\n        Handler(HandlerThread(\"AppFreezeMonitor\").apply { start() }.looper)\n    }\n\n    val screenStatusReceiver by lazy {\n        ScreenStatusReceiver()\n    }\n\n    private var registeredReceiver = false\n\n    @SuppressLint(\"UnspecifiedRegisterReceiverFlag\")\n    fun init(context: Context) {\n        if (!AppConfig.recordLog) {\n            if (registeredReceiver) {\n                registeredReceiver = false\n                context.unregisterReceiver(screenStatusReceiver)\n            }\n            return\n        }\n\n        if (!registeredReceiver) {\n            registeredReceiver = true\n            context.registerReceiver(screenStatusReceiver, screenStatusReceiver.filter)\n        }\n\n        var previous = SystemClock.uptimeMillis()\n\n        val runnable = object : Runnable {\n            override fun run() {\n                val current = SystemClock.uptimeMillis()\n                val elapsed = current - previous\n                val extra = elapsed - 3000\n\n                if (extra > 300) {\n                    LogUtils.d(TAG, \"检测到应用被系统冻结，时长：$extra 毫秒\")\n                }\n\n                previous = current\n\n                if (AppConfig.recordLog) {\n                    handler.postDelayed(this, 3000)\n                }\n            }\n        }\n        handler.postDelayed(runnable, 3000)\n    }\n\n    class ScreenStatusReceiver : BroadcastReceiver() {\n\n        val filter = IntentFilter().apply {\n            addAction(Intent.ACTION_SCREEN_ON)\n            addAction(Intent.ACTION_SCREEN_OFF)\n        }\n\n        override fun onReceive(context: Context?, intent: Intent?) {\n            when (intent?.action) {\n                Intent.ACTION_SCREEN_ON -> LogUtils.d(TAG, \"SCREEN_ON\")\n                Intent.ACTION_SCREEN_OFF -> LogUtils.d(TAG, \"SCREEN_OFF\")\n            }\n        }\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/help/AppWebDav.kt",
    "content": "package io.legado.app.help\n\nimport android.net.Uri\nimport io.legado.app.R\nimport io.legado.app.constant.AppLog\nimport io.legado.app.constant.PreferKey\nimport io.legado.app.data.appDb\nimport io.legado.app.data.entities.Book\nimport io.legado.app.data.entities.BookProgress\nimport io.legado.app.exception.NoStackTraceException\nimport io.legado.app.help.config.AppConfig\nimport io.legado.app.help.storage.Backup\nimport io.legado.app.help.storage.Restore\nimport io.legado.app.lib.webdav.Authorization\nimport io.legado.app.lib.webdav.WebDav\nimport io.legado.app.lib.webdav.WebDavException\nimport io.legado.app.lib.webdav.WebDavFile\nimport io.legado.app.model.remote.RemoteBookWebDav\nimport io.legado.app.utils.AlphanumComparator\nimport io.legado.app.utils.FileUtils\nimport io.legado.app.utils.GSON\nimport io.legado.app.utils.NetworkUtils\nimport io.legado.app.utils.UrlUtil\nimport io.legado.app.utils.compress.ZipUtils\nimport io.legado.app.utils.fromJsonObject\nimport io.legado.app.utils.getPrefString\nimport io.legado.app.utils.isJson\nimport io.legado.app.utils.normalizeFileName\nimport io.legado.app.utils.removePref\nimport io.legado.app.utils.toastOnUi\nimport kotlinx.coroutines.currentCoroutineContext\nimport kotlinx.coroutines.ensureActive\nimport kotlinx.coroutines.runBlocking\nimport splitties.init.appCtx\nimport java.io.File\n\n/**\n * webDav初始化会访问网络,不要放到主线程\n */\nobject AppWebDav {\n    private const val defaultWebDavUrl = \"https://dav.jianguoyun.com/dav/\"\n    private val bookProgressUrl get() = \"${rootWebDavUrl}bookProgress/\"\n    private val exportsWebDavUrl get() = \"${rootWebDavUrl}books/\"\n    private val bgWebDavUrl get() = \"${rootWebDavUrl}background/\"\n\n    var authorization: Authorization? = null\n        private set\n\n    var defaultBookWebDav: RemoteBookWebDav? = null\n\n    val isOk get() = authorization != null\n\n    val isJianGuoYun get() = rootWebDavUrl.startsWith(defaultWebDavUrl, true)\n\n    init {\n        runBlocking {\n            upConfig()\n        }\n    }\n\n    private val rootWebDavUrl: String\n        get() {\n            val configUrl = appCtx.getPrefString(PreferKey.webDavUrl)?.trim()\n            var url = if (configUrl.isNullOrEmpty()) defaultWebDavUrl else configUrl\n            if (!url.endsWith(\"/\")) url = \"${url}/\"\n            AppConfig.webDavDir?.trim()?.let {\n                if (it.isNotEmpty()) {\n                    url = \"${url}${it}/\"\n                }\n            }\n            return url\n        }\n\n    suspend fun upConfig() {\n        kotlin.runCatching {\n            authorization = null\n            defaultBookWebDav = null\n            val account = appCtx.getPrefString(PreferKey.webDavAccount)\n            val password = appCtx.getPrefString(PreferKey.webDavPassword)\n            if (!account.isNullOrEmpty() && !password.isNullOrEmpty()) {\n                val mAuthorization = Authorization(account, password)\n                checkAuthorization(mAuthorization)\n                WebDav(rootWebDavUrl, mAuthorization).makeAsDir()\n                WebDav(bookProgressUrl, mAuthorization).makeAsDir()\n                WebDav(exportsWebDavUrl, mAuthorization).makeAsDir()\n                WebDav(bgWebDavUrl, mAuthorization).makeAsDir()\n                val rootBooksUrl = \"${rootWebDavUrl}books/\"\n                defaultBookWebDav = RemoteBookWebDav(rootBooksUrl, mAuthorization)\n                authorization = mAuthorization\n            }\n        }\n    }\n\n    @Throws(WebDavException::class)\n    private suspend fun checkAuthorization(authorization: Authorization) {\n        if (!WebDav(rootWebDavUrl, authorization).check()) {\n            appCtx.removePref(PreferKey.webDavPassword)\n            appCtx.toastOnUi(R.string.webdav_application_authorization_error)\n            throw WebDavException(appCtx.getString(R.string.webdav_application_authorization_error))\n        }\n    }\n\n    @Throws(Exception::class)\n    suspend fun getBackupNames(): ArrayList<String> {\n        val names = arrayListOf<String>()\n        authorization?.let {\n            var files = WebDav(rootWebDavUrl, it).listFiles()\n            files = files.sortedWith { o1, o2 ->\n                AlphanumComparator.compare(o1.displayName, o2.displayName)\n            }.reversed()\n            files.forEach { webDav ->\n                val name = webDav.displayName\n                if (name.startsWith(\"backup\")) {\n                    names.add(name)\n                }\n            }\n        } ?: throw NoStackTraceException(\"webDav没有配置\")\n        return names\n    }\n\n    @Throws(WebDavException::class)\n    suspend fun restoreWebDav(name: String) {\n        authorization?.let {\n            val webDav = WebDav(rootWebDavUrl + name, it)\n            webDav.downloadTo(Backup.zipFilePath, true)\n            FileUtils.delete(Backup.backupPath)\n            ZipUtils.unZipToPath(File(Backup.zipFilePath), Backup.backupPath)\n            Restore.restoreLocked(Backup.backupPath)\n        }\n    }\n\n    suspend fun hasBackUp(backUpName: String): Boolean {\n        authorization?.let {\n            val url = \"$rootWebDavUrl${backUpName}\"\n            return WebDav(url, it).exists()\n        }\n        return false\n    }\n\n    suspend fun lastBackUp(): Result<WebDavFile?> {\n        return kotlin.runCatching {\n            authorization?.let {\n                var lastBackupFile: WebDavFile? = null\n                WebDav(rootWebDavUrl, it).listFiles().reversed().forEach { webDavFile ->\n                    if (webDavFile.displayName.startsWith(\"backup\")) {\n                        if (lastBackupFile == null\n                            || webDavFile.lastModify > lastBackupFile.lastModify\n                        ) {\n                            lastBackupFile = webDavFile\n                        }\n                    }\n                }\n                lastBackupFile\n            }\n        }\n    }\n\n    /**\n     * webDav备份\n     * @param fileName 备份文件名\n     */\n    @Throws(Exception::class)\n    suspend fun backUpWebDav(fileName: String) {\n        if (!NetworkUtils.isAvailable()) return\n        authorization?.let {\n            val putUrl = \"$rootWebDavUrl$fileName\"\n            WebDav(putUrl, it).upload(Backup.zipFilePath)\n        }\n    }\n\n    /**\n     * 获取云端所有背景名称\n     */\n    private suspend fun getAllBgWebDavFiles(): Result<List<WebDavFile>> {\n        return kotlin.runCatching {\n            if (!NetworkUtils.isAvailable())\n                throw NoStackTraceException(\"网络未连接\")\n            authorization.let {\n                it ?: throw NoStackTraceException(\"webDav未配置\")\n                WebDav(bgWebDavUrl, it).listFiles()\n            }\n        }\n    }\n\n    /**\n     * 上传背景图片\n     */\n    suspend fun upBgs(files: Array<File>) {\n        val authorization = authorization ?: return\n        if (!NetworkUtils.isAvailable()) return\n        val bgWebDavFiles = getAllBgWebDavFiles().getOrThrow()\n            .map { it.displayName }\n            .toSet()\n        files.forEach {\n            if (!bgWebDavFiles.contains(it.name) && it.exists()) {\n                WebDav(\"$bgWebDavUrl${it.name}\", authorization)\n                    .upload(it)\n            }\n        }\n    }\n\n    /**\n     * 下载背景图片\n     */\n    suspend fun downBgs() {\n        val authorization = authorization ?: return\n        if (!NetworkUtils.isAvailable()) return\n        val bgWebDavFiles = getAllBgWebDavFiles().getOrThrow()\n            .map { it.displayName }\n            .toSet()\n    }\n\n    @Suppress(\"unused\")\n    suspend fun exportWebDav(byteArray: ByteArray, fileName: String) {\n        if (!NetworkUtils.isAvailable()) return\n        try {\n            authorization?.let {\n                // 如果导出的本地文件存在,开始上传\n                val putUrl = exportsWebDavUrl + fileName\n                WebDav(putUrl, it).upload(byteArray, \"text/plain\")\n            }\n        } catch (e: Exception) {\n            currentCoroutineContext().ensureActive()\n            AppLog.put(\"WebDav导出失败\\n${e.localizedMessage}\", e, true)\n        }\n    }\n\n    suspend fun exportWebDav(uri: Uri, fileName: String) {\n        if (!NetworkUtils.isAvailable()) return\n        try {\n            authorization?.let {\n                // 如果导出的本地文件存在,开始上传\n                val putUrl = exportsWebDavUrl + fileName\n                WebDav(putUrl, it).upload(uri, \"text/plain\")\n            }\n        } catch (e: Exception) {\n            currentCoroutineContext().ensureActive()\n            AppLog.put(\"WebDav导出失败\\n${e.localizedMessage}\", e, true)\n        }\n    }\n\n    suspend fun uploadBookProgress(\n        book: Book,\n        toast: Boolean = false,\n        onSuccess: (() -> Unit)? = null\n    ) {\n        val authorization = authorization ?: return\n        if (!AppConfig.syncBookProgress) return\n        if (!NetworkUtils.isAvailable()) return\n        try {\n            val bookProgress = BookProgress(book)\n            val json = GSON.toJson(bookProgress)\n            val url = getProgressUrl(book.name, book.author)\n            WebDav(url, authorization).upload(json.toByteArray(), \"application/json\")\n            book.syncTime = System.currentTimeMillis()\n            onSuccess?.invoke()\n        } catch (e: Exception) {\n            currentCoroutineContext().ensureActive()\n            AppLog.put(\"上传进度失败\\n${e.localizedMessage}\", e, toast)\n        }\n    }\n\n    suspend fun uploadBookProgress(bookProgress: BookProgress, onSuccess: (() -> Unit)? = null) {\n        try {\n            val authorization = authorization ?: return\n            if (!AppConfig.syncBookProgress) return\n            if (!NetworkUtils.isAvailable()) return\n            val json = GSON.toJson(bookProgress)\n            val url = getProgressUrl(bookProgress.name, bookProgress.author)\n            WebDav(url, authorization).upload(json.toByteArray(), \"application/json\")\n            onSuccess?.invoke()\n        } catch (e: Exception) {\n            currentCoroutineContext().ensureActive()\n            AppLog.put(\"上传进度失败\\n${e.localizedMessage}\", e)\n        }\n    }\n\n    private fun getProgressUrl(name: String, author: String): String {\n        return bookProgressUrl + getProgressFileName(name, author)\n    }\n\n    private fun getProgressFileName(name: String, author: String): String {\n        return UrlUtil.replaceReservedChar(\"${name}_${author}\".normalizeFileName()) + \".json\"\n    }\n\n    /**\n     * 获取书籍进度\n     */\n    suspend fun getBookProgress(book: Book): BookProgress? {\n        val url = getProgressUrl(book.name, book.author)\n        kotlin.runCatching {\n            val authorization = authorization ?: return null\n            WebDav(url, authorization).download().let { byteArray ->\n                val json = String(byteArray)\n                if (json.isJson()) {\n                    return GSON.fromJsonObject<BookProgress>(json).getOrNull()\n                }\n            }\n        }.onFailure {\n            currentCoroutineContext().ensureActive()\n            AppLog.put(\"获取书籍进度失败\\n${it.localizedMessage}\", it)\n        }\n        return null\n    }\n\n    suspend fun downloadAllBookProgress() {\n        val authorization = authorization ?: return\n        if (!NetworkUtils.isAvailable()) return\n        val bookProgressFiles = WebDav(bookProgressUrl, authorization).listFiles()\n        val map = hashMapOf<String, WebDavFile>()\n        bookProgressFiles.forEach {\n            map[it.displayName] = it\n        }\n        appDb.bookDao.all.forEach { book ->\n            val progressFileName = getProgressFileName(book.name, book.author)\n            val webDavFile = map[progressFileName]\n            webDavFile ?: return\n            if (webDavFile.lastModify <= book.syncTime) {\n                //本地同步时间大于上传时间不用同步\n                return\n            }\n            getBookProgress(book)?.let { bookProgress ->\n                if (bookProgress.durChapterIndex > book.durChapterIndex\n                    || (bookProgress.durChapterIndex == book.durChapterIndex\n                            && bookProgress.durChapterPos > book.durChapterPos)\n                ) {\n                    book.durChapterIndex = bookProgress.durChapterIndex\n                    book.durChapterPos = bookProgress.durChapterPos\n                    book.durChapterTitle = bookProgress.durChapterTitle\n                    book.durChapterTime = bookProgress.durChapterTime\n                    book.syncTime = System.currentTimeMillis()\n                    appDb.bookDao.update(book)\n                }\n            }\n        }\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/help/CacheManager.kt",
    "content": "package io.legado.app.help\n\nimport androidx.annotation.Keep\nimport androidx.collection.LruCache\nimport io.legado.app.data.appDb\nimport io.legado.app.data.entities.Cache\nimport io.legado.app.model.analyzeRule.QueryTTF\nimport io.legado.app.utils.ACache\nimport io.legado.app.utils.memorySize\n\nprivate val queryTTFMap = LruCache<String, QueryTTF>(4)\n\n/**\n * 最多只缓存50M的数据,防止OOM\n */\nprivate val memoryLruCache = object : LruCache<String, Any>(1024 * 1024 * 50) {\n\n    override fun sizeOf(key: String, value: Any): Int {\n        return value.toString().memorySize()\n    }\n\n}\n\nobject AppCacheManager {\n\n    fun put(key: String, queryTTF: QueryTTF) {\n        queryTTFMap.put(key, queryTTF)\n    }\n\n    fun getQueryTTF(key: String): QueryTTF? {\n        return queryTTFMap[key]\n    }\n\n    fun clearSourceVariables() {\n        memoryLruCache.snapshot().keys.forEach {\n            if (it.startsWith(\"v_\")\n                || it.startsWith(\"userInfo_\")\n                || it.startsWith(\"loginHeader_\")\n                || it.startsWith(\"sourceVariable_\")\n            ) {\n                memoryLruCache.remove(it)\n            }\n        }\n    }\n\n}\n\n\n@Keep\n@Suppress(\"unused\")\nobject CacheManager {\n\n    /**\n     * saveTime 单位为秒\n     */\n    @JvmOverloads\n    fun put(key: String, value: Any, saveTime: Int = 0) {\n        val deadline =\n            if (saveTime == 0) 0 else System.currentTimeMillis() + saveTime * 1000\n        when (value) {\n            is ByteArray -> ACache.get().put(key, value, saveTime)\n            else -> {\n                val cache = Cache(key, value.toString(), deadline)\n                appDb.cacheDao.insert(cache)\n            }\n        }\n    }\n\n    fun putMemory(key: String, value: Any) {\n        memoryLruCache.put(key, value)\n    }\n\n    //从内存中获取数据 使用lruCache\n    fun getFromMemory(key: String): Any? {\n        return memoryLruCache[key]\n    }\n\n    fun deleteMemory(key: String) {\n        memoryLruCache.remove(key)\n    }\n\n    fun get(key: String): String? {\n        val cache = appDb.cacheDao.get(key)\n        if (cache != null && (cache.deadline == 0L || cache.deadline > System.currentTimeMillis())) {\n            return cache.value\n        }\n        return null\n    }\n\n    fun getInt(key: String): Int? {\n        return get(key)?.toIntOrNull()\n    }\n\n    fun getLong(key: String): Long? {\n        return get(key)?.toLongOrNull()\n    }\n\n    fun getDouble(key: String): Double? {\n        return get(key)?.toDoubleOrNull()\n    }\n\n    fun getFloat(key: String): Float? {\n        return get(key)?.toFloatOrNull()\n    }\n\n    fun getByteArray(key: String): ByteArray? {\n        return ACache.get().getAsBinary(key)\n    }\n\n    fun putFile(key: String, value: String, saveTime: Int = 0) {\n        ACache.get().put(key, value, saveTime)\n    }\n\n    fun getFile(key: String): String? {\n        return ACache.get().getAsString(key)\n    }\n\n    fun delete(key: String) {\n        appDb.cacheDao.delete(key)\n        deleteMemory(key)\n        ACache.get().remove(key)\n    }\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/help/ConcurrentRateLimiter.kt",
    "content": "package io.legado.app.help\n\nimport io.legado.app.data.entities.BaseSource\nimport io.legado.app.exception.ConcurrentException\nimport io.legado.app.model.analyzeRule.AnalyzeUrl.ConcurrentRecord\nimport kotlinx.coroutines.delay\n\nclass ConcurrentRateLimiter(val source: BaseSource?) {\n\n    companion object {\n        private val concurrentRecordMap = hashMapOf<String, ConcurrentRecord>()\n    }\n\n    /**\n     * 开始访问,并发判断\n     */\n    @Throws(ConcurrentException::class)\n    private fun fetchStart(): ConcurrentRecord? {\n        source ?: return null\n        val concurrentRate = source.concurrentRate\n        if (concurrentRate.isNullOrEmpty() || concurrentRate == \"0\") {\n            return null\n        }\n        val rateIndex = concurrentRate.indexOf(\"/\")\n        var fetchRecord = concurrentRecordMap[source.getKey()]\n        if (fetchRecord == null) {\n            synchronized(concurrentRecordMap) {\n                fetchRecord = concurrentRecordMap[source.getKey()]\n                if (fetchRecord == null) {\n                    fetchRecord = ConcurrentRecord(rateIndex > 0, System.currentTimeMillis(), 1)\n                    concurrentRecordMap[source.getKey()] = fetchRecord\n                    return fetchRecord\n                }\n            }\n        }\n        val waitTime: Int = synchronized(fetchRecord!!) {\n            try {\n                if (!fetchRecord.isConcurrent) {\n                    //并发控制非 次数/毫秒\n                    if (fetchRecord.frequency > 0) {\n                        //已经有访问线程,直接等待\n                        return@synchronized concurrentRate.toInt()\n                    }\n                    //没有线程访问,判断还剩多少时间可以访问\n                    val nextTime = fetchRecord.time + concurrentRate.toInt()\n                    if (System.currentTimeMillis() >= nextTime) {\n                        fetchRecord.time = System.currentTimeMillis()\n                        fetchRecord.frequency = 1\n                        return@synchronized 0\n                    }\n                    return@synchronized (nextTime - System.currentTimeMillis()).toInt()\n                } else {\n                    //并发控制为 次数/毫秒\n                    val sj = concurrentRate.substring(rateIndex + 1)\n                    val nextTime = fetchRecord.time + sj.toInt()\n                    if (System.currentTimeMillis() >= nextTime) {\n                        //已经过了限制时间,重置开始时间\n                        fetchRecord.time = System.currentTimeMillis()\n                        fetchRecord.frequency = 1\n                        return@synchronized 0\n                    }\n                    val cs = concurrentRate.substring(0, rateIndex)\n                    if (fetchRecord.frequency > cs.toInt()) {\n                        return@synchronized (nextTime - System.currentTimeMillis()).toInt()\n                    } else {\n                        fetchRecord.frequency += 1\n                        return@synchronized 0\n                    }\n                }\n            } catch (_: Exception) {\n                return@synchronized 0\n            }\n        }\n        if (waitTime > 0) {\n            throw ConcurrentException(\n                \"根据并发率还需等待${waitTime}毫秒才可以访问\",\n                waitTime = waitTime\n            )\n        }\n        return fetchRecord\n    }\n\n    /**\n     * 访问结束\n     */\n    fun fetchEnd(concurrentRecord: ConcurrentRecord?) {\n        if (concurrentRecord != null && !concurrentRecord.isConcurrent) {\n            synchronized(concurrentRecord) {\n                concurrentRecord.frequency -= 1\n            }\n        }\n    }\n\n    /**\n     * 获取并发记录，若处于并发限制状态下则会等待\n     */\n    suspend fun getConcurrentRecord(): ConcurrentRecord? {\n        while (true) {\n            try {\n                return fetchStart()\n            } catch (e: ConcurrentException) {\n                delay(e.waitTime.toLong())\n            }\n        }\n    }\n\n    fun getConcurrentRecordBlocking(): ConcurrentRecord? {\n        while (true) {\n            try {\n                return fetchStart()\n            } catch (e: ConcurrentException) {\n                Thread.sleep(e.waitTime.toLong())\n            }\n        }\n    }\n\n    suspend inline fun <T> withLimit(block: () -> T): T {\n        val concurrentRecord = getConcurrentRecord()\n        try {\n            return block()\n        } finally {\n            fetchEnd(concurrentRecord)\n        }\n    }\n\n    inline fun <T> withLimitBlocking(block: () -> T): T {\n        val concurrentRecord = getConcurrentRecordBlocking()\n        try {\n            return block()\n        } finally {\n            fetchEnd(concurrentRecord)\n        }\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/help/CrashHandler.kt",
    "content": "package io.legado.app.help\n\nimport android.annotation.SuppressLint\nimport android.content.Context\nimport android.net.Uri\nimport android.os.Build\nimport android.os.Debug\nimport android.os.Looper\nimport android.webkit.WebSettings\nimport io.legado.app.constant.AppConst\nimport io.legado.app.constant.AppLog\nimport io.legado.app.exception.NoStackTraceException\nimport io.legado.app.help.config.AppConfig\nimport io.legado.app.help.config.LocalConfig\nimport io.legado.app.model.ReadAloud\nimport io.legado.app.utils.FileDoc\nimport io.legado.app.utils.FileUtils\nimport io.legado.app.utils.createFileIfNotExist\nimport io.legado.app.utils.createFolderReplace\nimport io.legado.app.utils.externalCache\nimport io.legado.app.utils.getFile\nimport io.legado.app.utils.longToastOnUiLegacy\nimport io.legado.app.utils.stackTraceStr\nimport io.legado.app.utils.writeText\nimport splitties.init.appCtx\nimport java.io.PrintWriter\nimport java.io.StringWriter\nimport java.text.SimpleDateFormat\nimport java.util.Date\nimport java.util.concurrent.TimeUnit\n\n/**\n * 异常管理类\n */\nclass CrashHandler(val context: Context) : Thread.UncaughtExceptionHandler {\n\n    /**\n     * 系统默认UncaughtExceptionHandler\n     */\n    private var mDefaultHandler = Thread.getDefaultUncaughtExceptionHandler()\n\n    init {\n        //设置该CrashHandler为系统默认的\n        Thread.setDefaultUncaughtExceptionHandler(this)\n    }\n\n    /**\n     * uncaughtException 回调函数\n     */\n    override fun uncaughtException(thread: Thread, ex: Throwable) {\n        if (shouldAbsorb(ex)) {\n            AppLog.put(\"发生未捕获的异常\\n${ex.localizedMessage}\", ex)\n            Looper.loop()\n        } else {\n            ReadAloud.stop(context)\n            handleException(ex)\n            mDefaultHandler?.uncaughtException(thread, ex)\n        }\n    }\n\n    private fun shouldAbsorb(e: Throwable): Boolean {\n        return when {\n            e::class.simpleName == \"CannotDeliverBroadcastException\" -> true\n            e is SecurityException && e.message?.contains(\n                \"nor current process has android.permission.OBSERVE_GRANT_REVOKE_PERMISSIONS\",\n                true\n            ) == true -> true\n\n            else -> false\n        }\n    }\n\n    /**\n     * 处理该异常\n     */\n    private fun handleException(ex: Throwable?) {\n        if (ex == null) return\n        LocalConfig.appCrash = true\n        //保存日志文件\n        saveCrashInfo2File(ex)\n        if ((ex is OutOfMemoryError || ex.cause is OutOfMemoryError) && AppConfig.recordHeapDump) {\n            doHeapDump()\n        }\n        context.longToastOnUiLegacy(ex.stackTraceStr)\n        Thread.sleep(3000)\n    }\n\n    companion object {\n        /**\n         * 存储异常和参数信息\n         */\n        private val paramsMap by lazy {\n            val map = LinkedHashMap<String, String>()\n            kotlin.runCatching {\n                //获取系统信息\n                map[\"MANUFACTURER\"] = Build.MANUFACTURER\n                map[\"BRAND\"] = Build.BRAND\n                map[\"MODEL\"] = Build.MODEL\n                map[\"SDK_INT\"] = Build.VERSION.SDK_INT.toString()\n                map[\"RELEASE\"] = Build.VERSION.RELEASE\n                map[\"WebViewUserAgent\"] = try {\n                    WebSettings.getDefaultUserAgent(appCtx)\n                } catch (e: Throwable) {\n                    e.toString()\n                }\n                map[\"packageName\"] = appCtx.packageName\n                map[\"heapSize\"] = Runtime.getRuntime().maxMemory().toString()\n                //获取app版本信息\n                AppConst.appInfo.let {\n                    map[\"versionName\"] = it.versionName\n                    map[\"versionCode\"] = it.versionCode.toString()\n                }\n            }\n            map\n        }\n\n        /**\n         * 格式化时间\n         */\n        @SuppressLint(\"SimpleDateFormat\")\n        private val format = SimpleDateFormat(\"yyyy-MM-dd-HH-mm-ss\")\n\n        /**\n         * 保存错误信息到文件中\n         */\n        fun saveCrashInfo2File(ex: Throwable) {\n            val sb = StringBuilder()\n            for ((key, value) in paramsMap) {\n                sb.append(key).append(\"=\").append(value).append(\"\\n\")\n            }\n\n            val writer = StringWriter()\n            val printWriter = PrintWriter(writer)\n            ex.printStackTrace(printWriter)\n            var cause: Throwable? = ex.cause\n            while (cause != null) {\n                cause.printStackTrace(printWriter)\n                cause = cause.cause\n            }\n            printWriter.close()\n            val result = writer.toString()\n            sb.append(result)\n            val crashLog = sb.toString()\n            val timestamp = System.currentTimeMillis()\n            val time = format.format(Date())\n            val fileName = \"crash-$time-$timestamp.log\"\n            try {\n                val backupPath = AppConfig.backupPath\n                    ?: throw NoStackTraceException(\"备份路径未配置\")\n                val uri = Uri.parse(backupPath)\n                val fileDoc = FileDoc.fromUri(uri, true)\n                fileDoc.createFileIfNotExist(fileName, \"crash\")\n                    .writeText(crashLog)\n            } catch (_: Exception) {\n            }\n            kotlin.runCatching {\n                appCtx.externalCacheDir?.let { rootFile ->\n                    val exceedTimeMillis = System.currentTimeMillis() - TimeUnit.DAYS.toMillis(7)\n                    rootFile.getFile(\"crash\").listFiles()?.forEach {\n                        if (it.lastModified() < exceedTimeMillis) {\n                            it.delete()\n                        }\n                    }\n                    FileUtils.createFileIfNotExist(rootFile, \"crash\", fileName)\n                        .writeText(crashLog)\n                }\n            }\n        }\n\n        /**\n         * 进行堆转储\n         */\n        fun doHeapDump(manually: Boolean = false) {\n            val heapDir = appCtx\n                .externalCache\n                .getFile(\"heapDump\")\n            heapDir.createFolderReplace()\n            val fileName = if (manually) {\n                \"heap-dump-manually-${System.currentTimeMillis()}.hprof\"\n            } else {\n                \"heap-dump-${System.currentTimeMillis()}.hprof\"\n            }\n            val heapFile = heapDir.getFile(fileName)\n            val heapDumpName = heapFile.absolutePath\n            Debug.dumpHprofData(heapDumpName)\n        }\n\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/help/DefaultData.kt",
    "content": "package io.legado.app.help\n\nimport io.legado.app.constant.AppConst\nimport io.legado.app.data.appDb\nimport io.legado.app.data.entities.DictRule\nimport io.legado.app.data.entities.HttpTTS\nimport io.legado.app.data.entities.KeyboardAssist\nimport io.legado.app.data.entities.RssSource\nimport io.legado.app.data.entities.TxtTocRule\nimport io.legado.app.help.config.LocalConfig\nimport io.legado.app.help.config.ReadBookConfig\nimport io.legado.app.help.config.ThemeConfig\nimport io.legado.app.help.coroutine.Coroutine\nimport io.legado.app.model.BookCover\nimport io.legado.app.utils.GSON\nimport io.legado.app.utils.fromJsonArray\nimport io.legado.app.utils.fromJsonObject\nimport io.legado.app.utils.printOnDebug\nimport splitties.init.appCtx\nimport java.io.File\n\nobject DefaultData {\n\n    fun upVersion() {\n        if (LocalConfig.versionCode < AppConst.appInfo.versionCode) {\n            Coroutine.async {\n                if (LocalConfig.needUpHttpTTS) {\n                    importDefaultHttpTTS()\n                }\n                if (LocalConfig.needUpTxtTocRule) {\n                    importDefaultTocRules()\n                }\n                if (LocalConfig.needUpRssSources) {\n                    importDefaultRssSources()\n                }\n                if (LocalConfig.needUpDictRule) {\n                    importDefaultDictRules()\n                }\n            }.onError {\n                it.printOnDebug()\n            }\n        }\n    }\n\n    val httpTTS: List<HttpTTS> by lazy {\n        val json =\n            String(\n                appCtx.assets.open(\"defaultData${File.separator}httpTTS.json\")\n                    .readBytes()\n            )\n        HttpTTS.fromJsonArray(json).getOrElse {\n            emptyList()\n        }\n    }\n\n    val readConfigs: List<ReadBookConfig.Config> by lazy {\n        val json = String(\n            appCtx.assets.open(\"defaultData${File.separator}${ReadBookConfig.configFileName}\")\n                .readBytes()\n        )\n        GSON.fromJsonArray<ReadBookConfig.Config>(json).getOrNull()\n            ?: emptyList()\n    }\n\n    val txtTocRules: List<TxtTocRule> by lazy {\n        val json = String(\n            appCtx.assets.open(\"defaultData${File.separator}txtTocRule.json\")\n                .readBytes()\n        )\n        GSON.fromJsonArray<TxtTocRule>(json).getOrNull() ?: emptyList()\n    }\n\n    val themeConfigs: List<ThemeConfig.Config> by lazy {\n        val json = String(\n            appCtx.assets.open(\"defaultData${File.separator}${ThemeConfig.configFileName}\")\n                .readBytes()\n        )\n        GSON.fromJsonArray<ThemeConfig.Config>(json).getOrNull() ?: emptyList()\n    }\n\n    val rssSources: List<RssSource> by lazy {\n        val json = String(\n            appCtx.assets.open(\"defaultData${File.separator}rssSources.json\")\n                .readBytes()\n        )\n        GSON.fromJsonArray<RssSource>(json).getOrDefault(emptyList())\n    }\n\n    val coverRule: BookCover.CoverRule by lazy {\n        val json = String(\n            appCtx.assets.open(\"defaultData${File.separator}coverRule.json\")\n                .readBytes()\n        )\n        GSON.fromJsonObject<BookCover.CoverRule>(json).getOrThrow()\n    }\n\n    val dictRules: List<DictRule> by lazy {\n        val json = String(\n            appCtx.assets.open(\"defaultData${File.separator}dictRules.json\")\n                .readBytes()\n        )\n        GSON.fromJsonArray<DictRule>(json).getOrThrow()\n    }\n\n    val keyboardAssists: List<KeyboardAssist> by lazy {\n        val json = String(\n            appCtx.assets.open(\"defaultData${File.separator}keyboardAssists.json\")\n                .readBytes()\n        )\n        GSON.fromJsonArray<KeyboardAssist>(json).getOrThrow()\n    }\n\n    fun importDefaultHttpTTS() {\n        appDb.httpTTSDao.deleteDefault()\n        appDb.httpTTSDao.insert(*httpTTS.toTypedArray())\n    }\n\n    fun importDefaultTocRules() {\n        appDb.txtTocRuleDao.deleteDefault()\n        appDb.txtTocRuleDao.insert(*txtTocRules.toTypedArray())\n    }\n\n    fun importDefaultRssSources() {\n        appDb.rssSourceDao.deleteDefault()\n        appDb.rssSourceDao.insert(*rssSources.toTypedArray())\n    }\n\n    fun importDefaultDictRules() {\n        appDb.dictRuleDao.insert(*dictRules.toTypedArray())\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/help/DirectLinkUpload.kt",
    "content": "package io.legado.app.help\n\nimport androidx.annotation.Keep\nimport io.legado.app.exception.NoStackTraceException\nimport io.legado.app.model.analyzeRule.AnalyzeRule\nimport io.legado.app.model.analyzeRule.AnalyzeRule.Companion.setCoroutineContext\nimport io.legado.app.model.analyzeRule.AnalyzeUrl\nimport io.legado.app.utils.ACache\nimport io.legado.app.utils.FileUtils\nimport io.legado.app.utils.GSON\nimport io.legado.app.utils.compress.ZipUtils\nimport io.legado.app.utils.createFileReplace\nimport io.legado.app.utils.externalCache\nimport io.legado.app.utils.fromJsonArray\nimport io.legado.app.utils.fromJsonObject\nimport splitties.init.appCtx\nimport java.io.File\nimport kotlin.coroutines.coroutineContext\n\n@Suppress(\"MemberVisibilityCanBePrivate\")\nobject DirectLinkUpload {\n\n    const val ruleFileName = \"directLinkUploadRule.json\"\n\n    @Throws(NoStackTraceException::class)\n    suspend fun upLoad(\n        fileName: String,\n        file: Any,\n        contentType: String,\n        rule: Rule = getRule()\n    ): String {\n        val url = rule.uploadUrl\n        if (url.isBlank()) {\n            throw NoStackTraceException(\"上传url未配置\")\n        }\n        val downloadUrlRule = rule.downloadUrlRule\n        if (downloadUrlRule.isBlank()) {\n            throw NoStackTraceException(\"下载地址规则未配置\")\n        }\n        var mFileName = fileName\n        var mFile = file\n        var mContentType = contentType\n        if (rule.compress && contentType != \"application/zip\") {\n            mFileName = \"$fileName.zip\"\n            mContentType = \"application/zip\"\n            mFile = when (file) {\n                is File -> {\n                    val zipFile = File(FileUtils.getPath(appCtx.externalCache, \"upload\", mFileName))\n                    zipFile.createFileReplace()\n                    ZipUtils.zipFile(file, zipFile)\n                    zipFile\n                }\n\n                is ByteArray -> ZipUtils.zipByteArray(file, fileName)\n                is String -> ZipUtils.zipByteArray(file.toByteArray(), fileName)\n                else -> ZipUtils.zipByteArray(GSON.toJson(file).toByteArray(), fileName)\n            }\n        }\n        val analyzeUrl = AnalyzeUrl(url)\n        val res = analyzeUrl.upload(mFileName, mFile, mContentType)\n        if (mFile is File) {\n            mFile.delete()\n        }\n        val analyzeRule = AnalyzeRule().setContent(res.body, res.url)\n            .setCoroutineContext(coroutineContext)\n        val downloadUrl = analyzeRule.getString(downloadUrlRule)\n        if (downloadUrl.isBlank()) {\n            throw NoStackTraceException(\"上传失败,${res.body}\")\n        }\n        return downloadUrl\n    }\n\n    val defaultRules: List<Rule> by lazy {\n        val json = String(\n            appCtx.assets.open(\"defaultData${File.separator}directLinkUpload.json\")\n                .readBytes()\n        )\n        GSON.fromJsonArray<Rule>(json).getOrThrow()\n    }\n\n    fun getRule(): Rule {\n        return getConfig() ?: defaultRules[0]\n    }\n\n    fun getConfig(): Rule? {\n        val json = ACache.get(cacheDir = false).getAsString(ruleFileName)\n        return GSON.fromJsonObject<Rule>(json).getOrNull()\n    }\n\n    fun putConfig(rule: Rule) {\n        ACache.get(cacheDir = false).put(ruleFileName, GSON.toJson(rule))\n    }\n\n    fun delConfig() {\n        ACache.get(cacheDir = false).remove(ruleFileName)\n    }\n\n    fun getSummary(): String {\n        return getRule().summary\n    }\n\n    @Keep\n    data class Rule(\n        var uploadUrl: String, //创建分享链接\n        var downloadUrlRule: String, //下载链接规则\n        var summary: String, //注释\n        var compress: Boolean = false, //是否压缩\n    ) {\n\n        override fun toString(): String {\n            return summary\n        }\n\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/help/DispatchersMonitor.kt",
    "content": "package io.legado.app.help\n\nimport io.legado.app.help.config.AppConfig\nimport io.legado.app.utils.LogUtils\nimport kotlinx.coroutines.CoroutineDispatcher\nimport kotlinx.coroutines.CoroutineScope\nimport kotlinx.coroutines.Dispatchers.Default\nimport kotlinx.coroutines.Dispatchers.IO\nimport kotlinx.coroutines.Dispatchers.Main\nimport kotlinx.coroutines.ExperimentalCoroutinesApi\nimport kotlinx.coroutines.asCoroutineDispatcher\nimport kotlinx.coroutines.cancelChildren\nimport kotlinx.coroutines.delay\nimport kotlinx.coroutines.isActive\nimport kotlinx.coroutines.launch\nimport kotlinx.coroutines.selects.onTimeout\nimport kotlinx.coroutines.selects.select\nimport kotlinx.coroutines.withContext\nimport java.util.concurrent.Executors\n\nobject DispatchersMonitor {\n\n    private const val TAG = \"DispatchersMonitor\"\n\n    private val dispatcher by lazy {\n        Executors.newSingleThreadExecutor {\n            Thread(it, TAG)\n        }.asCoroutineDispatcher()\n    }\n\n    private val scope = CoroutineScope(dispatcher)\n\n    fun init() {\n        scope.coroutineContext.cancelChildren()\n        if (!AppConfig.recordLog) {\n            return\n        }\n        monitor(IO)\n        monitor(Default)\n        monitor(Main)\n    }\n\n    @OptIn(ExperimentalCoroutinesApi::class)\n    private fun monitor(dispatcher: CoroutineDispatcher) {\n        scope.launch {\n            while (isActive) select {\n                launch {\n                    withContext(dispatcher) {\n                        delay(3000)\n                    }\n                }.onJoin {}\n                onTimeout(5000) {\n                    LogUtils.d(TAG, \"Dispatcher $dispatcher is timed out waiting for for 5000ms.\")\n                }\n            }\n        }\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/help/EventMessage.kt",
    "content": "package io.legado.app.help\n\nimport android.text.TextUtils\n\n@Suppress(\"unused\")\nclass EventMessage {\n\n    var what: Int? = null\n    var tag: String? = null\n    var obj: Any? = null\n\n    fun isFrom(tag: String): Boolean {\n        return TextUtils.equals(this.tag, tag)\n    }\n\n    fun maybeFrom(vararg tags: String): Boolean {\n        return listOf(*tags).contains(tag)\n    }\n\n    companion object {\n\n        fun obtain(tag: String): EventMessage {\n            val message = EventMessage()\n            message.tag = tag\n            return message\n        }\n\n        fun obtain(what: Int): EventMessage {\n            val message = EventMessage()\n            message.what = what\n            return message\n        }\n\n        fun obtain(what: Int, obj: Any): EventMessage {\n            val message = EventMessage()\n            message.what = what\n            message.obj = obj\n            return message\n        }\n\n        fun obtain(tag: String, obj: Any): EventMessage {\n            val message = EventMessage()\n            message.tag = tag\n            message.obj = obj\n            return message\n        }\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/help/ExecutorService.kt",
    "content": "package io.legado.app.help\n\nimport java.util.concurrent.ExecutorService\nimport java.util.concurrent.Executors\n\nval globalExecutor: ExecutorService by lazy { Executors.newSingleThreadExecutor() }\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/help/IntentData.kt",
    "content": "package io.legado.app.help\n\nobject IntentData {\n\n    private val bigData: MutableMap<String, Any> = mutableMapOf()\n\n    @Synchronized\n    fun put(key: String, data: Any?): String {\n        data?.let {\n            bigData[key] = data\n        }\n        return key\n    }\n\n    @Synchronized\n    fun put(data: Any?): String {\n        val key = System.currentTimeMillis().toString()\n        data?.let {\n            bigData[key] = data\n        }\n        return key\n    }\n\n    @Suppress(\"UNCHECKED_CAST\")\n    @Synchronized\n    fun <T> get(key: String?): T? {\n        if (key == null) return null\n        val data = bigData[key]\n        bigData.remove(key)\n        return data as? T\n    }\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/help/IntentHelp.kt",
    "content": "package io.legado.app.help\n\nimport android.content.Context\nimport android.content.Intent\nimport android.net.Uri\nimport io.legado.app.R\nimport io.legado.app.utils.toastOnUi\nimport splitties.init.appCtx\n\n@Suppress(\"unused\")\nobject IntentHelp {\n\n    fun getBrowserIntent(url: String): Intent {\n        return getBrowserIntent(Uri.parse(url))\n    }\n\n    fun getBrowserIntent(uri: Uri): Intent {\n        val intent = Intent(Intent.ACTION_VIEW)\n        intent.data = uri\n        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)\n        if (intent.resolveActivity(appCtx.packageManager) == null) {\n            return Intent.createChooser(intent, \"请选择浏览器\")\n        }\n        return intent\n    }\n\n    fun openTTSSetting() {\n        //跳转到文字转语音设置界面\n        kotlin.runCatching {\n            val intent = Intent()\n            intent.action = \"com.android.settings.TTS_SETTINGS\"\n            intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK\n            appCtx.startActivity(intent)\n        }.onFailure {\n            appCtx.toastOnUi(R.string.tip_cannot_jump_setting_page)\n        }\n    }\n\n    fun toInstallUnknown(context: Context) {\n        kotlin.runCatching {\n            val intent = Intent()\n            intent.action = \"android.settings.MANAGE_UNKNOWN_APP_SOURCES\"\n            intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK\n            context.startActivity(intent)\n        }.onFailure {\n            context.toastOnUi(\"无法打开设置\")\n        }\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/help/JsEncodeUtils.kt",
    "content": "package io.legado.app.help\n\nimport android.util.Base64\nimport cn.hutool.crypto.digest.DigestUtil\nimport cn.hutool.crypto.digest.HMac\nimport cn.hutool.crypto.symmetric.SymmetricCrypto\nimport io.legado.app.help.crypto.AsymmetricCrypto\nimport io.legado.app.help.crypto.Sign\nimport io.legado.app.help.crypto.SymmetricCryptoAndroid\nimport io.legado.app.utils.MD5Utils\n\n\n/**\n * js加解密扩展类, 在js中通过java变量调用\n * 添加方法，请更新文档/legado/app/src/main/assets/help/JsHelp.md\n */\n@Suppress(\"unused\")\ninterface JsEncodeUtils {\n\n    fun md5Encode(str: String): String {\n        return MD5Utils.md5Encode(str)\n    }\n\n    fun md5Encode16(str: String): String {\n        return MD5Utils.md5Encode16(str)\n    }\n\n\n    //******************对称加密解密************************//\n\n    /**\n     * 在js中这样使用\n     * java.createSymmetricCrypto(transformation, key, iv).decrypt(data)\n     * java.createSymmetricCrypto(transformation, key, iv).decryptStr(data)\n\n     * java.createSymmetricCrypto(transformation, key, iv).encrypt(data)\n     * java.createSymmetricCrypto(transformation, key, iv).encryptBase64(data)\n     * java.createSymmetricCrypto(transformation, key, iv).encryptHex(data)\n     */\n\n    /* 调用SymmetricCrypto key为null时使用随机密钥*/\n    fun createSymmetricCrypto(\n        transformation: String,\n        key: ByteArray?,\n        iv: ByteArray?\n    ): SymmetricCrypto {\n        val symmetricCrypto = SymmetricCryptoAndroid(transformation, key)\n        return if (iv != null && iv.isNotEmpty()) symmetricCrypto.setIv(iv) else symmetricCrypto\n    }\n\n    fun createSymmetricCrypto(\n        transformation: String,\n        key: ByteArray\n    ): SymmetricCrypto {\n        return createSymmetricCrypto(transformation, key, null)\n    }\n\n    fun createSymmetricCrypto(\n        transformation: String,\n        key: String\n    ): SymmetricCrypto {\n        return createSymmetricCrypto(transformation, key, null)\n    }\n\n    fun createSymmetricCrypto(\n        transformation: String,\n        key: String,\n        iv: String?\n    ): SymmetricCrypto {\n        return createSymmetricCrypto(\n            transformation, key.encodeToByteArray(), iv?.encodeToByteArray()\n        )\n    }\n    //******************非对称加密解密************************//\n\n    /* keys都为null时使用随机密钥 */\n    fun createAsymmetricCrypto(\n        transformation: String\n    ): AsymmetricCrypto {\n        return AsymmetricCrypto(transformation)\n    }\n\n    //******************签名************************//\n    fun createSign(\n        algorithm: String\n    ): Sign {\n        return Sign(algorithm)\n    }\n    //******************对称加密解密old************************//\n\n    /////AES\n    /**\n     * AES 解码为 ByteArray\n     * @param str 传入的AES加密的数据\n     * @param key AES 解密的key\n     * @param transformation AES加密的方式\n     * @param iv ECB模式的偏移向量\n     */\n    @Deprecated(\n        \"过于繁琐弃用\",\n        ReplaceWith(\"createSymmetricCrypto(transformation, key, iv).decrypt(str)\")\n    )\n    fun aesDecodeToByteArray(\n        str: String, key: String, transformation: String, iv: String\n    ): ByteArray? {\n        return createSymmetricCrypto(transformation, key, iv).decrypt(str)\n    }\n\n    /**\n     * AES 解码为 String\n     * @param str 传入的AES加密的数据\n     * @param key AES 解密的key\n     * @param transformation AES加密的方式\n     * @param iv ECB模式的偏移向量\n     */\n    @Deprecated(\n        \"过于繁琐弃用\",\n        ReplaceWith(\"createSymmetricCrypto(transformation, key, iv).decryptStr(str)\")\n    )\n    fun aesDecodeToString(\n        str: String, key: String, transformation: String, iv: String\n    ): String? {\n        return createSymmetricCrypto(transformation, key, iv).decryptStr(str)\n    }\n\n    /**\n     * AES解码为String，算法参数经过Base64加密\n     *\n     * @param data 加密的字符串\n     * @param key Base64后的密钥\n     * @param mode 模式\n     * @param padding 补码方式\n     * @param iv Base64后的加盐\n     * @return 解密后的字符串\n     */\n    @Deprecated(\n        \"过于繁琐弃用\",\n        ReplaceWith(\"createSymmetricCrypto(transformation, key, iv).decryptStr(data)\")\n    )\n    fun aesDecodeArgsBase64Str(\n        data: String,\n        key: String,\n        mode: String,\n        padding: String,\n        iv: String\n    ): String? {\n        return createSymmetricCrypto(\n            \"AES/${mode}/${padding}\",\n            Base64.decode(key, Base64.NO_WRAP),\n            Base64.decode(iv, Base64.NO_WRAP)\n        ).decryptStr(data)\n    }\n\n    /**\n     * 已经base64的AES 解码为 ByteArray\n     * @param str 传入的AES Base64加密的数据\n     * @param key AES 解密的key\n     * @param transformation AES加密的方式\n     * @param iv ECB模式的偏移向量\n     */\n    @Deprecated(\n        \"过于繁琐弃用\",\n        ReplaceWith(\"createSymmetricCrypto(transformation, key, iv).decrypt(str)\")\n    )\n    fun aesBase64DecodeToByteArray(\n        str: String, key: String, transformation: String, iv: String\n    ): ByteArray? {\n        return createSymmetricCrypto(transformation, key, iv).decrypt(str)\n    }\n\n    /**\n     * 已经base64的AES 解码为 String\n     * @param str 传入的AES Base64加密的数据\n     * @param key AES 解密的key\n     * @param transformation AES加密的方式\n     * @param iv ECB模式的偏移向量\n     */\n    @Deprecated(\n        \"过于繁琐弃用\",\n        ReplaceWith(\"createSymmetricCrypto(transformation, key, iv).decryptStr(str)\")\n    )\n    fun aesBase64DecodeToString(\n        str: String, key: String, transformation: String, iv: String\n    ): String? {\n        return createSymmetricCrypto(transformation, key, iv).decryptStr(str)\n    }\n\n    /**\n     * 加密aes为ByteArray\n     * @param data 传入的原始数据\n     * @param key AES加密的key\n     * @param transformation AES加密的方式\n     * @param iv ECB模式的偏移向量\n     */\n    @Deprecated(\n        \"过于繁琐弃用\",\n        ReplaceWith(\"createSymmetricCrypto(transformation, key, iv).decrypt(data)\")\n    )\n    fun aesEncodeToByteArray(\n        data: String, key: String, transformation: String, iv: String\n    ): ByteArray? {\n        return createSymmetricCrypto(transformation, key, iv).encrypt(data)\n    }\n\n    /**\n     * 加密aes为String\n     * @param data 传入的原始数据\n     * @param key AES加密的key\n     * @param transformation AES加密的方式\n     * @param iv ECB模式的偏移向量\n     */\n    @Deprecated(\n        \"过于繁琐弃用\",\n        ReplaceWith(\"createSymmetricCrypto(transformation, key, iv).decryptStr(data)\")\n    )\n    fun aesEncodeToString(\n        data: String, key: String, transformation: String, iv: String\n    ): String? {\n        return createSymmetricCrypto(transformation, key, iv).decryptStr(data)\n    }\n\n    /**\n     * 加密aes后Base64化的ByteArray\n     * @param data 传入的原始数据\n     * @param key AES加密的key\n     * @param transformation AES加密的方式\n     * @param iv ECB模式的偏移向量\n     */\n    @Deprecated(\n        \"过于繁琐弃用\",\n        ReplaceWith(\"createSymmetricCrypto(transformation, key, iv).encryptBase64(data).toByteArray()\")\n    )\n    fun aesEncodeToBase64ByteArray(\n        data: String, key: String, transformation: String, iv: String\n    ): ByteArray? {\n        return createSymmetricCrypto(transformation, key, iv).encryptBase64(data).toByteArray()\n    }\n\n    /**\n     * 加密aes后Base64化的String\n     * @param data 传入的原始数据\n     * @param key AES加密的key\n     * @param transformation AES加密的方式\n     * @param iv ECB模式的偏移向量\n     */\n    @Deprecated(\n        \"过于繁琐弃用\",\n        ReplaceWith(\"createSymmetricCrypto(transformation, key, iv).encryptBase64(data)\")\n    )\n    fun aesEncodeToBase64String(\n        data: String, key: String, transformation: String, iv: String\n    ): String? {\n        return createSymmetricCrypto(transformation, key, iv).encryptBase64(data)\n    }\n\n\n    /**\n     * AES加密并转为Base64，算法参数经过Base64加密\n     *\n     * @param data 被加密的字符串\n     * @param key Base64后的密钥\n     * @param mode 模式\n     * @param padding 补码方式\n     * @param iv Base64后的加盐\n     * @return 加密后的Base64\n     */\n    @Deprecated(\n        \"过于繁琐弃用\",\n        ReplaceWith(\"createSymmetricCrypto(transformation, key, iv).encryptBase64(data)\")\n    )\n    fun aesEncodeArgsBase64Str(\n        data: String,\n        key: String,\n        mode: String,\n        padding: String,\n        iv: String\n    ): String? {\n        return createSymmetricCrypto(\"AES/${mode}/${padding}\", key, iv).encryptBase64(data)\n    }\n\n    /////DES\n    @Deprecated(\n        \"过于繁琐弃用\",\n        ReplaceWith(\"createSymmetricCrypto(transformation, key, iv).decryptStr(data)\")\n    )\n    fun desDecodeToString(\n        data: String, key: String, transformation: String, iv: String\n    ): String? {\n        return createSymmetricCrypto(transformation, key, iv).decryptStr(data)\n    }\n\n    @Deprecated(\n        \"过于繁琐弃用\",\n        ReplaceWith(\"createSymmetricCrypto(transformation, key, iv).decryptStr(data)\")\n    )\n    fun desBase64DecodeToString(\n        data: String, key: String, transformation: String, iv: String\n    ): String? {\n        return createSymmetricCrypto(transformation, key, iv).decryptStr(data)\n    }\n\n    @Deprecated(\n        \"过于繁琐弃用\",\n        ReplaceWith(\"createSymmetricCrypto(transformation, key, iv).encrypt(data)\")\n    )\n    fun desEncodeToString(\n        data: String, key: String, transformation: String, iv: String\n    ): String? {\n        return String(createSymmetricCrypto(transformation, key, iv).encrypt(data))\n    }\n\n    @Deprecated(\n        \"过于繁琐弃用\",\n        ReplaceWith(\"createSymmetricCrypto(transformation, key, iv).encryptBase64(data)\")\n    )\n    fun desEncodeToBase64String(\n        data: String, key: String, transformation: String, iv: String\n    ): String? {\n        return createSymmetricCrypto(transformation, key, iv).encryptBase64(data)\n    }\n\n    //////3DES\n    /**\n     * 3DES解密\n     *\n     * @param data 加密的字符串\n     * @param key 密钥\n     * @param mode 模式\n     * @param padding 补码方式\n     * @param iv 加盐\n     * @return 解密后的字符串\n     */\n    @Deprecated(\n        \"过于繁琐弃用\",\n        ReplaceWith(\"createSymmetricCrypto(transformation, key, iv).decryptStr(data)\")\n    )\n    fun tripleDESDecodeStr(\n        data: String,\n        key: String,\n        mode: String,\n        padding: String,\n        iv: String\n    ): String? {\n        return createSymmetricCrypto(\"DESede/${mode}/${padding}\", key, iv).decryptStr(data)\n    }\n\n    /**\n     * 3DES解密，算法参数经过Base64加密\n     *\n     * @param data 加密的字符串\n     * @param key Base64后的密钥\n     * @param mode 模式\n     * @param padding 补码方式\n     * @param iv Base64后的加盐\n     * @return 解密后的字符串\n     */\n    @Deprecated(\n        \"过于繁琐弃用\",\n        ReplaceWith(\"createSymmetricCrypto(transformation, key, iv).decryptStr(data)\")\n    )\n    fun tripleDESDecodeArgsBase64Str(\n        data: String,\n        key: String,\n        mode: String,\n        padding: String,\n        iv: String\n    ): String? {\n        return createSymmetricCrypto(\n            \"DESede/${mode}/${padding}\",\n            Base64.decode(key, Base64.NO_WRAP),\n            iv.encodeToByteArray()\n        ).decryptStr(data)\n    }\n\n\n    /**\n     * 3DES加密并转为Base64\n     *\n     * @param data 被加密的字符串\n     * @param key 密钥\n     * @param mode 模式\n     * @param padding 补码方式\n     * @param iv 加盐\n     * @return 加密后的Base64\n     */\n    @Deprecated(\n        \"过于繁琐弃用\",\n        ReplaceWith(\"createSymmetricCrypto(transformation, key, iv).encryptBase64(data)\")\n    )\n    fun tripleDESEncodeBase64Str(\n        data: String,\n        key: String,\n        mode: String,\n        padding: String,\n        iv: String\n    ): String? {\n        return createSymmetricCrypto(\"DESede/${mode}/${padding}\", key, iv)\n            .encryptBase64(data)\n    }\n\n    /**\n     * 3DES加密并转为Base64，算法参数经过Base64加密\n     *\n     * @param data 被加密的字符串\n     * @param key Base64后的密钥\n     * @param mode 模式\n     * @param padding 补码方式\n     * @param iv Base64后的加盐\n     * @return 加密后的Base64\n     */\n    @Deprecated(\n        \"过于繁琐弃用\",\n        ReplaceWith(\"createSymmetricCrypto(transformation, key, iv).encryptBase64(data)\")\n    )\n    fun tripleDESEncodeArgsBase64Str(\n        data: String,\n        key: String,\n        mode: String,\n        padding: String,\n        iv: String\n    ): String? {\n        return createSymmetricCrypto(\n            \"DESede/${mode}/${padding}\",\n            Base64.decode(key, Base64.NO_WRAP),\n            iv.encodeToByteArray()\n        ).encryptBase64(data)\n    }\n\n//******************消息摘要/散列消息鉴别码************************//\n\n    /**\n     * 生成摘要，并转为16进制字符串\n     *\n     * @param data 被摘要数据\n     * @param algorithm 签名算法\n     * @return 16进制字符串\n     */\n    fun digestHex(\n        data: String,\n        algorithm: String,\n    ): String {\n        return DigestUtil.digester(algorithm).digestHex(data)\n    }\n\n    /**\n     * 生成摘要，并转为Base64字符串\n     *\n     * @param data 被摘要数据\n     * @param algorithm 签名算法\n     * @return Base64字符串\n     */\n    fun digestBase64Str(\n        data: String,\n        algorithm: String,\n    ): String {\n        return Base64.encodeToString(DigestUtil.digester(algorithm).digest(data), Base64.NO_WRAP)\n    }\n\n    /**\n     * 生成散列消息鉴别码，并转为16进制字符串\n     *\n     * @param data 被摘要数据\n     * @param algorithm 签名算法\n     * @param key 密钥\n     * @return 16进制字符串\n     */\n    @Suppress(\"FunctionName\")\n    fun HMacHex(\n        data: String,\n        algorithm: String,\n        key: String\n    ): String {\n        return HMac(algorithm, key.toByteArray()).digestHex(data)\n    }\n\n    /**\n     * 生成散列消息鉴别码，并转为Base64字符串\n     *\n     * @param data 被摘要数据\n     * @param algorithm 签名算法\n     * @param key 密钥\n     * @return Base64字符串\n     */\n    @Suppress(\"FunctionName\")\n    fun HMacBase64(\n        data: String,\n        algorithm: String,\n        key: String\n    ): String {\n        return Base64.encodeToString(\n            HMac(algorithm, key.toByteArray()).digest(data),\n            Base64.NO_WRAP\n        )\n    }\n\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/help/JsExtensions.kt",
    "content": "package io.legado.app.help\n\nimport android.webkit.WebSettings\nimport androidx.annotation.Keep\nimport cn.hutool.core.codec.Base64\nimport cn.hutool.core.util.HexUtil\nimport com.script.rhino.rhinoContext\nimport com.script.rhino.rhinoContextOrNull\nimport io.legado.app.constant.AppConst\nimport io.legado.app.constant.AppConst.dateFormat\nimport io.legado.app.constant.AppLog\nimport io.legado.app.constant.AppPattern\nimport io.legado.app.data.entities.BaseSource\nimport io.legado.app.exception.NoStackTraceException\nimport io.legado.app.help.config.AppConfig\nimport io.legado.app.help.http.BackstageWebView\nimport io.legado.app.help.http.CookieManager.cookieJarHeader\nimport io.legado.app.help.http.CookieStore\nimport io.legado.app.help.http.SSLHelper\nimport io.legado.app.help.http.StrResponse\nimport io.legado.app.help.source.SourceVerificationHelp\nimport io.legado.app.help.source.getSourceType\nimport io.legado.app.model.Debug\nimport io.legado.app.model.analyzeRule.AnalyzeUrl\nimport io.legado.app.model.analyzeRule.QueryTTF\nimport io.legado.app.ui.association.OpenUrlConfirmActivity\nimport io.legado.app.utils.ArchiveUtils\nimport io.legado.app.utils.ChineseUtils\nimport io.legado.app.utils.EncoderUtils\nimport io.legado.app.utils.EncodingDetect\nimport io.legado.app.utils.FileUtils\nimport io.legado.app.utils.GSON\nimport io.legado.app.utils.HtmlFormatter\nimport io.legado.app.utils.JsURL\nimport io.legado.app.utils.MD5Utils\nimport io.legado.app.utils.StringUtils\nimport io.legado.app.utils.UrlUtil\nimport io.legado.app.utils.compress.LibArchiveUtils\nimport io.legado.app.utils.createFileReplace\nimport io.legado.app.utils.externalCache\nimport io.legado.app.utils.fromJsonObject\nimport io.legado.app.utils.isAbsUrl\nimport io.legado.app.utils.isMainThread\nimport io.legado.app.utils.longToastOnUi\nimport io.legado.app.utils.mapAsync\nimport io.legado.app.utils.stackTraceStr\nimport io.legado.app.utils.startActivity\nimport io.legado.app.utils.toStringArray\nimport io.legado.app.utils.toastOnUi\nimport kotlinx.coroutines.Dispatchers.IO\nimport kotlinx.coroutines.flow.asFlow\nimport kotlinx.coroutines.flow.flowOn\nimport kotlinx.coroutines.flow.toList\nimport kotlinx.coroutines.runBlocking\nimport okio.use\nimport org.jsoup.Connection\nimport org.jsoup.Jsoup\nimport splitties.init.appCtx\nimport java.io.ByteArrayInputStream\nimport java.io.ByteArrayOutputStream\nimport java.io.File\nimport java.net.URLEncoder\nimport java.nio.charset.Charset\nimport java.security.MessageDigest\nimport java.text.SimpleDateFormat\nimport java.util.Date\nimport java.util.Locale\nimport java.util.SimpleTimeZone\nimport java.util.UUID\nimport java.util.zip.ZipEntry\nimport java.util.zip.ZipInputStream\nimport kotlin.coroutines.CoroutineContext\nimport kotlin.coroutines.EmptyCoroutineContext\n\n/**\n * js扩展类, 在js中通过java变量调用\n * 添加方法，请更新文档/legado/app/src/main/assets/help/JsHelp.md\n * 所有对于文件的读写删操作都是相对路径,只能操作阅读缓存内的文件\n * /android/data/{package}/cache/...\n */\n@Keep\n@Suppress(\"unused\")\ninterface JsExtensions : JsEncodeUtils {\n\n    fun getSource(): BaseSource?\n\n    private val context: CoroutineContext\n        get() = rhinoContext.coroutineContext ?: EmptyCoroutineContext\n\n    /**\n     * 访问网络,返回String\n     */\n    fun ajax(url: Any): String? {\n        val urlStr = if (url is List<*>) {\n            url.firstOrNull().toString()\n        } else {\n            url.toString()\n        }\n        val analyzeUrl = AnalyzeUrl(urlStr, source = getSource(), coroutineContext = context)\n        return kotlin.runCatching {\n            analyzeUrl.getStrResponse().body\n        }.onFailure {\n            rhinoContext.ensureActive()\n            AppLog.put(\"ajax(${urlStr}) error\\n${it.localizedMessage}\", it)\n        }.getOrElse {\n            it.stackTraceStr\n        }\n    }\n\n    /**\n     * 并发访问网络\n     */\n    fun ajaxAll(urlList: Array<String>): Array<StrResponse> {\n        return runBlocking(context) {\n            urlList.asFlow().mapAsync(AppConfig.threadCount) { url ->\n                val analyzeUrl = AnalyzeUrl(\n                    url,\n                    source = getSource(),\n                    coroutineContext = coroutineContext\n                )\n                analyzeUrl.getStrResponseAwait()\n            }.flowOn(IO).toList().toTypedArray()\n        }\n    }\n\n    /**\n     * 访问网络,返回Response<String>\n     */\n    fun connect(urlStr: String): StrResponse {\n        val analyzeUrl = AnalyzeUrl(\n            urlStr,\n            source = getSource(),\n            coroutineContext = context\n        )\n        return kotlin.runCatching {\n            analyzeUrl.getStrResponse()\n        }.onFailure {\n            rhinoContext.ensureActive()\n            AppLog.put(\"connect(${urlStr}) error\\n${it.localizedMessage}\", it)\n        }.getOrElse {\n            StrResponse(analyzeUrl.url, it.stackTraceStr)\n        }\n    }\n\n    fun connect(urlStr: String, header: String?): StrResponse {\n        val headerMap = GSON.fromJsonObject<Map<String, String>>(header).getOrNull()\n        val analyzeUrl = AnalyzeUrl(\n            urlStr,\n            headerMapF = headerMap,\n            source = getSource(),\n            coroutineContext = context\n        )\n        return kotlin.runCatching {\n            analyzeUrl.getStrResponse()\n        }.onFailure {\n            rhinoContext.ensureActive()\n            AppLog.put(\"ajax($urlStr,$header) error\\n${it.localizedMessage}\", it)\n        }.getOrElse {\n            StrResponse(analyzeUrl.url, it.stackTraceStr)\n        }\n    }\n\n    /**\n     * 使用webView访问网络\n     * @param html 直接用webView载入的html, 如果html为空直接访问url\n     * @param url html内如果有相对路径的资源不传入url访问不了\n     * @param js 用来取返回值的js语句, 没有就返回整个源代码\n     * @return 返回js获取的内容\n     */\n    fun webView(html: String?, url: String?, js: String?): String? {\n        if (isMainThread) {\n            error(\"webView must be called on a background thread\")\n        }\n        return runBlocking(context) {\n            BackstageWebView(\n                url = url,\n                html = html,\n                javaScript = js,\n                headerMap = getSource()?.getHeaderMap(true),\n                tag = getSource()?.getKey()\n            ).getStrResponse().body\n        }\n    }\n\n    /**\n     * 使用webView获取资源url\n     */\n    fun webViewGetSource(html: String?, url: String?, js: String?, sourceRegex: String): String? {\n        if (isMainThread) {\n            error(\"webViewGetSource must be called on a background thread\")\n        }\n        return runBlocking(context) {\n            BackstageWebView(\n                url = url,\n                html = html,\n                javaScript = js,\n                headerMap = getSource()?.getHeaderMap(true),\n                tag = getSource()?.getKey(),\n                sourceRegex = sourceRegex\n            ).getStrResponse().body\n        }\n    }\n\n    /**\n     * 使用webView获取跳转url\n     */\n    fun webViewGetOverrideUrl(\n        html: String?,\n        url: String?,\n        js: String?,\n        overrideUrlRegex: String\n    ): String? {\n        if (isMainThread) {\n            error(\"webViewGetOverrideUrl must be called on a background thread\")\n        }\n        return runBlocking(context) {\n            BackstageWebView(\n                url = url,\n                html = html,\n                javaScript = js,\n                headerMap = getSource()?.getHeaderMap(true),\n                tag = getSource()?.getKey(),\n                overrideUrlRegex = overrideUrlRegex\n            ).getStrResponse().body\n        }\n    }\n\n    /**\n     * 使用内置浏览器打开链接，手动验证网站防爬\n     * @param url 要打开的链接\n     * @param title 浏览器页面的标题\n     */\n    fun startBrowser(url: String, title: String) {\n        rhinoContext.ensureActive()\n        SourceVerificationHelp.startBrowser(getSource(), url, title)\n    }\n\n    /**\n     * 使用内置浏览器打开链接，并等待网页结果\n     */\n    fun startBrowserAwait(url: String, title: String, refetchAfterSuccess: Boolean): StrResponse {\n        rhinoContext.ensureActive()\n        val body = SourceVerificationHelp.getVerificationResult(\n            getSource(), url, title, true, refetchAfterSuccess\n        )\n        return StrResponse(url, body)\n    }\n\n    fun startBrowserAwait(url: String, title: String): StrResponse {\n        return startBrowserAwait(url, title, true)\n    }\n\n    /**\n     * 打开图片验证码对话框，等待返回验证结果\n     */\n    fun getVerificationCode(imageUrl: String): String {\n        rhinoContext.ensureActive()\n        return SourceVerificationHelp.getVerificationResult(getSource(), imageUrl, \"\", false)\n    }\n\n    /**\n     * 可从网络，本地文件(阅读私有数据目录相对路径)导入JavaScript脚本\n     */\n    fun importScript(path: String): String {\n        val result = when {\n            path.startsWith(\"http\") -> cacheFile(path)\n            else -> readTxtFile(path)\n        }\n        if (result.isBlank()) throw NoStackTraceException(\"$path 内容获取失败或者为空\")\n        return result\n    }\n\n    /**\n     * 缓存以文本方式保存的文件 如.js .txt等\n     * @param urlStr 网络文件的链接\n     * @return 返回缓存后的文件内容\n     */\n    fun cacheFile(urlStr: String): String {\n        return cacheFile(urlStr, 0)\n    }\n\n    /**\n     * 缓存以文本方式保存的文件 如.js .txt等\n     * @param saveTime 缓存时间，单位：秒\n     */\n    fun cacheFile(urlStr: String, saveTime: Int): String {\n        val key = md5Encode16(urlStr)\n        val cachePath = CacheManager.get(key)\n        return if (\n            cachePath.isNullOrBlank() ||\n            !getFile(cachePath).exists()\n        ) {\n            val path = downloadFile(urlStr)\n            log(\"首次下载 $urlStr >> $path\")\n            CacheManager.put(key, path, saveTime)\n            readTxtFile(path)\n        } else {\n            readTxtFile(cachePath)\n        }\n    }\n\n    /**\n     *js实现读取cookie\n     */\n    fun getCookie(tag: String): String {\n        return getCookie(tag, null)\n    }\n\n    fun getCookie(tag: String, key: String?): String {\n        return if (key != null) {\n            CookieStore.getKey(tag, key)\n        } else {\n            CookieStore.getCookie(tag)\n        }\n    }\n\n    /**\n     * 下载文件\n     * @param url 下载地址:可带参数type\n     * @return 下载的文件相对路径\n     */\n    fun downloadFile(url: String): String {\n        rhinoContext.ensureActive()\n        val analyzeUrl = AnalyzeUrl(url, source = getSource(), coroutineContext = context)\n        val type = analyzeUrl.type ?: UrlUtil.getSuffix(url)\n        val path = FileUtils.getPath(\n            File(FileUtils.getCachePath()),\n            \"${MD5Utils.md5Encode16(url)}.${type}\"\n        )\n        val file = File(path)\n        file.delete()\n        analyzeUrl.getInputStream().use { iStream ->\n            file.createFileReplace()\n            try {\n                file.outputStream().buffered().use { oStream ->\n                    iStream.copyTo(oStream)\n                }\n            } catch (e: Throwable) {\n                file.delete()\n                throw e\n            }\n        }\n        return path.substring(FileUtils.getCachePath().length)\n    }\n\n\n    /**\n     * 实现16进制字符串转文件\n     * @param content 需要转成文件的16进制字符串\n     * @param url 通过url里的参数来判断文件类型\n     * @return 相对路径\n     */\n    @Deprecated(\n        \"Deprecated\",\n        ReplaceWith(\"downloadFile(url)\")\n    )\n    fun downloadFile(content: String, url: String): String {\n        rhinoContext.ensureActive()\n        val type = AnalyzeUrl(url, source = getSource(), coroutineContext = context).type\n            ?: return \"\"\n        val path = FileUtils.getPath(\n            FileUtils.createFolderIfNotExist(FileUtils.getCachePath()),\n            \"${MD5Utils.md5Encode16(url)}.${type}\"\n        )\n        val file = File(path)\n        file.createFileReplace()\n        HexUtil.decodeHex(content).let {\n            if (it.isNotEmpty()) {\n                file.writeBytes(it)\n            }\n        }\n        return path.substring(FileUtils.getCachePath().length)\n    }\n\n    /**\n     * js实现重定向拦截,网络访问get\n     */\n    fun get(urlStr: String, headers: Map<String, String>): Connection.Response {\n        val requestHeaders = if (getSource()?.enabledCookieJar == true) {\n            headers.toMutableMap().apply { put(cookieJarHeader, \"1\") }\n        } else headers\n        val rateLimiter = ConcurrentRateLimiter(getSource())\n        val response = rateLimiter.withLimitBlocking {\n            rhinoContext.ensureActive()\n            Jsoup.connect(urlStr)\n                .sslSocketFactory(SSLHelper.unsafeSSLSocketFactory)\n                .ignoreContentType(true)\n                .followRedirects(false)\n                .headers(requestHeaders)\n                .method(Connection.Method.GET)\n                .execute()\n        }\n        return response\n    }\n\n    /**\n     * js实现重定向拦截,网络访问head,不返回Response Body更省流量\n     */\n    fun head(urlStr: String, headers: Map<String, String>): Connection.Response {\n        val requestHeaders = if (getSource()?.enabledCookieJar == true) {\n            headers.toMutableMap().apply { put(cookieJarHeader, \"1\") }\n        } else headers\n        val rateLimiter = ConcurrentRateLimiter(getSource())\n        val response = rateLimiter.withLimitBlocking {\n            rhinoContext.ensureActive()\n            Jsoup.connect(urlStr)\n                .sslSocketFactory(SSLHelper.unsafeSSLSocketFactory)\n                .ignoreContentType(true)\n                .followRedirects(false)\n                .headers(requestHeaders)\n                .method(Connection.Method.HEAD)\n                .execute()\n        }\n        return response\n    }\n\n    /**\n     * 网络访问post\n     */\n    fun post(urlStr: String, body: String, headers: Map<String, String>): Connection.Response {\n        val requestHeaders = if (getSource()?.enabledCookieJar == true) {\n            headers.toMutableMap().apply { put(cookieJarHeader, \"1\") }\n        } else headers\n        val rateLimiter = ConcurrentRateLimiter(getSource())\n        val response = rateLimiter.withLimitBlocking {\n            rhinoContext.ensureActive()\n            Jsoup.connect(urlStr)\n                .sslSocketFactory(SSLHelper.unsafeSSLSocketFactory)\n                .ignoreContentType(true)\n                .followRedirects(false)\n                .requestBody(body)\n                .headers(requestHeaders)\n                .method(Connection.Method.POST)\n                .execute()\n        }\n        return response\n    }\n\n    /* Str转ByteArray */\n    fun strToBytes(str: String): ByteArray {\n        return str.toByteArray(charset(\"UTF-8\"))\n    }\n\n    fun strToBytes(str: String, charset: String): ByteArray {\n        return str.toByteArray(charset(charset))\n    }\n\n    /* ByteArray转Str */\n    fun bytesToStr(bytes: ByteArray): String {\n        return String(bytes, charset(\"UTF-8\"))\n    }\n\n    fun bytesToStr(bytes: ByteArray, charset: String): String {\n        return String(bytes, charset(charset))\n    }\n\n    /**\n     * js实现base64解码,不能删\n     */\n    fun base64Decode(str: String?): String {\n        return Base64.decodeStr(str)\n    }\n\n    fun base64Decode(str: String?, charset: String): String {\n        return Base64.decodeStr(str, charset(charset))\n    }\n\n    fun base64Decode(str: String, flags: Int): String {\n        return EncoderUtils.base64Decode(str, flags)\n    }\n\n    fun base64DecodeToByteArray(str: String?): ByteArray? {\n        if (str.isNullOrBlank()) {\n            return null\n        }\n        return EncoderUtils.base64DecodeToByteArray(str, 0)\n    }\n\n    fun base64DecodeToByteArray(str: String?, flags: Int): ByteArray? {\n        if (str.isNullOrBlank()) {\n            return null\n        }\n        return EncoderUtils.base64DecodeToByteArray(str, flags)\n    }\n\n    fun base64Encode(str: String): String? {\n        return EncoderUtils.base64Encode(str, 2)\n    }\n\n    fun base64Encode(str: String, flags: Int): String? {\n        return EncoderUtils.base64Encode(str, flags)\n    }\n\n    /* HexString 解码为字节数组 */\n    fun hexDecodeToByteArray(hex: String): ByteArray? {\n        return HexUtil.decodeHex(hex)\n    }\n\n    /* hexString 解码为utf8String*/\n    fun hexDecodeToString(hex: String): String? {\n        return HexUtil.decodeHexStr(hex)\n    }\n\n    /* utf8 编码为hexString */\n    fun hexEncodeToString(utf8: String): String? {\n        return HexUtil.encodeHexStr(utf8)\n    }\n\n    /**\n     * 格式化时间\n     */\n    fun timeFormatUTC(time: Long, format: String, sh: Int): String? {\n        val utc = SimpleTimeZone(sh, \"UTC\")\n        return SimpleDateFormat(format, Locale.getDefault()).run {\n            timeZone = utc\n            format(Date(time))\n        }\n    }\n\n    /**\n     * 时间格式化\n     */\n    fun timeFormat(time: Long): String {\n        return dateFormat.format(Date(time))\n    }\n\n    fun encodeURI(str: String): String {\n        return try {\n            URLEncoder.encode(str, \"UTF-8\")\n        } catch (e: Exception) {\n            \"\"\n        }\n    }\n\n    fun encodeURI(str: String, enc: String): String {\n        return try {\n            URLEncoder.encode(str, enc)\n        } catch (e: Exception) {\n            \"\"\n        }\n    }\n\n    fun htmlFormat(str: String): String {\n        return HtmlFormatter.formatKeepImg(str)\n    }\n\n    fun t2s(text: String): String {\n        return ChineseUtils.t2s(text)\n    }\n\n    fun s2t(text: String): String {\n        return ChineseUtils.s2t(text)\n    }\n\n    fun getWebViewUA(): String {\n        return WebSettings.getDefaultUserAgent(appCtx)\n    }\n\n//****************文件操作******************//\n\n    /**\n     * 获取本地文件\n     * @param path 相对路径\n     * @return File\n     */\n    fun getFile(path: String): File {\n        val cachePath = appCtx.externalCache.absolutePath\n        val aPath = if (path.startsWith(File.separator)) {\n            cachePath + path\n        } else {\n            cachePath + File.separator + path\n        }\n        val file = File(aPath)\n        val safePath = appCtx.externalCache.parent!!\n        if (!file.canonicalPath.startsWith(safePath)) {\n            throw SecurityException(\"非法路径\")\n        }\n        return file\n    }\n\n    fun readFile(path: String): ByteArray? {\n        val file = getFile(path)\n        if (file.exists()) {\n            return file.readBytes()\n        }\n        return null\n    }\n\n    fun readTxtFile(path: String): String {\n        val file = getFile(path)\n        if (file.exists()) {\n            val charsetName = EncodingDetect.getEncode(file)\n            return String(file.readBytes(), charset(charsetName))\n        }\n        return \"\"\n    }\n\n    fun readTxtFile(path: String, charsetName: String): String {\n        val file = getFile(path)\n        if (file.exists()) {\n            return String(file.readBytes(), charset(charsetName))\n        }\n        return \"\"\n    }\n\n    /**\n     * 删除本地文件\n     */\n    fun deleteFile(path: String): Boolean {\n        val file = getFile(path)\n        return FileUtils.delete(file, true)\n    }\n\n    /**\n     * js实现Zip压缩文件解压\n     * @param zipPath 相对路径\n     * @return 相对路径\n     */\n    fun unzipFile(zipPath: String): String {\n        return unArchiveFile(zipPath)\n    }\n\n    /**\n     * js实现7Zip压缩文件解压\n     * @param zipPath 相对路径\n     * @return 相对路径\n     */\n    fun un7zFile(zipPath: String): String {\n        return unArchiveFile(zipPath)\n    }\n\n    /**\n     * js实现Rar压缩文件解压\n     * @param zipPath 相对路径\n     * @return 相对路径\n     */\n    fun unrarFile(zipPath: String): String {\n        return unArchiveFile(zipPath)\n    }\n\n    /**\n     * js实现压缩文件解压\n     * @param zipPath 相对路径\n     * @return 相对路径\n     */\n    fun unArchiveFile(zipPath: String): String {\n        if (zipPath.isEmpty()) return \"\"\n        val zipFile = getFile(zipPath)\n        return ArchiveUtils.deCompress(zipFile.absolutePath).let {\n            ArchiveUtils.TEMP_FOLDER_NAME + File.separator + MD5Utils.md5Encode16(zipFile.name)\n        }\n    }\n\n    /**\n     * js实现文件夹内所有文本文件读取\n     * @param path 文件夹相对路径\n     * @return 所有文件字符串换行连接\n     */\n    fun getTxtInFolder(path: String): String {\n        if (path.isEmpty()) return \"\"\n        val folder = getFile(path)\n        val contents = StringBuilder()\n        folder.listFiles().let {\n            if (it != null) {\n                for (f in it) {\n                    val charsetName = EncodingDetect.getEncode(f)\n                    contents.append(String(f.readBytes(), charset(charsetName)))\n                        .append(\"\\n\")\n                }\n                contents.deleteCharAt(contents.length - 1)\n            }\n        }\n        FileUtils.delete(folder.absolutePath)\n        return contents.toString()\n    }\n\n    /**\n     * 获取网络zip文件里面的数据\n     * @param url zip文件的链接或十六进制字符串\n     * @param path 所需获取文件在zip内的路径\n     * @return zip指定文件的数据\n     */\n    fun getZipStringContent(url: String, path: String): String {\n        val byteArray = getZipByteArrayContent(url, path) ?: return \"\"\n        val charsetName = EncodingDetect.getEncode(byteArray)\n        return String(byteArray, Charset.forName(charsetName))\n    }\n\n    fun getZipStringContent(url: String, path: String, charsetName: String): String {\n        val byteArray = getZipByteArrayContent(url, path) ?: return \"\"\n        return String(byteArray, Charset.forName(charsetName))\n    }\n\n    /**\n     * 获取网络zip文件里面的数据\n     * @param url zip文件的链接或十六进制字符串\n     * @param path 所需获取文件在zip内的路径\n     * @return zip指定文件的数据\n     */\n    fun getRarStringContent(url: String, path: String): String {\n        val byteArray = getRarByteArrayContent(url, path) ?: return \"\"\n        val charsetName = EncodingDetect.getEncode(byteArray)\n        return String(byteArray, Charset.forName(charsetName))\n    }\n\n    fun getRarStringContent(url: String, path: String, charsetName: String): String {\n        val byteArray = getRarByteArrayContent(url, path) ?: return \"\"\n        return String(byteArray, Charset.forName(charsetName))\n    }\n\n    /**\n     * 获取网络7zip文件里面的数据\n     * @param url 7zip文件的链接或十六进制字符串\n     * @param path 所需获取文件在7zip内的路径\n     * @return zip指定文件的数据\n     */\n    fun get7zStringContent(url: String, path: String): String {\n        val byteArray = get7zByteArrayContent(url, path) ?: return \"\"\n        val charsetName = EncodingDetect.getEncode(byteArray)\n        return String(byteArray, Charset.forName(charsetName))\n    }\n\n    fun get7zStringContent(url: String, path: String, charsetName: String): String {\n        val byteArray = get7zByteArrayContent(url, path) ?: return \"\"\n        return String(byteArray, Charset.forName(charsetName))\n    }\n\n    /**\n     * 获取网络zip文件里面的数据\n     * @param url zip文件的链接或十六进制字符串\n     * @param path 所需获取文件在zip内的路径\n     * @return zip指定文件的数据\n     */\n    fun getZipByteArrayContent(url: String, path: String): ByteArray? {\n        val bytes = if (url.isAbsUrl()) {\n            AnalyzeUrl(url, source = getSource(), coroutineContext = context).getByteArray()\n        } else {\n            HexUtil.decodeHex(url)\n        }\n        val bos = ByteArrayOutputStream()\n        ZipInputStream(ByteArrayInputStream(bytes)).use { zis ->\n            var entry: ZipEntry\n            while (zis.nextEntry.also { entry = it } != null) {\n                if (entry.name.equals(path)) {\n                    zis.use { it.copyTo(bos) }\n                    return bos.toByteArray()\n                }\n                entry = zis.nextEntry\n            }\n        }\n\n        log(\"getZipContent 未发现内容\")\n        return null\n    }\n\n    /**\n     * 获取网络Rar文件里面的数据\n     * @param url Rar文件的链接或十六进制字符串\n     * @param path 所需获取文件在Rar内的路径\n     * @return Rar指定文件的数据\n     */\n    fun getRarByteArrayContent(url: String, path: String): ByteArray? {\n        val bytes = if (url.isAbsUrl()) {\n            AnalyzeUrl(url, source = getSource(), coroutineContext = context).getByteArray()\n        } else {\n            HexUtil.decodeHex(url)\n        }\n\n        return ByteArrayInputStream(bytes).use {\n            LibArchiveUtils.getByteArrayContent(it, path)\n        }\n    }\n\n    /**\n     * 获取网络7zip文件里面的数据\n     * @param url 7zip文件的链接或十六进制字符串\n     * @param path 所需获取文件在7zip内的路径\n     * @return 7zip指定文件的数据\n     */\n    fun get7zByteArrayContent(url: String, path: String): ByteArray? {\n        val bytes = if (url.isAbsUrl()) {\n            AnalyzeUrl(url, source = getSource(), coroutineContext = context).getByteArray()\n        } else {\n            HexUtil.decodeHex(url)\n        }\n\n        return ByteArrayInputStream(bytes).use {\n            LibArchiveUtils.getByteArrayContent(it, path)\n        }\n    }\n\n\n//******************文件操作************************//\n\n    /**\n     * 解析字体Base64数据,返回字体解析类\n     */\n    @Deprecated(\n        \"Deprecated\",\n        ReplaceWith(\"queryTTF(data)\")\n    )\n    fun queryBase64TTF(data: String?): QueryTTF? {\n        log(\"queryBase64TTF(String)方法已过时,并将在未来删除；请无脑使用queryTTF(Any)替代，新方法支持传入 url、本地文件、base64、ByteArray 自动判断&自动缓存，特殊情况需禁用缓存请传入第二可选参数false:Boolean\")\n        return queryTTF(data)\n    }\n\n    /**\n     * 返回字体解析类\n     * @param data 支持url,本地文件,base64,ByteArray,自动判断,自动缓存\n     * @param useCache 可选开关缓存,不传入该值默认开启缓存\n     */\n    @OptIn(ExperimentalStdlibApi::class)\n    fun queryTTF(data: Any?, useCache: Boolean): QueryTTF? {\n        try {\n            var key: String? = null\n            var qTTF: QueryTTF?\n            when (data) {\n                is String -> {\n                    if (useCache) {\n                        key = MessageDigest.getInstance(\"SHA-256\").digest(data.toByteArray())\n                            .toHexString()\n                        qTTF = AppCacheManager.getQueryTTF(key)\n                        if (qTTF != null) return qTTF\n                    }\n                    val font: ByteArray? = when {\n                        data.isAbsUrl() -> AnalyzeUrl(\n                            data,\n                            source = getSource(),\n                            coroutineContext = context\n                        ).getByteArray()\n\n                        else -> base64DecodeToByteArray(data)\n                    }\n                    font ?: return null\n                    qTTF = QueryTTF(font)\n                }\n\n                is ByteArray -> {\n                    if (useCache) {\n                        key = MessageDigest.getInstance(\"SHA-256\").digest(data).toHexString()\n                        qTTF = AppCacheManager.getQueryTTF(key)\n                        if (qTTF != null) return qTTF\n                    }\n                    qTTF = QueryTTF(data)\n                }\n\n                else -> return null\n            }\n            if (key != null) AppCacheManager.put(key, qTTF)\n            return qTTF\n        } catch (e: Exception) {\n            AppLog.put(\"[queryTTF] 获取字体处理类出错\", e)\n            throw e\n        }\n    }\n\n    fun queryTTF(data: Any?): QueryTTF? {\n        return queryTTF(data, true)\n    }\n\n    /**\n     * @param text 包含错误字体的内容\n     * @param errorQueryTTF 错误的字体\n     * @param correctQueryTTF 正确的字体\n     * @param filter 删除 errorQueryTTF 中不存在的字符\n     */\n    fun replaceFont(\n        text: String,\n        errorQueryTTF: QueryTTF?,\n        correctQueryTTF: QueryTTF?,\n        filter: Boolean\n    ): String {\n        if (errorQueryTTF == null || correctQueryTTF == null) return text\n        val contentArray = text.toStringArray() //这里不能用toCharArray,因为有些文字占多个字节\n        val intArray = IntArray(1)\n        contentArray.forEachIndexed { index, s ->\n            val oldCode = s.codePointAt(0)\n            // 忽略正常的空白字符\n            if (errorQueryTTF.isBlankUnicode(oldCode)) {\n                return@forEachIndexed\n            }\n            // 删除轮廓数据不存在的字符\n            var glyf = errorQueryTTF.getGlyfByUnicode(oldCode)  // 轮廓数据不存在\n            if (errorQueryTTF.getGlyfIdByUnicode(oldCode) == 0) glyf = null // 轮廓数据指向保留索引0\n            if (filter && (glyf == null)) {\n                contentArray[index] = \"\"\n                return@forEachIndexed\n            }\n            // 使用轮廓数据反查Unicode\n            val code = correctQueryTTF.getUnicodeByGlyf(glyf)\n            if (code != 0) {\n                intArray[0] = code\n                contentArray[index] = String(intArray, 0, 1)\n            }\n        }\n        return contentArray.joinToString(\"\")\n    }\n\n    /**\n     * @param text 包含错误字体的内容\n     * @param errorQueryTTF 错误的字体\n     * @param correctQueryTTF 正确的字体\n     */\n    fun replaceFont(\n        text: String,\n        errorQueryTTF: QueryTTF?,\n        correctQueryTTF: QueryTTF?\n    ): String {\n        return replaceFont(text, errorQueryTTF, correctQueryTTF, false)\n    }\n\n\n    /**\n     * 章节数转数字\n     */\n    fun toNumChapter(s: String?): String? {\n        s ?: return null\n        val matcher = AppPattern.titleNumPattern.matcher(s)\n        if (matcher.find()) {\n            val intStr = StringUtils.stringToInt(matcher.group(2))\n            return \"${matcher.group(1)}${intStr}${matcher.group(3)}\"\n        }\n        return s\n    }\n\n\n    fun toURL(urlStr: String): JsURL {\n        return JsURL(urlStr)\n    }\n\n    fun toURL(url: String, baseUrl: String? = null): JsURL {\n        return JsURL(url, baseUrl)\n    }\n\n    /**\n     * 弹窗提示\n     */\n    fun toast(msg: Any?) {\n        rhinoContext.ensureActive()\n        appCtx.toastOnUi(\"${getSource()?.getTag()}: ${msg.toString()}\")\n    }\n\n    /**\n     * 弹窗提示 停留时间较长\n     */\n    fun longToast(msg: Any?) {\n        rhinoContext.ensureActive()\n        appCtx.longToastOnUi(\"${getSource()?.getTag()}: ${msg.toString()}\")\n    }\n\n    /**\n     * 输出调试日志\n     */\n    fun log(msg: Any?): Any? {\n        rhinoContextOrNull?.ensureActive()\n        getSource()?.let {\n            Debug.log(it.getKey(), msg.toString())\n        } ?: Debug.log(msg.toString())\n        AppLog.putDebug(\"${getSource()?.getTag() ?: \"源\"}调试输出: $msg\")\n        return msg\n    }\n\n    /**\n     * 输出对象类型\n     */\n    fun logType(any: Any?) {\n        if (any == null) {\n            log(\"null\")\n        } else {\n            log(any.javaClass.name)\n        }\n    }\n\n    /**\n     * 生成UUID\n     */\n    fun randomUUID(): String {\n        return UUID.randomUUID().toString()\n    }\n\n    fun androidId(): String {\n        return AppConst.androidId\n    }\n\n    fun openUrl(url: String) {\n        openUrl(url, null)\n    }\n\n    // 新增 mimeType 参数，默认为 null（保持兼容性）\n    fun openUrl(url: String, mimeType: String? = null) {\n        require(url.length < 64 * 1024) { \"openUrl parameter url too long\" }\n        rhinoContext.ensureActive()\n        val source = getSource() ?: throw NoStackTraceException(\"openUrl source cannot be null\")\n        appCtx.startActivity<OpenUrlConfirmActivity> {\n            putExtra(\"uri\", url)\n            putExtra(\"mimeType\", mimeType)\n            putExtra(\"sourceOrigin\", source.getKey())\n            putExtra(\"sourceName\", source.getTag())\n            putExtra(\"sourceType\", source.getSourceType())\n        }\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/help/LauncherIconHelp.kt",
    "content": "package io.legado.app.help\n\nimport android.content.ComponentName\nimport android.content.pm.PackageManager\nimport android.os.Build\nimport io.legado.app.R\nimport io.legado.app.ui.welcome.*\nimport io.legado.app.utils.toastOnUi\nimport splitties.init.appCtx\n\n/**\n * Created by GKF on 2018/2/27.\n * 更换图标\n */\nobject LauncherIconHelp {\n    private val packageManager: PackageManager = appCtx.packageManager\n    private val componentNames = arrayListOf(\n        ComponentName(appCtx, Launcher1::class.java.name),\n        ComponentName(appCtx, Launcher2::class.java.name),\n        ComponentName(appCtx, Launcher3::class.java.name),\n        ComponentName(appCtx, Launcher4::class.java.name),\n        ComponentName(appCtx, Launcher5::class.java.name),\n        ComponentName(appCtx, Launcher6::class.java.name)\n    )\n\n    fun changeIcon(icon: String?) {\n        if (icon.isNullOrEmpty()) return\n        if (Build.VERSION.SDK_INT < 26) {\n            appCtx.toastOnUi(R.string.change_icon_error)\n            return\n        }\n        var hasEnabled = false\n        componentNames.forEach {\n            if (icon.equals(it.className.substringAfterLast(\".\"), true)) {\n                hasEnabled = true\n                //启用\n                packageManager.setComponentEnabledSetting(\n                    it,\n                    PackageManager.COMPONENT_ENABLED_STATE_ENABLED,\n                    PackageManager.DONT_KILL_APP\n                )\n            } else {\n                //禁用\n                packageManager.setComponentEnabledSetting(\n                    it,\n                    PackageManager.COMPONENT_ENABLED_STATE_DISABLED,\n                    PackageManager.DONT_KILL_APP\n                )\n            }\n        }\n        if (hasEnabled) {\n            packageManager.setComponentEnabledSetting(\n                ComponentName(appCtx, WelcomeActivity::class.java.name),\n                PackageManager.COMPONENT_ENABLED_STATE_DISABLED,\n                PackageManager.DONT_KILL_APP\n            )\n        } else {\n            packageManager.setComponentEnabledSetting(\n                ComponentName(appCtx, WelcomeActivity::class.java.name),\n                PackageManager.COMPONENT_ENABLED_STATE_ENABLED,\n                PackageManager.DONT_KILL_APP\n            )\n        }\n    }\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/help/LayoutManager.kt",
    "content": "package io.legado.app.help\n\nimport androidx.annotation.IntDef\nimport androidx.recyclerview.widget.GridLayoutManager\nimport androidx.recyclerview.widget.LinearLayoutManager\nimport androidx.recyclerview.widget.RecyclerView\nimport androidx.recyclerview.widget.StaggeredGridLayoutManager\n\n@Suppress(\"unused\")\nobject LayoutManager {\n\n    interface LayoutManagerFactory {\n        fun create(recyclerView: RecyclerView): RecyclerView.LayoutManager\n    }\n\n    @IntDef(LinearLayoutManager.HORIZONTAL, LinearLayoutManager.VERTICAL)\n    @Retention(AnnotationRetention.SOURCE)\n    annotation class Orientation\n\n    fun linear(): LayoutManagerFactory {\n        return object : LayoutManagerFactory {\n            override fun create(recyclerView: RecyclerView): RecyclerView.LayoutManager {\n                return LinearLayoutManager(recyclerView.context)\n            }\n        }\n    }\n\n\n    fun linear(@Orientation orientation: Int, reverseLayout: Boolean): LayoutManagerFactory {\n        return object : LayoutManagerFactory {\n            override fun create(recyclerView: RecyclerView): RecyclerView.LayoutManager {\n                return LinearLayoutManager(recyclerView.context, orientation, reverseLayout)\n            }\n        }\n    }\n\n\n    fun grid(spanCount: Int): LayoutManagerFactory {\n        return object : LayoutManagerFactory {\n            override fun create(recyclerView: RecyclerView): RecyclerView.LayoutManager {\n                return GridLayoutManager(recyclerView.context, spanCount)\n            }\n        }\n    }\n\n\n    fun grid(\n        spanCount: Int,\n        @Orientation orientation: Int,\n        reverseLayout: Boolean\n    ): LayoutManagerFactory {\n        return object : LayoutManagerFactory {\n            override fun create(recyclerView: RecyclerView): RecyclerView.LayoutManager {\n                return GridLayoutManager(\n                    recyclerView.context,\n                    spanCount,\n                    orientation,\n                    reverseLayout\n                )\n            }\n        }\n    }\n\n\n    fun staggeredGrid(spanCount: Int, @Orientation orientation: Int): LayoutManagerFactory {\n        return object : LayoutManagerFactory {\n            override fun create(recyclerView: RecyclerView): RecyclerView.LayoutManager {\n                return StaggeredGridLayoutManager(spanCount, orientation)\n            }\n        }\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/help/LifecycleHelp.kt",
    "content": "package io.legado.app.help\n\nimport android.app.Activity\nimport android.app.Application\nimport android.os.Bundle\nimport io.legado.app.base.BaseService\nimport io.legado.app.utils.LogUtils\nimport java.lang.ref.WeakReference\n\n/**\n * Activity管理器,管理项目中Activity的状态\n */\n@Suppress(\"unused\")\nobject LifecycleHelp : Application.ActivityLifecycleCallbacks {\n\n    private const val TAG = \"LifecycleHelp\"\n\n    private val activities: MutableList<WeakReference<Activity>> = arrayListOf()\n    private val services: MutableList<WeakReference<BaseService>> = arrayListOf()\n    private var appFinishedListener: (() -> Unit)? = null\n\n    fun activitySize(): Int {\n        return activities.size\n    }\n\n    /**\n     * 判断指定Activity是否存在\n     */\n    fun isExistActivity(activityClass: Class<*>): Boolean {\n        activities.forEach { item ->\n            if (item.get()?.javaClass == activityClass) {\n                return true\n            }\n        }\n        return false\n    }\n\n    /**\n     * 关闭指定 activity(class)\n     */\n    fun finishActivity(vararg activityClasses: Class<*>) {\n        val waitFinish = ArrayList<WeakReference<Activity>>()\n        for (temp in activities) {\n            for (activityClass in activityClasses) {\n                if (temp.get()?.javaClass == activityClass) {\n                    waitFinish.add(temp)\n                    break\n                }\n            }\n        }\n        waitFinish.forEach {\n            it.get()?.finish()\n        }\n    }\n\n    fun setOnAppFinishedListener(appFinishedListener: (() -> Unit)) {\n        this.appFinishedListener = appFinishedListener\n    }\n\n    override fun onActivityPaused(activity: Activity) {\n        LogUtils.d(TAG, \"${activity::class.simpleName} onPause\")\n    }\n\n    override fun onActivityResumed(activity: Activity) {\n        LogUtils.d(TAG, \"${activity::class.simpleName} onResume\")\n    }\n\n    override fun onActivityStarted(activity: Activity) {\n        LogUtils.d(TAG, \"${activity::class.simpleName} onStart\")\n    }\n\n    override fun onActivityDestroyed(activity: Activity) {\n        LogUtils.d(TAG, \"${activity::class.simpleName} onDestroy\")\n        for (temp in activities) {\n            if (temp.get() != null && temp.get() === activity) {\n                activities.remove(temp)\n                if (services.size == 0 && activities.size == 0) {\n                    onAppFinished()\n                }\n                break\n            }\n        }\n    }\n\n    override fun onActivitySaveInstanceState(activity: Activity, outState: Bundle) {\n        LogUtils.d(TAG, \"${activity::class.simpleName} onSaveInstanceState\")\n    }\n\n    override fun onActivityStopped(activity: Activity) {\n        LogUtils.d(TAG, \"${activity::class.simpleName} onStop\")\n    }\n\n    override fun onActivityCreated(activity: Activity, savedInstanceState: Bundle?) {\n        LogUtils.d(TAG, \"${activity::class.simpleName} onCreate\")\n        activities.add(WeakReference(activity))\n    }\n\n    @Synchronized\n    fun onServiceCreate(service: BaseService) {\n        LogUtils.d(TAG, \"${service::class.simpleName} onCreate\")\n        services.add(WeakReference(service))\n    }\n\n    @Synchronized\n    fun onServiceDestroy(service: BaseService) {\n        LogUtils.d(TAG, \"${service::class.simpleName} onDestroy\")\n        for (temp in services) {\n            if (temp.get() != null && temp.get() === service) {\n                services.remove(temp)\n                if (services.size == 0 && activities.size == 0) {\n                    onAppFinished()\n                }\n                break\n            }\n        }\n    }\n\n    private fun onAppFinished() {\n        appFinishedListener?.invoke()\n    }\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/help/MediaHelp.kt",
    "content": "package io.legado.app.help\n\nimport android.content.Context\nimport android.media.AudioManager\nimport android.media.MediaPlayer\nimport android.support.v4.media.session.PlaybackStateCompat\nimport androidx.media.AudioAttributesCompat\nimport androidx.media.AudioFocusRequestCompat\nimport androidx.media.AudioManagerCompat\nimport io.legado.app.R\nimport splitties.systemservices.audioManager\n\nobject MediaHelp {\n\n    const val MEDIA_SESSION_ACTIONS = (PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS\n            or PlaybackStateCompat.ACTION_REWIND\n            or PlaybackStateCompat.ACTION_PLAY\n            or PlaybackStateCompat.ACTION_PLAY_PAUSE\n            or PlaybackStateCompat.ACTION_PAUSE\n            or PlaybackStateCompat.ACTION_STOP\n            or PlaybackStateCompat.ACTION_FAST_FORWARD\n            or PlaybackStateCompat.ACTION_SKIP_TO_NEXT\n            or PlaybackStateCompat.ACTION_SEEK_TO\n            or PlaybackStateCompat.ACTION_SET_RATING\n            or PlaybackStateCompat.ACTION_PLAY_FROM_MEDIA_ID\n            or PlaybackStateCompat.ACTION_PLAY_FROM_SEARCH\n            or PlaybackStateCompat.ACTION_SKIP_TO_QUEUE_ITEM\n            or PlaybackStateCompat.ACTION_PLAY_FROM_URI\n            or PlaybackStateCompat.ACTION_PREPARE\n            or PlaybackStateCompat.ACTION_PREPARE_FROM_MEDIA_ID\n            or PlaybackStateCompat.ACTION_PREPARE_FROM_SEARCH\n            or PlaybackStateCompat.ACTION_PREPARE_FROM_URI\n            or PlaybackStateCompat.ACTION_SET_REPEAT_MODE\n            or PlaybackStateCompat.ACTION_SET_SHUFFLE_MODE\n            or PlaybackStateCompat.ACTION_SET_CAPTIONING_ENABLED)\n\n    fun buildAudioFocusRequestCompat(\n        audioFocusChangeListener: AudioManager.OnAudioFocusChangeListener\n    ): AudioFocusRequestCompat {\n        val mPlaybackAttributes = AudioAttributesCompat.Builder()\n            .setUsage(AudioAttributesCompat.USAGE_MEDIA)\n            .setContentType(AudioAttributesCompat.CONTENT_TYPE_MUSIC)\n            .build()\n        return AudioFocusRequestCompat.Builder(AudioManagerCompat.AUDIOFOCUS_GAIN)\n            .setAudioAttributes(mPlaybackAttributes)\n            .setOnAudioFocusChangeListener(audioFocusChangeListener)\n            .build()\n    }\n\n\n    /**\n     * @return 音频焦点\n     */\n    fun requestFocus(focusRequest: AudioFocusRequestCompat): Boolean {\n        val request = AudioManagerCompat.requestAudioFocus(audioManager, focusRequest)\n        return request == AudioManager.AUDIOFOCUS_REQUEST_GRANTED\n    }\n\n    /**\n     * 播放静音音频,用来获取音频焦点\n     */\n    fun playSilentSound(mContext: Context) {\n        kotlin.runCatching {\n            // Stupid Android 8 \"Oreo\" hack to make media buttons work\n            val mMediaPlayer = MediaPlayer.create(mContext, R.raw.silent_sound)\n            mMediaPlayer.setOnCompletionListener { mMediaPlayer.release() }\n            mMediaPlayer.start()\n        }\n    }\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/help/PaintPool.kt",
    "content": "package io.legado.app.help\n\nimport android.graphics.Paint\nimport io.legado.app.utils.objectpool.BaseSafeObjectPool\n\nobject PaintPool : BaseSafeObjectPool<Paint>(8) {\n\n    private val emptyPaint = Paint()\n\n    override fun create(): Paint = Paint()\n\n    override fun recycle(target: Paint) {\n        target.set(emptyPaint)\n        super.recycle(target)\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/help/README.md",
    "content": "# 放置一些帮助类"
  },
  {
    "path": "app/src/main/java/io/legado/app/help/ReplaceAnalyzer.kt",
    "content": "package io.legado.app.help\n\nimport io.legado.app.data.entities.ReplaceRule\nimport io.legado.app.exception.NoStackTraceException\nimport io.legado.app.utils.*\n\nobject ReplaceAnalyzer {\n\n    fun jsonToReplaceRules(json: String): Result<MutableList<ReplaceRule>> {\n        return kotlin.runCatching {\n            val replaceRules = mutableListOf<ReplaceRule>()\n            val items: List<Map<String, Any>> = jsonPath.parse(json).read(\"$\")\n            for (item in items) {\n                val jsonItem = jsonPath.parse(item)\n                jsonToReplaceRule(jsonItem.jsonString()).getOrThrow().let {\n                    if (it.isValid()) {\n                        replaceRules.add(it)\n                    }\n                }\n            }\n            replaceRules\n        }\n    }\n\n    fun jsonToReplaceRule(json: String): Result<ReplaceRule> {\n        return runCatching {\n            val replaceRule: ReplaceRule? =\n                GSON.fromJsonObject<ReplaceRule>(json.trim()).getOrNull()\n            if (replaceRule == null || replaceRule.pattern.isEmpty()) {\n                val jsonItem = jsonPath.parse(json.trim())\n                val rule = ReplaceRule()\n                rule.id = jsonItem.readLong(\"$.id\") ?: System.currentTimeMillis()\n                rule.pattern = jsonItem.readString(\"$.regex\") ?: \"\"\n                if (rule.pattern.isEmpty()) throw NoStackTraceException(\"格式不对\")\n                rule.name = jsonItem.readString(\"$.replaceSummary\") ?: \"\"\n                rule.replacement = jsonItem.readString(\"$.replacement\") ?: \"\"\n                rule.isRegex = jsonItem.readBool(\"$.isRegex\") == true\n                rule.scope = jsonItem.readString(\"$.useTo\")\n                rule.isEnabled = jsonItem.readBool(\"$.enable\") == true\n                rule.order = jsonItem.readInt(\"$.serialNumber\") ?: 0\n                return@runCatching rule\n            }\n            return@runCatching replaceRule\n        }\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/help/RuleBigDataHelp.kt",
    "content": "package io.legado.app.help\n\nimport io.legado.app.data.appDb\nimport io.legado.app.utils.FileUtils\nimport io.legado.app.utils.MD5Utils\nimport io.legado.app.utils.externalFiles\nimport io.legado.app.utils.getFile\nimport kotlinx.coroutines.Dispatchers.IO\nimport kotlinx.coroutines.withContext\nimport splitties.init.appCtx\nimport java.io.File\n\nobject RuleBigDataHelp {\n\n    private val ruleDataDir = FileUtils.createFolderIfNotExist(appCtx.externalFiles, \"ruleData\")\n    private val bookData = FileUtils.createFolderIfNotExist(ruleDataDir, \"book\")\n    private val rssData = FileUtils.createFolderIfNotExist(ruleDataDir, \"rss\")\n\n    suspend fun clearInvalid() {\n        withContext(IO) {\n            bookData.listFiles()?.forEach {\n                if (it.isFile) {\n                    FileUtils.delete(it)\n                } else {\n                    val bookUrlFile = it.getFile(\"bookUrl.txt\")\n                    if (!bookUrlFile.exists()) {\n                        FileUtils.delete(it)\n                    } else {\n                        val bookUrl = bookUrlFile.readText()\n                        if (!appDb.bookDao.has(bookUrl)) {\n                            FileUtils.delete(it)\n                        }\n                    }\n                }\n            }\n            rssData.listFiles()?.forEach {\n                if (it.isFile) {\n                    FileUtils.delete(it)\n                } else {\n                    val originFile = it.getFile(\"origin.txt\")\n                    if (!originFile.exists()) {\n                        FileUtils.delete(it)\n                    } else {\n                        val origin = originFile.readText()\n                        if (!appDb.rssSourceDao.has(origin)) {\n                            FileUtils.delete(it)\n                        }\n                    }\n                }\n            }\n        }\n    }\n\n    fun putBookVariable(bookUrl: String, key: String, value: String?) {\n        val md5BookUrl = MD5Utils.md5Encode(bookUrl)\n        val md5Key = MD5Utils.md5Encode(key)\n        if (value == null) {\n            FileUtils.delete(FileUtils.getPath(bookData, md5BookUrl, \"$md5Key.txt\"), true)\n        } else {\n            val valueFile = FileUtils.createFileIfNotExist(bookData, md5BookUrl, \"$md5Key.txt\")\n            valueFile.writeText(value)\n            val bookUrlFile = File(FileUtils.getPath(bookData, md5BookUrl, \"bookUrl.txt\"))\n            if (!bookUrlFile.exists()) {\n                bookUrlFile.writeText(bookUrl)\n            }\n        }\n    }\n\n    fun getBookVariable(bookUrl: String, key: String?): String? {\n        val md5BookUrl = MD5Utils.md5Encode(bookUrl)\n        val md5Key = MD5Utils.md5Encode(key)\n        val file = File(FileUtils.getPath(bookData, md5BookUrl, \"$md5Key.txt\"))\n        if (file.exists()) {\n            return file.readText()\n        }\n        return null\n    }\n\n    fun hasBookVariable(bookUrl: String, key: String): Boolean {\n        val md5BookUrl = MD5Utils.md5Encode(bookUrl)\n        val md5Key = MD5Utils.md5Encode(key)\n        val file = File(FileUtils.getPath(bookData, md5BookUrl, \"$md5Key.txt\"))\n        return file.exists()\n    }\n\n    fun putChapterVariable(bookUrl: String, chapterUrl: String, key: String, value: String?) {\n        val md5BookUrl = MD5Utils.md5Encode(bookUrl)\n        val md5ChapterUrl = MD5Utils.md5Encode(chapterUrl)\n        val md5Key = MD5Utils.md5Encode(key)\n        if (value == null) {\n            FileUtils.delete(FileUtils.getPath(bookData, md5BookUrl, md5ChapterUrl, \"$md5Key.txt\"))\n        } else {\n            val valueFile =\n                FileUtils.createFileIfNotExist(bookData, md5BookUrl, md5ChapterUrl, \"$md5Key.txt\")\n            valueFile.writeText(value)\n            val bookUrlFile = File(FileUtils.getPath(bookData, md5BookUrl, \"bookUrl.txt\"))\n            if (!bookUrlFile.exists()) {\n                bookUrlFile.writeText(bookUrl)\n            }\n        }\n    }\n\n    fun getChapterVariable(bookUrl: String, chapterUrl: String, key: String): String? {\n        val md5BookUrl = MD5Utils.md5Encode(bookUrl)\n        val md5ChapterUrl = MD5Utils.md5Encode(chapterUrl)\n        val md5Key = MD5Utils.md5Encode(key)\n        val file = File(FileUtils.getPath(bookData, md5BookUrl, md5ChapterUrl, \"$md5Key.txt\"))\n        if (file.exists()) {\n            return file.readText()\n        }\n        return null\n    }\n\n    fun putRssVariable(origin: String, link: String, key: String, value: String?) {\n        val md5Origin = MD5Utils.md5Encode(origin)\n        val md5Link = MD5Utils.md5Encode(link)\n        val md5Key = MD5Utils.md5Encode(key)\n        val filePath = FileUtils.getPath(rssData, md5Origin, md5Link, \"$md5Key.txt\")\n        if (value == null) {\n            FileUtils.delete(filePath)\n        } else {\n            val valueFile = FileUtils.createFileIfNotExist(filePath)\n            valueFile.writeText(value)\n            val originFile = File(FileUtils.getPath(rssData, md5Origin, \"origin.txt\"))\n            if (!originFile.exists()) {\n                originFile.writeText(origin)\n            }\n            val linFile = File(FileUtils.getPath(rssData, md5Origin, md5Link, \"origin.txt\"))\n            if (!linFile.exists()) {\n                linFile.writeText(link)\n            }\n        }\n    }\n\n    fun getRssVariable(origin: String, link: String, key: String): String? {\n        val md5Origin = MD5Utils.md5Encode(origin)\n        val md5Link = MD5Utils.md5Encode(link)\n        val md5Key = MD5Utils.md5Encode(key)\n        val filePath = FileUtils.getPath(rssData, md5Origin, md5Link, \"$md5Key.txt\")\n        val file = File(filePath)\n        if (file.exists()) {\n            return file.readText()\n        }\n        return null\n    }\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/help/RuleComplete.kt",
    "content": "package io.legado.app.help\n\n\n@Suppress(\"RegExpRedundantEscape\")\nobject RuleComplete {\n    // 需要补全\n    private val needComplete = Regex(\n        \"\"\"(?<!(@|/|^|[|%&]{2})(attr|text|ownText|textNodes|href|content|html|alt|all|value|src)(\\(\\))?)(?<seq>\\&{2}|%%|\\|{2}|$)\"\"\"\n    )\n\n    // 不能补全 存在js/json/{{xx}}的复杂情况\n    private val notComplete = Regex(\"\"\"^:|^##|\\{\\{|@js:|<js>|@Json:|\\$\\.\"\"\")\n\n    // 修正从图片获取信息\n    private val fixImgInfo =\n        Regex(\"\"\"(?<=(^|tag\\.|[\\+/@>~| &]))img(?<at>(\\[@?.+\\]|\\.[-\\w]+)?)[@/]+text(\\(\\))?(?<seq>\\&{2}|%%|\\|{2}|$)\"\"\")\n\n    private val isXpath = Regex(\"^//|^@Xpath:\")\n\n    /**\n     * 对简单规则进行补全，简化部分书源规则的编写\n     * 对JSOUP/XPath/CSS规则生效\n     * @author 希弥\n     * @return 补全后的规则 或 原规则\n     * @param rules 需要补全的规则\n     * @param preRule 预处理规则或列表规则\n     * @param type 补全结果的类型，可选的值有:\n     *  1 文字(默认)\n     *  2 链接\n     *  3 图片\n     */\n    fun autoComplete(\n        rules: String?,\n        preRule: String? = null,\n        type: Int = 1\n    ): String? {\n        if (rules.isNullOrEmpty() || rules.contains(notComplete) || preRule?.contains(notComplete) == true) {\n            return rules\n        }\n\n        /** 尾部##分割的正则或由,分割的参数 */\n        val tailStr: String\n\n        /** 分割字符 */\n        val splitStr: String\n\n        /**  用于获取文字时添加的规则 */\n        val textRule: String\n\n        /**  用于获取链接时添加的规则 */\n        val linkRule: String\n\n        /**  用于获取图片时添加的规则 */\n        val imgRule: String\n\n        /**  用于获取图片alt属性时添加的规则 */\n        val imgText: String\n\n        // 分离尾部规则\n        val regexSplit = rules.split(\"\"\"##|,\\{\"\"\".toRegex(), 2)\n        val cleanedRule = regexSplit[0]\n        if (regexSplit.size > 1) {\n            splitStr = \"\"\"##|,\\{\"\"\".toRegex().find(rules)?.value ?: \"\"\n            tailStr = splitStr + regexSplit[1]\n        } else {\n            tailStr = \"\"\n        }\n        if (cleanedRule.contains(isXpath)) {\n            textRule = \"//text()\\${seq}\"\n            linkRule = \"//@href\\${seq}\"\n            imgRule = \"//@src\\${seq}\"\n            imgText = \"img\\${at}/@alt\\${seq}\"\n        } else {\n            textRule = \"@text\\${seq}\"\n            linkRule = \"@href\\${seq}\"\n            imgRule = \"@src\\${seq}\"\n            imgText = \"img\\${at}@alt\\${seq}\"\n        }\n        return when (type) {\n            1 -> needComplete.replace(cleanedRule, textRule).replace(fixImgInfo, imgText) + tailStr\n            2 -> needComplete.replace(cleanedRule, linkRule) + tailStr\n            3 -> needComplete.replace(cleanedRule, imgRule) + tailStr\n            else -> rules\n        }\n    }\n\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/help/TTS.kt",
    "content": "package io.legado.app.help\n\nimport android.speech.tts.TextToSpeech\nimport android.speech.tts.UtteranceProgressListener\nimport io.legado.app.R\nimport io.legado.app.constant.AppLog\nimport io.legado.app.utils.buildMainHandler\nimport io.legado.app.utils.splitNotBlank\nimport io.legado.app.utils.toastOnUi\nimport splitties.init.appCtx\n\nclass TTS {\n\n    private val handler by lazy { buildMainHandler() }\n\n    private val tag = \"legado_tts\"\n\n    private val clearTtsRunnable = Runnable { clearTts() }\n\n    private var speakStateListener: SpeakStateListener? = null\n\n    private var textToSpeech: TextToSpeech? = null\n\n    private var text: String? = null\n\n    private var onInit = false\n\n    private val initListener by lazy {\n        InitListener()\n    }\n\n    private val utteranceListener by lazy {\n        TTSUtteranceListener()\n    }\n\n    val isSpeaking: Boolean\n        get() {\n            return textToSpeech?.isSpeaking ?: false\n        }\n\n    @Suppress(\"unused\")\n    fun setSpeakStateListener(speakStateListener: SpeakStateListener) {\n        this.speakStateListener = speakStateListener\n    }\n\n    @Suppress(\"unused\")\n    fun removeSpeakStateListener() {\n        speakStateListener = null\n    }\n\n    @Synchronized\n    fun speak(text: String) {\n        handler.removeCallbacks(clearTtsRunnable)\n        this.text = text\n        if (onInit) {\n            return\n        }\n        if (textToSpeech == null) {\n            onInit = true\n            textToSpeech = TextToSpeech(appCtx, initListener)\n        } else {\n            addTextToSpeakList()\n        }\n    }\n\n    fun stop() {\n        textToSpeech?.stop()\n    }\n\n    @Synchronized\n    fun clearTts() {\n        textToSpeech?.let { tts ->\n            tts.stop()\n            tts.shutdown()\n        }\n        textToSpeech = null\n    }\n\n    private fun addTextToSpeakList() {\n        val tts = textToSpeech ?: return\n        kotlin.runCatching {\n            var result = tts.speak(\"\", TextToSpeech.QUEUE_FLUSH, null, null)\n            if (result == TextToSpeech.ERROR) {\n                clearTts()\n                textToSpeech = TextToSpeech(appCtx, initListener)\n                return\n            }\n            text?.splitNotBlank(\"\\n\")?.forEachIndexed { i, s ->\n                result = tts.speak(s, TextToSpeech.QUEUE_ADD, null, tag + i)\n                if (result == TextToSpeech.ERROR) {\n                    AppLog.put(\"tts朗读出错:$text\")\n                }\n            }\n        }.onFailure {\n            AppLog.put(\"tts朗读出错\", it)\n            appCtx.toastOnUi(it.localizedMessage)\n        }\n    }\n\n    /**\n     * 初始化监听\n     */\n    private inner class InitListener : TextToSpeech.OnInitListener {\n\n        override fun onInit(status: Int) {\n            if (status == TextToSpeech.SUCCESS) {\n                textToSpeech?.setOnUtteranceProgressListener(utteranceListener)\n                addTextToSpeakList()\n            } else {\n                appCtx.toastOnUi(R.string.tts_init_failed)\n            }\n            onInit = false\n        }\n\n    }\n\n    /**\n     * 朗读监听\n     */\n    private inner class TTSUtteranceListener : UtteranceProgressListener() {\n\n        override fun onStart(utteranceId: String?) {\n            //开始朗读取消释放资源任务\n            handler.removeCallbacks(clearTtsRunnable)\n            speakStateListener?.onStart()\n        }\n\n        override fun onDone(utteranceId: String?) {\n            //一分钟没有朗读释放资源\n            handler.postDelayed(clearTtsRunnable, 60000L)\n            speakStateListener?.onDone()\n        }\n\n        @Deprecated(\"Deprecated in Java\")\n        override fun onError(utteranceId: String?) {\n            //Deprecated\n        }\n\n    }\n\n    interface SpeakStateListener {\n        fun onStart()\n        fun onDone()\n    }\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/help/book/BookContent.kt",
    "content": "package io.legado.app.help.book\n\nimport io.legado.app.data.entities.ReplaceRule\n\ndata class BookContent(\n    val sameTitleRemoved: Boolean,\n    val textList: List<String>,\n    //起效的替换规则\n    val effectiveReplaceRules: List<ReplaceRule>?\n) {\n\n    override fun toString(): String {\n        return textList.joinToString(\"\\n\")\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/help/book/BookExtensions.kt",
    "content": "@file:Suppress(\"unused\")\n\npackage io.legado.app.help.book\n\nimport android.net.Uri\nimport androidx.core.net.toUri\nimport com.script.buildScriptBindings\nimport com.script.rhino.RhinoScriptEngine\nimport io.legado.app.constant.AppLog\nimport io.legado.app.constant.AppPattern\nimport io.legado.app.constant.BookSourceType\nimport io.legado.app.constant.BookType\nimport io.legado.app.data.appDb\nimport io.legado.app.data.entities.BaseBook\nimport io.legado.app.data.entities.Book\nimport io.legado.app.data.entities.BookSource\nimport io.legado.app.exception.NoStackTraceException\nimport io.legado.app.help.RuleBigDataHelp\nimport io.legado.app.help.config.AppConfig\nimport io.legado.app.model.localBook.LocalBook\nimport io.legado.app.utils.FileDoc\nimport io.legado.app.utils.GSON\nimport io.legado.app.utils.MD5Utils\nimport io.legado.app.utils.exists\nimport io.legado.app.utils.find\nimport io.legado.app.utils.inputStream\nimport io.legado.app.utils.isUri\nimport io.legado.app.utils.normalizeFileName\nimport io.legado.app.utils.toastOnUi\nimport splitties.init.appCtx\nimport java.io.File\nimport java.time.LocalDate\nimport java.time.Period.between\nimport java.util.concurrent.ConcurrentHashMap\nimport kotlin.math.max\nimport kotlin.math.min\n\n\nval Book.isAudio: Boolean\n    get() = isType(BookType.audio)\n\nval Book.isImage: Boolean\n    get() = isType(BookType.image)\n\nval Book.isLocal: Boolean\n    get() {\n        if (type == 0) {\n            return origin == BookType.localTag || origin.startsWith(BookType.webDavTag)\n        }\n        return isType(BookType.local)\n    }\n\nval Book.isLocalTxt: Boolean\n    get() = isLocal && originName.endsWith(\".txt\", true)\n\nval Book.isEpub: Boolean\n    get() = isLocal && originName.endsWith(\".epub\", true)\n\nval Book.isUmd: Boolean\n    get() = isLocal && originName.endsWith(\".umd\", true)\n\nval Book.isPdf: Boolean\n    get() = isLocal && originName.endsWith(\".pdf\", true)\n\nval Book.isMobi: Boolean\n    get() = isLocal && (originName.endsWith(\".mobi\", true) ||\n            originName.endsWith(\".azw3\", true) ||\n            originName.endsWith(\".azw\", true))\n\nval Book.isOnLineTxt: Boolean\n    get() = !isLocal && isType(BookType.text)\n\nval Book.isWebFile: Boolean\n    get() = isType(BookType.webFile)\n\nval Book.isUpError: Boolean\n    get() = isType(BookType.updateError)\n\nval Book.isArchive: Boolean\n    get() = isType(BookType.archive)\n\nval Book.isNotShelf: Boolean\n    get() = isType(BookType.notShelf)\n\nval Book.archiveName: String\n    get() {\n        if (!isArchive) throw NoStackTraceException(\"Book is not deCompressed from archive\")\n        // local_book::archive.rar\n        // webDav::https://...../archive.rar\n        return origin.substringAfter(\"::\").substringAfterLast(\"/\")\n    }\n\nfun Book.contains(word: String?): Boolean {\n    if (word.isNullOrEmpty()) {\n        return true\n    }\n    return name.contains(word)\n            || author.contains(word)\n            || originName.contains(word)\n            || origin.contains(word)\n            || kind?.contains(word) == true\n            || intro?.contains(word) == true\n}\n\nprivate val localUriCache by lazy {\n    ConcurrentHashMap<String, Uri>()\n}\n\nfun Book.getLocalUri(): Uri {\n    if (!isLocal) {\n        throw NoStackTraceException(\"不是本地书籍\")\n    }\n    var uri = localUriCache[bookUrl]\n    if (uri != null) {\n        return uri\n    }\n    uri = if (bookUrl.isUri()) {\n        bookUrl.toUri()\n    } else {\n        Uri.fromFile(File(bookUrl))\n    }\n    //先检测uri是否有效,这个比较快\n    uri.inputStream(appCtx).getOrNull()?.use {\n        localUriCache[bookUrl] = uri\n    }?.let {\n        return uri\n    }\n    //不同的设备书籍保存路径可能不一样, uri无效时尝试寻找当前保存路径下的文件\n    val defaultBookDir = AppConfig.defaultBookTreeUri\n    val importBookDir = AppConfig.importBookPath\n\n    // 查找书籍保存目录\n    if (!defaultBookDir.isNullOrBlank()) {\n        val treeUri = defaultBookDir.toUri()\n        val treeFileDoc = FileDoc.fromUri(treeUri, true)\n        if (!treeFileDoc.exists()) {\n            appCtx.toastOnUi(\"书籍保存目录失效，请重新设置！\")\n        } else {\n            val fileDoc = treeFileDoc.find(originName, 5, 100)\n            if (fileDoc != null) {\n                localUriCache[bookUrl] = fileDoc.uri\n                //更新bookUrl 重启不用再找一遍\n                bookUrl = fileDoc.toString()\n                save()\n                return fileDoc.uri\n            }\n        }\n    }\n\n    // 查找添加本地选择的目录\n    if (!importBookDir.isNullOrBlank() && defaultBookDir != importBookDir) {\n        val treeUri = if (importBookDir.isUri()) {\n            importBookDir.toUri()\n        } else {\n            Uri.fromFile(File(importBookDir))\n        }\n        val treeFileDoc = FileDoc.fromUri(treeUri, true)\n        val fileDoc = treeFileDoc.find(originName, 5, 100)\n        if (fileDoc != null) {\n            localUriCache[bookUrl] = fileDoc.uri\n            bookUrl = fileDoc.toString()\n            save()\n            return fileDoc.uri\n        }\n    }\n\n    localUriCache[bookUrl] = uri\n    return uri\n}\n\n\nfun Book.getArchiveUri(): Uri? {\n    val defaultBookDir = AppConfig.defaultBookTreeUri\n    return if (isArchive && !defaultBookDir.isNullOrBlank()) {\n        FileDoc.fromUri(defaultBookDir.toUri(), true)\n            .find(archiveName)?.uri\n    } else {\n        null\n    }\n}\n\nfun Book.cacheLocalUri(uri: Uri) {\n    localUriCache[bookUrl] = uri\n}\n\nfun Book.removeLocalUriCache() {\n    localUriCache.remove(bookUrl)\n}\n\nfun Book.getRemoteUrl(): String? {\n    if (origin.startsWith(BookType.webDavTag)) {\n        return origin.substring(BookType.webDavTag.length)\n    }\n    return null\n}\n\nfun Book.setType(@BookType.Type vararg types: Int) {\n    type = 0\n    addType(*types)\n}\n\nfun Book.addType(@BookType.Type vararg types: Int) {\n    types.forEach {\n        type = type or it\n    }\n}\n\nfun Book.removeType(@BookType.Type vararg types: Int) {\n    types.forEach {\n        type = type and it.inv()\n    }\n}\n\nfun Book.removeAllBookType() {\n    removeType(BookType.allBookType)\n}\n\nfun Book.clearType() {\n    type = 0\n}\n\nfun Book.isType(@BookType.Type bookType: Int): Boolean = type and bookType > 0\n\nfun Book.upType() {\n    if (type < 8) {\n        type = when (type) {\n            BookSourceType.image -> BookType.image\n            BookSourceType.audio -> BookType.audio\n            BookSourceType.file -> BookType.webFile\n            else -> BookType.text\n        }\n        if (origin == BookType.localTag || origin.startsWith(BookType.webDavTag)) {\n            type = type or BookType.local\n        }\n    }\n}\n\nfun Book.sync(oldBook: Book) {\n    val curBook = appDb.bookDao.getBook(oldBook.bookUrl)!!\n    durChapterTime = curBook.durChapterTime\n    durChapterPos = curBook.durChapterPos\n    if (durChapterIndex != curBook.durChapterIndex) {\n        durChapterIndex = curBook.durChapterIndex\n        val replaceRules = ContentProcessor.get(this).getTitleReplaceRules()\n        appDb.bookChapterDao.getChapter(bookUrl, durChapterIndex)?.let {\n            durChapterTitle = it.getDisplayTitle(replaceRules, getUseReplaceRule())\n        }\n    }\n    canUpdate = curBook.canUpdate\n    readConfig = curBook.readConfig\n}\n\nfun Book.update() {\n    appDb.bookDao.update(this)\n}\n\nfun Book.primaryStr(): String {\n    return origin + bookUrl\n}\n\nfun Book.updateTo(newBook: Book): Book {\n    newBook.durChapterIndex = durChapterIndex\n    newBook.durChapterTitle = durChapterTitle\n    newBook.durChapterPos = durChapterPos\n    newBook.durChapterTime = durChapterTime\n    newBook.group = group\n    newBook.order = order\n    newBook.customCoverUrl = customCoverUrl\n    newBook.customIntro = customIntro\n    newBook.customTag = customTag\n    newBook.canUpdate = canUpdate\n    newBook.readConfig = readConfig\n    val variableMap = variableMap.toMutableMap()\n    variableMap.keys.removeIf {\n        newBook.hasVariable(it)\n    }\n    newBook.variableMap.putAll(variableMap)\n    newBook.variable = GSON.toJson(newBook.variableMap)\n    return newBook\n}\n\nfun Book.hasVariable(key: String): Boolean {\n    return variableMap.contains(key) || RuleBigDataHelp.hasBookVariable(bookUrl, key)\n}\n\nfun Book.getFolderNameNoCache(): String {\n    return name.replace(AppPattern.fileNameRegex, \"\").let {\n        it.substring(0, min(9, it.length)) + MD5Utils.md5Encode16(bookUrl)\n    }\n}\n\nfun Book.getBookSource(): BookSource? {\n    return appDb.bookSourceDao.getBookSource(origin)\n}\n\nfun Book.isLocalModified(): Boolean {\n    return isLocal && LocalBook.getLastModified(this).getOrDefault(0L) > latestChapterTime\n}\n\nfun Book.releaseHtmlData() {\n    infoHtml = null\n    tocHtml = null\n}\n\nfun Book.isSameNameAuthor(other: Any?): Boolean {\n    if (other is BaseBook) {\n        return name == other.name && author == other.author\n    }\n    return false\n}\n\nfun Book.getExportFileName(suffix: String): String {\n    val jsStr = AppConfig.bookExportFileName\n    if (jsStr.isNullOrBlank()) {\n        return \"$name 作者：${getRealAuthor()}.$suffix\"\n    }\n    val bindings = buildScriptBindings { bindings ->\n        bindings[\"epubIndex\"] = \"\"// 兼容老版本,修复可能存在的错误\n        bindings[\"name\"] = name\n        bindings[\"author\"] = getRealAuthor()\n    }\n    return kotlin.runCatching {\n        RhinoScriptEngine.eval(jsStr, bindings).toString() + \".\" + suffix\n    }.onFailure {\n        AppLog.put(\"导出书名规则错误,使用默认规则\\n${it.localizedMessage}\", it)\n    }.getOrDefault(\"$name 作者：${getRealAuthor()}.$suffix\")\n}\n\n/**\n * 获取分割文件后的文件名\n */\nfun Book.getExportFileName(\n    suffix: String,\n    epubIndex: Int,\n    jsStr: String? = AppConfig.episodeExportFileName\n): String {\n    // 默认规则\n    val default = \"$name 作者：${getRealAuthor()} [${epubIndex}].$suffix\"\n    if (jsStr.isNullOrBlank()) {\n        return default\n    }\n    val bindings = buildScriptBindings { bindings ->\n        bindings[\"name\"] = name\n        bindings[\"author\"] = getRealAuthor()\n        bindings[\"epubIndex\"] = epubIndex\n    }\n    return kotlin.runCatching {\n        RhinoScriptEngine.eval(jsStr, bindings).toString() + \".\" + suffix\n    }.onFailure {\n        AppLog.put(\"导出书名规则错误,使用默认规则\\n${it.localizedMessage}\", it)\n    }.getOrDefault(default).normalizeFileName()\n}\n\n// 根据当前日期计算章节总数\nfun Book.simulatedTotalChapterNum(): Int {\n    return if (readSimulating()) {\n        val currentDate = LocalDate.now()\n        val daysPassed = between(config.startDate, currentDate).days + 1\n        // 计算当前应该解锁到哪一章\n        val chaptersToUnlock =\n            max(0, (config.startChapter ?: 0) + (daysPassed * config.dailyChapters))\n        min(totalChapterNum, chaptersToUnlock)\n    } else {\n        totalChapterNum\n    }\n}\n\nfun Book.readSimulating(): Boolean {\n    return config.readSimulating\n}\n\nfun tryParesExportFileName(jsStr: String): Boolean {\n    val bindings = buildScriptBindings { bindings ->\n        bindings[\"name\"] = \"name\"\n        bindings[\"author\"] = \"author\"\n        bindings[\"epubIndex\"] = \"epubIndex\"\n    }\n    return runCatching {\n        RhinoScriptEngine.eval(jsStr, bindings)\n        true\n    }.getOrDefault(false)\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/help/book/BookHelp.kt",
    "content": "package io.legado.app.help.book\n\nimport android.graphics.BitmapFactory\nimport android.os.ParcelFileDescriptor\nimport androidx.documentfile.provider.DocumentFile\nimport com.script.rhino.runScriptWithContext\nimport io.legado.app.constant.AppLog\nimport io.legado.app.constant.AppPattern\nimport io.legado.app.constant.EventBus\nimport io.legado.app.data.appDb\nimport io.legado.app.data.entities.Book\nimport io.legado.app.data.entities.BookChapter\nimport io.legado.app.data.entities.BookSource\nimport io.legado.app.help.config.AppConfig\nimport io.legado.app.model.analyzeRule.AnalyzeUrl\nimport io.legado.app.model.localBook.LocalBook\nimport io.legado.app.utils.ArchiveUtils\nimport io.legado.app.utils.FileUtils\nimport io.legado.app.utils.ImageUtils\nimport io.legado.app.utils.MD5Utils\nimport io.legado.app.utils.NetworkUtils\nimport io.legado.app.utils.StringUtils\nimport io.legado.app.utils.SvgUtils\nimport io.legado.app.utils.UrlUtil\nimport io.legado.app.utils.createFileIfNotExist\nimport io.legado.app.utils.exists\nimport io.legado.app.utils.externalFiles\nimport io.legado.app.utils.getFile\nimport io.legado.app.utils.isContentScheme\nimport io.legado.app.utils.onEachParallel\nimport io.legado.app.utils.postEvent\nimport kotlinx.coroutines.Dispatchers.IO\nimport kotlinx.coroutines.coroutineScope\nimport kotlinx.coroutines.ensureActive\nimport kotlinx.coroutines.flow.Flow\nimport kotlinx.coroutines.flow.collect\nimport kotlinx.coroutines.flow.flow\nimport kotlinx.coroutines.sync.Mutex\nimport kotlinx.coroutines.withContext\nimport org.apache.commons.text.similarity.JaccardSimilarity\nimport splitties.init.appCtx\nimport java.io.ByteArrayInputStream\nimport java.io.File\nimport java.io.FileNotFoundException\nimport java.io.FileOutputStream\nimport java.io.IOException\nimport java.util.concurrent.ConcurrentHashMap\nimport java.util.regex.Pattern\nimport java.util.zip.ZipFile\nimport kotlin.coroutines.coroutineContext\nimport kotlin.math.abs\nimport kotlin.math.max\nimport kotlin.math.min\n\n@Suppress(\"unused\", \"ConstPropertyName\")\nobject BookHelp {\n    private val downloadDir: File = appCtx.externalFiles\n    private const val cacheFolderName = \"book_cache\"\n    private const val cacheImageFolderName = \"images\"\n    private const val cacheEpubFolderName = \"epub\"\n    private val downloadImages = ConcurrentHashMap<String, Mutex>()\n\n    val cachePath = FileUtils.getPath(downloadDir, cacheFolderName)\n\n    fun clearCache() {\n        FileUtils.delete(\n            FileUtils.getPath(downloadDir, cacheFolderName)\n        )\n    }\n\n    fun clearCache(book: Book) {\n        val filePath = FileUtils.getPath(downloadDir, cacheFolderName, book.getFolderName())\n        FileUtils.delete(filePath)\n    }\n\n    fun updateCacheFolder(oldBook: Book, newBook: Book) {\n        val oldFolderName = oldBook.getFolderNameNoCache()\n        val newFolderName = newBook.getFolderNameNoCache()\n        if (oldFolderName == newFolderName) return\n        val oldFolderPath = FileUtils.getPath(\n            downloadDir,\n            cacheFolderName,\n            oldFolderName\n        )\n        val newFolderPath = FileUtils.getPath(\n            downloadDir,\n            cacheFolderName,\n            newFolderName\n        )\n        FileUtils.move(oldFolderPath, newFolderPath)\n    }\n\n    /**\n     * 清除已删除书的缓存 解压缓存\n     */\n    suspend fun clearInvalidCache() {\n        withContext(IO) {\n            val bookFolderNames = hashSetOf<String>()\n            val originNames = hashSetOf<String>()\n            appDb.bookDao.all.forEach {\n                clearComicCache(it)\n                bookFolderNames.add(it.getFolderName())\n                if (it.isEpub) originNames.add(it.originName)\n            }\n            downloadDir.getFile(cacheFolderName)\n                .listFiles()?.forEach { bookFile ->\n                    if (!bookFolderNames.contains(bookFile.name)) {\n                        FileUtils.delete(bookFile.absolutePath)\n                    }\n                }\n            downloadDir.getFile(cacheEpubFolderName)\n                .listFiles()?.forEach { epubFile ->\n                    if (!originNames.contains(epubFile.name)) {\n                        FileUtils.delete(epubFile.absolutePath)\n                    }\n                }\n            FileUtils.delete(ArchiveUtils.TEMP_PATH)\n            val filesDir = appCtx.filesDir\n            FileUtils.delete(\"$filesDir/shareBookSource.json\")\n            FileUtils.delete(\"$filesDir/shareRssSource.json\")\n            FileUtils.delete(\"$filesDir/books.json\")\n        }\n    }\n\n    //清除已经看过的漫画数据\n    private fun clearComicCache(book: Book) {\n        //只处理漫画\n        //为0的时候，不清除已缓存数据\n        if (!book.isImage || AppConfig.imageRetainNum == 0) {\n            return\n        }\n        //向前保留设定数量，向后保留预下载数量\n        val startIndex = book.durChapterIndex - AppConfig.imageRetainNum\n        val endIndex = book.durChapterIndex + AppConfig.preDownloadNum\n        val chapterList = appDb.bookChapterDao.getChapterList(book.bookUrl, startIndex, endIndex)\n        val imgNames = hashSetOf<String>()\n        //获取需要保留章节的图片信息\n        chapterList.forEach {\n            val content = getContent(book, it)\n            if (content != null) {\n                val matcher = AppPattern.imgPattern.matcher(content)\n                while (matcher.find()) {\n                    val src = matcher.group(1) ?: continue\n                    val mSrc = NetworkUtils.getAbsoluteURL(it.url, src)\n                    imgNames.add(\"${MD5Utils.md5Encode16(mSrc)}.${getImageSuffix(mSrc)}\")\n                }\n            }\n        }\n        downloadDir.getFile(\n            cacheFolderName,\n            book.getFolderName(),\n            cacheImageFolderName\n        ).listFiles()?.forEach { imgFile ->\n            if (!imgNames.contains(imgFile.name)) {\n                imgFile.delete()\n            }\n        }\n    }\n\n    suspend fun saveContent(\n        bookSource: BookSource,\n        book: Book,\n        bookChapter: BookChapter,\n        content: String\n    ) {\n        try {\n            saveText(book, bookChapter, content)\n            //saveImages(bookSource, book, bookChapter, content)\n            postEvent(EventBus.SAVE_CONTENT, Pair(book, bookChapter))\n        } catch (e: Exception) {\n            e.printStackTrace()\n            AppLog.put(\"保存正文失败 ${book.name} ${bookChapter.title}\", e)\n        }\n    }\n\n    fun saveText(\n        book: Book,\n        bookChapter: BookChapter,\n        content: String\n    ) {\n        if (content.isEmpty()) return\n        //保存文本\n        FileUtils.createFileIfNotExist(\n            downloadDir,\n            cacheFolderName,\n            book.getFolderName(),\n            bookChapter.getFileName(),\n        ).writeText(content)\n        if (book.isOnLineTxt && AppConfig.tocCountWords) {\n            val wordCount = StringUtils.wordCountFormat(content.length)\n            bookChapter.wordCount = wordCount\n            appDb.bookChapterDao.upWordCount(bookChapter.bookUrl, bookChapter.url, wordCount)\n        }\n    }\n\n    fun flowImages(bookChapter: BookChapter, content: String): Flow<String> {\n        return flow {\n            val matcher = AppPattern.imgPattern.matcher(content)\n            while (matcher.find()) {\n                val src = matcher.group(1) ?: continue\n                val mSrc = NetworkUtils.getAbsoluteURL(bookChapter.url, src)\n                emit(mSrc)\n            }\n        }\n    }\n\n    suspend fun saveImages(\n        bookSource: BookSource,\n        book: Book,\n        bookChapter: BookChapter,\n        content: String,\n        concurrency: Int = AppConfig.threadCount\n    ) = coroutineScope {\n        flowImages(bookChapter, content).onEachParallel(concurrency) { mSrc ->\n            saveImage(bookSource, book, mSrc, bookChapter)\n        }.collect()\n    }\n\n    suspend fun saveImage(\n        bookSource: BookSource?,\n        book: Book,\n        src: String,\n        chapter: BookChapter? = null\n    ) {\n        if (isImageExist(book, src)) {\n            return\n        }\n        val mutex = synchronized(this) {\n            downloadImages.getOrPut(src) { Mutex() }\n        }\n        mutex.lock()\n        try {\n            if (isImageExist(book, src)) {\n                return\n            }\n            val analyzeUrl = AnalyzeUrl(\n                src, source = bookSource, coroutineContext = coroutineContext\n            )\n            val bytes = analyzeUrl.getByteArrayAwait()\n            //某些图片被加密，需要进一步解密\n            runScriptWithContext {\n                ImageUtils.decode(\n                    src, bytes, isCover = false, bookSource, book\n                )\n            }?.let {\n                if (!checkImage(it)) {\n                    // 如果部分图片失效，每次进入正文都会花很长时间再次获取图片数据\n                    // 所以无论如何都要将数据写入到文件里\n                    // throw NoStackTraceException(\"数据异常\")\n                    AppLog.put(\"${book.name} ${chapter?.title} 图片 $src 下载错误 数据异常\")\n                }\n                writeImage(book, src, it)\n            }\n        } catch (e: Exception) {\n            coroutineContext.ensureActive()\n            val msg = \"${book.name} ${chapter?.title} 图片 $src 下载失败\\n${e.localizedMessage}\"\n            AppLog.put(msg, e)\n        } finally {\n            downloadImages.remove(src)\n            mutex.unlock()\n        }\n    }\n\n    fun getImage(book: Book, src: String): File {\n        return downloadDir.getFile(\n            cacheFolderName,\n            book.getFolderName(),\n            cacheImageFolderName,\n            \"${MD5Utils.md5Encode16(src)}.${getImageSuffix(src)}\"\n        )\n    }\n\n    @Synchronized\n    fun writeImage(book: Book, src: String, bytes: ByteArray) {\n        getImage(book, src).createFileIfNotExist().writeBytes(bytes)\n    }\n\n    @Synchronized\n    fun isImageExist(book: Book, src: String): Boolean {\n        return getImage(book, src).exists()\n    }\n\n    fun getImageSuffix(src: String): String {\n        return UrlUtil.getSuffix(src, \"jpg\")\n    }\n\n    @Throws(IOException::class, FileNotFoundException::class)\n    fun getEpubFile(book: Book): ZipFile {\n        val uri = book.getLocalUri()\n        if (uri.isContentScheme()) {\n            FileUtils.createFolderIfNotExist(downloadDir, cacheEpubFolderName)\n            val path = FileUtils.getPath(downloadDir, cacheEpubFolderName, book.originName)\n            val file = File(path)\n            val doc = DocumentFile.fromSingleUri(appCtx, uri)\n                ?: throw IOException(\"文件不存在\")\n            if (!file.exists() || doc.lastModified() > book.latestChapterTime) {\n                LocalBook.getBookInputStream(book).use { inputStream ->\n                    FileOutputStream(file).use { outputStream ->\n                        inputStream.copyTo(outputStream)\n                    }\n                }\n            }\n            return ZipFile(file)\n        }\n        return ZipFile(uri.path)\n    }\n\n    /**\n     * 获取本地书籍文件的ParcelFileDescriptor\n     *\n     * @param book\n     * @return\n     */\n    @Throws(IOException::class, FileNotFoundException::class)\n    fun getBookPFD(book: Book): ParcelFileDescriptor? {\n        val uri = book.getLocalUri()\n        return if (uri.isContentScheme()) {\n            appCtx.contentResolver.openFileDescriptor(uri, \"r\")\n        } else {\n            ParcelFileDescriptor.open(File(uri.path!!), ParcelFileDescriptor.MODE_READ_ONLY)\n        }\n    }\n\n    fun getChapterFiles(book: Book): HashSet<String> {\n        val fileNames = hashSetOf<String>()\n        if (book.isLocalTxt) {\n            return fileNames\n        }\n        FileUtils.createFolderIfNotExist(\n            downloadDir,\n            subDirs = arrayOf(cacheFolderName, book.getFolderName())\n        ).list()?.let {\n            fileNames.addAll(it)\n        }\n        return fileNames\n    }\n\n    /**\n     * 检测该章节是否下载\n     */\n    fun hasContent(book: Book, bookChapter: BookChapter): Boolean {\n        return if (book.isLocalTxt ||\n            (bookChapter.isVolume && bookChapter.url.startsWith(bookChapter.title))\n        ) {\n            true\n        } else {\n            downloadDir.exists(\n                cacheFolderName,\n                book.getFolderName(),\n                bookChapter.getFileName()\n            )\n        }\n    }\n\n    /**\n     * 检测图片是否下载\n     */\n    fun hasImageContent(book: Book, bookChapter: BookChapter): Boolean {\n        if (!hasContent(book, bookChapter)) {\n            return false\n        }\n        var ret = true\n        val op = BitmapFactory.Options()\n        op.inJustDecodeBounds = true\n        getContent(book, bookChapter)?.let {\n            val matcher = AppPattern.imgPattern.matcher(it)\n            while (matcher.find()) {\n                val src = matcher.group(1)!!\n                val image = getImage(book, src)\n                if (!image.exists()) {\n                    ret = false\n                    continue\n                }\n                BitmapFactory.decodeFile(image.absolutePath, op)\n                if (op.outWidth < 1 && op.outHeight < 1) {\n                    if (SvgUtils.getSize(image.absolutePath) != null) {\n                        continue\n                    }\n                    ret = false\n                    image.delete()\n                }\n            }\n        }\n        return ret\n    }\n\n    private fun checkImage(bytes: ByteArray): Boolean {\n        val op = BitmapFactory.Options()\n        op.inJustDecodeBounds = true\n        BitmapFactory.decodeByteArray(bytes, 0, bytes.size, op)\n        if (op.outWidth < 1 && op.outHeight < 1) {\n            return SvgUtils.getSize(ByteArrayInputStream(bytes)) != null\n        }\n        return true\n    }\n\n    /**\n     * 读取章节内容\n     */\n    fun getContent(book: Book, bookChapter: BookChapter): String? {\n        val file = downloadDir.getFile(\n            cacheFolderName,\n            book.getFolderName(),\n            bookChapter.getFileName()\n        )\n        if (file.exists()) {\n            val string = file.readText()\n            if (string.isEmpty()) {\n                return null\n            }\n            return string\n        }\n        if (book.isLocal) {\n            val string = LocalBook.getContent(book, bookChapter)\n            if (string != null && book.isEpub) {\n                saveText(book, bookChapter, string)\n            }\n            return string\n        }\n        return null\n    }\n\n    /**\n     * 删除章节内容\n     */\n    fun delContent(book: Book, bookChapter: BookChapter) {\n        FileUtils.createFileIfNotExist(\n            downloadDir,\n            cacheFolderName,\n            book.getFolderName(),\n            bookChapter.getFileName()\n        ).delete()\n    }\n\n    /**\n     * 设置是否禁用正文的去除重复标题,针对单个章节\n     */\n    fun setRemoveSameTitle(book: Book, bookChapter: BookChapter, removeSameTitle: Boolean) {\n        val fileName = bookChapter.getFileName(\"nr\")\n        val contentProcessor = ContentProcessor.get(book)\n        if (removeSameTitle) {\n            val path = FileUtils.getPath(\n                downloadDir,\n                cacheFolderName,\n                book.getFolderName(),\n                fileName\n            )\n            contentProcessor.removeSameTitleCache.remove(fileName)\n            File(path).delete()\n        } else {\n            FileUtils.createFileIfNotExist(\n                downloadDir,\n                cacheFolderName,\n                book.getFolderName(),\n                fileName\n            )\n            contentProcessor.removeSameTitleCache.add(fileName)\n        }\n    }\n\n    /**\n     * 获取是否去除重复标题\n     */\n    fun removeSameTitle(book: Book, bookChapter: BookChapter): Boolean {\n        val path = FileUtils.getPath(\n            downloadDir,\n            cacheFolderName,\n            book.getFolderName(),\n            bookChapter.getFileName(\"nr\")\n        )\n        return !File(path).exists()\n    }\n\n    /**\n     * 格式化书名\n     */\n    fun formatBookName(name: String): String {\n        return name\n            .replace(AppPattern.nameRegex, \"\")\n            .trim { it <= ' ' }\n    }\n\n    /**\n     * 格式化作者\n     */\n    fun formatBookAuthor(author: String): String {\n        return author\n            .replace(AppPattern.authorRegex, \"\")\n            .trim { it <= ' ' }\n    }\n\n    private val jaccardSimilarity by lazy {\n        JaccardSimilarity()\n    }\n\n    /**\n     * 根据目录名获取当前章节\n     */\n    fun getDurChapter(\n        oldDurChapterIndex: Int,\n        oldDurChapterName: String?,\n        newChapterList: List<BookChapter>,\n        oldChapterListSize: Int = 0\n    ): Int {\n        if (oldDurChapterIndex <= 0) return 0\n        if (newChapterList.isEmpty()) return oldDurChapterIndex\n        val oldChapterNum = getChapterNum(oldDurChapterName)\n        val oldName = getPureChapterName(oldDurChapterName)\n        val newChapterSize = newChapterList.size\n        val durIndex =\n            if (oldChapterListSize == 0) oldDurChapterIndex\n            else oldDurChapterIndex * oldChapterListSize / newChapterSize\n        val min = max(0, min(oldDurChapterIndex, durIndex) - 10)\n        val max = min(newChapterSize - 1, max(oldDurChapterIndex, durIndex) + 10)\n        var nameSim = 0.0\n        var newIndex = 0\n        var newNum = 0\n        if (oldName.isNotEmpty()) {\n            for (i in min..max) {\n                val newName = getPureChapterName(newChapterList[i].title)\n                val temp = jaccardSimilarity.apply(oldName, newName)\n                if (temp > nameSim) {\n                    nameSim = temp\n                    newIndex = i\n                }\n            }\n        }\n        if (nameSim < 0.96 && oldChapterNum > 0) {\n            for (i in min..max) {\n                val temp = getChapterNum(newChapterList[i].title)\n                if (temp == oldChapterNum) {\n                    newNum = temp\n                    newIndex = i\n                    break\n                } else if (abs(temp - oldChapterNum) < abs(newNum - oldChapterNum)) {\n                    newNum = temp\n                    newIndex = i\n                }\n            }\n        }\n        return if (nameSim > 0.96 || abs(newNum - oldChapterNum) < 1) {\n            newIndex\n        } else {\n            min(max(0, newChapterList.size - 1), oldDurChapterIndex)\n        }\n    }\n\n    fun getDurChapter(\n        oldBook: Book,\n        newChapterList: List<BookChapter>\n    ): Int {\n        return oldBook.run {\n            getDurChapter(durChapterIndex, durChapterTitle, newChapterList, totalChapterNum)\n        }\n    }\n\n    private val chapterNamePattern1 by lazy {\n        Pattern.compile(\n            \".*?第([\\\\d零〇一二两三四五六七八九十百千万壹贰叁肆伍陆柒捌玖拾佰仟]+)[章节篇回集话]\"\n        )\n    }\n\n    @Suppress(\"RegExpSimplifiable\")\n    private val chapterNamePattern2 by lazy {\n        Pattern.compile(\n            \"^(?:[\\\\d零〇一二两三四五六七八九十百千万壹贰叁肆伍陆柒捌玖拾佰仟]+[,:、])*([\\\\d零〇一二两三四五六七八九十百千万壹贰叁肆伍陆柒捌玖拾佰仟]+)(?:[,:、]|\\\\.[^\\\\d])\"\n        )\n    }\n\n    private val regexA by lazy {\n        return@lazy \"\\\\s\".toRegex()\n    }\n\n    private fun getChapterNum(chapterName: String?): Int {\n        chapterName ?: return -1\n        val chapterName1 = StringUtils.fullToHalf(chapterName).replace(regexA, \"\")\n        return StringUtils.stringToInt(\n            (\n                    chapterNamePattern1.matcher(chapterName1).takeIf { it.find() }\n                        ?: chapterNamePattern2.matcher(chapterName1).takeIf { it.find() }\n                    )?.group(1)\n                ?: \"-1\"\n        )\n    }\n\n    private val regexOther by lazy {\n        // 所有非字母数字中日韩文字 CJK区+扩展A-F区\n        @Suppress(\"RegExpDuplicateCharacterInClass\")\n        return@lazy \"[^\\\\w\\\\u4E00-\\\\u9FEF〇\\\\u3400-\\\\u4DBF\\\\u20000-\\\\u2A6DF\\\\u2A700-\\\\u2EBEF]\".toRegex()\n    }\n\n    @Suppress(\"RegExpUnnecessaryNonCapturingGroup\", \"RegExpSimplifiable\")\n    private val regexB by lazy {\n        //章节序号，排除处于结尾的状况，避免将章节名替换为空字串\n        return@lazy \"^.*?第(?:[\\\\d零〇一二两三四五六七八九十百千万壹贰叁肆伍陆柒捌玖拾佰仟]+)[章节篇回集话](?!$)|^(?:[\\\\d零〇一二两三四五六七八九十百千万壹贰叁肆伍陆柒捌玖拾佰仟]+[,:、])*(?:[\\\\d零〇一二两三四五六七八九十百千万壹贰叁肆伍陆柒捌玖拾佰仟]+)(?:[,:、](?!$)|\\\\.(?=[^\\\\d]))\".toRegex()\n    }\n\n    private val regexC by lazy {\n        //前后附加内容，整个章节名都在括号中时只剔除首尾括号，避免将章节名替换为空字串\n        return@lazy \"(?!^)(?:[〖【《〔\\\\[{(][^〖【《〔\\\\[{()〕》】〗\\\\]}]+)?[)〕》】〗\\\\]}]$|^[〖【《〔\\\\[{(](?:[^〖【《〔\\\\[{()〕》】〗\\\\]}]+[〕》】〗\\\\]})])?(?!$)\".toRegex()\n    }\n\n    private fun getPureChapterName(chapterName: String?): String {\n        return if (chapterName == null) \"\" else StringUtils.fullToHalf(chapterName)\n            .replace(regexA, \"\")\n            .replace(regexB, \"\")\n            .replace(regexC, \"\")\n            .replace(regexOther, \"\")\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/help/book/ContentHelp.kt",
    "content": "package io.legado.app.help.book\n\nimport java.util.regex.Pattern\nimport kotlin.math.max\nimport kotlin.math.min\n\n@Suppress(\"SameParameterValue\", \"RegExpRedundantEscape\")\nobject ContentHelp {\n\n    /**\n     * 段落重排算法入口。把整篇内容输入，连接错误的分段，再把每个段落调用其他方法重新切分\n     *\n     * @param content     正文\n     * @param chapterName 标题\n     * @return\n     */\n    fun reSegment(content: String, chapterName: String): String {\n        var content1 = content\n        val dict = makeDict(content1)\n        var p = content1\n            .replace(\"&quot;\".toRegex(), \"“\")\n            .replace(\"[:：]['\\\"‘”“]+\".toRegex(), \"：“\")\n            .replace(\"[\\\"”“]+\\\\s*[\\\"”“][\\\\s\\\"”“]*\".toRegex(), \"”\\n“\")\n            .split(\"\\n(\\\\s*)\".toRegex()).toTypedArray()\n\n        //初始化StringBuilder的长度,在原content的长度基础上做冗余\n        var buffer = StringBuilder((content1.length * 1.15).toInt())\n        //          章节的文本格式为章节标题-空行-首段，所以处理段落时需要略过第一行文本。\n        buffer.append(\"  \")\n        if (chapterName.trim { it <= ' ' } != p[0].trim { it <= ' ' }) {\n            // 去除段落内空格。unicode 3000 象形字间隔（中日韩符号和标点），不包含在\\s内\n            buffer.append(p[0].replace(\"[\\u3000\\\\s]+\".toRegex(), \"\"))\n        }\n\n        //如果原文存在分段错误，需要把段落重新黏合\n        for (i in 1 until p.size) {\n            if (match(MARK_SENTENCES_END, buffer.last())\n                || (match(MARK_QUOTATION_RIGHT, buffer.last())\n                        && match(MARK_SENTENCES_END, buffer[buffer.lastIndex - 1]))\n            ) {\n                buffer.append(\"\\n\")\n            }\n            // 段落开头以外的地方不应该有空格\n            // 去除段落内空格。unicode 3000 象形字间隔（中日韩符号和标点），不包含在\\s内\n            buffer.append(p[i].replace(\"[\\u3000\\\\s]\".toRegex(), \"\"))\n        }\n        //     预分段预处理\n        //         ”“处理为”\\n“。\n        //         ”。“处理为”。\\n“。不考虑“？”  “！”的情况。\n        // ”。xxx处理为 ”。\\n xxx\n        p = buffer.toString()\n            .replace(\"[\\\"”“]+\\\\s*[\\\"”“]+\".toRegex(), \"”\\n“\")\n            .replace(\"[\\\"”“]+(？。！?!~)[\\\"”“]+\".toRegex(), \"”$1\\n“\")\n            .replace(\"[\\\"”“]+(？。！?!~)([^\\\"”“])\".toRegex(), \"”$1\\n$2\")\n            .replace(\n                \"([问说喊唱叫骂道着答])[\\\\.。]\".toRegex(),\n                \"$1。\\n\"\n            )\n            .split(\"\\n\".toRegex()).toTypedArray()\n        buffer = StringBuilder((content1.length * 1.15).toInt())\n        for (s in p) {\n            buffer.append(\"\\n\")\n            buffer.append(findNewLines(s, dict))\n        }\n        buffer = reduceLength(buffer)\n        content1 = (buffer.toString() //         处理章节头部空格和换行\n            .replaceFirst(\"^\\\\s+\".toRegex(), \"\")\n            .replace(\"\\\\s*[\\\"”“]+\\\\s*[\\\"”“][\\\\s\\\"”“]*\".toRegex(), \"”\\n“\")\n            .replace(\"[:：][”“\\\"\\\\s]+\".toRegex(), \"：“\")\n            .replace(\"\\n[\\\"“”]([^\\n\\\"“”]+)([,:，：][\\\"”“])([^\\n\\\"“”]+)\".toRegex(), \"\\n$1：“$3\")\n            .replace(\"\\n(\\\\s*)\".toRegex(), \"\\n\"))\n        return content1\n    }\n\n    /**\n     * 强制切分，减少段落内的句子\n     * 如果连续2对引号的段落没有提示语，进入对话模式。最后一对引号后强制切分段落\n     * 如果引号内的内容长于5句，可能引号状态有误，随机分段\n     * 如果引号外的内容长于3句，随机分段\n     *\n     * @param str\n     * @return\n     */\n    private fun reduceLength(str: StringBuilder): StringBuilder {\n        val p = str.toString().split(\"\\n\".toRegex()).toTypedArray()\n        val l = p.size\n        val b = BooleanArray(l)\n        for (i in 0 until l) {\n            b[i] = p[i].matches(PARAGRAPH_DIAGLOG)\n        }\n        var dialogue = 0\n        for (i in 0 until l) {\n            if (b[i]) {\n                if (dialogue < 0) dialogue = 1 else if (dialogue < 2) dialogue++\n            } else {\n                if (dialogue > 1) {\n                    p[i] = splitQuote(p[i])\n                    dialogue--\n                } else if (dialogue > 0 && i < l - 2) {\n                    if (b[i + 1]) p[i] = splitQuote(p[i])\n                }\n            }\n        }\n        val string = StringBuilder()\n        for (i in 0 until l) {\n            string.append('\\n')\n            string.append(p[i])\n            //System.out.print(\" \"+b[i]);\n        }\n        //System.out.println(\" \" + str);\n        return string\n    }\n\n    // 强制切分进入对话模式后，未构成 “xxx” 形式的段落\n    private fun splitQuote(str: String): String {\n        val length = str.length\n        if (length < 3) return str\n        if (match(MARK_QUOTATION, str[0])) {\n            val i = seekIndex(str, MARK_QUOTATION, 1, length - 2, true) + 1\n            if (i > 1) if (!match(MARK_QUOTATION_BEFORE, str[i - 1])) {\n                return \"${str.take(i)}\\n${str.substring(i)}\"\n            }\n        } else if (match(MARK_QUOTATION, str[length - 1])) {\n            val i = length - 1 - seekIndex(str, MARK_QUOTATION, 1, length - 2, false)\n            if (i > 1) {\n                if (!match(MARK_QUOTATION_BEFORE, str[i - 1])) {\n                    return \"${str.take(i)}\\n${str.substring(i)}\"\n                }\n            }\n        }\n        return str\n    }\n\n    /**\n     * 计算随机插入换行符的位置。\n     * @param str 字符串\n     * @param offset 传回的结果需要叠加的偏移量\n     * @param min 最低几个句子，随机插入换行\n     * @param gain 倍率。每个句子插入换行的数学期望 = 1 / gain , gain越大越不容易插入换行\n     * @return\n     */\n    private fun forceSplit(\n        str: String,\n        offset: Int,\n        min: Int,\n        gain: Int,\n        tigger: Int\n    ): ArrayList<Int> {\n        val result = ArrayList<Int>()\n        val arrayEnd = seekIndexes(str, MARK_SENTENCES_END_P, 0, str.length - 2, true)\n        val arrayMid = seekIndexes(str, MARK_SENTENCES_MID, 0, str.length - 2, true)\n        if (arrayEnd.size < tigger && arrayMid.size < tigger * 3) return result\n        var j = 0\n        var i = min\n        while (i < arrayEnd.size) {\n            var k = 0\n            while (j < arrayMid.size) {\n                if (arrayMid[j] < arrayEnd[i]) k++\n                j++\n            }\n            if (Math.random() * gain < 0.8 + k / 2.5) {\n                result.add(arrayEnd[i] + offset)\n                i = max(i + min, i)\n            }\n            i++\n        }\n        return result\n    }\n\n    // 对内容重新划分段落.输入参数str已经使用换行符预分割\n    private fun findNewLines(str: String, dict: List<String>): String {\n        val string = StringBuilder(str)\n        // 标记string中每个引号的位置.特别的，用引号进行列举时视为只有一对引号。 如：“锅”、“碗”视为“锅、碗”，从而避免误断句。\n        val arrayQuote: MutableList<Int> = ArrayList()\n        //  标记插入换行符的位置，int为插入位置（str的char下标）\n        var insN = ArrayList<Int>()\n\n        //mod[i]标记str的每一段处于引号内还是引号外。范围： str.substring( array_quote.get(i), array_quote.get(i+1) )的状态。\n        //长度：array_quote.size(),但是初始化时未预估占用的长度，用空间换时间\n        //0未知，正数引号内，负数引号外。\n        //如果相邻的两个标记都为+1，那么需要增加1个引号。\n        //引号内不进行断句\n        val mod = IntArray(str.length)\n        var waitClose = false\n        for (i in str.indices) {\n            val c = str[i]\n            if (match(MARK_QUOTATION, c)) {\n                val size = arrayQuote.size\n\n                //        把“xxx”、“yy”合并为“xxx_yy”进行处理\n                if (size > 0) {\n                    val quotePre = arrayQuote[size - 1]\n                    if (i - quotePre == 2) {\n                        var remove = false\n                        if (waitClose) {\n                            if (match(\",，、/\", str[i - 1])) {\n                                // 考虑出现“和”这种特殊情况\n                                remove = true\n                            }\n                        } else if (match(\",，、/和与或\", str[i - 1])) {\n                            remove = true\n                        }\n                        if (remove) {\n                            string.setCharAt(i, '“')\n                            string.setCharAt(i - 2, '”')\n                            arrayQuote.removeAt(size - 1)\n                            mod[size - 1] = 1\n                            mod[size] = -1\n                            continue\n                        }\n                    }\n                }\n                arrayQuote.add(i)\n\n                //  为xxx：“xxx”做标记\n                if (i > 1) {\n                    // 当前发言的正引号的前一个字符\n                    val charB1 = str[i - 1]\n                    // 上次发言的正引号的前一个字符\n                    var charB2 = 0.toChar()\n                    if (match(MARK_QUOTATION_BEFORE, charB1)) {\n                        // 如果不是第一处引号，寻找上一处断句，进行分段\n                        if (arrayQuote.size > 1) {\n                            val lastQuote = arrayQuote[arrayQuote.size - 2]\n                            var p = 0\n                            if (charB1 == ',' || charB1 == '，') {\n                                if (arrayQuote.size > 2) {\n                                    p = arrayQuote[arrayQuote.size - 3]\n                                    if (p > 0) {\n                                        charB2 = str[p - 1]\n                                    }\n                                }\n                            }\n                            //if(char_b2=='.' || char_b2=='。')\n                            if (match(MARK_SENTENCES_END_P, charB2)) {\n                                insN.add(p - 1)\n                            } else if (!match(\"的\", charB2)) {\n                                val lastEnd = seekLast(str, MARK_SENTENCES_END, i, lastQuote)\n                                if (lastEnd > 0) insN.add(lastEnd) else insN.add(lastQuote)\n                            }\n                        }\n                        waitClose = true\n                        mod[size] = 1\n                        if (size > 0) {\n                            mod[size - 1] = -1\n                            if (size > 1) {\n                                mod[size - 2] = 1\n                            }\n                        }\n                    } else if (waitClose) {\n                        run {\n                            waitClose = false\n                            insN.add(i)\n                        }\n                    }\n                }\n            }\n        }\n        val size = arrayQuote.size\n\n\n        //标记循环状态，此位置前的引号是否已经配对\n        var opend = false\n        if (size > 0) {\n            //第1次遍历array_quote，令其元素的值不为0\n            for (i in 0 until size) {\n                if (mod[i] > 0) {\n                    opend = true\n                } else if (mod[i] < 0) {\n                    //连续2个反引号表明存在冲突，强制把前一个设为正引号\n                    if (!opend) {\n                        if (i > 0) mod[i] = 3\n                    }\n                    opend = false\n                } else {\n                    opend = !opend\n                    if (opend) mod[i] = 2 else mod[i] = -2\n                }\n            }\n            //        修正，断尾必须封闭引号\n            if (opend) {\n                if (arrayQuote[size - 1] - string.length > -3) {\n                    //if((match(MARK_QUOTATION,string.charAt(string.length()-1)) || match(MARK_QUOTATION,string.charAt(string.length()-2)))){\n                    if (size > 1) mod[size - 2] = 4\n                    // 0<=i<size,故无需判断size>=1\n                    mod[size - 1] = -4\n                } else if (!match(MARK_SENTENCES_SAY, string[string.length - 2])) string.append(\n                    \"”\"\n                )\n            }\n\n\n            //第2次循环，mod[i]由负变正时，前1字符如果是句末，需要插入换行\n            var loop2Mod1 = -1 //上一个引号跟随内容的状态\n            var loop2Mod2: Int //当前引号跟随内容的状态\n            var i = 0\n            var j = arrayQuote[0] - 1 //当前引号前一字符的序号\n            if (j < 0) {\n                i = 1\n                loop2Mod1 = 0\n            }\n            while (i < size) {\n                j = arrayQuote[i] - 1\n                loop2Mod2 = mod[i]\n                if (loop2Mod1 < 0 && loop2Mod2 > 0) {\n                    if (match(MARK_SENTENCES_END, string[j])) insN.add(j)\n                }\n                loop2Mod1 = loop2Mod2\n                i++\n            }\n        }\n\n        //第3次循环，匹配并插入换行。\n        //\"xxxx\" xxxx。\\n xxx“xxxx”\n        //未实现\n\n        // 使用字典验证ins_n , 避免插入不必要的换行。\n        // 由于目前没有插入、的列表，无法解决 “xx”、“xx”“xx” 被插入换行的问题\n        val insN1 = ArrayList<Int>()\n        for (i in insN) {\n            if (match(\"\\\"'”“\", string[i])) {\n                val start: Int = seekLast(\n                    str,\n                    \"\\\"'”“\",\n                    i - 1,\n                    i - WORD_MAX_LENGTH\n                )\n                if (start > 0) {\n                    val word = str.substring(start + 1, i)\n                    if (dict.contains(word)) {\n                        //System.out.println(\"使用字典验证 跳过\\tins_n=\" + i + \"  word=\" + word);\n                        //引号内如果是字典词条，后方不插入换行符（前方不需要优化）\n                        continue\n                    } else {\n                        //System.out.println(\"使用字典验证 插入\\tins_n=\" + i + \"  word=\" + word);\n                        if (match(\"的地得\", str[start])) {\n                            //xx的“xx”，后方不插入换行符（前方不需要优化）\n                            continue\n                        }\n                    }\n                }\n            }\n            insN1.add(i)\n        }\n        insN = insN1\n\n//        随机在句末插入换行符\n        insN = ArrayList(HashSet(insN))\n        insN.sort()\n        run {\n            var subs: String\n            var j = 0\n            var progress = 0\n            var nextLine = -1\n            if (insN.isNotEmpty()) nextLine = insN[j]\n            var gain = 3\n            var min = 0\n            var trigger = 2\n            for (i in arrayQuote.indices) {\n                val qutoe = arrayQuote[i]\n                if (qutoe > 0) {\n                    gain = 4\n                    min = 2\n                    trigger = 4\n                } else {\n                    gain = 3\n                    min = 0\n                    trigger = 2\n                }\n\n//            把引号前的换行符与内容相间插入\n                while (j < insN.size) {\n\n//                如果下一个换行符在当前引号前，那么需要此次处理.如果紧挨当前引号，需要考虑插入引号的情况\n                    if (nextLine >= qutoe) break\n                    nextLine = insN[j]\n                    if (progress < nextLine) {\n                        subs = string.substring(progress, nextLine)\n                        insN.addAll(forceSplit(subs, progress, min, gain, trigger))\n                        progress = nextLine + 1\n                    }\n                    j++\n                }\n                if (progress < qutoe) {\n                    subs = string.substring(progress, qutoe + 1)\n                    insN.addAll(forceSplit(subs, progress, min, gain, trigger))\n                    progress = qutoe + 1\n                }\n            }\n            while (j < insN.size) {\n                nextLine = insN[j]\n                if (progress < nextLine) {\n                    subs = string.substring(progress, nextLine)\n                    insN.addAll(forceSplit(subs, progress, min, gain, trigger))\n                    progress = nextLine + 1\n                }\n                j++\n            }\n            if (progress < string.length) {\n                subs = string.substring(progress, string.length)\n                insN.addAll(forceSplit(subs, progress, min, gain, trigger))\n            }\n        }\n\n//     根据段落状态修正引号方向、计算需要插入引号的位置\n//     ins_quote跟随array_quote   ins_quote[i]!=0,则array_quote.get(i)的引号前需要前插入'”'\n        val insQuote = BooleanArray(size)\n        opend = false\n        for (i in 0 until size) {\n            val p = arrayQuote[i]\n            if (mod[i] > 0) {\n                string.setCharAt(p, '“')\n                if (opend) insQuote[i] = true\n                opend = true\n            } else if (mod[i] < 0) {\n                string.setCharAt(p, '”')\n                opend = false\n            } else {\n                opend = !opend\n                if (opend) string.setCharAt(p, '“') else string.setCharAt(p, '”')\n            }\n        }\n        insN = ArrayList(HashSet(insN))\n        insN.sort()\n\n//     完成字符串拼接（从string复制、插入引号和换行\n//     ins_quote 在引号前插入一个引号。   ins_quote[i]!=0,则array_quote.get(i)的引号前需要前插入'”'\n//     ins_n 插入换行。数组的值表示插入换行符的位置\n        val buffer = StringBuilder((str.length * 1.15).toInt())\n        var j = 0\n        var progress = 0\n        var nextLine = -1\n        if (insN.isNotEmpty()) nextLine = insN[j]\n        for (i in arrayQuote.indices) {\n            val quote = arrayQuote[i]\n\n//            把引号前的换行符与内容相间插入\n            while (j < insN.size) {\n\n//                如果下一个换行符在当前引号前，那么需要此次处理.如果紧挨当前引号，需要考虑插入引号的情况\n                if (nextLine >= quote) break\n                nextLine = insN[j]\n                buffer.append(string, progress, nextLine + 1)\n                buffer.append('\\n')\n                progress = nextLine + 1\n                j++\n            }\n            if (progress < quote) {\n                buffer.append(string, progress, quote + 1)\n                progress = quote + 1\n            }\n            if (insQuote[i] && buffer.length > 2) {\n                if (buffer[buffer.length - 1] == '\\n') buffer.append('“') else buffer.insert(\n                    buffer.length - 1,\n                    \"”\\n\"\n                )\n            }\n        }\n        while (j < insN.size) {\n            nextLine = insN[j]\n            if (progress <= nextLine) {\n                buffer.append(string, progress, nextLine + 1)\n                buffer.append('\\n')\n                progress = nextLine + 1\n            }\n            j++\n        }\n        if (progress < string.length) {\n            buffer.append(string, progress, string.length)\n        }\n        return buffer.toString()\n    }\n\n    /**\n     * 从字符串提取引号包围,且不止出现一次的内容为字典\n     *\n     * @param str\n     * @return 词条列表\n     */\n    private fun makeDict(str: String): List<String> {\n\n        // 引号中间不包含任何标点\n        val patten = Pattern.compile(\n            \"\"\"\n          (?<=[\"'”“])([^\n          \\p{P}]{1,$WORD_MAX_LENGTH})(?=[\"'”“])\n          \"\"\".trimIndent()\n        )\n        //Pattern patten = Pattern.compile(\"(?<=[\\\"'”“])([^\\n\\\"'”“]{1,16})(?=[\\\"'”“])\");\n        val matcher = patten.matcher(str)\n        val cache: MutableList<String> = ArrayList()\n        val dict: MutableList<String> = ArrayList()\n        while (matcher.find()) {\n            val word = matcher.group()\n            if (cache.contains(word)) {\n                if (!dict.contains(word)) dict.add(word)\n            } else cache.add(word)\n        }\n        return dict\n    }\n\n    /**\n     * 计算匹配到字典的每个字符的位置\n     *\n     * @param str     待匹配的字符串\n     * @param key     字典\n     * @param from    从字符串的第几个字符开始匹配\n     * @param to      匹配到第几个字符结束\n     * @param inOrder 是否按照从前向后的顺序匹配\n     * @return 返回距离构成的ArrayList<Int>\n     */\n    private fun seekIndexes(\n        str: String,\n        key: String,\n        from: Int,\n        to: Int,\n        inOrder: Boolean\n    ): ArrayList<Int> {\n        val list = ArrayList<Int>()\n        if (str.length - from < 1) return list\n        var i = 0\n        if (from > 0) i = from\n        var t = str.length\n        if (to > 0) t = min(t, to)\n        var c: Char\n        while (i < t) {\n            c = if (inOrder) str[i] else str[str.length - i - 1]\n            if (key.indexOf(c) != -1) {\n                if (list.isNotEmpty() && i - list.last() == 1) {\n                    list[list.lastIndex] = i\n                } else {\n                    list.add(i)\n                }\n            }\n            i++\n        }\n        return list\n    }\n\n    /**\n     * 计算字符串最后出现与字典中字符匹配的位置\n     *\n     * @param str  数据字符串\n     * @param key  字典字符串\n     * @param from 从哪个字符开始匹配，默认最末位\n     * @param to   匹配到哪个字符（不包含此字符）默认0\n     * @return 位置（正向计算)\n     */\n    private fun seekLast(str: String, key: String, from: Int, to: Int): Int {\n        if (str.length - from < 1) return -1\n        var i = str.lastIndex\n        if (from < i && i > 0) i = from\n        var t = 0\n        if (to > 0) t = to\n        var c: Char\n        while (i > t) {\n            c = str[i]\n            if (key.indexOf(c) != -1) {\n                return i\n            }\n            i--\n        }\n        return -1\n    }\n\n    /**\n     * 计算字符串与字典中字符的最短距离\n     *\n     * @param str     数据字符串\n     * @param key     字典字符串\n     * @param from    从哪个字符开始匹配，默认0\n     * @param to      匹配到哪个字符（不包含此字符）默认匹配到最末位\n     * @param inOrder 是否从正向开始匹配\n     * @return 返回最短距离, 注意不是str的char的下标\n     */\n    private fun seekIndex(str: String, key: String, from: Int, to: Int, inOrder: Boolean): Int {\n        if (str.length - from < 1) return -1\n        var i = 0\n        if (from > 0) i = from\n        var t = str.length\n        if (to > 0) t = min(t, to)\n        var c: Char\n        while (i < t) {\n            c = if (inOrder) str[i] else str[str.length - i - 1]\n            if (key.indexOf(c) != -1) {\n                return i\n            }\n            i++\n        }\n        return -1\n    }\n\n    /* 搜寻引号并进行分段。处理了一、二、五三类常见情况\n    参照百科词条[引号#应用示例](https://baike.baidu.com/item/%E5%BC%95%E5%8F%B7/998963?#5)对引号内容进行矫正并分句。\n    一、完整引用说话内容，在反引号内侧有断句标点。例如：\n            1) 丫姑折断几枝扔下来，边叫我的小名儿边说：“先喂饱你！”\n            2）“哎呀，真是美极了！”皇帝说，“我十分满意！”\n            3）“怕什么！海的美就在这里！”我说道。\n    二、部分引用，在反引号外侧有断句标点：\n            4）适当地改善自己的生活，岂但“你管得着吗”，而且是顺乎天理，合乎人情的。\n            5）现代画家徐悲鸿笔下的马，正如有的评论家所说的那样，“形神兼备，充满生机”。\n            6）唐朝的张嘉贞说它“制造奇特，人不知其所为”。\n    三、一段接着一段地直接引用时，中间段落只在段首用起引号，该段段尾却不用引回号。但是正统文学不在考虑范围内。\n    四、引号里面又要用引号时，外面一层用双引号，里面一层用单引号。暂时不需要考虑\n    五、反语和强调，周围没有断句符号。\n    */\n\n    //  句子结尾的标点。因为引号可能存在误判，不包含引号。\n    private const val MARK_SENTENCES_END = \"？。！?!~\"\n    private const val MARK_SENTENCES_END_P = \".？。！?!~\"\n\n    //  句中标点，由于某些网站常把“，”写为\".\"，故英文句点按照句中标点判断\n    private const val MARK_SENTENCES_MID = \".，、,—…\"\n    private const val MARK_SENTENCES_SAY = \"问说喊唱叫骂道着答\"\n\n    //  XXX说：“”的冒号\n    private const val MARK_QUOTATION_BEFORE = \"，：,:\"\n\n    //  引号\n    private const val MARK_QUOTATION = \"\\\"“”\"\n    private const val MARK_QUOTATION_RIGHT = \"\\\"”\"\n    private val PARAGRAPH_DIAGLOG = \"^[\\\"”“][^\\\"”“]+[\\\"”“]$\".toRegex()\n\n    //  限制字典的长度\n    private const val WORD_MAX_LENGTH = 16\n\n    private fun match(rule: String, chr: Char): Boolean {\n        return rule.indexOf(chr) != -1\n    }\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/help/book/ContentProcessor.kt",
    "content": "package io.legado.app.help.book\n\nimport android.os.Build\nimport io.legado.app.constant.AppLog\nimport io.legado.app.constant.AppPattern.spaceRegex\nimport io.legado.app.data.appDb\nimport io.legado.app.data.entities.Book\nimport io.legado.app.data.entities.BookChapter\nimport io.legado.app.data.entities.ReplaceRule\nimport io.legado.app.exception.RegexTimeoutException\nimport io.legado.app.help.config.AppConfig\nimport io.legado.app.help.config.ReadBookConfig\nimport io.legado.app.utils.ChineseUtils\nimport io.legado.app.utils.escapeRegex\nimport io.legado.app.utils.replace\nimport io.legado.app.utils.stackTraceStr\nimport io.legado.app.utils.toastOnUi\nimport kotlinx.coroutines.CancellationException\nimport splitties.init.appCtx\nimport java.lang.ref.WeakReference\nimport java.util.concurrent.CopyOnWriteArrayList\nimport java.util.regex.Pattern\n\nclass ContentProcessor private constructor(\n    private val bookName: String,\n    private val bookOrigin: String\n) {\n\n    companion object {\n        private val processors = hashMapOf<String, WeakReference<ContentProcessor>>()\n        private val isAndroid8 = Build.VERSION.SDK_INT in 26..27\n\n        fun get(book: Book) = get(book.name, book.origin)\n\n        fun get(bookName: String, bookOrigin: String): ContentProcessor {\n            val processorWr = processors[bookName + bookOrigin]\n            var processor: ContentProcessor? = processorWr?.get()\n            if (processor == null) {\n                processor = ContentProcessor(bookName, bookOrigin)\n                processors[bookName + bookOrigin] = WeakReference(processor)\n            }\n            return processor\n        }\n\n        fun upReplaceRules() {\n            processors.forEach {\n                it.value.get()?.upReplaceRules()\n            }\n        }\n\n    }\n\n    private val titleReplaceRules = CopyOnWriteArrayList<ReplaceRule>()\n    private val contentReplaceRules = CopyOnWriteArrayList<ReplaceRule>()\n    val removeSameTitleCache = hashSetOf<String>()\n\n    init {\n        upReplaceRules()\n        upRemoveSameTitle()\n    }\n\n    fun upReplaceRules() {\n        titleReplaceRules.run {\n            clear()\n            addAll(appDb.replaceRuleDao.findEnabledByTitleScope(bookName, bookOrigin))\n        }\n        contentReplaceRules.run {\n            clear()\n            addAll(appDb.replaceRuleDao.findEnabledByContentScope(bookName, bookOrigin))\n        }\n    }\n\n    private fun upRemoveSameTitle() {\n        val book = appDb.bookDao.getBookByOrigin(bookName, bookOrigin) ?: return\n        removeSameTitleCache.clear()\n        val files = BookHelp.getChapterFiles(book).filter {\n            it.endsWith(\"nr\")\n        }\n        removeSameTitleCache.addAll(files)\n    }\n\n    fun getTitleReplaceRules(): List<ReplaceRule> {\n        return titleReplaceRules\n    }\n\n    @Suppress(\"MemberVisibilityCanBePrivate\")\n    fun getContentReplaceRules(): List<ReplaceRule> {\n        return contentReplaceRules\n    }\n\n    fun getContent(\n        book: Book,\n        chapter: BookChapter,\n        content: String,\n        includeTitle: Boolean = true,\n        useReplace: Boolean = true,\n        chineseConvert: Boolean = true,\n        reSegment: Boolean = true\n    ): BookContent {\n        var mContent = content\n        var sameTitleRemoved = false\n        var effectiveReplaceRules: ArrayList<ReplaceRule>? = null\n        if (content != \"null\") {\n            //去除重复标题\n            val fileName = chapter.getFileName(\"nr\")\n            if (!removeSameTitleCache.contains(fileName)) try {\n                val name = Pattern.quote(book.name)\n                var title = chapter.title.escapeRegex().replace(spaceRegex, \"\\\\\\\\s*\")\n                var matcher = Pattern.compile(\"^(\\\\s|\\\\p{P}|${name})*${title}(\\\\s)*\")\n                    .matcher(mContent)\n                if (matcher.find()) {\n                    mContent = mContent.substring(matcher.end())\n                    sameTitleRemoved = true\n                } else if (useReplace && book.getUseReplaceRule()) {\n                    title = Pattern.quote(\n                        chapter.getDisplayTitle(\n                            titleReplaceRules,\n                            chineseConvert = false\n                        )\n                    )\n                    matcher = Pattern.compile(\"^(\\\\s|\\\\p{P}|${name})*${title}(\\\\s)*\")\n                        .matcher(mContent)\n                    if (matcher.find()) {\n                        mContent = mContent.substring(matcher.end())\n                        sameTitleRemoved = true\n                    }\n                }\n            } catch (e: Exception) {\n                AppLog.put(\"去除重复标题出错\\n${e.localizedMessage}\", e)\n            }\n            if (reSegment && book.getReSegment()) {\n                //重新分段\n                mContent = ContentHelp.reSegment(mContent, chapter.title)\n            }\n            if (chineseConvert) {\n                //简繁转换\n                try {\n                    when (AppConfig.chineseConverterType) {\n                        1 -> mContent = ChineseUtils.t2s(mContent)\n                        2 -> mContent = ChineseUtils.s2t(mContent)\n                    }\n                } catch (_: Exception) {\n                    appCtx.toastOnUi(\"简繁转换出错\")\n                }\n            }\n            if (useReplace && book.getUseReplaceRule()) {\n                //替换\n                effectiveReplaceRules = arrayListOf()\n                mContent = mContent.lines().joinToString(\"\\n\") { it.trim() }\n                getContentReplaceRules().forEach { item ->\n                    if (item.pattern.isEmpty()) {\n                        return@forEach\n                    }\n                    try {\n                        val tmp = if (item.isRegex) {\n                            mContent.replace(\n                                item.regex,\n                                item.replacement,\n                                item.getValidTimeoutMillisecond()\n                            )\n                        } else {\n                            mContent.replace(item.pattern, item.replacement)\n                        }\n                        if (mContent != tmp) {\n                            effectiveReplaceRules.add(item)\n                            mContent = tmp\n                        }\n                    } catch (e: RegexTimeoutException) {\n                        item.isEnabled = false\n                        appDb.replaceRuleDao.update(item)\n                        mContent = item.name + e.stackTraceStr\n                    } catch (_: CancellationException) {\n                    } catch (e: Exception) {\n                        AppLog.put(\"替换净化: 规则 ${item.name}替换出错.\\n${mContent}\", e)\n                        appCtx.toastOnUi(\"替换净化: 规则 ${item.name}替换出错\")\n                    }\n                }\n            }\n        }\n        if (includeTitle) {\n            //重新添加标题\n            mContent = chapter.getDisplayTitle(\n                getTitleReplaceRules(),\n                useReplace = useReplace && book.getUseReplaceRule()\n            ) + \"\\n\" + mContent\n        }\n        if (isAndroid8) {\n            mContent = mContent.replace('\\u00A0', ' ')\n        }\n        val contents = arrayListOf<String>()\n        mContent.split(\"\\n\").forEach { str ->\n            val paragraph = str.trim {\n                it.code <= 0x20 || it == '　'\n            }\n            if (paragraph.isNotEmpty()) {\n                if (contents.isEmpty() && includeTitle) {\n                    contents.add(paragraph)\n                } else {\n                    contents.add(\"${ReadBookConfig.paragraphIndent}$paragraph\")\n                }\n            }\n        }\n        return BookContent(sameTitleRemoved, contents, effectiveReplaceRules)\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/help/config/AppConfig.kt",
    "content": "package io.legado.app.help.config\n\nimport android.content.SharedPreferences\nimport android.os.Build\nimport io.legado.app.BuildConfig\nimport io.legado.app.constant.AppConst\nimport io.legado.app.constant.PreferKey\nimport io.legado.app.data.appDb\nimport io.legado.app.utils.canvasrecorder.CanvasRecorderFactory\nimport io.legado.app.utils.getPrefBoolean\nimport io.legado.app.utils.getPrefInt\nimport io.legado.app.utils.getPrefLong\nimport io.legado.app.utils.getPrefString\nimport io.legado.app.utils.isNightMode\nimport io.legado.app.utils.putPrefBoolean\nimport io.legado.app.utils.putPrefInt\nimport io.legado.app.utils.putPrefLong\nimport io.legado.app.utils.putPrefString\nimport io.legado.app.utils.removePref\nimport io.legado.app.utils.sysConfiguration\nimport io.legado.app.utils.toastOnUi\nimport splitties.init.appCtx\n\n@Suppress(\"MemberVisibilityCanBePrivate\", \"ConstPropertyName\")\nobject AppConfig : SharedPreferences.OnSharedPreferenceChangeListener {\n    val isCronet = appCtx.getPrefBoolean(PreferKey.cronet)\n    var useAntiAlias = appCtx.getPrefBoolean(PreferKey.antiAlias)\n    var userAgent: String = getPrefUserAgent()\n    var isEInkMode = appCtx.getPrefString(PreferKey.themeMode) == \"3\"\n    var clickActionTL = appCtx.getPrefInt(PreferKey.clickActionTL, 2)\n    var clickActionTC = appCtx.getPrefInt(PreferKey.clickActionTC, 2)\n    var clickActionTR = appCtx.getPrefInt(PreferKey.clickActionTR, 1)\n    var clickActionML = appCtx.getPrefInt(PreferKey.clickActionML, 2)\n    var clickActionMC = appCtx.getPrefInt(PreferKey.clickActionMC, 0)\n    var clickActionMR = appCtx.getPrefInt(PreferKey.clickActionMR, 1)\n    var clickActionBL = appCtx.getPrefInt(PreferKey.clickActionBL, 2)\n    var clickActionBC = appCtx.getPrefInt(PreferKey.clickActionBC, 1)\n    var clickActionBR = appCtx.getPrefInt(PreferKey.clickActionBR, 1)\n    var themeMode = appCtx.getPrefString(PreferKey.themeMode, \"0\")\n    var useDefaultCover = appCtx.getPrefBoolean(PreferKey.useDefaultCover, false)\n    var optimizeRender = CanvasRecorderFactory.isSupport\n            && appCtx.getPrefBoolean(PreferKey.optimizeRender, false)\n    var recordLog = appCtx.getPrefBoolean(PreferKey.recordLog)\n\n    override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) {\n        when (key) {\n            PreferKey.themeMode -> {\n                themeMode = appCtx.getPrefString(PreferKey.themeMode, \"0\")\n                isEInkMode = themeMode == \"3\"\n            }\n\n            PreferKey.clickActionTL -> clickActionTL =\n                appCtx.getPrefInt(PreferKey.clickActionTL, 2)\n\n            PreferKey.clickActionTC -> clickActionTC =\n                appCtx.getPrefInt(PreferKey.clickActionTC, 2)\n\n            PreferKey.clickActionTR -> clickActionTR =\n                appCtx.getPrefInt(PreferKey.clickActionTR, 1)\n\n            PreferKey.clickActionML -> clickActionML =\n                appCtx.getPrefInt(PreferKey.clickActionML, 2)\n\n            PreferKey.clickActionMC -> clickActionMC =\n                appCtx.getPrefInt(PreferKey.clickActionMC, 0)\n\n            PreferKey.clickActionMR -> clickActionMR =\n                appCtx.getPrefInt(PreferKey.clickActionMR, 1)\n\n            PreferKey.clickActionBL -> clickActionBL =\n                appCtx.getPrefInt(PreferKey.clickActionBL, 2)\n\n            PreferKey.clickActionBC -> clickActionBC =\n                appCtx.getPrefInt(PreferKey.clickActionBC, 1)\n\n            PreferKey.clickActionBR -> clickActionBR =\n                appCtx.getPrefInt(PreferKey.clickActionBR, 1)\n\n            PreferKey.readBodyToLh -> ReadBookConfig.readBodyToLh =\n                appCtx.getPrefBoolean(PreferKey.readBodyToLh, true)\n\n            PreferKey.useZhLayout -> ReadBookConfig.useZhLayout =\n                appCtx.getPrefBoolean(PreferKey.useZhLayout)\n\n            PreferKey.userAgent -> userAgent = getPrefUserAgent()\n\n            PreferKey.antiAlias -> useAntiAlias = appCtx.getPrefBoolean(PreferKey.antiAlias)\n\n            PreferKey.useDefaultCover -> useDefaultCover =\n                appCtx.getPrefBoolean(PreferKey.useDefaultCover, false)\n\n            PreferKey.optimizeRender -> optimizeRender = CanvasRecorderFactory.isSupport\n                    && appCtx.getPrefBoolean(PreferKey.optimizeRender, false)\n\n            PreferKey.recordLog -> recordLog = appCtx.getPrefBoolean(PreferKey.recordLog)\n\n        }\n    }\n\n    var isNightTheme: Boolean\n        get() = when (themeMode) {\n            \"1\" -> false\n            \"2\" -> true\n            \"3\" -> false\n            else -> sysConfiguration.isNightMode\n        }\n        set(value) {\n            if (isNightTheme != value) {\n                if (value) {\n                    appCtx.putPrefString(PreferKey.themeMode, \"2\")\n                } else {\n                    appCtx.putPrefString(PreferKey.themeMode, \"1\")\n                }\n            }\n        }\n\n    var showUnread: Boolean\n        get() = appCtx.getPrefBoolean(PreferKey.showUnread, true)\n        set(value) {\n            appCtx.putPrefBoolean(PreferKey.showUnread, value)\n        }\n\n    var showLastUpdateTime: Boolean\n        get() = appCtx.getPrefBoolean(PreferKey.showLastUpdateTime, false)\n        set(value) {\n            appCtx.putPrefBoolean(PreferKey.showLastUpdateTime, value)\n        }\n\n    var showWaitUpCount: Boolean\n        get() = appCtx.getPrefBoolean(PreferKey.showWaitUpCount, false)\n        set(value) {\n            appCtx.putPrefBoolean(PreferKey.showWaitUpCount, value)\n        }\n\n    var readBrightness: Int\n        get() = if (isNightTheme) {\n            appCtx.getPrefInt(PreferKey.nightBrightness, 100)\n        } else {\n            appCtx.getPrefInt(PreferKey.brightness, 100)\n        }\n        set(value) {\n            if (isNightTheme) {\n                appCtx.putPrefInt(PreferKey.nightBrightness, value)\n            } else {\n                appCtx.putPrefInt(PreferKey.brightness, value)\n            }\n        }\n\n    val textSelectAble: Boolean\n        get() = appCtx.getPrefBoolean(PreferKey.textSelectAble, true)\n\n    val isTransparentStatusBar: Boolean\n        get() = appCtx.getPrefBoolean(PreferKey.transparentStatusBar, true)\n\n    val immNavigationBar: Boolean\n        get() = appCtx.getPrefBoolean(PreferKey.immNavigationBar, true)\n\n    val screenOrientation: String?\n        get() = appCtx.getPrefString(PreferKey.screenOrientation)\n\n    var bookGroupStyle: Int\n        get() = appCtx.getPrefInt(PreferKey.bookGroupStyle, 0)\n        set(value) {\n            appCtx.putPrefInt(PreferKey.bookGroupStyle, value)\n        }\n\n    var bookshelfLayout: Int\n        get() = appCtx.getPrefInt(PreferKey.bookshelfLayout, 0)\n        set(value) {\n            appCtx.putPrefInt(PreferKey.bookshelfLayout, value)\n        }\n\n    var saveTabPosition: Int\n        get() = appCtx.getPrefInt(PreferKey.saveTabPosition, 0)\n        set(value) {\n            appCtx.putPrefInt(PreferKey.saveTabPosition, value)\n        }\n\n    var bookExportFileName: String?\n        get() = appCtx.getPrefString(PreferKey.bookExportFileName)\n        set(value) {\n            appCtx.putPrefString(PreferKey.bookExportFileName, value)\n        }\n\n    // 保存 自定义导出章节模式 文件名js表达式\n    var episodeExportFileName: String?\n        get() = appCtx.getPrefString(PreferKey.episodeExportFileName, \"\")\n        set(value) {\n            appCtx.putPrefString(PreferKey.episodeExportFileName, value)\n        }\n\n    var bookImportFileName: String?\n        get() = appCtx.getPrefString(PreferKey.bookImportFileName)\n        set(value) {\n            appCtx.putPrefString(PreferKey.bookImportFileName, value)\n        }\n\n    var backupPath: String?\n        get() = appCtx.getPrefString(PreferKey.backupPath)\n        set(value) {\n            if (value.isNullOrEmpty()) {\n                appCtx.removePref(PreferKey.backupPath)\n            } else {\n                appCtx.putPrefString(PreferKey.backupPath, value)\n            }\n        }\n\n    // 书籍保存位置\n    var defaultBookTreeUri: String?\n        get() = appCtx.getPrefString(PreferKey.defaultBookTreeUri)\n        set(value) {\n            if (value.isNullOrEmpty()) {\n                appCtx.removePref(PreferKey.defaultBookTreeUri)\n            } else {\n                appCtx.putPrefString(PreferKey.defaultBookTreeUri, value)\n            }\n        }\n\n    val showDiscovery: Boolean\n        get() = appCtx.getPrefBoolean(PreferKey.showDiscovery, true)\n\n    val showRSS: Boolean\n        get() = appCtx.getPrefBoolean(PreferKey.showRss, true)\n\n    val autoRefreshBook: Boolean\n        get() = appCtx.getPrefBoolean(PreferKey.autoRefresh)\n\n    var enableReview: Boolean\n        get() = BuildConfig.DEBUG && appCtx.getPrefBoolean(PreferKey.enableReview, false)\n        set(value) {\n            appCtx.putPrefBoolean(PreferKey.enableReview, value)\n        }\n\n    var threadCount: Int\n        get() = appCtx.getPrefInt(PreferKey.threadCount, 16)\n        set(value) {\n            appCtx.putPrefInt(PreferKey.threadCount, value)\n        }\n\n    var remoteServerId: Long\n        get() = appCtx.getPrefLong(PreferKey.remoteServerId)\n        set(value) {\n            appCtx.putPrefLong(PreferKey.remoteServerId, value)\n        }\n\n    // 添加本地选择的目录\n    var importBookPath: String?\n        get() = appCtx.getPrefString(\"importBookPath\")\n        set(value) {\n            if (value == null) {\n                appCtx.removePref(\"importBookPath\")\n            } else {\n                appCtx.putPrefString(\"importBookPath\", value)\n            }\n        }\n\n    var ttsFlowSys: Boolean\n        get() = appCtx.getPrefBoolean(PreferKey.ttsFollowSys, true)\n        set(value) {\n            appCtx.putPrefBoolean(PreferKey.ttsFollowSys, value)\n        }\n\n    val noAnimScrollPage: Boolean\n        get() = appCtx.getPrefBoolean(PreferKey.noAnimScrollPage, false)\n\n    const val defaultSpeechRate = 5\n\n    var ttsSpeechRate: Int\n        get() = appCtx.getPrefInt(PreferKey.ttsSpeechRate, defaultSpeechRate)\n        set(value) {\n            appCtx.putPrefInt(PreferKey.ttsSpeechRate, value)\n        }\n\n    var ttsTimer: Int\n        get() = appCtx.getPrefInt(PreferKey.ttsTimer, 0)\n        set(value) {\n            appCtx.putPrefInt(PreferKey.ttsTimer, value)\n        }\n\n    val speechRatePlay: Int get() = if (ttsFlowSys) defaultSpeechRate else ttsSpeechRate\n\n    var chineseConverterType: Int\n        get() = appCtx.getPrefInt(PreferKey.chineseConverterType)\n        set(value) {\n            appCtx.putPrefInt(PreferKey.chineseConverterType, value)\n        }\n\n    var systemTypefaces: Int\n        get() = appCtx.getPrefInt(PreferKey.systemTypefaces)\n        set(value) {\n            appCtx.putPrefInt(PreferKey.systemTypefaces, value)\n        }\n\n    var elevation: Int\n        get() = if (isEInkMode) 0 else appCtx.getPrefInt(\n            PreferKey.barElevation,\n            AppConst.sysElevation\n        )\n        set(value) {\n            appCtx.putPrefInt(PreferKey.barElevation, value)\n        }\n\n    var readUrlInBrowser: Boolean\n        get() = appCtx.getPrefBoolean(PreferKey.readUrlOpenInBrowser)\n        set(value) {\n            appCtx.putPrefBoolean(PreferKey.readUrlOpenInBrowser, value)\n        }\n\n    var exportCharset: String\n        get() {\n            val c = appCtx.getPrefString(PreferKey.exportCharset)\n            if (c.isNullOrBlank()) {\n                return \"UTF-8\"\n            }\n            return c\n        }\n        set(value) {\n            appCtx.putPrefString(PreferKey.exportCharset, value)\n        }\n\n    var exportUseReplace: Boolean\n        get() = appCtx.getPrefBoolean(PreferKey.exportUseReplace, true)\n        set(value) {\n            appCtx.putPrefBoolean(PreferKey.exportUseReplace, value)\n        }\n\n    var exportToWebDav: Boolean\n        get() = appCtx.getPrefBoolean(PreferKey.exportToWebDav)\n        set(value) {\n            appCtx.putPrefBoolean(PreferKey.exportToWebDav, value)\n        }\n    var exportNoChapterName: Boolean\n        get() = appCtx.getPrefBoolean(PreferKey.exportNoChapterName)\n        set(value) {\n            appCtx.putPrefBoolean(PreferKey.exportNoChapterName, value)\n        }\n\n    // 是否启用自定义导出 default->false\n    var enableCustomExport: Boolean\n        get() = appCtx.getPrefBoolean(PreferKey.enableCustomExport, false)\n        set(value) {\n            appCtx.putPrefBoolean(PreferKey.enableCustomExport, value)\n        }\n\n    var exportType: Int\n        get() = appCtx.getPrefInt(PreferKey.exportType)\n        set(value) {\n            appCtx.putPrefInt(PreferKey.exportType, value)\n        }\n    var exportPictureFile: Boolean\n        get() = appCtx.getPrefBoolean(PreferKey.exportPictureFile, false)\n        set(value) {\n            appCtx.putPrefBoolean(PreferKey.exportPictureFile, value)\n        }\n\n    var parallelExportBook: Boolean\n        get() = appCtx.getPrefBoolean(PreferKey.parallelExportBook, false)\n        set(value) {\n            appCtx.putPrefBoolean(PreferKey.parallelExportBook, value)\n        }\n\n    var changeSourceCheckAuthor: Boolean\n        get() = appCtx.getPrefBoolean(PreferKey.changeSourceCheckAuthor)\n        set(value) {\n            appCtx.putPrefBoolean(PreferKey.changeSourceCheckAuthor, value)\n        }\n\n    var ttsEngine: String?\n        get() = appCtx.getPrefString(PreferKey.ttsEngine)\n        set(value) {\n            appCtx.putPrefString(PreferKey.ttsEngine, value)\n        }\n\n    var webPort: Int\n        get() = appCtx.getPrefInt(PreferKey.webPort, 1122)\n        set(value) {\n            appCtx.putPrefInt(PreferKey.webPort, value)\n        }\n\n    var tocUiUseReplace: Boolean\n        get() = appCtx.getPrefBoolean(PreferKey.tocUiUseReplace)\n        set(value) {\n            appCtx.putPrefBoolean(PreferKey.tocUiUseReplace, value)\n        }\n\n    var tocCountWords: Boolean\n        get() = appCtx.getPrefBoolean(PreferKey.tocCountWords, true)\n        set(value) {\n            appCtx.putPrefBoolean(PreferKey.tocCountWords, value)\n        }\n\n    var enableReadRecord: Boolean\n        get() = appCtx.getPrefBoolean(PreferKey.enableReadRecord, true)\n        set(value) {\n            appCtx.putPrefBoolean(PreferKey.enableReadRecord, value)\n        }\n\n    val autoChangeSource: Boolean\n        get() = appCtx.getPrefBoolean(PreferKey.autoChangeSource, true)\n\n    var changeSourceLoadInfo: Boolean\n        get() = appCtx.getPrefBoolean(PreferKey.changeSourceLoadInfo)\n        set(value) {\n            appCtx.putPrefBoolean(PreferKey.changeSourceLoadInfo, value)\n        }\n\n    var changeSourceLoadToc: Boolean\n        get() = appCtx.getPrefBoolean(PreferKey.changeSourceLoadToc)\n        set(value) {\n            appCtx.putPrefBoolean(PreferKey.changeSourceLoadToc, value)\n        }\n\n    var changeSourceLoadWordCount: Boolean\n        get() = appCtx.getPrefBoolean(PreferKey.changeSourceLoadWordCount)\n        set(value) {\n            appCtx.putPrefBoolean(PreferKey.changeSourceLoadWordCount, value)\n        }\n\n    var openBookInfoByClickTitle: Boolean\n        get() = appCtx.getPrefBoolean(PreferKey.openBookInfoByClickTitle, true)\n        set(value) {\n            appCtx.putPrefBoolean(PreferKey.openBookInfoByClickTitle, value)\n        }\n\n    var showBookshelfFastScroller: Boolean\n        get() = appCtx.getPrefBoolean(PreferKey.showBookshelfFastScroller, false)\n        set(value) {\n            appCtx.putPrefBoolean(PreferKey.showBookshelfFastScroller, value)\n        }\n\n    var contentSelectSpeakMod: Int\n        get() = appCtx.getPrefInt(PreferKey.contentSelectSpeakMod)\n        set(value) {\n            appCtx.putPrefInt(PreferKey.contentSelectSpeakMod, value)\n        }\n\n    var batchChangeSourceDelay: Int\n        get() = appCtx.getPrefInt(PreferKey.batchChangeSourceDelay)\n        set(value) {\n            appCtx.putPrefInt(PreferKey.batchChangeSourceDelay, value)\n        }\n\n    val importKeepName get() = appCtx.getPrefBoolean(PreferKey.importKeepName)\n    val importKeepGroup get() = appCtx.getPrefBoolean(PreferKey.importKeepGroup)\n    var importKeepEnable: Boolean\n        get() = appCtx.getPrefBoolean(PreferKey.importKeepEnable, false)\n        set(value) {\n            appCtx.putPrefBoolean(PreferKey.importKeepEnable, value)\n        }\n\n    var previewImageByClick: Boolean\n        get() = appCtx.getPrefBoolean(PreferKey.previewImageByClick, false)\n        set(value) {\n            appCtx.putPrefBoolean(PreferKey.previewImageByClick, value)\n        }\n\n    var preDownloadNum\n        get() = appCtx.getPrefInt(PreferKey.preDownloadNum, 10)\n        set(value) {\n            appCtx.putPrefInt(PreferKey.preDownloadNum, value)\n        }\n\n    val syncBookProgress get() = appCtx.getPrefBoolean(PreferKey.syncBookProgress, true)\n\n    val syncBookProgressPlus get() = appCtx.getPrefBoolean(PreferKey.syncBookProgressPlus, false)\n\n    val mediaButtonOnExit get() = appCtx.getPrefBoolean(\"mediaButtonOnExit\", true)\n\n    val readAloudByMediaButton\n        get() = appCtx.getPrefBoolean(PreferKey.readAloudByMediaButton, false)\n\n    val replaceEnableDefault get() = appCtx.getPrefBoolean(PreferKey.replaceEnableDefault, true)\n\n    val webDavDir get() = appCtx.getPrefString(PreferKey.webDavDir, \"legado\")\n\n    val webDavDeviceName get() = appCtx.getPrefString(PreferKey.webDavDeviceName, Build.MODEL)\n\n    val recordHeapDump get() = appCtx.getPrefBoolean(PreferKey.recordHeapDump, false)\n\n    val loadCoverOnlyWifi get() = appCtx.getPrefBoolean(PreferKey.loadCoverOnlyWifi, false)\n\n    val showAddToShelfAlert get() = appCtx.getPrefBoolean(PreferKey.showAddToShelfAlert, true)\n\n    val ignoreAudioFocus get() = appCtx.getPrefBoolean(PreferKey.ignoreAudioFocus, false)\n\n    var pauseReadAloudWhilePhoneCalls\n        get() = appCtx.getPrefBoolean(PreferKey.pauseReadAloudWhilePhoneCalls, false)\n        set(value) = appCtx.putPrefBoolean(PreferKey.pauseReadAloudWhilePhoneCalls, value)\n\n    val onlyLatestBackup get() = appCtx.getPrefBoolean(PreferKey.onlyLatestBackup, true)\n\n    val autoCheckNewBackup get() = appCtx.getPrefBoolean(PreferKey.autoCheckNewBackup, true)\n\n    val defaultHomePage get() = appCtx.getPrefString(PreferKey.defaultHomePage, \"bookshelf\")\n\n    val updateToVariant get() = appCtx.getPrefString(PreferKey.updateToVariant, \"default_version\")\n\n    val streamReadAloudAudio get() = appCtx.getPrefBoolean(PreferKey.streamReadAloudAudio, false)\n\n    val doublePageHorizontal: String?\n        get() = appCtx.getPrefString(PreferKey.doublePageHorizontal)\n\n    val progressBarBehavior: String?\n        get() = appCtx.getPrefString(PreferKey.progressBarBehavior, \"page\")\n\n    val keyPageOnLongPress\n        get() = appCtx.getPrefBoolean(PreferKey.keyPageOnLongPress, false)\n\n    val volumeKeyPage\n        get() = appCtx.getPrefBoolean(PreferKey.volumeKeyPage, true)\n\n    val volumeKeyPageOnPlay\n        get() = appCtx.getPrefBoolean(PreferKey.volumeKeyPageOnPlay, true)\n\n    val mouseWheelPage\n        get() = appCtx.getPrefBoolean(PreferKey.mouseWheelPage, true)\n\n    val paddingDisplayCutouts\n        get() = appCtx.getPrefBoolean(PreferKey.paddingDisplayCutouts, false)\n\n    var searchScope: String\n        get() = appCtx.getPrefString(\"searchScope\") ?: \"\"\n        set(value) {\n            appCtx.putPrefString(\"searchScope\", value)\n        }\n\n    var searchGroup: String\n        get() = appCtx.getPrefString(\"searchGroup\") ?: \"\"\n        set(value) {\n            appCtx.putPrefString(\"searchGroup\", value)\n        }\n\n    var pageTouchSlop: Int\n        get() = appCtx.getPrefInt(PreferKey.pageTouchSlop, 0)\n        set(value) {\n            appCtx.putPrefInt(PreferKey.pageTouchSlop, value)\n        }\n\n    var bookshelfSort: Int\n        get() = appCtx.getPrefInt(PreferKey.bookshelfSort, 0)\n        set(value) {\n            appCtx.putPrefInt(PreferKey.bookshelfSort, value)\n        }\n\n    fun getBookSortByGroupId(groupId: Long): Int {\n        return appDb.bookGroupDao.getByID(groupId)?.getRealBookSort()\n            ?: bookshelfSort\n    }\n\n    private fun getPrefUserAgent(): String {\n        val ua = appCtx.getPrefString(PreferKey.userAgent)\n        if (ua.isNullOrBlank()) {\n            return \"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/\" + BuildConfig.Cronet_Main_Version + \" Safari/537.36\"\n        }\n        return ua\n    }\n\n    var bitmapCacheSize: Int\n        get() = appCtx.getPrefInt(PreferKey.bitmapCacheSize, 50)\n        set(value) {\n            appCtx.putPrefInt(PreferKey.bitmapCacheSize, value)\n        }\n\n    var imageRetainNum: Int\n        get() = appCtx.getPrefInt(PreferKey.imageRetainNum, 0)\n        set(value) {\n            appCtx.putPrefInt(PreferKey.imageRetainNum, value)\n        }\n\n    var showReadTitleBarAddition: Boolean\n        get() = appCtx.getPrefBoolean(PreferKey.showReadTitleAddition, true)\n        set(value) {\n            appCtx.putPrefBoolean(PreferKey.showReadTitleAddition, value)\n        }\n    var readBarStyleFollowPage: Boolean\n        get() = appCtx.getPrefBoolean(PreferKey.readBarStyleFollowPage, false)\n        set(value) {\n            appCtx.putPrefBoolean(PreferKey.readBarStyleFollowPage, value)\n        }\n\n    var sourceEditMaxLine: Int\n        get() {\n            val maxLine = appCtx.getPrefInt(PreferKey.sourceEditMaxLine, Int.MAX_VALUE)\n            if (maxLine < 10) {\n                return Int.MAX_VALUE\n            }\n            return maxLine\n        }\n        set(value) {\n            appCtx.putPrefInt(PreferKey.sourceEditMaxLine, value)\n        }\n\n    var audioPlayUseWakeLock: Boolean\n        get() = appCtx.getPrefBoolean(PreferKey.audioPlayWakeLock)\n        set(value) {\n            appCtx.putPrefBoolean(PreferKey.audioPlayWakeLock, value)\n        }\n\n    var brightnessVwPos: Boolean\n        get() = appCtx.getPrefBoolean(PreferKey.brightnessVwPos)\n        set(value) {\n            appCtx.putPrefBoolean(PreferKey.brightnessVwPos, value)\n        }\n\n    fun detectClickArea() {\n        if (clickActionTL * clickActionTC * clickActionTR\n            * clickActionML * clickActionMC * clickActionMR\n            * clickActionBL * clickActionBC * clickActionBR != 0\n        ) {\n            appCtx.putPrefInt(PreferKey.clickActionMC, 0)\n            appCtx.toastOnUi(\"当前没有配置菜单区域,自动恢复中间区域为菜单.\")\n        }\n    }\n\n    //跳转到漫画界面不使用富文本模式\n    val showMangaUi: Boolean\n        get() = appCtx.getPrefBoolean(PreferKey.showMangaUi, true)\n\n    //禁用漫画缩放\n    var disableMangaScale: Boolean\n        get() = appCtx.getPrefBoolean(PreferKey.disableMangaScale, true)\n        set(value) {\n            appCtx.putPrefBoolean(PreferKey.disableMangaScale, value)\n        }\n\n    var disableMangaPageAnim: Boolean\n        get() = appCtx.getPrefBoolean(PreferKey.disableMangaPageAnim, false)\n        set(value) {\n            appCtx.putPrefBoolean(PreferKey.disableMangaPageAnim, value)\n        }\n\n    //漫画预加载数量\n    var mangaPreDownloadNum\n        get() = appCtx.getPrefInt(PreferKey.mangaPreDownloadNum, 10)\n        set(value) {\n            appCtx.putPrefInt(PreferKey.mangaPreDownloadNum, value)\n        }\n\n    //点击翻页\n    var disableClickScroll\n        get() = appCtx.getPrefBoolean(PreferKey.disableClickScroll, false)\n        set(value) {\n            appCtx.putPrefBoolean(PreferKey.disableClickScroll, value)\n        }\n\n    //漫画滚动速度\n    var mangaAutoPageSpeed\n        get() = appCtx.getPrefInt(PreferKey.mangaAutoPageSpeed, 3)\n        set(value) {\n            appCtx.putPrefInt(PreferKey.mangaAutoPageSpeed, value)\n        }\n\n    //漫画页脚配置\n    var mangaFooterConfig\n        get() = appCtx.getPrefString(PreferKey.mangaFooterConfig, \"\")\n        set(value) {\n            appCtx.putPrefString(PreferKey.mangaFooterConfig, value)\n        }\n\n    //漫画水平滚动\n    var enableMangaHorizontalScroll\n        get() = appCtx.getPrefBoolean(PreferKey.enableMangaHorizontalScroll, false)\n        set(value) {\n            appCtx.putPrefBoolean(PreferKey.enableMangaHorizontalScroll, value)\n        }\n\n    var mangaColorFilter\n        get() = appCtx.getPrefString(PreferKey.mangaColorFilter, \"\")\n        set(value) {\n            appCtx.putPrefString(PreferKey.mangaColorFilter, value)\n        }\n\n    //禁用漫画内标题\n    var hideMangaTitle\n        get() = appCtx.getPrefBoolean(PreferKey.hideMangaTitle, false)\n        set(value) {\n            appCtx.putPrefBoolean(PreferKey.hideMangaTitle, value)\n        }\n\n    //开启墨水屏模式\n    var enableMangaEInk\n        get() = appCtx.getPrefBoolean(PreferKey.enableMangaEInk, false)\n        set(value) {\n            appCtx.putPrefBoolean(PreferKey.enableMangaEInk, value)\n        }\n\n    var mangaEInkThreshold\n        get() = appCtx.getPrefInt(PreferKey.mangaEInkThreshold, 150)\n        set(value) {\n            appCtx.putPrefInt(PreferKey.mangaEInkThreshold, value)\n        }\n\n    var disableHorizontalPageSnap\n        get() = appCtx.getPrefBoolean(PreferKey.disableHorizontalPageSnap, false)\n        set(value) {\n            appCtx.putPrefBoolean(PreferKey.disableHorizontalPageSnap, value)\n        }\n\n    var enableMangaGray\n        get() = appCtx.getPrefBoolean(PreferKey.enableMangaGray, false)\n        set(value) {\n            appCtx.putPrefBoolean(PreferKey.enableMangaGray, value)\n        }\n\n    var welcomeImage\n        get() = appCtx.getPrefString(PreferKey.welcomeImage)\n        set(value) {\n            appCtx.putPrefString(PreferKey.welcomeImage, value)\n        }\n\n    var welcomeShowText\n        get() = appCtx.getPrefBoolean(PreferKey.welcomeShowText, true)\n        set(value) {\n            appCtx.putPrefBoolean(PreferKey.welcomeShowText, value)\n        }\n\n    var welcomeShowIcon\n        get() = appCtx.getPrefBoolean(PreferKey.welcomeShowIcon, true)\n        set(value) {\n            appCtx.putPrefBoolean(PreferKey.welcomeShowIcon, value)\n        }\n\n    var welcomeImageDark\n        get() = appCtx.getPrefString(PreferKey.welcomeImageDark)\n        set(value) {\n            appCtx.putPrefString(PreferKey.welcomeImageDark, value)\n        }\n\n    var welcomeShowTextDark\n        get() = appCtx.getPrefBoolean(PreferKey.welcomeShowTextDark, true)\n        set(value) {\n            appCtx.putPrefBoolean(PreferKey.welcomeShowTextDark, value)\n        }\n\n    var welcomeShowIconDark\n        get() = appCtx.getPrefBoolean(PreferKey.welcomeShowIconDark, true)\n        set(value) {\n            appCtx.putPrefBoolean(PreferKey.welcomeShowIconDark, value)\n        }\n\n}\n\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/help/config/LocalConfig.kt",
    "content": "package io.legado.app.help.config\n\nimport android.content.Context\nimport android.content.SharedPreferences\nimport androidx.core.content.edit\nimport io.legado.app.utils.getBoolean\nimport io.legado.app.utils.putBoolean\nimport io.legado.app.utils.putLong\nimport io.legado.app.utils.putString\nimport io.legado.app.utils.remove\nimport splitties.init.appCtx\n\n@Suppress(\"ConstPropertyName\")\nobject LocalConfig : SharedPreferences\nby appCtx.getSharedPreferences(\"local\", Context.MODE_PRIVATE) {\n\n    private const val versionCodeKey = \"appVersionCode\"\n\n    /**\n     * 本地密码,用来对需要备份的敏感信息加密,如 webdav 配置等\n     */\n    var password: String?\n        get() = getString(\"password\", null)\n        set(value) {\n            if (value != null) {\n                putString(\"password\", value)\n            } else {\n                remove(\"password\")\n            }\n        }\n\n    var lastBackup: Long\n        get() = getLong(\"lastBackup\", 0)\n        set(value) {\n            putLong(\"lastBackup\", value)\n        }\n\n    var privacyPolicyOk: Boolean\n        get() = getBoolean(\"privacyPolicyOk\")\n        set(value) {\n            putBoolean(\"privacyPolicyOk\", value)\n        }\n\n    val readHelpVersionIsLast: Boolean\n        get() = isLastVersion(1, \"readHelpVersion\", \"firstRead\")\n\n    val backupHelpVersionIsLast: Boolean\n        get() = isLastVersion(1, \"backupHelpVersion\", \"firstBackup\")\n\n    val readMenuHelpVersionIsLast: Boolean\n        get() = isLastVersion(1, \"readMenuHelpVersion\", \"firstReadMenu\")\n\n    val bookSourcesHelpVersionIsLast: Boolean\n        get() = isLastVersion(1, \"bookSourceHelpVersion\", \"firstOpenBookSources\")\n\n    val webDavBookHelpVersionIsLast: Boolean\n        get() = isLastVersion(1, \"webDavBookHelpVersion\", \"firstOpenWebDavBook\")\n\n    val ruleHelpVersionIsLast: Boolean\n        get() = isLastVersion(1, \"ruleHelpVersion\")\n\n    val needUpHttpTTS: Boolean\n        get() = !isLastVersion(6, \"httpTtsVersion\")\n\n    val needUpTxtTocRule: Boolean\n        get() = !isLastVersion(3, \"txtTocRuleVersion\")\n\n    val needUpRssSources: Boolean\n        get() = !isLastVersion(6, \"rssSourceVersion\")\n\n    val needUpDictRule: Boolean\n        get() = !isLastVersion(2, \"needUpDictRule\")\n\n    var versionCode\n        get() = getLong(versionCodeKey, 0)\n        set(value) {\n            edit { putLong(versionCodeKey, value) }\n        }\n\n    val isFirstOpenApp: Boolean\n        get() {\n            val value = getBoolean(\"firstOpen\", true)\n            if (value) {\n                edit { putBoolean(\"firstOpen\", false) }\n            }\n            return value\n        }\n\n    @Suppress(\"SameParameterValue\")\n    private fun isLastVersion(\n        lastVersion: Int,\n        versionKey: String,\n        firstOpenKey: String? = null\n    ): Boolean {\n        var version = getInt(versionKey, 0)\n        if (version == 0 && firstOpenKey != null) {\n            if (!getBoolean(firstOpenKey, true)) {\n                version = 1\n            }\n        }\n        if (version < lastVersion) {\n            edit { putInt(versionKey, lastVersion) }\n            return false\n        }\n        return true\n    }\n\n    var bookInfoDeleteAlert: Boolean\n        get() = getBoolean(\"bookInfoDeleteAlert\", true)\n        set(value) {\n            putBoolean(\"bookInfoDeleteAlert\", value)\n        }\n\n    var deleteBookOriginal: Boolean\n        get() = getBoolean(\"deleteBookOriginal\")\n        set(value) {\n            putBoolean(\"deleteBookOriginal\", value)\n        }\n\n    var appCrash: Boolean\n        get() = getBoolean(\"appCrash\")\n        set(value) {\n            putBoolean(\"appCrash\", value)\n        }\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/help/config/ReadBookConfig.kt",
    "content": "package io.legado.app.help.config\n\nimport android.graphics.Color\nimport android.graphics.drawable.BitmapDrawable\nimport android.graphics.drawable.ColorDrawable\nimport android.graphics.drawable.Drawable\nimport androidx.annotation.Keep\nimport androidx.core.graphics.toColorInt\nimport io.legado.app.R\nimport io.legado.app.constant.AppLog\nimport io.legado.app.constant.PageAnim\nimport io.legado.app.constant.PreferKey\nimport io.legado.app.help.DefaultData\nimport io.legado.app.help.coroutine.Coroutine\nimport io.legado.app.utils.BitmapUtils\nimport io.legado.app.utils.FileUtils\nimport io.legado.app.utils.GSON\nimport io.legado.app.utils.compress.ZipUtils\nimport io.legado.app.utils.createFolderReplace\nimport io.legado.app.utils.externalCache\nimport io.legado.app.utils.externalFiles\nimport io.legado.app.utils.fromJsonArray\nimport io.legado.app.utils.fromJsonObject\nimport io.legado.app.utils.getCompatColor\nimport io.legado.app.utils.getFile\nimport io.legado.app.utils.getMeanColor\nimport io.legado.app.utils.getPrefBoolean\nimport io.legado.app.utils.getPrefInt\nimport io.legado.app.utils.hexString\nimport io.legado.app.utils.printOnDebug\nimport io.legado.app.utils.putPrefBoolean\nimport io.legado.app.utils.putPrefInt\nimport io.legado.app.utils.resizeAndRecycle\nimport splitties.init.appCtx\nimport java.io.File\n\n/**\n * 阅读界面配置\n */\n@Suppress(\"ConstPropertyName\")\n@Keep\nobject ReadBookConfig {\n    const val configFileName = \"readConfig.json\"\n    const val shareConfigFileName = \"shareReadConfig.json\"\n    val configFilePath = FileUtils.getPath(appCtx.filesDir, configFileName)\n    val shareConfigFilePath = FileUtils.getPath(appCtx.filesDir, shareConfigFileName)\n    val configList: ArrayList<Config> = arrayListOf()\n    lateinit var shareConfig: Config\n    var durConfig\n        get() = getConfig(styleSelect)\n        set(value) {\n            configList[styleSelect] = value\n            if (shareLayout) {\n                shareConfig = value\n            }\n        }\n\n    var isComic: Boolean = false\n    var bg: Drawable? = null\n    var bgMeanColor: Int = 0\n    val textColor: Int get() = durConfig.curTextColor()\n\n    init {\n        initConfigs()\n        initShareConfig()\n    }\n\n    @Synchronized\n    fun getConfig(index: Int): Config {\n        if (configList.size < 5) {\n            resetAll()\n        }\n        return configList.getOrNull(index) ?: configList[0]\n    }\n\n    fun initConfigs() {\n        val configFile = File(configFilePath)\n        var configs: List<Config>? = null\n        if (configFile.exists()) {\n            try {\n                val json = configFile.readText()\n                configs = GSON.fromJsonArray<Config>(json).getOrThrow()\n            } catch (e: Exception) {\n                AppLog.put(\"读取排版配置文件出错\", e)\n            }\n        }\n        (configs ?: DefaultData.readConfigs).let {\n            configList.clear()\n            configList.addAll(it)\n        }\n    }\n\n    fun initShareConfig() {\n        val configFile = File(shareConfigFilePath)\n        var c: Config? = null\n        if (configFile.exists()) {\n            try {\n                val json = configFile.readText()\n                c = GSON.fromJsonObject<Config>(json).getOrThrow()\n            } catch (e: Exception) {\n                e.printOnDebug()\n            }\n        }\n        shareConfig = c ?: configList.getOrNull(5) ?: Config()\n    }\n\n    fun upBg(width: Int, height: Int) {\n        val drawable = durConfig.curBgDrawable(width, height)\n        if (drawable is BitmapDrawable && drawable.bitmap != null) {\n            bgMeanColor = drawable.bitmap.getMeanColor()\n        } else if (drawable is ColorDrawable) {\n            bgMeanColor = drawable.color\n        }\n        val tmp = bg\n        bg = drawable\n        (tmp as? BitmapDrawable)?.bitmap?.recycle()\n    }\n\n    fun save() {\n        Coroutine.async {\n            synchronized(this) {\n                GSON.toJson(configList).let {\n                    FileUtils.delete(configFilePath)\n                    FileUtils.createFileIfNotExist(configFilePath).writeText(it)\n                }\n                GSON.toJson(shareConfig).let {\n                    FileUtils.delete(shareConfigFilePath)\n                    FileUtils.createFileIfNotExist(shareConfigFilePath).writeText(it)\n                }\n            }\n        }\n    }\n\n    fun getAllPicBgStr(): ArrayList<String> {\n        val list = arrayListOf<String>()\n        configList.forEach {\n            if (it.bgType == 2) {\n                list.add(it.bgStr)\n            }\n            if (it.bgTypeNight == 2) {\n                list.add(it.bgStrNight)\n            }\n            if (it.bgTypeEInk == 2) {\n                list.add(it.bgStrEInk)\n            }\n        }\n        return list\n    }\n\n    fun deleteDur(): Boolean {\n        if (configList.size > 5) {\n            val removeIndex = styleSelect\n            configList.removeAt(removeIndex)\n            if (removeIndex <= readStyleSelect) {\n                readStyleSelect -= 1\n            }\n            if (removeIndex <= comicStyleSelect) {\n                comicStyleSelect -= 1\n            }\n            return true\n        }\n        return false\n    }\n\n    fun clearBgAndCache() {\n        val bgs = hashSetOf<String>()\n        configList.forEach { config ->\n            repeat(3) {\n                config.getBgPath(it)?.let { path ->\n                    bgs.add(path)\n                }\n            }\n        }\n        appCtx.externalFiles.getFile(\"bg\").listFiles()?.forEach {\n            if (!bgs.contains(it.absolutePath)) {\n                it.delete()\n            }\n        }\n        FileUtils.delete(appCtx.externalCache.getFile(\"readConfig\"))\n        val configZipPath = FileUtils.getPath(appCtx.externalCache, \"readConfig.zip\")\n        FileUtils.delete(configZipPath)\n    }\n\n    private fun resetAll() {\n        DefaultData.readConfigs.let {\n            configList.clear()\n            configList.addAll(it)\n            save()\n        }\n    }\n\n    //配置写入读取\n    var readBodyToLh = appCtx.getPrefBoolean(PreferKey.readBodyToLh, true)\n    var autoReadSpeed = appCtx.getPrefInt(PreferKey.autoReadSpeed, 10)\n        set(value) {\n            field = value\n            appCtx.putPrefInt(PreferKey.autoReadSpeed, value)\n        }\n    var styleSelect: Int\n        get() = if (isComic) comicStyleSelect else readStyleSelect\n        set(value) {\n            if (isComic) {\n                comicStyleSelect = value\n            } else {\n                readStyleSelect = value\n            }\n        }\n    var readStyleSelect = appCtx.getPrefInt(PreferKey.readStyleSelect)\n        set(value) {\n            field = value\n            if (appCtx.getPrefInt(PreferKey.readStyleSelect) != value) {\n                appCtx.putPrefInt(PreferKey.readStyleSelect, value)\n            }\n        }\n    var comicStyleSelect = appCtx.getPrefInt(PreferKey.comicStyleSelect, readStyleSelect)\n        set(value) {\n            field = value\n            if (appCtx.getPrefInt(PreferKey.comicStyleSelect) != value) {\n                appCtx.putPrefInt(PreferKey.comicStyleSelect, value)\n            }\n        }\n    var shareLayout = appCtx.getPrefBoolean(PreferKey.shareLayout)\n        set(value) {\n            field = value\n            if (appCtx.getPrefBoolean(PreferKey.shareLayout) != value) {\n                appCtx.putPrefBoolean(PreferKey.shareLayout, value)\n            }\n        }\n\n    /**\n     * 两端对齐\n     */\n    val textFullJustify get() = appCtx.getPrefBoolean(PreferKey.textFullJustify, true)\n\n    /**\n     * 底部对齐\n     */\n    val textBottomJustify get() = appCtx.getPrefBoolean(PreferKey.textBottomJustify, true)\n    var hideStatusBar = appCtx.getPrefBoolean(PreferKey.hideStatusBar)\n    var hideNavigationBar = appCtx.getPrefBoolean(PreferKey.hideNavigationBar)\n    var useZhLayout = appCtx.getPrefBoolean(PreferKey.useZhLayout)\n\n    val config get() = if (shareLayout) shareConfig else durConfig\n\n    var bgAlpha: Int\n        get() = config.bgAlpha\n        set(value) {\n            config.bgAlpha = value\n        }\n\n    var pageAnim: Int\n        get() = config.curPageAnim()\n        set(@PageAnim.Anim value) {\n            config.setCurPageAnim(value)\n        }\n\n    var textFont: String\n        get() = config.textFont\n        set(value) {\n            config.textFont = value\n        }\n\n    var textBold: Int\n        get() = config.textBold\n        set(value) {\n            config.textBold = value\n        }\n\n    var textSize: Int\n        get() = config.textSize\n        set(value) {\n            config.textSize = value\n        }\n\n    var letterSpacing: Float\n        get() = config.letterSpacing\n        set(value) {\n            config.letterSpacing = value\n        }\n\n    var lineSpacingExtra: Int\n        get() = config.lineSpacingExtra\n        set(value) {\n            config.lineSpacingExtra = value\n        }\n\n    var paragraphSpacing: Int\n        get() = config.paragraphSpacing\n        set(value) {\n            config.paragraphSpacing = value\n        }\n\n    /**\n     * 标题位置 0:居左 1:居中 2:隐藏\n     */\n    var titleMode: Int\n        get() = config.titleMode\n        set(value) {\n            config.titleMode = value\n        }\n    var titleSize: Int\n        get() = config.titleSize\n        set(value) {\n            config.titleSize = value\n        }\n\n    /**\n     * 是否标题居中\n     */\n    val isMiddleTitle get() = titleMode == 1\n\n    var titleTopSpacing: Int\n        get() = config.titleTopSpacing\n        set(value) {\n            config.titleTopSpacing = value\n        }\n\n    var titleBottomSpacing: Int\n        get() = config.titleBottomSpacing\n        set(value) {\n            config.titleBottomSpacing = value\n        }\n\n    var paragraphIndent: String\n        get() = config.paragraphIndent\n        set(value) {\n            config.paragraphIndent = value\n        }\n\n    var underline: Boolean\n        get() = config.underline\n        set(value) {\n            config.underline = value\n        }\n\n    var paddingBottom: Int\n        get() = config.paddingBottom\n        set(value) {\n            config.paddingBottom = value\n        }\n\n    var paddingLeft: Int\n        get() = config.paddingLeft\n        set(value) {\n            config.paddingLeft = value\n        }\n\n    var paddingRight: Int\n        get() = config.paddingRight\n        set(value) {\n            config.paddingRight = value\n        }\n\n    var paddingTop: Int\n        get() = config.paddingTop\n        set(value) {\n            config.paddingTop = value\n        }\n\n    var headerPaddingBottom: Int\n        get() = config.headerPaddingBottom\n        set(value) {\n            config.headerPaddingBottom = value\n        }\n\n    var headerPaddingLeft: Int\n        get() = config.headerPaddingLeft\n        set(value) {\n            config.headerPaddingLeft = value\n        }\n\n    var headerPaddingRight: Int\n        get() = config.headerPaddingRight\n        set(value) {\n            config.headerPaddingRight = value\n        }\n\n    var headerPaddingTop: Int\n        get() = config.headerPaddingTop\n        set(value) {\n            config.headerPaddingTop = value\n        }\n\n    var footerPaddingBottom: Int\n        get() = config.footerPaddingBottom\n        set(value) {\n            config.footerPaddingBottom = value\n        }\n\n    var footerPaddingLeft: Int\n        get() = config.footerPaddingLeft\n        set(value) {\n            config.footerPaddingLeft = value\n        }\n\n    var footerPaddingRight: Int\n        get() = config.footerPaddingRight\n        set(value) {\n            config.footerPaddingRight = value\n        }\n\n    var footerPaddingTop: Int\n        get() = config.footerPaddingTop\n        set(value) {\n            config.footerPaddingTop = value\n        }\n\n    var showHeaderLine: Boolean\n        get() = config.showHeaderLine\n        set(value) {\n            config.showHeaderLine = value\n        }\n\n    var showFooterLine: Boolean\n        get() = config.showFooterLine\n        set(value) {\n            config.showFooterLine = value\n        }\n\n    fun getExportConfig(): Config {\n        val exportConfig = durConfig.copy()\n        if (shareLayout) {\n            exportConfig.textFont = shareConfig.textFont\n            exportConfig.textBold = shareConfig.textBold\n            exportConfig.textSize = shareConfig.textSize\n            exportConfig.letterSpacing = shareConfig.letterSpacing\n            exportConfig.lineSpacingExtra = shareConfig.lineSpacingExtra\n            exportConfig.paragraphSpacing = shareConfig.paragraphSpacing\n            exportConfig.titleMode = shareConfig.titleMode\n            exportConfig.titleSize = shareConfig.titleSize\n            exportConfig.titleTopSpacing = shareConfig.titleTopSpacing\n            exportConfig.titleBottomSpacing = shareConfig.titleBottomSpacing\n            exportConfig.paddingBottom = shareConfig.paddingBottom\n            exportConfig.paddingLeft = shareConfig.paddingLeft\n            exportConfig.paddingRight = shareConfig.paddingRight\n            exportConfig.paddingTop = shareConfig.paddingTop\n            exportConfig.headerPaddingBottom = shareConfig.headerPaddingBottom\n            exportConfig.headerPaddingLeft = shareConfig.headerPaddingLeft\n            exportConfig.headerPaddingRight = shareConfig.headerPaddingRight\n            exportConfig.headerPaddingTop = shareConfig.headerPaddingTop\n            exportConfig.footerPaddingBottom = shareConfig.footerPaddingBottom\n            exportConfig.footerPaddingLeft = shareConfig.footerPaddingLeft\n            exportConfig.footerPaddingRight = shareConfig.footerPaddingRight\n            exportConfig.footerPaddingTop = shareConfig.footerPaddingTop\n            exportConfig.showHeaderLine = shareConfig.showHeaderLine\n            exportConfig.showFooterLine = shareConfig.showFooterLine\n            exportConfig.tipHeaderLeft = shareConfig.tipHeaderLeft\n            exportConfig.tipHeaderMiddle = shareConfig.tipHeaderMiddle\n            exportConfig.tipHeaderRight = shareConfig.tipHeaderRight\n            exportConfig.tipFooterLeft = shareConfig.tipFooterLeft\n            exportConfig.tipFooterMiddle = shareConfig.tipFooterMiddle\n            exportConfig.tipFooterRight = shareConfig.tipFooterRight\n            exportConfig.tipColor = shareConfig.tipColor\n            exportConfig.headerMode = shareConfig.headerMode\n            exportConfig.footerMode = shareConfig.footerMode\n        }\n        return exportConfig\n    }\n\n    fun import(byteArray: ByteArray): Config {\n        val configZipPath = FileUtils.getPath(appCtx.externalCache, \"readConfig.zip\")\n        FileUtils.delete(configZipPath)\n        val zipFile = FileUtils.createFileIfNotExist(configZipPath)\n        zipFile.writeBytes(byteArray)\n        val configDir = appCtx.externalCache.getFile(\"readConfig\")\n        configDir.createFolderReplace()\n        ZipUtils.unZipToPath(zipFile, configDir)\n        val configFile = configDir.getFile(configFileName)\n        val config: Config = GSON.fromJsonObject<Config>(configFile.readText()).getOrThrow()\n        if (config.textFont.isNotEmpty()) {\n            val fontName = config.textFont\n            val fontPath =\n                FileUtils.getPath(appCtx.externalFiles, \"font\", fontName)\n            val fontFile = configDir.getFile(fontName)\n            if (fontFile.exists()) {\n                if (!FileUtils.exist(fontPath)) {\n                    fontFile.copyTo(File(fontPath))\n                }\n                config.textFont = fontPath\n            } else {\n                config.textFont = \"\"\n            }\n        }\n        if (config.bgType == 2) {\n            val bgName = FileUtils.getName(config.bgStr)\n            config.bgStr = bgName\n            val bgPath = FileUtils.getPath(appCtx.externalFiles, \"bg\", bgName)\n            if (!FileUtils.exist(bgPath)) {\n                val bgFile = configDir.getFile(bgName)\n                if (bgFile.exists()) {\n                    bgFile.copyTo(File(bgPath))\n                }\n            }\n            config.bgStr = bgPath\n        } else if (config.bgType == 0) {\n            config.bgStr.toColorInt()\n        }\n        if (config.bgTypeNight == 2) {\n            val bgName = FileUtils.getName(config.bgStrNight)\n            config.bgStrNight = bgName\n            val bgPath = FileUtils.getPath(appCtx.externalFiles, \"bg\", bgName)\n            if (!FileUtils.exist(bgPath)) {\n                val bgFile = configDir.getFile(bgName)\n                if (bgFile.exists()) {\n                    bgFile.copyTo(File(bgPath))\n                }\n            }\n            config.bgStrNight = bgPath\n        } else if (config.bgTypeNight == 0) {\n            config.bgStrNight.toColorInt()\n        }\n        if (config.bgTypeEInk == 2) {\n            val bgName = FileUtils.getName(config.bgStrEInk)\n            config.bgStrEInk = bgName\n            val bgPath = FileUtils.getPath(appCtx.externalFiles, \"bg\", bgName)\n            if (!FileUtils.exist(bgPath)) {\n                val bgFile = configDir.getFile(bgName)\n                if (bgFile.exists()) {\n                    bgFile.copyTo(File(bgPath))\n                }\n            }\n            config.bgStrEInk = bgPath\n        } else if (config.bgTypeEInk == 0) {\n            config.bgStrEInk.toColorInt()\n        }\n        config.curTextColor()\n        return config\n    }\n\n    @Keep\n    data class Config(\n        var name: String = \"\",\n        var bgStr: String = \"#EEEEEE\",//白天背景\n        var bgStrNight: String = \"#000000\",//夜间背景\n        var bgStrEInk: String = \"#FFFFFF\",//EInk背景\n        var bgAlpha: Int = 100,//背景透明度\n        var bgType: Int = 0,//白天背景类型 0:颜色, 1:assets图片, 2其它图片\n        var bgTypeNight: Int = 0,//夜间背景类型\n        var bgTypeEInk: Int = 0,//EInk背景类型\n        private var darkStatusIcon: Boolean = true,//白天是否暗色状态栏\n        private var darkStatusIconNight: Boolean = false,//晚上是否暗色状态栏\n        private var darkStatusIconEInk: Boolean = true,\n        private var textColor: String = \"#3E3D3B\",//白天文字颜色\n        private var textColorNight: String = \"#ADADAD\",//夜间文字颜色\n        private var textColorEInk: String = \"#000000\",\n        private var pageAnim: Int = 0,//翻页动画\n        private var pageAnimEInk: Int = 4,\n        var textFont: String = \"\",//字体\n        var textBold: Int = 0,//是否粗体字 0:正常, 1:粗体, 2:细体\n        var textSize: Int = 20,//文字大小\n        var letterSpacing: Float = 0.1f,//字间距\n        var lineSpacingExtra: Int = 12,//行间距\n        var paragraphSpacing: Int = 2,//段距\n        var titleMode: Int = 0,//标题位置 0:居左 1:居中 2:隐藏\n        var titleSize: Int = 0,\n        var titleTopSpacing: Int = 0,\n        var titleBottomSpacing: Int = 0,\n        var paragraphIndent: String = \"　　\",//段落缩进\n        var underline: Boolean = false, //下划线\n        var paddingBottom: Int = 6,\n        var paddingLeft: Int = 16,\n        var paddingRight: Int = 16,\n        var paddingTop: Int = 6,\n        var headerPaddingBottom: Int = 0,\n        var headerPaddingLeft: Int = 16,\n        var headerPaddingRight: Int = 16,\n        var headerPaddingTop: Int = 0,\n        var footerPaddingBottom: Int = 6,\n        var footerPaddingLeft: Int = 16,\n        var footerPaddingRight: Int = 16,\n        var footerPaddingTop: Int = 6,\n        var showHeaderLine: Boolean = false,\n        var showFooterLine: Boolean = true,\n        var tipHeaderLeft: Int = ReadTipConfig.time,\n        var tipHeaderMiddle: Int = ReadTipConfig.none,\n        var tipHeaderRight: Int = ReadTipConfig.battery,\n        var tipFooterLeft: Int = ReadTipConfig.chapterTitle,\n        var tipFooterMiddle: Int = ReadTipConfig.none,\n        var tipFooterRight: Int = ReadTipConfig.pageAndTotal,\n        var tipColor: Int = 0,\n        var tipDividerColor: Int = -1,\n        var headerMode: Int = 0,\n        var footerMode: Int = 0\n    ) {\n\n        @Transient\n        private var textColorIntEInk = -1\n\n        @Transient\n        private var textColorIntNight = -1\n\n        @Transient\n        private var textColorInt = -1\n\n        @Transient\n        private var initColorInt = false\n\n        private fun initColorInt() {\n            textColorIntEInk = Color.parseColor(textColorEInk)\n            textColorIntNight = Color.parseColor(textColorNight)\n            textColorInt = Color.parseColor(textColor)\n            initColorInt = true\n        }\n\n        fun setCurTextColor(color: Int) {\n            when {\n                AppConfig.isEInkMode -> {\n                    textColorEInk = \"#${color.hexString}\"\n                    textColorIntEInk = color\n                }\n\n                AppConfig.isNightTheme -> {\n                    textColorNight = \"#${color.hexString}\"\n                    textColorIntNight = color\n                }\n\n                else -> {\n                    textColor = \"#${color.hexString}\"\n                    textColorInt = color\n                }\n            }\n        }\n\n        fun curTextColor(): Int {\n            if (!initColorInt) {\n                initColorInt()\n            }\n            return when {\n                AppConfig.isEInkMode -> textColorIntEInk\n                AppConfig.isNightTheme -> textColorIntNight\n                else -> textColorInt\n            }\n        }\n\n        fun setCurStatusIconDark(isDark: Boolean) {\n            when {\n                AppConfig.isEInkMode -> darkStatusIconEInk = isDark\n                AppConfig.isNightTheme -> darkStatusIconNight = isDark\n                else -> darkStatusIcon = isDark\n            }\n        }\n\n        fun curStatusIconDark(): Boolean {\n            return when {\n                AppConfig.isEInkMode -> darkStatusIconEInk\n                AppConfig.isNightTheme -> darkStatusIconNight\n                else -> darkStatusIcon\n            }\n        }\n\n        fun setCurPageAnim(@PageAnim.Anim anim: Int) {\n            when {\n                AppConfig.isEInkMode -> pageAnimEInk = anim\n                else -> pageAnim = anim\n            }\n        }\n\n        fun curPageAnim(): Int {\n            return when {\n                AppConfig.isEInkMode -> pageAnimEInk\n                else -> pageAnim\n            }\n        }\n\n        fun setCurBg(bgType: Int, bg: String) {\n            when {\n                AppConfig.isEInkMode -> {\n                    bgTypeEInk = bgType\n                    bgStrEInk = bg\n                }\n\n                AppConfig.isNightTheme -> {\n                    bgTypeNight = bgType\n                    bgStrNight = bg\n                }\n\n                else -> {\n                    this.bgType = bgType\n                    bgStr = bg\n                }\n            }\n        }\n\n        fun curBgStr(): String {\n            return when {\n                AppConfig.isEInkMode -> bgStrEInk\n                AppConfig.isNightTheme -> bgStrNight\n                else -> bgStr\n            }\n        }\n\n        fun curBgType(): Int {\n            return when {\n                AppConfig.isEInkMode -> bgTypeEInk\n                AppConfig.isNightTheme -> bgTypeNight\n                else -> bgType\n            }\n        }\n\n        fun curBgDrawable(width: Int, height: Int): Drawable {\n            if (width == 0 || height == 0) {\n                return ColorDrawable(appCtx.getCompatColor(R.color.background))\n            }\n            var bgDrawable: Drawable? = null\n            val resources = appCtx.resources\n            try {\n                bgDrawable = when (curBgType()) {\n                    0 -> ColorDrawable(Color.parseColor(curBgStr()))\n                    1 -> {\n                        val path = \"bg\" + File.separator + curBgStr()\n                        val bitmap = BitmapUtils.decodeAssetsBitmap(appCtx, path, width, height)\n                        BitmapDrawable(resources, bitmap?.resizeAndRecycle(width, height))\n                    }\n\n                    else -> {\n                        val path = curBgStr().let {\n                            if (it.contains(File.separator)) it\n                            else FileUtils.getPath(appCtx.externalFiles, \"bg\", curBgStr())\n                        }\n                        val bitmap = BitmapUtils.decodeBitmap(path, width, height)\n                        BitmapDrawable(resources, bitmap?.resizeAndRecycle(width, height))\n                    }\n                }\n            } catch (e: OutOfMemoryError) {\n                e.printOnDebug()\n            } catch (e: Exception) {\n                e.printOnDebug()\n            }\n            return bgDrawable ?: ColorDrawable(appCtx.getCompatColor(R.color.background))\n        }\n\n        fun getBgPath(bgIndex: Int): String? {\n            val bgType = when (bgIndex) {\n                0 -> bgType\n                1 -> bgTypeNight\n                2 -> bgTypeEInk\n                else -> error(\"unknown bgIndex: $bgIndex\")\n            }\n            if (bgType != 2) {\n                return null\n            }\n            val bgStr = when (bgIndex) {\n                0 -> bgStr\n                1 -> bgStrNight\n                2 -> bgStrEInk\n                else -> error(\"unknown bgIndex: $bgIndex\")\n            }\n            val path = if (bgStr.contains(File.separator)) {\n                bgStr\n            } else {\n                FileUtils.getPath(appCtx.externalFiles, \"bg\", bgStr)\n            }\n            return path\n        }\n    }\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/help/config/ReadTipConfig.kt",
    "content": "package io.legado.app.help.config\n\nimport android.content.Context\nimport io.legado.app.R\nimport splitties.init.appCtx\n\n@Suppress(\"ConstPropertyName\")\nobject ReadTipConfig {\n\n    const val none = 0\n    const val chapterTitle = 1\n    const val time = 2\n    const val battery = 3\n    const val batteryPercentage = 10\n    const val page = 4\n    const val totalProgress = 5\n    const val pageAndTotal = 6\n    const val bookName = 7\n    const val timeBattery = 8\n    const val timeBatteryPercentage = 9\n    const val totalProgress1 = 11\n\n    val tipValues = arrayOf(\n        none, bookName, chapterTitle, time, battery, batteryPercentage, page,\n        totalProgress, totalProgress1, pageAndTotal, timeBattery, timeBatteryPercentage\n    )\n    val tipNames get() = appCtx.resources.getStringArray(R.array.read_tip).toList()\n\n    val tipColorNames get() = appCtx.resources.getStringArray(R.array.tip_color).toList()\n    val tipDividerColorNames\n        get() = appCtx.resources.getStringArray(R.array.tip_divider_color).toList()\n\n    var tipHeaderLeft: Int\n        get() = ReadBookConfig.config.tipHeaderLeft\n        set(value) {\n            ReadBookConfig.config.tipHeaderLeft = value\n        }\n\n    var tipHeaderMiddle: Int\n        get() = ReadBookConfig.config.tipHeaderMiddle\n        set(value) {\n            ReadBookConfig.config.tipHeaderMiddle = value\n        }\n\n    var tipHeaderRight: Int\n        get() = ReadBookConfig.config.tipHeaderRight\n        set(value) {\n            ReadBookConfig.config.tipHeaderRight = value\n        }\n\n    var tipFooterLeft: Int\n        get() = ReadBookConfig.config.tipFooterLeft\n        set(value) {\n            ReadBookConfig.config.tipFooterLeft = value\n        }\n\n    var tipFooterMiddle: Int\n        get() = ReadBookConfig.config.tipFooterMiddle\n        set(value) {\n            ReadBookConfig.config.tipFooterMiddle = value\n        }\n\n    var tipFooterRight: Int\n        get() = ReadBookConfig.config.tipFooterRight\n        set(value) {\n            ReadBookConfig.config.tipFooterRight = value\n        }\n\n    var headerMode: Int\n        get() = ReadBookConfig.config.headerMode\n        set(value) {\n            ReadBookConfig.config.headerMode = value\n        }\n\n    var footerMode: Int\n        get() = ReadBookConfig.config.footerMode\n        set(value) {\n            ReadBookConfig.config.footerMode = value\n        }\n\n    var tipColor: Int\n        get() = ReadBookConfig.config.tipColor\n        set(value) {\n            ReadBookConfig.config.tipColor = value\n        }\n\n    var tipDividerColor: Int\n        get() = ReadBookConfig.config.tipDividerColor\n        set(value) {\n            ReadBookConfig.config.tipDividerColor = value\n        }\n\n    fun getHeaderModes(context: Context): LinkedHashMap<Int, String> {\n        return linkedMapOf(\n            Pair(0, context.getString(R.string.hide_when_status_bar_show)),\n            Pair(1, context.getString(R.string.show)),\n            Pair(2, context.getString(R.string.hide))\n        )\n    }\n\n    fun getFooterModes(context: Context): LinkedHashMap<Int, String> {\n        return linkedMapOf(\n            Pair(0, context.getString(R.string.show)),\n            Pair(1, context.getString(R.string.hide))\n        )\n    }\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/help/config/SourceConfig.kt",
    "content": "package io.legado.app.help.config\n\nimport android.content.Context.MODE_PRIVATE\nimport androidx.core.content.edit\nimport splitties.init.appCtx\n\nobject SourceConfig {\n    private val sp = appCtx.getSharedPreferences(\"SourceConfig\", MODE_PRIVATE)\n    fun setBookScore(origin: String, name: String, author: String, score: Int) {\n        sp.edit {\n            val preScore = getBookScore(origin, name, author)\n            var newScore = score\n            if (preScore != 0) {\n                newScore = score - preScore\n            }\n\n            putInt(origin, getSourceScore(origin) + newScore)\n\n            putInt(\"${origin}_${name}_${author}\", score)\n        }\n    }\n\n    fun getBookScore(origin: String, name: String, author: String): Int {\n        return sp.getInt(\"${origin}_${name}_${author}\", 0)\n    }\n\n    fun getSourceScore(origin: String): Int {\n        return sp.getInt(origin, 0)\n    }\n\n\n    fun removeSource(origin: String) {\n        sp.all.keys.filter {\n            it.startsWith(origin)\n        }.let {\n            sp.edit {\n                it.forEach {\n                    remove(it)\n                }\n            }\n        }\n    }\n\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/help/config/ThemeConfig.kt",
    "content": "package io.legado.app.help.config\n\nimport android.content.Context\nimport android.graphics.Bitmap\nimport android.graphics.Color\nimport android.util.DisplayMetrics\nimport androidx.annotation.Keep\nimport androidx.appcompat.app.AppCompatDelegate\nimport androidx.core.graphics.toColorInt\nimport io.legado.app.R\nimport io.legado.app.constant.AppLog\nimport io.legado.app.constant.EventBus\nimport io.legado.app.constant.PreferKey\nimport io.legado.app.constant.Theme\nimport io.legado.app.help.DefaultData\nimport io.legado.app.lib.theme.ThemeStore\nimport io.legado.app.model.BookCover\nimport io.legado.app.utils.BitmapUtils\nimport io.legado.app.utils.ColorUtils\nimport io.legado.app.utils.FileUtils\nimport io.legado.app.utils.GSON\nimport io.legado.app.utils.externalFiles\nimport io.legado.app.utils.fromJsonArray\nimport io.legado.app.utils.fromJsonObject\nimport io.legado.app.utils.getCompatColor\nimport io.legado.app.utils.getFile\nimport io.legado.app.utils.getPrefInt\nimport io.legado.app.utils.getPrefString\nimport io.legado.app.utils.hexString\nimport io.legado.app.utils.postEvent\nimport io.legado.app.utils.printOnDebug\nimport io.legado.app.utils.putPrefInt\nimport io.legado.app.utils.stackBlur\nimport splitties.init.appCtx\nimport java.io.File\n\n@Keep\nobject ThemeConfig {\n    const val configFileName = \"themeConfig.json\"\n    val configFilePath = FileUtils.getPath(appCtx.filesDir, configFileName)\n\n    val configList: ArrayList<Config> by lazy {\n        val cList = getConfigs() ?: DefaultData.themeConfigs\n        ArrayList(cList)\n    }\n\n    fun getTheme() = when {\n        AppConfig.isEInkMode -> Theme.EInk\n        AppConfig.isNightTheme -> Theme.Dark\n        else -> Theme.Light\n    }\n\n    fun isDarkTheme(): Boolean {\n        return getTheme() == Theme.Dark\n    }\n\n    fun applyDayNight(context: Context) {\n        applyTheme(context)\n        initNightMode()\n        BookCover.upDefaultCover()\n        postEvent(EventBus.RECREATE, \"\")\n    }\n\n    fun applyDayNightInit(context: Context) {\n        applyTheme(context)\n        initNightMode()\n    }\n\n    private fun initNightMode() {\n        val targetMode =\n            if (AppConfig.isNightTheme) {\n                AppCompatDelegate.MODE_NIGHT_YES\n            } else {\n                AppCompatDelegate.MODE_NIGHT_NO\n            }\n        AppCompatDelegate.setDefaultNightMode(targetMode)\n    }\n\n    fun getBgImage(context: Context, metrics: DisplayMetrics): Bitmap? {\n        val bgCfg = when (getTheme()) {\n            Theme.Light -> Pair(\n                context.getPrefString(PreferKey.bgImage),\n                context.getPrefInt(PreferKey.bgImageBlurring, 0)\n            )\n\n            Theme.Dark -> Pair(\n                context.getPrefString(PreferKey.bgImageN),\n                context.getPrefInt(PreferKey.bgImageNBlurring, 0)\n            )\n\n            else -> null\n        } ?: return null\n        if (bgCfg.first.isNullOrBlank()) return null\n        val bgImage = BitmapUtils\n            .decodeBitmap(bgCfg.first!!, metrics.widthPixels, metrics.heightPixels)\n        if (bgCfg.second == 0) {\n            return bgImage\n        }\n        return bgImage?.stackBlur(bgCfg.second)\n    }\n\n    fun upConfig() {\n        getConfigs()?.forEach { config ->\n            addConfig(config)\n        }\n    }\n\n    fun save() {\n        val json = GSON.toJson(configList)\n        FileUtils.delete(configFilePath)\n        FileUtils.createFileIfNotExist(configFilePath).writeText(json)\n    }\n\n    fun delConfig(index: Int) {\n        configList.removeAt(index)\n        save()\n    }\n\n    fun addConfig(json: String): Boolean {\n        GSON.fromJsonObject<Config>(json.trim { it < ' ' }).getOrNull()\n            ?.let {\n                if (validateConfig(it)) {\n                    addConfig(it)\n                    return true\n                }\n            }\n        return false\n    }\n\n    fun addConfig(newConfig: Config) {\n        if (!validateConfig(newConfig)) {\n            return\n        }\n        configList.forEachIndexed { index, config ->\n            if (newConfig.themeName == config.themeName) {\n                configList[index] = newConfig\n                return\n            }\n        }\n        configList.add(newConfig)\n        save()\n    }\n\n    private fun validateConfig(config: Config): Boolean {\n        try {\n            config.primaryColor.toColorInt()\n            config.accentColor.toColorInt()\n            config.backgroundColor.toColorInt()\n            config.bottomBackground.toColorInt()\n            return true\n        } catch (_: Exception) {\n            return false\n        }\n    }\n\n    private fun getConfigs(): List<Config>? {\n        val configFile = File(configFilePath)\n        if (configFile.exists()) {\n            kotlin.runCatching {\n                val json = configFile.readText()\n                return GSON.fromJsonArray<Config>(json).getOrThrow()\n            }.onFailure {\n                it.printOnDebug()\n            }\n        }\n        return null\n    }\n\n    fun applyConfig(context: Context, config: Config) {\n        try {\n            val primary = Color.parseColor(config.primaryColor)\n            val accent = Color.parseColor(config.accentColor)\n            val background = Color.parseColor(config.backgroundColor)\n            val bBackground = Color.parseColor(config.bottomBackground)\n            if (config.isNightTheme) {\n                context.putPrefInt(PreferKey.cNPrimary, primary)\n                context.putPrefInt(PreferKey.cNAccent, accent)\n                context.putPrefInt(PreferKey.cNBackground, background)\n                context.putPrefInt(PreferKey.cNBBackground, bBackground)\n            } else {\n                context.putPrefInt(PreferKey.cPrimary, primary)\n                context.putPrefInt(PreferKey.cAccent, accent)\n                context.putPrefInt(PreferKey.cBackground, background)\n                context.putPrefInt(PreferKey.cBBackground, bBackground)\n            }\n            AppConfig.isNightTheme = config.isNightTheme\n            applyDayNight(context)\n        } catch (e: Exception) {\n            AppLog.put(\"设置主题出错\\n$e\", e, true)\n        }\n    }\n\n    fun saveDayTheme(context: Context, name: String) {\n        val primary =\n            context.getPrefInt(PreferKey.cPrimary, context.getCompatColor(R.color.md_brown_500))\n        val accent =\n            context.getPrefInt(PreferKey.cAccent, context.getCompatColor(R.color.md_red_600))\n        val background =\n            context.getPrefInt(PreferKey.cBackground, context.getCompatColor(R.color.md_grey_100))\n        val bBackground =\n            context.getPrefInt(PreferKey.cBBackground, context.getCompatColor(R.color.md_grey_200))\n        val config = Config(\n            themeName = name,\n            isNightTheme = false,\n            primaryColor = \"#${primary.hexString}\",\n            accentColor = \"#${accent.hexString}\",\n            backgroundColor = \"#${background.hexString}\",\n            bottomBackground = \"#${bBackground.hexString}\"\n        )\n        addConfig(config)\n    }\n\n    fun saveNightTheme(context: Context, name: String) {\n        val primary =\n            context.getPrefInt(\n                PreferKey.cNPrimary,\n                context.getCompatColor(R.color.md_blue_grey_600)\n            )\n        val accent =\n            context.getPrefInt(\n                PreferKey.cNAccent,\n                context.getCompatColor(R.color.md_deep_orange_800)\n            )\n        val background =\n            context.getPrefInt(PreferKey.cNBackground, context.getCompatColor(R.color.md_grey_900))\n        val bBackground =\n            context.getPrefInt(PreferKey.cNBBackground, context.getCompatColor(R.color.md_grey_850))\n        val config = Config(\n            themeName = name,\n            isNightTheme = true,\n            primaryColor = \"#${primary.hexString}\",\n            accentColor = \"#${accent.hexString}\",\n            backgroundColor = \"#${background.hexString}\",\n            bottomBackground = \"#${bBackground.hexString}\"\n        )\n        addConfig(config)\n    }\n\n    /**\n     * 更新主题\n     */\n    fun applyTheme(context: Context) = with(context) {\n        when {\n            AppConfig.isEInkMode -> {\n                ThemeStore.editTheme(this)\n                    .primaryColor(Color.WHITE)\n                    .accentColor(Color.BLACK)\n                    .backgroundColor(Color.WHITE)\n                    .bottomBackground(Color.WHITE)\n                    .apply()\n            }\n\n            AppConfig.isNightTheme -> {\n                val primary =\n                    getPrefInt(PreferKey.cNPrimary, getCompatColor(R.color.md_blue_grey_600))\n                val accent =\n                    getPrefInt(PreferKey.cNAccent, getCompatColor(R.color.md_deep_orange_800))\n                var background =\n                    getPrefInt(PreferKey.cNBackground, getCompatColor(R.color.md_grey_900))\n                if (ColorUtils.isColorLight(background)) {\n                    background = getCompatColor(R.color.md_grey_900)\n                    putPrefInt(PreferKey.cNBackground, background)\n                }\n                val bBackground =\n                    getPrefInt(PreferKey.cNBBackground, getCompatColor(R.color.md_grey_850))\n                ThemeStore.editTheme(this)\n                    .primaryColor(ColorUtils.withAlpha(primary, 1f))\n                    .accentColor(ColorUtils.withAlpha(accent, 1f))\n                    .backgroundColor(ColorUtils.withAlpha(background, 1f))\n                    .bottomBackground(ColorUtils.withAlpha(bBackground, 1f))\n                    .apply()\n            }\n\n            else -> {\n                val primary =\n                    getPrefInt(PreferKey.cPrimary, getCompatColor(R.color.md_brown_500))\n                val accent =\n                    getPrefInt(PreferKey.cAccent, getCompatColor(R.color.md_red_600))\n                var background =\n                    getPrefInt(PreferKey.cBackground, getCompatColor(R.color.md_grey_100))\n                if (!ColorUtils.isColorLight(background)) {\n                    background = getCompatColor(R.color.md_grey_100)\n                    putPrefInt(PreferKey.cBackground, background)\n                }\n                val bBackground =\n                    getPrefInt(PreferKey.cBBackground, getCompatColor(R.color.md_grey_200))\n                ThemeStore.editTheme(this)\n                    .primaryColor(ColorUtils.withAlpha(primary, 1f))\n                    .accentColor(ColorUtils.withAlpha(accent, 1f))\n                    .backgroundColor(ColorUtils.withAlpha(background, 1f))\n                    .bottomBackground(ColorUtils.withAlpha(bBackground, 1f))\n                    .apply()\n            }\n        }\n    }\n\n    fun clearBg() {\n        val bgImagePath = appCtx.getPrefString(PreferKey.bgImage)\n        appCtx.externalFiles.getFile(PreferKey.bgImage).listFiles()?.forEach {\n            if (it.absolutePath != bgImagePath) {\n                it.delete()\n            }\n        }\n        val bgImageNPath = appCtx.getPrefString(PreferKey.bgImageN)\n        appCtx.externalFiles.getFile(PreferKey.bgImageN).listFiles()?.forEach {\n            if (it.absolutePath != bgImageNPath) {\n                it.delete()\n            }\n        }\n    }\n\n    @Keep\n    data class Config(\n        var themeName: String,\n        var isNightTheme: Boolean,\n        var primaryColor: String,\n        var accentColor: String,\n        var backgroundColor: String,\n        var bottomBackground: String\n    ) {\n\n        override fun hashCode(): Int {\n            return GSON.toJson(this).hashCode()\n        }\n\n        override fun equals(other: Any?): Boolean {\n            other ?: return false\n            if (other is Config) {\n                return other.themeName == themeName\n                        && other.isNightTheme == isNightTheme\n                        && other.primaryColor == primaryColor\n                        && other.accentColor == accentColor\n                        && other.backgroundColor == backgroundColor\n                        && other.bottomBackground == bottomBackground\n            }\n            return false\n        }\n\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/help/coroutine/ActivelyCancelException.kt",
    "content": "package io.legado.app.help.coroutine\n\nimport kotlin.coroutines.cancellation.CancellationException\n\nclass ActivelyCancelException : CancellationException() {\n\n    override fun fillInStackTrace(): Throwable {\n        stackTrace = emptyArray()\n        return this\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/help/coroutine/CompositeCoroutine.kt",
    "content": "package io.legado.app.help.coroutine\n\n@Suppress(\"unused\")\nclass CompositeCoroutine : CoroutineContainer {\n\n    private var resources: HashSet<Coroutine<*>>? = null\n\n    val size: Int\n        get() = resources?.size ?: 0\n\n    val isEmpty: Boolean\n        get() = size == 0\n\n    constructor()\n\n    constructor(vararg coroutines: Coroutine<*>) {\n        this.resources = hashSetOf(*coroutines)\n    }\n\n    constructor(coroutines: Iterable<Coroutine<*>>) {\n        this.resources = hashSetOf()\n        for (d in coroutines) {\n            this.resources?.add(d)\n        }\n    }\n\n    override fun add(coroutine: Coroutine<*>): Boolean {\n        synchronized(this) {\n            var set: HashSet<Coroutine<*>>? = resources\n            if (resources == null) {\n                set = hashSetOf()\n                resources = set\n            }\n            return set!!.add(coroutine)\n        }\n    }\n\n    override fun addAll(vararg coroutines: Coroutine<*>): Boolean {\n        synchronized(this) {\n            var set: HashSet<Coroutine<*>>? = resources\n            if (resources == null) {\n                set = hashSetOf()\n                resources = set\n            }\n            for (coroutine in coroutines) {\n                val add = set!!.add(coroutine)\n                if (!add) {\n                    return false\n                }\n            }\n        }\n        return true\n    }\n\n    override fun remove(coroutine: Coroutine<*>): Boolean {\n        if (delete(coroutine)) {\n            coroutine.cancel()\n            return true\n        }\n        return false\n    }\n\n    override fun delete(coroutine: Coroutine<*>): Boolean {\n        synchronized(this) {\n            val set = resources\n            if (set == null || !set.remove(coroutine)) {\n                return false\n            }\n        }\n        return true\n    }\n\n    override fun clear() {\n        val set: HashSet<Coroutine<*>>?\n        synchronized(this) {\n            set = resources\n            resources = null\n        }\n\n        set?.forEachIndexed { _, coroutine ->\n            coroutine.cancel()\n        }\n    }\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/help/coroutine/Coroutine.kt",
    "content": "package io.legado.app.help.coroutine\n\nimport io.legado.app.utils.printOnDebug\nimport kotlinx.coroutines.CancellationException\nimport kotlinx.coroutines.CompletionHandler\nimport kotlinx.coroutines.CoroutineScope\nimport kotlinx.coroutines.CoroutineStart\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.DisposableHandle\nimport kotlinx.coroutines.Job\nimport kotlinx.coroutines.MainScope\nimport kotlinx.coroutines.ensureActive\nimport kotlinx.coroutines.isActive\nimport kotlinx.coroutines.launch\nimport kotlinx.coroutines.plus\nimport kotlinx.coroutines.sync.Semaphore\nimport kotlinx.coroutines.withContext\nimport kotlinx.coroutines.withTimeout\nimport kotlin.coroutines.CoroutineContext\n\n/**\n * 链式协程\n * 注意：如果协程太快完成，回调会不执行\n */\n@Suppress(\"unused\", \"MemberVisibilityCanBePrivate\")\nclass Coroutine<T>(\n    private val scope: CoroutineScope,\n    context: CoroutineContext = Dispatchers.IO,\n    private val startOption: CoroutineStart = CoroutineStart.DEFAULT,\n    private val executeContext: CoroutineContext = Dispatchers.Main,\n    private val semaphore: Semaphore? = null,\n    block: suspend CoroutineScope.() -> T\n) {\n\n    companion object {\n\n        private val DEFAULT = MainScope()\n\n        fun <T> async(\n            scope: CoroutineScope = DEFAULT,\n            context: CoroutineContext = Dispatchers.IO,\n            start: CoroutineStart = CoroutineStart.DEFAULT,\n            executeContext: CoroutineContext = Dispatchers.Main,\n            semaphore: Semaphore? = null,\n            block: suspend CoroutineScope.() -> T\n        ): Coroutine<T> {\n            return Coroutine(scope, context, start, executeContext, semaphore, block)\n        }\n\n    }\n\n    private val job: Job\n\n    private var start: VoidCallback? = null\n    private var success: Callback<T>? = null\n    private var error: Callback<Throwable>? = null\n    private var finally: VoidCallback? = null\n    private var cancel: VoidCallback? = null\n\n    private var timeMillis: Long? = null\n    private var errorReturn: Result<T>? = null\n\n    val isCancelled: Boolean\n        get() = job.isCancelled\n\n    val isActive: Boolean\n        get() = job.isActive\n\n    val isCompleted: Boolean\n        get() = job.isCompleted\n\n    init {\n        this.job = executeInternal(context, block)\n    }\n\n    fun timeout(timeMillis: () -> Long): Coroutine<T> {\n        this.timeMillis = timeMillis()\n        return this@Coroutine\n    }\n\n    fun timeout(timeMillis: Long): Coroutine<T> {\n        this.timeMillis = timeMillis\n        return this@Coroutine\n    }\n\n    fun onErrorReturn(value: () -> T?): Coroutine<T> {\n        this.errorReturn = Result(value())\n        return this@Coroutine\n    }\n\n    fun onErrorReturn(value: T?): Coroutine<T> {\n        this.errorReturn = Result(value)\n        return this@Coroutine\n    }\n\n    fun onStart(\n        context: CoroutineContext? = null,\n        block: (suspend CoroutineScope.() -> Unit)\n    ): Coroutine<T> {\n        this.start = VoidCallback(context, block)\n        return this@Coroutine\n    }\n\n    fun onSuccess(\n        context: CoroutineContext? = null,\n        block: suspend CoroutineScope.(T) -> Unit\n    ): Coroutine<T> {\n        this.success = Callback(context, block)\n        return this@Coroutine\n    }\n\n    fun onError(\n        context: CoroutineContext? = null,\n        block: suspend CoroutineScope.(Throwable) -> Unit\n    ): Coroutine<T> {\n        this.error = Callback(context, block)\n        return this@Coroutine\n    }\n\n    /**\n     * 如果协程被取消，不执行\n     */\n    fun onFinally(\n        context: CoroutineContext? = null,\n        block: suspend CoroutineScope.() -> Unit\n    ): Coroutine<T> {\n        this.finally = VoidCallback(context, block)\n        return this@Coroutine\n    }\n\n    fun onCancel(\n        context: CoroutineContext? = null,\n        block: suspend CoroutineScope.() -> Unit\n    ): Coroutine<T> {\n        this.cancel = VoidCallback(context, block)\n        job.invokeOnCompletion {\n            if (it is CancellationException && it !is ActivelyCancelException) {\n                cancel()\n            }\n        }\n        return this@Coroutine\n    }\n\n    //取消当前任务\n    fun cancel(cause: ActivelyCancelException = ActivelyCancelException()) {\n        if (!job.isCancelled) {\n            job.cancel(cause)\n        }\n        cancel?.let {\n            DEFAULT.launch(executeContext) {\n                if (null == it.context) {\n                    it.block.invoke(this)\n                } else {\n                    withContext(it.context) {\n                        it.block.invoke(this)\n                    }\n                }\n            }\n        }\n    }\n\n    fun invokeOnCompletion(handler: CompletionHandler): DisposableHandle {\n        return job.invokeOnCompletion(handler)\n    }\n\n    fun start() {\n        job.start()\n    }\n\n    private fun executeInternal(\n        context: CoroutineContext,\n        block: suspend CoroutineScope.() -> T\n    ): Job {\n        return (scope.plus(executeContext)).launch(start = startOption) {\n            semaphore?.acquire()\n            try {\n                start?.let { dispatchVoidCallback(this, it) }\n                ensureActive()\n                val value = executeBlock(this, context, timeMillis ?: 0L, block)\n                ensureActive()\n                success?.let { dispatchCallback(this, value, it) }\n            } catch (e: Throwable) {\n                e.printOnDebug()\n                val consume: Boolean = errorReturn?.value?.let { value ->\n                    success?.let { dispatchCallback(this, value, it) }\n                    true\n                } ?: false\n                if (!consume) {\n                    error?.let { dispatchCallback(this, e, it) }\n                }\n            } finally {\n                try {\n                    finally?.let { dispatchVoidCallback(this, it) }\n                } finally {\n                    semaphore?.release()\n                }\n            }\n        }\n    }\n\n    private suspend inline fun dispatchVoidCallback(scope: CoroutineScope, callback: VoidCallback) {\n        if (null == callback.context) {\n            callback.block.invoke(scope)\n        } else {\n            withContext(callback.context) {\n                callback.block.invoke(this)\n            }\n        }\n    }\n\n    private suspend inline fun <R> dispatchCallback(\n        scope: CoroutineScope,\n        value: R,\n        callback: Callback<R>\n    ) {\n        if (!scope.isActive) return\n        if (null == callback.context) {\n            callback.block.invoke(scope, value)\n        } else {\n            withContext(callback.context) {\n                callback.block.invoke(this, value)\n            }\n        }\n    }\n\n    private suspend inline fun executeBlock(\n        scope: CoroutineScope,\n        context: CoroutineContext,\n        timeMillis: Long,\n        noinline block: suspend CoroutineScope.() -> T\n    ): T {\n        return withContext(context) {\n            if (timeMillis > 0L) withTimeout(timeMillis) {\n                block()\n            } else {\n                block()\n            }\n        }\n    }\n\n    private data class Result<out T>(val value: T?)\n\n    private class VoidCallback(\n        val context: CoroutineContext?,\n        val block: suspend CoroutineScope.() -> Unit\n    )\n\n    private class Callback<VALUE>(\n        val context: CoroutineContext?,\n        val block: suspend CoroutineScope.(VALUE) -> Unit\n    )\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/help/coroutine/CoroutineContainer.kt",
    "content": "package io.legado.app.help.coroutine\n\ninternal interface CoroutineContainer {\n\n    fun add(coroutine: Coroutine<*>): Boolean\n\n    fun addAll(vararg coroutines: Coroutine<*>): Boolean\n\n    fun remove(coroutine: Coroutine<*>): Boolean\n\n    fun delete(coroutine: Coroutine<*>): Boolean\n\n    fun clear()\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/help/crypto/AsymmetricCrypto.kt",
    "content": "package io.legado.app.help.crypto\n\nimport androidx.annotation.Keep\nimport cn.hutool.crypto.KeyUtil\nimport cn.hutool.crypto.asymmetric.KeyType\nimport io.legado.app.utils.EncoderUtils\nimport java.io.InputStream\nimport cn.hutool.crypto.asymmetric.AsymmetricCrypto as HutoolAsymmetricCrypto\n\n@Keep\n@Suppress(\"unused\")\nclass AsymmetricCrypto(algorithm: String) : HutoolAsymmetricCrypto(algorithm) {\n\n    @Suppress(\"MemberVisibilityCanBePrivate\")\n    fun setPrivateKey(key: ByteArray): AsymmetricCrypto {\n        setPrivateKey(\n            KeyUtil.generatePrivateKey(this.algorithm, key)\n        )\n        return this\n    }\n\n    fun setPrivateKey(key: String): AsymmetricCrypto = setPrivateKey(key.encodeToByteArray())\n\n    @Suppress(\"MemberVisibilityCanBePrivate\")\n    fun setPublicKey(key: ByteArray): AsymmetricCrypto {\n        setPublicKey(\n            KeyUtil.generatePublicKey(this.algorithm, key)\n        )\n        return this\n    }\n\n    fun setPublicKey(key: String): AsymmetricCrypto = setPublicKey(key.encodeToByteArray())\n\n    private fun getKeyType(usePublicKey: Boolean? = true): KeyType {\n        return when (usePublicKey) {\n            true -> KeyType.PublicKey\n            else -> KeyType.PrivateKey\n        }\n    }\n\n    @JvmOverloads\n    fun decrypt(data: Any, usePublicKey: Boolean? = true): ByteArray {\n        return when (data) {\n            is ByteArray -> decrypt(data, getKeyType(usePublicKey))\n            is String -> decrypt(data, getKeyType(usePublicKey))\n            is InputStream -> decrypt(data, getKeyType(usePublicKey))\n            else -> throw IllegalArgumentException(\"Unexpected input type\")\n        }\n    }\n\n    @JvmOverloads\n    fun decryptStr(data: Any, usePublicKey: Boolean? = true): String {\n        return when (data) {\n            is ByteArray -> String(decrypt(data, getKeyType(usePublicKey)))\n            is String -> decryptStr(data, getKeyType(usePublicKey))\n            is InputStream -> String(decrypt(data, getKeyType(usePublicKey)))\n            else -> throw IllegalArgumentException(\"Unexpected input type\")\n        }\n    }\n\n    @JvmOverloads\n    fun encrypt(data: Any, usePublicKey: Boolean? = true): ByteArray {\n        return when (data) {\n            is ByteArray -> encrypt(data, getKeyType(usePublicKey))\n            is String -> encrypt(data, getKeyType(usePublicKey))\n            is InputStream -> encrypt(data, getKeyType(usePublicKey))\n            else -> throw IllegalArgumentException(\"Unexpected input type\")\n        }\n    }\n\n    @JvmOverloads\n    fun encryptHex(data: Any, usePublicKey: Boolean? = true): String {\n        return when (data) {\n            is ByteArray -> encryptHex(data, getKeyType(usePublicKey))\n            is String -> encryptHex(data, getKeyType(usePublicKey))\n            is InputStream -> encryptHex(data, getKeyType(usePublicKey))\n            else -> throw IllegalArgumentException(\"Unexpected input type\")\n        }\n    }\n\n    @JvmOverloads\n    fun encryptBase64(data: Any, usePublicKey: Boolean? = true): String {\n        return EncoderUtils.base64Encode(encrypt(data, usePublicKey))\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/help/crypto/README.md",
    "content": "https://github.com/gedoor/legado/pull/2880\n\n非对称加密一般只能知道其中一个密钥，而RhinoJs调用java方法不能传入null和KeyType, 因此提供以下重载函数\n```kotlin\nfun setPublicKey(key: ByteArray): T\nfun setPublicKey(key: String): T\nfun setPrivateKey(key: ByteArray): T\nfun setPrivateKey(key: String): T\n\nfun decrypt(data: Any, usePublicKey: Boolean? = true): ByteArray?\nfun decryptStr(data: Any, usePublicKey: Boolean? = true): String?\n\nfun encrypt(data: Any, usePublicKey: Boolean? = true): ByteArray?\nfun encryptHex(data: Any, usePublicKey: Boolean? = true): String?\nfun encryptBase64(data: Any, usePublicKey: Boolean? = true): String?\n```"
  },
  {
    "path": "app/src/main/java/io/legado/app/help/crypto/Sign.kt",
    "content": "package io.legado.app.help.crypto\n\nimport androidx.annotation.Keep\nimport cn.hutool.crypto.KeyUtil\nimport cn.hutool.crypto.asymmetric.Sign as HutoolSign\n\n@Keep\n@Suppress(\"unused\")\nclass Sign(algorithm: String): HutoolSign(algorithm) {\n\n    fun setPrivateKey(key: ByteArray): Sign {\n        setPrivateKey(KeyUtil.generatePrivateKey(algorithm, key))\n        return this\n    }\n\n    fun setPrivateKey(key: String): Sign = setPrivateKey(key.encodeToByteArray())\n\n    fun setPublicKey(key: ByteArray): Sign {\n        setPublicKey(KeyUtil.generatePublicKey(algorithm, key))\n        return this\n    }\n\n    fun setPublicKey(key: String): Sign = setPublicKey(key.encodeToByteArray())\n\n}\n\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/help/crypto/SymmetricCryptoAndroid.kt",
    "content": "package io.legado.app.help.crypto\n\nimport androidx.annotation.Keep\nimport cn.hutool.core.codec.Base64\nimport cn.hutool.core.util.HexUtil\nimport cn.hutool.crypto.symmetric.SymmetricCrypto\nimport io.legado.app.utils.EncoderUtils\nimport io.legado.app.utils.isHex\nimport java.io.InputStream\nimport java.nio.charset.Charset\n\n@Keep\nclass SymmetricCryptoAndroid(\n    algorithm: String,\n    key: ByteArray?,\n) : SymmetricCrypto(algorithm, key) {\n\n    override fun encryptBase64(data: ByteArray): String {\n        return EncoderUtils.base64Encode(encrypt(data))\n    }\n\n    override fun encryptBase64(data: String, charset: String?): String {\n        return EncoderUtils.base64Encode(encrypt(data, charset))\n    }\n\n    override fun encryptBase64(data: String, charset: Charset?): String {\n        return EncoderUtils.base64Encode(encrypt(data, charset))\n    }\n\n    override fun encryptBase64(data: String): String {\n        return EncoderUtils.base64Encode(encrypt(data))\n    }\n\n    override fun encryptBase64(data: InputStream): String {\n        return EncoderUtils.base64Encode(encrypt(data))\n    }\n\n    override fun decrypt(data: String): ByteArray {\n        val bytes = if (data.isHex()) {\n            HexUtil.decodeHex(data)\n        } else {\n            Base64.decode(data)\n        }\n        return decrypt(bytes)\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/help/exoplayer/ExoPlayerHelper.kt",
    "content": "package io.legado.app.help.exoplayer\n\nimport android.annotation.SuppressLint\nimport android.content.Context\nimport android.net.Uri\nimport androidx.media3.common.MediaItem\nimport androidx.media3.database.StandaloneDatabaseProvider\nimport androidx.media3.datasource.FileDataSource\nimport androidx.media3.datasource.ResolvingDataSource\nimport androidx.media3.datasource.cache.Cache\nimport androidx.media3.datasource.cache.CacheDataSink\nimport androidx.media3.datasource.cache.CacheDataSource\nimport androidx.media3.datasource.cache.LeastRecentlyUsedCacheEvictor\nimport androidx.media3.datasource.cache.SimpleCache\nimport androidx.media3.datasource.okhttp.OkHttpDataSource\nimport androidx.media3.exoplayer.DefaultLoadControl\nimport androidx.media3.exoplayer.ExoPlayer\nimport androidx.media3.exoplayer.source.DefaultMediaSourceFactory\nimport androidx.media3.extractor.DefaultExtractorsFactory\nimport com.google.gson.reflect.TypeToken\nimport io.legado.app.help.http.okHttpClient\nimport io.legado.app.utils.GSON\nimport io.legado.app.utils.externalCache\nimport okhttp3.CacheControl\nimport splitties.init.appCtx\nimport java.io.File\nimport java.util.concurrent.TimeUnit\n\n\n@Suppress(\"unused\")\n@SuppressLint(\"UnsafeOptInUsageError\")\nobject ExoPlayerHelper {\n\n    private const val SPLIT_TAG = \"\\uD83D\\uDEA7\"\n\n    private val mapType by lazy {\n        object : TypeToken<Map<String, String>>() {}.type\n    }\n\n    fun createMediaItem(url: String, headers: Map<String, String>): MediaItem {\n        val formatUrl = url + SPLIT_TAG + GSON.toJson(headers, mapType)\n        return MediaItem.Builder().setUri(formatUrl).build()\n    }\n\n    fun createHttpExoPlayer(context: Context): ExoPlayer {\n        return ExoPlayer.Builder(context).setLoadControl(\n            DefaultLoadControl.Builder().setBufferDurationsMs(\n                DefaultLoadControl.DEFAULT_MIN_BUFFER_MS,\n                DefaultLoadControl.DEFAULT_MAX_BUFFER_MS,\n                DefaultLoadControl.DEFAULT_BUFFER_FOR_PLAYBACK_MS / 10,\n                DefaultLoadControl.DEFAULT_BUFFER_FOR_PLAYBACK_AFTER_REBUFFER_MS / 10\n            ).build()\n\n        ).setMediaSourceFactory(\n            DefaultMediaSourceFactory(\n                context,\n                DefaultExtractorsFactory().setConstantBitrateSeekingEnabled(true)\n            ).setDataSourceFactory(resolvingDataSource)\n                .setLiveTargetOffsetMs(5000)\n        ).build()\n    }\n\n\n    private val resolvingDataSource: ResolvingDataSource.Factory by lazy {\n        ResolvingDataSource.Factory(cacheDataSourceFactory) {\n            var res = it\n\n            if (it.uri.toString().contains(SPLIT_TAG)) {\n                val urls = it.uri.toString().split(SPLIT_TAG)\n                val url = urls[0]\n                res = res.withUri(Uri.parse(url))\n                try {\n                    val headers: Map<String, String> = GSON.fromJson(urls[1], mapType)\n                    okhttpDataFactory.setDefaultRequestProperties(headers)\n                } catch (_: Exception) {\n                }\n            }\n\n            res\n\n        }\n    }\n\n\n    /**\n     * 支持缓存的DataSource.Factory\n     */\n    private val cacheDataSourceFactory by lazy {\n        //使用自定义的CacheDataSource以支持设置UA\n        CacheDataSource.Factory()\n            .setCache(cache)\n            .setUpstreamDataSourceFactory(okhttpDataFactory)\n            .setCacheReadDataSourceFactory(FileDataSource.Factory())\n            .setCacheWriteDataSinkFactory(\n                CacheDataSink.Factory()\n                    .setCache(cache)\n                    .setFragmentSize(CacheDataSink.DEFAULT_FRAGMENT_SIZE)\n            )\n    }\n\n    /**\n     * Okhttp DataSource.Factory\n     */\n    private val okhttpDataFactory by lazy {\n        val client = okHttpClient.newBuilder()\n            .callTimeout(0, TimeUnit.SECONDS)\n            .build()\n        OkHttpDataSource.Factory(client)\n            .setCacheControl(CacheControl.Builder().maxAge(1, TimeUnit.DAYS).build())\n    }\n\n    /**\n     * Exoplayer 内置的缓存\n     */\n    private val cache: Cache by lazy {\n        val databaseProvider = StandaloneDatabaseProvider(appCtx)\n        return@lazy SimpleCache(\n            //Exoplayer的缓存路径\n            File(appCtx.externalCache, \"exoplayer\"),\n            //100M的缓存\n            LeastRecentlyUsedCacheEvictor((100 * 1024 * 1024).toLong()),\n            //记录缓存的数据库\n            databaseProvider\n        )\n    }\n\n    /**\n     * 通过kotlin扩展函数+反射实现CacheDataSource.Factory设置默认请求头\n     * 需要添加混淆规则 -keepclassmembers class com.google.android.exoplayer2.upstream.cache.CacheDataSource$Factory{upstreamDataSourceFactory;}\n     * @param headers\n     * @return\n     */\n//    private fun CacheDataSource.Factory.setDefaultRequestProperties(headers: Map<String, String> = mapOf()): CacheDataSource.Factory {\n//        val declaredField = this.javaClass.getDeclaredField(\"upstreamDataSourceFactory\")\n//        declaredField.isAccessible = true\n//        val df = declaredField[this] as DataSource.Factory\n//        if (df is OkHttpDataSource.Factory) {\n//            df.setDefaultRequestProperties(headers)\n//        }\n//        return this\n//    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/help/exoplayer/InputStreamDataSource.kt",
    "content": "package io.legado.app.help.exoplayer\n\nimport android.annotation.SuppressLint\nimport android.net.Uri\nimport androidx.media3.common.C\nimport androidx.media3.datasource.BaseDataSource\nimport androidx.media3.datasource.DataSpec\nimport java.io.EOFException\nimport java.io.IOException\nimport java.io.InputStream\nimport kotlin.math.min\n\n\n@SuppressLint(\"UnsafeOptInUsageError\")\nclass InputStreamDataSource(private val supplier: () -> InputStream) : BaseDataSource(false) {\n    private var dataSpec: DataSpec? = null\n    private var bytesRemaining: Long = 0\n    private var opened = false\n    private val inputStream by lazy {\n        supplier.invoke()\n    }\n\n    @Throws(IOException::class)\n    override fun open(dataSpec: DataSpec): Long {\n        this.dataSpec = dataSpec\n        transferInitializing(dataSpec)\n\n        inputStream.skip(dataSpec.position)\n\n        bytesRemaining = dataSpec.length\n\n        opened = true\n        transferStarted(dataSpec)\n        return bytesRemaining\n    }\n\n    override fun getUri(): Uri? = dataSpec?.uri\n\n    @Throws(IOException::class)\n    override fun read(buffer: ByteArray, offset: Int, readLength: Int): Int {\n        if (readLength == 0) {\n            return 0\n        } else if (bytesRemaining == 0L) {\n            return C.RESULT_END_OF_INPUT\n        }\n\n        val bytesToRead =\n            if (bytesRemaining == C.LENGTH_UNSET.toLong()) readLength\n            else min(bytesRemaining, readLength.toLong()).toInt()\n\n        val bytesRead = inputStream.read(buffer, offset, bytesToRead)\n\n        if (bytesRead == -1) {\n            if (bytesRemaining != C.LENGTH_UNSET.toLong()) {\n                // End of stream reached having not read sufficient data.\n                throw EOFException()\n            }\n            return C.RESULT_END_OF_INPUT\n        }\n\n        if (bytesRemaining != C.LENGTH_UNSET.toLong()) {\n            bytesRemaining -= bytesRead.toLong()\n            bytesTransferred(bytesRead)\n        }\n\n        return bytesRead\n    }\n\n    @Throws(IOException::class)\n    override fun close() {\n        if (!opened) {\n            return\n        }\n        try {\n            inputStream.close()\n        } finally {\n            opened = false\n            transferEnded()\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/help/glide/AsyncRecycleBitmapPool.kt",
    "content": "package io.legado.app.help.glide\n\nimport android.graphics.Bitmap\nimport com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool\nimport com.bumptech.glide.load.engine.bitmap_recycle.BitmapPoolAdapter\nimport com.bumptech.glide.load.engine.bitmap_recycle.LruBitmapPool\nimport io.legado.app.help.globalExecutor\n\nclass AsyncRecycleBitmapPool(private val delegate: BitmapPool) : BitmapPool by delegate {\n\n    constructor(maxSize: Int) : this(\n        if (maxSize > 0) {\n            LruBitmapPool(maxSize.toLong())\n        } else {\n            BitmapPoolAdapter()\n        }\n    )\n\n    override fun put(bitmap: Bitmap) {\n        globalExecutor.execute {\n            delegate.put(bitmap)\n        }\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/help/glide/BlurTransformation.kt",
    "content": "package io.legado.app.help.glide\n\nimport android.graphics.Bitmap\nimport androidx.annotation.IntRange\nimport com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool\nimport com.bumptech.glide.load.resource.bitmap.BitmapTransformation\nimport io.legado.app.utils.stackBlur\nimport java.security.MessageDigest\n\n/**\n * 模糊\n * @radius: 0..25\n */\nclass BlurTransformation(\n    @param:IntRange(from = 0, to = 25) private val radius: Int\n) : BitmapTransformation() {\n\n    override fun transform(\n        pool: BitmapPool,\n        toTransform: Bitmap,\n        outWidth: Int,\n        outHeight: Int\n    ): Bitmap {\n        return toTransform.stackBlur(radius)\n    }\n\n    override fun updateDiskCacheKey(messageDigest: MessageDigest) {\n        messageDigest.update(\"blur transformation\".toByteArray())\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/help/glide/FilePathLoader.kt",
    "content": "package io.legado.app.help.glide\n\nimport com.bumptech.glide.Priority\nimport com.bumptech.glide.load.DataSource\nimport com.bumptech.glide.load.data.DataFetcher\nimport com.bumptech.glide.load.model.ModelLoader\nimport com.bumptech.glide.load.model.ModelLoaderFactory\nimport com.bumptech.glide.load.model.MultiModelLoaderFactory\nimport com.bumptech.glide.signature.ObjectKey\nimport java.io.File\n\nclass FilePathLoader : ModelLoader<String, File> {\n    override fun buildLoadData(\n        model: String,\n        width: Int,\n        height: Int,\n        options: com.bumptech.glide.load.Options\n    ): ModelLoader.LoadData<File>? {\n        return ModelLoader.LoadData(ObjectKey(model), FilePathFetcher(model))\n    }\n\n    override fun handles(model: String): Boolean {\n        return true\n    }\n\n    class FilePathFetcher(private val filePath: String) : DataFetcher<File> {\n        override fun loadData(\n            priority: Priority,\n            callback: DataFetcher.DataCallback<in File>\n        ) {\n            val file = File(filePath)\n            if (file.exists() && file.isFile) {\n                callback.onDataReady(file)\n            } else {\n                callback.onLoadFailed(Exception(\"File not found: $filePath\"))\n            }\n        }\n\n        override fun cleanup() {}\n\n        override fun cancel() {}\n\n        override fun getDataClass(): Class<File> = File::class.java\n\n        override fun getDataSource(): DataSource = DataSource.LOCAL\n    }\n\n    class Factory : ModelLoaderFactory<String, File> {\n        override fun build(multiFactory: MultiModelLoaderFactory): ModelLoader<String, File> {\n            return FilePathLoader()\n        }\n\n        override fun teardown() {}\n    }\n}\n\n\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/help/glide/GlideHeaders.kt",
    "content": "package io.legado.app.help.glide\n\nimport com.bumptech.glide.load.model.Headers\n\nclass GlideHeaders(private val headers: MutableMap<String, String>) : Headers {\n\n    override fun getHeaders(): MutableMap<String, String> {\n        return headers\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/help/glide/ImageLoader.kt",
    "content": "package io.legado.app.help.glide\n\nimport android.content.Context\nimport android.graphics.Bitmap\nimport android.graphics.drawable.Drawable\nimport android.net.Uri\nimport androidx.annotation.DrawableRes\nimport androidx.fragment.app.Fragment\nimport androidx.lifecycle.Lifecycle\nimport com.bumptech.glide.Glide\nimport com.bumptech.glide.RequestBuilder\nimport io.legado.app.utils.isAbsUrl\nimport io.legado.app.utils.isContentScheme\nimport io.legado.app.utils.isDataUrl\nimport io.legado.app.utils.lifecycle\nimport java.io.File\n\n//https://bumptech.github.io/glide/doc/generatedapi.html\n//Instead of GlideApp, use com.bumptech.Glide\n@Suppress(\"unused\")\nobject ImageLoader {\n\n    /**\n     * 自动判断path类型\n     */\n    fun load(context: Context, path: String?): RequestBuilder<Drawable> {\n        return when {\n            path.isNullOrEmpty() -> Glide.with(context).load(path)\n            path.isDataUrl() -> Glide.with(context).load(path)\n            path.isAbsUrl() -> Glide.with(context).load(path)\n            path.isContentScheme() -> Glide.with(context).load(Uri.parse(path))\n            else -> kotlin.runCatching {\n                Glide.with(context).load(File(path))\n            }.getOrElse {\n                Glide.with(context).load(path)\n            }\n        }\n    }\n\n    fun load(fragment: Fragment, lifecycle: Lifecycle, path: String?): RequestBuilder<Drawable> {\n        val requestManager = Glide.with(fragment).lifecycle(lifecycle)\n        return when {\n            path.isNullOrEmpty() -> requestManager.load(path)\n            path.isDataUrl() -> requestManager.load(path)\n            path.isAbsUrl() -> requestManager.load(path)\n            path.isContentScheme() -> requestManager.load(Uri.parse(path))\n\n            else -> kotlin.runCatching {\n                requestManager.load(File(path))\n            }.getOrElse {\n                requestManager.load(path)\n            }\n        }\n    }\n\n    fun loadBitmap(context: Context, path: String?): RequestBuilder<Bitmap> {\n        val requestManager = Glide.with(context).`as`(Bitmap::class.java)\n        return when {\n            path.isNullOrEmpty() -> requestManager.load(path)\n            path.isDataUrl() -> requestManager.load(path)\n            path.isAbsUrl() -> requestManager.load(path)\n            path.isContentScheme() -> requestManager.load(Uri.parse(path))\n            else -> kotlin.runCatching {\n                requestManager.load(File(path))\n            }.getOrElse {\n                requestManager.load(path)\n            }\n        }\n    }\n\n    fun loadFile(context: Context, path: String?): RequestBuilder<File> {\n        return when {\n            path.isNullOrEmpty() -> Glide.with(context).asFile().load(path)\n            path.isAbsUrl() -> Glide.with(context).asFile().load(path)\n            path.isContentScheme() -> Glide.with(context).asFile().load(Uri.parse(path))\n            else -> kotlin.runCatching {\n                Glide.with(context).asFile().load(File(path))\n            }.getOrElse {\n                Glide.with(context).asFile().load(path)\n            }\n        }\n    }\n\n    fun load(context: Context, @DrawableRes resId: Int?): RequestBuilder<Drawable> {\n        return Glide.with(context).load(resId)\n    }\n\n    fun load(context: Context, file: File?): RequestBuilder<Drawable> {\n        return Glide.with(context).load(file)\n    }\n\n    fun load(context: Context, uri: Uri?): RequestBuilder<Drawable> {\n        return Glide.with(context).load(uri)\n    }\n\n    fun load(context: Context, drawable: Drawable?): RequestBuilder<Drawable> {\n        return Glide.with(context).load(drawable)\n    }\n\n    fun load(context: Context, bitmap: Bitmap?): RequestBuilder<Drawable> {\n        return Glide.with(context).load(bitmap)\n    }\n\n    fun load(context: Context, bytes: ByteArray?): RequestBuilder<Drawable> {\n        return Glide.with(context).load(bytes)\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/help/glide/LegadoDataUrlLoader.kt",
    "content": "package io.legado.app.help.glide\n\nimport com.bumptech.glide.Priority\nimport com.bumptech.glide.load.DataSource\nimport com.bumptech.glide.load.Options\nimport com.bumptech.glide.load.data.DataFetcher\nimport com.bumptech.glide.load.model.ModelLoader\nimport com.bumptech.glide.load.model.ModelLoaderFactory\nimport com.bumptech.glide.load.model.MultiModelLoaderFactory\nimport com.bumptech.glide.signature.ObjectKey\nimport io.legado.app.exception.NoStackTraceException\nimport io.legado.app.model.ReadManga\nimport io.legado.app.model.analyzeRule.AnalyzeUrl\nimport io.legado.app.utils.ImageUtils\nimport com.script.rhino.runScriptWithContext\nimport kotlinx.coroutines.Job\nimport java.io.InputStream\n\nclass LegadoDataUrlLoader : ModelLoader<String, InputStream> {\n\n    override fun buildLoadData(\n        model: String,\n        width: Int,\n        height: Int,\n        options: Options\n    ): ModelLoader.LoadData<InputStream>? {\n        if (options.get(OkHttpModelLoader.mangaOption) == false) {\n            return null\n        }\n        return ModelLoader.LoadData(ObjectKey(model), LegadoDataUrlFetcher(model))\n    }\n\n    override fun handles(model: String): Boolean {\n        return model.startsWith(\"data:\")\n    }\n\n    class LegadoDataUrlFetcher(private val model: String) : DataFetcher<InputStream> {\n\n        private val coroutineContext = Job()\n\n        override fun loadData(\n            priority: Priority,\n            callback: DataFetcher.DataCallback<in InputStream>\n        ) {\n            try {\n                val bytes = AnalyzeUrl(\n                    model, source = ReadManga.bookSource,\n                    coroutineContext = coroutineContext\n                ).getByteArray()\n                val decoded = runScriptWithContext(coroutineContext) {\n                    ImageUtils.decode(\n                        model, bytes, isCover = false, ReadManga.bookSource, ReadManga.book\n                    )?.inputStream()\n                }\n                if (decoded == null) {\n                    throw NoStackTraceException(\"漫画图片解密失败\")\n                }\n                callback.onDataReady(decoded)\n            } catch (e: Exception) {\n                callback.onLoadFailed(e)\n            }\n        }\n\n        override fun cleanup() {\n            // do nothing\n        }\n\n        override fun cancel() {\n            coroutineContext.cancel()\n        }\n\n        override fun getDataClass(): Class<InputStream> {\n            return InputStream::class.java\n        }\n\n        override fun getDataSource(): DataSource {\n            return DataSource.LOCAL\n        }\n\n    }\n\n    class Factory : ModelLoaderFactory<String, InputStream> {\n        override fun build(multiFactory: MultiModelLoaderFactory): ModelLoader<String, InputStream> {\n            return LegadoDataUrlLoader()\n        }\n\n        override fun teardown() {\n            // do nothing\n        }\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/help/glide/LegadoGlideModule.kt",
    "content": "package io.legado.app.help.glide\n\nimport android.content.Context\nimport android.util.Log\nimport com.bumptech.glide.Glide\nimport com.bumptech.glide.GlideBuilder\nimport com.bumptech.glide.Registry\nimport com.bumptech.glide.annotation.GlideModule\nimport com.bumptech.glide.load.engine.cache.InternalCacheDiskCacheFactory\nimport com.bumptech.glide.load.engine.cache.MemorySizeCalculator\nimport com.bumptech.glide.load.model.GlideUrl\nimport com.bumptech.glide.module.AppGlideModule\nimport io.legado.app.BuildConfig\nimport io.legado.app.help.config.AppConfig\nimport java.io.File\nimport java.io.InputStream\n\n\n@Suppress(\"unused\")\n@GlideModule\nclass LegadoGlideModule : AppGlideModule() {\n\n    override fun registerComponents(context: Context, glide: Glide, registry: Registry) {\n        registry.replace(\n            GlideUrl::class.java,\n            InputStream::class.java,\n            OkHttpModeLoaderFactory\n        )\n        registry.prepend(\n            String::class.java,\n            InputStream::class.java,\n            LegadoDataUrlLoader.Factory()\n        )\n        registry.prepend(\n            String::class.java,\n            File::class.java,\n            FilePathLoader.Factory()\n        )\n    }\n\n    override fun applyOptions(context: Context, builder: GlideBuilder) {\n        super.applyOptions(context, builder)\n        val calculator = MemorySizeCalculator.Builder(context).build()\n        val bitmapPool = AsyncRecycleBitmapPool(calculator.bitmapPoolSize)\n        builder.setMemorySizeCalculator(calculator)\n        builder.setBitmapPool(bitmapPool)\n        builder.setDiskCache(InternalCacheDiskCacheFactory(context, 1024 * 1024 * 1000))\n        if (!BuildConfig.DEBUG && !AppConfig.recordLog) {\n            builder.setLogLevel(Log.ERROR)\n        }\n    }\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/help/glide/OkHttpModeLoaderFactory.kt",
    "content": "package io.legado.app.help.glide\n\nimport com.bumptech.glide.load.model.GlideUrl\nimport com.bumptech.glide.load.model.ModelLoader\nimport com.bumptech.glide.load.model.ModelLoaderFactory\nimport com.bumptech.glide.load.model.MultiModelLoaderFactory\nimport okhttp3.Call\nimport java.io.InputStream\n\n\nobject OkHttpModeLoaderFactory: ModelLoaderFactory<GlideUrl?, InputStream?> {\n\n    override fun build(multiFactory: MultiModelLoaderFactory): ModelLoader<GlideUrl?, InputStream?> {\n        return OkHttpModelLoader\n    }\n\n    override fun teardown() {\n        // Do nothing, this instance doesn't own the client.\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/help/glide/OkHttpModelLoader.kt",
    "content": "package io.legado.app.help.glide\n\nimport com.bumptech.glide.load.Option\nimport com.bumptech.glide.load.Options\nimport com.bumptech.glide.load.model.GlideUrl\nimport com.bumptech.glide.load.model.ModelLoader\nimport java.io.InputStream\n\nobject OkHttpModelLoader : ModelLoader<GlideUrl?, InputStream?> {\n\n    val loadOnlyWifiOption = Option.memory(\"loadOnlyWifi\", false)\n    val sourceOriginOption = Option.memory<String>(\"sourceOrigin\")\n    val mangaOption = Option.memory<Boolean>(\"manga\",false)\n\n    override fun buildLoadData(\n        model: GlideUrl,\n        width: Int,\n        height: Int,\n        options: Options,\n    ): ModelLoader.LoadData<InputStream?> {\n        return ModelLoader.LoadData(model, OkHttpStreamFetcher(model, options))\n    }\n\n    override fun handles(model: GlideUrl): Boolean {\n        return true\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/help/glide/OkHttpStreamFetcher.kt",
    "content": "package io.legado.app.help.glide\n\nimport com.bumptech.glide.Priority\nimport com.bumptech.glide.load.DataSource\nimport com.bumptech.glide.load.HttpException\nimport com.bumptech.glide.load.Options\nimport com.bumptech.glide.load.data.DataFetcher\nimport com.bumptech.glide.load.model.GlideUrl\nimport com.bumptech.glide.util.ContentLengthInputStream\nimport com.script.rhino.runScriptWithContext\nimport io.legado.app.data.entities.BaseSource\nimport io.legado.app.exception.NoStackTraceException\nimport io.legado.app.help.coroutine.Coroutine\nimport io.legado.app.help.http.addHeaders\nimport io.legado.app.help.http.okHttpClient\nimport io.legado.app.help.http.okHttpClientManga\nimport io.legado.app.help.source.SourceHelp\nimport io.legado.app.model.ReadManga\nimport io.legado.app.model.analyzeRule.AnalyzeUrl\nimport io.legado.app.utils.ImageUtils\nimport io.legado.app.utils.isWifiConnect\nimport kotlinx.coroutines.CoroutineScope\nimport kotlinx.coroutines.Dispatchers.IO\nimport kotlinx.coroutines.SupervisorJob\nimport okhttp3.Call\nimport okhttp3.Request\nimport okhttp3.Response\nimport okhttp3.ResponseBody\nimport splitties.init.appCtx\nimport java.io.ByteArrayInputStream\nimport java.io.IOException\nimport java.io.InputStream\n\n\nclass OkHttpStreamFetcher(\n    private val url: GlideUrl,\n    private val options: Options,\n) :\n    DataFetcher<InputStream>, okhttp3.Callback {\n    private var stream: InputStream? = null\n    private var responseBody: ResponseBody? = null\n    private var callback: DataFetcher.DataCallback<in InputStream>? = null\n    private var source: BaseSource? = null\n    private val manga = options.get(OkHttpModelLoader.mangaOption) == true\n    private val coroutineContext = SupervisorJob()\n    private val coroutineScope = CoroutineScope(coroutineContext)\n    private lateinit var analyzedUrl: GlideUrl\n\n    @Volatile\n    private var call: Call? = null\n\n    companion object {\n        private val failUrl = hashSetOf<String>()\n    }\n\n    override fun loadData(priority: Priority, callback: DataFetcher.DataCallback<in InputStream>) {\n        if (failUrl.contains(url.toStringUrl())) {\n            callback.onLoadFailed(NoStackTraceException(\"跳过加载失败的图片\"))\n            return\n        }\n        val loadOnlyWifi = options.get(OkHttpModelLoader.loadOnlyWifiOption) ?: false\n        if (loadOnlyWifi && !appCtx.isWifiConnect) {\n            callback.onLoadFailed(NoStackTraceException(\"只在wifi加载图片\"))\n            return\n        }\n\n        options.get(OkHttpModelLoader.sourceOriginOption)?.let { sourceUrl ->\n            source = SourceHelp.getSource(sourceUrl)\n        }\n\n        analyzedUrl = AnalyzeUrl(\n            url.toString(),\n            source = source,\n            coroutineContext = coroutineContext\n        ).getGlideUrl()\n\n        val requestBuilder = Request.Builder().url(analyzedUrl.toStringUrl())\n        requestBuilder.addHeaders(analyzedUrl.headers)\n        val request: Request = requestBuilder.build()\n        this.callback = callback\n        call = if (manga) {\n            okHttpClientManga.newCall(request)\n        } else {\n            okHttpClient.newCall(request)\n        }\n        call?.enqueue(this)\n    }\n\n    override fun cleanup() {\n        kotlin.runCatching {\n            stream?.close()\n        }\n        responseBody?.close()\n        coroutineContext.cancel()\n        callback = null\n    }\n\n    override fun cancel() {\n        call?.cancel()\n        coroutineContext.cancel()\n    }\n\n    override fun getDataClass(): Class<InputStream> {\n        return InputStream::class.java\n    }\n\n    override fun getDataSource(): DataSource {\n        return DataSource.REMOTE\n    }\n\n    override fun onFailure(call: Call, e: IOException) {\n        callback?.onLoadFailed(e)\n    }\n\n    override fun onResponse(call: Call, response: Response) {\n        responseBody = response.body\n        if (!response.isSuccessful) {\n            if (!manga) {\n                failUrl.add(url.toStringUrl())\n            }\n            callback?.onLoadFailed(HttpException(response.message, response.code))\n            return\n        }\n        if (ImageUtils.skipDecode(source, !manga)) {\n            onStreamReady(responseBody!!.byteStream())\n            return\n        }\n        Coroutine.async(coroutineScope, executeContext = IO) {\n            val decodeResult = runScriptWithContext(coroutineContext) {\n                if (manga) {\n                    ImageUtils.decode(\n                        url.toString(),\n                        responseBody!!.bytes(),\n                        isCover = false,\n                        source,\n                        ReadManga.book\n                    )?.inputStream()\n                } else {\n                    ImageUtils.decode(\n                        analyzedUrl.toStringUrl(), responseBody!!.byteStream(),\n                        isCover = true, source\n                    )\n                }\n            }\n            onStreamReady(decodeResult)\n        }\n    }\n\n    private fun onStreamReady(inputStream: InputStream?) {\n        if (inputStream == null) {\n            if (!manga) {\n                failUrl.add(url.toStringUrl())\n            }\n            callback?.onLoadFailed(NoStackTraceException(\"封面二次解密失败\"))\n        } else {\n            val contentLength: Long =\n                if (inputStream is ByteArrayInputStream) inputStream.available().toLong()\n                else responseBody!!.contentLength()\n            stream = ContentLengthInputStream.obtain(inputStream, contentLength)\n            callback?.onDataReady(stream)\n        }\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/help/glide/progress/OnProgressListener.kt",
    "content": "package io.legado.app.help.glide.progress\n\ntypealias OnProgressListener = (isComplete: Boolean, percentage: Int, bytesRead: Long, totalBytes: Long) -> Unit\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/help/glide/progress/ProgressManager.kt",
    "content": "package io.legado.app.help.glide.progress\n\nimport io.legado.app.model.analyzeRule.AnalyzeUrl\nimport java.util.concurrent.ConcurrentHashMap\n\n/**\n * 进度监听器管理类\n * 加入图片加载进度监听，加入Https支持\n */\nobject ProgressManager {\n    private val listenersMap = ConcurrentHashMap<String, OnProgressListener>()\n\n    val LISTENER = object : ProgressResponseBody.InternalProgressListener {\n        override fun onProgress(url: String, bytesRead: Long, totalBytes: Long) {\n            getProgressListener(url)?.let {\n                var percentage = (bytesRead * 1f / totalBytes * 100f).toInt()\n                var isComplete = percentage >= 100\n                if (percentage <= -100) {\n                    percentage = 0\n                    isComplete = true\n                }\n                it.invoke(isComplete, percentage, bytesRead, totalBytes)\n                if (isComplete) {\n                    removeListener(url)\n                }\n            }\n        }\n    }\n\n    fun addListener(url: String, listener: OnProgressListener) {\n        if (url.isNotEmpty()) {\n            val url = getUrlNoOption(url)\n            listenersMap[url] = listener\n            listener.invoke(false, 1, 0, 0)\n        }\n    }\n\n    fun removeListener(url: String) {\n        if (url.isNotEmpty()) {\n            val url = getUrlNoOption(url)\n            listenersMap.remove(url)\n        }\n    }\n\n    fun getProgressListener(url: String): OnProgressListener? {\n        return if (url.isEmpty() || listenersMap.isEmpty()) {\n            null\n        } else {\n            listenersMap[url]\n        }\n    }\n\n    private fun getUrlNoOption(url: String): String {\n        val urlMatcher = AnalyzeUrl.paramPattern.matcher(url)\n        return if (urlMatcher.find()) {\n            url.take(urlMatcher.start())\n        } else {\n            url\n        }\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/help/glide/progress/ProgressResponseBody.kt",
    "content": "package io.legado.app.help.glide.progress\n\nimport android.os.Handler\nimport android.os.Looper\nimport okhttp3.MediaType\nimport okhttp3.ResponseBody\nimport okio.*\nimport java.io.IOException\nimport kotlin.jvm.Throws\n\nclass ProgressResponseBody internal constructor(private val url: String, private val internalProgressListener: InternalProgressListener?, private val responseBody: ResponseBody) : ResponseBody() {\n    private var bufferedSource: BufferedSource? = null\n    override fun contentType(): MediaType? {\n        return responseBody.contentType()\n    }\n\n    override fun contentLength(): Long {\n        return responseBody.contentLength()\n    }\n\n    override fun source(): BufferedSource {\n        if (bufferedSource == null) {\n            bufferedSource = source(responseBody.source()).buffer()\n        }\n        return bufferedSource!!\n    }\n\n    private fun source(source: Source): Source {\n        return object : ForwardingSource(source) {\n            var totalBytesRead: Long = 0\n            var lastTotalBytesRead: Long = 0\n\n            @Throws(IOException::class)\n            override fun read(sink: Buffer, byteCount: Long): Long {\n                val bytesRead = super.read(sink, byteCount)\n                totalBytesRead += if (bytesRead == -1L) 0 else bytesRead\n                if (internalProgressListener != null && lastTotalBytesRead != totalBytesRead) {\n                    lastTotalBytesRead = totalBytesRead\n                    mainThreadHandler.post { internalProgressListener.onProgress(url, totalBytesRead, contentLength()) }\n                }\n                return bytesRead\n            }\n        }\n    }\n\n    interface InternalProgressListener {\n        fun onProgress(url: String, bytesRead: Long, totalBytes: Long)\n    }\n\n    companion object {\n        private val mainThreadHandler = Handler(Looper.getMainLooper())\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/help/http/BackstageWebView.kt",
    "content": "package io.legado.app.help.http\n\nimport android.annotation.SuppressLint\nimport android.net.http.SslError\nimport android.os.Build\nimport android.os.Handler\nimport android.os.Looper\nimport android.util.AndroidRuntimeException\nimport android.webkit.CookieManager\nimport android.webkit.SslErrorHandler\nimport android.webkit.WebResourceRequest\nimport android.webkit.WebSettings\nimport android.webkit.WebView\nimport android.webkit.WebViewClient\nimport io.legado.app.constant.AppConst\nimport io.legado.app.exception.NoStackTraceException\nimport io.legado.app.help.config.AppConfig\nimport io.legado.app.help.coroutine.Coroutine\nimport io.legado.app.utils.runOnUI\nimport kotlinx.coroutines.Dispatchers.IO\nimport kotlinx.coroutines.Runnable\nimport kotlinx.coroutines.suspendCancellableCoroutine\nimport kotlinx.coroutines.withTimeout\nimport okhttp3.Protocol\nimport okhttp3.Request\nimport okhttp3.Response\nimport org.apache.commons.text.StringEscapeUtils\nimport splitties.init.appCtx\nimport java.lang.ref.WeakReference\nimport kotlin.coroutines.resume\nimport kotlin.coroutines.resumeWithException\n\n/**\n * 后台webView\n */\nclass BackstageWebView(\n    private val url: String? = null,\n    private val html: String? = null,\n    private val encode: String? = null,\n    private val tag: String? = null,\n    private val headerMap: Map<String, String>? = null,\n    private val sourceRegex: String? = null,\n    private val overrideUrlRegex: String? = null,\n    private val javaScript: String? = null,\n    private val delayTime: Long = 0,\n) {\n\n    private val mHandler = Handler(Looper.getMainLooper())\n    private var callback: Callback? = null\n    private var mWebView: WebView? = null\n\n    suspend fun getStrResponse(): StrResponse = withTimeout(60000L) {\n        suspendCancellableCoroutine { block ->\n            block.invokeOnCancellation {\n                runOnUI {\n                    destroy()\n                }\n            }\n            callback = object : Callback() {\n                override fun onResult(response: StrResponse) {\n                    if (!block.isCompleted) {\n                        block.resume(response)\n                    }\n                }\n\n                override fun onError(error: Throwable) {\n                    if (!block.isCompleted)\n                        block.resumeWithException(error)\n                }\n            }\n            runOnUI {\n                try {\n                    load()\n                } catch (error: Throwable) {\n                    block.resumeWithException(error)\n                }\n            }\n        }\n    }\n\n    private fun getEncoding(): String {\n        return encode ?: \"utf-8\"\n    }\n\n    @Throws(AndroidRuntimeException::class)\n    private fun load() {\n        val webView = createWebView()\n        mWebView = webView\n        try {\n            when {\n                !html.isNullOrEmpty() -> if (url.isNullOrEmpty()) {\n                    webView.loadData(html, \"text/html\", getEncoding())\n                } else {\n                    webView.loadDataWithBaseURL(url, html, \"text/html\", getEncoding(), url)\n                }\n\n                else -> if (headerMap == null) {\n                    webView.loadUrl(url!!)\n                } else {\n                    webView.loadUrl(url!!, headerMap)\n                }\n            }\n        } catch (e: Exception) {\n            callback?.onError(e)\n        }\n    }\n\n    @SuppressLint(\"SetJavaScriptEnabled\", \"JavascriptInterface\")\n    private fun createWebView(): WebView {\n        val webView = WebView(appCtx)\n        val settings = webView.settings\n        settings.javaScriptEnabled = true\n        settings.domStorageEnabled = true\n        settings.blockNetworkImage = true\n        settings.userAgentString = headerMap?.get(AppConst.UA_NAME) ?: AppConfig.userAgent\n        settings.mixedContentMode = WebSettings.MIXED_CONTENT_ALWAYS_ALLOW\n        if (sourceRegex.isNullOrBlank() && overrideUrlRegex.isNullOrBlank()) {\n            webView.webViewClient = HtmlWebViewClient()\n        } else {\n            webView.webViewClient = SnifferWebClient()\n        }\n        return webView\n    }\n\n    private fun destroy() {\n        mWebView?.destroy()\n        mWebView = null\n    }\n\n    private fun getJs(): String {\n        javaScript?.let {\n            if (it.isNotEmpty()) {\n                return it\n            }\n        }\n        return JS\n    }\n\n    private fun setCookie(url: String) {\n        tag?.let {\n            Coroutine.async(executeContext = IO) {\n                val cookie = CookieManager.getInstance().getCookie(url)\n                CookieStore.setCookie(it, cookie)\n            }\n        }\n    }\n\n    private inner class HtmlWebViewClient : WebViewClient() {\n\n        private var runnable: EvalJsRunnable? = null\n        private var isRedirect = false\n\n        override fun shouldOverrideUrlLoading(\n            view: WebView,\n            request: WebResourceRequest\n        ): Boolean {\n            isRedirect = isRedirect || if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {\n                request.isRedirect\n            } else {\n                request.url.toString() != view.url\n            }\n            return super.shouldOverrideUrlLoading(view, request)\n        }\n\n        override fun onPageFinished(view: WebView, url: String) {\n            setCookie(url)\n            if (runnable == null) {\n                runnable = EvalJsRunnable(view, url, getJs())\n            }\n            mHandler.removeCallbacks(runnable!!)\n            mHandler.postDelayed(runnable!!, 1000 + delayTime)\n        }\n\n        @SuppressLint(\"WebViewClientOnReceivedSslError\")\n        override fun onReceivedSslError(\n            view: WebView?,\n            handler: SslErrorHandler?,\n            error: SslError?\n        ) {\n            handler?.proceed()\n        }\n\n        private inner class EvalJsRunnable(\n            webView: WebView,\n            private val url: String,\n            private val mJavaScript: String\n        ) : Runnable {\n            var retry = 0\n            private val mWebView: WeakReference<WebView> = WeakReference(webView)\n            override fun run() {\n                mWebView.get()?.evaluateJavascript(mJavaScript) {\n                    handleResult(it)\n                }\n            }\n\n            private fun handleResult(result: String) = Coroutine.async {\n                if (result.isNotEmpty() && result != \"null\") {\n                    val content = StringEscapeUtils.unescapeJson(result)\n                        .replace(quoteRegex, \"\")\n                    try {\n                        val response = buildStrResponse(content)\n                        callback?.onResult(response)\n                    } catch (e: Exception) {\n                        callback?.onError(e)\n                    }\n                    mHandler.post {\n                        destroy()\n                    }\n                    return@async\n                }\n                if (retry > 30) {\n                    callback?.onError(NoStackTraceException(\"js执行超时\"))\n                    mHandler.post {\n                        destroy()\n                    }\n                    return@async\n                }\n                retry++\n                mHandler.postDelayed(this@EvalJsRunnable, 1000)\n            }\n\n            private fun buildStrResponse(content: String): StrResponse {\n                if (!isRedirect) {\n                    return StrResponse(url, content)\n                }\n                val originUrl = this@BackstageWebView.url ?: url\n                val originResponse = Response.Builder()\n                    .code(302)\n                    .request(Request.Builder().url(originUrl).build())\n                    .protocol(Protocol.HTTP_1_1)\n                    .message(\"Found\")\n                    .build()\n                val response = Response.Builder()\n                    .code(200)\n                    .request(Request.Builder().url(url).build())\n                    .protocol(Protocol.HTTP_1_1)\n                    .message(\"OK\")\n                    .priorResponse(originResponse)\n                    .build()\n                return StrResponse(response, content)\n            }\n        }\n\n    }\n\n    private inner class SnifferWebClient : WebViewClient() {\n\n        override fun shouldOverrideUrlLoading(\n            view: WebView,\n            request: WebResourceRequest\n        ): Boolean {\n            if (shouldOverrideUrlLoading(request.url.toString())) {\n                return true\n            }\n            return super.shouldOverrideUrlLoading(view, request)\n        }\n\n        @Suppress(\"DEPRECATION\", \"OVERRIDE_DEPRECATION\", \"KotlinRedundantDiagnosticSuppress\")\n        override fun shouldOverrideUrlLoading(view: WebView, url: String): Boolean {\n            if (shouldOverrideUrlLoading(url)) {\n                return true\n            }\n            return super.shouldOverrideUrlLoading(view, url)\n        }\n\n        private fun shouldOverrideUrlLoading(requestUrl: String): Boolean {\n            overrideUrlRegex?.let {\n                if (requestUrl.matches(it.toRegex())) {\n                    try {\n                        val response = StrResponse(url!!, requestUrl)\n                        callback?.onResult(response)\n                    } catch (e: Exception) {\n                        callback?.onError(e)\n                    }\n                    destroy()\n                    return true\n                }\n            }\n            return false\n        }\n\n        override fun onLoadResource(view: WebView, resUrl: String) {\n            sourceRegex?.let {\n                if (resUrl.matches(it.toRegex())) {\n                    try {\n                        val response = StrResponse(url!!, resUrl)\n                        callback?.onResult(response)\n                    } catch (e: Exception) {\n                        callback?.onError(e)\n                    }\n                    destroy()\n                }\n            }\n        }\n\n        override fun onPageFinished(webView: WebView, url: String) {\n            setCookie(url)\n            if (!javaScript.isNullOrEmpty()) {\n                val runnable = LoadJsRunnable(webView, javaScript)\n                mHandler.postDelayed(runnable, 1000L + delayTime)\n            }\n        }\n\n        @SuppressLint(\"WebViewClientOnReceivedSslError\")\n        override fun onReceivedSslError(\n            view: WebView?,\n            handler: SslErrorHandler?,\n            error: SslError?\n        ) {\n            handler?.proceed()\n        }\n\n        private inner class LoadJsRunnable(\n            webView: WebView,\n            private val mJavaScript: String?\n        ) : Runnable {\n            private val mWebView: WeakReference<WebView> = WeakReference(webView)\n            override fun run() {\n                mWebView.get()?.loadUrl(\"javascript:${mJavaScript}\")\n            }\n        }\n\n    }\n\n    companion object {\n        const val JS = \"document.documentElement.outerHTML\"\n        private val quoteRegex = \"^\\\"|\\\"$\".toRegex()\n    }\n\n    abstract class Callback {\n        abstract fun onResult(response: StrResponse)\n        abstract fun onError(error: Throwable)\n    }\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/help/http/CookieManager.kt",
    "content": "package io.legado.app.help.http\n\nimport android.webkit.CookieManager\nimport io.legado.app.constant.AppLog\nimport io.legado.app.data.appDb\nimport io.legado.app.help.CacheManager\nimport io.legado.app.utils.NetworkUtils\nimport io.legado.app.utils.splitNotBlank\nimport okhttp3.Cookie\nimport okhttp3.Headers\nimport okhttp3.HttpUrl\nimport okhttp3.HttpUrl.Companion.toHttpUrlOrNull\nimport okhttp3.Request\nimport okhttp3.Response\nimport org.jsoup.Connection\n\n@Suppress(\"ConstPropertyName\")\nobject CookieManager {\n    /**\n     * <domain>_session_cookie 会话期 cookie，应用重启后失效\n     * <domain>_cookie cookies 缓存\n     */\n\n    const val cookieJarHeader = \"CookieJar\"\n\n    /**\n     * 从响应中保存Cookies\n     */\n    fun saveResponse(response: Response) {\n        val url = response.request.url\n        val headers = response.headers\n        saveCookiesFromHeaders(url, headers)\n    }\n\n    fun saveResponse(response: Connection.Response) {\n        val url = response.url().toHttpUrlOrNull() ?: return\n        val headers = response.multiHeaders().toHeaders()\n        saveCookiesFromHeaders(url, headers)\n    }\n\n    private fun saveCookiesFromHeaders(url: HttpUrl, headers: Headers) {\n        val domain = NetworkUtils.getSubDomain(url.toString())\n        val cookies = Cookie.parseAll(url, headers)\n\n        val sessionCookie = cookies.filter { !it.persistent }.getString()\n        updateSessionCookie(domain, sessionCookie)\n\n        val cookieString = cookies.filter { it.persistent }.getString()\n        CookieStore.replaceCookie(domain, cookieString)\n    }\n\n    /**\n     * 加载Cookies到请求中\n     */\n    fun loadRequest(request: Request): Request {\n        val url = request.url.toString()\n        val domain = NetworkUtils.getSubDomain(url)\n\n        val cookie = CookieStore.getCookie(domain)\n        val requestCookie = request.header(\"Cookie\")\n\n        val newCookie = mergeCookies(requestCookie, cookie) ?: return request\n\n        kotlin.runCatching {\n            return request.newBuilder()\n                .header(\"Cookie\", newCookie)\n                .build()\n        }.onFailure {\n            CookieStore.removeCookie(url)\n            val msg = \"设置cookie出错，已清除cookie $domain cookie:$newCookie\\n$it\"\n            AppLog.put(msg, it)\n        }\n\n        return request\n    }\n\n    private fun getSessionCookieMap(domain: String): MutableMap<String, String>? {\n        return getSessionCookie(domain)?.let { CookieStore.cookieToMap(it) }\n    }\n\n    fun getSessionCookie(domain: String): String? {\n        return CacheManager.getFromMemory(\"${domain}_session_cookie\") as? String\n    }\n\n    private fun updateSessionCookie(domain: String, cookies: String) {\n        val sessionCookie = getSessionCookie(domain)\n        if (sessionCookie.isNullOrEmpty()) {\n            CacheManager.putMemory(\"${domain}_session_cookie\", cookies)\n            return\n        }\n\n        val ck = mergeCookies(sessionCookie, cookies) ?: return\n        CacheManager.putMemory(\"${domain}_session_cookie\", ck)\n    }\n\n    fun mergeCookies(vararg cookies: String?): String? {\n        val cookieMap = mergeCookiesToMap(*cookies)\n        return CookieStore.mapToCookie(cookieMap)\n    }\n\n    fun mergeCookiesToMap(vararg cookies: String?): MutableMap<String, String> {\n        return cookies.filterNotNull().map {\n            CookieStore.cookieToMap(it)\n        }.reduce { acc, cookieMap ->\n            acc.apply { putAll(cookieMap) }\n        }\n    }\n\n    /**\n     * 删除单个Cookie\n     */\n    fun removeCookie(url: String, key: String) {\n        val domain = NetworkUtils.getSubDomain(url)\n\n        getSessionCookieMap(domain)?.let {\n            it.remove(key)\n            CookieStore.mapToCookie(it)?.let { cookie ->\n                CacheManager.putMemory(\"${domain}_session_cookie\", cookie)\n            }\n        }\n\n        val cookie = getCookieNoSession(url)\n        if (cookie.isNotEmpty()) {\n            val cookieMap = CookieStore.cookieToMap(cookie).apply { remove(key) }\n            CookieStore.mapToCookie(cookieMap)?.let {\n                CookieStore.setCookie(url, it)\n            }\n        }\n    }\n\n    fun getCookieNoSession(url: String): String {\n        val domain = NetworkUtils.getSubDomain(url)\n        val cacheCookie = CacheManager.getFromMemory(\"${domain}_cookie\") as? String\n\n        return if (cacheCookie != null) {\n            cacheCookie\n        } else {\n            val cookieBean = appDb.cookieDao.get(domain)\n            cookieBean?.cookie ?: \"\"\n        }\n    }\n\n    fun applyToWebView(url: String) {\n        val baseUrl = NetworkUtils.getBaseUrl(url) ?: return\n        val cookies = CookieStore.getCookie(url).splitNotBlank(\";\")\n        val cookieManager = CookieManager.getInstance()\n        cookieManager.removeSessionCookies(null)\n        cookies.forEach {\n            cookieManager.setCookie(baseUrl, it)\n        }\n    }\n\n    fun List<Cookie>.getString() = buildString {\n        this@getString.forEachIndexed { index, cookie ->\n            if (index > 0) append(\"; \")\n            append(cookie.name).append('=').append(cookie.value)\n        }\n    }\n\n    private fun Map<String, List<String>>.toHeaders(): Headers {\n        return Headers.Builder().apply {\n            this@toHeaders.forEach { (k, v) ->\n                v.forEach {\n                    add(k, it)\n                }\n            }\n        }.build()\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/help/http/CookieStore.kt",
    "content": "@file:Suppress(\"unused\")\n\npackage io.legado.app.help.http\n\nimport android.text.TextUtils\nimport androidx.annotation.Keep\nimport io.legado.app.constant.AppLog\nimport io.legado.app.constant.AppPattern.equalsRegex\nimport io.legado.app.constant.AppPattern.semicolonRegex\nimport io.legado.app.data.appDb\nimport io.legado.app.data.entities.Cookie\nimport io.legado.app.help.CacheManager\nimport io.legado.app.help.http.CookieManager.getCookieNoSession\nimport io.legado.app.help.http.CookieManager.mergeCookiesToMap\nimport io.legado.app.help.http.api.CookieManagerInterface\nimport io.legado.app.utils.NetworkUtils\nimport io.legado.app.utils.removeCookie\n\n@Keep\nobject CookieStore : CookieManagerInterface {\n\n    /**\n     *保存cookie到数据库，会自动识别url的二级域名\n     */\n    override fun setCookie(url: String, cookie: String?) {\n        try {\n            val domain = NetworkUtils.getSubDomain(url)\n            CacheManager.putMemory(\"${domain}_cookie\", cookie ?: \"\")\n            val cookieBean = Cookie(domain, cookie ?: \"\")\n            appDb.cookieDao.insert(cookieBean)\n        } catch (e: Exception) {\n            AppLog.put(\"保存Cookie失败\\n$e\", e)\n        }\n    }\n\n    override fun replaceCookie(url: String, cookie: String) {\n        if (TextUtils.isEmpty(url) || TextUtils.isEmpty(cookie)) {\n            return\n        }\n        val oldCookie = getCookieNoSession(url)\n        if (TextUtils.isEmpty(oldCookie)) {\n            setCookie(url, cookie)\n        } else {\n            val cookieMap = cookieToMap(oldCookie)\n            cookieMap.putAll(cookieToMap(cookie))\n            val newCookie = mapToCookie(cookieMap)\n            setCookie(url, newCookie)\n        }\n    }\n\n    /**\n     *获取url所属的二级域名的cookie\n     */\n    override fun getCookie(url: String): String {\n        val domain = NetworkUtils.getSubDomain(url)\n\n        val cookie = getCookieNoSession(url)\n        val sessionCookie = CookieManager.getSessionCookie(domain)\n\n        val cookieMap = mergeCookiesToMap(cookie, sessionCookie)\n\n        var ck = mapToCookie(cookieMap) ?: \"\"\n        while (ck.length > 4096) {\n            val removeKey = cookieMap.keys.random()\n            CookieManager.removeCookie(url, removeKey)\n            cookieMap.remove(removeKey)\n            ck = mapToCookie(cookieMap) ?: \"\"\n        }\n        return ck\n    }\n\n    fun getKey(url: String, key: String): String {\n        val cookie = getCookie(url)\n        val sessionCookie = CookieManager.getSessionCookie(url)\n        val cookieMap = mergeCookiesToMap(cookie, sessionCookie)\n        return cookieMap[key] ?: \"\"\n    }\n\n    override fun removeCookie(url: String) {\n        val domain = NetworkUtils.getSubDomain(url)\n        appDb.cookieDao.delete(domain)\n        CacheManager.deleteMemory(\"${domain}_cookie\")\n        CacheManager.deleteMemory(\"${domain}_session_cookie\")\n        android.webkit.CookieManager.getInstance().removeCookie(url)\n    }\n\n    override fun cookieToMap(cookie: String): MutableMap<String, String> {\n        val cookieMap = mutableMapOf<String, String>()\n        if (cookie.isBlank()) {\n            return cookieMap\n        }\n        val pairArray = cookie.split(semicolonRegex).dropLastWhile { it.isEmpty() }.toTypedArray()\n        for (pair in pairArray) {\n            val pairs = pair.split(equalsRegex, 2).dropLastWhile { it.isEmpty() }.toTypedArray()\n            if (pairs.size <= 1) {\n                continue\n            }\n            val key = pairs[0].trim { it <= ' ' }\n            val value = pairs[1]\n            if (value.isNotBlank() || value.trim { it <= ' ' } == \"null\") {\n                cookieMap[key] = value.trim { it <= ' ' }\n            }\n        }\n        return cookieMap\n    }\n\n    override fun mapToCookie(cookieMap: Map<String, String>?): String? {\n        if (cookieMap.isNullOrEmpty()) {\n            return null\n        }\n        val builder = StringBuilder()\n        cookieMap.keys.forEachIndexed { index, key ->\n            if (index > 0) builder.append(\"; \")\n            builder.append(key).append(\"=\").append(cookieMap[key])\n        }\n        return builder.toString()\n    }\n\n    fun clear() {\n        appDb.cookieDao.deleteOkHttp()\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/help/http/Cronet.kt",
    "content": "package io.legado.app.help.http\n\nimport io.legado.app.lib.cronet.CronetInterceptor\nimport io.legado.app.lib.cronet.CronetLoader\nimport okhttp3.Interceptor\n\nobject Cronet {\n\n    val loader: LoaderInterface? by lazy {\n        CronetLoader\n    }\n\n    fun preDownload() {\n        loader?.preDownload()\n    }\n\n    val interceptor: Interceptor? by lazy {\n        CronetInterceptor(cookieJar)\n    }\n\n    interface LoaderInterface {\n\n        fun install(): Boolean\n\n        fun preDownload()\n\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/help/http/DecompressInterceptor.kt",
    "content": "package io.legado.app.help.http\n\nimport okhttp3.Interceptor\nimport okhttp3.Response\nimport okhttp3.ResponseBody\nimport okhttp3.ResponseBody.Companion.asResponseBody\nimport okhttp3.internal.http.promisesBody\nimport okio.buffer\nimport okio.source\nimport java.util.zip.GZIPInputStream\nimport java.util.zip.Inflater\nimport java.util.zip.InflaterInputStream\n\nobject DecompressInterceptor : Interceptor {\n    override fun intercept(chain: Interceptor.Chain): Response {\n        val request = chain.request()\n        val requestBuilder = request.newBuilder()\n\n        var transparentDecompress = false\n        if (request.header(\"Accept-Encoding\") == null && request.header(\"Range\") == null) {\n            transparentDecompress = true\n            requestBuilder.header(\"Accept-Encoding\", \"gzip, deflate\")\n        }\n\n        val response = chain.proceed(requestBuilder.build())\n        val body = response.body\n\n        if (!transparentDecompress || !response.promisesBody() || body == ResponseBody.EMPTY) {\n            return response\n        }\n\n        val encoding = response.header(\"Content-Encoding\")?.lowercase()\n        val source = when (encoding) {\n            \"gzip\" -> GZIPInputStream(body.byteStream()).source().buffer()\n            \"deflate\" -> InflaterInputStream(body.byteStream(), Inflater(true)).source().buffer()\n            else -> return response\n        }\n\n        return response.newBuilder()\n            .removeHeader(\"Content-Encoding\")\n            .removeHeader(\"Content-Length\")\n            .body(source.asResponseBody(body.contentType(), -1))\n            .build()\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/help/http/HttpHelper.kt",
    "content": "package io.legado.app.help.http\n\nimport io.legado.app.constant.AppConst\nimport io.legado.app.help.CacheManager\nimport io.legado.app.help.config.AppConfig\nimport io.legado.app.help.glide.progress.ProgressManager.LISTENER\nimport io.legado.app.help.glide.progress.ProgressResponseBody\nimport io.legado.app.help.http.CookieManager.cookieJarHeader\nimport io.legado.app.model.ReadManga\nimport io.legado.app.utils.NetworkUtils\nimport okhttp3.ConnectionSpec\nimport okhttp3.Cookie\nimport okhttp3.CookieJar\nimport okhttp3.Credentials\nimport okhttp3.HttpUrl\nimport okhttp3.OkHttpClient\nimport java.net.InetSocketAddress\nimport java.net.Proxy\nimport java.util.concurrent.ConcurrentHashMap\nimport java.util.concurrent.ThreadFactory\nimport java.util.concurrent.ThreadPoolExecutor\nimport java.util.concurrent.TimeUnit\n\nprivate val proxyClientCache: ConcurrentHashMap<String, OkHttpClient> by lazy {\n    ConcurrentHashMap()\n}\n\nval cookieJar by lazy {\n    object : CookieJar {\n\n        override fun loadForRequest(url: HttpUrl): List<Cookie> {\n            return emptyList()\n        }\n\n        override fun saveFromResponse(url: HttpUrl, cookies: List<Cookie>) {\n            if (cookies.isEmpty()) return\n            //临时保存 书源启用cookie选项再添加到数据库\n            val cookieBuilder = StringBuilder()\n            cookies.forEachIndexed { index, cookie ->\n                if (index > 0) cookieBuilder.append(\";\")\n                cookieBuilder.append(cookie.name).append('=').append(cookie.value)\n            }\n            val domain = NetworkUtils.getSubDomain(url.toString())\n            CacheManager.putMemory(\"${domain}_cookieJar\", cookieBuilder.toString())\n        }\n\n    }\n}\n\nval okHttpClient: OkHttpClient by lazy {\n    val specs = arrayListOf(\n        ConnectionSpec.MODERN_TLS,\n        ConnectionSpec.COMPATIBLE_TLS,\n        ConnectionSpec.CLEARTEXT\n    )\n\n    val builder = OkHttpClient.Builder()\n        .connectTimeout(15, TimeUnit.SECONDS)\n        .writeTimeout(15, TimeUnit.SECONDS)\n        .readTimeout(60, TimeUnit.SECONDS)\n        .callTimeout(60, TimeUnit.SECONDS)\n        //.cookieJar(cookieJar = cookieJar)\n        .sslSocketFactory(SSLHelper.unsafeSSLSocketFactory, SSLHelper.unsafeTrustManager)\n        .retryOnConnectionFailure(true)\n        .hostnameVerifier(SSLHelper.unsafeHostnameVerifier)\n        .connectionSpecs(specs)\n        .followRedirects(true)\n        .followSslRedirects(true)\n        .addInterceptor(OkHttpExceptionInterceptor)\n        .addInterceptor { chain ->\n            val request = chain.request()\n            val builder = request.newBuilder()\n            if (request.header(AppConst.UA_NAME) == null) {\n                builder.addHeader(AppConst.UA_NAME, AppConfig.userAgent)\n            } else if (request.header(AppConst.UA_NAME) == \"null\") {\n                builder.removeHeader(AppConst.UA_NAME)\n            }\n            builder.addHeader(\"Keep-Alive\", \"300\")\n            builder.addHeader(\"Connection\", \"Keep-Alive\")\n            builder.addHeader(\"Cache-Control\", \"no-cache\")\n            chain.proceed(builder.build())\n        }\n        .addNetworkInterceptor { chain ->\n            var request = chain.request()\n            val enableCookieJar = request.header(cookieJarHeader) != null\n\n            if (enableCookieJar) {\n                val requestBuilder = request.newBuilder()\n                requestBuilder.removeHeader(cookieJarHeader)\n                request = CookieManager.loadRequest(requestBuilder.build())\n            }\n\n            val networkResponse = chain.proceed(request)\n\n            if (enableCookieJar) {\n                CookieManager.saveResponse(networkResponse)\n            }\n            networkResponse\n        }\n    if (AppConfig.isCronet) {\n        if (Cronet.loader?.install() == true) {\n            Cronet.interceptor?.let {\n                builder.addInterceptor(it)\n            }\n        }\n    }\n    builder.addInterceptor(DecompressInterceptor)\n    builder.build().apply {\n        val okHttpName =\n            OkHttpClient::class.java.name.removePrefix(\"okhttp3.\").removeSuffix(\"Client\")\n        val executor = dispatcher.executorService as ThreadPoolExecutor\n        val threadName = \"$okHttpName Dispatcher\"\n        executor.threadFactory = ThreadFactory { runnable ->\n            Thread(runnable, threadName).apply {\n                isDaemon = false\n                uncaughtExceptionHandler = OkhttpUncaughtExceptionHandler\n            }\n        }\n    }\n}\n\nval okHttpClientManga by lazy {\n    okHttpClient.newBuilder().run {\n        val interceptors = interceptors()\n        interceptors.add(1) { chain ->\n            val request = chain.request()\n            val response = chain.proceed(request)\n            val url = request.url.toString()\n            response.newBuilder()\n                .body(ProgressResponseBody(url, LISTENER, response.body))\n                .build()\n        }\n        interceptors.add(1) { chain ->\n            ReadManga.rateLimiter.withLimitBlocking {\n                chain.proceed(chain.request())\n            }\n        }\n        build()\n    }\n}\n\n/**\n * 缓存代理okHttp\n */\nfun getProxyClient(proxy: String? = null): OkHttpClient {\n    if (proxy.isNullOrBlank()) {\n        return okHttpClient\n    }\n    proxyClientCache[proxy]?.let {\n        return it\n    }\n    val r = Regex(\"(http|socks4|socks5)://(.*):(\\\\d{2,5})(@.*@.*)?\")\n    val ms = r.findAll(proxy)\n    val group = ms.first()\n    var username = \"\"       //代理服务器验证用户名\n    var password = \"\"       //代理服务器验证密码\n    val type = if (group.groupValues[1] == \"http\") \"http\" else \"socks\"\n    val host = group.groupValues[2]\n    val port = group.groupValues[3].toInt()\n    if (group.groupValues[4] != \"\") {\n        username = group.groupValues[4].split(\"@\")[1]\n        password = group.groupValues[4].split(\"@\")[2]\n    }\n    if (host != \"\") {\n        val builder = okHttpClient.newBuilder()\n        if (type == \"http\") {\n            builder.proxy(Proxy(Proxy.Type.HTTP, InetSocketAddress(host, port)))\n        } else {\n            builder.proxy(Proxy(Proxy.Type.SOCKS, InetSocketAddress(host, port)))\n        }\n        if (username != \"\" && password != \"\") {\n            builder.proxyAuthenticator { _, response -> //设置代理服务器账号密码\n                val credential: String = Credentials.basic(username, password)\n                response.request.newBuilder()\n                    .header(\"Proxy-Authorization\", credential)\n                    .build()\n            }\n        }\n        val proxyClient = builder.build()\n        proxyClientCache[proxy] = proxyClient\n        return proxyClient\n    }\n    return okHttpClient\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/help/http/ObsoleteUrlFactory.kt",
    "content": "package io.legado.app.help.http\n\nimport android.os.Build\nimport androidx.annotation.RequiresApi\nimport io.legado.app.help.http.CookieManager.cookieJarHeader\nimport io.legado.app.help.http.SSLHelper.unsafeTrustManager\nimport okhttp3.Call\nimport okhttp3.Callback\nimport okhttp3.Dispatcher\nimport okhttp3.Handshake\nimport okhttp3.Headers\nimport okhttp3.HttpUrl\nimport okhttp3.HttpUrl.Companion.toHttpUrl\nimport okhttp3.Interceptor\nimport okhttp3.MediaType\nimport okhttp3.OkHttpClient\nimport okhttp3.Protocol\nimport okhttp3.Request\nimport okhttp3.RequestBody\nimport okhttp3.Response\nimport okio.Buffer\nimport okio.BufferedSink\nimport okio.Pipe\nimport okio.Timeout\nimport okio.buffer\nimport java.io.BufferedReader\nimport java.io.FileNotFoundException\nimport java.io.IOException\nimport java.io.InputStream\nimport java.io.InputStreamReader\nimport java.io.InterruptedIOException\nimport java.io.OutputStream\nimport java.net.HttpURLConnection\nimport java.net.InetSocketAddress\nimport java.net.MalformedURLException\nimport java.net.ProtocolException\nimport java.net.Proxy\nimport java.net.SocketPermission\nimport java.net.SocketTimeoutException\nimport java.net.URL\nimport java.net.URLConnection\nimport java.net.URLStreamHandler\nimport java.net.URLStreamHandlerFactory\nimport java.security.AccessControlException\nimport java.security.Permission\nimport java.security.Principal\nimport java.security.cert.Certificate\nimport java.text.DateFormat\nimport java.text.SimpleDateFormat\nimport java.util.Collections\nimport java.util.Date\nimport java.util.Locale\nimport java.util.TimeZone\nimport java.util.TreeMap\nimport java.util.concurrent.TimeUnit\nimport javax.net.ssl.HostnameVerifier\nimport javax.net.ssl.HttpsURLConnection\nimport javax.net.ssl.SSLSocketFactory\n\n/**\n * OkHttp 3.14 dropped support for the long-deprecated OkUrlFactory class, which allows you to use\n * the HttpURLConnection API with OkHttp's implementation. This class does the same thing using only\n * public APIs in OkHttp. It requires OkHttp 3.14 or newer.\n *\n *\n * Rather than pasting this 1100 line gist into your source code, please upgrade to OkHttp's\n * request/response API. Your code will be shorter, easier to read, and you'll be able to use\n * interceptors.\n */\n@Suppress(\"unused\", \"MemberVisibilityCanBePrivate\")\nclass ObsoleteUrlFactory(private var client: OkHttpClient) : URLStreamHandlerFactory,\n    Cloneable {\n    fun client(): OkHttpClient {\n        return client\n    }\n\n    fun setClient(client: OkHttpClient): ObsoleteUrlFactory {\n        this.client = client\n        return this\n    }\n\n    /**\n     * Returns a copy of this stream handler factory that includes a shallow copy of the internal\n     * [HTTP client][OkHttpClient].\n     */\n    public override fun clone(): ObsoleteUrlFactory {\n        return ObsoleteUrlFactory(client)\n    }\n\n    fun open(url: URL): HttpURLConnection {\n        return open(url, client.proxy)\n    }\n\n    fun open(url: URL, proxy: Proxy?): HttpURLConnection {\n        val protocol = url.protocol\n        val copy = client.newBuilder()\n            .proxy(proxy)\n            .build()\n        if (protocol == \"http\") return OkHttpURLConnection(url, copy)\n        if (protocol == \"https\") return OkHttpsURLConnection(url, copy)\n        throw IllegalArgumentException(\"Unexpected protocol: $protocol\")\n    }\n\n    /**\n     * Creates a URLStreamHandler as a [java.net.URL.setURLStreamHandlerFactory].\n     *\n     *\n     * This code configures OkHttp to handle all HTTP and HTTPS connections\n     * created with [java.net.URL.openConnection]: <pre>   `OkHttpClient okHttpClient = new OkHttpClient();\n     * URL.setURLStreamHandlerFactory(new ObsoleteUrlFactory(okHttpClient));\n    `</pre> *\n     */\n    override fun createURLStreamHandler(protocol: String): URLStreamHandler? {\n        return if (protocol != \"http\" && protocol != \"https\") null else object :\n            URLStreamHandler() {\n            override fun openConnection(url: URL): URLConnection {\n                return open(url)\n            }\n\n            override fun openConnection(url: URL, proxy: Proxy): URLConnection {\n                return open(url, proxy)\n            }\n\n            override fun getDefaultPort(): Int {\n                if ((protocol == \"http\")) return 80\n                if ((protocol == \"https\")) return 443\n                throw AssertionError()\n            }\n        }\n    }\n\n    internal class OkHttpURLConnection(\n        url: URL?, // These fields are confined to the application thread that uses HttpURLConnection.\n        var client: OkHttpClient\n    ) :\n        HttpURLConnection(url), Callback {\n        private val networkInterceptor: NetworkInterceptor = NetworkInterceptor()\n        var requestHeaders: Headers.Builder = Headers.Builder()\n        var responseHeaders: Headers? = null\n        var executed = false\n        var call: Call? = null\n\n        /** Like the superclass field of the same name, but a long and available on all platforms.  */\n        //var fixedContentLength = -1L\n\n        // These fields are guarded by lock.\n        private val lock = Any()\n        private var response: Response? = null\n        private var callFailure: Throwable? = null\n        var networkResponse: Response? = null\n        var connectPending = true\n        var proxy: Proxy? = null\n        var handshake: Handshake? = null\n\n        @Throws(IOException::class)\n        override fun connect() {\n            if (executed) return\n            val call = buildCall()\n            executed = true\n            call.enqueue(this)\n            synchronized(lock) {\n                try {\n                    while (connectPending && (response == null) && (callFailure == null)) {\n                        lock.wait() // Wait 'til the network interceptor is reached or the call fails.\n                    }\n                    if (callFailure != null) {\n                        throw propagate(callFailure)\n                    }\n                } catch (e: InterruptedException) {\n                    Thread.currentThread().interrupt() // Retain interrupted status.\n                    throw InterruptedIOException()\n                }\n            }\n        }\n\n        override fun disconnect() {\n            // Calling disconnect() before a connection exists should have no effect.\n            if (call == null) return\n            networkInterceptor.proceed() // Unblock any waiting async thread.\n            call!!.cancel()\n        }\n\n        override fun getErrorStream(): InputStream? {\n            return try {\n                val response = getResponse(true)\n                if (hasBody(response) && response.code >= HTTP_BAD_REQUEST) {\n                    response.body.byteStream()\n                } else null\n            } catch (e: IOException) {\n                null\n            }\n        }\n\n        @get:Throws(IOException::class)\n        val headers: Headers\n            get() {\n                if (responseHeaders == null) {\n                    val response = getResponse(true)\n                    val headers = response.headers\n                    responseHeaders = headers.newBuilder()\n                        .add(SELECTED_PROTOCOL, response.protocol.toString())\n                        .add(RESPONSE_SOURCE, responseSourceHeader(response))\n                        .build()\n                }\n                return responseHeaders as Headers\n            }\n\n        override fun getHeaderField(position: Int): String? {\n            return try {\n                val headers = headers\n                if (position < 0 || position >= headers.size) null\n                else headers.value(position)\n            } catch (e: IOException) {\n                null\n            }\n        }\n\n        override fun getHeaderField(fieldName: String?): String? {\n            return try {\n                if (fieldName == null) statusLineToString(getResponse(true)) else headers[fieldName]\n            } catch (e: IOException) {\n                null\n            }\n        }\n\n        override fun getHeaderFieldKey(position: Int): String? {\n            return try {\n                val headers = headers\n                if (position < 0 || position >= headers.size) null else headers.name(position)\n            } catch (e: IOException) {\n                null\n            }\n        }\n\n        override fun getHeaderFields(): Map<String, List<String>> {\n            return try {\n                toMultimap(headers, statusLineToString(getResponse(true)))\n            } catch (e: IOException) {\n                emptyMap()\n            }\n        }\n\n        override fun getRequestProperties(): Map<String, List<String>> {\n            if (connected) {\n                throw IllegalStateException(\n                    \"Cannot access request header fields after connection is set\"\n                )\n            }\n            return toMultimap(requestHeaders.build(), null)\n        }\n\n        @Throws(IOException::class)\n        override fun getInputStream(): InputStream {\n            if (!doInput) {\n                throw ProtocolException(\"This protocol does not support input\")\n            }\n            val response = getResponse(false)\n            if (response.code >= HTTP_BAD_REQUEST) throw FileNotFoundException(url.toString())\n            return response.body.byteStream()\n        }\n\n        @Throws(IOException::class)\n        override fun getOutputStream(): OutputStream {\n            val requestBody = buildCall().request().body as OutputStreamRequestBody?\n                ?: throw ProtocolException(\"method does not support a request body: $method\")\n            if (requestBody is StreamedRequestBody) {\n                connect()\n                networkInterceptor.proceed()\n            }\n            if (requestBody.closed) {\n                throw ProtocolException(\"cannot write request body after response has been read\")\n            }\n            return requestBody.outputStream!!\n        }\n\n        override fun getPermission(): Permission {\n            val url = getURL()\n            var hostname = url.host\n            var hostPort = if (url.port != -1) url.port else HttpUrl.defaultPort(url.protocol)\n            if (usingProxy()) {\n                val proxyAddress = client.proxy!!.address() as InetSocketAddress\n                hostname = proxyAddress.hostName\n                hostPort = proxyAddress.port\n            }\n            return SocketPermission(\"$hostname:$hostPort\", \"connect, resolve\")\n        }\n\n        override fun getRequestProperty(field: String?): String? {\n            return if (field == null) null else requestHeaders[field]\n        }\n\n        override fun setConnectTimeout(timeoutMillis: Int) {\n            client = client.newBuilder()\n                .connectTimeout(timeoutMillis.toLong(), TimeUnit.MILLISECONDS)\n                .build()\n        }\n\n        override fun setInstanceFollowRedirects(followRedirects: Boolean) {\n            client = client.newBuilder()\n                .followRedirects(followRedirects)\n                .build()\n        }\n\n        override fun getInstanceFollowRedirects(): Boolean {\n            return client.followRedirects\n        }\n\n        override fun getConnectTimeout(): Int {\n            return client.connectTimeoutMillis\n        }\n\n        override fun setReadTimeout(timeoutMillis: Int) {\n            client = client.newBuilder()\n                .readTimeout(timeoutMillis.toLong(), TimeUnit.MILLISECONDS)\n                .build()\n        }\n\n        override fun getReadTimeout(): Int {\n            return client.readTimeoutMillis\n        }\n\n        @Throws(IOException::class)\n        private fun buildCall(): Call {\n            if (call != null) {\n                return call as Call\n            }\n            connected = true\n            if (doOutput) {\n                if (method == \"GET\") {\n                    method = \"POST\"\n                } else if (!permitsRequestBody(method)) {\n                    throw ProtocolException(\"$method does not support writing\")\n                }\n            }\n            if (requestHeaders[\"User-Agent\"] == null) {\n                requestHeaders.add(\"User-Agent\", defaultUserAgent())\n            }\n            var requestBody: OutputStreamRequestBody? = null\n            if (permitsRequestBody(method)) {\n                var contentType: String? = requestHeaders[\"Content-Type\"]\n                if (contentType == null) {\n                    contentType = \"application/x-www-form-urlencoded\"\n                    requestHeaders.add(\"Content-Type\", contentType)\n                }\n                val stream = fixedContentLength != -1 || chunkLength > 0\n                var contentLength = -1L\n                val contentLengthString: String? = requestHeaders[\"Content-Length\"]\n                if (fixedContentLength != -1) {\n                    contentLength = fixedContentLength.toLong()\n                } else if (contentLengthString != null) {\n                    contentLength = contentLengthString.toLong()\n                }\n                requestBody =\n                    if (stream) StreamedRequestBody(contentLength) else BufferedRequestBody(\n                        contentLength\n                    )\n                requestBody.timeout!!.timeout(\n                    client.writeTimeoutMillis.toLong(),\n                    TimeUnit.MILLISECONDS\n                )\n            }\n            val url: HttpUrl\n            try {\n                url = getURL().toString().toHttpUrl()\n            } catch (e: IllegalArgumentException) {\n                val malformedUrl = MalformedURLException()\n                malformedUrl.initCause(e)\n                throw malformedUrl\n            }\n            val request: Request = Request.Builder()\n                .url(url)\n                .headers(requestHeaders.build())\n                .method(method, requestBody)\n                .build()\n            val clientBuilder: OkHttpClient.Builder = client.newBuilder()\n            clientBuilder.interceptors().clear()\n            clientBuilder.interceptors().add(UnexpectedException.INTERCEPTOR)\n\n            clientBuilder.networkInterceptors().clear()\n            clientBuilder.networkInterceptors().add(networkInterceptor)\n            clientBuilder.addNetworkInterceptor { chain ->\n                var request1 = chain.request()\n                val enableCookieJar = request1.header(cookieJarHeader) != null\n\n                if (enableCookieJar) {\n                    val requestBuilder = request1.newBuilder()\n                    requestBuilder.removeHeader(cookieJarHeader)\n                    request1 = CookieManager.loadRequest(requestBuilder.build())\n                }\n\n                val networkResponse = chain.proceed(request1)\n\n                if (enableCookieJar) {\n                    CookieManager.saveResponse(networkResponse)\n                }\n                networkResponse\n            }\n\n            // Use a separate dispatcher so that limits aren't impacted. But use the same executor service!\n            clientBuilder.dispatcher(Dispatcher(client.dispatcher.executorService))\n\n            // If we're currently not using caches, make sure the engine's client doesn't have one.\n            if (!getUseCaches()) {\n                clientBuilder.cache(null)\n            }\n            return clientBuilder.build().newCall(request).also { call = it }\n        }\n\n        @Throws(IOException::class)\n        private fun getResponse(networkResponseOnError: Boolean): Response {\n            synchronized(lock) {\n                if (response != null) return response as Response\n                if (callFailure != null) {\n                    if (networkResponseOnError && networkResponse != null) return networkResponse as Response\n                    throw propagate(callFailure)\n                }\n            }\n            val call = buildCall()\n            networkInterceptor.proceed()\n            val requestBody = call.request().body as OutputStreamRequestBody?\n            if (requestBody != null) requestBody.outputStream!!.close()\n            if (executed) {\n                synchronized(lock) {\n                    try {\n                        while (response == null && callFailure == null) {\n                            lock.wait() // Wait until the response is returned or the call fails.\n                        }\n                    } catch (e: InterruptedException) {\n                        Thread.currentThread().interrupt() // Retain interrupted status.\n                        throw InterruptedIOException()\n                    }\n                }\n            } else {\n                executed = true\n                try {\n                    onResponse(call, call.execute())\n                } catch (e: IOException) {\n                    onFailure(call, e)\n                }\n            }\n            synchronized(lock) {\n                if (callFailure != null) throw propagate(callFailure)\n                if (response != null) return response as Response\n            }\n            throw AssertionError()\n        }\n\n        override fun usingProxy(): Boolean {\n            if (proxy != null) return true\n            val clientProxy = client.proxy\n            return clientProxy != null && clientProxy.type() != Proxy.Type.DIRECT\n        }\n\n        @Throws(IOException::class)\n        override fun getResponseMessage(): String {\n            return getResponse(true).message\n        }\n\n        @Throws(IOException::class)\n        override fun getResponseCode(): Int {\n            return getResponse(true).code\n        }\n\n        override fun setRequestProperty(field: String?, newValue: String?) {\n            if (connected) {\n                throw IllegalStateException(\"Cannot set request property after connection is made\")\n            }\n            if (field == null) {\n                throw NullPointerException(\"field == null\")\n            }\n            if (newValue == null) {\n                return\n            }\n            requestHeaders[field] = newValue\n        }\n\n        override fun setIfModifiedSince(newValue: Long) {\n            super.setIfModifiedSince(newValue)\n            if (ifModifiedSince != 0L) {\n                requestHeaders[\"If-Modified-Since\"] = format(Date(ifModifiedSince))\n            } else {\n                requestHeaders.removeAll(\"If-Modified-Since\")\n            }\n        }\n\n        override fun addRequestProperty(field: String?, value: String?) {\n            if (connected) {\n                throw IllegalStateException(\"Cannot add request property after connection is made\")\n            }\n            if (field == null) {\n                throw NullPointerException(\"field == null\")\n            }\n            if (value == null) {\n                return\n            }\n            requestHeaders.add(field, value)\n        }\n\n        @Throws(ProtocolException::class)\n        override fun setRequestMethod(method: String) {\n            if (!METHODS.contains(method)) {\n                throw ProtocolException(\"Expected one of $METHODS but was $method\")\n            }\n            this.method = method\n        }\n\n        override fun setFixedLengthStreamingMode(contentLength: Int) {\n            setFixedLengthStreamingMode(contentLength.toLong())\n        }\n\n        override fun setFixedLengthStreamingMode(contentLength: Long) {\n            if (super.connected) throw IllegalStateException(\"Already connected\")\n            if (chunkLength > 0) throw IllegalStateException(\"Already in chunked mode\")\n            if (contentLength < 0) throw IllegalArgumentException(\"contentLength < 0\")\n            this.fixedContentLength = contentLength.toInt()\n            super.fixedContentLength = contentLength.toInt().coerceAtMost(Int.MAX_VALUE)\n        }\n\n        override fun onFailure(call: Call, e: IOException) {\n            synchronized(lock) {\n                callFailure = if ((e is UnexpectedException)) e.cause else e\n                lock.notifyAll()\n            }\n        }\n\n        override fun onResponse(call: Call, response: Response) {\n            synchronized(lock) {\n                this.response = response\n                handshake = response.handshake\n                url = response.request.url.toUrl()\n                lock.notifyAll()\n            }\n        }\n\n        internal inner class NetworkInterceptor : Interceptor {\n            // Guarded by HttpUrlConnection.this.\n            private var proceed = false\n            fun proceed() {\n                synchronized(lock) {\n                    proceed = true\n                    lock.notifyAll()\n                }\n            }\n\n            @Throws(IOException::class)\n            override fun intercept(chain: Interceptor.Chain): Response {\n                var request: Request = chain.request()\n                synchronized(lock) {\n                    connectPending = false\n                    proxy = chain.connection()!!.route().proxy\n                    handshake = chain.connection()!!.handshake()\n                    lock.notifyAll()\n                    try {\n                        while (!proceed) {\n                            lock.wait() // Wait until proceed() is called.\n                        }\n                    } catch (e: InterruptedException) {\n                        Thread.currentThread().interrupt() // Retain interrupted status.\n                        throw InterruptedIOException()\n                    }\n                }\n\n                // Try to lock in the Content-Length before transmitting the request body.\n                if (request.body is OutputStreamRequestBody) {\n                    val requestBody = request.body as OutputStreamRequestBody?\n                    request = requestBody!!.prepareToSendRequest(request)\n                }\n                val response: Response = chain.proceed(request)\n                synchronized(lock) {\n                    networkResponse = response\n                    url = response.request.url.toUrl()\n                }\n                return response\n            }\n        }\n    }\n\n    internal abstract class OutputStreamRequestBody : RequestBody() {\n        var timeout: Timeout? = null\n        var expectedContentLength: Long = 0\n        var outputStream: OutputStream? = null\n        var closed = false\n        fun initOutputStream(sink: BufferedSink, expectedContentLength: Long) {\n            timeout = sink.timeout()\n            this.expectedContentLength = expectedContentLength\n\n            // An output stream that writes to sink. If expectedContentLength is not -1, then this expects\n            // exactly that many bytes to be written.\n            outputStream = object : OutputStream() {\n                private var bytesReceived: Long = 0\n\n                @Throws(IOException::class)\n                override fun write(b: Int) {\n                    write(byteArrayOf(b.toByte()), 0, 1)\n                }\n\n                @Throws(IOException::class)\n                override fun write(source: ByteArray, offset: Int, byteCount: Int) {\n                    if (closed) throw IOException(\"closed\") // Not IllegalStateException!\n                    if (expectedContentLength != -1L && bytesReceived + byteCount > expectedContentLength) {\n                        throw ProtocolException(\n                            \"expected \" + expectedContentLength\n                                    + \" bytes but received \" + bytesReceived + byteCount\n                        )\n                    }\n                    bytesReceived += byteCount.toLong()\n                    try {\n                        sink.write(source, offset, byteCount)\n                    } catch (e: InterruptedIOException) {\n                        throw SocketTimeoutException(e.message)\n                    }\n                }\n\n                @Throws(IOException::class)\n                override fun flush() {\n                    if (closed) return  // Weird, but consistent with historical behavior.\n                    sink.flush()\n                }\n\n                @Throws(IOException::class)\n                override fun close() {\n                    closed = true\n                    if (expectedContentLength != -1L && bytesReceived < expectedContentLength) {\n                        throw ProtocolException(\n                            (\"expected \" + expectedContentLength\n                                    + \" bytes but received \" + bytesReceived)\n                        )\n                    }\n                    sink.close()\n                }\n            }\n        }\n\n        override fun contentLength(): Long {\n            return expectedContentLength\n        }\n\n        override fun contentType(): MediaType? {\n            return null // Let the caller provide this in a regular header.\n        }\n\n        @Throws(IOException::class)\n        open fun prepareToSendRequest(request: Request): Request {\n            return request\n        }\n    }\n\n    @Suppress(\"MemberVisibilityCanBePrivate\")\n    internal class BufferedRequestBody(expectedContentLength: Long) :\n        OutputStreamRequestBody() {\n        val buffer = Buffer()\n        var contentLength = -1L\n\n        init {\n            initOutputStream(buffer, expectedContentLength)\n        }\n\n        override fun contentLength(): Long {\n            return contentLength\n        }\n\n        @Throws(IOException::class)\n        override fun prepareToSendRequest(request: Request): Request {\n            if (request.header(\"Content-Length\") != null) return request\n            outputStream!!.close()\n            contentLength = buffer.size\n            return request.newBuilder()\n                .removeHeader(\"Transfer-Encoding\")\n                .header(\"Content-Length\", buffer.size.toString())\n                .build()\n        }\n\n        override fun writeTo(sink: BufferedSink) {\n            buffer.copyTo(sink.buffer, 0, buffer.size)\n        }\n    }\n\n    internal class StreamedRequestBody(expectedContentLength: Long) :\n        OutputStreamRequestBody() {\n        private val pipe = Pipe(8192)\n\n        init {\n            initOutputStream(pipe.sink.buffer(), expectedContentLength)\n        }\n\n        override fun isOneShot(): Boolean {\n            return true\n        }\n\n        @Throws(IOException::class)\n        override fun writeTo(sink: BufferedSink) {\n            val buffer = Buffer()\n            while (pipe.source.read(buffer, 8192) != -1L) {\n                sink.write(buffer, buffer.size)\n            }\n        }\n    }\n\n    internal abstract class DelegatingHttpsURLConnection(private val delegate: HttpURLConnection) :\n        HttpsURLConnection(delegate.url) {\n        protected abstract fun handshake(): Handshake?\n        abstract override fun setHostnameVerifier(hostnameVerifier: HostnameVerifier)\n        abstract override fun getHostnameVerifier(): HostnameVerifier\n        abstract override fun setSSLSocketFactory(sslSocketFactory: SSLSocketFactory?)\n        abstract override fun getSSLSocketFactory(): SSLSocketFactory\n        override fun getCipherSuite(): String? {\n            val handshake = handshake()\n            return handshake?.cipherSuite?.javaName\n        }\n\n        override fun getLocalCertificates(): Array<Certificate>? {\n            val handshake = handshake() ?: return null\n            val result = handshake.localCertificates\n            return if (result.isNotEmpty()) result.toTypedArray() else null\n        }\n\n        override fun getServerCertificates(): Array<Certificate>? {\n            val handshake = handshake() ?: return null\n            val result = handshake.peerCertificates\n            return if (result.isNotEmpty()) result.toTypedArray() else null\n        }\n\n        override fun getPeerPrincipal(): Principal? {\n            return handshake()?.peerPrincipal\n        }\n\n        override fun getLocalPrincipal(): Principal? {\n            return handshake()?.localPrincipal\n        }\n\n        @Throws(IOException::class)\n        override fun connect() {\n            connected = true\n            delegate.connect()\n        }\n\n        override fun disconnect() {\n            delegate.disconnect()\n        }\n\n        override fun getErrorStream(): InputStream? {\n            return delegate.errorStream\n        }\n\n        override fun getRequestMethod(): String {\n            return delegate.requestMethod\n        }\n\n        @Throws(IOException::class)\n        override fun getResponseCode(): Int {\n            return delegate.responseCode\n        }\n\n        @Throws(IOException::class)\n        override fun getResponseMessage(): String? {\n            return delegate.responseMessage\n        }\n\n        @Throws(ProtocolException::class)\n        override fun setRequestMethod(method: String) {\n            delegate.requestMethod = method\n        }\n\n        override fun usingProxy(): Boolean {\n            return delegate.usingProxy()\n        }\n\n        override fun getInstanceFollowRedirects(): Boolean {\n            return delegate.instanceFollowRedirects\n        }\n\n        override fun setInstanceFollowRedirects(followRedirects: Boolean) {\n            delegate.instanceFollowRedirects = followRedirects\n        }\n\n        override fun getAllowUserInteraction(): Boolean {\n            return delegate.allowUserInteraction\n        }\n\n        @Throws(IOException::class)\n        override fun getContent(): Any {\n            return delegate.content\n        }\n\n        @Throws(IOException::class)\n        override fun getContent(types: Array<Class<*>?>?): Any? {\n            return delegate.getContent(types)\n        }\n\n        override fun getContentEncoding(): String? {\n            return delegate.contentEncoding\n        }\n\n        override fun getContentLength(): Int {\n            return delegate.contentLength\n        }\n\n        // Should only be invoked on Java 8+ or Android API 24+.\n        @RequiresApi(Build.VERSION_CODES.N)\n        override fun getContentLengthLong(): Long {\n            return delegate.contentLengthLong\n        }\n\n        override fun getContentType(): String? {\n            return delegate.contentType\n        }\n\n        override fun getDate(): Long {\n            return delegate.date\n        }\n\n        override fun getDefaultUseCaches(): Boolean {\n            return delegate.defaultUseCaches\n        }\n\n        override fun getDoInput(): Boolean {\n            return delegate.doInput\n        }\n\n        override fun getDoOutput(): Boolean {\n            return delegate.doOutput\n        }\n\n        override fun getExpiration(): Long {\n            return delegate.expiration\n        }\n\n        override fun getHeaderField(pos: Int): String? {\n            return delegate.getHeaderField(pos)\n        }\n\n        override fun getHeaderFields(): Map<String, List<String>> {\n            return delegate.headerFields\n        }\n\n        override fun getRequestProperties(): Map<String, List<String>> {\n            return delegate.requestProperties\n        }\n\n        override fun addRequestProperty(field: String, newValue: String) {\n            delegate.addRequestProperty(field, newValue)\n        }\n\n        override fun getHeaderField(key: String): String? {\n            return delegate.getHeaderField(key)\n        }\n\n        // Should only be invoked on Java 8+ or Android API 24+.\n        @RequiresApi(Build.VERSION_CODES.N)\n        override fun getHeaderFieldLong(field: String, defaultValue: Long): Long {\n            return delegate.getHeaderFieldLong(field, defaultValue)\n        }\n\n        override fun getHeaderFieldDate(field: String, defaultValue: Long): Long {\n            return delegate.getHeaderFieldDate(field, defaultValue)\n        }\n\n        override fun getHeaderFieldInt(field: String, defaultValue: Int): Int {\n            return delegate.getHeaderFieldInt(field, defaultValue)\n        }\n\n        override fun getHeaderFieldKey(position: Int): String? {\n            return delegate.getHeaderFieldKey(position)\n        }\n\n        override fun getIfModifiedSince(): Long {\n            return delegate.ifModifiedSince\n        }\n\n        @Throws(IOException::class)\n        override fun getInputStream(): InputStream {\n            return delegate.inputStream\n        }\n\n        override fun getLastModified(): Long {\n            return delegate.lastModified\n        }\n\n        @Throws(IOException::class)\n        override fun getOutputStream(): OutputStream {\n            return delegate.outputStream\n        }\n\n        @Throws(IOException::class)\n        override fun getPermission(): Permission {\n            return delegate.permission\n        }\n\n        override fun getRequestProperty(field: String): String? {\n            return delegate.getRequestProperty(field)\n        }\n\n        override fun getURL(): URL {\n            return delegate.url\n        }\n\n        override fun getUseCaches(): Boolean {\n            return delegate.useCaches\n        }\n\n        override fun setAllowUserInteraction(newValue: Boolean) {\n            delegate.allowUserInteraction = newValue\n        }\n\n        override fun setDefaultUseCaches(newValue: Boolean) {\n            delegate.defaultUseCaches = newValue\n        }\n\n        override fun setDoInput(newValue: Boolean) {\n            delegate.doInput = newValue\n        }\n\n        override fun setDoOutput(newValue: Boolean) {\n            delegate.doOutput = newValue\n        }\n\n        // Should only be invoked on Java 8+ or Android API 24+.\n        override fun setFixedLengthStreamingMode(contentLength: Long) {\n            delegate.setFixedLengthStreamingMode(contentLength)\n        }\n\n        override fun setIfModifiedSince(newValue: Long) {\n            delegate.ifModifiedSince = newValue\n        }\n\n        override fun setRequestProperty(field: String, newValue: String) {\n            delegate.setRequestProperty(field, newValue)\n        }\n\n        override fun setUseCaches(newValue: Boolean) {\n            delegate.useCaches = newValue\n        }\n\n        override fun setConnectTimeout(timeoutMillis: Int) {\n            delegate.connectTimeout = timeoutMillis\n        }\n\n        override fun getConnectTimeout(): Int {\n            return delegate.connectTimeout\n        }\n\n        override fun setReadTimeout(timeoutMillis: Int) {\n            delegate.readTimeout = timeoutMillis\n        }\n\n        override fun getReadTimeout(): Int {\n            return delegate.readTimeout\n        }\n\n        override fun toString(): String {\n            return delegate.toString()\n        }\n\n        override fun setFixedLengthStreamingMode(contentLength: Int) {\n            delegate.setFixedLengthStreamingMode(contentLength)\n        }\n\n        override fun setChunkedStreamingMode(chunkLength: Int) {\n            delegate.setChunkedStreamingMode(chunkLength)\n        }\n    }\n\n    internal class OkHttpsURLConnection(private val delegate: OkHttpURLConnection) :\n        DelegatingHttpsURLConnection(delegate) {\n        constructor(url: URL?, client: OkHttpClient) : this(OkHttpURLConnection(url, client))\n\n        override fun handshake(): Handshake? {\n            if (delegate.call == null) {\n                throw IllegalStateException(\"Connection has not yet been established\")\n            }\n            return delegate.handshake\n        }\n\n        override fun setHostnameVerifier(hostnameVerifier: HostnameVerifier) {\n            delegate.client = delegate.client.newBuilder()\n                .hostnameVerifier(hostnameVerifier)\n                .build()\n        }\n\n        override fun getHostnameVerifier(): HostnameVerifier {\n            return delegate.client.hostnameVerifier\n        }\n\n        override fun setSSLSocketFactory(sslSocketFactory: SSLSocketFactory?) {\n            if (sslSocketFactory == null) {\n                throw IllegalArgumentException(\"sslSocketFactory == null\")\n            }\n            // This fails in JDK 9 because OkHttp is unable to extract the trust manager.\n            delegate.client = delegate.client.newBuilder()\n                .sslSocketFactory(sslSocketFactory, unsafeTrustManager)\n                .build()\n        }\n\n        override fun getSSLSocketFactory(): SSLSocketFactory {\n            return delegate.client.sslSocketFactory\n        }\n    }\n\n    internal class UnexpectedException(cause: Throwable?) : IOException(cause) {\n        companion object {\n            val INTERCEPTOR = Interceptor { chain: Interceptor.Chain ->\n                try {\n                    return@Interceptor chain.proceed(chain.request())\n                } catch (e: Error) {\n                    throw UnexpectedException(e)\n                } catch (e: RuntimeException) {\n                    throw UnexpectedException(e)\n                }\n            }\n        }\n    }\n\n    companion object {\n        const val SELECTED_PROTOCOL = \"ObsoleteUrlFactory-Selected-Protocol\"\n        const val RESPONSE_SOURCE = \"ObsoleteUrlFactory-Response-Source\"\n        val METHODS: Set<String> = LinkedHashSet(\n            listOf(\"OPTIONS\", \"GET\", \"HEAD\", \"POST\", \"PUT\", \"DELETE\", \"TRACE\", \"PATCH\")\n        )\n        val UTC: TimeZone = TimeZone.getTimeZone(\"GMT\")\n        const val HTTP_CONTINUE = 100\n        val STANDARD_DATE_FORMAT: ThreadLocal<DateFormat> = ThreadLocal.withInitial {\n\n            // Date format specified by RFC 7231 section 7.1.1.1.\n            val rfc1123: DateFormat = SimpleDateFormat(\n                \"EEE, dd MMM yyyy HH:mm:ss 'GMT'\",\n                Locale.US\n            )\n            rfc1123.isLenient = false\n            rfc1123.timeZone = UTC\n            rfc1123\n        }\n        private val FIELD_NAME_COMPARATOR =\n            java.util.Comparator { a: String?, b: String? ->\n                // @FindBugsSuppressWarnings(\"ES_COMPARING_PARAMETER_STRING_WITH_EQ\")\n                if (a === b) {\n                    return@Comparator 0\n                } else if (a == null) {\n                    return@Comparator -1\n                } else if (b == null) {\n                    return@Comparator 1\n                } else {\n                    return@Comparator java.lang.String.CASE_INSENSITIVE_ORDER.compare(a, b)\n                }\n            }\n\n        @Suppress(\n            \"RECEIVER_NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS\",\n            \"NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS\"\n        )\n        fun format(value: Date?): String {\n            return STANDARD_DATE_FORMAT.get().format(value)\n        }\n\n        fun permitsRequestBody(method: String): Boolean {\n            return !((method == \"GET\") || (method == \"HEAD\"))\n        }\n\n        /** Returns true if the response must have a (possibly 0-length) body. See RFC 7231.  */\n        fun hasBody(response: Response): Boolean {\n            // HEAD requests never yield a body regardless of the response headers.\n            if ((response.request.method == \"HEAD\")) {\n                return false\n            }\n            val responseCode = response.code\n            if (((responseCode < HTTP_CONTINUE || responseCode >= 200)\n                        && (responseCode != HttpURLConnection.HTTP_NO_CONTENT\n                        ) && (responseCode != HttpURLConnection.HTTP_NOT_MODIFIED))\n            ) {\n                return true\n            }\n\n            // If the Content-Length or Transfer-Encoding headers disagree with the response code, the\n            // response is malformed. For best compatibility, we honor the headers.\n            return (contentLength(response.headers) != -1L\n                    || \"chunked\".equals(\n                response.header(\"Transfer-Encoding\"),\n                ignoreCase = true\n            ))\n        }\n\n        fun contentLength(headers: Headers): Long {\n            val s = headers[\"Content-Length\"] ?: return -1\n            return try {\n                s.toLong()\n            } catch (e: NumberFormatException) {\n                -1\n            }\n        }\n\n        fun responseSourceHeader(response: Response): String {\n            if (response.networkResponse == null) {\n                return if (response.cacheResponse == null) \"NONE\" else \"CACHE \" + response.code\n            }\n            return if (response.cacheResponse == null) \"NETWORK \" + response.code else \"CONDITIONAL_CACHE \" + response.networkResponse!!.code\n        }\n\n        fun statusLineToString(response: Response): String {\n            return ((if (response.protocol == Protocol.HTTP_1_0) \"HTTP/1.0\" else \"HTTP/1.1\")\n                    + ' ' + response.code\n                    + ' ' + response.message)\n        }\n\n        fun toHumanReadableAscii(s: String): String {\n            var i = 0\n            val length = s.length\n            var c: Int\n            while (i < length) {\n                c = s.codePointAt(i)\n                if (c > '\\u001f'.code && c < '\\u007f'.code) {\n                    i += Character.charCount(c)\n                    continue\n                }\n                val buffer = Buffer()\n                buffer.writeUtf8(s, 0, i)\n                buffer.writeUtf8CodePoint('?'.code)\n                var j = i + Character.charCount(c)\n                while (j < length) {\n                    c = s.codePointAt(j)\n                    buffer.writeUtf8CodePoint(\n                        (if (c > '\\u001f'.code && c < '\\u007f'.code) c else '?') as Int\n                    )\n                    j += Character.charCount(c)\n                }\n                return buffer.readUtf8()\n            }\n            return s\n        }\n\n        fun toMultimap(headers: Headers, valueForNullKey: String?): Map<String, List<String>> {\n            val result: MutableMap<String?, List<String>> = TreeMap(FIELD_NAME_COMPARATOR)\n            var i = 0\n            val size = headers.size\n            while (i < size) {\n                val fieldName = headers.name(i)\n                val value = headers.value(i)\n                val allValues: MutableList<String> = ArrayList()\n                val otherValues = result[fieldName]\n                if (otherValues != null) {\n                    allValues.addAll(otherValues)\n                }\n                allValues.add(value)\n                result[fieldName] = Collections.unmodifiableList(allValues)\n                i++\n            }\n            if (valueForNullKey != null) {\n                result[null] = Collections.unmodifiableList(listOf(valueForNullKey))\n            }\n            return Collections.unmodifiableMap(result)\n        }\n\n        @Suppress(\"NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS\")\n        fun getSystemProperty(key: String?, defaultValue: String?): String? {\n            val value: String?\n            try {\n                value = System.getProperty(key)\n            } catch (ex: AccessControlException) {\n                return defaultValue\n            }\n            return value ?: defaultValue\n        }\n\n        fun defaultUserAgent(): String {\n            val agent = getSystemProperty(\"http.agent\", null)\n            return if (agent != null) toHumanReadableAscii(agent) else \"ObsoleteUrlFactory\"\n        }\n\n        @Throws(IOException::class)\n        fun propagate(throwable: Throwable?): IOException {\n            if (throwable is IOException) throw throwable\n            if (throwable is Error) throw throwable\n            if (throwable is RuntimeException) throw throwable\n            throw AssertionError()\n        }\n\n        @Suppress(\"PLATFORM_CLASS_MAPPED_TO_KOTLIN\", \"NOTHING_TO_INLINE\")\n        private inline fun Any.wait() = (this as Object).wait()\n\n        @Suppress(\"PLATFORM_CLASS_MAPPED_TO_KOTLIN\", \"NOTHING_TO_INLINE\")\n        private inline fun Any.notify() = (this as Object).notify()\n\n        @Suppress(\"PLATFORM_CLASS_MAPPED_TO_KOTLIN\", \"NOTHING_TO_INLINE\")\n        private inline fun Any.notifyAll() = (this as Object).notifyAll()\n\n        @Throws(Exception::class)\n        @JvmStatic\n        fun main(args: Array<String>) {\n            val okHttpClient = OkHttpClient()\n            URL.setURLStreamHandlerFactory(ObsoleteUrlFactory(okHttpClient))\n            val url = URL(\"https://publicobject.com/helloworld.txt\")\n            val urlConnection = url.openConnection() as HttpURLConnection\n            BufferedReader(\n                InputStreamReader(urlConnection.inputStream)\n            ).use { reader ->\n                var line: String?\n                while ((reader.readLine().also { line = it }) != null) {\n                    println(line)\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/help/http/OkHttpExceptionInterceptor.kt",
    "content": "package io.legado.app.help.http\n\nimport okhttp3.Interceptor\nimport okhttp3.Response\nimport java.io.IOException\n\nobject OkHttpExceptionInterceptor : Interceptor {\n\n    @Throws(IOException::class)\n    override fun intercept(chain: Interceptor.Chain): Response {\n        try {\n            return chain.proceed(chain.request())\n        } catch (e: IOException) {\n            throw e\n        } catch (e: Throwable) {\n            throw IOException(e)\n        }\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/help/http/OkHttpUtils.kt",
    "content": "package io.legado.app.help.http\n\nimport io.legado.app.utils.EncodingDetect\nimport io.legado.app.utils.GSON\nimport io.legado.app.utils.Utf8BomUtils\nimport kotlinx.coroutines.suspendCancellableCoroutine\nimport okhttp3.Call\nimport okhttp3.Callback\nimport okhttp3.FormBody\nimport okhttp3.HttpUrl.Companion.toHttpUrl\nimport okhttp3.MediaType.Companion.toMediaType\nimport okhttp3.MultipartBody\nimport okhttp3.OkHttpClient\nimport okhttp3.Request\nimport okhttp3.RequestBody.Companion.asRequestBody\nimport okhttp3.RequestBody.Companion.toRequestBody\nimport okhttp3.Response\nimport okhttp3.ResponseBody\nimport okhttp3.internal.http.RealResponseBody\nimport okio.buffer\nimport okio.source\nimport java.io.File\nimport java.io.IOException\nimport java.nio.charset.Charset\nimport java.util.zip.ZipInputStream\nimport kotlin.coroutines.resume\nimport kotlin.coroutines.resumeWithException\n\nsuspend fun OkHttpClient.newCallResponse(\n    retry: Int = 0,\n    builder: Request.Builder.() -> Unit\n): Response {\n    val requestBuilder = Request.Builder()\n    requestBuilder.apply(builder)\n    var response: Response? = null\n    for (i in 0..retry) {\n        response = newCall(requestBuilder.build()).await()\n        if (response.isSuccessful) {\n            return response\n        }\n    }\n    return response!!\n}\n\nsuspend fun OkHttpClient.newCallResponseBody(\n    retry: Int = 0,\n    builder: Request.Builder.() -> Unit\n): ResponseBody {\n    return newCallResponse(retry, builder).body\n}\n\nsuspend fun OkHttpClient.newCallStrResponse(\n    retry: Int = 0,\n    builder: Request.Builder.() -> Unit\n): StrResponse {\n    return newCallResponse(retry, builder).let {\n        StrResponse(it, it.body.text())\n    }\n}\n\nsuspend fun Call.await(): Response = suspendCancellableCoroutine { block ->\n\n    block.invokeOnCancellation {\n        cancel()\n    }\n\n    enqueue(object : Callback {\n        override fun onFailure(call: Call, e: IOException) {\n            block.resumeWithException(e)\n        }\n\n        override fun onResponse(call: Call, response: Response) {\n            block.resume(response)\n        }\n    })\n\n}\n\nfun ResponseBody.text(encode: String? = null): String {\n    val responseBytes = Utf8BomUtils.removeUTF8BOM(bytes())\n    var charsetName: String? = encode\n\n    charsetName?.let {\n        return String(responseBytes, Charset.forName(charsetName))\n    }\n\n    //根据http头判断\n    contentType()?.charset()?.let { charset ->\n        return String(responseBytes, charset)\n    }\n\n    //根据内容判断\n    charsetName = EncodingDetect.getHtmlEncode(responseBytes)\n    return String(responseBytes, Charset.forName(charsetName))\n}\n\nfun ResponseBody.decompressed(): ResponseBody {\n    val contentType = contentType()?.toString()\n    if (contentType != \"application/zip\") {\n        return this\n    }\n    val source = ZipInputStream(byteStream()).apply {\n        try {\n            nextEntry\n        } catch (e: Exception) {\n            close()\n            throw e\n        }\n    }.source().buffer()\n    return RealResponseBody(null, -1, source)\n}\n\nfun Request.Builder.addHeaders(headers: Map<String, String>) {\n    headers.forEach {\n        addHeader(it.key, it.value)\n    }\n}\n\nfun Request.Builder.get(url: String, queryMap: Map<String, String>, encoded: Boolean = false) {\n    val httpBuilder = url.toHttpUrl().newBuilder()\n    queryMap.forEach {\n        if (encoded) {\n            httpBuilder.addEncodedQueryParameter(it.key, it.value)\n        } else {\n            httpBuilder.addQueryParameter(it.key, it.value)\n        }\n    }\n    url(httpBuilder.build())\n}\n\nfun Request.Builder.get(url: String, encodedQuery: String?) {\n    val httpBuilder = url.toHttpUrl().newBuilder()\n    httpBuilder.encodedQuery(encodedQuery)\n    url(httpBuilder.build())\n}\n\nprivate val formContentType = \"application/x-www-form-urlencoded\".toMediaType()\n\nfun Request.Builder.postForm(encodedForm: String) {\n    post(encodedForm.toRequestBody(formContentType))\n}\n\n@Suppress(\"unused\")\nfun Request.Builder.postForm(form: Map<String, String>, encoded: Boolean = false) {\n    val formBody = FormBody.Builder()\n    form.forEach {\n        if (encoded) {\n            formBody.addEncoded(it.key, it.value)\n        } else {\n            formBody.add(it.key, it.value)\n        }\n    }\n    post(formBody.build())\n}\n\nfun Request.Builder.postMultipart(type: String?, form: Map<String, Any>) {\n    val multipartBody = MultipartBody.Builder()\n    type?.let {\n        multipartBody.setType(type.toMediaType())\n    }\n    form.forEach {\n        when (val value = it.value) {\n            is Map<*, *> -> {\n                val fileName = value[\"fileName\"] as String\n                val file = value[\"file\"]\n                val mediaType = (value[\"contentType\"] as? String)?.toMediaType()\n                val requestBody = when (file) {\n                    is File -> {\n                        file.asRequestBody(mediaType)\n                    }\n\n                    is ByteArray -> {\n                        file.toRequestBody(mediaType)\n                    }\n\n                    is String -> {\n                        file.toRequestBody(mediaType)\n                    }\n\n                    else -> {\n                        GSON.toJson(file).toRequestBody(mediaType)\n                    }\n                }\n                multipartBody.addFormDataPart(it.key, fileName, requestBody)\n            }\n\n            else -> multipartBody.addFormDataPart(it.key, it.value.toString())\n        }\n    }\n    post(multipartBody.build())\n}\n\nfun Request.Builder.postJson(json: String?) {\n    json?.let {\n        val requestBody = json.toRequestBody(\"application/json; charset=UTF-8\".toMediaType())\n        post(requestBody)\n    }\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/help/http/OkhttpUncaughtExceptionHandler.kt",
    "content": "package io.legado.app.help.http\n\nimport io.legado.app.constant.AppLog\n\nobject OkhttpUncaughtExceptionHandler : Thread.UncaughtExceptionHandler {\n\n    override fun uncaughtException(t: Thread, e: Throwable) {\n        AppLog.put(\"Okhttp Dispatcher中的线程执行出错\\n${e.localizedMessage}\", e)\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/help/http/RequestMethod.kt",
    "content": "package io.legado.app.help.http\n\nenum class RequestMethod {\n    GET, POST\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/help/http/SSLHelper.kt",
    "content": "package io.legado.app.help.http\n\nimport android.annotation.SuppressLint\nimport android.net.http.X509TrustManagerExtensions\nimport io.legado.app.utils.printOnDebug\n\n\nimport java.io.IOException\nimport java.io.InputStream\nimport java.security.KeyManagementException\nimport java.security.KeyStore\nimport java.security.NoSuchAlgorithmException\nimport java.security.SecureRandom\nimport java.security.cert.CertificateException\nimport java.security.cert.CertificateFactory\nimport java.security.cert.X509Certificate\nimport javax.net.ssl.*\n\n@Suppress(\"unused\")\nobject SSLHelper {\n\n    /**\n     * 为了解决客户端不信任服务器数字证书的问题，\n     * 网络上大部分的解决方案都是让客户端不对证书做任何检查，\n     * 这是一种有很大安全漏洞的办法\n     */\n    val unsafeTrustManager: X509TrustManager =\n        @SuppressLint(\"CustomX509TrustManager\")\n        object : X509TrustManager {\n            @SuppressLint(\"TrustAllX509TrustManager\")\n            @Throws(CertificateException::class)\n            override fun checkClientTrusted(chain: Array<X509Certificate>, authType: String) {\n                //do nothing，接受任意客户端证书\n            }\n\n            @SuppressLint(\"TrustAllX509TrustManager\")\n            @Throws(CertificateException::class)\n            override fun checkServerTrusted(chain: Array<X509Certificate>, authType: String) {\n                //do nothing，接受任意客户端证书\n            }\n\n            fun checkServerTrusted(chain: Array<X509Certificate>, authType: String, host: String): List<X509Certificate> {\n                return chain.toList()\n            }\n\n            override fun getAcceptedIssuers(): Array<X509Certificate> {\n                return arrayOf()\n            }\n        }\n\n    val unsafeTrustManagerExtensions by lazy {\n        X509TrustManagerExtensions(unsafeTrustManager)\n    }\n\n    val unsafeSSLSocketFactory: SSLSocketFactory by lazy {\n        try {\n            val sslContext = SSLContext.getInstance(\"SSL\")\n            sslContext.init(null, arrayOf(unsafeTrustManager), SecureRandom())\n            sslContext.socketFactory\n        } catch (e: Exception) {\n            throw RuntimeException(e)\n        }\n    }\n\n    /**\n     * 此类是用于主机名验证的基接口。 在握手期间，如果 URL 的主机名和服务器的标识主机名不匹配，\n     * 则验证机制可以回调此接口的实现程序来确定是否应该允许此连接。策略可以是基于证书的或依赖于其他验证方案。\n     * 当验证 URL 主机名使用的默认规则失败时使用这些回调。如果主机名是可接受的，则返回 true\n     */\n    val unsafeHostnameVerifier: HostnameVerifier = HostnameVerifier { _, _ -> true }\n\n    class SSLParams {\n        lateinit var sSLSocketFactory: SSLSocketFactory\n        lateinit var trustManager: X509TrustManager\n    }\n\n    /**\n     * https单向认证\n     * 可以额外配置信任服务端的证书策略，否则默认是按CA证书去验证的，若不是CA可信任的证书，则无法通过验证\n     */\n    fun getSslSocketFactory(trustManager: X509TrustManager): SSLParams? {\n        return getSslSocketFactoryBase(trustManager, null, null)\n    }\n\n    /**\n     * https单向认证\n     * 用含有服务端公钥的证书校验服务端证书\n     */\n    fun getSslSocketFactory(vararg certificates: InputStream): SSLParams? {\n        return getSslSocketFactoryBase(null, null, null, *certificates)\n    }\n\n    /**\n     * https双向认证\n     * bksFile 和 password -> 客户端使用bks证书校验服务端证书\n     * certificates -> 用含有服务端公钥的证书校验服务端证书\n     */\n    fun getSslSocketFactory(\n        bksFile: InputStream,\n        password: String,\n        vararg certificates: InputStream\n    ): SSLParams? {\n        return getSslSocketFactoryBase(null, bksFile, password, *certificates)\n    }\n\n    /**\n     * https双向认证\n     * bksFile 和 password -> 客户端使用bks证书校验服务端证书\n     * X509TrustManager -> 如果需要自己校验，那么可以自己实现相关校验，如果不需要自己校验，那么传null即可\n     */\n    fun getSslSocketFactory(\n        bksFile: InputStream,\n        password: String,\n        trustManager: X509TrustManager\n    ): SSLParams? {\n        return getSslSocketFactoryBase(trustManager, bksFile, password)\n    }\n\n    private fun getSslSocketFactoryBase(\n        trustManager: X509TrustManager?,\n        bksFile: InputStream?,\n        password: String?,\n        vararg certificates: InputStream\n    ): SSLParams? {\n        val sslParams = SSLParams()\n        try {\n            val keyManagers = prepareKeyManager(bksFile, password)\n            val trustManagers = prepareTrustManager(*certificates)\n            val manager: X509TrustManager = trustManager ?: chooseTrustManager(trustManagers)\n            // 创建TLS类型的SSLContext对象， that uses our TrustManager\n            val sslContext = SSLContext.getInstance(\"TLS\")\n            // 用上面得到的trustManagers初始化SSLContext，这样sslContext就会信任keyStore中的证书\n            // 第一个参数是授权的密钥管理器，用来授权验证，比如授权自签名的证书验证。第二个是被授权的证书管理器，用来验证服务器端的证书\n            sslContext.init(keyManagers, arrayOf<TrustManager>(manager), null)\n            // 通过sslContext获取SSLSocketFactory对象\n            sslParams.sSLSocketFactory = sslContext.socketFactory\n            sslParams.trustManager = manager\n            return sslParams\n        } catch (e: NoSuchAlgorithmException) {\n            e.printOnDebug()\n        } catch (e: KeyManagementException) {\n            e.printOnDebug()\n        }\n        return null\n    }\n\n    private fun prepareKeyManager(bksFile: InputStream?, password: String?): Array<KeyManager>? {\n        try {\n            if (bksFile == null || password == null) return null\n            val clientKeyStore = KeyStore.getInstance(\"BKS\")\n            clientKeyStore.load(bksFile, password.toCharArray())\n            val kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm())\n            kmf.init(clientKeyStore, password.toCharArray())\n            return kmf.keyManagers\n        } catch (e: Exception) {\n            e.printOnDebug()\n        }\n        return null\n    }\n\n    private fun prepareTrustManager(vararg certificates: InputStream): Array<TrustManager> {\n        val certificateFactory = CertificateFactory.getInstance(\"X.509\")\n        // 创建一个默认类型的KeyStore，存储我们信任的证书\n        val keyStore = KeyStore.getInstance(KeyStore.getDefaultType())\n        keyStore.load(null)\n        for ((index, certStream) in certificates.withIndex()) {\n            val certificateAlias = index.toString()\n            // 证书工厂根据证书文件的流生成证书 cert\n            val cert = certificateFactory.generateCertificate(certStream)\n            // 将 cert 作为可信证书放入到keyStore中\n            keyStore.setCertificateEntry(certificateAlias, cert)\n            try {\n                certStream.close()\n            } catch (e: IOException) {\n                e.printOnDebug()\n            }\n        }\n        //我们创建一个默认类型的TrustManagerFactory\n        val tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm())\n        //用我们之前的keyStore实例初始化TrustManagerFactory，这样tmf就会信任keyStore中的证书\n        tmf.init(keyStore)\n        //通过tmf获取TrustManager数组，TrustManager也会信任keyStore中的证书\n        return tmf.trustManagers\n    }\n\n    private fun chooseTrustManager(trustManagers: Array<TrustManager>): X509TrustManager {\n        for (trustManager in trustManagers) {\n            if (trustManager is X509TrustManager) {\n                return trustManager\n            }\n        }\n        throw NullPointerException()\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/help/http/StrResponse.kt",
    "content": "package io.legado.app.help.http\n\nimport androidx.annotation.Keep\nimport okhttp3.Headers\nimport okhttp3.Protocol\nimport okhttp3.Request\nimport okhttp3.Response\nimport okhttp3.Response.Builder\nimport okhttp3.ResponseBody\n\n/**\n * An HTTP response.\n */\n@Keep\n@Suppress(\"unused\", \"MemberVisibilityCanBePrivate\")\nclass StrResponse {\n    var raw: Response\n        private set\n    var body: String? = null\n        private set\n    var errorBody: ResponseBody? = null\n        private set\n\n    constructor(rawResponse: Response, body: String?) {\n        this.raw = rawResponse\n        this.body = body\n    }\n\n    constructor(url: String, body: String?) {\n        val request = try {\n            Request.Builder().url(url).build()\n        } catch (e: Exception) {\n            Request.Builder().url(\"http://localhost/\").build()\n        }\n        raw = Builder()\n            .code(200)\n            .message(\"OK\")\n            .protocol(Protocol.HTTP_1_1)\n            .request(request)\n            .build()\n        this.body = body\n    }\n\n    constructor(rawResponse: Response, errorBody: ResponseBody?) {\n        this.raw = rawResponse\n        this.errorBody = errorBody\n    }\n\n    fun raw() = raw\n\n    fun url(): String {\n        raw.networkResponse?.let {\n            return it.request.url.toString()\n        }\n        return raw.request.url.toString()\n    }\n\n    val url: String get() = url()\n\n    fun body() = body\n\n    fun code(): Int {\n        return raw.code\n    }\n\n    fun message(): String {\n        return raw.message\n    }\n\n    fun headers(): Headers {\n        return raw.headers\n    }\n\n    fun isSuccessful(): Boolean = raw.isSuccessful\n\n    fun errorBody(): ResponseBody? {\n        return errorBody\n    }\n\n    override fun toString(): String {\n        return raw.toString()\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/help/http/api/CookieManagerInterface.kt",
    "content": "package io.legado.app.help.http.api\n\ninterface CookieManagerInterface {\n\n    /**\n     * 保存cookie\n     */\n    fun setCookie(url: String, cookie: String?)\n\n    /**\n     * 替换cookie\n     */\n    fun replaceCookie(url: String, cookie: String)\n\n    /**\n     * 获取cookie\n     */\n    fun getCookie(url: String): String\n\n    /**\n     * 移除cookie\n     */\n    fun removeCookie(url: String)\n\n    fun cookieToMap(cookie: String): MutableMap<String, String>\n\n    fun mapToCookie(cookieMap: Map<String, String>?): String?\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/help/rhino/NativeBaseSource.kt",
    "content": "package io.legado.app.help.rhino\n\nimport com.script.rhino.JavaObjectWrapFactory\nimport org.mozilla.javascript.NativeJavaObject\nimport org.mozilla.javascript.Scriptable\n\nclass NativeBaseSource(scope: Scriptable?, javaObject: Any, staticType: Class<*>?) :\n    NativeJavaObject(scope, javaObject, staticType) {\n\n    override fun has(name: String, start: Scriptable): Boolean {\n        if (name != \"setVariable\" && name.length > 3 && name.startsWith(\"set\")) {\n            val name = name.substring(3).replaceFirstChar { it.lowercase() }\n            if (super.has(name, start)) {\n                return false\n            }\n        }\n        return super.has(name, start)\n    }\n\n    override fun get(name: String, start: Scriptable): Any? {\n        if (name != \"setVariable\" && name.length > 3 && name.startsWith(\"set\")) {\n            val name = name.substring(3).replaceFirstChar { it.lowercase() }\n            if (super.has(name, start)) {\n                return NOT_FOUND\n            }\n        }\n        return super.get(name, start)\n    }\n\n    override fun put(\n        name: String,\n        start: Scriptable,\n        value: Any?\n    ) {\n        if (name == \"variable\") {\n            super.put(name, start, value)\n        }\n    }\n\n    companion object {\n        val factory = JavaObjectWrapFactory { scope, javaObject, staticType ->\n            NativeBaseSource(scope, javaObject, staticType)\n        }\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/help/source/BaseSourceExtensions.kt",
    "content": "package io.legado.app.help.source\n\nimport io.legado.app.constant.SourceType\nimport io.legado.app.data.entities.BaseSource\nimport io.legado.app.data.entities.BookSource\nimport io.legado.app.data.entities.RssSource\nimport io.legado.app.model.SharedJsScope\nimport org.mozilla.javascript.Scriptable\nimport kotlin.coroutines.CoroutineContext\n\nfun BaseSource.getShareScope(coroutineContext: CoroutineContext? = null): Scriptable? {\n    return SharedJsScope.getScope(jsLib, coroutineContext)\n}\n\nfun BaseSource.getSourceType(): Int {\n    return when (this) {\n        is BookSource -> SourceType.book\n        is RssSource -> SourceType.rss\n        else -> error(\"unknown source type: ${this::class.simpleName}.\")\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/help/source/BookSourceExtensions.kt",
    "content": "package io.legado.app.help.source\n\nimport com.script.rhino.runScriptWithContext\nimport io.legado.app.constant.BookSourceType\nimport io.legado.app.constant.BookType\nimport io.legado.app.data.entities.BookSource\nimport io.legado.app.data.entities.BookSourcePart\nimport io.legado.app.data.entities.rule.ExploreKind\nimport io.legado.app.utils.ACache\nimport io.legado.app.utils.GSON\nimport io.legado.app.utils.MD5Utils\nimport io.legado.app.utils.fromJsonArray\nimport io.legado.app.utils.isJsonArray\nimport io.legado.app.utils.printOnDebug\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.sync.Mutex\nimport kotlinx.coroutines.sync.withLock\nimport kotlinx.coroutines.withContext\nimport java.util.concurrent.ConcurrentHashMap\n\n/**\n * 采用md5作为key可以在分类修改后自动重新计算,不需要手动刷新\n */\n\nprivate val mutexMap by lazy { hashMapOf<String, Mutex>() }\nprivate val exploreKindsMap by lazy { ConcurrentHashMap<String, List<ExploreKind>>() }\nprivate val aCache by lazy { ACache.get(\"explore\") }\n\nprivate fun BookSource.getExploreKindsKey(): String {\n    return MD5Utils.md5Encode(bookSourceUrl + exploreUrl)\n}\n\nprivate fun BookSourcePart.getExploreKindsKey(): String {\n    return getBookSource()!!.getExploreKindsKey()\n}\n\nsuspend fun BookSourcePart.exploreKinds(): List<ExploreKind> {\n    return getBookSource()!!.exploreKinds()\n}\n\nsuspend fun BookSource.exploreKinds(): List<ExploreKind> {\n    val exploreKindsKey = getExploreKindsKey()\n    exploreKindsMap[exploreKindsKey]?.let { return it }\n    val exploreUrl = exploreUrl\n    if (exploreUrl.isNullOrBlank()) {\n        return emptyList()\n    }\n    val mutex = mutexMap[bookSourceUrl] ?: Mutex().apply { mutexMap[bookSourceUrl] = this }\n    mutex.withLock {\n        exploreKindsMap[exploreKindsKey]?.let { return it }\n        val kinds = arrayListOf<ExploreKind>()\n        withContext(Dispatchers.IO) {\n            kotlin.runCatching {\n                var ruleStr = exploreUrl\n                if (exploreUrl.startsWith(\"<js>\", true)\n                    || exploreUrl.startsWith(\"@js:\", true)\n                ) {\n                    ruleStr = aCache.getAsString(exploreKindsKey)\n                    if (ruleStr.isNullOrBlank()) {\n                        val jsStr = if (exploreUrl.startsWith(\"@\")) {\n                            exploreUrl.substring(4)\n                        } else {\n                            exploreUrl.substring(4, exploreUrl.lastIndexOf(\"<\"))\n                        }\n                        ruleStr = runScriptWithContext {\n                            evalJS(jsStr).toString().trim()\n                        }\n                        aCache.put(exploreKindsKey, ruleStr)\n                    }\n                }\n                if (ruleStr.isJsonArray()) {\n                    GSON.fromJsonArray<ExploreKind>(ruleStr).getOrThrow().let {\n                        kinds.addAll(it)\n                    }\n                } else {\n                    ruleStr.split(\"(&&|\\n)+\".toRegex()).forEach { kindStr ->\n                        val kindCfg = kindStr.split(\"::\")\n                        kinds.add(ExploreKind(kindCfg.first(), kindCfg.getOrNull(1)))\n                    }\n                }\n            }.onFailure {\n                kinds.add(ExploreKind(\"ERROR:${it.localizedMessage}\", it.stackTraceToString()))\n                it.printOnDebug()\n            }\n        }\n        exploreKindsMap[exploreKindsKey] = kinds\n        return kinds\n    }\n}\n\nsuspend fun BookSourcePart.clearExploreKindsCache() {\n    withContext(Dispatchers.IO) {\n        val exploreKindsKey = getExploreKindsKey()\n        aCache.remove(exploreKindsKey)\n        exploreKindsMap.remove(exploreKindsKey)\n    }\n}\n\nsuspend fun BookSource.clearExploreKindsCache() {\n    withContext(Dispatchers.IO) {\n        val exploreKindsKey = getExploreKindsKey()\n        aCache.remove(exploreKindsKey)\n        exploreKindsMap.remove(exploreKindsKey)\n    }\n}\n\nfun BookSource.exploreKindsJson(): String {\n    val exploreKindsKey = getExploreKindsKey()\n    return aCache.getAsString(exploreKindsKey)?.takeIf { it.isJsonArray() }\n        ?: exploreUrl.takeIf { it.isJsonArray() }\n        ?: \"\"\n}\n\nfun BookSource.getBookType(): Int {\n    return when (bookSourceType) {\n        BookSourceType.file -> BookType.text or BookType.webFile\n        BookSourceType.image -> BookType.image\n        BookSourceType.audio -> BookType.audio\n        else -> BookType.text\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/help/source/RssSourceExtensions.kt",
    "content": "package io.legado.app.help.source\n\nimport io.legado.app.data.entities.RssSource\nimport io.legado.app.utils.ACache\nimport io.legado.app.utils.MD5Utils\nimport io.legado.app.utils.NetworkUtils\nimport com.script.rhino.runScriptWithContext\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.withContext\n\nprivate val aCache by lazy { ACache.get(\"rssSortUrl\") }\n\nprivate fun RssSource.getSortUrlsKey(): String {\n    return MD5Utils.md5Encode(sourceUrl + sortUrl)\n}\n\nsuspend fun RssSource.sortUrls(): List<Pair<String, String>> {\n    return arrayListOf<Pair<String, String>>().apply {\n        val sortUrlsKey = getSortUrlsKey()\n        withContext(Dispatchers.IO) {\n            kotlin.runCatching {\n                var str = sortUrl\n                if (sortUrl?.startsWith(\"<js>\", false) == true\n                    || sortUrl?.startsWith(\"@js:\", false) == true\n                ) {\n                    str = aCache.getAsString(sortUrlsKey)\n                    if (str.isNullOrBlank()) {\n                        val jsStr = if (sortUrl!!.startsWith(\"@\")) {\n                            sortUrl!!.substring(4)\n                        } else {\n                            sortUrl!!.substring(4, sortUrl!!.lastIndexOf(\"<\"))\n                        }\n                        str = runScriptWithContext {\n                            evalJS(jsStr).toString()\n                        }\n                        aCache.put(sortUrlsKey, str)\n                    }\n                }\n                str?.split(\"(&&|\\n)+\".toRegex())?.forEach { sort ->\n                    val name = sort.substringBefore(\"::\")\n                    val url = sort.substringAfter(\"::\", \"\")\n                    if (url.isNotEmpty()) {\n                        if (url.startsWith(\"{{\")) {\n                            add(Pair(name, url))\n                        } else {\n                            add(Pair(name, NetworkUtils.getAbsoluteURL(sourceUrl, url)))\n                        }\n                    }\n                }\n                if (isEmpty()) {\n                    add(Pair(\"\", sourceUrl))\n                }\n            }\n        }\n    }\n}\n\nsuspend fun RssSource.removeSortCache() {\n    withContext(Dispatchers.IO) {\n        aCache.remove(getSortUrlsKey())\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/help/source/SourceHelp.kt",
    "content": "package io.legado.app.help.source\n\nimport io.legado.app.constant.SourceType\nimport io.legado.app.data.appDb\nimport io.legado.app.data.entities.BaseSource\nimport io.legado.app.data.entities.BookSource\nimport io.legado.app.data.entities.BookSourcePart\nimport io.legado.app.data.entities.RssSource\nimport io.legado.app.help.AppCacheManager\nimport io.legado.app.help.config.SourceConfig\nimport io.legado.app.help.coroutine.Coroutine\nimport io.legado.app.model.AudioPlay\nimport io.legado.app.model.ReadBook\nimport io.legado.app.model.ReadManga\nimport io.legado.app.utils.EncoderUtils\nimport io.legado.app.utils.NetworkUtils\nimport io.legado.app.utils.splitNotBlank\nimport io.legado.app.utils.toastOnUi\nimport splitties.init.appCtx\n\nobject SourceHelp {\n\n    private val list18Plus by lazy {\n        try {\n            return@lazy String(appCtx.assets.open(\"18PlusList.txt\").readBytes())\n                .splitNotBlank(\"\\n\").map {\n                    EncoderUtils.base64Decode(it)\n                }.toHashSet()\n        } catch (_: Exception) {\n            return@lazy emptySet()\n        }\n    }\n\n    fun getSource(key: String?): BaseSource? {\n        key ?: return null\n        if (ReadBook.bookSource?.bookSourceUrl == key) {\n            return ReadBook.bookSource\n        } else if (AudioPlay.bookSource?.bookSourceUrl == key) {\n            return AudioPlay.bookSource\n        } else if (ReadManga.bookSource?.bookSourceUrl == key) {\n            return ReadManga.bookSource\n        }\n        return appDb.bookSourceDao.getBookSource(key)\n            ?: appDb.rssSourceDao.getByKey(key)\n    }\n\n    fun getSource(key: String?, @SourceType.Type type: Int): BaseSource? {\n        key ?: return null\n        return when (type) {\n            SourceType.book -> appDb.bookSourceDao.getBookSource(key)\n            SourceType.rss -> appDb.rssSourceDao.getByKey(key)\n            else -> null\n        }\n    }\n\n    fun deleteSource(key: String, @SourceType.Type type: Int) {\n        when (type) {\n            SourceType.book -> deleteBookSource(key)\n            SourceType.rss -> deleteRssSource(key)\n        }\n    }\n\n    fun deleteBookSourceParts(sources: List<BookSourcePart>) {\n        appDb.runInTransaction {\n            sources.forEach {\n                deleteBookSourceInternal(it.bookSourceUrl)\n            }\n        }\n        AppCacheManager.clearSourceVariables()\n    }\n\n    fun deleteBookSources(sources: List<BookSource>) {\n        appDb.runInTransaction {\n            sources.forEach {\n                deleteBookSourceInternal(it.bookSourceUrl)\n            }\n        }\n        AppCacheManager.clearSourceVariables()\n    }\n\n    private fun deleteBookSourceInternal(key: String) {\n        appDb.bookSourceDao.delete(key)\n        appDb.cacheDao.deleteSourceVariables(key)\n        SourceConfig.removeSource(key)\n    }\n\n    fun deleteBookSource(key: String) {\n        deleteBookSourceInternal(key)\n        AppCacheManager.clearSourceVariables()\n    }\n\n    fun deleteRssSources(sources: List<RssSource>) {\n        appDb.runInTransaction {\n            sources.forEach {\n                deleteRssSourceInternal(it.sourceUrl)\n            }\n        }\n        AppCacheManager.clearSourceVariables()\n    }\n\n    private fun deleteRssSourceInternal(key: String) {\n        appDb.rssSourceDao.delete(key)\n        appDb.rssArticleDao.delete(key)\n        appDb.cacheDao.deleteSourceVariables(key)\n    }\n\n    fun deleteRssSource(key: String) {\n        deleteRssSourceInternal(key)\n        AppCacheManager.clearSourceVariables()\n    }\n\n    fun enableSource(key: String, @SourceType.Type type: Int, enable: Boolean) {\n        when (type) {\n            SourceType.book -> appDb.bookSourceDao.enable(key, enable)\n            SourceType.rss -> appDb.rssSourceDao.enable(key, enable)\n        }\n    }\n\n    fun insertRssSource(vararg rssSources: RssSource) {\n        val rssSourcesGroup = rssSources.groupBy {\n            is18Plus(it.sourceUrl)\n        }\n        rssSourcesGroup[true]?.forEach {\n            appCtx.toastOnUi(\"${it.sourceName}是18+网址,禁止导入.\")\n        }\n        rssSourcesGroup[false]?.let {\n            appDb.rssSourceDao.insert(*it.toTypedArray())\n        }\n    }\n\n    fun insertBookSource(vararg bookSources: BookSource) {\n        val bookSourcesGroup = bookSources.groupBy {\n            is18Plus(it.bookSourceUrl)\n        }\n        bookSourcesGroup[true]?.forEach {\n            appCtx.toastOnUi(\"${it.bookSourceName}是18+网址,禁止导入.\")\n        }\n        bookSourcesGroup[false]?.let {\n            appDb.bookSourceDao.insert(*it.toTypedArray())\n        }\n        Coroutine.async {\n            adjustSortNumber()\n        }\n    }\n\n    private fun is18Plus(url: String?): Boolean {\n        if (list18Plus.isEmpty()) {\n            return false\n        }\n        url ?: return false\n        val baseUrl = NetworkUtils.getBaseUrl(url) ?: return false\n        kotlin.runCatching {\n            val host = baseUrl.split(\"//\", \".\").let {\n                if (it.size > 2) \"${it[it.lastIndex - 1]}.${it.last()}\" else return false\n            }\n            return list18Plus.contains(host)\n        }\n        return false\n    }\n\n    /**\n     * 调整排序序号\n     */\n    fun adjustSortNumber() {\n        if (\n            appDb.bookSourceDao.maxOrder > 99999\n            || appDb.bookSourceDao.minOrder < -99999\n            || appDb.bookSourceDao.hasDuplicateOrder\n        ) {\n            val sources = appDb.bookSourceDao.allPart\n            sources.forEachIndexed { index, bookSource ->\n                bookSource.customOrder = index\n            }\n            appDb.bookSourceDao.upOrder(sources)\n        }\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/help/source/SourceVerificationHelp.kt",
    "content": "package io.legado.app.help.source\n\nimport io.legado.app.constant.AppLog\nimport io.legado.app.data.entities.BaseSource\nimport io.legado.app.exception.NoStackTraceException\nimport io.legado.app.help.CacheManager\nimport io.legado.app.help.IntentData\nimport io.legado.app.ui.association.VerificationCodeActivity\nimport io.legado.app.ui.browser.WebViewActivity\nimport io.legado.app.utils.isMainThread\nimport io.legado.app.utils.startActivity\nimport splitties.init.appCtx\nimport java.util.concurrent.locks.LockSupport\nimport kotlin.time.Duration.Companion.minutes\n\n/**\n * 源验证\n */\nobject SourceVerificationHelp {\n\n    private val waitTime = 1.minutes.inWholeNanoseconds\n\n    private fun getVerificationResultKey(source: BaseSource) =\n        getVerificationResultKey(source.getKey())\n\n    private fun getVerificationResultKey(sourceKey: String) = \"${sourceKey}_verificationResult\"\n\n    /**\n     * 获取书源验证结果\n     * 图片验证码 防爬 滑动验证码 点击字符 等等\n     */\n    @Synchronized\n    fun getVerificationResult(\n        source: BaseSource?,\n        url: String,\n        title: String,\n        useBrowser: Boolean,\n        refetchAfterSuccess: Boolean = true\n    ): String {\n        source\n            ?: throw NoStackTraceException(\"getVerificationResult parameter source cannot be null\")\n        require(url.length < 64 * 1024) { \"getVerificationResult parameter url too long\" }\n        check(!isMainThread) { \"getVerificationResult must be called on a background thread\" }\n\n        clearResult(source.getKey())\n\n        if (!useBrowser) {\n            appCtx.startActivity<VerificationCodeActivity> {\n                putExtra(\"imageUrl\", url)\n                putExtra(\"sourceOrigin\", source.getKey())\n                putExtra(\"sourceName\", source.getTag())\n                putExtra(\"sourceType\", source.getSourceType())\n                IntentData.put(getVerificationResultKey(source), Thread.currentThread())\n            }\n        } else {\n            startBrowser(source, url, title, true, refetchAfterSuccess)\n        }\n\n        var waitUserInput = false\n        while (getResult(source.getKey()) == null) {\n            if (!waitUserInput) {\n                AppLog.putDebug(\"等待返回验证结果...\")\n                waitUserInput = true\n            }\n            LockSupport.parkNanos(this, waitTime)\n        }\n\n        val result = getResult(source.getKey())!!\n        clearResult(source.getKey())\n        result.ifBlank {\n            throw NoStackTraceException(\"验证结果为空\")\n        }\n\n        return result\n    }\n\n    /**\n     * 启动内置浏览器\n     * @param saveResult 保存网页源代码到数据库\n     */\n    fun startBrowser(\n        source: BaseSource?,\n        url: String,\n        title: String,\n        saveResult: Boolean? = false,\n        refetchAfterSuccess: Boolean? = true\n    ) {\n        source ?: throw NoStackTraceException(\"startBrowser parameter source cannot be null\")\n        require(url.length < 64 * 1024) { \"startBrowser parameter url too long\" }\n        appCtx.startActivity<WebViewActivity> {\n            putExtra(\"title\", title)\n            putExtra(\"url\", url)\n            putExtra(\"sourceOrigin\", source.getKey())\n            putExtra(\"sourceName\", source.getTag())\n            putExtra(\"sourceType\", source.getSourceType())\n            putExtra(\"sourceVerificationEnable\", saveResult)\n            putExtra(\"refetchAfterSuccess\", refetchAfterSuccess)\n            IntentData.put(getVerificationResultKey(source), Thread.currentThread())\n        }\n    }\n\n\n    fun checkResult(sourceKey: String) {\n        getResult(sourceKey) ?: setResult(sourceKey, \"\")\n        val thread = IntentData.get<Thread>(getVerificationResultKey(sourceKey))\n        LockSupport.unpark(thread)\n    }\n\n    fun setResult(sourceKey: String, result: String?) {\n        CacheManager.putMemory(getVerificationResultKey(sourceKey), result ?: \"\")\n    }\n\n    fun getResult(sourceKey: String): String? {\n        return CacheManager.getFromMemory(getVerificationResultKey(sourceKey)) as? String\n    }\n\n    fun clearResult(sourceKey: String) {\n        CacheManager.delete(getVerificationResultKey(sourceKey))\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/help/storage/Backup.kt",
    "content": "package io.legado.app.help.storage\n\nimport android.content.Context\nimport android.net.Uri\nimport androidx.core.net.toUri\nimport androidx.documentfile.provider.DocumentFile\nimport io.legado.app.constant.AppLog\nimport io.legado.app.constant.PreferKey\nimport io.legado.app.data.appDb\nimport io.legado.app.exception.NoStackTraceException\nimport io.legado.app.help.AppWebDav\nimport io.legado.app.help.DirectLinkUpload\nimport io.legado.app.help.config.AppConfig\nimport io.legado.app.help.config.LocalConfig\nimport io.legado.app.help.config.ReadBookConfig\nimport io.legado.app.help.config.ThemeConfig\nimport io.legado.app.help.coroutine.Coroutine\nimport io.legado.app.model.BookCover\nimport io.legado.app.utils.FileUtils\nimport io.legado.app.utils.GSON\nimport io.legado.app.utils.LogUtils\nimport io.legado.app.utils.compress.ZipUtils\nimport io.legado.app.utils.createFolderIfNotExist\nimport io.legado.app.utils.defaultSharedPreferences\nimport io.legado.app.utils.externalFiles\nimport io.legado.app.utils.getFile\nimport io.legado.app.utils.getSharedPreferences\nimport io.legado.app.utils.isContentScheme\nimport io.legado.app.utils.normalizeFileName\nimport io.legado.app.utils.openOutputStream\nimport io.legado.app.utils.outputStream\nimport io.legado.app.utils.writeToOutputStream\nimport kotlinx.coroutines.Dispatchers.IO\nimport kotlinx.coroutines.currentCoroutineContext\nimport kotlinx.coroutines.ensureActive\nimport kotlinx.coroutines.sync.Mutex\nimport kotlinx.coroutines.sync.withLock\nimport kotlinx.coroutines.withContext\nimport splitties.init.appCtx\nimport java.io.File\nimport java.io.FileInputStream\nimport java.io.FileOutputStream\nimport java.text.SimpleDateFormat\nimport java.util.Date\nimport java.util.Locale\nimport java.util.concurrent.TimeUnit\n\n/**\n * 备份\n */\nobject Backup {\n\n    val backupPath: String by lazy {\n        appCtx.filesDir.getFile(\"backup\").createFolderIfNotExist().absolutePath\n    }\n    val zipFilePath = \"${appCtx.externalFiles.absolutePath}${File.separator}tmp_backup.zip\"\n\n    private const val TAG = \"Backup\"\n\n    private val mutex = Mutex()\n\n    private val backupFileNames by lazy {\n        arrayOf(\n            \"bookshelf.json\",\n            \"bookmark.json\",\n            \"bookGroup.json\",\n            \"bookSource.json\",\n            \"rssSources.json\",\n            \"rssStar.json\",\n            \"replaceRule.json\",\n            \"readRecord.json\",\n            \"searchHistory.json\",\n            \"sourceSub.json\",\n            \"txtTocRule.json\",\n            \"httpTTS.json\",\n            \"keyboardAssists.json\",\n            \"dictRule.json\",\n            \"servers.json\",\n            DirectLinkUpload.ruleFileName,\n            ReadBookConfig.configFileName,\n            ReadBookConfig.shareConfigFileName,\n            ThemeConfig.configFileName,\n            BookCover.configFileName,\n            \"config.xml\"\n        )\n    }\n\n    private fun getNowZipFileName(): String {\n        val backupDate = SimpleDateFormat(\"yyyy-MM-dd\", Locale.getDefault())\n            .format(Date(System.currentTimeMillis()))\n        val deviceName = AppConfig.webDavDeviceName\n        return if (deviceName?.isNotBlank() == true) {\n            \"backup${backupDate}-${deviceName}.zip\"\n        } else {\n            \"backup${backupDate}.zip\"\n        }.normalizeFileName()\n    }\n\n    private fun shouldBackup(): Boolean {\n        val lastBackup = LocalConfig.lastBackup\n        return lastBackup + TimeUnit.DAYS.toMillis(1) < System.currentTimeMillis()\n    }\n\n    fun autoBack(context: Context) {\n        if (shouldBackup()) {\n            Coroutine.async {\n                mutex.withLock {\n                    if (shouldBackup()) {\n                        val backupZipFileName = getNowZipFileName()\n                        if (!AppWebDav.hasBackUp(backupZipFileName)) {\n                            backup(context, AppConfig.backupPath)\n                        } else {\n                            LocalConfig.lastBackup = System.currentTimeMillis()\n                        }\n                    }\n                }\n            }.onError {\n                AppLog.put(\"自动备份失败\\n${it.localizedMessage}\")\n            }\n        }\n    }\n\n    suspend fun backupLocked(context: Context, path: String?) {\n        mutex.withLock {\n            withContext(IO) {\n                backup(context, path)\n            }\n        }\n    }\n\n    private suspend fun backup(context: Context, path: String?) {\n        LogUtils.d(TAG, \"开始备份 path:$path\")\n        LocalConfig.lastBackup = System.currentTimeMillis()\n        val aes = BackupAES()\n        FileUtils.delete(backupPath)\n        writeListToJson(appDb.bookDao.all, \"bookshelf.json\", backupPath)\n        writeListToJson(appDb.bookmarkDao.all, \"bookmark.json\", backupPath)\n        writeListToJson(appDb.bookGroupDao.all, \"bookGroup.json\", backupPath)\n        writeListToJson(appDb.bookSourceDao.all, \"bookSource.json\", backupPath)\n        writeListToJson(appDb.rssSourceDao.all, \"rssSources.json\", backupPath)\n        writeListToJson(appDb.rssStarDao.all, \"rssStar.json\", backupPath)\n        writeListToJson(appDb.replaceRuleDao.all, \"replaceRule.json\", backupPath)\n        writeListToJson(appDb.readRecordDao.all, \"readRecord.json\", backupPath)\n        writeListToJson(appDb.searchKeywordDao.all, \"searchHistory.json\", backupPath)\n        writeListToJson(appDb.ruleSubDao.all, \"sourceSub.json\", backupPath)\n        writeListToJson(appDb.txtTocRuleDao.all, \"txtTocRule.json\", backupPath)\n        writeListToJson(appDb.httpTTSDao.all, \"httpTTS.json\", backupPath)\n        writeListToJson(appDb.keyboardAssistsDao.all, \"keyboardAssists.json\", backupPath)\n        writeListToJson(appDb.dictRuleDao.all, \"dictRule.json\", backupPath)\n        GSON.toJson(appDb.serverDao.all).let { json ->\n            aes.runCatching {\n                encryptBase64(json)\n            }.getOrDefault(json).let {\n                FileUtils.createFileIfNotExist(backupPath + File.separator + \"servers.json\")\n                    .writeText(it)\n            }\n        }\n        currentCoroutineContext().ensureActive()\n        GSON.toJson(ReadBookConfig.configList).let {\n            FileUtils.createFileIfNotExist(backupPath + File.separator + ReadBookConfig.configFileName)\n                .writeText(it)\n        }\n        GSON.toJson(ReadBookConfig.shareConfig).let {\n            FileUtils.createFileIfNotExist(backupPath + File.separator + ReadBookConfig.shareConfigFileName)\n                .writeText(it)\n        }\n        GSON.toJson(ThemeConfig.configList).let {\n            FileUtils.createFileIfNotExist(backupPath + File.separator + ThemeConfig.configFileName)\n                .writeText(it)\n        }\n        DirectLinkUpload.getConfig()?.let {\n            FileUtils.createFileIfNotExist(backupPath + File.separator + DirectLinkUpload.ruleFileName)\n                .writeText(GSON.toJson(it))\n        }\n        BookCover.getConfig()?.let {\n            FileUtils.createFileIfNotExist(backupPath + File.separator + BookCover.configFileName)\n                .writeText(GSON.toJson(it))\n        }\n        currentCoroutineContext().ensureActive()\n        appCtx.getSharedPreferences(backupPath, \"config\")?.let { sp ->\n            val edit = sp.edit()\n            appCtx.defaultSharedPreferences.all.forEach { (key, value) ->\n                if (BackupConfig.keyIsNotIgnore(key)) {\n                    when (key) {\n                        PreferKey.webDavPassword -> {\n                            edit.putString(key, aes.runCatching {\n                                encryptBase64(value.toString())\n                            }.getOrDefault(value.toString()))\n                        }\n\n                        else -> when (value) {\n                            is Int -> edit.putInt(key, value)\n                            is Boolean -> edit.putBoolean(key, value)\n                            is Long -> edit.putLong(key, value)\n                            is Float -> edit.putFloat(key, value)\n                            is String -> edit.putString(key, value)\n                        }\n                    }\n                }\n            }\n            edit.commit()\n        }\n        currentCoroutineContext().ensureActive()\n        val zipFileName = getNowZipFileName()\n        val paths = arrayListOf(*backupFileNames)\n        for (i in 0 until paths.size) {\n            paths[i] = backupPath + File.separator + paths[i]\n        }\n        FileUtils.delete(zipFilePath)\n        FileUtils.delete(zipFilePath.replace(\"tmp_\", \"\"))\n        val backupFileName = if (AppConfig.onlyLatestBackup) {\n            \"backup.zip\"\n        } else {\n            zipFileName\n        }\n        if (ZipUtils.zipFiles(paths, zipFilePath)) {\n            when {\n                path.isNullOrBlank() -> {\n                    copyBackup(context.getExternalFilesDir(null)!!, backupFileName)\n                }\n\n                path.isContentScheme() -> {\n                    copyBackup(context, path.toUri(), backupFileName)\n                }\n\n                else -> {\n                    copyBackup(File(path), backupFileName)\n                }\n            }\n            try {\n                AppWebDav.backUpWebDav(zipFileName)\n            } catch (e: Exception) {\n                AppLog.put(\"上传备份至webdav失败\\n$e\", e)\n            }\n        }\n        FileUtils.delete(backupPath)\n        FileUtils.delete(zipFilePath)\n        currentCoroutineContext().ensureActive()\n        ReadBookConfig.getAllPicBgStr().map {\n            if (it.contains(File.separator)) {\n                File(it)\n            } else {\n                appCtx.externalFiles.getFile(\"bg\", it)\n            }\n        }.let {\n            AppWebDav.upBgs(it.toTypedArray())\n        }\n    }\n\n    private suspend fun writeListToJson(list: List<Any>, fileName: String, path: String) {\n        currentCoroutineContext().ensureActive()\n        withContext(IO) {\n            if (list.isNotEmpty()) {\n                LogUtils.d(TAG, \"阅读备份 $fileName 列表大小 ${list.size}\")\n                val file = FileUtils.createFileIfNotExist(path + File.separator + fileName)\n                file.outputStream().buffered().use {\n                    GSON.writeToOutputStream(it, list)\n                }\n                LogUtils.d(TAG, \"阅读备份 $fileName 写入大小 ${file.length()}\")\n            } else {\n                LogUtils.d(TAG, \"阅读备份 $fileName 列表为空\")\n            }\n        }\n    }\n\n    @Throws(Exception::class)\n    @Suppress(\"SameParameterValue\")\n    private fun copyBackup(context: Context, uri: Uri, fileName: String) {\n        val treeDoc = DocumentFile.fromTreeUri(context, uri)!!\n        treeDoc.findFile(fileName)?.delete()\n        val fileDoc = treeDoc.createFile(\"\", fileName)\n            ?: throw NoStackTraceException(\"创建文件失败\")\n        val outputS = fileDoc.openOutputStream()\n            ?: throw NoStackTraceException(\"打开OutputStream失败\")\n        outputS.use {\n            FileInputStream(zipFilePath).use { inputS ->\n                inputS.copyTo(outputS)\n            }\n        }\n    }\n\n    @Throws(Exception::class)\n    @Suppress(\"SameParameterValue\")\n    private fun copyBackup(rootFile: File, fileName: String) {\n        FileInputStream(File(zipFilePath)).use { inputS ->\n            val file = FileUtils.createFileIfNotExist(rootFile, fileName)\n            FileOutputStream(file).use { outputS ->\n                inputS.copyTo(outputS)\n            }\n        }\n    }\n\n    fun clearCache() {\n        FileUtils.delete(backupPath)\n        FileUtils.delete(zipFilePath)\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/help/storage/BackupAES.kt",
    "content": "package io.legado.app.help.storage\n\nimport cn.hutool.crypto.symmetric.AES\nimport io.legado.app.help.config.LocalConfig\nimport io.legado.app.utils.MD5Utils\n\nclass BackupAES : AES(\n    MD5Utils.md5Encode(LocalConfig.password ?: \"\").encodeToByteArray(0, 16)\n)"
  },
  {
    "path": "app/src/main/java/io/legado/app/help/storage/BackupConfig.kt",
    "content": "package io.legado.app.help.storage\n\nimport io.legado.app.R\nimport io.legado.app.constant.PreferKey\nimport io.legado.app.utils.FileUtils\nimport io.legado.app.utils.GSON\nimport io.legado.app.utils.fromJsonObject\nimport splitties.init.appCtx\n\n/**\n * 备份配置\n */\n@Suppress(\"ConstPropertyName\")\nobject BackupConfig {\n\n    private val ignoreConfigPath = FileUtils.getPath(appCtx.filesDir, \"restoreIgnore.json\")\n    val ignoreConfig: HashMap<String, Boolean> by lazy {\n        val file = FileUtils.createFileIfNotExist(ignoreConfigPath)\n        val json = file.readText()\n        GSON.fromJsonObject<HashMap<String, Boolean>>(json).getOrNull() ?: hashMapOf()\n    }\n\n    private const val readConfigKey = \"readConfig\"\n    private const val themeConfigKey = \"themeConfig\"\n    private const val coverConfigKey = \"coverConfig\"\n    private const val localBookKey = \"localBook\"\n\n    //配置忽略key\n    val ignoreKeys = arrayOf(\n        readConfigKey,\n        PreferKey.themeMode,\n        themeConfigKey,\n        coverConfigKey,\n        PreferKey.bookshelfLayout,\n        PreferKey.showRss,\n        PreferKey.threadCount,\n        localBookKey\n    )\n\n    //配置忽略标题\n    val ignoreTitle = arrayOf(\n        appCtx.getString(R.string.read_config),\n        appCtx.getString(R.string.theme_mode),\n        appCtx.getString(R.string.theme_config),\n        appCtx.getString(R.string.cover_config),\n        appCtx.getString(R.string.bookshelf_layout),\n        appCtx.getString(R.string.show_rss),\n        appCtx.getString(R.string.thread_count),\n        appCtx.getString(R.string.local_book)\n    )\n\n    //自动忽略keys\n    private val ignorePrefKeys = arrayOf(\n        PreferKey.defaultCover,\n        PreferKey.defaultCoverDark,\n        PreferKey.backupPath,\n        PreferKey.defaultBookTreeUri,\n        PreferKey.webDavDeviceName,\n        PreferKey.launcherIcon,\n        PreferKey.bitmapCacheSize,\n        PreferKey.webServiceWakeLock,\n        PreferKey.readAloudWakeLock,\n        PreferKey.audioPlayWakeLock\n    )\n\n    //阅读配置\n    private val readPrefKeys = arrayOf(\n        PreferKey.readStyleSelect,\n        PreferKey.comicStyleSelect,\n        PreferKey.shareLayout,\n        PreferKey.hideStatusBar,\n        PreferKey.hideNavigationBar,\n        PreferKey.autoReadSpeed,\n        PreferKey.clickActionTL,\n        PreferKey.clickActionTC,\n        PreferKey.clickActionTR,\n        PreferKey.clickActionML,\n        PreferKey.clickActionMC,\n        PreferKey.clickActionMR,\n        PreferKey.clickActionBL,\n        PreferKey.clickActionBC,\n        PreferKey.clickActionBR\n    )\n\n    private val themePrefKeys = arrayOf(\n        PreferKey.cPrimary,\n        PreferKey.cAccent,\n        PreferKey.cBackground,\n        PreferKey.cBBackground,\n        PreferKey.bgImage,\n        PreferKey.bgImageBlurring,\n        PreferKey.cNPrimary,\n        PreferKey.cNAccent,\n        PreferKey.cNBackground,\n        PreferKey.cNBBackground,\n        PreferKey.bgImageN,\n        PreferKey.bgImageNBlurring\n    )\n\n    private val coverPrefKeys = arrayOf(\n        PreferKey.useDefaultCover,\n        PreferKey.loadCoverOnlyWifi,\n        PreferKey.coverShowName,\n        PreferKey.coverShowAuthor,\n        PreferKey.coverShowNameN,\n        PreferKey.coverShowAuthorN\n    )\n\n    fun keyIsNotIgnore(key: String): Boolean {\n        return when {\n            ignorePrefKeys.contains(key) -> false\n            ignoreReadConfig && readPrefKeys.contains(key) -> false\n            ignoreThemeConfig && themePrefKeys.contains(key) -> false\n            ignoreCoverConfig && coverPrefKeys.contains(key) -> false\n            PreferKey.themeMode == key && ignoreThemeMode -> false\n            PreferKey.bookshelfLayout == key && ignoreBookshelfLayout -> false\n            PreferKey.showRss == key && ignoreShowRss -> false\n            PreferKey.threadCount == key && ignoreThreadCount -> false\n            else -> true\n        }\n    }\n\n    val ignoreReadConfig: Boolean\n        get() = ignoreConfig[readConfigKey] == true\n    private val ignoreThemeMode: Boolean\n        get() = ignoreConfig[PreferKey.themeMode] == true\n    private val ignoreThemeConfig: Boolean\n        get() = ignoreConfig[themeConfigKey] == true\n    private val ignoreCoverConfig: Boolean\n        get() = ignoreConfig[coverConfigKey] == true\n    private val ignoreBookshelfLayout: Boolean\n        get() = ignoreConfig[PreferKey.bookshelfLayout] == true\n    private val ignoreShowRss: Boolean\n        get() = ignoreConfig[PreferKey.showRss] == true\n    private val ignoreThreadCount: Boolean\n        get() = ignoreConfig[PreferKey.threadCount] == true\n    val ignoreLocalBook: Boolean\n        get() = ignoreConfig[localBookKey] == true\n\n    fun saveIgnoreConfig() {\n        val json = GSON.toJson(ignoreConfig)\n        FileUtils.createFileIfNotExist(ignoreConfigPath).writeText(json)\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/help/storage/ImportOldData.kt",
    "content": "package io.legado.app.help.storage\n\nimport android.content.Context\nimport android.net.Uri\nimport androidx.documentfile.provider.DocumentFile\nimport com.jayway.jsonpath.DocumentContext\nimport io.legado.app.R\nimport io.legado.app.constant.AppConst\nimport io.legado.app.constant.BookSourceType\nimport io.legado.app.constant.BookType\nimport io.legado.app.data.appDb\nimport io.legado.app.data.entities.Book\nimport io.legado.app.data.entities.BookSource\nimport io.legado.app.data.entities.rule.*\nimport io.legado.app.exception.NoStackTraceException\nimport io.legado.app.help.ReplaceAnalyzer\nimport io.legado.app.utils.*\nimport splitties.init.appCtx\nimport java.io.File\nimport java.util.regex.Pattern\n\nobject ImportOldData {\n\n    @Suppress(\"RegExpRedundantEscape\")\n    private val headerPattern = Pattern.compile(\"@Header:\\\\{.+?\\\\}\", Pattern.CASE_INSENSITIVE)\n    @Suppress(\"RegExpRedundantEscape\")\n    private val jsPattern = Pattern.compile(\"\\\\{\\\\{.+?\\\\}\\\\}\", Pattern.CASE_INSENSITIVE)\n\n    fun importUri(context: Context, uri: Uri) {\n        if (uri.isContentScheme()) {\n            DocumentFile.fromTreeUri(context, uri)?.listFiles()?.forEach { doc ->\n                when (doc.name) {\n                    \"myBookShelf.json\" ->\n                        kotlin.runCatching {\n                            doc.uri.readText(context).let { json ->\n                                val importCount = importOldBookshelf(json)\n                                context.toastOnUi(\"成功导入书架${importCount}\")\n                            }\n                        }.onFailure {\n                            context.toastOnUi(\"导入书架失败\\n${it.localizedMessage}\")\n                        }\n                    \"myBookSource.json\" ->\n                        kotlin.runCatching {\n                            doc.uri.readText(context).let { json ->\n                                val importCount = importOldSource(json)\n                                context.toastOnUi(\"成功导入书源${importCount}\")\n                            }\n                        }.onFailure {\n                            context.toastOnUi(\"导入源失败\\n${it.localizedMessage}\")\n                        }\n                    \"myBookReplaceRule.json\" ->\n                        kotlin.runCatching {\n                            doc.uri.readText(context).let { json ->\n                                val importCount = importOldReplaceRule(json)\n                                context.toastOnUi(\"成功导入替换规则${importCount}\")\n                            }\n                        }.onFailure {\n                            context.toastOnUi(\"导入替换规则失败\\n${it.localizedMessage}\")\n                        }\n                }\n            }\n        } else {\n            uri.path?.let { path ->\n                val file = File(path)\n                kotlin.runCatching {// 导入书架\n                    val shelfFile =\n                        FileUtils.createFileIfNotExist(file, \"myBookShelf.json\")\n                    val json = shelfFile.readText()\n                    val importCount = importOldBookshelf(json)\n                    context.toastOnUi(\"成功导入书架${importCount}\")\n                }.onFailure {\n                    context.toastOnUi(\"导入书架失败\\n${it.localizedMessage}\")\n                }\n\n                kotlin.runCatching {// Book source\n                    val sourceFile =\n                        file.getFile(\"myBookSource.json\")\n                    val json = sourceFile.readText()\n                    val importCount = importOldSource(json)\n                    context.toastOnUi(\"成功导入书源${importCount}\")\n                }.onFailure {\n                    context.toastOnUi(\"导入源失败\\n${it.localizedMessage}\")\n                }\n\n                kotlin.runCatching {// Replace rules\n                    val ruleFile = file.getFile(\"myBookReplaceRule.json\")\n                    if (ruleFile.exists()) {\n                        val json = ruleFile.readText()\n                        val importCount = importOldReplaceRule(json)\n                        context.toastOnUi(\"成功导入替换规则${importCount}\")\n                    } else {\n                        context.toastOnUi(\"未找到替换规则\")\n                    }\n                }.onFailure {\n                    context.toastOnUi(\"导入替换规则失败\\n${it.localizedMessage}\")\n                }\n            }\n        }\n    }\n\n    private fun importOldBookshelf(json: String): Int {\n        val books = fromOldBooks(json)\n        appDb.bookDao.insert(*books.toTypedArray())\n        return books.size\n    }\n\n    fun importOldSource(json: String): Int {\n        val sources = fromOldBookSources(json)\n        appDb.bookSourceDao.insert(*sources.toTypedArray())\n        return sources.size\n    }\n\n    private fun importOldReplaceRule(json: String): Int {\n        val rules = ReplaceAnalyzer.jsonToReplaceRules(json).getOrNull()\n        rules?.let {\n            appDb.replaceRuleDao.insert(*rules.toTypedArray())\n            return rules.size\n        }\n        return 0\n    }\n\n    private fun fromOldBooks(json: String): List<Book> {\n        val books = mutableListOf<Book>()\n        val items: List<Map<String, Any>> = jsonPath.parse(json).read(\"$\")\n        val existingBooks = appDb.bookDao.allBookUrls.toSet()\n        for (item in items) {\n            val jsonItem = jsonPath.parse(item)\n            val book = Book()\n            book.bookUrl = jsonItem.readString(\"$.noteUrl\") ?: \"\"\n            if (book.bookUrl.isBlank()) continue\n            book.name = jsonItem.readString(\"$.bookInfoBean.name\") ?: \"\"\n            if (book.bookUrl in existingBooks) {\n                DebugLog.d(javaClass.name, \"Found existing book: \" + book.name)\n                continue\n            }\n            book.origin = jsonItem.readString(\"$.tag\") ?: \"\"\n            book.originName = jsonItem.readString(\"$.bookInfoBean.origin\") ?: \"\"\n            book.author = jsonItem.readString(\"$.bookInfoBean.author\") ?: \"\"\n            val local = if (book.origin == \"loc_book\") BookType.local else 0\n            val isAudio = jsonItem.readString(\"$.bookInfoBean.bookSourceType\") == \"AUDIO\"\n            book.type = local or if (isAudio) BookType.audio else BookType.text\n            book.tocUrl = jsonItem.readString(\"$.bookInfoBean.chapterUrl\") ?: book.bookUrl\n            book.coverUrl = jsonItem.readString(\"$.bookInfoBean.coverUrl\")\n            book.customCoverUrl = jsonItem.readString(\"$.customCoverPath\")\n            book.lastCheckTime = jsonItem.readLong(\"$.bookInfoBean.finalRefreshData\") ?: 0\n            book.canUpdate = jsonItem.readBool(\"$.allowUpdate\") == true\n            book.totalChapterNum = jsonItem.readInt(\"$.chapterListSize\") ?: 0\n            book.durChapterIndex = jsonItem.readInt(\"$.durChapter\") ?: 0\n            book.durChapterTitle = jsonItem.readString(\"$.durChapterName\")\n            book.durChapterPos = jsonItem.readInt(\"$.durChapterPage\") ?: 0\n            book.durChapterTime = jsonItem.readLong(\"$.finalDate\") ?: 0\n            book.intro = jsonItem.readString(\"$.bookInfoBean.introduce\")\n            book.latestChapterTitle = jsonItem.readString(\"$.lastChapterName\")\n            book.lastCheckCount = jsonItem.readInt(\"$.newChapters\") ?: 0\n            book.order = jsonItem.readInt(\"$.serialNumber\") ?: 0\n            book.variable = jsonItem.readString(\"$.variable\")\n            book.setUseReplaceRule(jsonItem.readBool(\"$.useReplaceRule\") == true)\n            books.add(book)\n        }\n        return books\n    }\n\n    private fun fromOldBookSources(json: String): MutableList<BookSource> {\n        val sources = mutableListOf<BookSource>()\n        val items: List<Map<String, Any>> = jsonPath.parse(json).read(\"$\")\n        for (item in items) {\n            val jsonItem = jsonPath.parse(item)\n            val source = fromOldBookSource(jsonItem)\n            sources.add(source)\n        }\n        return sources\n    }\n\n    fun fromOldBookSource(jsonItem: DocumentContext): BookSource {\n        val source = BookSource()\n        return source.apply {\n            bookSourceUrl = jsonItem.readString(\"bookSourceUrl\")\n                ?: throw NoStackTraceException(appCtx.getString(R.string.wrong_format))\n            bookSourceName = jsonItem.readString(\"bookSourceName\") ?: \"\"\n            bookSourceGroup = jsonItem.readString(\"bookSourceGroup\")\n            loginUrl = jsonItem.readString(\"loginUrl\")\n            loginUi = jsonItem.readString(\"loginUi\")\n            loginCheckJs = jsonItem.readString(\"loginCheckJs\")\n            coverDecodeJs = jsonItem.readString(\"coverDecodeJs\")\n            bookSourceComment = jsonItem.readString(\"bookSourceComment\") ?: \"\"\n            bookUrlPattern = jsonItem.readString(\"ruleBookUrlPattern\")\n            customOrder = jsonItem.readInt(\"serialNumber\") ?: 0\n            header = uaToHeader(jsonItem.readString(\"httpUserAgent\"))\n            searchUrl = toNewUrl(jsonItem.readString(\"ruleSearchUrl\"))\n            exploreUrl = toNewUrls(jsonItem.readString(\"ruleFindUrl\"))\n            bookSourceType =\n                if (jsonItem.readString(\"bookSourceType\") == \"AUDIO\") BookSourceType.audio else BookSourceType.default\n            enabled = jsonItem.readBool(\"enable\") ?: true\n            if (exploreUrl.isNullOrBlank()) {\n                enabledExplore = false\n            }\n            ruleSearch = SearchRule(\n                bookList = toNewRule(jsonItem.readString(\"ruleSearchList\")),\n                name = toNewRule(jsonItem.readString(\"ruleSearchName\")),\n                author = toNewRule(jsonItem.readString(\"ruleSearchAuthor\")),\n                intro = toNewRule(jsonItem.readString(\"ruleSearchIntroduce\")),\n                kind = toNewRule(jsonItem.readString(\"ruleSearchKind\")),\n                bookUrl = toNewRule(jsonItem.readString(\"ruleSearchNoteUrl\")),\n                coverUrl = toNewRule(jsonItem.readString(\"ruleSearchCoverUrl\")),\n                lastChapter = toNewRule(jsonItem.readString(\"ruleSearchLastChapter\"))\n            )\n            ruleExplore = ExploreRule(\n                bookList = toNewRule(jsonItem.readString(\"ruleFindList\")),\n                name = toNewRule(jsonItem.readString(\"ruleFindName\")),\n                author = toNewRule(jsonItem.readString(\"ruleFindAuthor\")),\n                intro = toNewRule(jsonItem.readString(\"ruleFindIntroduce\")),\n                kind = toNewRule(jsonItem.readString(\"ruleFindKind\")),\n                bookUrl = toNewRule(jsonItem.readString(\"ruleFindNoteUrl\")),\n                coverUrl = toNewRule(jsonItem.readString(\"ruleFindCoverUrl\")),\n                lastChapter = toNewRule(jsonItem.readString(\"ruleFindLastChapter\"))\n            )\n            ruleBookInfo = BookInfoRule(\n                init = toNewRule(jsonItem.readString(\"ruleBookInfoInit\")),\n                name = toNewRule(jsonItem.readString(\"ruleBookName\")),\n                author = toNewRule(jsonItem.readString(\"ruleBookAuthor\")),\n                intro = toNewRule(jsonItem.readString(\"ruleIntroduce\")),\n                kind = toNewRule(jsonItem.readString(\"ruleBookKind\")),\n                coverUrl = toNewRule(jsonItem.readString(\"ruleCoverUrl\")),\n                lastChapter = toNewRule(jsonItem.readString(\"ruleBookLastChapter\")),\n                tocUrl = toNewRule(jsonItem.readString(\"ruleChapterUrl\"))\n            )\n            ruleToc = TocRule(\n                chapterList = toNewRule(jsonItem.readString(\"ruleChapterList\")),\n                chapterName = toNewRule(jsonItem.readString(\"ruleChapterName\")),\n                chapterUrl = toNewRule(jsonItem.readString(\"ruleContentUrl\")),\n                nextTocUrl = toNewRule(jsonItem.readString(\"ruleChapterUrlNext\"))\n            )\n            var content = toNewRule(jsonItem.readString(\"ruleBookContent\")) ?: \"\"\n            if (content.startsWith(\"$\") && !content.startsWith(\"$.\")) {\n                content = content.substring(1)\n            }\n            ruleContent = ContentRule(\n                content = content,\n                replaceRegex = toNewRule(jsonItem.readString(\"ruleBookContentReplace\")),\n                nextContentUrl = toNewRule(jsonItem.readString(\"ruleContentUrlNext\"))\n            )\n        }\n    }\n\n\n    // default规则适配\n    // #正则#替换内容 替换成 ##正则##替换内容\n    // | 替换成 ||\n    // & 替换成 &&\n    private fun toNewRule(oldRule: String?): String? {\n        if (oldRule.isNullOrBlank()) return null\n        var newRule = oldRule\n        var reverse = false\n        var allinone = false\n        if (oldRule.startsWith(\"-\")) {\n            reverse = true\n            newRule = oldRule.substring(1)\n        }\n        if (newRule.startsWith(\"+\")) {\n            allinone = true\n            newRule = newRule.substring(1)\n        }\n        if (!newRule.startsWith(\"@CSS:\", true) &&\n            !newRule.startsWith(\"@XPath:\", true) &&\n            !newRule.startsWith(\"//\") &&\n            !newRule.startsWith(\"##\") &&\n            !newRule.startsWith(\":\") &&\n            !newRule.contains(\"@js:\", true) &&\n            !newRule.contains(\"<js>\", true)\n        ) {\n            if (newRule.contains(\"#\") && !newRule.contains(\"##\")) {\n                newRule = oldRule.replace(\"#\", \"##\")\n            }\n            if (newRule.contains(\"|\") && !newRule.contains(\"||\")) {\n                if (newRule.contains(\"##\")) {\n                    val list = newRule.split(\"##\")\n                    if (list[0].contains(\"|\")) {\n                        newRule = list[0].replace(\"|\", \"||\")\n                        for (i in 1 until list.size) {\n                            newRule += \"##\" + list[i]\n                        }\n                    }\n                } else {\n                    newRule = newRule.replace(\"|\", \"||\")\n                }\n            }\n            if (newRule.contains(\"&\")\n                && !newRule.contains(\"&&\")\n                && !newRule.contains(\"http\")\n                && !newRule.startsWith(\"/\")\n            ) {\n                newRule = newRule.replace(\"&\", \"&&\")\n            }\n        }\n        if (allinone) {\n            newRule = \"+$newRule\"\n        }\n        if (reverse) {\n            newRule = \"-$newRule\"\n        }\n        return newRule\n    }\n\n    private fun toNewUrls(oldUrls: String?): String? {\n        if (oldUrls.isNullOrBlank()) return null\n        if (oldUrls.startsWith(\"@js:\") || oldUrls.startsWith(\"<js>\")) {\n            return oldUrls\n        }\n        if (!oldUrls.contains(\"\\n\") && !oldUrls.contains(\"&&\")) {\n            return toNewUrl(oldUrls)\n        }\n        val urls = oldUrls.split(\"(&&|\\r?\\n)+\".toRegex())\n        return urls.map {\n            toNewUrl(it)?.replace(\"\\n\\\\s*\".toRegex(), \"\")\n        }.joinToString(\"\\n\")\n    }\n\n    private fun toNewUrl(oldUrl: String?): String? {\n        if (oldUrl.isNullOrBlank()) return null\n        var url: String = oldUrl\n        if (oldUrl.startsWith(\"<js>\", true)) {\n            url = url.replace(\"=searchKey\", \"={{key}}\")\n                .replace(\"=searchPage\", \"={{page}}\")\n            return url\n        }\n        val map = HashMap<String, String>()\n        var mather = headerPattern.matcher(url)\n        if (mather.find()) {\n            val header = mather.group()\n            url = url.replace(header, \"\")\n            map[\"headers\"] = header.substring(8)\n        }\n        var urlList = url.split(\"|\")\n        url = urlList[0]\n        if (urlList.size > 1) {\n            map[\"charset\"] = urlList[1].split(\"=\")[1]\n        }\n        mather = jsPattern.matcher(url)\n        val jsList = arrayListOf<String>()\n        while (mather.find()) {\n            jsList.add(mather.group())\n            url = url.replace(jsList.last(), \"$${jsList.size - 1}\")\n        }\n        url = url.replace(\"{\", \"<\").replace(\"}\", \">\")\n        url = url.replace(\"searchKey\", \"{{key}}\")\n        url = url.replace(\"<searchPage([-+]1)>\".toRegex(), \"{{page$1}}\")\n            .replace(\"searchPage([-+]1)\".toRegex(), \"{{page$1}}\")\n            .replace(\"searchPage\", \"{{page}}\")\n        for ((index, item) in jsList.withIndex()) {\n            url = url.replace(\n                \"$$index\",\n                item.replace(\"searchKey\", \"key\").replace(\"searchPage\", \"page\")\n            )\n        }\n        urlList = url.split(\"@\")\n        url = urlList[0]\n        if (urlList.size > 1) {\n            map[\"method\"] = \"POST\"\n            map[\"body\"] = urlList[1]\n        }\n        if (map.size > 0) {\n            url += \",\" + GSON.toJson(map)\n        }\n        return url\n    }\n\n    private fun uaToHeader(ua: String?): String? {\n        if (ua.isNullOrEmpty()) return null\n        val map = mapOf(Pair(AppConst.UA_NAME, ua))\n        return GSON.toJson(map)\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/help/storage/Restore.kt",
    "content": "package io.legado.app.help.storage\n\nimport android.content.Context\nimport android.database.sqlite.SQLiteConstraintException\nimport android.net.Uri\nimport androidx.documentfile.provider.DocumentFile\nimport io.legado.app.BuildConfig\nimport io.legado.app.R\nimport io.legado.app.constant.AppConst.androidId\nimport io.legado.app.constant.AppLog\nimport io.legado.app.constant.PreferKey\nimport io.legado.app.data.appDb\nimport io.legado.app.data.entities.Book\nimport io.legado.app.data.entities.BookGroup\nimport io.legado.app.data.entities.BookSource\nimport io.legado.app.data.entities.Bookmark\nimport io.legado.app.data.entities.DictRule\nimport io.legado.app.data.entities.HttpTTS\nimport io.legado.app.data.entities.KeyboardAssist\nimport io.legado.app.data.entities.ReadRecord\nimport io.legado.app.data.entities.ReplaceRule\nimport io.legado.app.data.entities.RssSource\nimport io.legado.app.data.entities.RssStar\nimport io.legado.app.data.entities.RuleSub\nimport io.legado.app.data.entities.SearchKeyword\nimport io.legado.app.data.entities.Server\nimport io.legado.app.data.entities.TxtTocRule\nimport io.legado.app.help.DirectLinkUpload\nimport io.legado.app.help.LauncherIconHelp\nimport io.legado.app.help.book.isLocal\nimport io.legado.app.help.book.upType\nimport io.legado.app.help.config.LocalConfig\nimport io.legado.app.help.config.ReadBookConfig\nimport io.legado.app.help.config.ThemeConfig\nimport io.legado.app.model.BookCover\nimport io.legado.app.model.localBook.LocalBook\nimport io.legado.app.utils.ACache\nimport io.legado.app.utils.FileUtils\nimport io.legado.app.utils.GSON\nimport io.legado.app.utils.LogUtils\nimport io.legado.app.utils.compress.ZipUtils\nimport io.legado.app.utils.defaultSharedPreferences\nimport io.legado.app.utils.fromJsonArray\nimport io.legado.app.utils.getPrefBoolean\nimport io.legado.app.utils.getPrefInt\nimport io.legado.app.utils.getPrefString\nimport io.legado.app.utils.getSharedPreferences\nimport io.legado.app.utils.isContentScheme\nimport io.legado.app.utils.isJsonArray\nimport io.legado.app.utils.openInputStream\nimport io.legado.app.utils.toastOnUi\nimport kotlinx.coroutines.Dispatchers.Main\nimport kotlinx.coroutines.delay\nimport kotlinx.coroutines.sync.Mutex\nimport kotlinx.coroutines.sync.withLock\nimport kotlinx.coroutines.withContext\nimport splitties.init.appCtx\nimport java.io.File\nimport java.io.FileInputStream\n\n/**\n * 恢复\n */\nobject Restore {\n\n    private val mutex = Mutex()\n\n    private const val TAG = \"Restore\"\n\n    suspend fun restore(context: Context, uri: Uri) {\n        LogUtils.d(TAG, \"开始恢复备份 uri:$uri\")\n        kotlin.runCatching {\n            FileUtils.delete(Backup.backupPath)\n            if (uri.isContentScheme()) {\n                DocumentFile.fromSingleUri(context, uri)!!.openInputStream()!!.use {\n                    ZipUtils.unZipToPath(it, Backup.backupPath)\n                }\n            } else {\n                ZipUtils.unZipToPath(File(uri.path!!), Backup.backupPath)\n            }\n        }.onFailure {\n            AppLog.put(\"复制解压文件出错\\n${it.localizedMessage}\", it)\n            return\n        }\n        kotlin.runCatching {\n            restoreLocked(Backup.backupPath)\n            LocalConfig.lastBackup = System.currentTimeMillis()\n        }.onFailure {\n            appCtx.toastOnUi(\"恢复备份出错\\n${it.localizedMessage}\")\n            AppLog.put(\"恢复备份出错\\n${it.localizedMessage}\", it)\n        }\n    }\n\n    suspend fun restoreLocked(path: String) {\n        mutex.withLock {\n            restore(path)\n        }\n    }\n\n    private suspend fun restore(path: String) {\n        val aes = BackupAES()\n        fileToListT<Book>(path, \"bookshelf.json\")?.let {\n            it.forEach { book ->\n                book.upType()\n            }\n            it.filter { book -> book.isLocal }\n                .forEach { book ->\n                    book.coverUrl = LocalBook.getCoverPath(book)\n                }\n            val newBooks = arrayListOf<Book>()\n            val ignoreLocalBook = BackupConfig.ignoreLocalBook\n            it.forEach { book ->\n                if (ignoreLocalBook && book.isLocal) {\n                    return@forEach\n                }\n                if (appDb.bookDao.has(book.bookUrl)) {\n                    try {\n                        appDb.bookDao.update(book)\n                    } catch (_: SQLiteConstraintException) {\n                        appDb.bookDao.insert(book)\n                    }\n                } else {\n                    newBooks.add(book)\n                }\n            }\n            appDb.bookDao.insert(*newBooks.toTypedArray())\n        }\n        fileToListT<Bookmark>(path, \"bookmark.json\")?.let {\n            appDb.bookmarkDao.insert(*it.toTypedArray())\n        }\n        fileToListT<BookGroup>(path, \"bookGroup.json\")?.let {\n            appDb.bookGroupDao.insert(*it.toTypedArray())\n        }\n        fileToListT<BookSource>(path, \"bookSource.json\")?.let {\n            appDb.bookSourceDao.insert(*it.toTypedArray())\n        } ?: run {\n            val bookSourceFile = File(path, \"bookSource.json\")\n            if (bookSourceFile.exists()) {\n                val json = bookSourceFile.readText()\n                ImportOldData.importOldSource(json)\n            }\n        }\n        fileToListT<RssSource>(path, \"rssSources.json\")?.let {\n            appDb.rssSourceDao.insert(*it.toTypedArray())\n        }\n        fileToListT<RssStar>(path, \"rssStar.json\")?.let {\n            appDb.rssStarDao.insert(*it.toTypedArray())\n        }\n        fileToListT<ReplaceRule>(path, \"replaceRule.json\")?.let {\n            appDb.replaceRuleDao.insert(*it.toTypedArray())\n        }\n        fileToListT<SearchKeyword>(path, \"searchHistory.json\")?.let {\n            appDb.searchKeywordDao.insert(*it.toTypedArray())\n        }\n        fileToListT<RuleSub>(path, \"sourceSub.json\")?.let {\n            appDb.ruleSubDao.insert(*it.toTypedArray())\n        }\n        fileToListT<TxtTocRule>(path, \"txtTocRule.json\")?.let {\n            appDb.txtTocRuleDao.insert(*it.toTypedArray())\n        }\n        fileToListT<HttpTTS>(path, \"httpTTS.json\")?.let {\n            appDb.httpTTSDao.insert(*it.toTypedArray())\n        }\n        fileToListT<DictRule>(path, \"dictRule.json\")?.let {\n            appDb.dictRuleDao.insert(*it.toTypedArray())\n        }\n        fileToListT<KeyboardAssist>(path, \"keyboardAssists.json\")?.let {\n            appDb.keyboardAssistsDao.insert(*it.toTypedArray())\n        }\n        fileToListT<ReadRecord>(path, \"readRecord.json\")?.let {\n            it.forEach { readRecord ->\n                //判断是不是本机记录\n                if (readRecord.deviceId != androidId) {\n                    appDb.readRecordDao.insert(readRecord)\n                } else {\n                    val time = appDb.readRecordDao\n                        .getReadTime(readRecord.deviceId, readRecord.bookName)\n                    if (time == null || time < readRecord.readTime) {\n                        appDb.readRecordDao.insert(readRecord)\n                    }\n                }\n            }\n        }\n        File(path, \"servers.json\").takeIf {\n            it.exists()\n        }?.runCatching {\n            var json = readText()\n            if (!json.isJsonArray()) {\n                json = aes.decryptStr(json)\n            }\n            GSON.fromJsonArray<Server>(json).getOrNull()?.let {\n                appDb.serverDao.insert(*it.toTypedArray())\n            }\n        }?.onFailure {\n            AppLog.put(\"恢复服务器配置出错\\n${it.localizedMessage}\", it)\n        }\n        File(path, DirectLinkUpload.ruleFileName).takeIf {\n            it.exists()\n        }?.runCatching {\n            val json = readText()\n            ACache.get(cacheDir = false).put(DirectLinkUpload.ruleFileName, json)\n        }?.onFailure {\n            AppLog.put(\"恢复直链上传出错\\n${it.localizedMessage}\", it)\n        }\n        //恢复主题配置\n        File(path, ThemeConfig.configFileName).takeIf {\n            it.exists()\n        }?.runCatching {\n            FileUtils.delete(ThemeConfig.configFilePath)\n            copyTo(File(ThemeConfig.configFilePath))\n            ThemeConfig.upConfig()\n        }?.onFailure {\n            AppLog.put(\"恢复主题出错\\n${it.localizedMessage}\", it)\n        }\n        File(path, BookCover.configFileName).takeIf {\n            it.exists()\n        }?.runCatching {\n            val json = readText()\n            BookCover.saveCoverRule(json)\n        }?.onFailure {\n            AppLog.put(\"恢复封面规则出错\\n${it.localizedMessage}\", it)\n        }\n        if (!BackupConfig.ignoreReadConfig) {\n            //恢复阅读界面配置\n            File(path, ReadBookConfig.configFileName).takeIf {\n                it.exists()\n            }?.runCatching {\n                FileUtils.delete(ReadBookConfig.configFilePath)\n                copyTo(File(ReadBookConfig.configFilePath))\n                ReadBookConfig.initConfigs()\n            }?.onFailure {\n                AppLog.put(\"恢复阅读界面出错\\n${it.localizedMessage}\", it)\n            }\n            File(path, ReadBookConfig.shareConfigFileName).takeIf {\n                it.exists()\n            }?.runCatching {\n                FileUtils.delete(ReadBookConfig.shareConfigFilePath)\n                copyTo(File(ReadBookConfig.shareConfigFilePath))\n                ReadBookConfig.initShareConfig()\n            }?.onFailure {\n                AppLog.put(\"恢复阅读界面出错\\n${it.localizedMessage}\", it)\n            }\n        }\n        //AppWebDav.downBgs()\n        appCtx.getSharedPreferences(path, \"config\")?.all?.let { map ->\n            val edit = appCtx.defaultSharedPreferences.edit()\n\n            map.forEach { (key, value) ->\n                if (BackupConfig.keyIsNotIgnore(key)) {\n                    when (key) {\n                        PreferKey.webDavPassword -> {\n                            kotlin.runCatching {\n                                aes.decryptStr(value.toString())\n                            }.getOrNull()?.let {\n                                edit.putString(key, it)\n                            } ?: let {\n                                if (appCtx.getPrefString(PreferKey.webDavPassword)\n                                        .isNullOrBlank()\n                                ) {\n                                    edit.putString(key, value.toString())\n                                }\n                            }\n                        }\n\n                        else -> when (value) {\n                            is Int -> edit.putInt(key, value)\n                            is Boolean -> edit.putBoolean(key, value)\n                            is Long -> edit.putLong(key, value)\n                            is Float -> edit.putFloat(key, value)\n                            is String -> edit.putString(key, value)\n                        }\n                    }\n                }\n            }\n            edit.apply()\n        }\n        ReadBookConfig.apply {\n            comicStyleSelect = appCtx.getPrefInt(PreferKey.comicStyleSelect)\n            readStyleSelect = appCtx.getPrefInt(PreferKey.readStyleSelect)\n            shareLayout = appCtx.getPrefBoolean(PreferKey.shareLayout)\n            hideStatusBar = appCtx.getPrefBoolean(PreferKey.hideStatusBar)\n            hideNavigationBar = appCtx.getPrefBoolean(PreferKey.hideNavigationBar)\n            autoReadSpeed = appCtx.getPrefInt(PreferKey.autoReadSpeed, 46)\n        }\n        appCtx.toastOnUi(R.string.restore_success)\n        withContext(Main) {\n            delay(100)\n            if (!BuildConfig.DEBUG) {\n                LauncherIconHelp.changeIcon(appCtx.getPrefString(PreferKey.launcherIcon))\n            }\n            ThemeConfig.applyDayNight(appCtx)\n        }\n    }\n\n    private inline fun <reified T> fileToListT(path: String, fileName: String): List<T>? {\n        try {\n            val file = File(path, fileName)\n            if (file.exists()) {\n                LogUtils.d(TAG, \"阅读恢复备份 $fileName 文件大小 ${file.length()}\")\n                FileInputStream(file).use {\n                    return GSON.fromJsonArray<T>(it).getOrThrow().also { list ->\n                        LogUtils.d(TAG, \"阅读恢复备份 $fileName 列表大小 ${list.size}\")\n                    }\n                }\n            } else {\n                LogUtils.d(TAG, \"阅读恢复备份 $fileName 文件不存在\")\n            }\n        } catch (e: Exception) {\n            AppLog.put(\"$fileName\\n读取解析出错\\n${e.localizedMessage}\", e)\n            appCtx.toastOnUi(\"$fileName\\n读取文件出错\\n${e.localizedMessage}\")\n        }\n        return null\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/help/update/AppReleaseInfo.kt",
    "content": "package io.legado.app.help.update\n\nimport androidx.annotation.Keep\nimport com.google.gson.annotations.SerializedName\nimport io.legado.app.exception.NoStackTraceException\nimport java.time.Instant\n\ndata class AppReleaseInfo(\n    val appVariant: AppVariant,\n    val createdAt: Long,\n    val note: String,\n    val name: String,\n    val downloadUrl: String,\n    val assetUrl: String\n) {\n    val versionName: String = name.split(\"_\").getOrNull(2)?.dropLast(2) ?: \"\"\n}\n\nenum class AppVariant {\n    OFFICIAL,\n    BETA_RELEASEA,\n    BETA_RELEASE,\n    UNKNOWN;\n\n    fun isBeta(): Boolean {\n        return this == BETA_RELEASE || this == BETA_RELEASEA\n    }\n\n}\n\n@Keep\ndata class GithubRelease(\n    val assets: List<Asset>?,\n    val body: String,\n    @SerializedName(\"prerelease\")\n    val isPreRelease: Boolean,\n) {\n    fun gitReleaseToAppReleaseInfo(): List<AppReleaseInfo> {\n        assets ?: throw NoStackTraceException(\"获取新版本出错\")\n        return assets\n            .filter { it.isValid }\n            .map { it.assetToAppReleaseInfo(isPreRelease, body) }\n    }\n}\n\n@Keep\ndata class Asset(\n    @SerializedName(\"browser_download_url\")\n    val apkUrl: String,\n    @SerializedName(\"content_type\")\n    val contentType: String,\n    @SerializedName(\"created_at\")\n    val createdAt: String,\n    @SerializedName(\"download_count\")\n    val downloadCount: Int,\n    val id: Int,\n    val name: String,\n    val state: String,\n    val url: String\n) {\n    val isValid: Boolean\n        get() = (contentType == \"application/vnd.android.package-archive\") && (state == \"uploaded\")\n\n    fun assetToAppReleaseInfo(preRelease: Boolean, note: String): AppReleaseInfo {\n        val instant = Instant.parse(createdAt)\n        val timestamp: Long = instant.toEpochMilli()\n\n        val appVariant = when {\n            preRelease && name.contains(\"releaseA\") -> AppVariant.BETA_RELEASEA\n            preRelease && name.contains(\"release\") -> AppVariant.BETA_RELEASE\n            else -> AppVariant.OFFICIAL\n        }\n\n        return AppReleaseInfo(appVariant, timestamp, note, name, apkUrl, url)\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/help/update/AppUpdate.kt",
    "content": "package io.legado.app.help.update\n\nimport io.legado.app.help.coroutine.Coroutine\nimport kotlinx.coroutines.CoroutineScope\n\nobject AppUpdate {\n\n    val gitHubUpdate: AppUpdateInterface? by lazy {\n        AppUpdateGitHub\n    }\n\n    data class UpdateInfo(\n        val tagName: String,\n        val updateLog: String,\n        val downloadUrl: String,\n        val fileName: String\n    )\n\n    interface AppUpdateInterface {\n\n        fun check(scope: CoroutineScope): Coroutine<UpdateInfo>\n\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/help/update/AppUpdateGitHub.kt",
    "content": "package io.legado.app.help.update\n\nimport androidx.annotation.Keep\nimport io.legado.app.constant.AppConst\nimport io.legado.app.exception.NoStackTraceException\nimport io.legado.app.help.config.AppConfig\nimport io.legado.app.help.coroutine.Coroutine\nimport io.legado.app.help.http.newCallResponse\nimport io.legado.app.help.http.okHttpClient\nimport io.legado.app.help.http.text\nimport io.legado.app.utils.GSON\nimport io.legado.app.utils.fromJsonObject\nimport kotlinx.coroutines.CoroutineScope\n\n@Keep\n@Suppress(\"unused\")\nobject AppUpdateGitHub : AppUpdate.AppUpdateInterface {\n\n    private val checkVariant: AppVariant\n        get() = when (AppConfig.updateToVariant) {\n            \"official_version\" -> AppVariant.OFFICIAL\n            \"beta_release_version\" -> AppVariant.BETA_RELEASE\n            \"beta_releaseA_version\" -> AppVariant.BETA_RELEASEA\n            else -> AppConst.appInfo.appVariant\n        }\n\n    private suspend fun getLatestRelease(): List<AppReleaseInfo> {\n        val lastReleaseUrl = if (checkVariant.isBeta()) {\n            \"https://api.github.com/repos/gedoor/legado/releases/tags/beta\"\n        } else {\n            \"https://api.github.com/repos/gedoor/legado/releases/latest\"\n        }\n        val res = okHttpClient.newCallResponse {\n            url(lastReleaseUrl)\n        }\n        if (!res.isSuccessful) {\n            throw NoStackTraceException(\"获取新版本出错(${res.code})\")\n        }\n        val body = res.body.text()\n        if (body.isBlank()) {\n            throw NoStackTraceException(\"获取新版本出错\")\n        }\n        return GSON.fromJsonObject<GithubRelease>(body)\n            .getOrElse {\n                throw NoStackTraceException(\"获取新版本出错 \" + it.localizedMessage)\n            }\n            .gitReleaseToAppReleaseInfo()\n            .sortedByDescending { it.createdAt }\n    }\n\n    override fun check(\n        scope: CoroutineScope,\n    ): Coroutine<AppUpdate.UpdateInfo> {\n        return Coroutine.async(scope) {\n            getLatestRelease()\n                .filter { it.appVariant == checkVariant }\n                .firstOrNull { it.versionName > AppConst.appInfo.versionName }\n                ?.let {\n                    return@async AppUpdate.UpdateInfo(\n                        it.versionName,\n                        it.note,\n                        it.downloadUrl,\n                        it.name\n                    )\n                }\n                ?: throw NoStackTraceException(\"已是最新版本\")\n        }.timeout(10000)\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/lib/README.md",
    "content": "# 放置一些copy过来的库\n\n* dialogs 弹出框\n* icu4j 编码识别库\n* permission 权限申请库\n* theme 主题\n* webDav 网络存储"
  },
  {
    "path": "app/src/main/java/io/legado/app/lib/aliyun/ALiYun.kt",
    "content": "package io.legado.app.lib.aliyun\n\nobject ALiYun {\n\n    fun getToken() {\n\n    }\n\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/lib/cronet/AbsCallBack.kt",
    "content": "package io.legado.app.lib.cronet\n\nimport androidx.annotation.Keep\nimport io.legado.app.help.coroutine.Coroutine\nimport io.legado.app.help.http.CookieManager\nimport io.legado.app.help.http.CookieManager.cookieJarHeader\nimport io.legado.app.help.http.okHttpClient\nimport io.legado.app.utils.DebugLog\nimport io.legado.app.utils.asIOException\nimport io.legado.app.utils.splitNotBlank\nimport kotlinx.coroutines.delay\nimport okhttp3.Call\nimport okhttp3.Callback\nimport okhttp3.EventListener\nimport okhttp3.Headers\nimport okhttp3.MediaType.Companion.toMediaTypeOrNull\nimport okhttp3.Protocol\nimport okhttp3.Request\nimport okhttp3.Response\nimport okhttp3.ResponseBody\nimport okhttp3.ResponseBody.Companion.asResponseBody\nimport okhttp3.internal.http.HTTP_PERM_REDIRECT\nimport okhttp3.internal.http.HTTP_TEMP_REDIRECT\nimport okhttp3.internal.http.HttpMethod\nimport okio.Buffer\nimport okio.Source\nimport okio.Timeout\nimport okio.buffer\nimport org.chromium.net.CronetException\nimport org.chromium.net.UrlRequest\nimport org.chromium.net.UrlResponseInfo\nimport java.io.IOException\nimport java.net.ProtocolException\nimport java.nio.ByteBuffer\nimport java.util.Locale\nimport java.util.concurrent.ArrayBlockingQueue\nimport java.util.concurrent.TimeUnit\nimport java.util.concurrent.atomic.AtomicBoolean\n\n\n@Keep\nabstract class AbsCallBack(\n    var originalRequest: Request,\n    val mCall: Call,\n    var readTimeoutMillis: Int,\n    private val eventListener: EventListener? = null,\n    private val responseCallback: Callback? = null\n) : UrlRequest.Callback() {\n\n    var mResponse: Response\n    private var followCount = 0\n    private var request: UrlRequest? = null\n    private var finished = AtomicBoolean(false)\n    private val canceled = AtomicBoolean(false)\n    private val callbackResults = ArrayBlockingQueue<CallbackResult>(2)\n    private val urlResponseInfoChain = arrayListOf<UrlResponseInfo>()\n    private var cancelJob: Coroutine<*>? = null\n    private var followRedirect = false\n    private var enableCookieJar = false\n    private var redirectRequest: Request? = null\n\n    init {\n        if (readTimeoutMillis == 0) {\n            readTimeoutMillis = Int.MAX_VALUE\n        }\n        if (originalRequest.header(cookieJarHeader) != null) {\n            enableCookieJar = true\n            originalRequest = originalRequest.newBuilder()\n                .removeHeader(cookieJarHeader).build()\n        }\n    }\n\n\n    @Throws(IOException::class)\n    abstract fun waitForDone(urlRequest: UrlRequest): Response\n\n    /**\n     * 当发生错误时，通知子类终止阻塞抛出错误\n     * @param error\n     */\n    abstract fun onError(error: IOException)\n\n    /**\n     * 请求成功后，通知子类结束阻塞，返回response\n     * @param response\n     */\n    abstract fun onSuccess(response: Response)\n\n\n    override fun onRedirectReceived(\n        request: UrlRequest,\n        info: UrlResponseInfo,\n        newLocationUrl: String\n    ) {\n        if (followCount > MAX_FOLLOW_COUNT) {\n            request.cancel()\n            onError(IOException(\"Too many redirect\"))\n            return\n        }\n        if (mCall.isCanceled()) {\n            onError(IOException(\"Cronet Request Canceled\"))\n            request.cancel()\n            return\n        }\n        followCount += 1\n        urlResponseInfoChain.add(info)\n        val client = okHttpClient\n        if (originalRequest.url.isHttps\n            && newLocationUrl.startsWith(\"http://\")\n            && client.followSslRedirects\n        ) {\n            followRedirect = true\n        } else if (!originalRequest.url.isHttps\n            && newLocationUrl.startsWith(\"https://\")\n            && client.followSslRedirects\n        ) {\n            followRedirect = true\n        } else if (okHttpClient.followRedirects) {\n            followRedirect = true\n        }\n\n        if (!followRedirect) {\n            onError(IOException(\"Too many redirect\"))\n        } else {\n            val response = toResponse(originalRequest, info, urlResponseInfoChain)\n            if (enableCookieJar) {\n                CookieManager.saveResponse(response)\n            }\n            redirectRequest = buildRedirectRequest(response, originalRequest.method, newLocationUrl)\n        }\n        request.cancel()\n    }\n\n\n    override fun onResponseStarted(request: UrlRequest, info: UrlResponseInfo) {\n        this.request = request\n\n        val response: Response\n        try {\n            response = toResponse(originalRequest, info, urlResponseInfoChain, CronetBodySource())\n        } catch (e: IOException) {\n            request.cancel()\n            cancelJob?.cancel()\n            onError(e)\n            return\n        }\n\n        if (enableCookieJar) {\n            CookieManager.saveResponse(response)\n        }\n\n        mResponse = response\n        onSuccess(response)\n\n        //打印协议，用于调试\n        val msg = \"onResponseStarted[${info.negotiatedProtocol}][${info.httpStatusCode}]${info.url}\"\n        DebugLog.i(javaClass.simpleName, msg)\n        if (eventListener != null) {\n            eventListener.responseHeadersEnd(mCall, response)\n            eventListener.responseBodyStart(mCall)\n        }\n        try {\n            responseCallback?.onResponse(mCall, response)\n        } catch (e: IOException) {\n            // Pass?\n        }\n    }\n\n\n    @Throws(IOException::class)\n    override fun onReadCompleted(\n        request: UrlRequest,\n        info: UrlResponseInfo,\n        byteBuffer: ByteBuffer\n    ) {\n        callbackResults.add(CallbackResult(CallbackStep.ON_READ_COMPLETED, byteBuffer))\n    }\n\n\n    override fun onSucceeded(request: UrlRequest, info: UrlResponseInfo) {\n        callbackResults.add(CallbackResult(CallbackStep.ON_SUCCESS))\n        cancelJob?.cancel()\n        eventListener?.responseBodyEnd(mCall, info.receivedByteCount)\n        //DebugLog.i(javaClass.simpleName, \"end[${info.negotiatedProtocol}]${info.url}\")\n\n        eventListener?.callEnd(mCall)\n    }\n\n\n    //UrlResponseInfo可能为null\n    override fun onFailed(request: UrlRequest, info: UrlResponseInfo?, error: CronetException) {\n        callbackResults.add(CallbackResult(CallbackStep.ON_FAILED, null, error))\n        cancelJob?.cancel()\n        DebugLog.e(javaClass.name, error.message.toString())\n        onError(error.asIOException())\n        eventListener?.callFailed(mCall, error)\n        responseCallback?.onFailure(mCall, error)\n    }\n\n    override fun onCanceled(request: UrlRequest?, info: UrlResponseInfo?) {\n        if (followRedirect) {\n            followRedirect = false\n            if (enableCookieJar) {\n                val newRequest = CookieManager.loadRequest(redirectRequest!!)\n                buildRequest(newRequest, this)?.start()\n            } else {\n                buildRequest(redirectRequest!!, this)?.start()\n            }\n            return\n        }\n        canceled.set(true)\n        callbackResults.add(CallbackResult(CallbackStep.ON_CANCELED))\n        cancelJob?.cancel()\n        //DebugLog.i(javaClass.simpleName, \"cancel[${info?.negotiatedProtocol}]${info?.url}\")\n        eventListener?.callEnd(mCall)\n        onError(IOException(\"Cronet Request Canceled\"))\n    }\n\n    fun startCheckCancelJob(request: UrlRequest) {\n        cancelJob = Coroutine.async {\n            while (!mCall.isCanceled()) {\n                delay(1000)\n            }\n            request.cancel()\n        }\n    }\n\n    init {\n        mResponse = Response.Builder()\n            .sentRequestAtMillis(System.currentTimeMillis())\n            .request(originalRequest)\n            .protocol(Protocol.HTTP_1_0)\n            .code(0)\n            .message(\"\")\n            .build()\n    }\n\n    companion object {\n        const val MAX_FOLLOW_COUNT = 20\n        private val encodingsHandledByCronet = setOf(\"br\", \"deflate\", \"gzip\", \"x-gzip\")\n\n        private fun protocolFromNegotiatedProtocol(responseInfo: UrlResponseInfo): Protocol {\n            val negotiatedProtocol = responseInfo.negotiatedProtocol.lowercase(Locale.getDefault())\n            return when {\n                negotiatedProtocol.contains(\"h3\") -> {\n                    Protocol.QUIC\n                }\n\n                negotiatedProtocol.contains(\"quic\") -> {\n                    Protocol.QUIC\n                }\n\n                negotiatedProtocol.contains(\"spdy\") -> {\n                    @Suppress(\"DEPRECATION\")\n                    Protocol.SPDY_3\n                }\n\n                negotiatedProtocol.contains(\"h2\") -> {\n                    Protocol.HTTP_2\n                }\n\n                negotiatedProtocol.contains(\"1.1\") -> {\n                    Protocol.HTTP_1_1\n                }\n\n                else -> {\n                    Protocol.HTTP_1_0\n                }\n            }\n        }\n\n        private fun headersFromResponse(\n            responseInfo: UrlResponseInfo,\n            keepEncodingAffectedHeaders: Boolean\n        ): Headers {\n\n            val headers = responseInfo.allHeadersAsList\n            return Headers.Builder().apply {\n                for ((key, value) in headers) {\n                    try {\n\n                        if (!keepEncodingAffectedHeaders\n                            && (key.equals(\"content-encoding\", ignoreCase = true)\n                                    || key.equals(\"Content-Length\", ignoreCase = true))\n                        ) {\n                            // Strip all content encoding headers as decoding is done handled by cronet\n                            continue\n                        }\n                        add(key, value)\n                    } catch (e: Exception) {\n                        DebugLog.w(javaClass.name, \"Invalid HTTP header/value: $key$value\")\n                        // Ignore that header\n                    }\n                }\n\n            }.build()\n\n        }\n\n        @Throws(IOException::class)\n        private fun createResponse(\n            request: Request,\n            responseInfo: UrlResponseInfo,\n            bodySource: Source? = null\n        ): Response.Builder {\n            val protocol = protocolFromNegotiatedProtocol(responseInfo)\n\n            val contentEncodingHeaders =\n                responseInfo.allHeaders.getOrDefault(\"content-encoding\", emptyList())\n            val contentEncodingItems = contentEncodingHeaders.flatMap {\n                it.splitNotBlank(\",\").toList()\n            }\n            val keepEncodingAffectedHeaders = contentEncodingItems.isEmpty()\n                    || !encodingsHandledByCronet.containsAll(contentEncodingItems)\n\n            val headers = headersFromResponse(responseInfo, keepEncodingAffectedHeaders)\n            val contentLength = if (keepEncodingAffectedHeaders) {\n                responseInfo.allHeaders[\"Content-Length\"]?.lastOrNull()\n            } else null\n            val contentType = responseInfo.allHeaders[\"content-type\"]?.lastOrNull()\n                ?: \"text/plain; charset=\\\"utf-8\\\"\"\n\n            val responseBody = bodySource?.let {\n                createResponseBody(\n                    request,\n                    responseInfo.httpStatusCode,\n                    contentType,\n                    contentLength,\n                    bodySource\n                )\n            } ?: ResponseBody.EMPTY\n\n            return Response.Builder()\n                .request(request)\n                .receivedResponseAtMillis(System.currentTimeMillis())\n                .protocol(protocol)\n                .code(responseInfo.httpStatusCode)\n                .message(responseInfo.httpStatusText)\n                .headers(headers)\n                .body(responseBody)\n        }\n\n        private fun buildPriorResponse(\n            request: Request,\n            redirectResponseInfos: List<UrlResponseInfo>,\n        ): Response? {\n            var priorResponse: Response? = null\n            if (redirectResponseInfos.isNotEmpty()) {\n                for (i in redirectResponseInfos.indices) {\n                    val url = redirectResponseInfos[i].url\n                    val redirectedRequest = request.newBuilder().url(url).build()\n                    priorResponse = createResponse(redirectedRequest, redirectResponseInfos[i])\n                        .priorResponse(priorResponse)\n                        .build()\n                }\n\n            }\n            return priorResponse\n        }\n\n        @Throws(IOException::class)\n        private fun createResponseBody(\n            request: Request,\n            httpStatusCode: Int,\n            contentType: String?,\n            contentLengthString: String?,\n            bodySource: Source\n        ): ResponseBody {\n\n            // Ignore content-length header for HEAD requests (consistency with OkHttp)\n            val contentLength: Long = if (request.method == \"HEAD\") {\n                0\n            } else {\n                contentLengthString?.toLongOrNull() ?: -1\n            }\n\n            // Check for absence of body in No Content / Reset Content responses (OkHttp consistency)\n            if ((httpStatusCode == 204 || httpStatusCode == 205) && contentLength > 0) {\n                throw ProtocolException(\n                    \"HTTP $httpStatusCode had non-zero Content-Length: $contentLengthString\"\n                )\n            }\n            return bodySource.buffer()\n                .asResponseBody(contentType?.toMediaTypeOrNull(), contentLength)\n        }\n\n        private fun buildRedirectRequest(\n            userResponse: Response,\n            method: String,\n            newLocationUrl: String\n        ): Request {\n            // Most redirects don't include a request body.\n            val requestBuilder = userResponse.request.newBuilder()\n            if (HttpMethod.permitsRequestBody(method)) {\n                val responseCode = userResponse.code\n                val maintainBody = HttpMethod.redirectsWithBody(method) ||\n                        responseCode == HTTP_PERM_REDIRECT ||\n                        responseCode == HTTP_TEMP_REDIRECT\n                if (HttpMethod.redirectsToGet(method)\n                    && responseCode != HTTP_PERM_REDIRECT\n                    && responseCode != HTTP_TEMP_REDIRECT\n                ) {\n                    requestBuilder.method(\"GET\", null)\n                } else {\n                    val requestBody = if (maintainBody) userResponse.request.body else null\n                    requestBuilder.method(method, requestBody)\n                }\n                if (!maintainBody) {\n                    requestBuilder.removeHeader(\"Transfer-Encoding\")\n                    requestBuilder.removeHeader(\"Content-Length\")\n                    requestBuilder.removeHeader(\"Content-Type\")\n                }\n            }\n\n            return requestBuilder.url(newLocationUrl).build()\n        }\n\n        private fun toResponse(\n            request: Request,\n            responseInfo: UrlResponseInfo,\n            redirectResponseInfos: List<UrlResponseInfo>,\n            bodySource: Source? = null\n        ): Response {\n            val responseBuilder = createResponse(request, responseInfo, bodySource)\n            val newRequest = request.newBuilder().url(responseInfo.url).build()\n            return responseBuilder\n                .request(newRequest)\n                .priorResponse(buildPriorResponse(request, redirectResponseInfos))\n                .build()\n        }\n    }\n\n    inner class CronetBodySource : Source {\n\n        private var buffer = ByteBuffer.allocateDirect(32 * 1024)\n        private var closed = false\n        private val timeout = readTimeoutMillis.toLong()\n\n        override fun close() {\n            cancelJob?.cancel()\n            if (closed) {\n                return\n            }\n            closed = true\n            if (!finished.get()) {\n                request?.cancel()\n            }\n        }\n\n        @Suppress(\"NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS\")\n        override fun read(sink: Buffer, byteCount: Long): Long {\n            if (canceled.get()) {\n                throw IOException(\"Cronet Request Canceled\")\n            }\n\n            require(byteCount >= 0L) { \"byteCount < 0: $byteCount\" }\n            check(!closed) { \"closed\" }\n\n            if (finished.get()) {\n                return -1\n            }\n\n            if (byteCount < buffer.limit()) {\n                buffer.limit(byteCount.toInt())\n            }\n\n            request?.read(buffer)\n\n            val result = callbackResults.poll(timeout, TimeUnit.MILLISECONDS)\n            if (result == null) {\n                request?.cancel()\n                throw IOException(\"Cronet request body read timeout after wait $timeout ms\")\n            }\n\n            return when (result.callbackStep) {\n                CallbackStep.ON_FAILED -> {\n                    finished.set(true)\n                    buffer = null\n                    throw IOException(result.exception)\n                }\n\n                CallbackStep.ON_SUCCESS -> {\n                    finished.set(true)\n                    buffer = null\n                    -1\n                }\n\n                CallbackStep.ON_CANCELED -> {\n                    buffer = null\n                    throw IOException(\"Request Canceled\")\n                }\n\n                CallbackStep.ON_READ_COMPLETED -> {\n                    result.buffer!!.flip()\n                    val bytesWritten = sink.write(result.buffer)\n                    result.buffer.clear()\n                    bytesWritten.toLong()\n                }\n            }\n        }\n\n        override fun timeout(): Timeout {\n            return mCall.timeout()\n        }\n\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/lib/cronet/BodyUploadProvider.kt",
    "content": "package io.legado.app.lib.cronet\n\nimport androidx.annotation.Keep\nimport okhttp3.RequestBody\nimport okio.Buffer\nimport org.chromium.net.UploadDataProvider\nimport org.chromium.net.UploadDataSink\nimport java.io.IOException\nimport java.nio.ByteBuffer\n\n@Keep\nclass BodyUploadProvider(private val body: RequestBody) : UploadDataProvider(), AutoCloseable {\n\n    private val buffer = Buffer()\n\n    @Volatile\n    private var filled: Boolean = false\n\n    init {\n        fillBuffer()\n    }\n\n    private fun fillBuffer() {\n        try {\n            buffer.clear()\n            filled = true\n            body.writeTo(buffer)\n            buffer.flush()\n        } catch (e: IOException) {\n            e.printStackTrace()\n        }\n    }\n\n    @Throws(IOException::class)\n    override fun getLength(): Long {\n        return body.contentLength()\n    }\n\n    @Throws(IOException::class)\n    override fun read(uploadDataSink: UploadDataSink, byteBuffer: ByteBuffer) {\n        if (!filled) {\n            fillBuffer()\n        }\n        check(byteBuffer.hasRemaining()) { \"Cronet passed a buffer with no bytes remaining\" }\n        var read: Int\n        var bytesRead = 0\n        while (bytesRead == 0) {\n            read = buffer.read(byteBuffer)\n            bytesRead += read\n        }\n        uploadDataSink.onReadSucceeded(false)\n    }\n\n    @Throws(IOException::class)\n    override fun rewind(uploadDataSink: UploadDataSink) {\n        check(body.isOneShot()) { \"Okhttp RequestBody is oneShot\" }\n        filled = false\n        fillBuffer()\n        uploadDataSink.onRewindSucceeded()\n    }\n\n    @Throws(IOException::class)\n    override fun close() {\n        buffer.close()\n        super.close()\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/lib/cronet/CallbackResult.kt",
    "content": "package io.legado.app.lib.cronet\n\nimport org.chromium.net.CronetException\n\nimport java.nio.ByteBuffer\n\n\ndata class CallbackResult(\n    val callbackStep: CallbackStep,\n    val buffer: ByteBuffer? = null,\n    val exception: CronetException? = null\n)\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/lib/cronet/CallbackStep.kt",
    "content": "package io.legado.app.lib.cronet\n\nenum class CallbackStep {\n    ON_READ_COMPLETED,\n    ON_SUCCESS,\n    ON_FAILED,\n    ON_CANCELED\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/lib/cronet/CronetCoroutineInterceptor.kt",
    "content": "package io.legado.app.lib.cronet\n\nimport androidx.annotation.Keep\nimport io.legado.app.utils.printOnDebug\nimport kotlinx.coroutines.runBlocking\nimport kotlinx.coroutines.suspendCancellableCoroutine\nimport kotlinx.coroutines.withTimeout\nimport okhttp3.Call\nimport okhttp3.CookieJar\nimport okhttp3.HttpUrl\nimport okhttp3.Interceptor\nimport okhttp3.Request\nimport okhttp3.Response\nimport okhttp3.internal.http.receiveHeaders\nimport org.chromium.net.UrlRequest\nimport org.chromium.net.UrlResponseInfo\nimport java.io.IOException\nimport kotlin.coroutines.resume\nimport kotlin.coroutines.resumeWithException\n\n@Keep\n@Suppress(\"unused\")\nclass CronetCoroutineInterceptor(private val cookieJar: CookieJar) : Interceptor {\n\n    override fun intercept(chain: Interceptor.Chain): Response {\n        if (chain.call().isCanceled()) {\n            throw IOException(\"Canceled\")\n        }\n        val original: Request = chain.request()\n        //Cronet未初始化\n        return if (!CronetLoader.install() || cronetEngine == null) {\n            chain.proceed(original)\n        } else try {\n            val builder: Request.Builder = original.newBuilder()\n            //移除Keep-Alive,手动设置会导致400 BadRequest\n            builder.removeHeader(\"Keep-Alive\")\n            builder.removeHeader(\"Accept-Encoding\")\n            if (cookieJar != CookieJar.NO_COOKIES) {\n                val cookieStr = getCookie(original.url)\n                //设置Cookie\n                if (cookieStr.length > 3) {\n                    builder.addHeader(\"Cookie\", cookieStr)\n                }\n            }\n\n            val newReq = builder.build()\n            val timeout = chain.call().timeout().timeoutNanos() / 1000000\n            runBlocking() {\n                if (timeout > 0) {\n                    withTimeout(timeout) {\n                        proceedWithCronet(newReq, chain.call(), chain.readTimeoutMillis()).also { response ->\n                            cookieJar.receiveHeaders(newReq.url, response.headers)\n                        }\n                    }\n                } else {\n                    proceedWithCronet(newReq, chain.call(), chain.readTimeoutMillis()).also { response ->\n                        cookieJar.receiveHeaders(newReq.url, response.headers)\n                    }\n                }\n            }\n\n        } catch (e: Exception) {\n            //不能抛出错误,抛出错误会导致应用崩溃\n            //遇到Cronet处理有问题时的情况，如证书过期等等，回退到okhttp处理\n            if (!e.message.toString().contains(\"ERR_CERT_\", true)\n                && !e.message.toString().contains(\"ERR_SSL_\", true)\n            ) {\n                e.printOnDebug()\n            }\n            chain.proceed(original)\n        }\n\n    }\n\n\n    private suspend fun proceedWithCronet(\n        request: Request,\n        call: Call,\n        readTimeoutMillis: Int\n    ): Response =\n        suspendCancellableCoroutine<Response> { coroutine ->\n\n            val callBack = object : AbsCallBack(request, call, readTimeoutMillis) {\n                override fun waitForDone(urlRequest: UrlRequest): Response {\n                    TODO(\"Not yet implemented\")\n                }\n\n                override fun onError(error: IOException) {\n                    coroutine.resumeWithException(error)\n                }\n\n                override fun onSuccess(response: Response) {\n                    coroutine.resume(response)\n                }\n\n                override fun onCanceled(request: UrlRequest?, info: UrlResponseInfo?) {\n                    super.onCanceled(request, info)\n                    coroutine.cancel()\n                }\n\n\n            }\n\n            val req = buildRequest(request, callBack)?.also { it.start() }\n            coroutine.invokeOnCancellation {\n                req?.cancel()\n            }\n\n\n        }\n\n\n    /** Returns a 'Cookie' HTTP request header with all cookies, like `a=b; c=d`. */\n    private fun getCookie(url: HttpUrl): String = buildString {\n        val cookies = cookieJar.loadForRequest(url)\n        cookies.forEachIndexed { index, cookie ->\n            if (index > 0) append(\"; \")\n            append(cookie.name).append('=').append(cookie.value)\n        }\n    }\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/lib/cronet/CronetHelper.kt",
    "content": "@file:Keep\n@file:Suppress(\"DEPRECATION\")\n\npackage io.legado.app.lib.cronet\n\nimport androidx.annotation.Keep\nimport io.legado.app.constant.AppLog\nimport io.legado.app.help.http.CookieManager.cookieJarHeader\nimport io.legado.app.help.http.SSLHelper\nimport io.legado.app.help.http.okHttpClient\nimport io.legado.app.utils.DebugLog\nimport io.legado.app.utils.externalCache\nimport okhttp3.Headers\nimport okhttp3.MediaType\nimport okhttp3.Request\nimport org.chromium.net.CronetEngine.Builder.HTTP_CACHE_DISK\nimport org.chromium.net.ExperimentalCronetEngine\nimport org.chromium.net.UploadDataProvider\nimport org.chromium.net.UrlRequest\nimport org.chromium.net.X509Util\nimport org.json.JSONObject\nimport splitties.init.appCtx\n\ninternal const val BUFFER_SIZE = 32 * 1024\n\nval cronetEngine: ExperimentalCronetEngine? by lazy {\n    CronetLoader.preDownload()\n    disableCertificateVerify()\n    val builder = ExperimentalCronetEngine.Builder(appCtx).apply {\n        if (CronetLoader.install()) {\n            setLibraryLoader(CronetLoader)//设置自定义so库加载\n        }\n        setStoragePath(appCtx.externalCache.absolutePath)//设置缓存路径\n        enableHttpCache(HTTP_CACHE_DISK, (1024 * 1024 * 50).toLong())//设置50M的磁盘缓存\n        enableQuic(true)//设置支持http/3\n        enableHttp2(true)  //设置支持http/2\n        enablePublicKeyPinningBypassForLocalTrustAnchors(true)\n        enableBrotli(true)//Brotli压缩\n        setExperimentalOptions(options)\n    }\n    try {\n        val engine = builder.build()\n        DebugLog.d(\"Cronet Version:\", engine.versionString)\n        return@lazy engine\n    } catch (e: Throwable) {\n        AppLog.put(\"初始化cronetEngine出错\", e)\n        return@lazy null\n    }\n}\n\nval options by lazy {\n    val options = JSONObject()\n\n    //设置域名映射规则\n    //MAP hostname ip,MAP hostname ip\n//    val host = JSONObject()\n//    host.put(\"host_resolver_rules\",\"\")\n//    options.put(\"HostResolverRules\", host)\n\n    //启用DnsHttpsSvcb更容易迁移到http3\n    val dnsSvcb = JSONObject()\n    dnsSvcb.put(\"enable\", true)\n    dnsSvcb.put(\"enable_insecure\", true)\n    dnsSvcb.put(\"use_alpn\", true)\n    options.put(\"UseDnsHttpsSvcb\", dnsSvcb)\n\n    options.put(\"AsyncDNS\", JSONObject(\"{'enable':true}\"))\n\n\n    options.toString()\n}\n\nfun buildRequest(request: Request, callback: UrlRequest.Callback): UrlRequest? {\n    val url = request.url.toString()\n    val headers: Headers = request.headers\n    val requestBody = request.body\n    return cronetEngine?.newUrlRequestBuilder(\n        url,\n        callback,\n        okHttpClient.dispatcher.executorService\n    )?.apply {\n        setHttpMethod(request.method)//设置\n        allowDirectExecutor()\n        headers.forEachIndexed { index, _ ->\n            if (headers.name(index) == cookieJarHeader) return@forEachIndexed\n            addHeader(headers.name(index), headers.value(index))\n        }\n        if (requestBody != null) {\n            val contentType: MediaType? = requestBody.contentType()\n            if (contentType != null) {\n                addHeader(\"Content-Type\", contentType.toString())\n            } else {\n                addHeader(\"Content-Type\", \"text/plain\")\n            }\n            val provider: UploadDataProvider = if (requestBody.contentLength() > BUFFER_SIZE) {\n                LargeBodyUploadProvider(requestBody, okHttpClient.dispatcher.executorService)\n            } else {\n                BodyUploadProvider(requestBody)\n            }\n            provider.use {\n                this.setUploadDataProvider(it, okHttpClient.dispatcher.executorService)\n            }\n\n        }\n\n    }?.build()\n\n}\n\nprivate fun disableCertificateVerify() {\n    runCatching {\n        val sDefaultTrustManager = X509Util::class.java.getDeclaredField(\"sDefaultTrustManager\")\n        sDefaultTrustManager.isAccessible = true\n        sDefaultTrustManager.set(null, SSLHelper.unsafeTrustManagerExtensions)\n    }\n    runCatching {\n        val sTestTrustManager = X509Util::class.java.getDeclaredField(\"sTestTrustManager\")\n        sTestTrustManager.isAccessible = true\n        sTestTrustManager.set(null, SSLHelper.unsafeTrustManagerExtensions)\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/lib/cronet/CronetInterceptor.kt",
    "content": "package io.legado.app.lib.cronet\n\nimport android.annotation.SuppressLint\nimport android.os.Build\nimport androidx.annotation.Keep\nimport io.legado.app.help.http.CookieManager\nimport io.legado.app.help.http.CookieManager.cookieJarHeader\nimport io.legado.app.utils.printOnDebug\nimport okhttp3.Call\nimport okhttp3.CookieJar\nimport okhttp3.HttpUrl\nimport okhttp3.Interceptor\nimport okhttp3.Request\nimport okhttp3.Response\nimport java.io.IOException\n\n@Keep\n@Suppress(\"unused\")\nclass CronetInterceptor(private val cookieJar: CookieJar) : Interceptor {\n\n    @Throws(IOException::class)\n    override fun intercept(chain: Interceptor.Chain): Response {\n        if (chain.call().isCanceled()) {\n            throw IOException(\"Canceled\")\n        }\n        val original: Request = chain.request()\n        //Cronet未初始化\n        if (!CronetLoader.install() || cronetEngine == null) {\n            return chain.proceed(original)\n        }\n        val cronetException: Exception\n        try {\n            val builder: Request.Builder = original.newBuilder()\n            //移除Keep-Alive,手动设置会导致400 BadRequest\n            builder.removeHeader(\"Keep-Alive\")\n            builder.removeHeader(\"Accept-Encoding\")\n\n            // https://github.com/gedoor/legado/issues/5025#issuecomment-2851156500\n            if (!original.isHttps &&\n                original.header(\"User-Agent\")?.startsWith(\"Mozilla\", true) == true\n            ) {\n                val referer = original.header(\"Referer\")\n                if (referer != null && referer.startsWith(\"https:\", true)) {\n                    builder.header(\"Referer\", \"http\" + referer.substring(5))\n                }\n            }\n\n            var newReq = builder.build()\n\n            if (newReq.header(cookieJarHeader) != null) {\n                newReq = CookieManager.loadRequest(newReq)\n            }\n\n            return proceedWithCronet(newReq, chain.call(), chain.readTimeoutMillis())!!\n        } catch (e: Exception) {\n            cronetException = e\n            //不能抛出错误,抛出错误会导致应用崩溃\n            //遇到Cronet处理有问题时的情况，如证书过期等等，回退到okhttp处理\n            if (!e.message.toString().contains(\"ERR_CERT_\", true)\n                && !e.message.toString().contains(\"ERR_SSL_\", true)\n            ) {\n                e.printOnDebug()\n            }\n        }\n        try {\n            return chain.proceed(original)\n        } catch (e: Exception) {\n            e.addSuppressed(cronetException)\n            throw e\n        }\n    }\n\n    @SuppressLint(\"ObsoleteSdkInt\")\n    @Throws(IOException::class)\n    private fun proceedWithCronet(request: Request, call: Call, readTimeoutMillis: Int): Response? {\n        val callBack = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {\n            NewCallBack(request, call, readTimeoutMillis)\n        } else {\n            OldCallback(request, call, readTimeoutMillis)\n        }\n        buildRequest(request, callBack)?.let {\n            return callBack.waitForDone(it)\n        }\n        return null\n    }\n\n\n    /** Returns a 'Cookie' HTTP request header with all cookies, like `a=b; c=d`. */\n    private fun getCookie(url: HttpUrl): String = buildString {\n        val cookies = cookieJar.loadForRequest(url)\n        cookies.forEachIndexed { index, cookie ->\n            if (index > 0) append(\"; \")\n            append(cookie.name).append('=').append(cookie.value)\n        }\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/lib/cronet/CronetLoader.kt",
    "content": "package io.legado.app.lib.cronet\n\nimport android.annotation.SuppressLint\nimport android.content.Context\nimport android.content.pm.ApplicationInfo\nimport android.os.Build\nimport android.text.TextUtils\nimport androidx.annotation.Keep\nimport io.legado.app.BuildConfig\nimport io.legado.app.help.coroutine.Coroutine\nimport io.legado.app.help.http.Cronet\nimport io.legado.app.utils.DebugLog\nimport io.legado.app.utils.printOnDebug\nimport org.chromium.net.CronetEngine\nimport org.json.JSONObject\nimport splitties.init.appCtx\nimport java.io.BufferedReader\nimport java.io.File\nimport java.io.FileInputStream\nimport java.io.FileOutputStream\nimport java.io.IOException\nimport java.io.InputStream\nimport java.io.InputStreamReader\nimport java.io.OutputStream\nimport java.math.BigInteger\nimport java.net.HttpURLConnection\nimport java.net.URL\nimport java.security.MessageDigest\nimport java.util.Objects\n\n@Suppress(\"ConstPropertyName\")\n@Keep\nobject CronetLoader : CronetEngine.Builder.LibraryLoader(), Cronet.LoaderInterface {\n    //https://storage.googleapis.com/chromium-cronet/android/92.0.4515.159/Release/cronet/libs/arm64-v8a/libcronet.92.0.4515.159.so\n\n    private const val soVersion = BuildConfig.Cronet_Version\n    private const val soName = \"libcronet.$soVersion.so\"\n    private val soUrl: String\n    private val soFile: File\n    private val downloadFile: File\n    private var cpuAbi: String? = null\n    private var md5: String\n    var download = false\n\n    @Volatile\n    private var cacheInstall = false\n\n    init {\n        soUrl = (\"https://storage.googleapis.com/chromium-cronet/android/\"\n                + soVersion + \"/Release/cronet/libs/\"\n                + getCpuAbi(appCtx) + \"/\" + soName)\n        md5 = getMd5(appCtx)\n        val dir = appCtx.getDir(\"cronet\", Context.MODE_PRIVATE)\n        soFile = File(dir.toString() + \"/\" + getCpuAbi(appCtx), soName)\n        downloadFile = File(appCtx.cacheDir.toString() + \"/so_download\", soName)\n        DebugLog.d(javaClass.simpleName, \"soName+:$soName\")\n        DebugLog.d(javaClass.simpleName, \"destSuccessFile:$soFile\")\n        DebugLog.d(javaClass.simpleName, \"tempFile:$downloadFile\")\n        DebugLog.d(javaClass.simpleName, \"soUrl:$soUrl\")\n    }\n\n    /**\n     * 判断Cronet是否安装完成\n     */\n    override fun install(): Boolean {\n        synchronized(this) {\n            if (cacheInstall) {\n                return true\n            }\n        }\n\n        if (md5.length != 32 || !soFile.exists() || md5 != getFileMD5(soFile)) {\n            cacheInstall = false\n            return cacheInstall\n        }\n        cacheInstall = soFile.exists()\n        return cacheInstall\n    }\n\n\n    /**\n     * 预加载Cronet\n     */\n    override fun preDownload() {\n        Coroutine.async {\n            //md5 = getUrlMd5(md5Url)\n            if (soFile.exists() && md5 == getFileMD5(soFile)) {\n                DebugLog.d(javaClass.simpleName, \"So 库已存在\")\n            } else {\n                download(soUrl, md5, downloadFile, soFile)\n            }\n            DebugLog.d(javaClass.simpleName, soName)\n        }\n    }\n\n    private fun getMd5(context: Context): String {\n        val stringBuilder = StringBuilder()\n        return try {\n            //获取assets资源管理器\n            val assetManager = context.assets\n            //通过管理器打开文件并读取\n            val bf = BufferedReader(\n                InputStreamReader(\n                    assetManager.open(\"cronet.json\")\n                )\n            )\n            var line: String?\n            while (bf.readLine().also { line = it } != null) {\n                stringBuilder.append(line)\n            }\n            JSONObject(stringBuilder.toString()).optString(getCpuAbi(context), \"\")\n        } catch (e: java.lang.Exception) {\n            return \"\"\n        }\n    }\n\n    @SuppressLint(\"UnsafeDynamicallyLoadedCode\")\n    override fun loadLibrary(libName: String) {\n        DebugLog.d(javaClass.simpleName, \"libName:$libName\")\n        val start = System.currentTimeMillis()\n        @Suppress(\"SameParameterValue\")\n        try {\n            //非cronet的so调用系统方法加载\n            if (!libName.contains(\"cronet\")) {\n                System.loadLibrary(libName)\n                return\n            }\n            //以下逻辑为cronet加载，优先加载本地，否则从远程加载\n            //首先调用系统行为进行加载\n            System.loadLibrary(libName)\n            DebugLog.d(javaClass.simpleName, \"load from system\")\n        } catch (e: Throwable) {\n            //如果找不到，则从远程下载\n            //删除历史文件\n            deleteHistoryFile(Objects.requireNonNull(soFile.parentFile), soFile)\n            //md5 = getUrlMd5(md5Url)\n            DebugLog.d(javaClass.simpleName, \"soMD5:$md5\")\n            if (md5.length != 32 || soUrl.isEmpty()) {\n                //如果md5或下载的url为空，则调用系统行为进行加载\n                System.loadLibrary(libName)\n                return\n            }\n            if (!soFile.exists() || !soFile.isFile) {\n                soFile.delete()\n                download(soUrl, md5, downloadFile, soFile)\n                //如果文件不存在或不是文件，则调用系统行为进行加载\n                System.loadLibrary(libName)\n                return\n            }\n            if (soFile.exists()) {\n                //如果文件存在，则校验md5值\n                val fileMD5 = getFileMD5(soFile)\n                if (fileMD5 != null && fileMD5.equals(md5, ignoreCase = true)) {\n                    //md5值一样，则加载\n                    System.load(soFile.absolutePath)\n                    DebugLog.d(javaClass.simpleName, \"load from:$soFile\")\n                    return\n                }\n                //md5不一样则删除\n                soFile.delete()\n            }\n            //不存在则下载\n            download(soUrl, md5, downloadFile, soFile)\n            //使用系统加载方法\n            System.loadLibrary(libName)\n        } finally {\n            DebugLog.d(javaClass.simpleName, \"time:\" + (System.currentTimeMillis() - start))\n        }\n    }\n\n    @SuppressLint(\"DiscouragedPrivateApi\")\n    private fun getCpuAbi(context: Context): String? {\n        if (cpuAbi != null) {\n            return cpuAbi\n        }\n        // 5.0以上Application才有primaryCpuAbi字段\n        try {\n            val appInfo = context.applicationInfo\n            val abiField = ApplicationInfo::class.java.getDeclaredField(\"primaryCpuAbi\")\n            abiField.isAccessible = true\n            cpuAbi = abiField.get(appInfo) as String?\n        } catch (e: Exception) {\n            e.printOnDebug()\n        }\n        if (TextUtils.isEmpty(cpuAbi)) {\n            cpuAbi = Build.SUPPORTED_ABIS[0]\n        }\n        return cpuAbi\n    }\n\n\n    /**\n     * 删除历史文件\n     */\n    private fun deleteHistoryFile(dir: File, currentFile: File?) {\n        val files = dir.listFiles()\n        @Suppress(\"SameParameterValue\")\n        if (files != null && files.isNotEmpty()) {\n            for (f in files) {\n                if (f.exists() && (currentFile == null || f.absolutePath != currentFile.absolutePath)) {\n                    val delete = f.delete()\n                    DebugLog.d(javaClass.simpleName, \"delete file: $f result: $delete\")\n                    if (!delete) {\n                        f.deleteOnExit()\n                    }\n                }\n            }\n        }\n    }\n\n    /**\n     * 下载文件\n     */\n    private fun downloadFileIfNotExist(url: String, destFile: File): Boolean {\n        var inputStream: InputStream? = null\n        var outputStream: OutputStream? = null\n        try {\n            val connection = URL(url).openConnection() as HttpURLConnection\n            inputStream = connection.inputStream\n            if (destFile.exists()) {\n                return true\n            }\n            destFile.parentFile!!.mkdirs()\n            destFile.createNewFile()\n            outputStream = FileOutputStream(destFile)\n            val buffer = ByteArray(32768)\n            var read: Int\n            while (inputStream.read(buffer).also { read = it } != -1) {\n                outputStream.write(buffer, 0, read)\n                outputStream.flush()\n            }\n            return true\n        } catch (e: Throwable) {\n            e.printOnDebug()\n            if (destFile.exists() && !destFile.delete()) {\n                destFile.deleteOnExit()\n            }\n        } finally {\n            if (inputStream != null) {\n                try {\n                    inputStream.close()\n                } catch (e: IOException) {\n                    e.printOnDebug()\n                }\n            }\n            if (outputStream != null) {\n                try {\n                    outputStream.close()\n                } catch (e: IOException) {\n                    e.printOnDebug()\n                }\n            }\n        }\n        return false\n    }\n\n    /**\n     * 下载并拷贝文件\n     */\n    @Suppress(\"SameParameterValue\")\n    @Synchronized\n    private fun download(\n        url: String,\n        md5: String?,\n        downloadTempFile: File,\n        destSuccessFile: File\n    ) {\n        if (download) {\n            return\n        }\n        download = true\n\n        Coroutine.async {\n            val result = downloadFileIfNotExist(url, downloadTempFile)\n            DebugLog.d(javaClass.simpleName, \"download result:$result\")\n            //文件md5再次校验\n            val fileMD5 = getFileMD5(downloadTempFile)\n            if (md5 != null && !md5.equals(fileMD5, ignoreCase = true)) {\n                val delete = downloadTempFile.delete()\n                if (!delete) {\n                    downloadTempFile.deleteOnExit()\n                }\n                download = false\n                return@async\n            }\n            DebugLog.d(javaClass.simpleName, \"download success, copy to $destSuccessFile\")\n            //下载成功拷贝文件\n            copyFile(downloadTempFile, destSuccessFile)\n            cacheInstall = false\n            val parentFile = downloadTempFile.parentFile\n            @Suppress(\"SameParameterValue\")\n            (deleteHistoryFile(parentFile!!, null))\n        }\n    }\n\n    /**\n     * 拷贝文件\n     */\n    private fun copyFile(source: File?, dest: File?): Boolean {\n        if (source == null || !source.exists() || !source.isFile || dest == null) {\n            return false\n        }\n        if (source.absolutePath == dest.absolutePath) {\n            return true\n        }\n        var fileInputStream: FileInputStream? = null\n        var os: FileOutputStream? = null\n        val parent = dest.parentFile\n        if (parent != null && !parent.exists()) {\n            val mkdirs = parent.mkdirs()\n            if (!mkdirs) {\n                parent.mkdirs()\n            }\n        }\n        try {\n            fileInputStream = FileInputStream(source)\n            os = FileOutputStream(dest, false)\n            val buffer = ByteArray(1024 * 512)\n            var length: Int\n            while (fileInputStream.read(buffer).also { length = it } > 0) {\n                os.write(buffer, 0, length)\n            }\n            return true\n        } catch (e: Exception) {\n            e.printOnDebug()\n        } finally {\n            if (fileInputStream != null) {\n                try {\n                    fileInputStream.close()\n                } catch (e: Exception) {\n                    e.printOnDebug()\n                }\n            }\n            if (os != null) {\n                try {\n                    os.close()\n                } catch (e: Exception) {\n                    e.printOnDebug()\n                }\n            }\n        }\n        return false\n    }\n\n    /**\n     * 获得文件md5\n     */\n    private fun getFileMD5(file: File): String? {\n        var fileInputStream: FileInputStream? = null\n        try {\n            fileInputStream = FileInputStream(file)\n            val md5 = MessageDigest.getInstance(\"MD5\")\n            val buffer = ByteArray(1024)\n            var numRead: Int\n            while (fileInputStream.read(buffer).also { numRead = it } > 0) {\n                md5.update(buffer, 0, numRead)\n            }\n            return String.format(\"%032x\", BigInteger(1, md5.digest())).lowercase()\n        } catch (e: Exception) {\n            e.printOnDebug()\n        } catch (e: OutOfMemoryError) {\n            e.printOnDebug()\n        } finally {\n            if (fileInputStream != null) {\n                try {\n                    fileInputStream.close()\n                } catch (e: Exception) {\n                    e.printOnDebug()\n                }\n            }\n        }\n        return null\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/lib/cronet/LargeBodyUploadProvider.kt",
    "content": "package io.legado.app.lib.cronet\n\nimport androidx.annotation.Keep\nimport okhttp3.RequestBody\nimport okio.BufferedSource\nimport okio.Pipe\nimport okio.buffer\nimport org.chromium.net.UploadDataProvider\nimport org.chromium.net.UploadDataSink\nimport java.io.IOException\nimport java.nio.ByteBuffer\nimport java.util.concurrent.ExecutorService\n\n/**\n * 用于上传大型文件\n *\n * @property body\n * @property executorService\n */\n@Keep\nclass LargeBodyUploadProvider(\n    private val body: RequestBody,\n    private val executorService: ExecutorService\n) : UploadDataProvider(), AutoCloseable {\n    private val pipe = Pipe(BUFFER_SIZE.toLong())\n    private var source: BufferedSource = pipe.source.buffer()\n\n    @Volatile\n    private var filled: Boolean = false\n    override fun getLength(): Long {\n        return body.contentLength()\n    }\n\n    override fun read(uploadDataSink: UploadDataSink, byteBuffer: ByteBuffer) {\n        if (!filled) {\n            fillBuffer()\n        }\n        check(byteBuffer.hasRemaining()) { \"Cronet passed a buffer with no bytes remaining\" }\n        var read: Int\n        var bytesRead = 0\n        while (bytesRead <= 0) {\n            read = source.read(byteBuffer)\n            bytesRead += read\n        }\n        uploadDataSink.onReadSucceeded(false)\n    }\n\n    @Synchronized\n    private fun fillBuffer() {\n        executorService.submit {\n            try {\n                val writeSink = pipe.sink.buffer()\n                filled = true\n                body.writeTo(writeSink)\n                writeSink.flush()\n            } catch (e: IOException) {\n                e.printStackTrace()\n            }\n\n        }\n\n    }\n\n    override fun rewind(p0: UploadDataSink?) {\n        check(body.isOneShot()) { \"Okhttp RequestBody is OneShot\" }\n        filled = false\n        fillBuffer()\n    }\n\n    override fun close() {\n//        pipe.cancel()\n//        source.close()\n        super.close()\n    }\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/lib/cronet/NewCallBack.kt",
    "content": "package io.legado.app.lib.cronet\n\nimport android.annotation.SuppressLint\nimport android.os.Build\nimport androidx.annotation.Keep\nimport androidx.annotation.RequiresApi\nimport okhttp3.Call\nimport okhttp3.Request\nimport okhttp3.Response\nimport org.chromium.net.UrlRequest\nimport java.io.IOException\nimport java.util.concurrent.CompletableFuture\nimport java.util.concurrent.TimeUnit\n\n@SuppressLint(\"ObsoleteSdkInt\")\n@Keep\n@RequiresApi(api = Build.VERSION_CODES.N)\nclass NewCallBack(originalRequest: Request, mCall: Call, readTimeoutMillis: Int) :\n    AbsCallBack(originalRequest, mCall, readTimeoutMillis) {\n\n    private val responseFuture = CompletableFuture<Response>()\n\n    @Throws(IOException::class)\n    override fun waitForDone(urlRequest: UrlRequest): Response {\n        urlRequest.start()\n        startCheckCancelJob(urlRequest)\n        //DebugLog.i(javaClass.simpleName, \"start ${originalRequest.method} ${originalRequest.url}\")\n        return if (mCall.timeout().timeoutNanos() > 0) {\n            responseFuture.get(mCall.timeout().timeoutNanos(), TimeUnit.NANOSECONDS)\n        } else {\n            return responseFuture.get()\n        }\n\n    }\n\n    /**\n     * 当发生错误时，通知子类终止阻塞抛出错误\n     * @param error\n     */\n    override fun onError(error: IOException) {\n        responseFuture.completeExceptionally(error)\n    }\n\n    /**\n     * 请求成功后，通知子类结束阻塞，返回response\n     * @param response\n     */\n    override fun onSuccess(response: Response) {\n        responseFuture.complete(response)\n    }\n\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/lib/cronet/OldCallback.kt",
    "content": "package io.legado.app.lib.cronet\n\nimport android.os.ConditionVariable\nimport androidx.annotation.Keep\nimport okhttp3.Call\nimport okhttp3.Request\nimport okhttp3.Response\nimport org.chromium.net.UrlRequest\nimport java.io.IOException\n\n@Keep\nclass OldCallback(originalRequest: Request, mCall: Call, readTimeoutMillis: Int) :\n    AbsCallBack(originalRequest, mCall, readTimeoutMillis) {\n\n    private val mResponseCondition = ConditionVariable()\n    private var mException: IOException? = null\n\n    @Throws(IOException::class)\n    override fun waitForDone(urlRequest: UrlRequest): Response {\n        //获取okhttp call的完整请求的超时时间\n        val timeOutMs: Long = mCall.timeout().timeoutNanos() / 1000000\n        urlRequest.start()\n        startCheckCancelJob(urlRequest)\n        if (timeOutMs > 0) {\n            mResponseCondition.block(timeOutMs)\n        } else {\n            mResponseCondition.block()\n        }\n        //ConditionVariable 正常open或者超时open后，检查urlRequest是否完成\n        if (!urlRequest.isDone) {\n            urlRequest.cancel()\n            mException = IOException(\"Cronet timeout after wait \" + timeOutMs + \"ms\")\n        }\n\n        if (mException != null) {\n            throw mException as IOException\n        }\n        return mResponse\n    }\n\n    /**\n     * 当发生错误时，通知子类终止阻塞抛出错误\n     * @param error\n     */\n    override fun onError(error: IOException) {\n        mException = error\n        mResponseCondition.open()\n    }\n\n    /**\n     * 请求成功后，通知子类结束阻塞，返回response\n     * @param response\n     */\n    override fun onSuccess(response: Response) {\n        mResponseCondition.open()\n    }\n\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/lib/dialogs/AlertBuilder.kt",
    "content": "@file:Suppress(\"unused\")\n\npackage io.legado.app.lib.dialogs\n\nimport android.annotation.SuppressLint\nimport android.content.Context\nimport android.content.DialogInterface\nimport android.graphics.drawable.Drawable\nimport android.view.KeyEvent\nimport android.view.View\nimport androidx.annotation.DrawableRes\nimport androidx.annotation.StringRes\nimport io.legado.app.R\n\n@SuppressLint(\"SupportAnnotationUsage\")\ninterface AlertBuilder<out D : DialogInterface> {\n    val ctx: Context\n\n    fun setTitle(title: CharSequence)\n\n    fun setTitle(titleResource: Int)\n\n    fun setMessage(message: CharSequence)\n\n    fun setMessage(messageResource: Int)\n\n    fun setIcon(icon: Drawable)\n\n    fun setIcon(@DrawableRes iconResource: Int)\n\n    fun setCustomTitle(customTitle: View)\n\n    fun setCustomView(customView: View)\n\n    fun setCancelable(isCancelable: Boolean)\n\n    fun positiveButton(buttonText: String, onClicked: ((dialog: DialogInterface) -> Unit)? = null)\n    fun positiveButton(\n        @StringRes buttonTextResource: Int,\n        onClicked: ((dialog: DialogInterface) -> Unit)? = null\n    )\n\n    fun negativeButton(buttonText: String, onClicked: ((dialog: DialogInterface) -> Unit)? = null)\n    fun negativeButton(\n        @StringRes buttonTextResource: Int,\n        onClicked: ((dialog: DialogInterface) -> Unit)? = null\n    )\n\n    fun neutralButton(buttonText: String, onClicked: ((dialog: DialogInterface) -> Unit)? = null)\n    fun neutralButton(\n        @StringRes buttonTextResource: Int,\n        onClicked: ((dialog: DialogInterface) -> Unit)? = null\n    )\n\n    fun onCancelled(handler: (dialog: DialogInterface) -> Unit)\n\n    fun onKeyPressed(handler: (dialog: DialogInterface, keyCode: Int, e: KeyEvent) -> Boolean)\n\n    fun onDismiss(handler: (dialog: DialogInterface) -> Unit)\n\n    fun items(\n        items: List<CharSequence>,\n        onItemSelected: (dialog: DialogInterface, index: Int) -> Unit\n    )\n\n    fun <T> items(\n        items: List<T>,\n        onItemSelected: (dialog: DialogInterface, item: T, index: Int) -> Unit\n    )\n\n    fun multiChoiceItems(\n        items: Array<String>,\n        checkedItems: BooleanArray,\n        onClick: (dialog: DialogInterface, which: Int, isChecked: Boolean) -> Unit\n    )\n\n    fun singleChoiceItems(\n        items: Array<String>,\n        checkedItem: Int = 0,\n        onClick: ((dialog: DialogInterface, which: Int) -> Unit)? = null\n    )\n\n    fun build(): D\n    fun show(): D\n\n\n    fun customTitle(view: () -> View) {\n        setCustomTitle(view())\n    }\n\n    fun customView(view: () -> View) {\n        setCustomView(view())\n    }\n\n    fun okButton(handler: ((dialog: DialogInterface) -> Unit)? = null) =\n        positiveButton(android.R.string.ok, handler)\n\n    fun cancelButton(handler: ((dialog: DialogInterface) -> Unit)? = null) =\n        negativeButton(android.R.string.cancel, handler)\n\n    fun yesButton(handler: ((dialog: DialogInterface) -> Unit)? = null) =\n        positiveButton(R.string.yes, handler)\n\n    fun noButton(handler: ((dialog: DialogInterface) -> Unit)? = null) =\n        negativeButton(R.string.no, handler)\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/lib/dialogs/AndroidAlertBuilder.kt",
    "content": "package io.legado.app.lib.dialogs\n\nimport android.content.Context\nimport android.content.DialogInterface\nimport android.graphics.drawable.Drawable\nimport android.view.KeyEvent\nimport android.view.View\nimport androidx.appcompat.app.AlertDialog\nimport io.legado.app.R\nimport io.legado.app.help.config.AppConfig\nimport io.legado.app.utils.applyTint\n\ninternal class AndroidAlertBuilder(override val ctx: Context) : AlertBuilder<AlertDialog> {\n    private val builder = AlertDialog.Builder(ctx)\n\n    override fun setTitle(title: CharSequence) {\n        builder.setTitle(title)\n    }\n\n    override fun setTitle(titleResource: Int) {\n        builder.setTitle(titleResource)\n    }\n\n    override fun setMessage(message: CharSequence) {\n        builder.setMessage(message)\n    }\n\n    override fun setMessage(messageResource: Int) {\n        builder.setMessage(messageResource)\n    }\n\n    override fun setIcon(icon: Drawable) {\n        builder.setIcon(icon)\n    }\n\n    override fun setIcon(iconResource: Int) {\n        builder.setIcon(iconResource)\n    }\n\n    override fun setCustomTitle(customTitle: View) {\n        builder.setCustomTitle(customTitle)\n    }\n\n    override fun setCustomView(customView: View) {\n        builder.setView(customView)\n    }\n\n    override fun setCancelable(isCancelable: Boolean) {\n        builder.setCancelable(isCancelable)\n    }\n\n    override fun onCancelled(handler: (DialogInterface) -> Unit) {\n        builder.setOnCancelListener(handler)\n    }\n\n    override fun onKeyPressed(handler: (dialog: DialogInterface, keyCode: Int, e: KeyEvent) -> Boolean) {\n        builder.setOnKeyListener(handler)\n    }\n\n    override fun positiveButton(\n        buttonText: String,\n        onClicked: ((dialog: DialogInterface) -> Unit)?\n    ) {\n        builder.setPositiveButton(buttonText) { dialog, _ -> onClicked?.invoke(dialog) }\n    }\n\n    override fun positiveButton(\n        buttonTextResource: Int,\n        onClicked: ((dialog: DialogInterface) -> Unit)?\n    ) {\n        builder.setPositiveButton(buttonTextResource) { dialog, _ -> onClicked?.invoke(dialog) }\n    }\n\n    override fun negativeButton(\n        buttonText: String,\n        onClicked: ((dialog: DialogInterface) -> Unit)?\n    ) {\n        builder.setNegativeButton(buttonText) { dialog, _ -> onClicked?.invoke(dialog) }\n    }\n\n    override fun negativeButton(\n        buttonTextResource: Int,\n        onClicked: ((dialog: DialogInterface) -> Unit)?\n    ) {\n        builder.setNegativeButton(buttonTextResource) { dialog, _ -> onClicked?.invoke(dialog) }\n    }\n\n    override fun neutralButton(\n        buttonText: String,\n        onClicked: ((dialog: DialogInterface) -> Unit)?\n    ) {\n        builder.setNeutralButton(buttonText) { dialog, _ -> onClicked?.invoke(dialog) }\n    }\n\n    override fun neutralButton(\n        buttonTextResource: Int,\n        onClicked: ((dialog: DialogInterface) -> Unit)?\n    ) {\n        builder.setNeutralButton(buttonTextResource) { dialog, _ -> onClicked?.invoke(dialog) }\n    }\n\n    override fun onDismiss(handler: (dialog: DialogInterface) -> Unit) {\n        builder.setOnDismissListener(handler)\n    }\n\n    override fun items(\n        items: List<CharSequence>,\n        onItemSelected: (dialog: DialogInterface, index: Int) -> Unit\n    ) {\n        builder.setItems(Array(items.size) { i -> items[i].toString() }) { dialog, which ->\n            onItemSelected(dialog, which)\n        }\n    }\n\n    override fun <T> items(\n        items: List<T>,\n        onItemSelected: (dialog: DialogInterface, item: T, index: Int) -> Unit\n    ) {\n        builder.setItems(Array(items.size) { i -> items[i].toString() }) { dialog, which ->\n            onItemSelected(dialog, items[which], which)\n        }\n    }\n\n    override fun multiChoiceItems(\n        items: Array<String>,\n        checkedItems: BooleanArray,\n        onClick: (dialog: DialogInterface, which: Int, isChecked: Boolean) -> Unit\n    ) {\n        builder.setMultiChoiceItems(items, checkedItems) { dialog, which, isChecked ->\n            onClick(dialog, which, isChecked)\n        }\n    }\n\n    override fun singleChoiceItems(\n        items: Array<String>,\n        checkedItem: Int,\n        onClick: ((dialog: DialogInterface, which: Int) -> Unit)?\n    ) {\n        builder.setSingleChoiceItems(items, checkedItem) { dialog, which ->\n            onClick?.invoke(dialog, which)\n        }\n    }\n\n    override fun build(): AlertDialog {\n        val dialog = builder.create()\n        if (AppConfig.isEInkMode) {\n            dialog.window?.run {\n                val attr = attributes\n                attr.dimAmount = 0f\n                attr.windowAnimations = 0\n                attributes = attr\n                setBackgroundDrawableResource(R.drawable.bg_eink_border_dialog)\n            }\n        }\n        return dialog\n    }\n\n    override fun show(): AlertDialog {\n        val dialog = builder.show().applyTint()\n        if (AppConfig.isEInkMode) {\n            dialog.window?.run {\n                val attr = attributes\n                attr.dimAmount = 0f\n                attr.windowAnimations = 0\n                attributes = attr\n                setBackgroundDrawableResource(R.drawable.bg_eink_border_dialog)\n            }\n        }\n        return dialog\n    }\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/lib/dialogs/AndroidDialogs.kt",
    "content": "@file:Suppress(\"NOTHING_TO_INLINE\", \"unused\", \"DEPRECATION\")\n\npackage io.legado.app.lib.dialogs\n\nimport android.app.ProgressDialog\nimport android.content.Context\nimport android.content.DialogInterface\nimport androidx.appcompat.app.AlertDialog\nimport androidx.fragment.app.Fragment\n\nfun Context.alert(\n    title: CharSequence? = null,\n    message: CharSequence? = null,\n    init: (AlertBuilder<DialogInterface>.() -> Unit)? = null\n): AlertDialog {\n    return AndroidAlertBuilder(this).apply {\n        if (title != null) {\n            this.setTitle(title)\n        }\n        if (message != null) {\n            this.setMessage(message)\n        }\n        if (init != null) init()\n    }.show()\n}\n\ninline fun Fragment.alert(\n    title: CharSequence? = null,\n    message: CharSequence? = null,\n    noinline init: (AlertBuilder<DialogInterface>.() -> Unit)? = null\n) = requireActivity().alert(title, message, init)\n\nfun Context.alert(\n    titleResource: Int? = null,\n    messageResource: Int? = null,\n    init: (AlertBuilder<DialogInterface>.() -> Unit)? = null\n): AlertDialog {\n    return AndroidAlertBuilder(this).apply {\n        if (titleResource != null) {\n            this.setTitle(titleResource)\n        }\n        if (messageResource != null) {\n            this.setMessage(messageResource)\n        }\n        if (init != null) init()\n    }.show()\n}\n\ninline fun Fragment.alert(\n    titleResource: Int? = null,\n    messageResource: Int? = null,\n    noinline init: (AlertBuilder<DialogInterface>.() -> Unit)? = null\n) = requireActivity().alert(titleResource, messageResource, init)\n\nfun Context.alert(init: AlertBuilder<AlertDialog>.() -> Unit): AlertDialog =\n    AndroidAlertBuilder(this).apply {\n        init()\n    }.show()\n\ninline fun Fragment.alert(noinline init: AlertBuilder<DialogInterface>.() -> Unit) =\n    requireContext().alert(init)\n\ninline fun Fragment.progressDialog(\n    title: Int? = null,\n    message: Int? = null,\n    noinline init: (ProgressDialog.() -> Unit)? = null\n) = requireActivity().progressDialog(title, message, init)\n\nfun Context.progressDialog(\n    title: Int? = null,\n    message: Int? = null,\n    init: (ProgressDialog.() -> Unit)? = null\n) = progressDialog(title?.let { getString(it) }, message?.let { getString(it) }, false, init)\n\n\ninline fun Fragment.indeterminateProgressDialog(\n    title: Int? = null,\n    message: Int? = null,\n    noinline init: (ProgressDialog.() -> Unit)? = null\n) = requireActivity().indeterminateProgressDialog(title, message, init)\n\nfun Context.indeterminateProgressDialog(\n    title: Int? = null,\n    message: Int? = null,\n    init: (ProgressDialog.() -> Unit)? = null\n) = progressDialog(title?.let { getString(it) }, message?.let { getString(it) }, true, init)\n\ninline fun Fragment.progressDialog(\n    title: CharSequence? = null,\n    message: CharSequence? = null,\n    noinline init: (ProgressDialog.() -> Unit)? = null\n) = requireActivity().progressDialog(title, message, init)\n\nfun Context.progressDialog(\n    title: CharSequence? = null,\n    message: CharSequence? = null,\n    init: (ProgressDialog.() -> Unit)? = null\n) = progressDialog(title, message, false, init)\n\n\ninline fun Fragment.indeterminateProgressDialog(\n    title: CharSequence? = null,\n    message: CharSequence? = null,\n    noinline init: (ProgressDialog.() -> Unit)? = null\n) = requireActivity().indeterminateProgressDialog(title, message, init)\n\nfun Context.indeterminateProgressDialog(\n    title: CharSequence? = null,\n    message: CharSequence? = null,\n    init: (ProgressDialog.() -> Unit)? = null\n) = progressDialog(title, message, true, init)\n\n\nprivate fun Context.progressDialog(\n    title: CharSequence? = null,\n    message: CharSequence? = null,\n    indeterminate: Boolean,\n    init: (ProgressDialog.() -> Unit)? = null\n) = ProgressDialog(this).apply {\n    isIndeterminate = indeterminate\n    if (!indeterminate) setProgressStyle(ProgressDialog.STYLE_HORIZONTAL)\n    if (message != null) setMessage(message)\n    if (title != null) setTitle(title)\n    if (init != null) init()\n    show()\n}\n\ntypealias AlertBuilderFactory<D> = (Context) -> AlertBuilder<D>\n\ninline fun <D : DialogInterface> Fragment.alert(\n    noinline factory: AlertBuilderFactory<D>,\n    title: String? = null,\n    message: String? = null,\n    noinline init: (AlertBuilder<D>.() -> Unit)? = null\n) = activity?.alert(factory, title, message, init)\n\nfun <D : DialogInterface> Context.alert(\n    factory: AlertBuilderFactory<D>,\n    title: String? = null,\n    message: String? = null,\n    init: (AlertBuilder<D>.() -> Unit)? = null\n): AlertBuilder<D> {\n    return factory(this).apply {\n        if (title != null) {\n            this.setTitle(title)\n        }\n        if (message != null) {\n            this.setMessage(message)\n        }\n        if (init != null) init()\n    }\n}\n\ninline fun <D : DialogInterface> Fragment.alert(\n    noinline factory: AlertBuilderFactory<D>,\n    titleResource: Int? = null,\n    messageResource: Int? = null,\n    noinline init: (AlertBuilder<D>.() -> Unit)? = null\n) = requireActivity().alert(factory, titleResource, messageResource, init)\n\nfun <D : DialogInterface> Context.alert(\n    factory: AlertBuilderFactory<D>,\n    titleResource: Int? = null,\n    messageResource: Int? = null,\n    init: (AlertBuilder<D>.() -> Unit)? = null\n): AlertBuilder<D> {\n    return factory(this).apply {\n        if (titleResource != null) {\n            this.setTitle(titleResource)\n        }\n        if (messageResource != null) {\n            this.setMessage(messageResource)\n        }\n        if (init != null) init()\n    }\n}\n\ninline fun <D : DialogInterface> Fragment.alert(\n    noinline factory: AlertBuilderFactory<D>,\n    noinline init: AlertBuilder<D>.() -> Unit\n) = requireActivity().alert(factory, init)\n\nfun <D : DialogInterface> Context.alert(\n    factory: AlertBuilderFactory<D>,\n    init: AlertBuilder<D>.() -> Unit\n): AlertBuilder<D> = factory(this).apply { init() }\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/lib/dialogs/AndroidSelectors.kt",
    "content": "/*\n * Copyright 2016 JetBrains s.r.o.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n@file:Suppress(\"unused\")\n\npackage io.legado.app.lib.dialogs\n\nimport android.content.Context\nimport android.content.DialogInterface\n\nfun Context.selector(\n    items: List<CharSequence>,\n    onClick: (DialogInterface, Int) -> Unit\n) {\n    with(AndroidAlertBuilder(this)) {\n        items(items, onClick)\n        show()\n    }\n}\n\nfun <T> Context.selector(\n    items: List<T>,\n    onClick: (DialogInterface, T, Int) -> Unit\n) {\n    with(AndroidAlertBuilder(this)) {\n        items(items, onClick)\n        show()\n    }\n}\n\nfun Context.selector(\n    title: CharSequence,\n    items: List<CharSequence>,\n    onClick: (DialogInterface, Int) -> Unit\n) {\n    with(AndroidAlertBuilder(this)) {\n        this.setTitle(title)\n        items(items, onClick)\n        show()\n    }\n}\n\nfun <T> Context.selector(\n    title: CharSequence,\n    items: List<T>,\n    onClick: (DialogInterface, T, Int) -> Unit\n) {\n    with(AndroidAlertBuilder(this)) {\n        this.setTitle(title)\n        items(items, onClick)\n        show()\n    }\n}\n\nfun Context.selector(\n    titleSource: Int,\n    items: List<CharSequence>,\n    onClick: (DialogInterface, Int) -> Unit\n) {\n    with(AndroidAlertBuilder(this)) {\n        this.setTitle(titleSource)\n        items(items, onClick)\n        show()\n    }\n}\n\nfun <T> Context.selector(\n    titleSource: Int,\n    items: List<T>,\n    onClick: (DialogInterface, T, Int) -> Unit\n) {\n    with(AndroidAlertBuilder(this)) {\n        this.setTitle(titleSource)\n        items(items, onClick)\n        show()\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/lib/dialogs/SelectItem.kt",
    "content": "package io.legado.app.lib.dialogs\n\n@Suppress(\"unused\")\ndata class SelectItem<T>(\n    val title: String,\n    val value: T\n) {\n\n    override fun toString(): String {\n        return title\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/lib/icu4j/CharsetDetector.java",
    "content": "// © 2016 and later: Unicode, Inc. and others.\n// License & terms of use: http://www.unicode.org/copyright.html\n/*\n  ******************************************************************************\n  Copyright (C) 2005-2016, International Business Machines Corporation and    *\n  others. All Rights Reserved.                                                *\n  ******************************************************************************\n */\npackage io.legado.app.lib.icu4j;\n\nimport android.os.ParcelFileDescriptor;\nimport android.system.OsConstants;\n\nimport androidx.annotation.NonNull;\nimport androidx.annotation.Nullable;\n\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.Reader;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.Collections;\nimport java.util.List;\n\n\n/**\n * <code>CharsetDetector</code> provides a facility for detecting the\n * charset or encoding of character data in an unknown format.\n * The input data can either be from an input stream or an array of bytes.\n * The result of the detection operation is a list of possibly matching\n * charsets, or, for simple use, you can just ask for a Java Reader that\n * will will work over the input data.\n * <p>\n * Character set detection is at best an imprecise operation.  The detection\n * process will attempt to identify the charset that best matches the characteristics\n * of the byte data, but the process is partly statistical in nature, and\n * the results can not be guaranteed to always be correct.\n * <p>\n * For best accuracy in charset detection, the input data should be primarily\n * in a single language, and a minimum of a few hundred bytes worth of plain text\n * in the language are needed.  The detection process will attempt to\n * ignore html or xml style markup that could otherwise obscure the content.\n * <p>\n *\n * @stable ICU 3.4\n */\n@SuppressWarnings({\"JavaDoc\", \"unused\", \"RedundantSuppression\"})\npublic class CharsetDetector {\n\n//   Question: Should we have getters corresponding to the setters for input text\n//   and declared encoding?\n\n//   A thought: If we were to create our own type of Java Reader, we could defer\n//   figuring out an actual charset for data that starts out with too much English\n//   only ASCII until the user actually read through to something that didn't look\n//   like 7 bit English.  If  nothing else ever appeared, we would never need to\n//   actually choose the \"real\" charset.  All assuming that the application just\n//   wants the data, and doesn't care about a char set name.\n\n    /**\n     * Constructor\n     *\n     * @stable ICU 3.4\n     */\n    public CharsetDetector() {\n    }\n\n    /**\n     * Set the declared encoding for charset detection.\n     * The declared encoding of an input text is an encoding obtained\n     * from an http header or xml declaration or similar source that\n     * can be provided as additional information to the charset detector.\n     * A match between a declared encoding and a possible detected encoding\n     * will raise the quality of that detected encoding by a small delta,\n     * and will also appear as a \"reason\" for the match.\n     * <p>\n     * A declared encoding that is incompatible with the input data being\n     * analyzed will not be added to the list of possible encodings.\n     *\n     * @param encoding The declared encoding\n     * @stable ICU 3.4\n     */\n    public CharsetDetector setDeclaredEncoding(String encoding) {\n        fDeclaredEncoding = encoding;\n        return this;\n    }\n\n    /**\n     * Set the input text (byte) data whose charset is to be detected.\n     *\n     * @param in the input text of unknown encoding\n     * @return This CharsetDetector\n     * @stable ICU 3.4\n     */\n    public CharsetDetector setText(@NonNull byte[] in) {\n        fRawInput = in;\n        fRawLength = in.length;\n\n        return this;\n    }\n\n    public CharsetDetector setText(@NonNull ParcelFileDescriptor pfd) {\n        fRawInput = new byte[kBufSize];\n        try {\n            android.system.Os.lseek(pfd.getFileDescriptor(), 0, OsConstants.SEEK_SET);\n            fRawLength = android.system.Os.read(pfd.getFileDescriptor(), fRawInput, 0, fRawInput.length);\n            android.system.Os.lseek(pfd.getFileDescriptor(), 0, OsConstants.SEEK_SET);\n        } catch (Exception e) {\n            throw new RuntimeException(e);\n        }\n        return this;\n    }\n\n    private static final int kBufSize = 8000;\n\n    /**\n     * Set the input text (byte) data whose charset is to be detected.\n     * <p>\n     * The input stream that supplies the character data must have markSupported()\n     * == true; the charset detection process will read a small amount of data,\n     * then return the stream to its original position via\n     * the InputStream.reset() operation.  The exact amount that will\n     * be read depends on the characteristics of the data itself.\n     *\n     * @param in the input text of unknown encoding\n     * @return This CharsetDetector\n     * @stable ICU 3.4\n     */\n\n    public CharsetDetector setText(@NonNull InputStream in) throws IOException {\n        fInputStream = in;\n        fInputStream.mark(kBufSize);\n        fRawInput = new byte[kBufSize];   // Always make a new buffer because the\n        //   previous one may have come from the caller,\n        //   in which case we can't touch it.\n        fRawLength = 0;\n        int remainingLength = kBufSize;\n        while (remainingLength > 0) {\n            // read() may give data in smallish chunks, esp. for remote sources.  Hence, this loop.\n            int bytesRead = fInputStream.read(fRawInput, fRawLength, remainingLength);\n            if (bytesRead <= 0) {\n                break;\n            }\n            fRawLength += bytesRead;\n            remainingLength -= bytesRead;\n        }\n        fInputStream.reset();\n\n        return this;\n    }\n\n\n    /**\n     * Return the charset that best matches the supplied input data.\n     * <p>\n     * Note though, that because the detection\n     * only looks at the start of the input data,\n     * there is a possibility that the returned charset will fail to handle\n     * the full set of input data.\n     * p>\n     * aise an exception if\n     * <ul>\n     *   <li>no charset appears to match the data.</li>\n     *   <li>no input text has been provided</li>\n     * </ul>\n     *\n     * @return a CharsetMatch object representing the best matching charset, or\n     * <code>null</code> if there are no matches.\n     * @stable ICU 3.4\n     */\n    @Nullable\n    public CharsetMatch detect() {\n//   TODO:  A better implementation would be to copy the detect loop from\n//          detectAll(), and cut it short as soon as a match with a high confidence\n//          is found.  This is something to be done later, after things are otherwise\n//          working.\n        CharsetMatch[] matches = detectAll();\n\n        if (matches == null || matches.length == 0) {\n            return null;\n        }\n\n        return matches[0];\n    }\n\n    /**\n     * Return an array of all charsets that appear to be plausible\n     * matches with the input data.  The array is ordered with the\n     * best quality match first.\n     * <p>\n     * aise an exception if\n     * <ul>\n     *   <li>no charsets appear to match the input data.</li>\n     *   <li>no input text has been provided</li>\n     * </ul>\n     *\n     * @return An array of CharsetMatch objects representing possibly matching charsets.\n     * @stable ICU 3.4\n     */\n    public CharsetMatch[] detectAll() {\n        ArrayList<CharsetMatch> matches = new ArrayList<>();\n\n        MungeInput();  // Strip html markup, collect byte stats.\n\n        //  Iterate over all possible charsets, remember all that\n        //    give a match quality > 0.\n        for (int i = 0; i < ALL_CS_RECOGNIZERS.size(); i++) {\n            CSRecognizerInfo rcinfo = ALL_CS_RECOGNIZERS.get(i);\n            boolean active = (fEnabledRecognizers != null) ? fEnabledRecognizers[i] : rcinfo.isDefaultEnabled;\n            if (active) {\n                CharsetMatch m = rcinfo.recognizer.match(this);\n                if (m != null) {\n                    matches.add(m);\n                }\n            }\n        }\n        Collections.sort(matches);      // CharsetMatch compares on confidence\n        Collections.reverse(matches);   //  Put best match first.\n        CharsetMatch[] resultArray = new CharsetMatch[matches.size()];\n        resultArray = matches.toArray(resultArray);\n        return resultArray;\n    }\n\n\n    /**\n     * Autodetect the charset of an inputStream, and return a Java Reader\n     * to access the converted input data.\n     * <p>\n     * This is a convenience method that is equivalent to\n     * <code>this.setDeclaredEncoding(declaredEncoding).setText(in).detect().getReader();</code>\n     * <p>\n     * For the input stream that supplies the character data, markSupported()\n     * must be true; the  charset detection will read a small amount of data,\n     * then return the stream to its original position via\n     * the InputStream.reset() operation.  The exact amount that will\n     * be read depends on the characteristics of the data itself.\n     * <p>\n     * Raise an exception if no charsets appear to match the input data.\n     *\n     * @param in               The source of the byte data in the unknown charset.\n     * @param declaredEncoding A declared encoding for the data, if available,\n     *                         or null or an empty string if none is available.\n     * @stable ICU 3.4\n     */\n    public Reader getReader(InputStream in, String declaredEncoding) {\n        fDeclaredEncoding = declaredEncoding;\n\n        try {\n            setText(in);\n\n            CharsetMatch match = detect();\n\n            if (match == null) {\n                return null;\n            }\n\n            return match.getReader();\n        } catch (IOException e) {\n            return null;\n        }\n    }\n\n    /**\n     * Autodetect the charset of an inputStream, and return a String\n     * containing the converted input data.\n     * <p>\n     * This is a convenience method that is equivalent to\n     * <code>this.setDeclaredEncoding(declaredEncoding).setText(in).detect().getString();</code>\n     * <p>\n     * Raise an exception if no charsets appear to match the input data.\n     *\n     * @param in               The source of the byte data in the unknown charset.\n     * @param declaredEncoding A declared encoding for the data, if available,\n     *                         or null or an empty string if none is available.\n     * @stable ICU 3.4\n     */\n    public String getString(byte[] in, String declaredEncoding) {\n        fDeclaredEncoding = declaredEncoding;\n\n        try {\n            setText(in);\n\n            CharsetMatch match = detect();\n\n            if (match == null) {\n                return null;\n            }\n\n            return match.getString(-1);\n        } catch (IOException e) {\n            return null;\n        }\n    }\n\n\n    /**\n     * Get the names of all charsets supported by <code>CharsetDetector</code> class.\n     * <p>\n     * <b>Note:</b> Multiple different charset encodings in a same family may use\n     * a single shared name in this implementation. For example, this method returns\n     * an array including \"ISO-8859-1\" (ISO Latin 1), but not including \"windows-1252\"\n     * (Windows Latin 1). However, actual detection result could be \"windows-1252\"\n     * when the input data matches Latin 1 code points with any points only available\n     * in \"windows-1252\".\n     *\n     * @return an array of the names of all charsets supported by\n     * <code>CharsetDetector</code> class.\n     * @stable ICU 3.4\n     */\n    public static String[] getAllDetectableCharsets() {\n        String[] allCharsetNames = new String[ALL_CS_RECOGNIZERS.size()];\n        for (int i = 0; i < allCharsetNames.length; i++) {\n            allCharsetNames[i] = ALL_CS_RECOGNIZERS.get(i).recognizer.getName();\n        }\n        return allCharsetNames;\n    }\n\n    /**\n     * Test whether or not input filtering is enabled.\n     *\n     * @return <code>true</code> if input text will be filtered.\n     * @stable ICU 3.4\n     * @see #enableInputFilter\n     */\n    public boolean inputFilterEnabled() {\n        return fStripTags;\n    }\n\n    /**\n     * Enable filtering of input text. If filtering is enabled,\n     * text within angle brackets (\"&lt;\" and \"&gt;\") will be removed\n     * before detection.\n     *\n     * @param filter <code>true</code> to enable input text filtering.\n     * @return The previous setting.\n     * @stable ICU 3.4\n     */\n    public boolean enableInputFilter(boolean filter) {\n        boolean previous = fStripTags;\n\n        fStripTags = filter;\n\n        return previous;\n    }\n\n    /*\n     *  MungeInput - after getting a set of raw input data to be analyzed, preprocess\n     *               it by removing what appears to be html markup.\n     */\n    private void MungeInput() {\n        int srci;\n        int dsti = 0;\n        byte b;\n        boolean inMarkup = false;\n        int openTags = 0;\n        int badTags = 0;\n\n        //\n        //  html / xml markup stripping.\n        //     quick and dirty, not 100% accurate, but hopefully good enough, statistically.\n        //     discard everything within < brackets >\n        //     Count how many total '<' and illegal (nested) '<' occur, so we can make some\n        //     guess as to whether the input was actually marked up at all.\n        if (fStripTags) {\n            for (srci = 0; srci < fRawLength && dsti < fInputBytes.length; srci++) {\n                b = fRawInput[srci];\n                if (b == (byte) '<') {\n                    if (inMarkup) {\n                        badTags++;\n                    }\n                    inMarkup = true;\n                    openTags++;\n                }\n\n                if (!inMarkup) {\n                    fInputBytes[dsti++] = b;\n                }\n\n                if (b == (byte) '>') {\n                    inMarkup = false;\n                }\n            }\n\n            fInputLen = dsti;\n        }\n\n        //\n        //  If it looks like this input wasn't marked up, or if it looks like it's\n        //    essentially nothing but markup abandon the markup stripping.\n        //    Detection will have to work on the unstripped input.\n        //\n        if (openTags < 5 || openTags / 5 < badTags ||\n                (fInputLen < 100 && fRawLength > 600)) {\n            int limit = fRawLength;\n\n            if (limit > kBufSize) {\n                limit = kBufSize;\n            }\n\n            for (srci = 0; srci < limit; srci++) {\n                fInputBytes[srci] = fRawInput[srci];\n            }\n            fInputLen = srci;\n        }\n\n        //\n        // Tally up the byte occurence statistics.\n        //   These are available for use by the various detectors.\n        //\n        Arrays.fill(fByteStats, (short) 0);\n        for (srci = 0; srci < fInputLen; srci++) {\n            int val = fInputBytes[srci] & 0x00ff;\n            fByteStats[val]++;\n        }\n\n        fC1Bytes = false;\n        for (int i = 0x80; i <= 0x9F; i += 1) {\n            if (fByteStats[i] != 0) {\n                fC1Bytes = true;\n                break;\n            }\n        }\n    }\n\n    /*\n     *  The following items are accessed by individual CharsetRecongizers during\n     *     the recognition process\n     *\n     */\n    byte[] fInputBytes =       // The text to be checked.  Markup will have been\n            new byte[kBufSize];  //   removed if appropriate.\n\n    int fInputLen;          // Length of the byte data in fInputBytes.\n\n    short[] fByteStats =      // byte frequency statistics for the input text.\n            new short[256];  //   Value is percent, not absolute.\n    //   Value is rounded up, so zero really means zero occurences.\n\n    boolean fC1Bytes =          // True if any bytes in the range 0x80 - 0x9F are in the input;\n            false;\n\n    String fDeclaredEncoding;\n\n\n    byte[] fRawInput;     // Original, untouched input bytes.\n    //  If user gave us a byte array, this is it.\n    //  If user gave us a stream, it's read to a\n    //  buffer here.\n    int fRawLength;    // Length of data in fRawInput array.\n\n    InputStream fInputStream;  // User's input stream, or null if the user\n    //   gave us a byte array.\n\n    //\n    //  Stuff private to CharsetDetector\n    //\n    private boolean fStripTags =   // If true, setText() will strip tags from input text.\n            false;\n\n    private boolean[] fEnabledRecognizers;   // If not null, active set of charset recognizers had\n    // been changed from the default. The array index is\n    // corresponding to ALL_RECOGNIZER. See setDetectableCharset().\n\n    private static class CSRecognizerInfo {\n        CharsetRecognizer recognizer;\n        boolean isDefaultEnabled;\n\n        CSRecognizerInfo(CharsetRecognizer recognizer, boolean isDefaultEnabled) {\n            this.recognizer = recognizer;\n            this.isDefaultEnabled = isDefaultEnabled;\n        }\n    }\n\n    /*\n     * List of recognizers for all charsets known to the implementation.\n     */\n    private static final List<CSRecognizerInfo> ALL_CS_RECOGNIZERS;\n\n    static {\n        List<CSRecognizerInfo> list = new ArrayList<>();\n\n        list.add(new CSRecognizerInfo(new CharsetRecog_UTF8(), true));\n        list.add(new CSRecognizerInfo(new CharsetRecog_Unicode.CharsetRecog_UTF_16_BE(), true));\n        list.add(new CSRecognizerInfo(new CharsetRecog_Unicode.CharsetRecog_UTF_16_LE(), true));\n        list.add(new CSRecognizerInfo(new CharsetRecog_Unicode.CharsetRecog_UTF_32_BE(), true));\n        list.add(new CSRecognizerInfo(new CharsetRecog_Unicode.CharsetRecog_UTF_32_LE(), true));\n\n        list.add(new CSRecognizerInfo(new CharsetRecog_mbcs.CharsetRecog_sjis(), true));\n        list.add(new CSRecognizerInfo(new CharsetRecog_2022.CharsetRecog_2022JP(), true));\n        list.add(new CSRecognizerInfo(new CharsetRecog_2022.CharsetRecog_2022CN(), true));\n        list.add(new CSRecognizerInfo(new CharsetRecog_2022.CharsetRecog_2022KR(), true));\n        list.add(new CSRecognizerInfo(new CharsetRecog_mbcs.CharsetRecog_euc.CharsetRecog_gb_18030(), true));\n        list.add(new CSRecognizerInfo(new CharsetRecog_mbcs.CharsetRecog_euc.CharsetRecog_euc_jp(), true));\n        list.add(new CSRecognizerInfo(new CharsetRecog_mbcs.CharsetRecog_euc.CharsetRecog_euc_kr(), true));\n        list.add(new CSRecognizerInfo(new CharsetRecog_mbcs.CharsetRecog_big5(), true));\n\n        list.add(new CSRecognizerInfo(new CharsetRecog_sbcs.CharsetRecog_8859_1(), true));\n        list.add(new CSRecognizerInfo(new CharsetRecog_sbcs.CharsetRecog_8859_2(), true));\n        list.add(new CSRecognizerInfo(new CharsetRecog_sbcs.CharsetRecog_8859_5_ru(), true));\n        list.add(new CSRecognizerInfo(new CharsetRecog_sbcs.CharsetRecog_8859_6_ar(), true));\n        list.add(new CSRecognizerInfo(new CharsetRecog_sbcs.CharsetRecog_8859_7_el(), true));\n        list.add(new CSRecognizerInfo(new CharsetRecog_sbcs.CharsetRecog_8859_8_I_he(), true));\n        list.add(new CSRecognizerInfo(new CharsetRecog_sbcs.CharsetRecog_8859_8_he(), true));\n        list.add(new CSRecognizerInfo(new CharsetRecog_sbcs.CharsetRecog_windows_1251(), true));\n        list.add(new CSRecognizerInfo(new CharsetRecog_sbcs.CharsetRecog_windows_1256(), true));\n        list.add(new CSRecognizerInfo(new CharsetRecog_sbcs.CharsetRecog_KOI8_R(), true));\n        list.add(new CSRecognizerInfo(new CharsetRecog_sbcs.CharsetRecog_8859_9_tr(), true));\n\n        // IBM 420/424 recognizers are disabled by default\n        list.add(new CSRecognizerInfo(new CharsetRecog_sbcs.CharsetRecog_IBM424_he_rtl(), false));\n        list.add(new CSRecognizerInfo(new CharsetRecog_sbcs.CharsetRecog_IBM424_he_ltr(), false));\n        list.add(new CSRecognizerInfo(new CharsetRecog_sbcs.CharsetRecog_IBM420_ar_rtl(), false));\n        list.add(new CSRecognizerInfo(new CharsetRecog_sbcs.CharsetRecog_IBM420_ar_ltr(), false));\n\n        //noinspection Java9CollectionFactory\n        ALL_CS_RECOGNIZERS = Collections.unmodifiableList(list);\n    }\n\n    /**\n     * Get the names of charsets that can be recognized by this CharsetDetector instance.\n     *\n     * @return an array of the names of charsets that can be recognized by this CharsetDetector\n     * instance.\n     * @internal\n     * @deprecated This API is ICU internal only.\n     */\n    @Deprecated\n    public String[] getDetectableCharsets() {\n        List<String> csnames = new ArrayList<>(ALL_CS_RECOGNIZERS.size());\n        for (int i = 0; i < ALL_CS_RECOGNIZERS.size(); i++) {\n            CSRecognizerInfo rcinfo = ALL_CS_RECOGNIZERS.get(i);\n            boolean active = (fEnabledRecognizers == null) ? rcinfo.isDefaultEnabled : fEnabledRecognizers[i];\n            if (active) {\n                csnames.add(rcinfo.recognizer.getName());\n            }\n        }\n        return csnames.toArray(new String[0]);\n    }\n\n    /**\n     * Enable or disable individual charset encoding.\n     * A name of charset encoding must be included in the names returned by\n     * {@link #getAllDetectableCharsets()}.\n     *\n     * @param encoding the name of charset encoding.\n     * @param enabled  <code>true</code> to enable, or <code>false</code> to disable the\n     *                 charset encoding.\n     * @return A reference to this <code>CharsetDetector</code>.\n     * @throws IllegalArgumentException when the name of charset encoding is\n     *                                  not supported.\n     * @internal\n     * @deprecated This API is ICU internal only.\n     */\n    @Deprecated\n    public CharsetDetector setDetectableCharset(String encoding, boolean enabled) {\n        int modIdx = -1;\n        boolean isDefaultVal = false;\n        for (int i = 0; i < ALL_CS_RECOGNIZERS.size(); i++) {\n            CSRecognizerInfo csrinfo = ALL_CS_RECOGNIZERS.get(i);\n            if (csrinfo.recognizer.getName().equals(encoding)) {\n                modIdx = i;\n                isDefaultVal = (csrinfo.isDefaultEnabled == enabled);\n                break;\n            }\n        }\n        if (modIdx < 0) {\n            // No matching encoding found\n            throw new IllegalArgumentException(\"Invalid encoding: \" + \"\\\"\" + encoding + \"\\\"\");\n        }\n\n        if (fEnabledRecognizers == null && !isDefaultVal) {\n            // Create an array storing the non default setting\n            fEnabledRecognizers = new boolean[ALL_CS_RECOGNIZERS.size()];\n\n            // Initialize the array with default info\n            for (int i = 0; i < ALL_CS_RECOGNIZERS.size(); i++) {\n                fEnabledRecognizers[i] = ALL_CS_RECOGNIZERS.get(i).isDefaultEnabled;\n            }\n        }\n\n        if (fEnabledRecognizers != null) {\n            fEnabledRecognizers[modIdx] = enabled;\n        }\n\n        return this;\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/lib/icu4j/CharsetMatch.java",
    "content": "// © 2016 and later: Unicode, Inc. and others.\n// License & terms of use: http://www.unicode.org/copyright.html\n/*\n * ******************************************************************************\n * Copyright (C) 2005-2016, International Business Machines Corporation and    *\n * others. All Rights Reserved.                                                *\n * ******************************************************************************\n */\npackage io.legado.app.lib.icu4j;\n\nimport java.io.ByteArrayInputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.InputStreamReader;\nimport java.io.Reader;\n\n\n/**\n * This class represents a charset that has been identified by a CharsetDetector\n * as a possible encoding for a set of input data.  From an instance of this\n * class, you can ask for a confidence level in the charset identification,\n * or for Java Reader or String to access the original byte data in Unicode form.\n * <p>\n * Instances of this class are created only by CharsetDetectors.\n * <p>\n * Note:  this class has a natural ordering that is inconsistent with equals.\n * The natural ordering is based on the match confidence value.\n *\n * @stable ICU 3.4\n */\n@SuppressWarnings({\"JavaDoc\", \"unused\"})\npublic class CharsetMatch implements Comparable<CharsetMatch> {\n\n\n    /**\n     * Create a java.io.Reader for reading the Unicode character data corresponding\n     * to the original byte data supplied to the Charset detect operation.\n     * <p>\n     * CAUTION:  if the source of the byte data was an InputStream, a Reader\n     * can be created for only one matching char set using this method.  If more\n     * than one charset needs to be tried, the caller will need to reset\n     * the InputStream and create InputStreamReaders itself, based on the charset name.\n     *\n     * @return the Reader for the Unicode character data.\n     * @stable ICU 3.4\n     */\n    public Reader getReader() {\n        InputStream inputStream = fInputStream;\n\n        if (inputStream == null) {\n            inputStream = new ByteArrayInputStream(fRawInput, 0, fRawLength);\n        }\n\n        try {\n            inputStream.reset();\n            return new InputStreamReader(inputStream, getName());\n        } catch (IOException e) {\n            return null;\n        }\n    }\n\n    /**\n     * Create a Java String from Unicode character data corresponding\n     * to the original byte data supplied to the Charset detect operation.\n     *\n     * @return a String created from the converted input data.\n     * @stable ICU 3.4\n     */\n    public String getString() throws java.io.IOException {\n        return getString(-1);\n\n    }\n\n    /**\n     * Create a Java String from Unicode character data corresponding\n     * to the original byte data supplied to the Charset detect operation.\n     * The length of the returned string is limited to the specified size;\n     * the string will be trunctated to this length if necessary.  A limit value of\n     * zero or less is ignored, and treated as no limit.\n     *\n     * @param maxLength The maximium length of the String to be created when the\n     *                  source of the data is an input stream, or -1 for\n     *                  unlimited length.\n     * @return a String created from the converted input data.\n     * @stable ICU 3.4\n     */\n    public String getString(int maxLength) throws java.io.IOException {\n        String result;\n        if (fInputStream != null) {\n            StringBuilder sb = new StringBuilder();\n            char[] buffer = new char[1024];\n            Reader reader = getReader();\n            int max = maxLength < 0 ? Integer.MAX_VALUE : maxLength;\n            int bytesRead;\n\n            while ((bytesRead = reader.read(buffer, 0, Math.min(max, 1024))) >= 0) {\n                sb.append(buffer, 0, bytesRead);\n                max -= bytesRead;\n            }\n\n            reader.close();\n\n            return sb.toString();\n        } else {\n            String name = getName();\n            /*\n             * getName() may return a name with a suffix 'rtl' or 'ltr'. This cannot\n             * be used to open a charset (e.g. IBM424_rtl). The ending '_rtl' or 'ltr'\n             * should be stripped off before creating the string.\n             */\n            int startSuffix = !name.contains(\"_rtl\") ? name.indexOf(\"_ltr\") : name.indexOf(\"_rtl\");\n            if (startSuffix > 0) {\n                name = name.substring(0, startSuffix);\n            }\n            result = new String(fRawInput, name);\n        }\n        return result;\n\n    }\n\n    /**\n     * Get an indication of the confidence in the charset detected.\n     * Confidence values range from 0-100, with larger numbers indicating\n     * a better match of the input data to the characteristics of the\n     * charset.\n     *\n     * @return the confidence in the charset match\n     * @stable ICU 3.4\n     */\n    public int getConfidence() {\n        return fConfidence;\n    }\n\n    /**\n     * Get the name of the detected charset.\n     * The name will be one that can be used with other APIs on the\n     * platform that accept charset names.  It is the \"Canonical name\"\n     * as defined by the class java.nio.charset.Charset; for\n     * charsets that are registered with the IANA charset registry,\n     * this is the MIME-preferred registerd name.\n     *\n     * @return The name of the charset.\n     * @stable ICU 3.4\n     * @see java.nio.charset.Charset\n     * @see java.io.InputStreamReader\n     */\n    public String getName() {\n        return fCharsetName;\n    }\n\n    /**\n     * Get the ISO code for the language of the detected charset.\n     *\n     * @return The ISO code for the language or <code>null</code> if the language cannot be determined.\n     * @stable ICU 3.4\n     */\n    public String getLanguage() {\n        return fLang;\n    }\n\n    /**\n     * Compare to other CharsetMatch objects.\n     * Comparison is based on the match confidence value, which\n     * allows CharsetDetector.detectAll() to order its results.\n     *\n     * @param other the CharsetMatch object to compare against.\n     * @return a negative integer, zero, or a positive integer as the\n     * confidence level of this CharsetMatch\n     * is less than, equal to, or greater than that of\n     * the argument.\n     * @throws ClassCastException if the argument is not a CharsetMatch.\n     * @stable ICU 4.4\n     */\n    @Override\n    public int compareTo(CharsetMatch other) {\n        int compareResult = 0;\n        if (this.fConfidence > other.fConfidence) {\n            compareResult = 1;\n        } else if (this.fConfidence < other.fConfidence) {\n            compareResult = -1;\n        }\n        return compareResult;\n    }\n\n    /*\n     *  Constructor.  Implementation internal\n     */\n    CharsetMatch(CharsetDetector det, CharsetRecognizer rec, int conf) {\n        fConfidence = conf;\n\n        // The references to the original application input data must be copied out\n        //   of the charset recognizer to here, in case the application resets the\n        //   recognizer before using this CharsetMatch.\n        if (det.fInputStream == null) {\n            // We only want the existing input byte data if it came straight from the user,\n            //   not if is just the head of a stream.\n            fRawInput = det.fRawInput;\n            fRawLength = det.fRawLength;\n        }\n        fInputStream = det.fInputStream;\n        fCharsetName = rec.getName();\n        fLang = rec.getLanguage();\n    }\n\n    /*\n     *  Constructor.  Implementation internal\n     */\n    CharsetMatch(CharsetDetector det, CharsetRecognizer rec, int conf, String csName, String lang) {\n        fConfidence = conf;\n\n        // The references to the original application input data must be copied out\n        //   of the charset recognizer to here, in case the application resets the\n        //   recognizer before using this CharsetMatch.\n        if (det.fInputStream == null) {\n            // We only want the existing input byte data if it came straight from the user,\n            //   not if is just the head of a stream.\n            fRawInput = det.fRawInput;\n            fRawLength = det.fRawLength;\n        }\n        fInputStream = det.fInputStream;\n        fCharsetName = csName;\n        fLang = lang;\n    }\n\n\n    //\n    //   Private Data\n    //\n    private final int fConfidence;\n    private byte[] fRawInput = null;     // Original, untouched input bytes.\n    //  If user gave us a byte array, this is it.\n    private int fRawLength;           // Length of data in fRawInput array.\n\n    private final InputStream fInputStream;  // User's input stream, or null if the user\n    //   gave us a byte array.\n\n    private final String fCharsetName;         // The name of the charset this CharsetMatch\n    //   represents.  Filled in by the recognizer.\n    private final String fLang;                // The language, if one was determined by\n    //   the recognizer during the detect operation.\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/lib/icu4j/CharsetRecog_2022.java",
    "content": "// © 2016 and later: Unicode, Inc. and others.\n// License & terms of use: http://www.unicode.org/copyright.html\n/*\n *******************************************************************************\n * Copyright (C) 2005 - 2012, International Business Machines Corporation and  *\n * others. All Rights Reserved.                                                *\n *******************************************************************************\n */\npackage io.legado.app.lib.icu4j;\n\n/**\n * class CharsetRecog_2022  part of the ICU charset detection imlementation.\n * This is a superclass for the individual detectors for\n * each of the detectable members of the ISO 2022 family\n * of encodings.\n * <p>\n * The separate classes are nested within this class.\n */\nabstract class CharsetRecog_2022 extends CharsetRecognizer {\n\n\n    /**\n     * Matching function shared among the 2022 detectors JP, CN and KR\n     * Counts up the number of legal an unrecognized escape sequences in\n     * the sample of text, and computes a score based on the total number &\n     * the proportion that fit the encoding.\n     *\n     * @param text            the byte buffer containing text to analyse\n     * @param textLen         the size of the text in the byte.\n     * @param escapeSequences the byte escape sequences to test for.\n     * @return match quality, in the range of 0-100.\n     */\n    int match(byte[] text, int textLen, byte[][] escapeSequences) {\n        int i, j;\n        int escN;\n        int hits = 0;\n        int misses = 0;\n        int shifts = 0;\n        int quality;\n        scanInput:\n        for (i = 0; i < textLen; i++) {\n            if (text[i] == 0x1b) {\n                checkEscapes:\n                for (escN = 0; escN < escapeSequences.length; escN++) {\n                    byte[] seq = escapeSequences[escN];\n\n                    if ((textLen - i) < seq.length) {\n                        continue;\n                    }\n\n                    for (j = 1; j < seq.length; j++) {\n                        if (seq[j] != text[i + j]) {\n                            continue checkEscapes;\n                        }\n                    }\n\n                    hits++;\n                    i += seq.length - 1;\n                    continue scanInput;\n                }\n\n                misses++;\n            }\n\n            if (text[i] == 0x0e || text[i] == 0x0f) {\n                // Shift in/out\n                shifts++;\n            }\n        }\n\n        if (hits == 0) {\n            return 0;\n        }\n\n        //\n        // Initial quality is based on relative proportion of recongized vs.\n        //   unrecognized escape sequences.\n        //   All good:  quality = 100;\n        //   half or less good: quality = 0;\n        //   linear inbetween.\n        quality = (100 * hits - 100 * misses) / (hits + misses);\n\n        // Back off quality if there were too few escape sequences seen.\n        //   Include shifts in this computation, so that KR does not get penalized\n        //   for having only a single Escape sequence, but many shifts.\n        if (hits + shifts < 5) {\n            quality -= (5 - (hits + shifts)) * 10;\n        }\n\n        if (quality < 0) {\n            quality = 0;\n        }\n        return quality;\n    }\n\n\n    static class CharsetRecog_2022JP extends CharsetRecog_2022 {\n        private final byte[][] escapeSequences = {\n                {0x1b, 0x24, 0x28, 0x43},   // KS X 1001:1992\n                {0x1b, 0x24, 0x28, 0x44},   // JIS X 212-1990\n                {0x1b, 0x24, 0x40},         // JIS C 6226-1978\n                {0x1b, 0x24, 0x41},         // GB 2312-80\n                {0x1b, 0x24, 0x42},         // JIS X 208-1983\n                {0x1b, 0x26, 0x40},         // JIS X 208 1990, 1997\n                {0x1b, 0x28, 0x42},         // ASCII\n                {0x1b, 0x28, 0x48},         // JIS-Roman\n                {0x1b, 0x28, 0x49},         // Half-width katakana\n                {0x1b, 0x28, 0x4a},         // JIS-Roman\n                {0x1b, 0x2e, 0x41},         // ISO 8859-1\n                {0x1b, 0x2e, 0x46}          // ISO 8859-7\n        };\n\n        @Override\n        String getName() {\n            return \"ISO-2022-JP\";\n        }\n\n        @Override\n        CharsetMatch match(CharsetDetector det) {\n            int confidence = match(det.fInputBytes, det.fInputLen, escapeSequences);\n            return confidence == 0 ? null : new CharsetMatch(det, this, confidence);\n        }\n    }\n\n    static class CharsetRecog_2022KR extends CharsetRecog_2022 {\n        private final byte[][] escapeSequences = {\n                {0x1b, 0x24, 0x29, 0x43}\n        };\n\n        @Override\n        String getName() {\n            return \"ISO-2022-KR\";\n        }\n\n        @Override\n        CharsetMatch match(CharsetDetector det) {\n            int confidence = match(det.fInputBytes, det.fInputLen, escapeSequences);\n            return confidence == 0 ? null : new CharsetMatch(det, this, confidence);\n        }\n    }\n\n    static class CharsetRecog_2022CN extends CharsetRecog_2022 {\n        private final byte[][] escapeSequences = {\n                {0x1b, 0x24, 0x29, 0x41},   // GB 2312-80\n                {0x1b, 0x24, 0x29, 0x47},   // CNS 11643-1992 Plane 1\n                {0x1b, 0x24, 0x2A, 0x48},   // CNS 11643-1992 Plane 2\n                {0x1b, 0x24, 0x29, 0x45},   // ISO-IR-165\n                {0x1b, 0x24, 0x2B, 0x49},   // CNS 11643-1992 Plane 3\n                {0x1b, 0x24, 0x2B, 0x4A},   // CNS 11643-1992 Plane 4\n                {0x1b, 0x24, 0x2B, 0x4B},   // CNS 11643-1992 Plane 5\n                {0x1b, 0x24, 0x2B, 0x4C},   // CNS 11643-1992 Plane 6\n                {0x1b, 0x24, 0x2B, 0x4D},   // CNS 11643-1992 Plane 7\n                {0x1b, 0x4e},               // SS2\n                {0x1b, 0x4f},               // SS3\n        };\n\n        @Override\n        String getName() {\n            return \"ISO-2022-CN\";\n        }\n\n        @Override\n        CharsetMatch match(CharsetDetector det) {\n            int confidence = match(det.fInputBytes, det.fInputLen, escapeSequences);\n            return confidence == 0 ? null : new CharsetMatch(det, this, confidence);\n        }\n    }\n\n}\n\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/lib/icu4j/CharsetRecog_UTF8.java",
    "content": "// © 2016 and later: Unicode, Inc. and others.\n// License & terms of use: http://www.unicode.org/copyright.html\n/**\n * ******************************************************************************\n * Copyright (C) 2005 - 2014, International Business Machines Corporation and  *\n * others. All Rights Reserved.                                                *\n * ******************************************************************************\n */\npackage io.legado.app.lib.icu4j;\n\n/**\n * Charset recognizer for UTF-8\n */\nclass CharsetRecog_UTF8 extends CharsetRecognizer {\n\n    @Override\n    String getName() {\n        return \"UTF-8\";\n    }\n\n    /* (non-Javadoc)\n     * @see com.ibm.icu.text.CharsetRecognizer#match(com.ibm.icu.text.CharsetDetector)\n     */\n    @Override\n    CharsetMatch match(CharsetDetector det) {\n        boolean hasBOM = false;\n        int numValid = 0;\n        int numInvalid = 0;\n        byte[] input = det.fRawInput;\n        int i;\n        int trailBytes = 0;\n        int confidence;\n\n        if (det.fRawLength >= 3 &&\n                (input[0] & 0xFF) == 0xef && (input[1] & 0xFF) == 0xbb && (input[2] & 0xFF) == 0xbf) {\n            hasBOM = true;\n        }\n\n        // Scan for multi-byte sequences\n        for (i = 0; i < det.fRawLength; i++) {\n            int b = input[i];\n            if ((b & 0x80) == 0) {\n                continue;   // ASCII\n            }\n\n            // Hi bit on char found.  Figure out how long the sequence should be\n            if ((b & 0x0e0) == 0x0c0) {\n                trailBytes = 1;\n            } else if ((b & 0x0f0) == 0x0e0) {\n                trailBytes = 2;\n            } else if ((b & 0x0f8) == 0xf0) {\n                trailBytes = 3;\n            } else {\n                numInvalid++;\n                continue;\n            }\n\n            // Verify that we've got the right number of trail bytes in the sequence\n            for (; ; ) {\n                i++;\n                if (i >= det.fRawLength) {\n                    break;\n                }\n                b = input[i];\n                if ((b & 0xc0) != 0x080) {\n                    numInvalid++;\n                    break;\n                }\n                if (--trailBytes == 0) {\n                    numValid++;\n                    break;\n                }\n            }\n        }\n\n        // Cook up some sort of confidence score, based on presense of a BOM\n        //    and the existence of valid and/or invalid multi-byte sequences.\n        confidence = 0;\n        if (hasBOM && numInvalid == 0) {\n            confidence = 100;\n        } else if (hasBOM && numValid > numInvalid * 10) {\n            confidence = 80;\n        } else if (numValid > 3 && numInvalid == 0) {\n            confidence = 100;\n        } else if (numValid > 0 && numInvalid == 0) {\n            confidence = 80;\n        } else if (numValid == 0 && numInvalid == 0) {\n            // Plain ASCII. Confidence must be > 10, it's more likely than UTF-16, which\n            //              accepts ASCII with confidence = 10.\n            // TODO: add plain ASCII as an explicitly detected type.\n            confidence = 15;\n        } else if (numValid > numInvalid * 10) {\n            // Probably corruput utf-8 data.  Valid sequences aren't likely by chance.\n            confidence = 25;\n        }\n        return confidence == 0 ? null : new CharsetMatch(det, this, confidence);\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/lib/icu4j/CharsetRecog_Unicode.java",
    "content": "// © 2016 and later: Unicode, Inc. and others.\n// License & terms of use: http://www.unicode.org/copyright.html\n/*\n *******************************************************************************\n * Copyright (C) 1996-2013, International Business Machines Corporation and    *\n * others. All Rights Reserved.                                                *\n *******************************************************************************\n *\n */\n\npackage io.legado.app.lib.icu4j;\n\n/**\n * This class matches UTF-16 and UTF-32, both big- and little-endian. The\n * BOM will be used if it is present.\n */\nabstract class CharsetRecog_Unicode extends CharsetRecognizer {\n\n    /* (non-Javadoc)\n     * @see com.ibm.icu.text.CharsetRecognizer#getName()\n     */\n    @Override\n    abstract String getName();\n\n    /* (non-Javadoc)\n     * @see com.ibm.icu.text.CharsetRecognizer#match(com.ibm.icu.text.CharsetDetector)\n     */\n    @Override\n    abstract CharsetMatch match(CharsetDetector det);\n\n    static int codeUnit16FromBytes(byte hi, byte lo) {\n        return ((hi & 0xff) << 8) | (lo & 0xff);\n    }\n\n    // UTF-16 confidence calculation. Very simple minded, but better than nothing.\n    //   Any 8 bit non-control characters bump the confidence up. These have a zero high byte,\n    //     and are very likely to be UTF-16, although they could also be part of a UTF-32 code.\n    //   NULs are a contra-indication, they will appear commonly if the actual encoding is UTF-32.\n    //   NULs should be rare in actual text.\n    static int adjustConfidence(int codeUnit, int confidence) {\n        if (codeUnit == 0) {\n            confidence -= 10;\n        } else if ((codeUnit >= 0x20 && codeUnit <= 0xff) || codeUnit == 0x0a) {\n            confidence += 10;\n        }\n        if (confidence < 0) {\n            confidence = 0;\n        } else if (confidence > 100) {\n            confidence = 100;\n        }\n        return confidence;\n    }\n\n    static class CharsetRecog_UTF_16_BE extends CharsetRecog_Unicode {\n        @Override\n        String getName() {\n            return \"UTF-16BE\";\n        }\n\n        @Override\n        CharsetMatch match(CharsetDetector det) {\n            byte[] input = det.fRawInput;\n            int confidence = 10;\n\n            int bytesToCheck = Math.min(input.length, 30);\n            for (int charIndex = 0; charIndex < bytesToCheck - 1; charIndex += 2) {\n                int codeUnit = codeUnit16FromBytes(input[charIndex], input[charIndex + 1]);\n                if (charIndex == 0 && codeUnit == 0xFEFF) {\n                    confidence = 100;\n                    break;\n                }\n                confidence = adjustConfidence(codeUnit, confidence);\n                if (confidence == 0 || confidence == 100) {\n                    break;\n                }\n            }\n            if (bytesToCheck < 4 && confidence < 100) {\n                confidence = 0;\n            }\n            if (confidence > 0) {\n                return new CharsetMatch(det, this, confidence);\n            }\n            return null;\n        }\n    }\n\n    static class CharsetRecog_UTF_16_LE extends CharsetRecog_Unicode {\n        @Override\n        String getName() {\n            return \"UTF-16LE\";\n        }\n\n        @Override\n        CharsetMatch match(CharsetDetector det) {\n            byte[] input = det.fRawInput;\n            int confidence = 10;\n\n            int bytesToCheck = Math.min(input.length, 30);\n            for (int charIndex = 0; charIndex < bytesToCheck - 1; charIndex += 2) {\n                int codeUnit = codeUnit16FromBytes(input[charIndex + 1], input[charIndex]);\n                if (charIndex == 0 && codeUnit == 0xFEFF) {\n                    confidence = 100;\n                    break;\n                }\n                confidence = adjustConfidence(codeUnit, confidence);\n                if (confidence == 0 || confidence == 100) {\n                    break;\n                }\n            }\n            if (bytesToCheck < 4 && confidence < 100) {\n                confidence = 0;\n            }\n            if (confidence > 0) {\n                return new CharsetMatch(det, this, confidence);\n            }\n            return null;\n        }\n    }\n\n    static abstract class CharsetRecog_UTF_32 extends CharsetRecog_Unicode {\n        abstract int getChar(byte[] input, int index);\n\n        @Override\n        abstract String getName();\n\n        @Override\n        CharsetMatch match(CharsetDetector det) {\n            byte[] input = det.fRawInput;\n            int limit = (det.fRawLength / 4) * 4;\n            int numValid = 0;\n            int numInvalid = 0;\n            boolean hasBOM = false;\n            int confidence = 0;\n\n            if (limit == 0) {\n                return null;\n            }\n            if (getChar(input, 0) == 0x0000FEFF) {\n                hasBOM = true;\n            }\n\n            for (int i = 0; i < limit; i += 4) {\n                int ch = getChar(input, i);\n\n                if (ch < 0 || ch >= 0x10FFFF || (ch >= 0xD800 && ch <= 0xDFFF)) {\n                    numInvalid += 1;\n                } else {\n                    numValid += 1;\n                }\n            }\n\n\n            // Cook up some sort of confidence score, based on presence of a BOM\n            //    and the existence of valid and/or invalid multi-byte sequences.\n            if (hasBOM && numInvalid == 0) {\n                confidence = 100;\n            } else if (hasBOM && numValid > numInvalid * 10) {\n                confidence = 80;\n            } else if (numValid > 3 && numInvalid == 0) {\n                confidence = 100;\n            } else if (numValid > 0 && numInvalid == 0) {\n                confidence = 80;\n            } else if (numValid > numInvalid * 10) {\n                // Probably corrupt UTF-32BE data.  Valid sequences aren't likely by chance.\n                confidence = 25;\n            }\n\n            return confidence == 0 ? null : new CharsetMatch(det, this, confidence);\n        }\n    }\n\n    static class CharsetRecog_UTF_32_BE extends CharsetRecog_UTF_32 {\n        @Override\n        int getChar(byte[] input, int index) {\n            return (input[index + 0] & 0xFF) << 24 | (input[index + 1] & 0xFF) << 16 |\n                    (input[index + 2] & 0xFF) << 8 | (input[index + 3] & 0xFF);\n        }\n\n        @Override\n        String getName() {\n            return \"UTF-32BE\";\n        }\n    }\n\n\n    static class CharsetRecog_UTF_32_LE extends CharsetRecog_UTF_32 {\n        @Override\n        int getChar(byte[] input, int index) {\n            return (input[index + 3] & 0xFF) << 24 | (input[index + 2] & 0xFF) << 16 |\n                    (input[index + 1] & 0xFF) << 8 | (input[index + 0] & 0xFF);\n        }\n\n        @Override\n        String getName() {\n            return \"UTF-32LE\";\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/lib/icu4j/CharsetRecog_mbcs.java",
    "content": "// © 2016 and later: Unicode, Inc. and others.\n// License & terms of use: http://www.unicode.org/copyright.html\n/*\n ****************************************************************************\n * Copyright (C) 2005-2012, International Business Machines Corporation and *\n * others. All Rights Reserved.                                             *\n ****************************************************************************\n *\n */\npackage io.legado.app.lib.icu4j;\n\nimport java.util.Arrays;\n\n/**\n * CharsetRecognizer implemenation for Asian  - double or multi-byte - charsets.\n * Match is determined mostly by the input data adhering to the\n * encoding scheme for the charset, and, optionally,\n * frequency-of-occurence of characters.\n * <p/>\n * Instances of this class are singletons, one per encoding\n * being recognized.  They are created in the main\n * CharsetDetector class and kept in the global list of available\n * encodings to be checked.  The specific encoding being recognized\n * is determined by subclass.\n */\nabstract class CharsetRecog_mbcs extends CharsetRecognizer {\n\n    /**\n     * Get the IANA name of this charset.\n     *\n     * @return the charset name.\n     */\n    @Override\n    abstract String getName();\n\n\n    /**\n     * Test the match of this charset with the input text data\n     * which is obtained via the CharsetDetector object.\n     *\n     * @param det The CharsetDetector, which contains the input text\n     *            to be checked for being in this charset.\n     * @return Two values packed into one int  (Damn java, anyhow)\n     * <br/>\n     * bits 0-7:  the match confidence, ranging from 0-100\n     * <br/>\n     * bits 8-15: The match reason, an enum-like value.\n     */\n    int match(CharsetDetector det, int[] commonChars) {\n        @SuppressWarnings(\"unused\")\n        int singleByteCharCount = 0;  //TODO Do we really need this?\n        int doubleByteCharCount = 0;\n        int commonCharCount = 0;\n        int badCharCount = 0;\n        int totalCharCount = 0;\n        int confidence = 0;\n        iteratedChar iter = new iteratedChar();\n\n        detectBlock:\n        {\n            for (iter.reset(); nextChar(iter, det); ) {\n                totalCharCount++;\n                if (iter.error) {\n                    badCharCount++;\n                } else {\n                    long cv = iter.charValue & 0xFFFFFFFFL;\n\n                    if (cv <= 0xff) {\n                        singleByteCharCount++;\n                    } else {\n                        doubleByteCharCount++;\n                        if (commonChars != null) {\n                            // NOTE: This assumes that there are no 4-byte common chars.\n                            if (Arrays.binarySearch(commonChars, (int) cv) >= 0) {\n                                commonCharCount++;\n                            }\n                        }\n                    }\n                }\n                if (badCharCount >= 2 && badCharCount * 5 >= doubleByteCharCount) {\n                    // Bail out early if the byte data is not matching the encoding scheme.\n                    break detectBlock;\n                }\n            }\n\n            if (doubleByteCharCount <= 10 && badCharCount == 0) {\n                // Not many multi-byte chars.\n                if (doubleByteCharCount == 0 && totalCharCount < 10) {\n                    // There weren't any multibyte sequences, and there was a low density of non-ASCII single bytes.\n                    // We don't have enough data to have any confidence.\n                    // Statistical analysis of single byte non-ASCII charcters would probably help here.\n                    confidence = 0;\n                } else {\n                    //   ASCII or ISO file?  It's probably not our encoding,\n                    //   but is not incompatible with our encoding, so don't give it a zero.\n                    confidence = 10;\n                }\n\n                break detectBlock;\n            }\n\n            //\n            //  No match if there are too many characters that don't fit the encoding scheme.\n            //    (should we have zero tolerance for these?)\n            //\n            if (doubleByteCharCount < 20 * badCharCount) {\n                confidence = 0;\n                break detectBlock;\n            }\n\n            if (commonChars == null) {\n                // We have no statistics on frequently occuring characters.\n                //  Assess confidence purely on having a reasonable number of\n                //  multi-byte characters (the more the better\n                confidence = 30 + doubleByteCharCount - 20 * badCharCount;\n                if (confidence > 100) {\n                    confidence = 100;\n                }\n            } else {\n                //\n                // Frequency of occurence statistics exist.\n                //\n                double maxVal = Math.log((float) doubleByteCharCount / 4);\n                double scaleFactor = 90.0 / maxVal;\n                confidence = (int) (Math.log(commonCharCount + 1) * scaleFactor + 10);\n                confidence = Math.min(confidence, 100);\n            }\n        }   // end of detectBlock:\n\n        return confidence;\n    }\n\n    // \"Character\"  iterated character class.\n    //    Recognizers for specific mbcs encodings make their \"characters\" available\n    //    by providing a nextChar() function that fills in an instance of iteratedChar\n    //    with the next char from the input.\n    //    The returned characters are not converted to Unicode, but remain as the raw\n    //    bytes (concatenated into an int) from the codepage data.\n    //\n    //  For Asian charsets, use the raw input rather than the input that has been\n    //   stripped of markup.  Detection only considers multi-byte chars, effectively\n    //   stripping markup anyway, and double byte chars do occur in markup too.\n    //\n    static class iteratedChar {\n        int charValue = 0;             // 1-4 bytes from the raw input data\n        int nextIndex = 0;\n        boolean error = false;\n        boolean done = false;\n\n        void reset() {\n            charValue = 0;\n            nextIndex = 0;\n            error = false;\n            done = false;\n        }\n\n        int nextByte(CharsetDetector det) {\n            if (nextIndex >= det.fRawLength) {\n                done = true;\n                return -1;\n            }\n            return det.fRawInput[nextIndex++] & 0x00ff;\n        }\n    }\n\n    /**\n     * Get the next character (however many bytes it is) from the input data\n     * Subclasses for specific charset encodings must implement this function\n     * to get characters according to the rules of their encoding scheme.\n     * <p>\n     * This function is not a method of class iteratedChar only because\n     * that would require a lot of extra derived classes, which is awkward.\n     *\n     * @param it  The iteratedChar \"struct\" into which the returned char is placed.\n     * @param det The charset detector, which is needed to get at the input byte data\n     *            being iterated over.\n     * @return True if a character was returned, false at end of input.\n     */\n    abstract boolean nextChar(iteratedChar it, CharsetDetector det);\n\n\n    /**\n     * Shift-JIS charset recognizer.\n     */\n    static class CharsetRecog_sjis extends CharsetRecog_mbcs {\n        static int[] commonChars =\n                // TODO:  This set of data comes from the character frequency-\n                //        of-occurence analysis tool.  The data needs to be moved\n                //        into a resource and loaded from there.\n                {0x8140, 0x8141, 0x8142, 0x8145, 0x815b, 0x8169, 0x816a, 0x8175, 0x8176, 0x82a0,\n                        0x82a2, 0x82a4, 0x82a9, 0x82aa, 0x82ab, 0x82ad, 0x82af, 0x82b1, 0x82b3, 0x82b5,\n                        0x82b7, 0x82bd, 0x82be, 0x82c1, 0x82c4, 0x82c5, 0x82c6, 0x82c8, 0x82c9, 0x82cc,\n                        0x82cd, 0x82dc, 0x82e0, 0x82e7, 0x82e8, 0x82e9, 0x82ea, 0x82f0, 0x82f1, 0x8341,\n                        0x8343, 0x834e, 0x834f, 0x8358, 0x835e, 0x8362, 0x8367, 0x8375, 0x8376, 0x8389,\n                        0x838a, 0x838b, 0x838d, 0x8393, 0x8e96, 0x93fa, 0x95aa};\n\n        @Override\n        boolean nextChar(iteratedChar it, CharsetDetector det) {\n            it.error = false;\n            int firstByte;\n            firstByte = it.charValue = it.nextByte(det);\n            if (firstByte < 0) {\n                return false;\n            }\n\n            if (firstByte <= 0x7f || (firstByte > 0xa0 && firstByte <= 0xdf)) {\n                return true;\n            }\n\n            int secondByte = it.nextByte(det);\n            if (secondByte < 0) {\n                return false;\n            }\n            it.charValue = (firstByte << 8) | secondByte;\n            if (!((secondByte >= 0x40 && secondByte <= 0x7f) || (secondByte >= 0x80 && secondByte <= 0xff))) {\n                // Illegal second byte value.\n                it.error = true;\n            }\n            return true;\n        }\n\n        @Override\n        CharsetMatch match(CharsetDetector det) {\n            int confidence = match(det, commonChars);\n            return confidence == 0 ? null : new CharsetMatch(det, this, confidence);\n        }\n\n        @Override\n        String getName() {\n            return \"Shift_JIS\";\n        }\n\n        @Override\n        public String getLanguage() {\n            return \"ja\";\n        }\n\n\n    }\n\n\n    /**\n     * Big5 charset recognizer.\n     */\n    static class CharsetRecog_big5 extends CharsetRecog_mbcs {\n        static int[] commonChars =\n                // TODO:  This set of data comes from the character frequency-\n                //        of-occurence analysis tool.  The data needs to be moved\n                //        into a resource and loaded from there.\n                {0xa140, 0xa141, 0xa142, 0xa143, 0xa147, 0xa149, 0xa175, 0xa176, 0xa440, 0xa446,\n                        0xa447, 0xa448, 0xa451, 0xa454, 0xa457, 0xa464, 0xa46a, 0xa46c, 0xa477, 0xa4a3,\n                        0xa4a4, 0xa4a7, 0xa4c1, 0xa4ce, 0xa4d1, 0xa4df, 0xa4e8, 0xa4fd, 0xa540, 0xa548,\n                        0xa558, 0xa569, 0xa5cd, 0xa5e7, 0xa657, 0xa661, 0xa662, 0xa668, 0xa670, 0xa6a8,\n                        0xa6b3, 0xa6b9, 0xa6d3, 0xa6db, 0xa6e6, 0xa6f2, 0xa740, 0xa751, 0xa759, 0xa7da,\n                        0xa8a3, 0xa8a5, 0xa8ad, 0xa8d1, 0xa8d3, 0xa8e4, 0xa8fc, 0xa9c0, 0xa9d2, 0xa9f3,\n                        0xaa6b, 0xaaba, 0xaabe, 0xaacc, 0xaafc, 0xac47, 0xac4f, 0xacb0, 0xacd2, 0xad59,\n                        0xaec9, 0xafe0, 0xb0ea, 0xb16f, 0xb2b3, 0xb2c4, 0xb36f, 0xb44c, 0xb44e, 0xb54c,\n                        0xb5a5, 0xb5bd, 0xb5d0, 0xb5d8, 0xb671, 0xb7ed, 0xb867, 0xb944, 0xbad8, 0xbb44,\n                        0xbba1, 0xbdd1, 0xc2c4, 0xc3b9, 0xc440, 0xc45f};\n\n        @Override\n        boolean nextChar(iteratedChar it, CharsetDetector det) {\n            it.error = false;\n            int firstByte;\n            firstByte = it.charValue = it.nextByte(det);\n            if (firstByte < 0) {\n                return false;\n            }\n\n            if (firstByte <= 0x7f || firstByte == 0xff) {\n                // single byte character.\n                return true;\n            }\n\n            int secondByte = it.nextByte(det);\n            if (secondByte < 0) {\n                return false;\n            }\n            it.charValue = (it.charValue << 8) | secondByte;\n\n            if (secondByte < 0x40 ||\n                    secondByte == 0x7f ||\n                    secondByte == 0xff) {\n                it.error = true;\n            }\n            return true;\n        }\n\n        @Override\n        CharsetMatch match(CharsetDetector det) {\n            int confidence = match(det, commonChars);\n            return confidence == 0 ? null : new CharsetMatch(det, this, confidence);\n        }\n\n        @Override\n        String getName() {\n            return \"Big5\";\n        }\n\n\n        @Override\n        public String getLanguage() {\n            return \"zh\";\n        }\n    }\n\n\n    /**\n     * EUC charset recognizers.  One abstract class that provides the common function\n     * for getting the next character according to the EUC encoding scheme,\n     * and nested derived classes for EUC_KR, EUC_JP, EUC_CN.\n     */\n    abstract static class CharsetRecog_euc extends CharsetRecog_mbcs {\n\n        /*\n         *  (non-Javadoc)\n         *  Get the next character value for EUC based encodings.\n         *  Character \"value\" is simply the raw bytes that make up the character\n         *     packed into an int.\n         */\n        @Override\n        boolean nextChar(iteratedChar it, CharsetDetector det) {\n            it.error = false;\n            int firstByte;\n            int secondByte;\n            int thirdByte;\n            //int fourthByte = 0;\n\n            buildChar:\n            {\n                firstByte = it.charValue = it.nextByte(det);\n                if (firstByte < 0) {\n                    // Ran off the end of the input data\n                    it.done = true;\n                    break buildChar;\n                }\n                if (firstByte <= 0x8d) {\n                    // single byte char\n                    break buildChar;\n                }\n\n                secondByte = it.nextByte(det);\n                it.charValue = (it.charValue << 8) | secondByte;\n\n                if (firstByte >= 0xA1 && firstByte <= 0xfe) {\n                    // Two byte Char\n                    if (secondByte < 0xa1) {\n                        it.error = true;\n                    }\n                    break buildChar;\n                }\n                if (firstByte == 0x8e) {\n                    // Code Set 2.\n                    //   In EUC-JP, total char size is 2 bytes, only one byte of actual char value.\n                    //   In EUC-TW, total char size is 4 bytes, three bytes contribute to char value.\n                    // We don't know which we've got.\n                    // Treat it like EUC-JP.  If the data really was EUC-TW, the following two\n                    //   bytes will look like a well formed 2 byte char.\n                    if (secondByte < 0xa1) {\n                        it.error = true;\n                    }\n                    break buildChar;\n                }\n\n                if (firstByte == 0x8f) {\n                    // Code set 3.\n                    // Three byte total char size, two bytes of actual char value.\n                    thirdByte = it.nextByte(det);\n                    it.charValue = (it.charValue << 8) | thirdByte;\n                    if (thirdByte < 0xa1) {\n                        it.error = true;\n                    }\n                }\n            }\n\n            return (!it.done);\n        }\n\n        /**\n         * The charset recognize for EUC-JP.  A singleton instance of this class\n         * is created and kept by the public CharsetDetector class\n         */\n        static class CharsetRecog_euc_jp extends CharsetRecog_euc {\n            static int[] commonChars =\n                    // TODO:  This set of data comes from the character frequency-\n                    //        of-occurence analysis tool.  The data needs to be moved\n                    //        into a resource and loaded from there.\n                    {0xa1a1, 0xa1a2, 0xa1a3, 0xa1a6, 0xa1bc, 0xa1ca, 0xa1cb, 0xa1d6, 0xa1d7, 0xa4a2,\n                            0xa4a4, 0xa4a6, 0xa4a8, 0xa4aa, 0xa4ab, 0xa4ac, 0xa4ad, 0xa4af, 0xa4b1, 0xa4b3,\n                            0xa4b5, 0xa4b7, 0xa4b9, 0xa4bb, 0xa4bd, 0xa4bf, 0xa4c0, 0xa4c1, 0xa4c3, 0xa4c4,\n                            0xa4c6, 0xa4c7, 0xa4c8, 0xa4c9, 0xa4ca, 0xa4cb, 0xa4ce, 0xa4cf, 0xa4d0, 0xa4de,\n                            0xa4df, 0xa4e1, 0xa4e2, 0xa4e4, 0xa4e8, 0xa4e9, 0xa4ea, 0xa4eb, 0xa4ec, 0xa4ef,\n                            0xa4f2, 0xa4f3, 0xa5a2, 0xa5a3, 0xa5a4, 0xa5a6, 0xa5a7, 0xa5aa, 0xa5ad, 0xa5af,\n                            0xa5b0, 0xa5b3, 0xa5b5, 0xa5b7, 0xa5b8, 0xa5b9, 0xa5bf, 0xa5c3, 0xa5c6, 0xa5c7,\n                            0xa5c8, 0xa5c9, 0xa5cb, 0xa5d0, 0xa5d5, 0xa5d6, 0xa5d7, 0xa5de, 0xa5e0, 0xa5e1,\n                            0xa5e5, 0xa5e9, 0xa5ea, 0xa5eb, 0xa5ec, 0xa5ed, 0xa5f3, 0xb8a9, 0xb9d4, 0xbaee,\n                            0xbbc8, 0xbef0, 0xbfb7, 0xc4ea, 0xc6fc, 0xc7bd, 0xcab8, 0xcaf3, 0xcbdc, 0xcdd1};\n\n            @Override\n            String getName() {\n                return \"EUC-JP\";\n            }\n\n            @Override\n            CharsetMatch match(CharsetDetector det) {\n                int confidence = match(det, commonChars);\n                return confidence == 0 ? null : new CharsetMatch(det, this, confidence);\n            }\n\n            @Override\n            public String getLanguage() {\n                return \"ja\";\n            }\n        }\n\n        /**\n         * The charset recognize for EUC-KR.  A singleton instance of this class\n         * is created and kept by the public CharsetDetector class\n         */\n        static class CharsetRecog_euc_kr extends CharsetRecog_euc {\n            static int[] commonChars =\n                    // TODO:  This set of data comes from the character frequency-\n                    //        of-occurence analysis tool.  The data needs to be moved\n                    //        into a resource and loaded from there.\n                    {0xb0a1, 0xb0b3, 0xb0c5, 0xb0cd, 0xb0d4, 0xb0e6, 0xb0ed, 0xb0f8, 0xb0fa, 0xb0fc,\n                            0xb1b8, 0xb1b9, 0xb1c7, 0xb1d7, 0xb1e2, 0xb3aa, 0xb3bb, 0xb4c2, 0xb4cf, 0xb4d9,\n                            0xb4eb, 0xb5a5, 0xb5b5, 0xb5bf, 0xb5c7, 0xb5e9, 0xb6f3, 0xb7af, 0xb7c2, 0xb7ce,\n                            0xb8a6, 0xb8ae, 0xb8b6, 0xb8b8, 0xb8bb, 0xb8e9, 0xb9ab, 0xb9ae, 0xb9cc, 0xb9ce,\n                            0xb9fd, 0xbab8, 0xbace, 0xbad0, 0xbaf1, 0xbbe7, 0xbbf3, 0xbbfd, 0xbcad, 0xbcba,\n                            0xbcd2, 0xbcf6, 0xbdba, 0xbdc0, 0xbdc3, 0xbdc5, 0xbec6, 0xbec8, 0xbedf, 0xbeee,\n                            0xbef8, 0xbefa, 0xbfa1, 0xbfa9, 0xbfc0, 0xbfe4, 0xbfeb, 0xbfec, 0xbff8, 0xc0a7,\n                            0xc0af, 0xc0b8, 0xc0ba, 0xc0bb, 0xc0bd, 0xc0c7, 0xc0cc, 0xc0ce, 0xc0cf, 0xc0d6,\n                            0xc0da, 0xc0e5, 0xc0fb, 0xc0fc, 0xc1a4, 0xc1a6, 0xc1b6, 0xc1d6, 0xc1df, 0xc1f6,\n                            0xc1f8, 0xc4a1, 0xc5cd, 0xc6ae, 0xc7cf, 0xc7d1, 0xc7d2, 0xc7d8, 0xc7e5, 0xc8ad};\n\n            @Override\n            String getName() {\n                return \"EUC-KR\";\n            }\n\n            @Override\n            CharsetMatch match(CharsetDetector det) {\n                int confidence = match(det, commonChars);\n                return confidence == 0 ? null : new CharsetMatch(det, this, confidence);\n            }\n\n            @Override\n            public String getLanguage() {\n                return \"ko\";\n            }\n        }\n    }\n\n    /**\n     * GB-18030 recognizer. Uses simplified Chinese statistics.\n     */\n    static class CharsetRecog_gb_18030 extends CharsetRecog_mbcs {\n\n        /*\n         *  (non-Javadoc)\n         *  Get the next character value for EUC based encodings.\n         *  Character \"value\" is simply the raw bytes that make up the character\n         *     packed into an int.\n         */\n        @Override\n        boolean nextChar(iteratedChar it, CharsetDetector det) {\n            it.error = false;\n            int firstByte;\n            int secondByte;\n            int thirdByte;\n            int fourthByte;\n\n            buildChar:\n            {\n                firstByte = it.charValue = it.nextByte(det);\n\n                if (firstByte < 0) {\n                    // Ran off the end of the input data\n                    it.done = true;\n                    break buildChar;\n                }\n\n                if (firstByte <= 0x80) {\n                    // single byte char\n                    break buildChar;\n                }\n\n                secondByte = it.nextByte(det);\n                it.charValue = (it.charValue << 8) | secondByte;\n\n                if (firstByte >= 0x81 && firstByte <= 0xFE) {\n                    // Two byte Char\n                    if ((secondByte >= 0x40 && secondByte <= 0x7E) || (secondByte >= 80 && secondByte <= 0xFE)) {\n                        break buildChar;\n                    }\n\n                    // Four byte char\n                    if (secondByte >= 0x30 && secondByte <= 0x39) {\n                        thirdByte = it.nextByte(det);\n\n                        if (thirdByte >= 0x81 && thirdByte <= 0xFE) {\n                            fourthByte = it.nextByte(det);\n\n                            if (fourthByte >= 0x30 && fourthByte <= 0x39) {\n                                it.charValue = (it.charValue << 16) | (thirdByte << 8) | fourthByte;\n                                break buildChar;\n                            }\n                        }\n                    }\n\n                    it.error = true;\n                }\n            }\n\n            return (!it.done);\n        }\n\n        static int[] commonChars =\n                // TODO:  This set of data comes from the character frequency-\n                //        of-occurence analysis tool.  The data needs to be moved\n                //        into a resource and loaded from there.\n                {0xa1a1, 0xa1a2, 0xa1a3, 0xa1a4, 0xa1b0, 0xa1b1, 0xa1f1, 0xa1f3, 0xa3a1, 0xa3ac,\n                        0xa3ba, 0xb1a8, 0xb1b8, 0xb1be, 0xb2bb, 0xb3c9, 0xb3f6, 0xb4f3, 0xb5bd, 0xb5c4,\n                        0xb5e3, 0xb6af, 0xb6d4, 0xb6e0, 0xb7a2, 0xb7a8, 0xb7bd, 0xb7d6, 0xb7dd, 0xb8b4,\n                        0xb8df, 0xb8f6, 0xb9ab, 0xb9c9, 0xb9d8, 0xb9fa, 0xb9fd, 0xbacd, 0xbba7, 0xbbd6,\n                        0xbbe1, 0xbbfa, 0xbcbc, 0xbcdb, 0xbcfe, 0xbdcc, 0xbecd, 0xbedd, 0xbfb4, 0xbfc6,\n                        0xbfc9, 0xc0b4, 0xc0ed, 0xc1cb, 0xc2db, 0xc3c7, 0xc4dc, 0xc4ea, 0xc5cc, 0xc6f7,\n                        0xc7f8, 0xc8ab, 0xc8cb, 0xc8d5, 0xc8e7, 0xc9cf, 0xc9fa, 0xcab1, 0xcab5, 0xcac7,\n                        0xcad0, 0xcad6, 0xcaf5, 0xcafd, 0xccec, 0xcdf8, 0xceaa, 0xcec4, 0xced2, 0xcee5,\n                        0xcfb5, 0xcfc2, 0xcfd6, 0xd0c2, 0xd0c5, 0xd0d0, 0xd0d4, 0xd1a7, 0xd2aa, 0xd2b2,\n                        0xd2b5, 0xd2bb, 0xd2d4, 0xd3c3, 0xd3d0, 0xd3fd, 0xd4c2, 0xd4da, 0xd5e2, 0xd6d0};\n\n\n        @Override\n        String getName() {\n            return \"GB18030\";\n        }\n\n        @Override\n        CharsetMatch match(CharsetDetector det) {\n            int confidence = match(det, commonChars);\n            return confidence == 0 ? null : new CharsetMatch(det, this, confidence);\n        }\n\n        @Override\n        public String getLanguage() {\n            return \"zh\";\n        }\n    }\n\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/lib/icu4j/CharsetRecog_sbcs.java",
    "content": "// © 2016 and later: Unicode, Inc. and others.\n// License & terms of use: http://www.unicode.org/copyright.html\n/*\n ****************************************************************************\n * Copyright (C) 2005-2013, International Business Machines Corporation and *\n * others. All Rights Reserved.                                             *\n ************************************************************************** *\n *\n */\n\npackage io.legado.app.lib.icu4j;\n\n/**\n * This class recognizes single-byte encodings. Because the encoding scheme is so\n * simple, language statistics are used to do the matching.\n */\nabstract class CharsetRecog_sbcs extends CharsetRecognizer {\n\n    /* (non-Javadoc)\n     * @see com.ibm.icu.text.CharsetRecognizer#getName()\n     */\n    @Override\n    abstract String getName();\n\n    static class NGramParser {\n        //        private static final int N_GRAM_SIZE = 3;\n        private static final int N_GRAM_MASK = 0xFFFFFF;\n\n        protected int byteIndex = 0;\n        private int ngram = 0;\n\n        private final int[] ngramList;\n        protected byte[] byteMap;\n\n        private int ngramCount;\n        private int hitCount;\n\n        protected byte spaceChar;\n\n        public NGramParser(int[] theNgramList, byte[] theByteMap) {\n            ngramList = theNgramList;\n            byteMap = theByteMap;\n\n            ngram = 0;\n\n            ngramCount = hitCount = 0;\n        }\n\n        /*\n         * Binary search for value in table, which must have exactly 64 entries.\n         */\n        private static int search(int[] table, int value) {\n            int index = 0;\n\n            if (table[index + 32] <= value) {\n                index += 32;\n            }\n\n            if (table[index + 16] <= value) {\n                index += 16;\n            }\n\n            if (table[index + 8] <= value) {\n                index += 8;\n            }\n\n            if (table[index + 4] <= value) {\n                index += 4;\n            }\n\n            if (table[index + 2] <= value) {\n                index += 2;\n            }\n\n            if (table[index + 1] <= value) {\n                index += 1;\n            }\n\n            if (table[index] > value) {\n                index -= 1;\n            }\n\n            if (index < 0 || table[index] != value) {\n                return -1;\n            }\n\n            return index;\n        }\n\n        private void lookup(int thisNgram) {\n            ngramCount += 1;\n\n            if (search(ngramList, thisNgram) >= 0) {\n                hitCount += 1;\n            }\n\n        }\n\n        protected void addByte(int b) {\n            ngram = ((ngram << 8) + (b & 0xFF)) & N_GRAM_MASK;\n            lookup(ngram);\n        }\n\n        private int nextByte(CharsetDetector det) {\n            if (byteIndex >= det.fInputLen) {\n                return -1;\n            }\n\n            return det.fInputBytes[byteIndex++] & 0xFF;\n        }\n\n        protected void parseCharacters(CharsetDetector det) {\n            int b;\n            boolean ignoreSpace = false;\n\n            while ((b = nextByte(det)) >= 0) {\n                byte mb = byteMap[b];\n\n                // TODO: 0x20 might not be a space in all character sets...\n                if (mb != 0) {\n                    if (!(mb == spaceChar && ignoreSpace)) {\n                        addByte(mb);\n                    }\n\n                    ignoreSpace = (mb == spaceChar);\n                }\n            }\n\n        }\n\n        public int parse(CharsetDetector det) {\n            return parse(det, (byte) 0x20);\n        }\n\n        public int parse(CharsetDetector det, byte spaceCh) {\n\n            this.spaceChar = spaceCh;\n\n            parseCharacters(det);\n\n            // TODO: Is this OK? The buffer could have ended in the middle of a word...\n            addByte(spaceChar);\n\n            double rawPercent = (double) hitCount / (double) ngramCount;\n\n//                if (rawPercent <= 2.0) {\n//                    return 0;\n//                }\n\n            // TODO - This is a bit of a hack to take care of a case\n            // were we were getting a confidence of 135...\n            if (rawPercent > 0.33) {\n                return 98;\n            }\n\n            return (int) (rawPercent * 300.0);\n        }\n    }\n\n    static class NGramParser_IBM420 extends NGramParser {\n        private byte alef = 0x00;\n\n        protected static byte[] unshapeMap = {\n/*                 -0           -1           -2           -3           -4           -5           -6           -7           -8           -9           -A           -B           -C           -D           -E           -F   */\n/* 0- */    (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40,\n/* 1- */    (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40,\n/* 2- */    (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40,\n/* 3- */    (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40,\n/* 4- */    (byte) 0x40, (byte) 0x40, (byte) 0x42, (byte) 0x42, (byte) 0x44, (byte) 0x45, (byte) 0x46, (byte) 0x47, (byte) 0x47, (byte) 0x49, (byte) 0x4A, (byte) 0x4B, (byte) 0x4C, (byte) 0x4D, (byte) 0x4E, (byte) 0x4F,\n/* 5- */    (byte) 0x50, (byte) 0x49, (byte) 0x52, (byte) 0x53, (byte) 0x54, (byte) 0x55, (byte) 0x56, (byte) 0x56, (byte) 0x58, (byte) 0x58, (byte) 0x5A, (byte) 0x5B, (byte) 0x5C, (byte) 0x5D, (byte) 0x5E, (byte) 0x5F,\n/* 6- */    (byte) 0x60, (byte) 0x61, (byte) 0x62, (byte) 0x63, (byte) 0x63, (byte) 0x65, (byte) 0x65, (byte) 0x67, (byte) 0x67, (byte) 0x69, (byte) 0x6A, (byte) 0x6B, (byte) 0x6C, (byte) 0x6D, (byte) 0x6E, (byte) 0x6F,\n/* 7- */    (byte) 0x69, (byte) 0x71, (byte) 0x71, (byte) 0x73, (byte) 0x74, (byte) 0x75, (byte) 0x76, (byte) 0x77, (byte) 0x77, (byte) 0x79, (byte) 0x7A, (byte) 0x7B, (byte) 0x7C, (byte) 0x7D, (byte) 0x7E, (byte) 0x7F,\n/* 8- */    (byte) 0x80, (byte) 0x81, (byte) 0x82, (byte) 0x83, (byte) 0x84, (byte) 0x85, (byte) 0x86, (byte) 0x87, (byte) 0x88, (byte) 0x89, (byte) 0x80, (byte) 0x8B, (byte) 0x8B, (byte) 0x8D, (byte) 0x8D, (byte) 0x8F,\n/* 9- */    (byte) 0x90, (byte) 0x91, (byte) 0x92, (byte) 0x93, (byte) 0x94, (byte) 0x95, (byte) 0x96, (byte) 0x97, (byte) 0x98, (byte) 0x99, (byte) 0x9A, (byte) 0x9A, (byte) 0x9A, (byte) 0x9A, (byte) 0x9E, (byte) 0x9E,\n/* A- */    (byte) 0x9E, (byte) 0xA1, (byte) 0xA2, (byte) 0xA3, (byte) 0xA4, (byte) 0xA5, (byte) 0xA6, (byte) 0xA7, (byte) 0xA8, (byte) 0xA9, (byte) 0x9E, (byte) 0xAB, (byte) 0xAB, (byte) 0xAD, (byte) 0xAD, (byte) 0xAF,\n/* B- */    (byte) 0xAF, (byte) 0xB1, (byte) 0xB2, (byte) 0xB3, (byte) 0xB4, (byte) 0xB5, (byte) 0xB6, (byte) 0xB7, (byte) 0xB8, (byte) 0xB9, (byte) 0xB1, (byte) 0xBB, (byte) 0xBB, (byte) 0xBD, (byte) 0xBD, (byte) 0xBF,\n/* C- */    (byte) 0xC0, (byte) 0xC1, (byte) 0xC2, (byte) 0xC3, (byte) 0xC4, (byte) 0xC5, (byte) 0xC6, (byte) 0xC7, (byte) 0xC8, (byte) 0xC9, (byte) 0xCA, (byte) 0xBF, (byte) 0xCC, (byte) 0xBF, (byte) 0xCE, (byte) 0xCF,\n/* D- */    (byte) 0xD0, (byte) 0xD1, (byte) 0xD2, (byte) 0xD3, (byte) 0xD4, (byte) 0xD5, (byte) 0xD6, (byte) 0xD7, (byte) 0xD8, (byte) 0xD9, (byte) 0xDA, (byte) 0xDA, (byte) 0xDC, (byte) 0xDC, (byte) 0xDC, (byte) 0xDF,\n/* E- */    (byte) 0xE0, (byte) 0xE1, (byte) 0xE2, (byte) 0xE3, (byte) 0xE4, (byte) 0xE5, (byte) 0xE6, (byte) 0xE7, (byte) 0xE8, (byte) 0xE9, (byte) 0xEA, (byte) 0xEB, (byte) 0xEC, (byte) 0xED, (byte) 0xEE, (byte) 0xEF,\n/* F- */    (byte) 0xF0, (byte) 0xF1, (byte) 0xF2, (byte) 0xF3, (byte) 0xF4, (byte) 0xF5, (byte) 0xF6, (byte) 0xF7, (byte) 0xF8, (byte) 0xF9, (byte) 0xFA, (byte) 0xFB, (byte) 0xFC, (byte) 0xFD, (byte) 0xFE, (byte) 0xFF,\n        };\n\n\n        public NGramParser_IBM420(int[] theNgramList, byte[] theByteMap) {\n            super(theNgramList, theByteMap);\n        }\n\n        private byte isLamAlef(byte b) {\n            if (b == (byte) 0xb2 || b == (byte) 0xb3) {\n                return (byte) 0x47;\n            } else if (b == (byte) 0xb4 || b == (byte) 0xb5) {\n                return (byte) 0x49;\n            } else if (b == (byte) 0xb8 || b == (byte) 0xb9) {\n                return (byte) 0x56;\n            } else\n                return (byte) 0x00;\n        }\n\n        /*\n         * Arabic shaping needs to be done manually. Cannot call ArabicShaping class\n         * because CharsetDetector is dealing with bytes not Unicode code points. We could\n         * convert the bytes to Unicode code points but that would leave us dependent\n         * on CharsetICU which we try to avoid. IBM420 converter amongst different versions\n         * of JDK can produce different results and therefore is also avoided.\n         */\n        private int nextByte(CharsetDetector det) {\n            if (byteIndex >= det.fInputLen || det.fInputBytes[byteIndex] == 0) {\n                return -1;\n            }\n            int next;\n\n            alef = isLamAlef(det.fInputBytes[byteIndex]);\n            if (alef != (byte) 0x00)\n                next = 0xB1 & 0xFF;\n            else\n                next = unshapeMap[det.fInputBytes[byteIndex] & 0xFF] & 0xFF;\n\n            byteIndex++;\n\n            return next;\n        }\n\n        @Override\n        protected void parseCharacters(CharsetDetector det) {\n            int b;\n            boolean ignoreSpace = false;\n\n            while ((b = nextByte(det)) >= 0) {\n                byte mb = byteMap[b];\n\n                // TODO: 0x20 might not be a space in all character sets...\n                if (mb != 0) {\n                    if (!(mb == spaceChar && ignoreSpace)) {\n                        addByte(mb);\n                    }\n\n                    ignoreSpace = (mb == spaceChar);\n                }\n                if (alef != (byte) 0x00) {\n                    mb = byteMap[alef & 0xFF];\n\n                    // TODO: 0x20 might not be a space in all character sets...\n                    if (mb != 0) {\n                        if (!(mb == spaceChar && ignoreSpace)) {\n                            addByte(mb);\n                        }\n\n                        ignoreSpace = (mb == spaceChar);\n                    }\n\n                }\n            }\n        }\n    }\n\n\n    int match(CharsetDetector det, int[] ngrams, byte[] byteMap) {\n        return match(det, ngrams, byteMap, (byte) 0x20);\n    }\n\n    int match(CharsetDetector det, int[] ngrams, byte[] byteMap, byte spaceChar) {\n        NGramParser parser = new NGramParser(ngrams, byteMap);\n        return parser.parse(det, spaceChar);\n    }\n\n    @SuppressWarnings(\"SameParameterValue\")\n    int matchIBM420(CharsetDetector det, int[] ngrams, byte[] byteMap, byte spaceChar) {\n        NGramParser_IBM420 parser = new NGramParser_IBM420(ngrams, byteMap);\n        return parser.parse(det, spaceChar);\n    }\n\n    static class NGramsPlusLang {\n        int[] fNGrams;\n        String fLang;\n\n        NGramsPlusLang(String la, int[] ng) {\n            fLang = la;\n            fNGrams = ng;\n        }\n    }\n\n    static class CharsetRecog_8859_1 extends CharsetRecog_sbcs {\n        protected static byte[] byteMap = {\n                (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,\n                (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,\n                (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,\n                (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,\n                (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x00,\n                (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,\n                (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,\n                (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,\n                (byte) 0x20, (byte) 0x61, (byte) 0x62, (byte) 0x63, (byte) 0x64, (byte) 0x65, (byte) 0x66, (byte) 0x67,\n                (byte) 0x68, (byte) 0x69, (byte) 0x6A, (byte) 0x6B, (byte) 0x6C, (byte) 0x6D, (byte) 0x6E, (byte) 0x6F,\n                (byte) 0x70, (byte) 0x71, (byte) 0x72, (byte) 0x73, (byte) 0x74, (byte) 0x75, (byte) 0x76, (byte) 0x77,\n                (byte) 0x78, (byte) 0x79, (byte) 0x7A, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,\n                (byte) 0x20, (byte) 0x61, (byte) 0x62, (byte) 0x63, (byte) 0x64, (byte) 0x65, (byte) 0x66, (byte) 0x67,\n                (byte) 0x68, (byte) 0x69, (byte) 0x6A, (byte) 0x6B, (byte) 0x6C, (byte) 0x6D, (byte) 0x6E, (byte) 0x6F,\n                (byte) 0x70, (byte) 0x71, (byte) 0x72, (byte) 0x73, (byte) 0x74, (byte) 0x75, (byte) 0x76, (byte) 0x77,\n                (byte) 0x78, (byte) 0x79, (byte) 0x7A, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,\n                (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,\n                (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,\n                (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,\n                (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,\n                (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,\n                (byte) 0x20, (byte) 0x20, (byte) 0xAA, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,\n                (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0xB5, (byte) 0x20, (byte) 0x20,\n                (byte) 0x20, (byte) 0x20, (byte) 0xBA, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,\n                (byte) 0xE0, (byte) 0xE1, (byte) 0xE2, (byte) 0xE3, (byte) 0xE4, (byte) 0xE5, (byte) 0xE6, (byte) 0xE7,\n                (byte) 0xE8, (byte) 0xE9, (byte) 0xEA, (byte) 0xEB, (byte) 0xEC, (byte) 0xED, (byte) 0xEE, (byte) 0xEF,\n                (byte) 0xF0, (byte) 0xF1, (byte) 0xF2, (byte) 0xF3, (byte) 0xF4, (byte) 0xF5, (byte) 0xF6, (byte) 0x20,\n                (byte) 0xF8, (byte) 0xF9, (byte) 0xFA, (byte) 0xFB, (byte) 0xFC, (byte) 0xFD, (byte) 0xFE, (byte) 0xDF,\n                (byte) 0xE0, (byte) 0xE1, (byte) 0xE2, (byte) 0xE3, (byte) 0xE4, (byte) 0xE5, (byte) 0xE6, (byte) 0xE7,\n                (byte) 0xE8, (byte) 0xE9, (byte) 0xEA, (byte) 0xEB, (byte) 0xEC, (byte) 0xED, (byte) 0xEE, (byte) 0xEF,\n                (byte) 0xF0, (byte) 0xF1, (byte) 0xF2, (byte) 0xF3, (byte) 0xF4, (byte) 0xF5, (byte) 0xF6, (byte) 0x20,\n                (byte) 0xF8, (byte) 0xF9, (byte) 0xFA, (byte) 0xFB, (byte) 0xFC, (byte) 0xFD, (byte) 0xFE, (byte) 0xFF,\n        };\n\n\n        private static final NGramsPlusLang[] ngrams_8859_1 = new NGramsPlusLang[]{\n                new NGramsPlusLang(\n                        \"da\",\n                        new int[]{\n                                0x206166, 0x206174, 0x206465, 0x20656E, 0x206572, 0x20666F, 0x206861, 0x206920, 0x206D65, 0x206F67, 0x2070E5, 0x207369, 0x207374, 0x207469, 0x207669, 0x616620,\n                                0x616E20, 0x616E64, 0x617220, 0x617420, 0x646520, 0x64656E, 0x646572, 0x646574, 0x652073, 0x656420, 0x656465, 0x656E20, 0x656E64, 0x657220, 0x657265, 0x657320,\n                                0x657420, 0x666F72, 0x676520, 0x67656E, 0x676572, 0x696765, 0x696C20, 0x696E67, 0x6B6520, 0x6B6B65, 0x6C6572, 0x6C6967, 0x6C6C65, 0x6D6564, 0x6E6465, 0x6E6520,\n                                0x6E6720, 0x6E6765, 0x6F6720, 0x6F6D20, 0x6F7220, 0x70E520, 0x722064, 0x722065, 0x722073, 0x726520, 0x737465, 0x742073, 0x746520, 0x746572, 0x74696C, 0x766572,\n                        }),\n                new NGramsPlusLang(\n                        \"de\",\n                        new int[]{\n                                0x20616E, 0x206175, 0x206265, 0x206461, 0x206465, 0x206469, 0x206569, 0x206765, 0x206861, 0x20696E, 0x206D69, 0x207363, 0x207365, 0x20756E, 0x207665, 0x20766F,\n                                0x207765, 0x207A75, 0x626572, 0x636820, 0x636865, 0x636874, 0x646173, 0x64656E, 0x646572, 0x646965, 0x652064, 0x652073, 0x65696E, 0x656974, 0x656E20, 0x657220,\n                                0x657320, 0x67656E, 0x68656E, 0x687420, 0x696368, 0x696520, 0x696E20, 0x696E65, 0x697420, 0x6C6963, 0x6C6C65, 0x6E2061, 0x6E2064, 0x6E2073, 0x6E6420, 0x6E6465,\n                                0x6E6520, 0x6E6720, 0x6E6765, 0x6E7465, 0x722064, 0x726465, 0x726569, 0x736368, 0x737465, 0x742064, 0x746520, 0x74656E, 0x746572, 0x756E64, 0x756E67, 0x766572,\n                        }),\n                new NGramsPlusLang(\n                        \"en\",\n                        new int[]{\n                                0x206120, 0x20616E, 0x206265, 0x20636F, 0x20666F, 0x206861, 0x206865, 0x20696E, 0x206D61, 0x206F66, 0x207072, 0x207265, 0x207361, 0x207374, 0x207468, 0x20746F,\n                                0x207768, 0x616964, 0x616C20, 0x616E20, 0x616E64, 0x617320, 0x617420, 0x617465, 0x617469, 0x642061, 0x642074, 0x652061, 0x652073, 0x652074, 0x656420, 0x656E74,\n                                0x657220, 0x657320, 0x666F72, 0x686174, 0x686520, 0x686572, 0x696420, 0x696E20, 0x696E67, 0x696F6E, 0x697320, 0x6E2061, 0x6E2074, 0x6E6420, 0x6E6720, 0x6E7420,\n                                0x6F6620, 0x6F6E20, 0x6F7220, 0x726520, 0x727320, 0x732061, 0x732074, 0x736169, 0x737420, 0x742074, 0x746572, 0x746861, 0x746865, 0x74696F, 0x746F20, 0x747320,\n                        }),\n\n                new NGramsPlusLang(\n                        \"es\",\n                        new int[]{\n                                0x206120, 0x206361, 0x20636F, 0x206465, 0x20656C, 0x20656E, 0x206573, 0x20696E, 0x206C61, 0x206C6F, 0x207061, 0x20706F, 0x207072, 0x207175, 0x207265, 0x207365,\n                                0x20756E, 0x207920, 0x612063, 0x612064, 0x612065, 0x61206C, 0x612070, 0x616369, 0x61646F, 0x616C20, 0x617220, 0x617320, 0x6369F3, 0x636F6E, 0x646520, 0x64656C,\n                                0x646F20, 0x652064, 0x652065, 0x65206C, 0x656C20, 0x656E20, 0x656E74, 0x657320, 0x657374, 0x69656E, 0x69F36E, 0x6C6120, 0x6C6F73, 0x6E2065, 0x6E7465, 0x6F2064,\n                                0x6F2065, 0x6F6E20, 0x6F7220, 0x6F7320, 0x706172, 0x717565, 0x726120, 0x726573, 0x732064, 0x732065, 0x732070, 0x736520, 0x746520, 0x746F20, 0x756520, 0xF36E20,\n                        }),\n\n                new NGramsPlusLang(\n                        \"fr\",\n                        new int[]{\n                                0x206175, 0x20636F, 0x206461, 0x206465, 0x206475, 0x20656E, 0x206574, 0x206C61, 0x206C65, 0x207061, 0x20706F, 0x207072, 0x207175, 0x207365, 0x20736F, 0x20756E,\n                                0x20E020, 0x616E74, 0x617469, 0x636520, 0x636F6E, 0x646520, 0x646573, 0x647520, 0x652061, 0x652063, 0x652064, 0x652065, 0x65206C, 0x652070, 0x652073, 0x656E20,\n                                0x656E74, 0x657220, 0x657320, 0x657420, 0x657572, 0x696F6E, 0x697320, 0x697420, 0x6C6120, 0x6C6520, 0x6C6573, 0x6D656E, 0x6E2064, 0x6E6520, 0x6E7320, 0x6E7420,\n                                0x6F6E20, 0x6F6E74, 0x6F7572, 0x717565, 0x72206C, 0x726520, 0x732061, 0x732064, 0x732065, 0x73206C, 0x732070, 0x742064, 0x746520, 0x74696F, 0x756520, 0x757220,\n                        }),\n\n                new NGramsPlusLang(\n                        \"it\",\n                        new int[]{\n                                0x20616C, 0x206368, 0x20636F, 0x206465, 0x206469, 0x206520, 0x20696C, 0x20696E, 0x206C61, 0x207065, 0x207072, 0x20756E, 0x612063, 0x612064, 0x612070, 0x612073,\n                                0x61746F, 0x636865, 0x636F6E, 0x64656C, 0x646920, 0x652061, 0x652063, 0x652064, 0x652069, 0x65206C, 0x652070, 0x652073, 0x656C20, 0x656C6C, 0x656E74, 0x657220,\n                                0x686520, 0x692061, 0x692063, 0x692064, 0x692073, 0x696120, 0x696C20, 0x696E20, 0x696F6E, 0x6C6120, 0x6C6520, 0x6C6920, 0x6C6C61, 0x6E6520, 0x6E6920, 0x6E6F20,\n                                0x6E7465, 0x6F2061, 0x6F2064, 0x6F2069, 0x6F2073, 0x6F6E20, 0x6F6E65, 0x706572, 0x726120, 0x726520, 0x736920, 0x746120, 0x746520, 0x746920, 0x746F20, 0x7A696F,\n                        }),\n\n                new NGramsPlusLang(\n                        \"nl\",\n                        new int[]{\n                                0x20616C, 0x206265, 0x206461, 0x206465, 0x206469, 0x206565, 0x20656E, 0x206765, 0x206865, 0x20696E, 0x206D61, 0x206D65, 0x206F70, 0x207465, 0x207661, 0x207665,\n                                0x20766F, 0x207765, 0x207A69, 0x61616E, 0x616172, 0x616E20, 0x616E64, 0x617220, 0x617420, 0x636874, 0x646520, 0x64656E, 0x646572, 0x652062, 0x652076, 0x65656E,\n                                0x656572, 0x656E20, 0x657220, 0x657273, 0x657420, 0x67656E, 0x686574, 0x696520, 0x696E20, 0x696E67, 0x697320, 0x6E2062, 0x6E2064, 0x6E2065, 0x6E2068, 0x6E206F,\n                                0x6E2076, 0x6E6465, 0x6E6720, 0x6F6E64, 0x6F6F72, 0x6F7020, 0x6F7220, 0x736368, 0x737465, 0x742064, 0x746520, 0x74656E, 0x746572, 0x76616E, 0x766572, 0x766F6F,\n                        }),\n\n                new NGramsPlusLang(\n                        \"no\",\n                        new int[]{\n                                0x206174, 0x206176, 0x206465, 0x20656E, 0x206572, 0x20666F, 0x206861, 0x206920, 0x206D65, 0x206F67, 0x2070E5, 0x207365, 0x20736B, 0x20736F, 0x207374, 0x207469,\n                                0x207669, 0x20E520, 0x616E64, 0x617220, 0x617420, 0x646520, 0x64656E, 0x646574, 0x652073, 0x656420, 0x656E20, 0x656E65, 0x657220, 0x657265, 0x657420, 0x657474,\n                                0x666F72, 0x67656E, 0x696B6B, 0x696C20, 0x696E67, 0x6B6520, 0x6B6B65, 0x6C6520, 0x6C6C65, 0x6D6564, 0x6D656E, 0x6E2073, 0x6E6520, 0x6E6720, 0x6E6765, 0x6E6E65,\n                                0x6F6720, 0x6F6D20, 0x6F7220, 0x70E520, 0x722073, 0x726520, 0x736F6D, 0x737465, 0x742073, 0x746520, 0x74656E, 0x746572, 0x74696C, 0x747420, 0x747465, 0x766572,\n                        }),\n\n                new NGramsPlusLang(\n                        \"pt\",\n                        new int[]{\n                                0x206120, 0x20636F, 0x206461, 0x206465, 0x20646F, 0x206520, 0x206573, 0x206D61, 0x206E6F, 0x206F20, 0x207061, 0x20706F, 0x207072, 0x207175, 0x207265, 0x207365,\n                                0x20756D, 0x612061, 0x612063, 0x612064, 0x612070, 0x616465, 0x61646F, 0x616C20, 0x617220, 0x617261, 0x617320, 0x636F6D, 0x636F6E, 0x646120, 0x646520, 0x646F20,\n                                0x646F73, 0x652061, 0x652064, 0x656D20, 0x656E74, 0x657320, 0x657374, 0x696120, 0x696361, 0x6D656E, 0x6E7465, 0x6E746F, 0x6F2061, 0x6F2063, 0x6F2064, 0x6F2065,\n                                0x6F2070, 0x6F7320, 0x706172, 0x717565, 0x726120, 0x726573, 0x732061, 0x732064, 0x732065, 0x732070, 0x737461, 0x746520, 0x746F20, 0x756520, 0xE36F20, 0xE7E36F,\n\n                        }),\n\n                new NGramsPlusLang(\n                        \"sv\",\n                        new int[]{\n                                0x206174, 0x206176, 0x206465, 0x20656E, 0x2066F6, 0x206861, 0x206920, 0x20696E, 0x206B6F, 0x206D65, 0x206F63, 0x2070E5, 0x20736B, 0x20736F, 0x207374, 0x207469,\n                                0x207661, 0x207669, 0x20E472, 0x616465, 0x616E20, 0x616E64, 0x617220, 0x617474, 0x636820, 0x646520, 0x64656E, 0x646572, 0x646574, 0x656420, 0x656E20, 0x657220,\n                                0x657420, 0x66F672, 0x67656E, 0x696C6C, 0x696E67, 0x6B6120, 0x6C6C20, 0x6D6564, 0x6E2073, 0x6E6120, 0x6E6465, 0x6E6720, 0x6E6765, 0x6E696E, 0x6F6368, 0x6F6D20,\n                                0x6F6E20, 0x70E520, 0x722061, 0x722073, 0x726120, 0x736B61, 0x736F6D, 0x742073, 0x746120, 0x746520, 0x746572, 0x74696C, 0x747420, 0x766172, 0xE47220, 0xF67220,\n                        }),\n\n        };\n\n\n        @Override\n        public CharsetMatch match(CharsetDetector det) {\n            String name = det.fC1Bytes ? \"windows-1252\" : \"ISO-8859-1\";\n            int bestConfidenceSoFar = -1;\n            String lang = null;\n            for (NGramsPlusLang ngl : ngrams_8859_1) {\n                int confidence = match(det, ngl.fNGrams, byteMap);\n                if (confidence > bestConfidenceSoFar) {\n                    bestConfidenceSoFar = confidence;\n                    lang = ngl.fLang;\n                }\n            }\n            return bestConfidenceSoFar <= 0 ? null : new CharsetMatch(det, this, bestConfidenceSoFar, name, lang);\n        }\n\n\n        @Override\n        public String getName() {\n            return \"ISO-8859-1\";\n        }\n    }\n\n\n    static class CharsetRecog_8859_2 extends CharsetRecog_sbcs {\n        protected static byte[] byteMap = {\n                (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,\n                (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,\n                (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,\n                (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,\n                (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x00,\n                (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,\n                (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,\n                (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,\n                (byte) 0x20, (byte) 0x61, (byte) 0x62, (byte) 0x63, (byte) 0x64, (byte) 0x65, (byte) 0x66, (byte) 0x67,\n                (byte) 0x68, (byte) 0x69, (byte) 0x6A, (byte) 0x6B, (byte) 0x6C, (byte) 0x6D, (byte) 0x6E, (byte) 0x6F,\n                (byte) 0x70, (byte) 0x71, (byte) 0x72, (byte) 0x73, (byte) 0x74, (byte) 0x75, (byte) 0x76, (byte) 0x77,\n                (byte) 0x78, (byte) 0x79, (byte) 0x7A, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,\n                (byte) 0x20, (byte) 0x61, (byte) 0x62, (byte) 0x63, (byte) 0x64, (byte) 0x65, (byte) 0x66, (byte) 0x67,\n                (byte) 0x68, (byte) 0x69, (byte) 0x6A, (byte) 0x6B, (byte) 0x6C, (byte) 0x6D, (byte) 0x6E, (byte) 0x6F,\n                (byte) 0x70, (byte) 0x71, (byte) 0x72, (byte) 0x73, (byte) 0x74, (byte) 0x75, (byte) 0x76, (byte) 0x77,\n                (byte) 0x78, (byte) 0x79, (byte) 0x7A, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,\n                (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,\n                (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,\n                (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,\n                (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,\n                (byte) 0x20, (byte) 0xB1, (byte) 0x20, (byte) 0xB3, (byte) 0x20, (byte) 0xB5, (byte) 0xB6, (byte) 0x20,\n                (byte) 0x20, (byte) 0xB9, (byte) 0xBA, (byte) 0xBB, (byte) 0xBC, (byte) 0x20, (byte) 0xBE, (byte) 0xBF,\n                (byte) 0x20, (byte) 0xB1, (byte) 0x20, (byte) 0xB3, (byte) 0x20, (byte) 0xB5, (byte) 0xB6, (byte) 0xB7,\n                (byte) 0x20, (byte) 0xB9, (byte) 0xBA, (byte) 0xBB, (byte) 0xBC, (byte) 0x20, (byte) 0xBE, (byte) 0xBF,\n                (byte) 0xE0, (byte) 0xE1, (byte) 0xE2, (byte) 0xE3, (byte) 0xE4, (byte) 0xE5, (byte) 0xE6, (byte) 0xE7,\n                (byte) 0xE8, (byte) 0xE9, (byte) 0xEA, (byte) 0xEB, (byte) 0xEC, (byte) 0xED, (byte) 0xEE, (byte) 0xEF,\n                (byte) 0xF0, (byte) 0xF1, (byte) 0xF2, (byte) 0xF3, (byte) 0xF4, (byte) 0xF5, (byte) 0xF6, (byte) 0x20,\n                (byte) 0xF8, (byte) 0xF9, (byte) 0xFA, (byte) 0xFB, (byte) 0xFC, (byte) 0xFD, (byte) 0xFE, (byte) 0xDF,\n                (byte) 0xE0, (byte) 0xE1, (byte) 0xE2, (byte) 0xE3, (byte) 0xE4, (byte) 0xE5, (byte) 0xE6, (byte) 0xE7,\n                (byte) 0xE8, (byte) 0xE9, (byte) 0xEA, (byte) 0xEB, (byte) 0xEC, (byte) 0xED, (byte) 0xEE, (byte) 0xEF,\n                (byte) 0xF0, (byte) 0xF1, (byte) 0xF2, (byte) 0xF3, (byte) 0xF4, (byte) 0xF5, (byte) 0xF6, (byte) 0x20,\n                (byte) 0xF8, (byte) 0xF9, (byte) 0xFA, (byte) 0xFB, (byte) 0xFC, (byte) 0xFD, (byte) 0xFE, (byte) 0x20,\n        };\n\n        private static final NGramsPlusLang[] ngrams_8859_2 = new NGramsPlusLang[]{\n                new NGramsPlusLang(\n                        \"cs\",\n                        new int[]{\n                                0x206120, 0x206279, 0x20646F, 0x206A65, 0x206E61, 0x206E65, 0x206F20, 0x206F64, 0x20706F, 0x207072, 0x2070F8, 0x20726F, 0x207365, 0x20736F, 0x207374, 0x20746F,\n                                0x207620, 0x207679, 0x207A61, 0x612070, 0x636520, 0x636820, 0x652070, 0x652073, 0x652076, 0x656D20, 0x656EED, 0x686F20, 0x686F64, 0x697374, 0x6A6520, 0x6B7465,\n                                0x6C6520, 0x6C6920, 0x6E6120, 0x6EE920, 0x6EEC20, 0x6EED20, 0x6F2070, 0x6F646E, 0x6F6A69, 0x6F7374, 0x6F7520, 0x6F7661, 0x706F64, 0x706F6A, 0x70726F, 0x70F865,\n                                0x736520, 0x736F75, 0x737461, 0x737469, 0x73746E, 0x746572, 0x746EED, 0x746F20, 0x752070, 0xBE6520, 0xE16EED, 0xE9686F, 0xED2070, 0xED2073, 0xED6D20, 0xF86564,\n                        }),\n                new NGramsPlusLang(\n                        \"hu\",\n                        new int[]{\n                                0x206120, 0x20617A, 0x206265, 0x206567, 0x20656C, 0x206665, 0x206861, 0x20686F, 0x206973, 0x206B65, 0x206B69, 0x206BF6, 0x206C65, 0x206D61, 0x206D65, 0x206D69,\n                                0x206E65, 0x20737A, 0x207465, 0x20E973, 0x612061, 0x61206B, 0x61206D, 0x612073, 0x616B20, 0x616E20, 0x617A20, 0x62616E, 0x62656E, 0x656779, 0x656B20, 0x656C20,\n                                0x656C65, 0x656D20, 0x656E20, 0x657265, 0x657420, 0x657465, 0x657474, 0x677920, 0x686F67, 0x696E74, 0x697320, 0x6B2061, 0x6BF67A, 0x6D6567, 0x6D696E, 0x6E2061,\n                                0x6E616B, 0x6E656B, 0x6E656D, 0x6E7420, 0x6F6779, 0x732061, 0x737A65, 0x737A74, 0x737AE1, 0x73E967, 0x742061, 0x747420, 0x74E173, 0x7A6572, 0xE16E20, 0xE97320,\n                        }),\n                new NGramsPlusLang(\n                        \"pl\",\n                        new int[]{\n                                0x20637A, 0x20646F, 0x206920, 0x206A65, 0x206B6F, 0x206D61, 0x206D69, 0x206E61, 0x206E69, 0x206F64, 0x20706F, 0x207072, 0x207369, 0x207720, 0x207769, 0x207779,\n                                0x207A20, 0x207A61, 0x612070, 0x612077, 0x616E69, 0x636820, 0x637A65, 0x637A79, 0x646F20, 0x647A69, 0x652070, 0x652073, 0x652077, 0x65207A, 0x65676F, 0x656A20,\n                                0x656D20, 0x656E69, 0x676F20, 0x696120, 0x696520, 0x69656A, 0x6B6120, 0x6B6920, 0x6B6965, 0x6D6965, 0x6E6120, 0x6E6961, 0x6E6965, 0x6F2070, 0x6F7761, 0x6F7769,\n                                0x706F6C, 0x707261, 0x70726F, 0x70727A, 0x727A65, 0x727A79, 0x7369EA, 0x736B69, 0x737461, 0x776965, 0x796368, 0x796D20, 0x7A6520, 0x7A6965, 0x7A7920, 0xF37720,\n                        }),\n                new NGramsPlusLang(\n                        \"ro\",\n                        new int[]{\n                                0x206120, 0x206163, 0x206361, 0x206365, 0x20636F, 0x206375, 0x206465, 0x206469, 0x206C61, 0x206D61, 0x207065, 0x207072, 0x207365, 0x2073E3, 0x20756E, 0x20BA69,\n                                0x20EE6E, 0x612063, 0x612064, 0x617265, 0x617420, 0x617465, 0x617520, 0x636172, 0x636F6E, 0x637520, 0x63E320, 0x646520, 0x652061, 0x652063, 0x652064, 0x652070,\n                                0x652073, 0x656120, 0x656920, 0x656C65, 0x656E74, 0x657374, 0x692061, 0x692063, 0x692064, 0x692070, 0x696520, 0x696920, 0x696E20, 0x6C6120, 0x6C6520, 0x6C6F72,\n                                0x6C7569, 0x6E6520, 0x6E7472, 0x6F7220, 0x70656E, 0x726520, 0x726561, 0x727520, 0x73E320, 0x746520, 0x747275, 0x74E320, 0x756920, 0x756C20, 0xBA6920, 0xEE6E20,\n                        })\n        };\n\n        @Override\n        public CharsetMatch match(CharsetDetector det) {\n            String name = det.fC1Bytes ? \"windows-1250\" : \"ISO-8859-2\";\n            int bestConfidenceSoFar = -1;\n            String lang = null;\n            for (NGramsPlusLang ngl : ngrams_8859_2) {\n                int confidence = match(det, ngl.fNGrams, byteMap);\n                if (confidence > bestConfidenceSoFar) {\n                    bestConfidenceSoFar = confidence;\n                    lang = ngl.fLang;\n                }\n            }\n            return bestConfidenceSoFar <= 0 ? null : new CharsetMatch(det, this, bestConfidenceSoFar, name, lang);\n        }\n\n        @Override\n        public String getName() {\n            return \"ISO-8859-2\";\n        }\n\n    }\n\n\n    abstract static class CharsetRecog_8859_5 extends CharsetRecog_sbcs {\n        protected static byte[] byteMap = {\n                (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,\n                (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,\n                (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,\n                (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,\n                (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x00,\n                (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,\n                (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,\n                (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,\n                (byte) 0x20, (byte) 0x61, (byte) 0x62, (byte) 0x63, (byte) 0x64, (byte) 0x65, (byte) 0x66, (byte) 0x67,\n                (byte) 0x68, (byte) 0x69, (byte) 0x6A, (byte) 0x6B, (byte) 0x6C, (byte) 0x6D, (byte) 0x6E, (byte) 0x6F,\n                (byte) 0x70, (byte) 0x71, (byte) 0x72, (byte) 0x73, (byte) 0x74, (byte) 0x75, (byte) 0x76, (byte) 0x77,\n                (byte) 0x78, (byte) 0x79, (byte) 0x7A, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,\n                (byte) 0x20, (byte) 0x61, (byte) 0x62, (byte) 0x63, (byte) 0x64, (byte) 0x65, (byte) 0x66, (byte) 0x67,\n                (byte) 0x68, (byte) 0x69, (byte) 0x6A, (byte) 0x6B, (byte) 0x6C, (byte) 0x6D, (byte) 0x6E, (byte) 0x6F,\n                (byte) 0x70, (byte) 0x71, (byte) 0x72, (byte) 0x73, (byte) 0x74, (byte) 0x75, (byte) 0x76, (byte) 0x77,\n                (byte) 0x78, (byte) 0x79, (byte) 0x7A, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,\n                (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,\n                (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,\n                (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,\n                (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,\n                (byte) 0x20, (byte) 0xF1, (byte) 0xF2, (byte) 0xF3, (byte) 0xF4, (byte) 0xF5, (byte) 0xF6, (byte) 0xF7,\n                (byte) 0xF8, (byte) 0xF9, (byte) 0xFA, (byte) 0xFB, (byte) 0xFC, (byte) 0x20, (byte) 0xFE, (byte) 0xFF,\n                (byte) 0xD0, (byte) 0xD1, (byte) 0xD2, (byte) 0xD3, (byte) 0xD4, (byte) 0xD5, (byte) 0xD6, (byte) 0xD7,\n                (byte) 0xD8, (byte) 0xD9, (byte) 0xDA, (byte) 0xDB, (byte) 0xDC, (byte) 0xDD, (byte) 0xDE, (byte) 0xDF,\n                (byte) 0xE0, (byte) 0xE1, (byte) 0xE2, (byte) 0xE3, (byte) 0xE4, (byte) 0xE5, (byte) 0xE6, (byte) 0xE7,\n                (byte) 0xE8, (byte) 0xE9, (byte) 0xEA, (byte) 0xEB, (byte) 0xEC, (byte) 0xED, (byte) 0xEE, (byte) 0xEF,\n                (byte) 0xD0, (byte) 0xD1, (byte) 0xD2, (byte) 0xD3, (byte) 0xD4, (byte) 0xD5, (byte) 0xD6, (byte) 0xD7,\n                (byte) 0xD8, (byte) 0xD9, (byte) 0xDA, (byte) 0xDB, (byte) 0xDC, (byte) 0xDD, (byte) 0xDE, (byte) 0xDF,\n                (byte) 0xE0, (byte) 0xE1, (byte) 0xE2, (byte) 0xE3, (byte) 0xE4, (byte) 0xE5, (byte) 0xE6, (byte) 0xE7,\n                (byte) 0xE8, (byte) 0xE9, (byte) 0xEA, (byte) 0xEB, (byte) 0xEC, (byte) 0xED, (byte) 0xEE, (byte) 0xEF,\n                (byte) 0x20, (byte) 0xF1, (byte) 0xF2, (byte) 0xF3, (byte) 0xF4, (byte) 0xF5, (byte) 0xF6, (byte) 0xF7,\n                (byte) 0xF8, (byte) 0xF9, (byte) 0xFA, (byte) 0xFB, (byte) 0xFC, (byte) 0x20, (byte) 0xFE, (byte) 0xFF,\n        };\n\n        @Override\n        public String getName() {\n            return \"ISO-8859-5\";\n        }\n    }\n\n    static class CharsetRecog_8859_5_ru extends CharsetRecog_8859_5 {\n        private static final int[] ngrams = {\n                0x20D220, 0x20D2DE, 0x20D4DE, 0x20D7D0, 0x20D820, 0x20DAD0, 0x20DADE, 0x20DDD0, 0x20DDD5, 0x20DED1, 0x20DFDE, 0x20DFE0, 0x20E0D0, 0x20E1DE, 0x20E1E2, 0x20E2DE,\n                0x20E7E2, 0x20EDE2, 0xD0DDD8, 0xD0E2EC, 0xD3DE20, 0xD5DBEC, 0xD5DDD8, 0xD5E1E2, 0xD5E220, 0xD820DF, 0xD8D520, 0xD8D820, 0xD8EF20, 0xDBD5DD, 0xDBD820, 0xDBECDD,\n                0xDDD020, 0xDDD520, 0xDDD8D5, 0xDDD8EF, 0xDDDE20, 0xDDDED2, 0xDE20D2, 0xDE20DF, 0xDE20E1, 0xDED220, 0xDED2D0, 0xDED3DE, 0xDED920, 0xDEDBEC, 0xDEDC20, 0xDEE1E2,\n                0xDFDEDB, 0xDFE0D5, 0xDFE0D8, 0xDFE0DE, 0xE0D0D2, 0xE0D5D4, 0xE1E2D0, 0xE1E2D2, 0xE1E2D8, 0xE1EF20, 0xE2D5DB, 0xE2DE20, 0xE2DEE0, 0xE2EC20, 0xE7E2DE, 0xEBE520,\n        };\n\n        @Override\n        public String getLanguage() {\n            return \"ru\";\n        }\n\n        @Override\n        public CharsetMatch match(CharsetDetector det) {\n            int confidence = match(det, ngrams, byteMap);\n            return confidence == 0 ? null : new CharsetMatch(det, this, confidence);\n        }\n    }\n\n    abstract static class CharsetRecog_8859_6 extends CharsetRecog_sbcs {\n        protected static byte[] byteMap = {\n                (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,\n                (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,\n                (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,\n                (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,\n                (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x00,\n                (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,\n                (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,\n                (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,\n                (byte) 0x20, (byte) 0x61, (byte) 0x62, (byte) 0x63, (byte) 0x64, (byte) 0x65, (byte) 0x66, (byte) 0x67,\n                (byte) 0x68, (byte) 0x69, (byte) 0x6A, (byte) 0x6B, (byte) 0x6C, (byte) 0x6D, (byte) 0x6E, (byte) 0x6F,\n                (byte) 0x70, (byte) 0x71, (byte) 0x72, (byte) 0x73, (byte) 0x74, (byte) 0x75, (byte) 0x76, (byte) 0x77,\n                (byte) 0x78, (byte) 0x79, (byte) 0x7A, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,\n                (byte) 0x20, (byte) 0x61, (byte) 0x62, (byte) 0x63, (byte) 0x64, (byte) 0x65, (byte) 0x66, (byte) 0x67,\n                (byte) 0x68, (byte) 0x69, (byte) 0x6A, (byte) 0x6B, (byte) 0x6C, (byte) 0x6D, (byte) 0x6E, (byte) 0x6F,\n                (byte) 0x70, (byte) 0x71, (byte) 0x72, (byte) 0x73, (byte) 0x74, (byte) 0x75, (byte) 0x76, (byte) 0x77,\n                (byte) 0x78, (byte) 0x79, (byte) 0x7A, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,\n                (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,\n                (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,\n                (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,\n                (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,\n                (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,\n                (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,\n                (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,\n                (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,\n                (byte) 0x20, (byte) 0xC1, (byte) 0xC2, (byte) 0xC3, (byte) 0xC4, (byte) 0xC5, (byte) 0xC6, (byte) 0xC7,\n                (byte) 0xC8, (byte) 0xC9, (byte) 0xCA, (byte) 0xCB, (byte) 0xCC, (byte) 0xCD, (byte) 0xCE, (byte) 0xCF,\n                (byte) 0xD0, (byte) 0xD1, (byte) 0xD2, (byte) 0xD3, (byte) 0xD4, (byte) 0xD5, (byte) 0xD6, (byte) 0xD7,\n                (byte) 0xD8, (byte) 0xD9, (byte) 0xDA, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,\n                (byte) 0xE0, (byte) 0xE1, (byte) 0xE2, (byte) 0xE3, (byte) 0xE4, (byte) 0xE5, (byte) 0xE6, (byte) 0xE7,\n                (byte) 0xE8, (byte) 0xE9, (byte) 0xEA, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,\n                (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,\n                (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,\n        };\n\n        @Override\n        public String getName() {\n            return \"ISO-8859-6\";\n        }\n    }\n\n    static class CharsetRecog_8859_6_ar extends CharsetRecog_8859_6 {\n        private static final int[] ngrams = {\n                0x20C7E4, 0x20C7E6, 0x20C8C7, 0x20D9E4, 0x20E1EA, 0x20E4E4, 0x20E5E6, 0x20E8C7, 0xC720C7, 0xC7C120, 0xC7CA20, 0xC7D120, 0xC7E420, 0xC7E4C3, 0xC7E4C7, 0xC7E4C8,\n                0xC7E4CA, 0xC7E4CC, 0xC7E4CD, 0xC7E4CF, 0xC7E4D3, 0xC7E4D9, 0xC7E4E2, 0xC7E4E5, 0xC7E4E8, 0xC7E4EA, 0xC7E520, 0xC7E620, 0xC7E6CA, 0xC820C7, 0xC920C7, 0xC920E1,\n                0xC920E4, 0xC920E5, 0xC920E8, 0xCA20C7, 0xCF20C7, 0xCFC920, 0xD120C7, 0xD1C920, 0xD320C7, 0xD920C7, 0xD9E4E9, 0xE1EA20, 0xE420C7, 0xE4C920, 0xE4E920, 0xE4EA20,\n                0xE520C7, 0xE5C720, 0xE5C920, 0xE5E620, 0xE620C7, 0xE720C7, 0xE7C720, 0xE8C7E4, 0xE8E620, 0xE920C7, 0xEA20C7, 0xEA20E5, 0xEA20E8, 0xEAC920, 0xEAD120, 0xEAE620,\n        };\n\n        @Override\n        public String getLanguage() {\n            return \"ar\";\n        }\n\n        @Override\n        public CharsetMatch match(CharsetDetector det) {\n            int confidence = match(det, ngrams, byteMap);\n            return confidence == 0 ? null : new CharsetMatch(det, this, confidence);\n        }\n    }\n\n    abstract static class CharsetRecog_8859_7 extends CharsetRecog_sbcs {\n        protected static byte[] byteMap = {\n                (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,\n                (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,\n                (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,\n                (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,\n                (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x00,\n                (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,\n                (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,\n                (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,\n                (byte) 0x20, (byte) 0x61, (byte) 0x62, (byte) 0x63, (byte) 0x64, (byte) 0x65, (byte) 0x66, (byte) 0x67,\n                (byte) 0x68, (byte) 0x69, (byte) 0x6A, (byte) 0x6B, (byte) 0x6C, (byte) 0x6D, (byte) 0x6E, (byte) 0x6F,\n                (byte) 0x70, (byte) 0x71, (byte) 0x72, (byte) 0x73, (byte) 0x74, (byte) 0x75, (byte) 0x76, (byte) 0x77,\n                (byte) 0x78, (byte) 0x79, (byte) 0x7A, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,\n                (byte) 0x20, (byte) 0x61, (byte) 0x62, (byte) 0x63, (byte) 0x64, (byte) 0x65, (byte) 0x66, (byte) 0x67,\n                (byte) 0x68, (byte) 0x69, (byte) 0x6A, (byte) 0x6B, (byte) 0x6C, (byte) 0x6D, (byte) 0x6E, (byte) 0x6F,\n                (byte) 0x70, (byte) 0x71, (byte) 0x72, (byte) 0x73, (byte) 0x74, (byte) 0x75, (byte) 0x76, (byte) 0x77,\n                (byte) 0x78, (byte) 0x79, (byte) 0x7A, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,\n                (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,\n                (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,\n                (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,\n                (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,\n                (byte) 0x20, (byte) 0xA1, (byte) 0xA2, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,\n                (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,\n                (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0xDC, (byte) 0x20,\n                (byte) 0xDD, (byte) 0xDE, (byte) 0xDF, (byte) 0x20, (byte) 0xFC, (byte) 0x20, (byte) 0xFD, (byte) 0xFE,\n                (byte) 0xC0, (byte) 0xE1, (byte) 0xE2, (byte) 0xE3, (byte) 0xE4, (byte) 0xE5, (byte) 0xE6, (byte) 0xE7,\n                (byte) 0xE8, (byte) 0xE9, (byte) 0xEA, (byte) 0xEB, (byte) 0xEC, (byte) 0xED, (byte) 0xEE, (byte) 0xEF,\n                (byte) 0xF0, (byte) 0xF1, (byte) 0x20, (byte) 0xF3, (byte) 0xF4, (byte) 0xF5, (byte) 0xF6, (byte) 0xF7,\n                (byte) 0xF8, (byte) 0xF9, (byte) 0xFA, (byte) 0xFB, (byte) 0xDC, (byte) 0xDD, (byte) 0xDE, (byte) 0xDF,\n                (byte) 0xE0, (byte) 0xE1, (byte) 0xE2, (byte) 0xE3, (byte) 0xE4, (byte) 0xE5, (byte) 0xE6, (byte) 0xE7,\n                (byte) 0xE8, (byte) 0xE9, (byte) 0xEA, (byte) 0xEB, (byte) 0xEC, (byte) 0xED, (byte) 0xEE, (byte) 0xEF,\n                (byte) 0xF0, (byte) 0xF1, (byte) 0xF2, (byte) 0xF3, (byte) 0xF4, (byte) 0xF5, (byte) 0xF6, (byte) 0xF7,\n                (byte) 0xF8, (byte) 0xF9, (byte) 0xFA, (byte) 0xFB, (byte) 0xFC, (byte) 0xFD, (byte) 0xFE, (byte) 0x20,\n        };\n\n        @Override\n        public String getName() {\n            return \"ISO-8859-7\";\n        }\n    }\n\n    static class CharsetRecog_8859_7_el extends CharsetRecog_8859_7 {\n        private static final int[] ngrams = {\n                0x20E1ED, 0x20E1F0, 0x20E3E9, 0x20E4E9, 0x20E5F0, 0x20E720, 0x20EAE1, 0x20ECE5, 0x20EDE1, 0x20EF20, 0x20F0E1, 0x20F0EF, 0x20F0F1, 0x20F3F4, 0x20F3F5, 0x20F4E7,\n                0x20F4EF, 0xDFE120, 0xE120E1, 0xE120F4, 0xE1E920, 0xE1ED20, 0xE1F0FC, 0xE1F220, 0xE3E9E1, 0xE5E920, 0xE5F220, 0xE720F4, 0xE7ED20, 0xE7F220, 0xE920F4, 0xE9E120,\n                0xE9EADE, 0xE9F220, 0xEAE1E9, 0xEAE1F4, 0xECE520, 0xED20E1, 0xED20E5, 0xED20F0, 0xEDE120, 0xEFF220, 0xEFF520, 0xF0EFF5, 0xF0F1EF, 0xF0FC20, 0xF220E1, 0xF220E5,\n                0xF220EA, 0xF220F0, 0xF220F4, 0xF3E520, 0xF3E720, 0xF3F4EF, 0xF4E120, 0xF4E1E9, 0xF4E7ED, 0xF4E7F2, 0xF4E9EA, 0xF4EF20, 0xF4EFF5, 0xF4F9ED, 0xF9ED20, 0xFEED20,\n        };\n\n        @Override\n        public String getLanguage() {\n            return \"el\";\n        }\n\n        @Override\n        public CharsetMatch match(CharsetDetector det) {\n            String name = det.fC1Bytes ? \"windows-1253\" : \"ISO-8859-7\";\n            int confidence = match(det, ngrams, byteMap);\n            return confidence == 0 ? null : new CharsetMatch(det, this, confidence, name, \"el\");\n        }\n    }\n\n    abstract static class CharsetRecog_8859_8 extends CharsetRecog_sbcs {\n        protected static byte[] byteMap = {\n                (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,\n                (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,\n                (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,\n                (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,\n                (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x00,\n                (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,\n                (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,\n                (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,\n                (byte) 0x20, (byte) 0x61, (byte) 0x62, (byte) 0x63, (byte) 0x64, (byte) 0x65, (byte) 0x66, (byte) 0x67,\n                (byte) 0x68, (byte) 0x69, (byte) 0x6A, (byte) 0x6B, (byte) 0x6C, (byte) 0x6D, (byte) 0x6E, (byte) 0x6F,\n                (byte) 0x70, (byte) 0x71, (byte) 0x72, (byte) 0x73, (byte) 0x74, (byte) 0x75, (byte) 0x76, (byte) 0x77,\n                (byte) 0x78, (byte) 0x79, (byte) 0x7A, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,\n                (byte) 0x20, (byte) 0x61, (byte) 0x62, (byte) 0x63, (byte) 0x64, (byte) 0x65, (byte) 0x66, (byte) 0x67,\n                (byte) 0x68, (byte) 0x69, (byte) 0x6A, (byte) 0x6B, (byte) 0x6C, (byte) 0x6D, (byte) 0x6E, (byte) 0x6F,\n                (byte) 0x70, (byte) 0x71, (byte) 0x72, (byte) 0x73, (byte) 0x74, (byte) 0x75, (byte) 0x76, (byte) 0x77,\n                (byte) 0x78, (byte) 0x79, (byte) 0x7A, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,\n                (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,\n                (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,\n                (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,\n                (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,\n                (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,\n                (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,\n                (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0xB5, (byte) 0x20, (byte) 0x20,\n                (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,\n                (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,\n                (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,\n                (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,\n                (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,\n                (byte) 0xE0, (byte) 0xE1, (byte) 0xE2, (byte) 0xE3, (byte) 0xE4, (byte) 0xE5, (byte) 0xE6, (byte) 0xE7,\n                (byte) 0xE8, (byte) 0xE9, (byte) 0xEA, (byte) 0xEB, (byte) 0xEC, (byte) 0xED, (byte) 0xEE, (byte) 0xEF,\n                (byte) 0xF0, (byte) 0xF1, (byte) 0xF2, (byte) 0xF3, (byte) 0xF4, (byte) 0xF5, (byte) 0xF6, (byte) 0xF7,\n                (byte) 0xF8, (byte) 0xF9, (byte) 0xFA, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,\n        };\n\n        @Override\n        public String getName() {\n            return \"ISO-8859-8\";\n        }\n    }\n\n    static class CharsetRecog_8859_8_I_he extends CharsetRecog_8859_8 {\n        private static final int[] ngrams = {\n                0x20E0E5, 0x20E0E7, 0x20E0E9, 0x20E0FA, 0x20E1E9, 0x20E1EE, 0x20E4E0, 0x20E4E5, 0x20E4E9, 0x20E4EE, 0x20E4F2, 0x20E4F9, 0x20E4FA, 0x20ECE0, 0x20ECE4, 0x20EEE0,\n                0x20F2EC, 0x20F9EC, 0xE0FA20, 0xE420E0, 0xE420E1, 0xE420E4, 0xE420EC, 0xE420EE, 0xE420F9, 0xE4E5E0, 0xE5E020, 0xE5ED20, 0xE5EF20, 0xE5F820, 0xE5FA20, 0xE920E4,\n                0xE9E420, 0xE9E5FA, 0xE9E9ED, 0xE9ED20, 0xE9EF20, 0xE9F820, 0xE9FA20, 0xEC20E0, 0xEC20E4, 0xECE020, 0xECE420, 0xED20E0, 0xED20E1, 0xED20E4, 0xED20EC, 0xED20EE,\n                0xED20F9, 0xEEE420, 0xEF20E4, 0xF0E420, 0xF0E920, 0xF0E9ED, 0xF2EC20, 0xF820E4, 0xF8E9ED, 0xF9EC20, 0xFA20E0, 0xFA20E1, 0xFA20E4, 0xFA20EC, 0xFA20EE, 0xFA20F9,\n        };\n\n        @Override\n        public String getName() {\n            return \"ISO-8859-8-I\";\n        }\n\n        @Override\n        public String getLanguage() {\n            return \"he\";\n        }\n\n        @Override\n        public CharsetMatch match(CharsetDetector det) {\n            String name = det.fC1Bytes ? \"windows-1255\" : \"ISO-8859-8-I\";\n            int confidence = match(det, ngrams, byteMap);\n            return confidence == 0 ? null : new CharsetMatch(det, this, confidence, name, \"he\");\n        }\n    }\n\n    static class CharsetRecog_8859_8_he extends CharsetRecog_8859_8 {\n        private static final int[] ngrams = {\n                0x20E0E5, 0x20E0EC, 0x20E4E9, 0x20E4EC, 0x20E4EE, 0x20E4F0, 0x20E9F0, 0x20ECF2, 0x20ECF9, 0x20EDE5, 0x20EDE9, 0x20EFE5, 0x20EFE9, 0x20F8E5, 0x20F8E9, 0x20FAE0,\n                0x20FAE5, 0x20FAE9, 0xE020E4, 0xE020EC, 0xE020ED, 0xE020FA, 0xE0E420, 0xE0E5E4, 0xE0EC20, 0xE0EE20, 0xE120E4, 0xE120ED, 0xE120FA, 0xE420E4, 0xE420E9, 0xE420EC,\n                0xE420ED, 0xE420EF, 0xE420F8, 0xE420FA, 0xE4EC20, 0xE5E020, 0xE5E420, 0xE7E020, 0xE9E020, 0xE9E120, 0xE9E420, 0xEC20E4, 0xEC20ED, 0xEC20FA, 0xECF220, 0xECF920,\n                0xEDE9E9, 0xEDE9F0, 0xEDE9F8, 0xEE20E4, 0xEE20ED, 0xEE20FA, 0xEEE120, 0xEEE420, 0xF2E420, 0xF920E4, 0xF920ED, 0xF920FA, 0xF9E420, 0xFAE020, 0xFAE420, 0xFAE5E9,\n        };\n\n        @Override\n        public String getLanguage() {\n            return \"he\";\n        }\n\n        @Override\n        public CharsetMatch match(CharsetDetector det) {\n            String name = det.fC1Bytes ? \"windows-1255\" : \"ISO-8859-8\";\n            int confidence = match(det, ngrams, byteMap);\n            return confidence == 0 ? null : new CharsetMatch(det, this, confidence, name, \"he\");\n\n        }\n    }\n\n    abstract static class CharsetRecog_8859_9 extends CharsetRecog_sbcs {\n        protected static byte[] byteMap = {\n                (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,\n                (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,\n                (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,\n                (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,\n                (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x00,\n                (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,\n                (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,\n                (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,\n                (byte) 0x20, (byte) 0x61, (byte) 0x62, (byte) 0x63, (byte) 0x64, (byte) 0x65, (byte) 0x66, (byte) 0x67,\n                (byte) 0x68, (byte) 0x69, (byte) 0x6A, (byte) 0x6B, (byte) 0x6C, (byte) 0x6D, (byte) 0x6E, (byte) 0x6F,\n                (byte) 0x70, (byte) 0x71, (byte) 0x72, (byte) 0x73, (byte) 0x74, (byte) 0x75, (byte) 0x76, (byte) 0x77,\n                (byte) 0x78, (byte) 0x79, (byte) 0x7A, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,\n                (byte) 0x20, (byte) 0x61, (byte) 0x62, (byte) 0x63, (byte) 0x64, (byte) 0x65, (byte) 0x66, (byte) 0x67,\n                (byte) 0x68, (byte) 0x69, (byte) 0x6A, (byte) 0x6B, (byte) 0x6C, (byte) 0x6D, (byte) 0x6E, (byte) 0x6F,\n                (byte) 0x70, (byte) 0x71, (byte) 0x72, (byte) 0x73, (byte) 0x74, (byte) 0x75, (byte) 0x76, (byte) 0x77,\n                (byte) 0x78, (byte) 0x79, (byte) 0x7A, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,\n                (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,\n                (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,\n                (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,\n                (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,\n                (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,\n                (byte) 0x20, (byte) 0x20, (byte) 0xAA, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,\n                (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0xB5, (byte) 0x20, (byte) 0x20,\n                (byte) 0x20, (byte) 0x20, (byte) 0xBA, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,\n                (byte) 0xE0, (byte) 0xE1, (byte) 0xE2, (byte) 0xE3, (byte) 0xE4, (byte) 0xE5, (byte) 0xE6, (byte) 0xE7,\n                (byte) 0xE8, (byte) 0xE9, (byte) 0xEA, (byte) 0xEB, (byte) 0xEC, (byte) 0xED, (byte) 0xEE, (byte) 0xEF,\n                (byte) 0xF0, (byte) 0xF1, (byte) 0xF2, (byte) 0xF3, (byte) 0xF4, (byte) 0xF5, (byte) 0xF6, (byte) 0x20,\n                (byte) 0xF8, (byte) 0xF9, (byte) 0xFA, (byte) 0xFB, (byte) 0xFC, (byte) 0x69, (byte) 0xFE, (byte) 0xDF,\n                (byte) 0xE0, (byte) 0xE1, (byte) 0xE2, (byte) 0xE3, (byte) 0xE4, (byte) 0xE5, (byte) 0xE6, (byte) 0xE7,\n                (byte) 0xE8, (byte) 0xE9, (byte) 0xEA, (byte) 0xEB, (byte) 0xEC, (byte) 0xED, (byte) 0xEE, (byte) 0xEF,\n                (byte) 0xF0, (byte) 0xF1, (byte) 0xF2, (byte) 0xF3, (byte) 0xF4, (byte) 0xF5, (byte) 0xF6, (byte) 0x20,\n                (byte) 0xF8, (byte) 0xF9, (byte) 0xFA, (byte) 0xFB, (byte) 0xFC, (byte) 0xFD, (byte) 0xFE, (byte) 0xFF,\n        };\n\n        @Override\n        public String getName() {\n            return \"ISO-8859-9\";\n        }\n    }\n\n    static class CharsetRecog_8859_9_tr extends CharsetRecog_8859_9 {\n        private static final int[] ngrams = {\n                0x206261, 0x206269, 0x206275, 0x206461, 0x206465, 0x206765, 0x206861, 0x20696C, 0x206B61, 0x206B6F, 0x206D61, 0x206F6C, 0x207361, 0x207461, 0x207665, 0x207961,\n                0x612062, 0x616B20, 0x616C61, 0x616D61, 0x616E20, 0x616EFD, 0x617220, 0x617261, 0x6172FD, 0x6173FD, 0x617961, 0x626972, 0x646120, 0x646520, 0x646920, 0x652062,\n                0x65206B, 0x656469, 0x656E20, 0x657220, 0x657269, 0x657369, 0x696C65, 0x696E20, 0x696E69, 0x697220, 0x6C616E, 0x6C6172, 0x6C6520, 0x6C6572, 0x6E2061, 0x6E2062,\n                0x6E206B, 0x6E6461, 0x6E6465, 0x6E6520, 0x6E6920, 0x6E696E, 0x6EFD20, 0x72696E, 0x72FD6E, 0x766520, 0x796120, 0x796F72, 0xFD6E20, 0xFD6E64, 0xFD6EFD, 0xFDF0FD,\n        };\n\n        @Override\n        public String getLanguage() {\n            return \"tr\";\n        }\n\n        @Override\n        public CharsetMatch match(CharsetDetector det) {\n            String name = det.fC1Bytes ? \"windows-1254\" : \"ISO-8859-9\";\n            int confidence = match(det, ngrams, byteMap);\n            return confidence == 0 ? null : new CharsetMatch(det, this, confidence, name, \"tr\");\n        }\n    }\n\n    static class CharsetRecog_windows_1251 extends CharsetRecog_sbcs {\n        private static final int[] ngrams = {\n                0x20E220, 0x20E2EE, 0x20E4EE, 0x20E7E0, 0x20E820, 0x20EAE0, 0x20EAEE, 0x20EDE0, 0x20EDE5, 0x20EEE1, 0x20EFEE, 0x20EFF0, 0x20F0E0, 0x20F1EE, 0x20F1F2, 0x20F2EE,\n                0x20F7F2, 0x20FDF2, 0xE0EDE8, 0xE0F2FC, 0xE3EE20, 0xE5EBFC, 0xE5EDE8, 0xE5F1F2, 0xE5F220, 0xE820EF, 0xE8E520, 0xE8E820, 0xE8FF20, 0xEBE5ED, 0xEBE820, 0xEBFCED,\n                0xEDE020, 0xEDE520, 0xEDE8E5, 0xEDE8FF, 0xEDEE20, 0xEDEEE2, 0xEE20E2, 0xEE20EF, 0xEE20F1, 0xEEE220, 0xEEE2E0, 0xEEE3EE, 0xEEE920, 0xEEEBFC, 0xEEEC20, 0xEEF1F2,\n                0xEFEEEB, 0xEFF0E5, 0xEFF0E8, 0xEFF0EE, 0xF0E0E2, 0xF0E5E4, 0xF1F2E0, 0xF1F2E2, 0xF1F2E8, 0xF1FF20, 0xF2E5EB, 0xF2EE20, 0xF2EEF0, 0xF2FC20, 0xF7F2EE, 0xFBF520,\n        };\n\n        private static final byte[] byteMap = {\n                (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,\n                (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,\n                (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,\n                (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,\n                (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x00,\n                (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,\n                (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,\n                (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,\n                (byte) 0x20, (byte) 0x61, (byte) 0x62, (byte) 0x63, (byte) 0x64, (byte) 0x65, (byte) 0x66, (byte) 0x67,\n                (byte) 0x68, (byte) 0x69, (byte) 0x6A, (byte) 0x6B, (byte) 0x6C, (byte) 0x6D, (byte) 0x6E, (byte) 0x6F,\n                (byte) 0x70, (byte) 0x71, (byte) 0x72, (byte) 0x73, (byte) 0x74, (byte) 0x75, (byte) 0x76, (byte) 0x77,\n                (byte) 0x78, (byte) 0x79, (byte) 0x7A, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,\n                (byte) 0x20, (byte) 0x61, (byte) 0x62, (byte) 0x63, (byte) 0x64, (byte) 0x65, (byte) 0x66, (byte) 0x67,\n                (byte) 0x68, (byte) 0x69, (byte) 0x6A, (byte) 0x6B, (byte) 0x6C, (byte) 0x6D, (byte) 0x6E, (byte) 0x6F,\n                (byte) 0x70, (byte) 0x71, (byte) 0x72, (byte) 0x73, (byte) 0x74, (byte) 0x75, (byte) 0x76, (byte) 0x77,\n                (byte) 0x78, (byte) 0x79, (byte) 0x7A, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,\n                (byte) 0x90, (byte) 0x83, (byte) 0x20, (byte) 0x83, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,\n                (byte) 0x20, (byte) 0x20, (byte) 0x9A, (byte) 0x20, (byte) 0x9C, (byte) 0x9D, (byte) 0x9E, (byte) 0x9F,\n                (byte) 0x90, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,\n                (byte) 0x20, (byte) 0x20, (byte) 0x9A, (byte) 0x20, (byte) 0x9C, (byte) 0x9D, (byte) 0x9E, (byte) 0x9F,\n                (byte) 0x20, (byte) 0xA2, (byte) 0xA2, (byte) 0xBC, (byte) 0x20, (byte) 0xB4, (byte) 0x20, (byte) 0x20,\n                (byte) 0xB8, (byte) 0x20, (byte) 0xBA, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0xBF,\n                (byte) 0x20, (byte) 0x20, (byte) 0xB3, (byte) 0xB3, (byte) 0xB4, (byte) 0xB5, (byte) 0x20, (byte) 0x20,\n                (byte) 0xB8, (byte) 0x20, (byte) 0xBA, (byte) 0x20, (byte) 0xBC, (byte) 0xBE, (byte) 0xBE, (byte) 0xBF,\n                (byte) 0xE0, (byte) 0xE1, (byte) 0xE2, (byte) 0xE3, (byte) 0xE4, (byte) 0xE5, (byte) 0xE6, (byte) 0xE7,\n                (byte) 0xE8, (byte) 0xE9, (byte) 0xEA, (byte) 0xEB, (byte) 0xEC, (byte) 0xED, (byte) 0xEE, (byte) 0xEF,\n                (byte) 0xF0, (byte) 0xF1, (byte) 0xF2, (byte) 0xF3, (byte) 0xF4, (byte) 0xF5, (byte) 0xF6, (byte) 0xF7,\n                (byte) 0xF8, (byte) 0xF9, (byte) 0xFA, (byte) 0xFB, (byte) 0xFC, (byte) 0xFD, (byte) 0xFE, (byte) 0xFF,\n                (byte) 0xE0, (byte) 0xE1, (byte) 0xE2, (byte) 0xE3, (byte) 0xE4, (byte) 0xE5, (byte) 0xE6, (byte) 0xE7,\n                (byte) 0xE8, (byte) 0xE9, (byte) 0xEA, (byte) 0xEB, (byte) 0xEC, (byte) 0xED, (byte) 0xEE, (byte) 0xEF,\n                (byte) 0xF0, (byte) 0xF1, (byte) 0xF2, (byte) 0xF3, (byte) 0xF4, (byte) 0xF5, (byte) 0xF6, (byte) 0xF7,\n                (byte) 0xF8, (byte) 0xF9, (byte) 0xFA, (byte) 0xFB, (byte) 0xFC, (byte) 0xFD, (byte) 0xFE, (byte) 0xFF,\n        };\n\n        @Override\n        public String getName() {\n            return \"windows-1251\";\n        }\n\n        @Override\n        public String getLanguage() {\n            return \"ru\";\n        }\n\n        @Override\n        public CharsetMatch match(CharsetDetector det) {\n            int confidence = match(det, ngrams, byteMap);\n            return confidence == 0 ? null : new CharsetMatch(det, this, confidence);\n        }\n    }\n\n    static class CharsetRecog_windows_1256 extends CharsetRecog_sbcs {\n        private static final int[] ngrams = {\n                0x20C7E1, 0x20C7E4, 0x20C8C7, 0x20DAE1, 0x20DDED, 0x20E1E1, 0x20E3E4, 0x20E6C7, 0xC720C7, 0xC7C120, 0xC7CA20, 0xC7D120, 0xC7E120, 0xC7E1C3, 0xC7E1C7, 0xC7E1C8,\n                0xC7E1CA, 0xC7E1CC, 0xC7E1CD, 0xC7E1CF, 0xC7E1D3, 0xC7E1DA, 0xC7E1DE, 0xC7E1E3, 0xC7E1E6, 0xC7E1ED, 0xC7E320, 0xC7E420, 0xC7E4CA, 0xC820C7, 0xC920C7, 0xC920DD,\n                0xC920E1, 0xC920E3, 0xC920E6, 0xCA20C7, 0xCF20C7, 0xCFC920, 0xD120C7, 0xD1C920, 0xD320C7, 0xDA20C7, 0xDAE1EC, 0xDDED20, 0xE120C7, 0xE1C920, 0xE1EC20, 0xE1ED20,\n                0xE320C7, 0xE3C720, 0xE3C920, 0xE3E420, 0xE420C7, 0xE520C7, 0xE5C720, 0xE6C7E1, 0xE6E420, 0xEC20C7, 0xED20C7, 0xED20E3, 0xED20E6, 0xEDC920, 0xEDD120, 0xEDE420,\n        };\n\n        private static final byte[] byteMap = {\n                (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,\n                (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,\n                (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,\n                (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,\n                (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x00,\n                (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,\n                (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,\n                (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,\n                (byte) 0x20, (byte) 0x61, (byte) 0x62, (byte) 0x63, (byte) 0x64, (byte) 0x65, (byte) 0x66, (byte) 0x67,\n                (byte) 0x68, (byte) 0x69, (byte) 0x6A, (byte) 0x6B, (byte) 0x6C, (byte) 0x6D, (byte) 0x6E, (byte) 0x6F,\n                (byte) 0x70, (byte) 0x71, (byte) 0x72, (byte) 0x73, (byte) 0x74, (byte) 0x75, (byte) 0x76, (byte) 0x77,\n                (byte) 0x78, (byte) 0x79, (byte) 0x7A, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,\n                (byte) 0x20, (byte) 0x61, (byte) 0x62, (byte) 0x63, (byte) 0x64, (byte) 0x65, (byte) 0x66, (byte) 0x67,\n                (byte) 0x68, (byte) 0x69, (byte) 0x6A, (byte) 0x6B, (byte) 0x6C, (byte) 0x6D, (byte) 0x6E, (byte) 0x6F,\n                (byte) 0x70, (byte) 0x71, (byte) 0x72, (byte) 0x73, (byte) 0x74, (byte) 0x75, (byte) 0x76, (byte) 0x77,\n                (byte) 0x78, (byte) 0x79, (byte) 0x7A, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,\n                (byte) 0x20, (byte) 0x81, (byte) 0x20, (byte) 0x83, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,\n                (byte) 0x88, (byte) 0x20, (byte) 0x8A, (byte) 0x20, (byte) 0x9C, (byte) 0x8D, (byte) 0x8E, (byte) 0x8F,\n                (byte) 0x90, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,\n                (byte) 0x98, (byte) 0x20, (byte) 0x9A, (byte) 0x20, (byte) 0x9C, (byte) 0x20, (byte) 0x20, (byte) 0x9F,\n                (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,\n                (byte) 0x20, (byte) 0x20, (byte) 0xAA, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,\n                (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0xB5, (byte) 0x20, (byte) 0x20,\n                (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,\n                (byte) 0xC0, (byte) 0xC1, (byte) 0xC2, (byte) 0xC3, (byte) 0xC4, (byte) 0xC5, (byte) 0xC6, (byte) 0xC7,\n                (byte) 0xC8, (byte) 0xC9, (byte) 0xCA, (byte) 0xCB, (byte) 0xCC, (byte) 0xCD, (byte) 0xCE, (byte) 0xCF,\n                (byte) 0xD0, (byte) 0xD1, (byte) 0xD2, (byte) 0xD3, (byte) 0xD4, (byte) 0xD5, (byte) 0xD6, (byte) 0x20,\n                (byte) 0xD8, (byte) 0xD9, (byte) 0xDA, (byte) 0xDB, (byte) 0xDC, (byte) 0xDD, (byte) 0xDE, (byte) 0xDF,\n                (byte) 0xE0, (byte) 0xE1, (byte) 0xE2, (byte) 0xE3, (byte) 0xE4, (byte) 0xE5, (byte) 0xE6, (byte) 0xE7,\n                (byte) 0xE8, (byte) 0xE9, (byte) 0xEA, (byte) 0xEB, (byte) 0xEC, (byte) 0xED, (byte) 0xEE, (byte) 0xEF,\n                (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0xF4, (byte) 0x20, (byte) 0x20, (byte) 0x20,\n                (byte) 0x20, (byte) 0xF9, (byte) 0x20, (byte) 0xFB, (byte) 0xFC, (byte) 0x20, (byte) 0x20, (byte) 0xFF,\n        };\n\n        @Override\n        public String getName() {\n            return \"windows-1256\";\n        }\n\n        @Override\n        public String getLanguage() {\n            return \"ar\";\n        }\n\n        @Override\n        public CharsetMatch match(CharsetDetector det) {\n            int confidence = match(det, ngrams, byteMap);\n            return confidence == 0 ? null : new CharsetMatch(det, this, confidence);\n        }\n    }\n\n    static class CharsetRecog_KOI8_R extends CharsetRecog_sbcs {\n        private static final int[] ngrams = {\n                0x20C4CF, 0x20C920, 0x20CBC1, 0x20CBCF, 0x20CEC1, 0x20CEC5, 0x20CFC2, 0x20D0CF, 0x20D0D2, 0x20D2C1, 0x20D3CF, 0x20D3D4, 0x20D4CF, 0x20D720, 0x20D7CF, 0x20DAC1,\n                0x20DCD4, 0x20DED4, 0xC1CEC9, 0xC1D4D8, 0xC5CCD8, 0xC5CEC9, 0xC5D3D4, 0xC5D420, 0xC7CF20, 0xC920D0, 0xC9C520, 0xC9C920, 0xC9D120, 0xCCC5CE, 0xCCC920, 0xCCD8CE,\n                0xCEC120, 0xCEC520, 0xCEC9C5, 0xCEC9D1, 0xCECF20, 0xCECFD7, 0xCF20D0, 0xCF20D3, 0xCF20D7, 0xCFC7CF, 0xCFCA20, 0xCFCCD8, 0xCFCD20, 0xCFD3D4, 0xCFD720, 0xCFD7C1,\n                0xD0CFCC, 0xD0D2C5, 0xD0D2C9, 0xD0D2CF, 0xD2C1D7, 0xD2C5C4, 0xD3D120, 0xD3D4C1, 0xD3D4C9, 0xD3D4D7, 0xD4C5CC, 0xD4CF20, 0xD4CFD2, 0xD4D820, 0xD9C820, 0xDED4CF,\n        };\n\n        private static final byte[] byteMap = {\n                (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,\n                (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,\n                (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,\n                (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,\n                (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x00,\n                (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,\n                (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,\n                (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,\n                (byte) 0x20, (byte) 0x61, (byte) 0x62, (byte) 0x63, (byte) 0x64, (byte) 0x65, (byte) 0x66, (byte) 0x67,\n                (byte) 0x68, (byte) 0x69, (byte) 0x6A, (byte) 0x6B, (byte) 0x6C, (byte) 0x6D, (byte) 0x6E, (byte) 0x6F,\n                (byte) 0x70, (byte) 0x71, (byte) 0x72, (byte) 0x73, (byte) 0x74, (byte) 0x75, (byte) 0x76, (byte) 0x77,\n                (byte) 0x78, (byte) 0x79, (byte) 0x7A, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,\n                (byte) 0x20, (byte) 0x61, (byte) 0x62, (byte) 0x63, (byte) 0x64, (byte) 0x65, (byte) 0x66, (byte) 0x67,\n                (byte) 0x68, (byte) 0x69, (byte) 0x6A, (byte) 0x6B, (byte) 0x6C, (byte) 0x6D, (byte) 0x6E, (byte) 0x6F,\n                (byte) 0x70, (byte) 0x71, (byte) 0x72, (byte) 0x73, (byte) 0x74, (byte) 0x75, (byte) 0x76, (byte) 0x77,\n                (byte) 0x78, (byte) 0x79, (byte) 0x7A, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,\n                (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,\n                (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,\n                (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,\n                (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,\n                (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0xA3, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,\n                (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,\n                (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0xA3, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,\n                (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20, (byte) 0x20,\n                (byte) 0xC0, (byte) 0xC1, (byte) 0xC2, (byte) 0xC3, (byte) 0xC4, (byte) 0xC5, (byte) 0xC6, (byte) 0xC7,\n                (byte) 0xC8, (byte) 0xC9, (byte) 0xCA, (byte) 0xCB, (byte) 0xCC, (byte) 0xCD, (byte) 0xCE, (byte) 0xCF,\n                (byte) 0xD0, (byte) 0xD1, (byte) 0xD2, (byte) 0xD3, (byte) 0xD4, (byte) 0xD5, (byte) 0xD6, (byte) 0xD7,\n                (byte) 0xD8, (byte) 0xD9, (byte) 0xDA, (byte) 0xDB, (byte) 0xDC, (byte) 0xDD, (byte) 0xDE, (byte) 0xDF,\n                (byte) 0xC0, (byte) 0xC1, (byte) 0xC2, (byte) 0xC3, (byte) 0xC4, (byte) 0xC5, (byte) 0xC6, (byte) 0xC7,\n                (byte) 0xC8, (byte) 0xC9, (byte) 0xCA, (byte) 0xCB, (byte) 0xCC, (byte) 0xCD, (byte) 0xCE, (byte) 0xCF,\n                (byte) 0xD0, (byte) 0xD1, (byte) 0xD2, (byte) 0xD3, (byte) 0xD4, (byte) 0xD5, (byte) 0xD6, (byte) 0xD7,\n                (byte) 0xD8, (byte) 0xD9, (byte) 0xDA, (byte) 0xDB, (byte) 0xDC, (byte) 0xDD, (byte) 0xDE, (byte) 0xDF,\n        };\n\n        @Override\n        public String getName() {\n            return \"KOI8-R\";\n        }\n\n        @Override\n        public String getLanguage() {\n            return \"ru\";\n        }\n\n        @Override\n        public CharsetMatch match(CharsetDetector det) {\n            int confidence = match(det, ngrams, byteMap);\n            return confidence == 0 ? null : new CharsetMatch(det, this, confidence);\n        }\n    }\n\n    abstract static class CharsetRecog_IBM424_he extends CharsetRecog_sbcs {\n        protected static byte[] byteMap = {\n/*                 -0           -1           -2           -3           -4           -5           -6           -7           -8           -9           -A           -B           -C           -D           -E           -F   */\n/* 0- */    (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40,\n/* 1- */    (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40,\n/* 2- */    (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40,\n/* 3- */    (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40,\n/* 4- */    (byte) 0x40, (byte) 0x41, (byte) 0x42, (byte) 0x43, (byte) 0x44, (byte) 0x45, (byte) 0x46, (byte) 0x47, (byte) 0x48, (byte) 0x49, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40,\n/* 5- */    (byte) 0x40, (byte) 0x51, (byte) 0x52, (byte) 0x53, (byte) 0x54, (byte) 0x55, (byte) 0x56, (byte) 0x57, (byte) 0x58, (byte) 0x59, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40,\n/* 6- */    (byte) 0x40, (byte) 0x40, (byte) 0x62, (byte) 0x63, (byte) 0x64, (byte) 0x65, (byte) 0x66, (byte) 0x67, (byte) 0x68, (byte) 0x69, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40,\n/* 7- */    (byte) 0x40, (byte) 0x71, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x00, (byte) 0x40, (byte) 0x40,\n/* 8- */    (byte) 0x40, (byte) 0x81, (byte) 0x82, (byte) 0x83, (byte) 0x84, (byte) 0x85, (byte) 0x86, (byte) 0x87, (byte) 0x88, (byte) 0x89, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40,\n/* 9- */    (byte) 0x40, (byte) 0x91, (byte) 0x92, (byte) 0x93, (byte) 0x94, (byte) 0x95, (byte) 0x96, (byte) 0x97, (byte) 0x98, (byte) 0x99, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40,\n/* A- */    (byte) 0xA0, (byte) 0x40, (byte) 0xA2, (byte) 0xA3, (byte) 0xA4, (byte) 0xA5, (byte) 0xA6, (byte) 0xA7, (byte) 0xA8, (byte) 0xA9, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40,\n/* B- */    (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40,\n/* C- */    (byte) 0x40, (byte) 0x81, (byte) 0x82, (byte) 0x83, (byte) 0x84, (byte) 0x85, (byte) 0x86, (byte) 0x87, (byte) 0x88, (byte) 0x89, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40,\n/* D- */    (byte) 0x40, (byte) 0x91, (byte) 0x92, (byte) 0x93, (byte) 0x94, (byte) 0x95, (byte) 0x96, (byte) 0x97, (byte) 0x98, (byte) 0x99, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40,\n/* E- */    (byte) 0x40, (byte) 0x40, (byte) 0xA2, (byte) 0xA3, (byte) 0xA4, (byte) 0xA5, (byte) 0xA6, (byte) 0xA7, (byte) 0xA8, (byte) 0xA9, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40,\n/* F- */    (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40,\n        };\n\n        @Override\n        public String getLanguage() {\n            return \"he\";\n        }\n    }\n\n    static class CharsetRecog_IBM424_he_rtl extends CharsetRecog_IBM424_he {\n        @Override\n        public String getName() {\n            return \"IBM424_rtl\";\n        }\n\n        private static final int[] ngrams = {\n                0x404146, 0x404148, 0x404151, 0x404171, 0x404251, 0x404256, 0x404541, 0x404546, 0x404551, 0x404556, 0x404562, 0x404569, 0x404571, 0x405441, 0x405445, 0x405641,\n                0x406254, 0x406954, 0x417140, 0x454041, 0x454042, 0x454045, 0x454054, 0x454056, 0x454069, 0x454641, 0x464140, 0x465540, 0x465740, 0x466840, 0x467140, 0x514045,\n                0x514540, 0x514671, 0x515155, 0x515540, 0x515740, 0x516840, 0x517140, 0x544041, 0x544045, 0x544140, 0x544540, 0x554041, 0x554042, 0x554045, 0x554054, 0x554056,\n                0x554069, 0x564540, 0x574045, 0x584540, 0x585140, 0x585155, 0x625440, 0x684045, 0x685155, 0x695440, 0x714041, 0x714042, 0x714045, 0x714054, 0x714056, 0x714069,\n        };\n\n        @Override\n        public CharsetMatch match(CharsetDetector det) {\n            int confidence = match(det, ngrams, byteMap, (byte) 0x40);\n            return confidence == 0 ? null : new CharsetMatch(det, this, confidence);\n        }\n    }\n\n    static class CharsetRecog_IBM424_he_ltr extends CharsetRecog_IBM424_he {\n        @Override\n        public String getName() {\n            return \"IBM424_ltr\";\n        }\n\n        private static final int[] ngrams = {\n                0x404146, 0x404154, 0x404551, 0x404554, 0x404556, 0x404558, 0x405158, 0x405462, 0x405469, 0x405546, 0x405551, 0x405746, 0x405751, 0x406846, 0x406851, 0x407141,\n                0x407146, 0x407151, 0x414045, 0x414054, 0x414055, 0x414071, 0x414540, 0x414645, 0x415440, 0x415640, 0x424045, 0x424055, 0x424071, 0x454045, 0x454051, 0x454054,\n                0x454055, 0x454057, 0x454068, 0x454071, 0x455440, 0x464140, 0x464540, 0x484140, 0x514140, 0x514240, 0x514540, 0x544045, 0x544055, 0x544071, 0x546240, 0x546940,\n                0x555151, 0x555158, 0x555168, 0x564045, 0x564055, 0x564071, 0x564240, 0x564540, 0x624540, 0x694045, 0x694055, 0x694071, 0x694540, 0x714140, 0x714540, 0x714651\n\n        };\n\n        @Override\n        public CharsetMatch match(CharsetDetector det) {\n            int confidence = match(det, ngrams, byteMap, (byte) 0x40);\n            return confidence == 0 ? null : new CharsetMatch(det, this, confidence);\n        }\n    }\n\n    abstract static class CharsetRecog_IBM420_ar extends CharsetRecog_sbcs {\n\n        protected static byte[] byteMap = {\n/*                 -0           -1           -2           -3           -4           -5           -6           -7           -8           -9           -A           -B           -C           -D           -E           -F   */\n/* 0- */    (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40,\n/* 1- */    (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40,\n/* 2- */    (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40,\n/* 3- */    (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40,\n/* 4- */    (byte) 0x40, (byte) 0x40, (byte) 0x42, (byte) 0x43, (byte) 0x44, (byte) 0x45, (byte) 0x46, (byte) 0x47, (byte) 0x48, (byte) 0x49, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40,\n/* 5- */    (byte) 0x40, (byte) 0x51, (byte) 0x52, (byte) 0x40, (byte) 0x40, (byte) 0x55, (byte) 0x56, (byte) 0x57, (byte) 0x58, (byte) 0x59, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40,\n/* 6- */    (byte) 0x40, (byte) 0x40, (byte) 0x62, (byte) 0x63, (byte) 0x64, (byte) 0x65, (byte) 0x66, (byte) 0x67, (byte) 0x68, (byte) 0x69, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40,\n/* 7- */    (byte) 0x70, (byte) 0x71, (byte) 0x72, (byte) 0x73, (byte) 0x74, (byte) 0x75, (byte) 0x76, (byte) 0x77, (byte) 0x78, (byte) 0x79, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40,\n/* 8- */    (byte) 0x80, (byte) 0x81, (byte) 0x82, (byte) 0x83, (byte) 0x84, (byte) 0x85, (byte) 0x86, (byte) 0x87, (byte) 0x88, (byte) 0x89, (byte) 0x8A, (byte) 0x8B, (byte) 0x8C, (byte) 0x8D, (byte) 0x8E, (byte) 0x8F,\n/* 9- */    (byte) 0x90, (byte) 0x91, (byte) 0x92, (byte) 0x93, (byte) 0x94, (byte) 0x95, (byte) 0x96, (byte) 0x97, (byte) 0x98, (byte) 0x99, (byte) 0x9A, (byte) 0x9B, (byte) 0x9C, (byte) 0x9D, (byte) 0x9E, (byte) 0x9F,\n/* A- */    (byte) 0xA0, (byte) 0x40, (byte) 0xA2, (byte) 0xA3, (byte) 0xA4, (byte) 0xA5, (byte) 0xA6, (byte) 0xA7, (byte) 0xA8, (byte) 0xA9, (byte) 0xAA, (byte) 0xAB, (byte) 0xAC, (byte) 0xAD, (byte) 0xAE, (byte) 0xAF,\n/* B- */    (byte) 0xB0, (byte) 0xB1, (byte) 0xB2, (byte) 0xB3, (byte) 0xB4, (byte) 0xB5, (byte) 0x40, (byte) 0x40, (byte) 0xB8, (byte) 0xB9, (byte) 0xBA, (byte) 0xBB, (byte) 0xBC, (byte) 0xBD, (byte) 0xBE, (byte) 0xBF,\n/* C- */    (byte) 0x40, (byte) 0x81, (byte) 0x82, (byte) 0x83, (byte) 0x84, (byte) 0x85, (byte) 0x86, (byte) 0x87, (byte) 0x88, (byte) 0x89, (byte) 0x40, (byte) 0xCB, (byte) 0x40, (byte) 0xCD, (byte) 0x40, (byte) 0xCF,\n/* D- */    (byte) 0x40, (byte) 0x91, (byte) 0x92, (byte) 0x93, (byte) 0x94, (byte) 0x95, (byte) 0x96, (byte) 0x97, (byte) 0x98, (byte) 0x99, (byte) 0xDA, (byte) 0xDB, (byte) 0xDC, (byte) 0xDD, (byte) 0xDE, (byte) 0xDF,\n/* E- */    (byte) 0x40, (byte) 0x40, (byte) 0xA2, (byte) 0xA3, (byte) 0xA4, (byte) 0xA5, (byte) 0xA6, (byte) 0xA7, (byte) 0xA8, (byte) 0xA9, (byte) 0xEA, (byte) 0xEB, (byte) 0x40, (byte) 0xED, (byte) 0xEE, (byte) 0xEF,\n/* F- */    (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0x40, (byte) 0xFB, (byte) 0xFC, (byte) 0xFD, (byte) 0xFE, (byte) 0x40,\n        };\n\n\n        @Override\n        public String getLanguage() {\n            return \"ar\";\n        }\n\n    }\n\n    static class CharsetRecog_IBM420_ar_rtl extends CharsetRecog_IBM420_ar {\n        private static final int[] ngrams = {\n                0x4056B1, 0x4056BD, 0x405856, 0x409AB1, 0x40ABDC, 0x40B1B1, 0x40BBBD, 0x40CF56, 0x564056, 0x564640, 0x566340, 0x567540, 0x56B140, 0x56B149, 0x56B156, 0x56B158,\n                0x56B163, 0x56B167, 0x56B169, 0x56B173, 0x56B178, 0x56B19A, 0x56B1AD, 0x56B1BB, 0x56B1CF, 0x56B1DC, 0x56BB40, 0x56BD40, 0x56BD63, 0x584056, 0x624056, 0x6240AB,\n                0x6240B1, 0x6240BB, 0x6240CF, 0x634056, 0x734056, 0x736240, 0x754056, 0x756240, 0x784056, 0x9A4056, 0x9AB1DA, 0xABDC40, 0xB14056, 0xB16240, 0xB1DA40, 0xB1DC40,\n                0xBB4056, 0xBB5640, 0xBB6240, 0xBBBD40, 0xBD4056, 0xBF4056, 0xBF5640, 0xCF56B1, 0xCFBD40, 0xDA4056, 0xDC4056, 0xDC40BB, 0xDC40CF, 0xDC6240, 0xDC7540, 0xDCBD40,\n        };\n\n        @Override\n        public String getName() {\n            return \"IBM420_rtl\";\n        }\n\n        @Override\n        public CharsetMatch match(CharsetDetector det) {\n            int confidence = matchIBM420(det, ngrams, byteMap, (byte) 0x40);\n            return confidence == 0 ? null : new CharsetMatch(det, this, confidence);\n        }\n\n    }\n\n    static class CharsetRecog_IBM420_ar_ltr extends CharsetRecog_IBM420_ar {\n        private static final int[] ngrams = {\n                0x404656, 0x4056BB, 0x4056BF, 0x406273, 0x406275, 0x4062B1, 0x4062BB, 0x4062DC, 0x406356, 0x407556, 0x4075DC, 0x40B156, 0x40BB56, 0x40BD56, 0x40BDBB, 0x40BDCF,\n                0x40BDDC, 0x40DAB1, 0x40DCAB, 0x40DCB1, 0x49B156, 0x564056, 0x564058, 0x564062, 0x564063, 0x564073, 0x564075, 0x564078, 0x56409A, 0x5640B1, 0x5640BB, 0x5640BD,\n                0x5640BF, 0x5640DA, 0x5640DC, 0x565840, 0x56B156, 0x56CF40, 0x58B156, 0x63B156, 0x63BD56, 0x67B156, 0x69B156, 0x73B156, 0x78B156, 0x9AB156, 0xAB4062, 0xADB156,\n                0xB14062, 0xB15640, 0xB156CF, 0xB19A40, 0xB1B140, 0xBB4062, 0xBB40DC, 0xBBB156, 0xBD5640, 0xBDBB40, 0xCF4062, 0xCF40DC, 0xCFB156, 0xDAB19A, 0xDCAB40, 0xDCB156\n        };\n\n        @Override\n        public String getName() {\n            return \"IBM420_ltr\";\n        }\n\n        @Override\n        public CharsetMatch match(CharsetDetector det) {\n            int confidence = matchIBM420(det, ngrams, byteMap, (byte) 0x40);\n            return confidence == 0 ? null : new CharsetMatch(det, this, confidence);\n        }\n\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/lib/icu4j/CharsetRecognizer.java",
    "content": "// © 2016 and later: Unicode, Inc. and others.\n// License & terms of use: http://www.unicode.org/copyright.html\n/**\n * ******************************************************************************\n * Copyright (C) 2005-2012, International Business Machines Corporation and    *\n * others. All Rights Reserved.                                                *\n * ******************************************************************************\n */\npackage io.legado.app.lib.icu4j;\n\n/**\n * Abstract class for recognizing a single charset.\n * Part of the implementation of ICU's CharsetDetector.\n * <p>\n * Each specific charset that can be recognized will have an instance\n * of some subclass of this class.  All interaction between the overall\n * CharsetDetector and the stuff specific to an individual charset happens\n * via the interface provided here.\n * <p>\n * Instances of CharsetDetector DO NOT have or maintain\n * state pertaining to a specific match or detect operation.\n * The WILL be shared by multiple instances of CharsetDetector.\n * They encapsulate const charset-specific information.\n */\nabstract class CharsetRecognizer {\n    /**\n     * Get the IANA name of this charset.\n     *\n     * @return the charset name.\n     */\n    abstract String getName();\n\n    /**\n     * Get the ISO language code for this charset.\n     *\n     * @return the language code, or <code>null</code> if the language cannot be determined.\n     */\n    public String getLanguage() {\n        return null;\n    }\n\n    /**\n     * Test the match of this charset with the input text data\n     * which is obtained via the CharsetDetector object.\n     *\n     * @param det The CharsetDetector, which contains the input text\n     *            to be checked for being in this charset.\n     * @return A CharsetMatch object containing details of match\n     * with this charset, or null if there was no match.\n     */\n    abstract CharsetMatch match(CharsetDetector det);\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/lib/mobi/KF6Book.kt",
    "content": "package io.legado.app.lib.mobi\n\nimport io.legado.app.lib.mobi.entities.KF6Section\nimport io.legado.app.lib.mobi.entities.MobiEntryHeaders\nimport io.legado.app.lib.mobi.entities.NCX\nimport io.legado.app.lib.mobi.entities.TOC\nimport java.nio.CharBuffer\n\n\n/**\n * Kindle Format 6 Book\n */\nclass KF6Book(\n    pdbFile: PDBFile,\n    headers: MobiEntryHeaders,\n    kf8BoundaryOffset: Int,\n    resourceStart: Int\n) : MobiBook(pdbFile, headers, kf8BoundaryOffset, resourceStart) {\n\n    lateinit var sections: List<KF6Section>\n    lateinit var sectionIdMap: LinkedHashMap<Int, ArrayList<TOC>>\n\n    init {\n        processSections()\n        processNCX()\n        processSectionsMap()\n    }\n\n    fun getResourceByHref(href: String): ByteArray? {\n        val recindex = href.substringAfter(\"recindex:\").toIntOrNull() ?: return null\n        return getResource(recindex - 1).array()\n    }\n\n    fun getSectionText(section: KF6Section): String {\n        val inputStream = getTextRecordInputStream()\n        val byteArray = ByteArray(section.length)\n        inputStream.skip(section.start.toLong())\n        inputStream.read(byteArray)\n        return String(byteArray, charset)\n    }\n\n    fun getSectionByHref(href: String): KF6Section? {\n        val index = getIndexByHref(href)\n        return sections.getOrNull(index)\n    }\n\n    private fun processSectionsMap() {\n        sectionIdMap = linkedMapOf()\n        if (toc == null) {\n            return\n        }\n        fun fmap(item: TOC) {\n            val index = getIndexByHref(item.href)\n            if (index == -1) return\n            val array = sectionIdMap.getOrPut(index) { arrayListOf() }\n            array.add(item)\n            item.subitems?.forEach(::fmap)\n        }\n        toc!!.forEach(::fmap)\n    }\n\n    private fun getIndexByHref(href: String): Int {\n        val filepos = href.substringAfter(\"filepos:\").toIntOrNull() ?: return -1\n        return sections.indexOfFirst { it.end > filepos }\n    }\n\n    private fun processNCX() {\n        val ncx = getNCX() ?: return\n        fun fmap(item: NCX): TOC {\n            val filepos = item.offset!!\n            val href = \"filepos:${filepos.toString().padStart(10, '0')}\"\n            return TOC(item.label, href, item.children?.map(::fmap))\n        }\n        toc = ncx.map(::fmap)\n    }\n\n    private fun processSections() {\n        val sections = arrayListOf<KF6Section>()\n        val pattern = mbpPagebreakRegex.toPattern()\n        val inputStream = getTextRecordInputStream()\n        val available = inputStream.available()\n        val reader = inputStream.reader(Charsets.ISO_8859_1)\n        var buffer = CharBuffer.allocate(4096)\n        reader.read(buffer)\n        buffer.flip()\n        val matcher = pattern.matcher(buffer)\n        var droppedOffset = 0\n        var nextStart = 0\n        var position = 0\n        while (true) {\n            if (!matcher.find()) {\n                buffer.position(position)\n                if (buffer.limit() == buffer.capacity()) {\n                    if (position > 0) {\n                        buffer.compact()\n                    } else {\n                        val newBuf = CharBuffer.allocate(buffer.capacity() * 2)\n                        newBuf.put(buffer)\n                        buffer = newBuf\n                    }\n                    droppedOffset += position\n                    position = 0\n                }\n                if (reader.read(buffer) == -1) {\n                    break\n                }\n                buffer.flip()\n                matcher.reset(buffer)\n            } else {\n                val last = sections.lastOrNull()\n                val index = sections.size\n                val start = nextStart\n                val end = matcher.start() + droppedOffset\n                nextStart = matcher.end() + droppedOffset\n                position = matcher.end()\n                val length = end - start\n                val href = \"filepos:${start.toString().padStart(10, '0')}\"\n                val section = KF6Section(index, start, end, length, href)\n                last?.next = section\n                sections.add(section)\n            }\n        }\n        if (nextStart > 0) {\n            val last = sections.lastOrNull()\n            val index = sections.size\n            val start = nextStart\n            val length = available - start\n            val href = \"filepos:${start.toString().padStart(10, '0')}\"\n            val section = KF6Section(index, start, available, length, href)\n            last?.next = section\n            sections.add(section)\n        }\n        this.sections = sections\n    }\n\n    companion object {\n        val mbpPagebreakRegex = \"(?i)<\\\\s*(?:mbp:)?pagebreak[^>]*>\".toRegex()\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/lib/mobi/KF8Book.kt",
    "content": "package io.legado.app.lib.mobi\n\nimport io.legado.app.lib.mobi.entities.FdstHeader\nimport io.legado.app.lib.mobi.entities.Fragment\nimport io.legado.app.lib.mobi.entities.KF8Pos\nimport io.legado.app.lib.mobi.entities.KF8Resource\nimport io.legado.app.lib.mobi.entities.KF8Section\nimport io.legado.app.lib.mobi.entities.MobiEntryHeaders\nimport io.legado.app.lib.mobi.entities.NCX\nimport io.legado.app.lib.mobi.entities.Skeleton\nimport io.legado.app.lib.mobi.entities.TOC\nimport io.legado.app.lib.mobi.utils.readString\nimport io.legado.app.lib.mobi.utils.readUInt32\nimport java.nio.ByteBuffer\nimport java.util.Locale\n\n/**\n * Kindle Format 8 Book\n */\n@Suppress(\"SpellCheckingInspection\")\nclass KF8Book(\n    pdbFile: PDBFile,\n    headers: MobiEntryHeaders,\n    kf8BoundaryOffset: Int,\n    resourceStart: Int\n) : MobiBook(pdbFile, headers, kf8BoundaryOffset, resourceStart) {\n\n    private var fdstTableStarts: IntArray? = null\n    private var fdstTableEnds: IntArray? = null\n    private lateinit var skelTable: List<Skeleton>\n    private lateinit var fragTable: List<Fragment>\n    private var kf8 = headers.kf8!!\n    lateinit var sections: List<KF8Section>\n    lateinit var sectionIdMap: LinkedHashMap<Int, ArrayList<TOC>>\n\n    init {\n        readFdstTable()\n        readSkelTable()\n        readFragTable()\n        processSections()\n        processNCX()\n        processSectionsMap()\n    }\n\n    /**\n     * 建立 section id -> toc 的 map\n     */\n    private fun processSectionsMap() {\n        sectionIdMap = linkedMapOf()\n        if (toc == null) {\n            return\n        }\n        fun fmap(item: TOC) {\n            val index = getIndexByHref(item.href)\n            if (index == -1) return\n            val array = sectionIdMap.getOrPut(index) { arrayListOf() }\n            array.add(item)\n            item.subitems?.forEach(::fmap)\n        }\n        toc!!.forEach(::fmap)\n    }\n\n    fun parsePosURI(href: String): KF8Pos? {\n        val match = kindlePosRegex.find(href) ?: return null\n        val fid = match.groupValues[1].toInt(32)\n        val off = match.groupValues[2].toInt(32)\n        return KF8Pos(fid, off)\n    }\n\n    private fun parseResourceURI(href: String): KF8Resource? {\n        val match = kindleResourceRegex.find(href) ?: return null\n        val resourceType = match.groupValues[1]\n        val id = match.groupValues[2].toInt(32)\n        val type = match.groupValues[3]\n        return KF8Resource(resourceType, id, type)\n    }\n\n    fun getResourceByHref(href: String): ByteArray? {\n        val resource = parseResourceURI(href) ?: return null\n        if (resource.resourceType == \"flow\") return null\n        return getResource(resource.id - 1).array()\n    }\n\n    fun getSectionByHref(href: String): KF8Section? {\n        val index = getIndexByHref(href)\n        return sections.getOrNull(index)\n    }\n\n    private fun getIndexByHref(href: String): Int {\n        val pos = parsePosURI(href) ?: return -1\n        return getIndexByFID(pos.fid)\n    }\n\n    private fun getIndexByFID(fid: Int): Int {\n        return sections.indexOfFirst {\n            it.frags.any { frag ->\n                frag.index == fid\n            }\n        }\n    }\n\n    fun getTextByHref(href: String, nextHref: String): String {\n        val pos = parsePosURI(href) ?: return \"\"\n        val nextPos = parsePosURI(nextHref) ?: return \"\"\n        val index = getIndexByFID(pos.fid)\n        val nextIndex = getIndexByFID(nextPos.fid)\n        val startFid = pos.fid\n        val endFid = if (index == nextIndex) nextPos.fid else Int.MAX_VALUE\n        val section = sections[index]\n        val skel = section.skeleton\n        val droppedFrags = section.frags.filter { it.index < startFid }\n        var droppedFragsLength = droppedFrags.sumOf { it.length }\n        val frags = section.frags.filter { it.index in startFid..endFid }\n        val length = skel.length + frags.sumOf { it.length }\n        val raw = getRaw(skel.offset, section.length)\n        val lastFragDroppedLength =\n            if (index == nextIndex) frags.last().length - nextPos.offset else 0\n        val skeleton = ByteArray(length - pos.offset - lastFragDroppedLength)\n        var leftBytes = skeleton.size\n        raw.copyInto(skeleton, 0, 0, skel.length)\n        leftBytes -= skel.length\n        for ((i, frag) in frags.withIndex()) {\n            val isFirstFrag = i == 0\n            val isLastFrag = i == frags.lastIndex\n            val insertOffset = frag.insertOffset - skel.offset - droppedFragsLength\n            val offset = skel.length + frag.offset\n            skeleton.copyInto(\n                skeleton,\n                insertOffset + frag.length -\n                        (if (isFirstFrag) pos.offset else 0) -\n                        (if (isLastFrag) lastFragDroppedLength else 0),\n                insertOffset,\n                skeleton.size - leftBytes\n            )\n            raw.copyInto(\n                skeleton,\n                insertOffset,\n                offset + if (isFirstFrag) pos.offset else 0,\n                offset + frag.length - if (isLastFrag) lastFragDroppedLength else 0\n            )\n            leftBytes -= frag.length -\n                    (if (isFirstFrag) pos.offset else 0) -\n                    (if (isLastFrag && index == nextIndex) nextPos.offset else 0)\n            if (isFirstFrag) {\n                droppedFragsLength += pos.offset\n            }\n        }\n        return String(skeleton, charset)\n    }\n\n    fun getSectionText(section: KF8Section): String {\n        val skel = section.skeleton\n        val frags = section.frags\n        val length = section.length\n        val raw = getRaw(skel.offset, length)\n        val skeleton = ByteArray(raw.size)\n        var leftBytes = raw.size\n        raw.copyInto(skeleton, 0, 0, skel.length)\n        leftBytes -= skel.length\n        for (frag in frags) {\n            val insertOffset = frag.insertOffset - skel.offset\n            val offset = skel.length + frag.offset\n            skeleton.copyInto(\n                skeleton,\n                insertOffset + frag.length,\n                insertOffset,\n                skeleton.size - leftBytes\n            )\n            raw.copyInto(skeleton, insertOffset, offset, offset + frag.length)\n            leftBytes -= frag.length\n        }\n        return String(skeleton, charset)\n    }\n\n    private fun getRaw(offset: Int, len: Int): ByteArray {\n        val inputStream = getTextRecordInputStream()\n        val byteArray = ByteArray(len)\n        inputStream.skip(offset.toLong())\n        inputStream.read(byteArray)\n        return byteArray\n    }\n\n    private fun processNCX() {\n        val ncx = getNCX() ?: return\n        fun fmap(item: NCX): TOC {\n            val (fid, off) = item.pos!!\n            val href = makePosURI(fid, off)\n            return TOC(item.label, href, item.children?.map(::fmap))\n        }\n        toc = ncx.map(::fmap)\n    }\n\n    private fun makePosURI(fid: Int, off: Int): String {\n        val encodedFid = fid.toString(32).uppercase(Locale.ROOT).padStart(4, '0')\n        val encodedOff = off.toString(32).uppercase(Locale.ROOT).padStart(10, '0')\n        return \"kindle:pos:fid:$encodedFid:off:$encodedOff\"\n    }\n\n    private fun processSections() {\n        sections = skelTable.fold(arrayListOf()) { arr, skel ->\n            val last = arr.lastOrNull()\n            val index = arr.size\n            val fragStart = last?.fragEnd ?: 0\n            val fragEnd = fragStart + skel.numFrag\n            val frags = fragTable.slice(fragStart..<fragEnd)\n            val length = skel.length + frags.sumOf { it.length }\n            val totalLength = (last?.totalLength ?: 0) + length\n            val href = frags.firstOrNull()?.let { makePosURI(it.index, 0) } ?: \"\"\n            val section = KF8Section(index, skel, frags, fragEnd, length, totalLength, href)\n            last?.next = section\n            arr.add(section)\n            arr\n        }\n    }\n\n    private fun readFragTable() {\n        val fragData = getIndexData(kf8.frag)\n        fragTable = fragData.table.map { indexEntry ->\n            val tagMap = indexEntry.tagMap\n            Fragment(\n                indexEntry.label.toInt(),\n                fragData.cncx[tagMap[2].tagValues[0]],\n                tagMap[4].tagValues[0],\n                tagMap[6].tagValues[0],\n                tagMap[6].tagValues[1]\n            )\n        }\n    }\n\n    private fun readSkelTable() {\n        skelTable = getIndexData(kf8.skel).table.mapIndexed { index, indexEntry ->\n            val tagMap = indexEntry.tagMap\n            Skeleton(\n                index,\n                indexEntry.label,\n                tagMap[1].tagValues[0],\n                tagMap[6].tagValues[0],\n                tagMap[6].tagValues[1],\n            )\n        }\n    }\n\n    private fun readFdstTable() {\n        try {\n            val fdstBuffer = getRecord(kf8.fdst)\n            val fdstHeader = readFdstHeader(fdstBuffer)\n            fdstTableStarts = IntArray(fdstHeader.numEntries)\n            fdstTableEnds = IntArray(fdstHeader.numEntries)\n            fdstBuffer.position(12)\n            for (i in 0..<fdstHeader.numEntries) {\n                fdstTableStarts!![i] = fdstBuffer.readUInt32()\n                fdstTableEnds!![i] = fdstBuffer.readUInt32()\n            }\n        } catch (e: Exception) {\n            e.printStackTrace()\n        }\n    }\n\n    private fun readFdstHeader(buffer: ByteBuffer): FdstHeader {\n        val magic = buffer.readString(0, 4)\n        if (magic != \"FDST\") error(\"Missing FDST record\")\n        val numEntries = buffer.readUInt32(8)\n        return FdstHeader(magic, numEntries)\n    }\n\n    companion object {\n        val kindlePosRegex = \"kindle:pos:fid:(\\\\w+):off:(\\\\w+)\".toRegex()\n        val kindleResourceRegex =\n            \"kindle:(flow|embed):(\\\\w+)(?:\\\\?mime=(\\\\w+/[-+.\\\\w]+))?\".toRegex()\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/lib/mobi/MobiBook.kt",
    "content": "package io.legado.app.lib.mobi\n\nimport android.util.SparseArray\nimport io.legado.app.lib.mobi.decompress.Decompressor\nimport io.legado.app.lib.mobi.decompress.HuffcdicDecompressor\nimport io.legado.app.lib.mobi.decompress.Lz77Decompressor\nimport io.legado.app.lib.mobi.decompress.PlainDecompressor\nimport io.legado.app.lib.mobi.entities.IndexData\nimport io.legado.app.lib.mobi.entities.IndexEntry\nimport io.legado.app.lib.mobi.entities.IndexTag\nimport io.legado.app.lib.mobi.entities.IndxHeader\nimport io.legado.app.lib.mobi.entities.MobiEntryHeaders\nimport io.legado.app.lib.mobi.entities.MobiMetadata\nimport io.legado.app.lib.mobi.entities.NCX\nimport io.legado.app.lib.mobi.entities.Ptagx\nimport io.legado.app.lib.mobi.entities.TOC\nimport io.legado.app.lib.mobi.entities.TagxHeader\nimport io.legado.app.lib.mobi.entities.TagxTag\nimport io.legado.app.lib.mobi.utils.and\nimport io.legado.app.lib.mobi.utils.readString\nimport io.legado.app.lib.mobi.utils.readUInt16Array\nimport io.legado.app.lib.mobi.utils.readUInt32\nimport io.legado.app.lib.mobi.utils.readUInt8\nimport java.io.ByteArrayInputStream\nimport java.io.InputStream\nimport java.nio.ByteBuffer\nimport java.nio.charset.Charset\nimport kotlin.math.abs\nimport kotlin.math.max\nimport kotlin.math.min\n\n@Suppress(\"SpellCheckingInspection\")\nabstract class MobiBook(\n    private val pdbFile: PDBFile,\n    val headers: MobiEntryHeaders,\n    private val kf8BoundaryOffset: Int,\n    private val resourceStart: Int,\n) {\n    val mobi = headers.mobi\n    val palmdoc = headers.palmdoc\n    private val exth = headers.exth\n    private val trailingFlags = mobi.trailingFlags\n\n    val charset: Charset = when (val charset = mobi.encoding) {\n        65001 -> Charsets.UTF_8\n        1252 -> Charset.forName(\"windows-1252\")\n        else -> error(\"unknown charset $charset\")\n    }\n\n    private val decompressor: Decompressor = when (val compression = palmdoc.compression) {\n        1 -> PlainDecompressor()\n        2 -> Lz77Decompressor(max(4096, palmdoc.recordSize))\n        17480 -> HuffcdicDecompressor(this, mobi)\n        else -> error(\"unknown compression $charset\")\n    }\n\n    @Suppress(\"UNCHECKED_CAST\")\n    val metadata: MobiMetadata by lazy {\n        MobiMetadata(\n            mobi.uid.toString(),\n            exth[\"title\"] as? String ?: mobi.title,\n            exth[\"creator\"] as? List<String> ?: emptyList(),\n            exth[\"publisher\"] as? String ?: \"\",\n            exth[\"language\"] as? String ?: mobi.languege,\n            exth[\"date\"] as? String ?: \"\",\n            exth[\"description\"] as? String ?: \"\",\n            exth[\"subject\"] as? List<String> ?: emptyList(),\n            exth[\"rights\"] as? String ?: \"\"\n        )\n    }\n\n    var toc: List<TOC>? = null\n\n    private var textRecordOffsets = arrayListOf<Int>()\n\n    init {\n        buildTextRecordOffsets()\n    }\n\n    private fun buildTextRecordOffsets() {\n        var offset = 0\n        for (i in 0..<palmdoc.numTextRecords) {\n            offset += getTextRecord(i).size\n            textRecordOffsets.add(offset)\n        }\n    }\n\n    fun getRecord(index: Int): ByteBuffer {\n        return pdbFile.getRecordData(kf8BoundaryOffset + index)\n    }\n\n    fun getTextRecord(index: Int): ByteArray {\n        if (index < 0 && index >= palmdoc.numTextRecords) {\n            throw IndexOutOfBoundsException(\"Text record index out of bounds\")\n        }\n        var content = getRecord(index + 1).array()\n        content = removeTrailingEntries(content)\n        return decompressor.decompress(content)\n    }\n\n    fun getTextRecordInputStream(): InputStream {\n        return object : InputStream() {\n\n            private var index = -1\n            private var bis: ByteArrayInputStream = emptyByteArrayInputStream\n            private var available = textRecordOffsets.last()\n            private var pos = 0\n\n            override fun read(): Int {\n                if (index >= palmdoc.numTextRecords) {\n                    return -1\n                }\n\n                if (bis.available() == 0) {\n                    if (++index >= palmdoc.numTextRecords) {\n                        return -1\n                    }\n                    bis = getTextRecord(index).inputStream()\n                }\n\n                val b = bis.read()\n\n                available--\n                pos++\n\n                return b\n            }\n\n            override fun skip(n: Long): Long {\n                if (n == 0L) return 0L\n\n                val n1 = min(available, n.toInt())\n\n                if (n1 < bis.available()) {\n                    bis.skip(n1.toLong())\n\n                    available -= n1\n                    pos += n1\n\n                    return n1.toLong()\n                }\n\n                val bIndex = textRecordOffsets.binarySearch(pos + n1)\n                index = abs(bIndex + 1)\n\n                bis = getTextRecord(index).inputStream()\n\n                val offset = textRecordOffsets.getOrNull(index - 1) ?: 0\n                bis.skip((pos + n1 - offset).toLong())\n\n                available -= n1\n                pos += n1\n\n                return n1.toLong()\n            }\n\n            override fun available(): Int {\n                return available\n            }\n\n        }\n    }\n\n    private fun removeTrailingEntries(byteArray: ByteArray): ByteArray {\n        if (trailingFlags == 0) return byteArray\n        val multibyte = trailingFlags and 1 != 0\n        val numTrailingEntries = (trailingFlags shr 1).countOneBits()\n        val lastIndex = byteArray.lastIndex\n        var extraSize = 0\n        for (i in 0..<numTrailingEntries) {\n            var value = 0\n            for (j in max(0, lastIndex - 4 - extraSize)..max(0, lastIndex - extraSize)) {\n                val byte = byteArray[j]\n                if (byte and 0b1000_0000 != 0) {\n                    value = 0\n                }\n                value = (value shl 7) or (byte and 0b111_1111)\n            }\n            extraSize += value\n        }\n        if (multibyte) {\n            val byte = byteArray[byteArray.lastIndex - extraSize]\n            extraSize += (byte and 0b11) + 1\n        }\n        return byteArray.copyOfRange(0, byteArray.size - extraSize)\n    }\n\n    fun getResource(index: Int): ByteBuffer {\n        return pdbFile.getRecordData(resourceStart + index)\n    }\n\n    fun getNCX(): List<NCX>? {\n        val indxIndex = mobi.indx\n        if (indxIndex == -1) {\n            return null\n        }\n        val indexData = getIndexData(indxIndex)\n        val items = indexData.table.mapIndexed { index, indexEntry ->\n            val tagMap = indexEntry.tagMap\n            NCX(\n                index,\n                tagMap[1]?.tagValues?.getOrNull(0),\n                tagMap[2]?.tagValues?.getOrNull(0),\n                indexData.cncx[tagMap[3].tagValues[0]],\n                tagMap[4]?.tagValues?.getOrNull(0),\n                tagMap[6]?.tagValues,\n                tagMap[21]?.tagValues?.getOrNull(0),\n                tagMap[22]?.tagValues?.getOrNull(0),\n                tagMap[23]?.tagValues?.getOrNull(0),\n            )\n        }\n\n        val parentItemMap = hashMapOf<Int, ArrayList<NCX>>()\n\n        items.forEach {\n            val parent = it.parent ?: return@forEach\n            val array = parentItemMap.getOrPut(parent) { arrayListOf() }\n            array.add(it)\n        }\n\n        fun getChildren(item: NCX): NCX {\n            if (item.firstChild == null) return item\n            item.children = parentItemMap[item.index]?.map(::getChildren)\n            return item\n        }\n\n        return items.filter { it.headingLevel == 0 }.map(::getChildren)\n    }\n\n    fun getCover(): ByteArray? {\n        val coverOffset = exth[\"coverOffset\"] as? Int\n        val thumbnailOffset = exth[\"thumbnailOffset\"] as? Int\n\n        if (coverOffset != null && coverOffset != -1) {\n            return getResource(coverOffset).array()\n        }\n\n        if (thumbnailOffset != null && thumbnailOffset != -1) {\n            return getResource(thumbnailOffset).array()\n        }\n\n        return null\n    }\n\n    fun getIndexData(indxIndex: Int): IndexData {\n        val indxRecord = getRecord(indxIndex)\n        val indx = readIndxHeader(indxRecord)\n        indxRecord.position(indx.length)\n        val tagxBuffer = indxRecord.slice()\n        val tagx = readTagxHeader(tagxBuffer)\n        val tagTable = readTagxTags(tagx, tagxBuffer)\n        val cncx = readCncx(indxIndex, indx)\n\n        val table = arrayListOf<IndexEntry>()\n\n        for (i in 0..<indx.numRecords) {\n            val indxBuffer = getRecord(indxIndex + 1 + i)\n            val indxHeader = readIndxHeader(indxBuffer)\n            val idxt = readIdxt(indxBuffer, indxHeader)\n            for (j in 0..<indxHeader.numRecords) {\n                val idxtOffset = idxt[j]\n                val entry = readIndexEntry(indxBuffer, tagx, tagTable, idxtOffset)\n                table.add(entry)\n            }\n        }\n\n        return IndexData(table, cncx)\n    }\n\n    private fun readIndexEntry(\n        indxBuffer: ByteBuffer,\n        tagx: TagxHeader,\n        tagTable: List<TagxTag>,\n        idxtOffset: Int\n    ): IndexEntry {\n        val array = indxBuffer.array()\n\n        val len = indxBuffer.readUInt8(idxtOffset)\n        val label = indxBuffer.readString(idxtOffset + 1, len)\n\n        val ptagxs = arrayListOf<Ptagx>()\n        val startPos = idxtOffset + 1 + len\n        var controlByteIndex = 0\n        var pos = startPos + tagx.numControlBytes\n\n        for (tag in tagTable) {\n            if (tag.controlByte == 1) {\n                controlByteIndex++\n                continue\n            }\n            val offset = startPos + controlByteIndex\n            var value = indxBuffer.readUInt8(offset) and tag.bitmask\n            if (value == tag.bitmask) {\n                if (tag.bitmask.countOneBits() > 1) {\n                    var v = 0\n                    for (a in pos..<min(pos + 4, array.size)) {\n                        val byte = array[a]\n                        v = (v shl 7) or (byte and 0b111_1111)\n                        pos++\n                        if (byte and 0b1000_0000 != 0) break\n                    }\n                    ptagxs.add(Ptagx(tag.tag, tag.numValues, null, v))\n                } else {\n                    ptagxs.add(Ptagx(tag.tag, tag.numValues, 1, null))\n                }\n            } else {\n                var mask = tag.bitmask\n                while ((mask and 1) == 0) {\n                    mask = mask shr 1\n                    value = value shr 1\n                }\n                ptagxs.add(Ptagx(tag.tag, tag.numValues, value, null))\n            }\n        }\n\n        val tags = arrayListOf<IndexTag>()\n        val tagMap = SparseArray<IndexTag>()\n\n        for (ptagx in ptagxs) {\n            val values = arrayListOf<Int>()\n            if (ptagx.valueCount != null) {\n                repeat(ptagx.valueCount * ptagx.tagValueCount) {\n                    var v = 0\n                    for (a in pos..<min(pos + 4, array.size)) {\n                        val byte = array[a]\n                        v = (v shl 7) or (byte and 0b111_1111)\n                        pos++\n                        if (byte and 0b1000_0000 != 0) break\n                    }\n                    values.add(v)\n                }\n            } else {\n                var count = 0\n                while (count < ptagx.valueBytes!!) {\n                    var v = 0\n                    for (a in pos..<min(pos + 4, array.size)) {\n                        val byte = array[a]\n                        v = (v shl 7) or (byte and 0b111_1111)\n                        pos++\n                        count++\n                        if (byte and 0b1000_0000 != 0) break\n                    }\n                    values.add(v)\n                }\n            }\n            val tag = IndexTag(ptagx.tag, values)\n            tags.add(tag)\n            tagMap[tag.tagId] = tag\n        }\n\n        return IndexEntry(label, tags, tagMap)\n    }\n\n    private fun readIdxt(buffer: ByteBuffer, indxHeader: IndxHeader): IntArray {\n        return buffer.readUInt16Array(indxHeader.idxt + 4, indxHeader.numRecords)\n    }\n\n    private fun readTagxTags(tagx: TagxHeader, tagxBuffer: ByteBuffer): List<TagxTag> {\n        val numTags = (tagx.length - 12) / 4\n        val tags = arrayListOf<TagxTag>()\n        tagxBuffer.position(12)\n        for (i in 0..<numTags) {\n            val tag = tagxBuffer.readUInt8()\n            val numValues = tagxBuffer.readUInt8()\n            val bitmask = tagxBuffer.readUInt8()\n            val controlByte = tagxBuffer.readUInt8()\n            tags.add(TagxTag(tag, numValues, bitmask, controlByte))\n        }\n        return tags\n    }\n\n    private fun readCncx(indxIndex: Int, indx: IndxHeader): SparseArray<String> {\n        val cncx = SparseArray<String>()\n        var cncxRecordOffset = 0\n        for (i in 0..<indx.numCncx) {\n            val record = getRecord(indxIndex + indx.numRecords + i + 1)\n            val array = record.array()\n            var pos = 0\n            while (pos < array.size) {\n                val index = pos\n                var value = 0\n                var length = 0\n                for (a in pos..<min(pos + 4, array.size)) {\n                    val byte = array[a]\n                    value = (value shl 7) or (byte and 0b111_1111)\n                    length++\n                    if (byte and 0b1000_0000 != 0) break\n                }\n                pos += length\n                val result = record.readString(pos, value, charset)\n                pos += value\n                cncx[cncxRecordOffset + index] = result\n            }\n            cncxRecordOffset += 0x10000\n        }\n\n        return cncx\n    }\n\n    private fun readIndxHeader(indx: ByteBuffer): IndxHeader {\n        val magic = indx.readString(0, 4)\n        if (magic != \"INDX\") {\n            error(\"Invalid INDX record\")\n        }\n        val length = indx.readUInt32(4)\n        val type = indx.readUInt32(8)\n        val idxt = indx.readUInt32(20)\n        val numRecords = indx.readUInt32(24)\n        val encoding = indx.readUInt32(28)\n        val language = indx.readUInt32(32)\n        val total = indx.readUInt32(36)\n        val ordt = indx.readUInt32(40)\n        val ligt = indx.readUInt32(44)\n        val numLigt = indx.readUInt32(48)\n        val numCncx = indx.readUInt32(52)\n\n        return IndxHeader(\n            magic, length, type, idxt, numRecords, encoding, language, total, ordt,\n            ligt, numLigt, numCncx\n        )\n    }\n\n    private fun readTagxHeader(buffer: ByteBuffer): TagxHeader {\n        val magic = buffer.readString(0, 4)\n        if (magic != \"TAGX\") {\n            error(\"Invalid INDX record\")\n        }\n        val length = buffer.readUInt32(4)\n        val numControlBytes = buffer.readUInt32(8)\n\n        return TagxHeader(magic, length, numControlBytes)\n    }\n\n    fun close() {\n        pdbFile.close()\n    }\n\n    protected fun finalize() {\n        close()\n    }\n\n    companion object {\n        private val emptyByteArrayInputStream = ByteArrayInputStream(ByteArray(0))\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/lib/mobi/MobiReader.kt",
    "content": "package io.legado.app.lib.mobi\n\nimport android.os.ParcelFileDescriptor\nimport io.legado.app.lib.mobi.entities.ExthRecordType\nimport io.legado.app.lib.mobi.entities.KF8Header\nimport io.legado.app.lib.mobi.entities.MobiEntryHeaders\nimport io.legado.app.lib.mobi.entities.MobiHeader\nimport io.legado.app.lib.mobi.entities.PalmDocHeader\nimport io.legado.app.lib.mobi.utils.readString\nimport io.legado.app.lib.mobi.utils.readUInt16\nimport io.legado.app.lib.mobi.utils.readUInt32\nimport io.legado.app.lib.mobi.utils.readUInt8\nimport java.nio.ByteBuffer\nimport java.nio.charset.Charset\n\nclass MobiReader {\n\n    fun readMobi(pfd: ParcelFileDescriptor): MobiBook {\n        val pdbFile = PDBFile(pfd)\n        val record0 = pdbFile.getRecordData(0)\n\n        var mobiEntryHeaders = readMobiEntryHeaders(record0)\n        val mobi = mobiEntryHeaders.mobi\n        val exth = mobiEntryHeaders.exth\n        val resourceStart = mobi.resourceStart\n\n        var isKF8 = mobi.version >= 8\n\n        var kf8BoundaryOffset = 0\n\n        if (!isKF8) {\n            val boundary = exth[\"boundary\"] as? Int\n            if (boundary != null && boundary != -1) {\n                try {\n                    val buffer = pdbFile.getRecordData(boundary)\n                    mobiEntryHeaders = readMobiEntryHeaders(buffer)\n                    kf8BoundaryOffset = boundary\n                    isKF8 = true\n                } catch (e: Exception) {\n                    e.printStackTrace()\n                }\n            }\n        }\n\n        return if (isKF8) {\n            KF8Book(pdbFile, mobiEntryHeaders, kf8BoundaryOffset, resourceStart)\n        } else {\n            KF6Book(pdbFile, mobiEntryHeaders, kf8BoundaryOffset, resourceStart)\n        }\n    }\n\n    private fun readMobiEntryHeaders(buffer: ByteBuffer): MobiEntryHeaders {\n        val palmDocHeader = readPalmDocHeader(buffer)\n        val mobiHeader = readMobiHeader(buffer)\n        val exth = if (mobiHeader.exthFlag and 0b100_0000 != 0) {\n            buffer.position(mobiHeader.length + 16)\n            readExth(buffer.slice())\n        } else {\n            emptyMap()\n        }\n        val kF8Header = if (mobiHeader.version >= 8) {\n            readKF8Header(buffer)\n        } else {\n            null\n        }\n        return MobiEntryHeaders(palmDocHeader, mobiHeader, exth, kF8Header)\n    }\n\n    private fun readExth(buffer: ByteBuffer): Map<String, Any> {\n        val magic = buffer.readString(0, 4)\n        check(magic == \"EXTH\") { \"Invalid EXTH header\" }\n        val count = buffer.readUInt32(8)\n        var offset = 12\n        val map = HashMap<String, Any>()\n        for (i in 0..<count) {\n            val type = buffer.readUInt32(offset)\n            val length = buffer.readUInt32(offset + 4)\n            if (type in exthRecordTypeMap) {\n                val exthRecordType = exthRecordTypeMap[type]!!\n                val name = exthRecordType.name\n                val data: Any = if (exthRecordType.type == \"uint\") {\n                    buffer.readUInt32(offset + 8)\n                } else {\n                    buffer.readString(offset + 8, length - 8)\n                }\n                if (exthRecordType.many) {\n                    if (!map.contains(name)) {\n                        map[name] = arrayListOf<String>()\n                    }\n                    @Suppress(\"UNCHECKED_CAST\")\n                    val array = map[name] as ArrayList<String>\n                    array.add(data as String)\n                } else {\n                    map[name] = data\n                }\n            }\n            offset += length\n        }\n        return map\n    }\n\n    private fun readPalmDocHeader(content: ByteBuffer): PalmDocHeader {\n\n        val compression = content.readUInt16(0)\n        val numTextRecords = content.readUInt16(8)\n        val recordSize = content.readUInt16(10)\n        val encryption = content.readUInt16(12)\n\n        return PalmDocHeader(compression, numTextRecords, recordSize, encryption)\n    }\n\n    private fun readMobiHeader(content: ByteBuffer): MobiHeader {\n\n        val identifier = content.readString(16, 4)\n\n        check(identifier == \"MOBI\") { \"Missing MOBI header\" }\n\n        val length = content.readUInt32(20)\n        val type = content.readUInt32(24)\n        val encoding = content.readUInt32(28)\n        val uid = content.readUInt32(32)\n        val version = content.readUInt32(36)\n        val titleOffset = content.readUInt32(84)\n        val titleLength = content.readUInt32(88)\n        val localeRegion = content.readUInt8(94)\n        val localeLanguage = content.readUInt8(95)\n        val resourceStar = content.readUInt32(108)\n        val huffcdic = content.readUInt32(112)\n        val numHuffcdic = content.readUInt32(116)\n        val exthFlag = content.readUInt32(128)\n        val trailingFlags = content.readUInt32(240)\n        val indx = content.readUInt32(244)\n        val charset: Charset = when (encoding) {\n            65001 -> Charsets.UTF_8\n            1252 -> Charset.forName(\"windows-1252\")\n            else -> error(\"unknown charset $encoding\")\n        }\n        val title = content.readString(titleOffset, titleLength, charset)\n\n        val lang = mobiLangMap[localeLanguage]\n        val language = lang?.getOrNull(localeRegion shr 2) ?: lang?.first() ?: \"\"\n\n        return MobiHeader(\n            identifier, length, type, encoding, uid, version, titleOffset, titleLength,\n            localeRegion, localeLanguage, resourceStar, huffcdic, numHuffcdic, exthFlag,\n            trailingFlags, indx, title, language\n        )\n    }\n\n    private fun readKF8Header(content: ByteBuffer): KF8Header {\n        val fdst = content.readUInt32(192)\n        val numFdst = content.readUInt32(196)\n        val frag = content.readUInt32(248)\n        val skel = content.readUInt32(252)\n        val guide = content.readUInt32(260)\n\n        return KF8Header(fdst, numFdst, frag, skel, guide)\n    }\n\n    companion object {\n        val exthRecordTypeMap = mapOf(\n            100 to ExthRecordType(\"creator\", \"string\", true),\n            101 to ExthRecordType(\"publisher\"),\n            103 to ExthRecordType(\"description\"),\n            104 to ExthRecordType(\"isbn\"),\n            105 to ExthRecordType(\"subject\", \"string\", true),\n            106 to ExthRecordType(\"date\"),\n            108 to ExthRecordType(\"contributor\", \"string\", true),\n            109 to ExthRecordType(\"rights\"),\n            110 to ExthRecordType(\"subjectCode\", \"string\", true),\n            112 to ExthRecordType(\"source\", \"string\", true),\n            113 to ExthRecordType(\"asin\"),\n            121 to ExthRecordType(\"boundary\", \"uint\"),\n            122 to ExthRecordType(\"fixedLayout\"),\n            125 to ExthRecordType(\"numResources\", \"uint\"),\n            126 to ExthRecordType(\"originalResolution\"),\n            127 to ExthRecordType(\"zeroGutter\"),\n            128 to ExthRecordType(\"zeroMargin\"),\n            129 to ExthRecordType(\"coverURI\"),\n            132 to ExthRecordType(\"regionMagnification\"),\n            201 to ExthRecordType(\"coverOffset\", \"uint\"),\n            202 to ExthRecordType(\"thumbnailOffset\", \"uint\"),\n            204 to ExthRecordType(\"creatorSoftware\", \"uint\"),\n            503 to ExthRecordType(\"title\"),\n            524 to ExthRecordType(\"language\", \"string\", true),\n            527 to ExthRecordType(\"pageProgressionDirection\"),\n        )\n\n        val mobiLangMap = mapOf(\n            1 to listOf(\n                \"ar\", \"ar-SA\", \"ar-IQ\", \"ar-EG\", \"ar-LY\", \"ar-DZ\", \"ar-MA\", \"ar-TN\", \"ar-OM\",\n                \"ar-YE\", \"ar-SY\", \"ar-JO\", \"ar-LB\", \"ar-KW\", \"ar-AE\", \"ar-BH\", \"ar-QA\"\n            ),\n            2 to listOf(\"bg\"), 3 to listOf(\"ca\"),\n            4 to listOf(\"zh\", \"zh-TW\", \"zh-CN\", \"zh-HK\", \"zh-SG\"),\n            5 to listOf(\"cs\"), 6 to listOf(\"da\"),\n            7 to listOf(\"de\", \"de-DE\", \"de-CH\", \"de-AT\", \"de-LU\", \"de-LI\"), 8 to listOf(\"el\"),\n            9 to listOf(\n                \"en\", \"en-US\", \"en-GB\", \"en-AU\", \"en-CA\", \"en-NZ\", \"en-IE\", \"en-ZA\",\n                \"en-JM\", null, \"en-BZ\", \"en-TT\", \"en-ZW\", \"en-PH\"\n            ),\n            10 to listOf(\n                \"es\", \"es-ES\", \"es-MX\", null, \"es-GT\", \"es-CR\", \"es-PA\", \"es-DO\",\n                \"es-VE\", \"es-CO\", \"es-PE\", \"es-AR\", \"es-EC\", \"es-CL\", \"es-UY\", \"es-PY\",\n                \"es-BO\", \"es-SV\", \"es-HN\", \"es-NI\", \"es-PR\"\n            ),\n            11 to listOf(\"fi\"),\n            12 to listOf(\"fr\", \"fr-FR\", \"fr-BE\", \"fr-CA\", \"fr-CH\", \"fr-LU\", \"fr-MC\"),\n            13 to listOf(\"he\"), 14 to listOf(\"hu\"), 15 to listOf(\"is\"),\n            16 to listOf(\"it\", \"it-IT\", \"it-CH\"), 17 to listOf(\"ja\"), 18 to listOf(\"ko\"),\n            19 to listOf(\"nl\", \"nl-NL\", \"nl-BE\"), 20 to listOf(\"no\", \"nb\", \"nn\"),\n            21 to listOf(\"pl\"), 22 to listOf(\"pt\", \"pt-BR\", \"pt-PT\"), 23 to listOf(\"rm\"),\n            24 to listOf(\"ro\"), 25 to listOf(\"ru\"), 26 to listOf(\"hr\", null, \"sr\"),\n            27 to listOf(\"sk\"), 28 to listOf(\"sq\"), 29 to listOf(\"sv\", \"sv-SE\", \"sv-FI\"),\n            30 to listOf(\"th\"), 31 to listOf(\"tr\"), 32 to listOf(\"ur\"), 33 to listOf(\"id\"),\n            34 to listOf(\"uk\"), 35 to listOf(\"be\"), 36 to listOf(\"sl\"), 37 to listOf(\"et\"),\n            38 to listOf(\"lv\"), 39 to listOf(\"lt\"), 41 to listOf(\"fa\"), 42 to listOf(\"vi\"),\n            43 to listOf(\"hy\"), 44 to listOf(\"az\"), 45 to listOf(\"eu\"), 46 to listOf(\"hsb\"),\n            47 to listOf(\"mk\"), 48 to listOf(\"st\"), 49 to listOf(\"ts\"), 50 to listOf(\"tn\"),\n            52 to listOf(\"xh\"), 53 to listOf(\"zu\"), 54 to listOf(\"af\"), 55 to listOf(\"ka\"),\n            56 to listOf(\"fo\"), 57 to listOf(\"hi\"), 58 to listOf(\"mt\"), 59 to listOf(\"se\"),\n            62 to listOf(\"ms\"), 63 to listOf(\"kk\"), 65 to listOf(\"sw\"),\n            67 to listOf(\"uz\", null, \"uz-UZ\"), 68 to listOf(\"tt\"), 69 to listOf(\"bn\"),\n            70 to listOf(\"pa\"), 71 to listOf(\"gu\"), 72 to listOf(\"or\"), 73 to listOf(\"ta\"),\n            74 to listOf(\"te\"), 75 to listOf(\"kn\"), 76 to listOf(\"ml\"), 77 to listOf(\"as\"),\n            78 to listOf(\"mr\"), 79 to listOf(\"sa\"), 82 to listOf(\"cy\", \"cy-GB\"),\n            83 to listOf(\"gl\", \"gl-ES\"), 87 to listOf(\"kok\"), 97 to listOf(\"ne\"),\n            98 to listOf(\"fy\")\n        )\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/lib/mobi/PDBFile.kt",
    "content": "package io.legado.app.lib.mobi\n\nimport android.os.ParcelFileDescriptor\nimport io.legado.app.lib.mobi.utils.readString\nimport io.legado.app.lib.mobi.utils.readUInt16\nimport io.legado.app.lib.mobi.utils.readUInt32\nimport java.io.FileInputStream\nimport java.nio.ByteBuffer\nimport java.nio.channels.FileChannel\n\nclass PDBFile(private val pfd: ParcelFileDescriptor) {\n    private val fc: FileChannel = FileInputStream(pfd.fileDescriptor).channel\n    private val offsets: IntArray\n    val name: String\n    val type: String\n    val creator: String\n    val recordCount: Int\n\n    init {\n        var buffer = ByteBuffer.allocate(79)\n        fc.read(buffer)\n        name = buffer.readString(0, 32)\n        type = buffer.readString(60, 4)\n        creator = buffer.readString(64, 4)\n        recordCount = buffer.readUInt16(76)\n\n        buffer = ByteBuffer.allocate(recordCount * 8)\n        fc.read(buffer, 78)\n        offsets = IntArray(recordCount) {\n            buffer.readUInt32(it * 8)\n        }\n    }\n\n    fun getRecordData(index: Int): ByteBuffer {\n        if (index < 0 || index >= recordCount) {\n            throw IndexOutOfBoundsException(\"Record index out of bounds\")\n        }\n        val len = offsets.getOrElse(index + 1) { fc.size().toInt() } - offsets[index]\n        val buffer = ByteBuffer.allocate(len)\n        fc.read(buffer, offsets[index].toLong())\n        return buffer\n    }\n\n    fun close() {\n        fc.close()\n        pfd.close()\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/lib/mobi/decompress/CDICData.kt",
    "content": "package io.legado.app.lib.mobi.decompress\n\nclass CDICEntry(\n    var data: ByteArray,\n    var decompressed: Boolean\n)\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/lib/mobi/decompress/Decompressor.kt",
    "content": "package io.legado.app.lib.mobi.decompress\n\ninterface Decompressor {\n\n    fun decompress(data: ByteArray): ByteArray\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/lib/mobi/decompress/HuffcdicDecompressor.kt",
    "content": "package io.legado.app.lib.mobi.decompress\n\nimport io.legado.app.lib.mobi.MobiBook\nimport io.legado.app.lib.mobi.entities.MobiHeader\nimport io.legado.app.lib.mobi.utils.readIntArray\nimport io.legado.app.lib.mobi.utils.readString\nimport io.legado.app.lib.mobi.utils.readUInt16\nimport io.legado.app.lib.mobi.utils.readUInt32\nimport java.io.ByteArrayOutputStream\nimport java.nio.ByteBuffer\nimport kotlin.math.min\n\n@Suppress(\"SpellCheckingInspection\")\nclass HuffcdicDecompressor(\n    mobiBook: MobiBook,\n    mobiHeader: MobiHeader,\n) : Decompressor {\n\n    private val magic: String\n    private val offset1: Int\n    private val offset2: Int\n    private val table1: IntArray\n    private val mincodeTable = LongArray(33)\n    private val maxcodeTable = LongArray(33)\n    private val dictionary = arrayListOf<CDICEntry>()\n\n    init {\n        val huff = mobiBook.getRecord(mobiHeader.huffcdic)\n        magic = huff.readString(0, 4)\n        if (magic != \"HUFF\") error(\"Invalid HUFF record\")\n        offset1 = huff.readUInt32(8)\n        offset2 = huff.readUInt32(12)\n\n        table1 = huff.readIntArray(offset1, 256)\n\n        huff.position(offset2)\n\n        for (i in 1..32) {\n            val mincode = huff.readUInt32().toLong()\n            val maxcode = huff.readUInt32().toLong()\n            mincodeTable[i] = mincode shl (32 - i)\n            maxcodeTable[i] = ((maxcode + 1) shl (32 - i)) - 1\n        }\n\n        for (i in 1..<mobiHeader.numHuffcdic) {\n            val record = mobiBook.getRecord(mobiHeader.huffcdic + i)\n            val magic = record.readString(0, 4)\n            if (magic != \"CDIC\") error(\"Invalid CDIC record\")\n            val length = record.readUInt32(4)\n            val numEntries = record.readUInt32(8)\n            val codeLength = record.readUInt32(12)\n\n            val n = min(1 shl codeLength, numEntries - dictionary.size)\n\n            record.position(length)\n            val buffer = record.slice()\n            for (j in 0..<n) {\n                val offset = buffer.readUInt16(j * 2)\n                val x = buffer.readUInt16(offset)\n                val len = x and 0x7fff\n                val decompressed = x and 0x8000 != 0\n                val data = ByteArray(len)\n                buffer.position(offset + 2)\n                buffer.get(data)\n                dictionary.add(CDICEntry(data, decompressed))\n            }\n\n        }\n\n    }\n\n    override fun decompress(data: ByteArray): ByteArray {\n        val bos = ByteArrayOutputStream()\n\n        val buffer = ByteBuffer.wrap(data)\n\n        var bitsleft = data.size * 8\n        var pos = 0\n        var x = buffer.readUIntX(pos, 8)\n        var bitcount = 32\n\n        while (true) {\n            if (bitcount <= 0) {\n                pos += 4\n                x = buffer.readUIntX(pos, 8)\n                bitcount += 32\n            }\n\n            val code = (x shr bitcount) and ((1L shl 32) - 1)\n            val t1 = table1[(code shr 24).toInt()]\n            var codelen = t1 and 0x1f\n            var maxcode = (((t1.toLong() shr 8) + 1) shl ((32L - codelen).toInt())) - 1\n\n            if (t1 and 0x80 == 0) {\n                while (code < mincodeTable[codelen]) {\n                    codelen++\n                }\n                maxcode = maxcodeTable[codelen]\n            }\n\n            bitcount -= codelen\n            bitsleft -= codelen\n\n            if (bitsleft < 0) {\n                break\n            }\n\n            val index = (maxcode - code) shr ((32 - codelen))\n            val entry = dictionary[index.toInt()]\n            if (!entry.decompressed) {\n                entry.data = decompress(entry.data)\n                entry.decompressed = true\n            }\n\n            bos.write(entry.data)\n\n        }\n\n        return bos.toByteArray()\n    }\n\n    private fun ByteBuffer.readUIntX(offset: Int, maxlen: Int): Long {\n        position(offset)\n        var value = 0L\n        var i = maxlen\n        var bytesLeft = limit() - position()\n        while (i-- > 0 && bytesLeft-- > 0) {\n            value = value or ((get().toLong() and 0xFF) shl (i * 8))\n        }\n        return value\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/lib/mobi/decompress/Lz77Decompressor.kt",
    "content": "package io.legado.app.lib.mobi.decompress\n\nimport androidx.core.util.Pools.SynchronizedPool\n\nclass Lz77Decompressor(private val textRecordSize: Int) : Decompressor {\n\n    val pool = SynchronizedPool<ByteArray>(2)\n\n    override fun decompress(data: ByteArray): ByteArray {\n        val out = pool.acquire() ?: ByteArray(textRecordSize)\n\n        var i = 0\n        var o = 0\n        while (i < data.size) {\n            var c = data[i++].toInt() and 0x00FF\n            if (c in 0x01..0x08) {\n                var j = 0\n                while (j < c && i + j < data.size) {\n                    out[o++] = data[i + j]\n                    j++\n                }\n                i += c\n            } else if (c <= 0x7f) {\n                out[o++] = c.toByte()\n            } else if (c >= 0xC0) {\n                out[o++] = ' '.code.toByte()\n                out[o++] = (c xor 0x80).toByte()\n            } else {\n                if (i < data.size) {\n                    c = c shl 8 or (data[i++].toInt() and 0xFF)\n                    val length = (c and 0x0007) + 3\n                    val location = (c shr 3) and 0x7FF\n\n                    if (location in 1..o) {\n                        for (j in 0 until length) {\n                            val idx = o - location\n                            out[o++] = out[idx]\n                        }\n                    }\n                }\n            }\n        }\n\n        val result = ByteArray(o)\n        System.arraycopy(out, 0, result, 0, o)\n        return result.also { pool.release(out) }\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/lib/mobi/decompress/PlainDecompressor.kt",
    "content": "package io.legado.app.lib.mobi.decompress\n\nclass PlainDecompressor : Decompressor {\n\n    override fun decompress(data: ByteArray): ByteArray {\n        return data\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/lib/mobi/entities/ExthRecordType.kt",
    "content": "package io.legado.app.lib.mobi.entities\n\ndata class ExthRecordType(\n    val name: String,\n    val type: String = \"string\",\n    val many: Boolean = false\n)\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/lib/mobi/entities/FdstHeader.kt",
    "content": "package io.legado.app.lib.mobi.entities\n\ndata class FdstHeader(\n    val magic: String,\n    val numEntries: Int\n)\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/lib/mobi/entities/Fragment.kt",
    "content": "package io.legado.app.lib.mobi.entities\n\ndata class Fragment(\n    val insertOffset: Int,\n    val selector: String,\n    val index: Int,\n    val offset: Int,\n    val length: Int\n)\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/lib/mobi/entities/IndexData.kt",
    "content": "package io.legado.app.lib.mobi.entities\n\nimport android.util.SparseArray\n\ndata class IndexData(\n    val table: List<IndexEntry>,\n    val cncx: SparseArray<String>\n)\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/lib/mobi/entities/IndexEntry.kt",
    "content": "package io.legado.app.lib.mobi.entities\n\nimport android.util.SparseArray\n\ndata class IndexEntry(\n    val label: String,\n    val tags: List<IndexTag>,\n    val tagMap: SparseArray<IndexTag>\n)\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/lib/mobi/entities/IndexTag.kt",
    "content": "package io.legado.app.lib.mobi.entities\n\ndata class IndexTag(\n    val tagId: Int,\n    val tagValues: List<Int>\n)\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/lib/mobi/entities/IndxHeader.kt",
    "content": "package io.legado.app.lib.mobi.entities\n\ndata class IndxHeader(\n    val magic: String,\n    val length: Int,\n    val type: Int,\n    val idxt: Int,\n    val numRecords: Int,\n    val encoding: Int,\n    val language: Int,\n    val total: Int,\n    val ordt: Int,\n    val ligt: Int,\n    val numLigt: Int,\n    val numCncx: Int,\n)\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/lib/mobi/entities/KF6Section.kt",
    "content": "package io.legado.app.lib.mobi.entities\n\ndata class KF6Section(\n    val index: Int,\n    val start: Int,\n    val end: Int,\n    val length: Int,\n    val href: String,\n    var next: KF6Section? = null\n)\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/lib/mobi/entities/KF8Header.kt",
    "content": "package io.legado.app.lib.mobi.entities\n\ndata class KF8Header(\n    val fdst: Int,\n    val numFdst: Int,\n    val frag: Int,\n    val skel: Int,\n    val guide: Int,\n)\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/lib/mobi/entities/KF8Pos.kt",
    "content": "package io.legado.app.lib.mobi.entities\n\ndata class KF8Pos(\n    val fid: Int,\n    val offset: Int\n)\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/lib/mobi/entities/KF8Resource.kt",
    "content": "package io.legado.app.lib.mobi.entities\n\ndata class KF8Resource(\n    val resourceType: String,\n    val id: Int,\n    val type: String\n)\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/lib/mobi/entities/KF8Section.kt",
    "content": "package io.legado.app.lib.mobi.entities\n\ndata class KF8Section(\n    val index: Int,\n    val skeleton: Skeleton,\n    val frags: List<Fragment>,\n    val fragEnd: Int,\n    val length: Int,\n    val totalLength: Int,\n    val href: String,\n    var next: KF8Section? = null\n) {\n    val linear get() = frags.isNotEmpty()\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/lib/mobi/entities/MobiEntryHeaders.kt",
    "content": "package io.legado.app.lib.mobi.entities\n\ndata class MobiEntryHeaders(\n    val palmdoc: PalmDocHeader,\n    val mobi: MobiHeader,\n    val exth: Map<String, Any>,\n    val kf8: KF8Header?\n)\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/lib/mobi/entities/MobiHeader.kt",
    "content": "package io.legado.app.lib.mobi.entities\n\ndata class MobiHeader(\n    val identifier: String,\n    val length: Int,\n    val type: Int,\n    val encoding: Int,\n    val uid: Int,\n    val version: Int,\n    val titleOffset: Int,\n    val titleLength: Int,\n    val localeRegion: Int,\n    val localeLanguage: Int,\n    val resourceStart: Int,\n    val huffcdic: Int,\n    val numHuffcdic: Int,\n    val exthFlag: Int,\n    val trailingFlags: Int,\n    val indx: Int,\n    val title: String,\n    val languege: String\n)\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/lib/mobi/entities/MobiMetadata.kt",
    "content": "package io.legado.app.lib.mobi.entities\n\ndata class MobiMetadata(\n    val identifier: String,\n    val title: String,\n    val author: List<String>,\n    val publisher: String,\n    val language: String,\n    val published: String,\n    val description: String,\n    val subject: List<String>,\n    val rights: String\n)\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/lib/mobi/entities/NCX.kt",
    "content": "package io.legado.app.lib.mobi.entities\n\ndata class NCX(\n    val index: Int,\n    val offset: Int?,\n    val size: Int?,\n    val label: String,\n    val headingLevel: Int?,\n    val pos: List<Int>?,\n    val parent: Int?,\n    val firstChild: Int?,\n    val lastChild: Int?,\n    var children: List<NCX>? = null\n)\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/lib/mobi/entities/PalmDocHeader.kt",
    "content": "package io.legado.app.lib.mobi.entities\n\ndata class PalmDocHeader(\n    val compression: Int,\n    val numTextRecords: Int,\n    val recordSize: Int,\n    val encryption: Int\n)\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/lib/mobi/entities/Ptagx.kt",
    "content": "package io.legado.app.lib.mobi.entities\n\ndata class Ptagx(\n    val tag: Int,\n    val tagValueCount: Int,\n    val valueCount: Int?,\n    val valueBytes: Int?\n)\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/lib/mobi/entities/Skeleton.kt",
    "content": "package io.legado.app.lib.mobi.entities\n\ndata class Skeleton(\n    val index: Int,\n    val name: String,\n    val numFrag: Int,\n    val offset: Int,\n    val length: Int\n)\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/lib/mobi/entities/TOC.kt",
    "content": "package io.legado.app.lib.mobi.entities\n\ndata class TOC(\n    val label: String,\n    val href: String,\n    val subitems: List<TOC>? = null\n)\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/lib/mobi/entities/TagxHeader.kt",
    "content": "package io.legado.app.lib.mobi.entities\n\ndata class TagxHeader(\n    val magic: String,\n    val length: Int,\n    val numControlBytes: Int\n)\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/lib/mobi/entities/TagxTag.kt",
    "content": "package io.legado.app.lib.mobi.entities\n\ndata class TagxTag(\n    val tag: Int,\n    val numValues: Int,\n    val bitmask: Int,\n    val controlByte: Int,\n)\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/lib/mobi/utils/BitwiseExtensions.kt",
    "content": "package io.legado.app.lib.mobi.utils\n\ninternal infix fun Byte.and(mask: Int): Int = toInt() and mask\n\ninternal infix fun Short.and(mask: Int): Int = toInt() and mask\n\ninternal infix fun Int.and(mask: Long): Long = toLong() and mask\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/lib/mobi/utils/ByteBufferExtensions.kt",
    "content": "package io.legado.app.lib.mobi.utils\n\nimport java.nio.ByteBuffer\nimport java.nio.charset.Charset\n\nfun ByteBuffer.readByteArray(offset: Int, len: Int): ByteArray {\n    position(offset)\n    val b = ByteArray(len)\n    get(b)\n    return b\n}\n\nfun ByteBuffer.readByteArray(len: Int): ByteArray {\n    val b = ByteArray(len)\n    get(b)\n    return b\n}\n\nfun ByteBuffer.readIntArray(offset: Int, len: Int): IntArray {\n    position(offset)\n    return IntArray(len) {\n        getInt()\n    }\n}\n\nfun ByteBuffer.readUInt16Array(offset: Int, len: Int): IntArray {\n    position(offset)\n    return IntArray(len) {\n        getShort() and 0xFFFF\n    }\n}\n\nfun ByteBuffer.readString(len: Int): String {\n    return String(readByteArray(len))\n}\n\nfun ByteBuffer.readString(offset: Int, len: Int): String {\n    return String(readByteArray(offset, len))\n}\n\nfun ByteBuffer.readString(offset: Int, len: Int, charset: Charset): String {\n    return String(readByteArray(offset, len), charset)\n}\n\nfun ByteBuffer.readUInt8(offset: Int): Int {\n    position(offset)\n    return get() and 0xFF\n}\n\nfun ByteBuffer.readUInt8(): Int {\n    return get() and 0xFF\n}\n\nfun ByteBuffer.readUInt16(offset: Int): Int {\n    position(offset)\n    return getShort() and 0xFFFF\n}\n\nfun ByteBuffer.readUInt32(offset: Int): Int {\n    position(offset)\n    return getInt()\n}\n\nfun ByteBuffer.readUInt32(): Int {\n    return getInt()\n}\n\nfun ByteBuffer.readUInt64(offset: Int): Long {\n    position(offset)\n    return getLong()\n}\n\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/lib/permission/OnErrorCallback.kt",
    "content": "package io.legado.app.lib.permission\n\ninterface OnErrorCallback {\n\n    fun onError(e: Exception)\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/lib/permission/OnPermissionsDeniedCallback.kt",
    "content": "package io.legado.app.lib.permission\n\ninterface OnPermissionsDeniedCallback {\n\n    fun onPermissionsDenied(deniedPermissions: Array<String>)\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/lib/permission/OnPermissionsGrantedCallback.kt",
    "content": "package io.legado.app.lib.permission\n\ninterface OnPermissionsGrantedCallback {\n\n    fun onPermissionsGranted()\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/lib/permission/OnPermissionsResultCallback.kt",
    "content": "package io.legado.app.lib.permission\n\ninterface OnPermissionsResultCallback {\n\n    fun onPermissionsGranted()\n\n    fun onPermissionsDenied(deniedPermissions: Array<String>?)\n\n    fun onError(e: Exception)\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/lib/permission/OnRequestPermissionsResultCallback.kt",
    "content": "package io.legado.app.lib.permission\n\ninterface OnRequestPermissionsResultCallback {\n\n    fun onRequestPermissionsResult(permissions: Array<String>, grantResults: IntArray)\n\n    fun onSettingActivityResult()\n\n    fun onError(e: Exception)\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/lib/permission/PermissionActivity.kt",
    "content": "package io.legado.app.lib.permission\n\nimport android.annotation.SuppressLint\nimport android.content.Intent\nimport android.content.pm.PackageManager\nimport android.net.Uri\nimport android.os.Build\nimport android.os.Bundle\nimport android.provider.Settings\nimport androidx.activity.addCallback\nimport androidx.activity.result.contract.ActivityResultContracts\nimport androidx.appcompat.app.AlertDialog\nimport androidx.appcompat.app.AppCompatActivity\nimport androidx.lifecycle.lifecycleScope\nimport io.legado.app.R\nimport io.legado.app.constant.AppLog\nimport io.legado.app.exception.NoStackTraceException\nimport io.legado.app.utils.registerForActivityResult\nimport io.legado.app.utils.toastOnUi\nimport kotlinx.coroutines.launch\n\nclass PermissionActivity : AppCompatActivity() {\n\n    private var rationaleDialog: AlertDialog? = null\n\n    private val settingActivityResult =\n        registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {\n            onRequestPermissionFinish()\n        }\n    private val settingActivityResultAwait =\n        registerForActivityResult(ActivityResultContracts.StartActivityForResult())\n    private val requestPermissionResult =\n        registerForActivityResult(ActivityResultContracts.RequestPermission())\n    private val requestPermissionsResult =\n        registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions())\n\n    @SuppressLint(\"BatteryLife\")\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n        val rationale = intent.getStringExtra(KEY_RATIONALE)\n        val requestCode = intent.getIntExtra(KEY_INPUT_PERMISSIONS_CODE, 1000)\n        val permissions = intent.getStringArrayExtra(KEY_INPUT_PERMISSIONS)!!\n        when (intent.getIntExtra(KEY_INPUT_REQUEST_TYPE, Request.TYPE_REQUEST_PERMISSION)) {\n            //权限请求\n            Request.TYPE_REQUEST_PERMISSION -> showSettingDialog(permissions, rationale) {\n                lifecycleScope.launch {\n                    try {\n                        val result = requestPermissionsResult.launch(permissions)\n                        if (result.values.all { it }) {\n                            onRequestPermissionFinish()\n                        } else {\n                            openSettingsActivity()\n                        }\n                    } catch (e: Exception) {\n                        AppLog.put(\"请求权限出错\\n$e\", e, true)\n                        RequestPlugins.sRequestCallback?.onError(e)\n                        finish()\n                    }\n                }\n            }\n            //跳转到设置界面\n            Request.TYPE_REQUEST_SETTING -> showSettingDialog(permissions, rationale) {\n                openSettingsActivity()\n            }\n            //所有文件的管理权限\n            Request.TYPE_MANAGE_ALL_FILES_ACCESS -> showSettingDialog(permissions, rationale) {\n                try {\n                    if (Permissions.isManageExternalStorage()) {\n                        val settingIntent =\n                            Intent(\n                                Settings.ACTION_MANAGE_APP_ALL_FILES_ACCESS_PERMISSION,\n                                Uri.parse(\"package:$packageName\")\n                            )\n                        settingActivityResult.launch(settingIntent)\n                    } else {\n                        throw NoStackTraceException(\"no MANAGE_ALL_FILES_ACCESS_PERMISSION\")\n                    }\n                } catch (e: Exception) {\n                    AppLog.put(\"请求所有文件的管理权限出错\\n$e\", e, true)\n                    RequestPlugins.sRequestCallback?.onError(e)\n                    finish()\n                }\n            }\n\n            Request.TYPE_REQUEST_NOTIFICATIONS -> showSettingDialog(permissions, rationale) {\n                lifecycleScope.launch {\n                    try {\n                        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU\n                            && requestPermissionResult.launch(Permissions.POST_NOTIFICATIONS)\n                        ) {\n                            onRequestPermissionFinish()\n                        } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {\n                            //这种方案适用于 API 26, 即8.0（含8.0）以上可以用\n                            val intent = Intent()\n                            intent.action = Settings.ACTION_APP_NOTIFICATION_SETTINGS\n                            intent.putExtra(Settings.EXTRA_APP_PACKAGE, packageName)\n                            intent.putExtra(Settings.EXTRA_CHANNEL_ID, applicationInfo.uid)\n                            settingActivityResult.launch(intent)\n                        } else {\n                            openSettingsActivity()\n                        }\n                    } catch (e: Exception) {\n                        AppLog.put(\"请求通知权限出错\\n$e\", e, true)\n                        RequestPlugins.sRequestCallback?.onError(e)\n                        finish()\n                    }\n                }\n            }\n\n            Request.TYPE_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS -> showSettingDialog(\n                permissions, rationale\n            ) {\n                lifecycleScope.launch {\n                    try {\n                        val intent = Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS)\n                        intent.setData(Uri.parse(\"package:$packageName\"))\n                        val className =\n                            \"com.android.settings.fuelgauge.RequestIgnoreBatteryOptimizations\"\n                        val activities = packageManager.queryIntentActivities(\n                            intent,\n                            PackageManager.MATCH_DEFAULT_ONLY\n                        )\n                        if (activities.any { it.activityInfo.name == className }) {\n                            val component = intent.resolveActivity(packageManager)\n                            if (component.className != className) {\n                                intent.setClassName(\"com.android.settings\", className)\n                                settingActivityResultAwait.launch(intent)\n                            }\n                        }\n                        intent.component = null\n                        settingActivityResult.launch(intent)\n                    } catch (e: Exception) {\n                        AppLog.put(\"请求后台权限出错\\n$e\", e, true)\n                        RequestPlugins.sRequestCallback?.onError(e)\n                        finish()\n                    }\n                }\n            }\n        }\n        onBackPressedDispatcher.addCallback(this) {\n\n        }\n    }\n\n    private fun onRequestPermissionFinish() {\n        RequestPlugins.sRequestCallback?.onSettingActivityResult()\n        finish()\n    }\n\n    private fun openSettingsActivity() {\n        try {\n            val settingIntent = Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS)\n            settingIntent.data = Uri.fromParts(\"package\", packageName, null)\n            settingActivityResult.launch(settingIntent)\n        } catch (e: Exception) {\n            toastOnUi(R.string.tip_cannot_jump_setting_page)\n            RequestPlugins.sRequestCallback?.onError(e)\n            finish()\n        }\n    }\n\n    override fun onRequestPermissionsResult(\n        requestCode: Int,\n        permissions: Array<String>,\n        grantResults: IntArray\n    ) {\n        super.onRequestPermissionsResult(requestCode, permissions, grantResults)\n        RequestPlugins.sRequestCallback?.onRequestPermissionsResult(\n            permissions,\n            grantResults\n        )\n        finish()\n    }\n\n\n    override fun startActivity(intent: Intent) {\n        super.startActivity(intent)\n        @Suppress(\"DEPRECATION\")\n        overridePendingTransition(0, 0)\n    }\n\n    override fun finish() {\n        super.finish()\n        @Suppress(\"DEPRECATION\")\n        overridePendingTransition(0, 0)\n    }\n\n    private fun showSettingDialog(\n        permissions: Array<String>,\n        rationale: CharSequence?,\n        onOk: () -> Unit\n    ) {\n        rationaleDialog?.dismiss()\n        if (rationale.isNullOrEmpty()) {\n            finish()\n            return\n        }\n        rationaleDialog = AlertDialog.Builder(this)\n            .setTitle(R.string.dialog_title)\n            .setMessage(rationale)\n            .setPositiveButton(R.string.dialog_setting) { _, _ ->\n                onOk.invoke()\n            }\n            .setNegativeButton(R.string.dialog_cancel) { _, _ ->\n                RequestPlugins.sRequestCallback?.onRequestPermissionsResult(\n                    permissions,\n                    IntArray(0)\n                )\n                finish()\n            }.setOnCancelListener {\n                RequestPlugins.sRequestCallback?.onRequestPermissionsResult(\n                    permissions,\n                    IntArray(0)\n                )\n                finish()\n            }\n            .show()\n    }\n\n    companion object {\n\n        const val KEY_RATIONALE = \"KEY_RATIONALE\"\n        const val KEY_INPUT_REQUEST_TYPE = \"KEY_INPUT_REQUEST_TYPE\"\n        const val KEY_INPUT_PERMISSIONS_CODE = \"KEY_INPUT_PERMISSIONS_CODE\"\n        const val KEY_INPUT_PERMISSIONS = \"KEY_INPUT_PERMISSIONS\"\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/lib/permission/Permissions.kt",
    "content": "package io.legado.app.lib.permission\n\nimport android.os.Build\n\n@Suppress(\"unused\")\nobject Permissions {\n\n    const val POST_NOTIFICATIONS = \"android.permission.POST_NOTIFICATIONS\"\n\n    const val READ_CALENDAR = \"android.permission.READ_CALENDAR\"\n    const val WRITE_CALENDAR = \"android.permission.WRITE_CALENDAR\"\n\n    const val CAMERA = \"android.permission.CAMERA\"\n\n    const val READ_CONTACTS = \"android.permission.READ_CONTACTS\"\n    const val WRITE_CONTACTS = \"android.permission.WRITE_CONTACTS\"\n    const val GET_ACCOUNTS = \"android.permission.GET_ACCOUNTS\"\n\n    const val ACCESS_FINE_LOCATION = \"android.permission.ACCESS_FINE_LOCATION\"\n    const val ACCESS_COARSE_LOCATION = \"android.permission.ACCESS_COARSE_LOCATION\"\n\n    const val RECORD_AUDIO = \"android.permission.RECORD_AUDIO\"\n\n    const val READ_PHONE_STATE = \"android.permission.READ_PHONE_STATE\"\n    const val CALL_PHONE = \"android.permission.CALL_PHONE\"\n    const val READ_CALL_LOG = \"android.permission.READ_CALL_LOG\"\n    const val WRITE_CALL_LOG = \"android.permission.WRITE_CALL_LOG\"\n    const val ADD_VOICEMAIL = \"com.android.voicemail.permission.ADD_VOICEMAIL\"\n    const val USE_SIP = \"android.permission.USE_SIP\"\n    const val PROCESS_OUTGOING_CALLS = \"android.permission.PROCESS_OUTGOING_CALLS\"\n\n    const val BODY_SENSORS = \"android.permission.BODY_SENSORS\"\n\n    const val SEND_SMS = \"android.permission.SEND_SMS\"\n    const val RECEIVE_SMS = \"android.permission.RECEIVE_SMS\"\n    const val READ_SMS = \"android.permission.READ_SMS\"\n    const val RECEIVE_WAP_PUSH = \"android.permission.RECEIVE_WAP_PUSH\"\n    const val RECEIVE_MMS = \"android.permission.RECEIVE_MMS\"\n\n    const val READ_EXTERNAL_STORAGE = \"android.permission.READ_EXTERNAL_STORAGE\"\n    const val WRITE_EXTERNAL_STORAGE = \"android.permission.WRITE_EXTERNAL_STORAGE\"\n    const val MANAGE_EXTERNAL_STORAGE = \"android.permission.MANAGE_EXTERNAL_STORAGE\"\n\n    const val ACCESS_MEDIA_LOCATION = \"android.permission.ACCESS_MEDIA_LOCATION\"\n\n    const val REQUEST_IGNORE_BATTERY_OPTIMIZATIONS =\n        \"android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS\"\n\n    object Group {\n        val STORAGE = if (isManageExternalStorage()) {\n            arrayOf(MANAGE_EXTERNAL_STORAGE)\n        } else {\n            arrayOf(READ_EXTERNAL_STORAGE, WRITE_EXTERNAL_STORAGE)\n        }\n\n        val CAMERA = arrayOf(Permissions.CAMERA)\n\n        val CALENDAR = arrayOf(READ_CALENDAR, WRITE_CALENDAR)\n\n        val CONTACTS = arrayOf(READ_CONTACTS, WRITE_CONTACTS, GET_ACCOUNTS)\n\n        val LOCATION = arrayOf(ACCESS_FINE_LOCATION, ACCESS_COARSE_LOCATION)\n\n        val MICROPHONE = arrayOf(RECORD_AUDIO)\n\n        val PHONE = arrayOf(\n            READ_PHONE_STATE,\n            CALL_PHONE,\n            READ_CALL_LOG,\n            WRITE_CALL_LOG,\n            ADD_VOICEMAIL,\n            USE_SIP,\n            PROCESS_OUTGOING_CALLS\n        )\n\n        val SENSORS = arrayOf(BODY_SENSORS)\n\n        val SMS = arrayOf(\n            SEND_SMS,\n            RECEIVE_SMS,\n            READ_SMS,\n            RECEIVE_WAP_PUSH,\n            RECEIVE_MMS\n        )\n    }\n\n    fun isManageExternalStorage(): Boolean {\n        return Build.VERSION.SDK_INT >= Build.VERSION_CODES.R\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/lib/permission/PermissionsCompat.kt",
    "content": "package io.legado.app.lib.permission\n\nimport androidx.annotation.StringRes\n\n@Suppress(\"unused\")\nclass PermissionsCompat private constructor() {\n\n    private var request: Request? = null\n\n    fun request() {\n        RequestManager.pushRequest(request)\n    }\n\n    class Builder {\n        private val request: Request = Request()\n\n        fun addPermissions(vararg permissions: String): Builder {\n            request.addPermissions(*permissions)\n            return this\n        }\n\n        fun onGranted(callback: () -> Unit): Builder {\n            request.setOnGrantedCallback(object : OnPermissionsGrantedCallback {\n                override fun onPermissionsGranted() {\n                    callback()\n                }\n            })\n            return this\n        }\n\n        fun onDenied(callback: (deniedPermissions: Array<String>) -> Unit): Builder {\n            request.setOnDeniedCallback(object : OnPermissionsDeniedCallback {\n                override fun onPermissionsDenied(deniedPermissions: Array<String>) {\n                    callback(deniedPermissions)\n                }\n            })\n            return this\n        }\n\n        fun onError(callback: (e: Exception) -> Unit): Builder {\n            request.setOnErrorCallBack(object : OnErrorCallback{\n                override fun onError(e: Exception) {\n                    callback(e)\n                }\n            })\n            return this\n        }\n\n        fun rationale(rationale: CharSequence): Builder {\n            request.setRationale(rationale)\n            return this\n        }\n\n        fun rationale(@StringRes resId: Int): Builder {\n            request.setRationale(resId)\n            return this\n        }\n\n        fun build(): PermissionsCompat {\n            val compat = PermissionsCompat()\n            compat.request = request\n            return compat\n        }\n\n        fun request(): PermissionsCompat {\n            val compat = build()\n            compat.request()\n            return compat\n        }\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/lib/permission/Request.kt",
    "content": "package io.legado.app.lib.permission\n\nimport android.annotation.SuppressLint\nimport android.content.pm.PackageManager\nimport android.os.Build\nimport android.os.Environment\nimport androidx.annotation.StringRes\nimport androidx.core.app.NotificationManagerCompat\nimport androidx.core.content.ContextCompat\nimport io.legado.app.utils.startActivity\nimport splitties.init.appCtx\nimport splitties.systemservices.powerManager\n\n@Suppress(\"MemberVisibilityCanBePrivate\")\ninternal class Request : OnRequestPermissionsResultCallback {\n\n    internal val requestTime: Long = System.currentTimeMillis()\n    private var requestCode: Int = TYPE_REQUEST_PERMISSION\n    private var permissions: ArrayList<String> = ArrayList()\n    private var grantedCallback: OnPermissionsGrantedCallback? = null\n    private var deniedCallback: OnPermissionsDeniedCallback? = null\n    private var errorCallback: OnErrorCallback? = null\n    private var rationale: CharSequence? = null\n\n    private val deniedPermissions: Array<String>?\n        get() {\n            return getDeniedPermissions(this.permissions.toTypedArray())\n        }\n\n    fun addPermissions(vararg permissions: String) {\n        this.permissions.addAll(listOf(*permissions))\n    }\n\n    fun setOnGrantedCallback(callback: OnPermissionsGrantedCallback) {\n        grantedCallback = callback\n    }\n\n    fun setOnDeniedCallback(callback: OnPermissionsDeniedCallback) {\n        deniedCallback = callback\n    }\n\n    fun setOnErrorCallBack(callback: OnErrorCallback) {\n        errorCallback = callback\n    }\n\n    fun setRationale(@StringRes resId: Int) {\n        rationale = appCtx.getString(resId)\n    }\n\n    fun setRationale(rationale: CharSequence) {\n        this.rationale = rationale\n    }\n\n    @SuppressLint(\"ObsoleteSdkInt\")\n    fun start() {\n        RequestPlugins.setOnRequestPermissionsCallback(this)\n\n        val deniedPermissions = deniedPermissions\n        val rationale = this.rationale\n        if (deniedPermissions == null) {\n            onPermissionsGranted()\n            return\n        }\n        if (rationale == null) {\n            onPermissionsDenied(deniedPermissions)\n            return\n        }\n        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {\n            toSetting()\n        } else {\n            if (deniedPermissions.contains(Permissions.MANAGE_EXTERNAL_STORAGE)) {\n                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {\n                    toManageFileSetting(deniedPermissions)\n                }\n            } else if (deniedPermissions.contains(Permissions.POST_NOTIFICATIONS)) {\n                toNotificationSetting(deniedPermissions)\n            } else if (deniedPermissions.contains(Permissions.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS)) {\n                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {\n                    toIgnoreBatterySetting(deniedPermissions)\n                }\n            } else if (deniedPermissions.isNotEmpty()) {\n                appCtx.startActivity<PermissionActivity> {\n                    putExtra(PermissionActivity.KEY_RATIONALE, rationale)\n                    putExtra(PermissionActivity.KEY_INPUT_REQUEST_TYPE, TYPE_REQUEST_PERMISSION)\n                    putExtra(PermissionActivity.KEY_INPUT_PERMISSIONS_CODE, requestCode)\n                    putExtra(PermissionActivity.KEY_INPUT_PERMISSIONS, deniedPermissions)\n                }\n            }\n        }\n    }\n\n    fun clear() {\n        grantedCallback = null\n        deniedCallback = null\n    }\n\n    fun getDeniedPermissions(permissions: Array<String>): Array<String>? {\n        val deniedPermissionList = ArrayList<String>()\n        for (permission in permissions) {\n            when (permission) {\n                Permissions.POST_NOTIFICATIONS -> {\n                    if (!NotificationManagerCompat.from(appCtx).areNotificationsEnabled()) {\n                        deniedPermissionList.add(permission)\n                    }\n                }\n\n                Permissions.MANAGE_EXTERNAL_STORAGE -> {\n                    if (Permissions.isManageExternalStorage()) {\n                        if (!Environment.isExternalStorageManager()) {\n                            deniedPermissionList.add(permission)\n                        }\n                    }\n                }\n\n                Permissions.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS -> {\n                    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {\n                        if (!powerManager.isIgnoringBatteryOptimizations(appCtx.packageName)) {\n                            deniedPermissionList.add(permission)\n                        }\n                    }\n                }\n\n                else -> {\n                    if (\n                        ContextCompat.checkSelfPermission(appCtx, permission)\n                        != PackageManager.PERMISSION_GRANTED\n                    ) {\n                        deniedPermissionList.add(permission)\n                    }\n                }\n            }\n        }\n        val size = deniedPermissionList.size\n        if (size > 0) {\n            return deniedPermissionList.toTypedArray()\n        }\n        return null\n    }\n\n    private fun onPermissionsGranted() {\n        try {\n            grantedCallback?.onPermissionsGranted()\n        } catch (ignore: Exception) {\n        }\n\n        RequestPlugins.sResultCallback?.onPermissionsGranted()\n    }\n\n    private fun onPermissionsDenied(deniedPermissions: Array<String>) {\n        try {\n            deniedCallback?.onPermissionsDenied(deniedPermissions)\n        } catch (ignore: Exception) {\n        }\n\n        RequestPlugins.sResultCallback?.onPermissionsDenied(deniedPermissions)\n    }\n\n    private fun toSetting() {\n        appCtx.startActivity<PermissionActivity> {\n            putExtra(PermissionActivity.KEY_RATIONALE, rationale)\n            putExtra(PermissionActivity.KEY_INPUT_REQUEST_TYPE, TYPE_REQUEST_SETTING)\n        }\n    }\n\n    private fun toManageFileSetting(deniedPermissions: Array<String>) {\n        appCtx.startActivity<PermissionActivity> {\n            putExtra(PermissionActivity.KEY_RATIONALE, rationale)\n            putExtra(PermissionActivity.KEY_INPUT_REQUEST_TYPE, TYPE_MANAGE_ALL_FILES_ACCESS)\n            putExtra(PermissionActivity.KEY_INPUT_PERMISSIONS_CODE, requestCode)\n            putExtra(PermissionActivity.KEY_INPUT_PERMISSIONS, deniedPermissions)\n        }\n    }\n\n    private fun toNotificationSetting(deniedPermissions: Array<String>) {\n        appCtx.startActivity<PermissionActivity> {\n            putExtra(PermissionActivity.KEY_RATIONALE, rationale)\n            putExtra(PermissionActivity.KEY_INPUT_REQUEST_TYPE, TYPE_REQUEST_NOTIFICATIONS)\n            putExtra(PermissionActivity.KEY_INPUT_PERMISSIONS_CODE, requestCode)\n            putExtra(PermissionActivity.KEY_INPUT_PERMISSIONS, deniedPermissions)\n        }\n    }\n\n    private fun toIgnoreBatterySetting(deniedPermissions: Array<String>) {\n        appCtx.startActivity<PermissionActivity> {\n            putExtra(PermissionActivity.KEY_RATIONALE, rationale)\n            putExtra(\n                PermissionActivity.KEY_INPUT_REQUEST_TYPE,\n                TYPE_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS\n            )\n            putExtra(PermissionActivity.KEY_INPUT_PERMISSIONS_CODE, requestCode)\n            putExtra(PermissionActivity.KEY_INPUT_PERMISSIONS, deniedPermissions)\n        }\n    }\n\n    override fun onRequestPermissionsResult(\n        permissions: Array<String>,\n        grantResults: IntArray\n    ) {\n        val deniedPermissions = getDeniedPermissions(permissions)\n        if (deniedPermissions != null) {\n            onPermissionsDenied(deniedPermissions)\n        } else {\n            onPermissionsGranted()\n        }\n    }\n\n    override fun onSettingActivityResult() {\n        val deniedPermissions = deniedPermissions\n        if (deniedPermissions == null) {\n            onPermissionsGranted()\n        } else {\n            onPermissionsDenied(deniedPermissions)\n        }\n    }\n\n    override fun onError(e: Exception) {\n        errorCallback?.onError(e)\n        RequestPlugins.sResultCallback?.onError(e)\n    }\n\n    companion object {\n        const val TYPE_REQUEST_PERMISSION = 1\n        const val TYPE_REQUEST_SETTING = 2\n        const val TYPE_MANAGE_ALL_FILES_ACCESS = 3\n        const val TYPE_REQUEST_NOTIFICATIONS = 4\n        const val TYPE_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS = 5\n    }\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/lib/permission/RequestManager.kt",
    "content": "package io.legado.app.lib.permission\n\nimport android.os.Handler\nimport android.os.Looper\nimport java.util.*\n\ninternal object RequestManager : OnPermissionsResultCallback {\n\n    private var requests: Stack<Request>? = null\n    private var request: Request? = null\n\n    private val handler = Handler(Looper.getMainLooper())\n\n    private val requestRunnable = Runnable {\n        request?.start()\n    }\n\n    private val isCurrentRequestInvalid: Boolean\n        get() = request?.let { System.currentTimeMillis() - it.requestTime > 5 * 1000L } ?: true\n\n    init {\n        RequestPlugins.setOnPermissionsResultCallback(this)\n    }\n\n    fun pushRequest(request: Request?) {\n        if (request == null) return\n\n        if (requests == null) {\n            requests = Stack()\n        }\n\n        requests?.let {\n            val index = it.indexOf(request)\n            if (index >= 0) {\n                val to = it.size - 1\n                if (index != to) {\n                    @Suppress(\"NULLABILITY_MISMATCH_BASED_ON_JAVA_ANNOTATIONS\")\n                    Collections.swap(requests, index, to)\n                }\n            } else {\n                it.push(request)\n            }\n\n            if (!it.empty() && isCurrentRequestInvalid) {\n                this.request = it.pop()\n                handler.post(requestRunnable)\n            }\n        }\n    }\n\n    private fun startNextRequest() {\n        request?.clear()\n        request = null\n\n        requests?.let {\n            request = if (it.empty()) null else it.pop()\n            request?.let { handler.post(requestRunnable) }\n        }\n    }\n\n    override fun onPermissionsGranted() {\n        startNextRequest()\n    }\n\n    override fun onPermissionsDenied(deniedPermissions: Array<String>?) {\n        startNextRequest()\n    }\n\n    override fun onError(e: Exception) {\n        startNextRequest()\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/lib/permission/RequestPlugins.kt",
    "content": "package io.legado.app.lib.permission\n\ninternal object RequestPlugins {\n\n    @Volatile\n    var sRequestCallback: OnRequestPermissionsResultCallback? = null\n\n    @Volatile\n    var sResultCallback: OnPermissionsResultCallback? = null\n\n    fun setOnRequestPermissionsCallback(callback: OnRequestPermissionsResultCallback) {\n        sRequestCallback = callback\n    }\n\n    fun setOnPermissionsResultCallback(callback: OnPermissionsResultCallback) {\n        sResultCallback = callback\n    }\n\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/lib/prefs/ColorPreference.kt",
    "content": "package io.legado.app.lib.prefs\n\nimport android.content.Context\nimport android.content.ContextWrapper\nimport android.content.res.TypedArray\nimport android.graphics.Color\nimport android.os.Bundle\nimport android.util.AttributeSet\nimport androidx.annotation.ColorInt\nimport androidx.annotation.StringRes\nimport androidx.appcompat.app.AlertDialog\nimport androidx.fragment.app.FragmentActivity\nimport androidx.preference.PreferenceViewHolder\nimport com.jaredrummler.android.colorpicker.*\nimport io.legado.app.utils.ColorUtils\nimport io.legado.app.utils.applyTint\n\n@Suppress(\"MemberVisibilityCanBePrivate\", \"unused\")\nclass ColorPreference(context: Context, attrs: AttributeSet) : Preference(context, attrs),\n    ColorPickerDialogListener {\n\n    var onSaveColor: ((color: Int) -> Boolean)? = null\n\n    private val sizeNormal = 0\n    private val sizeLarge = 1\n\n    private var onShowDialogListener: OnShowDialogListener? = null\n    private var mColor = Color.BLACK\n    private var showDialog: Boolean = false\n\n    @ColorPickerDialog.DialogType\n    private var dialogType: Int = 0\n    private var colorShape: Int = 0\n    private var allowPresets: Boolean = false\n    private var allowCustom: Boolean = false\n    private var showAlphaSlider: Boolean = false\n    private var showColorShades: Boolean = false\n    private var previewSize: Int = 0\n    private var presets: IntArray? = null\n    private var dialogTitle: Int = 0\n\n    init {\n        isPersistent = true\n        layoutResource = io.legado.app.R.layout.view_preference\n\n        val a = context.obtainStyledAttributes(attrs, R.styleable.ColorPreference)\n        showDialog = a.getBoolean(R.styleable.ColorPreference_cpv_showDialog, true)\n\n        dialogType =\n            a.getInt(R.styleable.ColorPreference_cpv_dialogType, ColorPickerDialog.TYPE_PRESETS)\n        colorShape = a.getInt(R.styleable.ColorPreference_cpv_colorShape, ColorShape.CIRCLE)\n        allowPresets = a.getBoolean(R.styleable.ColorPreference_cpv_allowPresets, true)\n        allowCustom = a.getBoolean(R.styleable.ColorPreference_cpv_allowCustom, true)\n        showAlphaSlider = a.getBoolean(R.styleable.ColorPreference_cpv_showAlphaSlider, false)\n        showColorShades = a.getBoolean(R.styleable.ColorPreference_cpv_showColorShades, true)\n        previewSize = a.getInt(R.styleable.ColorPreference_cpv_previewSize, sizeNormal)\n        val presetsResId = a.getResourceId(R.styleable.ColorPreference_cpv_colorPresets, 0)\n        dialogTitle =\n            a.getResourceId(R.styleable.ColorPreference_cpv_dialogTitle, R.string.cpv_default_title)\n        presets = if (presetsResId != 0) {\n            context.resources.getIntArray(presetsResId)\n        } else {\n            ColorPickerDialog.MATERIAL_COLORS\n        }\n        widgetLayoutResource = if (colorShape == ColorShape.CIRCLE) {\n            if (previewSize == sizeLarge) R.layout.cpv_preference_circle_large else R.layout.cpv_preference_circle\n        } else {\n            if (previewSize == sizeLarge) R.layout.cpv_preference_square_large else R.layout.cpv_preference_square\n        }\n        a.recycle()\n    }\n\n    override fun onClick() {\n        super.onClick()\n        if (onShowDialogListener != null) {\n            onShowDialogListener!!.onShowColorPickerDialog(title as String, mColor)\n        } else if (showDialog) {\n            val dialog = ColorPickerDialogCompat.newBuilder()\n                .setDialogType(dialogType)\n                .setDialogTitle(dialogTitle)\n                .setColorShape(colorShape)\n                .setPresets(presets!!)\n                .setAllowPresets(allowPresets)\n                .setAllowCustom(allowCustom)\n                .setShowAlphaSlider(showAlphaSlider)\n                .setShowColorShades(showColorShades)\n                .setColor(mColor)\n                .create()\n            dialog.setColorPickerDialogListener(this)\n            getActivity().supportFragmentManager\n                .beginTransaction()\n                .add(dialog, getFragmentTag())\n                .commitAllowingStateLoss()\n        }\n    }\n\n    private fun getActivity(): FragmentActivity {\n        val context = context\n        if (context is FragmentActivity) {\n            return context\n        } else if (context is ContextWrapper) {\n            val baseContext = context.baseContext\n            if (baseContext is FragmentActivity) {\n                return baseContext\n            }\n        }\n        throw IllegalStateException(\"Error getting activity from context\")\n    }\n\n    override fun onAttached() {\n        super.onAttached()\n        if (showDialog) {\n            val fragment =\n                getActivity().supportFragmentManager.findFragmentByTag(getFragmentTag()) as ColorPickerDialog?\n            fragment?.setColorPickerDialogListener(this)\n        }\n    }\n\n    override fun onBindView(holder: PreferenceViewHolder) {\n        val v = bindView<ColorPanelView>(\n            context, holder, icon, title, summary, widgetLayoutResource,\n            R.id.cpv_preference_preview_color_panel, 30, 30\n        )\n        if (v is ColorPanelView) {\n            v.color = mColor\n        }\n    }\n\n    override fun onSetInitialValue(defaultValue: Any?) {\n        super.onSetInitialValue(defaultValue)\n        if (defaultValue is Int) {\n            mColor = if (!showAlphaSlider) ColorUtils.withAlpha(defaultValue, 1f) else defaultValue\n            persistInt(mColor)\n        } else {\n            mColor = getPersistedInt(-0x1000000)\n        }\n    }\n\n    override fun onGetDefaultValue(a: TypedArray, index: Int): Any {\n        return a.getInteger(index, Color.BLACK)\n    }\n\n    override fun onColorSelected(dialogId: Int, @ColorInt color: Int) {\n        //返回值为true时说明已经处理过,不再处理\n        if (onSaveColor?.invoke(color) == true) {\n            return\n        }\n        saveValue(color)\n    }\n\n    override fun onDialogDismissed(dialogId: Int) {\n        // no-op\n    }\n\n    /**\n     * Set the new color\n     *\n     * @param color The newly selected color\n     */\n    fun saveValue(@ColorInt color: Int) {\n        mColor = if (showAlphaSlider) color else ColorUtils.withAlpha(color, 1f)\n        persistInt(mColor)\n        notifyChanged()\n        callChangeListener(color)\n    }\n\n    /**\n     * Get the colors that will be shown in the [ColorPickerDialog].\n     *\n     * @return An array of color ints\n     */\n    fun getPresets(): IntArray? {\n        return presets\n    }\n\n    /**\n     * Set the colors shown in the [ColorPickerDialog].\n     *\n     * @param presets An array of color ints\n     */\n    fun setPresets(presets: IntArray) {\n        this.presets = presets\n    }\n\n    /**\n     * The listener used for showing the [ColorPickerDialog].\n     * Call [.saveValue] after the user chooses a color.\n     * If this is set then it is up to you to show the dialog.\n     *\n     * @param listener The listener to show the dialog\n     */\n    fun setOnShowDialogListener(listener: OnShowDialogListener) {\n        onShowDialogListener = listener\n    }\n\n    /**\n     * The tag used for the [ColorPickerDialog].\n     *\n     * @return The tag\n     */\n    fun getFragmentTag(): String {\n        return \"color_$key\"\n    }\n\n    interface OnShowDialogListener {\n\n        fun onShowColorPickerDialog(title: String, currentColor: Int)\n    }\n\n\n    internal class ColorPickerDialogCompat : ColorPickerDialog() {\n\n        override fun onStart() {\n            super.onStart()\n            val alertDialog = dialog as? AlertDialog\n            alertDialog?.applyTint()\n        }\n\n\n        companion object {\n            fun newBuilder(): Builder {\n                return Builder()\n            }\n\n            private const val ARG_ID = \"id\"\n            private const val ARG_TYPE = \"dialogType\"\n            private const val ARG_COLOR = \"color\"\n            private const val ARG_ALPHA = \"alpha\"\n            private const val ARG_PRESETS = \"presets\"\n            private const val ARG_ALLOW_PRESETS = \"allowPresets\"\n            private const val ARG_ALLOW_CUSTOM = \"allowCustom\"\n            private const val ARG_DIALOG_TITLE = \"dialogTitle\"\n            private const val ARG_SHOW_COLOR_SHADES = \"showColorShades\"\n            private const val ARG_COLOR_SHAPE = \"colorShape\"\n            private const val ARG_PRESETS_BUTTON_TEXT = \"presetsButtonText\"\n            private const val ARG_CUSTOM_BUTTON_TEXT = \"customButtonText\"\n            private const val ARG_SELECTED_BUTTON_TEXT = \"selectedButtonText\"\n        }\n\n        class Builder internal constructor() {\n\n            internal var colorPickerDialogListener: ColorPickerDialogListener? = null\n\n            @StringRes\n            internal var dialogTitle = R.string.cpv_default_title\n\n            @StringRes\n            internal var presetsButtonText = R.string.cpv_presets\n\n            @StringRes\n            internal var customButtonText = R.string.cpv_custom\n\n            @StringRes\n            internal var selectedButtonText = R.string.cpv_select\n\n            @DialogType\n            internal var dialogType = TYPE_PRESETS\n            internal var presets = MATERIAL_COLORS\n\n            @ColorInt\n            internal var color = Color.BLACK\n            internal var dialogId = 0\n            internal var showAlphaSlider = false\n            internal var allowPresets = true\n            internal var allowCustom = true\n            internal var showColorShades = true\n\n            @ColorShape\n            internal var colorShape = ColorShape.CIRCLE\n\n            /**\n             * Set the dialog title string resource id\n             *\n             * @param dialogTitle The string resource used for the dialog title\n             * @return This builder object for chaining method calls\n             */\n            fun setDialogTitle(@StringRes dialogTitle: Int): Builder {\n                this.dialogTitle = dialogTitle\n                return this\n            }\n\n            /**\n             * Set the selected button text string resource id\n             *\n             * @param selectedButtonText The string resource used for the selected button text\n             * @return This builder object for chaining method calls\n             */\n            fun setSelectedButtonText(@StringRes selectedButtonText: Int): Builder {\n                this.selectedButtonText = selectedButtonText\n                return this\n            }\n\n            /**\n             * Set the presets button text string resource id\n             *\n             * @param presetsButtonText The string resource used for the presets button text\n             * @return This builder object for chaining method calls\n             */\n            fun setPresetsButtonText(@StringRes presetsButtonText: Int): Builder {\n                this.presetsButtonText = presetsButtonText\n                return this\n            }\n\n            /**\n             * Set the custom button text string resource id\n             *\n             * @param customButtonText The string resource used for the custom button text\n             * @return This builder object for chaining method calls\n             */\n            fun setCustomButtonText(@StringRes customButtonText: Int): Builder {\n                this.customButtonText = customButtonText\n                return this\n            }\n\n            /**\n             * Set which dialog view to show.\n             *\n             * @param dialogType Either [ColorPickerDialog.TYPE_CUSTOM] or [ColorPickerDialog.TYPE_PRESETS].\n             * @return This builder object for chaining method calls\n             */\n            fun setDialogType(@DialogType dialogType: Int): Builder {\n                this.dialogType = dialogType\n                return this\n            }\n\n            /**\n             * Set the colors used for the presets\n             *\n             * @param presets An array of color ints.\n             * @return This builder object for chaining method calls\n             */\n            fun setPresets(presets: IntArray): Builder {\n                this.presets = presets\n                return this\n            }\n\n            /**\n             * Set the original color\n             *\n             * @param color The default color for the color picker\n             * @return This builder object for chaining method calls\n             */\n            fun setColor(color: Int): Builder {\n                this.color = color\n                return this\n            }\n\n            /**\n             * Set the dialog id used for callbacks\n             *\n             * @param dialogId The id that is sent back to the [ColorPickerDialogListener].\n             * @return This builder object for chaining method calls\n             */\n            fun setDialogId(dialogId: Int): Builder {\n                this.dialogId = dialogId\n                return this\n            }\n\n            /**\n             * Show the alpha slider\n             *\n             * @param showAlphaSlider `true` to show the alpha slider. Currently only supported with the [ ].\n             * @return This builder object for chaining method calls\n             */\n            fun setShowAlphaSlider(showAlphaSlider: Boolean): Builder {\n                this.showAlphaSlider = showAlphaSlider\n                return this\n            }\n\n            /**\n             * Show/Hide a neutral button to select preset colors.\n             *\n             * @param allowPresets `false` to disable showing the presets button.\n             * @return This builder object for chaining method calls\n             */\n            fun setAllowPresets(allowPresets: Boolean): Builder {\n                this.allowPresets = allowPresets\n                return this\n            }\n\n            /**\n             * Show/Hide the neutral button to select a custom color.\n             *\n             * @param allowCustom `false` to disable showing the custom button.\n             * @return This builder object for chaining method calls\n             */\n            fun setAllowCustom(allowCustom: Boolean): Builder {\n                this.allowCustom = allowCustom\n                return this\n            }\n\n            /**\n             * Show/Hide the color shades in the presets picker\n             *\n             * @param showColorShades `false` to hide the color shades.\n             * @return This builder object for chaining method calls\n             */\n            fun setShowColorShades(showColorShades: Boolean): Builder {\n                this.showColorShades = showColorShades\n                return this\n            }\n\n            /**\n             * Set the shape of the color panel view.\n             *\n             * @param colorShape Either [ColorShape.CIRCLE] or [ColorShape.SQUARE].\n             * @return This builder object for chaining method calls\n             */\n            fun setColorShape(colorShape: Int): Builder {\n                this.colorShape = colorShape\n                return this\n            }\n\n            /**\n             * Create the [ColorPickerDialog] instance.\n             *\n             * @return A new [ColorPickerDialog].\n             * @see .show\n             */\n            fun create(): ColorPickerDialog {\n                val dialog =\n                    ColorPickerDialogCompat()\n                val args = Bundle()\n                args.putInt(ARG_ID, dialogId)\n                args.putInt(ARG_TYPE, dialogType)\n                args.putInt(ARG_COLOR, color)\n                args.putIntArray(ARG_PRESETS, presets)\n                args.putBoolean(ARG_ALPHA, showAlphaSlider)\n                args.putBoolean(ARG_ALLOW_CUSTOM, allowCustom)\n                args.putBoolean(ARG_ALLOW_PRESETS, allowPresets)\n                args.putInt(ARG_DIALOG_TITLE, dialogTitle)\n                args.putBoolean(ARG_SHOW_COLOR_SHADES, showColorShades)\n                args.putInt(ARG_COLOR_SHAPE, colorShape)\n                args.putInt(ARG_PRESETS_BUTTON_TEXT, presetsButtonText)\n                args.putInt(ARG_CUSTOM_BUTTON_TEXT, customButtonText)\n                args.putInt(ARG_SELECTED_BUTTON_TEXT, selectedButtonText)\n                dialog.arguments = args\n                return dialog\n            }\n\n        }\n\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/lib/prefs/EditTextPreference.kt",
    "content": "package io.legado.app.lib.prefs\n\nimport android.content.Context\nimport android.util.AttributeSet\nimport android.widget.TextView\nimport androidx.preference.EditTextPreference.OnBindEditTextListener\nimport androidx.preference.PreferenceViewHolder\nimport io.legado.app.R\nimport io.legado.app.lib.theme.accentColor\nimport io.legado.app.utils.applyTint\n\nclass EditTextPreference(context: Context, attrs: AttributeSet) :\n    androidx.preference.EditTextPreference(context, attrs) {\n\n    private var mOnBindEditTextListener: OnBindEditTextListener? = null\n    private val onBindEditTextListener = OnBindEditTextListener { editText ->\n        editText.applyTint(context.accentColor)\n        mOnBindEditTextListener?.onBindEditText(editText)\n    }\n\n    init {\n        // isPersistent = true\n        layoutResource = R.layout.view_preference\n        super.setOnBindEditTextListener(onBindEditTextListener)\n    }\n\n    override fun onBindViewHolder(holder: PreferenceViewHolder) {\n        Preference.bindView<TextView>(context, holder, icon, title, summary, null, null)\n        super.onBindViewHolder(holder)\n    }\n\n    override fun setOnBindEditTextListener(onBindEditTextListener: OnBindEditTextListener?) {\n        mOnBindEditTextListener = onBindEditTextListener\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/lib/prefs/EditTextPreferenceDialog.kt",
    "content": "package io.legado.app.lib.prefs\n\nimport android.app.Dialog\nimport android.os.Bundle\nimport android.view.Gravity\nimport android.view.WindowManager\nimport androidx.appcompat.app.AlertDialog\nimport androidx.preference.EditTextPreferenceDialogFragmentCompat\nimport androidx.preference.PreferenceDialogFragmentCompat\nimport io.legado.app.R\nimport io.legado.app.help.config.AppConfig\nimport io.legado.app.lib.theme.accentColor\nimport io.legado.app.lib.theme.filletBackground\nimport io.legado.app.utils.dpToPx\n\nclass EditTextPreferenceDialog : EditTextPreferenceDialogFragmentCompat() {\n\n    companion object {\n\n        fun newInstance(key: String): EditTextPreferenceDialog {\n            val fragment = EditTextPreferenceDialog()\n            val b = Bundle(1)\n            b.putString(PreferenceDialogFragmentCompat.ARG_KEY, key)\n            fragment.arguments = b\n            return fragment\n        }\n\n    }\n\n    override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {\n        val dialog = super.onCreateDialog(savedInstanceState)\n        dialog.window?.setBackgroundDrawable(requireContext().filletBackground)\n        dialog.window?.decorView?.post {\n            (dialog as AlertDialog).run {\n                getButton(AlertDialog.BUTTON_NEGATIVE)?.setTextColor(accentColor)\n                getButton(AlertDialog.BUTTON_POSITIVE)?.setTextColor(accentColor)\n                getButton(AlertDialog.BUTTON_NEUTRAL)?.setTextColor(accentColor)\n            }\n        }\n        return dialog\n    }\n\n    override fun onStart() {\n        super.onStart()\n        if (AppConfig.isEInkMode) {\n            dialog?.window?.let {\n                it.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND)\n                val attr = it.attributes\n                attr.dimAmount = 0.0f\n                attr.windowAnimations = 0\n                it.attributes = attr\n                it.setBackgroundDrawableResource(R.color.transparent)\n                when (attr.gravity) {\n                    Gravity.TOP -> it.decorView.setBackgroundResource(R.drawable.bg_eink_border_bottom)\n                    Gravity.BOTTOM -> it.decorView.setBackgroundResource(R.drawable.bg_eink_border_top)\n                    else -> {\n                        val padding = 2.dpToPx();\n                        it.decorView.setPadding(padding, padding, padding, padding)\n                        it.decorView.setBackgroundResource(R.drawable.bg_eink_border_dialog)\n                    }\n                }\n            }\n        }\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/lib/prefs/IconListPreference.kt",
    "content": "package io.legado.app.lib.prefs\n\nimport android.content.Context\nimport android.content.ContextWrapper\nimport android.graphics.drawable.Drawable\nimport android.os.Bundle\nimport android.util.AttributeSet\nimport android.view.View\nimport android.view.ViewGroup\nimport android.widget.ImageView\nimport androidx.fragment.app.FragmentActivity\nimport androidx.preference.ListPreference\nimport androidx.preference.PreferenceViewHolder\nimport androidx.recyclerview.widget.LinearLayoutManager\nimport io.legado.app.R\nimport io.legado.app.base.BaseDialogFragment\nimport io.legado.app.base.adapter.ItemViewHolder\nimport io.legado.app.base.adapter.RecyclerAdapter\nimport io.legado.app.databinding.DialogRecyclerViewBinding\nimport io.legado.app.databinding.ItemIconPreferenceBinding\nimport io.legado.app.lib.theme.primaryColor\nimport io.legado.app.utils.getCompatDrawable\nimport io.legado.app.utils.setLayout\nimport io.legado.app.utils.viewbindingdelegate.viewBinding\n\n\nclass IconListPreference(context: Context, attrs: AttributeSet) : ListPreference(context, attrs) {\n    private var iconNames: Array<CharSequence>\n    private val mEntryDrawables = arrayListOf<Drawable?>()\n\n    init {\n        layoutResource = R.layout.view_preference\n        widgetLayoutResource = R.layout.view_icon\n\n        val a = context.theme.obtainStyledAttributes(attrs, R.styleable.IconListPreference, 0, 0)\n\n        iconNames = try {\n            a.getTextArray(R.styleable.IconListPreference_icons)\n        } finally {\n            a.recycle()\n        }\n\n        for (iconName in iconNames) {\n            val resId = context.resources\n                .getIdentifier(iconName.toString(), \"mipmap\", context.packageName)\n            var d: Drawable? = null\n            kotlin.runCatching {\n                d = context.getCompatDrawable(resId)\n            }\n            mEntryDrawables.add(d)\n        }\n    }\n\n    override fun onBindViewHolder(holder: PreferenceViewHolder) {\n        super.onBindViewHolder(holder)\n        val v = Preference.bindView<ImageView>(\n            context,\n            holder,\n            icon,\n            title,\n            summary,\n            widgetLayoutResource,\n            R.id.preview,\n            50,\n            50\n        )\n        if (v is ImageView) {\n            val selectedIndex = findIndexOfValue(value)\n            if (selectedIndex >= 0) {\n                val drawable = mEntryDrawables[selectedIndex]\n                v.setImageDrawable(drawable)\n            }\n        }\n    }\n\n    override fun onClick() {\n        getActivity()?.let {\n            val dialog = IconDialog().apply {\n                val args = Bundle()\n                args.putString(\"value\", value)\n                args.putCharSequenceArray(\"entries\", entries)\n                args.putCharSequenceArray(\"entryValues\", entryValues)\n                args.putCharSequenceArray(\"iconNames\", iconNames)\n                arguments = args\n                onChanged = { value ->\n                    this@IconListPreference.value = value\n                }\n            }\n            it.supportFragmentManager\n                .beginTransaction()\n                .add(dialog, getFragmentTag())\n                .commitAllowingStateLoss()\n        }\n    }\n\n    override fun onAttached() {\n        super.onAttached()\n        val fragment =\n            getActivity()?.supportFragmentManager?.findFragmentByTag(getFragmentTag()) as IconDialog?\n        fragment?.onChanged = { value ->\n            this@IconListPreference.value = value\n        }\n    }\n\n    private fun getActivity(): FragmentActivity? {\n        val context = context\n        if (context is FragmentActivity) {\n            return context\n        } else if (context is ContextWrapper) {\n            val baseContext = context.baseContext\n            if (baseContext is FragmentActivity) {\n                return baseContext\n            }\n        }\n        return null\n    }\n\n    private fun getFragmentTag(): String {\n        return \"icon_$key\"\n    }\n\n    class IconDialog : BaseDialogFragment(R.layout.dialog_recycler_view) {\n\n        var onChanged: ((value: String) -> Unit)? = null\n        var dialogValue: String? = null\n        var dialogEntries: Array<CharSequence>? = null\n        var dialogEntryValues: Array<CharSequence>? = null\n        var dialogIconNames: Array<CharSequence>? = null\n        private val binding by viewBinding(DialogRecyclerViewBinding::bind)\n\n        override fun onStart() {\n            super.onStart()\n            setLayout(0.8f, ViewGroup.LayoutParams.WRAP_CONTENT)\n        }\n\n        override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) {\n            binding.toolBar.setBackgroundColor(primaryColor)\n            binding.toolBar.setTitle(R.string.change_icon)\n            binding.recyclerView.layoutManager = LinearLayoutManager(requireContext())\n            val adapter = Adapter(requireContext())\n            binding.recyclerView.adapter = adapter\n            arguments?.let {\n                dialogValue = it.getString(\"value\")\n                dialogEntries = it.getCharSequenceArray(\"entries\")\n                dialogEntryValues = it.getCharSequenceArray(\"entryValues\")\n                dialogIconNames = it.getCharSequenceArray(\"iconNames\")\n                dialogEntryValues?.let { values ->\n                    adapter.setItems(values.toList())\n                }\n            }\n        }\n\n\n        inner class Adapter(context: Context) :\n            RecyclerAdapter<CharSequence, ItemIconPreferenceBinding>(context) {\n\n            override fun getViewBinding(parent: ViewGroup): ItemIconPreferenceBinding {\n                return ItemIconPreferenceBinding.inflate(inflater, parent, false)\n            }\n\n            override fun convert(\n                holder: ItemViewHolder,\n                binding: ItemIconPreferenceBinding,\n                item: CharSequence,\n                payloads: MutableList<Any>\n            ) {\n                binding.run {\n                    val index = findIndexOfValue(item.toString())\n                    dialogEntries?.let {\n                        label.text = it[index]\n                    }\n                    dialogIconNames?.let {\n                        val resId = context.resources\n                            .getIdentifier(it[index].toString(), \"mipmap\", context.packageName)\n                        val d = try {\n                            context.getCompatDrawable(resId)\n                        } catch (e: Exception) {\n                            null\n                        }\n                        d?.let {\n                            icon.setImageDrawable(d)\n                        }\n                    }\n                    label.isChecked = item.toString() == dialogValue\n                    root.setOnClickListener {\n                        onChanged?.invoke(item.toString())\n                        this@IconDialog.dismissAllowingStateLoss()\n                    }\n                }\n            }\n\n            override fun registerListener(\n                holder: ItemViewHolder,\n                binding: ItemIconPreferenceBinding\n            ) {\n                holder.itemView.setOnClickListener {\n                    getItem(holder.layoutPosition)?.let {\n                        onChanged?.invoke(it.toString())\n                    }\n                }\n            }\n\n            private fun findIndexOfValue(value: String?): Int {\n                dialogEntryValues?.let { values ->\n                    for (i in values.indices.reversed()) {\n                        if (values[i] == value) {\n                            return i\n                        }\n                    }\n                }\n                return -1\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/lib/prefs/ListPreferenceDialog.kt",
    "content": "package io.legado.app.lib.prefs\n\nimport android.app.Dialog\nimport android.os.Bundle\nimport android.view.Gravity\nimport android.view.WindowManager\nimport androidx.appcompat.app.AlertDialog\nimport androidx.core.view.forEach\nimport androidx.preference.ListPreferenceDialogFragmentCompat\nimport androidx.preference.PreferenceDialogFragmentCompat\nimport io.legado.app.R\nimport io.legado.app.help.config.AppConfig\nimport io.legado.app.lib.theme.accentColor\nimport io.legado.app.lib.theme.filletBackground\nimport io.legado.app.utils.applyTint\nimport io.legado.app.utils.dpToPx\n\nclass ListPreferenceDialog : ListPreferenceDialogFragmentCompat() {\n\n    companion object {\n\n        fun newInstance(key: String?): ListPreferenceDialog {\n            val fragment = ListPreferenceDialog()\n            val b = Bundle(1)\n            b.putString(PreferenceDialogFragmentCompat.ARG_KEY, key)\n            fragment.arguments = b\n            return fragment\n        }\n\n    }\n\n    override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {\n        val dialog = super.onCreateDialog(savedInstanceState)\n        dialog.window?.setBackgroundDrawable(requireContext().filletBackground)\n        dialog.window?.decorView?.post {\n            (dialog as AlertDialog).run {\n                getButton(AlertDialog.BUTTON_NEGATIVE)?.setTextColor(accentColor)\n                getButton(AlertDialog.BUTTON_POSITIVE)?.setTextColor(accentColor)\n                getButton(AlertDialog.BUTTON_NEUTRAL)?.setTextColor(accentColor)\n                listView?.forEach {\n                    it.applyTint(accentColor)\n                }\n            }\n        }\n        return dialog\n    }\n\n    override fun onStart() {\n        super.onStart()\n        if (AppConfig.isEInkMode) {\n            dialog?.window?.let {\n                it.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND)\n                val attr = it.attributes\n                attr.dimAmount = 0.0f\n                attr.windowAnimations = 0\n                it.attributes = attr\n                it.setBackgroundDrawableResource(R.color.transparent)\n                when (attr.gravity) {\n                    Gravity.TOP -> it.decorView.setBackgroundResource(R.drawable.bg_eink_border_bottom)\n                    Gravity.BOTTOM -> it.decorView.setBackgroundResource(R.drawable.bg_eink_border_top)\n                    else -> {\n                        val padding = 2.dpToPx();\n                        it.decorView.setPadding(padding, padding, padding, padding)\n                        it.decorView.setBackgroundResource(R.drawable.bg_eink_border_dialog)\n                    }\n                }\n            }\n        }\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/lib/prefs/MultiSelectListPreferenceDialog.kt",
    "content": "package io.legado.app.lib.prefs\n\nimport android.app.Dialog\nimport android.os.Bundle\nimport android.view.Gravity\nimport android.view.WindowManager\nimport androidx.appcompat.app.AlertDialog\nimport androidx.core.view.forEach\nimport androidx.preference.MultiSelectListPreferenceDialogFragmentCompat\nimport androidx.preference.PreferenceDialogFragmentCompat\nimport io.legado.app.R\nimport io.legado.app.help.config.AppConfig\nimport io.legado.app.lib.theme.accentColor\nimport io.legado.app.lib.theme.filletBackground\nimport io.legado.app.utils.applyTint\nimport io.legado.app.utils.dpToPx\n\nclass MultiSelectListPreferenceDialog : MultiSelectListPreferenceDialogFragmentCompat() {\n\n    companion object {\n\n        fun newInstance(key: String?): MultiSelectListPreferenceDialog {\n            val fragment =\n                MultiSelectListPreferenceDialog()\n            val b = Bundle(1)\n            b.putString(PreferenceDialogFragmentCompat.ARG_KEY, key)\n            fragment.arguments = b\n            return fragment\n        }\n\n    }\n\n\n    override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {\n        val dialog = super.onCreateDialog(savedInstanceState)\n        dialog.window?.setBackgroundDrawable(requireContext().filletBackground)\n        dialog.window?.decorView?.post {\n            (dialog as AlertDialog).run {\n                getButton(AlertDialog.BUTTON_NEGATIVE)?.setTextColor(accentColor)\n                getButton(AlertDialog.BUTTON_POSITIVE)?.setTextColor(accentColor)\n                getButton(AlertDialog.BUTTON_NEUTRAL)?.setTextColor(accentColor)\n                listView?.forEach {\n                    it.applyTint(accentColor)\n                }\n            }\n        }\n        return dialog\n    }\n\n    override fun onStart() {\n        super.onStart()\n        if (AppConfig.isEInkMode) {\n            dialog?.window?.let {\n                it.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND)\n                val attr = it.attributes\n                attr.dimAmount = 0.0f\n                attr.windowAnimations = 0\n                it.attributes = attr\n                it.setBackgroundDrawableResource(R.color.transparent)\n                when (attr.gravity) {\n                    Gravity.TOP -> it.decorView.setBackgroundResource(R.drawable.bg_eink_border_bottom)\n                    Gravity.BOTTOM -> it.decorView.setBackgroundResource(R.drawable.bg_eink_border_top)\n                    else -> {\n                        val padding = 2.dpToPx();\n                        it.decorView.setPadding(padding, padding, padding, padding)\n                        it.decorView.setBackgroundResource(R.drawable.bg_eink_border_dialog)\n                    }\n                }\n            }\n        }\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/lib/prefs/NameListPreference.kt",
    "content": "package io.legado.app.lib.prefs\n\nimport android.content.Context\nimport android.util.AttributeSet\nimport android.widget.TextView\nimport androidx.preference.ListPreference\nimport androidx.preference.PreferenceViewHolder\nimport io.legado.app.R\nimport io.legado.app.lib.theme.bottomBackground\nimport io.legado.app.lib.theme.getPrimaryTextColor\nimport io.legado.app.utils.ColorUtils\n\n\nclass NameListPreference(context: Context, attrs: AttributeSet) : ListPreference(context, attrs) {\n\n    private val isBottomBackground: Boolean\n\n    init {\n        layoutResource = R.layout.view_preference\n        widgetLayoutResource = R.layout.item_fillet_text\n        val typedArray = context.obtainStyledAttributes(attrs, R.styleable.Preference)\n        isBottomBackground = typedArray.getBoolean(R.styleable.Preference_isBottomBackground, false)\n        typedArray.recycle()\n    }\n\n    override fun onBindViewHolder(holder: PreferenceViewHolder) {\n        val v = Preference.bindView<TextView>(\n            context, holder, icon, title, summary, widgetLayoutResource,\n            R.id.text_view, isBottomBackground = isBottomBackground\n        )\n        if (v is TextView) {\n            v.text = entry\n            if (isBottomBackground) {\n                val bgColor = context.bottomBackground\n                val pTextColor = context.getPrimaryTextColor(ColorUtils.isColorLight(bgColor))\n                v.setTextColor(pTextColor)\n            }\n        }\n        super.onBindViewHolder(holder)\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/lib/prefs/Preference.kt",
    "content": "package io.legado.app.lib.prefs\n\nimport android.content.Context\nimport android.graphics.drawable.Drawable\nimport android.util.AttributeSet\nimport android.view.LayoutInflater\nimport android.view.View\nimport android.widget.FrameLayout\nimport android.widget.ImageView\nimport android.widget.TextView\nimport androidx.core.view.isGone\nimport androidx.core.view.isVisible\nimport androidx.preference.PreferenceViewHolder\nimport io.legado.app.R\nimport io.legado.app.lib.theme.accentColor\nimport io.legado.app.lib.theme.bottomBackground\nimport io.legado.app.lib.theme.getPrimaryTextColor\nimport io.legado.app.lib.theme.getSecondaryTextColor\nimport io.legado.app.utils.ColorUtils\nimport splitties.views.onLongClick\nimport kotlin.math.roundToInt\n\nopen class Preference(context: Context, attrs: AttributeSet) :\n    androidx.preference.Preference(context, attrs) {\n\n    private var onLongClick: ((preference: Preference) -> Boolean)? = null\n    private val isBottomBackground: Boolean\n\n    init {\n        layoutResource = R.layout.view_preference\n        val typedArray = context.obtainStyledAttributes(attrs, R.styleable.Preference)\n        isBottomBackground = typedArray.getBoolean(R.styleable.Preference_isBottomBackground, false)\n        typedArray.recycle()\n    }\n\n    companion object {\n\n        fun <T : View> bindView(\n            context: Context,\n            viewHolder: PreferenceViewHolder?,\n            icon: Drawable?,\n            title: CharSequence?,\n            summary: CharSequence?,\n            weightLayoutRes: Int? = null,\n            viewId: Int? = null,\n            weightWidth: Int = 0,\n            weightHeight: Int = 0,\n            isBottomBackground: Boolean = false\n        ): T? {\n            if (viewHolder == null) return null\n            val tvTitle = viewHolder.findViewById(R.id.preference_title) as? TextView\n            tvTitle?.let {\n                tvTitle.text = title\n                tvTitle.isVisible = !title.isNullOrEmpty()\n            }\n            val tvSummary = viewHolder.findViewById(R.id.preference_desc) as? TextView\n            tvSummary?.let {\n                tvSummary.text = summary\n                tvSummary.isGone = summary.isNullOrEmpty()\n            }\n            if (isBottomBackground && !viewHolder.itemView.isInEditMode) {\n                val isLight = ColorUtils.isColorLight(context.bottomBackground)\n                val pTextColor = context.getPrimaryTextColor(isLight)\n                tvTitle?.setTextColor(pTextColor)\n                val sTextColor = context.getSecondaryTextColor(isLight)\n                tvSummary?.setTextColor(sTextColor)\n            }\n            val iconView = viewHolder.findViewById(R.id.preference_icon)\n            if (iconView is ImageView) {\n                iconView.isVisible = icon != null\n                iconView.setImageDrawable(icon)\n                iconView.setColorFilter(context.accentColor)\n            }\n\n            if (weightLayoutRes != null && weightLayoutRes != 0 && viewId != null && viewId != 0) {\n                val lay = viewHolder.findViewById(R.id.preference_widget)\n                if (lay is FrameLayout) {\n                    var needRequestLayout = false\n                    var v = viewHolder.itemView.findViewById<T>(viewId)\n                    if (v == null) {\n                        val inflater: LayoutInflater = LayoutInflater.from(context)\n                        val childView = inflater.inflate(weightLayoutRes, null)\n                        lay.removeAllViews()\n                        lay.addView(childView)\n                        lay.isVisible = true\n                        v = lay.findViewById(viewId)\n                    } else\n                        needRequestLayout = true\n\n                    if (weightWidth > 0 || weightHeight > 0) {\n                        val lp = lay.layoutParams\n                        if (weightHeight > 0)\n                            lp.height =\n                                (context.resources.displayMetrics.density * weightHeight).roundToInt()\n                        if (weightWidth > 0)\n                            lp.width =\n                                (context.resources.displayMetrics.density * weightWidth).roundToInt()\n                        lay.layoutParams = lp\n                    } else if (needRequestLayout)\n                        v.requestLayout()\n\n                    return v\n                }\n            }\n\n            return null\n        }\n\n    }\n\n    final override fun onBindViewHolder(holder: PreferenceViewHolder) {\n        super.onBindViewHolder(holder)\n        onBindView(holder)\n        onLongClick?.let { listener ->\n            holder.itemView.onLongClick {\n                listener.invoke(this)\n            }\n        }\n    }\n\n    open fun onBindView(holder: PreferenceViewHolder) {\n        bindView<View>(\n            context, holder, icon, title, summary,\n            isBottomBackground = isBottomBackground\n        )\n    }\n\n    fun onLongClick(listener: (preference: Preference) -> Boolean) {\n        onLongClick = listener\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/lib/prefs/PreferenceCategory.kt",
    "content": "package io.legado.app.lib.prefs\n\nimport android.content.Context\nimport android.util.AttributeSet\nimport android.view.View\nimport android.widget.TextView\nimport androidx.core.view.isVisible\nimport androidx.preference.PreferenceCategory\nimport androidx.preference.PreferenceViewHolder\nimport io.legado.app.R\nimport io.legado.app.help.config.AppConfig\nimport io.legado.app.lib.theme.accentColor\nimport io.legado.app.lib.theme.backgroundColor\nimport io.legado.app.utils.ColorUtils\n\n\nclass PreferenceCategory(context: Context, attrs: AttributeSet) :\n    PreferenceCategory(context, attrs) {\n\n    init {\n        isPersistent = true\n        layoutResource = R.layout.view_preference_category\n    }\n\n    override fun onBindViewHolder(holder: PreferenceViewHolder) {\n        super.onBindViewHolder(holder)\n        val view = holder.findViewById(R.id.preference_title)\n        if (view is TextView) {  //  && !view.isInEditMode\n            view.text = title\n            if (view.isInEditMode) return\n            view.setTextColor(context.accentColor)\n            view.isVisible = !title.isNullOrEmpty()\n\n            val da = holder.findViewById(R.id.preference_divider_above)\n            val dividerColor = if (AppConfig.isNightTheme) {\n                ColorUtils.withAlpha(\n                    ColorUtils.shiftColor(context.backgroundColor, 1.05f),\n                    0.5f\n                )\n            } else {\n                ColorUtils.withAlpha(\n                    ColorUtils.shiftColor(context.backgroundColor, 0.95f),\n                    0.5f\n                )\n            }\n            if (da is View) {\n                da.setBackgroundColor(dividerColor)\n                da.isVisible = holder.isDividerAllowedAbove\n            }\n            val db = holder.findViewById(R.id.preference_divider_below)\n            if (db is View) {\n                db.setBackgroundColor(dividerColor)\n                db.isVisible = holder.isDividerAllowedBelow\n            }\n        }\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/lib/prefs/SwitchPreference.kt",
    "content": "package io.legado.app.lib.prefs\n\nimport android.content.Context\nimport android.util.AttributeSet\nimport androidx.appcompat.widget.SwitchCompat\nimport androidx.preference.PreferenceViewHolder\nimport androidx.preference.SwitchPreferenceCompat\nimport io.legado.app.R\nimport io.legado.app.lib.theme.accentColor\nimport io.legado.app.utils.applyTint\n\nclass SwitchPreference(context: Context, attrs: AttributeSet) :\n    SwitchPreferenceCompat(context, attrs) {\n\n    private val isBottomBackground: Boolean\n    private var onLongClick: ((preference: SwitchPreference) -> Boolean)? = null\n\n    init {\n        layoutResource = R.layout.view_preference\n        val typedArray = context.obtainStyledAttributes(attrs, R.styleable.Preference)\n        isBottomBackground = typedArray.getBoolean(R.styleable.Preference_isBottomBackground, false)\n        typedArray.recycle()\n    }\n\n    override fun onBindViewHolder(holder: PreferenceViewHolder) {\n        val v = Preference.bindView<SwitchCompat>(\n            context, holder, icon, title, summary,\n            widgetLayoutResource,\n            androidx.preference.R.id.switchWidget,\n            isBottomBackground = isBottomBackground\n        )\n        if (v is SwitchCompat && !v.isInEditMode) {\n            v.applyTint(context.accentColor)\n        }\n        super.onBindViewHolder(holder)\n        onLongClick?.let { listener ->\n            holder.itemView.setOnLongClickListener {\n                listener.invoke(this)\n            }\n        }\n    }\n\n    fun onLongClick(listener: (preference: SwitchPreference) -> Boolean) {\n        onLongClick = listener\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/lib/prefs/fragment/PreferenceFragment.kt",
    "content": "package io.legado.app.lib.prefs.fragment\n\nimport android.annotation.SuppressLint\nimport android.os.Bundle\nimport android.view.View\nimport androidx.fragment.app.DialogFragment\nimport androidx.preference.EditTextPreference\nimport androidx.preference.ListPreference\nimport androidx.preference.MultiSelectListPreference\nimport androidx.preference.Preference\nimport androidx.preference.PreferenceFragmentCompat\nimport io.legado.app.lib.prefs.EditTextPreferenceDialog\nimport io.legado.app.lib.prefs.ListPreferenceDialog\nimport io.legado.app.lib.prefs.MultiSelectListPreferenceDialog\nimport io.legado.app.utils.applyNavigationBarPadding\n\nabstract class PreferenceFragment : PreferenceFragmentCompat() {\n\n    private val dialogFragmentTag = \"androidx.preference.PreferenceFragment.DIALOG\"\n\n    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n        super.onViewCreated(view, savedInstanceState)\n        listView.clipToPadding = false\n        listView.applyNavigationBarPadding()\n    }\n\n    @SuppressLint(\"RestrictedApi\")\n    override fun onDisplayPreferenceDialog(preference: Preference) {\n\n        var handled = false\n        if (callbackFragment is OnPreferenceDisplayDialogCallback) {\n            handled =\n                (callbackFragment as OnPreferenceDisplayDialogCallback)\n                    .onPreferenceDisplayDialog(this, preference)\n        }\n        if (!handled && activity is OnPreferenceDisplayDialogCallback) {\n            handled = (activity as OnPreferenceDisplayDialogCallback)\n                .onPreferenceDisplayDialog(this, preference)\n        }\n\n        if (handled) {\n            return\n        }\n\n        // check if dialog is already showing\n        if (parentFragmentManager.findFragmentByTag(dialogFragmentTag) != null) {\n            return\n        }\n\n        val dialogFragment: DialogFragment = when (preference) {\n            is EditTextPreference -> {\n                EditTextPreferenceDialog.newInstance(preference.getKey())\n            }\n            is ListPreference -> {\n                ListPreferenceDialog.newInstance(preference.getKey())\n            }\n            is MultiSelectListPreference -> {\n                MultiSelectListPreferenceDialog.newInstance(preference.getKey())\n            }\n            else -> {\n                throw IllegalArgumentException(\n                    \"Cannot display dialog for an unknown Preference type: \"\n                            + preference.javaClass.simpleName\n                            + \". Make sure to implement onPreferenceDisplayDialog() to handle \"\n                            + \"displaying a custom dialog for this Preference.\"\n                )\n            }\n        }\n        @Suppress(\"DEPRECATION\")\n        dialogFragment.setTargetFragment(this, 0)\n\n        dialogFragment.show(parentFragmentManager, dialogFragmentTag)\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/lib/theme/MaterialValueHelper.kt",
    "content": "@file:Suppress(\"unused\")\n\npackage io.legado.app.lib.theme\n\nimport android.annotation.SuppressLint\nimport android.content.Context\nimport android.graphics.drawable.GradientDrawable\nimport androidx.annotation.ColorInt\nimport androidx.core.content.ContextCompat\nimport androidx.fragment.app.Fragment\nimport io.legado.app.R\nimport io.legado.app.help.config.AppConfig\nimport io.legado.app.utils.ColorUtils\nimport io.legado.app.utils.dpToPx\n\n/**\n * @author Karim Abou Zeid (kabouzeid)\n */\n@ColorInt\nfun Context.getPrimaryTextColor(dark: Boolean): Int {\n    return if (dark) {\n        ContextCompat.getColor(this, R.color.md_light_primary_text)\n    } else {\n        ContextCompat.getColor(this, R.color.md_dark_primary_text)\n    }\n}\n\n@ColorInt\nfun Context.getSecondaryTextColor(dark: Boolean): Int {\n    return if (dark) {\n        ContextCompat.getColor(this, R.color.md_light_secondary)\n    } else {\n        ContextCompat.getColor(this, R.color.md_dark_primary_text)\n    }\n}\n\n@ColorInt\nfun Context.getPrimaryDisabledTextColor(dark: Boolean): Int {\n    return if (dark) {\n        ContextCompat.getColor(this, R.color.md_light_disabled)\n    } else {\n        ContextCompat.getColor(this, R.color.md_dark_disabled)\n    }\n}\n\n@ColorInt\nfun Context.getSecondaryDisabledTextColor(dark: Boolean): Int {\n    return if (dark) {\n        ContextCompat.getColor(\n            this,\n            androidx.appcompat.R.color.secondary_text_disabled_material_light\n        )\n    } else {\n        ContextCompat.getColor(\n            this,\n            androidx.appcompat.R.color.secondary_text_disabled_material_dark\n        )\n    }\n}\n\nval Context.primaryColor: Int\n    get() = ThemeStore.primaryColor(this)\n\nval Context.primaryColorDark: Int\n    get() = ThemeStore.primaryColorDark(this)\n\nval Context.accentColor: Int\n    get() = ThemeStore.accentColor(this)\n\nval Context.backgroundColor: Int\n    get() = ThemeStore.backgroundColor(this)\n\nval Context.bottomBackground: Int\n    get() = ThemeStore.bottomBackground(this)\n\nval Context.primaryTextColor: Int\n    get() = getPrimaryTextColor(isDarkTheme)\n\nval Context.secondaryTextColor: Int\n    get() = getSecondaryTextColor(isDarkTheme)\n\nval Context.primaryDisabledTextColor: Int\n    get() = getPrimaryDisabledTextColor(isDarkTheme)\n\nval Context.secondaryDisabledTextColor: Int\n    get() = getSecondaryDisabledTextColor(isDarkTheme)\n\nval Fragment.primaryColor: Int\n    get() = ThemeStore.primaryColor(requireContext())\n\nval Fragment.primaryColorDark: Int\n    get() = ThemeStore.primaryColorDark(requireContext())\n\nval Fragment.accentColor: Int\n    get() = ThemeStore.accentColor(requireContext())\n\nval Fragment.backgroundColor: Int\n    get() = ThemeStore.backgroundColor(requireContext())\n\nval Fragment.bottomBackground: Int\n    get() = ThemeStore.bottomBackground(requireContext())\n\nval Fragment.primaryTextColor: Int\n    get() = requireContext().getPrimaryTextColor(isDarkTheme)\n\nval Fragment.secondaryTextColor: Int\n    get() = requireContext().getSecondaryTextColor(isDarkTheme)\n\nval Fragment.primaryDisabledTextColor: Int\n    get() = requireContext().getPrimaryDisabledTextColor(isDarkTheme)\n\nval Fragment.secondaryDisabledTextColor: Int\n    get() = requireContext().getSecondaryDisabledTextColor(isDarkTheme)\n\nval Context.buttonDisabledColor: Int\n    get() = if (isDarkTheme) {\n        ContextCompat.getColor(this, R.color.md_dark_disabled)\n    } else {\n        ContextCompat.getColor(this, R.color.md_light_disabled)\n    }\n\nval Context.isDarkTheme: Boolean\n    get() = ColorUtils.isColorLight(ThemeStore.primaryColor(this))\n\nval Fragment.isDarkTheme: Boolean\n    get() = requireContext().isDarkTheme\n\nval Context.elevation: Float\n    @SuppressLint(\"PrivateResource\")\n    get() {\n        return if (AppConfig.elevation < 0) {\n            ThemeUtils.resolveFloat(\n                this,\n                android.R.attr.elevation,\n                resources.getDimension(com.google.android.material.R.dimen.design_appbar_elevation)\n            )\n        } else {\n            AppConfig.elevation.toFloat().dpToPx()\n        }\n    }\n\nval Context.filletBackground: GradientDrawable\n    get() {\n        val background = GradientDrawable()\n        background.cornerRadius = 3f.dpToPx()\n        background.setColor(backgroundColor)\n        return background\n    }"
  },
  {
    "path": "app/src/main/java/io/legado/app/lib/theme/Selector.kt",
    "content": "package io.legado.app.lib.theme\n\nimport android.content.Context\nimport android.content.res.ColorStateList\nimport android.graphics.Color\nimport android.graphics.drawable.ColorDrawable\nimport android.graphics.drawable.Drawable\nimport android.graphics.drawable.GradientDrawable\nimport android.graphics.drawable.StateListDrawable\nimport androidx.annotation.ColorInt\nimport androidx.annotation.Dimension\nimport androidx.annotation.DrawableRes\nimport androidx.annotation.IntDef\nimport androidx.core.content.ContextCompat\n\n@Suppress(\"unused\")\nobject Selector {\n    fun shapeBuild(): ShapeSelector {\n        return ShapeSelector()\n    }\n\n    fun colorBuild(): ColorSelector {\n        return ColorSelector()\n    }\n\n    fun drawableBuild(): DrawableSelector {\n        return DrawableSelector()\n    }\n\n    /**\n     * 形状ShapeSelector\n     *\n     * @author hjy\n     * created at 2017/12/11 22:26\n     */\n    class ShapeSelector {\n\n        private var mShape: Int = 0               //the shape of background\n        private var mDefaultBgColor: Int = 0      //default background color\n        private var mDisabledBgColor: Int = 0     //state_enabled = false\n        private var mPressedBgColor: Int = 0      //state_pressed = true\n        private var mSelectedBgColor: Int = 0     //state_selected = true\n        private var mFocusedBgColor: Int = 0      //state_focused = true\n        private var mCheckedBgColor: Int = 0      //state_checked = true\n        private var mStrokeWidth: Int = 0         //stroke width in pixel\n        private var mDefaultStrokeColor: Int = 0  //default stroke color\n        private var mDisabledStrokeColor: Int = 0 //state_enabled = false\n        private var mPressedStrokeColor: Int = 0  //state_pressed = true\n        private var mSelectedStrokeColor: Int = 0 //state_selected = true\n        private var mFocusedStrokeColor: Int = 0  //state_focused = true\n        private var mCheckedStrokeColor: Int = 0  //state_checked = true\n        private var mCornerRadius: Int = 0        //corner radius\n\n        private var hasSetDisabledBgColor = false\n        private var hasSetPressedBgColor = false\n        private var hasSetSelectedBgColor = false\n        private val hasSetFocusedBgColor = false\n        private var hasSetCheckedBgColor = false\n\n        private var hasSetDisabledStrokeColor = false\n        private var hasSetPressedStrokeColor = false\n        private var hasSetSelectedStrokeColor = false\n        private var hasSetFocusedStrokeColor = false\n        private var hasSetCheckedStrokeColor = false\n\n        @IntDef(\n            GradientDrawable.RECTANGLE,\n            GradientDrawable.OVAL,\n            GradientDrawable.LINE,\n            GradientDrawable.RING\n        )\n        private annotation class Shape\n\n        init {\n            //initialize default values\n            mShape = GradientDrawable.RECTANGLE\n            mDefaultBgColor = Color.TRANSPARENT\n            mDisabledBgColor = Color.TRANSPARENT\n            mPressedBgColor = Color.TRANSPARENT\n            mSelectedBgColor = Color.TRANSPARENT\n            mFocusedBgColor = Color.TRANSPARENT\n            mStrokeWidth = 0\n            mDefaultStrokeColor = Color.TRANSPARENT\n            mDisabledStrokeColor = Color.TRANSPARENT\n            mPressedStrokeColor = Color.TRANSPARENT\n            mSelectedStrokeColor = Color.TRANSPARENT\n            mFocusedStrokeColor = Color.TRANSPARENT\n            mCornerRadius = 0\n        }\n\n        fun setShape(@Shape shape: Int): ShapeSelector {\n            mShape = shape\n            return this\n        }\n\n        fun setDefaultBgColor(@ColorInt color: Int): ShapeSelector {\n            mDefaultBgColor = color\n            if (!hasSetDisabledBgColor)\n                mDisabledBgColor = color\n            if (!hasSetPressedBgColor)\n                mPressedBgColor = color\n            if (!hasSetSelectedBgColor)\n                mSelectedBgColor = color\n            if (!hasSetFocusedBgColor)\n                mFocusedBgColor = color\n            return this\n        }\n\n        fun setDisabledBgColor(@ColorInt color: Int): ShapeSelector {\n            mDisabledBgColor = color\n            hasSetDisabledBgColor = true\n            return this\n        }\n\n        fun setPressedBgColor(@ColorInt color: Int): ShapeSelector {\n            mPressedBgColor = color\n            hasSetPressedBgColor = true\n            return this\n        }\n\n        fun setSelectedBgColor(@ColorInt color: Int): ShapeSelector {\n            mSelectedBgColor = color\n            hasSetSelectedBgColor = true\n            return this\n        }\n\n        fun setFocusedBgColor(@ColorInt color: Int): ShapeSelector {\n            mFocusedBgColor = color\n            hasSetPressedBgColor = true\n            return this\n        }\n\n        fun setCheckedBgColor(@ColorInt color: Int): ShapeSelector {\n            mCheckedBgColor = color\n            hasSetCheckedBgColor = true\n            return this\n        }\n\n        fun setStrokeWidth(@Dimension width: Int): ShapeSelector {\n            mStrokeWidth = width\n            return this\n        }\n\n        fun setDefaultStrokeColor(@ColorInt color: Int): ShapeSelector {\n            mDefaultStrokeColor = color\n            if (!hasSetDisabledStrokeColor)\n                mDisabledStrokeColor = color\n            if (!hasSetPressedStrokeColor)\n                mPressedStrokeColor = color\n            if (!hasSetSelectedStrokeColor)\n                mSelectedStrokeColor = color\n            if (!hasSetFocusedStrokeColor)\n                mFocusedStrokeColor = color\n            return this\n        }\n\n        fun setDisabledStrokeColor(@ColorInt color: Int): ShapeSelector {\n            mDisabledStrokeColor = color\n            hasSetDisabledStrokeColor = true\n            return this\n        }\n\n        fun setPressedStrokeColor(@ColorInt color: Int): ShapeSelector {\n            mPressedStrokeColor = color\n            hasSetPressedStrokeColor = true\n            return this\n        }\n\n        fun setSelectedStrokeColor(@ColorInt color: Int): ShapeSelector {\n            mSelectedStrokeColor = color\n            hasSetSelectedStrokeColor = true\n            return this\n        }\n\n        fun setCheckedStrokeColor(@ColorInt color: Int): ShapeSelector {\n            mCheckedStrokeColor = color\n            hasSetCheckedStrokeColor = true\n            return this\n        }\n\n        fun setFocusedStrokeColor(@ColorInt color: Int): ShapeSelector {\n            mFocusedStrokeColor = color\n            hasSetFocusedStrokeColor = true\n            return this\n        }\n\n        fun setCornerRadius(@Dimension radius: Int): ShapeSelector {\n            mCornerRadius = radius\n            return this\n        }\n\n        fun create(): StateListDrawable {\n            val selector = StateListDrawable()\n\n            //enabled = false\n            if (hasSetDisabledBgColor || hasSetDisabledStrokeColor) {\n                val disabledShape = getItemShape(\n                    mShape, mCornerRadius,\n                    mDisabledBgColor, mStrokeWidth, mDisabledStrokeColor\n                )\n                selector.addState(intArrayOf(-android.R.attr.state_enabled), disabledShape)\n            }\n\n            //pressed = true\n            if (hasSetPressedBgColor || hasSetPressedStrokeColor) {\n                val pressedShape = getItemShape(\n                    mShape, mCornerRadius,\n                    mPressedBgColor, mStrokeWidth, mPressedStrokeColor\n                )\n                selector.addState(intArrayOf(android.R.attr.state_pressed), pressedShape)\n            }\n\n            //selected = true\n            if (hasSetSelectedBgColor || hasSetSelectedStrokeColor) {\n                val selectedShape = getItemShape(\n                    mShape, mCornerRadius,\n                    mSelectedBgColor, mStrokeWidth, mSelectedStrokeColor\n                )\n                selector.addState(intArrayOf(android.R.attr.state_selected), selectedShape)\n            }\n\n            //focused = true\n            if (hasSetFocusedBgColor || hasSetFocusedStrokeColor) {\n                val focusedShape = getItemShape(\n                    mShape, mCornerRadius,\n                    mFocusedBgColor, mStrokeWidth, mFocusedStrokeColor\n                )\n                selector.addState(intArrayOf(android.R.attr.state_focused), focusedShape)\n            }\n\n            //checked = true\n            if (hasSetCheckedBgColor || hasSetCheckedStrokeColor) {\n                val checkedShape = getItemShape(\n                    mShape, mCornerRadius,\n                    mCheckedBgColor, mStrokeWidth, mCheckedStrokeColor\n                )\n                selector.addState(intArrayOf(android.R.attr.state_checked), checkedShape)\n            }\n\n            //default\n            val defaultShape = getItemShape(\n                mShape, mCornerRadius,\n                mDefaultBgColor, mStrokeWidth, mDefaultStrokeColor\n            )\n            selector.addState(intArrayOf(), defaultShape)\n\n            return selector\n        }\n\n        private fun getItemShape(\n            shape: Int, cornerRadius: Int,\n            solidColor: Int, strokeWidth: Int, strokeColor: Int\n        ): GradientDrawable {\n            val drawable = GradientDrawable()\n            drawable.shape = shape\n            drawable.setStroke(strokeWidth, strokeColor)\n            drawable.cornerRadius = cornerRadius.toFloat()\n            drawable.setColor(solidColor)\n            return drawable\n        }\n    }\n\n    /**\n     * 资源DrawableSelector\n     *\n     * @author hjy\n     * created at 2017/12/11 22:34\n     */\n    @Suppress(\"MemberVisibilityCanBePrivate\")\n    class DrawableSelector {\n\n        private var mDefaultDrawable: Drawable? = null\n        private var mDisabledDrawable: Drawable? = null\n        private var mPressedDrawable: Drawable? = null\n        private var mSelectedDrawable: Drawable? = null\n        private var mFocusedDrawable: Drawable? = null\n\n        private var hasSetDisabledDrawable = false\n        private var hasSetPressedDrawable = false\n        private var hasSetSelectedDrawable = false\n        private var hasSetFocusedDrawable = false\n\n        init {\n            mDefaultDrawable = ColorDrawable(Color.TRANSPARENT)\n        }\n\n        fun setDefaultDrawable(drawable: Drawable?): DrawableSelector {\n            mDefaultDrawable = drawable\n            if (!hasSetDisabledDrawable)\n                mDisabledDrawable = drawable\n            if (!hasSetPressedDrawable)\n                mPressedDrawable = drawable\n            if (!hasSetSelectedDrawable)\n                mSelectedDrawable = drawable\n            if (!hasSetFocusedDrawable)\n                mFocusedDrawable = drawable\n            return this\n        }\n\n        fun setDisabledDrawable(drawable: Drawable?): DrawableSelector {\n            mDisabledDrawable = drawable\n            hasSetDisabledDrawable = true\n            return this\n        }\n\n        fun setPressedDrawable(drawable: Drawable?): DrawableSelector {\n            mPressedDrawable = drawable\n            hasSetPressedDrawable = true\n            return this\n        }\n\n        fun setSelectedDrawable(drawable: Drawable?): DrawableSelector {\n            mSelectedDrawable = drawable\n            hasSetSelectedDrawable = true\n            return this\n        }\n\n        fun setFocusedDrawable(drawable: Drawable?): DrawableSelector {\n            mFocusedDrawable = drawable\n            hasSetFocusedDrawable = true\n            return this\n        }\n\n        fun create(): StateListDrawable {\n            val selector = StateListDrawable()\n            if (hasSetDisabledDrawable)\n                selector.addState(intArrayOf(-android.R.attr.state_enabled), mDisabledDrawable)\n            if (hasSetPressedDrawable)\n                selector.addState(intArrayOf(android.R.attr.state_pressed), mPressedDrawable)\n            if (hasSetSelectedDrawable)\n                selector.addState(intArrayOf(android.R.attr.state_selected), mSelectedDrawable)\n            if (hasSetFocusedDrawable)\n                selector.addState(intArrayOf(android.R.attr.state_focused), mFocusedDrawable)\n            selector.addState(intArrayOf(), mDefaultDrawable)\n            return selector\n        }\n\n        fun setDefaultDrawable(context: Context, @DrawableRes drawableRes: Int): DrawableSelector {\n            return setDefaultDrawable(ContextCompat.getDrawable(context, drawableRes))\n        }\n\n        fun setDisabledDrawable(context: Context, @DrawableRes drawableRes: Int): DrawableSelector {\n            return setDisabledDrawable(ContextCompat.getDrawable(context, drawableRes))\n        }\n\n        fun setPressedDrawable(context: Context, @DrawableRes drawableRes: Int): DrawableSelector {\n            return setPressedDrawable(ContextCompat.getDrawable(context, drawableRes))\n        }\n\n        fun setSelectedDrawable(context: Context, @DrawableRes drawableRes: Int): DrawableSelector {\n            return setSelectedDrawable(ContextCompat.getDrawable(context, drawableRes))\n        }\n\n        fun setFocusedDrawable(context: Context, @DrawableRes drawableRes: Int): DrawableSelector {\n            return setFocusedDrawable(ContextCompat.getDrawable(context, drawableRes))\n        }\n    }\n\n    /**\n     * 颜色ColorSelector\n     *\n     * @author hjy\n     * created at 2017/12/11 22:26\n     */\n    class ColorSelector {\n\n        private var mDefaultColor: Int = 0\n        private var mDisabledColor: Int = 0\n        private var mPressedColor: Int = 0\n        private var mSelectedColor: Int = 0\n        private var mFocusedColor: Int = 0\n        private var mCheckedColor: Int = 0\n\n        private var hasSetDisabledColor = false\n        private var hasSetPressedColor = false\n        private var hasSetSelectedColor = false\n        private var hasSetFocusedColor = false\n        private var hasSetCheckedColor = false\n\n        init {\n            mDefaultColor = Color.BLACK\n            mDisabledColor = Color.GRAY\n            mPressedColor = Color.BLACK\n            mSelectedColor = Color.BLACK\n            mFocusedColor = Color.BLACK\n        }\n\n        fun setDefaultColor(@ColorInt color: Int): ColorSelector {\n            mDefaultColor = color\n            if (!hasSetDisabledColor)\n                mDisabledColor = color\n            if (!hasSetPressedColor)\n                mPressedColor = color\n            if (!hasSetSelectedColor)\n                mSelectedColor = color\n            if (!hasSetFocusedColor)\n                mFocusedColor = color\n            return this\n        }\n\n        fun setDisabledColor(@ColorInt color: Int): ColorSelector {\n            mDisabledColor = color\n            hasSetDisabledColor = true\n            return this\n        }\n\n        fun setPressedColor(@ColorInt color: Int): ColorSelector {\n            mPressedColor = color\n            hasSetPressedColor = true\n            return this\n        }\n\n        fun setSelectedColor(@ColorInt color: Int): ColorSelector {\n            mSelectedColor = color\n            hasSetSelectedColor = true\n            return this\n        }\n\n        fun setFocusedColor(@ColorInt color: Int): ColorSelector {\n            mFocusedColor = color\n            hasSetFocusedColor = true\n            return this\n        }\n\n        fun setCheckedColor(@ColorInt color: Int): ColorSelector {\n            mCheckedColor = color\n            hasSetCheckedColor = true\n            return this\n        }\n\n        fun create(): ColorStateList {\n            val colors = intArrayOf(\n                if (hasSetDisabledColor) mDisabledColor else mDefaultColor,\n                if (hasSetPressedColor) mPressedColor else mDefaultColor,\n                if (hasSetSelectedColor) mSelectedColor else mDefaultColor,\n                if (hasSetFocusedColor) mFocusedColor else mDefaultColor,\n                if (hasSetCheckedColor) mCheckedColor else mDefaultColor,\n                mDefaultColor\n            )\n            val states = arrayOfNulls<IntArray>(6)\n            states[0] = intArrayOf(-android.R.attr.state_enabled)\n            states[1] = intArrayOf(android.R.attr.state_pressed)\n            states[2] = intArrayOf(android.R.attr.state_selected)\n            states[3] = intArrayOf(android.R.attr.state_focused)\n            states[4] = intArrayOf(android.R.attr.state_checked)\n            states[5] = intArrayOf()\n            return ColorStateList(states, colors)\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/lib/theme/ThemeStore.kt",
    "content": "package io.legado.app.lib.theme\n\nimport android.annotation.SuppressLint\nimport android.content.Context\nimport android.content.SharedPreferences\nimport android.graphics.Color\nimport androidx.annotation.AttrRes\nimport androidx.annotation.CheckResult\nimport androidx.annotation.ColorInt\nimport androidx.annotation.ColorRes\nimport androidx.core.content.ContextCompat\nimport io.legado.app.utils.ColorUtils\nimport io.legado.app.utils.LogUtils\nimport splitties.init.appCtx\n\n/**\n * @author Aidan Follestad (afollestad), Karim Abou Zeid (kabouzeid)\n */\n@Suppress(\"unused\")\nclass ThemeStore @SuppressLint(\"CommitPrefEdits\")\nprivate constructor(private val mContext: Context) : ThemeStoreInterface {\n\n    private val mEditor = prefs(mContext).edit()\n\n    override fun primaryColor(@ColorInt color: Int): ThemeStore {\n        mEditor.putInt(ThemeStorePrefKeys.KEY_PRIMARY_COLOR, color)\n        if (autoGeneratePrimaryDark(mContext))\n            primaryColorDark(ColorUtils.darkenColor(color))\n        return this\n    }\n\n    override fun primaryColorRes(@ColorRes colorRes: Int): ThemeStore {\n        return primaryColor(ContextCompat.getColor(mContext, colorRes))\n    }\n\n    override fun primaryColorAttr(@AttrRes colorAttr: Int): ThemeStore {\n        return primaryColor(ThemeUtils.resolveColor(mContext, colorAttr))\n    }\n\n    override fun primaryColorDark(@ColorInt color: Int): ThemeStore {\n        mEditor.putInt(ThemeStorePrefKeys.KEY_PRIMARY_COLOR_DARK, color)\n        return this\n    }\n\n    override fun primaryColorDarkRes(@ColorRes colorRes: Int): ThemeStore {\n        return primaryColorDark(ContextCompat.getColor(mContext, colorRes))\n    }\n\n    override fun primaryColorDarkAttr(@AttrRes colorAttr: Int): ThemeStore {\n        return primaryColorDark(ThemeUtils.resolveColor(mContext, colorAttr))\n    }\n\n    override fun accentColor(@ColorInt color: Int): ThemeStore {\n        mEditor.putInt(ThemeStorePrefKeys.KEY_ACCENT_COLOR, color)\n        return this\n    }\n\n    override fun accentColorRes(@ColorRes colorRes: Int): ThemeStore {\n        return accentColor(ContextCompat.getColor(mContext, colorRes))\n    }\n\n    override fun accentColorAttr(@AttrRes colorAttr: Int): ThemeStore {\n        return accentColor(ThemeUtils.resolveColor(mContext, colorAttr))\n    }\n\n    override fun statusBarColor(@ColorInt color: Int): ThemeStore {\n        mEditor.putInt(ThemeStorePrefKeys.KEY_STATUS_BAR_COLOR, color)\n        return this\n    }\n\n    override fun statusBarColorRes(@ColorRes colorRes: Int): ThemeStore {\n        return statusBarColor(ContextCompat.getColor(mContext, colorRes))\n    }\n\n    override fun statusBarColorAttr(@AttrRes colorAttr: Int): ThemeStore {\n        return statusBarColor(ThemeUtils.resolveColor(mContext, colorAttr))\n    }\n\n    override fun navigationBarColor(@ColorInt color: Int): ThemeStore {\n        mEditor.putInt(ThemeStorePrefKeys.KEY_NAVIGATION_BAR_COLOR, color)\n        return this\n    }\n\n    override fun navigationBarColorRes(@ColorRes colorRes: Int): ThemeStore {\n        return navigationBarColor(ContextCompat.getColor(mContext, colorRes))\n    }\n\n    override fun navigationBarColorAttr(@AttrRes colorAttr: Int): ThemeStore {\n        return navigationBarColor(ThemeUtils.resolveColor(mContext, colorAttr))\n    }\n\n    override fun textColorPrimary(@ColorInt color: Int): ThemeStore {\n        mEditor.putInt(ThemeStorePrefKeys.KEY_TEXT_COLOR_PRIMARY, color)\n        return this\n    }\n\n    override fun textColorPrimaryRes(@ColorRes colorRes: Int): ThemeStore {\n        return textColorPrimary(ContextCompat.getColor(mContext, colorRes))\n    }\n\n    override fun textColorPrimaryAttr(@AttrRes colorAttr: Int): ThemeStore {\n        return textColorPrimary(ThemeUtils.resolveColor(mContext, colorAttr))\n    }\n\n    override fun textColorPrimaryInverse(@ColorInt color: Int): ThemeStore {\n        mEditor.putInt(ThemeStorePrefKeys.KEY_TEXT_COLOR_PRIMARY_INVERSE, color)\n        return this\n    }\n\n    override fun textColorPrimaryInverseRes(@ColorRes colorRes: Int): ThemeStore {\n        return textColorPrimaryInverse(ContextCompat.getColor(mContext, colorRes))\n    }\n\n    override fun textColorPrimaryInverseAttr(@AttrRes colorAttr: Int): ThemeStore {\n        return textColorPrimaryInverse(ThemeUtils.resolveColor(mContext, colorAttr))\n    }\n\n    override fun textColorSecondary(@ColorInt color: Int): ThemeStore {\n        mEditor.putInt(ThemeStorePrefKeys.KEY_TEXT_COLOR_SECONDARY, color)\n        return this\n    }\n\n    override fun textColorSecondaryRes(@ColorRes colorRes: Int): ThemeStore {\n        return textColorSecondary(ContextCompat.getColor(mContext, colorRes))\n    }\n\n    override fun textColorSecondaryAttr(@AttrRes colorAttr: Int): ThemeStore {\n        return textColorSecondary(ThemeUtils.resolveColor(mContext, colorAttr))\n    }\n\n    override fun textColorSecondaryInverse(@ColorInt color: Int): ThemeStore {\n        mEditor.putInt(ThemeStorePrefKeys.KEY_TEXT_COLOR_SECONDARY_INVERSE, color)\n        return this\n    }\n\n    override fun textColorSecondaryInverseRes(@ColorRes colorRes: Int): ThemeStore {\n        return textColorSecondaryInverse(ContextCompat.getColor(mContext, colorRes))\n    }\n\n    override fun textColorSecondaryInverseAttr(@AttrRes colorAttr: Int): ThemeStore {\n        return textColorSecondaryInverse(ThemeUtils.resolveColor(mContext, colorAttr))\n    }\n\n    override fun backgroundColor(color: Int): ThemeStore {\n        mEditor.putInt(ThemeStorePrefKeys.KEY_BACKGROUND_COLOR, color)\n        return this\n    }\n\n    override fun bottomBackground(color: Int): ThemeStore {\n        mEditor.putInt(ThemeStorePrefKeys.KEY_BOTTOM_BACKGROUND, color)\n        return this\n    }\n\n    override fun autoGeneratePrimaryDark(autoGenerate: Boolean): ThemeStore {\n        mEditor.putBoolean(ThemeStorePrefKeys.KEY_AUTO_GENERATE_PRIMARYDARK, autoGenerate)\n        return this\n    }\n\n    // Commit method\n\n    override fun apply() {\n        mEditor.putLong(ThemeStorePrefKeys.VALUES_CHANGED, System.currentTimeMillis())\n            .putBoolean(ThemeStorePrefKeys.IS_CONFIGURED_KEY, true)\n            .apply()\n        accentColor = accentColor()\n    }\n\n    companion object {\n\n        var accentColor = accentColor()\n\n        fun editTheme(context: Context): ThemeStore {\n            return ThemeStore(context)\n        }\n\n        // Static getters\n\n        @CheckResult\n        internal fun prefs(context: Context): SharedPreferences {\n            return context.getSharedPreferences(\n                ThemeStorePrefKeys.CONFIG_PREFS_KEY_DEFAULT,\n                Context.MODE_PRIVATE\n            )\n        }\n\n        fun markChanged(context: Context) {\n            ThemeStore(context).apply()\n        }\n\n        @CheckResult\n        @ColorInt\n        fun primaryColor(context: Context = appCtx): Int {\n            return prefs(context).getInt(\n                ThemeStorePrefKeys.KEY_PRIMARY_COLOR,\n                ThemeUtils.resolveColor(\n                    context,\n                    androidx.appcompat.R.attr.colorPrimary,\n                    Color.parseColor(\"#455A64\")\n                )\n            )\n        }\n\n        @CheckResult\n        @ColorInt\n        fun primaryColorDark(context: Context): Int {\n            return prefs(context).getInt(\n                ThemeStorePrefKeys.KEY_PRIMARY_COLOR_DARK,\n                ThemeUtils.resolveColor(\n                    context,\n                    androidx.appcompat.R.attr.colorPrimaryDark,\n                    Color.parseColor(\"#37474F\")\n                )\n            )\n        }\n\n        @CheckResult\n        @ColorInt\n        fun accentColor(context: Context = appCtx): Int {\n            return prefs(context).getInt(\n                ThemeStorePrefKeys.KEY_ACCENT_COLOR,\n                ThemeUtils.resolveColor(\n                    context,\n                    androidx.appcompat.R.attr.colorAccent,\n                    Color.parseColor(\"#263238\")\n                )\n            )\n        }\n\n        @CheckResult\n        @ColorInt\n        fun statusBarColor(context: Context, transparent: Boolean): Int {\n            return if (transparent) {\n                prefs(context).getInt(\n                    ThemeStorePrefKeys.KEY_STATUS_BAR_COLOR,\n                    primaryColor(context)\n                )\n            } else {\n                prefs(context).getInt(\n                    ThemeStorePrefKeys.KEY_STATUS_BAR_COLOR,\n                    primaryColorDark(context)\n                )\n            }\n        }\n\n        @CheckResult\n        @ColorInt\n        fun navigationBarColor(context: Context): Int {\n            return prefs(context).getInt(\n                ThemeStorePrefKeys.KEY_NAVIGATION_BAR_COLOR,\n                bottomBackground(context)\n            )\n        }\n\n        @CheckResult\n        @ColorInt\n        fun textColorPrimary(context: Context): Int {\n            return prefs(context).getInt(\n                ThemeStorePrefKeys.KEY_TEXT_COLOR_PRIMARY,\n                ThemeUtils.resolveColor(context, android.R.attr.textColorPrimary)\n            )\n        }\n\n        @CheckResult\n        @ColorInt\n        fun textColorPrimaryInverse(context: Context): Int {\n            return prefs(context).getInt(\n                ThemeStorePrefKeys.KEY_TEXT_COLOR_PRIMARY_INVERSE,\n                ThemeUtils.resolveColor(context, android.R.attr.textColorPrimaryInverse)\n            )\n        }\n\n        @CheckResult\n        @ColorInt\n        fun textColorSecondary(context: Context): Int {\n            return prefs(context).getInt(\n                ThemeStorePrefKeys.KEY_TEXT_COLOR_SECONDARY,\n                ThemeUtils.resolveColor(context, android.R.attr.textColorSecondary)\n            )\n        }\n\n        @CheckResult\n        @ColorInt\n        fun textColorSecondaryInverse(context: Context): Int {\n            return prefs(context).getInt(\n                ThemeStorePrefKeys.KEY_TEXT_COLOR_SECONDARY_INVERSE,\n                ThemeUtils.resolveColor(context, android.R.attr.textColorSecondaryInverse)\n            )\n        }\n\n        @CheckResult\n        @ColorInt\n        fun backgroundColor(context: Context = appCtx): Int {\n            return prefs(context).getInt(\n                ThemeStorePrefKeys.KEY_BACKGROUND_COLOR,\n                ThemeUtils.resolveColor(context, android.R.attr.colorBackground)\n            )\n        }\n\n        @CheckResult\n        @ColorInt\n        fun bottomBackground(context: Context = appCtx): Int {\n            return prefs(context).getInt(\n                ThemeStorePrefKeys.KEY_BOTTOM_BACKGROUND,\n                ThemeUtils.resolveColor(context, android.R.attr.colorBackground)\n            )\n        }\n\n        @CheckResult\n        fun coloredStatusBar(context: Context): Boolean {\n            return prefs(context).getBoolean(\n                ThemeStorePrefKeys.KEY_APPLY_PRIMARYDARK_STATUSBAR,\n                true\n            )\n        }\n\n        @CheckResult\n        fun coloredNavigationBar(context: Context): Boolean {\n            return prefs(context).getBoolean(ThemeStorePrefKeys.KEY_APPLY_PRIMARY_NAVBAR, false)\n        }\n\n        @CheckResult\n        fun autoGeneratePrimaryDark(context: Context): Boolean {\n            return prefs(context).getBoolean(ThemeStorePrefKeys.KEY_AUTO_GENERATE_PRIMARYDARK, true)\n        }\n\n        @CheckResult\n        fun isConfigured(context: Context): Boolean {\n            return prefs(context).getBoolean(ThemeStorePrefKeys.IS_CONFIGURED_KEY, false)\n        }\n\n        @SuppressLint(\"CommitPrefEdits\")\n        fun isConfigured(context: Context, version: Int): Boolean {\n            val prefs = prefs(context)\n            val lastVersion = prefs.getInt(ThemeStorePrefKeys.IS_CONFIGURED_VERSION_KEY, -1)\n            if (version > lastVersion) {\n                prefs.edit().putInt(ThemeStorePrefKeys.IS_CONFIGURED_VERSION_KEY, version).apply()\n                return false\n            }\n            return true\n        }\n    }\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/lib/theme/ThemeStoreInterface.kt",
    "content": "package io.legado.app.lib.theme\n\n\nimport androidx.annotation.AttrRes\nimport androidx.annotation.ColorInt\nimport androidx.annotation.ColorRes\n\n/**\n * @author Aidan Follestad (afollestad), Karim Abou Zeid (kabouzeid)\n */\ninternal interface ThemeStoreInterface {\n\n    // Primary colors\n\n    fun primaryColor(@ColorInt color: Int): ThemeStore\n\n    fun primaryColorRes(@ColorRes colorRes: Int): ThemeStore\n\n    fun primaryColorAttr(@AttrRes colorAttr: Int): ThemeStore\n\n    fun autoGeneratePrimaryDark(autoGenerate: Boolean): ThemeStore\n\n    fun primaryColorDark(@ColorInt color: Int): ThemeStore\n\n    fun primaryColorDarkRes(@ColorRes colorRes: Int): ThemeStore\n\n    fun primaryColorDarkAttr(@AttrRes colorAttr: Int): ThemeStore\n\n    // Accent colors\n\n    fun accentColor(@ColorInt color: Int): ThemeStore\n\n    fun accentColorRes(@ColorRes colorRes: Int): ThemeStore\n\n    fun accentColorAttr(@AttrRes colorAttr: Int): ThemeStore\n\n    // Status bar color\n\n    fun statusBarColor(@ColorInt color: Int): ThemeStore\n\n    fun statusBarColorRes(@ColorRes colorRes: Int): ThemeStore\n\n    fun statusBarColorAttr(@AttrRes colorAttr: Int): ThemeStore\n\n    // Navigation bar color\n\n    fun navigationBarColor(@ColorInt color: Int): ThemeStore\n\n    fun navigationBarColorRes(@ColorRes colorRes: Int): ThemeStore\n\n    fun navigationBarColorAttr(@AttrRes colorAttr: Int): ThemeStore\n\n    // Primary text color\n\n    fun textColorPrimary(@ColorInt color: Int): ThemeStore\n\n    fun textColorPrimaryRes(@ColorRes colorRes: Int): ThemeStore\n\n    fun textColorPrimaryAttr(@AttrRes colorAttr: Int): ThemeStore\n\n    fun textColorPrimaryInverse(@ColorInt color: Int): ThemeStore\n\n    fun textColorPrimaryInverseRes(@ColorRes colorRes: Int): ThemeStore\n\n    fun textColorPrimaryInverseAttr(@AttrRes colorAttr: Int): ThemeStore\n\n    // Secondary text color\n\n    fun textColorSecondary(@ColorInt color: Int): ThemeStore\n\n    fun textColorSecondaryRes(@ColorRes colorRes: Int): ThemeStore\n\n    fun textColorSecondaryAttr(@AttrRes colorAttr: Int): ThemeStore\n\n    fun textColorSecondaryInverse(@ColorInt color: Int): ThemeStore\n\n    fun textColorSecondaryInverseRes(@ColorRes colorRes: Int): ThemeStore\n\n    fun textColorSecondaryInverseAttr(@AttrRes colorAttr: Int): ThemeStore\n\n    // Background\n\n    fun backgroundColor(@ColorInt color: Int): ThemeStore\n\n    fun bottomBackground(@ColorInt color: Int): ThemeStore\n\n    // Commit/apply\n\n    fun apply()\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/lib/theme/ThemeStorePrefKeys.kt",
    "content": "package io.legado.app.lib.theme\n\n/**\n * @author Aidan Follestad (afollestad), Karim Abou Zeid (kabouzeid)\n */\nobject ThemeStorePrefKeys {\n\n    const val CONFIG_PREFS_KEY_DEFAULT = \"app_themes\"\n    const val IS_CONFIGURED_KEY = \"is_configured\"\n    const val IS_CONFIGURED_VERSION_KEY = \"is_configured_version\"\n    const val VALUES_CHANGED = \"values_changed\"\n\n    const val KEY_PRIMARY_COLOR = \"primary_color\"\n    const val KEY_PRIMARY_COLOR_DARK = \"primary_color_dark\"\n    const val KEY_ACCENT_COLOR = \"accent_color\"\n    const val KEY_STATUS_BAR_COLOR = \"status_bar_color\"\n    const val KEY_NAVIGATION_BAR_COLOR = \"navigation_bar_color\"\n\n    const val KEY_TEXT_COLOR_PRIMARY = \"text_color_primary\"\n    const val KEY_TEXT_COLOR_PRIMARY_INVERSE = \"text_color_primary_inverse\"\n    const val KEY_TEXT_COLOR_SECONDARY = \"text_color_secondary\"\n    const val KEY_TEXT_COLOR_SECONDARY_INVERSE = \"text_color_secondary_inverse\"\n\n    const val KEY_BACKGROUND_COLOR = \"backgroundColor\"\n    const val KEY_BOTTOM_BACKGROUND = \"bottomBackground\"\n\n    const val KEY_APPLY_PRIMARYDARK_STATUSBAR = \"apply_primarydark_statusbar\"\n    const val KEY_APPLY_PRIMARY_NAVBAR = \"apply_primary_navbar\"\n    const val KEY_AUTO_GENERATE_PRIMARYDARK = \"auto_generate_primarydark\"\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/lib/theme/ThemeUtils.kt",
    "content": "package io.legado.app.lib.theme\n\nimport android.content.Context\nimport android.graphics.drawable.Drawable\nimport androidx.annotation.AttrRes\n\n/**\n * @author Aidan Follestad (afollestad)\n */\nobject ThemeUtils {\n\n    @JvmOverloads\n    fun resolveColor(context: Context, @AttrRes attr: Int, fallback: Int = 0): Int {\n        val a = context.theme.obtainStyledAttributes(intArrayOf(attr))\n        return try {\n            a.getColor(0, fallback)\n        } catch (e: Exception) {\n            fallback\n        } finally {\n            a.recycle()\n        }\n    }\n\n    @JvmOverloads\n    fun resolveFloat(context: Context, @AttrRes attr: Int, fallback: Float = 0.0f): Float {\n        val a = context.theme.obtainStyledAttributes(intArrayOf(attr))\n        return try {\n            a.getFloat(0, fallback)\n        } catch (e: Exception) {\n            fallback\n        } finally {\n            a.recycle()\n        }\n    }\n\n    fun resolveDrawable(context: Context, @AttrRes attr: Int): Drawable? {\n        val a = context.theme.obtainStyledAttributes(intArrayOf(attr))\n        return try {\n            a.getDrawable(0)\n        } finally {\n            a.recycle()\n        }\n    }\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/lib/theme/TintHelper.kt",
    "content": "package io.legado.app.lib.theme\n\nimport android.annotation.SuppressLint\nimport android.content.Context\nimport android.content.res.ColorStateList\nimport android.graphics.PorterDuff\nimport android.graphics.drawable.Drawable\nimport android.graphics.drawable.RippleDrawable\nimport android.view.View\nimport android.widget.Button\nimport android.widget.CheckBox\nimport android.widget.CheckedTextView\nimport android.widget.EditText\nimport android.widget.ImageView\nimport android.widget.ProgressBar\nimport android.widget.RadioButton\nimport android.widget.SeekBar\nimport android.widget.Switch\nimport android.widget.TextView\nimport androidx.annotation.CheckResult\nimport androidx.annotation.ColorInt\nimport androidx.appcompat.widget.AppCompatEditText\nimport androidx.appcompat.widget.SearchView\nimport androidx.appcompat.widget.SwitchCompat\nimport androidx.core.content.ContextCompat\nimport androidx.core.graphics.drawable.DrawableCompat\nimport androidx.core.widget.TextViewCompat\nimport com.google.android.material.floatingactionbutton.FloatingActionButton\nimport io.legado.app.R\nimport io.legado.app.utils.ColorUtils\n\n/**\n * @author afollestad, plusCubed\n */\n@Suppress(\"MemberVisibilityCanBePrivate\")\nobject TintHelper {\n\n    @SuppressLint(\"PrivateResource\")\n    @ColorInt\n    private fun getDefaultRippleColor(context: Context, useDarkRipple: Boolean): Int {\n        // Light ripple is actually translucent black, and vice versa\n        return ContextCompat.getColor(\n            context, if (useDarkRipple)\n                androidx.appcompat.R.color.ripple_material_light\n            else\n                androidx.appcompat.R.color.ripple_material_dark\n        )\n    }\n\n    private fun getDisabledColorStateList(\n        @ColorInt normal: Int,\n        @ColorInt disabled: Int\n    ): ColorStateList {\n        return ColorStateList(\n            arrayOf(\n                intArrayOf(-android.R.attr.state_enabled),\n                intArrayOf(android.R.attr.state_enabled)\n            ), intArrayOf(disabled, normal)\n        )\n    }\n\n    fun setTintSelector(view: View, @ColorInt color: Int, darker: Boolean, useDarkTheme: Boolean) {\n        val isColorLight = ColorUtils.isColorLight(color)\n        val disabled = ContextCompat.getColor(\n            view.context,\n            if (useDarkTheme) R.color.ate_button_disabled_dark else R.color.ate_button_disabled_light\n        )\n        val pressed = ColorUtils.shiftColor(color, if (darker) 0.9f else 1.1f)\n        val activated = ColorUtils.shiftColor(color, if (darker) 1.1f else 0.9f)\n        val rippleColor = getDefaultRippleColor(view.context, isColorLight)\n        val textColor = ContextCompat.getColor(\n            view.context,\n            if (isColorLight) R.color.ate_primary_text_light else R.color.ate_primary_text_dark\n        )\n\n        val sl: ColorStateList\n        when (view) {\n            is Button -> {\n                sl = getDisabledColorStateList(color, disabled)\n                if (view.getBackground() is RippleDrawable) {\n                    val rd = view.getBackground() as RippleDrawable\n                    rd.setColor(ColorStateList.valueOf(rippleColor))\n                }\n                // Disabled text color state for buttons, may get overridden later by ATE tags\n                view.setTextColor(\n                    getDisabledColorStateList(\n                        textColor,\n                        ContextCompat.getColor(\n                            view.getContext(),\n                            if (useDarkTheme) R.color.ate_button_text_disabled_dark else R.color.ate_button_text_disabled_light\n                        )\n                    )\n                )\n            }\n            is FloatingActionButton -> {\n                // FloatingActionButton doesn't support disabled state?\n                sl = ColorStateList(\n                    arrayOf(\n                        intArrayOf(-android.R.attr.state_pressed),\n                        intArrayOf(android.R.attr.state_pressed)\n                    ), intArrayOf(color, pressed)\n                )\n\n                view.rippleColor = rippleColor\n                view.backgroundTintList = sl\n                if (view.drawable != null)\n                    view.setImageDrawable(createTintedDrawable(view.drawable, textColor))\n                return\n            }\n            else -> {\n                sl = ColorStateList(\n                    arrayOf(\n                        intArrayOf(-android.R.attr.state_enabled),\n                        intArrayOf(android.R.attr.state_enabled),\n                        intArrayOf(android.R.attr.state_enabled, android.R.attr.state_pressed),\n                        intArrayOf(android.R.attr.state_enabled, android.R.attr.state_activated),\n                        intArrayOf(android.R.attr.state_enabled, android.R.attr.state_checked)\n                    ),\n                    intArrayOf(disabled, color, pressed, activated, activated)\n                )\n            }\n        }\n\n        var drawable: Drawable? = view.background\n        if (drawable != null) {\n            drawable = createTintedDrawable(drawable, sl)\n            ViewUtils.setBackgroundCompat(view, drawable)\n        }\n\n        if (view is TextView && view !is Button) {\n            view.setTextColor(\n                getDisabledColorStateList(\n                    textColor,\n                    ContextCompat.getColor(\n                        view.getContext(),\n                        if (isColorLight) R.color.ate_text_disabled_light else R.color.ate_text_disabled_dark\n                    )\n                )\n            )\n        }\n    }\n\n    fun setTintAuto(\n        view: View,\n        @ColorInt color: Int,\n        isBackground: Boolean,\n        isDark: Boolean\n    ) {\n        var isBg = isBackground\n        if (!isBg) {\n            when (view) {\n                is RadioButton -> setTint(view, color, isDark)\n                is SeekBar -> setTint(view, color, isDark)\n                is ProgressBar -> setTint(view, color)\n                is AppCompatEditText -> setTint(view, color, isDark)\n                is CheckBox -> setTint(view, color, isDark)\n                is CheckedTextView -> setTint(view, color, isDark)\n                is ImageView -> setTint(view, color)\n                is Switch -> setTint(view, color, isDark)\n                is SwitchCompat -> setTint(view, color, isDark)\n                is SearchView -> {\n                    val iconIdS =\n                        intArrayOf(\n                            androidx.appcompat.R.id.search_button,\n                            androidx.appcompat.R.id.search_close_btn,\n                            androidx.appcompat.R.id.search_go_btn,\n                            androidx.appcompat.R.id.search_voice_btn,\n                            androidx.appcompat.R.id.search_mag_icon\n                        )\n                    for (iconId in iconIdS) {\n                        val icon = view.findViewById<ImageView>(iconId)\n                        if (icon != null) {\n                            setTint(icon, color)\n                        }\n                    }\n                }\n                else -> isBg = true\n            }\n            if (!isBg && view.background is RippleDrawable) {\n                // Ripples for the above views (e.g. when you tap and hold a switch or checkbox)\n                val rd = view.background as RippleDrawable\n                @SuppressLint(\"PrivateResource\") val unchecked = ContextCompat.getColor(\n                    view.context,\n                    if (isDark) androidx.appcompat.R.color.ripple_material_dark else androidx.appcompat.R.color.ripple_material_light\n                )\n                val checked = ColorUtils.adjustAlpha(color, 0.4f)\n                val sl = ColorStateList(\n                    arrayOf(\n                        intArrayOf(-android.R.attr.state_activated, -android.R.attr.state_checked),\n                        intArrayOf(android.R.attr.state_activated),\n                        intArrayOf(android.R.attr.state_checked)\n                    ),\n                    intArrayOf(unchecked, checked, checked)\n                )\n                rd.setColor(sl)\n            }\n        }\n        if (isBg) {\n            // Need to tint the isBackground of a view\n            if (view is FloatingActionButton || view is Button) {\n                setTintSelector(view, color, false, isDark)\n            } else if (view.background != null) {\n                var drawable: Drawable? = view.background\n                if (drawable != null) {\n                    drawable = createTintedDrawable(drawable, color)\n                    ViewUtils.setBackgroundCompat(view, drawable)\n                }\n            }\n        }\n    }\n\n    @SuppressLint(\"PrivateResource\")\n    fun setTint(radioButton: RadioButton, @ColorInt color: Int, useDarker: Boolean) {\n        val sl = ColorStateList(\n            arrayOf(\n                intArrayOf(-android.R.attr.state_enabled),\n                intArrayOf(android.R.attr.state_enabled, -android.R.attr.state_checked),\n                intArrayOf(android.R.attr.state_enabled, android.R.attr.state_checked)\n            ), intArrayOf(\n                // Radio button includes own alpha for disabled state\n                ColorUtils.stripAlpha(\n                    ContextCompat.getColor(\n                        radioButton.context,\n                        if (useDarker) R.color.ate_control_disabled_dark else R.color.ate_control_disabled_light\n                    )\n                ),\n                ContextCompat.getColor(\n                    radioButton.context,\n                    if (useDarker) R.color.ate_control_normal_dark else R.color.ate_control_normal_light\n                ),\n                color\n            )\n        )\n        radioButton.buttonTintList = sl\n    }\n\n    fun setTint(seekBar: SeekBar, @ColorInt color: Int, useDarker: Boolean) {\n        val s1 = getDisabledColorStateList(\n            color,\n            ContextCompat.getColor(\n                seekBar.context,\n                if (useDarker) R.color.ate_control_disabled_dark else R.color.ate_control_disabled_light\n            )\n        )\n        seekBar.thumbTintList = s1\n        seekBar.progressTintList = s1\n    }\n\n    @JvmOverloads\n    fun setTint(\n        progressBar: ProgressBar, @ColorInt color: Int,\n        skipIndeterminate: Boolean = false\n    ) {\n        val sl = ColorStateList.valueOf(color)\n        progressBar.progressTintList = sl\n        progressBar.secondaryProgressTintList = sl\n        if (!skipIndeterminate)\n            progressBar.indeterminateTintList = sl\n    }\n\n\n    @SuppressLint(\"RestrictedApi\")\n    fun setTint(editText: AppCompatEditText, @ColorInt color: Int, useDarker: Boolean) {\n        val editTextColorStateList = ColorStateList(\n            arrayOf(\n                intArrayOf(-android.R.attr.state_enabled),\n                intArrayOf(\n                    android.R.attr.state_enabled,\n                    -android.R.attr.state_pressed,\n                    -android.R.attr.state_focused\n                ),\n                intArrayOf()\n            ),\n            intArrayOf(\n                ContextCompat.getColor(\n                    editText.context,\n                    if (useDarker) R.color.ate_text_disabled_dark else R.color.ate_text_disabled_light\n                ),\n                ContextCompat.getColor(\n                    editText.context,\n                    if (useDarker) R.color.ate_control_normal_dark else R.color.ate_control_normal_light\n                ),\n                color\n            )\n        )\n        editText.supportBackgroundTintList = editTextColorStateList\n        setCursorTint(editText, color)\n    }\n\n    @SuppressLint(\"PrivateResource\")\n    fun setTint(box: CheckBox, @ColorInt color: Int, useDarker: Boolean) {\n        val sl = ColorStateList(\n            arrayOf(\n                intArrayOf(-android.R.attr.state_enabled),\n                intArrayOf(android.R.attr.state_enabled, -android.R.attr.state_checked),\n                intArrayOf(android.R.attr.state_enabled, android.R.attr.state_checked)\n            ),\n            intArrayOf(\n                ContextCompat.getColor(\n                    box.context,\n                    if (useDarker) R.color.ate_control_disabled_dark else R.color.ate_control_disabled_light\n                ),\n                ContextCompat.getColor(\n                    box.context,\n                    if (useDarker) R.color.ate_control_normal_dark else R.color.ate_control_normal_light\n                ),\n                color\n            )\n        )\n        box.buttonTintList = sl\n    }\n\n    @SuppressLint(\"PrivateResource\")\n    fun setTint(checkedTextView: CheckedTextView, @ColorInt color: Int, useDarker: Boolean) {\n        val sl = ColorStateList(\n            arrayOf(\n                intArrayOf(-android.R.attr.state_enabled),\n                intArrayOf(android.R.attr.state_enabled, -android.R.attr.state_checked),\n                intArrayOf(android.R.attr.state_enabled, android.R.attr.state_checked)\n            ), intArrayOf(\n                ContextCompat.getColor(\n                    checkedTextView.context,\n                    if (useDarker) R.color.ate_control_disabled_dark else R.color.ate_control_disabled_light\n                ),\n                ContextCompat.getColor(\n                    checkedTextView.context,\n                    if (useDarker) R.color.ate_control_normal_dark else R.color.ate_control_normal_light\n                ),\n                color\n            )\n        )\n        checkedTextView.checkMarkTintList = sl\n        TextViewCompat.setCompoundDrawableTintList(checkedTextView, sl)\n    }\n\n    fun setTint(image: ImageView, @ColorInt color: Int) {\n        image.setColorFilter(color, PorterDuff.Mode.SRC_ATOP)\n    }\n\n    private fun modifySwitchDrawable(\n        context: Context,\n        from: Drawable,\n        @ColorInt tint: Int,\n        thumb: Boolean,\n        compatSwitch: Boolean,\n        useDarker: Boolean\n    ): Drawable? {\n        var tint1 = tint\n        if (useDarker) {\n            tint1 = ColorUtils.shiftColor(tint1, 1.1f)\n        }\n        tint1 = ColorUtils.adjustAlpha(tint1, if (compatSwitch && !thumb) 0.5f else 1.0f)\n        val disabled: Int\n        var normal: Int\n        if (thumb) {\n            disabled = ContextCompat.getColor(\n                context,\n                if (useDarker) R.color.ate_switch_thumb_disabled_dark else R.color.ate_switch_thumb_disabled_light\n            )\n            normal = ContextCompat.getColor(\n                context,\n                if (useDarker) R.color.ate_switch_thumb_normal_dark else R.color.ate_switch_thumb_normal_light\n            )\n        } else {\n            disabled = ContextCompat.getColor(\n                context,\n                if (useDarker) R.color.ate_switch_track_disabled_dark else R.color.ate_switch_track_disabled_light\n            )\n            normal = ContextCompat.getColor(\n                context,\n                if (useDarker) R.color.ate_switch_track_normal_dark else R.color.ate_switch_track_normal_light\n            )\n        }\n\n        // Stock switch includes its own alpha\n        if (!compatSwitch) {\n            normal = ColorUtils.stripAlpha(normal)\n        }\n\n        val sl = ColorStateList(\n            arrayOf(\n                intArrayOf(-android.R.attr.state_enabled),\n                intArrayOf(\n                    android.R.attr.state_enabled,\n                    -android.R.attr.state_activated,\n                    -android.R.attr.state_checked\n                ),\n                intArrayOf(android.R.attr.state_enabled, android.R.attr.state_activated),\n                intArrayOf(android.R.attr.state_enabled, android.R.attr.state_checked)\n            ),\n            intArrayOf(disabled, normal, tint1, tint1)\n        )\n        return createTintedDrawable(from, sl)\n    }\n\n    fun setTint(\n        @SuppressLint(\"UseSwitchCompatOrMaterialCode\") switchView: Switch,\n        @ColorInt color: Int,\n        useDarker: Boolean\n    ) {\n        if (switchView.trackDrawable != null) {\n            switchView.trackDrawable = modifySwitchDrawable(\n                switchView.context,\n                switchView.trackDrawable,\n                color,\n                thumb = false,\n                compatSwitch = false,\n                useDarker = useDarker\n            )\n        }\n        if (switchView.thumbDrawable != null) {\n            switchView.thumbDrawable = modifySwitchDrawable(\n                switchView.context,\n                switchView.thumbDrawable,\n                color,\n                thumb = true,\n                compatSwitch = false,\n                useDarker = useDarker\n            )\n        }\n    }\n\n    fun setTint(switchView: SwitchCompat, @ColorInt color: Int, useDarker: Boolean) {\n        if (switchView.trackDrawable != null) {\n            switchView.trackDrawable = modifySwitchDrawable(\n                switchView.context,\n                switchView.trackDrawable,\n                color,\n                thumb = false,\n                compatSwitch = true,\n                useDarker = useDarker\n            )\n        }\n        if (switchView.thumbDrawable != null) {\n            switchView.thumbDrawable = modifySwitchDrawable(\n                switchView.context,\n                switchView.thumbDrawable,\n                color,\n                thumb = true,\n                compatSwitch = true,\n                useDarker = useDarker\n            )\n        }\n    }\n\n    // This returns a NEW Drawable because of the mutate() call. The mutate() call is necessary because Drawables with the same resource have shared states otherwise.\n    @CheckResult\n    fun createTintedDrawable(drawable: Drawable?, @ColorInt color: Int): Drawable? {\n        var drawable1: Drawable? = drawable ?: return null\n        drawable1 = DrawableCompat.wrap(drawable1!!.mutate())\n        DrawableCompat.setTintMode(drawable1, PorterDuff.Mode.SRC_IN)\n        DrawableCompat.setTint(drawable1, color)\n        return drawable1\n    }\n\n    // This returns a NEW Drawable because of the mutate() call. The mutate() call is necessary because Drawables with the same resource have shared states otherwise.\n    @CheckResult\n    fun createTintedDrawable(drawable: Drawable?, sl: ColorStateList): Drawable? {\n        var drawable1: Drawable? = drawable ?: return null\n        drawable1 = DrawableCompat.wrap(drawable1!!.mutate())\n        DrawableCompat.setTintList(drawable1, sl)\n        return drawable1\n    }\n\n    @SuppressLint(\"DiscouragedPrivateApi\", \"SoonBlockedPrivateApi\")\n    fun setCursorTint(editText: EditText, @ColorInt color: Int) {\n        try {\n            val fCursorDrawableRes = TextView::class.java.getDeclaredField(\"mCursorDrawableRes\")\n            fCursorDrawableRes.isAccessible = true\n            val mCursorDrawableRes = fCursorDrawableRes.getInt(editText)\n            val fEditor = TextView::class.java.getDeclaredField(\"mEditor\")\n            fEditor.isAccessible = true\n            val editor = fEditor.get(editText)\n            val clazz = editor.javaClass\n            val fCursorDrawable = clazz.getDeclaredField(\"mCursorDrawable\")\n            fCursorDrawable.isAccessible = true\n            val drawables = arrayOfNulls<Drawable>(2)\n            drawables[0] = ContextCompat.getDrawable(editText.context, mCursorDrawableRes)\n            drawables[0] = createTintedDrawable(drawables[0], color)\n            drawables[1] = ContextCompat.getDrawable(editText.context, mCursorDrawableRes)\n            drawables[1] = createTintedDrawable(drawables[1], color)\n            fCursorDrawable.set(editor, drawables)\n        } catch (ignored: Exception) {\n        }\n\n    }\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/lib/theme/ViewUtils.kt",
    "content": "package io.legado.app.lib.theme\n\nimport android.graphics.drawable.ColorDrawable\nimport android.graphics.drawable.Drawable\nimport android.graphics.drawable.TransitionDrawable\nimport android.view.View\nimport android.view.ViewTreeObserver\nimport androidx.annotation.ColorInt\nimport io.legado.app.utils.DrawableUtils\n\n/**\n * @author Karim Abou Zeid (kabouzeid)\n */\n@Suppress(\"unused\")\nobject ViewUtils {\n\n    fun removeOnGlobalLayoutListener(v: View, listener: ViewTreeObserver.OnGlobalLayoutListener) {\n        v.viewTreeObserver.removeOnGlobalLayoutListener(listener)\n    }\n\n    fun setBackgroundCompat(view: View, drawable: Drawable?) {\n        view.background = drawable\n    }\n\n    fun setBackgroundTransition(view: View, newDrawable: Drawable): TransitionDrawable {\n        val transition = DrawableUtils.createTransitionDrawable(view.background, newDrawable)\n        setBackgroundCompat(view, transition)\n        return transition\n    }\n\n    fun setBackgroundColorTransition(view: View, @ColorInt newColor: Int): TransitionDrawable {\n        val oldColor = view.background\n\n        val start = oldColor ?: ColorDrawable(view.solidColor)\n        val end = ColorDrawable(newColor)\n\n        val transition = DrawableUtils.createTransitionDrawable(start, end)\n\n        setBackgroundCompat(view, transition)\n\n        return transition\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/lib/theme/view/ThemeBottomNavigationVIew.kt",
    "content": "package io.legado.app.lib.theme.view\n\nimport android.content.Context\nimport android.graphics.Color\nimport android.graphics.drawable.ColorDrawable\nimport android.util.AttributeSet\nimport android.view.LayoutInflater\nimport android.view.ViewGroup\nimport androidx.core.view.ViewCompat\nimport com.google.android.material.bottomnavigation.BottomNavigationView\nimport io.legado.app.databinding.ViewNavigationBadgeBinding\nimport io.legado.app.help.config.AppConfig\nimport io.legado.app.lib.theme.Selector\nimport io.legado.app.lib.theme.ThemeStore\nimport io.legado.app.lib.theme.bottomBackground\nimport io.legado.app.lib.theme.getSecondaryTextColor\nimport io.legado.app.ui.widget.text.BadgeView\nimport io.legado.app.utils.ColorUtils\n\nclass ThemeBottomNavigationVIew(context: Context, attrs: AttributeSet) :\n    BottomNavigationView(context, attrs) {\n\n    init {\n        val bgColor = context.bottomBackground\n        setBackgroundColor(bgColor)\n        val textIsDark = ColorUtils.isColorLight(bgColor)\n        val textColor = context.getSecondaryTextColor(textIsDark)\n        val colorStateList = Selector.colorBuild()\n            .setDefaultColor(textColor)\n            .setSelectedColor(ThemeStore.accentColor(context)).create()\n        itemIconTintList = colorStateList\n        itemTextColor = colorStateList\n\n        if (AppConfig.isEInkMode) {\n            isItemHorizontalTranslationEnabled = false\n            itemBackground = ColorDrawable(Color.TRANSPARENT)\n        }\n\n        ViewCompat.setOnApplyWindowInsetsListener(this, null)\n    }\n\n    fun addBadgeView(index: Int): BadgeView {\n        //获取底部菜单view\n        val menuView = getChildAt(0) as ViewGroup\n        //获取第index个itemView\n        val itemView = menuView.getChildAt(index) as ViewGroup\n        val badgeBinding = ViewNavigationBadgeBinding.inflate(LayoutInflater.from(context))\n        itemView.addView(badgeBinding.root)\n        return badgeBinding.viewBadge\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/lib/theme/view/ThemeCheckBox.kt",
    "content": "package io.legado.app.lib.theme.view\n\nimport android.content.Context\nimport android.util.AttributeSet\nimport androidx.appcompat.widget.AppCompatCheckBox\nimport io.legado.app.lib.theme.accentColor\nimport io.legado.app.utils.applyTint\n\nclass ThemeCheckBox(context: Context, attrs: AttributeSet) : AppCompatCheckBox(context, attrs) {\n\n    private var isUserAction = false\n\n    init {\n        if (!isInEditMode) {\n            applyTint(context.accentColor)\n        }\n    }\n\n    override fun performClick(): Boolean {\n        isUserAction = true\n        val result = super.performClick()\n        isUserAction = false\n        return result\n    }\n\n    fun setOnUserCheckedChangeListener(listener: ((Boolean) -> Unit)?) {\n        if (listener == null) {\n            return super.setOnCheckedChangeListener(null)\n        }\n        super.setOnCheckedChangeListener { _, isChecked ->\n            if (isUserAction) {\n                listener.invoke(isChecked)\n            }\n        }\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/lib/theme/view/ThemeEditText.kt",
    "content": "package io.legado.app.lib.theme.view\n\nimport android.content.Context\nimport android.os.Build\nimport android.util.AttributeSet\nimport androidx.appcompat.widget.AppCompatEditText\nimport io.legado.app.lib.theme.accentColor\nimport io.legado.app.utils.applyTint\n\nclass ThemeEditText @JvmOverloads constructor(\n    context: Context,\n    attrs: AttributeSet? = null\n) : AppCompatEditText(context, attrs) {\n\n    init {\n        if (!isInEditMode) {\n            applyTint(context.accentColor)\n        }\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM) {\n            isLocalePreferredLineHeightForMinimumUsed = false\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/lib/theme/view/ThemeProgressBar.kt",
    "content": "package io.legado.app.lib.theme.view\n\nimport android.content.Context\nimport android.util.AttributeSet\nimport android.widget.ProgressBar\nimport io.legado.app.lib.theme.accentColor\nimport io.legado.app.utils.applyTint\n\nclass ThemeProgressBar(context: Context, attrs: AttributeSet) : ProgressBar(context, attrs) {\n\n    init {\n        if (!isInEditMode) {\n            applyTint(context.accentColor)\n        }\n    }\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/lib/theme/view/ThemeRadioButton.kt",
    "content": "package io.legado.app.lib.theme.view\n\nimport android.content.Context\nimport android.util.AttributeSet\nimport androidx.appcompat.widget.AppCompatRadioButton\nimport io.legado.app.lib.theme.accentColor\nimport io.legado.app.utils.applyTint\n\nclass ThemeRadioButton(context: Context, attrs: AttributeSet) :\n    AppCompatRadioButton(context, attrs) {\n\n    private var isUserAction = false\n\n    init {\n        if (!isInEditMode) {\n            applyTint(context.accentColor)\n        }\n    }\n\n    override fun performClick(): Boolean {\n        isUserAction = true\n        val result = super.performClick()\n        isUserAction = false\n        return result\n    }\n\n    fun setOnUserCheckedChangeListener(listener: ((Boolean) -> Unit)?) {\n        if (listener == null) {\n            return super.setOnCheckedChangeListener(null)\n        }\n        super.setOnCheckedChangeListener { _, isChecked ->\n            if (isUserAction) {\n                listener.invoke(isChecked)\n            }\n        }\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/lib/theme/view/ThemeRadioNoButton.kt",
    "content": "package io.legado.app.lib.theme.view\n\nimport android.content.Context\nimport android.graphics.Color\nimport android.util.AttributeSet\nimport androidx.appcompat.widget.AppCompatRadioButton\nimport androidx.appcompat.widget.TooltipCompat\nimport io.legado.app.R\nimport io.legado.app.lib.theme.Selector\nimport io.legado.app.lib.theme.accentColor\nimport io.legado.app.lib.theme.bottomBackground\nimport io.legado.app.lib.theme.getPrimaryTextColor\nimport io.legado.app.utils.ColorUtils\nimport io.legado.app.utils.dpToPx\nimport io.legado.app.utils.getCompatColor\n\nclass ThemeRadioNoButton(context: Context, attrs: AttributeSet) :\n    AppCompatRadioButton(context, attrs) {\n\n    private val isBottomBackground: Boolean\n\n    init {\n        val typedArray = context.obtainStyledAttributes(attrs, R.styleable.ThemeRadioNoButton)\n        isBottomBackground =\n            typedArray.getBoolean(R.styleable.ThemeRadioNoButton_isBottomBackground, false)\n        typedArray.recycle()\n        initTheme()\n        TooltipCompat.setTooltipText(this, text)\n    }\n\n    private fun initTheme() {\n        when {\n            isInEditMode -> Unit\n            isBottomBackground -> {\n                val accentColor = context.accentColor\n                val isLight = ColorUtils.isColorLight(context.bottomBackground)\n                val textColor = context.getPrimaryTextColor(isLight)\n                val checkedTextColor = if (ColorUtils.isColorLight(accentColor)) {\n                    Color.BLACK\n                } else {\n                    Color.WHITE\n                }\n                background = Selector.shapeBuild()\n                    .setCornerRadius(2.dpToPx())\n                    .setStrokeWidth(2.dpToPx())\n                    .setCheckedBgColor(accentColor)\n                    .setCheckedStrokeColor(accentColor)\n                    .setDefaultStrokeColor(textColor)\n                    .create()\n                setTextColor(\n                    Selector.colorBuild()\n                        .setDefaultColor(textColor)\n                        .setCheckedColor(checkedTextColor)\n                        .create()\n                )\n            }\n            else -> {\n                val accentColor = context.accentColor\n                val defaultTextColor = context.getCompatColor(R.color.primaryText)\n                val checkedTextColor = if (ColorUtils.isColorLight(accentColor)) {\n                    Color.BLACK\n                } else {\n                    Color.WHITE\n                }\n                background = Selector.shapeBuild()\n                    .setCornerRadius(2.dpToPx())\n                    .setStrokeWidth(2.dpToPx())\n                    .setCheckedBgColor(accentColor)\n                    .setCheckedStrokeColor(accentColor)\n                    .setDefaultStrokeColor(defaultTextColor)\n                    .create()\n                setTextColor(\n                    Selector.colorBuild()\n                        .setDefaultColor(defaultTextColor)\n                        .setCheckedColor(checkedTextColor)\n                        .create()\n                )\n            }\n        }\n\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/lib/theme/view/ThemeSeekBar.kt",
    "content": "package io.legado.app.lib.theme.view\n\nimport android.content.Context\nimport android.util.AttributeSet\nimport androidx.appcompat.widget.AppCompatSeekBar\nimport io.legado.app.lib.theme.accentColor\nimport io.legado.app.utils.applyTint\n\n/**\n * @author Aidan Follestad (afollestad)\n */\nclass ThemeSeekBar(context: Context, attrs: AttributeSet) : AppCompatSeekBar(context, attrs) {\n\n    init {\n        if (!isInEditMode) {\n            applyTint(context.accentColor)\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/lib/theme/view/ThemeSwitch.kt",
    "content": "package io.legado.app.lib.theme.view\n\nimport android.content.Context\nimport android.util.AttributeSet\nimport androidx.appcompat.widget.SwitchCompat\nimport io.legado.app.lib.theme.accentColor\nimport io.legado.app.utils.applyTint\n\n/**\n * @author Aidan Follestad (afollestad)\n */\nclass ThemeSwitch(context: Context, attrs: AttributeSet) : SwitchCompat(context, attrs) {\n\n    private var isUserAction = false\n\n    init {\n        if (!isInEditMode) {\n            applyTint(context.accentColor)\n        }\n    }\n\n    override fun performClick(): Boolean {\n        isUserAction = true\n        val result = super.performClick()\n        isUserAction = false\n        return result\n    }\n\n    fun setOnUserCheckedChangeListener(listener: ((Boolean) -> Unit)?) {\n        if (listener == null) {\n            return super.setOnCheckedChangeListener(null)\n        }\n        super.setOnCheckedChangeListener { _, isChecked ->\n            if (isUserAction) {\n                listener.invoke(isChecked)\n            }\n        }\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/lib/webdav/Authorization.kt",
    "content": "package io.legado.app.lib.webdav\n\nimport io.legado.app.data.appDb\nimport io.legado.app.data.entities.Server.WebDavConfig\nimport okhttp3.Credentials\nimport java.nio.charset.Charset\nimport java.nio.charset.StandardCharsets\n\ndata class Authorization(\n    val username: String,\n    val password: String,\n    val charset: Charset = StandardCharsets.ISO_8859_1\n) {\n\n    var name = \"Authorization\"\n        private set\n\n    var data: String = Credentials.basic(username, password, charset)\n        private set\n\n    override fun toString(): String {\n        return \"$username:$password\"\n    }\n\n    constructor(serverID: Long) : this(\n        appDb.serverDao.get(serverID)?.getWebDavConfig()\n            ?: throw WebDavException(\"Unexpected WebDav Authorization\")\n    )\n\n    constructor(webDavConfig: WebDavConfig) : this(webDavConfig.username, webDavConfig.password)\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/lib/webdav/WebDav.kt",
    "content": "package io.legado.app.lib.webdav\n\nimport android.annotation.SuppressLint\nimport android.net.Uri\nimport cn.hutool.core.net.URLDecoder\nimport io.legado.app.constant.AppLog\nimport io.legado.app.exception.NoStackTraceException\nimport io.legado.app.help.http.newCallResponse\nimport io.legado.app.help.http.okHttpClient\nimport io.legado.app.help.http.text\nimport io.legado.app.model.analyzeRule.AnalyzeUrl\nimport io.legado.app.model.analyzeRule.CustomUrl\nimport io.legado.app.utils.NetworkUtils\nimport io.legado.app.utils.findNS\nimport io.legado.app.utils.findNSPrefix\nimport io.legado.app.utils.printOnDebug\nimport io.legado.app.utils.toRequestBody\nimport kotlinx.coroutines.Dispatchers.IO\nimport kotlinx.coroutines.currentCoroutineContext\nimport kotlinx.coroutines.ensureActive\nimport kotlinx.coroutines.withContext\nimport okhttp3.HttpUrl.Companion.toHttpUrl\nimport okhttp3.Interceptor\nimport okhttp3.MediaType.Companion.toMediaType\nimport okhttp3.RequestBody.Companion.asRequestBody\nimport okhttp3.RequestBody.Companion.toRequestBody\nimport okhttp3.Response\nimport org.intellij.lang.annotations.Language\nimport org.jsoup.Jsoup\nimport org.jsoup.parser.Parser\nimport java.io.File\nimport java.io.FileOutputStream\nimport java.io.InputStream\nimport java.net.MalformedURLException\nimport java.net.URL\nimport java.time.ZonedDateTime\nimport java.time.format.DateTimeFormatter\nimport java.util.concurrent.TimeUnit\n\n@Suppress(\"unused\", \"MemberVisibilityCanBePrivate\")\nopen class WebDav(\n    val path: String,\n    val authorization: Authorization\n) {\n    companion object {\n\n        fun fromPath(path: String): WebDav {\n            val id = AnalyzeUrl(path).serverID ?: throw WebDavException(\"没有serverID\")\n            val authorization = Authorization(id)\n            return WebDav(path, authorization)\n        }\n\n        @SuppressLint(\"DateTimeFormatter\")\n        private val dateTimeFormatter = DateTimeFormatter.RFC_1123_DATE_TIME\n\n        // 指定返回哪些属性\n        @Language(\"xml\")\n        private const val DIR =\n            \"\"\"<?xml version=\"1.0\"?>\n            <a:propfind xmlns:a=\"DAV:\">\n                <a:prop>\n                    <a:displayname/>\n                    <a:resourcetype/>\n                    <a:getcontentlength/>\n                    <a:creationdate/>\n                    <a:getlastmodified/>\n                    %s\n                </a:prop>\n            </a:propfind>\"\"\"\n\n        @Language(\"xml\")\n        private const val EXISTS =\n            \"\"\"<?xml version=\"1.0\"?>\n            <propfind xmlns=\"DAV:\">\n               <prop>\n                  <resourcetype />\n               </prop>\n            </propfind>\"\"\"\n\n        private const val DEFAULT_CONTENT_TYPE = \"application/octet-stream\"\n    }\n\n\n    private val url: URL = URL(CustomUrl(path).getUrl())\n    private val httpUrl: String? by lazy {\n        val raw = url.toString()\n            .replace(\"davs://\", \"https://\")\n            .replace(\"dav://\", \"http://\")\n        return@lazy kotlin.runCatching {\n            raw.toHttpUrl().toString()\n        }.getOrNull()\n    }\n    private val webDavClient by lazy {\n        val authInterceptor = Interceptor { chain ->\n            var request = chain.request()\n            if (request.url.host.equals(host, true)) {\n                request = request\n                    .newBuilder()\n                    .header(authorization.name, authorization.data)\n                    .build()\n            }\n            chain.proceed(request)\n        }\n        okHttpClient.newBuilder().run {\n            callTimeout(0, TimeUnit.SECONDS)\n            interceptors().add(0, authInterceptor)\n            addNetworkInterceptor(authInterceptor)\n            build()\n        }\n    }\n    private val host: String?\n        get() = url.host?.let {\n            if (it.startsWith(\"[\")) {\n                it.substring(1, it.lastIndex)\n            } else {\n                it\n            }\n        }\n\n    /**\n     * 获取当前url文件信息\n     */\n    @Throws(WebDavException::class)\n    suspend fun getWebDavFile(): WebDavFile? {\n        return propFindResponse(depth = 0)?.let {\n            parseBody(it).firstOrNull()\n        }\n    }\n\n    /**\n     * 列出当前路径下的文件\n     * @return 文件列表\n     */\n    @Throws(WebDavException::class)\n    suspend fun listFiles(): List<WebDavFile> {\n        propFindResponse()?.let { body ->\n            return parseBody(body).filter {\n                it.path != path\n            }\n        }\n        return emptyList()\n    }\n\n    /**\n     * @param propsList 指定列出文件的哪些属性\n     */\n    @Throws(WebDavException::class)\n    private suspend fun propFindResponse(\n        propsList: List<String> = emptyList(),\n        depth: Int = 1\n    ): String? {\n        val requestProps = StringBuilder()\n        for (p in propsList) {\n            requestProps.append(\"<a:\").append(p).append(\"/>\\n\")\n        }\n        val requestPropsStr: String = if (requestProps.toString().isEmpty()) {\n            DIR.replace(\"%s\", \"\")\n        } else {\n            String.format(DIR, requestProps.toString() + \"\\n\")\n        }\n        val url = httpUrl ?: return null\n        return webDavClient.newCallResponse {\n            url(url)\n            addHeader(\"Depth\", depth.toString())\n            // 添加RequestBody对象，可以只返回的属性。如果设为null，则会返回全部属性\n            // 注意：尽量手动指定需要返回的属性。若返回全部属性，可能后由于Prop.java里没有该属性名，而崩溃。\n            val requestBody = requestPropsStr.toRequestBody(\"text/plain\".toMediaType())\n            method(\"PROPFIND\", requestBody)\n        }.apply {\n            checkResult(this)\n        }.body.text()\n    }\n\n    /**\n     * 解析webDav返回的xml\n     */\n    private fun parseBody(s: String): List<WebDavFile> {\n        val list = ArrayList<WebDavFile>()\n        val document = kotlin.runCatching {\n            Jsoup.parse(s, Parser.xmlParser())\n        }.getOrElse {\n            Jsoup.parse(s)\n        }\n        val ns = document.findNSPrefix(\"DAV:\")\n        val elements = document.findNS(\"response\", ns)\n        val urlStr = httpUrl ?: return list\n        val baseUrl = NetworkUtils.getBaseUrl(urlStr)\n        for (element in elements) {\n            //依然是优化支持 caddy 自建的 WebDav ，其目录后缀都为“/”, 所以删除“/”的判定，不然无法获取该目录项\n            val href = element.findNS(\"href\", ns)[0].text()\n            val hrefDecode = URLDecoder.decodeForPath(href, Charsets.UTF_8)\n            val fileName = hrefDecode.removeSuffix(\"/\").substringAfterLast(\"/\")\n            val webDavFile: WebDav\n            try {\n                val urlName = hrefDecode.ifEmpty {\n                    url.file.replace(\"/\", \"\")\n                }\n                val displayName = element\n                    .findNS(\"displayname\", ns)\n                    .firstOrNull()?.text()?.takeIf { it.isNotEmpty() }\n                    ?.let { URLDecoder.decodeForPath(it, Charsets.UTF_8) } ?: fileName\n                val contentType = element\n                    .findNS(\"getcontenttype\", ns)\n                    .firstOrNull()?.text().orEmpty()\n                val resourceType = element\n                    .findNS(\"resourcetype\", ns)\n                    .firstOrNull()?.html()?.trim().orEmpty()\n                val size = kotlin.runCatching {\n                    element.findNS(\"getcontentlength\", ns)\n                        .firstOrNull()?.text()?.toLong() ?: 0\n                }.getOrDefault(0)\n                val lastModify: Long = kotlin.runCatching {\n                    element.findNS(\"getlastmodified\", ns)\n                        .firstOrNull()?.text()?.let {\n                            ZonedDateTime.parse(it, dateTimeFormatter)\n                                .toInstant().toEpochMilli()\n                        }\n                }.getOrNull() ?: 0\n                var fullURL = NetworkUtils.getAbsoluteURL(baseUrl, hrefDecode)\n                if (WebDavFile.isDir(contentType, resourceType) && !fullURL.endsWith(\"/\")) {\n                    fullURL += \"/\"\n                }\n                webDavFile = WebDavFile(\n                    fullURL,\n                    authorization,\n                    displayName = displayName,\n                    urlName = urlName,\n                    size = size,\n                    contentType = contentType,\n                    resourceType = resourceType,\n                    lastModify = lastModify\n                )\n                list.add(webDavFile)\n            } catch (e: MalformedURLException) {\n                e.printOnDebug()\n            }\n        }\n        return list\n    }\n\n    /**\n     * 文件是否存在\n     */\n    suspend fun exists(): Boolean {\n        val url = httpUrl ?: return false\n        return kotlin.runCatching {\n            return webDavClient.newCallResponse {\n                url(url)\n                addHeader(\"Depth\", \"0\")\n                val requestBody = EXISTS.toRequestBody(\"application/xml\".toMediaType())\n                method(\"PROPFIND\", requestBody)\n            }.use { it.isSuccessful }\n        }.onFailure {\n            currentCoroutineContext().ensureActive()\n        }.getOrDefault(false)\n    }\n\n    /**\n     * 检查用户名密码是否有效\n     */\n    suspend fun check(): Boolean {\n        return kotlin.runCatching {\n            webDavClient.newCallResponse {\n                url(url)\n                addHeader(\"Depth\", \"0\")\n                val requestBody = EXISTS.toRequestBody(\"application/xml\".toMediaType())\n                method(\"PROPFIND\", requestBody)\n            }.use { it.code != 401 }\n        }.onFailure {\n            currentCoroutineContext().ensureActive()\n        }.getOrDefault(true)\n    }\n\n    /**\n     * 根据自己的URL，在远程处创建对应的文件夹\n     * @return 是否创建成功\n     */\n    suspend fun makeAsDir(): Boolean {\n        val url = httpUrl ?: return false\n        //防止报错\n        return kotlin.runCatching {\n            if (!exists()) {\n                webDavClient.newCallResponse {\n                    url(url)\n                    method(\"MKCOL\", null)\n                }.use {\n                    checkResult(it)\n                }\n            }\n        }.onFailure {\n            currentCoroutineContext().ensureActive()\n            AppLog.put(\"WebDav创建目录失败\\n${it.localizedMessage}\", it)\n        }.isSuccess\n    }\n\n    /**\n     * 下载到本地\n     * @param savedPath       本地的完整路径，包括最后的文件名\n     * @param replaceExisting 是否替换本地的同名文件\n     */\n    @Throws(WebDavException::class)\n    suspend fun downloadTo(savedPath: String, replaceExisting: Boolean) {\n        val file = File(savedPath)\n        if (file.exists() && !replaceExisting) {\n            return\n        }\n        downloadInputStream().use { byteStream ->\n            FileOutputStream(file).use {\n                byteStream.copyTo(it)\n            }\n        }\n    }\n\n    /**\n     * 下载文件,返回ByteArray\n     */\n    @Throws(WebDavException::class)\n    suspend fun download(): ByteArray {\n        return downloadInputStream().use {\n            it.readBytes()\n        }\n    }\n\n    /**\n     * 上传文件\n     */\n    @Throws(WebDavException::class)\n    suspend fun upload(localPath: String, contentType: String = DEFAULT_CONTENT_TYPE) {\n        upload(File(localPath), contentType)\n    }\n\n    @Throws(WebDavException::class)\n    suspend fun upload(file: File, contentType: String = DEFAULT_CONTENT_TYPE) {\n        kotlin.runCatching {\n            withContext(IO) {\n                if (!file.exists()) throw WebDavException(\"文件不存在\")\n                // 务必注意RequestBody不要嵌套，不然上传时内容可能会被追加多余的文件信息\n                val fileBody = file.asRequestBody(contentType.toMediaType())\n                val url = httpUrl ?: throw WebDavException(\"url不能为空\")\n                webDavClient.newCallResponse {\n                    url(url)\n                    put(fileBody)\n                }.use {\n                    checkResult(it)\n                }\n            }\n        }.onFailure {\n            currentCoroutineContext().ensureActive()\n            AppLog.put(\"WebDav上传失败\\n${it.localizedMessage}\", it)\n            throw WebDavException(\"WebDav上传失败\\n${it.localizedMessage}\")\n        }\n    }\n\n    @Throws(WebDavException::class)\n    suspend fun upload(byteArray: ByteArray, contentType: String = DEFAULT_CONTENT_TYPE) {\n        // 务必注意RequestBody不要嵌套，不然上传时内容可能会被追加多余的文件信息\n        kotlin.runCatching {\n            withContext(IO) {\n                val fileBody = byteArray.toRequestBody(contentType.toMediaType())\n                val url = httpUrl ?: throw NoStackTraceException(\"url不能为空\")\n                webDavClient.newCallResponse {\n                    url(url)\n                    put(fileBody)\n                }.use {\n                    checkResult(it)\n                }\n            }\n        }.onFailure {\n            currentCoroutineContext().ensureActive()\n            AppLog.put(\"WebDav上传失败\\n${it.localizedMessage}\", it)\n            throw WebDavException(\"WebDav上传失败\\n${it.localizedMessage}\")\n        }\n    }\n\n    @Throws(WebDavException::class)\n    suspend fun upload(uri: Uri, contentType: String = DEFAULT_CONTENT_TYPE) {\n        // 务必注意RequestBody不要嵌套，不然上传时内容可能会被追加多余的文件信息\n        kotlin.runCatching {\n            withContext(IO) {\n                val fileBody = uri.toRequestBody(contentType.toMediaType())\n                val url = httpUrl ?: throw NoStackTraceException(\"url不能为空\")\n                webDavClient.newCallResponse {\n                    url(url)\n                    put(fileBody)\n                }.use {\n                    checkResult(it)\n                }\n            }\n        }.onFailure {\n            currentCoroutineContext().ensureActive()\n            AppLog.put(\"WebDav上传失败\\n${it.localizedMessage}\", it)\n            throw WebDavException(\"WebDav上传失败\\n${it.localizedMessage}\")\n        }\n    }\n\n    @Throws(WebDavException::class)\n    suspend fun downloadInputStream(): InputStream {\n        val url = httpUrl ?: throw WebDavException(\"WebDav下载出错\\nurl为空\")\n        val byteStream = webDavClient.newCallResponse {\n            url(url)\n        }.apply {\n            checkResult(this)\n        }.body.byteStream()\n        return byteStream\n    }\n\n    /**\n     * 移除文件/文件夹\n     */\n    suspend fun delete(): Boolean {\n        val url = httpUrl ?: return false\n        //防止报错\n        return kotlin.runCatching {\n            webDavClient.newCallResponse {\n                url(url)\n                method(\"DELETE\", null)\n            }.use {\n                checkResult(it)\n            }\n        }.onFailure {\n            currentCoroutineContext().ensureActive()\n            AppLog.put(\"WebDav删除失败\\n${it.localizedMessage}\", it)\n        }.isSuccess\n    }\n\n    /**\n     * 检测返回结果是否正确\n     */\n    private fun checkResult(response: Response) {\n        if (!response.isSuccessful) {\n            val body = response.body.string()\n            if (response.code == 401) {\n                val headers = response.headers(\"WWW-Authenticate\")\n                val supportBasicAuth = headers.any {\n                    it.startsWith(\"Basic\", ignoreCase = true)\n                }\n                if (headers.isNotEmpty() && !supportBasicAuth) {\n                    AppLog.put(\"服务器不支持BasicAuth认证\")\n                }\n            }\n\n            if (response.message.isNotBlank() || body.isBlank()) {\n                throw WebDavException(\"${url}\\n${response.code}:${response.message}\")\n            }\n            val document = Jsoup.parse(body)\n            val exception = document.getElementsByTag(\"s:exception\").firstOrNull()?.text()\n            val message = document.getElementsByTag(\"s:message\").firstOrNull()?.text()\n            if (exception == \"ObjectNotFound\") {\n                throw ObjectNotFoundException(\n                    message ?: \"$path doesn't exist. code:${response.code}\"\n                )\n            }\n            throw WebDavException(message ?: \"未知错误 code:${response.code}\")\n        }\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/lib/webdav/WebDavException.kt",
    "content": "package io.legado.app.lib.webdav\n\nopen class WebDavException(msg: String) : Exception(msg) {\n\n    override fun fillInStackTrace(): Throwable {\n        return this\n    }\n\n}\n\nclass ObjectNotFoundException(msg: String) : WebDavException(msg)"
  },
  {
    "path": "app/src/main/java/io/legado/app/lib/webdav/WebDavFile.kt",
    "content": "package io.legado.app.lib.webdav\n\n/**\n * webDavFile\n */\n@Suppress(\"unused\")\nclass WebDavFile(\n    urlStr: String,\n    authorization: Authorization,\n    val displayName: String,\n    val urlName: String,\n    val size: Long,\n    val contentType: String,\n    val resourceType: String,\n    val lastModify: Long\n) : WebDav(urlStr, authorization) {\n\n    val isDir by lazy {\n        isDir(contentType, resourceType)\n    }\n\n    companion object {\n        fun isDir(contentType: String, resourceType: String): Boolean {\n            return contentType == \"httpd/unix-directory\"\n                    || resourceType.lowercase().contains(\"collection\")\n        }\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/model/AudioPlay.kt",
    "content": "package io.legado.app.model\n\nimport android.annotation.SuppressLint\nimport android.content.Context\nimport android.content.Intent\nimport io.legado.app.R\nimport io.legado.app.constant.AppLog\nimport io.legado.app.constant.EventBus\nimport io.legado.app.constant.IntentAction\nimport io.legado.app.constant.Status\nimport io.legado.app.data.appDb\nimport io.legado.app.data.entities.Book\nimport io.legado.app.data.entities.BookChapter\nimport io.legado.app.data.entities.BookSource\nimport io.legado.app.help.book.ContentProcessor\nimport io.legado.app.help.book.getBookSource\nimport io.legado.app.help.book.readSimulating\nimport io.legado.app.help.book.simulatedTotalChapterNum\nimport io.legado.app.help.book.update\nimport io.legado.app.help.coroutine.Coroutine\nimport io.legado.app.model.webBook.WebBook\nimport io.legado.app.service.AudioPlayService\nimport io.legado.app.utils.postEvent\nimport io.legado.app.utils.startService\nimport io.legado.app.utils.toastOnUi\nimport kotlinx.coroutines.CoroutineScope\nimport kotlinx.coroutines.MainScope\nimport kotlinx.coroutines.cancelChildren\nimport splitties.init.appCtx\n\n@SuppressLint(\"StaticFieldLeak\")\n@Suppress(\"unused\")\nobject AudioPlay : CoroutineScope by MainScope() {\n    /**\n     * 播放模式枚举\n     */\n    enum class PlayMode(val iconRes: Int) {\n        LIST_END_STOP(R.drawable.ic_play_mode_list_end_stop),\n        SINGLE_LOOP(R.drawable.ic_play_mode_single_loop),\n        RANDOM(R.drawable.ic_play_mode_random),\n        LIST_LOOP(R.drawable.ic_play_mode_list_loop);\n\n        fun next(): PlayMode {\n            return when (this) {\n                LIST_END_STOP -> SINGLE_LOOP\n                SINGLE_LOOP -> RANDOM\n                RANDOM -> LIST_LOOP\n                LIST_LOOP -> LIST_END_STOP\n            }\n        }\n    }\n\n    var playMode = PlayMode.LIST_END_STOP\n    var status = Status.STOP\n    private var activityContext: Context? = null\n    private var serviceContext: Context? = null\n    private val context: Context get() = activityContext ?: serviceContext ?: appCtx\n    var callback: CallBack? = null\n    var book: Book? = null\n    var chapterSize = 0\n    var simulatedChapterSize = 0\n    var durChapterIndex = 0\n    var durChapterPos = 0\n    var durChapter: BookChapter? = null\n    var durPlayUrl = \"\"\n    var durAudioSize = 0\n    var inBookshelf = false\n    var bookSource: BookSource? = null\n    val loadingChapters = arrayListOf<Int>()\n\n    fun changePlayMode() {\n        playMode = playMode.next()\n        postEvent(EventBus.PLAY_MODE_CHANGED, playMode)\n    }\n\n    fun upData(book: Book) {\n        AudioPlay.book = book\n        chapterSize = appDb.bookChapterDao.getChapterCount(book.bookUrl)\n        simulatedChapterSize = if (book.readSimulating()) {\n            book.simulatedTotalChapterNum()\n        } else {\n            chapterSize\n        }\n        if (durChapterIndex != book.durChapterIndex) {\n            stopPlay()\n            durChapterIndex = book.durChapterIndex\n            durChapterPos = book.durChapterPos\n            durPlayUrl = \"\"\n            durAudioSize = 0\n        }\n        upDurChapter()\n    }\n\n    fun resetData(book: Book) {\n        stop()\n        AudioPlay.book = book\n        chapterSize = appDb.bookChapterDao.getChapterCount(book.bookUrl)\n        simulatedChapterSize = if (book.readSimulating()) {\n            book.simulatedTotalChapterNum()\n        } else {\n            chapterSize\n        }\n        bookSource = book.getBookSource()\n        durChapterIndex = book.durChapterIndex\n        durChapterPos = book.durChapterPos\n        durPlayUrl = \"\"\n        durAudioSize = 0\n        upDurChapter()\n        postEvent(EventBus.AUDIO_BUFFER_PROGRESS, 0)\n    }\n\n    private fun addLoading(index: Int): Boolean {\n        synchronized(this) {\n            if (loadingChapters.contains(index)) return false\n            loadingChapters.add(index)\n            return true\n        }\n    }\n\n    private fun removeLoading(index: Int) {\n        synchronized(this) {\n            loadingChapters.remove(index)\n        }\n    }\n\n    fun loadOrUpPlayUrl() {\n        if (durPlayUrl.isEmpty()) {\n            loadPlayUrl()\n        } else {\n            upPlayUrl()\n        }\n    }\n\n    /**\n     * 加载播放URL\n     */\n    private fun loadPlayUrl() {\n        val index = durChapterIndex\n        if (addLoading(index)) {\n            val book = book\n            val bookSource = bookSource\n            if (book != null && bookSource != null) {\n                upDurChapter()\n                val chapter = durChapter\n                if (chapter == null) {\n                    removeLoading(index)\n                    return\n                }\n                if (chapter.isVolume) {\n                    skipTo(index + 1)\n                    removeLoading(index)\n                    return\n                }\n                upLoading(true)\n                WebBook.getContent(this, bookSource, book, chapter)\n                    .onSuccess { content ->\n                        if (content.isEmpty()) {\n                            appCtx.toastOnUi(\"未获取到资源链接\")\n                        } else {\n                            contentLoadFinish(chapter, content)\n                        }\n                    }.onError {\n                        AppLog.put(\"获取资源链接出错\\n$it\", it, true)\n                        upLoading(false)\n                    }.onCancel {\n                        removeLoading(index)\n                    }.onFinally {\n                        removeLoading(index)\n                    }\n            } else {\n                removeLoading(index)\n                appCtx.toastOnUi(\"book or source is null\")\n            }\n        }\n    }\n\n    /**\n     * 加载完成\n     */\n    private fun contentLoadFinish(chapter: BookChapter, content: String) {\n        if (chapter.index == book?.durChapterIndex) {\n            durPlayUrl = content\n            upPlayUrl()\n        }\n    }\n\n    private fun upPlayUrl() {\n        if (isPlayToEnd()) {\n            playNew()\n        } else {\n            play()\n        }\n    }\n\n    /**\n     * 播放当前章节\n     */\n    fun play() {\n        context.startService<AudioPlayService> {\n            action = IntentAction.play\n        }\n    }\n\n    /**\n     * 从头播放新章节\n     */\n    private fun playNew() {\n        context.startService<AudioPlayService> {\n            action = IntentAction.playNew\n        }\n    }\n\n    /**\n     * 更新当前章节\n     */\n    fun upDurChapter() {\n        val book = book ?: return\n        durChapter = appDb.bookChapterDao.getChapter(book.bookUrl, durChapterIndex)\n        durAudioSize = durChapter?.end?.toInt() ?: 0\n        val title = durChapter?.title ?: appCtx.getString(R.string.data_loading)\n        postEvent(EventBus.AUDIO_SUB_TITLE, title)\n        postEvent(EventBus.AUDIO_SIZE, durAudioSize)\n        postEvent(EventBus.AUDIO_PROGRESS, durChapterPos)\n    }\n\n    fun pause(context: Context) {\n        if (AudioPlayService.isRun) {\n            context.startService<AudioPlayService> {\n                action = IntentAction.pause\n            }\n        }\n    }\n\n    fun resume(context: Context) {\n        if (AudioPlayService.isRun) {\n            context.startService<AudioPlayService> {\n                action = IntentAction.resume\n            }\n        }\n    }\n\n    fun stop() {\n        if (AudioPlayService.isRun) {\n            context.startService<AudioPlayService> {\n                action = IntentAction.stop\n            }\n        }\n    }\n\n    fun adjustSpeed(adjust: Float) {\n        if (AudioPlayService.isRun) {\n            context.startService<AudioPlayService> {\n                action = IntentAction.adjustSpeed\n                putExtra(\"adjust\", adjust)\n            }\n        }\n    }\n\n    fun adjustProgress(position: Int) {\n        durChapterPos = position\n        saveRead()\n        if (AudioPlayService.isRun) {\n            context.startService<AudioPlayService> {\n                action = IntentAction.adjustProgress\n                putExtra(\"position\", position)\n            }\n        }\n    }\n\n    fun skipTo(index: Int) {\n        Coroutine.async {\n            stopPlay()\n            if (index in 0..<simulatedChapterSize) {\n                durChapterIndex = index\n                durChapterPos = 0\n                durPlayUrl = \"\"\n                saveRead()\n                loadPlayUrl()\n            }\n        }\n    }\n\n    fun prev() {\n        Coroutine.async {\n            stopPlay()\n            if (durChapterIndex > 0) {\n                durChapterIndex -= 1\n                durChapterPos = 0\n                durPlayUrl = \"\"\n                saveRead()\n                loadPlayUrl()\n            }\n        }\n    }\n\n    fun next() {\n        stopPlay()\n        when (playMode) {\n            PlayMode.LIST_END_STOP -> {\n                if (durChapterIndex + 1 < simulatedChapterSize) {\n                    durChapterIndex += 1\n                    durChapterPos = 0\n                    durPlayUrl = \"\"\n                    saveRead()\n                    loadPlayUrl()\n                }\n            }\n\n            PlayMode.SINGLE_LOOP -> {\n                durChapterPos = 0\n                durPlayUrl = \"\"\n                saveRead()\n                loadPlayUrl()\n            }\n\n            PlayMode.RANDOM -> {\n                durChapterIndex = (0 until simulatedChapterSize).random()\n                durChapterPos = 0\n                durPlayUrl = \"\"\n                saveRead()\n                loadPlayUrl()\n            }\n\n            PlayMode.LIST_LOOP -> {\n                durChapterIndex = (durChapterIndex + 1) % simulatedChapterSize\n                durChapterPos = 0\n                durPlayUrl = \"\"\n                saveRead()\n                loadPlayUrl()\n            }\n        }\n    }\n\n    fun setTimer(minute: Int) {\n        if (AudioPlayService.isRun) {\n            val intent = Intent(context, AudioPlayService::class.java)\n            intent.action = IntentAction.setTimer\n            intent.putExtra(\"minute\", minute)\n            context.startService(intent)\n        } else {\n            AudioPlayService.timeMinute = minute\n            postEvent(EventBus.AUDIO_DS, minute)\n        }\n    }\n\n    fun addTimer() {\n        val intent = Intent(context, AudioPlayService::class.java)\n        intent.action = IntentAction.addTimer\n        context.startService(intent)\n    }\n\n    fun stopPlay() {\n        if (AudioPlayService.isRun) {\n            context.startService<AudioPlayService> {\n                action = IntentAction.stopPlay\n            }\n        }\n    }\n\n    fun saveRead() {\n        val book = book ?: return\n        Coroutine.async {\n            book.lastCheckCount = 0\n            book.durChapterTime = System.currentTimeMillis()\n            val chapterChanged = book.durChapterIndex != durChapterIndex\n            book.durChapterIndex = durChapterIndex\n            book.durChapterPos = durChapterPos\n            if (chapterChanged) {\n                appDb.bookChapterDao.getChapter(book.bookUrl, book.durChapterIndex)?.let {\n                    book.durChapterTitle = it.getDisplayTitle(\n                        ContentProcessor.get(book.name, book.origin).getTitleReplaceRules(),\n                        book.getUseReplaceRule()\n                    )\n                }\n            }\n            book.update()\n        }\n    }\n\n    /**\n     * 保存章节长度\n     */\n    fun saveDurChapter(audioSize: Long) {\n        val chapter = durChapter ?: return\n        Coroutine.async {\n            durAudioSize = audioSize.toInt()\n            chapter.end = audioSize\n            appDb.bookChapterDao.update(chapter)\n        }\n    }\n\n    fun playPositionChanged(position: Int) {\n        durChapterPos = position\n        saveRead()\n    }\n\n    fun upLoading(loading: Boolean) {\n        callback?.upLoading(loading)\n    }\n\n    private fun isPlayToEnd(): Boolean {\n        return durChapterIndex + 1 == simulatedChapterSize\n                && durChapterPos == durAudioSize\n    }\n\n    fun register(context: Context) {\n        activityContext = context\n        callback = context as CallBack\n    }\n\n    fun unregister(context: Context) {\n        if (activityContext === context) {\n            activityContext = null\n            callback = null\n        }\n        coroutineContext.cancelChildren()\n    }\n\n    fun registerService(context: Context) {\n        serviceContext = context\n    }\n\n    fun unregisterService() {\n        serviceContext = null\n    }\n\n    interface CallBack {\n\n        fun upLoading(loading: Boolean)\n\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/model/BookCover.kt",
    "content": "package io.legado.app.model\n\nimport android.annotation.SuppressLint\nimport android.content.Context\nimport android.graphics.Bitmap\nimport android.graphics.drawable.BitmapDrawable\nimport android.graphics.drawable.Drawable\nimport androidx.annotation.Keep\nimport com.bumptech.glide.Glide\nimport com.bumptech.glide.RequestBuilder\nimport com.bumptech.glide.load.DataSource\nimport com.bumptech.glide.load.Transformation\nimport com.bumptech.glide.load.engine.DiskCacheStrategy\nimport com.bumptech.glide.load.engine.GlideException\nimport com.bumptech.glide.load.resource.bitmap.CenterCrop\nimport com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions\nimport com.bumptech.glide.request.RequestListener\nimport com.bumptech.glide.request.RequestOptions\nimport com.bumptech.glide.request.target.Target\nimport com.bumptech.glide.request.target.Target.SIZE_ORIGINAL\nimport io.legado.app.R\nimport io.legado.app.constant.PreferKey\nimport io.legado.app.data.entities.BaseSource\nimport io.legado.app.data.entities.Book\nimport io.legado.app.help.CacheManager\nimport io.legado.app.help.DefaultData\nimport io.legado.app.help.config.AppConfig\nimport io.legado.app.help.glide.BlurTransformation\nimport io.legado.app.help.glide.ImageLoader\nimport io.legado.app.help.glide.OkHttpModelLoader\nimport io.legado.app.model.analyzeRule.AnalyzeRule\nimport io.legado.app.model.analyzeRule.AnalyzeRule.Companion.setCoroutineContext\nimport io.legado.app.model.analyzeRule.AnalyzeUrl\nimport io.legado.app.utils.BitmapUtils\nimport io.legado.app.utils.GSON\nimport io.legado.app.utils.fromJsonObject\nimport io.legado.app.utils.getPrefBoolean\nimport io.legado.app.utils.getPrefString\nimport kotlinx.coroutines.currentCoroutineContext\nimport splitties.init.appCtx\nimport java.io.File\n\n@Keep\n@Suppress(\"ConstPropertyName\")\nobject BookCover {\n\n    private const val coverRuleConfigKey = \"legadoCoverRuleConfig\"\n    const val configFileName = \"coverRule.json\"\n\n    var drawBookName = true\n        private set\n    var drawBookAuthor = true\n        private set\n    lateinit var defaultDrawable: Drawable\n        private set\n\n\n    init {\n        upDefaultCover()\n    }\n\n    @SuppressLint(\"UseCompatLoadingForDrawables\")\n    fun upDefaultCover() {\n        val isNightTheme = AppConfig.isNightTheme\n        drawBookName = if (isNightTheme) {\n            appCtx.getPrefBoolean(PreferKey.coverShowNameN, true)\n        } else {\n            appCtx.getPrefBoolean(PreferKey.coverShowName, true)\n        }\n        drawBookAuthor = if (isNightTheme) {\n            appCtx.getPrefBoolean(PreferKey.coverShowAuthorN, true)\n        } else {\n            appCtx.getPrefBoolean(PreferKey.coverShowAuthor, true)\n        }\n        val key = if (isNightTheme) PreferKey.defaultCoverDark else PreferKey.defaultCover\n        val path = appCtx.getPrefString(key)\n        if (path.isNullOrBlank()) {\n            defaultDrawable = appCtx.resources.getDrawable(R.drawable.image_cover_default, null)\n            return\n        }\n        defaultDrawable = kotlin.runCatching {\n            BitmapDrawable(appCtx.resources, BitmapUtils.decodeBitmap(path, 600, 900))\n        }.getOrDefault(appCtx.resources.getDrawable(R.drawable.image_cover_default, null))\n    }\n\n    /**\n     * 加载封面\n     */\n    fun load(\n        context: Context,\n        path: String?,\n        loadOnlyWifi: Boolean = false,\n        sourceOrigin: String? = null,\n        onLoadFinish: (() -> Unit)? = null,\n    ): RequestBuilder<Drawable> {\n        if (AppConfig.useDefaultCover) {\n            return ImageLoader.load(context, defaultDrawable)\n                .centerCrop()\n        }\n        var options = RequestOptions().set(OkHttpModelLoader.loadOnlyWifiOption, loadOnlyWifi)\n        if (sourceOrigin != null) {\n            options = options.set(OkHttpModelLoader.sourceOriginOption, sourceOrigin)\n        }\n        var builder = ImageLoader.load(context, path)\n            .apply(options)\n        if (onLoadFinish != null) {\n            builder = builder.addListener(object : RequestListener<Drawable> {\n                override fun onLoadFailed(\n                    e: GlideException?,\n                    model: Any?,\n                    target: Target<Drawable?>,\n                    isFirstResource: Boolean,\n                ): Boolean {\n                    onLoadFinish.invoke()\n                    return false\n                }\n\n                override fun onResourceReady(\n                    resource: Drawable,\n                    model: Any,\n                    target: Target<Drawable?>?,\n                    dataSource: DataSource,\n                    isFirstResource: Boolean,\n                ): Boolean {\n                    onLoadFinish.invoke()\n                    return false\n                }\n            })\n        }\n        return builder.placeholder(defaultDrawable)\n            .error(defaultDrawable)\n            .centerCrop()\n    }\n\n    /**\n     * 加载漫画图片\n     */\n    fun loadManga(\n        context: Context,\n        path: String?,\n        loadOnlyWifi: Boolean = false,\n        sourceOrigin: String? = null,\n        transformation: Transformation<Bitmap>? = null,\n    ): RequestBuilder<Drawable> {\n        var options = RequestOptions().set(OkHttpModelLoader.loadOnlyWifiOption, loadOnlyWifi)\n            .set(OkHttpModelLoader.mangaOption, true)\n        if (sourceOrigin != null) {\n            options = options.set(OkHttpModelLoader.sourceOriginOption, sourceOrigin)\n        }\n        return ImageLoader.load(context, path)\n            .apply(options)\n            .override(context.resources.displayMetrics.widthPixels, SIZE_ORIGINAL)\n            .diskCacheStrategy(DiskCacheStrategy.ALL)\n            .skipMemoryCache(true).let {\n                if (transformation != null) {\n                    it.transform(transformation)\n                } else {\n                    it\n                }\n            }\n    }\n\n    fun preloadManga(\n        context: Context,\n        path: String?,\n        loadOnlyWifi: Boolean = false,\n        sourceOrigin: String? = null,\n    ): RequestBuilder<File?> {\n        var options = RequestOptions().set(OkHttpModelLoader.loadOnlyWifiOption, loadOnlyWifi)\n            .set(OkHttpModelLoader.mangaOption, true)\n        if (sourceOrigin != null) {\n            options = options.set(OkHttpModelLoader.sourceOriginOption, sourceOrigin)\n        }\n        return Glide.with(context)\n            .downloadOnly()\n            .apply(options)\n            .load(path)\n    }\n\n    /**\n     * 加载模糊封面\n     */\n    fun loadBlur(\n        context: Context,\n        path: String?,\n        loadOnlyWifi: Boolean = false,\n        sourceOrigin: String? = null,\n    ): RequestBuilder<Drawable> {\n        val loadBlur = ImageLoader.load(context, defaultDrawable)\n            .transform(BlurTransformation(25), CenterCrop())\n        if (AppConfig.useDefaultCover) {\n            return loadBlur\n        }\n        var options = RequestOptions().set(OkHttpModelLoader.loadOnlyWifiOption, loadOnlyWifi)\n        if (sourceOrigin != null) {\n            options = options.set(OkHttpModelLoader.sourceOriginOption, sourceOrigin)\n        }\n        return ImageLoader.load(context, path)\n            .apply(options)\n            .transform(BlurTransformation(25), CenterCrop())\n            .transition(DrawableTransitionOptions.withCrossFade(1500))\n            .thumbnail(loadBlur)\n    }\n\n    fun getCoverRule(): CoverRule {\n        return getConfig() ?: DefaultData.coverRule\n    }\n\n    fun getConfig(): CoverRule? {\n        return GSON.fromJsonObject<CoverRule>(CacheManager.get(coverRuleConfigKey))\n            .getOrNull()\n    }\n\n    suspend fun searchCover(book: Book): String? {\n        val config = getCoverRule()\n        if (!config.enable || config.searchUrl.isBlank() || config.coverRule.isBlank()) {\n            return null\n        }\n        val analyzeUrl = AnalyzeUrl(\n            config.searchUrl,\n            book.name,\n            source = config,\n            coroutineContext = currentCoroutineContext(),\n            hasLoginHeader = false\n        )\n        val res = analyzeUrl.getStrResponseAwait()\n        val analyzeRule = AnalyzeRule(book)\n        analyzeRule.setCoroutineContext(currentCoroutineContext())\n        analyzeRule.setContent(res.body)\n        analyzeRule.setRedirectUrl(res.url)\n        return analyzeRule.getString(config.coverRule, isUrl = true)\n    }\n\n    fun saveCoverRule(config: CoverRule) {\n        val json = GSON.toJson(config)\n        saveCoverRule(json)\n    }\n\n    fun saveCoverRule(json: String) {\n        CacheManager.put(coverRuleConfigKey, json)\n    }\n\n    fun delCoverRule() {\n        CacheManager.delete(coverRuleConfigKey)\n    }\n\n    @Keep\n    data class CoverRule(\n        var enable: Boolean = true,\n        var searchUrl: String,\n        var coverRule: String,\n        override var concurrentRate: String? = null,\n        override var loginUrl: String? = null,\n        override var loginUi: String? = null,\n        override var header: String? = null,\n        override var jsLib: String? = null,\n        override var enabledCookieJar: Boolean? = false,\n    ) : BaseSource {\n\n        override fun getTag(): String {\n            return searchUrl\n        }\n\n        override fun getKey(): String {\n            return searchUrl\n        }\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/model/CacheBook.kt",
    "content": "package io.legado.app.model\n\nimport android.content.Context\nimport io.legado.app.constant.AppLog\nimport io.legado.app.constant.EventBus\nimport io.legado.app.constant.IntentAction\nimport io.legado.app.data.appDb\nimport io.legado.app.data.entities.Book\nimport io.legado.app.data.entities.BookChapter\nimport io.legado.app.data.entities.BookSource\nimport io.legado.app.exception.ConcurrentException\nimport io.legado.app.help.book.BookHelp\nimport io.legado.app.help.book.isLocal\nimport io.legado.app.help.config.AppConfig\nimport io.legado.app.help.coroutine.CompositeCoroutine\nimport io.legado.app.help.coroutine.Coroutine\nimport io.legado.app.model.webBook.WebBook\nimport io.legado.app.service.CacheBookService\nimport io.legado.app.utils.onEachParallel\nimport io.legado.app.utils.postEvent\nimport io.legado.app.utils.startService\nimport kotlinx.coroutines.CancellationException\nimport kotlinx.coroutines.CoroutineScope\nimport kotlinx.coroutines.CoroutineStart\nimport kotlinx.coroutines.Dispatchers.IO\nimport kotlinx.coroutines.coroutineScope\nimport kotlinx.coroutines.currentCoroutineContext\nimport kotlinx.coroutines.delay\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.flow.collect\nimport kotlinx.coroutines.flow.first\nimport kotlinx.coroutines.flow.flow\nimport kotlinx.coroutines.flow.onCompletion\nimport kotlinx.coroutines.flow.onStart\nimport kotlinx.coroutines.isActive\nimport kotlinx.coroutines.sync.Mutex\nimport kotlinx.coroutines.sync.Semaphore\nimport kotlinx.coroutines.sync.withLock\nimport java.util.concurrent.ConcurrentHashMap\nimport kotlin.coroutines.CoroutineContext\n\nobject CacheBook {\n\n    val cacheBookMap = ConcurrentHashMap<String, CacheBookModel>()\n\n    private val workingState = MutableStateFlow(true)\n    private val mutex = Mutex()\n\n    @Synchronized\n    fun getOrCreate(bookUrl: String): CacheBookModel? {\n        val book = appDb.bookDao.getBook(bookUrl) ?: return null\n        val bookSource = appDb.bookSourceDao.getBookSource(book.origin) ?: return null\n        updateBookSource(bookSource)\n        var cacheBook = cacheBookMap[bookUrl]\n        if (cacheBook != null) {\n            //存在时更新,书源可能会变化,必须更新\n            cacheBook.bookSource = bookSource\n            cacheBook.book = book\n            return cacheBook\n        }\n        cacheBook = CacheBookModel(bookSource, book)\n        cacheBookMap[bookUrl] = cacheBook\n        return cacheBook\n    }\n\n    @Synchronized\n    fun getOrCreate(bookSource: BookSource, book: Book): CacheBookModel {\n        updateBookSource(bookSource)\n        var cacheBook = cacheBookMap[book.bookUrl]\n        if (cacheBook != null) {\n            //存在时更新,书源可能会变化,必须更新\n            cacheBook.bookSource = bookSource\n            cacheBook.book = book\n            return cacheBook\n        }\n        cacheBook = CacheBookModel(bookSource, book)\n        cacheBookMap[book.bookUrl] = cacheBook\n        return cacheBook\n    }\n\n    private fun updateBookSource(newBookSource: BookSource) {\n        cacheBookMap.forEach {\n            val model = it.value\n            if (model.bookSource.bookSourceUrl == newBookSource.bookSourceUrl) {\n                model.bookSource = newBookSource\n            }\n        }\n    }\n\n    fun start(context: Context, book: Book, start: Int, end: Int) {\n        if (!book.isLocal) {\n            context.startService<CacheBookService> {\n                action = IntentAction.start\n                putExtra(\"bookUrl\", book.bookUrl)\n                putExtra(\"start\", start)\n                putExtra(\"end\", end)\n            }\n        }\n    }\n\n    fun remove(context: Context, bookUrl: String) {\n        context.startService<CacheBookService> {\n            action = IntentAction.remove\n            putExtra(\"bookUrl\", bookUrl)\n        }\n    }\n\n    fun stop(context: Context) {\n        if (CacheBookService.isRun) {\n            context.startService<CacheBookService> {\n                action = IntentAction.stop\n            }\n        }\n    }\n\n    fun close() {\n        cacheBookMap.forEach { it.value.stop() }\n        cacheBookMap.clear()\n        successDownloadSet.clear()\n        errorDownloadMap.clear()\n    }\n\n    fun setWorkingState(value: Boolean) {\n        workingState.value = value\n    }\n\n    suspend fun startProcessJob(context: CoroutineContext) = mutex.withLock {\n        setWorkingState(true)\n        flow {\n            while (currentCoroutineContext().isActive && cacheBookMap.isNotEmpty()) {\n                var emitted = false\n\n                cacheBookMap.forEach { (_, model) ->\n                    if (!model.isLoading()) {\n                        emit(model)\n                        emitted = true\n                    }\n                    workingState.first { it }\n                }\n\n                if (!emitted) {\n                    delay(1000)\n                }\n            }\n        }.onStart {\n            postEvent(EventBus.UP_DOWNLOAD_STATE, \"\")\n        }.onEachParallel(AppConfig.threadCount) {\n            coroutineScope {\n                it.download(this, context)\n            }\n        }.onCompletion {\n            postEvent(EventBus.UP_DOWNLOAD_STATE, \"\")\n        }.collect()\n    }\n\n\n    val downloadSummary: String\n        get() {\n            return \"正在下载:${onDownloadCount}|等待中:${waitCount}|失败:${errorDownloadMap.count()}|成功:${successDownloadSet.size}\"\n        }\n\n    val isRun: Boolean\n        get() {\n            cacheBookMap.forEach {\n                if (it.value.isRun()) {\n                    return true\n                }\n            }\n            return false\n        }\n\n    private val waitCount: Int\n        get() {\n            var count = 0\n            cacheBookMap.forEach {\n                count += it.value.waitCount\n            }\n            return count\n        }\n\n    val onDownloadCount: Int\n        get() {\n            var count = 0\n            cacheBookMap.forEach {\n                count += it.value.onDownloadCount\n            }\n            return count\n        }\n\n    val successDownloadSet = linkedSetOf<String>()\n    val errorDownloadMap = hashMapOf<String, Int>()\n\n    class CacheBookModel(var bookSource: BookSource, var book: Book) {\n\n        private val waitDownloadSet = linkedSetOf<Int>()\n        private val onDownloadSet = linkedSetOf<Int>()\n        private val tasks = CompositeCoroutine()\n        private var isStopped = false\n        private var waitingRetry = false\n        private var isLoading = false\n\n        val waitCount get() = waitDownloadSet.size\n        val onDownloadCount get() = onDownloadSet.size\n\n        init {\n            postEvent(EventBus.UP_DOWNLOAD, book.bookUrl)\n        }\n\n        @Synchronized\n        fun isRun(): Boolean {\n            return waitDownloadSet.isNotEmpty() || onDownloadSet.isNotEmpty() || isLoading\n        }\n\n        @Synchronized\n        fun isStop(): Boolean {\n            return isStopped || (!isRun() && !waitingRetry)\n        }\n\n        @Synchronized\n        fun isLoading(): Boolean {\n            return isLoading\n        }\n\n        @Synchronized\n        fun setLoading() {\n            isLoading = true\n        }\n\n        @Synchronized\n        fun stop() {\n            waitDownloadSet.clear()\n            tasks.clear()\n            isStopped = true\n            isLoading = false\n            postEvent(EventBus.UP_DOWNLOAD, book.bookUrl)\n        }\n\n        @Synchronized\n        fun addDownload(start: Int, end: Int) {\n            isStopped = false\n            for (i in start..end) {\n                if (!onDownloadSet.contains(i)) {\n                    waitDownloadSet.add(i)\n                }\n            }\n            cacheBookMap[book.bookUrl] = this\n            isLoading = false\n            postEvent(EventBus.UP_DOWNLOAD, book.bookUrl)\n        }\n\n        @Synchronized\n        private fun onSuccess(chapter: BookChapter) {\n            onDownloadSet.remove(chapter.index)\n            successDownloadSet.add(chapter.primaryStr())\n            errorDownloadMap.remove(chapter.primaryStr())\n        }\n\n        @Synchronized\n        private fun onPreError(chapter: BookChapter, error: Throwable) {\n            waitingRetry = true\n            if (error !is ConcurrentException) {\n                errorDownloadMap[chapter.primaryStr()] =\n                    (errorDownloadMap[chapter.primaryStr()] ?: 0) + 1\n            }\n            onDownloadSet.remove(chapter.index)\n        }\n\n        @Synchronized\n        private fun onPostError(chapter: BookChapter, error: Throwable) {\n            //重试3次\n            if ((errorDownloadMap[chapter.primaryStr()] ?: 0) < 3 && !isStopped) {\n                waitDownloadSet.add(chapter.index)\n            } else {\n                AppLog.put(\n                    \"下载${book.name}-${chapter.title}失败\\n${error.localizedMessage}\",\n                    error\n                )\n            }\n            waitingRetry = false\n        }\n\n        @Synchronized\n        private fun onError(chapter: BookChapter, error: Throwable) {\n            onPreError(chapter, error)\n            onPostError(chapter, error)\n        }\n\n        @Synchronized\n        private fun onCancel(index: Int) {\n            onDownloadSet.remove(index)\n            if (!isStopped) waitDownloadSet.add(index)\n        }\n\n        @Synchronized\n        private fun onFinally() {\n            if (waitDownloadSet.isEmpty() && onDownloadSet.isEmpty()) {\n                cacheBookMap.remove(book.bookUrl)\n            }\n            postEvent(EventBus.UP_DOWNLOAD, book.bookUrl)\n        }\n\n        /**\n         * 从待下载列表内取第一条下载\n         */\n        @Synchronized\n        fun download(scope: CoroutineScope, context: CoroutineContext) {\n            val chapterIndex = waitDownloadSet.firstOrNull()\n            if (chapterIndex == null) {\n                if (!isLoading && onDownloadSet.isEmpty()) {\n                    cacheBookMap.remove(book.bookUrl)\n                }\n                return\n            }\n            if (onDownloadSet.contains(chapterIndex)) {\n                waitDownloadSet.remove(chapterIndex)\n                return\n            }\n            val chapter = appDb.bookChapterDao.getChapter(book.bookUrl, chapterIndex) ?: let {\n                waitDownloadSet.remove(chapterIndex)\n                return\n            }\n            if (chapter.isVolume) {\n                /** 修正下载计数 */\n                postEvent(EventBus.SAVE_CONTENT, Pair(book, chapter))\n                waitDownloadSet.remove(chapterIndex)\n                return\n            }\n            if (BookHelp.hasImageContent(book, chapter)) {\n                waitDownloadSet.remove(chapterIndex)\n                return\n            }\n            waitDownloadSet.remove(chapterIndex)\n            onDownloadSet.add(chapterIndex)\n            if (BookHelp.hasContent(book, chapter)) {\n                Coroutine.async(scope, context, executeContext = context) {\n                    BookHelp.getContent(book, chapter)?.let {\n                        BookHelp.saveImages(bookSource, book, chapter, it, 1)\n                    }\n                }.onSuccess {\n                    onSuccess(chapter)\n                }.onError {\n                    onPreError(chapter, it)\n                    //出现错误等待一秒后重新加入待下载列表\n                    delay(1000)\n                    onPostError(chapter, it)\n                }.onCancel {\n                    onCancel(chapterIndex)\n                }.onFinally {\n                    onFinally()\n                }.let {\n                    tasks.add(it)\n                }\n                return\n            }\n            WebBook.getContent(\n                scope,\n                bookSource,\n                book,\n                chapter,\n                context = context,\n                start = CoroutineStart.LAZY,\n                executeContext = context\n            ).onSuccess { content ->\n                onSuccess(chapter)\n                downloadFinish(chapter, content)\n            }.onError {\n                onPreError(chapter, it)\n                //出现错误等待一秒后重新加入待下载列表\n                delay(1000)\n                onPostError(chapter, it)\n                downloadFinish(chapter, \"获取正文失败\\n${it.localizedMessage}\")\n            }.onCancel {\n                onCancel(chapterIndex)\n            }.onFinally {\n                onFinally()\n            }.apply {\n                tasks.add(this)\n            }.start()\n        }\n\n        suspend fun downloadAwait(chapter: BookChapter): String {\n            synchronized(this) {\n                onDownloadSet.add(chapter.index)\n                waitDownloadSet.remove(chapter.index)\n            }\n            try {\n                val content = WebBook.getContentAwait(bookSource, book, chapter)\n                onSuccess(chapter)\n                ReadBook.downloadedChapters.add(chapter.index)\n                ReadBook.downloadFailChapters.remove(chapter.index)\n                return content\n            } catch (e: Exception) {\n                if (e is CancellationException) {\n                    onCancel(chapter.index)\n                }\n                onError(chapter, e)\n                ReadBook.downloadFailChapters[chapter.index] =\n                    (ReadBook.downloadFailChapters[chapter.index] ?: 0) + 1\n                return \"获取正文失败\\n${e.localizedMessage}\"\n            } finally {\n                postEvent(EventBus.UP_DOWNLOAD, book.bookUrl)\n            }\n        }\n\n        @Synchronized\n        fun download(\n            scope: CoroutineScope,\n            chapter: BookChapter,\n            semaphore: Semaphore?,\n            resetPageOffset: Boolean = false\n        ) {\n            if (onDownloadSet.contains(chapter.index)) {\n                return\n            }\n            onDownloadSet.add(chapter.index)\n            waitDownloadSet.remove(chapter.index)\n            WebBook.getContent(\n                scope,\n                bookSource,\n                book,\n                chapter,\n                start = CoroutineStart.LAZY,\n                executeContext = IO,\n                semaphore = semaphore\n            ).onSuccess { content ->\n                onSuccess(chapter)\n                ReadBook.downloadedChapters.add(chapter.index)\n                ReadBook.downloadFailChapters.remove(chapter.index)\n                downloadFinish(chapter, content, resetPageOffset)\n            }.onError {\n                onError(chapter, it)\n                ReadBook.downloadFailChapters[chapter.index] =\n                    (ReadBook.downloadFailChapters[chapter.index] ?: 0) + 1\n                downloadFinish(chapter, \"获取正文失败\\n${it.localizedMessage}\", resetPageOffset)\n            }.onCancel {\n                onCancel(chapter.index)\n                downloadFinish(chapter, \"download canceled\", resetPageOffset, true)\n            }.onFinally {\n                postEvent(EventBus.UP_DOWNLOAD, book.bookUrl)\n            }.start()\n        }\n\n        private fun downloadFinish(\n            chapter: BookChapter,\n            content: String,\n            resetPageOffset: Boolean = false,\n            canceled: Boolean = false\n        ) {\n            if (ReadBook.book?.bookUrl == book.bookUrl) {\n                ReadBook.contentLoadFinish(\n                    book, chapter, content,\n                    resetPageOffset = resetPageOffset,\n                    canceled = canceled\n                )\n            }\n        }\n\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/model/CheckSource.kt",
    "content": "package io.legado.app.model\n\nimport android.content.Context\nimport io.legado.app.R\nimport io.legado.app.constant.IntentAction\nimport io.legado.app.data.entities.BookSourcePart\nimport io.legado.app.help.CacheManager\nimport io.legado.app.help.IntentData\nimport io.legado.app.service.CheckSourceService\nimport io.legado.app.utils.startService\nimport splitties.init.appCtx\n\nobject CheckSource {\n    var keyword = \"我的\"\n\n    //校验设置\n    var timeout = CacheManager.getLong(\"checkSourceTimeout\") ?: 180000L\n    var checkSearch = CacheManager.get(\"checkSearch\")?.toBoolean() ?: true\n    var checkDiscovery = CacheManager.get(\"checkDiscovery\")?.toBoolean() ?: true\n    var checkInfo = CacheManager.get(\"checkInfo\")?.toBoolean() ?: true\n    var checkCategory = CacheManager.get(\"checkCategory\")?.toBoolean() ?: true\n    var checkContent = CacheManager.get(\"checkContent\")?.toBoolean() ?: true\n    val summary get() = upSummary()\n\n    fun start(context: Context, sources: List<BookSourcePart>) {\n        val selectedIds = sources.map {\n            it.bookSourceUrl\n        }\n        IntentData.put(\"checkSourceSelectedIds\", selectedIds)\n        context.startService<CheckSourceService> {\n            action = IntentAction.start\n        }\n    }\n\n    fun stop(context: Context) {\n        context.startService<CheckSourceService> {\n            action = IntentAction.stop\n        }\n    }\n\n    fun resume(context: Context) {\n        context.startService<CheckSourceService> {\n            action = IntentAction.resume\n        }\n    }\n\n    fun putConfig() {\n        CacheManager.put(\"checkSourceTimeout\", timeout)\n        CacheManager.put(\"checkSearch\", checkSearch)\n        CacheManager.put(\"checkDiscovery\", checkDiscovery)\n        CacheManager.put(\"checkInfo\", checkInfo)\n        CacheManager.put(\"checkCategory\", checkCategory)\n        CacheManager.put(\"checkContent\", checkContent)\n    }\n\n    private fun upSummary(): String {\n        var checkItem = \"\"\n        if (checkSearch) checkItem = \"$checkItem ${appCtx.getString(R.string.search)}\"\n        if (checkDiscovery) checkItem = \"$checkItem ${appCtx.getString(R.string.discovery)}\"\n        if (checkInfo) checkItem = \"$checkItem ${appCtx.getString(R.string.source_tab_info)}\"\n        if (checkCategory) checkItem = \"$checkItem ${appCtx.getString(R.string.chapter_list)}\"\n        if (checkContent) checkItem = \"$checkItem ${appCtx.getString(R.string.main_body)}\"\n        return appCtx.getString(\n            R.string.check_source_config_summary,\n            (timeout / 1000).toString(),\n            checkItem\n        )\n    }\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/model/Debug.kt",
    "content": "package io.legado.app.model\n\nimport android.annotation.SuppressLint\nimport android.util.Log\nimport io.legado.app.BuildConfig\nimport io.legado.app.constant.AppPattern\nimport io.legado.app.data.entities.*\nimport io.legado.app.help.book.isWebFile\nimport io.legado.app.help.coroutine.CompositeCoroutine\nimport io.legado.app.help.source.sortUrls\nimport io.legado.app.model.rss.Rss\nimport io.legado.app.model.webBook.WebBook\nimport io.legado.app.utils.HtmlFormatter\nimport io.legado.app.utils.isAbsUrl\nimport io.legado.app.utils.stackTraceStr\nimport kotlinx.coroutines.CoroutineScope\nimport java.text.SimpleDateFormat\nimport java.util.*\n\nobject Debug {\n    var callback: Callback? = null\n    private var debugSource: String? = null\n    private val tasks: CompositeCoroutine = CompositeCoroutine()\n    val debugMessageMap = HashMap<String, String>()\n    private val debugTimeMap = HashMap<String, Long>()\n    var isChecking: Boolean = false\n\n    @SuppressLint(\"ConstantLocale\")\n    private val debugTimeFormat = SimpleDateFormat(\"[mm:ss.SSS]\", Locale.getDefault())\n    private var startTime: Long = System.currentTimeMillis()\n\n    @Synchronized\n    fun log(\n        sourceUrl: String?,\n        msg: String = \"\",\n        print: Boolean = true,\n        isHtml: Boolean = false,\n        showTime: Boolean = true,\n        state: Int = 1\n    ) {\n        if (BuildConfig.DEBUG) {\n            Log.d(\"sourceDebug\", msg)\n        }\n        //调试信息始终要执行\n        callback?.let {\n            if ((debugSource != sourceUrl || !print)) return\n            var printMsg = msg\n            if (isHtml) {\n                printMsg = HtmlFormatter.format(msg)\n            }\n            if (showTime) {\n                val time = debugTimeFormat.format(Date(System.currentTimeMillis() - startTime))\n                printMsg = \"$time $printMsg\"\n            }\n            it.printLog(state, printMsg)\n        }\n        if (isChecking && sourceUrl != null && (msg).length < 30) {\n            var printMsg = msg\n            if (isHtml) {\n                printMsg = HtmlFormatter.format(msg)\n            }\n            if (showTime && debugTimeMap[sourceUrl] != null) {\n                val time =\n                    debugTimeFormat.format(Date(System.currentTimeMillis() - debugTimeMap[sourceUrl]!!))\n                printMsg = printMsg.replace(AppPattern.debugMessageSymbolRegex, \"\")\n\n                debugMessageMap[sourceUrl] = \"$time $printMsg\"\n            }\n        }\n    }\n\n    @Synchronized\n    fun log(msg: String?) {\n        log(debugSource, msg ?: \"\", true)\n    }\n\n    fun cancelDebug(destroy: Boolean = false) {\n        tasks.clear()\n\n        if (destroy) {\n            debugSource = null\n            callback = null\n        }\n    }\n\n    fun startChecking(source: BookSource) {\n        isChecking = true\n        debugTimeMap[source.bookSourceUrl] = System.currentTimeMillis()\n        debugMessageMap[source.bookSourceUrl] = \"${debugTimeFormat.format(Date(0))} 开始校验\"\n    }\n\n    fun finishChecking() {\n        isChecking = false\n    }\n\n    fun getRespondTime(sourceUrl: String): Long {\n        return debugTimeMap[sourceUrl] ?: CheckSource.timeout\n    }\n\n    fun updateFinalMessage(sourceUrl: String, state: String) {\n        if (debugTimeMap[sourceUrl] != null && debugMessageMap[sourceUrl] != null) {\n            val spendingTime = System.currentTimeMillis() - debugTimeMap[sourceUrl]!!\n            debugTimeMap[sourceUrl] =\n                if (state == \"校验成功\") spendingTime else CheckSource.timeout + spendingTime\n            val printTime = debugTimeFormat.format(Date(spendingTime))\n            debugMessageMap[sourceUrl] = \"$printTime $state\"\n        }\n    }\n\n    suspend fun startDebug(scope: CoroutineScope, rssSource: RssSource) {\n        cancelDebug()\n        debugSource = rssSource.sourceUrl\n        log(debugSource, \"︾开始解析\")\n        val sort = rssSource.sortUrls().first()\n        Rss.getArticles(scope, sort.first, sort.second, rssSource, 1)\n            .onSuccess {\n                if (it.first.isEmpty()) {\n                    log(debugSource, \"⇒列表页解析成功，为空\")\n                    log(debugSource, \"︽解析完成\", state = 1000)\n                } else {\n                    val ruleContent = rssSource.ruleContent\n                    if (!rssSource.ruleArticles.isNullOrBlank() && rssSource.ruleDescription.isNullOrBlank()) {\n                        log(debugSource, \"︽列表页解析完成\")\n                        log(debugSource, showTime = false)\n                        if (ruleContent.isNullOrEmpty()) {\n                            log(debugSource, \"⇒内容规则为空，默认获取整个网页\", state = 1000)\n                        } else {\n                            rssContentDebug(scope, it.first[0], ruleContent, rssSource)\n                        }\n                    } else {\n                        log(debugSource, \"⇒存在描述规则，不解析内容页\")\n                        log(debugSource, \"︽解析完成\", state = 1000)\n                    }\n                }\n            }\n            .onError {\n                log(debugSource, it.stackTraceStr, state = -1)\n            }\n    }\n\n    private fun rssContentDebug(\n        scope: CoroutineScope,\n        rssArticle: RssArticle,\n        ruleContent: String,\n        rssSource: RssSource\n    ) {\n        log(debugSource, \"︾开始解析内容页\")\n        Rss.getContent(scope, rssArticle, ruleContent, rssSource)\n            .onSuccess {\n                log(debugSource, it)\n                log(debugSource, \"︽内容页解析完成\", state = 1000)\n            }\n            .onError {\n                log(debugSource, it.stackTraceStr, state = -1)\n            }\n    }\n\n    fun startDebug(scope: CoroutineScope, bookSource: BookSource, key: String) {\n        cancelDebug()\n        debugSource = bookSource.bookSourceUrl\n        startTime = System.currentTimeMillis()\n        when {\n            key.isAbsUrl() -> {\n                val book = Book()\n                book.origin = bookSource.bookSourceUrl\n                book.bookUrl = key\n                log(bookSource.bookSourceUrl, \"⇒开始访问详情页:$key\")\n                infoDebug(scope, bookSource, book)\n            }\n\n            key.contains(\"::\") -> {\n                val url = key.substringAfter(\"::\")\n                log(bookSource.bookSourceUrl, \"⇒开始访问发现页:$url\")\n                exploreDebug(scope, bookSource, url)\n            }\n\n            key.startsWith(\"++\") -> {\n                val url = key.substring(2)\n                val book = Book()\n                book.origin = bookSource.bookSourceUrl\n                book.tocUrl = url\n                log(bookSource.bookSourceUrl, \"⇒开始访目录页:$url\")\n                tocDebug(scope, bookSource, book)\n            }\n\n            key.startsWith(\"--\") -> {\n                val url = key.substring(2)\n                val book = Book()\n                book.origin = bookSource.bookSourceUrl\n                log(bookSource.bookSourceUrl, \"⇒开始访正文页:$url\")\n                val chapter = BookChapter()\n                chapter.title = \"调试\"\n                chapter.url = url\n                contentDebug(scope, bookSource, book, chapter, null)\n            }\n\n            else -> {\n                log(bookSource.bookSourceUrl, \"⇒开始搜索关键字:$key\")\n                searchDebug(scope, bookSource, key)\n            }\n        }\n    }\n\n    private fun exploreDebug(scope: CoroutineScope, bookSource: BookSource, url: String) {\n        log(debugSource, \"︾开始解析发现页\")\n        val explore = WebBook.exploreBook(scope, bookSource, url, 1)\n            .onSuccess { exploreBooks ->\n                if (exploreBooks.isNotEmpty()) {\n                    log(debugSource, \"︽发现页解析完成\")\n                    log(debugSource, showTime = false)\n                    infoDebug(scope, bookSource, exploreBooks[0].toBook())\n                } else {\n                    log(debugSource, \"︽未获取到书籍\", state = -1)\n                }\n            }\n            .onError {\n                log(debugSource, it.stackTraceStr, state = -1)\n            }\n        tasks.add(explore)\n    }\n\n    private fun searchDebug(scope: CoroutineScope, bookSource: BookSource, key: String) {\n        log(debugSource, \"︾开始解析搜索页\")\n        val search = WebBook.searchBook(scope, bookSource, key, 1)\n            .onSuccess { searchBooks ->\n                if (searchBooks.isNotEmpty()) {\n                    log(debugSource, \"︽搜索页解析完成\")\n                    log(debugSource, showTime = false)\n                    infoDebug(scope, bookSource, searchBooks[0].toBook())\n                } else {\n                    log(debugSource, \"︽未获取到书籍\", state = -1)\n                }\n            }\n            .onError {\n                log(debugSource, it.stackTraceStr, state = -1)\n            }\n        tasks.add(search)\n    }\n\n    private fun infoDebug(scope: CoroutineScope, bookSource: BookSource, book: Book) {\n        if (book.tocUrl.isNotBlank()) {\n            log(debugSource, \"≡已获取目录链接,跳过详情页\")\n            log(debugSource, showTime = false)\n            tocDebug(scope, bookSource, book)\n            return\n        }\n        log(debugSource, \"︾开始解析详情页\")\n        val info = WebBook.getBookInfo(scope, bookSource, book)\n            .onSuccess {\n                log(debugSource, \"︽详情页解析完成\")\n                log(debugSource, showTime = false)\n                if (!book.isWebFile) {\n                    tocDebug(scope, bookSource, book)\n                } else {\n                    log(debugSource, \"≡文件类书源跳过解析目录\", state = 1000)\n                }\n            }\n            .onError {\n                log(debugSource, it.stackTraceStr, state = -1)\n            }\n        tasks.add(info)\n    }\n\n    private fun tocDebug(scope: CoroutineScope, bookSource: BookSource, book: Book) {\n        log(debugSource, \"︾开始解析目录页\")\n        val chapterList = WebBook.getChapterList(scope, bookSource, book)\n            .onSuccess { chapters ->\n                log(debugSource, \"︽目录页解析完成\")\n                log(debugSource, showTime = false)\n                val toc = chapters.filter { !(it.isVolume && it.url.startsWith(it.title)) }\n                if (toc.isEmpty()) {\n                    log(debugSource, \"≡没有正文章节\")\n                    return@onSuccess\n                }\n                val nextChapterUrl = toc.getOrNull(1)?.url ?: toc.first().url\n                contentDebug(scope, bookSource, book, toc.first(), nextChapterUrl)\n            }\n            .onError {\n                log(debugSource, it.stackTraceStr, state = -1)\n            }\n        tasks.add(chapterList)\n    }\n\n    private fun contentDebug(\n        scope: CoroutineScope,\n        bookSource: BookSource,\n        book: Book,\n        bookChapter: BookChapter,\n        nextChapterUrl: String?\n    ) {\n        log(debugSource, \"︾开始解析正文页\")\n        val content = WebBook.getContent(\n            scope = scope,\n            bookSource = bookSource,\n            book = book,\n            bookChapter = bookChapter,\n            nextChapterUrl = nextChapterUrl,\n            needSave = false\n        ).onSuccess {\n            log(debugSource, \"︽正文页解析完成\", state = 1000)\n        }.onError {\n            log(debugSource, it.stackTraceStr, state = -1)\n        }\n        tasks.add(content)\n    }\n\n    interface Callback {\n        fun printLog(state: Int, msg: String)\n    }\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/model/Download.kt",
    "content": "package io.legado.app.model\n\nimport android.content.Context\nimport io.legado.app.constant.IntentAction\nimport io.legado.app.service.DownloadService\nimport io.legado.app.utils.startService\n\nobject Download {\n\n\n    fun start(context: Context, url: String, fileName: String) {\n        context.startService<DownloadService> {\n            action = IntentAction.start\n            putExtra(\"url\", url)\n            putExtra(\"fileName\", fileName)\n        }\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/model/ImageProvider.kt",
    "content": "package io.legado.app.model\n\nimport android.graphics.Bitmap\nimport android.graphics.BitmapFactory\nimport android.util.Size\nimport androidx.collection.LruCache\nimport io.legado.app.R\nimport io.legado.app.constant.AppLog.putDebug\nimport io.legado.app.data.entities.Book\nimport io.legado.app.data.entities.BookSource\nimport io.legado.app.exception.NoStackTraceException\nimport io.legado.app.help.book.BookHelp\nimport io.legado.app.help.book.isEpub\nimport io.legado.app.help.book.isMobi\nimport io.legado.app.help.book.isPdf\nimport io.legado.app.help.config.AppConfig\nimport io.legado.app.model.localBook.EpubFile\nimport io.legado.app.model.localBook.MobiFile\nimport io.legado.app.model.localBook.PdfFile\nimport io.legado.app.utils.BitmapUtils\nimport io.legado.app.utils.FileUtils\nimport io.legado.app.utils.SvgUtils\nimport io.legado.app.utils.toastOnUi\nimport kotlinx.coroutines.Dispatchers.IO\nimport kotlinx.coroutines.withContext\nimport splitties.init.appCtx\nimport java.io.File\nimport java.io.FileOutputStream\nimport kotlin.math.min\n\nobject ImageProvider {\n\n    private val errorBitmap: Bitmap by lazy {\n        BitmapFactory.decodeResource(appCtx.resources, R.drawable.image_loading_error)\n    }\n\n    /**\n     * 缓存bitmap LruCache实现\n     * filePath bitmap\n     */\n    private const val M = 1024 * 1024\n    val cacheSize: Int\n        get() {\n            if (AppConfig.bitmapCacheSize !in 1..1024) {\n                AppConfig.bitmapCacheSize = 50\n            }\n            return AppConfig.bitmapCacheSize * M\n        }\n\n    val bitmapLruCache = BitmapLruCache()\n\n    class BitmapLruCache : LruCache<String, Bitmap>(cacheSize) {\n\n        private var removeCount = 0\n\n        val count get() = putCount() + createCount() - evictionCount() - removeCount\n\n        override fun sizeOf(key: String, value: Bitmap): Int {\n            return value.byteCount\n        }\n\n        override fun entryRemoved(\n            evicted: Boolean,\n            key: String,\n            oldValue: Bitmap,\n            newValue: Bitmap?\n        ) {\n            if (!evicted) {\n                synchronized(this) {\n                    removeCount++\n                }\n            }\n            //错误图片不能释放,占位用,防止一直重复获取图片\n            if (oldValue != errorBitmap) {\n                oldValue.recycle()\n                //putDebug(\"ImageProvider: trigger bitmap recycle. URI: $filePath\")\n                //putDebug(\"ImageProvider : cacheUsage ${size()}bytes / ${maxSize()}bytes\")\n            }\n        }\n\n    }\n\n    fun put(key: String, bitmap: Bitmap) {\n        ensureLruCacheSize(bitmap)\n        bitmapLruCache.put(key, bitmap)\n    }\n\n    fun get(key: String): Bitmap? {\n        return bitmapLruCache[key]\n    }\n\n    fun remove(key: String): Bitmap? {\n        return bitmapLruCache.remove(key)\n    }\n\n    private fun getNotRecycled(key: String): Bitmap? {\n        val bitmap = bitmapLruCache[key] ?: return null\n        if (bitmap.isRecycled) {\n            bitmapLruCache.remove(key)\n            return null\n        }\n        return bitmap\n    }\n\n    private fun ensureLruCacheSize(bitmap: Bitmap) {\n        val lruMaxSize = bitmapLruCache.maxSize()\n        val lruSize = bitmapLruCache.size()\n        val byteCount = bitmap.byteCount\n        val size = if (byteCount > lruMaxSize) {\n            min(256 * M, (byteCount * 1.3).toInt())\n        } else if (lruSize + byteCount > lruMaxSize && bitmapLruCache.count < 5) {\n            min(256 * M, (lruSize + byteCount * 1.3).toInt())\n        } else {\n            lruMaxSize\n        }\n        if (size > lruMaxSize) {\n            bitmapLruCache.resize(size)\n        }\n    }\n\n    /**\n     *缓存网络图片和epub图片\n     */\n    suspend fun cacheImage(\n        book: Book,\n        src: String,\n        bookSource: BookSource?\n    ): File {\n        return withContext(IO) {\n            val vFile = BookHelp.getImage(book, src)\n            if (!BookHelp.isImageExist(book, src)) {\n                val inputStream = when {\n                    book.isEpub -> EpubFile.getImage(book, src)\n                    book.isPdf -> PdfFile.getImage(book, src)\n                    book.isMobi -> MobiFile.getImage(book, src)\n                    else -> {\n                        BookHelp.saveImage(bookSource, book, src)\n                        null\n                    }\n                }\n                inputStream?.use { input ->\n                    val newFile = FileUtils.createFileIfNotExist(vFile.absolutePath)\n                    FileOutputStream(newFile).use { output ->\n                        input.copyTo(output)\n                    }\n                }\n            }\n            return@withContext vFile\n        }\n    }\n\n    /**\n     *获取图片宽度高度信息\n     */\n    suspend fun getImageSize(\n        book: Book,\n        src: String,\n        bookSource: BookSource?\n    ): Size {\n        val file = cacheImage(book, src, bookSource)\n        val op = BitmapFactory.Options()\n        // inJustDecodeBounds如果设置为true,仅仅返回图片实际的宽和高,宽和高是赋值给opts.outWidth,opts.outHeight;\n        op.inJustDecodeBounds = true\n        BitmapFactory.decodeFile(file.absolutePath, op)\n        if (op.outWidth < 1 && op.outHeight < 1) {\n            //svg size\n            val size = SvgUtils.getSize(file.absolutePath)\n            if (size != null) return size\n            putDebug(\"ImageProvider: $src Unsupported image type\")\n            //file.delete() 重复下载\n            return Size(errorBitmap.width, errorBitmap.height)\n        }\n        return Size(op.outWidth, op.outHeight)\n    }\n\n    /**\n     *获取bitmap 使用LruCache缓存\n     */\n    fun getImage(\n        book: Book,\n        src: String,\n        width: Int,\n        height: Int? = null\n    ): Bitmap {\n        //src为空白时 可能被净化替换掉了 或者规则失效\n        if (book.getUseReplaceRule() && src.isBlank()) {\n            book.setUseReplaceRule(false)\n            appCtx.toastOnUi(R.string.error_image_url_empty)\n        }\n        val vFile = BookHelp.getImage(book, src)\n        if (!vFile.exists()) return errorBitmap\n        //epub文件提供图片链接是相对链接，同时阅读多个epub文件，缓存命中错误\n        //bitmapLruCache的key同一改成缓存文件的路径\n        val cacheBitmap = getNotRecycled(vFile.absolutePath)\n        if (cacheBitmap != null) return cacheBitmap\n        return kotlin.runCatching {\n            val bitmap = BitmapUtils.decodeBitmap(vFile.absolutePath, width, height)\n                ?: SvgUtils.createBitmap(vFile.absolutePath, width, height)\n                ?: throw NoStackTraceException(appCtx.getString(R.string.error_decode_bitmap))\n            put(vFile.absolutePath, bitmap)\n            bitmap\n        }.onFailure {\n            //错误图片占位,防止重复获取\n            put(vFile.absolutePath, errorBitmap)\n        }.getOrDefault(errorBitmap)\n    }\n\n    fun clear() {\n        bitmapLruCache.evictAll()\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/model/README.md",
    "content": "# 放置一些模块类\n\n* analyzeRule 书源规则解析\n* localBook 本地书籍解析\n* rss 订阅规则解析\n* webBook 获取网络书籍\n\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/model/ReadAloud.kt",
    "content": "package io.legado.app.model\n\nimport android.content.Context\nimport android.content.Intent\nimport android.os.Bundle\nimport io.legado.app.constant.AppLog\nimport io.legado.app.constant.EventBus\nimport io.legado.app.constant.IntentAction\nimport io.legado.app.data.appDb\nimport io.legado.app.data.entities.HttpTTS\nimport io.legado.app.help.config.AppConfig\nimport io.legado.app.service.BaseReadAloudService\nimport io.legado.app.service.HttpReadAloudService\nimport io.legado.app.service.TTSReadAloudService\nimport io.legado.app.utils.LogUtils\nimport io.legado.app.utils.StringUtils\nimport io.legado.app.utils.postEvent\nimport io.legado.app.utils.startForegroundServiceCompat\nimport io.legado.app.utils.toastOnUi\nimport splitties.init.appCtx\n\nobject ReadAloud {\n    private var aloudClass: Class<*> = getReadAloudClass()\n    val ttsEngine get() = ReadBook.book?.getTtsEngine() ?: AppConfig.ttsEngine\n    var httpTTS: HttpTTS? = null\n\n    private fun getReadAloudClass(): Class<*> {\n        val ttsEngine = ttsEngine\n        if (ttsEngine.isNullOrBlank()) {\n            return TTSReadAloudService::class.java\n        }\n        if (StringUtils.isNumeric(ttsEngine)) {\n            httpTTS = appDb.httpTTSDao.get(ttsEngine.toLong())\n            if (httpTTS != null) {\n                return HttpReadAloudService::class.java\n            }\n        }\n        return TTSReadAloudService::class.java\n    }\n\n    fun upReadAloudClass() {\n        stop(appCtx)\n        aloudClass = getReadAloudClass()\n    }\n\n    fun play(\n        context: Context,\n        play: Boolean = true,\n        pageIndex: Int = ReadBook.durPageIndex,\n        startPos: Int = 0\n    ) {\n        val intent = Intent(context, aloudClass)\n        intent.action = IntentAction.play\n        intent.putExtra(\"play\", play)\n        intent.putExtra(\"pageIndex\", pageIndex)\n        intent.putExtra(\"startPos\", startPos)\n        LogUtils.d(\"ReadAloud\", intent.toString())\n        try {\n            context.startForegroundServiceCompat(intent)\n        } catch (e: Exception) {\n            val msg = \"启动朗读服务出错\\n${e.localizedMessage}\"\n            AppLog.put(msg, e)\n            context.toastOnUi(msg)\n        }\n    }\n\n    fun playByEventBus(\n        play: Boolean = true,\n        pageIndex: Int = ReadBook.durPageIndex,\n        startPos: Int = 0\n    ) {\n        val bundle = Bundle().apply {\n            putBoolean(\"play\", play)\n            putInt(\"pageIndex\", pageIndex)\n            putInt(\"startPos\", startPos)\n        }\n        postEvent(EventBus.READ_ALOUD_PLAY, bundle)\n    }\n\n    fun pause(context: Context) {\n        if (BaseReadAloudService.isRun) {\n            val intent = Intent(context, aloudClass)\n            intent.action = IntentAction.pause\n            context.startForegroundServiceCompat(intent)\n        }\n    }\n\n    fun resume(context: Context) {\n        if (BaseReadAloudService.isRun) {\n            val intent = Intent(context, aloudClass)\n            intent.action = IntentAction.resume\n            context.startForegroundServiceCompat(intent)\n        }\n    }\n\n    fun stop(context: Context) {\n        if (BaseReadAloudService.isRun) {\n            val intent = Intent(context, aloudClass)\n            intent.action = IntentAction.stop\n            context.startForegroundServiceCompat(intent)\n        }\n    }\n\n    fun prevParagraph(context: Context) {\n        if (BaseReadAloudService.isRun) {\n            val intent = Intent(context, aloudClass)\n            intent.action = IntentAction.prevParagraph\n            context.startForegroundServiceCompat(intent)\n        }\n    }\n\n    fun nextParagraph(context: Context) {\n        if (BaseReadAloudService.isRun) {\n            val intent = Intent(context, aloudClass)\n            intent.action = IntentAction.nextParagraph\n            context.startForegroundServiceCompat(intent)\n        }\n    }\n\n    fun upTtsSpeechRate(context: Context) {\n        if (BaseReadAloudService.isRun) {\n            val intent = Intent(context, aloudClass)\n            intent.action = IntentAction.upTtsSpeechRate\n            context.startForegroundServiceCompat(intent)\n        }\n    }\n\n    fun setTimer(context: Context, minute: Int) {\n        if (BaseReadAloudService.isRun) {\n            val intent = Intent(context, aloudClass)\n            intent.action = IntentAction.setTimer\n            intent.putExtra(\"minute\", minute)\n            context.startForegroundServiceCompat(intent)\n        }\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/model/ReadBook.kt",
    "content": "package io.legado.app.model\n\nimport io.legado.app.constant.AppLog\nimport io.legado.app.constant.EventBus\nimport io.legado.app.constant.PageAnim.scrollPageAnim\nimport io.legado.app.data.appDb\nimport io.legado.app.data.entities.Book\nimport io.legado.app.data.entities.BookChapter\nimport io.legado.app.data.entities.BookProgress\nimport io.legado.app.data.entities.BookSource\nimport io.legado.app.data.entities.ReadRecord\nimport io.legado.app.help.AppWebDav\nimport io.legado.app.help.book.BookHelp\nimport io.legado.app.help.book.ContentProcessor\nimport io.legado.app.help.book.isImage\nimport io.legado.app.help.book.isLocal\nimport io.legado.app.help.book.isPdf\nimport io.legado.app.help.book.isSameNameAuthor\nimport io.legado.app.help.book.readSimulating\nimport io.legado.app.help.book.simulatedTotalChapterNum\nimport io.legado.app.help.book.update\nimport io.legado.app.help.config.AppConfig\nimport io.legado.app.help.config.ReadBookConfig\nimport io.legado.app.help.coroutine.Coroutine\nimport io.legado.app.help.globalExecutor\nimport io.legado.app.model.localBook.TextFile\nimport io.legado.app.model.webBook.WebBook\nimport io.legado.app.service.BaseReadAloudService\nimport io.legado.app.service.CacheBookService\nimport io.legado.app.ui.book.read.page.entities.TextChapter\nimport io.legado.app.ui.book.read.page.provider.ChapterProvider\nimport io.legado.app.ui.book.read.page.provider.LayoutProgressListener\nimport io.legado.app.utils.postEvent\nimport io.legado.app.utils.stackTraceStr\nimport io.legado.app.utils.toastOnUi\nimport kotlinx.coroutines.CancellationException\nimport kotlinx.coroutines.CoroutineScope\nimport kotlinx.coroutines.CoroutineStart\nimport kotlinx.coroutines.Dispatchers.IO\nimport kotlinx.coroutines.Dispatchers.Main\nimport kotlinx.coroutines.Job\nimport kotlinx.coroutines.MainScope\nimport kotlinx.coroutines.SupervisorJob\nimport kotlinx.coroutines.cancelChildren\nimport kotlinx.coroutines.delay\nimport kotlinx.coroutines.ensureActive\nimport kotlinx.coroutines.flow.collect\nimport kotlinx.coroutines.flow.receiveAsFlow\nimport kotlinx.coroutines.launch\nimport kotlinx.coroutines.sync.Mutex\nimport kotlinx.coroutines.sync.Semaphore\nimport kotlinx.coroutines.sync.withLock\nimport kotlinx.coroutines.withContext\nimport splitties.init.appCtx\nimport java.util.concurrent.ConcurrentHashMap\nimport kotlin.math.max\nimport kotlin.math.min\n\n\n@Suppress(\"MemberVisibilityCanBePrivate\")\nobject ReadBook : CoroutineScope by MainScope() {\n    var book: Book? = null\n    var callBack: CallBack? = null\n    var inBookshelf = false\n    var chapterSize = 0\n    var simulatedChapterSize = 0\n    var durChapterIndex = 0\n    var durChapterPos = 0\n    var isLocalBook = true\n    var chapterChanged = false\n    var prevTextChapter: TextChapter? = null\n    var curTextChapter: TextChapter? = null\n    var nextTextChapter: TextChapter? = null\n    var bookSource: BookSource? = null\n    var msg: String? = null\n    private val loadingChapters = arrayListOf<Int>()\n    private val readRecord = ReadRecord()\n    private val chapterLoadingJobs = ConcurrentHashMap<Int, Coroutine<*>>()\n    private val prevChapterLoadingLock = Mutex()\n    private val curChapterLoadingLock = Mutex()\n    private val nextChapterLoadingLock = Mutex()\n    var readStartTime: Long = System.currentTimeMillis()\n\n    /* 跳转进度前进度记录 */\n    var lastBookProgress: BookProgress? = null\n\n    /* web端阅读进度记录 */\n    var webBookProgress: BookProgress? = null\n\n    var preDownloadTask: Job? = null\n    val downloadedChapters = hashSetOf<Int>()\n    val downloadFailChapters = hashMapOf<Int, Int>()\n    var contentProcessor: ContentProcessor? = null\n    val downloadScope = CoroutineScope(SupervisorJob() + IO)\n    val preDownloadSemaphore = Semaphore(2)\n    val executor = globalExecutor\n\n    fun resetData(book: Book) {\n        releaseAndCancel()\n        ReadBook.book = book\n        readRecord.bookName = book.name\n        readRecord.readTime = appDb.readRecordDao.getReadTime(book.name) ?: 0\n        chapterSize = appDb.bookChapterDao.getChapterCount(book.bookUrl)\n        simulatedChapterSize = if (book.readSimulating()) {\n            book.simulatedTotalChapterNum()\n        } else {\n            chapterSize\n        }\n        contentProcessor = ContentProcessor.get(book)\n        durChapterIndex = book.durChapterIndex\n        durChapterPos = book.durChapterPos\n        isLocalBook = book.isLocal\n        clearTextChapter()\n        callBack?.upContent()\n        callBack?.upMenuView()\n        callBack?.upPageAnim()\n        upWebBook(book)\n        lastBookProgress = null\n        webBookProgress = null\n        TextFile.clear()\n        synchronized(this) {\n            loadingChapters.clear()\n            downloadedChapters.clear()\n            downloadFailChapters.clear()\n        }\n    }\n\n    fun upData(book: Book) {\n        releaseAndCancel()\n        ReadBook.book = book\n        chapterSize = appDb.bookChapterDao.getChapterCount(book.bookUrl)\n        simulatedChapterSize = if (book.readSimulating()) {\n            book.simulatedTotalChapterNum()\n        } else {\n            chapterSize\n        }\n        if (durChapterIndex != book.durChapterIndex) {\n            durChapterIndex = book.durChapterIndex\n            durChapterPos = book.durChapterPos\n            clearTextChapter()\n        }\n        if (curTextChapter?.isCompleted == false) {\n            curTextChapter = null\n        }\n        if (nextTextChapter?.isCompleted == false) {\n            nextTextChapter = null\n        }\n        if (prevTextChapter?.isCompleted == false) {\n            prevTextChapter = null\n        }\n        callBack?.upMenuView()\n        upWebBook(book)\n        synchronized(this) {\n            loadingChapters.clear()\n            downloadedChapters.clear()\n            downloadFailChapters.clear()\n        }\n    }\n\n    fun upWebBook(book: Book) {\n        if (book.isLocal) {\n            bookSource = null\n            if (book.getImageStyle().isNullOrBlank() && (book.isImage || book.isPdf)) {\n                book.setImageStyle(Book.imgStyleFull)\n            }\n        } else {\n            appDb.bookSourceDao.getBookSource(book.origin)?.let {\n                bookSource = it\n                if (book.getImageStyle().isNullOrBlank()) {\n                    var imageStyle = it.getContentRule().imageStyle\n                    if (imageStyle.isNullOrBlank() && (book.isImage || book.isPdf)) {\n                        imageStyle = Book.imgStyleFull\n                    }\n                    book.setImageStyle(imageStyle)\n                    if (imageStyle.equals(Book.imgStyleSingle, true)) {\n                        book.setPageAnim(0)\n                    }\n                }\n            } ?: let {\n                bookSource = null\n            }\n        }\n    }\n\n    fun upReadBookConfig(book: Book) {\n        val oldIndex = ReadBookConfig.styleSelect\n        ReadBookConfig.isComic = book.isImage\n        if (oldIndex != ReadBookConfig.styleSelect) {\n            postEvent(EventBus.UP_CONFIG, arrayListOf(1, 2, 5))\n            if (AppConfig.readBarStyleFollowPage) {\n                postEvent(EventBus.UPDATE_READ_ACTION_BAR, true)\n            }\n        }\n    }\n\n    fun setProgress(progress: BookProgress) {\n        if (progress.durChapterIndex < chapterSize &&\n            (durChapterIndex != progress.durChapterIndex\n                    || durChapterPos != progress.durChapterPos)\n        ) {\n            durChapterIndex = progress.durChapterIndex\n            durChapterPos = progress.durChapterPos\n            saveRead()\n            clearTextChapter()\n            callBack?.upContent()\n            loadContent(resetPageOffset = true)\n        }\n    }\n\n    //暂时保存跳转前进度\n    fun saveCurrentBookProgress() {\n        if (lastBookProgress != null) return //避免进度条连续跳转不能覆盖最初的进度记录\n        lastBookProgress = book?.let { BookProgress(it) }\n    }\n\n    //恢复跳转前进度\n    fun restoreLastBookProgress() {\n        lastBookProgress?.let {\n            setProgress(it)\n            lastBookProgress = null\n        }\n    }\n\n    fun clearTextChapter() {\n        clearExpiredChapterLoadingJob(true)\n        prevTextChapter = null\n        curTextChapter = null\n        nextTextChapter = null\n    }\n\n    fun clearSearchResult() {\n        curTextChapter?.clearSearchResult()\n        prevTextChapter?.clearSearchResult()\n        nextTextChapter?.clearSearchResult()\n    }\n\n    fun uploadProgress(toast: Boolean = false, successAction: (() -> Unit)? = null) {\n        book?.let {\n            launch(IO) {\n                AppWebDav.uploadBookProgress(it, toast) {\n                    successAction?.invoke()\n                }\n                ensureActive()\n                it.update()\n            }\n        }\n    }\n\n    /**\n     * 同步阅读进度\n     * 如果当前进度快于服务器进度或者没有进度进行上传，如果慢与服务器进度则执行传入动作\n     */\n    fun syncProgress(\n        newProgressAction: ((progress: BookProgress) -> Unit)? = null,\n        uploadSuccessAction: (() -> Unit)? = null,\n        syncSuccessAction: (() -> Unit)? = null\n    ) {\n        if (!AppConfig.syncBookProgress) return\n        val book = book ?: return\n        Coroutine.async {\n            AppWebDav.getBookProgress(book)\n        }.onError {\n            AppLog.put(\"拉取阅读进度失败\", it)\n        }.onSuccess { progress ->\n            if (progress == null || progress.durChapterIndex < book.durChapterIndex ||\n                (progress.durChapterIndex == book.durChapterIndex\n                        && progress.durChapterPos < book.durChapterPos)\n            ) {\n                // 服务器没有进度或者进度比服务器快，上传现有进度\n                Coroutine.async {\n                    AppWebDav.uploadBookProgress(BookProgress(book), uploadSuccessAction)\n                    book.update()\n                }\n            } else if (progress.durChapterIndex > book.durChapterIndex ||\n                progress.durChapterPos > book.durChapterPos\n            ) {\n                // 进度比服务器慢，执行传入动作\n                newProgressAction?.invoke(progress)\n            } else {\n                syncSuccessAction?.invoke()\n            }\n        }\n    }\n\n    fun upReadTime() {\n        executor.execute {\n            if (!AppConfig.enableReadRecord) {\n                return@execute\n            }\n            readRecord.readTime = readRecord.readTime + System.currentTimeMillis() - readStartTime\n            readStartTime = System.currentTimeMillis()\n            readRecord.lastRead = System.currentTimeMillis()\n            appDb.readRecordDao.insert(readRecord)\n        }\n    }\n\n    fun upMsg(msg: String?) {\n        if (ReadBook.msg != msg) {\n            ReadBook.msg = msg\n            callBack?.upContent()\n        }\n    }\n\n    fun moveToNextPage(): Boolean {\n        var hasNextPage = false\n        curTextChapter?.let {\n            val nextPagePos = it.getNextPageLength(durChapterPos)\n            if (nextPagePos >= 0) {\n                hasNextPage = true\n                it.getPage(durPageIndex)?.removePageAloudSpan()\n                durChapterPos = nextPagePos\n                callBack?.cancelSelect()\n                callBack?.upContent()\n                saveRead(true)\n            }\n        }\n        return hasNextPage\n    }\n\n    fun moveToPrevPage(): Boolean {\n        var hasPrevPage = false\n        curTextChapter?.let {\n            val prevPagePos = it.getPrevPageLength(durChapterPos)\n            if (prevPagePos >= 0) {\n                hasPrevPage = true\n                durChapterPos = prevPagePos\n                callBack?.upContent()\n                saveRead(true)\n            }\n        }\n        return hasPrevPage\n    }\n\n    fun moveToNextChapter(upContent: Boolean, upContentInPlace: Boolean = true): Boolean {\n        if (durChapterIndex < simulatedChapterSize - 1) {\n            durChapterPos = 0\n            durChapterIndex++\n            clearExpiredChapterLoadingJob()\n            prevTextChapter = curTextChapter\n            curTextChapter = nextTextChapter\n            nextTextChapter = null\n            if (curTextChapter == null) {\n                AppLog.putDebug(\"moveToNextChapter-章节未加载,开始加载\")\n                if (upContentInPlace) callBack?.upContent()\n                loadContent(durChapterIndex, upContent, resetPageOffset = false)\n            } else if (upContent && upContentInPlace) {\n                AppLog.putDebug(\"moveToNextChapter-章节已加载,刷新视图\")\n                callBack?.upContent()\n            }\n            loadContent(durChapterIndex.plus(1), upContent, false)\n            saveRead()\n            callBack?.upMenuView()\n            AppLog.putDebug(\"moveToNextChapter-curPageChanged()\")\n            curPageChanged()\n            return true\n        } else {\n            AppLog.putDebug(\"跳转下一章失败,没有下一章\")\n            return false\n        }\n    }\n\n    suspend fun moveToNextChapterAwait(\n        upContent: Boolean,\n        upContentInPlace: Boolean = true\n    ): Boolean {\n        if (durChapterIndex < simulatedChapterSize - 1) {\n            durChapterPos = 0\n            durChapterIndex++\n            clearExpiredChapterLoadingJob()\n            prevTextChapter = curTextChapter\n            curTextChapter = nextTextChapter\n            nextTextChapter = null\n            if (curTextChapter == null) {\n                AppLog.putDebug(\"moveToNextChapter-章节未加载,开始加载\")\n                if (upContentInPlace) callBack?.upContentAwait()\n                loadContentAwait(durChapterIndex, upContent, resetPageOffset = false)\n            } else if (upContent && upContentInPlace) {\n                AppLog.putDebug(\"moveToNextChapter-章节已加载,刷新视图\")\n                callBack?.upContentAwait()\n            }\n            loadContent(durChapterIndex.plus(1), upContent, false)\n            saveRead()\n            callBack?.upMenuView()\n            AppLog.putDebug(\"moveToNextChapter-curPageChanged()\")\n            curPageChanged()\n            return true\n        } else {\n            AppLog.putDebug(\"跳转下一章失败,没有下一章\")\n            return false\n        }\n    }\n\n    fun moveToPrevChapter(\n        upContent: Boolean,\n        toLast: Boolean = true,\n        upContentInPlace: Boolean = true\n    ): Boolean {\n        if (durChapterIndex > 0) {\n            durChapterPos = if (toLast) prevTextChapter?.lastReadLength ?: Int.MAX_VALUE else 0\n            durChapterIndex--\n            clearExpiredChapterLoadingJob()\n            nextTextChapter = curTextChapter\n            curTextChapter = prevTextChapter\n            prevTextChapter = null\n            if (curTextChapter == null) {\n                if (upContentInPlace) callBack?.upContent()\n                loadContent(durChapterIndex, upContent, resetPageOffset = false)\n            } else if (upContent && upContentInPlace) {\n                callBack?.upContent()\n            }\n            loadContent(durChapterIndex.minus(1), upContent, false)\n            saveRead()\n            callBack?.upMenuView()\n            curPageChanged()\n            return true\n        } else {\n            return false\n        }\n    }\n\n    fun skipToPage(index: Int, success: (() -> Unit)? = null) {\n        durChapterPos = curTextChapter?.getReadLength(index) ?: index\n        callBack?.upContent {\n            success?.invoke()\n        }\n        curPageChanged()\n        saveRead(true)\n    }\n\n    fun setPageIndex(index: Int) {\n        recycleRecorders(durPageIndex, index)\n        durChapterPos = curTextChapter?.getReadLength(index) ?: index\n        saveRead(true)\n        curPageChanged(true)\n    }\n\n    fun recycleRecorders(beforeIndex: Int, afterIndex: Int) {\n        if (!AppConfig.optimizeRender) {\n            return\n        }\n        executor.execute {\n            val textChapter = curTextChapter ?: return@execute\n            if (afterIndex > beforeIndex) {\n                textChapter.getPage(afterIndex - 2)?.recycleRecorders()\n            }\n            if (afterIndex < beforeIndex) {\n                textChapter.getPage(afterIndex + 3)?.recycleRecorders()\n            }\n        }\n    }\n\n    fun openChapter(\n        index: Int,\n        durChapterPos: Int = 0,\n        upContent: Boolean = true,\n        success: (() -> Unit)? = null\n    ) {\n        if (index < chapterSize) {\n            clearTextChapter()\n            if (upContent) callBack?.upContent()\n            durChapterIndex = index\n            ReadBook.durChapterPos = durChapterPos\n            saveRead()\n            loadContent(resetPageOffset = true) {\n                success?.invoke()\n            }\n        }\n    }\n\n    /**\n     * 当前页面变化\n     */\n    private fun curPageChanged(pageChanged: Boolean = false) {\n        callBack?.pageChanged()\n        curTextChapter?.let {\n            if (BaseReadAloudService.isRun && it.isCompleted) {\n                val scrollPageAnim = pageAnim() == 3\n                if (scrollPageAnim && pageChanged) {\n                    ReadAloud.pause(appCtx)\n                } else {\n                    readAloud(!BaseReadAloudService.pause)\n                }\n            }\n        }\n        upReadTime()\n        preDownload()\n    }\n\n    /**\n     * 朗读\n     */\n    fun readAloud(play: Boolean = true, startPos: Int = 0) {\n        book ?: return\n        val textChapter = curTextChapter ?: return\n        if (textChapter.isCompleted) {\n            ReadAloud.play(appCtx, play, startPos = startPos)\n        }\n    }\n\n    /**\n     * 当前页数\n     */\n    val durPageIndex: Int\n        get() {\n            return curTextChapter?.getPageIndexByCharIndex(durChapterPos) ?: durChapterPos\n        }\n\n    /**\n     * 是否排版到了当前阅读位置\n     */\n    val isLayoutAvailable inline get() = durPageIndex >= 0\n\n    val isScroll inline get() = pageAnim() == scrollPageAnim\n\n    val contentLoadFinish get() = curTextChapter != null || msg != null\n\n    /**\n     * chapterOnDur: 0为当前页,1为下一页,-1为上一页\n     */\n    fun textChapter(chapterOnDur: Int = 0): TextChapter? {\n        return when (chapterOnDur) {\n            0 -> curTextChapter\n            1 -> nextTextChapter\n            -1 -> prevTextChapter\n            else -> null\n        }\n    }\n\n    /**\n     * 加载当前章节和前后一章内容\n     * @param resetPageOffset 滚动阅读是否重置滚动位置\n     * @param success 当前章节加载完成回调\n     */\n    fun loadContent(\n        resetPageOffset: Boolean,\n        success: (() -> Unit)? = null\n    ) {\n        loadContent(durChapterIndex, resetPageOffset = resetPageOffset) {\n            success?.invoke()\n        }\n        loadContent(durChapterIndex + 1, resetPageOffset = resetPageOffset)\n        loadContent(durChapterIndex - 1, resetPageOffset = resetPageOffset)\n    }\n\n    fun loadOrUpContent() {\n        if (curTextChapter == null) {\n            loadContent(durChapterIndex)\n        } else {\n            callBack?.upContent()\n        }\n        if (nextTextChapter == null) {\n            loadContent(durChapterIndex + 1)\n        }\n        if (prevTextChapter == null) {\n            loadContent(durChapterIndex - 1)\n        }\n    }\n\n    /**\n     * 加载章节内容\n     * @param index 章节序号\n     * @param upContent 是否更新视图\n     * @param resetPageOffset 滚动阅读是否重置滚动位置\n     * @param success 加载完成回调\n     */\n    fun loadContent(\n        index: Int,\n        upContent: Boolean = true,\n        resetPageOffset: Boolean = false,\n        success: (() -> Unit)? = null\n    ) {\n        Coroutine.async {\n            val book = book!!\n            val chapter = appDb.bookChapterDao.getChapter(book.bookUrl, index) ?: return@async\n            if (addLoading(index)) {\n                BookHelp.getContent(book, chapter)?.let {\n                    contentLoadFinish(\n                        book,\n                        chapter,\n                        it,\n                        upContent,\n                        resetPageOffset,\n                        success = success\n                    )\n                } ?: download(\n                    downloadScope,\n                    chapter,\n                    resetPageOffset\n                )\n            }\n        }.onError {\n            AppLog.put(\"加载正文出错\\n${it.localizedMessage}\")\n        }\n    }\n\n    suspend fun loadContentAwait(\n        index: Int,\n        upContent: Boolean = true,\n        resetPageOffset: Boolean = false,\n        success: (() -> Unit)? = null\n    ) = withContext(IO) {\n        if (addLoading(index)) {\n            try {\n                val book = book!!\n                val chapter = appDb.bookChapterDao.getChapter(book.bookUrl, index)!!\n                val content = BookHelp.getContent(book, chapter) ?: downloadAwait(chapter)\n                contentLoadFinishAwait(book, chapter, content, upContent, resetPageOffset)\n                success?.invoke()\n            } catch (e: Exception) {\n                AppLog.put(\"加载正文出错\\n${e.localizedMessage}\")\n            } finally {\n                removeLoading(index)\n            }\n        }\n    }\n\n    /**\n     * 下载正文\n     */\n    private suspend fun downloadIndex(index: Int) {\n        if (index < 0) return\n        if (index > chapterSize - 1) {\n            upToc()\n            return\n        }\n        val book = book ?: return\n        val chapter = appDb.bookChapterDao.getChapter(book.bookUrl, index) ?: return\n        if (BookHelp.hasContent(book, chapter)) {\n            downloadedChapters.add(chapter.index)\n        } else {\n            delay(1000)\n            if (addLoading(index)) {\n                download(downloadScope, chapter, false, preDownloadSemaphore)\n            }\n        }\n    }\n\n    /**\n     * 下载正文\n     */\n    private fun download(\n        scope: CoroutineScope,\n        chapter: BookChapter,\n        resetPageOffset: Boolean,\n        semaphore: Semaphore? = null,\n        success: (() -> Unit)? = null\n    ) {\n        val book = book ?: return removeLoading(chapter.index)\n        val bookSource = bookSource\n        if (bookSource != null) {\n            CacheBook.getOrCreate(bookSource, book).download(scope, chapter, semaphore)\n        } else {\n            val msg = if (book.isLocal) \"无内容\" else \"没有书源\"\n            contentLoadFinish(\n                book,\n                chapter,\n                \"加载正文失败\\n$msg\",\n                resetPageOffset = resetPageOffset,\n                success = success\n            )\n        }\n    }\n\n    private suspend fun downloadAwait(chapter: BookChapter): String {\n        val book = book!!\n        val bookSource = bookSource\n        if (bookSource != null) {\n            return CacheBook.getOrCreate(bookSource, book).downloadAwait(chapter)\n        } else {\n            val msg = if (book.isLocal) \"无内容\" else \"没有书源\"\n            return \"加载正文失败\\n$msg\"\n        }\n    }\n\n    @Synchronized\n    private fun addLoading(index: Int): Boolean {\n        if (loadingChapters.contains(index)) return false\n        loadingChapters.add(index)\n        return true\n    }\n\n    @Synchronized\n    fun removeLoading(index: Int) {\n        loadingChapters.remove(index)\n    }\n\n    /**\n     * 内容加载完成\n     */\n    @Synchronized\n    fun contentLoadFinish(\n        book: Book,\n        chapter: BookChapter,\n        content: String,\n        upContent: Boolean = true,\n        resetPageOffset: Boolean,\n        canceled: Boolean = false,\n        success: (() -> Unit)? = null\n    ) {\n        removeLoading(chapter.index)\n        if (canceled || chapter.index !in durChapterIndex - 1..durChapterIndex + 1) {\n            return\n        }\n        chapterLoadingJobs[chapter.index]?.cancel()\n        val job = Coroutine.async(this, start = CoroutineStart.LAZY) {\n            val contentProcessor = ContentProcessor.get(book.name, book.origin)\n            val displayTitle = chapter.getDisplayTitle(\n                contentProcessor.getTitleReplaceRules(),\n                book.getUseReplaceRule()\n            )\n            val contents = contentProcessor\n                .getContent(book, chapter, content, includeTitle = false)\n            ensureActive()\n            val textChapter = ChapterProvider.getTextChapterAsync(\n                this, book, chapter, displayTitle, contents, simulatedChapterSize\n            )\n            when (val offset = chapter.index - durChapterIndex) {\n                0 -> curChapterLoadingLock.withLock {\n                    withContext(Main) {\n                        ensureActive()\n                        curTextChapter = textChapter\n                    }\n                    callBack?.upMenuView()\n                    var available = false\n                    for (page in textChapter.layoutChannel) {\n                        val index = page.index\n                        if (!available && page.containPos(durChapterPos)) {\n                            if (upContent) {\n                                callBack?.upContent(offset, resetPageOffset)\n                            }\n                            available = true\n                        }\n                        if (upContent && isScroll) {\n                            if (max(index - 3, 0) < durPageIndex) {\n                                callBack?.upContent(offset, false)\n                            }\n                        }\n                        callBack?.onLayoutPageCompleted(index, page)\n                    }\n                    if (upContent) callBack?.upContent(offset, !available && resetPageOffset)\n                    curPageChanged()\n                    callBack?.contentLoadFinish()\n                }\n\n                -1 -> prevChapterLoadingLock.withLock {\n                    withContext(Main) {\n                        ensureActive()\n                        prevTextChapter = textChapter\n                    }\n                    textChapter.layoutChannel.receiveAsFlow().collect()\n                    if (upContent) callBack?.upContent(offset, resetPageOffset)\n                }\n\n                1 -> nextChapterLoadingLock.withLock {\n                    withContext(Main) {\n                        ensureActive()\n                        nextTextChapter = textChapter\n                    }\n                    for (page in textChapter.layoutChannel) {\n                        if (page.index > 1) {\n                            continue\n                        }\n                        if (upContent) callBack?.upContent(offset, resetPageOffset)\n                    }\n                }\n            }\n\n            return@async\n        }.onError {\n            if (it is CancellationException) {\n                return@onError\n            }\n            AppLog.put(\"ChapterProvider ERROR\", it)\n            appCtx.toastOnUi(\"ChapterProvider ERROR:\\n${it.stackTraceStr}\")\n        }.onSuccess {\n            success?.invoke()\n        }\n        chapterLoadingJobs[chapter.index] = job\n        job.start()\n    }\n\n    suspend fun contentLoadFinishAwait(\n        book: Book,\n        chapter: BookChapter,\n        content: String,\n        upContent: Boolean = true,\n        resetPageOffset: Boolean\n    ) {\n        removeLoading(chapter.index)\n        if (chapter.index !in durChapterIndex - 1..durChapterIndex + 1) {\n            return\n        }\n        kotlin.runCatching {\n            val contentProcessor = ContentProcessor.get(book.name, book.origin)\n            val displayTitle = chapter.getDisplayTitle(\n                contentProcessor.getTitleReplaceRules(),\n                book.getUseReplaceRule()\n            )\n            val contents = contentProcessor\n                .getContent(book, chapter, content, includeTitle = false)\n            val textChapter = ChapterProvider.getTextChapterAsync(\n                this@ReadBook, book, chapter, displayTitle, contents, simulatedChapterSize\n            )\n            when (val offset = chapter.index - durChapterIndex) {\n                0 -> {\n                    curTextChapter?.cancelLayout()\n                    withContext(Main) {\n                        curTextChapter = textChapter\n                    }\n                    callBack?.upMenuView()\n                    var available = false\n                    for (page in textChapter.layoutChannel) {\n                        val index = page.index\n                        if (!available && page.containPos(durChapterPos)) {\n                            if (upContent) {\n                                callBack?.upContent(offset, resetPageOffset)\n                            }\n                            available = true\n                        }\n                        if (upContent && isScroll) {\n                            if (max(index - 3, 0) < durPageIndex) {\n                                callBack?.upContent(offset, false)\n                            }\n                        }\n                        callBack?.onLayoutPageCompleted(index, page)\n                    }\n                    if (upContent) callBack?.upContent(offset, !available && resetPageOffset)\n                    curPageChanged()\n                    callBack?.contentLoadFinish()\n                }\n\n                -1 -> {\n                    prevTextChapter?.cancelLayout()\n                    withContext(Main) {\n                        prevTextChapter = textChapter\n                    }\n                    textChapter.layoutChannel.receiveAsFlow().collect()\n                    if (upContent) callBack?.upContent(offset, resetPageOffset)\n                }\n\n                1 -> {\n                    nextTextChapter?.cancelLayout()\n                    withContext(Main) {\n                        nextTextChapter = textChapter\n                    }\n                    for (page in textChapter.layoutChannel) {\n                        if (page.index > 1) {\n                            continue\n                        }\n                        if (upContent) callBack?.upContent(offset, resetPageOffset)\n                    }\n                }\n            }\n        }.onFailure {\n            if (it is CancellationException) {\n                return@onFailure\n            }\n            AppLog.put(\"ChapterProvider ERROR\", it)\n            appCtx.toastOnUi(\"ChapterProvider ERROR:\\n${it.stackTraceStr}\")\n        }\n    }\n\n    @Synchronized\n    fun upToc() {\n        val bookSource = bookSource ?: return\n        val book = book ?: return\n        if (!book.canUpdate) return\n        if (chapterSize - durChapterIndex - 1 >= 3) return\n        if (System.currentTimeMillis() - book.lastCheckTime < 600000) return\n        book.lastCheckTime = System.currentTimeMillis()\n        val oldBook = book.copy()\n        WebBook.getChapterList(this, bookSource, book).onSuccess(IO) { cList ->\n            ensureActive()\n            if (cList.size > chapterSize) {\n                if (oldBook.bookUrl == book.bookUrl) {\n                    appDb.bookDao.update(book)\n                } else {\n                    appDb.bookDao.replace(oldBook, book)\n                    BookHelp.updateCacheFolder(oldBook, book)\n                }\n                appDb.bookChapterDao.delByBook(oldBook.bookUrl)\n                appDb.bookChapterDao.insert(*cList.toTypedArray())\n                onChapterListUpdated(book, false)\n                nextTextChapter ?: loadContent(durChapterIndex + 1)\n            }\n        }\n    }\n\n    fun pageAnim(): Int {\n        return book?.getPageAnim() ?: ReadBookConfig.pageAnim\n    }\n\n    fun setCharset(charset: String) {\n        book?.let {\n            it.charset = charset\n            callBack?.loadChapterList(it)\n        }\n        saveRead()\n    }\n\n    fun saveRead(pageChanged: Boolean = false) {\n        executor.execute {\n            kotlin.runCatching {\n                val book = book ?: return@execute\n                book.lastCheckCount = 0\n                book.durChapterTime = System.currentTimeMillis()\n                val chapterChanged = book.durChapterIndex != durChapterIndex\n                book.durChapterIndex = durChapterIndex\n                book.durChapterPos = durChapterPos\n                if (!pageChanged || chapterChanged) {\n                    appDb.bookChapterDao.getChapter(book.bookUrl, durChapterIndex)?.let {\n                        book.durChapterTitle = it.getDisplayTitle(\n                            ContentProcessor.get(book.name, book.origin).getTitleReplaceRules(),\n                            book.getUseReplaceRule()\n                        )\n                    }\n                }\n                appDb.bookDao.update(book)\n            }.onFailure {\n                AppLog.put(\"保存书籍阅读进度信息出错\\n$it\", it)\n            }\n        }\n    }\n\n    /**\n     * 预下载\n     */\n    private fun preDownload() {\n        if (book?.isLocal == true) return\n        executor.execute {\n            if (AppConfig.preDownloadNum < 2) {\n                upToc()\n                return@execute\n            }\n            preDownloadTask?.cancel()\n            preDownloadTask = launch(IO) {\n                //预下载\n                launch {\n                    val maxChapterIndex =\n                        min(durChapterIndex + AppConfig.preDownloadNum, chapterSize)\n                    for (i in durChapterIndex.plus(2)..maxChapterIndex) {\n                        if (downloadedChapters.contains(i)) continue\n                        if ((downloadFailChapters[i] ?: 0) >= 3) continue\n                        downloadIndex(i)\n                    }\n                }\n                launch {\n                    val minChapterIndex = durChapterIndex - min(5, AppConfig.preDownloadNum)\n                    for (i in durChapterIndex.minus(2) downTo minChapterIndex) {\n                        if (downloadedChapters.contains(i)) continue\n                        if ((downloadFailChapters[i] ?: 0) >= 3) continue\n                        downloadIndex(i)\n                    }\n                }\n            }\n        }\n    }\n\n    fun cancelPreDownloadTask() {\n        if (contentLoadFinish) {\n            preDownloadTask?.cancel()\n            downloadScope.coroutineContext.cancelChildren()\n        }\n    }\n\n    fun onChapterListUpdated(newBook: Book, loadContent: Boolean = true) {\n        if (newBook.isSameNameAuthor(book)) {\n            book = newBook\n            chapterSize = newBook.totalChapterNum\n            simulatedChapterSize = newBook.simulatedTotalChapterNum()\n            if (simulatedChapterSize > 0 && durChapterIndex > simulatedChapterSize - 1) {\n                durChapterIndex = simulatedChapterSize - 1\n            }\n            callBack?.upMenuView()\n            if (callBack == null) {\n                clearTextChapter()\n            } else if (loadContent) {\n                loadContent(true)\n            }\n        }\n    }\n\n    private fun clearExpiredChapterLoadingJob(clearAll: Boolean = false) {\n        val iterator = chapterLoadingJobs.iterator()\n        while (iterator.hasNext()) {\n            val (index, job) = iterator.next()\n            if (clearAll || index !in durChapterIndex - 1..durChapterIndex + 1) {\n                job.cancel()\n                iterator.remove()\n            }\n        }\n    }\n\n    /**\n     * 注册回调\n     */\n    fun register(cb: CallBack) {\n        callBack?.notifyBookChanged()\n        callBack = cb\n    }\n\n    /**\n     * 取消注册回调\n     */\n    fun unregister(cb: CallBack) {\n        if (callBack === cb) {\n            callBack = null\n        }\n        releaseAndCancel()\n    }\n\n    private fun releaseAndCancel() {\n        msg = null\n        preDownloadTask?.cancel()\n        downloadScope.coroutineContext.cancelChildren()\n        coroutineContext.cancelChildren()\n        ImageProvider.clear()\n        clearExpiredChapterLoadingJob(true)\n        if (!CacheBookService.isRun) {\n            CacheBook.close()\n        }\n    }\n\n    interface CallBack : LayoutProgressListener {\n        fun upMenuView()\n\n        fun loadChapterList(book: Book)\n\n        fun upContent(\n            relativePosition: Int = 0,\n            resetPageOffset: Boolean = true,\n            success: (() -> Unit)? = null\n        )\n\n        suspend fun upContentAwait(\n            relativePosition: Int = 0,\n            resetPageOffset: Boolean = true,\n            success: (() -> Unit)? = null\n        )\n\n        fun pageChanged()\n\n        fun contentLoadFinish()\n\n        fun upPageAnim(upRecorder: Boolean = false)\n\n        fun notifyBookChanged()\n\n        fun sureNewProgress(progress: BookProgress)\n\n        fun cancelSelect()\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/model/ReadManga.kt",
    "content": "package io.legado.app.model\n\nimport io.legado.app.constant.AppLog\nimport io.legado.app.data.appDb\nimport io.legado.app.data.entities.Book\nimport io.legado.app.data.entities.BookChapter\nimport io.legado.app.data.entities.BookProgress\nimport io.legado.app.data.entities.BookSource\nimport io.legado.app.data.entities.ReadRecord\nimport io.legado.app.help.AppWebDav\nimport io.legado.app.help.ConcurrentRateLimiter\nimport io.legado.app.help.book.BookHelp\nimport io.legado.app.help.book.ContentProcessor\nimport io.legado.app.help.book.isLocal\nimport io.legado.app.help.book.isSameNameAuthor\nimport io.legado.app.help.book.readSimulating\nimport io.legado.app.help.book.simulatedTotalChapterNum\nimport io.legado.app.help.book.update\nimport io.legado.app.help.config.AppConfig\nimport io.legado.app.help.coroutine.Coroutine\nimport io.legado.app.help.globalExecutor\nimport io.legado.app.model.webBook.WebBook\nimport io.legado.app.ui.book.manga.entities.BaseMangaPage\nimport io.legado.app.ui.book.manga.entities.MangaChapter\nimport io.legado.app.ui.book.manga.entities.MangaContent\nimport io.legado.app.ui.book.manga.entities.MangaPage\nimport io.legado.app.ui.book.manga.entities.ReaderLoading\nimport io.legado.app.utils.mapIndexed\nimport kotlinx.coroutines.CoroutineScope\nimport kotlinx.coroutines.CoroutineStart\nimport kotlinx.coroutines.Dispatchers.IO\nimport kotlinx.coroutines.Job\nimport kotlinx.coroutines.MainScope\nimport kotlinx.coroutines.SupervisorJob\nimport kotlinx.coroutines.cancelChildren\nimport kotlinx.coroutines.delay\nimport kotlinx.coroutines.ensureActive\nimport kotlinx.coroutines.flow.distinctUntilChanged\nimport kotlinx.coroutines.flow.toList\nimport kotlinx.coroutines.launch\nimport kotlinx.coroutines.sync.Semaphore\nimport kotlin.math.min\n\n@Suppress(\"MemberVisibilityCanBePrivate\")\nobject ReadManga : CoroutineScope by MainScope() {\n    var inBookshelf = false\n    var book: Book? = null\n    val executor = globalExecutor\n    var durChapterIndex = 0 //章节位置\n    var chapterSize = 0//总章节\n    var durChapterPos = 0\n    var chapterChanged = false\n    var prevMangaChapter: MangaChapter? = null\n    var curMangaChapter: MangaChapter? = null\n    var nextMangaChapter: MangaChapter? = null\n    var bookSource: BookSource? = null\n    var readStartTime: Long = System.currentTimeMillis()\n    private val readRecord = ReadRecord()\n    private val loadingChapters = arrayListOf<Int>()\n    var simulatedChapterSize = 0\n    var mCallback: Callback? = null\n    var preDownloadTask: Job? = null\n    val downloadedChapters = hashSetOf<Int>()\n    val downloadFailChapters = hashMapOf<Int, Int>()\n    val downloadScope = CoroutineScope(SupervisorJob() + IO)\n    val preDownloadSemaphore = Semaphore(2)\n    var rateLimiter = ConcurrentRateLimiter(null)\n    val mangaContents get() = buildMangaContent()\n    val hasNextChapter get() = durChapterIndex < simulatedChapterSize - 1\n\n    fun resetData(book: Book) {\n        ReadManga.book = book\n        readRecord.bookName = book.name\n        readRecord.readTime = appDb.readRecordDao.getReadTime(book.name) ?: 0\n        chapterSize = appDb.bookChapterDao.getChapterCount(book.bookUrl)\n        simulatedChapterSize = if (book.readSimulating()) {\n            book.simulatedTotalChapterNum()\n        } else {\n            chapterSize\n        }\n        durChapterIndex = book.durChapterIndex\n        durChapterPos = book.durChapterPos\n        clearMangaChapter()\n        upWebBook(book)\n        synchronized(this) {\n            loadingChapters.clear()\n            downloadedChapters.clear()\n            downloadFailChapters.clear()\n        }\n    }\n\n    fun upData(book: Book) {\n        ReadManga.book = book\n        chapterSize = appDb.bookChapterDao.getChapterCount(book.bookUrl)\n        simulatedChapterSize = if (book.readSimulating()) {\n            book.simulatedTotalChapterNum()\n        } else {\n            chapterSize\n        }\n\n        if (durChapterIndex != book.durChapterIndex) {\n            durChapterIndex = book.durChapterIndex\n            durChapterPos = book.durChapterPos\n            clearMangaChapter()\n        }\n        upWebBook(book)\n        synchronized(this) {\n            loadingChapters.clear()\n            downloadedChapters.clear()\n            downloadFailChapters.clear()\n        }\n    }\n\n    fun upWebBook(book: Book) {\n        appDb.bookSourceDao.getBookSource(book.origin)?.let {\n            bookSource = it\n            rateLimiter = ConcurrentRateLimiter(it)\n        } ?: let {\n            bookSource = null\n        }\n    }\n\n    fun clearMangaChapter() {\n        prevMangaChapter = null\n        curMangaChapter = null\n        nextMangaChapter = null\n    }\n\n    //每次切换章节更新阅读记录\n    fun upReadTime() {\n        executor.execute {\n            if (!AppConfig.enableReadRecord) {\n                return@execute\n            }\n            readRecord.readTime = readRecord.readTime + System.currentTimeMillis() - readStartTime\n            readStartTime = System.currentTimeMillis()\n            readRecord.lastRead = System.currentTimeMillis()\n            appDb.readRecordDao.insert(readRecord)\n        }\n    }\n\n    @Synchronized\n    private fun addLoading(index: Int): Boolean {\n        if (loadingChapters.contains(index)) return false\n        loadingChapters.add(index)\n        return true\n    }\n\n    @Synchronized\n    fun removeLoading(index: Int) {\n        loadingChapters.remove(index)\n    }\n\n    fun loadContent() {\n        clearMangaChapter()\n        loadContent(durChapterIndex)\n        loadContent(durChapterIndex + 1)\n        loadContent(durChapterIndex - 1)\n    }\n\n    fun loadOrUpContent() {\n        if (curMangaChapter == null) {\n            loadContent(durChapterIndex)\n        } else {\n            mCallback?.upContent()\n        }\n        if (nextMangaChapter == null) {\n            loadContent(durChapterIndex + 1)\n        }\n        if (prevMangaChapter == null) {\n            loadContent(durChapterIndex - 1)\n        }\n    }\n\n    private fun loadContent(index: Int) {\n        Coroutine.async {\n            val book = book!!\n            val chapter = appDb.bookChapterDao.getChapter(book.bookUrl, index) ?: return@async\n            if (addLoading(index)) {\n                BookHelp.getContent(book, chapter)?.let {\n                    contentLoadFinish(chapter, it)\n                } ?: run {\n                    download(downloadScope, chapter)\n                }\n            }\n        }.onError {\n            AppLog.put(\"加载正文出错\\n${it.localizedMessage}\")\n        }\n    }\n\n    /**\n     * 内容加载完成\n     */\n    suspend fun contentLoadFinish(\n        chapter: BookChapter,\n        content: String?,\n        errorMsg: String = \"加载内容失败\",\n        canceled: Boolean = false\n    ) {\n        removeLoading(chapter.index)\n        if (canceled || chapter.index !in durChapterIndex - 1..durChapterIndex + 1) {\n            return\n        }\n        when (val offset = chapter.index - durChapterIndex) {\n            0 -> {\n                if (content == null) {\n                    mCallback?.loadFail(errorMsg)\n                    return\n                }\n                if (content.isEmpty() && !chapter.isVolume) {\n                    mCallback?.loadFail(\"正文内容为空\")\n                    return\n                }\n                val mangaChapter = getManageChapter(chapter, content)\n                if (mangaChapter.imageCount == 0 && !chapter.isVolume) {\n                    mCallback?.loadFail(\"正文没有图片\")\n                    return\n                }\n                curMangaChapter = mangaChapter\n                mCallback?.upContent()\n            }\n\n            -1, 1 -> {\n                if (content == null || (!chapter.isVolume && content.isEmpty())) {\n                    return\n                }\n                val mangaChapter = getManageChapter(chapter, content)\n                if (mangaChapter.imageCount == 0 && !chapter.isVolume) {\n                    return\n                }\n\n                when (offset) {\n                    -1 -> prevMangaChapter = mangaChapter\n                    1 -> nextMangaChapter = mangaChapter\n                }\n\n                mCallback?.upContent()\n            }\n        }\n    }\n\n    private fun buildMangaContent(): MangaContent {\n        val items = arrayListOf<BaseMangaPage>()\n        var pos = 0\n        var curFinish = false\n        var nextFinish = false\n        prevMangaChapter?.let {\n            pos += it.pages.size\n            items.addAll(it.pages)\n        }\n        curMangaChapter?.let {\n            curFinish = true\n            items.addAll(it.pages)\n            durChapterPos = if (it.imageCount > 0) {\n                durChapterPos.coerceIn(0, it.imageCount - 1)\n            } else {\n                0\n            }\n            pos += durChapterPos\n            if (!AppConfig.hideMangaTitle && it.imageCount > 0) {\n                pos++\n            }\n        }\n        nextMangaChapter?.let {\n            nextFinish = true\n            items.addAll(it.pages)\n        }\n        return MangaContent(pos, items, curFinish, nextFinish)\n    }\n\n    /**\n     * 加载下一章\n     */\n    fun moveToNextChapter(toFirst: Boolean = false): Boolean {\n        if (durChapterIndex < simulatedChapterSize - 1) {\n            if (toFirst) {\n                mCallback?.showLoading()\n                durChapterPos = 0\n            }\n            durChapterIndex++\n            prevMangaChapter = curMangaChapter\n            curMangaChapter = nextMangaChapter\n            nextMangaChapter = null\n            if (curMangaChapter == null) {\n                mCallback?.startLoad()\n                loadContent(durChapterIndex)\n            } else {\n                mCallback?.upContent()\n            }\n            loadContent(durChapterIndex + 1)\n            saveRead()\n            AppLog.putDebug(\"moveToNextChapter-curPageChanged()\")\n            curPageChanged()\n            return true\n        } else {\n            AppLog.putDebug(\"跳转下一章失败,没有下一章\")\n            return false\n        }\n    }\n\n    fun moveToPrevChapter(toFirst: Boolean = false): Boolean {\n        if (durChapterIndex > 0) {\n            if (toFirst) {\n                mCallback?.showLoading()\n                durChapterPos = 0\n            }\n            durChapterIndex--\n            nextMangaChapter = curMangaChapter\n            curMangaChapter = prevMangaChapter\n            prevMangaChapter = null\n            if (curMangaChapter == null) {\n                loadContent(durChapterIndex)\n            } else {\n                mCallback?.upContent()\n            }\n            loadContent(durChapterIndex - 1)\n            saveRead()\n            return true\n        }\n        return false\n    }\n\n    fun curPageChanged() {\n        upReadTime()\n        preDownload()\n    }\n\n    fun saveRead(pageChanged: Boolean = false) {\n        executor.execute {\n            kotlin.runCatching {\n                val book = book ?: return@execute\n                book.lastCheckCount = 0\n                book.durChapterTime = System.currentTimeMillis()\n                val chapterChanged = book.durChapterIndex != durChapterIndex\n                book.durChapterIndex = durChapterIndex\n                book.durChapterPos = durChapterPos\n                if (!pageChanged || chapterChanged) {\n                    appDb.bookChapterDao.getChapter(book.bookUrl, durChapterIndex)?.let {\n                        book.durChapterTitle = it.getDisplayTitle(\n                            ContentProcessor.get(book.name, book.origin).getTitleReplaceRules(),\n                            book.getUseReplaceRule()\n                        )\n                    }\n                }\n                appDb.bookDao.update(book)\n            }.onFailure {\n                AppLog.put(\"保存漫画阅读进度信息出错\\n$it\", it)\n            }\n        }\n    }\n\n    private fun downloadNetworkContent(\n        bookSource: BookSource,\n        scope: CoroutineScope,\n        chapter: BookChapter,\n        book: Book,\n        semaphore: Semaphore?,\n        success: suspend (String) -> Unit = {},\n        error: suspend () -> Unit = {},\n        cancel: suspend () -> Unit = {},\n    ) {\n        WebBook.getContent(\n            scope,\n            bookSource,\n            book,\n            chapter,\n            start = CoroutineStart.LAZY,\n            executeContext = IO,\n            semaphore = semaphore\n        ).onSuccess { content ->\n            success.invoke(content)\n        }.onError {\n            error.invoke()\n        }.onCancel {\n            cancel.invoke()\n        }.start()\n    }\n\n    private fun preDownload() {\n        if (book?.isLocal == true) return\n        executor.execute {\n            if (AppConfig.preDownloadNum < 2) {\n                upToc()\n                return@execute\n            }\n            preDownloadTask?.cancel()\n            preDownloadTask = launch(IO) {\n                //预下载\n                launch {\n                    val maxChapterIndex =\n                        min(durChapterIndex + AppConfig.preDownloadNum, chapterSize)\n                    for (i in durChapterIndex.plus(2)..maxChapterIndex) {\n                        if (downloadedChapters.contains(i)) continue\n                        if ((downloadFailChapters[i] ?: 0) >= 3) continue\n                        downloadIndex(i)\n                    }\n                }\n                launch {\n                    val minChapterIndex = durChapterIndex - min(5, AppConfig.preDownloadNum)\n                    for (i in durChapterIndex.minus(2) downTo minChapterIndex) {\n                        if (downloadedChapters.contains(i)) continue\n                        if ((downloadFailChapters[i] ?: 0) >= 3) continue\n                        downloadIndex(i)\n                    }\n                }\n            }\n        }\n    }\n\n    fun cancelPreDownloadTask() {\n        if (curMangaChapter != null && nextMangaChapter != null) {\n            preDownloadTask?.cancel()\n            downloadScope.coroutineContext.cancelChildren()\n        }\n    }\n\n    private suspend fun downloadIndex(index: Int) {\n        if (index < 0) return\n        if (index > chapterSize - 1) {\n            upToc()\n            return\n        }\n        val book = book ?: return\n        val chapter = appDb.bookChapterDao.getChapter(book.bookUrl, index) ?: return\n        if (BookHelp.hasContent(book, chapter)) {\n            downloadedChapters.add(chapter.index)\n        } else {\n            delay(1000)\n            if (addLoading(index)) {\n                download(downloadScope, chapter, preDownloadSemaphore)\n            }\n        }\n    }\n\n    /**\n     * 获取正文\n     */\n    private suspend fun download(\n        scope: CoroutineScope,\n        chapter: BookChapter,\n        semaphore: Semaphore? = null,\n    ) {\n        val book = book ?: return removeLoading(chapter.index)\n        val bookSource = bookSource\n        if (bookSource != null) {\n            downloadNetworkContent(bookSource, scope, chapter, book, semaphore, success = {\n                downloadedChapters.add(chapter.index)\n                downloadFailChapters.remove(chapter.index)\n                contentLoadFinish(chapter, it)\n            }, error = {\n                downloadFailChapters[chapter.index] =\n                    (downloadFailChapters[chapter.index] ?: 0) + 1\n                contentLoadFinish(chapter, null)\n            }, cancel = {\n                contentLoadFinish(chapter, null, canceled = true)\n            })\n        } else {\n            contentLoadFinish(chapter, null, \"加载内容失败 没有书源\")\n        }\n    }\n\n    @Synchronized\n    fun upToc() {\n        val bookSource = bookSource ?: return\n        val book = book ?: return\n        if (!book.canUpdate) return\n        if (chapterSize - durChapterIndex - 1 >= 3) return\n        if (System.currentTimeMillis() - book.lastCheckTime < 600000) return\n        book.lastCheckTime = System.currentTimeMillis()\n        val oldBook = book.copy()\n        WebBook.getChapterList(this, bookSource, book).onSuccess(IO) { cList ->\n            ensureActive()\n            if (cList.size > chapterSize) {\n                if (oldBook.bookUrl == book.bookUrl) {\n                    appDb.bookDao.update(book)\n                } else {\n                    appDb.bookDao.replace(oldBook, book)\n                    BookHelp.updateCacheFolder(oldBook, book)\n                }\n                appDb.bookChapterDao.delByBook(oldBook.bookUrl)\n                appDb.bookChapterDao.insert(*cList.toTypedArray())\n                onChapterListUpdated(book, false)\n                nextMangaChapter ?: loadContent(durChapterIndex + 1)\n            }\n        }\n    }\n\n    fun uploadProgress(successAction: (() -> Unit)? = null) {\n        book?.let {\n            launch(IO) {\n                AppWebDav.uploadBookProgress(it) {\n                    successAction?.invoke()\n                }\n                ensureActive()\n                it.update()\n            }\n        }\n    }\n\n    /**\n     * 同步阅读进度\n     * 如果当前进度快于服务器进度或者没有进度进行上传，如果慢与服务器进度则执行传入动作\n     */\n    fun syncProgress(\n        newProgressAction: ((progress: BookProgress) -> Unit)? = null,\n        uploadSuccessAction: (() -> Unit)? = null,\n        syncSuccessAction: (() -> Unit)? = null,\n    ) {\n        if (!AppConfig.syncBookProgress) return\n        val book = book ?: return\n        Coroutine.async {\n            AppWebDav.getBookProgress(book)\n        }.onError {\n            AppLog.put(\"拉取阅读进度失败\", it)\n        }.onSuccess { progress ->\n            if (progress == null || progress.durChapterIndex < book.durChapterIndex ||\n                (progress.durChapterIndex == book.durChapterIndex\n                        && progress.durChapterPos < book.durChapterPos)\n            ) {\n                // 服务器没有进度或者进度比服务器快，上传现有进度\n                Coroutine.async {\n                    AppWebDav.uploadBookProgress(BookProgress(book), uploadSuccessAction)\n                    book.update()\n                }\n            } else if (progress.durChapterIndex > book.durChapterIndex ||\n                progress.durChapterPos > book.durChapterPos\n            ) {\n                // 进度比服务器慢，执行传入动作\n                newProgressAction?.invoke(progress)\n            } else {\n                syncSuccessAction?.invoke()\n            }\n        }\n    }\n\n    fun setProgress(progress: BookProgress) {\n        if (progress.durChapterIndex < chapterSize &&\n            (durChapterIndex != progress.durChapterIndex\n                    || durChapterPos != progress.durChapterPos)\n        ) {\n            mCallback?.showLoading()\n            if (progress.durChapterIndex == durChapterIndex) {\n                durChapterPos = progress.durChapterPos\n                mCallback?.upContent()\n            } else {\n                durChapterIndex = progress.durChapterIndex\n                durChapterPos = progress.durChapterPos\n                loadContent()\n            }\n            saveRead()\n        }\n    }\n\n    fun showLoading() {\n        mCallback?.showLoading()\n    }\n\n    fun loadFail(msg: String, retry: Boolean = true) {\n        mCallback?.loadFail(msg, retry)\n    }\n\n    fun onChapterListUpdated(newBook: Book, loadContent: Boolean = true) {\n        if (newBook.isSameNameAuthor(book)) {\n            book = newBook\n            chapterSize = newBook.totalChapterNum\n            simulatedChapterSize = newBook.simulatedTotalChapterNum()\n            if (simulatedChapterSize > 0 && durChapterIndex > simulatedChapterSize - 1) {\n                durChapterIndex = simulatedChapterSize - 1\n            }\n            if (mCallback == null) {\n                clearMangaChapter()\n            } else if (loadContent) {\n                loadContent()\n            }\n        }\n    }\n\n    /**\n     * 注册回调\n     */\n    fun register(cb: Callback) {\n        mCallback = cb\n    }\n\n    /**\n     * 取消注册回调\n     */\n    fun unregister(cb: Callback) {\n        if (mCallback === cb) {\n            mCallback = null\n        }\n        preDownloadTask?.cancel()\n        preDownloadTask = null\n        downloadScope.coroutineContext.cancelChildren()\n        coroutineContext.cancelChildren()\n    }\n\n    private suspend fun getManageChapter(chapter: BookChapter, content: String): MangaChapter {\n        val list = BookHelp.flowImages(chapter, content)\n            .distinctUntilChanged().mapIndexed { index, src ->\n                MangaPage(\n                    chapterIndex = chapter.index,\n                    chapterSize = chapterSize,\n                    mImageUrl = src,\n                    index = index,\n                    mChapterName = chapter.title\n                )\n            }.toList()\n\n        val imageCount = list.size\n\n        list.forEach {\n            it.imageCount = imageCount\n        }\n\n        if (AppConfig.hideMangaTitle && imageCount > 0) {\n            return MangaChapter(chapter, list, imageCount)\n        }\n\n        val pages = mutableListOf<BaseMangaPage>()\n\n        if (imageCount == 0 && chapter.isVolume) {\n            pages.add(ReaderLoading(chapter.index, -1, chapter.title, true))\n        } else {\n            pages.add(ReaderLoading(chapter.index, -1, \"阅读 ${chapter.title}\"))\n            pages.addAll(list)\n            pages.add(ReaderLoading(chapter.index, imageCount, \"已读完 ${chapter.title}\"))\n        }\n\n        return MangaChapter(chapter, pages, imageCount)\n    }\n\n    interface Callback {\n        fun upContent()\n        fun loadFail(msg: String, retry: Boolean = true)\n        fun sureNewProgress(progress: BookProgress)\n        fun showLoading()\n        fun startLoad()\n    }\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/model/SharedJsScope.kt",
    "content": "package io.legado.app.model\n\nimport androidx.collection.LruCache\nimport com.google.gson.reflect.TypeToken\nimport com.script.ScriptBindings\nimport com.script.rhino.RhinoScriptEngine\nimport io.legado.app.exception.NoStackTraceException\nimport io.legado.app.help.http.newCallStrResponse\nimport io.legado.app.help.http.okHttpClient\nimport io.legado.app.utils.ACache\nimport io.legado.app.utils.GSON\nimport io.legado.app.utils.MD5Utils\nimport io.legado.app.utils.isAbsUrl\nimport io.legado.app.utils.isJsonObject\nimport kotlinx.coroutines.runBlocking\nimport org.mozilla.javascript.Scriptable\nimport org.mozilla.javascript.ScriptableObject\nimport splitties.init.appCtx\nimport java.io.File\nimport java.lang.ref.WeakReference\nimport kotlin.coroutines.CoroutineContext\n\nobject SharedJsScope {\n\n    private val cacheFolder = File(appCtx.cacheDir, \"shareJs\")\n    private val aCache = ACache.get(cacheFolder)\n\n    private val scopeMap = LruCache<String, WeakReference<Scriptable>>(16)\n\n    fun getScope(jsLib: String?, coroutineContext: CoroutineContext?): Scriptable? {\n        if (jsLib.isNullOrBlank()) {\n            return null\n        }\n        val key = MD5Utils.md5Encode(jsLib)\n        var scope = scopeMap[key]?.get()\n        if (scope == null) {\n            scope = RhinoScriptEngine.run {\n                getRuntimeScope(ScriptBindings())\n            }\n            if (jsLib.isJsonObject()) {\n                val jsMap: Map<String, String> = GSON.fromJson(\n                    jsLib,\n                    TypeToken.getParameterized(\n                        Map::class.java,\n                        String::class.java,\n                        String::class.java\n                    ).type\n                )\n                jsMap.values.forEach { value ->\n                    if (value.isAbsUrl()) {\n                        val fileName = MD5Utils.md5Encode(value)\n                        var js = aCache.getAsString(fileName)\n                        if (js == null) {\n                            js = runBlocking {\n                                okHttpClient.newCallStrResponse {\n                                    url(value)\n                                }.body\n                            }\n                            if (js != null) {\n                                aCache.put(fileName, js)\n                            } else {\n                                throw NoStackTraceException(\"下载jsLib-${value}失败\")\n                            }\n                        }\n                        RhinoScriptEngine.eval(js, scope, coroutineContext)\n                    }\n                }\n            } else {\n                RhinoScriptEngine.eval(jsLib, scope, coroutineContext)\n            }\n            if (scope is ScriptableObject) {\n                scope.sealObject()\n            }\n            scopeMap.put(key, WeakReference(scope))\n        }\n        return scope\n    }\n\n    fun remove(jsLib: String?) {\n        if (jsLib.isNullOrBlank()) {\n            return\n        }\n        val key = MD5Utils.md5Encode(jsLib)\n        scopeMap.remove(key)\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/model/analyzeRule/AnalyzeByJSonPath.kt",
    "content": "package io.legado.app.model.analyzeRule\n\nimport androidx.annotation.Keep\nimport com.jayway.jsonpath.JsonPath\nimport com.jayway.jsonpath.ReadContext\nimport io.legado.app.utils.printOnDebug\n\n\n@Suppress(\"RegExpRedundantEscape\")\n@Keep\nclass AnalyzeByJSonPath(json: Any) {\n\n    companion object {\n\n        fun parse(json: Any): ReadContext {\n            return when (json) {\n                is ReadContext -> json\n                is String -> JsonPath.parse(json) //JsonPath.parse<String>(json)\n                else -> JsonPath.parse(json) //JsonPath.parse<Any>(json)\n            }\n        }\n    }\n\n    private var ctx: ReadContext = parse(json)\n\n    /**\n     * 改进解析方法\n     * 解决阅读”&&“、”||“与jsonPath支持的”&&“、”||“之间的冲突\n     * 解决{$.rule}形式规则可能匹配错误的问题，旧规则用正则解析内容含‘}’的json文本时，用规则中的字段去匹配这种内容会匹配错误.现改用平衡嵌套方法解决这个问题\n     * */\n    fun getString(rule: String): String? {\n        if (rule.isEmpty()) return null\n        var result: String\n        val ruleAnalyzes = RuleAnalyzer(rule, true) //设置平衡组为代码平衡\n        val rules = ruleAnalyzes.splitRule(\"&&\", \"||\")\n\n        if (rules.size == 1) {\n\n            ruleAnalyzes.reSetPos() //将pos重置为0，复用解析器\n\n            result = ruleAnalyzes.innerRule(\"{$.\") { getString(it) } //替换所有{$.rule...}\n\n            if (result.isEmpty()) { //st为空，表明无成功替换的内嵌规则\n                try {\n                    val ob = ctx.read<Any>(rule)\n                    result = if (ob is List<*>) {\n                        ob.joinToString(\"\\n\")\n                    } else {\n                        ob.toString()\n                    }\n                } catch (e: Exception) {\n                    e.printOnDebug()\n                }\n            }\n            return result\n        } else {\n            val textList = arrayListOf<String>()\n            for (rl in rules) {\n                val temp = getString(rl)\n                if (!temp.isNullOrEmpty()) {\n                    textList.add(temp)\n                    if (ruleAnalyzes.elementsType == \"||\") {\n                        break\n                    }\n                }\n            }\n            return textList.joinToString(\"\\n\")\n        }\n    }\n\n    internal fun getStringList(rule: String): List<String> {\n        val result = ArrayList<String>()\n        if (rule.isEmpty()) return result\n        val ruleAnalyzes = RuleAnalyzer(rule, true) //设置平衡组为代码平衡\n        val rules = ruleAnalyzes.splitRule(\"&&\", \"||\", \"%%\")\n\n        if (rules.size == 1) {\n            ruleAnalyzes.reSetPos() //将pos重置为0，复用解析器\n            val st = ruleAnalyzes.innerRule(\"{$.\") { getString(it) } //替换所有{$.rule...}\n            if (st.isEmpty()) { //st为空，表明无成功替换的内嵌规则\n                try {\n                    val obj = ctx.read<Any>(rule)\n                    if (obj is List<*>) {\n                        for (o in obj) result.add(o.toString())\n                    } else {\n                        result.add(obj.toString())\n                    }\n                } catch (e: Exception) {\n                    e.printOnDebug()\n                }\n            } else {\n                result.add(st)\n            }\n            return result\n        } else {\n            val results = ArrayList<List<String>>()\n            for (rl in rules) {\n                val temp = getStringList(rl)\n                if (temp.isNotEmpty()) {\n                    results.add(temp)\n                    if (temp.isNotEmpty() && ruleAnalyzes.elementsType == \"||\") {\n                        break\n                    }\n                }\n            }\n            if (results.size > 0) {\n                if (\"%%\" == ruleAnalyzes.elementsType) {\n                    for (i in results[0].indices) {\n                        for (temp in results) {\n                            if (i < temp.size) {\n                                result.add(temp[i])\n                            }\n                        }\n                    }\n                } else {\n                    for (temp in results) {\n                        result.addAll(temp)\n                    }\n                }\n            }\n            return result\n        }\n    }\n\n    internal fun getObject(rule: String): Any {\n        return ctx.read(rule)\n    }\n\n    internal fun getList(rule: String): ArrayList<Any>? {\n        val result = ArrayList<Any>()\n        if (rule.isEmpty()) return result\n        val ruleAnalyzes = RuleAnalyzer(rule, true) //设置平衡组为代码平衡\n        val rules = ruleAnalyzes.splitRule(\"&&\", \"||\", \"%%\")\n        if (rules.size == 1) {\n            ctx.let {\n                try {\n                    return it.read<ArrayList<Any>>(rules[0])\n                } catch (e: Exception) {\n                    e.printOnDebug()\n                }\n            }\n        } else {\n            val results = ArrayList<ArrayList<*>>()\n            for (rl in rules) {\n                val temp = getList(rl)\n                if (!temp.isNullOrEmpty()) {\n                    results.add(temp)\n                    if (temp.isNotEmpty() && ruleAnalyzes.elementsType == \"||\") {\n                        break\n                    }\n                }\n            }\n            if (results.size > 0) {\n                if (\"%%\" == ruleAnalyzes.elementsType) {\n                    for (i in 0 until results[0].size) {\n                        for (temp in results) {\n                            if (i < temp.size) {\n                                temp[i]?.let { result.add(it) }\n                            }\n                        }\n                    }\n                } else {\n                    for (temp in results) {\n                        result.addAll(temp)\n                    }\n                }\n            }\n        }\n        return result\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/model/analyzeRule/AnalyzeByJSoup.kt",
    "content": "package io.legado.app.model.analyzeRule\n\nimport androidx.annotation.Keep\nimport org.jsoup.Jsoup\nimport org.jsoup.nodes.Element\nimport org.jsoup.parser.Parser\nimport org.jsoup.select.Collector\nimport org.jsoup.select.Elements\nimport org.jsoup.select.Evaluator\nimport org.seimicrawler.xpath.JXNode\n\n/**\n * Created by GKF on 2018/1/25.\n * 书源规则解析\n */\n@Keep\nclass AnalyzeByJSoup(doc: Any) {\n\n    companion object {\n        private val nullSet = setOf(null)\n    }\n\n    private var element: Element = parse(doc)\n\n    private fun parse(doc: Any): Element {\n        if (doc is Element) {\n            return doc\n        }\n        if (doc is JXNode) {\n            return if (doc.isElement) doc.asElement() else Jsoup.parse(doc.toString())\n        }\n        kotlin.runCatching {\n            if (doc.toString().startsWith(\"<?xml\", true)) {\n                return Jsoup.parse(doc.toString(), Parser.xmlParser())\n            }\n        }\n        return Jsoup.parse(doc.toString())\n    }\n\n    /**\n     * 获取列表\n     */\n    internal fun getElements(rule: String) = getElements(element, rule)\n\n    /**\n     * 合并内容列表,得到内容\n     */\n    internal fun getString(ruleStr: String): String? {\n        if (ruleStr.isEmpty()) {\n            return null\n        }\n        val list = getStringList(ruleStr)\n        if (list.isEmpty()) {\n            return null\n        }\n        if (list.size == 1) {\n            return list.first()\n        }\n        return list.joinToString(\"\\n\")\n    }\n\n\n    /**\n     * 获取一个字符串\n     */\n    internal fun getString0(ruleStr: String) =\n        getStringList(ruleStr).let { if (it.isEmpty()) \"\" else it[0] }\n\n    /**\n     * 获取所有内容列表\n     */\n    internal fun getStringList(ruleStr: String): List<String> {\n\n        val textS = ArrayList<String>()\n\n        if (ruleStr.isEmpty()) return textS\n\n        //拆分规则\n        val sourceRule = SourceRule(ruleStr)\n\n        if (sourceRule.elementsRule.isEmpty()) {\n\n            textS.add(element.data() ?: \"\")\n\n        } else {\n\n            val ruleAnalyzes = RuleAnalyzer(sourceRule.elementsRule)\n            val ruleStrS = ruleAnalyzes.splitRule(\"&&\", \"||\", \"%%\")\n\n            val results = ArrayList<List<String>>()\n            for (ruleStrX in ruleStrS) {\n\n                val temp: ArrayList<String>? =\n                    if (sourceRule.isCss) {\n                        val lastIndex = ruleStrX.lastIndexOf('@')\n                        getResultLast(\n                            element.select(ruleStrX.substring(0, lastIndex)),\n                            ruleStrX.substring(lastIndex + 1)\n                        )\n                    } else {\n                        getResultList(ruleStrX)\n                    }\n\n                if (!temp.isNullOrEmpty()) {\n                    results.add(temp)\n                    if (ruleAnalyzes.elementsType == \"||\") break\n                }\n            }\n            if (results.size > 0) {\n                if (\"%%\" == ruleAnalyzes.elementsType) {\n                    for (i in results[0].indices) {\n                        for (temp in results) {\n                            if (i < temp.size) {\n                                textS.add(temp[i])\n                            }\n                        }\n                    }\n                } else {\n                    for (temp in results) {\n                        textS.addAll(temp)\n                    }\n                }\n            }\n        }\n        return textS\n    }\n\n    /**\n     * 获取Elements\n     */\n    private fun getElements(temp: Element?, rule: String): Elements {\n\n        if (temp == null || rule.isEmpty()) return Elements()\n\n        val elements = Elements()\n\n        val sourceRule = SourceRule(rule)\n        val ruleAnalyzes = RuleAnalyzer(sourceRule.elementsRule)\n        val ruleStrS = ruleAnalyzes.splitRule(\"&&\", \"||\", \"%%\")\n\n        val elementsList = ArrayList<Elements>()\n        if (sourceRule.isCss) {\n            for (ruleStr in ruleStrS) {\n                val tempS = temp.select(ruleStr)\n                elementsList.add(tempS)\n                if (tempS.size > 0 && ruleAnalyzes.elementsType == \"||\") {\n                    break\n                }\n            }\n        } else {\n            for (ruleStr in ruleStrS) {\n\n                val rsRule = RuleAnalyzer(ruleStr)\n\n                rsRule.trim()  // 修剪当前规则之前的\"@\"或者空白符\n\n                val rs = rsRule.splitRule(\"@\")\n\n                val el = if (rs.size > 1) {\n                    val el = Elements()\n                    el.add(temp)\n                    for (rl in rs) {\n                        val es = Elements()\n                        for (et in el) {\n                            es.addAll(getElements(et, rl))\n                        }\n                        el.clear()\n                        el.addAll(es)\n                    }\n                    el\n                } else ElementsSingle().getElementsSingle(temp, ruleStr)\n\n                elementsList.add(el)\n                if (el.size > 0 && ruleAnalyzes.elementsType == \"||\") {\n                    break\n                }\n            }\n        }\n        if (elementsList.size > 0) {\n            if (\"%%\" == ruleAnalyzes.elementsType) {\n                for (i in 0 until elementsList[0].size) {\n                    for (es in elementsList) {\n                        if (i < es.size) {\n                            elements.add(es[i])\n                        }\n                    }\n                }\n            } else {\n                for (es in elementsList) {\n                    elements.addAll(es)\n                }\n            }\n        }\n        return elements\n    }\n\n    /**\n     * 获取内容列表\n     */\n    private fun getResultList(ruleStr: String): ArrayList<String>? {\n\n        if (ruleStr.isEmpty()) return null\n\n        var elements = Elements()\n\n        elements.add(element)\n\n        val rule = RuleAnalyzer(ruleStr) //创建解析\n\n        rule.trim() //修建前置赘余符号\n\n        val rules = rule.splitRule(\"@\") // 切割成列表\n\n        val last = rules.size - 1\n        for (i in 0 until last) {\n            val es = Elements()\n            for (elt in elements) {\n                es.addAll(ElementsSingle().getElementsSingle(elt, rules[i]))\n            }\n            elements.clear()\n            elements = es\n        }\n        return if (elements.isEmpty()) null else getResultLast(elements, rules[last])\n    }\n\n    /**\n     * 根据最后一个规则获取内容\n     */\n    private fun getResultLast(elements: Elements, lastRule: String): ArrayList<String> {\n        val textS = ArrayList<String>()\n        when (lastRule) {\n            \"text\" -> for (element in elements) {\n                val text = element.text()\n                if (text.isNotEmpty()) {\n                    textS.add(text)\n                }\n            }\n\n            \"textNodes\" -> for (element in elements) {\n                val tn = arrayListOf<String>()\n                val contentEs = element.textNodes()\n                for (item in contentEs) {\n                    val text = item.text().trim { it <= ' ' }\n                    if (text.isNotEmpty()) {\n                        tn.add(text)\n                    }\n                }\n                if (tn.isNotEmpty()) {\n                    textS.add(tn.joinToString(\"\\n\"))\n                }\n            }\n\n            \"ownText\" -> for (element in elements) {\n                val text = element.ownText()\n                if (text.isNotEmpty()) {\n                    textS.add(text)\n                }\n            }\n\n            \"html\" -> {\n                elements.select(\"script\").remove()\n                elements.select(\"style\").remove()\n                val html = elements.outerHtml()\n                if (html.isNotEmpty()) {\n                    textS.add(html)\n                }\n            }\n\n            \"all\" -> textS.add(elements.outerHtml())\n            else -> for (element in elements) {\n\n                val url = element.attr(lastRule)\n\n                if (url.isBlank() || textS.contains(url)) continue\n\n                textS.add(url)\n            }\n        }\n        return textS\n    }\n\n    /**\n     * 1.支持阅读原有写法，':'分隔索引，!或.表示筛选方式，索引可为负数\n     * 例如 tag.div.-1:10:2 或 tag.div!0:3\n     *\n     * 2. 支持与jsonPath类似的[]索引写法\n     * 格式形如 [it,it，。。。] 或 [!it,it，。。。] 其中[!开头表示筛选方式为排除，it为单个索引或区间。\n     * 区间格式为 start:end 或 start:end:step，其中start为0可省略，end为-1可省略。\n     * 索引，区间两端及间隔都支持负数\n     * 例如 tag.div[-1, 3:-2:-10, 2]\n     * 特殊用法 tag.div[-1:0] 可在任意地方让列表反向\n     * */\n    @Suppress(\"UNCHECKED_CAST\")\n    data class ElementsSingle(\n        var split: Char = '.',\n        var beforeRule: String = \"\",\n        val indexDefault: MutableList<Int> = mutableListOf(),\n        val indexes: MutableList<Any> = mutableListOf()\n    ) {\n        /**\n         * 获取Elements按照一个规则\n         */\n        fun getElementsSingle(temp: Element, rule: String): Elements {\n\n            findIndexSet(rule) //执行索引列表处理器\n\n            /**\n             * 获取所有元素\n             * */\n            var elements =\n                if (beforeRule.isEmpty()) temp.children() //允许索引直接作为根元素，此时前置规则为空，效果与children相同\n                else {\n                    val rules = beforeRule.split(\".\")\n                    when (rules[0]) {\n                        \"children\" -> temp.children() //允许索引直接作为根元素，此时前置规则为空，效果与children相同\n                        \"class\" -> temp.getElementsByClass(rules[1])\n                        \"tag\" -> temp.getElementsByTag(rules[1])\n                        \"id\" -> Collector.collect(Evaluator.Id(rules[1]), temp)\n                        \"text\" -> temp.getElementsContainingOwnText(rules[1])\n                        else -> temp.select(beforeRule)\n                    }\n                }\n\n            val len = elements.size\n            val lastIndexes = (indexDefault.size - 1).takeIf { it != -1 } ?: (indexes.size - 1)\n            val indexSet = mutableSetOf<Int>()\n\n            /**\n             * 获取无重且不越界的索引集合\n             * */\n            if (indexes.isEmpty()) for (ix in lastIndexes downTo 0) { //indexes为空，表明是非[]式索引，集合是逆向遍历插入的，所以这里也逆向遍历，好还原顺序\n\n                val it = indexDefault[ix]\n                if (it in 0 until len) indexSet.add(it) //将正数不越界的索引添加到集合\n                else if (it < 0 && len >= -it) indexSet.add(it + len) //将负数不越界的索引添加到集合\n\n            } else for (ix in lastIndexes downTo 0) { //indexes不空，表明是[]式索引，集合是逆向遍历插入的，所以这里也逆向遍历，好还原顺序\n\n                if (indexes[ix] is Triple<*, *, *>) { //区间\n                    val (startX, endX, stepX) = indexes[ix] as Triple<Int?, Int?, Int> //还原储存时的类型\n\n                    var start = startX ?: 0 // 左端省略表示0\n                    if (start < 0) start += len // 将负索引转正\n\n                    var end = endX ?: (len - 1) // 右端省略表示 len - 1\n                    if (end < 0) end += len // 将负索引转正\n\n                    if ((start < 0 && end < 0) || (start >= len && end >= len)) {\n                        // start 和 end 同侧左右端越界，无效索引\n                        continue\n                    }\n\n                    if (start >= len) start = len - 1 // 右端越界，设置为最大索引\n                    else if (start < 0) start = 0 // 左端越界，设置为最小索引\n\n                    if (end >= len) end = len - 1 // 右端越界，设置为最大索引\n                    else if (end < 0) end = 0 // 左端越界，设置为最小索引\n\n                    if (start == end || stepX >= len) { //两端相同，区间里只有一个数。或间隔过大，区间实际上仅有首位\n\n                        indexSet.add(start)\n                        continue\n\n                    }\n\n                    val step =\n                        if (stepX > 0) stepX else if (-stepX < len) stepX + len else 1 //最小正数间隔为1\n\n                    //将区间展开到集合中,允许列表反向。\n                    indexSet.addAll(if (end > start) start..end step step else start downTo end step step)\n\n                } else {//单个索引\n\n                    val it = indexes[ix] as Int //还原储存时的类型\n\n                    if (it in 0 until len) indexSet.add(it) //将正数不越界的索引添加到集合\n                    else if (it < 0 && len >= -it) indexSet.add(it + len) //将负数不越界的索引添加到集合\n\n                }\n\n            }\n\n            /**\n             * 根据索引集合筛选元素\n             * */\n            if (split == '!') { //排除\n\n                for (pcInt in indexSet) elements[pcInt] = null\n\n                elements.removeAll(nullSet) //测试过，这样就行\n\n            } else if (split == '.') { //选择\n\n                val es = Elements()\n\n                for (pcInt in indexSet) es.add(elements[pcInt])\n\n                elements = es\n\n            }\n\n            return elements //返回筛选结果\n\n        }\n\n        private fun findIndexSet(rule: String) {\n\n            val rus = rule.trim { it <= ' ' }\n\n            var len = rus.length\n            var curInt: Int? //当前数字\n            var curMinus = false //当前数字是否为负\n            val curList = mutableListOf<Int?>() //当前数字区间\n            var l = \"\" //暂存数字字符串\n\n            val head = rus.last() == ']' //是否为常规索引写法\n\n            if (head) { //常规索引写法[index...]\n\n                len-- //跳过尾部']'\n\n                while (len-- >= 0) { //逆向遍历,可以无前置规则\n\n                    var rl = rus[len]\n                    if (rl == ' ') continue //跳过空格\n\n                    if (rl in '0'..'9') l = rl + l //将数值累接入临时字串中，遇到分界符才取出\n                    else if (rl == '-') curMinus = true\n                    else {\n\n                        curInt =\n                            if (l.isEmpty()) null else if (curMinus) -l.toInt() else l.toInt() //当前数字\n\n                        when (rl) {\n\n                            ':' -> curList.add(curInt) //区间右端或区间间隔\n\n                            else -> {\n\n                                //为保证查找顺序，区间和单个索引都添加到同一集合\n                                if (curList.isEmpty()) {\n\n                                    if (curInt == null) break //是jsoup选择器而非索引列表，跳出\n\n                                    indexes.add(curInt)\n                                } else {\n\n                                    //列表最后压入的是区间右端，若列表有两位则最先压入的是间隔\n                                    indexes.add(\n                                        Triple(\n                                            curInt,\n                                            curList.last(),\n                                            if (curList.size == 2) curList.first() else 1\n                                        )\n                                    )\n\n                                    curList.clear() //重置临时列表，避免影响到下个区间的处理\n\n                                }\n\n                                if (rl == '!') {\n                                    split = '!'\n                                    do {\n                                        rl = rus[--len]\n                                    } while (len > 0 && rl == ' ')//跳过所有空格\n                                }\n\n                                if (rl == '[') {\n                                    beforeRule = rus.substring(0, len) //遇到索引边界，返回结果\n                                    return\n                                }\n\n                                if (rl != ',') break //非索引结构，跳出\n\n                            }\n                        }\n\n                        l = \"\" //清空\n                        curMinus = false //重置\n                    }\n                }\n            } else while (len-- >= 0) { //阅读原本写法，逆向遍历,可以无前置规则\n\n                val rl = rus[len]\n                if (rl == ' ') continue //跳过空格\n\n                if (rl in '0'..'9') l = rl + l //将数值累接入临时字串中，遇到分界符才取出\n                else if (rl == '-') curMinus = true\n                else {\n\n                    if (rl == '!' || rl == '.' || rl == ':') { //分隔符或起始符\n\n                        indexDefault.add(if (curMinus) -l.toInt() else l.toInt()) // 当前数字追加到列表\n\n                        if (rl != ':') { //rl == '!'  || rl == '.'\n                            split = rl\n                            beforeRule = rus.substring(0, len)\n                            return\n                        }\n\n                    } else break //非索引结构，跳出循环\n\n                    l = \"\" //清空\n                    curMinus = false //重置\n                }\n            }\n\n            split = ' '\n            beforeRule = rus\n        }\n    }\n\n\n    internal inner class SourceRule(ruleStr: String) {\n        var isCss = false\n        var elementsRule: String = if (ruleStr.startsWith(\"@CSS:\", true)) {\n            isCss = true\n            ruleStr.substring(5).trim { it <= ' ' }\n        } else {\n            ruleStr\n        }\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/model/analyzeRule/AnalyzeByRegex.kt",
    "content": "package io.legado.app.model.analyzeRule\n\nimport androidx.annotation.Keep\nimport java.util.regex.Pattern\n\n@Keep\nobject AnalyzeByRegex {\n\n    fun getElement(res: String, regs: Array<String>, index: Int = 0): List<String>? {\n        var vIndex = index\n        val resM = Pattern.compile(regs[vIndex]).matcher(res)\n        if (!resM.find()) {\n            return null\n        }\n        // 判断索引的规则是最后一个规则\n        return if (vIndex + 1 == regs.size) {\n            // 新建容器\n            val info = arrayListOf<String>()\n            for (groupIndex in 0..resM.groupCount()) {\n                info.add(resM.group(groupIndex)!!)\n            }\n            info\n        } else {\n            val result = StringBuilder()\n            do {\n                result.append(resM.group())\n            } while (resM.find())\n            getElement(result.toString(), regs, ++vIndex)\n        }\n    }\n\n    fun getElements(res: String, regs: Array<String>, index: Int = 0): List<List<String>> {\n        var vIndex = index\n        val resM = Pattern.compile(regs[vIndex]).matcher(res)\n        if (!resM.find()) {\n            return arrayListOf()\n        }\n        // 判断索引的规则是最后一个规则\n        if (vIndex + 1 == regs.size) {\n            // 创建书息缓存数组\n            val books = ArrayList<List<String>>()\n            // 提取列表\n            do {\n                // 新建容器\n                val info = arrayListOf<String>()\n                for (groupIndex in 0..resM.groupCount()) {\n                    info.add(resM.group(groupIndex) ?: \"\")\n                }\n                books.add(info)\n            } while (resM.find())\n            return books\n        } else {\n            val result = StringBuilder()\n            do {\n                result.append(resM.group())\n            } while (resM.find())\n            return getElements(result.toString(), regs, ++vIndex)\n        }\n    }\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/model/analyzeRule/AnalyzeByXPath.kt",
    "content": "package io.legado.app.model.analyzeRule\n\nimport android.text.TextUtils\nimport androidx.annotation.Keep\nimport org.jsoup.Jsoup\nimport org.jsoup.nodes.Document\nimport org.jsoup.nodes.Element\nimport org.jsoup.parser.Parser\nimport org.jsoup.select.Elements\nimport org.seimicrawler.xpath.JXDocument\nimport org.seimicrawler.xpath.JXNode\n\n@Keep\nclass AnalyzeByXPath(doc: Any) {\n    private var jxNode: Any = parse(doc)\n\n    private fun parse(doc: Any): Any {\n        return when (doc) {\n            is JXNode -> if (doc.isElement) doc else strToJXDocument(doc.toString())\n            is Document -> JXDocument.create(doc)\n            is Element -> JXDocument.create(Elements(doc))\n            is Elements -> JXDocument.create(doc)\n            else -> strToJXDocument(doc.toString())\n        }\n    }\n\n    private fun strToJXDocument(html: String): JXDocument {\n        var html1 = html\n        if (html1.endsWith(\"</td>\")) {\n            html1 = \"<tr>${html1}</tr>\"\n        }\n        if (html1.endsWith(\"</tr>\") || html1.endsWith(\"</tbody>\")) {\n            html1 = \"<table>${html1}</table>\"\n        }\n        kotlin.runCatching {\n            if (html1.trim().startsWith(\"<?xml\", true)) {\n                return JXDocument.create(Jsoup.parse(html1, Parser.xmlParser()))\n            }\n        }\n        return JXDocument.create(html1)\n    }\n\n    private fun getResult(xPath: String): List<JXNode>? {\n        val node = jxNode\n        return if (node is JXNode) {\n            node.sel(xPath)\n        } else {\n            (node as JXDocument).selN(xPath)\n        }\n    }\n\n    internal fun getElements(xPath: String): List<JXNode>? {\n\n        if (xPath.isEmpty()) return null\n\n        val jxNodes = ArrayList<JXNode>()\n        val ruleAnalyzes = RuleAnalyzer(xPath)\n        val rules = ruleAnalyzes.splitRule(\"&&\", \"||\", \"%%\")\n\n        if (rules.size == 1) {\n            return getResult(rules[0])\n        } else {\n            val results = ArrayList<List<JXNode>>()\n            for (rl in rules) {\n                val temp = getElements(rl)\n                if (!temp.isNullOrEmpty()) {\n                    results.add(temp)\n                    if (temp.isNotEmpty() && ruleAnalyzes.elementsType == \"||\") {\n                        break\n                    }\n                }\n            }\n            if (results.size > 0) {\n                if (\"%%\" == ruleAnalyzes.elementsType) {\n                    for (i in results[0].indices) {\n                        for (temp in results) {\n                            if (i < temp.size) {\n                                jxNodes.add(temp[i])\n                            }\n                        }\n                    }\n                } else {\n                    for (temp in results) {\n                        jxNodes.addAll(temp)\n                    }\n                }\n            }\n        }\n        return jxNodes\n    }\n\n    internal fun getStringList(xPath: String): List<String> {\n\n        val result = ArrayList<String>()\n        val ruleAnalyzes = RuleAnalyzer(xPath)\n        val rules = ruleAnalyzes.splitRule(\"&&\", \"||\", \"%%\")\n\n        if (rules.size == 1) {\n            getResult(xPath)?.map {\n                result.add(it.asString())\n            }\n            return result\n        } else {\n            val results = ArrayList<List<String>>()\n            for (rl in rules) {\n                val temp = getStringList(rl)\n                if (temp.isNotEmpty()) {\n                    results.add(temp)\n                    if (temp.isNotEmpty() && ruleAnalyzes.elementsType == \"||\") {\n                        break\n                    }\n                }\n            }\n            if (results.size > 0) {\n                if (\"%%\" == ruleAnalyzes.elementsType) {\n                    for (i in results[0].indices) {\n                        for (temp in results) {\n                            if (i < temp.size) {\n                                result.add(temp[i])\n                            }\n                        }\n                    }\n                } else {\n                    for (temp in results) {\n                        result.addAll(temp)\n                    }\n                }\n            }\n        }\n        return result\n    }\n\n    fun getString(rule: String): String? {\n        val ruleAnalyzes = RuleAnalyzer(rule)\n        val rules = ruleAnalyzes.splitRule(\"&&\", \"||\")\n        if (rules.size == 1) {\n            getResult(rule)?.let {\n                return TextUtils.join(\"\\n\", it)\n            }\n            return null\n        } else {\n            val textList = arrayListOf<String>()\n            for (rl in rules) {\n                val temp = getString(rl)\n                if (!temp.isNullOrEmpty()) {\n                    textList.add(temp)\n                    if (ruleAnalyzes.elementsType == \"||\") {\n                        break\n                    }\n                }\n            }\n            return textList.joinToString(\"\\n\")\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/model/analyzeRule/AnalyzeRule.kt",
    "content": "package io.legado.app.model.analyzeRule\n\nimport android.text.TextUtils\nimport androidx.annotation.Keep\nimport com.script.CompiledScript\nimport com.script.buildScriptBindings\nimport com.script.rhino.RhinoScriptEngine\nimport io.legado.app.constant.AppPattern.JS_PATTERN\nimport io.legado.app.data.entities.BaseBook\nimport io.legado.app.data.entities.BaseSource\nimport io.legado.app.data.entities.Book\nimport io.legado.app.data.entities.BookChapter\nimport io.legado.app.data.entities.BookSource\nimport io.legado.app.data.entities.RssArticle\nimport io.legado.app.exception.NoStackTraceException\nimport io.legado.app.help.CacheManager\nimport io.legado.app.help.JsExtensions\nimport io.legado.app.help.http.CookieStore\nimport io.legado.app.help.source.getShareScope\nimport io.legado.app.model.Debug\nimport io.legado.app.model.webBook.WebBook\nimport io.legado.app.utils.GSON\nimport io.legado.app.utils.GSONStrict\nimport io.legado.app.utils.NetworkUtils\nimport io.legado.app.utils.fromJsonObject\nimport io.legado.app.utils.getOrPutLimit\nimport io.legado.app.utils.isDataUrl\nimport io.legado.app.utils.isJson\nimport io.legado.app.utils.printOnDebug\nimport io.legado.app.utils.splitNotBlank\nimport io.legado.app.utils.stackTraceStr\nimport kotlinx.coroutines.ensureActive\nimport kotlinx.coroutines.runBlocking\nimport kotlinx.coroutines.withTimeout\nimport org.apache.commons.text.StringEscapeUtils\nimport org.jsoup.nodes.Node\nimport org.mozilla.javascript.NativeObject\nimport org.mozilla.javascript.Scriptable\nimport java.lang.ref.WeakReference\nimport java.net.URL\nimport java.util.Locale\nimport java.util.regex.Pattern\nimport kotlin.coroutines.ContinuationInterceptor\nimport kotlin.coroutines.CoroutineContext\nimport kotlin.coroutines.EmptyCoroutineContext\n\n/**\n * 解析规则获取结果\n */\n@Keep\n@Suppress(\"unused\", \"RegExpRedundantEscape\", \"MemberVisibilityCanBePrivate\")\nclass AnalyzeRule(\n    private var ruleData: RuleDataInterface? = null,\n    private val source: BaseSource? = null,\n    private val preUpdateJs: Boolean = false\n) : JsExtensions {\n\n    private val book get() = ruleData as? BaseBook\n    private val rssArticle get() = ruleData as? RssArticle\n\n    private var chapter: BookChapter? = null\n    private var nextChapterUrl: String? = null\n    private var content: Any? = null\n    private var baseUrl: String? = null\n    private var redirectUrl: URL? = null\n    private var isJSON: Boolean = false\n    private var isRegex: Boolean = false\n\n    private var analyzeByXPath: AnalyzeByXPath? = null\n    private var analyzeByJSoup: AnalyzeByJSoup? = null\n    private var analyzeByJSonPath: AnalyzeByJSonPath? = null\n\n    private val stringRuleCache = hashMapOf<String, List<SourceRule>>()\n    private val regexCache = hashMapOf<String, Regex?>()\n    private val scriptCache = hashMapOf<String, CompiledScript>()\n    private var topScopeRef: WeakReference<Scriptable>? = null\n    private var evalJSCallCount = 0\n\n    private var coroutineContext: CoroutineContext = EmptyCoroutineContext\n\n    private var loggedNonStandardJSON = false\n\n    @JvmOverloads\n    fun setContent(content: Any?, baseUrl: String? = null): AnalyzeRule {\n        if (content == null) throw AssertionError(\"内容不可空（Content cannot be null）\")\n        this.content = content\n        isJSON = when (content) {\n            is Node -> false\n            else -> content.toString().isJson()\n        }\n        setBaseUrl(baseUrl)\n        analyzeByXPath = null\n        analyzeByJSoup = null\n        analyzeByJSonPath = null\n        return this\n    }\n\n    fun setBaseUrl(baseUrl: String?): AnalyzeRule {\n        baseUrl?.let {\n            this.baseUrl = baseUrl\n        }\n        return this\n    }\n\n    fun setRedirectUrl(url: String): URL? {\n        if (url.isDataUrl()) {\n            return redirectUrl\n        }\n        try {\n            redirectUrl = URL(url)\n        } catch (e: Exception) {\n            log(\"URL($url) error\\n${e.localizedMessage}\")\n        }\n        return redirectUrl\n    }\n\n    /**\n     * 获取XPath解析类\n     */\n    private fun getAnalyzeByXPath(o: Any): AnalyzeByXPath {\n        return if (o != content) {\n            AnalyzeByXPath(o)\n        } else {\n            if (analyzeByXPath == null) {\n                analyzeByXPath = AnalyzeByXPath(content!!)\n            }\n            analyzeByXPath!!\n        }\n    }\n\n    /**\n     * 获取JSOUP解析类\n     */\n    private fun getAnalyzeByJSoup(o: Any): AnalyzeByJSoup {\n        return if (o != content) {\n            AnalyzeByJSoup(o)\n        } else {\n            if (analyzeByJSoup == null) {\n                analyzeByJSoup = AnalyzeByJSoup(content!!)\n            }\n            analyzeByJSoup!!\n        }\n    }\n\n    /**\n     * 获取JSON解析类\n     */\n    private fun getAnalyzeByJSonPath(o: Any): AnalyzeByJSonPath {\n        return if (o != content) {\n            AnalyzeByJSonPath(o)\n        } else {\n            if (analyzeByJSonPath == null) {\n                analyzeByJSonPath = AnalyzeByJSonPath(content!!)\n            }\n            analyzeByJSonPath!!\n        }\n    }\n\n    /**\n     * 获取文本列表\n     */\n    @JvmOverloads\n    fun getStringList(rule: String?, mContent: Any? = null, isUrl: Boolean = false): List<String>? {\n        if (rule.isNullOrEmpty()) return null\n        val ruleList = splitSourceRuleCacheString(rule)\n        return getStringList(ruleList, mContent, isUrl)\n    }\n\n    @JvmOverloads\n    fun getStringList(\n        ruleList: List<SourceRule>,\n        mContent: Any? = null,\n        isUrl: Boolean = false\n    ): List<String>? {\n        var result: Any? = null\n        val content = mContent ?: this.content\n        if (content != null && ruleList.isNotEmpty()) {\n            result = content\n            if (result is NativeObject) {\n                val sourceRule = ruleList.first()\n                putRule(sourceRule.putMap)\n                sourceRule.makeUpRule(result)\n                result = if (sourceRule.getParamSize() > 1) {\n                    // get {{}}\n                    sourceRule.rule\n                } else {\n                    // 键值直接访问\n                    result[sourceRule.rule]\n                }\n                result?.let {\n                    if (sourceRule.replaceRegex.isNotEmpty() && it is List<*>) {\n                        result = it.map { o ->\n                            replaceRegex(o.toString(), sourceRule)\n                        }\n                    } else if (sourceRule.replaceRegex.isNotEmpty()) {\n                        result = replaceRegex(result.toString(), sourceRule)\n                    }\n                }\n            } else {\n                for (sourceRule in ruleList) {\n                    putRule(sourceRule.putMap)\n                    sourceRule.makeUpRule(result)\n                    result ?: continue\n                    val rule = sourceRule.rule\n                    if (rule.isNotEmpty()) {\n                        result = when (sourceRule.mode) {\n                            Mode.Js -> evalJS(rule, result)\n                            Mode.Json -> getAnalyzeByJSonPath(result).getStringList(rule)\n                            Mode.XPath -> getAnalyzeByXPath(result).getStringList(rule)\n                            Mode.Default -> getAnalyzeByJSoup(result).getStringList(rule)\n                            else -> rule\n                        }\n                    }\n                    if (sourceRule.replaceRegex.isNotEmpty() && result is List<*>) {\n                        val newList = ArrayList<String>()\n                        for (item in result) {\n                            newList.add(replaceRegex(item.toString(), sourceRule))\n                        }\n                        result = newList\n                    } else if (sourceRule.replaceRegex.isNotEmpty()) {\n                        result = replaceRegex(result.toString(), sourceRule)\n                    }\n                }\n            }\n        }\n        if (result == null) return null\n        if (result is String) {\n            result = result.split(\"\\n\")\n        }\n        if (isUrl) {\n            val urlList = ArrayList<String>()\n            if (result is List<*>) {\n                for (url in result) {\n                    val absoluteURL = NetworkUtils.getAbsoluteURL(redirectUrl, url.toString())\n                    if (absoluteURL.isNotEmpty() && !urlList.contains(absoluteURL)) {\n                        urlList.add(absoluteURL)\n                    }\n                }\n            }\n            return urlList\n        }\n        @Suppress(\"UNCHECKED_CAST\")\n        return result as? List<String>\n    }\n\n    /**\n     * 获取文本\n     */\n    @JvmOverloads\n    fun getString(ruleStr: String?, mContent: Any? = null, isUrl: Boolean = false): String {\n        if (TextUtils.isEmpty(ruleStr)) return \"\"\n        val ruleList = splitSourceRuleCacheString(ruleStr)\n        return getString(ruleList, mContent, isUrl)\n    }\n\n    fun getString(ruleStr: String?, unescape: Boolean): String {\n        if (TextUtils.isEmpty(ruleStr)) return \"\"\n        val ruleList = splitSourceRuleCacheString(ruleStr)\n        return getString(ruleList, unescape = unescape)\n    }\n\n    @JvmOverloads\n    fun getString(\n        ruleList: List<SourceRule>,\n        mContent: Any? = null,\n        isUrl: Boolean = false,\n        unescape: Boolean = true\n    ): String {\n        var result: Any? = null\n        val content = mContent ?: this.content\n        if (content != null && ruleList.isNotEmpty()) {\n            result = content\n            if (result is NativeObject) {\n                val sourceRule = ruleList.first()\n                putRule(sourceRule.putMap)\n                sourceRule.makeUpRule(result)\n                result = if (sourceRule.getParamSize() > 1) {\n                    // get {{}}\n                    sourceRule.rule\n                } else {\n                    // 键值直接访问\n                    result[sourceRule.rule]?.toString()\n                }?.let {\n                    replaceRegex(it, sourceRule)\n                }\n            } else {\n                for (sourceRule in ruleList) {\n                    putRule(sourceRule.putMap)\n                    sourceRule.makeUpRule(result)\n                    result ?: continue\n                    val rule = sourceRule.rule\n                    if (rule.isNotBlank() || sourceRule.replaceRegex.isEmpty()) {\n                        result = when (sourceRule.mode) {\n                            Mode.Js -> evalJS(rule, result)\n                            Mode.Json -> getAnalyzeByJSonPath(result).getString(rule)\n                            Mode.XPath -> getAnalyzeByXPath(result).getString(rule)\n                            Mode.Default -> if (isUrl) {\n                                getAnalyzeByJSoup(result).getString0(rule)\n                            } else {\n                                getAnalyzeByJSoup(result).getString(rule)\n                            }\n\n                            else -> rule\n                        }\n                    }\n                    if (result != null && sourceRule.replaceRegex.isNotEmpty()) {\n                        result = replaceRegex(result.toString(), sourceRule)\n                    }\n                }\n            }\n        }\n        if (result == null) result = \"\"\n        val resultStr = result.toString()\n        val str = if (unescape && resultStr.indexOf('&') > -1) {\n            StringEscapeUtils.unescapeHtml4(resultStr)\n        } else {\n            resultStr\n        }\n        if (isUrl) {\n            return if (str.isBlank()) {\n                baseUrl ?: \"\"\n            } else {\n                NetworkUtils.getAbsoluteURL(redirectUrl, str)\n            }\n        }\n        return str\n    }\n\n    /**\n     * 获取Element\n     */\n    fun getElement(ruleStr: String): Any? {\n        if (TextUtils.isEmpty(ruleStr)) return null\n        var result: Any? = null\n        val content = this.content\n        val ruleList = splitSourceRule(ruleStr, true)\n        if (content != null && ruleList.isNotEmpty()) {\n            result = content\n            for (sourceRule in ruleList) {\n                putRule(sourceRule.putMap)\n                sourceRule.makeUpRule(result)\n                result ?: continue\n                val rule = sourceRule.rule\n                result = when (sourceRule.mode) {\n                    Mode.Regex -> AnalyzeByRegex.getElement(\n                        result.toString(),\n                        rule.splitNotBlank(\"&&\")\n                    )\n\n                    Mode.Js -> evalJS(rule, result)\n                    Mode.Json -> getAnalyzeByJSonPath(result).getObject(rule)\n                    Mode.XPath -> getAnalyzeByXPath(result).getElements(rule)\n                    else -> getAnalyzeByJSoup(result).getElements(rule)\n                }\n                if (sourceRule.replaceRegex.isNotEmpty()) {\n                    result = replaceRegex(result.toString(), sourceRule)\n                }\n            }\n        }\n        return result\n    }\n\n    /**\n     * 获取列表\n     */\n    @Suppress(\"UNCHECKED_CAST\")\n    fun getElements(ruleStr: String): List<Any> {\n        var result: Any? = null\n        val content = this.content\n        val ruleList = splitSourceRule(ruleStr, true)\n        if (content != null && ruleList.isNotEmpty()) {\n            result = content\n            for (sourceRule in ruleList) {\n                putRule(sourceRule.putMap)\n                result ?: continue\n                val rule = sourceRule.rule\n                result = when (sourceRule.mode) {\n                    Mode.Regex -> AnalyzeByRegex.getElements(\n                        result.toString(),\n                        rule.splitNotBlank(\"&&\")\n                    )\n\n                    Mode.Js -> evalJS(rule, result)\n                    Mode.Json -> getAnalyzeByJSonPath(result).getList(rule)\n                    Mode.XPath -> getAnalyzeByXPath(result).getElements(rule)\n                    else -> getAnalyzeByJSoup(result).getElements(rule)\n                }\n            }\n        }\n        result?.let {\n            return it as List<Any>\n        }\n        return ArrayList()\n    }\n\n    /**\n     * 保存变量\n     */\n    private fun putRule(map: Map<String, String>) {\n        for ((key, value) in map) {\n            put(key, getString(value))\n        }\n    }\n\n    /**\n     * 分离put规则\n     */\n    private fun splitPutRule(ruleStr: String, putMap: HashMap<String, String>): String {\n        var vRuleStr = ruleStr\n        val putMatcher = putPattern.matcher(vRuleStr)\n        while (putMatcher.find()) {\n            vRuleStr = vRuleStr.replace(putMatcher.group(), \"\")\n            val putJsonStr = putMatcher.group(1)\n            val putJson = GSONStrict.fromJsonObject<Map<String, String>>(putJsonStr)\n                .getOrNull()\n            if (putJson != null) {\n                putMap.putAll(putJson)\n                continue\n            }\n            GSON.fromJsonObject<Map<String, String>>(putJsonStr)\n                .getOrNull()\n                ?.let {\n                    if (!loggedNonStandardJSON) {\n                        Debug.log(\"≡@put 规则 JSON 格式不规范，请改为规范格式\")\n                        loggedNonStandardJSON = true\n                    }\n                    putMap.putAll(it)\n                }\n        }\n        return vRuleStr\n    }\n\n    /**\n     * 正则替换\n     */\n    private fun replaceRegex(result: String, rule: SourceRule): String {\n        if (rule.replaceRegex.isEmpty()) return result\n        val replaceRegex = rule.replaceRegex\n        val replacement = rule.replacement\n        val regex = compileRegexCache(replaceRegex)\n        if (rule.replaceFirst) {\n            /* ##match##replace### 获取第一个匹配到的结果并进行替换 */\n            if (regex != null) kotlin.runCatching {\n                val pattern = regex.toPattern()\n                val matcher = pattern.matcher(result)\n                return if (matcher.find()) {\n                    matcher.group(0)!!.replaceFirst(regex, replacement)\n                } else {\n                    \"\"\n                }\n            }\n            return replacement\n        } else {\n            /* ##match##replace 替换*/\n            if (regex != null) kotlin.runCatching {\n                return result.replace(regex, replacement)\n            }\n            return result.replace(replaceRegex, replacement)\n        }\n    }\n\n    private fun compileRegexCache(regex: String): Regex? {\n        return regexCache.getOrPutLimit(regex, 16) {\n            try {\n                regex.toRegex()\n            } catch (e: Exception) {\n                null\n            }\n        }\n    }\n\n    /**\n     * getString 类规则缓存\n     */\n    private fun splitSourceRuleCacheString(ruleStr: String?): List<SourceRule> {\n        if (ruleStr.isNullOrEmpty()) return emptyList()\n        return stringRuleCache.getOrPut(ruleStr) {\n            splitSourceRule(ruleStr)\n        }\n    }\n\n    /**\n     * 分解规则生成规则列表\n     */\n    fun splitSourceRule(ruleStr: String?, allInOne: Boolean = false): List<SourceRule> {\n        if (ruleStr.isNullOrEmpty()) return emptyList()\n        val ruleList = ArrayList<SourceRule>()\n        var mMode: Mode = Mode.Default\n        var start = 0\n        //仅首字符为:时为AllInOne，其实:与伪类选择器冲突，建议改成?更合理\n        if (allInOne && ruleStr.startsWith(\":\")) {\n            mMode = Mode.Regex\n            isRegex = true\n            start = 1\n        } else if (isRegex) {\n            mMode = Mode.Regex\n        }\n        var tmp: String\n        val jsMatcher = JS_PATTERN.matcher(ruleStr)\n        while (jsMatcher.find()) {\n            if (jsMatcher.start() > start) {\n                tmp = ruleStr.substring(start, jsMatcher.start()).trim { it <= ' ' }\n                if (tmp.isNotEmpty()) {\n                    ruleList.add(SourceRule(tmp, mMode))\n                }\n            }\n            ruleList.add(SourceRule(jsMatcher.group(2) ?: jsMatcher.group(1), Mode.Js))\n            start = jsMatcher.end()\n        }\n\n        if (ruleStr.length > start) {\n            tmp = ruleStr.substring(start).trim { it <= ' ' }\n            if (tmp.isNotEmpty()) {\n                ruleList.add(SourceRule(tmp, mMode))\n            }\n        }\n\n        return ruleList\n    }\n\n    private fun getOrCreateSingleSourceRule(rule: String): List<SourceRule> {\n        return stringRuleCache.getOrPutLimit(rule, 16) {\n            listOf(SourceRule(rule))\n        }\n    }\n\n    /**\n     * 规则类\n     */\n    inner class SourceRule internal constructor(\n        ruleStr: String,\n        internal var mode: Mode = Mode.Default\n    ) {\n        internal var rule: String\n        internal var replaceRegex = \"\"\n        internal var replacement = \"\"\n        internal var replaceFirst = false\n        internal val putMap = HashMap<String, String>()\n        private val ruleParam = ArrayList<String>()\n        private val ruleType = ArrayList<Int>()\n        private val getRuleType = -2\n        private val jsRuleType = -1\n        private val defaultRuleType = 0\n\n        init {\n            rule = when {\n                mode == Mode.Js || mode == Mode.Regex -> ruleStr\n                ruleStr.startsWith(\"@CSS:\", true) -> {\n                    mode = Mode.Default\n                    ruleStr\n                }\n\n                ruleStr.startsWith(\"@@\") -> {\n                    mode = Mode.Default\n                    ruleStr.substring(2)\n                }\n\n                ruleStr.startsWith(\"@XPath:\", true) -> {\n                    mode = Mode.XPath\n                    ruleStr.substring(7)\n                }\n\n                ruleStr.startsWith(\"@Json:\", true) -> {\n                    mode = Mode.Json\n                    ruleStr.substring(6)\n                }\n\n                isJSON || ruleStr.startsWith(\"$.\") || ruleStr.startsWith(\"$[\") -> {\n                    mode = Mode.Json\n                    ruleStr\n                }\n\n                ruleStr.startsWith(\"/\") -> {//XPath特征很明显,无需配置单独的识别标头\n                    mode = Mode.XPath\n                    ruleStr\n                }\n\n                else -> ruleStr\n            }\n            //分离put\n            rule = splitPutRule(rule, putMap)\n            //@get,{{ }}, 拆分\n            var start = 0\n            var tmp: String\n            val evalMatcher = evalPattern.matcher(rule)\n\n            if (evalMatcher.find()) {\n                tmp = rule.substring(start, evalMatcher.start())\n                if (mode != Mode.Js && mode != Mode.Regex &&\n                    (evalMatcher.start() == 0 || !tmp.contains(\"##\"))\n                ) {\n                    mode = Mode.Regex\n                }\n                do {\n                    if (evalMatcher.start() > start) {\n                        tmp = rule.substring(start, evalMatcher.start())\n                        splitRegex(tmp)\n                    }\n                    tmp = evalMatcher.group()\n                    when {\n                        tmp.startsWith(\"@get:\", true) -> {\n                            ruleType.add(getRuleType)\n                            ruleParam.add(tmp.substring(6, tmp.lastIndex))\n                        }\n\n                        tmp.startsWith(\"{{\") -> {\n                            ruleType.add(jsRuleType)\n                            ruleParam.add(tmp.substring(2, tmp.length - 2))\n                        }\n\n                        else -> {\n                            splitRegex(tmp)\n                        }\n                    }\n                    start = evalMatcher.end()\n                } while (evalMatcher.find())\n            }\n            if (rule.length > start) {\n                tmp = rule.substring(start)\n                splitRegex(tmp)\n            }\n        }\n\n        /**\n         * 拆分\\$\\d{1,2}\n         */\n        private fun splitRegex(ruleStr: String) {\n            var start = 0\n            var tmp: String\n            val ruleStrArray = ruleStr.split(\"##\")\n            val regexMatcher = regexPattern.matcher(ruleStrArray[0])\n\n            if (regexMatcher.find()) {\n                if (mode != Mode.Js && mode != Mode.Regex) {\n                    mode = Mode.Regex\n                }\n                do {\n                    if (regexMatcher.start() > start) {\n                        tmp = ruleStr.substring(start, regexMatcher.start())\n                        ruleType.add(defaultRuleType)\n                        ruleParam.add(tmp)\n                    }\n                    tmp = regexMatcher.group()\n                    ruleType.add(tmp.substring(1).toInt())\n                    ruleParam.add(tmp)\n                    start = regexMatcher.end()\n                } while (regexMatcher.find())\n            }\n            if (ruleStr.length > start) {\n                tmp = ruleStr.substring(start)\n                ruleType.add(defaultRuleType)\n                ruleParam.add(tmp)\n            }\n        }\n\n        /**\n         * 替换@get,{{ }}\n         */\n        fun makeUpRule(result: Any?) {\n            val infoVal = StringBuilder()\n            if (ruleParam.isNotEmpty()) {\n                var index = ruleParam.size\n                while (index-- > 0) {\n                    val regType = ruleType[index]\n                    when {\n                        regType > defaultRuleType -> {\n                            @Suppress(\"UNCHECKED_CAST\")\n                            (result as? List<String?>)?.run {\n                                if (this.size > regType) {\n                                    this[regType]?.let {\n                                        infoVal.insert(0, it)\n                                    }\n                                }\n                            } ?: infoVal.insert(0, ruleParam[index])\n                        }\n\n                        regType == jsRuleType -> {\n                            if (isRule(ruleParam[index])) {\n                                val ruleList = getOrCreateSingleSourceRule(ruleParam[index])\n                                getString(ruleList).let {\n                                    infoVal.insert(0, it)\n                                }\n                            } else {\n                                val jsEval: Any? = evalJS(ruleParam[index], result)\n                                when {\n                                    jsEval == null -> Unit\n                                    jsEval is String -> infoVal.insert(0, jsEval)\n                                    jsEval is Double && jsEval % 1.0 == 0.0 -> infoVal.insert(\n                                        0,\n                                        String.format(Locale.ROOT, \"%.0f\", jsEval)\n                                    )\n\n                                    else -> infoVal.insert(0, jsEval.toString())\n                                }\n                            }\n                        }\n\n                        regType == getRuleType -> {\n                            infoVal.insert(0, get(ruleParam[index]))\n                        }\n\n                        else -> infoVal.insert(0, ruleParam[index])\n                    }\n                }\n                rule = infoVal.toString()\n            }\n            //分离正则表达式\n            val ruleStrS = rule.split(\"##\")\n            rule = ruleStrS[0].trim()\n            if (ruleStrS.size > 1) {\n                replaceRegex = ruleStrS[1]\n            }\n            if (ruleStrS.size > 2) {\n                replacement = ruleStrS[2]\n            }\n            if (ruleStrS.size > 3) {\n                replaceFirst = true\n            }\n        }\n\n        private fun isRule(ruleStr: String): Boolean {\n            return ruleStr.startsWith('@') //js首个字符不可能是@，除非是装饰器，所以@开头规定为规则\n                    || ruleStr.startsWith(\"$.\")\n                    || ruleStr.startsWith(\"$[\")\n                    || ruleStr.startsWith(\"//\")\n        }\n\n        fun getParamSize(): Int {\n            return ruleParam.size\n        }\n    }\n\n    enum class Mode {\n        XPath, Json, Default, Js, Regex\n    }\n\n    /**\n     * 保存数据\n     */\n    fun put(key: String, value: String): String {\n        if (key == \"bookName\" || key == \"title\") {\n            Debug.log(\"≡变量 $key 在特定情况下会被覆盖，建议使用其他键名\")\n        }\n        chapter?.putVariable(key, value)\n            ?: book?.putVariable(key, value)\n            ?: ruleData?.putVariable(key, value)\n            ?: source?.put(key, value)\n        return value\n    }\n\n    /**\n     * 获取保存的数据\n     */\n    fun get(key: String): String {\n        when (key) {\n            \"bookName\" -> book?.let {\n                return it.name\n            }\n\n            \"title\" -> chapter?.let {\n                return it.title\n            }\n        }\n        return chapter?.getVariable(key)?.takeIf { it.isNotEmpty() }\n            ?: book?.getVariable(key)?.takeIf { it.isNotEmpty() }\n            ?: ruleData?.getVariable(key)?.takeIf { it.isNotEmpty() }\n            ?: source?.get(key)?.takeIf { it.isNotEmpty() }\n            ?: \"\"\n    }\n\n    /**\n     * 执行JS\n     */\n    fun evalJS(jsStr: String, result: Any? = null): Any? {\n        val bindings = buildScriptBindings { bindings ->\n            bindings[\"java\"] = this\n            bindings[\"cookie\"] = CookieStore\n            bindings[\"cache\"] = CacheManager\n            bindings[\"source\"] = source\n            bindings[\"book\"] = book\n            bindings[\"result\"] = result\n            bindings[\"baseUrl\"] = baseUrl\n            bindings[\"chapter\"] = chapter\n            bindings[\"title\"] = chapter?.title\n            bindings[\"src\"] = content\n            bindings[\"nextChapterUrl\"] = nextChapterUrl\n            bindings[\"rssArticle\"] = rssArticle\n        }\n        val topScope = source?.getShareScope(coroutineContext) ?: topScopeRef?.get()\n        val scope = if (topScope == null) {\n            RhinoScriptEngine.getRuntimeScope(bindings).apply {\n                if (evalJSCallCount++ > 16) {\n                    topScopeRef = WeakReference(prototype)\n                }\n            }\n        } else {\n            bindings.apply {\n                prototype = topScope\n            }\n        }\n        val script = compileScriptCache(jsStr)\n        val result = script.eval(scope, coroutineContext)\n        return result\n    }\n\n    private fun compileScriptCache(jsStr: String): CompiledScript {\n        return scriptCache.getOrPutLimit(jsStr, 16) {\n            RhinoScriptEngine.compile(jsStr)\n        }\n    }\n\n    override fun getSource(): BaseSource? {\n        return source\n    }\n\n    /**\n     * js实现跨域访问,不能删\n     */\n    override fun ajax(url: Any): String? {\n        val urlStr = if (url is List<*>) {\n            url.firstOrNull().toString()\n        } else {\n            url.toString()\n        }\n        val analyzeUrl = AnalyzeUrl(\n            urlStr,\n            source = source,\n            ruleData = book,\n            coroutineContext = coroutineContext\n        )\n        return kotlin.runCatching {\n            analyzeUrl.getStrResponse().body\n        }.onFailure {\n            coroutineContext.ensureActive()\n            log(\"ajax(${urlStr}) error\\n${it.stackTraceToString()}\")\n            it.printOnDebug()\n        }.getOrElse {\n            it.stackTraceStr\n        }\n    }\n\n    /**\n     * 重新获取book\n     */\n    fun reGetBook() {\n        if (!preUpdateJs) throw NoStackTraceException(\"只能在 preUpdateJs 中调用\")\n        val bookSource = source as? BookSource\n        val book = book as? Book\n        if (bookSource == null || book == null) return\n        runBlocking(coroutineContext) {\n            withTimeout(1800000) {\n                WebBook.preciseSearchAwait(bookSource, book.name, book.author)\n                    .getOrThrow().let {\n                        book.bookUrl = it.bookUrl\n                        it.variableMap.forEach { entry ->\n                            book.putVariable(entry.key, entry.value)\n                        }\n                    }\n                WebBook.getBookInfoAwait(bookSource, book, false)\n            }\n        }\n    }\n\n    /**\n     * 更新tocUrl,有些书源目录url定期更新,可以在js调用更新\n     */\n    fun refreshTocUrl() {\n        if (!preUpdateJs) throw NoStackTraceException(\"只能在 preUpdateJs 中调用\")\n        val bookSource = source as? BookSource\n        val book = book as? Book\n        if (bookSource == null || book == null) return\n        runBlocking(coroutineContext) {\n            withTimeout(1800000) {\n                WebBook.getBookInfoAwait(bookSource, book, false)\n            }\n        }\n    }\n\n    companion object {\n        private val putPattern = Pattern.compile(\"@put:(\\\\{[^}]+?\\\\})\", Pattern.CASE_INSENSITIVE)\n        private val evalPattern =\n            Pattern.compile(\"@get:\\\\{[^}]+?\\\\}|\\\\{\\\\{[\\\\w\\\\W]*?\\\\}\\\\}\", Pattern.CASE_INSENSITIVE)\n        private val regexPattern = Pattern.compile(\"\\\\$\\\\d{1,2}\")\n\n        fun AnalyzeRule.setCoroutineContext(context: CoroutineContext): AnalyzeRule {\n            coroutineContext = context.minusKey(ContinuationInterceptor)\n            return this\n        }\n\n        fun AnalyzeRule.setRuleData(ruleData: RuleDataInterface?): AnalyzeRule {\n            this.ruleData = ruleData\n            return this\n        }\n\n        fun AnalyzeRule.setNextChapterUrl(nextChapterUrl: String?): AnalyzeRule {\n            this.nextChapterUrl = nextChapterUrl\n            return this\n        }\n\n        fun AnalyzeRule.setChapter(chapter: BookChapter?): AnalyzeRule {\n            this.chapter = chapter\n            return this\n        }\n\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/model/analyzeRule/AnalyzeUrl.kt",
    "content": "package io.legado.app.model.analyzeRule\n\nimport android.annotation.SuppressLint\nimport android.util.Base64\nimport androidx.annotation.Keep\nimport androidx.media3.common.MediaItem\nimport cn.hutool.core.codec.PercentCodec\nimport cn.hutool.core.net.RFC3986\nimport cn.hutool.core.util.HexUtil\nimport com.bumptech.glide.load.model.GlideUrl\nimport com.script.buildScriptBindings\nimport com.script.rhino.RhinoScriptEngine\nimport com.script.rhino.runScriptWithContext\nimport io.legado.app.constant.AppConst.UA_NAME\nimport io.legado.app.constant.AppPattern\nimport io.legado.app.constant.AppPattern.JS_PATTERN\nimport io.legado.app.constant.AppPattern.dataUriRegex\nimport io.legado.app.data.entities.BaseSource\nimport io.legado.app.data.entities.Book\nimport io.legado.app.data.entities.BookChapter\nimport io.legado.app.help.CacheManager\nimport io.legado.app.help.ConcurrentRateLimiter\nimport io.legado.app.help.JsExtensions\nimport io.legado.app.help.config.AppConfig\nimport io.legado.app.help.exoplayer.ExoPlayerHelper\nimport io.legado.app.help.glide.GlideHeaders\nimport io.legado.app.help.http.BackstageWebView\nimport io.legado.app.help.http.CookieManager\nimport io.legado.app.help.http.CookieManager.mergeCookies\nimport io.legado.app.help.http.CookieStore\nimport io.legado.app.help.http.RequestMethod\nimport io.legado.app.help.http.StrResponse\nimport io.legado.app.help.http.addHeaders\nimport io.legado.app.help.http.get\nimport io.legado.app.help.http.getProxyClient\nimport io.legado.app.help.http.newCallResponse\nimport io.legado.app.help.http.newCallStrResponse\nimport io.legado.app.help.http.postForm\nimport io.legado.app.help.http.postJson\nimport io.legado.app.help.http.postMultipart\nimport io.legado.app.help.source.getShareScope\nimport io.legado.app.model.Debug\nimport io.legado.app.utils.EncoderUtils\nimport io.legado.app.utils.GSON\nimport io.legado.app.utils.GSONStrict\nimport io.legado.app.utils.NetworkUtils\nimport io.legado.app.utils.fromJsonArray\nimport io.legado.app.utils.fromJsonObject\nimport io.legado.app.utils.get\nimport io.legado.app.utils.isJson\nimport io.legado.app.utils.isJsonArray\nimport io.legado.app.utils.isJsonObject\nimport io.legado.app.utils.isXml\nimport kotlinx.coroutines.runBlocking\nimport okhttp3.MediaType.Companion.toMediaType\nimport okhttp3.OkHttpClient\nimport okhttp3.RequestBody.Companion.toRequestBody\nimport okhttp3.Response\nimport java.io.ByteArrayInputStream\nimport java.io.InputStream\nimport java.net.URLEncoder\nimport java.nio.charset.Charset\nimport java.util.concurrent.TimeUnit\nimport java.util.regex.Pattern\nimport kotlin.coroutines.ContinuationInterceptor\nimport kotlin.coroutines.CoroutineContext\nimport kotlin.coroutines.EmptyCoroutineContext\nimport kotlin.math.max\n\n/**\n * Created by GKF on 2018/1/24.\n * 搜索URL规则解析\n */\n@Suppress(\"unused\", \"MemberVisibilityCanBePrivate\")\n@Keep\n@SuppressLint(\"DefaultLocale\")\nclass AnalyzeUrl(\n    private val mUrl: String,\n    private val key: String? = null,\n    private val page: Int? = null,\n    private val speakText: String? = null,\n    private val speakSpeed: Int? = null,\n    private var baseUrl: String = \"\",\n    private val source: BaseSource? = null,\n    private val ruleData: RuleDataInterface? = null,\n    private val chapter: BookChapter? = null,\n    private val readTimeout: Long? = null,\n    private val callTimeout: Long? = null,\n    private var coroutineContext: CoroutineContext = EmptyCoroutineContext,\n    headerMapF: Map<String, String>? = null,\n    hasLoginHeader: Boolean = true\n) : JsExtensions {\n\n    var ruleUrl = \"\"\n        private set\n    var url: String = \"\"\n        private set\n    var type: String? = null\n        private set\n    val headerMap = LinkedHashMap<String, String>()\n    private var body: String? = null\n    private var urlNoQuery: String = \"\"\n    private var encodedForm: String? = null\n    private var encodedQuery: String? = null\n    private var charset: String? = null\n    private var method = RequestMethod.GET\n    private var proxy: String? = null\n    private var retry: Int = 0\n    private var useWebView: Boolean = false\n    private var webJs: String? = null\n    private val enabledCookieJar = source?.enabledCookieJar == true\n    private val domain: String\n    private var webViewDelayTime: Long = 0\n    private val concurrentRateLimiter = ConcurrentRateLimiter(source)\n\n    // 服务器ID\n    var serverID: Long? = null\n        private set\n\n    init {\n        coroutineContext = coroutineContext.minusKey(ContinuationInterceptor)\n        val urlMatcher = paramPattern.matcher(baseUrl)\n        if (urlMatcher.find()) baseUrl = baseUrl.substring(0, urlMatcher.start())\n        (headerMapF ?: runScriptWithContext(coroutineContext) {\n            source?.getHeaderMap(hasLoginHeader)\n        })?.let {\n            headerMap.putAll(it)\n            if (it.containsKey(\"proxy\")) {\n                proxy = it[\"proxy\"]\n                headerMap.remove(\"proxy\")\n            }\n        }\n        initUrl()\n        domain = NetworkUtils.getSubDomain(source?.getKey() ?: url)\n    }\n\n    /**\n     * 处理url\n     */\n    fun initUrl() {\n        ruleUrl = mUrl\n        //执行@js,<js></js>\n        analyzeJs()\n        //替换参数\n        replaceKeyPageJs()\n        //处理URL\n        analyzeUrl()\n    }\n\n    /**\n     * 执行@js,<js></js>\n     */\n    private fun analyzeJs() {\n        var start = 0\n        val jsMatcher = JS_PATTERN.matcher(ruleUrl)\n        var result = ruleUrl\n        while (jsMatcher.find()) {\n            if (jsMatcher.start() > start) {\n                ruleUrl.substring(start, jsMatcher.start()).trim().let {\n                    if (it.isNotEmpty()) {\n                        result = it.replace(\"@result\", result)\n                    }\n                }\n            }\n            result = evalJS(jsMatcher.group(2) ?: jsMatcher.group(1), result).toString()\n            start = jsMatcher.end()\n        }\n        if (ruleUrl.length > start) {\n            ruleUrl.substring(start).trim().let {\n                if (it.isNotEmpty()) {\n                    result = it.replace(\"@result\", result)\n                }\n            }\n        }\n        ruleUrl = result\n    }\n\n    /**\n     * 替换关键字,页数,JS\n     */\n    private fun replaceKeyPageJs() { //先替换内嵌规则再替换页数规则，避免内嵌规则中存在大于小于号时，规则被切错\n        //js\n        if (ruleUrl.contains(\"{{\") && ruleUrl.contains(\"}}\")) {\n            val analyze = RuleAnalyzer(ruleUrl) //创建解析\n            //替换所有内嵌{{js}}\n            val url = analyze.innerRule(\"{{\", \"}}\") {\n                val jsEval = evalJS(it) ?: \"\"\n                when {\n                    jsEval is String -> jsEval\n                    jsEval is Double && jsEval % 1.0 == 0.0 -> String.format(\"%.0f\", jsEval)\n                    else -> jsEval.toString()\n                }\n            }\n            if (url.isNotEmpty()) ruleUrl = url\n        }\n        //page\n        page?.let {\n            val matcher = pagePattern.matcher(ruleUrl)\n            while (matcher.find()) {\n                val pages = matcher.group(1)!!.split(\",\")\n                ruleUrl = if (page < pages.size) { //pages[pages.size - 1]等同于pages.last()\n                    ruleUrl.replace(matcher.group(), pages[page - 1].trim { it <= ' ' })\n                } else {\n                    ruleUrl.replace(matcher.group(), pages.last().trim { it <= ' ' })\n                }\n            }\n        }\n    }\n\n    /**\n     * 解析Url\n     */\n    private fun analyzeUrl() {\n        //replaceKeyPageJs已经替换掉额外内容，此处url是基础形式，可以直接切首个‘,’之前字符串。\n        val urlMatcher = paramPattern.matcher(ruleUrl)\n        val urlNoOption =\n            if (urlMatcher.find()) ruleUrl.substring(0, urlMatcher.start()) else ruleUrl\n        url = NetworkUtils.getAbsoluteURL(baseUrl, urlNoOption)\n        NetworkUtils.getBaseUrl(url)?.let {\n            baseUrl = it\n        }\n        if (urlNoOption.length != ruleUrl.length) {\n            val urlOptionStr = ruleUrl.substring(urlMatcher.end())\n            var urlOption = GSONStrict.fromJsonObject<UrlOption>(urlOptionStr).getOrNull()\n            if (urlOption == null) {\n                urlOption = GSON.fromJsonObject<UrlOption>(urlOptionStr).getOrNull()\n                if (urlOption != null) {\n                    log(\"链接参数 JSON 格式不规范，请改为规范格式\")\n                }\n            }\n            urlOption?.let { option ->\n                option.getMethod()?.let {\n                    if (it.equals(\"POST\", true)) method = RequestMethod.POST\n                }\n                option.getHeaderMap()?.forEach { entry ->\n                    headerMap[entry.key.toString()] = entry.value.toString()\n                }\n                option.getBody()?.let {\n                    body = it\n                }\n                type = option.getType()\n                charset = option.getCharset()\n                retry = option.getRetry()\n                useWebView = option.useWebView()\n                webJs = option.getWebJs()\n                option.getJs()?.let { jsStr ->\n                    evalJS(jsStr, url)?.toString()?.let {\n                        url = it\n                    }\n                }\n                serverID = option.getServerID()\n                webViewDelayTime = max(0, option.getWebViewDelayTime() ?: 0)\n            }\n        }\n        urlNoQuery = url\n        when (method) {\n            RequestMethod.GET -> {\n                val pos = url.indexOf('?')\n                if (pos != -1) {\n                    analyzeQuery(url.substring(pos + 1))\n                    urlNoQuery = url.substring(0, pos)\n                }\n            }\n\n            RequestMethod.POST -> body?.let {\n                if (!it.isJson() && !it.isXml() && headerMap[\"Content-Type\"].isNullOrEmpty()) {\n                    analyzeFields(it)\n                }\n            }\n        }\n    }\n\n    /**\n     * 解析QueryMap <key>=<value>\n     * name=\n     * name=name\n     * name=<BASE64> eg name=bmFtZQ==\n     */\n    private fun analyzeFields(fieldsTxt: String) {\n        encodedForm = encodeParams(fieldsTxt, charset, false)\n    }\n\n    private fun analyzeQuery(query: String) {\n        encodedQuery = encodeParams(query, charset, true)\n    }\n\n    private fun encodeParams(params: String, charset: String?, isQuery: Boolean): String {\n        val checkEncoded = charset.isNullOrEmpty()\n        val charset = when {\n            charset.isNullOrEmpty() -> Charsets.UTF_8\n            charset == \"escape\" -> null\n            else -> charset(charset)\n        }\n        if (isQuery && charset != null) {\n            if (NetworkUtils.encodedQuery(params)) {\n                return params\n            }\n            return queryEncoder.encode(params, charset)\n        }\n        val len = params.length\n        val sb = StringBuilder()\n        var pos = 0\n        while (pos <= len) {\n            if (sb.isNotEmpty()) {\n                sb.append(\"&\")\n            }\n            var ampOffset = params.indexOf(\"&\", pos)\n            if (ampOffset == -1) {\n                ampOffset = len\n            }\n            val eqOffset = params.indexOf(\"=\", pos)\n            val key: String\n            val value: String?\n            if (eqOffset == -1 || eqOffset > ampOffset) {\n                key = params.substring(pos, ampOffset)\n                value = null\n            } else {\n                key = params.substring(pos, eqOffset)\n                value = params.substring(eqOffset + 1, ampOffset)\n            }\n            sb.appendEncoded(key, checkEncoded, charset)\n            if (value != null) {\n                sb.append(\"=\")\n                sb.appendEncoded(value, checkEncoded, charset)\n            }\n            pos = ampOffset + 1\n        }\n        return sb.toString()\n    }\n\n    private fun StringBuilder.appendEncoded(\n        value: String,\n        checkEncoded: Boolean,\n        charset: Charset?\n    ) {\n        if (checkEncoded && NetworkUtils.encodedForm(value)) {\n            append(value)\n        } else if (charset == null) {\n            append(EncoderUtils.escape(value))\n        } else {\n            append(URLEncoder.encode(value, charset))\n        }\n    }\n\n    /**\n     * 执行JS\n     */\n    fun evalJS(jsStr: String, result: Any? = null): Any? {\n        val bindings = buildScriptBindings { bindings ->\n            bindings[\"java\"] = this\n            bindings[\"baseUrl\"] = baseUrl\n            bindings[\"cookie\"] = CookieStore\n            bindings[\"cache\"] = CacheManager\n            bindings[\"page\"] = page\n            bindings[\"key\"] = key\n            bindings[\"speakText\"] = speakText\n            bindings[\"speakSpeed\"] = speakSpeed\n            bindings[\"book\"] = ruleData as? Book\n            bindings[\"source\"] = source\n            bindings[\"result\"] = result\n        }\n        val sharedScope = source?.getShareScope(coroutineContext)\n        val scope = if (sharedScope == null) {\n            RhinoScriptEngine.getRuntimeScope(bindings)\n        } else {\n            bindings.apply {\n                prototype = sharedScope\n            }\n        }\n        return RhinoScriptEngine.eval(jsStr, scope, coroutineContext)\n    }\n\n    fun put(key: String, value: String): String {\n        if (key == \"bookName\" || key == \"title\") {\n            Debug.log(\"≡变量 $key 在特定情况下会被覆盖，建议使用其他键名\")\n        }\n        chapter?.putVariable(key, value)\n            ?: ruleData?.putVariable(key, value)\n        return value\n    }\n\n    fun get(key: String): String {\n        when (key) {\n            \"bookName\" -> (ruleData as? Book)?.let {\n                return it.name\n            }\n\n            \"title\" -> chapter?.let {\n                return it.title\n            }\n        }\n        return chapter?.getVariable(key)?.takeIf { it.isNotEmpty() }\n            ?: ruleData?.getVariable(key)?.takeIf { it.isNotEmpty() }\n            ?: \"\"\n    }\n\n    /**\n     * 访问网站,返回StrResponse\n     */\n    suspend fun getStrResponseAwait(\n        jsStr: String? = null,\n        sourceRegex: String? = null,\n        useWebView: Boolean = true,\n    ): StrResponse {\n        if (type != null) {\n            return StrResponse(url, HexUtil.encodeHexStr(getByteArrayAwait()))\n        }\n        concurrentRateLimiter.withLimit {\n            setCookie()\n            val strResponse: StrResponse\n            if (this.useWebView && useWebView) {\n                strResponse = when (method) {\n                    RequestMethod.POST -> {\n                        val res = getClient().newCallStrResponse(retry) {\n                            addHeaders(headerMap)\n                            url(urlNoQuery)\n                            if (!encodedForm.isNullOrEmpty() || body.isNullOrBlank()) {\n                                postForm(encodedForm ?: \"\")\n                            } else {\n                                postJson(body)\n                            }\n                        }\n                        BackstageWebView(\n                            url = res.url,\n                            html = res.body,\n                            tag = source?.getKey(),\n                            javaScript = webJs ?: jsStr,\n                            sourceRegex = sourceRegex,\n                            headerMap = headerMap,\n                            delayTime = webViewDelayTime\n                        ).getStrResponse()\n                    }\n\n                    else -> BackstageWebView(\n                        url = url,\n                        tag = source?.getKey(),\n                        javaScript = webJs ?: jsStr,\n                        sourceRegex = sourceRegex,\n                        headerMap = headerMap,\n                        delayTime = webViewDelayTime\n                    ).getStrResponse()\n                }\n            } else {\n                strResponse = getClient().newCallStrResponse(retry) {\n                    addHeaders(headerMap)\n                    when (method) {\n                        RequestMethod.POST -> {\n                            url(urlNoQuery)\n                            val contentType = headerMap[\"Content-Type\"]\n                            val body = body\n                            if (!encodedForm.isNullOrEmpty() || body.isNullOrBlank()) {\n                                postForm(encodedForm ?: \"\")\n                            } else if (!contentType.isNullOrBlank()) {\n                                val requestBody = body.toRequestBody(contentType.toMediaType())\n                                post(requestBody)\n                            } else {\n                                postJson(body)\n                            }\n                        }\n\n                        else -> get(urlNoQuery, encodedQuery)\n                    }\n                }.let {\n                    val isXml = it.raw.body.contentType()?.toString()\n                        ?.matches(AppPattern.xmlContentTypeRegex) == true\n                    if (isXml && it.body?.trim()?.startsWith(\"<?xml\", true) == false) {\n                        StrResponse(it.raw, \"<?xml version=\\\"1.0\\\"?>\" + it.body)\n                    } else it\n                }\n            }\n            return strResponse\n        }\n    }\n\n    @JvmOverloads\n    fun getStrResponse(\n        jsStr: String? = null,\n        sourceRegex: String? = null,\n        useWebView: Boolean = true,\n    ): StrResponse {\n        return runBlocking(coroutineContext) {\n            getStrResponseAwait(jsStr, sourceRegex, useWebView)\n        }\n    }\n\n    /**\n     * 访问网站,返回Response\n     */\n    suspend fun getResponseAwait(): Response {\n        concurrentRateLimiter.withLimit {\n            setCookie()\n            val response = getClient().newCallResponse(retry) {\n                addHeaders(headerMap)\n                when (method) {\n                    RequestMethod.POST -> {\n                        url(urlNoQuery)\n                        val contentType = headerMap[\"Content-Type\"]\n                        val body = body\n                        if (!encodedForm.isNullOrEmpty() || body.isNullOrBlank()) {\n                            postForm(encodedForm ?: \"\")\n                        } else if (!contentType.isNullOrBlank()) {\n                            val requestBody = body.toRequestBody(contentType.toMediaType())\n                            post(requestBody)\n                        } else {\n                            postJson(body)\n                        }\n                    }\n\n                    else -> get(urlNoQuery, encodedQuery)\n                }\n            }\n            return response\n        }\n    }\n\n    private fun getClient(): OkHttpClient {\n        val client = getProxyClient(proxy)\n        if (readTimeout == null && callTimeout == null) {\n            return client\n        }\n        return client.newBuilder().run {\n            if (readTimeout != null) {\n                readTimeout(readTimeout, TimeUnit.MILLISECONDS)\n                callTimeout(max(60 * 1000L, readTimeout * 2), TimeUnit.MILLISECONDS)\n            }\n            if (callTimeout != null) {\n                callTimeout(callTimeout, TimeUnit.MILLISECONDS)\n            }\n            build()\n        }\n    }\n\n    fun getResponse(): Response {\n        return runBlocking(coroutineContext) {\n            getResponseAwait()\n        }\n    }\n\n    private fun getByteArrayIfDataUri(): ByteArray? {\n        if (!urlNoQuery.startsWith(\"data:\")) {\n            return null\n        }\n        val dataUriFindResult = dataUriRegex.find(urlNoQuery)\n        if (dataUriFindResult != null) {\n            val dataUriBase64 = dataUriFindResult.groupValues[1]\n            val byteArray = Base64.decode(dataUriBase64, Base64.DEFAULT)\n            return byteArray\n        }\n        return null\n    }\n\n    /**\n     * 访问网站,返回ByteArray\n     */\n    suspend fun getByteArrayAwait(): ByteArray {\n        getByteArrayIfDataUri()?.let {\n            return it\n        }\n        return getResponseAwait().body.bytes()\n    }\n\n    fun getByteArray(): ByteArray {\n        return runBlocking(coroutineContext) {\n            getByteArrayAwait()\n        }\n    }\n\n    /**\n     * 访问网站,返回InputStream\n     */\n    suspend fun getInputStreamAwait(): InputStream {\n        getByteArrayIfDataUri()?.let {\n            return ByteArrayInputStream(it)\n        }\n        return getResponseAwait().body.byteStream()\n    }\n\n    fun getInputStream(): InputStream {\n        return runBlocking(coroutineContext) {\n            getInputStreamAwait()\n        }\n    }\n\n    /**\n     * 上传文件\n     */\n    suspend fun upload(fileName: String, file: Any, contentType: String): StrResponse {\n        return getProxyClient(proxy).newCallStrResponse(retry) {\n            url(urlNoQuery)\n            val bodyMap = GSON.fromJsonObject<HashMap<String, Any>>(body).getOrNull()!!\n            bodyMap.forEach { entry ->\n                if (entry.value.toString() == \"fileRequest\") {\n                    bodyMap[entry.key] = mapOf(\n                        Pair(\"fileName\", fileName),\n                        Pair(\"file\", file),\n                        Pair(\"contentType\", contentType)\n                    )\n                }\n            }\n            postMultipart(type, bodyMap)\n        }\n    }\n\n    /**\n     * 设置cookie 优先级\n     * urlOption临时cookie > 数据库cookie\n     */\n    private fun setCookie() {\n        val cookie = kotlin.run {\n            /* 每次调用getXX cookieJar已经保存过了\n            if (enabledCookieJar) {\n                val key = \"${domain}_cookieJar\"\n                CacheManager.getFromMemory(key)?.let {\n                    return@run it\n                }\n            }\n            */\n            CookieStore.getCookie(domain)\n        }\n        if (cookie.isNotEmpty()) {\n            mergeCookies(cookie, headerMap[\"Cookie\"])?.let {\n                headerMap.put(\"Cookie\", it)\n            }\n        }\n        if (enabledCookieJar) {\n            headerMap[CookieManager.cookieJarHeader] = \"1\"\n        } else {\n            headerMap.remove(CookieManager.cookieJarHeader)\n        }\n    }\n\n    /**\n     * 保存cookieJar中的cookie在访问结束时就保存,不等到下次访问\n     */\n    private fun saveCookie() {\n        //书源启用保存cookie时 添加内存中的cookie到数据库\n        if (enabledCookieJar) {\n            val key = \"${domain}_cookieJar\"\n            CacheManager.getFromMemory(key)?.let {\n                if (it is String) {\n                    CookieStore.replaceCookie(domain, it)\n                    CacheManager.deleteMemory(key)\n                }\n            }\n        }\n    }\n\n    /**\n     *获取处理过阅读定义的urlOption和cookie的GlideUrl\n     */\n    fun getGlideUrl(): GlideUrl {\n        setCookie()\n        return GlideUrl(url, GlideHeaders(headerMap))\n    }\n\n    fun getUserAgent(): String {\n        return headerMap.get(UA_NAME, true) ?: AppConfig.userAgent\n    }\n\n    fun isPost(): Boolean {\n        return method == RequestMethod.POST\n    }\n\n    override fun getSource(): BaseSource? {\n        return source\n    }\n\n    companion object {\n        val paramPattern: Pattern = Pattern.compile(\"\\\\s*,\\\\s*(?=\\\\{)\")\n        private val pagePattern = Pattern.compile(\"<(.*?)>\")\n        private val queryEncoder =\n            RFC3986.UNRESERVED.orNew(PercentCodec.of(\"!$%&()*+,/:;=?@[\\\\]^`{|}\"))\n\n        fun AnalyzeUrl.getMediaItem(): MediaItem {\n            setCookie()\n            return ExoPlayerHelper.createMediaItem(url, headerMap)\n        }\n\n    }\n\n    @Keep\n    data class UrlOption(\n        private var method: String? = null,\n        private var charset: String? = null,\n        private var headers: Any? = null,\n        private var body: Any? = null,\n        /**\n         * 源Url\n         **/\n        private var origin: String? = null,\n        /**\n         * 重试次数\n         **/\n        private var retry: Int? = null,\n        /**\n         * 类型\n         **/\n        private var type: String? = null,\n        /**\n         * 是否使用webView\n         **/\n        private var webView: Any? = null,\n        /**\n         * webView中执行的js\n         **/\n        private var webJs: String? = null,\n        /**\n         * 解析完url参数时执行的js\n         * 执行结果会赋值给url\n         */\n        private var js: String? = null,\n        /**\n         * 服务器id\n         */\n        private var serverID: Long? = null,\n        /**\n         * webview等待页面加载完毕的延迟时间（毫秒）\n         */\n        private var webViewDelayTime: Long? = null,\n    ) {\n        fun setMethod(value: String?) {\n            method = if (value.isNullOrBlank()) null else value\n        }\n\n        fun getMethod(): String? {\n            return method\n        }\n\n        fun setCharset(value: String?) {\n            charset = if (value.isNullOrBlank()) null else value\n        }\n\n        fun getCharset(): String? {\n            return charset\n        }\n\n        fun setOrigin(value: String?) {\n            origin = if (value.isNullOrBlank()) null else value\n        }\n\n        fun getOrigin(): String? {\n            return origin\n        }\n\n        fun setRetry(value: String?) {\n            retry = if (value.isNullOrEmpty()) null else value.toIntOrNull()\n        }\n\n        fun getRetry(): Int {\n            return retry ?: 0\n        }\n\n        fun setType(value: String?) {\n            type = if (value.isNullOrBlank()) null else value\n        }\n\n        fun getType(): String? {\n            return type\n        }\n\n        fun useWebView(): Boolean {\n            return when (webView) {\n                null, \"\", false, \"false\" -> false\n                else -> true\n            }\n        }\n\n        fun useWebView(boolean: Boolean) {\n            webView = if (boolean) true else null\n        }\n\n        fun setHeaders(value: String?) {\n            headers = if (value.isNullOrBlank()) {\n                null\n            } else {\n                GSON.fromJsonObject<Map<String, Any>>(value).getOrNull()\n            }\n        }\n\n        fun getHeaderMap(): Map<*, *>? {\n            return when (val value = headers) {\n                is Map<*, *> -> value\n                is String -> GSON.fromJsonObject<Map<String, Any>>(value).getOrNull()\n                else -> null\n            }\n        }\n\n        fun setBody(value: String?) {\n            body = when {\n                value.isNullOrBlank() -> null\n                value.isJsonObject() -> GSON.fromJsonObject<Map<String, Any>>(value).getOrNull()\n                value.isJsonArray() -> GSON.fromJsonArray<Map<String, Any>>(value).getOrNull()\n                else -> value\n            }\n        }\n\n        fun getBody(): String? {\n            return body?.let {\n                it as? String ?: GSON.toJson(it)\n            }\n        }\n\n        fun setWebJs(value: String?) {\n            webJs = if (value.isNullOrBlank()) null else value\n        }\n\n        fun getWebJs(): String? {\n            return webJs\n        }\n\n        fun setJs(value: String?) {\n            js = if (value.isNullOrBlank()) null else value\n        }\n\n        fun getJs(): String? {\n            return js\n        }\n\n        fun setServerID(value: String?) {\n            serverID = if (value.isNullOrBlank()) null else value.toLong()\n        }\n\n        fun getServerID(): Long? {\n            return serverID\n        }\n\n        fun setWebViewDelayTime(value: String?) {\n            webViewDelayTime = if (value.isNullOrBlank()) null else value.toLong()\n        }\n\n        fun getWebViewDelayTime(): Long? {\n            return webViewDelayTime\n        }\n    }\n\n    data class ConcurrentRecord(\n        /**\n         * 是否按频率\n         */\n        val isConcurrent: Boolean,\n        /**\n         * 开始访问时间\n         */\n        var time: Long,\n        /**\n         * 正在访问的个数\n         */\n        var frequency: Int\n    )\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/model/analyzeRule/CustomUrl.kt",
    "content": "package io.legado.app.model.analyzeRule\n\nimport io.legado.app.utils.GSON\nimport io.legado.app.utils.fromJsonObject\n\n@Suppress(\"unused\")\nclass CustomUrl(url: String) {\n\n    private val mUrl: String\n    private val attribute = hashMapOf<String, Any>()\n\n    init {\n        val urlMatcher = AnalyzeUrl.paramPattern.matcher(url)\n        mUrl = if (urlMatcher.find()) {\n            val attr = url.substring(urlMatcher.end())\n            GSON.fromJsonObject<Map<String, Any>>(attr).getOrNull()?.let {\n                attribute.putAll(it)\n            }\n            url.substring(0, urlMatcher.start())\n        } else {\n            url\n        }\n    }\n\n    fun putAttribute(key: String, value: Any?): CustomUrl {\n        if (value == null) {\n            attribute.remove(key)\n        } else {\n            attribute[key] = value\n        }\n        return this\n    }\n\n    fun getUrl(): String {\n        return mUrl\n    }\n\n    fun getAttr(): Map<String, Any> {\n        return attribute\n    }\n\n    override fun toString(): String {\n        if (attribute.isEmpty()) {\n            return mUrl\n        }\n        return mUrl + \",\" + GSON.toJson(attribute)\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/model/analyzeRule/QueryTTF.java",
    "content": "package io.legado.app.model.analyzeRule;\n\nimport androidx.annotation.Keep;\n\nimport java.nio.ByteBuffer;\nimport java.nio.ByteOrder;\nimport java.nio.charset.StandardCharsets;\nimport java.util.HashMap;\nimport java.util.LinkedList;\n\n@Keep\n@SuppressWarnings({\"FieldCanBeLocal\", \"unused\"})\npublic class QueryTTF {\n    /**\n     * 文件头\n     *\n     * @url <a href=\"https://learn.microsoft.com/zh-cn/typography/opentype/spec/otff\">Microsoft opentype 字体文档</a>\n     */\n    private static class Header {\n        /**\n         * uint32   字体版本 0x00010000 (ttf)\n         */\n        public long sfntVersion;\n        /**\n         * uint16   Number of tables.\n         */\n        public int numTables;\n        /**\n         * uint16\n         */\n        public int searchRange;\n        /**\n         * uint16\n         */\n        public int entrySelector;\n        /**\n         * uint16\n         */\n        public int rangeShift;\n    }\n\n    /**\n     * 数据表目录\n     */\n    private static class Directory {\n        /**\n         * uint32 (表标识符)\n         */\n        public String tableTag;\n        /**\n         * uint32 (该表的校验和)\n         */\n        public int checkSum;\n        /**\n         * uint32 (TTF文件 Bytes 数据索引 0 开始的偏移地址)\n         */\n        public int offset;\n        /**\n         * uint32 (该表的长度)\n         */\n        public int length;\n    }\n\n    private static class NameLayout {\n        public int format;\n        public int count;\n        public int stringOffset;\n        public LinkedList<NameRecord> records = new LinkedList<>();\n    }\n\n    private static class NameRecord {\n        public int platformID;           // 平台标识符<0:Unicode, 1:Mac, 2:ISO, 3:Windows, 4:Custom>\n        public int encodingID;           // 编码标识符\n        public int languageID;           // 语言标识符\n        public int nameID;               // 名称标识符\n        public int length;               // 名称字符串的长度\n        public int offset;               // 名称字符串相对于stringOffset的字节偏移量\n    }\n\n    /**\n     * Font Header Table\n     */\n    private static class HeadLayout {\n        /**\n         * uint16\n         */\n        public int majorVersion;\n        /**\n         * uint16\n         */\n        public int minorVersion;\n        /**\n         * uint16\n         */\n        public int fontRevision;\n        /**\n         * uint32\n         */\n        public int checkSumAdjustment;\n        /**\n         * uint32\n         */\n        public int magicNumber;\n        /**\n         * uint16\n         */\n        public int flags;\n        /**\n         * uint16\n         */\n        public int unitsPerEm;\n        /**\n         * long\n         */\n        public long created;\n        /**\n         * long\n         */\n        public long modified;\n        /**\n         * int16\n         */\n        public short xMin;\n        /**\n         * int16\n         */\n        public short yMin;\n        /**\n         * int16\n         */\n        public short xMax;\n        /**\n         * int16\n         */\n        public short yMax;\n        /**\n         * uint16\n         */\n        public int macStyle;\n        /**\n         * uint16\n         */\n        public int lowestRecPPEM;\n        /**\n         * int16\n         */\n        public short fontDirectionHint;\n        /**\n         * int16\n         * <p> 0 表示短偏移 (Offset16)，1 表示长偏移 (Offset32)。\n         */\n        public short indexToLocFormat;\n        /**\n         * int16\n         */\n        public short glyphDataFormat;\n    }\n\n    /**\n     * Maximum Profile\n     */\n    private static class MaxpLayout {\n        /**\n         * uint32   高16位表示整数，低16位表示小数\n         */\n        public int version;\n        /**\n         * uint16   字体中的字形数量\n         */\n        public int numGlyphs;\n        /**\n         * uint16   非复合字形中包含的最大点数。点是构成字形轮廓的基本单位。\n         */\n        public int maxPoints;\n        /**\n         * uint16   非复合字形中包含的最大轮廓数。轮廓是由一系列点连接形成的封闭曲线。\n         */\n        public int maxContours;\n        /**\n         * uint16   复合字形中包含的最大点数。复合字形是由多个简单字形组合而成的。\n         */\n        public int maxCompositePoints;\n        /**\n         * uint16   复合字形中包含的最大轮廓数。\n         */\n        public int maxCompositeContours;\n        /**\n         * uint16\n         */\n        public int maxZones;\n        /**\n         * uint16\n         */\n        public int maxTwilightPoints;\n        /**\n         * uint16\n         */\n        public int maxStorage;\n        /**\n         * uint16\n         */\n        public int maxFunctionDefs;\n        /**\n         * uint16\n         */\n        public int maxInstructionDefs;\n        /**\n         * uint16\n         */\n        public int maxStackElements;\n        /**\n         * uint16\n         */\n        public int maxSizeOfInstructions;\n        /**\n         * uint16   任何复合字形在“顶层”引用的最大组件数。\n         */\n        public int maxComponentElements;\n        /**\n         * uint16   递归的最大层数；简单组件为1。\n         */\n        public int maxComponentDepth;\n    }\n\n    /**\n     * 字符到字形索引映射表\n     */\n    private static class CmapLayout {\n        /**\n         * uint16\n         */\n        public int version;\n        /**\n         * uint16   后面的编码表的数量\n         */\n        public int numTables;\n        public LinkedList<CmapRecord> records = new LinkedList<>();\n        public HashMap<Integer, CmapFormat> tables = new HashMap<>();\n    }\n\n    /**\n     * Encoding records and encodings\n     */\n    private static class CmapRecord {\n        /**\n         * uint16   Platform ID.\n         * <p> 0、Unicode\n         * <p> 1、Macintosh\n         * <p> 2、ISO\n         * <p> 3、Windows\n         * <p> 4、Custom\n         */\n        public int platformID;\n        /**\n         * uint16   Platform-specific encoding ID.\n         * <p> platform ID = 3\n         * <p>  0、Symbol\n         * <p>  1、Unicode BMP\n         * <p>  2、ShiftJIS\n         * <p>  3、PRC\n         * <p>  4、Big5\n         * <p>  5、Wansung\n         * <p>  6、Johab\n         * <p>  7、Reserved\n         * <p>  8、Reserved\n         * <p>  9、Reserved\n         * <p> 10、Unicode full repertoire\n         */\n        public int encodingID;\n        /**\n         * uint32   从 cmap 表开头到子表的字节偏移量\n         */\n        public int offset;\n    }\n\n    private static class CmapFormat {\n        /**\n         * uint16\n         * <p> cmapFormat 子表的格式类型\n         */\n        public int format;\n        /**\n         * uint16\n         * <p> 这个 Format 表的长度（以字节为单位）\n         */\n        public int length;\n        /**\n         * uint16\n         * <p> 仅 platformID=1 时有效\n         */\n        public int language;\n        /**\n         * uint16[256]\n         * <p> 仅 Format=2\n         * <p> 将高字节映射到 subHeaders 的数组：值为 subHeader 索引x8\n         */\n        public int[] subHeaderKeys;\n        /**\n         * uint16[]\n         * <p> 仅 Format=2\n         * <p> subHeader 子标头的可变长度数组\n         * <p> 其结构为 uint16[][4]{ {uint16,uint16,int16,uint16}, ... }\n         */\n        public int[] subHeaders;\n        /**\n         * uint16   segCount x2\n         * <p> 仅 Format=4\n         * <p> seg段计数乘以 2。这是因为每个段用两个字节表示，所以这个值是实际段数的两倍。\n         */\n        public int segCountX2;\n        /**\n         * uint16\n         * <p> 仅 Format=4\n         * <p> 小于或等于段数的最大二次幂，再乘以 2。这是为二分查找优化搜索过程。\n         */\n        public int searchRange;\n        /**\n         * uint16\n         * <p> 仅 Format=4\n         * <p> 等于 log2(searchRange/2)，这是最大二次幂的对数。\n         */\n        public int entrySelector;\n        /**\n         * uint16\n         * <p> 仅 Format=4\n         * <p> segCount * 2 - searchRange 用于调整搜索范围的偏移。\n         */\n        public int rangeShift;\n        /**\n         * uint16[segCount]\n         * <p> 仅 Format=4\n         * <p> 每个段的结束字符码，最后一个是 0xFFFF，表示 Unicode 范围的结束。\n         */\n        public int[] endCode;\n        /**\n         * uint16\n         * <p> 仅 Format=4\n         * <p> 固定设置为 0，用于填充保留位以保持数据对齐。\n         */\n        public int reservedPad;\n        /**\n         * uint16[segCount]\n         * <p> 仅 Format=4\n         * <p> 每个段的起始字符码。\n         */\n        public int[] startCode;\n        /**\n         * int16[segCount]\n         * <p> 仅 Format=4\n         * <p> 用于计算字形索引的偏移值。该值被加到从 startCode 到 endCode 的所有字符码上，得到相应的字形索引。\n         */\n        public int[] idDelta;\n        /**\n         * uint16[segCount]\n         * <p> 仅 Format=4\n         * <p> 偏移到 glyphIdArray 中的起始位置，如果没有额外的字形索引映射，则为 0。\n         */\n        public int[] idRangeOffsets;\n        /**\n         * uint16\n         * <p> 仅 Format=6\n         * <p> 子范围的第一个字符代码。这是连续字符代码范围的起始点。\n         */\n        public int firstCode;\n        /**\n         * uint16\n         * <p> 仅 Format=6\n         * <p> 子范围中字符代码的数量。这表示从 firstCode 开始，连续多少个字符代码被包含\n         */\n        public int entryCount;\n        /**\n         * 字形索引数组\n         * <p> Format=0 为 bye[256]数组\n         * <p> Format>0 为 uint16[] 数组\n         * <p> Format>12 为 uint32[] 数组\n         * <p> @url <a href=\"https://learn.microsoft.com/zh-cn/typography/opentype/spec/cmap#language\">Microsoft cmap文档</a>\n         */\n        public int[] glyphIdArray;\n    }\n\n    /**\n     * 字形轮廓数据表\n     */\n    private static class GlyfLayout {\n        /**\n         * int16    非负值为简单字形的轮廓数,负值表示为复合字形\n         */\n        public short numberOfContours;\n        /**\n         * int16    Minimum x for coordinate data.\n         */\n        public short xMin;\n        /**\n         * int16    Minimum y for coordinate data.\n         */\n        public short yMin;\n        /**\n         * int16    Maximum x for coordinate data.\n         */\n        public short xMax;\n        /**\n         * int16    Maximum y for coordinate data.\n         */\n        public short yMax;\n        /**\n         * 简单字形数据\n         */\n        public GlyphTableBySimple glyphSimple;\n        /**\n         * 复合字形数据\n         */\n        public LinkedList<GlyphTableComponent> glyphComponent;\n    }\n\n    /**\n     * 简单字形数据表\n     */\n    private static class GlyphTableBySimple {\n        /**\n         * uint16[numberOfContours]\n         */\n        int[] endPtsOfContours;\n        /**\n         * uint16\n         */\n        int instructionLength;\n        /**\n         * uint8[instructionLength]\n         */\n        int[] instructions;\n        /**\n         * uint8[variable]\n         * <p> bit0: 该点位于曲线上\n         * <p> bit1: < 1:xCoordinate为uint8 >\n         * <p> bit2: < 1:yCoordinate为uint8 >\n         * <p> bit3: < 1:下一个uint8为此条目之后插入的附加逻辑标志条目的数量 >\n         * <p> bit4: < bit1=1时表示符号[1.正,0.负]; bit1=0时[1.x坐标重复一次,0.x坐标读为int16] >\n         * <p> bit5: < bit2=1时表示符号[1.正,0.负]; bit2=0时[1.y坐标重复一次,0.y坐标读为int16] >\n         * <p> bit6: 字形描述中的轮廓可能会重叠\n         * <p> bit7: 保留位,无意义\n         */\n        int[] flags;\n        /**\n         * uint8[]  when(flags&0x02==0x02)\n         * int16[]  when(flags&0x12==0x00)\n         */\n        int[] xCoordinates;\n        /**\n         * uint8[]  when(flags&0x04==0x02)\n         * int16[]  when(flags&0x24==0x00)\n         */\n        int[] yCoordinates;\n    }\n\n    /**\n     * 复合字形数据表\n     */\n    private static class GlyphTableComponent {\n        /**\n         * uint16\n         * <p> bit0: < 1:argument是16bit，0:argument是8bit >\n         * <p> bit1: < 1:argument是有符号值，0:argument是无符号值 >\n         * <p> bit3: 该组件有一个缩放比例，否则比例为1.0\n         * <p> bit5: 表示在此字形之后还有字形\n         */\n        int flags;\n        /**\n         * uint16\n         */\n        int glyphIndex;\n        /**\n         * x-offset\n         * <p>  uint8 when flags&0x03==0\n         * <p>   int8 when flags&0x03==1\n         * <p> uint16 when flags&0x03==2\n         * <p>  int16 when flags&0x03==3\n         */\n        int argument1;\n        /**\n         * y-offset\n         * <p>  uint8 when flags&0x03==0\n         * <p>   int8 when flags&0x03==1\n         * <p> uint16 when flags&0x03==2\n         * <p>  int16 when flags&0x03==3\n         */\n        int argument2;\n        /**\n         * uint16\n         * <p> 值类型为 F2DOT14 的组件缩放X比例值\n         */\n        float xScale;\n        /**\n         * uint16\n         * <p> 值类型为 F2DOT14 的2x2变换矩阵01值\n         */\n        float scale01;\n        /**\n         * uint16\n         * <p> 值类型为 F2DOT14 的2x2变换矩阵10值\n         */\n        float scale10;\n        /**\n         * uint16\n         * <p> 值类型为 F2DOT14 的组件缩放Y比例值\n         */\n        float yScale;\n    }\n\n    private static class BufferReader {\n        private final ByteBuffer byteBuffer;\n\n        public BufferReader(byte[] buffer, int index) {\n            this.byteBuffer = ByteBuffer.wrap(buffer);\n            this.byteBuffer.order(ByteOrder.BIG_ENDIAN); // 设置为大端模式\n            this.byteBuffer.position(index); // 设置起始索引\n        }\n\n        public void position(int index) {\n            byteBuffer.position(index); // 设置起始索引\n        }\n\n        public int position() {\n            return byteBuffer.position();\n        }\n\n        public long ReadUInt64() {\n            return byteBuffer.getLong();\n        }\n\n        public int ReadUInt32() {\n            return byteBuffer.getInt();\n        }\n\n        public int ReadInt32() {\n            return byteBuffer.getInt();\n        }\n\n        public int ReadUInt16() {\n            return byteBuffer.getShort() & 0xFFFF;\n        }\n\n        public short ReadInt16() {\n            return byteBuffer.getShort();\n        }\n\n        public short ReadUInt8() {\n            return (short) (byteBuffer.get() & 0xFF);\n        }\n\n        public byte ReadInt8() {\n            return byteBuffer.get();\n        }\n\n        public byte[] ReadByteArray(int len) {\n            assert len >= 0;\n            byte[] result = new byte[len];\n            byteBuffer.get(result);\n            return result;\n        }\n\n        public int[] ReadUInt8Array(int len) {\n            assert len >= 0;\n            var result = new int[len];\n            for (int i = 0; i < len; ++i) result[i] = byteBuffer.get() & 0xFF;\n            return result;\n        }\n\n        public int[] ReadInt16Array(int len) {\n            assert len >= 0;\n            var result = new int[len];\n            for (int i = 0; i < len; ++i) result[i] = byteBuffer.getShort();\n            return result;\n        }\n\n        public int[] ReadUInt16Array(int len) {\n            assert len >= 0;\n            var result = new int[len];\n            for (int i = 0; i < len; ++i) result[i] = byteBuffer.getShort() & 0xFFFF;\n            return result;\n        }\n\n        public int[] ReadInt32Array(int len) {\n            assert len >= 0;\n            var result = new int[len];\n            for (int i = 0; i < len; ++i) result[i] = byteBuffer.getInt();\n            return result;\n        }\n    }\n\n    private final Header fileHeader = new Header();\n    private final HashMap<String, Directory> directorys = new HashMap<>();\n    private final NameLayout name = new NameLayout();\n    private final HeadLayout head = new HeadLayout();\n    private final MaxpLayout maxp = new MaxpLayout();\n    private final CmapLayout Cmap = new CmapLayout();\n    private final int[][] pps = new int[][]{{3, 10}, {0, 4}, {3, 1}, {1, 0}, {0, 3}, {0, 1}};\n\n    private void readNameTable(byte[] buffer) {\n        var dataTable = directorys.get(\"name\");\n        assert dataTable != null;\n        var reader = new BufferReader(buffer, dataTable.offset);\n        name.format = reader.ReadUInt16();\n        name.count = reader.ReadUInt16();\n        name.stringOffset = reader.ReadUInt16();\n        for (int i = 0; i < name.count; ++i) {\n            NameRecord record = new NameRecord();\n            record.platformID = reader.ReadUInt16();\n            record.encodingID = reader.ReadUInt16();\n            record.languageID = reader.ReadUInt16();\n            record.nameID = reader.ReadUInt16();\n            record.length = reader.ReadUInt16();\n            record.offset = reader.ReadUInt16();\n            name.records.add(record);\n        }\n    }\n\n    private void readHeadTable(byte[] buffer) {\n        var dataTable = directorys.get(\"head\");\n        assert dataTable != null;\n        var reader = new BufferReader(buffer, dataTable.offset);\n        head.majorVersion = reader.ReadUInt16();\n        head.minorVersion = reader.ReadUInt16();\n        head.fontRevision = reader.ReadUInt32();\n        head.checkSumAdjustment = reader.ReadUInt32();\n        head.magicNumber = reader.ReadUInt32();\n        head.flags = reader.ReadUInt16();\n        head.unitsPerEm = reader.ReadUInt16();\n        head.created = reader.ReadUInt64();\n        head.modified = reader.ReadUInt64();\n        head.xMin = reader.ReadInt16();\n        head.yMin = reader.ReadInt16();\n        head.xMax = reader.ReadInt16();\n        head.yMax = reader.ReadInt16();\n        head.macStyle = reader.ReadUInt16();\n        head.lowestRecPPEM = reader.ReadUInt16();\n        head.fontDirectionHint = reader.ReadInt16();\n        head.indexToLocFormat = reader.ReadInt16();\n        head.glyphDataFormat = reader.ReadInt16();\n    }\n\n    /**\n     * glyfId到glyphData的索引\n     * <p> 根据定义，索引零指向“丢失的字符”。\n     * <p> loca.length = maxp.numGlyphs + 1;\n     */\n    private int[] loca;\n\n    private void readLocaTable(byte[] buffer) {\n        var dataTable = directorys.get(\"loca\");\n        assert dataTable != null;\n        var reader = new BufferReader(buffer, dataTable.offset);\n        if (head.indexToLocFormat == 0) {\n            loca = reader.ReadUInt16Array(dataTable.length / 2);\n            // 当loca表数据长度为Uint16时,需要翻倍\n            for (var i = 0; i < loca.length; i++) loca[i] *= 2;\n        } else {\n            loca = reader.ReadInt32Array(dataTable.length / 4);\n        }\n    }\n\n    private void readCmapTable(byte[] buffer) {\n        var dataTable = directorys.get(\"cmap\");\n        assert dataTable != null;\n        var reader = new BufferReader(buffer, dataTable.offset);\n        Cmap.version = reader.ReadUInt16();\n        Cmap.numTables = reader.ReadUInt16();\n        for (int i = 0; i < Cmap.numTables; ++i) {\n            CmapRecord record = new CmapRecord();\n            record.platformID = reader.ReadUInt16();\n            record.encodingID = reader.ReadUInt16();\n            record.offset = reader.ReadUInt32();\n            Cmap.records.add(record);\n        }\n\n        for (var formatTable : Cmap.records) {\n            int fmtOffset = formatTable.offset;\n            if (Cmap.tables.containsKey(fmtOffset)) continue;\n            reader.position(dataTable.offset + fmtOffset);\n\n            CmapFormat f = new CmapFormat();\n            f.format = reader.ReadUInt16();\n            f.length = reader.ReadUInt16();\n            f.language = reader.ReadUInt16();\n            switch (f.format) {\n                case 0: {\n                    f.glyphIdArray = reader.ReadUInt8Array(f.length - 6);\n                    // 记录 unicode->glyphId 映射表\n                    int unicodeInclusive = 0;\n                    int unicodeExclusive = f.glyphIdArray.length;\n                    for (; unicodeInclusive < unicodeExclusive; unicodeInclusive++) {\n                        if (f.glyphIdArray[unicodeInclusive] == 0) continue; // 排除轮廓索引为0的Unicode\n                        unicodeToGlyphId.put(unicodeInclusive, f.glyphIdArray[unicodeInclusive]);\n                    }\n                    break;\n                }\n                case 4: {\n                    f.segCountX2 = reader.ReadUInt16();\n                    int segCount = f.segCountX2 / 2;\n                    f.searchRange = reader.ReadUInt16();\n                    f.entrySelector = reader.ReadUInt16();\n                    f.rangeShift = reader.ReadUInt16();\n                    f.endCode = reader.ReadUInt16Array(segCount);\n                    f.reservedPad = reader.ReadUInt16();\n                    f.startCode = reader.ReadUInt16Array(segCount);\n                    f.idDelta = reader.ReadInt16Array(segCount);\n                    f.idRangeOffsets = reader.ReadUInt16Array(segCount);\n                    // 一个包含字形索引的数组，其长度是任意的，取决于映射的复杂性和字体中的字符数量。\n                    int glyphIdArrayLength = (f.length - 16 - (segCount * 8)) / 2;\n                    f.glyphIdArray = reader.ReadUInt16Array(glyphIdArrayLength);\n\n                    // 记录 unicode->glyphId 映射表\n                    for (int segmentIndex = 0; segmentIndex < segCount; segmentIndex++) {\n                        int unicodeInclusive = f.startCode[segmentIndex];\n                        int unicodeExclusive = f.endCode[segmentIndex];\n                        int idDelta = f.idDelta[segmentIndex];\n                        int idRangeOffset = f.idRangeOffsets[segmentIndex];\n                        for (int unicode = unicodeInclusive; unicode <= unicodeExclusive; unicode++) {\n                            int glyphId = 0;\n                            if (idRangeOffset == 0) {\n                                glyphId = (unicode + idDelta) & 0xFFFF;\n                            } else {\n                                int gIndex = (idRangeOffset / 2) + unicode - unicodeInclusive + segmentIndex - segCount;\n                                if (gIndex < glyphIdArrayLength) glyphId = f.glyphIdArray[gIndex] + idDelta;\n                            }\n                            if (glyphId == 0) continue; // 排除轮廓索引为0的Unicode\n                            unicodeToGlyphId.put(unicode, glyphId);\n                        }\n                    }\n                    break;\n                }\n                case 6: {\n                    f.firstCode = reader.ReadUInt16();\n                    f.entryCount = reader.ReadUInt16();\n                    // 范围内字符代码的字形索引值数组。\n                    f.glyphIdArray = reader.ReadUInt16Array(f.entryCount);\n\n                    // 记录 unicode->glyphId 映射表\n                    int unicodeIndex = f.firstCode;\n                    int unicodeCount = f.entryCount;\n                    for (int gIndex = 0; gIndex < unicodeCount; gIndex++) {\n                        unicodeToGlyphId.put(unicodeIndex, f.glyphIdArray[gIndex]);\n                        unicodeIndex++;\n                    }\n                    break;\n                }\n                default:\n                    break;\n            }\n            Cmap.tables.put(fmtOffset, f);\n        }\n    }\n\n    private void readMaxpTable(byte[] buffer) {\n        var dataTable = directorys.get(\"maxp\");\n        assert dataTable != null;\n        var reader = new BufferReader(buffer, dataTable.offset);\n        maxp.version = reader.ReadUInt32();\n        maxp.numGlyphs = reader.ReadUInt16();\n        maxp.maxPoints = reader.ReadUInt16();\n        maxp.maxContours = reader.ReadUInt16();\n        maxp.maxCompositePoints = reader.ReadUInt16();\n        maxp.maxCompositeContours = reader.ReadUInt16();\n        maxp.maxZones = reader.ReadUInt16();\n        maxp.maxTwilightPoints = reader.ReadUInt16();\n        maxp.maxStorage = reader.ReadUInt16();\n        maxp.maxFunctionDefs = reader.ReadUInt16();\n        maxp.maxInstructionDefs = reader.ReadUInt16();\n        maxp.maxStackElements = reader.ReadUInt16();\n        maxp.maxSizeOfInstructions = reader.ReadUInt16();\n        maxp.maxComponentElements = reader.ReadUInt16();\n        maxp.maxComponentDepth = reader.ReadUInt16();\n    }\n\n    /**\n     * 字形轮廓表 数组\n     */\n    private GlyfLayout[] glyfArray;\n\n    private void readGlyfTable(byte[] buffer) {\n        var dataTable = directorys.get(\"glyf\");\n        assert dataTable != null;\n        int glyfCount = maxp.numGlyphs;\n        glyfArray = new GlyfLayout[glyfCount];  // 创建字形容器\n\n        var reader = new BufferReader(buffer, 0);\n        for (int index = 0; index < glyfCount; index++) {\n            if (loca[index] == loca[index + 1]) continue;   // 当前loca与下一个loca相同，表示这个字形不存在\n            int offset = dataTable.offset + loca[index];\n            // 读GlyphHeaders\n            var glyph = new GlyfLayout();\n            reader.position(offset);\n            glyph.numberOfContours = reader.ReadInt16();\n            if (glyph.numberOfContours > maxp.maxContours) continue; // 如果字形轮廓数大于非复合字形中包含的最大轮廓数，则说明该字形无效。\n            glyph.xMin = reader.ReadInt16();\n            glyph.yMin = reader.ReadInt16();\n            glyph.xMax = reader.ReadInt16();\n            glyph.yMax = reader.ReadInt16();\n\n            // 轮廓数为0时，不需要解析轮廓数据\n            if (glyph.numberOfContours == 0) continue;\n            // 读Glyph轮廓数据\n            if (glyph.numberOfContours > 0) {\n                // 简单轮廓\n                glyph.glyphSimple = new GlyphTableBySimple();\n                glyph.glyphSimple.endPtsOfContours = reader.ReadUInt16Array(glyph.numberOfContours);\n                glyph.glyphSimple.instructionLength = reader.ReadUInt16();\n                glyph.glyphSimple.instructions = reader.ReadUInt8Array(glyph.glyphSimple.instructionLength);\n                int flagLength = glyph.glyphSimple.endPtsOfContours[glyph.glyphSimple.endPtsOfContours.length - 1] + 1;\n                // 获取轮廓点描述标志\n                glyph.glyphSimple.flags = new int[flagLength];\n                for (int n = 0; n < flagLength; ++n) {\n                    var glyphSimpleFlag = reader.ReadUInt8();\n                    glyph.glyphSimple.flags[n] = glyphSimpleFlag;\n                    if ((glyphSimpleFlag & 0x08) == 0x08) {\n                        for (int m = reader.ReadUInt8(); m > 0; --m) {\n                            glyph.glyphSimple.flags[++n] = glyphSimpleFlag;\n                        }\n                    }\n                }\n                // 获取轮廓点描述x轴相对值\n                glyph.glyphSimple.xCoordinates = new int[flagLength];\n                for (int n = 0; n < flagLength; ++n) {\n                    switch (glyph.glyphSimple.flags[n] & 0x12) {\n                        case 0x02:\n                            glyph.glyphSimple.xCoordinates[n] = -1 * reader.ReadUInt8();\n                            break;\n                        case 0x12:\n                            glyph.glyphSimple.xCoordinates[n] = reader.ReadUInt8();\n                            break;\n                        case 0x10:\n                            glyph.glyphSimple.xCoordinates[n] = 0;  // 点位数据重复上一次数据，那么相对数据变化量就是0\n                            break;\n                        case 0x00:\n                            glyph.glyphSimple.xCoordinates[n] = reader.ReadInt16();\n                            break;\n                    }\n                }\n                // 获取轮廓点描述y轴相对值\n                glyph.glyphSimple.yCoordinates = new int[flagLength];\n                for (int n = 0; n < flagLength; ++n) {\n                    switch (glyph.glyphSimple.flags[n] & 0x24) {\n                        case 0x04:\n                            glyph.glyphSimple.yCoordinates[n] = -1 * reader.ReadUInt8();\n                            break;\n                        case 0x24:\n                            glyph.glyphSimple.yCoordinates[n] = reader.ReadUInt8();\n                            break;\n                        case 0x20:\n                            glyph.glyphSimple.yCoordinates[n] = 0;  // 点位数据重复上一次数据，那么相对数据变化量就是0\n                            break;\n                        case 0x00:\n                            glyph.glyphSimple.yCoordinates[n] = reader.ReadInt16();\n                            break;\n                    }\n                }\n            } else {\n                // 复合轮廓\n                glyph.glyphComponent = new LinkedList<>();\n                while (true) {\n                    var glyphTableComponent = new GlyphTableComponent();\n                    glyphTableComponent.flags = reader.ReadUInt16();\n                    glyphTableComponent.glyphIndex = reader.ReadUInt16();\n                    switch (glyphTableComponent.flags & 0b11) {\n                        case 0b00:\n                            glyphTableComponent.argument1 = reader.ReadUInt8();\n                            glyphTableComponent.argument2 = reader.ReadUInt8();\n                            break;\n                        case 0b10:\n                            glyphTableComponent.argument1 = reader.ReadInt8();\n                            glyphTableComponent.argument2 = reader.ReadInt8();\n                            break;\n                        case 0b01:\n                            glyphTableComponent.argument1 = reader.ReadUInt16();\n                            glyphTableComponent.argument2 = reader.ReadUInt16();\n                            break;\n                        case 0b11:\n                            glyphTableComponent.argument1 = reader.ReadInt16();\n                            glyphTableComponent.argument2 = reader.ReadInt16();\n                            break;\n                    }\n                    switch (glyphTableComponent.flags & 0b11001000) {\n                        case 0b00001000:\n                            // 有单一比例\n                            glyphTableComponent.yScale = glyphTableComponent.xScale = ((float) reader.ReadUInt16()) / 16384.0f;\n                            break;\n                        case 0b01000000:\n                            // 有X和Y的独立比例\n                            glyphTableComponent.xScale = ((float) reader.ReadUInt16()) / 16384.0f;\n                            glyphTableComponent.yScale = ((float) reader.ReadUInt16()) / 16384.0f;\n                            break;\n                        case 0b10000000:\n                            // 有2x2变换矩阵\n                            glyphTableComponent.xScale = ((float) reader.ReadUInt16()) / 16384.0f;\n                            glyphTableComponent.scale01 = ((float) reader.ReadUInt16()) / 16384.0f;\n                            glyphTableComponent.scale10 = ((float) reader.ReadUInt16()) / 16384.0f;\n                            glyphTableComponent.yScale = ((float) reader.ReadUInt16()) / 16384.0f;\n                            break;\n                    }\n                    glyph.glyphComponent.add(glyphTableComponent);\n                    if ((glyphTableComponent.flags & 0x20) == 0) break;\n                }\n            }\n            glyfArray[index] = glyph;\n        }\n    }\n\n    /**\n     * 使用轮廓索引值获取轮廓数据\n     *\n     * @param glyfId 轮廓索引\n     * @return 轮廓数据\n     */\n    public String getGlyfById(int glyfId) {\n        var glyph = glyfArray[glyfId];\n        if (glyph == null) return null;    // 过滤不存在的字体轮廓\n        String glyphString;\n        if (glyph.numberOfContours >= 0) {\n            // 简单字形\n            int dataCount = glyph.glyphSimple.flags.length;\n            String[] coordinateArray = new String[dataCount];\n            for (int i = 0; i < dataCount; i++) {\n                coordinateArray[i] = glyph.glyphSimple.xCoordinates[i] + \",\" + glyph.glyphSimple.yCoordinates[i];\n            }\n            glyphString = String.join(\"|\", coordinateArray);\n        } else {\n            // 复合字形\n            LinkedList<String> glyphIdList = new LinkedList<>();\n            for (var g : glyph.glyphComponent) {\n                glyphIdList.add(\"{\" +\n                        \"flags:\" + g.flags + \",\" +\n                        \"glyphIndex:\" + g.glyphIndex + \",\" +\n                        \"arg1:\" + g.argument1 + \",\" +\n                        \"arg2:\" + g.argument2 + \",\" +\n                        \"xScale:\" + g.xScale + \",\" +\n                        \"scale01:\" + g.scale01 + \",\" +\n                        \"scale10:\" + g.scale10 + \",\" +\n                        \"yScale:\" + g.yScale + \"}\");\n            }\n            glyphString = \"[\" + String.join(\",\", glyphIdList) + \"]\";\n        }\n        return glyphString;\n    }\n\n    /**\n     * 构造函数\n     *\n     * @param buffer 传入TTF字体二进制数组\n     */\n    public QueryTTF(final byte[] buffer) {\n        var fontReader = new BufferReader(buffer, 0);\n//        Log.i(\"QueryTTF\", \"读文件头\"); // 获取文件头\n        fileHeader.sfntVersion = fontReader.ReadUInt32();\n        fileHeader.numTables = fontReader.ReadUInt16();\n        fileHeader.searchRange = fontReader.ReadUInt16();\n        fileHeader.entrySelector = fontReader.ReadUInt16();\n        fileHeader.rangeShift = fontReader.ReadUInt16();\n        // 获取目录\n        for (int i = 0; i < fileHeader.numTables; ++i) {\n            Directory d = new Directory();\n            d.tableTag = new String(fontReader.ReadByteArray(4), StandardCharsets.US_ASCII);\n            d.checkSum = fontReader.ReadUInt32();\n            d.offset = fontReader.ReadUInt32();\n            d.length = fontReader.ReadUInt32();\n            directorys.put(d.tableTag, d);\n        }\n\n//        Log.i(\"QueryTTF\", \"解析表 name\"); // 字体信息,包含版权、名称、作者等...\n        readNameTable(buffer);\n//        Log.i(\"QueryTTF\", \"解析表 head\"); // 获取 head.indexToLocFormat\n        readHeadTable(buffer);\n//        Log.i(\"QueryTTF\", \"解析表 cmap\"); // Unicode编码->轮廓索引 对照表\n        readCmapTable(buffer);\n//        Log.i(\"QueryTTF\", \"解析表 loca\"); // 轮廓数据偏移地址表\n        readLocaTable(buffer);\n//        Log.i(\"QueryTTF\", \"解析表 maxp\"); // 获取 maxp.numGlyphs 字体轮廓数量\n        readMaxpTable(buffer);\n//        Log.i(\"QueryTTF\", \"解析表 glyf\"); // 字体轮廓数据表,需要解析loca,maxp表后计算\n        readGlyfTable(buffer);\n//        Log.i(\"QueryTTF\", \"建立Unicode&Glyph映射表\");\n        int glyfArrayLength = glyfArray.length;\n        for (var item : unicodeToGlyphId.entrySet()) {\n            int key = item.getKey();\n            int val = item.getValue();\n            if (val >= glyfArrayLength) continue;\n            String glyfString = getGlyfById(val);\n            unicodeToGlyph.put(key, glyfString);\n            if (glyfString == null) continue;   // null 不能用作hashmap的key\n            glyphToUnicode.put(glyfString, key);\n        }\n//        Log.i(\"QueryTTF\", \"字体处理完成\");\n    }\n\n    public final HashMap<Integer, String> unicodeToGlyph = new HashMap<>();\n    public final HashMap<String, Integer> glyphToUnicode = new HashMap<>();\n    public final HashMap<Integer, Integer> unicodeToGlyphId = new HashMap<>();\n\n    /**\n     * 使用 Unicode 值获查询廓索引\n     *\n     * @param unicode 传入 Unicode 值\n     * @return 轮廓索引\n     */\n    public int getGlyfIdByUnicode(int unicode) {\n        var result = unicodeToGlyphId.get(unicode);\n        if (result == null) return 0; // 如果找不到Unicode对应的轮廓索引，就返回默认值0\n        return result;\n    }\n\n    /**\n     * 使用 Unicode 值查询轮廓数据\n     *\n     * @param unicode 传入 Unicode 值\n     * @return 轮廓数据\n     */\n    public String getGlyfByUnicode(int unicode) {\n        return unicodeToGlyph.get(unicode);\n    }\n\n    /**\n     * 使用轮廓数据反查 Unicode 值\n     *\n     * @param glyph 传入轮廓数据\n     * @return Unicode\n     */\n    public int getUnicodeByGlyf(String glyph) {\n        var result = glyphToUnicode.get(glyph);\n        if (result == null) return 0; // 如果轮廓数据找不到对应的Unicode，就返回默认值0\n        return result;\n    }\n\n    /**\n     * Unicode 空白字符判断\n     *\n     * @param unicode 字符的 Unicode 值\n     * @return true:是空白字符; false:非空白字符\n     */\n    public boolean isBlankUnicode(int unicode) {\n        return switch (unicode) {\n            case 0x0009,    // 水平制表符 (Horizontal Tab)\n                    0x0020,    // 空格 (Space)\n                    0x00A0,    // 不中断空格 (No-Break Space)\n                    0x2002,    // En空格 (En Space)\n                    0x2003,    // Em空格 (Em Space)\n                    0x2007,    // 刚性空格 (Figure Space)\n                    0x200A,    // 发音修饰字母的连字符 (Hair Space)\n                    0x200B,    // 零宽空格 (Zero Width Space)\n                    0x200C,    // 零宽不连字 (Zero Width Non-Joiner)\n                    0x200D,    // 零宽连字 (Zero Width Joiner)\n                    0x202F,    // 狭窄不中断空格 (Narrow No-Break Space)\n                    0x205F     // 中等数学空格 (Medium Mathematical Space)\n                    -> true;\n            default -> false;\n        };\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/model/analyzeRule/RuleAnalyzer.kt",
    "content": "package io.legado.app.model.analyzeRule\n\n//通用的规则切分处理\nclass RuleAnalyzer(data: String, code: Boolean = false) {\n\n    private var queue: String = data //被处理字符串\n    private var pos = 0 //当前处理到的位置\n    private var start = 0 //当前处理字段的开始\n    private var startX = 0 //当前规则的开始\n\n    private var rule = ArrayList<String>()  //分割出的规则列表\n    private var step: Int = 0 //分割字符的长度\n    var elementsType = \"\" //当前分割字符串\n\n    fun trim() { // 修剪当前规则之前的\"@\"或者空白符\n        if (queue[pos] == '@' || queue[pos] < '!') { //在while里重复设置start和startX会拖慢执行速度，所以先来个判断是否存在需要修剪的字段，最后再一次性设置start和startX\n            pos++\n            while (queue[pos] == '@' || queue[pos] < '!') pos++\n            start = pos //开始点推移\n            startX = pos //规则起始点推移\n        }\n    }\n\n    //将pos重置为0，方便复用\n    fun reSetPos() {\n        pos = 0\n        startX = 0\n    }\n\n    /**\n     * 从剩余字串中拉出一个字符串，直到但不包括匹配序列\n     * @param seq 查找的字符串 **区分大小写**\n     * @return 是否找到相应字段。\n     */\n    private fun consumeTo(seq: String): Boolean {\n        start = pos //将处理到的位置设置为规则起点\n        val offset = queue.indexOf(seq, pos)\n        return if (offset != -1) {\n            pos = offset\n            true\n        } else false\n    }\n\n    /**\n     * 从剩余字串中拉出一个字符串，直到但不包括匹配序列（匹配参数列表中一项即为匹配），或剩余字串用完。\n     * @param seq 匹配字符串序列\n     * @return 成功返回true并设置间隔，失败则直接返回fasle\n     */\n    private fun consumeToAny(vararg seq: String): Boolean {\n\n        var pos = pos //声明新变量记录匹配位置，不更改类本身的位置\n\n        while (pos != queue.length) {\n\n            for (s in seq) {\n                if (queue.regionMatches(pos, s, 0, s.length)) {\n                    step = s.length //间隔数\n                    this.pos = pos //匹配成功, 同步处理位置到类\n                    return true //匹配就返回 true\n                }\n            }\n\n            pos++ //逐个试探\n        }\n        return false\n    }\n\n    /**\n     * 从剩余字串中拉出一个字符串，直到但不包括匹配序列（匹配参数列表中一项即为匹配），或剩余字串用完。\n     * @param seq 匹配字符序列\n     * @return 返回匹配位置\n     */\n    private fun findToAny(vararg seq: Char): Int {\n\n        var pos = pos //声明新变量记录匹配位置，不更改类本身的位置\n\n        while (pos != queue.length) {\n\n            for (s in seq) if (queue[pos] == s) return pos //匹配则返回位置\n\n            pos++ //逐个试探\n\n        }\n\n        return -1\n    }\n\n    /**\n     * 拉出一个非内嵌代码平衡组，存在转义文本\n     */\n    private fun chompCodeBalanced(open: Char, close: Char): Boolean {\n\n        var pos = pos //声明临时变量记录匹配位置，匹配成功后才同步到类的pos\n\n        var depth = 0 //嵌套深度\n        var otherDepth = 0 //其他对称符合嵌套深度\n\n        var inSingleQuote = false //单引号\n        var inDoubleQuote = false //双引号\n\n        do {\n            if (pos == queue.length) break\n            val c = queue[pos++]\n            if (c != ESC) { //非转义字符\n                if (c == '\\'' && !inDoubleQuote) inSingleQuote = !inSingleQuote //匹配具有语法功能的单引号\n                else if (c == '\"' && !inSingleQuote) inDoubleQuote = !inDoubleQuote //匹配具有语法功能的双引号\n\n                if (inSingleQuote || inDoubleQuote) continue //语法单元未匹配结束，直接进入下个循环\n\n                if (c == '[') depth++ //开始嵌套一层\n                else if (c == ']') depth-- //闭合一层嵌套\n                else if (depth == 0) {\n                    //处于默认嵌套中的非默认字符不需要平衡，仅depth为0时默认嵌套全部闭合，此字符才进行嵌套\n                    if (c == open) otherDepth++\n                    else if (c == close) otherDepth--\n                }\n\n            } else pos++\n\n        } while (depth > 0 || otherDepth > 0) //拉出一个平衡字串\n\n        return if (depth > 0 || otherDepth > 0) false else {\n            this.pos = pos //同步位置\n            true\n        }\n    }\n\n    /**\n     * 拉出一个规则平衡组，经过仔细测试xpath和jsoup中，引号内转义字符无效。\n     */\n    private fun chompRuleBalanced(open: Char, close: Char): Boolean {\n\n        var pos = pos //声明临时变量记录匹配位置，匹配成功后才同步到类的pos\n        var depth = 0 //嵌套深度\n        var inSingleQuote = false //单引号\n        var inDoubleQuote = false //双引号\n\n        do {\n            if (pos == queue.length) break\n            val c = queue[pos++]\n            if (c == '\\'' && !inDoubleQuote) inSingleQuote = !inSingleQuote //匹配具有语法功能的单引号\n            else if (c == '\"' && !inSingleQuote) inDoubleQuote = !inDoubleQuote //匹配具有语法功能的双引号\n\n            if (inSingleQuote || inDoubleQuote) continue //语法单元未匹配结束，直接进入下个循环\n            else if (c == '\\\\') { //不在引号中的转义字符才将下个字符转义\n                pos++\n                continue\n            }\n\n            if (c == open) depth++ //开始嵌套一层\n            else if (c == close) depth-- //闭合一层嵌套\n\n        } while (depth > 0) //拉出一个平衡字串\n\n        return if (depth > 0) false else {\n            this.pos = pos //同步位置\n            true\n        }\n    }\n\n    /**\n     * 不用正则,不到最后不切片也不用中间变量存储,只在序列中标记当前查找字段的开头结尾,到返回时才切片,高效快速准确切割规则\n     * 解决jsonPath自带的\"&&\"和\"||\"与阅读的规则冲突,以及规则正则或字符串中包含\"&&\"、\"||\"、\"%%\"、\"@\"导致的冲突\n     */\n    tailrec fun splitRule(vararg split: String): ArrayList<String> { //首段匹配,elementsType为空\n\n        if (split.size == 1) {\n            elementsType = split[0] //设置分割字串\n            return if (!consumeTo(elementsType)) {\n                rule += queue.substring(startX)\n                rule\n            } else {\n                step = elementsType.length //设置分隔符长度\n                splitRule()\n            } //递归匹配\n        } else if (!consumeToAny(* split)) { //未找到分隔符\n            rule += queue.substring(startX)\n            return rule\n        }\n\n        val end = pos //记录分隔位置\n        pos = start //重回开始，启动另一种查找\n\n        do {\n            val st = findToAny('[', '(') //查找筛选器位置\n\n            if (st == -1) {\n\n                rule = arrayListOf(queue.substring(startX, end)) //压入分隔的首段规则到数组\n\n                elementsType = queue.substring(end, end + step) //设置组合类型\n                pos = end + step //跳过分隔符\n\n                while (consumeTo(elementsType)) { //循环切分规则压入数组\n                    rule += queue.substring(start, pos)\n                    pos += step //跳过分隔符\n                }\n\n                rule += queue.substring(pos) //将剩余字段压入数组末尾\n\n                return rule\n            }\n\n            if (st > end) { //先匹配到st1pos，表明分隔字串不在选择器中，将选择器前分隔字串分隔的字段依次压入数组\n\n                rule = arrayListOf(queue.substring(startX, end)) //压入分隔的首段规则到数组\n\n                elementsType = queue.substring(end, end + step) //设置组合类型\n                pos = end + step //跳过分隔符\n\n                while (consumeTo(elementsType) && pos < st) { //循环切分规则压入数组\n                    rule += queue.substring(start, pos)\n                    pos += step //跳过分隔符\n                }\n\n                return if (pos > st) {\n                    startX = start\n                    splitRule() //首段已匹配,但当前段匹配未完成,调用二段匹配\n                } else { //执行到此，证明后面再无分隔字符\n                    rule += queue.substring(pos) //将剩余字段压入数组末尾\n                    rule\n                }\n            }\n\n            pos = st //位置推移到筛选器处\n            val next = if (queue[pos] == '[') ']' else ')' //平衡组末尾字符\n\n            if (!chompBalanced(queue[pos], next)) throw Error(\n                queue.substring(0, start) + \"后未平衡\"\n            ) //拉出一个筛选器,不平衡则报错\n\n        } while (end > pos)\n\n        start = pos //设置开始查找筛选器位置的起始位置\n\n        return splitRule(* split) //递归调用首段匹配\n    }\n\n    @JvmName(\"splitRuleNext\")\n    private tailrec fun splitRule(): ArrayList<String> { //二段匹配被调用,elementsType非空(已在首段赋值),直接按elementsType查找,比首段采用的方式更快\n\n        val end = pos //记录分隔位置\n        pos = start //重回开始，启动另一种查找\n\n        do {\n            val st = findToAny('[', '(') //查找筛选器位置\n\n            if (st == -1) {\n\n                rule += arrayOf(queue.substring(startX, end)) //压入分隔的首段规则到数组\n                pos = end + step //跳过分隔符\n\n                while (consumeTo(elementsType)) { //循环切分规则压入数组\n                    rule += queue.substring(start, pos)\n                    pos += step //跳过分隔符\n                }\n\n                rule += queue.substring(pos) //将剩余字段压入数组末尾\n\n                return rule\n            }\n\n            if (st > end) { //先匹配到st1pos，表明分隔字串不在选择器中，将选择器前分隔字串分隔的字段依次压入数组\n\n                rule += arrayListOf(queue.substring(startX, end)) //压入分隔的首段规则到数组\n                pos = end + step //跳过分隔符\n\n                while (consumeTo(elementsType) && pos < st) { //循环切分规则压入数组\n                    rule += queue.substring(start, pos)\n                    pos += step //跳过分隔符\n                }\n\n                return if (pos > st) {\n                    startX = start\n                    splitRule() //首段已匹配,但当前段匹配未完成,调用二段匹配\n                } else { //执行到此，证明后面再无分隔字符\n                    rule += queue.substring(pos) //将剩余字段压入数组末尾\n                    rule\n                }\n            }\n\n            pos = st //位置推移到筛选器处\n            val next = if (queue[pos] == '[') ']' else ')' //平衡组末尾字符\n\n            if (!chompBalanced(queue[pos], next)) throw Error(\n                queue.substring(0, start) + \"后未平衡\"\n            ) //拉出一个筛选器,不平衡则报错\n\n        } while (end > pos)\n\n        start = pos //设置开始查找筛选器位置的起始位置\n\n        return if (!consumeTo(elementsType)) {\n            rule += queue.substring(startX)\n            rule\n        } else splitRule() //递归匹配\n\n    }\n\n    /**\n     * 替换内嵌规则\n     * @param inner 起始标志,如{$.\n     * @param startStep 不属于规则部分的前置字符长度，如{$.中{不属于规则的组成部分，故startStep为1\n     * @param endStep 不属于规则部分的后置字符长度\n     * @param fr 查找到内嵌规则时，用于解析的函数\n     *\n     * */\n    fun innerRule(\n        inner: String,\n        startStep: Int = 1,\n        endStep: Int = 1,\n        fr: (String) -> String?\n    ): String {\n        val st = StringBuilder()\n\n        while (consumeTo(inner)) { //拉取成功返回true，ruleAnalyzes里的字符序列索引变量pos后移相应位置，否则返回false,且isEmpty为true\n            val posPre = pos //记录consumeTo匹配位置\n            if (chompCodeBalanced('{', '}')) {\n                val frv = fr(queue.substring(posPre + startStep, pos - endStep))\n                if (!frv.isNullOrEmpty()) {\n                    st.append(queue.substring(startX, posPre) + frv) //压入内嵌规则前的内容，及内嵌规则解析得到的字符串\n                    startX = pos //记录下次规则起点\n                    continue //获取内容成功，继续选择下个内嵌规则\n                }\n            }\n            pos += inner.length //拉出字段不平衡，inner只是个普通字串，跳到此inner后继续匹配\n        }\n\n        return if (startX == 0) \"\" else st.apply {\n            append(queue.substring(startX))\n        }.toString()\n    }\n\n    /**\n     * 替换内嵌规则\n     * @param fr 查找到内嵌规则时，用于解析的函数\n     *\n     * */\n    fun innerRule(\n        startStr: String,\n        endStr: String,\n        fr: (String) -> String?\n    ): String {\n\n        val st = StringBuilder()\n        while (consumeTo(startStr)) { //拉取成功返回true，ruleAnalyzes里的字符序列索引变量pos后移相应位置，否则返回false,且isEmpty为true\n            pos += startStr.length //跳过开始字符串\n            val posPre = pos //记录consumeTo匹配位置\n            if (consumeTo(endStr)) {\n                val frv = fr(queue.substring(posPre, pos))\n                st.append(\n                    queue.substring(\n                        startX,\n                        posPre - startStr.length\n                    ) + frv\n                ) //压入内嵌规则前的内容，及内嵌规则解析得到的字符串\n                pos += endStr.length //跳过结束字符串\n                startX = pos //记录下次规则起点\n            }\n        }\n\n        return if (startX == 0) queue else st.apply {\n            append(queue.substring(startX))\n        }.toString()\n    }\n\n    //设置平衡组函数，json或JavaScript时设置成chompCodeBalanced，否则为chompRuleBalanced\n    val chompBalanced = if (code) ::chompCodeBalanced else ::chompRuleBalanced\n\n    companion object {\n\n        /**\n         * 转义字符\n         */\n        private const val ESC = '\\\\'\n\n    }\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/model/analyzeRule/RuleData.kt",
    "content": "package io.legado.app.model.analyzeRule\n\nimport io.legado.app.utils.GSON\n\nclass RuleData : RuleDataInterface {\n\n    override val variableMap by lazy {\n        hashMapOf<String, String>()\n    }\n\n    override fun putBigVariable(key: String, value: String?) {\n        if (value == null) {\n            variableMap.remove(key)\n        } else {\n            variableMap[key] = value\n        }\n    }\n\n    override fun getBigVariable(key: String): String? {\n        return null\n    }\n\n    fun getVariable(): String? {\n        if (variableMap.isEmpty()) {\n            return null\n        }\n        return GSON.toJson(variableMap)\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/model/analyzeRule/RuleDataInterface.kt",
    "content": "package io.legado.app.model.analyzeRule\n\ninterface RuleDataInterface {\n\n    val variableMap: HashMap<String, String>\n\n    fun putVariable(key: String, value: String?): Boolean {\n        val keyExist = variableMap.contains(key)\n        return when {\n            value == null -> {\n                variableMap.remove(key)\n                putBigVariable(key, null)\n                keyExist\n            }\n\n            value.length < 10000 -> {\n                putBigVariable(key, null)\n                variableMap[key] = value\n                true\n            }\n\n            else -> {\n                variableMap.remove(key)\n                putBigVariable(key, value)\n                keyExist\n            }\n        }\n    }\n\n    fun putBigVariable(key: String, value: String?)\n\n    fun getVariable(key: String): String {\n        return variableMap[key] ?: getBigVariable(key) ?: \"\"\n    }\n\n    fun getBigVariable(key: String): String?\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/model/localBook/BaseLocalBookParse.kt",
    "content": "package io.legado.app.model.localBook\n\nimport io.legado.app.data.entities.Book\nimport io.legado.app.data.entities.BookChapter\nimport java.io.InputStream\n\n/**\n *companion object interface\n *see EpubFile.kt\n */\ninterface BaseLocalBookParse {\n\n    fun upBookInfo(book: Book)\n\n    fun getChapterList(book: Book): ArrayList<BookChapter>\n\n    fun getContent(book: Book, chapter: BookChapter): String?\n\n    fun getImage(book: Book, href: String): InputStream?\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/model/localBook/EpubFile.kt",
    "content": "package io.legado.app.model.localBook\n\nimport android.graphics.Bitmap\nimport android.graphics.BitmapFactory\nimport android.os.ParcelFileDescriptor\nimport android.text.TextUtils\nimport io.legado.app.constant.AppLog\nimport io.legado.app.data.entities.Book\nimport io.legado.app.data.entities.BookChapter\nimport io.legado.app.help.book.BookHelp\nimport io.legado.app.utils.FileUtils\nimport io.legado.app.utils.HtmlFormatter\nimport io.legado.app.utils.encodeURI\nimport io.legado.app.utils.isXml\nimport io.legado.app.utils.printOnDebug\nimport me.ag2s.epublib.domain.EpubBook\nimport me.ag2s.epublib.domain.Resource\nimport me.ag2s.epublib.domain.TOCReference\nimport me.ag2s.epublib.epub.EpubReader\nimport me.ag2s.epublib.util.zip.AndroidZipFile\nimport org.jsoup.Jsoup\nimport org.jsoup.nodes.Element\nimport org.jsoup.parser.Parser\nimport org.jsoup.select.Elements\nimport java.io.File\nimport java.io.FileOutputStream\nimport java.io.IOException\nimport java.io.InputStream\nimport java.net.URI\nimport java.net.URLDecoder\nimport java.nio.charset.Charset\n\nclass EpubFile(var book: Book) {\n\n    companion object : BaseLocalBookParse {\n        private var eFile: EpubFile? = null\n\n        @Synchronized\n        private fun getEFile(book: Book): EpubFile {\n            if (eFile == null || eFile?.book?.bookUrl != book.bookUrl) {\n                eFile = EpubFile(book)\n                //对于Epub文件默认不启用替换\n                //io.legado.app.data.entities.Book getUseReplaceRule\n                return eFile!!\n            }\n            eFile?.book = book\n            return eFile!!\n        }\n\n        @Synchronized\n        override fun getChapterList(book: Book): ArrayList<BookChapter> {\n            return getEFile(book).getChapterList()\n        }\n\n        @Synchronized\n        override fun getContent(book: Book, chapter: BookChapter): String? {\n            return getEFile(book).getContent(chapter)\n        }\n\n        @Synchronized\n        override fun getImage(\n            book: Book,\n            href: String\n        ): InputStream? {\n            return getEFile(book).getImage(href)\n        }\n\n        @Synchronized\n        override fun upBookInfo(book: Book) {\n            return getEFile(book).upBookInfo()\n        }\n\n        fun clear() {\n            eFile = null\n        }\n    }\n\n    private var mCharset: Charset = Charset.defaultCharset()\n\n    /**\n     *持有引用，避免被回收\n     */\n    private var fileDescriptor: ParcelFileDescriptor? = null\n    private var epubBook: EpubBook? = null\n        get() {\n            if (field == null || fileDescriptor == null) {\n                field = readEpub()\n            }\n            return field\n        }\n    private var epubBookContents: List<Resource>? = null\n        get() {\n            if (field == null || fileDescriptor == null) {\n                field = epubBook?.contents\n            }\n            return field\n        }\n\n    init {\n        upBookCover(true)\n    }\n\n    /**\n     * 重写epub文件解析代码，直接读出压缩包文件生成Resources给epublib，这样的好处是可以逐一修改某些文件的格式错误\n     */\n    private fun readEpub(): EpubBook? {\n        return kotlin.runCatching {\n            //ContentScheme拷贝到私有文件夹采用懒加载防止OOM\n            //val zipFile = BookHelp.getEpubFile(book)\n            BookHelp.getBookPFD(book)?.let {\n                fileDescriptor = it\n                val zipFile = AndroidZipFile(it, book.originName)\n                EpubReader().readEpubLazy(zipFile, \"utf-8\")\n            }\n\n\n        }.onFailure {\n            AppLog.put(\"读取Epub文件失败\\n${it.localizedMessage}\", it)\n            it.printOnDebug()\n        }.getOrThrow()\n    }\n\n    private fun getContent(chapter: BookChapter): String? {\n        /*获取当前章节文本*/\n        val contents = epubBookContents ?: return null\n        val nextChapterFirstResourceHref = chapter.getVariable(\"nextUrl\").substringBeforeLast(\"#\")\n        val currentChapterFirstResourceHref = chapter.url.substringBeforeLast(\"#\")\n        val isLastChapter = nextChapterFirstResourceHref.isBlank()\n        val startFragmentId = chapter.startFragmentId\n        val endFragmentId = chapter.endFragmentId\n        val elements = Elements()\n        var findChapterFirstSource = false\n        val includeNextChapterResource = !endFragmentId.isNullOrBlank()\n        /*一些书籍依靠href索引的resource会包含多个章节，需要依靠fragmentId来截取到当前章节的内容*/\n        /*注:这里较大增加了内容加载的时间，所以首次获取内容后可存储到本地cache，减少重复加载*/\n        for (res in contents) {\n            if (!findChapterFirstSource) {\n                if (currentChapterFirstResourceHref != res.href) continue\n                findChapterFirstSource = true\n                // 第一个xhtml文件\n                elements.add(\n                    getBody(res, startFragmentId, endFragmentId)\n                )\n                // 不是最后章节 且 已经遍历到下一章节的内容时停止\n                if (!isLastChapter && res.href == nextChapterFirstResourceHref) break\n                continue\n            }\n            if (nextChapterFirstResourceHref != res.href) {\n                // 其余部分\n                elements.add(getBody(res, null, null))\n            } else {\n                // 下一章节的第一个xhtml\n                if (includeNextChapterResource) {\n                    //有Fragment 则添加到上一章节\n                    elements.add(getBody(res, null, endFragmentId))\n                }\n                break\n            }\n        }\n        //title标签中的内容不需要显示在正文中，去除\n        elements.select(\"title\").remove()\n        elements.select(\"[style*=display:none]\").remove()\n        elements.select(\"img[src=\\\"cover.jpeg\\\"]\").forEachIndexed { i, it ->\n            if (i > 0) it.remove()\n        }\n        elements.select(\"img\").forEach {\n            if (it.attributesSize() <= 1) {\n                return@forEach\n            }\n            val src = it.attr(\"src\")\n            it.clearAttributes()\n            it.attr(\"src\", src)\n        }\n        val tag = Book.rubyTag\n        if (book.getDelTag(tag)) {\n            elements.select(\"rp, rt\").remove()\n        }\n        val html = elements.outerHtml()\n        return HtmlFormatter.formatKeepImg(html)\n    }\n\n    private fun getBody(res: Resource, startFragmentId: String?, endFragmentId: String?): Element {\n        /**\n         * <image width=\"1038\" height=\"670\" xlink:href=\"...\"/>\n         * ...titlepage.xhtml\n         * 大多数epub文件的封面页都会带有cover，可以一定程度上解决封面读取问题\n         */\n        if (res.href.contains(\"titlepage.xhtml\") ||\n            res.href.contains(\"cover\")\n        ) {\n            return Jsoup.parseBodyFragment(\"<img src=\\\"cover.jpeg\\\" />\")\n        }\n\n        // Jsoup可能会修复不规范的xhtml文件 解析处理后再获取\n        var bodyElement = Jsoup.parse(String(res.data, mCharset)).body()\n        bodyElement.children().run {\n            select(\"script\").remove()\n            select(\"style\").remove()\n        }\n        // 获取body对应的文本\n        var bodyString = bodyElement.outerHtml()\n        val originBodyString = bodyString\n        /**\n         * 某些xhtml文件 章节标题和内容不在一个节点或者不是兄弟节点\n         * <div>\n         *    <a class=\"mulu1>目录1</a>\n         * </div>\n         * <p>....</p>\n         * <div>\n         *    <a class=\"mulu2>目录2</a>\n         * </div>\n         * <p>....</p>\n         * 先找到FragmentId对应的Element 然后直接截取之间的html\n         */\n        if (!startFragmentId.isNullOrBlank()) {\n            bodyElement.getElementById(startFragmentId)?.outerHtml()?.let {\n                val tagStart = it.substringBefore(\"\\n\")\n                bodyString = tagStart + bodyString.substringAfter(tagStart)\n            }\n        }\n        if (!endFragmentId.isNullOrBlank() && endFragmentId != startFragmentId) {\n            bodyElement.getElementById(endFragmentId)?.outerHtml()?.let {\n                val tagStart = it.substringBefore(\"\\n\")\n                bodyString = bodyString.substringBefore(tagStart)\n            }\n        }\n        //截取过再重新解析\n        if (bodyString != originBodyString) {\n            bodyElement = Jsoup.parse(bodyString).body()\n        }\n        /*选择去除正文中的H标签，部分书籍标题与阅读标题重复待优化*/\n        val tag = Book.hTag\n        if (book.getDelTag(tag)) {\n            bodyElement.run {\n                select(\"h1, h2, h3, h4, h5, h6\").remove()\n                //getElementsMatchingOwnText(chapter.title)?.remove()\n            }\n        }\n        bodyElement.select(\"image\").forEach {\n            it.tagName(\"img\", Parser.NamespaceHtml)\n            it.attr(\"src\", it.attr(\"xlink:href\"))\n        }\n        bodyElement.select(\"img\").forEach {\n            val src = it.attr(\"src\").trim().encodeURI()\n            val href = res.href.encodeURI()\n            val resolvedHref = URLDecoder.decode(URI(href).resolve(src).toString(), \"UTF-8\")\n            it.attr(\"src\", resolvedHref)\n        }\n        return bodyElement\n    }\n\n    private fun getImage(href: String): InputStream? {\n        if (href == \"cover.jpeg\") return epubBook?.coverImage?.inputStream\n        val abHref = URLDecoder.decode(href, \"UTF-8\")\n        return epubBook?.resources?.getByHref(abHref)?.inputStream\n    }\n\n    private fun upBookCover(fastCheck: Boolean = false) {\n        try {\n            epubBook?.let {\n                if (book.coverUrl.isNullOrEmpty()) {\n                    book.coverUrl = LocalBook.getCoverPath(book)\n                }\n                if (fastCheck && File(book.coverUrl!!).exists()) {\n                    return\n                }\n                /*部分书籍DRM处理后，封面获取异常，待优化*/\n                it.coverImage?.inputStream?.use { input ->\n                    val cover = BitmapFactory.decodeStream(input)\n                    val out = FileOutputStream(FileUtils.createFileIfNotExist(book.coverUrl!!))\n                    cover.compress(Bitmap.CompressFormat.JPEG, 90, out)\n                    out.flush()\n                    out.close()\n                } ?: AppLog.putDebug(\"Epub: 封面获取为空. path: ${book.bookUrl}\")\n            }\n        } catch (e: Exception) {\n            AppLog.put(\"加载书籍封面失败\\n${e.localizedMessage}\", e)\n            e.printOnDebug()\n        }\n    }\n\n    private fun upBookInfo() {\n        if (epubBook == null) {\n            eFile = null\n            book.intro = \"书籍导入异常\"\n        } else {\n            upBookCover()\n            val metadata = epubBook!!.metadata\n            book.name = metadata.firstTitle\n            if (book.name.isEmpty()) {\n                book.name = book.originName.replace(\".epub\", \"\")\n            }\n\n            if (metadata.authors.isNotEmpty()) {\n                val author =\n                    metadata.authors[0].toString().replace(\"^, |, $\".toRegex(), \"\")\n                book.author = author\n            }\n            if (metadata.descriptions.isNotEmpty()) {\n                val desc = metadata.descriptions[0]\n                book.intro = if (desc.isXml()) {\n                    Jsoup.parse(metadata.descriptions[0]).text()\n                } else {\n                    desc\n                }\n            }\n        }\n    }\n\n    private fun getChapterList(): ArrayList<BookChapter> {\n        val chapterList = ArrayList<BookChapter>()\n        epubBook?.let { eBook ->\n            val refs = eBook.tableOfContents.tocReferences\n            if (refs == null || refs.isEmpty()) {\n                AppLog.putDebug(\"Epub: NCX file parse error, check the file: ${book.bookUrl}\")\n                val spineReferences = eBook.spine.spineReferences\n                var i = 0\n                val size = spineReferences.size\n                while (i < size) {\n                    val resource = spineReferences[i].resource\n                    var title = resource.title\n                    if (TextUtils.isEmpty(title)) {\n                        try {\n                            val doc =\n                                Jsoup.parse(String(resource.data, mCharset))\n                            val elements = doc.getElementsByTag(\"title\")\n                            if (elements.isNotEmpty()) {\n                                title = elements[0].text()\n                            }\n                        } catch (e: IOException) {\n                            e.printStackTrace()\n                        }\n                    }\n                    val chapter = BookChapter()\n                    chapter.index = i\n                    chapter.bookUrl = book.bookUrl\n                    chapter.url = resource.href\n                    if (i == 0 && title.isEmpty()) {\n                        chapter.title = \"封面\"\n                    } else {\n                        chapter.title = title\n                    }\n                    chapterList.lastOrNull()?.putVariable(\"nextUrl\", chapter.url)\n                    chapterList.add(chapter)\n                    i++\n                }\n            } else {\n                parseFirstPage(chapterList, refs)\n                parseMenu(chapterList, refs, 0)\n                for (i in chapterList.indices) {\n                    chapterList[i].index = i\n                }\n            }\n        }\n        return chapterList\n    }\n\n    /*获取书籍起始页内容。部分书籍第一章之前存在封面，引言，扉页等内容*/\n    /*tile获取不同书籍风格杂乱，格式化处理待优化*/\n    private var durIndex = 0\n    private fun parseFirstPage(\n        chapterList: ArrayList<BookChapter>,\n        refs: List<TOCReference>?\n    ) {\n        val contents = epubBook?.contents\n        if (epubBook == null || contents == null || refs == null) return\n        val firstRef = refs.firstOrNull { it.resource != null } ?: return\n        var i = 0\n        durIndex = 0\n        while (i < contents.size) {\n            val content = contents[i]\n            if (!content.mediaType.toString().contains(\"htm\")) {\n                i++\n                continue\n            }\n            /**\n             * 检索到第一章href停止\n             * completeHref可能有fragment(#id) 必须去除\n             * fix https://github.com/gedoor/legado/issues/1932\n             */\n            if (firstRef.completeHref.substringBeforeLast(\"#\") == content.href) break\n            val chapter = BookChapter()\n            var title = content.title\n            if (TextUtils.isEmpty(title)) {\n                val elements = Jsoup.parse(\n                    String(epubBook!!.resources.getByHref(content.href).data, mCharset)\n                ).getElementsByTag(\"title\")\n                title =\n                    if (elements.isNotEmpty() && elements[0].text().isNotBlank())\n                        elements[0].text()\n                    else\n                        \"--卷首--\"\n            }\n            chapter.bookUrl = book.bookUrl\n            chapter.title = title\n            chapter.url = content.href\n            chapter.startFragmentId =\n                if (content.href.substringAfter(\"#\") == content.href) null\n                else content.href.substringAfter(\"#\")\n\n            chapterList.lastOrNull()?.endFragmentId = chapter.startFragmentId\n            chapterList.lastOrNull()?.putVariable(\"nextUrl\", chapter.url)\n            chapterList.add(chapter)\n            durIndex++\n            i++\n        }\n    }\n\n    private fun parseMenu(\n        chapterList: ArrayList<BookChapter>,\n        refs: List<TOCReference>?,\n        level: Int\n    ) {\n        refs?.forEach { ref ->\n            if (ref.resource != null) {\n                val chapter = BookChapter()\n                chapter.bookUrl = book.bookUrl\n                chapter.title = ref.title\n                chapter.url = ref.completeHref\n                chapter.startFragmentId = ref.fragmentId\n                chapterList.lastOrNull()?.endFragmentId = chapter.startFragmentId\n                chapterList.lastOrNull()?.putVariable(\"nextUrl\", chapter.url)\n                chapterList.add(chapter)\n                durIndex++\n            }\n            if (ref.children != null && ref.children.isNotEmpty()) {\n                chapterList.lastOrNull()?.isVolume = true\n                parseMenu(chapterList, ref.children, level + 1)\n            }\n        }\n    }\n\n\n    protected fun finalize() {\n        fileDescriptor?.close()\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/model/localBook/LocalBook.kt",
    "content": "package io.legado.app.model.localBook\n\nimport android.net.Uri\nimport android.util.Base64\nimport androidx.documentfile.provider.DocumentFile\nimport com.script.ScriptBindings\nimport com.script.rhino.RhinoScriptEngine\nimport io.legado.app.R\nimport io.legado.app.constant.AppLog\nimport io.legado.app.constant.AppPattern\nimport io.legado.app.constant.BookType\nimport io.legado.app.data.appDb\nimport io.legado.app.data.entities.BaseSource\nimport io.legado.app.data.entities.Book\nimport io.legado.app.data.entities.BookChapter\nimport io.legado.app.exception.EmptyFileException\nimport io.legado.app.exception.NoBooksDirException\nimport io.legado.app.exception.NoStackTraceException\nimport io.legado.app.exception.TocEmptyException\nimport io.legado.app.help.AppWebDav\nimport io.legado.app.help.book.BookHelp\nimport io.legado.app.help.book.ContentProcessor\nimport io.legado.app.help.book.addType\nimport io.legado.app.help.book.archiveName\nimport io.legado.app.help.book.getArchiveUri\nimport io.legado.app.help.book.getLocalUri\nimport io.legado.app.help.book.getRemoteUrl\nimport io.legado.app.help.book.isArchive\nimport io.legado.app.help.book.isEpub\nimport io.legado.app.help.book.isMobi\nimport io.legado.app.help.book.isPdf\nimport io.legado.app.help.book.isUmd\nimport io.legado.app.help.book.removeLocalUriCache\nimport io.legado.app.help.book.simulatedTotalChapterNum\nimport io.legado.app.help.config.AppConfig\nimport io.legado.app.lib.webdav.WebDav\nimport io.legado.app.lib.webdav.WebDavException\nimport io.legado.app.model.analyzeRule.AnalyzeUrl\nimport io.legado.app.utils.ArchiveUtils\nimport io.legado.app.utils.FileDoc\nimport io.legado.app.utils.FileUtils\nimport io.legado.app.utils.GSON\nimport io.legado.app.utils.MD5Utils\nimport io.legado.app.utils.externalFiles\nimport io.legado.app.utils.fromJsonObject\nimport io.legado.app.utils.getFile\nimport io.legado.app.utils.inputStream\nimport io.legado.app.utils.isAbsUrl\nimport io.legado.app.utils.isContentScheme\nimport io.legado.app.utils.isDataUrl\nimport io.legado.app.utils.printOnDebug\nimport kotlinx.coroutines.runBlocking\nimport org.apache.commons.text.StringEscapeUtils\nimport splitties.init.appCtx\nimport java.io.ByteArrayInputStream\nimport java.io.File\nimport java.io.FileInputStream\nimport java.io.FileNotFoundException\nimport java.io.FileOutputStream\nimport java.io.InputStream\nimport java.util.regex.Pattern\nimport kotlin.coroutines.coroutineContext\n\n/**\n * 书籍文件导入 目录正文解析\n * 支持在线文件(txt epub umd 压缩文件 本地文件\n */\nobject LocalBook {\n\n    private val nameAuthorPatterns = arrayOf(\n        Pattern.compile(\"(.*?)《([^《》]+)》.*?作者：(.*)\"),\n        Pattern.compile(\"(.*?)《([^《》]+)》(.*)\"),\n        Pattern.compile(\"(^)(.+) 作者：(.+)$\"),\n        Pattern.compile(\"(^)(.+) by (.+)$\")\n    )\n\n    @Throws(FileNotFoundException::class, SecurityException::class)\n    fun getBookInputStream(book: Book): InputStream {\n        val uri = book.getLocalUri()\n        val inputStream = uri.inputStream(appCtx).getOrNull()\n            ?: let {\n                book.removeLocalUriCache()\n                val localArchiveUri = book.getArchiveUri()\n                val webDavUrl = book.getRemoteUrl()\n                if (localArchiveUri != null) {\n                    // 重新导入对应的压缩包\n                    importArchiveFile(localArchiveUri, book.originName) {\n                        it.contains(book.originName)\n                    }.firstOrNull()?.let {\n                        getBookInputStream(it)\n                    }\n                } else if (webDavUrl != null && downloadRemoteBook(book)) {\n                    // 下载远程链接\n                    getBookInputStream(book)\n                } else {\n                    null\n                }\n            }\n        if (inputStream != null) return inputStream\n        book.removeLocalUriCache()\n        throw FileNotFoundException(\"${uri.path} 文件不存在\")\n    }\n\n    fun getLastModified(book: Book): Result<Long> {\n        return kotlin.runCatching {\n            val uri = Uri.parse(book.bookUrl)\n            if (uri.isContentScheme()) {\n                return@runCatching DocumentFile.fromSingleUri(appCtx, uri)!!.lastModified()\n            }\n            val file = File(uri.path!!)\n            if (file.exists()) {\n                return@runCatching file.lastModified()\n            }\n            throw FileNotFoundException(\"${uri.path} 文件不存在\")\n        }\n    }\n\n    @Throws(TocEmptyException::class)\n    fun getChapterList(book: Book): ArrayList<BookChapter> {\n        val chapters = when {\n            book.isEpub -> {\n                EpubFile.getChapterList(book)\n            }\n\n            book.isUmd -> {\n                UmdFile.getChapterList(book)\n            }\n\n            book.isPdf -> {\n                PdfFile.getChapterList(book)\n            }\n\n            book.isMobi -> {\n                MobiFile.getChapterList(book)\n            }\n\n            else -> {\n                TextFile.getChapterList(book)\n            }\n        }\n        if (chapters.isEmpty()) {\n            throw TocEmptyException(appCtx.getString(R.string.chapter_list_empty))\n        }\n        val list = ArrayList(LinkedHashSet(chapters))\n        list.forEachIndexed { index, bookChapter ->\n            bookChapter.index = index\n            if (bookChapter.title.isEmpty()) {\n                bookChapter.title = \"无标题章节\"\n            }\n        }\n        val replaceRules = ContentProcessor.get(book).getTitleReplaceRules()\n        book.durChapterTitle = list.getOrElse(book.durChapterIndex) { list.last() }\n            .getDisplayTitle(replaceRules, book.getUseReplaceRule())\n        book.latestChapterTitle =\n            list.getOrElse(book.simulatedTotalChapterNum() - 1) { list.last() }\n                .getDisplayTitle(replaceRules, book.getUseReplaceRule())\n        book.totalChapterNum = list.size\n        book.latestChapterTime = System.currentTimeMillis()\n        return list\n    }\n\n    fun getContent(book: Book, chapter: BookChapter): String? {\n        var content = try {\n            when {\n                book.isEpub -> {\n                    EpubFile.getContent(book, chapter)\n                }\n\n                book.isUmd -> {\n                    UmdFile.getContent(book, chapter)\n                }\n\n                book.isPdf -> {\n                    PdfFile.getContent(book, chapter)\n                }\n\n                book.isMobi -> {\n                    MobiFile.getContent(book, chapter)\n                }\n\n                else -> {\n                    TextFile.getContent(book, chapter)\n                }\n            }\n        } catch (e: Exception) {\n            e.printOnDebug()\n            AppLog.put(\"获取本地书籍内容失败\\n${e.localizedMessage}\", e)\n            \"获取本地书籍内容失败\\n${e.localizedMessage}\"\n        }\n        if (book.isEpub) {\n            content ?: return null\n            if (content.indexOf('&') > -1) {\n                content = content.replace(\"&lt;img\", \"&lt; img\", true)\n                return StringEscapeUtils.unescapeHtml4(content)\n            }\n        }\n\n        if (content.isNullOrEmpty()) {\n            return null\n        }\n\n        return content\n    }\n\n    fun getCoverPath(book: Book): String {\n        return getCoverPath(book.bookUrl)\n    }\n\n    private fun getCoverPath(bookUrl: String): String {\n        return FileUtils.getPath(\n            appCtx.externalFiles,\n            \"covers\",\n            \"${MD5Utils.md5Encode16(bookUrl)}.jpg\"\n        )\n    }\n\n    /**\n     * 下载在线的文件并自动导入到阅读（txt umd epub)\n     */\n    suspend fun importFileOnLine(\n        str: String,\n        fileName: String,\n        source: BaseSource? = null,\n    ): Book {\n        return importFile(saveBookFile(str, fileName, source))\n    }\n\n    /**\n     * 导入本地文件\n     */\n    fun importFile(uri: Uri): Book {\n        val bookUrl: String\n        //updateTime变量不要修改,否则会导致读取不到缓存\n        val (fileName, _, _, updateTime, _) = FileDoc.fromUri(uri, false).apply {\n            if (size == 0L) throw EmptyFileException(\"Unexpected empty File\")\n\n            bookUrl = toString()\n        }\n        var book = appDb.bookDao.getBook(bookUrl)\n        if (book == null) {\n            val nameAuthor = analyzeNameAuthor(fileName)\n            book = Book(\n                type = BookType.text or BookType.local,\n                bookUrl = bookUrl,\n                name = nameAuthor.first,\n                author = nameAuthor.second,\n                originName = fileName,\n                latestChapterTime = updateTime,\n                order = appDb.bookDao.minOrder - 1\n            )\n            upBookInfo(book)\n            appDb.bookDao.insert(book)\n        } else {\n            deleteBook(book, false)\n            upBookInfo(book)\n            // 触发 isLocalModified\n            book.latestChapterTime = 0\n            //已有书籍说明是更新,删除原有目录\n            appDb.bookChapterDao.delByBook(bookUrl)\n        }\n        return book\n    }\n\n    fun upBookInfo(book: Book) {\n        when {\n            book.isEpub -> EpubFile.upBookInfo(book)\n            book.isUmd -> UmdFile.upBookInfo(book)\n            book.isPdf -> PdfFile.upBookInfo(book)\n            book.isMobi -> MobiFile.upBookInfo(book)\n        }\n    }\n\n    /* 导入压缩包内的书籍 */\n    fun importArchiveFile(\n        archiveFileUri: Uri,\n        saveFileName: String? = null,\n        filter: ((String) -> Boolean)? = null\n    ): List<Book> {\n        val archiveFileDoc = FileDoc.fromUri(archiveFileUri, false)\n        val files = ArchiveUtils.deCompress(archiveFileDoc, filter = filter)\n        if (files.isEmpty()) {\n            throw NoStackTraceException(appCtx.getString(R.string.unsupport_archivefile_entry))\n        }\n        return files.map {\n            saveBookFile(FileInputStream(it), saveFileName ?: it.name).let { uri ->\n                importFile(uri).apply {\n                    //附加压缩包名称 以便解压文件被删后再解压\n                    origin = \"${BookType.localTag}::${archiveFileDoc.name}\"\n                    addType(BookType.archive)\n                    save()\n                }\n            }\n        }\n    }\n\n    /* 批量导入 支持自动导入压缩包的支持书籍 */\n    fun importFiles(uri: Uri): List<Book> {\n        val books = mutableListOf<Book>()\n        val fileDoc = FileDoc.fromUri(uri, false)\n        if (ArchiveUtils.isArchive(fileDoc.name)) {\n            books.addAll(\n                importArchiveFile(uri) {\n                    it.matches(AppPattern.bookFileRegex)\n                }\n            )\n        } else {\n            books.add(importFile(uri))\n        }\n        return books\n    }\n\n    fun importFiles(uris: List<Uri>) {\n        var errorCount = 0\n        uris.forEach { uri ->\n            val fileDoc = FileDoc.fromUri(uri, false)\n            kotlin.runCatching {\n                if (ArchiveUtils.isArchive(fileDoc.name)) {\n                    importArchiveFile(uri) {\n                        it.matches(AppPattern.bookFileRegex)\n                    }\n                } else {\n                    importFile(uri)\n                }\n            }.onFailure {\n                AppLog.put(\"ImportFile Error:\\nFile $fileDoc\\n${it.localizedMessage}\", it)\n                errorCount += 1\n            }\n        }\n        if (errorCount == uris.size) {\n            throw NoStackTraceException(\"ImportFiles Error:\\nAll input files occur error\")\n        }\n    }\n\n    /**\n     * 从文件分析书籍必要信息（书名 作者等）\n     */\n    private fun analyzeNameAuthor(fileName: String): Pair<String, String> {\n        val tempFileName = fileName.substringBeforeLast(\".\")\n        var name = \"\"\n        var author = \"\"\n        if (!AppConfig.bookImportFileName.isNullOrBlank()) {\n            try {\n                //在用户脚本后添加捕获author、name的代码，只要脚本中author、name有值就会被捕获\n                val js =\n                    AppConfig.bookImportFileName + \"\\nJSON.stringify({author:author,name:name})\"\n                //在脚本中定义如何分解文件名成书名、作者名\n                val jsonStr = RhinoScriptEngine.run {\n                    val bindings = ScriptBindings()\n                    bindings[\"src\"] = tempFileName\n                    eval(js, bindings)\n                }.toString()\n                val bookMess = GSON.fromJsonObject<HashMap<String, String>>(jsonStr)\n                    .getOrThrow()\n                name = bookMess[\"name\"] ?: \"\"\n                author = bookMess[\"author\"]?.takeIf { it.length != tempFileName.length } ?: \"\"\n            } catch (e: Exception) {\n                AppLog.put(\"执行导入文件名规则出错\\n${e.localizedMessage}\", e)\n            }\n        }\n        if (name.isBlank()) {\n            for (pattern in nameAuthorPatterns) {\n                pattern.matcher(tempFileName).takeIf { it.find() }?.run {\n                    name = group(2)!!\n                    val group1 = group(1) ?: \"\"\n                    val group3 = group(3) ?: \"\"\n                    author = BookHelp.formatBookAuthor(group1 + group3)\n                    return Pair(name, author)\n                }\n            }\n            name = BookHelp.formatBookName(tempFileName)\n            author = BookHelp.formatBookAuthor(tempFileName.replace(name, \"\"))\n                .takeIf { it.length != tempFileName.length } ?: \"\"\n        }\n        return Pair(name, author)\n    }\n\n    fun deleteBook(book: Book, deleteOriginal: Boolean) {\n        kotlin.runCatching {\n            BookHelp.clearCache(book)\n            if (!book.coverUrl.isNullOrEmpty()) {\n                FileUtils.delete(book.coverUrl!!)\n            }\n            if (deleteOriginal) {\n                if (book.bookUrl.isContentScheme()) {\n                    val uri = Uri.parse(book.bookUrl)\n                    DocumentFile.fromSingleUri(appCtx, uri)?.delete()\n                } else {\n                    FileUtils.delete(book.bookUrl)\n                }\n            }\n        }\n    }\n\n    /**\n     * 下载在线的文件\n     */\n    suspend fun saveBookFile(\n        str: String,\n        fileName: String,\n        source: BaseSource? = null,\n    ): Uri {\n        AppConfig.defaultBookTreeUri\n            ?: throw NoBooksDirException()\n        val inputStream = when {\n            str.isAbsUrl() -> AnalyzeUrl(\n                str, source = source, callTimeout = 0,\n                coroutineContext = coroutineContext\n            ).getInputStreamAwait()\n\n            str.isDataUrl() -> ByteArrayInputStream(\n                Base64.decode(\n                    str.substringAfter(\"base64,\"),\n                    Base64.DEFAULT\n                )\n            )\n\n            else -> throw NoStackTraceException(\"在线导入书籍支持http/https/DataURL\")\n        }\n        return saveBookFile(inputStream, fileName)\n    }\n\n    @Throws(SecurityException::class)\n    fun saveBookFile(\n        inputStream: InputStream,\n        fileName: String\n    ): Uri {\n        inputStream.use {\n            val defaultBookTreeUri = AppConfig.defaultBookTreeUri\n            if (defaultBookTreeUri.isNullOrBlank()) throw NoBooksDirException()\n            val treeUri = Uri.parse(defaultBookTreeUri)\n            return if (treeUri.isContentScheme()) {\n                val treeDoc = DocumentFile.fromTreeUri(appCtx, treeUri)\n                var doc = treeDoc!!.findFile(fileName)\n                if (doc == null) {\n                    doc = treeDoc.createFile(FileUtils.getMimeType(fileName), fileName)\n                        ?: throw SecurityException(\"请重新设置书籍保存位置\\nPermission Denial\")\n                }\n                appCtx.contentResolver.openOutputStream(doc.uri)!!.use { oStream ->\n                    it.copyTo(oStream)\n                }\n                doc.uri\n            } else {\n                try {\n                    val treeFile = File(treeUri.path!!)\n                    val file = treeFile.getFile(fileName)\n                    FileOutputStream(file).use { oStream ->\n                        it.copyTo(oStream)\n                    }\n                    Uri.fromFile(file)\n                } catch (e: FileNotFoundException) {\n                    throw SecurityException(\"请重新设置书籍保存位置\\nPermission Denial\\n$e\").apply {\n                        addSuppressed(e)\n                    }\n                }\n            }\n        }\n    }\n\n    fun isOnBookShelf(\n        fileName: String\n    ): Boolean {\n        return appDb.bookDao.hasFile(fileName) == true\n    }\n\n    //文件类书源 合并在线书籍信息 在线 > 本地\n    fun mergeBook(localBook: Book, onLineBook: Book?): Book {\n        onLineBook ?: return localBook\n        localBook.name = onLineBook.name.ifBlank { localBook.name }\n        localBook.author = onLineBook.author.ifBlank { localBook.author }\n        localBook.coverUrl = onLineBook.coverUrl\n        localBook.intro =\n            if (onLineBook.intro.isNullOrBlank()) localBook.intro else onLineBook.intro\n        localBook.save()\n        return localBook\n    }\n\n    //下载book对应的远程文件 并更新Book\n    private fun downloadRemoteBook(localBook: Book): Boolean {\n        val webDavUrl = localBook.getRemoteUrl()\n        if (webDavUrl.isNullOrBlank()) throw NoStackTraceException(\"Book file is not webDav File\")\n        try {\n            AppConfig.defaultBookTreeUri\n                ?: throw NoBooksDirException()\n            // 兼容旧版链接\n            val webdav: WebDav = kotlin.runCatching {\n                WebDav.fromPath(webDavUrl)\n            }.getOrElse {\n                AppWebDav.authorization?.let { WebDav(webDavUrl, it) }\n                    ?: throw WebDavException(\"Unexpected defaultBookWebDav\")\n            }\n            val inputStream = runBlocking {\n                webdav.downloadInputStream()\n            }\n            inputStream.use {\n                if (localBook.isArchive) {\n                    // 压缩包\n                    val archiveUri = saveBookFile(it, localBook.archiveName)\n                    val newBook = importArchiveFile(archiveUri, localBook.originName) { name ->\n                        name.contains(localBook.originName)\n                    }.first()\n                    localBook.origin = newBook.origin\n                    localBook.bookUrl = newBook.bookUrl\n                } else {\n                    // txt epub pdf umd\n                    val fileUri = saveBookFile(it, localBook.originName)\n                    localBook.bookUrl = FileDoc.fromUri(fileUri, false).toString()\n                    localBook.save()\n                }\n            }\n            return true\n        } catch (e: Exception) {\n            e.printOnDebug()\n            AppLog.put(\"自动下载webDav书籍失败\", e)\n            return false\n        }\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/model/localBook/MobiFile.kt",
    "content": "package io.legado.app.model.localBook\n\nimport android.graphics.Bitmap\nimport android.graphics.BitmapFactory\nimport android.os.ParcelFileDescriptor\nimport io.legado.app.constant.AppLog\nimport io.legado.app.data.entities.Book\nimport io.legado.app.data.entities.BookChapter\nimport io.legado.app.help.book.BookHelp\nimport io.legado.app.lib.mobi.KF6Book\nimport io.legado.app.lib.mobi.KF8Book\nimport io.legado.app.lib.mobi.MobiBook\nimport io.legado.app.lib.mobi.MobiReader\nimport io.legado.app.lib.mobi.entities.TOC\nimport io.legado.app.utils.FileUtils\nimport io.legado.app.utils.HtmlFormatter\nimport io.legado.app.utils.printOnDebug\nimport org.jsoup.Jsoup\nimport java.io.File\nimport java.io.FileOutputStream\nimport java.io.InputStream\n\nclass MobiFile(var book: Book) {\n\n    companion object : BaseLocalBookParse {\n        private var mFile: MobiFile? = null\n        private val xmlDeclarationRegex = \"<\\\\?xml[^>]*>\".toRegex()\n        private val doctypeDeclarationRegex = \"<!DOCTYPE[^>]*>\".toRegex()\n\n        @Synchronized\n        private fun getMFile(book: Book): MobiFile {\n            if (mFile == null || mFile?.book?.bookUrl != book.bookUrl) {\n                mFile = MobiFile(book)\n                return mFile!!\n            }\n            mFile?.book = book\n            return mFile!!\n        }\n\n        @Synchronized\n        override fun getChapterList(book: Book): ArrayList<BookChapter> {\n            return getMFile(book).getChapterList()\n        }\n\n        @Synchronized\n        override fun getContent(book: Book, chapter: BookChapter): String? {\n            return getMFile(book).getContent(chapter)\n        }\n\n        @Synchronized\n        override fun getImage(book: Book, href: String): InputStream? {\n            return getMFile(book).getImage(href)\n        }\n\n        @Synchronized\n        override fun upBookInfo(book: Book) {\n            return getMFile(book).upBookInfo()\n        }\n\n        fun clear() {\n            mFile = null\n        }\n    }\n\n    private var fileDescriptor: ParcelFileDescriptor? = null\n    private var mobiBook: MobiBook? = null\n        get() {\n            if (field == null || fileDescriptor == null) {\n                field = readMobi()\n            }\n            return field\n        }\n\n    init {\n        upBookCover(true)\n    }\n\n    private fun readMobi(): MobiBook? {\n        return kotlin.runCatching {\n            BookHelp.getBookPFD(book)?.let {\n                fileDescriptor = it\n                MobiReader().readMobi(it)\n            }\n        }.onFailure {\n            AppLog.put(\"读取Mobi文件失败\\n${it.localizedMessage}\", it)\n            it.printOnDebug()\n        }.getOrThrow()\n    }\n\n    private fun getChapterList(): ArrayList<BookChapter> {\n        return when (val book = mobiBook) {\n            is KF8Book -> getChapterListKF8(book)\n            is KF6Book -> getChapterListKF6(book)\n            else -> error(\"impossible condition\")\n        }\n    }\n\n    private fun getChapterListKF6(kF6Book: KF6Book): ArrayList<BookChapter> {\n        val chapterList = arrayListOf<BookChapter>()\n        val toc = kF6Book.toc\n\n        if (kF6Book.sectionIdMap[0] == null) {\n            val section = kF6Book.sections.firstOrNull()\n            if (section != null) {\n                val chapter = BookChapter()\n                val content = kF6Book.getSectionText(section)\n                val soup = Jsoup.parse(content)\n                val title = soup.getElementsByTag(\"title\").first()?.text() ?: \"卷首\"\n                chapter.bookUrl = book.bookUrl\n                chapter.title = title\n                chapter.url = \"0:\" + section.href\n                chapterList.add(chapter)\n            }\n        }\n\n        fun append(ref: TOC) {\n            val chapter = BookChapter()\n            chapter.bookUrl = book.bookUrl\n            chapter.title = ref.label\n            chapter.url = \"${chapterList.size}:${ref.href}\"\n            chapter.isVolume = ref.subitems != null\n            val lastChapter = chapterList.lastOrNull()\n            if (lastChapter != null &&\n                lastChapter.isVolume &&\n                lastChapter.url.substringAfter(\":\") == chapter.url.substringAfter(\":\")\n            ) {\n                lastChapter.url = \"skip:\" + lastChapter.url\n            }\n            lastChapter?.putVariable(\"nextUrl\", chapter.url)\n            chapterList.add(chapter)\n            ref.subitems?.forEach(::append)\n        }\n\n        toc?.forEach(::append)\n\n        return chapterList\n    }\n\n    private fun getChapterListKF8(kf8Book: KF8Book): ArrayList<BookChapter> {\n        val chapterList = arrayListOf<BookChapter>()\n        val toc = kf8Book.toc\n\n        if (kf8Book.sectionIdMap[0] == null) {\n            val section = kf8Book.sections.firstOrNull { it.href.isNotEmpty() }\n            if (section != null) {\n                val chapter = BookChapter()\n                val content = kf8Book.getSectionText(section)\n                val soup = Jsoup.parse(content)\n                val title = soup.getElementsByTag(\"title\").first()?.text() ?: \"卷首\"\n                chapter.bookUrl = book.bookUrl\n                chapter.title = title\n                chapter.url = \"0:\" + section.href\n                chapterList.add(chapter)\n            }\n        }\n\n        fun append(ref: TOC) {\n            val chapter = BookChapter()\n            chapter.bookUrl = book.bookUrl\n            chapter.title = ref.label\n            chapter.url = \"${chapterList.size}:${ref.href}\"\n            chapter.isVolume = ref.subitems != null\n            val lastChapter = chapterList.lastOrNull()\n            if (lastChapter != null &&\n                lastChapter.isVolume &&\n                lastChapter.url.substringAfter(\":\") == chapter.url.substringAfter(\":\")\n            ) {\n                lastChapter.url = \"skip:\" + lastChapter.url\n            }\n            lastChapter?.putVariable(\"nextUrl\", chapter.url)\n            chapterList.add(chapter)\n            ref.subitems?.forEach(::append)\n        }\n\n        toc?.forEach(::append)\n\n        return chapterList\n    }\n\n    private fun getContent(chapter: BookChapter): String? {\n        return when (val book = mobiBook) {\n            is KF8Book -> getContentKF8(book, chapter)\n            is KF6Book -> getContentKF6(book, chapter)\n            else -> error(\"impossible condition\")\n        }\n    }\n\n    private fun getContentKF6(kf6Book: KF6Book, chapter: BookChapter): String? {\n        if (chapter.isVolume && chapter.url.startsWith(\"skip:\")) return \"\"\n        var section = kf6Book.getSectionByHref(chapter.url) ?: return null\n        val nextSectionHref = chapter.getVariable(\"nextUrl\")\n\n        val sb = StringBuilder()\n        sb.append(kf6Book.getSectionText(section))\n        while (true) {\n            section = section.next ?: break\n            if (section.href == nextSectionHref) {\n                break\n            }\n            if (kf6Book.sectionIdMap[section.index] != null) {\n                break\n            }\n            sb.append(kf6Book.getSectionText(section))\n        }\n\n        val soup = Jsoup.parse(sb.toString())\n\n        soup.select(\"title\").remove()\n        soup.select(\"[style*=display:none]\").remove()\n        soup.select(\"img[recindex]\").forEach {\n            val recindex = it.attr(\"recindex\")\n            it.clearAttributes()\n            it.attr(\"src\", \"recindex:$recindex\")\n        }\n\n        return format(soup.outerHtml())\n    }\n\n    private fun getContentKF8(kf8Book: KF8Book, chapter: BookChapter): String? {\n        if (chapter.isVolume && chapter.url.startsWith(\"skip:\")) return \"\"\n        var section = kf8Book.getSectionByHref(chapter.url) ?: return null\n        val nextSectionHref = chapter.getVariable(\"nextUrl\")\n        val nextPos = kf8Book.parsePosURI(nextSectionHref)\n\n        val sb = StringBuilder()\n        sb.append(kf8Book.getTextByHref(chapter.url, nextSectionHref))\n        while (true) {\n            if (nextPos != null && section.frags.any { it.index == nextPos.fid }) {\n                break\n            }\n            section = section.next ?: break\n            if (!section.linear) {\n                continue\n            }\n            if (section.href == nextSectionHref) {\n                break\n            }\n            if (kf8Book.sectionIdMap[section.index] != null) {\n                break\n            }\n            sb.append(kf8Book.getSectionText(section))\n        }\n\n        val soup = Jsoup.parse(sb.toString())\n\n        soup.select(\"title\").remove()\n        soup.select(\"[style*=display:none]\").remove()\n\n        return format(soup.outerHtml())\n    }\n\n    private fun format(html: String): String {\n        return HtmlFormatter.formatKeepImg(html)\n            .replace(xmlDeclarationRegex, \"\")\n            .replace(doctypeDeclarationRegex, \"\")\n    }\n\n    private fun getImage(href: String): InputStream? {\n        return when (val book = mobiBook) {\n            is KF8Book -> getImageKF8(book, href)\n            is KF6Book -> getImageKF6(book, href)\n            else -> error(\"impossible condition\")\n        }\n    }\n\n    private fun getImageKF6(kf6Book: KF6Book, href: String): InputStream? {\n        return kf6Book.getResourceByHref(href)?.inputStream()\n    }\n\n    private fun getImageKF8(kf8Book: KF8Book, href: String): InputStream? {\n        return kf8Book.getResourceByHref(href)?.inputStream()\n    }\n\n    private fun upBookCover(fastCheck: Boolean = false) {\n        try {\n            mobiBook?.let {\n                if (book.coverUrl.isNullOrEmpty()) {\n                    book.coverUrl = LocalBook.getCoverPath(book)\n                }\n                if (fastCheck && File(book.coverUrl!!).exists()) {\n                    return\n                }\n                it.getCover()?.let { bytes ->\n                    val bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.size)\n                    val file = FileUtils.createFileIfNotExist(book.coverUrl!!)\n                    FileOutputStream(file).use { out ->\n                        bitmap.compress(Bitmap.CompressFormat.JPEG, 90, out)\n                        out.flush()\n                    }\n                }\n            }\n        } catch (e: Exception) {\n            AppLog.put(\"加载书籍封面失败\\n${e.localizedMessage}\", e)\n            e.printOnDebug()\n        }\n    }\n\n    private fun upBookInfo() {\n        if (mobiBook == null) {\n            mFile = null\n            book.intro = \"书籍导入异常\"\n        } else {\n            upBookCover()\n            val metadata = mobiBook!!.metadata\n            book.name = metadata.title\n            if (book.name.isEmpty()) {\n                book.name = book.originName.replace(\"(?i)\\\\.(mobi|azw3)$\".toRegex(), \"\")\n            }\n            if (metadata.author.isNotEmpty()) {\n                book.author = metadata.author.first()\n            }\n            if (metadata.description.isNotBlank()) {\n                book.intro = HtmlFormatter.format(metadata.description)\n            }\n        }\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/model/localBook/PdfFile.kt",
    "content": "package io.legado.app.model.localBook\n\nimport android.graphics.Bitmap\nimport android.graphics.Color\nimport android.graphics.pdf.PdfRenderer\nimport android.os.ParcelFileDescriptor\nimport androidx.core.graphics.createBitmap\nimport io.legado.app.constant.AppLog\nimport io.legado.app.data.entities.Book\nimport io.legado.app.data.entities.BookChapter\nimport io.legado.app.help.book.getLocalUri\nimport io.legado.app.utils.BitmapUtils\nimport io.legado.app.utils.FileUtils\nimport io.legado.app.utils.SystemUtils\nimport io.legado.app.utils.isContentScheme\nimport io.legado.app.utils.printOnDebug\nimport splitties.init.appCtx\nimport java.io.File\nimport java.io.FileOutputStream\nimport java.io.InputStream\nimport kotlin.math.ceil\n\n\nclass PdfFile(var book: Book) {\n    companion object : BaseLocalBookParse {\n        private var pFile: PdfFile? = null\n\n        /**\n         * pdf分页尺寸\n         */\n        const val PAGE_SIZE = 10\n\n        @Synchronized\n        private fun getPFile(book: Book): PdfFile {\n            if (pFile == null || pFile?.book?.bookUrl != book.bookUrl) {\n                pFile = PdfFile(book)\n                return pFile!!\n            }\n            pFile?.book = book\n            return pFile!!\n        }\n\n        @Synchronized\n        override fun upBookInfo(book: Book) {\n            getPFile(book).upBookInfo()\n        }\n\n        @Synchronized\n        override fun getChapterList(book: Book): ArrayList<BookChapter> {\n            return getPFile(book).getChapterList()\n        }\n\n        @Synchronized\n        override fun getContent(book: Book, chapter: BookChapter): String? {\n            return getPFile(book).getContent(chapter)\n        }\n\n        @Synchronized\n        override fun getImage(book: Book, href: String): InputStream? {\n            return getPFile(book).getImage(href)\n        }\n\n    }\n\n    /**\n     *持有引用，避免被回收\n     */\n    private var fileDescriptor: ParcelFileDescriptor? = null\n    private var pdfRenderer: PdfRenderer? = null\n        get() {\n            if (field != null && fileDescriptor != null) {\n                return field\n            }\n            field = readPdf()\n            return field\n        }\n\n    init {\n        upBookCover(true)\n    }\n\n    /**\n     * 读取PDF文件\n     *\n     * @return\n     */\n    private fun readPdf(): PdfRenderer? {\n        val uri = book.getLocalUri()\n        if (uri.isContentScheme()) {\n            fileDescriptor = appCtx.contentResolver.openFileDescriptor(uri, \"r\")?.also {\n                pdfRenderer = PdfRenderer(it)\n            }\n        } else {\n            fileDescriptor =\n                ParcelFileDescriptor.open(File(uri.path!!), ParcelFileDescriptor.MODE_READ_ONLY)\n                    ?.also {\n                        pdfRenderer = PdfRenderer(it)\n                    }\n        }\n        return pdfRenderer\n    }\n\n    /**\n     * 关闭pdf文件\n     *\n     */\n    private fun closePdf() {\n        pdfRenderer?.close()\n        fileDescriptor?.close()\n    }\n\n\n    /**\n     * 渲染PDF页面\n     * 根据index打开pdf页面,并渲染到Bitmap\n     *\n     * @param renderer\n     * @param index\n     * @return\n     */\n    private fun openPdfPage(renderer: PdfRenderer, index: Int): Bitmap? {\n        if (index >= renderer.pageCount) {\n            return null\n        }\n        return renderer.openPage(index).use { page ->\n            createBitmap(\n                SystemUtils.screenWidthPx,\n                (SystemUtils.screenWidthPx.toDouble() * page.height / page.width).toInt()\n            ).apply {\n                this.eraseColor(Color.WHITE)\n                page.render(this, null, null, PdfRenderer.Page.RENDER_MODE_FOR_DISPLAY)\n            }\n        }\n\n    }\n\n    private fun getContent(chapter: BookChapter): String? =\n        if (pdfRenderer == null) {\n            null\n        } else {\n            pdfRenderer?.let { renderer ->\n\n                buildString {\n                    val start = chapter.index * PAGE_SIZE\n                    val end = ((chapter.index + 1) * PAGE_SIZE).coerceAtMost(renderer.pageCount)\n                    (start until end).forEach {\n                        append(\"<img src=\").append('\"').append(it).append('\"').append(\" >\")\n                            .append('\\n')\n                    }\n\n                }\n\n            }\n        }\n\n\n    private fun getImage(href: String): InputStream? {\n        if (pdfRenderer == null) {\n            return null\n        }\n        return try {\n            val index = href.toInt()\n            val bitmap = openPdfPage(pdfRenderer!!, index)\n            if (bitmap != null) {\n                BitmapUtils.toInputStream(bitmap)\n            } else {\n                null\n            }\n\n        } catch (_: Exception) {\n            return null\n        }\n    }\n\n    private fun getChapterList(): ArrayList<BookChapter> {\n        val chapterList = ArrayList<BookChapter>()\n\n        pdfRenderer?.let { renderer ->\n            if (renderer.pageCount > 0) {\n                val chapterCount = ceil((renderer.pageCount.toDouble() / PAGE_SIZE)).toInt()\n                (0 until chapterCount).forEach {\n                    val chapter = BookChapter()\n                    chapter.index = it\n                    chapter.bookUrl = book.bookUrl\n                    chapter.title = \"分段_${it}\"\n                    chapter.url = \"pdf_${it}\"\n                    chapterList.add(chapter)\n                }\n            }\n        }\n        return chapterList\n    }\n\n    private fun upBookCover(fastCheck: Boolean = false) {\n        try {\n            pdfRenderer?.let { renderer ->\n                if (book.coverUrl.isNullOrEmpty()) {\n                    book.coverUrl = LocalBook.getCoverPath(book)\n                }\n                if (fastCheck && File(book.coverUrl!!).exists()) {\n                    return\n                }\n                FileOutputStream(FileUtils.createFileIfNotExist(book.coverUrl!!)).use { out ->\n                    openPdfPage(renderer, 0)?.compress(Bitmap.CompressFormat.JPEG, 90, out)\n                    out.flush()\n                }\n            }\n        } catch (e: Exception) {\n            AppLog.put(\"加载书籍封面失败\\n${e.localizedMessage}\", e)\n            e.printOnDebug()\n        }\n    }\n\n    private fun upBookInfo() {\n        if (pdfRenderer == null) {\n            pFile = null\n            book.intro = \"书籍导入异常\"\n        } else {\n            upBookCover()\n            if (book.name.isEmpty()) {\n                book.name = book.originName.replace(\".pdf\", \"\")\n            }\n        }\n\n    }\n\n    protected fun finalize() {\n        closePdf()\n    }\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/model/localBook/README.md",
    "content": "# 书籍文件导入解析\n\n* BaseLocalBookParse.kt 本地书籍解析接口\n* LocalBook.kt 导入解析总入口\n* TextFile.kt 解析txt\n* EpubFile.kt 解析epub\n* PdfFile.kt 解析pdf 纯图片形式\n* UmdFile.kt 解析umd"
  },
  {
    "path": "app/src/main/java/io/legado/app/model/localBook/TextFile.kt",
    "content": "package io.legado.app.model.localBook\n\nimport io.legado.app.constant.AppLog\nimport io.legado.app.data.appDb\nimport io.legado.app.data.entities.Book\nimport io.legado.app.data.entities.BookChapter\nimport io.legado.app.data.entities.TxtTocRule\nimport io.legado.app.exception.EmptyFileException\nimport io.legado.app.help.DefaultData\nimport io.legado.app.help.book.isLocalModified\nimport io.legado.app.utils.EncodingDetect\nimport io.legado.app.utils.MD5Utils\nimport io.legado.app.utils.StringUtils\nimport io.legado.app.utils.Utf8BomUtils\nimport java.io.FileNotFoundException\nimport java.nio.charset.Charset\nimport java.util.regex.Matcher\nimport java.util.regex.Pattern\nimport java.util.regex.PatternSyntaxException\nimport kotlin.math.min\n\nclass TextFile(private var book: Book) {\n\n    @Suppress(\"ConstPropertyName\")\n    companion object {\n        private val padRegex = \"^[\\\\n\\\\s]+\".toRegex()\n        private const val txtBufferSize = 8 * 1024 * 1024\n        private var textFile: TextFile? = null\n\n        @Synchronized\n        private fun getTextFile(book: Book): TextFile {\n            if (textFile == null || textFile?.book?.bookUrl != book.bookUrl || book.isLocalModified()) {\n                textFile = TextFile(book)\n                return textFile!!\n            }\n            textFile?.book = book\n            return textFile!!\n        }\n\n        @Throws(FileNotFoundException::class)\n        fun getChapterList(book: Book): ArrayList<BookChapter> {\n            return getTextFile(book).getChapterList()\n        }\n\n        @Synchronized\n        @Throws(FileNotFoundException::class)\n        fun getContent(book: Book, bookChapter: BookChapter): String {\n            return getTextFile(book).getContent(bookChapter)\n        }\n\n        fun clear() {\n            textFile = null\n        }\n\n    }\n\n    private val blank: Byte = 0x0a\n\n    //默认从文件中获取数据的长度\n    private val bufferSize = 512000\n\n    //没有标题的时候，每个章节的最大长度\n    private val maxLengthWithNoToc = 10 * 1024\n\n    //使用正则划分目录，每个章节的最大允许长度\n    private val maxLengthWithToc = 102400\n\n    private var charset: Charset = book.fileCharset()\n\n    private var txtBuffer: ByteArray? = null\n    private var bufferStart = -1L\n    private var bufferEnd = -1L\n\n    /**\n     * 获取目录\n     */\n    @Throws(FileNotFoundException::class, SecurityException::class, EmptyFileException::class)\n    fun getChapterList(): ArrayList<BookChapter> {\n        val modified = book.isLocalModified()\n        if (book.charset == null || book.tocUrl.isBlank() || modified) {\n            LocalBook.getBookInputStream(book).use { bis ->\n                val buffer = ByteArray(bufferSize)\n                val length = bis.read(buffer)\n                if (length == -1) throw EmptyFileException(\"Unexpected Empty Txt File\")\n                if (book.charset.isNullOrBlank() || modified) {\n                    book.charset = EncodingDetect.getEncode(buffer.copyOf(length))\n                }\n                charset = book.fileCharset()\n                if (book.tocUrl.isBlank() || modified) {\n                    val blockContent = String(buffer, 0, length, charset)\n                    book.tocUrl = getTocRule(blockContent)?.pattern() ?: \"\"\n                }\n            }\n        }\n        val (toc, wordCount) = analyze(book.tocUrl.toPattern(Pattern.MULTILINE))\n        book.wordCount = StringUtils.wordCountFormat(wordCount)\n        toc.forEachIndexed { index, bookChapter ->\n            bookChapter.index = index\n            bookChapter.bookUrl = book.bookUrl\n            bookChapter.url = MD5Utils.md5Encode16(book.originName + index + bookChapter.title)\n        }\n        return toc\n    }\n\n    fun getContent(chapter: BookChapter): String {\n        val start = chapter.start!!\n        val end = chapter.end!!\n        if (txtBuffer == null || start > bufferEnd || end < bufferStart) {\n            LocalBook.getBookInputStream(book).use { bis ->\n                bufferStart = txtBufferSize * (start / txtBufferSize)\n                txtBuffer = ByteArray(min(txtBufferSize, bis.available() - bufferStart.toInt()))\n                bufferEnd = bufferStart + txtBuffer!!.size\n                bis.skip(bufferStart)\n                bis.read(txtBuffer)\n            }\n        }\n\n        val count = (end - start).toInt()\n        val buffer = ByteArray(count)\n\n        @Suppress(\"ConvertTwoComparisonsToRangeCheck\")\n        if (start < bufferEnd && end > bufferEnd || start < bufferStart && end > bufferStart) {\n            /** 章节内容在缓冲区交界处 */\n            LocalBook.getBookInputStream(book).use { bis ->\n                bis.skip(start)\n                bis.read(buffer)\n            }\n        } else {\n            /** 章节内容在缓冲区内 */\n            txtBuffer!!.copyInto(\n                buffer,\n                0,\n                (start - bufferStart).toInt(),\n                (end - bufferStart).toInt()\n            )\n        }\n\n        return String(buffer, charset)\n            .substringAfter(chapter.title)\n            .replace(padRegex, \"　　\")\n    }\n\n    /**\n     * 按规则解析目录\n     */\n    private fun analyze(pattern: Pattern?): Pair<ArrayList<BookChapter>, Int> {\n        if (pattern == null || pattern.pattern().isNullOrEmpty()) {\n            return analyze()\n        }\n        val toc = arrayListOf<BookChapter>()\n        var bookWordCount = 0\n        LocalBook.getBookInputStream(book).use { bis ->\n            var blockContent: String\n            //加载章节\n            var curOffset: Long = 0\n            //读取的长度\n            var length: Int\n            var lastChapterWordCount = 0\n            val buffer = ByteArray(bufferSize)\n            var bufferStart = 3\n            bis.read(buffer, 0, 3)\n            if (Utf8BomUtils.hasBom(buffer)) {\n                bufferStart = 0\n                curOffset = 3\n            }\n            //获取文件中的数据到buffer，直到没有数据为止\n            while (bis.read(\n                    buffer, bufferStart, bufferSize - bufferStart\n                ).also { length = it } > 0\n            ) {\n                var end = bufferStart + length\n                if (end == bufferSize) {\n                    for (i in bufferStart + length - 1 downTo 0) {\n                        if (buffer[i] == blank) {\n                            end = i\n                            break\n                        }\n                    }\n                }\n                //将数据转换成String, 不能超过length\n                blockContent = String(buffer, 0, end, charset)\n                buffer.copyInto(buffer, 0, end, bufferStart + length)\n                bufferStart = bufferStart + length - end\n                length = end\n                //当前Block下使过的String的指针\n                var seekPos = 0\n                //进行正则匹配\n                val matcher: Matcher = pattern.matcher(blockContent)\n                //如果存在相应章节\n                while (matcher.find()) { //获取匹配到的字符在字符串中的起始位置\n                    val chapterStart = matcher.start()\n                    //获取章节内容\n                    val chapterContent = blockContent.substring(seekPos, chapterStart)\n                    val chapterLength = chapterContent.toByteArray(charset).size.toLong()\n                    val lastStart = toc.lastOrNull()?.start ?: curOffset\n                    if (book.getSplitLongChapter() && curOffset + chapterLength - lastStart > maxLengthWithToc) {\n                        toc.lastOrNull()?.let {\n                            it.end = it.start\n                            it.tag = null\n                        }\n                        //章节字数太多进行拆分\n                        val lastTitle = toc.lastOrNull()?.title\n                        val lastTitleLength = lastTitle?.toByteArray(charset)?.size ?: 0\n                        val (chapters, wordCount) = analyze(\n                            lastStart + lastTitleLength, curOffset + chapterLength\n                        )\n                        lastTitle?.let {\n                            chapters.forEachIndexed { index, bookChapter ->\n                                bookChapter.title = \"$lastTitle(${index + 1})\"\n                            }\n                        }\n                        toc.addAll(chapters)\n                        bookWordCount += wordCount\n                        //创建当前章节\n                        val curChapter = BookChapter()\n                        curChapter.title = matcher.group()\n                        curChapter.start = curOffset + chapterLength\n                        curChapter.end = curChapter.start\n                        toc.add(curChapter)\n                        lastChapterWordCount = 0\n                    } else if (seekPos == 0 && chapterStart != 0) {\n                        /**\n                         * 如果 seekPos == 0 && chapterStart != 0 表示当前block处前面有一段内容\n                         * 第一种情况一定是序章 第二种情况是上一个章节的内容\n                         */\n                        if (toc.isEmpty()) { //如果当前没有章节，那么就是序章\n                            //加入简介\n                            if (chapterContent.isNotBlank()) {\n                                val qyChapter = BookChapter()\n                                qyChapter.title = \"前言\"\n                                qyChapter.start = curOffset\n                                qyChapter.end = curOffset + chapterLength\n                                qyChapter.wordCount =\n                                    StringUtils.wordCountFormat(chapterContent.length)\n                                toc.add(qyChapter)\n                                book.intro = if (chapterContent.length <= 500) {\n                                    chapterContent\n                                } else {\n                                    chapterContent.substring(0, 500)\n                                }\n                            }\n                            //创建当前章节\n                            val curChapter = BookChapter()\n                            curChapter.title = matcher.group()\n                            curChapter.start = curOffset + chapterLength\n                            curChapter.end = curChapter.start\n                            toc.add(curChapter)\n                        } else { //否则就block分割之后，上一个章节的剩余内容\n                            //获取上一章节\n                            val lastChapter = toc.last()\n                            lastChapter.isVolume =\n                                chapterContent.substringAfter(lastChapter.title).isBlank()\n                            //将当前段落添加上一章去\n                            lastChapter.end = lastChapter.end!! + chapterLength\n                            lastChapterWordCount += chapterContent.length\n                            lastChapter.wordCount =\n                                StringUtils.wordCountFormat(lastChapterWordCount)\n                            //创建当前章节\n                            val curChapter = BookChapter()\n                            curChapter.title = matcher.group()\n                            curChapter.start = lastChapter.end\n                            curChapter.end = curChapter.start\n                            toc.add(curChapter)\n                        }\n                        bookWordCount += chapterContent.length\n                        lastChapterWordCount = 0\n                    } else {\n                        if (toc.isNotEmpty()) { //获取章节内容\n                            //获取上一章节\n                            val lastChapter = toc.last()\n                            lastChapter.isVolume =\n                                chapterContent.substringAfter(lastChapter.title).isBlank()\n                            lastChapter.end =\n                                lastChapter.start!! + chapterLength\n                            lastChapter.wordCount =\n                                StringUtils.wordCountFormat(chapterContent.length)\n                            //创建当前章节\n                            val curChapter = BookChapter()\n                            curChapter.title = matcher.group()\n                            curChapter.start = lastChapter.end\n                            curChapter.end = curChapter.start\n                            toc.add(curChapter)\n                        } else { //如果章节不存在则创建章节\n                            val curChapter = BookChapter()\n                            curChapter.title = matcher.group()\n                            curChapter.start = curOffset\n                            curChapter.end = curOffset\n                            curChapter.wordCount =\n                                StringUtils.wordCountFormat(chapterContent.length)\n                            toc.add(curChapter)\n                        }\n                        bookWordCount += chapterContent.length\n                        lastChapterWordCount = 0\n                    }\n                    //设置指针偏移\n                    seekPos += chapterContent.length\n                }\n                val wordCount = blockContent.length - seekPos\n                bookWordCount += wordCount\n                lastChapterWordCount += wordCount\n                //block的偏移点\n                curOffset += length.toLong()\n                //设置上一章的结尾\n                toc.lastOrNull()?.let {\n                    it.end = curOffset\n                    it.wordCount = StringUtils.wordCountFormat(lastChapterWordCount)\n                }\n            }\n            toc.lastOrNull()?.let { chapter ->\n                //章节字数太多进行拆分\n                if (book.getSplitLongChapter() && chapter.end!! - chapter.start!! > maxLengthWithToc) {\n                    val end = chapter.end!!\n                    chapter.end = chapter.start\n                    chapter.tag = null\n                    val lastTitle = chapter.title\n                    val lastTitleLength = lastTitle.toByteArray(charset).size\n                    val (chapters, _) = analyze(\n                        chapter.start!! + lastTitleLength, end\n                    )\n                    chapters.forEachIndexed { index, bookChapter ->\n                        bookChapter.title = \"$lastTitle(${index + 1})\"\n                    }\n                    toc.addAll(chapters)\n                }\n            }\n        }\n        System.gc()\n        System.runFinalization()\n        return toc to bookWordCount\n    }\n\n    /**\n     * 无规则拆分目录\n     */\n    private fun analyze(\n        fileStart: Long = 0L, fileEnd: Long = Long.MAX_VALUE\n    ): Pair<ArrayList<BookChapter>, Int> {\n        val toc = arrayListOf<BookChapter>()\n        var bookWordCount = 0\n        LocalBook.getBookInputStream(book).use { bis ->\n            //block的个数\n            var blockPos = 0\n            //加载章节\n            var curOffset: Long = 0\n            var chapterPos = 0\n            //读取的长度\n            var length = 0\n            var lastChapterWordCount = 0\n            val buffer = ByteArray(bufferSize)\n            var bufferStart = 3\n            if (fileStart == 0L) {\n                bis.read(buffer, 0, 3)\n                if (Utf8BomUtils.hasBom(buffer)) {\n                    bufferStart = 0\n                    curOffset = 3\n                }\n            } else {\n                bis.skip(fileStart)\n                curOffset = fileStart\n                bufferStart = 0\n            }\n            //获取文件中的数据到buffer，直到没有数据为止\n            while (fileEnd - curOffset - bufferStart > 0 && bis.read(\n                    buffer, bufferStart, min(\n                        (bufferSize - bufferStart).toLong(), fileEnd - curOffset - bufferStart\n                    ).toInt()\n                ).also { length = it } > 0\n            ) {\n                blockPos++\n                //章节在buffer的偏移量\n                var chapterOffset = 0\n                //当前剩余可分配的长度\n                length += bufferStart\n                var strLength = length\n                //分章的位置\n                chapterPos = 0\n                while (strLength > 0) {\n                    chapterPos++\n                    //是否长度超过一章\n                    if (strLength > maxLengthWithNoToc) { //在buffer中一章的终止点\n                        var end = length\n                        //寻找换行符作为终止点\n                        for (i in chapterOffset + maxLengthWithNoToc until length) {\n                            if (buffer[i] == blank) {\n                                end = i\n                                break\n                            }\n                        }\n                        val content = String(buffer, chapterOffset, end - chapterOffset, charset)\n                        bookWordCount += content.length\n                        lastChapterWordCount = content.length\n                        val chapter = BookChapter()\n                        chapter.title = \"第${blockPos}章($chapterPos)\"\n                        chapter.start = toc.lastOrNull()?.end ?: curOffset\n                        chapter.end = chapter.start!! + end - chapterOffset\n                        chapter.wordCount = StringUtils.wordCountFormat(content.length)\n                        toc.add(chapter)\n                        //减去已经被分配的长度\n                        strLength -= (end - chapterOffset)\n                        //设置偏移的位置\n                        chapterOffset = end\n                    } else {\n                        buffer.copyInto(buffer, 0, length - strLength, length)\n                        length -= strLength\n                        bufferStart = strLength\n                        strLength = 0\n                    }\n                }\n                //block的偏移点\n                curOffset += length.toLong()\n            }\n            //设置结尾章节\n            val content = String(buffer, 0, bufferStart, charset)\n            bookWordCount += content.length\n            if (bufferStart > 100 || toc.isEmpty()) {\n                val chapter = BookChapter()\n                chapter.title = \"第${blockPos}章(${chapterPos})\"\n                chapter.start = toc.lastOrNull()?.end ?: curOffset\n                chapter.end = chapter.start!! + bufferStart\n                chapter.wordCount = StringUtils.wordCountFormat(content.length)\n                toc.add(chapter)\n            } else {\n                val wordCount = lastChapterWordCount + content.length\n                toc.lastOrNull()?.let {\n                    it.end = it.end!! + bufferStart\n                    it.wordCount = StringUtils.wordCountFormat(wordCount)\n                }\n            }\n        }\n        return toc to bookWordCount\n    }\n\n    /**\n     * 获取合适的目录规则\n     */\n    private fun getTocRule(content: String): Pattern? {\n        val rules = getTocRules().reversed()\n        var maxNum = 1\n        var tocPattern: Pattern? = null\n        for (tocRule in rules) {\n            val pattern = try {\n                tocRule.rule.toPattern(Pattern.MULTILINE)\n            } catch (e: PatternSyntaxException) {\n                AppLog.put(\"TXT目录规则正则语法错误:${tocRule.name}\\n$e\", e)\n                continue\n            }\n            val matcher = pattern.matcher(content)\n            var start = 0\n            var num = 0\n            while (matcher.find()) {\n                if (start == 0 || matcher.start() - start > 1000) {\n                    num++\n                    start = matcher.end()\n                }\n            }\n            if (num >= maxNum) {\n                maxNum = num\n                tocPattern = pattern\n            }\n        }\n        return tocPattern\n    }\n\n    /**\n     * 获取启用的目录规则\n     */\n    private fun getTocRules(): List<TxtTocRule> {\n        var rules = appDb.txtTocRuleDao.enabled\n        if (appDb.txtTocRuleDao.count == 0) {\n            rules = DefaultData.txtTocRules.apply {\n                appDb.txtTocRuleDao.insert(*this.toTypedArray())\n            }.filter {\n                it.enable\n            }\n        }\n        return rules\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/model/localBook/UmdFile.kt",
    "content": "package io.legado.app.model.localBook\n\nimport io.legado.app.data.entities.Book\nimport io.legado.app.data.entities.BookChapter\nimport io.legado.app.utils.DebugLog\nimport io.legado.app.utils.FileUtils\nimport io.legado.app.utils.printOnDebug\nimport me.ag2s.umdlib.domain.UmdBook\nimport me.ag2s.umdlib.umd.UmdReader\nimport java.io.File\nimport java.io.InputStream\n\nclass UmdFile(var book: Book) {\n    companion object : BaseLocalBookParse {\n        private var uFile: UmdFile? = null\n\n        @Synchronized\n        private fun getUFile(book: Book): UmdFile {\n            if (uFile == null || uFile?.book?.bookUrl != book.bookUrl) {\n                uFile = UmdFile(book)\n                return uFile!!\n            }\n            uFile?.book = book\n            return uFile!!\n        }\n\n        @Synchronized\n        override fun getChapterList(book: Book): ArrayList<BookChapter> {\n            return getUFile(book).getChapterList()\n        }\n\n        @Synchronized\n        override fun getContent(book: Book, chapter: BookChapter): String? {\n            return getUFile(book).getContent(chapter)\n        }\n\n        @Synchronized\n        override fun getImage(\n            book: Book,\n            href: String\n        ): InputStream? {\n            return getUFile(book).getImage(href)\n        }\n\n\n        @Synchronized\n        override fun upBookInfo(book: Book) {\n            return getUFile(book).upBookInfo()\n        }\n    }\n\n\n    private var umdBook: UmdBook? = null\n        get() {\n            if (field != null) {\n                return field\n            }\n            field = readUmd()\n            return field\n        }\n\n    init {\n        upBookCover(true)\n    }\n\n    private fun readUmd(): UmdBook? {\n        val input = LocalBook.getBookInputStream(book)\n        return UmdReader().read(input)\n    }\n\n    private fun upBookCover(fastCheck: Boolean = false) {\n        try {\n            umdBook?.let {\n                if (book.coverUrl.isNullOrEmpty()) {\n                    book.coverUrl = LocalBook.getCoverPath(book)\n                }\n                if (fastCheck && File(book.coverUrl!!).exists()) {\n                    return\n                }\n                FileUtils.writeBytes(book.coverUrl!!, it.cover.coverData)\n            }\n        } catch (e: Exception) {\n            e.printOnDebug()\n        }\n    }\n\n    private fun upBookInfo() {\n        if (umdBook == null) {\n            uFile = null\n            book.intro = \"书籍导入异常\"\n        } else {\n            upBookCover()\n            val hd = umdBook!!.header\n            book.name = hd.title\n            book.author = hd.author\n            book.kind = hd.bookType\n        }\n    }\n\n    private fun getContent(chapter: BookChapter): String? {\n        return umdBook?.chapters?.getContentString(chapter.index)\n    }\n\n    private fun getChapterList(): ArrayList<BookChapter> {\n        val chapterList = ArrayList<BookChapter>()\n        umdBook?.chapters?.titles?.forEachIndexed { index, _ ->\n            val title = umdBook!!.chapters.getTitle(index)\n            val chapter = BookChapter()\n            chapter.title = title\n            chapter.index = index\n            chapter.bookUrl = book.bookUrl\n            chapter.url = index.toString()\n            DebugLog.d(javaClass.name, chapter.url)\n            chapterList.add(chapter)\n        }\n        return chapterList\n    }\n\n    private fun getImage(@Suppress(\"UNUSED_PARAMETER\") href: String): InputStream? {\n        return null\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/model/remote/RemoteBook.kt",
    "content": "package io.legado.app.model.remote\n\nimport androidx.annotation.Keep\nimport io.legado.app.lib.webdav.WebDavFile\nimport io.legado.app.model.localBook.LocalBook\n\n@Keep\ndata class RemoteBook(\n    val filename: String,\n    val path: String,\n    val size: Long,\n    val lastModify: Long,\n    var contentType: String = \"folder\",\n    var isOnBookShelf: Boolean = false\n) {\n\n    val isDir get() = contentType == \"folder\"\n\n    constructor(webDavFile: WebDavFile) : this(\n        webDavFile.displayName,\n        webDavFile.path,\n        webDavFile.size,\n        webDavFile.lastModify\n    ) {\n        if (!webDavFile.isDir) {\n            contentType = webDavFile.displayName.substringAfterLast(\".\")\n            isOnBookShelf = LocalBook.isOnBookShelf(webDavFile.displayName)\n        }\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/model/remote/RemoteBookManager.kt",
    "content": "package io.legado.app.model.remote\n\nimport android.net.Uri\nimport io.legado.app.data.entities.Book\n\nabstract class RemoteBookManager {\n\n    /**\n     * 获取书籍列表\n     */\n    @Throws(Exception::class)\n    abstract suspend fun getRemoteBookList(path: String): MutableList<RemoteBook>\n\n    /**\n     * 根据书籍地址获取书籍信息\n     */\n    @Throws(Exception::class)\n    abstract suspend fun getRemoteBook(path: String): RemoteBook?\n\n    /**\n     * @return Uri：下载到本地的路径\n     */\n    @Throws(Exception::class)\n    abstract suspend fun downloadRemoteBook(remoteBook: RemoteBook): Uri\n\n    /**\n     * 上传书籍\n     */\n    @Throws(Exception::class)\n    abstract suspend fun upload(book: Book)\n\n    /**\n     * 删除书籍\n     */\n    @Throws(Exception::class)\n    abstract suspend fun delete(remoteBookUrl: String)\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/model/remote/RemoteBookWebDav.kt",
    "content": "package io.legado.app.model.remote\n\nimport android.net.Uri\nimport io.legado.app.constant.AppPattern.archiveFileRegex\nimport io.legado.app.constant.AppPattern.bookFileRegex\nimport io.legado.app.constant.BookType\nimport io.legado.app.data.entities.Book\nimport io.legado.app.exception.NoStackTraceException\nimport io.legado.app.help.book.update\nimport io.legado.app.help.config.AppConfig\nimport io.legado.app.lib.webdav.Authorization\nimport io.legado.app.lib.webdav.WebDav\nimport io.legado.app.lib.webdav.WebDavFile\nimport io.legado.app.model.analyzeRule.CustomUrl\nimport io.legado.app.model.localBook.LocalBook\nimport io.legado.app.utils.NetworkUtils\nimport io.legado.app.utils.isContentScheme\nimport kotlinx.coroutines.runBlocking\n\nclass RemoteBookWebDav(\n    val rootBookUrl: String,\n    val authorization: Authorization,\n    val serverID: Long? = null\n) : RemoteBookManager() {\n\n    init {\n        runBlocking {\n            WebDav(rootBookUrl, authorization).makeAsDir()\n        }\n    }\n\n\n    @Throws(Exception::class)\n    override suspend fun getRemoteBookList(path: String): MutableList<RemoteBook> {\n        if (!NetworkUtils.isAvailable()) throw NoStackTraceException(\"网络不可用\")\n        val remoteBooks = mutableListOf<RemoteBook>()\n        //读取文件列表\n        val remoteWebDavFileList: List<WebDavFile> = WebDav(path, authorization).listFiles()\n        //转化远程文件信息到本地对象\n        remoteWebDavFileList.forEach { webDavFile ->\n            if (webDavFile.isDir\n                || bookFileRegex.matches(webDavFile.displayName)\n                || archiveFileRegex.matches(webDavFile.displayName)\n            ) {\n                //扩展名符合阅读的格式则认为是书籍\n                remoteBooks.add(RemoteBook(webDavFile))\n            }\n        }\n        return remoteBooks\n    }\n\n    override suspend fun getRemoteBook(path: String): RemoteBook? {\n        if (!NetworkUtils.isAvailable()) throw NoStackTraceException(\"网络不可用\")\n        val webDavFile = WebDav(path, authorization).getWebDavFile()\n            ?: return null\n        return RemoteBook(webDavFile)\n    }\n\n    override suspend fun downloadRemoteBook(remoteBook: RemoteBook): Uri {\n        AppConfig.defaultBookTreeUri\n            ?: throw NoStackTraceException(\"没有设置书籍保存位置!\")\n        if (!NetworkUtils.isAvailable()) throw NoStackTraceException(\"网络不可用\")\n        val webdav = WebDav(remoteBook.path, authorization)\n        return webdav.downloadInputStream().let { inputStream ->\n            LocalBook.saveBookFile(inputStream, remoteBook.filename)\n        }\n    }\n\n    override suspend fun upload(book: Book) {\n        if (!NetworkUtils.isAvailable()) throw NoStackTraceException(\"网络不可用\")\n        val localBookUri = Uri.parse(book.bookUrl)\n        val putUrl = \"$rootBookUrl${book.originName}\"\n        val webDav = WebDav(putUrl, authorization)\n        if (localBookUri.isContentScheme()) {\n            webDav.upload(localBookUri)\n        } else {\n            webDav.upload(localBookUri.path!!)\n        }\n        book.origin = BookType.webDavTag + CustomUrl(putUrl)\n            .putAttribute(\"serverID\", serverID)\n            .toString()\n        book.update()\n    }\n\n    override suspend fun delete(remoteBookUrl: String) {\n        if (!NetworkUtils.isAvailable()) throw NoStackTraceException(\"网络不可用\")\n        WebDav(remoteBookUrl, authorization).delete()\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/model/rss/Rss.kt",
    "content": "package io.legado.app.model.rss\n\nimport io.legado.app.data.entities.RssArticle\nimport io.legado.app.data.entities.RssSource\nimport io.legado.app.help.coroutine.Coroutine\nimport io.legado.app.help.http.StrResponse\nimport io.legado.app.model.Debug\nimport io.legado.app.model.analyzeRule.AnalyzeRule\nimport io.legado.app.model.analyzeRule.AnalyzeRule.Companion.setCoroutineContext\nimport io.legado.app.model.analyzeRule.AnalyzeUrl\nimport io.legado.app.model.analyzeRule.RuleData\nimport io.legado.app.utils.NetworkUtils\nimport kotlinx.coroutines.CoroutineScope\nimport kotlinx.coroutines.Dispatchers\nimport kotlin.coroutines.CoroutineContext\nimport kotlin.coroutines.coroutineContext\n\n@Suppress(\"MemberVisibilityCanBePrivate\")\nobject Rss {\n\n    fun getArticles(\n        scope: CoroutineScope,\n        sortName: String,\n        sortUrl: String,\n        rssSource: RssSource,\n        page: Int,\n        context: CoroutineContext = Dispatchers.IO\n    ): Coroutine<Pair<MutableList<RssArticle>, String?>> {\n        return Coroutine.async(scope, context) {\n            getArticlesAwait(sortName, sortUrl, rssSource, page)\n        }\n    }\n\n    suspend fun getArticlesAwait(\n        sortName: String,\n        sortUrl: String,\n        rssSource: RssSource,\n        page: Int,\n    ): Pair<MutableList<RssArticle>, String?> {\n        val ruleData = RuleData()\n        val analyzeUrl = AnalyzeUrl(\n            sortUrl,\n            page = page,\n            source = rssSource,\n            ruleData = ruleData,\n            coroutineContext = coroutineContext,\n            hasLoginHeader = false\n        )\n        val res = analyzeUrl.getStrResponseAwait()\n        checkRedirect(rssSource, res)\n        return RssParserByRule.parseXML(sortName, sortUrl, res.url, res.body, rssSource, ruleData)\n    }\n\n    fun getContent(\n        scope: CoroutineScope,\n        rssArticle: RssArticle,\n        ruleContent: String,\n        rssSource: RssSource,\n        context: CoroutineContext = Dispatchers.IO\n    ): Coroutine<String> {\n        return Coroutine.async(scope, context) {\n            getContentAwait(rssArticle, ruleContent, rssSource)\n        }\n    }\n\n    suspend fun getContentAwait(\n        rssArticle: RssArticle,\n        ruleContent: String,\n        rssSource: RssSource,\n    ): String {\n        val analyzeUrl = AnalyzeUrl(\n            rssArticle.link,\n            baseUrl = rssArticle.origin,\n            source = rssSource,\n            ruleData = rssArticle,\n            coroutineContext = coroutineContext,\n            hasLoginHeader = false\n        )\n        val res = analyzeUrl.getStrResponseAwait()\n        checkRedirect(rssSource, res)\n        Debug.log(rssSource.sourceUrl, \"≡获取成功:${rssSource.sourceUrl}\")\n        Debug.log(rssSource.sourceUrl, res.body ?: \"\", state = 20)\n        val analyzeRule = AnalyzeRule(rssArticle, rssSource)\n        analyzeRule.setContent(res.body)\n            .setBaseUrl(NetworkUtils.getAbsoluteURL(rssArticle.origin, rssArticle.link))\n            .setCoroutineContext(coroutineContext)\n            .setRedirectUrl(res.url)\n        return analyzeRule.getString(ruleContent)\n    }\n\n    /**\n     * 检测重定向\n     */\n    private fun checkRedirect(rssSource: RssSource, response: StrResponse) {\n        response.raw.priorResponse?.let {\n            if (it.isRedirect) {\n                Debug.log(rssSource.sourceUrl, \"≡检测到重定向(${it.code})\")\n                Debug.log(rssSource.sourceUrl, \"┌重定向后地址\")\n                Debug.log(rssSource.sourceUrl, \"└${response.url}\")\n            }\n        }\n    }\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/model/rss/RssParserByRule.kt",
    "content": "package io.legado.app.model.rss\n\nimport androidx.annotation.Keep\nimport io.legado.app.R\nimport io.legado.app.data.entities.RssArticle\nimport io.legado.app.data.entities.RssSource\nimport io.legado.app.exception.NoStackTraceException\nimport io.legado.app.model.Debug\nimport io.legado.app.model.analyzeRule.AnalyzeRule\nimport io.legado.app.model.analyzeRule.AnalyzeRule.Companion.setCoroutineContext\nimport io.legado.app.model.analyzeRule.AnalyzeRule.Companion.setRuleData\nimport io.legado.app.model.analyzeRule.RuleData\nimport io.legado.app.utils.NetworkUtils\nimport splitties.init.appCtx\nimport java.util.Locale\nimport kotlin.coroutines.coroutineContext\n\n@Keep\nobject RssParserByRule {\n\n    @Throws(Exception::class)\n    suspend fun parseXML(\n        sortName: String,\n        sortUrl: String,\n        redirectUrl: String,\n        body: String?,\n        rssSource: RssSource,\n        ruleData: RuleData\n    ): Pair<MutableList<RssArticle>, String?> {\n        val sourceUrl = rssSource.sourceUrl\n        var nextUrl: String? = null\n        if (body.isNullOrBlank()) {\n            throw NoStackTraceException(\n                appCtx.getString(R.string.error_get_web_content, rssSource.sourceUrl)\n            )\n        }\n        Debug.log(sourceUrl, \"≡获取成功:$sourceUrl\")\n        Debug.log(sourceUrl, body, state = 10)\n        var ruleArticles = rssSource.ruleArticles\n        if (ruleArticles.isNullOrBlank()) {\n            Debug.log(sourceUrl, \"⇒列表规则为空, 使用默认规则解析\")\n            return RssParserDefault.parseXML(sortName, body, sourceUrl)\n        } else {\n            val articleList = mutableListOf<RssArticle>()\n            val analyzeRule = AnalyzeRule(ruleData, rssSource)\n            analyzeRule.setCoroutineContext(coroutineContext)\n            analyzeRule.setContent(body).setBaseUrl(sortUrl)\n            analyzeRule.setRedirectUrl(redirectUrl)\n            var reverse = false\n            if (ruleArticles.startsWith(\"-\")) {\n                reverse = true\n                ruleArticles = ruleArticles.substring(1)\n            }\n            Debug.log(sourceUrl, \"┌获取列表\")\n            val collections = analyzeRule.getElements(ruleArticles)\n            Debug.log(sourceUrl, \"└列表大小:${collections.size}\")\n            if (!rssSource.ruleNextPage.isNullOrEmpty()) {\n                Debug.log(sourceUrl, \"┌获取下一页链接\")\n                if (rssSource.ruleNextPage!!.uppercase(Locale.getDefault()) == \"PAGE\") {\n                    nextUrl = sortUrl\n                } else {\n                    nextUrl = analyzeRule.getString(rssSource.ruleNextPage)\n                    if (nextUrl.isNotEmpty()) {\n                        nextUrl = NetworkUtils.getAbsoluteURL(sortUrl, nextUrl)\n                    }\n                }\n                Debug.log(sourceUrl, \"└$nextUrl\")\n            }\n            val ruleTitle = analyzeRule.splitSourceRule(rssSource.ruleTitle)\n            val rulePubDate = analyzeRule.splitSourceRule(rssSource.rulePubDate)\n            val ruleDescription = analyzeRule.splitSourceRule(rssSource.ruleDescription)\n            val ruleImage = analyzeRule.splitSourceRule(rssSource.ruleImage)\n            val ruleLink = analyzeRule.splitSourceRule(rssSource.ruleLink)\n            val variable = ruleData.getVariable()\n            for ((index, item) in collections.withIndex()) {\n                getItem(\n                    sourceUrl, item, analyzeRule, variable, index == 0,\n                    ruleTitle, rulePubDate, ruleDescription, ruleImage, ruleLink\n                )?.let {\n                    it.sort = sortName\n                    it.origin = sourceUrl\n                    articleList.add(it)\n                }\n            }\n            if (reverse) {\n                articleList.reverse()\n            }\n            return Pair(articleList, nextUrl)\n        }\n    }\n\n    private fun getItem(\n        sourceUrl: String,\n        item: Any,\n        analyzeRule: AnalyzeRule,\n        variable: String?,\n        log: Boolean,\n        ruleTitle: List<AnalyzeRule.SourceRule>,\n        rulePubDate: List<AnalyzeRule.SourceRule>,\n        ruleDescription: List<AnalyzeRule.SourceRule>,\n        ruleImage: List<AnalyzeRule.SourceRule>,\n        ruleLink: List<AnalyzeRule.SourceRule>\n    ): RssArticle? {\n        val rssArticle = RssArticle(variable = variable)\n        analyzeRule.setRuleData(rssArticle)\n        analyzeRule.setContent(item)\n        Debug.log(sourceUrl, \"┌获取标题\", log)\n        rssArticle.title = analyzeRule.getString(ruleTitle)\n        Debug.log(sourceUrl, \"└${rssArticle.title}\", log)\n        Debug.log(sourceUrl, \"┌获取时间\", log)\n        rssArticle.pubDate = analyzeRule.getString(rulePubDate)\n        Debug.log(sourceUrl, \"└${rssArticle.pubDate}\", log)\n        Debug.log(sourceUrl, \"┌获取描述\", log)\n        if (ruleDescription.isEmpty()) {\n            rssArticle.description = null\n            Debug.log(sourceUrl, \"└描述规则为空，将会解析内容页\", log)\n        } else {\n            rssArticle.description = analyzeRule.getString(ruleDescription)\n            Debug.log(sourceUrl, \"└${rssArticle.description}\", log)\n        }\n        Debug.log(sourceUrl, \"┌获取图片url\", log)\n        rssArticle.image = analyzeRule.getString(ruleImage, isUrl = true)\n        Debug.log(sourceUrl, \"└${rssArticle.image}\", log)\n        Debug.log(sourceUrl, \"┌获取文章链接\", log)\n        rssArticle.link = NetworkUtils.getAbsoluteURL(sourceUrl, analyzeRule.getString(ruleLink))\n        Debug.log(sourceUrl, \"└${rssArticle.link}\", log)\n        if (rssArticle.title.isBlank()) {\n            return null\n        }\n        return rssArticle\n    }\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/model/rss/RssParserDefault.kt",
    "content": "package io.legado.app.model.rss\n\nimport io.legado.app.data.entities.RssArticle\nimport io.legado.app.model.Debug\nimport org.xmlpull.v1.XmlPullParser\nimport org.xmlpull.v1.XmlPullParserException\nimport org.xmlpull.v1.XmlPullParserFactory\nimport java.io.IOException\nimport java.io.StringReader\n\n@Suppress(\"unused\")\nobject RssParserDefault {\n\n    @Throws(XmlPullParserException::class, IOException::class)\n    fun parseXML(\n        sortName: String,\n        xml: String,\n        sourceUrl: String\n    ): Pair<MutableList<RssArticle>, String?> {\n\n        val articleList = mutableListOf<RssArticle>()\n        var currentArticle = RssArticle()\n\n        val factory = XmlPullParserFactory.newInstance()\n        factory.isNamespaceAware = false\n\n        val xmlPullParser = factory.newPullParser()\n        xmlPullParser.setInput(StringReader(xml))\n\n        // A flag just to be sure of the correct parsing\n        var insideItem = false\n\n        var eventType = xmlPullParser.eventType\n\n        // Start parsing the xml\n        loop@ while (eventType != XmlPullParser.END_DOCUMENT) {\n\n            // Start parsing the item\n            if (eventType == XmlPullParser.START_TAG) {\n                when {\n                    xmlPullParser.name.equals(RSS_ITEM, true) ->\n                        insideItem = true\n                    xmlPullParser.name.equals(RSS_ITEM_TITLE, true) ->\n                        if (insideItem) currentArticle.title = xmlPullParser.nextText().trim()\n                    xmlPullParser.name.equals(RSS_ITEM_LINK, true) ->\n                        if (insideItem) currentArticle.link = xmlPullParser.nextText().trim()\n                    xmlPullParser.name.equals(RSS_ITEM_THUMBNAIL, true) ->\n                        if (insideItem) currentArticle.image =\n                            xmlPullParser.getAttributeValue(null, RSS_ITEM_URL)\n                    xmlPullParser.name.equals(RSS_ITEM_ENCLOSURE, true) ->\n                        if (insideItem) {\n                            val type =\n                                xmlPullParser.getAttributeValue(null, RSS_ITEM_TYPE)\n                            if (type != null && type.contains(\"image/\")) {\n                                currentArticle.image =\n                                    xmlPullParser.getAttributeValue(null, RSS_ITEM_URL)\n                            }\n                        }\n                    xmlPullParser.name\n                        .equals(RSS_ITEM_DESCRIPTION, true) ->\n                        if (insideItem) {\n                            val description = xmlPullParser.nextText()\n                            currentArticle.description = description.trim()\n                            if (currentArticle.image == null) {\n                                currentArticle.image = getImageUrl(description)\n                            }\n                        }\n                    xmlPullParser.name.equals(RSS_ITEM_CONTENT, true) ->\n                        if (insideItem) {\n                            val content = xmlPullParser.nextText().trim()\n                            currentArticle.content = content\n                            if (currentArticle.image == null) {\n                                currentArticle.image = getImageUrl(content)\n                            }\n                        }\n                    xmlPullParser.name\n                        .equals(RSS_ITEM_PUB_DATE, true) ->\n                        if (insideItem) {\n                            val nextTokenType = xmlPullParser.next()\n                            if (nextTokenType == XmlPullParser.TEXT) {\n                                currentArticle.pubDate = xmlPullParser.text.trim()\n                            }\n                            // Skip to be able to find date inside 'tag' tag\n                            continue@loop\n                        }\n                    xmlPullParser.name.equals(RSS_ITEM_TIME, true) ->\n                        if (insideItem) currentArticle.pubDate = xmlPullParser.nextText()\n                }\n            } else if (eventType == XmlPullParser.END_TAG\n                && xmlPullParser.name.equals(\"item\", true)\n            ) {\n                // The item is correctly parsed\n                insideItem = false\n                currentArticle.origin = sourceUrl\n                currentArticle.sort = sortName\n                articleList.add(currentArticle)\n                currentArticle = RssArticle()\n            }\n            eventType = xmlPullParser.next()\n        }\n        articleList.firstOrNull()?.let {\n            Debug.log(sourceUrl, \"┌获取标题\")\n            Debug.log(sourceUrl, \"└${it.title}\")\n            Debug.log(sourceUrl, \"┌获取时间\")\n            Debug.log(sourceUrl, \"└${it.pubDate}\")\n            Debug.log(sourceUrl, \"┌获取描述\")\n            Debug.log(sourceUrl, \"└${it.description}\")\n            Debug.log(sourceUrl, \"┌获取图片url\")\n            Debug.log(sourceUrl, \"└${it.image}\")\n            Debug.log(sourceUrl, \"┌获取文章链接\")\n            Debug.log(sourceUrl, \"└${it.link}\")\n        }\n        return Pair(articleList, null)\n    }\n\n    /**\n     * Finds the first img tag and get the src as featured image\n     *\n     * @param input The content in which to search for the tag\n     * @return The url, if there is one\n     */\n    private fun getImageUrl(input: String): String? {\n\n        var url: String? = null\n        val patternImg = \"(<img [^>]*>)\".toPattern()\n        val matcherImg = patternImg.matcher(input)\n        if (matcherImg.find()) {\n            val imgTag = matcherImg.group(1)\n            val patternLink = \"src\\\\s*=\\\\s*\\\"([^\\\"]+)\\\"\".toPattern()\n            val matcherLink = patternLink.matcher(imgTag!!)\n            if (matcherLink.find()) {\n                url = matcherLink.group(1)!!.trim()\n            }\n        }\n        return url\n    }\n\n    private const val RSS_ITEM = \"item\"\n    private const val RSS_ITEM_TITLE = \"title\"\n    private const val RSS_ITEM_LINK = \"link\"\n    private const val RSS_ITEM_CATEGORY = \"category\"\n    private const val RSS_ITEM_THUMBNAIL = \"media:thumbnail\"\n    private const val RSS_ITEM_ENCLOSURE = \"enclosure\"\n    private const val RSS_ITEM_DESCRIPTION = \"description\"\n    private const val RSS_ITEM_CONTENT = \"content:encoded\"\n    private const val RSS_ITEM_PUB_DATE = \"pubDate\"\n    private const val RSS_ITEM_TIME = \"time\"\n    private const val RSS_ITEM_URL = \"url\"\n    private const val RSS_ITEM_TYPE = \"type\"\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/model/webBook/BookChapterList.kt",
    "content": "package io.legado.app.model.webBook\n\nimport android.text.TextUtils\nimport com.script.ScriptBindings\nimport com.script.rhino.RhinoScriptEngine\nimport io.legado.app.R\nimport io.legado.app.data.appDb\nimport io.legado.app.data.entities.Book\nimport io.legado.app.data.entities.BookChapter\nimport io.legado.app.data.entities.BookSource\nimport io.legado.app.data.entities.rule.TocRule\nimport io.legado.app.exception.NoStackTraceException\nimport io.legado.app.exception.TocEmptyException\nimport io.legado.app.help.book.ContentProcessor\nimport io.legado.app.help.book.simulatedTotalChapterNum\nimport io.legado.app.help.config.AppConfig\nimport io.legado.app.model.Debug\nimport io.legado.app.model.analyzeRule.AnalyzeRule\nimport io.legado.app.model.analyzeRule.AnalyzeRule.Companion.setChapter\nimport io.legado.app.model.analyzeRule.AnalyzeRule.Companion.setCoroutineContext\nimport io.legado.app.model.analyzeRule.AnalyzeUrl\nimport io.legado.app.utils.isTrue\nimport io.legado.app.utils.mapAsync\nimport kotlinx.coroutines.ensureActive\nimport kotlinx.coroutines.flow.flow\nimport org.mozilla.javascript.Context\nimport splitties.init.appCtx\nimport kotlin.coroutines.coroutineContext\n\n/**\n * 获取目录\n */\nobject BookChapterList {\n\n    suspend fun analyzeChapterList(\n        bookSource: BookSource,\n        book: Book,\n        baseUrl: String,\n        redirectUrl: String,\n        body: String?\n    ): List<BookChapter> {\n        body ?: throw NoStackTraceException(\n            appCtx.getString(R.string.error_get_web_content, baseUrl)\n        )\n        val chapterList = ArrayList<BookChapter>()\n        Debug.log(bookSource.bookSourceUrl, \"≡获取成功:${baseUrl}\")\n        Debug.log(bookSource.bookSourceUrl, body, state = 30)\n        val tocRule = bookSource.getTocRule()\n        val nextUrlList = arrayListOf(redirectUrl)\n        var reverse = false\n        var listRule = tocRule.chapterList ?: \"\"\n        if (listRule.startsWith(\"-\")) {\n            reverse = true\n            listRule = listRule.substring(1)\n        }\n        if (listRule.startsWith(\"+\")) {\n            listRule = listRule.substring(1)\n        }\n        var chapterData =\n            analyzeChapterList(\n                book, baseUrl, redirectUrl, body,\n                tocRule, listRule, bookSource, log = true\n            )\n        chapterList.addAll(chapterData.first)\n        when (chapterData.second.size) {\n            0 -> Unit\n            1 -> {\n                var nextUrl = chapterData.second[0]\n                while (nextUrl.isNotEmpty() && !nextUrlList.contains(nextUrl)) {\n                    nextUrlList.add(nextUrl)\n                    val analyzeUrl = AnalyzeUrl(\n                        mUrl = nextUrl,\n                        source = bookSource,\n                        ruleData = book,\n                        coroutineContext = coroutineContext\n                    )\n                    val res = analyzeUrl.getStrResponseAwait() //控制并发访问\n                    res.body?.let { nextBody ->\n                        chapterData = analyzeChapterList(\n                            book, nextUrl, nextUrl,\n                            nextBody, tocRule, listRule, bookSource\n                        )\n                        nextUrl = chapterData.second.firstOrNull() ?: \"\"\n                        chapterList.addAll(chapterData.first)\n                    }\n                }\n                Debug.log(bookSource.bookSourceUrl, \"◇目录总页数:${nextUrlList.size}\")\n            }\n\n            else -> {\n                Debug.log(\n                    bookSource.bookSourceUrl,\n                    \"◇并发解析目录,总页数:${chapterData.second.size}\"\n                )\n                flow {\n                    for (urlStr in chapterData.second) {\n                        emit(urlStr)\n                    }\n                }.mapAsync(AppConfig.threadCount) { urlStr ->\n                    val analyzeUrl = AnalyzeUrl(\n                        mUrl = urlStr,\n                        source = bookSource,\n                        ruleData = book,\n                        coroutineContext = coroutineContext\n                    )\n                    val res = analyzeUrl.getStrResponseAwait() //控制并发访问\n                    analyzeChapterList(\n                        book, urlStr, res.url,\n                        res.body!!, tocRule, listRule, bookSource, false\n                    ).first\n                }.collect {\n                    chapterList.addAll(it)\n                }\n            }\n        }\n        if (chapterList.isEmpty()) {\n            throw TocEmptyException(appCtx.getString(R.string.chapter_list_empty))\n        }\n        if (!reverse) {\n            chapterList.reverse()\n        }\n        coroutineContext.ensureActive()\n        //去重\n        val lh = LinkedHashSet(chapterList)\n        val list = ArrayList(lh)\n        if (!book.getReverseToc()) {\n            list.reverse()\n        }\n        Debug.log(book.origin, \"◇目录总数:${list.size}\")\n        coroutineContext.ensureActive()\n        list.forEachIndexed { index, bookChapter ->\n            bookChapter.index = index\n        }\n        val formatJs = tocRule.formatJs\n        if (!formatJs.isNullOrBlank()) {\n            Context.enter().use {\n                val bindings = ScriptBindings()\n                bindings[\"gInt\"] = 0\n                list.forEachIndexed { index, bookChapter ->\n                    bindings[\"index\"] = index + 1\n                    bindings[\"chapter\"] = bookChapter\n                    bindings[\"title\"] = bookChapter.title\n                    RhinoScriptEngine.runCatching {\n                        eval(formatJs, bindings)?.toString()?.let {\n                            bookChapter.title = it\n                        }\n                    }.onFailure {\n                        Debug.log(book.origin, \"格式化标题出错, ${it.localizedMessage}\")\n                    }\n                }\n            }\n        }\n        val replaceRules = ContentProcessor.get(book).getTitleReplaceRules()\n        book.durChapterTitle = list.getOrElse(book.durChapterIndex) { list.last() }\n            .getDisplayTitle(replaceRules, book.getUseReplaceRule())\n        if (book.totalChapterNum < list.size) {\n            book.lastCheckCount = list.size - book.totalChapterNum\n            book.latestChapterTime = System.currentTimeMillis()\n        }\n        book.lastCheckTime = System.currentTimeMillis()\n        book.totalChapterNum = list.size\n        book.latestChapterTitle =\n            list.getOrElse(book.simulatedTotalChapterNum() - 1) { list.last() }\n                .getDisplayTitle(replaceRules, book.getUseReplaceRule())\n        coroutineContext.ensureActive()\n        getWordCount(list, book)\n        return list\n    }\n\n    private suspend fun analyzeChapterList(\n        book: Book,\n        baseUrl: String,\n        redirectUrl: String,\n        body: String,\n        tocRule: TocRule,\n        listRule: String,\n        bookSource: BookSource,\n        getNextUrl: Boolean = true,\n        log: Boolean = false\n    ): Pair<List<BookChapter>, List<String>> {\n        val analyzeRule = AnalyzeRule(book, bookSource)\n        analyzeRule.setContent(body).setBaseUrl(baseUrl)\n        analyzeRule.setRedirectUrl(redirectUrl)\n        analyzeRule.setCoroutineContext(coroutineContext)\n        //获取目录列表\n        val chapterList = arrayListOf<BookChapter>()\n        Debug.log(bookSource.bookSourceUrl, \"┌获取目录列表\", log)\n        val elements = analyzeRule.getElements(listRule)\n        Debug.log(bookSource.bookSourceUrl, \"└列表大小:${elements.size}\", log)\n        //获取下一页链接\n        val nextUrlList = arrayListOf<String>()\n        val nextTocRule = tocRule.nextTocUrl\n        if (getNextUrl && !nextTocRule.isNullOrEmpty()) {\n            Debug.log(bookSource.bookSourceUrl, \"┌获取目录下一页列表\", log)\n            analyzeRule.getStringList(nextTocRule, isUrl = true)?.let {\n                for (item in it) {\n                    if (item != redirectUrl) {\n                        nextUrlList.add(item)\n                    }\n                }\n            }\n            Debug.log(\n                bookSource.bookSourceUrl,\n                \"└\" + TextUtils.join(\"，\\n\", nextUrlList),\n                log\n            )\n        }\n        coroutineContext.ensureActive()\n        if (elements.isNotEmpty()) {\n            Debug.log(bookSource.bookSourceUrl, \"┌解析目录列表\", log)\n            val nameRule = analyzeRule.splitSourceRule(tocRule.chapterName)\n            val urlRule = analyzeRule.splitSourceRule(tocRule.chapterUrl)\n            val vipRule = analyzeRule.splitSourceRule(tocRule.isVip)\n            val payRule = analyzeRule.splitSourceRule(tocRule.isPay)\n            val upTimeRule = analyzeRule.splitSourceRule(tocRule.updateTime)\n            val isVolumeRule = analyzeRule.splitSourceRule(tocRule.isVolume)\n            elements.forEachIndexed { index, item ->\n                coroutineContext.ensureActive()\n                analyzeRule.setContent(item)\n                val bookChapter = BookChapter(bookUrl = book.bookUrl, baseUrl = redirectUrl)\n                analyzeRule.setChapter(bookChapter)\n                bookChapter.title = analyzeRule.getString(nameRule)\n                bookChapter.url = analyzeRule.getString(urlRule)\n                bookChapter.tag = analyzeRule.getString(upTimeRule)\n                val isVolume = analyzeRule.getString(isVolumeRule)\n                bookChapter.isVolume = false\n                if (isVolume.isTrue()) {\n                    bookChapter.isVolume = true\n                }\n                if (bookChapter.url.isEmpty()) {\n                    if (bookChapter.isVolume) {\n                        bookChapter.url = bookChapter.title + index\n                        Debug.log(\n                            bookSource.bookSourceUrl,\n                            \"⇒一级目录${index}未获取到url,使用标题替代\"\n                        )\n                    } else {\n                        bookChapter.url = baseUrl\n                        Debug.log(\n                            bookSource.bookSourceUrl,\n                            \"⇒目录${index}未获取到url,使用baseUrl替代\"\n                        )\n                    }\n                }\n                if (bookChapter.title.isNotEmpty()) {\n                    val isVip = analyzeRule.getString(vipRule)\n                    val isPay = analyzeRule.getString(payRule)\n                    if (isVip.isTrue()) {\n                        bookChapter.isVip = true\n                    }\n                    if (isPay.isTrue()) {\n                        bookChapter.isPay = true\n                    }\n                    chapterList.add(bookChapter)\n                }\n            }\n            Debug.log(bookSource.bookSourceUrl, \"└目录列表解析完成\", log)\n            if (chapterList.isEmpty()) {\n                Debug.log(bookSource.bookSourceUrl, \"◇章节列表为空\", log)\n            } else {\n                Debug.log(bookSource.bookSourceUrl, \"≡首章信息\", log)\n                Debug.log(bookSource.bookSourceUrl, \"◇章节名称:${chapterList[0].title}\", log)\n                Debug.log(bookSource.bookSourceUrl, \"◇章节链接:${chapterList[0].url}\", log)\n                Debug.log(bookSource.bookSourceUrl, \"◇章节信息:${chapterList[0].tag}\", log)\n                Debug.log(bookSource.bookSourceUrl, \"◇是否VIP:${chapterList[0].isVip}\", log)\n                Debug.log(bookSource.bookSourceUrl, \"◇是否购买:${chapterList[0].isPay}\", log)\n            }\n        }\n        return Pair(chapterList, nextUrlList)\n    }\n\n    private fun getWordCount(list: ArrayList<BookChapter>, book: Book) {\n        if (!AppConfig.tocCountWords) {\n            return\n        }\n        val chapterList = appDb.bookChapterDao.getChapterList(book.bookUrl)\n        if (chapterList.isNotEmpty()) {\n            val map = chapterList.associateBy({ it.getFileName() }, { it.wordCount })\n            for (bookChapter in list) {\n                val wordCount = map[bookChapter.getFileName()]\n                if (wordCount != null) {\n                    bookChapter.wordCount = wordCount\n                }\n            }\n        }\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/model/webBook/BookContent.kt",
    "content": "package io.legado.app.model.webBook\n\nimport io.legado.app.R\nimport io.legado.app.constant.AppPattern\nimport io.legado.app.data.appDb\nimport io.legado.app.data.entities.Book\nimport io.legado.app.data.entities.BookChapter\nimport io.legado.app.data.entities.BookSource\nimport io.legado.app.data.entities.rule.ContentRule\nimport io.legado.app.exception.ContentEmptyException\nimport io.legado.app.exception.NoStackTraceException\nimport io.legado.app.help.book.BookHelp\nimport io.legado.app.help.config.AppConfig\nimport io.legado.app.model.Debug\nimport io.legado.app.model.analyzeRule.AnalyzeRule\nimport io.legado.app.model.analyzeRule.AnalyzeRule.Companion.setChapter\nimport io.legado.app.model.analyzeRule.AnalyzeRule.Companion.setCoroutineContext\nimport io.legado.app.model.analyzeRule.AnalyzeRule.Companion.setNextChapterUrl\nimport io.legado.app.model.analyzeRule.AnalyzeUrl\nimport io.legado.app.utils.HtmlFormatter\nimport io.legado.app.utils.NetworkUtils\nimport io.legado.app.utils.mapAsync\nimport kotlinx.coroutines.ensureActive\nimport kotlinx.coroutines.flow.flow\nimport org.apache.commons.text.StringEscapeUtils\nimport splitties.init.appCtx\nimport kotlin.coroutines.coroutineContext\n\n/**\n * 获取正文\n */\nobject BookContent {\n\n    @Throws(Exception::class)\n    suspend fun analyzeContent(\n        bookSource: BookSource,\n        book: Book,\n        bookChapter: BookChapter,\n        baseUrl: String,\n        redirectUrl: String,\n        body: String?,\n        nextChapterUrl: String?,\n        needSave: Boolean = true\n    ): String {\n        body ?: throw NoStackTraceException(\n            appCtx.getString(R.string.error_get_web_content, baseUrl)\n        )\n        Debug.log(bookSource.bookSourceUrl, \"≡获取成功:${baseUrl}\")\n        Debug.log(bookSource.bookSourceUrl, body, state = 40)\n        val mNextChapterUrl = if (nextChapterUrl.isNullOrEmpty()) {\n            appDb.bookChapterDao.getChapter(book.bookUrl, bookChapter.index + 1)?.url\n                ?: appDb.bookChapterDao.getChapter(book.bookUrl, 0)?.url\n        } else {\n            nextChapterUrl\n        }\n        val contentList = arrayListOf<String>()\n        val nextUrlList = arrayListOf(redirectUrl)\n        val contentRule = bookSource.getContentRule()\n        val analyzeRule = AnalyzeRule(book, bookSource)\n        analyzeRule.setContent(body, baseUrl)\n        analyzeRule.setRedirectUrl(redirectUrl)\n        analyzeRule.setCoroutineContext(coroutineContext)\n        analyzeRule.setChapter(bookChapter)\n        analyzeRule.setNextChapterUrl(mNextChapterUrl)\n        coroutineContext.ensureActive()\n        val titleRule = contentRule.title\n        if (!titleRule.isNullOrBlank()) {\n            val title = analyzeRule.runCatching {\n                getString(titleRule)\n            }.onFailure {\n                Debug.log(bookSource.bookSourceUrl, \"获取标题出错, ${it.localizedMessage}\")\n            }.getOrNull()\n            if (!title.isNullOrBlank()) {\n                bookChapter.title = title\n                bookChapter.titleMD5 = null\n                appDb.bookChapterDao.update(bookChapter)\n            }\n        }\n        var contentData = analyzeContent(\n            book, baseUrl, redirectUrl, body, contentRule, bookChapter, bookSource, mNextChapterUrl\n        )\n        contentList.add(contentData.first)\n        if (contentData.second.size == 1) {\n            var nextUrl = contentData.second[0]\n            while (nextUrl.isNotEmpty() && !nextUrlList.contains(nextUrl)) {\n                if (!mNextChapterUrl.isNullOrEmpty()\n                    && NetworkUtils.getAbsoluteURL(redirectUrl, nextUrl)\n                    == NetworkUtils.getAbsoluteURL(redirectUrl, mNextChapterUrl)\n                ) break\n                nextUrlList.add(nextUrl)\n                coroutineContext.ensureActive()\n                val analyzeUrl = AnalyzeUrl(\n                    mUrl = nextUrl,\n                    source = bookSource,\n                    ruleData = book,\n                    coroutineContext = coroutineContext\n                )\n                val res = analyzeUrl.getStrResponseAwait() //控制并发访问\n                res.body?.let { nextBody ->\n                    contentData = analyzeContent(\n                        book, nextUrl, res.url, nextBody, contentRule,\n                        bookChapter, bookSource, mNextChapterUrl,\n                        printLog = false\n                    )\n                    nextUrl =\n                        if (contentData.second.isNotEmpty()) contentData.second[0] else \"\"\n                    contentList.add(contentData.first)\n                    Debug.log(bookSource.bookSourceUrl, \"第${contentList.size}页完成\")\n                }\n            }\n            Debug.log(bookSource.bookSourceUrl, \"◇本章总页数:${nextUrlList.size}\")\n        } else if (contentData.second.size > 1) {\n            Debug.log(bookSource.bookSourceUrl, \"◇并发解析正文,总页数:${contentData.second.size}\")\n            flow {\n                for (urlStr in contentData.second) {\n                    emit(urlStr)\n                }\n            }.mapAsync(AppConfig.threadCount) { urlStr ->\n                val analyzeUrl = AnalyzeUrl(\n                    mUrl = urlStr,\n                    source = bookSource,\n                    ruleData = book,\n                    coroutineContext = coroutineContext\n                )\n                val res = analyzeUrl.getStrResponseAwait() //控制并发访问\n                analyzeContent(\n                    book, urlStr, res.url, res.body!!, contentRule,\n                    bookChapter, bookSource, mNextChapterUrl,\n                    getNextPageUrl = false,\n                    printLog = false\n                ).first\n            }.collect {\n                coroutineContext.ensureActive()\n                contentList.add(it)\n            }\n        }\n        var contentStr = contentList.joinToString(\"\\n\")\n        //全文替换\n        val replaceRegex = contentRule.replaceRegex\n        if (!replaceRegex.isNullOrEmpty()) {\n            contentStr = contentStr.split(AppPattern.LFRegex).joinToString(\"\\n\") { it.trim() }\n            contentStr = analyzeRule.getString(replaceRegex, contentStr)\n            contentStr = contentStr.split(AppPattern.LFRegex).joinToString(\"\\n\") { \"　　$it\" }\n        }\n        Debug.log(bookSource.bookSourceUrl, \"┌获取章节名称\")\n        Debug.log(bookSource.bookSourceUrl, \"└${bookChapter.title}\")\n        Debug.log(bookSource.bookSourceUrl, \"┌获取正文内容\")\n        Debug.log(bookSource.bookSourceUrl, \"└\\n$contentStr\")\n        if (!bookChapter.isVolume && contentStr.isBlank()) {\n            throw ContentEmptyException(\"内容为空\")\n        }\n        if (needSave) {\n            BookHelp.saveContent(bookSource, book, bookChapter, contentStr)\n        }\n        return contentStr\n    }\n\n    @Throws(Exception::class)\n    private suspend fun analyzeContent(\n        book: Book,\n        baseUrl: String,\n        redirectUrl: String,\n        body: String,\n        contentRule: ContentRule,\n        chapter: BookChapter,\n        bookSource: BookSource,\n        nextChapterUrl: String?,\n        getNextPageUrl: Boolean = true,\n        printLog: Boolean = true\n    ): Pair<String, List<String>> {\n        val analyzeRule = AnalyzeRule(book, bookSource)\n        analyzeRule.setContent(body, baseUrl)\n        analyzeRule.setCoroutineContext(coroutineContext)\n        val rUrl = analyzeRule.setRedirectUrl(redirectUrl)\n        analyzeRule.setNextChapterUrl(nextChapterUrl)\n        val nextUrlList = arrayListOf<String>()\n        analyzeRule.setChapter(chapter)\n        //获取正文\n        var content = analyzeRule.getString(contentRule.content, unescape = false)\n        content = HtmlFormatter.formatKeepImg(content, rUrl)\n        if (content.indexOf('&') > -1) {\n            content = StringEscapeUtils.unescapeHtml4(content)\n        }\n        //获取下一页链接\n        if (getNextPageUrl) {\n            val nextUrlRule = contentRule.nextContentUrl\n            if (!nextUrlRule.isNullOrEmpty()) {\n                Debug.log(bookSource.bookSourceUrl, \"┌获取正文下一页链接\", printLog)\n                analyzeRule.getStringList(nextUrlRule, isUrl = true)?.let {\n                    nextUrlList.addAll(it)\n                }\n                Debug.log(bookSource.bookSourceUrl, \"└\" + nextUrlList.joinToString(\"，\"), printLog)\n            }\n        }\n        return Pair(content, nextUrlList)\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/model/webBook/BookInfo.kt",
    "content": "package io.legado.app.model.webBook\n\nimport android.text.TextUtils\nimport io.legado.app.R\nimport io.legado.app.data.entities.Book\nimport io.legado.app.data.entities.BookSource\nimport io.legado.app.exception.NoStackTraceException\nimport io.legado.app.help.book.BookHelp\nimport io.legado.app.help.book.isWebFile\nimport io.legado.app.model.Debug\nimport io.legado.app.model.analyzeRule.AnalyzeRule\nimport io.legado.app.model.analyzeRule.AnalyzeRule.Companion.setCoroutineContext\nimport io.legado.app.utils.DebugLog\nimport io.legado.app.utils.HtmlFormatter\nimport io.legado.app.utils.NetworkUtils\nimport io.legado.app.utils.StringUtils.wordCountFormat\nimport kotlinx.coroutines.ensureActive\nimport splitties.init.appCtx\nimport kotlin.coroutines.coroutineContext\n\n\n/**\n * 获取详情\n */\nobject BookInfo {\n\n    @Throws(Exception::class)\n    suspend fun analyzeBookInfo(\n        bookSource: BookSource,\n        book: Book,\n        baseUrl: String,\n        redirectUrl: String,\n        body: String?,\n        canReName: Boolean,\n    ) {\n        body ?: throw NoStackTraceException(\n            appCtx.getString(R.string.error_get_web_content, baseUrl)\n        )\n        Debug.log(bookSource.bookSourceUrl, \"≡获取成功:${baseUrl}\")\n        Debug.log(bookSource.bookSourceUrl, body, state = 20)\n        val analyzeRule = AnalyzeRule(book, bookSource)\n        analyzeRule.setContent(body).setBaseUrl(baseUrl)\n        analyzeRule.setRedirectUrl(redirectUrl)\n        analyzeRule.setCoroutineContext(coroutineContext)\n        analyzeBookInfo(book, body, analyzeRule, bookSource, baseUrl, redirectUrl, canReName)\n    }\n\n    suspend fun analyzeBookInfo(\n        book: Book,\n        body: String,\n        analyzeRule: AnalyzeRule,\n        bookSource: BookSource,\n        baseUrl: String,\n        redirectUrl: String,\n        canReName: Boolean,\n    ) {\n        val infoRule = bookSource.getBookInfoRule()\n        infoRule.init?.let {\n            if (it.isNotBlank()) {\n                coroutineContext.ensureActive()\n                Debug.log(bookSource.bookSourceUrl, \"≡执行详情页初始化规则\")\n                analyzeRule.setContent(analyzeRule.getElement(it))\n            }\n        }\n        val mCanReName = canReName && !infoRule.canReName.isNullOrBlank()\n        coroutineContext.ensureActive()\n        Debug.log(bookSource.bookSourceUrl, \"┌获取书名\")\n        BookHelp.formatBookName(analyzeRule.getString(infoRule.name)).let {\n            if (it.isNotEmpty() && (mCanReName || book.name.isEmpty())) {\n                book.name = it\n            }\n            Debug.log(bookSource.bookSourceUrl, \"└${it}\")\n        }\n        coroutineContext.ensureActive()\n        Debug.log(bookSource.bookSourceUrl, \"┌获取作者\")\n        BookHelp.formatBookAuthor(analyzeRule.getString(infoRule.author)).let {\n            if (it.isNotEmpty() && (mCanReName || book.author.isEmpty())) {\n                book.author = it\n            }\n            Debug.log(bookSource.bookSourceUrl, \"└${it}\")\n        }\n        coroutineContext.ensureActive()\n        Debug.log(bookSource.bookSourceUrl, \"┌获取分类\")\n        try {\n            analyzeRule.getStringList(infoRule.kind)\n                ?.joinToString(\",\")\n                ?.let {\n                    if (it.isNotEmpty()) book.kind = it\n                    Debug.log(bookSource.bookSourceUrl, \"└${it}\")\n                } ?: Debug.log(bookSource.bookSourceUrl, \"└\")\n        } catch (e: Exception) {\n            coroutineContext.ensureActive()\n            Debug.log(bookSource.bookSourceUrl, \"└${e.localizedMessage}\")\n            DebugLog.e(\"获取分类出错\", e)\n        }\n        coroutineContext.ensureActive()\n        Debug.log(bookSource.bookSourceUrl, \"┌获取字数\")\n        try {\n            wordCountFormat(analyzeRule.getString(infoRule.wordCount)).let {\n                if (it.isNotEmpty()) book.wordCount = it\n                Debug.log(bookSource.bookSourceUrl, \"└${it}\")\n            }\n        } catch (e: Exception) {\n            coroutineContext.ensureActive()\n            Debug.log(bookSource.bookSourceUrl, \"└${e.localizedMessage}\")\n            DebugLog.e(\"获取字数出错\", e)\n        }\n        coroutineContext.ensureActive()\n        Debug.log(bookSource.bookSourceUrl, \"┌获取最新章节\")\n        try {\n            analyzeRule.getString(infoRule.lastChapter).let {\n                if (it.isNotEmpty()) book.latestChapterTitle = it\n                Debug.log(bookSource.bookSourceUrl, \"└${it}\")\n            }\n        } catch (e: Exception) {\n            coroutineContext.ensureActive()\n            Debug.log(bookSource.bookSourceUrl, \"└${e.localizedMessage}\")\n            DebugLog.e(\"获取最新章节出错\", e)\n        }\n        coroutineContext.ensureActive()\n        Debug.log(bookSource.bookSourceUrl, \"┌获取简介\")\n        try {\n            HtmlFormatter.format(analyzeRule.getString(infoRule.intro)).let {\n                if (it.isNotEmpty()) book.intro = it\n                Debug.log(bookSource.bookSourceUrl, \"└${it}\")\n            }\n        } catch (e: Exception) {\n            coroutineContext.ensureActive()\n            Debug.log(bookSource.bookSourceUrl, \"└${e.localizedMessage}\")\n            DebugLog.e(\"获取简介出错\", e)\n        }\n        coroutineContext.ensureActive()\n        Debug.log(bookSource.bookSourceUrl, \"┌获取封面链接\")\n        try {\n            analyzeRule.getString(infoRule.coverUrl).let {\n                if (it.isNotEmpty()) {\n                    book.coverUrl =\n                        NetworkUtils.getAbsoluteURL(redirectUrl, it)\n                }\n                Debug.log(bookSource.bookSourceUrl, \"└${it}\")\n            }\n        } catch (e: Exception) {\n            coroutineContext.ensureActive()\n            Debug.log(bookSource.bookSourceUrl, \"└${e.localizedMessage}\")\n            DebugLog.e(\"获取封面出错\", e)\n        }\n        coroutineContext.ensureActive()\n        if (!book.isWebFile) {\n            Debug.log(bookSource.bookSourceUrl, \"┌获取目录链接\")\n            book.tocUrl = analyzeRule.getString(infoRule.tocUrl, isUrl = true)\n            if (book.tocUrl.isEmpty()) book.tocUrl = baseUrl\n            if (book.tocUrl == baseUrl) {\n                book.tocHtml = body\n            }\n            Debug.log(bookSource.bookSourceUrl, \"└${book.tocUrl}\")\n        } else {\n            Debug.log(bookSource.bookSourceUrl, \"┌获取文件下载链接\")\n            book.downloadUrls = analyzeRule.getStringList(infoRule.downloadUrls, isUrl = true)\n            if (book.downloadUrls.isNullOrEmpty()) {\n                Debug.log(bookSource.bookSourceUrl, \"└\")\n                throw NoStackTraceException(\"下载链接为空\")\n            } else {\n                Debug.log(\n                    bookSource.bookSourceUrl,\n                    \"└\" + TextUtils.join(\"，\\n\", book.downloadUrls!!)\n                )\n            }\n        }\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/model/webBook/BookList.kt",
    "content": "package io.legado.app.model.webBook\n\nimport io.legado.app.R\nimport io.legado.app.data.entities.Book\nimport io.legado.app.data.entities.BookSource\nimport io.legado.app.data.entities.SearchBook\nimport io.legado.app.data.entities.rule.BookListRule\nimport io.legado.app.data.entities.rule.ExploreKind\nimport io.legado.app.exception.NoStackTraceException\nimport io.legado.app.help.book.BookHelp\nimport io.legado.app.help.source.exploreKindsJson\nimport io.legado.app.help.source.getBookType\nimport io.legado.app.model.Debug\nimport io.legado.app.model.analyzeRule.AnalyzeRule\nimport io.legado.app.model.analyzeRule.AnalyzeRule.Companion.setCoroutineContext\nimport io.legado.app.model.analyzeRule.AnalyzeRule.Companion.setRuleData\nimport io.legado.app.model.analyzeRule.AnalyzeUrl\nimport io.legado.app.model.analyzeRule.RuleData\nimport io.legado.app.utils.GSON\nimport io.legado.app.utils.GSONStrict\nimport io.legado.app.utils.HtmlFormatter\nimport io.legado.app.utils.NetworkUtils\nimport io.legado.app.utils.StringUtils.wordCountFormat\nimport io.legado.app.utils.fromJsonArray\nimport kotlinx.coroutines.ensureActive\nimport splitties.init.appCtx\nimport kotlin.coroutines.coroutineContext\n\n/**\n * 获取书籍列表\n */\nobject BookList {\n\n    @Throws(Exception::class)\n    suspend fun analyzeBookList(\n        bookSource: BookSource,\n        ruleData: RuleData,\n        analyzeUrl: AnalyzeUrl,\n        baseUrl: String,\n        body: String?,\n        isSearch: Boolean = true,\n        isRedirect: Boolean = false,\n        filter: ((name: String, author: String) -> Boolean)? = null,\n        shouldBreak: ((size: Int) -> Boolean)? = null\n    ): ArrayList<SearchBook> {\n        body ?: throw NoStackTraceException(\n            appCtx.getString(\n                R.string.error_get_web_content,\n                analyzeUrl.ruleUrl\n            )\n        )\n        val bookList = ArrayList<SearchBook>()\n        Debug.log(bookSource.bookSourceUrl, \"≡获取成功:${analyzeUrl.ruleUrl}\")\n        Debug.log(bookSource.bookSourceUrl, body, state = 10)\n        val analyzeRule = AnalyzeRule(ruleData, bookSource)\n        analyzeRule.setContent(body).setBaseUrl(baseUrl)\n        analyzeRule.setRedirectUrl(baseUrl)\n        analyzeRule.setCoroutineContext(coroutineContext)\n        if (!isSearch) {\n            checkExploreJson(bookSource)\n        }\n        if (isSearch) bookSource.bookUrlPattern?.let {\n            coroutineContext.ensureActive()\n            if (baseUrl.matches(it.toRegex())) {\n                Debug.log(bookSource.bookSourceUrl, \"≡链接为详情页\")\n                getInfoItem(\n                    bookSource,\n                    analyzeRule,\n                    analyzeUrl,\n                    body,\n                    baseUrl,\n                    ruleData.getVariable(),\n                    isRedirect,\n                    filter\n                )?.let { searchBook ->\n                    searchBook.infoHtml = body\n                    bookList.add(searchBook)\n                }\n                return bookList\n            }\n        }\n        val collections: List<Any>\n        var reverse = false\n        val bookListRule: BookListRule = when {\n            isSearch -> bookSource.getSearchRule()\n            bookSource.getExploreRule().bookList.isNullOrBlank() -> bookSource.getSearchRule()\n            else -> bookSource.getExploreRule()\n        }\n        var ruleList: String = bookListRule.bookList ?: \"\"\n        if (ruleList.startsWith(\"-\")) {\n            reverse = true\n            ruleList = ruleList.substring(1)\n        }\n        if (ruleList.startsWith(\"+\")) {\n            ruleList = ruleList.substring(1)\n        }\n        Debug.log(bookSource.bookSourceUrl, \"┌获取书籍列表\")\n        collections = analyzeRule.getElements(ruleList)\n        coroutineContext.ensureActive()\n        if (collections.isEmpty() && bookSource.bookUrlPattern.isNullOrEmpty()) {\n            Debug.log(bookSource.bookSourceUrl, \"└列表为空,按详情页解析\")\n            getInfoItem(\n                bookSource, analyzeRule, analyzeUrl, body, baseUrl, ruleData.getVariable(),\n                isRedirect, filter\n            )?.let { searchBook ->\n                searchBook.infoHtml = body\n                bookList.add(searchBook)\n            }\n        } else {\n            val ruleName = analyzeRule.splitSourceRule(bookListRule.name)\n            val ruleBookUrl = analyzeRule.splitSourceRule(bookListRule.bookUrl)\n            val ruleAuthor = analyzeRule.splitSourceRule(bookListRule.author)\n            val ruleCoverUrl = analyzeRule.splitSourceRule(bookListRule.coverUrl)\n            val ruleIntro = analyzeRule.splitSourceRule(bookListRule.intro)\n            val ruleKind = analyzeRule.splitSourceRule(bookListRule.kind)\n            val ruleLastChapter = analyzeRule.splitSourceRule(bookListRule.lastChapter)\n            val ruleWordCount = analyzeRule.splitSourceRule(bookListRule.wordCount)\n            Debug.log(bookSource.bookSourceUrl, \"└列表大小:${collections.size}\")\n            for ((index, item) in collections.withIndex()) {\n                getSearchItem(\n                    bookSource, analyzeRule, item, baseUrl, ruleData.getVariable(),\n                    index == 0,\n                    filter,\n                    ruleName = ruleName,\n                    ruleBookUrl = ruleBookUrl,\n                    ruleAuthor = ruleAuthor,\n                    ruleCoverUrl = ruleCoverUrl,\n                    ruleIntro = ruleIntro,\n                    ruleKind = ruleKind,\n                    ruleLastChapter = ruleLastChapter,\n                    ruleWordCount = ruleWordCount\n                )?.let { searchBook ->\n                    if (baseUrl == searchBook.bookUrl) {\n                        searchBook.infoHtml = body\n                    }\n                    bookList.add(searchBook)\n                }\n                if (shouldBreak?.invoke(bookList.size) == true) {\n                    break\n                }\n            }\n            val lh = LinkedHashSet(bookList)\n            bookList.clear()\n            bookList.addAll(lh)\n            if (reverse) {\n                bookList.reverse()\n            }\n        }\n        Debug.log(bookSource.bookSourceUrl, \"◇书籍总数:${bookList.size}\")\n        return bookList\n    }\n\n    @Throws(Exception::class)\n    private suspend fun getInfoItem(\n        bookSource: BookSource,\n        analyzeRule: AnalyzeRule,\n        analyzeUrl: AnalyzeUrl,\n        body: String,\n        baseUrl: String,\n        variable: String?,\n        isRedirect: Boolean,\n        filter: ((name: String, author: String) -> Boolean)?\n    ): SearchBook? {\n        val book = Book(variable = variable)\n        book.bookUrl = if (isRedirect) {\n            baseUrl\n        } else {\n            NetworkUtils.getAbsoluteURL(analyzeUrl.url, analyzeUrl.ruleUrl)\n        }\n        book.origin = bookSource.bookSourceUrl\n        book.originName = bookSource.bookSourceName\n        book.originOrder = bookSource.customOrder\n        book.type = bookSource.getBookType()\n        analyzeRule.setRuleData(book)\n        BookInfo.analyzeBookInfo(\n            book,\n            body,\n            analyzeRule,\n            bookSource,\n            baseUrl,\n            baseUrl,\n            false\n        )\n        if (filter?.invoke(book.name, book.author) == false) {\n            return null\n        }\n        if (book.name.isNotBlank()) {\n            return book.toSearchBook()\n        }\n        return null\n    }\n\n    @Throws(Exception::class)\n    private suspend fun getSearchItem(\n        bookSource: BookSource,\n        analyzeRule: AnalyzeRule,\n        item: Any,\n        baseUrl: String,\n        variable: String?,\n        log: Boolean,\n        filter: ((name: String, author: String) -> Boolean)?,\n        ruleName: List<AnalyzeRule.SourceRule>,\n        ruleBookUrl: List<AnalyzeRule.SourceRule>,\n        ruleAuthor: List<AnalyzeRule.SourceRule>,\n        ruleKind: List<AnalyzeRule.SourceRule>,\n        ruleCoverUrl: List<AnalyzeRule.SourceRule>,\n        ruleWordCount: List<AnalyzeRule.SourceRule>,\n        ruleIntro: List<AnalyzeRule.SourceRule>,\n        ruleLastChapter: List<AnalyzeRule.SourceRule>\n    ): SearchBook? {\n        val searchBook = SearchBook(variable = variable)\n        searchBook.type = bookSource.getBookType()\n        searchBook.origin = bookSource.bookSourceUrl\n        searchBook.originName = bookSource.bookSourceName\n        searchBook.originOrder = bookSource.customOrder\n        analyzeRule.setRuleData(searchBook)\n        analyzeRule.setContent(item)\n        coroutineContext.ensureActive()\n        Debug.log(bookSource.bookSourceUrl, \"┌获取书名\", log)\n        searchBook.name = BookHelp.formatBookName(analyzeRule.getString(ruleName))\n        Debug.log(bookSource.bookSourceUrl, \"└${searchBook.name}\", log)\n        if (searchBook.name.isNotEmpty()) {\n            coroutineContext.ensureActive()\n            Debug.log(bookSource.bookSourceUrl, \"┌获取作者\", log)\n            searchBook.author = BookHelp.formatBookAuthor(analyzeRule.getString(ruleAuthor))\n            Debug.log(bookSource.bookSourceUrl, \"└${searchBook.author}\", log)\n            if (filter?.invoke(searchBook.name, searchBook.author) == false) {\n                return null\n            }\n            coroutineContext.ensureActive()\n            Debug.log(bookSource.bookSourceUrl, \"┌获取分类\", log)\n            try {\n                searchBook.kind = analyzeRule.getStringList(ruleKind)?.joinToString(\",\")\n                Debug.log(bookSource.bookSourceUrl, \"└${searchBook.kind ?: \"\"}\", log)\n            } catch (e: Exception) {\n                coroutineContext.ensureActive()\n                Debug.log(bookSource.bookSourceUrl, \"└${e.localizedMessage}\", log)\n            }\n            coroutineContext.ensureActive()\n            Debug.log(bookSource.bookSourceUrl, \"┌获取字数\", log)\n            try {\n                searchBook.wordCount = wordCountFormat(analyzeRule.getString(ruleWordCount))\n                Debug.log(bookSource.bookSourceUrl, \"└${searchBook.wordCount}\", log)\n            } catch (e: Exception) {\n                coroutineContext.ensureActive()\n                Debug.log(bookSource.bookSourceUrl, \"└${e.localizedMessage}\", log)\n            }\n            coroutineContext.ensureActive()\n            Debug.log(bookSource.bookSourceUrl, \"┌获取最新章节\", log)\n            try {\n                searchBook.latestChapterTitle = analyzeRule.getString(ruleLastChapter)\n                Debug.log(bookSource.bookSourceUrl, \"└${searchBook.latestChapterTitle}\", log)\n            } catch (e: Exception) {\n                coroutineContext.ensureActive()\n                Debug.log(bookSource.bookSourceUrl, \"└${e.localizedMessage}\", log)\n            }\n            coroutineContext.ensureActive()\n            Debug.log(bookSource.bookSourceUrl, \"┌获取简介\", log)\n            try {\n                searchBook.intro = HtmlFormatter.format(analyzeRule.getString(ruleIntro))\n                Debug.log(bookSource.bookSourceUrl, \"└${searchBook.intro}\", log)\n            } catch (e: Exception) {\n                coroutineContext.ensureActive()\n                Debug.log(bookSource.bookSourceUrl, \"└${e.localizedMessage}\", log)\n            }\n            coroutineContext.ensureActive()\n            Debug.log(bookSource.bookSourceUrl, \"┌获取封面链接\", log)\n            try {\n                analyzeRule.getString(ruleCoverUrl).let {\n                    if (it.isNotEmpty()) {\n                        searchBook.coverUrl = NetworkUtils.getAbsoluteURL(baseUrl, it)\n                    }\n                }\n                Debug.log(bookSource.bookSourceUrl, \"└${searchBook.coverUrl ?: \"\"}\", log)\n            } catch (e: Exception) {\n                coroutineContext.ensureActive()\n                Debug.log(bookSource.bookSourceUrl, \"└${e.localizedMessage}\", log)\n            }\n            coroutineContext.ensureActive()\n            Debug.log(bookSource.bookSourceUrl, \"┌获取详情页链接\", log)\n            searchBook.bookUrl = analyzeRule.getString(ruleBookUrl, isUrl = true)\n            if (searchBook.bookUrl.isEmpty()) {\n                searchBook.bookUrl = baseUrl\n            }\n            Debug.log(bookSource.bookSourceUrl, \"└${searchBook.bookUrl}\", log)\n            return searchBook\n        }\n        return null\n    }\n\n    private fun checkExploreJson(bookSource: BookSource) {\n        if (Debug.callback == null) {\n            return\n        }\n        val json = bookSource.exploreKindsJson()\n        if (json.isEmpty()) {\n            return\n        }\n        val kinds = GSONStrict.fromJsonArray<ExploreKind>(json).getOrNull()\n        if (kinds != null) {\n            return\n        }\n        GSON.fromJsonArray<ExploreKind>(json).getOrNull()?.let {\n            Debug.log(\"≡发现地址规则 JSON 格式不规范，请改为规范格式\")\n        }\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/model/webBook/SearchModel.kt",
    "content": "package io.legado.app.model.webBook\n\nimport io.legado.app.constant.AppConst\nimport io.legado.app.constant.AppLog\nimport io.legado.app.constant.PreferKey\nimport io.legado.app.data.appDb\nimport io.legado.app.data.entities.BookSourcePart\nimport io.legado.app.data.entities.SearchBook\nimport io.legado.app.exception.NoStackTraceException\nimport io.legado.app.help.config.AppConfig\nimport io.legado.app.ui.book.search.SearchScope\nimport io.legado.app.utils.getPrefBoolean\nimport io.legado.app.utils.mapParallelSafe\nimport kotlinx.coroutines.CoroutineScope\nimport kotlinx.coroutines.ExecutorCoroutineDispatcher\nimport kotlinx.coroutines.Job\nimport kotlinx.coroutines.asCoroutineDispatcher\nimport kotlinx.coroutines.currentCoroutineContext\nimport kotlinx.coroutines.ensureActive\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.flow.catch\nimport kotlinx.coroutines.flow.collect\nimport kotlinx.coroutines.flow.first\nimport kotlinx.coroutines.flow.flow\nimport kotlinx.coroutines.flow.onCompletion\nimport kotlinx.coroutines.flow.onEach\nimport kotlinx.coroutines.flow.onStart\nimport kotlinx.coroutines.launch\nimport kotlinx.coroutines.withTimeout\nimport splitties.init.appCtx\nimport java.util.concurrent.Executors\nimport kotlin.coroutines.coroutineContext\nimport kotlin.math.min\n\nclass SearchModel(private val scope: CoroutineScope, private val callBack: CallBack) {\n    val threadCount = AppConfig.threadCount\n    private var searchPool: ExecutorCoroutineDispatcher? = null\n    private var mSearchId = 0L\n    private var searchPage = 1\n    private var searchKey: String = \"\"\n    private var bookSourceParts = emptyList<BookSourcePart>()\n    private var searchBooks = arrayListOf<SearchBook>()\n    private var searchJob: Job? = null\n    private var workingState = MutableStateFlow(true)\n\n\n    private fun initSearchPool() {\n        searchPool?.close()\n        searchPool = Executors\n            .newFixedThreadPool(min(threadCount, AppConst.MAX_THREAD)).asCoroutineDispatcher()\n    }\n\n    fun search(searchId: Long, key: String) {\n        if (searchId != mSearchId) {\n            if (key.isEmpty()) {\n                return\n            }\n            searchKey = key\n            if (mSearchId != 0L) {\n                close()\n            }\n            searchBooks.clear()\n            bookSourceParts = callBack.getSearchScope().getBookSourceParts()\n            if (bookSourceParts.isEmpty()) {\n                callBack.onSearchCancel(NoStackTraceException(\"启用书源为空\"))\n                return\n            }\n            mSearchId = searchId\n            searchPage = 1\n            initSearchPool()\n        } else {\n            searchPage++\n        }\n        startSearch()\n    }\n\n    private fun startSearch() {\n        val precision = appCtx.getPrefBoolean(PreferKey.precisionSearch)\n        var hasMore = false\n        searchJob = scope.launch(searchPool!!) {\n            flow {\n                for (bs in bookSourceParts) {\n                    bs.getBookSource()?.let {\n                        emit(it)\n                    }\n                    workingState.first { it }\n                }\n            }.onStart {\n                callBack.onSearchStart()\n            }.mapParallelSafe(threadCount) {\n                withTimeout(30000L) {\n                    WebBook.searchBookAwait(\n                        it, searchKey, searchPage,\n                        filter = { name, author ->\n                            !precision || name.contains(searchKey) ||\n                                    author.contains(searchKey)\n                        })\n                }\n            }.onEach { items ->\n                for (book in items) {\n                    book.releaseHtmlData()\n                }\n                hasMore = hasMore || items.isNotEmpty()\n                appDb.searchBookDao.insert(*items.toTypedArray())\n                mergeItems(items, precision)\n                currentCoroutineContext().ensureActive()\n                callBack.onSearchSuccess(searchBooks)\n            }.onCompletion {\n                if (it == null) callBack.onSearchFinish(searchBooks.isEmpty(), hasMore)\n            }.catch {\n                AppLog.put(\"书源搜索出错\\n${it.localizedMessage}\", it)\n            }.collect()\n        }\n    }\n\n    private suspend fun mergeItems(newDataS: List<SearchBook>, precision: Boolean) {\n        if (newDataS.isNotEmpty()) {\n            val copyData = ArrayList(searchBooks)\n            val equalData = arrayListOf<SearchBook>()\n            val containsData = arrayListOf<SearchBook>()\n            val otherData = arrayListOf<SearchBook>()\n            copyData.forEach {\n                coroutineContext.ensureActive()\n                if (it.name == searchKey || it.author == searchKey) {\n                    equalData.add(it)\n                } else if (it.name.contains(searchKey) || it.author.contains(searchKey)) {\n                    containsData.add(it)\n                } else {\n                    otherData.add(it)\n                }\n            }\n            newDataS.forEach { nBook ->\n                coroutineContext.ensureActive()\n                if (nBook.name == searchKey || nBook.author == searchKey) {\n                    var hasSame = false\n                    equalData.forEach { pBook ->\n                        coroutineContext.ensureActive()\n                        if (pBook.name == nBook.name && pBook.author == nBook.author) {\n                            pBook.addOrigin(nBook.origin)\n                            hasSame = true\n                        }\n                    }\n                    if (!hasSame) {\n                        equalData.add(nBook)\n                    }\n                } else if (nBook.name.contains(searchKey) || nBook.author.contains(searchKey)) {\n                    var hasSame = false\n                    containsData.forEach { pBook ->\n                        coroutineContext.ensureActive()\n                        if (pBook.name == nBook.name && pBook.author == nBook.author) {\n                            pBook.addOrigin(nBook.origin)\n                            hasSame = true\n                        }\n                    }\n                    if (!hasSame) {\n                        containsData.add(nBook)\n                    }\n                } else if (!precision) {\n                    var hasSame = false\n                    otherData.forEach { pBook ->\n                        coroutineContext.ensureActive()\n                        if (pBook.name == nBook.name && pBook.author == nBook.author) {\n                            pBook.addOrigin(nBook.origin)\n                            hasSame = true\n                        }\n                    }\n                    if (!hasSame) {\n                        otherData.add(nBook)\n                    }\n                }\n            }\n            coroutineContext.ensureActive()\n            equalData.sortByDescending { it.origins.size }\n            equalData.addAll(containsData.sortedByDescending { it.origins.size })\n            if (!precision) {\n                equalData.addAll(otherData)\n            }\n            coroutineContext.ensureActive()\n            searchBooks = equalData\n        }\n    }\n\n    fun pause() {\n        workingState.value = false\n    }\n\n    fun resume() {\n        workingState.value = true\n    }\n\n    fun cancelSearch() {\n        close()\n        callBack.onSearchCancel()\n    }\n\n    fun close() {\n        searchJob?.cancel()\n        searchPool?.close()\n        searchPool = null\n        mSearchId = 0L\n    }\n\n    interface CallBack {\n        fun getSearchScope(): SearchScope\n        fun onSearchStart()\n        fun onSearchSuccess(searchBooks: List<SearchBook>)\n        fun onSearchFinish(isEmpty: Boolean, hasMore: Boolean)\n        fun onSearchCancel(exception: Throwable? = null)\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/model/webBook/WebBook.kt",
    "content": "package io.legado.app.model.webBook\n\nimport io.legado.app.constant.AppLog\nimport io.legado.app.data.entities.Book\nimport io.legado.app.data.entities.BookChapter\nimport io.legado.app.data.entities.BookSource\nimport io.legado.app.data.entities.BookSourcePart\nimport io.legado.app.data.entities.SearchBook\nimport io.legado.app.exception.NoStackTraceException\nimport io.legado.app.help.book.addType\nimport io.legado.app.help.book.removeAllBookType\nimport io.legado.app.help.coroutine.Coroutine\nimport io.legado.app.help.http.StrResponse\nimport io.legado.app.help.source.getBookType\nimport io.legado.app.model.Debug\nimport io.legado.app.model.analyzeRule.AnalyzeRule\nimport io.legado.app.model.analyzeRule.AnalyzeRule.Companion.setCoroutineContext\nimport io.legado.app.model.analyzeRule.AnalyzeUrl\nimport io.legado.app.model.analyzeRule.RuleData\nimport kotlinx.coroutines.CoroutineScope\nimport kotlinx.coroutines.CoroutineStart\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.ensureActive\nimport kotlinx.coroutines.sync.Semaphore\nimport kotlin.coroutines.CoroutineContext\nimport kotlin.coroutines.coroutineContext\n\n@Suppress(\"MemberVisibilityCanBePrivate\")\nobject WebBook {\n\n    /**\n     * 搜索\n     */\n    fun searchBook(\n        scope: CoroutineScope,\n        bookSource: BookSource,\n        key: String,\n        page: Int? = 1,\n        context: CoroutineContext = Dispatchers.IO,\n        start: CoroutineStart = CoroutineStart.DEFAULT,\n        executeContext: CoroutineContext = Dispatchers.Main,\n    ): Coroutine<ArrayList<SearchBook>> {\n        return Coroutine.async(scope, context, start = start, executeContext = executeContext) {\n            searchBookAwait(bookSource, key, page)\n        }\n    }\n\n    suspend fun searchBookAwait(\n        bookSource: BookSource,\n        key: String,\n        page: Int? = 1,\n        filter: ((name: String, author: String) -> Boolean)? = null,\n        shouldBreak: ((size: Int) -> Boolean)? = null\n    ): ArrayList<SearchBook> {\n        val searchUrl = bookSource.searchUrl\n        if (searchUrl.isNullOrBlank()) {\n            throw NoStackTraceException(\"搜索url不能为空\")\n        }\n        val ruleData = RuleData()\n        val analyzeUrl = AnalyzeUrl(\n            mUrl = searchUrl,\n            key = key,\n            page = page,\n            baseUrl = bookSource.bookSourceUrl,\n            source = bookSource,\n            ruleData = ruleData,\n            coroutineContext = coroutineContext\n        )\n        var res = analyzeUrl.getStrResponseAwait()\n        //检测书源是否已登录\n        bookSource.loginCheckJs?.let { checkJs ->\n            if (checkJs.isNotBlank()) {\n                res = analyzeUrl.evalJS(checkJs, res) as StrResponse\n            }\n        }\n        checkRedirect(bookSource, res)\n        return BookList.analyzeBookList(\n            bookSource = bookSource,\n            ruleData = ruleData,\n            analyzeUrl = analyzeUrl,\n            baseUrl = res.url,\n            body = res.body,\n            isSearch = true,\n            isRedirect = res.raw.priorResponse?.isRedirect == true,\n            filter = filter,\n            shouldBreak = shouldBreak\n        )\n    }\n\n    /**\n     * 发现\n     */\n    fun exploreBook(\n        scope: CoroutineScope,\n        bookSource: BookSource,\n        url: String,\n        page: Int? = 1,\n        context: CoroutineContext = Dispatchers.IO,\n    ): Coroutine<List<SearchBook>> {\n        return Coroutine.async(scope, context) {\n            exploreBookAwait(bookSource, url, page)\n        }\n    }\n\n    suspend fun exploreBookAwait(\n        bookSource: BookSource,\n        url: String,\n        page: Int? = 1,\n    ): ArrayList<SearchBook> {\n        val ruleData = RuleData()\n        val analyzeUrl = AnalyzeUrl(\n            mUrl = url,\n            page = page,\n            baseUrl = bookSource.bookSourceUrl,\n            source = bookSource,\n            ruleData = ruleData,\n            coroutineContext = coroutineContext\n        )\n        var res = analyzeUrl.getStrResponseAwait()\n        //检测书源是否已登录\n        bookSource.loginCheckJs?.let { checkJs ->\n            if (checkJs.isNotBlank()) {\n                res = analyzeUrl.evalJS(checkJs, result = res) as StrResponse\n            }\n        }\n        checkRedirect(bookSource, res)\n        return BookList.analyzeBookList(\n            bookSource = bookSource,\n            ruleData = ruleData,\n            analyzeUrl = analyzeUrl,\n            baseUrl = res.url,\n            body = res.body,\n            isSearch = false\n        )\n    }\n\n    /**\n     * 书籍信息\n     */\n    fun getBookInfo(\n        scope: CoroutineScope,\n        bookSource: BookSource,\n        book: Book,\n        context: CoroutineContext = Dispatchers.IO,\n        canReName: Boolean = true,\n    ): Coroutine<Book> {\n        return Coroutine.async(scope, context) {\n            getBookInfoAwait(bookSource, book, canReName)\n        }\n    }\n\n    suspend fun getBookInfoAwait(\n        bookSource: BookSource,\n        book: Book,\n        canReName: Boolean = true,\n    ): Book {\n        book.removeAllBookType()\n        book.addType(bookSource.getBookType())\n        if (!book.infoHtml.isNullOrEmpty()) {\n            BookInfo.analyzeBookInfo(\n                bookSource = bookSource,\n                book = book,\n                baseUrl = book.bookUrl,\n                redirectUrl = book.bookUrl,\n                body = book.infoHtml,\n                canReName = canReName\n            )\n        } else {\n            val analyzeUrl = AnalyzeUrl(\n                mUrl = book.bookUrl,\n                baseUrl = bookSource.bookSourceUrl,\n                source = bookSource,\n                ruleData = book,\n                coroutineContext = coroutineContext\n            )\n            var res = analyzeUrl.getStrResponseAwait()\n            //检测书源是否已登录\n            bookSource.loginCheckJs?.let { checkJs ->\n                if (checkJs.isNotBlank()) {\n                    res = analyzeUrl.evalJS(checkJs, result = res) as StrResponse\n                }\n            }\n            checkRedirect(bookSource, res)\n            BookInfo.analyzeBookInfo(\n                bookSource = bookSource,\n                book = book,\n                baseUrl = book.bookUrl,\n                redirectUrl = res.url,\n                body = res.body,\n                canReName = canReName\n            )\n        }\n        return book\n    }\n\n    /**\n     * 目录\n     */\n    fun getChapterList(\n        scope: CoroutineScope,\n        bookSource: BookSource,\n        book: Book,\n        runPerJs: Boolean = false,\n        context: CoroutineContext = Dispatchers.IO\n    ): Coroutine<List<BookChapter>> {\n        return Coroutine.async(scope, context) {\n            getChapterListAwait(bookSource, book, runPerJs).getOrThrow()\n        }\n    }\n\n    suspend fun runPreUpdateJs(bookSource: BookSource, book: Book): Result<Unit> {\n        return kotlin.runCatching {\n            val preUpdateJs = bookSource.ruleToc?.preUpdateJs\n            if (!preUpdateJs.isNullOrBlank()) {\n                AnalyzeRule(book, bookSource, true)\n                    .setCoroutineContext(coroutineContext)\n                    .evalJS(preUpdateJs)\n            }\n        }.onFailure {\n            coroutineContext.ensureActive()\n            AppLog.put(\"执行preUpdateJs规则失败 书源:${bookSource.bookSourceName}\", it)\n        }\n    }\n\n    suspend fun getChapterListAwait(\n        bookSource: BookSource,\n        book: Book,\n        runPerJs: Boolean = false\n    ): Result<List<BookChapter>> {\n        book.removeAllBookType()\n        book.addType(bookSource.getBookType())\n        return kotlin.runCatching {\n            if (runPerJs) {\n                runPreUpdateJs(bookSource, book).getOrThrow()\n            }\n            if (book.bookUrl == book.tocUrl && !book.tocHtml.isNullOrEmpty()) {\n                BookChapterList.analyzeChapterList(\n                    bookSource = bookSource,\n                    book = book,\n                    baseUrl = book.tocUrl,\n                    redirectUrl = book.tocUrl,\n                    body = book.tocHtml\n                )\n            } else {\n                val analyzeUrl = AnalyzeUrl(\n                    mUrl = book.tocUrl,\n                    baseUrl = book.bookUrl,\n                    source = bookSource,\n                    ruleData = book,\n                    coroutineContext = coroutineContext\n                )\n                var res = analyzeUrl.getStrResponseAwait()\n                //检测书源是否已登录\n                bookSource.loginCheckJs?.let { checkJs ->\n                    if (checkJs.isNotBlank()) {\n                        res = analyzeUrl.evalJS(checkJs, result = res) as StrResponse\n                    }\n                }\n                checkRedirect(bookSource, res)\n                BookChapterList.analyzeChapterList(\n                    bookSource = bookSource,\n                    book = book,\n                    baseUrl = book.tocUrl,\n                    redirectUrl = res.url,\n                    body = res.body\n                )\n            }\n        }.onFailure {\n            coroutineContext.ensureActive()\n        }\n    }\n\n    /**\n     * 章节内容\n     */\n    fun getContent(\n        scope: CoroutineScope,\n        bookSource: BookSource,\n        book: Book,\n        bookChapter: BookChapter,\n        nextChapterUrl: String? = null,\n        needSave: Boolean = true,\n        context: CoroutineContext = Dispatchers.IO,\n        start: CoroutineStart = CoroutineStart.DEFAULT,\n        executeContext: CoroutineContext = Dispatchers.Main,\n        semaphore: Semaphore? = null,\n    ): Coroutine<String> {\n        return Coroutine.async(\n            scope,\n            context,\n            start = start,\n            executeContext = executeContext,\n            semaphore = semaphore\n        ) {\n            getContentAwait(bookSource, book, bookChapter, nextChapterUrl, needSave)\n        }\n    }\n\n    suspend fun getContentAwait(\n        bookSource: BookSource,\n        book: Book,\n        bookChapter: BookChapter,\n        nextChapterUrl: String? = null,\n        needSave: Boolean = true\n    ): String {\n        if (bookSource.getContentRule().content.isNullOrEmpty()) {\n            Debug.log(bookSource.bookSourceUrl, \"⇒正文规则为空,使用章节链接:${bookChapter.url}\")\n            return bookChapter.url\n        }\n        if (bookChapter.isVolume && bookChapter.url.startsWith(bookChapter.title)) {\n            Debug.log(bookSource.bookSourceUrl, \"⇒一级目录正文不解析规则\")\n            return bookChapter.tag ?: \"\"\n        }\n        return if (bookChapter.url == book.bookUrl && !book.tocHtml.isNullOrEmpty()) {\n            BookContent.analyzeContent(\n                bookSource = bookSource,\n                book = book,\n                bookChapter = bookChapter,\n                baseUrl = bookChapter.getAbsoluteURL(),\n                redirectUrl = bookChapter.getAbsoluteURL(),\n                body = book.tocHtml,\n                nextChapterUrl = nextChapterUrl,\n                needSave = needSave\n            )\n        } else {\n            val analyzeUrl = AnalyzeUrl(\n                mUrl = bookChapter.getAbsoluteURL(),\n                baseUrl = book.tocUrl,\n                source = bookSource,\n                ruleData = book,\n                chapter = bookChapter,\n                coroutineContext = coroutineContext\n            )\n            var res = analyzeUrl.getStrResponseAwait(\n                jsStr = bookSource.getContentRule().webJs,\n                sourceRegex = bookSource.getContentRule().sourceRegex\n            )\n            //检测书源是否已登录\n            bookSource.loginCheckJs?.let { checkJs ->\n                if (checkJs.isNotBlank()) {\n                    res = analyzeUrl.evalJS(checkJs, result = res) as StrResponse\n                }\n            }\n            checkRedirect(bookSource, res)\n            BookContent.analyzeContent(\n                bookSource = bookSource,\n                book = book,\n                bookChapter = bookChapter,\n                baseUrl = bookChapter.getAbsoluteURL(),\n                redirectUrl = res.url,\n                body = res.body,\n                nextChapterUrl = nextChapterUrl,\n                needSave = needSave\n            )\n        }\n    }\n\n    /**\n     * 精准搜索\n     */\n    fun preciseSearch(\n        scope: CoroutineScope,\n        bookSourceParts: List<BookSourcePart>,\n        name: String,\n        author: String,\n        context: CoroutineContext = Dispatchers.IO,\n        semaphore: Semaphore? = null,\n    ): Coroutine<Pair<Book, BookSource>> {\n        return Coroutine.async(scope, context, semaphore = semaphore) {\n            for (s in bookSourceParts) {\n                val source = s.getBookSource() ?: continue\n                val book = preciseSearchAwait(source, name, author).getOrNull()\n                if (book != null) {\n                    return@async Pair(book, source)\n                }\n            }\n            throw NoStackTraceException(\"没有搜索到<$name>$author\")\n        }\n    }\n\n    suspend fun preciseSearchAwait(\n        bookSource: BookSource,\n        name: String,\n        author: String,\n    ): Result<Book> {\n        return kotlin.runCatching {\n            coroutineContext.ensureActive()\n            searchBookAwait(\n                bookSource, name,\n                filter = { fName, fAuthor -> fName == name && fAuthor == author },\n                shouldBreak = { it > 0 }\n            ).firstOrNull()?.let { searchBook ->\n                coroutineContext.ensureActive()\n                return@runCatching searchBook.toBook()\n            }\n            throw NoStackTraceException(\"未搜索到 $name($author) 书籍\")\n        }.onFailure {\n            coroutineContext.ensureActive()\n        }\n    }\n\n    /**\n     * 检测重定向\n     */\n    private fun checkRedirect(bookSource: BookSource, response: StrResponse) {\n        response.raw.priorResponse?.let {\n            if (it.isRedirect) {\n                Debug.log(bookSource.bookSourceUrl, \"≡检测到重定向(${it.code})\")\n                Debug.log(bookSource.bookSourceUrl, \"┌重定向后地址\")\n                Debug.log(bookSource.bookSourceUrl, \"└${response.url}\")\n            }\n        }\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/receiver/MediaButtonReceiver.kt",
    "content": "package io.legado.app.receiver\n\nimport android.content.BroadcastReceiver\nimport android.content.Context\nimport android.content.Intent\nimport android.view.KeyEvent\nimport io.legado.app.constant.EventBus\nimport io.legado.app.data.appDb\nimport io.legado.app.help.LifecycleHelp\nimport io.legado.app.help.config.AppConfig\nimport io.legado.app.model.AudioPlay\nimport io.legado.app.model.ReadAloud\nimport io.legado.app.model.ReadBook\nimport io.legado.app.service.AudioPlayService\nimport io.legado.app.service.BaseReadAloudService\nimport io.legado.app.ui.book.audio.AudioPlayActivity\nimport io.legado.app.ui.book.read.ReadBookActivity\nimport io.legado.app.utils.LogUtils\nimport io.legado.app.utils.getPrefBoolean\nimport io.legado.app.utils.postEvent\n\n\n/**\n * Created by GKF on 2018/1/6.\n * 监听耳机键\n */\nclass MediaButtonReceiver : BroadcastReceiver() {\n\n    override fun onReceive(context: Context, intent: Intent) {\n        if (handleIntent(context, intent) && isOrderedBroadcast) {\n            abortBroadcast()\n        }\n    }\n\n    companion object {\n\n        private const val TAG = \"MediaButtonReceiver\"\n\n        fun handleIntent(context: Context, intent: Intent): Boolean {\n            val intentAction = intent.action\n            if (Intent.ACTION_MEDIA_BUTTON == intentAction) {\n                @Suppress(\"DEPRECATION\")\n                val keyEvent = intent.getParcelableExtra<KeyEvent>(Intent.EXTRA_KEY_EVENT)\n                    ?: return false\n                val keycode: Int = keyEvent.keyCode\n                val action: Int = keyEvent.action\n                if (action == KeyEvent.ACTION_DOWN) {\n                    LogUtils.d(TAG, \"Receive mediaButton event, keycode:$keycode\")\n                    when (keycode) {\n                        KeyEvent.KEYCODE_MEDIA_PREVIOUS -> {\n                            if (context.getPrefBoolean(\"mediaButtonPerNext\", false)) {\n                                ReadBook.moveToPrevChapter(true)\n                            } else {\n                                ReadAloud.prevParagraph(context)\n                            }\n                        }\n\n                        KeyEvent.KEYCODE_MEDIA_NEXT -> {\n                            if (context.getPrefBoolean(\"mediaButtonPerNext\", false)) {\n                                ReadBook.moveToNextChapter(true)\n                            } else {\n                                ReadAloud.nextParagraph(context)\n                            }\n                        }\n\n                        else -> readAloud(context)\n                    }\n                }\n            }\n            return true\n        }\n\n        fun readAloud(context: Context, isMediaKey: Boolean = true) {\n            when {\n                BaseReadAloudService.isRun -> {\n                    if (BaseReadAloudService.isPlay()) {\n                        ReadAloud.pause(context)\n                        AudioPlay.pause(context)\n                    } else {\n                        ReadAloud.resume(context)\n                        AudioPlay.resume(context)\n                    }\n                }\n\n                AudioPlayService.isRun -> {\n                    if (AudioPlayService.pause) {\n                        AudioPlay.resume(context)\n                    } else {\n                        AudioPlay.pause(context)\n                    }\n                }\n\n                isMediaKey && !AppConfig.readAloudByMediaButton -> {\n                    // break\n                }\n\n                LifecycleHelp.isExistActivity(ReadBookActivity::class.java) ->\n                    postEvent(EventBus.MEDIA_BUTTON, true)\n\n                LifecycleHelp.isExistActivity(AudioPlayActivity::class.java) ->\n                    postEvent(EventBus.MEDIA_BUTTON, true)\n\n                else -> if (AppConfig.mediaButtonOnExit || LifecycleHelp.activitySize() > 0 || !isMediaKey) {\n                    ReadAloud.upReadAloudClass()\n                    if (ReadBook.book != null) {\n                        ReadBook.readAloud()\n                    } else {\n                        appDb.bookDao.lastReadBook?.let {\n                            ReadBook.resetData(it)\n                            ReadBook.clearTextChapter()\n                            ReadBook.loadContent(false) {\n                                ReadBook.readAloud()\n                            }\n                        }\n                    }\n                }\n            }\n        }\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/receiver/NetworkChangedListener.kt",
    "content": "package io.legado.app.receiver\n\nimport android.annotation.SuppressLint\nimport android.content.BroadcastReceiver\nimport android.content.Context\nimport android.content.Intent\nimport android.content.IntentFilter\nimport android.net.ConnectivityManager\nimport android.net.Network\nimport android.os.Build\nimport splitties.systemservices.connectivityManager\n\n/**\n * 监测网络变化\n */\n@SuppressLint(\"ObsoleteSdkInt\")\nclass NetworkChangedListener(private val context: Context) {\n\n    var onNetworkChanged: (() -> Unit)? = null\n\n    private val receiver: NetworkChangedReceiver? by lazy {\n        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {\n            NetworkChangedReceiver()\n        }\n        return@lazy null\n    }\n\n    private val networkCallback: ConnectivityManager.NetworkCallback? by lazy {\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {\n            return@lazy object : ConnectivityManager.NetworkCallback() {\n                override fun onAvailable(network: Network) {\n                    onNetworkChanged?.invoke()\n                }\n            }\n        }\n        return@lazy null\n    }\n\n    @SuppressLint(\"MissingPermission\", \"UnspecifiedRegisterReceiverFlag\")\n    fun register() {\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {\n            networkCallback?.let {\n                connectivityManager.registerDefaultNetworkCallback(it)\n            }\n        } else {\n            receiver?.let {\n                context.registerReceiver(it, it.filter)\n            }\n        }\n    }\n\n    fun unRegister() {\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {\n            networkCallback?.let {\n                connectivityManager.unregisterNetworkCallback(it)\n            }\n        } else {\n            receiver?.let {\n                context.unregisterReceiver(it)\n            }\n        }\n    }\n\n    inner class NetworkChangedReceiver : BroadcastReceiver() {\n\n        val filter = IntentFilter().apply {\n            @Suppress(\"DEPRECATION\")\n            addAction(ConnectivityManager.CONNECTIVITY_ACTION)\n        }\n\n        override fun onReceive(context: Context, intent: Intent) {\n            onNetworkChanged?.invoke()\n        }\n\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/receiver/SharedReceiverActivity.kt",
    "content": "package io.legado.app.receiver\n\nimport android.annotation.SuppressLint\nimport android.content.Intent\nimport android.os.Build\nimport android.os.Bundle\nimport androidx.appcompat.app.AppCompatActivity\nimport io.legado.app.ui.book.search.SearchActivity\nimport io.legado.app.ui.main.MainActivity\nimport io.legado.app.utils.startActivity\nimport splitties.init.appCtx\n\nclass SharedReceiverActivity : AppCompatActivity() {\n\n    private val receivingType = \"text/plain\"\n\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n        initIntent()\n        finish()\n    }\n\n    @SuppressLint(\"ObsoleteSdkInt\")\n    private fun initIntent() {\n        when {\n            intent.action == Intent.ACTION_SEND && intent.type == receivingType -> {\n                intent.getStringExtra(Intent.EXTRA_TEXT)?.let {\n                    dispose(it)\n                }\n            }\n            Build.VERSION.SDK_INT >= Build.VERSION_CODES.M\n                    && intent.action == Intent.ACTION_PROCESS_TEXT\n                    && intent.type == receivingType -> {\n                intent.getStringExtra(Intent.EXTRA_PROCESS_TEXT)?.let {\n                    dispose(it)\n                }\n            }\n            intent.getStringExtra(\"action\") == \"readAloud\" -> {\n                MediaButtonReceiver.readAloud(appCtx, false)\n            }\n        }\n    }\n\n    private fun dispose(text: String) {\n        if (text.isBlank()) {\n            return\n        }\n        val urls = text.split(\"\\\\s\".toRegex()).dropLastWhile { it.isEmpty() }.toTypedArray()\n        val result = StringBuilder()\n        for (url in urls) {\n            if (url.matches(\"http.+\".toRegex()))\n                result.append(\"\\n\").append(url.trim { it <= ' ' })\n        }\n        if (result.length > 1) {\n            startActivity<MainActivity>()\n        } else {\n            SearchActivity.start(this, text)\n        }\n    }\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/receiver/TimeBatteryReceiver.kt",
    "content": "package io.legado.app.receiver\n\nimport android.content.BroadcastReceiver\nimport android.content.Context\nimport android.content.Intent\nimport android.content.IntentFilter\nimport android.os.BatteryManager\nimport io.legado.app.constant.EventBus\nimport io.legado.app.utils.postEvent\n\n\nclass TimeBatteryReceiver : BroadcastReceiver() {\n\n    val filter = IntentFilter().apply {\n        addAction(Intent.ACTION_TIME_TICK)\n        addAction(Intent.ACTION_BATTERY_CHANGED)\n    }\n\n    override fun onReceive(context: Context, intent: Intent) {\n        when (intent.action) {\n            Intent.ACTION_TIME_TICK -> {\n                postEvent(EventBus.TIME_CHANGED, \"\")\n            }\n            Intent.ACTION_BATTERY_CHANGED -> {\n                val level = intent.getIntExtra(BatteryManager.EXTRA_LEVEL, -1)\n                postEvent(EventBus.BATTERY_CHANGED, level)\n            }\n        }\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/service/AudioPlayService.kt",
    "content": "package io.legado.app.service\n\nimport android.annotation.SuppressLint\nimport android.content.BroadcastReceiver\nimport android.content.Context\nimport android.content.Intent\nimport android.content.IntentFilter\nimport android.graphics.Bitmap\nimport android.graphics.BitmapFactory\nimport android.media.AudioManager\nimport android.net.wifi.WifiManager.WIFI_MODE_FULL_HIGH_PERF\nimport android.os.Build\nimport android.os.Bundle\nimport android.os.PowerManager\nimport android.support.v4.media.MediaMetadataCompat\nimport android.support.v4.media.session.MediaSessionCompat\nimport android.support.v4.media.session.PlaybackStateCompat\nimport androidx.core.app.NotificationCompat\nimport androidx.lifecycle.lifecycleScope\nimport androidx.media.AudioFocusRequestCompat\nimport androidx.media3.common.PlaybackException\nimport androidx.media3.common.Player\nimport androidx.media3.exoplayer.ExoPlayer\nimport io.legado.app.R\nimport io.legado.app.base.BaseService\nimport io.legado.app.constant.AppConst\nimport io.legado.app.constant.AppLog\nimport io.legado.app.constant.EventBus\nimport io.legado.app.constant.IntentAction\nimport io.legado.app.constant.NotificationId\nimport io.legado.app.constant.Status\nimport io.legado.app.help.MediaHelp\nimport io.legado.app.help.config.AppConfig\nimport io.legado.app.help.coroutine.Coroutine\nimport io.legado.app.help.exoplayer.ExoPlayerHelper\nimport io.legado.app.help.glide.ImageLoader\nimport io.legado.app.model.AudioPlay\nimport io.legado.app.model.analyzeRule.AnalyzeUrl\nimport io.legado.app.model.analyzeRule.AnalyzeUrl.Companion.getMediaItem\nimport io.legado.app.receiver.MediaButtonReceiver\nimport io.legado.app.ui.book.audio.AudioPlayActivity\nimport io.legado.app.utils.activityPendingIntent\nimport io.legado.app.utils.broadcastPendingIntent\nimport io.legado.app.utils.postEvent\nimport io.legado.app.utils.printOnDebug\nimport io.legado.app.utils.servicePendingIntent\nimport io.legado.app.utils.toastOnUi\nimport kotlinx.coroutines.Dispatchers.Main\nimport kotlinx.coroutines.Job\nimport kotlinx.coroutines.delay\nimport kotlinx.coroutines.isActive\nimport kotlinx.coroutines.launch\nimport splitties.init.appCtx\nimport splitties.systemservices.audioManager\nimport splitties.systemservices.notificationManager\nimport splitties.systemservices.powerManager\nimport splitties.systemservices.wifiManager\n\n/**\n * 音频播放服务\n */\nclass AudioPlayService : BaseService(),\n    AudioManager.OnAudioFocusChangeListener,\n    Player.Listener {\n\n    companion object {\n        @JvmStatic\n        var isRun = false\n            private set\n\n        @JvmStatic\n        var pause = true\n            private set\n\n        @JvmStatic\n        var timeMinute: Int = 0\n\n        var url: String = \"\"\n            private set\n\n        private const val MEDIA_SESSION_ACTIONS = (PlaybackStateCompat.ACTION_PLAY\n                or PlaybackStateCompat.ACTION_PAUSE\n                or PlaybackStateCompat.ACTION_PLAY_PAUSE\n                or PlaybackStateCompat.ACTION_SEEK_TO)\n\n        private const val APP_ACTION_STOP = \"Stop\"\n        private const val APP_ACTION_TIMER = \"Timer\"\n    }\n\n    private val useWakeLock = AppConfig.audioPlayUseWakeLock\n    private val wakeLock by lazy {\n        powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, \"legado:AudioPlayService\")\n            .apply {\n                this.setReferenceCounted(false)\n            }\n    }\n    private val wifiLock by lazy {\n        @Suppress(\"DEPRECATION\")\n        wifiManager?.createWifiLock(WIFI_MODE_FULL_HIGH_PERF, \"legado:AudioPlayService\")?.apply {\n            setReferenceCounted(false)\n        }\n    }\n    private val mFocusRequest: AudioFocusRequestCompat by lazy {\n        MediaHelp.buildAudioFocusRequestCompat(this)\n    }\n    private val exoPlayer: ExoPlayer by lazy {\n        ExoPlayerHelper.createHttpExoPlayer(this)\n    }\n    private var mediaSessionCompat: MediaSessionCompat? = null\n    private var broadcastReceiver: BroadcastReceiver? = null\n    private var needResumeOnAudioFocusGain = false\n    private var position = AudioPlay.book?.durChapterPos ?: 0\n    private var dsJob: Job? = null\n    private var upNotificationJob: Coroutine<*>? = null\n    private var upPlayProgressJob: Job? = null\n    private var playSpeed: Float = 1f\n    private var cover: Bitmap =\n        BitmapFactory.decodeResource(appCtx.resources, R.drawable.icon_read_book)\n\n    override fun onCreate() {\n        super.onCreate()\n        isRun = true\n        exoPlayer.addListener(this)\n        AudioPlay.registerService(this)\n        initMediaSession()\n        initBroadcastReceiver()\n        upMediaSessionPlaybackState(PlaybackStateCompat.STATE_PLAYING)\n        doDs()\n        execute {\n            ImageLoader\n                .loadBitmap(this@AudioPlayService, AudioPlay.book?.getDisplayCover())\n                .submit()\n                .get()\n        }.onSuccess {\n            if (it.width > 16 && it.height > 16) {\n                cover = it\n                upMediaMetadata()\n                upAudioPlayNotification()\n            }\n        }\n    }\n\n    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {\n        intent?.action?.let { action ->\n            when (action) {\n                IntentAction.play -> {\n                    exoPlayer.stop()\n                    upPlayProgressJob?.cancel()\n                    pause = false\n                    position = AudioPlay.book?.durChapterPos ?: 0\n                    url = AudioPlay.durPlayUrl\n                    play()\n                }\n\n                IntentAction.playNew -> {\n                    exoPlayer.stop()\n                    upPlayProgressJob?.cancel()\n                    pause = false\n                    position = 0\n                    url = AudioPlay.durPlayUrl\n                    play()\n                }\n\n                IntentAction.stopPlay -> {\n                    exoPlayer.stop()\n                    upPlayProgressJob?.cancel()\n                    AudioPlay.status = Status.STOP\n                    postEvent(EventBus.AUDIO_STATE, Status.STOP)\n                }\n\n                IntentAction.pause -> pause()\n                IntentAction.resume -> resume()\n                IntentAction.prev -> AudioPlay.prev()\n                IntentAction.next -> AudioPlay.next()\n                IntentAction.adjustSpeed -> upSpeed(intent.getFloatExtra(\"adjust\", 1f))\n                IntentAction.addTimer -> addTimer()\n                IntentAction.setTimer -> setTimer(intent.getIntExtra(\"minute\", 0))\n                IntentAction.adjustProgress -> {\n                    adjustProgress(intent.getIntExtra(\"position\", position))\n                }\n\n                IntentAction.stop -> stopSelf()\n            }\n        }\n        return super.onStartCommand(intent, flags, startId)\n    }\n\n    override fun onDestroy() {\n        super.onDestroy()\n        if (useWakeLock) {\n            wakeLock.release()\n            wifiLock?.release()\n        }\n        isRun = false\n        abandonFocus()\n        exoPlayer.release()\n        mediaSessionCompat?.release()\n        unregisterReceiver(broadcastReceiver)\n        upMediaSessionPlaybackState(PlaybackStateCompat.STATE_STOPPED)\n        AudioPlay.status = Status.STOP\n        postEvent(EventBus.AUDIO_STATE, Status.STOP)\n        AudioPlay.unregisterService()\n        upNotificationJob?.invokeOnCompletion {\n            notificationManager.cancel(NotificationId.AudioPlayService)\n        }\n    }\n\n    /**\n     * 播放音频\n     */\n    @SuppressLint(\"WakelockTimeout\")\n    private fun play() {\n        if (useWakeLock) {\n            wakeLock.acquire()\n            wifiLock?.acquire()\n        }\n        upAudioPlayNotification()\n        if (!requestFocus()) {\n            return\n        }\n        execute(context = Main) {\n            AudioPlay.status = Status.STOP\n            postEvent(EventBus.AUDIO_STATE, Status.STOP)\n            upPlayProgressJob?.cancel()\n            val analyzeUrl = AnalyzeUrl(\n                url,\n                source = AudioPlay.bookSource,\n                ruleData = AudioPlay.book,\n                chapter = AudioPlay.durChapter,\n                coroutineContext = coroutineContext\n            )\n            exoPlayer.setMediaItem(analyzeUrl.getMediaItem())\n            exoPlayer.playWhenReady = true\n            exoPlayer.seekTo(position.toLong())\n            exoPlayer.prepare()\n        }.onError {\n            AppLog.put(\"播放出错\\n${it.localizedMessage}\", it)\n            toastOnUi(\"$url ${it.localizedMessage}\")\n            stopSelf()\n        }\n    }\n\n    /**\n     * 暂停播放\n     */\n    private fun pause(abandonFocus: Boolean = true) {\n        if (useWakeLock) {\n            wakeLock.release()\n            wifiLock?.release()\n        }\n        try {\n            pause = true\n            if (abandonFocus) {\n                abandonFocus()\n            }\n            upPlayProgressJob?.cancel()\n            position = exoPlayer.currentPosition.toInt()\n            if (exoPlayer.isPlaying) exoPlayer.pause()\n            upMediaSessionPlaybackState(PlaybackStateCompat.STATE_PAUSED)\n            AudioPlay.status = Status.PAUSE\n            postEvent(EventBus.AUDIO_STATE, Status.PAUSE)\n            upAudioPlayNotification()\n        } catch (e: Exception) {\n            e.printOnDebug()\n        }\n    }\n\n    /**\n     * 恢复播放\n     */\n    @SuppressLint(\"WakelockTimeout\")\n    private fun resume() {\n        if (useWakeLock) {\n            wakeLock.acquire()\n            wifiLock?.acquire()\n        }\n        try {\n            pause = false\n            if (url.isEmpty()) {\n                AudioPlay.loadOrUpPlayUrl()\n                return\n            }\n            if (!exoPlayer.isPlaying) {\n                exoPlayer.play()\n            }\n            upPlayProgress()\n            upMediaSessionPlaybackState(PlaybackStateCompat.STATE_PLAYING)\n            AudioPlay.status = Status.PLAY\n            postEvent(EventBus.AUDIO_STATE, Status.PLAY)\n            upAudioPlayNotification()\n        } catch (e: Exception) {\n            e.printOnDebug()\n            stopSelf()\n        }\n    }\n\n    /**\n     * 调节进度\n     */\n    private fun adjustProgress(position: Int) {\n        this.position = position\n        exoPlayer.seekTo(position.toLong())\n    }\n\n    /**\n     * 调节速度\n     */\n    @SuppressLint(value = [\"ObsoleteSdkInt\"])\n    private fun upSpeed(adjust: Float) {\n        kotlin.runCatching {\n\n            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {\n                playSpeed += adjust\n                exoPlayer.setPlaybackSpeed(playSpeed)\n                postEvent(EventBus.AUDIO_SPEED, playSpeed)\n            }\n        }\n    }\n\n    /**\n     * 播放状态监控\n     */\n    override fun onPlaybackStateChanged(playbackState: Int) {\n        super.onPlaybackStateChanged(playbackState)\n        when (playbackState) {\n            Player.STATE_IDLE -> {\n                // 空闲\n            }\n\n            Player.STATE_BUFFERING -> {\n                // 缓冲中\n            }\n\n            Player.STATE_READY -> {\n                // 准备好\n                AudioPlay.upLoading(false)\n                if (exoPlayer.playWhenReady) {\n                    AudioPlay.status = Status.PLAY\n                    postEvent(EventBus.AUDIO_STATE, Status.PLAY)\n                } else {\n                    AudioPlay.status = Status.PAUSE\n                    postEvent(EventBus.AUDIO_STATE, Status.PAUSE)\n                }\n                postEvent(EventBus.AUDIO_SIZE, exoPlayer.duration.toInt())\n                upMediaMetadata()\n                upPlayProgress()\n                AudioPlay.saveDurChapter(exoPlayer.duration)\n            }\n\n            Player.STATE_ENDED -> {\n                // 结束\n                upPlayProgressJob?.cancel()\n                AudioPlay.playPositionChanged(exoPlayer.duration.toInt())\n                AudioPlay.next()\n            }\n        }\n        upAudioPlayNotification()\n    }\n\n    private fun upMediaMetadata() {\n        val metadata = MediaMetadataCompat.Builder()\n            .putBitmap(MediaMetadataCompat.METADATA_KEY_ART, cover)\n            .putText(MediaMetadataCompat.METADATA_KEY_TITLE, AudioPlay.durChapter?.title ?: \"null\")\n            .putText(MediaMetadataCompat.METADATA_KEY_ARTIST, AudioPlay.book?.name ?: \"null\")\n            .putText(MediaMetadataCompat.METADATA_KEY_ALBUM, AudioPlay.book?.author ?: \"null\")\n            .putLong(MediaMetadataCompat.METADATA_KEY_DURATION, exoPlayer.duration)\n            .build()\n        mediaSessionCompat?.setMetadata(metadata)\n    }\n\n    /**\n     * 播放错误事件\n     */\n    override fun onPlayerError(error: PlaybackException) {\n        super.onPlayerError(error)\n        AudioPlay.status = Status.STOP\n        postEvent(EventBus.AUDIO_STATE, Status.STOP)\n        AudioPlay.upLoading(false)\n        val errorMsg = \"音频播放出错\\n${error.errorCodeName} ${error.errorCode}\"\n        AppLog.put(errorMsg, error)\n        toastOnUi(errorMsg)\n    }\n\n    private fun setTimer(minute: Int) {\n        timeMinute = minute\n        doDs()\n    }\n\n    private fun addTimer() {\n        if (timeMinute == 180) {\n            timeMinute = 0\n        } else {\n            timeMinute += 10\n            if (timeMinute > 180) timeMinute = 180\n        }\n        doDs()\n    }\n\n    /**\n     * 定时\n     */\n    private fun doDs() {\n        postEvent(EventBus.AUDIO_DS, timeMinute)\n        upAudioPlayNotification()\n        dsJob?.cancel()\n        dsJob = lifecycleScope.launch {\n            while (isActive) {\n                delay(60000)\n                if (!pause) {\n                    if (timeMinute >= 0) {\n                        timeMinute--\n                    }\n                    if (timeMinute == 0) {\n                        AudioPlay.stop()\n                        postEvent(EventBus.AUDIO_DS, timeMinute)\n                        break\n                    }\n                }\n                postEvent(EventBus.AUDIO_DS, timeMinute)\n                upAudioPlayNotification()\n            }\n        }\n    }\n\n    /**\n     * 每隔1秒发送播放进度\n     */\n    private fun upPlayProgress() {\n        upPlayProgressJob?.cancel()\n        upPlayProgressJob = lifecycleScope.launch {\n            while (isActive) {\n                //更新buffer位置\n                AudioPlay.playPositionChanged(exoPlayer.currentPosition.toInt())\n                postEvent(EventBus.AUDIO_BUFFER_PROGRESS, exoPlayer.bufferedPosition.toInt())\n                postEvent(EventBus.AUDIO_PROGRESS, AudioPlay.durChapterPos)\n                postEvent(EventBus.AUDIO_SIZE, exoPlayer.duration.toInt())\n                upMediaSessionPlaybackState(PlaybackStateCompat.STATE_PLAYING)\n                delay(1000)\n            }\n        }\n    }\n\n    /**\n     * 更新媒体状态\n     */\n    private fun upMediaSessionPlaybackState(state: Int) {\n        mediaSessionCompat?.setPlaybackState(\n            PlaybackStateCompat.Builder()\n                .setActions(MEDIA_SESSION_ACTIONS)\n                .setState(state, exoPlayer.currentPosition, 1f)\n                .setBufferedPosition(exoPlayer.bufferedPosition)\n                .addCustomAction(\n                    APP_ACTION_STOP,\n                    getString(R.string.stop),\n                    R.drawable.ic_stop_black_24dp\n                )\n                .addCustomAction(\n                    APP_ACTION_TIMER,\n                    getString(R.string.set_timer),\n                    R.drawable.ic_time_add_24dp\n                )\n                .build()\n        )\n    }\n\n    /**\n     * 初始化MediaSession, 注册多媒体按钮\n     */\n    @SuppressLint(\"UnspecifiedImmutableFlag\")\n    private fun initMediaSession() {\n        mediaSessionCompat = MediaSessionCompat(this, \"readAloud\")\n        mediaSessionCompat?.setCallback(object : MediaSessionCompat.Callback() {\n            override fun onSeekTo(pos: Long) {\n                position = pos.toInt()\n                exoPlayer.seekTo(pos)\n            }\n\n            override fun onMediaButtonEvent(mediaButtonEvent: Intent): Boolean {\n                return MediaButtonReceiver.handleIntent(this@AudioPlayService, mediaButtonEvent)\n            }\n\n            override fun onPlay() = resume()\n\n            override fun onPause() = pause()\n\n            override fun onCustomAction(action: String?, extras: Bundle?) {\n                action ?: return\n\n                when (action) {\n                    APP_ACTION_STOP -> stopSelf()\n                    APP_ACTION_TIMER -> addTimer()\n                }\n            }\n        })\n        mediaSessionCompat?.setMediaButtonReceiver(\n            broadcastPendingIntent<MediaButtonReceiver>(Intent.ACTION_MEDIA_BUTTON)\n        )\n        mediaSessionCompat?.isActive = true\n    }\n\n    /**\n     * 断开耳机监听\n     */\n    private fun initBroadcastReceiver() {\n        broadcastReceiver = object : BroadcastReceiver() {\n            override fun onReceive(context: Context, intent: Intent) {\n                if (AudioManager.ACTION_AUDIO_BECOMING_NOISY == intent.action) {\n                    pause()\n                }\n            }\n        }\n        val intentFilter = IntentFilter(AudioManager.ACTION_AUDIO_BECOMING_NOISY)\n        registerReceiver(broadcastReceiver, intentFilter)\n    }\n\n    /**\n     * 音频焦点变化\n     */\n    override fun onAudioFocusChange(focusChange: Int) {\n        if (AppConfig.ignoreAudioFocus) {\n            AppLog.put(\"忽略音频焦点处理(有声)\")\n            return\n        }\n        when (focusChange) {\n            AudioManager.AUDIOFOCUS_GAIN -> {\n                if (needResumeOnAudioFocusGain) {\n                    AppLog.put(\"音频焦点获得,继续播放\")\n                    resume()\n                } else {\n                    AppLog.put(\"音频焦点获得\")\n                }\n            }\n\n            AudioManager.AUDIOFOCUS_LOSS -> {\n                AppLog.put(\"音频焦点丢失,暂停播放\")\n                pause()\n            }\n\n            AudioManager.AUDIOFOCUS_LOSS_TRANSIENT -> {\n                AppLog.put(\"音频焦点暂时丢失并会很快再次获得,暂停播放\")\n                if (!pause) {\n                    needResumeOnAudioFocusGain = true\n                    pause(false)\n                }\n            }\n\n            AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK -> {\n                // 短暂丢失焦点，这种情况是被其他应用申请了短暂的焦点希望其他声音能压低音量（或者关闭声音）凸显这个声音（比如短信提示音），\n                AppLog.put(\"音频焦点短暂丢失,不做处理\")\n            }\n        }\n    }\n\n    private fun createNotification(): NotificationCompat.Builder {\n        var nTitle: String = when {\n            pause -> getString(R.string.audio_pause)\n            timeMinute in 1..60 -> getString(\n                R.string.playing_timer,\n                timeMinute\n            )\n\n            else -> getString(R.string.audio_play_t)\n        }\n        nTitle += \": ${AudioPlay.book?.name}\"\n        var nSubtitle = AudioPlay.durChapter?.title\n        if (nSubtitle.isNullOrEmpty()) {\n            nSubtitle = getString(R.string.audio_play_s)\n        }\n        val builder = NotificationCompat\n            .Builder(this@AudioPlayService, AppConst.channelIdReadAloud)\n            .setSmallIcon(R.drawable.ic_volume_up)\n            .setSubText(getString(R.string.audio))\n            .setOngoing(true)\n            .setOnlyAlertOnce(true)\n            .setContentTitle(nTitle)\n            .setContentText(nSubtitle)\n            .setContentIntent(\n                activityPendingIntent<AudioPlayActivity>(\"activity\")\n            )\n        builder.setLargeIcon(cover)\n        if (pause) {\n            builder.addAction(\n                R.drawable.ic_play_24dp,\n                getString(R.string.resume),\n                servicePendingIntent<AudioPlayService>(IntentAction.resume)\n            )\n        } else {\n            builder.addAction(\n                R.drawable.ic_pause_24dp,\n                getString(R.string.pause),\n                servicePendingIntent<AudioPlayService>(IntentAction.pause)\n            )\n        }\n        builder.addAction(\n            R.drawable.ic_stop_black_24dp,\n            getString(R.string.stop),\n            servicePendingIntent<AudioPlayService>(IntentAction.stop)\n        )\n        builder.addAction(\n            R.drawable.ic_time_add_24dp,\n            getString(R.string.set_timer),\n            servicePendingIntent<AudioPlayService>(IntentAction.addTimer)\n        )\n        builder.setStyle(\n            androidx.media.app.NotificationCompat.MediaStyle()\n                .setShowActionsInCompactView(0, 1, 2)\n                .setMediaSession(mediaSessionCompat?.sessionToken)\n        )\n        builder.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)\n        return builder\n    }\n\n    private fun upAudioPlayNotification() {\n        upNotificationJob = execute {\n            try {\n                val notification = createNotification()\n                notificationManager.notify(NotificationId.AudioPlayService, notification.build())\n            } catch (e: Exception) {\n                AppLog.put(\"创建音频播放通知出错,${e.localizedMessage}\", e, true)\n            }\n        }\n    }\n\n    /**\n     * 更新通知\n     */\n    override fun startForegroundNotification() {\n        execute {\n            try {\n                val notification = createNotification()\n                startForeground(NotificationId.AudioPlayService, notification.build())\n            } catch (e: Exception) {\n                AppLog.put(\"创建音频播放通知出错,${e.localizedMessage}\", e, true)\n                //创建通知出错不结束服务就会崩溃,服务必须绑定通知\n                stopSelf()\n            }\n        }\n    }\n\n    /**\n     * 请求音频焦点\n     * @return 音频焦点\n     */\n    private fun requestFocus(): Boolean {\n        if (AppConfig.ignoreAudioFocus) {\n            return true\n        }\n        return MediaHelp.requestFocus(mFocusRequest)\n    }\n\n    /**\n     * 放弃音频焦点\n     */\n    private fun abandonFocus() {\n        @Suppress(\"DEPRECATION\")\n        audioManager.abandonAudioFocus(this)\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/service/BaseReadAloudService.kt",
    "content": "@file:Suppress(\"DEPRECATION\")\n\npackage io.legado.app.service\n\nimport android.annotation.SuppressLint\nimport android.app.PendingIntent\nimport android.content.BroadcastReceiver\nimport android.content.Context\nimport android.content.Intent\nimport android.content.IntentFilter\nimport android.graphics.Bitmap\nimport android.graphics.BitmapFactory\nimport android.media.AudioManager\nimport android.net.wifi.WifiManager\nimport android.os.Bundle\nimport android.os.PowerManager\nimport android.support.v4.media.session.MediaSessionCompat\nimport android.support.v4.media.session.PlaybackStateCompat\nimport android.telephony.PhoneStateListener\nimport android.telephony.TelephonyManager\nimport androidx.annotation.CallSuper\nimport androidx.core.app.NotificationCompat\nimport androidx.lifecycle.lifecycleScope\nimport androidx.media.AudioFocusRequestCompat\nimport androidx.media.AudioManagerCompat\nimport io.legado.app.R\nimport io.legado.app.base.BaseService\nimport io.legado.app.constant.AppConst\nimport io.legado.app.constant.AppLog\nimport io.legado.app.constant.AppPattern\nimport io.legado.app.constant.EventBus\nimport io.legado.app.constant.IntentAction\nimport io.legado.app.constant.NotificationId\nimport io.legado.app.constant.PreferKey\nimport io.legado.app.constant.Status\nimport io.legado.app.help.MediaHelp\nimport io.legado.app.help.config.AppConfig\nimport io.legado.app.help.coroutine.Coroutine\nimport io.legado.app.help.glide.ImageLoader\nimport io.legado.app.lib.permission.Permissions\nimport io.legado.app.lib.permission.PermissionsCompat\nimport io.legado.app.model.ReadAloud\nimport io.legado.app.model.ReadBook\nimport io.legado.app.receiver.MediaButtonReceiver\nimport io.legado.app.ui.book.read.ReadBookActivity\nimport io.legado.app.ui.book.read.page.entities.TextChapter\nimport io.legado.app.utils.LogUtils\nimport io.legado.app.utils.activityPendingIntent\nimport io.legado.app.utils.broadcastPendingIntent\nimport io.legado.app.utils.getPrefBoolean\nimport io.legado.app.utils.observeEvent\nimport io.legado.app.utils.observeSharedPreferences\nimport io.legado.app.utils.postEvent\nimport io.legado.app.utils.toastOnUi\nimport kotlinx.coroutines.Dispatchers.IO\nimport kotlinx.coroutines.Dispatchers.Main\nimport kotlinx.coroutines.Job\nimport kotlinx.coroutines.delay\nimport kotlinx.coroutines.isActive\nimport kotlinx.coroutines.launch\nimport splitties.init.appCtx\nimport splitties.systemservices.audioManager\nimport splitties.systemservices.notificationManager\nimport splitties.systemservices.powerManager\nimport splitties.systemservices.telephonyManager\nimport splitties.systemservices.wifiManager\n\n/**\n * 朗读服务\n */\nabstract class BaseReadAloudService : BaseService(),\n    AudioManager.OnAudioFocusChangeListener {\n\n    companion object {\n        @JvmStatic\n        var isRun = false\n            private set\n\n        @JvmStatic\n        var pause = true\n            private set\n\n        @JvmStatic\n        var timeMinute: Int = 0\n            private set\n\n        fun isPlay(): Boolean {\n            return isRun && !pause\n        }\n\n        private const val TAG = \"BaseReadAloudService\"\n\n    }\n\n    private val useWakeLock = appCtx.getPrefBoolean(PreferKey.readAloudWakeLock, false)\n    private val wakeLock by lazy {\n        powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, \"legado:ReadAloudService\")\n            .apply {\n                this.setReferenceCounted(false)\n            }\n    }\n    private val wifiLock by lazy {\n        @Suppress(\"DEPRECATION\")\n        wifiManager?.createWifiLock(WifiManager.WIFI_MODE_FULL_HIGH_PERF, \"legado:AudioPlayService\")\n            ?.apply {\n                setReferenceCounted(false)\n            }\n    }\n    private val mFocusRequest: AudioFocusRequestCompat by lazy {\n        MediaHelp.buildAudioFocusRequestCompat(this)\n    }\n    private val mediaSessionCompat: MediaSessionCompat by lazy {\n        MediaSessionCompat(this, \"readAloud\")\n    }\n    private val phoneStateListener by lazy {\n        ReadAloudPhoneStateListener()\n    }\n    internal var contentList = emptyList<String>()\n    internal var nowSpeak: Int = 0\n    internal var readAloudNumber: Int = 0\n    internal var textChapter: TextChapter? = null\n    internal var pageIndex = 0\n    private var needResumeOnAudioFocusGain = false\n    private var needResumeOnCallStateIdle = false\n    private var registeredPhoneStateListener = false\n    private var dsJob: Job? = null\n    private var upNotificationJob: Coroutine<*>? = null\n    private var cover: Bitmap =\n        BitmapFactory.decodeResource(appCtx.resources, R.drawable.icon_read_book)\n    var pageChanged = false\n    private var toLast = false\n    var paragraphStartPos = 0\n    var readAloudByPage = false\n        private set\n\n    private val broadcastReceiver = object : BroadcastReceiver() {\n        override fun onReceive(context: Context, intent: Intent) {\n            if (AudioManager.ACTION_AUDIO_BECOMING_NOISY == intent.action) {\n                pauseReadAloud()\n            }\n        }\n    }\n\n    @SuppressLint(\"WakelockTimeout\")\n    override fun onCreate() {\n        super.onCreate()\n        isRun = true\n        pause = false\n        observeLiveBus()\n        initMediaSession()\n        initBroadcastReceiver()\n        initPhoneStateListener()\n        upMediaSessionPlaybackState(PlaybackStateCompat.STATE_PLAYING)\n        setTimer(AppConfig.ttsTimer)\n        if (AppConfig.ttsTimer > 0) {\n            toastOnUi(\"朗读定时 ${AppConfig.ttsTimer} 分钟\")\n        }\n        execute {\n            ImageLoader\n                .loadBitmap(this@BaseReadAloudService, ReadBook.book?.getDisplayCover())\n                .submit()\n                .get()\n        }.onSuccess {\n            if (it.width > 16 && it.height > 16) {\n                cover = it\n                upReadAloudNotification()\n            }\n        }\n    }\n\n    fun observeLiveBus() {\n        observeEvent<Bundle>(EventBus.READ_ALOUD_PLAY) {\n            val play = it.getBoolean(\"play\")\n            val pageIndex = it.getInt(\"pageIndex\")\n            val startPos = it.getInt(\"startPos\")\n            newReadAloud(play, pageIndex, startPos)\n        }\n        observeSharedPreferences { _, key ->\n            when (key) {\n                PreferKey.ignoreAudioFocus,\n                PreferKey.pauseReadAloudWhilePhoneCalls -> {\n                    initPhoneStateListener()\n                }\n            }\n        }\n    }\n\n    override fun onDestroy() {\n        super.onDestroy()\n        if (useWakeLock) {\n            wakeLock.release()\n            wifiLock?.release()\n        }\n        isRun = false\n        pause = true\n        abandonFocus()\n        unregisterReceiver(broadcastReceiver)\n        postEvent(EventBus.ALOUD_STATE, Status.STOP)\n        notificationManager.cancel(NotificationId.ReadAloudService)\n        upMediaSessionPlaybackState(PlaybackStateCompat.STATE_STOPPED)\n        mediaSessionCompat.release()\n        ReadBook.uploadProgress()\n        unregisterPhoneStateListener(phoneStateListener)\n        upNotificationJob?.invokeOnCompletion {\n            notificationManager.cancel(NotificationId.ReadAloudService)\n        }\n    }\n\n    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {\n        when (intent?.action) {\n            IntentAction.play -> newReadAloud(\n                intent.getBooleanExtra(\"play\", true),\n                intent.getIntExtra(\"pageIndex\", ReadBook.durPageIndex),\n                intent.getIntExtra(\"startPos\", 0)\n            )\n\n            IntentAction.pause -> pauseReadAloud()\n            IntentAction.resume -> resumeReadAloud()\n            IntentAction.upTtsSpeechRate -> upSpeechRate(true)\n            IntentAction.prevParagraph -> prevP()\n            IntentAction.nextParagraph -> nextP()\n            IntentAction.prev -> prevChapter()\n            IntentAction.next -> nextChapter()\n            IntentAction.addTimer -> addTimer()\n            IntentAction.setTimer -> setTimer(intent.getIntExtra(\"minute\", 0))\n            IntentAction.stop -> stopSelf()\n        }\n        return super.onStartCommand(intent, flags, startId)\n    }\n\n    private fun newReadAloud(play: Boolean, pageIndex: Int, startPos: Int) {\n        execute(executeContext = IO) {\n            this@BaseReadAloudService.pageIndex = pageIndex\n            textChapter = ReadBook.curTextChapter\n            val textChapter = textChapter ?: return@execute\n            if (!textChapter.isCompleted) {\n                return@execute\n            }\n            readAloudNumber = textChapter.getReadLength(pageIndex) + startPos\n            readAloudByPage = getPrefBoolean(PreferKey.readAloudByPage)\n            contentList = textChapter.getNeedReadAloud(0, readAloudByPage, 0)\n                .split(\"\\n\")\n                .filter { it.isNotEmpty() }\n            var pos = startPos\n            val page = textChapter.getPage(pageIndex)!!\n            if (pos > 0) {\n                for (paragraph in page.paragraphs) {\n                    val tmp = pos - paragraph.length - 1\n                    if (tmp < 0) break\n                    pos = tmp\n                }\n            }\n            nowSpeak = textChapter.getParagraphNum(readAloudNumber + 1, readAloudByPage) - 1\n            if (!readAloudByPage && startPos == 0 && !toLast) {\n                pos = page.chapterPosition -\n                        textChapter.paragraphs[nowSpeak].chapterPosition\n            }\n            if (toLast) {\n                toLast = false\n                readAloudNumber = textChapter.getLastParagraphPosition()\n                nowSpeak = contentList.lastIndex\n                if (page.paragraphs.size == 1) {\n                    pos = page.chapterPosition -\n                            textChapter.paragraphs[nowSpeak].chapterPosition\n                }\n            }\n            paragraphStartPos = pos\n            launch(Main) {\n                if (play) play() else pageChanged = true\n            }\n        }.onError {\n            AppLog.put(\"启动朗读出错\\n${it.localizedMessage}\", it, true)\n        }\n    }\n\n    @SuppressLint(\"WakelockTimeout\")\n    open fun play() {\n        if (useWakeLock) {\n            wakeLock.acquire()\n            wifiLock?.acquire()\n        }\n        isRun = true\n        pause = false\n        needResumeOnAudioFocusGain = false\n        needResumeOnCallStateIdle = false\n        upReadAloudNotification()\n        upMediaSessionPlaybackState(PlaybackStateCompat.STATE_PLAYING)\n        postEvent(EventBus.ALOUD_STATE, Status.PLAY)\n    }\n\n    abstract fun playStop()\n\n    @CallSuper\n    open fun pauseReadAloud(abandonFocus: Boolean = true) {\n        if (useWakeLock) {\n            wakeLock.release()\n            wifiLock?.release()\n        }\n        pause = true\n        if (abandonFocus) {\n            abandonFocus()\n        }\n        upReadAloudNotification()\n        upMediaSessionPlaybackState(PlaybackStateCompat.STATE_PAUSED)\n        postEvent(EventBus.ALOUD_STATE, Status.PAUSE)\n        ReadBook.uploadProgress()\n        doDs()\n    }\n\n    @SuppressLint(\"WakelockTimeout\")\n    @CallSuper\n    open fun resumeReadAloud() {\n        resumeReadAloudInternal()\n    }\n\n    private fun resumeReadAloudInternal() {\n        pause = false\n        needResumeOnAudioFocusGain = false\n        needResumeOnCallStateIdle = false\n        upReadAloudNotification()\n        upMediaSessionPlaybackState(PlaybackStateCompat.STATE_PLAYING)\n        postEvent(EventBus.ALOUD_STATE, Status.PLAY)\n    }\n\n    abstract fun upSpeechRate(reset: Boolean = false)\n\n    fun upTtsProgress(progress: Int) {\n        postEvent(EventBus.TTS_PROGRESS, progress)\n    }\n\n    private fun prevP() {\n        if (nowSpeak > 0) {\n            playStop()\n            do {\n                nowSpeak--\n                readAloudNumber -= contentList[nowSpeak].length + 1 + paragraphStartPos\n                paragraphStartPos = 0\n            } while (contentList[nowSpeak].matches(AppPattern.notReadAloudRegex))\n            textChapter?.let {\n                if (readAloudByPage) {\n                    val paragraphs = it.getParagraphs(true)\n                    if (!paragraphs[nowSpeak].isParagraphEnd) readAloudNumber++\n                }\n                if (readAloudNumber < it.getReadLength(pageIndex)) {\n                    pageIndex--\n                    ReadBook.moveToPrevPage()\n                }\n            }\n            upTtsProgress(readAloudNumber + 1)\n            play()\n        } else {\n            toLast = true\n            ReadBook.moveToPrevChapter(true)\n        }\n    }\n\n    private fun nextP() {\n        if (nowSpeak < contentList.size - 1) {\n            playStop()\n            readAloudNumber += contentList[nowSpeak].length.plus(1) - paragraphStartPos\n            paragraphStartPos = 0\n            nowSpeak++\n            textChapter?.let {\n                if (readAloudByPage) {\n                    val paragraphs = it.getParagraphs(true)\n                    if (!paragraphs[nowSpeak].isParagraphEnd) readAloudNumber--\n                }\n                if (pageIndex + 1 < it.pageSize\n                    && readAloudNumber >= it.getReadLength(pageIndex + 1)\n                ) {\n                    pageIndex++\n                    ReadBook.moveToNextPage()\n                }\n            }\n            upTtsProgress(readAloudNumber + 1)\n            play()\n        } else {\n            nextChapter()\n        }\n    }\n\n    private fun setTimer(minute: Int) {\n        timeMinute = minute\n        doDs()\n    }\n\n    private fun addTimer() {\n        if (timeMinute == 180) {\n            timeMinute = 0\n        } else {\n            timeMinute += 10\n            if (timeMinute > 180) timeMinute = 180\n        }\n        doDs()\n    }\n\n    /**\n     * 定时\n     */\n    @Synchronized\n    private fun doDs() {\n        postEvent(EventBus.READ_ALOUD_DS, timeMinute)\n        upReadAloudNotification()\n        dsJob?.cancel()\n        dsJob = lifecycleScope.launch {\n            while (isActive) {\n                delay(60000)\n                if (!pause) {\n                    if (timeMinute >= 0) {\n                        timeMinute--\n                    }\n                    if (timeMinute == 0) {\n                        ReadAloud.stop(this@BaseReadAloudService)\n                        postEvent(EventBus.READ_ALOUD_DS, timeMinute)\n                        break\n                    }\n                }\n                postEvent(EventBus.READ_ALOUD_DS, timeMinute)\n                upReadAloudNotification()\n            }\n        }\n    }\n\n    /**\n     * 请求音频焦点\n     * @return 音频焦点\n     */\n    fun requestFocus(): Boolean {\n        if (AppConfig.ignoreAudioFocus) {\n            return true\n        }\n        val requestFocus = MediaHelp.requestFocus(mFocusRequest)\n        if (!requestFocus) {\n            pauseReadAloud(false)\n            toastOnUi(\"未获取到音频焦点\")\n        }\n        return requestFocus\n    }\n\n    /**\n     * 放弃音频焦点\n     */\n    private fun abandonFocus() {\n        AudioManagerCompat.abandonAudioFocusRequest(audioManager, mFocusRequest)\n    }\n\n    /**\n     * 更新媒体状态\n     */\n    private fun upMediaSessionPlaybackState(state: Int) {\n        mediaSessionCompat.setPlaybackState(\n            PlaybackStateCompat.Builder()\n                .setActions(MediaHelp.MEDIA_SESSION_ACTIONS)\n                .setState(state, nowSpeak.toLong(), 1f)\n                // 为系统媒体控件添加定时按钮\n                .addCustomAction(\n                    PlaybackStateCompat.CustomAction.Builder(\n                        \"ACTION_ADD_TIMER\",\n                        getString(R.string.set_timer),\n                        R.drawable.ic_time_add_24dp\n                    ).build()\n                )\n                .build()\n        )\n    }\n\n    /**\n     * 初始化MediaSession, 注册多媒体按钮\n     */\n    @SuppressLint(\"UnspecifiedImmutableFlag\")\n    private fun initMediaSession() {\n        if (getPrefBoolean(\"systemMediaControlCompatibilityChange\")) {\n            mediaSessionCompat.setCallback(object : MediaSessionCompat.Callback() {\n                override fun onPlay() {\n                    resumeReadAloud()\n                }\n\n                override fun onPause() {\n                    pauseReadAloud()\n                }\n\n                override fun onSkipToNext() {\n                    if (getPrefBoolean(\"mediaButtonPerNext\", false)) {\n                        nextChapter()\n                    } else {\n                        nextP()\n                    }\n                }\n\n                override fun onSkipToPrevious() {\n                    if (getPrefBoolean(\"mediaButtonPerNext\", false)) {\n                        prevChapter()\n                    } else {\n                        prevP()\n                    }\n                }\n\n                override fun onStop() {\n                    stopSelf()\n                }\n\n                override fun onCustomAction(action: String, extras: Bundle?) {\n                    if (action == \"ACTION_ADD_TIMER\") addTimer()\n                }\n\n                override fun onMediaButtonEvent(mediaButtonEvent: Intent): Boolean {\n                    return MediaButtonReceiver.handleIntent(\n                        this@BaseReadAloudService, mediaButtonEvent\n                    )\n                }\n            })\n        } else {\n            mediaSessionCompat.setCallback(object : MediaSessionCompat.Callback() {\n                override fun onMediaButtonEvent(mediaButtonEvent: Intent): Boolean {\n                    return MediaButtonReceiver.handleIntent(\n                        this@BaseReadAloudService, mediaButtonEvent\n                    )\n                }\n            })\n        }\n        mediaSessionCompat.setMediaButtonReceiver(\n            broadcastPendingIntent<MediaButtonReceiver>(Intent.ACTION_MEDIA_BUTTON)\n        )\n        mediaSessionCompat.isActive = true\n    }\n\n    /**\n     * 注册多媒体按钮监听\n     */\n    private fun initBroadcastReceiver() {\n        val intentFilter = IntentFilter(AudioManager.ACTION_AUDIO_BECOMING_NOISY)\n        registerReceiver(broadcastReceiver, intentFilter)\n    }\n\n    /**\n     * 音频焦点变化\n     */\n    override fun onAudioFocusChange(focusChange: Int) {\n        if (AppConfig.ignoreAudioFocus) {\n            AppLog.put(\"忽略音频焦点处理(TTS)\")\n            return\n        }\n        when (focusChange) {\n            AudioManager.AUDIOFOCUS_GAIN -> {\n                if (needResumeOnAudioFocusGain) {\n                    AppLog.put(\"音频焦点获得,继续朗读\")\n                    resumeReadAloud()\n                } else {\n                    AppLog.put(\"音频焦点获得\")\n                }\n            }\n\n            AudioManager.AUDIOFOCUS_LOSS -> {\n                AppLog.put(\"音频焦点丢失,暂停朗读\")\n                pauseReadAloud()\n            }\n\n            AudioManager.AUDIOFOCUS_LOSS_TRANSIENT -> {\n                AppLog.put(\"音频焦点暂时丢失并会很快再次获得,暂停朗读\")\n                if (!pause) {\n                    needResumeOnAudioFocusGain = true\n                    pauseReadAloud(false)\n                }\n            }\n\n            AudioManager.AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK -> {\n                // 短暂丢失焦点，这种情况是被其他应用申请了短暂的焦点希望其他声音能压低音量（或者关闭声音）凸显这个声音（比如短信提示音），\n                AppLog.put(\"音频焦点短暂丢失,不做处理\")\n            }\n        }\n    }\n\n    private fun upReadAloudNotification() {\n        upNotificationJob = execute {\n            try {\n                val notification = createNotification()\n                notificationManager.notify(NotificationId.ReadAloudService, notification.build())\n            } catch (e: Exception) {\n                AppLog.put(\"创建朗读通知出错,${e.localizedMessage}\", e, true)\n            }\n        }\n    }\n\n    private fun choiceMediaStyle(): androidx.media.app.NotificationCompat.MediaStyle {\n        val mediaStyle = androidx.media.app.NotificationCompat.MediaStyle()\n            .setShowActionsInCompactView(1, 2, 4)\n        if (getPrefBoolean(\"systemMediaControlCompatibilityChange\")) {\n            //fix #4090 android 14 can not show play control in lock screen\n            mediaStyle.setMediaSession(mediaSessionCompat.sessionToken)\n        }\n        return mediaStyle\n    }\n\n    private fun createNotification(): NotificationCompat.Builder {\n        var nTitle: String = when {\n            pause -> getString(R.string.read_aloud_pause)\n            timeMinute > 0 -> getString(\n                R.string.read_aloud_timer,\n                timeMinute\n            )\n\n            else -> getString(R.string.read_aloud_t)\n        }\n        nTitle += \": ${ReadBook.book?.name}\"\n        var nSubtitle = ReadBook.curTextChapter?.title\n        if (nSubtitle.isNullOrBlank())\n            nSubtitle = getString(R.string.read_aloud_s)\n        val builder = NotificationCompat\n            .Builder(this, AppConst.channelIdReadAloud)\n            .setVisibility(NotificationCompat.VISIBILITY_PUBLIC)\n            .setForegroundServiceBehavior(NotificationCompat.FOREGROUND_SERVICE_IMMEDIATE)\n            .setCategory(NotificationCompat.CATEGORY_TRANSPORT)\n            .setSmallIcon(R.drawable.ic_volume_up)\n            .setSubText(getString(R.string.read_aloud))\n            .setOngoing(true)\n            .setOnlyAlertOnce(true)\n            .setContentTitle(nTitle)\n            .setContentText(nSubtitle)\n            .setContentIntent(\n                activityPendingIntent<ReadBookActivity>(\"activity\")\n            )\n            .setVibrate(null)\n            .setSound(null)\n            .setLights(0, 0, 0)\n        builder.setLargeIcon(cover)\n        // 按钮定义：上一章、播放、停止、下一章、定时\n        builder.addAction(\n            R.drawable.ic_skip_previous,\n            getString(R.string.previous_chapter),\n            aloudServicePendingIntent(IntentAction.prev)\n        )\n        if (pause) {\n            builder.addAction(\n                R.drawable.ic_play_24dp,\n                getString(R.string.resume),\n                aloudServicePendingIntent(IntentAction.resume)\n            )\n        } else {\n            builder.addAction(\n                R.drawable.ic_pause_24dp,\n                getString(R.string.pause),\n                aloudServicePendingIntent(IntentAction.pause)\n            )\n        }\n        builder.addAction(\n            R.drawable.ic_stop_black_24dp,\n            getString(R.string.stop),\n            aloudServicePendingIntent(IntentAction.stop)\n        )\n        builder.addAction(\n            R.drawable.ic_skip_next,\n            getString(R.string.next_chapter),\n            aloudServicePendingIntent(IntentAction.next)\n        )\n        builder.addAction(\n            R.drawable.ic_time_add_24dp,\n            getString(R.string.set_timer),\n            aloudServicePendingIntent(IntentAction.addTimer)\n        )\n        builder.setStyle(choiceMediaStyle())\n        return builder\n    }\n\n    /**\n     * 更新通知\n     */\n    override fun startForegroundNotification() {\n        execute {\n            try {\n                val notification = createNotification()\n                startForeground(NotificationId.ReadAloudService, notification.build())\n            } catch (e: Exception) {\n                AppLog.put(\"创建朗读通知出错,${e.localizedMessage}\", e, true)\n                //创建通知出错不结束服务就会崩溃,服务必须绑定通知\n                stopSelf()\n            }\n        }\n    }\n\n    abstract fun aloudServicePendingIntent(actionStr: String): PendingIntent?\n\n    open fun prevChapter() {\n        toLast = false\n        resumeReadAloudInternal()\n        ReadBook.moveToPrevChapter(true, toLast = false)\n    }\n\n    open fun nextChapter() {\n        ReadBook.upReadTime()\n        AppLog.putDebug(\"${ReadBook.curTextChapter?.chapter?.title} 朗读结束跳转下一章并朗读\")\n        resumeReadAloudInternal()\n        if (!ReadBook.moveToNextChapter(true)) {\n            stopSelf()\n        }\n    }\n\n    private fun initPhoneStateListener() {\n        val needRegister = AppConfig.ignoreAudioFocus && AppConfig.pauseReadAloudWhilePhoneCalls\n        if (needRegister && registeredPhoneStateListener) {\n            return\n        }\n        if (needRegister) {\n            registerPhoneStateListener(phoneStateListener)\n        } else {\n            unregisterPhoneStateListener(phoneStateListener)\n        }\n    }\n\n    private fun unregisterPhoneStateListener(l: PhoneStateListener) {\n        if (registeredPhoneStateListener) {\n            withReadPhoneStatePermission {\n                telephonyManager.listen(l, PhoneStateListener.LISTEN_NONE)\n                registeredPhoneStateListener = false\n            }\n        }\n    }\n\n    private fun registerPhoneStateListener(l: PhoneStateListener) {\n        withReadPhoneStatePermission {\n            telephonyManager.listen(l, PhoneStateListener.LISTEN_CALL_STATE)\n            registeredPhoneStateListener = true\n        }\n    }\n\n    private fun withReadPhoneStatePermission(block: () -> Unit) {\n        try {\n            block.invoke()\n        } catch (_: SecurityException) {\n            PermissionsCompat.Builder()\n                .addPermissions(Permissions.READ_PHONE_STATE)\n                .rationale(R.string.read_aloud_read_phone_state_permission_rationale)\n                .onGranted {\n                    try {\n                        block.invoke()\n                    } catch (_: SecurityException) {\n                        LogUtils.d(TAG, \"Grant read phone state permission fail.\")\n                    }\n                }\n                .request()\n        }\n    }\n\n    @Suppress(\"OVERRIDE_DEPRECATION\")\n    inner class ReadAloudPhoneStateListener : PhoneStateListener() {\n        override fun onCallStateChanged(state: Int, phoneNumber: String?) {\n            super.onCallStateChanged(state, phoneNumber)\n            when (state) {\n                TelephonyManager.CALL_STATE_IDLE -> {\n                    if (needResumeOnCallStateIdle) {\n                        AppLog.put(\"来电结束,继续朗读\")\n                        resumeReadAloud()\n                    } else {\n                        AppLog.put(\"来电结束\")\n                    }\n                }\n\n                TelephonyManager.CALL_STATE_RINGING -> {\n                    if (!pause) {\n                        AppLog.put(\"来电响铃,暂停朗读\")\n                        needResumeOnCallStateIdle = true\n                        pauseReadAloud()\n                    } else {\n                        AppLog.put(\"来电响铃\")\n                    }\n                }\n\n                TelephonyManager.CALL_STATE_OFFHOOK -> {\n                    AppLog.put(\"来电接听,不做处理\")\n                }\n            }\n        }\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/service/CacheBookService.kt",
    "content": "package io.legado.app.service\n\nimport android.content.Intent\nimport androidx.core.app.NotificationCompat\nimport androidx.lifecycle.lifecycleScope\nimport io.legado.app.R\nimport io.legado.app.base.BaseService\nimport io.legado.app.constant.AppConst\nimport io.legado.app.constant.AppLog\nimport io.legado.app.constant.EventBus\nimport io.legado.app.constant.IntentAction\nimport io.legado.app.constant.NotificationId\nimport io.legado.app.data.appDb\nimport io.legado.app.help.book.update\nimport io.legado.app.help.config.AppConfig\nimport io.legado.app.model.CacheBook\nimport io.legado.app.model.webBook.WebBook\nimport io.legado.app.ui.book.cache.CacheActivity\nimport io.legado.app.utils.activityPendingIntent\nimport io.legado.app.utils.postEvent\nimport io.legado.app.utils.servicePendingIntent\nimport kotlinx.coroutines.Job\nimport kotlinx.coroutines.asCoroutineDispatcher\nimport kotlinx.coroutines.delay\nimport kotlinx.coroutines.isActive\nimport kotlinx.coroutines.launch\nimport kotlinx.coroutines.sync.Mutex\nimport kotlinx.coroutines.sync.withLock\nimport splitties.init.appCtx\nimport splitties.systemservices.notificationManager\nimport java.util.concurrent.Executors\nimport kotlin.math.min\n\n/**\n * 缓存书籍服务\n */\nclass CacheBookService : BaseService() {\n\n    companion object {\n        var isRun = false\n            private set\n    }\n\n    private val threadCount = AppConfig.threadCount\n    private var cachePool =\n        Executors.newFixedThreadPool(min(threadCount, AppConst.MAX_THREAD)).asCoroutineDispatcher()\n    private var downloadJob: Job? = null\n    private var notificationContent = appCtx.getString(R.string.service_starting)\n    private var mutex = Mutex()\n    private val notificationBuilder by lazy {\n        val builder = NotificationCompat.Builder(this, AppConst.channelIdDownload)\n            .setSmallIcon(R.drawable.ic_download)\n            .setOngoing(true)\n            .setOnlyAlertOnce(true)\n            .setContentTitle(getString(R.string.offline_cache))\n            .setContentIntent(activityPendingIntent<CacheActivity>(\"cacheActivity\"))\n        builder.addAction(\n            R.drawable.ic_stop_black_24dp,\n            getString(R.string.cancel),\n            servicePendingIntent<CacheBookService>(IntentAction.stop)\n        )\n        builder.setVisibility(NotificationCompat.VISIBILITY_PUBLIC)\n    }\n\n    override fun onCreate() {\n        super.onCreate()\n        isRun = true\n        lifecycleScope.launch {\n            while (isActive) {\n                delay(1000)\n                notificationContent = CacheBook.downloadSummary\n                upCacheBookNotification()\n                postEvent(EventBus.UP_DOWNLOAD, \"\")\n            }\n        }\n    }\n\n    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {\n        intent?.action?.let { action ->\n            when (action) {\n                IntentAction.start -> addDownloadData(\n                    intent.getStringExtra(\"bookUrl\"),\n                    intent.getIntExtra(\"start\", 0),\n                    intent.getIntExtra(\"end\", 0)\n                )\n\n                IntentAction.remove -> removeDownload(intent.getStringExtra(\"bookUrl\"))\n                IntentAction.stop -> stopSelf()\n            }\n        }\n        return super.onStartCommand(intent, flags, startId)\n    }\n\n    override fun onDestroy() {\n        isRun = false\n        cachePool.close()\n        CacheBook.close()\n        super.onDestroy()\n        postEvent(EventBus.UP_DOWNLOAD, \"\")\n    }\n\n    private fun addDownloadData(bookUrl: String?, start: Int, end: Int) {\n        bookUrl ?: return\n        execute {\n            val cacheBook = CacheBook.getOrCreate(bookUrl) ?: return@execute\n            val chapterCount = appDb.bookChapterDao.getChapterCount(bookUrl)\n            val book = cacheBook.book\n            if (chapterCount == 0) {\n                cacheBook.setLoading()\n                mutex.withLock {\n                    val name = book.name\n                    if (book.tocUrl.isEmpty()) {\n                        kotlin.runCatching {\n                            WebBook.getBookInfoAwait(cacheBook.bookSource, book)\n                        }.onFailure {\n                            removeDownload(bookUrl)\n                            val msg = \"《$name》目录为空且加载详情页失败\\n${it.localizedMessage}\"\n                            AppLog.put(msg, it, true)\n                            return@execute\n                        }\n                    }\n                    WebBook.getChapterListAwait(cacheBook.bookSource, book).onFailure {\n                        if (book.totalChapterNum > 0) {\n                            book.totalChapterNum = 0\n                            book.update()\n                        }\n                        removeDownload(bookUrl)\n                        val msg = \"《$name》目录为空且加载目录失败\\n${it.localizedMessage}\"\n                        AppLog.put(msg, it, true)\n                        return@execute\n                    }.getOrNull()?.let { toc ->\n                        appDb.bookChapterDao.insert(*toc.toTypedArray())\n                    }\n                    book.update()\n                }\n            }\n            val end2 = if (end < 0) {\n                book.lastChapterIndex\n            } else {\n                min(end, book.lastChapterIndex)\n            }\n            cacheBook.addDownload(start, end2)\n            notificationContent = CacheBook.downloadSummary\n            upCacheBookNotification()\n        }.onFinally {\n            if (downloadJob == null) {\n                download()\n            }\n        }\n    }\n\n    private fun removeDownload(bookUrl: String?) {\n        CacheBook.cacheBookMap[bookUrl]?.stop()\n        postEvent(EventBus.UP_DOWNLOAD, \"\")\n        if (downloadJob == null && CacheBook.isRun) {\n            download()\n            return\n        }\n        if (CacheBook.cacheBookMap.isEmpty()) {\n            stopSelf()\n        }\n    }\n\n    private fun download() {\n        downloadJob?.cancel()\n        downloadJob = lifecycleScope.launch(cachePool) {\n            CacheBook.startProcessJob(cachePool)\n            stopSelf()\n        }\n    }\n\n    private fun upCacheBookNotification() {\n        notificationBuilder.setContentText(notificationContent)\n        val notification = notificationBuilder.build()\n        notificationManager.notify(NotificationId.CacheBookService, notification)\n    }\n\n    /**\n     * 更新通知\n     */\n    override fun startForegroundNotification() {\n        notificationBuilder.setContentText(notificationContent)\n        val notification = notificationBuilder.build()\n        startForeground(NotificationId.CacheBookService, notification)\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/service/CheckSourceService.kt",
    "content": "package io.legado.app.service\n\nimport android.content.Intent\nimport androidx.core.app.NotificationCompat\nimport androidx.lifecycle.lifecycleScope\nimport com.script.ScriptException\nimport io.legado.app.R\nimport io.legado.app.base.BaseService\nimport io.legado.app.constant.AppConst\nimport io.legado.app.constant.BookSourceType\nimport io.legado.app.constant.EventBus\nimport io.legado.app.constant.IntentAction\nimport io.legado.app.constant.NotificationId\nimport io.legado.app.data.appDb\nimport io.legado.app.data.entities.Book\nimport io.legado.app.data.entities.BookSource\nimport io.legado.app.exception.ContentEmptyException\nimport io.legado.app.exception.NoStackTraceException\nimport io.legado.app.exception.TocEmptyException\nimport io.legado.app.help.IntentData\nimport io.legado.app.help.config.AppConfig\nimport io.legado.app.help.source.exploreKinds\nimport io.legado.app.model.CheckSource\nimport io.legado.app.model.Debug\nimport io.legado.app.model.webBook.WebBook\nimport io.legado.app.ui.book.source.manage.BookSourceActivity\nimport io.legado.app.utils.activityPendingIntent\nimport io.legado.app.utils.onEachParallel\nimport io.legado.app.utils.postEvent\nimport io.legado.app.utils.servicePendingIntent\nimport io.legado.app.utils.toastOnUi\nimport kotlinx.coroutines.Job\nimport kotlinx.coroutines.TimeoutCancellationException\nimport kotlinx.coroutines.asCoroutineDispatcher\nimport kotlinx.coroutines.ensureActive\nimport kotlinx.coroutines.flow.collect\nimport kotlinx.coroutines.flow.flow\nimport kotlinx.coroutines.flow.onCompletion\nimport kotlinx.coroutines.flow.onEach\nimport kotlinx.coroutines.flow.onStart\nimport kotlinx.coroutines.launch\nimport kotlinx.coroutines.withTimeout\nimport org.mozilla.javascript.WrappedException\nimport splitties.init.appCtx\nimport splitties.systemservices.notificationManager\nimport java.util.concurrent.Executors\nimport kotlin.coroutines.coroutineContext\nimport kotlin.math.min\n\n/**\n * 校验书源\n */\nclass CheckSourceService : BaseService() {\n    private var threadCount = AppConfig.threadCount\n    private var searchCoroutine =\n        Executors.newFixedThreadPool(min(threadCount, AppConst.MAX_THREAD)).asCoroutineDispatcher()\n    private var notificationMsg = appCtx.getString(R.string.service_starting)\n    private var checkJob: Job? = null\n    private var originSize = 0\n    private var finishCount = 0\n\n    private val notificationBuilder by lazy {\n        NotificationCompat.Builder(this, AppConst.channelIdReadAloud)\n            .setSmallIcon(R.drawable.ic_network_check)\n            .setOngoing(true)\n            .setOnlyAlertOnce(true)\n            .setContentTitle(getString(R.string.check_book_source))\n            .setContentIntent(\n                activityPendingIntent<BookSourceActivity>(\"activity\")\n            )\n            .addAction(\n                R.drawable.ic_stop_black_24dp,\n                getString(R.string.cancel),\n                servicePendingIntent<CheckSourceService>(IntentAction.stop)\n            )\n            .setVisibility(NotificationCompat.VISIBILITY_PUBLIC)\n    }\n\n    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {\n        when (intent?.action) {\n            IntentAction.start -> IntentData.get<List<String>>(\"checkSourceSelectedIds\")?.let {\n                check(it)\n            }\n\n            IntentAction.resume -> upNotification()\n            IntentAction.stop -> stopSelf()\n        }\n        return super.onStartCommand(intent, flags, startId)\n    }\n\n    override fun onDestroy() {\n        super.onDestroy()\n        Debug.finishChecking()\n        searchCoroutine.close()\n        postEvent(EventBus.CHECK_SOURCE_DONE, 0)\n        notificationManager.cancel(NotificationId.CheckSourceService)\n    }\n\n    private fun check(ids: List<String>) {\n        if (checkJob?.isActive == true) {\n            toastOnUi(\"已有书源在校验,等完成后再试\")\n            return\n        }\n        checkJob = lifecycleScope.launch(searchCoroutine) {\n            flow {\n                for (origin in ids) {\n                    appDb.bookSourceDao.getBookSource(origin)?.let {\n                        emit(it)\n                    }\n                }\n            }.onStart {\n                originSize = ids.size\n                finishCount = 0\n                notificationMsg = getString(R.string.progress_show, \"\", 0, originSize)\n                upNotification()\n            }.onEachParallel(threadCount) {\n                checkSource(it)\n            }.onEach {\n                finishCount++\n                notificationMsg = getString(\n                    R.string.progress_show,\n                    it.bookSourceName,\n                    finishCount,\n                    originSize\n                )\n                upNotification()\n                appDb.bookSourceDao.update(it)\n            }.onCompletion {\n                stopSelf()\n            }.collect()\n        }\n    }\n\n    private suspend fun checkSource(source: BookSource) {\n        kotlin.runCatching {\n            withTimeout(CheckSource.timeout) {\n                doCheckSource(source)\n            }\n        }.onSuccess {\n            Debug.updateFinalMessage(source.bookSourceUrl, \"校验成功\")\n        }.onFailure {\n            coroutineContext.ensureActive()\n            when (it) {\n                is TimeoutCancellationException -> source.addGroup(\"校验超时\")\n                is ScriptException, is WrappedException -> source.addGroup(\"js失效\")\n                !is NoStackTraceException -> source.addGroup(\"网站失效\")\n            }\n            source.addErrorComment(it)\n            Debug.updateFinalMessage(source.bookSourceUrl, \"校验失败:${it.localizedMessage}\")\n        }\n        source.respondTime = Debug.getRespondTime(source.bookSourceUrl)\n    }\n\n    private suspend fun doCheckSource(source: BookSource) {\n        Debug.startChecking(source)\n        source.removeInvalidGroups()\n        source.removeErrorComment()\n        //校验搜索书籍\n        if (CheckSource.checkSearch) {\n            val searchWord = source.getCheckKeyword(CheckSource.keyword)\n            if (!source.searchUrl.isNullOrBlank()) {\n                source.removeGroup(\"搜索链接规则为空\")\n                val searchBooks = WebBook.searchBookAwait(source, searchWord)\n                if (searchBooks.isEmpty()) {\n                    source.addGroup(\"搜索失效\")\n                } else {\n                    source.removeGroup(\"搜索失效\")\n                    checkBook(searchBooks.first().toBook(), source)\n                }\n            } else {\n                source.addGroup(\"搜索链接规则为空\")\n            }\n        }\n        //校验发现书籍\n        if (CheckSource.checkDiscovery && !source.exploreUrl.isNullOrBlank()) {\n            val url = source.exploreKinds().firstOrNull {\n                !it.url.isNullOrBlank()\n            }?.url\n            if (url.isNullOrBlank()) {\n                source.addGroup(\"发现规则为空\")\n            } else {\n                source.removeGroup(\"发现规则为空\")\n                val exploreBooks = WebBook.exploreBookAwait(source, url)\n                if (exploreBooks.isEmpty()) {\n                    source.addGroup(\"发现失效\")\n                } else {\n                    source.removeGroup(\"发现失效\")\n                    checkBook(exploreBooks.first().toBook(), source, false)\n                }\n            }\n        }\n        val finalCheckMessage = source.getInvalidGroupNames()\n        if (finalCheckMessage.isNotBlank()) {\n            throw NoStackTraceException(finalCheckMessage)\n        }\n    }\n\n    /**\n     *校验书源的详情目录正文\n     */\n    private suspend fun checkBook(book: Book, source: BookSource, isSearchBook: Boolean = true) {\n        kotlin.runCatching {\n            if (!CheckSource.checkInfo) {\n                return\n            }\n            //校验详情\n            if (book.tocUrl.isBlank()) {\n                WebBook.getBookInfoAwait(source, book)\n            }\n            if (!CheckSource.checkCategory || source.bookSourceType == BookSourceType.file) {\n                return\n            }\n            //校验目录\n            val toc = WebBook.getChapterListAwait(source, book).getOrThrow().asSequence()\n                .filter { !(it.isVolume && it.url.startsWith(it.title)) }\n                .take(2)\n                .toList()\n            val nextChapterUrl = toc.getOrNull(1)?.url ?: toc.first().url\n            if (!CheckSource.checkContent) {\n                return\n            }\n            //校验正文\n            WebBook.getContentAwait(\n                bookSource = source,\n                book = book,\n                bookChapter = toc.first(),\n                nextChapterUrl = nextChapterUrl,\n                needSave = false\n            )\n        }.onFailure {\n            val bookType = if (isSearchBook) \"搜索\" else \"发现\"\n            when (it) {\n                is ContentEmptyException -> source.addGroup(\"${bookType}正文失效\")\n                is TocEmptyException -> source.addGroup(\"${bookType}目录失效\")\n                else -> throw it\n            }\n        }.onSuccess {\n            val bookType = if (isSearchBook) \"搜索\" else \"发现\"\n            source.removeGroup(\"${bookType}目录失效\")\n            source.removeGroup(\"${bookType}正文失效\")\n        }\n    }\n\n    private fun upNotification() {\n        notificationBuilder.setContentText(notificationMsg)\n        notificationBuilder.setProgress(originSize, finishCount, false)\n        postEvent(EventBus.CHECK_SOURCE, notificationMsg)\n        notificationManager.notify(NotificationId.CheckSourceService, notificationBuilder.build())\n    }\n\n    /**\n     * 更新通知\n     */\n    override fun startForegroundNotification() {\n        notificationBuilder.setContentText(notificationMsg)\n        notificationBuilder.setProgress(originSize, finishCount, false)\n        postEvent(EventBus.CHECK_SOURCE, notificationMsg)\n        startForeground(NotificationId.CheckSourceService, notificationBuilder.build())\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/service/DownloadService.kt",
    "content": "package io.legado.app.service\n\nimport android.annotation.SuppressLint\nimport android.app.DownloadManager\nimport android.content.BroadcastReceiver\nimport android.content.Context\nimport android.content.Intent\nimport android.content.IntentFilter\nimport android.net.Uri\nimport android.os.Environment\nimport androidx.core.app.NotificationCompat\nimport androidx.core.content.ContextCompat\nimport androidx.lifecycle.lifecycleScope\nimport io.legado.app.R\nimport io.legado.app.base.BaseService\nimport io.legado.app.constant.AppConst\nimport io.legado.app.constant.AppLog\nimport io.legado.app.constant.IntentAction\nimport io.legado.app.constant.NotificationId\nimport io.legado.app.utils.IntentType\nimport io.legado.app.utils.openFileUri\nimport io.legado.app.utils.servicePendingIntent\nimport io.legado.app.utils.toastOnUi\nimport kotlinx.coroutines.Job\nimport kotlinx.coroutines.delay\nimport kotlinx.coroutines.isActive\nimport kotlinx.coroutines.launch\nimport splitties.init.appCtx\nimport splitties.systemservices.downloadManager\nimport splitties.systemservices.notificationManager\n\n/**\n * 下载文件\n */\nclass DownloadService : BaseService() {\n    private val groupKey = \"${appCtx.packageName}.download\"\n    private val downloads = hashMapOf<Long, DownloadInfo>()\n    private val completeDownloads = hashSetOf<Long>()\n    private var upStateJob: Job? = null\n    private val downloadReceiver = object : BroadcastReceiver() {\n        override fun onReceive(context: Context, intent: Intent) {\n            queryState()\n        }\n    }\n\n    @SuppressLint(\"UnspecifiedRegisterReceiverFlag\")\n    override fun onCreate() {\n        super.onCreate()\n        ContextCompat.registerReceiver(\n            this,\n            downloadReceiver,\n            IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE),\n            ContextCompat.RECEIVER_EXPORTED\n        )\n    }\n\n    override fun onDestroy() {\n        super.onDestroy()\n        unregisterReceiver(downloadReceiver)\n    }\n\n    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {\n        when (intent?.action) {\n            IntentAction.start -> startDownload(\n                intent.getStringExtra(\"url\"),\n                intent.getStringExtra(\"fileName\")\n            )\n\n            IntentAction.play -> {\n                val id = intent.getLongExtra(\"downloadId\", 0)\n                if (completeDownloads.contains(id)) {\n                    openDownload(id, downloads[id]?.fileName)\n                } else {\n                    toastOnUi(\"未完成,下载的文件夹Download\")\n                }\n            }\n\n            IntentAction.stop -> {\n                val downloadId = intent.getLongExtra(\"downloadId\", 0)\n                removeDownload(downloadId)\n            }\n        }\n        return super.onStartCommand(intent, flags, startId)\n    }\n\n    /**\n     * 开始下载\n     */\n    @Synchronized\n    private fun startDownload(url: String?, fileName: String?) {\n        if (url == null || fileName == null) {\n            if (downloads.isEmpty()) {\n                stopSelf()\n            }\n            return\n        }\n        if (downloads.values.any { it.url == url }) {\n            toastOnUi(\"已在下载列表\")\n            return\n        }\n        kotlin.runCatching {\n            // 指定下载地址\n            val request = DownloadManager.Request(Uri.parse(url))\n            // 设置通知\n            request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_HIDDEN)\n            // 设置下载文件保存的路径和文件名\n            request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS, fileName)\n            // 添加一个下载任务\n            val downloadId = downloadManager.enqueue(request)\n            downloads[downloadId] =\n                DownloadInfo(url, fileName, NotificationId.Download + downloads.size)\n            queryState()\n            if (upStateJob == null) {\n                checkDownloadState()\n            }\n        }.onFailure {\n            it.printStackTrace()\n            val msg = when (it) {\n                is SecurityException -> \"下载出错,没有存储权限\"\n                else -> \"下载出错,${it.localizedMessage}\"\n            }\n            toastOnUi(msg)\n            AppLog.put(msg, it)\n        }\n    }\n\n    /**\n     * 取消下载\n     */\n    @Synchronized\n    private fun removeDownload(downloadId: Long) {\n        if (!completeDownloads.contains(downloadId)) {\n            downloadManager.remove(downloadId)\n        }\n        downloads.remove(downloadId)\n        completeDownloads.remove(downloadId)\n        notificationManager.cancel(downloadId.toInt())\n    }\n\n    /**\n     * 下载成功\n     */\n    @Synchronized\n    private fun successDownload(downloadId: Long) {\n        if (!completeDownloads.contains(downloadId)) {\n            completeDownloads.add(downloadId)\n            val fileName = downloads[downloadId]?.fileName\n            openDownload(downloadId, fileName)\n        }\n    }\n\n    private fun checkDownloadState() {\n        upStateJob?.cancel()\n        upStateJob = lifecycleScope.launch {\n            while (isActive) {\n                queryState()\n                delay(1000)\n            }\n        }\n    }\n\n    /**\n     * 查询下载进度\n     */\n    @Synchronized\n    private fun queryState() {\n        if (downloads.isEmpty()) {\n            stopSelf()\n            return\n        }\n        val ids = downloads.keys\n        val query = DownloadManager.Query()\n        query.setFilterById(*ids.toLongArray())\n        downloadManager.query(query).use { cursor ->\n            if (cursor.moveToFirst()) {\n                val idIndex = cursor.getColumnIndex(DownloadManager.COLUMN_ID)\n                val progressIndex =\n                    cursor.getColumnIndex(DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR)\n                val fileSizeIndex = cursor.getColumnIndex(DownloadManager.COLUMN_TOTAL_SIZE_BYTES)\n                val statusIndex = cursor.getColumnIndex(DownloadManager.COLUMN_STATUS)\n                do {\n                    val id = cursor.getLong(idIndex)\n                    val progress = cursor.getInt(progressIndex)\n                    val max = cursor.getInt(fileSizeIndex)\n                    val status = when (cursor.getInt(statusIndex)) {\n                        DownloadManager.STATUS_PAUSED -> getString(R.string.pause)\n                        DownloadManager.STATUS_PENDING -> getString(R.string.wait_download)\n                        DownloadManager.STATUS_RUNNING -> getString(R.string.downloading)\n                        DownloadManager.STATUS_SUCCESSFUL -> {\n                            successDownload(id)\n                            getString(R.string.download_success)\n                        }\n\n                        DownloadManager.STATUS_FAILED -> getString(R.string.download_error)\n                        else -> getString(R.string.unknown_state)\n                    }\n                    downloads[id]?.let { downloadInfo ->\n                        upDownloadNotification(\n                            id,\n                            downloadInfo.notificationId,\n                            \"${downloadInfo.fileName} $status\",\n                            max,\n                            progress,\n                            downloadInfo.startTime\n                        )\n                    }\n                } while (cursor.moveToNext())\n            }\n        }\n    }\n\n    /**\n     * 打开下载文件\n     */\n    private fun openDownload(downloadId: Long, fileName: String?) {\n        kotlin.runCatching {\n            downloadManager.getUriForDownloadedFile(downloadId)?.let { uri ->\n                val type = IntentType.from(fileName)\n                openFileUri(uri, type)\n            }\n        }.onFailure {\n            AppLog.put(\"打开下载文件${fileName}出错\", it)\n        }\n    }\n\n    override fun startForegroundNotification() {\n        val notification = NotificationCompat.Builder(this, AppConst.channelIdDownload)\n            .setSmallIcon(R.drawable.ic_download)\n            .setSubText(getString(R.string.action_download))\n            .setGroup(groupKey)\n            .setGroupSummary(true)\n            .setOngoing(true)\n            .build()\n        startForeground(NotificationId.DownloadService, notification)\n    }\n\n    /**\n     * 更新通知\n     */\n    private fun upDownloadNotification(\n        downloadId: Long,\n        notificationId: Int,\n        content: String,\n        max: Int,\n        progress: Int,\n        startTime: Long\n    ) {\n        val notificationBuilder = NotificationCompat.Builder(this, AppConst.channelIdDownload)\n            .setSmallIcon(R.drawable.ic_download)\n            .setSubText(getString(R.string.action_download))\n            .setContentTitle(content)\n            .setOnlyAlertOnce(true)\n            .setContentIntent(\n                servicePendingIntent<DownloadService>(IntentAction.play, downloadId.toInt()) {\n                    putExtra(\"downloadId\", downloadId)\n                }\n            )\n            .setDeleteIntent(\n                servicePendingIntent<DownloadService>(IntentAction.stop, downloadId.toInt()) {\n                    putExtra(\"downloadId\", downloadId)\n                }\n            )\n            .setVisibility(NotificationCompat.VISIBILITY_PUBLIC)\n            .setGroup(groupKey)\n            .setWhen(startTime)\n        if (progress < max) {\n            notificationBuilder.setProgress(max, progress, false)\n        }\n        notificationManager.notify(notificationId, notificationBuilder.build())\n    }\n\n    private data class DownloadInfo(\n        val url: String,\n        val fileName: String,\n        val notificationId: Int,\n        val startTime: Long = System.currentTimeMillis()\n    )\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/service/ExportBookService.kt",
    "content": "package io.legado.app.service\n\nimport android.annotation.SuppressLint\nimport android.content.Intent\nimport androidx.core.app.NotificationCompat\nimport androidx.lifecycle.lifecycleScope\nimport com.bumptech.glide.Glide\nimport io.legado.app.R\nimport io.legado.app.base.BaseService\nimport io.legado.app.constant.AppConst\nimport io.legado.app.constant.AppLog\nimport io.legado.app.constant.AppPattern\nimport io.legado.app.constant.EventBus\nimport io.legado.app.constant.IntentAction\nimport io.legado.app.constant.NotificationId\nimport io.legado.app.data.appDb\nimport io.legado.app.data.entities.Book\nimport io.legado.app.data.entities.BookChapter\nimport io.legado.app.exception.NoStackTraceException\nimport io.legado.app.help.AppWebDav\nimport io.legado.app.help.book.BookHelp\nimport io.legado.app.help.book.ContentProcessor\nimport io.legado.app.help.book.getExportFileName\nimport io.legado.app.help.book.isLocalModified\nimport io.legado.app.help.config.AppConfig\nimport io.legado.app.model.ReadBook\nimport io.legado.app.model.localBook.LocalBook\nimport io.legado.app.ui.book.cache.CacheActivity\nimport io.legado.app.utils.FileDoc\nimport io.legado.app.utils.FileUtils\nimport io.legado.app.utils.HtmlFormatter\nimport io.legado.app.utils.MD5Utils\nimport io.legado.app.utils.NetworkUtils\nimport io.legado.app.utils.activityPendingIntent\nimport io.legado.app.utils.cnCompare\nimport io.legado.app.utils.createFileIfNotExist\nimport io.legado.app.utils.delete\nimport io.legado.app.utils.find\nimport io.legado.app.utils.list\nimport io.legado.app.utils.mapAsync\nimport io.legado.app.utils.mapAsyncIndexed\nimport io.legado.app.utils.openOutputStream\nimport io.legado.app.utils.postEvent\nimport io.legado.app.utils.servicePendingIntent\nimport io.legado.app.utils.toastOnUi\nimport io.legado.app.utils.writeFile\nimport kotlinx.coroutines.Dispatchers.IO\nimport kotlinx.coroutines.Job\nimport kotlinx.coroutines.coroutineScope\nimport kotlinx.coroutines.ensureActive\nimport kotlinx.coroutines.flow.collectIndexed\nimport kotlinx.coroutines.flow.flow\nimport kotlinx.coroutines.isActive\nimport kotlinx.coroutines.launch\nimport me.ag2s.epublib.domain.Author\nimport me.ag2s.epublib.domain.Date\nimport me.ag2s.epublib.domain.EpubBook\nimport me.ag2s.epublib.domain.FileResourceProvider\nimport me.ag2s.epublib.domain.LazyResource\nimport me.ag2s.epublib.domain.LazyResourceProvider\nimport me.ag2s.epublib.domain.Metadata\nimport me.ag2s.epublib.domain.Resource\nimport me.ag2s.epublib.domain.TOCReference\nimport me.ag2s.epublib.epub.EpubWriter\nimport me.ag2s.epublib.epub.EpubWriterProcessor\nimport me.ag2s.epublib.util.ResourceUtil\nimport splitties.init.appCtx\nimport splitties.systemservices.notificationManager\nimport java.nio.charset.Charset\nimport java.util.concurrent.ConcurrentHashMap\nimport kotlin.coroutines.coroutineContext\nimport kotlin.math.min\n\n/**\n * 导出书籍服务\n */\nclass ExportBookService : BaseService() {\n\n    companion object {\n        val exportProgress = ConcurrentHashMap<String, Int>()\n        val exportMsg = ConcurrentHashMap<String, String>()\n    }\n\n    data class ExportConfig(\n        val path: String,\n        val type: String,\n        val epubSize: Int = 1,\n        val epubScope: String? = null\n    )\n\n    private val groupKey = \"${appCtx.packageName}.exportBook\"\n    private val waitExportBooks = linkedMapOf<String, ExportConfig>()\n    private var exportJob: Job? = null\n    private var notificationContentText = appCtx.getString(R.string.service_starting)\n\n\n    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {\n        when (intent?.action) {\n            IntentAction.start -> kotlin.runCatching {\n                val bookUrl = intent.getStringExtra(\"bookUrl\")!!\n                if (!exportProgress.contains(bookUrl)) {\n                    val exportConfig = ExportConfig(\n                        path = intent.getStringExtra(\"exportPath\")!!,\n                        type = intent.getStringExtra(\"exportType\")!!,\n                        epubSize = intent.getIntExtra(\"epubSize\", 1),\n                        epubScope = intent.getStringExtra(\"epubScope\")\n                    )\n                    waitExportBooks[bookUrl] = exportConfig\n                    exportMsg[bookUrl] = getString(R.string.export_wait)\n                    postEvent(EventBus.EXPORT_BOOK, bookUrl)\n                    export()\n                }\n            }.onFailure {\n                toastOnUi(it.localizedMessage)\n            }\n\n            IntentAction.stop -> {\n                notificationManager.cancel(NotificationId.ExportBook)\n                stopSelf()\n            }\n        }\n        return super.onStartCommand(intent, flags, startId)\n    }\n\n    override fun onDestroy() {\n        super.onDestroy()\n        exportProgress.clear()\n        exportMsg.clear()\n        waitExportBooks.keys.forEach {\n            postEvent(EventBus.EXPORT_BOOK, it)\n        }\n    }\n\n    @SuppressLint(\"MissingPermission\")\n    override fun startForegroundNotification() {\n        val notification = NotificationCompat.Builder(this, AppConst.channelIdDownload)\n            .setSmallIcon(R.drawable.ic_export)\n            .setSubText(getString(R.string.export_book))\n            .setVisibility(NotificationCompat.VISIBILITY_PUBLIC)\n            .setGroup(groupKey)\n            .setGroupSummary(true)\n        startForeground(NotificationId.ExportBookService, notification.build())\n    }\n\n    private fun upExportNotification(finish: Boolean = false) {\n        val notification = NotificationCompat.Builder(this, AppConst.channelIdDownload)\n            .setSmallIcon(R.drawable.ic_export)\n            .setSubText(getString(R.string.export_book))\n            .setContentIntent(activityPendingIntent<CacheActivity>(\"cacheActivity\"))\n            .setVisibility(NotificationCompat.VISIBILITY_PUBLIC)\n            .setContentText(notificationContentText)\n            .setDeleteIntent(servicePendingIntent<ExportBookService>(IntentAction.stop))\n            .setGroup(groupKey)\n            .setOnlyAlertOnce(true)\n        if (!finish) {\n            notification.setOngoing(true)\n            notification.addAction(\n                R.drawable.ic_stop_black_24dp,\n                getString(R.string.cancel),\n                servicePendingIntent<ExportBookService>(IntentAction.stop)\n            )\n        }\n        notificationManager.notify(NotificationId.ExportBook, notification.build())\n    }\n\n    private fun export() {\n        if (exportJob?.isActive == true) {\n            return\n        }\n        exportJob = lifecycleScope.launch(IO) {\n            while (isActive) {\n                val (bookUrl, exportConfig) = waitExportBooks.entries.firstOrNull() ?: let {\n                    notificationContentText = \"导出完成\"\n                    upExportNotification(true)\n                    stopSelf()\n                    return@launch\n                }\n                exportProgress[bookUrl] = 0\n                waitExportBooks.remove(bookUrl)\n                val book = appDb.bookDao.getBook(bookUrl)\n                try {\n                    book ?: throw NoStackTraceException(\"获取${bookUrl}书籍出错\")\n                    refreshChapterList(book)\n                    notificationContentText = getString(\n                        R.string.export_book_notification_content,\n                        book.name,\n                        waitExportBooks.size\n                    )\n                    upExportNotification()\n                    if (exportConfig.type == \"epub\") {\n                        if (exportConfig.epubScope.isNullOrBlank()) {\n                            exportEpub(exportConfig.path, book)\n                        } else {\n                            CustomExporter(\n                                exportConfig.epubScope,\n                                exportConfig.epubSize\n                            ).export(exportConfig.path, book)\n                        }\n                    } else {\n                        exportTxt(exportConfig.path, book)\n                    }\n                    exportMsg[book.bookUrl] = getString(R.string.export_success)\n                } catch (e: Throwable) {\n                    ensureActive()\n                    exportMsg[bookUrl] = e.localizedMessage ?: \"ERROR\"\n                    AppLog.put(\"导出书籍<${book?.name ?: bookUrl}>出错\", e)\n                } finally {\n                    exportProgress.remove(bookUrl)\n                    postEvent(EventBus.EXPORT_BOOK, bookUrl)\n                }\n            }\n        }\n    }\n\n    private fun refreshChapterList(book: Book) {\n        if (!book.isLocalModified()) {\n            return\n        }\n        kotlin.runCatching {\n            LocalBook.getChapterList(book)\n        }.onSuccess {\n            appDb.bookChapterDao.delByBook(book.bookUrl)\n            appDb.bookChapterDao.insert(*it.toTypedArray())\n            appDb.bookDao.update(book)\n            ReadBook.onChapterListUpdated(book)\n        }\n    }\n\n    private data class SrcData(\n        val chapterTitle: String,\n        val index: Int,\n        val src: String\n    )\n\n    private suspend fun exportTxt(path: String, book: Book) {\n        exportMsg.remove(book.bookUrl)\n        postEvent(EventBus.EXPORT_BOOK, book.bookUrl)\n        val fileDoc = FileDoc.fromDir(path)\n        exportTxt(fileDoc, book)\n    }\n\n    private suspend fun exportTxt(fileDoc: FileDoc, book: Book) {\n        val filename = book.getExportFileName(\"txt\")\n        fileDoc.find(filename)?.delete()\n\n        val bookDoc = fileDoc.createFileIfNotExist(filename)\n        val charset = Charset.forName(AppConfig.exportCharset)\n        bookDoc.openOutputStream().getOrThrow().bufferedWriter(charset).use { bw ->\n            getAllContents(book) { text, srcList ->\n                bw.write(text)\n                srcList?.forEach {\n                    val vFile = BookHelp.getImage(book, it.src)\n                    if (vFile.exists()) {\n                        fileDoc.createFileIfNotExist(\n                            \"${it.index}-${MD5Utils.md5Encode16(it.src)}.jpg\",\n                            subDirs = arrayOf(\n                                \"${book.name}_${book.author}\",\n                                \"images\",\n                                it.chapterTitle\n                            )\n                        ).writeFile(vFile)\n                    }\n                }\n            }\n        }\n        if (AppConfig.exportToWebDav) {\n            // 导出到webdav\n            AppWebDav.exportWebDav(bookDoc.uri, filename)\n        }\n    }\n\n    private suspend fun getAllContents(\n        book: Book,\n        append: (text: String, srcList: ArrayList<SrcData>?) -> Unit\n    ) = coroutineScope {\n        val useReplace = AppConfig.exportUseReplace && book.getUseReplaceRule()\n        val contentProcessor = ContentProcessor.get(book.name, book.origin)\n        val qy = \"${book.name}\\n${\n            getString(R.string.author_show, book.getRealAuthor())\n        }\\n${\n            getString(\n                R.string.intro_show,\n                \"\\n\" + HtmlFormatter.format(book.getDisplayIntro())\n            )\n        }\"\n        append(qy, null)\n        val threads = if (AppConfig.parallelExportBook) {\n            AppConst.MAX_THREAD\n        } else {\n            1\n        }\n        flow {\n            appDb.bookChapterDao.getChapterList(book.bookUrl).forEach { chapter ->\n                emit(chapter)\n            }\n        }.mapAsync(threads) { chapter ->\n            getExportData(book, chapter, contentProcessor, useReplace)\n        }.collectIndexed { index, result ->\n            postEvent(EventBus.EXPORT_BOOK, book.bookUrl)\n            exportProgress[book.bookUrl] = index\n            append.invoke(result.first, result.second)\n        }\n\n    }\n\n    private fun getExportData(\n        book: Book,\n        chapter: BookChapter,\n        contentProcessor: ContentProcessor,\n        useReplace: Boolean\n    ): Pair<String, ArrayList<SrcData>?> {\n        val content = BookHelp.getContent(book, chapter)\n        val content1 = contentProcessor\n            .getContent(\n                book,\n                // 不导出vip标识\n                chapter.apply { isVip = false },\n                content ?: if (chapter.isVolume) \"\" else \"null\",\n                includeTitle = !AppConfig.exportNoChapterName,\n                useReplace = useReplace,\n                chineseConvert = false,\n                reSegment = false\n            ).toString()\n        if (AppConfig.exportPictureFile) {\n            //txt导出图片文件\n            val srcList = arrayListOf<SrcData>()\n            content?.split(\"\\n\")?.forEachIndexed { index, text ->\n                val matcher = AppPattern.imgPattern.matcher(text)\n                while (matcher.find()) {\n                    matcher.group(1)?.let {\n                        val src = NetworkUtils.getAbsoluteURL(chapter.url, it)\n                        srcList.add(SrcData(chapter.title, index, src))\n                    }\n                }\n            }\n            return Pair(\"\\n\\n$content1\", srcList)\n        } else {\n            return Pair(\"\\n\\n$content1\", null)\n        }\n    }\n\n    /**\n     * 导出Epub\n     */\n    private suspend fun exportEpub(path: String, book: Book) {\n        exportMsg.remove(book.bookUrl)\n        postEvent(EventBus.EXPORT_BOOK, book.bookUrl)\n        val fileDoc = FileDoc.fromDir(path)\n        exportEpub(fileDoc, book)\n    }\n\n    private suspend fun exportEpub(fileDoc: FileDoc, book: Book) {\n        val filename = book.getExportFileName(\"epub\")\n        fileDoc.find(filename)?.delete()\n\n        val epubBook = EpubBook()\n        epubBook.version = \"2.0\"\n        //set metadata\n        setEpubMetadata(book, epubBook)\n        //set cover\n        setCover(book, epubBook)\n        //set css\n        val contentModel = setAssets(fileDoc, book, epubBook)\n\n        //设置正文\n        setEpubContent(contentModel, book, epubBook)\n\n        val bookDoc = fileDoc.createFileIfNotExist(filename)\n        bookDoc.openOutputStream().getOrThrow().buffered().use { bookOs ->\n            EpubWriter().write(epubBook, bookOs)\n        }\n\n        if (AppConfig.exportToWebDav) {\n            // 导出到webdav\n            AppWebDav.exportWebDav(bookDoc.uri, filename)\n        }\n    }\n\n    private fun setAssets(doc: FileDoc, book: Book, epubBook: EpubBook): String {\n        val customPath = doc.find(\"Asset\")\n        val contentModel = if (customPath == null) {//使用内置模板\n            setAssets(book, epubBook)\n        } else {//外部模板\n            setAssetsExternal(customPath, book, epubBook)\n        }\n\n        return contentModel\n    }\n\n    private fun setAssetsExternal(doc: FileDoc, book: Book, epubBook: EpubBook): String {\n        var contentModel = \"\"\n        doc.list()!!.forEach { folder ->\n            if (folder.isDir && folder.name == \"Text\") {\n                folder.list()!!.sortedWith { o1, o2 ->\n                    o1.name.cnCompare(o2.name)\n                }.forEach loop@{ file ->\n                    if (file.isDir) {\n                        return@loop\n                    }\n                    when {\n                        //正文模板\n                        file.name.equals(\"chapter.html\", true)\n                                || file.name.equals(\"chapter.xhtml\", true) -> {\n                            contentModel = file.readText()\n                        }\n                        //封面等其他模板\n                        file.name.endsWith(\"html\", true) -> {\n                            epubBook.addSection(\n                                FileUtils.getNameExcludeExtension(file.name),\n                                ResourceUtil.createPublicResource(\n                                    book.name,\n                                    book.getRealAuthor(),\n                                    book.getDisplayIntro(),\n                                    book.kind,\n                                    book.wordCount,\n                                    file.readText(),\n                                    \"${folder.name}/${file.name}\"\n                                )\n                            )\n                        }\n                        //其他格式文件当做资源文件\n                        else -> {\n                            epubBook.resources.add(\n                                Resource(\n                                    file.readBytes(),\n                                    \"${folder.name}/${file.name}\"\n                                )\n                            )\n                        }\n                    }\n                }\n            } else if (folder.isDir) {\n                //资源文件\n                folder.list()!!.forEach loop2@{\n                    if (it.isDir) {\n                        return@loop2\n                    }\n                    epubBook.resources.add(\n                        Resource(\n                            it.readBytes(),\n                            \"${folder.name}/${it.name}\"\n                        )\n                    )\n                }\n            } else {//Asset下面的资源文件\n                epubBook.resources.add(\n                    Resource(\n                        folder.readBytes(),\n                        folder.name\n                    )\n                )\n            }\n        }\n        return contentModel\n    }\n\n    private fun setAssets(book: Book, epubBook: EpubBook): String {\n        epubBook.resources.add(\n            Resource(\n                appCtx.assets.open(\"epub/fonts.css\").readBytes(),\n                \"Styles/fonts.css\"\n            )\n        )\n        epubBook.resources.add(\n            Resource(\n                appCtx.assets.open(\"epub/main.css\").readBytes(),\n                \"Styles/main.css\"\n            )\n        )\n        epubBook.resources.add(\n            Resource(\n                appCtx.assets.open(\"epub/logo.png\").readBytes(),\n                \"Images/logo.png\"\n            )\n        )\n        epubBook.addSection(\n            getString(R.string.img_cover),\n            ResourceUtil.createPublicResource(\n                book.name,\n                book.getRealAuthor(),\n                book.getDisplayIntro(),\n                book.kind,\n                book.wordCount,\n                String(appCtx.assets.open(\"epub/cover.html\").readBytes()),\n                \"Text/cover.html\"\n            )\n        )\n        epubBook.addSection(\n            getString(R.string.book_intro),\n            ResourceUtil.createPublicResource(\n                book.name,\n                book.getRealAuthor(),\n                book.getDisplayIntro(),\n                book.kind,\n                book.wordCount,\n                String(appCtx.assets.open(\"epub/intro.html\").readBytes()),\n                \"Text/intro.html\"\n            )\n        )\n        return String(appCtx.assets.open(\"epub/chapter.html\").readBytes())\n    }\n\n    private fun setCover(book: Book, epubBook: EpubBook) {\n        kotlin.runCatching {\n            val file = Glide.with(this)\n                .asFile()\n                .load(book.getDisplayCover())\n                .submit()\n                .get()\n            val provider = LazyResourceProvider { _ ->\n                file.inputStream()\n            }\n            epubBook.coverImage = LazyResource(provider, \"Images/cover.jpg\")\n        }.onFailure {\n            AppLog.put(\"获取书籍封面出错\\n${it.localizedMessage}\", it)\n        }\n    }\n\n    private suspend fun setEpubContent(\n        contentModel: String,\n        book: Book,\n        epubBook: EpubBook\n    ) = coroutineScope {\n        //正文\n        val useReplace = AppConfig.exportUseReplace && book.getUseReplaceRule()\n        val contentProcessor = ContentProcessor.get(book.name, book.origin)\n        val threads = if (AppConfig.parallelExportBook) {\n            AppConst.MAX_THREAD\n        } else {\n            1\n        }\n        var parentSection: TOCReference? = null\n        flow {\n            appDb.bookChapterDao.getChapterList(book.bookUrl).forEach { chapter ->\n                emit(chapter)\n            }\n        }.mapAsyncIndexed(threads) { index, chapter ->\n            val content = BookHelp.getContent(book, chapter)\n            val (contentFix, resources) = fixPic(\n                book,\n                content ?: if (chapter.isVolume) \"\" else \"null\",\n                chapter\n            )\n            // 不导出vip标识\n            chapter.isVip = false\n            val content1 = contentProcessor\n                .getContent(\n                    book,\n                    chapter,\n                    contentFix,\n                    includeTitle = false,\n                    useReplace = useReplace,\n                    chineseConvert = false,\n                    reSegment = false\n                ).toString()\n            val title = chapter.run {\n                // 不导出vip标识\n                isVip = false\n                getDisplayTitle(\n                    contentProcessor.getTitleReplaceRules(),\n                    useReplace = useReplace\n                )\n            }\n            val chapterResource = ResourceUtil.createChapterResource(\n                title.replace(\"\\uD83D\\uDD12\", \"\"),\n                content1,\n                contentModel,\n                \"Text/chapter_${index}.html\"\n            )\n            ExportChapter(title, chapterResource, resources, chapter)\n        }.collectIndexed { index, exportChapter ->\n            postEvent(EventBus.EXPORT_BOOK, book.bookUrl)\n            exportProgress[book.bookUrl] = index\n            val (title, chapterResource, resources, chapter) = exportChapter\n            epubBook.resources.addAll(resources)\n            if (chapter.isVolume) {\n                parentSection = epubBook.addSection(title, chapterResource)\n            } else if (parentSection == null) {\n                epubBook.addSection(title, chapterResource)\n            } else {\n                epubBook.addSection(parentSection, title, chapterResource)\n            }\n        }\n    }\n\n    data class ExportChapter(\n        val title: String,\n        val chapterResource: Resource,\n        val resources: ArrayList<Resource>,\n        val chapter: BookChapter\n    )\n\n    private fun fixPic(\n        book: Book,\n        content: String,\n        chapter: BookChapter\n    ): Pair<String, ArrayList<Resource>> {\n        val data = StringBuilder(\"\")\n        val resources = arrayListOf<Resource>()\n        content.split(\"\\n\").forEach { text ->\n            var text1 = text\n            val matcher = AppPattern.imgPattern.matcher(text)\n            while (matcher.find()) {\n                matcher.group(1)?.let {\n                    val src = NetworkUtils.getAbsoluteURL(chapter.url, it)\n                    val originalHref =\n                        \"${MD5Utils.md5Encode16(src)}.${BookHelp.getImageSuffix(src)}\"\n                    val href =\n                        \"Images/${MD5Utils.md5Encode16(src)}.${BookHelp.getImageSuffix(src)}\"\n                    val vFile = BookHelp.getImage(book, src)\n                    val fp = FileResourceProvider(vFile.parent)\n                    if (vFile.exists()) {\n                        val img = LazyResource(fp, href, originalHref)\n                        resources.add(img)\n                    }\n                    text1 = text1.replace(src, \"../${href}\")\n                }\n            }\n            data.append(text1).append(\"\\n\")\n        }\n        return data.toString() to resources\n    }\n\n    private fun setEpubMetadata(book: Book, epubBook: EpubBook) {\n        val metadata = Metadata()\n        metadata.titles.add(book.name)//书籍的名称\n        metadata.authors.add(Author(book.getRealAuthor()))//书籍的作者\n        metadata.language = \"zh\"//数据的语言\n        metadata.dates.add(Date())//数据的创建日期\n        metadata.publishers.add(\"Legado\")//数据的创建者\n        metadata.descriptions.add(book.getDisplayIntro())//书籍的简介\n        //metadata.subjects.add(\"\")//书籍的主题，在静读天下里面有使用这个分类书籍\n        epubBook.metadata = metadata\n    }\n\n    //////end of EPUB\n\n    //////start of custom exporter\n    /**\n     * 自定义Exporter\n     * @param scope 导出范围\n     * @param size epub 文件包含最大章节数\n     */\n    inner class CustomExporter(scopeStr: String, private val size: Int) {\n\n        private var scope = parseScope(scopeStr)\n\n        /**\n         * 导出Epub\n         * @param path 导出的路径\n         * @param book 书籍\n         */\n        suspend fun export(\n            path: String,\n            book: Book\n        ) {\n            exportProgress[book.bookUrl] = 0\n            exportMsg.remove(book.bookUrl)\n            postEvent(EventBus.EXPORT_BOOK, book.bookUrl)\n            val currentTimeMillis = System.currentTimeMillis()\n            val count = appDb.bookChapterDao.getChapterCount(book.bookUrl)\n            scope = scope.filter { it < count }.toHashSet()\n\n            val fileDoc = FileDoc.fromDir(path)\n\n            val (contentModel, epubList) = createEpubs(book, fileDoc)\n            var progressBar = 0.0\n            epubList.forEachIndexed { index, ep ->\n                val (filename, epubBook) = ep\n                //设置正文\n                setEpubContent(\n                    contentModel,\n                    book,\n                    epubBook,\n                    index\n                ) { _, _ ->\n                    // 将章节写入内存时更新进度条\n                    postEvent(EventBus.EXPORT_BOOK, book.bookUrl)\n                    progressBar += book.totalChapterNum.toDouble() / scope.size / 2\n                    exportProgress[book.bookUrl] = progressBar.toInt()\n                }\n                save2Drive(filename, epubBook, fileDoc) { total, _ ->\n                    //写入硬盘时更新进度条\n                    progressBar += book.totalChapterNum.toDouble() / epubList.size / total / 2\n                    postEvent(EventBus.EXPORT_BOOK, book.bookUrl)\n                    exportProgress[book.bookUrl] = progressBar.toInt()\n                }\n            }\n\n            val elapsed = System.currentTimeMillis() - currentTimeMillis\n            AppLog.put(\"分割导出书籍 ${book.name} 一共耗时 $elapsed\")\n        }\n\n\n        /**\n         * 设置epub正文\n         *\n         * @param contentModel 正文模板\n         * @param book 书籍\n         * @param epubBook 分割后的epub\n         * @param epubBookIndex 分割后的epub序号\n         */\n        private suspend fun setEpubContent(\n            contentModel: String,\n            book: Book,\n            epubBook: EpubBook,\n            epubBookIndex: Int,\n            updateProgress: (chapterList: MutableList<BookChapter>, index: Int) -> Unit\n        ) {\n            //正文\n            val useReplace = AppConfig.exportUseReplace && book.getUseReplaceRule()\n            val contentProcessor = ContentProcessor.get(book.name, book.origin)\n            var chapterList: MutableList<BookChapter> = ArrayList()\n            appDb.bookChapterDao.getChapterList(book.bookUrl).forEachIndexed { index, chapter ->\n                if (scope.contains(index)) {\n                    chapterList.add(chapter)\n                }\n                if (scope.size == chapterList.size) {\n                    return@forEachIndexed\n                }\n            }\n            // val totalChapterNum = book.totalChapterNum / scope.size\n            if (chapterList.isEmpty()) {\n                throw RuntimeException(\"书籍<${book.name}>(${epubBookIndex + 1})未找到章节信息\")\n            }\n            chapterList = chapterList.subList(\n                epubBookIndex * size,\n                min(scope.size, (epubBookIndex + 1) * size)\n            )\n            chapterList.forEachIndexed { index, chapter ->\n                coroutineContext.ensureActive()\n                updateProgress(chapterList, index)\n                BookHelp.getContent(book, chapter).let { content ->\n                    val (contentFix, resources) = fixPic(\n                        book,\n                        content ?: if (chapter.isVolume) \"\" else \"null\",\n                        chapter\n                    )\n                    epubBook.resources.addAll(resources)\n                    val content1 = contentProcessor\n                        .getContent(\n                            book,\n                            chapter,\n                            contentFix,\n                            includeTitle = false,\n                            useReplace = useReplace,\n                            chineseConvert = false,\n                            reSegment = false\n                        ).toString()\n                    val title = chapter.run {\n                        // 不导出vip标识\n                        isVip = false\n                        getDisplayTitle(\n                            contentProcessor.getTitleReplaceRules(),\n                            useReplace = useReplace\n                        )\n                    }\n                    epubBook.addSection(\n                        title,\n                        ResourceUtil.createChapterResource(\n                            title.replace(\"\\uD83D\\uDD12\", \"\"),\n                            content1,\n                            contentModel,\n                            \"Text/chapter_${index}.html\"\n                        )\n                    )\n                }\n            }\n        }\n\n        /**\n         * 创建多个epub 对象\n         *\n         * 分割epub时，一个书籍需要创建多个epub对象\n         * @param book 书籍\n         * @param fileDoc 导出文件夹文档\n         *\n         * @return <内容模板字符串, <epub文件名, epub对象>>\n         */\n        private fun createEpubs(\n            book: Book,\n            fileDoc: FileDoc\n        ): Pair<String, List<Pair<String, EpubBook>>> {\n            val paresNumOfEpub = paresNumOfEpub(scope.size, size)\n            val result: MutableList<Pair<String, EpubBook>> = ArrayList(paresNumOfEpub)\n            var contentModel = \"\"\n            for (i in 1..paresNumOfEpub) {\n                val filename = book.getExportFileName(\"epub\", i)\n                fileDoc.find(filename)?.delete()\n\n                val epubBook = EpubBook()\n                epubBook.version = \"2.0\"\n                //set metadata\n                setEpubMetadata(book, epubBook)\n                //set cover\n                setCover(book, epubBook)\n                //set css\n                contentModel = setAssets(fileDoc, book, epubBook)\n\n                // add epubBook\n                result.add(Pair(filename, epubBook))\n            }\n            return Pair(contentModel, result)\n        }\n\n        /**\n         * 保存文件到 设备\n         */\n        private suspend fun save2Drive(\n            filename: String,\n            epubBook: EpubBook,\n            fileDoc: FileDoc,\n            callback: (total: Int, progress: Int) -> Unit\n        ) {\n            val bookDoc = fileDoc.createFileIfNotExist(filename)\n            bookDoc.openOutputStream().getOrThrow().buffered().use { bookOs ->\n                EpubWriter()\n                    .setCallback(object : EpubWriterProcessor.Callback {\n                        override fun onProgressing(total: Int, progress: Int) {\n                            callback(total, progress)\n                        }\n                    })\n                    .write(epubBook, bookOs)\n            }\n\n            if (AppConfig.exportToWebDav) {\n                // 导出到webdav\n                AppWebDav.exportWebDav(bookDoc.uri, filename)\n            }\n        }\n\n        /**\n         * 解析 分割epub后的数量\n         *\n         * @param total 章节总数\n         * @param size 每个epub文件包含多少章节\n         */\n        private fun paresNumOfEpub(total: Int, size: Int): Int {\n            val i = total % size\n            var result = total / size\n            if (i > 0) {\n                result++\n            }\n            return result\n        }\n\n        /**\n         * 解析范围字符串\n         *\n         * @param scope 范围字符串\n         * @return 范围\n         *\n         * @since 2023/5/22\n         * @author Discut\n         */\n        private fun parseScope(scope: String): Set<Int> {\n            val split = scope.split(\",\")\n\n            val result = linkedSetOf<Int>()\n            for (s in split) {\n                val v = s.split(\"-\")\n                if (v.size != 2) {\n                    result.add(s.toInt() - 1)\n                    continue\n                }\n                val left = v[0].toInt()\n                val right = v[1].toInt()\n                if (left > right) {\n                    AppLog.put(\"Error expression : $s; left > right\")\n                    continue\n                }\n                for (i in left..right)\n                    result.add(i - 1)\n            }\n            return result\n        }\n    }\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/service/HttpReadAloudService.kt",
    "content": "package io.legado.app.service\n\nimport android.annotation.SuppressLint\nimport android.app.PendingIntent\nimport android.net.Uri\nimport androidx.core.net.toUri\nimport androidx.lifecycle.lifecycleScope\nimport androidx.media3.common.C\nimport androidx.media3.common.MediaItem\nimport androidx.media3.common.PlaybackException\nimport androidx.media3.common.Player\nimport androidx.media3.common.Timeline\nimport androidx.media3.database.StandaloneDatabaseProvider\nimport androidx.media3.datasource.DataSource\nimport androidx.media3.datasource.cache.CacheDataSink\nimport androidx.media3.datasource.cache.CacheDataSource\nimport androidx.media3.datasource.cache.LeastRecentlyUsedCacheEvictor\nimport androidx.media3.datasource.cache.SimpleCache\nimport androidx.media3.exoplayer.ExoPlayer\nimport androidx.media3.exoplayer.offline.DefaultDownloaderFactory\nimport androidx.media3.exoplayer.offline.DownloadRequest\nimport androidx.media3.exoplayer.offline.Downloader\nimport androidx.media3.exoplayer.source.DefaultMediaSourceFactory\nimport androidx.media3.exoplayer.source.MediaSource\nimport androidx.media3.exoplayer.upstream.DefaultLoadErrorHandlingPolicy\nimport androidx.media3.exoplayer.upstream.LoadErrorHandlingPolicy\nimport com.script.ScriptException\nimport io.legado.app.R\nimport io.legado.app.constant.AppLog\nimport io.legado.app.constant.AppPattern\nimport io.legado.app.data.entities.HttpTTS\nimport io.legado.app.exception.NoStackTraceException\nimport io.legado.app.help.config.AppConfig\nimport io.legado.app.help.coroutine.Coroutine\nimport io.legado.app.help.exoplayer.InputStreamDataSource\nimport io.legado.app.help.http.okHttpClient\nimport io.legado.app.model.ReadAloud\nimport io.legado.app.model.ReadBook\nimport io.legado.app.model.analyzeRule.AnalyzeUrl\nimport io.legado.app.ui.book.read.page.entities.TextChapter\nimport io.legado.app.utils.FileUtils\nimport io.legado.app.utils.MD5Utils\nimport io.legado.app.utils.printOnDebug\nimport io.legado.app.utils.servicePendingIntent\nimport io.legado.app.utils.toastOnUi\nimport kotlinx.coroutines.CancellationException\nimport kotlinx.coroutines.Dispatchers.Main\nimport kotlinx.coroutines.Job\nimport kotlinx.coroutines.channels.Channel\nimport kotlinx.coroutines.currentCoroutineContext\nimport kotlinx.coroutines.delay\nimport kotlinx.coroutines.ensureActive\nimport kotlinx.coroutines.launch\nimport kotlinx.coroutines.runBlocking\nimport kotlinx.coroutines.sync.Mutex\nimport kotlinx.coroutines.sync.withLock\nimport okhttp3.Response\nimport org.mozilla.javascript.WrappedException\nimport splitties.init.appCtx\nimport java.io.File\nimport java.io.InputStream\nimport java.net.ConnectException\nimport java.net.SocketTimeoutException\n\n/**\n * 在线朗读\n */\n@SuppressLint(\"UnsafeOptInUsageError\")\nclass HttpReadAloudService : BaseReadAloudService(),\n    Player.Listener {\n    private val exoPlayer: ExoPlayer by lazy {\n        ExoPlayer.Builder(this).build()\n    }\n    private val ttsFolderPath: String by lazy {\n        cacheDir.absolutePath + File.separator + \"httpTTS\" + File.separator\n    }\n    private val cache by lazy {\n        SimpleCache(\n            File(cacheDir, \"httpTTS_cache\"),\n            LeastRecentlyUsedCacheEvictor(128 * 1024 * 1024),\n            StandaloneDatabaseProvider(appCtx)\n        )\n    }\n    private val cacheDataSinkFactory by lazy {\n        CacheDataSink.Factory()\n            .setCache(cache)\n    }\n    private val loadErrorHandlingPolicy by lazy {\n        CustomLoadErrorHandlingPolicy()\n    }\n    private var speechRate: Int = AppConfig.speechRatePlay + 5\n    private var downloadTask: Coroutine<*>? = null\n    private var playIndexJob: Job? = null\n    private var downloadErrorNo: Int = 0\n    private var playErrorNo = 0\n    private val downloadTaskActiveLock = Mutex()\n\n    override fun onCreate() {\n        super.onCreate()\n        exoPlayer.addListener(this)\n    }\n\n    override fun onDestroy() {\n        super.onDestroy()\n        downloadTask?.cancel()\n        exoPlayer.release()\n        cache.release()\n        Coroutine.async {\n            removeCacheFile()\n        }\n    }\n\n    override fun play() {\n        pageChanged = false\n        exoPlayer.stop()\n        if (!requestFocus()) return\n        if (contentList.isEmpty()) {\n            AppLog.putDebug(\"朗读列表为空\")\n            ReadBook.readAloud()\n        } else {\n            super.play()\n            if (AppConfig.streamReadAloudAudio) {\n                downloadAndPlayAudiosStream()\n            } else {\n                downloadAndPlayAudios()\n            }\n        }\n    }\n\n    override fun playStop() {\n        exoPlayer.stop()\n        playIndexJob?.cancel()\n    }\n\n    private fun updateNextPos() {\n        readAloudNumber += contentList[nowSpeak].length + 1 - paragraphStartPos\n        paragraphStartPos = 0\n        if (nowSpeak < contentList.lastIndex) {\n            nowSpeak++\n        } else {\n            nextChapter()\n        }\n    }\n\n    private fun downloadAndPlayAudios() {\n        exoPlayer.clearMediaItems()\n        downloadTask?.cancel()\n        downloadTask = execute {\n            downloadTaskActiveLock.withLock {\n                ensureActive()\n                val httpTts = ReadAloud.httpTTS ?: throw NoStackTraceException(\"tts is null\")\n                contentList.forEachIndexed { index, content ->\n                    ensureActive()\n                    if (index < nowSpeak) return@forEachIndexed\n                    var text = content\n                    if (paragraphStartPos > 0 && index == nowSpeak) {\n                        text = text.substring(paragraphStartPos)\n                    }\n                    val fileName = md5SpeakFileName(text)\n                    val speakText = text.replace(AppPattern.notReadAloudRegex, \"\")\n                    if (speakText.isEmpty()) {\n                        AppLog.put(\"阅读段落内容为空，使用无声音频代替。\\n朗读文本：$text\")\n                        createSilentSound(fileName)\n                    } else if (!hasSpeakFile(fileName)) {\n                        runCatching {\n                            val inputStream = getSpeakStream(httpTts, speakText)\n                            if (inputStream != null) {\n                                createSpeakFile(fileName, inputStream)\n                            } else {\n                                createSilentSound(fileName)\n                            }\n                        }.onFailure {\n                            when (it) {\n                                is CancellationException -> Unit\n                                else -> pauseReadAloud()\n                            }\n                            return@execute\n                        }\n                    }\n                    val file = getSpeakFileAsMd5(fileName)\n                    val mediaItem = MediaItem.fromUri(Uri.fromFile(file))\n                    launch(Main) {\n                        exoPlayer.addMediaItem(mediaItem)\n                    }\n                }\n                preDownloadAudios(httpTts)\n            }\n        }.onError {\n            AppLog.put(\"朗读下载出错\\n${it.localizedMessage}\", it, true)\n        }\n    }\n\n    private suspend fun preDownloadAudios(httpTts: HttpTTS) {\n        val textChapter = ReadBook.nextTextChapter ?: return\n        val contentList = textChapter.getNeedReadAloud(0, readAloudByPage, 0, 1)\n            .splitToSequence(\"\\n\")\n            .filter { it.isNotEmpty() }\n            .take(10)\n            .toList()\n        contentList.forEach { content ->\n            currentCoroutineContext().ensureActive()\n            val fileName = md5SpeakFileName(content, textChapter)\n            val speakText = content.replace(AppPattern.notReadAloudRegex, \"\")\n            if (speakText.isEmpty()) {\n                createSilentSound(fileName)\n            } else if (!hasSpeakFile(fileName)) {\n                runCatching {\n                    val inputStream = getSpeakStream(httpTts, speakText)\n                    if (inputStream != null) {\n                        createSpeakFile(fileName, inputStream)\n                    } else {\n                        createSilentSound(fileName)\n                    }\n                }\n            }\n        }\n    }\n\n    private fun downloadAndPlayAudiosStream() {\n        exoPlayer.clearMediaItems()\n        downloadTask?.cancel()\n        downloadTask = execute {\n            downloadTaskActiveLock.withLock {\n                ensureActive()\n                val httpTts = ReadAloud.httpTTS ?: throw NoStackTraceException(\"tts is null\")\n                val downloaderChannel = Channel<Downloader>()\n                launch {\n                    for (downloader in downloaderChannel) {\n                        downloader.download(null)\n                    }\n                }\n                contentList.forEachIndexed { index, content ->\n                    ensureActive()\n                    if (index < nowSpeak) return@forEachIndexed\n                    var text = content\n                    if (paragraphStartPos > 0 && index == nowSpeak) {\n                        text = text.substring(paragraphStartPos)\n                    }\n                    val speakText = text.replace(AppPattern.notReadAloudRegex, \"\")\n                    if (speakText.isEmpty()) {\n                        AppLog.put(\"阅读段落内容为空，使用无声音频代替。\\n朗读文本：$speakText\")\n                    }\n                    val fileName = md5SpeakFileName(text)\n                    val dataSourceFactory = createDataSourceFactory(httpTts, speakText)\n                    val downloader = createDownloader(dataSourceFactory, fileName)\n                    downloaderChannel.send(downloader)\n                    val mediaSource = createMediaSource(dataSourceFactory, fileName)\n                    launch(Main) {\n                        exoPlayer.addMediaSource(mediaSource)\n                    }\n                }\n                preDownloadAudiosStream(httpTts, downloaderChannel)\n            }\n        }.onError {\n            AppLog.put(\"朗读下载出错\\n${it.localizedMessage}\", it, true)\n        }\n    }\n\n    private suspend fun preDownloadAudiosStream(\n        httpTts: HttpTTS,\n        downloaderChannel: Channel<Downloader>\n    ) {\n        val textChapter = ReadBook.nextTextChapter ?: return\n        val contentList = textChapter.getNeedReadAloud(0, readAloudByPage, 0, 1)\n            .splitToSequence(\"\\n\")\n            .filter { it.isNotEmpty() }\n            .take(10)\n            .toList()\n        contentList.forEach { content ->\n            currentCoroutineContext().ensureActive()\n            val fileName = md5SpeakFileName(content, textChapter)\n            val speakText = content.replace(AppPattern.notReadAloudRegex, \"\")\n            val dataSourceFactory = createDataSourceFactory(httpTts, speakText)\n            val downloader = createDownloader(dataSourceFactory, fileName)\n            downloaderChannel.send(downloader)\n        }\n    }\n\n    private fun createDataSourceFactory(\n        httpTts: HttpTTS,\n        speakText: String\n    ): CacheDataSource.Factory {\n        val upstreamFactory = DataSource.Factory {\n            InputStreamDataSource {\n                if (speakText.isEmpty()) {\n                    null\n                } else {\n                    kotlin.runCatching {\n                        runBlocking(lifecycleScope.coroutineContext[Job]!!) {\n                            getSpeakStream(httpTts, speakText)\n                        }\n                    }.onFailure {\n                        when (it) {\n                            is InterruptedException,\n                            is CancellationException -> Unit\n\n                            else -> pauseReadAloud()\n                        }\n                    }.getOrThrow()\n                } ?: resources.openRawResource(R.raw.silent_sound)\n            }\n        }\n        val factory = CacheDataSource.Factory()\n            .setCache(cache)\n            .setUpstreamDataSourceFactory(upstreamFactory)\n            .setCacheWriteDataSinkFactory(cacheDataSinkFactory)\n        return factory\n    }\n\n    private fun createDownloader(factory: CacheDataSource.Factory, fileName: String): Downloader {\n        val uri = fileName.toUri()\n        val request = DownloadRequest.Builder(fileName, uri).build()\n        return DefaultDownloaderFactory(factory, okHttpClient.dispatcher.executorService)\n            .createDownloader(request)\n    }\n\n    private fun createMediaSource(factory: DataSource.Factory, fileName: String): MediaSource {\n        return DefaultMediaSourceFactory(this)\n            .setDataSourceFactory(factory)\n            .setLoadErrorHandlingPolicy(loadErrorHandlingPolicy)\n            .createMediaSource(MediaItem.fromUri(fileName))\n    }\n\n    private suspend fun getSpeakStream(\n        httpTts: HttpTTS,\n        speakText: String\n    ): InputStream? {\n        while (true) {\n            try {\n                val analyzeUrl = AnalyzeUrl(\n                    httpTts.url,\n                    speakText = speakText,\n                    speakSpeed = speechRate,\n                    source = httpTts,\n                    readTimeout = 300 * 1000L,\n                    coroutineContext = currentCoroutineContext()\n                )\n                var response = analyzeUrl.getResponseAwait()\n                currentCoroutineContext().ensureActive()\n                val checkJs = httpTts.loginCheckJs\n                if (checkJs?.isNotBlank() == true) {\n                    response = analyzeUrl.evalJS(checkJs, response) as Response\n                }\n                response.headers[\"Content-Type\"]?.let { contentType ->\n                    val contentType = contentType.substringBefore(\";\")\n                    val ct = httpTts.contentType\n                    if (contentType == \"application/json\" || contentType.startsWith(\"text/\")) {\n                        throw NoStackTraceException(response.body.string())\n                    } else if (ct?.isNotBlank() == true) {\n                        if (!contentType.matches(ct.toRegex())) {\n                            throw NoStackTraceException(\n                                \"TTS服务器返回错误：\" + response.body.string()\n                            )\n                        }\n                    }\n                }\n                currentCoroutineContext().ensureActive()\n                response.body.byteStream().let { stream ->\n                    downloadErrorNo = 0\n                    return stream\n                }\n            } catch (e: Exception) {\n                when (e) {\n                    is CancellationException -> throw e\n                    is ScriptException, is WrappedException -> {\n                        AppLog.put(\"js错误\\n${e.localizedMessage}\", e, true)\n                        e.printOnDebug()\n                        throw e\n                    }\n\n                    is SocketTimeoutException, is ConnectException -> {\n                        downloadErrorNo++\n                        if (downloadErrorNo > 5) {\n                            val msg = \"tts超时或连接错误超过5次\\n${e.localizedMessage}\"\n                            AppLog.put(msg, e, true)\n                            throw e\n                        }\n                    }\n\n                    else -> {\n                        downloadErrorNo++\n                        val msg = \"tts下载错误\\n${e.localizedMessage}\"\n                        AppLog.put(msg, e)\n                        e.printOnDebug()\n                        if (downloadErrorNo > 5) {\n                            val msg1 = \"TTS服务器连续5次错误，已暂停阅读。\"\n                            AppLog.put(msg1, e, true)\n                            throw e\n                        } else {\n                            AppLog.put(\"TTS下载音频出错，使用无声音频代替。\\n朗读文本：$speakText\")\n                            break\n                        }\n                    }\n                }\n            }\n        }\n        return null\n    }\n\n    private fun md5SpeakFileName(content: String, textChapter: TextChapter? = this.textChapter): String {\n        return MD5Utils.md5Encode16(textChapter?.title ?: \"\") + \"_\" +\n                MD5Utils.md5Encode16(\"${ReadAloud.httpTTS?.url}-|-$speechRate-|-$content\")\n    }\n\n    private fun createSilentSound(fileName: String) {\n        val file = createSpeakFile(fileName)\n        file.writeBytes(resources.openRawResource(R.raw.silent_sound).readBytes())\n    }\n\n    private fun hasSpeakFile(name: String): Boolean {\n        return FileUtils.exist(\"${ttsFolderPath}$name.mp3\")\n    }\n\n    private fun getSpeakFileAsMd5(name: String): File {\n        return File(\"${ttsFolderPath}$name.mp3\")\n    }\n\n    private fun createSpeakFile(name: String): File {\n        return FileUtils.createFileIfNotExist(\"${ttsFolderPath}$name.mp3\")\n    }\n\n    private fun createSpeakFile(name: String, inputStream: InputStream) {\n        FileUtils.createFileIfNotExist(\"${ttsFolderPath}$name.mp3\").outputStream().use { out ->\n            inputStream.use {\n                it.copyTo(out)\n            }\n        }\n    }\n\n    /**\n     * 移除缓存文件\n     */\n    private fun removeCacheFile() {\n        val titleMd5 = MD5Utils.md5Encode16(textChapter?.title ?: \"\")\n        FileUtils.listDirsAndFiles(ttsFolderPath)?.forEach {\n            val isSilentSound = it.length() == 2160L\n            if ((!it.name.startsWith(titleMd5)\n                        && System.currentTimeMillis() - it.lastModified() > 600000)\n                || isSilentSound\n            ) {\n                FileUtils.delete(it.absolutePath)\n            }\n        }\n    }\n\n\n    override fun pauseReadAloud(abandonFocus: Boolean) {\n        super.pauseReadAloud(abandonFocus)\n        kotlin.runCatching {\n            playIndexJob?.cancel()\n            exoPlayer.pause()\n        }\n    }\n\n    override fun resumeReadAloud() {\n        super.resumeReadAloud()\n        kotlin.runCatching {\n            if (pageChanged) {\n                play()\n            } else {\n                exoPlayer.play()\n                upPlayPos()\n            }\n        }\n    }\n\n    private fun upPlayPos() {\n        playIndexJob?.cancel()\n        val textChapter = textChapter ?: return\n        playIndexJob = lifecycleScope.launch {\n            upTtsProgress(readAloudNumber + 1)\n            if (exoPlayer.duration <= 0) {\n                return@launch\n            }\n            val speakTextLength = contentList[nowSpeak].length\n            if (speakTextLength <= 0) {\n                return@launch\n            }\n            val sleep = exoPlayer.duration / speakTextLength\n            val start = speakTextLength * exoPlayer.currentPosition / exoPlayer.duration\n            for (i in start..contentList[nowSpeak].length) {\n                if (pageIndex + 1 < textChapter.pageSize\n                    && readAloudNumber + i > textChapter.getReadLength(pageIndex + 1)\n                ) {\n                    pageIndex++\n                    ReadBook.moveToNextPage()\n                    upTtsProgress(readAloudNumber + i.toInt())\n                }\n                delay(sleep)\n            }\n        }\n    }\n\n    /**\n     * 更新朗读速度\n     */\n    override fun upSpeechRate(reset: Boolean) {\n        downloadTask?.cancel()\n        exoPlayer.stop()\n        speechRate = AppConfig.speechRatePlay + 5\n        if (AppConfig.streamReadAloudAudio) {\n            downloadAndPlayAudiosStream()\n        } else {\n            downloadAndPlayAudios()\n        }\n    }\n\n    override fun onPlaybackStateChanged(playbackState: Int) {\n        super.onPlaybackStateChanged(playbackState)\n        when (playbackState) {\n            Player.STATE_IDLE -> {\n                // 空闲\n            }\n\n            Player.STATE_BUFFERING -> {\n                // 缓冲中\n            }\n\n            Player.STATE_READY -> {\n                // 准备好\n                if (pause) return\n                exoPlayer.play()\n                upPlayPos()\n            }\n\n            Player.STATE_ENDED -> {\n                // 结束\n                playErrorNo = 0\n                updateNextPos()\n                exoPlayer.stop()\n                exoPlayer.clearMediaItems()\n            }\n        }\n    }\n\n    override fun onTimelineChanged(timeline: Timeline, reason: Int) {\n        when (reason) {\n            Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED -> {\n                if (!timeline.isEmpty && exoPlayer.playbackState == Player.STATE_IDLE) {\n                    exoPlayer.prepare()\n                }\n            }\n\n            else -> {}\n        }\n    }\n\n    override fun onMediaItemTransition(mediaItem: MediaItem?, reason: Int) {\n        if (reason == Player.MEDIA_ITEM_TRANSITION_REASON_PLAYLIST_CHANGED) return\n        if (reason == Player.MEDIA_ITEM_TRANSITION_REASON_AUTO) {\n            playErrorNo = 0\n        }\n        updateNextPos()\n        upPlayPos()\n    }\n\n    override fun onPlayerError(error: PlaybackException) {\n        super.onPlayerError(error)\n        AppLog.put(\"朗读错误\\n${contentList[nowSpeak]}\", error)\n        deleteCurrentSpeakFile()\n        playErrorNo++\n        if (playErrorNo >= 5) {\n            toastOnUi(\"朗读连续5次错误, 最后一次错误代码(${error.localizedMessage})\")\n            AppLog.put(\"朗读连续5次错误, 最后一次错误代码(${error.localizedMessage})\", error)\n            pauseReadAloud()\n        } else {\n            if (exoPlayer.hasNextMediaItem()) {\n                exoPlayer.seekToNextMediaItem()\n                exoPlayer.prepare()\n            } else {\n                exoPlayer.clearMediaItems()\n                updateNextPos()\n            }\n        }\n    }\n\n    private fun deleteCurrentSpeakFile() {\n        if (AppConfig.streamReadAloudAudio) {\n            return\n        }\n        val mediaItem = exoPlayer.currentMediaItem ?: return\n        val filePath = mediaItem.localConfiguration!!.uri.path!!\n        File(filePath).delete()\n    }\n\n    override fun aloudServicePendingIntent(actionStr: String): PendingIntent? {\n        return servicePendingIntent<HttpReadAloudService>(actionStr)\n    }\n\n    class CustomLoadErrorHandlingPolicy : DefaultLoadErrorHandlingPolicy(0) {\n        override fun getRetryDelayMsFor(loadErrorInfo: LoadErrorHandlingPolicy.LoadErrorInfo): Long {\n            return C.TIME_UNSET\n        }\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/service/README.md",
    "content": "# android服务\n\n* AudioPlayService 音频播放服务\n* CheckSourceService 书源检测服务\n* DownloadService 缓存服务\n* HttpReadAloudService 在线朗读服务\n* TTSReadAloudService tts朗读服务\n* WebService web服务"
  },
  {
    "path": "app/src/main/java/io/legado/app/service/TTSReadAloudService.kt",
    "content": "package io.legado.app.service\n\nimport android.app.PendingIntent\nimport android.speech.tts.TextToSpeech\nimport android.speech.tts.UtteranceProgressListener\nimport io.legado.app.R\nimport io.legado.app.constant.AppConst\nimport io.legado.app.constant.AppLog\nimport io.legado.app.constant.AppPattern\nimport io.legado.app.exception.NoStackTraceException\nimport io.legado.app.help.MediaHelp\nimport io.legado.app.help.config.AppConfig\nimport io.legado.app.help.coroutine.Coroutine\nimport io.legado.app.lib.dialogs.SelectItem\nimport io.legado.app.model.ReadAloud\nimport io.legado.app.model.ReadBook\nimport io.legado.app.utils.GSON\nimport io.legado.app.utils.LogUtils\nimport io.legado.app.utils.fromJsonObject\nimport io.legado.app.utils.servicePendingIntent\nimport io.legado.app.utils.toastOnUi\nimport kotlinx.coroutines.delay\nimport kotlinx.coroutines.ensureActive\n\n/**\n * 本地朗读\n */\nclass TTSReadAloudService : BaseReadAloudService(), TextToSpeech.OnInitListener {\n\n    private var textToSpeech: TextToSpeech? = null\n    private var ttsInitFinish = false\n    private val ttsUtteranceListener = TTSUtteranceListener()\n    private var speakJob: Coroutine<*>? = null\n    private val TAG = \"TTSReadAloudService\"\n\n    override fun onCreate() {\n        super.onCreate()\n        kotlin.runCatching {\n            initTts()\n        }.onFailure {\n            AppLog.put(\"${getString(R.string.tts_init_failed)}\\n$it\", it, true)\n        }\n    }\n\n    override fun onDestroy() {\n        super.onDestroy()\n        clearTTS()\n    }\n\n    @Synchronized\n    private fun initTts() {\n        ttsInitFinish = false\n        val engine = GSON.fromJsonObject<SelectItem<String>>(ReadAloud.ttsEngine).getOrNull()?.value\n        LogUtils.d(TAG, \"initTts engine:$engine\")\n        textToSpeech = if (engine.isNullOrBlank()) {\n            TextToSpeech(this, this)\n        } else {\n            TextToSpeech(this, this, engine)\n        }\n        upSpeechRate()\n    }\n\n    @Synchronized\n    fun clearTTS() {\n        textToSpeech?.runCatching {\n            stop()\n            shutdown()\n        }\n        textToSpeech = null\n        ttsInitFinish = false\n    }\n\n    override fun onInit(status: Int) {\n        if (status == TextToSpeech.SUCCESS) {\n            textToSpeech?.let {\n                it.setOnUtteranceProgressListener(ttsUtteranceListener)\n                ttsInitFinish = true\n                play()\n            }\n        } else {\n            toastOnUi(R.string.tts_init_failed)\n        }\n    }\n\n    @Synchronized\n    override fun play() {\n        if (!ttsInitFinish) return\n        if (!requestFocus()) return\n        if (contentList.isEmpty()) {\n            AppLog.putDebug(\"朗读列表为空\")\n            ReadBook.readAloud()\n            return\n        }\n        super.play()\n        MediaHelp.playSilentSound(this@TTSReadAloudService)\n        speakJob?.cancel()\n        speakJob = execute {\n            LogUtils.d(TAG, \"朗读列表大小 ${contentList.size}\")\n            LogUtils.d(TAG, \"朗读页数 ${textChapter?.pageSize}\")\n            val tts = textToSpeech ?: throw NoStackTraceException(\"tts is null\")\n            val contentList = contentList\n            var isAddedText = false\n            for (i in nowSpeak until contentList.size) {\n                ensureActive()\n                var text = contentList[i]\n                if (paragraphStartPos > 0 && i == nowSpeak) {\n                    text = text.substring(paragraphStartPos)\n                }\n                if (text.matches(AppPattern.notReadAloudRegex)) {\n                    continue\n                }\n                if (!isAddedText) {\n                    val result = tts.runCatching {\n                        speak(text, TextToSpeech.QUEUE_FLUSH, null, AppConst.APP_TAG + i)\n                    }.getOrElse {\n                        AppLog.put(\"tts出错\\n${it.localizedMessage}\", it, true)\n                        TextToSpeech.ERROR\n                    }\n                    if (result == TextToSpeech.ERROR) {\n                        AppLog.put(\"tts出错 尝试重新初始化\")\n                        clearTTS()\n                        initTts()\n                        return@execute\n                    }\n                } else {\n                    val result = tts.runCatching {\n                        speak(text, TextToSpeech.QUEUE_ADD, null, AppConst.APP_TAG + i)\n                    }.getOrElse {\n                        AppLog.put(\"tts出错\\n${it.localizedMessage}\", it, true)\n                        TextToSpeech.ERROR\n                    }\n                    if (result == TextToSpeech.ERROR) {\n                        AppLog.put(\"tts朗读出错:$text\")\n                    }\n                }\n                isAddedText = true\n            }\n            LogUtils.d(TAG, \"朗读内容添加完成\")\n            if (!isAddedText) {\n                playStop()\n                delay(1000)\n                nextChapter()\n            }\n        }.onError {\n            AppLog.put(\"tts朗读出错\\n${it.localizedMessage}\", it, true)\n        }\n    }\n\n    override fun playStop() {\n        textToSpeech?.runCatching {\n            stop()\n        }\n    }\n\n    /**\n     * 更新朗读速度\n     */\n    override fun upSpeechRate(reset: Boolean) {\n        if (AppConfig.ttsFlowSys) {\n            if (reset) {\n                clearTTS()\n                initTts()\n            }\n        } else {\n            val speechRate = (AppConfig.ttsSpeechRate + 5) / 10f\n            textToSpeech?.setSpeechRate(speechRate)\n        }\n    }\n\n    /**\n     * 暂停朗读\n     */\n    override fun pauseReadAloud(abandonFocus: Boolean) {\n        super.pauseReadAloud(abandonFocus)\n        speakJob?.cancel()\n        textToSpeech?.runCatching {\n            stop()\n        }\n    }\n\n    /**\n     * 恢复朗读\n     */\n    override fun resumeReadAloud() {\n        super.resumeReadAloud()\n        play()\n    }\n\n    /**\n     * 朗读监听\n     */\n    private inner class TTSUtteranceListener : UtteranceProgressListener() {\n\n        private val TAG = \"TTSUtteranceListener\"\n\n        override fun onStart(s: String) {\n            LogUtils.d(TAG, \"onStart nowSpeak:$nowSpeak pageIndex:$pageIndex utteranceId:$s\")\n            textChapter?.let {\n                if (contentList[nowSpeak].matches(AppPattern.notReadAloudRegex)) {\n                    nextParagraph()\n                }\n                if (pageIndex + 1 < it.pageSize\n                    && readAloudNumber + 1 > it.getReadLength(pageIndex + 1)\n                ) {\n                    pageIndex++\n                    ReadBook.moveToNextPage()\n                }\n                upTtsProgress(readAloudNumber + 1)\n            }\n        }\n\n        override fun onDone(s: String) {\n            LogUtils.d(TAG, \"onDone utteranceId:$s\")\n            nextParagraph()\n        }\n\n        override fun onRangeStart(utteranceId: String?, start: Int, end: Int, frame: Int) {\n            super.onRangeStart(utteranceId, start, end, frame)\n            val msg =\n                \"onRangeStart nowSpeak:$nowSpeak pageIndex:$pageIndex utteranceId:$utteranceId start:$start end:$end frame:$frame\"\n            LogUtils.d(TAG, msg)\n            textChapter?.let {\n                if (pageIndex + 1 < it.pageSize\n                    && readAloudNumber + start > it.getReadLength(pageIndex + 1)\n                ) {\n                    pageIndex++\n                    ReadBook.moveToNextPage()\n                    upTtsProgress(readAloudNumber + start)\n                }\n            }\n        }\n\n        override fun onError(utteranceId: String?, errorCode: Int) {\n            LogUtils.d(\n                TAG,\n                \"onError nowSpeak:$nowSpeak pageIndex:$pageIndex utteranceId:$utteranceId errorCode:$errorCode\"\n            )\n            nextParagraph()\n        }\n\n        private fun nextParagraph() {\n            //跳过全标点段落\n            do {\n                readAloudNumber += contentList[nowSpeak].length + 1 - paragraphStartPos\n                paragraphStartPos = 0\n                nowSpeak++\n                if (nowSpeak >= contentList.size) {\n                    nextChapter()\n                    return\n                }\n            } while (contentList[nowSpeak].matches(AppPattern.notReadAloudRegex))\n        }\n\n        @Deprecated(\"Deprecated in Java\")\n        override fun onError(s: String) {\n            LogUtils.d(TAG, \"onError nowSpeak:$nowSpeak pageIndex:$pageIndex s:$s\")\n            nextParagraph()\n        }\n\n    }\n\n    override fun aloudServicePendingIntent(actionStr: String): PendingIntent? {\n        return servicePendingIntent<TTSReadAloudService>(actionStr)\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/service/WebService.kt",
    "content": "package io.legado.app.service\n\nimport android.annotation.SuppressLint\nimport android.content.Context\nimport android.content.Intent\nimport android.net.wifi.WifiManager\nimport android.os.Build\nimport android.os.PowerManager\nimport androidx.core.app.NotificationCompat\nimport io.legado.app.R\nimport io.legado.app.base.BaseService\nimport io.legado.app.constant.AppConst\nimport io.legado.app.constant.EventBus\nimport io.legado.app.constant.IntentAction\nimport io.legado.app.constant.NotificationId\nimport io.legado.app.constant.PreferKey\nimport io.legado.app.receiver.NetworkChangedListener\nimport io.legado.app.utils.NetworkUtils\nimport io.legado.app.utils.getPrefBoolean\nimport io.legado.app.utils.getPrefInt\nimport io.legado.app.utils.postEvent\nimport io.legado.app.utils.printOnDebug\nimport io.legado.app.utils.sendToClip\nimport io.legado.app.utils.servicePendingIntent\nimport io.legado.app.utils.startForegroundServiceCompat\nimport io.legado.app.utils.startService\nimport io.legado.app.utils.stopService\nimport io.legado.app.utils.toastOnUi\nimport io.legado.app.web.HttpServer\nimport io.legado.app.web.WebSocketServer\nimport splitties.init.appCtx\nimport splitties.systemservices.powerManager\nimport splitties.systemservices.wifiManager\nimport java.io.IOException\n\nclass WebService : BaseService() {\n\n    companion object {\n        var isRun = false\n        var hostAddress = \"\"\n\n        fun start(context: Context) {\n            context.startService<WebService>()\n        }\n\n        fun startForeground(context: Context) {\n            val intent = Intent(context, WebService::class.java)\n            context.startForegroundServiceCompat(intent)\n        }\n\n        fun stop(context: Context) {\n            context.stopService<WebService>()\n        }\n\n        fun serve() {\n            appCtx.startService<WebService> {\n                action = \"serve\"\n            }\n        }\n    }\n\n    private val useWakeLock = appCtx.getPrefBoolean(PreferKey.webServiceWakeLock, false)\n    private val wakeLock: PowerManager.WakeLock by lazy {\n        powerManager.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, \"legado:WebService\")\n            .apply {\n                setReferenceCounted(false)\n            }\n    }\n    private val wifiLock by lazy {\n        @Suppress(\"DEPRECATION\")\n        wifiManager?.createWifiLock(WifiManager.WIFI_MODE_FULL_HIGH_PERF, \"legado:WebService\")\n            ?.apply {\n                setReferenceCounted(false)\n            }\n    }\n    private var httpServer: HttpServer? = null\n    private var webSocketServer: WebSocketServer? = null\n    private var notificationList = mutableListOf(appCtx.getString(R.string.service_starting))\n    private val networkChangedListener by lazy {\n        NetworkChangedListener(this)\n    }\n\n    @SuppressLint(\"WakelockTimeout\")\n    override fun onCreate() {\n        super.onCreate()\n        if (useWakeLock) {\n            wakeLock.acquire()\n            wifiLock?.acquire()\n        }\n        isRun = true\n        upTile(true)\n        networkChangedListener.register()\n        networkChangedListener.onNetworkChanged = {\n            val addressList = NetworkUtils.getLocalIPAddress()\n            notificationList.clear()\n            if (addressList.any()) {\n                notificationList.addAll(addressList.map { address ->\n                    getString(\n                        R.string.http_ip,\n                        address.hostAddress,\n                        getPort()\n                    )\n                })\n                hostAddress = notificationList.first()\n            } else {\n                hostAddress = getString(R.string.network_connection_unavailable)\n                notificationList.add(hostAddress)\n            }\n            startForegroundNotification()\n            postEvent(EventBus.WEB_SERVICE, hostAddress)\n        }\n    }\n\n    @SuppressLint(\"WakelockTimeout\")\n    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {\n        when (intent?.action) {\n            IntentAction.stop -> stopSelf()\n            \"copyHostAddress\" -> sendToClip(hostAddress)\n            \"serve\" -> if (useWakeLock) {\n                wakeLock.acquire()\n                wifiLock?.acquire()\n            }\n\n            else -> upWebServer()\n        }\n        return super.onStartCommand(intent, flags, startId)\n    }\n\n    override fun onDestroy() {\n        super.onDestroy()\n        if (useWakeLock) {\n            wakeLock.release()\n            wifiLock?.release()\n        }\n        networkChangedListener.unRegister()\n        isRun = false\n        if (httpServer?.isAlive == true) {\n            httpServer?.stop()\n        }\n        if (webSocketServer?.isAlive == true) {\n            webSocketServer?.stop()\n        }\n        postEvent(EventBus.WEB_SERVICE, \"\")\n        upTile(false)\n    }\n\n    private fun upWebServer() {\n        if (httpServer?.isAlive == true) {\n            httpServer?.stop()\n        }\n        if (webSocketServer?.isAlive == true) {\n            webSocketServer?.stop()\n        }\n        val addressList = NetworkUtils.getLocalIPAddress()\n        if (addressList.any()) {\n            val port = getPort()\n            httpServer = HttpServer(port)\n            webSocketServer = WebSocketServer(port + 1)\n            try {\n                httpServer?.start()\n                webSocketServer?.start(1000 * 30) // 通信超时设置\n                notificationList.clear()\n                notificationList.addAll(addressList.map { address ->\n                    getString(\n                        R.string.http_ip,\n                        address.hostAddress,\n                        getPort()\n                    )\n                })\n                hostAddress = notificationList.first()\n                isRun = true\n                postEvent(EventBus.WEB_SERVICE, hostAddress)\n                startForegroundNotification()\n            } catch (e: IOException) {\n                toastOnUi(e.localizedMessage ?: \"\")\n                e.printOnDebug()\n                stopSelf()\n            }\n        } else {\n            toastOnUi(\"web service cant start, no ip address\")\n            stopSelf()\n        }\n    }\n\n    private fun getPort(): Int {\n        var port = getPrefInt(PreferKey.webPort, 1122)\n        if (port > 65530 || port < 1024) {\n            port = 1122\n        }\n        return port\n    }\n\n    /**\n     * 更新通知\n     */\n    override fun startForegroundNotification() {\n        val builder = NotificationCompat.Builder(this, AppConst.channelIdWeb)\n            .setVisibility(NotificationCompat.VISIBILITY_PUBLIC)\n            .setSmallIcon(R.drawable.ic_web_service_noti)\n            .setOngoing(true)\n            .setContentTitle(getString(R.string.web_service))\n            .setContentText(notificationList.joinToString(\"\\n\"))\n            .setContentIntent(\n                servicePendingIntent<WebService>(\"copyHostAddress\")\n            )\n        builder.addAction(\n            R.drawable.ic_stop_black_24dp,\n            getString(R.string.cancel),\n            servicePendingIntent<WebService>(IntentAction.stop)\n        )\n        val notification = builder.build()\n        startForeground(NotificationId.WebService, notification)\n    }\n\n    @SuppressLint(\"ObsoleteSdkInt\")\n    private fun upTile(active: Boolean) {\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {\n            kotlin.runCatching {\n                startService<WebTileService> {\n                    action = if (active) {\n                        IntentAction.start\n                    } else {\n                        IntentAction.stop\n                    }\n                }\n            }\n\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/service/WebTileService.kt",
    "content": "package io.legado.app.service\n\nimport android.app.Dialog\nimport android.app.ForegroundServiceStartNotAllowedException\nimport android.content.Intent\nimport android.os.Build\nimport android.service.quicksettings.Tile\nimport android.service.quicksettings.TileService\nimport android.view.WindowManager.BadTokenException\nimport androidx.annotation.RequiresApi\nimport io.legado.app.R\nimport io.legado.app.constant.IntentAction\nimport io.legado.app.utils.printOnDebug\n\n\n/**\n * web服务快捷开关\n */\n@RequiresApi(Build.VERSION_CODES.N)\nclass WebTileService : TileService() {\n\n    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {\n        try {\n            when (intent?.action) {\n                IntentAction.start -> qsTile?.run {\n                    state = Tile.STATE_ACTIVE\n                    updateTile()\n                }\n\n                IntentAction.stop -> qsTile?.run {\n                    state = Tile.STATE_INACTIVE\n                    updateTile()\n                }\n            }\n        } catch (e: Exception) {\n            e.printOnDebug()\n        }\n        return super.onStartCommand(intent, flags, startId)\n    }\n\n    override fun onStartListening() {\n        super.onStartListening()\n        qsTile?.run {\n            state = if (WebService.isRun) {\n                Tile.STATE_ACTIVE\n            } else {\n                Tile.STATE_INACTIVE\n            }\n            updateTile()\n        }\n    }\n\n    override fun onClick() {\n        super.onClick()\n        if (WebService.isRun) {\n            WebService.stop(this)\n        } else {\n            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.UPSIDE_DOWN_CAKE) {\n                val dialog = Dialog(this, R.style.AppTheme_Transparent)\n                dialog.setOnShowListener {\n                    try {\n                        WebService.startForeground(this)\n                    } catch (e: ForegroundServiceStartNotAllowedException) {\n                        e.printStackTrace()\n                    }\n                    dialog.dismiss()\n                }\n                try {\n                    showDialog(dialog)\n                } catch (e: BadTokenException) {\n                    e.printStackTrace()\n                }\n            } else {\n                WebService.start(this)\n            }\n        }\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/README.md",
    "content": "# 放置与界面有关的类\n\n* about 关于界面\n* association 导入书源界面\n* book\\audio 音频播放界面\n* book\\arrange 书架整理界面\n* book\\info 书籍信息查看\n* book\\read 书籍阅读界面\n* book\\search 搜索书籍界面\n* book\\source 书源界面\n* book\\changeCover 封面换源界面\n* book\\changeSource 换源界面\n* book\\toc 目录界面\n* book\\download 下载界面\n* book\\explore 发现界面\n* book\\local 书籍导入界面\n* document 文件选择界面\n* config 配置界面\n* main 主界面\n* qrCode 二维码扫描界面\n* replaceRule 替换净化界面\n* rss\\article 订阅条目界面\n* rss\\read 订阅阅读界面\n* rss\\source 订阅源界面\n* welcome 欢迎界面\n* widget 自定义插件"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/about/AboutActivity.kt",
    "content": "package io.legado.app.ui.about\n\nimport android.os.Bundle\nimport android.text.Spannable\nimport android.text.SpannableString\nimport android.text.style.ForegroundColorSpan\nimport android.view.Menu\nimport android.view.MenuItem\nimport io.legado.app.R\nimport io.legado.app.base.BaseActivity\nimport io.legado.app.databinding.ActivityAboutBinding\nimport io.legado.app.lib.theme.accentColor\nimport io.legado.app.lib.theme.filletBackground\nimport io.legado.app.utils.openUrl\nimport io.legado.app.utils.share\nimport io.legado.app.utils.viewbindingdelegate.viewBinding\n\n\nclass AboutActivity : BaseActivity<ActivityAboutBinding>() {\n\n    override val binding by viewBinding(ActivityAboutBinding::inflate)\n\n    override fun onActivityCreated(savedInstanceState: Bundle?) {\n        binding.llAbout.background = filletBackground\n        val fTag = \"aboutFragment\"\n        var aboutFragment = supportFragmentManager.findFragmentByTag(fTag)\n        if (aboutFragment == null) aboutFragment = AboutFragment()\n        supportFragmentManager.beginTransaction()\n            .replace(R.id.fl_fragment, aboutFragment, fTag)\n            .commit()\n        binding.tvAppSummary.post {\n            kotlin.runCatching {\n                val span = ForegroundColorSpan(accentColor)\n                val spannableString = SpannableString(binding.tvAppSummary.text)\n                val gzh = getString(R.string.legado_gzh)\n                val start = spannableString.indexOf(gzh)\n                spannableString.setSpan(\n                    span, start, start + gzh.length,\n                    Spannable.SPAN_EXCLUSIVE_EXCLUSIVE\n                )\n                binding.tvAppSummary.text = spannableString\n            }\n        }\n    }\n\n    override fun onCompatCreateOptionsMenu(menu: Menu): Boolean {\n        menuInflater.inflate(R.menu.about, menu)\n        return super.onCompatCreateOptionsMenu(menu)\n    }\n\n    override fun onCompatOptionsItemSelected(item: MenuItem): Boolean {\n        when (item.itemId) {\n            R.id.menu_scoring -> openUrl(\"market://details?id=$packageName\")\n            R.id.menu_share_it -> share(\n                getString(R.string.app_share_description),\n                getString(R.string.app_name)\n            )\n        }\n        return super.onCompatOptionsItemSelected(item)\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/about/AboutFragment.kt",
    "content": "package io.legado.app.ui.about\n\nimport android.content.Intent\nimport android.net.Uri\nimport android.os.Bundle\nimport android.view.View\nimport androidx.annotation.StringRes\nimport androidx.lifecycle.lifecycleScope\nimport androidx.preference.Preference\nimport androidx.preference.PreferenceFragmentCompat\nimport io.legado.app.R\nimport io.legado.app.constant.AppConst.appInfo\nimport io.legado.app.constant.AppLog\nimport io.legado.app.help.CrashHandler\nimport io.legado.app.help.config.AppConfig\nimport io.legado.app.help.coroutine.Coroutine\nimport io.legado.app.help.update.AppUpdate\nimport io.legado.app.ui.widget.dialog.TextDialog\nimport io.legado.app.ui.widget.dialog.WaitDialog\nimport io.legado.app.utils.FileDoc\nimport io.legado.app.utils.compress.ZipUtils\nimport io.legado.app.utils.createFileIfNotExist\nimport io.legado.app.utils.createFolderIfNotExist\nimport io.legado.app.utils.delete\nimport io.legado.app.utils.externalCache\nimport io.legado.app.utils.find\nimport io.legado.app.utils.list\nimport io.legado.app.utils.openInputStream\nimport io.legado.app.utils.openOutputStream\nimport io.legado.app.utils.openUrl\nimport io.legado.app.utils.sendMail\nimport io.legado.app.utils.sendToClip\nimport io.legado.app.utils.showDialogFragment\nimport io.legado.app.utils.toastOnUi\nimport kotlinx.coroutines.delay\nimport splitties.init.appCtx\nimport java.io.File\n\nclass AboutFragment : PreferenceFragmentCompat() {\n\n    private val waitDialog by lazy {\n        WaitDialog(requireContext())\n    }\n\n    override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {\n        addPreferencesFromResource(R.xml.about)\n        findPreference<Preference>(\"update_log\")?.summary =\n            \"${getString(R.string.version)} ${appInfo.versionName}\"\n    }\n\n    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n        super.onViewCreated(view, savedInstanceState)\n        listView.overScrollMode = View.OVER_SCROLL_NEVER\n    }\n\n    override fun onPreferenceTreeClick(preference: Preference): Boolean {\n        when (preference.key) {\n            \"contributors\" -> openUrl(R.string.contributors_url)\n            \"update_log\" -> showMdFile(getString(R.string.update_log), \"updateLog.md\")\n            \"check_update\" -> checkUpdate()\n            \"mail\" -> requireContext().sendMail(getString(R.string.email))\n            \"license\" -> showMdFile(getString(R.string.license), \"LICENSE.md\")\n            \"disclaimer\" -> showMdFile(getString(R.string.disclaimer), \"disclaimer.md\")\n            \"privacyPolicy\" -> showMdFile(getString(R.string.privacy_policy), \"privacyPolicy.md\")\n            \"gzGzh\" -> requireContext().sendToClip(getString(R.string.legado_gzh))\n            \"crashLog\" -> showDialogFragment<CrashLogsDialog>()\n            \"saveLog\" -> saveLog()\n            \"createHeapDump\" -> createHeapDump()\n        }\n        return super.onPreferenceTreeClick(preference)\n    }\n\n    @Suppress(\"SameParameterValue\")\n    private fun openUrl(@StringRes addressID: Int) {\n        requireContext().openUrl(getString(addressID))\n    }\n\n    /**\n     * 显示md文件\n     */\n    private fun showMdFile(title: String, fileName: String) {\n        val mdText = String(requireContext().assets.open(fileName).readBytes())\n        showDialogFragment(TextDialog(title, mdText, TextDialog.Mode.MD))\n    }\n\n    /**\n     * 检测更新\n     */\n    private fun checkUpdate() {\n        waitDialog.show()\n        AppUpdate.gitHubUpdate?.run {\n            check(lifecycleScope)\n                .onSuccess {\n                    showDialogFragment(\n                        UpdateDialog(it)\n                    )\n                }.onError {\n                    appCtx.toastOnUi(\"${getString(R.string.check_update)}\\n${it.localizedMessage}\")\n                }.onFinally {\n                    waitDialog.dismiss()\n                }\n        }\n    }\n\n\n    /**\n     * 加入qq群\n     */\n    private fun joinQQGroup(key: String): Boolean {\n        val intent = Intent()\n        intent.data =\n            Uri.parse(\"mqqopensdkapi://bizAgent/qm/qr?url=http%3A%2F%2Fqm.qq.com%2Fcgi-bin%2Fqm%2Fqr%3Ffrom%3Dapp%26p%3Dandroid%26k%3D$key\")\n        // 此Flag可根据具体产品需要自定义，如设置，则在加群界面按返回，返回手Q主界面，不设置，按返回会返回到呼起产品界面\n        // intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)\n        kotlin.runCatching {\n            startActivity(intent)\n            return true\n        }.onFailure {\n            toastOnUi(\"添加失败,请手动添加\")\n        }\n        return false\n    }\n\n    private fun saveLog() {\n        Coroutine.async {\n            val backupPath = AppConfig.backupPath ?: let {\n                appCtx.toastOnUi(\"未设置备份目录\")\n                return@async\n            }\n            if (!AppConfig.recordLog) {\n                appCtx.toastOnUi(\"未开启日志记录，请去其他设置里打开记录日志\")\n                delay(3000)\n            }\n            val doc = FileDoc.fromUri(Uri.parse(backupPath), true)\n            copyLogs(doc)\n            copyHeapDump(doc)\n            appCtx.toastOnUi(\"已保存至备份目录\")\n        }.onError {\n            AppLog.put(\"保存日志出错\\n${it.localizedMessage}\", it, true)\n        }\n    }\n\n    private fun createHeapDump() {\n        Coroutine.async {\n            val backupPath = AppConfig.backupPath ?: let {\n                appCtx.toastOnUi(\"未设置备份目录\")\n                return@async\n            }\n            if (!AppConfig.recordHeapDump) {\n                appCtx.toastOnUi(\"未开启堆转储记录，请去其他设置里打开记录堆转储\")\n                delay(3000)\n            }\n            appCtx.toastOnUi(\"开始创建堆转储\")\n            System.gc()\n            CrashHandler.doHeapDump(true)\n            val doc = FileDoc.fromUri(Uri.parse(backupPath), true)\n            if (!copyHeapDump(doc)) {\n                appCtx.toastOnUi(\"未找到堆转储文件\")\n            } else {\n                appCtx.toastOnUi(\"已保存至备份目录\")\n            }\n        }.onError {\n            AppLog.put(\"保存堆转储失败\\n${it.localizedMessage}\", it)\n        }\n    }\n\n    private fun copyLogs(doc: FileDoc) {\n        val cacheDir = appCtx.externalCache\n        val logFiles = File(cacheDir, \"logs\")\n        val crashFiles = File(cacheDir, \"crash\")\n        val logcatFile = File(cacheDir, \"logcat.txt\")\n\n        dumpLogcat(logcatFile)\n\n        val zipFile = File(cacheDir, \"logs.zip\")\n        ZipUtils.zipFiles(arrayListOf(logFiles, crashFiles, logcatFile), zipFile)\n\n        doc.find(\"logs.zip\")?.delete()\n\n        zipFile.inputStream().use { input ->\n            doc.createFileIfNotExist(\"logs.zip\").openOutputStream().getOrNull()\n                ?.use {\n                    input.copyTo(it)\n                }\n        }\n        zipFile.delete()\n    }\n\n    private fun copyHeapDump(doc: FileDoc): Boolean {\n        val heapFile = FileDoc.fromFile(File(appCtx.externalCache, \"heapDump\")).list()\n            ?.firstOrNull() ?: return false\n        doc.find(\"heapDump\")?.delete()\n        val heapDumpDoc = doc.createFolderIfNotExist(\"heapDump\")\n        heapFile.openInputStream().getOrNull()?.use { input ->\n            heapDumpDoc.createFileIfNotExist(heapFile.name).openOutputStream().getOrNull()\n                ?.use {\n                    input.copyTo(it)\n                }\n        }\n        return true\n    }\n\n    private fun dumpLogcat(file: File) {\n        try {\n            val process = Runtime.getRuntime().exec(\"logcat -d\")\n            file.outputStream().use {\n                process.inputStream.copyTo(it)\n            }\n        } catch (e: Exception) {\n            AppLog.put(\"保存Logcat失败\\n$e\", e)\n        }\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/about/AppLogDialog.kt",
    "content": "package io.legado.app.ui.about\n\nimport android.content.Context\nimport android.os.Bundle\nimport android.view.MenuItem\nimport android.view.View\nimport android.view.ViewGroup\nimport androidx.appcompat.widget.Toolbar\nimport androidx.recyclerview.widget.LinearLayoutManager\nimport io.legado.app.R\nimport io.legado.app.base.BaseDialogFragment\nimport io.legado.app.base.adapter.ItemViewHolder\nimport io.legado.app.base.adapter.RecyclerAdapter\nimport io.legado.app.constant.AppLog\nimport io.legado.app.databinding.DialogRecyclerViewBinding\nimport io.legado.app.databinding.ItemAppLogBinding\nimport io.legado.app.lib.theme.primaryColor\nimport io.legado.app.ui.widget.dialog.TextDialog\nimport io.legado.app.utils.LogUtils\nimport io.legado.app.utils.setLayout\nimport io.legado.app.utils.showDialogFragment\nimport io.legado.app.utils.viewbindingdelegate.viewBinding\nimport splitties.views.onClick\nimport java.util.*\n\nclass AppLogDialog : BaseDialogFragment(R.layout.dialog_recycler_view),\n    Toolbar.OnMenuItemClickListener {\n\n    private val binding by viewBinding(DialogRecyclerViewBinding::bind)\n    private val adapter by lazy {\n        LogAdapter(requireContext())\n    }\n\n    override fun onStart() {\n        super.onStart()\n        setLayout(0.9f, ViewGroup.LayoutParams.WRAP_CONTENT)\n    }\n\n    override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) {\n        binding.run {\n            toolBar.setBackgroundColor(primaryColor)\n            toolBar.setTitle(R.string.log)\n            toolBar.inflateMenu(R.menu.app_log)\n            toolBar.setOnMenuItemClickListener(this@AppLogDialog)\n            recyclerView.layoutManager = LinearLayoutManager(requireContext())\n            recyclerView.adapter = adapter\n        }\n        adapter.setItems(AppLog.logs)\n    }\n\n    override fun onMenuItemClick(item: MenuItem?): Boolean {\n        when (item?.itemId) {\n            R.id.menu_clear -> {\n                AppLog.clear()\n                adapter.clearItems()\n            }\n        }\n        return true\n    }\n\n    inner class LogAdapter(context: Context) :\n        RecyclerAdapter<Triple<Long, String, Throwable?>, ItemAppLogBinding>(context) {\n\n        override fun getViewBinding(parent: ViewGroup): ItemAppLogBinding {\n            return ItemAppLogBinding.inflate(inflater, parent, false)\n        }\n\n        override fun convert(\n            holder: ItemViewHolder,\n            binding: ItemAppLogBinding,\n            item: Triple<Long, String, Throwable?>,\n            payloads: MutableList<Any>\n        ) {\n            binding.textTime.text = LogUtils.logTimeFormat.format(Date(item.first))\n            binding.textMessage.text = item.second\n        }\n\n        override fun registerListener(holder: ItemViewHolder, binding: ItemAppLogBinding) {\n            binding.root.onClick {\n                getItem(holder.layoutPosition)?.let { item ->\n                    item.third?.let {\n                        showDialogFragment(TextDialog(\"Log\", it.stackTraceToString()))\n                    }\n                }\n            }\n        }\n\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/about/CrashLogsDialog.kt",
    "content": "package io.legado.app.ui.about\n\nimport android.app.Application\nimport android.net.Uri\nimport android.os.Bundle\nimport android.view.MenuItem\nimport android.view.View\nimport android.view.ViewGroup\nimport androidx.appcompat.widget.Toolbar\nimport androidx.fragment.app.viewModels\nimport androidx.lifecycle.MutableLiveData\nimport androidx.lifecycle.lifecycleScope\nimport androidx.recyclerview.widget.LinearLayoutManager\nimport io.legado.app.R\nimport io.legado.app.base.BaseDialogFragment\nimport io.legado.app.base.BaseViewModel\nimport io.legado.app.base.adapter.ItemViewHolder\nimport io.legado.app.base.adapter.RecyclerAdapter\nimport io.legado.app.databinding.DialogRecyclerViewBinding\nimport io.legado.app.databinding.Item1lineTextBinding\nimport io.legado.app.help.config.AppConfig\nimport io.legado.app.lib.theme.primaryColor\nimport io.legado.app.ui.widget.dialog.TextDialog\nimport io.legado.app.utils.FileDoc\nimport io.legado.app.utils.FileUtils\nimport io.legado.app.utils.delete\nimport io.legado.app.utils.find\nimport io.legado.app.utils.getFile\nimport io.legado.app.utils.list\nimport io.legado.app.utils.setLayout\nimport io.legado.app.utils.showDialogFragment\nimport io.legado.app.utils.toastOnUi\nimport io.legado.app.utils.viewbindingdelegate.viewBinding\nimport kotlinx.coroutines.isActive\nimport java.io.FileFilter\n\nclass CrashLogsDialog : BaseDialogFragment(R.layout.dialog_recycler_view),\n    Toolbar.OnMenuItemClickListener {\n\n    private val binding by viewBinding(DialogRecyclerViewBinding::bind)\n    private val viewModel by viewModels<CrashViewModel>()\n    private val adapter by lazy { LogAdapter() }\n\n    override fun onStart() {\n        super.onStart()\n        setLayout(0.9f, ViewGroup.LayoutParams.WRAP_CONTENT)\n    }\n\n    override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) {\n        binding.toolBar.setBackgroundColor(primaryColor)\n        binding.toolBar.setTitle(R.string.crash_log)\n        binding.toolBar.inflateMenu(R.menu.crash_log)\n        binding.toolBar.setOnMenuItemClickListener(this)\n        binding.recyclerView.layoutManager = LinearLayoutManager(requireContext())\n        binding.recyclerView.adapter = adapter\n        viewModel.logLiveData.observe(viewLifecycleOwner) {\n            adapter.setItems(it)\n        }\n        viewModel.initData()\n    }\n\n    override fun onMenuItemClick(item: MenuItem): Boolean {\n        when (item.itemId) {\n            R.id.menu_clear -> viewModel.clearCrashLog()\n        }\n        return true\n    }\n\n    private fun showLogFile(fileDoc: FileDoc) {\n        viewModel.readFile(fileDoc) {\n            if (lifecycleScope.isActive) {\n                showDialogFragment(TextDialog(fileDoc.name, it))\n            }\n        }\n\n    }\n\n    inner class LogAdapter : RecyclerAdapter<FileDoc, Item1lineTextBinding>(requireContext()) {\n\n        override fun getViewBinding(parent: ViewGroup): Item1lineTextBinding {\n            return Item1lineTextBinding.inflate(inflater, parent, false)\n        }\n\n        override fun registerListener(holder: ItemViewHolder, binding: Item1lineTextBinding) {\n            binding.root.setOnClickListener {\n                getItemByLayoutPosition(holder.layoutPosition)?.let { item ->\n                    showLogFile(item)\n                }\n            }\n        }\n\n        override fun convert(\n            holder: ItemViewHolder,\n            binding: Item1lineTextBinding,\n            item: FileDoc,\n            payloads: MutableList<Any>\n        ) {\n            binding.textView.text = item.name\n        }\n\n    }\n\n    class CrashViewModel(application: Application) : BaseViewModel(application) {\n\n        val logLiveData = MutableLiveData<List<FileDoc>>()\n\n        fun initData() {\n            execute {\n                val list = arrayListOf<FileDoc>()\n                context.externalCacheDir\n                    ?.getFile(\"crash\")\n                    ?.listFiles(FileFilter { it.isFile })\n                    ?.forEach {\n                        list.add(FileDoc.fromFile(it))\n                    }\n                val backupPath = AppConfig.backupPath\n                if (!backupPath.isNullOrEmpty()) {\n                    val uri = Uri.parse(backupPath)\n                    FileDoc.fromUri(uri, true)\n                        .find(\"crash\")\n                        ?.list {\n                            !it.isDir\n                        }?.let {\n                            list.addAll(it)\n                        }\n                }\n                return@execute list.sortedByDescending { it.name }.distinctBy { it.name }\n            }.onSuccess {\n                logLiveData.postValue(it)\n            }\n        }\n\n        fun readFile(fileDoc: FileDoc, success: (String) -> Unit) {\n            execute {\n                String(fileDoc.readBytes())\n            }.onSuccess {\n                success.invoke(it)\n            }.onError {\n                context.toastOnUi(it.localizedMessage)\n            }\n        }\n\n        fun clearCrashLog() {\n            execute {\n                context.externalCacheDir\n                    ?.getFile(\"crash\")\n                    ?.let {\n                        FileUtils.delete(it, false)\n                    }\n                val backupPath = AppConfig.backupPath\n                if (!backupPath.isNullOrEmpty()) {\n                    val uri = Uri.parse(backupPath)\n                    FileDoc.fromUri(uri, true)\n                        .find(\"crash\")\n                        ?.delete()\n                }\n            }.onError {\n                context.toastOnUi(it.localizedMessage)\n            }.onFinally {\n                initData()\n            }\n        }\n\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/about/ReadRecordActivity.kt",
    "content": "package io.legado.app.ui.about\n\nimport android.content.Context\nimport android.os.Bundle\nimport android.view.Menu\nimport android.view.MenuItem\nimport android.view.ViewGroup\nimport androidx.appcompat.widget.SearchView\nimport androidx.lifecycle.lifecycleScope\nimport io.legado.app.R\nimport io.legado.app.base.BaseActivity\nimport io.legado.app.base.adapter.ItemViewHolder\nimport io.legado.app.base.adapter.RecyclerAdapter\nimport io.legado.app.data.appDb\nimport io.legado.app.data.entities.ReadRecordShow\nimport io.legado.app.databinding.ActivityReadRecordBinding\nimport io.legado.app.databinding.ItemReadRecordBinding\nimport io.legado.app.help.config.AppConfig\nimport io.legado.app.help.config.LocalConfig\nimport io.legado.app.lib.dialogs.alert\nimport io.legado.app.lib.theme.primaryTextColor\nimport io.legado.app.ui.book.search.SearchActivity\nimport io.legado.app.utils.applyNavigationBarPadding\nimport io.legado.app.utils.applyTint\nimport io.legado.app.utils.cnCompare\nimport io.legado.app.utils.getInt\nimport io.legado.app.utils.putInt\nimport io.legado.app.utils.startActivityForBook\nimport io.legado.app.utils.viewbindingdelegate.viewBinding\nimport kotlinx.coroutines.Dispatchers.IO\nimport kotlinx.coroutines.launch\nimport kotlinx.coroutines.withContext\nimport java.text.SimpleDateFormat\nimport java.util.Locale\n\nclass ReadRecordActivity : BaseActivity<ActivityReadRecordBinding>() {\n\n    private val adapter by lazy { RecordAdapter(this) }\n    private var sortMode\n        get() = LocalConfig.getInt(\"readRecordSort\")\n        set(value) {\n            LocalConfig.putInt(\"readRecordSort\", value)\n        }\n    private val searchView: SearchView by lazy {\n        binding.titleBar.findViewById(R.id.search_view)\n    }\n\n    override val binding by viewBinding(ActivityReadRecordBinding::inflate)\n\n    override fun onActivityCreated(savedInstanceState: Bundle?) {\n        initView()\n        initAllTime()\n        initData()\n    }\n\n    override fun onCompatCreateOptionsMenu(menu: Menu): Boolean {\n        menuInflater.inflate(R.menu.book_read_record, menu)\n        return super.onCompatCreateOptionsMenu(menu)\n    }\n\n    override fun onMenuOpened(featureId: Int, menu: Menu): Boolean {\n        menu.findItem(R.id.menu_enable_record)?.isChecked = AppConfig.enableReadRecord\n        when (sortMode) {\n            1 -> menu.findItem(R.id.menu_sort_read_long)?.isChecked = true\n            2 -> menu.findItem(R.id.menu_sort_read_time)?.isChecked = true\n            else -> menu.findItem(R.id.menu_sort_name)?.isChecked = true\n        }\n        return super.onMenuOpened(featureId, menu)\n    }\n\n    override fun onCompatOptionsItemSelected(item: MenuItem): Boolean {\n        when (item.itemId) {\n            R.id.menu_sort_name -> {\n                sortMode = 0\n                item.isChecked = true\n                initData()\n            }\n\n            R.id.menu_sort_read_long -> {\n                sortMode = 1\n                item.isChecked = true\n                initData()\n            }\n\n            R.id.menu_sort_read_time -> {\n                sortMode = 2\n                item.isChecked = true\n                initData()\n            }\n\n            R.id.menu_enable_record -> {\n                AppConfig.enableReadRecord = !item.isChecked\n            }\n        }\n        return super.onCompatOptionsItemSelected(item)\n    }\n\n    private fun initView() {\n        initSearchView()\n        binding.tvBookName.setText(R.string.all_read_time)\n        binding.tvRemove.setOnClickListener {\n            alert(R.string.delete, R.string.sure_del) {\n                yesButton {\n                    appDb.readRecordDao.clear()\n                    initData()\n                }\n                noButton()\n            }\n        }\n        binding.recyclerView.adapter = adapter\n        binding.recyclerView.applyNavigationBarPadding()\n    }\n\n    private fun initSearchView() {\n        searchView.applyTint(primaryTextColor)\n        searchView.isSubmitButtonEnabled = true\n        searchView.queryHint = getString(R.string.search)\n        searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener {\n            override fun onQueryTextSubmit(query: String): Boolean {\n                searchView.clearFocus()\n                return false\n            }\n\n            override fun onQueryTextChange(newText: String?): Boolean {\n                initData(newText)\n                return false\n            }\n        })\n    }\n\n    private fun initAllTime() {\n        lifecycleScope.launch {\n            val allTime = withContext(IO) {\n                appDb.readRecordDao.allTime\n            }\n            binding.tvReadingTime.text = formatDuring(allTime)\n        }\n    }\n\n    private fun initData(searchKey: String? = null) {\n        lifecycleScope.launch {\n            val readRecords = withContext(IO) {\n                appDb.readRecordDao.search(searchKey ?: \"\").let { records ->\n                    when (sortMode) {\n                        1 -> records.sortedByDescending { it.readTime }\n                        2 -> records.sortedByDescending { it.lastRead }\n                        else -> records.sortedWith { o1, o2 ->\n                            o1.bookName.cnCompare(o2.bookName)\n                        }\n                    }\n                }\n            }\n            adapter.setItems(readRecords)\n        }\n    }\n\n    inner class RecordAdapter(context: Context) :\n        RecyclerAdapter<ReadRecordShow, ItemReadRecordBinding>(context) {\n\n        private val dateFormat = SimpleDateFormat(\"yyyy-MM-dd\", Locale.getDefault())\n\n        override fun getViewBinding(parent: ViewGroup): ItemReadRecordBinding {\n            return ItemReadRecordBinding.inflate(inflater, parent, false)\n        }\n\n        override fun convert(\n            holder: ItemViewHolder,\n            binding: ItemReadRecordBinding,\n            item: ReadRecordShow,\n            payloads: MutableList<Any>,\n        ) {\n            binding.apply {\n                tvBookName.text = item.bookName\n                tvReadingTime.text = formatDuring(item.readTime)\n                if (item.lastRead > 0) {\n                    tvLastReadTime.text = dateFormat.format(item.lastRead)\n                } else {\n                    tvLastReadTime.text = \"\"\n                }\n            }\n        }\n\n        override fun registerListener(holder: ItemViewHolder, binding: ItemReadRecordBinding) {\n            binding.apply {\n                root.setOnClickListener {\n                    val item = getItem(holder.layoutPosition) ?: return@setOnClickListener\n                    lifecycleScope.launch {\n                        val book = withContext(IO) {\n                            appDb.bookDao.findByName(item.bookName).firstOrNull()\n                        }\n                        if (book == null) {\n                            SearchActivity.start(this@ReadRecordActivity, item.bookName)\n                        } else {\n                            startActivityForBook(book)\n                        }\n                    }\n                }\n                tvRemove.setOnClickListener {\n                    getItem(holder.layoutPosition)?.let { item ->\n                        sureDelAlert(item)\n                    }\n                }\n            }\n        }\n\n        private fun sureDelAlert(item: ReadRecordShow) {\n            alert(R.string.delete) {\n                setMessage(getString(R.string.sure_del_any, item.bookName))\n                yesButton {\n                    appDb.readRecordDao.deleteByName(item.bookName)\n                    initData()\n                }\n                noButton()\n            }\n        }\n\n    }\n\n    fun formatDuring(mss: Long): String {\n        val days = mss / (1000 * 60 * 60 * 24)\n        val hours = mss % (1000 * 60 * 60 * 24) / (1000 * 60 * 60)\n        val minutes = mss % (1000 * 60 * 60) / (1000 * 60)\n        val seconds = mss % (1000 * 60) / 1000\n        val d = if (days > 0) \"${days}天\" else \"\"\n        val h = if (hours > 0) \"${hours}小时\" else \"\"\n        val m = if (minutes > 0) \"${minutes}分钟\" else \"\"\n        val s = if (seconds > 0) \"${seconds}秒\" else \"\"\n        var time = \"$d$h$m$s\"\n        if (time.isBlank()) {\n            time = \"0秒\"\n        }\n        return time\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/about/UpdateDialog.kt",
    "content": "package io.legado.app.ui.about\n\nimport android.os.Bundle\nimport android.view.View\nimport android.view.ViewGroup\nimport io.legado.app.R\nimport io.legado.app.base.BaseDialogFragment\nimport io.legado.app.databinding.DialogUpdateBinding\nimport io.legado.app.help.update.AppUpdate\nimport io.legado.app.lib.theme.primaryColor\nimport io.legado.app.model.Download\nimport io.legado.app.utils.setLayout\nimport io.legado.app.utils.toastOnUi\nimport io.legado.app.utils.viewbindingdelegate.viewBinding\nimport io.noties.markwon.Markwon\nimport io.noties.markwon.ext.tables.TablePlugin\nimport io.noties.markwon.html.HtmlPlugin\nimport io.noties.markwon.image.glide.GlideImagesPlugin\n\nclass UpdateDialog() : BaseDialogFragment(R.layout.dialog_update) {\n\n    constructor(updateInfo: AppUpdate.UpdateInfo) : this() {\n        arguments = Bundle().apply {\n            putString(\"newVersion\", updateInfo.tagName)\n            putString(\"updateBody\", updateInfo.updateLog)\n            putString(\"url\", updateInfo.downloadUrl)\n            putString(\"name\", updateInfo.fileName)\n        }\n    }\n\n    val binding by viewBinding(DialogUpdateBinding::bind)\n\n    override fun onStart() {\n        super.onStart()\n        setLayout(0.9f, ViewGroup.LayoutParams.WRAP_CONTENT)\n    }\n\n    override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) {\n        binding.toolBar.setBackgroundColor(primaryColor)\n        binding.toolBar.title = arguments?.getString(\"newVersion\")\n        val updateBody = arguments?.getString(\"updateBody\")\n        if (updateBody == null) {\n            toastOnUi(\"没有数据\")\n            dismiss()\n            return\n        }\n        binding.textView.post {\n            Markwon.builder(requireContext())\n                .usePlugin(GlideImagesPlugin.create(requireContext()))\n                .usePlugin(HtmlPlugin.create())\n                .usePlugin(TablePlugin.create(requireContext()))\n                .build()\n                .setMarkdown(binding.textView, updateBody)\n        }\n        binding.toolBar.inflateMenu(R.menu.app_update)\n        binding.toolBar.setOnMenuItemClickListener {\n            when (it.itemId) {\n                R.id.menu_download -> {\n                    val url = arguments?.getString(\"url\")\n                    val name = arguments?.getString(\"name\")\n                    if (url != null && name != null) {\n                        Download.start(requireContext(), url, name)\n                        toastOnUi(R.string.download_start)\n                    }\n                }\n            }\n            return@setOnMenuItemClickListener true\n        }\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/association/AddToBookshelfDialog.kt",
    "content": "package io.legado.app.ui.association\n\nimport android.annotation.SuppressLint\nimport android.app.Application\nimport android.content.DialogInterface\nimport android.os.Bundle\nimport android.view.View\nimport android.view.ViewGroup\nimport androidx.fragment.app.viewModels\nimport androidx.lifecycle.MutableLiveData\nimport io.legado.app.R\nimport io.legado.app.base.BaseDialogFragment\nimport io.legado.app.base.BaseViewModel\nimport io.legado.app.constant.AppLog\nimport io.legado.app.data.appDb\nimport io.legado.app.data.entities.Book\nimport io.legado.app.data.entities.BookSource\nimport io.legado.app.databinding.DialogAddToBookshelfBinding\nimport io.legado.app.exception.NoStackTraceException\nimport io.legado.app.model.analyzeRule.AnalyzeUrl\nimport io.legado.app.model.webBook.WebBook\nimport io.legado.app.ui.book.info.BookInfoActivity\nimport io.legado.app.utils.GSON\nimport io.legado.app.utils.NetworkUtils\nimport io.legado.app.utils.fromJsonObject\nimport io.legado.app.utils.setLayout\nimport io.legado.app.utils.startActivity\nimport io.legado.app.utils.toastOnUi\nimport io.legado.app.utils.viewbindingdelegate.viewBinding\n\n/**\n * 添加书籍链接到书架，需要对应网站书源\n * ${origin}/${path}, {origin: bookSourceUrl}\n * 按以下顺序尝试匹配书源并添加网址\n * - UrlOption中的指定的书源网址bookSourceUrl\n * - 在所有启用的书源中匹配orgin\n * - 在所有启用的书源中使用详情页正则匹配${origin}/${path}, {origin: bookSourceUrl}\n */\nclass AddToBookshelfDialog() : BaseDialogFragment(R.layout.dialog_add_to_bookshelf) {\n\n    constructor(bookUrl: String, finishOnDismiss: Boolean = false) : this() {\n        arguments = Bundle().apply {\n            putString(\"bookUrl\", bookUrl)\n            putBoolean(\"finishOnDismiss\", finishOnDismiss)\n        }\n    }\n\n    val binding by viewBinding(DialogAddToBookshelfBinding::bind)\n    val viewModel by viewModels<ViewModel>()\n\n    override fun onStart() {\n        super.onStart()\n        setLayout(0.9f, ViewGroup.LayoutParams.WRAP_CONTENT)\n    }\n\n    override fun onDismiss(dialog: DialogInterface) {\n        super.onDismiss(dialog)\n        if (arguments?.getBoolean(\"finishOnDismiss\") == true) {\n            activity?.finish()\n        }\n    }\n\n    @SuppressLint(\"SetTextI18n\")\n    override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) {\n        val bookUrl = arguments?.getString(\"bookUrl\")\n        if (bookUrl.isNullOrBlank()) {\n            toastOnUi(\"url不能为空\")\n            dismiss()\n            return\n        }\n        viewModel.loadStateLiveData.observe(this) {\n            if (it) {\n                binding.rotateLoading.visible()\n            } else {\n                binding.rotateLoading.gone()\n            }\n        }\n        viewModel.loadErrorLiveData.observe(this) {\n            toastOnUi(it)\n            dismiss()\n        }\n        viewModel.load(bookUrl) {\n            viewModel.saveSearchBook(it) {\n                startActivity<BookInfoActivity> {\n                    putExtra(\"name\", it.name)\n                    putExtra(\"author\", it.author)\n                    putExtra(\"bookUrl\", it.bookUrl)\n                }\n                dismiss()\n            }\n        }\n        binding.tvCancel.setOnClickListener {\n            dismiss()\n        }\n    }\n\n    class ViewModel(application: Application) : BaseViewModel(application) {\n\n        val loadStateLiveData = MutableLiveData<Boolean>()\n        val loadErrorLiveData = MutableLiveData<String>()\n        var book: Book? = null\n\n        fun load(bookUrl: String, success: (book: Book) -> Unit) {\n            execute {\n                appDb.bookDao.getBook(bookUrl)?.let {\n                    throw NoStackTraceException(\"${it.name} 已在书架\")\n                }\n                val baseUrl = NetworkUtils.getBaseUrl(bookUrl)\n                    ?: throw NoStackTraceException(\"书籍地址格式不对\")\n                val urlMatcher = AnalyzeUrl.paramPattern.matcher(bookUrl)\n                if (urlMatcher.find()) {\n                    val origin = GSON.fromJsonObject<AnalyzeUrl.UrlOption>(\n                        bookUrl.substring(urlMatcher.end())\n                    ).getOrNull()?.getOrigin()\n                    origin?.let {\n                        val source = appDb.bookSourceDao.getBookSource(it)\n                        source?.let {\n                            getBookInfo(bookUrl, source)?.let { book ->\n                                return@execute book\n                            }\n                        }\n                    }\n                }\n                appDb.bookSourceDao.getBookSourceAddBook(baseUrl)?.let { source ->\n                    getBookInfo(bookUrl, source)?.let { book ->\n                        return@execute book\n                    }\n                }\n                appDb.bookSourceDao.hasBookUrlPattern.forEach { source ->\n                    try {\n                        val bs = source.getBookSource()!!\n                        if (bookUrl.matches(bs.bookUrlPattern!!.toRegex())) {\n                            getBookInfo(bookUrl, bs)?.let { book ->\n                                return@execute book\n                            }\n                        }\n                    } catch (_: Exception) {\n                    }\n                }\n                throw NoStackTraceException(\"未找到匹配书源\")\n            }.onError {\n                AppLog.put(\"添加书籍 $bookUrl 出错\", it)\n                loadErrorLiveData.postValue(it.localizedMessage)\n            }.onSuccess {\n                book = it\n                success.invoke(it)\n            }.onStart {\n                loadStateLiveData.postValue(true)\n            }.onFinally {\n                loadStateLiveData.postValue(false)\n            }\n        }\n\n        private suspend fun getBookInfo(bookUrl: String, source: BookSource): Book? {\n            return kotlin.runCatching {\n                val book = Book(\n                    bookUrl = bookUrl,\n                    origin = source.bookSourceUrl,\n                    originName = source.bookSourceName\n                )\n                WebBook.getBookInfoAwait(source, book)\n            }.getOrNull()\n        }\n\n        fun saveSearchBook(book: Book, success: () -> Unit) {\n            execute {\n                val searchBook = book.toSearchBook()\n                appDb.searchBookDao.insert(searchBook)\n                searchBook\n            }.onSuccess {\n                success.invoke()\n            }\n        }\n\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/association/BaseAssociationViewModel.kt",
    "content": "package io.legado.app.ui.association\n\nimport android.app.Application\nimport android.net.Uri\nimport androidx.lifecycle.MutableLiveData\nimport io.legado.app.base.BaseViewModel\nimport io.legado.app.utils.inputStream\nimport io.legado.app.utils.jsonPath\n\nabstract class BaseAssociationViewModel(application: Application) : BaseViewModel(application) {\n\n    val successLive = MutableLiveData<Pair<String, String>>()\n    val errorLive = MutableLiveData<String>()\n\n    fun importJson(uri: Uri) {\n        val map = uri.inputStream(context).getOrThrow().use {\n            jsonPath.parse(it).read<Map<String, *>>(\"$[0]\")\n        } ?: uri.inputStream(context).getOrThrow().use {\n            jsonPath.parse(it).read(\"$\")\n        }\n\n        when {\n            map.containsKey(\"bookSourceUrl\") ->\n                successLive.postValue(\"bookSource\" to uri.toString())\n\n            map.containsKey(\"sourceUrl\") ->\n                successLive.postValue(\"rssSource\" to uri.toString())\n\n            map.containsKey(\"pattern\") ->\n                successLive.postValue(\"replaceRule\" to uri.toString())\n\n            map.containsKey(\"themeName\") ->\n                successLive.postValue(\"theme\" to uri.toString())\n\n            map.containsKey(\"showRule\") ->\n                successLive.postValue(\"dictRule\" to uri.toString())\n\n            map.containsKey(\"name\") && map.containsKey(\"rule\") ->\n                successLive.postValue(\"txtRule\" to uri.toString())\n\n            map.containsKey(\"name\") && map.containsKey(\"url\") ->\n                successLive.postValue(\"httpTts\" to uri.toString())\n\n            else -> errorLive.postValue(\"格式不对\")\n        }\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/association/FileAssociationActivity.kt",
    "content": "package io.legado.app.ui.association\n\nimport android.net.Uri\nimport android.os.Bundle\nimport androidx.activity.viewModels\nimport androidx.core.os.postDelayed\nimport androidx.documentfile.provider.DocumentFile\nimport androidx.lifecycle.lifecycleScope\nimport io.legado.app.R\nimport io.legado.app.base.VMBaseActivity\nimport io.legado.app.constant.AppLog\nimport io.legado.app.databinding.ActivityTranslucenceBinding\nimport io.legado.app.exception.InvalidBooksDirException\nimport io.legado.app.help.config.AppConfig\nimport io.legado.app.lib.dialogs.alert\nimport io.legado.app.lib.permission.Permissions\nimport io.legado.app.lib.permission.PermissionsCompat\nimport io.legado.app.ui.file.HandleFileContract\nimport io.legado.app.utils.FileUtils\nimport io.legado.app.utils.buildMainHandler\nimport io.legado.app.utils.canRead\nimport io.legado.app.utils.checkWrite\nimport io.legado.app.utils.getFile\nimport io.legado.app.utils.isContentScheme\nimport io.legado.app.utils.readUri\nimport io.legado.app.utils.showDialogFragment\nimport io.legado.app.utils.startActivity\nimport io.legado.app.utils.startActivityForBook\nimport io.legado.app.utils.toastOnUi\nimport io.legado.app.utils.viewbindingdelegate.viewBinding\nimport kotlinx.coroutines.Dispatchers.IO\nimport kotlinx.coroutines.launch\nimport kotlinx.coroutines.withContext\nimport splitties.init.appCtx\nimport java.io.File\nimport java.io.FileOutputStream\n\nclass FileAssociationActivity :\n    VMBaseActivity<ActivityTranslucenceBinding, FileAssociationViewModel>() {\n\n    private val localBookTreeSelect = registerForActivityResult(HandleFileContract()) {\n        intent.data?.let { uri ->\n            it.uri?.let { treeUri ->\n                AppConfig.defaultBookTreeUri = treeUri.toString()\n                importBook(treeUri, uri)\n            } ?: let {\n                val storageHelp = String(assets.open(\"storageHelp.md\").readBytes())\n                toastOnUi(storageHelp)\n                importBook(null, uri)\n            }\n        }\n    }\n\n    override val binding by viewBinding(ActivityTranslucenceBinding::inflate)\n\n    override val viewModel by viewModels<FileAssociationViewModel>()\n\n    private val handler by lazy {\n        buildMainHandler()\n    }\n\n    override fun onActivityCreated(savedInstanceState: Bundle?) {\n        binding.rotateLoading.visible()\n        viewModel.importBookLiveData.observe(this) { uri ->\n            importBook(uri)\n        }\n        viewModel.onLineImportLive.observe(this) {\n            startActivity<OnLineImportActivity> {\n                data = it\n            }\n            finish()\n        }\n        viewModel.successLive.observe(this) {\n            when (it.first) {\n                \"bookSource\" -> showDialogFragment(ImportBookSourceDialog(it.second, true))\n                \"rssSource\" -> showDialogFragment(ImportRssSourceDialog(it.second, true))\n                \"replaceRule\" -> showDialogFragment(ImportReplaceRuleDialog(it.second, true))\n                \"httpTts\" -> showDialogFragment(ImportHttpTtsDialog(it.second, true))\n                \"theme\" -> showDialogFragment(ImportThemeDialog(it.second, true))\n                \"txtRule\" -> showDialogFragment(ImportTxtTocRuleDialog(it.second, true))\n                \"dictRule\" -> showDialogFragment(ImportDictRuleDialog(it.second, true))\n            }\n        }\n        viewModel.errorLive.observe(this) {\n            binding.rotateLoading.gone()\n            toastOnUi(it)\n            handler.postDelayed(2000) {\n                finish()\n            }\n        }\n        viewModel.openBookLiveData.observe(this) {\n            binding.rotateLoading.gone()\n            startActivityForBook(it)\n            finish()\n        }\n        viewModel.notSupportedLiveData.observe(this) { data ->\n            binding.rotateLoading.gone()\n            alert(\n                title = appCtx.getString(R.string.draw),\n                message = appCtx.getString(R.string.file_not_supported, data.second)\n            ) {\n                yesButton {\n                    importBook(data.first)\n                }\n                noButton {\n                    finish()\n                }\n                onCancelled {\n                    finish()\n                }\n            }\n        }\n        intent.data?.let { data ->\n            if (data.isContentScheme() && data.canRead()) {\n                viewModel.dispatchIntent(data)\n            } else {\n                PermissionsCompat.Builder()\n                    .addPermissions(*Permissions.Group.STORAGE)\n                    .rationale(R.string.tip_perm_request_storage)\n                    .onGranted {\n                        viewModel.dispatchIntent(data)\n                    }.onDenied {\n                        toastOnUi(\"请求存储权限失败。\")\n                        handler.postDelayed(2000) {\n                            finish()\n                        }\n                    }.request()\n            }\n        } ?: finish()\n    }\n\n    private fun importBook(uri: Uri) {\n        if (uri.isContentScheme()) {\n            val treeUriStr = AppConfig.defaultBookTreeUri\n            if (treeUriStr.isNullOrEmpty()) {\n                localBookTreeSelect.launch {\n                    title = getString(R.string.select_book_folder)\n                    mode = HandleFileContract.DIR_SYS\n                }\n            } else {\n                importBook(Uri.parse(treeUriStr), uri)\n            }\n        } else {\n            importBook(null, uri)\n        }\n    }\n\n    private fun importBook(treeUri: Uri?, uri: Uri) {\n        lifecycleScope.launch {\n            runCatching {\n                withContext(IO) {\n                    if (treeUri == null) {\n                        viewModel.importBook(uri)\n                    } else if (treeUri.isContentScheme()) {\n                        val treeDoc =\n                            DocumentFile.fromTreeUri(this@FileAssociationActivity, treeUri)\n                        if (!treeDoc!!.checkWrite()) {\n                            throw InvalidBooksDirException(\n                                \"请重新设置书籍保存位置\\nPermission Denial\"\n                            )\n                        }\n                        readUri(uri) { fileDoc, inputStream ->\n                            val name = fileDoc.name\n                            var doc = treeDoc.findFile(name)\n                            if (doc == null || fileDoc.lastModified > doc.lastModified()) {\n                                if (doc == null) {\n                                    doc = treeDoc.createFile(FileUtils.getMimeType(name), name)\n                                        ?: throw InvalidBooksDirException(\n                                            \"请重新设置书籍保存位置\\nPermission Denial\"\n                                        )\n                                }\n                                contentResolver.openOutputStream(doc.uri)!!.use { oStream ->\n                                    inputStream.copyTo(oStream)\n                                    oStream.flush()\n                                }\n                            }\n                            viewModel.importBook(doc.uri)\n                        }\n                    } else {\n                        val treeFile = File(treeUri.path ?: treeUri.toString())\n                        if (!treeFile.checkWrite()) {\n                            throw InvalidBooksDirException(\n                                \"请重新设置书籍保存位置\\nPermission Denial\"\n                            )\n                        }\n                        readUri(uri) { fileDoc, inputStream ->\n                            val name = fileDoc.name\n                            val file = treeFile.getFile(name)\n                            if (!file.exists() || fileDoc.lastModified > file.lastModified()) {\n                                FileOutputStream(file).use { oStream ->\n                                    inputStream.copyTo(oStream)\n                                    oStream.flush()\n                                }\n                            }\n                            viewModel.importBook(Uri.fromFile(file))\n                        }\n                    }\n                }\n            }.onFailure {\n                when (it) {\n                    is InvalidBooksDirException -> localBookTreeSelect.launch {\n                        title = getString(R.string.select_book_folder)\n                        mode = HandleFileContract.DIR_SYS\n                    }\n\n                    else -> {\n                        val msg = \"导入书籍失败\\n${it.localizedMessage}\"\n                        AppLog.put(msg, it)\n                        toastOnUi(msg)\n                        handler.postDelayed(2000) {\n                            finish()\n                        }\n                    }\n                }\n            }\n        }\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/association/FileAssociationViewModel.kt",
    "content": "package io.legado.app.ui.association\n\nimport android.app.Application\nimport android.net.Uri\nimport androidx.lifecycle.MutableLiveData\nimport io.legado.app.constant.AppLog\nimport io.legado.app.constant.AppPattern\nimport io.legado.app.constant.AppPattern.bookFileRegex\nimport io.legado.app.data.entities.Book\nimport io.legado.app.model.localBook.LocalBook\nimport io.legado.app.utils.*\n\nclass FileAssociationViewModel(application: Application) : BaseAssociationViewModel(application) {\n    val importBookLiveData = MutableLiveData<Uri>()\n    val onLineImportLive = MutableLiveData<Uri>()\n    val openBookLiveData = MutableLiveData<Book>()\n    val notSupportedLiveData = MutableLiveData<Pair<Uri, String>>()\n\n    fun dispatchIntent(uri: Uri) {\n        execute {\n            //如果是普通的url，需要根据返回的内容判断是什么\n            if (uri.isContentScheme() || uri.isFileScheme()) {\n                val fileDoc = FileDoc.fromUri(uri, false)\n                val fileName = fileDoc.name\n                if (fileName.matches(AppPattern.archiveFileRegex)) {\n                    ArchiveUtils.deCompress(fileDoc, ArchiveUtils.TEMP_PATH) {\n                        it.matches(bookFileRegex)\n                    }.forEach {\n                        dispatch(FileDoc.fromFile(it))\n                    }\n                } else {\n                    dispatch(fileDoc)\n                }\n            } else {\n                onLineImportLive.postValue(uri)\n            }\n        }.onError {\n            it.printOnDebug()\n            val msg = \"无法打开文件\\n${it.localizedMessage}\"\n            errorLive.postValue(msg)\n            AppLog.put(msg, it)\n        }\n    }\n\n    private fun dispatch(fileDoc: FileDoc) {\n        kotlin.runCatching {\n            if (fileDoc.openInputStream().getOrNull().isJson()) {\n                importJson(fileDoc.uri)\n                return\n            }\n        }.onFailure {\n            it.printOnDebug()\n            AppLog.put(\"尝试导入为JSON文件失败\\n${it.localizedMessage}\", it)\n        }\n        if (fileDoc.name.matches(bookFileRegex)) {\n            importBookLiveData.postValue(fileDoc.uri)\n            return\n        }\n        notSupportedLiveData.postValue(Pair(fileDoc.uri, fileDoc.name))\n    }\n\n    fun importBook(uri: Uri) {\n        val book = LocalBook.importFile(uri)\n        openBookLiveData.postValue(book)\n    }\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/association/ImportBookSourceDialog.kt",
    "content": "package io.legado.app.ui.association\n\nimport android.annotation.SuppressLint\nimport android.content.Context\nimport android.content.DialogInterface\nimport android.os.Bundle\nimport android.view.MenuItem\nimport android.view.View\nimport android.view.ViewGroup\nimport androidx.appcompat.widget.Toolbar\nimport androidx.fragment.app.viewModels\nimport androidx.recyclerview.widget.LinearLayoutManager\nimport io.legado.app.R\nimport io.legado.app.base.BaseDialogFragment\nimport io.legado.app.base.adapter.ItemViewHolder\nimport io.legado.app.base.adapter.RecyclerAdapter\nimport io.legado.app.constant.PreferKey\nimport io.legado.app.data.appDb\nimport io.legado.app.data.entities.BookSource\nimport io.legado.app.databinding.DialogCustomGroupBinding\nimport io.legado.app.databinding.DialogRecyclerViewBinding\nimport io.legado.app.databinding.ItemSourceImportBinding\nimport io.legado.app.help.config.AppConfig\nimport io.legado.app.lib.dialogs.alert\nimport io.legado.app.lib.theme.primaryColor\nimport io.legado.app.ui.widget.dialog.CodeDialog\nimport io.legado.app.ui.widget.dialog.WaitDialog\nimport io.legado.app.utils.GSON\nimport io.legado.app.utils.dpToPx\nimport io.legado.app.utils.fromJsonObject\nimport io.legado.app.utils.putPrefBoolean\nimport io.legado.app.utils.setLayout\nimport io.legado.app.utils.showDialogFragment\nimport io.legado.app.utils.viewbindingdelegate.viewBinding\nimport io.legado.app.utils.visible\nimport splitties.views.onClick\n\n\n/**\n * 导入书源弹出窗口\n */\nclass ImportBookSourceDialog() : BaseDialogFragment(R.layout.dialog_recycler_view),\n    Toolbar.OnMenuItemClickListener,\n    CodeDialog.Callback {\n\n    constructor(source: String, finishOnDismiss: Boolean = false) : this() {\n        arguments = Bundle().apply {\n            putString(\"source\", source)\n            putBoolean(\"finishOnDismiss\", finishOnDismiss)\n        }\n    }\n\n    private val binding by viewBinding(DialogRecyclerViewBinding::bind)\n    private val viewModel by viewModels<ImportBookSourceViewModel>()\n    private val adapter by lazy { SourcesAdapter(requireContext()) }\n\n    override fun onStart() {\n        super.onStart()\n        setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)\n    }\n\n    override fun onDismiss(dialog: DialogInterface) {\n        super.onDismiss(dialog)\n        if (arguments?.getBoolean(\"finishOnDismiss\") == true) {\n            activity?.finish()\n        }\n    }\n\n    @SuppressLint(\"NotifyDataSetChanged\")\n    override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) {\n        binding.toolBar.setBackgroundColor(primaryColor)\n        binding.toolBar.setTitle(R.string.import_book_source)\n        binding.rotateLoading.visible()\n        initMenu()\n        binding.recyclerView.layoutManager = LinearLayoutManager(requireContext())\n        binding.recyclerView.adapter = adapter\n        binding.tvCancel.visible()\n        binding.tvCancel.setOnClickListener {\n            dismissAllowingStateLoss()\n        }\n        binding.tvOk.visible()\n        binding.tvOk.setOnClickListener {\n            val waitDialog = WaitDialog(requireContext())\n            waitDialog.show()\n            viewModel.importSelect {\n                waitDialog.dismiss()\n                dismissAllowingStateLoss()\n            }\n        }\n        binding.tvFooterLeft.visible()\n        binding.tvFooterLeft.setOnClickListener {\n            val selectAll = viewModel.isSelectAll\n            viewModel.selectStatus.forEachIndexed { index, b ->\n                if (b != !selectAll) {\n                    viewModel.selectStatus[index] = !selectAll\n                }\n            }\n            adapter.notifyDataSetChanged()\n            upSelectText()\n        }\n        viewModel.errorLiveData.observe(this) {\n            binding.rotateLoading.gone()\n            binding.tvMsg.apply {\n                text = it\n                visible()\n            }\n        }\n        viewModel.successLiveData.observe(this) {\n            binding.rotateLoading.gone()\n            if (it > 0) {\n                adapter.setItems(viewModel.allSources)\n                upSelectText()\n            } else {\n                binding.tvMsg.apply {\n                    setText(R.string.wrong_format)\n                    visible()\n                }\n            }\n        }\n        val source = arguments?.getString(\"source\")\n        if (source.isNullOrEmpty()) {\n            dismiss()\n            return\n        }\n        viewModel.importSource(source)\n    }\n\n    private fun upSelectText() {\n        if (viewModel.isSelectAll) {\n            binding.tvFooterLeft.text = getString(\n                R.string.select_cancel_count,\n                viewModel.selectCount,\n                viewModel.allSources.size\n            )\n        } else {\n            binding.tvFooterLeft.text = getString(\n                R.string.select_all_count,\n                viewModel.selectCount,\n                viewModel.allSources.size\n            )\n        }\n    }\n\n    private fun initMenu() {\n        binding.toolBar.setOnMenuItemClickListener(this)\n        binding.toolBar.inflateMenu(R.menu.import_source)\n        binding.toolBar.menu.findItem(R.id.menu_keep_original_name)\n            ?.isChecked = AppConfig.importKeepName\n        binding.toolBar.menu.findItem(R.id.menu_keep_group)\n            ?.isChecked = AppConfig.importKeepGroup\n        binding.toolBar.menu.findItem(R.id.menu_keep_enable)\n            ?.isChecked = AppConfig.importKeepEnable\n    }\n\n    @SuppressLint(\"InflateParams\", \"NotifyDataSetChanged\")\n    override fun onMenuItemClick(item: MenuItem): Boolean {\n        when (item.itemId) {\n            R.id.menu_new_group -> alertCustomGroup(item)\n            R.id.menu_select_new_source -> {\n                val selectAllNew = viewModel.isSelectAllNew\n                viewModel.newSourceStatus.forEachIndexed { index, b ->\n                    if (b) {\n                        viewModel.selectStatus[index] = !selectAllNew\n                    }\n                }\n                adapter.notifyDataSetChanged()\n                upSelectText()\n            }\n\n            R.id.menu_select_update_source -> {\n                val selectAllUpdate = viewModel.isSelectAllUpdate\n                viewModel.updateSourceStatus.forEachIndexed { index, b ->\n                    if (b) {\n                        viewModel.selectStatus[index] = !selectAllUpdate\n                    }\n                }\n                adapter.notifyDataSetChanged()\n                upSelectText()\n            }\n\n            R.id.menu_keep_original_name -> {\n                item.isChecked = !item.isChecked\n                putPrefBoolean(PreferKey.importKeepName, item.isChecked)\n            }\n\n            R.id.menu_keep_group -> {\n                item.isChecked = !item.isChecked\n                putPrefBoolean(PreferKey.importKeepGroup, item.isChecked)\n            }\n\n            R.id.menu_keep_enable -> {\n                item.isChecked = !item.isChecked\n                AppConfig.importKeepEnable = item.isChecked\n            }\n        }\n        return false\n    }\n\n    private fun alertCustomGroup(item: MenuItem) {\n        alert(R.string.diy_edit_source_group) {\n            val alertBinding = DialogCustomGroupBinding.inflate(layoutInflater).apply {\n                val groups = appDb.bookSourceDao.allGroups()\n                textInputLayout.setHint(R.string.group_name)\n                editView.setFilterValues(groups.toList())\n                editView.dropDownHeight = 180.dpToPx()\n            }\n            customView {\n                alertBinding.root\n            }\n            okButton {\n                viewModel.isAddGroup = alertBinding.swAddGroup.isChecked\n                viewModel.groupName = alertBinding.editView.text?.toString()\n                if (viewModel.groupName.isNullOrBlank()) {\n                    item.title = getString(R.string.diy_source_group)\n                } else {\n                    val group = getString(R.string.diy_edit_source_group_title, viewModel.groupName)\n                    if (viewModel.isAddGroup) {\n                        item.title = \"+$group\"\n                    } else {\n                        item.title = group\n                    }\n                }\n            }\n            cancelButton()\n        }\n    }\n\n    override fun onCodeSave(code: String, requestId: String?) {\n        requestId?.toInt()?.let {\n            GSON.fromJsonObject<BookSource>(code).getOrNull()?.let { source ->\n                viewModel.allSources[it] = source\n                adapter.setItem(it, source)\n            }\n        }\n    }\n\n    inner class SourcesAdapter(context: Context) :\n        RecyclerAdapter<BookSource, ItemSourceImportBinding>(context) {\n\n        override fun getViewBinding(parent: ViewGroup): ItemSourceImportBinding {\n            return ItemSourceImportBinding.inflate(inflater, parent, false)\n        }\n\n        override fun convert(\n            holder: ItemViewHolder,\n            binding: ItemSourceImportBinding,\n            item: BookSource,\n            payloads: MutableList<Any>\n        ) {\n            binding.apply {\n                cbSourceName.isChecked = viewModel.selectStatus[holder.layoutPosition]\n                cbSourceName.text = item.bookSourceName\n                val localSource = viewModel.checkSources[holder.layoutPosition]\n                tvSourceState.text = when {\n                    localSource == null -> \"新增\"\n                    item.lastUpdateTime > localSource.lastUpdateTime -> \"更新\"\n                    else -> \"已有\"\n                }\n            }\n        }\n\n        override fun registerListener(holder: ItemViewHolder, binding: ItemSourceImportBinding) {\n            binding.apply {\n                cbSourceName.setOnUserCheckedChangeListener { isChecked ->\n                    viewModel.selectStatus[holder.layoutPosition] = isChecked\n                    upSelectText()\n                }\n                root.onClick {\n                    cbSourceName.isChecked = !cbSourceName.isChecked\n                    viewModel.selectStatus[holder.layoutPosition] = cbSourceName.isChecked\n                    upSelectText()\n                }\n                tvOpen.setOnClickListener {\n                    val source = viewModel.allSources[holder.layoutPosition]\n                    showDialogFragment(\n                        CodeDialog(\n                            GSON.toJson(source),\n                            disableEdit = false,\n                            requestId = holder.layoutPosition.toString()\n                        )\n                    )\n                }\n            }\n        }\n\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/association/ImportBookSourceViewModel.kt",
    "content": "package io.legado.app.ui.association\n\nimport android.app.Application\nimport android.net.Uri\nimport androidx.lifecycle.MutableLiveData\nimport com.jayway.jsonpath.JsonPath\nimport io.legado.app.R\nimport io.legado.app.base.BaseViewModel\nimport io.legado.app.constant.AppConst\nimport io.legado.app.constant.AppLog\nimport io.legado.app.constant.AppPattern\nimport io.legado.app.data.appDb\nimport io.legado.app.data.entities.BookSource\nimport io.legado.app.data.entities.BookSourcePart\nimport io.legado.app.exception.NoStackTraceException\nimport io.legado.app.help.book.ContentProcessor\nimport io.legado.app.help.config.AppConfig\nimport io.legado.app.help.http.decompressed\nimport io.legado.app.help.http.newCallResponseBody\nimport io.legado.app.help.http.okHttpClient\nimport io.legado.app.help.source.SourceHelp\nimport io.legado.app.utils.GSON\nimport io.legado.app.utils.fromJsonArray\nimport io.legado.app.utils.fromJsonObject\nimport io.legado.app.utils.inputStream\nimport io.legado.app.utils.isAbsUrl\nimport io.legado.app.utils.isJsonArray\nimport io.legado.app.utils.isJsonObject\nimport io.legado.app.utils.isUri\nimport io.legado.app.utils.splitNotBlank\n\n\nclass ImportBookSourceViewModel(app: Application) : BaseViewModel(app) {\n    var isAddGroup = false\n    var groupName: String? = null\n    val errorLiveData = MutableLiveData<String>()\n    val successLiveData = MutableLiveData<Int>()\n\n    val allSources = arrayListOf<BookSource>()\n    val checkSources = arrayListOf<BookSourcePart?>()\n    val selectStatus = arrayListOf<Boolean>()\n    val newSourceStatus = arrayListOf<Boolean>()\n    val updateSourceStatus = arrayListOf<Boolean>()\n\n    val isSelectAll: Boolean\n        get() {\n            selectStatus.forEach {\n                if (!it) {\n                    return false\n                }\n            }\n            return true\n        }\n\n    val isSelectAllNew: Boolean\n        get() {\n            newSourceStatus.forEachIndexed { index, b ->\n                if (b && !selectStatus[index]) {\n                    return false\n                }\n            }\n            return true\n        }\n\n    val isSelectAllUpdate: Boolean\n        get() {\n            updateSourceStatus.forEachIndexed { index, b ->\n                if (b && !selectStatus[index]) {\n                    return false\n                }\n            }\n            return true\n        }\n\n    val selectCount: Int\n        get() {\n            var count = 0\n            selectStatus.forEach {\n                if (it) {\n                    count++\n                }\n            }\n            return count\n        }\n\n    fun importSelect(finally: () -> Unit) {\n        execute {\n            val group = groupName?.trim()\n            val keepName = AppConfig.importKeepName\n            val keepGroup = AppConfig.importKeepGroup\n            val keepEnable = AppConfig.importKeepEnable\n            val selectSource = arrayListOf<BookSource>()\n            selectStatus.forEachIndexed { index, b ->\n                if (b) {\n                    val source = allSources[index]\n                    checkSources[index]?.let {\n                        if (keepName) {\n                            source.bookSourceName = it.bookSourceName\n                        }\n                        if (keepGroup) {\n                            source.bookSourceGroup = it.bookSourceGroup\n                        }\n                        if (keepEnable) {\n                            source.enabled = it.enabled\n                            source.enabledExplore = it.enabledExplore\n                        }\n                        source.customOrder = it.customOrder\n                    }\n                    if (!group.isNullOrEmpty()) {\n                        if (isAddGroup) {\n                            val groups = linkedSetOf<String>()\n                            source.bookSourceGroup?.splitNotBlank(AppPattern.splitGroupRegex)?.let {\n                                groups.addAll(it)\n                            }\n                            groups.add(group)\n                            source.bookSourceGroup = groups.joinToString(\",\")\n                        } else {\n                            source.bookSourceGroup = group\n                        }\n                    }\n                    selectSource.add(source)\n                }\n            }\n            SourceHelp.insertBookSource(*selectSource.toTypedArray())\n            ContentProcessor.upReplaceRules()\n        }.onFinally {\n            finally.invoke()\n        }\n    }\n\n    fun importSource(text: String) {\n        execute {\n            val mText = text.trim()\n            when {\n                mText.isJsonObject() -> {\n                    kotlin.runCatching {\n                        val json = JsonPath.parse(mText)\n                        json.read<List<String>>(\"$.sourceUrls\")\n                    }.onSuccess { listUrl ->\n                        listUrl.forEach {\n                            importSourceUrl(it)\n                        }\n                    }.onFailure {\n                        GSON.fromJsonObject<BookSource>(mText).getOrThrow().let {\n                            if (it.bookSourceUrl.isEmpty()) {\n                                throw NoStackTraceException(\"不是书源\")\n                            }\n                            allSources.add(it)\n                        }\n                    }\n                }\n\n                mText.isJsonArray() -> GSON.fromJsonArray<BookSource>(mText).getOrThrow()\n                    .let { items ->\n                        val source = items.firstOrNull() ?: return@let\n                        if (source.bookSourceUrl.isEmpty()) {\n                            throw NoStackTraceException(\"不是书源\")\n                        }\n                        allSources.addAll(items)\n                    }\n\n                mText.isAbsUrl() -> {\n                    importSourceUrl(mText)\n                }\n\n                mText.isUri() -> {\n                    val uri = Uri.parse(mText)\n                    uri.inputStream(context).getOrThrow().use { inputS ->\n                        GSON.fromJsonArray<BookSource>(inputS).getOrThrow().let {\n                            val source = it.firstOrNull() ?: return@let\n                            if (source.bookSourceUrl.isEmpty()) {\n                                throw NoStackTraceException(\"不是书源\")\n                            }\n                            allSources.addAll(it)\n                        }\n                    }\n                }\n\n                else -> throw NoStackTraceException(context.getString(R.string.wrong_format))\n            }\n        }.onError {\n            errorLiveData.postValue(\"ImportError:${it.localizedMessage}\")\n            AppLog.put(\"ImportError:${it.localizedMessage}\", it)\n        }.onSuccess {\n            comparisonSource()\n        }\n    }\n\n    private suspend fun importSourceUrl(url: String) {\n        okHttpClient.newCallResponseBody {\n            if (url.endsWith(\"#requestWithoutUA\")) {\n                url(url.substringBeforeLast(\"#requestWithoutUA\"))\n                header(AppConst.UA_NAME, \"null\")\n            } else {\n                url(url)\n            }\n        }.decompressed().byteStream().use {\n            GSON.fromJsonArray<BookSource>(it).getOrThrow().let { list ->\n                val source = list.firstOrNull() ?: return@let\n                if (source.bookSourceUrl.isEmpty()) {\n                    throw NoStackTraceException(\"不是书源\")\n                }\n                allSources.addAll(list)\n            }\n        }\n    }\n\n    private fun comparisonSource() {\n        execute {\n            allSources.forEach {\n                val source = appDb.bookSourceDao.getBookSourcePart(it.bookSourceUrl)\n                checkSources.add(source)\n                selectStatus.add(source == null || source.lastUpdateTime < it.lastUpdateTime)\n                newSourceStatus.add(source == null)\n                updateSourceStatus.add(source != null && source.lastUpdateTime < it.lastUpdateTime)\n            }\n            successLiveData.postValue(allSources.size)\n        }\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/association/ImportDictRuleDialog.kt",
    "content": "package io.legado.app.ui.association\n\nimport android.annotation.SuppressLint\nimport android.content.Context\nimport android.content.DialogInterface\nimport android.os.Bundle\nimport android.view.View\nimport android.view.ViewGroup\nimport androidx.fragment.app.viewModels\nimport androidx.recyclerview.widget.LinearLayoutManager\nimport io.legado.app.R\nimport io.legado.app.base.BaseDialogFragment\nimport io.legado.app.base.adapter.ItemViewHolder\nimport io.legado.app.base.adapter.RecyclerAdapter\nimport io.legado.app.data.entities.DictRule\nimport io.legado.app.databinding.DialogRecyclerViewBinding\nimport io.legado.app.databinding.ItemSourceImportBinding\nimport io.legado.app.lib.theme.primaryColor\nimport io.legado.app.ui.widget.dialog.CodeDialog\nimport io.legado.app.ui.widget.dialog.WaitDialog\nimport io.legado.app.utils.GSON\nimport io.legado.app.utils.fromJsonObject\nimport io.legado.app.utils.setLayout\nimport io.legado.app.utils.showDialogFragment\nimport io.legado.app.utils.viewbindingdelegate.viewBinding\nimport io.legado.app.utils.visible\nimport splitties.views.onClick\n\nclass ImportDictRuleDialog() : BaseDialogFragment(R.layout.dialog_recycler_view),\n    CodeDialog.Callback {\n\n    constructor(source: String, finishOnDismiss: Boolean = false) : this() {\n        arguments = Bundle().apply {\n            putString(\"source\", source)\n            putBoolean(\"finishOnDismiss\", finishOnDismiss)\n        }\n    }\n\n    private val binding by viewBinding(DialogRecyclerViewBinding::bind)\n    private val viewModel by viewModels<ImportDictRuleViewModel>()\n    private val adapter by lazy { SourcesAdapter(requireContext()) }\n\n    override fun onStart() {\n        super.onStart()\n        setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)\n    }\n\n    override fun onDismiss(dialog: DialogInterface) {\n        super.onDismiss(dialog)\n        if (arguments?.getBoolean(\"finishOnDismiss\") == true) {\n            activity?.finish()\n        }\n    }\n\n    @SuppressLint(\"NotifyDataSetChanged\")\n    override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) {\n        binding.toolBar.setBackgroundColor(primaryColor)\n        binding.toolBar.setTitle(R.string.import_dict_rule)\n        binding.rotateLoading.visible()\n        binding.recyclerView.layoutManager = LinearLayoutManager(requireContext())\n        binding.recyclerView.adapter = adapter\n        binding.tvCancel.visible()\n        binding.tvCancel.setOnClickListener {\n            dismissAllowingStateLoss()\n        }\n        binding.tvOk.visible()\n        binding.tvOk.setOnClickListener {\n            val waitDialog = WaitDialog(requireContext())\n            waitDialog.show()\n            viewModel.importSelect {\n                waitDialog.dismiss()\n                dismissAllowingStateLoss()\n            }\n        }\n        binding.tvFooterLeft.visible()\n        binding.tvFooterLeft.setOnClickListener {\n            val selectAll = viewModel.isSelectAll\n            viewModel.selectStatus.forEachIndexed { index, b ->\n                if (b != !selectAll) {\n                    viewModel.selectStatus[index] = !selectAll\n                }\n            }\n            adapter.notifyDataSetChanged()\n            upSelectText()\n        }\n        viewModel.errorLiveData.observe(this) {\n            binding.rotateLoading.gone()\n            binding.tvMsg.apply {\n                text = it\n                visible()\n            }\n        }\n        viewModel.successLiveData.observe(this) {\n            binding.rotateLoading.gone()\n            if (it > 0) {\n                adapter.setItems(viewModel.allSources)\n                upSelectText()\n            } else {\n                binding.tvMsg.apply {\n                    setText(R.string.wrong_format)\n                    visible()\n                }\n            }\n        }\n        val source = arguments?.getString(\"source\")\n        if (source.isNullOrEmpty()) {\n            dismiss()\n            return\n        }\n        viewModel.importSource(source)\n    }\n\n    private fun upSelectText() {\n        if (viewModel.isSelectAll) {\n            binding.tvFooterLeft.text = getString(\n                R.string.select_cancel_count,\n                viewModel.selectCount,\n                viewModel.allSources.size\n            )\n        } else {\n            binding.tvFooterLeft.text = getString(\n                R.string.select_all_count,\n                viewModel.selectCount,\n                viewModel.allSources.size\n            )\n        }\n    }\n\n    inner class SourcesAdapter(context: Context) :\n        RecyclerAdapter<DictRule, ItemSourceImportBinding>(context) {\n\n        override fun getViewBinding(parent: ViewGroup): ItemSourceImportBinding {\n            return ItemSourceImportBinding.inflate(inflater, parent, false)\n        }\n\n        override fun convert(\n            holder: ItemViewHolder,\n            binding: ItemSourceImportBinding,\n            item: DictRule,\n            payloads: MutableList<Any>\n        ) {\n            binding.apply {\n                cbSourceName.isChecked = viewModel.selectStatus[holder.layoutPosition]\n                cbSourceName.text = item.name\n                val localSource = viewModel.checkSources[holder.layoutPosition]\n                tvSourceState.text = when (localSource) {\n                    null -> \"新增\"\n                    else -> \"已有\"\n                }\n            }\n        }\n\n        override fun registerListener(holder: ItemViewHolder, binding: ItemSourceImportBinding) {\n            binding.apply {\n                cbSourceName.setOnUserCheckedChangeListener { isChecked ->\n                    viewModel.selectStatus[holder.layoutPosition] = isChecked\n                    upSelectText()\n                }\n                root.onClick {\n                    cbSourceName.isChecked = !cbSourceName.isChecked\n                    viewModel.selectStatus[holder.layoutPosition] = cbSourceName.isChecked\n                    upSelectText()\n                }\n                tvOpen.setOnClickListener {\n                    val source = viewModel.allSources[holder.layoutPosition]\n                    showDialogFragment(\n                        CodeDialog(\n                            GSON.toJson(source),\n                            disableEdit = false,\n                            requestId = holder.layoutPosition.toString()\n                        )\n                    )\n                }\n            }\n        }\n\n    }\n\n    override fun onCodeSave(code: String, requestId: String?) {\n        requestId?.toInt()?.let {\n            GSON.fromJsonObject<DictRule>(code).getOrNull()?.let { source ->\n                viewModel.allSources[it] = source\n                adapter.setItem(it, source)\n            }\n        }\n    }\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/association/ImportDictRuleViewModel.kt",
    "content": "package io.legado.app.ui.association\n\nimport android.app.Application\nimport androidx.core.net.toUri\nimport androidx.lifecycle.MutableLiveData\nimport io.legado.app.R\nimport io.legado.app.base.BaseViewModel\nimport io.legado.app.constant.AppConst\nimport io.legado.app.constant.AppLog\nimport io.legado.app.data.appDb\nimport io.legado.app.data.entities.DictRule\nimport io.legado.app.exception.NoStackTraceException\nimport io.legado.app.help.http.decompressed\nimport io.legado.app.help.http.newCallResponseBody\nimport io.legado.app.help.http.okHttpClient\nimport io.legado.app.help.http.text\nimport io.legado.app.utils.GSON\nimport io.legado.app.utils.fromJsonArray\nimport io.legado.app.utils.fromJsonObject\nimport io.legado.app.utils.isAbsUrl\nimport io.legado.app.utils.isJsonArray\nimport io.legado.app.utils.isJsonObject\nimport io.legado.app.utils.isUri\nimport io.legado.app.utils.readText\nimport splitties.init.appCtx\n\nclass ImportDictRuleViewModel(app: Application) : BaseViewModel(app) {\n\n    val errorLiveData = MutableLiveData<String>()\n    val successLiveData = MutableLiveData<Int>()\n\n    val allSources = arrayListOf<DictRule>()\n    val checkSources = arrayListOf<DictRule?>()\n    val selectStatus = arrayListOf<Boolean>()\n\n    val isSelectAll: Boolean\n        get() {\n            selectStatus.forEach {\n                if (!it) {\n                    return false\n                }\n            }\n            return true\n        }\n\n    val selectCount: Int\n        get() {\n            var count = 0\n            selectStatus.forEach {\n                if (it) {\n                    count++\n                }\n            }\n            return count\n        }\n\n    fun importSelect(finally: () -> Unit) {\n        execute {\n            val selectSource = arrayListOf<DictRule>()\n            selectStatus.forEachIndexed { index, b ->\n                if (b) {\n                    selectSource.add(allSources[index])\n                }\n            }\n            appDb.dictRuleDao.insert(*selectSource.toTypedArray())\n        }.onFinally {\n            finally.invoke()\n        }\n    }\n\n    fun importSource(text: String) {\n        execute {\n            importSourceAwait(text.trim())\n        }.onError {\n            errorLiveData.postValue(\"ImportError:${it.localizedMessage}\")\n            AppLog.put(\"ImportError:${it.localizedMessage}\", it)\n        }.onSuccess {\n            comparisonSource()\n        }\n    }\n\n    private suspend fun importSourceAwait(text: String) {\n        when {\n            text.isJsonObject() -> {\n                GSON.fromJsonObject<DictRule>(text).getOrThrow().let {\n                    allSources.add(it)\n                }\n            }\n\n            text.isJsonArray() -> GSON.fromJsonArray<DictRule>(text).getOrThrow().let { items ->\n                allSources.addAll(items)\n            }\n\n            text.isAbsUrl() -> {\n                importSourceUrl(text)\n            }\n\n            text.isUri() -> {\n                importSourceAwait(text.toUri().readText(appCtx))\n            }\n\n            else -> throw NoStackTraceException(context.getString(R.string.wrong_format))\n        }\n    }\n\n    private suspend fun importSourceUrl(url: String) {\n        okHttpClient.newCallResponseBody {\n            if (url.endsWith(\"#requestWithoutUA\")) {\n                url(url.substringBeforeLast(\"#requestWithoutUA\"))\n                header(AppConst.UA_NAME, \"null\")\n            } else {\n                url(url)\n            }\n        }.decompressed().text().let {\n            importSourceAwait(it)\n        }\n    }\n\n    private fun comparisonSource() {\n        execute {\n            allSources.forEach {\n                val source = appDb.dictRuleDao.getByName(it.name)\n                checkSources.add(source)\n                selectStatus.add(source == null)\n            }\n            successLiveData.postValue(allSources.size)\n        }\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/association/ImportHttpTtsDialog.kt",
    "content": "package io.legado.app.ui.association\n\nimport android.annotation.SuppressLint\nimport android.content.Context\nimport android.content.DialogInterface\nimport android.os.Bundle\nimport android.view.View\nimport android.view.ViewGroup\nimport androidx.fragment.app.viewModels\nimport androidx.recyclerview.widget.LinearLayoutManager\nimport io.legado.app.R\nimport io.legado.app.base.BaseDialogFragment\nimport io.legado.app.base.adapter.ItemViewHolder\nimport io.legado.app.base.adapter.RecyclerAdapter\nimport io.legado.app.data.entities.HttpTTS\nimport io.legado.app.databinding.DialogRecyclerViewBinding\nimport io.legado.app.databinding.ItemSourceImportBinding\nimport io.legado.app.lib.theme.primaryColor\nimport io.legado.app.ui.widget.dialog.CodeDialog\nimport io.legado.app.ui.widget.dialog.WaitDialog\nimport io.legado.app.utils.GSON\nimport io.legado.app.utils.setLayout\nimport io.legado.app.utils.showDialogFragment\nimport io.legado.app.utils.viewbindingdelegate.viewBinding\nimport io.legado.app.utils.visible\nimport splitties.views.onClick\n\nclass ImportHttpTtsDialog() : BaseDialogFragment(R.layout.dialog_recycler_view),\n    CodeDialog.Callback {\n\n    constructor(source: String, finishOnDismiss: Boolean = false) : this() {\n        arguments = Bundle().apply {\n            putString(\"source\", source)\n            putBoolean(\"finishOnDismiss\", finishOnDismiss)\n        }\n    }\n\n    private val binding by viewBinding(DialogRecyclerViewBinding::bind)\n    private val viewModel by viewModels<ImportHttpTtsViewModel>()\n    private val adapter by lazy { SourcesAdapter(requireContext()) }\n\n    override fun onStart() {\n        super.onStart()\n        setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)\n    }\n\n    override fun onDismiss(dialog: DialogInterface) {\n        super.onDismiss(dialog)\n        if (arguments?.getBoolean(\"finishOnDismiss\") == true) {\n            activity?.finish()\n        }\n    }\n\n    @SuppressLint(\"NotifyDataSetChanged\")\n    override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) {\n        binding.toolBar.setBackgroundColor(primaryColor)\n        binding.toolBar.setTitle(R.string.import_tts)\n        binding.rotateLoading.visible()\n        binding.recyclerView.layoutManager = LinearLayoutManager(requireContext())\n        binding.recyclerView.adapter = adapter\n        binding.tvCancel.visible()\n        binding.tvCancel.setOnClickListener {\n            dismissAllowingStateLoss()\n        }\n        binding.tvOk.visible()\n        binding.tvOk.setOnClickListener {\n            val waitDialog = WaitDialog(requireContext())\n            waitDialog.show()\n            viewModel.importSelect {\n                waitDialog.dismiss()\n                dismissAllowingStateLoss()\n            }\n        }\n        binding.tvFooterLeft.visible()\n        binding.tvFooterLeft.setOnClickListener {\n            val selectAll = viewModel.isSelectAll\n            viewModel.selectStatus.forEachIndexed { index, b ->\n                if (b != !selectAll) {\n                    viewModel.selectStatus[index] = !selectAll\n                }\n            }\n            adapter.notifyDataSetChanged()\n            upSelectText()\n        }\n        viewModel.errorLiveData.observe(this) {\n            binding.rotateLoading.gone()\n            binding.tvMsg.apply {\n                text = it\n                visible()\n            }\n        }\n        viewModel.successLiveData.observe(this) {\n            binding.rotateLoading.gone()\n            if (it > 0) {\n                adapter.setItems(viewModel.allSources)\n                upSelectText()\n            } else {\n                binding.tvMsg.apply {\n                    setText(R.string.wrong_format)\n                    visible()\n                }\n            }\n        }\n        val source = arguments?.getString(\"source\")\n        if (source.isNullOrEmpty()) {\n            dismiss()\n            return\n        }\n        viewModel.importSource(source)\n    }\n\n    private fun upSelectText() {\n        if (viewModel.isSelectAll) {\n            binding.tvFooterLeft.text = getString(\n                R.string.select_cancel_count,\n                viewModel.selectCount,\n                viewModel.allSources.size\n            )\n        } else {\n            binding.tvFooterLeft.text = getString(\n                R.string.select_all_count,\n                viewModel.selectCount,\n                viewModel.allSources.size\n            )\n        }\n    }\n\n    inner class SourcesAdapter(context: Context) :\n        RecyclerAdapter<HttpTTS, ItemSourceImportBinding>(context) {\n\n        override fun getViewBinding(parent: ViewGroup): ItemSourceImportBinding {\n            return ItemSourceImportBinding.inflate(inflater, parent, false)\n        }\n\n        override fun convert(\n            holder: ItemViewHolder,\n            binding: ItemSourceImportBinding,\n            item: HttpTTS,\n            payloads: MutableList<Any>\n        ) {\n            binding.apply {\n                cbSourceName.isChecked = viewModel.selectStatus[holder.layoutPosition]\n                cbSourceName.text = item.name\n                val localSource = viewModel.checkSources[holder.layoutPosition]\n                tvSourceState.text = when {\n                    localSource == null -> \"新增\"\n                    item.lastUpdateTime > localSource.lastUpdateTime -> \"更新\"\n                    else -> \"已有\"\n                }\n            }\n        }\n\n        override fun registerListener(holder: ItemViewHolder, binding: ItemSourceImportBinding) {\n            binding.apply {\n                cbSourceName.setOnUserCheckedChangeListener { isChecked ->\n                    viewModel.selectStatus[holder.layoutPosition] = isChecked\n                    upSelectText()\n                }\n                root.onClick {\n                    cbSourceName.isChecked = !cbSourceName.isChecked\n                    viewModel.selectStatus[holder.layoutPosition] = cbSourceName.isChecked\n                    upSelectText()\n                }\n                tvOpen.setOnClickListener {\n                    val source = viewModel.allSources[holder.layoutPosition]\n                    showDialogFragment(\n                        CodeDialog(\n                            GSON.toJson(source),\n                            disableEdit = false,\n                            requestId = holder.layoutPosition.toString()\n                        )\n                    )\n                }\n            }\n        }\n\n    }\n\n    override fun onCodeSave(code: String, requestId: String?) {\n        requestId?.toInt()?.let {\n            HttpTTS.fromJson(code).getOrNull()?.let { source ->\n                viewModel.allSources[it] = source\n                adapter.setItem(it, source)\n            }\n        }\n    }\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/association/ImportHttpTtsViewModel.kt",
    "content": "package io.legado.app.ui.association\n\nimport android.app.Application\nimport androidx.core.net.toUri\nimport androidx.lifecycle.MutableLiveData\nimport io.legado.app.R\nimport io.legado.app.base.BaseViewModel\nimport io.legado.app.constant.AppConst\nimport io.legado.app.constant.AppLog\nimport io.legado.app.data.appDb\nimport io.legado.app.data.entities.HttpTTS\nimport io.legado.app.exception.NoStackTraceException\nimport io.legado.app.help.http.decompressed\nimport io.legado.app.help.http.newCallResponseBody\nimport io.legado.app.help.http.okHttpClient\nimport io.legado.app.help.http.text\nimport io.legado.app.utils.isAbsUrl\nimport io.legado.app.utils.isJsonArray\nimport io.legado.app.utils.isJsonObject\nimport io.legado.app.utils.isUri\nimport io.legado.app.utils.readText\nimport splitties.init.appCtx\n\nclass ImportHttpTtsViewModel(app: Application) : BaseViewModel(app) {\n\n    val errorLiveData = MutableLiveData<String>()\n    val successLiveData = MutableLiveData<Int>()\n\n    val allSources = arrayListOf<HttpTTS>()\n    val checkSources = arrayListOf<HttpTTS?>()\n    val selectStatus = arrayListOf<Boolean>()\n\n    val isSelectAll: Boolean\n        get() {\n            selectStatus.forEach {\n                if (!it) {\n                    return false\n                }\n            }\n            return true\n        }\n\n    val selectCount: Int\n        get() {\n            var count = 0\n            selectStatus.forEach {\n                if (it) {\n                    count++\n                }\n            }\n            return count\n        }\n\n    fun importSelect(finally: () -> Unit) {\n        execute {\n            val selectSource = arrayListOf<HttpTTS>()\n            selectStatus.forEachIndexed { index, b ->\n                if (b) {\n                    selectSource.add(allSources[index])\n                }\n            }\n            appDb.httpTTSDao.insert(*selectSource.toTypedArray())\n        }.onFinally {\n            finally.invoke()\n        }\n    }\n\n    fun importSource(text: String) {\n        execute {\n            importSourceAwait(text.trim())\n        }.onError {\n            errorLiveData.postValue(\"ImportError:${it.localizedMessage}\")\n            AppLog.put(\"ImportError:${it.localizedMessage}\", it)\n        }.onSuccess {\n            comparisonSource()\n        }\n    }\n\n    private suspend fun importSourceAwait(text: String) {\n        when {\n            text.isJsonObject() -> {\n                HttpTTS.fromJson(text).getOrThrow().let {\n                    allSources.add(it)\n                }\n            }\n\n            text.isJsonArray() -> HttpTTS.fromJsonArray(text).getOrThrow().let { items ->\n                allSources.addAll(items)\n            }\n\n            text.isAbsUrl() -> {\n                importSourceUrl(text)\n            }\n\n            text.isUri() -> {\n                importSourceAwait(text.toUri().readText(appCtx))\n            }\n\n            else -> throw NoStackTraceException(context.getString(R.string.wrong_format))\n        }\n    }\n\n    private suspend fun importSourceUrl(url: String) {\n        okHttpClient.newCallResponseBody {\n            if (url.endsWith(\"#requestWithoutUA\")) {\n                url(url.substringBeforeLast(\"#requestWithoutUA\"))\n                header(AppConst.UA_NAME, \"null\")\n            } else {\n                url(url)\n            }\n        }.decompressed().text().let {\n            importSourceAwait(it)\n        }\n    }\n\n    private fun comparisonSource() {\n        execute {\n            allSources.forEach {\n                val source = appDb.httpTTSDao.get(it.id)\n                checkSources.add(source)\n                selectStatus.add(source == null || source.lastUpdateTime < it.lastUpdateTime)\n            }\n            successLiveData.postValue(allSources.size)\n        }\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/association/ImportReplaceRuleDialog.kt",
    "content": "package io.legado.app.ui.association\n\nimport android.annotation.SuppressLint\nimport android.content.Context\nimport android.content.DialogInterface\nimport android.os.Bundle\nimport android.view.MenuItem\nimport android.view.View\nimport android.view.ViewGroup\nimport androidx.appcompat.widget.Toolbar\nimport androidx.fragment.app.viewModels\nimport androidx.recyclerview.widget.LinearLayoutManager\nimport io.legado.app.R\nimport io.legado.app.base.BaseDialogFragment\nimport io.legado.app.base.adapter.ItemViewHolder\nimport io.legado.app.base.adapter.RecyclerAdapter\nimport io.legado.app.constant.PreferKey\nimport io.legado.app.data.appDb\nimport io.legado.app.data.entities.ReplaceRule\nimport io.legado.app.databinding.DialogCustomGroupBinding\nimport io.legado.app.databinding.DialogRecyclerViewBinding\nimport io.legado.app.databinding.ItemSourceImportBinding\nimport io.legado.app.lib.dialogs.alert\nimport io.legado.app.lib.theme.primaryColor\nimport io.legado.app.ui.widget.dialog.CodeDialog\nimport io.legado.app.ui.widget.dialog.WaitDialog\nimport io.legado.app.utils.GSON\nimport io.legado.app.utils.dpToPx\nimport io.legado.app.utils.fromJsonObject\nimport io.legado.app.utils.putPrefBoolean\nimport io.legado.app.utils.setLayout\nimport io.legado.app.utils.showDialogFragment\nimport io.legado.app.utils.viewbindingdelegate.viewBinding\nimport io.legado.app.utils.visible\nimport splitties.views.onClick\n\nclass ImportReplaceRuleDialog() : BaseDialogFragment(R.layout.dialog_recycler_view),\n    Toolbar.OnMenuItemClickListener,\n    CodeDialog.Callback {\n\n    constructor(source: String, finishOnDismiss: Boolean = false) : this() {\n        arguments = Bundle().apply {\n            putString(\"source\", source)\n            putBoolean(\"finishOnDismiss\", finishOnDismiss)\n        }\n    }\n\n    private val binding by viewBinding(DialogRecyclerViewBinding::bind)\n    private val viewModel by viewModels<ImportReplaceRuleViewModel>()\n    private val adapter by lazy { SourcesAdapter(requireContext()) }\n\n    override fun onStart() {\n        super.onStart()\n        setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)\n    }\n\n    override fun onDismiss(dialog: DialogInterface) {\n        super.onDismiss(dialog)\n        if (arguments?.getBoolean(\"finishOnDismiss\") == true) {\n            activity?.finish()\n        }\n    }\n\n    @SuppressLint(\"NotifyDataSetChanged\")\n    override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) {\n        binding.toolBar.setBackgroundColor(primaryColor)\n        binding.toolBar.setTitle(R.string.import_replace_rule)\n        binding.rotateLoading.visible()\n        initMenu()\n        binding.recyclerView.layoutManager = LinearLayoutManager(requireContext())\n        binding.recyclerView.adapter = adapter\n        binding.tvCancel.visible()\n        binding.tvCancel.setOnClickListener {\n            dismissAllowingStateLoss()\n        }\n        binding.tvOk.visible()\n        binding.tvOk.setOnClickListener {\n            val waitDialog = WaitDialog(requireContext())\n            waitDialog.show()\n            viewModel.importSelect {\n                waitDialog.dismiss()\n                dismissAllowingStateLoss()\n            }\n        }\n        binding.tvFooterLeft.visible()\n        binding.tvFooterLeft.setOnClickListener {\n            val selectAll = viewModel.isSelectAll\n            viewModel.selectStatus.forEachIndexed { index, b ->\n                if (b != !selectAll) {\n                    viewModel.selectStatus[index] = !selectAll\n                }\n            }\n            adapter.notifyDataSetChanged()\n            upSelectText()\n        }\n        viewModel.errorLiveData.observe(this) {\n            binding.rotateLoading.gone()\n            binding.tvMsg.apply {\n                text = it\n                visible()\n            }\n        }\n        viewModel.successLiveData.observe(this) {\n            binding.rotateLoading.gone()\n            if (it > 0) {\n                adapter.setItems(viewModel.allRules)\n                upSelectText()\n            } else {\n                binding.tvMsg.apply {\n                    setText(R.string.wrong_format)\n                    visible()\n                }\n            }\n        }\n        val source = arguments?.getString(\"source\")\n        if (source.isNullOrEmpty()) {\n            dismiss()\n            return\n        }\n        viewModel.import(source)\n    }\n\n    private fun initMenu() {\n        binding.toolBar.setOnMenuItemClickListener(this)\n        binding.toolBar.inflateMenu(R.menu.import_replace)\n    }\n\n    override fun onMenuItemClick(item: MenuItem?): Boolean {\n        when (item?.itemId) {\n            R.id.menu_new_group -> alertCustomGroup(item)\n            R.id.menu_keep_original_name -> {\n                item.isChecked = !item.isChecked\n                putPrefBoolean(PreferKey.importKeepName, item.isChecked)\n            }\n        }\n        return true\n    }\n\n    private fun alertCustomGroup(item: MenuItem) {\n        alert(R.string.diy_edit_source_group) {\n            val alertBinding = DialogCustomGroupBinding.inflate(layoutInflater).apply {\n                val groups = appDb.replaceRuleDao.allGroups()\n                textInputLayout.setHint(R.string.group_name)\n                editView.setFilterValues(groups.toList())\n                editView.dropDownHeight = 180.dpToPx()\n            }\n            customView {\n                alertBinding.root\n            }\n            okButton {\n                viewModel.isAddGroup = alertBinding.swAddGroup.isChecked\n                viewModel.groupName = alertBinding.editView.text?.toString()\n                if (viewModel.groupName.isNullOrBlank()) {\n                    item.title = getString(R.string.diy_source_group)\n                } else {\n                    val group = getString(R.string.diy_edit_source_group_title, viewModel.groupName)\n                    if (viewModel.isAddGroup) {\n                        item.title = \"+$group\"\n                    } else {\n                        item.title = group\n                    }\n                }\n            }\n            noButton()\n        }\n    }\n\n    private fun upSelectText() {\n        if (viewModel.isSelectAll) {\n            binding.tvFooterLeft.text = getString(\n                R.string.select_cancel_count,\n                viewModel.selectCount,\n                viewModel.allRules.size\n            )\n        } else {\n            binding.tvFooterLeft.text = getString(\n                R.string.select_all_count,\n                viewModel.selectCount,\n                viewModel.allRules.size\n            )\n        }\n    }\n\n    override fun onCodeSave(code: String, requestId: String?) {\n        requestId?.toInt()?.let {\n            GSON.fromJsonObject<ReplaceRule>(code).getOrNull()?.let { rule ->\n                viewModel.allRules[it] = rule\n                adapter.setItem(it, rule)\n            }\n        }\n    }\n\n    inner class SourcesAdapter(context: Context) :\n        RecyclerAdapter<ReplaceRule, ItemSourceImportBinding>(context) {\n\n        override fun getViewBinding(parent: ViewGroup): ItemSourceImportBinding {\n            return ItemSourceImportBinding.inflate(inflater, parent, false)\n        }\n\n        @SuppressLint(\"SetTextI18n\")\n        override fun convert(\n            holder: ItemViewHolder,\n            binding: ItemSourceImportBinding,\n            item: ReplaceRule,\n            payloads: MutableList<Any>\n        ) {\n            binding.run {\n                cbSourceName.isChecked = viewModel.selectStatus[holder.layoutPosition]\n                cbSourceName.text = if (item.group.isNullOrBlank()) {\n                    item.name\n                } else {\n                    \"${item.name}(${item.group})\"\n                }\n                val localRule = viewModel.checkRules[holder.layoutPosition]\n                tvSourceState.text = when {\n                    localRule == null -> \"新增\"\n                    item.pattern != localRule.pattern\n                            || item.replacement != localRule.replacement\n                            || item.isRegex != localRule.isRegex\n                            || item.scope != localRule.scope -> \"更新\"\n\n                    else -> \"已有\"\n                }\n            }\n        }\n\n        override fun registerListener(holder: ItemViewHolder, binding: ItemSourceImportBinding) {\n            binding.run {\n                cbSourceName.setOnUserCheckedChangeListener { isChecked ->\n                    viewModel.selectStatus[holder.layoutPosition] = isChecked\n                    upSelectText()\n                }\n                root.onClick {\n                    cbSourceName.isChecked = !cbSourceName.isChecked\n                    viewModel.selectStatus[holder.layoutPosition] = cbSourceName.isChecked\n                    upSelectText()\n                }\n                tvOpen.setOnClickListener {\n                    val source = viewModel.allRules[holder.layoutPosition]\n                    showDialogFragment(\n                        CodeDialog(\n                            GSON.toJson(source),\n                            disableEdit = false,\n                            requestId = holder.layoutPosition.toString()\n                        )\n                    )\n                }\n            }\n        }\n\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/association/ImportReplaceRuleViewModel.kt",
    "content": "package io.legado.app.ui.association\n\nimport android.app.Application\nimport androidx.core.net.toUri\nimport androidx.lifecycle.MutableLiveData\nimport io.legado.app.base.BaseViewModel\nimport io.legado.app.constant.AppConst\nimport io.legado.app.constant.AppLog\nimport io.legado.app.constant.AppPattern\nimport io.legado.app.data.appDb\nimport io.legado.app.data.entities.ReplaceRule\nimport io.legado.app.exception.NoStackTraceException\nimport io.legado.app.help.ReplaceAnalyzer\nimport io.legado.app.help.http.decompressed\nimport io.legado.app.help.http.newCallResponseBody\nimport io.legado.app.help.http.okHttpClient\nimport io.legado.app.help.http.text\nimport io.legado.app.utils.isAbsUrl\nimport io.legado.app.utils.isJsonArray\nimport io.legado.app.utils.isJsonObject\nimport io.legado.app.utils.isUri\nimport io.legado.app.utils.readText\nimport io.legado.app.utils.splitNotBlank\nimport splitties.init.appCtx\n\nclass ImportReplaceRuleViewModel(app: Application) : BaseViewModel(app) {\n    var isAddGroup = false\n    var groupName: String? = null\n    val errorLiveData = MutableLiveData<String>()\n    val successLiveData = MutableLiveData<Int>()\n\n    val allRules = arrayListOf<ReplaceRule>()\n    val checkRules = arrayListOf<ReplaceRule?>()\n    val selectStatus = arrayListOf<Boolean>()\n\n    val isSelectAll: Boolean\n        get() {\n            selectStatus.forEach {\n                if (!it) {\n                    return false\n                }\n            }\n            return true\n        }\n\n    val selectCount: Int\n        get() {\n            var count = 0\n            selectStatus.forEach {\n                if (it) {\n                    count++\n                }\n            }\n            return count\n        }\n\n    fun importSelect(finally: () -> Unit) {\n        execute {\n            val group = groupName?.trim()\n            val selectRules = arrayListOf<ReplaceRule>()\n            selectStatus.forEachIndexed { index, b ->\n                if (b) {\n                    val rule = allRules[index]\n                    if (!group.isNullOrEmpty()) {\n                        if (isAddGroup) {\n                            val groups = linkedSetOf<String>()\n                            rule.group?.splitNotBlank(AppPattern.splitGroupRegex)?.let {\n                                groups.addAll(it)\n                            }\n                            groups.add(group)\n                            rule.group = groups.joinToString(\",\")\n                        } else {\n                            rule.group = group\n                        }\n                    }\n                    selectRules.add(rule)\n                }\n            }\n            appDb.replaceRuleDao.insert(*selectRules.toTypedArray())\n        }.onFinally {\n            finally.invoke()\n        }\n    }\n\n    fun import(text: String) {\n        execute {\n            importAwait(text.trim())\n        }.onError {\n            errorLiveData.postValue(\"ImportError:${it.localizedMessage}\")\n            AppLog.put(\"ImportError:${it.localizedMessage}\", it)\n        }.onSuccess {\n            comparisonSource()\n        }\n    }\n\n    private suspend fun importAwait(text: String) {\n        when {\n            text.isAbsUrl() -> importUrl(text)\n            text.isJsonArray() -> {\n                val rules = ReplaceAnalyzer.jsonToReplaceRules(text).getOrThrow()\n                allRules.addAll(rules)\n            }\n\n            text.isJsonObject() -> {\n                val rule = ReplaceAnalyzer.jsonToReplaceRule(text).getOrThrow()\n                allRules.add(rule)\n            }\n\n            text.isUri() -> {\n                importAwait(text.toUri().readText(appCtx))\n            }\n\n            else -> throw NoStackTraceException(\"格式不对\")\n        }\n    }\n\n    private suspend fun importUrl(url: String) {\n        okHttpClient.newCallResponseBody {\n            if (url.endsWith(\"#requestWithoutUA\")) {\n                url(url.substringBeforeLast(\"#requestWithoutUA\"))\n                header(AppConst.UA_NAME, \"null\")\n            } else {\n                url(url)\n            }\n        }.decompressed().text(\"utf-8\").let {\n            importAwait(it)\n        }\n    }\n\n    private fun comparisonSource() {\n        execute {\n            allRules.forEach {\n                val rule = appDb.replaceRuleDao.findById(it.id)\n                checkRules.add(rule)\n                selectStatus.add(rule == null)\n            }\n        }.onSuccess {\n            successLiveData.postValue(allRules.size)\n        }\n    }\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/association/ImportRssSourceDialog.kt",
    "content": "package io.legado.app.ui.association\n\nimport android.annotation.SuppressLint\nimport android.content.Context\nimport android.content.DialogInterface\nimport android.os.Bundle\nimport android.view.MenuItem\nimport android.view.View\nimport android.view.ViewGroup\nimport androidx.appcompat.widget.Toolbar\nimport androidx.fragment.app.viewModels\nimport androidx.recyclerview.widget.LinearLayoutManager\nimport io.legado.app.R\nimport io.legado.app.base.BaseDialogFragment\nimport io.legado.app.base.adapter.ItemViewHolder\nimport io.legado.app.base.adapter.RecyclerAdapter\nimport io.legado.app.constant.PreferKey\nimport io.legado.app.data.appDb\nimport io.legado.app.data.entities.RssSource\nimport io.legado.app.databinding.DialogCustomGroupBinding\nimport io.legado.app.databinding.DialogRecyclerViewBinding\nimport io.legado.app.databinding.ItemSourceImportBinding\nimport io.legado.app.help.config.AppConfig\nimport io.legado.app.lib.dialogs.alert\nimport io.legado.app.lib.theme.primaryColor\nimport io.legado.app.ui.widget.dialog.CodeDialog\nimport io.legado.app.ui.widget.dialog.WaitDialog\nimport io.legado.app.utils.GSON\nimport io.legado.app.utils.dpToPx\nimport io.legado.app.utils.fromJsonObject\nimport io.legado.app.utils.putPrefBoolean\nimport io.legado.app.utils.setLayout\nimport io.legado.app.utils.showDialogFragment\nimport io.legado.app.utils.viewbindingdelegate.viewBinding\nimport io.legado.app.utils.visible\nimport splitties.views.onClick\n\n/**\n * 导入rss源弹出窗口\n */\nclass ImportRssSourceDialog() : BaseDialogFragment(R.layout.dialog_recycler_view),\n    Toolbar.OnMenuItemClickListener,\n    CodeDialog.Callback {\n\n    constructor(source: String, finishOnDismiss: Boolean = false) : this() {\n        arguments = Bundle().apply {\n            putString(\"source\", source)\n            putBoolean(\"finishOnDismiss\", finishOnDismiss)\n        }\n    }\n\n    private val binding by viewBinding(DialogRecyclerViewBinding::bind)\n    private val viewModel by viewModels<ImportRssSourceViewModel>()\n    private val adapter by lazy { SourcesAdapter(requireContext()) }\n\n    override fun onStart() {\n        super.onStart()\n        setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)\n    }\n\n    override fun onDismiss(dialog: DialogInterface) {\n        super.onDismiss(dialog)\n        if (arguments?.getBoolean(\"finishOnDismiss\") == true) {\n            activity?.finish()\n        }\n    }\n\n    @SuppressLint(\"NotifyDataSetChanged\")\n    override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) {\n        binding.toolBar.setBackgroundColor(primaryColor)\n        binding.toolBar.setTitle(R.string.import_rss_source)\n        binding.rotateLoading.visible()\n        initMenu()\n        binding.recyclerView.layoutManager = LinearLayoutManager(requireContext())\n        binding.recyclerView.adapter = adapter\n        binding.tvCancel.visible()\n        binding.tvCancel.setOnClickListener {\n            dismissAllowingStateLoss()\n        }\n        binding.tvOk.visible()\n        binding.tvOk.setOnClickListener {\n            val waitDialog = WaitDialog(requireContext())\n            waitDialog.show()\n            viewModel.importSelect {\n                waitDialog.dismiss()\n                dismissAllowingStateLoss()\n            }\n        }\n        binding.tvFooterLeft.visible()\n        binding.tvFooterLeft.setOnClickListener {\n            val selectAll = viewModel.isSelectAll\n            viewModel.selectStatus.forEachIndexed { index, b ->\n                if (b != !selectAll) {\n                    viewModel.selectStatus[index] = !selectAll\n                }\n            }\n            adapter.notifyDataSetChanged()\n            upSelectText()\n        }\n        viewModel.errorLiveData.observe(this) {\n            binding.rotateLoading.gone()\n            binding.tvMsg.apply {\n                text = it\n                visible()\n            }\n        }\n        viewModel.successLiveData.observe(this) {\n            binding.rotateLoading.gone()\n            if (it > 0) {\n                adapter.setItems(viewModel.allSources)\n                upSelectText()\n            } else {\n                binding.tvMsg.apply {\n                    setText(R.string.wrong_format)\n                    visible()\n                }\n            }\n        }\n        val source = arguments?.getString(\"source\")\n        if (source.isNullOrEmpty()) {\n            dismiss()\n            return\n        }\n        viewModel.importSource(source)\n    }\n\n    private fun upSelectText() {\n        if (viewModel.isSelectAll) {\n            binding.tvFooterLeft.text = getString(\n                R.string.select_cancel_count,\n                viewModel.selectCount,\n                viewModel.allSources.size\n            )\n        } else {\n            binding.tvFooterLeft.text = getString(\n                R.string.select_all_count,\n                viewModel.selectCount,\n                viewModel.allSources.size\n            )\n        }\n    }\n\n    private fun initMenu() {\n        binding.toolBar.setOnMenuItemClickListener(this)\n        binding.toolBar.inflateMenu(R.menu.import_source)\n        binding.toolBar.menu.findItem(R.id.menu_keep_original_name)?.isChecked =\n            AppConfig.importKeepName\n        binding.toolBar.menu.findItem(R.id.menu_keep_group)?.isChecked =\n            AppConfig.importKeepGroup\n        binding.toolBar.menu.findItem(R.id.menu_keep_enable)?.isChecked =\n            AppConfig.importKeepEnable\n        binding.toolBar.menu.findItem(R.id.menu_select_new_source)?.isVisible = false\n        binding.toolBar.menu.findItem(R.id.menu_select_update_source)?.isVisible = false\n    }\n\n    @SuppressLint(\"InflateParams\")\n    override fun onMenuItemClick(item: MenuItem): Boolean {\n        when (item.itemId) {\n            R.id.menu_new_group -> alertCustomGroup(item)\n            R.id.menu_keep_original_name -> {\n                item.isChecked = !item.isChecked\n                putPrefBoolean(PreferKey.importKeepName, item.isChecked)\n            }\n\n            R.id.menu_keep_group -> {\n                item.isChecked = !item.isChecked\n                putPrefBoolean(PreferKey.importKeepGroup, item.isChecked)\n            }\n\n            R.id.menu_keep_enable -> {\n                item.isChecked = !item.isChecked\n                AppConfig.importKeepEnable = item.isChecked\n            }\n        }\n        return false\n    }\n\n    private fun alertCustomGroup(item: MenuItem) {\n        alert(R.string.diy_edit_source_group) {\n            val alertBinding = DialogCustomGroupBinding.inflate(layoutInflater).apply {\n                val groups = appDb.rssSourceDao.allGroups()\n                textInputLayout.setHint(R.string.group_name)\n                editView.setFilterValues(groups.toList())\n                editView.dropDownHeight = 180.dpToPx()\n            }\n            customView {\n                alertBinding.root\n            }\n            okButton {\n                viewModel.isAddGroup = alertBinding.swAddGroup.isChecked\n                viewModel.groupName = alertBinding.editView.text?.toString()\n                if (viewModel.groupName.isNullOrBlank()) {\n                    item.title = getString(R.string.diy_source_group)\n                } else {\n                    val group = getString(R.string.diy_edit_source_group_title, viewModel.groupName)\n                    if (viewModel.isAddGroup) {\n                        item.title = \"+$group\"\n                    } else {\n                        item.title = group\n                    }\n                }\n            }\n            cancelButton()\n        }\n    }\n\n    override fun onCodeSave(code: String, requestId: String?) {\n        requestId?.toInt()?.let {\n            GSON.fromJsonObject<RssSource>(code).getOrNull()?.let { source ->\n                viewModel.allSources[it] = source\n                adapter.setItem(it, source)\n            }\n        }\n    }\n\n    inner class SourcesAdapter(context: Context) :\n        RecyclerAdapter<RssSource, ItemSourceImportBinding>(context) {\n\n        override fun getViewBinding(parent: ViewGroup): ItemSourceImportBinding {\n            return ItemSourceImportBinding.inflate(inflater, parent, false)\n        }\n\n        override fun convert(\n            holder: ItemViewHolder,\n            binding: ItemSourceImportBinding,\n            item: RssSource,\n            payloads: MutableList<Any>\n        ) {\n            binding.apply {\n                cbSourceName.isChecked = viewModel.selectStatus[holder.layoutPosition]\n                cbSourceName.text = item.sourceName\n                val localSource = viewModel.checkSources[holder.layoutPosition]\n                tvSourceState.text = when {\n                    localSource == null -> \"新增\"\n                    item.lastUpdateTime > localSource.lastUpdateTime -> \"更新\"\n                    else -> \"已有\"\n                }\n            }\n        }\n\n        override fun registerListener(holder: ItemViewHolder, binding: ItemSourceImportBinding) {\n            binding.apply {\n                cbSourceName.setOnUserCheckedChangeListener { isChecked ->\n                    viewModel.selectStatus[holder.layoutPosition] = isChecked\n                    upSelectText()\n                }\n                root.onClick {\n                    cbSourceName.isChecked = !cbSourceName.isChecked\n                    viewModel.selectStatus[holder.layoutPosition] = cbSourceName.isChecked\n                    upSelectText()\n                }\n                tvOpen.setOnClickListener {\n                    val source = viewModel.allSources[holder.layoutPosition]\n                    showDialogFragment(\n                        CodeDialog(\n                            GSON.toJson(source),\n                            disableEdit = false,\n                            requestId = holder.layoutPosition.toString()\n                        )\n                    )\n                }\n            }\n        }\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/association/ImportRssSourceViewModel.kt",
    "content": "package io.legado.app.ui.association\n\nimport android.app.Application\nimport androidx.core.net.toUri\nimport androidx.lifecycle.MutableLiveData\nimport com.jayway.jsonpath.JsonPath\nimport io.legado.app.R\nimport io.legado.app.base.BaseViewModel\nimport io.legado.app.constant.AppConst\nimport io.legado.app.constant.AppLog\nimport io.legado.app.constant.AppPattern\nimport io.legado.app.data.appDb\nimport io.legado.app.data.entities.RssSource\nimport io.legado.app.exception.NoStackTraceException\nimport io.legado.app.help.config.AppConfig\nimport io.legado.app.help.http.decompressed\nimport io.legado.app.help.http.newCallResponseBody\nimport io.legado.app.help.http.okHttpClient\nimport io.legado.app.help.source.SourceHelp\nimport io.legado.app.utils.GSON\nimport io.legado.app.utils.fromJsonArray\nimport io.legado.app.utils.fromJsonObject\nimport io.legado.app.utils.isAbsUrl\nimport io.legado.app.utils.isJsonArray\nimport io.legado.app.utils.isJsonObject\nimport io.legado.app.utils.isUri\nimport io.legado.app.utils.jsonPath\nimport io.legado.app.utils.readText\nimport io.legado.app.utils.splitNotBlank\nimport splitties.init.appCtx\n\nclass ImportRssSourceViewModel(app: Application) : BaseViewModel(app) {\n    var isAddGroup = false\n    var groupName: String? = null\n    val errorLiveData = MutableLiveData<String>()\n    val successLiveData = MutableLiveData<Int>()\n\n    val allSources = arrayListOf<RssSource>()\n    val checkSources = arrayListOf<RssSource?>()\n    val selectStatus = arrayListOf<Boolean>()\n\n    val isSelectAll: Boolean\n        get() {\n            selectStatus.forEach {\n                if (!it) {\n                    return false\n                }\n            }\n            return true\n        }\n\n    val selectCount: Int\n        get() {\n            var count = 0\n            selectStatus.forEach {\n                if (it) {\n                    count++\n                }\n            }\n            return count\n        }\n\n    fun importSelect(finally: () -> Unit) {\n        execute {\n            val group = groupName?.trim()\n            val keepName = AppConfig.importKeepName\n            val keepGroup = AppConfig.importKeepGroup\n            val keepEnable = AppConfig.importKeepEnable\n            val selectSource = arrayListOf<RssSource>()\n            selectStatus.forEachIndexed { index, b ->\n                if (b) {\n                    val source = allSources[index]\n                    checkSources[index]?.let {\n                        if (keepName) {\n                            source.sourceName = it.sourceName\n                        }\n                        if (keepGroup) {\n                            source.sourceGroup = it.sourceGroup\n                        }\n                        if (keepEnable) {\n                            source.enabled = it.enabled\n                        }\n                        source.customOrder = it.customOrder\n                    }\n                    if (!group.isNullOrEmpty()) {\n                        if (isAddGroup) {\n                            val groups = linkedSetOf<String>()\n                            source.sourceGroup?.splitNotBlank(AppPattern.splitGroupRegex)?.let {\n                                groups.addAll(it)\n                            }\n                            groups.add(group)\n                            source.sourceGroup = groups.joinToString(\",\")\n                        } else {\n                            source.sourceGroup = group\n                        }\n                    }\n                    selectSource.add(source)\n                }\n            }\n            SourceHelp.insertRssSource(*selectSource.toTypedArray())\n        }.onFinally {\n            finally.invoke()\n        }\n    }\n\n    fun importSource(text: String) {\n        execute {\n            importSourceAwait(text)\n        }.onError {\n            errorLiveData.postValue(\"ImportError:${it.localizedMessage}\")\n            AppLog.put(\"ImportError:${it.localizedMessage}\", it)\n        }.onSuccess {\n            comparisonSource()\n        }\n    }\n\n    private suspend fun importSourceAwait(text: String) {\n        val mText = text.trim()\n        when {\n            mText.isJsonObject() -> kotlin.runCatching {\n                val json = JsonPath.parse(mText)\n                val urls = json.read<List<String>>(\"$.sourceUrls\")\n                if (!urls.isNullOrEmpty()) {\n                    urls.forEach {\n                        importSourceUrl(it)\n                    }\n                }\n            }.onFailure {\n                GSON.fromJsonArray<RssSource>(mText).getOrThrow().let {\n                    val source = it.firstOrNull() ?: return@let\n                    if (source.sourceUrl.isEmpty()) {\n                        throw NoStackTraceException(\"不是订阅源\")\n                    }\n                    allSources.addAll(it)\n                }\n            }\n\n            mText.isJsonArray() -> {\n                GSON.fromJsonArray<RssSource>(mText).getOrThrow().let {\n                    val source = it.firstOrNull() ?: return@let\n                    if (source.sourceUrl.isEmpty()) {\n                        throw NoStackTraceException(\"不是订阅源\")\n                    }\n                    allSources.addAll(it)\n                }\n            }\n\n            mText.isAbsUrl() -> {\n                importSourceUrl(mText)\n            }\n\n            mText.isUri() -> {\n                importSourceAwait(mText.toUri().readText(appCtx))\n            }\n\n            else -> throw NoStackTraceException(context.getString(R.string.wrong_format))\n        }\n    }\n\n    private suspend fun importSourceUrl(url: String) {\n        okHttpClient.newCallResponseBody {\n            if (url.endsWith(\"#requestWithoutUA\")) {\n                url(url.substringBeforeLast(\"#requestWithoutUA\"))\n                header(AppConst.UA_NAME, \"null\")\n            } else {\n                url(url)\n            }\n        }.decompressed().byteStream().use { body ->\n            val items: List<Map<String, Any>> = jsonPath.parse(body).read(\"$\")\n            for (item in items) {\n                if (!item.containsKey(\"sourceUrl\")) {\n                    throw NoStackTraceException(\"不是订阅源\")\n                }\n                val jsonItem = jsonPath.parse(item)\n                GSON.fromJsonObject<RssSource>(jsonItem.jsonString()).getOrThrow().let { source ->\n                    allSources.add(source)\n                }\n            }\n        }\n    }\n\n    private fun comparisonSource() {\n        execute {\n            allSources.forEach {\n                val has = appDb.rssSourceDao.getByKey(it.sourceUrl)\n                checkSources.add(has)\n                selectStatus.add(has == null || has.lastUpdateTime < it.lastUpdateTime)\n            }\n            successLiveData.postValue(allSources.size)\n        }\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/association/ImportThemeDialog.kt",
    "content": "package io.legado.app.ui.association\n\nimport android.annotation.SuppressLint\nimport android.content.Context\nimport android.content.DialogInterface\nimport android.os.Bundle\nimport android.view.View\nimport android.view.ViewGroup\nimport androidx.fragment.app.viewModels\nimport androidx.recyclerview.widget.LinearLayoutManager\nimport io.legado.app.R\nimport io.legado.app.base.BaseDialogFragment\nimport io.legado.app.base.adapter.ItemViewHolder\nimport io.legado.app.base.adapter.RecyclerAdapter\nimport io.legado.app.databinding.DialogRecyclerViewBinding\nimport io.legado.app.databinding.ItemSourceImportBinding\nimport io.legado.app.help.config.ThemeConfig\nimport io.legado.app.lib.theme.primaryColor\nimport io.legado.app.ui.widget.dialog.CodeDialog\nimport io.legado.app.ui.widget.dialog.WaitDialog\nimport io.legado.app.utils.GSON\nimport io.legado.app.utils.setLayout\nimport io.legado.app.utils.showDialogFragment\nimport io.legado.app.utils.viewbindingdelegate.viewBinding\nimport io.legado.app.utils.visible\nimport splitties.views.onClick\n\nclass ImportThemeDialog() : BaseDialogFragment(R.layout.dialog_recycler_view) {\n\n    constructor(source: String, finishOnDismiss: Boolean = false) : this() {\n        arguments = Bundle().apply {\n            putString(\"source\", source)\n            putBoolean(\"finishOnDismiss\", finishOnDismiss)\n        }\n    }\n\n    private val binding by viewBinding(DialogRecyclerViewBinding::bind)\n    private val viewModel by viewModels<ImportThemeViewModel>()\n    private val adapter by lazy { SourcesAdapter(requireContext()) }\n\n    override fun onStart() {\n        super.onStart()\n        setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)\n    }\n\n    override fun onDismiss(dialog: DialogInterface) {\n        super.onDismiss(dialog)\n        if (arguments?.getBoolean(\"finishOnDismiss\") == true) {\n            activity?.finish()\n        }\n    }\n\n    @SuppressLint(\"NotifyDataSetChanged\")\n    override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) {\n        binding.toolBar.setBackgroundColor(primaryColor)\n        binding.toolBar.setTitle(R.string.import_theme)\n        binding.rotateLoading.visible()\n        binding.recyclerView.layoutManager = LinearLayoutManager(requireContext())\n        binding.recyclerView.adapter = adapter\n        binding.tvCancel.visible()\n        binding.tvCancel.setOnClickListener {\n            dismissAllowingStateLoss()\n        }\n        binding.tvOk.visible()\n        binding.tvOk.setOnClickListener {\n            val waitDialog = WaitDialog(requireContext())\n            waitDialog.show()\n            viewModel.importSelect {\n                waitDialog.dismiss()\n                dismissAllowingStateLoss()\n            }\n        }\n        binding.tvFooterLeft.visible()\n        binding.tvFooterLeft.setOnClickListener {\n            val selectAll = viewModel.isSelectAll\n            viewModel.selectStatus.forEachIndexed { index, b ->\n                if (b != !selectAll) {\n                    viewModel.selectStatus[index] = !selectAll\n                }\n            }\n            adapter.notifyDataSetChanged()\n            upSelectText()\n        }\n        viewModel.errorLiveData.observe(this) {\n            binding.rotateLoading.gone()\n            binding.tvMsg.apply {\n                text = it\n                visible()\n            }\n        }\n        viewModel.successLiveData.observe(this) {\n            binding.rotateLoading.gone()\n            if (it > 0) {\n                adapter.setItems(viewModel.allSources)\n                upSelectText()\n            } else {\n                binding.tvMsg.apply {\n                    setText(R.string.wrong_format)\n                    visible()\n                }\n            }\n        }\n        val source = arguments?.getString(\"source\")\n        if (source.isNullOrEmpty()) {\n            dismiss()\n            return\n        }\n        viewModel.importSource(source)\n    }\n\n    private fun upSelectText() {\n        if (viewModel.isSelectAll) {\n            binding.tvFooterLeft.text = getString(\n                R.string.select_cancel_count,\n                viewModel.selectCount,\n                viewModel.allSources.size\n            )\n        } else {\n            binding.tvFooterLeft.text = getString(\n                R.string.select_all_count,\n                viewModel.selectCount,\n                viewModel.allSources.size\n            )\n        }\n    }\n\n    inner class SourcesAdapter(context: Context) :\n        RecyclerAdapter<ThemeConfig.Config, ItemSourceImportBinding>(context) {\n\n        override fun getViewBinding(parent: ViewGroup): ItemSourceImportBinding {\n            return ItemSourceImportBinding.inflate(inflater, parent, false)\n        }\n\n        override fun convert(\n            holder: ItemViewHolder,\n            binding: ItemSourceImportBinding,\n            item: ThemeConfig.Config,\n            payloads: MutableList<Any>\n        ) {\n            binding.apply {\n                cbSourceName.isChecked = viewModel.selectStatus[holder.layoutPosition]\n                cbSourceName.text = item.themeName\n                val localSource = viewModel.checkSources[holder.layoutPosition]\n                tvSourceState.text = when {\n                    localSource == null -> \"新增\"\n                    localSource != item -> \"更新\"\n                    else -> \"已有\"\n                }\n            }\n        }\n\n        override fun registerListener(holder: ItemViewHolder, binding: ItemSourceImportBinding) {\n            binding.apply {\n                cbSourceName.setOnUserCheckedChangeListener { isChecked ->\n                    viewModel.selectStatus[holder.layoutPosition] = isChecked\n                    upSelectText()\n                }\n                root.onClick {\n                    cbSourceName.isChecked = !cbSourceName.isChecked\n                    viewModel.selectStatus[holder.layoutPosition] = cbSourceName.isChecked\n                    upSelectText()\n                }\n                tvOpen.setOnClickListener {\n                    val source = viewModel.allSources[holder.layoutPosition]\n                    showDialogFragment(\n                        CodeDialog(\n                            GSON.toJson(source),\n                            disableEdit = false,\n                            requestId = holder.layoutPosition.toString()\n                        )\n                    )\n                }\n            }\n        }\n\n    }\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/association/ImportThemeViewModel.kt",
    "content": "package io.legado.app.ui.association\n\nimport android.app.Application\nimport androidx.core.net.toUri\nimport androidx.lifecycle.MutableLiveData\nimport io.legado.app.R\nimport io.legado.app.base.BaseViewModel\nimport io.legado.app.constant.AppConst\nimport io.legado.app.constant.AppLog\nimport io.legado.app.exception.NoStackTraceException\nimport io.legado.app.help.config.ThemeConfig\nimport io.legado.app.help.http.decompressed\nimport io.legado.app.help.http.newCallResponseBody\nimport io.legado.app.help.http.okHttpClient\nimport io.legado.app.help.http.text\nimport io.legado.app.utils.GSON\nimport io.legado.app.utils.fromJsonArray\nimport io.legado.app.utils.fromJsonObject\nimport io.legado.app.utils.isAbsUrl\nimport io.legado.app.utils.isJsonArray\nimport io.legado.app.utils.isJsonObject\nimport io.legado.app.utils.isUri\nimport io.legado.app.utils.readText\nimport splitties.init.appCtx\n\nclass ImportThemeViewModel(app: Application) : BaseViewModel(app) {\n\n    val errorLiveData = MutableLiveData<String>()\n    val successLiveData = MutableLiveData<Int>()\n\n    val allSources = arrayListOf<ThemeConfig.Config>()\n    val checkSources = arrayListOf<ThemeConfig.Config?>()\n    val selectStatus = arrayListOf<Boolean>()\n\n    val isSelectAll: Boolean\n        get() {\n            selectStatus.forEach {\n                if (!it) {\n                    return false\n                }\n            }\n            return true\n        }\n\n    val selectCount: Int\n        get() {\n            var count = 0\n            selectStatus.forEach {\n                if (it) {\n                    count++\n                }\n            }\n            return count\n        }\n\n    fun importSelect(finally: () -> Unit) {\n        execute {\n            selectStatus.forEachIndexed { index, b ->\n                if (b) {\n                    ThemeConfig.addConfig(allSources[index])\n                }\n            }\n        }.onFinally {\n            finally.invoke()\n        }\n    }\n\n    fun importSource(text: String) {\n        execute {\n            importSourceAwait(text.trim())\n        }.onError {\n            errorLiveData.postValue(\"ImportError:${it.localizedMessage}\")\n            AppLog.put(\"ImportError:${it.localizedMessage}\", it)\n        }.onSuccess {\n            comparisonSource()\n        }\n    }\n\n    private suspend fun importSourceAwait(text: String) {\n        when {\n            text.isJsonObject() -> {\n                GSON.fromJsonObject<ThemeConfig.Config>(text).getOrThrow().let {\n                    allSources.add(it)\n                }\n            }\n\n            text.isJsonArray() -> GSON.fromJsonArray<ThemeConfig.Config>(text).getOrThrow()\n                .let { items ->\n                    allSources.addAll(items)\n                }\n\n            text.isAbsUrl() -> {\n                importSourceUrl(text)\n            }\n\n            text.isUri() -> {\n                importSourceAwait(text.toUri().readText(appCtx))\n            }\n\n            else -> throw NoStackTraceException(context.getString(R.string.wrong_format))\n        }\n    }\n\n    private suspend fun importSourceUrl(url: String) {\n        okHttpClient.newCallResponseBody {\n            if (url.endsWith(\"#requestWithoutUA\")) {\n                url(url.substringBeforeLast(\"#requestWithoutUA\"))\n                header(AppConst.UA_NAME, \"null\")\n            } else {\n                url(url)\n            }\n        }.decompressed().text().let {\n            importSourceAwait(it)\n        }\n    }\n\n    private fun comparisonSource() {\n        execute {\n            allSources.forEach { config ->\n                val source = ThemeConfig.configList.find {\n                    it.themeName == config.themeName\n                }\n                checkSources.add(source)\n                selectStatus.add(source == null || source != config)\n            }\n            successLiveData.postValue(allSources.size)\n        }\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/association/ImportTxtTocRuleDialog.kt",
    "content": "package io.legado.app.ui.association\n\nimport android.annotation.SuppressLint\nimport android.content.Context\nimport android.content.DialogInterface\nimport android.os.Bundle\nimport android.view.View\nimport android.view.ViewGroup\nimport androidx.fragment.app.viewModels\nimport androidx.recyclerview.widget.LinearLayoutManager\nimport io.legado.app.R\nimport io.legado.app.base.BaseDialogFragment\nimport io.legado.app.base.adapter.ItemViewHolder\nimport io.legado.app.base.adapter.RecyclerAdapter\nimport io.legado.app.data.entities.TxtTocRule\nimport io.legado.app.databinding.DialogRecyclerViewBinding\nimport io.legado.app.databinding.ItemSourceImportBinding\nimport io.legado.app.lib.theme.primaryColor\nimport io.legado.app.ui.widget.dialog.CodeDialog\nimport io.legado.app.ui.widget.dialog.WaitDialog\nimport io.legado.app.utils.GSON\nimport io.legado.app.utils.setLayout\nimport io.legado.app.utils.showDialogFragment\nimport io.legado.app.utils.viewbindingdelegate.viewBinding\nimport io.legado.app.utils.visible\nimport splitties.views.onClick\n\nclass ImportTxtTocRuleDialog() : BaseDialogFragment(R.layout.dialog_recycler_view) {\n\n    constructor(source: String, finishOnDismiss: Boolean = false) : this() {\n        arguments = Bundle().apply {\n            putString(\"source\", source)\n            putBoolean(\"finishOnDismiss\", finishOnDismiss)\n        }\n    }\n\n    private val binding by viewBinding(DialogRecyclerViewBinding::bind)\n    private val viewModel by viewModels<ImportTxtTocRuleViewModel>()\n    private val adapter by lazy { SourcesAdapter(requireContext()) }\n\n    override fun onStart() {\n        super.onStart()\n        setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)\n    }\n\n    override fun onDismiss(dialog: DialogInterface) {\n        super.onDismiss(dialog)\n        if (arguments?.getBoolean(\"finishOnDismiss\") == true) {\n            activity?.finish()\n        }\n    }\n\n    @SuppressLint(\"NotifyDataSetChanged\")\n    override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) {\n        binding.toolBar.setBackgroundColor(primaryColor)\n        binding.toolBar.setTitle(R.string.import_txt_toc_rule)\n        binding.rotateLoading.visible()\n        binding.recyclerView.layoutManager = LinearLayoutManager(requireContext())\n        binding.recyclerView.adapter = adapter\n        binding.tvCancel.visible()\n        binding.tvCancel.setOnClickListener {\n            dismissAllowingStateLoss()\n        }\n        binding.tvOk.visible()\n        binding.tvOk.setOnClickListener {\n            val waitDialog = WaitDialog(requireContext())\n            waitDialog.show()\n            viewModel.importSelect {\n                waitDialog.dismiss()\n                dismissAllowingStateLoss()\n            }\n        }\n        binding.tvFooterLeft.visible()\n        binding.tvFooterLeft.setOnClickListener {\n            val selectAll = viewModel.isSelectAll\n            viewModel.selectStatus.forEachIndexed { index, b ->\n                if (b != !selectAll) {\n                    viewModel.selectStatus[index] = !selectAll\n                }\n            }\n            adapter.notifyDataSetChanged()\n            upSelectText()\n        }\n        viewModel.errorLiveData.observe(this) {\n            binding.rotateLoading.gone()\n            binding.tvMsg.apply {\n                text = it\n                visible()\n            }\n        }\n        viewModel.successLiveData.observe(this) {\n            binding.rotateLoading.gone()\n            if (it > 0) {\n                adapter.setItems(viewModel.allSources)\n                upSelectText()\n            } else {\n                binding.tvMsg.apply {\n                    setText(R.string.wrong_format)\n                    visible()\n                }\n            }\n        }\n        val source = arguments?.getString(\"source\")\n        if (source.isNullOrEmpty()) {\n            dismiss()\n            return\n        }\n        viewModel.importSource(source)\n    }\n\n    private fun upSelectText() {\n        if (viewModel.isSelectAll) {\n            binding.tvFooterLeft.text = getString(\n                R.string.select_cancel_count,\n                viewModel.selectCount,\n                viewModel.allSources.size\n            )\n        } else {\n            binding.tvFooterLeft.text = getString(\n                R.string.select_all_count,\n                viewModel.selectCount,\n                viewModel.allSources.size\n            )\n        }\n    }\n\n    inner class SourcesAdapter(context: Context) :\n        RecyclerAdapter<TxtTocRule, ItemSourceImportBinding>(context) {\n\n        override fun getViewBinding(parent: ViewGroup): ItemSourceImportBinding {\n            return ItemSourceImportBinding.inflate(inflater, parent, false)\n        }\n\n        override fun convert(\n            holder: ItemViewHolder,\n            binding: ItemSourceImportBinding,\n            item: TxtTocRule,\n            payloads: MutableList<Any>\n        ) {\n            binding.apply {\n                cbSourceName.isChecked = viewModel.selectStatus[holder.layoutPosition]\n                cbSourceName.text = item.name\n                val localSource = viewModel.checkSources[holder.layoutPosition]\n                tvSourceState.text = when {\n                    localSource == null -> \"新增\"\n                    item != localSource -> \"更新\"\n                    else -> \"已有\"\n                }\n            }\n        }\n\n        override fun registerListener(holder: ItemViewHolder, binding: ItemSourceImportBinding) {\n            binding.apply {\n                cbSourceName.setOnUserCheckedChangeListener { isChecked ->\n                    viewModel.selectStatus[holder.layoutPosition] = isChecked\n                    upSelectText()\n                }\n                root.onClick {\n                    cbSourceName.isChecked = !cbSourceName.isChecked\n                    viewModel.selectStatus[holder.layoutPosition] = cbSourceName.isChecked\n                    upSelectText()\n                }\n                tvOpen.setOnClickListener {\n                    val source = viewModel.allSources[holder.layoutPosition]\n                    showDialogFragment(\n                        CodeDialog(\n                            GSON.toJson(source),\n                            disableEdit = false,\n                            requestId = holder.layoutPosition.toString()\n                        )\n                    )\n                }\n            }\n        }\n\n    }\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/association/ImportTxtTocRuleViewModel.kt",
    "content": "package io.legado.app.ui.association\n\nimport android.app.Application\nimport androidx.core.net.toUri\nimport androidx.lifecycle.MutableLiveData\nimport io.legado.app.R\nimport io.legado.app.base.BaseViewModel\nimport io.legado.app.constant.AppConst\nimport io.legado.app.constant.AppLog\nimport io.legado.app.data.appDb\nimport io.legado.app.data.entities.TxtTocRule\nimport io.legado.app.exception.NoStackTraceException\nimport io.legado.app.help.http.decompressed\nimport io.legado.app.help.http.newCallResponseBody\nimport io.legado.app.help.http.okHttpClient\nimport io.legado.app.help.http.text\nimport io.legado.app.utils.GSON\nimport io.legado.app.utils.fromJsonArray\nimport io.legado.app.utils.fromJsonObject\nimport io.legado.app.utils.isAbsUrl\nimport io.legado.app.utils.isJsonArray\nimport io.legado.app.utils.isJsonObject\nimport io.legado.app.utils.isUri\nimport io.legado.app.utils.readText\nimport splitties.init.appCtx\n\nclass ImportTxtTocRuleViewModel(app: Application) : BaseViewModel(app) {\n\n    val errorLiveData = MutableLiveData<String>()\n    val successLiveData = MutableLiveData<Int>()\n\n    val allSources = arrayListOf<TxtTocRule>()\n    val checkSources = arrayListOf<TxtTocRule?>()\n    val selectStatus = arrayListOf<Boolean>()\n\n    val isSelectAll: Boolean\n        get() {\n            selectStatus.forEach {\n                if (!it) {\n                    return false\n                }\n            }\n            return true\n        }\n\n    val selectCount: Int\n        get() {\n            var count = 0\n            selectStatus.forEach {\n                if (it) {\n                    count++\n                }\n            }\n            return count\n        }\n\n    fun importSelect(finally: () -> Unit) {\n        execute {\n            val selectSource = arrayListOf<TxtTocRule>()\n            selectStatus.forEachIndexed { index, b ->\n                if (b) {\n                    selectSource.add(allSources[index])\n                }\n            }\n            appDb.txtTocRuleDao.insert(*selectSource.toTypedArray())\n        }.onFinally {\n            finally.invoke()\n        }\n    }\n\n    fun importSource(text: String) {\n        execute {\n            importSourceAwait(text.trim())\n        }.onError {\n            errorLiveData.postValue(\"ImportError:${it.localizedMessage}\")\n            AppLog.put(\"ImportError:${it.localizedMessage}\", it)\n        }.onSuccess {\n            comparisonSource()\n        }\n    }\n\n    private suspend fun importSourceAwait(text: String) {\n        when {\n            text.isJsonObject() -> {\n                GSON.fromJsonObject<TxtTocRule>(text).getOrThrow().let {\n                    allSources.add(it)\n                }\n            }\n\n            text.isJsonArray() -> GSON.fromJsonArray<TxtTocRule>(text).getOrThrow()\n                .let { items ->\n                    allSources.addAll(items)\n                }\n\n            text.isAbsUrl() -> {\n                importSourceUrl(text)\n            }\n\n            text.isUri() -> {\n                importSourceAwait(text.toUri().readText(appCtx))\n            }\n\n            else -> throw NoStackTraceException(context.getString(R.string.wrong_format))\n        }\n    }\n\n    private suspend fun importSourceUrl(url: String) {\n        okHttpClient.newCallResponseBody {\n            if (url.endsWith(\"#requestWithoutUA\")) {\n                url(url.substringBeforeLast(\"#requestWithoutUA\"))\n                header(AppConst.UA_NAME, \"null\")\n            } else {\n                url(url)\n            }\n        }.decompressed().text().let {\n            importSourceAwait(it)\n        }\n    }\n\n    private fun comparisonSource() {\n        execute {\n            allSources.forEach {\n                val source = appDb.txtTocRuleDao.get(it.id)\n                checkSources.add(source)\n                selectStatus.add(source == null || it != source)\n            }\n            successLiveData.postValue(allSources.size)\n        }\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/association/OnLineImportActivity.kt",
    "content": "package io.legado.app.ui.association\n\nimport android.os.Bundle\nimport androidx.activity.viewModels\nimport io.legado.app.R\nimport io.legado.app.base.VMBaseActivity\nimport io.legado.app.databinding.ActivityTranslucenceBinding\nimport io.legado.app.lib.dialogs.alert\nimport io.legado.app.utils.showDialogFragment\nimport io.legado.app.utils.viewbindingdelegate.viewBinding\n\n/**\n * 网络一键导入\n * 格式: legado://import/{path}?src={url}\n */\nclass OnLineImportActivity :\n    VMBaseActivity<ActivityTranslucenceBinding, OnLineImportViewModel>() {\n\n    override val binding by viewBinding(ActivityTranslucenceBinding::inflate)\n    override val viewModel by viewModels<OnLineImportViewModel>()\n\n    override fun onActivityCreated(savedInstanceState: Bundle?) {\n        viewModel.successLive.observe(this) {\n            when (it.first) {\n                \"bookSource\" -> showDialogFragment(\n                    ImportBookSourceDialog(it.second, true)\n                )\n                \"rssSource\" -> showDialogFragment(\n                    ImportRssSourceDialog(it.second, true)\n                )\n                \"replaceRule\" -> showDialogFragment(\n                    ImportReplaceRuleDialog(it.second, true)\n                )\n                \"httpTts\" -> showDialogFragment(\n                    ImportHttpTtsDialog(it.second, true)\n                )\n                \"theme\" -> showDialogFragment(\n                    ImportThemeDialog(it.second, true)\n                )\n                \"txtRule\" -> showDialogFragment(\n                    ImportTxtTocRuleDialog(it.second, true)\n                )\n                \"dictRule\" -> showDialogFragment(\n                    ImportDictRuleDialog(it.second, true)\n                )\n            }\n        }\n        viewModel.errorLive.observe(this) {\n            finallyDialog(getString(R.string.error), it)\n        }\n        intent.data?.let {\n            val url = it.getQueryParameter(\"src\")\n            if (url.isNullOrEmpty()) {\n                finish()\n                return\n            }\n            when (it.path) {\n                \"/bookSource\" -> showDialogFragment(\n                    ImportBookSourceDialog(url, true)\n                )\n\n                \"/rssSource\" -> showDialogFragment(\n                    ImportRssSourceDialog(url, true)\n                )\n\n                \"/replaceRule\" -> showDialogFragment(\n                    ImportReplaceRuleDialog(url, true)\n                )\n                \"/textTocRule\" -> showDialogFragment(\n                    ImportTxtTocRuleDialog(url, true)\n                )\n                \"/httpTTS\" -> showDialogFragment(\n                    ImportHttpTtsDialog(url, true)\n                )\n                \"/dictRule\" -> showDialogFragment(\n                    ImportDictRuleDialog(url, true)\n                )\n                \"/theme\" -> showDialogFragment(\n                    ImportThemeDialog(url, true)\n                )\n                \"/readConfig\" -> viewModel.getBytes(url) { bytes ->\n                    viewModel.importReadConfig(bytes, this::finallyDialog)\n                }\n                \"/addToBookshelf\" -> showDialogFragment(\n                    AddToBookshelfDialog(url, true)\n                )\n                \"/importonline\" -> when (it.host) {\n                    \"booksource\" -> showDialogFragment(\n                        ImportBookSourceDialog(url, true)\n                    )\n                    \"rsssource\" -> showDialogFragment(\n                        ImportRssSourceDialog(url, true)\n                    )\n                    \"replace\" -> showDialogFragment(\n                        ImportReplaceRuleDialog(url, true)\n                    )\n                    else -> {\n                        viewModel.determineType(url, this::finallyDialog)\n                    }\n                }\n                else -> viewModel.determineType(url, this::finallyDialog)\n            }\n        }\n    }\n\n    private fun finallyDialog(title: String, msg: String) {\n        alert(title, msg) {\n            okButton()\n            onDismiss {\n                finish()\n            }\n        }\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/association/OnLineImportViewModel.kt",
    "content": "package io.legado.app.ui.association\n\nimport android.app.Application\nimport androidx.core.net.toUri\nimport io.legado.app.R\nimport io.legado.app.constant.AppConst\nimport io.legado.app.help.config.ReadBookConfig\nimport io.legado.app.help.http.decompressed\nimport io.legado.app.help.http.newCallResponseBody\nimport io.legado.app.help.http.okHttpClient\nimport io.legado.app.help.http.text\nimport io.legado.app.utils.FileUtils\nimport io.legado.app.utils.externalCache\nimport okhttp3.MediaType.Companion.toMediaType\nimport splitties.init.appCtx\n\nclass OnLineImportViewModel(app: Application) : BaseAssociationViewModel(app) {\n\n    fun getText(url: String, success: (text: String) -> Unit) {\n        execute {\n            okHttpClient.newCallResponseBody {\n                if (url.endsWith(\"#requestWithoutUA\")) {\n                    url(url.substringBeforeLast(\"#requestWithoutUA\"))\n                    header(AppConst.UA_NAME, \"null\")\n                } else {\n                    url(url)\n                }\n            }.decompressed().text(\"utf-8\")\n        }.onSuccess {\n            success.invoke(it)\n        }.onError {\n            errorLive.postValue(\n                it.localizedMessage ?: context.getString(R.string.unknown_error)\n            )\n        }\n    }\n\n    fun getBytes(url: String, success: (bytes: ByteArray) -> Unit) {\n        execute {\n            okHttpClient.newCallResponseBody {\n                if (url.endsWith(\"#requestWithoutUA\")) {\n                    url(url.substringBeforeLast(\"#requestWithoutUA\"))\n                    header(AppConst.UA_NAME, \"null\")\n                } else {\n                    url(url)\n                }\n            }.bytes()\n        }.onSuccess {\n            success.invoke(it)\n        }.onError {\n            errorLive.postValue(\n                it.localizedMessage ?: context.getString(R.string.unknown_error)\n            )\n        }\n    }\n\n    fun importReadConfig(bytes: ByteArray, finally: (title: String, msg: String) -> Unit) {\n        execute {\n            val config = ReadBookConfig.import(bytes)\n            ReadBookConfig.configList.forEachIndexed { index, c ->\n                if (c.name == config.name) {\n                    ReadBookConfig.configList[index] = config\n                    return@execute config.name\n                }\n                ReadBookConfig.configList.add(config)\n                return@execute config.name\n            }\n        }.onSuccess {\n            finally.invoke(context.getString(R.string.success), \"导入排版成功\")\n        }.onError {\n            finally.invoke(\n                context.getString(R.string.error),\n                it.localizedMessage ?: context.getString(R.string.unknown_error)\n            )\n        }\n    }\n\n    fun determineType(url: String, finally: (title: String, msg: String) -> Unit) {\n        execute {\n            val rs = okHttpClient.newCallResponseBody {\n                if (url.endsWith(\"#requestWithoutUA\")) {\n                    url(url.substringBeforeLast(\"#requestWithoutUA\"))\n                    header(AppConst.UA_NAME, \"null\")\n                } else {\n                    url(url)\n                }\n            }\n            when (rs.contentType()) {\n                \"application/zip\".toMediaType(),\n                \"application/octet-stream\".toMediaType() -> {\n                    importReadConfig(rs.bytes(), finally)\n                }\n                else -> {\n                    val inputStream = rs.byteStream()\n                    val file = FileUtils.createFileIfNotExist(\n                        appCtx.externalCache,\n                        \"download\",\n                        \"scheme_import_cache.json\"\n                    )\n                    file.outputStream().use { out ->\n                        inputStream.use {\n                            it.copyTo(out)\n                        }\n                    }\n                    importJson(file.toUri())\n                }\n            }\n        }\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/association/OpenUrlConfirmActivity.kt",
    "content": "package io.legado.app.ui.association\n\nimport android.os.Bundle\nimport io.legado.app.base.BaseActivity\nimport io.legado.app.constant.SourceType\nimport io.legado.app.databinding.ActivityTranslucenceBinding\nimport io.legado.app.utils.showDialogFragment\nimport io.legado.app.utils.viewbindingdelegate.viewBinding\n\nclass OpenUrlConfirmActivity :\n    BaseActivity<ActivityTranslucenceBinding>() {\n\n    override val binding by viewBinding(ActivityTranslucenceBinding::inflate)\n\n    override fun onActivityCreated(savedInstanceState: Bundle?) {\n        intent.getStringExtra(\"uri\")?.let {\n            val mimeType = intent.getStringExtra(\"mimeType\")\n            val sourceOrigin = intent.getStringExtra(\"sourceOrigin\")\n            val sourceName = intent.getStringExtra(\"sourceName\")\n            val sourceType = intent.getIntExtra(\"sourceType\", SourceType.book)\n            showDialogFragment(OpenUrlConfirmDialog(it, mimeType, sourceOrigin, sourceName, sourceType))\n        } ?: finish()\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/association/OpenUrlConfirmDialog.kt",
    "content": "package io.legado.app.ui.association\n\nimport android.content.Intent\nimport android.os.Bundle\nimport android.view.MenuItem\nimport android.view.View\nimport android.view.ViewGroup\nimport androidx.appcompat.widget.Toolbar\nimport androidx.core.net.toUri\nimport androidx.fragment.app.viewModels\nimport io.legado.app.R\nimport io.legado.app.base.BaseDialogFragment\nimport io.legado.app.constant.AppLog\nimport io.legado.app.databinding.DialogOpenUrlConfirmBinding\nimport io.legado.app.lib.dialogs.alert\nimport io.legado.app.lib.theme.primaryColor\nimport io.legado.app.utils.applyTint\nimport io.legado.app.utils.setLayout\nimport io.legado.app.utils.toastOnUi\nimport io.legado.app.utils.viewbindingdelegate.viewBinding\nimport splitties.init.appCtx\n\nclass OpenUrlConfirmDialog() : BaseDialogFragment(R.layout.dialog_open_url_confirm),\n    Toolbar.OnMenuItemClickListener {\n\n    constructor(\n        uri: String,\n        mimeType: String?,\n        sourceOrigin: String? = null,\n        sourceName: String? = null,\n        sourceType: Int\n    ) : this() {\n        arguments = Bundle().apply {\n            putString(\"uri\", uri)\n            putString(\"mimeType\", mimeType)\n            putString(\"sourceOrigin\", sourceOrigin)\n            putString(\"sourceName\", sourceName)\n            putInt(\"sourceType\", sourceType)\n        }\n    }\n\n    val binding by viewBinding(DialogOpenUrlConfirmBinding::bind)\n    val viewModel by viewModels<OpenUrlConfirmViewModel>()\n\n    override fun onStart() {\n        super.onStart()\n        setLayout(1f, ViewGroup.LayoutParams.WRAP_CONTENT)\n    }\n\n    override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) {\n        initMenu()\n        val arguments = arguments ?: return\n        viewModel.initData(arguments)\n        if (viewModel.uri.isBlank()) {\n            dismiss()\n            return\n        }\n        binding.toolBar.setBackgroundColor(primaryColor)\n        binding.toolBar.subtitle = viewModel.sourceName\n        initView()\n    }\n\n    private fun initMenu() {\n        binding.toolBar.setOnMenuItemClickListener(this)\n        binding.toolBar.inflateMenu(R.menu.open_url_confirm)\n        binding.toolBar.menu.applyTint(requireContext())\n    }\n\n    private fun initView() {\n        binding.message.text = \"${viewModel.sourceName} 正在请求跳转链接/应用，是否跳转？\"\n        binding.btnNegative.setOnClickListener { dismiss() }\n        binding.btnPositive.setOnClickListener {\n            openUrl()\n            dismiss()\n        }\n    }\n\n    private fun openUrl() {\n        try {\n            val uri = viewModel.uri.toUri()\n            val mimeType = viewModel.mimeType\n            // 创建目标 Intent 并设置类型\n            val targetIntent = Intent(Intent.ACTION_VIEW).apply {\n                // 同时设置 Data 和 Type\n                if (!mimeType.isNullOrBlank()) {\n                    setDataAndType(uri, mimeType)\n                } else {\n                    data = uri\n                }\n                addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)\n            }\n\n            // 验证是否有应用可以处理\n            if (targetIntent.resolveActivity(appCtx.packageManager) != null) {\n                startActivity(targetIntent)\n            } else {\n                toastOnUi(R.string.can_not_open)\n            }\n        } catch (e: Exception) {\n            AppLog.put(\"打开链接失败\", e, true)\n        }\n    }\n\n    override fun onMenuItemClick(item: MenuItem): Boolean {\n        when (item.itemId) {\n            R.id.menu_disable_source -> {\n                viewModel.disableSource {\n                    dismiss()\n                }\n            }\n\n            R.id.menu_delete_source -> {\n                alert(R.string.draw) {\n                    setMessage(getString(R.string.sure_del) + \"\\n\" + viewModel.sourceName)\n                    noButton()\n                    yesButton {\n                        viewModel.deleteSource {\n                            dismiss()\n                        }\n                    }\n                }\n            }\n        }\n        return false\n    }\n\n    override fun onDestroy() {\n        super.onDestroy()\n        activity?.finish()\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/association/OpenUrlConfirmViewModel.kt",
    "content": "package io.legado.app.ui.association\n\nimport android.app.Application\nimport android.os.Bundle\nimport io.legado.app.base.BaseViewModel\nimport io.legado.app.constant.SourceType\nimport io.legado.app.data.appDb\nimport io.legado.app.help.source.SourceHelp\n\nclass OpenUrlConfirmViewModel(app: Application): BaseViewModel(app) {\n\n    var uri = \"\"\n    var mimeType: String? = null\n    var sourceOrigin = \"\"\n    var sourceName = \"\"\n    var sourceType = SourceType.book\n\n    fun initData(arguments: Bundle) {\n        uri = arguments.getString(\"uri\") ?: \"\"\n        mimeType = arguments.getString(\"mimeType\")\n        sourceName = arguments.getString(\"sourceName\") ?: \"\"\n        sourceOrigin = arguments.getString(\"sourceOrigin\") ?: \"\"\n        sourceType = arguments.getInt(\"sourceType\", SourceType.book)\n    }\n\n    fun disableSource(block: () -> Unit) {\n        execute {\n            SourceHelp.enableSource(sourceOrigin, sourceType, false)\n        }.onSuccess {\n            block.invoke()\n        }\n    }\n\n    fun deleteSource(block: () -> Unit) {\n        execute {\n            SourceHelp.deleteSource(sourceOrigin, sourceType)\n        }.onSuccess {\n            block.invoke()\n        }\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/association/VerificationCodeActivity.kt",
    "content": "package io.legado.app.ui.association\n\nimport android.os.Bundle\nimport io.legado.app.base.BaseActivity\nimport io.legado.app.constant.SourceType\nimport io.legado.app.databinding.ActivityTranslucenceBinding\nimport io.legado.app.utils.showDialogFragment\nimport io.legado.app.utils.viewbindingdelegate.viewBinding\n\n/**\n * 验证码\n */\nclass VerificationCodeActivity :\n    BaseActivity<ActivityTranslucenceBinding>() {\n\n    override val binding by viewBinding(ActivityTranslucenceBinding::inflate)\n\n    override fun onActivityCreated(savedInstanceState: Bundle?) {\n        intent.getStringExtra(\"imageUrl\")?.let {\n            val sourceOrigin = intent.getStringExtra(\"sourceOrigin\")\n            val sourceName = intent.getStringExtra(\"sourceName\")\n            val sourceType = intent.getIntExtra(\"sourceType\", SourceType.book)\n            showDialogFragment(\n                VerificationCodeDialog(it, sourceOrigin, sourceName, sourceType)\n            )\n        } ?: finish()\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/association/VerificationCodeDialog.kt",
    "content": "package io.legado.app.ui.association\n\nimport android.annotation.SuppressLint\nimport android.graphics.Bitmap\nimport android.os.Bundle\nimport android.view.MenuItem\nimport android.view.View\nimport android.view.ViewGroup\nimport androidx.appcompat.widget.Toolbar\nimport androidx.fragment.app.viewModels\nimport com.bumptech.glide.load.DataSource\nimport com.bumptech.glide.load.engine.DiskCacheStrategy\nimport com.bumptech.glide.load.engine.GlideException\nimport com.bumptech.glide.request.RequestListener\nimport com.bumptech.glide.request.RequestOptions\nimport com.bumptech.glide.request.target.Target\nimport io.legado.app.R\nimport io.legado.app.base.BaseDialogFragment\nimport io.legado.app.databinding.DialogVerificationCodeViewBinding\nimport io.legado.app.help.glide.ImageLoader\nimport io.legado.app.help.glide.OkHttpModelLoader\nimport io.legado.app.help.source.SourceVerificationHelp\nimport io.legado.app.lib.dialogs.alert\nimport io.legado.app.lib.theme.primaryColor\nimport io.legado.app.model.ImageProvider\nimport io.legado.app.ui.widget.dialog.PhotoDialog\nimport io.legado.app.utils.applyTint\nimport io.legado.app.utils.setLayout\nimport io.legado.app.utils.showDialogFragment\nimport io.legado.app.utils.viewbindingdelegate.viewBinding\n\n/**\n * 图片验证码对话框\n * 结果保存在内存中\n * val key = \"${sourceOrigin ?: \"\"}_verificationResult\"\n * CacheManager.get(key)\n */\nclass VerificationCodeDialog() : BaseDialogFragment(R.layout.dialog_verification_code_view),\n    Toolbar.OnMenuItemClickListener {\n\n    constructor(\n        imageUrl: String,\n        sourceOrigin: String? = null,\n        sourceName: String? = null,\n        sourceType: Int\n    ) : this() {\n        arguments = Bundle().apply {\n            putString(\"imageUrl\", imageUrl)\n            putString(\"sourceOrigin\", sourceOrigin)\n            putString(\"sourceName\", sourceName)\n            putInt(\"sourceType\", sourceType)\n        }\n    }\n\n    val binding by viewBinding(DialogVerificationCodeViewBinding::bind)\n    val viewModel by viewModels<VerificationCodeViewModel>()\n\n    override fun onStart() {\n        super.onStart()\n        setLayout(1f, ViewGroup.LayoutParams.WRAP_CONTENT)\n    }\n\n    private var sourceOrigin: String? = null\n\n    override fun onFragmentCreated(view: View, savedInstanceState: Bundle?): Unit = binding.run {\n        initMenu()\n        val arguments = arguments ?: return@run\n        viewModel.initData(arguments)\n        toolBar.setBackgroundColor(primaryColor)\n        toolBar.subtitle = arguments.getString(\"sourceName\")\n        sourceOrigin = arguments.getString(\"sourceOrigin\")\n        val imageUrl = arguments.getString(\"imageUrl\") ?: return@run\n        loadImage(imageUrl, sourceOrigin)\n        verificationCodeImageView.setOnClickListener {\n            showDialogFragment(PhotoDialog(imageUrl, sourceOrigin))\n        }\n    }\n\n    private fun initMenu() {\n        binding.toolBar.setOnMenuItemClickListener(this)\n        binding.toolBar.inflateMenu(R.menu.verification_code)\n        binding.toolBar.menu.applyTint(requireContext())\n    }\n\n    @SuppressLint(\"CheckResult\")\n    private fun loadImage(url: String, sourceUrl: String?) {\n        ImageProvider.remove(url)\n        ImageLoader.loadBitmap(requireContext(), url).apply {\n            sourceUrl?.let {\n                apply(RequestOptions().set(OkHttpModelLoader.sourceOriginOption, it))\n            }\n        }.error(R.drawable.image_loading_error)\n            .diskCacheStrategy(DiskCacheStrategy.NONE)\n            .skipMemoryCache(true)\n            .listener(object : RequestListener<Bitmap> {\n                override fun onLoadFailed(\n                    e: GlideException?,\n                    model: Any?,\n                    target: Target<Bitmap?>,\n                    isFirstResource: Boolean\n                ): Boolean {\n                    return false\n                }\n\n                override fun onResourceReady(\n                    resource: Bitmap,\n                    model: Any,\n                    target: Target<Bitmap?>?,\n                    dataSource: DataSource,\n                    isFirstResource: Boolean\n                ): Boolean {\n                    val bitmap = resource.copy(resource.config!!, true)\n                    ImageProvider.put(url, bitmap) // 传给 PhotoDialog\n                    return false\n                }\n            })\n            .into(binding.verificationCodeImageView)\n    }\n\n    @SuppressLint(\"InflateParams\")\n    override fun onMenuItemClick(item: MenuItem): Boolean {\n        when (item.itemId) {\n            R.id.menu_ok -> {\n                val verificationCode = binding.verificationCode.text.toString()\n                SourceVerificationHelp.setResult(sourceOrigin!!, verificationCode)\n                dismiss()\n            }\n\n            R.id.menu_disable_source -> {\n                viewModel.disableSource {\n                    dismiss()\n                }\n            }\n\n            R.id.menu_delete_source -> {\n                alert(R.string.draw) {\n                    setMessage(getString(R.string.sure_del) + \"\\n\" + viewModel.sourceName)\n                    noButton()\n                    yesButton {\n                        viewModel.deleteSource {\n                            dismiss()\n                        }\n                    }\n                }\n            }\n        }\n        return false\n    }\n\n    override fun onDestroy() {\n        SourceVerificationHelp.checkResult(sourceOrigin!!)\n        super.onDestroy()\n        activity?.finish()\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/association/VerificationCodeViewModel.kt",
    "content": "package io.legado.app.ui.association\n\nimport android.app.Application\nimport android.os.Bundle\nimport io.legado.app.base.BaseViewModel\nimport io.legado.app.constant.SourceType\nimport io.legado.app.help.source.SourceHelp\n\nclass VerificationCodeViewModel(app: Application): BaseViewModel(app) {\n\n    var sourceOrigin = \"\"\n    var sourceName = \"\"\n    private var sourceType = SourceType.book\n\n    fun initData(arguments: Bundle) {\n        sourceName = arguments.getString(\"sourceName\") ?: \"\"\n        sourceOrigin = arguments.getString(\"sourceOrigin\") ?: \"\"\n        sourceType = arguments.getInt(\"sourceType\", SourceType.book)\n    }\n\n    fun disableSource(block: () -> Unit) {\n        execute {\n            SourceHelp.enableSource(sourceOrigin, sourceType, false)\n        }.onSuccess {\n            block.invoke()\n        }\n    }\n\n    fun deleteSource(block: () -> Unit) {\n        execute {\n            SourceHelp.deleteSource(sourceOrigin, sourceType)\n        }.onSuccess {\n            block.invoke()\n        }\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/book/audio/AudioPlayActivity.kt",
    "content": "package io.legado.app.ui.book.audio\n\nimport android.annotation.SuppressLint\nimport android.os.Build\nimport android.os.Bundle\nimport android.view.Gravity\nimport android.view.Menu\nimport android.view.MenuItem\nimport android.widget.SeekBar\nimport androidx.activity.viewModels\nimport androidx.lifecycle.lifecycleScope\nimport io.legado.app.R\nimport io.legado.app.base.VMBaseActivity\nimport io.legado.app.constant.BookType\nimport io.legado.app.constant.EventBus\nimport io.legado.app.constant.Status\nimport io.legado.app.constant.Theme\nimport io.legado.app.data.appDb\nimport io.legado.app.data.entities.Book\nimport io.legado.app.data.entities.BookChapter\nimport io.legado.app.data.entities.BookSource\nimport io.legado.app.databinding.ActivityAudioPlayBinding\nimport io.legado.app.help.book.isAudio\nimport io.legado.app.help.book.removeType\nimport io.legado.app.help.config.AppConfig\nimport io.legado.app.lib.dialogs.alert\nimport io.legado.app.model.AudioPlay\nimport io.legado.app.model.BookCover\nimport io.legado.app.service.AudioPlayService\nimport io.legado.app.ui.about.AppLogDialog\nimport io.legado.app.ui.book.changesource.ChangeBookSourceDialog\nimport io.legado.app.ui.book.source.edit.BookSourceEditActivity\nimport io.legado.app.ui.book.toc.TocActivityResult\nimport io.legado.app.ui.login.SourceLoginActivity\nimport io.legado.app.ui.widget.seekbar.SeekBarChangeListener\nimport io.legado.app.utils.StartActivityContract\nimport io.legado.app.utils.applyNavigationBarPadding\nimport io.legado.app.utils.dpToPx\nimport io.legado.app.utils.invisible\nimport io.legado.app.utils.observeEvent\nimport io.legado.app.utils.observeEventSticky\nimport io.legado.app.utils.sendToClip\nimport io.legado.app.utils.showDialogFragment\nimport io.legado.app.utils.startActivity\nimport io.legado.app.utils.startActivityForBook\nimport io.legado.app.utils.toDurationTime\nimport io.legado.app.utils.viewbindingdelegate.viewBinding\nimport io.legado.app.utils.visible\nimport kotlinx.coroutines.Dispatchers.IO\nimport kotlinx.coroutines.launch\nimport kotlinx.coroutines.withContext\nimport splitties.views.onLongClick\nimport java.util.Locale\n\n/**\n * 音频播放\n */\n@SuppressLint(\"ObsoleteSdkInt\")\nclass AudioPlayActivity :\n    VMBaseActivity<ActivityAudioPlayBinding, AudioPlayViewModel>(toolBarTheme = Theme.Dark),\n    ChangeBookSourceDialog.CallBack,\n    AudioPlay.CallBack {\n\n    override val binding by viewBinding(ActivityAudioPlayBinding::inflate)\n    override val viewModel by viewModels<AudioPlayViewModel>()\n    private val timerSliderPopup by lazy { TimerSliderPopup(this) }\n    private var adjustProgress = false\n    private var playMode = AudioPlay.PlayMode.LIST_END_STOP\n\n    private val tocActivityResult = registerForActivityResult(TocActivityResult()) {\n        it?.let {\n            if (it.first != AudioPlay.book?.durChapterIndex\n                || it.second == 0\n            ) {\n                AudioPlay.skipTo(it.first)\n            }\n        }\n    }\n    private val sourceEditResult =\n        registerForActivityResult(StartActivityContract(BookSourceEditActivity::class.java)) {\n            if (it.resultCode == RESULT_OK) {\n                viewModel.upSource()\n            }\n        }\n\n    override fun onActivityCreated(savedInstanceState: Bundle?) {\n        binding.titleBar.setBackgroundResource(R.color.transparent)\n        AudioPlay.register(this)\n        viewModel.titleData.observe(this) {\n            binding.titleBar.title = it\n        }\n        viewModel.coverData.observe(this) {\n            upCover(it)\n        }\n        viewModel.initData(intent)\n        initView()\n    }\n\n    override fun onCompatCreateOptionsMenu(menu: Menu): Boolean {\n        menuInflater.inflate(R.menu.audio_play, menu)\n        return super.onCompatCreateOptionsMenu(menu)\n    }\n\n    override fun onMenuOpened(featureId: Int, menu: Menu): Boolean {\n        menu.findItem(R.id.menu_login)?.isVisible = !AudioPlay.bookSource?.loginUrl.isNullOrBlank()\n        menu.findItem(R.id.menu_wake_lock)?.isChecked = AppConfig.audioPlayUseWakeLock\n        return super.onMenuOpened(featureId, menu)\n    }\n\n    override fun onCompatOptionsItemSelected(item: MenuItem): Boolean {\n        when (item.itemId) {\n            R.id.menu_change_source -> AudioPlay.book?.let {\n                showDialogFragment(ChangeBookSourceDialog(it.name, it.author))\n            }\n\n            R.id.menu_login -> AudioPlay.bookSource?.let {\n                startActivity<SourceLoginActivity> {\n                    putExtra(\"type\", \"bookSource\")\n                    putExtra(\"key\", it.bookSourceUrl)\n                }\n            }\n\n            R.id.menu_wake_lock -> AppConfig.audioPlayUseWakeLock = !AppConfig.audioPlayUseWakeLock\n            R.id.menu_copy_audio_url -> sendToClip(AudioPlayService.url)\n            R.id.menu_edit_source -> AudioPlay.bookSource?.let {\n                sourceEditResult.launch {\n                    putExtra(\"sourceUrl\", it.bookSourceUrl)\n                }\n            }\n\n            R.id.menu_log -> showDialogFragment<AppLogDialog>()\n        }\n        return super.onCompatOptionsItemSelected(item)\n    }\n\n    private fun initView() {\n        binding.ivPlayMode.setOnClickListener {\n            AudioPlay.changePlayMode()\n        }\n\n        observeEventSticky<AudioPlay.PlayMode>(EventBus.PLAY_MODE_CHANGED) {\n            playMode = it\n            updatePlayModeIcon()\n        }\n\n        binding.fabPlayStop.setOnClickListener {\n            playButton()\n        }\n        binding.fabPlayStop.onLongClick {\n            AudioPlay.stop()\n        }\n        binding.ivSkipNext.setOnClickListener {\n            AudioPlay.next()\n        }\n        binding.ivSkipPrevious.setOnClickListener {\n            AudioPlay.prev()\n        }\n        binding.playerProgress.setOnSeekBarChangeListener(object : SeekBarChangeListener {\n            override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {\n                binding.tvDurTime.text = progress.toDurationTime()\n            }\n\n            override fun onStartTrackingTouch(seekBar: SeekBar) {\n                adjustProgress = true\n            }\n\n            override fun onStopTrackingTouch(seekBar: SeekBar) {\n                adjustProgress = false\n                AudioPlay.adjustProgress(seekBar.progress)\n            }\n        })\n        binding.ivChapter.setOnClickListener {\n            AudioPlay.book?.let {\n                tocActivityResult.launch(it.bookUrl)\n            }\n        }\n        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {\n            binding.ivFastRewind.invisible()\n            binding.ivFastForward.invisible()\n        }\n        binding.ivFastForward.setOnClickListener {\n            AudioPlay.adjustSpeed(0.1f)\n        }\n        binding.ivFastRewind.setOnClickListener {\n            AudioPlay.adjustSpeed(-0.1f)\n        }\n        binding.ivTimer.setOnClickListener {\n            timerSliderPopup.showAsDropDown(it, 0, (-100).dpToPx(), Gravity.TOP)\n        }\n        binding.llPlayMenu.applyNavigationBarPadding()\n    }\n\n    private fun updatePlayModeIcon() {\n        binding.ivPlayMode.setImageResource(playMode.iconRes)\n    }\n\n    private fun upCover(path: String?) {\n        BookCover.load(this, path, sourceOrigin = AudioPlay.bookSource?.bookSourceUrl) {\n            BookCover.loadBlur(this, path, sourceOrigin = AudioPlay.bookSource?.bookSourceUrl)\n                .into(binding.ivBg)\n        }.into(binding.ivCover)\n    }\n\n    private fun playButton() {\n        when (AudioPlay.status) {\n            Status.PLAY -> AudioPlay.pause(this)\n            Status.PAUSE -> AudioPlay.resume(this)\n            else -> AudioPlay.loadOrUpPlayUrl()\n        }\n    }\n\n    override val oldBook: Book?\n        get() = AudioPlay.book\n\n    override fun changeTo(source: BookSource, book: Book, toc: List<BookChapter>) {\n        if (book.isAudio) {\n            viewModel.changeTo(source, book, toc)\n        } else {\n            AudioPlay.stop()\n            lifecycleScope.launch {\n                withContext(IO) {\n                    AudioPlay.book?.migrateTo(book, toc)\n                    book.removeType(BookType.updateError)\n                    AudioPlay.book?.delete()\n                    appDb.bookDao.insert(book)\n                }\n                startActivityForBook(book)\n                finish()\n            }\n        }\n    }\n\n    override fun finish() {\n        val book = AudioPlay.book ?: return super.finish()\n\n        if (AudioPlay.inBookshelf) {\n            return super.finish()\n        }\n\n        if (!AppConfig.showAddToShelfAlert) {\n            viewModel.removeFromBookshelf { super.finish() }\n        } else {\n            alert(title = getString(R.string.add_to_bookshelf)) {\n                setMessage(getString(R.string.check_add_bookshelf, book.name))\n                okButton {\n                    AudioPlay.book?.removeType(BookType.notShelf)\n                    AudioPlay.book?.save()\n                    AudioPlay.inBookshelf = true\n                    setResult(RESULT_OK)\n                }\n                noButton { viewModel.removeFromBookshelf { super.finish() } }\n            }\n        }\n    }\n\n    override fun onDestroy() {\n        super.onDestroy()\n        if (AudioPlay.status != Status.PLAY) {\n            AudioPlay.stop()\n        }\n        AudioPlay.unregister(this)\n    }\n\n    @SuppressLint(\"SetTextI18n\")\n    override fun observeLiveBus() {\n        observeEvent<Boolean>(EventBus.MEDIA_BUTTON) {\n            if (it) {\n                playButton()\n            }\n        }\n        observeEventSticky<Int>(EventBus.AUDIO_STATE) {\n            AudioPlay.status = it\n            if (it == Status.PLAY) {\n                binding.fabPlayStop.setImageResource(R.drawable.ic_pause_24dp)\n            } else {\n                binding.fabPlayStop.setImageResource(R.drawable.ic_play_24dp)\n            }\n        }\n        observeEventSticky<String>(EventBus.AUDIO_SUB_TITLE) {\n            binding.tvSubTitle.text = it\n            binding.ivSkipPrevious.isEnabled = AudioPlay.durChapterIndex > 0\n            binding.ivSkipNext.isEnabled =\n                AudioPlay.durChapterIndex < AudioPlay.simulatedChapterSize - 1\n        }\n        observeEventSticky<Int>(EventBus.AUDIO_SIZE) {\n            binding.playerProgress.max = it\n            binding.tvAllTime.text = it.toDurationTime()\n        }\n        observeEventSticky<Int>(EventBus.AUDIO_PROGRESS) {\n            if (!adjustProgress) binding.playerProgress.progress = it\n            binding.tvDurTime.text = it.toDurationTime()\n        }\n        observeEventSticky<Int>(EventBus.AUDIO_BUFFER_PROGRESS) {\n            binding.playerProgress.secondaryProgress = it\n\n        }\n        observeEventSticky<Float>(EventBus.AUDIO_SPEED) {\n            binding.tvSpeed.text = String.format(Locale.ROOT, \"%.1fX\", it)\n            binding.tvSpeed.visible()\n        }\n        observeEventSticky<Int>(EventBus.AUDIO_DS) {\n            binding.tvTimer.text = \"${it}m\"\n            binding.tvTimer.visible(it > 0)\n        }\n    }\n\n    override fun upLoading(loading: Boolean) {\n        runOnUiThread {\n            binding.progressLoading.visible(loading)\n        }\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/book/audio/AudioPlayViewModel.kt",
    "content": "package io.legado.app.ui.book.audio\n\nimport android.app.Application\nimport android.content.Intent\nimport androidx.lifecycle.MutableLiveData\nimport io.legado.app.R\nimport io.legado.app.base.BaseViewModel\nimport io.legado.app.constant.AppLog\nimport io.legado.app.constant.BookType\nimport io.legado.app.constant.EventBus\nimport io.legado.app.data.appDb\nimport io.legado.app.data.entities.Book\nimport io.legado.app.data.entities.BookChapter\nimport io.legado.app.data.entities.BookSource\nimport io.legado.app.help.book.getBookSource\nimport io.legado.app.help.book.removeType\nimport io.legado.app.help.book.simulatedTotalChapterNum\nimport io.legado.app.model.AudioPlay\nimport io.legado.app.model.webBook.WebBook\nimport io.legado.app.utils.postEvent\nimport io.legado.app.utils.toastOnUi\n\nclass AudioPlayViewModel(application: Application) : BaseViewModel(application) {\n    val titleData = MutableLiveData<String>()\n    val coverData = MutableLiveData<String>()\n\n    fun initData(intent: Intent) = AudioPlay.apply {\n        execute {\n            val bookUrl = intent.getStringExtra(\"bookUrl\") ?: book?.bookUrl ?: return@execute\n            val book = appDb.bookDao.getBook(bookUrl) ?: return@execute\n            inBookshelf = intent.getBooleanExtra(\"inBookshelf\", true)\n            initBook(book)\n        }.onFinally {\n            saveRead()\n        }\n    }\n\n    private suspend fun initBook(book: Book) {\n        val isSameBook = AudioPlay.book?.bookUrl == book.bookUrl\n        if (isSameBook) {\n            AudioPlay.upData(book)\n        } else {\n            AudioPlay.resetData(book)\n        }\n        titleData.postValue(book.name)\n        coverData.postValue(book.getDisplayCover())\n        if (book.tocUrl.isEmpty() && !loadBookInfo(book)) {\n            return\n        }\n        if (AudioPlay.chapterSize == 0 && !loadChapterList(book)) {\n            return\n        }\n    }\n\n    private suspend fun loadBookInfo(book: Book): Boolean {\n        val bookSource = AudioPlay.bookSource ?: return true\n        try {\n            WebBook.getBookInfoAwait(bookSource, book)\n            return true\n        } catch (e: Exception) {\n            AppLog.put(\"详情页出错: ${e.localizedMessage}\", e, true)\n            return false\n        }\n    }\n\n    private suspend fun loadChapterList(book: Book): Boolean {\n        val bookSource = AudioPlay.bookSource ?: return true\n        try {\n            val oldBook = book.copy()\n            val cList = WebBook.getChapterListAwait(bookSource, book).getOrThrow()\n            if (oldBook.bookUrl == book.bookUrl) {\n                appDb.bookDao.update(book)\n            } else {\n                appDb.bookDao.replace(oldBook, book)\n            }\n            appDb.bookChapterDao.delByBook(book.bookUrl)\n            appDb.bookChapterDao.insert(*cList.toTypedArray())\n            AudioPlay.chapterSize = cList.size\n            AudioPlay.simulatedChapterSize = book.simulatedTotalChapterNum()\n            AudioPlay.upDurChapter()\n            return true\n        } catch (e: Exception) {\n            context.toastOnUi(R.string.error_load_toc)\n            return false\n        }\n    }\n\n    fun upSource() {\n        execute {\n            val book = AudioPlay.book ?: return@execute\n            AudioPlay.bookSource = book.getBookSource()\n        }\n    }\n\n    fun changeTo(source: BookSource, book: Book, toc: List<BookChapter>) {\n        execute {\n            AudioPlay.book?.migrateTo(book, toc)\n            book.removeType(BookType.updateError)\n            AudioPlay.book?.delete()\n            appDb.bookDao.insert(book)\n            AudioPlay.book = book\n            AudioPlay.bookSource = source\n            appDb.bookChapterDao.insert(*toc.toTypedArray())\n            AudioPlay.upDurChapter()\n        }.onFinally {\n            postEvent(EventBus.SOURCE_CHANGED, book.bookUrl)\n        }\n    }\n\n    fun removeFromBookshelf(success: (() -> Unit)?) {\n        execute {\n            AudioPlay.book?.let {\n                appDb.bookDao.delete(it)\n            }\n        }.onSuccess {\n            success?.invoke()\n        }\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/book/audio/TimerSliderPopup.kt",
    "content": "package io.legado.app.ui.book.audio\n\nimport android.content.Context\nimport android.view.LayoutInflater\nimport android.view.View\nimport android.view.ViewGroup\nimport android.widget.PopupWindow\nimport android.widget.SeekBar\nimport io.legado.app.R\nimport io.legado.app.databinding.PopupSeekBarBinding\nimport io.legado.app.model.AudioPlay\nimport io.legado.app.service.AudioPlayService\nimport io.legado.app.ui.widget.seekbar.SeekBarChangeListener\n\nclass TimerSliderPopup(private val context: Context) :\n    PopupWindow(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT) {\n\n    private val binding = PopupSeekBarBinding.inflate(LayoutInflater.from(context))\n\n    init {\n        contentView = binding.root\n\n        isTouchable = true\n        isOutsideTouchable = false\n        isFocusable = true\n\n        binding.seekBar.max = 180\n        setProcessTextValue(binding.seekBar.progress)\n        binding.seekBar.setOnSeekBarChangeListener(object : SeekBarChangeListener {\n\n            override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {\n                setProcessTextValue(progress)\n                if (fromUser) {\n                    AudioPlay.setTimer(progress)\n                }\n            }\n\n        })\n    }\n\n    override fun showAsDropDown(anchor: View?, xoff: Int, yoff: Int, gravity: Int) {\n        super.showAsDropDown(anchor, xoff, yoff, gravity)\n        binding.seekBar.progress = AudioPlayService.timeMinute\n    }\n\n    override fun showAtLocation(parent: View?, gravity: Int, x: Int, y: Int) {\n        super.showAtLocation(parent, gravity, x, y)\n        binding.seekBar.progress = AudioPlayService.timeMinute\n    }\n\n    private fun setProcessTextValue(process: Int) {\n        binding.tvSeekValue.text = context.getString(R.string.timer_m, process)\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/book/bookmark/AllBookmarkActivity.kt",
    "content": "package io.legado.app.ui.book.bookmark\n\nimport android.os.Bundle\nimport android.view.Menu\nimport android.view.MenuItem\nimport androidx.activity.viewModels\nimport androidx.lifecycle.lifecycleScope\nimport io.legado.app.R\nimport io.legado.app.base.VMBaseActivity\nimport io.legado.app.constant.AppLog\nimport io.legado.app.data.appDb\nimport io.legado.app.data.entities.Bookmark\nimport io.legado.app.databinding.ActivityAllBookmarkBinding\nimport io.legado.app.ui.file.HandleFileContract\nimport io.legado.app.utils.applyNavigationBarPadding\nimport io.legado.app.utils.showDialogFragment\nimport io.legado.app.utils.viewbindingdelegate.viewBinding\nimport kotlinx.coroutines.Dispatchers.IO\nimport kotlinx.coroutines.flow.catch\nimport kotlinx.coroutines.flow.flowOn\nimport kotlinx.coroutines.launch\n\n/**\n * 所有书签\n */\nclass AllBookmarkActivity : VMBaseActivity<ActivityAllBookmarkBinding, AllBookmarkViewModel>(),\n    BookmarkAdapter.Callback {\n\n    override val viewModel by viewModels<AllBookmarkViewModel>()\n    override val binding by viewBinding(ActivityAllBookmarkBinding::inflate)\n    private val adapter by lazy {\n        BookmarkAdapter(this, this)\n    }\n    private val exportDir = registerForActivityResult(HandleFileContract()) {\n        it.uri?.let { uri ->\n            when (it.requestCode) {\n                1 -> viewModel.exportBookmark(uri)\n                2 -> viewModel.exportBookmarkMd(uri)\n            }\n        }\n    }\n\n    override fun onActivityCreated(savedInstanceState: Bundle?) {\n        initView()\n        lifecycleScope.launch {\n            appDb.bookmarkDao.flowAll().catch {\n                AppLog.put(\"所有书签界面获取数据失败\\n${it.localizedMessage}\", it)\n            }.flowOn(IO).collect {\n                adapter.setItems(it)\n            }\n        }\n    }\n\n    private fun initView() {\n        binding.recyclerView.addItemDecoration(BookmarkDecoration(adapter))\n        binding.recyclerView.adapter = adapter\n        binding.recyclerView.applyNavigationBarPadding()\n    }\n\n    override fun onCompatCreateOptionsMenu(menu: Menu): Boolean {\n        menuInflater.inflate(R.menu.bookmark, menu)\n        return super.onCompatCreateOptionsMenu(menu)\n    }\n\n    override fun onCompatOptionsItemSelected(item: MenuItem): Boolean {\n        when (item.itemId) {\n            R.id.menu_export -> exportDir.launch {\n                requestCode = 1\n            }\n\n            R.id.menu_export_md -> exportDir.launch {\n                requestCode = 2\n            }\n        }\n        return super.onCompatOptionsItemSelected(item)\n    }\n\n    override fun onItemClick(bookmark: Bookmark, position: Int) {\n        showDialogFragment(BookmarkDialog(bookmark, position))\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/book/bookmark/AllBookmarkViewModel.kt",
    "content": "package io.legado.app.ui.book.bookmark\n\nimport android.app.Application\nimport android.net.Uri\nimport io.legado.app.base.BaseViewModel\nimport io.legado.app.constant.AppLog\nimport io.legado.app.data.appDb\nimport io.legado.app.utils.FileDoc\nimport io.legado.app.utils.GSON\nimport io.legado.app.utils.createFileIfNotExist\nimport io.legado.app.utils.openOutputStream\nimport io.legado.app.utils.toastOnUi\nimport io.legado.app.utils.writeToOutputStream\nimport java.text.SimpleDateFormat\nimport java.util.Date\nimport java.util.Locale\n\nclass AllBookmarkViewModel(application: Application) : BaseViewModel(application) {\n\n\n    /**\n     * 导出书签\n     */\n    fun exportBookmark(treeUri: Uri) {\n        execute {\n            val dateFormat = SimpleDateFormat(\"yyMMddHHmmss\", Locale.getDefault())\n            val fileName = \"bookmark-${dateFormat.format(Date())}.json\"\n            val dirDoc = FileDoc.fromUri(treeUri, true)\n            dirDoc.createFileIfNotExist(fileName).openOutputStream().getOrThrow().use {\n                GSON.writeToOutputStream(it, appDb.bookmarkDao.all)\n            }\n        }.onError {\n            AppLog.put(\"导出失败\\n${it.localizedMessage}\", it, true)\n        }.onSuccess {\n            context.toastOnUi(\"导出成功\")\n        }\n    }\n\n\n    fun exportBookmarkMd(treeUri: Uri) {\n        execute {\n            val dateFormat = SimpleDateFormat(\"yyMMddHHmmss\", Locale.getDefault())\n            val fileName = \"bookmark-${dateFormat.format(Date())}.md\"\n            val dirDoc = FileDoc.fromUri(treeUri, true)\n            val fileDoc = dirDoc.createFileIfNotExist(fileName).openOutputStream().getOrThrow()\n            fileDoc.use { outputStream ->\n                var name = \"\"\n                var author = \"\"\n                appDb.bookmarkDao.all.forEach {\n                    if (it.bookName != name && it.bookAuthor != author) {\n                        name = it.bookName\n                        author = it.bookAuthor\n                        outputStream.write(\"## ${it.bookName} ${it.bookAuthor}\\n\\n\".toByteArray())\n                    }\n                    outputStream.write(\"#### ${it.chapterName}\\n\\n\".toByteArray())\n                    outputStream.write(\"###### 原文\\n ${it.bookText}\\n\\n\".toByteArray())\n                    outputStream.write(\"###### 摘要\\n ${it.content}\\n\\n\".toByteArray())\n                }\n            }\n        }.onError {\n            AppLog.put(\"导出失败\\n${it.localizedMessage}\", it, true)\n        }.onSuccess {\n            context.toastOnUi(\"导出成功\")\n        }\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/book/bookmark/BookmarkAdapter.kt",
    "content": "package io.legado.app.ui.book.bookmark\n\nimport android.content.Context\nimport android.view.ViewGroup\nimport io.legado.app.base.adapter.ItemViewHolder\nimport io.legado.app.base.adapter.RecyclerAdapter\nimport io.legado.app.data.entities.Bookmark\nimport io.legado.app.databinding.ItemBookmarkBinding\nimport io.legado.app.utils.gone\nimport splitties.views.onClick\n\nclass BookmarkAdapter(context: Context, val callback: Callback) :\n    RecyclerAdapter<Bookmark, ItemBookmarkBinding>(context) {\n\n    override fun getViewBinding(parent: ViewGroup): ItemBookmarkBinding {\n        return ItemBookmarkBinding.inflate(inflater, parent, false)\n    }\n\n    override fun convert(\n        holder: ItemViewHolder,\n        binding: ItemBookmarkBinding,\n        item: Bookmark,\n        payloads: MutableList<Any>\n    ) {\n        binding.tvChapterName.text = item.chapterName\n        binding.tvBookText.gone(item.bookText.isEmpty())\n        binding.tvBookText.text = item.bookText\n        binding.tvContent.gone(item.content.isEmpty())\n        binding.tvContent.text = item.content\n    }\n\n    override fun registerListener(holder: ItemViewHolder, binding: ItemBookmarkBinding) {\n        binding.root.onClick {\n            getItemByLayoutPosition(holder.layoutPosition)?.let {\n                callback.onItemClick(it, holder.layoutPosition)\n            }\n        }\n    }\n\n    fun getHeaderText(position: Int): String {\n        return with(getItem(position)) {\n            \"${this?.bookName ?: \"\"}(${this?.bookAuthor ?: \"\"})\"\n        }\n    }\n\n    fun isItemHeader(position: Int): Boolean {\n        if (position == 0) return true\n        val lastItem = getItem(position - 1)\n        val curItem = getItem(position)\n        return !(lastItem?.bookName == curItem?.bookName\n                && lastItem?.bookAuthor == curItem?.bookAuthor)\n    }\n\n    interface Callback {\n\n        fun onItemClick(bookmark: Bookmark, position: Int)\n\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/book/bookmark/BookmarkDecoration.kt",
    "content": "package io.legado.app.ui.book.bookmark\n\nimport android.graphics.Canvas\nimport android.graphics.Paint\nimport android.graphics.Rect\nimport android.text.TextPaint\nimport android.view.View\nimport androidx.recyclerview.widget.LinearLayoutManager\nimport androidx.recyclerview.widget.RecyclerView\nimport io.legado.app.lib.theme.accentColor\nimport io.legado.app.lib.theme.backgroundColor\nimport io.legado.app.utils.dpToPx\nimport io.legado.app.utils.spToPx\nimport splitties.init.appCtx\nimport kotlin.math.min\n\nclass BookmarkDecoration(val adapter: BookmarkAdapter) : RecyclerView.ItemDecoration() {\n\n    private val headerLeft = 16f.dpToPx()\n    private val headerHeight = 32f.dpToPx()\n\n    private val headerPaint = Paint().apply {\n        color = appCtx.backgroundColor\n    }\n    private val textPaint = TextPaint().apply {\n        textSize = 16f.spToPx()\n        color = appCtx.accentColor\n        isAntiAlias = true\n    }\n    private val textRect = Rect()\n\n    override fun onDraw(c: Canvas, parent: RecyclerView, state: RecyclerView.State) {\n        val count = parent.childCount\n        for (i in 0 until count) {\n            val view = parent.getChildAt(i)\n            val position = parent.getChildLayoutPosition(view)\n            val isHeader = adapter.isItemHeader(position)\n            if (isHeader) {\n                c.drawRect(\n                    0f,\n                    view.top - headerHeight,\n                    parent.width.toFloat(),\n                    view.top.toFloat(),\n                    headerPaint\n                )\n                val headerText = adapter.getHeaderText(position)\n                textPaint.getTextBounds(headerText, 0, headerText.length, textRect)\n                c.drawText(\n                    headerText,\n                    headerLeft,\n                    (view.top - headerHeight) + headerHeight / 2 + textRect.height() / 2,\n                    textPaint\n                )\n            }\n        }\n    }\n\n    override fun onDrawOver(c: Canvas, parent: RecyclerView, state: RecyclerView.State) {\n        val position = (parent.layoutManager as LinearLayoutManager).findFirstVisibleItemPosition()\n        val view = parent.findViewHolderForAdapterPosition(position)?.itemView ?: return\n        val isHeader = adapter.isItemHeader(position + 1)\n        val headerText = adapter.getHeaderText(position)\n        if (isHeader) {\n            val bottom = min(headerHeight.toInt(), view.bottom)\n            c.drawRect(\n                0f,\n                view.top - headerHeight,\n                parent.width.toFloat(),\n                bottom.toFloat(),\n                headerPaint\n            )\n            textPaint.getTextBounds(headerText, 0, headerText.length, textRect)\n            c.drawText(\n                headerText,\n                headerLeft,\n                headerHeight / 2 + textRect.height() / 2 - (headerHeight - bottom),\n                textPaint\n            )\n        } else {\n            c.drawRect(\n                0f,\n                0f,\n                parent.width.toFloat(),\n                headerHeight,\n                headerPaint\n            )\n            textPaint.getTextBounds(headerText, 0, headerText.length, textRect)\n            c.drawText(\n                headerText,\n                headerLeft,\n                headerHeight / 2 + textRect.height() / 2,\n                textPaint\n            )\n        }\n        c.save()\n    }\n\n    override fun getItemOffsets(\n        outRect: Rect,\n        view: View,\n        parent: RecyclerView,\n        state: RecyclerView.State\n    ) {\n        val position = parent.getChildLayoutPosition(view)\n        val isHeader = adapter.isItemHeader(position)\n        if (isHeader) {\n            outRect.top = headerHeight.toInt()\n        }\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/book/bookmark/BookmarkDialog.kt",
    "content": "package io.legado.app.ui.book.bookmark\n\nimport android.os.Bundle\nimport android.view.View\nimport android.view.ViewGroup\nimport androidx.lifecycle.lifecycleScope\nimport io.legado.app.R\nimport io.legado.app.base.BaseDialogFragment\nimport io.legado.app.data.appDb\nimport io.legado.app.data.entities.Bookmark\nimport io.legado.app.databinding.DialogBookmarkBinding\nimport io.legado.app.lib.theme.primaryColor\nimport io.legado.app.utils.setLayout\nimport io.legado.app.utils.viewbindingdelegate.viewBinding\nimport io.legado.app.utils.visible\nimport kotlinx.coroutines.Dispatchers.IO\nimport kotlinx.coroutines.launch\nimport kotlinx.coroutines.withContext\n\nclass BookmarkDialog() : BaseDialogFragment(R.layout.dialog_bookmark, true) {\n\n    constructor(bookmark: Bookmark, editPos: Int = -1) : this() {\n        arguments = Bundle().apply {\n            putInt(\"editPos\", editPos)\n            putParcelable(\"bookmark\", bookmark)\n        }\n    }\n\n    private val binding by viewBinding(DialogBookmarkBinding::bind)\n\n    override fun onStart() {\n        super.onStart()\n        setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)\n    }\n\n    override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) {\n        binding.toolBar.setBackgroundColor(primaryColor)\n        val arguments = arguments ?: let {\n            dismiss()\n            return\n        }\n\n        @Suppress(\"DEPRECATION\")\n        val bookmark = arguments.getParcelable<Bookmark>(\"bookmark\")\n        bookmark ?: let {\n            dismiss()\n            return\n        }\n        val editPos = arguments.getInt(\"editPos\", -1)\n        binding.tvFooterLeft.visible(editPos >= 0)\n        binding.run {\n            tvChapterName.text = bookmark.chapterName\n            editBookText.setText(bookmark.bookText)\n            editContent.setText(bookmark.content)\n            tvCancel.setOnClickListener {\n                dismiss()\n            }\n            tvOk.setOnClickListener {\n                bookmark.bookText = editBookText.text?.toString() ?: \"\"\n                bookmark.content = editContent.text?.toString() ?: \"\"\n                lifecycleScope.launch {\n                    withContext(IO) {\n                        appDb.bookmarkDao.insert(bookmark)\n                    }\n                    dismiss()\n                }\n            }\n            tvFooterLeft.setOnClickListener {\n                lifecycleScope.launch {\n                    withContext(IO) {\n                        appDb.bookmarkDao.delete(bookmark)\n                    }\n                    dismiss()\n                }\n            }\n        }\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/book/cache/CacheActivity.kt",
    "content": "package io.legado.app.ui.book.cache\n\nimport android.annotation.SuppressLint\nimport android.os.Bundle\nimport android.view.Menu\nimport android.view.MenuItem\nimport android.view.View\nimport androidx.activity.viewModels\nimport androidx.appcompat.app.AlertDialog\nimport androidx.appcompat.widget.PopupMenu\nimport androidx.lifecycle.lifecycleScope\nimport androidx.recyclerview.widget.LinearLayoutManager\nimport com.google.android.material.textfield.TextInputLayout\nimport io.legado.app.R\nimport io.legado.app.base.VMBaseActivity\nimport io.legado.app.constant.AppConst.charsets\nimport io.legado.app.constant.AppLog\nimport io.legado.app.constant.EventBus\nimport io.legado.app.constant.IntentAction\nimport io.legado.app.data.AppDatabase\nimport io.legado.app.data.appDb\nimport io.legado.app.data.entities.Book\nimport io.legado.app.data.entities.BookChapter\nimport io.legado.app.data.entities.BookGroup\nimport io.legado.app.databinding.ActivityCacheBookBinding\nimport io.legado.app.databinding.DialogEditTextBinding\nimport io.legado.app.databinding.DialogSelectSectionExportBinding\nimport io.legado.app.help.book.getExportFileName\nimport io.legado.app.help.book.isAudio\nimport io.legado.app.help.book.tryParesExportFileName\nimport io.legado.app.help.config.AppConfig\nimport io.legado.app.lib.dialogs.SelectItem\nimport io.legado.app.lib.dialogs.alert\nimport io.legado.app.lib.dialogs.selector\nimport io.legado.app.model.CacheBook\nimport io.legado.app.service.ExportBookService\nimport io.legado.app.ui.about.AppLogDialog\nimport io.legado.app.ui.file.HandleFileContract\nimport io.legado.app.utils.ACache\nimport io.legado.app.utils.FileDoc\nimport io.legado.app.utils.applyNavigationBarPadding\nimport io.legado.app.utils.applyOpenTint\nimport io.legado.app.utils.applyTint\nimport io.legado.app.utils.checkWrite\nimport io.legado.app.utils.cnCompare\nimport io.legado.app.utils.enableCustomExport\nimport io.legado.app.utils.flowWithLifecycleAndDatabaseChange\nimport io.legado.app.utils.iconItemOnLongClick\nimport io.legado.app.utils.isContentScheme\nimport io.legado.app.utils.observeEvent\nimport io.legado.app.utils.setIconCompat\nimport io.legado.app.utils.showDialogFragment\nimport io.legado.app.utils.startService\nimport io.legado.app.utils.toastOnUi\nimport io.legado.app.utils.verificationField\nimport io.legado.app.utils.viewbindingdelegate.viewBinding\nimport kotlinx.coroutines.Dispatchers.IO\nimport kotlinx.coroutines.Job\nimport kotlinx.coroutines.flow.catch\nimport kotlinx.coroutines.flow.conflate\nimport kotlinx.coroutines.flow.flowOn\nimport kotlinx.coroutines.flow.map\nimport kotlinx.coroutines.launch\nimport kotlinx.coroutines.withContext\nimport splitties.init.appCtx\nimport kotlin.math.max\n\n/**\n * cache/download 缓存界面\n */\nclass CacheActivity : VMBaseActivity<ActivityCacheBookBinding, CacheViewModel>(),\n    PopupMenu.OnMenuItemClickListener,\n    CacheAdapter.CallBack {\n\n    override val binding by viewBinding(ActivityCacheBookBinding::inflate)\n    override val viewModel by viewModels<CacheViewModel>()\n\n    private val exportBookPathKey = \"exportBookPath\"\n    private val exportTypes = arrayListOf(\"txt\", \"epub\")\n    private val layoutManager by lazy { LinearLayoutManager(this) }\n    private val adapter by lazy { CacheAdapter(this, this) }\n    private var booksFlowJob: Job? = null\n    private var menu: Menu? = null\n    private val groupList: ArrayList<BookGroup> = arrayListOf()\n    private var groupId: Long = -1\n\n    private val exportDir = registerForActivityResult(HandleFileContract()) { result ->\n        var isReadyPath = false\n        var dirPath = \"\"\n        result.uri?.let { uri ->\n            if (uri.isContentScheme()) {\n                ACache.get().put(exportBookPathKey, uri.toString())\n                dirPath = uri.toString()\n                isReadyPath = true\n            } else {\n                uri.path?.let { path ->\n                    ACache.get().put(exportBookPathKey, path)\n                    dirPath = path\n                    isReadyPath = true\n                }\n            }\n        }\n        if (!isReadyPath) {\n            return@registerForActivityResult\n        }\n        if (enableCustomExport()) {// 启用自定义导出 and 导出类型为Epub\n            configExportSection(dirPath, result.requestCode)\n        } else {\n            startExport(dirPath, result.requestCode)\n        }\n    }\n\n    override fun onActivityCreated(savedInstanceState: Bundle?) {\n        groupId = intent.getLongExtra(\"groupId\", -1)\n        lifecycleScope.launch {\n            binding.titleBar.subtitle = withContext(IO) {\n                appDb.bookGroupDao.getByID(groupId)?.groupName\n                    ?: getString(R.string.no_group)\n            }\n        }\n        initRecyclerView()\n        initGroupData()\n        initBookData()\n    }\n\n    override fun onCompatCreateOptionsMenu(menu: Menu): Boolean {\n        menuInflater.inflate(R.menu.book_cache, menu)\n        menu.iconItemOnLongClick(R.id.menu_download) {\n            PopupMenu(this, it).apply {\n                inflate(R.menu.book_cache_download)\n                this.menu.applyOpenTint(this@CacheActivity)\n                setOnMenuItemClickListener(this@CacheActivity)\n            }.show()\n        }\n        return super.onCompatCreateOptionsMenu(menu)\n    }\n\n    override fun onPrepareOptionsMenu(menu: Menu): Boolean {\n        this.menu = menu\n        upMenu()\n        return super.onPrepareOptionsMenu(menu)\n    }\n\n    override fun onMenuOpened(featureId: Int, menu: Menu): Boolean {\n        menu.findItem(R.id.menu_enable_replace)?.isChecked = AppConfig.exportUseReplace\n        // 菜单打开时读取状态[enableCustomExport]\n        menu.findItem(R.id.menu_enable_custom_export)?.isChecked = AppConfig.enableCustomExport\n        menu.findItem(R.id.menu_export_no_chapter_name)?.isChecked = AppConfig.exportNoChapterName\n        menu.findItem(R.id.menu_export_web_dav)?.isChecked = AppConfig.exportToWebDav\n        menu.findItem(R.id.menu_export_pics_file)?.isChecked = AppConfig.exportPictureFile\n        menu.findItem(R.id.menu_parallel_export)?.isChecked = AppConfig.parallelExportBook\n        menu.findItem(R.id.menu_export_type)?.title =\n            \"${getString(R.string.export_type)}(${getTypeName()})\"\n        menu.findItem(R.id.menu_export_charset)?.title =\n            \"${getString(R.string.export_charset)}(${AppConfig.exportCharset})\"\n        return super.onMenuOpened(featureId, menu)\n    }\n\n    private fun upMenu() {\n        menu?.findItem(R.id.menu_book_group)?.subMenu?.let { subMenu ->\n            subMenu.removeGroup(R.id.menu_group)\n            groupList.forEach { bookGroup ->\n                subMenu.add(R.id.menu_group, bookGroup.order, Menu.NONE, bookGroup.groupName)\n            }\n        }\n    }\n\n    /**\n     * 菜单按下回调\n     */\n    override fun onCompatOptionsItemSelected(item: MenuItem): Boolean {\n        when (item.itemId) {\n            R.id.menu_download,\n            R.id.menu_download_after -> {\n                if (!CacheBook.isRun) sureCacheBook {\n                    adapter.getItems().forEach { book ->\n                        CacheBook.start(\n                            this@CacheActivity,\n                            book,\n                            book.durChapterIndex,\n                            book.lastChapterIndex\n                        )\n                    }\n                } else {\n                    CacheBook.stop(this@CacheActivity)\n                }\n            }\n\n            R.id.menu_download_all -> {\n                if (!CacheBook.isRun) sureCacheBook {\n                    adapter.getItems().forEach { book ->\n                        CacheBook.start(\n                            this@CacheActivity,\n                            book,\n                            0,\n                            book.lastChapterIndex\n                        )\n                    }\n                } else {\n                    CacheBook.stop(this@CacheActivity)\n                }\n            }\n\n            R.id.menu_export_all -> exportAll()\n            R.id.menu_enable_replace -> AppConfig.exportUseReplace = !item.isChecked\n            // 更改菜单状态[enableCustomExport]\n            R.id.menu_enable_custom_export -> AppConfig.enableCustomExport = !item.isChecked\n            R.id.menu_export_no_chapter_name -> AppConfig.exportNoChapterName = !item.isChecked\n            R.id.menu_export_web_dav -> AppConfig.exportToWebDav = !item.isChecked\n            R.id.menu_export_pics_file -> AppConfig.exportPictureFile = !item.isChecked\n            R.id.menu_parallel_export -> AppConfig.parallelExportBook = !item.isChecked\n            R.id.menu_export_folder -> {\n                selectExportFolder(-1)\n            }\n\n            R.id.menu_export_file_name -> alertExportFileName()\n            R.id.menu_export_type -> showExportTypeConfig()\n            R.id.menu_export_charset -> showCharsetConfig()\n            R.id.menu_log -> showDialogFragment<AppLogDialog>()\n            else -> if (item.groupId == R.id.menu_group) {\n                binding.titleBar.subtitle = item.title\n                groupId = appDb.bookGroupDao.getByName(item.title.toString())?.groupId ?: 0\n                initBookData()\n            }\n        }\n        return super.onCompatOptionsItemSelected(item)\n    }\n\n    override fun onMenuItemClick(item: MenuItem): Boolean {\n        return onCompatOptionsItemSelected(item)\n    }\n\n    private fun initRecyclerView() {\n        binding.recyclerView.layoutManager = layoutManager\n        binding.recyclerView.adapter = adapter\n        binding.recyclerView.applyNavigationBarPadding()\n    }\n\n    private fun initBookData() {\n        booksFlowJob?.cancel()\n        booksFlowJob = lifecycleScope.launch {\n            appDb.bookDao.flowByGroup(groupId).map { books ->\n                val booksDownload = books.filter {\n                    !it.isAudio\n                }\n                when (AppConfig.getBookSortByGroupId(groupId)) {\n                    1 -> booksDownload.sortedByDescending { it.latestChapterTime }\n                    2 -> booksDownload.sortedWith { o1, o2 ->\n                        o1.name.cnCompare(o2.name)\n                    }\n\n                    3 -> booksDownload.sortedBy { it.order }\n                    4 -> booksDownload.sortedByDescending {\n                        max(it.latestChapterTime, it.durChapterTime)\n                    }\n\n                    else -> booksDownload.sortedByDescending { it.durChapterTime }\n                }\n            }.flowWithLifecycleAndDatabaseChange(\n                lifecycle, table = AppDatabase.BOOK_TABLE_NAME\n            ).catch {\n                AppLog.put(\"缓存管理界面获取书籍列表失败\\n${it.localizedMessage}\", it)\n            }.flowOn(IO).conflate().collect { books ->\n                adapter.setItems(books)\n                viewModel.loadCacheFiles(books)\n            }\n        }\n    }\n\n    @SuppressLint(\"NotifyDataSetChanged\")\n    private fun initGroupData() {\n        lifecycleScope.launch {\n            appDb.bookGroupDao.flowAll().catch {\n                AppLog.put(\"缓存管理界面获取分组数据失败\\n${it.localizedMessage}\", it)\n            }.flowOn(IO).conflate().collect {\n                groupList.clear()\n                groupList.addAll(it)\n                adapter.notifyDataSetChanged()\n                upMenu()\n            }\n        }\n    }\n\n    private fun notifyItemChanged(bookUrl: String) {\n        kotlin.runCatching {\n            adapter.getItems().forEachIndexed { index, book ->\n                if (bookUrl == book.bookUrl) {\n                    adapter.notifyItemChanged(index, true)\n                    return\n                }\n            }\n        }\n    }\n\n    override fun observeLiveBus() {\n        viewModel.upAdapterLiveData.observe(this) {\n            notifyItemChanged(it)\n        }\n        observeEvent<String>(EventBus.EXPORT_BOOK) {\n            notifyItemChanged(it)\n        }\n        observeEvent<String>(EventBus.UP_DOWNLOAD) {\n            notifyItemChanged(it)\n        }\n        observeEvent<String>(EventBus.UP_DOWNLOAD_STATE) {\n            if (!CacheBook.isRun) {\n                menu?.findItem(R.id.menu_download)?.let { item ->\n                    item.setIconCompat(R.drawable.ic_play_24dp)\n                    item.setTitle(R.string.download_start)\n                }\n                menu?.applyTint(this)\n            } else {\n                menu?.findItem(R.id.menu_download)?.let { item ->\n                    item.setIconCompat(R.drawable.ic_stop_black_24dp)\n                    item.setTitle(R.string.stop)\n                }\n                menu?.applyTint(this)\n            }\n        }\n        observeEvent<Pair<Book, BookChapter>>(EventBus.SAVE_CONTENT) { (book, chapter) ->\n            viewModel.cacheChapters[book.bookUrl]?.add(chapter.url)\n            notifyItemChanged(book.bookUrl)\n        }\n    }\n\n    override fun export(position: Int) {\n        val path = ACache.get().getAsString(exportBookPathKey)\n        lifecycleScope.launch {\n            if (path.isNullOrEmpty() ||\n                withContext(IO) { !FileDoc.fromDir(path).checkWrite() }\n            ) {\n                selectExportFolder(position)\n            } else if (enableCustomExport()) {// 启用自定义导出 and 导出类型为Epub\n                configExportSection(path, position)\n            } else {\n                startExport(path, position)\n            }\n        }\n    }\n\n    private fun exportAll() {\n        val path = ACache.get().getAsString(exportBookPathKey)\n        if (path.isNullOrEmpty()) {\n            selectExportFolder(-10)\n        } else {\n            startExport(path, -10)\n        }\n    }\n\n    /**\n     * 配置自定义导出对话框\n     *\n     * @param path  导出路径\n     * @param position  book位置\n     * @author Discut\n     * @since 1.0.0\n     */\n    private fun configExportSection(path: String, position: Int) {\n\n        val alertBinding = DialogSelectSectionExportBinding.inflate(layoutInflater)\n            .apply {\n                fun verifyExportFileNameJsStr(js: String): Boolean {\n                    return tryParesExportFileName(js) && etEpubFilename.text.toString()\n                        .isNotEmpty()\n                }\n\n                fun enableLyEtEpubFilenameIcon() {\n                    lyEtEpubFilename.endIconMode = TextInputLayout.END_ICON_CUSTOM\n                    lyEtEpubFilename.setEndIconOnClickListener {\n                        adapter.getItem(position)?.run {\n                            lyEtEpubFilename.helperText =\n                                if (verifyExportFileNameJsStr(etEpubFilename.text.toString()))\n                                    \"${resources.getString(R.string.result_analyzed)}: ${\n                                        getExportFileName(\n                                            \"epub\",\n                                            1,\n                                            etEpubFilename.text.toString()\n                                        )\n                                    }\"\n                                else \"Error\"\n                        } ?: run {\n                            lyEtEpubFilename.helperText = \"Error\"\n                            AppLog.put(\"未找到书籍，position is $position\")\n                        }\n                    }\n                }\n                etEpubSize.setText(\"1\")\n                // lyEtEpubFilename.endIconMode = TextInputLayout.END_ICON_NONE\n                etEpubFilename.text?.append(AppConfig.episodeExportFileName)\n                // 存储解析文件名的jsStr\n                etEpubFilename.let {\n                    it.setOnFocusChangeListener { _, hasFocus ->\n                        if (hasFocus)\n                            return@setOnFocusChangeListener\n                        it.text?.run {\n                            if (verifyExportFileNameJsStr(toString())) {\n                                AppConfig.episodeExportFileName = toString()\n                            }\n                        }\n                    }\n                }\n                tvAllExport.setOnClickListener {\n                    cbAllExport.callOnClick()\n                }\n                tvSelectExport.setOnClickListener {\n                    cbSelectExport.callOnClick()\n                }\n                cbSelectExport.onCheckedChangeListener = { _, isChecked ->\n                    if (isChecked) {\n                        etEpubSize.isEnabled = true\n                        etInputScope.isEnabled = true\n                        etEpubFilename.isEnabled = true\n                        enableLyEtEpubFilenameIcon()\n                        cbAllExport.isChecked = false\n                    }\n                }\n                cbAllExport.onCheckedChangeListener = { _, isChecked ->\n                    if (isChecked) {\n                        etEpubSize.isEnabled = false\n                        etInputScope.isEnabled = false\n                        etEpubFilename.isEnabled = false\n                        lyEtEpubFilename.endIconMode = TextInputLayout.END_ICON_NONE\n                        cbSelectExport.isChecked = false\n                    }\n                }\n\n                etInputScope.onFocusChangeListener =\n                    View.OnFocusChangeListener { _, hasFocus ->\n                        if (hasFocus) {\n                            etInputScope.hint = \"1-5,8,10-18\"\n                        } else {\n                            etInputScope.hint = \"\"\n                        }\n                    }\n\n                // 默认选择自定义导出\n                cbSelectExport.callOnClick()\n            }\n        val alertDialog = alert(titleResource = R.string.select_section_export) {\n            customView { alertBinding.root }\n            positiveButton(R.string.ok)\n            cancelButton()\n        }\n        alertDialog.getButton(AlertDialog.BUTTON_POSITIVE).setOnClickListener {\n            alertBinding.apply {\n                if (cbAllExport.isChecked) {\n                    startExport(path, position)\n                    alertDialog.hide()\n                    return@apply\n                }\n                val epubScope = etInputScope.text.toString()\n                if (!verificationField(epubScope)) {\n                    etInputScope.error = appCtx.getString(R.string.error_scope_input)//\"请输入正确的范围\"\n                    return@apply\n                }\n                etInputScope.error = null\n                val epubSize = etEpubSize.text.toString().toIntOrNull() ?: 1\n                adapter.getItem(position)?.let { book ->\n                    startService<ExportBookService> {\n                        action = IntentAction.start\n                        putExtra(\"bookUrl\", book.bookUrl)\n                        putExtra(\"exportType\", \"epub\")\n                        putExtra(\"exportPath\", path)\n                        putExtra(\"epubSize\", epubSize)\n                        putExtra(\"epubScope\", epubScope)\n                    }\n                }\n                alertDialog.hide()\n            }\n\n        }\n    }\n\n    private fun selectExportFolder(exportPosition: Int) {\n        val default = arrayListOf<SelectItem<Int>>()\n        val path = ACache.get().getAsString(exportBookPathKey)\n        if (!path.isNullOrEmpty()) {\n            default.add(SelectItem(path, -1))\n        }\n        exportDir.launch {\n            otherActions = default\n            requestCode = exportPosition\n        }\n    }\n\n    private fun startExport(path: String, exportPosition: Int) {\n        val exportType = when (AppConfig.exportType) {\n            1 -> \"epub\"\n            else -> \"txt\"\n        }\n        if (exportPosition == -10) {\n            if (adapter.getItems().isNotEmpty()) {\n                adapter.getItems().forEach { book ->\n                    startService<ExportBookService> {\n                        action = IntentAction.start\n                        putExtra(\"bookUrl\", book.bookUrl)\n                        putExtra(\"exportType\", exportType)\n                        putExtra(\"exportPath\", path)\n                    }\n                }\n            } else {\n                toastOnUi(R.string.no_book)\n            }\n        } else if (exportPosition >= 0) {\n            adapter.getItem(exportPosition)?.let { book ->\n                startService<ExportBookService> {\n                    action = IntentAction.start\n                    putExtra(\"bookUrl\", book.bookUrl)\n                    putExtra(\"exportType\", exportType)\n                    putExtra(\"exportPath\", path)\n                }\n            }\n        }\n    }\n\n    @SuppressLint(\"SetTextI18n\")\n    private fun alertExportFileName() {\n        alert(R.string.export_file_name) {\n            val message = \"Variable: name, author.\"\n            setMessage(message)\n            val alertBinding = DialogEditTextBinding.inflate(layoutInflater).apply {\n                editView.hint = \"file name js\"\n                editView.setText(AppConfig.bookExportFileName)\n            }\n            customView { alertBinding.root }\n            okButton {\n                AppConfig.bookExportFileName = alertBinding.editView.text?.toString()\n            }\n            cancelButton()\n        }\n    }\n\n    private fun getTypeName(): String {\n        return exportTypes.getOrElse(AppConfig.exportType) {\n            exportTypes[0]\n        }\n    }\n\n    private fun showExportTypeConfig() {\n        selector(R.string.export_type, exportTypes) { _, i ->\n            AppConfig.exportType = i\n        }\n    }\n\n    private fun showCharsetConfig() {\n        alert(R.string.set_charset) {\n            val alertBinding = DialogEditTextBinding.inflate(layoutInflater).apply {\n                editView.hint = \"charset name\"\n                editView.setFilterValues(charsets)\n                editView.setText(AppConfig.exportCharset)\n            }\n            customView { alertBinding.root }\n            okButton {\n                AppConfig.exportCharset = alertBinding.editView.text?.toString() ?: \"UTF-8\"\n            }\n            cancelButton()\n        }\n    }\n\n    private fun sureCacheBook(action: () -> Unit) {\n        alert(R.string.draw) {\n            setMessage(R.string.sure_cache_book)\n            noButton()\n            yesButton {\n                action.invoke()\n            }\n        }\n    }\n\n    override val cacheChapters: HashMap<String, HashSet<String>>\n        get() = viewModel.cacheChapters\n\n    override fun exportProgress(bookUrl: String): Int? {\n        return ExportBookService.exportProgress[bookUrl]\n    }\n\n    override fun exportMsg(bookUrl: String): String? {\n        return ExportBookService.exportMsg[bookUrl]\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/book/cache/CacheAdapter.kt",
    "content": "package io.legado.app.ui.book.cache\n\nimport android.content.Context\nimport android.view.ViewGroup\nimport android.widget.ImageView\nimport android.widget.ProgressBar\nimport android.widget.TextView\nimport androidx.recyclerview.widget.DiffUtil\nimport io.legado.app.R\nimport io.legado.app.base.adapter.DiffRecyclerAdapter\nimport io.legado.app.base.adapter.ItemViewHolder\nimport io.legado.app.data.entities.Book\nimport io.legado.app.databinding.ItemDownloadBinding\nimport io.legado.app.help.book.isLocal\nimport io.legado.app.model.CacheBook\nimport io.legado.app.utils.gone\nimport io.legado.app.utils.visible\n\nclass CacheAdapter(context: Context, private val callBack: CallBack) :\n    DiffRecyclerAdapter<Book, ItemDownloadBinding>(context) {\n\n    override val diffItemCallback: DiffUtil.ItemCallback<Book>\n        get() = object : DiffUtil.ItemCallback<Book>() {\n            override fun areItemsTheSame(oldItem: Book, newItem: Book): Boolean {\n                return oldItem.bookUrl == newItem.bookUrl\n            }\n\n            override fun areContentsTheSame(oldItem: Book, newItem: Book): Boolean {\n                return oldItem.name == newItem.name\n                        && oldItem.author == newItem.author\n            }\n\n        }\n\n    override fun getViewBinding(parent: ViewGroup): ItemDownloadBinding {\n        return ItemDownloadBinding.inflate(inflater, parent, false)\n    }\n\n    override fun convert(\n        holder: ItemViewHolder,\n        binding: ItemDownloadBinding,\n        item: Book,\n        payloads: MutableList<Any>\n    ) {\n        binding.run {\n            if (payloads.isEmpty()) {\n                tvName.text = item.name\n                tvAuthor.text = context.getString(R.string.author_show, item.getRealAuthor())\n                if (item.isLocal) {\n                    tvDownload.setText(R.string.local_book)\n                } else {\n                    val cs = callBack.cacheChapters[item.bookUrl]\n                    if (cs == null) {\n                        tvDownload.setText(R.string.loading)\n                    } else {\n                        tvDownload.text =\n                            context.getString(\n                                R.string.download_count,\n                                cs.size,\n                                item.totalChapterNum\n                            )\n                    }\n                }\n            } else {\n                if (item.isLocal) {\n                    tvDownload.setText(R.string.local_book)\n                } else {\n                    val cacheSize = callBack.cacheChapters[item.bookUrl]?.size ?: 0\n                    tvDownload.text =\n                        context.getString(R.string.download_count, cacheSize, item.totalChapterNum)\n                }\n            }\n            upDownloadIv(ivDownload, item)\n            upExportInfo(tvMsg, progressExport, item)\n        }\n    }\n\n    override fun registerListener(holder: ItemViewHolder, binding: ItemDownloadBinding) {\n        binding.run {\n            ivDownload.setOnClickListener {\n                getItem(holder.layoutPosition)?.let { book ->\n                    CacheBook.cacheBookMap[book.bookUrl]?.let {\n                        if (!it.isStop()) {\n                            CacheBook.remove(context, book.bookUrl)\n                        } else {\n                            CacheBook.start(context, book, 0, book.lastChapterIndex)\n                        }\n                    } ?: let {\n                        CacheBook.start(context, book, 0, book.lastChapterIndex)\n                    }\n                }\n            }\n            tvExport.setOnClickListener {\n                callBack.export(holder.layoutPosition)\n            }\n        }\n    }\n\n    private fun upDownloadIv(iv: ImageView, book: Book) {\n        if (book.isLocal) {\n            iv.gone()\n        } else {\n            iv.visible()\n            CacheBook.cacheBookMap[book.bookUrl]?.let {\n                if (!it.isStop()) {\n                    iv.setImageResource(R.drawable.ic_stop_black_24dp)\n                } else {\n                    iv.setImageResource(R.drawable.ic_play_24dp)\n                }\n            } ?: let {\n                iv.setImageResource(R.drawable.ic_play_24dp)\n            }\n        }\n    }\n\n    private fun upExportInfo(msgView: TextView, progressView: ProgressBar, book: Book) {\n        val msg = callBack.exportMsg(book.bookUrl)\n        if (msg != null) {\n            msgView.text = msg\n            msgView.visible()\n            progressView.gone()\n            return\n        }\n        msgView.gone()\n        val progress = callBack.exportProgress(book.bookUrl)\n        if (progress != null) {\n            progressView.max = book.totalChapterNum\n            progressView.progress = progress\n            progressView.visible()\n            return\n        }\n        progressView.gone()\n    }\n\n    interface CallBack {\n        val cacheChapters: HashMap<String, HashSet<String>>\n        fun export(position: Int)\n        fun exportProgress(bookUrl: String): Int?\n        fun exportMsg(bookUrl: String): String?\n    }\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/book/cache/CacheViewModel.kt",
    "content": "package io.legado.app.ui.book.cache\n\nimport android.app.Application\nimport androidx.lifecycle.MutableLiveData\nimport io.legado.app.base.BaseViewModel\nimport io.legado.app.data.appDb\nimport io.legado.app.data.entities.Book\nimport io.legado.app.help.book.BookHelp\nimport io.legado.app.help.book.isLocal\nimport io.legado.app.help.coroutine.Coroutine\nimport io.legado.app.utils.sendValue\nimport kotlinx.coroutines.ensureActive\nimport kotlin.collections.set\n\n\nclass CacheViewModel(application: Application) : BaseViewModel(application) {\n    val upAdapterLiveData = MutableLiveData<String>()\n\n    private var loadChapterCoroutine: Coroutine<Unit>? = null\n    val cacheChapters = hashMapOf<String, HashSet<String>>()\n\n    fun loadCacheFiles(books: List<Book>) {\n        loadChapterCoroutine?.cancel()\n        loadChapterCoroutine = execute {\n            books.forEach { book ->\n                if (!book.isLocal && !cacheChapters.contains(book.bookUrl)) {\n                    val chapterCaches = hashSetOf<String>()\n                    val cacheNames = BookHelp.getChapterFiles(book)\n                    if (cacheNames.isNotEmpty()) {\n                        appDb.bookChapterDao.getChapterList(book.bookUrl).also {\n                            book.totalChapterNum = it.size\n                        }.forEach { chapter ->\n                            if (cacheNames.contains(chapter.getFileName()) || chapter.isVolume) {\n                                chapterCaches.add(chapter.url)\n                            }\n                        }\n                    }\n                    cacheChapters[book.bookUrl] = chapterCaches\n                    upAdapterLiveData.sendValue(book.bookUrl)\n                }\n                ensureActive()\n            }\n        }\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/book/changecover/ChangeCoverDialog.kt",
    "content": "package io.legado.app.ui.book.changecover\n\nimport android.os.Bundle\nimport android.view.MenuItem\nimport android.view.View\nimport android.view.ViewGroup\nimport androidx.appcompat.widget.Toolbar\nimport androidx.fragment.app.viewModels\nimport androidx.lifecycle.Lifecycle.State.STARTED\nimport androidx.lifecycle.lifecycleScope\nimport androidx.recyclerview.widget.GridLayoutManager\nimport io.legado.app.R\nimport io.legado.app.base.BaseDialogFragment\nimport io.legado.app.databinding.DialogChangeCoverBinding\nimport io.legado.app.lib.theme.primaryColor\nimport io.legado.app.utils.applyTint\nimport io.legado.app.utils.setLayout\nimport io.legado.app.utils.viewbindingdelegate.viewBinding\nimport kotlinx.coroutines.delay\nimport kotlinx.coroutines.flow.conflate\nimport kotlinx.coroutines.flow.first\nimport kotlinx.coroutines.launch\n\n/**\n * 换封面\n */\nclass ChangeCoverDialog() : BaseDialogFragment(R.layout.dialog_change_cover),\n    Toolbar.OnMenuItemClickListener,\n    CoverAdapter.CallBack {\n\n    constructor(name: String, author: String) : this() {\n        arguments = Bundle().apply {\n            putString(\"name\", name)\n            putString(\"author\", author)\n        }\n    }\n\n    private val binding by viewBinding(DialogChangeCoverBinding::bind)\n    private val callBack: CallBack? get() = activity as? CallBack\n    private val viewModel: ChangeCoverViewModel by viewModels()\n    private val adapter by lazy { CoverAdapter(requireContext(), this) }\n\n    private val startStopMenuItem: MenuItem?\n        get() = binding.toolBar.menu.findItem(R.id.menu_start_stop)\n\n    override fun onStart() {\n        super.onStart()\n        setLayout(1f, ViewGroup.LayoutParams.MATCH_PARENT)\n    }\n\n    override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) {\n        binding.toolBar.setBackgroundColor(primaryColor)\n        binding.toolBar.setTitle(R.string.change_cover_source)\n        viewModel.initData(arguments)\n        initMenu()\n        initView()\n        initData()\n    }\n\n    private fun initMenu() {\n        binding.toolBar.inflateMenu(R.menu.change_cover)\n        binding.toolBar.menu.applyTint(requireContext())\n        binding.toolBar.setOnMenuItemClickListener(this)\n    }\n\n    private fun initView() {\n        binding.recyclerView.layoutManager = GridLayoutManager(requireContext(), 3)\n        binding.recyclerView.adapter = adapter\n    }\n\n    private fun initData() {\n        lifecycleScope.launch {\n            lifecycle.currentStateFlow.first { it.isAtLeast(STARTED) }\n            viewModel.dataFlow.conflate().collect {\n                adapter.setItems(it)\n                delay(1000)\n            }\n        }\n    }\n\n    override fun observeLiveBus() {\n        super.observeLiveBus()\n        viewModel.searchStateData.observe(viewLifecycleOwner) {\n            binding.refreshProgressBar.isAutoLoading = it\n            if (it) {\n                startStopMenuItem?.let { item ->\n                    item.setIcon(R.drawable.ic_stop_black_24dp)\n                    item.setTitle(R.string.stop)\n                }\n            } else {\n                startStopMenuItem?.let { item ->\n                    item.setIcon(R.drawable.ic_refresh_black_24dp)\n                    item.setTitle(R.string.refresh)\n                }\n            }\n            binding.toolBar.menu.applyTint(requireContext())\n        }\n\n    }\n\n    override fun onMenuItemClick(item: MenuItem?): Boolean {\n        when (item?.itemId) {\n            R.id.menu_start_stop -> viewModel.startOrStopSearch()\n        }\n        return false\n    }\n\n    override fun changeTo(coverUrl: String) {\n        callBack?.coverChangeTo(coverUrl)\n        dismissAllowingStateLoss()\n    }\n\n    interface CallBack {\n        fun coverChangeTo(coverUrl: String)\n    }\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/book/changecover/ChangeCoverViewModel.kt",
    "content": "package io.legado.app.ui.book.changecover\n\nimport android.app.Application\nimport android.os.Bundle\nimport androidx.lifecycle.MutableLiveData\nimport androidx.lifecycle.viewModelScope\nimport io.legado.app.base.BaseViewModel\nimport io.legado.app.constant.AppConst\nimport io.legado.app.constant.AppLog\nimport io.legado.app.constant.AppPattern\nimport io.legado.app.data.appDb\nimport io.legado.app.data.entities.BookSource\nimport io.legado.app.data.entities.BookSourcePart\nimport io.legado.app.data.entities.SearchBook\nimport io.legado.app.help.config.AppConfig\nimport io.legado.app.model.webBook.WebBook\nimport io.legado.app.utils.mapParallelSafe\nimport kotlinx.coroutines.Dispatchers.IO\nimport kotlinx.coroutines.ExecutorCoroutineDispatcher\nimport kotlinx.coroutines.Job\nimport kotlinx.coroutines.asCoroutineDispatcher\nimport kotlinx.coroutines.channels.awaitClose\nimport kotlinx.coroutines.flow.callbackFlow\nimport kotlinx.coroutines.flow.catch\nimport kotlinx.coroutines.flow.collect\nimport kotlinx.coroutines.flow.flow\nimport kotlinx.coroutines.flow.flowOn\nimport kotlinx.coroutines.flow.onCompletion\nimport kotlinx.coroutines.flow.onStart\nimport kotlinx.coroutines.launch\nimport kotlinx.coroutines.withTimeout\nimport java.util.Collections\nimport java.util.concurrent.Executors\nimport kotlin.math.min\n\nclass ChangeCoverViewModel(application: Application) : BaseViewModel(application) {\n    private val threadCount = AppConfig.threadCount\n    private var searchPool: ExecutorCoroutineDispatcher? = null\n    private var searchSuccess: ((SearchBook) -> Unit)? = null\n    private var upAdapter: (() -> Unit)? = null\n    private var bookSourceParts = arrayListOf<BookSourcePart>()\n    private val defaultCover by lazy {\n        listOf(\n            SearchBook(\n                originName = \"默认封面\",\n                name = name,\n                author = author,\n                coverUrl = \"use_default_cover\"\n            )\n        )\n    }\n    private var task: Job? = null\n    val searchStateData = MutableLiveData<Boolean>()\n    var name: String = \"\"\n    var author: String = \"\"\n    val searchBooks: MutableList<SearchBook> = Collections.synchronizedList(arrayListOf())\n    val dataFlow = callbackFlow {\n\n        searchSuccess = { searchBook ->\n            if (!searchBooks.contains(searchBook)) {\n                searchBooks.add(searchBook)\n                trySend(defaultCover + searchBooks.sortedBy { it.originOrder })\n            }\n        }\n\n        upAdapter = {\n            trySend(defaultCover + searchBooks.sortedBy { it.originOrder })\n        }\n\n        appDb.searchBookDao.getEnableHasCover(name, author).let {\n            searchBooks.addAll(it)\n            trySend(defaultCover + searchBooks.toList())\n        }\n\n        if (searchBooks.size <= 1) {\n            startSearch()\n        }\n\n        awaitClose {\n            searchBooks.clear()\n            searchSuccess = null\n            upAdapter = null\n        }\n    }.flowOn(IO)\n\n    fun initData(arguments: Bundle?) {\n        arguments?.let { bundle ->\n            bundle.getString(\"name\")?.let {\n                name = it\n            }\n            bundle.getString(\"author\")?.let {\n                author = it.replace(AppPattern.authorRegex, \"\")\n            }\n        }\n    }\n\n    private fun initSearchPool() {\n        searchPool = Executors\n            .newFixedThreadPool(min(threadCount, AppConst.MAX_THREAD)).asCoroutineDispatcher()\n    }\n\n    private fun startSearch() {\n        execute {\n            stopSearch()\n            searchBooks.clear()\n            upAdapter?.invoke()\n            bookSourceParts.clear()\n            bookSourceParts.addAll(appDb.bookSourceDao.allEnabledPart)\n            initSearchPool()\n            search()\n        }\n    }\n\n    private fun search() {\n        task = viewModelScope.launch(searchPool!!) {\n            flow {\n                for (bs in bookSourceParts) {\n                    bs.getBookSource()?.let {\n                        emit(it)\n                    }\n                }\n            }.onStart {\n                searchStateData.postValue(true)\n            }.mapParallelSafe(threadCount) {\n                withTimeout(60000L) {\n                    search(it)\n                }\n            }.onCompletion {\n                searchStateData.postValue(false)\n            }.catch {\n                AppLog.put(\"封面换源搜索出错\\n${it.localizedMessage}\", it)\n            }.collect()\n        }\n    }\n\n    private suspend fun search(source: BookSource) {\n        if (source.getSearchRule().coverUrl.isNullOrBlank()) {\n            return\n        }\n        val searchBook = WebBook.searchBookAwait(\n            source, name,\n            shouldBreak = { it > 0 }).firstOrNull() ?: return\n        if (searchBook.name == name && searchBook.author == author\n            && !searchBook.coverUrl.isNullOrEmpty()\n        ) {\n            appDb.searchBookDao.insert(searchBook)\n            searchSuccess?.invoke(searchBook)\n        }\n    }\n\n    fun startOrStopSearch() {\n        if (task == null || !task!!.isActive) {\n            startSearch()\n        } else {\n            stopSearch()\n        }\n    }\n\n    private fun stopSearch() {\n        task?.cancel()\n        searchPool?.close()\n        searchStateData.postValue(false)\n    }\n\n    override fun onCleared() {\n        super.onCleared()\n        searchPool?.close()\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/book/changecover/CoverAdapter.kt",
    "content": "package io.legado.app.ui.book.changecover\n\nimport android.content.Context\nimport android.view.ViewGroup\nimport androidx.recyclerview.widget.DiffUtil\nimport io.legado.app.base.adapter.DiffRecyclerAdapter\nimport io.legado.app.base.adapter.ItemViewHolder\nimport io.legado.app.data.entities.SearchBook\nimport io.legado.app.databinding.ItemCoverBinding\n\n\nclass CoverAdapter(context: Context, val callBack: CallBack) :\n    DiffRecyclerAdapter<SearchBook, ItemCoverBinding>(context) {\n\n    override val diffItemCallback: DiffUtil.ItemCallback<SearchBook>\n        get() = object : DiffUtil.ItemCallback<SearchBook>() {\n            override fun areItemsTheSame(oldItem: SearchBook, newItem: SearchBook): Boolean {\n                return oldItem.bookUrl == newItem.bookUrl\n            }\n\n            override fun areContentsTheSame(oldItem: SearchBook, newItem: SearchBook): Boolean {\n                return oldItem.originName == newItem.originName\n                        && oldItem.coverUrl == newItem.coverUrl\n            }\n\n        }\n\n    override fun getViewBinding(parent: ViewGroup): ItemCoverBinding {\n        return ItemCoverBinding.inflate(inflater, parent, false)\n    }\n\n    override fun convert(\n        holder: ItemViewHolder,\n        binding: ItemCoverBinding,\n        item: SearchBook,\n        payloads: MutableList<Any>\n    ) = binding.run {\n        ivCover.load(item.coverUrl, item.name, item.author, false, item.origin)\n        tvSource.text = item.originName\n    }\n\n    override fun registerListener(holder: ItemViewHolder, binding: ItemCoverBinding) {\n        holder.itemView.apply {\n            setOnClickListener {\n                getItem(holder.layoutPosition)?.let {\n                    callBack.changeTo(it.coverUrl ?: \"\")\n                }\n            }\n        }\n    }\n\n    interface CallBack {\n        fun changeTo(coverUrl: String)\n    }\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/book/changesource/ChangeBookSourceAdapter.kt",
    "content": "package io.legado.app.ui.book.changesource\n\nimport android.content.Context\nimport android.os.Bundle\nimport android.view.View\nimport android.view.ViewGroup\nimport androidx.appcompat.widget.PopupMenu\nimport androidx.core.graphics.drawable.DrawableCompat\nimport androidx.core.view.isVisible\nimport androidx.recyclerview.widget.DiffUtil\nimport io.legado.app.R\nimport io.legado.app.base.adapter.DiffRecyclerAdapter\nimport io.legado.app.base.adapter.ItemViewHolder\nimport io.legado.app.data.entities.SearchBook\nimport io.legado.app.databinding.ItemChangeSourceBinding\nimport io.legado.app.help.config.AppConfig\nimport io.legado.app.lib.dialogs.alert\nimport io.legado.app.utils.getCompatColor\nimport io.legado.app.utils.gone\nimport io.legado.app.utils.invisible\nimport io.legado.app.utils.visible\nimport splitties.init.appCtx\nimport splitties.views.onLongClick\n\n\nclass ChangeBookSourceAdapter(\n    context: Context,\n    val viewModel: ChangeBookSourceViewModel,\n    val callBack: CallBack\n) : DiffRecyclerAdapter<SearchBook, ItemChangeSourceBinding>(context) {\n\n    override val diffItemCallback = object : DiffUtil.ItemCallback<SearchBook>() {\n        override fun areItemsTheSame(oldItem: SearchBook, newItem: SearchBook): Boolean {\n            return oldItem.bookUrl == newItem.bookUrl\n        }\n\n        override fun areContentsTheSame(oldItem: SearchBook, newItem: SearchBook): Boolean {\n            return oldItem.originName == newItem.originName\n                    && oldItem.getDisplayLastChapterTitle() == newItem.getDisplayLastChapterTitle()\n                    && oldItem.chapterWordCountText == newItem.chapterWordCountText\n                    && oldItem.respondTime == newItem.respondTime\n        }\n\n    }\n\n    override fun getViewBinding(parent: ViewGroup): ItemChangeSourceBinding {\n        return ItemChangeSourceBinding.inflate(inflater, parent, false)\n    }\n\n    override fun convert(\n        holder: ItemViewHolder,\n        binding: ItemChangeSourceBinding,\n        item: SearchBook,\n        payloads: MutableList<Any>\n    ) {\n        binding.apply {\n            if (payloads.isEmpty()) {\n                tvOrigin.text = item.originName\n                tvAuthor.text = item.author\n                tvLast.text = item.getDisplayLastChapterTitle()\n                tvCurrentChapterWordCount.text = item.chapterWordCountText\n                tvRespondTime.text = context.getString(R.string.respondTime, item.respondTime)\n                if (callBack.oldBookUrl == item.bookUrl) {\n                    ivChecked.visible()\n                } else {\n                    ivChecked.invisible()\n                }\n            } else {\n                for (i in payloads.indices) {\n                    val bundle = payloads[i] as Bundle\n                    bundle.keySet().forEach {\n                        when (it) {\n                            \"name\" -> tvOrigin.text = item.originName\n                            \"latest\" -> tvLast.text = item.getDisplayLastChapterTitle()\n                            \"upCurSource\" -> if (callBack.oldBookUrl == item.bookUrl) {\n                                ivChecked.visible()\n                            } else {\n                                ivChecked.invisible()\n                            }\n                        }\n                    }\n                }\n            }\n            val score = callBack.getBookScore(item)\n            if (score > 0) {\n                binding.ivBad.gone()\n                binding.ivGood.visible()\n                DrawableCompat.setTint(\n                    binding.ivGood.drawable,\n                    appCtx.getCompatColor(R.color.md_red_A200)\n                )\n                DrawableCompat.setTint(\n                    binding.ivBad.drawable,\n                    appCtx.getCompatColor(R.color.md_blue_100)\n                )\n            } else if (score < 0) {\n                binding.ivGood.gone()\n                binding.ivBad.visible()\n                DrawableCompat.setTint(\n                    binding.ivGood.drawable,\n                    appCtx.getCompatColor(R.color.md_red_100)\n                )\n                DrawableCompat.setTint(\n                    binding.ivBad.drawable,\n                    appCtx.getCompatColor(R.color.md_blue_A200)\n                )\n            } else {\n                binding.ivGood.visible()\n                binding.ivBad.visible()\n                DrawableCompat.setTint(\n                    binding.ivGood.drawable,\n                    appCtx.getCompatColor(R.color.md_red_100)\n                )\n                DrawableCompat.setTint(\n                    binding.ivBad.drawable,\n                    appCtx.getCompatColor(R.color.md_blue_100)\n                )\n            }\n\n            if (AppConfig.changeSourceLoadWordCount && !item.chapterWordCountText.isNullOrBlank()) {\n                tvCurrentChapterWordCount.visible()\n            } else {\n                tvCurrentChapterWordCount.gone()\n            }\n\n            if (AppConfig.changeSourceLoadWordCount && item.respondTime >= 0) {\n                tvRespondTime.visible()\n            } else {\n                tvRespondTime.gone()\n            }\n        }\n    }\n\n    override fun registerListener(holder: ItemViewHolder, binding: ItemChangeSourceBinding) {\n        binding.ivGood.setOnClickListener {\n            if (binding.ivBad.isVisible) {\n                DrawableCompat.setTint(\n                    binding.ivGood.drawable,\n                    appCtx.getCompatColor(R.color.md_red_A200)\n                )\n                binding.ivBad.gone()\n                getItem(holder.layoutPosition)?.let {\n                    callBack.setBookScore(it, 1)\n                }\n            } else {\n                DrawableCompat.setTint(\n                    binding.ivGood.drawable,\n                    appCtx.getCompatColor(R.color.md_red_100)\n                )\n                binding.ivBad.visible()\n                getItem(holder.layoutPosition)?.let {\n                    callBack.setBookScore(it, 0)\n                }\n            }\n        }\n        binding.ivBad.setOnClickListener {\n            if (binding.ivGood.isVisible) {\n                DrawableCompat.setTint(\n                    binding.ivBad.drawable,\n                    appCtx.getCompatColor(R.color.md_blue_A200)\n                )\n                binding.ivGood.gone()\n                getItem(holder.layoutPosition)?.let {\n                    callBack.setBookScore(it, -1)\n                }\n            } else {\n                DrawableCompat.setTint(\n                    binding.ivBad.drawable,\n                    appCtx.getCompatColor(R.color.md_blue_100)\n                )\n                binding.ivGood.visible()\n                getItem(holder.layoutPosition)?.let {\n                    callBack.setBookScore(it, 0)\n                }\n            }\n        }\n        holder.itemView.setOnClickListener {\n            getItem(holder.layoutPosition)?.let {\n                if (it.bookUrl != callBack.oldBookUrl) {\n                    callBack.changeTo(it)\n                }\n            }\n        }\n        holder.itemView.onLongClick {\n            showMenu(holder.itemView, getItem(holder.layoutPosition))\n        }\n    }\n\n    private fun showMenu(view: View, searchBook: SearchBook?) {\n        searchBook ?: return\n        val popupMenu = PopupMenu(context, view)\n        popupMenu.inflate(R.menu.change_source_item)\n        popupMenu.setOnMenuItemClickListener {\n            when (it.itemId) {\n                R.id.menu_top_source -> {\n                    callBack.topSource(searchBook)\n                }\n\n                R.id.menu_bottom_source -> {\n                    callBack.bottomSource(searchBook)\n                }\n\n                R.id.menu_edit_source -> {\n                    callBack.editSource(searchBook)\n                }\n\n                R.id.menu_disable_source -> {\n                    callBack.disableSource(searchBook)\n                }\n\n                R.id.menu_delete_source -> context.alert(R.string.draw) {\n                    setMessage(context.getString(R.string.sure_del) + \"\\n\" + searchBook.originName)\n                    noButton()\n                    yesButton {\n                        callBack.deleteSource(searchBook)\n                        updateItems(0, itemCount, listOf<Int>())\n                    }\n                }\n            }\n            true\n        }\n        popupMenu.show()\n    }\n\n    interface CallBack {\n        val oldBookUrl: String?\n        fun changeTo(searchBook: SearchBook)\n        fun topSource(searchBook: SearchBook)\n        fun bottomSource(searchBook: SearchBook)\n        fun editSource(searchBook: SearchBook)\n        fun disableSource(searchBook: SearchBook)\n        fun deleteSource(searchBook: SearchBook)\n        fun setBookScore(searchBook: SearchBook, score: Int)\n        fun getBookScore(searchBook: SearchBook): Int\n    }\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/book/changesource/ChangeBookSourceDialog.kt",
    "content": "package io.legado.app.ui.book.changesource\n\nimport android.os.Bundle\nimport android.view.Menu\nimport android.view.MenuItem\nimport android.view.View\nimport android.view.ViewGroup\nimport android.widget.ImageButton\nimport androidx.appcompat.widget.SearchView\nimport androidx.appcompat.widget.Toolbar\nimport androidx.core.os.bundleOf\nimport androidx.fragment.app.viewModels\nimport androidx.lifecycle.Lifecycle.State.STARTED\nimport androidx.lifecycle.lifecycleScope\nimport androidx.lifecycle.repeatOnLifecycle\nimport androidx.recyclerview.widget.LinearLayoutManager\nimport androidx.recyclerview.widget.RecyclerView\nimport io.legado.app.R\nimport io.legado.app.base.BaseDialogFragment\nimport io.legado.app.constant.AppLog\nimport io.legado.app.constant.EventBus\nimport io.legado.app.data.appDb\nimport io.legado.app.data.entities.Book\nimport io.legado.app.data.entities.BookChapter\nimport io.legado.app.data.entities.BookSource\nimport io.legado.app.data.entities.SearchBook\nimport io.legado.app.databinding.DialogBookChangeSourceBinding\nimport io.legado.app.help.config.AppConfig\nimport io.legado.app.lib.dialogs.alert\nimport io.legado.app.lib.theme.elevation\nimport io.legado.app.lib.theme.getPrimaryTextColor\nimport io.legado.app.lib.theme.primaryColor\nimport io.legado.app.ui.book.read.ReadBookActivity\nimport io.legado.app.ui.book.source.edit.BookSourceEditActivity\nimport io.legado.app.ui.book.source.manage.BookSourceActivity\nimport io.legado.app.ui.widget.dialog.WaitDialog\nimport io.legado.app.ui.widget.recycler.VerticalDivider\nimport io.legado.app.utils.ColorUtils\nimport io.legado.app.utils.StartActivityContract\nimport io.legado.app.utils.applyTint\nimport io.legado.app.utils.dpToPx\nimport io.legado.app.utils.getCompatDrawable\nimport io.legado.app.utils.observeEvent\nimport io.legado.app.utils.setLayout\nimport io.legado.app.utils.startActivity\nimport io.legado.app.utils.transaction\nimport io.legado.app.utils.viewbindingdelegate.viewBinding\nimport kotlinx.coroutines.Dispatchers.IO\nimport kotlinx.coroutines.delay\nimport kotlinx.coroutines.flow.conflate\nimport kotlinx.coroutines.flow.drop\nimport kotlinx.coroutines.flow.first\nimport kotlinx.coroutines.launch\n\n/**\n * 换源界面\n */\nclass ChangeBookSourceDialog() : BaseDialogFragment(R.layout.dialog_book_change_source),\n    Toolbar.OnMenuItemClickListener,\n    ChangeBookSourceAdapter.CallBack {\n\n    constructor(name: String, author: String) : this() {\n        arguments = Bundle().apply {\n            putString(\"name\", name)\n            putString(\"author\", author)\n        }\n    }\n\n    private val binding by viewBinding(DialogBookChangeSourceBinding::bind)\n    private val groups = linkedSetOf<String>()\n    private val callBack: CallBack? get() = activity as? CallBack\n    private val viewModel: ChangeBookSourceViewModel by viewModels()\n    private val waitDialog by lazy { WaitDialog(requireContext()) }\n    private val adapter by lazy { ChangeBookSourceAdapter(requireContext(), viewModel, this) }\n    private val editSourceResult =\n        registerForActivityResult(StartActivityContract(BookSourceEditActivity::class.java)) {\n            val origin = it.data?.getStringExtra(\"origin\") ?: return@registerForActivityResult\n            viewModel.startSearch(origin)\n        }\n    private val searchFinishCallback: (isEmpty: Boolean) -> Unit = {\n        if (it) {\n            val searchGroup = AppConfig.searchGroup\n            if (searchGroup.isNotEmpty()) {\n                lifecycleScope.launch {\n                    context?.alert(\"搜索结果为空\") {\n                        setMessage(\"${searchGroup}分组搜索结果为空,是否切换到全部分组\")\n                        cancelButton()\n                        okButton {\n                            AppConfig.searchGroup = \"\"\n                            upGroupMenuName()\n                            viewModel.startSearch()\n                        }\n                    }\n                }\n            }\n        }\n    }\n\n    override fun onStart() {\n        super.onStart()\n        setLayout(1f, ViewGroup.LayoutParams.MATCH_PARENT)\n    }\n\n    override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) {\n        binding.toolBar.setBackgroundColor(primaryColor)\n        viewModel.initData(arguments, callBack?.oldBook, activity is ReadBookActivity)\n        showTitle()\n        initMenu()\n        initRecyclerView()\n        initNavigationView()\n        initSearchView()\n        initBottomBar()\n        initLiveData()\n        viewModel.searchFinishCallback = searchFinishCallback\n    }\n\n    override fun onDestroy() {\n        super.onDestroy()\n        viewModel.searchFinishCallback = null\n    }\n\n    private fun showTitle() {\n        binding.toolBar.title = viewModel.name\n        binding.toolBar.subtitle = viewModel.author\n        binding.toolBar.navigationIcon =\n            getCompatDrawable(androidx.appcompat.R.drawable.abc_ic_ab_back_material)\n        binding.toolBar.elevation = requireContext().elevation\n    }\n\n    private fun initMenu() {\n        binding.toolBar.inflateMenu(R.menu.change_source)\n        binding.toolBar.menu.applyTint(requireContext())\n        binding.toolBar.setOnMenuItemClickListener(this)\n        binding.toolBar.menu.findItem(R.id.menu_check_author)\n            ?.isChecked = AppConfig.changeSourceCheckAuthor\n        binding.toolBar.menu.findItem(R.id.menu_load_info)\n            ?.isChecked = AppConfig.changeSourceLoadInfo\n        binding.toolBar.menu.findItem(R.id.menu_load_toc)\n            ?.isChecked = AppConfig.changeSourceLoadToc\n        binding.toolBar.menu.findItem(R.id.menu_load_word_count)\n            ?.isChecked = AppConfig.changeSourceLoadWordCount\n    }\n\n    private fun initRecyclerView() {\n        binding.recyclerView.addItemDecoration(VerticalDivider(requireContext()))\n        binding.recyclerView.adapter = adapter\n        adapter.registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() {\n            override fun onItemRangeInserted(positionStart: Int, itemCount: Int) {\n                if (positionStart == 0) {\n                    binding.recyclerView.scrollToPosition(0)\n                }\n            }\n\n            override fun onItemRangeMoved(fromPosition: Int, toPosition: Int, itemCount: Int) {\n                if (toPosition == 0) {\n                    binding.recyclerView.scrollToPosition(0)\n                }\n            }\n        })\n    }\n\n    private fun initSearchView() {\n        val searchView = binding.toolBar.menu.findItem(R.id.menu_screen).actionView as SearchView\n        searchView.setOnCloseListener {\n            showTitle()\n            false\n        }\n        searchView.setOnSearchClickListener {\n            binding.toolBar.title = \"\"\n            binding.toolBar.subtitle = \"\"\n            binding.toolBar.navigationIcon = null\n        }\n        searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener {\n            override fun onQueryTextSubmit(query: String?): Boolean {\n                return false\n            }\n\n            override fun onQueryTextChange(newText: String?): Boolean {\n                viewModel.screen(newText)\n                return false\n            }\n\n        })\n    }\n\n    private fun initNavigationView() {\n        binding.toolBar.navigationIcon =\n            getCompatDrawable(androidx.appcompat.R.drawable.abc_ic_ab_back_material)\n        binding.toolBar.setNavigationContentDescription(\n            androidx.appcompat.R.string.abc_action_bar_up_description\n        )\n        binding.toolBar.setNavigationOnClickListener {\n            dismissAllowingStateLoss()\n        }\n        kotlin.runCatching {\n            val mNavButtonViewField = Toolbar::class.java.getDeclaredField(\"mNavButtonView\")\n            mNavButtonViewField.isAccessible = true\n            val navigationView = mNavButtonViewField.get(binding.toolBar) as ImageButton\n            val isLight = ColorUtils.isColorLight(primaryColor)\n            val textColor = requireContext().getPrimaryTextColor(isLight)\n            navigationView.setColorFilter(textColor)\n        }\n    }\n\n    private fun initBottomBar() {\n        binding.tvDur.text = callBack?.oldBook?.originName\n        binding.tvDur.setOnClickListener {\n            scrollToDurSource()\n        }\n        binding.ivTop.setOnClickListener {\n            binding.recyclerView.scrollToPosition(0)\n        }\n        binding.ivBottom.setOnClickListener {\n            binding.recyclerView.scrollToPosition(adapter.itemCount - 1)\n        }\n    }\n\n    private fun initLiveData() {\n        viewModel.searchStateData.observe(viewLifecycleOwner) {\n            binding.refreshProgressBar.isAutoLoading = it\n            if (it) {\n                startStopMenuItem?.let { item ->\n                    item.setIcon(R.drawable.ic_stop_black_24dp)\n                    item.setTitle(R.string.stop)\n                }\n            } else {\n                startStopMenuItem?.let { item ->\n                    item.setIcon(R.drawable.ic_refresh_black_24dp)\n                    item.setTitle(R.string.refresh)\n                }\n            }\n            binding.toolBar.menu.applyTint(requireContext())\n        }\n        lifecycleScope.launch {\n            lifecycle.currentStateFlow.first { it.isAtLeast(STARTED) }\n            viewModel.searchDataFlow.conflate().collect {\n                adapter.setItems(it)\n                delay(1000)\n            }\n        }\n\n        lifecycleScope.launch {\n            repeatOnLifecycle(STARTED) {\n                viewModel.changeSourceProgress\n                    .drop(1)\n                    .collect { (count, name) ->\n                        binding.tvDur.text =\n                            getString(\n                                R.string.change_source_progress,\n                                adapter.itemCount,\n                                count,\n                                viewModel.totalSourceCount,\n                                name\n                            )\n                        delay(500)\n                    }\n            }\n        }\n\n        lifecycleScope.launch {\n            appDb.bookSourceDao.flowEnabledGroups().conflate().collect {\n                groups.clear()\n                groups.addAll(it)\n                upGroupMenu()\n            }\n        }\n    }\n\n    private val startStopMenuItem: MenuItem?\n        get() = binding.toolBar.menu.findItem(R.id.menu_start_stop)\n\n    override fun onMenuItemClick(item: MenuItem?): Boolean {\n        when (item?.itemId) {\n            R.id.menu_check_author -> {\n                AppConfig.changeSourceCheckAuthor = !item.isChecked\n                item.isChecked = !item.isChecked\n                viewModel.refresh()\n            }\n\n            R.id.menu_load_info -> {\n                AppConfig.changeSourceLoadInfo = !item.isChecked\n                item.isChecked = !item.isChecked\n            }\n\n            R.id.menu_load_toc -> {\n                AppConfig.changeSourceLoadToc = !item.isChecked\n                item.isChecked = !item.isChecked\n            }\n\n            R.id.menu_load_word_count -> {\n                AppConfig.changeSourceLoadWordCount = !item.isChecked\n                item.isChecked = !item.isChecked\n                viewModel.onLoadWordCountChecked(item.isChecked)\n            }\n\n            R.id.menu_start_stop -> viewModel.startOrStopSearch()\n            R.id.menu_source_manage -> startActivity<BookSourceActivity>()\n            R.id.menu_close -> dismissAllowingStateLoss()\n            R.id.menu_refresh_list -> viewModel.startRefreshList()\n            else -> if (item?.groupId == R.id.source_group && !item.isChecked) {\n                item.isChecked = true\n                if (item.title.toString() == getString(R.string.all_source)) {\n                    AppConfig.searchGroup = \"\"\n                } else {\n                    AppConfig.searchGroup = item.title.toString()\n                }\n                upGroupMenuName()\n                lifecycleScope.launch(IO) {\n                    viewModel.stopSearch()\n                    if (viewModel.refresh()) {\n                        viewModel.startSearch()\n                    }\n                }\n            }\n        }\n        return false\n    }\n\n    private fun scrollToDurSource() {\n        adapter.getItems().forEachIndexed { index, searchBook ->\n            if (searchBook.bookUrl == oldBookUrl) {\n                (binding.recyclerView.layoutManager as LinearLayoutManager)\n                    .scrollToPositionWithOffset(index, 60.dpToPx())\n                return\n            }\n        }\n    }\n\n    override fun changeTo(searchBook: SearchBook) {\n        val oldBookType = callBack?.oldBook?.type ?: 0\n        if (searchBook.sameBookTypeLocal(oldBookType)) {\n            changeSource(searchBook) {\n                dismissAllowingStateLoss()\n            }\n        } else {\n            alert(\n                titleResource = R.string.book_type_different,\n                messageResource = R.string.soure_change_source\n            ) {\n                okButton {\n                    changeSource(searchBook) {\n                        dismissAllowingStateLoss()\n                    }\n                }\n                cancelButton()\n            }\n        }\n    }\n\n    override val oldBookUrl: String?\n        get() = callBack?.oldBook?.bookUrl\n\n    override fun topSource(searchBook: SearchBook) {\n        viewModel.topSource(searchBook)\n    }\n\n    override fun bottomSource(searchBook: SearchBook) {\n        viewModel.bottomSource(searchBook)\n    }\n\n    override fun editSource(searchBook: SearchBook) {\n        editSourceResult.launch {\n            putExtra(\"sourceUrl\", searchBook.origin)\n        }\n    }\n\n    override fun disableSource(searchBook: SearchBook) {\n        viewModel.disableSource(searchBook)\n    }\n\n    override fun deleteSource(searchBook: SearchBook) {\n        viewModel.del(searchBook)\n        if (oldBookUrl == searchBook.bookUrl) {\n            viewModel.autoChangeSource(callBack?.oldBook?.type) { book, toc, source ->\n                callBack?.changeTo(source, book, toc)\n            }\n        }\n    }\n\n    override fun setBookScore(searchBook: SearchBook, score: Int) {\n        viewModel.setBookScore(searchBook, score)\n    }\n\n    override fun getBookScore(searchBook: SearchBook): Int {\n        return viewModel.getBookScore(searchBook)\n    }\n\n    private fun changeSource(searchBook: SearchBook, onSuccess: (() -> Unit)? = null) {\n        waitDialog.setText(R.string.load_toc)\n        waitDialog.show()\n        val book = viewModel.bookMap[searchBook.primaryStr()] ?: searchBook.toBook()\n        val coroutine = viewModel.getToc(book, { toc, source ->\n            waitDialog.dismiss()\n            callBack?.changeTo(source, book, toc)\n            onSuccess?.invoke()\n        }, {\n            waitDialog.dismiss()\n            AppLog.put(\"换源获取目录出错\\n$it\", it, true)\n        })\n        waitDialog.setOnCancelListener {\n            coroutine.cancel()\n        }\n    }\n\n    /**\n     * 更新分组菜单\n     */\n    private fun upGroupMenu() {\n        binding.toolBar.menu.findItem(R.id.menu_group)?.run {\n            subMenu?.transaction { menu ->\n                val selectedGroup = AppConfig.searchGroup\n                menu.removeGroup(R.id.source_group)\n                val allItem = menu.add(R.id.source_group, Menu.NONE, Menu.NONE, R.string.all_source)\n                var hasSelectedGroup = false\n                groups.forEach { group ->\n                    menu.add(R.id.source_group, Menu.NONE, Menu.NONE, group)?.let {\n                        if (group == selectedGroup) {\n                            it.isChecked = true\n                            hasSelectedGroup = true\n                        }\n                    }\n                }\n                menu.setGroupCheckable(R.id.source_group, true, true)\n                if (hasSelectedGroup) {\n                    title = getString(R.string.group) + \"(\" + AppConfig.searchGroup + \")\"\n                } else {\n                    allItem.isChecked = true\n                    title = getString(R.string.group)\n                }\n            }\n        }\n    }\n\n    /**\n     * 更新分组菜单名\n     */\n    private fun upGroupMenuName() {\n        val menuGroup = binding.toolBar.menu.findItem(R.id.menu_group)\n        val searchGroup = AppConfig.searchGroup\n        if (searchGroup.isEmpty()) {\n            menuGroup?.title = getString(R.string.group)\n        } else {\n            menuGroup?.title = getString(R.string.group) + \"($searchGroup)\"\n        }\n    }\n\n    override fun observeLiveBus() {\n        observeEvent<String>(EventBus.SOURCE_CHANGED) {\n            adapter.notifyItemRangeChanged(\n                0,\n                adapter.itemCount,\n                bundleOf(Pair(\"upCurSource\", oldBookUrl))\n            )\n        }\n    }\n\n    interface CallBack {\n        val oldBook: Book?\n        fun changeTo(source: BookSource, book: Book, toc: List<BookChapter>)\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/book/changesource/ChangeBookSourceViewModel.kt",
    "content": "package io.legado.app.ui.book.changesource\n\nimport android.app.Application\nimport android.os.Bundle\nimport androidx.annotation.CallSuper\nimport androidx.lifecycle.MutableLiveData\nimport androidx.lifecycle.viewModelScope\nimport io.legado.app.base.BaseViewModel\nimport io.legado.app.constant.AppConst\nimport io.legado.app.constant.AppLog\nimport io.legado.app.constant.AppPattern\nimport io.legado.app.data.appDb\nimport io.legado.app.data.entities.Book\nimport io.legado.app.data.entities.BookChapter\nimport io.legado.app.data.entities.BookSource\nimport io.legado.app.data.entities.BookSourcePart\nimport io.legado.app.data.entities.SearchBook\nimport io.legado.app.exception.NoStackTraceException\nimport io.legado.app.help.book.BookHelp\nimport io.legado.app.help.book.ContentProcessor\nimport io.legado.app.help.book.primaryStr\nimport io.legado.app.help.book.releaseHtmlData\nimport io.legado.app.help.config.AppConfig\nimport io.legado.app.help.config.SourceConfig\nimport io.legado.app.help.coroutine.Coroutine\nimport io.legado.app.help.source.SourceHelp\nimport io.legado.app.model.webBook.WebBook\nimport io.legado.app.utils.internString\nimport io.legado.app.utils.mapParallel\nimport io.legado.app.utils.mapParallelSafe\nimport io.legado.app.utils.onEachIndexed\nimport io.legado.app.utils.toastOnUi\nimport kotlinx.coroutines.CancellationException\nimport kotlinx.coroutines.Dispatchers.IO\nimport kotlinx.coroutines.ExecutorCoroutineDispatcher\nimport kotlinx.coroutines.Job\nimport kotlinx.coroutines.asCoroutineDispatcher\nimport kotlinx.coroutines.channels.awaitClose\nimport kotlinx.coroutines.coroutineScope\nimport kotlinx.coroutines.currentCoroutineContext\nimport kotlinx.coroutines.ensureActive\nimport kotlinx.coroutines.flow.MutableStateFlow\nimport kotlinx.coroutines.flow.asStateFlow\nimport kotlinx.coroutines.flow.callbackFlow\nimport kotlinx.coroutines.flow.catch\nimport kotlinx.coroutines.flow.collect\nimport kotlinx.coroutines.flow.flow\nimport kotlinx.coroutines.flow.flowOn\nimport kotlinx.coroutines.flow.map\nimport kotlinx.coroutines.flow.onCompletion\nimport kotlinx.coroutines.flow.onStart\nimport kotlinx.coroutines.flow.update\nimport kotlinx.coroutines.launch\nimport kotlinx.coroutines.withTimeout\nimport java.util.Collections\nimport java.util.concurrent.ConcurrentHashMap\nimport java.util.concurrent.Executors\nimport kotlin.math.min\n\n@Suppress(\"MemberVisibilityCanBePrivate\")\nopen class ChangeBookSourceViewModel(application: Application) : BaseViewModel(application) {\n    private val threadCount = AppConfig.threadCount\n    private var searchPool: ExecutorCoroutineDispatcher? = null\n    val searchStateData = MutableLiveData<Boolean>()\n    var searchFinishCallback: ((isEmpty: Boolean) -> Unit)? = null\n    var name: String = \"\"\n    var author: String = \"\"\n    private var fromReadBookActivity = false\n    private var oldBook: Book? = null\n    private var screenKey: String = \"\"\n    private var bookSourceParts = arrayListOf<BookSourcePart>()\n    val totalSourceCount: Int\n        get() = bookSourceParts.size\n    private var searchBookList = arrayListOf<SearchBook>()\n    private val searchBooks = Collections.synchronizedList(arrayListOf<SearchBook>())\n    private val tocMap = ConcurrentHashMap<String, List<BookChapter>>()\n    private val _changeSourceProgress = MutableStateFlow(0 to \"\")\n    val changeSourceProgress = _changeSourceProgress.asStateFlow()\n    private var tocMapChapterCount = 0\n    private val contentProcessor by lazy {\n        ContentProcessor.get(oldBook!!)\n    }\n    private var searchCallback: SourceCallback? = null\n    private val chapterNumRegex = \"^\\\\[(\\\\d+)]\".toRegex()\n    private val comparatorBase by lazy {\n        compareByDescending<SearchBook> { getBookScore(it) }\n            .thenByDescending { SourceConfig.getSourceScore(it.origin) }\n    }\n    private val defaultComparator by lazy {\n        comparatorBase.thenBy { it.originOrder }\n    }\n    private val wordCountComparator by lazy {\n        comparatorBase.thenByDescending { it.chapterWordCount > 1000 }\n            .thenByDescending { getChapterNum(it.chapterWordCountText) }\n            .thenByDescending { it.chapterWordCount }\n            .thenBy { it.originOrder }\n    }\n    private var task: Job? = null\n    val bookMap = ConcurrentHashMap<String, Book>()\n    val searchDataFlow = callbackFlow {\n\n        searchCallback = object : SourceCallback {\n\n            override fun searchSuccess(searchBook: SearchBook) {\n                searchBook.releaseHtmlData()\n                appDb.searchBookDao.insert(searchBook)\n                when {\n                    screenKey.isEmpty() -> searchBooks.add(searchBook)\n                    searchBook.name.contains(screenKey) -> searchBooks.add(searchBook)\n                    else -> return\n                }\n                trySend(arrayOf(searchBooks))\n            }\n\n            override fun upAdapter() {\n                trySend(arrayOf(searchBooks))\n            }\n\n        }\n\n        getDbSearchBooks().let {\n            searchBooks.clear()\n            searchBooks.addAll(it)\n            trySend(arrayOf(searchBooks))\n        }\n\n        if (searchBooks.isEmpty()) {\n            startSearch()\n        }\n\n        awaitClose {\n            searchCallback = null\n        }\n    }.map {\n        kotlin.runCatching {\n            val comparator = if (AppConfig.changeSourceLoadWordCount) {\n                wordCountComparator\n            } else {\n                defaultComparator\n            }\n            searchBooks.sortedWith(comparator)\n        }.onFailure {\n            AppLog.put(\"换源排序出错\\n${it.localizedMessage}\", it)\n        }.getOrDefault(searchBooks)\n    }.flowOn(IO)\n\n    override fun onCleared() {\n        super.onCleared()\n        searchPool?.close()\n    }\n\n    @CallSuper\n    open fun initData(arguments: Bundle?, book: Book?, fromReadBookActivity: Boolean) {\n        arguments?.let { bundle ->\n            bundle.getString(\"name\")?.let {\n                name = it\n            }\n            bundle.getString(\"author\")?.let {\n                author = it.replace(AppPattern.authorRegex, \"\")\n            }\n            this.fromReadBookActivity = fromReadBookActivity\n            oldBook = book\n        }\n    }\n\n    private fun initSearchPool() {\n        searchPool = Executors\n            .newFixedThreadPool(min(threadCount, AppConst.MAX_THREAD)).asCoroutineDispatcher()\n    }\n\n    fun refresh(): Boolean {\n        getDbSearchBooks().let {\n            searchBooks.clear()\n            searchBooks.addAll(it)\n            searchCallback?.upAdapter()\n        }\n        return searchBooks.isEmpty()\n    }\n\n    /**\n     * 搜索书籍\n     */\n    fun startSearch() {\n        execute {\n            stopSearch()\n            if (searchBooks.isNotEmpty()) {\n                appDb.searchBookDao.delete(*searchBooks.toTypedArray())\n                searchBooks.clear()\n            }\n            searchCallback?.upAdapter()\n            bookSourceParts.clear()\n            tocMap.clear()\n            bookMap.clear()\n            tocMapChapterCount = 0\n            _changeSourceProgress.value = 0 to \"\"\n            val searchGroup = AppConfig.searchGroup\n            if (searchGroup.isBlank()) {\n                bookSourceParts.addAll(appDb.bookSourceDao.allEnabledPart)\n            } else {\n                val sources = appDb.bookSourceDao.getEnabledPartByGroup(searchGroup)\n                if (sources.isEmpty()) {\n                    AppConfig.searchGroup = \"\"\n                    bookSourceParts.addAll(appDb.bookSourceDao.allEnabledPart)\n                } else {\n                    bookSourceParts.addAll(sources)\n                }\n            }\n            initSearchPool()\n            search()\n        }\n    }\n\n    fun startSearch(origin: String) {\n        execute {\n            stopSearch()\n            bookSourceParts.clear()\n            tocMap.clear()\n            bookMap.clear()\n            tocMapChapterCount = 0\n            bookSourceParts.add(appDb.bookSourceDao.getBookSourcePart(origin)!!)\n            searchBooks.removeIf { it.origin == origin }\n            initSearchPool()\n            search()\n        }\n    }\n\n    private fun search() {\n        task = viewModelScope.launch(searchPool!!) {\n            flow {\n                for (bs in bookSourceParts) {\n                    bs.getBookSource()?.let {\n                        emit(it)\n                    }\n                }\n            }.onStart {\n                searchStateData.postValue(true)\n            }.mapParallel(threadCount) {\n                try {\n                    withTimeout(60000L) {\n                        search(it)\n                    }\n                } catch (_: Throwable) {\n                    currentCoroutineContext().ensureActive()\n                }\n                it\n            }.onEachIndexed { index, value ->\n                _changeSourceProgress.update { _ ->\n                    index + 1 to value.bookSourceName\n                }\n            }.onCompletion {\n                ensureActive()\n                searchStateData.postValue(false)\n                searchFinishCallback?.invoke(searchBooks.isEmpty())\n            }.catch {\n                AppLog.put(\"换源搜索出错\\n${it.localizedMessage}\", it)\n            }.collect()\n        }\n    }\n\n    private suspend fun search(source: BookSource) {\n        val checkAuthor = AppConfig.changeSourceCheckAuthor\n        val loadInfo = AppConfig.changeSourceLoadInfo\n        val loadToc = AppConfig.changeSourceLoadToc\n        val loadWordCount = AppConfig.changeSourceLoadWordCount\n        val resultBooks = WebBook.searchBookAwait(\n            source, name,\n            filter = { fName, fAuthor ->\n                fName == name && (!checkAuthor || fAuthor.contains(author))\n            })\n        resultBooks.forEach { searchBook ->\n            when {\n                loadInfo || loadToc || loadWordCount -> {\n                    loadBookInfo(source, searchBook.toBook())\n                }\n\n                else -> {\n                    searchCallback?.searchSuccess(searchBook)\n                }\n            }\n        }\n    }\n\n    private suspend fun loadBookInfo(source: BookSource, book: Book) {\n        if (book.tocUrl.isEmpty()) {\n            WebBook.getBookInfoAwait(source, book)\n        }\n        if (AppConfig.changeSourceLoadToc || AppConfig.changeSourceLoadWordCount) {\n            loadBookToc(source, book)\n        } else {\n            //从详情页里获取最新章节\n            val searchBook = book.toSearchBook()\n            searchCallback?.searchSuccess(searchBook)\n        }\n    }\n\n    private suspend fun loadBookToc(source: BookSource, book: Book) {\n        val chapters = WebBook.getChapterListAwait(source, book).getOrThrow()\n        for (chapter in chapters) {\n            chapter.internString()\n        }\n        if (tocMapChapterCount < 30000) {\n            tocMapChapterCount += chapters.size\n            tocMap[book.primaryStr()] = chapters\n        }\n        bookMap[book.primaryStr()] = book\n        book.releaseHtmlData()\n        if (AppConfig.changeSourceLoadWordCount) {\n            loadBookWordCount(source, book, chapters)\n        } else {\n            val searchBook = book.toSearchBook()\n            searchCallback?.searchSuccess(searchBook)\n        }\n    }\n\n    private suspend fun loadBookWordCount(\n        source: BookSource,\n        book: Book,\n        chapters: List<BookChapter>\n    ) = coroutineScope {\n        val chapterIndex = if (fromReadBookActivity) {\n            BookHelp.getDurChapter(oldBook!!, chapters)\n        } else {\n            chapters.lastIndex\n        }\n        val bookChapter = chapters[chapterIndex]\n        var title = bookChapter.title.trim()\n        if (title.length > 20) {\n            title = title.substring(0, 20) + \"…\"\n        }\n        val startTime = System.currentTimeMillis()\n        val pair = try {\n            val nextChapterUrl = chapters.getOrNull(chapterIndex + 1)?.url\n            var content = WebBook.getContentAwait(source, book, bookChapter, nextChapterUrl, false)\n            content = contentProcessor.getContent(oldBook!!, bookChapter, content, false).toString()\n            val len = content.length\n            len to \"[${chapterIndex + 1}] ${title}\\n字数：${len}\"\n        } catch (t: Throwable) {\n            if (t is CancellationException) throw t\n            -1 to \"[${chapterIndex + 1}] ${title}\\n获取字数失败：${t.localizedMessage}\"\n        }\n        val endTime = System.currentTimeMillis()\n        val searchBook = book.toSearchBook().apply {\n            chapterWordCountText = pair.second\n            chapterWordCount = pair.first\n            respondTime = (endTime - startTime).toInt()\n        }\n        searchCallback?.searchSuccess(searchBook)\n    }\n\n    fun onLoadWordCountChecked(isChecked: Boolean) {\n        if (isChecked) {\n            startRefreshList(true)\n        }\n    }\n\n    /**\n     * 刷新列表\n     */\n    fun startRefreshList(onlyRefreshNoWordCountBook: Boolean = false) {\n        execute {\n            stopSearch()\n            searchBookList.clear()\n            if (onlyRefreshNoWordCountBook) {\n                searchBooks.filterTo(searchBookList) {\n                    it.chapterWordCountText == null\n                }\n                searchBooks.removeIf { it.chapterWordCountText == null }\n            } else {\n                searchBookList.addAll(searchBooks)\n                searchBooks.clear()\n            }\n            searchCallback?.upAdapter()\n            initSearchPool()\n            refreshList()\n        }\n    }\n\n    private fun refreshList() {\n        task = viewModelScope.launch(searchPool!!) {\n            flow {\n                for (searchBook in searchBookList) {\n                    emit(searchBook)\n                }\n            }.onStart {\n                searchStateData.postValue(true)\n            }.mapParallelSafe(threadCount) {\n                val source = appDb.bookSourceDao.getBookSource(it.origin)!!\n                withTimeout(60000L) {\n                    loadBookInfo(source, it.toBook())\n                }\n            }.onCompletion {\n                searchStateData.postValue(false)\n            }.catch {\n                AppLog.put(\"换源刷新列表出错\\n${it.localizedMessage}\", it)\n            }.collect()\n        }\n    }\n\n    private fun getDbSearchBooks(): List<SearchBook> {\n        return if (screenKey.isEmpty()) {\n            if (AppConfig.changeSourceCheckAuthor) {\n                appDb.searchBookDao.changeSourceByGroup(\n                    name, author, AppConfig.searchGroup\n                )\n            } else {\n                appDb.searchBookDao.changeSourceByGroup(\n                    name, \"\", AppConfig.searchGroup\n                )\n            }\n        } else {\n            if (AppConfig.changeSourceCheckAuthor) {\n                appDb.searchBookDao.changeSourceSearch(\n                    name, author, screenKey, AppConfig.searchGroup\n                )\n            } else {\n                appDb.searchBookDao.changeSourceSearch(\n                    name, \"\", screenKey, AppConfig.searchGroup\n                )\n            }\n        }\n    }\n\n    /**\n     * 筛选\n     */\n    fun screen(key: String?) {\n        screenKey = key?.trim() ?: \"\"\n        execute {\n            getDbSearchBooks().let {\n                searchBooks.clear()\n                searchBooks.addAll(it)\n                searchCallback?.upAdapter()\n            }\n        }\n    }\n\n    fun startOrStopSearch() {\n        if (task == null || !task!!.isActive) {\n            startSearch()\n        } else {\n            stopSearch()\n        }\n    }\n\n    fun stopSearch() {\n        task?.cancel()\n        searchPool?.close()\n        searchStateData.postValue(false)\n    }\n\n    fun getToc(\n        book: Book,\n        onSuccess: (toc: List<BookChapter>, source: BookSource) -> Unit,\n        onError: (e: Throwable) -> Unit\n    ): Coroutine<Pair<List<BookChapter>, BookSource>> {\n        return execute {\n            val toc = tocMap[book.primaryStr()]\n            if (toc != null) {\n                val source = appDb.bookSourceDao.getBookSource(book.origin)\n                return@execute Pair(toc, source!!)\n            }\n            val result = getToc(book).getOrThrow()\n            tocMap[book.primaryStr()] = result.first\n            return@execute result\n        }.onSuccess {\n            onSuccess.invoke(it.first, it.second)\n        }.onError {\n            onError.invoke(it)\n        }\n    }\n\n    suspend fun getToc(book: Book): Result<Pair<List<BookChapter>, BookSource>> {\n        return kotlin.runCatching {\n            val source = appDb.bookSourceDao.getBookSource(book.origin)\n                ?: throw NoStackTraceException(\"书源不存在\")\n            if (book.tocUrl.isEmpty()) {\n                WebBook.getBookInfoAwait(source, book)\n            }\n            val toc = WebBook.getChapterListAwait(source, book).getOrThrow()\n            Pair(toc, source)\n        }\n    }\n\n    fun disableSource(searchBook: SearchBook) {\n        execute {\n            appDb.bookSourceDao.getBookSource(searchBook.origin)?.let { source ->\n                source.enabled = false\n                appDb.bookSourceDao.update(source)\n            }\n            searchBooks.remove(searchBook)\n            searchCallback?.upAdapter()\n        }\n    }\n\n    fun topSource(searchBook: SearchBook) {\n        execute {\n            appDb.bookSourceDao.getBookSource(searchBook.origin)?.let { source ->\n                val minOrder = appDb.bookSourceDao.minOrder - 1\n                source.customOrder = minOrder\n                searchBook.originOrder = source.customOrder\n                appDb.bookSourceDao.update(source)\n                updateSource(searchBook)\n            }\n            searchCallback?.upAdapter()\n        }\n    }\n\n    fun bottomSource(searchBook: SearchBook) {\n        execute {\n            appDb.bookSourceDao.getBookSource(searchBook.origin)?.let { source ->\n                val maxOrder = appDb.bookSourceDao.maxOrder + 1\n                source.customOrder = maxOrder\n                searchBook.originOrder = source.customOrder\n                appDb.bookSourceDao.update(source)\n                updateSource(searchBook)\n            }\n            searchCallback?.upAdapter()\n        }\n    }\n\n    fun updateSource(searchBook: SearchBook) {\n        appDb.searchBookDao.update(searchBook)\n    }\n\n    fun del(searchBook: SearchBook) {\n        execute {\n            SourceHelp.deleteBookSource(searchBook.origin)\n            appDb.searchBookDao.delete(searchBook)\n        }\n        searchBooks.remove(searchBook)\n        searchCallback?.upAdapter()\n    }\n\n    fun autoChangeSource(\n        bookType: Int?,\n        onSuccess: (book: Book, toc: List<BookChapter>, source: BookSource) -> Unit\n    ) {\n        execute {\n            searchBooks.forEach {\n                if (it.type == bookType) {\n                    val book = it.toBook()\n                    val result = getToc(book).getOrNull()\n                    if (result != null) {\n                        return@execute Triple(book, result.first, result.second)\n                    }\n                }\n            }\n            throw NoStackTraceException(\"没有有效源\")\n        }.onSuccess {\n            onSuccess.invoke(it.first, it.second, it.third)\n        }.onError {\n            context.toastOnUi(\"自动换源失败\\n${it.localizedMessage}\")\n        }\n    }\n\n    fun setBookScore(searchBook: SearchBook, score: Int) {\n        execute {\n            SourceConfig.setBookScore(searchBook.origin, searchBook.name, searchBook.author, score)\n            searchCallback?.upAdapter()\n        }\n    }\n\n    fun getBookScore(searchBook: SearchBook): Int {\n        return SourceConfig.getBookScore(searchBook.origin, searchBook.name, searchBook.author)\n    }\n\n    private fun getChapterNum(wordCountText: String?): Int {\n        wordCountText ?: return -1\n        return chapterNumRegex.find(wordCountText)?.groupValues?.get(1)?.toIntOrNull() ?: -1\n    }\n\n    interface SourceCallback {\n\n        fun searchSuccess(searchBook: SearchBook)\n\n        fun upAdapter()\n\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/book/changesource/ChangeChapterSourceAdapter.kt",
    "content": "package io.legado.app.ui.book.changesource\n\nimport android.content.Context\nimport android.os.Bundle\nimport android.view.View\nimport android.view.ViewGroup\nimport androidx.appcompat.widget.PopupMenu\nimport androidx.core.graphics.drawable.DrawableCompat\nimport androidx.core.view.isVisible\nimport androidx.recyclerview.widget.DiffUtil\nimport io.legado.app.R\nimport io.legado.app.base.adapter.DiffRecyclerAdapter\nimport io.legado.app.base.adapter.ItemViewHolder\nimport io.legado.app.data.entities.SearchBook\nimport io.legado.app.databinding.ItemChangeSourceBinding\nimport io.legado.app.help.config.AppConfig\nimport io.legado.app.utils.getCompatColor\nimport io.legado.app.utils.gone\nimport io.legado.app.utils.invisible\nimport io.legado.app.utils.visible\nimport splitties.init.appCtx\nimport splitties.views.onLongClick\n\n\nclass ChangeChapterSourceAdapter(\n    context: Context,\n    val viewModel: ChangeChapterSourceViewModel,\n    val callBack: CallBack\n) : DiffRecyclerAdapter<SearchBook, ItemChangeSourceBinding>(context) {\n\n    override val diffItemCallback = object : DiffUtil.ItemCallback<SearchBook>() {\n        override fun areItemsTheSame(oldItem: SearchBook, newItem: SearchBook): Boolean {\n            return oldItem.bookUrl == newItem.bookUrl\n        }\n\n        override fun areContentsTheSame(oldItem: SearchBook, newItem: SearchBook): Boolean {\n            return oldItem.originName == newItem.originName\n                    && oldItem.getDisplayLastChapterTitle() == newItem.getDisplayLastChapterTitle()\n        }\n\n    }\n\n    override fun getViewBinding(parent: ViewGroup): ItemChangeSourceBinding {\n        return ItemChangeSourceBinding.inflate(inflater, parent, false)\n    }\n\n    override fun convert(\n        holder: ItemViewHolder,\n        binding: ItemChangeSourceBinding,\n        item: SearchBook,\n        payloads: MutableList<Any>\n    ) {\n        binding.apply {\n            if (payloads.isEmpty()) {\n                tvOrigin.text = item.originName\n                tvAuthor.text = item.author\n                tvLast.text = item.getDisplayLastChapterTitle()\n                tvCurrentChapterWordCount.text = item.chapterWordCountText\n                tvRespondTime.text = context.getString(R.string.respondTime, item.respondTime)\n                if (callBack.oldBookUrl == item.bookUrl) {\n                    ivChecked.visible()\n                } else {\n                    ivChecked.invisible()\n                }\n            } else {\n                for (i in payloads.indices) {\n                    val bundle = payloads[i] as Bundle\n                    bundle.keySet().forEach {\n                        when (it) {\n                            \"name\" -> tvOrigin.text = item.originName\n                            \"latest\" -> tvLast.text = item.getDisplayLastChapterTitle()\n                            \"upCurSource\" -> if (callBack.oldBookUrl == item.bookUrl) {\n                                ivChecked.visible()\n                            } else {\n                                ivChecked.invisible()\n                            }\n                        }\n                    }\n                }\n            }\n            val score = callBack.getBookScore(item)\n            if (score > 0) {\n                binding.ivBad.gone()\n                binding.ivGood.visible()\n                DrawableCompat.setTint(binding.ivGood.drawable, appCtx.getCompatColor(R.color.md_red_A200))\n                DrawableCompat.setTint(binding.ivBad.drawable, appCtx.getCompatColor(R.color.md_blue_100))\n            } else if (score < 0) {\n                binding.ivGood.gone()\n                binding.ivBad.visible()\n                DrawableCompat.setTint(\n                    binding.ivGood.drawable,\n                    appCtx.getCompatColor(R.color.md_red_100)\n                )\n                DrawableCompat.setTint(\n                    binding.ivBad.drawable,\n                    appCtx.getCompatColor(R.color.md_blue_A200)\n                )\n            } else {\n                binding.ivGood.visible()\n                binding.ivBad.visible()\n                DrawableCompat.setTint(\n                    binding.ivGood.drawable,\n                    appCtx.getCompatColor(R.color.md_red_100)\n                )\n                DrawableCompat.setTint(\n                    binding.ivBad.drawable,\n                    appCtx.getCompatColor(R.color.md_blue_100)\n                )\n            }\n\n            if (AppConfig.changeSourceLoadWordCount && !item.chapterWordCountText.isNullOrBlank()) {\n                tvCurrentChapterWordCount.visible()\n            } else {\n                tvCurrentChapterWordCount.gone()\n            }\n\n            if (AppConfig.changeSourceLoadWordCount && item.respondTime >= 0) {\n                tvRespondTime.visible()\n            } else {\n                tvRespondTime.gone()\n            }\n        }\n    }\n\n    override fun registerListener(holder: ItemViewHolder, binding: ItemChangeSourceBinding) {\n        binding.ivGood.setOnClickListener {\n            if (binding.ivBad.isVisible) {\n                DrawableCompat.setTint(binding.ivGood.drawable, appCtx.getCompatColor(R.color.md_red_A200))\n                binding.ivBad.gone()\n                getItem(holder.layoutPosition)?.let {\n                    callBack.setBookScore(it, 1)\n                }\n            } else {\n                DrawableCompat.setTint(binding.ivGood.drawable, appCtx.getCompatColor(R.color.md_red_100))\n                binding.ivBad.visible()\n                getItem(holder.layoutPosition)?.let {\n                    callBack.setBookScore(it, 0)\n                }\n            }\n        }\n        binding.ivBad.setOnClickListener {\n            if (binding.ivGood.isVisible) {\n                DrawableCompat.setTint(binding.ivBad.drawable, appCtx.getCompatColor(R.color.md_blue_A200))\n                binding.ivGood.gone()\n                getItem(holder.layoutPosition)?.let {\n                    callBack.setBookScore(it, -1)\n                }\n            } else {\n                DrawableCompat.setTint(binding.ivBad.drawable, appCtx.getCompatColor(R.color.md_blue_100))\n                binding.ivGood.visible()\n                getItem(holder.layoutPosition)?.let {\n                    callBack.setBookScore(it, 0)\n                }\n            }\n        }\n        holder.itemView.setOnClickListener {\n            getItem(holder.layoutPosition)?.let {\n                callBack.openToc(it)\n            }\n        }\n        holder.itemView.onLongClick {\n            showMenu(holder.itemView, getItem(holder.layoutPosition))\n        }\n    }\n\n    private fun showMenu(view: View, searchBook: SearchBook?) {\n        searchBook ?: return\n        val popupMenu = PopupMenu(context, view)\n        popupMenu.inflate(R.menu.change_source_item)\n        popupMenu.setOnMenuItemClickListener {\n            when (it.itemId) {\n                R.id.menu_top_source -> {\n                    callBack.topSource(searchBook)\n                }\n                R.id.menu_bottom_source -> {\n                    callBack.bottomSource(searchBook)\n                }\n                R.id.menu_edit_source -> {\n                    callBack.editSource(searchBook)\n                }\n                R.id.menu_disable_source -> {\n                    callBack.disableSource(searchBook)\n                }\n                R.id.menu_delete_source -> {\n                    callBack.deleteSource(searchBook)\n                    updateItems(0, itemCount, listOf<Int>())\n                }\n            }\n            true\n        }\n        popupMenu.show()\n    }\n\n    interface CallBack {\n        val oldBookUrl: String?\n        fun openToc(searchBook: SearchBook)\n        fun topSource(searchBook: SearchBook)\n        fun bottomSource(searchBook: SearchBook)\n        fun editSource(searchBook: SearchBook)\n        fun disableSource(searchBook: SearchBook)\n        fun deleteSource(searchBook: SearchBook)\n        fun setBookScore(searchBook: SearchBook, score: Int)\n        fun getBookScore(searchBook: SearchBook): Int\n    }\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/book/changesource/ChangeChapterSourceDialog.kt",
    "content": "package io.legado.app.ui.book.changesource\n\nimport android.os.Bundle\nimport android.view.Menu\nimport android.view.MenuItem\nimport android.view.View\nimport android.view.ViewGroup\nimport androidx.activity.addCallback\nimport androidx.appcompat.widget.SearchView\nimport androidx.appcompat.widget.Toolbar\nimport androidx.core.os.bundleOf\nimport androidx.core.view.isVisible\nimport androidx.fragment.app.viewModels\nimport androidx.lifecycle.Lifecycle.State.STARTED\nimport androidx.lifecycle.lifecycleScope\nimport androidx.recyclerview.widget.LinearLayoutManager\nimport androidx.recyclerview.widget.RecyclerView\nimport io.legado.app.R\nimport io.legado.app.base.BaseDialogFragment\nimport io.legado.app.constant.AppLog\nimport io.legado.app.constant.EventBus\nimport io.legado.app.data.appDb\nimport io.legado.app.data.entities.Book\nimport io.legado.app.data.entities.BookChapter\nimport io.legado.app.data.entities.BookSource\nimport io.legado.app.data.entities.SearchBook\nimport io.legado.app.databinding.DialogChapterChangeSourceBinding\nimport io.legado.app.help.book.BookHelp\nimport io.legado.app.help.config.AppConfig\nimport io.legado.app.lib.dialogs.alert\nimport io.legado.app.lib.theme.elevation\nimport io.legado.app.lib.theme.primaryColor\nimport io.legado.app.ui.book.read.ReadBookActivity\nimport io.legado.app.ui.book.source.edit.BookSourceEditActivity\nimport io.legado.app.ui.book.source.manage.BookSourceActivity\nimport io.legado.app.ui.widget.recycler.VerticalDivider\nimport io.legado.app.utils.StartActivityContract\nimport io.legado.app.utils.applyTint\nimport io.legado.app.utils.dpToPx\nimport io.legado.app.utils.gone\nimport io.legado.app.utils.observeEvent\nimport io.legado.app.utils.setLayout\nimport io.legado.app.utils.startActivity\nimport io.legado.app.utils.toastOnUi\nimport io.legado.app.utils.transaction\nimport io.legado.app.utils.viewbindingdelegate.viewBinding\nimport io.legado.app.utils.visible\nimport kotlinx.coroutines.Dispatchers.IO\nimport kotlinx.coroutines.delay\nimport kotlinx.coroutines.flow.conflate\nimport kotlinx.coroutines.flow.first\nimport kotlinx.coroutines.launch\n\n\nclass ChangeChapterSourceDialog() : BaseDialogFragment(R.layout.dialog_chapter_change_source),\n    Toolbar.OnMenuItemClickListener,\n    ChangeChapterSourceAdapter.CallBack,\n    ChangeChapterTocAdapter.Callback {\n\n    constructor(name: String, author: String, chapterIndex: Int, chapterTitle: String) : this() {\n        arguments = Bundle().apply {\n            putString(\"name\", name)\n            putString(\"author\", author)\n            putInt(\"chapterIndex\", chapterIndex)\n            putString(\"chapterTitle\", chapterTitle)\n        }\n    }\n\n    private val binding by viewBinding(DialogChapterChangeSourceBinding::bind)\n    private val groups = linkedSetOf<String>()\n    private val callBack: CallBack? get() = activity as? CallBack\n    private val viewModel: ChangeChapterSourceViewModel by viewModels()\n    private val editSourceResult =\n        registerForActivityResult(StartActivityContract(BookSourceEditActivity::class.java)) {\n            viewModel.startSearch()\n        }\n    private val searchBookAdapter by lazy {\n        ChangeChapterSourceAdapter(requireContext(), viewModel, this)\n    }\n    private val tocAdapter by lazy {\n        ChangeChapterTocAdapter(requireContext(), this)\n    }\n    private val contentSuccess: (content: String) -> Unit = {\n        binding.loadingToc.gone()\n        callBack?.replaceContent(it)\n        dismissAllowingStateLoss()\n    }\n    private var searchBook: SearchBook? = null\n    private val searchFinishCallback: (isEmpty: Boolean) -> Unit = {\n        if (it) {\n            val searchGroup = AppConfig.searchGroup\n            if (searchGroup.isNotEmpty()) {\n                lifecycleScope.launch {\n                    context?.alert(\"搜索结果为空\") {\n                        setMessage(\"${searchGroup}分组搜索结果为空,是否切换到全部分组\")\n                        noButton()\n                        yesButton {\n                            AppConfig.searchGroup = \"\"\n                            upGroupMenu()\n                            viewModel.startSearch()\n                        }\n                    }\n                }\n            }\n        }\n    }\n\n    override fun onStart() {\n        super.onStart()\n        setLayout(1f, ViewGroup.LayoutParams.MATCH_PARENT)\n    }\n\n    override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) {\n        binding.toolBar.setBackgroundColor(primaryColor)\n        viewModel.initData(arguments, callBack?.oldBook, activity is ReadBookActivity)\n        showTitle()\n        initMenu()\n        initView()\n        initRecyclerView()\n        initSearchView()\n        initBottomBar()\n        initLiveData()\n        viewModel.searchFinishCallback = searchFinishCallback\n        activity?.onBackPressedDispatcher?.addCallback(this) {\n            if (binding.clToc.isVisible) {\n                binding.clToc.gone()\n                return@addCallback\n            }\n            dismissAllowingStateLoss()\n        }\n    }\n\n    override fun onDestroy() {\n        super.onDestroy()\n        viewModel.searchFinishCallback = null\n    }\n\n    private fun showTitle() {\n        binding.toolBar.title = viewModel.chapterTitle\n    }\n\n    private fun initMenu() {\n        binding.toolBar.inflateMenu(R.menu.change_source)\n        binding.toolBar.menu.applyTint(requireContext())\n        binding.toolBar.setOnMenuItemClickListener(this)\n        binding.toolBar.menu.findItem(R.id.menu_check_author)\n            ?.isChecked = AppConfig.changeSourceCheckAuthor\n        binding.toolBar.menu.findItem(R.id.menu_load_info)\n            ?.isChecked = AppConfig.changeSourceLoadInfo\n        binding.toolBar.menu.findItem(R.id.menu_load_toc)\n            ?.isChecked = AppConfig.changeSourceLoadToc\n        binding.toolBar.menu.findItem(R.id.menu_load_word_count)\n            ?.isChecked = AppConfig.changeSourceLoadWordCount\n    }\n\n    private fun initView() {\n        binding.ivHideToc.setOnClickListener {\n            binding.clToc.gone()\n        }\n        binding.flHideToc.elevation = requireContext().elevation\n    }\n\n    private fun initRecyclerView() {\n        binding.recyclerView.addItemDecoration(VerticalDivider(requireContext()))\n        binding.recyclerView.adapter = searchBookAdapter\n        searchBookAdapter.registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() {\n            override fun onItemRangeInserted(positionStart: Int, itemCount: Int) {\n                if (positionStart == 0) {\n                    binding.recyclerView.scrollToPosition(0)\n                }\n            }\n\n            override fun onItemRangeMoved(fromPosition: Int, toPosition: Int, itemCount: Int) {\n                if (toPosition == 0) {\n                    binding.recyclerView.scrollToPosition(0)\n                }\n            }\n        })\n        binding.recyclerViewToc.adapter = tocAdapter\n    }\n\n    private fun initSearchView() {\n        val searchView = binding.toolBar.menu.findItem(R.id.menu_screen).actionView as SearchView\n        searchView.setOnCloseListener {\n            showTitle()\n            false\n        }\n        searchView.setOnSearchClickListener {\n            binding.toolBar.title = \"\"\n            binding.toolBar.subtitle = \"\"\n        }\n        searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener {\n            override fun onQueryTextSubmit(query: String?): Boolean {\n                return false\n            }\n\n            override fun onQueryTextChange(newText: String?): Boolean {\n                viewModel.screen(newText)\n                return false\n            }\n\n        })\n    }\n\n    private fun initBottomBar() {\n        binding.tvDur.text = callBack?.oldBook?.originName\n        binding.tvDur.setOnClickListener {\n            scrollToDurSource()\n        }\n        binding.ivTop.setOnClickListener {\n            binding.recyclerView.scrollToPosition(0)\n        }\n        binding.ivBottom.setOnClickListener {\n            binding.recyclerView.scrollToPosition(searchBookAdapter.itemCount - 1)\n        }\n    }\n\n    private fun initLiveData() {\n        viewModel.searchStateData.observe(viewLifecycleOwner) {\n            binding.refreshProgressBar.isAutoLoading = it\n            if (it) {\n                startStopMenuItem?.let { item ->\n                    item.setIcon(R.drawable.ic_stop_black_24dp)\n                    item.setTitle(R.string.stop)\n                }\n            } else {\n                startStopMenuItem?.let { item ->\n                    item.setIcon(R.drawable.ic_refresh_black_24dp)\n                    item.setTitle(R.string.refresh)\n                }\n            }\n            binding.toolBar.menu.applyTint(requireContext())\n        }\n        lifecycleScope.launch {\n            lifecycle.currentStateFlow.first { it.isAtLeast(STARTED) }\n            viewModel.searchDataFlow.conflate().collect {\n                searchBookAdapter.setItems(it)\n                delay(1000)\n            }\n        }\n        lifecycleScope.launch {\n            appDb.bookSourceDao.flowEnabledGroups().conflate().collect {\n                groups.clear()\n                groups.addAll(it)\n                upGroupMenu()\n            }\n        }\n    }\n\n    private val startStopMenuItem: MenuItem?\n        get() = binding.toolBar.menu.findItem(R.id.menu_start_stop)\n\n    override fun onMenuItemClick(item: MenuItem?): Boolean {\n        when (item?.itemId) {\n            R.id.menu_check_author -> {\n                AppConfig.changeSourceCheckAuthor = !item.isChecked\n                item.isChecked = !item.isChecked\n                viewModel.refresh()\n            }\n\n            R.id.menu_load_info -> {\n                AppConfig.changeSourceLoadInfo = !item.isChecked\n                item.isChecked = !item.isChecked\n            }\n\n            R.id.menu_load_toc -> {\n                AppConfig.changeSourceLoadToc = !item.isChecked\n                item.isChecked = !item.isChecked\n            }\n\n            R.id.menu_load_word_count -> {\n                AppConfig.changeSourceLoadWordCount = !item.isChecked\n                item.isChecked = !item.isChecked\n                viewModel.onLoadWordCountChecked(item.isChecked)\n            }\n\n            R.id.menu_start_stop -> viewModel.startOrStopSearch()\n            R.id.menu_source_manage -> startActivity<BookSourceActivity>()\n            else -> if (item?.groupId == R.id.source_group && !item.isChecked) {\n                item.isChecked = true\n                if (item.title.toString() == getString(R.string.all_source)) {\n                    AppConfig.searchGroup = \"\"\n                } else {\n                    AppConfig.searchGroup = item.title.toString()\n                }\n                lifecycleScope.launch(IO) {\n                    viewModel.stopSearch()\n                    if (viewModel.refresh()) {\n                        viewModel.startSearch()\n                    }\n                }\n            }\n        }\n        return false\n    }\n\n    private fun scrollToDurSource() {\n        searchBookAdapter.getItems().forEachIndexed { index, searchBook ->\n            if (searchBook.bookUrl == oldBookUrl) {\n                (binding.recyclerView.layoutManager as LinearLayoutManager)\n                    .scrollToPositionWithOffset(index, 60.dpToPx())\n                return\n            }\n        }\n    }\n\n    override fun openToc(searchBook: SearchBook) {\n        this.searchBook = searchBook\n        tocAdapter.setItems(null)\n        binding.clToc.visible()\n        binding.loadingToc.visible()\n        val book = searchBook.toBook()\n        viewModel.getToc(book, { toc: List<BookChapter>, _: BookSource ->\n            tocAdapter.durChapterIndex =\n                BookHelp.getDurChapter(viewModel.chapterIndex, viewModel.chapterTitle, toc)\n            binding.loadingToc.gone()\n            tocAdapter.setItems(toc)\n            binding.recyclerViewToc.scrollToPosition(tocAdapter.durChapterIndex - 5)\n        }, {\n            binding.clToc.gone()\n            AppLog.put(\"单章换源获取目录出错\\n$it\", it, true)\n        })\n    }\n\n    override val oldBookUrl: String?\n        get() = callBack?.oldBook?.bookUrl\n\n    override fun topSource(searchBook: SearchBook) {\n        viewModel.topSource(searchBook)\n    }\n\n    override fun bottomSource(searchBook: SearchBook) {\n        viewModel.bottomSource(searchBook)\n    }\n\n    override fun editSource(searchBook: SearchBook) {\n        editSourceResult.launch {\n            putExtra(\"sourceUrl\", searchBook.origin)\n        }\n    }\n\n    override fun disableSource(searchBook: SearchBook) {\n        viewModel.disableSource(searchBook)\n    }\n\n    override fun deleteSource(searchBook: SearchBook) {\n        viewModel.del(searchBook)\n        if (oldBookUrl == searchBook.bookUrl) {\n            viewModel.autoChangeSource(callBack?.oldBook?.type) { book, toc, source ->\n                callBack?.changeTo(source, book, toc)\n            }\n        }\n    }\n\n    override fun setBookScore(searchBook: SearchBook, score: Int) {\n        viewModel.setBookScore(searchBook, score)\n    }\n\n    override fun getBookScore(searchBook: SearchBook): Int {\n        return viewModel.getBookScore(searchBook)\n    }\n\n    override fun clickChapter(bookChapter: BookChapter, nextChapterUrl: String?) {\n        searchBook?.let {\n            binding.loadingToc.visible()\n            viewModel.getContent(it.toBook(), bookChapter, nextChapterUrl, contentSuccess) { msg ->\n                binding.loadingToc.gone()\n                binding.clToc.gone()\n                toastOnUi(msg)\n            }\n        }\n    }\n\n    /**\n     * 更新分组菜单\n     */\n    private fun upGroupMenu() {\n        binding.toolBar.menu.findItem(R.id.menu_group)?.subMenu?.transaction { menu ->\n            val selectedGroup = AppConfig.searchGroup\n            menu.removeGroup(R.id.source_group)\n            val allItem = menu.add(R.id.source_group, Menu.NONE, Menu.NONE, R.string.all_source)\n            var hasSelectedGroup = false\n            groups.forEach { group ->\n                menu.add(R.id.source_group, Menu.NONE, Menu.NONE, group)?.let {\n                    if (group == selectedGroup) {\n                        it.isChecked = true\n                        hasSelectedGroup = true\n                    }\n                }\n            }\n            menu.setGroupCheckable(R.id.source_group, true, true)\n            if (!hasSelectedGroup) {\n                allItem.isChecked = true\n            }\n        }\n    }\n\n    override fun observeLiveBus() {\n        observeEvent<String>(EventBus.SOURCE_CHANGED) {\n            searchBookAdapter.notifyItemRangeChanged(\n                0,\n                searchBookAdapter.itemCount,\n                bundleOf(Pair(\"upCurSource\", oldBookUrl))\n            )\n        }\n    }\n\n    interface CallBack {\n        val oldBook: Book?\n        fun changeTo(source: BookSource, book: Book, toc: List<BookChapter>)\n        fun replaceContent(content: String)\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/book/changesource/ChangeChapterSourceViewModel.kt",
    "content": "package io.legado.app.ui.book.changesource\n\nimport android.app.Application\nimport android.os.Bundle\nimport io.legado.app.data.appDb\nimport io.legado.app.data.entities.Book\nimport io.legado.app.data.entities.BookChapter\nimport io.legado.app.exception.NoStackTraceException\nimport io.legado.app.model.webBook.WebBook\n\n@Suppress(\"MemberVisibilityCanBePrivate\")\nclass ChangeChapterSourceViewModel(application: Application) :\n    ChangeBookSourceViewModel(application) {\n\n    var chapterIndex: Int = 0\n    var chapterTitle: String = \"\"\n\n    override fun initData(arguments: Bundle?, book: Book?, fromReadBookActivity: Boolean) {\n        super.initData(arguments, book, fromReadBookActivity)\n        arguments?.let { bundle ->\n            bundle.getString(\"chapterTitle\")?.let {\n                chapterTitle = it\n            }\n            chapterIndex = bundle.getInt(\"chapterIndex\")\n        }\n    }\n\n    fun getContent(\n        book: Book,\n        chapter: BookChapter,\n        nextChapterUrl: String?,\n        success: (content: String) -> Unit,\n        error: (msg: String) -> Unit\n    ) {\n        execute {\n            val bookSource = appDb.bookSourceDao.getBookSource(book.origin)\n                ?: throw NoStackTraceException(\"书源不存在\")\n            WebBook.getContentAwait(bookSource, book, chapter, nextChapterUrl, false)\n        }.onSuccess {\n            success.invoke(it)\n        }.onError {\n            error.invoke(it.localizedMessage ?: \"获取正文出错\")\n        }\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/book/changesource/ChangeChapterTocAdapter.kt",
    "content": "package io.legado.app.ui.book.changesource\n\nimport android.content.Context\nimport android.view.ViewGroup\nimport io.legado.app.R\nimport io.legado.app.base.adapter.ItemViewHolder\nimport io.legado.app.base.adapter.RecyclerAdapter\nimport io.legado.app.data.entities.BookChapter\nimport io.legado.app.databinding.ItemChapterListBinding\nimport io.legado.app.lib.theme.ThemeUtils\nimport io.legado.app.lib.theme.accentColor\nimport io.legado.app.utils.getCompatColor\nimport io.legado.app.utils.gone\nimport io.legado.app.utils.visible\n\nclass ChangeChapterTocAdapter(context: Context, val callback: Callback) :\n    RecyclerAdapter<BookChapter, ItemChapterListBinding>(context) {\n\n    var durChapterIndex = 0\n\n    override fun getViewBinding(parent: ViewGroup): ItemChapterListBinding {\n        return ItemChapterListBinding.inflate(inflater, parent, false)\n    }\n\n    override fun convert(\n        holder: ItemViewHolder,\n        binding: ItemChapterListBinding,\n        item: BookChapter,\n        payloads: MutableList<Any>\n    ) {\n        binding.run {\n            val isDur = durChapterIndex == item.index\n            if (isDur) {\n                tvChapterName.setTextColor(context.accentColor)\n            } else {\n                tvChapterName.setTextColor(context.getCompatColor(R.color.primaryText))\n            }\n            tvChapterName.text = item.title\n            if (item.isVolume) {\n                //卷名，如第一卷 突出显示\n                tvChapterItem.setBackgroundColor(context.getCompatColor(R.color.btn_bg_press))\n            } else {\n                //普通章节 保持不变\n                tvChapterItem.background =\n                    ThemeUtils.resolveDrawable(context, android.R.attr.selectableItemBackground)\n            }\n            if (!item.tag.isNullOrEmpty() && !item.isVolume) {\n                //卷名不显示tag(更新时间规则)\n                tvTag.text = item.tag\n                tvTag.visible()\n            } else {\n                tvTag.gone()\n            }\n            ivChecked.setImageResource(R.drawable.ic_check)\n            ivChecked.visible(isDur)\n        }\n    }\n\n    override fun registerListener(holder: ItemViewHolder, binding: ItemChapterListBinding) {\n        holder.itemView.setOnClickListener {\n            getItem(holder.layoutPosition)?.let {\n                callback.clickChapter(it, getItem(holder.layoutPosition + 1)?.url)\n            }\n        }\n    }\n\n    interface Callback {\n        fun clickChapter(bookChapter: BookChapter, nextChapterUrl: String?)\n    }\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/book/explore/ExploreShowActivity.kt",
    "content": "package io.legado.app.ui.book.explore\n\nimport android.os.Bundle\nimport androidx.activity.viewModels\nimport androidx.core.os.bundleOf\nimport androidx.recyclerview.widget.RecyclerView\nimport io.legado.app.R\nimport io.legado.app.base.VMBaseActivity\nimport io.legado.app.data.entities.SearchBook\nimport io.legado.app.databinding.ActivityExploreShowBinding\nimport io.legado.app.databinding.ViewLoadMoreBinding\nimport io.legado.app.ui.book.info.BookInfoActivity\nimport io.legado.app.ui.widget.recycler.LoadMoreView\nimport io.legado.app.ui.widget.recycler.VerticalDivider\nimport io.legado.app.utils.applyNavigationBarPadding\nimport io.legado.app.utils.startActivity\nimport io.legado.app.utils.viewbindingdelegate.viewBinding\n\n/**\n * 发现列表\n */\nclass ExploreShowActivity : VMBaseActivity<ActivityExploreShowBinding, ExploreShowViewModel>(),\n    ExploreShowAdapter.CallBack {\n    override val binding by viewBinding(ActivityExploreShowBinding::inflate)\n    override val viewModel by viewModels<ExploreShowViewModel>()\n\n    private val adapter by lazy { ExploreShowAdapter(this, this) }\n    private val loadMoreView by lazy { LoadMoreView(this) }\n\n    override fun onActivityCreated(savedInstanceState: Bundle?) {\n        binding.titleBar.title = intent.getStringExtra(\"exploreName\")\n        initRecyclerView()\n        viewModel.booksData.observe(this) { upData(it) }\n        viewModel.initData(intent)\n        viewModel.errorLiveData.observe(this) {\n            loadMoreView.error(it)\n        }\n        viewModel.upAdapterLiveData.observe(this) {\n            adapter.notifyItemRangeChanged(0, adapter.itemCount, bundleOf(it to null))\n        }\n    }\n\n    private fun initRecyclerView() {\n        binding.recyclerView.addItemDecoration(VerticalDivider(this))\n        binding.recyclerView.adapter = adapter\n        binding.recyclerView.applyNavigationBarPadding()\n        adapter.addFooterView {\n            ViewLoadMoreBinding.bind(loadMoreView)\n        }\n        loadMoreView.startLoad()\n        loadMoreView.setOnClickListener {\n            if (!loadMoreView.isLoading) {\n                scrollToBottom(true)\n            }\n        }\n        binding.recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() {\n            override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {\n                super.onScrolled(recyclerView, dx, dy)\n                if (!recyclerView.canScrollVertically(1)) {\n                    scrollToBottom()\n                }\n            }\n        })\n    }\n\n    private fun scrollToBottom(forceLoad: Boolean = false) {\n        if ((loadMoreView.hasMore && !loadMoreView.isLoading) || forceLoad) {\n            loadMoreView.hasMore()\n            viewModel.explore()\n        }\n    }\n\n    private fun upData(books: List<SearchBook>) {\n        loadMoreView.stopLoad()\n        if (books.isEmpty() && adapter.isEmpty()) {\n            loadMoreView.noMore(getString(R.string.empty))\n        } else if (adapter.getActualItemCount() == books.size) {\n            loadMoreView.noMore()\n        } else {\n            adapter.setItems(books)\n        }\n    }\n\n    override fun isInBookshelf(book: SearchBook): Boolean {\n        return viewModel.isInBookShelf(book)\n    }\n\n    override fun showBookInfo(book: SearchBook) {\n        startActivity<BookInfoActivity> {\n            putExtra(\"name\", book.name)\n            putExtra(\"author\", book.author)\n            putExtra(\"bookUrl\", book.bookUrl)\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/book/explore/ExploreShowAdapter.kt",
    "content": "package io.legado.app.ui.book.explore\n\nimport android.content.Context\nimport android.os.Bundle\nimport android.view.ViewGroup\nimport androidx.core.view.isVisible\nimport io.legado.app.R\nimport io.legado.app.base.adapter.ItemViewHolder\nimport io.legado.app.base.adapter.RecyclerAdapter\nimport io.legado.app.data.entities.SearchBook\nimport io.legado.app.databinding.ItemSearchBinding\nimport io.legado.app.help.config.AppConfig\nimport io.legado.app.utils.gone\nimport io.legado.app.utils.visible\n\n\nclass ExploreShowAdapter(context: Context, val callBack: CallBack) :\n    RecyclerAdapter<SearchBook, ItemSearchBinding>(context) {\n\n    override fun getViewBinding(parent: ViewGroup): ItemSearchBinding {\n        return ItemSearchBinding.inflate(inflater, parent, false)\n    }\n\n    override fun convert(\n        holder: ItemViewHolder,\n        binding: ItemSearchBinding,\n        item: SearchBook,\n        payloads: MutableList<Any>\n    ) {\n        if (payloads.isEmpty()) {\n            bind(binding, item)\n        } else {\n            for (i in payloads.indices) {\n                val bundle = payloads[i] as Bundle\n                bindChange(binding, item, bundle)\n            }\n        }\n\n    }\n\n    private fun bind(binding: ItemSearchBinding, item: SearchBook) {\n        binding.run {\n            tvName.text = item.name\n            tvAuthor.text = context.getString(R.string.author_show, item.author)\n            ivInBookshelf.isVisible = callBack.isInBookshelf(item)\n            if (item.latestChapterTitle.isNullOrEmpty()) {\n                tvLasted.gone()\n            } else {\n                tvLasted.text = context.getString(R.string.lasted_show, item.latestChapterTitle)\n                tvLasted.visible()\n            }\n            tvIntroduce.text = item.trimIntro(context)\n            val kinds = item.getKindList()\n            if (kinds.isEmpty()) {\n                llKind.gone()\n            } else {\n                llKind.visible()\n                llKind.setLabels(kinds)\n            }\n            ivCover.load(\n                item.coverUrl,\n                item.name,\n                item.author,\n                AppConfig.loadCoverOnlyWifi,\n                item.origin\n            )\n        }\n    }\n\n    private fun bindChange(binding: ItemSearchBinding, item: SearchBook, bundle: Bundle) {\n        binding.run {\n            bundle.keySet().forEach {\n                when (it) {\n                    \"isInBookshelf\" -> ivInBookshelf.isVisible =\n                        callBack.isInBookshelf(item)\n                }\n            }\n        }\n    }\n\n    override fun registerListener(holder: ItemViewHolder, binding: ItemSearchBinding) {\n        holder.itemView.setOnClickListener {\n            getItem(holder.layoutPosition)?.let {\n                callBack.showBookInfo(it)\n            }\n        }\n    }\n\n    interface CallBack {\n        /**\n         * 是否已经加入书架\n         */\n        fun isInBookshelf(book: SearchBook): Boolean\n\n        fun showBookInfo(book: SearchBook)\n    }\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/book/explore/ExploreShowViewModel.kt",
    "content": "package io.legado.app.ui.book.explore\n\nimport android.app.Application\nimport android.content.Intent\nimport androidx.lifecycle.MutableLiveData\nimport androidx.lifecycle.viewModelScope\nimport io.legado.app.BuildConfig\nimport io.legado.app.base.BaseViewModel\nimport io.legado.app.constant.AppLog\nimport io.legado.app.data.appDb\nimport io.legado.app.data.entities.BookSource\nimport io.legado.app.data.entities.SearchBook\nimport io.legado.app.help.book.isNotShelf\nimport io.legado.app.model.webBook.WebBook\nimport io.legado.app.utils.printOnDebug\nimport io.legado.app.utils.stackTraceStr\nimport kotlinx.coroutines.Dispatchers.IO\nimport kotlinx.coroutines.ExperimentalCoroutinesApi\nimport kotlinx.coroutines.flow.catch\nimport kotlinx.coroutines.flow.mapLatest\nimport java.util.concurrent.ConcurrentHashMap\n\n\n@OptIn(ExperimentalCoroutinesApi::class)\nclass ExploreShowViewModel(application: Application) : BaseViewModel(application) {\n    val bookshelf: MutableSet<String> = ConcurrentHashMap.newKeySet()\n    val upAdapterLiveData = MutableLiveData<String>()\n    val booksData = MutableLiveData<List<SearchBook>>()\n    val errorLiveData = MutableLiveData<String>()\n    private var bookSource: BookSource? = null\n    private var exploreUrl: String? = null\n    private var page = 1\n    private var books = linkedSetOf<SearchBook>()\n\n    init {\n        execute {\n            appDb.bookDao.flowAll().mapLatest { books ->\n                val keys = arrayListOf<String>()\n                books.filterNot { it.isNotShelf }\n                    .forEach {\n                        keys.add(\"${it.name}-${it.author}\")\n                        keys.add(it.name)\n                        keys.add(it.bookUrl)\n                    }\n                keys\n            }.catch {\n                AppLog.put(\"发现列表界面获取书籍数据失败\\n${it.localizedMessage}\", it)\n            }.collect {\n                bookshelf.clear()\n                bookshelf.addAll(it)\n                upAdapterLiveData.postValue(\"isInBookshelf\")\n            }\n        }.onError {\n            AppLog.put(\"加载书架数据失败\", it)\n        }\n    }\n\n    fun initData(intent: Intent) {\n        execute {\n            val sourceUrl = intent.getStringExtra(\"sourceUrl\")\n            exploreUrl = intent.getStringExtra(\"exploreUrl\")\n            if (bookSource == null && sourceUrl != null) {\n                bookSource = appDb.bookSourceDao.getBookSource(sourceUrl)\n            }\n            explore()\n        }\n    }\n\n    fun explore() {\n        val source = bookSource\n        val url = exploreUrl\n        if (source == null || url == null) return\n        WebBook.exploreBook(viewModelScope, source, url, page)\n            .timeout(if (BuildConfig.DEBUG) 0L else 30000L)\n            .onSuccess(IO) { searchBooks ->\n                books.addAll(searchBooks)\n                booksData.postValue(books.toList())\n                appDb.searchBookDao.insert(*searchBooks.toTypedArray())\n                page++\n            }.onError {\n                it.printOnDebug()\n                errorLiveData.postValue(it.stackTraceStr)\n            }\n    }\n\n    fun isInBookShelf(book: SearchBook): Boolean {\n        val name = book.name\n        val author = book.author\n        val bookUrl = book.bookUrl\n        val key = if (author.isNotBlank()) \"$name-$author\" else name\n        return bookshelf.contains(key) || bookshelf.contains(bookUrl)\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/book/group/GroupEditDialog.kt",
    "content": "package io.legado.app.ui.book.group\n\nimport android.os.Bundle\nimport android.view.View\nimport android.view.ViewGroup\nimport androidx.fragment.app.viewModels\nimport io.legado.app.R\nimport io.legado.app.base.BaseDialogFragment\nimport io.legado.app.data.entities.BookGroup\nimport io.legado.app.databinding.DialogBookGroupEditBinding\nimport io.legado.app.lib.dialogs.alert\nimport io.legado.app.lib.theme.primaryColor\nimport io.legado.app.ui.file.HandleFileContract\nimport io.legado.app.utils.FileUtils\nimport io.legado.app.utils.MD5Utils\nimport io.legado.app.utils.externalFiles\nimport io.legado.app.utils.gone\nimport io.legado.app.utils.inputStream\nimport io.legado.app.utils.readUri\nimport io.legado.app.utils.setLayout\nimport io.legado.app.utils.toastOnUi\nimport io.legado.app.utils.viewbindingdelegate.viewBinding\nimport io.legado.app.utils.visible\nimport splitties.init.appCtx\nimport splitties.views.onClick\nimport java.io.FileOutputStream\n\nclass GroupEditDialog() : BaseDialogFragment(R.layout.dialog_book_group_edit) {\n\n    constructor(bookGroup: BookGroup? = null) : this() {\n        arguments = Bundle().apply {\n            putParcelable(\"group\", bookGroup?.copy())\n        }\n    }\n\n    private val binding by viewBinding(DialogBookGroupEditBinding::bind)\n    private val viewModel by viewModels<GroupViewModel>()\n    private var bookGroup: BookGroup? = null\n    private val selectImage = registerForActivityResult(HandleFileContract()) {\n        it.uri ?: return@registerForActivityResult\n        readUri(it.uri) { fileDoc, inputStream ->\n            try {\n                var file = requireContext().externalFiles\n                val suffix = fileDoc.name.substringAfterLast(\".\")\n                val fileName = it.uri.inputStream(requireContext()).getOrThrow().use { tmp ->\n                    MD5Utils.md5Encode(tmp) + \".$suffix\"\n                }\n                file = FileUtils.createFileIfNotExist(file, \"covers\", fileName)\n                FileOutputStream(file).use { outputStream ->\n                    inputStream.copyTo(outputStream)\n                }\n                binding.ivCover.load(file.absolutePath)\n            } catch (e: Exception) {\n                appCtx.toastOnUi(e.localizedMessage)\n            }\n        }\n    }\n\n    override fun onStart() {\n        super.onStart()\n        setLayout(0.9f, ViewGroup.LayoutParams.WRAP_CONTENT)\n    }\n\n    override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) {\n        binding.toolBar.setBackgroundColor(primaryColor)\n        @Suppress(\"DEPRECATION\")\n        bookGroup = arguments?.getParcelable(\"group\")\n        bookGroup?.let {\n            binding.btnDelete.visible(it.groupId > 0 || it.groupId == Long.MIN_VALUE)\n            binding.tieGroupName.setText(it.groupName)\n            binding.ivCover.load(it.cover)\n            if (it.bookSort + 1 !in 0..<binding.spSort.count) {\n                it.bookSort = -1\n            }\n            binding.spSort.setSelection(it.bookSort + 1)\n            binding.cbEnableRefresh.isChecked = it.enableRefresh\n        } ?: let {\n            binding.toolBar.title = getString(R.string.add_group)\n            binding.btnDelete.gone()\n            binding.ivCover.load()\n        }\n        binding.run {\n            ivCover.onClick {\n                selectImage.launch {\n                    mode = HandleFileContract.IMAGE\n                }\n            }\n            btnCancel.onClick {\n                dismiss()\n            }\n            btnOk.onClick {\n                val groupName = tieGroupName.text?.toString()\n                if (groupName.isNullOrEmpty()) {\n                    toastOnUi(\"分组名称不能为空\")\n                } else {\n                    val bookSort = binding.spSort.selectedItemPosition - 1\n                    val coverPath = binding.ivCover.bitmapPath\n                    val enableRefresh = binding.cbEnableRefresh.isChecked\n                    bookGroup?.let {\n                        it.groupName = groupName\n                        it.cover = coverPath\n                        it.bookSort = bookSort\n                        it.enableRefresh = enableRefresh\n                        viewModel.upGroup(it) {\n                            dismiss()\n                        }\n                    } ?: let {\n                        viewModel.addGroup(\n                            groupName,\n                            bookSort,\n                            enableRefresh,\n                            coverPath\n                        ) {\n                            dismiss()\n                        }\n                    }\n                }\n\n            }\n            btnDelete.onClick {\n                deleteGroup {\n                    bookGroup?.let {\n                        viewModel.delGroup(it) {\n                            dismiss()\n                        }\n                    }\n                }\n            }\n        }\n    }\n\n    private fun deleteGroup(ok: () -> Unit) {\n        alert(R.string.delete, R.string.sure_del) {\n            yesButton {\n                ok.invoke()\n            }\n            noButton()\n        }\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/book/group/GroupManageDialog.kt",
    "content": "package io.legado.app.ui.book.group\n\nimport android.content.Context\nimport android.os.Bundle\nimport android.view.MenuItem\nimport android.view.View\nimport android.view.ViewGroup\nimport androidx.appcompat.widget.Toolbar\nimport androidx.fragment.app.viewModels\nimport androidx.lifecycle.lifecycleScope\nimport androidx.recyclerview.widget.ItemTouchHelper\nimport androidx.recyclerview.widget.LinearLayoutManager\nimport androidx.recyclerview.widget.RecyclerView\nimport io.legado.app.R\nimport io.legado.app.base.BaseDialogFragment\nimport io.legado.app.base.adapter.ItemViewHolder\nimport io.legado.app.base.adapter.RecyclerAdapter\nimport io.legado.app.constant.AppLog\nimport io.legado.app.data.appDb\nimport io.legado.app.data.entities.BookGroup\nimport io.legado.app.databinding.DialogRecyclerViewBinding\nimport io.legado.app.databinding.ItemBookGroupManageBinding\nimport io.legado.app.lib.theme.accentColor\nimport io.legado.app.lib.theme.backgroundColor\nimport io.legado.app.lib.theme.primaryColor\nimport io.legado.app.ui.widget.recycler.ItemTouchCallback\nimport io.legado.app.ui.widget.recycler.VerticalDivider\nimport io.legado.app.utils.applyTint\nimport io.legado.app.utils.setLayout\nimport io.legado.app.utils.showDialogFragment\nimport io.legado.app.utils.toastOnUi\nimport io.legado.app.utils.viewbindingdelegate.viewBinding\nimport io.legado.app.utils.visible\nimport kotlinx.coroutines.Dispatchers.IO\nimport kotlinx.coroutines.flow.catch\nimport kotlinx.coroutines.flow.conflate\nimport kotlinx.coroutines.flow.flowOn\nimport kotlinx.coroutines.launch\n\n/**\n * 书籍分组管理\n */\nclass GroupManageDialog : BaseDialogFragment(R.layout.dialog_recycler_view),\n    Toolbar.OnMenuItemClickListener {\n\n    private val viewModel: GroupViewModel by viewModels()\n    private val binding by viewBinding(DialogRecyclerViewBinding::bind)\n    private val adapter by lazy { GroupAdapter(requireContext()) }\n\n    override fun onStart() {\n        super.onStart()\n        setLayout(0.9f, 0.9f)\n    }\n\n    override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) {\n        binding.toolBar.setBackgroundColor(primaryColor)\n        binding.toolBar.setTitle(R.string.group_manage)\n        initView()\n        initData()\n        initMenu()\n    }\n\n    private fun initView() {\n        binding.recyclerView.layoutManager = LinearLayoutManager(requireContext())\n        binding.recyclerView.addItemDecoration(VerticalDivider(requireContext()))\n        binding.recyclerView.adapter = adapter\n        val itemTouchCallback = ItemTouchCallback(adapter)\n        itemTouchCallback.isCanDrag = true\n        ItemTouchHelper(itemTouchCallback).attachToRecyclerView(binding.recyclerView)\n        binding.tvOk.setTextColor(requireContext().accentColor)\n        binding.tvOk.visible()\n        binding.tvOk.setOnClickListener {\n            dismissAllowingStateLoss()\n        }\n    }\n\n    private fun initData() {\n        lifecycleScope.launch {\n            appDb.bookGroupDao.flowAll().catch {\n                AppLog.put(\"书籍分组管理界面获取分组数据失败\\n${it.localizedMessage}\", it)\n            }.flowOn(IO).conflate().collect {\n                adapter.setItems(it)\n            }\n        }\n    }\n\n    private fun initMenu() {\n        binding.toolBar.setOnMenuItemClickListener(this)\n        binding.toolBar.inflateMenu(R.menu.book_group_manage)\n        binding.toolBar.menu.applyTint(requireContext())\n    }\n\n    override fun onMenuItemClick(item: MenuItem?): Boolean {\n        when (item?.itemId) {\n            R.id.menu_add -> {\n                if (appDb.bookGroupDao.canAddGroup) {\n                    showDialogFragment(GroupEditDialog())\n                } else {\n                    toastOnUi(\"分组已达上限(64个)\")\n                }\n            }\n        }\n        return true\n    }\n\n    private inner class GroupAdapter(context: Context) :\n        RecyclerAdapter<BookGroup, ItemBookGroupManageBinding>(context),\n        ItemTouchCallback.Callback {\n\n        private var isMoved = false\n\n        override fun getViewBinding(parent: ViewGroup): ItemBookGroupManageBinding {\n            return ItemBookGroupManageBinding.inflate(inflater, parent, false)\n        }\n\n        override fun convert(\n            holder: ItemViewHolder,\n            binding: ItemBookGroupManageBinding,\n            item: BookGroup,\n            payloads: MutableList<Any>\n        ) {\n            binding.run {\n                root.setBackgroundColor(context.backgroundColor)\n                tvGroup.text = item.getManageName(context)\n                swShow.isChecked = item.show\n            }\n        }\n\n        override fun registerListener(holder: ItemViewHolder, binding: ItemBookGroupManageBinding) {\n            binding.run {\n                tvEdit.setOnClickListener {\n                    getItem(holder.layoutPosition)?.let { bookGroup ->\n                        showDialogFragment(\n                            GroupEditDialog(bookGroup)\n                        )\n                    }\n                }\n                swShow.setOnUserCheckedChangeListener { isChecked ->\n                    getItem(holder.layoutPosition)?.let {\n                        viewModel.upGroup(it.copy(show = isChecked))\n                    }\n                }\n            }\n        }\n\n        override fun swap(srcPosition: Int, targetPosition: Int): Boolean {\n            swapItem(srcPosition, targetPosition)\n            isMoved = true\n            return true\n        }\n\n        override fun onClearView(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder) {\n            if (isMoved) {\n                for ((index, item) in getItems().withIndex()) {\n                    item.order = index + 1\n                }\n                viewModel.upGroup(*getItems().toTypedArray())\n            }\n            isMoved = false\n        }\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/book/group/GroupSelectDialog.kt",
    "content": "package io.legado.app.ui.book.group\n\nimport android.content.Context\nimport android.os.Bundle\nimport android.view.MenuItem\nimport android.view.View\nimport android.view.ViewGroup\nimport androidx.appcompat.widget.Toolbar\nimport androidx.fragment.app.viewModels\nimport androidx.lifecycle.lifecycleScope\nimport androidx.recyclerview.widget.ItemTouchHelper\nimport androidx.recyclerview.widget.LinearLayoutManager\nimport androidx.recyclerview.widget.RecyclerView\nimport io.legado.app.R\nimport io.legado.app.base.BaseDialogFragment\nimport io.legado.app.base.adapter.ItemViewHolder\nimport io.legado.app.base.adapter.RecyclerAdapter\nimport io.legado.app.data.appDb\nimport io.legado.app.data.entities.BookGroup\nimport io.legado.app.databinding.DialogBookGroupPickerBinding\nimport io.legado.app.databinding.ItemGroupSelectBinding\nimport io.legado.app.lib.theme.accentColor\nimport io.legado.app.lib.theme.backgroundColor\nimport io.legado.app.lib.theme.primaryColor\nimport io.legado.app.ui.widget.recycler.ItemTouchCallback\nimport io.legado.app.ui.widget.recycler.VerticalDivider\nimport io.legado.app.utils.applyTint\nimport io.legado.app.utils.setLayout\nimport io.legado.app.utils.showDialogFragment\nimport io.legado.app.utils.viewbindingdelegate.viewBinding\nimport kotlinx.coroutines.flow.conflate\nimport kotlinx.coroutines.launch\n\n\nclass GroupSelectDialog() : BaseDialogFragment(R.layout.dialog_book_group_picker),\n    Toolbar.OnMenuItemClickListener {\n\n    constructor(groupId: Long, requestCode: Int = -1) : this() {\n        arguments = Bundle().apply {\n            putLong(\"groupId\", groupId)\n            putInt(\"requestCode\", requestCode)\n        }\n    }\n\n    private val binding by viewBinding(DialogBookGroupPickerBinding::bind)\n    private var requestCode: Int = -1\n    private val viewModel: GroupViewModel by viewModels()\n    private val adapter by lazy { GroupAdapter(requireContext()) }\n    private val callBack get() = (activity as? CallBack)\n    private var groupId: Long = 0\n\n    override fun onStart() {\n        super.onStart()\n        setLayout(0.9f, 0.9f)\n    }\n\n    override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) {\n        binding.toolBar.setBackgroundColor(primaryColor)\n        arguments?.let {\n            groupId = it.getLong(\"groupId\")\n            requestCode = it.getInt(\"requestCode\", -1)\n        }\n        initView()\n        initData()\n    }\n\n    private fun initView() {\n        binding.toolBar.title = getString(R.string.group_select)\n        binding.toolBar.inflateMenu(R.menu.book_group_manage)\n        binding.toolBar.menu.applyTint(requireContext())\n        binding.toolBar.setOnMenuItemClickListener(this)\n        binding.recyclerView.layoutManager = LinearLayoutManager(requireContext())\n        binding.recyclerView.addItemDecoration(VerticalDivider(requireContext()))\n        binding.recyclerView.adapter = adapter\n        val itemTouchCallback = ItemTouchCallback(adapter)\n        itemTouchCallback.isCanDrag = true\n        ItemTouchHelper(itemTouchCallback).attachToRecyclerView(binding.recyclerView)\n        binding.tvCancel.setOnClickListener {\n            dismissAllowingStateLoss()\n        }\n        binding.tvOk.setTextColor(requireContext().accentColor)\n        binding.tvOk.setOnClickListener {\n            callBack?.upGroup(requestCode, groupId)\n            dismissAllowingStateLoss()\n        }\n    }\n\n    private fun initData() {\n        lifecycleScope.launch {\n            appDb.bookGroupDao.flowSelect().conflate().collect {\n                adapter.setItems(it)\n            }\n        }\n    }\n\n    override fun onMenuItemClick(item: MenuItem?): Boolean {\n        when (item?.itemId) {\n            R.id.menu_add -> showDialogFragment(\n                GroupEditDialog()\n            )\n        }\n        return true\n    }\n\n    private inner class GroupAdapter(context: Context) :\n        RecyclerAdapter<BookGroup, ItemGroupSelectBinding>(context),\n        ItemTouchCallback.Callback {\n\n        private var isMoved: Boolean = false\n\n        override fun getViewBinding(parent: ViewGroup): ItemGroupSelectBinding {\n            return ItemGroupSelectBinding.inflate(inflater, parent, false)\n        }\n\n        override fun convert(\n            holder: ItemViewHolder,\n            binding: ItemGroupSelectBinding,\n            item: BookGroup,\n            payloads: MutableList<Any>\n        ) {\n            binding.run {\n                root.setBackgroundColor(context.backgroundColor)\n                cbGroup.text = item.groupName\n                cbGroup.isChecked = (groupId and item.groupId) > 0\n            }\n        }\n\n        override fun registerListener(holder: ItemViewHolder, binding: ItemGroupSelectBinding) {\n            binding.run {\n                cbGroup.setOnUserCheckedChangeListener { isChecked ->\n                    getItem(holder.layoutPosition)?.let {\n                        groupId = if (isChecked) {\n                            groupId + it.groupId\n                        } else {\n                            groupId - it.groupId\n                        }\n                    }\n                }\n                tvEdit.setOnClickListener {\n                    showDialogFragment(\n                        GroupEditDialog(getItem(holder.layoutPosition))\n                    )\n                }\n            }\n        }\n\n        override fun swap(srcPosition: Int, targetPosition: Int): Boolean {\n            swapItem(srcPosition, targetPosition)\n            isMoved = true\n            return true\n        }\n\n        override fun onClearView(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder) {\n            if (isMoved) {\n                for ((index, item) in getItems().withIndex()) {\n                    item.order = index + 1\n                }\n                viewModel.upGroup(*getItems().toTypedArray())\n            }\n            isMoved = false\n        }\n    }\n\n    interface CallBack {\n        fun upGroup(requestCode: Int, groupId: Long)\n    }\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/book/group/GroupViewModel.kt",
    "content": "package io.legado.app.ui.book.group\n\nimport android.app.Application\nimport io.legado.app.base.BaseViewModel\nimport io.legado.app.data.appDb\nimport io.legado.app.data.entities.BookGroup\n\nclass GroupViewModel(application: Application) : BaseViewModel(application) {\n\n    fun upGroup(vararg bookGroup: BookGroup, finally: (() -> Unit)? = null) {\n        execute {\n            appDb.bookGroupDao.update(*bookGroup)\n        }.onFinally {\n            finally?.invoke()\n        }\n    }\n\n    fun addGroup(\n        groupName: String,\n        bookSort: Int,\n        enableRefresh: Boolean,\n        cover: String?,\n        finally: () -> Unit\n    ) {\n        execute {\n            val groupId = appDb.bookGroupDao.getUnusedId()\n            val bookGroup = BookGroup(\n                groupId = groupId,\n                groupName = groupName,\n                cover = cover,\n                bookSort = bookSort,\n                enableRefresh = enableRefresh,\n                order = appDb.bookGroupDao.maxOrder.plus(1)\n            )\n            appDb.bookGroupDao.getByID(groupId) ?: appDb.bookDao.removeGroup(groupId)\n            appDb.bookGroupDao.insert(bookGroup)\n        }.onFinally {\n            finally()\n        }\n    }\n\n    fun delGroup(bookGroup: BookGroup, finally: () -> Unit) {\n        execute {\n            appDb.bookGroupDao.delete(bookGroup)\n            appDb.bookDao.removeGroup(bookGroup.groupId)\n        }.onFinally {\n            finally()\n        }\n    }\n\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/book/import/BaseImportBookActivity.kt",
    "content": "package io.legado.app.ui.book.import\n\nimport android.os.Bundle\nimport androidx.appcompat.widget.SearchView\nimport androidx.lifecycle.ViewModel\nimport io.legado.app.R\nimport io.legado.app.base.VMBaseActivity\nimport io.legado.app.constant.AppPattern\nimport io.legado.app.data.appDb\nimport io.legado.app.data.entities.Book\nimport io.legado.app.databinding.ActivityImportBookBinding\nimport io.legado.app.help.config.AppConfig\nimport io.legado.app.lib.dialogs.alert\nimport io.legado.app.lib.dialogs.selector\nimport io.legado.app.lib.theme.primaryTextColor\nimport io.legado.app.model.localBook.LocalBook\nimport io.legado.app.ui.file.HandleFileContract\nimport io.legado.app.utils.ArchiveUtils\nimport io.legado.app.utils.FileDoc\nimport io.legado.app.utils.applyTint\nimport io.legado.app.utils.startActivityForBook\nimport io.legado.app.utils.toastOnUi\nimport io.legado.app.utils.viewbindingdelegate.viewBinding\nimport kotlinx.coroutines.suspendCancellableCoroutine\nimport kotlin.coroutines.resume\n\nabstract class BaseImportBookActivity<VM : ViewModel> :\n    VMBaseActivity<ActivityImportBookBinding, VM>() {\n\n    final override val binding by viewBinding(ActivityImportBookBinding::inflate)\n\n    private var localBookTreeSelectListener: ((Boolean) -> Unit)? = null\n    protected val searchView: SearchView by lazy {\n        binding.titleBar.findViewById(R.id.search_view)\n    }\n\n    val localBookTreeSelect = registerForActivityResult(HandleFileContract()) {\n        it.uri?.let { treeUri ->\n            AppConfig.defaultBookTreeUri = treeUri.toString()\n            localBookTreeSelectListener?.invoke(true)\n        } ?: localBookTreeSelectListener?.invoke(false)\n    }\n\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n        initSearchView()\n    }\n\n    /**\n     * 设置书籍保存位置\n     */\n    protected suspend fun setBookStorage() = suspendCancellableCoroutine sc@{ block ->\n        localBookTreeSelectListener = {\n            localBookTreeSelectListener = null\n            block.resume(it)\n        }\n        //测试书籍保存位置是否设置\n        if (!AppConfig.defaultBookTreeUri.isNullOrBlank()) {\n            localBookTreeSelectListener = null\n            block.resume(true)\n            return@sc\n        }\n        //测试读写??\n        val storageHelp = String(assets.open(\"storageHelp.md\").readBytes())\n        val hint = getString(R.string.select_book_folder)\n        alert(hint, storageHelp) {\n            okButton {\n                localBookTreeSelect.launch {\n                    title = hint\n                }\n            }\n            cancelButton {\n                localBookTreeSelectListener = null\n                block.resume(false)\n            }\n            onCancelled {\n                localBookTreeSelectListener = null\n                block.resume(false)\n            }\n        }\n    }\n\n    abstract fun onSearchTextChange(newText: String?)\n\n    protected fun startReadBook(book: Book) {\n        startActivityForBook(book)\n    }\n\n    protected fun onArchiveFileClick(fileDoc: FileDoc) {\n        val fileNames = ArchiveUtils.getArchiveFilesName(fileDoc) {\n            it.matches(AppPattern.bookFileRegex)\n        }\n        if (fileNames.size == 1) {\n            val name = fileNames[0]\n            appDb.bookDao.getBookByFileName(name)?.let {\n                startReadBook(it)\n            } ?: showImportAlert(fileDoc, name)\n        } else {\n            showSelectBookReadAlert(fileDoc, fileNames)\n        }\n    }\n\n    private fun showSelectBookReadAlert(fileDoc: FileDoc, fileNames: List<String>) {\n        if (fileNames.isEmpty()) {\n            toastOnUi(R.string.unsupport_archivefile_entry)\n            return\n        }\n        selector(\n            R.string.start_read,\n            fileNames\n        ) { _, name, _ ->\n            appDb.bookDao.getBookByFileName(name)?.let {\n                startReadBook(it)\n            } ?: showImportAlert(fileDoc, name)\n        }\n    }\n\n    /* 添加压缩包内指定文件到书架 */\n    private inline fun addArchiveToBookShelf(\n        fileDoc: FileDoc,\n        fileName: String,\n        onSuccess: (Book) -> Unit\n    ) {\n        LocalBook.importArchiveFile(fileDoc.uri, fileName) {\n            it.contains(fileName)\n        }.firstOrNull()?.run {\n            onSuccess.invoke(this)\n        }\n    }\n\n    /* 提示是否重新导入所点击的压缩文件 */\n    private fun showImportAlert(fileDoc: FileDoc, fileName: String) {\n        alert(\n            R.string.draw,\n            R.string.no_book_found_bookshelf\n        ) {\n            okButton {\n                addArchiveToBookShelf(fileDoc, fileName) {\n                    startReadBook(it)\n                }\n            }\n            noButton()\n        }\n    }\n\n    private fun initSearchView() {\n        searchView.applyTint(primaryTextColor)\n        searchView.isSubmitButtonEnabled = true\n        searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener {\n            override fun onQueryTextSubmit(query: String?): Boolean {\n                return false\n            }\n\n            override fun onQueryTextChange(newText: String?): Boolean {\n                onSearchTextChange(newText)\n                return false\n            }\n        })\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/book/import/local/ImportBook.kt",
    "content": "package io.legado.app.ui.book.import.local\n\nimport io.legado.app.model.localBook.LocalBook\nimport io.legado.app.utils.FileDoc\n\ndata class ImportBook(\n    val file: FileDoc,\n    var isOnBookShelf: Boolean = !file.isDir && LocalBook.isOnBookShelf(file.name)\n) {\n    val name get() = file.name\n    val isDir get() = file.isDir\n    val size get() = file.size\n    val lastModified get() = file.lastModified\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/book/import/local/ImportBookActivity.kt",
    "content": "package io.legado.app.ui.book.import.local\n\nimport android.annotation.SuppressLint\nimport android.net.Uri\nimport android.os.Bundle\nimport android.view.Menu\nimport android.view.MenuItem\nimport androidx.activity.addCallback\nimport androidx.activity.viewModels\nimport androidx.appcompat.widget.PopupMenu\nimport androidx.core.net.toUri\nimport androidx.documentfile.provider.DocumentFile\nimport androidx.lifecycle.lifecycleScope\nimport androidx.recyclerview.widget.LinearLayoutManager\nimport io.legado.app.R\nimport io.legado.app.constant.PreferKey\nimport io.legado.app.data.appDb\nimport io.legado.app.databinding.DialogEditTextBinding\nimport io.legado.app.help.config.AppConfig\nimport io.legado.app.lib.dialogs.alert\nimport io.legado.app.lib.permission.Permissions\nimport io.legado.app.lib.permission.PermissionsCompat\nimport io.legado.app.lib.theme.backgroundColor\nimport io.legado.app.ui.book.import.BaseImportBookActivity\nimport io.legado.app.ui.file.HandleFileContract\nimport io.legado.app.ui.widget.SelectActionBar\nimport io.legado.app.utils.ArchiveUtils\nimport io.legado.app.utils.FileDoc\nimport io.legado.app.utils.gone\nimport io.legado.app.utils.isContentScheme\nimport io.legado.app.utils.isUri\nimport io.legado.app.utils.launch\nimport io.legado.app.utils.putPrefInt\nimport io.legado.app.utils.visible\nimport kotlinx.coroutines.Dispatchers.IO\nimport kotlinx.coroutines.Dispatchers.Main\nimport kotlinx.coroutines.Job\nimport kotlinx.coroutines.flow.conflate\nimport kotlinx.coroutines.launch\nimport kotlinx.coroutines.withContext\nimport java.io.File\n\n/**\n * 导入本地书籍界面\n */\nclass ImportBookActivity : BaseImportBookActivity<ImportBookViewModel>(),\n    PopupMenu.OnMenuItemClickListener,\n    ImportBookAdapter.CallBack,\n    SelectActionBar.CallBack {\n\n    override val viewModel by viewModels<ImportBookViewModel>()\n    private val adapter by lazy { ImportBookAdapter(this, this) }\n    private var scanDocJob: Job? = null\n\n    private val selectFolder = registerForActivityResult(HandleFileContract()) {\n        it.uri?.let { uri ->\n            AppConfig.importBookPath = uri.toString()\n            initRootDoc(true)\n        }\n    }\n\n    override fun onActivityCreated(savedInstanceState: Bundle?) {\n        searchView.queryHint = getString(R.string.screen) + \" • \" + getString(R.string.local_book)\n        onBackPressedDispatcher.addCallback(this) {\n            if (!goBackDir()) {\n                finish()\n            }\n        }\n        lifecycleScope.launch {\n            initView()\n            initEvent()\n            if (setBookStorage() && AppConfig.importBookPath.isNullOrBlank()) {\n                AppConfig.importBookPath = AppConfig.defaultBookTreeUri\n            }\n            initData()\n        }\n    }\n\n    override fun onCompatCreateOptionsMenu(menu: Menu): Boolean {\n        menuInflater.inflate(R.menu.import_book, menu)\n        return super.onCompatCreateOptionsMenu(menu)\n    }\n\n    override fun onMenuOpened(featureId: Int, menu: Menu): Boolean {\n        menu.findItem(R.id.menu_sort_name)?.isChecked = viewModel.sort == 0\n        menu.findItem(R.id.menu_sort_size)?.isChecked = viewModel.sort == 1\n        menu.findItem(R.id.menu_sort_time)?.isChecked = viewModel.sort == 2\n        return super.onMenuOpened(featureId, menu)\n    }\n\n    override fun onCompatOptionsItemSelected(item: MenuItem): Boolean {\n        when (item.itemId) {\n            R.id.menu_select_folder -> selectFolder.launch()\n            R.id.menu_scan_folder -> scanFolder()\n            R.id.menu_import_file_name -> alertImportFileName()\n            R.id.menu_sort_name -> upSort(0)\n            R.id.menu_sort_size -> upSort(1)\n            R.id.menu_sort_time -> upSort(2)\n        }\n        return super.onCompatOptionsItemSelected(item)\n    }\n\n    override fun onMenuItemClick(item: MenuItem?): Boolean {\n        when (item?.itemId) {\n            R.id.menu_del_selection -> viewModel.deleteDoc(adapter.selected) {\n                adapter.removeSelection()\n            }\n        }\n        return false\n    }\n\n    override fun selectAll(selectAll: Boolean) {\n        adapter.selectAll(selectAll)\n    }\n\n    override fun revertSelection() {\n        adapter.revertSelection()\n    }\n\n    @SuppressLint(\"NotifyDataSetChanged\")\n    override fun onClickSelectBarMainAction() {\n        viewModel.addToBookshelf(adapter.selected) {\n            adapter.selected.forEach {\n                it.isOnBookShelf = true\n            }\n            adapter.selected.clear()\n            adapter.notifyDataSetChanged()\n        }\n    }\n\n    private fun initView() {\n        binding.layTop.setBackgroundColor(backgroundColor)\n        binding.tvEmptyMsg.setText(R.string.empty_msg_import_book)\n        binding.recyclerView.layoutManager = LinearLayoutManager(this)\n        binding.recyclerView.adapter = adapter\n        binding.recyclerView.recycledViewPool.setMaxRecycledViews(0, 15)\n        binding.selectActionBar.setMainActionText(R.string.add_to_bookshelf)\n        binding.selectActionBar.inflateMenu(R.menu.import_book_sel)\n        binding.selectActionBar.setOnMenuItemClickListener(this)\n        binding.selectActionBar.setCallBack(this)\n    }\n\n    private fun initEvent() {\n        binding.tvGoBack.setOnClickListener {\n            goBackDir()\n        }\n    }\n\n    private fun initData() {\n        viewModel.dataFlowStart = {\n            initRootDoc()\n        }\n        lifecycleScope.launch {\n            viewModel.dataFlow.conflate().collect { docs ->\n                adapter.setItems(docs)\n            }\n        }\n    }\n\n    private fun initRootDoc(changedFolder: Boolean = false) {\n        if (viewModel.rootDoc != null && !changedFolder) {\n            upPath()\n        } else {\n            val lastPath = AppConfig.importBookPath\n            if (lastPath.isNullOrBlank()) {\n                binding.tvEmptyMsg.visible()\n                selectFolder.launch()\n            } else {\n                val rootUri = if (lastPath.isUri()) {\n                    lastPath.toUri()\n                } else {\n                    Uri.fromFile(File(lastPath))\n                }\n                when {\n                    rootUri.isContentScheme() -> initRootPath(rootUri)\n                    else -> initRootPath(rootUri.path!!)\n                }\n            }\n        }\n    }\n\n    private fun initRootPath(rootUri: Uri) {\n        kotlin.runCatching {\n            val doc = DocumentFile.fromTreeUri(this, rootUri)\n            if (doc == null || doc.name.isNullOrEmpty() || !doc.isDirectory) {\n                binding.tvEmptyMsg.visible()\n                selectFolder.launch()\n            } else {\n                viewModel.subDocs.clear()\n                viewModel.rootDoc = FileDoc.fromDocumentFile(doc)\n                upPath()\n            }\n        }.onFailure {\n            binding.tvEmptyMsg.visible()\n            selectFolder.launch()\n        }\n    }\n\n    private fun initRootPath(path: String) {\n        binding.tvEmptyMsg.visible()\n        PermissionsCompat.Builder()\n            .addPermissions(*Permissions.Group.STORAGE)\n            .rationale(R.string.tip_perm_request_storage)\n            .onGranted {\n                kotlin.runCatching {\n                    val file = File(path)\n                    if (!file.isDirectory) {\n                        binding.tvEmptyMsg.visible()\n                        selectFolder.launch()\n                    } else {\n                        viewModel.subDocs.clear()\n                        viewModel.rootDoc = FileDoc.fromFile(file)\n                        upPath()\n                    }\n                }.onFailure {\n                    binding.tvEmptyMsg.visible()\n                    selectFolder.launch()\n                }\n            }\n            .request()\n    }\n\n    private fun upSort(sort: Int) {\n        viewModel.sort = sort\n        putPrefInt(PreferKey.localBookImportSort, sort)\n        if (scanDocJob?.isActive != true) {\n            viewModel.dataCallback?.upAdapter()\n        }\n    }\n\n    @Synchronized\n    private fun upPath() {\n        binding.tvGoBack.isEnabled = viewModel.subDocs.isNotEmpty()\n        viewModel.rootDoc?.let {\n            scanDocJob?.cancel()\n            upDocs(it)\n        }\n    }\n\n    private fun upDocs(rootDoc: FileDoc) {\n        binding.tvEmptyMsg.gone()\n        var path = rootDoc.name + File.separator\n        var lastDoc = rootDoc\n        for (doc in viewModel.subDocs) {\n            lastDoc = doc\n            path = path + doc.name + File.separator\n        }\n        binding.tvPath.text = path\n        adapter.selected.clear()\n        adapter.clearItems()\n        viewModel.loadDoc(lastDoc)\n    }\n\n    /**\n     * 扫描当前文件夹及所有子文件夹\n     */\n    private fun scanFolder() {\n        viewModel.rootDoc?.let { doc ->\n            adapter.clearItems()\n            val lastDoc = viewModel.subDocs.lastOrNull() ?: doc\n            binding.refreshProgressBar.isAutoLoading = true\n            scanDocJob?.cancel()\n            scanDocJob = lifecycleScope.launch(IO) {\n                viewModel.scanDoc(lastDoc)\n                withContext(Main) {\n                    binding.refreshProgressBar.isAutoLoading = false\n                }\n            }\n        }\n    }\n\n    private fun alertImportFileName() {\n        alert(R.string.import_file_name) {\n            setMessage(\"\"\"使用js处理文件名变量src，将书名作者分别赋值到变量name author\"\"\")\n            val alertBinding = DialogEditTextBinding.inflate(layoutInflater).apply {\n                editView.hint = \"js\"\n                editView.setText(AppConfig.bookImportFileName)\n            }\n            customView { alertBinding.root }\n            okButton {\n                AppConfig.bookImportFileName = alertBinding.editView.text?.toString()\n            }\n            cancelButton()\n        }\n    }\n\n    @Synchronized\n    override fun nextDoc(fileDoc: FileDoc) {\n        viewModel.subDocs.add(fileDoc)\n        upPath()\n    }\n\n    @Synchronized\n    private fun goBackDir(): Boolean {\n        return if (viewModel.subDocs.isNotEmpty()) {\n            viewModel.subDocs.removeAt(viewModel.subDocs.lastIndex)\n            upPath()\n            true\n        } else {\n            false\n        }\n    }\n\n    override fun onSearchTextChange(newText: String?) {\n        viewModel.updateCallBackFlow(newText)\n    }\n\n    override fun upCountView() {\n        binding.selectActionBar.upCountView(adapter.selected.size, adapter.checkableCount)\n    }\n\n    override fun startRead(fileDoc: FileDoc) {\n        if (!ArchiveUtils.isArchive(fileDoc.name)) {\n            appDb.bookDao.getBookByFileName(fileDoc.name)?.let {\n                val filePath = fileDoc.toString()\n                if (it.bookUrl != filePath) {\n                    it.bookUrl = filePath\n                    appDb.bookDao.insert(it)\n                }\n                startReadBook(it)\n            }\n        } else {\n            onArchiveFileClick(fileDoc)\n        }\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/book/import/local/ImportBookAdapter.kt",
    "content": "package io.legado.app.ui.book.import.local\n\nimport android.annotation.SuppressLint\nimport android.content.Context\nimport android.view.ViewGroup\nimport io.legado.app.R\nimport io.legado.app.base.adapter.ItemViewHolder\nimport io.legado.app.base.adapter.RecyclerAdapter\nimport io.legado.app.constant.AppConst\nimport io.legado.app.databinding.ItemImportBookBinding\nimport io.legado.app.utils.ConvertUtils\nimport io.legado.app.utils.FileDoc\nimport io.legado.app.utils.gone\nimport io.legado.app.utils.invisible\nimport io.legado.app.utils.visible\n\n\nclass ImportBookAdapter(context: Context, val callBack: CallBack) :\n    RecyclerAdapter<ImportBook, ItemImportBookBinding>(context) {\n    val selected = hashSetOf<ImportBook>()\n    var checkableCount = 0\n\n    override fun getViewBinding(parent: ViewGroup): ItemImportBookBinding {\n        return ItemImportBookBinding.inflate(inflater, parent, false)\n    }\n\n    override fun onCurrentListChanged() {\n        upCheckableCount()\n    }\n\n    override fun convert(\n        holder: ItemViewHolder,\n        binding: ItemImportBookBinding,\n        item: ImportBook,\n        payloads: MutableList<Any>\n    ) {\n        binding.run {\n            if (payloads.isEmpty()) {\n                if (item.isDir) {\n                    ivIcon.setImageResource(R.drawable.ic_folder)\n                    ivIcon.visible()\n                    cbSelect.invisible()\n                    llBrief.gone()\n                    cbSelect.isChecked = false\n                } else {\n                    if (item.isOnBookShelf) {\n                        ivIcon.setImageResource(R.drawable.ic_book_has)\n                        ivIcon.visible()\n                        cbSelect.invisible()\n                    } else {\n                        ivIcon.invisible()\n                        cbSelect.visible()\n                    }\n                    llBrief.visible()\n                    tvTag.text = item.name.substringAfterLast(\".\")\n                    tvSize.text = ConvertUtils.formatFileSize(item.size)\n                    tvDate.text = AppConst.dateFormat.format(item.lastModified)\n                    cbSelect.isChecked = selected.contains(item)\n                }\n                tvName.text = item.name\n            } else {\n                cbSelect.isChecked = selected.contains(item)\n            }\n        }\n    }\n\n    override fun registerListener(holder: ItemViewHolder, binding: ItemImportBookBinding) {\n        holder.itemView.setOnClickListener {\n            getItem(holder.layoutPosition)?.let {\n                if (it.isDir) {\n                    callBack.nextDoc(it.file)\n                } else if (!it.isOnBookShelf) {\n                    if (!selected.contains(it)) {\n                        selected.add(it)\n                    } else {\n                        selected.remove(it)\n                    }\n                    notifyItemChanged(holder.layoutPosition, true)\n                    callBack.upCountView()\n                } else {\n                    /* 点击开始阅读 */\n                    callBack.startRead(it.file)\n                }\n            }\n        }\n    }\n\n    private fun upCheckableCount() {\n        checkableCount = 0\n        getItems().forEach {\n            if (!it.isDir && !it.isOnBookShelf) {\n                checkableCount++\n            }\n        }\n        callBack.upCountView()\n    }\n\n    @SuppressLint(\"NotifyDataSetChanged\")\n    fun selectAll(selectAll: Boolean) {\n        if (selectAll) {\n            getItems().forEach {\n                if (!it.isDir && !it.isOnBookShelf) {\n                    selected.add(it)\n                }\n            }\n        } else {\n            selected.clear()\n        }\n        notifyDataSetChanged()\n        callBack.upCountView()\n    }\n\n    fun revertSelection() {\n        getItems().forEach {\n            if (!it.isDir && !it.isOnBookShelf) {\n                if (selected.contains(it)) {\n                    selected.remove(it)\n                } else {\n                    selected.add(it)\n                }\n            }\n        }\n        notifyItemRangeChanged(0, itemCount, true)\n        callBack.upCountView()\n    }\n\n    fun removeSelection() {\n        for (i in getItems().lastIndex downTo 0) {\n            if (getItem(i) in selected) {\n                removeItem(i)\n            }\n        }\n    }\n\n    interface CallBack {\n        fun nextDoc(fileDoc: FileDoc)\n        fun upCountView()\n        fun startRead(fileDoc: FileDoc)\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/book/import/local/ImportBookViewModel.kt",
    "content": "package io.legado.app.ui.book.import.local\n\nimport android.app.Application\nimport io.legado.app.base.BaseViewModel\nimport io.legado.app.constant.AppLog\nimport io.legado.app.constant.AppPattern.archiveFileRegex\nimport io.legado.app.constant.AppPattern.bookFileRegex\nimport io.legado.app.constant.PreferKey\nimport io.legado.app.model.localBook.LocalBook\nimport io.legado.app.utils.AlphanumComparator\nimport io.legado.app.utils.FileDoc\nimport io.legado.app.utils.delete\nimport io.legado.app.utils.getPrefInt\nimport io.legado.app.utils.list\nimport io.legado.app.utils.mapParallel\nimport io.legado.app.utils.toastOnUi\nimport kotlinx.coroutines.Dispatchers.IO\nimport kotlinx.coroutines.Dispatchers.Main\nimport kotlinx.coroutines.channels.Channel\nimport kotlinx.coroutines.channels.Channel.Factory.UNLIMITED\nimport kotlinx.coroutines.channels.awaitClose\nimport kotlinx.coroutines.flow.callbackFlow\nimport kotlinx.coroutines.flow.catch\nimport kotlinx.coroutines.flow.collect\nimport kotlinx.coroutines.flow.consumeAsFlow\nimport kotlinx.coroutines.flow.flowOn\nimport kotlinx.coroutines.flow.map\nimport kotlinx.coroutines.flow.onEach\nimport kotlinx.coroutines.flow.takeWhile\nimport kotlinx.coroutines.withContext\nimport java.util.Collections\n\nclass ImportBookViewModel(application: Application) : BaseViewModel(application) {\n    var rootDoc: FileDoc? = null\n    val subDocs = arrayListOf<FileDoc>()\n    var sort = context.getPrefInt(PreferKey.localBookImportSort)\n    var dataCallback: DataCallback? = null\n    var dataFlowStart: (() -> Unit)? = null\n    var filterKey: String? = null\n    val dataFlow = callbackFlow<List<ImportBook>> {\n\n        val list = Collections.synchronizedList(ArrayList<ImportBook>())\n\n        dataCallback = object : DataCallback {\n\n            override fun setItems(fileDocs: List<FileDoc>) {\n                list.clear()\n                fileDocs.mapTo(list) {\n                    ImportBook(it)\n                }\n                trySend(list)\n            }\n\n            override fun addItems(fileDocs: List<FileDoc>) {\n                fileDocs.mapTo(list) {\n                    ImportBook(it)\n                }\n                trySend(list)\n            }\n\n            override fun clear() {\n                list.clear()\n                trySend(emptyList())\n            }\n\n            override fun upAdapter() {\n                trySend(list)\n            }\n        }\n\n        withContext(Main) {\n            dataFlowStart?.invoke()\n        }\n\n        awaitClose {\n            dataCallback = null\n        }\n\n    }.map { docList ->\n        val docList = docList.toList()\n        val filterKey = filterKey\n        val skipFilter = filterKey.isNullOrBlank()\n        val comparator = when (sort) {\n            2 -> compareBy<ImportBook>({ !it.isDir }, { -it.lastModified })\n            1 -> compareBy({ !it.isDir }, { -it.size })\n            else -> compareBy { !it.isDir }\n        } then compareBy(AlphanumComparator) { it.name }\n        docList.asSequence().filter {\n            skipFilter || it.name.contains(filterKey)\n        }.sortedWith(comparator).toList()\n    }.flowOn(IO)\n\n    fun addToBookshelf(bookList: HashSet<ImportBook>, finally: () -> Unit) {\n        execute {\n            val fileUris = bookList.map {\n                it.file.uri\n            }\n            LocalBook.importFiles(fileUris)\n        }.onError {\n            context.toastOnUi(\"添加书架失败，请尝试重新选择文件夹\")\n            AppLog.put(\"添加书架失败\\n${it.localizedMessage}\", it)\n        }.onSuccess {\n            context.toastOnUi(\"添加书架成功\")\n        }.onFinally {\n            finally.invoke()\n        }\n    }\n\n    fun deleteDoc(bookList: HashSet<ImportBook>, finally: () -> Unit) {\n        execute {\n            bookList.forEach {\n                it.file.delete()\n            }\n        }.onFinally {\n            finally.invoke()\n        }\n    }\n\n    fun loadDoc(fileDoc: FileDoc) {\n        execute {\n            val docList = fileDoc.list { item ->\n                when {\n                    item.name.startsWith(\".\") -> false\n                    item.isDir -> true\n                    else -> item.name.matches(bookFileRegex) || item.name.matches(archiveFileRegex)\n                }\n            }\n            dataCallback?.setItems(docList!!)\n        }.onError {\n            context.toastOnUi(\"获取文件列表出错\\n${it.localizedMessage}\")\n        }\n    }\n\n    suspend fun scanDoc(fileDoc: FileDoc) {\n        dataCallback?.clear()\n        val channel = Channel<FileDoc>(UNLIMITED)\n        var n = 1\n        channel.trySend(fileDoc)\n        val list = arrayListOf<FileDoc>()\n        channel.consumeAsFlow()\n            .mapParallel(16) { fileDoc ->\n                fileDoc.list()!!\n            }.onEach { fileDocs ->\n                n--\n                list.clear()\n                fileDocs.forEach {\n                    if (it.isDir) {\n                        n++\n                        channel.trySend(it)\n                    } else if (it.name.matches(bookFileRegex)\n                        || it.name.matches(archiveFileRegex)\n                    ) {\n                        list.add(it)\n                    }\n                }\n                dataCallback?.addItems(list)\n            }.takeWhile {\n                n > 0\n            }.catch {\n                context.toastOnUi(\"扫描文件夹出错\\n${it.localizedMessage}\")\n            }.collect()\n    }\n\n    fun updateCallBackFlow(filterKey: String?) {\n        this.filterKey = filterKey\n        dataCallback?.upAdapter()\n    }\n\n    interface DataCallback {\n\n        fun setItems(fileDocs: List<FileDoc>)\n\n        fun addItems(fileDocs: List<FileDoc>)\n\n        fun clear()\n\n        fun upAdapter()\n\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/book/import/remote/RemoteBookActivity.kt",
    "content": "package io.legado.app.ui.book.import.remote\n\nimport android.annotation.SuppressLint\nimport android.net.Uri\nimport android.os.Bundle\nimport android.view.Menu\nimport android.view.MenuItem\nimport android.view.SubMenu\nimport androidx.activity.addCallback\nimport androidx.activity.viewModels\nimport androidx.core.view.isGone\nimport androidx.lifecycle.lifecycleScope\nimport androidx.recyclerview.widget.LinearLayoutManager\nimport io.legado.app.R\nimport io.legado.app.data.appDb\nimport io.legado.app.help.config.AppConfig\nimport io.legado.app.help.config.LocalConfig\nimport io.legado.app.lib.dialogs.alert\nimport io.legado.app.lib.theme.backgroundColor\nimport io.legado.app.model.remote.RemoteBook\nimport io.legado.app.ui.about.AppLogDialog\nimport io.legado.app.ui.book.import.BaseImportBookActivity\nimport io.legado.app.ui.widget.SelectActionBar\nimport io.legado.app.utils.ArchiveUtils\nimport io.legado.app.utils.FileDoc\nimport io.legado.app.utils.find\nimport io.legado.app.utils.showDialogFragment\nimport io.legado.app.utils.showHelp\nimport kotlinx.coroutines.delay\nimport kotlinx.coroutines.flow.conflate\nimport kotlinx.coroutines.launch\nimport java.io.File\n\n/**\n * 展示远程书籍\n */\nclass RemoteBookActivity : BaseImportBookActivity<RemoteBookViewModel>(),\n    RemoteBookAdapter.CallBack,\n    SelectActionBar.CallBack,\n    ServersDialog.Callback {\n\n    override val viewModel by viewModels<RemoteBookViewModel>()\n    private val adapter by lazy { RemoteBookAdapter(this, this) }\n    private var groupMenu: SubMenu? = null\n\n    override fun onActivityCreated(savedInstanceState: Bundle?) {\n        searchView.queryHint = getString(R.string.screen) + \" • \" + getString(R.string.remote_book)\n        onBackPressedDispatcher.addCallback(this) {\n            if (!goBackDir()) {\n                finish()\n            }\n        }\n        lifecycleScope.launch {\n            if (!setBookStorage()) {\n                finish()\n                return@launch\n            }\n            initView()\n            initEvent()\n            launch {\n                viewModel.dataFlow.conflate().collect { sortedRemoteBooks ->\n                    binding.refreshProgressBar.isAutoLoading = false\n                    binding.tvEmptyMsg.isGone = sortedRemoteBooks.isNotEmpty()\n                    adapter.setItems(sortedRemoteBooks)\n                    delay(500)\n                }\n            }\n            viewModel.initData {\n                upPath()\n            }\n        }\n    }\n\n    override fun observeLiveBus() {\n        viewModel.permissionDenialLiveData.observe(this) {\n            localBookTreeSelect.launch {\n                title = getString(R.string.select_book_folder)\n            }\n        }\n    }\n\n    private fun initView() {\n        binding.layTop.setBackgroundColor(backgroundColor)\n        binding.recyclerView.layoutManager = LinearLayoutManager(this)\n        binding.recyclerView.adapter = adapter\n        binding.selectActionBar.setMainActionText(R.string.add_to_bookshelf)\n        binding.selectActionBar.setCallBack(this)\n        if (!LocalConfig.webDavBookHelpVersionIsLast) {\n            showHelp(\"webDavBookHelp\")\n        }\n    }\n\n    private fun sortCheck(sortKey: RemoteBookSort) {\n        if (viewModel.sortKey == sortKey) {\n            viewModel.sortAscending = !viewModel.sortAscending\n        } else {\n            viewModel.sortAscending = true\n            viewModel.sortKey = sortKey\n        }\n    }\n\n    private fun initEvent() {\n        binding.tvGoBack.setOnClickListener {\n            goBackDir()\n        }\n    }\n\n\n    override fun onCompatCreateOptionsMenu(menu: Menu): Boolean {\n        menuInflater.inflate(R.menu.book_remote, menu)\n        return super.onCompatCreateOptionsMenu(menu)\n    }\n\n    override fun onCompatOptionsItemSelected(item: MenuItem): Boolean {\n        when (item.itemId) {\n            R.id.menu_refresh -> upPath()\n            R.id.menu_server_config -> showDialogFragment<ServersDialog>()\n            R.id.menu_log -> showDialogFragment<AppLogDialog>()\n            R.id.menu_help -> showHelp(\"webDavBookHelp\")\n            R.id.menu_sort_name -> {\n                item.isChecked = true\n                sortCheck(RemoteBookSort.Name)\n                upPath()\n            }\n            R.id.menu_sort_time -> {\n                item.isChecked = true\n                sortCheck(RemoteBookSort.Default)\n                upPath()\n            }\n        }\n        return super.onCompatOptionsItemSelected(item)\n    }\n\n    override fun onPrepareOptionsMenu(menu: Menu): Boolean {\n        groupMenu = menu.findItem(R.id.menu_sort)?.subMenu\n        groupMenu?.setGroupCheckable(R.id.menu_group_sort, true, true)\n        groupMenu?.findItem(R.id.menu_sort_name)?.isChecked =\n            viewModel.sortKey == RemoteBookSort.Name\n        groupMenu?.findItem(R.id.menu_sort_time)?.isChecked =\n            viewModel.sortKey == RemoteBookSort.Default\n        return super.onPrepareOptionsMenu(menu)\n    }\n\n    override fun revertSelection() {\n        adapter.revertSelection()\n    }\n\n    override fun selectAll(selectAll: Boolean) {\n        adapter.selectAll(selectAll)\n    }\n\n    @SuppressLint(\"NotifyDataSetChanged\")\n    override fun onClickSelectBarMainAction() {\n        binding.refreshProgressBar.isAutoLoading = true\n        viewModel.addToBookshelf(adapter.selected) {\n            adapter.selected.clear()\n            adapter.notifyDataSetChanged()\n            binding.refreshProgressBar.isAutoLoading = false\n        }\n    }\n\n    private fun goBackDir(): Boolean {\n        if (viewModel.dirList.isEmpty()) {\n            return false\n        }\n        viewModel.dirList.removeLastOrNull()\n        upPath()\n        return true\n    }\n\n    private fun upPath() {\n        binding.tvGoBack.isEnabled = viewModel.dirList.isNotEmpty()\n        var path = if (viewModel.isDefaultWebdav) {\n            \"books\" + File.separator\n        } else {\n            File.separator\n        }\n        viewModel.dirList.forEach {\n            path = path + it.filename + File.separator\n        }\n        binding.tvPath.text = path\n        viewModel.dataCallback?.clear()\n        adapter.selected.clear()\n        viewModel.loadRemoteBookList(\n            viewModel.dirList.lastOrNull()?.path\n        ) {\n            binding.refreshProgressBar.isAutoLoading = it\n        }\n    }\n\n    override fun openDir(remoteBook: RemoteBook) {\n        viewModel.dirList.add(remoteBook)\n        upPath()\n    }\n\n    override fun upCountView() {\n        binding.selectActionBar.upCountView(adapter.selected.size, adapter.checkableCount)\n    }\n\n    override fun onDialogDismiss(tag: String) {\n        viewModel.initData {\n            upPath()\n        }\n    }\n\n    override fun onSearchTextChange(newText: String?) {\n        viewModel.updateCallBackFlow(newText)\n    }\n\n    private fun showRemoteBookDownloadAlert(\n        remoteBook: RemoteBook,\n        onDownloadFinish: (() -> Unit)? = null\n    ) {\n        alert(\n            R.string.draw,\n            R.string.archive_not_found\n        ) {\n            okButton {\n                viewModel.addToBookshelf(hashSetOf(remoteBook)) {\n                    onDownloadFinish?.invoke()\n                }\n            }\n            noButton()\n        }\n    }\n\n    override fun startRead(remoteBook: RemoteBook) {\n        val downloadFileName = remoteBook.filename\n        if (!ArchiveUtils.isArchive(downloadFileName)) {\n            appDb.bookDao.getBookByFileName(downloadFileName)?.let {\n                startReadBook(it)\n            }\n        } else {\n            AppConfig.defaultBookTreeUri ?: return\n            val downloadArchiveFileDoc = FileDoc.fromUri(Uri.parse(AppConfig.defaultBookTreeUri), true)\n                .find(downloadFileName)\n            if (downloadArchiveFileDoc == null) {\n                showRemoteBookDownloadAlert(remoteBook) {\n                    startRead(remoteBook)\n                }\n            } else {\n                onArchiveFileClick(downloadArchiveFileDoc)\n            }\n        }\n    }\n\n    override fun addToBookShelfAgain(remoteBook: RemoteBook) {\n        alert(getString(R.string.sure), \"是否重新加入书架？\") {\n            yesButton {\n                binding.refreshProgressBar.isAutoLoading = true\n                viewModel.addToBookshelf(hashSetOf(remoteBook)) {\n                    binding.refreshProgressBar.isAutoLoading = false\n                }\n            }\n            noButton()\n        }\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/book/import/remote/RemoteBookAdapter.kt",
    "content": "package io.legado.app.ui.book.import.remote\n\nimport android.annotation.SuppressLint\nimport android.content.Context\nimport android.view.ViewGroup\nimport io.legado.app.R\nimport io.legado.app.base.adapter.ItemViewHolder\nimport io.legado.app.base.adapter.RecyclerAdapter\nimport io.legado.app.constant.AppConst\nimport io.legado.app.databinding.ItemImportBookBinding\nimport io.legado.app.model.remote.RemoteBook\nimport io.legado.app.utils.ConvertUtils\nimport io.legado.app.utils.gone\nimport io.legado.app.utils.invisible\nimport io.legado.app.utils.visible\n\n\n/**\n * 适配器\n * @author qianfanguojin\n */\nclass RemoteBookAdapter(context: Context, val callBack: CallBack) :\n    RecyclerAdapter<RemoteBook, ItemImportBookBinding>(context) {\n    var selected = hashSetOf<RemoteBook>()\n    var checkableCount = 0\n\n    override fun getViewBinding(parent: ViewGroup): ItemImportBookBinding {\n        return ItemImportBookBinding.inflate(inflater, parent, false)\n    }\n\n    override fun onCurrentListChanged() {\n        upCheckableCount()\n    }\n\n    /**\n     * 绑定RecycleView 中每一个项的视图和数据\n     */\n    override fun convert(\n        holder: ItemViewHolder,\n        binding: ItemImportBookBinding,\n        item: RemoteBook,\n        payloads: MutableList<Any>\n    ) {\n        binding.run {\n            if (payloads.isEmpty()) {\n                if (item.isDir) {\n                    ivIcon.setImageResource(R.drawable.ic_folder)\n                    ivIcon.visible()\n                    cbSelect.invisible()\n                    llBrief.gone()\n                    cbSelect.isChecked = false\n                } else {\n                    if (item.isOnBookShelf) {\n                        ivIcon.setImageResource(R.drawable.ic_book_has)\n                        ivIcon.visible()\n                        cbSelect.invisible()\n                    } else {\n                        ivIcon.invisible()\n                        cbSelect.visible()\n                    }\n                    llBrief.visible()\n                    tvTag.text = item.contentType\n                    tvSize.text = ConvertUtils.formatFileSize(item.size)\n                    tvDate.text = AppConst.dateFormat.format(item.lastModify)\n                    cbSelect.isChecked = selected.contains(item)\n                }\n                tvName.text = item.filename\n            } else {\n                cbSelect.isChecked = selected.contains(item)\n            }\n        }\n    }\n\n    override fun registerListener(holder: ItemViewHolder, binding: ItemImportBookBinding) {\n        holder.itemView.setOnClickListener {\n            getItem(holder.layoutPosition)?.let {\n                if (it.isDir) {\n                    callBack.openDir(it)\n                } else if (!it.isOnBookShelf) {\n                    if (!selected.contains(it)) {\n                        selected.add(it)\n                    } else {\n                        selected.remove(it)\n                    }\n                    notifyItemChanged(holder.layoutPosition, true)\n                    callBack.upCountView()\n                } else {\n                    /* 点击开始阅读 */\n                    callBack.startRead(it)\n                }\n            }\n        }\n        holder.itemView.setOnLongClickListener {\n            getItem(holder.layoutPosition)?.let { remoteBook ->\n                if (remoteBook.isOnBookShelf) {\n                    callBack.addToBookShelfAgain(remoteBook)\n                }\n            }\n            true\n        }\n    }\n\n    private fun upCheckableCount() {\n        checkableCount = 0\n        getItems().forEach {\n            if (!it.isDir && !it.isOnBookShelf) {\n                checkableCount++\n            }\n        }\n        callBack.upCountView()\n    }\n\n    @SuppressLint(\"NotifyDataSetChanged\")\n    fun selectAll(selectAll: Boolean) {\n        if (selectAll) {\n            getItems().forEach {\n                if (!it.isDir && !it.isOnBookShelf) {\n                    selected.add(it)\n                }\n            }\n        } else {\n            selected.clear()\n        }\n        notifyDataSetChanged()\n        callBack.upCountView()\n    }\n\n    fun revertSelection() {\n        getItems().forEach {\n            if (!it.isDir && !it.isOnBookShelf) {\n                if (selected.contains(it)) {\n                    selected.remove(it)\n                } else {\n                    selected.add(it)\n                }\n            }\n        }\n        notifyItemRangeChanged(0, itemCount, true)\n        callBack.upCountView()\n    }\n\n    fun removeSelection() {\n        for (i in getItems().lastIndex downTo 0) {\n            if (getItem(i) in selected) {\n                removeItem(i)\n            }\n        }\n    }\n\n\n    interface CallBack {\n        fun openDir(remoteBook: RemoteBook)\n        fun upCountView()\n        fun startRead(remoteBook: RemoteBook)\n        fun addToBookShelfAgain(remoteBook: RemoteBook)\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/book/import/remote/RemoteBookSort.kt",
    "content": "package io.legado.app.ui.book.import.remote\n\nenum class RemoteBookSort {\n    Default, Name\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/book/import/remote/RemoteBookViewModel.kt",
    "content": "package io.legado.app.ui.book.import.remote\n\nimport android.app.Application\nimport androidx.lifecycle.MutableLiveData\nimport io.legado.app.base.BaseViewModel\nimport io.legado.app.constant.AppLog\nimport io.legado.app.constant.BookType\nimport io.legado.app.data.appDb\nimport io.legado.app.exception.NoStackTraceException\nimport io.legado.app.help.AppWebDav\nimport io.legado.app.help.config.AppConfig\nimport io.legado.app.lib.webdav.Authorization\nimport io.legado.app.model.analyzeRule.CustomUrl\nimport io.legado.app.model.localBook.LocalBook\nimport io.legado.app.model.remote.RemoteBook\nimport io.legado.app.model.remote.RemoteBookWebDav\nimport io.legado.app.utils.AlphanumComparator\nimport io.legado.app.utils.toastOnUi\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.channels.awaitClose\nimport kotlinx.coroutines.flow.callbackFlow\nimport kotlinx.coroutines.flow.flowOn\nimport kotlinx.coroutines.flow.map\nimport java.util.Collections\n\nclass RemoteBookViewModel(application: Application) : BaseViewModel(application) {\n    var sortKey = RemoteBookSort.Default\n    var sortAscending = false\n    val dirList = arrayListOf<RemoteBook>()\n    val permissionDenialLiveData = MutableLiveData<Int>()\n\n    var dataCallback: DataCallback? = null\n\n    val dataFlow = callbackFlow<List<RemoteBook>> {\n\n        val list = Collections.synchronizedList(ArrayList<RemoteBook>())\n\n        dataCallback = object : DataCallback {\n\n            override fun setItems(remoteFiles: List<RemoteBook>) {\n                list.clear()\n                list.addAll(remoteFiles)\n                trySend(list)\n            }\n\n            override fun addItems(remoteFiles: List<RemoteBook>) {\n                list.addAll(remoteFiles)\n                trySend(list)\n            }\n\n            override fun clear() {\n                list.clear()\n                trySend(emptyList())\n            }\n\n            override fun screen(key: String?) {\n                if (key.isNullOrBlank()) {\n                    trySend(list)\n                } else {\n                    trySend(\n                        list.filter { it.filename.contains(key) }\n                    )\n                }\n            }\n        }\n\n        awaitClose {\n            dataCallback = null\n        }\n    }.map { list ->\n        if (sortAscending) when (sortKey) {\n            RemoteBookSort.Name -> list.sortedWith(compareBy<RemoteBook> { !it.isDir }\n                    then compareBy(AlphanumComparator) { it.filename })\n\n            else -> list.sortedWith(compareBy({ !it.isDir }, { it.lastModify }))\n        } else when (sortKey) {\n            RemoteBookSort.Name -> list.sortedWith { o1, o2 ->\n                val compare = -compareValues(o1.isDir, o2.isDir)\n                if (compare == 0) {\n                    return@sortedWith -AlphanumComparator.compare(o1.filename, o2.filename)\n                }\n                return@sortedWith compare\n            }\n\n            else -> list.sortedWith { o1, o2 ->\n                val compare = -compareValues(o1.isDir, o2.isDir)\n                if (compare == 0) {\n                    return@sortedWith -compareValues(o1.lastModify, o2.lastModify)\n                }\n                return@sortedWith compare\n            }\n        }\n    }.flowOn(Dispatchers.IO)\n\n    private var remoteBookWebDav: RemoteBookWebDav? = null\n    var isDefaultWebdav = false\n\n    fun initData(onSuccess: () -> Unit) {\n        execute {\n            isDefaultWebdav = false\n            appDb.serverDao.get(AppConfig.remoteServerId)?.getWebDavConfig()?.let {\n                val authorization = Authorization(it)\n                remoteBookWebDav = RemoteBookWebDav(it.url, authorization, AppConfig.remoteServerId)\n                return@execute\n            }\n            isDefaultWebdav = true\n            remoteBookWebDav = AppWebDav.defaultBookWebDav\n                ?: throw NoStackTraceException(\"webDav没有配置\")\n        }.onError {\n            context.toastOnUi(\"初始化webDav出错:${it.localizedMessage}\")\n        }.onSuccess {\n            onSuccess.invoke()\n        }\n    }\n\n    fun loadRemoteBookList(path: String?, loadCallback: (loading: Boolean) -> Unit) {\n        executeLazy {\n            val bookWebDav = remoteBookWebDav\n                ?: throw NoStackTraceException(\"没有配置webDav\")\n            dataCallback?.clear()\n            val url = path ?: bookWebDav.rootBookUrl\n            val bookList = bookWebDav.getRemoteBookList(url)\n            dataCallback?.setItems(bookList)\n        }.onError {\n            AppLog.put(\"获取webDav书籍出错\\n${it.localizedMessage}\", it)\n            context.toastOnUi(\"获取webDav书籍出错\\n${it.localizedMessage}\")\n        }.onStart {\n            loadCallback.invoke(true)\n        }.onFinally {\n            loadCallback.invoke(false)\n        }.start()\n    }\n\n    fun addToBookshelf(remoteBooks: HashSet<RemoteBook>, finally: () -> Unit) {\n        execute {\n            val bookWebDav = remoteBookWebDav\n                ?: throw NoStackTraceException(\"没有配置webDav\")\n            remoteBooks.forEach { remoteBook ->\n                val downloadBookUri = bookWebDav.downloadRemoteBook(remoteBook)\n                LocalBook.importFiles(downloadBookUri).forEach { book ->\n                    book.origin = BookType.webDavTag + CustomUrl(remoteBook.path)\n                        .putAttribute(\"serverID\", bookWebDav.serverID)\n                        .toString()\n                    book.save()\n                }\n                remoteBook.isOnBookShelf = true\n            }\n        }.onError {\n            AppLog.put(\"导入出错\\n${it.localizedMessage}\", it)\n            context.toastOnUi(\"导入出错\\n${it.localizedMessage}\")\n            if (it is SecurityException) {\n                permissionDenialLiveData.postValue(1)\n            }\n        }.onFinally {\n            finally.invoke()\n        }\n    }\n\n    fun updateCallBackFlow(filterKey: String?) {\n        dataCallback?.screen(filterKey)\n    }\n\n    interface DataCallback {\n\n        fun setItems(remoteFiles: List<RemoteBook>)\n\n        fun addItems(remoteFiles: List<RemoteBook>)\n\n        fun clear()\n\n        fun screen(key: String?)\n\n    }\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/book/import/remote/ServerConfigDialog.kt",
    "content": "package io.legado.app.ui.book.import.remote\n\nimport android.os.Bundle\nimport android.text.InputType\nimport android.view.MenuItem\nimport android.view.View\nimport android.view.ViewGroup\nimport androidx.appcompat.widget.Toolbar\nimport androidx.fragment.app.viewModels\nimport io.legado.app.R\nimport io.legado.app.base.BaseDialogFragment\nimport io.legado.app.data.entities.Server\nimport io.legado.app.data.entities.rule.RowUi\nimport io.legado.app.databinding.DialogWebdavServerBinding\nimport io.legado.app.databinding.ItemSourceEditBinding\nimport io.legado.app.lib.theme.primaryColor\nimport io.legado.app.utils.GSON\nimport io.legado.app.utils.applyTint\nimport io.legado.app.utils.setLayout\nimport io.legado.app.utils.viewbindingdelegate.viewBinding\nimport org.json.JSONObject\n\nclass ServerConfigDialog() : BaseDialogFragment(R.layout.dialog_webdav_server, true),\n    Toolbar.OnMenuItemClickListener {\n\n    constructor(id: Long) : this() {\n        arguments = Bundle().apply {\n            putLong(\"id\", id)\n        }\n    }\n\n    private val binding by viewBinding(DialogWebdavServerBinding::bind)\n    private val viewModel by viewModels<ServerConfigViewModel>()\n\n    private val webDavServerUi = listOf(\n        RowUi(\"url\"),\n        RowUi(\"username\"),\n        RowUi(\"password\", RowUi.Type.password)\n    )\n\n    override fun onStart() {\n        super.onStart()\n        setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)\n    }\n\n    override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) {\n        binding.toolBar.setBackgroundColor(primaryColor)\n        binding.toolBar.inflateMenu(R.menu.server_config)\n        binding.toolBar.menu.applyTint(requireContext())\n        binding.toolBar.setOnMenuItemClickListener(this)\n        viewModel.init(arguments?.getLong(\"id\")) {\n            upConfigView(viewModel.mServer)\n        }\n    }\n\n    override fun onMenuItemClick(item: MenuItem): Boolean {\n        when (item.itemId) {\n            R.id.menu_save -> getServer().let {\n                viewModel.save(it) {\n                    dismissAllowingStateLoss()\n                }\n            }\n        }\n        return true\n    }\n\n    private fun upConfigView(server: Server?) {\n        binding.etName.setText(server?.name)\n        binding.spType.setSelection(\n            when (server?.type) {\n                else -> 0\n            }\n        )\n        when (server?.type) {\n            else -> upWebDavServerUi(server?.getConfigJsonObject())\n        }\n    }\n\n    private fun upWebDavServerUi(config: JSONObject?) {\n        webDavServerUi.forEachIndexed { index, rowUi ->\n            when (rowUi.type) {\n                RowUi.Type.text -> ItemSourceEditBinding.inflate(\n                    layoutInflater,\n                    binding.root,\n                    false\n                ).let {\n                    binding.flexbox.addView(it.root)\n                    it.root.id = index + 1000\n                    it.textInputLayout.hint = rowUi.name\n                    it.editText.setText(config?.getString(rowUi.name))\n                }\n                RowUi.Type.password -> ItemSourceEditBinding.inflate(\n                    layoutInflater,\n                    binding.root,\n                    false\n                ).let {\n                    binding.flexbox.addView(it.root)\n                    it.root.id = index + 1000\n                    it.textInputLayout.hint = rowUi.name\n                    it.editText.inputType =\n                        InputType.TYPE_TEXT_VARIATION_PASSWORD or InputType.TYPE_CLASS_TEXT\n                    it.editText.setText(config?.getString(rowUi.name))\n                }\n            }\n        }\n    }\n\n    private fun getServer(): Server {\n        val server = viewModel.mServer?.copy() ?: Server()\n        server.name = binding.etName.text.toString()\n        server.type = when (binding.spType.selectedItemPosition) {\n            else -> Server.TYPE.WEBDAV\n        }\n        server.config = when (server.type) {\n            else -> GSON.toJson(getWebDavConfig())\n        }\n        return server\n    }\n\n    private fun getWebDavConfig(): HashMap<String, String> {\n        val data = hashMapOf<String, String>()\n        webDavServerUi.forEachIndexed { index, rowUi ->\n            val rowView = binding.root.findViewById<View>(index + 1000)\n            ItemSourceEditBinding.bind(rowView).editText.text?.let {\n                data[rowUi.name] = it.toString()\n            }\n        }\n        return data\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/book/import/remote/ServerConfigViewModel.kt",
    "content": "package io.legado.app.ui.book.import.remote\n\nimport android.app.Application\nimport io.legado.app.base.BaseViewModel\nimport io.legado.app.data.appDb\nimport io.legado.app.data.entities.Server\nimport io.legado.app.utils.toastOnUi\n\nclass ServerConfigViewModel(application: Application): BaseViewModel(application) {\n\n    var mServer: Server? = null\n\n    fun init(id: Long?, onSuccess: () -> Unit) {\n        //mServer不为空可能是旋转屏幕界面重新创建,不用更新数据\n        if (mServer != null) return\n        execute {\n            mServer = if (id != null) {\n                appDb.serverDao.get(id)\n            } else {\n                Server()\n            }\n        }.onSuccess {\n            onSuccess.invoke()\n        }\n    }\n\n    fun save(server: Server, onSuccess: () -> Unit) {\n        execute {\n            mServer?.let {\n                appDb.serverDao.delete(it)\n            }\n            mServer = server\n            appDb.serverDao.insert(server)\n        }.onSuccess {\n            onSuccess.invoke()\n        }.onError {\n            context.toastOnUi(\"保存出错\\n${it.localizedMessage}\")\n        }\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/book/import/remote/ServersDialog.kt",
    "content": "package io.legado.app.ui.book.import.remote\n\nimport android.content.Context\nimport android.content.DialogInterface\nimport android.os.Bundle\nimport android.view.MenuItem\nimport android.view.View\nimport android.view.ViewGroup\nimport androidx.appcompat.widget.Toolbar\nimport androidx.fragment.app.viewModels\nimport androidx.lifecycle.lifecycleScope\nimport androidx.recyclerview.widget.LinearLayoutManager\nimport io.legado.app.R\nimport io.legado.app.base.BaseDialogFragment\nimport io.legado.app.base.adapter.ItemViewHolder\nimport io.legado.app.base.adapter.RecyclerAdapter\nimport io.legado.app.constant.AppConst.DEFAULT_WEBDAV_ID\nimport io.legado.app.constant.AppLog\nimport io.legado.app.data.appDb\nimport io.legado.app.data.entities.Server\nimport io.legado.app.databinding.DialogRecyclerViewBinding\nimport io.legado.app.databinding.ItemServerSelectBinding\nimport io.legado.app.help.config.AppConfig\nimport io.legado.app.lib.dialogs.alert\nimport io.legado.app.lib.theme.backgroundColor\nimport io.legado.app.lib.theme.primaryColor\nimport io.legado.app.ui.widget.recycler.VerticalDivider\nimport io.legado.app.utils.applyTint\nimport io.legado.app.utils.setLayout\nimport io.legado.app.utils.showDialogFragment\nimport io.legado.app.utils.viewbindingdelegate.viewBinding\nimport io.legado.app.utils.visible\nimport kotlinx.coroutines.Dispatchers.IO\nimport kotlinx.coroutines.flow.catch\nimport kotlinx.coroutines.flow.flowOn\nimport kotlinx.coroutines.launch\n\n/**\n * 服务器配置\n */\nclass ServersDialog : BaseDialogFragment(R.layout.dialog_recycler_view),\n    Toolbar.OnMenuItemClickListener {\n\n    val binding by viewBinding(DialogRecyclerViewBinding::bind)\n    val viewModel by viewModels<ServersViewModel>()\n\n    private val callback get() = (activity as? Callback)\n    private val adapter by lazy { ServersAdapter(requireContext()) }\n\n    override fun onStart() {\n        super.onStart()\n        setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)\n    }\n\n\n    override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) {\n        binding.toolBar.setBackgroundColor(primaryColor)\n        binding.toolBar.setTitle(R.string.server_config)\n        initView()\n        initData()\n    }\n\n    private fun initView() {\n        binding.toolBar.inflateMenu(R.menu.servers)\n        binding.toolBar.menu.applyTint(requireContext())\n        binding.toolBar.setOnMenuItemClickListener(this)\n        binding.recyclerView.layoutManager = LinearLayoutManager(requireContext())\n        binding.recyclerView.addItemDecoration(VerticalDivider(requireContext()))\n        binding.recyclerView.adapter = adapter\n        binding.tvFooterLeft.text = getString(R.string.text_default)\n        binding.tvFooterLeft.visible()\n        binding.tvFooterLeft.setOnClickListener {\n            AppConfig.remoteServerId = DEFAULT_WEBDAV_ID\n            dismissAllowingStateLoss()\n        }\n        binding.tvCancel.visible()\n        binding.tvCancel.setOnClickListener {\n            dismissAllowingStateLoss()\n        }\n        binding.tvOk.visible()\n        binding.tvOk.setOnClickListener {\n            AppConfig.remoteServerId = adapter.selectServerId\n            dismissAllowingStateLoss()\n        }\n    }\n\n    private fun initData() {\n        lifecycleScope.launch {\n            appDb.serverDao.observeAll().catch {\n                AppLog.put(\"服务器配置界面获取数据失败\\n${it.localizedMessage}\", it)\n            }.flowOn(IO).collect {\n                adapter.setItems(it)\n            }\n        }\n    }\n\n    override fun onMenuItemClick(item: MenuItem): Boolean {\n        when (item.itemId) {\n            R.id.menu_add -> showDialogFragment(ServerConfigDialog())\n        }\n        return true\n    }\n\n    override fun onDismiss(dialog: DialogInterface) {\n        super.onDismiss(dialog)\n        callback?.onDialogDismiss(\"serversDialog\")\n    }\n\n    inner class ServersAdapter(context: Context) :\n        RecyclerAdapter<Server, ItemServerSelectBinding>(context) {\n\n        var selectServerId: Long = AppConfig.remoteServerId\n\n        override fun getViewBinding(parent: ViewGroup): ItemServerSelectBinding {\n            return ItemServerSelectBinding.inflate(inflater, parent, false)\n        }\n\n        override fun registerListener(holder: ItemViewHolder, binding: ItemServerSelectBinding) {\n            binding.rbServer.setOnUserCheckedChangeListener { isChecked ->\n                if (isChecked) {\n                    selectServerId = getItemByLayoutPosition(holder.layoutPosition)!!.id\n                    adapter.updateItems(0, itemCount - 1, \"upSelect\")\n                }\n            }\n            binding.ivEdit.setOnClickListener {\n                getItemByLayoutPosition(holder.layoutPosition)?.let { server ->\n                    showDialogFragment(ServerConfigDialog(server.id))\n                }\n            }\n            binding.ivDelete.setOnClickListener {\n                alert {\n                    setTitle(R.string.draw)\n                    setMessage(R.string.sure_del)\n                    yesButton {\n                        getItemByLayoutPosition(holder.layoutPosition)?.let { server ->\n                            viewModel.delete(server)\n                        }\n                    }\n                    noButton()\n                }\n            }\n        }\n\n        override fun convert(\n            holder: ItemViewHolder,\n            binding: ItemServerSelectBinding,\n            item: Server,\n            payloads: MutableList<Any>\n        ) {\n            if (payloads.isEmpty()) {\n                binding.root.setBackgroundColor(context.backgroundColor)\n                binding.rbServer.text = item.name\n                binding.rbServer.isChecked = item.id == selectServerId\n            } else {\n                binding.rbServer.isChecked = item.id == selectServerId\n            }\n        }\n\n    }\n\n    interface Callback {\n\n        fun onDialogDismiss(tag: String)\n\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/book/import/remote/ServersViewModel.kt",
    "content": "package io.legado.app.ui.book.import.remote\n\nimport android.app.Application\nimport io.legado.app.base.BaseViewModel\nimport io.legado.app.data.appDb\nimport io.legado.app.data.entities.Server\n\nclass ServersViewModel(application: Application): BaseViewModel(application) {\n\n\n    fun delete(server: Server) {\n        execute {\n            appDb.serverDao.delete(server)\n        }\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/book/info/BookInfoActivity.kt",
    "content": "package io.legado.app.ui.book.info\n\nimport android.annotation.SuppressLint\nimport android.content.Intent\nimport android.net.Uri\nimport android.os.Build\nimport android.os.Bundle\nimport android.view.Menu\nimport android.view.MenuItem\nimport android.view.MotionEvent\nimport android.widget.CheckBox\nimport android.widget.LinearLayout\nimport androidx.activity.result.contract.ActivityResultContracts\nimport androidx.activity.viewModels\nimport androidx.lifecycle.lifecycleScope\nimport io.legado.app.R\nimport io.legado.app.base.VMBaseActivity\nimport io.legado.app.constant.BookType\nimport io.legado.app.constant.Theme\nimport io.legado.app.data.appDb\nimport io.legado.app.data.entities.Book\nimport io.legado.app.data.entities.BookChapter\nimport io.legado.app.data.entities.BookSource\nimport io.legado.app.databinding.ActivityBookInfoBinding\nimport io.legado.app.exception.NoStackTraceException\nimport io.legado.app.help.AppWebDav\nimport io.legado.app.help.book.addType\nimport io.legado.app.help.book.getRemoteUrl\nimport io.legado.app.help.book.isAudio\nimport io.legado.app.help.book.isImage\nimport io.legado.app.help.book.isLocal\nimport io.legado.app.help.book.isLocalTxt\nimport io.legado.app.help.book.isWebFile\nimport io.legado.app.help.book.removeType\nimport io.legado.app.help.config.AppConfig\nimport io.legado.app.help.config.LocalConfig\nimport io.legado.app.lib.dialogs.alert\nimport io.legado.app.lib.dialogs.selector\nimport io.legado.app.lib.theme.accentColor\nimport io.legado.app.lib.theme.backgroundColor\nimport io.legado.app.lib.theme.bottomBackground\nimport io.legado.app.lib.theme.getPrimaryTextColor\nimport io.legado.app.model.BookCover\nimport io.legado.app.model.remote.RemoteBookWebDav\nimport io.legado.app.ui.about.AppLogDialog\nimport io.legado.app.ui.book.audio.AudioPlayActivity\nimport io.legado.app.ui.book.changecover.ChangeCoverDialog\nimport io.legado.app.ui.book.changesource.ChangeBookSourceDialog\nimport io.legado.app.ui.book.group.GroupSelectDialog\nimport io.legado.app.ui.book.info.edit.BookInfoEditActivity\nimport io.legado.app.ui.book.manga.ReadMangaActivity\nimport io.legado.app.ui.book.read.ReadBookActivity\nimport io.legado.app.ui.book.read.ReadBookActivity.Companion.RESULT_DELETED\nimport io.legado.app.ui.book.search.SearchActivity\nimport io.legado.app.ui.book.source.edit.BookSourceEditActivity\nimport io.legado.app.ui.book.toc.TocActivityResult\nimport io.legado.app.ui.file.HandleFileContract\nimport io.legado.app.ui.login.SourceLoginActivity\nimport io.legado.app.ui.widget.dialog.PhotoDialog\nimport io.legado.app.ui.widget.dialog.VariableDialog\nimport io.legado.app.ui.widget.dialog.WaitDialog\nimport io.legado.app.utils.ColorUtils\nimport io.legado.app.utils.ConvertUtils\nimport io.legado.app.utils.FileDoc\nimport io.legado.app.utils.GSON\nimport io.legado.app.utils.StartActivityContract\nimport io.legado.app.utils.applyNavigationBarPadding\nimport io.legado.app.utils.dpToPx\nimport io.legado.app.utils.gone\nimport io.legado.app.utils.longToastOnUi\nimport io.legado.app.utils.openFileUri\nimport io.legado.app.utils.sendToClip\nimport io.legado.app.utils.shareWithQr\nimport io.legado.app.utils.showDialogFragment\nimport io.legado.app.utils.startActivity\nimport io.legado.app.utils.toastOnUi\nimport io.legado.app.utils.viewbindingdelegate.viewBinding\nimport io.legado.app.utils.visible\nimport kotlinx.coroutines.Dispatchers.IO\nimport kotlinx.coroutines.launch\nimport kotlinx.coroutines.withContext\n\nclass BookInfoActivity :\n    VMBaseActivity<ActivityBookInfoBinding, BookInfoViewModel>(toolBarTheme = Theme.Dark),\n    GroupSelectDialog.CallBack,\n    ChangeBookSourceDialog.CallBack,\n    ChangeCoverDialog.CallBack,\n    VariableDialog.Callback {\n\n    private val tocActivityResult = registerForActivityResult(TocActivityResult()) {\n        it?.let {\n            viewModel.getBook(false)?.let { book ->\n                lifecycleScope.launch {\n                    withContext(IO) {\n                        book.durChapterIndex = it.first\n                        book.durChapterPos = it.second\n                        chapterChanged = it.third\n                        appDb.bookDao.update(book)\n                    }\n                    startReadActivity(book)\n                }\n            }\n        } ?: let {\n            if (!viewModel.inBookshelf) {\n                viewModel.delBook()\n            }\n        }\n    }\n    private val localBookTreeSelect = registerForActivityResult(HandleFileContract()) {\n        it.uri?.let { treeUri ->\n            AppConfig.defaultBookTreeUri = treeUri.toString()\n        }\n    }\n    private val readBookResult = registerForActivityResult(\n        ActivityResultContracts.StartActivityForResult()\n    ) {\n        viewModel.upBook(intent)\n        when (it.resultCode) {\n            RESULT_OK -> {\n                viewModel.inBookshelf = true\n                upTvBookshelf()\n            }\n\n            RESULT_DELETED -> {\n                setResult(RESULT_OK)\n                finish()\n            }\n        }\n    }\n    private val infoEditResult = registerForActivityResult(\n        StartActivityContract(BookInfoEditActivity::class.java)\n    ) {\n        if (it.resultCode == RESULT_OK) {\n            viewModel.upEditBook()\n        }\n    }\n    private val editSourceResult = registerForActivityResult(\n        StartActivityContract(BookSourceEditActivity::class.java)\n    ) {\n        if (it.resultCode == RESULT_CANCELED) {\n            return@registerForActivityResult\n        }\n        book?.let { book ->\n            viewModel.bookSource = appDb.bookSourceDao.getBookSource(book.origin)\n            viewModel.refreshBook(book)\n        }\n    }\n    private var chapterChanged = false\n    private val waitDialog by lazy { WaitDialog(this) }\n    private var editMenuItem: MenuItem? = null\n    private val book get() = viewModel.getBook(false)\n\n    override val binding by viewBinding(ActivityBookInfoBinding::inflate)\n    override val viewModel by viewModels<BookInfoViewModel>()\n\n    @SuppressLint(\"PrivateResource\")\n    override fun onActivityCreated(savedInstanceState: Bundle?) {\n        binding.titleBar.setBackgroundResource(R.color.transparent)\n        binding.refreshLayout?.setColorSchemeColors(accentColor)\n        binding.arcView.setBgColor(backgroundColor)\n        binding.llInfo.setBackgroundColor(backgroundColor)\n        binding.flAction.setBackgroundColor(bottomBackground)\n        binding.flAction.applyNavigationBarPadding()\n        binding.tvShelf.setTextColor(getPrimaryTextColor(ColorUtils.isColorLight(bottomBackground)))\n        binding.tvToc.text = getString(R.string.toc_s, getString(R.string.loading))\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) {\n            binding.tvIntro.revealOnFocusHint = false\n        }\n        viewModel.bookData.observe(this) { showBook(it) }\n        viewModel.chapterListData.observe(this) { upLoading(false, it) }\n        viewModel.waitDialogData.observe(this) { upWaitDialogStatus(it) }\n        viewModel.initData(intent)\n        initViewEvent()\n    }\n\n    override fun onCompatCreateOptionsMenu(menu: Menu): Boolean {\n        menuInflater.inflate(R.menu.book_info, menu)\n        editMenuItem = menu.findItem(R.id.menu_edit)\n        return super.onCompatCreateOptionsMenu(menu)\n    }\n\n    override fun onMenuOpened(featureId: Int, menu: Menu): Boolean {\n        menu.findItem(R.id.menu_can_update)?.isChecked =\n            viewModel.bookData.value?.canUpdate ?: true\n        menu.findItem(R.id.menu_split_long_chapter)?.isChecked =\n            viewModel.bookData.value?.getSplitLongChapter() ?: true\n        menu.findItem(R.id.menu_login)?.isVisible =\n            !viewModel.bookSource?.loginUrl.isNullOrBlank()\n        menu.findItem(R.id.menu_set_source_variable)?.isVisible =\n            viewModel.bookSource != null\n        menu.findItem(R.id.menu_set_book_variable)?.isVisible =\n            viewModel.bookSource != null\n        menu.findItem(R.id.menu_can_update)?.isVisible =\n            viewModel.bookSource != null\n        menu.findItem(R.id.menu_split_long_chapter)?.isVisible =\n            viewModel.bookData.value?.isLocalTxt ?: false\n        menu.findItem(R.id.menu_upload)?.isVisible =\n            viewModel.bookData.value?.isLocal ?: false\n        menu.findItem(R.id.menu_delete_alert)?.isChecked =\n            LocalConfig.bookInfoDeleteAlert\n        return super.onMenuOpened(featureId, menu)\n    }\n\n    override fun onCompatOptionsItemSelected(item: MenuItem): Boolean {\n        when (item.itemId) {\n            R.id.menu_edit -> {\n                viewModel.getBook()?.let {\n                    infoEditResult.launch {\n                        putExtra(\"bookUrl\", it.bookUrl)\n                    }\n                }\n            }\n\n            R.id.menu_share_it -> {\n                viewModel.getBook()?.let {\n                    val bookJson = GSON.toJson(it)\n                    val shareStr = \"${it.bookUrl}#$bookJson\"\n                    shareWithQr(shareStr, it.name)\n                }\n            }\n\n            R.id.menu_refresh -> {\n                refreshBook()\n            }\n\n            R.id.menu_login -> viewModel.bookSource?.let {\n                startActivity<SourceLoginActivity> {\n                    putExtra(\"type\", \"bookSource\")\n                    putExtra(\"key\", it.bookSourceUrl)\n                }\n            }\n\n            R.id.menu_top -> viewModel.topBook()\n            R.id.menu_set_source_variable -> setSourceVariable()\n            R.id.menu_set_book_variable -> setBookVariable()\n            R.id.menu_copy_book_url -> viewModel.getBook()?.bookUrl?.let {\n                sendToClip(it)\n            }\n\n            R.id.menu_copy_toc_url -> viewModel.getBook()?.tocUrl?.let {\n                sendToClip(it)\n            }\n\n            R.id.menu_can_update -> {\n                viewModel.getBook()?.let {\n                    it.canUpdate = !it.canUpdate\n                    if (viewModel.inBookshelf) {\n                        if (!it.canUpdate) {\n                            it.removeType(BookType.updateError)\n                        }\n                        viewModel.saveBook(it)\n                    }\n                }\n            }\n\n            R.id.menu_clear_cache -> viewModel.clearCache()\n            R.id.menu_log -> showDialogFragment<AppLogDialog>()\n            R.id.menu_split_long_chapter -> {\n                upLoading(true)\n                viewModel.getBook()?.let {\n                    it.setSplitLongChapter(!item.isChecked)\n                    viewModel.loadBookInfo(it, false)\n                }\n                item.isChecked = !item.isChecked\n                if (!item.isChecked) longToastOnUi(R.string.need_more_time_load_content)\n            }\n\n            R.id.menu_delete_alert -> LocalConfig.bookInfoDeleteAlert = !item.isChecked\n            R.id.menu_upload -> {\n                viewModel.getBook()?.let { book ->\n                    book.getRemoteUrl()?.let {\n                        alert(R.string.draw, R.string.sure_upload) {\n                            okButton {\n                                upLoadBook(book)\n                            }\n                            cancelButton()\n                        }\n                    } ?: upLoadBook(book)\n                }\n            }\n        }\n        return super.onCompatOptionsItemSelected(item)\n    }\n\n    override fun observeLiveBus() {\n        viewModel.actionLive.observe(this) {\n            when (it) {\n                \"selectBooksDir\" -> localBookTreeSelect.launch {\n                    title = getString(R.string.select_book_folder)\n                }\n            }\n        }\n    }\n\n    override fun dispatchTouchEvent(ev: MotionEvent): Boolean {\n        if (ev.action == MotionEvent.ACTION_DOWN) {\n            currentFocus?.let {\n                if (it === binding.tvIntro && binding.tvIntro.hasSelection()) {\n                    it.clearFocus()\n                }\n            }\n        }\n        return super.dispatchTouchEvent(ev)\n    }\n\n    private fun refreshBook() {\n        upLoading(true)\n        viewModel.getBook()?.let {\n            viewModel.refreshBook(it)\n        }\n    }\n\n    private fun upLoadBook(\n        book: Book,\n        bookWebDav: RemoteBookWebDav? = AppWebDav.defaultBookWebDav,\n    ) {\n        lifecycleScope.launch {\n            waitDialog.setText(\"上传中.....\")\n            waitDialog.show()\n            try {\n                bookWebDav\n                    ?.upload(book)\n                    ?: throw NoStackTraceException(\"未配置webDav\")\n                //更新书籍最后更新时间,使之比远程书籍的时间新\n                book.lastCheckTime = System.currentTimeMillis()\n                viewModel.saveBook(book)\n            } catch (e: Exception) {\n                toastOnUi(e.localizedMessage)\n            } finally {\n                waitDialog.dismiss()\n            }\n        }\n    }\n\n    private fun showBook(book: Book) = binding.run {\n        showCover(book)\n        tvName.text = book.name\n        tvAuthor.text = getString(R.string.author_show, book.getRealAuthor())\n        tvOrigin.text = getString(R.string.origin_show, book.originName)\n        tvLasted.text = getString(R.string.lasted_show, book.latestChapterTitle)\n        tvIntro.text = book.getDisplayIntro()\n        llToc?.visible(!book.isWebFile)\n        upTvBookshelf()\n        upKinds(book)\n        upGroup(book.group)\n    }\n\n    private fun upKinds(book: Book) = binding.run {\n        lifecycleScope.launch {\n            var kinds = book.getKindList()\n            if (book.isLocal) {\n                withContext(IO) {\n                    val size = FileDoc.fromFile(book.bookUrl).size\n                    if (size > 0) {\n                        kinds = kinds.toMutableList()\n                        kinds.add(ConvertUtils.formatFileSize(size))\n                    }\n                }\n            }\n            if (kinds.isEmpty()) {\n                lbKind.gone()\n            } else {\n                lbKind.visible()\n                lbKind.setLabels(kinds)\n            }\n        }\n    }\n\n    private fun showCover(book: Book) {\n        binding.ivCover.load(book.getDisplayCover(), book.name, book.author, false, book.origin) {\n            if (!AppConfig.isEInkMode) {\n                BookCover.loadBlur(this, book.getDisplayCover(), false, book.origin)\n                    .into(binding.bgBook)\n            }\n        }\n    }\n\n    private fun upLoading(isLoading: Boolean, chapterList: List<BookChapter>? = null) {\n        when {\n            isLoading -> {\n                binding.tvToc.text = getString(R.string.toc_s, getString(R.string.loading))\n            }\n\n            chapterList.isNullOrEmpty() -> {\n                binding.tvToc.text = getString(\n                    R.string.toc_s,\n                    getString(R.string.error_load_toc)\n                )\n            }\n\n            else -> {\n                book?.let {\n                    binding.tvToc.text = getString(R.string.toc_s, it.durChapterTitle)\n                    binding.tvLasted.text = getString(R.string.lasted_show, it.latestChapterTitle)\n                }\n            }\n        }\n    }\n\n    private fun upTvBookshelf() {\n        if (viewModel.inBookshelf) {\n            binding.tvShelf.text = getString(R.string.remove_from_bookshelf)\n        } else {\n            binding.tvShelf.text = getString(R.string.add_to_bookshelf)\n        }\n        editMenuItem?.isVisible = viewModel.inBookshelf\n    }\n\n    private fun upGroup(groupId: Long) {\n        viewModel.loadGroup(groupId) {\n            if (it.isNullOrEmpty()) {\n                binding.tvGroup.text = if (book?.isLocal == true) {\n                    getString(R.string.group_s, getString(R.string.local_no_group))\n                } else {\n                    getString(R.string.group_s, getString(R.string.no_group))\n                }\n            } else {\n                binding.tvGroup.text = getString(R.string.group_s, it)\n            }\n        }\n    }\n\n    private fun initViewEvent() = binding.run {\n        ivCover.setOnClickListener {\n            viewModel.getBook()?.let {\n                showDialogFragment(\n                    ChangeCoverDialog(it.name, it.author)\n                )\n            }\n        }\n        ivCover.setOnLongClickListener {\n            viewModel.getBook()?.getDisplayCover()?.let { path ->\n                showDialogFragment(PhotoDialog(path))\n            }\n            true\n        }\n        tvRead.setOnClickListener {\n            viewModel.getBook()?.let { book ->\n                if (book.isWebFile) {\n                    showWebFileDownloadAlert {\n                        readBook(it)\n                    }\n                } else {\n                    readBook(book)\n                }\n            }\n        }\n        tvShelf.setOnClickListener {\n            viewModel.getBook()?.let { book ->\n                if (viewModel.inBookshelf) {\n                    deleteBook()\n                } else {\n                    if (book.isWebFile) {\n                        showWebFileDownloadAlert()\n                    } else {\n                        viewModel.addToBookshelf {\n                            upTvBookshelf()\n                        }\n                    }\n                }\n            }\n        }\n        tvOrigin.setOnClickListener {\n            viewModel.getBook()?.let { book ->\n                if (book.isLocal) return@let\n                if (!appDb.bookSourceDao.has(book.origin)) {\n                    toastOnUi(R.string.error_no_source)\n                    return@let\n                }\n                editSourceResult.launch {\n                    putExtra(\"sourceUrl\", book.origin)\n                }\n            }\n        }\n        tvChangeSource.setOnClickListener {\n            viewModel.getBook()?.let { book ->\n                showDialogFragment(ChangeBookSourceDialog(book.name, book.author))\n            }\n        }\n        tvTocView.setOnClickListener {\n            if (viewModel.chapterListData.value.isNullOrEmpty()) {\n                toastOnUi(R.string.chapter_list_empty)\n                return@setOnClickListener\n            }\n            viewModel.getBook()?.let { book ->\n                if (!viewModel.inBookshelf) {\n                    viewModel.saveBook(book) {\n                        viewModel.saveChapterList {\n                            openChapterList()\n                        }\n                    }\n                } else {\n                    openChapterList()\n                }\n            }\n        }\n        tvChangeGroup.setOnClickListener {\n            viewModel.getBook()?.let {\n                showDialogFragment(\n                    GroupSelectDialog(it.group)\n                )\n            }\n        }\n        tvAuthor.setOnClickListener {\n            viewModel.getBook(false)?.let { book ->\n                startActivity<SearchActivity> {\n                    putExtra(\"key\", book.author)\n                }\n            }\n        }\n        tvName.setOnClickListener {\n            viewModel.getBook(false)?.let { book ->\n                startActivity<SearchActivity> {\n                    putExtra(\"key\", book.name)\n                }\n            }\n        }\n        refreshLayout?.setOnRefreshListener {\n            refreshLayout.isRefreshing = false\n            refreshBook()\n        }\n    }\n\n    private fun setSourceVariable() {\n        lifecycleScope.launch {\n            val source = viewModel.bookSource\n            if (source == null) {\n                toastOnUi(\"书源不存在\")\n                return@launch\n            }\n            val comment =\n                source.getDisplayVariableComment(\"源变量可在js中通过source.getVariable()获取\")\n            val variable = withContext(IO) { source.getVariable() }\n            showDialogFragment(\n                VariableDialog(\n                    getString(R.string.set_source_variable),\n                    source.getKey(),\n                    variable,\n                    comment\n                )\n            )\n        }\n    }\n\n    private fun setBookVariable() {\n        lifecycleScope.launch {\n            val source = viewModel.bookSource\n            if (source == null) {\n                toastOnUi(\"书源不存在\")\n                return@launch\n            }\n            val book = viewModel.getBook() ?: return@launch\n            val variable = withContext(IO) { book.getCustomVariable() }\n            val comment = source.getDisplayVariableComment(\n                \"\"\"书籍变量可在js中通过book.getVariable(\"custom\")获取\"\"\"\n            )\n            showDialogFragment(\n                VariableDialog(\n                    getString(R.string.set_book_variable),\n                    book.bookUrl,\n                    variable,\n                    comment\n                )\n            )\n        }\n    }\n\n    override fun setVariable(key: String, variable: String?) {\n        when (key) {\n            viewModel.bookSource?.getKey() -> viewModel.bookSource?.setVariable(variable)\n            viewModel.bookData.value?.bookUrl -> viewModel.bookData.value?.let {\n                it.putCustomVariable(variable)\n                if (viewModel.inBookshelf) {\n                    viewModel.saveBook(it)\n                }\n            }\n        }\n    }\n\n    @SuppressLint(\"InflateParams\")\n    private fun deleteBook() {\n        viewModel.getBook()?.let {\n            if (LocalConfig.bookInfoDeleteAlert) {\n                alert(\n                    titleResource = R.string.draw,\n                    messageResource = R.string.sure_del\n                ) {\n                    var checkBox: CheckBox? = null\n                    if (it.isLocal) {\n                        checkBox = CheckBox(this@BookInfoActivity).apply {\n                            setText(R.string.delete_book_file)\n                            isChecked = LocalConfig.deleteBookOriginal\n                        }\n                        val view = LinearLayout(this@BookInfoActivity).apply {\n                            setPadding(16.dpToPx(), 0, 16.dpToPx(), 0)\n                            addView(checkBox)\n                        }\n                        customView { view }\n                    }\n                    yesButton {\n                        if (checkBox != null) {\n                            LocalConfig.deleteBookOriginal = checkBox.isChecked\n                        }\n                        viewModel.delBook(LocalConfig.deleteBookOriginal) {\n                            setResult(RESULT_OK)\n                            finish()\n                        }\n                    }\n                    noButton()\n                }\n            } else {\n                viewModel.delBook(LocalConfig.deleteBookOriginal) {\n                    setResult(RESULT_OK)\n                    finish()\n                }\n            }\n        }\n    }\n\n    private fun openChapterList() {\n        viewModel.getBook()?.let {\n            tocActivityResult.launch(it.bookUrl)\n        }\n    }\n\n    private fun showWebFileDownloadAlert(\n        onClick: ((Book) -> Unit)? = null,\n    ) {\n        val webFiles = viewModel.webFiles\n        if (webFiles.isEmpty()) {\n            toastOnUi(\"Unexpected webFileData\")\n            return\n        }\n        selector(\n            R.string.download_and_import_file,\n            webFiles\n        ) { _, webFile, _ ->\n            if (webFile.isSupported) {\n                /* import */\n                viewModel.importOrDownloadWebFile<Book>(webFile) {\n                    onClick?.invoke(it)\n                }\n            } else if (webFile.isSupportDecompress) {\n                /* 解压筛选后再选择导入项 */\n                viewModel.importOrDownloadWebFile<Uri>(webFile) { uri ->\n                    viewModel.getArchiveFilesName(uri) { fileNames ->\n                        if (fileNames.size == 1) {\n                            viewModel.importArchiveBook(uri, fileNames[0]) {\n                                onClick?.invoke(it)\n                            }\n                        } else {\n                            showDecompressFileImportAlert(uri, fileNames, onClick)\n                        }\n                    }\n                }\n            } else {\n                alert(\n                    title = getString(R.string.draw),\n                    message = getString(R.string.file_not_supported, webFile.name)\n                ) {\n                    neutralButton(R.string.open_fun) {\n                        /* download only */\n                        viewModel.importOrDownloadWebFile<Uri>(webFile) {\n                            openFileUri(it, \"*/*\")\n                        }\n                    }\n                    noButton()\n                }\n            }\n        }\n    }\n\n    private fun showDecompressFileImportAlert(\n        archiveFileUri: Uri,\n        fileNames: List<String>,\n        success: ((Book) -> Unit)? = null,\n    ) {\n        if (fileNames.isEmpty()) {\n            toastOnUi(R.string.unsupport_archivefile_entry)\n            return\n        }\n        selector(\n            R.string.import_select_book,\n            fileNames\n        ) { _, name, _ ->\n            viewModel.importArchiveBook(archiveFileUri, name) {\n                success?.invoke(it)\n            }\n        }\n    }\n\n    private fun readBook(book: Book) {\n        if (!viewModel.inBookshelf) {\n            book.addType(BookType.notShelf)\n            viewModel.saveBook(book) {\n                viewModel.saveChapterList {\n                    startReadActivity(book)\n                }\n            }\n        } else {\n            viewModel.saveBook(book) {\n                startReadActivity(book)\n            }\n        }\n    }\n\n    private fun startReadActivity(book: Book) {\n        when {\n            book.isAudio -> readBookResult.launch(\n                Intent(this, AudioPlayActivity::class.java)\n                    .putExtra(\"bookUrl\", book.bookUrl)\n                    .putExtra(\"inBookshelf\", viewModel.inBookshelf)\n            )\n\n            else -> readBookResult.launch(\n                Intent(\n                    this,\n                    if (!book.isLocal && book.isImage && AppConfig.showMangaUi) ReadMangaActivity::class.java\n                    else ReadBookActivity::class.java\n                )\n                    .putExtra(\"bookUrl\", book.bookUrl)\n                    .putExtra(\"inBookshelf\", viewModel.inBookshelf)\n                    .putExtra(\"chapterChanged\", chapterChanged)\n            )\n        }\n    }\n\n    override val oldBook: Book?\n        get() = viewModel.bookData.value\n\n    override fun changeTo(source: BookSource, book: Book, toc: List<BookChapter>) {\n        viewModel.changeTo(source, book, toc)\n    }\n\n    override fun coverChangeTo(coverUrl: String) {\n        viewModel.bookData.value?.let { book ->\n            book.customCoverUrl = coverUrl\n            showCover(book)\n            if (viewModel.inBookshelf) {\n                viewModel.saveBook(book)\n            }\n        }\n    }\n\n    override fun upGroup(requestCode: Int, groupId: Long) {\n        upGroup(groupId)\n        viewModel.getBook()?.let { book ->\n            book.group = groupId\n            if (viewModel.inBookshelf) {\n                viewModel.saveBook(book)\n            } else if (groupId > 0) {\n                viewModel.addToBookshelf {\n                    upTvBookshelf()\n                }\n            }\n        }\n    }\n\n    private fun upWaitDialogStatus(isShow: Boolean) {\n        val showText = \"Loading.....\"\n        if (isShow) {\n            waitDialog.run {\n                setText(showText)\n                show()\n            }\n        } else {\n            waitDialog.dismiss()\n        }\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/book/info/BookInfoViewModel.kt",
    "content": "package io.legado.app.ui.book.info\n\nimport android.app.Application\nimport android.content.Intent\nimport android.net.Uri\nimport androidx.lifecycle.MutableLiveData\nimport androidx.lifecycle.viewModelScope\nimport io.legado.app.R\nimport io.legado.app.base.BaseViewModel\nimport io.legado.app.constant.AppLog\nimport io.legado.app.constant.AppPattern\nimport io.legado.app.constant.BookType\nimport io.legado.app.constant.EventBus\nimport io.legado.app.data.appDb\nimport io.legado.app.data.entities.Book\nimport io.legado.app.data.entities.BookChapter\nimport io.legado.app.data.entities.BookSource\nimport io.legado.app.exception.NoBooksDirException\nimport io.legado.app.exception.NoStackTraceException\nimport io.legado.app.help.AppWebDav\nimport io.legado.app.help.book.BookHelp\nimport io.legado.app.help.book.getExportFileName\nimport io.legado.app.help.book.getRemoteUrl\nimport io.legado.app.help.book.isLocal\nimport io.legado.app.help.book.isNotShelf\nimport io.legado.app.help.book.isSameNameAuthor\nimport io.legado.app.help.book.isWebFile\nimport io.legado.app.help.book.removeType\nimport io.legado.app.help.book.updateTo\nimport io.legado.app.help.coroutine.Coroutine\nimport io.legado.app.lib.webdav.ObjectNotFoundException\nimport io.legado.app.model.AudioPlay\nimport io.legado.app.model.BookCover\nimport io.legado.app.model.ReadBook\nimport io.legado.app.model.ReadManga\nimport io.legado.app.model.analyzeRule.AnalyzeUrl\nimport io.legado.app.model.localBook.LocalBook\nimport io.legado.app.model.webBook.WebBook\nimport io.legado.app.utils.ArchiveUtils\nimport io.legado.app.utils.UrlUtil\nimport io.legado.app.utils.isContentScheme\nimport io.legado.app.utils.postEvent\nimport io.legado.app.utils.toastOnUi\nimport kotlinx.coroutines.CoroutineScope\nimport kotlinx.coroutines.Dispatchers.IO\n\nclass BookInfoViewModel(application: Application) : BaseViewModel(application) {\n    val bookData = MutableLiveData<Book>()\n    val chapterListData = MutableLiveData<List<BookChapter>>()\n    val webFiles = mutableListOf<WebFile>()\n    var inBookshelf = false\n    var bookSource: BookSource? = null\n    private var changeSourceCoroutine: Coroutine<*>? = null\n    val waitDialogData = MutableLiveData<Boolean>()\n    val actionLive = MutableLiveData<String>()\n\n    fun initData(intent: Intent) {\n        execute {\n            val name = intent.getStringExtra(\"name\") ?: \"\"\n            val author = intent.getStringExtra(\"author\") ?: \"\"\n            val bookUrl = intent.getStringExtra(\"bookUrl\") ?: \"\"\n            appDb.bookDao.getBook(name, author)?.let {\n                inBookshelf = !it.isNotShelf\n                upBook(it)\n                return@execute\n            }\n            if (bookUrl.isNotBlank()) {\n                appDb.bookDao.getBook(bookUrl)?.let {\n                    inBookshelf = !it.isNotShelf\n                    upBook(it)\n                    return@execute\n                }\n                appDb.searchBookDao.getSearchBook(bookUrl)?.toBook()?.let {\n                    upBook(it)\n                    return@execute\n                }\n            }\n            appDb.searchBookDao.getFirstByNameAuthor(name, author)?.toBook()?.let {\n                upBook(it)\n                return@execute\n            }\n            throw NoStackTraceException(\"未找到书籍\")\n        }.onError {\n            AppLog.put(it.localizedMessage, it)\n            context.toastOnUi(it.localizedMessage)\n        }\n    }\n\n    fun upBook(intent: Intent) {\n        execute {\n            val name = intent.getStringExtra(\"name\") ?: \"\"\n            val author = intent.getStringExtra(\"author\") ?: \"\"\n            appDb.bookDao.getBook(name, author)?.let { book ->\n                upBook(book)\n            }\n        }\n    }\n\n    private fun upBook(book: Book) {\n        execute {\n            bookData.postValue(book)\n            upCoverByRule(book)\n            bookSource = if (book.isLocal) null else\n                appDb.bookSourceDao.getBookSource(book.origin)\n            if (book.tocUrl.isEmpty() && !book.isLocal) {\n                loadBookInfo(book, runPreUpdateJs = inBookshelf)\n            } else {\n                val chapterList = appDb.bookChapterDao.getChapterList(book.bookUrl)\n                if (chapterList.isNotEmpty()) {\n                    chapterListData.postValue(chapterList)\n                } else {\n                    loadChapter(book)\n                }\n            }\n        }\n    }\n\n    private fun upCoverByRule(book: Book) {\n        execute {\n            if (book.coverUrl.isNullOrBlank() && book.customCoverUrl.isNullOrBlank()) {\n                val coverUrl = BookCover.searchCover(book)\n                if (coverUrl.isNullOrBlank()) {\n                    return@execute\n                }\n                book.customCoverUrl = coverUrl\n                bookData.postValue(book)\n                if (inBookshelf) {\n                    saveBook(book)\n                }\n            }\n        }\n    }\n\n    fun refreshBook(book: Book) {\n        executeLazy(executeContext = IO) {\n            if (book.isLocal) {\n                book.tocUrl = \"\"\n                book.getRemoteUrl()?.let {\n                    val bookWebDav = AppWebDav.defaultBookWebDav\n                        ?: throw NoStackTraceException(\"webDav没有配置\")\n                    val remoteBook = bookWebDav.getRemoteBook(it)\n                    if (remoteBook == null) {\n                        book.origin = BookType.localTag\n                    } else if (remoteBook.lastModify > book.lastCheckTime) {\n                        val uri = bookWebDav.downloadRemoteBook(remoteBook)\n                        book.bookUrl = if (uri.isContentScheme()) uri.toString() else uri.path!!\n                        book.lastCheckTime = remoteBook.lastModify\n                    }\n                }\n            } else {\n                val bs = bookSource ?: return@executeLazy\n                if (book.originName != bs.bookSourceName) {\n                    book.originName = bs.bookSourceName\n                }\n            }\n        }.onError {\n            when (it) {\n                is ObjectNotFoundException -> {\n                    book.origin = BookType.localTag\n                }\n\n                else -> {\n                    AppLog.put(\"下载远程书籍<${book.name}>失败\", it)\n                }\n            }\n        }.onFinally {\n            loadBookInfo(book, false)\n        }.start()\n    }\n\n    fun loadBookInfo(\n        book: Book,\n        canReName: Boolean = true,\n        runPreUpdateJs: Boolean = true,\n        scope: CoroutineScope = viewModelScope\n    ) {\n        if (book.isLocal) {\n            LocalBook.upBookInfo(book)\n            bookData.postValue(book)\n            loadChapter(book)\n        } else {\n            val bookSource = bookSource ?: let {\n                chapterListData.postValue(emptyList())\n                context.toastOnUi(R.string.error_no_source)\n                return\n            }\n            WebBook.getBookInfo(scope, bookSource, book, canReName = canReName)\n                .onSuccess(IO) {\n                    val dbBook = appDb.bookDao.getBook(book.name, book.author)\n                    if (!inBookshelf && dbBook != null && !dbBook.isNotShelf && dbBook.origin == book.origin) {\n                        /**\n                         * book 来自搜索时(inBookshelf == false)，搜索的书名不存在于书架，但是加载详情后，书名更新，存在同名书籍\n                         * 此时 book 的数据会与数据库中的不同，需要更新 #3652 #4619\n                         * book 加载详情后虽然书名作者相同，但是又可能不是数据库中(书源不同)的那本书 #3149\n                         */\n                        dbBook.updateTo(it)\n                        inBookshelf = true\n                    }\n                    bookData.postValue(it)\n                    if (inBookshelf) {\n                        it.save()\n                    }\n                    if (it.isWebFile) {\n                        loadWebFile(it)\n                    } else {\n                        loadChapter(it, runPreUpdateJs)\n                    }\n                }.onError {\n                    AppLog.put(\"获取书籍信息失败\\n${it.localizedMessage}\", it)\n                    context.toastOnUi(R.string.error_get_book_info)\n                }\n        }\n    }\n\n    private fun loadChapter(\n        book: Book,\n        runPreUpdateJs: Boolean = true,\n        scope: CoroutineScope = viewModelScope\n    ) {\n        if (book.isLocal) {\n            execute(scope) {\n                LocalBook.getChapterList(book).let {\n                    appDb.bookDao.update(book)\n                    appDb.bookChapterDao.delByBook(book.bookUrl)\n                    appDb.bookChapterDao.insert(*it.toTypedArray())\n                    ReadBook.onChapterListUpdated(book)\n                    bookData.postValue(book)\n                    chapterListData.postValue(it)\n                }\n            }.onError {\n                context.toastOnUi(\"LoadTocError:${it.localizedMessage}\")\n            }\n        } else {\n            val bookSource = bookSource ?: let {\n                chapterListData.postValue(emptyList())\n                context.toastOnUi(R.string.error_no_source)\n                return\n            }\n            val oldBook = book.copy()\n            WebBook.getChapterList(scope, bookSource, book, runPreUpdateJs)\n                .onSuccess(IO) {\n                    if (inBookshelf) {\n                        appDb.bookDao.replace(oldBook, book)\n                        /**\n                         * runPreUpdateJs 有可能会修改 book 的 bookUrl\n                         */\n                        if (oldBook.bookUrl != book.bookUrl) {\n                            BookHelp.updateCacheFolder(oldBook, book)\n                        }\n                        appDb.bookChapterDao.delByBook(oldBook.bookUrl)\n                        appDb.bookChapterDao.insert(*it.toTypedArray())\n                        ReadBook.onChapterListUpdated(book)\n                    }\n                    bookData.postValue(book)\n                    chapterListData.postValue(it)\n                }.onError {\n                    chapterListData.postValue(emptyList())\n                    AppLog.put(\"获取目录失败\\n${it.localizedMessage}\", it)\n                    context.toastOnUi(R.string.error_get_chapter_list)\n                }\n        }\n    }\n\n\n    fun loadGroup(groupId: Long, success: ((groupNames: String?) -> Unit)) {\n        execute {\n            appDb.bookGroupDao.getGroupNames(groupId).joinToString(\",\")\n        }.onSuccess {\n            success.invoke(it)\n        }\n    }\n\n    private fun loadWebFile(book: Book) {\n        execute {\n            webFiles.clear()\n            val fileNameNoExtension = if (book.author.isBlank()) book.name\n            else \"${book.name} 作者：${book.author}\"\n            book.downloadUrls!!.map {\n                val analyzeUrl = AnalyzeUrl(\n                    it, source = bookSource,\n                    coroutineContext = coroutineContext\n                )\n                val mFileName = UrlUtil.getFileName(analyzeUrl)\n                    ?: \"${fileNameNoExtension}.${analyzeUrl.type}\"\n                WebFile(it, mFileName)\n            }\n        }.onError {\n            context.toastOnUi(\"LoadWebFileError\\n${it.localizedMessage}\")\n        }.onSuccess {\n            webFiles.addAll(it)\n        }\n    }\n\n    /* 导入或者下载在线文件 */\n    fun <T> importOrDownloadWebFile(webFile: WebFile, success: ((T) -> Unit)?) {\n        bookSource ?: return\n        execute {\n            waitDialogData.postValue(true)\n            if (webFile.isSupported) {\n                val book = LocalBook.importFileOnLine(\n                    webFile.url,\n                    bookData.value!!.getExportFileName(webFile.suffix),\n                    bookSource\n                )\n                changeToLocalBook(book)\n            } else {\n                LocalBook.saveBookFile(\n                    webFile.url,\n                    bookData.value!!.getExportFileName(webFile.suffix),\n                    bookSource\n                )\n            }\n        }.onSuccess {\n            @Suppress(\"unchecked_cast\")\n            success?.invoke(it as T)\n        }.onError {\n            when (it) {\n                is NoBooksDirException -> actionLive.postValue(\"selectBooksDir\")\n                else -> {\n                    AppLog.put(\"ImportWebFileError\\n${it.localizedMessage}\", it)\n                    context.toastOnUi(\"ImportWebFileError\\n${it.localizedMessage}\")\n                    webFiles.remove(webFile)\n                }\n            }\n        }.onFinally {\n            waitDialogData.postValue(false)\n        }\n    }\n\n    fun getArchiveFilesName(archiveFileUri: Uri, onSuccess: (List<String>) -> Unit) {\n        execute {\n            ArchiveUtils.getArchiveFilesName(archiveFileUri) {\n                AppPattern.bookFileRegex.matches(it)\n            }\n        }.onError {\n            AppLog.put(\"getArchiveEntriesName Error:\\n${it.localizedMessage}\", it)\n            context.toastOnUi(\"getArchiveEntriesName Error:\\n${it.localizedMessage}\")\n        }.onSuccess {\n            onSuccess.invoke(it)\n        }\n    }\n\n    fun importArchiveBook(\n        archiveFileUri: Uri,\n        archiveEntryName: String,\n        success: ((Book) -> Unit)? = null\n    ) {\n        execute {\n            val suffix = archiveEntryName.substringAfterLast(\".\")\n            LocalBook.importArchiveFile(\n                archiveFileUri,\n                bookData.value!!.getExportFileName(suffix)\n            ) {\n                it.contains(archiveEntryName)\n            }.first()\n        }.onSuccess {\n            val book = changeToLocalBook(it)\n            success?.invoke(book)\n        }.onError {\n            AppLog.put(\"importArchiveBook Error:\\n${it.localizedMessage}\", it)\n            context.toastOnUi(\"importArchiveBook Error:\\n${it.localizedMessage}\")\n        }\n    }\n\n    fun changeTo(source: BookSource, book: Book, toc: List<BookChapter>) {\n        changeSourceCoroutine?.cancel()\n        changeSourceCoroutine = execute {\n            bookSource = source\n            bookData.value?.migrateTo(book, toc)\n            if (inBookshelf) {\n                book.removeType(BookType.updateError)\n                bookData.value?.delete()\n                appDb.bookDao.insert(book)\n                appDb.bookChapterDao.insert(*toc.toTypedArray())\n            }\n            bookData.postValue(book)\n            chapterListData.postValue(toc)\n        }.onFinally {\n            postEvent(EventBus.SOURCE_CHANGED, book.bookUrl)\n        }\n    }\n\n    fun topBook() {\n        execute {\n            bookData.value?.let { book ->\n                val minOrder = appDb.bookDao.minOrder\n                book.order = minOrder - 1\n                book.durChapterTime = System.currentTimeMillis()\n                appDb.bookDao.update(book)\n            }\n        }\n    }\n\n    fun saveBook(book: Book?, success: (() -> Unit)? = null) {\n        book ?: return\n        execute {\n            if (book.order == 0) {\n                book.order = appDb.bookDao.minOrder - 1\n            }\n            appDb.bookDao.getBook(book.name, book.author)?.let {\n                book.durChapterIndex = it.durChapterIndex\n                book.durChapterPos = it.durChapterPos\n                book.durChapterTitle = it.durChapterTitle\n            }\n            book.save()\n            if (ReadBook.book?.isSameNameAuthor(book) == true) {\n                ReadBook.book = book\n            } else if (AudioPlay.book?.isSameNameAuthor(book) == true) {\n                AudioPlay.book = book\n            }\n        }.onSuccess {\n            success?.invoke()\n        }\n    }\n\n    fun saveChapterList(success: (() -> Unit)?) {\n        execute {\n            chapterListData.value?.let {\n                appDb.bookChapterDao.insert(*it.toTypedArray())\n            }\n        }.onSuccess {\n            success?.invoke()\n        }\n    }\n\n    fun addToBookshelf(success: (() -> Unit)?) {\n        execute {\n            bookData.value?.let { book ->\n                book.removeType(BookType.notShelf)\n                if (book.order == 0) {\n                    book.order = appDb.bookDao.minOrder - 1\n                }\n                appDb.bookDao.getBook(book.name, book.author)?.let {\n                    book.durChapterIndex = it.durChapterIndex\n                    book.durChapterPos = it.durChapterPos\n                    book.durChapterTitle = it.durChapterTitle\n                }\n                if (ReadBook.book?.isSameNameAuthor(book) == true) {\n                    ReadBook.book = book\n                } else if (AudioPlay.book?.isSameNameAuthor(book) == true) {\n                    AudioPlay.book = book\n                }\n                book.save()\n            }\n            chapterListData.value?.let {\n                appDb.bookChapterDao.insert(*it.toTypedArray())\n            }\n            inBookshelf = true\n        }.onSuccess {\n            success?.invoke()\n        }\n    }\n\n    fun getBook(toastNull: Boolean = true): Book? {\n        val book = bookData.value\n        if (toastNull && book == null) {\n            context.toastOnUi(\"book is null\")\n        }\n        return book\n    }\n\n    fun delBook(deleteOriginal: Boolean = false, success: (() -> Unit)? = null) {\n        execute {\n            bookData.value?.let {\n                it.delete()\n                inBookshelf = false\n                if (it.isLocal) {\n                    LocalBook.deleteBook(it, deleteOriginal)\n                }\n            }\n        }.onSuccess {\n            success?.invoke()\n        }\n    }\n\n    fun clearCache() {\n        execute {\n            BookHelp.clearCache(bookData.value!!)\n            if (ReadBook.book?.bookUrl == bookData.value!!.bookUrl) {\n                ReadBook.clearTextChapter()\n            }\n            if (ReadManga.book?.bookUrl == bookData.value!!.bookUrl) {\n                ReadManga.clearMangaChapter()\n            }\n        }.onSuccess {\n            context.toastOnUi(R.string.clear_cache_success)\n        }.onError {\n            context.toastOnUi(\"清理缓存出错\\n${it.localizedMessage}\")\n        }\n    }\n\n    fun upEditBook() {\n        bookData.value?.let {\n            appDb.bookDao.getBook(it.bookUrl)?.let { book ->\n                bookData.postValue(book)\n            }\n        }\n    }\n\n    private fun changeToLocalBook(localBook: Book): Book {\n        return LocalBook.mergeBook(localBook, bookData.value).let {\n            bookData.postValue(it)\n            loadChapter(it)\n            inBookshelf = true\n            it\n        }\n    }\n\n    data class WebFile(\n        val url: String,\n        val name: String,\n    ) {\n\n        override fun toString(): String {\n            return name\n        }\n\n        // 后缀\n        val suffix: String = UrlUtil.getSuffix(name)\n\n        // txt epub umd pdf等文件\n        val isSupported: Boolean = AppPattern.bookFileRegex.matches(name)\n\n        // 压缩包形式的txt epub umd pdf文件\n        val isSupportDecompress: Boolean = AppPattern.archiveFileRegex.matches(name)\n\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/book/info/edit/BookInfoEditActivity.kt",
    "content": "package io.legado.app.ui.book.info.edit\n\nimport android.net.Uri\nimport android.os.Bundle\nimport android.view.Menu\nimport android.view.MenuItem\nimport androidx.activity.viewModels\nimport androidx.core.view.WindowInsetsCompat\nimport io.legado.app.R\nimport io.legado.app.base.VMBaseActivity\nimport io.legado.app.constant.BookType\nimport io.legado.app.data.entities.Book\nimport io.legado.app.databinding.ActivityBookInfoEditBinding\nimport io.legado.app.help.book.BookHelp\nimport io.legado.app.help.book.addType\nimport io.legado.app.help.book.isAudio\nimport io.legado.app.help.book.isImage\nimport io.legado.app.help.book.isLocal\nimport io.legado.app.help.book.removeType\nimport io.legado.app.ui.book.changecover.ChangeCoverDialog\nimport io.legado.app.ui.file.HandleFileContract\nimport io.legado.app.utils.FileUtils\nimport io.legado.app.utils.MD5Utils\nimport io.legado.app.utils.externalFiles\nimport io.legado.app.utils.inputStream\nimport io.legado.app.utils.readUri\nimport io.legado.app.utils.setOnApplyWindowInsetsListenerCompat\nimport io.legado.app.utils.showDialogFragment\nimport io.legado.app.utils.toastOnUi\nimport io.legado.app.utils.viewbindingdelegate.viewBinding\nimport splitties.init.appCtx\nimport splitties.views.bottomPadding\nimport java.io.FileOutputStream\n\nclass BookInfoEditActivity :\n    VMBaseActivity<ActivityBookInfoEditBinding, BookInfoEditViewModel>(),\n    ChangeCoverDialog.CallBack {\n\n    private val selectCover = registerForActivityResult(HandleFileContract()) {\n        it.uri?.let { uri ->\n            coverChangeTo(uri)\n        }\n    }\n\n    override val binding by viewBinding(ActivityBookInfoEditBinding::inflate)\n    override val viewModel by viewModels<BookInfoEditViewModel>()\n\n    override fun onActivityCreated(savedInstanceState: Bundle?) {\n        viewModel.bookData.observe(this) { upView(it) }\n        if (viewModel.bookData.value == null) {\n            intent.getStringExtra(\"bookUrl\")?.let {\n                viewModel.loadBook(it)\n            }\n        }\n        initView()\n        initEvent()\n    }\n\n    override fun onCompatCreateOptionsMenu(menu: Menu): Boolean {\n        menuInflater.inflate(R.menu.book_info_edit, menu)\n        return super.onCompatCreateOptionsMenu(menu)\n    }\n\n    override fun onCompatOptionsItemSelected(item: MenuItem): Boolean {\n        when (item.itemId) {\n            R.id.menu_save -> saveData()\n        }\n        return super.onCompatOptionsItemSelected(item)\n    }\n\n    private fun initView() {\n        binding.root.setOnApplyWindowInsetsListenerCompat { view, windowInsets ->\n            val typeMask = WindowInsetsCompat.Type.systemBars() or WindowInsetsCompat.Type.ime()\n            val insets = windowInsets.getInsets(typeMask)\n            view.bottomPadding = insets.bottom\n            windowInsets\n        }\n    }\n\n    private fun initEvent() = binding.run {\n        tvChangeCover.setOnClickListener {\n            viewModel.bookData.value?.let {\n                showDialogFragment(\n                    ChangeCoverDialog(it.name, it.author)\n                )\n            }\n        }\n        tvSelectCover.setOnClickListener {\n            selectCover.launch {\n                mode = HandleFileContract.IMAGE\n            }\n        }\n        tvRefreshCover.setOnClickListener {\n            viewModel.book?.customCoverUrl = tieCoverUrl.text?.toString()\n            upCover()\n        }\n    }\n\n    private fun upView(book: Book) = binding.run {\n        tieBookName.setText(book.name)\n        tieBookAuthor.setText(book.author)\n        spType.setSelection(\n            when {\n                book.isImage -> 2\n                book.isAudio -> 1\n                else -> 0\n            }\n        )\n        tieCoverUrl.setText(book.getDisplayCover())\n        tieBookIntro.setText(book.getDisplayIntro())\n        upCover()\n    }\n\n    private fun upCover() {\n        viewModel.book?.let {\n            binding.ivCover.load(it.getDisplayCover(), it.name, it.author, false, it.origin)\n        }\n    }\n\n    private fun saveData() = binding.run {\n        val book = viewModel.book ?: return@run\n        val oldBook = book.copy()\n        book.name = tieBookName.text?.toString() ?: \"\"\n        book.author = tieBookAuthor.text?.toString() ?: \"\"\n        val local = if (book.isLocal) BookType.local else 0\n        val bookType = when (spType.selectedItemPosition) {\n            2 -> BookType.image or local\n            1 -> BookType.audio or local\n            else -> BookType.text or local\n        }\n        book.removeType(BookType.local, BookType.image, BookType.audio, BookType.text)\n        book.addType(bookType)\n        val customCoverUrl = tieCoverUrl.text?.toString()\n        book.customCoverUrl = if (customCoverUrl == book.coverUrl) null else customCoverUrl\n        val customIntro = tieBookIntro.text?.toString()\n        book.customIntro = if (customIntro == book.intro) null else customIntro\n        BookHelp.updateCacheFolder(oldBook, book)\n        viewModel.saveBook(book) {\n            setResult(RESULT_OK)\n            finish()\n        }\n    }\n\n    override fun coverChangeTo(coverUrl: String) {\n        viewModel.book?.customCoverUrl = coverUrl\n        binding.tieCoverUrl.setText(coverUrl)\n        upCover()\n    }\n\n    private fun coverChangeTo(uri: Uri) {\n        readUri(uri) { fileDoc, inputStream ->\n            runCatching {\n                inputStream.use {\n                    var file = this.externalFiles\n                    val suffix = fileDoc.name.substringAfterLast(\".\")\n                    val fileName = uri.inputStream(this).getOrThrow().use {\n                        MD5Utils.md5Encode(it) + \".$suffix\"\n                    }\n                    file = FileUtils.createFileIfNotExist(file, \"covers\", fileName)\n                    FileOutputStream(file).use { outputStream ->\n                        inputStream.copyTo(outputStream)\n                    }\n                    coverChangeTo(file.absolutePath)\n                }\n            }.onFailure {\n                appCtx.toastOnUi(it.localizedMessage)\n            }\n        }\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/book/info/edit/BookInfoEditViewModel.kt",
    "content": "package io.legado.app.ui.book.info.edit\n\nimport android.app.Application\nimport android.database.sqlite.SQLiteConstraintException\nimport androidx.lifecycle.MutableLiveData\nimport io.legado.app.base.BaseViewModel\nimport io.legado.app.constant.AppLog\nimport io.legado.app.data.appDb\nimport io.legado.app.data.entities.Book\nimport io.legado.app.model.ReadBook\n\nclass BookInfoEditViewModel(application: Application) : BaseViewModel(application) {\n    var book: Book? = null\n    val bookData = MutableLiveData<Book>()\n\n    fun loadBook(bookUrl: String) {\n        execute {\n            book = appDb.bookDao.getBook(bookUrl)\n            book?.let {\n                bookData.postValue(it)\n            }\n        }\n    }\n\n    fun saveBook(book: Book, success: (() -> Unit)?) {\n        execute {\n            if (ReadBook.book?.bookUrl == book.bookUrl) {\n                ReadBook.book = book\n            }\n            appDb.bookDao.update(book)\n        }.onSuccess {\n            success?.invoke()\n        }.onError {\n            if (it is SQLiteConstraintException) {\n                AppLog.put(\"书籍信息保存失败，存在相同书名作者书籍\\n$it\", it, true)\n            } else {\n                AppLog.put(\"书籍信息保存失败\\n$it\", it, true)\n            }\n        }\n    }\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/book/manage/BookAdapter.kt",
    "content": "package io.legado.app.ui.book.manage\n\nimport android.annotation.SuppressLint\nimport android.content.Context\nimport android.view.View\nimport android.view.ViewGroup\nimport androidx.core.os.bundleOf\nimport androidx.recyclerview.widget.RecyclerView\nimport io.legado.app.R\nimport io.legado.app.base.adapter.ItemViewHolder\nimport io.legado.app.base.adapter.RecyclerAdapter\nimport io.legado.app.data.entities.Book\nimport io.legado.app.data.entities.BookGroup\nimport io.legado.app.databinding.ItemArrangeBookBinding\nimport io.legado.app.help.book.isLocal\nimport io.legado.app.help.config.AppConfig\nimport io.legado.app.lib.theme.backgroundColor\nimport io.legado.app.ui.widget.recycler.DragSelectTouchHelper\nimport io.legado.app.ui.widget.recycler.ItemTouchCallback\nimport java.util.Collections\n\nclass BookAdapter(context: Context, val callBack: CallBack) :\n    RecyclerAdapter<Book, ItemArrangeBookBinding>(context),\n\n    ItemTouchCallback.Callback {\n    val groupRequestCode = 12\n    private val selectedBooks: HashSet<Book> = hashSetOf()\n    var actionItem: Book? = null\n\n    val selection: List<Book>\n        get() {\n            return getItems().filter {\n                selectedBooks.contains(it)\n            }\n        }\n\n    override fun getViewBinding(parent: ViewGroup): ItemArrangeBookBinding {\n        return ItemArrangeBookBinding.inflate(inflater, parent, false)\n    }\n\n    override fun onCurrentListChanged() {\n        callBack.upSelectCount()\n    }\n\n    override fun convert(\n        holder: ItemViewHolder,\n        binding: ItemArrangeBookBinding,\n        item: Book,\n        payloads: MutableList<Any>\n    ) {\n        binding.apply {\n            root.setBackgroundColor(context.backgroundColor)\n            tvName.text = item.name\n            tvAuthor.text = item.author\n            tvAuthor.visibility = if (item.author.isEmpty()) View.GONE else View.VISIBLE\n            tvGroupS.text = getGroupName(item.group)\n            checkbox.isChecked = selectedBooks.contains(item)\n            if (item.isLocal) {\n                tvOrigin.setText(R.string.local_book)\n            } else {\n                tvOrigin.text = item.originName\n            }\n        }\n    }\n\n    override fun registerListener(holder: ItemViewHolder, binding: ItemArrangeBookBinding) {\n        binding.apply {\n            checkbox.setOnUserCheckedChangeListener { isChecked ->\n                getItem(holder.layoutPosition)?.let {\n                    if (isChecked) {\n                        selectedBooks.add(it)\n                    } else {\n                        selectedBooks.remove(it)\n                    }\n                    callBack.upSelectCount()\n                }\n            }\n            root.setOnClickListener {\n                getItem(holder.layoutPosition)?.let {\n                    checkbox.isChecked = !checkbox.isChecked\n                    if (checkbox.isChecked) {\n                        selectedBooks.add(it)\n                    } else {\n                        selectedBooks.remove(it)\n                    }\n                    callBack.upSelectCount()\n                }\n            }\n            if (AppConfig.openBookInfoByClickTitle) {\n                tvName.setOnClickListener {\n                    getItem(holder.layoutPosition)?.let {\n                        callBack.openBook(it)\n                    }\n                }\n            }\n            tvDelete.setOnClickListener {\n                getItem(holder.layoutPosition)?.let {\n                    callBack.deleteBook(it)\n                }\n            }\n            tvGroup.setOnClickListener {\n                getItem(holder.layoutPosition)?.let {\n                    actionItem = it\n                    callBack.selectGroup(groupRequestCode, it.group)\n                }\n            }\n        }\n    }\n\n    @SuppressLint(\"NotifyDataSetChanged\")\n    fun selectAll(selectAll: Boolean) {\n        if (selectAll) {\n            getItems().forEach {\n                selectedBooks.add(it)\n            }\n        } else {\n            selectedBooks.clear()\n        }\n        notifyDataSetChanged()\n        callBack.upSelectCount()\n    }\n\n    @SuppressLint(\"NotifyDataSetChanged\")\n    fun revertSelection() {\n        getItems().forEach {\n            if (selectedBooks.contains(it)) {\n                selectedBooks.remove(it)\n            } else {\n                selectedBooks.add(it)\n            }\n        }\n        notifyDataSetChanged()\n        callBack.upSelectCount()\n    }\n\n    fun checkSelectedInterval() {\n        val selectedPosition = linkedSetOf<Int>()\n        getItems().forEachIndexed { index, it ->\n            if (selectedBooks.contains(it)) {\n                selectedPosition.add(index)\n            }\n        }\n        val minPosition = Collections.min(selectedPosition)\n        val maxPosition = Collections.max(selectedPosition)\n        val itemCount = maxPosition - minPosition + 1\n        for (i in minPosition..maxPosition) {\n            getItem(i)?.let {\n                selectedBooks.add(it)\n            }\n        }\n        notifyItemRangeChanged(minPosition, itemCount, bundleOf(Pair(\"selected\", null)))\n        callBack.upSelectCount()\n    }\n\n    private fun getGroupList(groupId: Long): List<String> {\n        val groupNames = arrayListOf<String>()\n        callBack.groupList.forEach {\n            if (it.groupId > 0 && it.groupId and groupId > 0) {\n                groupNames.add(it.groupName)\n            }\n        }\n        return groupNames\n    }\n\n    private fun getGroupName(groupId: Long): String {\n        val groupNames = getGroupList(groupId)\n        if (groupNames.isEmpty()) {\n            return \"\"\n        }\n        return groupNames.joinToString(\",\")\n    }\n\n    private var isMoved = false\n\n    override fun swap(srcPosition: Int, targetPosition: Int): Boolean {\n        val srcItem = getItem(srcPosition)\n        val targetItem = getItem(targetPosition)\n        if (srcItem != null && targetItem != null) {\n            if (srcItem.order == targetItem.order) {\n                for ((index, item) in getItems().withIndex()) {\n                    item.order = index + 1\n                }\n            } else {\n                val pos = srcItem.order\n                srcItem.order = targetItem.order\n                targetItem.order = pos\n            }\n        }\n        swapItem(srcPosition, targetPosition)\n        isMoved = true\n        return true\n    }\n\n    override fun onClearView(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder) {\n        if (isMoved) {\n            callBack.updateBook(*getItems().toTypedArray())\n        }\n        isMoved = false\n    }\n\n    val dragSelectCallback: DragSelectTouchHelper.Callback =\n        object : DragSelectTouchHelper.AdvanceCallback<Book>(Mode.ToggleAndReverse) {\n            override fun currentSelectedId(): MutableSet<Book> {\n                return selectedBooks\n            }\n\n            override fun getItemId(position: Int): Book {\n                return getItem(position)!!\n            }\n\n            override fun updateSelectState(position: Int, isSelected: Boolean): Boolean {\n                getItem(position)?.let {\n                    if (isSelected) {\n                        selectedBooks.add(it)\n                    } else {\n                        selectedBooks.remove(it)\n                    }\n                    notifyItemChanged(position, bundleOf(Pair(\"selected\", null)))\n                    callBack.upSelectCount()\n                    return true\n                }\n                return false\n            }\n        }\n\n    interface CallBack {\n        val groupList: List<BookGroup>\n\n        fun upSelectCount()\n\n        fun updateBook(vararg book: Book)\n\n        fun deleteBook(book: Book)\n\n        fun selectGroup(requestCode: Int, groupId: Long)\n\n        fun openBook(book: Book)\n    }\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/book/manage/BookshelfManageActivity.kt",
    "content": "package io.legado.app.ui.book.manage\n\nimport android.annotation.SuppressLint\nimport android.os.Bundle\nimport android.view.Menu\nimport android.view.MenuItem\nimport android.widget.CheckBox\nimport android.widget.LinearLayout\nimport androidx.activity.viewModels\nimport androidx.appcompat.widget.PopupMenu\nimport androidx.appcompat.widget.SearchView\nimport androidx.lifecycle.lifecycleScope\nimport androidx.recyclerview.widget.ItemTouchHelper\nimport androidx.recyclerview.widget.LinearLayoutManager\nimport io.legado.app.R\nimport io.legado.app.base.VMBaseActivity\nimport io.legado.app.constant.AppLog\nimport io.legado.app.data.appDb\nimport io.legado.app.data.entities.Book\nimport io.legado.app.data.entities.BookGroup\nimport io.legado.app.data.entities.BookSource\nimport io.legado.app.databinding.ActivityArrangeBookBinding\nimport io.legado.app.databinding.DialogEditTextBinding\nimport io.legado.app.help.DirectLinkUpload\nimport io.legado.app.help.book.contains\nimport io.legado.app.help.book.isLocal\nimport io.legado.app.help.config.AppConfig\nimport io.legado.app.help.config.LocalConfig\nimport io.legado.app.lib.dialogs.alert\nimport io.legado.app.lib.theme.primaryColor\nimport io.legado.app.lib.theme.primaryTextColor\nimport io.legado.app.ui.book.group.GroupManageDialog\nimport io.legado.app.ui.book.group.GroupSelectDialog\nimport io.legado.app.ui.book.info.BookInfoActivity\nimport io.legado.app.ui.file.HandleFileContract\nimport io.legado.app.ui.widget.SelectActionBar\nimport io.legado.app.ui.widget.dialog.WaitDialog\nimport io.legado.app.ui.widget.recycler.DragSelectTouchHelper\nimport io.legado.app.ui.widget.recycler.ItemTouchCallback\nimport io.legado.app.ui.widget.recycler.VerticalDivider\nimport io.legado.app.utils.applyTint\nimport io.legado.app.utils.cnCompare\nimport io.legado.app.utils.dpToPx\nimport io.legado.app.utils.isAbsUrl\nimport io.legado.app.utils.sendToClip\nimport io.legado.app.utils.setEdgeEffectColor\nimport io.legado.app.utils.showDialogFragment\nimport io.legado.app.utils.startActivity\nimport io.legado.app.utils.viewbindingdelegate.viewBinding\nimport kotlinx.coroutines.Dispatchers.IO\nimport kotlinx.coroutines.Job\nimport kotlinx.coroutines.flow.catch\nimport kotlinx.coroutines.flow.conflate\nimport kotlinx.coroutines.flow.flowOn\nimport kotlinx.coroutines.flow.map\nimport kotlinx.coroutines.launch\nimport kotlinx.coroutines.withContext\nimport kotlin.math.max\n\n/**\n * 书架管理\n */\nclass BookshelfManageActivity :\n    VMBaseActivity<ActivityArrangeBookBinding, BookshelfManageViewModel>(),\n    PopupMenu.OnMenuItemClickListener,\n    SelectActionBar.CallBack,\n    BookAdapter.CallBack,\n    SourcePickerDialog.Callback,\n    GroupSelectDialog.CallBack {\n\n    override val binding by viewBinding(ActivityArrangeBookBinding::inflate)\n    override val viewModel by viewModels<BookshelfManageViewModel>()\n    override val groupList: ArrayList<BookGroup> = arrayListOf()\n    private val groupRequestCode = 22\n    private val addToGroupRequestCode = 34\n    private val adapter by lazy { BookAdapter(this, this) }\n    private val itemTouchCallback by lazy { ItemTouchCallback(adapter) }\n    private var booksFlowJob: Job? = null\n    private var menu: Menu? = null\n    private val searchView: SearchView by lazy {\n        binding.titleBar.findViewById(R.id.search_view)\n    }\n    private var books: List<Book>? = null\n    private val waitDialog by lazy { WaitDialog(this) }\n    private val exportDir = registerForActivityResult(HandleFileContract()) {\n        it.uri?.let { uri ->\n            alert(R.string.export_success) {\n                if (uri.toString().isAbsUrl()) {\n                    setMessage(DirectLinkUpload.getSummary())\n                }\n                val alertBinding = DialogEditTextBinding.inflate(layoutInflater).apply {\n                    editView.hint = getString(R.string.path)\n                    editView.setText(uri.toString())\n                }\n                customView { alertBinding.root }\n                okButton {\n                    sendToClip(uri.toString())\n                }\n            }\n        }\n    }\n\n    override fun onActivityCreated(savedInstanceState: Bundle?) {\n        viewModel.groupId = intent.getLongExtra(\"groupId\", -1)\n        lifecycleScope.launch {\n            viewModel.groupName = withContext(IO) {\n                appDb.bookGroupDao.getByID(viewModel.groupId)?.groupName\n                    ?: getString(R.string.no_group)\n            }\n            upTitle()\n        }\n        initSearchView()\n        initRecyclerView()\n        initOtherView()\n        initGroupData()\n        upBookDataByGroupId()\n    }\n\n    override fun observeLiveBus() {\n        viewModel.batchChangeSourceState.observe(this) {\n            if (it) {\n                waitDialog.setText(R.string.change_source_batch)\n                waitDialog.show()\n            } else {\n                waitDialog.dismiss()\n            }\n        }\n        viewModel.batchChangeSourceProcessLiveData.observe(this) {\n            waitDialog.setText(it)\n        }\n    }\n\n    override fun onCompatCreateOptionsMenu(menu: Menu): Boolean {\n        menuInflater.inflate(R.menu.bookshelf_manage, menu)\n        return super.onCompatCreateOptionsMenu(menu)\n    }\n\n    override fun onPrepareOptionsMenu(menu: Menu): Boolean {\n        this.menu = menu\n        menu.findItem(R.id.menu_open_book_info_by_click_title)?.isChecked =\n            AppConfig.openBookInfoByClickTitle\n        upMenu()\n        return super.onPrepareOptionsMenu(menu)\n    }\n\n    override fun selectAll(selectAll: Boolean) {\n        adapter.selectAll(selectAll)\n    }\n\n    override fun revertSelection() {\n        adapter.revertSelection()\n    }\n\n    override fun onClickSelectBarMainAction() {\n        selectGroup(groupRequestCode, 0)\n    }\n\n    private fun upTitle() {\n        searchView.queryHint = getString(R.string.screen) + \" • \" + viewModel.groupName\n    }\n\n    private fun initSearchView() {\n        searchView.applyTint(primaryTextColor)\n        searchView.isSubmitButtonEnabled = true\n        searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener {\n            override fun onQueryTextSubmit(query: String?): Boolean {\n                return false\n            }\n\n            override fun onQueryTextChange(newText: String?): Boolean {\n                upBookData()\n                return false\n            }\n\n        })\n    }\n\n    private fun initRecyclerView() {\n        binding.recyclerView.setEdgeEffectColor(primaryColor)\n        binding.recyclerView.layoutManager = LinearLayoutManager(this)\n        binding.recyclerView.addItemDecoration(VerticalDivider(this))\n        binding.recyclerView.adapter = adapter\n        itemTouchCallback.isCanDrag = AppConfig.bookshelfSort == 3\n        val dragSelectTouchHelper: DragSelectTouchHelper =\n            DragSelectTouchHelper(adapter.dragSelectCallback).setSlideArea(16, 50)\n        dragSelectTouchHelper.attachToRecyclerView(binding.recyclerView)\n        // When this page is opened, it is in selection mode\n        dragSelectTouchHelper.activeSlideSelect()\n        // Note: need judge selection first, so add ItemTouchHelper after it.\n        ItemTouchHelper(itemTouchCallback).attachToRecyclerView(binding.recyclerView)\n    }\n\n    private fun initOtherView() {\n        binding.selectActionBar.setMainActionText(R.string.move_to_group)\n        binding.selectActionBar.inflateMenu(R.menu.bookshelf_menage_sel)\n        binding.selectActionBar.setOnMenuItemClickListener(this)\n        binding.selectActionBar.setCallBack(this)\n        waitDialog.setOnCancelListener {\n            viewModel.batchChangeSourceCoroutine?.cancel()\n        }\n    }\n\n    @SuppressLint(\"NotifyDataSetChanged\")\n    private fun initGroupData() {\n        lifecycleScope.launch {\n            appDb.bookGroupDao.flowAll().catch {\n                AppLog.put(\"书架管理界面获取分组数据失败\\n${it.localizedMessage}\", it)\n            }.flowOn(IO).conflate().collect {\n                groupList.clear()\n                groupList.addAll(it)\n                adapter.notifyDataSetChanged()\n                upMenu()\n            }\n        }\n    }\n\n    private fun upBookDataByGroupId() {\n        booksFlowJob?.cancel()\n        booksFlowJob = lifecycleScope.launch {\n            val bookSort = AppConfig.getBookSortByGroupId(viewModel.groupId)\n            appDb.bookDao.flowByGroup(viewModel.groupId).map { list ->\n                when (bookSort) {\n                    1 -> list.sortedByDescending {\n                        it.latestChapterTime\n                    }\n\n                    2 -> list.sortedWith { o1, o2 ->\n                        o1.name.cnCompare(o2.name)\n                    }\n\n                    3 -> list.sortedBy {\n                        it.order\n                    }\n\n                    4 -> list.sortedByDescending {\n                        max(it.latestChapterTime, it.durChapterTime)\n                    }\n\n                    else -> list.sortedByDescending {\n                        it.durChapterTime\n                    }\n                }\n            }.catch {\n                AppLog.put(\"书架管理界面获取书籍列表失败\\n${it.localizedMessage}\", it)\n            }.flowOn(IO)\n                .conflate().collect {\n                    books = it\n                    upBookData()\n                    itemTouchCallback.isCanDrag = bookSort == 3\n                }\n        }\n    }\n\n    private fun upBookData() {\n        books?.let { books ->\n            val searchKey = searchView.query\n            if (searchKey.isNullOrEmpty()) {\n                adapter.setItems(books)\n            } else {\n                books.filter {\n                    it.contains(searchKey.toString())\n                }.let {\n                    adapter.setItems(it)\n                }\n            }\n        }\n    }\n\n    override fun onCompatOptionsItemSelected(item: MenuItem): Boolean {\n        when (item.itemId) {\n            R.id.menu_group_manage -> showDialogFragment<GroupManageDialog>()\n            R.id.menu_open_book_info_by_click_title -> {\n                AppConfig.openBookInfoByClickTitle = !item.isChecked\n                adapter.notifyItemRangeChanged(0, adapter.itemCount)\n            }\n\n            R.id.menu_export_all_use_book_source -> viewModel.saveAllUseBookSourceToFile { file ->\n                exportDir.launch {\n                    mode = HandleFileContract.EXPORT\n                    fileData = HandleFileContract.FileData(\n                        \"bookSource.json\",\n                        file,\n                        \"application/json\"\n                    )\n                }\n            }\n\n            else -> if (item.groupId == R.id.menu_group) {\n                viewModel.groupName = item.title.toString()\n                upTitle()\n                viewModel.groupId =\n                    appDb.bookGroupDao.getByName(item.title.toString())?.groupId ?: 0\n                upBookDataByGroupId()\n            }\n        }\n        return super.onCompatOptionsItemSelected(item)\n    }\n\n    override fun onMenuItemClick(item: MenuItem?): Boolean {\n        when (item?.itemId) {\n            R.id.menu_del_selection -> alertDelSelection()\n            R.id.menu_update_enable ->\n                viewModel.upCanUpdate(adapter.selection, true)\n\n            R.id.menu_update_disable ->\n                viewModel.upCanUpdate(adapter.selection, false)\n\n            R.id.menu_add_to_group -> selectGroup(addToGroupRequestCode, 0)\n            R.id.menu_change_source -> showDialogFragment<SourcePickerDialog>()\n            R.id.menu_clear_cache -> viewModel.clearCache(adapter.selection)\n            R.id.menu_check_selected_interval -> adapter.checkSelectedInterval()\n        }\n        return false\n    }\n\n    private fun upMenu() {\n        menu?.findItem(R.id.menu_book_group)?.subMenu?.let { subMenu ->\n            subMenu.removeGroup(R.id.menu_group)\n            groupList.forEach { bookGroup ->\n                subMenu.add(R.id.menu_group, bookGroup.order, Menu.NONE, bookGroup.groupName)\n            }\n        }\n    }\n\n    private fun alertDelSelection() {\n        alert(titleResource = R.string.draw, messageResource = R.string.sure_del) {\n            val checkBox = CheckBox(this@BookshelfManageActivity).apply {\n                setText(R.string.delete_book_file)\n                isChecked = LocalConfig.deleteBookOriginal\n            }\n            val view = LinearLayout(this@BookshelfManageActivity).apply {\n                setPadding(16.dpToPx(), 0, 16.dpToPx(), 0)\n                addView(checkBox)\n            }\n            customView { view }\n            okButton {\n                LocalConfig.deleteBookOriginal = checkBox.isChecked\n                viewModel.deleteBook(adapter.selection, checkBox.isChecked)\n            }\n            noButton()\n        }\n    }\n\n    override fun selectGroup(requestCode: Int, groupId: Long) {\n        showDialogFragment(\n            GroupSelectDialog(groupId, requestCode)\n        )\n    }\n\n    override fun upGroup(requestCode: Int, groupId: Long) {\n        when (requestCode) {\n            groupRequestCode -> adapter.selection.let { books ->\n                val array = Array(books.size) {\n                    books[it].copy(group = groupId)\n                }\n                viewModel.updateBook(*array)\n            }\n\n            adapter.groupRequestCode -> {\n                adapter.actionItem?.let {\n                    viewModel.updateBook(it.copy(group = groupId))\n                }\n            }\n\n            addToGroupRequestCode -> adapter.selection.let { books ->\n                val array = Array(books.size) { index ->\n                    val book = books[index]\n                    book.copy(group = book.group or groupId)\n                }\n                viewModel.updateBook(*array)\n            }\n        }\n    }\n\n    override fun upSelectCount() {\n        binding.selectActionBar.upCountView(adapter.selection.size, adapter.getItems().size)\n    }\n\n    override fun updateBook(vararg book: Book) {\n        viewModel.updateBook(*book)\n    }\n\n    override fun deleteBook(book: Book) {\n        alert(titleResource = R.string.draw, messageResource = R.string.sure_del) {\n            var checkBox: CheckBox? = null\n            if (book.isLocal) {\n                checkBox = CheckBox(this@BookshelfManageActivity).apply {\n                    setText(R.string.delete_book_file)\n                    isChecked = LocalConfig.deleteBookOriginal\n                }\n                val view = LinearLayout(this@BookshelfManageActivity).apply {\n                    setPadding(16.dpToPx(), 0, 16.dpToPx(), 0)\n                    addView(checkBox)\n                }\n                customView { view }\n            }\n            okButton {\n                if (checkBox != null) {\n                    LocalConfig.deleteBookOriginal = checkBox.isChecked\n                }\n                viewModel.deleteBook(listOf(book), LocalConfig.deleteBookOriginal)\n            }\n        }\n    }\n\n    override fun openBook(book: Book) {\n        startActivity<BookInfoActivity> {\n            putExtra(\"name\", book.name)\n            putExtra(\"author\", book.author)\n        }\n    }\n\n    override fun sourceOnClick(source: BookSource) {\n        viewModel.changeSource(adapter.selection, source)\n        viewModel.batchChangeSourceState.value = true\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/book/manage/BookshelfManageViewModel.kt",
    "content": "package io.legado.app.ui.book.manage\n\nimport android.app.Application\nimport androidx.lifecycle.MutableLiveData\nimport io.legado.app.R\nimport io.legado.app.base.BaseViewModel\nimport io.legado.app.constant.AppLog\nimport io.legado.app.constant.BookType\nimport io.legado.app.data.appDb\nimport io.legado.app.data.entities.Book\nimport io.legado.app.data.entities.BookSource\nimport io.legado.app.help.book.BookHelp\nimport io.legado.app.help.book.isLocal\nimport io.legado.app.help.book.removeType\nimport io.legado.app.help.config.AppConfig\nimport io.legado.app.help.coroutine.Coroutine\nimport io.legado.app.model.localBook.LocalBook\nimport io.legado.app.model.webBook.WebBook\nimport io.legado.app.utils.FileUtils\nimport io.legado.app.utils.GSON\nimport io.legado.app.utils.stackTraceStr\nimport io.legado.app.utils.toastOnUi\nimport io.legado.app.utils.writeToOutputStream\nimport kotlinx.coroutines.delay\nimport kotlinx.coroutines.ensureActive\nimport java.io.File\n\n\nclass BookshelfManageViewModel(application: Application) : BaseViewModel(application) {\n    var groupId: Long = -1L\n    var groupName: String? = null\n    val batchChangeSourceState = MutableLiveData<Boolean>()\n    val batchChangeSourceProcessLiveData = MutableLiveData<String>()\n    var batchChangeSourceCoroutine: Coroutine<Unit>? = null\n\n    fun upCanUpdate(books: List<Book>, canUpdate: Boolean) {\n        execute {\n            val array = Array(books.size) {\n                books[it].copy(canUpdate = canUpdate).apply {\n                    if (!canUpdate) {\n                        removeType(BookType.updateError)\n                    }\n                }\n            }\n            appDb.bookDao.update(*array)\n        }\n    }\n\n    fun updateBook(vararg book: Book) {\n        execute {\n            appDb.bookDao.update(*book)\n        }\n    }\n\n    fun deleteBook(books: List<Book>, deleteOriginal: Boolean = false) {\n        execute {\n            appDb.bookDao.delete(*books.toTypedArray())\n            books.forEach {\n                if (it.isLocal) {\n                    LocalBook.deleteBook(it, deleteOriginal)\n                }\n            }\n        }\n    }\n\n    fun saveAllUseBookSourceToFile(success: (file: File) -> Unit) {\n        execute {\n            val path = \"${context.filesDir}/shareBookSource.json\"\n            FileUtils.delete(path)\n            val file = FileUtils.createFileWithReplace(path)\n            val sources = appDb.bookDao.getAllUseBookSource()\n            file.outputStream().buffered().use {\n                GSON.writeToOutputStream(it, sources)\n            }\n            file\n        }.onSuccess {\n            success.invoke(it)\n        }.onError {\n            context.toastOnUi(it.stackTraceStr)\n        }\n    }\n\n    fun changeSource(books: List<Book>, source: BookSource) {\n        batchChangeSourceCoroutine?.cancel()\n        batchChangeSourceCoroutine = execute {\n            val changeSourceDelay = AppConfig.batchChangeSourceDelay * 1000L\n            books.forEachIndexed { index, book ->\n                batchChangeSourceProcessLiveData.postValue(\"${index + 1} / ${books.size}\")\n                if (book.isLocal) return@forEachIndexed\n                if (book.origin == source.bookSourceUrl) return@forEachIndexed\n                val newBook = WebBook.preciseSearchAwait(source, book.name, book.author)\n                    .onFailure {\n                        AppLog.put(\"搜索书籍出错\\n${it.localizedMessage}\", it, true)\n                    }.getOrNull() ?: return@forEachIndexed\n                kotlin.runCatching {\n                    if (newBook.tocUrl.isEmpty()) {\n                        WebBook.getBookInfoAwait(source, newBook)\n                    }\n                }.onFailure {\n                    AppLog.put(\"获取书籍详情出错\\n${it.localizedMessage}\", it, true)\n                    return@forEachIndexed\n                }\n                WebBook.getChapterListAwait(source, newBook)\n                    .onFailure {\n                        AppLog.put(\"获取目录出错\\n${it.localizedMessage}\", it, true)\n                    }.getOrNull()?.let { toc ->\n                        book.migrateTo(newBook, toc)\n                        book.removeType(BookType.updateError)\n                        appDb.bookDao.insert(newBook)\n                        appDb.bookChapterDao.insert(*toc.toTypedArray())\n                    }\n                delay(changeSourceDelay)\n            }\n        }.onStart {\n            batchChangeSourceState.postValue(true)\n        }.onFinally {\n            batchChangeSourceState.postValue(false)\n        }\n    }\n\n    fun clearCache(books: List<Book>) {\n        execute {\n            books.forEach {\n                BookHelp.clearCache(it)\n            }\n        }.onSuccess {\n            context.toastOnUi(R.string.clear_cache_success)\n        }\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/book/manage/SourcePickerDialog.kt",
    "content": "package io.legado.app.ui.book.manage\n\nimport android.content.Context\nimport android.os.Bundle\nimport android.view.MenuItem\nimport android.view.View\nimport android.view.ViewGroup\nimport androidx.appcompat.widget.SearchView\nimport androidx.appcompat.widget.Toolbar\nimport androidx.core.view.setPadding\nimport androidx.lifecycle.lifecycleScope\nimport androidx.recyclerview.widget.LinearLayoutManager\nimport io.legado.app.R\nimport io.legado.app.base.BaseDialogFragment\nimport io.legado.app.base.adapter.ItemViewHolder\nimport io.legado.app.base.adapter.RecyclerAdapter\nimport io.legado.app.constant.AppLog\nimport io.legado.app.data.appDb\nimport io.legado.app.data.entities.BookSource\nimport io.legado.app.data.entities.BookSourcePart\nimport io.legado.app.databinding.DialogSourcePickerBinding\nimport io.legado.app.databinding.Item1lineTextBinding\nimport io.legado.app.help.config.AppConfig\nimport io.legado.app.lib.theme.primaryColor\nimport io.legado.app.lib.theme.primaryTextColor\nimport io.legado.app.ui.widget.number.NumberPickerDialog\nimport io.legado.app.utils.applyTint\nimport io.legado.app.utils.dpToPx\nimport io.legado.app.utils.setLayout\nimport io.legado.app.utils.viewbindingdelegate.viewBinding\nimport kotlinx.coroutines.Dispatchers.IO\nimport kotlinx.coroutines.Job\nimport kotlinx.coroutines.flow.catch\nimport kotlinx.coroutines.flow.flowOn\nimport kotlinx.coroutines.launch\nimport splitties.views.onClick\n\n/**\n * 书源选择\n */\nclass SourcePickerDialog : BaseDialogFragment(R.layout.dialog_source_picker),\n    Toolbar.OnMenuItemClickListener {\n\n    private val binding by viewBinding(DialogSourcePickerBinding::bind)\n    private val searchView: SearchView by lazy {\n        binding.toolBar.findViewById(R.id.search_view)\n    }\n    private val toolBar: Toolbar by lazy {\n        binding.toolBar.toolbar\n    }\n    private val adapter by lazy {\n        SourceAdapter(requireContext())\n    }\n    private var sourceFlowJob: Job? = null\n\n    override fun onStart() {\n        super.onStart()\n        setLayout(1f, ViewGroup.LayoutParams.MATCH_PARENT)\n    }\n\n    override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) {\n        initView()\n        initData()\n        initMenu()\n    }\n\n    private fun initView() {\n        binding.toolBar.setBackgroundColor(primaryColor)\n        binding.toolBar.title = \"选择书源\"\n        binding.recyclerView.layoutManager = LinearLayoutManager(requireContext())\n        binding.recyclerView.adapter = adapter\n        searchView.applyTint(primaryTextColor)\n        searchView.isSubmitButtonEnabled = true\n        searchView.queryHint = getString(R.string.search_book_source)\n        searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener {\n            override fun onQueryTextSubmit(query: String?): Boolean {\n                return false\n            }\n\n            override fun onQueryTextChange(newText: String?): Boolean {\n                initData(newText)\n                return false\n            }\n        })\n    }\n\n    private fun initData(searchKey: String? = null) {\n        sourceFlowJob?.cancel()\n        sourceFlowJob = lifecycleScope.launch {\n            when {\n                searchKey.isNullOrEmpty() -> appDb.bookSourceDao.flowEnabled()\n                else -> appDb.bookSourceDao.flowSearchEnabled(searchKey)\n            }.catch {\n                AppLog.put(\"书源选择界面获取书源数据失败\\n${it.localizedMessage}\", it)\n            }.flowOn(IO).collect {\n                adapter.setItems(it)\n            }\n        }\n    }\n\n    private fun initMenu() {\n        toolBar.setOnMenuItemClickListener(this)\n        toolBar.inflateMenu(R.menu.source_picker)\n        toolBar.menu.applyTint(requireContext())\n    }\n\n    override fun onMenuItemClick(item: MenuItem?): Boolean {\n        when (item?.itemId) {\n            R.id.menu_change_source_delay -> NumberPickerDialog(requireContext())\n                .setTitle(getString(R.string.change_source_delay))\n                .setMaxValue(9999)\n                .setMinValue(0)\n                .setValue(AppConfig.batchChangeSourceDelay)\n                .show {\n                    AppConfig.batchChangeSourceDelay = it\n                }\n        }\n        return true\n    }\n\n    inner class SourceAdapter(context: Context) :\n        RecyclerAdapter<BookSourcePart, Item1lineTextBinding>(context) {\n\n        override fun getViewBinding(parent: ViewGroup): Item1lineTextBinding {\n            return Item1lineTextBinding.inflate(inflater, parent, false).apply {\n                root.setPadding(16.dpToPx())\n            }\n        }\n\n        override fun convert(\n            holder: ItemViewHolder,\n            binding: Item1lineTextBinding,\n            item: BookSourcePart,\n            payloads: MutableList<Any>\n        ) {\n            binding.textView.text = item.getDisPlayNameGroup()\n        }\n\n        override fun registerListener(holder: ItemViewHolder, binding: Item1lineTextBinding) {\n            binding.root.onClick {\n                getItemByLayoutPosition(holder.layoutPosition)?.let {\n                    it.getBookSource()?.let { source ->\n                        callback?.sourceOnClick(source)\n                    }\n                    dismissAllowingStateLoss()\n                }\n            }\n        }\n\n    }\n\n    private val callback: Callback?\n        get() {\n            return (parentFragment as? Callback) ?: activity as? Callback\n        }\n\n    interface Callback {\n        fun sourceOnClick(source: BookSource)\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/book/manga/ReadMangaActivity.kt",
    "content": "package io.legado.app.ui.book.manga\n\nimport android.annotation.SuppressLint\nimport android.content.Intent\nimport android.os.Build\nimport android.os.Bundle\nimport android.view.KeyEvent\nimport android.view.Menu\nimport android.view.MenuItem\nimport android.view.WindowManager\nimport android.view.animation.LinearInterpolator\nimport androidx.activity.viewModels\nimport androidx.appcompat.app.AlertDialog\nimport androidx.core.view.isGone\nimport androidx.core.view.isVisible\nimport androidx.lifecycle.lifecycleScope\nimport androidx.recyclerview.widget.LinearLayoutManager\nimport androidx.recyclerview.widget.PagerSnapHelper\nimport com.bumptech.glide.Glide\nimport com.bumptech.glide.integration.recyclerview.RecyclerViewPreloader\nimport com.bumptech.glide.request.target.Target.SIZE_ORIGINAL\nimport com.bumptech.glide.util.FixedPreloadSizeProvider\nimport io.legado.app.BuildConfig\nimport io.legado.app.R\nimport io.legado.app.base.VMBaseActivity\nimport io.legado.app.constant.BookType\nimport io.legado.app.constant.EventBus\nimport io.legado.app.data.entities.Book\nimport io.legado.app.data.entities.BookChapter\nimport io.legado.app.data.entities.BookProgress\nimport io.legado.app.data.entities.BookSource\nimport io.legado.app.databinding.ActivityMangaBinding\nimport io.legado.app.databinding.ViewLoadMoreBinding\nimport io.legado.app.help.book.isImage\nimport io.legado.app.help.book.removeType\nimport io.legado.app.help.config.AppConfig\nimport io.legado.app.help.storage.Backup\nimport io.legado.app.lib.dialogs.alert\nimport io.legado.app.model.ReadManga\nimport io.legado.app.receiver.NetworkChangedListener\nimport io.legado.app.ui.book.changesource.ChangeBookSourceDialog\nimport io.legado.app.ui.book.info.BookInfoActivity\nimport io.legado.app.ui.book.manga.config.MangaColorFilterConfig\nimport io.legado.app.ui.book.manga.config.MangaColorFilterDialog\nimport io.legado.app.ui.book.manga.config.MangaEpaperDialog\nimport io.legado.app.ui.book.manga.config.MangaFooterConfig\nimport io.legado.app.ui.book.manga.config.MangaFooterSettingDialog\nimport io.legado.app.ui.book.manga.entities.BaseMangaPage\nimport io.legado.app.ui.book.manga.entities.MangaPage\nimport io.legado.app.ui.book.manga.recyclerview.MangaAdapter\nimport io.legado.app.ui.book.manga.recyclerview.MangaLayoutManager\nimport io.legado.app.ui.book.manga.recyclerview.ScrollTimer\nimport io.legado.app.ui.book.read.MangaMenu\nimport io.legado.app.ui.book.read.ReadBookActivity.Companion.RESULT_DELETED\nimport io.legado.app.ui.book.toc.TocActivityResult\nimport io.legado.app.ui.widget.number.NumberPickerDialog\nimport io.legado.app.ui.widget.recycler.LoadMoreView\nimport io.legado.app.utils.GSON\nimport io.legado.app.utils.NetworkUtils\nimport io.legado.app.utils.StartActivityContract\nimport io.legado.app.utils.canScroll\nimport io.legado.app.utils.fastBinarySearch\nimport io.legado.app.utils.findCenterViewPosition\nimport io.legado.app.utils.fromJsonObject\nimport io.legado.app.utils.getCompatColor\nimport io.legado.app.utils.gone\nimport io.legado.app.utils.observeEvent\nimport io.legado.app.utils.showDialogFragment\nimport io.legado.app.utils.toastOnUi\nimport io.legado.app.utils.toggleSystemBar\nimport io.legado.app.utils.viewbindingdelegate.viewBinding\nimport io.legado.app.utils.visible\nimport kotlinx.coroutines.Dispatchers.IO\nimport kotlinx.coroutines.launch\nimport kotlinx.coroutines.withContext\nimport java.text.DecimalFormat\nimport kotlin.math.ceil\n\nclass ReadMangaActivity : VMBaseActivity<ActivityMangaBinding, ReadMangaViewModel>(),\n    ReadManga.Callback, ChangeBookSourceDialog.CallBack, MangaMenu.CallBack,\n    MangaColorFilterDialog.Callback, ScrollTimer.ScrollCallback, MangaEpaperDialog.Callback {\n\n    private val mLayoutManager by lazy {\n        MangaLayoutManager(this)\n    }\n    private val mAdapter: MangaAdapter by lazy {\n        MangaAdapter(this)\n    }\n\n    private val mSizeProvider by lazy {\n        FixedPreloadSizeProvider<Any>(resources.displayMetrics.widthPixels, SIZE_ORIGINAL)\n    }\n\n    private val mPagerSnapHelper: PagerSnapHelper by lazy {\n        PagerSnapHelper()\n    }\n\n    private lateinit var mMangaFooterConfig: MangaFooterConfig\n    private val mLabelBuilder by lazy { StringBuilder() }\n\n    private var mMenu: Menu? = null\n\n    private var mRecyclerViewPreloader: RecyclerViewPreloader<Any>? = null\n\n    private val networkChangedListener by lazy {\n        NetworkChangedListener(this)\n    }\n\n    private var justInitData: Boolean = false\n    private var syncDialog: AlertDialog? = null\n    private val mScrollTimer by lazy {\n        ScrollTimer(this, binding.recyclerView, lifecycleScope).apply {\n            setSpeed(AppConfig.mangaAutoPageSpeed)\n        }\n    }\n    private var enableAutoScrollPage = false\n    private var enableAutoScroll = false\n    private val mLinearInterpolator by lazy {\n        LinearInterpolator()\n    }\n\n    private val loadMoreView by lazy {\n        LoadMoreView(this).apply {\n            setBackgroundColor(getCompatColor(R.color.book_ant_10))\n            setLoadingColor(R.color.white)\n            setLoadingTextColor(R.color.white)\n        }\n    }\n\n    //打开目录返回选择章节返回结果\n    private val tocActivity = registerForActivityResult(TocActivityResult()) {\n        it?.let {\n            viewModel.openChapter(it.first, it.second)\n        }\n    }\n    private val bookInfoActivity =\n        registerForActivityResult(StartActivityContract(BookInfoActivity::class.java)) {\n            if (it.resultCode == RESULT_OK) {\n                setResult(RESULT_DELETED)\n                super.finish()\n            } else {\n                ReadManga.loadOrUpContent()\n            }\n        }\n    override val binding by viewBinding(ActivityMangaBinding::inflate)\n    override val viewModel by viewModels<ReadMangaViewModel>()\n    private val loadingViewVisible get() = binding.flLoading.isVisible\n    private val df by lazy {\n        DecimalFormat(\"0.0%\")\n    }\n\n    override fun onCreate(savedInstanceState: Bundle?) {\n        upLayoutInDisplayCutoutMode()\n        super.onCreate(savedInstanceState)\n    }\n\n    override fun onActivityCreated(savedInstanceState: Bundle?) {\n        ReadManga.register(this)\n        upSystemUiVisibility(false)\n        initRecyclerView()\n        binding.tvRetry.setOnClickListener {\n            binding.llLoading.isVisible = true\n            binding.llRetry.isGone = true\n            ReadManga.loadOrUpContent()\n        }\n        binding.pbLoading.isVisible = !AppConfig.isEInkMode\n        mAdapter.addFooterView {\n            ViewLoadMoreBinding.bind(loadMoreView)\n        }\n        loadMoreView.setOnClickListener {\n            if (!loadMoreView.isLoading && ReadManga.hasNextChapter) {\n                loadMoreView.startLoad()\n                ReadManga.loadOrUpContent()\n            }\n        }\n        loadMoreView.gone()\n        mMangaFooterConfig =\n            GSON.fromJsonObject<MangaFooterConfig>(AppConfig.mangaFooterConfig).getOrNull()\n                ?: MangaFooterConfig()\n    }\n\n    override fun observeLiveBus() {\n        observeEvent<MangaFooterConfig>(EventBus.UP_MANGA_CONFIG) {\n            mMangaFooterConfig = it\n            val item = mAdapter.getItem(binding.recyclerView.findCenterViewPosition())\n            upInfoBar(item)\n        }\n    }\n\n    private fun initRecyclerView() {\n        val mangaColorFilter =\n            GSON.fromJsonObject<MangaColorFilterConfig>(AppConfig.mangaColorFilter).getOrNull()\n                ?: MangaColorFilterConfig()\n        mAdapter.run {\n            setMangaImageColorFilter(mangaColorFilter)\n            enableMangaEInk(AppConfig.enableMangaEInk, AppConfig.mangaEInkThreshold)\n            enableGray(AppConfig.enableMangaGray)\n        }\n        setHorizontalScroll(AppConfig.enableMangaHorizontalScroll)\n        binding.recyclerView.run {\n            adapter = mAdapter\n            itemAnimator = null\n            layoutManager = mLayoutManager\n            setHasFixedSize(true)\n            setDisableClickScroll(AppConfig.disableClickScroll)\n            setDisableMangaScale(AppConfig.disableMangaScale)\n            setRecyclerViewPreloader(AppConfig.mangaPreDownloadNum)\n            setPreScrollListener { _, _, _, position ->\n                if (mAdapter.isNotEmpty()) {\n                    val item = mAdapter.getItem(position)\n                    if (item is BaseMangaPage) {\n                        if (ReadManga.durChapterIndex < item.chapterIndex) {\n                            ReadManga.moveToNextChapter()\n                        } else if (ReadManga.durChapterIndex > item.chapterIndex) {\n                            ReadManga.moveToPrevChapter()\n                        } else {\n                            ReadManga.durChapterPos = item.index\n                            ReadManga.curPageChanged()\n                        }\n                        if (item is MangaPage) {\n                            binding.mangaMenu.upSeekBar(item.index, item.imageCount)\n                            upInfoBar(item)\n                        }\n                    }\n                }\n            }\n        }\n        binding.webtoonFrame.run {\n            onTouchMiddle {\n                if (!binding.mangaMenu.isVisible && !loadingViewVisible) {\n                    binding.mangaMenu.runMenuIn()\n                }\n            }\n            onNextPage {\n                scrollToNext()\n            }\n            onPrevPage {\n                scrollToPrev()\n            }\n        }\n    }\n\n    override fun onNewIntent(intent: Intent) {\n        super.onNewIntent(intent)\n        viewModel.initData(intent)\n    }\n\n    override fun onPostCreate(savedInstanceState: Bundle?) {\n        super.onPostCreate(savedInstanceState)\n        viewModel.initData(intent)\n        justInitData = true\n    }\n\n    override fun upContent() {\n        lifecycleScope.launch {\n            setTitle(ReadManga.book?.name)\n            val data = withContext(IO) { ReadManga.mangaContents }\n            val pos = data.pos\n            val list = data.items\n            val curFinish = data.curFinish\n            val nextFinish = data.nextFinish\n            mAdapter.submitList(list) {\n                if (loadingViewVisible && curFinish) {\n                    binding.infobar.isVisible = true\n                    upInfoBar(list[pos])\n                    mLayoutManager.scrollToPositionWithOffset(pos, 0)\n                    binding.flLoading.isGone = true\n                    loadMoreView.visible()\n                    binding.mangaMenu.upSeekBar(\n                        ReadManga.durChapterPos, ReadManga.curMangaChapter!!.imageCount\n                    )\n                }\n\n                if (curFinish) {\n                    if (!ReadManga.hasNextChapter) {\n                        loadMoreView.noMore(\"暂无章节了！\")\n                    } else if (nextFinish) {\n                        loadMoreView.stopLoad()\n                    } else {\n                        loadMoreView.startLoad()\n                    }\n                }\n            }\n        }\n    }\n\n    private fun upInfoBar(page: Any?) {\n        if (page !is MangaPage) {\n            return\n        }\n        val chapterIndex = page.chapterIndex\n        val chapterSize = page.chapterSize\n        val chapterPos = page.index\n        val imageCount = page.imageCount\n        val chapterName = page.mChapterName\n        mMangaFooterConfig.run {\n            mLabelBuilder.clear()\n            binding.infobar.isGone = hideFooter\n            binding.infobar.textInfoAlignment = footerOrientation\n\n            if (!hideChapterName) {\n                mLabelBuilder.append(chapterName).append(\" \")\n            }\n\n            if (!hidePageNumber) {\n                if (!hidePageNumberLabel) {\n                    mLabelBuilder.append(getString(R.string.manga_check_page_number))\n                }\n                mLabelBuilder.append(\"${chapterPos + 1}/${imageCount}\").append(\" \")\n            }\n\n            if (!hideChapter) {\n                if (!hideChapterLabel) {\n                    mLabelBuilder.append(getString(R.string.manga_check_chapter))\n                }\n                mLabelBuilder.append(\"${chapterIndex + 1}/${chapterSize}\").append(\" \")\n            }\n\n            if (!hideProgressRatio) {\n                if (!hideProgressRatioLabel) {\n                    mLabelBuilder.append(getString(R.string.manga_check_progress))\n                }\n                val percent = if (chapterSize == 0 || imageCount == 0 && chapterIndex == 0) {\n                    \"0.0%\"\n                } else if (imageCount == 0) {\n                    df.format((chapterIndex + 1.0f) / chapterSize.toDouble())\n                } else {\n                    var percent =\n                        df.format(\n                            chapterIndex * 1.0f / chapterSize + 1.0f /\n                                    chapterSize * (chapterPos + 1) / imageCount.toDouble()\n                        )\n                    if (percent == \"100.0%\" && (chapterIndex + 1 != chapterSize || chapterPos + 1 != imageCount)) {\n                        percent = \"99.9%\"\n                    }\n                    percent\n                }\n                mLabelBuilder.append(percent)\n            }\n        }\n        binding.infobar.update(\n            if (mLabelBuilder.isEmpty()) \"\" else mLabelBuilder.toString()\n        )\n    }\n\n    override fun onResume() {\n        super.onResume()\n        networkChangedListener.register()\n        networkChangedListener.onNetworkChanged = {\n            // 当网络是可用状态且无需初始化时同步进度（初始化中已有同步进度逻辑）\n            if (AppConfig.syncBookProgressPlus && NetworkUtils.isAvailable() && !justInitData && ReadManga.inBookshelf) {\n                ReadManga.syncProgress({ progress -> sureNewProgress(progress) })\n            }\n        }\n        if (enableAutoScrollPage) {\n            mScrollTimer.isEnabledPage = true\n        }\n        if (enableAutoScroll) {\n            mScrollTimer.isEnabled = true\n        }\n    }\n\n    override fun onPause() {\n        super.onPause()\n        if (ReadManga.inBookshelf) {\n            ReadManga.saveRead()\n            if (!BuildConfig.DEBUG) {\n                if (AppConfig.syncBookProgressPlus) {\n                    ReadManga.syncProgress()\n                } else {\n                    ReadManga.uploadProgress()\n                }\n            }\n        }\n        if (!BuildConfig.DEBUG) {\n            Backup.autoBack(this)\n        }\n        ReadManga.cancelPreDownloadTask()\n        networkChangedListener.unRegister()\n        mScrollTimer.isEnabledPage = false\n        mScrollTimer.isEnabled = false\n    }\n\n    override fun loadFail(msg: String, retry: Boolean) {\n        lifecycleScope.launch {\n            if (loadingViewVisible) {\n                binding.llLoading.isGone = true\n                binding.llRetry.isVisible = true\n                binding.tvRetry.isVisible = retry\n                binding.tvMsg.text = msg\n            } else {\n                loadMoreView.error(null, \"加载失败，点击重试\")\n            }\n        }\n    }\n\n    override fun onDestroy() {\n        ReadManga.unregister(this)\n        super.onDestroy()\n    }\n\n    override fun onLowMemory() {\n        super.onLowMemory()\n        Glide.get(this).clearMemory()\n    }\n\n    override fun sureNewProgress(progress: BookProgress) {\n        syncDialog?.dismiss()\n        syncDialog = alert(R.string.get_book_progress) {\n            setMessage(R.string.cloud_progress_exceeds_current)\n            okButton {\n                ReadManga.setProgress(progress)\n            }\n            noButton()\n        }\n    }\n\n    override fun showLoading() {\n        lifecycleScope.launch {\n            binding.flLoading.isVisible = true\n        }\n    }\n\n    override fun startLoad() {\n        lifecycleScope.launch {\n            loadMoreView.startLoad()\n        }\n    }\n\n    override fun scrollBy(distance: Int) {\n        if (!binding.recyclerView.canScroll(1)) {\n            return\n        }\n        val time = ceil(16f / distance * 10000).toInt()\n        binding.recyclerView.smoothScrollBy(10000, 10000, mLinearInterpolator, time)\n    }\n\n    override fun scrollPage() {\n        scrollToNext()\n    }\n\n    override val oldBook: Book?\n        get() = ReadManga.book\n\n    override fun changeTo(source: BookSource, book: Book, toc: List<BookChapter>) {\n        if (book.isImage) {\n            binding.flLoading.isVisible = true\n            viewModel.changeTo(book, toc)\n        } else {\n            toastOnUi(\"所选择的源不是漫画源\")\n        }\n    }\n\n    override fun updateColorFilter(config: MangaColorFilterConfig) {\n        mAdapter.setMangaImageColorFilter(config)\n        updateWindowBrightness(config.l)\n    }\n\n    @SuppressLint(\"StringFormatMatches\")\n    override fun onCompatCreateOptionsMenu(menu: Menu): Boolean {\n        menuInflater.inflate(R.menu.book_manga, menu)\n        upMenu(menu)\n        return super.onCompatCreateOptionsMenu(menu)\n    }\n\n    /**\n     * 菜单\n     */\n    @SuppressLint(\"StringFormatMatches\", \"NotifyDataSetChanged\")\n    override fun onCompatOptionsItemSelected(item: MenuItem): Boolean {\n        when (item.itemId) {\n            R.id.menu_change_source -> {\n                binding.mangaMenu.runMenuOut()\n                ReadManga.book?.let {\n                    showDialogFragment(ChangeBookSourceDialog(it.name, it.author))\n                }\n            }\n\n            R.id.menu_catalog -> {\n                ReadManga.book?.let {\n                    tocActivity.launch(it.bookUrl)\n                }\n            }\n\n            R.id.menu_refresh -> {\n                binding.flLoading.isVisible = true\n                ReadManga.book?.let {\n                    viewModel.refreshContentDur(it)\n                }\n            }\n\n            R.id.menu_pre_manga_number -> {\n                showNumberPickerDialog(\n                    0,\n                    getString(R.string.pre_download),\n                    AppConfig.mangaPreDownloadNum\n                ) {\n                    AppConfig.mangaPreDownloadNum = it\n                    item.title = getString(R.string.pre_download_m, it)\n                    setRecyclerViewPreloader(it)\n                }\n            }\n\n            R.id.menu_disable_manga_scale -> {\n                item.isChecked = !item.isChecked\n                AppConfig.disableMangaScale = item.isChecked\n                setDisableMangaScale(item.isChecked)\n            }\n\n            R.id.menu_disable_click_scroll -> {\n                item.isChecked = !item.isChecked\n                AppConfig.disableClickScroll = item.isChecked\n                setDisableClickScroll(item.isChecked)\n            }\n\n            R.id.menu_enable_auto_page -> {\n                item.isChecked = !item.isChecked\n                val menuMangaAutoPageSpeed = mMenu?.findItem(R.id.menu_manga_auto_page_speed)\n                mScrollTimer.isEnabledPage = item.isChecked\n                menuMangaAutoPageSpeed?.isVisible = item.isChecked\n                enableAutoScrollPage = item.isChecked\n                enableAutoScroll = false\n                mScrollTimer.isEnabled = false\n                mMenu?.findItem(R.id.menu_enable_auto_scroll)?.isChecked = false\n            }\n\n            R.id.menu_manga_auto_page_speed -> {\n                showNumberPickerDialog(\n                    1, getString(R.string.setting_manga_auto_page_speed),\n                    AppConfig.mangaAutoPageSpeed\n                ) {\n                    AppConfig.mangaAutoPageSpeed = it\n                    item.title = getString(R.string.manga_auto_page_speed, it)\n                    mScrollTimer.setSpeed(it)\n                    if (enableAutoScrollPage) {\n                        mScrollTimer.isEnabledPage = true\n                    }\n                }\n            }\n\n            R.id.menu_manga_footer_config -> {\n                showDialogFragment(MangaFooterSettingDialog())\n            }\n\n            R.id.menu_enable_horizontal_scroll -> {\n                item.isChecked = !item.isChecked\n                AppConfig.enableMangaHorizontalScroll = item.isChecked\n                mMenu?.findItem(R.id.menu_disable_horizontal_page_snap)?.isVisible = item.isChecked\n                setHorizontalScroll(item.isChecked)\n                mAdapter.notifyDataSetChanged()\n            }\n\n            R.id.menu_manga_color_filter -> {\n                binding.mangaMenu.runMenuOut()\n                showDialogFragment(MangaColorFilterDialog())\n            }\n\n            R.id.menu_enable_auto_scroll -> {\n                item.isChecked = !item.isChecked\n                mScrollTimer.isEnabled = item.isChecked\n                mMenu?.findItem(R.id.menu_enable_auto_page)?.isChecked = false\n                enableAutoScroll = item.isChecked\n                enableAutoScrollPage = false\n                mScrollTimer.isEnabledPage = false\n                mMenu?.findItem(R.id.menu_manga_auto_page_speed)?.isVisible = item.isChecked\n                if (enableAutoScroll) {\n                    mPagerSnapHelper.attachToRecyclerView(null)\n                } else if (AppConfig.enableMangaHorizontalScroll) {\n                    mPagerSnapHelper.attachToRecyclerView(binding.recyclerView)\n                }\n            }\n\n            R.id.menu_hide_manga_title -> {\n                item.isChecked = !item.isChecked\n                AppConfig.hideMangaTitle = item.isChecked\n                ReadManga.loadContent()\n            }\n\n            R.id.menu_epaper_manga -> {\n                item.isChecked = !item.isChecked\n                AppConfig.enableMangaEInk = item.isChecked\n                mMenu?.findItem(R.id.menu_gray_manga)?.isChecked = false\n                AppConfig.enableMangaGray = false\n                mMenu?.findItem(R.id.menu_epaper_manga_setting)?.isVisible = item.isChecked\n                mAdapter.enableMangaEInk(item.isChecked, AppConfig.mangaEInkThreshold)\n            }\n\n            R.id.menu_epaper_manga_setting -> {\n                showDialogFragment(MangaEpaperDialog())\n            }\n\n            R.id.menu_disable_horizontal_page_snap -> {\n                item.isChecked = !item.isChecked\n                AppConfig.disableHorizontalPageSnap = item.isChecked\n                if (item.isChecked) {\n                    mPagerSnapHelper.attachToRecyclerView(null)\n                } else {\n                    mPagerSnapHelper.attachToRecyclerView(binding.recyclerView)\n                }\n            }\n\n            R.id.menu_disable_manga_page_anim -> {\n                item.isChecked = !item.isChecked\n                mMenu?.findItem(R.id.menu_disable_horizontal_page_snap)?.isVisible = !item.isChecked\n                AppConfig.disableMangaPageAnim = item.isChecked\n                if (item.isChecked) {\n                    mPagerSnapHelper.attachToRecyclerView(null)\n                } else {\n                    if (AppConfig.enableMangaHorizontalScroll && !AppConfig.disableHorizontalPageSnap) {\n                        mPagerSnapHelper.attachToRecyclerView(binding.recyclerView)\n                    }\n                }\n            }\n\n            R.id.menu_gray_manga -> {\n                item.isChecked = !item.isChecked\n                AppConfig.enableMangaGray = item.isChecked\n                mMenu?.findItem(R.id.menu_epaper_manga)?.isChecked = false\n                AppConfig.enableMangaEInk = false\n                mMenu?.findItem(R.id.menu_epaper_manga_setting)?.isVisible = false\n                mAdapter.enableGray(item.isChecked)\n            }\n        }\n        return super.onCompatOptionsItemSelected(item)\n    }\n\n    override fun openBookInfoActivity() {\n        ReadManga.book?.let {\n            bookInfoActivity.launch {\n                putExtra(\"name\", it.name)\n                putExtra(\"author\", it.author)\n            }\n        }\n    }\n\n    override fun upSystemUiVisibility(menuIsVisible: Boolean) {\n        toggleSystemBar(menuIsVisible)\n        if (enableAutoScroll) {\n            mScrollTimer.isEnabled = !menuIsVisible\n        }\n        if (enableAutoScrollPage) {\n            mScrollTimer.isEnabledPage = !menuIsVisible\n        }\n    }\n\n    override fun dispatchKeyEvent(event: KeyEvent): Boolean {\n        val keyCode = event.keyCode\n        val action = event.action\n        val isDown = action == 0\n\n        if (keyCode == KeyEvent.KEYCODE_MENU) {\n            if (isDown && !binding.mangaMenu.canShowMenu) {\n                binding.mangaMenu.runMenuIn()\n                return true\n            }\n            if (!isDown && !binding.mangaMenu.canShowMenu) {\n                binding.mangaMenu.canShowMenu = true\n                return true\n            }\n        }\n        return super.dispatchKeyEvent(event)\n    }\n\n    private fun setRecyclerViewPreloader(maxPreload: Int) {\n        if (mRecyclerViewPreloader != null) {\n            binding.recyclerView.removeOnScrollListener(mRecyclerViewPreloader!!)\n        }\n        mRecyclerViewPreloader = RecyclerViewPreloader(\n            Glide.with(this), mAdapter, mSizeProvider, maxPreload\n        )\n        binding.recyclerView.addOnScrollListener(mRecyclerViewPreloader!!)\n    }\n\n    private fun setHorizontalScroll(enable: Boolean) {\n        mAdapter.isHorizontal = enable\n        if (enable) {\n            if (!enableAutoScroll) {\n                if (AppConfig.disableHorizontalPageSnap || AppConfig.disableMangaPageAnim) {\n                    mPagerSnapHelper.attachToRecyclerView(null)\n                } else {\n                    mPagerSnapHelper.attachToRecyclerView(binding.recyclerView)\n                }\n            }\n            mLayoutManager.orientation = LinearLayoutManager.HORIZONTAL\n        } else {\n            mPagerSnapHelper.attachToRecyclerView(null)\n            mLayoutManager.orientation = LinearLayoutManager.VERTICAL\n        }\n    }\n\n    @SuppressLint(\"StringFormatMatches\")\n    private fun upMenu(menu: Menu) {\n        this.mMenu = menu\n        menu.findItem(R.id.menu_pre_manga_number).title =\n            getString(R.string.pre_download_m, AppConfig.mangaPreDownloadNum)\n        menu.findItem(R.id.menu_disable_manga_scale).isChecked = AppConfig.disableMangaScale\n        menu.findItem(R.id.menu_disable_click_scroll).isChecked = AppConfig.disableClickScroll\n        menu.findItem(R.id.menu_manga_auto_page_speed).title =\n            getString(R.string.manga_auto_page_speed, AppConfig.mangaAutoPageSpeed)\n        menu.findItem(R.id.menu_enable_horizontal_scroll).isChecked =\n            AppConfig.enableMangaHorizontalScroll\n        menu.findItem(R.id.menu_epaper_manga).isChecked = AppConfig.enableMangaEInk\n        menu.findItem(R.id.menu_epaper_manga_setting).isVisible = AppConfig.enableMangaEInk\n        menu.findItem(R.id.menu_disable_horizontal_page_snap).run {\n            isVisible = AppConfig.enableMangaHorizontalScroll && !AppConfig.disableMangaPageAnim\n            isChecked = AppConfig.disableHorizontalPageSnap || AppConfig.disableMangaPageAnim\n        }\n        menu.findItem(R.id.menu_disable_manga_page_anim).isChecked = AppConfig.disableMangaPageAnim\n        menu.findItem(R.id.menu_gray_manga).isChecked = AppConfig.enableMangaGray\n    }\n\n    private fun setDisableMangaScale(disable: Boolean) {\n        binding.webtoonFrame.disableMangaScale = disable\n        binding.recyclerView.disableMangaScale = disable\n        if (disable) {\n            binding.recyclerView.resetZoom()\n        }\n    }\n\n    private fun setDisableClickScroll(disable: Boolean) {\n        binding.webtoonFrame.disabledClickScroll = disable\n    }\n\n    private fun upLayoutInDisplayCutoutMode() {\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {\n            window.attributes = window.attributes.apply {\n                layoutInDisplayCutoutMode =\n                    WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES\n            }\n        }\n    }\n\n    private fun scrollToNext() {\n        scrollPageTo(1)\n    }\n\n    private fun scrollToPrev() {\n        scrollPageTo(-1)\n    }\n\n    private fun scrollPageTo(direction: Int) {\n        if (!binding.recyclerView.canScroll(direction)) {\n            return\n        }\n        var dx = 0\n        var dy = 0\n        if (AppConfig.enableMangaHorizontalScroll) {\n            dx = binding.recyclerView.run {\n                width - paddingStart - paddingEnd\n            }\n        } else {\n            dy = binding.recyclerView.run {\n                height - paddingTop - paddingBottom\n            }\n        }\n        dx *= direction\n        dy *= direction\n        if (AppConfig.disableMangaPageAnim) {\n            binding.recyclerView.scrollBy(dx, dy)\n        } else {\n            binding.recyclerView.smoothScrollBy(dx, dy)\n        }\n    }\n\n    private fun showNumberPickerDialog(\n        min: Int,\n        title: String,\n        initValue: Int,\n        callback: (Int) -> Unit,\n    ) {\n        NumberPickerDialog(this)\n            .setTitle(title)\n            .setMaxValue(9999)\n            .setMinValue(min)\n            .setValue(initValue)\n            .show {\n                callback.invoke(it)\n            }\n    }\n\n    override fun finish() {\n        val book = ReadManga.book ?: return super.finish()\n\n        if (ReadManga.inBookshelf) {\n            return super.finish()\n        }\n\n        if (!AppConfig.showAddToShelfAlert) {\n            viewModel.removeFromBookshelf { super.finish() }\n        } else {\n            alert(title = getString(R.string.add_to_bookshelf)) {\n                setMessage(getString(R.string.check_add_bookshelf, book.name))\n                okButton {\n                    ReadManga.book?.removeType(BookType.notShelf)\n                    ReadManga.book?.save()\n                    ReadManga.inBookshelf = true\n                    setResult(RESULT_OK)\n                }\n                noButton { viewModel.removeFromBookshelf { super.finish() } }\n            }\n        }\n    }\n\n    fun updateWindowBrightness(brightness: Int) {\n        val layoutParams = window.attributes\n        val normalizedBrightness = brightness.toFloat() / 255.0f\n        layoutParams.screenBrightness = normalizedBrightness.coerceIn(0f, 1f)\n        window.attributes = layoutParams\n        // 强制刷新屏幕\n        window.decorView.postInvalidate()\n    }\n\n    override fun skipToPage(index: Int) {\n        val durChapterIndex = ReadManga.durChapterIndex\n        val itemPos = mAdapter.getItems().fastBinarySearch {\n            val chapterIndex: Int\n            val pageIndex: Int\n            if (it is BaseMangaPage) {\n                chapterIndex = it.chapterIndex\n                pageIndex = it.index\n            } else {\n                error(\"unknown item type\")\n            }\n            val delta = chapterIndex - durChapterIndex\n            if (delta != 0) {\n                delta\n            } else {\n                pageIndex - index\n            }\n        }\n        if (itemPos > -1) {\n            mLayoutManager.scrollToPositionWithOffset(itemPos, 0)\n            upInfoBar(mAdapter.getItem(itemPos))\n            ReadManga.durChapterPos = index\n        }\n    }\n\n    override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {\n        when (keyCode) {\n            KeyEvent.KEYCODE_VOLUME_UP -> {\n                scrollToPrev()\n                return true\n            }\n\n            KeyEvent.KEYCODE_VOLUME_DOWN -> {\n                scrollToNext()\n                return true\n            }\n        }\n        return super.onKeyDown(keyCode, event)\n    }\n\n    override fun updateEepaper(value: Int) {\n        mAdapter.updateThreshold(value)\n    }\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/book/manga/ReadMangaViewModel.kt",
    "content": "package io.legado.app.ui.book.manga\n\nimport android.app.Application\nimport android.content.Intent\nimport io.legado.app.R\nimport io.legado.app.base.BaseViewModel\nimport io.legado.app.constant.AppLog\nimport io.legado.app.constant.BookType\nimport io.legado.app.constant.EventBus\nimport io.legado.app.data.appDb\nimport io.legado.app.data.entities.Book\nimport io.legado.app.data.entities.BookChapter\nimport io.legado.app.data.entities.BookProgress\nimport io.legado.app.exception.NoStackTraceException\nimport io.legado.app.help.AppWebDav\nimport io.legado.app.help.book.BookHelp\nimport io.legado.app.help.book.isLocal\nimport io.legado.app.help.book.isLocalModified\nimport io.legado.app.help.book.removeType\nimport io.legado.app.help.book.simulatedTotalChapterNum\nimport io.legado.app.help.config.AppConfig\nimport io.legado.app.help.coroutine.Coroutine\nimport io.legado.app.model.ReadManga\nimport io.legado.app.model.localBook.LocalBook\nimport io.legado.app.model.webBook.WebBook\nimport io.legado.app.utils.mapParallelSafe\nimport io.legado.app.utils.postEvent\nimport io.legado.app.utils.toastOnUi\nimport kotlinx.coroutines.currentCoroutineContext\nimport kotlinx.coroutines.ensureActive\nimport kotlinx.coroutines.flow.catch\nimport kotlinx.coroutines.flow.collect\nimport kotlinx.coroutines.flow.flow\nimport kotlinx.coroutines.flow.onCompletion\nimport kotlinx.coroutines.flow.onEach\nimport kotlinx.coroutines.flow.onEmpty\nimport kotlinx.coroutines.flow.onStart\nimport kotlinx.coroutines.flow.take\nimport splitties.init.appCtx\n\nclass ReadMangaViewModel(application: Application) : BaseViewModel(application) {\n\n    private var changeSourceCoroutine: Coroutine<*>? = null\n\n    /**\n     * 初始化\n     */\n    fun initData(intent: Intent, success: (() -> Unit)? = null) {\n        execute {\n            ReadManga.inBookshelf = intent.getBooleanExtra(\"inBookshelf\", true)\n            ReadManga.chapterChanged = intent.getBooleanExtra(\"chapterChanged\", false)\n            val bookUrl = intent.getStringExtra(\"bookUrl\")\n            val book = when {\n                bookUrl.isNullOrEmpty() -> appDb.bookDao.lastReadBook\n                else -> appDb.bookDao.getBook(bookUrl)\n            } ?: ReadManga.book\n            when {\n                book != null -> initManga(book)\n                else -> {\n                    ReadManga.loadFail(context.getString(R.string.no_book), false)\n                    AppLog.put(\"未找到漫画书籍\\nbookUrl:$bookUrl\")\n                }\n            }\n        }.onSuccess {\n            success?.invoke()\n        }.onError {\n            val msg = \"初始化数据失败\\n${it.localizedMessage}\"\n            AppLog.put(msg, it)\n        }.onFinally {\n            ReadManga.saveRead()\n        }\n    }\n\n    private suspend fun initManga(book: Book) {\n        val isSameBook = ReadManga.book?.bookUrl == book.bookUrl\n        if (isSameBook) {\n            ReadManga.upData(book)\n        } else {\n            ReadManga.resetData(book)\n        }\n        if (!book.isLocal && book.tocUrl.isEmpty() && !loadBookInfo(book)) {\n            return\n        }\n\n        if (book.isLocal && !checkLocalBookFileExist(book)) {\n            return\n        }\n\n        if ((ReadManga.chapterSize == 0 || book.isLocalModified()) && !loadChapterListAwait(book)) {\n            return\n        }\n\n        //开始加载内容\n        if (!isSameBook) {\n            ReadManga.loadContent()\n        } else {\n            ReadManga.loadOrUpContent()\n        }\n\n        if (ReadManga.chapterChanged) {\n            // 有章节跳转不同步阅读进度\n            ReadManga.chapterChanged = false\n        } else if (ReadManga.inBookshelf) {\n            if (AppConfig.syncBookProgressPlus) {\n                ReadManga.syncProgress(\n                    { progress -> ReadManga.mCallback?.sureNewProgress(progress) })\n            } else {\n                syncBookProgress(book)\n            }\n        }\n\n        //自动换源\n        if (!book.isLocal && ReadManga.bookSource == null) {\n            autoChangeSource(book.name, book.author)\n            return\n        }\n    }\n\n    private suspend fun loadChapterListAwait(book: Book): Boolean {\n        val bookSource = ReadManga.bookSource ?: return true\n        val oldBook = book.copy()\n        WebBook.getChapterListAwait(bookSource, book, true).onSuccess { cList ->\n            if (oldBook.bookUrl == book.bookUrl) {\n                appDb.bookDao.update(book)\n            } else {\n                appDb.bookDao.replace(oldBook, book)\n                BookHelp.updateCacheFolder(oldBook, book)\n            }\n            appDb.bookChapterDao.delByBook(oldBook.bookUrl)\n            appDb.bookChapterDao.insert(*cList.toTypedArray())\n            ReadManga.onChapterListUpdated(book)\n            return true\n        }.onFailure {\n            currentCoroutineContext().ensureActive()\n            //加载章节出错\n            ReadManga.mCallback?.loadFail(appCtx.getString(R.string.error_load_toc))\n            return false\n        }\n        return true\n    }\n\n    /**\n     * 加载详情页\n     */\n    private suspend fun loadBookInfo(book: Book): Boolean {\n        val source = ReadManga.bookSource ?: return true\n        try {\n            WebBook.getBookInfoAwait(source, book, canReName = false)\n            return true\n        } catch (e: Throwable) {\n            currentCoroutineContext().ensureActive()\n            ReadManga.mCallback?.loadFail(\"详情页出错: ${e.localizedMessage}\")\n            return false\n        }\n    }\n\n    /**\n     * 自动换源\n     */\n    private fun autoChangeSource(name: String, author: String) {\n        if (!AppConfig.autoChangeSource) return\n        execute {\n            val sources = appDb.bookSourceDao.allTextEnabledPart\n            flow {\n                for (source in sources) {\n                    source.getBookSource()?.let {\n                        emit(it)\n                    }\n                }\n            }.onStart {\n                // 自动换源\n\n            }.mapParallelSafe(AppConfig.threadCount) { source ->\n                val book = WebBook.preciseSearchAwait(source, name, author).getOrThrow()\n                if (book.tocUrl.isEmpty()) {\n                    WebBook.getBookInfoAwait(source, book)\n                }\n                val toc = WebBook.getChapterListAwait(source, book).getOrThrow()\n                val chapter = toc.getOrElse(book.durChapterIndex) {\n                    toc.last()\n                }\n                val nextChapter = toc.getOrElse(chapter.index) {\n                    toc.first()\n                }\n                WebBook.getContentAwait(\n                    bookSource = source,\n                    book = book,\n                    bookChapter = chapter,\n                    nextChapterUrl = nextChapter.url\n                )\n                book to toc\n            }.take(1).onEach { (book, toc) ->\n                changeTo(book, toc)\n            }.onEmpty {\n                throw NoStackTraceException(\"没有合适书源\")\n            }.onCompletion {\n                // 换源完成\n            }.catch {\n                AppLog.put(\"自动换源失败\\n${it.localizedMessage}\", it)\n                context.toastOnUi(\"自动换源失败\\n${it.localizedMessage}\")\n            }.collect()\n        }\n    }\n\n    /**\n     * 同步进度\n     */\n    fun syncBookProgress(\n        book: Book,\n        alertSync: ((progress: BookProgress) -> Unit)? = null\n    ) {\n        if (!AppConfig.syncBookProgress) return\n        execute {\n            AppWebDav.getBookProgress(book)\n        }.onError {\n            AppLog.put(\"拉取阅读进度失败《${book.name}》\\n${it.localizedMessage}\", it)\n        }.onSuccess { progress ->\n            progress ?: return@onSuccess\n            if (progress.durChapterIndex == book.durChapterIndex && progress.durChapterPos == book.durChapterPos) {\n                return@onSuccess\n            }\n            if (progress.durChapterIndex < book.durChapterIndex ||\n                (progress.durChapterIndex == book.durChapterIndex\n                        && progress.durChapterPos < book.durChapterPos)\n            ) {\n                alertSync?.invoke(progress)\n            } else if (progress.durChapterIndex < book.simulatedTotalChapterNum()) {\n                ReadManga.setProgress(progress)\n                AppLog.put(\"自动同步阅读进度成功《${book.name}》 ${progress.durChapterTitle}\")\n                context.toastOnUi(\"已同步最新漫画阅读进度\")\n            }\n        }\n    }\n\n    /**\n     * 换源\n     */\n    fun changeTo(book: Book, toc: List<BookChapter>) {\n        changeSourceCoroutine?.cancel()\n        changeSourceCoroutine = execute {\n            //换源中\n            ReadManga.book?.migrateTo(book, toc)\n            book.removeType(BookType.updateError)\n            ReadManga.book?.delete()\n            appDb.bookDao.insert(book)\n            appDb.bookChapterDao.insert(*toc.toTypedArray())\n            ReadManga.resetData(book)\n            ReadManga.loadContent()\n        }.onError {\n            AppLog.put(\"换源失败\\n$it\", it, true)\n        }.onFinally {\n            postEvent(EventBus.SOURCE_CHANGED, book.bookUrl)\n        }\n    }\n\n    private fun checkLocalBookFileExist(book: Book): Boolean {\n        try {\n            LocalBook.getBookInputStream(book)\n            return true\n        } catch (_: Throwable) {\n            return false\n        }\n    }\n\n    fun openChapter(index: Int, durChapterPos: Int = 0) {\n        if (index < ReadManga.chapterSize) {\n            ReadManga.showLoading()\n            ReadManga.durChapterIndex = index\n            ReadManga.durChapterPos = durChapterPos\n            ReadManga.saveRead()\n            ReadManga.loadContent()\n        }\n    }\n\n    fun removeFromBookshelf(success: (() -> Unit)?) {\n        val book = ReadManga.book\n        Coroutine.async {\n            book?.delete()\n        }.onSuccess {\n            success?.invoke()\n        }\n    }\n\n    override fun onCleared() {\n        super.onCleared()\n        changeSourceCoroutine?.cancel()\n    }\n\n    fun refreshContentDur(book: Book) {\n        execute {\n            appDb.bookChapterDao.getChapter(book.bookUrl, ReadManga.durChapterIndex)\n                ?.let { chapter ->\n                    BookHelp.delContent(book, chapter)\n                    openChapter(ReadManga.durChapterIndex, ReadManga.durChapterPos)\n                }\n        }\n    }\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/book/manga/config/MangaColorFilterConfig.kt",
    "content": "package io.legado.app.ui.book.manga.config\n\nimport io.legado.app.utils.GSON\n\ndata class MangaColorFilterConfig(\n    var r: Int = 0,\n    var g: Int = 0,\n    var b: Int = 0,\n    var a: Int = 0,\n    var l: Int = 0\n) {\n    fun toJson(): String {\n        if (r == 0 && g == 0 && b == 0 && a == 0 && l == 0) {\n            return \"\"\n        }\n        return GSON.toJson(this)\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/book/manga/config/MangaColorFilterDialog.kt",
    "content": "package io.legado.app.ui.book.manga.config\n\nimport android.content.DialogInterface\nimport android.os.Bundle\nimport android.view.View\nimport android.view.ViewGroup\nimport android.view.WindowManager\nimport io.legado.app.R\nimport io.legado.app.base.BaseDialogFragment\nimport io.legado.app.databinding.DialogMangaColorFilterBinding\nimport io.legado.app.help.config.AppConfig\nimport io.legado.app.utils.GSON\nimport io.legado.app.utils.fromJsonObject\nimport io.legado.app.utils.setLayout\nimport io.legado.app.utils.viewbindingdelegate.viewBinding\n\nclass MangaColorFilterDialog : BaseDialogFragment(R.layout.dialog_manga_color_filter) {\n    private val binding by viewBinding(DialogMangaColorFilterBinding::bind)\n    private val mConfig =\n        GSON.fromJsonObject<MangaColorFilterConfig>(AppConfig.mangaColorFilter).getOrNull()\n            ?: MangaColorFilterConfig()\n    private val callback get() = activity as? Callback\n\n    override fun onStart() {\n        super.onStart()\n        dialog?.window?.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND)\n        setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)\n    }\n\n    override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) {\n        initData()\n        initView()\n    }\n\n    private fun initData() {\n        binding.run {\n            dsbBrightness.progress = mConfig.l\n            dsbR.progress = mConfig.r\n            dsbG.progress = mConfig.g\n            dsbB.progress = mConfig.b\n            dsbA.progress = mConfig.a\n        }\n    }\n\n    private fun initView() {\n        binding.run {\n            dsbBrightness.onChanged = {\n                mConfig.l = it\n                callback?.updateColorFilter(mConfig)\n            }\n            dsbR.onChanged = {\n                mConfig.r = it\n                callback?.updateColorFilter(mConfig)\n            }\n            dsbG.onChanged = {\n                mConfig.g = it\n                callback?.updateColorFilter(mConfig)\n            }\n            dsbB.onChanged = {\n                mConfig.b = it\n                callback?.updateColorFilter(mConfig)\n            }\n            dsbA.onChanged = {\n                mConfig.a = it\n                callback?.updateColorFilter(mConfig)\n            }\n        }\n    }\n\n    override fun onDismiss(dialog: DialogInterface) {\n        super.onDismiss(dialog)\n        AppConfig.mangaColorFilter = mConfig.toJson()\n    }\n\n    interface Callback {\n        fun updateColorFilter(config: MangaColorFilterConfig)\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/book/manga/config/MangaEpaperDialog.kt",
    "content": "package io.legado.app.ui.book.manga.config\n\nimport android.content.DialogInterface\nimport android.os.Bundle\nimport android.view.View\nimport android.view.ViewGroup\nimport android.view.WindowManager\nimport io.legado.app.R\nimport io.legado.app.base.BaseDialogFragment\nimport io.legado.app.databinding.DialogMangaEpaperBinding\nimport io.legado.app.help.config.AppConfig\nimport io.legado.app.utils.setLayout\nimport io.legado.app.utils.viewbindingdelegate.viewBinding\n\nclass MangaEpaperDialog : BaseDialogFragment(R.layout.dialog_manga_epaper) {\n    private val binding by viewBinding(DialogMangaEpaperBinding::bind)\n    private val callback get() = activity as? Callback\n    private var mMangaEInkThreshold = 150\n    override fun onStart() {\n        super.onStart()\n        dialog?.window?.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND)\n        setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)\n    }\n\n    override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) {\n        initData()\n        initView()\n    }\n\n    private fun initData() {\n        binding.dsbEpaper.progress = AppConfig.mangaEInkThreshold\n    }\n\n    private fun initView() {\n        binding.dsbEpaper.onChanged = {\n            mMangaEInkThreshold = it\n            callback?.updateEepaper(it)\n        }\n    }\n\n    override fun onDismiss(dialog: DialogInterface) {\n        super.onDismiss(dialog)\n        AppConfig.mangaEInkThreshold = mMangaEInkThreshold\n    }\n\n    interface Callback {\n        fun updateEepaper(value: Int)\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/book/manga/config/MangaFooterConfig.kt",
    "content": "package io.legado.app.ui.book.manga.config\n\nimport androidx.annotation.Keep\nimport io.legado.app.ui.widget.ReaderInfoBarView\n\n@Keep\ndata class MangaFooterConfig(\n    var hideChapterLabel: Boolean = false,\n    var hideChapter: Boolean = false,\n    var hidePageNumberLabel: Boolean = false,\n    var hidePageNumber: Boolean = false,\n    var hideProgressRatioLabel: Boolean = false,\n    var hideProgressRatio: Boolean = false,\n    var footerOrientation: Int = ReaderInfoBarView.ALIGN_LEFT,//默认靠左\n    var hideFooter: Boolean = false,\n    var hideChapterName:Boolean=false,\n)"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/book/manga/config/MangaFooterSettingDialog.kt",
    "content": "package io.legado.app.ui.book.manga.config\n\nimport android.content.DialogInterface\nimport android.os.Bundle\nimport android.view.View\nimport android.view.ViewGroup\nimport io.legado.app.R\nimport io.legado.app.base.BaseDialogFragment\nimport io.legado.app.constant.EventBus\nimport io.legado.app.databinding.DialogMangaFooterSettingBinding\nimport io.legado.app.help.config.AppConfig\nimport io.legado.app.ui.widget.ReaderInfoBarView\nimport io.legado.app.utils.GSON\nimport io.legado.app.utils.fromJsonObject\nimport io.legado.app.utils.postEvent\nimport io.legado.app.utils.setLayout\nimport io.legado.app.utils.viewbindingdelegate.viewBinding\n\nclass MangaFooterSettingDialog : BaseDialogFragment(R.layout.dialog_manga_footer_setting) {\n    val config = GSON.fromJsonObject<MangaFooterConfig>(AppConfig.mangaFooterConfig).getOrNull()\n        ?: MangaFooterConfig()\n    private val binding by viewBinding(DialogMangaFooterSettingBinding::bind)\n\n    override fun onStart() {\n        super.onStart()\n        setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)\n    }\n\n    override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) {\n        binding.cbChapterLabel.run {\n            isChecked = config.hideChapterLabel\n            setOnCheckedChangeListener { _, isChecked ->\n                config.hideChapterLabel = isChecked\n                postEvent(EventBus.UP_MANGA_CONFIG, config)\n            }\n        }\n        binding.cbChapter.run {\n            isChecked = config.hideChapter\n            setOnCheckedChangeListener { _, isChecked ->\n                config.hideChapter = isChecked\n                postEvent(EventBus.UP_MANGA_CONFIG, config)\n            }\n        }\n        binding.cbPageNumberLabel.run {\n            isChecked = config.hidePageNumberLabel\n            setOnCheckedChangeListener { _, isChecked ->\n                config.hidePageNumberLabel = isChecked\n                postEvent(EventBus.UP_MANGA_CONFIG, config)\n            }\n        }\n        binding.cbPageNumber.run {\n            isChecked = config.hidePageNumber\n            setOnCheckedChangeListener { _, isChecked ->\n                config.hidePageNumber = isChecked\n                postEvent(EventBus.UP_MANGA_CONFIG, config)\n            }\n        }\n        binding.cbProgressRatioLabel.run {\n            isChecked = config.hideProgressRatioLabel\n            setOnCheckedChangeListener { _, isChecked ->\n                config.hideProgressRatioLabel = isChecked\n                postEvent(EventBus.UP_MANGA_CONFIG, config)\n            }\n        }\n        binding.cbProgressRatio.run {\n            isChecked = config.hideProgressRatio\n            setOnCheckedChangeListener { _, isChecked ->\n                config.hideProgressRatio = isChecked\n                postEvent(EventBus.UP_MANGA_CONFIG, config)\n            }\n        }\n        binding.cbChapterName.run {\n            isChecked = config.hideChapterName\n            setOnCheckedChangeListener { _, isChecked ->\n                config.hideChapterName = isChecked\n                postEvent(EventBus.UP_MANGA_CONFIG, config)\n            }\n        }\n        binding.rgFooterOrientation.check(if (config.footerOrientation == ReaderInfoBarView.ALIGN_CENTER) R.id.rb_center else R.id.rb_left)\n        binding.rgFooterOrientation.setOnCheckedChangeListener { _, checkedId ->\n            when (checkedId) {\n                R.id.rb_left -> {\n                    config.footerOrientation = ReaderInfoBarView.ALIGN_LEFT\n                }\n\n                R.id.rb_center -> {\n                    config.footerOrientation = ReaderInfoBarView.ALIGN_CENTER\n                }\n            }\n            postEvent(EventBus.UP_MANGA_CONFIG, config)\n        }\n\n        binding.rgFooter.check(if (config.hideFooter) R.id.rb_hide else R.id.rb_show)\n        binding.rgFooter.setOnCheckedChangeListener { _, checkedId ->\n            when (checkedId) {\n                R.id.rb_show -> {\n                    config.hideFooter = false\n                }\n\n                R.id.rb_hide -> {\n                    config.hideFooter = true\n                }\n            }\n            postEvent(EventBus.UP_MANGA_CONFIG, config)\n        }\n    }\n\n    override fun onDismiss(dialog: DialogInterface) {\n        super.onDismiss(dialog)\n        AppConfig.mangaFooterConfig = GSON.toJson(config)\n    }\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/book/manga/entities/BaseMangaPage.kt",
    "content": "package io.legado.app.ui.book.manga.entities\n\ninterface BaseMangaPage {\n    val chapterIndex: Int\n    val index: Int\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/book/manga/entities/EpaperTransformation.kt",
    "content": "package io.legado.app.ui.book.manga.entities\n\nimport android.graphics.Bitmap\nimport android.graphics.Canvas\nimport android.graphics.Color\nimport android.graphics.ColorMatrix\nimport android.graphics.ColorMatrixColorFilter\nimport android.graphics.Paint\nimport androidx.annotation.IntRange\nimport com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool\nimport com.bumptech.glide.load.resource.bitmap.BitmapTransformation\nimport java.security.MessageDigest\n\n/**\n * 墨水屏图片转换器。\n * 将彩色图片转换为灰度图，并可选择进行简单的二值化处理，以提高墨水屏显示效果。\n *\n * @param threshold 二值化的阈值（0-255）。低于此值的像素变为黑色，高于此值的像素变为白色。\n *                  仅当applyBinarization为true时有效。\n */\nclass EpaperTransformation(\n    @param:IntRange(0, 255) private val threshold: Int = 128,\n) : BitmapTransformation() {\n\n    private val ID =\n        \"io.legado.app.model.EpaperTransformation.${threshold}\"\n    private val ID_BYTES = ID.toByteArray(CHARSET)\n\n    override fun transform(\n        pool: BitmapPool,\n        toTransform: Bitmap,\n        outWidth: Int,\n        outHeight: Int,\n    ): Bitmap {\n        val resultBitmap = pool.get(outWidth, outHeight, Bitmap.Config.ARGB_8888)\n        val canvas = Canvas(resultBitmap)\n        val paint = Paint()\n\n        val colorMatrix = ColorMatrix()\n        colorMatrix.setSaturation(0f)\n        val filter = ColorMatrixColorFilter(colorMatrix)\n        paint.colorFilter = filter\n        canvas.drawBitmap(toTransform, 0f, 0f, paint)\n\n        val pixels = IntArray(outWidth * outHeight)\n        resultBitmap.getPixels(pixels, 0, outWidth, 0, 0, outWidth, outHeight)\n\n        for (i in pixels.indices) {\n            val pixel = pixels[i]\n            val gray = Color.red(pixel)\n            pixels[i] =\n                if (gray < threshold) Color.BLACK else Color.WHITE\n        }\n        resultBitmap.setPixels(pixels, 0, outWidth, 0, 0, outWidth, outHeight)\n\n        return resultBitmap\n    }\n\n    override fun updateDiskCacheKey(messageDigest: MessageDigest) {\n        messageDigest.update(ID_BYTES)\n    }\n\n    override fun equals(other: Any?): Boolean {\n        if (this === other) return true\n        if (javaClass != other?.javaClass) return false\n\n        other as EpaperTransformation\n\n        if (threshold != other.threshold) return false\n        if (ID != other.ID) return false\n\n        return true\n    }\n\n    override fun hashCode(): Int {\n        var result = 31 + threshold\n        result = 31 * result + ID.hashCode()\n        return result\n    }\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/book/manga/entities/GrayscaleTransformation.kt",
    "content": "package io.legado.app.ui.book.manga.entities\n\nimport android.graphics.Bitmap\nimport android.graphics.Canvas\nimport android.graphics.ColorMatrix\nimport android.graphics.ColorMatrixColorFilter\nimport android.graphics.Paint\nimport com.bumptech.glide.load.engine.bitmap_recycle.BitmapPool\nimport com.bumptech.glide.load.resource.bitmap.BitmapTransformation\nimport java.nio.charset.StandardCharsets\nimport java.security.MessageDigest\n\nclass GrayscaleTransformation : BitmapTransformation() {\n    private val ID = \"io.legado.app.model.GrayscaleTransformation\"\n\n    private val ID_BYTES = ID.toByteArray(StandardCharsets.UTF_8)\n\n    override fun transform(\n        pool: BitmapPool,\n        toTransform: Bitmap,\n        outWidth: Int,\n        outHeight: Int,\n    ): Bitmap {\n        val resultBitmap = pool.get(outWidth, outHeight, Bitmap.Config.ARGB_8888)\n\n        val canvas = Canvas(resultBitmap)\n        val paint = Paint()\n\n        val matrix = ColorMatrix(\n            floatArrayOf(\n                0.299f, 0.587f, 0.114f, 0f, 0f,\n                0.299f, 0.587f, 0.114f, 0f, 0f,\n                0.299f, 0.587f, 0.114f, 0f, 0f,\n                0f, 0f, 0f, 1f, 0f\n            )\n        )\n        val filter = ColorMatrixColorFilter(matrix)\n        paint.colorFilter = filter\n        canvas.drawBitmap(toTransform, 0f, 0f, paint)\n        return resultBitmap\n    }\n\n    override fun updateDiskCacheKey(messageDigest: MessageDigest) {\n        messageDigest.update(ID_BYTES)\n    }\n\n    override fun equals(other: Any?): Boolean {\n        if (this === other) return true\n        if (javaClass != other?.javaClass) return false\n        other as GrayscaleTransformation\n        return ID == other.ID\n    }\n\n    override fun hashCode(): Int {\n        return ID.hashCode()\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/book/manga/entities/MangaChapter.kt",
    "content": "package io.legado.app.ui.book.manga.entities\n\nimport io.legado.app.data.entities.BookChapter\n\ndata class MangaChapter(\n    val chapter: BookChapter,\n    val pages: List<BaseMangaPage>,\n    val imageCount: Int\n)\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/book/manga/entities/MangaContent.kt",
    "content": "package io.legado.app.ui.book.manga.entities\n\ndata class MangaContent(\n    val pos: Int,\n    val items: List<Any>,\n    val curFinish: Boolean,\n    val nextFinish: Boolean\n)\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/book/manga/entities/MangaPage.kt",
    "content": "package io.legado.app.ui.book.manga.entities\n\ndata class MangaPage(\n    override val chapterIndex: Int = 0,//总章节位置\n    val chapterSize: Int,//总章节数量\n    val mImageUrl: String = \"\",//当前URL\n    override val index: Int = 0,//当前章节位置\n    var imageCount: Int = 0,//当前章节内容总数\n    val mChapterName: String = \"\",//章节名称\n) : BaseMangaPage\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/book/manga/entities/ReaderLoading.kt",
    "content": "package io.legado.app.ui.book.manga.entities\n\ndata class ReaderLoading(\n    override val chapterIndex: Int = 0,\n    override val index: Int = 0,\n    val mMessage: String? = null,\n    val isVolume: Boolean = false\n) : BaseMangaPage\n\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/book/manga/recyclerview/GestureDetectorWithLongTap.kt",
    "content": "package io.legado.app.ui.book.manga.recyclerview\n\nimport android.content.Context\nimport android.os.Handler\nimport android.os.Looper\nimport android.view.GestureDetector\nimport android.view.MotionEvent\nimport android.view.ViewConfiguration\nimport kotlin.math.abs\n\nopen class GestureDetectorWithLongTap(\n    context: Context,\n    listener: Listener,\n) : GestureDetector(context, listener) {\n\n    private val handler = Handler(Looper.getMainLooper())\n    private val slop = ViewConfiguration.get(context).scaledTouchSlop\n    private val longTapTime = ViewConfiguration.getLongPressTimeout().toLong()\n    private val doubleTapTime = ViewConfiguration.getDoubleTapTimeout().toLong()\n\n    private var downX = 0f\n    private var downY = 0f\n    private var lastUp = 0L\n    private var lastDownEvent: MotionEvent? = null\n\n\n    private val longTapFn = Runnable { listener.onLongTapConfirmed(lastDownEvent!!) }\n\n    override fun onTouchEvent(ev: MotionEvent): Boolean {\n        when (ev.actionMasked) {\n            MotionEvent.ACTION_DOWN -> {\n                lastDownEvent?.recycle()\n                lastDownEvent = MotionEvent.obtain(ev)\n\n                if (ev.downTime - lastUp > doubleTapTime) {\n                    downX = ev.rawX\n                    downY = ev.rawY\n                    handler.postDelayed(longTapFn, longTapTime)\n                }\n            }\n\n            MotionEvent.ACTION_MOVE -> {\n                if (abs(ev.rawX - downX) > slop || abs(ev.rawY - downY) > slop) {\n                    handler.removeCallbacks(longTapFn)\n                }\n            }\n\n            MotionEvent.ACTION_UP -> {\n                lastUp = ev.eventTime\n                handler.removeCallbacks(longTapFn)\n            }\n\n            MotionEvent.ACTION_CANCEL, MotionEvent.ACTION_POINTER_DOWN -> {\n                handler.removeCallbacks(longTapFn)\n            }\n        }\n        return super.onTouchEvent(ev)\n    }\n\n    open class Listener : SimpleOnGestureListener() {\n        open fun onLongTapConfirmed(ev: MotionEvent) {\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/book/manga/recyclerview/MangaAdapter.kt",
    "content": "package io.legado.app.ui.book.manga.recyclerview\n\nimport android.content.Context\nimport android.graphics.ColorMatrix\nimport android.graphics.ColorMatrixColorFilter\nimport android.util.SparseArray\nimport android.view.LayoutInflater\nimport android.view.ViewGroup\nimport android.view.ViewGroup.LayoutParams.MATCH_PARENT\nimport androidx.annotation.IntRange\nimport androidx.core.util.size\nimport androidx.core.view.updateLayoutParams\nimport androidx.recyclerview.widget.AsyncListDiffer\nimport androidx.recyclerview.widget.DiffUtil\nimport androidx.recyclerview.widget.RecyclerView\nimport androidx.viewbinding.ViewBinding\nimport com.bumptech.glide.Glide\nimport com.bumptech.glide.ListPreloader.PreloadModelProvider\nimport com.bumptech.glide.RequestBuilder\nimport com.bumptech.glide.load.resource.bitmap.BitmapTransformation\nimport io.legado.app.base.adapter.ItemViewHolder\nimport io.legado.app.base.adapter.RecyclerAdapter.Companion.TYPE_FOOTER_VIEW\nimport io.legado.app.databinding.ItemBookMangaEdgeBinding\nimport io.legado.app.databinding.ItemBookMangaPageBinding\nimport io.legado.app.help.glide.progress.ProgressManager\nimport io.legado.app.model.BookCover\nimport io.legado.app.model.ReadManga\nimport io.legado.app.ui.book.manga.config.MangaColorFilterConfig\nimport io.legado.app.ui.book.manga.entities.EpaperTransformation\nimport io.legado.app.ui.book.manga.entities.GrayscaleTransformation\nimport io.legado.app.ui.book.manga.entities.MangaPage\nimport io.legado.app.ui.book.manga.entities.ReaderLoading\nimport io.legado.app.utils.dpToPx\n\n\nclass MangaAdapter(private val context: Context) :\n    RecyclerView.Adapter<RecyclerView.ViewHolder>(), PreloadModelProvider<Any> {\n\n    private val inflater: LayoutInflater = LayoutInflater.from(context)\n    private lateinit var mConfig: MangaColorFilterConfig\n    private var mTransformation: BitmapTransformation? = null\n    private var currentMangaEInkThreshold = 0\n\n    companion object {\n        private const val LOADING_VIEW = 0\n        private const val CONTENT_VIEW = 1\n    }\n\n    var isHorizontal = false\n\n    private val mDiffCallback: DiffUtil.ItemCallback<Any> = object : DiffUtil.ItemCallback<Any>() {\n        override fun areItemsTheSame(oldItem: Any, newItem: Any): Boolean {\n            return if (oldItem is ReaderLoading && newItem is ReaderLoading) {\n                newItem.mMessage == oldItem.mMessage\n            } else if (oldItem is MangaPage && newItem is MangaPage) {\n                oldItem.mImageUrl == newItem.mImageUrl\n            } else false\n        }\n\n        override fun areContentsTheSame(oldItem: Any, newItem: Any): Boolean {\n            return if (oldItem is ReaderLoading && newItem is ReaderLoading) {\n                oldItem == newItem\n            } else if (oldItem is MangaPage && newItem is MangaPage) {\n                oldItem == newItem\n            } else false\n        }\n    }\n\n    private val mDiffer = AsyncListDiffer(this, mDiffCallback)\n\n    fun getItem(@IntRange(from = 0) position: Int) = mDiffer.currentList.getOrNull(position)\n\n    fun getItems() = mDiffer.currentList\n\n    fun isEmpty() = mDiffer.currentList.isEmpty()\n\n    fun isNotEmpty() = !isEmpty()\n\n    //全部替换数据\n    fun submitList(contents: List<Any>, runnable: Runnable? = null) {\n        mDiffer.submitList(contents, runnable)\n    }\n\n    inner class PageViewHolder(binding: ItemBookMangaPageBinding) :\n        MangaVH<ItemBookMangaPageBinding>(binding, context) {\n\n        init {\n            initComponent(\n                binding.loading,\n                binding.image,\n                binding.progress,\n                binding.retry,\n                binding.flProgress\n            )\n            binding.retry.setOnClickListener {\n                val item = mDiffer.currentList[layoutPosition]\n                if (item is MangaPage) {\n                    val isLastImage = item.imageCount > 0 && item.index == item.imageCount - 1\n                    loadImageWithRetry(\n                        item.mImageUrl, isHorizontal, isLastImage, mTransformation\n                    )\n                }\n            }\n        }\n\n        fun onBind(item: MangaPage) {\n            setImageColorFilter()\n            val isLastImage = item.imageCount > 0 && item.index == item.imageCount - 1\n            loadImageWithRetry(item.mImageUrl, isHorizontal, isLastImage, mTransformation)\n        }\n\n        fun setImageColorFilter() {\n            require(\n                mConfig.r in 0..255 &&\n                        mConfig.g in 0..255 &&\n                        mConfig.b in 0..255 &&\n                        mConfig.a in 0..255\n            ) {\n                \"ARGB values must be between 0-255\"\n            }\n            val matrix = floatArrayOf(\n                (255 - mConfig.r) / 255f, 0f, 0f, 0f, 0f,\n                0f, (255 - mConfig.g) / 255f, 0f, 0f, 0f,\n                0f, 0f, (255 - mConfig.b) / 255f, 0f, 0f,\n                0f, 0f, 0f, (255 - mConfig.a) / 255f, 0f\n            )\n            binding.image.colorFilter = ColorMatrixColorFilter(ColorMatrix(matrix))\n        }\n    }\n\n    inner class PageMoreViewHolder(val binding: ItemBookMangaEdgeBinding) :\n        RecyclerView.ViewHolder(binding.root) {\n        fun onBind(item: ReaderLoading) {\n            val message = item.mMessage\n            binding.text.text = message\n            itemView.updateLayoutParams {\n                height = if (item.isVolume) {\n                    MATCH_PARENT\n                } else {\n                    96.dpToPx()\n                }\n            }\n        }\n    }\n\n\n    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {\n        return when {\n            viewType >= TYPE_FOOTER_VIEW -> {\n                ItemViewHolder(footerItems.get(viewType).invoke(parent))\n            }\n\n            viewType == LOADING_VIEW -> {\n                PageMoreViewHolder(ItemBookMangaEdgeBinding.inflate(inflater, parent, false))\n            }\n\n            viewType == CONTENT_VIEW -> {\n                PageViewHolder(ItemBookMangaPageBinding.inflate(inflater, parent, false))\n            }\n\n            else -> error(\"Unknown view type!\")\n        }\n    }\n\n    override fun getItemCount(): Int = getActualItemCount() + getFooterCount()\n\n    override fun getItemViewType(position: Int): Int {\n        return when {\n            isFooter(position) -> TYPE_FOOTER_VIEW + position - getActualItemCount()\n            getItem(position) is MangaPage -> CONTENT_VIEW\n            getItem(position) is ReaderLoading -> LOADING_VIEW\n            else -> error(\"Unknown view type!\")\n        }\n    }\n\n    fun getFooterCount() = footerItems.size\n\n    private fun isFooter(position: Int) = position >= getActualItemCount()\n\n    override fun onViewRecycled(vh: RecyclerView.ViewHolder) {\n        super.onViewRecycled(vh)\n        when (vh) {\n            is PageViewHolder -> {\n                vh.itemView.updateLayoutParams<ViewGroup.LayoutParams> {\n                    height = MATCH_PARENT\n                }\n                Glide.with(context).clear(vh.binding.image)\n                if (vh.binding.image.tag is String) {\n                    ProgressManager.removeListener(vh.binding.image.tag as String)\n                }\n            }\n        }\n    }\n\n    override fun onBindViewHolder(vh: RecyclerView.ViewHolder, position: Int) {\n        when (vh) {\n            is PageViewHolder -> vh.onBind(getItem(position) as MangaPage)\n            is PageMoreViewHolder -> vh.onBind(getItem(position) as ReaderLoading)\n        }\n    }\n\n\n    private val footerItems: SparseArray<(parent: ViewGroup) -> ViewBinding> by lazy { SparseArray() }\n\n    @Synchronized\n    fun addFooterView(footer: ((parent: ViewGroup) -> ViewBinding)) {\n        kotlin.runCatching {\n            val index = getActualItemCount() + footerItems.size\n            footerItems.put(TYPE_FOOTER_VIEW + footerItems.size, footer)\n            notifyItemInserted(index)\n        }\n    }\n\n    /**\n     * 除去header和footer\n     */\n    fun getActualItemCount() = getItems().size\n\n    @Synchronized\n    fun removeFooterView(footer: ((parent: ViewGroup) -> ViewBinding)) {\n        kotlin.runCatching {\n            val index = footerItems.indexOfValue(footer)\n            if (index >= 0) {\n                footerItems.remove(index)\n                notifyItemRemoved(getActualItemCount() + index - 2)\n            }\n        }\n    }\n\n    override fun getPreloadItems(position: Int): List<Any> {\n        if (isEmpty() || position >= getItems().size) {\n            return emptyList()\n        }\n        return getItems().subList(position, position + 1)\n    }\n\n    override fun getPreloadRequestBuilder(item: Any): RequestBuilder<*>? {\n        if (item is MangaPage) {\n            return BookCover.preloadManga(\n                context,\n                item.mImageUrl,\n                sourceOrigin = ReadManga.book?.origin,\n            )\n        }\n        return null\n    }\n\n    fun setMangaImageColorFilter(config: MangaColorFilterConfig) {\n        mConfig = config\n        notifyItemRangeChanged(0, itemCount)\n    }\n\n    fun enableMangaEInk(enable: Boolean, value: Int) {\n        if (enable) {\n            currentMangaEInkThreshold = value\n            mTransformation = EpaperTransformation(currentMangaEInkThreshold)\n        } else {\n            mTransformation = null\n        }\n        notifyItemRangeChanged(0, itemCount)\n    }\n\n    fun updateThreshold(mangaEInkThreshold: Int) {\n        if (currentMangaEInkThreshold != mangaEInkThreshold) {\n            currentMangaEInkThreshold = mangaEInkThreshold\n            mTransformation = EpaperTransformation(currentMangaEInkThreshold)\n            notifyItemRangeChanged(0, itemCount)\n        }\n    }\n\n    //开启灰色图片\n    fun enableGray(enable: Boolean) {\n        mTransformation = if (enable) {\n            GrayscaleTransformation()\n        } else {\n            null\n        }\n        notifyItemRangeChanged(0, itemCount)\n    }\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/book/manga/recyclerview/MangaLayoutManager.kt",
    "content": "package io.legado.app.ui.book.manga.recyclerview\n\nimport android.content.Context\nimport androidx.recyclerview.widget.LinearLayoutManager\nimport androidx.recyclerview.widget.RecyclerView\n\nclass MangaLayoutManager(context: Context) :\n    LinearLayoutManager(context) {\n\n    private val extraLayoutSpace = context.resources.displayMetrics.heightPixels * 3 / 4\n\n    @Deprecated(\"Deprecated in Java\")\n    override fun getExtraLayoutSpace(state: RecyclerView.State?): Int {\n        return extraLayoutSpace\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/book/manga/recyclerview/MangaVH.kt",
    "content": "package io.legado.app.ui.book.manga.recyclerview\n\nimport android.annotation.SuppressLint\nimport android.content.Context\nimport android.graphics.Bitmap\nimport android.graphics.drawable.Drawable\nimport android.view.Gravity\nimport android.view.ViewGroup\nimport android.widget.Button\nimport android.widget.FrameLayout\nimport android.widget.ImageView\nimport android.widget.ProgressBar\nimport android.widget.TextView\nimport androidx.appcompat.widget.AppCompatImageView\nimport androidx.core.view.isGone\nimport androidx.core.view.isVisible\nimport androidx.core.view.updateLayoutParams\nimport androidx.recyclerview.widget.RecyclerView\nimport androidx.viewbinding.ViewBinding\nimport com.bumptech.glide.load.DataSource\nimport com.bumptech.glide.load.Transformation\nimport com.bumptech.glide.load.engine.GlideException\nimport com.bumptech.glide.request.RequestListener\nimport com.bumptech.glide.request.target.Target\nimport io.legado.app.help.glide.progress.ProgressManager\nimport io.legado.app.model.BookCover\nimport io.legado.app.model.ReadManga\nimport io.legado.app.utils.printOnDebug\n\nopen class MangaVH<VB : ViewBinding>(val binding: VB, private val context: Context) :\n    RecyclerView.ViewHolder(binding.root) {\n\n    protected lateinit var mLoading: ProgressBar\n    protected lateinit var mImage: AppCompatImageView\n    protected lateinit var mProgress: TextView\n    protected lateinit var mFlProgress: FrameLayout\n    protected var mRetry: Button? = null\n\n    private val minHeight = context.resources.displayMetrics.heightPixels * 2 / 3\n\n    fun initComponent(\n        loading: ProgressBar,\n        image: AppCompatImageView,\n        progress: TextView,\n        button: Button? = null,\n        flProgress: FrameLayout,\n    ) {\n        mLoading = loading\n        mImage = image\n        mRetry = button\n        mProgress = progress\n        mFlProgress = flProgress\n    }\n\n    @SuppressLint(\"CheckResult\")\n    fun loadImageWithRetry(\n        imageUrl: String,\n        isHorizontal: Boolean,\n        isLastImage: Boolean,\n        transformation: Transformation<Bitmap>?\n    ) {\n        mFlProgress.isVisible = true\n        mLoading.isVisible = true\n        mRetry?.isGone = true\n        mProgress.isVisible = true\n        ProgressManager.removeListener(imageUrl)\n        ProgressManager.addListener(imageUrl) { _, percentage, _, _ ->\n            @SuppressLint(\"SetTextI18n\")\n            mProgress.text = \"$percentage%\"\n        }\n        try {\n            mImage.tag = imageUrl\n            BookCover.loadManga(\n                context,\n                imageUrl,\n                sourceOrigin = ReadManga.book?.origin,\n                transformation = transformation\n            ).addListener(object : RequestListener<Drawable> {\n                override fun onLoadFailed(\n                    e: GlideException?,\n                    model: Any?,\n                    target: Target<Drawable>,\n                    isFirstResource: Boolean,\n                ): Boolean {\n                    mFlProgress.isVisible = true\n                    mLoading.isGone = true\n                    mRetry?.isVisible = true\n                    mProgress.isGone = true\n                    itemView.updateLayoutParams<ViewGroup.LayoutParams> {\n                        height = ViewGroup.LayoutParams.MATCH_PARENT\n                    }\n                    return false\n                }\n\n                override fun onResourceReady(\n                    resource: Drawable,\n                    model: Any,\n                    target: Target<Drawable>?,\n                    dataSource: DataSource,\n                    isFirstResource: Boolean,\n                ): Boolean {\n                    mFlProgress.isGone = true\n                    if (!isHorizontal) {\n                        itemView.updateLayoutParams<ViewGroup.LayoutParams> {\n                            height = ViewGroup.LayoutParams.WRAP_CONTENT\n                        }\n                        mImage.updateLayoutParams<FrameLayout.LayoutParams> {\n                            gravity = Gravity.NO_GRAVITY\n                        }\n                        if (isLastImage) {\n                            mImage.updateLayoutParams<FrameLayout.LayoutParams> {\n                                height = ViewGroup.LayoutParams.WRAP_CONTENT\n                            }\n                            itemView.minimumHeight = minHeight\n                        } else {\n                            mImage.updateLayoutParams<FrameLayout.LayoutParams> {\n                                height = ViewGroup.LayoutParams.MATCH_PARENT\n                            }\n                            itemView.minimumHeight = 0\n                        }\n                        mImage.scaleType = ImageView.ScaleType.FIT_XY\n                    } else {\n                        itemView.updateLayoutParams<ViewGroup.LayoutParams> {\n                            height = ViewGroup.LayoutParams.MATCH_PARENT\n                        }\n                        itemView.minimumHeight = 0\n                        mImage.updateLayoutParams<FrameLayout.LayoutParams> {\n                            height = ViewGroup.LayoutParams.MATCH_PARENT\n                            gravity = Gravity.CENTER\n                        }\n                        mImage.scaleType = ImageView.ScaleType.FIT_CENTER\n                    }\n                    return false\n                }\n            }).into(mImage)\n        } catch (e: Exception) {\n            e.printOnDebug()\n        }\n\n    }\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/book/manga/recyclerview/ScrollTimer.kt",
    "content": "package io.legado.app.ui.book.manga.recyclerview\n\nimport androidx.recyclerview.widget.RecyclerView\nimport androidx.recyclerview.widget.RecyclerView.SCROLL_STATE_IDLE\nimport kotlinx.coroutines.CoroutineScope\nimport kotlinx.coroutines.Job\nimport kotlinx.coroutines.delay\nimport kotlinx.coroutines.isActive\nimport kotlinx.coroutines.launch\n\nclass ScrollTimer(\n    private val callback: ScrollCallback,\n    private val recyclerView: RecyclerView,\n    private val coroutineScope: CoroutineScope,\n) : RecyclerView.OnScrollListener() {\n    private var distance = 1\n    private var mScrollPageJob: Job? = null\n    var isEnabled: Boolean = false\n        set(value) {\n            if (field != value) {\n                field = value\n                if (value) {\n                    recyclerView.addOnScrollListener(this)\n                    startScroll()\n                } else {\n                    recyclerView.removeOnScrollListener(this)\n                    recyclerView.stopScroll()\n                }\n            }\n        }\n\n    var isEnabledPage: Boolean = false\n        set(value) {\n            if (field != value) {\n                field = value\n                if (value) {\n                    mScrollPageJob?.cancel()\n                    startScrollPage()\n                } else {\n                    mScrollPageJob?.cancel()\n                }\n            }\n        }\n\n    override fun onScrollStateChanged(\n        recyclerView: RecyclerView,\n        newState: Int,\n    ) {\n        if (newState == SCROLL_STATE_IDLE) {\n            startScroll()\n        }\n    }\n\n    fun setSpeed(distance: Int) {\n        this.distance = distance\n    }\n\n    private fun startScroll() {\n        callback.scrollBy(distance)\n    }\n\n    private fun startScrollPage() {\n        mScrollPageJob = coroutineScope.launch {\n            while (isActive) {\n                delay(distance.times(1000L))\n                callback.scrollPage()\n            }\n        }\n    }\n\n    interface ScrollCallback {\n        fun scrollBy(distance: Int)\n        fun scrollPage()\n    }\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/book/manga/recyclerview/WebtoonFrame.kt",
    "content": "package io.legado.app.ui.book.manga.recyclerview\n\nimport android.content.Context\nimport android.graphics.Rect\nimport android.graphics.RectF\nimport android.util.AttributeSet\nimport android.view.GestureDetector\nimport android.view.MotionEvent\nimport android.view.ScaleGestureDetector\nimport android.widget.FrameLayout\n\nclass WebtoonFrame : FrameLayout {\n\n    constructor(context: Context) : super(context)\n    constructor(context: Context, attrs: AttributeSet) : super(context, attrs)\n    constructor(context: Context, attrs: AttributeSet, defStyle: Int) : super(\n        context,\n        attrs,\n        defStyle\n    )\n\n    private val scaleDetector = ScaleGestureDetector(context, ScaleListener())\n    private val flingDetector = GestureDetector(context, FlingListener())\n\n    var doubleTapZoom = true\n        set(value) {\n            field = value\n            recycler?.doubleTapZoom = value\n            scaleDetector.isQuickScaleEnabled = value\n        }\n\n    var disableMangaScale = false\n\n    private val recycler: WebtoonRecyclerView?\n        get() = getChildAt(0) as? WebtoonRecyclerView\n\n    private val mcRect = RectF()\n    private val blRect = RectF()\n    private val brRect = RectF()\n\n    private var mTouchMiddle: (() -> Unit)? = null\n    fun onTouchMiddle(init: () -> Unit) = apply { this.mTouchMiddle = init }\n    private var mNextPage: (() -> Unit)? = null\n    fun onNextPage(init: () -> Unit) = apply { this.mNextPage = init }\n    private var mPrevPage: (() -> Unit)? = null\n    fun onPrevPage(init: () -> Unit) = apply { this.mPrevPage = init }\n\n    var disabledClickScroll = false\n\n    override fun onAttachedToWindow() {\n        super.onAttachedToWindow()\n        recycler?.tapListener = { ev ->\n            when {\n                mcRect.contains(ev.rawX, ev.rawY) -> {\n                    mTouchMiddle?.invoke()\n                }\n\n                blRect.contains(ev.rawX, ev.rawY) && !disabledClickScroll -> {\n                    mPrevPage?.invoke()\n                }\n\n                brRect.contains(ev.rawX, ev.rawY) && !disabledClickScroll -> {\n                    mNextPage?.invoke()\n                }\n            }\n        }\n    }\n\n    override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {\n        super.onSizeChanged(w, h, oldw, oldh)\n        mcRect.set(width * 0.33f, height * 0.33f, width * 0.66f, height * 0.66f)\n        blRect.set(0f, height * 0.66f, width * 0.33f, height.toFloat())\n        brRect.set(width * 0.66f, height * 0.66f, width.toFloat(), height.toFloat())\n    }\n\n    override fun dispatchTouchEvent(ev: MotionEvent): Boolean {\n        if (!disableMangaScale) {\n            scaleDetector.onTouchEvent(ev)\n            flingDetector.onTouchEvent(ev)\n            val recyclerRect = Rect()\n            recycler?.getHitRect(recyclerRect) ?: return super.dispatchTouchEvent(ev)\n            recyclerRect.inset(1, 1)\n\n            if (recyclerRect.right < recyclerRect.left || recyclerRect.bottom < recyclerRect.top) {\n                return super.dispatchTouchEvent(ev)\n            }\n\n            ev.setLocation(\n                ev.x.coerceIn(recyclerRect.left.toFloat(), recyclerRect.right.toFloat()),\n                ev.y.coerceIn(recyclerRect.top.toFloat(), recyclerRect.bottom.toFloat()),\n            )\n        }\n        return super.dispatchTouchEvent(ev)\n    }\n\n    inner class ScaleListener : ScaleGestureDetector.SimpleOnScaleGestureListener() {\n        override fun onScaleBegin(detector: ScaleGestureDetector): Boolean {\n            recycler?.onScaleBegin()\n            return true\n        }\n\n        override fun onScale(detector: ScaleGestureDetector): Boolean {\n            recycler?.onScale(detector.scaleFactor)\n            return true\n        }\n\n        override fun onScaleEnd(detector: ScaleGestureDetector) {\n            recycler?.onScaleEnd()\n        }\n    }\n\n    inner class FlingListener : GestureDetector.SimpleOnGestureListener() {\n        override fun onDown(e: MotionEvent): Boolean {\n            return true\n        }\n\n        override fun onFling(\n            e1: MotionEvent?,\n            e2: MotionEvent,\n            velocityX: Float,\n            velocityY: Float,\n        ): Boolean {\n            return recycler?.zoomFling(velocityX.toInt(), velocityY.toInt()) ?: false\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/book/manga/recyclerview/WebtoonRecyclerView.kt",
    "content": "package io.legado.app.ui.book.manga.recyclerview\n\nimport android.animation.AnimatorSet\nimport android.animation.ValueAnimator\nimport android.annotation.SuppressLint\nimport android.content.Context\nimport android.util.AttributeSet\nimport android.view.HapticFeedbackConstants\nimport android.view.MotionEvent\nimport android.view.ViewConfiguration\nimport android.view.animation.DecelerateInterpolator\nimport androidx.core.animation.doOnEnd\nimport androidx.recyclerview.widget.LinearLayoutManager\nimport androidx.recyclerview.widget.RecyclerView\nimport io.legado.app.utils.findCenterViewPosition\nimport kotlin.math.abs\n\n\nclass WebtoonRecyclerView @JvmOverloads constructor(\n    context: Context,\n    attrs: AttributeSet? = null,\n    defStyle: Int = 0,\n) : RecyclerView(context, attrs, defStyle) {\n\n    private var isZooming = false\n    private var atLastPosition = false\n    private var atFirstPosition = false\n    private var halfWidth = 0\n    private var halfHeight = 0\n    private var originalHeight = 0\n    private var heightSet = false\n    private var firstVisibleItemPosition = 0\n    private var lastVisibleItemPosition = 0\n    private var currentScale = DEFAULT_RATE\n    private var mLastCenterViewPosition = 0\n\n    private var mPreScrollListener: IComicPreScroll? = null\n    private var mNestedPreScrollListener: IComicPreScroll? = null\n    private val listener = GestureListener()\n    private val detector = Detector()\n\n    var doubleTapZoom = true\n    var tapListener: ((MotionEvent) -> Unit)? = null\n    var longTapListener: ((MotionEvent) -> Boolean)? = null\n    var disableMangaScale = false\n\n    override fun onMeasure(widthSpec: Int, heightSpec: Int) {\n        halfWidth = MeasureSpec.getSize(widthSpec) / 2\n        halfHeight = MeasureSpec.getSize(heightSpec) / 2\n        if (!heightSet) {\n            originalHeight = MeasureSpec.getSize(heightSpec)\n            heightSet = true\n        }\n        super.onMeasure(widthSpec, heightSpec)\n    }\n\n    @SuppressLint(\"ClickableViewAccessibility\")\n    override fun onTouchEvent(e: MotionEvent): Boolean {\n        return detector.onTouchEvent(e) || super.onTouchEvent(e)\n    }\n\n    override fun onScrolled(dx: Int, dy: Int) {\n        super.onScrolled(dx, dy)\n        val layoutManager = layoutManager as LinearLayoutManager\n        lastVisibleItemPosition = layoutManager.findLastVisibleItemPosition()\n        firstVisibleItemPosition = layoutManager.findFirstVisibleItemPosition()\n\n        val position = findCenterViewPosition()\n        if (position != NO_POSITION && position != mLastCenterViewPosition) {\n            mLastCenterViewPosition = position\n            mPreScrollListener?.onPreScrollListener(this, dx, dy, position)\n        }\n    }\n\n    override fun onScrollStateChanged(state: Int) {\n        super.onScrollStateChanged(state)\n        val layoutManager = layoutManager\n        val visibleItemCount = layoutManager?.childCount ?: 0\n        val totalItemCount = layoutManager?.itemCount ?: 0\n        atLastPosition = visibleItemCount > 0 && lastVisibleItemPosition == totalItemCount - 1\n        atFirstPosition = firstVisibleItemPosition == 0\n    }\n\n    override fun dispatchNestedPreScroll(\n        dx: Int,\n        dy: Int,\n        consumed: IntArray?,\n        offsetInWindow: IntArray?,\n        type: Int\n    ): Boolean {\n        val position = findCenterViewPosition()\n        mNestedPreScrollListener?.onPreScrollListener(this, dx, dy, position)\n        return super.dispatchNestedPreScroll(dx, dy, consumed, offsetInWindow, type)\n    }\n\n    private fun getPositionX(positionX: Float): Float {\n        if (currentScale < 1) {\n            return 0f\n        }\n        val maxPositionX = halfWidth * (currentScale - 1)\n        return positionX.coerceIn(-maxPositionX, maxPositionX)\n    }\n\n    private fun getPositionY(positionY: Float): Float {\n        if (currentScale < 1) {\n            return (originalHeight / 2 - halfHeight).toFloat()\n        }\n        val maxPositionY = halfHeight * (currentScale - 1)\n        return positionY.coerceIn(-maxPositionY, maxPositionY)\n    }\n\n    private fun zoom(\n        fromRate: Float,\n        toRate: Float,\n        fromX: Float,\n        toX: Float,\n        fromY: Float,\n        toY: Float,\n    ) {\n        isZooming = true\n        val animatorSet = AnimatorSet()\n        val translationXAnimator = ValueAnimator.ofFloat(fromX, toX)\n        translationXAnimator.addUpdateListener { animation -> x = animation.animatedValue as Float }\n\n        val translationYAnimator = ValueAnimator.ofFloat(fromY, toY)\n        translationYAnimator.addUpdateListener { animation -> y = animation.animatedValue as Float }\n\n        val scaleAnimator = ValueAnimator.ofFloat(fromRate, toRate)\n        scaleAnimator.addUpdateListener { animation ->\n            currentScale = animation.animatedValue as Float\n            setScaleRate(currentScale)\n        }\n        animatorSet.playTogether(translationXAnimator, translationYAnimator, scaleAnimator)\n        animatorSet.duration = ANIMATOR_DURATION_TIME.toLong()\n        animatorSet.interpolator = DecelerateInterpolator()\n        animatorSet.start()\n        animatorSet.doOnEnd {\n            isZooming = false\n            currentScale = toRate\n        }\n    }\n\n    fun zoomFling(velocityX: Int, velocityY: Int): Boolean {\n        if (currentScale <= 1f) return false\n\n        val distanceTimeFactor = 0.4f\n        val animatorSet = AnimatorSet()\n\n        if (velocityX != 0) {\n            val dx = (distanceTimeFactor * velocityX / 2)\n            val newX = getPositionX(x + dx)\n            val translationXAnimator = ValueAnimator.ofFloat(x, newX)\n            translationXAnimator.addUpdateListener { animation ->\n                x = getPositionX(animation.animatedValue as Float)\n            }\n            animatorSet.play(translationXAnimator)\n        }\n        if (velocityY != 0 && (atFirstPosition || atLastPosition)) {\n            val dy = (distanceTimeFactor * velocityY / 2)\n            val newY = getPositionY(y + dy)\n            val translationYAnimator = ValueAnimator.ofFloat(y, newY)\n            translationYAnimator.addUpdateListener { animation ->\n                y = getPositionY(animation.animatedValue as Float)\n            }\n            animatorSet.play(translationYAnimator)\n        }\n\n        animatorSet.duration = 400\n        animatorSet.interpolator = DecelerateInterpolator()\n        animatorSet.start()\n\n        return true\n    }\n\n    fun resetZoom() {\n        zoom(currentScale, DEFAULT_RATE, x, 0f, y, 0f)\n    }\n\n    private fun zoomScrollBy(dx: Int, dy: Int) {\n        if (dx != 0) {\n            x = getPositionX(x + dx)\n        }\n        if (dy != 0) {\n            y = getPositionY(y + dy)\n        }\n    }\n\n    private fun setScaleRate(rate: Float) {\n        scaleX = rate\n        scaleY = rate\n    }\n\n    fun onScale(scaleFactor: Float) {\n        currentScale *= scaleFactor\n        currentScale = currentScale.coerceIn(\n            MIN_RATE,\n            MAX_SCALE_RATE,\n        )\n\n        setScaleRate(currentScale)\n\n        layoutParams.height = if (currentScale < 1) {\n            (originalHeight / currentScale).toInt()\n        } else {\n            originalHeight\n        }\n        halfHeight = layoutParams.height / 2\n\n        if (currentScale != DEFAULT_RATE) {\n            x = getPositionX(x)\n            y = getPositionY(y)\n        } else {\n            x = 0f\n            y = 0f\n        }\n\n        requestLayout()\n    }\n\n    fun onScaleBegin() {\n        if (detector.isDoubleTapping) {\n            detector.isQuickScaling = true\n        }\n    }\n\n    fun onScaleEnd() {\n        if (scaleX < MIN_RATE) {\n            zoom(currentScale, MIN_RATE, x, 0f, y, 0f)\n        }\n    }\n\n    inner class GestureListener : GestureDetectorWithLongTap.Listener() {\n\n        override fun onSingleTapConfirmed(ev: MotionEvent): Boolean {\n            tapListener?.invoke(ev)\n            return false\n        }\n\n        override fun onDoubleTap(ev: MotionEvent): Boolean {\n            detector.isDoubleTapping = true\n            return false\n        }\n\n        fun onDoubleTapConfirmed(ev: MotionEvent) {\n            if (!isZooming && doubleTapZoom) {\n                if (scaleX != DEFAULT_RATE) {\n                    zoom(currentScale, DEFAULT_RATE, x, 0f, y, 0f)\n                } else {\n                    val toScale = 2f\n                    val toX = (halfWidth - ev.x) * (toScale - 1)\n                    val toY = (halfHeight - ev.y) * (toScale - 1)\n                    zoom(DEFAULT_RATE, toScale, 0f, toX, 0f, toY)\n                }\n            }\n        }\n\n        override fun onLongTapConfirmed(ev: MotionEvent) {\n            if (longTapListener?.invoke(ev) == true) {\n                performHapticFeedback(HapticFeedbackConstants.LONG_PRESS)\n            }\n        }\n    }\n\n    inner class Detector : GestureDetectorWithLongTap(context, listener) {\n\n        private var scrollPointerId = 0\n        private var downX = 0\n        private var downY = 0\n        private val touchSlop = ViewConfiguration.get(context).scaledTouchSlop\n        private var isZoomDragging = false\n        var isDoubleTapping = false\n        var isQuickScaling = false\n        override fun onTouchEvent(ev: MotionEvent): Boolean {\n            val action = ev.actionMasked\n            val actionIndex = ev.actionIndex\n\n            when (action) {\n                MotionEvent.ACTION_DOWN -> {\n                    scrollPointerId = ev.getPointerId(0)\n                    downX = (ev.x + 0.5f).toInt()\n                    downY = (ev.y + 0.5f).toInt()\n                }\n\n                MotionEvent.ACTION_POINTER_DOWN -> {\n                    scrollPointerId = ev.getPointerId(actionIndex)\n                    downX = (ev.getX(actionIndex) + 0.5f).toInt()\n                    downY = (ev.getY(actionIndex) + 0.5f).toInt()\n                }\n\n                MotionEvent.ACTION_MOVE -> {\n                    if (disableMangaScale) {\n                        return super.onTouchEvent(ev)\n                    }\n                    if (isDoubleTapping && isQuickScaling) {\n                        return true\n                    }\n\n                    val index = ev.findPointerIndex(scrollPointerId)\n                    if (index < 0) {\n                        return false\n                    }\n\n                    val x = (ev.getX(index) + 0.5f).toInt()\n                    val y = (ev.getY(index) + 0.5f).toInt()\n                    var dx = x - downX\n                    var dy = if (atFirstPosition || atLastPosition) y - downY else 0\n\n                    if (!isZoomDragging && currentScale > 1f) {\n                        var startScroll = false\n\n                        if (abs(dx) > touchSlop) {\n                            if (dx < 0) {\n                                dx += touchSlop\n                            } else {\n                                dx -= touchSlop\n                            }\n                            startScroll = true\n                        }\n                        if (abs(dy) > touchSlop) {\n                            if (dy < 0) {\n                                dy += touchSlop\n                            } else {\n                                dy -= touchSlop\n                            }\n                            startScroll = true\n                        }\n\n                        if (startScroll) {\n                            isZoomDragging = true\n                        }\n                    }\n\n                    if (isZoomDragging) {\n                        zoomScrollBy(dx, dy)\n                    }\n                }\n\n                MotionEvent.ACTION_UP -> {\n                    if (isDoubleTapping && !isQuickScaling && !disableMangaScale) {\n                        listener.onDoubleTapConfirmed(ev)\n                    }\n                    isZoomDragging = false\n                    isDoubleTapping = false\n                    isQuickScaling = false\n                }\n\n                MotionEvent.ACTION_CANCEL -> {\n                    isZoomDragging = false\n                    isDoubleTapping = false\n                    isQuickScaling = false\n                }\n            }\n            return super.onTouchEvent(ev)\n        }\n    }\n\n    fun setPreScrollListener(iComicPreScroll: IComicPreScroll) {\n        mPreScrollListener = iComicPreScroll\n    }\n\n    fun setNestedPreScrollListener(iComicPreScroll: IComicPreScroll) {\n        mNestedPreScrollListener = iComicPreScroll\n    }\n\n    fun interface IComicPreScroll {\n        fun onPreScrollListener(recyclerView: RecyclerView, dx: Int, dy: Int, position: Int)\n    }\n}\n\nprivate const val ANIMATOR_DURATION_TIME = 200\nprivate const val MIN_RATE = 0.5f\nprivate const val DEFAULT_RATE = 1f\nprivate const val MAX_SCALE_RATE = 3f\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/book/read/BaseReadBookActivity.kt",
    "content": "package io.legado.app.ui.book.read\n\nimport android.annotation.SuppressLint\nimport android.app.DatePickerDialog\nimport android.content.pm.ActivityInfo\nimport android.os.Build\nimport android.os.Bundle\nimport android.view.KeyEvent\nimport android.view.View\nimport android.view.WindowInsets\nimport android.view.WindowManager\nimport androidx.activity.viewModels\nimport androidx.core.view.WindowInsetsCompat\nimport androidx.core.view.isVisible\nimport androidx.core.view.updateLayoutParams\nimport io.legado.app.R\nimport io.legado.app.base.VMBaseActivity\nimport io.legado.app.constant.AppConst.charsets\nimport io.legado.app.constant.PreferKey\nimport io.legado.app.databinding.ActivityBookReadBinding\nimport io.legado.app.databinding.DialogDownloadChoiceBinding\nimport io.legado.app.databinding.DialogEditTextBinding\nimport io.legado.app.databinding.DialogSimulatedReadingBinding\nimport io.legado.app.help.config.AppConfig\nimport io.legado.app.help.config.LocalConfig\nimport io.legado.app.help.config.ReadBookConfig\nimport io.legado.app.lib.dialogs.alert\nimport io.legado.app.lib.dialogs.selector\nimport io.legado.app.lib.theme.ThemeStore\nimport io.legado.app.lib.theme.bottomBackground\nimport io.legado.app.model.CacheBook\nimport io.legado.app.model.ReadBook\nimport io.legado.app.ui.book.read.config.BgTextConfigDialog\nimport io.legado.app.ui.book.read.config.ClickActionConfigDialog\nimport io.legado.app.ui.book.read.config.PaddingConfigDialog\nimport io.legado.app.ui.book.read.config.PageKeyDialog\nimport io.legado.app.ui.file.HandleFileContract\nimport io.legado.app.utils.ColorUtils\nimport io.legado.app.utils.FileDoc\nimport io.legado.app.utils.find\nimport io.legado.app.utils.getPrefString\nimport io.legado.app.utils.gone\nimport io.legado.app.utils.isTv\nimport io.legado.app.utils.setLightStatusBar\nimport io.legado.app.utils.setNavigationBarColorAuto\nimport io.legado.app.utils.setOnApplyWindowInsetsListenerCompat\nimport io.legado.app.utils.showDialogFragment\nimport io.legado.app.utils.viewbindingdelegate.viewBinding\nimport java.time.LocalDate\nimport java.time.format.DateTimeFormatter\n\n/**\n * 阅读界面\n */\nabstract class BaseReadBookActivity :\n    VMBaseActivity<ActivityBookReadBinding, ReadBookViewModel>(imageBg = false) {\n\n    override val binding by viewBinding(ActivityBookReadBinding::inflate)\n    override val viewModel by viewModels<ReadBookViewModel>()\n    protected val menuLayoutIsVisible\n        get() = bottomDialog > 0 || binding.readMenu.isVisible || binding.searchMenu.bottomMenuVisible\n\n    var bottomDialog = 0\n        set(value) {\n            if (field != value) {\n                field = value\n                onBottomDialogChange()\n            }\n        }\n    private val selectBookFolderResult = registerForActivityResult(HandleFileContract()) {\n        it.uri?.let { uri ->\n            ReadBook.book?.let { book ->\n                FileDoc.fromUri(uri, true).find(book.originName)?.let { doc ->\n                    book.bookUrl = doc.uri.toString()\n                    book.save()\n                    viewModel.loadChapterList(book)\n                } ?: ReadBook.upMsg(\"找不到文件\")\n            }\n        } ?: ReadBook.upMsg(\"没有权限访问\")\n    }\n\n    override fun onCreate(savedInstanceState: Bundle?) {\n        ReadBook.msg = null\n        setOrientation()\n        upLayoutInDisplayCutoutMode()\n        super.onCreate(savedInstanceState)\n        binding.navigationBar.setOnApplyWindowInsetsListenerCompat { view, windowInsets ->\n            val insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())\n            view.updateLayoutParams {\n                height = insets.bottom\n            }\n            windowInsets\n        }\n    }\n\n    override fun onActivityCreated(savedInstanceState: Bundle?) {\n        binding.navigationBar.setBackgroundColor(bottomBackground)\n        viewModel.permissionDenialLiveData.observe(this) {\n            selectBookFolderResult.launch {\n                mode = HandleFileContract.DIR_SYS\n                title = \"选择书籍所在文件夹\"\n            }\n        }\n        if (!LocalConfig.readHelpVersionIsLast) {\n            if (isTv) {\n                showCustomPageKeyConfig()\n            } else {\n                showClickRegionalConfig()\n            }\n        }\n    }\n\n    private fun onBottomDialogChange() {\n        when (bottomDialog) {\n            0 -> onMenuHide()\n            1 -> onMenuShow()\n        }\n    }\n\n    open fun onMenuShow() {\n\n    }\n\n    open fun onMenuHide() {\n\n    }\n\n    fun showPaddingConfig() {\n        showDialogFragment<PaddingConfigDialog>()\n    }\n\n    fun showBgTextConfig() {\n        showDialogFragment<BgTextConfigDialog>()\n    }\n\n    fun showClickRegionalConfig() {\n        showDialogFragment<ClickActionConfigDialog>()\n    }\n\n    private fun showCustomPageKeyConfig() {\n        PageKeyDialog(this).show()\n    }\n\n    /**\n     * 屏幕方向\n     */\n    @SuppressLint(\"SourceLockedOrientationActivity\")\n    fun setOrientation() {\n        when (AppConfig.screenOrientation) {\n            \"0\" -> requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED\n            \"1\" -> requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT\n            \"2\" -> requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE\n            \"3\" -> requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_SENSOR\n            \"4\" -> requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_REVERSE_PORTRAIT\n        }\n    }\n\n    /**\n     * 更新状态栏,导航栏\n     */\n    fun upSystemUiVisibility(\n        isInMultiWindow: Boolean,\n        toolBarHide: Boolean = true,\n        useBgMeanColor: Boolean = false\n    ) {\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {\n            window.insetsController?.run {\n                if (toolBarHide && ReadBookConfig.hideNavigationBar) {\n                    hide(WindowInsets.Type.navigationBars())\n                } else {\n                    show(WindowInsets.Type.navigationBars())\n                }\n                if (toolBarHide && ReadBookConfig.hideStatusBar) {\n                    hide(WindowInsets.Type.statusBars())\n                } else {\n                    show(WindowInsets.Type.statusBars())\n                }\n            }\n        }\n        upSystemUiVisibilityO(isInMultiWindow, toolBarHide)\n        if (toolBarHide) {\n            setLightStatusBar(ReadBookConfig.durConfig.curStatusIconDark())\n        } else {\n            val statusBarColor =\n                if (AppConfig.readBarStyleFollowPage\n                    && ReadBookConfig.durConfig.curBgType() == 0\n                    || useBgMeanColor\n                ) {\n                    ReadBookConfig.bgMeanColor\n                } else {\n                    ThemeStore.statusBarColor(this, AppConfig.isTransparentStatusBar)\n                }\n            setLightStatusBar(ColorUtils.isColorLight(statusBarColor))\n        }\n    }\n\n    @Suppress(\"DEPRECATION\")\n    private fun upSystemUiVisibilityO(\n        isInMultiWindow: Boolean,\n        toolBarHide: Boolean = true\n    ) {\n        var flag = (View.SYSTEM_UI_FLAG_LAYOUT_STABLE\n                or View.SYSTEM_UI_FLAG_IMMERSIVE\n                or View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY)\n        if (!isInMultiWindow) {\n            flag = flag or View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN\n        }\n        if (ReadBookConfig.hideNavigationBar) {\n            flag = flag or View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION\n            if (toolBarHide) {\n                flag = flag or View.SYSTEM_UI_FLAG_HIDE_NAVIGATION\n            }\n        }\n        if (ReadBookConfig.hideStatusBar && toolBarHide) {\n            flag = flag or View.SYSTEM_UI_FLAG_FULLSCREEN\n        }\n        window.decorView.systemUiVisibility = flag\n    }\n\n    override fun upNavigationBarColor() {\n        upNavigationBar()\n        when {\n            binding.readMenu.isVisible -> super.upNavigationBarColor()\n            binding.searchMenu.bottomMenuVisible -> super.upNavigationBarColor()\n            bottomDialog > 0 -> super.upNavigationBarColor()\n            !AppConfig.immNavigationBar -> super.upNavigationBarColor()\n            else -> setNavigationBarColorAuto(ReadBookConfig.bgMeanColor)\n        }\n    }\n\n    @SuppressLint(\"RtlHardcoded\")\n    private fun upNavigationBar() {\n        binding.navigationBar.gone(!menuLayoutIsVisible)\n    }\n\n    /**\n     * 保持亮屏\n     */\n    fun keepScreenOn(on: Boolean) {\n        val isScreenOn =\n            (window.attributes.flags and WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) != 0\n        if (on == isScreenOn) return\n        if (on) {\n            window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)\n        } else {\n            window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)\n        }\n    }\n\n    /**\n     * 适配刘海\n     */\n    private fun upLayoutInDisplayCutoutMode() {\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {\n            window.attributes = window.attributes.apply {\n                layoutInDisplayCutoutMode = if (ReadBookConfig.readBodyToLh) {\n                    WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_SHORT_EDGES\n                } else {\n                    WindowManager.LayoutParams.LAYOUT_IN_DISPLAY_CUTOUT_MODE_NEVER\n                }\n            }\n        }\n    }\n\n    @SuppressLint(\"InflateParams\", \"SetTextI18n\")\n    fun showDownloadDialog() {\n        ReadBook.book?.let { book ->\n            alert(titleResource = R.string.offline_cache) {\n                val alertBinding = DialogDownloadChoiceBinding.inflate(layoutInflater).apply {\n                    editStart.setText((book.durChapterIndex + 1).toString())\n                    editEnd.setText(book.totalChapterNum.toString())\n                }\n                customView { alertBinding.root }\n                okButton {\n                    alertBinding.run {\n                        val start = editStart.text!!.toString().let {\n                            if (it.isEmpty()) 0 else it.toInt()\n                        }\n                        val end = editEnd.text!!.toString().let {\n                            if (it.isEmpty()) book.totalChapterNum else it.toInt()\n                        }\n                        CacheBook.start(this@BaseReadBookActivity, book, start - 1, end - 1)\n                    }\n                }\n                cancelButton()\n            }\n        }\n    }\n\n    fun showSimulatedReading() {\n        val book = ReadBook.book ?: return\n        val dateFormatter = DateTimeFormatter.ofPattern(\"yyyy-MM-dd\")\n        val alertBinding = DialogSimulatedReadingBinding.inflate(layoutInflater).apply {\n            srEnabled.isChecked = book.getReadSimulating()\n            editStart.setText(book.getStartChapter().toString())\n            editNum.setText(book.getDailyChapters().toString())\n            startDate.setText(book.getStartDate()?.format(dateFormatter))\n            startDate.isFocusable = false // 设置为false，不允许获得焦点\n            startDate.isCursorVisible = false // 不显示光标\n            startDate.setOnClickListener {\n                // 获取当前日期\n                val localStartDate = LocalDate.parse(startDate.text)\n                // 创建 DatePickerDialog\n                val datePickerDialog = DatePickerDialog(\n                    root.context,\n                    { _, yy, mm, dayOfMonth ->\n                        // 使用Java 8的日期和时间API来格式化日期\n                        val date = LocalDate.of(yy, mm + 1, dayOfMonth) // Java 8的LocalDate，月份从1开始\n                        val formattedDate = date.format(dateFormatter)\n                        startDate.setText(formattedDate)\n                    }, localStartDate.year,\n                    localStartDate.monthValue - 1,\n                    localStartDate.dayOfMonth\n                )\n                datePickerDialog.show()\n            }\n        }\n        alert(titleResource = R.string.simulated_reading) {\n            customView { alertBinding.root }\n            okButton {\n                alertBinding.run {\n                    val start = editStart.text!!.toString().let {\n                        if (it.isEmpty()) 0 else it.toInt()\n                    }\n                    val num = editNum.text!!.toString().let {\n                        if (it.isEmpty()) book.totalChapterNum else it.toInt()\n                    }\n                    val enabled = srEnabled.isChecked\n                    val date = startDate.text!!.toString().let {\n                        if (it.isEmpty()) LocalDate.now()\n                        else LocalDate.parse(it, dateFormatter)\n                    }\n                    book.setStartDate(date)\n                    book.setDailyChapters(num)\n                    book.setStartChapter(start)\n                    book.setReadSimulating(enabled)\n                    book.save()\n                    ReadBook.clearTextChapter()\n                    viewModel.initData(intent)\n                }\n            }\n            cancelButton()\n        }\n    }\n\n    fun showCharsetConfig() {\n        alert(R.string.set_charset) {\n            val alertBinding = DialogEditTextBinding.inflate(layoutInflater).apply {\n                editView.hint = \"charset\"\n                editView.setFilterValues(charsets)\n                editView.setText(ReadBook.book?.charset)\n            }\n            customView { alertBinding.root }\n            okButton {\n                alertBinding.editView.text?.toString()?.let {\n                    ReadBook.setCharset(it)\n                }\n            }\n            cancelButton()\n        }\n    }\n\n    fun showPageAnimConfig(success: () -> Unit) {\n        val items = arrayListOf<String>()\n        items.add(getString(R.string.btn_default_s))\n        items.add(getString(R.string.page_anim_cover))\n        items.add(getString(R.string.page_anim_slide))\n        items.add(getString(R.string.page_anim_simulation))\n        items.add(getString(R.string.page_anim_scroll))\n        items.add(getString(R.string.page_anim_none))\n        selector(R.string.page_anim, items) { _, i ->\n            ReadBook.book?.setPageAnim(i - 1)\n            success()\n        }\n    }\n\n    fun isPrevKey(keyCode: Int): Boolean {\n        if (keyCode == KeyEvent.KEYCODE_UNKNOWN) {\n            return false\n        }\n        val prevKeysStr = getPrefString(PreferKey.prevKeys)\n        return prevKeysStr?.split(\",\")?.contains(keyCode.toString()) ?: false\n    }\n\n    fun isNextKey(keyCode: Int): Boolean {\n        if (keyCode == KeyEvent.KEYCODE_UNKNOWN) {\n            return false\n        }\n        val nextKeysStr = getPrefString(PreferKey.nextKeys)\n        return nextKeysStr?.split(\",\")?.contains(keyCode.toString()) ?: false\n    }\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/book/read/ContentEditDialog.kt",
    "content": "package io.legado.app.ui.book.read\n\nimport android.app.Application\nimport android.content.DialogInterface\nimport android.os.Bundle\nimport android.view.View\nimport android.view.ViewGroup\nimport androidx.fragment.app.viewModels\nimport androidx.lifecycle.MutableLiveData\nimport androidx.lifecycle.lifecycleScope\nimport io.legado.app.R\nimport io.legado.app.base.BaseDialogFragment\nimport io.legado.app.base.BaseViewModel\nimport io.legado.app.data.appDb\nimport io.legado.app.data.entities.BookChapter\nimport io.legado.app.databinding.DialogContentEditBinding\nimport io.legado.app.databinding.DialogEditTextBinding\nimport io.legado.app.help.book.BookHelp\nimport io.legado.app.help.book.ContentProcessor\nimport io.legado.app.help.book.isLocal\nimport io.legado.app.help.coroutine.Coroutine\nimport io.legado.app.lib.dialogs.alert\nimport io.legado.app.lib.theme.primaryColor\nimport io.legado.app.model.ReadBook\nimport io.legado.app.model.webBook.WebBook\nimport io.legado.app.utils.applyTint\nimport io.legado.app.utils.sendToClip\nimport io.legado.app.utils.setLayout\nimport io.legado.app.utils.viewbindingdelegate.viewBinding\nimport kotlinx.coroutines.Dispatchers.IO\nimport kotlinx.coroutines.launch\nimport kotlinx.coroutines.withContext\n\n/**\n * 内容编辑\n */\nclass ContentEditDialog : BaseDialogFragment(R.layout.dialog_content_edit) {\n\n    val binding by viewBinding(DialogContentEditBinding::bind)\n    val viewModel by viewModels<ContentEditViewModel>()\n\n    override fun onStart() {\n        super.onStart()\n        setLayout(1f, ViewGroup.LayoutParams.MATCH_PARENT)\n    }\n\n    override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) {\n        binding.toolBar.setBackgroundColor(primaryColor)\n        binding.toolBar.title = ReadBook.curTextChapter?.title\n        initMenu()\n        binding.toolBar.setOnClickListener {\n            lifecycleScope.launch {\n                val book = ReadBook.book ?: return@launch\n                val chapter = withContext(IO) {\n                    appDb.bookChapterDao.getChapter(book.bookUrl, ReadBook.durChapterIndex)\n                } ?: return@launch\n                editTitle(chapter)\n            }\n        }\n        viewModel.loadStateLiveData.observe(viewLifecycleOwner) {\n            if (it) {\n                binding.rlLoading.visible()\n            } else {\n                binding.rlLoading.gone()\n            }\n        }\n        viewModel.initContent {\n            binding.contentView.setText(it)\n            binding.contentView.post {\n                binding.contentView.apply {\n                    val lineIndex = layout.getLineForOffset(ReadBook.durChapterPos)\n                    val lineHeight = layout.getLineTop(lineIndex)\n                    scrollTo(0, lineHeight)\n                }\n            }\n        }\n    }\n\n    private fun initMenu() {\n        binding.toolBar.inflateMenu(R.menu.content_edit)\n        binding.toolBar.menu.applyTint(requireContext())\n        binding.toolBar.setOnMenuItemClickListener {\n            when (it.itemId) {\n                R.id.menu_save -> {\n                    save()\n                    dismiss()\n                }\n                R.id.menu_reset -> viewModel.initContent(true) { content ->\n                    binding.contentView.setText(content)\n                    ReadBook.loadContent(ReadBook.durChapterIndex, resetPageOffset = false)\n                }\n                R.id.menu_copy_all -> requireContext()\n                    .sendToClip(\"${binding.toolBar.title}\\n${binding.contentView.text}\")\n            }\n            return@setOnMenuItemClickListener true\n        }\n    }\n\n    private fun editTitle(chapter: BookChapter) {\n        alert {\n            setTitle(R.string.edit)\n            val alertBinding = DialogEditTextBinding.inflate(layoutInflater)\n            alertBinding.editView.setText(chapter.title)\n            setCustomView(alertBinding.root)\n            okButton {\n                chapter.title = alertBinding.editView.text.toString()\n                lifecycleScope.launch {\n                    withContext(IO) {\n                        appDb.bookChapterDao.update(chapter)\n                    }\n                    binding.toolBar.title = chapter.getDisplayTitle()\n                    ReadBook.loadContent(ReadBook.durChapterIndex, resetPageOffset = false)\n                }\n            }\n        }\n    }\n\n    override fun onCancel(dialog: DialogInterface) {\n        super.onCancel(dialog)\n        save()\n    }\n\n    private fun save() {\n        val content = binding.contentView.text?.toString() ?: return\n        Coroutine.async {\n            val book = ReadBook.book ?: return@async\n            val chapter = appDb.bookChapterDao\n                .getChapter(book.bookUrl, ReadBook.durChapterIndex)\n                ?: return@async\n            BookHelp.saveText(book, chapter, content)\n            ReadBook.loadContent(ReadBook.durChapterIndex, resetPageOffset = false)\n        }\n    }\n\n    class ContentEditViewModel(application: Application) : BaseViewModel(application) {\n        val loadStateLiveData = MutableLiveData<Boolean>()\n        var content: String? = null\n\n        fun initContent(reset: Boolean = false, success: (String) -> Unit) {\n            execute {\n                val book = ReadBook.book ?: return@execute null\n                val chapter = appDb.bookChapterDao\n                    .getChapter(book.bookUrl, ReadBook.durChapterIndex)\n                    ?: return@execute null\n                if (reset) {\n                    content = null\n                    BookHelp.delContent(book, chapter)\n                    if (!book.isLocal) ReadBook.bookSource?.let { bookSource ->\n                        WebBook.getContentAwait(bookSource, book, chapter)\n                    }\n                }\n                return@execute content ?: let {\n                    val contentProcessor = ContentProcessor.get(book.name, book.origin)\n                    val content = BookHelp.getContent(book, chapter) ?: return@let null\n                    contentProcessor.getContent(book, chapter, content, includeTitle = false)\n                        .toString()\n                }\n            }.onStart {\n                loadStateLiveData.postValue(true)\n            }.onSuccess {\n                content = it\n                success.invoke(it ?: \"\")\n            }.onFinally {\n                loadStateLiveData.postValue(false)\n            }\n        }\n\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/book/read/EffectiveReplacesDialog.kt",
    "content": "package io.legado.app.ui.book.read\n\nimport android.content.Context\nimport android.content.DialogInterface\nimport android.os.Bundle\nimport android.view.View\nimport android.view.ViewGroup\nimport androidx.activity.result.contract.ActivityResultContracts\nimport androidx.appcompat.app.AppCompatActivity\nimport androidx.fragment.app.activityViewModels\nimport androidx.recyclerview.widget.LinearLayoutManager\nimport io.legado.app.R\nimport io.legado.app.base.BaseDialogFragment\nimport io.legado.app.base.adapter.ItemViewHolder\nimport io.legado.app.base.adapter.RecyclerAdapter\nimport io.legado.app.data.entities.ReplaceRule\nimport io.legado.app.databinding.DialogRecyclerViewBinding\nimport io.legado.app.databinding.Item1lineTextBinding\nimport io.legado.app.help.config.AppConfig\nimport io.legado.app.lib.dialogs.alert\nimport io.legado.app.lib.theme.primaryColor\nimport io.legado.app.model.ReadBook\nimport io.legado.app.ui.replace.edit.ReplaceEditActivity\nimport io.legado.app.utils.setLayout\nimport io.legado.app.utils.viewbindingdelegate.viewBinding\n\n/**\n * 起效的替换规则\n */\nclass EffectiveReplacesDialog : BaseDialogFragment(R.layout.dialog_recycler_view) {\n\n    private val binding by viewBinding(DialogRecyclerViewBinding::bind)\n    private val viewModel by activityViewModels<ReadBookViewModel>()\n    private val adapter by lazy { ReplaceAdapter(requireContext()) }\n    private val chineseConvert by lazy { ReplaceRule(0, \"繁简转换\") }\n\n    private var isEdit = false\n\n    private val editActivity =\n        registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {\n            if (it.resultCode == AppCompatActivity.RESULT_OK) {\n                isEdit = true\n            }\n        }\n\n    override fun onStart() {\n        super.onStart()\n        setLayout(0.9f, ViewGroup.LayoutParams.WRAP_CONTENT)\n    }\n\n    override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) {\n        binding.run {\n            toolBar.setBackgroundColor(primaryColor)\n            toolBar.setTitle(R.string.effective_replaces)\n            recyclerView.layoutManager = LinearLayoutManager(requireContext())\n            recyclerView.adapter = adapter\n        }\n        val effectiveReplaceRules = ReadBook.curTextChapter?.effectiveReplaceRules ?: emptyList()\n        if (AppConfig.chineseConverterType > 0) {\n            adapter.setItems(effectiveReplaceRules + chineseConvert)\n        } else {\n            adapter.setItems(effectiveReplaceRules)\n        }\n    }\n\n    override fun onDismiss(dialog: DialogInterface) {\n        super.onDismiss(dialog)\n        if (isEdit) {\n            viewModel.replaceRuleChanged()\n        }\n    }\n    \n    private fun showChineseConvertAlert() {\n        alert(titleResource = R.string.chinese_converter) {\n            items(resources.getStringArray(R.array.chinese_mode).toList()) { _, i ->\n                if (AppConfig.chineseConverterType != i) {\n                    AppConfig.chineseConverterType = i\n                    isEdit = true\n                }\n            }\n        }\n    }\n\n    private inner class ReplaceAdapter(context: Context) :\n        RecyclerAdapter<ReplaceRule, Item1lineTextBinding>(context) {\n\n        override fun getViewBinding(parent: ViewGroup): Item1lineTextBinding {\n            return Item1lineTextBinding.inflate(inflater, parent, false)\n        }\n\n        override fun registerListener(holder: ItemViewHolder, binding: Item1lineTextBinding) {\n            binding.root.setOnClickListener {\n                getItem(holder.layoutPosition)?.let { item ->\n                    if (item == chineseConvert) {\n                        showChineseConvertAlert()\n                        return@let\n                    }\n                    editActivity.launch(ReplaceEditActivity.startIntent(requireContext(), item.id))\n                }\n            }\n        }\n\n        override fun convert(\n            holder: ItemViewHolder,\n            binding: Item1lineTextBinding,\n            item: ReplaceRule,\n            payloads: MutableList<Any>\n        ) {\n            binding.textView.text = item.name\n        }\n\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/book/read/MangaMenu.kt",
    "content": "package io.legado.app.ui.book.read\n\nimport android.annotation.SuppressLint\nimport android.content.Context\nimport android.graphics.drawable.GradientDrawable\nimport android.util.AttributeSet\nimport android.view.LayoutInflater\nimport android.view.animation.Animation\nimport android.widget.FrameLayout\nimport android.widget.SeekBar\nimport androidx.core.view.isGone\nimport androidx.core.view.isVisible\nimport io.legado.app.R\nimport io.legado.app.databinding.ViewMangaMenuBinding\nimport io.legado.app.help.config.AppConfig\nimport io.legado.app.help.source.getSourceType\nimport io.legado.app.lib.dialogs.alert\nimport io.legado.app.lib.theme.bottomBackground\nimport io.legado.app.model.ReadBook\nimport io.legado.app.model.ReadManga\nimport io.legado.app.ui.browser.WebViewActivity\nimport io.legado.app.ui.widget.seekbar.SeekBarChangeListener\nimport io.legado.app.utils.ColorUtils\nimport io.legado.app.utils.ConstraintModify\nimport io.legado.app.utils.activity\nimport io.legado.app.utils.applyNavigationBarPadding\nimport io.legado.app.utils.dpToPx\nimport io.legado.app.utils.gone\nimport io.legado.app.utils.invisible\nimport io.legado.app.utils.loadAnimation\nimport io.legado.app.utils.modifyBegin\nimport io.legado.app.utils.openUrl\nimport io.legado.app.utils.startActivity\nimport io.legado.app.utils.visible\n\nclass MangaMenu @JvmOverloads constructor(\n    context: Context,\n    attrs: AttributeSet? = null,\n) : FrameLayout(context, attrs) {\n    private val binding = ViewMangaMenuBinding.inflate(LayoutInflater.from(context), this, true)\n    private val callBack: CallBack get() = activity as CallBack\n    var canShowMenu: Boolean = false\n    private val menuTopIn: Animation by lazy {\n        loadAnimation(context, R.anim.anim_readbook_top_in)\n    }\n    private val menuTopOut: Animation by lazy {\n        loadAnimation(context, R.anim.anim_readbook_top_out)\n    }\n    private val menuBottomIn: Animation by lazy {\n        loadAnimation(context, R.anim.anim_readbook_bottom_in)\n    }\n    private val menuBottomOut: Animation by lazy {\n        loadAnimation(context, R.anim.anim_readbook_bottom_out)\n    }\n    private var isMenuOutAnimating = false\n    private var bgColor = context.bottomBackground\n\n    private val menuOutListener = object : Animation.AnimationListener {\n        override fun onAnimationStart(animation: Animation) {\n            isMenuOutAnimating = true\n            binding.vwMenuBg.setOnClickListener(null)\n        }\n\n        override fun onAnimationEnd(animation: Animation) {\n            this@MangaMenu.invisible()\n            binding.titleBar.invisible()\n            binding.bottomMenu.invisible()\n            isMenuOutAnimating = false\n            canShowMenu = false\n            callBack.upSystemUiVisibility(false)\n        }\n\n        override fun onAnimationRepeat(animation: Animation) = Unit\n    }\n    private val menuInListener = object : Animation.AnimationListener {\n        override fun onAnimationStart(animation: Animation) {\n            binding.tvSourceAction.text =\n                ReadManga.bookSource?.bookSourceName ?: context.getString(R.string.book_source)\n            callBack.upSystemUiVisibility(true)\n            binding.tvSourceAction.isGone = false\n        }\n\n        @SuppressLint(\"RtlHardcoded\")\n        override fun onAnimationEnd(animation: Animation) {\n            binding.run {\n                vwMenuBg.setOnClickListener { runMenuOut() }\n            }\n        }\n\n        override fun onAnimationRepeat(animation: Animation) = Unit\n    }\n\n    init {\n        initView()\n        bindEvent()\n    }\n\n    private fun initView() = binding.run {\n        initAnimation()\n        val brightnessBackground = GradientDrawable()\n        brightnessBackground.cornerRadius = 5F.dpToPx()\n        brightnessBackground.setColor(ColorUtils.adjustAlpha(bgColor, 0.5f))\n        if (AppConfig.isEInkMode) {\n            titleBar.setBackgroundResource(R.drawable.bg_eink_border_bottom)\n            bottomMenu.setBackgroundResource(R.drawable.bg_eink_border_top)\n        } else {\n            bottomMenu.setBackgroundColor(bgColor)\n        }\n        if (AppConfig.showReadTitleBarAddition) {\n            titleBarAddition.visible()\n        } else {\n            titleBarAddition.gone()\n        }\n        upBrightnessVwPos()\n        /**\n         * 确保视图不被导航栏遮挡\n         */\n        bottomMenu.applyNavigationBarPadding()\n    }\n\n    private fun upBrightnessVwPos() {\n        if (AppConfig.brightnessVwPos) {\n            binding.root.modifyBegin()\n                .clear(R.id.ll_brightness, ConstraintModify.Anchor.LEFT)\n                .rightToRightOf(R.id.ll_brightness, R.id.vw_menu_root)\n                .commit()\n        } else {\n            binding.root.modifyBegin()\n                .clear(R.id.ll_brightness, ConstraintModify.Anchor.RIGHT)\n                .leftToLeftOf(R.id.ll_brightness, R.id.vw_menu_root)\n                .commit()\n        }\n    }\n\n    private fun initAnimation() {\n        menuTopIn.setAnimationListener(menuInListener)\n        menuTopOut.setAnimationListener(menuOutListener)\n    }\n\n    fun runMenuOut(anim: Boolean = !AppConfig.isEInkMode) {\n        if (isMenuOutAnimating) {\n            return\n        }\n        if (this.isVisible) {\n            if (anim) {\n                binding.titleBar.startAnimation(menuTopOut)\n                binding.bottomMenu.startAnimation(menuBottomOut)\n            } else {\n                menuOutListener.onAnimationStart(menuBottomOut)\n                menuOutListener.onAnimationEnd(menuBottomOut)\n            }\n        }\n    }\n\n    fun runMenuIn(anim: Boolean = !AppConfig.isEInkMode) {\n        this.visible()\n        binding.titleBar.visible()\n        binding.bottomMenu.visible()\n        if (anim) {\n            binding.titleBar.startAnimation(menuTopIn)\n            binding.bottomMenu.startAnimation(menuBottomIn)\n        } else {\n            menuInListener.onAnimationStart(menuBottomIn)\n            menuInListener.onAnimationEnd(menuBottomIn)\n        }\n    }\n\n\n    private fun bindEvent() = binding.run {\n        vwMenuBg.setOnClickListener { runMenuOut() }\n        titleBar.toolbar.setOnClickListener {\n            callBack.openBookInfoActivity()\n        }\n        val chapterViewClickListener = OnClickListener {\n            if (AppConfig.readUrlInBrowser) {\n                context.openUrl(tvChapterUrl.text.toString().substringBefore(\",{\"))\n            } else {\n                context.startActivity<WebViewActivity> {\n                    val url = tvChapterUrl.text.toString()\n                    val bookSource = ReadBook.bookSource\n                    putExtra(\"title\", tvChapterName.text)\n                    putExtra(\"url\", url)\n                    putExtra(\"sourceOrigin\", bookSource?.bookSourceUrl)\n                    putExtra(\"sourceName\", bookSource?.bookSourceName)\n                    putExtra(\"sourceType\", bookSource?.getSourceType())\n                }\n            }\n        }\n        val chapterViewLongClickListener = OnLongClickListener {\n            context.alert(R.string.open_fun) {\n                setMessage(R.string.use_browser_open)\n                okButton {\n                    AppConfig.readUrlInBrowser = true\n                }\n                noButton {\n                    AppConfig.readUrlInBrowser = false\n                }\n            }\n            true\n        }\n        tvChapterName.setOnClickListener(chapterViewClickListener)\n        tvChapterName.setOnLongClickListener(chapterViewLongClickListener)\n        tvChapterUrl.setOnClickListener(chapterViewClickListener)\n        tvChapterUrl.setOnLongClickListener(chapterViewLongClickListener)\n\n        tvNext.setOnClickListener {\n            ReadManga.moveToNextChapter(true)\n        }\n        tvPre.setOnClickListener {\n            ReadManga.moveToPrevChapter(true)\n        }\n\n        seekReadPage.setOnSeekBarChangeListener(object : SeekBarChangeListener {\n            override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {\n                if (fromUser) {\n                    callBack.skipToPage(seekBar.progress)\n                }\n            }\n\n            override fun onStartTrackingTouch(seekBar: SeekBar) {\n                binding.vwMenuBg.setOnClickListener(null)\n            }\n\n            override fun onStopTrackingTouch(seekBar: SeekBar) {\n                binding.vwMenuBg.setOnClickListener { runMenuOut() }\n            }\n        })\n    }\n\n    fun upSeekBar(value: Int, count: Int) {\n        binding.seekReadPage.apply {\n            max = count.minus(1)\n            progress = value\n        }\n    }\n\n    interface CallBack {\n        fun openBookInfoActivity()\n        fun upSystemUiVisibility(menuIsVisible: Boolean)\n        fun skipToPage(index: Int)\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/book/read/ReadBookActivity.kt",
    "content": "package io.legado.app.ui.book.read\n\nimport android.annotation.SuppressLint\nimport android.content.Intent\nimport android.content.res.Configuration\nimport android.net.Uri\nimport android.os.Bundle\nimport android.os.Looper\nimport android.view.Gravity\nimport android.view.InputDevice\nimport android.view.KeyEvent\nimport android.view.Menu\nimport android.view.MenuItem\nimport android.view.MotionEvent\nimport android.view.View\nimport androidx.activity.addCallback\nimport androidx.activity.result.contract.ActivityResultContracts\nimport androidx.appcompat.app.AlertDialog\nimport androidx.appcompat.widget.PopupMenu\nimport androidx.core.view.get\nimport androidx.core.view.size\nimport androidx.lifecycle.lifecycleScope\nimport com.jaredrummler.android.colorpicker.ColorPickerDialogListener\nimport io.legado.app.BuildConfig\nimport io.legado.app.R\nimport io.legado.app.constant.AppConst\nimport io.legado.app.constant.AppLog\nimport io.legado.app.constant.BookType\nimport io.legado.app.constant.EventBus\nimport io.legado.app.constant.PreferKey\nimport io.legado.app.constant.Status\nimport io.legado.app.data.appDb\nimport io.legado.app.data.entities.Book\nimport io.legado.app.data.entities.BookChapter\nimport io.legado.app.data.entities.BookProgress\nimport io.legado.app.data.entities.BookSource\nimport io.legado.app.exception.NoStackTraceException\nimport io.legado.app.help.AppWebDav\nimport io.legado.app.help.IntentData\nimport io.legado.app.help.TTS\nimport io.legado.app.help.book.BookHelp\nimport io.legado.app.help.book.ContentProcessor\nimport io.legado.app.help.book.isAudio\nimport io.legado.app.help.book.isEpub\nimport io.legado.app.help.book.isLocal\nimport io.legado.app.help.book.isLocalTxt\nimport io.legado.app.help.book.isMobi\nimport io.legado.app.help.book.removeType\nimport io.legado.app.help.book.update\nimport io.legado.app.help.config.AppConfig\nimport io.legado.app.help.config.ReadBookConfig\nimport io.legado.app.help.config.ReadTipConfig\nimport io.legado.app.help.coroutine.Coroutine\nimport io.legado.app.help.source.getSourceType\nimport io.legado.app.help.storage.Backup\nimport io.legado.app.lib.dialogs.SelectItem\nimport io.legado.app.lib.dialogs.alert\nimport io.legado.app.lib.dialogs.selector\nimport io.legado.app.lib.theme.accentColor\nimport io.legado.app.model.ReadAloud\nimport io.legado.app.model.ReadBook\nimport io.legado.app.model.analyzeRule.AnalyzeRule\nimport io.legado.app.model.analyzeRule.AnalyzeRule.Companion.setChapter\nimport io.legado.app.model.analyzeRule.AnalyzeRule.Companion.setCoroutineContext\nimport io.legado.app.model.localBook.EpubFile\nimport io.legado.app.model.localBook.MobiFile\nimport io.legado.app.receiver.NetworkChangedListener\nimport io.legado.app.receiver.TimeBatteryReceiver\nimport io.legado.app.service.BaseReadAloudService\nimport io.legado.app.ui.about.AppLogDialog\nimport io.legado.app.ui.book.bookmark.BookmarkDialog\nimport io.legado.app.ui.book.changesource.ChangeBookSourceDialog\nimport io.legado.app.ui.book.changesource.ChangeChapterSourceDialog\nimport io.legado.app.ui.book.info.BookInfoActivity\nimport io.legado.app.ui.book.read.config.AutoReadDialog\nimport io.legado.app.ui.book.read.config.BgTextConfigDialog.Companion.BG_COLOR\nimport io.legado.app.ui.book.read.config.BgTextConfigDialog.Companion.TEXT_COLOR\nimport io.legado.app.ui.book.read.config.MoreConfigDialog\nimport io.legado.app.ui.book.read.config.ReadAloudDialog\nimport io.legado.app.ui.book.read.config.ReadStyleDialog\nimport io.legado.app.ui.book.read.config.TipConfigDialog.Companion.TIP_COLOR\nimport io.legado.app.ui.book.read.config.TipConfigDialog.Companion.TIP_DIVIDER_COLOR\nimport io.legado.app.ui.book.read.page.ContentTextView\nimport io.legado.app.ui.book.read.page.ReadView\nimport io.legado.app.ui.book.read.page.entities.PageDirection\nimport io.legado.app.ui.book.read.page.entities.TextPage\nimport io.legado.app.ui.book.read.page.provider.ChapterProvider\nimport io.legado.app.ui.book.read.page.provider.LayoutProgressListener\nimport io.legado.app.ui.book.searchContent.SearchContentActivity\nimport io.legado.app.ui.book.searchContent.SearchResult\nimport io.legado.app.ui.book.source.edit.BookSourceEditActivity\nimport io.legado.app.ui.book.toc.TocActivityResult\nimport io.legado.app.ui.book.toc.rule.TxtTocRuleDialog\nimport io.legado.app.ui.browser.WebViewActivity\nimport io.legado.app.ui.dict.DictDialog\nimport io.legado.app.ui.file.HandleFileContract\nimport io.legado.app.ui.login.SourceLoginActivity\nimport io.legado.app.ui.replace.ReplaceRuleActivity\nimport io.legado.app.ui.replace.edit.ReplaceEditActivity\nimport io.legado.app.ui.widget.PopupAction\nimport io.legado.app.ui.widget.dialog.PhotoDialog\nimport io.legado.app.utils.ACache\nimport io.legado.app.utils.Debounce\nimport io.legado.app.utils.LogUtils\nimport io.legado.app.utils.NetworkUtils\nimport io.legado.app.utils.StartActivityContract\nimport io.legado.app.utils.applyOpenTint\nimport io.legado.app.utils.buildMainHandler\nimport io.legado.app.utils.dismissDialogFragment\nimport io.legado.app.utils.getPrefBoolean\nimport io.legado.app.utils.getPrefString\nimport io.legado.app.utils.hexString\nimport io.legado.app.utils.iconItemOnLongClick\nimport io.legado.app.utils.invisible\nimport io.legado.app.utils.isAbsUrl\nimport io.legado.app.utils.isTrue\nimport io.legado.app.utils.launch\nimport io.legado.app.utils.navigationBarGravity\nimport io.legado.app.utils.observeEvent\nimport io.legado.app.utils.observeEventSticky\nimport io.legado.app.utils.postEvent\nimport io.legado.app.utils.showDialogFragment\nimport io.legado.app.utils.showHelp\nimport io.legado.app.utils.startActivity\nimport io.legado.app.utils.startActivityForBook\nimport io.legado.app.utils.sysScreenOffTime\nimport io.legado.app.utils.throttle\nimport io.legado.app.utils.toastOnUi\nimport io.legado.app.utils.visible\nimport kotlinx.coroutines.Dispatchers.IO\nimport kotlinx.coroutines.Dispatchers.Main\nimport kotlinx.coroutines.Job\nimport kotlinx.coroutines.delay\nimport kotlinx.coroutines.ensureActive\nimport kotlinx.coroutines.launch\nimport kotlinx.coroutines.withContext\n\n/**\n * 阅读界面\n */\nclass ReadBookActivity : BaseReadBookActivity(),\n    View.OnTouchListener,\n    ReadView.CallBack,\n    TextActionMenu.CallBack,\n    ContentTextView.CallBack,\n    PopupMenu.OnMenuItemClickListener,\n    ReadMenu.CallBack,\n    SearchMenu.CallBack,\n    ReadAloudDialog.CallBack,\n    ChangeBookSourceDialog.CallBack,\n    ChangeChapterSourceDialog.CallBack,\n    ReadBook.CallBack,\n    AutoReadDialog.CallBack,\n    TxtTocRuleDialog.CallBack,\n    ColorPickerDialogListener,\n    LayoutProgressListener {\n\n    private val tocActivity =\n        registerForActivityResult(TocActivityResult()) {\n            it?.let {\n                viewModel.openChapter(it.first, it.second)\n            }\n        }\n    private val sourceEditActivity =\n        registerForActivityResult(StartActivityContract(BookSourceEditActivity::class.java)) {\n            if (it.resultCode == RESULT_OK) {\n                viewModel.upBookSource {\n                    upMenuView()\n                }\n            }\n        }\n    private val replaceActivity =\n        registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {\n            if (it.resultCode == RESULT_OK) {\n                viewModel.replaceRuleChanged()\n            }\n        }\n    private val searchContentActivity =\n        registerForActivityResult(StartActivityContract(SearchContentActivity::class.java)) {\n            val data = it.data ?: return@registerForActivityResult\n            val key = data.getLongExtra(\"key\", System.currentTimeMillis())\n            val index = data.getIntExtra(\"index\", 0)\n            val searchResult = IntentData.get<SearchResult>(\"searchResult$key\")\n            val searchResultList = IntentData.get<List<SearchResult>>(\"searchResultList$key\")\n            if (searchResult != null && searchResultList != null) {\n                viewModel.searchContentQuery = searchResult.query\n                binding.searchMenu.upSearchResultList(searchResultList)\n                isShowingSearchResult = true\n                viewModel.searchResultIndex = index\n                binding.searchMenu.updateSearchResultIndex(index)\n                binding.searchMenu.selectedSearchResult?.let { currentResult ->\n                    ReadBook.saveCurrentBookProgress() //退出全文搜索恢复此时进度\n                    skipToSearch(currentResult)\n                    showActionMenu()\n                }\n            }\n        }\n    private val bookInfoActivity =\n        registerForActivityResult(StartActivityContract(BookInfoActivity::class.java)) {\n            if (it.resultCode == RESULT_OK) {\n                setResult(RESULT_DELETED)\n                super.finish()\n            } else {\n                ReadBook.loadOrUpContent()\n            }\n        }\n    private val selectImageDir = registerForActivityResult(HandleFileContract()) {\n        it.uri?.let { uri ->\n            ACache.get().put(AppConst.imagePathKey, uri.toString())\n            viewModel.saveImage(it.value, uri)\n        }\n    }\n    private var menu: Menu? = null\n    private var backupJob: Job? = null\n    private var tts: TTS? = null\n    val textActionMenu: TextActionMenu by lazy {\n        TextActionMenu(this, this)\n    }\n    private val popupAction: PopupAction by lazy {\n        PopupAction(this)\n    }\n    override val isInitFinish: Boolean get() = viewModel.isInitFinish\n    override val isScroll: Boolean get() = binding.readView.isScroll\n    private val isAutoPage get() = binding.readView.isAutoPage\n    override var isShowingSearchResult = false\n    override var isSelectingSearchResult = false\n        set(value) {\n            field = value && isShowingSearchResult\n        }\n    private val timeBatteryReceiver = TimeBatteryReceiver()\n    private var screenTimeOut: Long = 0\n    private var loadStates: Boolean = false\n    override val pageFactory get() = binding.readView.pageFactory\n    override val pageDelegate get() = binding.readView.pageDelegate\n    override val headerHeight: Int get() = binding.readView.curPage.headerHeight\n    private val nextPageDebounce by lazy { Debounce { keyPage(PageDirection.NEXT) } }\n    private val prevPageDebounce by lazy { Debounce { keyPage(PageDirection.PREV) } }\n    private var bookChanged = false\n    private var pageChanged = false\n    private val handler by lazy { buildMainHandler() }\n    private val screenOffRunnable by lazy { Runnable { keepScreenOn(false) } }\n    private val executor = ReadBook.executor\n    private val upSeekBarThrottle = throttle(200) {\n        runOnUiThread {\n            upSeekBarProgress()\n            binding.readMenu.upSeekBar()\n        }\n    }\n\n    //恢复跳转前进度对话框的交互结果\n    private var confirmRestoreProcess: Boolean? = null\n    private val networkChangedListener by lazy {\n        NetworkChangedListener(this)\n    }\n    private var justInitData: Boolean = false\n    private var syncDialog: AlertDialog? = null\n\n    @SuppressLint(\"ClickableViewAccessibility\")\n    override fun onActivityCreated(savedInstanceState: Bundle?) {\n        super.onActivityCreated(savedInstanceState)\n        binding.cursorLeft.setColorFilter(accentColor)\n        binding.cursorRight.setColorFilter(accentColor)\n        binding.cursorLeft.setOnTouchListener(this)\n        binding.cursorRight.setOnTouchListener(this)\n        window.setBackgroundDrawable(null)\n        upScreenTimeOut()\n        ReadBook.register(this)\n        onBackPressedDispatcher.addCallback(this) {\n            if (isShowingSearchResult) {\n                exitSearchMenu()\n                restoreLastBookProcess()\n                return@addCallback\n            }\n            //拦截返回供恢复阅读进度\n            if (ReadBook.lastBookProgress != null && confirmRestoreProcess != false) {\n                restoreLastBookProcess()\n                return@addCallback\n            }\n            if (BaseReadAloudService.isPlay()) {\n                ReadAloud.pause(this@ReadBookActivity)\n                toastOnUi(R.string.read_aloud_pause)\n                return@addCallback\n            }\n            if (isAutoPage) {\n                autoPageStop()\n                return@addCallback\n            }\n            if (getPrefBoolean(\"disableReturnKey\") && !menuLayoutIsVisible) {\n                return@addCallback\n            }\n            finish()\n        }\n    }\n\n    override fun onPostCreate(savedInstanceState: Bundle?) {\n        super.onPostCreate(savedInstanceState)\n        viewModel.initReadBookConfig(intent)\n        Looper.myQueue().addIdleHandler {\n            viewModel.initData(intent)\n            false\n        }\n        justInitData = true\n    }\n\n    override fun onNewIntent(intent: Intent) {\n        super.onNewIntent(intent)\n        viewModel.initData(intent)\n    }\n\n    override fun onWindowFocusChanged(hasFocus: Boolean) {\n        super.onWindowFocusChanged(hasFocus)\n        upSystemUiVisibility()\n        if (hasFocus) {\n            binding.readMenu.upBrightnessState()\n        } else if (!menuLayoutIsVisible) {\n            ReadBook.cancelPreDownloadTask()\n        }\n    }\n\n    override fun onConfigurationChanged(newConfig: Configuration) {\n        super.onConfigurationChanged(newConfig)\n        upSystemUiVisibility()\n        binding.readView.upStatusBar()\n    }\n\n    override fun onTopResumedActivityChanged(isTopResumedActivity: Boolean) {\n        if (!isTopResumedActivity) {\n            ReadBook.cancelPreDownloadTask()\n        }\n    }\n\n    @SuppressLint(\"UnspecifiedRegisterReceiverFlag\")\n    override fun onResume() {\n        super.onResume()\n        ReadBook.readStartTime = System.currentTimeMillis()\n        if (bookChanged) {\n            bookChanged = false\n            ReadBook.callBack = this\n            viewModel.initData(intent)\n            justInitData = true\n        } else {\n            //web端阅读时，app处于阅读界面，本地记录会覆盖web保存的进度，在此处恢复\n            ReadBook.webBookProgress?.let {\n                ReadBook.setProgress(it)\n                ReadBook.webBookProgress = null\n            }\n        }\n        upSystemUiVisibility()\n        registerReceiver(timeBatteryReceiver, timeBatteryReceiver.filter)\n        binding.readView.upTime()\n        screenOffTimerStart()\n        // 网络监听，当从无网切换到网络环境时同步进度（注意注册的同时就会收到监听，因此界面激活时无需重复执行同步操作）\n        networkChangedListener.register()\n        networkChangedListener.onNetworkChanged = {\n            // 当网络是可用状态且无需初始化时同步进度（初始化中已有同步进度逻辑）\n            if (AppConfig.syncBookProgressPlus && NetworkUtils.isAvailable() && !justInitData && ReadBook.inBookshelf) {\n                ReadBook.syncProgress({ progress -> sureNewProgress(progress) })\n            }\n        }\n    }\n\n    override fun onPause() {\n        super.onPause()\n        autoPageStop()\n        backupJob?.cancel()\n        ReadBook.saveRead()\n        ReadBook.cancelPreDownloadTask()\n        unregisterReceiver(timeBatteryReceiver)\n        upSystemUiVisibility()\n        if (!BuildConfig.DEBUG && ReadBook.inBookshelf) {\n            if (AppConfig.syncBookProgressPlus) {\n                ReadBook.syncProgress()\n            } else {\n                ReadBook.uploadProgress()\n            }\n        }\n        if (!BuildConfig.DEBUG) {\n            Backup.autoBack(this)\n        }\n        justInitData = false\n        networkChangedListener.unRegister()\n    }\n\n    override fun onCompatCreateOptionsMenu(menu: Menu): Boolean {\n        menuInflater.inflate(R.menu.book_read, menu)\n        menu.iconItemOnLongClick(R.id.menu_change_source) {\n            PopupMenu(this, it).apply {\n                inflate(R.menu.book_read_change_source)\n                this.menu.applyOpenTint(this@ReadBookActivity)\n                setOnMenuItemClickListener(this@ReadBookActivity)\n            }.show()\n        }\n        menu.iconItemOnLongClick(R.id.menu_refresh) {\n            PopupMenu(this, it).apply {\n                inflate(R.menu.book_read_refresh)\n                this.menu.applyOpenTint(this@ReadBookActivity)\n                setOnMenuItemClickListener(this@ReadBookActivity)\n            }.show()\n        }\n        binding.readMenu.refreshMenuColorFilter()\n        return super.onCompatCreateOptionsMenu(menu)\n    }\n\n    override fun onPrepareOptionsMenu(menu: Menu): Boolean {\n        this.menu = menu\n        upMenu()\n        return super.onPrepareOptionsMenu(menu)\n    }\n\n    override fun onMenuOpened(featureId: Int, menu: Menu): Boolean {\n        menu.findItem(R.id.menu_same_title_removed)?.isChecked =\n            ReadBook.curTextChapter?.sameTitleRemoved == true\n        return super.onMenuOpened(featureId, menu)\n    }\n\n    /**\n     * 更新菜单\n     */\n    private fun upMenu() {\n        val menu = menu ?: return\n        val book = ReadBook.book ?: return\n        val onLine = !book.isLocal\n        for (i in 0 until menu.size) {\n            val item = menu[i]\n            when (item.groupId) {\n                R.id.menu_group_on_line -> item.isVisible = onLine\n                R.id.menu_group_local -> item.isVisible = !onLine\n                R.id.menu_group_text -> item.isVisible = book.isLocalTxt\n                R.id.menu_group_epub -> item.isVisible = book.isEpub\n                else -> when (item.itemId) {\n                    R.id.menu_enable_replace -> item.isChecked = book.getUseReplaceRule()\n                    R.id.menu_re_segment -> item.isChecked = book.getReSegment()\n//                    R.id.menu_enable_review -> {\n//                        item.isVisible = BuildConfig.DEBUG\n//                        item.isChecked = AppConfig.enableReview\n//                    }\n\n                    R.id.menu_reverse_content -> item.isVisible = onLine\n                    R.id.menu_del_ruby_tag -> item.isChecked = book.getDelTag(Book.rubyTag)\n                    R.id.menu_del_h_tag -> item.isChecked = book.getDelTag(Book.hTag)\n                }\n            }\n        }\n        lifecycleScope.launch {\n            val show = ReadBook.inBookshelf && withContext(IO) {\n                AppWebDav.isOk\n            }\n            menu.findItem(R.id.menu_get_progress)?.isVisible = show\n            menu.findItem(R.id.menu_cover_progress)?.isVisible = show\n        }\n    }\n\n    /**\n     * 菜单\n     */\n    override fun onCompatOptionsItemSelected(item: MenuItem): Boolean {\n        when (item.itemId) {\n            R.id.menu_change_source,\n            R.id.menu_book_change_source -> {\n                binding.readMenu.runMenuOut()\n                ReadBook.book?.let {\n                    showDialogFragment(ChangeBookSourceDialog(it.name, it.author))\n                }\n            }\n\n            R.id.menu_chapter_change_source -> lifecycleScope.launch {\n                val book = ReadBook.book ?: return@launch\n                val chapter =\n                    appDb.bookChapterDao.getChapter(book.bookUrl, ReadBook.durChapterIndex)\n                        ?: return@launch\n                binding.readMenu.runMenuOut()\n                showDialogFragment(\n                    ChangeChapterSourceDialog(book.name, book.author, chapter.index, chapter.title)\n                )\n            }\n\n            R.id.menu_refresh,\n            R.id.menu_refresh_dur -> {\n                if (ReadBook.bookSource == null) {\n                    upContent()\n                } else {\n                    ReadBook.book?.let {\n                        ReadBook.curTextChapter = null\n                        binding.readView.upContent()\n                        viewModel.refreshContentDur(it)\n                    }\n                }\n            }\n\n            R.id.menu_refresh_after -> {\n                if (ReadBook.bookSource == null) {\n                    upContent()\n                } else {\n                    ReadBook.book?.let {\n                        ReadBook.clearTextChapter()\n                        binding.readView.upContent()\n                        viewModel.refreshContentAfter(it)\n                    }\n                }\n            }\n\n            R.id.menu_refresh_all -> {\n                if (ReadBook.bookSource == null) {\n                    upContent()\n                } else {\n                    ReadBook.book?.let {\n                        refreshContentAll(it)\n                    }\n                }\n            }\n\n            R.id.menu_download -> showDownloadDialog()\n            R.id.menu_add_bookmark -> addBookmark()\n            R.id.menu_simulated_reading -> showSimulatedReading()\n            R.id.menu_edit_content -> showDialogFragment(ContentEditDialog())\n            R.id.menu_update_toc -> ReadBook.book?.let {\n                if (it.isEpub) {\n                    BookHelp.clearCache(it)\n                    EpubFile.clear()\n                }\n                if (it.isMobi) {\n                    MobiFile.clear()\n                }\n                loadChapterList(it)\n            }\n\n            R.id.menu_enable_replace -> changeReplaceRuleState()\n            R.id.menu_re_segment -> ReadBook.book?.let {\n                it.setReSegment(!it.getReSegment())\n                item.isChecked = it.getReSegment()\n                ReadBook.loadContent(false)\n            }\n\n//            R.id.menu_enable_review -> {\n//                AppConfig.enableReview = !AppConfig.enableReview\n//                item.isChecked = AppConfig.enableReview\n//                ReadBook.loadContent(false)\n//            }\n\n            R.id.menu_del_ruby_tag -> ReadBook.book?.let {\n                item.isChecked = !item.isChecked\n                if (item.isChecked) {\n                    it.addDelTag(Book.rubyTag)\n                } else {\n                    it.removeDelTag(Book.rubyTag)\n                }\n                refreshContentAll(it)\n            }\n\n            R.id.menu_del_h_tag -> ReadBook.book?.let {\n                item.isChecked = !item.isChecked\n                if (item.isChecked) {\n                    it.addDelTag(Book.hTag)\n                } else {\n                    it.removeDelTag(Book.hTag)\n                }\n                refreshContentAll(it)\n            }\n\n            R.id.menu_page_anim -> showPageAnimConfig {\n                binding.readView.upPageAnim()\n                ReadBook.loadContent(false)\n            }\n\n            R.id.menu_log -> showDialogFragment<AppLogDialog>()\n            R.id.menu_toc_regex -> showDialogFragment(\n                TxtTocRuleDialog(ReadBook.book?.tocUrl)\n            )\n\n            R.id.menu_reverse_content -> ReadBook.book?.let {\n                viewModel.reverseContent(it)\n            }\n\n            R.id.menu_set_charset -> showCharsetConfig()\n            R.id.menu_image_style -> {\n                val imgStyles =\n                    arrayListOf(\n                        Book.imgStyleDefault, Book.imgStyleFull, Book.imgStyleText,\n                        Book.imgStyleSingle\n                    )\n                selector(\n                    R.string.image_style,\n                    imgStyles\n                ) { _, index ->\n                    val imageStyle = imgStyles[index]\n                    ReadBook.book?.setImageStyle(imageStyle)\n                    if (imageStyle == Book.imgStyleSingle) {\n                        ReadBook.book?.setPageAnim(0)  // 切换图片样式single后，自动切换为覆盖\n                        binding.readView.upPageAnim()\n                    }\n                    ReadBook.loadContent(false)\n                }\n            }\n\n            R.id.menu_get_progress -> ReadBook.book?.let {\n                viewModel.syncBookProgress(it) { progress ->\n                    sureSyncProgress(progress)\n                }\n            }\n\n            R.id.menu_cover_progress -> ReadBook.book?.let {\n                ReadBook.uploadProgress(true) { toastOnUi(R.string.upload_book_success) }\n            }\n\n            R.id.menu_same_title_removed -> {\n                ReadBook.book?.let {\n                    val contentProcessor = ContentProcessor.get(it)\n                    val textChapter = ReadBook.curTextChapter\n                    if (textChapter != null\n                        && !textChapter.sameTitleRemoved\n                        && !contentProcessor.removeSameTitleCache.contains(\n                            textChapter.chapter.getFileName(\"nr\")\n                        )\n                    ) {\n                        toastOnUi(\"未找到可移除的重复标题\")\n                    }\n                }\n                viewModel.reverseRemoveSameTitle()\n            }\n\n            R.id.menu_effective_replaces -> showDialogFragment<EffectiveReplacesDialog>()\n\n            R.id.menu_help -> showHelp()\n        }\n        return super.onCompatOptionsItemSelected(item)\n    }\n\n    private fun refreshContentAll(book: Book) {\n        ReadBook.clearTextChapter()\n        binding.readView.upContent()\n        viewModel.refreshContentAll(book)\n    }\n\n    override fun onMenuItemClick(item: MenuItem): Boolean {\n        return onCompatOptionsItemSelected(item)\n    }\n\n    /**\n     * 按键拦截,显示菜单\n     */\n    override fun dispatchKeyEvent(event: KeyEvent): Boolean {\n        val keyCode = event.keyCode\n        val action = event.action\n        val isDown = action == 0\n\n        if (keyCode == KeyEvent.KEYCODE_MENU) {\n            if (isDown && !binding.readMenu.canShowMenu) {\n                binding.readMenu.runMenuIn()\n                return true\n            }\n            if (!isDown && !binding.readMenu.canShowMenu) {\n                binding.readMenu.canShowMenu = true\n                return true\n            }\n        }\n        return super.dispatchKeyEvent(event)\n    }\n\n    /**\n     * 鼠标滚轮事件\n     */\n    override fun onGenericMotionEvent(event: MotionEvent): Boolean {\n        if (0 != (event.source and InputDevice.SOURCE_CLASS_POINTER)) {\n            if (event.action == MotionEvent.ACTION_SCROLL) {\n                val axisValue = event.getAxisValue(MotionEvent.AXIS_VSCROLL)\n                LogUtils.d(\"onGenericMotionEvent\", \"axisValue = $axisValue\")\n                // 获得垂直坐标上的滚动方向\n                if (axisValue < 0.0f) { // 滚轮向下滚\n                    mouseWheelPage(PageDirection.NEXT)\n                } else { // 滚轮向上滚\n                    mouseWheelPage(PageDirection.PREV)\n                }\n                return true\n            }\n        }\n        return super.onGenericMotionEvent(event)\n    }\n\n    /**\n     * 按键事件\n     */\n    override fun onKeyDown(keyCode: Int, event: KeyEvent): Boolean {\n        if (menuLayoutIsVisible) {\n            return super.onKeyDown(keyCode, event)\n        }\n        val longPress = event.repeatCount > 0\n        when {\n            isPrevKey(keyCode) -> {\n                handleKeyPage(PageDirection.PREV, longPress)\n                return true\n            }\n\n            isNextKey(keyCode) -> {\n                handleKeyPage(PageDirection.NEXT, longPress)\n                return true\n            }\n        }\n        when (keyCode) {\n            KeyEvent.KEYCODE_VOLUME_UP -> if (volumeKeyPage(PageDirection.PREV, longPress)) {\n                return true\n            }\n\n            KeyEvent.KEYCODE_VOLUME_DOWN -> if (volumeKeyPage(PageDirection.NEXT, longPress)) {\n                return true\n            }\n\n            KeyEvent.KEYCODE_PAGE_UP -> {\n                handleKeyPage(PageDirection.PREV, longPress)\n                return true\n            }\n\n            KeyEvent.KEYCODE_PAGE_DOWN -> {\n                handleKeyPage(PageDirection.NEXT, longPress)\n                return true\n            }\n\n            KeyEvent.KEYCODE_SPACE -> {\n                handleKeyPage(PageDirection.NEXT, longPress)\n                return true\n            }\n        }\n\n        return super.onKeyDown(keyCode, event)\n    }\n\n    /**\n     * 松开按键事件\n     */\n    override fun onKeyUp(keyCode: Int, event: KeyEvent): Boolean {\n        when (keyCode) {\n            KeyEvent.KEYCODE_VOLUME_UP, KeyEvent.KEYCODE_VOLUME_DOWN -> {\n                if (volumeKeyPage(PageDirection.NONE, false)) {\n                    return true\n                }\n            }\n\n        }\n        return super.onKeyUp(keyCode, event)\n    }\n\n    /**\n     * view触摸,文字选择\n     */\n    @SuppressLint(\"ClickableViewAccessibility\")\n    override fun onTouch(v: View, event: MotionEvent): Boolean = binding.run {\n        if (!binding.readView.isTextSelected) {\n            return false\n        }\n        when (event.action) {\n            MotionEvent.ACTION_DOWN -> textActionMenu.dismiss()\n            MotionEvent.ACTION_MOVE -> {\n                when (v.id) {\n                    R.id.cursor_left -> if (!readView.curPage.getReverseStartCursor()) {\n                        readView.curPage.selectStartMove(\n                            event.rawX + cursorLeft.width,\n                            event.rawY - cursorLeft.height\n                        )\n                    } else {\n                        readView.curPage.selectEndMove(\n                            event.rawX - cursorRight.width,\n                            event.rawY - cursorRight.height\n                        )\n                    }\n\n                    R.id.cursor_right -> if (readView.curPage.getReverseEndCursor()) {\n                        readView.curPage.selectStartMove(\n                            event.rawX + cursorLeft.width,\n                            event.rawY - cursorLeft.height\n                        )\n                    } else {\n                        readView.curPage.selectEndMove(\n                            event.rawX - cursorRight.width,\n                            event.rawY - cursorRight.height\n                        )\n                    }\n                }\n            }\n\n            MotionEvent.ACTION_UP -> {\n                readView.curPage.resetReverseCursor()\n                showTextActionMenu()\n            }\n        }\n        return true\n    }\n\n    /**\n     * 更新文字选择开始位置\n     */\n    override fun upSelectedStart(x: Float, y: Float, top: Float) = binding.run {\n        cursorLeft.x = x - cursorLeft.width\n        cursorLeft.y = y\n        cursorLeft.visible(true)\n        textMenuPosition.x = x\n        textMenuPosition.y = top\n    }\n\n    /**\n     * 更新文字选择结束位置\n     */\n    override fun upSelectedEnd(x: Float, y: Float) = binding.run {\n        cursorRight.x = x\n        cursorRight.y = y\n        cursorRight.visible(true)\n    }\n\n    /**\n     * 取消文字选择\n     */\n    override fun onCancelSelect() = binding.run {\n        cursorLeft.invisible()\n        cursorRight.invisible()\n        textActionMenu.dismiss()\n    }\n\n    override fun onLongScreenshotTouchEvent(event: MotionEvent): Boolean {\n        return binding.readView.onTouchEvent(event)\n    }\n\n    /**\n     * 显示文本操作菜单\n     */\n    override fun showTextActionMenu() {\n        val navigationBarHeight =\n            if (!ReadBookConfig.hideNavigationBar && navigationBarGravity == Gravity.BOTTOM)\n                binding.navigationBar.height else 0\n        textActionMenu.show(\n            binding.textMenuPosition,\n            binding.root.height + navigationBarHeight,\n            binding.textMenuPosition.x.toInt(),\n            binding.textMenuPosition.y.toInt(),\n            binding.cursorLeft.y.toInt() + binding.cursorLeft.height,\n            binding.cursorRight.x.toInt(),\n            binding.cursorRight.y.toInt() + binding.cursorRight.height\n        )\n    }\n\n    /**\n     * 当前选择的文本\n     */\n    override val selectedText: String get() = binding.readView.getSelectText()\n\n    /**\n     * 文本选择菜单操作\n     */\n    override fun onMenuItemSelected(itemId: Int): Boolean {\n        when (itemId) {\n            R.id.menu_aloud -> when (AppConfig.contentSelectSpeakMod) {\n                1 -> lifecycleScope.launch {\n                    binding.readView.aloudStartSelect()\n                }\n\n                else -> speak(binding.readView.getSelectText())\n            }\n\n            R.id.menu_bookmark -> binding.readView.curPage.let {\n                val bookmark = it.createBookmark()\n                if (bookmark == null) {\n                    toastOnUi(R.string.create_bookmark_error)\n                } else {\n                    showDialogFragment(BookmarkDialog(bookmark))\n                }\n                return true\n            }\n\n            R.id.menu_replace -> {\n                val scopes = arrayListOf<String>()\n                ReadBook.book?.name?.let {\n                    scopes.add(it)\n                }\n                ReadBook.bookSource?.bookSourceUrl?.let {\n                    scopes.add(it)\n                }\n                val text = selectedText.lineSequence().map { it.trim() }.joinToString(\"\\n\")\n                replaceActivity.launch(\n                    ReplaceEditActivity.startIntent(\n                        this,\n                        pattern = text,\n                        scope = scopes.joinToString(\";\")\n                    )\n                )\n                return true\n            }\n\n            R.id.menu_search_content -> {\n                viewModel.searchContentQuery = selectedText\n                openSearchActivity(selectedText)\n                return true\n            }\n\n            R.id.menu_dict -> {\n                showDialogFragment(DictDialog(selectedText))\n                return true\n            }\n        }\n        return false\n    }\n\n    /**\n     * 文本选择菜单操作完成\n     */\n    override fun onMenuActionFinally() = binding.run {\n        textActionMenu.dismiss()\n        readView.cancelSelect()\n    }\n\n    private fun speak(text: String) {\n        if (tts == null) {\n            tts = TTS()\n        }\n        tts?.speak(text)\n    }\n\n    /**\n     * 鼠标滚轮翻页\n     */\n    private fun mouseWheelPage(direction: PageDirection) {\n        if (menuLayoutIsVisible || !AppConfig.mouseWheelPage) {\n            return\n        }\n        keyPageDebounce(direction, mouseWheel = true, longPress = false)\n    }\n\n    /**\n     * 音量键翻页\n     */\n    private fun volumeKeyPage(direction: PageDirection, longPress: Boolean): Boolean {\n        if (!AppConfig.volumeKeyPage) {\n            return false\n        }\n        if (!AppConfig.volumeKeyPageOnPlay && BaseReadAloudService.isPlay()) {\n            return false\n        }\n        handleKeyPage(direction, longPress)\n        return true\n    }\n\n    private fun handleKeyPage(direction: PageDirection, longPress: Boolean) {\n        if (AppConfig.keyPageOnLongPress || direction == PageDirection.NONE) {\n            keyPage(direction)\n        } else {\n            keyPageDebounce(direction, longPress = longPress)\n        }\n    }\n\n    private fun keyPageDebounce(\n        direction: PageDirection,\n        mouseWheel: Boolean = false,\n        longPress: Boolean\n    ) {\n        if (longPress) {\n            return\n        }\n        nextPageDebounce.apply {\n            wait = if (mouseWheel) 200L else 600L\n            leading = !mouseWheel\n            trailing = mouseWheel\n        }\n        prevPageDebounce.apply {\n            wait = if (mouseWheel) 200L else 600L\n            leading = !mouseWheel\n            trailing = mouseWheel\n        }\n        when (direction) {\n            PageDirection.NEXT -> nextPageDebounce.invoke()\n            PageDirection.PREV -> prevPageDebounce.invoke()\n            else -> {}\n        }\n    }\n\n    private fun keyPage(direction: PageDirection) {\n        binding.readView.cancelSelect()\n        binding.readView.pageDelegate?.isCancel = false\n        binding.readView.pageDelegate?.keyTurnPage(direction)\n    }\n\n    override fun upMenuView() {\n        handler.post {\n            upMenu()\n            binding.readMenu.upBookView()\n        }\n    }\n\n    override fun loadChapterList(book: Book) {\n        ReadBook.upMsg(getString(R.string.toc_updateing))\n        viewModel.loadChapterList(book)\n    }\n\n    /**\n     * 内容加载完成\n     */\n    override fun contentLoadFinish() {\n        if (intent.getBooleanExtra(\"readAloud\", false)) {\n            intent.removeExtra(\"readAloud\")\n            ReadBook.readAloud()\n        }\n        loadStates = true\n    }\n\n    /**\n     * 更新内容\n     */\n    override fun upContent(\n        relativePosition: Int,\n        resetPageOffset: Boolean,\n        success: (() -> Unit)?\n    ) {\n        lifecycleScope.launch {\n            binding.readView.upContent(relativePosition, resetPageOffset)\n            if (relativePosition == 0) {\n                upSeekBarProgress()\n            }\n            loadStates = false\n            success?.invoke()\n        }\n    }\n\n    override suspend fun upContentAwait(\n        relativePosition: Int,\n        resetPageOffset: Boolean,\n        success: (() -> Unit)?\n    ) = withContext(Main.immediate) {\n        binding.readView.upContent(relativePosition, resetPageOffset)\n        if (relativePosition == 0) {\n            upSeekBarProgress()\n        }\n        loadStates = false\n    }\n\n    override fun upPageAnim(upRecorder: Boolean) {\n        lifecycleScope.launch {\n            binding.readView.upPageAnim(upRecorder)\n        }\n    }\n\n    override fun notifyBookChanged() {\n        bookChanged = true\n        if (!ReadBook.inBookshelf) {\n            viewModel.removeFromBookshelf { super.finish() }\n        }\n    }\n\n    override fun cancelSelect() {\n        runOnUiThread {\n            binding.readView.cancelSelect()\n        }\n    }\n\n    /**\n     * 页面改变\n     */\n    override fun pageChanged() {\n        pageChanged = true\n        binding.readView.onPageChange()\n        handler.post {\n            upSeekBarProgress()\n        }\n        executor.execute {\n            startBackupJob()\n        }\n    }\n\n    /**\n     * 更新进度条位置\n     */\n    private fun upSeekBarProgress() {\n        val progress = when (AppConfig.progressBarBehavior) {\n            \"page\" -> ReadBook.durPageIndex\n            else /* chapter */ -> ReadBook.durChapterIndex\n        }\n        binding.readMenu.setSeekPage(progress)\n    }\n\n    /**\n     * 显示菜单\n     */\n    override fun showMenuBar() {\n        binding.readMenu.runMenuIn()\n    }\n\n    override val oldBook: Book?\n        get() = ReadBook.book\n\n    override fun changeTo(source: BookSource, book: Book, toc: List<BookChapter>) {\n        if (!book.isAudio) {\n            viewModel.changeTo(book, toc)\n        } else {\n            ReadAloud.stop(this)\n            lifecycleScope.launch {\n                withContext(IO) {\n                    ReadBook.book?.migrateTo(book, toc)\n                    book.removeType(BookType.updateError)\n                    ReadBook.book?.delete()\n                    appDb.bookDao.insert(book)\n                }\n                startActivityForBook(book)\n                finish()\n            }\n        }\n    }\n\n    override fun replaceContent(content: String) {\n        ReadBook.book?.let {\n            viewModel.saveContent(it, content)\n        }\n    }\n\n    override fun showActionMenu() {\n        when {\n            BaseReadAloudService.isRun -> showReadAloudDialog()\n            isAutoPage -> showDialogFragment<AutoReadDialog>()\n            isShowingSearchResult -> binding.searchMenu.runMenuIn()\n            else -> binding.readMenu.runMenuIn()\n        }\n    }\n\n    /**\n     * 显示朗读菜单\n     */\n    override fun showReadAloudDialog() {\n        showDialogFragment<ReadAloudDialog>()\n    }\n\n    /**\n     * 自动翻页\n     */\n    override fun autoPage() {\n        ReadAloud.stop(this)\n        if (isAutoPage) {\n            autoPageStop()\n        } else {\n            binding.readView.autoPager.start()\n            binding.readMenu.setAutoPage(true)\n            screenTimeOut = -1L\n            screenOffTimerStart()\n        }\n    }\n\n    override fun autoPageStop() {\n        if (isAutoPage) {\n            binding.readView.autoPager.stop()\n            binding.readMenu.setAutoPage(false)\n            dismissDialogFragment<AutoReadDialog>()\n            upScreenTimeOut()\n        }\n    }\n\n    override fun openSourceEditActivity() {\n        ReadBook.bookSource?.let {\n            sourceEditActivity.launch {\n                putExtra(\"sourceUrl\", it.bookSourceUrl)\n            }\n        }\n    }\n\n    override fun openBookInfoActivity() {\n        ReadBook.book?.let {\n            bookInfoActivity.launch {\n                putExtra(\"name\", it.name)\n                putExtra(\"author\", it.author)\n            }\n        }\n    }\n\n    /**\n     * 替换\n     */\n    override fun openReplaceRule() {\n        replaceActivity.launch(Intent(this, ReplaceRuleActivity::class.java))\n    }\n\n    /**\n     * 打开目录\n     */\n    override fun openChapterList() {\n        ReadBook.book?.let {\n            tocActivity.launch(it.bookUrl)\n        }\n    }\n\n    /**\n     * 打开搜索界面\n     */\n    override fun openSearchActivity(searchWord: String?) {\n        val book = ReadBook.book ?: return\n        searchContentActivity.launch {\n            putExtra(\"bookUrl\", book.bookUrl)\n            putExtra(\"searchWord\", searchWord ?: viewModel.searchContentQuery)\n            putExtra(\"searchResultIndex\", viewModel.searchResultIndex)\n            viewModel.searchResultList?.first()?.let {\n                if (it.query == viewModel.searchContentQuery) {\n                    IntentData.put(\"searchResultList\", viewModel.searchResultList)\n                }\n            }\n        }\n    }\n\n    /**\n     * 禁用书源\n     */\n    override fun disableSource() {\n        viewModel.disableSource()\n    }\n\n    /**\n     * 显示阅读样式配置\n     */\n    override fun showReadStyle() {\n        showDialogFragment<ReadStyleDialog>()\n    }\n\n    /**\n     * 显示更多设置\n     */\n    override fun showMoreSetting() {\n        showDialogFragment<MoreConfigDialog>()\n    }\n\n    override fun showSearchSetting() {\n        showDialogFragment<MoreConfigDialog>()\n    }\n\n    /**\n     * 更新状态栏,导航栏\n     */\n    override fun upSystemUiVisibility() {\n        upSystemUiVisibility(isInMultiWindow, !menuLayoutIsVisible, bottomDialog > 0)\n        upNavigationBarColor()\n    }\n\n    // 退出全文搜索\n    override fun exitSearchMenu() {\n        if (isShowingSearchResult) {\n            isShowingSearchResult = false\n            binding.searchMenu.invalidate()\n            binding.searchMenu.invisible()\n            ReadBook.clearSearchResult()\n            binding.readView.cancelSelect(true)\n        }\n    }\n\n    /* 恢复到 全文搜索/进度条跳转前的位置 */\n    private fun restoreLastBookProcess() {\n        if (confirmRestoreProcess == true) {\n            ReadBook.restoreLastBookProgress()\n        } else if (confirmRestoreProcess == null) {\n            alert(R.string.draw) {\n                setMessage(R.string.restore_last_book_process)\n                yesButton {\n                    confirmRestoreProcess = true\n                    ReadBook.restoreLastBookProgress() //恢复启动全文搜索前的进度\n                }\n                noButton {\n                    ReadBook.lastBookProgress = null\n                    confirmRestoreProcess = false\n                }\n                onCancelled {\n                    ReadBook.lastBookProgress = null\n                    confirmRestoreProcess = false\n                }\n            }\n        }\n    }\n\n    override fun showLogin() {\n        ReadBook.bookSource?.let {\n            startActivity<SourceLoginActivity> {\n                putExtra(\"type\", \"bookSource\")\n                putExtra(\"key\", it.bookSourceUrl)\n            }\n        }\n    }\n\n    override fun payAction() {\n        val book = ReadBook.book ?: return\n        if (book.isLocal) return\n        val chapter = appDb.bookChapterDao.getChapter(book.bookUrl, ReadBook.durChapterIndex)\n        if (chapter == null) {\n            toastOnUi(\"no chapter\")\n            return\n        }\n        alert(R.string.chapter_pay) {\n            setMessage(chapter.title)\n            yesButton {\n                Coroutine.async(lifecycleScope) {\n                    val source =\n                        ReadBook.bookSource ?: throw NoStackTraceException(\"no book source\")\n                    val payAction = source.getContentRule().payAction\n                    if (payAction.isNullOrBlank()) {\n                        throw NoStackTraceException(\"no pay action\")\n                    }\n                    val analyzeRule = AnalyzeRule(book, source)\n                    analyzeRule.setCoroutineContext(coroutineContext)\n                    analyzeRule.setBaseUrl(chapter.url)\n                    analyzeRule.setChapter(chapter)\n                    analyzeRule.evalJS(payAction).toString()\n                }.onSuccess(IO) {\n                    if (it.isAbsUrl()) {\n                        startActivity<WebViewActivity> {\n                            val bookSource = ReadBook.bookSource\n                            putExtra(\"title\", getString(R.string.chapter_pay))\n                            putExtra(\"url\", it)\n                            putExtra(\"sourceOrigin\", bookSource?.bookSourceUrl)\n                            putExtra(\"sourceName\", bookSource?.bookSourceName)\n                            putExtra(\"sourceType\", bookSource?.getSourceType())\n                        }\n                    } else if (it.isTrue()) {\n                        //购买成功后刷新目录\n                        ReadBook.book?.let {\n                            ReadBook.curTextChapter = null\n                            BookHelp.delContent(book, chapter)\n                            loadChapterList(book)\n                        }\n                    }\n                }.onError {\n                    AppLog.put(\"执行购买操作出错\\n${it.localizedMessage}\", it, true)\n                }\n            }\n            noButton()\n        }\n    }\n\n    /**\n     * 朗读按钮\n     */\n    override fun onClickReadAloud() {\n        autoPageStop()\n        when {\n            !BaseReadAloudService.isRun -> {\n                ReadAloud.upReadAloudClass()\n                val scrollPageAnim = ReadBook.pageAnim() == 3\n                if (scrollPageAnim) {\n                    val pos = binding.readView.getReadAloudPos()\n                    if (pos != null) {\n                        val (index, line) = pos\n                        if (ReadBook.durChapterIndex != index) {\n                            ReadBook.openChapter(index, line.chapterPosition, false) {\n                                ReadBook.readAloud(startPos = line.pagePosition)\n                            }\n                        } else {\n                            ReadBook.durChapterPos = line.chapterPosition\n                            ReadBook.readAloud(startPos = line.pagePosition)\n                        }\n                    } else {\n                        ReadBook.readAloud()\n                    }\n                } else {\n                    ReadBook.readAloud()\n                }\n            }\n\n            BaseReadAloudService.pause -> {\n                val scrollPageAnim = ReadBook.pageAnim() == 3\n                if (scrollPageAnim && pageChanged) {\n                    pageChanged = false\n                    val pos = binding.readView.getReadAloudPos()\n                    if (pos != null) {\n                        val (index, line) = pos\n                        if (ReadBook.durChapterIndex != index) {\n                            ReadBook.openChapter(index, line.chapterPosition, false) {\n                                ReadBook.readAloud(startPos = line.pagePosition)\n                            }\n                        } else {\n                            ReadBook.durChapterPos = line.chapterPosition\n                            ReadBook.readAloud(startPos = line.pagePosition)\n                        }\n                    } else {\n                        ReadBook.readAloud()\n                    }\n                } else {\n                    ReadAloud.resume(this)\n                }\n            }\n\n            else -> ReadAloud.pause(this)\n        }\n    }\n\n    override fun showHelp() {\n        showHelp(\"readMenuHelp\")\n    }\n\n    /**\n     * 长按图片\n     */\n    @SuppressLint(\"RtlHardcoded\")\n    override fun onImageLongPress(x: Float, y: Float, src: String) {\n        popupAction.setItems(\n            listOf(\n                SelectItem(getString(R.string.show), \"show\"),\n                SelectItem(getString(R.string.refresh), \"refresh\"),\n                SelectItem(getString(R.string.action_save), \"save\"),\n                SelectItem(getString(R.string.menu), \"menu\"),\n                SelectItem(getString(R.string.select_folder), \"selectFolder\")\n            )\n        )\n        popupAction.onActionClick = {\n            when (it) {\n                \"show\" -> showDialogFragment(PhotoDialog(src))\n                \"refresh\" -> viewModel.refreshImage(src)\n                \"save\" -> {\n                    val path = ACache.get().getAsString(AppConst.imagePathKey)\n                    if (path.isNullOrEmpty()) {\n                        selectImageDir.launch {\n                            value = src\n                        }\n                    } else {\n                        viewModel.saveImage(src, Uri.parse(path))\n                    }\n                }\n\n                \"menu\" -> showActionMenu()\n                \"selectFolder\" -> selectImageDir.launch()\n            }\n            popupAction.dismiss()\n        }\n        val navigationBarHeight =\n            if (!ReadBookConfig.hideNavigationBar && navigationBarGravity == Gravity.BOTTOM)\n                binding.navigationBar.height else 0\n        popupAction.showAtLocation(\n            binding.readView, Gravity.BOTTOM or Gravity.LEFT, x.toInt(),\n            binding.root.height + navigationBarHeight - y.toInt()\n        )\n    }\n\n    /**\n     * colorSelectDialog\n     */\n    override fun onColorSelected(dialogId: Int, color: Int) = ReadBookConfig.durConfig.run {\n        when (dialogId) {\n            TEXT_COLOR -> {\n                setCurTextColor(color)\n                postEvent(EventBus.UP_CONFIG, arrayListOf(2, 6, 9, 11))\n                if (AppConfig.readBarStyleFollowPage) {\n                    postEvent(EventBus.UPDATE_READ_ACTION_BAR, true)\n                }\n            }\n\n            BG_COLOR -> {\n                setCurBg(0, \"#${color.hexString}\")\n                postEvent(EventBus.UP_CONFIG, arrayListOf(1))\n                if (AppConfig.readBarStyleFollowPage) {\n                    postEvent(EventBus.UPDATE_READ_ACTION_BAR, true)\n                }\n            }\n\n            TIP_COLOR -> {\n                ReadTipConfig.tipColor = color\n                postEvent(EventBus.TIP_COLOR, \"\")\n                postEvent(EventBus.UP_CONFIG, arrayListOf(2))\n            }\n\n            TIP_DIVIDER_COLOR -> {\n                ReadTipConfig.tipDividerColor = color\n                postEvent(EventBus.TIP_COLOR, \"\")\n                postEvent(EventBus.UP_CONFIG, arrayListOf(2))\n            }\n        }\n    }\n\n    /**\n     * colorSelectDialog\n     */\n    override fun onDialogDismissed(dialogId: Int) = Unit\n\n    override fun onTocRegexDialogResult(tocRegex: String) {\n        ReadBook.book?.let {\n            it.tocUrl = tocRegex\n            loadChapterList(it)\n        }\n    }\n\n    private fun sureSyncProgress(progress: BookProgress) {\n        alert(R.string.get_book_progress) {\n            setMessage(R.string.current_progress_exceeds_cloud)\n            okButton {\n                ReadBook.setProgress(progress)\n            }\n            noButton()\n        }\n    }\n\n    /* 进度条跳转到指定章节 */\n    override fun skipToChapter(index: Int) {\n        ReadBook.saveCurrentBookProgress() //退出章节跳转恢复此时进度\n        viewModel.openChapter(index)\n    }\n\n    /* 全文搜索跳转 */\n    override fun navigateToSearch(searchResult: SearchResult, index: Int) {\n        viewModel.searchResultIndex = index\n        skipToSearch(searchResult)\n    }\n\n    override fun onMenuShow() {\n        binding.readView.autoPager.pause()\n    }\n\n    override fun onMenuHide() {\n        binding.readView.autoPager.resume()\n    }\n\n    override fun onLayoutPageCompleted(index: Int, page: TextPage) {\n        upSeekBarThrottle.invoke()\n        binding.readView.onLayoutPageCompleted(index, page)\n    }\n\n    /* 全文搜索跳转 */\n    private fun skipToSearch(searchResult: SearchResult) {\n        if (searchResult.chapterIndex != ReadBook.durChapterIndex) {\n            viewModel.openChapter(searchResult.chapterIndex) {\n                jumpToPosition(searchResult)\n            }\n        } else {\n            jumpToPosition(searchResult)\n        }\n    }\n\n    private fun jumpToPosition(searchResult: SearchResult) {\n        val curTextChapter = ReadBook.curTextChapter ?: return\n        binding.searchMenu.updateSearchInfo()\n        val (pageIndex, lineIndex, charIndex, addLine, charIndex2) =\n            viewModel.searchResultPositions(curTextChapter, searchResult)\n        ReadBook.skipToPage(pageIndex) {\n            isSelectingSearchResult = true\n            binding.readView.curPage.selectStartMoveIndex(0, lineIndex, charIndex)\n            when (addLine) {\n                0 -> binding.readView.curPage.selectEndMoveIndex(\n                    0,\n                    lineIndex,\n                    charIndex + viewModel.searchContentQuery.length - 1\n                )\n\n                1 -> binding.readView.curPage.selectEndMoveIndex(\n                    0, lineIndex + 1, charIndex2\n                )\n                //consider change page, jump to scroll position\n                -1 -> binding.readView.curPage.selectEndMoveIndex(1, 0, charIndex2)\n            }\n            binding.readView.isTextSelected = true\n            isSelectingSearchResult = false\n        }\n    }\n\n    override fun addBookmark() {\n        val book = ReadBook.book\n        val page = ReadBook.curTextChapter?.getPage(ReadBook.durPageIndex)\n        if (book != null && page != null) {\n            val bookmark = book.createBookMark().apply {\n                chapterIndex = ReadBook.durChapterIndex\n                chapterPos = ReadBook.durChapterPos\n                chapterName = page.title\n                bookText = page.text.trim()\n            }\n            showDialogFragment(BookmarkDialog(bookmark))\n        }\n    }\n\n    override fun changeReplaceRuleState() {\n        ReadBook.book?.let {\n            it.setUseReplaceRule(!it.getUseReplaceRule())\n            ReadBook.saveRead()\n            menu?.findItem(R.id.menu_enable_replace)?.isChecked = it.getUseReplaceRule()\n            viewModel.replaceRuleChanged()\n        }\n    }\n\n    private fun startBackupJob() {\n        backupJob?.cancel()\n        backupJob = lifecycleScope.launch(IO) {\n            delay(300000)\n            ReadBook.book?.let {\n                AppWebDav.uploadBookProgress(it)\n                ensureActive()\n                it.update()\n                Backup.autoBack(this@ReadBookActivity)\n            }\n        }\n    }\n\n    override fun sureNewProgress(progress: BookProgress) {\n        syncDialog?.dismiss()\n        syncDialog = alert(R.string.get_book_progress) {\n            setMessage(R.string.cloud_progress_exceeds_current)\n            okButton {\n                ReadBook.setProgress(progress)\n            }\n            noButton()\n        }\n    }\n\n    override fun finish() {\n        val book = ReadBook.book ?: return super.finish()\n\n        if (ReadBook.inBookshelf) {\n            return super.finish()\n        }\n\n        if (!AppConfig.showAddToShelfAlert) {\n            viewModel.removeFromBookshelf { super.finish() }\n        } else {\n            alert(title = getString(R.string.add_to_bookshelf)) {\n                setMessage(getString(R.string.check_add_bookshelf, book.name))\n                okButton {\n                    ReadBook.book?.removeType(BookType.notShelf)\n                    ReadBook.book?.save()\n                    ReadBook.inBookshelf = true\n                    setResult(RESULT_OK)\n                }\n                noButton { viewModel.removeFromBookshelf { super.finish() } }\n            }\n        }\n    }\n\n    override fun onDestroy() {\n        super.onDestroy()\n        tts?.clearTts()\n        textActionMenu.dismiss()\n        popupAction.dismiss()\n        binding.readView.onDestroy()\n        ReadBook.unregister(this)\n        if (!ReadBook.inBookshelf && !isChangingConfigurations) {\n            viewModel.removeFromBookshelf(null)\n        }\n        if (!BuildConfig.DEBUG) {\n            Backup.autoBack(this)\n        }\n    }\n\n    override fun observeLiveBus() = binding.run {\n        observeEvent<String>(EventBus.TIME_CHANGED) { readView.upTime() }\n        observeEvent<Int>(EventBus.BATTERY_CHANGED) { readView.upBattery(it) }\n        observeEvent<Boolean>(EventBus.MEDIA_BUTTON) {\n            if (it) {\n                onClickReadAloud()\n            } else {\n                ReadBook.readAloud(!BaseReadAloudService.pause)\n            }\n        }\n        observeEvent<ArrayList<Int>>(EventBus.UP_CONFIG) {\n            it.forEach { value ->\n                when (value) {\n                    0 -> upSystemUiVisibility()\n                    1 -> readView.upBg()\n                    2 -> readView.upStyle()\n                    3 -> readView.upBgAlpha()\n                    4 -> readView.upPageSlopSquare()\n                    5 -> if (isInitFinish) ReadBook.loadContent(resetPageOffset = false)\n                    6 -> readView.upContent(resetPageOffset = false)\n                    8 -> ChapterProvider.upStyle()\n                    9 -> readView.invalidateTextPage()\n                    10 -> ChapterProvider.upLayout()\n                    11 -> readView.submitRenderTask()\n                }\n            }\n        }\n        observeEvent<Int>(EventBus.ALOUD_STATE) {\n            if (it == Status.STOP || it == Status.PAUSE) {\n                ReadBook.curTextChapter?.let { textChapter ->\n                    val page = textChapter.getPageByReadPos(ReadBook.durChapterPos)\n                    if (page != null) {\n                        page.removePageAloudSpan()\n                        readView.upContent(resetPageOffset = false)\n                    }\n                }\n            }\n        }\n        observeEventSticky<Int>(EventBus.TTS_PROGRESS) { chapterStart ->\n            lifecycleScope.launch(IO) {\n                if (BaseReadAloudService.isPlay()) {\n                    ReadBook.curTextChapter?.let { textChapter ->\n                        ReadBook.durChapterPos = chapterStart\n                        val pageIndex = ReadBook.durPageIndex\n                        val aloudSpanStart = chapterStart - textChapter.getReadLength(pageIndex)\n                        textChapter.getPage(pageIndex)\n                            ?.upPageAloudSpan(aloudSpanStart)\n                        upContent()\n                    }\n                }\n            }\n        }\n        observeEvent<Boolean>(PreferKey.keepLight) {\n            upScreenTimeOut()\n        }\n        observeEvent<Boolean>(PreferKey.textSelectAble) {\n            readView.curPage.upSelectAble(it)\n        }\n        observeEvent<String>(PreferKey.showBrightnessView) {\n            readMenu.upBrightnessState()\n        }\n        observeEvent<List<SearchResult>>(EventBus.SEARCH_RESULT) {\n            viewModel.searchResultList = it\n        }\n        observeEvent<Boolean>(EventBus.UPDATE_READ_ACTION_BAR) {\n            readMenu.reset()\n        }\n        observeEvent<Boolean>(EventBus.UP_SEEK_BAR) {\n            readMenu.upSeekBar()\n        }\n    }\n\n    private fun upScreenTimeOut() {\n        val keepLightPrefer = getPrefString(PreferKey.keepLight)?.toInt() ?: 0\n        screenTimeOut = keepLightPrefer * 1000L\n        screenOffTimerStart()\n    }\n\n    /**\n     * 重置黑屏时间\n     */\n    override fun screenOffTimerStart() {\n        handler.post {\n            if (screenTimeOut < 0) {\n                keepScreenOn(true)\n                return@post\n            }\n            val t = screenTimeOut - sysScreenOffTime\n            if (t > 0) {\n                keepScreenOn(true)\n                handler.removeCallbacks(screenOffRunnable)\n                handler.postDelayed(screenOffRunnable, screenTimeOut)\n            } else {\n                keepScreenOn(false)\n            }\n        }\n    }\n\n    companion object {\n        const val RESULT_DELETED = 100\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/book/read/ReadBookViewModel.kt",
    "content": "package io.legado.app.ui.book.read\n\nimport android.app.Application\nimport android.content.Intent\nimport android.net.Uri\nimport androidx.documentfile.provider.DocumentFile\nimport androidx.lifecycle.MutableLiveData\nimport io.legado.app.R\nimport io.legado.app.base.BaseViewModel\nimport io.legado.app.constant.AppLog\nimport io.legado.app.constant.BookType\nimport io.legado.app.constant.EventBus\nimport io.legado.app.data.appDb\nimport io.legado.app.data.entities.Book\nimport io.legado.app.data.entities.BookChapter\nimport io.legado.app.data.entities.BookProgress\nimport io.legado.app.exception.NoStackTraceException\nimport io.legado.app.help.AppWebDav\nimport io.legado.app.help.book.BookHelp\nimport io.legado.app.help.book.ContentProcessor\nimport io.legado.app.help.book.isLocal\nimport io.legado.app.help.book.isLocalModified\nimport io.legado.app.help.book.removeType\nimport io.legado.app.help.book.simulatedTotalChapterNum\nimport io.legado.app.help.config.AppConfig\nimport io.legado.app.help.coroutine.Coroutine\nimport io.legado.app.model.ImageProvider\nimport io.legado.app.model.ReadAloud\nimport io.legado.app.model.ReadBook\nimport io.legado.app.model.localBook.LocalBook\nimport io.legado.app.model.webBook.WebBook\nimport io.legado.app.service.BaseReadAloudService\nimport io.legado.app.ui.book.read.page.entities.TextChapter\nimport io.legado.app.ui.book.searchContent.SearchResult\nimport io.legado.app.utils.DocumentUtils\nimport io.legado.app.utils.FileUtils\nimport io.legado.app.utils.isContentScheme\nimport io.legado.app.utils.mapParallelSafe\nimport io.legado.app.utils.postEvent\nimport io.legado.app.utils.toStringArray\nimport io.legado.app.utils.toastOnUi\nimport kotlinx.coroutines.currentCoroutineContext\nimport kotlinx.coroutines.ensureActive\nimport kotlinx.coroutines.flow.catch\nimport kotlinx.coroutines.flow.collect\nimport kotlinx.coroutines.flow.flow\nimport kotlinx.coroutines.flow.onCompletion\nimport kotlinx.coroutines.flow.onEach\nimport kotlinx.coroutines.flow.onEmpty\nimport kotlinx.coroutines.flow.onStart\nimport kotlinx.coroutines.flow.take\nimport java.io.File\nimport java.io.FileInputStream\nimport java.io.FileNotFoundException\nimport java.io.FileOutputStream\n\n/**\n * 阅读界面数据处理\n */\nclass ReadBookViewModel(application: Application) : BaseViewModel(application) {\n    val permissionDenialLiveData = MutableLiveData<Int>()\n    var isInitFinish = false\n    var searchContentQuery = \"\"\n    var searchResultList: List<SearchResult>? = null\n    var searchResultIndex: Int = 0\n    private var changeSourceCoroutine: Coroutine<*>? = null\n\n    init {\n        AppConfig.detectClickArea()\n    }\n\n    fun initReadBookConfig(intent: Intent) {\n        val bookUrl = intent.getStringExtra(\"bookUrl\")\n        val book = when {\n            bookUrl.isNullOrEmpty() -> appDb.bookDao.lastReadBook\n            else -> appDb.bookDao.getBook(bookUrl)\n        } ?: return\n        ReadBook.upReadBookConfig(book)\n    }\n\n    /**\n     * 初始化\n     */\n    fun initData(intent: Intent, success: (() -> Unit)? = null) {\n        execute {\n            ReadBook.inBookshelf = intent.getBooleanExtra(\"inBookshelf\", true)\n            ReadBook.chapterChanged = intent.getBooleanExtra(\"chapterChanged\", false)\n            val bookUrl = intent.getStringExtra(\"bookUrl\")\n            val book = when {\n                bookUrl.isNullOrEmpty() -> appDb.bookDao.lastReadBook\n                else -> appDb.bookDao.getBook(bookUrl)\n            } ?: ReadBook.book\n            when {\n                book != null -> initBook(book)\n                else -> {\n                    ReadBook.upMsg(context.getString(R.string.no_book))\n                    AppLog.put(\"未找到书籍\\nbookUrl:$bookUrl\")\n                }\n            }\n        }.onSuccess {\n            success?.invoke()\n        }.onError {\n            val msg = \"初始化数据失败\\n${it.localizedMessage}\"\n            ReadBook.upMsg(msg)\n            AppLog.put(msg, it)\n        }.onFinally {\n            ReadBook.saveRead()\n        }\n    }\n\n    private suspend fun initBook(book: Book) {\n        val isSameBook = ReadBook.book?.bookUrl == book.bookUrl\n        if (isSameBook) {\n            ReadBook.upData(book)\n        } else {\n            ReadBook.resetData(book)\n        }\n        isInitFinish = true\n        if (!book.isLocal && book.tocUrl.isEmpty() && !loadBookInfo(book)) {\n            return\n        }\n        if (book.isLocal && !checkLocalBookFileExist(book)) {\n            return\n        }\n        if ((ReadBook.chapterSize == 0 || book.isLocalModified()) && !loadChapterListAwait(book)) {\n            return\n        }\n        ReadBook.upMsg(null)\n        if (!isSameBook) {\n            ReadBook.loadContent(resetPageOffset = true)\n        } else {\n            ReadBook.loadOrUpContent()\n        }\n        if (ReadBook.chapterChanged) {\n            // 有章节跳转不同步阅读进度\n            ReadBook.chapterChanged = false\n        } else if (!(isSameBook && BaseReadAloudService.isRun) && ReadBook.inBookshelf) {\n            if (AppConfig.syncBookProgressPlus) {\n                ReadBook.syncProgress({ progress -> ReadBook.callBack?.sureNewProgress(progress) })\n            } else {\n                syncBookProgress(book)\n            }\n        }\n        if (!book.isLocal && ReadBook.bookSource == null) {\n            autoChangeSource(book.name, book.author)\n            return\n        }\n    }\n\n    private fun checkLocalBookFileExist(book: Book): Boolean {\n        try {\n            LocalBook.getBookInputStream(book)\n            return true\n        } catch (e: Throwable) {\n            ReadBook.upMsg(\"打开本地书籍出错: ${e.localizedMessage}\")\n            if (e is SecurityException || e is FileNotFoundException) {\n                permissionDenialLiveData.postValue(0)\n            }\n            return false\n        }\n    }\n\n    /**\n     * 加载详情页\n     */\n    private suspend fun loadBookInfo(book: Book): Boolean {\n        val source = ReadBook.bookSource ?: return true\n        try {\n            WebBook.getBookInfoAwait(source, book, canReName = false)\n            return true\n        } catch (e: Throwable) {\n            currentCoroutineContext().ensureActive()\n            ReadBook.upMsg(\"详情页出错: ${e.localizedMessage}\")\n            return false\n        }\n    }\n\n    /**\n     * 加载目录\n     */\n    fun loadChapterList(book: Book) {\n        execute {\n            if (loadChapterListAwait(book)) {\n                ReadBook.upMsg(null)\n            }\n        }\n    }\n\n    private suspend fun loadChapterListAwait(book: Book): Boolean {\n        if (book.isLocal) {\n            kotlin.runCatching {\n                LocalBook.getChapterList(book).let {\n                    appDb.bookChapterDao.delByBook(book.bookUrl)\n                    appDb.bookChapterDao.insert(*it.toTypedArray())\n                    appDb.bookDao.update(book)\n                    ReadBook.onChapterListUpdated(book)\n                }\n                return true\n            }.onFailure {\n                when (it) {\n                    is SecurityException, is FileNotFoundException -> {\n                        permissionDenialLiveData.postValue(1)\n                    }\n\n                    else -> {\n                        AppLog.put(\"LoadTocError:${it.localizedMessage}\", it)\n                        ReadBook.upMsg(\"LoadTocError:${it.localizedMessage}\")\n                    }\n                }\n                return false\n            }\n        } else {\n            ReadBook.bookSource?.let {\n                val oldBook = book.copy()\n                WebBook.getChapterListAwait(it, book, true)\n                    .onSuccess { cList ->\n                        if (oldBook.bookUrl == book.bookUrl) {\n                            appDb.bookDao.update(book)\n                        } else {\n                            appDb.bookDao.replace(oldBook, book)\n                            BookHelp.updateCacheFolder(oldBook, book)\n                        }\n                        appDb.bookChapterDao.delByBook(oldBook.bookUrl)\n                        appDb.bookChapterDao.insert(*cList.toTypedArray())\n                        ReadBook.onChapterListUpdated(book)\n                        return true\n                    }.onFailure {\n                        currentCoroutineContext().ensureActive()\n                        ReadBook.upMsg(context.getString(R.string.error_load_toc))\n                        return false\n                    }\n            }\n        }\n        return true\n    }\n\n    /**\n     * 同步进度\n     */\n    fun syncBookProgress(\n        book: Book,\n        alertSync: ((progress: BookProgress) -> Unit)? = null\n    ) {\n        if (!AppConfig.syncBookProgress) return\n        execute {\n            AppWebDav.getBookProgress(book)\n        }.onError {\n            AppLog.put(\"拉取阅读进度失败《${book.name}》\\n${it.localizedMessage}\", it)\n        }.onSuccess { progress ->\n            progress ?: return@onSuccess\n            if (progress.durChapterIndex == book.durChapterIndex && progress.durChapterPos == book.durChapterPos) {\n                return@onSuccess\n            }\n            if (progress.durChapterIndex < book.durChapterIndex ||\n                (progress.durChapterIndex == book.durChapterIndex\n                        && progress.durChapterPos < book.durChapterPos)\n            ) {\n                alertSync?.invoke(progress)\n            } else if (progress.durChapterIndex < book.simulatedTotalChapterNum()) {\n                ReadBook.setProgress(progress)\n                AppLog.put(\"自动同步阅读进度成功《${book.name}》 ${progress.durChapterTitle}\")\n                context.toastOnUi(\"已同步最新阅读进度\")\n            }\n        }\n    }\n\n    /**\n     * 换源\n     */\n    fun changeTo(book: Book, toc: List<BookChapter>) {\n        changeSourceCoroutine?.cancel()\n        changeSourceCoroutine = execute {\n            ReadBook.upMsg(context.getString(R.string.loading))\n            ReadBook.book?.migrateTo(book, toc)\n            book.removeType(BookType.updateError)\n            ReadBook.book?.delete()\n            appDb.bookDao.insert(book)\n            appDb.bookChapterDao.insert(*toc.toTypedArray())\n            ReadBook.resetData(book)\n            ReadBook.upMsg(null)\n            ReadBook.loadContent(resetPageOffset = true)\n        }.onError {\n            AppLog.put(\"换源失败\\n$it\", it, true)\n            ReadBook.upMsg(null)\n        }.onFinally {\n            postEvent(EventBus.SOURCE_CHANGED, book.bookUrl)\n        }\n    }\n\n    /**\n     * 自动换源\n     */\n    private fun autoChangeSource(name: String, author: String) {\n        if (!AppConfig.autoChangeSource) return\n        execute {\n            val sources = appDb.bookSourceDao.allTextEnabledPart\n            flow {\n                for (source in sources) {\n                    source.getBookSource()?.let {\n                        emit(it)\n                    }\n                }\n            }.onStart {\n                ReadBook.upMsg(context.getString(R.string.source_auto_changing))\n            }.mapParallelSafe(AppConfig.threadCount) { source ->\n                val book = WebBook.preciseSearchAwait(source, name, author).getOrThrow()\n                if (book.tocUrl.isEmpty()) {\n                    WebBook.getBookInfoAwait(source, book)\n                }\n                val toc = WebBook.getChapterListAwait(source, book).getOrThrow()\n                val chapter = toc.getOrElse(book.durChapterIndex) {\n                    toc.last()\n                }\n                val nextChapter = toc.getOrElse(chapter.index) {\n                    toc.first()\n                }\n                WebBook.getContentAwait(\n                    bookSource = source,\n                    book = book,\n                    bookChapter = chapter,\n                    nextChapterUrl = nextChapter.url\n                )\n                book to toc\n            }.take(1).onEach { (book, toc) ->\n                changeTo(book, toc)\n            }.onEmpty {\n                throw NoStackTraceException(\"没有合适书源\")\n            }.onCompletion {\n                ReadBook.upMsg(null)\n            }.catch {\n                AppLog.put(\"自动换源失败\\n${it.localizedMessage}\", it)\n                context.toastOnUi(\"自动换源失败\\n${it.localizedMessage}\")\n            }.collect()\n        }\n    }\n\n    fun openChapter(index: Int, durChapterPos: Int = 0, success: (() -> Unit)? = null) {\n        ReadBook.openChapter(index, durChapterPos, success = success)\n    }\n\n    fun removeFromBookshelf(success: (() -> Unit)?) {\n        val book = ReadBook.book\n        Coroutine.async {\n            book?.delete()\n        }.onSuccess {\n            success?.invoke()\n        }\n    }\n\n    fun upBookSource(success: (() -> Unit)?) {\n        execute {\n            ReadBook.book?.let { book ->\n                ReadBook.bookSource = appDb.bookSourceDao.getBookSource(book.origin)\n            }\n        }.onSuccess {\n            success?.invoke()\n        }\n    }\n\n    fun refreshContentDur(book: Book) {\n        execute {\n            appDb.bookChapterDao.getChapter(book.bookUrl, ReadBook.durChapterIndex)\n                ?.let { chapter ->\n                    BookHelp.delContent(book, chapter)\n                    ReadBook.loadContent(ReadBook.durChapterIndex, resetPageOffset = false)\n                }\n        }\n    }\n\n    fun refreshContentAfter(book: Book) {\n        execute {\n            appDb.bookChapterDao.getChapterList(\n                book.bookUrl,\n                ReadBook.durChapterIndex,\n                book.totalChapterNum\n            ).forEach { chapter ->\n                BookHelp.delContent(book, chapter)\n            }\n            ReadBook.loadContent(false)\n        }\n    }\n\n    fun refreshContentAll(book: Book) {\n        execute {\n            BookHelp.clearCache(book)\n            ReadBook.loadContent(false)\n        }\n    }\n\n    /**\n     * 保存内容\n     */\n    fun saveContent(book: Book, content: String) {\n        execute {\n            appDb.bookChapterDao.getChapter(book.bookUrl, ReadBook.durChapterIndex)\n                ?.let { chapter ->\n                    BookHelp.saveText(book, chapter, content)\n                    ReadBook.loadContent(ReadBook.durChapterIndex, resetPageOffset = false)\n                }\n        }\n    }\n\n    /**\n     * 反转内容\n     */\n    fun reverseContent(book: Book) {\n        execute {\n            val chapter = appDb.bookChapterDao.getChapter(book.bookUrl, ReadBook.durChapterIndex)\n                ?: return@execute\n            val content = BookHelp.getContent(book, chapter) ?: return@execute\n            val stringBuilder = StringBuilder()\n            content.toStringArray().forEach {\n                stringBuilder.insert(0, it)\n            }\n            BookHelp.saveText(book, chapter, stringBuilder.toString())\n            ReadBook.loadContent(ReadBook.durChapterIndex, resetPageOffset = false)\n        }\n    }\n\n    /**\n     * 内容搜索跳转\n     */\n    fun searchResultPositions(\n        textChapter: TextChapter,\n        searchResult: SearchResult\n    ): Array<Int> {\n        // calculate search result's pageIndex\n        val pages = textChapter.pages\n        val content = textChapter.getContent()\n        val queryLength = searchContentQuery.length\n\n        var count = 0\n        var index = content.indexOf(searchContentQuery)\n        while (count != searchResult.resultCountWithinChapter) {\n            index = content.indexOf(searchContentQuery, index + queryLength)\n            count += 1\n        }\n        val contentPosition = index\n        var pageIndex = 0\n        var length = pages[pageIndex].text.length\n        while (length < contentPosition && pageIndex + 1 < pages.size) {\n            pageIndex += 1\n            length += pages[pageIndex].text.length\n        }\n\n        // calculate search result's lineIndex\n        val currentPage = pages[pageIndex]\n        val curTextLines = currentPage.lines\n        var lineIndex = 0\n        var curLine = curTextLines[lineIndex]\n        length = length - currentPage.text.length + curLine.text.length\n        if (curLine.isParagraphEnd) length++\n        while (length <= contentPosition && lineIndex + 1 < curTextLines.size) {\n            lineIndex += 1\n            curLine = curTextLines[lineIndex]\n            length += curLine.text.length\n            if (curLine.isParagraphEnd) length++\n        }\n\n        // charIndex\n        val currentLine = currentPage.lines[lineIndex]\n        var curLineLength = currentLine.text.length\n        if (currentLine.isParagraphEnd) curLineLength++\n        length -= curLineLength\n\n        val charIndex = contentPosition - length\n        var addLine = 0\n        var charIndex2 = 0\n        // change line\n        if ((charIndex + queryLength) > curLineLength) {\n            addLine = 1\n            charIndex2 = charIndex + queryLength - curLineLength - 1\n        }\n        // changePage\n        if ((lineIndex + addLine + 1) > currentPage.lines.size) {\n            addLine = -1\n            charIndex2 = charIndex + queryLength - curLineLength - 1\n        }\n        return arrayOf(pageIndex, lineIndex, charIndex, addLine, charIndex2)\n    }\n\n    /**\n     * 翻转删除重复标题\n     */\n    fun reverseRemoveSameTitle() {\n        execute {\n            val book = ReadBook.book ?: return@execute\n            val textChapter = ReadBook.curTextChapter ?: return@execute\n            BookHelp.setRemoveSameTitle(\n                book, textChapter.chapter, !textChapter.sameTitleRemoved\n            )\n            ReadBook.loadContent(ReadBook.durChapterIndex)\n        }\n    }\n\n    /**\n     * 刷新图片\n     */\n    fun refreshImage(src: String) {\n        execute {\n            ReadBook.book?.let { book ->\n                val vFile = BookHelp.getImage(book, src)\n                ImageProvider.bitmapLruCache.remove(vFile.absolutePath)\n                vFile.delete()\n            }\n        }.onFinally {\n            ReadBook.loadContent(false)\n        }\n    }\n\n    /**\n     * 保存图片\n     */\n    fun saveImage(src: String?, uri: Uri) {\n        src ?: return\n        val book = ReadBook.book ?: return\n        execute {\n            val image = BookHelp.getImage(book, src)\n            FileInputStream(image).use { input ->\n                if (uri.isContentScheme()) {\n                    DocumentFile.fromTreeUri(context, uri)?.let { doc ->\n                        val imageDoc = DocumentUtils.createFileIfNotExist(doc, image.name)!!\n                        context.contentResolver.openOutputStream(imageDoc.uri)!!.use { output ->\n                            input.copyTo(output)\n                        }\n                    }\n                } else {\n                    val dir = File(uri.path ?: uri.toString())\n                    val file = FileUtils.createFileIfNotExist(dir, image.name)\n                    FileOutputStream(file).use { output ->\n                        input.copyTo(output)\n                    }\n                }\n            }\n        }.onError {\n            AppLog.put(\"保存图片出错\\n${it.localizedMessage}\", it)\n            context.toastOnUi(\"保存图片出错\\n${it.localizedMessage}\")\n        }\n    }\n\n    /**\n     * 替换规则变化\n     */\n    fun replaceRuleChanged() {\n        execute {\n            ReadBook.book?.let {\n                ContentProcessor.get(it.name, it.origin).upReplaceRules()\n                ReadBook.loadContent(resetPageOffset = false)\n            }\n        }\n    }\n\n    fun disableSource() {\n        execute {\n            ReadBook.bookSource?.let {\n                it.enabled = false\n                appDb.bookSourceDao.update(it)\n            }\n        }\n    }\n\n    override fun onCleared() {\n        super.onCleared()\n        if (BaseReadAloudService.isRun && BaseReadAloudService.pause) {\n            ReadAloud.stop(context)\n        }\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/book/read/ReadMenu.kt",
    "content": "package io.legado.app.ui.book.read\n\nimport android.annotation.SuppressLint\nimport android.content.Context\nimport android.content.res.ColorStateList\nimport android.graphics.Color\nimport android.graphics.PorterDuff\nimport android.graphics.drawable.GradientDrawable\nimport android.util.AttributeSet\nimport android.view.LayoutInflater\nimport android.view.WindowManager.LayoutParams.BRIGHTNESS_OVERRIDE_NONE\nimport android.view.animation.Animation\nimport android.widget.FrameLayout\nimport android.widget.SeekBar\nimport androidx.appcompat.widget.PopupMenu\nimport androidx.core.view.isGone\nimport androidx.core.view.isVisible\nimport io.legado.app.R\nimport io.legado.app.constant.PreferKey\nimport io.legado.app.databinding.ViewReadMenuBinding\nimport io.legado.app.help.config.AppConfig\nimport io.legado.app.help.config.LocalConfig\nimport io.legado.app.help.config.ReadBookConfig\nimport io.legado.app.help.config.ThemeConfig\nimport io.legado.app.help.coroutine.Coroutine\nimport io.legado.app.help.source.getSourceType\nimport io.legado.app.lib.dialogs.alert\nimport io.legado.app.lib.theme.Selector\nimport io.legado.app.lib.theme.accentColor\nimport io.legado.app.lib.theme.bottomBackground\nimport io.legado.app.lib.theme.buttonDisabledColor\nimport io.legado.app.lib.theme.getPrimaryTextColor\nimport io.legado.app.lib.theme.primaryColor\nimport io.legado.app.lib.theme.primaryTextColor\nimport io.legado.app.model.ReadBook\nimport io.legado.app.ui.browser.WebViewActivity\nimport io.legado.app.ui.widget.seekbar.SeekBarChangeListener\nimport io.legado.app.utils.ColorUtils\nimport io.legado.app.utils.ConstraintModify\nimport io.legado.app.utils.activity\nimport io.legado.app.utils.applyNavigationBarPadding\nimport io.legado.app.utils.dpToPx\nimport io.legado.app.utils.getPrefBoolean\nimport io.legado.app.utils.gone\nimport io.legado.app.utils.invisible\nimport io.legado.app.utils.loadAnimation\nimport io.legado.app.utils.modifyBegin\nimport io.legado.app.utils.openUrl\nimport io.legado.app.utils.putPrefBoolean\nimport io.legado.app.utils.startActivity\nimport io.legado.app.utils.visible\nimport splitties.views.onClick\nimport splitties.views.onLongClick\n\n/**\n * 阅读界面菜单\n */\nclass ReadMenu @JvmOverloads constructor(\n    context: Context,\n    attrs: AttributeSet? = null\n) : FrameLayout(context, attrs) {\n    var canShowMenu: Boolean = false\n    private val callBack: CallBack get() = activity as CallBack\n    private val binding = ViewReadMenuBinding.inflate(LayoutInflater.from(context), this, true)\n    private var confirmSkipToChapter: Boolean = false\n    private var isMenuOutAnimating = false\n    private val menuTopIn: Animation by lazy {\n        loadAnimation(context, R.anim.anim_readbook_top_in)\n    }\n    private val menuTopOut: Animation by lazy {\n        loadAnimation(context, R.anim.anim_readbook_top_out)\n    }\n    private val menuBottomIn: Animation by lazy {\n        loadAnimation(context, R.anim.anim_readbook_bottom_in)\n    }\n    private val menuBottomOut: Animation by lazy {\n        loadAnimation(context, R.anim.anim_readbook_bottom_out)\n    }\n    private val immersiveMenu: Boolean\n        get() = AppConfig.readBarStyleFollowPage && ReadBookConfig.durConfig.curBgType() == 0\n    private var bgColor: Int = if (immersiveMenu) {\n        kotlin.runCatching {\n            Color.parseColor(ReadBookConfig.durConfig.curBgStr())\n        }.getOrDefault(context.bottomBackground)\n    } else {\n        context.bottomBackground\n    }\n    private var textColor: Int = if (immersiveMenu) {\n        ReadBookConfig.durConfig.curTextColor()\n    } else {\n        context.getPrimaryTextColor(ColorUtils.isColorLight(bgColor))\n    }\n\n    private var bottomBackgroundList: ColorStateList = Selector.colorBuild()\n        .setDefaultColor(bgColor)\n        .setPressedColor(ColorUtils.darkenColor(bgColor))\n        .create()\n    private var onMenuOutEnd: (() -> Unit)? = null\n    private val showBrightnessView\n        get() = context.getPrefBoolean(\n            PreferKey.showBrightnessView,\n            true\n        )\n    private val sourceMenu by lazy {\n        PopupMenu(context, binding.tvSourceAction).apply {\n            inflate(R.menu.book_read_source)\n            setOnMenuItemClickListener {\n                when (it.itemId) {\n                    R.id.menu_login -> callBack.showLogin()\n                    R.id.menu_chapter_pay -> callBack.payAction()\n                    R.id.menu_edit_source -> callBack.openSourceEditActivity()\n                    R.id.menu_disable_source -> callBack.disableSource()\n                }\n                true\n            }\n        }\n    }\n    private val menuInListener = object : Animation.AnimationListener {\n        override fun onAnimationStart(animation: Animation) {\n            binding.tvSourceAction.text =\n                ReadBook.bookSource?.bookSourceName ?: context.getString(R.string.book_source)\n            binding.tvSourceAction.isGone = ReadBook.isLocalBook\n            callBack.upSystemUiVisibility()\n            binding.llBrightness.visible(showBrightnessView)\n        }\n\n        @SuppressLint(\"RtlHardcoded\")\n        override fun onAnimationEnd(animation: Animation) {\n            binding.vwMenuBg.setOnClickListener { runMenuOut() }\n            callBack.upSystemUiVisibility()\n            if (!LocalConfig.readMenuHelpVersionIsLast) {\n                callBack.showHelp()\n            }\n        }\n\n        override fun onAnimationRepeat(animation: Animation) = Unit\n    }\n    private val menuOutListener = object : Animation.AnimationListener {\n        override fun onAnimationStart(animation: Animation) {\n            isMenuOutAnimating = true\n            binding.vwMenuBg.setOnClickListener(null)\n        }\n\n        override fun onAnimationEnd(animation: Animation) {\n            this@ReadMenu.invisible()\n            binding.titleBar.invisible()\n            binding.bottomMenu.invisible()\n            canShowMenu = false\n            isMenuOutAnimating = false\n            onMenuOutEnd?.invoke()\n            callBack.upSystemUiVisibility()\n        }\n\n        override fun onAnimationRepeat(animation: Animation) = Unit\n    }\n\n    init {\n        initView()\n        upBrightnessState()\n        bindEvent()\n    }\n\n    private fun initView(reset: Boolean = false) = binding.run {\n        if (AppConfig.isNightTheme) {\n            fabNightTheme.setImageResource(R.drawable.ic_daytime)\n        } else {\n            fabNightTheme.setImageResource(R.drawable.ic_brightness)\n        }\n        initAnimation()\n        if (immersiveMenu) {\n            val lightTextColor = ColorUtils.withAlpha(ColorUtils.lightenColor(textColor), 0.75f)\n            titleBar.setTextColor(textColor)\n            titleBar.setBackgroundColor(bgColor)\n            titleBar.setColorFilter(textColor)\n            tvChapterName.setTextColor(lightTextColor)\n            tvChapterUrl.setTextColor(lightTextColor)\n        } else if (reset) {\n            val bgColor = context.primaryColor\n            val textColor = context.primaryTextColor\n            titleBar.setTextColor(textColor)\n            titleBar.setBackgroundColor(bgColor)\n            titleBar.setColorFilter(textColor)\n            tvChapterName.setTextColor(textColor)\n            tvChapterUrl.setTextColor(textColor)\n        }\n        val brightnessBackground = GradientDrawable()\n        brightnessBackground.cornerRadius = 5F.dpToPx()\n        brightnessBackground.setColor(ColorUtils.adjustAlpha(bgColor, 0.5f))\n        llBrightness.background = brightnessBackground\n        if (AppConfig.isEInkMode) {\n            titleBar.setBackgroundResource(R.drawable.bg_eink_border_bottom)\n            llBottomBg.setBackgroundResource(R.drawable.bg_eink_border_top)\n        } else {\n            llBottomBg.setBackgroundColor(bgColor)\n        }\n        fabSearch.backgroundTintList = bottomBackgroundList\n        fabSearch.setColorFilter(textColor)\n        fabAutoPage.backgroundTintList = bottomBackgroundList\n        fabAutoPage.setColorFilter(textColor)\n        fabReplaceRule.backgroundTintList = bottomBackgroundList\n        fabReplaceRule.setColorFilter(textColor)\n        fabNightTheme.backgroundTintList = bottomBackgroundList\n        fabNightTheme.setColorFilter(textColor)\n        tvPre.setTextColor(textColor)\n        tvNext.setTextColor(textColor)\n        ivCatalog.setColorFilter(textColor, PorterDuff.Mode.SRC_IN)\n        tvCatalog.setTextColor(textColor)\n        ivReadAloud.setColorFilter(textColor, PorterDuff.Mode.SRC_IN)\n        tvReadAloud.setTextColor(textColor)\n        ivFont.setColorFilter(textColor, PorterDuff.Mode.SRC_IN)\n        tvFont.setTextColor(textColor)\n        ivSetting.setColorFilter(textColor, PorterDuff.Mode.SRC_IN)\n        tvSetting.setTextColor(textColor)\n        vwBrightnessPosAdjust.setColorFilter(textColor, PorterDuff.Mode.SRC_IN)\n        llBrightness.setOnClickListener(null)\n        seekBrightness.post {\n            seekBrightness.progress = AppConfig.readBrightness\n        }\n        if (AppConfig.showReadTitleBarAddition) {\n            titleBarAddition.visible()\n        } else {\n            titleBarAddition.gone()\n        }\n        upBrightnessVwPos()\n        /**\n         * 确保视图不被导航栏遮挡\n         */\n        applyNavigationBarPadding()\n    }\n\n    fun reset() {\n        upColorConfig()\n        initView(true)\n    }\n\n    fun refreshMenuColorFilter() {\n        if (immersiveMenu) {\n            binding.titleBar.setColorFilter(textColor)\n        }\n    }\n\n    private fun upColorConfig() {\n        bgColor = if (immersiveMenu) {\n            kotlin.runCatching {\n                Color.parseColor(ReadBookConfig.durConfig.curBgStr())\n            }.getOrDefault(context.bottomBackground)\n        } else {\n            context.bottomBackground\n        }\n        textColor = if (immersiveMenu) {\n            ReadBookConfig.durConfig.curTextColor()\n        } else {\n            context.getPrimaryTextColor(ColorUtils.isColorLight(bgColor))\n        }\n        bottomBackgroundList = Selector.colorBuild()\n            .setDefaultColor(bgColor)\n            .setPressedColor(ColorUtils.darkenColor(bgColor))\n            .create()\n    }\n\n    fun upBrightnessState() {\n        if (brightnessAuto()) {\n            binding.ivBrightnessAuto.setColorFilter(context.accentColor)\n            binding.seekBrightness.isEnabled = false\n        } else {\n            binding.ivBrightnessAuto.setColorFilter(context.buttonDisabledColor)\n            binding.seekBrightness.isEnabled = true\n        }\n        setScreenBrightness(AppConfig.readBrightness.toFloat())\n    }\n\n    /**\n     * 设置屏幕亮度\n     */\n    fun setScreenBrightness(value: Float) {\n        activity?.run {\n            var brightness = BRIGHTNESS_OVERRIDE_NONE\n            if (!brightnessAuto() && value != BRIGHTNESS_OVERRIDE_NONE) {\n                brightness = value\n                if (brightness < 1f) brightness = 1f\n                brightness /= 255f\n            }\n            val params = window.attributes\n            params.screenBrightness = brightness\n            window.attributes = params\n        }\n    }\n\n    fun runMenuIn(anim: Boolean = !AppConfig.isEInkMode) {\n        callBack.onMenuShow()\n        this.visible()\n        binding.titleBar.visible()\n        binding.bottomMenu.visible()\n        if (anim) {\n            binding.titleBar.startAnimation(menuTopIn)\n            binding.bottomMenu.startAnimation(menuBottomIn)\n        } else {\n            menuInListener.onAnimationStart(menuBottomIn)\n            menuInListener.onAnimationEnd(menuBottomIn)\n        }\n    }\n\n    fun runMenuOut(anim: Boolean = !AppConfig.isEInkMode, onMenuOutEnd: (() -> Unit)? = null) {\n        if (isMenuOutAnimating) {\n            return\n        }\n        callBack.onMenuHide()\n        this.onMenuOutEnd = onMenuOutEnd\n        if (this.isVisible) {\n            if (anim) {\n                binding.titleBar.startAnimation(menuTopOut)\n                binding.bottomMenu.startAnimation(menuBottomOut)\n            } else {\n                menuOutListener.onAnimationStart(menuBottomOut)\n                menuOutListener.onAnimationEnd(menuBottomOut)\n            }\n        }\n    }\n\n    private fun brightnessAuto(): Boolean {\n        return context.getPrefBoolean(\"brightnessAuto\", true) || !showBrightnessView\n    }\n\n    private fun bindEvent() = binding.run {\n        vwMenuBg.setOnClickListener { runMenuOut() }\n        titleBar.toolbar.setOnClickListener {\n            callBack.openBookInfoActivity()\n        }\n        val chapterViewClickListener = OnClickListener {\n            if (ReadBook.isLocalBook) {\n                return@OnClickListener\n            }\n            if (AppConfig.readUrlInBrowser) {\n                context.openUrl(tvChapterUrl.text.toString().substringBefore(\",{\"))\n            } else {\n                Coroutine.async {\n                    context.startActivity<WebViewActivity> {\n                        val url = tvChapterUrl.text.toString()\n                        val bookSource = ReadBook.bookSource\n                        putExtra(\"title\", tvChapterName.text)\n                        putExtra(\"url\", url)\n                        putExtra(\"sourceOrigin\", bookSource?.bookSourceUrl)\n                        putExtra(\"sourceName\", bookSource?.bookSourceName)\n                        putExtra(\"sourceType\", bookSource?.getSourceType())\n                    }\n                }\n            }\n        }\n        val chapterViewLongClickListener = OnLongClickListener {\n            if (ReadBook.isLocalBook) {\n                return@OnLongClickListener true\n            }\n            context.alert(R.string.open_fun) {\n                setMessage(R.string.use_browser_open)\n                okButton {\n                    AppConfig.readUrlInBrowser = true\n                }\n                noButton {\n                    AppConfig.readUrlInBrowser = false\n                }\n            }\n            true\n        }\n        tvChapterName.setOnClickListener(chapterViewClickListener)\n        tvChapterName.setOnLongClickListener(chapterViewLongClickListener)\n        tvChapterUrl.setOnClickListener(chapterViewClickListener)\n        tvChapterUrl.setOnLongClickListener(chapterViewLongClickListener)\n        //书源操作\n        tvSourceAction.onClick {\n            sourceMenu.menu.findItem(R.id.menu_login).isVisible =\n                !ReadBook.bookSource?.loginUrl.isNullOrEmpty()\n            sourceMenu.menu.findItem(R.id.menu_chapter_pay).isVisible =\n                !ReadBook.bookSource?.loginUrl.isNullOrEmpty()\n                        && ReadBook.curTextChapter?.isVip == true\n                        && ReadBook.curTextChapter?.isPay != true\n            sourceMenu.show()\n        }\n        //亮度跟随\n        ivBrightnessAuto.setOnClickListener {\n            context.putPrefBoolean(\"brightnessAuto\", !brightnessAuto())\n            upBrightnessState()\n        }\n        //亮度调节\n        seekBrightness.setOnSeekBarChangeListener(object : SeekBarChangeListener {\n\n            override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {\n                if (fromUser) {\n                    setScreenBrightness(progress.toFloat())\n                }\n            }\n\n            override fun onStopTrackingTouch(seekBar: SeekBar) {\n                AppConfig.readBrightness = seekBar.progress\n            }\n\n        })\n        vwBrightnessPosAdjust.setOnClickListener {\n            AppConfig.brightnessVwPos = !AppConfig.brightnessVwPos\n            upBrightnessVwPos()\n        }\n        //阅读进度\n        seekReadPage.setOnSeekBarChangeListener(object : SeekBarChangeListener {\n\n            override fun onStartTrackingTouch(seekBar: SeekBar) {\n                binding.vwMenuBg.setOnClickListener(null)\n            }\n\n            override fun onStopTrackingTouch(seekBar: SeekBar) {\n                binding.vwMenuBg.setOnClickListener { runMenuOut() }\n                when (AppConfig.progressBarBehavior) {\n                    \"page\" -> ReadBook.skipToPage(seekBar.progress)\n                    \"chapter\" -> {\n                        if (confirmSkipToChapter) {\n                            callBack.skipToChapter(seekBar.progress)\n                        } else {\n                            context.alert(\"章节跳转确认\", \"确定要跳转章节吗？\") {\n                                yesButton {\n                                    confirmSkipToChapter = true\n                                    callBack.skipToChapter(seekBar.progress)\n                                }\n                                noButton {\n                                    upSeekBar()\n                                }\n                                onCancelled {\n                                    upSeekBar()\n                                }\n                            }\n                        }\n                    }\n                }\n            }\n\n        })\n\n        //搜索\n        fabSearch.setOnClickListener {\n            runMenuOut {\n                callBack.openSearchActivity(null)\n            }\n        }\n\n        //自动翻页\n        fabAutoPage.setOnClickListener {\n            runMenuOut {\n                callBack.autoPage()\n            }\n        }\n\n        //替换\n        fabReplaceRule.setOnClickListener { callBack.openReplaceRule() }\n\n        //夜间模式\n        fabNightTheme.setOnClickListener {\n            AppConfig.isNightTheme = !AppConfig.isNightTheme\n            ThemeConfig.applyDayNight(context)\n        }\n\n        //上一章\n        tvPre.setOnClickListener { ReadBook.moveToPrevChapter(upContent = true, toLast = false) }\n\n        //下一章\n        tvNext.setOnClickListener { ReadBook.moveToNextChapter(true) }\n\n        //目录\n        llCatalog.setOnClickListener {\n            runMenuOut {\n                callBack.openChapterList()\n            }\n        }\n\n        //朗读\n        llReadAloud.setOnClickListener {\n            runMenuOut {\n                callBack.onClickReadAloud()\n            }\n        }\n        llReadAloud.onLongClick {\n            runMenuOut {\n                callBack.showReadAloudDialog()\n            }\n        }\n        //界面\n        llFont.setOnClickListener {\n            runMenuOut {\n                callBack.showReadStyle()\n            }\n        }\n\n        //设置\n        llSetting.setOnClickListener {\n            runMenuOut {\n                callBack.showMoreSetting()\n            }\n        }\n    }\n\n    private fun initAnimation() {\n        menuTopIn.setAnimationListener(menuInListener)\n        menuTopOut.setAnimationListener(menuOutListener)\n    }\n\n    fun upBookView() {\n        binding.titleBar.title = ReadBook.book?.name\n        ReadBook.curTextChapter?.let {\n            binding.tvChapterName.text = it.title\n            binding.tvChapterName.visible()\n            if (!ReadBook.isLocalBook) {\n                binding.tvChapterUrl.text = it.chapter.getAbsoluteURL()\n                binding.tvChapterUrl.visible()\n            } else {\n                binding.tvChapterUrl.gone()\n            }\n            upSeekBar()\n            binding.tvPre.isEnabled = ReadBook.durChapterIndex != 0\n            binding.tvNext.isEnabled = ReadBook.durChapterIndex != ReadBook.simulatedChapterSize - 1\n        } ?: let {\n            binding.tvChapterName.gone()\n            binding.tvChapterUrl.gone()\n        }\n    }\n\n    fun upSeekBar() {\n        binding.seekReadPage.apply {\n            when (AppConfig.progressBarBehavior) {\n                \"page\" -> {\n                    ReadBook.curTextChapter?.let {\n                        max = it.pageSize.minus(1)\n                        progress = ReadBook.durPageIndex\n                    }\n                }\n\n                \"chapter\" -> {\n                    max = ReadBook.simulatedChapterSize - 1\n                    progress = ReadBook.durChapterIndex\n                }\n            }\n        }\n    }\n\n    fun setSeekPage(seek: Int) {\n        binding.seekReadPage.progress = seek\n    }\n\n    fun setAutoPage(autoPage: Boolean) = binding.run {\n        if (autoPage) {\n            fabAutoPage.setImageResource(R.drawable.ic_auto_page_stop)\n            fabAutoPage.contentDescription = context.getString(R.string.auto_next_page_stop)\n        } else {\n            fabAutoPage.setImageResource(R.drawable.ic_auto_page)\n            fabAutoPage.contentDescription = context.getString(R.string.auto_next_page)\n        }\n        fabAutoPage.setColorFilter(textColor)\n    }\n\n    private fun upBrightnessVwPos() {\n        if (AppConfig.brightnessVwPos) {\n            binding.root.modifyBegin()\n                .clear(R.id.ll_brightness, ConstraintModify.Anchor.LEFT)\n                .rightToRightOf(R.id.ll_brightness, R.id.vw_menu_root)\n                .commit()\n        } else {\n            binding.root.modifyBegin()\n                .clear(R.id.ll_brightness, ConstraintModify.Anchor.RIGHT)\n                .leftToLeftOf(R.id.ll_brightness, R.id.vw_menu_root)\n                .commit()\n        }\n    }\n\n    interface CallBack {\n        fun autoPage()\n        fun openReplaceRule()\n        fun openChapterList()\n        fun openSearchActivity(searchWord: String?)\n        fun openSourceEditActivity()\n        fun openBookInfoActivity()\n        fun showReadStyle()\n        fun showMoreSetting()\n        fun showReadAloudDialog()\n        fun upSystemUiVisibility()\n        fun onClickReadAloud()\n        fun showHelp()\n        fun showLogin()\n        fun payAction()\n        fun disableSource()\n        fun skipToChapter(index: Int)\n        fun onMenuShow()\n        fun onMenuHide()\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/book/read/SearchMenu.kt",
    "content": "package io.legado.app.ui.book.read\n\nimport android.annotation.SuppressLint\nimport android.content.Context\nimport android.content.res.ColorStateList\nimport android.graphics.PorterDuff\nimport android.util.AttributeSet\nimport android.view.LayoutInflater\nimport android.view.animation.Animation\nimport android.widget.FrameLayout\nimport androidx.core.view.isVisible\nimport io.legado.app.R\nimport io.legado.app.databinding.ViewSearchMenuBinding\nimport io.legado.app.lib.theme.Selector\nimport io.legado.app.lib.theme.bottomBackground\nimport io.legado.app.lib.theme.getPrimaryTextColor\nimport io.legado.app.model.ReadBook\nimport io.legado.app.ui.book.searchContent.SearchResult\nimport io.legado.app.utils.ColorUtils\nimport io.legado.app.utils.activity\nimport io.legado.app.utils.applyNavigationBarPadding\nimport io.legado.app.utils.invisible\nimport io.legado.app.utils.loadAnimation\nimport io.legado.app.utils.visible\n\n/**\n * 搜索界面菜单\n */\nclass SearchMenu @JvmOverloads constructor(\n    context: Context, attrs: AttributeSet? = null\n) : FrameLayout(context, attrs) {\n\n    private val callBack: CallBack get() = activity as CallBack\n    private val binding = ViewSearchMenuBinding.inflate(LayoutInflater.from(context), this, true)\n\n    private val menuBottomIn: Animation = loadAnimation(context, R.anim.anim_readbook_bottom_in)\n    private val menuBottomOut: Animation = loadAnimation(context, R.anim.anim_readbook_bottom_out)\n    private val bgColor: Int = context.bottomBackground\n    private val textColor: Int = context.getPrimaryTextColor(ColorUtils.isColorLight(bgColor))\n    private val bottomBackgroundList: ColorStateList =\n        Selector.colorBuild().setDefaultColor(bgColor)\n            .setPressedColor(ColorUtils.darkenColor(bgColor)).create()\n    private var onMenuOutEnd: (() -> Unit)? = null\n    private var isMenuOutAnimating = false\n\n    private val searchResultList: MutableList<SearchResult> = mutableListOf()\n    private var currentSearchResultIndex: Int = -1\n    private var lastSearchResultIndex: Int = -1\n    private val hasSearchResult: Boolean\n        get() = searchResultList.isNotEmpty()\n    val selectedSearchResult: SearchResult?\n        get() = searchResultList.getOrNull(currentSearchResultIndex)\n    val previousSearchResult: SearchResult?\n        get() = searchResultList.getOrNull(lastSearchResultIndex)\n    val bottomMenuVisible get() = isVisible && binding.llBottomMenu.isVisible\n\n    init {\n        initAnimation()\n        initView()\n        bindEvent()\n        updateSearchInfo()\n    }\n\n    fun upSearchResultList(resultList: List<SearchResult>) {\n        searchResultList.clear()\n        searchResultList.addAll(resultList)\n        updateSearchInfo()\n    }\n\n    private fun initView() = binding.run {\n        llSearchBaseInfo.setBackgroundColor(bgColor)\n        tvCurrentSearchInfo.setTextColor(bottomBackgroundList)\n        llBottomBg.setBackgroundColor(bgColor)\n        fabLeft.backgroundTintList = bottomBackgroundList\n        fabLeft.setColorFilter(textColor, PorterDuff.Mode.SRC_IN)\n        fabRight.backgroundTintList = bottomBackgroundList\n        fabRight.setColorFilter(textColor, PorterDuff.Mode.SRC_IN)\n        tvMainMenu.setTextColor(textColor)\n        tvSearchResults.setTextColor(textColor)\n        tvSearchExit.setTextColor(textColor)\n        ivMainMenu.setColorFilter(textColor, PorterDuff.Mode.SRC_IN)\n        ivSearchResults.setColorFilter(textColor, PorterDuff.Mode.SRC_IN)\n        ivSearchExit.setColorFilter(textColor, PorterDuff.Mode.SRC_IN)\n        ivSearchContentUp.setColorFilter(textColor, PorterDuff.Mode.SRC_IN)\n        ivSearchContentDown.setColorFilter(textColor, PorterDuff.Mode.SRC_IN)\n        tvCurrentSearchInfo.setTextColor(textColor)\n        applyNavigationBarPadding()\n    }\n\n\n    fun runMenuIn() {\n        this.visible()\n        binding.llBottomMenu.visible()\n        binding.vwMenuBg.visible()\n        binding.llBottomMenu.startAnimation(menuBottomIn)\n    }\n\n    fun runMenuOut(onMenuOutEnd: (() -> Unit)? = null) {\n        if (isMenuOutAnimating) {\n            return\n        }\n        this.onMenuOutEnd = onMenuOutEnd\n        if (this.isVisible) {\n            binding.llBottomMenu.startAnimation(menuBottomOut)\n        }\n    }\n\n    @SuppressLint(\"SetTextI18n\")\n    fun updateSearchInfo() {\n        ReadBook.curTextChapter?.let {\n            binding.tvCurrentSearchInfo.text =\n                \"\"\"${context.getString(R.string.search_content_size)}: ${searchResultList.size} / 当前章节: ${it.title}\"\"\"\n        }\n    }\n\n    fun updateSearchResultIndex(updateIndex: Int) {\n        lastSearchResultIndex = currentSearchResultIndex\n        currentSearchResultIndex = when {\n            updateIndex < 0 -> 0\n            updateIndex >= searchResultList.size -> searchResultList.size - 1\n            else -> updateIndex\n        }\n    }\n\n    private fun bindEvent() = binding.run {\n        //搜索结果\n        llSearchResults.setOnClickListener {\n            runMenuOut {\n                callBack.openSearchActivity(selectedSearchResult?.query)\n            }\n        }\n\n        //主菜单\n        llMainMenu.setOnClickListener {\n            runMenuOut {\n                callBack.cancelSelect()\n                callBack.showMenuBar()\n                this@SearchMenu.invisible()\n            }\n        }\n\n        //退出\n        llSearchExit.setOnClickListener {\n            runMenuOut {\n                callBack.exitSearchMenu()\n            }\n        }\n\n        fabLeft.setOnClickListener {\n            updateSearchResultIndex(currentSearchResultIndex - 1)\n            callBack.navigateToSearch(\n                searchResultList[currentSearchResultIndex],\n                currentSearchResultIndex\n            )\n        }\n\n        ivSearchContentUp.setOnClickListener {\n            updateSearchResultIndex(currentSearchResultIndex - 1)\n            callBack.navigateToSearch(\n                searchResultList[currentSearchResultIndex],\n                currentSearchResultIndex\n            )\n        }\n\n        ivSearchContentDown.setOnClickListener {\n            updateSearchResultIndex(currentSearchResultIndex + 1)\n            callBack.navigateToSearch(\n                searchResultList[currentSearchResultIndex],\n                currentSearchResultIndex\n            )\n        }\n\n        fabRight.setOnClickListener {\n            updateSearchResultIndex(currentSearchResultIndex + 1)\n            callBack.navigateToSearch(\n                searchResultList[currentSearchResultIndex],\n                currentSearchResultIndex\n            )\n        }\n    }\n\n    private fun initAnimation() {\n        //显示菜单\n        menuBottomIn.setAnimationListener(object : Animation.AnimationListener {\n            override fun onAnimationStart(animation: Animation) {\n                callBack.upSystemUiVisibility()\n                binding.fabLeft.visible(hasSearchResult)\n                binding.fabRight.visible(hasSearchResult)\n            }\n\n            @SuppressLint(\"RtlHardcoded\")\n            override fun onAnimationEnd(animation: Animation) {\n                binding.vwMenuBg.setOnClickListener { runMenuOut() }\n                callBack.upSystemUiVisibility()\n            }\n\n            override fun onAnimationRepeat(animation: Animation) = Unit\n        })\n\n        //隐藏菜单\n        menuBottomOut.setAnimationListener(object : Animation.AnimationListener {\n            override fun onAnimationStart(animation: Animation) {\n                isMenuOutAnimating = true\n                binding.vwMenuBg.setOnClickListener(null)\n            }\n\n            override fun onAnimationEnd(animation: Animation) {\n                isMenuOutAnimating = false\n                binding.llBottomMenu.invisible()\n                binding.vwMenuBg.invisible()\n                binding.vwMenuBg.setOnClickListener { runMenuOut() }\n\n                onMenuOutEnd?.invoke()\n                callBack.upSystemUiVisibility()\n            }\n\n            override fun onAnimationRepeat(animation: Animation) = Unit\n        })\n    }\n\n    interface CallBack {\n        var isShowingSearchResult: Boolean\n        fun openSearchActivity(searchWord: String?)\n        fun showSearchSetting()\n        fun upSystemUiVisibility()\n        fun exitSearchMenu()\n        fun showMenuBar()\n        fun navigateToSearch(searchResult: SearchResult, index: Int)\n        fun onMenuShow()\n        fun onMenuHide()\n        fun cancelSelect()\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/book/read/TextActionMenu.kt",
    "content": "package io.legado.app.ui.book.read\n\nimport android.annotation.SuppressLint\nimport android.app.SearchManager\nimport android.content.Context\nimport android.content.Intent\nimport android.content.pm.ResolveInfo\nimport android.net.Uri\nimport android.os.Build\nimport android.view.Gravity\nimport android.view.LayoutInflater\nimport android.view.Menu\nimport android.view.View\nimport android.view.ViewGroup\nimport android.widget.PopupWindow\nimport androidx.annotation.RequiresApi\nimport androidx.appcompat.view.SupportMenuInflater\nimport androidx.appcompat.view.menu.MenuBuilder\nimport androidx.appcompat.view.menu.MenuItemImpl\nimport androidx.core.view.isVisible\nimport io.legado.app.R\nimport io.legado.app.base.adapter.ItemViewHolder\nimport io.legado.app.base.adapter.RecyclerAdapter\nimport io.legado.app.constant.AppLog\nimport io.legado.app.constant.PreferKey\nimport io.legado.app.databinding.ItemTextBinding\nimport io.legado.app.databinding.PopupActionMenuBinding\nimport io.legado.app.help.config.AppConfig\nimport io.legado.app.utils.getPrefBoolean\nimport io.legado.app.utils.gone\nimport io.legado.app.utils.isAbsUrl\nimport io.legado.app.utils.printOnDebug\nimport io.legado.app.utils.sendToClip\nimport io.legado.app.utils.share\nimport io.legado.app.utils.toastOnUi\nimport io.legado.app.utils.visible\n\n@SuppressLint(\"RestrictedApi\")\nclass TextActionMenu(private val context: Context, private val callBack: CallBack) :\n    PopupWindow(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT) {\n\n    private val binding = PopupActionMenuBinding.inflate(LayoutInflater.from(context))\n    private val adapter = Adapter(context).apply {\n        setHasStableIds(true)\n    }\n    private val menuItems: List<MenuItemImpl>\n    private val visibleMenuItems = arrayListOf<MenuItemImpl>()\n    private val moreMenuItems = arrayListOf<MenuItemImpl>()\n    private val expandTextMenu get() = context.getPrefBoolean(PreferKey.expandTextMenu)\n\n    init {\n        @SuppressLint(\"InflateParams\")\n        contentView = binding.root\n\n        isTouchable = true\n        isOutsideTouchable = false\n        isFocusable = false\n\n        val myMenu = MenuBuilder(context)\n        val otherMenu = MenuBuilder(context)\n        SupportMenuInflater(context).inflate(R.menu.content_select_action, myMenu)\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {\n            onInitializeMenu(otherMenu)\n        }\n        menuItems = myMenu.visibleItems + otherMenu.visibleItems\n        visibleMenuItems.addAll(menuItems.subList(0, 5))\n        moreMenuItems.addAll(menuItems.subList(5, menuItems.size))\n        binding.recyclerView.adapter = adapter\n        binding.recyclerViewMore.adapter = adapter\n        setOnDismissListener {\n            if (!context.getPrefBoolean(PreferKey.expandTextMenu)) {\n                binding.ivMenuMore.setImageResource(R.drawable.ic_more_vert)\n                binding.recyclerViewMore.gone()\n                adapter.setItems(visibleMenuItems)\n                binding.recyclerView.visible()\n            }\n        }\n        binding.ivMenuMore.setOnClickListener {\n            if (binding.recyclerView.isVisible) {\n                binding.ivMenuMore.setImageResource(R.drawable.ic_arrow_back)\n                adapter.setItems(moreMenuItems)\n                binding.recyclerView.gone()\n                binding.recyclerViewMore.visible()\n            } else {\n                binding.ivMenuMore.setImageResource(R.drawable.ic_more_vert)\n                binding.recyclerViewMore.gone()\n                adapter.setItems(visibleMenuItems)\n                binding.recyclerView.visible()\n            }\n        }\n        upMenu()\n    }\n\n    fun upMenu() {\n        if (expandTextMenu) {\n            adapter.setItems(menuItems)\n            binding.ivMenuMore.gone()\n        } else {\n            adapter.setItems(visibleMenuItems)\n            binding.ivMenuMore.visible()\n        }\n    }\n\n    fun show(\n        view: View,\n        windowHeight: Int,\n        startX: Int,\n        startTopY: Int,\n        startBottomY: Int,\n        endX: Int,\n        endBottomY: Int\n    ) {\n        if (expandTextMenu) {\n            when {\n                startTopY > 500 -> {\n                    showAtLocation(\n                        view,\n                        Gravity.BOTTOM or Gravity.START,\n                        startX,\n                        windowHeight - startTopY\n                    )\n                }\n\n                endBottomY - startBottomY > 500 -> {\n                    showAtLocation(view, Gravity.TOP or Gravity.START, startX, startBottomY)\n                }\n\n                else -> {\n                    showAtLocation(view, Gravity.TOP or Gravity.START, endX, endBottomY)\n                }\n            }\n        } else {\n            contentView.measure(\n                View.MeasureSpec.UNSPECIFIED,\n                View.MeasureSpec.UNSPECIFIED,\n            )\n            val popupHeight = contentView.measuredHeight\n            when {\n                startBottomY > 500 -> {\n                    showAtLocation(\n                        view,\n                        Gravity.TOP or Gravity.START,\n                        startX,\n                        startTopY - popupHeight\n                    )\n                }\n\n                endBottomY - startBottomY > 500 -> {\n                    showAtLocation(\n                        view,\n                        Gravity.TOP or Gravity.START,\n                        startX,\n                        startBottomY\n                    )\n                }\n\n                else -> {\n                    showAtLocation(\n                        view,\n                        Gravity.TOP or Gravity.START,\n                        endX,\n                        endBottomY\n                    )\n                }\n            }\n        }\n    }\n\n    inner class Adapter(context: Context) :\n        RecyclerAdapter<MenuItemImpl, ItemTextBinding>(context) {\n\n        override fun getItemId(position: Int): Long {\n            return position.toLong()\n        }\n\n        override fun getViewBinding(parent: ViewGroup): ItemTextBinding {\n            return ItemTextBinding.inflate(inflater, parent, false)\n        }\n\n        override fun convert(\n            holder: ItemViewHolder,\n            binding: ItemTextBinding,\n            item: MenuItemImpl,\n            payloads: MutableList<Any>\n        ) {\n            with(binding) {\n                textView.text = item.title\n            }\n        }\n\n        override fun registerListener(holder: ItemViewHolder, binding: ItemTextBinding) {\n            holder.itemView.setOnClickListener {\n                getItem(holder.layoutPosition)?.let {\n                    if (!callBack.onMenuItemSelected(it.itemId)) {\n                        onMenuItemSelected(it)\n                    }\n                }\n                callBack.onMenuActionFinally()\n            }\n            holder.itemView.setOnLongClickListener {\n                if (AppConfig.contentSelectSpeakMod == 0) {\n                    AppConfig.contentSelectSpeakMod = 1\n                    context.toastOnUi(\"切换为从选择的地方开始一直朗读\")\n                } else {\n                    AppConfig.contentSelectSpeakMod = 0\n                    context.toastOnUi(\"切换为朗读选择内容\")\n                }\n                true\n            }\n        }\n    }\n\n    private fun onMenuItemSelected(item: MenuItemImpl) {\n        when (item.itemId) {\n            R.id.menu_copy -> context.sendToClip(callBack.selectedText)\n            R.id.menu_share_str -> context.share(callBack.selectedText)\n            R.id.menu_browser -> {\n                kotlin.runCatching {\n                    val intent = if (callBack.selectedText.isAbsUrl()) {\n                        Intent(Intent.ACTION_VIEW).apply {\n                            data = Uri.parse(callBack.selectedText)\n                        }\n                    } else {\n                        Intent(Intent.ACTION_WEB_SEARCH).apply {\n                            putExtra(SearchManager.QUERY, callBack.selectedText)\n                        }\n                    }\n                    context.startActivity(intent)\n                }.onFailure {\n                    it.printOnDebug()\n                    context.toastOnUi(it.localizedMessage ?: \"ERROR\")\n                }\n            }\n\n            else -> item.intent?.let {\n                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {\n                    kotlin.runCatching {\n                        it.putExtra(Intent.EXTRA_PROCESS_TEXT, callBack.selectedText)\n                        context.startActivity(it)\n                    }.onFailure { e ->\n                        AppLog.put(\"执行文本菜单操作出错\\n$e\", e, true)\n                    }\n                }\n            }\n        }\n    }\n\n    @RequiresApi(Build.VERSION_CODES.M)\n    private fun createProcessTextIntent(): Intent {\n        return Intent()\n            .setAction(Intent.ACTION_PROCESS_TEXT)\n            .setType(\"text/plain\")\n    }\n\n    @RequiresApi(Build.VERSION_CODES.M)\n    private fun getSupportedActivities(): List<ResolveInfo> {\n        return context.packageManager\n            .queryIntentActivities(createProcessTextIntent(), 0)\n    }\n\n    @RequiresApi(Build.VERSION_CODES.M)\n    private fun createProcessTextIntentForResolveInfo(info: ResolveInfo): Intent {\n        return createProcessTextIntent()\n            .putExtra(Intent.EXTRA_PROCESS_TEXT_READONLY, false)\n            .setClassName(info.activityInfo.packageName, info.activityInfo.name)\n    }\n\n    /**\n     * Start with a menu Item order value that is high enough\n     * so that your \"PROCESS_TEXT\" menu items appear after the\n     * standard selection menu items like Cut, Copy, Paste.\n     */\n    @RequiresApi(Build.VERSION_CODES.M)\n    private fun onInitializeMenu(menu: Menu) {\n        kotlin.runCatching {\n            var menuItemOrder = 100\n            for (resolveInfo in getSupportedActivities()) {\n                menu.add(\n                    Menu.NONE, Menu.NONE,\n                    menuItemOrder++, resolveInfo.loadLabel(context.packageManager)\n                ).intent = createProcessTextIntentForResolveInfo(resolveInfo)\n            }\n        }.onFailure {\n            context.toastOnUi(\"获取文字操作菜单出错:${it.localizedMessage}\")\n        }\n    }\n\n    interface CallBack {\n        val selectedText: String\n\n        fun onMenuItemSelected(itemId: Int): Boolean\n\n        fun onMenuActionFinally()\n    }\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/book/read/config/AutoReadDialog.kt",
    "content": "package io.legado.app.ui.book.read.config\n\nimport android.content.DialogInterface\nimport android.graphics.PorterDuff\nimport android.os.Bundle\nimport android.view.Gravity\nimport android.view.View\nimport android.view.ViewGroup\nimport android.view.WindowManager\nimport android.widget.SeekBar\nimport io.legado.app.R\nimport io.legado.app.base.BaseDialogFragment\nimport io.legado.app.databinding.DialogAutoReadBinding\nimport io.legado.app.help.config.ReadBookConfig\nimport io.legado.app.lib.theme.bottomBackground\nimport io.legado.app.lib.theme.getPrimaryTextColor\nimport io.legado.app.model.ReadAloud\nimport io.legado.app.model.ReadBook\nimport io.legado.app.service.BaseReadAloudService\nimport io.legado.app.ui.book.read.BaseReadBookActivity\nimport io.legado.app.ui.book.read.ReadBookActivity\nimport io.legado.app.ui.widget.seekbar.SeekBarChangeListener\nimport io.legado.app.utils.ColorUtils\nimport io.legado.app.utils.viewbindingdelegate.viewBinding\nimport java.util.Locale\n\n\nclass AutoReadDialog : BaseDialogFragment(R.layout.dialog_auto_read) {\n\n    private val binding by viewBinding(DialogAutoReadBinding::bind)\n    private val callBack: CallBack? get() = activity as? CallBack\n\n    override fun onStart() {\n        super.onStart()\n        dialog?.window?.run {\n            clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND)\n            setBackgroundDrawableResource(R.color.background)\n            decorView.setPadding(0, 0, 0, 0)\n            val attr = attributes\n            attr.dimAmount = 0.0f\n            attr.gravity = Gravity.BOTTOM\n            attributes = attr\n            setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)\n        }\n    }\n\n    override fun onDismiss(dialog: DialogInterface) {\n        super.onDismiss(dialog)\n        (activity as ReadBookActivity).bottomDialog--\n    }\n\n    override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) = binding.run {\n        val bottomDialog = (activity as ReadBookActivity).bottomDialog++\n        if (bottomDialog > 0) {\n            dismiss()\n            return@run\n        }\n        val bg = requireContext().bottomBackground\n        val isLight = ColorUtils.isColorLight(bg)\n        val textColor = requireContext().getPrimaryTextColor(isLight)\n        root.setBackgroundColor(bg)\n        tvReadSpeedTitle.setTextColor(textColor)\n        tvReadSpeed.setTextColor(textColor)\n        ivCatalog.setColorFilter(textColor, PorterDuff.Mode.SRC_IN)\n        tvCatalog.setTextColor(textColor)\n        ivMainMenu.setColorFilter(textColor, PorterDuff.Mode.SRC_IN)\n        tvMainMenu.setTextColor(textColor)\n        ivAutoPageStop.setColorFilter(textColor, PorterDuff.Mode.SRC_IN)\n        tvAutoPageStop.setTextColor(textColor)\n        ivSetting.setColorFilter(textColor, PorterDuff.Mode.SRC_IN)\n        tvSetting.setTextColor(textColor)\n        initOnChange()\n        initData()\n        initEvent()\n    }\n\n    private fun initData() {\n        val speed = if (ReadBookConfig.autoReadSpeed < 1) 1 else ReadBookConfig.autoReadSpeed\n        binding.tvReadSpeed.text = String.format(Locale.ROOT, \"%ds\", speed)\n        binding.seekAutoRead.progress = speed\n    }\n\n    private fun initOnChange() {\n        binding.seekAutoRead.setOnSeekBarChangeListener(object : SeekBarChangeListener {\n            override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {\n                val speed = if (progress < 1) 1 else progress\n                binding.tvReadSpeed.text = String.format(Locale.ROOT, \"%ds\", speed)\n            }\n\n            override fun onStopTrackingTouch(seekBar: SeekBar) {\n                ReadBookConfig.autoReadSpeed =\n                    if (binding.seekAutoRead.progress < 1) 1 else binding.seekAutoRead.progress\n                upTtsSpeechRate()\n            }\n        })\n    }\n\n    private fun initEvent() {\n        binding.llMainMenu.setOnClickListener {\n            callBack?.showMenuBar()\n            dismissAllowingStateLoss()\n        }\n        binding.llSetting.setOnClickListener {\n            (activity as BaseReadBookActivity).showPageAnimConfig {\n                (activity as ReadBookActivity).upPageAnim()\n                ReadBook.loadContent(false)\n            }\n        }\n        binding.llCatalog.setOnClickListener { callBack?.openChapterList() }\n        binding.llAutoPageStop.setOnClickListener {\n            callBack?.autoPageStop()\n            binding.llAutoPageStop.post {\n                dismissAllowingStateLoss()\n            }\n        }\n    }\n\n    private fun upTtsSpeechRate() {\n        ReadAloud.upTtsSpeechRate(requireContext())\n        if (!BaseReadAloudService.pause) {\n            ReadAloud.pause(requireContext())\n            ReadAloud.resume(requireContext())\n        }\n    }\n\n    interface CallBack {\n        fun showMenuBar()\n        fun openChapterList()\n        fun autoPageStop()\n    }\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/book/read/config/BgAdapter.kt",
    "content": "package io.legado.app.ui.book.read.config\n\nimport android.content.Context\nimport android.view.ViewGroup\nimport io.legado.app.base.adapter.ItemViewHolder\nimport io.legado.app.base.adapter.RecyclerAdapter\nimport io.legado.app.constant.EventBus\nimport io.legado.app.databinding.ItemBgImageBinding\nimport io.legado.app.help.config.ReadBookConfig\nimport io.legado.app.help.glide.ImageLoader\nimport io.legado.app.utils.postEvent\nimport java.io.File\n\nclass BgAdapter(context: Context, val textColor: Int) :\n    RecyclerAdapter<String, ItemBgImageBinding>(context) {\n\n    override fun getViewBinding(parent: ViewGroup): ItemBgImageBinding {\n        return ItemBgImageBinding.inflate(inflater, parent, false)\n    }\n\n    override fun convert(\n        holder: ItemViewHolder,\n        binding: ItemBgImageBinding,\n        item: String,\n        payloads: MutableList<Any>\n    ) {\n        binding.run {\n            ImageLoader.load(\n                context,\n                context.assets.open(\"bg${File.separator}$item\").readBytes()\n            )\n                .centerCrop()\n                .into(ivBg)\n            tvName.setTextColor(textColor)\n            tvName.text = item.substringBeforeLast(\".\")\n        }\n    }\n\n    override fun registerListener(holder: ItemViewHolder, binding: ItemBgImageBinding) {\n        holder.itemView.apply {\n            this.setOnClickListener {\n                getItemByLayoutPosition(holder.layoutPosition)?.let {\n                    ReadBookConfig.durConfig.setCurBg(1, it)\n                    postEvent(EventBus.UP_CONFIG, arrayListOf(1))\n                }\n            }\n        }\n    }\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/book/read/config/BgTextConfigDialog.kt",
    "content": "package io.legado.app.ui.book.read.config\n\nimport android.annotation.SuppressLint\nimport android.content.DialogInterface\nimport android.graphics.PorterDuff\nimport android.net.Uri\nimport android.os.Bundle\nimport android.view.Gravity\nimport android.view.View\nimport android.view.ViewGroup\nimport android.view.WindowManager\nimport android.widget.SeekBar\nimport androidx.appcompat.widget.TooltipCompat\nimport androidx.core.graphics.toColorInt\nimport androidx.core.view.isGone\nimport com.jaredrummler.android.colorpicker.ColorPickerDialog\nimport io.legado.app.R\nimport io.legado.app.base.BaseDialogFragment\nimport io.legado.app.constant.AppLog\nimport io.legado.app.constant.EventBus\nimport io.legado.app.databinding.DialogEditTextBinding\nimport io.legado.app.databinding.DialogReadBgTextBinding\nimport io.legado.app.databinding.ItemBgImageBinding\nimport io.legado.app.help.DefaultData\nimport io.legado.app.help.book.isImage\nimport io.legado.app.help.config.ReadBookConfig\nimport io.legado.app.help.http.newCallResponseBody\nimport io.legado.app.help.http.okHttpClient\nimport io.legado.app.lib.dialogs.SelectItem\nimport io.legado.app.lib.dialogs.alert\nimport io.legado.app.lib.dialogs.selector\nimport io.legado.app.lib.theme.bottomBackground\nimport io.legado.app.lib.theme.getPrimaryTextColor\nimport io.legado.app.lib.theme.getSecondaryTextColor\nimport io.legado.app.model.ReadBook\nimport io.legado.app.ui.book.read.ReadBookActivity\nimport io.legado.app.ui.file.HandleFileContract\nimport io.legado.app.ui.widget.seekbar.SeekBarChangeListener\nimport io.legado.app.utils.ColorUtils\nimport io.legado.app.utils.FileDoc\nimport io.legado.app.utils.FileUtils\nimport io.legado.app.utils.GSON\nimport io.legado.app.utils.MD5Utils\nimport io.legado.app.utils.compress.ZipUtils\nimport io.legado.app.utils.createFileIfNotExist\nimport io.legado.app.utils.createFileReplace\nimport io.legado.app.utils.createFolderReplace\nimport io.legado.app.utils.delete\nimport io.legado.app.utils.externalCache\nimport io.legado.app.utils.externalFiles\nimport io.legado.app.utils.find\nimport io.legado.app.utils.getFile\nimport io.legado.app.utils.inputStream\nimport io.legado.app.utils.longToast\nimport io.legado.app.utils.openInputStream\nimport io.legado.app.utils.openOutputStream\nimport io.legado.app.utils.outputStream\nimport io.legado.app.utils.postEvent\nimport io.legado.app.utils.printOnDebug\nimport io.legado.app.utils.readBytes\nimport io.legado.app.utils.readUri\nimport io.legado.app.utils.stackTraceStr\nimport io.legado.app.utils.toastOnUi\nimport io.legado.app.utils.viewbindingdelegate.viewBinding\nimport splitties.init.appCtx\nimport java.io.File\nimport java.io.FileOutputStream\n\nclass BgTextConfigDialog : BaseDialogFragment(R.layout.dialog_read_bg_text) {\n\n    companion object {\n        const val TEXT_COLOR = 121\n        const val BG_COLOR = 122\n    }\n\n    private val binding by viewBinding(DialogReadBgTextBinding::bind)\n    private val configFileName = \"readConfig.zip\"\n    private val adapter by lazy { BgAdapter(requireContext(), secondaryTextColor) }\n    private var primaryTextColor = 0\n    private var secondaryTextColor = 0\n    private val importFormNet = \"网络导入\"\n    private val selectBgImage = registerForActivityResult(HandleFileContract()) {\n        it.uri?.let { uri ->\n            setBgFromUri(uri)\n        }\n    }\n    private val selectExportDir = registerForActivityResult(HandleFileContract()) {\n        it.uri?.let { uri ->\n            exportConfig(uri)\n        }\n    }\n    private val selectImportDoc = registerForActivityResult(HandleFileContract()) {\n        it.uri?.let { uri ->\n            if (uri.path == \"/$importFormNet\") {\n                importNetConfigAlert()\n            } else {\n                importConfig(uri)\n            }\n        }\n    }\n\n    override fun onStart() {\n        super.onStart()\n        dialog?.window?.run {\n            clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND)\n            setBackgroundDrawableResource(R.color.background)\n            decorView.setPadding(0, 0, 0, 0)\n            val attr = attributes\n            attr.dimAmount = 0.0f\n            attr.gravity = Gravity.BOTTOM\n            attributes = attr\n            setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)\n        }\n    }\n\n    override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) {\n        (activity as ReadBookActivity).bottomDialog++\n        initView()\n        initData()\n        initEvent()\n    }\n\n    override fun onDismiss(dialog: DialogInterface) {\n        super.onDismiss(dialog)\n        ReadBookConfig.save()\n        (activity as ReadBookActivity).bottomDialog--\n    }\n\n    private fun initView() = binding.run {\n        val bg = requireContext().bottomBackground\n        val isLight = ColorUtils.isColorLight(bg)\n        primaryTextColor = requireContext().getPrimaryTextColor(isLight)\n        secondaryTextColor = requireContext().getSecondaryTextColor(isLight)\n        rootView.setBackgroundColor(bg)\n        tvNameTitle.setTextColor(primaryTextColor)\n        tvName.setTextColor(secondaryTextColor)\n        ivEdit.setColorFilter(secondaryTextColor, PorterDuff.Mode.SRC_IN)\n        tvRestore.setTextColor(primaryTextColor)\n        swDarkStatusIcon.setTextColor(primaryTextColor)\n        swUnderline.setTextColor(primaryTextColor)\n        ivImport.setColorFilter(primaryTextColor, PorterDuff.Mode.SRC_IN)\n        ivExport.setColorFilter(primaryTextColor, PorterDuff.Mode.SRC_IN)\n        ivDelete.setColorFilter(primaryTextColor, PorterDuff.Mode.SRC_IN)\n        tvBgAlpha.setTextColor(primaryTextColor)\n        tvBgImage.setTextColor(primaryTextColor)\n        swUnderline.isGone = ReadBook.book?.isImage == true\n        recyclerView.adapter = adapter\n        adapter.addHeaderView {\n            ItemBgImageBinding.inflate(layoutInflater, it, false).apply {\n                tvName.setTextColor(secondaryTextColor)\n                tvName.text = getString(R.string.select_image)\n                ivBg.setImageResource(R.drawable.ic_image)\n                ivBg.setColorFilter(primaryTextColor, PorterDuff.Mode.SRC_IN)\n                root.setOnClickListener {\n                    selectBgImage.launch {\n                        mode = HandleFileContract.IMAGE\n                    }\n                }\n            }\n        }\n        requireContext().assets.list(\"bg\")?.let {\n            adapter.setItems(it.toList())\n        }\n    }\n\n    @SuppressLint(\"InflateParams\")\n    private fun initData() = with(ReadBookConfig.durConfig) {\n        binding.tvName.text = name.ifBlank { \"文字\" }\n        binding.swDarkStatusIcon.isChecked = curStatusIconDark()\n        binding.swUnderline.isChecked = underline\n        binding.sbBgAlpha.progress = bgAlpha\n    }\n\n    @SuppressLint(\"InflateParams\")\n    private fun initEvent() = with(ReadBookConfig.durConfig) {\n        binding.ivEdit.setOnClickListener {\n            alert(R.string.style_name) {\n                val alertBinding = DialogEditTextBinding.inflate(layoutInflater).apply {\n                    editView.hint = \"name\"\n                    editView.setText(ReadBookConfig.durConfig.name)\n                }\n                customView { alertBinding.root }\n                okButton {\n                    alertBinding.editView.text?.toString()?.let {\n                        binding.tvName.text = it\n                        ReadBookConfig.durConfig.name = it\n                    }\n                }\n                cancelButton()\n            }\n        }\n        binding.tvRestore.setOnClickListener {\n            val defaultConfigs = DefaultData.readConfigs\n            val layoutNames = defaultConfigs.map { it.name }\n            context?.selector(\"选择预设布局\", layoutNames) { _, i ->\n                if (i >= 0) {\n                    ReadBookConfig.durConfig = defaultConfigs[i].copy()\n                    initData()\n                    postEvent(EventBus.UP_CONFIG, arrayListOf(1, 2, 5))\n                }\n            }\n        }\n        binding.swDarkStatusIcon.setOnCheckedChangeListener { _, isChecked ->\n            setCurStatusIconDark(isChecked)\n            (activity as? ReadBookActivity)?.upSystemUiVisibility()\n        }\n        binding.swUnderline.setOnCheckedChangeListener { _, isChecked ->\n            underline = isChecked\n            postEvent(EventBus.UP_CONFIG, arrayListOf(6, 9, 11))\n        }\n        binding.tvTextColor.setOnClickListener {\n            ColorPickerDialog.newBuilder()\n                .setColor(curTextColor())\n                .setShowAlphaSlider(false)\n                .setDialogType(ColorPickerDialog.TYPE_CUSTOM)\n                .setDialogId(TEXT_COLOR)\n                .show(requireActivity())\n        }\n        binding.tvBgColor.setOnClickListener {\n            val bgColor =\n                if (curBgType() == 0) curBgStr().toColorInt()\n                else \"#015A86\".toColorInt()\n            ColorPickerDialog.newBuilder()\n                .setColor(bgColor)\n                .setShowAlphaSlider(false)\n                .setDialogType(ColorPickerDialog.TYPE_CUSTOM)\n                .setDialogId(BG_COLOR)\n                .show(requireActivity())\n        }\n        binding.tvBgColor.apply {\n            TooltipCompat.setTooltipText(this, text)\n        }\n        binding.ivImport.setOnClickListener {\n            selectImportDoc.launch {\n                mode = HandleFileContract.FILE\n                title = getString(R.string.import_str)\n                allowExtensions = arrayOf(\"zip\")\n                otherActions = arrayListOf(SelectItem(importFormNet, -1))\n            }\n        }\n        binding.ivExport.setOnClickListener {\n            selectExportDir.launch {\n                title = getString(R.string.export_str)\n            }\n        }\n        binding.ivDelete.setOnClickListener {\n            if (ReadBookConfig.deleteDur()) {\n                postEvent(EventBus.UP_CONFIG, arrayListOf(1, 2, 5))\n                dismissAllowingStateLoss()\n            } else {\n                toastOnUi(\"数量已是最少,不能删除.\")\n            }\n        }\n        binding.sbBgAlpha.setOnSeekBarChangeListener(object : SeekBarChangeListener {\n            override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {\n                ReadBookConfig.bgAlpha = progress\n                postEvent(EventBus.UP_CONFIG, arrayListOf(3))\n            }\n\n            override fun onStopTrackingTouch(seekBar: SeekBar) {\n                postEvent(EventBus.UP_CONFIG, arrayListOf(3))\n            }\n        })\n    }\n\n    private fun exportConfig(uri: Uri) {\n        val exportFileName = if (ReadBookConfig.config.name.isBlank()) {\n            configFileName\n        } else {\n            \"${ReadBookConfig.config.name}.zip\"\n        }\n        execute {\n            val exportFiles = arrayListOf<File>()\n            val configDir = requireContext().externalCache.getFile(\"readConfig\")\n            configDir.createFolderReplace()\n            val configFile = configDir.getFile(\"readConfig.json\")\n            configFile.createFileReplace()\n            val config = ReadBookConfig.getExportConfig()\n            val fontPath = ReadBookConfig.textFont\n            if (fontPath.isNotEmpty()) {\n                val fontDoc = FileDoc.fromFile(fontPath)\n                val fontName = fontDoc.name\n                val fontInputStream = fontDoc.openInputStream().getOrNull()\n                fontInputStream?.use {\n                    val fontExportFile = FileUtils.createFileIfNotExist(configDir, fontName)\n                    fontExportFile.outputStream().use { out ->\n                        it.copyTo(out)\n                    }\n                    config.textFont = fontName\n                    exportFiles.add(fontExportFile)\n                }\n            }\n            configFile.writeText(GSON.toJson(config))\n            exportFiles.add(configFile)\n            repeat(3) {\n                val path = ReadBookConfig.durConfig.getBgPath(it) ?: return@repeat\n                val bgExportFile = copyBgImage(path, configDir) ?: return@repeat\n                exportFiles.add(bgExportFile)\n            }\n            val configZipPath = FileUtils.getPath(requireContext().externalCache, configFileName)\n            if (ZipUtils.zipFiles(exportFiles, File(configZipPath))) {\n                val exportDir = FileDoc.fromDir(uri)\n                exportDir.find(exportFileName)?.delete()\n                val exportFileDoc = exportDir.createFileIfNotExist(exportFileName)\n                exportFileDoc.openOutputStream().getOrThrow().use { out ->\n                    File(configZipPath).inputStream().use {\n                        it.copyTo(out)\n                    }\n                }\n            }\n        }.onSuccess {\n            toastOnUi(\"导出成功, 文件名为 $exportFileName\")\n        }.onError {\n            it.printOnDebug()\n            AppLog.put(\"导出失败:${it.localizedMessage}\", it)\n            longToast(\"导出失败:${it.localizedMessage}\")\n        }\n    }\n\n    private fun copyBgImage(path: String, configDir: File): File? {\n        val bgName = FileUtils.getName(path)\n        val bgFile = File(path)\n        if (bgFile.exists()) {\n            val bgExportFile = File(FileUtils.getPath(configDir, bgName))\n            if (!bgExportFile.exists()) {\n                bgFile.copyTo(bgExportFile)\n                return bgExportFile\n            }\n        }\n        return null\n    }\n\n    @SuppressLint(\"InflateParams\")\n    private fun importNetConfigAlert() {\n        alert(\"输入地址\") {\n            val alertBinding = DialogEditTextBinding.inflate(layoutInflater)\n            customView { alertBinding.root }\n            okButton {\n                alertBinding.editView.text?.toString()?.let { url ->\n                    importNetConfig(url)\n                }\n            }\n            cancelButton()\n        }\n    }\n\n    private fun importNetConfig(url: String) {\n        execute {\n            okHttpClient.newCallResponseBody {\n                url(url)\n            }.bytes().let {\n                importConfig(it)\n            }\n        }.onError {\n            longToast(it.stackTraceStr)\n        }\n    }\n\n    private fun importConfig(uri: Uri) {\n        execute {\n            importConfig(uri.readBytes(requireContext()))\n        }.onError {\n            it.printOnDebug()\n            longToast(\"导入失败:${it.localizedMessage}\")\n        }\n    }\n\n    private fun importConfig(byteArray: ByteArray) {\n        execute {\n            ReadBookConfig.import(byteArray)\n        }.onSuccess {\n            ReadBookConfig.durConfig = it\n            postEvent(EventBus.UP_CONFIG, arrayListOf(1, 2, 5))\n            toastOnUi(\"导入成功\")\n        }.onError {\n            it.printOnDebug()\n            longToast(\"导入失败:${it.localizedMessage}\")\n        }\n    }\n\n    private fun setBgFromUri(uri: Uri) {\n        readUri(uri) { fileDoc, inputStream ->\n            kotlin.runCatching {\n                var file = requireContext().externalFiles\n                val suffix = fileDoc.name.substringAfterLast(\".\")\n                val fileName = uri.inputStream(requireContext()).getOrThrow().use {\n                    MD5Utils.md5Encode(it) + \".$suffix\"\n                }\n                file = FileUtils.createFileIfNotExist(file, \"bg\", fileName)\n                FileOutputStream(file).use { outputStream ->\n                    inputStream.copyTo(outputStream)\n                }\n                ReadBookConfig.durConfig.setCurBg(2, fileName)\n                postEvent(EventBus.UP_CONFIG, arrayListOf(1))\n            }.onFailure {\n                appCtx.toastOnUi(it.localizedMessage)\n            }\n        }\n    }\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/book/read/config/ChineseConverter.kt",
    "content": "package io.legado.app.ui.book.read.config\n\nimport android.content.Context\nimport android.text.Spannable\nimport android.text.SpannableString\nimport android.text.style.ForegroundColorSpan\nimport android.util.AttributeSet\nimport io.legado.app.R\nimport io.legado.app.help.config.AppConfig\nimport io.legado.app.lib.dialogs.alert\nimport io.legado.app.lib.theme.accentColor\nimport io.legado.app.ui.widget.text.StrokeTextView\n\n\nclass ChineseConverter(context: Context, attrs: AttributeSet?) : StrokeTextView(context, attrs) {\n\n    private val spannableString = SpannableString(\"简/繁\")\n    private var enabledSpan: ForegroundColorSpan = ForegroundColorSpan(context.accentColor)\n    private var onChanged: (() -> Unit)? = null\n\n    init {\n        text = spannableString\n        if (!isInEditMode) {\n            upUi(AppConfig.chineseConverterType)\n        }\n        setOnClickListener {\n            selectType()\n        }\n    }\n\n    private fun upUi(type: Int) {\n        spannableString.removeSpan(enabledSpan)\n        when (type) {\n            1 -> spannableString.setSpan(enabledSpan, 0, 1, Spannable.SPAN_INCLUSIVE_EXCLUSIVE)\n            2 -> spannableString.setSpan(enabledSpan, 2, 3, Spannable.SPAN_INCLUSIVE_EXCLUSIVE)\n        }\n        text = spannableString\n    }\n\n    private fun selectType() {\n        context.alert(titleResource = R.string.chinese_converter) {\n            items(context.resources.getStringArray(R.array.chinese_mode).toList()) { _, i ->\n                AppConfig.chineseConverterType = i\n                upUi(i)\n                onChanged?.invoke()\n            }\n        }\n    }\n\n    fun onChanged(unit: () -> Unit) {\n        onChanged = unit\n    }\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/book/read/config/ClickActionConfigDialog.kt",
    "content": "package io.legado.app.ui.book.read.config\n\nimport android.content.DialogInterface\nimport android.os.Bundle\nimport android.view.View\nimport android.view.ViewGroup\nimport android.widget.TextView\nimport io.legado.app.R\nimport io.legado.app.base.BaseDialogFragment\nimport io.legado.app.constant.PreferKey\nimport io.legado.app.databinding.DialogClickActionConfigBinding\nimport io.legado.app.help.config.AppConfig\nimport io.legado.app.lib.dialogs.selector\nimport io.legado.app.ui.book.read.ReadBookActivity\nimport io.legado.app.utils.getCompatColor\nimport io.legado.app.utils.putPrefInt\nimport io.legado.app.utils.viewbindingdelegate.viewBinding\n\n/**\n * 点击区域设置\n */\nclass ClickActionConfigDialog : BaseDialogFragment(R.layout.dialog_click_action_config) {\n    private val binding by viewBinding(DialogClickActionConfigBinding::bind)\n    private val actions by lazy {\n        linkedMapOf(\n            Pair(-1, getString(R.string.non_action)),\n            Pair(0, getString(R.string.menu)),\n            Pair(1, getString(R.string.next_page)),\n            Pair(2, getString(R.string.prev_page)),\n            Pair(3, getString(R.string.next_chapter)),\n            Pair(4, getString(R.string.previous_chapter)),\n            Pair(5, getString(R.string.read_aloud_prev_paragraph)),\n            Pair(6, getString(R.string.read_aloud_next_paragraph)),\n            Pair(7, getString(R.string.bookmark_add)),\n            Pair(8, getString(R.string.edit_content)),\n            Pair(9, getString(R.string.replace_state_change)),\n            Pair(10, getString(R.string.chapter_list)),\n            Pair(11, getString(R.string.search_content)),\n            Pair(12, getString(R.string.sync_book_progress_t)),\n            Pair(13, getString(R.string.read_aloud_pause_resume))\n        )\n    }\n\n    override fun onStart() {\n        super.onStart()\n        dialog?.window?.run {\n            setBackgroundDrawableResource(R.color.transparent)\n            setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)\n        }\n    }\n\n    override fun onDismiss(dialog: DialogInterface) {\n        super.onDismiss(dialog)\n        (activity as ReadBookActivity).bottomDialog--\n    }\n\n    override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) {\n        (activity as ReadBookActivity).bottomDialog++\n        view.setBackgroundColor(getCompatColor(R.color.translucent))\n        initData()\n        initViewEvent()\n    }\n\n    private fun initData() = binding.run {\n        tvTopLeft.text = actions[AppConfig.clickActionTL]\n        tvTopCenter.text = actions[AppConfig.clickActionTC]\n        tvTopRight.text = actions[AppConfig.clickActionTR]\n        tvMiddleLeft.text = actions[AppConfig.clickActionML]\n        tvMiddleCenter.text = actions[AppConfig.clickActionMC]\n        tvMiddleRight.text = actions[AppConfig.clickActionMR]\n        tvBottomLeft.text = actions[AppConfig.clickActionBL]\n        tvBottomCenter.text = actions[AppConfig.clickActionBC]\n        tvBottomRight.text = actions[AppConfig.clickActionBR]\n    }\n\n    private fun initViewEvent() {\n        binding.ivClose.setOnClickListener {\n            dismissAllowingStateLoss()\n        }\n        binding.tvTopLeft.setOnClickListener {\n            selectAction { action ->\n                putPrefInt(PreferKey.clickActionTL, action)\n                (it as? TextView)?.text = actions[action]\n            }\n        }\n        binding.tvTopCenter.setOnClickListener {\n            selectAction { action ->\n                putPrefInt(PreferKey.clickActionTC, action)\n                (it as? TextView)?.text = actions[action]\n            }\n        }\n        binding.tvTopRight.setOnClickListener {\n            selectAction { action ->\n                putPrefInt(PreferKey.clickActionTR, action)\n                (it as? TextView)?.text = actions[action]\n            }\n        }\n        binding.tvMiddleLeft.setOnClickListener {\n            selectAction { action ->\n                putPrefInt(PreferKey.clickActionML, action)\n                (it as? TextView)?.text = actions[action]\n            }\n        }\n        binding.tvMiddleCenter.setOnClickListener {\n            selectAction { action ->\n                putPrefInt(PreferKey.clickActionMC, action)\n                (it as? TextView)?.text = actions[action]\n            }\n        }\n        binding.tvMiddleRight.setOnClickListener {\n            selectAction { action ->\n                putPrefInt(PreferKey.clickActionMR, action)\n                (it as? TextView)?.text = actions[action]\n            }\n        }\n        binding.tvBottomLeft.setOnClickListener {\n            selectAction { action ->\n                putPrefInt(PreferKey.clickActionBL, action)\n                (it as? TextView)?.text = actions[action]\n            }\n        }\n        binding.tvBottomCenter.setOnClickListener {\n            selectAction { action ->\n                putPrefInt(PreferKey.clickActionBC, action)\n                (it as? TextView)?.text = actions[action]\n            }\n        }\n        binding.tvBottomRight.setOnClickListener {\n            selectAction { action ->\n                putPrefInt(PreferKey.clickActionBR, action)\n                (it as? TextView)?.text = actions[action]\n            }\n        }\n    }\n\n    private fun selectAction(success: (action: Int) -> Unit) {\n        context?.selector(\n            getString(R.string.select_action),\n            actions.values.toList()\n        ) { _, index ->\n            success.invoke(actions.keys.toList()[index])\n        }\n    }\n\n    override fun onDestroy() {\n        super.onDestroy()\n        AppConfig.detectClickArea()\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/book/read/config/HttpTtsEditDialog.kt",
    "content": "package io.legado.app.ui.book.read.config\n\nimport android.os.Bundle\nimport android.view.MenuItem\nimport android.view.View\nimport android.view.ViewGroup\nimport androidx.appcompat.widget.Toolbar\nimport androidx.fragment.app.viewModels\nimport io.legado.app.R\nimport io.legado.app.base.BaseDialogFragment\nimport io.legado.app.data.entities.HttpTTS\nimport io.legado.app.databinding.DialogHttpTtsEditBinding\nimport io.legado.app.lib.dialogs.alert\nimport io.legado.app.lib.theme.primaryColor\nimport io.legado.app.ui.about.AppLogDialog\nimport io.legado.app.ui.login.SourceLoginActivity\nimport io.legado.app.ui.widget.code.addJsPattern\nimport io.legado.app.ui.widget.code.addJsonPattern\nimport io.legado.app.ui.widget.code.addLegadoPattern\nimport io.legado.app.utils.GSON\nimport io.legado.app.utils.applyTint\nimport io.legado.app.utils.sendToClip\nimport io.legado.app.utils.setLayout\nimport io.legado.app.utils.showDialogFragment\nimport io.legado.app.utils.showHelp\nimport io.legado.app.utils.startActivity\nimport io.legado.app.utils.toastOnUi\nimport io.legado.app.utils.viewbindingdelegate.viewBinding\n\nclass HttpTtsEditDialog() : BaseDialogFragment(R.layout.dialog_http_tts_edit, true),\n    Toolbar.OnMenuItemClickListener {\n\n    constructor(id: Long) : this() {\n        arguments = Bundle().apply {\n            putLong(\"id\", id)\n        }\n    }\n\n    private val binding by viewBinding(DialogHttpTtsEditBinding::bind)\n    private val viewModel by viewModels<HttpTtsEditViewModel>()\n\n    override fun onStart() {\n        super.onStart()\n        setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)\n    }\n\n    override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) {\n        binding.toolBar.setBackgroundColor(primaryColor)\n        binding.tvUrl.run {\n            addLegadoPattern()\n            addJsonPattern()\n            addJsPattern()\n        }\n        binding.tvLoginUrl.run {\n            addLegadoPattern()\n            addJsonPattern()\n            addJsPattern()\n        }\n        binding.tvLoginUi.addJsonPattern()\n        binding.tvLoginCheckJs.addJsPattern()\n        binding.tvHeaders.run {\n            addLegadoPattern()\n            addJsonPattern()\n            addJsPattern()\n        }\n        viewModel.initData(arguments) {\n            initView(httpTTS = it)\n        }\n        initMenu()\n    }\n\n    fun initMenu() {\n        binding.toolBar.inflateMenu(R.menu.speak_engine_edit)\n        binding.toolBar.menu.applyTint(requireContext())\n        binding.toolBar.setOnMenuItemClickListener(this)\n    }\n\n    fun initView(httpTTS: HttpTTS) {\n        binding.tvName.setText(httpTTS.name)\n        binding.tvUrl.setText(httpTTS.url)\n        binding.tvContentType.setText(httpTTS.contentType)\n        binding.tvConcurrentRate.setText(httpTTS.concurrentRate)\n        binding.tvLoginUrl.setText(httpTTS.loginUrl)\n        binding.tvLoginUi.setText(httpTTS.loginUi)\n        binding.tvLoginCheckJs.setText(httpTTS.loginCheckJs)\n        binding.tvHeaders.setText(httpTTS.header)\n    }\n\n    override fun onMenuItemClick(item: MenuItem?): Boolean {\n        when (item?.itemId) {\n            R.id.menu_save -> viewModel.save(dataFromView()) {\n                toastOnUi(\"保存成功\")\n            }\n            R.id.menu_login -> dataFromView().let { httpTts ->\n                if (httpTts.loginUrl.isNullOrBlank()) {\n                    toastOnUi(\"登录url不能为空\")\n                } else {\n                    viewModel.save(httpTts) {\n                        startActivity<SourceLoginActivity> {\n                            putExtra(\"type\", \"httpTts\")\n                            putExtra(\"key\", httpTts.id.toString())\n                        }\n                    }\n                }\n            }\n            R.id.menu_show_login_header -> alert {\n                setTitle(R.string.login_header)\n                dataFromView().getLoginHeader()?.let { loginHeader ->\n                    setMessage(loginHeader)\n                }\n            }\n            R.id.menu_del_login_header -> dataFromView().removeLoginHeader()\n            R.id.menu_copy_source -> dataFromView().let {\n                context?.sendToClip(GSON.toJson(it))\n            }\n            R.id.menu_paste_source -> viewModel.importFromClip {\n                initView(it)\n            }\n            R.id.menu_log -> showDialogFragment<AppLogDialog>()\n            R.id.menu_help -> showHelp(\"httpTTSHelp\")\n        }\n        return true\n    }\n\n    private fun dataFromView(): HttpTTS {\n        return HttpTTS(\n            id = viewModel.id ?: System.currentTimeMillis(),\n            name = binding.tvName.text.toString(),\n            url = binding.tvUrl.text.toString(),\n            contentType = binding.tvContentType.text?.toString(),\n            concurrentRate = binding.tvConcurrentRate.text?.toString(),\n            loginUrl = binding.tvLoginUrl.text?.toString(),\n            loginUi = binding.tvLoginUi.text?.toString(),\n            loginCheckJs = binding.tvLoginCheckJs.text?.toString(),\n            header = binding.tvHeaders.text?.toString()\n        )\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/book/read/config/HttpTtsEditViewModel.kt",
    "content": "package io.legado.app.ui.book.read.config\n\nimport android.app.Application\nimport android.os.Bundle\nimport io.legado.app.base.BaseViewModel\nimport io.legado.app.data.appDb\nimport io.legado.app.data.entities.HttpTTS\nimport io.legado.app.exception.NoStackTraceException\nimport io.legado.app.model.ReadAloud\nimport io.legado.app.utils.getClipText\nimport io.legado.app.utils.isJsonArray\nimport io.legado.app.utils.isJsonObject\nimport io.legado.app.utils.toastOnUi\n\nclass HttpTtsEditViewModel(app: Application) : BaseViewModel(app) {\n\n    var id: Long? = null\n\n    fun initData(arguments: Bundle?, success: (httpTTS: HttpTTS) -> Unit) {\n        execute {\n            if (id == null) {\n                val argumentId = arguments?.getLong(\"id\")\n                if (argumentId != null && argumentId != 0L) {\n                    id = argumentId\n                    return@execute appDb.httpTTSDao.get(argumentId)\n                }\n            }\n            return@execute null\n        }.onSuccess {\n            it?.let {\n                success.invoke(it)\n            }\n        }\n    }\n\n    fun save(httpTTS: HttpTTS, success: (() -> Unit)? = null) {\n        id = httpTTS.id\n        execute {\n            appDb.httpTTSDao.insert(httpTTS)\n            if (ReadAloud.ttsEngine == httpTTS.id.toString()) ReadAloud.upReadAloudClass()\n        }.onSuccess {\n            success?.invoke()\n        }\n    }\n\n    fun importFromClip(onSuccess: (httpTTS: HttpTTS) -> Unit) {\n        val text = context.getClipText()\n        if (text.isNullOrBlank()) {\n            context.toastOnUi(\"剪贴板为空\")\n        } else {\n            importSource(text, onSuccess)\n        }\n    }\n\n    fun importSource(text: String, onSuccess: (httpTTS: HttpTTS) -> Unit) {\n        val text1 = text.trim()\n        execute {\n            when {\n                text1.isJsonObject() -> {\n                    HttpTTS.fromJson(text1).getOrThrow()\n                }\n                text1.isJsonArray() -> {\n                    HttpTTS.fromJsonArray(text1).getOrThrow().first()\n                }\n                else -> {\n                    throw NoStackTraceException(\"格式不对\")\n                }\n            }\n        }.onSuccess {\n            onSuccess.invoke(it)\n        }.onError {\n            context.toastOnUi(it.localizedMessage)\n        }\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/book/read/config/MoreConfigDialog.kt",
    "content": "package io.legado.app.ui.book.read.config\n\nimport android.annotation.SuppressLint\nimport android.content.DialogInterface\nimport android.content.SharedPreferences\nimport android.os.Bundle\nimport android.view.Gravity\nimport android.view.LayoutInflater\nimport android.view.View\nimport android.view.ViewConfiguration\nimport android.view.ViewGroup\nimport android.view.WindowManager\nimport android.widget.LinearLayout\nimport androidx.preference.Preference\nimport io.legado.app.R\nimport io.legado.app.base.BasePrefDialogFragment\nimport io.legado.app.constant.EventBus\nimport io.legado.app.constant.PreferKey\nimport io.legado.app.help.config.AppConfig\nimport io.legado.app.help.config.ReadBookConfig\nimport io.legado.app.lib.prefs.fragment.PreferenceFragment\nimport io.legado.app.lib.theme.bottomBackground\nimport io.legado.app.lib.theme.primaryColor\nimport io.legado.app.model.ReadBook\nimport io.legado.app.ui.book.read.ReadBookActivity\nimport io.legado.app.ui.book.read.page.provider.ChapterProvider\nimport io.legado.app.ui.widget.number.NumberPickerDialog\nimport io.legado.app.utils.canvasrecorder.CanvasRecorderFactory\nimport io.legado.app.utils.dpToPx\nimport io.legado.app.utils.getPrefBoolean\nimport io.legado.app.utils.postEvent\nimport io.legado.app.utils.removePref\nimport io.legado.app.utils.setEdgeEffectColor\n\nclass MoreConfigDialog : BasePrefDialogFragment() {\n    private val readPreferTag = \"readPreferenceFragment\"\n\n    override fun onStart() {\n        super.onStart()\n        dialog?.window?.run {\n            clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND)\n            setBackgroundDrawableResource(R.color.background)\n            decorView.setPadding(0, 0, 0, 0)\n            val attr = attributes\n            attr.dimAmount = 0.0f\n            attr.gravity = Gravity.BOTTOM\n            attributes = attr\n            setLayout(ViewGroup.LayoutParams.MATCH_PARENT, 360.dpToPx())\n        }\n    }\n\n    override fun onCreateView(\n        inflater: LayoutInflater,\n        container: ViewGroup?,\n        savedInstanceState: Bundle?\n    ): View {\n        (activity as ReadBookActivity).bottomDialog++\n        val view = LinearLayout(context)\n        view.setBackgroundColor(requireContext().bottomBackground)\n        view.id = R.id.tag1\n        container?.addView(view)\n        return view\n    }\n\n    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n        super.onViewCreated(view, savedInstanceState)\n        var preferenceFragment = childFragmentManager.findFragmentByTag(readPreferTag)\n        if (preferenceFragment == null) preferenceFragment = ReadPreferenceFragment()\n        childFragmentManager.beginTransaction()\n            .replace(view.id, preferenceFragment, readPreferTag)\n            .commit()\n    }\n\n    override fun onDismiss(dialog: DialogInterface) {\n        super.onDismiss(dialog)\n        (activity as ReadBookActivity).bottomDialog--\n    }\n\n    class ReadPreferenceFragment : PreferenceFragment(),\n        SharedPreferences.OnSharedPreferenceChangeListener {\n\n        private val slopSquare by lazy { ViewConfiguration.get(requireContext()).scaledTouchSlop }\n\n        @SuppressLint(\"RestrictedApi\")\n        override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {\n            addPreferencesFromResource(R.xml.pref_config_read)\n            upPreferenceSummary(PreferKey.pageTouchSlop, slopSquare.toString())\n            if (!CanvasRecorderFactory.isSupport) {\n                removePref(PreferKey.optimizeRender)\n                preferenceScreen.removePreferenceRecursively(PreferKey.optimizeRender)\n            }\n        }\n\n        override fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n            super.onViewCreated(view, savedInstanceState)\n            listView.setEdgeEffectColor(primaryColor)\n        }\n\n        override fun onResume() {\n            super.onResume()\n            preferenceManager\n                .sharedPreferences\n                ?.registerOnSharedPreferenceChangeListener(this)\n        }\n\n        override fun onPause() {\n            preferenceManager\n                .sharedPreferences\n                ?.unregisterOnSharedPreferenceChangeListener(this)\n            super.onPause()\n        }\n\n        override fun onSharedPreferenceChanged(\n            sharedPreferences: SharedPreferences?,\n            key: String?\n        ) {\n            when (key) {\n                PreferKey.readBodyToLh -> activity?.recreate()\n                PreferKey.hideStatusBar -> {\n                    ReadBookConfig.hideStatusBar = getPrefBoolean(PreferKey.hideStatusBar)\n                    postEvent(EventBus.UP_CONFIG, arrayListOf(0, 2))\n                }\n\n                PreferKey.hideNavigationBar -> {\n                    ReadBookConfig.hideNavigationBar = getPrefBoolean(PreferKey.hideNavigationBar)\n                    postEvent(EventBus.UP_CONFIG, arrayListOf(0, 2))\n                }\n\n                PreferKey.keepLight -> postEvent(key, true)\n                PreferKey.textSelectAble -> postEvent(key, getPrefBoolean(key))\n                PreferKey.screenOrientation -> {\n                    (activity as? ReadBookActivity)?.setOrientation()\n                }\n\n                PreferKey.textFullJustify,\n                PreferKey.textBottomJustify,\n                PreferKey.useZhLayout -> {\n                    postEvent(EventBus.UP_CONFIG, arrayListOf(5))\n                }\n\n                PreferKey.showBrightnessView -> {\n                    postEvent(PreferKey.showBrightnessView, \"\")\n                }\n\n                PreferKey.expandTextMenu -> {\n                    (activity as? ReadBookActivity)?.textActionMenu?.upMenu()\n                }\n\n                PreferKey.doublePageHorizontal -> {\n                    ChapterProvider.upLayout()\n                    ReadBook.loadContent(false)\n                }\n\n                PreferKey.showReadTitleAddition,\n                PreferKey.readBarStyleFollowPage -> {\n                    postEvent(EventBus.UPDATE_READ_ACTION_BAR, true)\n                }\n\n                PreferKey.progressBarBehavior -> {\n                    postEvent(EventBus.UP_SEEK_BAR, true)\n                }\n\n                PreferKey.noAnimScrollPage -> {\n                    ReadBook.callBack?.upPageAnim()\n                }\n\n                PreferKey.optimizeRender -> {\n                    ChapterProvider.upStyle()\n                    ReadBook.callBack?.upPageAnim(true)\n                    ReadBook.loadContent(false)\n                }\n\n                PreferKey.paddingDisplayCutouts -> {\n                    postEvent(EventBus.UP_CONFIG, arrayListOf(2))\n                }\n            }\n        }\n\n        override fun onPreferenceTreeClick(preference: Preference): Boolean {\n            when (preference.key) {\n                \"customPageKey\" -> PageKeyDialog(requireContext()).show()\n                \"clickRegionalConfig\" -> {\n                    (activity as? ReadBookActivity)?.showClickRegionalConfig()\n                }\n\n                PreferKey.pageTouchSlop -> {\n                    NumberPickerDialog(requireContext())\n                        .setTitle(getString(R.string.page_touch_slop_dialog_title))\n                        .setMaxValue(9999)\n                        .setMinValue(0)\n                        .setValue(AppConfig.pageTouchSlop)\n                        .show {\n                            AppConfig.pageTouchSlop = it\n                            postEvent(EventBus.UP_CONFIG, arrayListOf(4))\n                        }\n                }\n            }\n            return super.onPreferenceTreeClick(preference)\n        }\n\n        @Suppress(\"SameParameterValue\")\n        private fun upPreferenceSummary(preferenceKey: String, value: String?) {\n            val preference = findPreference<Preference>(preferenceKey) ?: return\n            when (preferenceKey) {\n                PreferKey.pageTouchSlop -> preference.summary =\n                    getString(R.string.page_touch_slop_summary, value)\n            }\n        }\n\n    }\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/book/read/config/PaddingConfigDialog.kt",
    "content": "package io.legado.app.ui.book.read.config\n\nimport android.content.DialogInterface\nimport android.os.Bundle\nimport android.view.View\nimport android.view.ViewGroup\nimport android.view.WindowManager\nimport io.legado.app.R\nimport io.legado.app.base.BaseDialogFragment\nimport io.legado.app.constant.EventBus\nimport io.legado.app.databinding.DialogReadPaddingBinding\nimport io.legado.app.help.config.ReadBookConfig\nimport io.legado.app.utils.postEvent\nimport io.legado.app.utils.setLayout\nimport io.legado.app.utils.viewbindingdelegate.viewBinding\n\nclass PaddingConfigDialog : BaseDialogFragment(R.layout.dialog_read_padding) {\n\n    private val binding by viewBinding(DialogReadPaddingBinding::bind)\n\n    override fun onStart() {\n        super.onStart()\n        dialog?.window?.let {\n            it.clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND)\n            val attr = it.attributes\n            attr.dimAmount = 0.0f\n            it.attributes = attr\n        }\n        setLayout(0.9f, ViewGroup.LayoutParams.WRAP_CONTENT)\n    }\n\n    override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) {\n        initData()\n        initView()\n    }\n\n    override fun onDismiss(dialog: DialogInterface) {\n        super.onDismiss(dialog)\n        ReadBookConfig.save()\n    }\n\n    private fun initData() = binding.run {\n        //正文\n        dsbPaddingTop.progress = ReadBookConfig.paddingTop\n        dsbPaddingBottom.progress = ReadBookConfig.paddingBottom\n        dsbPaddingLeft.progress = ReadBookConfig.paddingLeft\n        dsbPaddingRight.progress = ReadBookConfig.paddingRight\n        //页眉\n        dsbHeaderPaddingTop.progress = ReadBookConfig.headerPaddingTop\n        dsbHeaderPaddingBottom.progress = ReadBookConfig.headerPaddingBottom\n        dsbHeaderPaddingLeft.progress = ReadBookConfig.headerPaddingLeft\n        dsbHeaderPaddingRight.progress = ReadBookConfig.headerPaddingRight\n        //页脚\n        dsbFooterPaddingTop.progress = ReadBookConfig.footerPaddingTop\n        dsbFooterPaddingBottom.progress = ReadBookConfig.footerPaddingBottom\n        dsbFooterPaddingLeft.progress = ReadBookConfig.footerPaddingLeft\n        dsbFooterPaddingRight.progress = ReadBookConfig.footerPaddingRight\n        cbShowTopLine.isChecked = ReadBookConfig.showHeaderLine\n        cbShowBottomLine.isChecked = ReadBookConfig.showFooterLine\n    }\n\n    private fun initView() = binding.run {\n        //正文\n        dsbPaddingTop.onChanged = {\n            ReadBookConfig.paddingTop = it\n            postEvent(EventBus.UP_CONFIG, arrayListOf(10, 5))\n        }\n        dsbPaddingBottom.onChanged = {\n            ReadBookConfig.paddingBottom = it\n            postEvent(EventBus.UP_CONFIG, arrayListOf(10, 5))\n        }\n        dsbPaddingLeft.onChanged = {\n            ReadBookConfig.paddingLeft = it\n            postEvent(EventBus.UP_CONFIG, arrayListOf(10, 5))\n        }\n        dsbPaddingRight.onChanged = {\n            ReadBookConfig.paddingRight = it\n            postEvent(EventBus.UP_CONFIG, arrayListOf(10, 5))\n        }\n        //页眉\n        dsbHeaderPaddingTop.onChanged = {\n            ReadBookConfig.headerPaddingTop = it\n            postEvent(EventBus.UP_CONFIG, arrayListOf(2))\n        }\n        dsbHeaderPaddingBottom.onChanged = {\n            ReadBookConfig.headerPaddingBottom = it\n            postEvent(EventBus.UP_CONFIG, arrayListOf(2))\n        }\n        dsbHeaderPaddingLeft.onChanged = {\n            ReadBookConfig.headerPaddingLeft = it\n            postEvent(EventBus.UP_CONFIG, arrayListOf(2))\n        }\n        dsbHeaderPaddingRight.onChanged = {\n            ReadBookConfig.headerPaddingRight = it\n            postEvent(EventBus.UP_CONFIG, arrayListOf(2))\n        }\n        //页脚\n        dsbFooterPaddingTop.onChanged = {\n            ReadBookConfig.footerPaddingTop = it\n            postEvent(EventBus.UP_CONFIG, arrayListOf(2))\n        }\n        dsbFooterPaddingBottom.onChanged = {\n            ReadBookConfig.footerPaddingBottom = it\n            postEvent(EventBus.UP_CONFIG, arrayListOf(2))\n        }\n        dsbFooterPaddingLeft.onChanged = {\n            ReadBookConfig.footerPaddingLeft = it\n            postEvent(EventBus.UP_CONFIG, arrayListOf(2))\n        }\n        dsbFooterPaddingRight.onChanged = {\n            ReadBookConfig.footerPaddingRight = it\n            postEvent(EventBus.UP_CONFIG, arrayListOf(2))\n        }\n        cbShowTopLine.onCheckedChangeListener = { _, isChecked ->\n            ReadBookConfig.showHeaderLine = isChecked\n            postEvent(EventBus.UP_CONFIG, arrayListOf(2))\n        }\n        cbShowBottomLine.onCheckedChangeListener = { _, isChecked ->\n            ReadBookConfig.showFooterLine = isChecked\n            postEvent(EventBus.UP_CONFIG, arrayListOf(2))\n        }\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/book/read/config/PageKeyDialog.kt",
    "content": "package io.legado.app.ui.book.read.config\n\nimport android.app.Dialog\nimport android.content.Context\nimport android.view.KeyEvent\nimport android.view.ViewGroup\nimport io.legado.app.constant.PreferKey\nimport io.legado.app.databinding.DialogPageKeyBinding\nimport io.legado.app.lib.theme.backgroundColor\nimport io.legado.app.utils.getPrefString\nimport io.legado.app.utils.hideSoftInput\nimport io.legado.app.utils.putPrefString\nimport io.legado.app.utils.setLayout\nimport splitties.views.onClick\n\n\nclass PageKeyDialog(context: Context) : Dialog(context) {\n\n    private val binding = DialogPageKeyBinding.inflate(layoutInflater)\n\n    override fun onStart() {\n        super.onStart()\n        setLayout(0.9f, ViewGroup.LayoutParams.WRAP_CONTENT)\n    }\n\n    init {\n        setContentView(binding.root)\n        binding.run {\n            contentView.setBackgroundColor(context.backgroundColor)\n            etPrev.setText(context.getPrefString(PreferKey.prevKeys))\n            etNext.setText(context.getPrefString(PreferKey.nextKeys))\n            tvReset.onClick {\n                etPrev.setText(\"\")\n                etNext.setText(\"\")\n            }\n            tvOk.setOnClickListener {\n                context.putPrefString(PreferKey.prevKeys, etPrev.text?.toString())\n                context.putPrefString(PreferKey.nextKeys, etNext.text?.toString())\n                dismiss()\n            }\n        }\n    }\n\n    override fun onKeyDown(keyCode: Int, event: KeyEvent): Boolean {\n        if (keyCode != KeyEvent.KEYCODE_BACK && keyCode != KeyEvent.KEYCODE_DEL) {\n            if (binding.etPrev.hasFocus()) {\n                val editableText = binding.etPrev.editableText\n                if (editableText.isEmpty() or editableText.endsWith(\",\")) {\n                    editableText.append(keyCode.toString())\n                } else {\n                    editableText.append(\",\").append(keyCode.toString())\n                }\n                return true\n            } else if (binding.etNext.hasFocus()) {\n                val editableText = binding.etNext.editableText\n                if (editableText.isEmpty() or editableText.endsWith(\",\")) {\n                    editableText.append(keyCode.toString())\n                } else {\n                    editableText.append(\",\").append(keyCode.toString())\n                }\n                return true\n            }\n        }\n        return super.onKeyDown(keyCode, event)\n    }\n\n    override fun dismiss() {\n        super.dismiss()\n        currentFocus?.hideSoftInput()\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/book/read/config/ReadAloudConfigDialog.kt",
    "content": "package io.legado.app.ui.book.read.config\n\nimport android.content.SharedPreferences\nimport android.os.Bundle\nimport android.view.LayoutInflater\nimport android.view.View\nimport android.view.ViewGroup\nimport android.widget.LinearLayout\nimport androidx.preference.ListPreference\nimport androidx.preference.Preference\nimport io.legado.app.R\nimport io.legado.app.base.BasePrefDialogFragment\nimport io.legado.app.constant.EventBus\nimport io.legado.app.constant.PreferKey\nimport io.legado.app.data.appDb\nimport io.legado.app.help.IntentHelp\nimport io.legado.app.help.config.AppConfig\nimport io.legado.app.lib.dialogs.SelectItem\nimport io.legado.app.lib.prefs.SwitchPreference\nimport io.legado.app.lib.prefs.fragment.PreferenceFragment\nimport io.legado.app.lib.theme.backgroundColor\nimport io.legado.app.lib.theme.primaryColor\nimport io.legado.app.model.ReadAloud\nimport io.legado.app.service.BaseReadAloudService\nimport io.legado.app.utils.GSON\nimport io.legado.app.utils.StringUtils\nimport io.legado.app.utils.fromJsonObject\nimport io.legado.app.utils.postEvent\nimport io.legado.app.utils.setEdgeEffectColor\nimport io.legado.app.utils.setLayout\nimport io.legado.app.utils.showDialogFragment\n\nclass ReadAloudConfigDialog : BasePrefDialogFragment() {\n    private val readAloudPreferTag = \"readAloudPreferTag\"\n\n    override fun onStart() {\n        super.onStart()\n        dialog?.window?.run {\n            setBackgroundDrawableResource(R.color.transparent)\n            setLayout(0.9f, ViewGroup.LayoutParams.WRAP_CONTENT)\n        }\n    }\n\n    override fun onCreateView(\n        inflater: LayoutInflater,\n        container: ViewGroup?,\n        savedInstanceState: Bundle?\n    ): View {\n        val view = LinearLayout(requireContext())\n        view.setBackgroundColor(requireContext().backgroundColor)\n        view.id = R.id.tag1\n        container?.addView(view)\n        return view\n    }\n\n    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n        super.onViewCreated(view, savedInstanceState)\n        var preferenceFragment = childFragmentManager.findFragmentByTag(readAloudPreferTag)\n        if (preferenceFragment == null) preferenceFragment = ReadAloudPreferenceFragment()\n        childFragmentManager.beginTransaction()\n            .replace(view.id, preferenceFragment, readAloudPreferTag)\n            .commit()\n    }\n\n    class ReadAloudPreferenceFragment : PreferenceFragment(),\n        SpeakEngineDialog.CallBack,\n        SharedPreferences.OnSharedPreferenceChangeListener {\n\n        private val speakEngineSummary: String\n            get() {\n                val ttsEngine = ReadAloud.ttsEngine\n                    ?: return getString(R.string.system_tts)\n                if (StringUtils.isNumeric(ttsEngine)) {\n                    return appDb.httpTTSDao.getName(ttsEngine.toLong())\n                        ?: getString(R.string.system_tts)\n                }\n                return GSON.fromJsonObject<SelectItem<String>>(ttsEngine).getOrNull()?.title\n                    ?: getString(R.string.system_tts)\n            }\n\n        override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {\n            addPreferencesFromResource(R.xml.pref_config_aloud)\n            upSpeakEngineSummary()\n            findPreference<SwitchPreference>(PreferKey.pauseReadAloudWhilePhoneCalls)?.let {\n                it.isEnabled = AppConfig.ignoreAudioFocus\n            }\n        }\n\n        override fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n            super.onViewCreated(view, savedInstanceState)\n            listView.setEdgeEffectColor(primaryColor)\n        }\n\n        override fun onResume() {\n            super.onResume()\n            preferenceManager.sharedPreferences?.registerOnSharedPreferenceChangeListener(this)\n        }\n\n        override fun onPause() {\n            preferenceManager.sharedPreferences?.unregisterOnSharedPreferenceChangeListener(this)\n            super.onPause()\n        }\n\n        override fun onPreferenceTreeClick(preference: Preference): Boolean {\n            when (preference.key) {\n                PreferKey.ttsEngine -> showDialogFragment(SpeakEngineDialog())\n                \"sysTtsConfig\" -> IntentHelp.openTTSSetting()\n            }\n            return super.onPreferenceTreeClick(preference)\n        }\n\n        override fun onSharedPreferenceChanged(\n            sharedPreferences: SharedPreferences?,\n            key: String?\n        ) {\n            when (key) {\n                PreferKey.readAloudByPage, PreferKey.streamReadAloudAudio -> {\n                    if (BaseReadAloudService.isRun) {\n                        postEvent(EventBus.MEDIA_BUTTON, false)\n                    }\n                }\n\n                PreferKey.ignoreAudioFocus -> {\n                    findPreference<SwitchPreference>(PreferKey.pauseReadAloudWhilePhoneCalls)?.let {\n                        it.isEnabled = AppConfig.ignoreAudioFocus\n                    }\n                }\n            }\n        }\n\n        private fun upPreferenceSummary(preference: Preference?, value: String) {\n            when (preference) {\n                is ListPreference -> {\n                    val index = preference.findIndexOfValue(value)\n                    preference.summary = if (index >= 0) preference.entries[index] else null\n                }\n\n                else -> {\n                    preference?.summary = value\n                }\n            }\n        }\n\n        override fun upSpeakEngineSummary() {\n            upPreferenceSummary(\n                findPreference(PreferKey.ttsEngine),\n                speakEngineSummary\n            )\n        }\n    }\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/book/read/config/ReadAloudDialog.kt",
    "content": "package io.legado.app.ui.book.read.config\n\nimport android.annotation.SuppressLint\nimport android.content.DialogInterface\nimport android.os.Bundle\nimport android.view.Gravity\nimport android.view.View\nimport android.view.ViewGroup\nimport android.view.WindowManager\nimport android.widget.SeekBar\nimport io.legado.app.R\nimport io.legado.app.base.BaseDialogFragment\nimport io.legado.app.constant.EventBus\nimport io.legado.app.databinding.DialogReadAloudBinding\nimport io.legado.app.help.config.AppConfig\nimport io.legado.app.lib.dialogs.selector\nimport io.legado.app.lib.theme.bottomBackground\nimport io.legado.app.lib.theme.getPrimaryTextColor\nimport io.legado.app.model.ReadAloud\nimport io.legado.app.model.ReadBook\nimport io.legado.app.service.BaseReadAloudService\nimport io.legado.app.ui.book.read.ReadBookActivity\nimport io.legado.app.ui.widget.seekbar.SeekBarChangeListener\nimport io.legado.app.utils.*\nimport io.legado.app.utils.viewbindingdelegate.viewBinding\n\n\nclass ReadAloudDialog : BaseDialogFragment(R.layout.dialog_read_aloud) {\n    private val callBack: CallBack? get() = activity as? CallBack\n    private val binding by viewBinding(DialogReadAloudBinding::bind)\n\n    override fun onStart() {\n        super.onStart()\n        dialog?.window?.run {\n            clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND)\n            setBackgroundDrawableResource(R.color.background)\n            decorView.setPadding(0, 0, 0, 0)\n            val attr = attributes\n            attr.dimAmount = 0.0f\n            attr.gravity = Gravity.BOTTOM\n            attributes = attr\n            setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)\n        }\n    }\n\n    override fun onDismiss(dialog: DialogInterface) {\n        super.onDismiss(dialog)\n        (activity as ReadBookActivity).bottomDialog--\n    }\n\n    override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) {\n        val bottomDialog = (activity as ReadBookActivity).bottomDialog++\n        if (bottomDialog > 0) {\n            dismiss()\n            return\n        }\n        val bg = requireContext().bottomBackground\n        val isLight = ColorUtils.isColorLight(bg)\n        val textColor = requireContext().getPrimaryTextColor(isLight)\n        binding.run {\n            rootView.setBackgroundColor(bg)\n            tvPre.setTextColor(textColor)\n            tvNext.setTextColor(textColor)\n            ivPlayPrev.setColorFilter(textColor)\n            ivPlayPause.setColorFilter(textColor)\n            ivPlayNext.setColorFilter(textColor)\n            ivStop.setColorFilter(textColor)\n            ivTimer.setColorFilter(textColor)\n            tvTimer.setTextColor(textColor)\n            ivTtsSpeechReduce.setColorFilter(textColor)\n            tvTtsSpeed.setTextColor(textColor)\n            tvTtsSpeedValue.setTextColor(textColor)\n            ivTtsSpeechAdd.setColorFilter(textColor)\n            ivCatalog.setColorFilter(textColor)\n            tvCatalog.setTextColor(textColor)\n            ivMainMenu.setColorFilter(textColor)\n            tvMainMenu.setTextColor(textColor)\n            ivToBackstage.setColorFilter(textColor)\n            tvToBackstage.setTextColor(textColor)\n            ivSetting.setColorFilter(textColor)\n            tvSetting.setTextColor(textColor)\n            cbTtsFollowSys.setTextColor(textColor)\n        }\n        initData()\n        initEvent()\n    }\n\n    private fun initData() = binding.run {\n        upPlayState()\n        upTimerText(BaseReadAloudService.timeMinute)\n        cbTtsFollowSys.isChecked = requireContext().getPrefBoolean(\"ttsFollowSys\", true)\n        upTtsSpeechRateEnabled(!cbTtsFollowSys.isChecked)\n        upSeekTimer()\n    }\n\n    private fun initEvent() = binding.run {\n        llMainMenu.setOnClickListener {\n            callBack?.showMenuBar()\n            dismissAllowingStateLoss()\n        }\n        llSetting.setOnClickListener {\n            ReadAloudConfigDialog().show(childFragmentManager, \"readAloudConfigDialog\")\n        }\n        tvPre.setOnClickListener { ReadBook.moveToPrevChapter(upContent = true, toLast = false) }\n        tvNext.setOnClickListener { ReadBook.moveToNextChapter(true) }\n        ivStop.setOnClickListener {\n            ReadAloud.stop(requireContext())\n            dismissAllowingStateLoss()\n        }\n        ivPlayPause.setOnClickListener { callBack?.onClickReadAloud() }\n        ivPlayPrev.setOnClickListener { ReadAloud.prevParagraph(requireContext()) }\n        ivPlayNext.setOnClickListener { ReadAloud.nextParagraph(requireContext()) }\n        llCatalog.setOnClickListener { callBack?.openChapterList() }\n        llToBackstage.setOnClickListener { callBack?.finish() }\n        cbTtsFollowSys.setOnCheckedChangeListener { _, isChecked ->\n            AppConfig.ttsFlowSys = isChecked\n            upTtsSpeechRateEnabled(!isChecked)\n            upTtsSpeechRate()\n        }\n        ivTtsSpeechReduce.setOnClickListener {\n            seekTtsSpeechRate.progress = AppConfig.ttsSpeechRate - 1\n            AppConfig.ttsSpeechRate -= 1\n            upTtsSpeechRate()\n        }\n        ivTtsSpeechAdd.setOnClickListener {\n            seekTtsSpeechRate.progress = AppConfig.ttsSpeechRate + 1\n            AppConfig.ttsSpeechRate += 1\n            upTtsSpeechRate()\n        }\n        ivTimer.setOnClickListener {\n            AppConfig.ttsTimer = seekTimer.progress\n            toastOnUi(\"保存设定时间成功！\")\n        }\n        tvTimer.setOnClickListener {\n            val times = intArrayOf(0, 5, 10, 15, 30, 60, 90, 180)\n            val timeKeys = times.map { \"$it 分钟\" }\n            context?.selector(\"设定时间\", timeKeys) { _, index ->\n                ReadAloud.setTimer(requireContext(), times[index])\n            }\n        }\n        //设置保存的默认值\n        seekTtsSpeechRate.progress = AppConfig.ttsSpeechRate\n        seekTtsSpeechRate.setOnSeekBarChangeListener(object : SeekBarChangeListener {\n\n            override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {\n                super.onProgressChanged(seekBar, progress, fromUser)\n                upTtsSpeechRateText(progress)\n            }\n\n            override fun onStopTrackingTouch(seekBar: SeekBar) {\n                AppConfig.ttsSpeechRate = seekBar.progress\n                upTtsSpeechRate()\n            }\n        })\n        seekTimer.setOnSeekBarChangeListener(object : SeekBarChangeListener {\n            override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {\n                upTimerText(progress)\n            }\n\n            override fun onStopTrackingTouch(seekBar: SeekBar) {\n                ReadAloud.setTimer(requireContext(), seekTimer.progress)\n            }\n        })\n    }\n\n    private fun upTtsSpeechRateEnabled(enabled: Boolean) {\n        binding.run {\n            upTtsSpeechRateText(AppConfig.ttsSpeechRate)\n            tvTtsSpeedValue.visible(enabled)\n            seekTtsSpeechRate.isEnabled = enabled\n            ivTtsSpeechReduce.isEnabled = enabled\n            ivTtsSpeechAdd.isEnabled = enabled\n        }\n    }\n\n    private fun upPlayState() {\n        if (!BaseReadAloudService.pause) {\n            binding.ivPlayPause.setImageResource(R.drawable.ic_pause_24dp)\n            binding.ivPlayPause.contentDescription = getString(R.string.pause)\n        } else {\n            binding.ivPlayPause.setImageResource(R.drawable.ic_play_24dp)\n            binding.ivPlayPause.contentDescription = getString(R.string.audio_play)\n        }\n        val bg = requireContext().bottomBackground\n        val isLight = ColorUtils.isColorLight(bg)\n        val textColor = requireContext().getPrimaryTextColor(isLight)\n        binding.ivPlayPause.setColorFilter(textColor)\n    }\n\n    private fun upSeekTimer() {\n        binding.seekTimer.post {\n            if (BaseReadAloudService.timeMinute > 0) {\n                binding.seekTimer.progress = BaseReadAloudService.timeMinute\n            } else {\n                binding.seekTimer.progress = AppConfig.ttsTimer\n            }\n        }\n    }\n\n    private fun upTimerText(timeMinute: Int) {\n        if (timeMinute < 0) {\n            binding.tvTimer.text = requireContext().getString(R.string.timer_m, 0)\n        } else {\n            binding.tvTimer.text = requireContext().getString(R.string.timer_m, timeMinute)\n        }\n    }\n\n    @SuppressLint(\"SetTextI18n\")\n    private fun upTtsSpeechRateText(value: Int) {\n        binding.tvTtsSpeedValue.text = ((value + 5) / 10f).toString()\n    }\n\n    private fun upTtsSpeechRate() {\n        ReadAloud.upTtsSpeechRate(requireContext())\n        if (!BaseReadAloudService.pause) {\n            ReadAloud.pause(requireContext())\n            ReadAloud.resume(requireContext())\n        }\n    }\n\n    override fun observeLiveBus() {\n        observeEvent<Int>(EventBus.ALOUD_STATE) { upPlayState() }\n        observeEvent<Int>(EventBus.READ_ALOUD_DS) { binding.seekTimer.progress = it }\n    }\n\n    interface CallBack {\n        fun showMenuBar()\n        fun openChapterList()\n        fun onClickReadAloud()\n        fun finish()\n    }\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/book/read/config/ReadStyleDialog.kt",
    "content": "package io.legado.app.ui.book.read.config\n\nimport android.content.DialogInterface\nimport android.os.Bundle\nimport android.view.Gravity\nimport android.view.View\nimport android.view.ViewGroup\nimport android.view.WindowManager\nimport androidx.core.view.get\nimport com.github.liuyueyi.quick.transfer.constants.TransType\nimport io.legado.app.R\nimport io.legado.app.base.BaseDialogFragment\nimport io.legado.app.base.adapter.ItemViewHolder\nimport io.legado.app.base.adapter.RecyclerAdapter\nimport io.legado.app.constant.EventBus\nimport io.legado.app.databinding.DialogReadBookStyleBinding\nimport io.legado.app.databinding.ItemReadStyleBinding\nimport io.legado.app.help.config.AppConfig\nimport io.legado.app.help.config.ReadBookConfig\nimport io.legado.app.lib.dialogs.selector\nimport io.legado.app.lib.theme.accentColor\nimport io.legado.app.lib.theme.bottomBackground\nimport io.legado.app.lib.theme.getPrimaryTextColor\nimport io.legado.app.model.ReadBook\nimport io.legado.app.ui.book.read.ReadBookActivity\nimport io.legado.app.ui.font.FontSelectDialog\nimport io.legado.app.utils.ChineseUtils\nimport io.legado.app.utils.ColorUtils\nimport io.legado.app.utils.dpToPx\nimport io.legado.app.utils.getIndexById\nimport io.legado.app.utils.postEvent\nimport io.legado.app.utils.showDialogFragment\nimport io.legado.app.utils.viewbindingdelegate.viewBinding\nimport splitties.views.onLongClick\n\nclass ReadStyleDialog : BaseDialogFragment(R.layout.dialog_read_book_style),\n    FontSelectDialog.CallBack {\n\n    private val binding by viewBinding(DialogReadBookStyleBinding::bind)\n    private val callBack get() = activity as? ReadBookActivity\n    private lateinit var styleAdapter: StyleAdapter\n\n    override fun onStart() {\n        super.onStart()\n        dialog?.window?.run {\n            clearFlags(WindowManager.LayoutParams.FLAG_DIM_BEHIND)\n            setBackgroundDrawableResource(R.color.background)\n            decorView.setPadding(0, 0, 0, 0)\n            val attr = attributes\n            attr.dimAmount = 0.0f\n            attr.gravity = Gravity.BOTTOM\n            attributes = attr\n            setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)\n        }\n    }\n\n    override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) {\n        (activity as ReadBookActivity).bottomDialog++\n        initView()\n        initData()\n        initViewEvent()\n    }\n\n    override fun onDismiss(dialog: DialogInterface) {\n        super.onDismiss(dialog)\n        ReadBookConfig.save()\n        (activity as ReadBookActivity).bottomDialog--\n    }\n\n    private fun initView() = binding.run {\n        val bg = requireContext().bottomBackground\n        val isLight = ColorUtils.isColorLight(bg)\n        val textColor = requireContext().getPrimaryTextColor(isLight)\n        rootView.setBackgroundColor(bg)\n        tvPageAnim.setTextColor(textColor)\n        tvBgTs.setTextColor(textColor)\n        tvShareLayout.setTextColor(textColor)\n        dsbTextSize.valueFormat = {\n            (it + 5).toString()\n        }\n        dsbTextLetterSpacing.valueFormat = {\n            ((it - 50) / 100f).toString()\n        }\n        dsbLineSize.valueFormat = { ((it - 10) / 10f).toString() }\n        dsbParagraphSpacing.valueFormat = { (it / 10f).toString() }\n        styleAdapter = StyleAdapter()\n        rvStyle.adapter = styleAdapter\n        styleAdapter.addFooterView {\n            ItemReadStyleBinding.inflate(layoutInflater, it, false).apply {\n                ivStyle.setPadding(6.dpToPx(), 6.dpToPx(), 6.dpToPx(), 6.dpToPx())\n                ivStyle.setText(null)\n                ivStyle.setColorFilter(textColor)\n                ivStyle.borderColor = textColor\n                ivStyle.setImageResource(R.drawable.ic_add)\n                root.setOnClickListener {\n                    ReadBookConfig.configList.add(ReadBookConfig.Config())\n                    showBgTextConfig(ReadBookConfig.configList.lastIndex)\n                }\n            }\n        }\n    }\n\n    private fun initData() {\n        binding.cbShareLayout.isChecked = ReadBookConfig.shareLayout\n        upView()\n        styleAdapter.setItems(ReadBookConfig.configList)\n    }\n\n    private fun initViewEvent() = binding.run {\n        chineseConverter.onChanged {\n            ChineseUtils.unLoad(*TransType.entries.toTypedArray())\n            postEvent(EventBus.UP_CONFIG, arrayListOf(5))\n        }\n        textFontWeightConverter.onChanged {\n            postEvent(EventBus.UP_CONFIG, arrayListOf(8, 9, 6))\n        }\n        tvTextFont.setOnClickListener {\n            showDialogFragment<FontSelectDialog>()\n        }\n        tvTextIndent.setOnClickListener {\n            context?.selector(\n                title = getString(R.string.text_indent),\n                items = resources.getStringArray(R.array.indent).toList()\n            ) { _, index ->\n                ReadBookConfig.paragraphIndent = \"　\".repeat(index)\n                postEvent(EventBus.UP_CONFIG, arrayListOf(8, 5))\n            }\n        }\n        tvPadding.setOnClickListener {\n            dismissAllowingStateLoss()\n            callBack?.showPaddingConfig()\n        }\n        tvTip.setOnClickListener {\n            TipConfigDialog().show(childFragmentManager, \"tipConfigDialog\")\n        }\n        rgPageAnim.setOnCheckedChangeListener { _, checkedId ->\n            ReadBook.book?.setPageAnim(-1)\n            ReadBookConfig.pageAnim = binding.rgPageAnim.getIndexById(checkedId)\n            callBack?.upPageAnim()\n            ReadBook.loadContent(false)\n        }\n        cbShareLayout.onCheckedChangeListener = { _, isChecked ->\n            ReadBookConfig.shareLayout = isChecked\n            upView()\n            postEvent(EventBus.UP_CONFIG, arrayListOf(1, 2, 5))\n        }\n        dsbTextSize.onChanged = {\n            ReadBookConfig.textSize = it + 5\n            postEvent(EventBus.UP_CONFIG, arrayListOf(8, 5))\n        }\n        dsbTextLetterSpacing.onChanged = {\n            ReadBookConfig.letterSpacing = (it - 50) / 100f\n            postEvent(EventBus.UP_CONFIG, arrayListOf(8, 5))\n        }\n        dsbLineSize.onChanged = {\n            ReadBookConfig.lineSpacingExtra = it\n            postEvent(EventBus.UP_CONFIG, arrayListOf(8, 5))\n        }\n        dsbParagraphSpacing.onChanged = {\n            ReadBookConfig.paragraphSpacing = it\n            postEvent(EventBus.UP_CONFIG, arrayListOf(8, 5))\n        }\n    }\n\n    private fun changeBgTextConfig(index: Int) {\n        val oldIndex = ReadBookConfig.styleSelect\n        if (index != oldIndex) {\n            ReadBookConfig.styleSelect = index\n            upView()\n            styleAdapter.notifyItemChanged(oldIndex)\n            styleAdapter.notifyItemChanged(index)\n            postEvent(EventBus.UP_CONFIG, arrayListOf(1, 2, 5))\n            if (AppConfig.readBarStyleFollowPage) {\n                postEvent(EventBus.UPDATE_READ_ACTION_BAR, true)\n            }\n        }\n    }\n\n    private fun showBgTextConfig(index: Int): Boolean {\n        dismissAllowingStateLoss()\n        changeBgTextConfig(index)\n        callBack?.showBgTextConfig()\n        return true\n    }\n\n    private fun upView() = binding.run {\n        textFontWeightConverter.upUi(ReadBookConfig.textBold)\n        ReadBook.pageAnim().let {\n            if (it >= 0 && it < rgPageAnim.childCount) {\n                rgPageAnim.check(rgPageAnim[it].id)\n            }\n        }\n        ReadBookConfig.let {\n            dsbTextSize.progress = it.textSize - 5\n            dsbTextLetterSpacing.progress = (it.letterSpacing * 100).toInt() + 50\n            dsbLineSize.progress = it.lineSpacingExtra\n            dsbParagraphSpacing.progress = it.paragraphSpacing\n        }\n    }\n\n    override val curFontPath: String\n        get() = ReadBookConfig.textFont\n\n    override fun selectFont(path: String) {\n        if (path != ReadBookConfig.textFont || path.isEmpty()) {\n            ReadBookConfig.textFont = path\n            postEvent(EventBus.UP_CONFIG, arrayListOf(2, 5))\n        }\n    }\n\n    inner class StyleAdapter :\n        RecyclerAdapter<ReadBookConfig.Config, ItemReadStyleBinding>(requireContext()) {\n\n        override fun getViewBinding(parent: ViewGroup): ItemReadStyleBinding {\n            return ItemReadStyleBinding.inflate(inflater, parent, false)\n        }\n\n        override fun convert(\n            holder: ItemViewHolder,\n            binding: ItemReadStyleBinding,\n            item: ReadBookConfig.Config,\n            payloads: MutableList<Any>\n        ) {\n            binding.apply {\n                ivStyle.setText(item.name.ifBlank { \"文字\" })\n                ivStyle.setTextColor(item.curTextColor())\n                ivStyle.setImageDrawable(item.curBgDrawable(100, 150))\n                if (ReadBookConfig.styleSelect == holder.layoutPosition) {\n                    ivStyle.borderColor = accentColor\n                    ivStyle.setTextBold(true)\n                } else {\n                    ivStyle.borderColor = item.curTextColor()\n                    ivStyle.setTextBold(false)\n                }\n            }\n        }\n\n        override fun registerListener(holder: ItemViewHolder, binding: ItemReadStyleBinding) {\n            binding.apply {\n                ivStyle.setOnClickListener {\n                    if (ivStyle.isInView) {\n                        changeBgTextConfig(holder.layoutPosition)\n                    }\n                }\n                ivStyle.onLongClick(ivStyle.isInView) {\n                    if (ivStyle.isInView) {\n                        showBgTextConfig(holder.layoutPosition)\n                    }\n                }\n            }\n        }\n\n    }\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/book/read/config/SpeakEngineDialog.kt",
    "content": "package io.legado.app.ui.book.read.config\n\nimport android.content.Context\nimport android.os.Bundle\nimport android.view.MenuItem\nimport android.view.View\nimport android.view.ViewGroup\nimport android.widget.RadioButton\nimport androidx.appcompat.widget.Toolbar\nimport androidx.fragment.app.viewModels\nimport androidx.lifecycle.lifecycleScope\nimport androidx.recyclerview.widget.LinearLayoutManager\nimport io.legado.app.R\nimport io.legado.app.base.BaseDialogFragment\nimport io.legado.app.base.adapter.ItemViewHolder\nimport io.legado.app.base.adapter.RecyclerAdapter\nimport io.legado.app.constant.AppLog\nimport io.legado.app.data.appDb\nimport io.legado.app.data.entities.HttpTTS\nimport io.legado.app.databinding.DialogEditTextBinding\nimport io.legado.app.databinding.DialogRecyclerViewBinding\nimport io.legado.app.databinding.ItemHttpTtsBinding\nimport io.legado.app.help.DirectLinkUpload\nimport io.legado.app.help.config.AppConfig\nimport io.legado.app.lib.dialogs.SelectItem\nimport io.legado.app.lib.dialogs.alert\nimport io.legado.app.lib.theme.primaryColor\nimport io.legado.app.model.ReadAloud\nimport io.legado.app.model.ReadBook\nimport io.legado.app.ui.association.ImportHttpTtsDialog\nimport io.legado.app.ui.file.HandleFileContract\nimport io.legado.app.ui.login.SourceLoginActivity\nimport io.legado.app.utils.ACache\nimport io.legado.app.utils.GSON\nimport io.legado.app.utils.applyTint\nimport io.legado.app.utils.fromJsonObject\nimport io.legado.app.utils.gone\nimport io.legado.app.utils.isAbsUrl\nimport io.legado.app.utils.isJsonObject\nimport io.legado.app.utils.sendToClip\nimport io.legado.app.utils.setEdgeEffectColor\nimport io.legado.app.utils.setLayout\nimport io.legado.app.utils.showDialogFragment\nimport io.legado.app.utils.splitNotBlank\nimport io.legado.app.utils.startActivity\nimport io.legado.app.utils.viewbindingdelegate.viewBinding\nimport io.legado.app.utils.visible\nimport kotlinx.coroutines.Dispatchers.IO\nimport kotlinx.coroutines.flow.catch\nimport kotlinx.coroutines.flow.conflate\nimport kotlinx.coroutines.flow.flowOn\nimport kotlinx.coroutines.launch\n\n/**\n * tts引擎管理\n */\nclass SpeakEngineDialog() : BaseDialogFragment(R.layout.dialog_recycler_view),\n    Toolbar.OnMenuItemClickListener {\n\n    private val binding by viewBinding(DialogRecyclerViewBinding::bind)\n    private val viewModel: SpeakEngineViewModel by viewModels()\n    private val ttsUrlKey = \"ttsUrlKey\"\n    private val adapter by lazy { Adapter(requireContext()) }\n    private var ttsEngine: String? = ReadAloud.ttsEngine\n    private val sysTtsViews = arrayListOf<RadioButton>()\n    private val callBack: CallBack? get() = parentFragment as? CallBack\n    private val importDocResult = registerForActivityResult(HandleFileContract()) {\n        it.uri?.let { uri ->\n            showDialogFragment(ImportHttpTtsDialog(uri.toString()))\n        }\n    }\n    private val exportDirResult = registerForActivityResult(HandleFileContract()) {\n        it.uri?.let { uri ->\n            alert(R.string.export_success) {\n                if (uri.toString().isAbsUrl()) {\n                    setMessage(DirectLinkUpload.getSummary())\n                }\n                val alertBinding = DialogEditTextBinding.inflate(layoutInflater).apply {\n                    editView.hint = getString(R.string.path)\n                    editView.setText(uri.toString())\n                }\n                customView { alertBinding.root }\n                okButton {\n                    requireContext().sendToClip(uri.toString())\n                }\n            }\n        }\n    }\n\n    override fun onStart() {\n        super.onStart()\n        setLayout(ViewGroup.LayoutParams.MATCH_PARENT, 0.9f)\n    }\n\n    override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) {\n        initView()\n        initMenu()\n        initData()\n    }\n\n    private fun initView() = binding.run {\n        toolBar.setBackgroundColor(primaryColor)\n        toolBar.setTitle(R.string.speak_engine)\n        recyclerView.setEdgeEffectColor(primaryColor)\n        recyclerView.layoutManager = LinearLayoutManager(requireContext())\n        recyclerView.adapter = adapter\n        adapter.addHeaderView {\n            ItemHttpTtsBinding.inflate(layoutInflater, recyclerView, false).apply {\n                sysTtsViews.add(cbName)\n                ivEdit.gone()\n                ivMenuDelete.gone()\n                labelSys.visible()\n                cbName.text = \"系统默认\"\n                cbName.tag = \"\"\n                cbName.isChecked = ttsEngine == null || ttsEngine!!.isJsonObject()\n                        && GSON.fromJsonObject<SelectItem<String>>(ttsEngine)\n                    .getOrNull()?.value.isNullOrEmpty()\n                cbName.setOnClickListener {\n                    upTts(GSON.toJson(SelectItem(\"系统默认\", \"\")))\n                }\n            }\n        }\n        viewModel.sysEngines.forEach { engine ->\n            adapter.addHeaderView {\n                ItemHttpTtsBinding.inflate(layoutInflater, recyclerView, false).apply {\n                    sysTtsViews.add(cbName)\n                    ivEdit.gone()\n                    ivMenuDelete.gone()\n                    labelSys.visible()\n                    cbName.text = engine.label\n                    cbName.tag = engine.name\n                    cbName.isChecked = GSON.fromJsonObject<SelectItem<String>>(ttsEngine)\n                        .getOrNull()?.value == cbName.tag\n                    cbName.setOnClickListener {\n                        upTts(GSON.toJson(SelectItem(engine.label, engine.name)))\n                    }\n                }\n            }\n        }\n        tvFooterLeft.setText(R.string.book)\n        tvFooterLeft.visible()\n        tvFooterLeft.setOnClickListener {\n            ReadBook.book?.setTtsEngine(ttsEngine)\n            callBack?.upSpeakEngineSummary()\n            ReadAloud.upReadAloudClass()\n            dismissAllowingStateLoss()\n        }\n        tvOk.setText(R.string.general)\n        tvOk.visible()\n        tvOk.setOnClickListener {\n            ReadBook.book?.setTtsEngine(null)\n            AppConfig.ttsEngine = ttsEngine\n            callBack?.upSpeakEngineSummary()\n            ReadAloud.upReadAloudClass()\n            dismissAllowingStateLoss()\n        }\n        tvCancel.visible()\n        tvCancel.setOnClickListener {\n            dismissAllowingStateLoss()\n        }\n    }\n\n    private fun initMenu() = binding.run {\n        toolBar.inflateMenu(R.menu.speak_engine)\n        toolBar.menu.applyTint(requireContext())\n        toolBar.setOnMenuItemClickListener(this@SpeakEngineDialog)\n    }\n\n    private fun initData() {\n        lifecycleScope.launch {\n            appDb.httpTTSDao.flowAll().catch {\n                AppLog.put(\"朗读引擎界面获取数据失败\\n${it.localizedMessage}\", it)\n            }.flowOn(IO).conflate().collect {\n                adapter.setItems(it)\n            }\n        }\n    }\n\n    override fun onMenuItemClick(item: MenuItem?): Boolean {\n        when (item?.itemId) {\n            R.id.menu_add -> showDialogFragment<HttpTtsEditDialog>()\n            R.id.menu_default -> viewModel.importDefault()\n            R.id.menu_import_local -> importDocResult.launch {\n                mode = HandleFileContract.FILE\n                allowExtensions = arrayOf(\"txt\", \"json\")\n            }\n\n            R.id.menu_import_onLine -> importAlert()\n            R.id.menu_export -> exportDirResult.launch {\n                mode = HandleFileContract.EXPORT\n                fileData = HandleFileContract.FileData(\n                    \"httpTts.json\",\n                    GSON.toJson(adapter.getItems()).toByteArray(),\n                    \"application/json\"\n                )\n            }\n        }\n        return true\n    }\n\n    private fun importAlert() {\n        val aCache = ACache.get(cacheDir = false)\n        val cacheUrls: MutableList<String> = aCache\n            .getAsString(ttsUrlKey)\n            ?.splitNotBlank(\",\")\n            ?.toMutableList() ?: mutableListOf()\n        alert(R.string.import_on_line) {\n            val alertBinding = DialogEditTextBinding.inflate(layoutInflater).apply {\n                editView.hint = \"url\"\n                editView.setFilterValues(cacheUrls)\n                editView.delCallBack = {\n                    cacheUrls.remove(it)\n                    aCache.put(ttsUrlKey, cacheUrls.joinToString(\",\"))\n                }\n            }\n            customView { alertBinding.root }\n            okButton {\n                alertBinding.editView.text?.toString()?.let { url ->\n                    if (url.isAbsUrl() && !cacheUrls.contains(url)) {\n                        cacheUrls.add(0, url)\n                        aCache.put(ttsUrlKey, cacheUrls.joinToString(\",\"))\n                    }\n                    showDialogFragment(ImportHttpTtsDialog(url))\n                }\n            }\n        }\n    }\n\n    private fun upTts(tts: String) {\n        ttsEngine = tts\n        sysTtsViews.forEach {\n            it.isChecked = GSON.fromJsonObject<SelectItem<String>>(ttsEngine)\n                .getOrNull()?.value == it.tag\n        }\n        adapter.notifyItemRangeChanged(adapter.getHeaderCount(), adapter.itemCount)\n    }\n\n    inner class Adapter(context: Context) :\n        RecyclerAdapter<HttpTTS, ItemHttpTtsBinding>(context) {\n\n        override fun getViewBinding(parent: ViewGroup): ItemHttpTtsBinding {\n            return ItemHttpTtsBinding.inflate(inflater, parent, false)\n        }\n\n        override fun convert(\n            holder: ItemViewHolder,\n            binding: ItemHttpTtsBinding,\n            item: HttpTTS,\n            payloads: MutableList<Any>\n        ) {\n            binding.apply {\n                cbName.text = item.name\n                cbName.isChecked = item.id.toString() == ttsEngine\n            }\n        }\n\n        override fun registerListener(holder: ItemViewHolder, binding: ItemHttpTtsBinding) {\n            binding.run {\n                cbName.setOnClickListener {\n                    getItemByLayoutPosition(holder.layoutPosition)?.let { httpTTS ->\n                        val id = httpTTS.id.toString()\n                        upTts(id)\n                        if (!httpTTS.loginUrl.isNullOrBlank()\n                            && httpTTS.getLoginInfo().isNullOrBlank()\n                        ) {\n                            startActivity<SourceLoginActivity> {\n                                putExtra(\"type\", \"httpTts\")\n                                putExtra(\"key\", id)\n                            }\n                        }\n                    }\n                }\n                ivEdit.setOnClickListener {\n                    val id = getItemByLayoutPosition(holder.layoutPosition)!!.id\n                    showDialogFragment(HttpTtsEditDialog(id))\n                }\n                ivMenuDelete.setOnClickListener {\n                    getItemByLayoutPosition(holder.layoutPosition)?.let { httpTTS ->\n                        appDb.httpTTSDao.delete(httpTTS)\n                    }\n                }\n            }\n        }\n\n    }\n\n    interface CallBack {\n        fun upSpeakEngineSummary()\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/book/read/config/SpeakEngineViewModel.kt",
    "content": "package io.legado.app.ui.book.read.config\n\nimport android.app.Application\nimport android.speech.tts.TextToSpeech\nimport io.legado.app.base.BaseViewModel\nimport io.legado.app.help.DefaultData\n\nclass SpeakEngineViewModel(application: Application) : BaseViewModel(application) {\n\n    val sysEngines: List<TextToSpeech.EngineInfo> by lazy {\n        val tts = TextToSpeech(context, null)\n        val engines = tts.engines\n        tts.shutdown()\n        engines\n    }\n\n    fun importDefault() {\n        execute {\n            DefaultData.importDefaultHttpTTS()\n        }\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/book/read/config/TextFontWeightConverter.kt",
    "content": "package io.legado.app.ui.book.read.config\n\nimport android.content.Context\nimport android.text.Spannable\nimport android.text.SpannableString\nimport android.text.style.ForegroundColorSpan\nimport android.util.AttributeSet\nimport io.legado.app.R\nimport io.legado.app.help.config.ReadBookConfig\nimport io.legado.app.lib.dialogs.alert\nimport io.legado.app.lib.theme.accentColor\nimport io.legado.app.ui.widget.text.StrokeTextView\n\n\nclass TextFontWeightConverter(context: Context, attrs: AttributeSet?) :\n    StrokeTextView(context, attrs) {\n\n    private val spannableString = SpannableString(context.getString(R.string.font_weight_text))\n    private var enabledSpan: ForegroundColorSpan = ForegroundColorSpan(context.accentColor)\n    private var onChanged: (() -> Unit)? = null\n\n    init {\n        text = spannableString\n        if (!isInEditMode) {\n            upUi(ReadBookConfig.textBold)\n        }\n        setOnClickListener {\n            selectType()\n        }\n    }\n\n    @Suppress(\"MemberVisibilityCanBePrivate\")\n    fun upUi(type: Int) {\n        spannableString.removeSpan(enabledSpan)\n        when (type) {\n            0 -> spannableString.setSpan(enabledSpan, 0, 1, Spannable.SPAN_INCLUSIVE_EXCLUSIVE)\n            1 -> spannableString.setSpan(enabledSpan, 2, 3, Spannable.SPAN_INCLUSIVE_EXCLUSIVE)\n            2 -> spannableString.setSpan(enabledSpan, 4, 5, Spannable.SPAN_INCLUSIVE_EXCLUSIVE)\n        }\n        text = spannableString\n    }\n\n    private fun selectType() {\n        context.alert(titleResource = R.string.text_font_weight_converter) {\n            items(context.resources.getStringArray(R.array.text_font_weight).toList()) { _, i ->\n                ReadBookConfig.textBold = i\n                upUi(i)\n                onChanged?.invoke()\n            }\n        }\n    }\n\n    fun onChanged(unit: () -> Unit) {\n        onChanged = unit\n    }\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/book/read/config/TipConfigDialog.kt",
    "content": "package io.legado.app.ui.book.read.config\n\nimport android.os.Bundle\nimport android.view.View\nimport android.view.ViewGroup\nimport androidx.core.view.indices\nimport com.jaredrummler.android.colorpicker.ColorPickerDialog\nimport io.legado.app.R\nimport io.legado.app.base.BaseDialogFragment\nimport io.legado.app.constant.EventBus\nimport io.legado.app.databinding.DialogTipConfigBinding\nimport io.legado.app.help.config.ReadBookConfig\nimport io.legado.app.help.config.ReadTipConfig\nimport io.legado.app.lib.dialogs.selector\nimport io.legado.app.utils.checkByIndex\nimport io.legado.app.utils.getIndexById\nimport io.legado.app.utils.hexString\nimport io.legado.app.utils.observeEvent\nimport io.legado.app.utils.postEvent\nimport io.legado.app.utils.setLayout\nimport io.legado.app.utils.viewbindingdelegate.viewBinding\n\n\nclass TipConfigDialog : BaseDialogFragment(R.layout.dialog_tip_config) {\n\n    companion object {\n        const val TIP_COLOR = 7897\n        const val TIP_DIVIDER_COLOR = 7898\n    }\n\n    private val binding by viewBinding(DialogTipConfigBinding::bind)\n\n    override fun onStart() {\n        super.onStart()\n        setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)\n    }\n\n    override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) {\n        initView()\n        initEvent()\n        observeEvent<String>(EventBus.TIP_COLOR) {\n            upTvTipColor()\n            upTvTipDividerColor()\n        }\n    }\n\n    private fun initView() {\n        if (ReadBookConfig.titleMode !in binding.rgTitleMode.indices) {\n            ReadBookConfig.titleMode = 0\n        }\n        binding.rgTitleMode.checkByIndex(ReadBookConfig.titleMode)\n        binding.dsbTitleSize.progress = ReadBookConfig.titleSize\n        binding.dsbTitleTop.progress = ReadBookConfig.titleTopSpacing\n        binding.dsbTitleBottom.progress = ReadBookConfig.titleBottomSpacing\n\n        binding.tvHeaderShow.text =\n            ReadTipConfig.getHeaderModes(requireContext())[ReadTipConfig.headerMode]\n        binding.tvFooterShow.text =\n            ReadTipConfig.getFooterModes(requireContext())[ReadTipConfig.footerMode]\n\n        ReadTipConfig.run {\n            tipNames.let { tipNames ->\n                binding.tvHeaderLeft.text =\n                    tipNames.getOrElse(tipValues.indexOf(tipHeaderLeft)) { tipNames[none] }\n                binding.tvHeaderMiddle.text =\n                    tipNames.getOrElse(tipValues.indexOf(tipHeaderMiddle)) { tipNames[none] }\n                binding.tvHeaderRight.text =\n                    tipNames.getOrElse(tipValues.indexOf(tipHeaderRight)) { tipNames[none] }\n                binding.tvFooterLeft.text =\n                    tipNames.getOrElse(tipValues.indexOf(tipFooterLeft)) { tipNames[none] }\n                binding.tvFooterMiddle.text =\n                    tipNames.getOrElse(tipValues.indexOf(tipFooterMiddle)) { tipNames[none] }\n                binding.tvFooterRight.text =\n                    tipNames.getOrElse(tipValues.indexOf(tipFooterRight)) { tipNames[none] }\n            }\n        }\n        upTvTipColor()\n        upTvTipDividerColor()\n    }\n\n    private fun upTvTipColor() {\n        val tipColorNames = ReadTipConfig.tipColorNames\n        val tipColor = ReadTipConfig.tipColor\n        binding.tvTipColor.text = if (tipColor == 0) {\n            tipColorNames.first()\n        } else {\n            \"#${tipColor.hexString}\"\n        }\n    }\n\n    private fun upTvTipDividerColor() {\n        val tipDividerColorNames = ReadTipConfig.tipDividerColorNames\n        val tipDividerColor = ReadTipConfig.tipDividerColor\n        binding.tvTipDividerColor.text = when (tipDividerColor) {\n            -1, 0 -> tipDividerColorNames[tipDividerColor + 1]\n            else -> \"#${tipDividerColor.hexString}\"\n        }\n    }\n\n    private fun initEvent() = binding.run {\n        rgTitleMode.setOnCheckedChangeListener { _, checkedId ->\n            ReadBookConfig.titleMode = rgTitleMode.getIndexById(checkedId)\n            postEvent(EventBus.UP_CONFIG, arrayListOf(5))\n        }\n        dsbTitleSize.onChanged = {\n            ReadBookConfig.titleSize = it\n            postEvent(EventBus.UP_CONFIG, arrayListOf(8, 5))\n        }\n        dsbTitleTop.onChanged = {\n            ReadBookConfig.titleTopSpacing = it\n            postEvent(EventBus.UP_CONFIG, arrayListOf(8, 5))\n        }\n        dsbTitleBottom.onChanged = {\n            ReadBookConfig.titleBottomSpacing = it\n            postEvent(EventBus.UP_CONFIG, arrayListOf(8, 5))\n        }\n        llHeaderShow.setOnClickListener {\n            val headerModes = ReadTipConfig.getHeaderModes(requireContext())\n            context?.selector(items = headerModes.values.toList()) { _, i ->\n                ReadTipConfig.headerMode = headerModes.keys.toList()[i]\n                tvHeaderShow.text = headerModes[ReadTipConfig.headerMode]\n                postEvent(EventBus.UP_CONFIG, arrayListOf(2))\n            }\n        }\n        llFooterShow.setOnClickListener {\n            val footerModes = ReadTipConfig.getFooterModes(requireContext())\n            context?.selector(items = footerModes.values.toList()) { _, i ->\n                ReadTipConfig.footerMode = footerModes.keys.toList()[i]\n                tvFooterShow.text = footerModes[ReadTipConfig.footerMode]\n                postEvent(EventBus.UP_CONFIG, arrayListOf(2))\n            }\n        }\n        llHeaderLeft.setOnClickListener {\n            context?.selector(items = ReadTipConfig.tipNames) { _, i ->\n                val tipValue = ReadTipConfig.tipValues[i]\n                clearRepeat(tipValue)\n                ReadTipConfig.tipHeaderLeft = tipValue\n                tvHeaderLeft.text = ReadTipConfig.tipNames[i]\n                postEvent(EventBus.UP_CONFIG, arrayListOf(2, 6))\n            }\n        }\n        llHeaderMiddle.setOnClickListener {\n            context?.selector(items = ReadTipConfig.tipNames) { _, i ->\n                val tipValue = ReadTipConfig.tipValues[i]\n                clearRepeat(tipValue)\n                ReadTipConfig.tipHeaderMiddle = tipValue\n                tvHeaderMiddle.text = ReadTipConfig.tipNames[i]\n                postEvent(EventBus.UP_CONFIG, arrayListOf(2, 6))\n            }\n        }\n        llHeaderRight.setOnClickListener {\n            context?.selector(items = ReadTipConfig.tipNames) { _, i ->\n                val tipValue = ReadTipConfig.tipValues[i]\n                clearRepeat(tipValue)\n                ReadTipConfig.tipHeaderRight = tipValue\n                tvHeaderRight.text = ReadTipConfig.tipNames[i]\n                postEvent(EventBus.UP_CONFIG, arrayListOf(2, 6))\n            }\n        }\n        llFooterLeft.setOnClickListener {\n            context?.selector(items = ReadTipConfig.tipNames) { _, i ->\n                val tipValue = ReadTipConfig.tipValues[i]\n                clearRepeat(tipValue)\n                ReadTipConfig.tipFooterLeft = tipValue\n                tvFooterLeft.text = ReadTipConfig.tipNames[i]\n                postEvent(EventBus.UP_CONFIG, arrayListOf(2, 6))\n            }\n        }\n        llFooterMiddle.setOnClickListener {\n            context?.selector(items = ReadTipConfig.tipNames) { _, i ->\n                val tipValue = ReadTipConfig.tipValues[i]\n                clearRepeat(tipValue)\n                ReadTipConfig.tipFooterMiddle = tipValue\n                tvFooterMiddle.text = ReadTipConfig.tipNames[i]\n                postEvent(EventBus.UP_CONFIG, arrayListOf(2, 6))\n            }\n        }\n        llFooterRight.setOnClickListener {\n            context?.selector(items = ReadTipConfig.tipNames) { _, i ->\n                val tipValue = ReadTipConfig.tipValues[i]\n                clearRepeat(tipValue)\n                ReadTipConfig.tipFooterRight = tipValue\n                tvFooterRight.text = ReadTipConfig.tipNames[i]\n                postEvent(EventBus.UP_CONFIG, arrayListOf(2, 6))\n            }\n        }\n        llTipColor.setOnClickListener {\n            context?.selector(items = ReadTipConfig.tipColorNames) { _, i ->\n                when (i) {\n                    0 -> {\n                        ReadTipConfig.tipColor = 0\n                        upTvTipColor()\n                        postEvent(EventBus.UP_CONFIG, arrayListOf(2))\n                    }\n\n                    1 -> ColorPickerDialog.newBuilder()\n                        .setShowAlphaSlider(false)\n                        .setDialogType(ColorPickerDialog.TYPE_CUSTOM)\n                        .setDialogId(TIP_COLOR)\n                        .show(requireActivity())\n                }\n            }\n        }\n        llTipDividerColor.setOnClickListener {\n            context?.selector(items = ReadTipConfig.tipDividerColorNames) { _, i ->\n                when (i) {\n                    0, 1 -> {\n                        ReadTipConfig.tipDividerColor = i - 1\n                        upTvTipDividerColor()\n                        postEvent(EventBus.UP_CONFIG, arrayListOf(2))\n                    }\n\n                    2 -> ColorPickerDialog.newBuilder()\n                        .setShowAlphaSlider(false)\n                        .setDialogType(ColorPickerDialog.TYPE_CUSTOM)\n                        .setDialogId(TIP_DIVIDER_COLOR)\n                        .show(requireActivity())\n                }\n            }\n        }\n    }\n\n    private fun clearRepeat(repeat: Int) = ReadTipConfig.apply {\n        if (repeat != none) {\n            if (tipHeaderLeft == repeat) {\n                tipHeaderLeft = none\n                binding.tvHeaderLeft.text = tipNames[none]\n            }\n            if (tipHeaderMiddle == repeat) {\n                tipHeaderMiddle = none\n                binding.tvHeaderMiddle.text = tipNames[none]\n            }\n            if (tipHeaderRight == repeat) {\n                tipHeaderRight = none\n                binding.tvHeaderRight.text = tipNames[none]\n            }\n            if (tipFooterLeft == repeat) {\n                tipFooterLeft = none\n                binding.tvFooterLeft.text = tipNames[none]\n            }\n            if (tipFooterMiddle == repeat) {\n                tipFooterMiddle = none\n                binding.tvFooterMiddle.text = tipNames[none]\n            }\n            if (tipFooterRight == repeat) {\n                tipFooterRight = none\n                binding.tvFooterRight.text = tipNames[none]\n            }\n        }\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/book/read/page/AutoPager.kt",
    "content": "package io.legado.app.ui.book.read.page\n\nimport android.graphics.Canvas\nimport android.graphics.Paint\nimport android.os.SystemClock\nimport androidx.core.graphics.withClip\nimport io.legado.app.help.config.AppConfig\nimport io.legado.app.help.config.ReadBookConfig\nimport io.legado.app.lib.theme.ThemeStore\nimport io.legado.app.ui.book.read.page.entities.PageDirection\nimport io.legado.app.utils.canvasrecorder.CanvasRecorderFactory\nimport io.legado.app.utils.canvasrecorder.recordIfNeeded\n\n/**\n * 自动翻页\n */\nclass AutoPager(private val readView: ReadView) : Runnable {\n    private var progress = 0\n    var isRunning = false\n        private set\n    private var isPausing = false\n    private var isEInkMode = false\n    private var scrollOffsetRemain = 0.0\n    private var scrollOffset = 0\n    private var lastTimeMillis = 0L\n    private var canvasRecorder = CanvasRecorderFactory.create()\n    private val paint by lazy { Paint() }\n\n\n    fun start() {\n        isRunning = true\n        isEInkMode = AppConfig.isEInkMode\n        readView.curPage.upSelectAble(false)\n        if (isEInkMode) {\n            readView.postDelayed(this, ReadBookConfig.autoReadSpeed * 1000L)\n        } else {\n            paint.color = ThemeStore.accentColor\n            lastTimeMillis = SystemClock.uptimeMillis()\n            readView.invalidate()\n        }\n    }\n\n    fun stop() {\n        if (!isRunning) {\n            return\n        }\n        isRunning = false\n        isPausing = false\n        isEInkMode = false\n        readView.removeCallbacks(this)\n        readView.curPage.upSelectAble(AppConfig.textSelectAble)\n        readView.invalidate()\n        reset()\n        canvasRecorder.recycle()\n    }\n\n    fun pause() {\n        if (!isRunning) {\n            return\n        }\n        isPausing = true\n        readView.removeCallbacks(this)\n    }\n\n    fun resume() {\n        if (!isRunning) {\n            return\n        }\n        isPausing = false\n        if (isEInkMode) {\n            readView.postDelayed(this, ReadBookConfig.autoReadSpeed * 1000L)\n        } else {\n            lastTimeMillis = SystemClock.uptimeMillis()\n            readView.invalidate()\n        }\n    }\n\n    fun reset() {\n        if (isEInkMode) {\n            readView.removeCallbacks(this)\n            readView.postDelayed(this, ReadBookConfig.autoReadSpeed * 1000L)\n        } else {\n            progress = 0\n            scrollOffsetRemain = 0.0\n            scrollOffset = 0\n            canvasRecorder.invalidate()\n        }\n    }\n\n    fun upRecorder() {\n        canvasRecorder.recycle()\n        canvasRecorder = CanvasRecorderFactory.create()\n    }\n\n    fun onDraw(canvas: Canvas) {\n        if (!isRunning || isEInkMode) {\n            return\n        }\n\n        if (readView.isScroll) {\n            if (!isPausing) {\n                readView.curPage.scroll(-scrollOffset)\n                scrollOffset = 0\n            }\n        } else {\n            val bottom = progress\n            val width = readView.width\n\n            canvasRecorder.recordIfNeeded(readView.nextPage)\n            canvas.withClip(0, 0, width, bottom) {\n                canvasRecorder.draw(this)\n            }\n\n            canvas.drawRect(\n                0f,\n                bottom.toFloat() - 1,\n                width.toFloat(),\n                bottom.toFloat(),\n                paint\n            )\n            if (!isPausing) readView.postInvalidate()\n        }\n\n    }\n\n    fun computeOffset() {\n        if (!isRunning || isPausing || isEInkMode) {\n            return\n        }\n\n        val currentTime = SystemClock.uptimeMillis()\n        val elapsedTime = currentTime - lastTimeMillis\n        lastTimeMillis = currentTime\n\n        val readTime = ReadBookConfig.autoReadSpeed * 1000.0\n        val height = readView.height\n        scrollOffsetRemain += height / readTime * elapsedTime\n        if (scrollOffsetRemain < 1) {\n            return\n        }\n        scrollOffset = scrollOffsetRemain.toInt()\n        this.scrollOffsetRemain -= scrollOffset\n        if (!readView.isScroll) {\n            progress += scrollOffset\n            if (progress >= height) {\n                if (!readView.fillPage(PageDirection.NEXT)) {\n                    stop()\n                } else {\n                    reset()\n                }\n            }\n        }\n    }\n\n    override fun run() {\n        if (!isRunning || isPausing) {\n            return\n        }\n\n        if (!readView.fillPage(PageDirection.NEXT)) {\n            stop()\n        } else {\n            readView.postDelayed(this, ReadBookConfig.autoReadSpeed * 1000L)\n        }\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/book/read/page/ContentTextView.kt",
    "content": "package io.legado.app.ui.book.read.page\n\nimport android.content.Context\nimport android.graphics.Canvas\nimport android.graphics.Paint\nimport android.util.AttributeSet\nimport android.view.MotionEvent\nimport android.view.View\nimport io.legado.app.R\nimport io.legado.app.data.entities.Bookmark\nimport io.legado.app.help.config.AppConfig\nimport io.legado.app.model.ReadBook\nimport io.legado.app.ui.book.read.page.delegate.PageDelegate\nimport io.legado.app.ui.book.read.page.entities.TextLine\nimport io.legado.app.ui.book.read.page.entities.TextPage\nimport io.legado.app.ui.book.read.page.entities.TextPos\nimport io.legado.app.ui.book.read.page.entities.column.BaseColumn\nimport io.legado.app.ui.book.read.page.entities.column.ButtonColumn\nimport io.legado.app.ui.book.read.page.entities.column.ImageColumn\nimport io.legado.app.ui.book.read.page.entities.column.ReviewColumn\nimport io.legado.app.ui.book.read.page.entities.column.TextColumn\nimport io.legado.app.ui.book.read.page.provider.ChapterProvider\nimport io.legado.app.ui.book.read.page.provider.TextPageFactory\nimport io.legado.app.ui.widget.dialog.PhotoDialog\nimport io.legado.app.utils.activity\nimport io.legado.app.utils.dpToPx\nimport io.legado.app.utils.getCompatColor\nimport io.legado.app.utils.showDialogFragment\nimport io.legado.app.utils.toastOnUi\nimport java.util.concurrent.Executors\nimport kotlin.math.max\nimport kotlin.math.min\n\n/**\n * 阅读内容视图\n */\nclass ContentTextView(context: Context, attrs: AttributeSet?) : View(context, attrs) {\n    var selectAble = AppConfig.textSelectAble\n    val selectedPaint by lazy {\n        Paint().apply {\n            color = context.getCompatColor(R.color.btn_bg_press_2)\n            style = Paint.Style.FILL\n        }\n    }\n    private var callBack: CallBack\n    private val visibleRect = ChapterProvider.visibleRect\n    val selectStart = TextPos(0, -1, -1)\n    private val selectEnd = TextPos(0, -1, -1)\n    var textPage: TextPage = TextPage()\n        private set\n    var isMainView = false\n    var longScreenshot = false\n    var reverseStartCursor = false\n    var reverseEndCursor = false\n\n    //滚动参数\n    private val pageFactory get() = callBack.pageFactory\n    private val pageDelegate get() = callBack.pageDelegate\n    private var pageOffset = 0\n    private var autoPager: AutoPager? = null\n    private var isScroll = false\n    private val renderRunnable by lazy { Runnable { preRenderPage() } }\n\n    //绘制图片的paint\n    val imagePaint by lazy {\n        Paint().apply {\n            isAntiAlias = AppConfig.useAntiAlias\n        }\n    }\n\n    init {\n        callBack = activity as CallBack\n    }\n\n    /**\n     * 设置内容\n     */\n    fun setContent(textPage: TextPage) {\n        this.textPage = textPage\n        // 非滑动翻页动画需要同步重绘，不然翻页可能会出现闪烁\n        if (isScroll) {\n            postInvalidate()\n        } else {\n            invalidate()\n        }\n    }\n\n    override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {\n        super.onSizeChanged(w, h, oldw, oldh)\n        if (!isMainView) return\n        ChapterProvider.upViewSize(w, h)\n        textPage.format()\n    }\n\n    override fun onDraw(canvas: Canvas) {\n        super.onDraw(canvas)\n        autoPager?.onDraw(canvas)\n        if (longScreenshot) {\n            canvas.translate(0f, scrollY.toFloat())\n        }\n        check(!visibleRect.isEmpty) { \"visibleRect 为空\" }\n        canvas.clipRect(visibleRect)\n        drawPage(canvas)\n    }\n\n    /**\n     * 绘制页面\n     */\n    private fun drawPage(canvas: Canvas) {\n        var relativeOffset = relativeOffset(0)\n        textPage.draw(this, canvas, relativeOffset)\n        if (!callBack.isScroll) return\n        //滚动翻页\n        if (!pageFactory.hasNext()) return\n        val textPage1 = relativePage(1)\n        relativeOffset += textPage.height\n        textPage1.draw(this, canvas, relativeOffset)\n        if (!pageFactory.hasNextPlus()) return\n        relativeOffset += textPage1.height\n        if (relativeOffset < ChapterProvider.visibleHeight) {\n            val textPage2 = relativePage(2)\n            textPage2.draw(this, canvas, relativeOffset)\n        }\n    }\n\n    override fun computeScroll() {\n        pageDelegate?.computeScroll()\n        autoPager?.computeOffset()\n    }\n\n    /**\n     * 滚动事件\n     * pageOffset 向上滚动 减小 向下滚动 增大\n     * pageOffset 范围 0 ~ -textPage.height 大于0为上一页，小于-textPage.height为下一页\n     * 以内容显示区域顶端为界，pageOffset的绝对值为textPage上方的高度\n     * pageOffset + textPage.height 为 textPage 下方的高度\n     */\n    fun scroll(mOffset: Int) {\n        pageOffset += mOffset\n        if (longScreenshot) {\n            scrollY += -mOffset\n        }\n        if (!pageFactory.hasPrev() && pageOffset > 0) {\n            pageOffset = 0\n            pageDelegate?.abortAnim()\n        } else if (!pageFactory.hasNext()\n            && pageOffset < 0\n            && pageOffset + textPage.height < ChapterProvider.visibleHeight\n        ) {\n            val offset = (ChapterProvider.visibleHeight - textPage.height).toInt()\n            pageOffset = min(0, offset)\n            pageDelegate?.abortAnim()\n        } else if (pageOffset > 0) {\n            if (pageFactory.moveToPrev(true)) {\n                pageOffset -= textPage.height.toInt()\n            } else {\n                pageOffset = 0\n                pageDelegate?.abortAnim()\n            }\n        } else if (pageOffset < -textPage.height) {\n            val height = textPage.height\n            if (pageFactory.moveToNext(upContent = true)) {\n                pageOffset += height.toInt()\n            } else {\n                pageOffset = -height.toInt()\n                pageDelegate?.abortAnim()\n            }\n        }\n        postInvalidate()\n    }\n\n    fun submitRenderTask() {\n        renderThread.submit(renderRunnable)\n    }\n\n    private fun preRenderPage() {\n        val view = this\n        var invalidate = false\n        pageFactory.run {\n            if (hasPrev() && prevPage.render(view)) {\n                invalidate = true\n            }\n            if (curPage.render(view)) {\n                invalidate = true\n            }\n            if (hasNext() && nextPage.render(view) && callBack.isScroll) {\n                invalidate = true\n            }\n            if (hasNextPlus() && nextPlusPage.render(view) && callBack.isScroll\n                && relativeOffset(2) < ChapterProvider.visibleHeight\n            ) {\n                invalidate = true\n            }\n            if (invalidate) {\n                postInvalidate()\n                pageDelegate?.postInvalidate()\n            }\n        }\n    }\n\n    /**\n     * 重置滚动位置\n     */\n    fun resetPageOffset() {\n        pageOffset = 0\n    }\n\n    /**\n     * 长按\n     */\n    fun longPress(\n        x: Float,\n        y: Float,\n        select: (textPos: TextPos) -> Unit,\n    ) {\n        touch(x, y) { _, textPos, _, _, column ->\n            when (column) {\n                is ImageColumn -> callBack.onImageLongPress(x, y, column.src)\n                is TextColumn -> {\n                    if (!selectAble) return@touch\n                    column.selected = true\n                    select(textPos)\n                }\n            }\n        }\n    }\n\n    /**\n     * 单击\n     * @return true:已处理, false:未处理\n     */\n    @Suppress(\"UNUSED_ANONYMOUS_PARAMETER\")\n    fun click(x: Float, y: Float): Boolean {\n        var handled = false\n        touch(x, y) { _, textPos, textPage, textLine, column ->\n            when (column) {\n                is ButtonColumn -> {\n                    context.toastOnUi(\"Button Pressed!\")\n                    handled = true\n                }\n\n                is ReviewColumn -> {\n                    context.toastOnUi(\"Button Pressed!\")\n                    handled = true\n                }\n\n                is ImageColumn -> if (AppConfig.previewImageByClick) {\n                    activity?.showDialogFragment(PhotoDialog(column.src))\n                    handled = true\n                }\n            }\n        }\n        return handled\n    }\n\n    /**\n     * 选择文字\n     */\n    fun selectText(\n        x: Float,\n        y: Float,\n        select: (textPos: TextPos) -> Unit,\n    ) {\n        touchRough(x, y) { _, textPos, _, _, column ->\n            if (column is TextColumn) {\n                column.selected = true\n                select(textPos)\n            }\n        }\n    }\n\n    /**\n     * 开始选择符移动\n     */\n    fun selectStartMove(x: Float, y: Float) {\n        touchRough(x, y) { _, textPos, _, _, _ ->\n            if (selectStart.compare(textPos) == 0) {\n                return@touchRough\n            }\n            if (textPos.compare(selectEnd) <= 0) {\n                selectStartMoveIndex(textPos)\n            } else {\n                touchRough(x - 2 * cursorWidth, y) { _, textPos, _, _, _ ->\n                    if (textPos.compare(selectEnd) > 0) {\n                        reverseStartCursor = true\n                        reverseEndCursor = false\n                        selectEnd.columnIndex++\n                        selectStartMoveIndex(selectEnd)\n                        selectEndMoveIndex(textPos)\n                    }\n                }\n            }\n        }\n    }\n\n    /**\n     * 结束选择符移动\n     */\n    fun selectEndMove(x: Float, y: Float) {\n        touchRough(x, y) { _, textPos, _, _, _ ->\n            if (textPos.compare(selectEnd) == 0) {\n                return@touchRough\n            }\n            if (textPos.compare(selectStart) >= 0) {\n                selectEndMoveIndex(textPos)\n            } else {\n                touchRough(x + 2 * cursorWidth, y) { _, textPos, _, _, _ ->\n                    if (textPos.compare(selectStart) < 0) {\n                        reverseEndCursor = true\n                        reverseStartCursor = false\n                        selectStart.columnIndex--\n                        selectEndMoveIndex(selectStart)\n                        selectStartMoveIndex(textPos)\n                    }\n                }\n            }\n        }\n    }\n\n    /**\n     * 触碰位置信息\n     * @param touched 回调\n     */\n    private fun touch(\n        x: Float,\n        y: Float,\n        touched: (\n            relativeOffset: Float,\n            textPos: TextPos,\n            textPage: TextPage,\n            textLine: TextLine,\n            column: BaseColumn\n        ) -> Unit\n    ) {\n        if (!visibleRect.contains(x, y)) return\n        var relativeOffset: Float\n        for (relativePos in 0..2) {\n            relativeOffset = relativeOffset(relativePos)\n            if (relativePos > 0) {\n                //滚动翻页\n                if (!callBack.isScroll) return\n                if (relativeOffset >= ChapterProvider.visibleHeight) return\n            }\n            val textPage = relativePage(relativePos)\n            for ((lineIndex, textLine) in textPage.lines.withIndex()) {\n                if (textLine.isTouch(x, y, relativeOffset)) {\n                    for ((charIndex, textColumn) in textLine.columns.withIndex()) {\n                        if (textColumn.isTouch(x)) {\n                            touched.invoke(\n                                relativeOffset,\n                                TextPos(relativePos, lineIndex, charIndex),\n                                textPage, textLine, textColumn\n                            )\n                            return\n                        }\n                    }\n                    return\n                }\n            }\n        }\n    }\n\n    /**\n     * 触碰位置信息\n     * 文本选择专用\n     * @param touched 回调\n     */\n    private fun touchRough(\n        x: Float,\n        y: Float,\n        touched: (\n            relativeOffset: Float,\n            textPos: TextPos,\n            textPage: TextPage,\n            textLine: TextLine,\n            column: BaseColumn\n        ) -> Unit\n    ) {\n        var relativeOffset: Float\n        for (relativePos in 0..2) {\n            relativeOffset = relativeOffset(relativePos)\n            if (relativePos > 0) {\n                //滚动翻页\n                if (!callBack.isScroll) return\n                if (relativeOffset >= ChapterProvider.visibleHeight) return\n            }\n            val textPage = relativePage(relativePos)\n            for (lineIndex in textPage.lines.indices) {\n                val textLine = textPage.getLine(lineIndex)\n                if (textLine.isTouchY(y, relativeOffset)) {\n                    if (textPage.doublePage) {\n                        val halfWidth = width / 2\n                        if (textLine.isLeftLine && x > halfWidth) {\n                            continue\n                        }\n                        if (!textLine.isLeftLine && x < halfWidth) {\n                            continue\n                        }\n                    }\n                    val columns = textLine.columns\n                    for (charIndex in columns.indices) {\n                        val textColumn = columns[charIndex]\n                        if (textColumn.isTouch(x)) {\n                            touched.invoke(\n                                relativeOffset,\n                                TextPos(relativePos, lineIndex, charIndex),\n                                textPage, textLine, textColumn\n                            )\n                            return\n                        }\n                    }\n                    val isLast = columns.first().start < x\n                    val charIndex = if (isLast) columns.lastIndex + 1 else -1\n                    val textColumn = if (isLast) columns.last() else columns.first()\n                    touched.invoke(\n                        relativeOffset,\n                        TextPos(relativePos, lineIndex, charIndex),\n                        textPage, textLine, textColumn\n                    )\n                    return\n                }\n            }\n        }\n    }\n\n    fun getCurVisiblePage(): TextPage {\n        val visiblePage = TextPage()\n        var relativeOffset: Float\n        for (relativePos in 0..2) {\n            relativeOffset = relativeOffset(relativePos)\n            if (relativePos > 0) {\n                //滚动翻页\n                if (!callBack.isScroll) break\n                if (relativeOffset >= ChapterProvider.visibleHeight) break\n            }\n            val textPage = relativePage(relativePos)\n            val lines = textPage.lines\n            for (i in lines.indices) {\n                val textLine = lines[i]\n                if (textLine.isVisible(relativeOffset)) {\n                    val visibleLine = textLine.copy().apply {\n                        lineTop += relativeOffset\n                        lineBottom += relativeOffset\n                    }\n                    visiblePage.addLine(visibleLine)\n                }\n            }\n        }\n        return visiblePage\n    }\n\n    fun getReadAloudPos(): Pair<Int, TextLine>? {\n        var relativeOffset: Float\n        for (relativePos in 0..2) {\n            relativeOffset = relativeOffset(relativePos)\n            if (relativePos > 0) {\n                //滚动翻页\n                if (!callBack.isScroll) break\n                if (relativeOffset >= ChapterProvider.visibleHeight) break\n            }\n            val textPage = relativePage(relativePos)\n            val lines = textPage.lines\n            for (i in lines.indices) {\n                val textLine = lines[i]\n                if (textLine.isVisible(relativeOffset)) {\n                    val visibleLine = textLine.copy().apply {\n                        lineTop += relativeOffset\n                        lineBottom += relativeOffset\n                    }\n                    return textPage.chapterIndex to visibleLine\n                }\n            }\n        }\n        return null\n    }\n\n    /**\n     * 选择开始文字\n     */\n    fun selectStartMoveIndex(\n        relativePagePos: Int,\n        lineIndex: Int,\n        charIndex: Int,\n    ) {\n        selectStart.relativePagePos = relativePagePos\n        selectStart.lineIndex = lineIndex\n        selectStart.columnIndex = max(0, charIndex)\n        val textLine = relativePage(relativePagePos).getLine(lineIndex)\n        val textColumn = textLine.getColumn(charIndex)\n        upSelectedStart(\n            if (charIndex < textLine.columns.size) textColumn.start else textColumn.end,\n            textLine.lineBottom + relativeOffset(relativePagePos),\n            textLine.lineTop + relativeOffset(relativePagePos)\n        )\n        upSelectChars()\n    }\n\n    fun selectStartMoveIndex(textPos: TextPos) = textPos.run {\n        selectStartMoveIndex(relativePagePos, lineIndex, columnIndex)\n    }\n\n    /**\n     * 选择结束文字\n     */\n    fun selectEndMoveIndex(\n        relativePage: Int,\n        lineIndex: Int,\n        charIndex: Int,\n    ) {\n        selectEnd.relativePagePos = relativePage\n        selectEnd.lineIndex = lineIndex\n        val textLine = relativePage(relativePage).getLine(lineIndex)\n        selectEnd.columnIndex = min(charIndex, textLine.columns.lastIndex)\n        val textColumn = textLine.getColumn(charIndex)\n        upSelectedEnd(\n            if (charIndex > -1) textColumn.end else textColumn.start,\n            textLine.lineBottom + relativeOffset(relativePage)\n        )\n        upSelectChars()\n    }\n\n    fun selectEndMoveIndex(textPos: TextPos) = textPos.run {\n        selectEndMoveIndex(relativePagePos, lineIndex, columnIndex)\n    }\n\n    private fun upSelectChars() {\n        if (!selectStart.isSelected() && !selectEnd.isSelected()) {\n            return\n        }\n        val last = if (callBack.isScroll) 2 else 0\n        val textPos = TextPos(0, 0, 0)\n        for (relativePos in 0..last) {\n            textPos.relativePagePos = relativePos\n            val textPage = relativePage(relativePos)\n            for ((lineIndex, textLine) in textPage.lines.withIndex()) {\n                textPos.lineIndex = lineIndex\n                for ((charIndex, column) in textLine.columns.withIndex()) {\n                    textPos.columnIndex = charIndex\n                    if (column is TextColumn) {\n                        val compareStart = textPos.compare(selectStart)\n                        val compareEnd = textPos.compare(selectEnd)\n                        column.selected = compareStart >= 0 && compareEnd <= 0\n                        column.isSearchResult =\n                            column.selected && callBack.isSelectingSearchResult\n                        if (column.isSearchResult) {\n                            textPage.searchResult.add(column)\n                        }\n                    }\n                }\n            }\n        }\n        postInvalidate()\n    }\n\n    private fun upSelectedStart(x: Float, y: Float, top: Float) {\n        callBack.run {\n            upSelectedStart(x, y + headerHeight, top + headerHeight)\n        }\n    }\n\n    private fun upSelectedEnd(x: Float, y: Float) {\n        callBack.run {\n            upSelectedEnd(x, y + headerHeight)\n        }\n    }\n\n    fun resetReverseCursor() {\n        reverseStartCursor = false\n        reverseEndCursor = false\n    }\n\n    fun cancelSelect(clearSearchResult: Boolean = false) {\n        val last = if (callBack.isScroll) 2 else 0\n        for (relativePos in 0..last) {\n            val textPage = relativePage(relativePos)\n            textPage.lines.forEach { textLine ->\n                textLine.columns.forEach {\n                    if (it is TextColumn) {\n                        it.selected = false\n                        if (clearSearchResult) {\n                            it.isSearchResult = false\n                            textPage.searchResult.remove(it)\n                        }\n                    }\n                }\n            }\n        }\n        selectStart.reset()\n        selectEnd.reset()\n        postInvalidate()\n        callBack.onCancelSelect()\n    }\n\n    fun getSelectedText(): String {\n        val textPos = TextPos(0, 0, 0)\n        val builder = StringBuilder()\n        for (relativePos in selectStart.relativePagePos..selectEnd.relativePagePos) {\n            val textPage = relativePage(relativePos)\n            textPos.relativePagePos = relativePos\n            textPage.lines.forEachIndexed { lineIndex, textLine ->\n                textPos.lineIndex = lineIndex\n                textLine.columns.forEachIndexed { charIndex, column ->\n                    textPos.columnIndex = charIndex\n                    val compareStart = textPos.compare(selectStart)\n                    val compareEnd = textPos.compare(selectEnd)\n                    if (column is TextColumn) {\n                        when {\n                            compareStart == -1 -> if (\n                                selectStart.columnIndex == textLine.columns.size\n                                && charIndex == textLine.columns.lastIndex\n                            ) {\n                                builder.append(\"\\n\")\n                            }\n\n                            compareEnd == 1 -> if (selectEnd.columnIndex == -1 && charIndex == 0) {\n                                builder.append(\"\\n\")\n                            }\n\n                            compareStart >= 0 && compareEnd <= 0 -> {\n                                builder.append(column.charData)\n                                if (\n                                    textLine.isParagraphEnd\n                                    && charIndex == textLine.columns.lastIndex\n                                    && compareEnd != 0\n                                ) {\n                                    builder.append(\"\\n\")\n                                }\n                            }\n                        }\n                    }\n                }\n            }\n        }\n        return builder.toString()\n    }\n\n    fun createBookmark(): Bookmark? {\n        val page = relativePage(selectStart.relativePagePos)\n        page.getTextChapter().let { chapter ->\n            ReadBook.book?.let { book ->\n                return book.createBookMark().apply {\n                    chapterIndex = page.chapterIndex\n                    chapterPos = chapter.getReadLength(page.index) +\n                            page.getPosByLineColumn(selectStart.lineIndex, selectStart.columnIndex)\n                    chapterName = chapter.title\n                    bookText = getSelectedText()\n                }\n            }\n        }\n        return null\n    }\n\n    private fun relativeOffset(relativePos: Int): Float {\n        return when (relativePos) {\n            0 -> pageOffset.toFloat()\n            1 -> pageOffset + textPage.height\n            else -> pageOffset + textPage.height + pageFactory.nextPage.height\n        }\n    }\n\n    fun relativePage(relativePos: Int): TextPage {\n        return when (relativePos) {\n            0 -> textPage\n            1 -> pageFactory.nextPage\n            else -> pageFactory.nextPlusPage\n        }\n    }\n\n    fun setAutoPager(autoPager: AutoPager?) {\n        this.autoPager = autoPager\n    }\n\n    fun setIsScroll(value: Boolean) {\n        isScroll = value\n    }\n\n    override fun canScrollVertically(direction: Int): Boolean {\n        return callBack.isScroll && pageFactory.hasNext()\n    }\n\n    override fun dispatchTouchEvent(event: MotionEvent): Boolean {\n        when (event.action) {\n            MotionEvent.ACTION_DOWN -> {\n                longScreenshot = true\n                scrollY = 0\n            }\n\n            MotionEvent.ACTION_UP -> {\n                longScreenshot = false\n                scrollY = 0\n            }\n        }\n        return callBack.onLongScreenshotTouchEvent(event)\n    }\n\n    companion object {\n        private val renderThread by lazy {\n            Executors.newSingleThreadExecutor {\n                Thread(it, \"TextPageRender\")\n            }\n        }\n        private val cursorWidth = 24.dpToPx()\n    }\n\n    interface CallBack {\n        val headerHeight: Int\n        val pageFactory: TextPageFactory\n        val pageDelegate: PageDelegate?\n        val isScroll: Boolean\n        var isSelectingSearchResult: Boolean\n        fun upSelectedStart(x: Float, y: Float, top: Float)\n        fun upSelectedEnd(x: Float, y: Float)\n        fun onImageLongPress(x: Float, y: Float, src: String)\n        fun onCancelSelect()\n        fun onLongScreenshotTouchEvent(event: MotionEvent): Boolean\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/book/read/page/PageView.kt",
    "content": "package io.legado.app.ui.book.read.page\n\nimport android.annotation.SuppressLint\nimport android.content.Context\nimport android.graphics.drawable.LayerDrawable\nimport android.view.LayoutInflater\nimport android.widget.FrameLayout\nimport androidx.core.content.ContextCompat\nimport androidx.core.graphics.drawable.toDrawable\nimport androidx.core.view.ViewCompat\nimport androidx.core.view.WindowInsetsCompat\nimport androidx.core.view.isGone\nimport androidx.core.view.isInvisible\nimport io.legado.app.R\nimport io.legado.app.constant.AppConst.timeFormat\nimport io.legado.app.data.entities.Bookmark\nimport io.legado.app.databinding.ViewBookPageBinding\nimport io.legado.app.help.config.AppConfig\nimport io.legado.app.help.config.ReadBookConfig\nimport io.legado.app.help.config.ReadTipConfig\nimport io.legado.app.model.ReadBook\nimport io.legado.app.ui.book.read.ReadBookActivity\nimport io.legado.app.ui.book.read.page.entities.TextLine\nimport io.legado.app.ui.book.read.page.entities.TextPage\nimport io.legado.app.ui.book.read.page.entities.TextPos\nimport io.legado.app.ui.book.read.page.provider.ChapterProvider\nimport io.legado.app.ui.widget.BatteryView\nimport io.legado.app.utils.activity\nimport io.legado.app.utils.applyNavigationBarPadding\nimport io.legado.app.utils.applyStatusBarPadding\nimport io.legado.app.utils.dpToPx\nimport io.legado.app.utils.gone\nimport io.legado.app.utils.setOnApplyWindowInsetsListenerCompat\nimport io.legado.app.utils.setTextIfNotEqual\nimport splitties.views.backgroundColor\nimport java.util.Date\n\n/**\n * 页面视图\n */\nclass PageView(context: Context) : FrameLayout(context) {\n\n    private val binding = ViewBookPageBinding.inflate(LayoutInflater.from(context), this, true)\n    private val readBookActivity get() = activity as? ReadBookActivity\n    private var battery = 100\n    private var tvTitle: BatteryView? = null\n    private var tvTime: BatteryView? = null\n    private var tvBattery: BatteryView? = null\n    private var tvBatteryP: BatteryView? = null\n    private var tvPage: BatteryView? = null\n    private var tvTotalProgress: BatteryView? = null\n    private var tvTotalProgress1: BatteryView? = null\n    private var tvPageAndTotal: BatteryView? = null\n    private var tvBookName: BatteryView? = null\n    private var tvTimeBattery: BatteryView? = null\n    private var tvTimeBatteryP: BatteryView? = null\n    private var isMainView = false\n    var isScroll = false\n\n    val headerHeight: Int\n        get() {\n            val h1 = if (binding.vwStatusBar.isGone) 0 else binding.vwStatusBar.height\n            val h2 = if (binding.llHeader.isGone) 0 else binding.llHeader.height\n            return h1 + h2 + binding.vwRoot.paddingTop\n        }\n\n    init {\n        if (!isInEditMode) {\n            upStyle()\n            binding.vwStatusBar.applyStatusBarPadding()\n            binding.vwNavigationBar.applyNavigationBarPadding()\n        }\n    }\n\n    override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {\n        super.onSizeChanged(w, h, oldw, oldh)\n        upBg()\n    }\n\n    fun upStyle() = binding.run {\n        upTipStyle()\n        ReadBookConfig.let {\n            val textColor = it.textColor\n            val tipColor = with(ReadTipConfig) {\n                if (tipColor == 0) textColor else tipColor\n            }\n            val tipDividerColor = with(ReadTipConfig) {\n                when (tipDividerColor) {\n                    -1 -> ContextCompat.getColor(context, R.color.divider)\n                    0 -> textColor\n                    else -> tipDividerColor\n                }\n            }\n            tvHeaderLeft.setColor(tipColor)\n            tvHeaderMiddle.setColor(tipColor)\n            tvHeaderRight.setColor(tipColor)\n            tvFooterLeft.setColor(tipColor)\n            tvFooterMiddle.setColor(tipColor)\n            tvFooterRight.setColor(tipColor)\n            vwTopDivider.backgroundColor = tipDividerColor\n            vwBottomDivider.backgroundColor = tipDividerColor\n            upStatusBar()\n            upNavigationBar()\n            upPaddingDisplayCutouts()\n            llHeader.setPadding(\n                it.headerPaddingLeft.dpToPx(),\n                it.headerPaddingTop.dpToPx(),\n                it.headerPaddingRight.dpToPx(),\n                it.headerPaddingBottom.dpToPx()\n            )\n            llFooter.setPadding(\n                it.footerPaddingLeft.dpToPx(),\n                it.footerPaddingTop.dpToPx(),\n                it.footerPaddingRight.dpToPx(),\n                it.footerPaddingBottom.dpToPx()\n            )\n            vwTopDivider.gone(llHeader.isGone || !it.showHeaderLine)\n            vwBottomDivider.gone(llFooter.isGone || !it.showFooterLine)\n        }\n        upTime()\n        upBattery(battery)\n    }\n\n    /**\n     * 显示状态栏时隐藏header\n     */\n    fun upStatusBar() = with(binding.vwStatusBar) {\n//        setPadding(paddingLeft, context.statusBarHeight, paddingRight, paddingBottom)\n        isGone = ReadBookConfig.hideStatusBar || readBookActivity?.isInMultiWindow == true\n    }\n\n    fun upNavigationBar() {\n        binding.vwNavigationBar.isGone = ReadBookConfig.hideNavigationBar\n    }\n\n    fun upPaddingDisplayCutouts() {\n        if (AppConfig.paddingDisplayCutouts) {\n            binding.vwRoot.setOnApplyWindowInsetsListenerCompat { _, windowInsets ->\n                val insets = windowInsets.getInsets(WindowInsetsCompat.Type.displayCutout())\n                binding.vwRoot.setPadding(\n                    insets.left,\n                    if (binding.vwStatusBar.isGone) insets.top else 0,\n                    insets.right,\n                    insets.bottom\n                )\n                windowInsets\n            }\n        } else {\n            ViewCompat.setOnApplyWindowInsetsListener(binding.vwRoot, null)\n            binding.vwRoot.setPadding(0, 0, 0, 0)\n        }\n    }\n\n    /**\n     * 更新阅读信息\n     */\n    private fun upTipStyle() = binding.run {\n        tvHeaderLeft.tag = null\n        tvHeaderMiddle.tag = null\n        tvHeaderRight.tag = null\n        tvFooterLeft.tag = null\n        tvFooterMiddle.tag = null\n        tvFooterRight.tag = null\n        llHeader.isGone = when (ReadTipConfig.headerMode) {\n            1 -> false\n            2 -> true\n            else -> !ReadBookConfig.hideStatusBar\n        }\n        llFooter.isGone = when (ReadTipConfig.footerMode) {\n            1 -> true\n            else -> false\n        }\n        ReadTipConfig.apply {\n            tvHeaderLeft.isGone = tipHeaderLeft == none\n            tvHeaderRight.isGone = tipHeaderRight == none\n            tvHeaderMiddle.isGone = tipHeaderMiddle == none\n            tvFooterLeft.isInvisible = tipFooterLeft == none\n            tvFooterRight.isGone = tipFooterRight == none\n            tvFooterMiddle.isGone = tipFooterMiddle == none\n        }\n        tvTitle = getTipView(ReadTipConfig.chapterTitle)?.apply {\n            tag = ReadTipConfig.chapterTitle\n            isBattery = false\n            typeface = ChapterProvider.typeface\n            textSize = 12f\n        }\n        tvTime = getTipView(ReadTipConfig.time)?.apply {\n            tag = ReadTipConfig.time\n            isBattery = false\n            typeface = ChapterProvider.typeface\n            textSize = 12f\n        }\n        tvBattery = getTipView(ReadTipConfig.battery)?.apply {\n            tag = ReadTipConfig.battery\n            isBattery = true\n            textSize = 11f\n        }\n        tvPage = getTipView(ReadTipConfig.page)?.apply {\n            tag = ReadTipConfig.page\n            isBattery = false\n            typeface = ChapterProvider.typeface\n            textSize = 12f\n        }\n        tvTotalProgress = getTipView(ReadTipConfig.totalProgress)?.apply {\n            tag = ReadTipConfig.totalProgress\n            isBattery = false\n            typeface = ChapterProvider.typeface\n            textSize = 12f\n        }\n        tvTotalProgress1 = getTipView(ReadTipConfig.totalProgress1)?.apply {\n            tag = ReadTipConfig.totalProgress1\n            isBattery = false\n            typeface = ChapterProvider.typeface\n            textSize = 12f\n        }\n        tvPageAndTotal = getTipView(ReadTipConfig.pageAndTotal)?.apply {\n            tag = ReadTipConfig.pageAndTotal\n            isBattery = false\n            typeface = ChapterProvider.typeface\n            textSize = 12f\n        }\n        tvBookName = getTipView(ReadTipConfig.bookName)?.apply {\n            tag = ReadTipConfig.bookName\n            isBattery = false\n            typeface = ChapterProvider.typeface\n            textSize = 12f\n        }\n        tvTimeBattery = getTipView(ReadTipConfig.timeBattery)?.apply {\n            tag = ReadTipConfig.timeBattery\n            isBattery = true\n            typeface = ChapterProvider.typeface\n            textSize = 11f\n        }\n        tvBatteryP = getTipView(ReadTipConfig.batteryPercentage)?.apply {\n            tag = ReadTipConfig.batteryPercentage\n            isBattery = false\n            typeface = ChapterProvider.typeface\n            textSize = 12f\n        }\n        tvTimeBatteryP = getTipView(ReadTipConfig.timeBatteryPercentage)?.apply {\n            tag = ReadTipConfig.timeBatteryPercentage\n            isBattery = false\n            typeface = ChapterProvider.typeface\n            textSize = 12f\n        }\n    }\n\n    /**\n     * 获取信息视图\n     * @param tip 信息类型\n     */\n    private fun getTipView(tip: Int): BatteryView? = binding.run {\n        return when (tip) {\n            ReadTipConfig.tipHeaderLeft -> tvHeaderLeft\n            ReadTipConfig.tipHeaderMiddle -> tvHeaderMiddle\n            ReadTipConfig.tipHeaderRight -> tvHeaderRight\n            ReadTipConfig.tipFooterLeft -> tvFooterLeft\n            ReadTipConfig.tipFooterMiddle -> tvFooterMiddle\n            ReadTipConfig.tipFooterRight -> tvFooterRight\n            else -> null\n        }\n    }\n\n    /**\n     * 更新背景\n     */\n    fun upBg() {\n        binding.vwRoot.background = LayerDrawable(\n            arrayOf(\n                ReadBookConfig.bgMeanColor.toDrawable(),\n                ReadBookConfig.bg\n            )\n        )\n        upBgAlpha()\n    }\n\n    /**\n     * 更新背景透明度\n     */\n    fun upBgAlpha() {\n        ReadBookConfig.bg?.alpha = (ReadBookConfig.bgAlpha / 100f * 255).toInt()\n        binding.vwRoot.invalidate()\n    }\n\n    /**\n     * 更新时间信息\n     */\n    fun upTime() {\n        tvTime?.text = timeFormat.format(Date(System.currentTimeMillis()))\n        upTimeBattery()\n    }\n\n    /**\n     * 更新电池信息\n     */\n    @SuppressLint(\"SetTextI18n\")\n    fun upBattery(battery: Int) {\n        this.battery = battery\n        tvBattery?.setBattery(battery)\n        tvBatteryP?.text = \"$battery%\"\n        upTimeBattery()\n    }\n\n    /**\n     * 更新电池信息\n     */\n    @SuppressLint(\"SetTextI18n\")\n    private fun upTimeBattery() {\n        val time = timeFormat.format(Date(System.currentTimeMillis()))\n        tvTimeBattery?.setBattery(battery, time)\n        tvTimeBatteryP?.text = \"$time $battery%\"\n    }\n\n    /**\n     * 设置内容\n     */\n    fun setContent(textPage: TextPage, resetPageOffset: Boolean = true) {\n        if (isMainView && !isScroll) {\n            setProgress(textPage)\n        } else {\n            post {\n                setProgress(textPage)\n            }\n        }\n        if (resetPageOffset) {\n            resetPageOffset()\n        }\n        binding.contentTextView.setContent(textPage)\n    }\n\n    fun invalidateContentView() {\n        binding.contentTextView.invalidate()\n    }\n\n    /**\n     * 设置无障碍文本\n     */\n    fun setContentDescription(content: String) {\n        binding.contentTextView.contentDescription = content\n    }\n\n    /**\n     * 重置滚动位置\n     */\n    fun resetPageOffset() {\n        binding.contentTextView.resetPageOffset()\n    }\n\n    /**\n     * 设置进度\n     */\n    @SuppressLint(\"SetTextI18n\")\n    fun setProgress(textPage: TextPage) = textPage.apply {\n        tvBookName?.setTextIfNotEqual(ReadBook.book?.name)\n        tvTitle?.setTextIfNotEqual(textPage.title)\n        val readProgress = readProgress\n        tvTotalProgress?.setTextIfNotEqual(readProgress)\n        tvTotalProgress1?.setTextIfNotEqual(\"${chapterIndex.plus(1)}/${chapterSize}\")\n        if (textChapter.isCompleted) {\n            tvPageAndTotal?.setTextIfNotEqual(\"${index.plus(1)}/$pageSize  $readProgress\")\n            tvPage?.setTextIfNotEqual(\"${index.plus(1)}/$pageSize\")\n        } else {\n            val pageSizeInt = pageSize\n            val pageSize = if (pageSizeInt <= 0) \"-\" else \"~$pageSizeInt\"\n            tvPageAndTotal?.setTextIfNotEqual(\"${index.plus(1)}/$pageSize  $readProgress\")\n            tvPage?.setTextIfNotEqual(\"${index.plus(1)}/$pageSize\")\n        }\n    }\n\n    fun setAutoPager(autoPager: AutoPager?) {\n        binding.contentTextView.setAutoPager(autoPager)\n    }\n\n    fun submitRenderTask() {\n        binding.contentTextView.submitRenderTask()\n    }\n\n    fun setIsScroll(value: Boolean) {\n        isScroll = value\n        binding.contentTextView.setIsScroll(value)\n    }\n\n    /**\n     * 滚动事件\n     */\n    fun scroll(offset: Int) {\n        binding.contentTextView.scroll(offset)\n    }\n\n    /**\n     * 更新是否开启选择功能\n     */\n    fun upSelectAble(selectAble: Boolean) {\n        binding.contentTextView.selectAble = selectAble\n    }\n\n    /**\n     * 优先处理页面内单击\n     * @return true:已处理, false:未处理\n     */\n    fun onClick(x: Float, y: Float): Boolean {\n        return binding.contentTextView.click(x, y - headerHeight)\n    }\n\n    /**\n     * 长按事件\n     */\n    fun longPress(\n        x: Float, y: Float,\n        select: (textPos: TextPos) -> Unit,\n    ) {\n        return binding.contentTextView.longPress(x, y - headerHeight, select)\n    }\n\n    /**\n     * 选择文本\n     */\n    fun selectText(\n        x: Float, y: Float,\n        select: (textPos: TextPos) -> Unit,\n    ) {\n        return binding.contentTextView.selectText(x, y - headerHeight, select)\n    }\n\n    fun getCurVisiblePage(): TextPage {\n        return binding.contentTextView.getCurVisiblePage()\n    }\n\n    fun getReadAloudPos(): Pair<Int, TextLine>? {\n        return binding.contentTextView.getReadAloudPos()\n    }\n\n    fun markAsMainView() {\n        isMainView = true\n        binding.contentTextView.isMainView = true\n    }\n\n    fun selectStartMove(x: Float, y: Float) {\n        binding.contentTextView.selectStartMove(x, y - headerHeight)\n    }\n\n    fun selectStartMoveIndex(\n        relativePagePos: Int,\n        lineIndex: Int,\n        charIndex: Int\n    ) {\n        binding.contentTextView.selectStartMoveIndex(relativePagePos, lineIndex, charIndex)\n    }\n\n    fun selectStartMoveIndex(textPos: TextPos) {\n        binding.contentTextView.selectStartMoveIndex(textPos)\n    }\n\n    fun selectEndMove(x: Float, y: Float) {\n        binding.contentTextView.selectEndMove(x, y - headerHeight)\n    }\n\n    fun selectEndMoveIndex(\n        relativePagePos: Int,\n        lineIndex: Int,\n        charIndex: Int\n    ) {\n        binding.contentTextView.selectEndMoveIndex(relativePagePos, lineIndex, charIndex)\n    }\n\n    fun selectEndMoveIndex(textPos: TextPos) {\n        binding.contentTextView.selectEndMoveIndex(textPos)\n    }\n\n    fun getReverseStartCursor(): Boolean {\n        return binding.contentTextView.reverseStartCursor\n    }\n\n    fun getReverseEndCursor(): Boolean {\n        return binding.contentTextView.reverseEndCursor\n    }\n\n    fun isLongScreenShot(): Boolean {\n        return binding.contentTextView.longScreenshot\n    }\n\n    fun resetReverseCursor() {\n        binding.contentTextView.resetReverseCursor()\n    }\n\n    fun cancelSelect(clearSearchResult: Boolean = false) {\n        binding.contentTextView.cancelSelect(clearSearchResult)\n    }\n\n    fun createBookmark(): Bookmark? {\n        return binding.contentTextView.createBookmark()\n    }\n\n    fun relativePage(relativePagePos: Int): TextPage {\n        return binding.contentTextView.relativePage(relativePagePos)\n    }\n\n    val textPage get() = binding.contentTextView.textPage\n\n    val selectedText: String get() = binding.contentTextView.getSelectedText()\n\n    val selectStartPos get() = binding.contentTextView.selectStart\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/book/read/page/ReadView.kt",
    "content": "package io.legado.app.ui.book.read.page\n\nimport android.annotation.SuppressLint\nimport android.content.Context\nimport android.graphics.Canvas\nimport android.graphics.RectF\nimport android.os.Build\nimport android.util.AttributeSet\nimport android.view.MotionEvent\nimport android.view.ViewConfiguration\nimport android.view.WindowInsets\nimport android.widget.FrameLayout\nimport io.legado.app.R\nimport io.legado.app.constant.PageAnim\nimport io.legado.app.data.entities.BookProgress\nimport io.legado.app.help.config.AppConfig\nimport io.legado.app.help.config.ReadBookConfig\nimport io.legado.app.model.ReadAloud\nimport io.legado.app.model.ReadBook\nimport io.legado.app.service.BaseReadAloudService\nimport io.legado.app.ui.book.read.ContentEditDialog\nimport io.legado.app.ui.book.read.page.api.DataSource\nimport io.legado.app.ui.book.read.page.delegate.CoverPageDelegate\nimport io.legado.app.ui.book.read.page.delegate.HorizontalPageDelegate\nimport io.legado.app.ui.book.read.page.delegate.NoAnimPageDelegate\nimport io.legado.app.ui.book.read.page.delegate.PageDelegate\nimport io.legado.app.ui.book.read.page.delegate.ScrollPageDelegate\nimport io.legado.app.ui.book.read.page.delegate.SimulationPageDelegate\nimport io.legado.app.ui.book.read.page.delegate.SlidePageDelegate\nimport io.legado.app.ui.book.read.page.entities.PageDirection\nimport io.legado.app.ui.book.read.page.entities.TextChapter\nimport io.legado.app.ui.book.read.page.entities.TextLine\nimport io.legado.app.ui.book.read.page.entities.TextPage\nimport io.legado.app.ui.book.read.page.entities.TextPos\nimport io.legado.app.ui.book.read.page.entities.column.TextColumn\nimport io.legado.app.ui.book.read.page.provider.ChapterProvider\nimport io.legado.app.ui.book.read.page.provider.LayoutProgressListener\nimport io.legado.app.ui.book.read.page.provider.TextPageFactory\nimport io.legado.app.utils.activity\nimport io.legado.app.utils.invisible\nimport io.legado.app.utils.longToastOnUi\nimport io.legado.app.utils.showDialogFragment\nimport io.legado.app.utils.throttle\nimport java.text.BreakIterator\nimport java.util.Locale\nimport kotlin.math.abs\n\n/**\n * 阅读视图\n */\nclass ReadView(context: Context, attrs: AttributeSet) :\n    FrameLayout(context, attrs),\n    DataSource, LayoutProgressListener {\n\n    val callBack: CallBack get() = activity as CallBack\n    var pageFactory: TextPageFactory = TextPageFactory(this)\n    var pageDelegate: PageDelegate? = null\n        private set(value) {\n            field?.onDestroy()\n            field = null\n            field = value\n            upContent()\n        }\n    override var isScroll = false\n    val prevPage by lazy { PageView(context) }\n    val curPage by lazy { PageView(context) }\n    val nextPage by lazy { PageView(context) }\n    val defaultAnimationSpeed = 300\n    private var pressDown = false\n    private var isMove = false\n\n    //起始点\n    var startX: Float = 0f\n    var startY: Float = 0f\n\n    //上一个触碰点\n    var lastX: Float = 0f\n    var lastY: Float = 0f\n\n    //触碰点\n    var touchX: Float = 0f\n    var touchY: Float = 0f\n\n    //是否停止动画动作\n    var isAbortAnim = false\n\n    //长按\n    private var longPressed = false\n    private val longPressTimeout = 600L\n    private val longPressRunnable = Runnable {\n        longPressed = true\n        onLongPress()\n    }\n    var isTextSelected = false\n    private var pressOnTextSelected = false\n    private val initialTextPos = TextPos(0, 0, 0)\n\n    private val slopSquare by lazy { ViewConfiguration.get(context).scaledTouchSlop }\n    private var pageSlopSquare: Int = slopSquare\n    var pageSlopSquare2: Int = pageSlopSquare * pageSlopSquare\n    private val tlRect = RectF()\n    private val tcRect = RectF()\n    private val trRect = RectF()\n    private val mlRect = RectF()\n    private val mcRect = RectF()\n    private val mrRect = RectF()\n    private val blRect = RectF()\n    private val bcRect = RectF()\n    private val brRect = RectF()\n    private val boundary by lazy { BreakIterator.getWordInstance(Locale.getDefault()) }\n    private val upProgressThrottle = throttle(200) { post { upProgress() } }\n    val autoPager = AutoPager(this)\n    val isAutoPage get() = autoPager.isRunning\n\n    init {\n        addView(nextPage)\n        addView(curPage)\n        addView(prevPage)\n        prevPage.invisible()\n        nextPage.invisible()\n        curPage.markAsMainView()\n        if (!isInEditMode) {\n            upBg()\n            setWillNotDraw(false)\n            upPageAnim()\n            upPageSlopSquare()\n        }\n    }\n\n    private fun setRect9x() {\n        tlRect.set(0f, 0f, width * 0.33f, height * 0.33f)\n        tcRect.set(width * 0.33f, 0f, width * 0.66f, height * 0.33f)\n        trRect.set(width * 0.36f, 0f, width.toFloat(), height * 0.33f)\n        mlRect.set(0f, height * 0.33f, width * 0.33f, height * 0.66f)\n        mcRect.set(width * 0.33f, height * 0.33f, width * 0.66f, height * 0.66f)\n        mrRect.set(width * 0.66f, height * 0.33f, width.toFloat(), height * 0.66f)\n        blRect.set(0f, height * 0.66f, width * 0.33f, height.toFloat())\n        bcRect.set(width * 0.33f, height * 0.66f, width * 0.66f, height.toFloat())\n        brRect.set(width * 0.66f, height * 0.66f, width.toFloat(), height.toFloat())\n    }\n\n    override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {\n        super.onSizeChanged(w, h, oldw, oldh)\n        setRect9x()\n        prevPage.x = -w.toFloat()\n        pageDelegate?.setViewSize(w, h)\n        if (w > 0 && h > 0) {\n            upBg()\n            callBack.upSystemUiVisibility()\n        }\n    }\n\n    override fun dispatchDraw(canvas: Canvas) {\n        super.dispatchDraw(canvas)\n        pageDelegate?.onDraw(canvas)\n        autoPager.onDraw(canvas)\n    }\n\n    override fun computeScroll() {\n        pageDelegate?.computeScroll()\n        autoPager.computeOffset()\n    }\n\n    override fun onInterceptTouchEvent(ev: MotionEvent?): Boolean {\n        return true\n    }\n\n    /**\n     * 触摸事件\n     */\n    @SuppressLint(\"ClickableViewAccessibility\")\n    override fun onTouchEvent(event: MotionEvent): Boolean {\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {\n            val insets = this.rootWindowInsets.getInsetsIgnoringVisibility(\n                WindowInsets.Type.mandatorySystemGestures()\n            )\n            val height = activity?.windowManager?.currentWindowMetrics?.bounds?.height()\n            if (height != null) {\n                if (event.y > height.minus(insets.bottom)\n                    && event.action != MotionEvent.ACTION_UP\n                    && event.action != MotionEvent.ACTION_CANCEL\n                ) {\n                    return true\n                }\n            }\n        }\n\n        //在多点触控时，事件不走ACTION_DOWN分支而产生的特殊事件处理\n        if (event.actionMasked == MotionEvent.ACTION_POINTER_DOWN || event.actionMasked == MotionEvent.ACTION_POINTER_UP) {\n            pageDelegate?.onTouch(event)\n        }\n        when (event.action) {\n            MotionEvent.ACTION_DOWN -> {\n                callBack.screenOffTimerStart()\n                if (isTextSelected) {\n                    curPage.cancelSelect()\n                    isTextSelected = false\n                    pressOnTextSelected = true\n                } else {\n                    pressOnTextSelected = false\n                }\n                longPressed = false\n                postDelayed(longPressRunnable, longPressTimeout)\n                pressDown = true\n                isMove = false\n                pageDelegate?.onTouch(event)\n                pageDelegate?.onDown()\n                setStartPoint(event.x, event.y, false)\n            }\n\n            MotionEvent.ACTION_MOVE -> {\n                if (!pressDown) return true\n                val absX = abs(startX - event.x)\n                val absY = abs(startY - event.y)\n                if (!isMove) {\n                    isMove = absX > slopSquare || absY > slopSquare\n                }\n                if (isMove) {\n                    longPressed = false\n                    removeCallbacks(longPressRunnable)\n                    if (isTextSelected) {\n                        selectText(event.x, event.y)\n                    } else {\n                        pageDelegate?.onTouch(event)\n                    }\n                }\n            }\n\n            MotionEvent.ACTION_UP -> {\n                callBack.screenOffTimerStart()\n                removeCallbacks(longPressRunnable)\n                if (!pressDown) return true\n                pressDown = false\n                if (!pageDelegate!!.isMoved && !isMove) {\n                    if (!longPressed && !pressOnTextSelected) {\n                        if (!curPage.onClick(startX, startY)) {\n                            onSingleTapUp()\n                        }\n                        return true\n                    }\n                }\n                if (isTextSelected) {\n                    callBack.showTextActionMenu()\n                } else if (pageDelegate!!.isMoved) {\n                    pageDelegate?.onTouch(event)\n                }\n                pressOnTextSelected = false\n            }\n\n            MotionEvent.ACTION_CANCEL -> {\n                removeCallbacks(longPressRunnable)\n                if (!pressDown) return true\n                pressDown = false\n                if (isTextSelected) {\n                    callBack.showTextActionMenu()\n                } else if (pageDelegate!!.isMoved) {\n                    pageDelegate?.onTouch(event)\n                }\n                pressOnTextSelected = false\n                autoPager.resume()\n            }\n        }\n        return true\n    }\n\n    fun cancelSelect(clearSearchResult: Boolean = false) {\n        if (isTextSelected) {\n            curPage.cancelSelect(clearSearchResult)\n            isTextSelected = false\n        }\n    }\n\n    /**\n     * 更新状态栏\n     */\n    fun upStatusBar() {\n        curPage.upStatusBar()\n        prevPage.upStatusBar()\n        nextPage.upStatusBar()\n    }\n\n    /**\n     * 保存开始位置\n     */\n    fun setStartPoint(x: Float, y: Float, invalidate: Boolean = true) {\n        startX = x\n        startY = y\n        lastX = x\n        lastY = y\n        touchX = x\n        touchY = y\n\n        if (invalidate) {\n            invalidate()\n        }\n    }\n\n    /**\n     * 保存当前位置\n     */\n    fun setTouchPoint(x: Float, y: Float, invalidate: Boolean = true) {\n        lastX = touchX\n        lastY = touchY\n        touchX = x\n        touchY = y\n        if (invalidate) {\n            invalidate()\n        }\n        pageDelegate?.onScroll()\n        val offset = touchY - lastY\n        touchY -= offset - offset.toInt()\n    }\n\n    /**\n     * 长按选择\n     */\n    private fun onLongPress() {\n        kotlin.runCatching {\n            curPage.longPress(startX, startY) { textPos: TextPos ->\n                isTextSelected = true\n                pressOnTextSelected = true\n                initialTextPos.upData(textPos)\n                val startPos = textPos.copy()\n                val endPos = textPos.copy()\n                val page = curPage.relativePage(textPos.relativePagePos)\n                val stringBuilder = StringBuilder()\n                var cIndex = textPos.columnIndex\n                var lineStart = textPos.lineIndex\n                var lineEnd = textPos.lineIndex\n                for (index in textPos.lineIndex - 1 downTo 0) {\n                    val textLine = page.getLine(index)\n                    if (textLine.isParagraphEnd) {\n                        break\n                    } else {\n                        stringBuilder.insert(0, textLine.text)\n                        lineStart -= 1\n                        cIndex += textLine.charSize\n                    }\n                }\n                for (index in textPos.lineIndex until page.lineSize) {\n                    val textLine = page.getLine(index)\n                    stringBuilder.append(textLine.text)\n                    lineEnd += 1\n                    if (textLine.isParagraphEnd) {\n                        break\n                    }\n                }\n                var start: Int\n                var end: Int\n                boundary.setText(stringBuilder.toString())\n                start = boundary.first()\n                end = boundary.next()\n                while (end != BreakIterator.DONE) {\n                    if (cIndex in start until end) {\n                        break\n                    }\n                    start = end\n                    end = boundary.next()\n                }\n                kotlin.run {\n                    var ci = 0\n                    for (index in lineStart..lineEnd) {\n                        val textLine = page.getLine(index)\n                        for (j in textLine.columns.indices) {\n                            if (ci == start) {\n                                startPos.lineIndex = index\n                                startPos.columnIndex = j\n                            } else if (ci == end - 1) {\n                                endPos.lineIndex = index\n                                endPos.columnIndex = j\n                                return@run\n                            }\n                            val column = textLine.getColumn(j)\n                            if (column is TextColumn) {\n                                ci += column.charData.length\n                            } else {\n                                ci++\n                            }\n                        }\n                    }\n                }\n                curPage.selectStartMoveIndex(startPos)\n                curPage.selectEndMoveIndex(endPos)\n            }\n        }\n    }\n\n    /**\n     * 单击\n     */\n    private fun onSingleTapUp() {\n        when {\n            isTextSelected -> Unit\n            mcRect.contains(startX, startY) -> if (!isAbortAnim) {\n                click(AppConfig.clickActionMC)\n            }\n\n            bcRect.contains(startX, startY) -> {\n                click(AppConfig.clickActionBC)\n            }\n\n            blRect.contains(startX, startY) -> {\n                click(AppConfig.clickActionBL)\n            }\n\n            brRect.contains(startX, startY) -> {\n                click(AppConfig.clickActionBR)\n            }\n\n            mlRect.contains(startX, startY) -> {\n                click(AppConfig.clickActionML)\n            }\n\n            mrRect.contains(startX, startY) -> {\n                click(AppConfig.clickActionMR)\n            }\n\n            tlRect.contains(startX, startY) -> {\n                click(AppConfig.clickActionTL)\n            }\n\n            tcRect.contains(startX, startY) -> {\n                click(AppConfig.clickActionTC)\n            }\n\n            trRect.contains(startX, startY) -> {\n                click(AppConfig.clickActionTR)\n            }\n        }\n    }\n\n    /**\n     * 点击\n     */\n    private fun click(action: Int) {\n        when (action) {\n            0 -> {\n                pageDelegate?.dismissSnackBar()\n                callBack.showActionMenu()\n            }\n\n            1 -> pageDelegate?.nextPageByAnim(defaultAnimationSpeed)\n            2 -> pageDelegate?.prevPageByAnim(defaultAnimationSpeed)\n            3 -> ReadBook.moveToNextChapter(true)\n            4 -> ReadBook.moveToPrevChapter(upContent = true, toLast = false)\n            5 -> ReadAloud.prevParagraph(context)\n            6 -> ReadAloud.nextParagraph(context)\n            7 -> callBack.addBookmark()\n            8 -> activity?.showDialogFragment(ContentEditDialog())\n            9 -> callBack.changeReplaceRuleState()\n            10 -> callBack.openChapterList()\n            11 -> callBack.openSearchActivity(null)\n            12 -> ReadBook.syncProgress(\n                { progress -> callBack.sureNewProgress(progress) },\n                { context.longToastOnUi(context.getString(R.string.upload_book_success)) },\n                { context.longToastOnUi(context.getString(R.string.sync_book_progress_success)) })\n\n            13 -> {\n                if (BaseReadAloudService.isPlay()) {\n                    ReadAloud.pause(context)\n                } else {\n                    ReadAloud.resume(context)\n                }\n            }\n        }\n    }\n\n    /**\n     * 选择文本\n     */\n    private fun selectText(x: Float, y: Float) {\n        curPage.selectText(x, y) { textPos ->\n            val compare = initialTextPos.compare(textPos)\n            when {\n                compare > 0 -> {\n                    curPage.selectStartMoveIndex(textPos)\n                    curPage.selectEndMoveIndex(\n                        initialTextPos.relativePagePos,\n                        initialTextPos.lineIndex,\n                        initialTextPos.columnIndex - 1\n                    )\n                }\n\n                else -> {\n                    curPage.selectStartMoveIndex(initialTextPos)\n                    curPage.selectEndMoveIndex(textPos)\n                }\n            }\n        }\n    }\n\n    /**\n     * 销毁事件\n     */\n    fun onDestroy() {\n        pageDelegate?.onDestroy()\n        curPage.cancelSelect()\n        invalidateTextPage()\n    }\n\n    /**\n     * 翻页动画完成后事件\n     * @param direction 翻页方向\n     */\n    fun fillPage(direction: PageDirection): Boolean {\n        return when (direction) {\n            PageDirection.PREV -> {\n                pageFactory.moveToPrev(true)\n            }\n\n            PageDirection.NEXT -> {\n                pageFactory.moveToNext(true)\n            }\n\n            else -> false\n        }\n    }\n\n    /**\n     * 更新翻页动画\n     */\n    fun upPageAnim(upRecorder: Boolean = false) {\n        isScroll = ReadBook.pageAnim() == 3\n        ChapterProvider.upLayout()\n        when (ReadBook.pageAnim()) {\n            PageAnim.coverPageAnim -> if (pageDelegate !is CoverPageDelegate) {\n                pageDelegate = CoverPageDelegate(this)\n            }\n\n            PageAnim.slidePageAnim -> if (pageDelegate !is SlidePageDelegate) {\n                pageDelegate = SlidePageDelegate(this)\n            }\n\n            PageAnim.simulationPageAnim -> if (pageDelegate !is SimulationPageDelegate) {\n                pageDelegate = SimulationPageDelegate(this)\n            }\n\n            PageAnim.scrollPageAnim -> if (pageDelegate !is ScrollPageDelegate) {\n                pageDelegate = ScrollPageDelegate(this)\n            }\n\n            else -> if (pageDelegate !is NoAnimPageDelegate) {\n                pageDelegate = NoAnimPageDelegate(this)\n            }\n        }\n        (pageDelegate as? ScrollPageDelegate)?.noAnim = AppConfig.noAnimScrollPage\n        if (upRecorder) {\n            (pageDelegate as? HorizontalPageDelegate)?.upRecorder()\n            autoPager.upRecorder()\n        }\n        pageDelegate?.setViewSize(width, height)\n        if (isScroll) {\n            curPage.setAutoPager(autoPager)\n        } else {\n            curPage.setAutoPager(null)\n        }\n        curPage.setIsScroll(isScroll)\n    }\n\n    /**\n     * 更新阅读内容\n     * @param relativePosition 相对位置 -1 上一页 0 当前页 1 下一页\n     * @param resetPageOffset 滚动阅读是是否重置位置\n     */\n    override fun upContent(relativePosition: Int, resetPageOffset: Boolean) {\n        post {\n            curPage.setContentDescription(pageFactory.curPage.text)\n        }\n        if (isScroll && !isAutoPage) {\n            if (relativePosition == 0) {\n                curPage.setContent(pageFactory.curPage, resetPageOffset)\n            } else {\n                curPage.invalidateContentView()\n            }\n        } else {\n            when (relativePosition) {\n                -1 -> prevPage.setContent(pageFactory.prevPage)\n                1 -> nextPage.setContent(pageFactory.nextPage)\n                else -> {\n                    curPage.setContent(pageFactory.curPage, resetPageOffset)\n                    nextPage.setContent(pageFactory.nextPage)\n                    prevPage.setContent(pageFactory.prevPage)\n                }\n            }\n        }\n        callBack.screenOffTimerStart()\n    }\n\n    private fun upProgress() {\n        curPage.setProgress(pageFactory.curPage)\n    }\n\n    /**\n     * 更新滑动距离\n     */\n    fun upPageSlopSquare() {\n        val pageTouchSlop = AppConfig.pageTouchSlop\n        this.pageSlopSquare = if (pageTouchSlop == 0) slopSquare else pageTouchSlop\n        pageSlopSquare2 = this.pageSlopSquare * this.pageSlopSquare\n    }\n\n    /**\n     * 更新样式\n     */\n    fun upStyle() {\n        ChapterProvider.upStyle()\n        curPage.upStyle()\n        prevPage.upStyle()\n        nextPage.upStyle()\n    }\n\n    /**\n     * 更新背景\n     */\n    fun upBg() {\n        ReadBookConfig.upBg(width, height)\n        curPage.upBg()\n        prevPage.upBg()\n        nextPage.upBg()\n    }\n\n    /**\n     * 更新背景透明度\n     */\n    fun upBgAlpha() {\n        curPage.upBgAlpha()\n        prevPage.upBgAlpha()\n        nextPage.upBgAlpha()\n    }\n\n    /**\n     * 更新时间信息\n     */\n    fun upTime() {\n        curPage.upTime()\n        prevPage.upTime()\n        nextPage.upTime()\n    }\n\n    /**\n     * 更新电量信息\n     */\n    fun upBattery(battery: Int) {\n        curPage.upBattery(battery)\n        prevPage.upBattery(battery)\n        nextPage.upBattery(battery)\n    }\n\n    /**\n     * 从选择位置开始朗读\n     */\n    suspend fun aloudStartSelect() {\n        val selectStartPos = curPage.selectStartPos\n        var pagePos = selectStartPos.relativePagePos\n        val line = selectStartPos.lineIndex\n        val column = selectStartPos.columnIndex\n        while (pagePos > 0) {\n            if (!ReadBook.moveToNextPage()) {\n                ReadBook.moveToNextChapterAwait(false)\n            }\n            pagePos--\n        }\n        val startPos = curPage.textPage.getPosByLineColumn(line, column)\n        ReadBook.readAloud(startPos = startPos)\n    }\n\n    /**\n     * @return 选择的文本\n     */\n    fun getSelectText(): String {\n        return curPage.selectedText\n    }\n\n    fun getCurVisiblePage(): TextPage {\n        return curPage.getCurVisiblePage()\n    }\n\n    fun getReadAloudPos(): Pair<Int, TextLine>? {\n        return curPage.getReadAloudPos()\n    }\n\n    fun invalidateTextPage() {\n        if (!AppConfig.optimizeRender) {\n            return\n        }\n        pageFactory.run {\n            prevPage.invalidateAll()\n            curPage.invalidateAll()\n            nextPage.invalidateAll()\n            nextPlusPage.invalidateAll()\n        }\n    }\n\n    fun onScrollAnimStart() {\n        autoPager.pause()\n    }\n\n    fun onScrollAnimStop() {\n        autoPager.resume()\n    }\n\n    fun onPageChange() {\n        autoPager.reset()\n        submitRenderTask()\n    }\n\n    fun submitRenderTask() {\n        if (!AppConfig.optimizeRender) {\n            return\n        }\n        curPage.submitRenderTask()\n    }\n\n    fun isLongScreenShot(): Boolean {\n        return curPage.isLongScreenShot()\n    }\n\n    override fun onLayoutPageCompleted(index: Int, page: TextPage) {\n        upProgressThrottle.invoke()\n    }\n\n    override val currentChapter: TextChapter?\n        get() {\n            return if (callBack.isInitFinish) ReadBook.textChapter(0) else null\n        }\n\n    override val nextChapter: TextChapter?\n        get() {\n            return if (callBack.isInitFinish) ReadBook.textChapter(1) else null\n        }\n\n    override val prevChapter: TextChapter?\n        get() {\n            return if (callBack.isInitFinish) ReadBook.textChapter(-1) else null\n        }\n\n    override fun hasNextChapter(): Boolean {\n        return ReadBook.durChapterIndex < ReadBook.simulatedChapterSize - 1\n    }\n\n    override fun hasPrevChapter(): Boolean {\n        return ReadBook.durChapterIndex > 0\n    }\n\n    interface CallBack {\n        val isInitFinish: Boolean\n        fun showActionMenu()\n        fun screenOffTimerStart()\n        fun showTextActionMenu()\n        fun autoPageStop()\n        fun openChapterList()\n        fun addBookmark()\n        fun changeReplaceRuleState()\n        fun openSearchActivity(searchWord: String?)\n        fun upSystemUiVisibility()\n        fun sureNewProgress(progress: BookProgress)\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/book/read/page/api/DataSource.kt",
    "content": "package io.legado.app.ui.book.read.page.api\n\nimport io.legado.app.model.ReadBook\nimport io.legado.app.ui.book.read.page.entities.TextChapter\n\ninterface DataSource {\n\n    val pageIndex: Int get() = ReadBook.durPageIndex\n\n    val currentChapter: TextChapter?\n\n    val nextChapter: TextChapter?\n\n    val prevChapter: TextChapter?\n\n    val isScroll: Boolean\n\n    fun hasNextChapter(): Boolean\n\n    fun hasPrevChapter(): Boolean\n\n    fun upContent(relativePosition: Int = 0, resetPageOffset: Boolean = true)\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/book/read/page/api/PageFactory.kt",
    "content": "package io.legado.app.ui.book.read.page.api\n\nabstract class PageFactory<DATA>(protected val dataSource: DataSource) {\n\n    abstract fun moveToFirst()\n\n    abstract fun moveToLast()\n\n    abstract fun moveToNext(upContent: Boolean): Boolean\n\n    abstract fun moveToPrev(upContent: Boolean): Boolean\n\n    abstract val nextPage: DATA\n\n    abstract val prevPage: DATA\n\n    abstract val curPage: DATA\n\n    abstract val nextPlusPage: DATA\n\n    abstract fun hasNext(): Boolean\n\n    abstract fun hasPrev(): Boolean\n\n    abstract fun hasNextPlus(): Boolean\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/book/read/page/delegate/CoverPageDelegate.kt",
    "content": "package io.legado.app.ui.book.read.page.delegate\n\nimport android.graphics.Canvas\nimport android.graphics.drawable.GradientDrawable\nimport androidx.core.graphics.withClip\nimport androidx.core.graphics.withTranslation\nimport io.legado.app.ui.book.read.page.ReadView\nimport io.legado.app.ui.book.read.page.entities.PageDirection\nimport io.legado.app.utils.screenshot\n\nclass CoverPageDelegate(readView: ReadView) : HorizontalPageDelegate(readView) {\n    private val shadowDrawableR: GradientDrawable\n\n    init {\n        val shadowColors = intArrayOf(0x66111111, 0x00000000)\n        shadowDrawableR = GradientDrawable(\n            GradientDrawable.Orientation.LEFT_RIGHT, shadowColors\n        )\n        shadowDrawableR.gradientType = GradientDrawable.LINEAR_GRADIENT\n    }\n\n    override fun onDraw(canvas: Canvas) {\n        if (!isRunning) return\n        val offsetX = touchX - startX\n\n        if ((mDirection == PageDirection.NEXT && offsetX > 0)\n            || (mDirection == PageDirection.PREV && offsetX < 0)\n        ) {\n            return\n        }\n\n        val distanceX = if (offsetX > 0) offsetX - viewWidth else offsetX + viewWidth\n        if (mDirection == PageDirection.PREV) {\n            if (offsetX <= viewWidth) {\n                canvas.withTranslation(distanceX) {\n                    prevRecorder.draw(canvas)\n                }\n                addShadow(distanceX, canvas)\n            } else {\n                prevRecorder.draw(canvas)\n            }\n        } else if (mDirection == PageDirection.NEXT) {\n            val width = nextRecorder.width.toFloat()\n            val height = nextRecorder.height.toFloat()\n            canvas.withClip(width + offsetX, 0f, width, height) {\n                nextRecorder.draw(this)\n            }\n            canvas.withTranslation(distanceX - viewWidth) {\n                curRecorder.draw(this)\n            }\n            addShadow(distanceX, canvas)\n        }\n    }\n\n    override fun setBitmap() {\n        when (mDirection) {\n            PageDirection.PREV -> {\n                prevPage.screenshot(prevRecorder)\n            }\n\n            PageDirection.NEXT -> {\n                nextPage.screenshot(nextRecorder)\n                curPage.screenshot(curRecorder)\n            }\n\n            else -> Unit\n        }\n    }\n\n    private fun addShadow(left: Float, canvas: Canvas) {\n        if (left == 0f) return\n        val dx = if (left < 0) {\n            left + viewWidth\n        } else {\n            left\n        }\n        canvas.withTranslation(dx) {\n            shadowDrawableR.draw(canvas)\n        }\n    }\n\n    override fun setViewSize(width: Int, height: Int) {\n        super.setViewSize(width, height)\n        shadowDrawableR.setBounds(0, 0, 30, viewHeight)\n    }\n\n    override fun onAnimStop() {\n        if (!isCancel) {\n            readView.fillPage(mDirection)\n        }\n    }\n\n    override fun onAnimStart(animationSpeed: Int) {\n        val distanceX: Float\n        when (mDirection) {\n            PageDirection.NEXT -> distanceX =\n                if (isCancel) {\n                    var dis = viewWidth - startX + touchX\n                    if (dis > viewWidth) {\n                        dis = viewWidth.toFloat()\n                    }\n                    viewWidth - dis\n                } else {\n                    -(touchX + (viewWidth - startX))\n                }\n\n            else -> distanceX =\n                if (isCancel) {\n                    -(touchX - startX)\n                } else {\n                    viewWidth - (touchX - startX)\n                }\n        }\n        startScroll(touchX.toInt(), 0, distanceX.toInt(), 0, animationSpeed)\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/book/read/page/delegate/HorizontalPageDelegate.kt",
    "content": "package io.legado.app.ui.book.read.page.delegate\n\nimport android.view.MotionEvent\nimport io.legado.app.ui.book.read.page.ReadView\nimport io.legado.app.ui.book.read.page.entities.PageDirection\nimport io.legado.app.utils.canvasrecorder.CanvasRecorderFactory\nimport io.legado.app.utils.screenshot\n\nabstract class HorizontalPageDelegate(readView: ReadView) : PageDelegate(readView) {\n\n    protected var curRecorder = CanvasRecorderFactory.create()\n    protected var prevRecorder = CanvasRecorderFactory.create()\n    protected var nextRecorder = CanvasRecorderFactory.create()\n    private val slopSquare get() = readView.pageSlopSquare2\n\n    override fun setDirection(direction: PageDirection) {\n        super.setDirection(direction)\n        setBitmap()\n    }\n\n    open fun setBitmap() {\n        when (mDirection) {\n            PageDirection.PREV -> {\n                prevPage.screenshot(prevRecorder)\n                curPage.screenshot(curRecorder)\n            }\n\n            PageDirection.NEXT -> {\n                nextPage.screenshot(nextRecorder)\n                curPage.screenshot(curRecorder)\n            }\n\n            else -> Unit\n        }\n    }\n\n    fun upRecorder() {\n        curRecorder.recycle()\n        prevRecorder.recycle()\n        nextRecorder.recycle()\n        curRecorder = CanvasRecorderFactory.create()\n        prevRecorder = CanvasRecorderFactory.create()\n        nextRecorder = CanvasRecorderFactory.create()\n    }\n\n    override fun onTouch(event: MotionEvent) {\n        when (event.action) {\n            MotionEvent.ACTION_DOWN -> {\n                abortAnim()\n            }\n\n            MotionEvent.ACTION_MOVE -> {\n                onScroll(event)\n            }\n\n            MotionEvent.ACTION_CANCEL, MotionEvent.ACTION_UP -> {\n                onAnimStart(readView.defaultAnimationSpeed)\n            }\n        }\n    }\n\n    private fun onScroll(event: MotionEvent) {\n        val action: Int = event.action\n        val pointerUp =\n            action and MotionEvent.ACTION_MASK == MotionEvent.ACTION_POINTER_UP\n        val skipIndex = if (pointerUp) event.actionIndex else -1\n        // Determine focal point\n        var sumX = 0f\n        var sumY = 0f\n        val count: Int = event.pointerCount\n        for (i in 0 until count) {\n            if (skipIndex == i) continue\n            sumX += event.getX(i)\n            sumY += event.getY(i)\n        }\n        val div = if (pointerUp) count - 1 else count\n        val focusX = sumX / div\n        val focusY = sumY / div\n        //判断是否移动了\n        if (!isMoved) {\n            val deltaX = (focusX - startX).toInt()\n            val deltaY = (focusY - startY).toInt()\n            val distance = deltaX * deltaX + deltaY * deltaY\n            isMoved = distance > slopSquare\n            if (isMoved) {\n                if (sumX - startX > 0) {\n                    //如果上一页不存在\n                    if (!hasPrev()) {\n                        noNext = true\n                        return\n                    }\n                    setDirection(PageDirection.PREV)\n                } else {\n                    //如果不存在表示没有下一页了\n                    if (!hasNext()) {\n                        noNext = true\n                        return\n                    }\n                    setDirection(PageDirection.NEXT)\n                }\n                readView.setStartPoint(event.x, event.y, false)\n            }\n        }\n        if (isMoved) {\n            isCancel = if (mDirection == PageDirection.NEXT) sumX > lastX else sumX < lastX\n            isRunning = true\n            //设置触摸点\n            readView.setTouchPoint(sumX, sumY)\n        }\n    }\n\n    override fun abortAnim() {\n        isStarted = false\n        isMoved = false\n        isRunning = false\n        if (!scroller.isFinished) {\n            readView.isAbortAnim = true\n            scroller.abortAnimation()\n            if (!isCancel) {\n                readView.fillPage(mDirection)\n                readView.invalidate()\n            }\n        } else {\n            readView.isAbortAnim = false\n        }\n    }\n\n    override fun nextPageByAnim(animationSpeed: Int) {\n        abortAnim()\n        if (!hasNext()) return\n        setDirection(PageDirection.NEXT)\n        val y = when {\n            startY > viewHeight / 2 -> viewHeight.toFloat() * 0.9f\n            else -> 1f\n        }\n        readView.setStartPoint(viewWidth.toFloat() * 0.9f, y, false)\n        onAnimStart(animationSpeed)\n    }\n\n    override fun prevPageByAnim(animationSpeed: Int) {\n        abortAnim()\n        if (!hasPrev()) return\n        setDirection(PageDirection.PREV)\n        readView.setStartPoint(0f, viewHeight.toFloat(), false)\n        onAnimStart(animationSpeed)\n    }\n\n    override fun onDestroy() {\n        super.onDestroy()\n        prevRecorder.recycle()\n        curRecorder.recycle()\n        nextRecorder.recycle()\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/book/read/page/delegate/NoAnimPageDelegate.kt",
    "content": "package io.legado.app.ui.book.read.page.delegate\n\nimport android.graphics.Canvas\nimport io.legado.app.ui.book.read.page.ReadView\n\nclass NoAnimPageDelegate(readView: ReadView) : HorizontalPageDelegate(readView) {\n\n    override fun onAnimStart(animationSpeed: Int) {\n        if (!isCancel) {\n            readView.fillPage(mDirection)\n        }\n        stopScroll()\n    }\n\n    override fun setBitmap() {\n        // nothing\n    }\n\n    override fun onDraw(canvas: Canvas) {\n        // nothing\n    }\n\n    override fun onAnimStop() {\n        // nothing\n    }\n\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/book/read/page/delegate/PageDelegate.kt",
    "content": "package io.legado.app.ui.book.read.page.delegate\n\nimport android.content.Context\nimport android.graphics.Canvas\nimport android.view.MotionEvent\nimport android.view.animation.LinearInterpolator\nimport android.widget.Scroller\nimport androidx.annotation.CallSuper\nimport com.google.android.material.snackbar.Snackbar\nimport io.legado.app.R\nimport io.legado.app.ui.book.read.page.PageView\nimport io.legado.app.ui.book.read.page.ReadView\nimport io.legado.app.ui.book.read.page.entities.PageDirection\nimport kotlin.math.abs\n\nabstract class PageDelegate(protected val readView: ReadView) {\n\n    protected val context: Context = readView.context\n\n    //起始点\n    protected val startX: Float get() = readView.startX\n    protected val startY: Float get() = readView.startY\n\n    //上一个触碰点\n    protected val lastX: Float get() = readView.lastX\n    protected val lastY: Float get() = readView.lastY\n\n    //触碰点\n    protected val touchX: Float get() = readView.touchX\n    protected val touchY: Float get() = readView.touchY\n\n    protected val nextPage: PageView get() = readView.nextPage\n    protected val curPage: PageView get() = readView.curPage\n    protected val prevPage: PageView get() = readView.prevPage\n\n    protected var viewWidth: Int = readView.width\n    protected var viewHeight: Int = readView.height\n\n    protected val scroller: Scroller by lazy {\n        Scroller(readView.context, LinearInterpolator())\n    }\n\n    private val snackBar: Snackbar by lazy {\n        Snackbar.make(readView, \"\", Snackbar.LENGTH_SHORT)\n    }\n\n    var isMoved = false\n    var noNext = true\n\n    //移动方向\n    var mDirection = PageDirection.NONE\n    var isCancel = false\n    var isRunning = false\n    var isStarted = false\n\n    private var selectedOnDown = false\n\n    init {\n        curPage.resetPageOffset()\n    }\n\n    open fun fling(\n        startX: Int, startY: Int, velocityX: Int, velocityY: Int,\n        minX: Int, maxX: Int, minY: Int, maxY: Int\n    ) {\n        scroller.fling(startX, startY, velocityX, velocityY, minX, maxX, minY, maxY)\n        isRunning = true\n        isStarted = true\n        readView.invalidate()\n    }\n\n    protected fun startScroll(startX: Int, startY: Int, dx: Int, dy: Int, animationSpeed: Int) {\n        val duration = if (dx != 0) {\n            (animationSpeed * abs(dx)) / viewWidth\n        } else {\n            (animationSpeed * abs(dy)) / viewHeight\n        }\n        scroller.startScroll(startX, startY, dx, dy, duration)\n        isRunning = true\n        isStarted = true\n        readView.invalidate()\n    }\n\n    protected fun stopScroll() {\n        isStarted = false\n        readView.post {\n            isMoved = false\n            isRunning = false\n            readView.invalidate()\n        }\n    }\n\n    @CallSuper\n    open fun setViewSize(width: Int, height: Int) {\n        viewWidth = width\n        viewHeight = height\n    }\n\n    open fun computeScroll() {\n        if (scroller.computeScrollOffset()) {\n            readView.setTouchPoint(scroller.currX.toFloat(), scroller.currY.toFloat())\n        } else if (isStarted) {\n            onAnimStop()\n            stopScroll()\n        }\n    }\n\n    open fun onScroll() = Unit\n\n    abstract fun abortAnim()\n\n    abstract fun onAnimStart(animationSpeed: Int) //scroller start\n\n    abstract fun onDraw(canvas: Canvas) //绘制\n\n    abstract fun onAnimStop() //scroller finish\n\n    abstract fun nextPageByAnim(animationSpeed: Int)\n\n    abstract fun prevPageByAnim(animationSpeed: Int)\n\n    open fun keyTurnPage(direction: PageDirection) {\n        if (isRunning) return\n        when (direction) {\n            PageDirection.NEXT -> nextPageByAnim(100)\n            PageDirection.PREV -> prevPageByAnim(100)\n            else -> return\n        }\n    }\n\n    @CallSuper\n    open fun setDirection(direction: PageDirection) {\n        mDirection = direction\n    }\n\n    /**\n     * 触摸事件处理\n     */\n    abstract fun onTouch(event: MotionEvent)\n\n    /**\n     * 按下\n     */\n    fun onDown() {\n        //是否移动\n        isMoved = false\n        //是否存在下一章\n        noNext = false\n        //是否正在执行动画\n        isRunning = false\n        //取消\n        isCancel = false\n        //是下一章还是前一章\n        setDirection(PageDirection.NONE)\n    }\n\n    /**\n     * 判断是否有上一页\n     */\n    fun hasPrev(): Boolean {\n        val hasPrev = readView.pageFactory.hasPrev()\n        if (!hasPrev) {\n            if (!snackBar.isShown) {\n                snackBar.setText(R.string.no_prev_page)\n                snackBar.show()\n            }\n        }\n        return hasPrev\n    }\n\n    /**\n     * 判断是否有下一页\n     */\n    fun hasNext(): Boolean {\n        val hasNext = readView.pageFactory.hasNext()\n        if (!hasNext) {\n            readView.callBack.autoPageStop()\n            if (!snackBar.isShown) {\n                snackBar.setText(R.string.no_next_page)\n                snackBar.show()\n            }\n        }\n        return hasNext\n    }\n\n    fun dismissSnackBar() {\n        // 判断snackBar是否显示，并关闭\n        if (snackBar.isShown) {\n            snackBar.dismiss()\n        }\n    }\n\n    fun postInvalidate() {\n        if (isStarted && isRunning && this is HorizontalPageDelegate) {\n            readView.post {\n                if (isStarted && isRunning) {\n                    setBitmap()\n                    readView.invalidate()\n                }\n            }\n        }\n    }\n\n    open fun onDestroy() {\n        // run on destroy\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/book/read/page/delegate/ScrollPageDelegate.kt",
    "content": "package io.legado.app.ui.book.read.page.delegate\n\nimport android.graphics.Canvas\nimport android.view.MotionEvent\nimport android.view.VelocityTracker\nimport io.legado.app.data.entities.Book\nimport io.legado.app.help.book.isImage\nimport io.legado.app.model.ReadBook\nimport io.legado.app.ui.book.read.page.ReadView\nimport io.legado.app.ui.book.read.page.provider.ChapterProvider\n\nclass ScrollPageDelegate(readView: ReadView) : PageDelegate(readView) {\n\n    // 滑动追踪的时间\n    private val velocityDuration = 1000\n\n    //速度追踪器\n    private val mVelocity: VelocityTracker = VelocityTracker.obtain()\n    private val slopSquare get() = readView.pageSlopSquare2\n\n    var noAnim: Boolean = false\n\n    override fun onAnimStart(animationSpeed: Int) {\n        readView.onScrollAnimStart()\n        //惯性滚动\n        fling(\n            0, touchY.toInt(), 0, mVelocity.yVelocity.toInt(),\n            0, 0, -10 * viewHeight, 10 * viewHeight\n        )\n    }\n\n    override fun onAnimStop() {\n        readView.onScrollAnimStop()\n    }\n\n    override fun onTouch(event: MotionEvent) {\n        //在多点触控时，事件不走ACTION_DOWN分支而产生的特殊事件处理\n        if (event.actionMasked == MotionEvent.ACTION_POINTER_DOWN) {\n            //当多个手指同时按下的情况，将最后一个按下的手指的坐标设置为起始坐标，所以只有最后一个手指的滑动事件被处理\n            readView.setStartPoint(\n                event.getX(event.pointerCount - 1),\n                event.getY(event.pointerCount - 1),\n                false\n            )\n        } else if (event.actionMasked == MotionEvent.ACTION_POINTER_UP) {\n            //当多个手指同时按下的情况，当抬起一个手指时，起始坐标恢复为第一次按下的手指的坐标\n            readView.setStartPoint(event.x, event.y, false)\n            return\n        }\n        when (event.action) {\n            MotionEvent.ACTION_DOWN -> {\n                abortAnim()\n                mVelocity.clear()\n            }\n\n            MotionEvent.ACTION_MOVE -> {\n                onScroll(event)\n            }\n\n            MotionEvent.ACTION_CANCEL, MotionEvent.ACTION_UP -> {\n                onAnimStart(readView.defaultAnimationSpeed)\n            }\n        }\n    }\n\n    override fun onScroll() {\n        curPage.scroll((touchY - lastY).toInt())\n    }\n\n    override fun onDraw(canvas: Canvas) {\n        // nothing\n    }\n\n    private fun onScroll(event: MotionEvent) {\n        mVelocity.addMovement(event)\n        mVelocity.computeCurrentVelocity(velocityDuration)\n        //取最后添加(即最新的)一个触摸点来计算滚动位置\n        //多点触控时即最后按下的手指产生的事件点\n        val pointX = event.getX(event.pointerCount - 1)\n        val pointY = event.getY(event.pointerCount - 1)\n        if (isMoved || readView.isLongScreenShot()) {\n            readView.setTouchPoint(pointX, pointY, false)\n        }\n        if (!isMoved) {\n            val deltaX = (pointX - startX).toInt()\n            val deltaY = (pointY - startY).toInt()\n            val distance = deltaX * deltaX + deltaY * deltaY\n            isMoved = distance > slopSquare\n            if (isMoved) {\n                readView.setStartPoint(event.x, event.y, false)\n            }\n        }\n        if (isMoved) {\n            isRunning = true\n        }\n    }\n\n    override fun computeScroll() {\n        if (scroller.computeScrollOffset()) {\n            readView.setTouchPoint(scroller.currX.toFloat(), scroller.currY.toFloat(), false)\n        } else if (isStarted) {\n            onAnimStop()\n            stopScroll()\n        }\n    }\n\n    override fun onDestroy() {\n        super.onDestroy()\n        mVelocity.recycle()\n    }\n\n    override fun abortAnim() {\n        readView.onScrollAnimStop()\n        isStarted = false\n        isMoved = false\n        isRunning = false\n        if (!scroller.isFinished) {\n            readView.isAbortAnim = true\n            scroller.abortAnimation()\n        } else {\n            readView.isAbortAnim = false\n        }\n    }\n\n    override fun nextPageByAnim(animationSpeed: Int) {\n        if (readView.isAbortAnim) {\n            readView.isAbortAnim = false\n            return\n        }\n        if (noAnim) {\n            curPage.scroll(calcNextPageOffset())\n            return\n        }\n        readView.setStartPoint(0f, 0f, false)\n        startScroll(0, 0, 0, calcNextPageOffset(), animationSpeed)\n    }\n\n    override fun prevPageByAnim(animationSpeed: Int) {\n        if (readView.isAbortAnim) {\n            readView.isAbortAnim = false\n            return\n        }\n        if (noAnim) {\n            curPage.scroll(calcPrevPageOffset())\n            return\n        }\n        readView.setStartPoint(0f, 0f, false)\n        startScroll(0, 0, 0, calcPrevPageOffset(), animationSpeed)\n    }\n\n    /**\n     * 计算点击翻页保留一行的滚动距离\n     * 图片页使用可视高度作为滚动距离\n     */\n    private fun calcNextPageOffset(): Int {\n        val visibleHeight = ChapterProvider.visibleHeight\n        val book = ReadBook.book\n        if (book == null || book.isImage) {\n            return -visibleHeight\n        }\n        val visiblePage = readView.getCurVisiblePage()\n        val isTextStyle = book.getImageStyle().equals(Book.imgStyleText, true)\n        if (!isTextStyle && visiblePage.hasImageOrEmpty()) {\n            return -visibleHeight\n        }\n        val lastLineTop = visiblePage.lines.last().lineTop.toInt()\n        val offset = lastLineTop - ChapterProvider.paddingTop\n        return -offset\n    }\n\n    private fun calcPrevPageOffset(): Int {\n        val visibleHeight = ChapterProvider.visibleHeight\n        val book = ReadBook.book\n        if (book == null || book.isImage) {\n            return visibleHeight\n        }\n        val visiblePage = readView.getCurVisiblePage()\n        val isTextStyle = book.getImageStyle().equals(Book.imgStyleText, true)\n        if (!isTextStyle && visiblePage.hasImageOrEmpty()) {\n            return visibleHeight\n        }\n        val firstLineBottom = visiblePage.lines.first().lineBottom.toInt()\n        val offset = visibleHeight - (firstLineBottom - ChapterProvider.paddingTop)\n        return offset\n    }\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/book/read/page/delegate/SimulationPageDelegate.kt",
    "content": "package io.legado.app.ui.book.read.page.delegate\n\nimport android.graphics.Bitmap\nimport android.graphics.Canvas\nimport android.graphics.ColorMatrix\nimport android.graphics.ColorMatrixColorFilter\nimport android.graphics.Matrix\nimport android.graphics.Paint\nimport android.graphics.Path\nimport android.graphics.PointF\nimport android.graphics.Region\nimport android.graphics.drawable.GradientDrawable\nimport android.os.Build\nimport android.view.MotionEvent\nimport io.legado.app.help.config.ReadBookConfig\nimport io.legado.app.ui.book.read.page.ReadView\nimport io.legado.app.ui.book.read.page.entities.PageDirection\nimport io.legado.app.utils.screenshot\nimport kotlin.math.abs\nimport kotlin.math.atan2\nimport kotlin.math.cos\nimport kotlin.math.hypot\nimport kotlin.math.min\nimport kotlin.math.sin\n\n@Suppress(\"DEPRECATION\")\nclass SimulationPageDelegate(readView: ReadView) : HorizontalPageDelegate(readView) {\n    //不让x,y为0,否则在点计算时会有问题\n    private var mTouchX = 0.1f\n    private var mTouchY = 0.1f\n\n    // 拖拽点对应的页脚\n    private var mCornerX = 1\n    private var mCornerY = 1\n    private val mPath0: Path = Path()\n    private val mPath1: Path = Path()\n\n    // 贝塞尔曲线起始点\n    private val mBezierStart1 = PointF()\n\n    // 贝塞尔曲线控制点\n    private val mBezierControl1 = PointF()\n\n    // 贝塞尔曲线顶点\n    private val mBezierVertex1 = PointF()\n\n    // 贝塞尔曲线结束点\n    private var mBezierEnd1 = PointF()\n\n    // 另一条贝塞尔曲线\n    // 贝塞尔曲线起始点\n    private val mBezierStart2 = PointF()\n\n    // 贝塞尔曲线控制点\n    private val mBezierControl2 = PointF()\n\n    // 贝塞尔曲线顶点\n    private val mBezierVertex2 = PointF()\n\n    // 贝塞尔曲线结束点\n    private var mBezierEnd2 = PointF()\n\n    private var mMiddleX = 0f\n    private var mMiddleY = 0f\n    private var mDegrees = 0f\n    private var mTouchToCornerDis = 0f\n    private var mColorMatrixFilter = ColorMatrixColorFilter(\n        ColorMatrix(\n            floatArrayOf(\n                1f, 0f, 0f, 0f, 0f,\n                0f, 1f, 0f, 0f, 0f,\n                0f, 0f, 1f, 0f, 0f,\n                0f, 0f, 0f, 1f, 0f\n            )\n        )\n    )\n    private val mMatrix: Matrix = Matrix()\n    private val mMatrixArray = floatArrayOf(0f, 0f, 0f, 0f, 0f, 0f, 0f, 0f, 1f)\n\n    // 是否属于右上左下\n    private var mIsRtOrLb = false\n    private var mMaxLength = hypot(viewWidth.toDouble(), viewHeight.toDouble()).toFloat()\n\n    // 背面颜色组\n    private var mBackShadowColors: IntArray\n\n    // 前面颜色组\n    private var mFrontShadowColors: IntArray\n\n    // 有阴影的GradientDrawable\n    private var mBackShadowDrawableLR: GradientDrawable\n    private var mBackShadowDrawableRL: GradientDrawable\n    private var mFolderShadowDrawableLR: GradientDrawable\n    private var mFolderShadowDrawableRL: GradientDrawable\n\n    private var mFrontShadowDrawableHBT: GradientDrawable\n    private var mFrontShadowDrawableHTB: GradientDrawable\n    private var mFrontShadowDrawableVLR: GradientDrawable\n    private var mFrontShadowDrawableVRL: GradientDrawable\n\n    private val mPaint: Paint = Paint().apply { style = Paint.Style.FILL }\n\n    private var curBitmap: Bitmap? = null\n    private var prevBitmap: Bitmap? = null\n    private var nextBitmap: Bitmap? = null\n    private var canvas: Canvas = Canvas()\n\n    init {\n        //设置颜色数组\n        val color = intArrayOf(0x333333, -0x4fcccccd)\n        mFolderShadowDrawableRL = GradientDrawable(GradientDrawable.Orientation.RIGHT_LEFT, color)\n        mFolderShadowDrawableRL.gradientType = GradientDrawable.LINEAR_GRADIENT\n\n        mFolderShadowDrawableLR = GradientDrawable(GradientDrawable.Orientation.LEFT_RIGHT, color)\n        mFolderShadowDrawableLR.gradientType = GradientDrawable.LINEAR_GRADIENT\n\n        mBackShadowColors = intArrayOf(-0xeeeeef, 0x111111)\n        mBackShadowDrawableRL =\n            GradientDrawable(GradientDrawable.Orientation.RIGHT_LEFT, mBackShadowColors)\n        mBackShadowDrawableRL.gradientType = GradientDrawable.LINEAR_GRADIENT\n\n        mBackShadowDrawableLR =\n            GradientDrawable(GradientDrawable.Orientation.LEFT_RIGHT, mBackShadowColors)\n        mBackShadowDrawableLR.gradientType = GradientDrawable.LINEAR_GRADIENT\n\n        mFrontShadowColors = intArrayOf(-0x7feeeeef, 0x111111)\n        mFrontShadowDrawableVLR =\n            GradientDrawable(GradientDrawable.Orientation.LEFT_RIGHT, mFrontShadowColors)\n        mFrontShadowDrawableVLR.gradientType = GradientDrawable.LINEAR_GRADIENT\n\n        mFrontShadowDrawableVRL =\n            GradientDrawable(GradientDrawable.Orientation.RIGHT_LEFT, mFrontShadowColors)\n        mFrontShadowDrawableVRL.gradientType = GradientDrawable.LINEAR_GRADIENT\n\n        mFrontShadowDrawableHTB =\n            GradientDrawable(GradientDrawable.Orientation.TOP_BOTTOM, mFrontShadowColors)\n        mFrontShadowDrawableHTB.gradientType = GradientDrawable.LINEAR_GRADIENT\n\n        mFrontShadowDrawableHBT =\n            GradientDrawable(GradientDrawable.Orientation.BOTTOM_TOP, mFrontShadowColors)\n        mFrontShadowDrawableHBT.gradientType = GradientDrawable.LINEAR_GRADIENT\n    }\n\n    override fun setBitmap() {\n        when (mDirection) {\n            PageDirection.PREV -> {\n                prevBitmap = prevPage.screenshot(prevBitmap, canvas)\n                curBitmap = curPage.screenshot(curBitmap, canvas)\n            }\n\n            PageDirection.NEXT -> {\n                nextBitmap = nextPage.screenshot(nextBitmap, canvas)\n                curBitmap = curPage.screenshot(curBitmap, canvas)\n            }\n\n            else -> Unit\n        }\n    }\n\n    override fun setViewSize(width: Int, height: Int) {\n        super.setViewSize(width, height)\n        mMaxLength = hypot(viewWidth.toDouble(), viewHeight.toDouble()).toFloat()\n    }\n\n    override fun onTouch(event: MotionEvent) {\n        super.onTouch(event)\n        when (event.action) {\n            MotionEvent.ACTION_DOWN -> {\n                calcCornerXY(event.x, event.y)\n            }\n\n            MotionEvent.ACTION_MOVE -> {\n                if ((startY > viewHeight / 3 && startY < viewHeight * 2 / 3)\n                    || mDirection == PageDirection.PREV\n                ) {\n                    readView.touchY = viewHeight.toFloat()\n                }\n\n                if (startY > viewHeight / 3 && startY < viewHeight / 2\n                    && mDirection == PageDirection.NEXT\n                ) {\n                    readView.touchY = 1f\n                }\n            }\n        }\n    }\n\n    override fun setDirection(direction: PageDirection) {\n        super.setDirection(direction)\n        when (direction) {\n            PageDirection.PREV ->\n                //上一页滑动不出现对角\n                if (startX > viewWidth / 2) {\n                    calcCornerXY(startX, viewHeight.toFloat())\n                } else {\n                    calcCornerXY(viewWidth - startX, viewHeight.toFloat())\n                }\n\n            PageDirection.NEXT ->\n                if (viewWidth / 2 > startX) {\n                    calcCornerXY(viewWidth - startX, startY)\n                }\n\n            else -> Unit\n        }\n    }\n\n    override fun onAnimStart(animationSpeed: Int) {\n        var dx: Float\n        val dy: Float\n        // dy 垂直方向滑动的距离，负值会使滚动向上滚动\n        if (isCancel) {\n            dx = if (mCornerX > 0 && mDirection == PageDirection.NEXT) {\n                (viewWidth - touchX)\n            } else {\n                -touchX\n            }\n            if (mDirection != PageDirection.NEXT) {\n                dx = -(viewWidth + touchX)\n            }\n            dy = if (mCornerY > 0) {\n                (viewHeight - touchY)\n            } else {\n                -touchY // 防止mTouchY最终变为0\n            }\n        } else {\n            dx = if (mCornerX > 0 && mDirection == PageDirection.NEXT) {\n                -(viewWidth + touchX)\n            } else {\n                viewWidth - touchX\n            }\n            dy = if (mCornerY > 0) {\n                (viewHeight - touchY)\n            } else {\n                (1 - touchY) // 防止mTouchY最终变为0\n            }\n        }\n        startScroll(touchX.toInt(), touchY.toInt(), dx.toInt(), dy.toInt(), animationSpeed)\n    }\n\n    override fun onAnimStop() {\n        if (!isCancel) {\n            readView.fillPage(mDirection)\n        }\n    }\n\n    override fun onDraw(canvas: Canvas) {\n        if (!isRunning) return\n        when (mDirection) {\n            PageDirection.NEXT -> {\n                calcPoints()\n                drawCurrentPageArea(canvas, curBitmap)\n                drawNextPageAreaAndShadow(canvas, nextBitmap)\n                drawCurrentPageShadow(canvas)\n                drawCurrentBackArea(canvas, curBitmap)\n            }\n\n            PageDirection.PREV -> {\n                calcPoints()\n                drawCurrentPageArea(canvas, prevBitmap)\n                drawNextPageAreaAndShadow(canvas, curBitmap)\n                drawCurrentPageShadow(canvas)\n                drawCurrentBackArea(canvas, prevBitmap)\n            }\n\n            else -> return\n        }\n    }\n\n    /**\n     * 绘制翻起页背面\n     */\n    private fun drawCurrentBackArea(\n        canvas: Canvas,\n        bitmap: Bitmap?\n    ) {\n        bitmap ?: return\n        val i = ((mBezierStart1.x + mBezierControl1.x) / 2).toInt()\n        val f1 = abs(i - mBezierControl1.x)\n        val i1 = ((mBezierStart2.y + mBezierControl2.y) / 2).toInt()\n        val f2 = abs(i1 - mBezierControl2.y)\n        val f3 = min(f1, f2)\n        mPath1.reset()\n        mPath1.moveTo(mBezierVertex2.x, mBezierVertex2.y)\n        mPath1.lineTo(mBezierVertex1.x, mBezierVertex1.y)\n        mPath1.lineTo(mBezierEnd1.x, mBezierEnd1.y)\n        mPath1.lineTo(mTouchX, mTouchY)\n        mPath1.lineTo(mBezierEnd2.x, mBezierEnd2.y)\n        mPath1.close()\n        val mFolderShadowDrawable: GradientDrawable\n        val left: Int\n        val right: Int\n        if (mIsRtOrLb) {\n            left = (mBezierStart1.x - 1).toInt()\n            right = (mBezierStart1.x + f3 + 1).toInt()\n            mFolderShadowDrawable = mFolderShadowDrawableLR\n        } else {\n            left = (mBezierStart1.x - f3 - 1).toInt()\n            right = (mBezierStart1.x + 1).toInt()\n            mFolderShadowDrawable = mFolderShadowDrawableRL\n        }\n        canvas.save()\n        canvas.clipPath(mPath0)\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {\n            canvas.clipPath(mPath1)\n        } else {\n            canvas.clipPath(mPath1, Region.Op.INTERSECT)\n        }\n\n        mPaint.colorFilter = mColorMatrixFilter\n        val dis = hypot(\n            mCornerX - mBezierControl1.x.toDouble(),\n            mBezierControl2.y - mCornerY.toDouble()\n        ).toFloat()\n        val f8 = (mCornerX - mBezierControl1.x) / dis\n        val f9 = (mBezierControl2.y - mCornerY) / dis\n        mMatrixArray[0] = 1 - 2 * f9 * f9\n        mMatrixArray[1] = 2 * f8 * f9\n        mMatrixArray[3] = mMatrixArray[1]\n        mMatrixArray[4] = 1 - 2 * f8 * f8\n        mMatrix.reset()\n        mMatrix.setValues(mMatrixArray)\n        mMatrix.preTranslate(-mBezierControl1.x, -mBezierControl1.y)\n        mMatrix.postTranslate(mBezierControl1.x, mBezierControl1.y)\n        canvas.drawColor(ReadBookConfig.bgMeanColor)\n        canvas.drawBitmap(bitmap, mMatrix, mPaint)\n        mPaint.colorFilter = null\n        canvas.rotate(mDegrees, mBezierStart1.x, mBezierStart1.y)\n        mFolderShadowDrawable.setBounds(\n            left, mBezierStart1.y.toInt(),\n            right, (mBezierStart1.y + mMaxLength).toInt()\n        )\n        mFolderShadowDrawable.draw(canvas)\n        canvas.restore()\n    }\n\n    /**\n     * 绘制翻起页的阴影\n     */\n    private fun drawCurrentPageShadow(canvas: Canvas) {\n        val degree: Double = if (mIsRtOrLb) {\n            Math.PI / 4 - atan2(mBezierControl1.y - mTouchY, mTouchX - mBezierControl1.x)\n        } else {\n            Math.PI / 4 - atan2(mTouchY - mBezierControl1.y, mTouchX - mBezierControl1.x)\n        }\n        // 翻起页阴影顶点与touch点的距离\n        val d1 = 25.toFloat() * 1.414 * cos(degree)\n        val d2 = 25.toFloat() * 1.414 * sin(degree)\n        val x = (mTouchX + d1).toFloat()\n        val y: Float = if (mIsRtOrLb) {\n            (mTouchY + d2).toFloat()\n        } else {\n            (mTouchY - d2).toFloat()\n        }\n        mPath1.reset()\n        mPath1.moveTo(x, y)\n        mPath1.lineTo(mTouchX, mTouchY)\n        mPath1.lineTo(mBezierControl1.x, mBezierControl1.y)\n        mPath1.lineTo(mBezierStart1.x, mBezierStart1.y)\n        mPath1.close()\n        canvas.save()\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {\n            canvas.clipOutPath(mPath0)\n        } else {\n            canvas.clipPath(mPath0, Region.Op.XOR)\n        }\n        canvas.clipPath(mPath1, Region.Op.INTERSECT)\n\n        var leftX: Int\n        var rightX: Int\n        var mCurrentPageShadow: GradientDrawable\n        if (mIsRtOrLb) {\n            leftX = mBezierControl1.x.toInt()\n            rightX = (mBezierControl1.x + 25).toInt()\n            mCurrentPageShadow = mFrontShadowDrawableVLR\n        } else {\n            leftX = (mBezierControl1.x - 25).toInt()\n            rightX = (mBezierControl1.x + 1).toInt()\n            mCurrentPageShadow = mFrontShadowDrawableVRL\n        }\n        var rotateDegrees = Math.toDegrees(\n            atan2(mTouchX - mBezierControl1.x, mBezierControl1.y - mTouchY).toDouble()\n        ).toFloat()\n        canvas.rotate(rotateDegrees, mBezierControl1.x, mBezierControl1.y)\n        mCurrentPageShadow.setBounds(\n            leftX, (mBezierControl1.y - mMaxLength).toInt(),\n            rightX, mBezierControl1.y.toInt()\n        )\n        mCurrentPageShadow.draw(canvas)\n        canvas.restore()\n\n        mPath1.reset()\n        mPath1.moveTo(x, y)\n        mPath1.lineTo(mTouchX, mTouchY)\n        mPath1.lineTo(mBezierControl2.x, mBezierControl2.y)\n        mPath1.lineTo(mBezierStart2.x, mBezierStart2.y)\n        mPath1.close()\n        canvas.save()\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {\n            canvas.clipOutPath(mPath0)\n        } else {\n            canvas.clipPath(mPath0, Region.Op.XOR)\n        }\n        canvas.clipPath(mPath1)\n\n        if (mIsRtOrLb) {\n            leftX = mBezierControl2.y.toInt()\n            rightX = (mBezierControl2.y + 25).toInt()\n            mCurrentPageShadow = mFrontShadowDrawableHTB\n        } else {\n            leftX = (mBezierControl2.y - 25).toInt()\n            rightX = (mBezierControl2.y + 1).toInt()\n            mCurrentPageShadow = mFrontShadowDrawableHBT\n        }\n        rotateDegrees = Math.toDegrees(\n            atan2(mBezierControl2.y - mTouchY, mBezierControl2.x - mTouchX).toDouble()\n        ).toFloat()\n        canvas.rotate(rotateDegrees, mBezierControl2.x, mBezierControl2.y)\n        val temp =\n            if (mBezierControl2.y < 0) (mBezierControl2.y - viewHeight).toDouble()\n            else mBezierControl2.y.toDouble()\n        val hmg = hypot(mBezierControl2.x.toDouble(), temp)\n        if (hmg > mMaxLength)\n            mCurrentPageShadow.setBounds(\n                (mBezierControl2.x - 25 - hmg).toInt(), leftX,\n                (mBezierControl2.x + mMaxLength - hmg).toInt(), rightX\n            )\n        else\n            mCurrentPageShadow.setBounds(\n                (mBezierControl2.x - mMaxLength).toInt(), leftX,\n                mBezierControl2.x.toInt(), rightX\n            )\n        mCurrentPageShadow.draw(canvas)\n        canvas.restore()\n    }\n\n    //\n    private fun drawNextPageAreaAndShadow(\n        canvas: Canvas,\n        bitmap: Bitmap?\n    ) {\n        bitmap ?: return\n        mPath1.reset()\n        mPath1.moveTo(mBezierStart1.x, mBezierStart1.y)\n        mPath1.lineTo(mBezierVertex1.x, mBezierVertex1.y)\n        mPath1.lineTo(mBezierVertex2.x, mBezierVertex2.y)\n        mPath1.lineTo(mBezierStart2.x, mBezierStart2.y)\n        mPath1.lineTo(mCornerX.toFloat(), mCornerY.toFloat())\n        mPath1.close()\n        mDegrees = Math.toDegrees(\n            atan2(\n                (mBezierControl1.x - mCornerX).toDouble(),\n                mBezierControl2.y - mCornerY.toDouble()\n            )\n        ).toFloat()\n        val leftX: Int\n        val rightX: Int\n        val mBackShadowDrawable: GradientDrawable\n        if (mIsRtOrLb) { //左下及右上\n            leftX = mBezierStart1.x.toInt()\n            rightX = (mBezierStart1.x + mTouchToCornerDis / 4).toInt()\n            mBackShadowDrawable = mBackShadowDrawableLR\n        } else {\n            leftX = (mBezierStart1.x - mTouchToCornerDis / 4).toInt()\n            rightX = mBezierStart1.x.toInt()\n            mBackShadowDrawable = mBackShadowDrawableRL\n        }\n        canvas.save()\n        canvas.clipPath(mPath0)\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {\n            canvas.clipPath(mPath1)\n        } else {\n            canvas.clipPath(mPath1, Region.Op.INTERSECT)\n        }\n        canvas.drawBitmap(bitmap, 0f, 0f, null)\n        canvas.rotate(mDegrees, mBezierStart1.x, mBezierStart1.y)\n        mBackShadowDrawable.setBounds(\n            leftX, mBezierStart1.y.toInt(),\n            rightX, (mMaxLength + mBezierStart1.y).toInt()\n        ) //左上及右下角的xy坐标值,构成一个矩形\n        mBackShadowDrawable.draw(canvas)\n        canvas.restore()\n    }\n\n    //\n    private fun drawCurrentPageArea(\n        canvas: Canvas,\n        bitmap: Bitmap?\n    ) {\n        bitmap ?: return\n        mPath0.reset()\n        mPath0.moveTo(mBezierStart1.x, mBezierStart1.y)\n        mPath0.quadTo(mBezierControl1.x, mBezierControl1.y, mBezierEnd1.x, mBezierEnd1.y)\n        mPath0.lineTo(mTouchX, mTouchY)\n        mPath0.lineTo(mBezierEnd2.x, mBezierEnd2.y)\n        mPath0.quadTo(mBezierControl2.x, mBezierControl2.y, mBezierStart2.x, mBezierStart2.y)\n        mPath0.lineTo(mCornerX.toFloat(), mCornerY.toFloat())\n        mPath0.close()\n\n        canvas.save()\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {\n            canvas.clipOutPath(mPath0)\n        } else {\n            canvas.clipPath(mPath0, Region.Op.XOR)\n        }\n        canvas.drawBitmap(bitmap, 0f, 0f, null)\n        canvas.restore()\n    }\n\n    /**\n     * 计算拖拽点对应的拖拽脚\n     */\n    private fun calcCornerXY(x: Float, y: Float) {\n        mCornerX = if (x <= viewWidth / 2) 0 else viewWidth\n        mCornerY = if (y <= viewHeight / 2) 0 else viewHeight\n        mIsRtOrLb = (mCornerX == 0 && mCornerY == viewHeight)\n                || (mCornerY == 0 && mCornerX == viewWidth)\n    }\n\n    private fun calcPoints() {\n        mTouchX = touchX\n        mTouchY = touchY\n\n        mMiddleX = (mTouchX + mCornerX) / 2\n        mMiddleY = (mTouchY + mCornerY) / 2\n        mBezierControl1.x =\n            mMiddleX - (mCornerY - mMiddleY) * (mCornerY - mMiddleY) / (mCornerX - mMiddleX)\n        mBezierControl1.y = mCornerY.toFloat()\n        mBezierControl2.x = mCornerX.toFloat()\n\n        val f4 = mCornerY - mMiddleY\n        if (f4 == 0f) {\n            mBezierControl2.y = mMiddleY - (mCornerX - mMiddleX) * (mCornerX - mMiddleX) / 0.1f\n\n        } else {\n            mBezierControl2.y =\n                mMiddleY - (mCornerX - mMiddleX) * (mCornerX - mMiddleX) / (mCornerY - mMiddleY)\n        }\n        mBezierStart1.x = mBezierControl1.x - (mCornerX - mBezierControl1.x) / 2\n        mBezierStart1.y = mCornerY.toFloat()\n\n        // 固定左边上下两个点\n        if (mTouchX > 0 && mTouchX < viewWidth) {\n            if (mBezierStart1.x < 0 || mBezierStart1.x > viewWidth) {\n                if (mBezierStart1.x < 0)\n                    mBezierStart1.x = viewWidth - mBezierStart1.x\n\n                val f1 = abs(mCornerX - mTouchX)\n                val f2 = viewWidth * f1 / mBezierStart1.x\n                mTouchX = abs(mCornerX - f2)\n\n                val f3 = abs(mCornerX - mTouchX) * abs(mCornerY - mTouchY) / f1\n                mTouchY = abs(mCornerY - f3)\n\n                mMiddleX = (mTouchX + mCornerX) / 2\n                mMiddleY = (mTouchY + mCornerY) / 2\n\n                mBezierControl1.x =\n                    mMiddleX - (mCornerY - mMiddleY) * (mCornerY - mMiddleY) / (mCornerX - mMiddleX)\n                mBezierControl1.y = mCornerY.toFloat()\n\n                mBezierControl2.x = mCornerX.toFloat()\n\n                val f5 = mCornerY - mMiddleY\n                if (f5 == 0f) {\n                    mBezierControl2.y =\n                        mMiddleY - (mCornerX - mMiddleX) * (mCornerX - mMiddleX) / 0.1f\n                } else {\n                    mBezierControl2.y =\n                        mMiddleY - (mCornerX - mMiddleX) * (mCornerX - mMiddleX) / (mCornerY - mMiddleY)\n                }\n\n                mBezierStart1.x = mBezierControl1.x - (mCornerX - mBezierControl1.x) / 2\n            }\n        }\n        mBezierStart2.x = mCornerX.toFloat()\n        mBezierStart2.y = mBezierControl2.y - (mCornerY - mBezierControl2.y) / 2\n\n        mTouchToCornerDis = hypot(\n            (mTouchX - mCornerX).toDouble(),\n            (mTouchY - mCornerY).toDouble()\n        ).toFloat()\n\n        mBezierEnd1 = getCross(\n            PointF(mTouchX, mTouchY), mBezierControl1, mBezierStart1,\n            mBezierStart2\n        )\n        mBezierEnd2 = getCross(\n            PointF(mTouchX, mTouchY), mBezierControl2, mBezierStart1,\n            mBezierStart2\n        )\n\n        mBezierVertex1.x = (mBezierStart1.x + 2 * mBezierControl1.x + mBezierEnd1.x) / 4\n        mBezierVertex1.y = (2 * mBezierControl1.y + mBezierStart1.y + mBezierEnd1.y) / 4\n        mBezierVertex2.x = (mBezierStart2.x + 2 * mBezierControl2.x + mBezierEnd2.x) / 4\n        mBezierVertex2.y = (2 * mBezierControl2.y + mBezierStart2.y + mBezierEnd2.y) / 4\n    }\n\n    /**\n     * 求解直线P1P2和直线P3P4的交点坐标\n     */\n    private fun getCross(P1: PointF, P2: PointF, P3: PointF, P4: PointF): PointF {\n        val crossP = PointF()\n        // 二元函数通式： y=ax+b\n        val a1 = (P2.y - P1.y) / (P2.x - P1.x)\n        val b1 = (P1.x * P2.y - P2.x * P1.y) / (P1.x - P2.x)\n        val a2 = (P4.y - P3.y) / (P4.x - P3.x)\n        val b2 = (P3.x * P4.y - P4.x * P3.y) / (P3.x - P4.x)\n        crossP.x = (b2 - b1) / (a1 - a2)\n        crossP.y = a1 * crossP.x + b1\n        return crossP\n    }\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/book/read/page/delegate/SlidePageDelegate.kt",
    "content": "package io.legado.app.ui.book.read.page.delegate\n\nimport android.graphics.Canvas\nimport androidx.core.graphics.withTranslation\nimport io.legado.app.ui.book.read.page.ReadView\nimport io.legado.app.ui.book.read.page.entities.PageDirection\n\nclass SlidePageDelegate(readView: ReadView) : HorizontalPageDelegate(readView) {\n\n    override fun onAnimStart(animationSpeed: Int) {\n        val distanceX: Float\n        when (mDirection) {\n            PageDirection.NEXT -> distanceX =\n                if (isCancel) {\n                    var dis = viewWidth - startX + touchX\n                    if (dis > viewWidth) {\n                        dis = viewWidth.toFloat()\n                    }\n                    viewWidth - dis\n                } else {\n                    -(touchX + (viewWidth - startX))\n                }\n\n            else -> distanceX =\n                if (isCancel) {\n                    -(touchX - startX)\n                } else {\n                    viewWidth - (touchX - startX)\n                }\n        }\n        startScroll(touchX.toInt(), 0, distanceX.toInt(), 0, animationSpeed)\n    }\n\n    override fun onDraw(canvas: Canvas) {\n        val offsetX = touchX - startX\n\n        if ((mDirection == PageDirection.NEXT && offsetX > 0)\n            || (mDirection == PageDirection.PREV && offsetX < 0)\n        ) return\n        val distanceX = if (offsetX > 0) offsetX - viewWidth else offsetX + viewWidth\n        if (!isRunning) return\n        if (mDirection == PageDirection.PREV) {\n            canvas.withTranslation(distanceX + viewWidth) {\n                curRecorder.draw(this)\n            }\n            canvas.withTranslation(distanceX) {\n                prevRecorder.draw(this)\n            }\n        } else if (mDirection == PageDirection.NEXT) {\n            canvas.withTranslation(distanceX) {\n                nextRecorder.draw(this)\n            }\n            canvas.withTranslation(distanceX - viewWidth) {\n                curRecorder.draw(this)\n            }\n        }\n    }\n\n    override fun onAnimStop() {\n        if (!isCancel) {\n            readView.fillPage(mDirection)\n        }\n    }\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/book/read/page/entities/PageDirection.kt",
    "content": "package io.legado.app.ui.book.read.page.entities\n\nenum class PageDirection {\n    NONE, PREV, NEXT\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/book/read/page/entities/TextChapter.kt",
    "content": "package io.legado.app.ui.book.read.page.entities\n\n\nimport androidx.annotation.Keep\nimport io.legado.app.data.entities.Book\nimport io.legado.app.data.entities.BookChapter\nimport io.legado.app.data.entities.ReplaceRule\nimport io.legado.app.help.book.BookContent\nimport io.legado.app.ui.book.read.page.provider.LayoutProgressListener\nimport io.legado.app.ui.book.read.page.provider.TextChapterLayout\nimport io.legado.app.utils.fastBinarySearchBy\nimport kotlinx.coroutines.CoroutineScope\nimport kotlin.math.abs\nimport kotlin.math.min\n\n/**\n * 章节信息\n */\n@Keep\n@Suppress(\"unused\")\ndata class TextChapter(\n    val chapter: BookChapter,\n    val position: Int,\n    val title: String,\n    val chaptersSize: Int,\n    val sameTitleRemoved: Boolean,\n    val isVip: Boolean,\n    val isPay: Boolean,\n    //起效的替换规则\n    val effectiveReplaceRules: List<ReplaceRule>?\n) : LayoutProgressListener {\n\n    private val textPages = arrayListOf<TextPage>()\n    val pages: List<TextPage> get() = textPages\n\n    private var layout: TextChapterLayout? = null\n\n    val layoutChannel get() = layout!!.channel\n\n    fun getPage(index: Int): TextPage? {\n        return pages.getOrNull(index)\n    }\n\n    fun getPageByReadPos(readPos: Int): TextPage? {\n        return getPage(getPageIndexByCharIndex(readPos))\n    }\n\n    val lastPage: TextPage? get() = pages.lastOrNull()\n\n    val lastIndex: Int get() = pages.lastIndex\n\n    val lastReadLength: Int get() = getReadLength(lastIndex)\n\n    val pageSize: Int get() = pages.size\n\n    var listener: LayoutProgressListener? = null\n\n    var isCompleted = false\n\n    val paragraphs by lazy {\n        paragraphsInternal\n    }\n\n    val pageParagraphs by lazy {\n        pageParagraphsInternal\n    }\n\n    val paragraphsInternal: ArrayList<TextParagraph>\n        get() {\n            val paragraphs = arrayListOf<TextParagraph>()\n            for (i in pages.indices) {\n                val lines = pages[i].lines\n                for (a in lines.indices) {\n                    val line = lines[a]\n                    if (line.paragraphNum <= 0) continue\n                    if (paragraphs.lastIndex < line.paragraphNum - 1) {\n                        paragraphs.add(TextParagraph(line.paragraphNum))\n                    }\n                    paragraphs[line.paragraphNum - 1].textLines.add(line)\n                }\n            }\n            return paragraphs\n        }\n\n    val pageParagraphsInternal: List<TextParagraph>\n        get() {\n            val paragraphs = arrayListOf<TextParagraph>()\n            for (i in pages.indices) {\n                paragraphs.addAll(pages[i].paragraphs)\n            }\n            for (i in paragraphs.indices) {\n                paragraphs[i].num = i + 1\n            }\n            return paragraphs\n        }\n\n    /**\n     * @param index 页数\n     * @return 是否是最后一页\n     */\n    fun isLastIndex(index: Int): Boolean {\n        return isCompleted && index >= pages.size - 1\n    }\n\n    fun isLastIndexCurrent(index: Int): Boolean {\n        return index >= pages.size - 1\n    }\n\n    /**\n     * @param pageIndex 页数\n     * @return 已读长度\n     */\n    fun getReadLength(pageIndex: Int): Int {\n        if (pageIndex < 0) return 0\n        return pages[min(pageIndex, lastIndex)].chapterPosition\n        /*\n        var length = 0\n        val maxIndex = min(pageIndex, pages.size)\n        for (index in 0 until maxIndex) {\n            length += pages[index].charSize\n        }\n        return length\n        */\n    }\n\n    /**\n     * @param length 当前页面文字在章节中的位置\n     * @return 下一页位置,如果没有下一页返回-1\n     */\n    fun getNextPageLength(length: Int): Int {\n        val pageIndex = getPageIndexByCharIndex(length)\n        if (pageIndex + 1 >= pageSize) {\n            return -1\n        }\n        return getReadLength(pageIndex + 1)\n    }\n\n    /**\n     * @param length 当前页面文字在章节中的位置\n     * @return 上一页位置,如果没有上一页返回-1\n     */\n    fun getPrevPageLength(length: Int): Int {\n        val pageIndex = getPageIndexByCharIndex(length)\n        if (pageIndex - 1 < 0) {\n            return -1\n        }\n        return getReadLength(pageIndex - 1)\n    }\n\n    /**\n     * 获取内容\n     */\n    fun getContent(): String {\n        val stringBuilder = StringBuilder()\n        pages.forEach {\n            stringBuilder.append(it.text)\n        }\n        return stringBuilder.toString()\n    }\n\n    /**\n     * @return 获取未读文字\n     */\n    fun getUnRead(pageIndex: Int): String {\n        val stringBuilder = StringBuilder()\n        if (pages.isNotEmpty()) {\n            for (index in pageIndex..pages.lastIndex) {\n                stringBuilder.append(pages[index].text)\n            }\n        }\n        return stringBuilder.toString()\n    }\n\n    /**\n     * @return 需要朗读的文本列表\n     * @param pageIndex 起始页\n     * @param pageSplit 是否分页\n     * @param startPos 从当前页什么地方开始朗读\n     */\n    fun getNeedReadAloud(\n        pageIndex: Int,\n        pageSplit: Boolean,\n        startPos: Int,\n        pageEndIndex: Int = pages.lastIndex\n    ): String {\n        val stringBuilder = StringBuilder()\n        if (pages.isNotEmpty()) {\n            for (index in pageIndex..min(pageEndIndex, pages.lastIndex)) {\n                stringBuilder.append(pages[index].text)\n                if (pageSplit && !stringBuilder.endsWith(\"\\n\")) {\n                    stringBuilder.append(\"\\n\")\n                }\n            }\n        }\n        return stringBuilder.substring(startPos).toString()\n    }\n\n    fun getParagraphNum(\n        position: Int,\n        pageSplit: Boolean,\n    ): Int {\n        val paragraphs = getParagraphs(pageSplit)\n        paragraphs.forEach { paragraph ->\n            if (position in paragraph.chapterIndices) {\n                return paragraph.num\n            }\n        }\n        return -1\n    }\n\n    fun getParagraphs(pageSplit: Boolean): List<TextParagraph> {\n        return if (pageSplit) {\n            if (isCompleted) pageParagraphs else pageParagraphsInternal\n        } else {\n            if (isCompleted) paragraphs else paragraphsInternal\n        }\n    }\n\n    fun getLastParagraphPosition(): Int {\n        return pageParagraphs.last().chapterPosition\n    }\n\n    /**\n     * @return 根据索引位置获取所在页\n     */\n    fun getPageIndexByCharIndex(charIndex: Int): Int {\n        val pageSize = pages.size\n        if (pageSize == 0) {\n            return -1\n        }\n        val bIndex = pages.fastBinarySearchBy(charIndex, 0, pageSize) {\n            it.chapterPosition\n        }\n        val index = abs(bIndex + 1) - 1\n        // 判断是否已经排版到 charIndex ，没有则返回 -1\n        if (!isCompleted && index == pageSize - 1) {\n            val page = pages[index]\n            val pageEndPos = page.chapterPosition + page.charSize\n            if (charIndex > pageEndPos) {\n                return -1\n            }\n        }\n        return index\n        /*\n        var length = 0\n        for (i in pages.indices) {\n            val page = pages[i]\n            length += page.charSize\n            if (length > charIndex) {\n                return page.index\n            }\n        }\n        return pages.lastIndex\n        */\n    }\n\n    fun clearSearchResult() {\n        for (i in pages.indices) {\n            val page = pages[i]\n            page.searchResult.forEach {\n                it.selected = false\n                it.isSearchResult = false\n            }\n            page.searchResult.clear()\n        }\n    }\n\n    fun createLayout(scope: CoroutineScope, book: Book, bookContent: BookContent) {\n        if (layout != null) {\n            throw IllegalStateException(\"已经排版过了\")\n        }\n        layout = TextChapterLayout(\n            scope,\n            this,\n            textPages,\n            book,\n            bookContent,\n        )\n    }\n\n    fun setProgressListener(l: LayoutProgressListener?) {\n        if (isCompleted) {\n            // no op\n        } else if (layout?.exception != null) {\n            l?.onLayoutException(layout?.exception!!)\n        } else {\n            listener = l\n        }\n    }\n\n    override fun onLayoutPageCompleted(index: Int, page: TextPage) {\n        listener?.onLayoutPageCompleted(index, page)\n    }\n\n    override fun onLayoutCompleted() {\n        isCompleted = true\n        listener?.onLayoutCompleted()\n        listener = null\n    }\n\n    override fun onLayoutException(e: Throwable) {\n        listener?.onLayoutException(e)\n        listener = null\n    }\n\n    fun cancelLayout() {\n        layout?.cancel()\n        listener = null\n    }\n\n    companion object {\n        val emptyTextChapter = TextChapter(\n            BookChapter(), -1, \"emptyTextChapter\", -1,\n            sameTitleRemoved = false,\n            isVip = false,\n            isPay = false,\n            null\n        ).apply { isCompleted = true }\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/book/read/page/entities/TextLine.kt",
    "content": "package io.legado.app.ui.book.read.page.entities\n\nimport android.annotation.SuppressLint\nimport android.graphics.Canvas\nimport android.graphics.Paint.FontMetrics\nimport android.os.Build\nimport androidx.annotation.Keep\nimport io.legado.app.help.PaintPool\nimport io.legado.app.help.book.isImage\nimport io.legado.app.help.config.AppConfig\nimport io.legado.app.help.config.ReadBookConfig\nimport io.legado.app.lib.theme.ThemeStore\nimport io.legado.app.model.ReadBook\nimport io.legado.app.ui.book.read.page.ContentTextView\nimport io.legado.app.ui.book.read.page.entities.TextPage.Companion.emptyTextPage\nimport io.legado.app.ui.book.read.page.entities.column.BaseColumn\nimport io.legado.app.ui.book.read.page.entities.column.TextColumn\nimport io.legado.app.ui.book.read.page.provider.ChapterProvider\nimport io.legado.app.utils.canvasrecorder.CanvasRecorderFactory\nimport io.legado.app.utils.canvasrecorder.recordIfNeededThenDraw\nimport io.legado.app.utils.dpToPx\n\n/**\n * 行信息\n */\n@Keep\n@Suppress(\"unused\", \"MemberVisibilityCanBePrivate\")\ndata class TextLine(\n    var text: String = \"\",\n    private val textColumns: ArrayList<BaseColumn> = arrayListOf(),\n    var lineTop: Float = 0f,\n    var lineBase: Float = 0f,\n    var lineBottom: Float = 0f,\n    var indentWidth: Float = 0f,\n    var paragraphNum: Int = 0,\n    var chapterPosition: Int = 0,\n    var pagePosition: Int = 0,\n    val isTitle: Boolean = false,\n    var isParagraphEnd: Boolean = false,\n    var isImage: Boolean = false,\n    var startX: Float = 0f,\n    var indentSize: Int = 0,\n    var extraLetterSpacing: Float = 0f,\n    var extraLetterSpacingOffsetX: Float = 0f,\n    var wordSpacing: Float = 0f,\n    var exceed: Boolean = false,\n    var onlyTextColumn: Boolean = true,\n) {\n\n    val columns: List<BaseColumn> get() = textColumns\n    val charSize: Int get() = text.length\n    val lineStart: Float get() = textColumns.firstOrNull()?.start ?: 0f\n    val lineEnd: Float get() = textColumns.lastOrNull()?.end ?: 0f\n    val chapterIndices: IntRange get() = chapterPosition..chapterPosition + charSize\n    val height: Float inline get() = lineBottom - lineTop\n    val canvasRecorder = CanvasRecorderFactory.create()\n    var searchResultColumnCount = 0\n    var isReadAloud: Boolean = false\n        set(value) {\n            if (field != value) {\n                invalidate()\n            }\n            if (value) {\n                textPage.hasReadAloudSpan = true\n            }\n            field = value\n        }\n    var textPage: TextPage = emptyTextPage\n    var isLeftLine = true\n\n    fun addColumn(column: BaseColumn) {\n        if (column !is TextColumn) {\n            onlyTextColumn = false\n        }\n        column.textLine = this\n        textColumns.add(column)\n    }\n\n    fun getColumn(index: Int): BaseColumn {\n        return textColumns.getOrElse(index) {\n            textColumns.last()\n        }\n    }\n\n    fun getColumnReverseAt(index: Int, offset: Int = 0): BaseColumn {\n        return textColumns[textColumns.lastIndex - offset - index]\n    }\n\n    fun getColumnsCount(): Int {\n        return textColumns.size\n    }\n\n    fun upTopBottom(durY: Float, textHeight: Float, fontMetrics: FontMetrics) {\n        lineTop = ChapterProvider.paddingTop + durY\n        lineBottom = lineTop + textHeight\n        lineBase = lineBottom - fontMetrics.descent\n    }\n\n    fun isTouch(x: Float, y: Float, relativeOffset: Float): Boolean {\n        return y > lineTop + relativeOffset\n                && y < lineBottom + relativeOffset\n                && x >= lineStart\n                && x <= lineEnd\n    }\n\n    fun isTouchY(y: Float, relativeOffset: Float): Boolean {\n        return y > lineTop + relativeOffset\n                && y < lineBottom + relativeOffset\n    }\n\n    fun isVisible(relativeOffset: Float): Boolean {\n        val top = lineTop + relativeOffset\n        val bottom = lineBottom + relativeOffset\n        val width = bottom - top\n        val visibleTop = ChapterProvider.paddingTop\n        val visibleBottom = ChapterProvider.visibleBottom\n        val visible = when {\n            // 完全可视\n            top >= visibleTop && bottom <= visibleBottom -> true\n            top <= visibleTop && bottom >= visibleBottom -> true\n            // 上方第一行部分可视\n            top < visibleTop && bottom > visibleTop && bottom < visibleBottom -> {\n                if (isImage) {\n                    true\n                } else {\n                    val visibleRate = (bottom - visibleTop) / width\n                    visibleRate > 0.6\n                }\n            }\n            // 下方第一行部分可视\n            top > visibleTop && top < visibleBottom && bottom > visibleBottom -> {\n                if (isImage) {\n                    true\n                } else {\n                    val visibleRate = (visibleBottom - top) / width\n                    visibleRate > 0.6\n                }\n            }\n            // 不可视\n            else -> false\n        }\n        return visible\n    }\n\n    fun draw(view: ContentTextView, canvas: Canvas) {\n        if (AppConfig.optimizeRender) {\n            canvasRecorder.recordIfNeededThenDraw(canvas, view.width, height.toInt()) {\n                drawTextLine(view, this)\n            }\n        } else {\n            drawTextLine(view, canvas)\n        }\n    }\n\n    private fun drawTextLine(view: ContentTextView, canvas: Canvas) {\n        if (checkFastDraw()) {\n            fastDrawTextLine(view, canvas)\n        } else {\n            for (i in columns.indices) {\n                columns[i].draw(view, canvas)\n            }\n        }\n\n        // 墨水屏模式下的朗读和搜索下划线\n        if (AppConfig.isEInkMode && (isReadAloud || searchResultColumnCount > 0)) {\n            val underlinePaint = PaintPool.obtain()\n            underlinePaint.set(ChapterProvider.contentPaint)\n            underlinePaint.strokeWidth = 1.dpToPx().toFloat()\n            val lineY = height - 1.dpToPx()\n            canvas.drawLine(lineStart + indentWidth, lineY, lineEnd, lineY, underlinePaint)\n            PaintPool.recycle(underlinePaint)\n        }\n        \n        if (ReadBookConfig.underline && !isImage && ReadBook.book?.isImage != true) {\n            drawUnderline(canvas)\n        }\n    }\n\n    @SuppressLint(\"NewApi\")\n    private fun fastDrawTextLine(view: ContentTextView, canvas: Canvas) {\n        val textPaint = if (isTitle) {\n            ChapterProvider.titlePaint\n        } else {\n            ChapterProvider.contentPaint\n        }\n        val textColor = if (isReadAloud) {\n            ThemeStore.accentColor\n        } else {\n            ReadBookConfig.textColor\n        }\n        if (textPaint.color != textColor) {\n            textPaint.color = textColor\n        }\n        val paint = PaintPool.obtain()\n        paint.set(textPaint)\n        val letterSpacing = paint.letterSpacing * paint.textSize\n        val letterSpacingHalf = letterSpacing * 0.5f\n        if (extraLetterSpacing != 0f) {\n            paint.letterSpacing += extraLetterSpacing\n        }\n        if (wordSpacing != 0f) {\n            paint.wordSpacing = wordSpacing\n        }\n        val offsetX = if (atLeastApi35) letterSpacingHalf else extraLetterSpacingOffsetX\n        canvas.drawText(text, indentSize, text.length, startX + offsetX, lineBase - lineTop, paint)\n        PaintPool.recycle(paint)\n        for (i in columns.indices) {\n            val column = columns[i] as TextColumn\n            if (column.selected) {\n                canvas.drawRect(column.start, 0f, column.end, height, view.selectedPaint)\n            }\n        }\n    }\n\n    /**\n     * 绘制下划线\n     */\n    private fun drawUnderline(canvas: Canvas) {\n        val lineY = height - 1.dpToPx()\n        canvas.drawLine(\n            lineStart + indentWidth,\n            lineY,\n            lineEnd,\n            lineY,\n            ChapterProvider.contentPaint\n        )\n    }\n\n    fun checkFastDraw(): Boolean {\n        if (!AppConfig.optimizeRender || exceed || !onlyTextColumn || textPage.isMsgPage) {\n            return false\n        }\n        if (wordSpacing != 0f && (!atLeastApi26 || !wordSpacingWorking)) {\n            return false\n        }\n        return searchResultColumnCount == 0\n    }\n\n    fun invalidate() {\n        invalidateSelf()\n        textPage.invalidate()\n    }\n\n    fun invalidateSelf() {\n        canvasRecorder.invalidate()\n    }\n\n    fun recycleRecorder() {\n        canvasRecorder.recycle()\n    }\n\n    @SuppressLint(\"NewApi\")\n    companion object {\n        val emptyTextLine = TextLine()\n        private val atLeastApi26 = Build.VERSION.SDK_INT >= Build.VERSION_CODES.O\n        private val atLeastApi35 = Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM\n        private val wordSpacingWorking by lazy {\n            // issue 3785 3846\n            val paint = PaintPool.obtain()\n            val text = \"一二 三\"\n            val width1 = paint.measureText(text)\n            try {\n                paint.wordSpacing = 10f\n                val width2 = paint.measureText(text)\n                width2 - width1 == 10f\n            } catch (e: NoSuchMethodError) {\n                false\n            } finally {\n                PaintPool.recycle(paint)\n            }\n        }\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/book/read/page/entities/TextPage.kt",
    "content": "package io.legado.app.ui.book.read.page.entities\n\nimport android.graphics.Canvas\nimport android.graphics.Paint\nimport android.os.Build\nimport android.text.Layout\nimport android.text.StaticLayout\nimport androidx.annotation.Keep\nimport androidx.core.graphics.withTranslation\nimport io.legado.app.R\nimport io.legado.app.help.PaintPool\nimport io.legado.app.help.config.AppConfig\nimport io.legado.app.help.config.ReadBookConfig\nimport io.legado.app.ui.book.read.page.ContentTextView\nimport io.legado.app.ui.book.read.page.entities.TextChapter.Companion.emptyTextChapter\nimport io.legado.app.ui.book.read.page.entities.column.TextColumn\nimport io.legado.app.ui.book.read.page.provider.ChapterProvider\nimport io.legado.app.utils.canvasrecorder.CanvasRecorderFactory\nimport io.legado.app.utils.canvasrecorder.recordIfNeeded\nimport io.legado.app.utils.dpToPx\nimport splitties.init.appCtx\nimport java.text.DecimalFormat\nimport kotlin.math.ceil\nimport kotlin.math.max\nimport kotlin.math.min\n\n/**\n * 页面信息\n */\n@Keep\n@Suppress(\"unused\", \"MemberVisibilityCanBePrivate\")\ndata class TextPage(\n    var index: Int = 0,\n    var text: String = appCtx.getString(R.string.data_loading),\n    var title: String = appCtx.getString(R.string.data_loading),\n    private val textLines: ArrayList<TextLine> = arrayListOf(),\n    var chapterSize: Int = 0,\n    var chapterIndex: Int = 0,\n    var height: Float = 0f,\n    var leftLineSize: Int = 0,\n    var renderHeight: Int = 0\n) {\n\n    companion object {\n        val readProgressFormatter = DecimalFormat(\"0.0%\")\n        val emptyTextPage = TextPage()\n    }\n\n    val lines: List<TextLine> get() = textLines\n    val lineSize: Int get() = textLines.size\n    val charSize: Int get() = text.length.coerceAtLeast(1)\n    val chapterPosition: Int get() = textLines.first().chapterPosition\n    val searchResult = hashSetOf<TextColumn>()\n    var isMsgPage: Boolean = false\n    var canvasRecorder = CanvasRecorderFactory.create(true)\n    var doublePage = false\n    var paddingTop = ChapterProvider.paddingTop\n    var isCompleted = false\n    var hasReadAloudSpan = false\n\n    @JvmField\n    var textChapter = emptyTextChapter\n    val pageSize get() = textChapter.pageSize\n\n    val paragraphs by lazy {\n        paragraphsInternal\n    }\n\n    val paragraphsInternal: ArrayList<TextParagraph>\n        get() {\n            val paragraphs = arrayListOf<TextParagraph>()\n            val lines = textLines.filter { it.paragraphNum > 0 }\n            val offset = lines.first().paragraphNum - 1\n            lines.forEach { line ->\n                if (paragraphs.lastIndex < line.paragraphNum - offset - 1) {\n                    paragraphs.add(TextParagraph(0))\n                }\n                paragraphs[line.paragraphNum - offset - 1].textLines.add(line)\n            }\n            return paragraphs\n        }\n\n    fun addLine(line: TextLine) {\n        line.textPage = this\n        textLines.add(line)\n    }\n\n    fun getLine(index: Int): TextLine {\n        return textLines.getOrElse(index) {\n            textLines.last()\n        }\n    }\n\n    /**\n     * 底部对齐更新行位置\n     */\n    fun upLinesPosition() {\n        if (!ReadBookConfig.textBottomJustify) return\n        if (textLines.size <= 1) return\n        if (leftLineSize == 0) {\n            leftLineSize = lineSize\n        }\n        ChapterProvider.run {\n            val lastLine = textLines[leftLineSize - 1]\n            if (lastLine.isImage) return@run\n            val lastLineHeight = with(lastLine) { lineBottom - lineTop }\n            val pageHeight = lastLine.lineBottom + contentPaintTextHeight * lineSpacingExtra\n            if (visibleHeight - pageHeight >= lastLineHeight) return@run\n            val surplus = (visibleBottom - lastLine.lineBottom)\n            if (surplus == 0f) return@run\n            height += surplus\n            val tj = surplus / (leftLineSize - 1)\n            for (i in 1 until leftLineSize) {\n                val line = textLines[i]\n                line.lineTop += tj * i\n                line.lineBase += tj * i\n                line.lineBottom += tj * i\n            }\n        }\n        if (leftLineSize == lineSize) return\n        ChapterProvider.run {\n            val lastLine = textLines.last()\n            if (lastLine.isImage) return@run\n            val lastLineHeight = with(lastLine) { lineBottom - lineTop }\n            val pageHeight = lastLine.lineBottom + contentPaintTextHeight * lineSpacingExtra\n            if (visibleHeight - pageHeight >= lastLineHeight) return@run\n            val surplus = (visibleBottom - lastLine.lineBottom)\n            if (surplus == 0f) return@run\n            val tj = surplus / (textLines.size - leftLineSize - 1)\n            for (i in leftLineSize + 1 until textLines.size) {\n                val line = textLines[i]\n                val surplusIndex = i - leftLineSize\n                line.lineTop += tj * surplusIndex\n                line.lineBase += tj * surplusIndex\n                line.lineBottom += tj * surplusIndex\n            }\n        }\n    }\n\n    /**\n     * 计算文字位置,只用作单页面内容\n     */\n    @Suppress(\"DEPRECATION\")\n    fun format(): TextPage {\n        if (textLines.isEmpty()) isMsgPage = true\n        if (isMsgPage && ChapterProvider.viewWidth > 0) {\n            textLines.clear()\n            val visibleWidth = ChapterProvider.visibleRight - ChapterProvider.paddingLeft\n            val paint = ChapterProvider.contentPaint\n            val layout = StaticLayout(\n                text, paint, visibleWidth,\n                Layout.Alignment.ALIGN_NORMAL, 1f, 0f, false\n            )\n            val letterSpacing = paint.letterSpacing * paint.textSize\n            var y = (ChapterProvider.visibleHeight - layout.height) / 2f\n            if (y < 0) y = 0f\n            for (lineIndex in 0 until layout.lineCount) {\n                val textLine = TextLine()\n                textLine.lineTop = ChapterProvider.paddingTop + y + layout.getLineTop(lineIndex)\n                textLine.lineBase =\n                    ChapterProvider.paddingTop + y + layout.getLineBaseline(lineIndex)\n                textLine.lineBottom =\n                    ChapterProvider.paddingTop + y + layout.getLineBottom(lineIndex)\n                var x = ChapterProvider.paddingLeft +\n                        (visibleWidth - layout.getLineMax(lineIndex)) / 2\n                textLine.text =\n                    text.substring(layout.getLineStart(lineIndex), layout.getLineEnd(lineIndex))\n                for (i in textLine.text.indices) {\n                    val char = textLine.text[i].toString()\n                    var cw = StaticLayout.getDesiredWidth(char, paint)\n                    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM) {\n                        cw += letterSpacing\n                    }\n                    val x1 = x + cw\n                    textLine.addColumn(\n                        TextColumn(start = x, end = x1, char)\n                    )\n                    x = x1\n                }\n                addLine(textLine)\n            }\n            height = ChapterProvider.visibleHeight.toFloat()\n            upRenderHeight()\n            invalidate()\n            isCompleted = true\n        }\n        return this\n    }\n\n    /**\n     * 移除朗读标志\n     */\n    fun removePageAloudSpan(): TextPage {\n        if (!hasReadAloudSpan) {\n            return this\n        }\n        hasReadAloudSpan = false\n        for (i in textLines.indices) {\n            textLines[i].isReadAloud = false\n        }\n        return this\n    }\n\n    /**\n     * 更新朗读标志\n     * @param aloudSpanStart 朗读文字开始位置\n     */\n    fun upPageAloudSpan(aloudSpanStart: Int) {\n        removePageAloudSpan()\n        var lineStart = 0\n        for (index in textLines.indices) {\n            val textLine = textLines[index]\n            val lineLength = textLine.text.length + if (textLine.isParagraphEnd) 1 else 0\n            if (aloudSpanStart >= lineStart && aloudSpanStart < lineStart + lineLength) {\n                for (i in index - 1 downTo 0) {\n                    if (textLines[i].isParagraphEnd) {\n                        break\n                    } else {\n                        textLines[i].isReadAloud = true\n                    }\n                }\n                for (i in index until textLines.size) {\n                    if (textLines[i].isParagraphEnd) {\n                        textLines[i].isReadAloud = true\n                        break\n                    } else {\n                        textLines[i].isReadAloud = true\n                    }\n                }\n                break\n            }\n            lineStart += lineLength\n        }\n    }\n\n    /**\n     * 阅读进度\n     */\n    val readProgress: String\n        get() {\n            val df = readProgressFormatter\n            if (chapterSize == 0 || pageSize == 0 && chapterIndex == 0) {\n                return \"0.0%\"\n            } else if (pageSize == 0) {\n                return df.format((chapterIndex + 1.0f) / chapterSize.toDouble())\n            }\n            var percent =\n                df.format(chapterIndex * 1.0f / chapterSize + 1.0f / chapterSize * (index + 1) / pageSize.toDouble())\n            if (percent == \"100.0%\" && (chapterIndex + 1 != chapterSize || index + 1 != pageSize)) {\n                percent = \"99.9%\"\n            }\n            return percent\n        }\n\n    /**\n     * 根据行和列返回字符在本页的位置\n     * @param lineIndex 字符在第几行\n     * @param columnIndex 字符在第几列\n     * @return 字符在本页位置\n     */\n    fun getPosByLineColumn(lineIndex: Int, columnIndex: Int): Int {\n        var length = 0\n        val maxIndex = min(lineIndex, lineSize - 1)\n        for (index in 0 until maxIndex) {\n            length += textLines[index].charSize\n            if (textLines[index].isParagraphEnd) {\n                length++\n            }\n        }\n        val columns = textLines[maxIndex].columns\n        for (index in 0 until columnIndex) {\n            val column = columns[index]\n            if (column is TextColumn) {\n                length += column.charData.length\n            }\n        }\n        return length\n    }\n\n    /**\n     * @return 页面所在章节\n     */\n    fun getTextChapter(): TextChapter {\n        return textChapter\n    }\n\n    /**\n     * 判断章节字符位置是否在这一页中\n     *\n     * @param chapterPos 章节字符位置\n     * @return\n     */\n    fun containPos(chapterPos: Int): Boolean {\n        val line = lines.first()\n        val startPos = line.chapterPosition\n        val endPos = startPos + charSize\n        return chapterPos in startPos..<endPos\n    }\n\n    fun draw(view: ContentTextView, canvas: Canvas, relativeOffset: Float) {\n        if (AppConfig.optimizeRender) {\n            render(view)\n            canvas.withTranslation(0f, relativeOffset) {\n                canvasRecorder.draw(this)\n            }\n        } else {\n            canvas.withTranslation(0f, relativeOffset) {\n                drawPage(view, this)\n            }\n        }\n    }\n\n    private fun drawDebugInfo(canvas: Canvas) {\n        ChapterProvider.run {\n            val paint = PaintPool.obtain()\n            paint.style = Paint.Style.STROKE\n            canvas.drawRect(\n                paddingLeft.toFloat(),\n                0f,\n                (paddingLeft + visibleWidth).toFloat(),\n                height - 1.dpToPx(),\n                paint\n            )\n            PaintPool.recycle(paint)\n        }\n    }\n\n    private fun drawPage(view: ContentTextView, canvas: Canvas) {\n        for (i in lines.indices) {\n            val line = lines[i]\n            canvas.withTranslation(0f, line.lineTop) {\n                line.draw(view, this)\n            }\n        }\n    }\n\n    fun render(view: ContentTextView): Boolean {\n        if (!isCompleted) return false\n        return canvasRecorder.recordIfNeeded(view.width, renderHeight) {\n            drawPage(view, this)\n        }\n    }\n\n    fun invalidate() {\n        canvasRecorder.invalidate()\n    }\n\n    fun invalidateAll() {\n        for (i in lines.indices) {\n            lines[i].invalidateSelf()\n        }\n        invalidate()\n    }\n\n    fun recycleRecorders() {\n        canvasRecorder.recycle()\n        for (i in lines.indices) {\n            lines[i].recycleRecorder()\n        }\n    }\n\n    fun hasImageOrEmpty(): Boolean {\n        return textLines.any { it.isImage } || textLines.isEmpty()\n    }\n\n    fun upRenderHeight() {\n        renderHeight = ceil(lines.last().lineBottom).toInt()\n        if (leftLineSize > 0 && leftLineSize != lines.size) {\n            val leftHeight = ceil(lines[leftLineSize - 1].lineBottom).toInt()\n            renderHeight = max(renderHeight, leftHeight)\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/book/read/page/entities/TextParagraph.kt",
    "content": "package io.legado.app.ui.book.read.page.entities\n\n@Suppress(\"unused\", \"MemberVisibilityCanBePrivate\")\ndata class TextParagraph(\n    var num: Int,\n    val textLines: ArrayList<TextLine> = arrayListOf(),\n) {\n    val text: String get() = textLines.joinToString(\"\") { it.text }\n    val length: Int get() = text.length\n    val firstLine: TextLine get() = textLines.first()\n    val lastLine: TextLine get() = textLines.last()\n    val chapterIndices: IntRange get() = firstLine.chapterPosition..lastLine.chapterPosition + lastLine.charSize\n    val chapterPosition: Int get() = firstLine.chapterPosition\n    val realNum: Int get() = firstLine.paragraphNum\n    val isParagraphEnd: Boolean get() = lastLine.isParagraphEnd\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/book/read/page/entities/TextPos.kt",
    "content": "package io.legado.app.ui.book.read.page.entities\n\nimport androidx.annotation.Keep\n\n/**\n * 位置信息\n */\n@Keep\n@Suppress(\"unused\")\ndata class TextPos(\n    var relativePagePos: Int,\n    var lineIndex: Int,\n    var columnIndex: Int,\n) {\n\n    fun upData(\n        relativePos: Int,\n        lineIndex: Int,\n        charIndex: Int,\n    ) {\n        this.relativePagePos = relativePos\n        this.lineIndex = lineIndex\n        this.columnIndex = charIndex\n    }\n\n    fun upData(pos: TextPos) {\n        relativePagePos = pos.relativePagePos\n        lineIndex = pos.lineIndex\n        columnIndex = pos.columnIndex\n    }\n\n    fun compare(pos: TextPos): Int {\n        return when {\n            relativePagePos < pos.relativePagePos -> -3\n            relativePagePos > pos.relativePagePos -> 3\n            lineIndex < pos.lineIndex -> -2\n            lineIndex > pos.lineIndex -> 2\n            columnIndex < pos.columnIndex -> -1\n            columnIndex > pos.columnIndex -> 1\n            else -> 0\n        }\n    }\n\n    fun compare(relativePos: Int, lineIndex: Int, charIndex: Int): Int {\n        return when {\n            this.relativePagePos < relativePos -> -3\n            this.relativePagePos > relativePos -> 3\n            this.lineIndex < lineIndex -> -2\n            this.lineIndex > lineIndex -> 2\n            this.columnIndex < charIndex -> -1\n            this.columnIndex > charIndex -> 1\n            else -> 0\n        }\n    }\n\n    fun reset() {\n        relativePagePos = 0\n        lineIndex = -1\n        columnIndex = -1\n    }\n\n    fun isSelected(): Boolean {\n        return lineIndex >= 0 && columnIndex >= 0\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/book/read/page/entities/column/BaseColumn.kt",
    "content": "package io.legado.app.ui.book.read.page.entities.column\n\nimport android.graphics.Canvas\nimport io.legado.app.ui.book.read.page.ContentTextView\nimport io.legado.app.ui.book.read.page.entities.TextLine\n\n/**\n * 列基类\n */\ninterface BaseColumn {\n    var start: Float\n    var end: Float\n    var textLine: TextLine\n\n    fun draw(view: ContentTextView, canvas: Canvas)\n\n    fun isTouch(x: Float): Boolean {\n        return x > start && x < end\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/book/read/page/entities/column/ButtonColumn.kt",
    "content": "package io.legado.app.ui.book.read.page.entities.column\n\nimport android.graphics.Canvas\nimport androidx.annotation.Keep\nimport io.legado.app.ui.book.read.page.ContentTextView\nimport io.legado.app.ui.book.read.page.entities.TextLine\nimport io.legado.app.ui.book.read.page.entities.TextLine.Companion.emptyTextLine\n\n\n/**\n * 按钮列\n */\n@Keep\ndata class ButtonColumn(\n    override var start: Float,\n    override var end: Float,\n) : BaseColumn {\n    override var textLine: TextLine = emptyTextLine\n    override fun draw(view: ContentTextView, canvas: Canvas) {\n\n    }\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/book/read/page/entities/column/ImageColumn.kt",
    "content": "package io.legado.app.ui.book.read.page.entities.column\n\nimport android.graphics.Canvas\nimport android.graphics.RectF\nimport androidx.annotation.Keep\nimport io.legado.app.model.ImageProvider\nimport io.legado.app.model.ReadBook\nimport io.legado.app.ui.book.read.page.ContentTextView\nimport io.legado.app.ui.book.read.page.entities.TextLine\nimport io.legado.app.ui.book.read.page.entities.TextLine.Companion.emptyTextLine\nimport io.legado.app.utils.toastOnUi\nimport splitties.init.appCtx\n\n/**\n * 图片列\n */\n@Keep\ndata class ImageColumn(\n    override var start: Float,\n    override var end: Float,\n    var src: String\n) : BaseColumn {\n\n    override var textLine: TextLine = emptyTextLine\n    override fun draw(view: ContentTextView, canvas: Canvas) {\n        val book = ReadBook.book ?: return\n\n        val height = textLine.height\n\n        val bitmap = ImageProvider.getImage(\n            book,\n            src,\n            (end - start).toInt(),\n            height.toInt()\n        )\n\n        val rectF = if (textLine.isImage) {\n            RectF(start, 0f, end, height)\n        } else {\n            /*以宽度为基准保持图片的原始比例叠加，当div为负数时，允许高度比字符更高*/\n            val h = (end - start) / bitmap.width * bitmap.height\n            val div = (height - h) / 2\n            RectF(start, div, end, height - div)\n        }\n        kotlin.runCatching {\n            canvas.drawBitmap(bitmap, null, rectF, view.imagePaint)\n        }.onFailure { e ->\n            appCtx.toastOnUi(e.localizedMessage)\n        }\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/book/read/page/entities/column/ReviewColumn.kt",
    "content": "package io.legado.app.ui.book.read.page.entities.column\n\nimport android.graphics.Canvas\nimport android.graphics.Paint\nimport android.graphics.Path\nimport androidx.annotation.Keep\nimport io.legado.app.ui.book.read.page.ContentTextView\nimport io.legado.app.ui.book.read.page.entities.TextLine\nimport io.legado.app.ui.book.read.page.entities.TextLine.Companion.emptyTextLine\nimport io.legado.app.ui.book.read.page.provider.ChapterProvider\n\n/**\n * 评论按钮列\n */\n@Keep\ndata class ReviewColumn(\n    override var start: Float,\n    override var end: Float,\n    val count: Int = 0\n) : BaseColumn {\n\n    override var textLine: TextLine = emptyTextLine\n    override fun draw(view: ContentTextView, canvas: Canvas) {\n        val textPaint = if (textLine.isTitle) {\n            ChapterProvider.titlePaint\n        } else {\n            ChapterProvider.contentPaint\n        }\n        drawToCanvas(canvas, textLine.lineBase, textPaint.textSize)\n    }\n\n    val countText by lazy {\n        if (count > 999) {\n            return@lazy \"999\"\n        }\n        return@lazy count.toString()\n    }\n\n    val path by lazy { Path() }\n\n    fun drawToCanvas(canvas: Canvas, baseLine: Float, height: Float) {\n        if (count == 0) return\n        path.reset()\n        path.moveTo(start + 1, baseLine - height * 2 / 5)\n        path.lineTo(start + height / 6, baseLine - height * 0.55f)\n        path.lineTo(start + height / 6, baseLine - height * 0.8f)\n        path.lineTo(end - 1, baseLine - height * 0.8f)\n        path.lineTo(end - 1, baseLine)\n        path.lineTo(start + height / 6, baseLine)\n        path.lineTo(start + height / 6, baseLine - height / 4)\n        path.close()\n        val reviewPaint = ChapterProvider.reviewPaint\n        reviewPaint.style = Paint.Style.STROKE\n        canvas.drawPath(path, reviewPaint)\n        reviewPaint.style = Paint.Style.FILL\n        canvas.drawText(\n            countText,\n            (start + height / 9 + end) / 2,\n            baseLine - height * 0.23f,\n            reviewPaint\n        )\n    }\n\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/book/read/page/entities/column/TextColumn.kt",
    "content": "package io.legado.app.ui.book.read.page.entities.column\n\nimport android.graphics.Canvas\nimport android.os.Build\nimport androidx.annotation.Keep\nimport io.legado.app.help.config.ReadBookConfig\nimport io.legado.app.lib.theme.ThemeStore\nimport io.legado.app.ui.book.read.page.ContentTextView\nimport io.legado.app.ui.book.read.page.entities.TextLine\nimport io.legado.app.ui.book.read.page.entities.TextLine.Companion.emptyTextLine\nimport io.legado.app.ui.book.read.page.provider.ChapterProvider\n\n/**\n * 文字列\n */\n@Keep\ndata class TextColumn(\n    override var start: Float,\n    override var end: Float,\n    val charData: String,\n) : BaseColumn {\n\n    override var textLine: TextLine = emptyTextLine\n\n    var selected: Boolean = false\n        set(value) {\n            if (field != value) {\n                textLine.invalidate()\n            }\n            field = value\n        }\n    var isSearchResult: Boolean = false\n        set(value) {\n            if (field != value) {\n                textLine.invalidate()\n                if (value) {\n                    textLine.searchResultColumnCount++\n                } else {\n                    textLine.searchResultColumnCount--\n                }\n            }\n            field = value\n        }\n\n    override fun draw(view: ContentTextView, canvas: Canvas) {\n        val textPaint = if (textLine.isTitle) {\n            ChapterProvider.titlePaint\n        } else {\n            ChapterProvider.contentPaint\n        }\n        val textColor = if (textLine.isReadAloud || isSearchResult) {\n            ThemeStore.accentColor\n        } else {\n            ReadBookConfig.textColor\n        }\n        if (textPaint.color != textColor) {\n            textPaint.color = textColor\n        }\n        val y = textLine.lineBase - textLine.lineTop\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM) {\n            val letterSpacing = textPaint.letterSpacing * textPaint.textSize\n            val letterSpacingHalf = letterSpacing * 0.5f\n            canvas.drawText(charData, start + letterSpacingHalf, y, textPaint)\n        } else {\n            canvas.drawText(charData, start, y, textPaint)\n        }\n        if (selected) {\n            canvas.drawRect(start, 0f, end, textLine.height, view.selectedPaint)\n        }\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/book/read/page/provider/ChapterProvider.kt",
    "content": "package io.legado.app.ui.book.read.page.provider\n\nimport android.graphics.Paint.FontMetrics\nimport android.graphics.RectF\nimport android.graphics.Typeface\nimport android.net.Uri\nimport android.os.Build\nimport android.text.Layout\nimport android.text.StaticLayout\nimport android.text.TextPaint\nimport androidx.core.os.postDelayed\nimport io.legado.app.constant.AppLog\nimport io.legado.app.constant.AppPattern\nimport io.legado.app.constant.EventBus\nimport io.legado.app.data.entities.Book\nimport io.legado.app.data.entities.BookChapter\nimport io.legado.app.help.book.BookContent\nimport io.legado.app.help.config.AppConfig\nimport io.legado.app.help.config.ReadBookConfig\nimport io.legado.app.model.ImageProvider\nimport io.legado.app.model.ReadBook\nimport io.legado.app.ui.book.read.page.entities.TextChapter\nimport io.legado.app.ui.book.read.page.entities.TextLine\nimport io.legado.app.ui.book.read.page.entities.TextPage\nimport io.legado.app.ui.book.read.page.entities.column.ImageColumn\nimport io.legado.app.ui.book.read.page.entities.column.ReviewColumn\nimport io.legado.app.ui.book.read.page.entities.column.TextColumn\nimport io.legado.app.utils.RealPathUtil\nimport io.legado.app.utils.buildMainHandler\nimport io.legado.app.utils.dpToPx\nimport io.legado.app.utils.fastSum\nimport io.legado.app.utils.isContentScheme\nimport io.legado.app.utils.isPad\nimport io.legado.app.utils.postEvent\nimport io.legado.app.utils.spToPx\nimport io.legado.app.utils.splitNotBlank\nimport io.legado.app.utils.textHeight\nimport kotlinx.coroutines.CoroutineScope\nimport splitties.init.appCtx\nimport java.util.LinkedList\nimport java.util.Locale\n\n/**\n * 解析内容生成章节和页面\n */\n@Suppress(\"DEPRECATION\", \"ConstPropertyName\")\nobject ChapterProvider {\n    //用于图片字的替换\n    const val srcReplaceChar = \"▩\"\n\n    //用于评论按钮的替换\n    const val reviewChar = \"▨\"\n\n    const val indentChar = \"　\"\n\n    @JvmStatic\n    var viewWidth = 0\n        private set\n\n    @JvmStatic\n    var viewHeight = 0\n        private set\n\n    @JvmStatic\n    var paddingLeft = 0\n        private set\n\n    @JvmStatic\n    var paddingTop = 0\n        private set\n\n    @JvmStatic\n    var paddingRight = 0\n        private set\n\n    @JvmStatic\n    var paddingBottom = 0\n        private set\n\n    @JvmStatic\n    var visibleWidth = 0\n        private set\n\n    @JvmStatic\n    var visibleHeight = 0\n        private set\n\n    @JvmStatic\n    var visibleRight = 0\n        private set\n\n    @JvmStatic\n    var visibleBottom = 0\n        private set\n\n    @JvmStatic\n    var lineSpacingExtra = 0f\n        private set\n\n    @JvmStatic\n    var paragraphSpacing = 0\n        private set\n\n    @JvmStatic\n    var titleTopSpacing = 0\n        private set\n\n    @JvmStatic\n    var titleBottomSpacing = 0\n        private set\n\n    @JvmStatic\n    var indentCharWidth = 0f\n        private set\n\n    @JvmStatic\n    var titlePaintTextHeight = 0f\n        private set\n\n    @JvmStatic\n    var contentPaintTextHeight = 0f\n        private set\n\n    @JvmStatic\n    var titlePaintFontMetrics = FontMetrics()\n\n    @JvmStatic\n    var contentPaintFontMetrics = FontMetrics()\n\n    @JvmStatic\n    var typeface: Typeface? = Typeface.DEFAULT\n        private set\n\n    @JvmStatic\n    var titlePaint: TextPaint = TextPaint()\n\n    @JvmStatic\n    var contentPaint: TextPaint = TextPaint()\n\n    @JvmStatic\n    var reviewPaint: TextPaint = TextPaint()\n\n    @JvmStatic\n    var doublePage = false\n        private set\n\n    @JvmStatic\n    var visibleRect = RectF()\n\n    private val handler by lazy {\n        buildMainHandler()\n    }\n\n    private var upViewSizeRunnable: Runnable? = null\n\n    init {\n        upStyle()\n    }\n\n    /**\n     * 获取拆分完的章节数据\n     */\n    suspend fun getTextChapter(\n        book: Book,\n        bookChapter: BookChapter,\n        displayTitle: String,\n        bookContent: BookContent,\n        chapterSize: Int,\n    ): TextChapter {\n        val contents = bookContent.textList\n        val textPages = arrayListOf<TextPage>()\n        val stringBuilder = StringBuilder()\n        var absStartX = paddingLeft\n        var durY = 0f\n        textPages.add(TextPage())\n        if (ReadBookConfig.titleMode != 2 || bookChapter.isVolume) {\n            //标题非隐藏\n            displayTitle.splitNotBlank(\"\\n\").forEach { text ->\n                setTypeText(\n                    book, absStartX, durY,\n                    if (AppConfig.enableReview) text + reviewChar else text,\n                    textPages,\n                    stringBuilder,\n                    titlePaint,\n                    titlePaintTextHeight,\n                    titlePaintFontMetrics,\n                    isTitle = true,\n                    emptyContent = contents.isEmpty(),\n                    isVolumeTitle = bookChapter.isVolume\n                ).let {\n                    absStartX = it.first\n                    durY = it.second\n                }\n            }\n            textPages.last().lines.last().isParagraphEnd = true\n            stringBuilder.append(\"\\n\")\n            durY += titleBottomSpacing\n        }\n        contents.forEach { content ->\n            if (book.getImageStyle().equals(Book.imgStyleText, true)) {\n                //图片样式为文字嵌入类型\n                var text = content.replace(srcReplaceChar, \"▣\")\n                val srcList = LinkedList<String>()\n                val sb = StringBuffer()\n                val matcher = AppPattern.imgPattern.matcher(text)\n                while (matcher.find()) {\n                    matcher.group(1)?.let { src ->\n                        srcList.add(src)\n                        matcher.appendReplacement(sb, srcReplaceChar)\n                    }\n                }\n                matcher.appendTail(sb)\n                text = sb.toString()\n                setTypeText(\n                    book,\n                    absStartX,\n                    durY,\n                    text,\n                    textPages,\n                    stringBuilder,\n                    contentPaint,\n                    contentPaintTextHeight,\n                    contentPaintFontMetrics,\n                    srcList = srcList\n                ).let {\n                    absStartX = it.first\n                    durY = it.second\n                }\n            } else {\n                val matcher = AppPattern.imgPattern.matcher(content)\n                var start = 0\n                while (matcher.find()) {\n                    val text = content.substring(start, matcher.start())\n                    if (text.isNotBlank()) {\n                        setTypeText(\n                            book,\n                            absStartX,\n                            durY,\n                            text,\n                            textPages,\n                            stringBuilder,\n                            contentPaint,\n                            contentPaintTextHeight,\n                            contentPaintFontMetrics\n                        ).let {\n                            absStartX = it.first\n                            durY = it.second\n                        }\n                    }\n                    setTypeImage(\n                        book,\n                        matcher.group(1)!!,\n                        absStartX,\n                        durY,\n                        textPages,\n                        contentPaintTextHeight,\n                        stringBuilder,\n                        book.getImageStyle()\n                    ).let {\n                        absStartX = it.first\n                        durY = it.second\n                    }\n                    start = matcher.end()\n                }\n                if (start < content.length) {\n                    val text = content.substring(start, content.length)\n                    if (text.isNotBlank()) {\n                        setTypeText(\n                            book, absStartX, durY,\n                            if (AppConfig.enableReview) text + reviewChar else text,\n                            textPages,\n                            stringBuilder,\n                            contentPaint,\n                            contentPaintTextHeight,\n                            contentPaintFontMetrics\n                        ).let {\n                            absStartX = it.first\n                            durY = it.second\n                        }\n                    }\n                }\n            }\n            textPages.last().lines.last().isParagraphEnd = true\n            stringBuilder.append(\"\\n\")\n        }\n        val textPage = textPages.last()\n        val endPadding = 20.dpToPx()\n        val durYPadding = durY + endPadding\n        if (textPage.height < durYPadding) {\n            textPage.height = durYPadding\n        } else {\n            textPage.height += endPadding\n        }\n        textPage.text = stringBuilder.toString()\n        textPages.forEachIndexed { index, item ->\n            item.index = index\n            //item.pageSize = textPages.size\n            item.chapterIndex = bookChapter.index\n            item.chapterSize = chapterSize\n            item.title = displayTitle\n            item.doublePage = doublePage\n            item.paddingTop = paddingTop\n            item.upLinesPosition()\n        }\n\n        return TextChapter(\n            bookChapter,\n            bookChapter.index, displayTitle,\n            //textPages,\n            chapterSize,\n            bookContent.sameTitleRemoved,\n            bookChapter.isVip,\n            bookChapter.isPay,\n            bookContent.effectiveReplaceRules\n        )\n    }\n\n    fun getTextChapterAsync(\n        scope: CoroutineScope,\n        book: Book,\n        bookChapter: BookChapter,\n        displayTitle: String,\n        bookContent: BookContent,\n        chapterSize: Int,\n    ): TextChapter {\n\n        val textChapter = TextChapter(\n            bookChapter,\n            bookChapter.index, displayTitle,\n            chapterSize,\n            bookContent.sameTitleRemoved,\n            bookChapter.isVip,\n            bookChapter.isPay,\n            bookContent.effectiveReplaceRules\n        ).apply {\n            createLayout(scope, book, bookContent)\n        }\n\n        return textChapter\n    }\n\n    /**\n     * 排版图片\n     */\n    private suspend fun setTypeImage(\n        book: Book,\n        src: String,\n        x: Int,\n        y: Float,\n        textPages: ArrayList<TextPage>,\n        textHeight: Float,\n        stringBuilder: StringBuilder,\n        imageStyle: String?,\n    ): Pair<Int, Float> {\n        var absStartX = x\n        var durY = y\n        val size = ImageProvider.getImageSize(book, src, ReadBook.bookSource)\n        if (size.width > 0 && size.height > 0) {\n            if (durY > visibleHeight) {\n                val textPage = textPages.last()\n                if (textPage.height < durY) {\n                    textPage.height = durY\n                }\n                textPage.text = stringBuilder.toString().ifEmpty { \"本页无文字内容\" }\n                stringBuilder.clear()\n                textPages.add(TextPage())\n                durY = 0f\n            }\n            var height = size.height\n            var width = size.width\n            when (imageStyle?.uppercase(Locale.ROOT)) {\n                Book.imgStyleFull -> {\n                    width = visibleWidth\n                    height = size.height * visibleWidth / size.width\n                }\n\n                Book.imgStyleSingle -> {\n                    width = visibleWidth\n                    height = size.height * visibleWidth / size.width\n                    if (height > visibleHeight) {\n                        width = width * visibleHeight / height\n                        height = visibleHeight\n                    }\n                    if (durY > 0f) {\n                        val textPage = textPages.last()\n                        if (doublePage && absStartX < viewWidth / 2) {\n                            //当前页面左列结束\n                            textPage.leftLineSize = textPage.lineSize\n                            absStartX = viewWidth / 2 + paddingLeft\n                        } else {\n                            //当前页面结束\n                            if (textPage.leftLineSize == 0) {\n                                textPage.leftLineSize = textPage.lineSize\n                            }\n                            textPage.text = stringBuilder.toString().ifEmpty { \"本页无文字内容\" }\n                            stringBuilder.clear()\n                            textPages.add(TextPage())\n                        }\n                        // 双页的 durY 不正确，可能会小于实际高度\n                        if (textPage.height < durY) {\n                            textPage.height = durY\n                        }\n                        durY = 0f\n                    }\n\n                    // 图片竖直方向居中：调整 Y 坐标\n                    if (height < visibleHeight) {\n                        val adjustHeight = (visibleHeight - height) / 2f\n                        durY = adjustHeight // 将 Y 坐标设置为居中位置\n                    }\n                }\n\n                else -> {\n                    if (size.width > visibleWidth) {\n                        height = size.height * visibleWidth / size.width\n                        width = visibleWidth\n                    }\n                    if (height > visibleHeight) {\n                        width = width * visibleHeight / height\n                        height = visibleHeight\n                    }\n                    if (durY + height > visibleHeight) {\n                        val textPage = textPages.last()\n                        if (doublePage && absStartX < viewWidth / 2) {\n                            //当前页面左列结束\n                            textPage.leftLineSize = textPage.lineSize\n                            absStartX = viewWidth / 2 + paddingLeft\n                        } else {\n                            //当前页面结束\n                            if (textPage.leftLineSize == 0) {\n                                textPage.leftLineSize = textPage.lineSize\n                            }\n                            textPage.text = stringBuilder.toString().ifEmpty { \"本页无文字内容\" }\n                            stringBuilder.clear()\n                            textPages.add(TextPage())\n                        }\n                        // 双页的 durY 不正确，可能会小于实际高度\n                        if (textPage.height < durY) {\n                            textPage.height = durY\n                        }\n                        durY = 0f\n                    }\n                }\n            }\n            val textLine = TextLine(isImage = true)\n            textLine.lineTop = durY + paddingTop\n            durY += height\n            textLine.lineBottom = durY + paddingTop\n            val (start, end) = if (visibleWidth > width) {\n                val adjustWidth = (visibleWidth - width) / 2f\n                Pair(adjustWidth, adjustWidth + width)\n            } else {\n                Pair(0f, width.toFloat())\n            }\n            textLine.addColumn(\n                ImageColumn(start = x + start, end = x + end, src = src)\n            )\n            calcTextLinePosition(textPages, textLine, stringBuilder.length)\n            stringBuilder.append(\" \") // 确保翻页时索引计算正确\n            textPages.last().addLine(textLine)\n        }\n        return absStartX to durY + textHeight * paragraphSpacing / 10f\n    }\n\n    /**\n     * 排版文字\n     */\n    private suspend fun setTypeText(\n        book: Book,\n        x: Int,\n        y: Float,\n        text: String,\n        textPages: ArrayList<TextPage>,\n        stringBuilder: StringBuilder,\n        textPaint: TextPaint,\n        textHeight: Float,\n        fontMetrics: FontMetrics,\n        isTitle: Boolean = false,\n        emptyContent: Boolean = false,\n        isVolumeTitle: Boolean = false,\n        srcList: LinkedList<String>? = null\n    ): Pair<Int, Float> {\n        var absStartX = x\n        val layout = if (ReadBookConfig.useZhLayout) {\n            ZhLayout(\n                text, textPaint, visibleWidth, emptyList(), emptyList(),\n                ReadBookConfig.paragraphIndent.length\n            )\n        } else {\n            StaticLayout(text, textPaint, visibleWidth, Layout.Alignment.ALIGN_NORMAL, 0f, 0f, true)\n        }\n        var durY = when {\n            //标题y轴居中\n            emptyContent && textPages.size == 1 -> {\n                val textPage = textPages.last()\n                if (textPage.lineSize == 0) {\n                    val ty = (visibleHeight - layout.lineCount * textHeight) / 2\n                    if (ty > titleTopSpacing) ty else titleTopSpacing.toFloat()\n                } else {\n                    var textLayoutHeight = layout.lineCount * textHeight\n                    val fistLine = textPage.getLine(0)\n                    if (fistLine.lineTop < textLayoutHeight + titleTopSpacing) {\n                        textLayoutHeight = fistLine.lineTop - titleTopSpacing\n                    }\n                    textPage.lines.forEach {\n                        it.lineTop -= textLayoutHeight\n                        it.lineBase -= textLayoutHeight\n                        it.lineBottom -= textLayoutHeight\n                    }\n                    y - textLayoutHeight\n                }\n            }\n\n            isTitle && textPages.size == 1 && textPages.last().lines.isEmpty() ->\n                y + titleTopSpacing\n\n            else -> y\n        }\n        for (lineIndex in 0 until layout.lineCount) {\n            val textLine = TextLine(isTitle = isTitle)\n            if (durY + textHeight > visibleHeight) {\n                val textPage = textPages.last()\n                if (doublePage && absStartX < viewWidth / 2) {\n                    //当前页面左列结束\n                    textPage.leftLineSize = textPage.lineSize\n                    absStartX = viewWidth / 2 + paddingLeft\n                } else {\n                    //当前页面结束,设置各种值\n                    if (textPage.leftLineSize == 0) {\n                        textPage.leftLineSize = textPage.lineSize\n                    }\n                    textPage.text = stringBuilder.toString()\n                    //新建页面\n                    textPages.add(TextPage())\n                    stringBuilder.clear()\n                    absStartX = paddingLeft\n                }\n                if (textPage.height < durY) {\n                    textPage.height = durY\n                }\n                durY = 0f\n            }\n            val lineStart = layout.getLineStart(lineIndex)\n            val lineEnd = layout.getLineEnd(lineIndex)\n            val lineText = text.substring(lineStart, lineEnd)\n            val (words, widths) = measureTextSplit(lineText, textPaint)\n            val desiredWidth = widths.fastSum()\n            when {\n                lineIndex == 0 && layout.lineCount > 1 && !isTitle -> {\n                    //第一行 非标题\n                    textLine.text = lineText\n                    addCharsToLineFirst(\n                        book, absStartX, textLine, words,\n                        desiredWidth, widths, srcList\n                    )\n                }\n\n                lineIndex == layout.lineCount - 1 -> {\n                    //最后一行\n                    textLine.text = lineText\n                    //标题x轴居中\n                    val startX = if (\n                        isTitle &&\n                        (ReadBookConfig.isMiddleTitle || emptyContent || isVolumeTitle)\n                    ) {\n                        (visibleWidth - desiredWidth) / 2\n                    } else {\n                        0f\n                    }\n                    addCharsToLineNatural(\n                        book, absStartX, textLine, words,\n                        startX, !isTitle && lineIndex == 0, widths, srcList\n                    )\n                }\n\n                else -> {\n                    if (\n                        isTitle &&\n                        (ReadBookConfig.isMiddleTitle || emptyContent || isVolumeTitle)\n                    ) {\n                        //标题居中\n                        val startX = (visibleWidth - desiredWidth) / 2\n                        addCharsToLineNatural(\n                            book, absStartX, textLine, words,\n                            startX, false, widths, srcList\n                        )\n                    } else {\n                        //中间行\n                        textLine.text = lineText\n                        addCharsToLineMiddle(\n                            book, absStartX, textLine, words,\n                            desiredWidth, 0f, widths, srcList\n                        )\n                    }\n                }\n            }\n            if (doublePage) {\n                textLine.isLeftLine = absStartX < viewWidth / 2\n            }\n            calcTextLinePosition(textPages, textLine, stringBuilder.length)\n            stringBuilder.append(lineText)\n            textLine.upTopBottom(durY, textHeight, fontMetrics)\n            val textPage = textPages.last()\n            textPage.addLine(textLine)\n            durY += textHeight * lineSpacingExtra\n            if (textPage.height < durY) {\n                textPage.height = durY\n            }\n        }\n        durY += textHeight * paragraphSpacing / 10f\n        return Pair(absStartX, durY)\n    }\n\n    private fun calcTextLinePosition(\n        textPages: ArrayList<TextPage>,\n        textLine: TextLine,\n        sbLength: Int\n    ) {\n        val lastLine = textPages.last().lines.lastOrNull { it.paragraphNum > 0 }\n            ?: textPages.getOrNull(\n                textPages.lastIndex - 1\n            )?.lines?.lastOrNull { it.paragraphNum > 0 }\n        val paragraphNum = when {\n            lastLine == null -> 1\n            lastLine.isParagraphEnd -> lastLine.paragraphNum + 1\n            else -> lastLine.paragraphNum\n        }\n        textLine.paragraphNum = paragraphNum\n        textLine.chapterPosition =\n            (textPages.getOrNull(textPages.lastIndex - 1)?.lines?.lastOrNull()?.run {\n                chapterPosition + charSize + if (isParagraphEnd) 1 else 0\n            } ?: 0) + sbLength\n        textLine.pagePosition = sbLength\n    }\n\n    /**\n     * 有缩进,两端对齐\n     */\n    private suspend fun addCharsToLineFirst(\n        book: Book,\n        absStartX: Int,\n        textLine: TextLine,\n        words: List<String>,\n        /**自然排版长度**/\n        desiredWidth: Float,\n        textWidths: List<Float>,\n        srcList: LinkedList<String>?\n    ) {\n        var x = 0f\n        if (!ReadBookConfig.textFullJustify) {\n            addCharsToLineNatural(\n                book, absStartX, textLine, words,\n                x, true, textWidths, srcList\n            )\n            return\n        }\n        val bodyIndent = ReadBookConfig.paragraphIndent\n        for (i in bodyIndent.indices) {\n            val x1 = x + indentCharWidth\n            textLine.addColumn(\n                TextColumn(\n                    charData = indentChar,\n                    start = absStartX + x,\n                    end = absStartX + x1\n                )\n            )\n            x = x1\n            textLine.indentWidth = x\n        }\n        if (words.size > bodyIndent.length) {\n            val text1 = words.subList(bodyIndent.length, words.size)\n            val textWidths1 = textWidths.subList(bodyIndent.length, textWidths.size)\n            addCharsToLineMiddle(\n                book, absStartX, textLine, text1,\n                desiredWidth, x, textWidths1, srcList\n            )\n        }\n    }\n\n    /**\n     * 无缩进,两端对齐\n     */\n    private suspend fun addCharsToLineMiddle(\n        book: Book,\n        absStartX: Int,\n        textLine: TextLine,\n        words: List<String>,\n        /**自然排版长度**/\n        desiredWidth: Float,\n        /**起始x坐标**/\n        startX: Float,\n        textWidths: List<Float>,\n        srcList: LinkedList<String>?\n    ) {\n        if (!ReadBookConfig.textFullJustify) {\n            addCharsToLineNatural(\n                book, absStartX, textLine, words,\n                startX, false, textWidths, srcList\n            )\n            return\n        }\n        val residualWidth = visibleWidth - desiredWidth\n        val spaceSize = words.count { it == \" \" }\n        if (spaceSize > 1) {\n            val d = residualWidth / spaceSize\n            var x = startX\n            for (index in words.indices) {\n                val char = words[index]\n                val cw = textWidths[index]\n                val x1 = if (char == \" \") {\n                    if (index != words.lastIndex) (x + cw + d) else (x + cw)\n                } else {\n                    (x + cw)\n                }\n                addCharToLine(\n                    book, absStartX, textLine, char,\n                    x, x1, index + 1 == words.size, srcList\n                )\n                x = x1\n            }\n        } else {\n            val gapCount: Int = words.lastIndex\n            val d = residualWidth / gapCount\n            var x = startX\n            for (index in words.indices) {\n                val char = words[index]\n                val cw = textWidths[index]\n                val x1 = if (index != words.lastIndex) (x + cw + d) else (x + cw)\n                addCharToLine(\n                    book, absStartX, textLine, char,\n                    x, x1, index + 1 == words.size, srcList\n                )\n                x = x1\n            }\n        }\n        exceed(absStartX, textLine, words)\n    }\n\n    /**\n     * 自然排列\n     */\n    private suspend fun addCharsToLineNatural(\n        book: Book,\n        absStartX: Int,\n        textLine: TextLine,\n        words: List<String>,\n        startX: Float,\n        hasIndent: Boolean,\n        textWidths: List<Float>,\n        srcList: LinkedList<String>?\n    ) {\n        val indentLength = ReadBookConfig.paragraphIndent.length\n        var x = startX\n        for (index in words.indices) {\n            val char = words[index]\n            val cw = textWidths[index]\n            val x1 = x + cw\n            addCharToLine(book, absStartX, textLine, char, x, x1, index + 1 == words.size, srcList)\n            x = x1\n            if (hasIndent && index == indentLength - 1) {\n                textLine.indentWidth = x\n            }\n        }\n        exceed(absStartX, textLine, words)\n    }\n\n    /**\n     * 添加字符\n     */\n    private suspend fun addCharToLine(\n        book: Book,\n        absStartX: Int,\n        textLine: TextLine,\n        char: String,\n        xStart: Float,\n        xEnd: Float,\n        isLineEnd: Boolean,\n        srcList: LinkedList<String>?\n    ) {\n        val column = when {\n            srcList != null && char == srcReplaceChar -> {\n                val src = srcList.removeFirst()\n                ImageProvider.cacheImage(book, src, ReadBook.bookSource)\n                ImageColumn(\n                    start = absStartX + xStart,\n                    end = absStartX + xEnd,\n                    src = src\n                )\n            }\n\n            isLineEnd && char == reviewChar -> {\n                ReviewColumn(\n                    start = absStartX + xStart,\n                    end = absStartX + xEnd,\n                    count = 100\n                )\n            }\n\n            else -> {\n                TextColumn(\n                    start = absStartX + xStart,\n                    end = absStartX + xEnd,\n                    charData = char\n                )\n            }\n        }\n        textLine.addColumn(column)\n    }\n\n    /**\n     * 超出边界处理\n     */\n    private fun exceed(absStartX: Int, textLine: TextLine, words: List<String>) {\n        val visibleEnd = absStartX + visibleWidth\n        val endX = textLine.columns.lastOrNull()?.end ?: return\n        if (endX > visibleEnd) {\n            val cc = (endX - visibleEnd) / words.size\n            for (i in 0..words.lastIndex) {\n                textLine.getColumnReverseAt(i).let {\n                    val py = cc * (words.size - i)\n                    it.start -= py\n                    it.end -= py\n                }\n            }\n        }\n    }\n\n    fun measureTextSplit(\n        text: String,\n        paint: TextPaint\n    ): Pair<ArrayList<String>, ArrayList<Float>> {\n        val length = text.length\n        val widthsArray = FloatArray(length)\n        paint.getTextWidths(text, widthsArray)\n        val clusterCount = widthsArray.count { it > 0f }\n        val widths = ArrayList<Float>(clusterCount)\n        val stringList = ArrayList<String>(clusterCount)\n        var i = 0\n        while (i < length) {\n            val clusterBaseIndex = i++\n            widths.add(widthsArray[clusterBaseIndex])\n            while (i < length && widthsArray[i] == 0f) {\n                i++\n            }\n            stringList.add(text.substring(clusterBaseIndex, i))\n        }\n        return stringList to widths\n    }\n\n    /**\n     * 更新样式\n     */\n    fun upStyle() {\n        typeface = getTypeface(ReadBookConfig.textFont)\n        getPaints(typeface).let {\n            titlePaint = it.first\n            contentPaint = it.second\n//            reviewPaint.color = contentPaint.color\n//            reviewPaint.textSize = contentPaint.textSize * 0.45f\n//            reviewPaint.textAlign = Paint.Align.CENTER\n        }\n        //间距\n        lineSpacingExtra = ReadBookConfig.lineSpacingExtra / 10f\n        paragraphSpacing = ReadBookConfig.paragraphSpacing\n        titleTopSpacing = ReadBookConfig.titleTopSpacing.dpToPx()\n        titleBottomSpacing = ReadBookConfig.titleBottomSpacing.dpToPx()\n        val bodyIndent = ReadBookConfig.paragraphIndent\n        indentCharWidth = if (bodyIndent.isNotEmpty()) {\n            var indentWidth = StaticLayout.getDesiredWidth(bodyIndent, contentPaint)\n            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM) {\n                indentWidth += contentPaint.letterSpacing * contentPaint.textSize\n            }\n            indentWidth / bodyIndent.length\n        } else {\n            0f\n        }\n        titlePaintTextHeight = titlePaint.textHeight\n        contentPaintTextHeight = contentPaint.textHeight\n        titlePaintFontMetrics = titlePaint.fontMetrics\n        contentPaintFontMetrics = contentPaint.fontMetrics\n        upLayout()\n    }\n\n    private fun getTypeface(fontPath: String): Typeface? {\n        return kotlin.runCatching {\n            when {\n                fontPath.isContentScheme() && Build.VERSION.SDK_INT >= Build.VERSION_CODES.O -> {\n                    appCtx.contentResolver\n                        .openFileDescriptor(Uri.parse(fontPath), \"r\")!!\n                        .use {\n                            Typeface.Builder(it.fileDescriptor).build()\n                        }\n                }\n\n                fontPath.isContentScheme() -> {\n                    Typeface.createFromFile(RealPathUtil.getPath(appCtx, Uri.parse(fontPath)))\n                }\n\n                fontPath.isNotEmpty() -> Typeface.createFromFile(fontPath)\n                else -> when (AppConfig.systemTypefaces) {\n                    1 -> Typeface.SERIF\n                    2 -> Typeface.MONOSPACE\n                    else -> Typeface.SANS_SERIF\n                }\n            }\n        }.getOrElse {\n            ReadBookConfig.textFont = \"\"\n            ReadBookConfig.save()\n            Typeface.SANS_SERIF\n        } ?: Typeface.DEFAULT\n    }\n\n    private fun getPaints(typeface: Typeface?): Pair<TextPaint, TextPaint> {\n        // 字体统一处理\n        val bold = Typeface.create(typeface, Typeface.BOLD)\n        val normal = Typeface.create(typeface, Typeface.NORMAL)\n        val (titleFont, textFont) = when (ReadBookConfig.textBold) {\n            1 -> {\n                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P)\n                    Pair(Typeface.create(typeface, 900, false), bold)\n                else\n                    Pair(bold, bold)\n            }\n\n            2 -> {\n                if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P)\n                    Pair(normal, Typeface.create(typeface, 300, false))\n                else\n                    Pair(normal, normal)\n            }\n\n            else -> Pair(bold, normal)\n        }\n\n        //标题\n        val tPaint = TextPaint()\n        tPaint.color = ReadBookConfig.textColor\n        tPaint.letterSpacing = ReadBookConfig.letterSpacing\n        tPaint.typeface = titleFont\n        tPaint.textSize = with(ReadBookConfig) { textSize + titleSize }.toFloat().spToPx()\n        tPaint.isAntiAlias = true\n        if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.Q && AppConfig.optimizeRender) {\n            tPaint.isLinearText = true\n        }\n        //正文\n        val cPaint = TextPaint()\n        cPaint.color = ReadBookConfig.textColor\n        cPaint.letterSpacing = ReadBookConfig.letterSpacing\n        cPaint.typeface = textFont\n        cPaint.textSize = ReadBookConfig.textSize.toFloat().spToPx()\n        cPaint.isAntiAlias = true\n        if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.Q && AppConfig.optimizeRender) {\n            cPaint.isLinearText = true\n        }\n        return Pair(tPaint, cPaint)\n    }\n\n    /**\n     * 更新View尺寸\n     */\n    fun upViewSize(width: Int, height: Int) {\n        if (width <= 0 || height <= 0) {\n            return\n        }\n        if (width != viewWidth || height != viewHeight) {\n            if (width == viewWidth) {\n                upViewSizeRunnable = handler.postDelayed(300) {\n                    upViewSizeRunnable = null\n                    notifyViewSizeChange(width, height)\n                }\n            } else {\n                notifyViewSizeChange(width, height)\n            }\n        } else if (upViewSizeRunnable != null) {\n            handler.removeCallbacks(upViewSizeRunnable!!)\n            upViewSizeRunnable = null\n        }\n    }\n\n    private fun notifyViewSizeChange(width: Int, height: Int) {\n        viewWidth = width\n        viewHeight = height\n        upLayout()\n        postEvent(EventBus.UP_CONFIG, arrayListOf(5))\n    }\n\n    /**\n     * 更新绘制尺寸\n     */\n    fun upLayout() {\n        when (AppConfig.doublePageHorizontal) {\n            \"0\" -> doublePage = false\n            \"1\" -> doublePage = true\n            \"2\" -> {\n                doublePage = (viewWidth > viewHeight)\n                        && ReadBook.pageAnim() != 3\n            }\n\n            \"3\" -> {\n                doublePage = (viewWidth > viewHeight || appCtx.isPad)\n                        && ReadBook.pageAnim() != 3\n            }\n        }\n\n        if (viewWidth <= 0 || viewHeight <= 0) {\n            return\n        }\n\n        paddingLeft = ReadBookConfig.paddingLeft.dpToPx()\n        paddingTop = ReadBookConfig.paddingTop.dpToPx()\n        paddingRight = ReadBookConfig.paddingRight.dpToPx()\n        paddingBottom = ReadBookConfig.paddingBottom.dpToPx()\n        visibleWidth = if (doublePage) {\n            viewWidth / 2 - paddingLeft - paddingRight\n        } else {\n            viewWidth - paddingLeft - paddingRight\n        }\n        //留1dp画最后一行下划线\n        visibleHeight = viewHeight - paddingTop - paddingBottom\n        visibleRight = viewWidth - paddingRight\n        visibleBottom = paddingTop + visibleHeight\n\n        if (paddingLeft >= visibleRight || paddingTop >= visibleBottom) {\n            AppLog.put(\"边距设置过大，请重新设置\", toast = true)\n            setFallbackLayout()\n        }\n\n        visibleRect.set(\n            paddingLeft.toFloat(),\n            paddingTop.toFloat(),\n            visibleRight.toFloat(),\n            visibleBottom.toFloat()\n        )\n\n    }\n\n    private fun setFallbackLayout() {\n        paddingLeft = 20.dpToPx()\n        paddingTop = 5.dpToPx()\n        paddingRight = 20.dpToPx()\n        paddingBottom = 5.dpToPx()\n        visibleWidth = if (doublePage) {\n            viewWidth / 2 - paddingLeft - paddingRight\n        } else {\n            viewWidth - paddingLeft - paddingRight\n        }\n        //留1dp画最后一行下划线\n        visibleHeight = viewHeight - paddingTop - paddingBottom\n        visibleRight = viewWidth - paddingRight\n        visibleBottom = paddingTop + visibleHeight\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/book/read/page/provider/LayoutProgressListener.kt",
    "content": "package io.legado.app.ui.book.read.page.provider\n\nimport io.legado.app.ui.book.read.page.entities.TextPage\n\ninterface LayoutProgressListener {\n\n    /**\n     * 单页排版完成\n     */\n    fun onLayoutPageCompleted(index: Int, page: TextPage) {}\n\n    /**\n     * 全部排版完成\n     */\n    fun onLayoutCompleted() {}\n\n    /**\n     * 排版出现异常\n     */\n    fun onLayoutException(e: Throwable) {}\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/book/read/page/provider/TextChapterLayout.kt",
    "content": "package io.legado.app.ui.book.read.page.provider\n\nimport android.graphics.Paint\nimport android.text.Layout\nimport android.text.StaticLayout\nimport android.text.TextPaint\nimport io.legado.app.constant.AppLog\nimport io.legado.app.constant.AppPattern\nimport io.legado.app.constant.PageAnim\nimport io.legado.app.data.entities.Book\nimport io.legado.app.data.entities.BookChapter\nimport io.legado.app.help.book.BookContent\nimport io.legado.app.help.book.BookHelp\nimport io.legado.app.help.book.getBookSource\nimport io.legado.app.help.config.AppConfig\nimport io.legado.app.help.config.ReadBookConfig\nimport io.legado.app.help.coroutine.Coroutine\nimport io.legado.app.model.ImageProvider\nimport io.legado.app.model.ReadBook\nimport io.legado.app.ui.book.read.page.entities.TextChapter\nimport io.legado.app.ui.book.read.page.entities.TextLine\nimport io.legado.app.ui.book.read.page.entities.TextPage\nimport io.legado.app.ui.book.read.page.entities.column.ImageColumn\nimport io.legado.app.ui.book.read.page.entities.column.TextColumn\nimport io.legado.app.utils.dpToPx\nimport io.legado.app.utils.fastSum\nimport io.legado.app.utils.getTextWidthsCompat\nimport io.legado.app.utils.splitNotBlank\nimport kotlinx.coroutines.CancellationException\nimport kotlinx.coroutines.CoroutineScope\nimport kotlinx.coroutines.CoroutineStart\nimport kotlinx.coroutines.Dispatchers.IO\nimport kotlinx.coroutines.channels.Channel\nimport kotlinx.coroutines.currentCoroutineContext\nimport kotlinx.coroutines.ensureActive\nimport kotlinx.coroutines.launch\nimport java.util.LinkedList\nimport kotlin.math.roundToInt\n\nclass TextChapterLayout(\n    scope: CoroutineScope,\n    private val textChapter: TextChapter,\n    private val textPages: ArrayList<TextPage>,\n    private val book: Book,\n    private val bookContent: BookContent,\n) {\n\n    @Volatile\n    private var listener: LayoutProgressListener? = textChapter\n\n    private val paddingLeft = ChapterProvider.paddingLeft\n    private val paddingTop = ChapterProvider.paddingTop\n\n    private val titlePaint = ChapterProvider.titlePaint\n    private val titlePaintTextHeight = ChapterProvider.titlePaintTextHeight\n    private val titlePaintFontMetrics = ChapterProvider.titlePaintFontMetrics\n\n    private val contentPaint = ChapterProvider.contentPaint\n    private val contentPaintTextHeight = ChapterProvider.contentPaintTextHeight\n    private val contentPaintFontMetrics = ChapterProvider.contentPaintFontMetrics\n\n    private val titleTopSpacing = ChapterProvider.titleTopSpacing\n    private val titleBottomSpacing = ChapterProvider.titleBottomSpacing\n    private val lineSpacingExtra = ChapterProvider.lineSpacingExtra\n    private val paragraphSpacing = ChapterProvider.paragraphSpacing\n\n    private val visibleHeight = ChapterProvider.visibleHeight\n    private val visibleWidth = ChapterProvider.visibleWidth\n\n    private val viewWidth = ChapterProvider.viewWidth\n    private val doublePage = ChapterProvider.doublePage\n    private val indentCharWidth = ChapterProvider.indentCharWidth\n    private val stringBuilder = StringBuilder()\n\n    private val paragraphIndent = ReadBookConfig.paragraphIndent\n    private val titleMode = ReadBookConfig.titleMode\n    private val useZhLayout = ReadBookConfig.useZhLayout\n    private val isMiddleTitle = ReadBookConfig.isMiddleTitle\n    private val textFullJustify = ReadBookConfig.textFullJustify\n    private val pageAnim = book.getPageAnim()\n\n    private var pendingTextPage = TextPage()\n\n    private val bookChapter inline get() = textChapter.chapter\n    private val displayTitle inline get() = textChapter.title\n    private val chaptersSize inline get() = textChapter.chaptersSize\n\n    private var durY = 0f\n    private var absStartX = paddingLeft\n    private var floatArray = FloatArray(128)\n\n    private var isCompleted = false\n    private val job: Coroutine<*>\n\n    var exception: Throwable? = null\n\n    var channel = Channel<TextPage>(Channel.UNLIMITED)\n\n\n    init {\n        job = Coroutine.async(\n            scope,\n            start = CoroutineStart.LAZY,\n            executeContext = IO\n        ) {\n            launch {\n                val bookSource = book.getBookSource() ?: return@launch\n                BookHelp.saveImages(bookSource, book, bookChapter, bookContent.toString())\n            }\n            getTextChapter(book, bookChapter, displayTitle, bookContent)\n        }.onError {\n            exception = it\n            onException(it)\n        }.onCancel {\n            channel.cancel()\n        }.onFinally {\n            isCompleted = true\n        }\n        job.start()\n    }\n\n    fun setProgressListener(l: LayoutProgressListener?) {\n        try {\n            if (isCompleted) {\n                // no op\n            } else if (exception != null) {\n                l?.onLayoutException(exception!!)\n            } else {\n                listener = l\n            }\n        } catch (e: Exception) {\n            e.printStackTrace()\n            AppLog.put(\"调用布局进度监听回调出错\\n${e.localizedMessage}\", e)\n        }\n    }\n\n    fun cancel() {\n        job.cancel()\n        listener = null\n    }\n\n    private fun onPageCompleted() {\n        val textPage = pendingTextPage\n        textPage.index = textPages.size\n        textPage.chapterIndex = bookChapter.index\n        textPage.chapterSize = chaptersSize\n        textPage.title = displayTitle\n        textPage.doublePage = doublePage\n        textPage.paddingTop = paddingTop\n        textPage.isCompleted = true\n        textPage.textChapter = textChapter\n        textPage.upLinesPosition()\n        textPage.upRenderHeight()\n        textPages.add(textPage)\n        channel.trySend(textPage)\n        try {\n            listener?.onLayoutPageCompleted(textPages.lastIndex, textPage)\n        } catch (e: Exception) {\n            e.printStackTrace()\n            AppLog.put(\"调用布局进度监听回调出错\\n${e.localizedMessage}\", e)\n        }\n    }\n\n    private fun onCompleted() {\n        channel.close()\n        try {\n            listener?.onLayoutCompleted()\n        } catch (e: Exception) {\n            e.printStackTrace()\n            AppLog.put(\"调用布局进度监听回调出错\\n${e.localizedMessage}\", e)\n        } finally {\n            listener = null\n        }\n    }\n\n    private fun onException(e: Throwable) {\n        channel.close(e)\n        if (e is CancellationException) {\n            listener = null\n            return\n        }\n        try {\n            listener?.onLayoutException(e)\n        } catch (e: Exception) {\n            e.printStackTrace()\n            AppLog.put(\"调用布局进度监听回调出错\\n${e.localizedMessage}\", e)\n        } finally {\n            listener = null\n        }\n    }\n\n    /**\n     * 获取拆分完的章节数据\n     */\n    private suspend fun getTextChapter(\n        book: Book,\n        bookChapter: BookChapter,\n        displayTitle: String,\n        bookContent: BookContent,\n    ) {\n        val contents = bookContent.textList\n        val imageStyle = book.getImageStyle()\n        val isSingleImageStyle = imageStyle.equals(Book.imgStyleSingle, true)\n        val isTextImageStyle = imageStyle.equals(Book.imgStyleText, true)\n\n        if (titleMode != 2 || bookChapter.isVolume || contents.isEmpty()) {\n            //标题非隐藏\n            displayTitle.splitNotBlank(\"\\n\").forEach { text ->\n                setTypeText(\n                    book,\n                    if (AppConfig.enableReview) text + ChapterProvider.reviewChar else text,\n                    titlePaint,\n                    titlePaintTextHeight,\n                    titlePaintFontMetrics,\n                    imageStyle,\n                    isTitle = true,\n                    emptyContent = contents.isEmpty(),\n                    isVolumeTitle = bookChapter.isVolume\n                )\n                pendingTextPage.lines.last().isParagraphEnd = true\n                stringBuilder.append(\"\\n\")\n            }\n            durY += titleBottomSpacing\n\n            // 如果是单图模式且当前页有内容，强制分页\n            if (isSingleImageStyle && pendingTextPage.lines.isNotEmpty() && contents.isNotEmpty()) {\n                prepareNextPageIfNeed()\n            }\n        }\n\n        val sb = StringBuffer()\n        var isSetTypedImage = false\n        contents.forEach { content ->\n            currentCoroutineContext().ensureActive()\n            if (isTextImageStyle) {\n                //图片样式为文字嵌入类型\n                var text = content.replace(ChapterProvider.srcReplaceChar, \"▣\")\n                val srcList = LinkedList<String>()\n                sb.setLength(0)\n                val matcher = AppPattern.imgPattern.matcher(text)\n                while (matcher.find()) {\n                    matcher.group(1)?.let { src ->\n                        srcList.add(src)\n                        matcher.appendReplacement(sb, ChapterProvider.srcReplaceChar)\n                    }\n                }\n                matcher.appendTail(sb)\n                text = sb.toString()\n                setTypeText(\n                    book,\n                    text,\n                    contentPaint,\n                    contentPaintTextHeight,\n                    contentPaintFontMetrics,\n                    imageStyle,\n                    srcList = srcList\n                )\n            } else {\n                if (isSingleImageStyle && isSetTypedImage) {\n                    isSetTypedImage = false\n                    prepareNextPageIfNeed()\n                }\n                var start = 0\n                if (content.contains(\"<img\")) {\n                    val matcher = AppPattern.imgPattern.matcher(content)\n                    while (matcher.find()) {\n                        currentCoroutineContext().ensureActive()\n                        val text = content.substring(start, matcher.start())\n                        if (text.isNotBlank()) {\n                            setTypeText(\n                                book,\n                                text,\n                                contentPaint,\n                                contentPaintTextHeight,\n                                contentPaintFontMetrics,\n                                imageStyle,\n                                isFirstLine = start == 0\n                            )\n                        }\n                        setTypeImage(\n                            book,\n                            matcher.group(1)!!,\n                            contentPaintTextHeight,\n                            imageStyle\n                        )\n                        isSetTypedImage = true\n                        start = matcher.end()\n                    }\n                }\n                if (start < content.length) {\n                    if (isSingleImageStyle && isSetTypedImage) {\n                        isSetTypedImage = false\n                        prepareNextPageIfNeed()\n                    }\n                    val text = content.substring(start, content.length)\n                    if (text.isNotBlank()) {\n                        setTypeText(\n                            book,\n                            if (AppConfig.enableReview) text + ChapterProvider.reviewChar else text,\n                            contentPaint,\n                            contentPaintTextHeight,\n                            contentPaintFontMetrics,\n                            imageStyle,\n                            isFirstLine = start == 0\n                        )\n                    }\n                }\n            }\n            pendingTextPage.lines.last().isParagraphEnd = true\n            stringBuilder.append(\"\\n\")\n        }\n        val textPage = pendingTextPage\n        val endPadding = 20.dpToPx()\n        val durYPadding = durY + endPadding\n        if (textPage.height < durYPadding) {\n            textPage.height = durYPadding\n        } else {\n            textPage.height += endPadding\n        }\n        textPage.text = stringBuilder.toString()\n        currentCoroutineContext().ensureActive()\n        onPageCompleted()\n        onCompleted()\n    }\n\n    /**\n     * 排版图片\n     */\n    private suspend fun setTypeImage(\n        book: Book,\n        src: String,\n        textHeight: Float,\n        imageStyle: String?,\n    ) {\n        val size = ImageProvider.getImageSize(book, src, ReadBook.bookSource)\n        if (size.width > 0 && size.height > 0) {\n            prepareNextPageIfNeed(durY)\n            var height = size.height\n            var width = size.width\n            when (imageStyle?.uppercase()) {\n                Book.imgStyleFull -> {\n                    width = visibleWidth\n                    height = size.height * visibleWidth / size.width\n                    if (pageAnim != PageAnim.scrollPageAnim && height > visibleHeight - durY) {\n                        if (height > visibleHeight) {\n                            width = width * visibleHeight / height\n                            height = visibleHeight\n                        }\n                        prepareNextPageIfNeed(durY + height)\n                    }\n                }\n\n                Book.imgStyleSingle -> {\n                    width = visibleWidth\n                    height = size.height * visibleWidth / size.width\n                    if (height > visibleHeight) {\n                        width = width * visibleHeight / height\n                        height = visibleHeight\n                    }\n                    if (durY > 0f) {\n                        prepareNextPageIfNeed()\n                    }\n\n                    // 图片竖直方向居中：调整 Y 坐标\n                    if (height < visibleHeight) {\n                        val adjustHeight = (visibleHeight - height) / 2f\n                        durY = adjustHeight // 将 Y 坐标设置为居中位置\n                    }\n                }\n\n                else -> {\n                    if (size.width > visibleWidth) {\n                        height = size.height * visibleWidth / size.width\n                        width = visibleWidth\n                    }\n                    if (height > visibleHeight) {\n                        width = width * visibleHeight / height\n                        height = visibleHeight\n                    }\n                    prepareNextPageIfNeed(durY + height)\n                }\n            }\n            val textLine = TextLine(isImage = true)\n            textLine.text = \" \"\n            textLine.lineTop = durY + paddingTop\n            durY += height\n            textLine.lineBottom = durY + paddingTop\n            val (start, end) = if (visibleWidth > width) {\n                val adjustWidth = (visibleWidth - width) / 2f\n                Pair(adjustWidth, adjustWidth + width)\n            } else {\n                Pair(0f, width.toFloat())\n            }\n            textLine.addColumn(\n                ImageColumn(start = absStartX + start, end = absStartX + end, src = src)\n            )\n            calcTextLinePosition(textPages, textLine, stringBuilder.length)\n            stringBuilder.append(\" \") // 确保翻页时索引计算正确\n            pendingTextPage.addLine(textLine)\n        }\n        durY += textHeight * paragraphSpacing / 10f\n    }\n\n    /**\n     * 排版文字\n     */\n    @Suppress(\"DEPRECATION\")\n    private suspend fun setTypeText(\n        book: Book,\n        text: String,\n        textPaint: TextPaint,\n        textHeight: Float,\n        fontMetrics: Paint.FontMetrics,\n        imageStyle: String?,\n        isTitle: Boolean = false,\n        isFirstLine: Boolean = true,\n        emptyContent: Boolean = false,\n        isVolumeTitle: Boolean = false,\n        srcList: LinkedList<String>? = null\n    ) {\n        val widthsArray = allocateFloatArray(text.length)\n        textPaint.getTextWidthsCompat(text, widthsArray)\n        val layout = if (useZhLayout) {\n            val (words, widths) = measureTextSplit(text, widthsArray)\n            val indentSize = if (isFirstLine) paragraphIndent.length else 0\n            ZhLayout(text, textPaint, visibleWidth, words, widths, indentSize)\n        } else {\n            StaticLayout(text, textPaint, visibleWidth, Layout.Alignment.ALIGN_NORMAL, 0f, 0f, true)\n        }\n        durY = when {\n            //标题y轴居中\n            emptyContent && textPages.isEmpty() -> {\n                val textPage = pendingTextPage\n                if (textPage.lineSize == 0) {\n                    val ty = (visibleHeight - layout.lineCount * textHeight) / 2\n                    if (ty > titleTopSpacing) ty else titleTopSpacing.toFloat()\n                } else {\n                    var textLayoutHeight = layout.lineCount * textHeight\n                    val fistLine = textPage.getLine(0)\n                    if (fistLine.lineTop < textLayoutHeight + titleTopSpacing) {\n                        textLayoutHeight = fistLine.lineTop - titleTopSpacing\n                    }\n                    textPage.lines.forEach {\n                        it.lineTop -= textLayoutHeight\n                        it.lineBase -= textLayoutHeight\n                        it.lineBottom -= textLayoutHeight\n                    }\n                    durY - textLayoutHeight\n                }\n            }\n\n            isTitle && textPages.isEmpty() && pendingTextPage.lines.isEmpty() -> {\n                when (imageStyle?.uppercase()) {\n                    Book.imgStyleSingle -> {\n                        val ty = (visibleHeight - layout.lineCount * textHeight) / 2\n                        if (ty > titleTopSpacing) ty else titleTopSpacing.toFloat()\n                    }\n\n                    else -> durY + titleTopSpacing\n                }\n            }\n\n            else -> durY\n        }\n        for (lineIndex in 0 until layout.lineCount) {\n            val textLine = TextLine(isTitle = isTitle)\n            prepareNextPageIfNeed(durY + textHeight)\n            val lineStart = layout.getLineStart(lineIndex)\n            val lineEnd = layout.getLineEnd(lineIndex)\n            val lineText = text.substring(lineStart, lineEnd)\n            val (words, widths) = measureTextSplit(lineText, widthsArray, lineStart)\n            val desiredWidth = widths.fastSum()\n            textLine.text = lineText\n            when {\n                lineIndex == 0 && layout.lineCount > 1 && !isTitle && isFirstLine -> {\n                    //多行的第一行 非标题\n                    addCharsToLineFirst(\n                        book, absStartX, textLine, words, textPaint,\n                        desiredWidth, widths, srcList\n                    )\n                }\n\n                lineIndex == layout.lineCount - 1 -> {\n                    //最后一行、单行\n                    //标题x轴居中\n                    val startX = if (\n                        isTitle &&\n                        (isMiddleTitle || emptyContent || isVolumeTitle\n                                || imageStyle?.uppercase() == Book.imgStyleSingle)\n                    ) {\n                        (visibleWidth - desiredWidth) / 2\n                    } else {\n                        0f\n                    }\n                    addCharsToLineNatural(\n                        book, absStartX, textLine, words,\n                        startX, !isTitle && lineIndex == 0, widths, srcList\n                    )\n                }\n\n                else -> {\n                    if (\n                        isTitle &&\n                        (isMiddleTitle || emptyContent || isVolumeTitle\n                                || imageStyle?.uppercase() == Book.imgStyleSingle)\n                    ) {\n                        //标题居中\n                        val startX = (visibleWidth - desiredWidth) / 2\n                        addCharsToLineNatural(\n                            book, absStartX, textLine, words,\n                            startX, false, widths, srcList\n                        )\n                    } else {\n                        //中间行\n                        addCharsToLineMiddle(\n                            book, absStartX, textLine, words, textPaint,\n                            desiredWidth, 0f, widths, srcList\n                        )\n                    }\n                }\n            }\n            if (doublePage) {\n                textLine.isLeftLine = absStartX < viewWidth / 2\n            }\n            calcTextLinePosition(textPages, textLine, stringBuilder.length)\n            stringBuilder.append(lineText)\n            textLine.upTopBottom(durY, textHeight, fontMetrics)\n            val textPage = pendingTextPage\n            textPage.addLine(textLine)\n            durY += textHeight * lineSpacingExtra\n            if (textPage.height < durY) {\n                textPage.height = durY\n            }\n        }\n        durY += textHeight * paragraphSpacing / 10f\n    }\n\n    private fun calcTextLinePosition(\n        textPages: ArrayList<TextPage>,\n        textLine: TextLine,\n        sbLength: Int\n    ) {\n        val lastLine = pendingTextPage.lines.lastOrNull { it.paragraphNum > 0 }\n            ?: textPages.lastOrNull()?.lines?.lastOrNull { it.paragraphNum > 0 }\n        val paragraphNum = when {\n            lastLine == null -> 1\n            lastLine.isParagraphEnd -> lastLine.paragraphNum + 1\n            else -> lastLine.paragraphNum\n        }\n        textLine.paragraphNum = paragraphNum\n        textLine.chapterPosition =\n            (textPages.lastOrNull()?.lines?.lastOrNull()?.run {\n                chapterPosition + charSize + if (isParagraphEnd) 1 else 0\n            } ?: 0) + sbLength\n        textLine.pagePosition = sbLength\n    }\n\n    /**\n     * 有缩进,两端对齐\n     */\n    private suspend fun addCharsToLineFirst(\n        book: Book,\n        absStartX: Int,\n        textLine: TextLine,\n        words: List<String>,\n        textPaint: TextPaint,\n        /**自然排版长度**/\n        desiredWidth: Float,\n        textWidths: List<Float>,\n        srcList: LinkedList<String>?\n    ) {\n        var x = 0f\n        if (!textFullJustify) {\n            addCharsToLineNatural(\n                book, absStartX, textLine, words,\n                x, true, textWidths, srcList\n            )\n            return\n        }\n        val bodyIndent = paragraphIndent\n        repeat(bodyIndent.length) {\n            val x1 = x + indentCharWidth\n            textLine.addColumn(\n                TextColumn(\n                    charData = ChapterProvider.indentChar,\n                    start = absStartX + x,\n                    end = absStartX + x1\n                )\n            )\n            x = x1\n            textLine.indentWidth = x\n        }\n        textLine.indentSize = bodyIndent.length\n        if (words.size > bodyIndent.length) {\n            val text1 = words.subList(bodyIndent.length, words.size)\n            val textWidths1 = textWidths.subList(bodyIndent.length, textWidths.size)\n            addCharsToLineMiddle(\n                book, absStartX, textLine, text1, textPaint,\n                desiredWidth, x, textWidths1, srcList\n            )\n        }\n    }\n\n    /**\n     * 无缩进,两端对齐\n     */\n    private suspend fun addCharsToLineMiddle(\n        book: Book,\n        absStartX: Int,\n        textLine: TextLine,\n        words: List<String>,\n        textPaint: TextPaint,\n        /**自然排版长度**/\n        desiredWidth: Float,\n        /**起始x坐标**/\n        startX: Float,\n        textWidths: List<Float>,\n        srcList: LinkedList<String>?\n    ) {\n        if (!textFullJustify) {\n            addCharsToLineNatural(\n                book, absStartX, textLine, words,\n                startX, false, textWidths, srcList\n            )\n            return\n        }\n        val residualWidth = visibleWidth - desiredWidth\n        val spaceSize = words.count { it == \" \" }\n        textLine.startX = absStartX + startX\n        if (spaceSize > 1) {\n            val d = residualWidth / spaceSize\n            textLine.wordSpacing = d\n            var x = startX\n            for (index in words.indices) {\n                val char = words[index]\n                val cw = textWidths[index]\n                val x1 = if (char == \" \") {\n                    if (index != words.lastIndex) (x + cw + d) else (x + cw)\n                } else {\n                    (x + cw)\n                }\n                addCharToLine(\n                    book, absStartX, textLine, char,\n                    x, x1, index + 1 == words.size, srcList\n                )\n                x = x1\n            }\n        } else {\n            val gapCount: Int = words.lastIndex\n            val d = if (gapCount > 0) residualWidth / gapCount else 0f\n            textLine.extraLetterSpacingOffsetX = -d / 2\n            textLine.extraLetterSpacing = d / textPaint.textSize\n            var x = startX\n            for (index in words.indices) {\n                val char = words[index]\n                val cw = textWidths[index]\n                val x1 = if (index != words.lastIndex) (x + cw + d) else (x + cw)\n                addCharToLine(\n                    book, absStartX, textLine, char,\n                    x, x1, index + 1 == words.size, srcList\n                )\n                x = x1\n            }\n        }\n        exceed(absStartX, textLine, words)\n    }\n\n    /**\n     * 自然排列\n     */\n    private suspend fun addCharsToLineNatural(\n        book: Book,\n        absStartX: Int,\n        textLine: TextLine,\n        words: List<String>,\n        startX: Float,\n        hasIndent: Boolean,\n        textWidths: List<Float>,\n        srcList: LinkedList<String>?\n    ) {\n        val indentLength = paragraphIndent.length\n        var x = startX\n        textLine.startX = absStartX + startX\n        for (index in words.indices) {\n            val char = words[index]\n            val cw = textWidths[index]\n            val x1 = x + cw\n            addCharToLine(book, absStartX, textLine, char, x, x1, index + 1 == words.size, srcList)\n            x = x1\n            if (hasIndent && index == indentLength - 1) {\n                textLine.indentWidth = x\n            }\n        }\n        exceed(absStartX, textLine, words)\n    }\n\n    /**\n     * 添加字符\n     */\n    private suspend fun addCharToLine(\n        book: Book,\n        absStartX: Int,\n        textLine: TextLine,\n        char: String,\n        xStart: Float,\n        xEnd: Float,\n        isLineEnd: Boolean,\n        srcList: LinkedList<String>?\n    ) {\n        val column = when {\n            srcList != null && char == ChapterProvider.srcReplaceChar -> {\n                val src = srcList.removeFirst()\n                ImageProvider.cacheImage(book, src, ReadBook.bookSource)\n                ImageColumn(\n                    start = absStartX + xStart,\n                    end = absStartX + xEnd,\n                    src = src\n                )\n            }\n\n//            isLineEnd && char == ChapterProvider.reviewChar -> {\n//                ReviewColumn(\n//                    start = absStartX + xStart,\n//                    end = absStartX + xEnd,\n//                    count = 100\n//                )\n//            }\n\n            else -> {\n                TextColumn(\n                    start = absStartX + xStart,\n                    end = absStartX + xEnd,\n                    charData = char\n                )\n            }\n        }\n        textLine.addColumn(column)\n    }\n\n    /**\n     * 超出边界处理\n     */\n    private fun exceed(absStartX: Int, textLine: TextLine, words: List<String>) {\n        var size = words.size\n        if (size < 2) return\n        val visibleEnd = absStartX + visibleWidth\n        val columns = textLine.columns\n        var offset = 0\n        val endColumn = if (words.last() == \" \") {\n            size--\n            offset++\n            columns[columns.lastIndex - 1]\n        } else {\n            columns.last()\n        }\n        val endX = endColumn.end.roundToInt()\n        if (endX > visibleEnd) {\n            textLine.exceed = true\n            val cc = (endX - visibleEnd) / size\n            for (i in 0..<size) {\n                textLine.getColumnReverseAt(i, offset).let {\n                    val py = cc * (size - i)\n                    it.start -= py\n                    it.end -= py\n                }\n            }\n        }\n    }\n\n    private suspend fun prepareNextPageIfNeed(requestHeight: Float = -1f) {\n        if (requestHeight > visibleHeight || requestHeight == -1f) {\n            val textPage = pendingTextPage\n            // 双页的 durY 不正确，可能会小于实际高度\n            if (textPage.height < durY) {\n                textPage.height = durY\n            }\n            if (doublePage && absStartX < viewWidth / 2) {\n                //当前页面左列结束\n                textPage.leftLineSize = textPage.lineSize\n                absStartX = viewWidth / 2 + paddingLeft\n            } else {\n                //当前页面结束,设置各种值\n                if (textPage.leftLineSize == 0) {\n                    textPage.leftLineSize = textPage.lineSize\n                }\n                textPage.text = stringBuilder.toString()\n                currentCoroutineContext().ensureActive()\n                onPageCompleted()\n                //新建页面\n                pendingTextPage = TextPage()\n                stringBuilder.clear()\n                absStartX = paddingLeft\n            }\n            durY = 0f\n        }\n    }\n\n    private fun allocateFloatArray(size: Int): FloatArray {\n        if (size > floatArray.size) {\n            floatArray = FloatArray(size)\n        }\n        return floatArray\n    }\n\n    private fun measureTextSplit(\n        text: String,\n        widthsArray: FloatArray,\n        start: Int = 0\n    ): Pair<ArrayList<String>, ArrayList<Float>> {\n        val length = text.length\n        var clusterCount = 0\n        for (i in start..<start + length) {\n            if (widthsArray[i] > 0) clusterCount++\n        }\n        val widths = ArrayList<Float>(clusterCount)\n        val stringList = ArrayList<String>(clusterCount)\n        var i = 0\n        while (i < length) {\n            val clusterBaseIndex = i++\n            widths.add(widthsArray[start + clusterBaseIndex])\n            while (i < length && widthsArray[start + i] == 0f && !isZeroWidthChar(text[i])) {\n                i++\n            }\n            stringList.add(text.substring(clusterBaseIndex, i))\n        }\n        return stringList to widths\n    }\n\n    private fun isZeroWidthChar(char: Char): Boolean {\n        val code = char.code\n        return code == 8203 || code == 8204 || code == 8205 || code == 8288\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/book/read/page/provider/TextMeasure.kt",
    "content": "package io.legado.app.ui.book.read.page.provider\n\nimport android.text.TextPaint\nimport android.util.SparseArray\nimport androidx.core.util.getOrDefault\nimport kotlin.math.ceil\n\nclass TextMeasure(private var paint: TextPaint) {\n\n    private var chineseCommonWidth = paint.measureText(\"一\")\n    private val asciiWidths = FloatArray(128) { -1f }\n    private val codePointWidths = SparseArray<Float>()\n\n    private fun measureCodePoint(codePoint: Int): Float {\n        if (codePoint < 128) {\n            return asciiWidths[codePoint]\n        }\n        // 中文 Unicode 范围 U+4E00 - U+9FA5\n        if (codePoint in 19968 .. 40869) {\n            return chineseCommonWidth\n        }\n        return codePointWidths.getOrDefault(codePoint, -1f)\n    }\n\n    private fun measureCodePoints(codePoints: List<Int>) {\n        val charArray = String(codePoints.toIntArray(), 0, codePoints.size).toCharArray()\n        val widths = FloatArray(charArray.size)\n        paint.getTextWidths(charArray, 0, charArray.size, widths)\n        val widthsList = ArrayList<Float>(charArray.size)\n        val buf = IntArray(1)\n        for (i in charArray.indices) {\n            if (charArray[i].isLowSurrogate()) continue\n            val width = ceil(widths[i])\n            widthsList.add(width)\n            // 可能需要检查是否不可见字符\n            if (width == 0f && widthsList.size > 1) {\n                val lastIndex = widthsList.lastIndex\n                buf[0] = codePoints[lastIndex - 1]\n                widthsList[lastIndex - 1] = paint.measureText(String(buf, 0, 1))\n                buf[0] = codePoints[lastIndex]\n                widthsList[lastIndex] = paint.measureText(String(buf, 0, 1))\n            }\n        }\n        for (i in codePoints.indices) {\n            val codePoint = codePoints[i]\n            val width = widthsList[i]\n            if (codePoint < 128) {\n                asciiWidths[codePoint] = width\n            } else {\n                codePointWidths[codePoint] = width\n            }\n        }\n    }\n\n\n    fun measureTextSplit(text: String): Pair<ArrayList<String>, ArrayList<Float>> {\n        var needMeasureCodePoints: HashSet<Int>? = null\n        val codePoints = text.toCodePoints()\n        val size = codePoints.size\n        val widths = ArrayList<Float>(size)\n        val stringList = ArrayList<String>(size)\n        val buf = IntArray(1)\n        for (i in codePoints.indices) {\n            val codePoint = codePoints[i]\n            val width = measureCodePoint(codePoint)\n            widths.add(width)\n            if (width == -1f) {\n                if (needMeasureCodePoints == null) {\n                    needMeasureCodePoints = hashSetOf()\n                }\n                needMeasureCodePoints.add(codePoint)\n            }\n            buf[0] = codePoint\n            stringList.add(String(buf, 0, 1))\n        }\n        if (!needMeasureCodePoints.isNullOrEmpty()) {\n            measureCodePoints(needMeasureCodePoints.toList())\n            for (i in codePoints.indices) {\n                if (widths[i] == -1f) {\n                    widths[i] = measureCodePoint(codePoints[i])\n                }\n            }\n        }\n        return stringList to widths\n    }\n\n    fun measureText(text: String): Float {\n        var textWidth = 0f\n        var needMeasureCodePoints: ArrayList<Int>? = null\n        val codePoints = text.toCodePoints()\n        for (i in codePoints.indices) {\n            val codePoint = codePoints[i]\n            val width = measureCodePoint(codePoint)\n            if (width == -1f) {\n                if (needMeasureCodePoints == null) {\n                    needMeasureCodePoints = ArrayList()\n                }\n                needMeasureCodePoints.add(codePoint)\n                continue\n            }\n            textWidth += width\n        }\n        if (!needMeasureCodePoints.isNullOrEmpty()) {\n            measureCodePoints(needMeasureCodePoints.toHashSet().toList())\n            for (i in needMeasureCodePoints.indices) {\n                textWidth += measureCodePoint(needMeasureCodePoints[i])\n            }\n        }\n        return textWidth\n    }\n\n    private fun String.toCodePoints(): List<Int> {\n        val codePoints = ArrayList<Int>(length)\n        val charArray = toCharArray()\n        val size = length\n        var i = 0\n        while (i < size) {\n            val c1 = charArray[i++]\n            var cp = c1.code\n            if (c1.isHighSurrogate() && i < size) {\n                val c2 = charArray[i]\n                if (c2.isLowSurrogate()) {\n                    i++\n                    cp = Character.toCodePoint(c1, c2)\n                }\n            }\n            codePoints.add(cp)\n        }\n        return codePoints\n    }\n\n    fun setPaint(paint: TextPaint) {\n        this.paint = paint\n        invalidate()\n    }\n\n    private fun invalidate() {\n        chineseCommonWidth = paint.measureText(\"一\")\n        codePointWidths.clear()\n        asciiWidths.fill(-1f)\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/book/read/page/provider/TextPageFactory.kt",
    "content": "package io.legado.app.ui.book.read.page.provider\n\nimport io.legado.app.R\nimport io.legado.app.model.ReadBook\nimport io.legado.app.ui.book.read.page.api.DataSource\nimport io.legado.app.ui.book.read.page.api.PageFactory\nimport io.legado.app.ui.book.read.page.entities.TextPage\nimport splitties.init.appCtx\n\nclass TextPageFactory(dataSource: DataSource) : PageFactory<TextPage>(dataSource) {\n\n    private val keepSwipeTip = appCtx.getString(R.string.keep_swipe_tip)\n\n    override fun hasPrev(): Boolean = with(dataSource) {\n        return hasPrevChapter() || pageIndex > 0\n    }\n\n    override fun hasNext(): Boolean = with(dataSource) {\n        return hasNextChapter() || (currentChapter != null && currentChapter?.isLastIndex(pageIndex) != true)\n    }\n\n    override fun hasNextPlus(): Boolean = with(dataSource) {\n        return hasNextChapter() || pageIndex < (currentChapter?.pageSize ?: 1) - 2\n    }\n\n    override fun moveToFirst() {\n        ReadBook.setPageIndex(0)\n    }\n\n    override fun moveToLast() = with(dataSource) {\n        currentChapter?.let {\n            if (it.pageSize == 0) {\n                ReadBook.setPageIndex(0)\n            } else {\n                ReadBook.setPageIndex(it.pageSize.minus(1))\n            }\n        } ?: ReadBook.setPageIndex(0)\n    }\n\n    override fun moveToNext(upContent: Boolean): Boolean = with(dataSource) {\n        return if (hasNext()) {\n            val pageIndex = pageIndex\n            if (currentChapter == null || currentChapter?.isLastIndex(pageIndex) == true) {\n                if ((currentChapter == null || isScroll) && nextChapter == null) {\n                    return@with false\n                }\n                ReadBook.moveToNextChapter(upContent, false)\n            } else {\n                if (pageIndex < 0 || currentChapter?.isLastIndexCurrent(pageIndex) == true) {\n                    return@with false\n                }\n                ReadBook.setPageIndex(pageIndex.plus(1))\n            }\n            if (upContent) upContent(resetPageOffset = false)\n            true\n        } else\n            false\n    }\n\n    override fun moveToPrev(upContent: Boolean): Boolean = with(dataSource) {\n        return if (hasPrev()) {\n            if (pageIndex <= 0) {\n                if (currentChapter == null && prevChapter == null) {\n                    return@with false\n                }\n                if (prevChapter != null && prevChapter?.isCompleted == false) {\n                    return@with false\n                }\n                ReadBook.moveToPrevChapter(upContent, upContentInPlace = false)\n            } else {\n                if (currentChapter == null) {\n                    return@with false\n                }\n                ReadBook.setPageIndex(pageIndex.minus(1))\n            }\n            if (upContent) upContent(resetPageOffset = false)\n            true\n        } else\n            false\n    }\n\n    override val curPage: TextPage\n        get() = with(dataSource) {\n            ReadBook.msg?.let {\n                return@with TextPage(text = it).format()\n            }\n            currentChapter?.let {\n                return@with it.getPage(pageIndex)\n                    ?: TextPage(title = it.title).apply { textChapter = it }.format()\n            }\n            return TextPage().format()\n        }\n\n    override val nextPage: TextPage\n        get() = with(dataSource) {\n            ReadBook.msg?.let {\n                return@with TextPage(text = it).format()\n            }\n            currentChapter?.let {\n                val pageIndex = pageIndex\n                if (pageIndex < it.pageSize - 1) {\n                    return@with it.getPage(pageIndex + 1)?.removePageAloudSpan()\n                        ?: TextPage(title = it.title).format()\n                }\n                if (!it.isCompleted) {\n                    return@with TextPage(title = it.title).format()\n                }\n            }\n            nextChapter?.let {\n                return@with it.getPage(0)?.removePageAloudSpan()\n                    ?: TextPage(title = it.title).format()\n            }\n            return TextPage().format()\n        }\n\n    override val prevPage: TextPage\n        get() = with(dataSource) {\n            ReadBook.msg?.let {\n                return@with TextPage(text = it).format()\n            }\n            currentChapter?.let {\n                val pageIndex = pageIndex\n                if (pageIndex > 0) {\n                    return@with it.getPage(pageIndex - 1)?.removePageAloudSpan()\n                        ?: TextPage(title = it.title).format()\n                }\n                if (!it.isCompleted) {\n                    return@with TextPage(title = it.title).format()\n                }\n            }\n            prevChapter?.let {\n                return@with it.lastPage?.removePageAloudSpan()\n                    ?: TextPage(title = it.title).format()\n            }\n            return TextPage().format()\n        }\n\n    override val nextPlusPage: TextPage\n        get() = with(dataSource) {\n            currentChapter?.let {\n                val pageIndex = pageIndex\n                if (pageIndex < it.pageSize - 2) {\n                    return@with it.getPage(pageIndex + 2)?.removePageAloudSpan()\n                        ?: TextPage(title = it.title).format()\n                }\n                if (!it.isCompleted) {\n                    return@with TextPage(title = it.title).format()\n                }\n                nextChapter?.let { nc ->\n                    if (pageIndex < it.pageSize - 1) {\n                        return@with nc.getPage(0)?.removePageAloudSpan()\n                            ?: TextPage(title = nc.title).format()\n                    }\n                    return@with nc.getPage(1)?.removePageAloudSpan()\n                        ?: TextPage(text = keepSwipeTip).format()\n                }\n            }\n            return TextPage().format()\n        }\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/book/read/page/provider/ZhLayout.kt",
    "content": "package io.legado.app.ui.book.read.page.provider\n\nimport android.graphics.Paint\nimport android.graphics.Rect\nimport android.os.Build\nimport android.text.Layout\nimport android.text.TextPaint\nimport java.util.WeakHashMap\nimport kotlin.math.max\n\n/**\n * 针对中文的断行排版处理-by hoodie13\n * 因为StaticLayout对标点处理不符合国人习惯，继承Layout\n * */\n@Suppress(\"MemberVisibilityCanBePrivate\", \"unused\")\nclass ZhLayout(\n    text: CharSequence,\n    textPaint: TextPaint,\n    width: Int,\n    words: List<String>,\n    widths: List<Float>,\n    indentSize: Int\n) : Layout(text, textPaint, width, Alignment.ALIGN_NORMAL, 0f, 0f) {\n    companion object {\n        private val postPanc = hashSetOf(\n            \"，\", \"。\", \"：\", \"？\", \"！\", \"、\", \"”\", \"’\", \"）\", \"》\", \"}\",\n            \"】\", \")\", \">\", \"]\", \"}\", \",\", \".\", \"?\", \"!\", \":\", \"」\", \"；\", \";\"\n        )\n        private val prePanc = hashSetOf(\"“\", \"（\", \"《\", \"【\", \"‘\", \"‘\", \"(\", \"<\", \"[\", \"{\", \"「\")\n        private val cnCharWidthCache = WeakHashMap<Paint, Float>()\n    }\n\n    private val defaultCapacity = 10\n    var lineStart = IntArray(defaultCapacity)\n    var lineWidth = FloatArray(defaultCapacity)\n    private var lineCount = 0\n    private val curPaint = textPaint\n    private val cnCharWidth = cnCharWidthCache[textPaint]\n        ?: getDesiredWidth(\"我\", textPaint).also {\n            cnCharWidthCache[textPaint] = it\n        }\n\n    enum class BreakMod { NORMAL, BREAK_ONE_CHAR, BREAK_MORE_CHAR, CPS_1, CPS_2, CPS_3, }\n    class Locate {\n        var start: Float = 0f\n        var end: Float = 0f\n    }\n\n    class Interval {\n        var total: Float = 0f\n        var single: Float = 0f\n    }\n\n    init {\n        var line = 0\n        var lineW = 0f\n        var cwPre = 0f\n        var length = 0\n        words.forEachIndexed { index, s ->\n            val cw = widths[index]\n            var breakMod: BreakMod\n            var breakLine = false\n            lineW += cw\n            var offset = 0f\n            var breakCharCnt = 0\n\n            if (lineW > width) {\n                /*禁止在行尾的标点处理*/\n                breakMod = if (index >= 1 && isPrePanc(words[index - 1])) {\n                    if (index >= 2 && isPrePanc(words[index - 2])) BreakMod.CPS_2//如果后面还有一个禁首标点则异常\n                    else BreakMod.BREAK_ONE_CHAR //无异常场景\n                }\n                /*禁止在行首的标点处理*/\n                else if (isPostPanc(words[index])) {\n                    if (index >= 1 && isPostPanc(words[index - 1])) BreakMod.CPS_1//如果后面还有一个禁首标点则异常，不过三个连续行尾标点的用法不通用\n                    else if (index >= 2 && isPrePanc(words[index - 2])) BreakMod.CPS_3//如果后面还有一个禁首标点则异常\n                    else BreakMod.BREAK_ONE_CHAR //无异常场景\n                } else {\n                    BreakMod.NORMAL //无异常场景\n                }\n\n                /*判断上述逻辑解决不了的特殊情况*/\n                var reCheck = false\n                var breakIndex = 0\n                if (breakMod == BreakMod.CPS_1 &&\n                    (inCompressible(widths[index]) || inCompressible(widths[index - 1]))\n                ) reCheck = true\n                if (breakMod == BreakMod.CPS_2 &&\n                    (inCompressible(widths[index - 1]) || inCompressible(widths[index - 2]))\n                ) reCheck = true\n                if (breakMod == BreakMod.CPS_3 &&\n                    (inCompressible(widths[index]) || inCompressible(widths[index - 2]))\n                ) reCheck = true\n                if (breakMod > BreakMod.BREAK_MORE_CHAR\n                    && index < words.lastIndex && isPostPanc(words[index + 1])\n                ) reCheck = true\n\n                /*特殊标点使用难保证显示效果，所以不考虑间隔，直接查找到能满足条件的分割字*/\n                var breakLength = 0\n                if (reCheck && index > 2) {\n                    val startPos = if (line == 0) indentSize else getLineStart(line)\n                    breakMod = BreakMod.NORMAL\n                    for (i in (index) downTo 1 + startPos) {\n                        if (i == index) {\n                            breakIndex = 0\n                            cwPre = 0f\n                        } else {\n                            breakIndex++\n                            breakLength += words[i].length\n                            cwPre += widths[i]\n                        }\n                        if (!isPostPanc(words[i]) && !isPrePanc(words[i - 1])) {\n                            breakMod = BreakMod.BREAK_MORE_CHAR\n                            break\n                        }\n                    }\n                }\n\n                when (breakMod) {\n                    BreakMod.NORMAL -> {//模式0 正常断行\n                        offset = cw\n                        lineStart[line + 1] = length\n                        breakCharCnt = 1\n                    }\n\n                    BreakMod.BREAK_ONE_CHAR -> {//模式1 当前行下移一个字\n                        offset = cw + cwPre\n                        lineStart[line + 1] = length - words[index - 1].length\n                        breakCharCnt = 2\n                    }\n\n                    BreakMod.BREAK_MORE_CHAR -> {//模式2 当前行下移多个字\n                        offset = cw + cwPre\n                        lineStart[line + 1] = length - breakLength\n                        breakCharCnt = breakIndex + 1\n                    }\n\n                    BreakMod.CPS_1 -> {//模式3 两个后置标点压缩\n                        offset = 0f\n                        lineStart[line + 1] = length + s.length\n                        breakCharCnt = 0\n                    }\n\n                    BreakMod.CPS_2 -> { //模式4 前置标点压缩+前置标点压缩+字\n                        offset = 0f\n                        lineStart[line + 1] = length + s.length\n                        breakCharCnt = 0\n                    }\n\n                    BreakMod.CPS_3 -> {//模式5 前置标点压缩+字+后置标点压缩\n                        offset = 0f\n                        lineStart[line + 1] = length + s.length\n                        breakCharCnt = 0\n                    }\n                }\n                breakLine = true\n            }\n\n            /*当前行写满情况下的断行*/\n            if (breakLine) {\n                lineWidth[line] = lineW - offset\n                lineW = offset\n                addLineArray(++line)\n            }\n            /*已到最后一个字符*/\n            if ((words.lastIndex) == index) {\n                if (!breakLine) {\n                    offset = 0f\n                    lineStart[line + 1] = length + s.length\n                    lineWidth[line] = lineW - offset\n                    lineW = offset\n                    addLineArray(++line)\n                }\n                /*写满断行、段落末尾、且需要下移字符，这种特殊情况下要额外多一行*/\n                else if (breakCharCnt > 0) {\n                    lineStart[line + 1] = lineStart[line] + breakCharCnt\n                    lineWidth[line] = lineW\n                    addLineArray(++line)\n                }\n            }\n            length += s.length\n            cwPre = cw\n        }\n\n        lineCount = line\n\n    }\n\n    private fun addLineArray(line: Int) {\n        if (lineStart.size <= line + 1) {\n            lineStart = lineStart.copyOf(line + defaultCapacity)\n            lineWidth = lineWidth.copyOf(line + defaultCapacity)\n        }\n    }\n\n    private fun isPostPanc(string: String): Boolean {\n        return postPanc.contains(string)\n    }\n\n    private fun isPrePanc(string: String): Boolean {\n        return prePanc.contains(string)\n    }\n\n    private fun inCompressible(width: Float): Boolean {\n        return width < cnCharWidth\n    }\n\n    private val gap = (cnCharWidth / 12.75).toFloat()\n    private fun getPostPancOffset(string: String): Float {\n        val textRect = Rect()\n        curPaint.getTextBounds(string, 0, 1, textRect)\n        return max(textRect.left.toFloat() - gap, 0f)\n    }\n\n    private fun getPrePancOffset(string: String): Float {\n        val textRect = Rect()\n        curPaint.getTextBounds(string, 0, 1, textRect)\n        val d = max(cnCharWidth - textRect.right.toFloat() - gap, 0f)\n        return cnCharWidth / 2 - d\n    }\n\n    fun getDesiredWidth(string: String, paint: TextPaint): Float {\n        var width = paint.measureText(string)\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM) {\n            width += paint.letterSpacing * paint.textSize\n        }\n        return width\n    }\n\n    override fun getLineCount(): Int {\n        return lineCount\n    }\n\n    override fun getLineTop(line: Int): Int {\n        return 0\n    }\n\n    override fun getLineDescent(line: Int): Int {\n        return 0\n    }\n\n    override fun getLineStart(line: Int): Int {\n        return lineStart[line]\n    }\n\n    override fun getParagraphDirection(line: Int): Int {\n        return 0\n    }\n\n    override fun getLineContainsTab(line: Int): Boolean {\n        return true\n    }\n\n    override fun getLineDirections(line: Int): Directions? {\n        return null\n    }\n\n    override fun getTopPadding(): Int {\n        return 0\n    }\n\n    override fun getBottomPadding(): Int {\n        return 0\n    }\n\n    override fun getLineWidth(line: Int): Float {\n        return lineWidth[line]\n    }\n\n    override fun getEllipsisStart(line: Int): Int {\n        return 0\n    }\n\n    override fun getEllipsisCount(line: Int): Int {\n        return 0\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/book/search/BookAdapter.kt",
    "content": "package io.legado.app.ui.book.search\n\nimport android.content.Context\nimport android.view.ViewGroup\nimport io.legado.app.base.adapter.ItemViewHolder\nimport io.legado.app.base.adapter.RecyclerAdapter\nimport io.legado.app.data.entities.Book\nimport io.legado.app.databinding.ItemFilletTextBinding\n\n\nclass BookAdapter(context: Context, val callBack: CallBack) :\n    RecyclerAdapter<Book, ItemFilletTextBinding>(context) {\n\n    override fun getItemId(position: Int): Long {\n        return position.toLong()\n    }\n\n    override fun getViewBinding(parent: ViewGroup): ItemFilletTextBinding {\n        return ItemFilletTextBinding.inflate(inflater, parent, false)\n    }\n\n    override fun convert(\n        holder: ItemViewHolder,\n        binding: ItemFilletTextBinding,\n        item: Book,\n        payloads: MutableList<Any>\n    ) {\n        binding.run {\n            textView.text = item.name\n        }\n    }\n\n    override fun registerListener(holder: ItemViewHolder, binding: ItemFilletTextBinding) {\n        holder.itemView.apply {\n            setOnClickListener {\n                getItem(holder.layoutPosition)?.let {\n                    callBack.showBookInfo(it)\n                }\n            }\n        }\n    }\n\n    interface CallBack {\n        fun showBookInfo(book: Book)\n    }\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/book/search/HistoryKeyAdapter.kt",
    "content": "package io.legado.app.ui.book.search\n\nimport android.view.ViewGroup\nimport io.legado.app.base.adapter.ItemViewHolder\nimport io.legado.app.base.adapter.RecyclerAdapter\nimport io.legado.app.data.entities.SearchKeyword\nimport io.legado.app.databinding.ItemFilletTextBinding\nimport io.legado.app.ui.widget.anima.explosion_field.ExplosionField\nimport splitties.views.onLongClick\n\nclass HistoryKeyAdapter(activity: SearchActivity, val callBack: CallBack) :\n    RecyclerAdapter<SearchKeyword, ItemFilletTextBinding>(activity) {\n\n    private val explosionField = ExplosionField.attach2Window(activity)\n\n    override fun getItemId(position: Int): Long {\n        return position.toLong()\n    }\n\n    override fun getViewBinding(parent: ViewGroup): ItemFilletTextBinding {\n        return ItemFilletTextBinding.inflate(inflater, parent, false)\n    }\n\n    override fun convert(\n        holder: ItemViewHolder,\n        binding: ItemFilletTextBinding,\n        item: SearchKeyword,\n        payloads: MutableList<Any>\n    ) {\n        binding.run {\n            textView.text = item.word\n        }\n    }\n\n    override fun registerListener(holder: ItemViewHolder, binding: ItemFilletTextBinding) {\n        holder.itemView.apply {\n            setOnClickListener {\n                getItemByLayoutPosition(holder.layoutPosition)?.let {\n                    callBack.searchHistory(it.word)\n                }\n            }\n            onLongClick {\n                explosionField.explode(this, true)\n                getItemByLayoutPosition(holder.layoutPosition)?.let {\n                    callBack.deleteHistory(it)\n                }\n            }\n        }\n    }\n\n    interface CallBack {\n        fun searchHistory(key: String)\n        fun deleteHistory(searchKeyword: SearchKeyword)\n    }\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/book/search/SearchActivity.kt",
    "content": "package io.legado.app.ui.book.search\n\nimport android.content.Context\nimport android.content.Intent\nimport android.os.Bundle\nimport android.view.Menu\nimport android.view.MenuItem\nimport android.view.View.GONE\nimport android.view.View.VISIBLE\nimport android.widget.TextView\nimport androidx.activity.viewModels\nimport androidx.appcompat.widget.SearchView\nimport androidx.core.os.bundleOf\nimport androidx.core.view.isVisible\nimport androidx.lifecycle.Lifecycle\nimport androidx.lifecycle.lifecycleScope\nimport androidx.lifecycle.repeatOnLifecycle\nimport androidx.recyclerview.widget.LinearLayoutManager\nimport androidx.recyclerview.widget.RecyclerView\nimport com.google.android.flexbox.FlexboxLayoutManager\nimport io.legado.app.R\nimport io.legado.app.base.VMBaseActivity\nimport io.legado.app.constant.AppLog\nimport io.legado.app.constant.PreferKey\nimport io.legado.app.data.appDb\nimport io.legado.app.data.entities.Book\nimport io.legado.app.data.entities.SearchBook\nimport io.legado.app.data.entities.SearchKeyword\nimport io.legado.app.databinding.ActivityBookSearchBinding\nimport io.legado.app.lib.dialogs.alert\nimport io.legado.app.lib.theme.Selector\nimport io.legado.app.lib.theme.accentColor\nimport io.legado.app.lib.theme.backgroundColor\nimport io.legado.app.lib.theme.primaryColor\nimport io.legado.app.lib.theme.primaryTextColor\nimport io.legado.app.ui.about.AppLogDialog\nimport io.legado.app.ui.book.info.BookInfoActivity\nimport io.legado.app.ui.book.source.manage.BookSourceActivity\nimport io.legado.app.utils.ColorUtils\nimport io.legado.app.utils.applyNavigationBarMargin\nimport io.legado.app.utils.applyNavigationBarPadding\nimport io.legado.app.utils.applyTint\nimport io.legado.app.utils.getPrefBoolean\nimport io.legado.app.utils.gone\nimport io.legado.app.utils.invisible\nimport io.legado.app.utils.putPrefBoolean\nimport io.legado.app.utils.setEdgeEffectColor\nimport io.legado.app.utils.showDialogFragment\nimport io.legado.app.utils.startActivity\nimport io.legado.app.utils.transaction\nimport io.legado.app.utils.viewbindingdelegate.viewBinding\nimport io.legado.app.utils.visible\nimport kotlinx.coroutines.Dispatchers.IO\nimport kotlinx.coroutines.Job\nimport kotlinx.coroutines.awaitCancellation\nimport kotlinx.coroutines.flow.catch\nimport kotlinx.coroutines.flow.conflate\nimport kotlinx.coroutines.flow.flowOn\nimport kotlinx.coroutines.launch\nimport kotlinx.coroutines.withContext\nimport splitties.init.appCtx\nimport kotlin.math.abs\n\nclass SearchActivity : VMBaseActivity<ActivityBookSearchBinding, SearchViewModel>(),\n    BookAdapter.CallBack,\n    HistoryKeyAdapter.CallBack,\n    SearchScopeDialog.Callback,\n    SearchAdapter.CallBack {\n\n    override val binding by viewBinding(ActivityBookSearchBinding::inflate)\n    override val viewModel by viewModels<SearchViewModel>()\n\n    private val adapter by lazy { SearchAdapter(this, this) }\n    private val bookAdapter by lazy {\n        BookAdapter(this, this).apply {\n            setHasStableIds(true)\n        }\n    }\n    private val historyKeyAdapter by lazy {\n        HistoryKeyAdapter(this, this).apply {\n            setHasStableIds(true)\n        }\n    }\n    private val searchView: SearchView by lazy {\n        binding.titleBar.findViewById(R.id.search_view)\n    }\n    private var menu: Menu? = null\n    private var groups: List<String>? = null\n    private var historyFlowJob: Job? = null\n    private var booksFlowJob: Job? = null\n    private var precisionSearchMenuItem: MenuItem? = null\n    private var isManualStopSearch = false\n\n    override fun onActivityCreated(savedInstanceState: Bundle?) {\n        binding.llInputHelp.setBackgroundColor(backgroundColor)\n        initRecyclerView()\n        initSearchView()\n        initOtherView()\n        initData()\n        receiptIntent(intent)\n    }\n\n    override fun onNewIntent(intent: Intent) {\n        super.onNewIntent(intent)\n        receiptIntent(intent)\n    }\n\n    override fun onCompatCreateOptionsMenu(menu: Menu): Boolean {\n        menuInflater.inflate(R.menu.book_search, menu)\n        this.menu = menu\n        precisionSearchMenuItem = menu.findItem(R.id.menu_precision_search)\n        precisionSearchMenuItem?.isChecked = getPrefBoolean(PreferKey.precisionSearch)\n        return super.onCompatCreateOptionsMenu(menu)\n    }\n\n    override fun onMenuOpened(featureId: Int, menu: Menu): Boolean {\n        menu.transaction {\n            menu.removeGroup(R.id.menu_group_1)\n            menu.removeGroup(R.id.menu_group_2)\n            var hasChecked = false\n            val searchScopeNames = viewModel.searchScope.displayNames\n            if (viewModel.searchScope.isSource()) {\n                menu.add(R.id.menu_group_1, Menu.NONE, Menu.NONE, searchScopeNames.first()).apply {\n                    isChecked = true\n                    hasChecked = true\n                }\n            }\n            val allSourceMenu =\n                menu.add(R.id.menu_group_2, R.id.menu_1, Menu.NONE, getString(R.string.all_source))\n                    .apply {\n                        if (searchScopeNames.isEmpty()) {\n                            isChecked = true\n                            hasChecked = true\n                        }\n                    }\n            groups?.forEach {\n                if (searchScopeNames.contains(it)) {\n                    menu.add(R.id.menu_group_1, Menu.NONE, Menu.NONE, it).apply {\n                        isChecked = true\n                        hasChecked = true\n                    }\n                } else {\n                    menu.add(R.id.menu_group_2, Menu.NONE, Menu.NONE, it)\n                }\n            }\n            if (!hasChecked) {\n                viewModel.searchScope.update(\"\")\n                allSourceMenu.isChecked = true\n            }\n            menu.setGroupCheckable(R.id.menu_group_1, true, false)\n            menu.setGroupCheckable(R.id.menu_group_2, true, true)\n        }\n        return super.onMenuOpened(featureId, menu)\n    }\n\n    override fun onCompatOptionsItemSelected(item: MenuItem): Boolean {\n        when (item.itemId) {\n            R.id.menu_precision_search -> {\n                putPrefBoolean(\n                    PreferKey.precisionSearch,\n                    !getPrefBoolean(PreferKey.precisionSearch)\n                )\n                precisionSearchMenuItem?.isChecked = getPrefBoolean(PreferKey.precisionSearch)\n                searchView.query?.toString()?.trim()?.let {\n                    searchView.setQuery(it, true)\n                }\n            }\n\n            R.id.menu_search_scope -> alertSearchScope()\n            R.id.menu_source_manage -> startActivity<BookSourceActivity>()\n            R.id.menu_log -> showDialogFragment(AppLogDialog())\n            R.id.menu_1 -> viewModel.searchScope.update(\"\")\n            else -> {\n                if (item.groupId == R.id.menu_group_1) {\n                    viewModel.searchScope.remove(item.title.toString())\n                } else if (item.groupId == R.id.menu_group_2) {\n                    viewModel.searchScope.update(item.title.toString())\n                }\n            }\n        }\n        return super.onCompatOptionsItemSelected(item)\n    }\n\n    private fun initSearchView() {\n        searchView.applyTint(primaryTextColor)\n        searchView.isSubmitButtonEnabled = true\n        searchView.queryHint = getString(R.string.search_book_key)\n        searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener {\n            override fun onQueryTextSubmit(query: String): Boolean {\n                searchView.clearFocus()\n                query.trim().let { searchKey ->\n                    isManualStopSearch = false\n                    viewModel.saveSearchKey(searchKey)\n                    viewModel.searchKey = \"\"\n                    viewModel.search(searchKey)\n                }\n                visibleInputHelp(false)\n                return true\n            }\n\n            override fun onQueryTextChange(newText: String): Boolean {\n                viewModel.stop()\n                binding.fbStartStop.invisible()\n                upHistory(newText.trim())\n                return false\n            }\n        })\n        searchView.setOnQueryTextFocusChangeListener { _, hasFocus ->\n            if (binding.refreshProgressBar.isAutoLoading || (!hasFocus && adapter.isNotEmpty() && searchView.query.isNotBlank())) {\n                visibleInputHelp(false)\n            } else {\n                visibleInputHelp(true)\n            }\n        }\n        visibleInputHelp(true)\n    }\n\n    private fun initRecyclerView() {\n        binding.recyclerView.setEdgeEffectColor(primaryColor)\n        binding.rvBookshelfSearch.setEdgeEffectColor(primaryColor)\n        binding.rvHistoryKey.setEdgeEffectColor(primaryColor)\n        binding.rvBookshelfSearch.layoutManager = FlexboxLayoutManager(this)\n        binding.rvBookshelfSearch.adapter = bookAdapter\n        binding.rvBookshelfSearch.applyNavigationBarMargin()\n        binding.rvHistoryKey.layoutManager = FlexboxLayoutManager(this)\n        binding.rvHistoryKey.adapter = historyKeyAdapter\n        binding.rvHistoryKey.applyNavigationBarMargin()\n        binding.recyclerView.layoutManager = LinearLayoutManager(this)\n        binding.recyclerView.adapter = adapter\n        binding.recyclerView.itemAnimator = null\n        binding.recyclerView.applyNavigationBarPadding()\n        adapter.registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() {\n            override fun onItemRangeInserted(positionStart: Int, itemCount: Int) {\n                super.onItemRangeInserted(positionStart, itemCount)\n                if (positionStart == 0) {\n                    binding.recyclerView.scrollToPosition(0)\n                }\n            }\n\n            override fun onItemRangeMoved(fromPosition: Int, toPosition: Int, itemCount: Int) {\n                super.onItemRangeMoved(fromPosition, toPosition, itemCount)\n                if (toPosition == 0) {\n                    binding.recyclerView.scrollToPosition(0)\n                }\n            }\n        })\n        binding.recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() {\n            override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {\n                super.onScrolled(recyclerView, dx, dy)\n                if (!recyclerView.canScrollVertically(1)) {\n                    val layoutManager = recyclerView.layoutManager as LinearLayoutManager\n                    val lastPosition = layoutManager.findLastCompletelyVisibleItemPosition()\n                    if (lastPosition == RecyclerView.NO_POSITION) {\n                        return\n                    }\n                    val lastView = layoutManager.findViewByPosition(lastPosition)\n                    if (lastView == null) {\n                        scrollToBottom()\n                        return\n                    }\n                    val bottom =\n                        abs(lastView.bottom - recyclerView.height) - recyclerView.paddingBottom\n                    if (bottom <= 1) {\n                        scrollToBottom()\n                    }\n                }\n            }\n        })\n    }\n\n    private fun initOtherView() {\n        binding.fbStartStop.backgroundTintList =\n            Selector.colorBuild()\n                .setDefaultColor(accentColor)\n                .setPressedColor(ColorUtils.darkenColor(accentColor))\n                .create()\n        binding.fbStartStop.setOnClickListener {\n            if (viewModel.isSearchLiveData.value == true) {\n                isManualStopSearch = true\n                viewModel.stop()\n                binding.refreshProgressBar.isAutoLoading = false\n            } else {\n                viewModel.search(\"\")\n            }\n        }\n        binding.fbStartStop.applyNavigationBarMargin(true)\n        binding.tvClearHistory.setOnClickListener { alertClearHistory() }\n    }\n\n    private fun initData() {\n        viewModel.searchScope.stateLiveData.observe(this) {\n            if (!binding.llInputHelp.isVisible) {\n                searchView.query?.toString()?.trim()?.let {\n                    searchView.setQuery(it, true)\n                }\n            }\n        }\n        viewModel.isSearchLiveData.observe(this) {\n            if (it) {\n                startSearch()\n            } else {\n                searchFinally()\n            }\n        }\n        viewModel.searchBookLiveData.observe(this) {\n            adapter.setItems(it)\n        }\n        lifecycleScope.launch {\n            appDb.bookSourceDao.flowEnabledGroups().collect {\n                groups = it\n            }\n        }\n        lifecycleScope.launch {\n            repeatOnLifecycle(Lifecycle.State.RESUMED) {\n                viewModel.resume()\n                try {\n                    awaitCancellation()\n                } finally {\n                    viewModel.pause()\n                }\n            }\n        }\n    }\n\n    /**\n     * 处理传入数据\n     */\n    private fun receiptIntent(intent: Intent? = null) {\n        val searchScope = intent?.getStringExtra(\"searchScope\")\n        searchScope?.let {\n            viewModel.searchScope.update(searchScope, false)\n        }\n        val key = intent?.getStringExtra(\"key\")\n        if (key.isNullOrBlank()) {\n            searchView.findViewById<TextView>(androidx.appcompat.R.id.search_src_text)\n                .requestFocus()\n        } else {\n            searchView.setQuery(key, true)\n        }\n    }\n\n    /**\n     * 滚动到底部事件\n     */\n    private fun scrollToBottom() {\n        if (isManualStopSearch) {\n            return\n        }\n        if (viewModel.isSearchLiveData.value == false\n            && viewModel.searchKey.isNotEmpty()\n            && viewModel.hasMore\n        ) {\n            viewModel.search(\"\")\n        }\n    }\n\n    /**\n     * 打开关闭输入帮助\n     */\n    private fun visibleInputHelp(visible: Boolean) {\n        if (visible) {\n            upHistory(searchView.query.toString())\n            binding.llInputHelp.visibility = VISIBLE\n        } else {\n            binding.llInputHelp.visibility = GONE\n        }\n    }\n\n    /**\n     * 更新搜索历史\n     */\n    private fun upHistory(key: String? = null) {\n        booksFlowJob?.cancel()\n        booksFlowJob = lifecycleScope.launch {\n            if (key.isNullOrBlank()) {\n                binding.tvBookShow.gone()\n                binding.rvBookshelfSearch.gone()\n            } else {\n                appDb.bookDao.flowSearch(key).conflate().collect {\n                    if (it.isEmpty()) {\n                        binding.tvBookShow.gone()\n                        binding.rvBookshelfSearch.gone()\n                    } else {\n                        binding.tvBookShow.visible()\n                        binding.rvBookshelfSearch.visible()\n                    }\n                    bookAdapter.setItems(it)\n                }\n            }\n        }\n        historyFlowJob?.cancel()\n        historyFlowJob = lifecycleScope.launch {\n            when {\n                key.isNullOrBlank() -> appDb.searchKeywordDao.flowByTime()\n                else -> appDb.searchKeywordDao.flowSearch(key)\n            }.catch {\n                AppLog.put(\"搜索界面获取搜索历史数据失败\\n${it.localizedMessage}\", it)\n            }.flowOn(IO).conflate().collect {\n                historyKeyAdapter.setItems(it)\n                if (it.isEmpty()) {\n                    binding.tvClearHistory.invisible()\n                } else {\n                    binding.tvClearHistory.visible()\n                }\n            }\n        }\n    }\n\n    /**\n     * 开始搜索\n     */\n    private fun startSearch() {\n        binding.refreshProgressBar.visible()\n        binding.refreshProgressBar.isAutoLoading = true\n        binding.fbStartStop.setImageResource(R.drawable.ic_stop_black_24dp)\n        binding.fbStartStop.visible()\n    }\n\n    /**\n     * 搜索结束\n     */\n    private fun searchFinally() {\n        binding.refreshProgressBar.isAutoLoading = false\n        binding.refreshProgressBar.gone()\n        if (!isManualStopSearch && viewModel.hasMore) {\n            binding.fbStartStop.setImageResource(R.drawable.ic_play_24dp)\n        } else {\n            binding.fbStartStop.invisible()\n        }\n    }\n\n    override fun observeLiveBus() {\n        viewModel.upAdapterLiveData.observe(this) {\n            adapter.notifyItemRangeChanged(0, adapter.itemCount, bundleOf(it to null))\n        }\n        viewModel.searchFinishLiveData.observe(this) { isEmpty ->\n            if (!isEmpty || viewModel.searchScope.isAll()) return@observe\n            alert(\"搜索结果为空\") {\n                val precisionSearch = appCtx.getPrefBoolean(PreferKey.precisionSearch)\n                val displayScope = viewModel.searchScope.display\n                if (precisionSearch) {\n                    setMessage(\"${displayScope}分组搜索结果为空，是否关闭精准搜索？\")\n                    yesButton {\n                        appCtx.putPrefBoolean(PreferKey.precisionSearch, false)\n                        precisionSearchMenuItem?.isChecked = false\n                        viewModel.searchKey = \"\"\n                        viewModel.search(searchView.query.toString())\n                    }\n                } else {\n                    setMessage(\"${displayScope}分组搜索结果为空，是否切换到全部分组？\")\n                    yesButton {\n                        viewModel.searchScope.update(\"\")\n                    }\n                }\n                noButton()\n            }\n        }\n    }\n\n    /**\n     * 显示书籍详情\n     */\n    override fun showBookInfo(name: String, author: String, bookUrl: String) {\n        startActivity<BookInfoActivity> {\n            putExtra(\"name\", name)\n            putExtra(\"author\", author)\n            putExtra(\"bookUrl\", bookUrl)\n        }\n    }\n\n    /**\n     * 是否已经加入书架\n     */\n    override fun isInBookshelf(book: SearchBook): Boolean {\n        return viewModel.isInBookShelf(book)\n    }\n\n    /**\n     * 显示书籍详情\n     */\n    override fun showBookInfo(book: Book) {\n        showBookInfo(book.name, book.author, book.bookUrl)\n    }\n\n    /**\n     * 点击历史关键字\n     */\n    override fun searchHistory(key: String) {\n        lifecycleScope.launch {\n            when {\n                searchView.query.toString() == key -> {\n                    searchView.setQuery(key, true)\n                }\n\n                withContext(IO) { appDb.bookDao.findByName(key).isEmpty() } -> {\n                    searchView.setQuery(key, true)\n                }\n\n                else -> {\n                    searchView.setQuery(key, false)\n                }\n            }\n        }\n    }\n\n    /**\n     * 删除搜索记录\n     */\n    override fun deleteHistory(searchKeyword: SearchKeyword) {\n        viewModel.deleteHistory(searchKeyword)\n    }\n\n\n    override fun onSearchScopeOk(searchScope: SearchScope) {\n        viewModel.searchScope.update(searchScope.toString())\n    }\n\n    private fun alertSearchScope() {\n        showDialogFragment<SearchScopeDialog>()\n    }\n\n    private fun alertClearHistory() {\n        alert(R.string.draw) {\n            setMessage(R.string.sure_clear_search_history)\n            yesButton {\n                viewModel.clearHistory()\n            }\n            noButton()\n        }\n    }\n\n    override fun finish() {\n        if (searchView.hasFocus()) {\n            searchView.clearFocus()\n            return\n        }\n        super.finish()\n    }\n\n    companion object {\n\n        fun start(context: Context, key: String?) {\n            context.startActivity<SearchActivity> {\n                putExtra(\"key\", key)\n            }\n        }\n\n    }\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/book/search/SearchAdapter.kt",
    "content": "package io.legado.app.ui.book.search\n\nimport android.content.Context\nimport android.os.Bundle\nimport android.view.ViewGroup\nimport androidx.core.view.isVisible\nimport androidx.recyclerview.widget.DiffUtil\nimport io.legado.app.R\nimport io.legado.app.base.adapter.DiffRecyclerAdapter\nimport io.legado.app.base.adapter.ItemViewHolder\nimport io.legado.app.data.entities.SearchBook\nimport io.legado.app.databinding.ItemSearchBinding\nimport io.legado.app.help.config.AppConfig\nimport io.legado.app.utils.gone\nimport io.legado.app.utils.visible\n\n\nclass SearchAdapter(context: Context, val callBack: CallBack) :\n    DiffRecyclerAdapter<SearchBook, ItemSearchBinding>(context) {\n\n    override val keepScrollPosition = true\n\n    override val diffItemCallback: DiffUtil.ItemCallback<SearchBook>\n        get() = object : DiffUtil.ItemCallback<SearchBook>() {\n\n            override fun areItemsTheSame(oldItem: SearchBook, newItem: SearchBook): Boolean {\n                return when {\n                    oldItem.name != newItem.name -> false\n                    oldItem.author != newItem.author -> false\n                    else -> true\n                }\n            }\n\n            override fun areContentsTheSame(oldItem: SearchBook, newItem: SearchBook): Boolean {\n                return false\n            }\n\n            override fun getChangePayload(oldItem: SearchBook, newItem: SearchBook): Any {\n                val payload = Bundle()\n                payload.putInt(\"origins\", newItem.origins.size)\n                if (oldItem.coverUrl != newItem.coverUrl)\n                    payload.putString(\"cover\", newItem.coverUrl)\n                if (oldItem.kind != newItem.kind)\n                    payload.putString(\"kind\", newItem.kind)\n                if (oldItem.latestChapterTitle != newItem.latestChapterTitle)\n                    payload.putString(\"last\", newItem.latestChapterTitle)\n                if (oldItem.intro != newItem.intro)\n                    payload.putString(\"intro\", newItem.intro)\n                return payload\n            }\n\n        }\n\n    override fun getViewBinding(parent: ViewGroup): ItemSearchBinding {\n        return ItemSearchBinding.inflate(inflater, parent, false)\n    }\n\n    override fun convert(\n        holder: ItemViewHolder,\n        binding: ItemSearchBinding,\n        item: SearchBook,\n        payloads: MutableList<Any>\n    ) {\n        if (payloads.isEmpty()) {\n            bind(binding, item)\n        } else {\n            for (i in payloads.indices) {\n                val bundle = payloads[i] as Bundle\n                bindChange(binding, item, bundle)\n            }\n        }\n    }\n\n    override fun registerListener(holder: ItemViewHolder, binding: ItemSearchBinding) {\n        binding.root.setOnClickListener {\n            getItem(holder.layoutPosition)?.let {\n                callBack.showBookInfo(it.name, it.author, it.bookUrl)\n            }\n        }\n    }\n\n    private fun bind(binding: ItemSearchBinding, searchBook: SearchBook) {\n        binding.run {\n            tvName.text = searchBook.name\n            tvAuthor.text = context.getString(R.string.author_show, searchBook.author)\n            ivInBookshelf.isVisible = callBack.isInBookshelf(searchBook)\n            bvOriginCount.setBadgeCount(searchBook.origins.size)\n            upLasted(binding, searchBook.latestChapterTitle)\n            tvIntroduce.text = searchBook.trimIntro(context)\n            upKind(binding, searchBook.getKindList())\n            ivCover.load(\n                searchBook.coverUrl,\n                searchBook.name,\n                searchBook.author,\n                AppConfig.loadCoverOnlyWifi,\n                searchBook.origin\n            )\n        }\n    }\n\n    private fun bindChange(binding: ItemSearchBinding, searchBook: SearchBook, bundle: Bundle) {\n        binding.run {\n            bundle.keySet().forEach {\n                when (it) {\n                    \"origins\" -> bvOriginCount.setBadgeCount(searchBook.origins.size)\n                    \"last\" -> upLasted(binding, searchBook.latestChapterTitle)\n                    \"intro\" -> tvIntroduce.text = searchBook.trimIntro(context)\n                    \"kind\" -> upKind(binding, searchBook.getKindList())\n                    \"isInBookshelf\" -> ivInBookshelf.isVisible = callBack.isInBookshelf(searchBook)\n                    \"cover\" -> ivCover.load(\n                        searchBook.coverUrl,\n                        searchBook.name,\n                        searchBook.author,\n                        false,\n                        searchBook.origin\n                    )\n                }\n            }\n        }\n    }\n\n    private fun upLasted(binding: ItemSearchBinding, latestChapterTitle: String?) {\n        binding.run {\n            if (latestChapterTitle.isNullOrEmpty()) {\n                tvLasted.gone()\n            } else {\n                tvLasted.text =\n                    context.getString(R.string.lasted_show, latestChapterTitle)\n                tvLasted.visible()\n            }\n        }\n    }\n\n    private fun upKind(binding: ItemSearchBinding, kinds: List<String>) = binding.run {\n        if (kinds.isEmpty()) {\n            llKind.gone()\n        } else {\n            llKind.visible()\n            llKind.setLabels(kinds)\n        }\n    }\n\n    interface CallBack {\n\n        /**\n         * 是否已经加入书架\n         */\n        fun isInBookshelf(book: SearchBook): Boolean\n\n        /**\n         * 显示书籍详情\n         */\n        fun showBookInfo(name: String, author: String, bookUrl: String)\n    }\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/book/search/SearchScope.kt",
    "content": "package io.legado.app.ui.book.search\n\nimport androidx.lifecycle.MutableLiveData\nimport io.legado.app.R\nimport io.legado.app.data.appDb\nimport io.legado.app.data.entities.BookSource\nimport io.legado.app.data.entities.BookSourcePart\nimport io.legado.app.help.config.AppConfig\nimport io.legado.app.utils.splitNotBlank\nimport splitties.init.appCtx\n\n/**\n * 搜索范围\n */\n@Suppress(\"unused\")\ndata class SearchScope(private var scope: String) {\n\n    constructor(groups: List<String>) : this(groups.joinToString(\",\"))\n\n    constructor(source: BookSource) : this(\n        \"${source.bookSourceName.replace(\":\", \"\")}::${source.bookSourceUrl}\"\n    )\n\n    constructor(source: BookSourcePart) : this(\n        \"${source.bookSourceName.replace(\":\", \"\")}::${source.bookSourceUrl}\"\n    )\n\n    override fun toString(): String {\n        return scope\n    }\n\n    val stateLiveData = MutableLiveData(scope)\n\n    fun update(scope: String, postValue: Boolean = true) {\n        this.scope = scope\n        if (postValue) stateLiveData.postValue(scope)\n        save()\n    }\n\n    fun update(groups: List<String>) {\n        scope = groups.joinToString(\",\")\n        stateLiveData.postValue(scope)\n        save()\n    }\n\n    fun update(source: BookSource) {\n        scope = \"${source.bookSourceName}::${source.bookSourceUrl}\"\n        stateLiveData.postValue(scope)\n        save()\n    }\n\n    fun isSource(): Boolean {\n        return scope.contains(\"::\")\n    }\n\n    val display: String\n        get() {\n            if (scope.contains(\"::\")) {\n                return scope.substringBefore(\"::\")\n            }\n            if (scope.isEmpty()) {\n                return appCtx.getString(R.string.all_source)\n            }\n            return scope\n        }\n\n    /**\n     * 搜索范围显示\n     */\n    val displayNames: List<String>\n        get() {\n            val list = arrayListOf<String>()\n            if (scope.contains(\"::\")) {\n                list.add(scope.substringBefore(\"::\"))\n            } else {\n                scope.splitNotBlank(\",\").forEach {\n                    list.add(it)\n                }\n            }\n            return list\n        }\n\n    fun remove(scope: String) {\n        if (isSource()) {\n            this.scope = \"\"\n        } else {\n            val stringBuilder = StringBuilder()\n            this.scope.split(\",\").forEach {\n                if (it != scope) {\n                    if (stringBuilder.isNotEmpty()) {\n                        stringBuilder.append(\",\")\n                    }\n                    stringBuilder.append(it)\n                }\n            }\n            this.scope = stringBuilder.toString()\n        }\n        stateLiveData.postValue(this.scope)\n    }\n\n    /**\n     * 搜索范围书源\n     */\n    fun getBookSourceParts(): List<BookSourcePart> {\n        val list = hashSetOf<BookSourcePart>()\n        if (scope.isEmpty()) {\n            list.addAll(appDb.bookSourceDao.allEnabledPart)\n        } else {\n            if (scope.contains(\"::\")) {\n                scope.substringAfter(\"::\").let {\n                    appDb.bookSourceDao.getBookSourcePart(it)?.let { source ->\n                        list.add(source)\n                    }\n                }\n            } else {\n                val oldScope = scope.splitNotBlank(\",\")\n                val newScope = oldScope.filter {\n                    val bookSources = appDb.bookSourceDao.getEnabledPartByGroup(it)\n                    list.addAll(bookSources)\n                    bookSources.isNotEmpty()\n                }\n                if (oldScope.size != newScope.size) {\n                    update(newScope)\n                    stateLiveData.postValue(scope)\n                }\n            }\n            if (list.isEmpty()) {\n                scope = \"\"\n                appDb.bookSourceDao.allEnabledPart.let {\n                    if (it.isNotEmpty()) {\n                        stateLiveData.postValue(scope)\n                        list.addAll(it)\n                    }\n                }\n            }\n        }\n        return list.sortedBy { it.customOrder }\n    }\n\n    fun isAll(): Boolean {\n        return scope.isEmpty()\n    }\n\n    fun save() {\n        AppConfig.searchScope = scope\n        if (isAll() || isSource() || scope.contains(\",\")) {\n            AppConfig.searchGroup = \"\"\n        } else {\n            AppConfig.searchGroup = scope\n        }\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/book/search/SearchScopeDialog.kt",
    "content": "package io.legado.app.ui.book.search\n\nimport android.annotation.SuppressLint\nimport android.os.Bundle\nimport android.view.View\nimport android.view.ViewGroup\nimport androidx.appcompat.widget.SearchView\nimport androidx.lifecycle.lifecycleScope\nimport androidx.recyclerview.widget.RecyclerView\nimport io.legado.app.R\nimport io.legado.app.base.BaseDialogFragment\nimport io.legado.app.base.adapter.ItemViewHolder\nimport io.legado.app.constant.AppLog\nimport io.legado.app.data.AppDatabase\nimport io.legado.app.data.appDb\nimport io.legado.app.data.entities.BookSourcePart\nimport io.legado.app.databinding.DialogSearchScopeBinding\nimport io.legado.app.databinding.ItemCheckBoxBinding\nimport io.legado.app.databinding.ItemRadioButtonBinding\nimport io.legado.app.lib.theme.primaryColor\nimport io.legado.app.utils.applyTint\nimport io.legado.app.utils.flowWithLifecycleAndDatabaseChange\nimport io.legado.app.utils.setLayout\nimport io.legado.app.utils.viewbindingdelegate.viewBinding\nimport kotlinx.coroutines.Dispatchers.IO\nimport kotlinx.coroutines.Job\nimport kotlinx.coroutines.delay\nimport kotlinx.coroutines.flow.catch\nimport kotlinx.coroutines.flow.conflate\nimport kotlinx.coroutines.flow.flowOn\nimport kotlinx.coroutines.launch\nimport kotlinx.coroutines.withContext\n\nclass SearchScopeDialog : BaseDialogFragment(R.layout.dialog_search_scope) {\n\n    private val binding by viewBinding(DialogSearchScopeBinding::bind)\n    private var sourceFlowJob: Job? = null\n    val callback: Callback get() = parentFragment as? Callback ?: activity as Callback\n    var groups: List<String> = emptyList()\n    val screenSources = arrayListOf<BookSourcePart>()\n    var screenText: String? = null\n\n    val adapter by lazy {\n        RecyclerAdapter()\n    }\n\n    override fun onStart() {\n        super.onStart()\n        setLayout(0.9f, 0.8f)\n    }\n\n    override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) {\n        binding.toolBar.setBackgroundColor(primaryColor)\n        binding.recyclerView.adapter = adapter\n        initMenu()\n        initSearchView()\n        initOtherView()\n        initData()\n    }\n\n    private fun initMenu() {\n        binding.toolBar.inflateMenu(R.menu.book_search_scope)\n        binding.toolBar.menu.applyTint(requireContext())\n    }\n\n    private fun initSearchView() {\n        val searchView = binding.toolBar.menu.findItem(R.id.menu_screen).actionView as SearchView\n        searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener {\n            override fun onQueryTextSubmit(query: String?): Boolean {\n                return false\n            }\n\n            override fun onQueryTextChange(newText: String?): Boolean {\n                screenText = newText\n                upData()\n                return false\n            }\n\n        })\n    }\n\n    private fun initOtherView() {\n        binding.rgScope.setOnCheckedChangeListener { _, checkedId ->\n            binding.toolBar.menu.findItem(R.id.menu_screen)?.isVisible = checkedId == R.id.rb_source\n            upData()\n        }\n        binding.tvCancel.setOnClickListener {\n            dismiss()\n        }\n        binding.tvAllSource.setOnClickListener {\n            callback.onSearchScopeOk(SearchScope(\"\"))\n            dismiss()\n        }\n        binding.tvOk.setOnClickListener {\n            if (binding.rbGroup.isChecked) {\n                callback.onSearchScopeOk(SearchScope(adapter.selectGroups))\n            } else {\n                val selectSource = adapter.selectSource\n                if (selectSource != null) {\n                    callback.onSearchScopeOk(SearchScope(selectSource))\n                } else {\n                    callback.onSearchScopeOk(SearchScope(\"\"))\n                }\n            }\n            dismiss()\n        }\n    }\n\n    private fun initData() {\n        lifecycleScope.launch {\n            groups = withContext(IO) {\n                appDb.bookSourceDao.allEnabledGroups()\n            }\n            upData()\n        }\n    }\n\n    @SuppressLint(\"NotifyDataSetChanged\")\n    private fun upData() {\n        if (binding.rbSource.isChecked) {\n            upBookSource(screenText)\n        } else {\n            adapter.notifyDataSetChanged()\n        }\n    }\n\n    @SuppressLint(\"NotifyDataSetChanged\")\n    private fun upBookSource(searchKey: String? = null) {\n        sourceFlowJob?.cancel()\n        sourceFlowJob = lifecycleScope.launch {\n            when {\n                searchKey.isNullOrEmpty() -> {\n                    appDb.bookSourceDao.flowAll()\n                }\n\n                else -> {\n                    appDb.bookSourceDao.flowSearch(searchKey)\n                }\n            }.flowWithLifecycleAndDatabaseChange(\n                lifecycle,\n                table = AppDatabase.BOOK_SOURCE_TABLE_NAME\n            ).catch {\n                AppLog.put(\"多分组/书源界面更新书源出错\", it)\n            }.flowOn(IO).conflate().collect { data ->\n                screenSources.clear()\n                screenSources.addAll(data)\n                adapter.notifyDataSetChanged()\n                delay(500)\n            }\n        }\n    }\n\n    inner class RecyclerAdapter : RecyclerView.Adapter<ItemViewHolder>() {\n\n        val selectGroups = arrayListOf<String>()\n        var selectSource: BookSourcePart? = null\n\n        override fun getItemViewType(position: Int): Int {\n            return if (binding.rbSource.isChecked) {\n                1\n            } else {\n                0\n            }\n        }\n\n        override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ItemViewHolder {\n            return if (viewType == 1) {\n                ItemViewHolder(ItemRadioButtonBinding.inflate(layoutInflater, parent, false))\n            } else {\n                ItemViewHolder(ItemCheckBoxBinding.inflate(layoutInflater, parent, false))\n            }\n        }\n\n        override fun onBindViewHolder(\n            holder: ItemViewHolder,\n            position: Int,\n            payloads: MutableList<Any>\n        ) {\n            if (payloads.isEmpty()) {\n                super.onBindViewHolder(holder, position, payloads)\n            } else {\n                when (holder.binding) {\n                    is ItemCheckBoxBinding -> {\n                        groups.getOrNull(position)?.let {\n                            holder.binding.checkBox.isChecked = selectGroups.contains(it)\n                            holder.binding.checkBox.text = it\n                        }\n                    }\n\n                    is ItemRadioButtonBinding -> {\n                        screenSources.getOrNull(position)?.let {\n                            holder.binding.radioButton.isChecked = selectSource == it\n                            holder.binding.radioButton.text = it.bookSourceName\n                        }\n                    }\n                }\n            }\n        }\n\n        override fun onBindViewHolder(holder: ItemViewHolder, position: Int) {\n            when (holder.binding) {\n                is ItemCheckBoxBinding -> {\n                    groups.getOrNull(position)?.let {\n                        holder.binding.checkBox.isChecked = selectGroups.contains(it)\n                        holder.binding.checkBox.text = it\n                        holder.binding.checkBox.setOnUserCheckedChangeListener { isChecked ->\n                            if (isChecked) {\n                                selectGroups.add(it)\n                            } else {\n                                selectGroups.remove(it)\n                            }\n                            holder.itemView.post {\n                                notifyItemRangeChanged(0, itemCount, \"up\")\n                            }\n                        }\n                    }\n                }\n\n                is ItemRadioButtonBinding -> {\n                    screenSources.getOrNull(position)?.let {\n                        holder.binding.radioButton.isChecked = selectSource == it\n                        holder.binding.radioButton.text = it.bookSourceName\n                        holder.binding.radioButton.setOnUserCheckedChangeListener { isChecked ->\n                            if (isChecked) {\n                                selectSource = it\n                            }\n                            holder.itemView.post {\n                                notifyItemRangeChanged(0, itemCount, \"up\")\n                            }\n                        }\n                    }\n                }\n            }\n        }\n\n        override fun getItemCount(): Int {\n            return if (binding.rbSource.isChecked) {\n                screenSources.size\n            } else {\n                groups.size\n            }\n        }\n\n    }\n\n    interface Callback {\n\n        /**\n         * 搜索范围确认\n         */\n        fun onSearchScopeOk(searchScope: SearchScope)\n\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/book/search/SearchViewModel.kt",
    "content": "package io.legado.app.ui.book.search\n\nimport android.app.Application\nimport android.os.Handler\nimport android.os.Looper\nimport androidx.lifecycle.MutableLiveData\nimport androidx.lifecycle.viewModelScope\nimport io.legado.app.base.BaseViewModel\nimport io.legado.app.constant.AppLog\nimport io.legado.app.data.appDb\nimport io.legado.app.data.entities.SearchBook\nimport io.legado.app.data.entities.SearchKeyword\nimport io.legado.app.help.book.isNotShelf\nimport io.legado.app.help.config.AppConfig\nimport io.legado.app.model.webBook.SearchModel\nimport io.legado.app.utils.ConflateLiveData\nimport io.legado.app.utils.toastOnUi\nimport kotlinx.coroutines.ExperimentalCoroutinesApi\nimport kotlinx.coroutines.flow.catch\nimport kotlinx.coroutines.flow.mapLatest\nimport java.util.concurrent.ConcurrentHashMap\n\n@OptIn(ExperimentalCoroutinesApi::class)\nclass SearchViewModel(application: Application) : BaseViewModel(application) {\n    val handler = Handler(Looper.getMainLooper())\n    val bookshelf: MutableSet<String> = ConcurrentHashMap.newKeySet()\n    val upAdapterLiveData = MutableLiveData<String>()\n    var searchBookLiveData = ConflateLiveData<List<SearchBook>>(1000)\n    val searchScope: SearchScope = SearchScope(AppConfig.searchScope)\n    var searchFinishLiveData = MutableLiveData<Boolean>()\n    var isSearchLiveData = MutableLiveData<Boolean>()\n    var searchKey: String = \"\"\n    var hasMore = true\n    private var searchID = 0L\n    private val searchModel = SearchModel(viewModelScope, object : SearchModel.CallBack {\n\n        override fun getSearchScope(): SearchScope {\n            return searchScope\n        }\n\n        override fun onSearchStart() {\n            isSearchLiveData.postValue(true)\n        }\n\n        override fun onSearchSuccess(searchBooks: List<SearchBook>) {\n            searchBookLiveData.postValue(searchBooks)\n        }\n\n        override fun onSearchFinish(isEmpty: Boolean, hasMore: Boolean) {\n            this@SearchViewModel.hasMore = hasMore\n            isSearchLiveData.postValue(false)\n            searchFinishLiveData.postValue(isEmpty)\n        }\n\n        override fun onSearchCancel(exception: Throwable?) {\n            isSearchLiveData.postValue(false)\n            exception?.let {\n                context.toastOnUi(it.localizedMessage)\n            }\n        }\n\n    })\n\n    init {\n        execute {\n            appDb.bookDao.flowAll().mapLatest { books ->\n                val keys = arrayListOf<String>()\n                books.filterNot { it.isNotShelf }\n                    .forEach {\n                        keys.add(\"${it.name}-${it.author}\")\n                        keys.add(it.name)\n                        keys.add(it.bookUrl)\n                    }\n                keys\n            }.catch {\n                AppLog.put(\"搜索界面获取书籍列表失败\\n${it.localizedMessage}\", it)\n            }.collect {\n                bookshelf.clear()\n                bookshelf.addAll(it)\n                upAdapterLiveData.postValue(\"isInBookshelf\")\n            }\n        }.onError {\n            AppLog.put(\"加载书架数据失败\", it)\n        }\n    }\n\n    fun isInBookShelf(book: SearchBook): Boolean {\n        val name = book.name\n        val author = book.author\n        val bookUrl = book.bookUrl\n        val key = if (author.isNotBlank()) \"$name-$author\" else name\n        return bookshelf.contains(key) || bookshelf.contains(bookUrl)\n    }\n\n    /**\n     * 开始搜索\n     */\n    fun search(key: String) {\n        execute {\n            if ((searchKey == key) || key.isNotEmpty()) {\n                searchModel.cancelSearch()\n                searchID = System.currentTimeMillis()\n                searchBookLiveData.postValue(emptyList())\n                searchKey = key\n                hasMore = true\n            }\n            if (searchKey.isEmpty()) {\n                return@execute\n            }\n            searchModel.search(searchID, searchKey)\n        }\n    }\n\n    /**\n     * 停止搜索\n     */\n    fun stop() {\n        searchModel.cancelSearch()\n    }\n\n    fun pause() {\n        searchModel.pause()\n    }\n\n    fun resume() {\n        searchModel.resume()\n    }\n\n    /**\n     * 保存搜索关键字\n     */\n    fun saveSearchKey(key: String) {\n        execute {\n            appDb.searchKeywordDao.get(key)?.let {\n                it.usage += 1\n                it.lastUseTime = System.currentTimeMillis()\n                appDb.searchKeywordDao.update(it)\n            } ?: appDb.searchKeywordDao.insert(SearchKeyword(key, 1))\n        }\n    }\n\n    /**\n     * 清楚搜索关键字\n     */\n    fun clearHistory() {\n        execute {\n            appDb.searchKeywordDao.deleteAll()\n        }\n    }\n\n    fun deleteHistory(searchKeyword: SearchKeyword) {\n        execute {\n            appDb.searchKeywordDao.delete(searchKeyword)\n        }\n    }\n\n    override fun onCleared() {\n        super.onCleared()\n        searchModel.close()\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/book/searchContent/SearchContentActivity.kt",
    "content": "package io.legado.app.ui.book.searchContent\n\nimport android.annotation.SuppressLint\nimport android.content.Intent\nimport android.os.Bundle\nimport android.view.Menu\nimport android.view.MenuItem\nimport android.widget.EditText\nimport androidx.activity.viewModels\nimport androidx.appcompat.widget.SearchView\nimport androidx.core.view.allViews\nimport androidx.lifecycle.lifecycleScope\nimport io.legado.app.R\nimport io.legado.app.base.VMBaseActivity\nimport io.legado.app.constant.AppLog\nimport io.legado.app.constant.EventBus\nimport io.legado.app.data.appDb\nimport io.legado.app.data.entities.Book\nimport io.legado.app.data.entities.BookChapter\nimport io.legado.app.databinding.ActivitySearchContentBinding\nimport io.legado.app.help.IntentData\nimport io.legado.app.help.book.BookHelp\nimport io.legado.app.help.book.isLocal\nimport io.legado.app.lib.theme.bottomBackground\nimport io.legado.app.lib.theme.getPrimaryTextColor\nimport io.legado.app.lib.theme.primaryTextColor\nimport io.legado.app.ui.widget.recycler.UpLinearLayoutManager\nimport io.legado.app.ui.widget.recycler.VerticalDivider\nimport io.legado.app.utils.ColorUtils\nimport io.legado.app.utils.applyNavigationBarMargin\nimport io.legado.app.utils.applyTint\nimport io.legado.app.utils.invisible\nimport io.legado.app.utils.observeEvent\nimport io.legado.app.utils.postEvent\nimport io.legado.app.utils.showSoftInput\nimport io.legado.app.utils.viewbindingdelegate.viewBinding\nimport io.legado.app.utils.visible\nimport kotlinx.coroutines.Dispatchers.IO\nimport kotlinx.coroutines.Job\nimport kotlinx.coroutines.ensureActive\nimport kotlinx.coroutines.launch\nimport kotlinx.coroutines.withContext\n\n\nclass SearchContentActivity :\n    VMBaseActivity<ActivitySearchContentBinding, SearchContentViewModel>(),\n    SearchContentAdapter.Callback {\n\n    override val binding by viewBinding(ActivitySearchContentBinding::inflate)\n    override val viewModel by viewModels<SearchContentViewModel>()\n    private val adapter by lazy { SearchContentAdapter(this, this) }\n    private val mLayoutManager by lazy { UpLinearLayoutManager(this) }\n    private val searchView: SearchView by lazy {\n        binding.titleBar.findViewById(R.id.search_view)\n    }\n    private var durChapterIndex = 0\n    private var searchJob: Job? = null\n    private var initJob: Job? = null\n\n    override fun onActivityCreated(savedInstanceState: Bundle?) {\n        val bbg = bottomBackground\n        val btc = getPrimaryTextColor(ColorUtils.isColorLight(bbg))\n        binding.llSearchBaseInfo.setBackgroundColor(bbg)\n        binding.llSearchBaseInfo.applyNavigationBarMargin()\n        binding.tvCurrentSearchInfo.setTextColor(btc)\n        binding.ivSearchContentTop.setColorFilter(btc)\n        binding.ivSearchContentBottom.setColorFilter(btc)\n        val searchResultList = IntentData.get<List<SearchResult>>(\"searchResultList\")\n        val position = intent.getIntExtra(\"searchResultIndex\", 0)\n        val noSearchResult = searchResultList == null\n        initSearchView(noSearchResult)\n        initRecyclerView()\n        initView()\n        val bookUrl = intent.getStringExtra(\"bookUrl\") ?: return\n        viewModel.initBook(bookUrl) {\n            initSearchResultList(searchResultList, position)\n            initBook(noSearchResult)\n        }\n    }\n\n    override fun onCompatCreateOptionsMenu(menu: Menu): Boolean {\n        menuInflater.inflate(R.menu.content_search, menu)\n        return super.onCompatCreateOptionsMenu(menu)\n    }\n\n    override fun onMenuOpened(featureId: Int, menu: Menu): Boolean {\n        menu.findItem(R.id.menu_enable_replace)?.isChecked = viewModel.replaceEnabled\n        return super.onMenuOpened(featureId, menu)\n    }\n\n    override fun onCompatOptionsItemSelected(item: MenuItem): Boolean {\n        when (item.itemId) {\n            R.id.menu_enable_replace -> {\n                viewModel.replaceEnabled = !viewModel.replaceEnabled\n                item.isChecked = viewModel.replaceEnabled\n            }\n        }\n        return super.onCompatOptionsItemSelected(item)\n    }\n\n    private fun initSearchResultList(list: List<SearchResult>?, position: Int) {\n        list ?: return\n        viewModel.searchResultList.addAll(list)\n        viewModel.searchResultCounts = list.size\n        adapter.setItems(list)\n        binding.recyclerView.scrollToPosition(position)\n    }\n\n    private fun initSearchView(requestFocus: Boolean) {\n        searchView.applyTint(primaryTextColor)\n        searchView.isSubmitButtonEnabled = true\n        searchView.queryHint = getString(R.string.search)\n        if (requestFocus) searchView.isIconified = false\n        searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener {\n            override fun onQueryTextSubmit(query: String): Boolean {\n                startContentSearch(query.trim())\n                searchView.clearFocus()\n                return false\n            }\n\n            override fun onQueryTextChange(newText: String): Boolean {\n                return false\n            }\n        })\n    }\n\n    private fun initRecyclerView() {\n        binding.recyclerView.layoutManager = mLayoutManager\n        binding.recyclerView.addItemDecoration(VerticalDivider(this))\n        binding.recyclerView.adapter = adapter\n    }\n\n    private fun initView() {\n        binding.ivSearchContentTop.setOnClickListener {\n            mLayoutManager.scrollToPositionWithOffset(0, 0)\n        }\n        binding.ivSearchContentBottom.setOnClickListener {\n            if (adapter.itemCount > 0) {\n                mLayoutManager.scrollToPositionWithOffset(adapter.itemCount - 1, 0)\n            }\n        }\n        binding.tvCurrentSearchInfo.setOnClickListener {\n            searchView.allViews.forEach { view ->\n                if (view is EditText) {\n                    view.showSoftInput()\n                    return@setOnClickListener\n                }\n            }\n        }\n        binding.fbStop.setOnClickListener {\n            searchJob?.cancel()\n        }\n    }\n\n    @SuppressLint(\"SetTextI18n\")\n    private fun initBook(submit: Boolean = true) {\n        binding.tvCurrentSearchInfo.text =\n            this.getString(R.string.search_content_size) + \": ${viewModel.searchResultCounts}\"\n        viewModel.book?.let {\n            initCacheFileNames(it)\n            durChapterIndex = it.durChapterIndex\n            intent.getStringExtra(\"searchWord\")?.let { searchWord ->\n                searchView.setQuery(searchWord, submit)\n            }\n        }\n    }\n\n    private fun initCacheFileNames(book: Book) {\n        initJob = lifecycleScope.launch {\n            withContext(IO) {\n                viewModel.cacheChapterNames.addAll(BookHelp.getChapterFiles(book))\n            }\n            adapter.notifyItemRangeChanged(0, adapter.itemCount, true)\n        }\n    }\n\n    override fun observeLiveBus() {\n        observeEvent<Pair<Book, BookChapter>>(EventBus.SAVE_CONTENT) { (book, chapter) ->\n            viewModel.book?.bookUrl?.let { bookUrl ->\n                if (book.bookUrl == bookUrl) {\n                    viewModel.cacheChapterNames.add(chapter.getFileName())\n                    adapter.notifyItemChanged(chapter.index, true)\n                }\n            }\n        }\n    }\n\n    @SuppressLint(\"SetTextI18n\")\n    fun startContentSearch(query: String) {\n        // 按章节搜索内容\n        if (query.isBlank()) return\n        searchJob?.cancel()\n        adapter.clearItems()\n        viewModel.searchResultList.clear()\n        viewModel.searchResultCounts = 0\n        viewModel.lastQuery = query\n        binding.refreshProgressBar.isAutoLoading = true\n        binding.fbStop.visible()\n        searchJob = lifecycleScope.launch(IO) {\n            initJob?.join()\n            kotlin.runCatching {\n                appDb.bookChapterDao.getChapterList(viewModel.bookUrl).forEach { bookChapter ->\n                    ensureActive()\n                    val searchResults = if (isLocalBook\n                        || viewModel.cacheChapterNames.contains(bookChapter.getFileName())\n                    ) {\n                        viewModel.searchChapter(query, bookChapter)\n                    } else {\n                        return@forEach\n                    }\n                    ensureActive()\n                    if (searchResults.isNotEmpty()) {\n                        viewModel.searchResultList.addAll(searchResults)\n                        binding.tvCurrentSearchInfo.post {\n                            binding.tvCurrentSearchInfo.text =\n                                this@SearchContentActivity.getString(R.string.search_content_size) + \": ${viewModel.searchResultCounts}\"\n                            adapter.addItems(searchResults)\n                        }\n                    }\n                }\n                if (viewModel.searchResultCounts == 0) {\n                    val noSearchResult =\n                        SearchResult(resultText = getString(R.string.search_content_empty))\n                    binding.tvCurrentSearchInfo.post {\n                        adapter.addItem(noSearchResult)\n                    }\n                }\n            }.onFailure {\n                AppLog.put(\"全文搜索出错\\n${it.localizedMessage}\", it)\n            }\n            binding.tvCurrentSearchInfo.post {\n                binding.fbStop.invisible()\n                binding.refreshProgressBar.isAutoLoading = false\n            }\n        }\n    }\n\n    private val isLocalBook: Boolean\n        get() = viewModel.book?.isLocal == true\n\n    override fun openSearchResult(searchResult: SearchResult, index: Int) {\n        searchJob?.cancel()\n        postEvent(EventBus.SEARCH_RESULT, viewModel.searchResultList as List<SearchResult>)\n        val searchData = Intent()\n        val key = System.currentTimeMillis()\n        IntentData.put(\"searchResult$key\", searchResult)\n        IntentData.put(\"searchResultList$key\", viewModel.searchResultList)\n        searchData.putExtra(\"key\", key)\n        searchData.putExtra(\"index\", index)\n        setResult(RESULT_OK, searchData)\n        finish()\n    }\n\n    override fun durChapterIndex(): Int {\n        return durChapterIndex\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/book/searchContent/SearchContentAdapter.kt",
    "content": "package io.legado.app.ui.book.searchContent\n\nimport android.content.Context\nimport android.view.ViewGroup\nimport io.legado.app.R\nimport io.legado.app.base.adapter.ItemViewHolder\nimport io.legado.app.base.adapter.RecyclerAdapter\nimport io.legado.app.databinding.ItemSearchListBinding\nimport io.legado.app.lib.theme.accentColor\nimport io.legado.app.utils.getCompatColor\nimport io.legado.app.utils.hexString\n\n\nclass SearchContentAdapter(context: Context, val callback: Callback) :\n    RecyclerAdapter<SearchResult, ItemSearchListBinding>(context) {\n\n    val textColor = context.getCompatColor(R.color.primaryText).hexString.substring(2)\n    val accentColor = context.accentColor.hexString.substring(2)\n\n    override fun getViewBinding(parent: ViewGroup): ItemSearchListBinding {\n        return ItemSearchListBinding.inflate(inflater, parent, false)\n    }\n\n    override fun convert(\n        holder: ItemViewHolder,\n        binding: ItemSearchListBinding,\n        item: SearchResult,\n        payloads: MutableList<Any>\n    ) {\n        binding.run {\n            val isDur = callback.durChapterIndex() == item.chapterIndex\n            if (payloads.isEmpty()) {\n                tvSearchResult.text = item.getHtmlCompat(textColor, accentColor)\n                tvSearchResult.paint.isFakeBoldText = isDur\n            }\n        }\n    }\n\n    override fun registerListener(holder: ItemViewHolder, binding: ItemSearchListBinding) {\n        holder.itemView.setOnClickListener {\n            getItem(holder.layoutPosition)?.let {\n                if (it.query.isNotBlank()) callback.openSearchResult(it, holder.layoutPosition)\n            }\n        }\n    }\n\n    interface Callback {\n        fun openSearchResult(searchResult: SearchResult, index: Int)\n        fun durChapterIndex(): Int\n    }\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/book/searchContent/SearchContentViewModel.kt",
    "content": "package io.legado.app.ui.book.searchContent\n\n\nimport android.app.Application\nimport io.legado.app.base.BaseViewModel\nimport io.legado.app.data.appDb\nimport io.legado.app.data.entities.Book\nimport io.legado.app.data.entities.BookChapter\nimport io.legado.app.help.book.BookHelp\nimport io.legado.app.help.book.ContentProcessor\nimport io.legado.app.help.config.AppConfig\nimport io.legado.app.utils.ChineseUtils\nimport kotlinx.coroutines.ensureActive\nimport kotlin.coroutines.coroutineContext\n\nclass SearchContentViewModel(application: Application) : BaseViewModel(application) {\n    var bookUrl: String = \"\"\n    var book: Book? = null\n    private var contentProcessor: ContentProcessor? = null\n    var lastQuery: String = \"\"\n    var searchResultCounts = 0\n    val cacheChapterNames = hashSetOf<String>()\n    val searchResultList: MutableList<SearchResult> = mutableListOf()\n    var replaceEnabled = false\n\n    fun initBook(bookUrl: String, success: () -> Unit) {\n        this.bookUrl = bookUrl\n        execute {\n            book = appDb.bookDao.getBook(bookUrl)\n            book?.let {\n                contentProcessor = ContentProcessor.get(it.name, it.origin)\n            }\n        }.onSuccess {\n            success.invoke()\n        }\n    }\n\n    suspend fun searchChapter(\n        query: String,\n        chapter: BookChapter\n    ): List<SearchResult> {\n        val searchResultsWithinChapter: MutableList<SearchResult> = mutableListOf()\n        val book = book ?: return searchResultsWithinChapter\n        val chapterContent = BookHelp.getContent(book, chapter) ?: return searchResultsWithinChapter\n        coroutineContext.ensureActive()\n        chapter.title = when (AppConfig.chineseConverterType) {\n            1 -> ChineseUtils.t2s(chapter.title)\n            2 -> ChineseUtils.s2t(chapter.title)\n            else -> chapter.title\n        }\n        coroutineContext.ensureActive()\n        val mContent = contentProcessor!!.getContent(\n            book, chapter, chapterContent, useReplace = replaceEnabled\n        ).toString()\n        val positions = searchPosition(mContent, query)\n        positions.forEachIndexed { index, position ->\n            coroutineContext.ensureActive()\n            val construct = getResultAndQueryIndex(mContent, position, query)\n            val result = SearchResult(\n                resultCountWithinChapter = index,\n                resultText = construct.second,\n                chapterTitle = chapter.title,\n                query = query,\n                chapterIndex = chapter.index,\n                queryIndexInResult = construct.first,\n                queryIndexInChapter = position\n            )\n            searchResultsWithinChapter.add(result)\n        }\n        searchResultCounts += searchResultsWithinChapter.size\n        return searchResultsWithinChapter\n    }\n\n    private suspend fun searchPosition(content: String, pattern: String): List<Int> {\n        val position: MutableList<Int> = mutableListOf()\n        var index = content.indexOf(pattern)\n        while (index >= 0) {\n            coroutineContext.ensureActive()\n            position.add(index)\n            index = content.indexOf(pattern, index + pattern.length)\n        }\n        return position\n    }\n\n    private fun getResultAndQueryIndex(\n        content: String,\n        queryIndexInContent: Int,\n        query: String\n    ): Pair<Int, String> {\n        // 左右移动20个字符，构建关键词周边文字，在搜索结果里显示\n        // 判断段落，只在关键词所在段落内分割\n        // 利用标点符号分割完整的句\n        // length和设置结合，自由调整周边文字长度\n        val length = 20\n        var po1 = queryIndexInContent - length\n        var po2 = queryIndexInContent + query.length + length\n        if (po1 < 0) {\n            po1 = 0\n        }\n        if (po2 > content.length) {\n            po2 = content.length\n        }\n        val queryIndexInResult = queryIndexInContent - po1\n        val newText = content.substring(po1, po2)\n        return queryIndexInResult to newText\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/book/searchContent/SearchResult.kt",
    "content": "package io.legado.app.ui.book.searchContent\n\nimport android.text.Spanned\nimport androidx.core.text.HtmlCompat\nimport io.legado.app.help.config.AppConfig\n\ndata class SearchResult(\n    val resultCount: Int = 0,\n    val resultCountWithinChapter: Int = 0,\n    val resultText: String = \"\",\n    val chapterTitle: String = \"\",\n    val query: String = \"\",\n    val pageSize: Int = 0,\n    val chapterIndex: Int = 0,\n    val pageIndex: Int = 0,\n    val queryIndexInResult: Int = 0,\n    val queryIndexInChapter: Int = 0\n) {\n\n    fun getHtmlCompat(textColor: String, accentColor: String): Spanned {\n        return if (query.isNotBlank()) {\n            val queryIndexInSurrounding = resultText.indexOf(query)\n            val leftString = resultText.substring(0, queryIndexInSurrounding)\n            val rightString =\n                resultText.substring(queryIndexInSurrounding + query.length, resultText.length)\n            \n            // 检查是否为墨水屏模式\n            val html = if (AppConfig.isEInkMode) {\n                // 墨水屏模式：使用下划线\n                buildString {\n                    append(\"<u>${chapterTitle}</u>\")\n                    append(\"<br>\")\n                    append(leftString)\n                    append(\"<u>${query}</u>\")\n                    append(rightString)\n                }\n            } else {\n                // 普通模式：使用颜色\n                buildString {\n                    append(chapterTitle.colorTextForHtml(accentColor))\n                    append(\"<br>\")\n                    append(leftString.colorTextForHtml(textColor))\n                    append(query.colorTextForHtml(accentColor))\n                    append(rightString.colorTextForHtml(textColor))\n                }\n            }\n            HtmlCompat.fromHtml(html, HtmlCompat.FROM_HTML_MODE_LEGACY)\n        } else {\n            val html = if (AppConfig.isEInkMode) {\n                resultText\n            } else {\n                resultText.colorTextForHtml(textColor)\n            }\n            HtmlCompat.fromHtml(html, HtmlCompat.FROM_HTML_MODE_LEGACY)\n        }\n    }\n\n    private fun String.colorTextForHtml(textColor: String) =\n        \"<font color=#${textColor}>$this</font>\"\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/book/source/debug/BookSourceDebugActivity.kt",
    "content": "package io.legado.app.ui.book.source.debug\n\nimport android.annotation.SuppressLint\nimport android.os.Bundle\nimport android.view.Menu\nimport android.view.MenuItem\nimport android.view.View\nimport androidx.activity.viewModels\nimport androidx.appcompat.widget.SearchView\nimport androidx.lifecycle.lifecycleScope\nimport io.legado.app.R\nimport io.legado.app.base.VMBaseActivity\nimport io.legado.app.databinding.ActivitySourceDebugBinding\nimport io.legado.app.help.source.clearExploreKindsCache\nimport io.legado.app.help.source.exploreKinds\nimport io.legado.app.lib.dialogs.selector\nimport io.legado.app.lib.theme.accentColor\nimport io.legado.app.lib.theme.primaryColor\nimport io.legado.app.ui.qrcode.QrCodeResult\nimport io.legado.app.ui.widget.dialog.TextDialog\nimport io.legado.app.utils.applyNavigationBarPadding\nimport io.legado.app.utils.launch\nimport io.legado.app.utils.setEdgeEffectColor\nimport io.legado.app.utils.showDialogFragment\nimport io.legado.app.utils.showHelp\nimport io.legado.app.utils.toastOnUi\nimport io.legado.app.utils.viewbindingdelegate.viewBinding\nimport kotlinx.coroutines.launch\nimport splitties.views.onClick\nimport splitties.views.onLongClick\n\nclass BookSourceDebugActivity : VMBaseActivity<ActivitySourceDebugBinding, BookSourceDebugModel>() {\n\n    override val binding by viewBinding(ActivitySourceDebugBinding::inflate)\n    override val viewModel by viewModels<BookSourceDebugModel>()\n\n    private val adapter by lazy { BookSourceDebugAdapter(this) }\n    private val searchView: SearchView by lazy {\n        binding.titleBar.findViewById(R.id.search_view)\n    }\n    private val qrCodeResult = registerForActivityResult(QrCodeResult()) {\n        it?.let {\n            startSearch(it)\n        }\n    }\n\n    override fun onActivityCreated(savedInstanceState: Bundle?) {\n        initRecyclerView()\n        initSearchView()\n        viewModel.init(intent.getStringExtra(\"key\")) {\n            initHelpView()\n        }\n        viewModel.observe { state, msg ->\n            lifecycleScope.launch {\n                adapter.addItem(msg)\n                if (state == -1 || state == 1000) {\n                    binding.rotateLoading.gone()\n                }\n            }\n        }\n    }\n\n    private fun initRecyclerView() {\n        binding.recyclerView.setEdgeEffectColor(primaryColor)\n        binding.recyclerView.adapter = adapter\n        binding.recyclerView.applyNavigationBarPadding()\n        binding.rotateLoading.loadingColor = accentColor\n    }\n\n    private fun initSearchView() {\n        searchView.onActionViewExpanded()\n        searchView.isSubmitButtonEnabled = true\n        searchView.queryHint = getString(R.string.search_book_key)\n        searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener {\n            override fun onQueryTextSubmit(query: String?): Boolean {\n                searchView.clearFocus()\n                openOrCloseHelp(false)\n                startSearch(query ?: \"我的\")\n                return true\n            }\n\n            override fun onQueryTextChange(newText: String?): Boolean {\n                return false\n            }\n        })\n        searchView.setOnQueryTextFocusChangeListener { _, hasFocus ->\n            openOrCloseHelp(hasFocus)\n        }\n        openOrCloseHelp(true)\n    }\n\n    @SuppressLint(\"SetTextI18n\")\n    private fun initHelpView() {\n        viewModel.bookSource?.ruleSearch?.checkKeyWord?.let {\n            if (it.isNotBlank()) {\n                binding.textMy.text = it\n            }\n        }\n        binding.textMy.onClick {\n            searchView.setQuery(binding.textMy.text, true)\n        }\n        binding.textXt.onClick {\n            searchView.setQuery(binding.textXt.text, true)\n        }\n        binding.textFx.onClick {\n            if (!binding.textFx.text.startsWith(\"ERROR:\")) {\n                searchView.setQuery(binding.textFx.text, true)\n            }\n        }\n        binding.textInfo.onClick {\n            if (!searchView.query.isNullOrBlank()) {\n                searchView.setQuery(searchView.query, true)\n            }\n        }\n        binding.textToc.onClick {\n            prefixAutoComplete(\"++\")\n        }\n        binding.textContent.onClick {\n            prefixAutoComplete(\"--\")\n        }\n        initExploreKinds()\n    }\n\n    @SuppressLint(\"SetTextI18n\")\n    private fun initExploreKinds() {\n        lifecycleScope.launch {\n            try {\n                val exploreKinds = viewModel.bookSource?.exploreKinds()?.filter {\n                    !it.url.isNullOrBlank()\n                }\n                exploreKinds?.firstOrNull()?.let {\n                    binding.textFx.text = \"${it.title}::${it.url}\"\n                    if (it.title.startsWith(\"ERROR:\")) {\n                        adapter.addItem(\"获取发现出错\\n${it.url}\")\n                        openOrCloseHelp(false)\n                        searchView.clearFocus()\n                        return@launch\n                    }\n                }\n                @Suppress(\"USELESS_ELVIS\")\n                exploreKinds?.map { it.title ?: \"\" }?.let { exploreKindTitles ->\n                    binding.textFx.onLongClick {\n                        selector(\"选择发现\", exploreKindTitles) { _, index ->\n                            val explore = exploreKinds[index]\n                            binding.textFx.text = \"${explore.title}::${explore.url}\"\n                            searchView.setQuery(binding.textFx.text, true)\n                        }\n                    }\n                }\n            } catch (e: NullPointerException) {\n                adapter.addItem(\"获取发现出错 JSON 数据错误\\n$e\")\n                openOrCloseHelp(false)\n                searchView.clearFocus()\n            }\n        }\n    }\n\n    private fun prefixAutoComplete(prefix: String) {\n        val query = searchView.query\n        if (query.isNullOrBlank() || query.length <= 2) {\n            searchView.setQuery(prefix, false)\n        } else {\n            if (!query.startsWith(prefix)) {\n                searchView.setQuery(\"$prefix$query\", true)\n            } else {\n                searchView.setQuery(query, true)\n            }\n        }\n    }\n\n    /**\n     * 打开关闭历史界面\n     */\n    private fun openOrCloseHelp(open: Boolean) {\n        if (open) {\n            binding.help.visibility = View.VISIBLE\n        } else {\n            binding.help.visibility = View.GONE\n        }\n    }\n\n    private fun startSearch(key: String) {\n        adapter.clearItems()\n        viewModel.startDebug(key, {\n            binding.rotateLoading.visible()\n        }, {\n            toastOnUi(\"未获取到书源\")\n        })\n    }\n\n    override fun onCompatCreateOptionsMenu(menu: Menu): Boolean {\n        menuInflater.inflate(R.menu.book_source_debug, menu)\n        return super.onCompatCreateOptionsMenu(menu)\n    }\n\n    override fun onCompatOptionsItemSelected(item: MenuItem): Boolean {\n        when (item.itemId) {\n            R.id.menu_scan -> qrCodeResult.launch()\n            R.id.menu_search_src -> showDialogFragment(TextDialog(\"html\", viewModel.searchSrc))\n            R.id.menu_book_src -> showDialogFragment(TextDialog(\"html\", viewModel.bookSrc))\n            R.id.menu_toc_src -> showDialogFragment(TextDialog(\"html\", viewModel.tocSrc))\n            R.id.menu_content_src -> showDialogFragment(TextDialog(\"html\", viewModel.contentSrc))\n            R.id.menu_refresh_explore -> lifecycleScope.launch {\n                viewModel.bookSource?.clearExploreKindsCache()\n                adapter.clearItems()\n                openOrCloseHelp(true)\n                initExploreKinds()\n            }\n\n            R.id.menu_help -> showHelp(\"debugHelp\")\n        }\n        return super.onCompatOptionsItemSelected(item)\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/book/source/debug/BookSourceDebugAdapter.kt",
    "content": "package io.legado.app.ui.book.source.debug\n\nimport android.content.Context\nimport android.view.View\nimport android.view.ViewGroup\nimport io.legado.app.R\nimport io.legado.app.base.adapter.ItemViewHolder\nimport io.legado.app.base.adapter.RecyclerAdapter\nimport io.legado.app.databinding.ItemLogBinding\n\nclass BookSourceDebugAdapter(context: Context) :\n    RecyclerAdapter<String, ItemLogBinding>(context) {\n\n    override fun getViewBinding(parent: ViewGroup): ItemLogBinding {\n        return ItemLogBinding.inflate(inflater, parent, false)\n    }\n\n    override fun convert(\n        holder: ItemViewHolder,\n        binding: ItemLogBinding,\n        item: String,\n        payloads: MutableList<Any>\n    ) {\n        binding.apply {\n            if (textView.getTag(R.id.tag1) == null) {\n                val listener = object : View.OnAttachStateChangeListener {\n                    override fun onViewAttachedToWindow(v: View) {\n                        textView.isCursorVisible = false\n                        textView.isCursorVisible = true\n                    }\n\n                    override fun onViewDetachedFromWindow(v: View) {}\n                }\n                textView.addOnAttachStateChangeListener(listener)\n                textView.setTag(R.id.tag1, listener)\n            }\n            textView.text = item\n        }\n    }\n\n    override fun registerListener(holder: ItemViewHolder, binding: ItemLogBinding) {\n        //nothing\n    }\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/book/source/debug/BookSourceDebugModel.kt",
    "content": "package io.legado.app.ui.book.source.debug\n\nimport android.app.Application\nimport io.legado.app.base.BaseViewModel\nimport io.legado.app.data.appDb\nimport io.legado.app.data.entities.BookSource\nimport io.legado.app.model.Debug\n\nclass BookSourceDebugModel(application: Application) : BaseViewModel(application),\n    Debug.Callback {\n\n    var bookSource: BookSource? = null\n    private var callback: ((Int, String) -> Unit)? = null\n    var searchSrc: String? = null\n    var bookSrc: String? = null\n    var tocSrc: String? = null\n    var contentSrc: String? = null\n\n    fun init(sourceUrl: String?, finally: () -> Unit) {\n        sourceUrl?.let {\n            //优先使用这个，不会抛出异常\n            execute {\n                bookSource = appDb.bookSourceDao.getBookSource(sourceUrl)\n            }.onFinally {\n                finally.invoke()\n            }\n        }\n    }\n\n    fun observe(callback: (Int, String) -> Unit) {\n        this.callback = callback\n    }\n\n    fun startDebug(key: String, start: (() -> Unit)? = null, error: (() -> Unit)? = null) {\n        execute {\n            Debug.callback = this@BookSourceDebugModel\n            Debug.startDebug(this, bookSource!!, key)\n        }.onStart {\n            start?.invoke()\n        }.onError {\n            error?.invoke()\n        }\n    }\n\n    override fun printLog(state: Int, msg: String) {\n        when (state) {\n            10 -> searchSrc = msg\n            20 -> bookSrc = msg\n            30 -> tocSrc = msg\n            40 -> contentSrc = msg\n            else -> callback?.invoke(state, msg)\n        }\n    }\n\n    override fun onCleared() {\n        super.onCleared()\n        Debug.cancelDebug(true)\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/book/source/edit/BookSourceEditActivity.kt",
    "content": "package io.legado.app.ui.book.source.edit\n\nimport android.content.Intent\nimport android.os.Bundle\nimport android.view.Menu\nimport android.view.MenuItem\nimport android.widget.EditText\nimport androidx.activity.viewModels\nimport androidx.lifecycle.lifecycleScope\nimport com.google.android.material.tabs.TabLayout\nimport com.google.zxing.qrcode.decoder.ErrorCorrectionLevel\nimport io.legado.app.R\nimport io.legado.app.base.VMBaseActivity\nimport io.legado.app.constant.BookSourceType\nimport io.legado.app.data.appDb\nimport io.legado.app.data.entities.BookSource\nimport io.legado.app.data.entities.rule.BookInfoRule\nimport io.legado.app.data.entities.rule.ContentRule\nimport io.legado.app.data.entities.rule.ExploreRule\nimport io.legado.app.data.entities.rule.SearchRule\nimport io.legado.app.data.entities.rule.TocRule\nimport io.legado.app.databinding.ActivityBookSourceEditBinding\nimport io.legado.app.help.config.LocalConfig\nimport io.legado.app.lib.dialogs.SelectItem\nimport io.legado.app.lib.dialogs.alert\nimport io.legado.app.lib.dialogs.selector\nimport io.legado.app.lib.theme.accentColor\nimport io.legado.app.lib.theme.backgroundColor\nimport io.legado.app.lib.theme.primaryColor\nimport io.legado.app.ui.book.search.SearchActivity\nimport io.legado.app.ui.book.search.SearchScope\nimport io.legado.app.ui.book.source.debug.BookSourceDebugActivity\nimport io.legado.app.ui.file.HandleFileContract\nimport io.legado.app.ui.login.SourceLoginActivity\nimport io.legado.app.ui.qrcode.QrCodeResult\nimport io.legado.app.ui.widget.dialog.UrlOptionDialog\nimport io.legado.app.ui.widget.dialog.VariableDialog\nimport io.legado.app.ui.widget.keyboard.KeyboardToolPop\nimport io.legado.app.ui.widget.recycler.NoChildScrollLinearLayoutManager\nimport io.legado.app.ui.widget.text.EditEntity\nimport io.legado.app.utils.GSON\nimport io.legado.app.utils.imeHeight\nimport io.legado.app.utils.isContentScheme\nimport io.legado.app.utils.launch\nimport io.legado.app.utils.navigationBarHeight\nimport io.legado.app.utils.sendToClip\nimport io.legado.app.utils.setEdgeEffectColor\nimport io.legado.app.utils.setOnApplyWindowInsetsListenerCompat\nimport io.legado.app.utils.share\nimport io.legado.app.utils.shareWithQr\nimport io.legado.app.utils.showDialogFragment\nimport io.legado.app.utils.showHelp\nimport io.legado.app.utils.startActivity\nimport io.legado.app.utils.viewbindingdelegate.viewBinding\nimport kotlinx.coroutines.Dispatchers.IO\nimport kotlinx.coroutines.launch\nimport kotlinx.coroutines.withContext\nimport splitties.views.bottomPadding\n\nclass BookSourceEditActivity :\n    VMBaseActivity<ActivityBookSourceEditBinding, BookSourceEditViewModel>(),\n    KeyboardToolPop.CallBack,\n    VariableDialog.Callback {\n\n    override val binding by viewBinding(ActivityBookSourceEditBinding::inflate)\n    override val viewModel by viewModels<BookSourceEditViewModel>()\n\n    private val adapter by lazy { BookSourceEditAdapter() }\n    private val sourceEntities: ArrayList<EditEntity> = ArrayList()\n    private val searchEntities: ArrayList<EditEntity> = ArrayList()\n    private val exploreEntities: ArrayList<EditEntity> = ArrayList()\n    private val infoEntities: ArrayList<EditEntity> = ArrayList()\n    private val tocEntities: ArrayList<EditEntity> = ArrayList()\n    private val contentEntities: ArrayList<EditEntity> = ArrayList()\n\n    //    private val reviewEntities: ArrayList<EditEntity> = ArrayList()\n    private val qrCodeResult = registerForActivityResult(QrCodeResult()) {\n        it ?: return@registerForActivityResult\n        viewModel.importSource(it) { source ->\n            upSourceView(source)\n        }\n    }\n    private val selectDoc = registerForActivityResult(HandleFileContract()) {\n        it.uri?.let { uri ->\n            if (uri.isContentScheme()) {\n                sendText(uri.toString())\n            } else {\n                sendText(uri.path.toString())\n            }\n        }\n    }\n\n    private val softKeyboardTool by lazy {\n        KeyboardToolPop(this, lifecycleScope, binding.root, this)\n    }\n\n    override fun onActivityCreated(savedInstanceState: Bundle?) {\n        softKeyboardTool.attachToWindow(window)\n        initView()\n        viewModel.initData(intent) {\n            upSourceView(viewModel.bookSource)\n        }\n    }\n\n    override fun onPostCreate(savedInstanceState: Bundle?) {\n        super.onPostCreate(savedInstanceState)\n        if (!LocalConfig.ruleHelpVersionIsLast) {\n            showHelp(\"ruleHelp\")\n        }\n    }\n\n    override fun onCompatCreateOptionsMenu(menu: Menu): Boolean {\n        menuInflater.inflate(R.menu.source_edit, menu)\n        return super.onCompatCreateOptionsMenu(menu)\n    }\n\n    override fun onMenuOpened(featureId: Int, menu: Menu): Boolean {\n        menu.findItem(R.id.menu_login)?.isVisible = !getSource().loginUrl.isNullOrBlank()\n        menu.findItem(R.id.menu_auto_complete)?.isChecked = viewModel.autoComplete\n        return super.onMenuOpened(featureId, menu)\n    }\n\n    override fun onCompatOptionsItemSelected(item: MenuItem): Boolean {\n        when (item.itemId) {\n            R.id.menu_save -> viewModel.save(getSource()) {\n                setResult(RESULT_OK, Intent().putExtra(\"origin\", it.bookSourceUrl))\n                finish()\n            }\n\n            R.id.menu_debug_source -> viewModel.save(getSource()) { source ->\n                startActivity<BookSourceDebugActivity> {\n                    putExtra(\"key\", source.bookSourceUrl)\n                }\n            }\n\n            R.id.menu_clear_cookie -> viewModel.clearCookie(getSource().bookSourceUrl)\n            R.id.menu_auto_complete -> viewModel.autoComplete = !viewModel.autoComplete\n            R.id.menu_copy_source -> sendToClip(GSON.toJson(getSource()))\n            R.id.menu_paste_source -> viewModel.pasteSource { upSourceView(it) }\n            R.id.menu_qr_code_camera -> qrCodeResult.launch()\n            R.id.menu_share_str -> share(GSON.toJson(getSource()))\n            R.id.menu_share_qr -> shareWithQr(\n                GSON.toJson(getSource()),\n                getString(R.string.share_book_source),\n                ErrorCorrectionLevel.L\n            )\n\n            R.id.menu_help -> showHelp(\"ruleHelp\")\n            R.id.menu_login -> viewModel.save(getSource()) { source ->\n                startActivity<SourceLoginActivity> {\n                    putExtra(\"type\", \"bookSource\")\n                    putExtra(\"key\", source.bookSourceUrl)\n                }\n            }\n\n            R.id.menu_set_source_variable -> setSourceVariable()\n            R.id.menu_search -> viewModel.save(getSource()) { source ->\n                startActivity<SearchActivity> {\n                    putExtra(\"searchScope\", SearchScope(source).toString())\n                }\n            }\n\n        }\n        return super.onCompatOptionsItemSelected(item)\n    }\n\n    private fun initView() {\n        binding.tabLayout.addTab(binding.tabLayout.newTab().apply {\n            setText(R.string.source_tab_base)\n        })\n        binding.tabLayout.addTab(binding.tabLayout.newTab().apply {\n            setText(R.string.source_tab_search)\n        })\n        binding.tabLayout.addTab(binding.tabLayout.newTab().apply {\n            setText(R.string.source_tab_find)\n        })\n        binding.tabLayout.addTab(binding.tabLayout.newTab().apply {\n            setText(R.string.source_tab_info)\n        })\n        binding.tabLayout.addTab(binding.tabLayout.newTab().apply {\n            setText(R.string.source_tab_toc)\n        })\n        binding.tabLayout.addTab(binding.tabLayout.newTab().apply {\n            setText(R.string.source_tab_content)\n        })\n        binding.recyclerView.setEdgeEffectColor(primaryColor)\n        binding.recyclerView.layoutManager = NoChildScrollLinearLayoutManager(this)\n        binding.recyclerView.adapter = adapter\n        binding.tabLayout.setBackgroundColor(backgroundColor)\n        binding.tabLayout.setSelectedTabIndicatorColor(accentColor)\n        binding.tabLayout.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener {\n            override fun onTabReselected(tab: TabLayout.Tab?) {\n\n            }\n\n            override fun onTabUnselected(tab: TabLayout.Tab?) {\n\n            }\n\n            override fun onTabSelected(tab: TabLayout.Tab?) {\n                setEditEntities(tab?.position)\n            }\n        })\n        binding.recyclerView.setOnApplyWindowInsetsListenerCompat { view, windowInsets ->\n            val navigationBarHeight = windowInsets.navigationBarHeight\n            val imeHeight = windowInsets.imeHeight\n            view.bottomPadding = if (imeHeight == 0) navigationBarHeight else 0\n            softKeyboardTool.initialPadding = imeHeight\n            windowInsets\n        }\n    }\n\n    override fun finish() {\n        val source = getSource()\n        if (!source.equal(viewModel.bookSource ?: BookSource())) {\n            alert(R.string.exit) {\n                setMessage(R.string.exit_no_save)\n                positiveButton(R.string.yes)\n                negativeButton(R.string.no) {\n                    super.finish()\n                }\n            }\n        } else {\n            super.finish()\n        }\n    }\n\n    override fun onDestroy() {\n        super.onDestroy()\n        softKeyboardTool.dismiss()\n    }\n\n    private fun setEditEntities(tabPosition: Int?) {\n        adapter.editEntities = when (tabPosition) {\n            1 -> searchEntities\n            2 -> exploreEntities\n            3 -> infoEntities\n            4 -> tocEntities\n            5 -> contentEntities\n//            6 -> reviewEntities\n            else -> sourceEntities\n        }\n        binding.recyclerView.scrollToPosition(0)\n    }\n\n    private fun upSourceView(bookSource: BookSource?) {\n        val bs = bookSource ?: BookSource()\n        bs.let {\n            binding.cbIsEnable.isChecked = it.enabled\n            binding.cbIsEnableExplore.isChecked = it.enabledExplore\n            binding.cbIsEnableCookie.isChecked = it.enabledCookieJar ?: false\n            binding.spType.setSelection(\n                when (it.bookSourceType) {\n                    BookSourceType.file -> 3\n                    BookSourceType.image -> 2\n                    BookSourceType.audio -> 1\n                    else -> 0\n                }\n            )\n        }\n        // 基本信息\n        sourceEntities.clear()\n        sourceEntities.apply {\n            add(EditEntity(\"bookSourceUrl\", bs.bookSourceUrl, R.string.source_url))\n            add(EditEntity(\"bookSourceName\", bs.bookSourceName, R.string.source_name))\n            add(EditEntity(\"bookSourceGroup\", bs.bookSourceGroup, R.string.source_group))\n            add(EditEntity(\"bookSourceComment\", bs.bookSourceComment, R.string.comment))\n            add(EditEntity(\"loginUrl\", bs.loginUrl, R.string.login_url))\n            add(EditEntity(\"loginUi\", bs.loginUi, R.string.login_ui))\n            add(EditEntity(\"loginCheckJs\", bs.loginCheckJs, R.string.login_check_js))\n            add(EditEntity(\"coverDecodeJs\", bs.coverDecodeJs, R.string.cover_decode_js))\n            add(EditEntity(\"bookUrlPattern\", bs.bookUrlPattern, R.string.book_url_pattern))\n            add(EditEntity(\"header\", bs.header, R.string.source_http_header))\n            add(EditEntity(\"variableComment\", bs.variableComment, R.string.variable_comment))\n            add(EditEntity(\"concurrentRate\", bs.concurrentRate, R.string.concurrent_rate))\n            add(EditEntity(\"jsLib\", bs.jsLib, \"jsLib\"))\n        }\n        // 搜索\n        val sr = bs.getSearchRule()\n        searchEntities.clear()\n        searchEntities.apply {\n            add(EditEntity(\"searchUrl\", bs.searchUrl, R.string.r_search_url))\n            add(EditEntity(\"checkKeyWord\", sr.checkKeyWord, R.string.check_key_word))\n            add(EditEntity(\"bookList\", sr.bookList, R.string.r_book_list))\n            add(EditEntity(\"name\", sr.name, R.string.r_book_name))\n            add(EditEntity(\"author\", sr.author, R.string.r_author))\n            add(EditEntity(\"kind\", sr.kind, R.string.rule_book_kind))\n            add(EditEntity(\"wordCount\", sr.wordCount, R.string.rule_word_count))\n            add(EditEntity(\"lastChapter\", sr.lastChapter, R.string.rule_last_chapter))\n            add(EditEntity(\"intro\", sr.intro, R.string.rule_book_intro))\n            add(EditEntity(\"coverUrl\", sr.coverUrl, R.string.rule_cover_url))\n            add(EditEntity(\"bookUrl\", sr.bookUrl, R.string.r_book_url))\n        }\n        // 发现\n        val er = bs.getExploreRule()\n        exploreEntities.clear()\n        exploreEntities.apply {\n            add(EditEntity(\"exploreUrl\", bs.exploreUrl, R.string.r_find_url))\n            add(EditEntity(\"bookList\", er.bookList, R.string.r_book_list))\n            add(EditEntity(\"name\", er.name, R.string.r_book_name))\n            add(EditEntity(\"author\", er.author, R.string.r_author))\n            add(EditEntity(\"kind\", er.kind, R.string.rule_book_kind))\n            add(EditEntity(\"wordCount\", er.wordCount, R.string.rule_word_count))\n            add(EditEntity(\"lastChapter\", er.lastChapter, R.string.rule_last_chapter))\n            add(EditEntity(\"intro\", er.intro, R.string.rule_book_intro))\n            add(EditEntity(\"coverUrl\", er.coverUrl, R.string.rule_cover_url))\n            add(EditEntity(\"bookUrl\", er.bookUrl, R.string.r_book_url))\n        }\n        // 详情页\n        val ir = bs.getBookInfoRule()\n        infoEntities.clear()\n        infoEntities.apply {\n            add(EditEntity(\"init\", ir.init, R.string.rule_book_info_init))\n            add(EditEntity(\"name\", ir.name, R.string.r_book_name))\n            add(EditEntity(\"author\", ir.author, R.string.r_author))\n            add(EditEntity(\"kind\", ir.kind, R.string.rule_book_kind))\n            add(EditEntity(\"wordCount\", ir.wordCount, R.string.rule_word_count))\n            add(EditEntity(\"lastChapter\", ir.lastChapter, R.string.rule_last_chapter))\n            add(EditEntity(\"intro\", ir.intro, R.string.rule_book_intro))\n            add(EditEntity(\"coverUrl\", ir.coverUrl, R.string.rule_cover_url))\n            add(EditEntity(\"tocUrl\", ir.tocUrl, R.string.rule_toc_url))\n            add(EditEntity(\"canReName\", ir.canReName, R.string.rule_can_re_name))\n            add(EditEntity(\"downloadUrls\", ir.downloadUrls, R.string.download_url_rule))\n        }\n        // 目录页\n        val tr = bs.getTocRule()\n        tocEntities.clear()\n        tocEntities.apply {\n            add(EditEntity(\"preUpdateJs\", tr.preUpdateJs, R.string.pre_update_js))\n            add(EditEntity(\"chapterList\", tr.chapterList, R.string.rule_chapter_list))\n            add(EditEntity(\"chapterName\", tr.chapterName, R.string.rule_chapter_name))\n            add(EditEntity(\"chapterUrl\", tr.chapterUrl, R.string.rule_chapter_url))\n            add(EditEntity(\"formatJs\", tr.formatJs, R.string.format_js_rule))\n            add(EditEntity(\"isVolume\", tr.isVolume, R.string.rule_is_volume))\n            add(EditEntity(\"updateTime\", tr.updateTime, R.string.rule_update_time))\n            add(EditEntity(\"isVip\", tr.isVip, R.string.rule_is_vip))\n            add(EditEntity(\"isPay\", tr.isPay, R.string.rule_is_pay))\n            add(EditEntity(\"nextTocUrl\", tr.nextTocUrl, R.string.rule_next_toc_url))\n        }\n        // 正文页\n        val cr = bs.getContentRule()\n        contentEntities.clear()\n        contentEntities.apply {\n            add(EditEntity(\"content\", cr.content, R.string.rule_book_content))\n            add(EditEntity(\"title\", cr.title, R.string.rule_chapter_name))\n            add(EditEntity(\"nextContentUrl\", cr.nextContentUrl, R.string.rule_next_content))\n            add(EditEntity(\"webJs\", cr.webJs, R.string.rule_web_js))\n            add(EditEntity(\"sourceRegex\", cr.sourceRegex, R.string.rule_source_regex))\n            add(EditEntity(\"replaceRegex\", cr.replaceRegex, R.string.rule_replace_regex))\n            add(EditEntity(\"imageStyle\", cr.imageStyle, R.string.rule_image_style))\n            add(EditEntity(\"imageDecode\", cr.imageDecode, R.string.rule_image_decode))\n            add(EditEntity(\"payAction\", cr.payAction, R.string.rule_pay_action))\n        }\n        // 段评\n//        val rr = bs.getReviewRule()\n//        reviewEntities.clear()\n//        reviewEntities.apply {\n//            add(EditEntity(\"reviewUrl\", rr.reviewUrl, R.string.rule_review_url))\n//            add(EditEntity(\"avatarRule\", rr.avatarRule, R.string.rule_avatar))\n//            add(EditEntity(\"contentRule\", rr.contentRule, R.string.rule_review_content))\n//            add(EditEntity(\"postTimeRule\", rr.postTimeRule, R.string.rule_post_time))\n//            add(EditEntity(\"reviewQuoteUrl\", rr.reviewQuoteUrl, R.string.rule_review_quote))\n//            add(EditEntity(\"voteUpUrl\", rr.voteUpUrl, R.string.review_vote_up))\n//            add(EditEntity(\"voteDownUrl\", rr.voteDownUrl, R.string.review_vote_down))\n//            add(EditEntity(\"postReviewUrl\", rr.postReviewUrl, R.string.post_review_url))\n//            add(EditEntity(\"postQuoteUrl\", rr.postQuoteUrl, R.string.post_quote_url))\n//            add(EditEntity(\"deleteUrl\", rr.deleteUrl, R.string.delete_review_url))\n//        }\n        binding.tabLayout.selectTab(binding.tabLayout.getTabAt(0))\n        setEditEntities(0)\n    }\n\n    private fun getSource(): BookSource {\n        val source = viewModel.bookSource?.copy() ?: BookSource()\n        source.enabled = binding.cbIsEnable.isChecked\n        source.enabledExplore = binding.cbIsEnableExplore.isChecked\n        source.enabledCookieJar = binding.cbIsEnableCookie.isChecked\n        source.bookSourceType = when (binding.spType.selectedItemPosition) {\n            3 -> BookSourceType.file\n            2 -> BookSourceType.image\n            1 -> BookSourceType.audio\n            else -> BookSourceType.default\n        }\n        val searchRule = SearchRule()\n        val exploreRule = ExploreRule()\n        val bookInfoRule = BookInfoRule()\n        val tocRule = TocRule()\n        val contentRule = ContentRule()\n//        val reviewRule = ReviewRule()\n        sourceEntities.forEach {\n            it.value = it.value?.takeIf { s -> s.isNotBlank() }\n            when (it.key) {\n                \"bookSourceUrl\" -> source.bookSourceUrl = it.value ?: \"\"\n                \"bookSourceName\" -> source.bookSourceName = it.value ?: \"\"\n                \"bookSourceGroup\" -> source.bookSourceGroup = it.value\n                \"loginUrl\" -> source.loginUrl = it.value\n                \"loginUi\" -> source.loginUi = it.value\n                \"loginCheckJs\" -> source.loginCheckJs = it.value\n                \"coverDecodeJs\" -> source.coverDecodeJs = it.value\n                \"bookUrlPattern\" -> source.bookUrlPattern = it.value\n                \"header\" -> source.header = it.value\n                \"bookSourceComment\" -> source.bookSourceComment = it.value\n                \"concurrentRate\" -> source.concurrentRate = it.value\n                \"variableComment\" -> source.variableComment = it.value\n                \"jsLib\" -> source.jsLib = it.value\n            }\n        }\n        searchEntities.forEach {\n            it.value = it.value?.takeIf { s -> s.isNotBlank() }\n            when (it.key) {\n                \"searchUrl\" -> source.searchUrl = it.value\n                \"checkKeyWord\" -> searchRule.checkKeyWord = it.value\n                \"bookList\" -> searchRule.bookList = it.value\n                \"name\" -> searchRule.name =\n                    viewModel.ruleComplete(it.value, searchRule.bookList)\n\n                \"author\" -> searchRule.author =\n                    viewModel.ruleComplete(it.value, searchRule.bookList)\n\n                \"kind\" -> searchRule.kind =\n                    viewModel.ruleComplete(it.value, searchRule.bookList)\n\n                \"intro\" -> searchRule.intro =\n                    viewModel.ruleComplete(it.value, searchRule.bookList)\n\n//                \"updateTime\" -> searchRule.updateTime =\n//                    viewModel.ruleComplete(it.value, searchRule.bookList)\n\n                \"wordCount\" -> searchRule.wordCount =\n                    viewModel.ruleComplete(it.value, searchRule.bookList)\n\n                \"lastChapter\" -> searchRule.lastChapter =\n                    viewModel.ruleComplete(it.value, searchRule.bookList)\n\n                \"coverUrl\" -> searchRule.coverUrl =\n                    viewModel.ruleComplete(it.value, searchRule.bookList, 3)\n\n                \"bookUrl\" -> searchRule.bookUrl =\n                    viewModel.ruleComplete(it.value, searchRule.bookList, 2)\n            }\n        }\n        exploreEntities.forEach {\n            it.value = it.value?.takeIf { s -> s.isNotBlank() }\n            when (it.key) {\n                \"exploreUrl\" -> source.exploreUrl = it.value\n                \"bookList\" -> exploreRule.bookList = it.value\n                \"name\" -> exploreRule.name =\n                    viewModel.ruleComplete(it.value, exploreRule.bookList)\n\n                \"author\" -> exploreRule.author =\n                    viewModel.ruleComplete(it.value, exploreRule.bookList)\n\n                \"kind\" -> exploreRule.kind =\n                    viewModel.ruleComplete(it.value, exploreRule.bookList)\n\n                \"intro\" -> exploreRule.intro =\n                    viewModel.ruleComplete(it.value, exploreRule.bookList)\n\n//                \"updateTime\" -> exploreRule.updateTime =\n//                    viewModel.ruleComplete(it.value, exploreRule.bookList)\n\n                \"wordCount\" -> exploreRule.wordCount =\n                    viewModel.ruleComplete(it.value, exploreRule.bookList)\n\n                \"lastChapter\" -> exploreRule.lastChapter =\n                    viewModel.ruleComplete(it.value, exploreRule.bookList)\n\n                \"coverUrl\" -> exploreRule.coverUrl =\n                    viewModel.ruleComplete(it.value, exploreRule.bookList, 3)\n\n                \"bookUrl\" -> exploreRule.bookUrl =\n                    viewModel.ruleComplete(it.value, exploreRule.bookList, 2)\n            }\n        }\n        infoEntities.forEach {\n            it.value = it.value?.takeIf { s -> s.isNotBlank() }\n            when (it.key) {\n                \"init\" -> bookInfoRule.init = it.value\n                \"name\" -> bookInfoRule.name = viewModel.ruleComplete(it.value, bookInfoRule.init)\n                \"author\" -> bookInfoRule.author =\n                    viewModel.ruleComplete(it.value, bookInfoRule.init)\n\n                \"kind\" -> bookInfoRule.kind =\n                    viewModel.ruleComplete(it.value, bookInfoRule.init)\n\n                \"intro\" -> bookInfoRule.intro =\n                    viewModel.ruleComplete(it.value, bookInfoRule.init)\n\n//                \"updateTime\" -> bookInfoRule.updateTime =\n//                    viewModel.ruleComplete(it.value, bookInfoRule.init)\n\n                \"wordCount\" -> bookInfoRule.wordCount =\n                    viewModel.ruleComplete(it.value, bookInfoRule.init)\n\n                \"lastChapter\" -> bookInfoRule.lastChapter =\n                    viewModel.ruleComplete(it.value, bookInfoRule.init)\n\n                \"coverUrl\" -> bookInfoRule.coverUrl =\n                    viewModel.ruleComplete(it.value, bookInfoRule.init, 3)\n\n                \"tocUrl\" -> bookInfoRule.tocUrl =\n                    viewModel.ruleComplete(it.value, bookInfoRule.init, 2)\n\n                \"canReName\" -> bookInfoRule.canReName = it.value\n                \"downloadUrls\" -> bookInfoRule.downloadUrls =\n                    viewModel.ruleComplete(it.value, bookInfoRule.init)\n            }\n        }\n        tocEntities.forEach {\n            it.value = it.value?.takeIf { s -> s.isNotBlank() }\n            when (it.key) {\n                \"preUpdateJs\" -> tocRule.preUpdateJs = it.value\n                \"chapterList\" -> tocRule.chapterList = it.value\n                \"chapterName\" -> tocRule.chapterName =\n                    viewModel.ruleComplete(it.value, tocRule.chapterList)\n\n                \"chapterUrl\" -> tocRule.chapterUrl =\n                    viewModel.ruleComplete(it.value, tocRule.chapterList, 2)\n\n                \"formatJs\" -> tocRule.formatJs = it.value\n                \"isVolume\" -> tocRule.isVolume = it.value\n                \"updateTime\" -> tocRule.updateTime = it.value\n                \"isVip\" -> tocRule.isVip = it.value\n                \"isPay\" -> tocRule.isPay = it.value\n                \"nextTocUrl\" -> tocRule.nextTocUrl =\n                    viewModel.ruleComplete(it.value, tocRule.chapterList, 2)\n            }\n        }\n        contentEntities.forEach {\n            it.value = it.value?.takeIf { s -> s.isNotBlank() }\n            when (it.key) {\n                \"content\" -> contentRule.content = viewModel.ruleComplete(it.value)\n                \"title\" -> contentRule.title = viewModel.ruleComplete(it.value)\n                \"nextContentUrl\" -> contentRule.nextContentUrl =\n                    viewModel.ruleComplete(it.value, type = 2)\n\n                \"webJs\" -> contentRule.webJs = it.value\n                \"sourceRegex\" -> contentRule.sourceRegex = it.value\n                \"replaceRegex\" -> contentRule.replaceRegex = it.value\n                \"imageStyle\" -> contentRule.imageStyle = it.value\n                \"imageDecode\" -> contentRule.imageDecode = it.value\n                \"payAction\" -> contentRule.payAction = it.value\n            }\n        }\n//        reviewEntities.forEach {\n//            when (it.key) {\n//                \"reviewUrl\" -> reviewRule.reviewUrl = it.value\n//                \"avatarRule\" -> reviewRule.avatarRule =\n//                    viewModel.ruleComplete(it.value, reviewRule.reviewUrl, 3)\n//\n//                \"contentRule\" -> reviewRule.contentRule =\n//                    viewModel.ruleComplete(it.value, reviewRule.reviewUrl)\n//\n//                \"postTimeRule\" -> reviewRule.postTimeRule =\n//                    viewModel.ruleComplete(it.value, reviewRule.reviewUrl)\n//\n//                \"reviewQuoteUrl\" -> reviewRule.reviewQuoteUrl =\n//                    viewModel.ruleComplete(it.value, reviewRule.reviewUrl, 2)\n//\n//                \"voteUpUrl\" -> reviewRule.voteUpUrl = it.value\n//                \"voteDownUrl\" -> reviewRule.voteDownUrl = it.value\n//                \"postReviewUrl\" -> reviewRule.postReviewUrl = it.value\n//                \"postQuoteUrl\" -> reviewRule.postQuoteUrl = it.value\n//                \"deleteUrl\" -> reviewRule.deleteUrl = it.value\n//            }\n//        }\n        source.ruleSearch = searchRule\n        source.ruleExplore = exploreRule\n        source.ruleBookInfo = bookInfoRule\n        source.ruleToc = tocRule\n        source.ruleContent = contentRule\n//        source.ruleReview = reviewRule\n        return source\n    }\n\n    private fun alertGroups() {\n        lifecycleScope.launch {\n            val groups = withContext(IO) {\n                appDb.bookSourceDao.allGroups()\n            }\n            selector(groups) { _, s, _ ->\n                sendText(s)\n            }\n        }\n    }\n\n    override fun helpActions(): List<SelectItem<String>> {\n        val helpActions = arrayListOf(\n            SelectItem(\"插入URL参数\", \"urlOption\"),\n            SelectItem(\"书源教程\", \"ruleHelp\"),\n            SelectItem(\"js教程\", \"jsHelp\"),\n            SelectItem(\"正则教程\", \"regexHelp\"),\n        )\n        val view = window.decorView.findFocus()\n        if (view is EditText) {\n            when (view.getTag(R.id.tag)) {\n                \"bookSourceGroup\" -> {\n                    helpActions.add(\n                        SelectItem(\"插入分组\", \"addGroup\")\n                    )\n                }\n\n                else -> {\n                    helpActions.add(\n                        SelectItem(\"选择文件\", \"selectFile\")\n                    )\n                }\n            }\n        }\n        return helpActions\n    }\n\n    override fun onHelpActionSelect(action: String) {\n        when (action) {\n            \"addGroup\" -> alertGroups()\n            \"urlOption\" -> UrlOptionDialog(this) { sendText(it) }.show()\n            \"ruleHelp\" -> showHelp(\"ruleHelp\")\n            \"jsHelp\" -> showHelp(\"jsHelp\")\n            \"regexHelp\" -> showHelp(\"regexHelp\")\n            \"selectFile\" -> selectDoc.launch {\n                mode = HandleFileContract.FILE\n            }\n        }\n    }\n\n    override fun sendText(text: String) {\n        if (text.isBlank()) return\n        val view = window.decorView.findFocus()\n        if (view is EditText) {\n            val start = view.selectionStart\n            val end = view.selectionEnd\n            val edit = view.editableText//获取EditText的文字\n            if (start < 0 || start >= edit.length) {\n                edit.append(text)\n            } else if (start > end) {\n                edit.replace(end, start, text)\n            } else {\n                edit.replace(start, end, text)//光标所在位置插入文字\n            }\n        }\n    }\n\n    private fun setSourceVariable() {\n        viewModel.save(getSource()) { source ->\n            lifecycleScope.launch {\n                val comment =\n                    source.getDisplayVariableComment(\"源变量可在js中通过source.getVariable()获取\")\n                val variable = withContext(IO) { source.getVariable() }\n                showDialogFragment(\n                    VariableDialog(\n                        getString(R.string.set_source_variable),\n                        source.getKey(),\n                        variable,\n                        comment\n                    )\n                )\n            }\n        }\n    }\n\n    override fun setVariable(key: String, variable: String?) {\n        viewModel.bookSource?.setVariable(variable)\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/book/source/edit/BookSourceEditAdapter.kt",
    "content": "package io.legado.app.ui.book.source.edit\n\nimport android.annotation.SuppressLint\nimport android.text.Editable\nimport android.text.TextWatcher\nimport android.view.LayoutInflater\nimport android.view.View\nimport android.view.ViewGroup\nimport androidx.recyclerview.widget.RecyclerView\nimport io.legado.app.R\nimport io.legado.app.databinding.ItemSourceEditBinding\nimport io.legado.app.help.config.AppConfig\nimport io.legado.app.ui.widget.code.addJsPattern\nimport io.legado.app.ui.widget.code.addJsonPattern\nimport io.legado.app.ui.widget.code.addLegadoPattern\nimport io.legado.app.ui.widget.text.EditEntity\n\nclass BookSourceEditAdapter : RecyclerView.Adapter<BookSourceEditAdapter.MyViewHolder>() {\n\n    val editEntityMaxLine = AppConfig.sourceEditMaxLine\n\n    var editEntities: ArrayList<EditEntity> = ArrayList()\n        @SuppressLint(\"NotifyDataSetChanged\")\n        set(value) {\n            field = value\n            notifyDataSetChanged()\n        }\n\n    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {\n        val binding = ItemSourceEditBinding\n            .inflate(LayoutInflater.from(parent.context), parent, false)\n        binding.editText.addLegadoPattern()\n        binding.editText.addJsonPattern()\n        binding.editText.addJsPattern()\n        return MyViewHolder(binding)\n    }\n\n    override fun onBindViewHolder(holder: MyViewHolder, position: Int) {\n        holder.bind(editEntities[position])\n    }\n\n    override fun getItemCount(): Int {\n        return editEntities.size\n    }\n\n    inner class MyViewHolder(val binding: ItemSourceEditBinding) :\n        RecyclerView.ViewHolder(binding.root) {\n\n        fun bind(editEntity: EditEntity) = binding.run {\n            editText.setTag(R.id.tag, editEntity.key)\n            editText.maxLines = editEntityMaxLine\n            if (editText.getTag(R.id.tag1) == null) {\n                val listener = object : View.OnAttachStateChangeListener {\n                    override fun onViewAttachedToWindow(v: View) {\n                        editText.isCursorVisible = false\n                        editText.isCursorVisible = true\n                        editText.isFocusable = true\n                        editText.isFocusableInTouchMode = true\n                    }\n\n                    override fun onViewDetachedFromWindow(v: View) {\n\n                    }\n                }\n                editText.addOnAttachStateChangeListener(listener)\n                editText.setTag(R.id.tag1, listener)\n            }\n            editText.getTag(R.id.tag2)?.let {\n                if (it is TextWatcher) {\n                    editText.removeTextChangedListener(it)\n                }\n            }\n            editText.setText(editEntity.value)\n            textInputLayout.hint = editEntity.hint\n            val textWatcher = object : TextWatcher {\n                override fun beforeTextChanged(\n                    s: CharSequence,\n                    start: Int,\n                    count: Int,\n                    after: Int\n                ) {\n\n                }\n\n                override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {\n\n                }\n\n                override fun afterTextChanged(s: Editable?) {\n                    editEntity.value = (s?.toString())\n                }\n            }\n            editText.addTextChangedListener(textWatcher)\n            editText.setTag(R.id.tag2, textWatcher)\n        }\n    }\n\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/book/source/edit/BookSourceEditViewModel.kt",
    "content": "package io.legado.app.ui.book.source.edit\n\nimport android.app.Application\nimport android.content.Intent\nimport io.legado.app.R\nimport io.legado.app.base.BaseViewModel\nimport io.legado.app.data.appDb\nimport io.legado.app.data.entities.BookSource\nimport io.legado.app.exception.NoStackTraceException\nimport io.legado.app.help.RuleComplete\nimport io.legado.app.help.config.SourceConfig\nimport io.legado.app.help.http.CookieStore\nimport io.legado.app.help.http.newCallStrResponse\nimport io.legado.app.help.http.okHttpClient\nimport io.legado.app.help.source.SourceHelp\nimport io.legado.app.help.source.clearExploreKindsCache\nimport io.legado.app.help.storage.ImportOldData\nimport io.legado.app.model.SharedJsScope\nimport io.legado.app.utils.GSON\nimport io.legado.app.utils.fromJsonArray\nimport io.legado.app.utils.fromJsonObject\nimport io.legado.app.utils.getClipText\nimport io.legado.app.utils.isAbsUrl\nimport io.legado.app.utils.isJsonArray\nimport io.legado.app.utils.isJsonObject\nimport io.legado.app.utils.jsonPath\nimport io.legado.app.utils.printOnDebug\nimport io.legado.app.utils.toastOnUi\nimport kotlinx.coroutines.Dispatchers\n\n\nclass BookSourceEditViewModel(application: Application) : BaseViewModel(application) {\n    var autoComplete = false\n    var bookSource: BookSource? = null\n\n    fun initData(intent: Intent, onFinally: () -> Unit) {\n        execute {\n            val sourceUrl = intent.getStringExtra(\"sourceUrl\")\n            var source: BookSource? = null\n            if (sourceUrl != null) {\n                source = appDb.bookSourceDao.getBookSource(sourceUrl)\n            }\n            source?.let {\n                bookSource = it\n            }\n        }.onFinally {\n            onFinally()\n        }\n    }\n\n    fun save(source: BookSource, success: ((BookSource) -> Unit)? = null) {\n        execute {\n            if (source.bookSourceUrl.isBlank() || source.bookSourceName.isBlank()) {\n                throw NoStackTraceException(context.getString(R.string.non_null_name_url))\n            }\n            val oldSource = bookSource ?: BookSource()\n            if (!source.equal(oldSource)) {\n                source.lastUpdateTime = System.currentTimeMillis()\n                if (oldSource.exploreUrl != source.exploreUrl) {\n                    oldSource.clearExploreKindsCache()\n                }\n                if (oldSource.jsLib != source.jsLib) {\n                    SharedJsScope.remove(oldSource.jsLib)\n                }\n            }\n            bookSource?.let {\n                if (it.bookSourceUrl != source.bookSourceUrl) {\n                    SourceHelp.deleteBookSource(it.bookSourceUrl)\n                } else {\n                    appDb.bookSourceDao.delete(it)\n                    SourceConfig.removeSource(it.bookSourceUrl)\n                }\n            }\n            appDb.bookSourceDao.insert(source)\n            bookSource = source\n            source\n        }.onSuccess {\n            success?.invoke(it)\n        }.onError {\n            context.toastOnUi(it.localizedMessage)\n            it.printOnDebug()\n        }\n    }\n\n    fun pasteSource(onSuccess: (source: BookSource) -> Unit) {\n        execute(context = Dispatchers.Main) {\n            val text = context.getClipText()\n            if (text.isNullOrBlank()) {\n                throw NoStackTraceException(\"剪贴板为空\")\n            } else {\n                importSource(text, onSuccess)\n            }\n        }.onError {\n            context.toastOnUi(it.localizedMessage ?: \"Error\")\n            it.printOnDebug()\n        }\n    }\n\n    fun importSource(text: String, finally: (source: BookSource) -> Unit) {\n        execute {\n            importSource(text)\n        }.onSuccess {\n            finally.invoke(it)\n        }.onError {\n            context.toastOnUi(it.localizedMessage ?: \"Error\")\n            it.printOnDebug()\n        }\n    }\n\n    suspend fun importSource(text: String): BookSource {\n        return when {\n            text.isAbsUrl() -> {\n                val text1 = okHttpClient.newCallStrResponse { url(text) }.body\n                importSource(text1!!)\n            }\n\n            text.isJsonArray() -> {\n                if (text.contains(\"ruleSearchUrl\") || text.contains(\"ruleFindUrl\")) {\n                    val items: List<Map<String, Any>> = jsonPath.parse(text).read(\"$\")\n                    val jsonItem = jsonPath.parse(items[0])\n                    ImportOldData.fromOldBookSource(jsonItem)\n                } else {\n                    GSON.fromJsonArray<BookSource>(text).getOrThrow()[0]\n                }\n            }\n\n            text.isJsonObject() -> {\n                if (text.contains(\"ruleSearchUrl\") || text.contains(\"ruleFindUrl\")) {\n                    val jsonItem = jsonPath.parse(text)\n                    ImportOldData.fromOldBookSource(jsonItem)\n                } else {\n                    GSON.fromJsonObject<BookSource>(text).getOrThrow()\n                }\n            }\n\n            else -> throw NoStackTraceException(\"格式不对\")\n        }\n    }\n\n    fun clearCookie(url: String) {\n        execute {\n            CookieStore.removeCookie(url)\n        }\n    }\n\n    fun ruleComplete(rule: String?, preRule: String? = null, type: Int = 1): String? {\n        if (autoComplete) {\n            return RuleComplete.autoComplete(rule, preRule, type)\n        }\n        return rule\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/book/source/manage/BookSourceActivity.kt",
    "content": "package io.legado.app.ui.book.source.manage\n\nimport android.annotation.SuppressLint\nimport android.os.Bundle\nimport android.view.Menu\nimport android.view.MenuItem\nimport android.view.SubMenu\nimport android.view.WindowManager\nimport androidx.activity.viewModels\nimport androidx.appcompat.app.AlertDialog\nimport androidx.appcompat.widget.PopupMenu\nimport androidx.appcompat.widget.SearchView\nimport androidx.core.os.bundleOf\nimport androidx.lifecycle.Lifecycle\nimport androidx.lifecycle.LifecycleOwner\nimport androidx.lifecycle.LifecycleRegistry\nimport androidx.lifecycle.lifecycleScope\nimport androidx.lifecycle.repeatOnLifecycle\nimport androidx.recyclerview.widget.ItemTouchHelper\nimport com.google.android.material.snackbar.Snackbar\nimport io.legado.app.R\nimport io.legado.app.base.VMBaseActivity\nimport io.legado.app.constant.AppLog\nimport io.legado.app.constant.EventBus\nimport io.legado.app.data.AppDatabase\nimport io.legado.app.data.appDb\nimport io.legado.app.data.entities.BookSourcePart\nimport io.legado.app.databinding.ActivityBookSourceBinding\nimport io.legado.app.databinding.DialogEditTextBinding\nimport io.legado.app.help.DirectLinkUpload\nimport io.legado.app.help.config.LocalConfig\nimport io.legado.app.lib.dialogs.alert\nimport io.legado.app.lib.theme.primaryColor\nimport io.legado.app.lib.theme.primaryTextColor\nimport io.legado.app.model.CheckSource\nimport io.legado.app.model.Debug\nimport io.legado.app.ui.association.ImportBookSourceDialog\nimport io.legado.app.ui.book.search.SearchActivity\nimport io.legado.app.ui.book.search.SearchScope\nimport io.legado.app.ui.book.source.debug.BookSourceDebugActivity\nimport io.legado.app.ui.book.source.edit.BookSourceEditActivity\nimport io.legado.app.ui.config.CheckSourceConfig\nimport io.legado.app.ui.file.HandleFileContract\nimport io.legado.app.ui.qrcode.QrCodeResult\nimport io.legado.app.ui.widget.SelectActionBar\nimport io.legado.app.ui.widget.recycler.DragSelectTouchHelper\nimport io.legado.app.ui.widget.recycler.ItemTouchCallback\nimport io.legado.app.ui.widget.recycler.VerticalDivider\nimport io.legado.app.utils.ACache\nimport io.legado.app.utils.NetworkUtils\nimport io.legado.app.utils.applyTint\nimport io.legado.app.utils.cnCompare\nimport io.legado.app.utils.dpToPx\nimport io.legado.app.utils.flowWithLifecycleAndDatabaseChange\nimport io.legado.app.utils.flowWithLifecycleAndDatabaseChangeFirst\nimport io.legado.app.utils.isAbsUrl\nimport io.legado.app.utils.launch\nimport io.legado.app.utils.observeEvent\nimport io.legado.app.utils.sendToClip\nimport io.legado.app.utils.setEdgeEffectColor\nimport io.legado.app.utils.share\nimport io.legado.app.utils.showDialogFragment\nimport io.legado.app.utils.showHelp\nimport io.legado.app.utils.splitNotBlank\nimport io.legado.app.utils.startActivity\nimport io.legado.app.utils.toastOnUi\nimport io.legado.app.utils.transaction\nimport io.legado.app.utils.viewbindingdelegate.viewBinding\nimport kotlinx.coroutines.Dispatchers.IO\nimport kotlinx.coroutines.Job\nimport kotlinx.coroutines.delay\nimport kotlinx.coroutines.flow.catch\nimport kotlinx.coroutines.flow.conflate\nimport kotlinx.coroutines.flow.distinctUntilChanged\nimport kotlinx.coroutines.flow.flowOn\nimport kotlinx.coroutines.flow.map\nimport kotlinx.coroutines.isActive\nimport kotlinx.coroutines.launch\n\n/**\n * 书源管理界面\n */\nclass BookSourceActivity : VMBaseActivity<ActivityBookSourceBinding, BookSourceViewModel>(),\n    PopupMenu.OnMenuItemClickListener,\n    BookSourceAdapter.CallBack,\n    SelectActionBar.CallBack,\n    SearchView.OnQueryTextListener {\n    override val binding by viewBinding(ActivityBookSourceBinding::inflate)\n    override val viewModel by viewModels<BookSourceViewModel>()\n    private val importRecordKey = \"bookSourceRecordKey\"\n    private val adapter by lazy { BookSourceAdapter(this, this, binding.recyclerView) }\n    private val itemTouchCallback by lazy { ItemTouchCallback(adapter) }\n    private val searchView: SearchView by lazy {\n        binding.titleBar.findViewById(R.id.search_view)\n    }\n    private var sourceFlowJob: Job? = null\n    private var checkMessageRefreshJob: Job? = null\n    private val groups = linkedSetOf<String>()\n    private var groupMenu: SubMenu? = null\n    override var sort = BookSourceSort.Default\n        private set\n    override var sortAscending = true\n        private set\n    private var snackBar: Snackbar? = null\n    private var groupSourcesByDomain = false\n    private val hostMap = hashMapOf<String, String>()\n    private val qrResult = registerForActivityResult(QrCodeResult()) {\n        it ?: return@registerForActivityResult\n        showDialogFragment(ImportBookSourceDialog(it))\n    }\n    private val importDoc = registerForActivityResult(HandleFileContract()) {\n        it.uri?.let { uri ->\n            showDialogFragment(ImportBookSourceDialog(uri.toString()))\n        }\n    }\n    private val exportDir = registerForActivityResult(HandleFileContract()) {\n        it.uri?.let { uri ->\n            alert(R.string.export_success) {\n                if (uri.toString().isAbsUrl()) {\n                    setMessage(DirectLinkUpload.getSummary())\n                }\n                val alertBinding = DialogEditTextBinding.inflate(layoutInflater).apply {\n                    editView.hint = getString(R.string.path)\n                    editView.setText(uri.toString())\n                }\n                customView { alertBinding.root }\n                okButton {\n                    sendToClip(uri.toString())\n                }\n            }\n        }\n    }\n    private val groupMenuLifecycleOwner = object : LifecycleOwner {\n        private val registry = LifecycleRegistry(this)\n        override val lifecycle: Lifecycle get() = registry\n\n        fun onMenuOpened() {\n            registry.handleLifecycleEvent(Lifecycle.Event.ON_START)\n        }\n\n        fun onMenuClosed() {\n            registry.handleLifecycleEvent(Lifecycle.Event.ON_STOP)\n        }\n\n    }\n\n    override fun onActivityCreated(savedInstanceState: Bundle?) {\n        initRecyclerView()\n        initSearchView()\n        upBookSource()\n        initLiveDataGroup()\n        initSelectActionBar()\n        resumeCheckSource()\n        if (!LocalConfig.bookSourcesHelpVersionIsLast) {\n            showHelp(\"SourceMBookHelp\")\n        }\n    }\n\n    override fun onCompatCreateOptionsMenu(menu: Menu): Boolean {\n        menuInflater.inflate(R.menu.book_source, menu)\n        return super.onCompatCreateOptionsMenu(menu)\n    }\n\n    override fun onPrepareOptionsMenu(menu: Menu): Boolean {\n        groupMenu = menu.findItem(R.id.menu_group).subMenu\n        val sortSubMenu = menu.findItem(R.id.action_sort).subMenu!!\n        sortSubMenu.findItem(R.id.menu_sort_desc).isChecked = !sortAscending\n        sortSubMenu.setGroupCheckable(R.id.menu_group_sort, true, true)\n        upGroupMenu()\n        return super.onPrepareOptionsMenu(menu)\n    }\n\n    override fun onCompatOptionsItemSelected(item: MenuItem): Boolean {\n        when (item.itemId) {\n            R.id.menu_add_book_source -> startActivity<BookSourceEditActivity>()\n            R.id.menu_import_qr -> qrResult.launch()\n            R.id.menu_group_manage -> showDialogFragment<GroupManageDialog>()\n            R.id.menu_import_local -> importDoc.launch {\n                mode = HandleFileContract.FILE\n                allowExtensions = arrayOf(\"txt\", \"json\")\n            }\n\n            R.id.menu_import_onLine -> showImportDialog()\n\n            R.id.menu_sort_desc -> {\n                sortAscending = !sortAscending\n                item.isChecked = !sortAscending\n                upBookSource(searchView.query?.toString())\n            }\n\n            R.id.menu_sort_manual -> {\n                item.isChecked = true\n                sort = BookSourceSort.Default\n                upBookSource(searchView.query?.toString())\n            }\n\n            R.id.menu_sort_auto -> {\n                item.isChecked = true\n                sort = BookSourceSort.Weight\n                upBookSource(searchView.query?.toString())\n            }\n\n            R.id.menu_sort_name -> {\n                item.isChecked = true\n                sort = BookSourceSort.Name\n                upBookSource(searchView.query?.toString())\n            }\n\n            R.id.menu_sort_url -> {\n                item.isChecked = true\n                sort = BookSourceSort.Url\n                upBookSource(searchView.query?.toString())\n            }\n\n            R.id.menu_sort_time -> {\n                item.isChecked = true\n                sort = BookSourceSort.Update\n                upBookSource(searchView.query?.toString())\n            }\n\n            R.id.menu_sort_respondTime -> {\n                item.isChecked = true\n                sort = BookSourceSort.Respond\n                upBookSource(searchView.query?.toString())\n            }\n\n            R.id.menu_sort_enable -> {\n                item.isChecked = true\n                sort = BookSourceSort.Enable\n                upBookSource(searchView.query?.toString())\n            }\n\n            R.id.menu_enabled_group -> {\n                searchView.setQuery(getString(R.string.enabled), true)\n            }\n\n            R.id.menu_disabled_group -> {\n                searchView.setQuery(getString(R.string.disabled), true)\n            }\n\n            R.id.menu_group_login -> {\n                searchView.setQuery(getString(R.string.need_login), true)\n            }\n\n            R.id.menu_group_null -> {\n                searchView.setQuery(getString(R.string.no_group), true)\n            }\n\n            R.id.menu_enabled_explore_group -> {\n                searchView.setQuery(getString(R.string.enabled_explore), true)\n            }\n\n            R.id.menu_disabled_explore_group -> {\n                searchView.setQuery(getString(R.string.disabled_explore), true)\n            }\n\n            R.id.menu_group_sources_by_domain -> {\n                item.isChecked = !item.isChecked\n                groupSourcesByDomain = item.isChecked\n                adapter.showSourceHost = item.isChecked\n                upBookSource(searchView.query?.toString())\n            }\n\n            R.id.menu_help -> showHelp(\"SourceMBookHelp\")\n        }\n        if (item.groupId == R.id.source_group) {\n            searchView.setQuery(\"group:${item.title}\", true)\n        }\n        return super.onCompatOptionsItemSelected(item)\n    }\n\n    private fun initRecyclerView() {\n        binding.recyclerView.setEdgeEffectColor(primaryColor)\n        binding.recyclerView.addItemDecoration(VerticalDivider(this))\n        binding.recyclerView.adapter = adapter\n        binding.recyclerView.recycledViewPool.setMaxRecycledViews(0, 15)\n        // When this page is opened, it is in selection mode\n        val dragSelectTouchHelper =\n            DragSelectTouchHelper(adapter.dragSelectCallback).setSlideArea(16, 50)\n        dragSelectTouchHelper.attachToRecyclerView(binding.recyclerView)\n        dragSelectTouchHelper.activeSlideSelect()\n        // Note: need judge selection first, so add ItemTouchHelper after it.\n        ItemTouchHelper(itemTouchCallback).attachToRecyclerView(binding.recyclerView)\n    }\n\n    private fun initSearchView() {\n        searchView.applyTint(primaryTextColor)\n        searchView.queryHint = getString(R.string.search_book_source)\n        searchView.setOnQueryTextListener(this)\n    }\n\n\n    private fun upBookSource(searchKey: String? = null) {\n        sourceFlowJob?.cancel()\n        sourceFlowJob = lifecycleScope.launch {\n            when {\n                searchKey.isNullOrEmpty() -> {\n                    appDb.bookSourceDao.flowAll()\n                }\n\n                searchKey == getString(R.string.enabled) -> {\n                    appDb.bookSourceDao.flowEnabled()\n                }\n\n                searchKey == getString(R.string.disabled) -> {\n                    appDb.bookSourceDao.flowDisabled()\n                }\n\n                searchKey == getString(R.string.need_login) -> {\n                    appDb.bookSourceDao.flowLogin()\n                }\n\n                searchKey == getString(R.string.no_group) -> {\n                    appDb.bookSourceDao.flowNoGroup()\n                }\n\n                searchKey == getString(R.string.enabled_explore) -> {\n                    appDb.bookSourceDao.flowEnabledExplore()\n                }\n\n                searchKey == getString(R.string.disabled_explore) -> {\n                    appDb.bookSourceDao.flowDisabledExplore()\n                }\n\n                searchKey.startsWith(\"group:\") -> {\n                    val key = searchKey.substringAfter(\"group:\")\n                    appDb.bookSourceDao.flowGroupSearch(key)\n                }\n\n                else -> {\n                    appDb.bookSourceDao.flowSearch(searchKey)\n                }\n            }.map { data ->\n                hostMap.clear()\n                if (groupSourcesByDomain) {\n                    data.sortedWith(\n                        compareBy<BookSourcePart> { getSourceHost(it.bookSourceUrl) == \"#\" }\n                            .thenBy { getSourceHost(it.bookSourceUrl) }\n                            .thenByDescending { it.lastUpdateTime })\n                } else if (sortAscending) {\n                    when (sort) {\n                        BookSourceSort.Weight -> data.sortedBy { it.weight }\n                        BookSourceSort.Name -> data.sortedWith { o1, o2 ->\n                            o1.bookSourceName.cnCompare(o2.bookSourceName)\n                        }\n\n                        BookSourceSort.Url -> data.sortedBy { it.bookSourceUrl }\n                        BookSourceSort.Update -> data.sortedByDescending { it.lastUpdateTime }\n                        BookSourceSort.Respond -> data.sortedBy { it.respondTime }\n                        BookSourceSort.Enable -> data.sortedWith { o1, o2 ->\n                            var sort = -o1.enabled.compareTo(o2.enabled)\n                            if (sort == 0) {\n                                sort = o1.bookSourceName.cnCompare(o2.bookSourceName)\n                            }\n                            sort\n                        }\n\n                        else -> data\n                    }\n                } else {\n                    when (sort) {\n                        BookSourceSort.Weight -> data.sortedByDescending { it.weight }\n                        BookSourceSort.Name -> data.sortedWith { o1, o2 ->\n                            o2.bookSourceName.cnCompare(o1.bookSourceName)\n                        }\n\n                        BookSourceSort.Url -> data.sortedByDescending { it.bookSourceUrl }\n                        BookSourceSort.Update -> data.sortedBy { it.lastUpdateTime }\n                        BookSourceSort.Respond -> data.sortedByDescending { it.respondTime }\n                        BookSourceSort.Enable -> data.sortedWith { o1, o2 ->\n                            var sort = o1.enabled.compareTo(o2.enabled)\n                            if (sort == 0) {\n                                sort = o1.bookSourceName.cnCompare(o2.bookSourceName)\n                            }\n                            sort\n                        }\n\n                        else -> data.reversed()\n                    }\n                }\n            }.flowWithLifecycleAndDatabaseChange(\n                lifecycle,\n                table = AppDatabase.BOOK_SOURCE_TABLE_NAME\n            ).catch {\n                AppLog.put(\"书源界面更新书源出错\", it)\n            }.flowOn(IO).conflate().collect { data ->\n                adapter.setItems(data, adapter.diffItemCallback, !Debug.isChecking)\n                itemTouchCallback.isCanDrag =\n                    sort == BookSourceSort.Default && !groupSourcesByDomain\n                delay(500)\n            }\n        }\n    }\n\n    private fun initLiveDataGroup() {\n        lifecycleScope.launch {\n            appDb.bookSourceDao.flowGroups()\n                .flowWithLifecycleAndDatabaseChange(\n                    lifecycle,\n                    table = AppDatabase.BOOK_SOURCE_TABLE_NAME\n                )\n                .flowWithLifecycleAndDatabaseChangeFirst(\n                    groupMenuLifecycleOwner.lifecycle,\n                    table = AppDatabase.BOOK_SOURCE_TABLE_NAME\n                )\n                .conflate()\n                .distinctUntilChanged()\n                .collect {\n                    groups.clear()\n                    groups.addAll(it)\n                    upGroupMenu()\n                    delay(500)\n                }\n        }\n    }\n\n    override fun selectAll(selectAll: Boolean) {\n        if (selectAll) {\n            adapter.selectAll()\n        } else {\n            adapter.revertSelection()\n        }\n    }\n\n    override fun revertSelection() {\n        adapter.revertSelection()\n    }\n\n    override fun onClickSelectBarMainAction() {\n        alert(titleResource = R.string.draw, messageResource = R.string.sure_del) {\n            yesButton { viewModel.del(adapter.selection) }\n            noButton()\n        }\n    }\n\n    override fun onMenuOpened(featureId: Int, menu: Menu): Boolean {\n        if (menu === groupMenu) {\n            groupMenuLifecycleOwner.onMenuOpened()\n        }\n        return super.onMenuOpened(featureId, menu)\n    }\n\n    override fun onPanelClosed(featureId: Int, menu: Menu) {\n        super.onPanelClosed(featureId, menu)\n        if (menu === groupMenu) {\n            groupMenuLifecycleOwner.onMenuClosed()\n        }\n    }\n\n    private fun initSelectActionBar() {\n        binding.selectActionBar.setMainActionText(R.string.delete)\n        binding.selectActionBar.inflateMenu(R.menu.book_source_sel)\n        binding.selectActionBar.setOnMenuItemClickListener(this)\n        binding.selectActionBar.setCallBack(this)\n    }\n\n    override fun onMenuItemClick(item: MenuItem?): Boolean {\n        when (item?.itemId) {\n            R.id.menu_enable_selection -> viewModel.enableSelection(adapter.selection)\n            R.id.menu_disable_selection -> viewModel.disableSelection(adapter.selection)\n            R.id.menu_enable_explore -> viewModel.enableSelectExplore(adapter.selection)\n            R.id.menu_disable_explore -> viewModel.disableSelectExplore(adapter.selection)\n            R.id.menu_check_source -> checkSource()\n            R.id.menu_top_sel -> viewModel.topSource(*adapter.selection.toTypedArray())\n            R.id.menu_bottom_sel -> viewModel.bottomSource(*adapter.selection.toTypedArray())\n            R.id.menu_add_group -> selectionAddToGroups()\n            R.id.menu_remove_group -> selectionRemoveFromGroups()\n            R.id.menu_export_selection -> viewModel.saveToFile(\n                adapter,\n                searchView.query?.toString(),\n                sortAscending,\n                sort\n            ) { file ->\n                exportDir.launch {\n                    mode = HandleFileContract.EXPORT\n                    fileData = HandleFileContract.FileData(\n                        \"bookSource.json\",\n                        file,\n                        \"application/json\"\n                    )\n                }\n            }\n\n            R.id.menu_share_source -> viewModel.saveToFile(\n                adapter,\n                searchView.query?.toString(),\n                sortAscending,\n                sort\n            ) {\n                share(it)\n            }\n\n            R.id.menu_check_selected_interval -> adapter.checkSelectedInterval()\n        }\n        return true\n    }\n\n    @SuppressLint(\"InflateParams\")\n    private fun checkSource() {\n        val dialog = alert(titleResource = R.string.search_book_key) {\n            val alertBinding = DialogEditTextBinding.inflate(layoutInflater).apply {\n                editView.hint = \"search word\"\n                editView.setText(CheckSource.keyword)\n            }\n            customView { alertBinding.root }\n            okButton {\n                keepScreenOn(true)\n                alertBinding.editView.text?.toString()?.let {\n                    if (it.isNotEmpty()) {\n                        CheckSource.keyword = it\n                    }\n                }\n                val selectItems = adapter.selection\n                CheckSource.start(this@BookSourceActivity, selectItems)\n                val adapterItems = adapter.getItems()\n                val firstItem = adapterItems.indexOf(selectItems.firstOrNull())\n                val lastItem = adapterItems.indexOf(selectItems.lastOrNull())\n                Debug.isChecking = firstItem >= 0 && lastItem >= 0\n                startCheckMessageRefreshJob(firstItem, lastItem)\n            }\n            neutralButton(R.string.check_source_config)\n            cancelButton()\n        }\n        //手动设置监听 避免点击打开校验设置后对话框关闭\n        dialog.getButton(AlertDialog.BUTTON_NEUTRAL)?.setOnClickListener {\n            showDialogFragment<CheckSourceConfig>()\n        }\n    }\n\n    private fun resumeCheckSource() {\n        if (!Debug.isChecking) {\n            return\n        }\n        keepScreenOn(true)\n        CheckSource.resume(this)\n        startCheckMessageRefreshJob(0, 0)\n    }\n\n    @SuppressLint(\"InflateParams\")\n    private fun selectionAddToGroups() {\n        alert(titleResource = R.string.add_group) {\n            val alertBinding = DialogEditTextBinding.inflate(layoutInflater).apply {\n                editView.setHint(R.string.group_name)\n                editView.setFilterValues(groups.toList())\n                editView.dropDownHeight = 180.dpToPx()\n            }\n            customView { alertBinding.root }\n            okButton {\n                alertBinding.editView.text?.toString()?.let {\n                    if (it.isNotEmpty()) {\n                        viewModel.selectionAddToGroups(adapter.selection, it)\n                    }\n                }\n            }\n            cancelButton()\n        }\n    }\n\n    @SuppressLint(\"InflateParams\")\n    private fun selectionRemoveFromGroups() {\n        alert(titleResource = R.string.remove_group) {\n            val alertBinding = DialogEditTextBinding.inflate(layoutInflater).apply {\n                editView.setHint(R.string.group_name)\n                editView.setFilterValues(groups.toList())\n                editView.dropDownHeight = 180.dpToPx()\n            }\n            customView { alertBinding.root }\n            okButton {\n                alertBinding.editView.text?.toString()?.let {\n                    if (it.isNotEmpty()) {\n                        viewModel.selectionRemoveFromGroups(adapter.selection, it)\n                    }\n                }\n            }\n            cancelButton()\n        }\n    }\n\n    private fun upGroupMenu() = groupMenu?.transaction { menu ->\n        menu.removeGroup(R.id.source_group)\n        groups.forEach {\n            menu.add(R.id.source_group, Menu.NONE, Menu.NONE, it)\n        }\n    }\n\n    @SuppressLint(\"InflateParams\")\n    private fun showImportDialog() {\n        val aCache = ACache.get(cacheDir = false)\n        val cacheUrls: MutableList<String> = aCache\n            .getAsString(importRecordKey)\n            ?.splitNotBlank(\",\")\n            ?.toMutableList() ?: mutableListOf()\n        alert(titleResource = R.string.import_on_line) {\n            val alertBinding = DialogEditTextBinding.inflate(layoutInflater).apply {\n                editView.hint = \"url\"\n                editView.setFilterValues(cacheUrls)\n                editView.delCallBack = {\n                    cacheUrls.remove(it)\n                    aCache.put(importRecordKey, cacheUrls.joinToString(\",\"))\n                }\n            }\n            customView { alertBinding.root }\n            okButton {\n                val text = alertBinding.editView.text?.toString()\n                text?.let {\n                    if (it.isAbsUrl() && !cacheUrls.contains(it)) {\n                        cacheUrls.add(0, it)\n                        aCache.put(importRecordKey, cacheUrls.joinToString(\",\"))\n                    }\n                    showDialogFragment(ImportBookSourceDialog(it))\n                }\n            }\n            cancelButton()\n        }\n    }\n\n    override fun observeLiveBus() {\n        observeEvent<String>(EventBus.CHECK_SOURCE) { msg ->\n            snackBar?.setText(msg) ?: let {\n                snackBar = Snackbar\n                    .make(binding.root, msg, Snackbar.LENGTH_INDEFINITE)\n                    .setAction(R.string.cancel) {\n                        CheckSource.stop(this)\n                        Debug.finishChecking()\n                    }.apply { show() }\n            }\n        }\n        observeEvent<Int>(EventBus.CHECK_SOURCE_DONE) {\n            keepScreenOn(false)\n            snackBar?.dismiss()\n            snackBar = null\n            adapter.notifyItemRangeChanged(\n                0,\n                adapter.itemCount,\n                bundleOf(Pair(\"checkSourceMessage\", null))\n            )\n            groups.forEach { group ->\n                if (group.contains(\"失效\") && searchView.query.isEmpty()) {\n                    searchView.setQuery(\"失效\", true)\n                    toastOnUi(\"发现有失效书源，已为您自动筛选！\")\n                }\n            }\n        }\n    }\n\n    private fun startCheckMessageRefreshJob(firstItem: Int, lastItem: Int) {\n        checkMessageRefreshJob?.cancel()\n        checkMessageRefreshJob = lifecycleScope.launch {\n            repeatOnLifecycle(Lifecycle.State.STARTED) {\n                while (isActive) {\n                    if (lastItem == 0) {\n                        adapter.notifyItemRangeChanged(\n                            0,\n                            adapter.itemCount,\n                            bundleOf(Pair(\"checkSourceMessage\", null))\n                        )\n                    } else {\n                        adapter.notifyItemRangeChanged(\n                            firstItem,\n                            lastItem + 1,\n                            bundleOf(Pair(\"checkSourceMessage\", null))\n                        )\n                    }\n                    if (!Debug.isChecking) {\n                        checkMessageRefreshJob?.cancel()\n                    }\n                    delay(300L)\n                }\n            }\n        }\n    }\n\n    /**\n     * 保持亮屏\n     */\n    private fun keepScreenOn(on: Boolean) {\n        val isScreenOn =\n            (window.attributes.flags and WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) != 0\n        if (on == isScreenOn) return\n        if (on) {\n            window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)\n        } else {\n            window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)\n        }\n    }\n\n    override fun upCountView() {\n        binding.selectActionBar\n            .upCountView(adapter.selection.size, adapter.itemCount)\n    }\n\n    override fun getSourceHost(origin: String): String {\n        return hostMap.getOrPut(origin) {\n            NetworkUtils.getSubDomainOrNull(origin) ?: \"#\"\n        }\n    }\n\n    override fun onQueryTextChange(newText: String?): Boolean {\n        newText?.let {\n            upBookSource(it)\n        }\n        return false\n    }\n\n    override fun onQueryTextSubmit(query: String?): Boolean {\n        return false\n    }\n\n    override fun del(bookSource: BookSourcePart) {\n        alert(R.string.draw) {\n            setMessage(getString(R.string.sure_del) + \"\\n\" + bookSource.bookSourceName)\n            noButton()\n            yesButton {\n                viewModel.del(listOf(bookSource))\n            }\n        }\n    }\n\n    override fun edit(bookSource: BookSourcePart) {\n        startActivity<BookSourceEditActivity> {\n            putExtra(\"sourceUrl\", bookSource.bookSourceUrl)\n        }\n    }\n\n    override fun upOrder(items: List<BookSourcePart>) {\n        viewModel.upOrder(items)\n    }\n\n    override fun enable(enable: Boolean, bookSource: BookSourcePart) {\n        viewModel.enable(enable, listOf(bookSource))\n    }\n\n    override fun enableExplore(enable: Boolean, bookSource: BookSourcePart) {\n        viewModel.enableExplore(enable, listOf(bookSource))\n    }\n\n    override fun toTop(bookSource: BookSourcePart) {\n        if (sortAscending) {\n            viewModel.topSource(bookSource)\n        } else {\n            viewModel.bottomSource(bookSource)\n        }\n    }\n\n    override fun toBottom(bookSource: BookSourcePart) {\n        if (sortAscending) {\n            viewModel.bottomSource(bookSource)\n        } else {\n            viewModel.topSource(bookSource)\n        }\n    }\n\n    override fun searchBook(bookSource: BookSourcePart) {\n        startActivity<SearchActivity> {\n            putExtra(\"searchScope\", SearchScope(bookSource).toString())\n        }\n    }\n\n    override fun debug(bookSource: BookSourcePart) {\n        startActivity<BookSourceDebugActivity> {\n            putExtra(\"key\", bookSource.bookSourceUrl)\n        }\n    }\n\n    override fun finish() {\n        if (searchView.query.isNullOrEmpty()) {\n            super.finish()\n        } else {\n            searchView.setQuery(\"\", true)\n        }\n    }\n\n    override fun onDestroy() {\n        super.onDestroy()\n        if (!Debug.isChecking) {\n            Debug.debugMessageMap.clear()\n        }\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/book/source/manage/BookSourceAdapter.kt",
    "content": "package io.legado.app.ui.book.source.manage\n\nimport android.content.Context\nimport android.graphics.Color\nimport android.os.Bundle\nimport android.view.View\nimport android.view.ViewGroup\nimport android.widget.ImageView\nimport android.widget.PopupMenu\nimport androidx.core.os.bundleOf\nimport androidx.core.view.doOnLayout\nimport androidx.recyclerview.widget.DiffUtil\nimport androidx.recyclerview.widget.RecyclerView\nimport io.legado.app.R\nimport io.legado.app.base.adapter.ItemViewHolder\nimport io.legado.app.base.adapter.RecyclerAdapter\nimport io.legado.app.data.entities.BookSourcePart\nimport io.legado.app.databinding.ItemBookSourceBinding\nimport io.legado.app.lib.theme.backgroundColor\nimport io.legado.app.model.Debug\nimport io.legado.app.ui.login.SourceLoginActivity\nimport io.legado.app.ui.widget.recycler.DragSelectTouchHelper\nimport io.legado.app.ui.widget.recycler.ItemTouchCallback\nimport io.legado.app.utils.ColorUtils\nimport io.legado.app.utils.buildMainHandler\nimport io.legado.app.utils.gone\nimport io.legado.app.utils.invisible\nimport io.legado.app.utils.startActivity\nimport io.legado.app.utils.visible\nimport java.util.Collections\n\n\nclass BookSourceAdapter(\n    context: Context,\n    private val callBack: CallBack,\n    private val recyclerView: RecyclerView\n) : RecyclerAdapter<BookSourcePart, ItemBookSourceBinding>(context),\n    ItemTouchCallback.Callback {\n\n    private val selected = linkedSetOf<BookSourcePart>()\n    private val finalMessageRegex = Regex(\"成功|失败\")\n    private val handler = buildMainHandler()\n    var showSourceHost = false\n\n    val selection: List<BookSourcePart>\n        get() {\n            return getItems().filter {\n                selected.contains(it)\n            }\n        }\n\n    val diffItemCallback = object : DiffUtil.ItemCallback<BookSourcePart>() {\n\n        override fun areItemsTheSame(oldItem: BookSourcePart, newItem: BookSourcePart): Boolean {\n            return oldItem.bookSourceUrl == newItem.bookSourceUrl\n        }\n\n        override fun areContentsTheSame(oldItem: BookSourcePart, newItem: BookSourcePart): Boolean {\n            return oldItem.bookSourceName == newItem.bookSourceName\n                    && oldItem.bookSourceGroup == newItem.bookSourceGroup\n                    && oldItem.enabled == newItem.enabled\n                    && oldItem.enabledExplore == newItem.enabledExplore\n                    && oldItem.hasExploreUrl == newItem.hasExploreUrl\n        }\n\n        override fun getChangePayload(oldItem: BookSourcePart, newItem: BookSourcePart): Any? {\n            val payload = Bundle()\n            if (oldItem.bookSourceName != newItem.bookSourceName\n                || oldItem.bookSourceGroup != newItem.bookSourceGroup\n            ) {\n                payload.putBoolean(\"upName\", true)\n            }\n            if (oldItem.enabled != newItem.enabled) {\n                payload.putBoolean(\"enabled\", newItem.enabled)\n            }\n            if (oldItem.enabledExplore != newItem.enabledExplore ||\n                oldItem.hasExploreUrl != newItem.hasExploreUrl\n            ) {\n                payload.putBoolean(\"upExplore\", true)\n            }\n            if (payload.isEmpty) {\n                return null\n            }\n            return payload\n        }\n\n    }\n\n    override fun getViewBinding(parent: ViewGroup): ItemBookSourceBinding {\n        return ItemBookSourceBinding.inflate(inflater, parent, false)\n    }\n\n    override fun convert(\n        holder: ItemViewHolder,\n        binding: ItemBookSourceBinding,\n        item: BookSourcePart,\n        payloads: MutableList<Any>\n    ) {\n        binding.run {\n            if (payloads.isEmpty()) {\n                root.setBackgroundColor(ColorUtils.withAlpha(context.backgroundColor, 0.5f))\n                cbBookSource.text = item.getDisPlayNameGroup()\n                swtEnabled.isChecked = item.enabled\n                cbBookSource.isChecked = selected.contains(item)\n                upCheckSourceMessage(binding, item)\n                upShowExplore(ivExplore, item)\n                upSourceHost(binding, holder.layoutPosition)\n            } else {\n                for (i in payloads.indices) {\n                    val bundle = payloads[i] as Bundle\n                    bundle.keySet().forEach {\n                        when (it) {\n                            \"enabled\" -> swtEnabled.isChecked = bundle.getBoolean(\"enabled\")\n                            \"upName\" -> cbBookSource.text = item.getDisPlayNameGroup()\n                            \"upExplore\" -> upShowExplore(ivExplore, item)\n                            \"selected\" -> cbBookSource.isChecked = selected.contains(item)\n                            \"checkSourceMessage\" -> upCheckSourceMessage(binding, item)\n                            \"upSourceHost\" -> upSourceHost(binding, holder.layoutPosition)\n                        }\n                    }\n                }\n            }\n        }\n    }\n\n    override fun registerListener(holder: ItemViewHolder, binding: ItemBookSourceBinding) {\n        binding.apply {\n            swtEnabled.setOnUserCheckedChangeListener { checked ->\n                getItem(holder.layoutPosition)?.let {\n                    it.enabled = checked\n                    callBack.enable(checked, it)\n                }\n            }\n            cbBookSource.setOnUserCheckedChangeListener { checked ->\n                getItem(holder.layoutPosition)?.let {\n                    if (checked) {\n                        selected.add(it)\n                    } else {\n                        selected.remove(it)\n                    }\n                    callBack.upCountView()\n                }\n            }\n            ivEdit.setOnClickListener {\n                getItem(holder.layoutPosition)?.let {\n                    callBack.edit(it)\n                }\n            }\n            ivMenuMore.setOnClickListener {\n                showMenu(ivMenuMore, holder.layoutPosition)\n            }\n        }\n    }\n\n    override fun onCurrentListChanged() {\n        callBack.upCountView()\n        recyclerView.doOnLayout {\n            handler.post {\n                notifyItemRangeChanged(0, itemCount, bundleOf(\"upSourceHost\" to null))\n            }\n        }\n    }\n\n    private fun showMenu(view: View, position: Int) {\n        val source = getItem(position) ?: return\n        val popupMenu = PopupMenu(context, view)\n        popupMenu.inflate(R.menu.book_source_item)\n        popupMenu.menu.findItem(R.id.menu_top).isVisible = callBack.sort == BookSourceSort.Default\n        popupMenu.menu.findItem(R.id.menu_bottom).isVisible =\n            callBack.sort == BookSourceSort.Default\n        val qyMenu = popupMenu.menu.findItem(R.id.menu_enable_explore)\n        if (!source.hasExploreUrl) {\n            qyMenu.isVisible = false\n        } else {\n            if (source.enabledExplore) {\n                qyMenu.setTitle(R.string.disable_explore)\n            } else {\n                qyMenu.setTitle(R.string.enable_explore)\n            }\n        }\n        val loginMenu = popupMenu.menu.findItem(R.id.menu_login)\n        loginMenu.isVisible = source.hasLoginUrl\n        popupMenu.setOnMenuItemClickListener { menuItem ->\n            when (menuItem.itemId) {\n                R.id.menu_top -> callBack.toTop(source)\n                R.id.menu_bottom -> callBack.toBottom(source)\n                R.id.menu_login -> context.startActivity<SourceLoginActivity> {\n                    putExtra(\"type\", \"bookSource\")\n                    putExtra(\"key\", source.bookSourceUrl)\n                }\n\n                R.id.menu_search -> callBack.searchBook(source)\n                R.id.menu_debug_source -> callBack.debug(source)\n                R.id.menu_del -> {\n                    callBack.del(source)\n                    selected.remove(source)\n                }\n\n                R.id.menu_enable_explore -> {\n                    callBack.enableExplore(!source.enabledExplore, source)\n                }\n            }\n            true\n        }\n        popupMenu.show()\n    }\n\n    private fun upShowExplore(iv: ImageView, source: BookSourcePart) {\n        when {\n            !source.hasExploreUrl -> {\n                iv.invisible()\n            }\n\n            source.enabledExplore -> {\n                iv.setColorFilter(Color.GREEN)\n                iv.visible()\n                iv.contentDescription = context.getString(R.string.tag_explore_enabled)\n            }\n\n            else -> {\n                iv.setColorFilter(Color.RED)\n                iv.visible()\n                iv.contentDescription = context.getString(R.string.tag_explore_disabled)\n            }\n        }\n    }\n\n    private fun upCheckSourceMessage(\n        binding: ItemBookSourceBinding,\n        item: BookSourcePart\n    ) = binding.run {\n        val msg = Debug.debugMessageMap[item.bookSourceUrl] ?: \"\"\n        ivDebugText.text = msg\n        val isEmpty = msg.isEmpty()\n        var isFinalMessage = msg.contains(finalMessageRegex)\n        if (!Debug.isChecking && !isFinalMessage) {\n            Debug.updateFinalMessage(item.bookSourceUrl, \"校验失败\")\n            ivDebugText.text = Debug.debugMessageMap[item.bookSourceUrl] ?: \"\"\n            isFinalMessage = true\n        }\n        ivDebugText.visibility =\n            if (!isEmpty) View.VISIBLE else View.GONE\n        ivProgressBar.visibility =\n            if (isFinalMessage || isEmpty || !Debug.isChecking) View.GONE else View.VISIBLE\n    }\n\n    private fun upSourceHost(binding: ItemBookSourceBinding, position: Int) = binding.run {\n        if (showSourceHost && isItemHeader(position)) {\n            tvHostText.text = getHeaderText(position)\n            tvHostText.visible()\n        } else {\n            tvHostText.gone()\n        }\n    }\n\n    fun selectAll() {\n        getItems().forEach {\n            selected.add(it)\n        }\n        notifyItemRangeChanged(0, itemCount, bundleOf(Pair(\"selected\", null)))\n        callBack.upCountView()\n    }\n\n    fun revertSelection() {\n        getItems().forEach {\n            if (selected.contains(it)) {\n                selected.remove(it)\n            } else {\n                selected.add(it)\n            }\n        }\n        notifyItemRangeChanged(0, itemCount, bundleOf(Pair(\"selected\", null)))\n        callBack.upCountView()\n    }\n\n    fun checkSelectedInterval() {\n        val selectedPosition = linkedSetOf<Int>()\n        getItems().forEachIndexed { index, it ->\n            if (selected.contains(it)) {\n                selectedPosition.add(index)\n            }\n        }\n        val minPosition = Collections.min(selectedPosition)\n        val maxPosition = Collections.max(selectedPosition)\n        val itemCount = maxPosition - minPosition + 1\n        for (i in minPosition..maxPosition) {\n            getItem(i)?.let {\n                selected.add(it)\n            }\n        }\n        notifyItemRangeChanged(minPosition, itemCount, bundleOf(Pair(\"selected\", null)))\n        callBack.upCountView()\n    }\n\n    fun getHeaderText(position: Int): String {\n        val source = getItem(position)!!\n        return callBack.getSourceHost(source.bookSourceUrl)\n    }\n\n    fun isItemHeader(position: Int): Boolean {\n        if (position == 0) return true\n        val lastHost = getHeaderText(position - 1)\n        val curHost = getHeaderText(position)\n        return lastHost != curHost\n    }\n\n    override fun swap(srcPosition: Int, targetPosition: Int): Boolean {\n        val srcItem = getItem(srcPosition)\n        val targetItem = getItem(targetPosition)\n        if (srcItem != null && targetItem != null) {\n            val srcOrder = srcItem.customOrder\n            srcItem.customOrder = targetItem.customOrder\n            targetItem.customOrder = srcOrder\n            movedItems.add(srcItem)\n            movedItems.add(targetItem)\n        }\n        swapItem(srcPosition, targetPosition)\n        return true\n    }\n\n    private val movedItems = hashSetOf<BookSourcePart>()\n\n    override fun onClearView(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder) {\n        if (movedItems.isNotEmpty()) {\n            val sortNumberSet = hashSetOf<Int>()\n            movedItems.forEach {\n                sortNumberSet.add(it.customOrder)\n            }\n            if (movedItems.size > sortNumberSet.size) {\n                callBack.upOrder(getItems().mapIndexed { index, bookSourcePart ->\n                    bookSourcePart.customOrder = if (callBack.sortAscending) index else -index\n                    bookSourcePart\n                })\n            } else {\n                callBack.upOrder(movedItems.toList())\n            }\n            movedItems.clear()\n        }\n    }\n\n    val dragSelectCallback: DragSelectTouchHelper.Callback =\n        object : DragSelectTouchHelper.AdvanceCallback<BookSourcePart>(Mode.ToggleAndReverse) {\n            override fun currentSelectedId(): MutableSet<BookSourcePart> {\n                return selected\n            }\n\n            override fun getItemId(position: Int): BookSourcePart {\n                return getItem(position)!!\n            }\n\n            override fun updateSelectState(position: Int, isSelected: Boolean): Boolean {\n                getItem(position)?.let {\n                    if (isSelected) {\n                        selected.add(it)\n                    } else {\n                        selected.remove(it)\n                    }\n                    notifyItemChanged(position, bundleOf(Pair(\"selected\", null)))\n                    callBack.upCountView()\n                    return true\n                }\n                return false\n            }\n        }\n\n    interface CallBack {\n        val sort: BookSourceSort\n        val sortAscending: Boolean\n        fun del(bookSource: BookSourcePart)\n        fun edit(bookSource: BookSourcePart)\n        fun toTop(bookSource: BookSourcePart)\n        fun toBottom(bookSource: BookSourcePart)\n        fun searchBook(bookSource: BookSourcePart)\n        fun debug(bookSource: BookSourcePart)\n        fun upOrder(items: List<BookSourcePart>)\n        fun enable(enable: Boolean, bookSource: BookSourcePart)\n        fun enableExplore(enable: Boolean, bookSource: BookSourcePart)\n        fun upCountView()\n        fun getSourceHost(origin: String): String\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/book/source/manage/BookSourceSort.kt",
    "content": "package io.legado.app.ui.book.source.manage\n\nenum class BookSourceSort {\n    Default, Name, Url, Weight, Update, Enable, Respond\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/book/source/manage/BookSourceViewModel.kt",
    "content": "package io.legado.app.ui.book.source.manage\n\nimport android.app.Application\nimport android.text.TextUtils\nimport io.legado.app.R\nimport io.legado.app.base.BaseViewModel\nimport io.legado.app.data.appDb\nimport io.legado.app.data.entities.BookSource\nimport io.legado.app.data.entities.BookSourcePart\nimport io.legado.app.data.entities.toBookSource\nimport io.legado.app.help.source.SourceHelp\nimport io.legado.app.utils.FileUtils\nimport io.legado.app.utils.GSON\nimport io.legado.app.utils.cnCompare\nimport io.legado.app.utils.outputStream\nimport io.legado.app.utils.splitNotBlank\nimport io.legado.app.utils.stackTraceStr\nimport io.legado.app.utils.toastOnUi\nimport io.legado.app.utils.writeToOutputStream\nimport splitties.init.appCtx\nimport java.io.File\n\n/**\n * 书源管理数据修改\n * 修改数据要copy,直接修改会导致界面不刷新\n */\nclass BookSourceViewModel(application: Application) : BaseViewModel(application) {\n\n    fun topSource(vararg sources: BookSourcePart) {\n        execute {\n            sources.sortBy { it.customOrder }\n            val minOrder = appDb.bookSourceDao.minOrder - 1\n            val array = sources.mapIndexed { index, it ->\n                it.copy(customOrder = minOrder - index)\n            }\n            appDb.bookSourceDao.upOrder(array)\n        }\n    }\n\n    fun bottomSource(vararg sources: BookSourcePart) {\n        execute {\n            sources.sortBy { it.customOrder }\n            val maxOrder = appDb.bookSourceDao.maxOrder + 1\n            val array = sources.mapIndexed { index, it ->\n                it.copy(customOrder = maxOrder + index)\n            }\n            appDb.bookSourceDao.upOrder(array)\n        }\n    }\n\n    fun del(sources: List<BookSourcePart>) {\n        execute {\n            SourceHelp.deleteBookSourceParts(sources)\n        }\n    }\n\n    fun update(vararg bookSource: BookSource) {\n        execute { appDb.bookSourceDao.update(*bookSource) }\n    }\n\n    fun upOrder(items: List<BookSourcePart>) {\n        if (items.isEmpty()) return\n        execute {\n            appDb.bookSourceDao.upOrder(items)\n        }\n    }\n\n    fun enable(enable: Boolean, items: List<BookSourcePart>) {\n        execute {\n            appDb.bookSourceDao.enable(enable, items)\n        }\n    }\n\n    fun enableSelection(sources: List<BookSourcePart>) {\n        execute {\n            appDb.bookSourceDao.enable(true, sources)\n        }\n    }\n\n    fun disableSelection(sources: List<BookSourcePart>) {\n        execute {\n            appDb.bookSourceDao.enable(false, sources)\n        }\n    }\n\n    fun enableExplore(enable: Boolean, items: List<BookSourcePart>) {\n        execute {\n            appDb.bookSourceDao.enableExplore(enable, items)\n        }\n    }\n\n    fun enableSelectExplore(sources: List<BookSourcePart>) {\n        execute {\n            appDb.bookSourceDao.enableExplore(true, sources)\n        }\n    }\n\n    fun disableSelectExplore(sources: List<BookSourcePart>) {\n        execute {\n            appDb.bookSourceDao.enableExplore(false, sources)\n        }\n    }\n\n    fun selectionAddToGroups(sources: List<BookSourcePart>, groups: String) {\n        execute {\n            val array = sources.map {\n                it.copy().apply {\n                    addGroup(groups)\n                }\n            }\n            appDb.bookSourceDao.upGroup(array)\n        }\n    }\n\n    fun selectionRemoveFromGroups(sources: List<BookSourcePart>, groups: String) {\n        execute {\n            val array = sources.map {\n                it.copy().apply {\n                    removeGroup(groups)\n                }\n            }\n            appDb.bookSourceDao.upGroup(array)\n        }\n    }\n\n    private fun saveToFile(sources: List<BookSource>, success: (file: File) -> Unit) {\n        execute {\n            val path = \"${context.filesDir}/shareBookSource.json\"\n            FileUtils.delete(path)\n            val file = FileUtils.createFileWithReplace(path)\n            file.outputStream().buffered().use {\n                GSON.writeToOutputStream(it, sources)\n            }\n            file\n        }.onSuccess {\n            success.invoke(it)\n        }.onError {\n            context.toastOnUi(it.stackTraceStr)\n        }\n    }\n\n    fun saveToFile(\n        adapter: BookSourceAdapter,\n        searchKey: String?,\n        sortAscending: Boolean,\n        sort: BookSourceSort,\n        success: (file: File) -> Unit\n    ) {\n        execute {\n            val selection = adapter.selection\n            val selectedRate = selection.size.toFloat() / adapter.itemCount.toFloat()\n            val sources = if (selectedRate == 1f) {\n                getBookSources(searchKey, sortAscending, sort)\n            } else if (selectedRate < 0.3) {\n                selection.toBookSource()\n            } else {\n                val keys = selection.map { it.bookSourceUrl }.toHashSet()\n                val bookSources = getBookSources(searchKey, sortAscending, sort)\n                bookSources.filter {\n                    keys.contains(it.bookSourceUrl)\n                }\n            }\n            saveToFile(sources, success)\n        }\n    }\n\n    private fun getBookSources(\n        searchKey: String?,\n        sortAscending: Boolean,\n        sort: BookSourceSort\n    ): List<BookSource> {\n        return when {\n            searchKey.isNullOrEmpty() -> {\n                appDb.bookSourceDao.all\n            }\n\n            searchKey == appCtx.getString(R.string.enabled) -> {\n                appDb.bookSourceDao.allEnabled\n            }\n\n            searchKey == appCtx.getString(R.string.disabled) -> {\n                appDb.bookSourceDao.allDisabled\n            }\n\n            searchKey == appCtx.getString(R.string.need_login) -> {\n                appDb.bookSourceDao.allLogin\n            }\n\n            searchKey == appCtx.getString(R.string.no_group) -> {\n                appDb.bookSourceDao.allNoGroup\n            }\n\n            searchKey == appCtx.getString(R.string.enabled_explore) -> {\n                appDb.bookSourceDao.allEnabledExplore\n            }\n\n            searchKey == appCtx.getString(R.string.disabled_explore) -> {\n                appDb.bookSourceDao.allDisabledExplore\n            }\n\n            searchKey.startsWith(\"group:\") -> {\n                val key = searchKey.substringAfter(\"group:\")\n                appDb.bookSourceDao.groupSearch(key)\n            }\n\n            else -> {\n                appDb.bookSourceDao.search(searchKey)\n            }\n        }.let { data ->\n            if (sortAscending) when (sort) {\n                BookSourceSort.Weight -> data.sortedBy { it.weight }\n                BookSourceSort.Name -> data.sortedWith { o1, o2 ->\n                    o1.bookSourceName.cnCompare(o2.bookSourceName)\n                }\n\n                BookSourceSort.Url -> data.sortedBy { it.bookSourceUrl }\n                BookSourceSort.Update -> data.sortedByDescending { it.lastUpdateTime }\n                BookSourceSort.Respond -> data.sortedBy { it.respondTime }\n                BookSourceSort.Enable -> data.sortedWith { o1, o2 ->\n                    var sortNum = -o1.enabled.compareTo(o2.enabled)\n                    if (sortNum == 0) {\n                        sortNum = o1.bookSourceName.cnCompare(o2.bookSourceName)\n                    }\n                    sortNum\n                }\n\n                else -> data\n            }\n            else when (sort) {\n                BookSourceSort.Weight -> data.sortedByDescending { it.weight }\n                BookSourceSort.Name -> data.sortedWith { o1, o2 ->\n                    o2.bookSourceName.cnCompare(o1.bookSourceName)\n                }\n\n                BookSourceSort.Url -> data.sortedByDescending { it.bookSourceUrl }\n                BookSourceSort.Update -> data.sortedBy { it.lastUpdateTime }\n                BookSourceSort.Respond -> data.sortedByDescending { it.respondTime }\n                BookSourceSort.Enable -> data.sortedWith { o1, o2 ->\n                    var sortNum = o1.enabled.compareTo(o2.enabled)\n                    if (sortNum == 0) {\n                        sortNum = o1.bookSourceName.cnCompare(o2.bookSourceName)\n                    }\n                    sortNum\n                }\n\n                else -> data.reversed()\n            }\n        }\n    }\n\n    fun addGroup(group: String) {\n        execute {\n            val sources = appDb.bookSourceDao.noGroup\n            sources.forEach { source ->\n                source.bookSourceGroup = group\n            }\n            appDb.bookSourceDao.update(*sources.toTypedArray())\n        }\n    }\n\n    fun upGroup(oldGroup: String, newGroup: String?) {\n        execute {\n            val sources = appDb.bookSourceDao.getByGroup(oldGroup)\n            sources.forEach { source ->\n                source.bookSourceGroup?.splitNotBlank(\",\")?.toHashSet()?.let {\n                    it.remove(oldGroup)\n                    if (!newGroup.isNullOrEmpty())\n                        it.add(newGroup)\n                    source.bookSourceGroup = TextUtils.join(\",\", it)\n                }\n            }\n            appDb.bookSourceDao.update(*sources.toTypedArray())\n        }\n    }\n\n    fun delGroup(group: String) {\n        execute {\n            execute {\n                val sources = appDb.bookSourceDao.getByGroup(group)\n                sources.forEach { source ->\n                    source.removeGroup(group)\n                }\n                appDb.bookSourceDao.update(*sources.toTypedArray())\n            }\n        }\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/book/source/manage/GroupManageDialog.kt",
    "content": "package io.legado.app.ui.book.source.manage\n\nimport android.annotation.SuppressLint\nimport android.content.Context\nimport android.os.Bundle\nimport android.view.MenuItem\nimport android.view.View\nimport android.view.ViewGroup\nimport androidx.appcompat.widget.Toolbar\nimport androidx.fragment.app.activityViewModels\nimport androidx.lifecycle.lifecycleScope\nimport androidx.recyclerview.widget.LinearLayoutManager\nimport io.legado.app.R\nimport io.legado.app.base.BaseDialogFragment\nimport io.legado.app.base.adapter.ItemViewHolder\nimport io.legado.app.base.adapter.RecyclerAdapter\nimport io.legado.app.data.appDb\nimport io.legado.app.databinding.DialogEditTextBinding\nimport io.legado.app.databinding.DialogRecyclerViewBinding\nimport io.legado.app.databinding.ItemGroupManageBinding\nimport io.legado.app.lib.dialogs.alert\nimport io.legado.app.lib.theme.backgroundColor\nimport io.legado.app.lib.theme.primaryColor\nimport io.legado.app.ui.widget.recycler.VerticalDivider\nimport io.legado.app.utils.applyTint\nimport io.legado.app.utils.requestInputMethod\nimport io.legado.app.utils.setLayout\nimport io.legado.app.utils.viewbindingdelegate.viewBinding\nimport kotlinx.coroutines.launch\n\n\nclass GroupManageDialog : BaseDialogFragment(R.layout.dialog_recycler_view),\n    Toolbar.OnMenuItemClickListener {\n\n    private val viewModel: BookSourceViewModel by activityViewModels()\n    private val binding by viewBinding(DialogRecyclerViewBinding::bind)\n    private val adapter by lazy { GroupAdapter(requireContext()) }\n\n    override fun onStart() {\n        super.onStart()\n        setLayout(0.9f, 0.9f)\n    }\n\n    override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) {\n        view.setBackgroundColor(backgroundColor)\n        binding.toolBar.setBackgroundColor(primaryColor)\n        binding.toolBar.title = getString(R.string.group_manage)\n        binding.toolBar.inflateMenu(R.menu.group_manage)\n        binding.toolBar.menu.applyTint(requireContext())\n        binding.toolBar.setOnMenuItemClickListener(this)\n        binding.recyclerView.layoutManager = LinearLayoutManager(requireContext())\n        binding.recyclerView.addItemDecoration(VerticalDivider(requireContext()))\n        binding.recyclerView.adapter = adapter\n        initData()\n    }\n\n    private fun initData() {\n        lifecycleScope.launch {\n            appDb.bookSourceDao.flowGroups().collect {\n                adapter.setItems(it)\n            }\n        }\n    }\n\n    override fun onMenuItemClick(item: MenuItem?): Boolean {\n        when (item?.itemId) {\n            R.id.menu_add -> addGroup()\n        }\n        return true\n    }\n\n    @SuppressLint(\"InflateParams\")\n    private fun addGroup() {\n        alert(title = getString(R.string.add_group)) {\n            val alertBinding = DialogEditTextBinding.inflate(layoutInflater).apply {\n                editView.setHint(R.string.group_name)\n            }\n            customView { alertBinding.root }\n            okButton {\n                alertBinding.editView.text?.toString()?.let {\n                    if (it.isNotBlank()) {\n                        viewModel.addGroup(it)\n                    }\n                }\n            }\n            cancelButton()\n        }.requestInputMethod()\n    }\n\n    @SuppressLint(\"InflateParams\")\n    private fun editGroup(group: String) {\n        alert(title = getString(R.string.group_edit)) {\n            val alertBinding = DialogEditTextBinding.inflate(layoutInflater).apply {\n                editView.setHint(R.string.group_name)\n                editView.setText(group)\n            }\n            customView { alertBinding.root }\n            okButton {\n                viewModel.upGroup(group, alertBinding.editView.text?.toString())\n            }\n            cancelButton()\n        }.requestInputMethod()\n    }\n\n    private inner class GroupAdapter(context: Context) :\n        RecyclerAdapter<String, ItemGroupManageBinding>(context) {\n\n        override fun getViewBinding(parent: ViewGroup): ItemGroupManageBinding {\n            return ItemGroupManageBinding.inflate(inflater, parent, false)\n        }\n\n        override fun convert(\n            holder: ItemViewHolder,\n            binding: ItemGroupManageBinding,\n            item: String,\n            payloads: MutableList<Any>\n        ) {\n            binding.run {\n                root.setBackgroundColor(context.backgroundColor)\n                tvGroup.text = item\n            }\n        }\n\n        override fun registerListener(holder: ItemViewHolder, binding: ItemGroupManageBinding) {\n            binding.apply {\n                tvEdit.setOnClickListener {\n                    getItem(holder.layoutPosition)?.let {\n                        editGroup(it)\n                    }\n                }\n                tvDel.setOnClickListener {\n                    getItem(holder.layoutPosition)?.let { viewModel.delGroup(it) }\n                }\n            }\n        }\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/book/toc/BookmarkAdapter.kt",
    "content": "package io.legado.app.ui.book.toc\n\nimport android.content.Context\nimport android.view.ViewGroup\nimport io.legado.app.base.adapter.ItemViewHolder\nimport io.legado.app.base.adapter.RecyclerAdapter\nimport io.legado.app.data.entities.Bookmark\nimport io.legado.app.databinding.ItemBookmarkBinding\nimport io.legado.app.utils.gone\nimport splitties.views.onLongClick\n\nclass BookmarkAdapter(context: Context, val callback: Callback) :\n    RecyclerAdapter<Bookmark, ItemBookmarkBinding>(context) {\n\n    override fun getViewBinding(parent: ViewGroup): ItemBookmarkBinding {\n        return ItemBookmarkBinding.inflate(inflater, parent, false)\n    }\n\n    override fun convert(\n        holder: ItemViewHolder,\n        binding: ItemBookmarkBinding,\n        item: Bookmark,\n        payloads: MutableList<Any>\n    ) {\n        binding.tvChapterName.text = item.chapterName\n        binding.tvBookText.gone(item.bookText.isEmpty())\n        binding.tvBookText.text = item.bookText\n        binding.tvContent.gone(item.content.isEmpty())\n        binding.tvContent.text = item.content\n    }\n\n    override fun registerListener(holder: ItemViewHolder, binding: ItemBookmarkBinding) {\n        binding.root.setOnClickListener {\n            getItem(holder.layoutPosition)?.let { bookmark ->\n                callback.onClick(bookmark)\n            }\n        }\n        binding.root.onLongClick {\n            getItem(holder.layoutPosition)?.let { bookmark ->\n                callback.onLongClick(bookmark, holder.layoutPosition)\n            }\n        }\n\n    }\n\n    interface Callback {\n        fun onClick(bookmark: Bookmark)\n        fun onLongClick(bookmark: Bookmark, pos: Int)\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/book/toc/BookmarkFragment.kt",
    "content": "package io.legado.app.ui.book.toc\n\nimport android.app.Activity\nimport android.content.Intent\nimport android.os.Bundle\nimport android.view.View\nimport androidx.fragment.app.activityViewModels\nimport androidx.lifecycle.lifecycleScope\nimport io.legado.app.R\nimport io.legado.app.base.VMBaseFragment\nimport io.legado.app.constant.AppLog\nimport io.legado.app.data.appDb\nimport io.legado.app.data.entities.Bookmark\nimport io.legado.app.databinding.FragmentBookmarkBinding\nimport io.legado.app.lib.theme.primaryColor\nimport io.legado.app.ui.book.bookmark.BookmarkDialog\nimport io.legado.app.ui.widget.recycler.UpLinearLayoutManager\nimport io.legado.app.ui.widget.recycler.VerticalDivider\nimport io.legado.app.utils.applyNavigationBarPadding\nimport io.legado.app.utils.setEdgeEffectColor\nimport io.legado.app.utils.showDialogFragment\nimport io.legado.app.utils.viewbindingdelegate.viewBinding\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.Dispatchers.IO\nimport kotlinx.coroutines.flow.catch\nimport kotlinx.coroutines.flow.flowOn\nimport kotlinx.coroutines.launch\nimport kotlinx.coroutines.withContext\n\n\nclass BookmarkFragment : VMBaseFragment<TocViewModel>(R.layout.fragment_bookmark),\n    BookmarkAdapter.Callback,\n    TocViewModel.BookmarkCallBack {\n    override val viewModel by activityViewModels<TocViewModel>()\n    private val binding by viewBinding(FragmentBookmarkBinding::bind)\n    private val mLayoutManager by lazy { UpLinearLayoutManager(requireContext()) }\n    private val adapter by lazy { BookmarkAdapter(requireContext(), this) }\n    private var durChapterIndex = 0\n\n    override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) {\n        viewModel.bookMarkCallBack = this\n        initRecyclerView()\n        viewModel.bookData.observe(this) {\n            durChapterIndex = it.durChapterIndex\n            upBookmark(null)\n        }\n    }\n\n    private fun initRecyclerView() {\n        binding.recyclerView.setEdgeEffectColor(primaryColor)\n        binding.recyclerView.layoutManager = mLayoutManager\n        binding.recyclerView.addItemDecoration(VerticalDivider(requireContext()))\n        binding.recyclerView.adapter = adapter\n        binding.recyclerView.applyNavigationBarPadding()\n    }\n\n    override fun upBookmark(searchKey: String?) {\n        val book = viewModel.bookData.value ?: return\n        lifecycleScope.launch {\n            when {\n                searchKey.isNullOrBlank() -> appDb.bookmarkDao.flowByBook(book.name, book.author)\n                else -> appDb.bookmarkDao.flowSearch(book.name, book.author, searchKey)\n            }.catch {\n                AppLog.put(\"目录界面获取书签数据失败\\n${it.localizedMessage}\", it)\n            }.flowOn(IO).collect {\n                adapter.setItems(it)\n                var scrollPos = 0\n                withContext(Dispatchers.Default) {\n                    adapter.getItems().forEachIndexed { index, bookmark ->\n                        if (bookmark.chapterIndex >= durChapterIndex) {\n                            return@withContext\n                        }\n                        scrollPos = index\n                    }\n                }\n                mLayoutManager.scrollToPositionWithOffset(scrollPos, 0)\n            }\n        }\n    }\n\n\n    override fun onClick(bookmark: Bookmark) {\n        activity?.run {\n            setResult(Activity.RESULT_OK, Intent().apply {\n                putExtra(\"index\", bookmark.chapterIndex)\n                putExtra(\"chapterPos\", bookmark.chapterPos)\n            })\n            finish()\n        }\n    }\n\n    override fun onLongClick(bookmark: Bookmark, pos: Int) {\n        showDialogFragment(BookmarkDialog(bookmark, pos))\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/book/toc/ChapterListAdapter.kt",
    "content": "package io.legado.app.ui.book.toc\n\nimport android.content.Context\nimport android.os.Handler\nimport android.os.Looper\nimport android.view.ViewGroup\nimport androidx.recyclerview.widget.DiffUtil\nimport io.legado.app.R\nimport io.legado.app.base.adapter.DiffRecyclerAdapter\nimport io.legado.app.base.adapter.ItemViewHolder\nimport io.legado.app.data.entities.Book\nimport io.legado.app.data.entities.BookChapter\nimport io.legado.app.databinding.ItemChapterListBinding\nimport io.legado.app.help.book.ContentProcessor\nimport io.legado.app.help.config.AppConfig\nimport io.legado.app.help.coroutine.Coroutine\nimport io.legado.app.lib.theme.ThemeUtils\nimport io.legado.app.lib.theme.accentColor\nimport io.legado.app.utils.getCompatColor\nimport io.legado.app.utils.gone\nimport io.legado.app.utils.longToastOnUi\nimport io.legado.app.utils.visible\nimport kotlinx.coroutines.CoroutineScope\nimport kotlinx.coroutines.ensureActive\nimport kotlinx.coroutines.launch\nimport java.util.concurrent.ConcurrentHashMap\n\nclass ChapterListAdapter(context: Context, val callback: Callback) :\n    DiffRecyclerAdapter<BookChapter, ItemChapterListBinding>(context) {\n\n    val cacheFileNames = hashSetOf<String>()\n    private val displayTitleMap = ConcurrentHashMap<String, String>()\n    private val handler = Handler(Looper.getMainLooper())\n\n    override val diffItemCallback: DiffUtil.ItemCallback<BookChapter>\n        get() = object : DiffUtil.ItemCallback<BookChapter>() {\n\n            override fun areItemsTheSame(\n                oldItem: BookChapter,\n                newItem: BookChapter\n            ): Boolean {\n                return oldItem.index == newItem.index\n            }\n\n            override fun areContentsTheSame(\n                oldItem: BookChapter,\n                newItem: BookChapter\n            ): Boolean {\n                return oldItem.bookUrl == newItem.bookUrl\n                        && oldItem.url == newItem.url\n                        && oldItem.isVip == newItem.isVip\n                        && oldItem.isPay == newItem.isPay\n                        && oldItem.title == newItem.title\n                        && oldItem.tag == newItem.tag\n                        && oldItem.wordCount == newItem.wordCount\n                        && oldItem.isVolume == newItem.isVolume\n            }\n\n        }\n\n    private var upDisplayTileJob: Coroutine<*>? = null\n\n    override fun onCurrentListChanged() {\n        super.onCurrentListChanged()\n        callback.onListChanged()\n    }\n\n    fun clearDisplayTitle() {\n        upDisplayTileJob?.cancel()\n        displayTitleMap.clear()\n    }\n\n    fun upDisplayTitles(startIndex: Int) {\n        upDisplayTileJob?.cancel()\n        upDisplayTileJob = Coroutine.async(callback.scope) {\n            val book = callback.book ?: return@async\n            val replaceRules = ContentProcessor.get(book.name, book.origin).getTitleReplaceRules()\n            val useReplace = AppConfig.tocUiUseReplace && book.getUseReplaceRule()\n            val items = getItems()\n            launch {\n                for (i in startIndex until items.size) {\n                    val item = items[i]\n                    if (displayTitleMap[item.title] == null) {\n                        ensureActive()\n                        val displayTitle = item.getDisplayTitle(replaceRules, useReplace)\n                        ensureActive()\n                        displayTitleMap[item.title] = displayTitle\n                        handler.post {\n                            notifyItemChanged(i, true)\n                        }\n                    }\n                }\n            }\n            launch {\n                for (i in startIndex downTo 0) {\n                    val item = items[i]\n                    if (displayTitleMap[item.title] == null) {\n                        ensureActive()\n                        val displayTitle = item.getDisplayTitle(replaceRules, useReplace)\n                        ensureActive()\n                        displayTitleMap[item.title] = displayTitle\n                        handler.post {\n                            notifyItemChanged(i, true)\n                        }\n                    }\n                }\n            }\n        }\n    }\n\n    private fun getDisplayTitle(chapter: BookChapter): String {\n        return displayTitleMap[chapter.title] ?: chapter.title\n    }\n\n    override fun getViewBinding(parent: ViewGroup): ItemChapterListBinding {\n        return ItemChapterListBinding.inflate(inflater, parent, false)\n    }\n\n    override fun convert(\n        holder: ItemViewHolder,\n        binding: ItemChapterListBinding,\n        item: BookChapter,\n        payloads: MutableList<Any>\n    ) {\n        binding.run {\n            val isDur = callback.durChapterIndex() == item.index\n            val cached = callback.isLocalBook\n                    || item.isVolume\n                    || cacheFileNames.contains(item.getFileName())\n            if (payloads.isEmpty()) {\n                if (isDur) {\n                    tvChapterName.setTextColor(context.accentColor)\n                } else {\n                    tvChapterName.setTextColor(context.getCompatColor(R.color.primaryText))\n                }\n                tvChapterName.text = getDisplayTitle(item)\n                if (item.isVolume) {\n                    //卷名，如第一卷 突出显示\n                    tvChapterItem.setBackgroundColor(context.getCompatColor(R.color.btn_bg_press))\n                } else {\n                    //普通章节 保持不变\n                    tvChapterItem.background =\n                        ThemeUtils.resolveDrawable(context, android.R.attr.selectableItemBackground)\n                }\n\n                //卷名不显示\n                if (!item.tag.isNullOrEmpty() && !item.isVolume) {\n                    //更新时间规则\n                    tvTag.text = item.tag\n                    tvTag.visible()\n                } else {\n                    tvTag.gone()\n                }\n                if (AppConfig.tocCountWords && !item.wordCount.isNullOrEmpty() && !item.isVolume) {\n                    //章节字数\n                    tvWordCount.text = item.wordCount\n                    tvWordCount.visible()\n                } else {\n                    tvWordCount.gone()\n                }\n\n                if (item.isVip && !item.isPay) {\n                    ivLocked.visible()\n                } else {\n                    ivLocked.gone()\n                }\n\n                upHasCache(binding, isDur, cached)\n            } else {\n                tvChapterName.text = getDisplayTitle(item)\n                upHasCache(binding, isDur, cached)\n            }\n        }\n    }\n\n    override fun registerListener(holder: ItemViewHolder, binding: ItemChapterListBinding) {\n        holder.itemView.setOnClickListener {\n            getItem(holder.layoutPosition)?.let {\n                callback.openChapter(it)\n            }\n        }\n        holder.itemView.setOnLongClickListener {\n            getItem(holder.layoutPosition)?.let { item ->\n                context.longToastOnUi(getDisplayTitle(item))\n            }\n            true\n        }\n    }\n\n    private fun upHasCache(binding: ItemChapterListBinding, isDur: Boolean, cached: Boolean) =\n        binding.apply {\n            ivChecked.setImageResource(R.drawable.ic_outline_cloud_24)\n            ivChecked.visible(!cached)\n            if (isDur) {\n                ivChecked.setImageResource(R.drawable.ic_check)\n                ivChecked.visible()\n            }\n        }\n\n    interface Callback {\n        val scope: CoroutineScope\n        val book: Book?\n        val isLocalBook: Boolean\n        fun openChapter(bookChapter: BookChapter)\n        fun durChapterIndex(): Int\n        fun onListChanged()\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/book/toc/ChapterListFragment.kt",
    "content": "package io.legado.app.ui.book.toc\n\nimport android.annotation.SuppressLint\nimport android.app.Activity.RESULT_OK\nimport android.content.Intent\nimport android.graphics.PorterDuff\nimport android.os.Bundle\nimport android.view.View\nimport androidx.fragment.app.activityViewModels\nimport androidx.lifecycle.lifecycleScope\nimport io.legado.app.R\nimport io.legado.app.base.VMBaseFragment\nimport io.legado.app.constant.EventBus\nimport io.legado.app.data.appDb\nimport io.legado.app.data.entities.Book\nimport io.legado.app.data.entities.BookChapter\nimport io.legado.app.databinding.FragmentChapterListBinding\nimport io.legado.app.help.book.BookHelp\nimport io.legado.app.help.book.isLocal\nimport io.legado.app.help.book.simulatedTotalChapterNum\nimport io.legado.app.lib.theme.bottomBackground\nimport io.legado.app.lib.theme.getPrimaryTextColor\nimport io.legado.app.ui.widget.recycler.UpLinearLayoutManager\nimport io.legado.app.ui.widget.recycler.VerticalDivider\nimport io.legado.app.utils.ColorUtils\nimport io.legado.app.utils.applyNavigationBarPadding\nimport io.legado.app.utils.observeEvent\nimport io.legado.app.utils.viewbindingdelegate.viewBinding\nimport kotlinx.coroutines.CoroutineScope\nimport kotlinx.coroutines.Dispatchers.Default\nimport kotlinx.coroutines.Dispatchers.IO\nimport kotlinx.coroutines.Dispatchers.Main\nimport kotlinx.coroutines.launch\nimport kotlinx.coroutines.withContext\n\nclass ChapterListFragment : VMBaseFragment<TocViewModel>(R.layout.fragment_chapter_list),\n    ChapterListAdapter.Callback,\n    TocViewModel.ChapterListCallBack {\n    override val viewModel by activityViewModels<TocViewModel>()\n    private val binding by viewBinding(FragmentChapterListBinding::bind)\n    private val mLayoutManager by lazy { UpLinearLayoutManager(requireContext()) }\n    private val adapter by lazy { ChapterListAdapter(requireContext(), this) }\n    private var durChapterIndex = 0\n\n    override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) = binding.run {\n        viewModel.chapterListCallBack = this@ChapterListFragment\n        val bbg = bottomBackground\n        val btc = requireContext().getPrimaryTextColor(ColorUtils.isColorLight(bbg))\n        llChapterBaseInfo.setBackgroundColor(bbg)\n        tvCurrentChapterInfo.setTextColor(btc)\n        ivChapterTop.setColorFilter(btc, PorterDuff.Mode.SRC_IN)\n        ivChapterBottom.setColorFilter(btc, PorterDuff.Mode.SRC_IN)\n        initRecyclerView()\n        initView()\n        viewModel.bookData.observe(this@ChapterListFragment) {\n            initBook(it)\n        }\n    }\n\n    private fun initRecyclerView() {\n        binding.recyclerView.layoutManager = mLayoutManager\n        binding.recyclerView.addItemDecoration(VerticalDivider(requireContext()))\n        binding.recyclerView.adapter = adapter\n    }\n\n    private fun initView() = binding.run {\n        ivChapterTop.setOnClickListener {\n            mLayoutManager.scrollToPositionWithOffset(0, 0)\n        }\n        ivChapterBottom.setOnClickListener {\n            if (adapter.itemCount > 0) {\n                mLayoutManager.scrollToPositionWithOffset(adapter.itemCount - 1, 0)\n            }\n        }\n        tvCurrentChapterInfo.setOnClickListener {\n            mLayoutManager.scrollToPositionWithOffset(durChapterIndex, 0)\n        }\n        binding.llChapterBaseInfo.applyNavigationBarPadding()\n    }\n\n    @SuppressLint(\"SetTextI18n\")\n    private fun initBook(book: Book) {\n        lifecycleScope.launch {\n            upChapterList(null)\n            durChapterIndex = book.durChapterIndex\n            binding.tvCurrentChapterInfo.text =\n                \"${book.durChapterTitle}(${book.durChapterIndex + 1}/${book.simulatedTotalChapterNum()})\"\n            initCacheFileNames(book)\n        }\n    }\n\n    private fun initCacheFileNames(book: Book) {\n        lifecycleScope.launch(IO) {\n            adapter.cacheFileNames.addAll(BookHelp.getChapterFiles(book))\n            withContext(Main) {\n                adapter.notifyItemRangeChanged(0, adapter.itemCount, true)\n            }\n        }\n    }\n\n    override fun observeLiveBus() {\n        observeEvent<Pair<Book, BookChapter>>(EventBus.SAVE_CONTENT) { (book, chapter) ->\n            viewModel.bookData.value?.bookUrl?.let { bookUrl ->\n                if (book.bookUrl == bookUrl) {\n                    adapter.cacheFileNames.add(chapter.getFileName())\n                    if (viewModel.searchKey.isNullOrEmpty()) {\n                        adapter.notifyItemChanged(chapter.index, true)\n                    } else {\n                        adapter.getItems().forEachIndexed { index, bookChapter ->\n                            if (bookChapter.index == chapter.index) {\n                                adapter.notifyItemChanged(index, true)\n                            }\n                        }\n                    }\n                }\n            }\n        }\n    }\n\n    override fun upChapterList(searchKey: String?) {\n        lifecycleScope.launch {\n            withContext(IO) {\n                val end = (book?.simulatedTotalChapterNum() ?: Int.MAX_VALUE) - 1\n                when {\n                    searchKey.isNullOrBlank() ->\n                        appDb.bookChapterDao.getChapterList(viewModel.bookUrl, 0, end)\n\n                    else -> appDb.bookChapterDao.search(viewModel.bookUrl, searchKey, 0, end)\n                }\n            }.let {\n                adapter.setItems(it)\n            }\n        }\n    }\n\n    override fun onListChanged() {\n        lifecycleScope.launch {\n            var scrollPos = 0\n            withContext(Default) {\n                adapter.getItems().forEachIndexed { index, bookChapter ->\n                    if (bookChapter.index >= durChapterIndex) {\n                        return@withContext\n                    }\n                    scrollPos = index\n                }\n            }\n            mLayoutManager.scrollToPositionWithOffset(scrollPos, 0)\n            adapter.upDisplayTitles(scrollPos)\n        }\n    }\n\n    override fun clearDisplayTitle() {\n        adapter.clearDisplayTitle()\n        adapter.upDisplayTitles(mLayoutManager.findFirstVisibleItemPosition())\n    }\n\n    override fun upAdapter() {\n        adapter.notifyItemRangeChanged(0, adapter.itemCount)\n    }\n\n    override val scope: CoroutineScope\n        get() = lifecycleScope\n\n    override val book: Book?\n        get() = viewModel.bookData.value\n\n    override val isLocalBook: Boolean\n        get() = viewModel.bookData.value?.isLocal == true\n\n    override fun durChapterIndex(): Int {\n        return durChapterIndex\n    }\n\n    override fun openChapter(bookChapter: BookChapter) {\n        activity?.run {\n            setResult(\n                RESULT_OK, Intent()\n                    .putExtra(\"index\", bookChapter.index)\n                    .putExtra(\"chapterChanged\", bookChapter.index != durChapterIndex)\n            )\n            finish()\n        }\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/book/toc/TocActivity.kt",
    "content": "@file:Suppress(\"DEPRECATION\")\n\npackage io.legado.app.ui.book.toc\n\nimport android.content.Intent\nimport android.os.Bundle\nimport android.view.Menu\nimport android.view.MenuItem\nimport androidx.activity.viewModels\nimport androidx.appcompat.widget.SearchView\nimport androidx.fragment.app.Fragment\nimport androidx.fragment.app.FragmentPagerAdapter\nimport com.google.android.material.tabs.TabLayout\nimport io.legado.app.R\nimport io.legado.app.base.VMBaseActivity\nimport io.legado.app.data.entities.Book\nimport io.legado.app.databinding.ActivityChapterListBinding\nimport io.legado.app.help.book.isLocalTxt\nimport io.legado.app.help.config.AppConfig\nimport io.legado.app.lib.theme.accentColor\nimport io.legado.app.lib.theme.primaryTextColor\nimport io.legado.app.model.ReadBook\nimport io.legado.app.ui.about.AppLogDialog\nimport io.legado.app.ui.book.toc.rule.TxtTocRuleDialog\nimport io.legado.app.ui.file.HandleFileContract\nimport io.legado.app.ui.widget.dialog.WaitDialog\nimport io.legado.app.utils.applyTint\nimport io.legado.app.utils.gone\nimport io.legado.app.utils.showDialogFragment\nimport io.legado.app.utils.viewbindingdelegate.viewBinding\nimport io.legado.app.utils.visible\n\n/**\n * 目录\n */\nclass TocActivity : VMBaseActivity<ActivityChapterListBinding, TocViewModel>(),\n    TxtTocRuleDialog.CallBack {\n\n    override val binding by viewBinding(ActivityChapterListBinding::inflate)\n    override val viewModel by viewModels<TocViewModel>()\n\n    private lateinit var tabLayout: TabLayout\n    private var menu: Menu? = null\n    private var searchView: SearchView? = null\n    private val waitDialog by lazy { WaitDialog(this) }\n    private val exportDir = registerForActivityResult(HandleFileContract()) {\n        it.uri?.let { uri ->\n            when (it.requestCode) {\n                1 -> viewModel.saveBookmark(uri)\n                2 -> viewModel.saveBookmarkMd(uri)\n            }\n        }\n    }\n\n    override fun onActivityCreated(savedInstanceState: Bundle?) {\n        tabLayout = binding.titleBar.findViewById(R.id.tab_layout)\n        tabLayout.isTabIndicatorFullWidth = false\n        tabLayout.setSelectedTabIndicatorColor(accentColor)\n        binding.viewPager.adapter = TabFragmentPageAdapter()\n        tabLayout.setupWithViewPager(binding.viewPager)\n        tabLayout.tabGravity = TabLayout.GRAVITY_CENTER\n        viewModel.bookData.observe(this) {\n            menu?.setGroupVisible(R.id.menu_group_text, it.isLocalTxt)\n        }\n        intent.getStringExtra(\"bookUrl\")?.let {\n            viewModel.initBook(it)\n        }\n    }\n\n    override fun onCompatCreateOptionsMenu(menu: Menu): Boolean {\n        menuInflater.inflate(R.menu.book_toc, menu)\n        this.menu = menu\n        val search = menu.findItem(R.id.menu_search)\n        searchView = (search.actionView as SearchView).apply {\n            applyTint(primaryTextColor)\n            maxWidth = resources.displayMetrics.widthPixels\n            onActionViewCollapsed()\n            setOnCloseListener {\n                tabLayout.visible()\n                false\n            }\n            setOnSearchClickListener { tabLayout.gone() }\n            setOnQueryTextListener(object : SearchView.OnQueryTextListener {\n                override fun onQueryTextSubmit(query: String): Boolean {\n                    viewModel.searchKey = query\n                    return false\n                }\n\n                override fun onQueryTextChange(newText: String): Boolean {\n                    viewModel.searchKey = newText\n                    if (tabLayout.selectedTabPosition == 1) {\n                        viewModel.startBookmarkSearch(newText)\n                    } else {\n                        viewModel.startChapterListSearch(newText)\n                    }\n                    return false\n                }\n            })\n            setOnQueryTextFocusChangeListener { _, hasFocus ->\n                if (!hasFocus) {\n                    searchView?.isIconified = true\n                }\n            }\n        }\n        return super.onCompatCreateOptionsMenu(menu)\n    }\n\n    override fun onMenuOpened(featureId: Int, menu: Menu): Boolean {\n        if (tabLayout.selectedTabPosition == 1) {\n            menu.setGroupVisible(R.id.menu_group_bookmark, true)\n            menu.setGroupVisible(R.id.menu_group_toc, false)\n            menu.setGroupVisible(R.id.menu_group_text, false)\n        } else {\n            menu.setGroupVisible(R.id.menu_group_bookmark, false)\n            menu.setGroupVisible(R.id.menu_group_toc, true)\n            menu.setGroupVisible(R.id.menu_group_text, viewModel.bookData.value?.isLocalTxt == true)\n        }\n        menu.findItem(R.id.menu_use_replace)?.isChecked =\n            AppConfig.tocUiUseReplace\n        menu.findItem(R.id.menu_load_word_count)?.isChecked =\n            AppConfig.tocCountWords\n        menu.findItem(R.id.menu_split_long_chapter)?.isChecked =\n            viewModel.bookData.value?.getSplitLongChapter() == true\n        return super.onMenuOpened(featureId, menu)\n    }\n\n    override fun onCompatOptionsItemSelected(item: MenuItem): Boolean {\n        when (item.itemId) {\n            R.id.menu_toc_regex -> showDialogFragment(\n                TxtTocRuleDialog(viewModel.bookData.value?.tocUrl)\n            )\n\n            R.id.menu_split_long_chapter -> {\n                viewModel.bookData.value?.let { book ->\n                    item.isChecked = !item.isChecked\n                    book.setSplitLongChapter(item.isChecked)\n                    upBookAndToc(book)\n                }\n            }\n\n            R.id.menu_reverse_toc -> viewModel.reverseToc {\n                viewModel.chapterListCallBack?.upChapterList(searchView?.query?.toString())\n                setResult(RESULT_OK, Intent().apply {\n                    putExtra(\"index\", it.durChapterIndex)\n                    putExtra(\"chapterPos\", 0)\n                })\n            }\n\n            R.id.menu_use_replace -> {\n                AppConfig.tocUiUseReplace = !item.isChecked\n                viewModel.chapterListCallBack?.clearDisplayTitle()\n                viewModel.chapterListCallBack?.upChapterList(searchView?.query?.toString())\n            }\n\n            R.id.menu_load_word_count -> {\n                AppConfig.tocCountWords = !item.isChecked\n                viewModel.upChapterListAdapter()\n            }\n\n            R.id.menu_export_bookmark -> exportDir.launch {\n                requestCode = 1\n            }\n\n            R.id.menu_export_md -> exportDir.launch {\n                requestCode = 2\n            }\n\n            R.id.menu_log -> showDialogFragment<AppLogDialog>()\n        }\n        return super.onCompatOptionsItemSelected(item)\n    }\n\n    override fun onTocRegexDialogResult(tocRegex: String) {\n        viewModel.bookData.value?.let { book ->\n            book.tocUrl = tocRegex\n            upBookAndToc(book)\n        }\n    }\n\n    private fun upBookAndToc(book: Book) {\n        waitDialog.show()\n        viewModel.upBookTocRule(book) {\n            waitDialog.dismiss()\n            if (ReadBook.book == book) {\n                if (it == null) {\n                    ReadBook.upMsg(null)\n                } else {\n                    ReadBook.upMsg(\"LoadTocError:${it.localizedMessage}\")\n                }\n            }\n        }\n    }\n\n    @Suppress(\"DEPRECATION\")\n    private inner class TabFragmentPageAdapter :\n        FragmentPagerAdapter(supportFragmentManager, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {\n\n        override fun getItem(position: Int): Fragment {\n            return when (position) {\n                1 -> BookmarkFragment()\n                else -> ChapterListFragment()\n            }\n        }\n\n        override fun getCount(): Int {\n            return 2\n        }\n\n        override fun getPageTitle(position: Int): CharSequence {\n            return when (position) {\n                1 -> getString(R.string.bookmark)\n                else -> getString(R.string.chapter_list)\n            }\n        }\n\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/book/toc/TocActivityResult.kt",
    "content": "package io.legado.app.ui.book.toc\n\nimport android.app.Activity.RESULT_OK\nimport android.content.Context\nimport android.content.Intent\nimport androidx.activity.result.contract.ActivityResultContract\n\nclass TocActivityResult : ActivityResultContract<String, Triple<Int, Int, Boolean>?>() {\n\n    override fun createIntent(context: Context, input: String): Intent {\n        return Intent(context, TocActivity::class.java)\n            .putExtra(\"bookUrl\", input)\n    }\n\n    override fun parseResult(resultCode: Int, intent: Intent?): Triple<Int, Int, Boolean>? {\n        if (resultCode == RESULT_OK) {\n            intent?.let {\n                return Triple(\n                    it.getIntExtra(\"index\", 0),\n                    it.getIntExtra(\"chapterPos\", 0),\n                    it.getBooleanExtra(\"chapterChanged\", false)\n                )\n            }\n        }\n        return null\n    }\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/book/toc/TocViewModel.kt",
    "content": "package io.legado.app.ui.book.toc\n\n\nimport android.app.Application\nimport android.net.Uri\nimport androidx.lifecycle.MutableLiveData\nimport io.legado.app.R\nimport io.legado.app.base.BaseViewModel\nimport io.legado.app.constant.AppLog\nimport io.legado.app.data.appDb\nimport io.legado.app.data.entities.Book\nimport io.legado.app.exception.NoStackTraceException\nimport io.legado.app.model.ReadBook\nimport io.legado.app.model.localBook.LocalBook\nimport io.legado.app.utils.FileDoc\nimport io.legado.app.utils.GSON\nimport io.legado.app.utils.createFileIfNotExist\nimport io.legado.app.utils.openOutputStream\nimport io.legado.app.utils.toastOnUi\nimport io.legado.app.utils.writeText\n\nclass TocViewModel(application: Application) : BaseViewModel(application) {\n    var bookUrl: String = \"\"\n    var bookData = MutableLiveData<Book>()\n    var chapterListCallBack: ChapterListCallBack? = null\n    var bookMarkCallBack: BookmarkCallBack? = null\n    var searchKey: String? = null\n\n    fun initBook(bookUrl: String) {\n        this.bookUrl = bookUrl\n        execute {\n            appDb.bookDao.getBook(bookUrl)?.let {\n                bookData.postValue(it)\n            }\n        }\n    }\n\n    fun upBookTocRule(book: Book, complete: (Throwable?) -> Unit) {\n        execute {\n            appDb.bookDao.update(book)\n            LocalBook.getChapterList(book).let {\n                appDb.bookChapterDao.delByBook(book.bookUrl)\n                appDb.bookChapterDao.insert(*it.toTypedArray())\n                appDb.bookDao.update(book)\n                ReadBook.onChapterListUpdated(book)\n                bookData.postValue(book)\n            }\n        }.onSuccess {\n            complete.invoke(null)\n        }.onError {\n            complete.invoke(it)\n        }\n    }\n\n    fun reverseToc(success: (book: Book) -> Unit) {\n        execute {\n            bookData.value?.apply {\n                setReverseToc(!getReverseToc())\n                val toc = appDb.bookChapterDao.getChapterList(bookUrl)\n                val newToc = toc.reversed()\n                newToc.forEachIndexed { index, bookChapter ->\n                    bookChapter.index = index\n                }\n                appDb.bookChapterDao.insert(*newToc.toTypedArray())\n            }\n        }.onSuccess {\n            it?.let(success)\n        }\n    }\n\n    fun startChapterListSearch(newText: String?) {\n        chapterListCallBack?.upChapterList(newText)\n    }\n\n    fun startBookmarkSearch(newText: String?) {\n        bookMarkCallBack?.upBookmark(newText)\n    }\n\n    fun upChapterListAdapter() {\n        chapterListCallBack?.upAdapter()\n    }\n\n    fun saveBookmark(treeUri: Uri) {\n        execute {\n            val book = bookData.value\n                ?: throw NoStackTraceException(context.getString(R.string.no_book))\n            val fileName = \"bookmark-${book.name} ${book.author}.json\"\n            val doc = FileDoc.fromUri(treeUri, true)\n            doc.createFileIfNotExist(fileName).writeText(\n                GSON.toJson(\n                    appDb.bookmarkDao.getByBook(book.name, book.author)\n                )\n            )\n        }.onError {\n            AppLog.put(\"导出失败\\n${it.localizedMessage}\", it, true)\n        }.onSuccess {\n            context.toastOnUi(\"导出成功\")\n        }\n    }\n\n    fun saveBookmarkMd(treeUri: Uri) {\n        execute {\n            val book = bookData.value\n                ?: throw NoStackTraceException(context.getString(R.string.no_book))\n            val fileName = \"bookmark-${book.name} ${book.author}.md\"\n            val treeDoc = FileDoc.fromUri(treeUri, true)\n            val fileDoc = treeDoc.createFileIfNotExist(fileName)\n                .openOutputStream()\n                .getOrThrow()\n            fileDoc.use { outputStream ->\n                outputStream.write(\"## ${book.name} ${book.author}\\n\\n\".toByteArray())\n                appDb.bookmarkDao.getByBook(book.name, book.author).forEach {\n                    outputStream.write(\"#### ${it.chapterName}\\n\\n\".toByteArray())\n                    outputStream.write(\"###### 原文\\n ${it.bookText}\\n\\n\".toByteArray())\n                    outputStream.write(\"###### 摘要\\n ${it.content}\\n\\n\".toByteArray())\n                }\n            }\n        }.onError {\n            AppLog.put(\"导出失败\\n${it.localizedMessage}\", it, true)\n        }.onSuccess {\n            context.toastOnUi(\"导出成功\")\n        }\n    }\n\n    interface ChapterListCallBack {\n        fun upChapterList(searchKey: String?)\n\n        fun clearDisplayTitle()\n\n        fun upAdapter()\n    }\n\n    interface BookmarkCallBack {\n        fun upBookmark(searchKey: String?)\n    }\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/book/toc/rule/TxtTocRuleActivity.kt",
    "content": "package io.legado.app.ui.book.toc.rule\n\nimport android.annotation.SuppressLint\nimport android.os.Bundle\nimport android.view.Menu\nimport android.view.MenuItem\nimport androidx.activity.viewModels\nimport androidx.appcompat.widget.PopupMenu\nimport androidx.lifecycle.lifecycleScope\nimport androidx.recyclerview.widget.ItemTouchHelper\nimport io.legado.app.R\nimport io.legado.app.base.VMBaseActivity\nimport io.legado.app.constant.AppLog\nimport io.legado.app.data.appDb\nimport io.legado.app.data.entities.TxtTocRule\nimport io.legado.app.databinding.ActivityTxtTocRuleBinding\nimport io.legado.app.databinding.DialogEditTextBinding\nimport io.legado.app.help.DirectLinkUpload\nimport io.legado.app.lib.dialogs.alert\nimport io.legado.app.lib.theme.primaryColor\nimport io.legado.app.ui.association.ImportTxtTocRuleDialog\nimport io.legado.app.ui.file.HandleFileContract\nimport io.legado.app.ui.qrcode.QrCodeResult\nimport io.legado.app.ui.widget.SelectActionBar\nimport io.legado.app.ui.widget.recycler.DragSelectTouchHelper\nimport io.legado.app.ui.widget.recycler.ItemTouchCallback\nimport io.legado.app.ui.widget.recycler.VerticalDivider\nimport io.legado.app.utils.ACache\nimport io.legado.app.utils.GSON\nimport io.legado.app.utils.isAbsUrl\nimport io.legado.app.utils.launch\nimport io.legado.app.utils.sendToClip\nimport io.legado.app.utils.setEdgeEffectColor\nimport io.legado.app.utils.showDialogFragment\nimport io.legado.app.utils.showHelp\nimport io.legado.app.utils.splitNotBlank\nimport io.legado.app.utils.viewbindingdelegate.viewBinding\nimport kotlinx.coroutines.Dispatchers.IO\nimport kotlinx.coroutines.flow.catch\nimport kotlinx.coroutines.flow.conflate\nimport kotlinx.coroutines.flow.flowOn\nimport kotlinx.coroutines.launch\n\nclass TxtTocRuleActivity : VMBaseActivity<ActivityTxtTocRuleBinding, TxtTocRuleViewModel>(),\n    TxtTocRuleAdapter.CallBack,\n    SelectActionBar.CallBack,\n    TxtTocRuleEditDialog.Callback,\n    PopupMenu.OnMenuItemClickListener {\n\n    override val viewModel by viewModels<TxtTocRuleViewModel>()\n    override val binding by viewBinding(ActivityTxtTocRuleBinding::inflate)\n    private val adapter: TxtTocRuleAdapter by lazy {\n        TxtTocRuleAdapter(this, this)\n    }\n    private val importTocRuleKey = \"tocRuleUrl\"\n    private val qrCodeResult = registerForActivityResult(QrCodeResult()) {\n        it ?: return@registerForActivityResult\n        showDialogFragment(ImportTxtTocRuleDialog(it))\n    }\n    private val importDoc = registerForActivityResult(HandleFileContract()) {\n        it.uri?.let { uri ->\n            showDialogFragment(ImportTxtTocRuleDialog(uri.toString()))\n        }\n    }\n    private val exportResult = registerForActivityResult(HandleFileContract()) {\n        it.uri?.let { uri ->\n            alert(R.string.export_success) {\n                if (uri.toString().isAbsUrl()) {\n                    setMessage(DirectLinkUpload.getSummary())\n                }\n                val alertBinding = DialogEditTextBinding.inflate(layoutInflater).apply {\n                    editView.hint = getString(R.string.path)\n                    editView.setText(uri.toString())\n                }\n                customView { alertBinding.root }\n                okButton {\n                    sendToClip(uri.toString())\n                }\n            }\n        }\n    }\n\n    override fun onActivityCreated(savedInstanceState: Bundle?) {\n        initView()\n        initBottomActionBar()\n        initData()\n    }\n\n    private fun initView() = binding.run {\n        recyclerView.setEdgeEffectColor(primaryColor)\n        recyclerView.addItemDecoration(VerticalDivider(this@TxtTocRuleActivity))\n        recyclerView.adapter = adapter\n        // When this page is opened, it is in selection mode\n        val dragSelectTouchHelper =\n            DragSelectTouchHelper(adapter.dragSelectCallback).setSlideArea(16, 50)\n        dragSelectTouchHelper.attachToRecyclerView(binding.recyclerView)\n        dragSelectTouchHelper.activeSlideSelect()\n        // Note: need judge selection first, so add ItemTouchHelper after it.\n        val itemTouchCallback = ItemTouchCallback(adapter)\n        itemTouchCallback.isCanDrag = true\n        ItemTouchHelper(itemTouchCallback).attachToRecyclerView(binding.recyclerView)\n    }\n\n    private fun initBottomActionBar() {\n        binding.selectActionBar.setMainActionText(R.string.delete)\n        binding.selectActionBar.inflateMenu(R.menu.txt_toc_rule_sel)\n        binding.selectActionBar.setOnMenuItemClickListener(this)\n        binding.selectActionBar.setCallBack(this)\n    }\n\n    private fun initData() {\n        lifecycleScope.launch {\n            appDb.txtTocRuleDao.observeAll().catch {\n                AppLog.put(\"TXT目录规则界面获取数据失败\\n${it.localizedMessage}\", it)\n            }.flowOn(IO).conflate().collect { tocRules ->\n                adapter.setItems(tocRules, adapter.diffItemCallBack)\n            }\n        }\n    }\n\n    override fun onCompatCreateOptionsMenu(menu: Menu): Boolean {\n        menuInflater.inflate(R.menu.txt_toc_rule, menu)\n        return super.onCompatCreateOptionsMenu(menu)\n    }\n\n    override fun onCompatOptionsItemSelected(item: MenuItem): Boolean {\n        when (item.itemId) {\n            R.id.menu_add -> showDialogFragment(TxtTocRuleEditDialog())\n            R.id.menu_import_local -> importDoc.launch {\n                mode = HandleFileContract.FILE\n                allowExtensions = arrayOf(\"txt\", \"json\")\n            }\n\n            R.id.menu_import_onLine -> showImportDialog()\n            R.id.menu_import_qr -> qrCodeResult.launch()\n            R.id.menu_import_default -> viewModel.importDefault()\n            R.id.menu_help -> showHelp(\"txtTocRuleHelp\")\n\n        }\n        return super.onCompatOptionsItemSelected(item)\n    }\n\n    override fun del(source: TxtTocRule) {\n        alert(R.string.draw) {\n            setMessage(getString(R.string.sure_del) + \"\\n\" + source.name)\n            noButton()\n            yesButton {\n                viewModel.del(source)\n            }\n        }\n    }\n\n    override fun edit(source: TxtTocRule) {\n        showDialogFragment(TxtTocRuleEditDialog(source.id))\n    }\n\n    override fun onClickSelectBarMainAction() {\n        delSourceDialog()\n    }\n\n    override fun revertSelection() {\n        adapter.revertSelection()\n    }\n\n    override fun selectAll(selectAll: Boolean) {\n        if (selectAll) {\n            adapter.selectAll()\n        } else {\n            adapter.revertSelection()\n        }\n    }\n\n    override fun saveTxtTocRule(txtTocRule: TxtTocRule) {\n        viewModel.save(txtTocRule)\n    }\n\n    override fun update(vararg source: TxtTocRule) {\n        viewModel.update(*source)\n    }\n\n    override fun toTop(source: TxtTocRule) {\n        viewModel.toTop(source)\n    }\n\n    override fun toBottom(source: TxtTocRule) {\n        viewModel.toBottom(source)\n    }\n\n    override fun upOrder() {\n        viewModel.upOrder()\n    }\n\n    override fun upCountView() {\n        binding.selectActionBar\n            .upCountView(adapter.selection.size, adapter.itemCount)\n    }\n\n    private fun delSourceDialog() {\n        alert(titleResource = R.string.draw, messageResource = R.string.sure_del) {\n            yesButton { viewModel.del(*adapter.selection.toTypedArray()) }\n            noButton()\n        }\n    }\n\n    @SuppressLint(\"InflateParams\")\n    private fun showImportDialog() {\n        val aCache = ACache.get(cacheDir = false)\n        val defaultUrl = \"https://gitee.com/fisher52/YueDuJson/raw/master/myTxtChapterRule.json\"\n        val cacheUrls: MutableList<String> = aCache\n            .getAsString(importTocRuleKey)\n            ?.splitNotBlank(\",\")\n            ?.toMutableList()\n            ?: mutableListOf()\n        if (!cacheUrls.contains(defaultUrl)) {\n            cacheUrls.add(0, defaultUrl)\n        }\n        alert(titleResource = R.string.import_on_line) {\n            val alertBinding = DialogEditTextBinding.inflate(layoutInflater).apply {\n                editView.hint = \"url\"\n                editView.setFilterValues(cacheUrls)\n                editView.delCallBack = {\n                    cacheUrls.remove(it)\n                    aCache.put(importTocRuleKey, cacheUrls.joinToString(\",\"))\n                }\n            }\n            customView { alertBinding.root }\n            okButton {\n                val text = alertBinding.editView.text?.toString()\n                text?.let {\n                    if (it.isAbsUrl() && !cacheUrls.contains(it)) {\n                        cacheUrls.add(0, it)\n                        aCache.put(importTocRuleKey, cacheUrls.joinToString(\",\"))\n                    }\n                    showDialogFragment(ImportTxtTocRuleDialog(it))\n                }\n            }\n            cancelButton()\n        }\n    }\n\n    override fun onMenuItemClick(item: MenuItem): Boolean {\n        when (item.itemId) {\n            R.id.menu_enable_selection -> viewModel.enableSelection(\n                *adapter.selection.toTypedArray()\n            )\n\n            R.id.menu_disable_selection -> viewModel.disableSelection(\n                *adapter.selection.toTypedArray()\n            )\n\n            R.id.menu_export_selection -> exportResult.launch {\n                mode = HandleFileContract.EXPORT\n                fileData = HandleFileContract.FileData(\n                    \"exportTxtTocRule.json\",\n                    GSON.toJson(adapter.selection).toByteArray(),\n                    \"application/json\"\n                )\n            }\n        }\n        return true\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/book/toc/rule/TxtTocRuleAdapter.kt",
    "content": "package io.legado.app.ui.book.toc.rule\n\nimport android.content.Context\nimport android.os.Bundle\nimport android.view.View\nimport android.view.ViewGroup\nimport android.widget.PopupMenu\nimport androidx.core.os.bundleOf\nimport androidx.recyclerview.widget.DiffUtil\nimport androidx.recyclerview.widget.RecyclerView\nimport io.legado.app.R\nimport io.legado.app.base.adapter.ItemViewHolder\nimport io.legado.app.base.adapter.RecyclerAdapter\nimport io.legado.app.data.entities.TxtTocRule\nimport io.legado.app.databinding.ItemTxtTocRuleBinding\nimport io.legado.app.lib.theme.backgroundColor\nimport io.legado.app.ui.widget.recycler.DragSelectTouchHelper\nimport io.legado.app.ui.widget.recycler.ItemTouchCallback\nimport io.legado.app.utils.ColorUtils\n\nclass TxtTocRuleAdapter(context: Context, private val callBack: CallBack) :\n    RecyclerAdapter<TxtTocRule, ItemTxtTocRuleBinding>(context),\n    ItemTouchCallback.Callback {\n\n    private val selected = linkedSetOf<TxtTocRule>()\n\n    val selection: List<TxtTocRule>\n        get() = getItems().filter {\n            selected.contains(it)\n        }\n\n    val diffItemCallBack = object : DiffUtil.ItemCallback<TxtTocRule>() {\n\n        override fun areItemsTheSame(oldItem: TxtTocRule, newItem: TxtTocRule): Boolean {\n            return oldItem.id == newItem.id\n        }\n\n        override fun areContentsTheSame(oldItem: TxtTocRule, newItem: TxtTocRule): Boolean {\n            if (oldItem.name != newItem.name) {\n                return false\n            }\n            if (oldItem.enable != newItem.enable) {\n                return false\n            }\n            if (oldItem.example != newItem.example) {\n                return false\n            }\n            return true\n        }\n\n        override fun getChangePayload(oldItem: TxtTocRule, newItem: TxtTocRule): Any? {\n            val payload = Bundle()\n            if (oldItem.name != newItem.name) {\n                payload.putBoolean(\"upName\", true)\n            }\n            if (oldItem.enable != newItem.enable) {\n                payload.putBoolean(\"enabled\", newItem.enable)\n            }\n            if (oldItem.example != newItem.example) {\n                payload.putBoolean(\"upExample\", true)\n            }\n            if (payload.isEmpty) {\n                return null\n            }\n            return payload\n        }\n    }\n\n    override fun getViewBinding(parent: ViewGroup): ItemTxtTocRuleBinding {\n        return ItemTxtTocRuleBinding.inflate(inflater, parent, false)\n    }\n\n    override fun convert(\n        holder: ItemViewHolder,\n        binding: ItemTxtTocRuleBinding,\n        item: TxtTocRule,\n        payloads: MutableList<Any>\n    ) {\n        binding.run {\n            if (payloads.isEmpty()) {\n                root.setBackgroundColor(ColorUtils.withAlpha(context.backgroundColor, 0.5f))\n                cbSource.text = item.name\n                swtEnabled.isChecked = item.enable\n                cbSource.isChecked = selected.contains(item)\n                titleExample.text = item.example\n            } else {\n                for (i in payloads.indices) {\n                    val bundle = payloads[i] as Bundle\n                    bundle.keySet().forEach {\n                        when (it) {\n                            \"selected\" -> cbSource.isChecked = selected.contains(item)\n                            \"upName\" -> cbSource.text = item.name\n                            \"upExample\" -> titleExample.text = item.example\n                            \"enabled\" -> swtEnabled.isChecked = item.enable\n                        }\n                    }\n                }\n            }\n        }\n    }\n\n    override fun registerListener(holder: ItemViewHolder, binding: ItemTxtTocRuleBinding) {\n        binding.cbSource.setOnUserCheckedChangeListener { isChecked ->\n            getItem(holder.layoutPosition)?.let {\n                if (isChecked) {\n                    selected.add(it)\n                } else {\n                    selected.remove(it)\n                }\n                callBack.upCountView()\n            }\n        }\n        binding.swtEnabled.setOnUserCheckedChangeListener { isChecked ->\n            getItem(holder.layoutPosition)?.let {\n                it.enable = isChecked\n                callBack.update(it)\n            }\n        }\n        binding.ivEdit.setOnClickListener {\n            getItem(holder.layoutPosition)?.let {\n                callBack.edit(it)\n            }\n        }\n        binding.ivMenuMore.setOnClickListener {\n            showMenu(it, holder.layoutPosition)\n        }\n    }\n\n    override fun onCurrentListChanged() {\n        callBack.upCountView()\n    }\n\n    private fun showMenu(view: View, position: Int) {\n        val source = getItem(position) ?: return\n        val popupMenu = PopupMenu(context, view)\n        popupMenu.inflate(R.menu.txt_toc_rule_item)\n        popupMenu.setOnMenuItemClickListener { menuItem ->\n            when (menuItem.itemId) {\n                R.id.menu_top -> callBack.toTop(source)\n                R.id.menu_bottom -> callBack.toBottom(source)\n                R.id.menu_del -> {\n                    callBack.del(source)\n                    selected.remove(source)\n                }\n            }\n            true\n        }\n        popupMenu.show()\n    }\n\n    fun selectAll() {\n        getItems().forEach {\n            selected.add(it)\n        }\n        notifyItemRangeChanged(0, itemCount, bundleOf(Pair(\"selected\", null)))\n        callBack.upCountView()\n    }\n\n    fun revertSelection() {\n        getItems().forEach {\n            if (selected.contains(it)) {\n                selected.remove(it)\n            } else {\n                selected.add(it)\n            }\n        }\n        notifyItemRangeChanged(0, itemCount, bundleOf(Pair(\"selected\", null)))\n        callBack.upCountView()\n    }\n\n    override fun swap(srcPosition: Int, targetPosition: Int): Boolean {\n        val srcItem = getItem(srcPosition)\n        val targetItem = getItem(targetPosition)\n        if (srcItem != null && targetItem != null) {\n            if (srcItem.serialNumber == targetItem.serialNumber) {\n                callBack.upOrder()\n            } else {\n                val srcOrder = srcItem.serialNumber\n                srcItem.serialNumber = targetItem.serialNumber\n                targetItem.serialNumber = srcOrder\n                movedItems.add(srcItem)\n                movedItems.add(targetItem)\n            }\n        }\n        swapItem(srcPosition, targetPosition)\n        return true\n    }\n\n    private val movedItems = hashSetOf<TxtTocRule>()\n\n    override fun onClearView(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder) {\n        if (movedItems.isNotEmpty()) {\n            callBack.update(*movedItems.toTypedArray())\n            movedItems.clear()\n        }\n    }\n\n    val dragSelectCallback: DragSelectTouchHelper.Callback =\n        object : DragSelectTouchHelper.AdvanceCallback<TxtTocRule>(Mode.ToggleAndReverse) {\n            override fun currentSelectedId(): MutableSet<TxtTocRule> {\n                return selected\n            }\n\n            override fun getItemId(position: Int): TxtTocRule {\n                return getItem(position)!!\n            }\n\n            override fun updateSelectState(position: Int, isSelected: Boolean): Boolean {\n                getItem(position)?.let {\n                    if (isSelected) {\n                        selected.add(it)\n                    } else {\n                        selected.remove(it)\n                    }\n                    notifyItemChanged(position, bundleOf(Pair(\"selected\", null)))\n                    callBack.upCountView()\n                    return true\n                }\n                return false\n            }\n        }\n\n    interface CallBack {\n        fun del(source: TxtTocRule)\n        fun edit(source: TxtTocRule)\n        fun update(vararg source: TxtTocRule)\n        fun toTop(source: TxtTocRule)\n        fun toBottom(source: TxtTocRule)\n        fun upOrder()\n        fun upCountView()\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/book/toc/rule/TxtTocRuleDialog.kt",
    "content": "package io.legado.app.ui.book.toc.rule\n\nimport android.annotation.SuppressLint\nimport android.content.Context\nimport android.os.Bundle\nimport android.view.MenuItem\nimport android.view.View\nimport android.view.ViewGroup\nimport androidx.appcompat.widget.Toolbar\nimport androidx.core.os.bundleOf\nimport androidx.fragment.app.viewModels\nimport androidx.lifecycle.lifecycleScope\nimport androidx.recyclerview.widget.DiffUtil\nimport androidx.recyclerview.widget.ItemTouchHelper\nimport androidx.recyclerview.widget.RecyclerView\nimport io.legado.app.R\nimport io.legado.app.base.BaseDialogFragment\nimport io.legado.app.base.adapter.ItemViewHolder\nimport io.legado.app.base.adapter.RecyclerAdapter\nimport io.legado.app.constant.AppLog\nimport io.legado.app.data.appDb\nimport io.legado.app.data.entities.TxtTocRule\nimport io.legado.app.databinding.DialogEditTextBinding\nimport io.legado.app.databinding.DialogTocRegexBinding\nimport io.legado.app.databinding.ItemTocRegexBinding\nimport io.legado.app.lib.dialogs.alert\nimport io.legado.app.lib.theme.backgroundColor\nimport io.legado.app.lib.theme.primaryColor\nimport io.legado.app.ui.association.ImportTxtTocRuleDialog\nimport io.legado.app.ui.file.HandleFileContract\nimport io.legado.app.ui.qrcode.QrCodeResult\nimport io.legado.app.ui.widget.recycler.ItemTouchCallback\nimport io.legado.app.ui.widget.recycler.VerticalDivider\nimport io.legado.app.utils.ACache\nimport io.legado.app.utils.applyTint\nimport io.legado.app.utils.isAbsUrl\nimport io.legado.app.utils.launch\nimport io.legado.app.utils.setLayout\nimport io.legado.app.utils.showDialogFragment\nimport io.legado.app.utils.showHelp\nimport io.legado.app.utils.splitNotBlank\nimport io.legado.app.utils.viewbindingdelegate.viewBinding\nimport kotlinx.coroutines.Dispatchers.IO\nimport kotlinx.coroutines.flow.catch\nimport kotlinx.coroutines.flow.conflate\nimport kotlinx.coroutines.flow.flowOn\nimport kotlinx.coroutines.launch\n\n/**\n * txt目录规则\n */\nclass TxtTocRuleDialog() : BaseDialogFragment(R.layout.dialog_toc_regex),\n    Toolbar.OnMenuItemClickListener,\n    TxtTocRuleEditDialog.Callback {\n\n    constructor(tocRegex: String?) : this() {\n        arguments = Bundle().apply {\n            putString(\"tocRegex\", tocRegex)\n        }\n    }\n\n    private val importTocRuleKey = \"tocRuleUrl\"\n    private val viewModel: TxtTocRuleViewModel by viewModels()\n    private val binding by viewBinding(DialogTocRegexBinding::bind)\n    private val adapter by lazy { TocRegexAdapter(requireContext()) }\n    var selectedName: String? = null\n    private var durRegex: String? = null\n    private val qrCodeResult = registerForActivityResult(QrCodeResult()) {\n        it ?: return@registerForActivityResult\n        showDialogFragment(ImportTxtTocRuleDialog(it))\n    }\n    private val importDoc = registerForActivityResult(HandleFileContract()) {\n        it.uri?.let { uri ->\n            showDialogFragment(ImportTxtTocRuleDialog(uri.toString()))\n        }\n    }\n\n    override fun onStart() {\n        super.onStart()\n        setLayout(0.9f, 0.8f)\n    }\n\n    override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) {\n        binding.toolBar.setBackgroundColor(primaryColor)\n        durRegex = arguments?.getString(\"tocRegex\")\n        binding.toolBar.setTitle(R.string.txt_toc_rule)\n        binding.toolBar.inflateMenu(R.menu.txt_toc_rule)\n        binding.toolBar.menu.applyTint(requireContext())\n        binding.toolBar.setOnMenuItemClickListener(this)\n        initView()\n        initData()\n    }\n\n    private fun initView() = binding.run {\n        recyclerView.addItemDecoration(VerticalDivider(requireContext()))\n        recyclerView.adapter = adapter\n        val itemTouchCallback = ItemTouchCallback(adapter)\n        itemTouchCallback.isCanDrag = true\n        ItemTouchHelper(itemTouchCallback).attachToRecyclerView(recyclerView)\n        tvCancel.setOnClickListener {\n            dismissAllowingStateLoss()\n        }\n        tvOk.setOnClickListener {\n            adapter.getItems().forEach { tocRule ->\n                if (selectedName == tocRule.name) {\n                    val callBack = activity as? CallBack\n                    callBack?.onTocRegexDialogResult(tocRule.rule)\n                    dismissAllowingStateLoss()\n                    return@setOnClickListener\n                }\n            }\n        }\n    }\n\n    private fun initData() {\n        lifecycleScope.launch {\n            appDb.txtTocRuleDao.observeAll().catch {\n                AppLog.put(\"TXT目录规则对话框获取数据失败\\n${it.localizedMessage}\", it)\n            }.flowOn(IO).conflate().collect { tocRules ->\n                initSelectedName(tocRules)\n                adapter.setItems(tocRules, adapter.diffItemCallBack)\n            }\n        }\n    }\n\n    private fun initSelectedName(tocRules: List<TxtTocRule>) {\n        if (selectedName == null && durRegex != null) {\n            tocRules.forEach {\n                if (durRegex == it.rule) {\n                    selectedName = it.name\n                    return@forEach\n                }\n            }\n            if (selectedName == null) {\n                selectedName = \"\"\n            }\n        }\n    }\n\n    override fun onMenuItemClick(item: MenuItem?): Boolean {\n        when (item?.itemId) {\n            R.id.menu_add -> showDialogFragment(TxtTocRuleEditDialog())\n            R.id.menu_import_local -> importDoc.launch {\n                mode = HandleFileContract.FILE\n                allowExtensions = arrayOf(\"txt\", \"json\")\n            }\n\n            R.id.menu_import_onLine -> showImportDialog()\n            R.id.menu_import_qr -> qrCodeResult.launch()\n            R.id.menu_import_default -> viewModel.importDefault()\n            R.id.menu_help -> showHelp(\"txtTocRuleHelp\")\n        }\n        return false\n    }\n\n    override fun saveTxtTocRule(txtTocRule: TxtTocRule) {\n        viewModel.save(txtTocRule)\n    }\n\n    @SuppressLint(\"InflateParams\")\n    private fun showImportDialog() {\n        val aCache = ACache.get(cacheDir = false)\n        val defaultUrl = \"https://gitee.com/fisher52/YueDuJson/raw/master/myTxtChapterRule.json\"\n        val cacheUrls: MutableList<String> = aCache\n            .getAsString(importTocRuleKey)\n            ?.splitNotBlank(\",\")\n            ?.toMutableList()\n            ?: mutableListOf()\n        if (!cacheUrls.contains(defaultUrl)) {\n            cacheUrls.add(0, defaultUrl)\n        }\n        requireContext().alert(titleResource = R.string.import_on_line) {\n            val alertBinding = DialogEditTextBinding.inflate(layoutInflater).apply {\n                editView.hint = \"url\"\n                editView.setFilterValues(cacheUrls)\n                editView.delCallBack = {\n                    cacheUrls.remove(it)\n                    aCache.put(importTocRuleKey, cacheUrls.joinToString(\",\"))\n                }\n            }\n            customView { alertBinding.root }\n            okButton {\n                val text = alertBinding.editView.text?.toString()\n                text?.let {\n                    if (it.isAbsUrl() && !cacheUrls.contains(it)) {\n                        cacheUrls.add(0, it)\n                        aCache.put(importTocRuleKey, cacheUrls.joinToString(\",\"))\n                    }\n                    showDialogFragment(ImportTxtTocRuleDialog(it))\n                }\n            }\n            cancelButton()\n        }\n    }\n\n    inner class TocRegexAdapter(context: Context) :\n        RecyclerAdapter<TxtTocRule, ItemTocRegexBinding>(context),\n        ItemTouchCallback.Callback {\n\n        val diffItemCallBack = object : DiffUtil.ItemCallback<TxtTocRule>() {\n\n            override fun areItemsTheSame(oldItem: TxtTocRule, newItem: TxtTocRule): Boolean {\n                return oldItem.id == newItem.id\n            }\n\n            override fun areContentsTheSame(oldItem: TxtTocRule, newItem: TxtTocRule): Boolean {\n                if (oldItem.name != newItem.name) {\n                    return false\n                }\n                if (oldItem.enable != newItem.enable) {\n                    return false\n                }\n                if (oldItem.example != newItem.example) {\n                    return false\n                }\n                return true\n            }\n\n            override fun getChangePayload(oldItem: TxtTocRule, newItem: TxtTocRule): Any? {\n                val payload = Bundle()\n                if (oldItem.name != newItem.name) {\n                    payload.putBoolean(\"upName\", true)\n                }\n                if (oldItem.enable != newItem.enable) {\n                    payload.putBoolean(\"enabled\", newItem.enable)\n                }\n                if (oldItem.example != newItem.example) {\n                    payload.putBoolean(\"upExample\", true)\n                }\n                if (payload.isEmpty) {\n                    return null\n                }\n                return payload\n            }\n        }\n\n        override fun getViewBinding(parent: ViewGroup): ItemTocRegexBinding {\n            return ItemTocRegexBinding.inflate(inflater, parent, false)\n        }\n\n        override fun convert(\n            holder: ItemViewHolder,\n            binding: ItemTocRegexBinding,\n            item: TxtTocRule,\n            payloads: MutableList<Any>\n        ) {\n            binding.apply {\n                if (payloads.isEmpty()) {\n                    root.setBackgroundColor(context.backgroundColor)\n                    rbRegexName.text = item.name\n                    titleExample.text = item.example\n                    rbRegexName.isChecked = item.name == selectedName\n                    swtEnabled.isChecked = item.enable\n                } else {\n                    for (i in payloads.indices) {\n                        val bundle = payloads[i] as Bundle\n                        bundle.keySet().forEach {\n                            when (it) {\n                                \"upName\" -> rbRegexName.text = item.name\n                                \"upExample\" -> titleExample.text = item.example\n                                \"enabled\" -> swtEnabled.isChecked = item.enable\n                                \"upSelect\" -> rbRegexName.isChecked = item.name == selectedName\n                            }\n                        }\n                    }\n                }\n            }\n        }\n\n        override fun registerListener(holder: ItemViewHolder, binding: ItemTocRegexBinding) {\n            binding.apply {\n                rbRegexName.setOnUserCheckedChangeListener { isChecked ->\n                    if (isChecked) {\n                        selectedName = getItem(holder.layoutPosition)?.name\n                        updateItems(0, itemCount - 1, bundleOf(\"upSelect\" to null))\n                    }\n                }\n                swtEnabled.setOnUserCheckedChangeListener { isChecked ->\n                    getItem(holder.layoutPosition)?.let {\n                        it.enable = isChecked\n                        viewModel.update(it)\n                    }\n                }\n                ivEdit.setOnClickListener {\n                    showDialogFragment(TxtTocRuleEditDialog(getItem(holder.layoutPosition)?.id))\n                }\n                ivDelete.setOnClickListener {\n                    getItem(holder.layoutPosition)?.let { item ->\n                        alert(R.string.draw) {\n                            setMessage(getString(R.string.sure_del) + \"\\n\" + item.name)\n                            noButton()\n                            yesButton {\n                                viewModel.del(item)\n                            }\n                        }\n                    }\n                }\n            }\n        }\n\n        private var isMoved = false\n\n        override fun swap(srcPosition: Int, targetPosition: Int): Boolean {\n            swapItem(srcPosition, targetPosition)\n            isMoved = true\n            return super.swap(srcPosition, targetPosition)\n        }\n\n        override fun onClearView(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder) {\n            super.onClearView(recyclerView, viewHolder)\n            if (isMoved) {\n                for ((index, item) in getItems().withIndex()) {\n                    item.serialNumber = index + 1\n                }\n                viewModel.update(*getItems().toTypedArray())\n            }\n            isMoved = false\n        }\n    }\n\n    interface CallBack {\n        fun onTocRegexDialogResult(tocRegex: String) {}\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/book/toc/rule/TxtTocRuleEditDialog.kt",
    "content": "package io.legado.app.ui.book.toc.rule\n\nimport android.app.Application\nimport android.os.Bundle\nimport android.view.MenuItem\nimport android.view.View\nimport android.view.ViewGroup\nimport androidx.appcompat.widget.Toolbar\nimport androidx.fragment.app.viewModels\nimport io.legado.app.R\nimport io.legado.app.base.BaseDialogFragment\nimport io.legado.app.base.BaseViewModel\nimport io.legado.app.constant.AppLog\nimport io.legado.app.data.appDb\nimport io.legado.app.data.entities.TxtTocRule\nimport io.legado.app.databinding.DialogTocRegexEditBinding\nimport io.legado.app.exception.NoStackTraceException\nimport io.legado.app.lib.theme.primaryColor\nimport io.legado.app.utils.*\nimport io.legado.app.utils.viewbindingdelegate.viewBinding\nimport kotlinx.coroutines.Dispatchers\nimport java.util.regex.Pattern\nimport java.util.regex.PatternSyntaxException\n\nclass TxtTocRuleEditDialog() : BaseDialogFragment(R.layout.dialog_toc_regex_edit, true),\n    Toolbar.OnMenuItemClickListener {\n\n    constructor(id: Long?) : this() {\n        id ?: return\n        arguments = Bundle().apply {\n            putLong(\"id\", id)\n        }\n    }\n\n    private val binding by viewBinding(DialogTocRegexEditBinding::bind)\n    private val viewModel by viewModels<ViewModel>()\n    private val callback get() = (parentFragment as? Callback) ?: activity as? Callback\n\n    override fun onStart() {\n        super.onStart()\n        setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)\n    }\n\n    override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) {\n        binding.toolBar.setBackgroundColor(primaryColor)\n        initMenu()\n        viewModel.initData(arguments?.getLong(\"id\")) {\n            upRuleView(it)\n        }\n    }\n\n    private fun initMenu() {\n        binding.toolBar.inflateMenu(R.menu.txt_toc_rule_edit)\n        binding.toolBar.menu.applyTint(requireContext())\n        binding.toolBar.setOnMenuItemClickListener(this)\n    }\n\n    override fun onMenuItemClick(item: MenuItem?): Boolean {\n        when (item?.itemId) {\n            R.id.menu_save -> {\n                val tocRule = getRuleFromView()\n                if (checkValid(tocRule)) {\n                    callback?.saveTxtTocRule(getRuleFromView())\n                    dismissAllowingStateLoss()\n                }\n            }\n            R.id.menu_copy_rule -> context?.sendToClip(GSON.toJson(getRuleFromView()))\n            R.id.menu_paste_rule -> viewModel.pasteRule {\n                upRuleView(it)\n            }\n        }\n        return true\n    }\n\n    private fun checkValid(tocRule: TxtTocRule): Boolean {\n        if (tocRule.name.isEmpty()) {\n            toastOnUi(\"名称不能为空\")\n            return false\n        }\n\n        try {\n            Pattern.compile(tocRule.rule, Pattern.MULTILINE)\n        } catch (ex: PatternSyntaxException) {\n            AppLog.put(\"正则语法错误或不支持(txt)：${ex.localizedMessage}\", ex, true)\n            return false\n        }\n\n        return true\n    }\n\n    private fun upRuleView(tocRule: TxtTocRule?) {\n        binding.tvRuleName.setText(tocRule?.name)\n        binding.tvRuleRegex.setText(tocRule?.rule)\n        binding.tvRuleExample.setText(tocRule?.example)\n    }\n\n    private fun getRuleFromView(): TxtTocRule {\n        val tocRule = viewModel.tocRule ?: TxtTocRule().apply {\n            viewModel.tocRule = this\n        }\n        binding.run {\n            tocRule.name = tvRuleName.text.toString()\n            tocRule.rule = tvRuleRegex.text.toString()\n            tocRule.example = tvRuleExample.text.toString()\n        }\n        return tocRule\n    }\n\n    class ViewModel(application: Application) : BaseViewModel(application) {\n\n        var tocRule: TxtTocRule? = null\n\n        fun initData(id: Long?, finally: (tocRule: TxtTocRule?) -> Unit) {\n            if (tocRule != null) return\n            execute {\n                if (id == null) return@execute\n                tocRule = appDb.txtTocRuleDao.get(id)\n            }.onFinally {\n                finally.invoke(tocRule)\n            }\n        }\n\n        fun pasteRule(success: (TxtTocRule) -> Unit) {\n            execute(context = Dispatchers.Main) {\n                val text = context.getClipText()\n                if (text.isNullOrBlank()) {\n                    throw NoStackTraceException(\"剪贴板为空\")\n                }\n                GSON.fromJsonObject<TxtTocRule>(text).getOrNull()\n                    ?: throw NoStackTraceException(\"格式不对\")\n            }.onSuccess {\n                success.invoke(it)\n            }.onError {\n                context.toastOnUi(it.localizedMessage ?: \"Error\")\n                it.printOnDebug()\n            }\n        }\n\n    }\n\n    interface Callback {\n\n        fun saveTxtTocRule(txtTocRule: TxtTocRule)\n\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/book/toc/rule/TxtTocRuleViewModel.kt",
    "content": "package io.legado.app.ui.book.toc.rule\n\nimport android.app.Application\nimport io.legado.app.base.BaseViewModel\nimport io.legado.app.data.appDb\nimport io.legado.app.data.entities.TxtTocRule\nimport io.legado.app.help.DefaultData\n\nclass TxtTocRuleViewModel(app: Application) : BaseViewModel(app) {\n\n    fun save(txtTocRule: TxtTocRule) {\n        execute {\n            appDb.txtTocRuleDao.insert(txtTocRule)\n        }\n    }\n\n    fun del(vararg txtTocRule: TxtTocRule) {\n        execute {\n            appDb.txtTocRuleDao.delete(*txtTocRule)\n        }\n    }\n\n    fun update(vararg txtTocRule: TxtTocRule) {\n        execute {\n            appDb.txtTocRuleDao.update(*txtTocRule)\n        }\n    }\n\n    fun importDefault() {\n        execute {\n            DefaultData.importDefaultTocRules()\n        }\n    }\n\n    fun toTop(vararg rules: TxtTocRule) {\n        execute {\n            val minOrder = appDb.txtTocRuleDao.minOrder - 1\n            rules.forEachIndexed { index, source ->\n                source.serialNumber = minOrder - index\n            }\n            appDb.txtTocRuleDao.update(*rules)\n        }\n    }\n\n    fun toBottom(vararg sources: TxtTocRule) {\n        execute {\n            val maxOrder = appDb.txtTocRuleDao.maxOrder + 1\n            sources.forEachIndexed { index, source ->\n                source.serialNumber = maxOrder + index\n            }\n            appDb.txtTocRuleDao.update(*sources)\n        }\n    }\n\n    fun upOrder() {\n        execute {\n            val sources = appDb.txtTocRuleDao.all\n            for ((index: Int, source: TxtTocRule) in sources.withIndex()) {\n                source.serialNumber = index + 1\n            }\n            appDb.txtTocRuleDao.update(*sources.toTypedArray())\n        }\n    }\n\n    fun enableSelection(vararg txtTocRule: TxtTocRule) {\n        execute {\n            val array = txtTocRule.map { it.copy(enable = true) }.toTypedArray()\n            appDb.txtTocRuleDao.insert(*array)\n        }\n    }\n\n    fun disableSelection(vararg txtTocRule: TxtTocRule) {\n        execute {\n            val array = txtTocRule.map { it.copy(enable = false) }.toTypedArray()\n            appDb.txtTocRuleDao.insert(*array)\n        }\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/browser/WebViewActivity.kt",
    "content": "package io.legado.app.ui.browser\n\nimport android.annotation.SuppressLint\nimport android.content.pm.ActivityInfo\nimport android.net.Uri\nimport android.net.http.SslError\nimport android.os.Bundle\nimport android.view.Menu\nimport android.view.MenuItem\nimport android.view.View\nimport android.webkit.CookieManager\nimport android.webkit.SslErrorHandler\nimport android.webkit.URLUtil\nimport android.webkit.WebChromeClient\nimport android.webkit.WebResourceRequest\nimport android.webkit.WebSettings\nimport android.webkit.WebView\nimport android.webkit.WebViewClient\nimport androidx.activity.addCallback\nimport androidx.activity.viewModels\nimport androidx.core.view.size\nimport io.legado.app.R\nimport io.legado.app.base.VMBaseActivity\nimport io.legado.app.constant.AppConst\nimport io.legado.app.constant.AppConst.imagePathKey\nimport io.legado.app.databinding.ActivityWebViewBinding\nimport io.legado.app.help.config.AppConfig\nimport io.legado.app.help.http.CookieStore\nimport io.legado.app.help.source.SourceVerificationHelp\nimport io.legado.app.lib.dialogs.SelectItem\nimport io.legado.app.lib.dialogs.alert\nimport io.legado.app.lib.dialogs.selector\nimport io.legado.app.lib.theme.accentColor\nimport io.legado.app.model.Download\nimport io.legado.app.ui.association.OnLineImportActivity\nimport io.legado.app.ui.file.HandleFileContract\nimport io.legado.app.utils.ACache\nimport io.legado.app.utils.gone\nimport io.legado.app.utils.invisible\nimport io.legado.app.utils.keepScreenOn\nimport io.legado.app.utils.longSnackbar\nimport io.legado.app.utils.openUrl\nimport io.legado.app.utils.sendToClip\nimport io.legado.app.utils.setDarkeningAllowed\nimport io.legado.app.utils.startActivity\nimport io.legado.app.utils.toggleSystemBar\nimport io.legado.app.utils.viewbindingdelegate.viewBinding\nimport io.legado.app.utils.visible\nimport java.net.URLDecoder\nimport io.legado.app.help.http.CookieManager as AppCookieManager\n\nclass WebViewActivity : VMBaseActivity<ActivityWebViewBinding, WebViewModel>() {\n\n    override val binding by viewBinding(ActivityWebViewBinding::inflate)\n    override val viewModel by viewModels<WebViewModel>()\n    private var customWebViewCallback: WebChromeClient.CustomViewCallback? = null\n    private var webPic: String? = null\n    private var isCloudflareChallenge = false\n    private var isFullScreen = false\n    private val saveImage = registerForActivityResult(HandleFileContract()) {\n        it.uri?.let { uri ->\n            ACache.get().put(imagePathKey, uri.toString())\n            viewModel.saveImage(webPic, uri.toString())\n        }\n    }\n\n    override fun onActivityCreated(savedInstanceState: Bundle?) {\n        binding.titleBar.title = intent.getStringExtra(\"title\") ?: getString(R.string.loading)\n        binding.titleBar.subtitle = intent.getStringExtra(\"sourceName\")\n        viewModel.initData(intent) {\n            val url = viewModel.baseUrl\n            val headerMap = viewModel.headerMap\n            initWebView(url, headerMap)\n            val html = viewModel.html\n            if (html.isNullOrEmpty()) {\n                binding.webView.loadUrl(url, headerMap)\n            } else {\n                binding.webView.loadDataWithBaseURL(url, html, \"text/html\", \"utf-8\", url)\n            }\n        }\n        onBackPressedDispatcher.addCallback(this) {\n            if (binding.customWebView.size > 0) {\n                customWebViewCallback?.onCustomViewHidden()\n                return@addCallback\n            } else if (binding.webView.canGoBack()\n                && binding.webView.copyBackForwardList().size > 1\n            ) {\n                binding.webView.goBack()\n                return@addCallback\n            }\n            if (isFullScreen) {\n                toggleFullScreen()\n                return@addCallback\n            }\n            finish()\n        }\n    }\n\n    override fun onCompatCreateOptionsMenu(menu: Menu): Boolean {\n        menuInflater.inflate(R.menu.web_view, menu)\n        return super.onCompatCreateOptionsMenu(menu)\n    }\n\n    override fun onPrepareOptionsMenu(menu: Menu): Boolean {\n        if (viewModel.sourceOrigin.isNotEmpty()) {\n            menu.findItem(R.id.menu_disable_source)?.isVisible = true\n            menu.findItem(R.id.menu_delete_source)?.isVisible = true\n        }\n        return super.onPrepareOptionsMenu(menu)\n    }\n\n    override fun onCompatOptionsItemSelected(item: MenuItem): Boolean {\n        when (item.itemId) {\n            R.id.menu_open_in_browser -> openUrl(viewModel.baseUrl)\n            R.id.menu_copy_url -> sendToClip(viewModel.baseUrl)\n            R.id.menu_ok -> {\n                if (viewModel.sourceVerificationEnable) {\n                    viewModel.saveVerificationResult(binding.webView) {\n                        finish()\n                    }\n                } else {\n                    finish()\n                }\n            }\n\n            R.id.menu_full_screen -> toggleFullScreen()\n            R.id.menu_disable_source -> {\n                viewModel.disableSource {\n                    finish()\n                }\n            }\n\n            R.id.menu_delete_source -> {\n                alert(R.string.draw) {\n                    setMessage(getString(R.string.sure_del) + \"\\n\" + viewModel.sourceName)\n                    noButton()\n                    yesButton {\n                        viewModel.deleteSource {\n                            finish()\n                        }\n                    }\n                }\n            }\n        }\n        return super.onCompatOptionsItemSelected(item)\n    }\n\n    //实现starBrowser调起页面全屏\n    private fun toggleFullScreen() {\n        isFullScreen = !isFullScreen\n\n        toggleSystemBar(!isFullScreen)\n\n        if (isFullScreen) {\n            supportActionBar?.hide()\n        } else {\n            supportActionBar?.show()\n        }\n    }\n\n    @SuppressLint(\"SetJavaScriptEnabled\")\n    private fun initWebView(url: String, headerMap: HashMap<String, String>) {\n        binding.progressBar.fontColor = accentColor\n        binding.webView.webChromeClient = CustomWebChromeClient()\n        binding.webView.webViewClient = CustomWebViewClient()\n        binding.webView.settings.apply {\n            setDarkeningAllowed(AppConfig.isNightTheme)\n            mixedContentMode = WebSettings.MIXED_CONTENT_ALWAYS_ALLOW\n            domStorageEnabled = true\n            allowContentAccess = true\n            useWideViewPort = true\n            loadWithOverviewMode = true\n            javaScriptEnabled = true\n            builtInZoomControls = true\n            displayZoomControls = false\n            headerMap[AppConst.UA_NAME]?.let {\n                userAgentString = it\n            }\n        }\n        AppCookieManager.applyToWebView(url)\n        binding.webView.setOnLongClickListener {\n            val hitTestResult = binding.webView.hitTestResult\n            if (hitTestResult.type == WebView.HitTestResult.IMAGE_TYPE ||\n                hitTestResult.type == WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE\n            ) {\n                hitTestResult.extra?.let { webPic ->\n                    selector(\n                        arrayListOf(\n                            SelectItem(getString(R.string.action_save), \"save\"),\n                            SelectItem(getString(R.string.select_folder), \"selectFolder\")\n                        )\n                    ) { _, charSequence, _ ->\n                        when (charSequence.value) {\n                            \"save\" -> saveImage(webPic)\n                            \"selectFolder\" -> selectSaveFolder()\n                        }\n                    }\n                    return@setOnLongClickListener true\n                }\n            }\n            return@setOnLongClickListener false\n        }\n        binding.webView.setDownloadListener { downloadUrl, _, contentDisposition, _, _ ->\n            var fileName = URLUtil.guessFileName(downloadUrl, contentDisposition, null)\n            fileName = URLDecoder.decode(fileName, \"UTF-8\")\n            binding.llView.longSnackbar(fileName, getString(R.string.action_download)) {\n                Download.start(this, downloadUrl, fileName)\n            }\n        }\n    }\n\n    private fun saveImage(webPic: String) {\n        this.webPic = webPic\n        val path = ACache.get().getAsString(imagePathKey)\n        if (path.isNullOrEmpty()) {\n            selectSaveFolder()\n        } else {\n            viewModel.saveImage(webPic, path)\n        }\n    }\n\n    private fun selectSaveFolder() {\n        val default = arrayListOf<SelectItem<Int>>()\n        val path = ACache.get().getAsString(imagePathKey)\n        if (!path.isNullOrEmpty()) {\n            default.add(SelectItem(path, -1))\n        }\n        saveImage.launch {\n            otherActions = default\n        }\n    }\n\n    override fun finish() {\n        SourceVerificationHelp.checkResult(viewModel.sourceOrigin)\n        super.finish()\n    }\n\n    override fun onDestroy() {\n        super.onDestroy()\n        binding.webView.destroy()\n    }\n\n    inner class CustomWebChromeClient : WebChromeClient() {\n\n        override fun onProgressChanged(view: WebView?, newProgress: Int) {\n            super.onProgressChanged(view, newProgress)\n            binding.progressBar.setDurProgress(newProgress)\n            binding.progressBar.gone(newProgress == 100)\n        }\n\n        override fun onShowCustomView(view: View?, callback: CustomViewCallback?) {\n            requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_SENSOR\n            binding.llView.invisible()\n            binding.customWebView.addView(view)\n            customWebViewCallback = callback\n            keepScreenOn(true)\n            toggleSystemBar(false)\n        }\n\n        override fun onHideCustomView() {\n            binding.customWebView.removeAllViews()\n            binding.llView.visible()\n            requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED\n            keepScreenOn(false)\n            toggleSystemBar(true)\n        }\n    }\n\n    inner class CustomWebViewClient : WebViewClient() {\n        override fun shouldOverrideUrlLoading(\n            view: WebView?,\n            request: WebResourceRequest?\n        ): Boolean {\n            request?.let {\n                return shouldOverrideUrlLoading(it.url)\n            }\n            return true\n        }\n\n        @Suppress(\"DEPRECATION\", \"OVERRIDE_DEPRECATION\", \"KotlinRedundantDiagnosticSuppress\")\n        override fun shouldOverrideUrlLoading(view: WebView?, url: String?): Boolean {\n            url?.let {\n                return shouldOverrideUrlLoading(Uri.parse(it))\n            }\n            return true\n        }\n\n        override fun onPageFinished(view: WebView?, url: String?) {\n            super.onPageFinished(view, url)\n            val cookieManager = CookieManager.getInstance()\n            url?.let {\n                CookieStore.setCookie(it, cookieManager.getCookie(it))\n            }\n            view?.title?.let { title ->\n                if (title != url && title != view.url && title.isNotBlank()) {\n                    binding.titleBar.title = title\n                } else {\n                    binding.titleBar.title = intent.getStringExtra(\"title\")\n                }\n                view.evaluateJavascript(\"!!window._cf_chl_opt\") {\n                    if (it == \"true\") {\n                        isCloudflareChallenge = true\n                    } else if (isCloudflareChallenge && viewModel.sourceVerificationEnable) {\n                        viewModel.saveVerificationResult(binding.webView) {\n                            finish()\n                        }\n                    }\n                }\n            }\n        }\n\n        private fun shouldOverrideUrlLoading(url: Uri): Boolean {\n            when (url.scheme) {\n                \"http\", \"https\" -> {\n                    return false\n                }\n\n                \"legado\", \"yuedu\" -> {\n                    startActivity<OnLineImportActivity> {\n                        data = url\n                    }\n                    return true\n                }\n\n                else -> {\n                    binding.root.longSnackbar(R.string.jump_to_another_app, R.string.confirm) {\n                        openUrl(url)\n                    }\n                    return true\n                }\n            }\n        }\n\n        @SuppressLint(\"WebViewClientOnReceivedSslError\")\n        override fun onReceivedSslError(\n            view: WebView?,\n            handler: SslErrorHandler?,\n            error: SslError?\n        ) {\n            handler?.proceed()\n        }\n\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/browser/WebViewModel.kt",
    "content": "package io.legado.app.ui.browser\n\nimport android.app.Application\nimport android.content.Intent\nimport android.util.Base64\nimport android.webkit.URLUtil\nimport android.webkit.WebView\nimport io.legado.app.base.BaseViewModel\nimport io.legado.app.constant.AppConst\nimport io.legado.app.constant.AppConst.imagePathKey\nimport io.legado.app.constant.SourceType\nimport io.legado.app.data.appDb\nimport io.legado.app.exception.NoStackTraceException\nimport io.legado.app.help.http.newCallResponseBody\nimport io.legado.app.help.http.okHttpClient\nimport io.legado.app.help.source.SourceHelp\nimport io.legado.app.help.source.SourceVerificationHelp\nimport io.legado.app.model.analyzeRule.AnalyzeUrl\nimport io.legado.app.utils.ACache\nimport io.legado.app.utils.FileDoc\nimport io.legado.app.utils.createFileIfNotExist\nimport io.legado.app.utils.openOutputStream\nimport io.legado.app.utils.printOnDebug\nimport io.legado.app.utils.toastOnUi\nimport org.apache.commons.text.StringEscapeUtils\nimport java.util.Date\n\nclass WebViewModel(application: Application) : BaseViewModel(application) {\n    var intent: Intent? = null\n    var baseUrl: String = \"\"\n    var html: String? = null\n    val headerMap: HashMap<String, String> = hashMapOf()\n    var sourceVerificationEnable: Boolean = false\n    var refetchAfterSuccess: Boolean = true\n    var sourceName: String = \"\"\n    var sourceOrigin: String = \"\"\n    var sourceType = SourceType.book\n\n    fun initData(\n        intent: Intent,\n        success: () -> Unit\n    ) {\n        execute {\n            this@WebViewModel.intent = intent\n            val url = intent.getStringExtra(\"url\")\n                ?: throw NoStackTraceException(\"url不能为空\")\n            sourceName = intent.getStringExtra(\"sourceName\") ?: \"\"\n            sourceOrigin = intent.getStringExtra(\"sourceOrigin\") ?: \"\"\n            sourceType = intent.getIntExtra(\"sourceType\", SourceType.book)\n            sourceVerificationEnable = intent.getBooleanExtra(\"sourceVerificationEnable\", false)\n            refetchAfterSuccess = intent.getBooleanExtra(\"refetchAfterSuccess\", true)\n            val source = SourceHelp.getSource(sourceOrigin, sourceType)\n            val analyzeUrl = AnalyzeUrl(url, source = source, coroutineContext = coroutineContext)\n            baseUrl = analyzeUrl.url\n            headerMap.putAll(analyzeUrl.headerMap)\n            if (analyzeUrl.isPost()) {\n                html = analyzeUrl.getStrResponseAwait(useWebView = false).body\n            }\n        }.onSuccess {\n            success.invoke()\n        }.onError {\n            context.toastOnUi(\"error\\n${it.localizedMessage}\")\n            it.printOnDebug()\n        }\n    }\n\n    fun saveImage(webPic: String?, path: String) {\n        webPic ?: return\n        execute {\n            val fileName = \"${AppConst.fileNameFormat.format(Date(System.currentTimeMillis()))}.jpg\"\n            webData2bitmap(webPic)?.let { byteArray ->\n                val fileDoc = FileDoc.fromDir(path)\n                val picFile = fileDoc.createFileIfNotExist(fileName)\n                picFile.openOutputStream().getOrThrow().use {\n                    it.write(byteArray)\n                }\n            } ?: throw Throwable(\"NULL\")\n        }.onError {\n            ACache.get().remove(imagePathKey)\n            context.toastOnUi(\"保存图片失败:${it.localizedMessage}\")\n        }.onSuccess {\n            context.toastOnUi(\"保存成功\")\n        }\n    }\n\n    private suspend fun webData2bitmap(data: String): ByteArray? {\n        return if (URLUtil.isValidUrl(data)) {\n            okHttpClient.newCallResponseBody {\n                url(data)\n            }.bytes()\n        } else {\n            Base64.decode(data.split(\",\").toTypedArray()[1], Base64.DEFAULT)\n        }\n    }\n\n    fun saveVerificationResult(webView: WebView, success: () -> Unit) {\n        if (!sourceVerificationEnable) {\n            return success.invoke()\n        }\n        if (refetchAfterSuccess) {\n            execute {\n                val url = intent!!.getStringExtra(\"url\")!!\n                val source = appDb.bookSourceDao.getBookSource(sourceOrigin)\n                html = AnalyzeUrl(\n                    url,\n                    headerMapF = headerMap,\n                    source = source,\n                    coroutineContext = coroutineContext\n                ).getStrResponseAwait(useWebView = false).body\n                SourceVerificationHelp.setResult(sourceOrigin, html ?: \"\")\n            }.onSuccess {\n                success.invoke()\n            }\n        } else {\n            webView.evaluateJavascript(\"document.documentElement.outerHTML\") {\n                execute {\n                    html = StringEscapeUtils.unescapeJson(it).trim('\"')\n                    SourceVerificationHelp.setResult(sourceOrigin, html ?: \"\")\n                }.onSuccess {\n                    success.invoke()\n                }\n            }\n        }\n    }\n\n    fun disableSource(block: () -> Unit) {\n        execute {\n            SourceHelp.enableSource(sourceOrigin, sourceType, false)\n        }.onSuccess {\n            block.invoke()\n        }\n    }\n\n    fun deleteSource(block: () -> Unit) {\n        execute {\n            SourceHelp.deleteSource(sourceOrigin, sourceType)\n        }.onSuccess {\n            block.invoke()\n        }\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/config/BackupConfigFragment.kt",
    "content": "package io.legado.app.ui.config\n\nimport android.content.Context\nimport android.content.SharedPreferences\nimport android.os.Bundle\nimport android.text.InputType\nimport android.view.Menu\nimport android.view.MenuInflater\nimport android.view.MenuItem\nimport android.view.View\nimport androidx.core.view.MenuProvider\nimport androidx.fragment.app.activityViewModels\nimport androidx.lifecycle.lifecycleScope\nimport androidx.preference.EditTextPreference\nimport androidx.preference.ListPreference\nimport androidx.preference.Preference\nimport io.legado.app.R\nimport io.legado.app.constant.AppLog\nimport io.legado.app.constant.PreferKey\nimport io.legado.app.exception.NoStackTraceException\nimport io.legado.app.help.AppWebDav\nimport io.legado.app.help.config.AppConfig\nimport io.legado.app.help.config.LocalConfig\nimport io.legado.app.help.coroutine.Coroutine\nimport io.legado.app.help.storage.Backup\nimport io.legado.app.help.storage.BackupConfig\nimport io.legado.app.help.storage.ImportOldData\nimport io.legado.app.help.storage.Restore\nimport io.legado.app.lib.dialogs.alert\nimport io.legado.app.lib.dialogs.selector\nimport io.legado.app.lib.permission.Permissions\nimport io.legado.app.lib.permission.PermissionsCompat\nimport io.legado.app.lib.prefs.fragment.PreferenceFragment\nimport io.legado.app.lib.theme.primaryColor\nimport io.legado.app.ui.about.AppLogDialog\nimport io.legado.app.ui.file.HandleFileContract\nimport io.legado.app.ui.widget.dialog.WaitDialog\nimport io.legado.app.utils.FileDoc\nimport io.legado.app.utils.applyTint\nimport io.legado.app.utils.checkWrite\nimport io.legado.app.utils.getPrefString\nimport io.legado.app.utils.isContentScheme\nimport io.legado.app.utils.launch\nimport io.legado.app.utils.setEdgeEffectColor\nimport io.legado.app.utils.showDialogFragment\nimport io.legado.app.utils.showHelp\nimport io.legado.app.utils.toEditable\nimport io.legado.app.utils.toastOnUi\nimport kotlinx.coroutines.Dispatchers.IO\nimport kotlinx.coroutines.Dispatchers.Main\nimport kotlinx.coroutines.Job\nimport kotlinx.coroutines.currentCoroutineContext\nimport kotlinx.coroutines.ensureActive\nimport kotlinx.coroutines.launch\nimport kotlinx.coroutines.withContext\nimport splitties.init.appCtx\n\nclass BackupConfigFragment : PreferenceFragment(),\n    SharedPreferences.OnSharedPreferenceChangeListener,\n    MenuProvider {\n\n    private val viewModel by activityViewModels<ConfigViewModel>()\n    private val waitDialog by lazy { WaitDialog(requireContext()) }\n    private var backupJob: Job? = null\n    private var restoreJob: Job? = null\n\n    private val selectBackupPath = registerForActivityResult(HandleFileContract()) {\n        it.uri?.let { uri ->\n            if (uri.isContentScheme()) {\n                AppConfig.backupPath = uri.toString()\n            } else {\n                AppConfig.backupPath = uri.path\n            }\n        }\n    }\n    private val backupDir = registerForActivityResult(HandleFileContract()) { result ->\n        result.uri?.let { uri ->\n            if (uri.isContentScheme()) {\n                AppConfig.backupPath = uri.toString()\n                backup(uri.toString())\n            } else {\n                uri.path?.let { path ->\n                    AppConfig.backupPath = path\n                    backup(path)\n                }\n            }\n        }\n    }\n    private val restoreDoc = registerForActivityResult(HandleFileContract()) {\n        it.uri?.let { uri ->\n            waitDialog.setText(\"恢复中…\")\n            waitDialog.show()\n            val task = Coroutine.async {\n                Restore.restore(appCtx, uri)\n            }.onFinally {\n                waitDialog.dismiss()\n            }\n            waitDialog.setOnCancelListener {\n                task.cancel()\n            }\n        }\n    }\n    private val restoreOld = registerForActivityResult(HandleFileContract()) {\n        it.uri?.let { uri ->\n            ImportOldData.importUri(appCtx, uri)\n        }\n    }\n\n    override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {\n        addPreferencesFromResource(R.xml.pref_config_backup)\n        findPreference<EditTextPreference>(PreferKey.webDavPassword)?.let {\n            it.setOnBindEditTextListener { editText ->\n                editText.inputType =\n                    InputType.TYPE_TEXT_VARIATION_PASSWORD or InputType.TYPE_CLASS_TEXT\n                editText.setSelection(editText.text.length)\n            }\n        }\n        findPreference<EditTextPreference>(PreferKey.webDavDir)?.let {\n            it.setOnBindEditTextListener { editText ->\n                editText.text = AppConfig.webDavDir?.toEditable()\n                editText.setSelection(editText.text.length)\n            }\n        }\n        findPreference<EditTextPreference>(PreferKey.webDavDeviceName)?.let {\n            it.setOnBindEditTextListener { editText ->\n                editText.text = AppConfig.webDavDeviceName?.toEditable()\n                editText.setSelection(editText.text.length)\n            }\n        }\n        upPreferenceSummary(PreferKey.webDavUrl, getPrefString(PreferKey.webDavUrl))\n        upPreferenceSummary(PreferKey.webDavAccount, getPrefString(PreferKey.webDavAccount))\n        upPreferenceSummary(PreferKey.webDavPassword, getPrefString(PreferKey.webDavPassword))\n        upPreferenceSummary(PreferKey.webDavDir, AppConfig.webDavDir)\n        upPreferenceSummary(PreferKey.webDavDeviceName, AppConfig.webDavDeviceName)\n        upPreferenceSummary(PreferKey.backupPath, getPrefString(PreferKey.backupPath))\n        findPreference<io.legado.app.lib.prefs.Preference>(\"web_dav_restore\")\n            ?.onLongClick {\n                restoreFromLocal()\n                true\n            }\n    }\n\n    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n        super.onViewCreated(view, savedInstanceState)\n        activity?.setTitle(R.string.backup_restore)\n        preferenceManager.sharedPreferences?.registerOnSharedPreferenceChangeListener(this)\n        listView.setEdgeEffectColor(primaryColor)\n        activity?.addMenuProvider(this, viewLifecycleOwner)\n        if (!LocalConfig.backupHelpVersionIsLast) {\n            showHelp(\"webDavHelp\")\n        }\n    }\n\n    override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {\n        menuInflater.inflate(R.menu.backup_restore, menu)\n        menu.applyTint(requireContext())\n    }\n\n    override fun onMenuItemSelected(menuItem: MenuItem): Boolean {\n        when (menuItem.itemId) {\n            R.id.menu_help -> {\n                showHelp(\"webDavHelp\")\n                return true\n            }\n\n            R.id.menu_log -> showDialogFragment<AppLogDialog>()\n        }\n        return false\n    }\n\n    override fun onDestroy() {\n        super.onDestroy()\n        preferenceManager.sharedPreferences?.unregisterOnSharedPreferenceChangeListener(this)\n    }\n\n    override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) {\n        when (key) {\n            PreferKey.backupPath -> upPreferenceSummary(key, getPrefString(key))\n            PreferKey.webDavUrl,\n            PreferKey.webDavAccount,\n            PreferKey.webDavPassword,\n            PreferKey.webDavDir -> listView.post {\n                upPreferenceSummary(key, appCtx.getPrefString(key))\n                viewModel.upWebDavConfig()\n            }\n\n            PreferKey.webDavDeviceName -> upPreferenceSummary(key, getPrefString(key))\n        }\n    }\n\n    private fun upPreferenceSummary(preferenceKey: String, value: String?) {\n        val preference = findPreference<Preference>(preferenceKey) ?: return\n        when (preferenceKey) {\n            PreferKey.webDavUrl ->\n                if (value.isNullOrBlank()) {\n                    preference.summary = getString(R.string.web_dav_url_s)\n                } else {\n                    preference.summary = value\n                }\n\n            PreferKey.webDavAccount ->\n                if (value.isNullOrBlank()) {\n                    preference.summary = getString(R.string.web_dav_account_s)\n                } else {\n                    preference.summary = value\n                }\n\n            PreferKey.webDavPassword ->\n                if (value.isNullOrEmpty()) {\n                    preference.summary = getString(R.string.web_dav_pw_s)\n                } else {\n                    preference.summary = \"*\".repeat(value.length)\n                }\n\n            PreferKey.webDavDir -> preference.summary = when (value) {\n                null -> \"legado\"\n                else -> value\n            }\n\n            else -> {\n                if (preference is ListPreference) {\n                    val index = preference.findIndexOfValue(value)\n                    // Set the summary to reflect the new value.\n                    preference.summary = if (index >= 0) preference.entries[index] else null\n                } else {\n                    preference.summary = value\n                }\n            }\n        }\n    }\n\n    override fun onPreferenceTreeClick(preference: Preference): Boolean {\n        when (preference.key) {\n            PreferKey.backupPath -> selectBackupPath.launch()\n            PreferKey.restoreIgnore -> backupIgnore()\n            \"web_dav_backup\" -> backup()\n            \"web_dav_restore\" -> restore()\n            \"import_old\" -> restoreOld.launch()\n        }\n        return super.onPreferenceTreeClick(preference)\n    }\n\n    /**\n     * 备份忽略设置\n     */\n    private fun backupIgnore() {\n        val checkedItems = BooleanArray(BackupConfig.ignoreKeys.size) {\n            BackupConfig.ignoreConfig[BackupConfig.ignoreKeys[it]] ?: false\n        }\n        alert(R.string.restore_ignore) {\n            multiChoiceItems(BackupConfig.ignoreTitle, checkedItems) { _, which, isChecked ->\n                BackupConfig.ignoreConfig[BackupConfig.ignoreKeys[which]] = isChecked\n            }\n            onDismiss {\n                BackupConfig.saveIgnoreConfig()\n            }\n        }\n    }\n\n\n    fun backup() {\n        val backupPath = AppConfig.backupPath\n        if (backupPath.isNullOrEmpty()) {\n            backupDir.launch()\n        } else {\n            if (backupPath.isContentScheme()) {\n                lifecycleScope.launch {\n                    val canWrite = withContext(IO) {\n                        FileDoc.fromDir(backupPath).checkWrite()\n                    }\n                    if (canWrite) {\n                        backup(backupPath)\n                    } else {\n                        backupDir.launch()\n                    }\n                }\n            } else {\n                backupUsePermission(backupPath)\n            }\n        }\n    }\n\n    private fun backup(backupPath: String) {\n        waitDialog.setText(\"备份中…\")\n        waitDialog.setOnCancelListener {\n            backupJob?.cancel()\n        }\n        waitDialog.show()\n        backupJob?.cancel()\n        backupJob = lifecycleScope.launch {\n            try {\n                Backup.backupLocked(requireContext(), backupPath)\n                appCtx.toastOnUi(R.string.backup_success)\n            } catch (e: Throwable) {\n                ensureActive()\n                AppLog.put(\"备份出错\\n${e.localizedMessage}\", e)\n                appCtx.toastOnUi(\n                    appCtx.getString(\n                        R.string.backup_fail,\n                        e.localizedMessage\n                    )\n                )\n            } finally {\n                ensureActive()\n                waitDialog.dismiss()\n            }\n        }\n    }\n\n    private fun backupUsePermission(path: String) {\n        PermissionsCompat.Builder()\n            .addPermissions(*Permissions.Group.STORAGE)\n            .rationale(R.string.tip_perm_request_storage)\n            .onGranted {\n                backup(path)\n            }\n            .request()\n    }\n\n    fun restore() {\n        waitDialog.setText(R.string.loading)\n        waitDialog.setOnCancelListener {\n            restoreJob?.cancel()\n        }\n        waitDialog.show()\n        Coroutine.async {\n            restoreJob = coroutineContext[Job]\n            showRestoreDialog(requireContext())\n        }.onError {\n            AppLog.put(\"恢复备份出错WebDavError\\n${it.localizedMessage}\", it)\n            if (context == null) {\n                return@onError\n            }\n            alert {\n                setTitle(R.string.restore)\n                setMessage(\"WebDavError\\n${it.localizedMessage}\\n将从本地备份恢复。\")\n                okButton {\n                    restoreFromLocal()\n                }\n                cancelButton()\n            }\n        }.onFinally {\n            waitDialog.dismiss()\n        }\n    }\n\n    private suspend fun showRestoreDialog(context: Context) {\n        val names = withContext(IO) { AppWebDav.getBackupNames() }\n        if (AppWebDav.isJianGuoYun && names.size > 700) {\n            context.toastOnUi(\"由于坚果云限制列出文件数量，部分备份可能未显示，请及时清理旧备份\")\n        }\n        if (names.isNotEmpty()) {\n            currentCoroutineContext().ensureActive()\n            withContext(Main) {\n                context.selector(\n                    title = context.getString(R.string.select_restore_file),\n                    items = names\n                ) { _, index ->\n                    if (index in 0 until names.size) {\n                        listView.post {\n                            restoreWebDav(names[index])\n                        }\n                    }\n                }\n            }\n        } else {\n            throw NoStackTraceException(\"Web dav no back up file\")\n        }\n    }\n\n    private fun restoreWebDav(name: String) {\n        waitDialog.setText(\"恢复中…\")\n        waitDialog.show()\n        val task = Coroutine.async {\n            AppWebDav.restoreWebDav(name)\n        }.onError {\n            AppLog.put(\"WebDav恢复出错\\n${it.localizedMessage}\", it)\n            appCtx.toastOnUi(\"WebDav恢复出错\\n${it.localizedMessage}\")\n        }.onFinally {\n            waitDialog.dismiss()\n        }\n        waitDialog.setOnCancelListener {\n            task.cancel()\n        }\n    }\n\n    private fun restoreFromLocal() {\n        restoreDoc.launch {\n            title = getString(R.string.select_restore_file)\n            mode = HandleFileContract.FILE\n            allowExtensions = arrayOf(\"zip\")\n        }\n    }\n\n    override fun onDestroyView() {\n        super.onDestroyView()\n        waitDialog.dismiss()\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/config/CheckSourceConfig.kt",
    "content": "package io.legado.app.ui.config\n\nimport android.os.Bundle\nimport android.view.View\nimport android.view.ViewGroup\nimport io.legado.app.R\nimport io.legado.app.base.BaseDialogFragment\nimport io.legado.app.constant.PreferKey\nimport io.legado.app.databinding.DialogCheckSourceConfigBinding\nimport io.legado.app.lib.theme.primaryColor\nimport io.legado.app.model.CheckSource\nimport io.legado.app.utils.putPrefString\nimport io.legado.app.utils.setLayout\nimport io.legado.app.utils.toastOnUi\nimport io.legado.app.utils.viewbindingdelegate.viewBinding\nimport splitties.views.onClick\n\nclass CheckSourceConfig : BaseDialogFragment(R.layout.dialog_check_source_config) {\n\n    private val binding by viewBinding(DialogCheckSourceConfigBinding::bind)\n\n    //允许的最小超时时间，秒\n    private val minTimeout = 0L\n\n    override fun onStart() {\n        super.onStart()\n        setLayout(0.9f, ViewGroup.LayoutParams.WRAP_CONTENT)\n    }\n\n    override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) {\n        binding.toolBar.setBackgroundColor(primaryColor)\n        binding.run {\n            checkSearch.onClick {\n                if (!checkSearch.isChecked && !checkDiscovery.isChecked) {\n                    checkDiscovery.isChecked = true\n                }\n            }\n            checkDiscovery.onClick {\n                if (!checkSearch.isChecked && !checkDiscovery.isChecked) {\n                    checkSearch.isChecked = true\n                }\n            }\n            checkInfo.onClick {\n                if (!checkInfo.isChecked) {\n                    checkCategory.isChecked = false\n                    checkContent.isChecked = false\n                    checkCategory.isEnabled = false\n                    checkContent.isEnabled = false\n                } else {\n                    checkCategory.isEnabled = true\n                }\n            }\n            checkCategory.onClick {\n                if (!checkCategory.isChecked) {\n                    checkContent.isChecked = false\n                    checkContent.isEnabled = false\n                } else {\n                    checkContent.isEnabled = true\n                }\n            }\n        }\n        CheckSource.run {\n            binding.checkSourceTimeout.setText((timeout / 1000).toString())\n            binding.checkSearch.isChecked = checkSearch\n            binding.checkDiscovery.isChecked = checkDiscovery\n            binding.checkInfo.isChecked = checkInfo\n            binding.checkCategory.isChecked = checkCategory\n            binding.checkContent.isChecked = checkContent\n            binding.checkCategory.isEnabled = checkInfo\n            binding.checkContent.isEnabled = checkCategory\n            binding.tvCancel.onClick {\n                dismiss()\n            }\n            binding.tvOk.onClick {\n                val text = binding.checkSourceTimeout.text.toString()\n                when {\n                    text.isBlank() -> {\n                        toastOnUi(\"${getString(R.string.timeout)}${getString(R.string.cannot_empty)}\")\n                        return@onClick\n                    }\n                    text.toLong() <= minTimeout -> {\n                        toastOnUi(\n                            \"${getString(R.string.timeout)}${getString(R.string.less_than)}${minTimeout}${\n                                getString(\n                                    R.string.seconds\n                                )\n                            }\"\n                        )\n                        return@onClick\n                    }\n                    else -> timeout = text.toLong() * 1000\n                }\n                checkSearch = binding.checkSearch.isChecked\n                checkDiscovery = binding.checkDiscovery.isChecked\n                checkInfo = binding.checkInfo.isChecked\n                checkCategory = binding.checkCategory.isChecked\n                checkContent = binding.checkContent.isChecked\n                putConfig()\n                putPrefString(PreferKey.checkSource, summary)\n                dismiss()\n            }\n        }\n    }\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/config/ConfigActivity.kt",
    "content": "package io.legado.app.ui.config\n\nimport android.os.Bundle\nimport androidx.activity.viewModels\nimport androidx.fragment.app.Fragment\nimport io.legado.app.R\nimport io.legado.app.base.VMBaseActivity\nimport io.legado.app.constant.EventBus\nimport io.legado.app.databinding.ActivityConfigBinding\nimport io.legado.app.utils.observeEvent\nimport io.legado.app.utils.viewbindingdelegate.viewBinding\n\nclass ConfigActivity : VMBaseActivity<ActivityConfigBinding, ConfigViewModel>() {\n\n    override val binding by viewBinding(ActivityConfigBinding::inflate)\n    override val viewModel by viewModels<ConfigViewModel>()\n\n    override fun onActivityCreated(savedInstanceState: Bundle?) {\n        when (val configTag = intent.getStringExtra(\"configTag\")) {\n            ConfigTag.OTHER_CONFIG -> replaceFragment<OtherConfigFragment>(configTag)\n            ConfigTag.THEME_CONFIG -> replaceFragment<ThemeConfigFragment>(configTag)\n            ConfigTag.BACKUP_CONFIG -> replaceFragment<BackupConfigFragment>(configTag)\n            ConfigTag.COVER_CONFIG -> replaceFragment<CoverConfigFragment>(configTag)\n            ConfigTag.WELCOME_CONFIG -> replaceFragment<WelcomeConfigFragment>(configTag)\n            else -> finish()\n        }\n    }\n\n    override fun setTitle(resId: Int) {\n        super.setTitle(resId)\n        binding.titleBar.setTitle(resId)\n    }\n\n    inline fun <reified T : Fragment> replaceFragment(configTag: String) {\n        intent.putExtra(\"configTag\", configTag)\n        @Suppress(\"DEPRECATION\")\n        val configFragment = supportFragmentManager.findFragmentByTag(configTag)\n            ?: T::class.java.newInstance()\n        supportFragmentManager.beginTransaction()\n            .replace(R.id.configFrameLayout, configFragment, configTag)\n            .commit()\n    }\n\n    override fun observeLiveBus() {\n        super.observeLiveBus()\n        observeEvent<String>(EventBus.RECREATE) {\n            recreate()\n        }\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/config/ConfigTag.kt",
    "content": "package io.legado.app.ui.config\n\nobject ConfigTag {\n\n    const val OTHER_CONFIG = \"otherConfig\"\n    const val THEME_CONFIG = \"themeConfig\"\n    const val BACKUP_CONFIG = \"backupConfig\"\n    const val COVER_CONFIG = \"coverConfig\"\n    const val WELCOME_CONFIG = \"welcomeConfig\"\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/config/ConfigViewModel.kt",
    "content": "package io.legado.app.ui.config\n\nimport android.app.Application\nimport android.content.Context\nimport io.legado.app.R\nimport io.legado.app.base.BaseViewModel\nimport io.legado.app.data.appDb\nimport io.legado.app.help.AppWebDav\nimport io.legado.app.help.book.BookHelp\nimport io.legado.app.utils.FileUtils\nimport io.legado.app.utils.restart\nimport io.legado.app.utils.toastOnUi\nimport kotlinx.coroutines.delay\nimport splitties.init.appCtx\n\nclass ConfigViewModel(application: Application) : BaseViewModel(application) {\n\n    fun upWebDavConfig() {\n        execute {\n            AppWebDav.upConfig()\n        }\n    }\n\n    fun clearCache() {\n        execute {\n            BookHelp.clearCache()\n            FileUtils.delete(context.cacheDir.absolutePath)\n        }.onSuccess {\n            context.toastOnUi(R.string.clear_cache_success)\n        }\n    }\n\n    fun clearWebViewData() {\n        execute {\n            FileUtils.delete(context.getDir(\"webview\", Context.MODE_PRIVATE))\n            FileUtils.delete(context.getDir(\"hws_webview\", Context.MODE_PRIVATE), true)\n            context.toastOnUi(R.string.clear_webview_data_success)\n            delay(3000)\n            appCtx.restart()\n        }\n    }\n\n    fun shrinkDatabase() {\n        execute {\n            appDb.openHelper.writableDatabase.execSQL(\"VACUUM\")\n        }.onSuccess {\n            context.toastOnUi(R.string.success)\n        }\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/config/CoverConfigFragment.kt",
    "content": "package io.legado.app.ui.config\n\nimport android.annotation.SuppressLint\nimport android.content.SharedPreferences\nimport android.net.Uri\nimport android.os.Bundle\nimport android.view.View\nimport androidx.preference.Preference\nimport io.legado.app.R\nimport io.legado.app.constant.PreferKey\nimport io.legado.app.lib.dialogs.selector\nimport io.legado.app.lib.prefs.SwitchPreference\nimport io.legado.app.lib.prefs.fragment.PreferenceFragment\nimport io.legado.app.lib.theme.primaryColor\nimport io.legado.app.model.BookCover\nimport io.legado.app.ui.file.HandleFileContract\nimport io.legado.app.utils.FileUtils\nimport io.legado.app.utils.MD5Utils\nimport io.legado.app.utils.externalFiles\nimport io.legado.app.utils.getPrefBoolean\nimport io.legado.app.utils.getPrefString\nimport io.legado.app.utils.inputStream\nimport io.legado.app.utils.putPrefString\nimport io.legado.app.utils.readUri\nimport io.legado.app.utils.removePref\nimport io.legado.app.utils.setEdgeEffectColor\nimport io.legado.app.utils.showDialogFragment\nimport io.legado.app.utils.toastOnUi\nimport splitties.init.appCtx\nimport java.io.FileOutputStream\n\nclass CoverConfigFragment : PreferenceFragment(),\n    SharedPreferences.OnSharedPreferenceChangeListener {\n\n    private val requestCodeCover = 111\n    private val requestCodeCoverDark = 112\n    private val selectImage = registerForActivityResult(HandleFileContract()) {\n        it.uri?.let { uri ->\n            when (it.requestCode) {\n                requestCodeCover -> setCoverFromUri(PreferKey.defaultCover, uri)\n                requestCodeCoverDark -> setCoverFromUri(PreferKey.defaultCoverDark, uri)\n            }\n        }\n    }\n\n    override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {\n        addPreferencesFromResource(R.xml.pref_config_cover)\n        upPreferenceSummary(PreferKey.defaultCover, getPrefString(PreferKey.defaultCover))\n        upPreferenceSummary(PreferKey.defaultCoverDark, getPrefString(PreferKey.defaultCoverDark))\n        findPreference<SwitchPreference>(PreferKey.coverShowAuthor)\n            ?.isEnabled = getPrefBoolean(PreferKey.coverShowName)\n        findPreference<SwitchPreference>(PreferKey.coverShowAuthorN)\n            ?.isEnabled = getPrefBoolean(PreferKey.coverShowNameN)\n    }\n\n    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n        super.onViewCreated(view, savedInstanceState)\n        activity?.setTitle(R.string.cover_config)\n        listView.setEdgeEffectColor(primaryColor)\n    }\n\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n        preferenceManager.sharedPreferences?.registerOnSharedPreferenceChangeListener(this)\n    }\n\n    override fun onDestroy() {\n        super.onDestroy()\n        preferenceManager.sharedPreferences?.unregisterOnSharedPreferenceChangeListener(this)\n    }\n\n    override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) {\n        sharedPreferences ?: return\n        when (key) {\n            PreferKey.defaultCover,\n            PreferKey.defaultCoverDark -> {\n                upPreferenceSummary(key, getPrefString(key))\n            }\n\n            PreferKey.coverShowName -> {\n                findPreference<SwitchPreference>(PreferKey.coverShowAuthor)\n                    ?.isEnabled = getPrefBoolean(key)\n                BookCover.upDefaultCover()\n            }\n\n            PreferKey.coverShowNameN -> {\n                findPreference<SwitchPreference>(PreferKey.coverShowAuthorN)\n                    ?.isEnabled = getPrefBoolean(key)\n                BookCover.upDefaultCover()\n            }\n\n            PreferKey.coverShowAuthor,\n            PreferKey.coverShowAuthorN -> {\n                BookCover.upDefaultCover()\n            }\n        }\n    }\n\n    @SuppressLint(\"PrivateResource\")\n    override fun onPreferenceTreeClick(preference: Preference): Boolean {\n        when (preference.key) {\n            \"coverRule\" -> showDialogFragment(CoverRuleConfigDialog())\n            PreferKey.defaultCover ->\n                if (getPrefString(preference.key).isNullOrEmpty()) {\n                    selectImage.launch {\n                        requestCode = requestCodeCover\n                        mode = HandleFileContract.IMAGE\n                    }\n                } else {\n                    context?.selector(\n                        items = arrayListOf(\n                            getString(R.string.delete),\n                            getString(R.string.select_image)\n                        )\n                    ) { _, i ->\n                        if (i == 0) {\n                            removePref(preference.key)\n                            BookCover.upDefaultCover()\n                        } else {\n                            selectImage.launch {\n                                requestCode = requestCodeCover\n                                mode = HandleFileContract.IMAGE\n                            }\n                        }\n                    }\n                }\n\n            PreferKey.defaultCoverDark ->\n                if (getPrefString(preference.key).isNullOrEmpty()) {\n                    selectImage.launch {\n                        requestCode = requestCodeCoverDark\n                        mode = HandleFileContract.IMAGE\n                    }\n                } else {\n                    context?.selector(\n                        items = arrayListOf(\n                            getString(R.string.delete),\n                            getString(R.string.select_image)\n                        )\n                    ) { _, i ->\n                        if (i == 0) {\n                            removePref(preference.key)\n                            BookCover.upDefaultCover()\n                        } else {\n                            selectImage.launch {\n                                requestCode = requestCodeCoverDark\n                                mode = HandleFileContract.IMAGE\n                            }\n                        }\n                    }\n                }\n        }\n        return super.onPreferenceTreeClick(preference)\n    }\n\n    private fun upPreferenceSummary(preferenceKey: String, value: String?) {\n        val preference = findPreference<Preference>(preferenceKey) ?: return\n        when (preferenceKey) {\n            PreferKey.defaultCover,\n            PreferKey.defaultCoverDark -> preference.summary = if (value.isNullOrBlank()) {\n                getString(R.string.select_image)\n            } else {\n                value\n            }\n\n            else -> preference.summary = value\n        }\n    }\n\n    private fun setCoverFromUri(preferenceKey: String, uri: Uri) {\n        readUri(uri) { fileDoc, inputStream ->\n            kotlin.runCatching {\n                var file = requireContext().externalFiles\n                val suffix = fileDoc.name.substringAfterLast(\".\")\n                val fileName = uri.inputStream(requireContext()).getOrThrow().use {\n                    MD5Utils.md5Encode(it) + \".$suffix\"\n                }\n                file = FileUtils.createFileIfNotExist(file, \"covers\", fileName)\n                FileOutputStream(file).use {\n                    inputStream.copyTo(it)\n                }\n                putPrefString(preferenceKey, file.absolutePath)\n                BookCover.upDefaultCover()\n            }.onFailure {\n                appCtx.toastOnUi(it.localizedMessage)\n            }\n        }\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/config/CoverRuleConfigDialog.kt",
    "content": "package io.legado.app.ui.config\n\nimport android.os.Bundle\nimport android.util.Log\nimport android.view.View\nimport android.view.ViewGroup\nimport androidx.lifecycle.lifecycleScope\nimport io.legado.app.R\nimport io.legado.app.base.BaseDialogFragment\nimport io.legado.app.databinding.DialogCoverRuleConfigBinding\nimport io.legado.app.lib.theme.primaryColor\nimport io.legado.app.model.BookCover\nimport io.legado.app.utils.GSON\nimport io.legado.app.utils.setLayout\nimport io.legado.app.utils.toastOnUi\nimport io.legado.app.utils.viewbindingdelegate.viewBinding\nimport kotlinx.coroutines.Dispatchers.IO\nimport kotlinx.coroutines.launch\nimport kotlinx.coroutines.withContext\nimport splitties.views.onClick\n\nclass CoverRuleConfigDialog : BaseDialogFragment(R.layout.dialog_cover_rule_config) {\n\n    val binding by viewBinding(DialogCoverRuleConfigBinding::bind)\n\n    override fun onStart() {\n        super.onStart()\n        setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)\n    }\n\n    override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) {\n        binding.toolBar.setBackgroundColor(primaryColor)\n        initData()\n        binding.tvCancel.onClick {\n            dismissAllowingStateLoss()\n        }\n        binding.tvOk.onClick {\n            val enable = binding.cbEnable.isChecked\n            val searchUrl = binding.editSearchUrl.text?.toString()\n            val coverRule = binding.editCoverUrlRule.text?.toString()\n            if (searchUrl.isNullOrBlank() || coverRule.isNullOrBlank()) {\n                toastOnUi(\"搜索url和cover规则不能为空\")\n            } else {\n                BookCover.CoverRule(enable, searchUrl, coverRule).let { config ->\n                    BookCover.saveCoverRule(config)\n                }\n                dismissAllowingStateLoss()\n            }\n        }\n        binding.tvFooterLeft.onClick {\n            BookCover.delCoverRule()\n            dismissAllowingStateLoss()\n        }\n    }\n\n    private fun initData() {\n        lifecycleScope.launch {\n            val rule = withContext(IO) {\n                BookCover.getCoverRule()\n            }\n            Log.e(\"coverRule\", GSON.toJson(rule))\n            binding.cbEnable.isChecked = rule.enable\n            binding.editSearchUrl.setText(rule.searchUrl)\n            binding.editCoverUrlRule.setText(rule.coverRule)\n        }\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/config/DirectLinkUploadConfig.kt",
    "content": "package io.legado.app.ui.config\n\nimport android.os.Bundle\nimport android.view.MenuItem\nimport android.view.View\nimport android.view.ViewGroup\nimport androidx.appcompat.widget.Toolbar\nimport io.legado.app.R\nimport io.legado.app.base.BaseDialogFragment\nimport io.legado.app.databinding.DialogDirectLinkUploadConfigBinding\nimport io.legado.app.help.DirectLinkUpload\nimport io.legado.app.lib.dialogs.alert\nimport io.legado.app.lib.dialogs.selector\nimport io.legado.app.lib.theme.primaryColor\nimport io.legado.app.utils.GSON\nimport io.legado.app.utils.applyTint\nimport io.legado.app.utils.fromJsonObject\nimport io.legado.app.utils.getClipText\nimport io.legado.app.utils.sendToClip\nimport io.legado.app.utils.setLayout\nimport io.legado.app.utils.toastOnUi\nimport io.legado.app.utils.viewbindingdelegate.viewBinding\nimport splitties.init.appCtx\nimport splitties.views.onClick\n\nclass DirectLinkUploadConfig : BaseDialogFragment(R.layout.dialog_direct_link_upload_config),\n    Toolbar.OnMenuItemClickListener {\n\n    private val binding by viewBinding(DialogDirectLinkUploadConfigBinding::bind)\n\n    override fun onStart() {\n        super.onStart()\n        setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)\n    }\n\n    override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) {\n        binding.toolBar.setBackgroundColor(primaryColor)\n        binding.toolBar.inflateMenu(R.menu.direct_link_upload_config)\n        binding.toolBar.menu.applyTint(requireContext())\n        binding.toolBar.setOnMenuItemClickListener(this)\n        upView(DirectLinkUpload.getRule())\n        binding.tvCancel.onClick {\n            dismiss()\n        }\n        binding.tvFooterLeft.onClick {\n            test()\n        }\n        binding.tvOk.onClick {\n            getRule()?.let { rule ->\n                DirectLinkUpload.putConfig(rule)\n                dismiss()\n            }\n        }\n    }\n\n    override fun onMenuItemClick(item: MenuItem?): Boolean {\n        when (item?.itemId) {\n            R.id.menu_import_default -> importDefault()\n            R.id.menu_copy_rule -> getRule()?.let { rule ->\n                requireContext().sendToClip(GSON.toJson(rule))\n            }\n\n            R.id.menu_paste_rule -> runCatching {\n                requireContext().getClipText()!!.let {\n                    val rule = GSON.fromJsonObject<DirectLinkUpload.Rule>(it).getOrThrow()\n                    upView(rule)\n                }\n            }.onFailure {\n                toastOnUi(\"剪贴板为空或格式不对\")\n            }\n        }\n        return true\n    }\n\n    private fun upView(rule: DirectLinkUpload.Rule) {\n        binding.editUploadUrl.setText(rule.uploadUrl)\n        binding.editDownloadUrlRule.setText(rule.downloadUrlRule)\n        binding.editSummary.setText(rule.summary)\n        binding.cbCompress.isChecked = rule.compress\n    }\n\n    private fun getRule(): DirectLinkUpload.Rule? {\n        val uploadUrl = binding.editUploadUrl.text?.toString()\n        val downloadUrlRule = binding.editDownloadUrlRule.text?.toString()\n        val summary = binding.editSummary.text?.toString()\n        val compress = binding.cbCompress.isChecked\n        if (uploadUrl.isNullOrBlank()) {\n            toastOnUi(\"上传Url不能为空\")\n            return null\n        }\n        if (downloadUrlRule.isNullOrBlank()) {\n            toastOnUi(\"下载Url规则不能为空\")\n            return null\n        }\n        if (summary.isNullOrBlank()) {\n            toastOnUi(\"注释不能为空\")\n            return null\n        }\n        return DirectLinkUpload.Rule(uploadUrl, downloadUrlRule, summary, compress)\n    }\n\n    private fun importDefault() {\n        requireContext().selector(DirectLinkUpload.defaultRules) { _, rule, _ ->\n            upView(rule)\n        }\n    }\n\n    private fun test() {\n        val rule = getRule() ?: return\n        execute {\n            DirectLinkUpload.upLoad(\"test.json\", \"{}\", \"application/json\", rule)\n        }.onError {\n            alertTestResult(it.localizedMessage ?: \"ERROR\")\n        }.onSuccess { result ->\n            alertTestResult(result)\n        }\n    }\n\n    private fun alertTestResult(result: String) {\n        alert {\n            setTitle(\"result\")\n            setMessage(result)\n            okButton()\n            negativeButton(R.string.copy_text) {\n                appCtx.sendToClip(result)\n            }\n        }\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/config/OtherConfigFragment.kt",
    "content": "package io.legado.app.ui.config\n\nimport android.annotation.SuppressLint\nimport android.content.ComponentName\nimport android.content.SharedPreferences\nimport android.content.pm.PackageManager\nimport android.os.Bundle\nimport android.view.View\nimport androidx.core.view.postDelayed\nimport androidx.fragment.app.activityViewModels\nimport androidx.preference.ListPreference\nimport androidx.preference.Preference\nimport com.jeremyliao.liveeventbus.LiveEventBus\nimport io.legado.app.R\nimport io.legado.app.constant.EventBus\nimport io.legado.app.constant.PreferKey\nimport io.legado.app.databinding.DialogEditTextBinding\nimport io.legado.app.help.AppFreezeMonitor\nimport io.legado.app.help.DispatchersMonitor\nimport io.legado.app.help.config.AppConfig\nimport io.legado.app.help.config.LocalConfig\nimport io.legado.app.lib.dialogs.alert\nimport io.legado.app.lib.prefs.fragment.PreferenceFragment\nimport io.legado.app.lib.theme.primaryColor\nimport io.legado.app.model.CheckSource\nimport io.legado.app.model.ImageProvider\nimport io.legado.app.receiver.SharedReceiverActivity\nimport io.legado.app.service.WebService\nimport io.legado.app.ui.file.HandleFileContract\nimport io.legado.app.ui.widget.number.NumberPickerDialog\nimport io.legado.app.utils.LogUtils\nimport io.legado.app.utils.getPrefBoolean\nimport io.legado.app.utils.postEvent\nimport io.legado.app.utils.putPrefBoolean\nimport io.legado.app.utils.putPrefString\nimport io.legado.app.utils.removePref\nimport io.legado.app.utils.restart\nimport io.legado.app.utils.setEdgeEffectColor\nimport io.legado.app.utils.showDialogFragment\nimport splitties.init.appCtx\n\n/**\n * 其它设置\n */\nclass OtherConfigFragment : PreferenceFragment(),\n    SharedPreferences.OnSharedPreferenceChangeListener {\n\n    private val viewModel by activityViewModels<ConfigViewModel>()\n    private val packageManager = appCtx.packageManager\n    private val componentName = ComponentName(\n        appCtx,\n        SharedReceiverActivity::class.java.name\n    )\n    private val localBookTreeSelect = registerForActivityResult(HandleFileContract()) {\n        it.uri?.let { treeUri ->\n            AppConfig.defaultBookTreeUri = treeUri.toString()\n        }\n    }\n\n    override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {\n        putPrefBoolean(PreferKey.processText, isProcessTextEnabled())\n        addPreferencesFromResource(R.xml.pref_config_other)\n        upPreferenceSummary(PreferKey.userAgent, AppConfig.userAgent)\n        upPreferenceSummary(PreferKey.preDownloadNum, AppConfig.preDownloadNum.toString())\n        upPreferenceSummary(PreferKey.threadCount, AppConfig.threadCount.toString())\n        upPreferenceSummary(PreferKey.webPort, AppConfig.webPort.toString())\n        AppConfig.defaultBookTreeUri?.let {\n            upPreferenceSummary(PreferKey.defaultBookTreeUri, it)\n        }\n        upPreferenceSummary(PreferKey.checkSource, CheckSource.summary)\n        upPreferenceSummary(PreferKey.bitmapCacheSize, AppConfig.bitmapCacheSize.toString())\n        upPreferenceSummary(PreferKey.imageRetainNum, AppConfig.imageRetainNum.toString())\n        upPreferenceSummary(PreferKey.sourceEditMaxLine, AppConfig.sourceEditMaxLine.toString())\n    }\n\n    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n        super.onViewCreated(view, savedInstanceState)\n        activity?.setTitle(R.string.other_setting)\n        preferenceManager.sharedPreferences?.registerOnSharedPreferenceChangeListener(this)\n        listView.setEdgeEffectColor(primaryColor)\n    }\n\n    override fun onDestroy() {\n        super.onDestroy()\n        preferenceManager.sharedPreferences?.unregisterOnSharedPreferenceChangeListener(this)\n    }\n\n    override fun onPreferenceTreeClick(preference: Preference): Boolean {\n        when (preference.key) {\n            PreferKey.userAgent -> showUserAgentDialog()\n            PreferKey.defaultBookTreeUri -> localBookTreeSelect.launch {\n                title = getString(R.string.select_book_folder)\n                mode = HandleFileContract.DIR_SYS\n            }\n\n            PreferKey.preDownloadNum -> NumberPickerDialog(requireContext())\n                .setTitle(getString(R.string.pre_download))\n                .setMaxValue(9999)\n                .setMinValue(0)\n                .setValue(AppConfig.preDownloadNum)\n                .show {\n                    AppConfig.preDownloadNum = it\n                }\n\n            PreferKey.threadCount -> NumberPickerDialog(requireContext())\n                .setTitle(getString(R.string.threads_num_title))\n                .setMaxValue(999)\n                .setMinValue(1)\n                .setValue(AppConfig.threadCount)\n                .show {\n                    AppConfig.threadCount = it\n                }\n\n            PreferKey.webPort -> NumberPickerDialog(requireContext())\n                .setTitle(getString(R.string.web_port_title))\n                .setMaxValue(60000)\n                .setMinValue(1024)\n                .setValue(AppConfig.webPort)\n                .show {\n                    AppConfig.webPort = it\n                }\n\n            PreferKey.cleanCache -> clearCache()\n            PreferKey.uploadRule -> showDialogFragment<DirectLinkUploadConfig>()\n            PreferKey.checkSource -> showDialogFragment<CheckSourceConfig>()\n            PreferKey.bitmapCacheSize -> {\n                NumberPickerDialog(requireContext())\n                    .setTitle(getString(R.string.bitmap_cache_size))\n                    .setMaxValue(1024)\n                    .setMinValue(1)\n                    .setValue(AppConfig.bitmapCacheSize)\n                    .show {\n                        AppConfig.bitmapCacheSize = it\n                        ImageProvider.bitmapLruCache.resize(ImageProvider.cacheSize)\n                    }\n            }\n            PreferKey.imageRetainNum -> NumberPickerDialog(requireContext())\n                .setTitle(getString(R.string.image_retain_number))\n                .setMaxValue(999)\n                .setMinValue(0)\n                .setValue(AppConfig.imageRetainNum)\n                .show {\n                    AppConfig.imageRetainNum = it\n                }\n\n            PreferKey.sourceEditMaxLine -> {\n                NumberPickerDialog(requireContext())\n                    .setTitle(getString(R.string.source_edit_text_max_line))\n                    .setMaxValue(Int.MAX_VALUE)\n                    .setMinValue(10)\n                    .setValue(AppConfig.sourceEditMaxLine)\n                    .show {\n                        AppConfig.sourceEditMaxLine = it\n                    }\n            }\n\n            PreferKey.clearWebViewData -> clearWebViewData()\n            \"localPassword\" -> alertLocalPassword()\n            PreferKey.shrinkDatabase -> shrinkDatabase()\n        }\n        return super.onPreferenceTreeClick(preference)\n    }\n\n    override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) {\n        when (key) {\n            PreferKey.preDownloadNum -> {\n                upPreferenceSummary(key, AppConfig.preDownloadNum.toString())\n            }\n\n            PreferKey.threadCount -> {\n                upPreferenceSummary(key, AppConfig.threadCount.toString())\n                postEvent(PreferKey.threadCount, \"\")\n            }\n\n            PreferKey.webPort -> {\n                upPreferenceSummary(key, AppConfig.webPort.toString())\n                if (WebService.isRun) {\n                    WebService.stop(requireContext())\n                    WebService.start(requireContext())\n                }\n            }\n\n            PreferKey.defaultBookTreeUri -> {\n                upPreferenceSummary(key, AppConfig.defaultBookTreeUri)\n            }\n\n            PreferKey.recordLog -> {\n                AppConfig.recordLog = appCtx.getPrefBoolean(PreferKey.recordLog)\n                LogUtils.upLevel()\n                LogUtils.logDeviceInfo()\n                LiveEventBus.config().enableLogger(AppConfig.recordLog)\n                AppFreezeMonitor.init(appCtx)\n                DispatchersMonitor.init()\n            }\n\n            PreferKey.processText -> sharedPreferences?.let {\n                setProcessTextEnable(it.getBoolean(key, true))\n            }\n\n            PreferKey.showDiscovery, PreferKey.showRss -> postEvent(EventBus.NOTIFY_MAIN, true)\n            PreferKey.language -> listView.postDelayed(1000) {\n                appCtx.restart()\n            }\n\n            PreferKey.userAgent -> listView.post {\n                upPreferenceSummary(PreferKey.userAgent, AppConfig.userAgent)\n            }\n\n            PreferKey.checkSource -> listView.post {\n                upPreferenceSummary(PreferKey.checkSource, CheckSource.summary)\n            }\n\n            PreferKey.bitmapCacheSize -> {\n                upPreferenceSummary(key, AppConfig.bitmapCacheSize.toString())\n            }\n\n            PreferKey.imageRetainNum -> {\n                upPreferenceSummary(key, AppConfig.imageRetainNum.toString())\n            }\n\n            PreferKey.sourceEditMaxLine -> {\n                upPreferenceSummary(key, AppConfig.sourceEditMaxLine.toString())\n            }\n        }\n    }\n\n    private fun upPreferenceSummary(preferenceKey: String, value: String?) {\n        val preference = findPreference<Preference>(preferenceKey) ?: return\n        when (preferenceKey) {\n            PreferKey.preDownloadNum -> preference.summary =\n                getString(R.string.pre_download_s, value)\n\n            PreferKey.threadCount -> preference.summary = getString(R.string.threads_num, value)\n            PreferKey.webPort -> preference.summary = getString(R.string.web_port_summary, value)\n            PreferKey.bitmapCacheSize -> preference.summary =\n                getString(R.string.bitmap_cache_size_summary, value)\n            PreferKey.imageRetainNum -> preference.summary =\n                getString(R.string.image_retain_number_summary, value)\n\n            PreferKey.sourceEditMaxLine -> preference.summary =\n                getString(R.string.source_edit_max_line_summary, value)\n\n            else -> if (preference is ListPreference) {\n                val index = preference.findIndexOfValue(value)\n                // Set the summary to reflect the new value.\n                preference.summary = if (index >= 0) preference.entries[index] else null\n            } else {\n                preference.summary = value\n            }\n        }\n    }\n\n    @SuppressLint(\"InflateParams\")\n    private fun showUserAgentDialog() {\n        alert(getString(R.string.user_agent)) {\n            val alertBinding = DialogEditTextBinding.inflate(layoutInflater).apply {\n                editView.hint = getString(R.string.user_agent)\n                editView.setText(AppConfig.userAgent)\n            }\n            customView { alertBinding.root }\n            okButton {\n                val userAgent = alertBinding.editView.text?.toString()\n                if (userAgent.isNullOrBlank()) {\n                    removePref(PreferKey.userAgent)\n                } else {\n                    putPrefString(PreferKey.userAgent, userAgent)\n                }\n            }\n            cancelButton()\n        }\n    }\n\n    private fun clearCache() {\n        requireContext().alert(\n            titleResource = R.string.clear_cache,\n            messageResource = R.string.sure_del\n        ) {\n            okButton {\n                viewModel.clearCache()\n            }\n            noButton()\n        }\n    }\n\n    private fun shrinkDatabase() {\n        alert(R.string.sure, R.string.shrink_database) {\n            okButton {\n                viewModel.shrinkDatabase()\n            }\n            noButton()\n        }\n    }\n\n    private fun clearWebViewData() {\n        alert(R.string.clear_webview_data, R.string.sure_del) {\n            okButton {\n                viewModel.clearWebViewData()\n            }\n            noButton()\n        }\n    }\n\n    private fun isProcessTextEnabled(): Boolean {\n        return packageManager.getComponentEnabledSetting(componentName) != PackageManager.COMPONENT_ENABLED_STATE_DISABLED\n    }\n\n    private fun setProcessTextEnable(enable: Boolean) {\n        if (enable) {\n            packageManager.setComponentEnabledSetting(\n                componentName,\n                PackageManager.COMPONENT_ENABLED_STATE_ENABLED, PackageManager.DONT_KILL_APP\n            )\n        } else {\n            packageManager.setComponentEnabledSetting(\n                componentName,\n                PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP\n            )\n        }\n    }\n\n    private fun alertLocalPassword() {\n        context?.alert(R.string.set_local_password, R.string.set_local_password_summary) {\n            val editTextBinding = DialogEditTextBinding.inflate(layoutInflater).apply {\n                editView.hint = \"password\"\n            }\n            customView {\n                editTextBinding.root\n            }\n            okButton {\n                LocalConfig.password = editTextBinding.editView.text.toString()\n            }\n            cancelButton()\n        }\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/config/ThemeConfigFragment.kt",
    "content": "package io.legado.app.ui.config\n\nimport android.annotation.SuppressLint\nimport android.content.SharedPreferences\nimport android.net.Uri\nimport android.os.Build\nimport android.os.Bundle\nimport android.view.Menu\nimport android.view.MenuInflater\nimport android.view.MenuItem\nimport android.view.View\nimport android.widget.SeekBar\nimport androidx.core.view.MenuProvider\nimport androidx.preference.Preference\nimport io.legado.app.R\nimport io.legado.app.base.AppContextWrapper\nimport io.legado.app.constant.AppConst\nimport io.legado.app.constant.EventBus\nimport io.legado.app.constant.PreferKey\nimport io.legado.app.databinding.DialogEditTextBinding\nimport io.legado.app.databinding.DialogImageBlurringBinding\nimport io.legado.app.help.LauncherIconHelp\nimport io.legado.app.help.config.AppConfig\nimport io.legado.app.help.config.ThemeConfig\nimport io.legado.app.lib.dialogs.alert\nimport io.legado.app.lib.dialogs.selector\nimport io.legado.app.lib.prefs.ColorPreference\nimport io.legado.app.lib.prefs.fragment.PreferenceFragment\nimport io.legado.app.lib.theme.primaryColor\nimport io.legado.app.ui.file.HandleFileContract\nimport io.legado.app.ui.widget.number.NumberPickerDialog\nimport io.legado.app.ui.widget.seekbar.SeekBarChangeListener\nimport io.legado.app.utils.ColorUtils\nimport io.legado.app.utils.FileUtils\nimport io.legado.app.utils.MD5Utils\nimport io.legado.app.utils.applyTint\nimport io.legado.app.utils.externalFiles\nimport io.legado.app.utils.getPrefInt\nimport io.legado.app.utils.getPrefString\nimport io.legado.app.utils.inputStream\nimport io.legado.app.utils.postEvent\nimport io.legado.app.utils.putPrefInt\nimport io.legado.app.utils.putPrefString\nimport io.legado.app.utils.readUri\nimport io.legado.app.utils.removePref\nimport io.legado.app.utils.setEdgeEffectColor\nimport io.legado.app.utils.startActivity\nimport io.legado.app.utils.toastOnUi\nimport splitties.init.appCtx\nimport java.io.FileOutputStream\n\n\n@Suppress(\"SameParameterValue\")\nclass ThemeConfigFragment : PreferenceFragment(),\n    SharedPreferences.OnSharedPreferenceChangeListener,\n    MenuProvider {\n\n    private val requestCodeBgLight = 121\n    private val requestCodeBgDark = 122\n    private val selectImage = registerForActivityResult(HandleFileContract()) {\n        it.uri?.let { uri ->\n            when (it.requestCode) {\n                requestCodeBgLight -> setBgFromUri(uri, PreferKey.bgImage) {\n                    upTheme(false)\n                }\n\n                requestCodeBgDark -> setBgFromUri(uri, PreferKey.bgImageN) {\n                    upTheme(true)\n                }\n            }\n        }\n    }\n\n    override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {\n        addPreferencesFromResource(R.xml.pref_config_theme)\n        if (Build.VERSION.SDK_INT < 26) {\n            preferenceScreen.removePreferenceRecursively(PreferKey.launcherIcon)\n        }\n        upPreferenceSummary(PreferKey.bgImage, getPrefString(PreferKey.bgImage))\n        upPreferenceSummary(PreferKey.bgImageN, getPrefString(PreferKey.bgImageN))\n        upPreferenceSummary(PreferKey.barElevation, AppConfig.elevation.toString())\n        upPreferenceSummary(PreferKey.fontScale)\n        findPreference<ColorPreference>(PreferKey.cBackground)?.let {\n            it.onSaveColor = { color ->\n                if (!ColorUtils.isColorLight(color)) {\n                    toastOnUi(R.string.day_background_too_dark)\n                    true\n                } else {\n                    false\n                }\n            }\n        }\n        findPreference<ColorPreference>(PreferKey.cNBackground)?.let {\n            it.onSaveColor = { color ->\n                if (ColorUtils.isColorLight(color)) {\n                    toastOnUi(R.string.night_background_too_light)\n                    true\n                } else {\n                    false\n                }\n            }\n        }\n    }\n\n    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n        super.onViewCreated(view, savedInstanceState)\n        activity?.setTitle(R.string.theme_setting)\n        listView.setEdgeEffectColor(primaryColor)\n        activity?.addMenuProvider(this, viewLifecycleOwner)\n    }\n\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n        preferenceManager.sharedPreferences?.registerOnSharedPreferenceChangeListener(this)\n    }\n\n    override fun onDestroy() {\n        super.onDestroy()\n        preferenceManager.sharedPreferences?.unregisterOnSharedPreferenceChangeListener(this)\n    }\n\n    override fun onCreateMenu(menu: Menu, menuInflater: MenuInflater) {\n        menuInflater.inflate(R.menu.theme_config, menu)\n        menu.applyTint(requireContext())\n    }\n\n    override fun onMenuItemSelected(menuItem: MenuItem): Boolean {\n        when (menuItem.itemId) {\n            R.id.menu_theme_mode -> {\n                AppConfig.isNightTheme = !AppConfig.isNightTheme\n                ThemeConfig.applyDayNight(requireContext())\n                return true\n            }\n        }\n        return false\n    }\n\n    override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) {\n        sharedPreferences ?: return\n        when (key) {\n            PreferKey.launcherIcon -> LauncherIconHelp.changeIcon(getPrefString(key))\n            PreferKey.transparentStatusBar -> recreateActivities()\n            PreferKey.immNavigationBar -> recreateActivities()\n            PreferKey.cPrimary,\n            PreferKey.cAccent,\n            PreferKey.cBackground,\n            PreferKey.cBBackground -> {\n                upTheme(false)\n            }\n\n            PreferKey.cNPrimary,\n            PreferKey.cNAccent,\n            PreferKey.cNBackground,\n            PreferKey.cNBBackground -> {\n                upTheme(true)\n            }\n\n            PreferKey.bgImage,\n            PreferKey.bgImageN -> {\n                upPreferenceSummary(key, getPrefString(key))\n            }\n        }\n\n    }\n\n    @SuppressLint(\"PrivateResource\")\n    override fun onPreferenceTreeClick(preference: Preference): Boolean {\n        when (val key = preference.key) {\n            PreferKey.barElevation -> NumberPickerDialog(requireContext())\n                .setTitle(getString(R.string.bar_elevation))\n                .setMaxValue(32)\n                .setMinValue(0)\n                .setValue(AppConfig.elevation)\n                .setCustomButton((R.string.btn_default_s)) {\n                    AppConfig.elevation = AppConst.sysElevation\n                    recreateActivities()\n                }\n                .show {\n                    AppConfig.elevation = it\n                    recreateActivities()\n                }\n\n            PreferKey.fontScale -> NumberPickerDialog(requireContext())\n                .setTitle(getString(R.string.font_scale))\n                .setMaxValue(16)\n                .setMinValue(8)\n                .setValue(10)\n                .setCustomButton((R.string.btn_default_s)) {\n                    putPrefInt(PreferKey.fontScale, 0)\n                    recreateActivities()\n                }\n                .show {\n                    putPrefInt(PreferKey.fontScale, it)\n                    recreateActivities()\n                }\n\n            PreferKey.bgImage -> selectBgAction(false)\n            PreferKey.bgImageN -> selectBgAction(true)\n            \"themeList\" -> ThemeListDialog().show(childFragmentManager, \"themeList\")\n            \"saveDayTheme\",\n            \"saveNightTheme\" -> alertSaveTheme(key)\n\n            \"coverConfig\" -> startActivity<ConfigActivity> {\n                putExtra(\"configTag\", ConfigTag.COVER_CONFIG)\n            }\n\n            \"welcomeStyle\" -> startActivity<ConfigActivity> {\n                putExtra(\"configTag\", ConfigTag.WELCOME_CONFIG)\n            }\n        }\n        return super.onPreferenceTreeClick(preference)\n    }\n\n    @SuppressLint(\"InflateParams\")\n    private fun alertSaveTheme(key: String) {\n        alert(R.string.theme_name) {\n            val alertBinding = DialogEditTextBinding.inflate(layoutInflater).apply {\n                editView.hint = \"name\"\n            }\n            customView { alertBinding.root }\n            okButton {\n                alertBinding.editView.text?.toString()?.let { themeName ->\n                    when (key) {\n                        \"saveDayTheme\" -> {\n                            ThemeConfig.saveDayTheme(requireContext(), themeName)\n                        }\n\n                        \"saveNightTheme\" -> {\n                            ThemeConfig.saveNightTheme(requireContext(), themeName)\n                        }\n                    }\n                }\n            }\n            cancelButton()\n        }\n    }\n\n    private fun selectBgAction(isNight: Boolean) {\n        val bgKey = if (isNight) PreferKey.bgImageN else PreferKey.bgImage\n        val blurringKey = if (isNight) PreferKey.bgImageNBlurring else PreferKey.bgImageBlurring\n        val actions = arrayListOf(\n            getString(R.string.background_image_blurring),\n            getString(R.string.select_image)\n        )\n        if (!getPrefString(bgKey).isNullOrEmpty()) {\n            actions.add(getString(R.string.delete))\n        }\n        context?.selector(items = actions) { _, i ->\n            when (i) {\n                0 -> alertImageBlurring(blurringKey) {\n                    upTheme(isNight)\n                }\n\n                1 -> {\n                    if (isNight) {\n                        selectImage.launch {\n                            requestCode = requestCodeBgDark\n                            mode = HandleFileContract.IMAGE\n                        }\n                    } else {\n                        selectImage.launch {\n                            requestCode = requestCodeBgLight\n                            mode = HandleFileContract.IMAGE\n                        }\n                    }\n                }\n\n                2 -> {\n                    removePref(bgKey)\n                    upTheme(isNight)\n                }\n            }\n        }\n    }\n\n    private fun alertImageBlurring(preferKey: String, success: () -> Unit) {\n        alert(R.string.background_image_blurring) {\n            val alertBinding = DialogImageBlurringBinding.inflate(layoutInflater).apply {\n                getPrefInt(preferKey, 0).let {\n                    seekBar.progress = it\n                    textViewValue.text = it.toString()\n                }\n                seekBar.setOnSeekBarChangeListener(object : SeekBarChangeListener {\n                    override fun onProgressChanged(\n                        seekBar: SeekBar,\n                        progress: Int,\n                        fromUser: Boolean\n                    ) {\n                        textViewValue.text = progress.toString()\n                    }\n                })\n            }\n            customView { alertBinding.root }\n            okButton {\n                alertBinding.seekBar.progress.let {\n                    putPrefInt(preferKey, it)\n                    success.invoke()\n                }\n            }\n            cancelButton()\n        }\n    }\n\n    private fun upTheme(isNightTheme: Boolean) {\n        if (AppConfig.isNightTheme == isNightTheme) {\n            listView.post {\n                ThemeConfig.applyTheme(requireContext())\n                recreateActivities()\n            }\n        }\n    }\n\n    private fun recreateActivities() {\n        postEvent(EventBus.RECREATE, \"\")\n    }\n\n    private fun upPreferenceSummary(preferenceKey: String, value: String? = null) {\n        val preference = findPreference<Preference>(preferenceKey) ?: return\n        when (preferenceKey) {\n            PreferKey.barElevation -> preference.summary =\n                getString(R.string.bar_elevation_s, value)\n\n            PreferKey.fontScale -> {\n                val fontScale = AppContextWrapper.getFontScale(requireContext())\n                preference.summary = getString(R.string.font_scale_summary, fontScale)\n            }\n\n            PreferKey.bgImage,\n            PreferKey.bgImageN -> preference.summary = if (value.isNullOrBlank()) {\n                getString(R.string.select_image)\n            } else {\n                value\n            }\n\n            else -> preference.summary = value\n        }\n    }\n\n    private fun setBgFromUri(uri: Uri, preferenceKey: String, success: () -> Unit) {\n        readUri(uri) { fileDoc, inputStream ->\n            kotlin.runCatching {\n                var file = requireContext().externalFiles\n                val suffix = fileDoc.name.substringAfterLast(\".\")\n                val fileName = uri.inputStream(requireContext()).getOrThrow().use {\n                    MD5Utils.md5Encode(it) + \".$suffix\"\n                }\n                file = FileUtils.createFileIfNotExist(file, preferenceKey, fileName)\n                FileOutputStream(file).use {\n                    inputStream.copyTo(it)\n                }\n                putPrefString(preferenceKey, file.absolutePath)\n                success()\n            }.onFailure {\n                appCtx.toastOnUi(it.localizedMessage)\n            }\n        }\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/config/ThemeListDialog.kt",
    "content": "package io.legado.app.ui.config\n\nimport android.content.Context\nimport android.os.Bundle\nimport android.view.MenuItem\nimport android.view.View\nimport android.view.ViewGroup\nimport androidx.appcompat.widget.Toolbar\nimport androidx.recyclerview.widget.LinearLayoutManager\nimport io.legado.app.R\nimport io.legado.app.base.BaseDialogFragment\nimport io.legado.app.base.adapter.ItemViewHolder\nimport io.legado.app.base.adapter.RecyclerAdapter\nimport io.legado.app.databinding.DialogRecyclerViewBinding\nimport io.legado.app.databinding.ItemThemeConfigBinding\nimport io.legado.app.help.config.ThemeConfig\nimport io.legado.app.lib.dialogs.alert\nimport io.legado.app.lib.theme.primaryColor\nimport io.legado.app.ui.widget.recycler.VerticalDivider\nimport io.legado.app.utils.*\nimport io.legado.app.utils.viewbindingdelegate.viewBinding\n\nclass ThemeListDialog : BaseDialogFragment(R.layout.dialog_recycler_view),\n    Toolbar.OnMenuItemClickListener {\n\n    private val binding by viewBinding(DialogRecyclerViewBinding::bind)\n    private val adapter by lazy { Adapter(requireContext()) }\n\n    override fun onStart() {\n        super.onStart()\n        setLayout(0.9f, 0.9f)\n    }\n\n    override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) {\n        binding.toolBar.setBackgroundColor(primaryColor)\n        binding.toolBar.setTitle(R.string.theme_list)\n        initView()\n        initMenu()\n        initData()\n    }\n\n    private fun initView() = binding.run {\n        recyclerView.layoutManager = LinearLayoutManager(requireContext())\n        recyclerView.addItemDecoration(VerticalDivider(requireContext()))\n        recyclerView.adapter = adapter\n    }\n\n    private fun initMenu() = binding.run {\n        toolBar.setOnMenuItemClickListener(this@ThemeListDialog)\n        toolBar.inflateMenu(R.menu.theme_list)\n        toolBar.menu.applyTint(requireContext())\n    }\n\n    fun initData() {\n        adapter.setItems(ThemeConfig.configList)\n    }\n\n    override fun onMenuItemClick(item: MenuItem?): Boolean {\n        when (item?.itemId) {\n            R.id.menu_import -> {\n                requireContext().getClipText()?.let {\n                    if (ThemeConfig.addConfig(it)) {\n                        initData()\n                    } else {\n                        toastOnUi(\"格式不对,添加失败\")\n                    }\n                }\n            }\n        }\n        return true\n    }\n\n    fun delete(index: Int) {\n        alert(R.string.delete, R.string.sure_del) {\n            yesButton {\n                ThemeConfig.delConfig(index)\n                initData()\n            }\n            noButton()\n        }\n    }\n\n    fun share(index: Int) {\n        val json = GSON.toJson(ThemeConfig.configList[index])\n        requireContext().share(json, \"主题分享\")\n    }\n\n    inner class Adapter(context: Context) :\n        RecyclerAdapter<ThemeConfig.Config, ItemThemeConfigBinding>(context) {\n\n        override fun getViewBinding(parent: ViewGroup): ItemThemeConfigBinding {\n            return ItemThemeConfigBinding.inflate(inflater, parent, false)\n        }\n\n        override fun convert(\n            holder: ItemViewHolder,\n            binding: ItemThemeConfigBinding,\n            item: ThemeConfig.Config,\n            payloads: MutableList<Any>\n        ) {\n            binding.apply {\n                tvName.text = item.themeName\n            }\n        }\n\n        override fun registerListener(holder: ItemViewHolder, binding: ItemThemeConfigBinding) {\n            binding.apply {\n                root.setOnClickListener {\n                    ThemeConfig.applyConfig(context, ThemeConfig.configList[holder.layoutPosition])\n                }\n                ivShare.setOnClickListener {\n                    share(holder.layoutPosition)\n                }\n                ivDelete.setOnClickListener {\n                    delete(holder.layoutPosition)\n                }\n            }\n        }\n\n    }\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/config/WelcomeConfigFragment.kt",
    "content": "package io.legado.app.ui.config\n\nimport android.annotation.SuppressLint\nimport android.content.SharedPreferences\nimport android.net.Uri\nimport android.os.Bundle\nimport android.view.View\nimport androidx.preference.Preference\nimport io.legado.app.R\nimport io.legado.app.constant.PreferKey\nimport io.legado.app.help.config.AppConfig\nimport io.legado.app.lib.dialogs.selector\nimport io.legado.app.lib.prefs.SwitchPreference\nimport io.legado.app.lib.prefs.fragment.PreferenceFragment\nimport io.legado.app.lib.theme.primaryColor\nimport io.legado.app.model.BookCover\nimport io.legado.app.ui.file.HandleFileContract\nimport io.legado.app.utils.FileUtils\nimport io.legado.app.utils.MD5Utils\nimport io.legado.app.utils.externalFiles\nimport io.legado.app.utils.getPrefString\nimport io.legado.app.utils.inputStream\nimport io.legado.app.utils.putPrefString\nimport io.legado.app.utils.readUri\nimport io.legado.app.utils.removePref\nimport io.legado.app.utils.setEdgeEffectColor\nimport io.legado.app.utils.toastOnUi\nimport splitties.init.appCtx\nimport java.io.FileOutputStream\n\nclass WelcomeConfigFragment : PreferenceFragment(),\n    SharedPreferences.OnSharedPreferenceChangeListener {\n\n    private val requestWelcomeImage = 221\n    private val requestWelcomeImageDark = 222\n    private val selectImage = registerForActivityResult(HandleFileContract()) {\n        it.uri?.let { uri ->\n            when (it.requestCode) {\n                requestWelcomeImage -> setCoverFromUri(PreferKey.welcomeImage, uri)\n                requestWelcomeImageDark -> setCoverFromUri(PreferKey.welcomeImageDark, uri)\n            }\n        }\n    }\n\n    override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {\n        addPreferencesFromResource(R.xml.pref_config_welcome)\n        val welcomeImage = AppConfig.welcomeImage\n        val welcomeImageDark = AppConfig.welcomeImageDark\n        upPreferenceSummary(PreferKey.welcomeImage, welcomeImage)\n        upPreferenceSummary(PreferKey.welcomeImageDark, welcomeImageDark)\n        findPreference<SwitchPreference>(PreferKey.welcomeShowText)?.let {\n            it.isEnabled = !welcomeImage.isNullOrEmpty()\n        }\n        findPreference<SwitchPreference>(PreferKey.welcomeShowIcon)?.let {\n            it.isEnabled = !welcomeImage.isNullOrEmpty()\n        }\n        findPreference<SwitchPreference>(PreferKey.welcomeShowTextDark)?.let {\n            it.isEnabled = !welcomeImageDark.isNullOrEmpty()\n        }\n        findPreference<SwitchPreference>(PreferKey.welcomeShowIconDark)?.let {\n            it.isEnabled = !welcomeImageDark.isNullOrEmpty()\n        }\n    }\n\n    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n        super.onViewCreated(view, savedInstanceState)\n        activity?.setTitle(R.string.welcome_style)\n        listView.setEdgeEffectColor(primaryColor)\n    }\n\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n        preferenceManager.sharedPreferences?.registerOnSharedPreferenceChangeListener(this)\n    }\n\n    override fun onDestroy() {\n        super.onDestroy()\n        preferenceManager.sharedPreferences?.unregisterOnSharedPreferenceChangeListener(this)\n    }\n\n    override fun onSharedPreferenceChanged(sharedPreferences: SharedPreferences?, key: String?) {\n        sharedPreferences ?: return\n        when (key) {\n            PreferKey.welcomeImage -> {\n                val welcomeImage = getPrefString(key)\n                upPreferenceSummary(key, welcomeImage)\n                findPreference<SwitchPreference>(PreferKey.welcomeShowText)?.let {\n                    it.isEnabled = !welcomeImage.isNullOrEmpty()\n                }\n                findPreference<SwitchPreference>(PreferKey.welcomeShowIcon)?.let {\n                    it.isEnabled = !welcomeImage.isNullOrEmpty()\n                }\n            }\n\n            PreferKey.welcomeImageDark -> {\n                val welcomeImageDark = getPrefString(key)\n                upPreferenceSummary(key, welcomeImageDark)\n                findPreference<SwitchPreference>(PreferKey.welcomeShowTextDark)?.let {\n                    it.isEnabled = !welcomeImageDark.isNullOrEmpty()\n                }\n                findPreference<SwitchPreference>(PreferKey.welcomeShowIconDark)?.let {\n                    it.isEnabled = !welcomeImageDark.isNullOrEmpty()\n                }\n            }\n        }\n    }\n\n    @SuppressLint(\"PrivateResource\")\n    override fun onPreferenceTreeClick(preference: Preference): Boolean {\n        when (preference.key) {\n            PreferKey.welcomeImage ->\n                if (getPrefString(preference.key).isNullOrEmpty()) {\n                    selectImage.launch {\n                        requestCode = requestWelcomeImage\n                        mode = HandleFileContract.IMAGE\n                    }\n                } else {\n                    context?.selector(\n                        items = arrayListOf(\n                            getString(R.string.delete),\n                            getString(R.string.select_image)\n                        )\n                    ) { _, i ->\n                        if (i == 0) {\n                            removePref(preference.key)\n                            AppConfig.welcomeShowText = true\n                            AppConfig.welcomeShowIcon = true\n                            findPreference<SwitchPreference>(PreferKey.welcomeShowText)?.let {\n                                it.isChecked = true\n                            }\n                            findPreference<SwitchPreference>(PreferKey.welcomeShowIcon)?.let {\n                                it.isChecked = true\n                            }\n                            BookCover.upDefaultCover()\n                        } else {\n                            selectImage.launch {\n                                requestCode = requestWelcomeImage\n                                mode = HandleFileContract.IMAGE\n                            }\n                        }\n                    }\n                }\n\n            PreferKey.welcomeImageDark ->\n                if (getPrefString(preference.key).isNullOrEmpty()) {\n                    selectImage.launch {\n                        requestCode = requestWelcomeImageDark\n                        mode = HandleFileContract.IMAGE\n                    }\n                } else {\n                    context?.selector(\n                        items = arrayListOf(\n                            getString(R.string.delete),\n                            getString(R.string.select_image)\n                        )\n                    ) { _, i ->\n                        if (i == 0) {\n                            removePref(preference.key)\n                            AppConfig.welcomeShowTextDark = true\n                            AppConfig.welcomeShowIconDark = true\n                            findPreference<SwitchPreference>(PreferKey.welcomeShowTextDark)?.let {\n                                it.isChecked = true\n                            }\n                            findPreference<SwitchPreference>(PreferKey.welcomeShowIconDark)?.let {\n                                it.isChecked = true\n                            }\n                            BookCover.upDefaultCover()\n                        } else {\n                            selectImage.launch {\n                                requestCode = requestWelcomeImageDark\n                                mode = HandleFileContract.IMAGE\n                            }\n                        }\n                    }\n                }\n        }\n        return super.onPreferenceTreeClick(preference)\n    }\n\n    private fun upPreferenceSummary(preferenceKey: String, value: String?) {\n        val preference = findPreference<Preference>(preferenceKey) ?: return\n        when (preferenceKey) {\n            PreferKey.welcomeImage,\n            PreferKey.welcomeImageDark -> preference.summary = if (value.isNullOrBlank()) {\n                getString(R.string.select_image)\n            } else {\n                value\n            }\n\n            else -> preference.summary = value\n        }\n    }\n\n    private fun setCoverFromUri(preferenceKey: String, uri: Uri) {\n        readUri(uri) { fileDoc, inputStream ->\n            kotlin.runCatching {\n                var file = requireContext().externalFiles\n                val suffix = fileDoc.name.substringAfterLast(\".\")\n                val fileName = uri.inputStream(requireContext()).getOrThrow().use {\n                    MD5Utils.md5Encode(it) + \".$suffix\"\n                }\n                file = FileUtils.createFileIfNotExist(file, \"covers\", fileName)\n                FileOutputStream(file).use {\n                    inputStream.copyTo(it)\n                }\n                putPrefString(preferenceKey, file.absolutePath)\n            }.onFailure {\n                appCtx.toastOnUi(it.localizedMessage)\n            }\n        }\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/dict/DictDialog.kt",
    "content": "package io.legado.app.ui.dict\n\nimport android.os.Bundle\nimport android.text.method.LinkMovementMethod\nimport android.view.View\nimport android.view.ViewGroup\nimport androidx.fragment.app.viewModels\nimport com.google.android.material.tabs.TabLayout\nimport io.legado.app.R\nimport io.legado.app.base.BaseDialogFragment\nimport io.legado.app.data.entities.DictRule\nimport io.legado.app.databinding.DialogDictBinding\nimport io.legado.app.lib.theme.accentColor\nimport io.legado.app.lib.theme.backgroundColor\nimport io.legado.app.utils.setHtml\nimport io.legado.app.utils.setLayout\nimport io.legado.app.utils.toastOnUi\nimport io.legado.app.utils.viewbindingdelegate.viewBinding\n\n/**\n * 词典\n */\nclass DictDialog() : BaseDialogFragment(R.layout.dialog_dict) {\n\n    constructor(word: String) : this() {\n        arguments = Bundle().apply {\n            putString(\"word\", word)\n        }\n    }\n\n    private val viewModel by viewModels<DictViewModel>()\n    private val binding by viewBinding(DialogDictBinding::bind)\n\n    private var word: String? = null\n\n    override fun onStart() {\n        super.onStart()\n        setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)\n    }\n\n    override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) {\n        binding.tvDict.movementMethod = LinkMovementMethod()\n        word = arguments?.getString(\"word\")\n        if (word.isNullOrEmpty()) {\n            toastOnUi(R.string.cannot_empty)\n            dismiss()\n            return\n        }\n        binding.tabLayout.setBackgroundColor(backgroundColor)\n        binding.tabLayout.setSelectedTabIndicatorColor(accentColor)\n        binding.tabLayout.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener {\n            override fun onTabReselected(tab: TabLayout.Tab) {\n\n            }\n\n            override fun onTabUnselected(tab: TabLayout.Tab) {\n\n            }\n\n            override fun onTabSelected(tab: TabLayout.Tab) {\n                val dictRule = tab.tag as DictRule\n                binding.rotateLoading.visible()\n                viewModel.dict(dictRule, word!!) {\n                    binding.rotateLoading.inVisible()\n                    binding.tvDict.setHtml(it)\n                }\n            }\n        })\n        viewModel.initData {\n            it.forEach {\n                binding.tabLayout.addTab(binding.tabLayout.newTab().apply {\n                    text = it.name\n                    tag = it\n                })\n            }\n            \n            setupTabLayoutMode(it.size)\n        }\n    }\n\n    //根据已启用词典数动态选取布局\n    private fun setupTabLayoutMode(dictCount: Int) {\n        if (dictCount <= 4) {\n            binding.tabLayout.tabMode = TabLayout.MODE_FIXED\n            binding.tabLayout.tabGravity = TabLayout.GRAVITY_FILL\n        } else {\n            binding.tabLayout.tabMode = TabLayout.MODE_SCROLLABLE\n            binding.tabLayout.tabGravity = TabLayout.GRAVITY_CENTER\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/dict/DictViewModel.kt",
    "content": "package io.legado.app.ui.dict\n\nimport android.app.Application\nimport io.legado.app.base.BaseViewModel\nimport io.legado.app.data.appDb\nimport io.legado.app.data.entities.DictRule\nimport io.legado.app.help.coroutine.Coroutine\n\nclass DictViewModel(application: Application) : BaseViewModel(application) {\n\n    private var dictJob: Coroutine<String>? = null\n\n    fun initData(onSuccess: (List<DictRule>) -> Unit) {\n        execute {\n            appDb.dictRuleDao.enabled\n        }.onSuccess {\n            onSuccess.invoke(it)\n        }\n    }\n\n    fun dict(\n        dictRule: DictRule,\n        word: String,\n        onFinally: (String) -> Unit\n    ) {\n        dictJob?.cancel()\n        dictJob = execute {\n            dictRule.search(word)\n        }.onSuccess {\n            onFinally.invoke(it)\n        }.onError {\n            onFinally.invoke(it.localizedMessage ?: \"ERROR\")\n        }\n    }\n\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/dict/rule/DictRuleActivity.kt",
    "content": "package io.legado.app.ui.dict.rule\n\nimport android.annotation.SuppressLint\nimport android.os.Bundle\nimport android.view.Menu\nimport android.view.MenuItem\nimport androidx.activity.viewModels\nimport androidx.appcompat.widget.PopupMenu\nimport androidx.lifecycle.lifecycleScope\nimport androidx.recyclerview.widget.ItemTouchHelper\nimport androidx.recyclerview.widget.LinearLayoutManager\nimport io.legado.app.R\nimport io.legado.app.base.VMBaseActivity\nimport io.legado.app.constant.AppLog\nimport io.legado.app.data.appDb\nimport io.legado.app.data.entities.DictRule\nimport io.legado.app.databinding.ActivityDictRuleBinding\nimport io.legado.app.databinding.DialogEditTextBinding\nimport io.legado.app.help.DirectLinkUpload\nimport io.legado.app.lib.dialogs.alert\nimport io.legado.app.lib.theme.primaryColor\nimport io.legado.app.ui.association.ImportDictRuleDialog\nimport io.legado.app.ui.file.HandleFileContract\nimport io.legado.app.ui.qrcode.QrCodeResult\nimport io.legado.app.ui.widget.SelectActionBar\nimport io.legado.app.ui.widget.recycler.DragSelectTouchHelper\nimport io.legado.app.ui.widget.recycler.ItemTouchCallback\nimport io.legado.app.ui.widget.recycler.VerticalDivider\nimport io.legado.app.utils.ACache\nimport io.legado.app.utils.GSON\nimport io.legado.app.utils.isAbsUrl\nimport io.legado.app.utils.launch\nimport io.legado.app.utils.sendToClip\nimport io.legado.app.utils.setEdgeEffectColor\nimport io.legado.app.utils.showDialogFragment\nimport io.legado.app.utils.showHelp\nimport io.legado.app.utils.splitNotBlank\nimport io.legado.app.utils.viewbindingdelegate.viewBinding\nimport kotlinx.coroutines.Dispatchers.IO\nimport kotlinx.coroutines.flow.catch\nimport kotlinx.coroutines.flow.flowOn\nimport kotlinx.coroutines.launch\n\nclass DictRuleActivity : VMBaseActivity<ActivityDictRuleBinding, DictRuleViewModel>(),\n    PopupMenu.OnMenuItemClickListener,\n    SelectActionBar.CallBack,\n    DictRuleAdapter.CallBack {\n\n    override val viewModel by viewModels<DictRuleViewModel>()\n    override val binding by viewBinding(ActivityDictRuleBinding::inflate)\n    private val importRecordKey = \"dictRuleUrls\"\n    private val adapter by lazy { DictRuleAdapter(this, this) }\n    private val qrCodeResult = registerForActivityResult(QrCodeResult()) {\n        it ?: return@registerForActivityResult\n        showDialogFragment(ImportDictRuleDialog(it))\n    }\n    private val importDoc = registerForActivityResult(HandleFileContract()) {\n        it.uri?.let { uri ->\n            showDialogFragment(ImportDictRuleDialog(uri.toString()))\n        }\n    }\n    private val exportResult = registerForActivityResult(HandleFileContract()) {\n        it.uri?.let { uri ->\n            alert(R.string.export_success) {\n                if (uri.toString().isAbsUrl()) {\n                    setMessage(DirectLinkUpload.getSummary())\n                }\n                val alertBinding = DialogEditTextBinding.inflate(layoutInflater).apply {\n                    editView.hint = getString(R.string.path)\n                    editView.setText(uri.toString())\n                }\n                customView { alertBinding.root }\n                okButton {\n                    sendToClip(uri.toString())\n                }\n            }\n        }\n    }\n\n    override fun onActivityCreated(savedInstanceState: Bundle?) {\n        initRecyclerView()\n        initSelectActionView()\n        observeDictRuleData()\n    }\n\n    override fun onCompatCreateOptionsMenu(menu: Menu): Boolean {\n        menuInflater.inflate(R.menu.dict_rule, menu)\n        return super.onCompatCreateOptionsMenu(menu)\n    }\n\n    private fun initRecyclerView() {\n        binding.recyclerView.setEdgeEffectColor(primaryColor)\n        binding.recyclerView.layoutManager = LinearLayoutManager(this)\n        binding.recyclerView.adapter = adapter\n        binding.recyclerView.addItemDecoration(VerticalDivider(this))\n        val itemTouchCallback = ItemTouchCallback(adapter)\n        itemTouchCallback.isCanDrag = true\n        val dragSelectTouchHelper: DragSelectTouchHelper =\n            DragSelectTouchHelper(adapter.dragSelectCallback).setSlideArea(16, 50)\n        dragSelectTouchHelper.attachToRecyclerView(binding.recyclerView)\n        // When this page is opened, it is in selection mode\n        dragSelectTouchHelper.activeSlideSelect()\n\n        // Note: need judge selection first, so add ItemTouchHelper after it.\n        ItemTouchHelper(itemTouchCallback).attachToRecyclerView(binding.recyclerView)\n    }\n\n\n    private fun initSelectActionView() {\n        binding.selectActionBar.setMainActionText(R.string.delete)\n        binding.selectActionBar.inflateMenu(R.menu.dict_rule_sel)\n        binding.selectActionBar.setOnMenuItemClickListener(this)\n        binding.selectActionBar.setCallBack(this)\n    }\n\n    private fun observeDictRuleData() {\n        lifecycleScope.launch {\n            appDb.dictRuleDao.flowAll().catch {\n                AppLog.put(\"字典规则获取数据失败\\n${it.localizedMessage}\", it)\n            }.flowOn(IO).collect {\n                adapter.setItems(it, adapter.diffItemCallBack)\n            }\n        }\n    }\n\n    override fun onCompatOptionsItemSelected(item: MenuItem): Boolean {\n        when (item.itemId) {\n            R.id.menu_add -> showDialogFragment<DictRuleEditDialog>()\n            R.id.menu_import_local -> importDoc.launch {\n                mode = HandleFileContract.FILE\n                allowExtensions = arrayOf(\"txt\", \"json\")\n            }\n\n            R.id.menu_import_onLine -> showImportDialog()\n            R.id.menu_import_qr -> qrCodeResult.launch()\n            R.id.menu_import_default -> viewModel.importDefault()\n            R.id.menu_help -> showHelp(\"dictRuleHelp\")\n        }\n        return super.onCompatOptionsItemSelected(item)\n    }\n\n    override fun onMenuItemClick(item: MenuItem): Boolean {\n        when (item.itemId) {\n            R.id.menu_enable_selection -> {\n                viewModel.enableSelection(*adapter.selection.toTypedArray())\n            }\n\n            R.id.menu_disable_selection -> {\n                viewModel.disableSelection(*adapter.selection.toTypedArray())\n            }\n\n            R.id.menu_export_selection -> exportResult.launch {\n                mode = HandleFileContract.EXPORT\n                fileData = HandleFileContract.FileData(\n                    \"exportDictRule.json\",\n                    GSON.toJson(adapter.selection).toByteArray(),\n                    \"application/json\"\n                )\n            }\n        }\n        return true\n    }\n\n    override fun onClickSelectBarMainAction() {\n        viewModel.delete(*adapter.selection.toTypedArray())\n    }\n\n    override fun selectAll(selectAll: Boolean) {\n        if (selectAll) {\n            adapter.selectAll()\n        } else {\n            adapter.revertSelection()\n        }\n    }\n\n    override fun revertSelection() {\n        adapter.revertSelection()\n    }\n\n    override fun update(vararg rule: DictRule) {\n        viewModel.update(*rule)\n    }\n\n    override fun delete(rule: DictRule) {\n        alert(R.string.draw) {\n            setMessage(getString(R.string.sure_del) + \"\\n\" + rule.name)\n            noButton()\n            yesButton {\n                viewModel.delete(rule)\n            }\n        }\n    }\n\n    override fun edit(rule: DictRule) {\n        showDialogFragment(DictRuleEditDialog(rule.name))\n    }\n\n    override fun upOrder() {\n        viewModel.upSortNumber()\n    }\n\n    override fun upCountView() {\n        binding.selectActionBar.upCountView(\n            adapter.selection.size,\n            adapter.itemCount\n        )\n    }\n\n    @SuppressLint(\"InflateParams\")\n    private fun showImportDialog() {\n        val aCache = ACache.get(cacheDir = false)\n        val cacheUrls: MutableList<String> = aCache\n            .getAsString(importRecordKey)\n            ?.splitNotBlank(\",\")\n            ?.toMutableList() ?: mutableListOf()\n        alert(titleResource = R.string.import_on_line) {\n            val alertBinding = DialogEditTextBinding.inflate(layoutInflater).apply {\n                editView.hint = \"url\"\n                editView.setFilterValues(cacheUrls)\n                editView.delCallBack = {\n                    cacheUrls.remove(it)\n                    aCache.put(importRecordKey, cacheUrls.joinToString(\",\"))\n                }\n            }\n            customView { alertBinding.root }\n            okButton {\n                val text = alertBinding.editView.text?.toString()\n                text?.let {\n                    if (it.isAbsUrl() && !cacheUrls.contains(it)) {\n                        cacheUrls.add(0, it)\n                        aCache.put(importRecordKey, cacheUrls.joinToString(\",\"))\n                    }\n                    showDialogFragment(\n                        ImportDictRuleDialog(it)\n                    )\n                }\n            }\n            cancelButton()\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/dict/rule/DictRuleAdapter.kt",
    "content": "package io.legado.app.ui.dict.rule\n\nimport android.content.Context\nimport android.os.Bundle\nimport android.view.ViewGroup\nimport androidx.core.os.bundleOf\nimport androidx.recyclerview.widget.DiffUtil\nimport androidx.recyclerview.widget.RecyclerView\nimport io.legado.app.base.adapter.ItemViewHolder\nimport io.legado.app.base.adapter.RecyclerAdapter\nimport io.legado.app.data.entities.DictRule\nimport io.legado.app.databinding.ItemDictRuleBinding\nimport io.legado.app.lib.theme.backgroundColor\nimport io.legado.app.ui.widget.recycler.DragSelectTouchHelper\nimport io.legado.app.ui.widget.recycler.ItemTouchCallback\nimport io.legado.app.utils.ColorUtils\n\n\nclass DictRuleAdapter(context: Context, var callBack: CallBack) :\n    RecyclerAdapter<DictRule, ItemDictRuleBinding>(context),\n    ItemTouchCallback.Callback {\n\n    private val selected = linkedSetOf<DictRule>()\n\n    val selection: List<DictRule>\n        get() {\n            return getItems().filter {\n                selected.contains(it)\n            }\n        }\n\n    val diffItemCallBack = object : DiffUtil.ItemCallback<DictRule>() {\n\n        override fun areItemsTheSame(oldItem: DictRule, newItem: DictRule): Boolean {\n            return oldItem.name == newItem.name\n        }\n\n        override fun areContentsTheSame(oldItem: DictRule, newItem: DictRule): Boolean {\n            if (oldItem.name != newItem.name) {\n                return false\n            }\n            if (oldItem.enabled != newItem.enabled) {\n                return false\n            }\n            return true\n        }\n\n        override fun getChangePayload(oldItem: DictRule, newItem: DictRule): Any? {\n            val payload = Bundle()\n            if (oldItem.name != newItem.name) {\n                payload.putBoolean(\"upName\", true)\n            }\n            if (oldItem.enabled != newItem.enabled) {\n                payload.putBoolean(\"enabled\", newItem.enabled)\n            }\n            if (payload.isEmpty) {\n                return null\n            }\n            return payload\n        }\n    }\n\n    fun selectAll() {\n        getItems().forEach {\n            selected.add(it)\n        }\n        notifyItemRangeChanged(0, itemCount, bundleOf(Pair(\"selected\", null)))\n        callBack.upCountView()\n    }\n\n    fun revertSelection() {\n        getItems().forEach {\n            if (selected.contains(it)) {\n                selected.remove(it)\n            } else {\n                selected.add(it)\n            }\n        }\n        notifyItemRangeChanged(0, itemCount, bundleOf(Pair(\"selected\", null)))\n        callBack.upCountView()\n    }\n\n    override fun getViewBinding(parent: ViewGroup): ItemDictRuleBinding {\n        return ItemDictRuleBinding.inflate(inflater, parent, false)\n    }\n\n    override fun onCurrentListChanged() {\n        callBack.upCountView()\n    }\n\n    override fun convert(\n        holder: ItemViewHolder,\n        binding: ItemDictRuleBinding,\n        item: DictRule,\n        payloads: MutableList<Any>\n    ) {\n        binding.run {\n            if (payloads.isEmpty()) {\n                root.setBackgroundColor(ColorUtils.withAlpha(context.backgroundColor, 0.5f))\n                cbName.text = item.name\n                swtEnabled.isChecked = item.enabled\n                cbName.isChecked = selected.contains(item)\n            } else {\n                for (i in payloads.indices) {\n                    val bundle = payloads[i] as Bundle\n                    bundle.keySet().forEach {\n                        when (it) {\n                            \"selected\" -> cbName.isChecked = selected.contains(item)\n                            \"upName\" -> cbName.text = item.name\n                            \"enabled\" -> swtEnabled.isChecked = item.enabled\n                        }\n                    }\n                }\n            }\n        }\n    }\n\n    override fun registerListener(holder: ItemViewHolder, binding: ItemDictRuleBinding) {\n        binding.apply {\n            swtEnabled.setOnUserCheckedChangeListener { isChecked ->\n                getItem(holder.layoutPosition)?.let {\n                    it.enabled = isChecked\n                    callBack.update(it)\n                }\n            }\n            cbName.setOnClickListener {\n                getItem(holder.layoutPosition)?.let {\n                    if (cbName.isChecked) {\n                        selected.add(it)\n                    } else {\n                        selected.remove(it)\n                    }\n                }\n                callBack.upCountView()\n            }\n            ivEdit.setOnClickListener {\n                getItem(holder.layoutPosition)?.let {\n                    callBack.edit(it)\n                }\n            }\n            ivDelete.setOnClickListener {\n                getItem(holder.layoutPosition)?.let {\n                    callBack.delete(it)\n                }\n            }\n        }\n    }\n\n    override fun swap(srcPosition: Int, targetPosition: Int): Boolean {\n        val srcItem = getItem(srcPosition)\n        val targetItem = getItem(targetPosition)\n        if (srcItem != null && targetItem != null) {\n            if (srcItem.sortNumber == targetItem.sortNumber) {\n                callBack.upOrder()\n            } else {\n                val srcOrder = srcItem.sortNumber\n                srcItem.sortNumber = targetItem.sortNumber\n                targetItem.sortNumber = srcOrder\n                movedItems.add(srcItem)\n                movedItems.add(targetItem)\n            }\n        }\n        swapItem(srcPosition, targetPosition)\n        return true\n    }\n\n    private val movedItems = linkedSetOf<DictRule>()\n\n    override fun onClearView(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder) {\n        if (movedItems.isNotEmpty()) {\n            callBack.update(*movedItems.toTypedArray())\n            movedItems.clear()\n        }\n    }\n\n    val dragSelectCallback: DragSelectTouchHelper.Callback =\n        object : DragSelectTouchHelper.AdvanceCallback<DictRule>(Mode.ToggleAndReverse) {\n            override fun currentSelectedId(): MutableSet<DictRule> {\n                return selected\n            }\n\n            override fun getItemId(position: Int): DictRule {\n                return getItem(position)!!\n            }\n\n            override fun updateSelectState(position: Int, isSelected: Boolean): Boolean {\n                getItem(position)?.let {\n                    if (isSelected) {\n                        selected.add(it)\n                    } else {\n                        selected.remove(it)\n                    }\n                    notifyItemChanged(position, bundleOf(Pair(\"selected\", null)))\n                    callBack.upCountView()\n                    return true\n                }\n                return false\n            }\n        }\n\n    interface CallBack {\n        fun update(vararg rule: DictRule)\n        fun delete(rule: DictRule)\n        fun edit(rule: DictRule)\n        fun upOrder()\n        fun upCountView()\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/dict/rule/DictRuleEditDialog.kt",
    "content": "package io.legado.app.ui.dict.rule\n\nimport android.app.Application\nimport android.os.Bundle\nimport android.view.MenuItem\nimport android.view.View\nimport android.view.ViewGroup\nimport androidx.appcompat.widget.Toolbar\n\nimport androidx.fragment.app.viewModels\nimport io.legado.app.R\nimport io.legado.app.base.BaseDialogFragment\nimport io.legado.app.base.BaseViewModel\nimport io.legado.app.data.appDb\nimport io.legado.app.data.entities.DictRule\nimport io.legado.app.databinding.DialogDictRuleEditBinding\nimport io.legado.app.lib.theme.primaryColor\nimport io.legado.app.utils.*\nimport io.legado.app.utils.viewbindingdelegate.viewBinding\n\nclass DictRuleEditDialog() : BaseDialogFragment(R.layout.dialog_dict_rule_edit, true),\n    Toolbar.OnMenuItemClickListener {\n\n    val viewModel by viewModels<DictRuleEditViewModel>()\n    val binding by viewBinding(DialogDictRuleEditBinding::bind)\n\n    constructor(name: String) : this() {\n        arguments = Bundle().apply {\n            putString(\"name\", name)\n        }\n    }\n\n    override fun onStart() {\n        super.onStart()\n        setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)\n    }\n\n    override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) {\n        binding.toolBar.setBackgroundColor(primaryColor)\n        binding.toolBar.inflateMenu(R.menu.dict_rule_edit)\n        binding.toolBar.menu.applyTint(requireContext())\n        binding.toolBar.setOnMenuItemClickListener(this)\n        viewModel.initData(arguments?.getString(\"name\")) {\n            upRuleView(viewModel.dictRule)\n        }\n    }\n\n    override fun onMenuItemClick(item: MenuItem): Boolean {\n        when (item.itemId) {\n            R.id.menu_save -> viewModel.save(getDictRule()) {\n                dismissAllowingStateLoss()\n            }\n            R.id.menu_copy_rule -> viewModel.copyRule(getDictRule())\n            R.id.menu_paste_rule -> viewModel.pasteRule {\n                upRuleView(it)\n            }\n        }\n        return true\n    }\n\n    private fun upRuleView(dictRule: DictRule?) {\n        binding.tvRuleName.setText(dictRule?.name)\n        binding.tvUrlRule.setText(dictRule?.urlRule)\n        binding.tvShowRule.setText(dictRule?.showRule)\n    }\n\n    private fun getDictRule(): DictRule {\n        val dictRule = viewModel.dictRule?.copy() ?: DictRule()\n        dictRule.name = binding.tvRuleName.text.toString()\n        dictRule.urlRule = binding.tvUrlRule.text.toString()\n        dictRule.showRule = binding.tvShowRule.text.toString()\n        return dictRule\n    }\n\n    class DictRuleEditViewModel(application: Application) : BaseViewModel(application) {\n\n        var dictRule: DictRule? = null\n\n        fun initData(name: String?, onFinally: () -> Unit) {\n            execute {\n                if (dictRule == null && name != null) {\n                    dictRule = appDb.dictRuleDao.getByName(name)\n                }\n            }.onFinally {\n                onFinally.invoke()\n            }\n        }\n\n        fun save(newDictRule: DictRule, onFinally: () -> Unit) {\n            execute {\n                dictRule?.let {\n                    appDb.dictRuleDao.delete(it)\n                }\n                appDb.dictRuleDao.insert(newDictRule)\n                dictRule = newDictRule\n            }.onFinally {\n                onFinally.invoke()\n            }\n        }\n\n        fun copyRule(dictRule: DictRule) {\n            context.sendToClip(GSON.toJson(dictRule))\n        }\n\n        fun pasteRule(success: (DictRule) -> Unit) {\n            val text = context.getClipText()\n            if (text.isNullOrBlank()) {\n                context.toastOnUi(\"剪贴板没有内容\")\n                return\n            }\n            execute {\n                GSON.fromJsonObject<DictRule>(text).getOrThrow()\n            }.onSuccess {\n                success.invoke(it)\n            }.onError {\n                context.toastOnUi(\"格式不对\")\n            }\n        }\n\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/dict/rule/DictRuleViewModel.kt",
    "content": "package io.legado.app.ui.dict.rule\n\nimport android.app.Application\nimport io.legado.app.base.BaseViewModel\nimport io.legado.app.constant.AppLog\nimport io.legado.app.data.appDb\nimport io.legado.app.data.entities.DictRule\nimport io.legado.app.help.DefaultData\nimport io.legado.app.utils.toastOnUi\n\nclass DictRuleViewModel(application: Application) : BaseViewModel(application) {\n\n\n    fun update(vararg dictRule: DictRule) {\n        execute {\n            appDb.dictRuleDao.update(*dictRule)\n        }.onError {\n            val msg = \"更新字典规则出错\\n${it.localizedMessage}\"\n            AppLog.put(msg, it)\n            context.toastOnUi(msg)\n        }\n    }\n\n    fun delete(vararg dictRule: DictRule) {\n        execute {\n            appDb.dictRuleDao.delete(*dictRule)\n        }.onError {\n            val msg = \"删除字典规则出错\\n${it.localizedMessage}\"\n            AppLog.put(msg, it)\n            context.toastOnUi(msg)\n        }\n    }\n\n    fun upSortNumber() {\n        execute {\n            val rules = appDb.dictRuleDao.all\n            for ((index, rule) in rules.withIndex()) {\n                rule.sortNumber = index + 1\n            }\n            appDb.dictRuleDao.insert(*rules.toTypedArray())\n        }\n    }\n\n    fun enableSelection(vararg dictRule: DictRule) {\n        execute {\n            val array = dictRule.map { it.copy(enabled = true) }.toTypedArray()\n            appDb.dictRuleDao.insert(*array)\n        }\n    }\n\n    fun disableSelection(vararg dictRule: DictRule) {\n        execute {\n            val array = dictRule.map { it.copy(enabled = false) }.toTypedArray()\n            appDb.dictRuleDao.insert(*array)\n        }\n    }\n\n    fun importDefault() {\n        execute {\n            DefaultData.importDefaultDictRules()\n        }\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/file/FileManageActivity.kt",
    "content": "package io.legado.app.ui.file\n\nimport android.annotation.SuppressLint\nimport android.os.Bundle\nimport android.view.View\nimport android.view.ViewGroup\nimport android.widget.PopupMenu\nimport androidx.activity.addCallback\nimport androidx.activity.viewModels\nimport androidx.appcompat.widget.SearchView\nimport androidx.core.content.FileProvider\nimport androidx.recyclerview.widget.LinearLayoutManager\nimport androidx.recyclerview.widget.RecyclerView\nimport io.legado.app.R\nimport io.legado.app.base.VMBaseActivity\nimport io.legado.app.base.adapter.ItemViewHolder\nimport io.legado.app.base.adapter.RecyclerAdapter\nimport io.legado.app.constant.AppConst\nimport io.legado.app.databinding.ActivityFileManageBinding\nimport io.legado.app.databinding.ItemFileBinding\nimport io.legado.app.databinding.ItemPathPickerBinding\nimport io.legado.app.lib.theme.primaryTextColor\nimport io.legado.app.ui.file.utils.FilePickerIcon\nimport io.legado.app.ui.widget.recycler.VerticalDivider\nimport io.legado.app.utils.ConvertUtils\nimport io.legado.app.utils.applyNavigationBarPadding\nimport io.legado.app.utils.applyTint\nimport io.legado.app.utils.openFileUri\nimport io.legado.app.utils.viewbindingdelegate.viewBinding\nimport java.io.File\n\nclass FileManageActivity : VMBaseActivity<ActivityFileManageBinding, FileManageViewModel>() {\n\n    override val binding by viewBinding(ActivityFileManageBinding::inflate)\n    override val viewModel by viewModels<FileManageViewModel>()\n    private val dirParent = \"..\"\n    private val searchView: SearchView by lazy {\n        binding.titleBar.findViewById(R.id.search_view)\n    }\n    private val pathAdapter by lazy {\n        PathAdapter()\n    }\n    private val fileAdapter by lazy {\n        FileAdapter()\n    }\n    private val currentFiles = arrayListOf<File>()\n\n    override fun onActivityCreated(savedInstanceState: Bundle?) {\n        initView()\n        initSearchView()\n        viewModel.upFiles(viewModel.rootDoc)\n    }\n\n    private fun initView() {\n        binding.rvPath.layoutManager = LinearLayoutManager(this, RecyclerView.HORIZONTAL, false)\n        binding.rvPath.adapter = pathAdapter\n        binding.recyclerView.layoutManager = LinearLayoutManager(this)\n        binding.recyclerView.addItemDecoration(VerticalDivider(this))\n        binding.recyclerView.adapter = fileAdapter\n        binding.recyclerView.applyNavigationBarPadding()\n        onBackPressedDispatcher.addCallback(this) {\n            if (viewModel.lastDir != viewModel.rootDoc) {\n                gotoLastDir()\n                return@addCallback\n            }\n            finish()\n        }\n    }\n\n    private fun initSearchView() {\n        searchView.applyTint(primaryTextColor)\n        searchView.queryHint = getString(R.string.screen) + \" • \" + getString(R.string.file_manage)\n        searchView.isSubmitButtonEnabled = true\n        searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener {\n            override fun onQueryTextSubmit(query: String?): Boolean {\n                return false\n            }\n\n            override fun onQueryTextChange(newText: String?): Boolean {\n                updateFiles()\n                return false\n            }\n        })\n    }\n\n    private fun updateFiles() {\n        if (searchView.query.isNotEmpty()) {\n            currentFiles.filter {\n                it.name == dirParent || it.name.contains(searchView.query)\n            }.let {\n                fileAdapter.setItems(it)\n            }\n        } else {\n            fileAdapter.setItems(currentFiles)\n        }\n    }\n\n    private fun gotoLastDir() {\n        viewModel.subDocs.removeLastOrNull()\n        pathAdapter.setItems(viewModel.subDocs)\n        viewModel.upFiles(viewModel.lastDir)\n    }\n\n    override fun observeLiveBus() {\n        viewModel.filesLiveData.observe(this) {\n            searchView.setQuery(\"\", false)\n            currentFiles.clear()\n            currentFiles.addAll(it)\n            updateFiles()\n        }\n    }\n\n    @SuppressLint(\"SetTextI18n\")\n    inner class PathAdapter :\n        RecyclerAdapter<File, ItemPathPickerBinding>(this@FileManageActivity) {\n\n        private val arrowIcon = ConvertUtils.toDrawable(FilePickerIcon.getArrow())\n\n        init {\n            addHeaderView {\n                ItemPathPickerBinding.inflate(inflater, it, false).apply {\n                    textView.text = \"root\"\n                    imageView.setImageDrawable(arrowIcon)\n                    root.setOnClickListener {\n                        viewModel.subDocs.clear()\n                        setItems(viewModel.subDocs)\n                        viewModel.upFiles(viewModel.rootDoc)\n                    }\n                }\n            }\n        }\n\n        override fun getViewBinding(parent: ViewGroup): ItemPathPickerBinding {\n            return ItemPathPickerBinding.inflate(inflater, parent, false).apply {\n                imageView.setImageDrawable(arrowIcon)\n            }\n        }\n\n        override fun registerListener(holder: ItemViewHolder, binding: ItemPathPickerBinding) {\n            binding.root.setOnClickListener {\n                viewModel.subDocs = viewModel.subDocs.subList(0, holder.layoutPosition)\n                setItems(viewModel.subDocs)\n                viewModel.upFiles(viewModel.subDocs.lastOrNull())\n            }\n        }\n\n        override fun convert(\n            holder: ItemViewHolder,\n            binding: ItemPathPickerBinding,\n            item: File,\n            payloads: MutableList<Any>\n        ) {\n            binding.textView.text = item.name\n        }\n\n    }\n\n    inner class FileAdapter : RecyclerAdapter<File, ItemFileBinding>(this@FileManageActivity) {\n        private val upIcon = ConvertUtils.toDrawable(FilePickerIcon.getUpDir())!!\n        private val folderIcon = ConvertUtils.toDrawable(FilePickerIcon.getFolder())!!\n        private val fileIcon = ConvertUtils.toDrawable(FilePickerIcon.getFile())!!\n\n        override fun getViewBinding(parent: ViewGroup): ItemFileBinding {\n            return ItemFileBinding.inflate(inflater, parent, false)\n        }\n\n        override fun registerListener(holder: ItemViewHolder, binding: ItemFileBinding) {\n            binding.root.setOnClickListener {\n                val item = getItemByLayoutPosition(holder.layoutPosition)\n                item?.let {\n                    if (item == viewModel.lastDir) {\n                        gotoLastDir()\n                    } else if (item.isDirectory) {\n                        viewModel.subDocs.add(item)\n                        pathAdapter.setItems(viewModel.subDocs)\n                        viewModel.upFiles(item)\n                    } else {\n                        openFileUri(\n                            FileProvider.getUriForFile(\n                                this@FileManageActivity,\n                                AppConst.authority,\n                                item\n                            )\n                        )\n                    }\n                }\n            }\n            binding.root.setOnLongClickListener { view ->\n                val item = getItemByLayoutPosition(holder.layoutPosition)\n                if (item == viewModel.lastDir) {\n                    return@setOnLongClickListener true\n                }\n                item?.let {\n                    showFileMenu(view, item)\n                }\n                return@setOnLongClickListener true\n            }\n        }\n\n        override fun convert(\n            holder: ItemViewHolder,\n            binding: ItemFileBinding,\n            item: File,\n            payloads: MutableList<Any>\n        ) {\n            if (item == viewModel.lastDir) {\n                binding.imageView.setImageDrawable(upIcon)\n                binding.textView.text = dirParent\n            } else if (item.isDirectory) {\n                binding.imageView.setImageDrawable(folderIcon)\n                binding.textView.text = item.name\n            } else {\n                binding.imageView.setImageDrawable(fileIcon)\n                binding.textView.text = item.name\n            }\n        }\n\n        private fun showFileMenu(view: View, file: File) {\n            val popupMenu = PopupMenu(context, view)\n            popupMenu.inflate(R.menu.file_long_click)\n            popupMenu.setOnMenuItemClickListener {\n                when (it.itemId) {\n                    R.id.menu_del -> viewModel.delFile(file)\n                }\n                true\n            }\n            popupMenu.show()\n        }\n\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/file/FileManageViewModel.kt",
    "content": "package io.legado.app.ui.file\n\nimport android.app.Application\nimport androidx.lifecycle.MutableLiveData\nimport io.legado.app.base.BaseViewModel\nimport io.legado.app.utils.toastOnUi\nimport java.io.File\n\nclass FileManageViewModel(application: Application) : BaseViewModel(application) {\n\n    val rootDoc = context.getExternalFilesDir(null)?.parentFile\n    var subDocs = mutableListOf<File>()\n    val filesLiveData = MutableLiveData<List<File>>()\n\n    val lastDir: File? get() = subDocs.lastOrNull() ?: rootDoc\n\n    fun upFiles(parentFile: File?) {\n        execute {\n            parentFile ?: return@execute emptyList()\n            if (parentFile == rootDoc) {\n                parentFile.listFiles()?.sortedWith(\n                    compareBy({ it.isFile }, { it.name })\n                )\n            } else {\n                val list = arrayListOf(parentFile)\n                parentFile.listFiles()?.sortedWith(\n                    compareBy({ it.isFile }, { it.name })\n                )?.let {\n                    list.addAll(it)\n                }\n                list\n            }\n        }.onStart {\n            filesLiveData.postValue(emptyList())\n        }.onSuccess {\n            filesLiveData.postValue(it ?: emptyList())\n        }.onError {\n            context.toastOnUi(it.localizedMessage)\n        }\n    }\n\n    fun delFile(file: File) {\n        execute {\n            file.delete()\n        }.onSuccess {\n            upFiles(lastDir)\n        }.onError {\n            context.toastOnUi(it.localizedMessage)\n        }\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/file/FilePickerDialog.kt",
    "content": "package io.legado.app.ui.file\n\nimport android.annotation.SuppressLint\nimport android.content.DialogInterface\nimport android.content.Intent\nimport android.net.Uri\nimport android.os.Bundle\nimport android.view.MenuItem\nimport android.view.View\nimport android.view.ViewGroup\nimport androidx.appcompat.widget.Toolbar\nimport androidx.core.content.res.ResourcesCompat\nimport androidx.core.graphics.drawable.DrawableCompat\nimport androidx.fragment.app.FragmentManager\nimport androidx.fragment.app.viewModels\nimport androidx.recyclerview.widget.LinearLayoutManager\nimport androidx.recyclerview.widget.RecyclerView\nimport io.legado.app.R\nimport io.legado.app.base.BaseDialogFragment\nimport io.legado.app.base.adapter.ItemViewHolder\nimport io.legado.app.base.adapter.RecyclerAdapter\nimport io.legado.app.databinding.DialogEditTextBinding\nimport io.legado.app.databinding.DialogFileChooserBinding\nimport io.legado.app.databinding.ItemFilePickerBinding\nimport io.legado.app.databinding.ItemPathPickerBinding\nimport io.legado.app.help.config.AppConfig\nimport io.legado.app.lib.dialogs.alert\nimport io.legado.app.lib.theme.getPrimaryDisabledTextColor\nimport io.legado.app.lib.theme.getPrimaryTextColor\nimport io.legado.app.lib.theme.primaryColor\nimport io.legado.app.ui.file.HandleFileContract.Companion.FILE\nimport io.legado.app.ui.file.utils.FilePickerIcon\nimport io.legado.app.ui.widget.recycler.VerticalDivider\nimport io.legado.app.utils.ConvertUtils\nimport io.legado.app.utils.FileUtils\nimport io.legado.app.utils.applyTint\nimport io.legado.app.utils.getCompatColor\nimport io.legado.app.utils.setLayout\nimport io.legado.app.utils.toastOnUi\nimport io.legado.app.utils.viewbindingdelegate.viewBinding\nimport java.io.File\n\n\nclass FilePickerDialog : BaseDialogFragment(R.layout.dialog_file_chooser),\n    Toolbar.OnMenuItemClickListener {\n\n    companion object {\n        const val tag = \"FileChooserDialog\"\n\n        fun show(\n            manager: FragmentManager,\n            mode: Int = FILE,\n            title: String? = null,\n            initPath: String? = null,\n            isShowHideDir: Boolean = false,\n            allowExtensions: Array<String>? = null,\n        ) {\n            FilePickerDialog().apply {\n                val bundle = Bundle()\n                bundle.putInt(\"mode\", mode)\n                bundle.putString(\"title\", title)\n                bundle.putBoolean(\"isShowHideDir\", isShowHideDir)\n                bundle.putString(\"initPath\", initPath)\n                bundle.putStringArray(\"allowExtensions\", allowExtensions)\n                arguments = bundle\n            }.show(manager, tag)\n        }\n    }\n\n    private val binding by viewBinding(DialogFileChooserBinding::bind)\n    private val viewModel by viewModels<FilePickerViewModel>()\n    private val dirParent = \"..\"\n    private val pathAdapter by lazy { PathAdapter() }\n    private val fileAdapter by lazy { FileAdapter() }\n\n    override fun onStart() {\n        super.onStart()\n        setLayout(0.9f, 0.8f)\n    }\n\n    override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) {\n        binding.toolBar.setBackgroundColor(primaryColor)\n        view.setBackgroundResource(R.color.background_card)\n        initMenu()\n        initContentView()\n        viewModel.filesLiveData.observe(viewLifecycleOwner) {\n            fileAdapter.selectFile = null\n            fileAdapter.setItems(it)\n        }\n        viewModel.initData(arguments)\n        binding.toolBar.title = arguments?.getString(\"title\") ?: let {\n            if (viewModel.isSelectDir) {\n                getString(R.string.folder_chooser)\n            } else {\n                getString(R.string.file_chooser)\n            }\n        }\n    }\n\n    private fun initMenu() {\n        binding.toolBar.inflateMenu(R.menu.file_chooser)\n        binding.toolBar.menu.applyTint(requireContext())\n        binding.toolBar.setOnMenuItemClickListener(this)\n    }\n\n    private fun initContentView() {\n        binding.rvPath.layoutManager = LinearLayoutManager(activity, RecyclerView.HORIZONTAL, false)\n        binding.rvPath.adapter = pathAdapter\n\n        binding.rvFile.addItemDecoration(VerticalDivider(requireContext()))\n        binding.rvFile.layoutManager = LinearLayoutManager(activity)\n        binding.rvFile.adapter = fileAdapter\n\n        binding.tvOk.setOnClickListener {\n            if (viewModel.isSelectDir) {\n                viewModel.lastDir?.let {\n                    setResultData(it.path)\n                    dismissAllowingStateLoss()\n                }\n            } else {\n                val file = fileAdapter.selectFile\n                if (file == null) {\n                    toastOnUi(\"请选择文件\")\n                } else {\n                    setResultData(file.path)\n                    dismissAllowingStateLoss()\n                }\n            }\n        }\n    }\n\n    override fun onMenuItemClick(item: MenuItem?): Boolean {\n        when (item?.itemId) {\n            R.id.menu_create -> alert(R.string.create_folder) {\n                val alertBinding = DialogEditTextBinding.inflate(layoutInflater).apply {\n                    editView.hint = \"文件夹名\"\n                }\n                customView { alertBinding.root }\n                okButton {\n                    val text = alertBinding.editView.text?.toString()\n                    if (text.isNullOrBlank()) {\n                        toastOnUi(\"文件夹名不能为空\")\n                    } else {\n                        viewModel.createFolder(text.trim())\n                    }\n                }\n                cancelButton()\n            }\n        }\n        return true\n    }\n\n    private fun setResultData(path: String) {\n        val data = Intent().setData(Uri.fromFile(File(path)))\n        (parentFragment as? CallBack)?.onResult(data)\n        (activity as? CallBack)?.onResult(data)\n    }\n\n    override fun onDismiss(dialog: DialogInterface) {\n        super.onDismiss(dialog)\n        activity?.finish()\n    }\n\n    @SuppressLint(\"SetTextI18n\")\n    inner class PathAdapter :\n        RecyclerAdapter<File, ItemPathPickerBinding>(requireContext()) {\n\n        private val arrowIcon = ConvertUtils.toDrawable(FilePickerIcon.getArrow())\n\n        init {\n            addHeaderView {\n                ItemPathPickerBinding.inflate(inflater, it, false).apply {\n                    textView.text = \"root\"\n                    imageView.setImageDrawable(arrowIcon)\n                    root.setOnClickListener {\n                        viewModel.subDocs.clear()\n                        setItems(viewModel.subDocs)\n                        viewModel.upFiles(viewModel.rootDoc)\n                    }\n                }\n            }\n        }\n\n        override fun getViewBinding(parent: ViewGroup): ItemPathPickerBinding {\n            return ItemPathPickerBinding.inflate(inflater, parent, false).apply {\n                imageView.setImageDrawable(arrowIcon)\n            }\n        }\n\n        override fun registerListener(holder: ItemViewHolder, binding: ItemPathPickerBinding) {\n            binding.root.setOnClickListener {\n                viewModel.subDocs = viewModel.subDocs.subList(0, holder.layoutPosition)\n                setItems(viewModel.subDocs)\n                viewModel.upFiles(viewModel.subDocs.lastOrNull())\n            }\n        }\n\n        override fun convert(\n            holder: ItemViewHolder,\n            binding: ItemPathPickerBinding,\n            item: File,\n            payloads: MutableList<Any>\n        ) {\n            binding.textView.text = item.name\n        }\n\n    }\n\n    inner class FileAdapter : RecyclerAdapter<File, ItemFilePickerBinding>(requireContext()) {\n        private val primaryTextColor = context.getPrimaryTextColor(!AppConfig.isNightTheme)\n        private val disabledTextColor = context.getPrimaryDisabledTextColor(!AppConfig.isNightTheme)\n        private val upIcon = ConvertUtils.toDrawable(FilePickerIcon.getUpDir())!!\n        private val folderIcon = ConvertUtils.toDrawable(FilePickerIcon.getFolder())!!\n        private val fileIcon = ConvertUtils.toDrawable(FilePickerIcon.getFile())!!\n        private val selectDrawable =\n            ResourcesCompat.getDrawable(resources, R.drawable.shape_radius_1dp, null)!!.apply {\n                DrawableCompat.setTint(this, primaryTextColor)\n            }\n        var selectFile: File? = null\n\n        override fun getViewBinding(parent: ViewGroup): ItemFilePickerBinding {\n            return ItemFilePickerBinding.inflate(inflater, parent, false)\n        }\n\n        override fun registerListener(holder: ItemViewHolder, binding: ItemFilePickerBinding) {\n            binding.root.setOnClickListener {\n                val item = getItemByLayoutPosition(holder.layoutPosition)\n                item?.let {\n                    if (item == viewModel.lastDir) {\n                        viewModel.subDocs.removeLastOrNull()\n                        pathAdapter.setItems(viewModel.subDocs)\n                        viewModel.upFiles(viewModel.subDocs.lastOrNull() ?: viewModel.rootDoc)\n                    } else if (item.isDirectory) {\n                        viewModel.subDocs.add(item)\n                        pathAdapter.setItems(viewModel.subDocs)\n                        viewModel.upFiles(item)\n                    } else if (viewModel.isSelectFile) {\n                        viewModel.allowExtensions.let {\n                            if (it.isNullOrEmpty() || it.contains(FileUtils.getExtension(item.path))) {\n                                selectFile = item\n                                notifyItemRangeChanged(getHeaderCount(), itemCount, \"selectFile\")\n                            }\n                        }\n                    }\n                }\n            }\n        }\n\n        override fun convert(\n            holder: ItemViewHolder,\n            binding: ItemFilePickerBinding,\n            item: File,\n            payloads: MutableList<Any>\n        ) {\n            if (payloads.isEmpty()) {\n                if (item == viewModel.lastDir) {\n                    binding.imageView.setImageDrawable(upIcon)\n                    binding.textView.text = dirParent\n                } else if (item.isDirectory) {\n                    binding.imageView.setImageDrawable(folderIcon)\n                    binding.textView.text = item.name\n                } else {\n                    binding.imageView.setImageDrawable(fileIcon)\n                    binding.textView.text = item.name\n                }\n                if (item.isDirectory) {\n                    binding.textView.setTextColor(primaryTextColor)\n                } else {\n                    if (viewModel.isSelectDir) {\n                        binding.textView.setTextColor(disabledTextColor)\n                    } else {\n                        viewModel.allowExtensions?.let {\n                            if (it.isEmpty() || it.contains(FileUtils.getExtension(item.path))) {\n                                binding.textView.setTextColor(primaryTextColor)\n                            } else {\n                                binding.textView.setTextColor(disabledTextColor)\n                            }\n                        } ?: binding.textView.setTextColor(primaryTextColor)\n                    }\n                }\n            }\n            binding.root.isSelected = item == selectFile\n            if (item == selectFile) {\n                binding.root.background = selectDrawable\n            } else {\n                binding.root.setBackgroundColor(getCompatColor(R.color.transparent))\n            }\n        }\n\n    }\n\n    interface CallBack {\n        fun onResult(data: Intent)\n    }\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/file/FilePickerViewModel.kt",
    "content": "package io.legado.app.ui.file\n\nimport android.app.Application\nimport android.os.Bundle\nimport android.os.Environment\nimport androidx.lifecycle.MutableLiveData\nimport io.legado.app.base.BaseViewModel\nimport io.legado.app.exception.NoStackTraceException\nimport io.legado.app.utils.toastOnUi\nimport java.io.File\n\nclass FilePickerViewModel(application: Application) : BaseViewModel(application) {\n\n    var rootDoc: File? = Environment.getExternalStorageDirectory()\n    var subDocs = mutableListOf<File>()\n    val filesLiveData = MutableLiveData<List<File>>()\n    var mode: Int = HandleFileContract.FILE\n    var isShowHideDir: Boolean = false\n    var allowExtensions: Array<String>? = null\n    val isSelectDir: Boolean get() = mode == HandleFileContract.DIR\n    val isSelectFile: Boolean get() = mode == HandleFileContract.FILE\n    val lastDir: File? get() = subDocs.lastOrNull() ?: rootDoc\n\n    fun initData(arguments: Bundle?) {\n        arguments?.let {\n            mode = it.getInt(\"mode\", HandleFileContract.FILE)\n            isShowHideDir = it.getBoolean(\"isShowHideDir\")\n            it.getString(\"initPath\")?.let { path ->\n                rootDoc = File(path)\n            }\n            allowExtensions = it.getStringArray(\"allowExtensions\")\n        }\n        upFiles(rootDoc)\n    }\n\n    fun upFiles(parentFile: File?) {\n        execute {\n            parentFile ?: return@execute emptyList()\n            if (parentFile == rootDoc) {\n                parentFile.listFiles()?.sortedWith(\n                    compareBy({ it.isFile }, { it.name })\n                )\n            } else {\n                val list = arrayListOf(parentFile)\n                parentFile.listFiles()?.sortedWith(\n                    compareBy({ it.isFile }, { it.name })\n                )?.let {\n                    list.addAll(it)\n                }\n                list\n            }\n        }.onStart {\n            filesLiveData.postValue(emptyList())\n        }.onSuccess {\n            filesLiveData.postValue(it ?: emptyList())\n        }.onError {\n            context.toastOnUi(it.localizedMessage)\n        }\n    }\n\n    fun createFolder(name: String) {\n        execute {\n            val dir = lastDir ?: throw NoStackTraceException(\"父文件夹不存在\")\n            val folder = File(dir, name)\n            if (!folder.canonicalPath.contains(dir.canonicalPath)) {\n                throw NoStackTraceException(\"非法文件名\")\n            }\n            folder.mkdir()\n        }.onSuccess {\n            upFiles(lastDir)\n        }.onError {\n            context.toastOnUi(it.localizedMessage)\n        }\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/file/HandleFileActivity.kt",
    "content": "package io.legado.app.ui.file\n\nimport android.content.Intent\nimport android.net.Uri\nimport android.os.Bundle\nimport android.os.Environment\nimport android.webkit.MimeTypeMap\nimport androidx.activity.result.contract.ActivityResultContracts\nimport androidx.activity.viewModels\nimport androidx.core.net.toUri\nimport androidx.lifecycle.lifecycleScope\nimport io.legado.app.R\nimport io.legado.app.base.VMBaseActivity\nimport io.legado.app.constant.AppLog\nimport io.legado.app.databinding.ActivityTranslucenceBinding\nimport io.legado.app.databinding.DialogEditTextBinding\nimport io.legado.app.help.IntentData\nimport io.legado.app.lib.dialogs.SelectItem\nimport io.legado.app.lib.dialogs.alert\nimport io.legado.app.lib.permission.Permissions\nimport io.legado.app.lib.permission.PermissionsCompat\nimport io.legado.app.utils.SelectImageContract\nimport io.legado.app.utils.checkWrite\nimport io.legado.app.utils.externalFiles\nimport io.legado.app.utils.getJsonArray\nimport io.legado.app.utils.isContentScheme\nimport io.legado.app.utils.launch\nimport io.legado.app.utils.toastOnUi\nimport io.legado.app.utils.viewbindingdelegate.viewBinding\nimport splitties.init.appCtx\nimport java.io.File\n\nclass HandleFileActivity :\n    VMBaseActivity<ActivityTranslucenceBinding, HandleFileViewModel>(),\n    FilePickerDialog.CallBack {\n\n    override val binding by viewBinding(ActivityTranslucenceBinding::inflate)\n    override val viewModel by viewModels<HandleFileViewModel>()\n    private var mode = 0\n\n    private val selectDocTree =\n        registerForActivityResult(ActivityResultContracts.OpenDocumentTree()) { uri ->\n            uri?.let {\n                if (uri.isContentScheme()) {\n                    val modeFlags =\n                        Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION\n                    contentResolver.takePersistableUriPermission(uri, modeFlags)\n                }\n                onResult(Intent().setData(uri))\n            } ?: finish()\n        }\n\n    private val selectDoc = registerForActivityResult(ActivityResultContracts.OpenDocument()) {\n        it?.let {\n            if (it.isContentScheme()) {\n                val modeFlags =\n                    Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION\n                contentResolver.takePersistableUriPermission(it, modeFlags)\n            }\n            onResult(Intent().setData(it))\n        } ?: finish()\n    }\n\n    private val selectImage = registerForActivityResult(SelectImageContract()) {\n        it.uri?.let { uri ->\n            onResult(Intent().setData(uri))\n        } ?: finish()\n    }\n\n    override fun onActivityCreated(savedInstanceState: Bundle?) {\n        mode = intent.getIntExtra(\"mode\", 0)\n        viewModel.errorLiveData.observe(this) {\n            toastOnUi(it)\n            finish()\n        }\n        val allowExtensions = intent.getStringArrayExtra(\"allowExtensions\")\n        val selectList: ArrayList<SelectItem<Int>> = when (mode) {\n            HandleFileContract.DIR_SYS -> getDirActions(true)\n            HandleFileContract.DIR -> getDirActions()\n            HandleFileContract.FILE -> getFileActions()\n            HandleFileContract.EXPORT -> arrayListOf(\n                SelectItem(getString(R.string.upload_url), 111)\n            ).apply {\n                addAll(getDirActions())\n            }\n\n            HandleFileContract.IMAGE -> getImageActions()\n            else -> arrayListOf()\n        }\n        intent.getJsonArray<SelectItem<Int>>(\"otherActions\")?.let {\n            selectList.addAll(it)\n        }\n        val title = intent.getStringExtra(\"title\") ?: let {\n            when (mode) {\n                HandleFileContract.EXPORT -> return@let getString(R.string.export)\n                HandleFileContract.DIR -> return@let getString(R.string.select_folder)\n                HandleFileContract.IMAGE -> return@let getString(R.string.select_image)\n                else -> return@let getString(R.string.select_file)\n            }\n        }\n        alert(title) {\n            items(selectList) { _, item, _ ->\n                when (item.value) {\n                    HandleFileContract.DIR -> kotlin.runCatching {\n                        selectDocTree.launch()\n                    }.onFailure {\n                        AppLog.put(getString(R.string.open_sys_dir_picker_error), it, true)\n                        checkPermissions {\n                            FilePickerDialog.show(\n                                supportFragmentManager,\n                                mode = HandleFileContract.DIR\n                            )\n                        }\n                    }\n\n                    HandleFileContract.FILE -> kotlin.runCatching {\n                        selectDoc.launch(typesOfExtensions(allowExtensions))\n                    }.onFailure {\n                        AppLog.put(getString(R.string.open_sys_dir_picker_error), it, true)\n                        checkPermissions {\n                            FilePickerDialog.show(\n                                supportFragmentManager,\n                                mode = HandleFileContract.FILE,\n                                allowExtensions = allowExtensions\n                            )\n                        }\n                    }\n\n                    HandleFileContract.IMAGE -> {\n                        selectImage.launch()\n                    }\n\n                    10 -> checkPermissions {\n                        @Suppress(\"DEPRECATION\")\n                        lifecycleScope.launchWhenResumed {\n                            FilePickerDialog.show(\n                                supportFragmentManager,\n                                mode = HandleFileContract.DIR\n                            )\n                        }\n                    }\n\n                    11 -> checkPermissions {\n                        @Suppress(\"DEPRECATION\")\n                        lifecycleScope.launchWhenResumed {\n                            FilePickerDialog.show(\n                                supportFragmentManager,\n                                mode = HandleFileContract.FILE,\n                                allowExtensions = allowExtensions\n                            )\n                        }\n                    }\n\n                    111 -> getFileData()?.let {\n                        viewModel.upload(it.first, it.second, it.third) { url ->\n                            val uri = url.toUri()\n                            setResult(RESULT_OK, Intent().setData(uri))\n                            finish()\n                        }\n                    }\n\n                    112 -> checkPermissions { // 手动输入目录路径\n                        showInputDirectoryDialog()\n                    }\n\n                    else -> {\n                        val path = item.title\n                        val uri = if (path.isContentScheme()) {\n                            path.toUri()\n                        } else {\n                            Uri.fromFile(File(path))\n                        }\n                        onResult(Intent().setData(uri))\n                    }\n                }\n            }\n            onCancelled {\n                finish()\n            }\n        }\n    }\n\n    private fun showInputDirectoryDialog() {\n        val alertBinding = DialogEditTextBinding.inflate(layoutInflater).apply {\n            editView.hint = getString(R.string.enter_directory_path)\n        }\n\n        alert(getString(R.string.manual_input)) {\n            customView { alertBinding.root }\n            okButton {\n                val inputPath = alertBinding.editView.text.toString()\n                if (inputPath.isBlank()) {\n                    toastOnUi(getString(R.string.empty_directory_input))\n                    return@okButton\n                }\n                val file = File(inputPath)\n                if (file.exists() &&\n                    file.isDirectory &&\n                    isExternalStorage(file) &&\n                    file.checkWrite()\n                ) {\n                    onResult(Intent().setData(Uri.fromFile(file)))\n                } else {\n                    toastOnUi(getString(R.string.invalid_directory))\n                }\n            }\n            onDismiss {\n                finish()\n            }\n            cancelButton()\n        }\n    }\n\n    private fun isExternalStorage(path: File): Boolean {\n        if (path.canonicalPath.startsWith(appCtx.externalFiles.parent!!)) {\n            return false\n        }\n        try {\n            if (Environment.isExternalStorageEmulated(path)) {\n                return true\n            }\n        } catch (_: IllegalArgumentException) {\n        }\n        try {\n            if (Environment.isExternalStorageRemovable(path)) {\n                return true\n            }\n        } catch (_: IllegalArgumentException) {\n        }\n        return false\n    }\n\n    private fun getFileData(): Triple<String, Any, String>? {\n        val fileName = intent.getStringExtra(\"fileName\")\n        val file = intent.getStringExtra(\"fileKey\")?.let {\n            IntentData.get<Any>(it)\n        }\n        val contentType = intent.getStringExtra(\"contentType\")\n        if (fileName != null && file != null && contentType != null) {\n            return Triple(fileName, file, contentType)\n        }\n        return null\n    }\n\n    private fun getDirActions(onlySys: Boolean = false): ArrayList<SelectItem<Int>> {\n        return if (onlySys) {\n            arrayListOf(\n                SelectItem(getString(R.string.sys_folder_picker), HandleFileContract.DIR),\n                SelectItem(getString(R.string.manual_input), 112) // 添加手动输入选项\n            )\n        } else {\n            arrayListOf(\n                SelectItem(getString(R.string.sys_folder_picker), HandleFileContract.DIR),\n                SelectItem(getString(R.string.app_folder_picker), 10),\n                SelectItem(getString(R.string.manual_input), 112) // 添加手动输入选项\n            )\n        }\n    }\n\n    private fun getFileActions(): ArrayList<SelectItem<Int>> {\n        return arrayListOf(\n            SelectItem(getString(R.string.sys_file_picker), HandleFileContract.FILE),\n            SelectItem(getString(R.string.app_file_picker), 11)\n        )\n    }\n\n    private fun getImageActions(): ArrayList<SelectItem<Int>> {\n        return arrayListOf(\n            SelectItem(getString(R.string.sys_image_picker), HandleFileContract.IMAGE)\n        ).apply {\n            addAll(getFileActions())\n        }\n    }\n\n    private fun checkPermissions(success: (() -> Unit)? = null) {\n        PermissionsCompat.Builder()\n            .addPermissions(*Permissions.Group.STORAGE)\n            .rationale(R.string.tip_perm_request_storage)\n            .onGranted {\n                success?.invoke()\n            }\n            .onDenied {\n                finish()\n            }\n            .onError {\n                finish()\n            }\n            .request()\n    }\n\n    private fun typesOfExtensions(allowExtensions: Array<String>?): Array<String> {\n        val types = hashSetOf<String>()\n        if (allowExtensions.isNullOrEmpty()) {\n            types.add(\"*/*\")\n        } else {\n            allowExtensions.forEach {\n                when (it) {\n                    \"*\" -> types.add(\"*/*\")\n                    \"txt\", \"xml\" -> types.add(\"text/*\")\n                    else -> {\n                        val mime = MimeTypeMap.getSingleton()\n                            .getMimeTypeFromExtension(it)\n                            ?: \"application/octet-stream\"\n                        types.add(mime)\n                    }\n                }\n            }\n        }\n        return types.toTypedArray()\n    }\n\n    override fun onResult(data: Intent) {\n        val uri = data.data\n        uri ?: let {\n            finish()\n            return\n        }\n        if (mode == HandleFileContract.EXPORT) {\n            getFileData()?.let { fileData ->\n                viewModel.saveToLocal(uri, fileData.first, fileData.second) { savedUri ->\n                    setResult(RESULT_OK, Intent().setData(savedUri))\n                    finish()\n                }\n            }\n        } else {\n            data.putExtra(\"value\", intent.getStringExtra(\"value\"))\n            setResult(RESULT_OK, data)\n            finish()\n        }\n    }\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/file/HandleFileContract.kt",
    "content": "package io.legado.app.ui.file\n\nimport android.app.Activity.RESULT_OK\nimport android.content.Context\nimport android.content.Intent\nimport android.net.Uri\nimport androidx.activity.result.contract.ActivityResultContract\nimport io.legado.app.help.IntentData\nimport io.legado.app.lib.dialogs.SelectItem\nimport io.legado.app.utils.RealPathUtil\nimport io.legado.app.utils.externalFiles\nimport io.legado.app.utils.putJson\nimport splitties.init.appCtx\n\n@Suppress(\"unused\")\nclass HandleFileContract :\n    ActivityResultContract<(HandleFileContract.HandleFileParam.() -> Unit)?, HandleFileContract.Result>() {\n\n    private var requestCode: Int = 0\n\n    override fun createIntent(context: Context, input: (HandleFileParam.() -> Unit)?): Intent {\n        val intent = Intent(context, HandleFileActivity::class.java)\n        val handleFileParam = HandleFileParam()\n        input?.let {\n            handleFileParam.apply(input)\n        }\n        if (handleFileParam.mode == IMAGE) {\n            handleFileParam.allowExtensions = arrayOf(\"jpg\", \"png\", \"bmp\", \"webp\")\n        }\n        handleFileParam.let {\n            requestCode = it.requestCode\n            intent.putExtra(\"mode\", it.mode)\n            intent.putExtra(\"title\", it.title)\n            intent.putExtra(\"allowExtensions\", it.allowExtensions)\n            intent.putJson(\"otherActions\", it.otherActions)\n            it.fileData?.let { fileData ->\n                intent.putExtra(\"fileName\", fileData.name)\n                intent.putExtra(\"fileKey\", IntentData.put(fileData.data))\n                intent.putExtra(\"contentType\", fileData.type)\n            }\n            intent.putExtra(\"value\", it.value)\n        }\n        return intent\n    }\n\n    override fun parseResult(resultCode: Int, intent: Intent?): Result {\n        val uri = if (resultCode != RESULT_OK || intent?.data == null ||\n            RealPathUtil.getTreePath(intent.data!!)\n                ?.startsWith(appCtx.externalFiles.parent!!) == true\n        ) {\n            null\n        } else {\n            intent.data\n        }\n        return Result(uri, requestCode, intent?.getStringExtra(\"value\"))\n    }\n\n    companion object {\n        const val DIR = 0\n        const val FILE = 1\n        const val DIR_SYS = 2\n        const val EXPORT = 3\n        const val IMAGE = 4\n    }\n\n    @Suppress(\"ArrayInDataClass\")\n    data class HandleFileParam(\n        var mode: Int = DIR,\n        var title: String? = null,\n        var allowExtensions: Array<String> = arrayOf(),\n        var otherActions: ArrayList<SelectItem<Int>>? = null,\n        var fileData: FileData? = null,\n        var requestCode: Int = 0,\n        var value: String? = null\n    )\n\n    data class Result(\n        val uri: Uri?,\n        val requestCode: Int,\n        val value: String?\n    )\n\n    data class FileData(\n        val name: String,\n        val data: Any,\n        val type: String\n    )\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/file/HandleFileViewModel.kt",
    "content": "package io.legado.app.ui.file\n\nimport android.app.Application\nimport android.net.Uri\nimport androidx.documentfile.provider.DocumentFile\nimport androidx.lifecycle.MutableLiveData\nimport io.legado.app.base.BaseViewModel\nimport io.legado.app.constant.AppLog\nimport io.legado.app.help.DirectLinkUpload\nimport io.legado.app.utils.*\n\nimport java.io.File\n\nclass HandleFileViewModel(application: Application) : BaseViewModel(application) {\n\n    val errorLiveData = MutableLiveData<String>()\n\n    fun upload(\n        fileName: String,\n        file: Any,\n        contentType: String,\n        success: (url: String) -> Unit\n    ) {\n        execute {\n            DirectLinkUpload.upLoad(fileName, file, contentType)\n        }.onSuccess {\n            success.invoke(it)\n        }.onError {\n            AppLog.put(\"上传文件失败\\n${it.localizedMessage}\", it)\n            it.printOnDebug()\n            errorLiveData.postValue(it.localizedMessage)\n        }\n    }\n\n    fun saveToLocal(uri: Uri, fileName: String, data: Any, success: (uri: Uri) -> Unit) {\n        execute {\n            val bytes = when (data) {\n                is File -> data.readBytes()\n                is ByteArray -> data\n                is String -> data.toByteArray()\n                else -> GSON.toJson(data).toByteArray()\n            }\n            return@execute if (uri.isContentScheme()) {\n                val doc = DocumentFile.fromTreeUri(context, uri)!!\n                doc.findFile(fileName)?.delete()\n                val newDoc = doc.createFile(\"\", fileName)\n                newDoc!!.writeBytes(context, bytes)\n                newDoc.uri\n            } else {\n                val file = File(uri.path ?: uri.toString())\n                val newFile = FileUtils.createFileIfNotExist(file, fileName)\n                newFile.writeBytes(bytes)\n                Uri.fromFile(newFile)\n            }\n        }.onError {\n            it.printOnDebug()\n            errorLiveData.postValue(it.localizedMessage)\n        }.onSuccess {\n            success.invoke(it)\n        }\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/file/utils/FilePickerIcon.java",
    "content": "package io.legado.app.ui.file.utils;\n\n/**\n * Generated by https://github.com/gzu-liyujiang/Image2ByteVar\n *\n * @author 李玉江[QQ:1023694760]\n * @since 2017/01/04 06:03\n */\npublic class FilePickerIcon {\n\n    public static byte[] getFile() {\n        return FILE;\n    }\n\n    public static byte[] getFolder() {\n        return FOLDER;\n    }\n\n    public static byte[] getHome() {\n        return HOME;\n    }\n\n    public static byte[] getUpDir() {\n        return UPDIR;\n    }\n\n    public static byte[] getArrow() {\n        return ARROW;\n    }\n\n    // fixed: 17-1-7 \"static final\" arrays should be \"private\"\n    private static final byte[] FILE = {\n            -119, 80, 78, 71, 13, 10, 26, 10, 0, 0, 0, 13, 73, 72, 68, 82, 0, 0, 0, 42,\n            0, 0, 0, 40, 8, 6, 0, 0, 0, -120, 11, 104, 80, 0, 0, 0, 6, 98, 75, 71,\n            68, 0, -1, 0, -1, 0, -1, -96, -67, -89, -109, 0, 0, 0, 9, 112, 72, 89, 115, 0,\n            0, 14, -60, 0, 0, 14, -60, 1, -107, 43, 14, 27, 0, 0, 1, -73, 73, 68, 65, 84,\n            88, -123, -19, -106, 63, 75, 3, 49, 24, -121, 127, -74, 69, -37, 110, -30, -94, -109, -120, -126,\n            -125, -101, 56, 72, 63, -125, -97, -64, -63, 77, 28, 69, 20, 81, 68, 113, 83, 113, -12, 91,\n            -120, -96, -117, -120, -85, 31, 65, 29, 28, -100, -124, -94, -101, 46, -83, -105, 92, 114, 113, -24,\n            31, -38, -110, 59, -34, -9, 114, 87, 69, -14, 64, 41, -92, 73, -34, -89, -55, -17, 114, 1,\n            60, 30, -113, -25, 79, 50, 66, -19, 120, -7, 16, 26, 0, -112, 82, -61, 24, 3, 41, 35,\n            0, -128, 20, 10, 74, 69, -48, 58, -126, 82, -90, -37, -90, -75, -127, 82, 81, 119, 124, 16,\n            40, -100, 109, 79, -109, -21, 13, 82, -94, 118, -108, 82, 99, -91, 54, -58, 25, 2, 0, 120,\n            -85, 7, -72, -71, 127, -57, -18, -58, 12, -102, -51, 87, 115, 113, 56, -105, 74, -74, 64, -19,\n            104, -116, 73, 51, 63, 0, -96, 88, 104, -107, 57, -34, -102, -59, -6, -63, 75, -86, -119, -56,\n            -94, -99, -83, -26, -96, 123, -122, 20, -37, -107, 78, -9, -25, -79, -74, -13, -52, -106, 37, -117,\n            114, -23, 72, 42, 109, -96, -93, 8, 66, 0, -97, 95, -83, -49, -55, -34, 2, 86, 55, 31,\n            89, -78, -12, -116, 10, -59, 18, -20, 48, 90, -86, -96, -47, -48, -72, -66, -5, 64, -40, -98,\n            -94, 90, -27, -81, 15, 89, -76, -9, 9, -114, 99, 80, 18, 0, -90, 38, -127, -38, -46, 4,\n            -76, 78, -97, 113, -128, 33, 42, -124, 78, -4, -35, 38, -87, -62, -42, -9, -14, -30, 120, 127,\n            -69, 6, -82, 110, -21, -44, -46, 0, -72, 103, 77, 12, 73, -110, 125, 109, -55, -1, 53, 17,\n            86, 70, 109, 66, 113, -72, 72, -39, 32, -89, -38, 53, 99, -82, 100, -14, 48, -39, -74, 57,\n            107, -100, -49, -47, -84, -77, 24, 7, 121, 69, -125, -64, 126, -114, 82, -91, -66, 3, 106, 37,\n            59, -12, 21, 85, -46, -87, 80, -91, -20, 52, -100, 46, -38, -108, -87, 111, 104, -103, -64, 58,\n            71, -121, -15, -48, -60, -63, -54, -24, -80, -14, 104, 35, -73, -37, 83, -42, -112, 69, 5, -15,\n            -10, -108, 23, -12, 3, 63, -92, -65, 63, 43, 101, -5, -10, 7, 14, -111, 112, -66, -108, -28,\n            -111, 71, 27, -1, 47, -93, -65, 77, 38, -9, 81, 27, 46, 121, -76, -63, 18, 29, 86, 30,\n            109, -80, 68, -113, -50, -97, -14, -14, -16, 120, 60, -98, 1, 126, 0, -110, 81, -78, -5, 36,\n            19, -64, 3, 0, 0, 0, 0, 73, 69, 78, 68, -82, 66, 96, -126};\n    private static final byte[] FOLDER = {\n            -119, 80, 78, 71, 13, 10, 26, 10, 0, 0, 0, 13, 73, 72, 68, 82, 0, 0, 0, 42,\n            0, 0, 0, 40, 8, 6, 0, 0, 0, -120, 11, 104, 80, 0, 0, 0, 6, 98, 75, 71,\n            68, 0, -1, 0, -1, 0, -1, -96, -67, -89, -109, 0, 0, 0, 9, 112, 72, 89, 115, 0,\n            0, 14, -60, 0, 0, 14, -60, 1, -107, 43, 14, 27, 0, 0, 2, -102, 73, 68, 65, 84,\n            88, -123, -19, -105, 61, -113, 19, 49, 16, -122, 95, 111, -110, 75, -124, -124, -60, -49, 65, 20,\n            -48, 34, 81, 80, 32, 81, 83, 80, -94, 52, 72, 8, 78, 66, -94, 64, 20, -108, 124, 20,\n            39, -47, 64, -51, -11, -7, 87, -96, 11, -38, -75, 61, 99, 15, -59, 110, 54, 118, -42, -5,\n            113, -71, 77, 117, -5, 54, -55, -50, -38, -42, -77, -81, 61, 99, 27, -104, 52, 105, -46, -92,\n            73, -73, 66, 106, 104, -61, -51, 57, -92, -21, -3, -29, 79, -61, -57, 58, 70, -13, 33, -115,\n            54, -25, -112, 71, -17, 127, 0, 0, -106, -38, -105, 65, 93, 64, -56, 0, 108, 33, 76, -72,\n            -28, -113, -14, -20, -77, 59, 25, 108, 54, 4, -14, -63, -6, 59, -112, 123, -84, -4, -84, -114,\n            -117, 39, 96, -74, -17, -2, -12, -59, 91, 92, -66, -103, 117, -70, 126, 19, 117, 58, -80, 57,\n            -121, 60, 92, 127, 5, 0, -84, -106, -53, -3, 11, 93, -108, -96, 0, 96, 52, -124, 9, 96,\n            6, 116, -127, -33, -65, -66, -44, -51, 56, -117, -121, -73, 62, 3, -7, 56, -74, 123, 126, -11,\n            -83, -24, 100, -23, -99, -6, -43, -67, -5, -119, -96, -119, -66, 80, 1, -128, 49, 0, -128, -25,\n            31, -98, -108, 65, 103, -93, 46, -62, -36, 24, -58, -27, -37, -6, -65, -39, -66, -108, -41, 63,\n            -13, 86, -40, 65, 107, 84, -7, 43, 64, -103, 70, 92, 108, 17, -45, 82, -94, -115, -47, 77,\n            -40, -22, 35, -108, 53, 32, 34, 56, 34, 112, -49, -14, 30, 4, 90, 66, -2, -119, 99, -34,\n            2, -69, -23, -33, -119, -10, -32, 18, -66, -37, -63, 114, 16, 99, -122, 2, -64, 87, 127, 97,\n            -56, -61, -53, 24, -96, -121, -14, -107, 35, 103, 11, -120, -91, 116, 27, -25, -45, -15, 96, 9,\n            80, -95, 97, -56, -61, 114, 127, 14, -10, 102, 125, 27, 36, -128, 86, -56, -70, 34, -52, 50,\n            -128, 109, -78, 77, 40, 118, -82, -73, 77, -65, -93, 98, 26, -128, -111, -62, 41, 62, -101, 67,\n            116, -111, 110, -41, 34, -53, 2, -26, 22, -9, 3, 29, 53, -11, -111, -109, -39, -94, -4, -83,\n            0, 85, -74, -120, -41, -25, 72, -22, 4, -27, -86, -58, -119, 45, -102, -119, 3, 52, -36, -124,\n            61, 40, 65, 81, -58, 55, -5, -117, 99, -80, 115, -89, 115, 52, 9, 93, 65, -90, -36, 76,\n            65, 94, 87, -67, -96, 74, -52, -2, 52, -46, 54, -91, -95, -109, -119, 108, 87, -13, -59, 126,\n            -9, 74, 40, -109, 27, 58, 106, 124, 6, -79, 40, -117, -7, 33, 100, 0, 23, -71, -72, -37,\n            -1, -85, 105, 31, -61, 77, 96, -24, -44, -109, 1, 40, -19, 70, 50, 113, -126, -75, -39, -22,\n            -26, 53, -85, -61, 113, 89, 127, 8, 23, 78, 119, 80, 55, -5, -36, -12, 117, 34, -11, 23,\n            -4, 78, 80, -14, 10, 112, 22, -30, 92, -5, -6, 28, 2, 25, -70, -103, 112, -46, -75, -19,\n            98, 67, 65, 57, 60, -110, -11, 13, 118, 4, -92, -9, 2, 79, 4, -94, 17, 118, 38, 97,\n            6, -104, 64, -108, -34, -103, -124, -35, -63, 115, -20, -68, -46, 58, 124, 2, 0, 56, 91, -18,\n            118, -123, -74, -48, 122, 88, -78, 117, -126, 90, 95, 102, -80, 49, 5, -88, 26, 80, -101, -46,\n            33, 83, -24, -42, 126, 53, 116, 42, 97, -104, -22, 2, -97, 111, 115, 108, -13, 17, 64, 1,\n            -64, 85, 103, 71, 50, 84, 1, -106, 110, 88, 106, 95, 10, 93, -5, -67, -81, 62, -44, 22,\n            26, 84, 1, -33, -67, -77, -24, 5, -19, -67, -116, 93, -84, 87, 2, 32, -70, 66, -104, -32,\n            -112, -53, 94, -63, -113, 112, 1, 125, 119, -15, -17, -92, -73, -40, 73, -109, 38, 77, -70, -19,\n            -6, 15, -2, -54, -98, -96, -19, -118, -95, -10, 0, 0, 0, 0, 73, 69, 78, 68, -82, 66,\n            96, -126};\n    private static final byte[] HOME = {\n            -119, 80, 78, 71, 13, 10, 26, 10, 0, 0, 0, 13, 73, 72, 68, 82, 0, 0, 0, 42,\n            0, 0, 0, 40, 8, 4, 0, 0, 0, 34, 2, -96, -37, 0, 0, 0, 1, 115, 82, 71,\n            66, 0, -82, -50, 28, -23, 0, 0, 0, 2, 98, 75, 71, 68, 0, -1, -121, -113, -52, -65,\n            0, 0, 0, 9, 112, 72, 89, 115, 0, 0, 46, 35, 0, 0, 46, 35, 1, 120, -91, 63,\n            118, 0, 0, 0, 7, 116, 73, 77, 69, 7, -37, 8, 4, 10, 36, 16, -22, -9, -18, -50,\n            0, 0, 2, -84, 73, 68, 65, 84, 72, -57, -19, -106, 91, 72, 20, 97, 20, -57, 127, -77,\n            -18, -106, -41, 54, -14, -106, -23, -102, 4, 21, 97, 5, 5, 61, 70, 74, -76, -122, -94, -11,\n            18, -108, -92, 121, 41, 16, -124, 96, -95, 48, -118, -108, -94, 18, -70, 96, 33, 68, -81, -127,\n            15, 25, 68, 15, 21, 89, 80, 42, 25, 42, -91, 97, -106, -103, -122, 107, 41, -19, -86, -37,\n            -22, 122, -39, 77, -73, -103, -81, 7, -105, 84, -36, -85, -26, -125, -44, -127, -31, 59, -52, -103,\n            -7, 113, -50, -103, -17, -4, -65, -127, -1, -74, 44, 76, -102, 113, -29, 22, -119, 50, -3, -15,\n            84, 75, -111, -87, -38, -81, -89, 84, 24, -120, 1, -96, -106, -102, -65, 83, -66, -118, 59, -119,\n            39, 54, 97, 69, -31, -109, 115, -14, 8, 15, 124, -107, -17, 27, 42, 113, 123, 125, 81, 46,\n            102, 28, 8, -84, -68, 116, 78, -26, 80, 77, 5, -79, 104, -48, -48, 70, -39, 124, -88, -38,\n            103, 37, -107, -70, -94, 28, -6, 105, 7, 34, 89, 67, -86, -90, -74, 106, 82, -51, 65, 125,\n            -110, 13, 27, -99, 43, 2, -17, -87, -60, -51, -124, -30, 99, -104, -88, -63, -12, 20, -93, -90,\n            96, 111, -80, -106, 20, 117, -3, -35, -97, -65, 54, -13, 29, 21, 8, -9, -3, -14, -122, -68,\n            17, 127, 50, 15, 51, -49, 49, 85, -109, 73, -79, 51, -81, 94, -103, 96, 21, 123, -126, 66,\n            86, 10, 64, 4, 12, -107, -72, -70, -50, -112, -57, 0, 47, -24, 123, 66, 46, 50, 80, -19,\n            40, 121, -59, 20, -31, -20, 102, 0, 121, 1, -48, -14, -72, 83, -7, 12, 81, -121, -79, -106,\n            67, 76, -71, -18, 94, -73, 85, 54, 34, 19, -122, 115, -102, 23, 16, -12, 114, 92, 73, 1,\n            22, 26, -24, 110, -26, 0, -114, 89, 17, -61, -32, -61, 22, 4, 2, -127, -30, -31, 125, -9,\n            -48, -117, -79, 103, -13, -79, -48, 68, -57, 123, -46, 25, -101, 19, -109, 57, -38, -41, -40, -127,\n            64, 16, -127, 70, -113, -34, 63, 104, 105, -52, -7, 2, -84, -76, -48, -42, 69, 26, -42, 121,\n            113, 59, 89, 93, -35, -67, 8, -126, -39, -87, 81, -35, 103, -69, 111, -24, -71, -24, 11, -123,\n            12, -45, -58, -37, -81, -20, -61, -20, -74, 18, 11, -23, -19, -125, 102, 20, -76, 36, 107, 121,\n            76, -68, 119, -24, -103, -88, 75, -123, -116, -16, -111, 38, 51, 122, -66, 121, -4, -116, 95, 68,\n            -42, 59, -5, 8, -126, 24, 54, -24, 120, 68, -124, 103, -24, -23, -88, -14, -29, -40, -24, -28,\n            -75, 85, -92, -47, -27, 117, 48, -102, -27, -20, 86, 121, 28, 5, 29, 107, 119, 112, 111, -10,\n            24, 5, -51, -72, 17, -122, -32, 107, -39, -110, -99, 30, -22, -58, -108, -3, -76, -6, 20, -93,\n            -49, -78, -59, -102, 17, -50, 40, -126, -47, -115, 66, 59, 94, -29, 78, 80, -86, -40, 74, -108,\n            20, -119, 34, 50, -88, -13, 83, 58, -81, -112, 73, 47, 70, -116, 52, -104, -34, 120, 86, 41,\n            -119, 4, 10, 1, 24, -26, -106, 71, 88, 10, 41, 0, 60, -93, -47, 31, -107, 18, -124, -123,\n            -106, -19, -30, 7, 31, 122, -68, 64, 83, 19, 75, -75, -12, 51, 108, -101, -127, -6, -40, -4,\n            97, -92, -110, -20, 67, 18, -109, -40, -58, 106, -113, -86, -66, 96, 83, -36, 15, -2, 34, -96,\n            -110, 88, 8, 84, -8, 60, -37, 60, 67, -43, -18, -127, 2, 1, 26, -74, 120, -124, 70, 11,\n            20, -108, 64, -113, 104, -119, 88, -99, -24, 112, -105, -71, 112, 117, -44, 22, 88, -90, 32, 8,\n            -27, 48, 33, 76, 119, 78, -52, 89, 101, 20, -100, -12, 49, 21, 24, 84, 97, 12, 59, 19,\n            46, -44, -36, 75, 65, 70, 70, 118, -83, -2, 67, 101, -21, 80, -123, -65, -69, -64, -79, -68,\n            127, -48, -106, 4, -6, -113, -37, 111, 38, -57, 11, 112, 71, 102, 113, -50, 0, 0, 0, 0,\n            73, 69, 78, 68, -82, 66, 96, -126};\n    private static final byte[] UPDIR = {\n            -119, 80, 78, 71, 13, 10, 26, 10, 0, 0, 0, 13, 73, 72, 68, 82, 0, 0, 0, 42,\n            0, 0, 0, 40, 8, 6, 0, 0, 0, -120, 11, 104, 80, 0, 0, 0, 6, 98, 75, 71,\n            68, 0, -1, 0, -1, 0, -1, -96, -67, -89, -109, 0, 0, 0, 9, 112, 72, 89, 115, 0,\n            0, 14, -60, 0, 0, 14, -60, 1, -107, 43, 14, 27, 0, 0, 2, -111, 73, 68, 65, 84,\n            88, -123, -19, -106, -51, 74, -21, 64, 20, -57, -1, -87, -47, 7, -15, 41, 92, 118, -47, 82,\n            10, -59, 110, 20, -95, 32, 77, -101, 44, -92, -37, -66, 80, -23, 3, 8, -126, -76, -48, -115,\n            -120, 43, 117, -19, -109, -44, -113, -50, -57, -103, 115, 87, 103, 76, 106, -117, 77, 19, -17, -107,\n            75, -2, 48, -52, -112, 100, 38, -65, 57, -25, -52, 57, 3, 84, -86, 84, -87, -46, -1, -83,\n            36, 73, 56, 73, 18, -2, -87, -11, -61, 50, 22, -119, -29, -104, 47, 47, 47, 97, -116, -127,\n            -75, -106, 39, -109, 73, 80, -58, -70, 105, 21, 94, 48, -114, 99, 78, -110, 4, 68, 4, -91,\n            20, -116, 49, 120, 121, 121, -63, 120, 60, 46, 21, -74, 86, 100, -14, 112, 56, -28, -85, -85,\n            43, 0, -128, -75, 22, -50, 57, 48, 51, -114, -113, -113, -47, -21, -11, 74, 13, -125, -67, 65,\n            -121, -61, 33, -113, 70, 35, 4, 65, 0, 99, -116, 111, -42, 90, 16, 17, 58, -99, 14, 46,\n            46, 46, 74, -125, -35, 11, 84, 44, -23, -100, -61, -57, -57, 7, 86, -85, 21, -116, 49, 32,\n            34, 111, 89, 107, 45, 90, -83, 22, -50, -50, -50, 74, -127, -51, 125, -104, 6, -125, 1, -57,\n            113, -20, -83, -89, -108, -126, -75, 22, 0, -32, -100, -13, 45, 8, 2, 48, 51, -102, -51, 38,\n            -100, 115, 124, 125, 125, 93, 40, 102, 115, -127, 14, 6, 3, -114, -94, -56, -69, 87, 107, 13,\n            34, 2, 0, -1, 44, 8, -78, 60, 68, -124, 70, -93, 1, 34, -30, -101, -101, -101, -67, 97,\n            119, 6, -115, -94, -120, 123, -67, -98, -121, 19, 43, 10, -88, -12, -58, 24, 56, -25, -4, 55,\n            -78, -127, 122, -67, 14, 34, -30, -37, -37, -37, -67, 96, 119, -102, 20, 69, 17, 119, -69, 93,\n            15, 98, -83, -59, -63, -63, 1, 0, 32, 12, -61, 12, -88, -124, -61, -37, -37, 27, -34, -33,\n            -33, -3, -122, 68, 15, 15, 15, -104, -49, -25, -71, 97, -65, -99, -48, -17, -9, -71, -35, 110,\n            -5, 83, 45, 58, 58, 58, -14, -112, 34, -79, -30, -21, -21, 43, -106, -53, 37, -76, -42, 0,\n            0, -26, -49, -13, 20, -122, 33, -18, -17, -17, -79, 88, 44, 114, -63, 126, -21, 122, 102, -58,\n            108, 54, -13, 22, 19, 32, 0, 56, 61, 61, -3, -14, 76, 107, -115, -43, 106, 5, 34, -62,\n            -45, -45, 83, -26, -35, -6, -72, 84, -48, -23, 116, -70, 113, -25, -25, -25, -25, -20, -100, -53,\n            -4, 60, 29, -109, 114, -6, 103, -77, 89, 41, 21, 106, -17, 90, 47, -112, 2, 8, -64, 3,\n            -118, -85, -45, -33, 20, -43, -34, -96, -52, -100, 57, -35, -64, 103, -116, -82, 3, -1, 83, 80,\n            -87, 62, 2, -72, -98, 79, 5, -74, 44, -107, 2, 42, 112, 0, 50, -15, -7, 43, 64, -103,\n            121, 35, -24, -81, 118, -67, -28, -41, -76, -53, -91, -43, -21, 117, -106, 119, -23, 94, -58, -113,\n            -113, -113, 59, 101, -123, -62, -96, -101, -54, -87, 88, -14, -28, -28, 4, -121, -121, -121, 25, 48,\n            -71, 35, 40, -91, -16, -4, -4, -68, -13, -1, 10, -71, 62, 125, -75, 91, 7, -83, -43, 106,\n            30, 74, 42, -104, -28, 89, -83, 53, -76, -42, -71, 98, -72, -112, 69, -91, 68, 110, -69, -96,\n            -92, 123, -7, 70, 41, 5, -91, 84, -18, 10, 85, 10, 104, 58, 70, -45, 125, -6, -80, 73,\n            47, 22, -51, -85, 66, -82, 87, 74, 1, -128, -17, 55, -127, 109, 2, -1, -85, -96, 98, -47,\n            -76, 91, -73, 1, -82, -113, 1, -8, 107, -30, -113, -125, -34, -35, -35, 5, 68, -28, 19, -27,\n            -74, -12, 83, 102, -46, -81, 84, -87, 82, -91, 95, -88, 63, 49, -122, -88, 68, 127, -55, -90,\n            73, 0, 0, 0, 0, 73, 69, 78, 68, -82, 66, 96, -126};\n    private static final byte[] ARROW = {-119, 80, 78, 71, 13, 10, 26, 10, 0, 0, 0, 13, 73, 72, 68, 82, 0, 0, 0, 48,\n            0, 0, 0, 117, 8, 3, 0, 0, 0, 63, 73, -110, 106, 0, 0, 2, -9, 80, 76, 84,\n            69, -46, -46, -46, -48, -48, -48, -48, -48, -48, -48, -48, -48, -48, -48, -48, -65, -65, -65, 0,\n            0, 0, -52, -52, -52, -49, -49, -49, -47, -47, -47, -47, -47, -47, -48, -48, -48, -54, -54, -54,\n            -48, -48, -48, -48, -48, -48, -47, -47, -47, -47, -47, -47, -49, -49, -49, -86, -86, -86, -48, -48,\n            -48, -48, -48, -48, -47, -47, -47, -52, -52, -52, -49, -49, -49, -48, -48, -48, -48, -48, -48, -1,\n            -1, -1, -49, -49, -49, -48, -48, -48, -48, -48, -48, -51, -51, -51, -50, -50, -50, -48, -48, -48,\n            -48, -48, -48, -50, -50, -50, -48, -48, -48, -48, -48, -48, -48, -48, -48, -52, -52, -52, -48, -48,\n            -48, -48, -48, -48, -48, -48, -48, -46, -46, -46, -47, -47, -47, -52, -52, -52, -48, -48, -48, -48,\n            -48, -48, -1, -1, -1, -48, -48, -48, -48, -48, -48, -48, -48, -48, -48, -48, -48, -48, -48, -48,\n            -48, -48, -48, -41, -41, -41, -48, -48, -48, -49, -49, -49, -43, -43, -43, -47, -47, -47, -49, -49,\n            -49, -48, -48, -48, -48, -48, -48, -48, -48, -48, -48, -48, -48, -48, -48, -48, -49, -49, -49, -47,\n            -47, -47, -49, -49, -49, -47, -47, -47, -48, -48, -48, -48, -48, -48, -48, -48, -48, -49, -49, -49,\n            -47, -47, -47, -44, -44, -44, -48, -48, -48, -48, -48, -48, -1, -1, -1, -46, -46, -46, -48, -48,\n            -48, -49, -49, -49, -47, -47, -47, -49, -49, -49, -35, -35, -35, -49, -49, -49, -49, -49, -49, -49,\n            -49, -49, -47, -47, -47, -40, -40, -40, -49, -49, -49, -48, -48, -48, -48, -48, -48, -47, -47, -47,\n            -47, -47, -47, -47, -47, -47, -48, -48, -48, -60, -60, -60, -53, -53, -53, -48, -48, -48, -46, -46,\n            -46, -65, -65, -65, -48, -48, -48, -54, -54, -54, -45, -45, -45, -47, -47, -47, -49, -49, -49, -48,\n            -48, -48, -49, -49, -49, -51, -51, -51, -48, -48, -48, -48, -48, -48, -46, -46, -46, -47, -47, -47,\n            -47, -47, -47, -37, -37, -37, -47, -47, -47, -49, -49, -49, -50, -50, -50, -48, -48, -48, -128, -128,\n            -128, -48, -48, -48, -48, -48, -48, -50, -50, -50, -48, -48, -48, -48, -48, -48, -43, -43, -43, -47,\n            -47, -47, -48, -48, -48, -48, -48, -48, -48, -48, -48, -43, -43, -43, -47, -47, -47, -48, -48, -48,\n            -50, -50, -50, -49, -49, -49, -48, -48, -48, -48, -48, -48, -33, -33, -33, -48, -48, -48, -52, -52,\n            -52, -51, -51, -51, -48, -48, -48, -48, -48, -48, -48, -48, -48, -48, -48, -48, -48, -48, -48, -52,\n            -52, -52, -48, -48, -48, -46, -46, -46, -48, -48, -48, -37, -37, -37, -29, -29, -29, -48, -48, -48,\n            -48, -48, -48, -47, -47, -47, -48, -48, -48, -49, -49, -49, -46, -46, -46, -48, -48, -48, -49, -49,\n            -49, -47, -47, -47, -58, -58, -58, -49, -49, -49, -47, -47, -47, -48, -48, -48, -50, -50, -50, -47,\n            -47, -47, -50, -50, -50, -48, -48, -48, -48, -48, -48, -50, -50, -50, -48, -48, -48, -48, -48, -48,\n            -48, -48, -48, -55, -55, -55, -45, -45, -45, -48, -48, -48, -48, -48, -48, -48, -48, -48, -48, -48,\n            -48, -46, -46, -46, -47, -47, -47, -48, -48, -48, -47, -47, -47, -51, -51, -51, -47, -47, -47, -51,\n            -51, -51, -48, -48, -48, -47, -47, -47, -48, -48, -48, -65, -65, -65, -48, -48, -48, -50, -50, -50,\n            -48, -48, -48, -47, -47, -47, -49, -49, -49, -47, -47, -47, -47, -47, -47, -48, -48, -48, -48, -48,\n            -48, -50, -50, -50, -47, -47, -47, -49, -49, -49, -49, -49, -49, -47, -47, -47, -48, -48, -48, -47,\n            -47, -47, -49, -49, -49, -39, -39, -39, -50, -50, -50, -47, -47, -47, -48, -48, -48, -42, -42, -42,\n            -47, -47, -47, -46, -46, -46, -47, -47, -47, -47, -47, -47, -48, -48, -48, -49, -49, -49, -49, -49,\n            -49, -47, -47, -47, -48, -48, -48, -47, -47, -47, -48, -48, -48, -50, -50, -50, -47, -47, -47, -48,\n            -48, -48, -47, -47, -47, -49, -49, -49, -48, -48, -48, -49, -49, -49, -50, -50, -50, -47, -47, -47,\n            -56, -56, -56, -48, -48, -48, -48, -48, -48, -40, -40, -40, -45, -45, -45, -47, -47, -47, -47, -47,\n            -47, -49, -49, -49, -48, -48, -48, -43, -43, -43, -52, -52, -52, -49, -49, -49, -47, -47, -47, -47,\n            -47, -47, -50, -50, -50, -49, -49, -49, -47, -47, -47, -49, -49, -49, -48, -48, -48, -49, -49, -49,\n            -115, 52, -27, 40, 0, 0, 0, -3, 116, 82, 78, 83, 34, -37, -1, -7, 120, 4, 0, 5,\n            118, -9, -41, 38, 24, -46, -2, -100, 78, 69, 3, -98, -54, 28, 40, -37, -8, 115, 3, 122,\n            -6, -43, 36, 31, -49, -109, 78, 70, -93, -50, 20, 43, -38, -10, 113, 123, 30, -45, -115, 2,\n            86, -5, -9, 65, -94, -55, 19, -29, 111, 6, 127, 32, -39, -3, -114, 92, -4, 58, -86, -63,\n            22, -32, 109, -126, -5, -51, 36, -44, -121, 1, 90, -11, 59, -79, -59, 15, 48, -11, 106, -123,\n            26, 37, -40, 98, -3, -14, -80, -61, 13, 44, -25, 103, 8, -120, 29, 35, -35, -4, -127, 101,\n            46, -76, -71, 17, -24, 99, 7, -118, -58, 42, 124, 2, 103, -13, 47, -67, -65, 12, 55, -27,\n            -116, -57, 24, 44, -35, 119, 112, -21, 49, 8, -70, 10, 51, -19, -14, 93, -111, -62, 25, -30,\n            119, -78, 14, 9, -108, 113, 117, -18, -53, 63, -23, -15, 89, 9, -106, -2, -66, 21, 50, -125,\n            -33, -28, 57, 97, -97, -73, 19, 69, -12, -17, 81, -87, 57, 94, -99, -73, 72, -18, 82, -88,\n            -20, 27, 12, -100, 67, -16, 83, 16, -91, -122, 54, -26, 104, -101, 64, 85, 11, -82, 100, -101,\n            20, 84, -47, 125, 31, -102, 62, -57, -88, -125, 107, -102, -69, -15, -53, -77, 52, -31, -105, 66,\n            -21, 87, 121, 110, -67, 23, -60, -84, 13, 46, 111, 88, -75, 119, 42, 50, -32, 106, -107, 63,\n            91, -78, 117, 114, -108, 41, -51, -28, -65, 0, 0, 3, -107, 73, 68, 65, 84, 88, -61, -107,\n            -40, 105, 84, -108, 85, 24, 7, 112, -4, -113, -13, -112, -37, -101, -111, -96, 78, 67, -94, 38,\n            -18, -66, 14, 46, 52, -38, -28, -96, 34, 90, -67, -31, -106, 75, -114, 70, 74, 42, 90, 86,\n            -109, 4, 38, -88, -111, -90, 50, -126, -72, -46, -54, -102, 38, -102, -53, 104, -101, 123, -88, -71,\n            78, -18, -91, -90, 88, 82, 46, 89, 90, -71, -107, -27, 7, 95, 62, 120, -114, -100, -93, -121,\n            -5, 127, 63, -65, -65, 15, -9, -36, 123, -97, -5, 127, -98, -96, -96, 106, -80, 84, -73, -118,\n            -14, 23, 20, -4, 64, 13, -44, -84, 69, 0, -87, 93, 7, -38, -125, 12, -112, -70, 22, -32,\n            33, 6, -124, 60, 12, -44, 11, 37, -128, -124, -43, 71, -125, -122, 33, 4, -80, 61, 98, 71,\n            -8, -93, 4, -112, 70, 17, -48, 26, 51, 64, -102, 0, 53, -102, 50, 64, 30, -45, -48, 44,\n            -110, 1, -51, 91, -96, 101, 43, 43, 1, -84, -83, -19, -88, -39, -122, 0, 18, -38, 22, 104,\n            -89, 19, 64, -38, 59, 16, -43, -127, 1, -42, -114, 64, -89, -50, 4, -112, -80, 104, 68, 61,\n            -82, 19, -64, -39, -59, -126, -120, -82, 4, -112, -48, 39, -32, 122, -110, 1, -46, -51, 1, 119,\n            12, 3, -84, -35, -127, 30, 61, 9, 32, -51, 99, 17, -43, 75, 39, 64, 112, 92, 111, -12,\n            105, 68, 0, 121, -22, 105, 104, -49, 48, -64, 120, 22, -120, -17, 75, 0, -111, 126, 64, -1,\n            1, 12, 24, 24, -117, -25, 6, -39, 8, -32, 28, -20, 64, -77, 33, 4, -112, -95, -49, 3,\n            113, 6, 1, 100, -104, 7, -61, 99, 24, -32, 28, 1, -68, -112, 64, 0, 121, 113, 36, -30,\n            71, -23, 4, 72, 124, -55, -114, 78, -93, 9, 32, 99, -58, 34, 105, 28, 3, 100, -68, 27,\n            13, 94, 102, -128, -13, 21, 96, -62, -85, 4, -112, -127, -81, 33, -2, 117, -125, 0, -34, 55,\n            -110, 48, 49, -108, 0, -110, -36, 2, -82, 55, 25, -112, -110, 10, 76, 122, -117, 0, 34, -109,\n            -127, -76, 116, 6, 76, -103, -118, 73, -61, 116, 2, 120, -89, -71, -15, 118, 6, 1, -28, -99,\n            -23, -64, 12, -125, 0, -58, -69, -64, -52, 89, 4, -112, -39, -26, -70, 51, 125, 4, -112, 57,\n            89, -56, -98, -101, 66, 0, 95, -114, 3, -13, -26, 19, 64, -110, 23, -64, -79, -112, 1, 82,\n            -35, -123, -8, -95, 12, -16, -103, -21, 94, -76, -104, 0, 50, 101, 38, -122, 119, 51, 8, -112,\n            -5, -98, 11, 105, -75, 9, 80, 81, 58, -19, -17, 51, 64, -1, 0, -8, 112, 12, 1, -60,\n            -6, -111, -71, -18, -39, 4, -112, -113, -13, -112, 95, -112, 66, -128, -62, 34, -83, -8, -109, 37,\n            4, -112, -91, -79, -16, -92, 50, -64, -8, 20, 88, -58, 0, -55, 1, 74, 24, -80, 124, 36,\n            44, 43, 8, -112, -16, -103, -85, 120, -27, 42, 2, -84, 94, 3, -1, 90, 67, 29, -92, -41,\n            1, -42, 89, -43, 55, -50, 54, 24, -120, 14, 34, -114, -58, -25, 37, 112, 127, 65, 28, 62,\n            -33, -105, -59, -38, 87, -99, 9, -16, -11, 122, -8, 55, 16, 23, -56, 23, 14, 108, -12, -86,\n            95, 81, 91, 28, -80, 105, 51, 81, 4, -52, 10, 110, 73, 37, -54, -52, -106, -83, 46, -84,\n            -116, 36, -64, 55, 107, 80, -70, -115, -88, -83, -37, -21, 1, 59, -104, 98, -4, -83, -122, -84,\n            -47, 68, -71, -33, -71, 11, -10, -35, -60, 11, -108, -66, -57, -115, -52, 72, 2, -20, -51, -122,\n            127, -97, -95, 14, 2, 17, -64, 119, 78, -11, 103, 87, -17, 98, -34, -29, 90, -60, -61, -66,\n            60, 26, -98, -3, 68, 116, -16, 29, -48, -76, -52, -125, 4, 88, -67, 9, -2, 67, 68, -4,\n            -15, 30, 6, -70, 39, -86, 7, 44, -37, 17, 32, -21, 40, 17, -31, 118, -106, -64, 50, -120,\n            8, -119, -127, -17, 93, -104, -80, -124, 0, 63, 100, 35, -65, 61, -111, -116, -73, 31, 3, -114,\n            7, 8, 112, -62, 60, -43, 109, -120, -80, -66, 116, 42, -20, 63, 18, -3, 67, -32, -92, 27,\n            -89, 14, 18, -96, 111, -39, -67, 79, -11, -3, -128, -17, 52, -16, -109, 83, -67, 105, -46, -51,\n            61, -34, 21, 70, -76, 101, 63, -97, -127, 103, 60, -47, -8, 21, -106, 87, 122, -110, -85, 6,\n            -65, -4, -118, -46, -77, -122, 58, -16, -98, -82, 28, 43, -86, 2, 33, -25, -52, -32, 114, -108,\n            104, -64, -25, -100, -127, -3, 60, -47, -30, -5, -54, 93, -72, -16, 27, 1, 98, -4, -56, 46,\n            32, -26, 26, 9, 23, -127, -33, -1, 32, -64, 17, 13, 101, 42, 73, -27, -50, 63, -105, 74,\n            -48, -5, 50, 49, -3, 9, 20, 37, -95, -86, 61, -82, 4, 98, 74, -111, -1, -89, -95, 14,\n            114, -51, 123, -4, 87, -94, -6, -56, -53, -8, 27, -56, -69, 66, 12, -43, -82, 94, -125, -25,\n            58, 49, -74, 43, -68, -95, 21, 87, 57, -109, -71, 27, -4, -109, -121, -78, 127, 83, -44, -127,\n            -43, 124, -113, 111, 22, -118, 50, -48, -1, 3, -4, -86, -109, -54, 10, 80, 17, -8, -1, 23,\n            117, -112, 123, -53, 108, 41, 50, -44, -63, 109, -80, -19, 79, -78, 17, 39, 44, -102, 0, 0,\n            0, 0, 73, 69, 78, 68, -82, 66, 96, -126};\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/font/FontAdapter.kt",
    "content": "package io.legado.app.ui.font\n\nimport android.content.Context\nimport android.graphics.Typeface\nimport android.os.Build\nimport android.view.ViewGroup\nimport io.legado.app.base.adapter.ItemViewHolder\nimport io.legado.app.base.adapter.RecyclerAdapter\nimport io.legado.app.constant.AppLog\nimport io.legado.app.databinding.ItemFontBinding\nimport io.legado.app.utils.*\nimport java.io.File\nimport java.net.URLDecoder\n\nclass FontAdapter(context: Context, curFilePath: String, val callBack: CallBack) :\n    RecyclerAdapter<FileDoc, ItemFontBinding>(context) {\n\n    private val curName = kotlin.runCatching {\n        URLDecoder.decode(curFilePath, \"utf-8\")\n    }.getOrNull()?.substringAfterLast(File.separator)\n\n    override fun getViewBinding(parent: ViewGroup): ItemFontBinding {\n        return ItemFontBinding.inflate(inflater, parent, false)\n    }\n\n    override fun convert(\n        holder: ItemViewHolder,\n        binding: ItemFontBinding,\n        item: FileDoc,\n        payloads: MutableList<Any>\n    ) {\n        binding.run {\n            kotlin.runCatching {\n                val typeface: Typeface? = if (item.isContentScheme) {\n                    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {\n                        context.contentResolver\n                            .openFileDescriptor(item.uri, \"r\")?.use {\n                                Typeface.Builder(it.fileDescriptor).build()\n                            }\n                    } else {\n                        Typeface.createFromFile(RealPathUtil.getPath(context, item.uri))\n                    }\n                } else {\n                    Typeface.createFromFile(item.uri.path!!)\n                }\n                tvFont.typeface = typeface\n            }.onFailure {\n                it.printOnDebug()\n                AppLog.put(\"读取字体 ${item.name} 出错\\n${it.localizedMessage}\", it, true)\n            }\n            tvFont.text = item.name\n            root.setOnClickListener { callBack.onFontSelect(item) }\n            if (item.name == curName) {\n                ivChecked.visible()\n            } else {\n                ivChecked.invisible()\n            }\n        }\n    }\n\n    override fun registerListener(holder: ItemViewHolder, binding: ItemFontBinding) {\n        holder.itemView.setOnClickListener {\n            getItem(holder.layoutPosition)?.let {\n                callBack.onFontSelect(it)\n            }\n        }\n    }\n\n    interface CallBack {\n        fun onFontSelect(docItem: FileDoc)\n    }\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/font/FontSelectDialog.kt",
    "content": "package io.legado.app.ui.font\n\nimport android.net.Uri\nimport android.os.Bundle\nimport android.view.MenuItem\nimport android.view.View\nimport androidx.appcompat.widget.Toolbar\nimport androidx.documentfile.provider.DocumentFile\nimport androidx.lifecycle.lifecycleScope\nimport androidx.recyclerview.widget.LinearLayoutManager\nimport io.legado.app.R\nimport io.legado.app.base.BaseDialogFragment\nimport io.legado.app.constant.AppLog\nimport io.legado.app.constant.PreferKey\nimport io.legado.app.databinding.DialogFontSelectBinding\nimport io.legado.app.help.config.AppConfig\nimport io.legado.app.lib.dialogs.SelectItem\nimport io.legado.app.lib.dialogs.alert\nimport io.legado.app.lib.permission.Permissions\nimport io.legado.app.lib.permission.PermissionsCompat\nimport io.legado.app.lib.theme.primaryColor\nimport io.legado.app.ui.file.HandleFileContract\nimport io.legado.app.utils.FileDoc\nimport io.legado.app.utils.FileUtils\nimport io.legado.app.utils.RealPathUtil\nimport io.legado.app.utils.applyTint\nimport io.legado.app.utils.cnCompare\nimport io.legado.app.utils.externalFiles\nimport io.legado.app.utils.getPrefString\nimport io.legado.app.utils.isContentScheme\nimport io.legado.app.utils.list\nimport io.legado.app.utils.listFileDocs\nimport io.legado.app.utils.putPrefString\nimport io.legado.app.utils.setLayout\nimport io.legado.app.utils.toastOnUi\nimport io.legado.app.utils.viewbindingdelegate.viewBinding\nimport kotlinx.coroutines.Dispatchers.Main\nimport kotlinx.coroutines.launch\nimport java.io.File\n\n/**\n * 字体选择对话框\n */\nclass FontSelectDialog : BaseDialogFragment(R.layout.dialog_font_select),\n    Toolbar.OnMenuItemClickListener,\n    FontAdapter.CallBack {\n    private val fontRegex = Regex(\"(?i).*\\\\.[ot]tf\")\n    private val binding by viewBinding(DialogFontSelectBinding::bind)\n    private val adapter by lazy {\n        val curFontPath = callBack?.curFontPath ?: \"\"\n        FontAdapter(requireContext(), curFontPath, this)\n    }\n    private val selectFontDir = registerForActivityResult(HandleFileContract()) {\n        it.uri?.let { uri ->\n            if (uri.isContentScheme()) {\n                putPrefString(PreferKey.fontFolder, uri.toString())\n                val doc = DocumentFile.fromTreeUri(requireContext(), uri)\n                if (doc != null) {\n                    loadFontFiles(FileDoc.fromDocumentFile(doc))\n                } else {\n                    RealPathUtil.getPath(requireContext(), uri)?.let { path ->\n                        loadFontFilesByPermission(path)\n                    }\n                }\n            } else {\n                uri.path?.let { path ->\n                    putPrefString(PreferKey.fontFolder, path)\n                    loadFontFilesByPermission(path)\n                }\n            }\n        }\n    }\n\n    override fun onStart() {\n        super.onStart()\n        setLayout(0.9f, 0.9f)\n    }\n\n    override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) {\n        binding.toolBar.setBackgroundColor(primaryColor)\n        binding.toolBar.setTitle(R.string.select_font)\n        binding.toolBar.inflateMenu(R.menu.font_select)\n        binding.toolBar.menu.applyTint(requireContext())\n        binding.toolBar.setOnMenuItemClickListener(this)\n        binding.recyclerView.layoutManager = LinearLayoutManager(context)\n        binding.recyclerView.adapter = adapter\n\n        val fontPath = getPrefString(PreferKey.fontFolder)\n        if (fontPath.isNullOrEmpty()) {\n            openFolder()\n        } else {\n            if (fontPath.isContentScheme()) {\n                val doc = DocumentFile.fromTreeUri(requireContext(), Uri.parse(fontPath))\n                if (doc?.canRead() == true) {\n                    loadFontFiles(FileDoc.fromDocumentFile(doc))\n                } else {\n                    openFolder()\n                }\n            } else {\n                loadFontFilesByPermission(fontPath)\n            }\n        }\n    }\n\n    override fun onMenuItemClick(item: MenuItem?): Boolean {\n        when (item?.itemId) {\n            R.id.menu_default -> {\n                val requireContext = requireContext()\n                alert(titleResource = R.string.system_typeface) {\n                    items(\n                        requireContext.resources.getStringArray(R.array.system_typefaces).toList()\n                    ) { _, i ->\n                        AppConfig.systemTypefaces = i\n                        onDefaultFontChange()\n                        dismissAllowingStateLoss()\n                    }\n                }\n            }\n            R.id.menu_other -> {\n                openFolder()\n            }\n        }\n        return true\n    }\n\n    private fun openFolder() {\n        lifecycleScope.launch {\n            val defaultPath = \"SD${File.separator}Fonts\"\n            selectFontDir.launch {\n                otherActions = arrayListOf(SelectItem(defaultPath, -1))\n            }\n        }\n    }\n\n    private fun getLocalFonts(): ArrayList<FileDoc> {\n        val path = FileUtils.getPath(requireContext().externalFiles, \"font\")\n        return File(path).listFileDocs {\n            it.name.matches(fontRegex)\n        }\n    }\n\n    private fun loadFontFilesByPermission(path: String) {\n        PermissionsCompat.Builder()\n            .addPermissions(*Permissions.Group.STORAGE)\n            .rationale(R.string.tip_perm_request_storage)\n            .onGranted {\n                loadFontFiles(\n                    FileDoc.fromFile(File(path))\n                )\n            }\n            .request()\n    }\n\n    private fun loadFontFiles(fileDoc: FileDoc) {\n        execute {\n            val fontItems = fileDoc.list {\n                it.name.matches(fontRegex)\n            } ?: ArrayList()\n            mergeFontItems(fontItems, getLocalFonts())\n        }.onSuccess {\n            adapter.setItems(it)\n        }.onError {\n            AppLog.put(\"加载字体文件失败\\n${it.localizedMessage}\", it)\n            toastOnUi(\"getFontFiles:${it.localizedMessage}\")\n        }\n    }\n\n    private fun mergeFontItems(\n        items1: ArrayList<FileDoc>,\n        items2: ArrayList<FileDoc>\n    ): List<FileDoc> {\n        val items = ArrayList(items1)\n        items2.forEach { item2 ->\n            var isInFirst = false\n            items1.forEach for1@{ item1 ->\n                if (item2.name == item1.name) {\n                    isInFirst = true\n                    return@for1\n                }\n            }\n            if (!isInFirst) {\n                items.add(item2)\n            }\n        }\n        return items.sortedWith { o1, o2 ->\n            o1.name.cnCompare(o2.name)\n        }\n    }\n\n    override fun onFontSelect(docItem: FileDoc) {\n        execute {\n            callBack?.selectFont(docItem.toString())\n        }.onSuccess {\n            dismissAllowingStateLoss()\n        }\n    }\n\n    private fun onDefaultFontChange() {\n        callBack?.selectFont(\"\")\n    }\n\n    private val callBack: CallBack?\n        get() = (parentFragment as? CallBack) ?: (activity as? CallBack)\n\n    interface CallBack {\n        fun selectFont(path: String)\n        val curFontPath: String\n    }\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/login/SourceLoginActivity.kt",
    "content": "package io.legado.app.ui.login\n\nimport android.os.Bundle\nimport androidx.activity.viewModels\nimport io.legado.app.R\nimport io.legado.app.base.VMBaseActivity\nimport io.legado.app.data.entities.BaseSource\nimport io.legado.app.databinding.ActivitySourceLoginBinding\nimport io.legado.app.utils.showDialogFragment\nimport io.legado.app.utils.viewbindingdelegate.viewBinding\n\n\nclass SourceLoginActivity : VMBaseActivity<ActivitySourceLoginBinding, SourceLoginViewModel>() {\n\n    override val binding by viewBinding(ActivitySourceLoginBinding::inflate)\n    override val viewModel by viewModels<SourceLoginViewModel>()\n\n    override fun onActivityCreated(savedInstanceState: Bundle?) {\n        viewModel.initData(intent, success = { source ->\n            initView(source)\n        }, error = {\n            finish()\n        })\n    }\n\n    private fun initView(source: BaseSource) {\n        if (source.loginUi.isNullOrEmpty()) {\n            supportFragmentManager.beginTransaction()\n                .replace(R.id.fl_fragment, WebViewLoginFragment(), \"webViewLogin\")\n                .commit()\n        } else {\n            showDialogFragment<SourceLoginDialog>()\n        }\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/login/SourceLoginDialog.kt",
    "content": "package io.legado.app.ui.login\n\nimport android.content.DialogInterface\nimport android.os.Bundle\nimport android.text.InputType\nimport android.view.View\nimport android.view.ViewGroup\nimport androidx.core.view.setPadding\nimport androidx.fragment.app.activityViewModels\nimport androidx.lifecycle.lifecycleScope\nimport com.script.rhino.runScriptWithContext\nimport io.legado.app.R\nimport io.legado.app.base.BaseDialogFragment\nimport io.legado.app.constant.AppLog\nimport io.legado.app.data.entities.BaseSource\nimport io.legado.app.data.entities.rule.RowUi\nimport io.legado.app.databinding.DialogLoginBinding\nimport io.legado.app.databinding.ItemFilletTextBinding\nimport io.legado.app.databinding.ItemSourceEditBinding\nimport io.legado.app.lib.dialogs.alert\nimport io.legado.app.lib.theme.primaryColor\nimport io.legado.app.ui.about.AppLogDialog\nimport io.legado.app.utils.GSON\nimport io.legado.app.utils.applyTint\nimport io.legado.app.utils.dpToPx\nimport io.legado.app.utils.isAbsUrl\nimport io.legado.app.utils.openUrl\nimport io.legado.app.utils.printOnDebug\nimport io.legado.app.utils.sendToClip\nimport io.legado.app.utils.setLayout\nimport io.legado.app.utils.showDialogFragment\nimport io.legado.app.utils.toastOnUi\nimport io.legado.app.utils.viewbindingdelegate.viewBinding\nimport kotlinx.coroutines.Dispatchers.IO\nimport kotlinx.coroutines.Dispatchers.Main\nimport kotlinx.coroutines.ensureActive\nimport kotlinx.coroutines.launch\nimport kotlinx.coroutines.withContext\nimport splitties.init.appCtx\nimport splitties.views.onClick\n\n\nclass SourceLoginDialog : BaseDialogFragment(R.layout.dialog_login, true) {\n\n    private val binding by viewBinding(DialogLoginBinding::bind)\n    private val viewModel by activityViewModels<SourceLoginViewModel>()\n\n    override fun onStart() {\n        super.onStart()\n        setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)\n    }\n\n    override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) {\n        val source = viewModel.source ?: return\n        binding.toolBar.setBackgroundColor(primaryColor)\n        binding.toolBar.title = getString(R.string.login_source, source.getTag())\n        val loginInfo = source.getLoginInfoMap()\n        val loginUi = source.loginUi()\n        try {\n            loginUi?.forEachIndexed { index, rowUi ->\n                when (rowUi.type) {\n                    RowUi.Type.text -> ItemSourceEditBinding.inflate(\n                        layoutInflater,\n                        binding.root,\n                        false\n                    ).let {\n                        binding.flexbox.addView(it.root)\n                        it.root.id = index + 1000\n                        it.textInputLayout.hint = rowUi.name\n                        it.editText.setText(loginInfo?.get(rowUi.name))\n                    }\n\n                    RowUi.Type.password -> ItemSourceEditBinding.inflate(\n                        layoutInflater,\n                        binding.root,\n                        false\n                    ).let {\n                        binding.flexbox.addView(it.root)\n                        it.root.id = index + 1000\n                        it.textInputLayout.hint = rowUi.name\n                        it.editText.inputType =\n                            InputType.TYPE_TEXT_VARIATION_PASSWORD or InputType.TYPE_CLASS_TEXT\n                        it.editText.setText(loginInfo?.get(rowUi.name))\n                    }\n\n                    RowUi.Type.button -> ItemFilletTextBinding.inflate(\n                        layoutInflater,\n                        binding.root,\n                        false\n                    ).let {\n                        binding.flexbox.addView(it.root)\n                        rowUi.style().apply(it.root)\n                        it.root.id = index + 1000\n                        it.textView.text = rowUi.name\n                        it.textView.setPadding(16.dpToPx())\n                        it.root.onClick {\n                            handleButtonClick(source, rowUi, loginUi)\n                        }\n                    }\n                }\n            }\n        } catch (e: NullPointerException) {\n            AppLog.put(\"登录UI JSON 数据错误\", e, true)\n        }\n        binding.toolBar.inflateMenu(R.menu.source_login)\n        binding.toolBar.menu.applyTint(requireContext())\n        binding.toolBar.setOnMenuItemClickListener { item ->\n            when (item.itemId) {\n                R.id.menu_ok -> {\n                    val loginData = getLoginData(loginUi)\n                    login(source, loginData)\n                }\n\n                R.id.menu_show_login_header -> alert {\n                    setTitle(R.string.login_header)\n                    source.getLoginHeader()?.let { loginHeader ->\n                        setMessage(loginHeader)\n                        positiveButton(R.string.copy_text) {\n                            appCtx.sendToClip(loginHeader)\n                        }\n                    }\n                }\n\n                R.id.menu_del_login_header -> source.removeLoginHeader()\n                R.id.menu_log -> showDialogFragment<AppLogDialog>()\n            }\n            return@setOnMenuItemClickListener true\n        }\n    }\n\n    private fun handleButtonClick(source: BaseSource, rowUi: RowUi, loginUi: List<RowUi>) {\n        lifecycleScope.launch(IO) {\n            if (rowUi.action.isAbsUrl()) {\n                context?.openUrl(rowUi.action!!)\n            } else if (rowUi.action != null) {\n                // JavaScript\n                val buttonFunctionJS = rowUi.action!!\n                val loginJS = source.getLoginJs() ?: return@launch\n                kotlin.runCatching {\n                    runScriptWithContext {\n                        source.evalJS(\"$loginJS\\n$buttonFunctionJS\") {\n                            put(\"result\", getLoginData(loginUi))\n                        }\n                    }\n                }.onFailure { e ->\n                    ensureActive()\n                    AppLog.put(\"LoginUI Button ${rowUi.name} JavaScript error\", e)\n                }\n            }\n        }\n    }\n\n    private fun getLoginData(loginUi: List<RowUi>?): HashMap<String, String> {\n        val loginData = hashMapOf<String, String>()\n        loginUi?.forEachIndexed { index, rowUi ->\n            when (rowUi.type) {\n                \"text\", \"password\" -> {\n                    val rowView = binding.root.findViewById<View>(index + 1000)\n                    ItemSourceEditBinding.bind(rowView).editText.text?.let {\n                        loginData[rowUi.name] = it.toString()\n                    }\n                }\n            }\n        }\n        return loginData\n    }\n\n    private fun login(source: BaseSource, loginData: HashMap<String, String>) {\n        lifecycleScope.launch(IO) {\n            if (loginData.isEmpty()) {\n                source.removeLoginInfo()\n                withContext(Main) {\n                    dismiss()\n                }\n            } else if (source.putLoginInfo(GSON.toJson(loginData))) {\n                try {\n                    runScriptWithContext {\n                        source.login()\n                    }\n                    context?.toastOnUi(R.string.success)\n                    withContext(Main) {\n                        dismiss()\n                    }\n                } catch (e: Exception) {\n                    AppLog.put(\"登录出错\\n${e.localizedMessage}\", e)\n                    context?.toastOnUi(\"登录出错\\n${e.localizedMessage}\")\n                    e.printOnDebug()\n                }\n            }\n        }\n    }\n\n    override fun onDismiss(dialog: DialogInterface) {\n        super.onDismiss(dialog)\n        activity?.finish()\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/login/SourceLoginViewModel.kt",
    "content": "package io.legado.app.ui.login\n\nimport android.app.Application\nimport android.content.Intent\nimport com.script.rhino.runScriptWithContext\nimport io.legado.app.base.BaseViewModel\nimport io.legado.app.constant.AppLog\nimport io.legado.app.data.appDb\nimport io.legado.app.data.entities.BaseSource\nimport io.legado.app.exception.NoStackTraceException\nimport io.legado.app.utils.toastOnUi\n\nclass SourceLoginViewModel(application: Application) : BaseViewModel(application) {\n\n    var source: BaseSource? = null\n    var headerMap: Map<String, String> = emptyMap()\n\n    fun initData(intent: Intent, success: (bookSource: BaseSource) -> Unit, error: () -> Unit) {\n        execute {\n            val sourceKey = intent.getStringExtra(\"key\")\n                ?: throw NoStackTraceException(\"没有参数\")\n            when (intent.getStringExtra(\"type\")) {\n                \"bookSource\" -> source = appDb.bookSourceDao.getBookSource(sourceKey)\n                \"rssSource\" -> source = appDb.rssSourceDao.getByKey(sourceKey)\n                \"httpTts\" -> source = appDb.httpTTSDao.get(sourceKey.toLong())\n            }\n            headerMap = runScriptWithContext {\n                source?.getHeaderMap(true) ?: emptyMap()\n            }\n            source\n        }.onSuccess {\n            if (it != null) {\n                success.invoke(it)\n            } else {\n                context.toastOnUi(\"未找到书源\")\n            }\n        }.onError {\n            error.invoke()\n            AppLog.put(\"登录 UI 初始化失败\\n$it\", it, true)\n        }\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/login/WebViewLoginFragment.kt",
    "content": "package io.legado.app.ui.login\n\nimport android.annotation.SuppressLint\nimport android.graphics.Bitmap\nimport android.net.Uri\nimport android.net.http.SslError\nimport android.os.Bundle\nimport android.view.Menu\nimport android.view.MenuItem\nimport android.view.View\nimport android.webkit.CookieManager\nimport android.webkit.SslErrorHandler\nimport android.webkit.WebChromeClient\nimport android.webkit.WebResourceRequest\nimport android.webkit.WebSettings\nimport android.webkit.WebView\nimport android.webkit.WebViewClient\nimport androidx.fragment.app.activityViewModels\nimport io.legado.app.R\nimport io.legado.app.base.BaseFragment\nimport io.legado.app.constant.AppConst\nimport io.legado.app.data.entities.BaseSource\nimport io.legado.app.databinding.FragmentWebViewLoginBinding\nimport io.legado.app.help.http.CookieStore\nimport io.legado.app.lib.theme.accentColor\nimport io.legado.app.utils.NetworkUtils\nimport io.legado.app.utils.gone\nimport io.legado.app.utils.longSnackbar\nimport io.legado.app.utils.openUrl\nimport io.legado.app.utils.snackbar\nimport io.legado.app.utils.viewbindingdelegate.viewBinding\n\nclass WebViewLoginFragment : BaseFragment(R.layout.fragment_web_view_login) {\n\n    private val binding by viewBinding(FragmentWebViewLoginBinding::bind)\n    private val viewModel by activityViewModels<SourceLoginViewModel>()\n\n    private var checking = false\n\n    override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) {\n        setSupportToolbar(binding.titleBar.toolbar)\n        viewModel.source?.let {\n            binding.titleBar.title = getString(R.string.login_source, it.getTag())\n            initWebView(it)\n        }\n    }\n\n    override fun onCompatCreateOptionsMenu(menu: Menu) {\n        menuInflater.inflate(R.menu.source_webview_login, menu)\n    }\n\n    override fun onCompatOptionsItemSelected(item: MenuItem) {\n        when (item.itemId) {\n            R.id.menu_ok -> {\n                if (!checking) {\n                    checking = true\n                    binding.titleBar.snackbar(R.string.check_host_cookie)\n                    viewModel.source?.let {\n                        loadUrl(it)\n                    }\n                }\n            }\n        }\n    }\n\n    @SuppressLint(\"SetJavaScriptEnabled\")\n    private fun initWebView(source: BaseSource) {\n        binding.progressBar.fontColor = accentColor\n        binding.webView.settings.apply {\n            mixedContentMode = WebSettings.MIXED_CONTENT_ALWAYS_ALLOW\n            domStorageEnabled = true\n            useWideViewPort = true\n            loadWithOverviewMode = true\n            builtInZoomControls = true\n            javaScriptEnabled = true\n            displayZoomControls = false\n            viewModel.headerMap[AppConst.UA_NAME]?.let {\n                userAgentString = it\n            }\n        }\n        val cookieManager = CookieManager.getInstance()\n        binding.webView.webViewClient = object : WebViewClient() {\n            override fun onPageStarted(view: WebView?, url: String?, favicon: Bitmap?) {\n                val cookie = cookieManager.getCookie(url)\n                CookieStore.setCookie(source.getKey(), cookie)\n                super.onPageStarted(view, url, favicon)\n            }\n\n            override fun onPageFinished(view: WebView?, url: String?) {\n                val cookie = cookieManager.getCookie(url)\n                CookieStore.setCookie(source.getKey(), cookie)\n                if (checking) {\n                    activity?.finish()\n                }\n                super.onPageFinished(view, url)\n            }\n\n            override fun shouldOverrideUrlLoading(\n                view: WebView,\n                request: WebResourceRequest\n            ): Boolean {\n                return shouldOverrideUrlLoading(request.url)\n            }\n\n            @Suppress(\"DEPRECATION\", \"OVERRIDE_DEPRECATION\", \"KotlinRedundantDiagnosticSuppress\")\n            override fun shouldOverrideUrlLoading(view: WebView, url: String): Boolean {\n                return shouldOverrideUrlLoading(Uri.parse(url))\n            }\n\n            private fun shouldOverrideUrlLoading(url: Uri): Boolean {\n                when (url.scheme) {\n                    \"http\", \"https\" -> {\n                        return false\n                    }\n\n                    else -> {\n                        binding.root.longSnackbar(R.string.jump_to_another_app, R.string.confirm) {\n                            context?.openUrl(url)\n                        }\n                        return true\n                    }\n                }\n            }\n\n            @SuppressLint(\"WebViewClientOnReceivedSslError\")\n            override fun onReceivedSslError(\n                view: WebView?,\n                handler: SslErrorHandler?,\n                error: SslError?\n            ) {\n                handler?.proceed()\n            }\n        }\n        binding.webView.webChromeClient = object : WebChromeClient() {\n\n            override fun onProgressChanged(view: WebView?, newProgress: Int) {\n                super.onProgressChanged(view, newProgress)\n                binding.progressBar.setDurProgress(newProgress)\n                binding.progressBar.gone(newProgress == 100)\n            }\n\n        }\n        loadUrl(source)\n    }\n\n    private fun loadUrl(source: BaseSource) {\n        val loginUrl = source.loginUrl ?: return\n        val absoluteUrl = NetworkUtils.getAbsoluteURL(source.getKey(), loginUrl)\n        binding.webView.loadUrl(absoluteUrl, viewModel.headerMap)\n    }\n\n    override fun onDestroy() {\n        super.onDestroy()\n        binding.webView.destroy()\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/main/MainActivity.kt",
    "content": "@file:Suppress(\"DEPRECATION\")\n\npackage io.legado.app.ui.main\n\nimport android.os.Bundle\nimport android.text.format.DateUtils\nimport android.view.MenuItem\nimport android.view.ViewGroup\nimport androidx.activity.addCallback\nimport androidx.activity.viewModels\nimport androidx.core.view.get\nimport androidx.core.view.postDelayed\nimport androidx.fragment.app.Fragment\nimport androidx.fragment.app.FragmentManager\nimport androidx.fragment.app.FragmentStatePagerAdapter\nimport androidx.lifecycle.lifecycleScope\nimport androidx.viewpager.widget.ViewPager\nimport com.google.android.material.bottomnavigation.BottomNavigationView\nimport io.legado.app.BuildConfig\nimport io.legado.app.R\nimport io.legado.app.base.VMBaseActivity\nimport io.legado.app.constant.AppConst.appInfo\nimport io.legado.app.constant.EventBus\nimport io.legado.app.constant.PreferKey\nimport io.legado.app.databinding.ActivityMainBinding\nimport io.legado.app.databinding.DialogEditTextBinding\nimport io.legado.app.help.AppWebDav\nimport io.legado.app.help.book.BookHelp\nimport io.legado.app.help.config.AppConfig\nimport io.legado.app.help.config.LocalConfig\nimport io.legado.app.help.coroutine.Coroutine\nimport io.legado.app.help.storage.Backup\nimport io.legado.app.lib.dialogs.alert\nimport io.legado.app.lib.theme.elevation\nimport io.legado.app.lib.theme.primaryColor\nimport io.legado.app.service.BaseReadAloudService\nimport io.legado.app.ui.about.CrashLogsDialog\nimport io.legado.app.ui.main.bookshelf.BaseBookshelfFragment\nimport io.legado.app.ui.main.bookshelf.style1.BookshelfFragment1\nimport io.legado.app.ui.main.bookshelf.style2.BookshelfFragment2\nimport io.legado.app.ui.main.explore.ExploreFragment\nimport io.legado.app.ui.main.my.MyFragment\nimport io.legado.app.ui.main.rss.RssFragment\nimport io.legado.app.ui.widget.dialog.TextDialog\nimport io.legado.app.ui.widget.text.BadgeView\nimport io.legado.app.utils.isCreated\nimport io.legado.app.utils.navigationBarHeight\nimport io.legado.app.utils.observeEvent\nimport io.legado.app.utils.setEdgeEffectColor\nimport io.legado.app.utils.setOnApplyWindowInsetsListenerCompat\nimport io.legado.app.utils.showDialogFragment\nimport io.legado.app.utils.toastOnUi\nimport io.legado.app.utils.viewbindingdelegate.viewBinding\nimport kotlinx.coroutines.Dispatchers.IO\nimport kotlinx.coroutines.launch\nimport kotlinx.coroutines.suspendCancellableCoroutine\nimport kotlinx.coroutines.withContext\nimport splitties.views.bottomPadding\nimport kotlin.coroutines.resume\n\n/**\n * 主界面\n */\n@Suppress(\"PrivatePropertyName\")\nclass MainActivity : VMBaseActivity<ActivityMainBinding, MainViewModel>(),\n    BottomNavigationView.OnNavigationItemSelectedListener,\n    BottomNavigationView.OnNavigationItemReselectedListener {\n\n    override val binding by viewBinding(ActivityMainBinding::inflate)\n    override val viewModel by viewModels<MainViewModel>()\n    private val idBookshelf = 0\n    private val idBookshelf1 = 11\n    private val idBookshelf2 = 12\n    private val idExplore = 1\n    private val idRss = 2\n    private val idMy = 3\n    private var exitTime: Long = 0\n    private var bookshelfReselected: Long = 0\n    private var exploreReselected: Long = 0\n    private var pagePosition = 0\n    private val fragmentMap = hashMapOf<Int, Fragment>()\n    private var bottomMenuCount = 4\n    private val EXIT_INTERVAL = 2000L\n    private val realPositions = arrayOf(idBookshelf, idExplore, idRss, idMy)\n    private val adapter by lazy {\n        TabFragmentPageAdapter(supportFragmentManager)\n    }\n    private var onUpBooksBadgeView: BadgeView? = null\n\n    override fun onActivityCreated(savedInstanceState: Bundle?) {\n        upBottomMenu()\n        initView()\n        upHomePage()\n        onBackPressedDispatcher.addCallback(this) {\n            if (pagePosition != 0) {\n                binding.viewPagerMain.currentItem = 0\n                return@addCallback\n            }\n            (fragmentMap[getFragmentId(0)] as? BookshelfFragment2)?.let {\n                if (it.back()) {\n                    return@addCallback\n                }\n            }\n            if (System.currentTimeMillis() - exitTime > EXIT_INTERVAL) {\n                toastOnUi(R.string.double_click_exit)\n                exitTime = System.currentTimeMillis()\n            } else {\n                if (BaseReadAloudService.pause) {\n                    finish()\n                } else {\n                    moveTaskToBack(true)\n                }\n            }\n        }\n    }\n\n    override fun onPostCreate(savedInstanceState: Bundle?) {\n        super.onPostCreate(savedInstanceState)\n        lifecycleScope.launch {\n            //隐私协议\n            if (!privacyPolicy()) return@launch\n            //版本更新\n            upVersion()\n            //设置本地密码\n            setLocalPassword()\n            notifyAppCrash()\n            //备份同步\n            backupSync()\n            //自动更新书籍\n            val isAutoRefreshedBook = savedInstanceState?.getBoolean(\"isAutoRefreshedBook\") ?: false\n            if (AppConfig.autoRefreshBook && !isAutoRefreshedBook) {\n                binding.viewPagerMain.postDelayed(1000) {\n                    viewModel.upAllBookToc()\n                }\n            }\n            binding.viewPagerMain.postDelayed(3000) {\n                viewModel.postLoad()\n            }\n        }\n    }\n\n    override fun onNavigationItemSelected(item: MenuItem): Boolean = binding.run {\n        when (item.itemId) {\n            R.id.menu_bookshelf ->\n                viewPagerMain.setCurrentItem(0, false)\n\n            R.id.menu_discovery ->\n                viewPagerMain.setCurrentItem(realPositions.indexOf(idExplore), false)\n\n            R.id.menu_rss ->\n                viewPagerMain.setCurrentItem(realPositions.indexOf(idRss), false)\n\n            R.id.menu_my_config ->\n                viewPagerMain.setCurrentItem(realPositions.indexOf(idMy), false)\n        }\n        return false\n    }\n\n    override fun onNavigationItemReselected(item: MenuItem) {\n        when (item.itemId) {\n            R.id.menu_bookshelf -> {\n                if (System.currentTimeMillis() - bookshelfReselected > 300) {\n                    bookshelfReselected = System.currentTimeMillis()\n                } else {\n                    (fragmentMap[getFragmentId(0)] as? BaseBookshelfFragment)?.gotoTop()\n                }\n            }\n\n            R.id.menu_discovery -> {\n                if (System.currentTimeMillis() - exploreReselected > 300) {\n                    exploreReselected = System.currentTimeMillis()\n                } else {\n                    (fragmentMap[1] as? ExploreFragment)?.compressExplore()\n                }\n            }\n        }\n    }\n\n    private fun initView() = binding.run {\n        viewPagerMain.setEdgeEffectColor(primaryColor)\n        viewPagerMain.offscreenPageLimit = 3\n        viewPagerMain.adapter = adapter\n        viewPagerMain.addOnPageChangeListener(PageChangeCallback())\n        bottomNavigationView.elevation = elevation\n        bottomNavigationView.setOnNavigationItemSelectedListener(this@MainActivity)\n        bottomNavigationView.setOnNavigationItemReselectedListener(this@MainActivity)\n        if (AppConfig.isEInkMode) {\n            bottomNavigationView.setBackgroundResource(R.drawable.bg_eink_border_top)\n        }\n        bottomNavigationView.setOnApplyWindowInsetsListenerCompat { view, windowInsets ->\n            val height = windowInsets.navigationBarHeight\n            view.bottomPadding = height\n            windowInsets.inset(0, 0, 0, height)\n        }\n    }\n\n    /**\n     * 用户隐私与协议\n     */\n    private suspend fun privacyPolicy(): Boolean = suspendCancellableCoroutine sc@{ block ->\n        if (LocalConfig.privacyPolicyOk) {\n            block.resume(true)\n            return@sc\n        }\n        val privacyPolicy = String(assets.open(\"privacyPolicy.md\").readBytes())\n        alert(getString(R.string.privacy_policy), privacyPolicy) {\n            positiveButton(R.string.agree) {\n                LocalConfig.privacyPolicyOk = true\n                block.resume(true)\n            }\n            negativeButton(R.string.refuse) {\n                finish()\n                block.resume(false)\n            }\n        }\n    }\n\n    /**\n     * 版本更新日志\n     */\n    private suspend fun upVersion() = suspendCancellableCoroutine sc@{ block ->\n        if (LocalConfig.versionCode == appInfo.versionCode) {\n            block.resume(null)\n            return@sc\n        }\n        LocalConfig.versionCode = appInfo.versionCode\n        if (LocalConfig.isFirstOpenApp) {\n            val help = String(assets.open(\"web/help/md/appHelp.md\").readBytes())\n            val dialog = TextDialog(getString(R.string.help), help, TextDialog.Mode.MD)\n            dialog.setOnDismissListener {\n                block.resume(null)\n            }\n            showDialogFragment(dialog)\n        } else if (!BuildConfig.DEBUG) {\n            val log = String(assets.open(\"updateLog.md\").readBytes())\n            val dialog = TextDialog(getString(R.string.update_log), log, TextDialog.Mode.MD)\n            dialog.setOnDismissListener {\n                block.resume(null)\n            }\n            showDialogFragment(dialog)\n        } else {\n            block.resume(null)\n        }\n    }\n\n    /**\n     * 设置本地密码\n     */\n    private suspend fun setLocalPassword() = suspendCancellableCoroutine sc@{ block ->\n        if (LocalConfig.password != null) {\n            block.resume(null)\n            return@sc\n        }\n        alert(R.string.set_local_password, R.string.set_local_password_summary) {\n            val editTextBinding = DialogEditTextBinding.inflate(layoutInflater).apply {\n                editView.hint = \"password\"\n            }\n            customView {\n                editTextBinding.root\n            }\n            onDismiss {\n                block.resume(null)\n            }\n            okButton {\n                LocalConfig.password = editTextBinding.editView.text.toString()\n            }\n            cancelButton {\n                LocalConfig.password = \"\"\n            }\n        }\n    }\n\n    private fun notifyAppCrash() {\n        if (!LocalConfig.appCrash || BuildConfig.DEBUG) {\n            return\n        }\n        LocalConfig.appCrash = false\n        alert(getString(R.string.draw), \"检测到阅读发生了崩溃，是否打开崩溃日志以便报告问题？\") {\n            yesButton {\n                showDialogFragment<CrashLogsDialog>()\n            }\n            noButton()\n        }\n    }\n\n    /**\n     * 备份同步\n     */\n    private fun backupSync() {\n        if (!AppConfig.autoCheckNewBackup) {\n            return\n        }\n        lifecycleScope.launch {\n            val lastBackupFile =\n                withContext(IO) { AppWebDav.lastBackUp().getOrNull() } ?: return@launch\n            if (lastBackupFile.lastModify - LocalConfig.lastBackup > DateUtils.MINUTE_IN_MILLIS) {\n                LocalConfig.lastBackup = lastBackupFile.lastModify\n                alert(R.string.restore, R.string.webdav_after_local_restore_confirm) {\n                    cancelButton()\n                    okButton {\n                        viewModel.restoreWebDav(lastBackupFile.displayName)\n                    }\n                }\n            }\n        }\n    }\n\n    override fun onSaveInstanceState(outState: Bundle) {\n        super.onSaveInstanceState(outState)\n        if (AppConfig.autoRefreshBook) {\n            outState.putBoolean(\"isAutoRefreshedBook\", true)\n        }\n    }\n\n    override fun onDestroy() {\n        super.onDestroy()\n        Coroutine.async {\n            BookHelp.clearInvalidCache()\n        }\n        if (!BuildConfig.DEBUG) {\n            Backup.autoBack(this)\n        }\n    }\n\n    /**\n     * 如果重启太快fragment不会重建,这里更新一下书架的排序\n     */\n    override fun recreate() {\n        (fragmentMap[getFragmentId(0)] as? BaseBookshelfFragment)?.run {\n            upSort()\n        }\n        super.recreate()\n    }\n\n    override fun observeLiveBus() {\n        viewModel.onUpBooksLiveData.observe(this) {\n            if (onUpBooksBadgeView == null) {\n                onUpBooksBadgeView = binding.bottomNavigationView.addBadgeView(0)\n            }\n            onUpBooksBadgeView!!.setBadgeCount(it)\n        }\n        observeEvent<String>(EventBus.RECREATE) {\n            recreate()\n        }\n        observeEvent<Boolean>(EventBus.NOTIFY_MAIN) {\n            binding.apply {\n                if (it) {\n                    bottomNavigationView.menu.clear()\n                    bottomNavigationView.inflateMenu(R.menu.main_bnv)\n                    onUpBooksBadgeView = null\n                }\n                upBottomMenu()\n                if (it) {\n                    viewPagerMain.setCurrentItem(bottomMenuCount - 1, false)\n                }\n            }\n        }\n        observeEvent<String>(PreferKey.threadCount) {\n            viewModel.upPool()\n        }\n    }\n\n    private fun upBottomMenu() {\n        val showDiscovery = AppConfig.showDiscovery\n        val showRss = AppConfig.showRSS\n        binding.bottomNavigationView.menu.let { menu ->\n            menu.findItem(R.id.menu_discovery).isVisible = showDiscovery\n            menu.findItem(R.id.menu_rss).isVisible = showRss\n        }\n        var index = 0\n        if (showDiscovery) {\n            index++\n            realPositions[index] = idExplore\n        }\n        if (showRss) {\n            index++\n            realPositions[index] = idRss\n        }\n        index++\n        realPositions[index] = idMy\n        bottomMenuCount = index + 1\n        adapter.notifyDataSetChanged()\n    }\n\n    private fun upHomePage() {\n        when (AppConfig.defaultHomePage) {\n            \"bookshelf\" -> {}\n            \"explore\" -> if (AppConfig.showDiscovery) {\n                binding.viewPagerMain.setCurrentItem(realPositions.indexOf(idExplore), false)\n            }\n\n            \"rss\" -> if (AppConfig.showRSS) {\n                binding.viewPagerMain.setCurrentItem(realPositions.indexOf(idRss), false)\n            }\n\n            \"my\" -> binding.viewPagerMain.setCurrentItem(realPositions.indexOf(idMy), false)\n        }\n    }\n\n    private fun getFragmentId(position: Int): Int {\n        val id = realPositions[position]\n        if (id == idBookshelf) {\n            return if (AppConfig.bookGroupStyle == 1) idBookshelf2 else idBookshelf1\n        }\n        return id\n    }\n\n    private inner class PageChangeCallback : ViewPager.SimpleOnPageChangeListener() {\n\n        override fun onPageSelected(position: Int) {\n            pagePosition = position\n            binding.bottomNavigationView.menu[realPositions[position]].isChecked = true\n        }\n\n    }\n\n    @Suppress(\"DEPRECATION\")\n    private inner class TabFragmentPageAdapter(fm: FragmentManager) :\n        FragmentStatePagerAdapter(fm, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {\n\n        private fun getId(position: Int): Int {\n            return getFragmentId(position)\n        }\n\n        override fun getItemPosition(any: Any): Int {\n            val position = (any as MainFragmentInterface).position\n                ?: return POSITION_NONE\n            val fragmentId = getId(position)\n            if ((fragmentId == idBookshelf1 && any is BookshelfFragment1)\n                || (fragmentId == idBookshelf2 && any is BookshelfFragment2)\n                || (fragmentId == idExplore && any is ExploreFragment)\n                || (fragmentId == idRss && any is RssFragment)\n                || (fragmentId == idMy && any is MyFragment)\n            ) {\n                return POSITION_UNCHANGED\n            }\n            return POSITION_NONE\n        }\n\n        override fun getItem(position: Int): Fragment {\n            return when (getId(position)) {\n                idBookshelf1 -> BookshelfFragment1(position)\n                idBookshelf2 -> BookshelfFragment2(position)\n                idExplore -> ExploreFragment(position)\n                idRss -> RssFragment(position)\n                else -> MyFragment(position)\n            }\n        }\n\n        override fun getCount(): Int {\n            return bottomMenuCount\n        }\n\n        override fun instantiateItem(container: ViewGroup, position: Int): Any {\n            var fragment = super.instantiateItem(container, position) as Fragment\n            if (fragment.isCreated && getItemPosition(fragment) == POSITION_NONE) {\n                destroyItem(container, position, fragment)\n                fragment = super.instantiateItem(container, position) as Fragment\n            }\n            fragmentMap[getId(position)] = fragment\n            return fragment\n        }\n\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/main/MainFragmentInterface.kt",
    "content": "package io.legado.app.ui.main\n\ninterface MainFragmentInterface {\n\n    val position: Int?\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/main/MainViewModel.kt",
    "content": "package io.legado.app.ui.main\n\nimport android.app.Application\nimport androidx.lifecycle.MutableLiveData\nimport androidx.lifecycle.viewModelScope\nimport androidx.recyclerview.widget.RecyclerView.RecycledViewPool\nimport io.legado.app.base.BaseViewModel\nimport io.legado.app.constant.AppConst\nimport io.legado.app.constant.AppLog\nimport io.legado.app.constant.BookType\nimport io.legado.app.constant.EventBus\nimport io.legado.app.data.appDb\nimport io.legado.app.data.entities.Book\nimport io.legado.app.data.entities.BookSource\nimport io.legado.app.help.AppWebDav\nimport io.legado.app.help.DefaultData\nimport io.legado.app.help.book.BookHelp\nimport io.legado.app.help.book.addType\nimport io.legado.app.help.book.isLocal\nimport io.legado.app.help.book.isUpError\nimport io.legado.app.help.book.removeType\nimport io.legado.app.help.book.sync\nimport io.legado.app.help.config.AppConfig\nimport io.legado.app.model.CacheBook\nimport io.legado.app.model.ReadBook\nimport io.legado.app.model.webBook.WebBook\nimport io.legado.app.service.CacheBookService\nimport io.legado.app.utils.onEachParallel\nimport io.legado.app.utils.postEvent\nimport kotlinx.coroutines.Job\nimport kotlinx.coroutines.asCoroutineDispatcher\nimport kotlinx.coroutines.currentCoroutineContext\nimport kotlinx.coroutines.delay\nimport kotlinx.coroutines.ensureActive\nimport kotlinx.coroutines.flow.catch\nimport kotlinx.coroutines.flow.collect\nimport kotlinx.coroutines.flow.flow\nimport kotlinx.coroutines.flow.onCompletion\nimport kotlinx.coroutines.flow.onEach\nimport kotlinx.coroutines.isActive\nimport kotlinx.coroutines.launch\nimport java.util.LinkedList\nimport java.util.concurrent.ConcurrentHashMap\nimport java.util.concurrent.Executors\nimport kotlin.math.min\n\nclass MainViewModel(application: Application) : BaseViewModel(application) {\n    private var threadCount = AppConfig.threadCount\n    private var poolSize = min(threadCount, AppConst.MAX_THREAD)\n    private var upTocPool = Executors.newFixedThreadPool(poolSize).asCoroutineDispatcher()\n    private val waitUpTocBooks = LinkedList<String>()\n    private val onUpTocBooks = ConcurrentHashMap.newKeySet<String>()\n    val onUpBooksLiveData = MutableLiveData<Int>()\n    private var upTocJob: Job? = null\n    private var cacheBookJob: Job? = null\n    val booksListRecycledViewPool = RecycledViewPool().apply {\n        setMaxRecycledViews(0, 30)\n    }\n    val booksGridRecycledViewPool = RecycledViewPool().apply {\n        setMaxRecycledViews(0, 100)\n    }\n\n    init {\n        deleteNotShelfBook()\n    }\n\n    override fun onCleared() {\n        super.onCleared()\n        upTocPool.close()\n    }\n\n    fun upPool() {\n        threadCount = AppConfig.threadCount\n        if (upTocJob?.isActive == true || cacheBookJob?.isActive == true) {\n            return\n        }\n        val newPoolSize = min(threadCount, AppConst.MAX_THREAD)\n        if (poolSize == newPoolSize) {\n            return\n        }\n        poolSize = newPoolSize\n        upTocPool.close()\n        upTocPool = Executors.newFixedThreadPool(poolSize).asCoroutineDispatcher()\n    }\n\n    fun isUpdate(bookUrl: String): Boolean {\n        return onUpTocBooks.contains(bookUrl)\n    }\n\n    fun upAllBookToc() {\n        execute {\n            addToWaitUp(appDb.bookDao.hasUpdateBooks)\n        }\n    }\n\n    fun upToc(books: List<Book>) {\n        execute(context = upTocPool) {\n            books.filter {\n                !it.isLocal && it.canUpdate\n            }.let {\n                addToWaitUp(it)\n            }\n        }\n    }\n\n    @Synchronized\n    private fun addToWaitUp(books: List<Book>) {\n        books.forEach { book ->\n            if (!waitUpTocBooks.contains(book.bookUrl) && !onUpTocBooks.contains(book.bookUrl)) {\n                waitUpTocBooks.add(book.bookUrl)\n            }\n        }\n        if (upTocJob == null) {\n            startUpTocJob()\n        }\n    }\n\n    private fun startUpTocJob() {\n        upPool()\n        postUpBooksLiveData()\n        upTocJob = viewModelScope.launch(upTocPool) {\n            flow {\n                while (true) {\n                    emit(waitUpTocBooks.poll() ?: break)\n                }\n            }.onEachParallel(threadCount) {\n                onUpTocBooks.add(it)\n                postEvent(EventBus.UP_BOOKSHELF, it)\n                updateToc(it)\n            }.onEach {\n                onUpTocBooks.remove(it)\n                postEvent(EventBus.UP_BOOKSHELF, it)\n                postUpBooksLiveData()\n            }.onCompletion {\n                upTocJob = null\n                if (waitUpTocBooks.isNotEmpty()) {\n                    startUpTocJob()\n                }\n                if (it == null && cacheBookJob == null && !CacheBookService.isRun) {\n                    //所有目录更新完再开始缓存章节\n                    cacheBook()\n                }\n            }.catch {\n                AppLog.put(\"更新目录出错\\n${it.localizedMessage}\", it)\n            }.collect()\n        }\n    }\n\n    private suspend fun updateToc(bookUrl: String) {\n        val book = appDb.bookDao.getBook(bookUrl) ?: return\n        val source = appDb.bookSourceDao.getBookSource(book.origin)\n        if (source == null) {\n            if (!book.isUpError) {\n                book.addType(BookType.updateError)\n                appDb.bookDao.update(book)\n            }\n            return\n        }\n        kotlin.runCatching {\n            val oldBook = book.copy()\n            if (book.tocUrl.isBlank()) {\n                WebBook.getBookInfoAwait(source, book)\n            } else {\n                WebBook.runPreUpdateJs(source, book)\n            }\n            val toc = WebBook.getChapterListAwait(source, book).getOrThrow()\n            book.sync(oldBook)\n            book.removeType(BookType.updateError)\n            if (book.bookUrl == bookUrl) {\n                appDb.bookDao.update(book)\n            } else {\n                appDb.bookDao.replace(oldBook, book)\n                BookHelp.updateCacheFolder(oldBook, book)\n            }\n            appDb.bookChapterDao.delByBook(bookUrl)\n            appDb.bookChapterDao.insert(*toc.toTypedArray())\n            ReadBook.onChapterListUpdated(book)\n            addDownload(source, book)\n        }.onFailure {\n            currentCoroutineContext().ensureActive()\n            AppLog.put(\"${book.name} 更新目录失败\\n${it.localizedMessage}\", it)\n            //这里可能因为时间太长书籍信息已经更改,所以重新获取\n            appDb.bookDao.getBook(book.bookUrl)?.let { book ->\n                book.addType(BookType.updateError)\n                appDb.bookDao.update(book)\n            }\n        }\n    }\n\n    fun postUpBooksLiveData(reset: Boolean = false) {\n        if (AppConfig.showWaitUpCount) {\n            onUpBooksLiveData.postValue(waitUpTocBooks.size + onUpTocBooks.size)\n        } else if (reset) {\n            onUpBooksLiveData.postValue(0)\n        }\n    }\n\n    @Synchronized\n    private fun addDownload(source: BookSource, book: Book) {\n        if (AppConfig.preDownloadNum == 0) return\n        val endIndex = min(\n            book.totalChapterNum - 1,\n            book.durChapterIndex.plus(AppConfig.preDownloadNum)\n        )\n        val cacheBook = CacheBook.getOrCreate(source, book)\n        cacheBook.addDownload(book.durChapterIndex, endIndex)\n    }\n\n    /**\n     * 缓存书籍\n     */\n    private fun cacheBook() {\n        if (AppConfig.preDownloadNum == 0) return\n        cacheBookJob?.cancel()\n        cacheBookJob = viewModelScope.launch(upTocPool) {\n            launch {\n                while (isActive && CacheBook.isRun) {\n                    //有目录更新是不缓存,优先更新目录,现在更多网站限制并发\n                    CacheBook.setWorkingState(waitUpTocBooks.isEmpty() && onUpTocBooks.isEmpty())\n                    delay(1000)\n                }\n            }\n            CacheBook.startProcessJob(upTocPool)\n        }\n    }\n\n    fun postLoad() {\n        execute {\n            if (appDb.httpTTSDao.count == 0) {\n                DefaultData.httpTTS.let {\n                    appDb.httpTTSDao.insert(*it.toTypedArray())\n                }\n            }\n        }\n    }\n\n    fun restoreWebDav(name: String) {\n        execute {\n            AppWebDav.restoreWebDav(name)\n        }\n    }\n\n    private fun deleteNotShelfBook() {\n        execute {\n            appDb.bookDao.deleteNotShelfBook()\n        }\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/main/bookshelf/BaseBookshelfFragment.kt",
    "content": "package io.legado.app.ui.main.bookshelf\n\nimport android.annotation.SuppressLint\nimport android.view.Menu\nimport android.view.MenuItem\nimport androidx.core.view.indices\nimport androidx.fragment.app.activityViewModels\nimport androidx.fragment.app.viewModels\nimport androidx.lifecycle.LiveData\nimport io.legado.app.R\nimport io.legado.app.base.VMBaseFragment\nimport io.legado.app.constant.EventBus\nimport io.legado.app.data.appDb\nimport io.legado.app.data.entities.Book\nimport io.legado.app.data.entities.BookGroup\nimport io.legado.app.databinding.DialogBookshelfConfigBinding\nimport io.legado.app.databinding.DialogEditTextBinding\nimport io.legado.app.help.DirectLinkUpload\nimport io.legado.app.help.config.AppConfig\nimport io.legado.app.lib.dialogs.alert\nimport io.legado.app.ui.about.AppLogDialog\nimport io.legado.app.ui.book.cache.CacheActivity\nimport io.legado.app.ui.book.group.GroupManageDialog\nimport io.legado.app.ui.book.import.local.ImportBookActivity\nimport io.legado.app.ui.book.import.remote.RemoteBookActivity\nimport io.legado.app.ui.book.manage.BookshelfManageActivity\nimport io.legado.app.ui.book.search.SearchActivity\nimport io.legado.app.ui.file.HandleFileContract\nimport io.legado.app.ui.main.MainFragmentInterface\nimport io.legado.app.ui.main.MainViewModel\nimport io.legado.app.ui.widget.dialog.WaitDialog\nimport io.legado.app.utils.checkByIndex\nimport io.legado.app.utils.getCheckedIndex\nimport io.legado.app.utils.isAbsUrl\nimport io.legado.app.utils.postEvent\nimport io.legado.app.utils.readText\nimport io.legado.app.utils.sendToClip\nimport io.legado.app.utils.showDialogFragment\nimport io.legado.app.utils.startActivity\nimport io.legado.app.utils.toastOnUi\n\nabstract class BaseBookshelfFragment(layoutId: Int) : VMBaseFragment<BookshelfViewModel>(layoutId),\n    MainFragmentInterface {\n\n    override val position: Int? get() = arguments?.getInt(\"position\")\n\n    val activityViewModel by activityViewModels<MainViewModel>()\n    override val viewModel by viewModels<BookshelfViewModel>()\n\n    private val importBookshelf = registerForActivityResult(HandleFileContract()) {\n        kotlin.runCatching {\n            it.uri?.readText(requireContext())?.let { text ->\n                viewModel.importBookshelf(text, groupId)\n            }\n        }.onFailure {\n            toastOnUi(it.localizedMessage ?: \"ERROR\")\n        }\n    }\n    private val exportResult = registerForActivityResult(HandleFileContract()) {\n        it.uri?.let { uri ->\n            alert(R.string.export_success) {\n                if (uri.toString().isAbsUrl()) {\n                    setMessage(DirectLinkUpload.getSummary())\n                }\n                val alertBinding = DialogEditTextBinding.inflate(layoutInflater).apply {\n                    editView.hint = getString(R.string.path)\n                    editView.setText(uri.toString())\n                }\n                customView { alertBinding.root }\n                okButton {\n                    requireContext().sendToClip(uri.toString())\n                }\n            }\n        }\n    }\n    abstract val groupId: Long\n    abstract val books: List<Book>\n    private var groupsLiveData: LiveData<List<BookGroup>>? = null\n    private val waitDialog by lazy {\n        WaitDialog(requireContext()).apply {\n            setOnCancelListener {\n                viewModel.addBookJob?.cancel()\n            }\n        }\n    }\n\n    abstract fun gotoTop()\n\n    override fun onCompatCreateOptionsMenu(menu: Menu) {\n        menuInflater.inflate(R.menu.main_bookshelf, menu)\n    }\n\n    override fun onCompatOptionsItemSelected(item: MenuItem) {\n        super.onCompatOptionsItemSelected(item)\n        when (item.itemId) {\n            R.id.menu_remote -> startActivity<RemoteBookActivity>()\n            R.id.menu_search -> startActivity<SearchActivity>()\n            R.id.menu_update_toc -> activityViewModel.upToc(books)\n            R.id.menu_bookshelf_layout -> configBookshelf()\n            R.id.menu_group_manage -> showDialogFragment<GroupManageDialog>()\n            R.id.menu_add_local -> startActivity<ImportBookActivity>()\n            R.id.menu_add_url -> showAddBookByUrlAlert()\n            R.id.menu_bookshelf_manage -> startActivity<BookshelfManageActivity> {\n                putExtra(\"groupId\", groupId)\n            }\n\n            R.id.menu_download -> startActivity<CacheActivity> {\n                putExtra(\"groupId\", groupId)\n            }\n\n            R.id.menu_export_bookshelf -> viewModel.exportBookshelf(books) { file ->\n                exportResult.launch {\n                    mode = HandleFileContract.EXPORT\n                    fileData =\n                        HandleFileContract.FileData(\"bookshelf.json\", file, \"application/json\")\n                }\n            }\n\n            R.id.menu_import_bookshelf -> importBookshelfAlert(groupId)\n            R.id.menu_log -> showDialogFragment<AppLogDialog>()\n        }\n    }\n\n    protected fun initBookGroupData() {\n        groupsLiveData?.removeObservers(viewLifecycleOwner)\n        groupsLiveData = appDb.bookGroupDao.show.apply {\n            observe(viewLifecycleOwner) {\n                upGroup(it)\n            }\n        }\n    }\n\n    abstract fun upGroup(data: List<BookGroup>)\n\n    abstract fun upSort()\n\n    override fun observeLiveBus() {\n        viewModel.addBookProgressLiveData.observe(this) { count ->\n            if (count < 0) {\n                waitDialog.dismiss()\n            } else {\n                waitDialog.setText(\"添加中... ($count)\")\n            }\n        }\n    }\n\n    @SuppressLint(\"InflateParams\")\n    fun showAddBookByUrlAlert() {\n        alert(titleResource = R.string.add_book_url) {\n            val alertBinding = DialogEditTextBinding.inflate(layoutInflater).apply {\n                editView.hint = \"url\"\n            }\n            customView { alertBinding.root }\n            okButton {\n                alertBinding.editView.text?.toString()?.let {\n                    waitDialog.setText(\"添加中...\")\n                    waitDialog.show()\n                    viewModel.addBookByUrl(it)\n                }\n            }\n            cancelButton()\n        }\n    }\n\n    @SuppressLint(\"InflateParams\")\n    fun configBookshelf() {\n        alert(titleResource = R.string.bookshelf_layout) {\n            var bookshelfLayout = AppConfig.bookshelfLayout\n            var bookshelfSort = AppConfig.bookshelfSort\n            val alertBinding =\n                DialogBookshelfConfigBinding.inflate(layoutInflater)\n                    .apply {\n                        if (AppConfig.bookGroupStyle !in 0..<spGroupStyle.count) {\n                            AppConfig.bookGroupStyle = 0\n                        }\n                        if (bookshelfLayout !in rgLayout.indices) {\n                            bookshelfLayout = 0\n                            AppConfig.bookshelfLayout = 0\n                        }\n                        if (bookshelfSort !in rgSort.indices) {\n                            bookshelfSort = 0\n                            AppConfig.bookshelfSort = 0\n                        }\n                        spGroupStyle.setSelection(AppConfig.bookGroupStyle)\n                        swShowUnread.isChecked = AppConfig.showUnread\n                        swShowLastUpdateTime.isChecked = AppConfig.showLastUpdateTime\n                        swShowWaitUpBooks.isChecked = AppConfig.showWaitUpCount\n                        swShowBookshelfFastScroller.isChecked = AppConfig.showBookshelfFastScroller\n                        rgLayout.checkByIndex(bookshelfLayout)\n                        rgSort.checkByIndex(bookshelfSort)\n                    }\n            customView { alertBinding.root }\n            okButton {\n                alertBinding.apply {\n                    var notifyMain = false\n                    var recreate = false\n                    if (AppConfig.bookGroupStyle != spGroupStyle.selectedItemPosition) {\n                        AppConfig.bookGroupStyle = spGroupStyle.selectedItemPosition\n                        notifyMain = true\n                    }\n                    if (AppConfig.showUnread != swShowUnread.isChecked) {\n                        AppConfig.showUnread = swShowUnread.isChecked\n                        postEvent(EventBus.BOOKSHELF_REFRESH, \"\")\n                    }\n                    if (AppConfig.showLastUpdateTime != swShowLastUpdateTime.isChecked) {\n                        AppConfig.showLastUpdateTime = swShowLastUpdateTime.isChecked\n                        postEvent(EventBus.BOOKSHELF_REFRESH, \"\")\n                    }\n                    if (AppConfig.showWaitUpCount != swShowWaitUpBooks.isChecked) {\n                        AppConfig.showWaitUpCount = swShowWaitUpBooks.isChecked\n                        activityViewModel.postUpBooksLiveData(true)\n                    }\n                    if (AppConfig.showBookshelfFastScroller != swShowBookshelfFastScroller.isChecked) {\n                        AppConfig.showBookshelfFastScroller = swShowBookshelfFastScroller.isChecked\n                        postEvent(EventBus.BOOKSHELF_REFRESH, \"\")\n                    }\n                    if (bookshelfSort != rgSort.getCheckedIndex()) {\n                        AppConfig.bookshelfSort = rgSort.getCheckedIndex()\n                        upSort()\n                    }\n                    if (bookshelfLayout != rgLayout.getCheckedIndex()) {\n                        AppConfig.bookshelfLayout = rgLayout.getCheckedIndex()\n                        if (AppConfig.bookshelfLayout == 0) {\n                            activityViewModel.booksGridRecycledViewPool.clear()\n                        } else {\n                            activityViewModel.booksListRecycledViewPool.clear()\n                        }\n                        recreate = true\n                    }\n                    if (recreate) {\n                        postEvent(EventBus.RECREATE, \"\")\n                    } else if (notifyMain) {\n                        postEvent(EventBus.NOTIFY_MAIN, false)\n                    }\n                }\n            }\n            cancelButton()\n        }\n    }\n\n\n    private fun importBookshelfAlert(groupId: Long) {\n        alert(titleResource = R.string.import_bookshelf) {\n            val alertBinding = DialogEditTextBinding.inflate(layoutInflater).apply {\n                editView.hint = \"url/json\"\n            }\n            customView { alertBinding.root }\n            okButton {\n                alertBinding.editView.text?.toString()?.let {\n                    viewModel.importBookshelf(it, groupId)\n                }\n            }\n            cancelButton()\n            neutralButton(R.string.select_file) {\n                importBookshelf.launch {\n                    mode = HandleFileContract.FILE\n                    allowExtensions = arrayOf(\"txt\", \"json\")\n                }\n            }\n        }\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/main/bookshelf/BookshelfViewModel.kt",
    "content": "package io.legado.app.ui.main.bookshelf\n\nimport android.app.Application\nimport androidx.lifecycle.MutableLiveData\nimport com.google.gson.stream.JsonWriter\nimport io.legado.app.R\nimport io.legado.app.base.BaseViewModel\nimport io.legado.app.constant.AppLog\nimport io.legado.app.data.appDb\nimport io.legado.app.data.entities.Book\nimport io.legado.app.data.entities.BookSourcePart\nimport io.legado.app.exception.NoStackTraceException\nimport io.legado.app.help.config.AppConfig\nimport io.legado.app.help.coroutine.Coroutine\nimport io.legado.app.help.http.decompressed\nimport io.legado.app.help.http.newCallResponseBody\nimport io.legado.app.help.http.okHttpClient\nimport io.legado.app.help.http.text\nimport io.legado.app.model.webBook.WebBook\nimport io.legado.app.utils.FileUtils\nimport io.legado.app.utils.GSON\nimport io.legado.app.utils.NetworkUtils\nimport io.legado.app.utils.fromJsonArray\nimport io.legado.app.utils.isAbsUrl\nimport io.legado.app.utils.isJsonArray\nimport io.legado.app.utils.printOnDebug\nimport io.legado.app.utils.toastOnUi\nimport kotlinx.coroutines.sync.Semaphore\nimport kotlinx.coroutines.sync.withPermit\nimport java.io.File\nimport java.io.FileOutputStream\nimport java.io.OutputStreamWriter\n\nclass BookshelfViewModel(application: Application) : BaseViewModel(application) {\n    val addBookProgressLiveData = MutableLiveData(-1)\n    var addBookJob: Coroutine<*>? = null\n\n    fun addBookByUrl(bookUrls: String) {\n        var successCount = 0\n        addBookJob = execute {\n            val hasBookUrlPattern: List<BookSourcePart> by lazy {\n                appDb.bookSourceDao.hasBookUrlPattern\n            }\n            val urls = bookUrls.split(\"\\n\")\n            for (url in urls) {\n                val bookUrl = url.trim()\n                if (bookUrl.isEmpty()) continue\n                if (appDb.bookDao.getBook(bookUrl) != null) {\n                    successCount++\n                    continue\n                }\n                val baseUrl = NetworkUtils.getBaseUrl(bookUrl) ?: continue\n                var source = appDb.bookSourceDao.getBookSourceAddBook(baseUrl)\n                if (source == null) {\n                    for (bookSource in hasBookUrlPattern) {\n                        try {\n                            val bs = bookSource.getBookSource()!!\n                            if (bookUrl.matches(bs.bookUrlPattern!!.toRegex())) {\n                                source = bs\n                                break\n                            }\n                        } catch (_: Exception) {\n                        }\n                    }\n                }\n                val bookSource = source ?: continue\n                val book = Book(\n                    bookUrl = bookUrl,\n                    origin = bookSource.bookSourceUrl,\n                    originName = bookSource.bookSourceName\n                )\n                kotlin.runCatching {\n                    WebBook.getBookInfoAwait(bookSource, book)\n                }.onSuccess {\n                    val dbBook = appDb.bookDao.getBook(it.name, it.author)\n                    if (dbBook != null) {\n                        val toc = WebBook.getChapterListAwait(bookSource, it).getOrThrow()\n                        dbBook.migrateTo(it, toc)\n                        appDb.bookDao.insert(it)\n                        appDb.bookChapterDao.insert(*toc.toTypedArray())\n                    } else {\n                        it.order = appDb.bookDao.minOrder - 1\n                        it.save()\n                    }\n                    successCount++\n                    addBookProgressLiveData.postValue(successCount)\n                }\n            }\n        }.onSuccess {\n            if (successCount > 0) {\n                context.toastOnUi(R.string.success)\n            } else {\n                context.toastOnUi(\"添加网址失败\")\n            }\n        }.onError {\n            AppLog.put(\"添加网址出错\\n${it.localizedMessage}\", it, true)\n        }.onFinally {\n            addBookProgressLiveData.postValue(-1)\n        }\n    }\n\n    fun exportBookshelf(books: List<Book>?, success: (file: File) -> Unit) {\n        execute {\n            books?.let {\n                val path = \"${context.filesDir}/books.json\"\n                FileUtils.delete(path)\n                val file = FileUtils.createFileWithReplace(path)\n                FileOutputStream(file).use { out ->\n                    val writer = JsonWriter(OutputStreamWriter(out, \"UTF-8\"))\n                    writer.setIndent(\"  \")\n                    writer.beginArray()\n                    books.forEach {\n                        val bookMap = hashMapOf<String, String?>()\n                        bookMap[\"name\"] = it.name\n                        bookMap[\"author\"] = it.author\n                        bookMap[\"intro\"] = it.getDisplayIntro()\n                        GSON.toJson(bookMap, bookMap::class.java, writer)\n                    }\n                    writer.endArray()\n                    writer.close()\n                }\n                file\n            } ?: throw NoStackTraceException(\"书籍不能为空\")\n        }.onSuccess {\n            success(it)\n        }.onError {\n            context.toastOnUi(\"导出书籍出错\\n${it.localizedMessage}\")\n        }\n    }\n\n    fun importBookshelf(str: String, groupId: Long) {\n        execute {\n            val text = str.trim()\n            when {\n                text.isAbsUrl() -> {\n                    okHttpClient.newCallResponseBody {\n                        url(text)\n                    }.decompressed().text().let {\n                        importBookshelf(it, groupId)\n                    }\n                }\n\n                text.isJsonArray() -> {\n                    importBookshelfByJson(text, groupId)\n                }\n\n                else -> {\n                    throw NoStackTraceException(\"格式不对\")\n                }\n            }\n        }.onError {\n            context.toastOnUi(it.localizedMessage ?: \"ERROR\")\n        }\n    }\n\n    private fun importBookshelfByJson(json: String, groupId: Long) {\n        execute {\n            val bookSourceParts = appDb.bookSourceDao.allEnabledPart\n            val semaphore = Semaphore(AppConfig.threadCount)\n            GSON.fromJsonArray<Map<String, String?>>(json).getOrThrow().forEach { bookInfo ->\n                val name = bookInfo[\"name\"] ?: \"\"\n                val author = bookInfo[\"author\"] ?: \"\"\n                if (name.isEmpty() || appDb.bookDao.has(name, author)) {\n                    return@forEach\n                }\n                semaphore.withPermit {\n                    WebBook.preciseSearch(\n                        this, bookSourceParts, name, author,\n                        semaphore = semaphore\n                    ).onSuccess {\n                        val book = it.first\n                        if (groupId > 0) {\n                            book.group = groupId\n                        }\n                        book.save()\n                    }.onError { e ->\n                        context.toastOnUi(e.localizedMessage)\n                    }\n                }\n            }\n        }.onError {\n            it.printOnDebug()\n        }.onFinally {\n            context.toastOnUi(R.string.success)\n        }\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/main/bookshelf/style1/BookshelfFragment1.kt",
    "content": "@file:Suppress(\"DEPRECATION\")\n\npackage io.legado.app.ui.main.bookshelf.style1\n\nimport android.os.Bundle\nimport android.view.View\nimport android.view.ViewGroup\nimport androidx.appcompat.widget.SearchView\nimport androidx.fragment.app.Fragment\nimport androidx.fragment.app.FragmentManager\nimport androidx.fragment.app.FragmentStatePagerAdapter\nimport com.google.android.material.tabs.TabLayout\nimport io.legado.app.R\nimport io.legado.app.data.appDb\nimport io.legado.app.data.entities.Book\nimport io.legado.app.data.entities.BookGroup\nimport io.legado.app.databinding.FragmentBookshelf1Binding\nimport io.legado.app.help.config.AppConfig\nimport io.legado.app.lib.theme.accentColor\nimport io.legado.app.lib.theme.primaryColor\nimport io.legado.app.ui.book.group.GroupEditDialog\nimport io.legado.app.ui.book.search.SearchActivity\nimport io.legado.app.ui.main.bookshelf.BaseBookshelfFragment\nimport io.legado.app.ui.main.bookshelf.style1.books.BooksFragment\nimport io.legado.app.utils.isCreated\nimport io.legado.app.utils.setEdgeEffectColor\nimport io.legado.app.utils.showDialogFragment\nimport io.legado.app.utils.toastOnUi\nimport io.legado.app.utils.viewbindingdelegate.viewBinding\nimport kotlin.collections.set\n\n/**\n * 书架界面\n */\nclass BookshelfFragment1() : BaseBookshelfFragment(R.layout.fragment_bookshelf1),\n    TabLayout.OnTabSelectedListener,\n    SearchView.OnQueryTextListener {\n\n    constructor(position: Int) : this() {\n        val bundle = Bundle()\n        bundle.putInt(\"position\", position)\n        arguments = bundle\n    }\n\n    private val binding by viewBinding(FragmentBookshelf1Binding::bind)\n    private val adapter by lazy { TabFragmentPageAdapter(childFragmentManager) }\n    private val tabLayout: TabLayout by lazy {\n        binding.titleBar.findViewById(R.id.tab_layout)\n    }\n    private val bookGroups = mutableListOf<BookGroup>()\n    private val fragmentMap = hashMapOf<Long, BooksFragment>()\n    override val groupId: Long get() = selectedGroup?.groupId ?: 0\n\n    override val books: List<Book>\n        get() {\n            val fragment = fragmentMap[groupId]\n            return fragment?.getBooks() ?: emptyList()\n        }\n\n    override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) {\n        setSupportToolbar(binding.titleBar.toolbar)\n        initView()\n        initBookGroupData()\n    }\n\n    private val selectedGroup: BookGroup?\n        get() = bookGroups.getOrNull(tabLayout.selectedTabPosition)\n\n    private fun initView() {\n        binding.viewPagerBookshelf.setEdgeEffectColor(primaryColor)\n        tabLayout.isTabIndicatorFullWidth = false\n        tabLayout.tabMode = TabLayout.MODE_SCROLLABLE\n        tabLayout.setSelectedTabIndicatorColor(requireContext().accentColor)\n        tabLayout.setupWithViewPager(binding.viewPagerBookshelf)\n        binding.viewPagerBookshelf.offscreenPageLimit = 1\n        binding.viewPagerBookshelf.adapter = adapter\n    }\n\n    override fun onQueryTextSubmit(query: String?): Boolean {\n        SearchActivity.start(requireContext(), query)\n        return false\n    }\n\n    override fun onQueryTextChange(newText: String?): Boolean {\n        return false\n    }\n\n    @Synchronized\n    override fun upGroup(data: List<BookGroup>) {\n        if (data.isEmpty()) {\n            appDb.bookGroupDao.enableGroup(BookGroup.IdAll)\n        } else {\n            if (data != bookGroups) {\n                bookGroups.clear()\n                bookGroups.addAll(data)\n                adapter.notifyDataSetChanged()\n                selectLastTab()\n                for (i in 0 until adapter.count) {\n                    tabLayout.getTabAt(i)?.view?.setOnLongClickListener {\n                        showDialogFragment(GroupEditDialog(bookGroups[i]))\n                        true\n                    }\n                }\n            }\n        }\n    }\n\n    override fun upSort() {\n        adapter.notifyDataSetChanged()\n    }\n\n    private fun selectLastTab() {\n        tabLayout.post {\n            tabLayout.removeOnTabSelectedListener(this)\n            tabLayout.getTabAt(AppConfig.saveTabPosition)?.select()\n            tabLayout.addOnTabSelectedListener(this)\n        }\n    }\n\n    override fun onTabReselected(tab: TabLayout.Tab) {\n        selectedGroup?.let { group ->\n            fragmentMap[group.groupId]?.let {\n                toastOnUi(\"${group.groupName}(${it.getBooksCount()})\")\n            }\n        }\n    }\n\n    override fun onTabUnselected(tab: TabLayout.Tab) = Unit\n\n    override fun onTabSelected(tab: TabLayout.Tab) {\n        AppConfig.saveTabPosition = tab.position\n    }\n\n    override fun gotoTop() {\n        fragmentMap[groupId]?.gotoTop()\n    }\n\n    private inner class TabFragmentPageAdapter(fm: FragmentManager) :\n        FragmentStatePagerAdapter(fm, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {\n\n        override fun getPageTitle(position: Int): CharSequence {\n            return bookGroups[position].groupName\n        }\n\n        /**\n         * 确定视图位置是否更改时调用\n         * @return POSITION_NONE 已更改,刷新视图. POSITION_UNCHANGED 未更改,不刷新视图\n         */\n        override fun getItemPosition(any: Any): Int {\n            val fragment = any as BooksFragment\n            val position = fragment.position\n            val group = bookGroups.getOrNull(position)\n            if (fragment.groupId != group?.groupId) {\n                return POSITION_NONE\n            }\n            val bookSort = group.getRealBookSort()\n            fragment.setEnableRefresh(group.enableRefresh)\n            if (fragment.bookSort != bookSort) {\n                fragment.upBookSort(bookSort)\n            }\n            return POSITION_UNCHANGED\n        }\n\n        override fun getItem(position: Int): Fragment {\n            val group = bookGroups[position]\n            return BooksFragment(position, group)\n        }\n\n        override fun getCount(): Int {\n            return bookGroups.size\n        }\n\n        override fun instantiateItem(container: ViewGroup, position: Int): Any {\n            var fragment = super.instantiateItem(container, position) as BooksFragment\n            val group = bookGroups[position]\n            /**\n             * Activity recreate 会复用之前的 Fragment，不正确的需要重新创建\n             */\n            if (fragment.isCreated && getItemPosition(fragment) == POSITION_NONE) {\n                destroyItem(container, position, fragment)\n                fragment = super.instantiateItem(container, position) as BooksFragment\n            }\n            fragmentMap[group.groupId] = fragment\n            return fragment\n        }\n\n    }\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/main/bookshelf/style1/books/BaseBooksAdapter.kt",
    "content": "package io.legado.app.ui.main.bookshelf.style1.books\n\nimport android.content.Context\nimport androidx.core.os.bundleOf\nimport androidx.recyclerview.widget.DiffUtil\nimport androidx.viewbinding.ViewBinding\nimport io.legado.app.base.adapter.DiffRecyclerAdapter\nimport io.legado.app.base.adapter.ItemViewHolder\nimport io.legado.app.data.entities.Book\n\nabstract class BaseBooksAdapter<VB : ViewBinding>(context: Context) :\n    DiffRecyclerAdapter<Book, VB>(context) {\n\n    override val keepScrollPosition = true\n\n    override val diffItemCallback: DiffUtil.ItemCallback<Book> =\n        object : DiffUtil.ItemCallback<Book>() {\n\n            override fun areItemsTheSame(oldItem: Book, newItem: Book): Boolean {\n                return oldItem.name == newItem.name\n                        && oldItem.author == newItem.author\n            }\n\n            override fun areContentsTheSame(oldItem: Book, newItem: Book): Boolean {\n                return when {\n                    oldItem.durChapterTime != newItem.durChapterTime -> false\n                    oldItem.name != newItem.name -> false\n                    oldItem.author != newItem.author -> false\n                    oldItem.durChapterTitle != newItem.durChapterTitle -> false\n                    oldItem.latestChapterTitle != newItem.latestChapterTitle -> false\n                    oldItem.lastCheckCount != newItem.lastCheckCount -> false\n                    oldItem.getDisplayCover() != newItem.getDisplayCover() -> false\n                    oldItem.getUnreadChapterNum() != newItem.getUnreadChapterNum() -> false\n                    else -> true\n                }\n            }\n\n            override fun getChangePayload(oldItem: Book, newItem: Book): Any? {\n                val bundle = bundleOf()\n                if (oldItem.name != newItem.name) {\n                    bundle.putString(\"name\", newItem.name)\n                }\n                if (oldItem.author != newItem.author) {\n                    bundle.putString(\"author\", newItem.author)\n                }\n                if (oldItem.durChapterTitle != newItem.durChapterTitle) {\n                    bundle.putString(\"dur\", newItem.durChapterTitle)\n                }\n                if (oldItem.latestChapterTitle != newItem.latestChapterTitle) {\n                    bundle.putString(\"last\", newItem.latestChapterTitle)\n                }\n                if (oldItem.getDisplayCover() != newItem.getDisplayCover()) {\n                    bundle.putString(\"cover\", newItem.getDisplayCover())\n                }\n                if (oldItem.lastCheckCount != newItem.lastCheckCount\n                    || oldItem.durChapterTime != newItem.durChapterTime\n                    || oldItem.getUnreadChapterNum() != newItem.getUnreadChapterNum()\n                    || oldItem.lastCheckCount != newItem.lastCheckCount\n                ) {\n                    bundle.putBoolean(\"refresh\", true)\n                }\n                if (oldItem.latestChapterTime != newItem.latestChapterTime) {\n                    bundle.putBoolean(\"lastUpdateTime\", true)\n                }\n                if (bundle.isEmpty) return null\n                return bundle\n            }\n\n        }\n\n    override fun onViewRecycled(holder: ItemViewHolder) {\n        super.onViewRecycled(holder)\n        holder.itemView.setOnClickListener(null)\n        holder.itemView.setOnLongClickListener(null)\n    }\n\n    fun notification(bookUrl: String) {\n        getItems().forEachIndexed { i, it ->\n            if (it.bookUrl == bookUrl) {\n                notifyItemChanged(i, bundleOf(Pair(\"refresh\", null), Pair(\"lastUpdateTime\", null)))\n                return\n            }\n        }\n    }\n\n    fun upLastUpdateTime() {\n        notifyItemRangeChanged(0, itemCount, bundleOf(Pair(\"lastUpdateTime\", null)))\n    }\n\n    interface CallBack {\n        fun open(book: Book)\n        fun openBookInfo(book: Book)\n        fun isUpdate(bookUrl: String): Boolean\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/main/bookshelf/style1/books/BooksAdapterGrid.kt",
    "content": "package io.legado.app.ui.main.bookshelf.style1.books\n\nimport android.content.Context\nimport android.os.Bundle\nimport android.view.ViewGroup\nimport io.legado.app.base.adapter.ItemViewHolder\nimport io.legado.app.data.entities.Book\nimport io.legado.app.databinding.ItemBookshelfGridBinding\nimport io.legado.app.help.book.isLocal\nimport io.legado.app.help.config.AppConfig\nimport io.legado.app.utils.invisible\nimport splitties.views.onLongClick\n\nclass BooksAdapterGrid(context: Context, private val callBack: CallBack) :\n    BaseBooksAdapter<ItemBookshelfGridBinding>(context) {\n\n    override fun getViewBinding(parent: ViewGroup): ItemBookshelfGridBinding {\n        return ItemBookshelfGridBinding.inflate(inflater, parent, false)\n    }\n\n    override fun convert(\n        holder: ItemViewHolder,\n        binding: ItemBookshelfGridBinding,\n        item: Book,\n        payloads: MutableList<Any>\n    ) = binding.run {\n        if (payloads.isEmpty()) {\n            tvName.text = item.name\n            ivCover.load(item.getDisplayCover(), item.name, item.author, false, item.origin)\n            upRefresh(binding, item)\n        } else {\n            for (i in payloads.indices) {\n                val bundle = payloads[i] as Bundle\n                bundle.keySet().forEach {\n                    when (it) {\n                        \"name\" -> tvName.text = item.name\n                        \"cover\" -> ivCover.load(item.getDisplayCover(), item.name, item.author, false, item.origin)\n                        \"refresh\" -> upRefresh(binding, item)\n                    }\n                }\n            }\n        }\n    }\n\n    private fun upRefresh(binding: ItemBookshelfGridBinding, item: Book) {\n        if (!item.isLocal && callBack.isUpdate(item.bookUrl)) {\n            binding.bvUnread.invisible()\n            binding.rlLoading.visible()\n        } else {\n            binding.rlLoading.inVisible()\n            if (AppConfig.showUnread) {\n                binding.bvUnread.setBadgeCount(item.getUnreadChapterNum())\n                binding.bvUnread.setHighlight(item.lastCheckCount > 0)\n            } else {\n                binding.bvUnread.invisible()\n            }\n        }\n    }\n\n    override fun registerListener(holder: ItemViewHolder, binding: ItemBookshelfGridBinding) {\n        holder.itemView.apply {\n            setOnClickListener {\n                getItem(holder.layoutPosition)?.let {\n                    callBack.open(it)\n                }\n            }\n\n            onLongClick {\n                getItem(holder.layoutPosition)?.let {\n                    callBack.openBookInfo(it)\n                }\n            }\n        }\n    }\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/main/bookshelf/style1/books/BooksAdapterList.kt",
    "content": "package io.legado.app.ui.main.bookshelf.style1.books\n\nimport android.content.Context\nimport android.os.Bundle\nimport android.view.ViewGroup\nimport androidx.fragment.app.Fragment\nimport androidx.lifecycle.Lifecycle\nimport io.legado.app.base.adapter.ItemViewHolder\nimport io.legado.app.data.entities.Book\nimport io.legado.app.databinding.ItemBookshelfListBinding\nimport io.legado.app.help.book.isLocal\nimport io.legado.app.help.config.AppConfig\nimport io.legado.app.utils.invisible\nimport io.legado.app.utils.toTimeAgo\nimport splitties.views.onLongClick\n\nclass BooksAdapterList(\n    context: Context,\n    private val fragment: Fragment,\n    private val callBack: CallBack,\n    private val lifecycle: Lifecycle\n) : BaseBooksAdapter<ItemBookshelfListBinding>(context) {\n\n    override fun getViewBinding(parent: ViewGroup): ItemBookshelfListBinding {\n        return ItemBookshelfListBinding.inflate(inflater, parent, false)\n    }\n\n    override fun convert(\n        holder: ItemViewHolder,\n        binding: ItemBookshelfListBinding,\n        item: Book,\n        payloads: MutableList<Any>\n    ) = binding.run {\n        if (payloads.isEmpty()) {\n            tvName.text = item.name\n            tvAuthor.text = item.author\n            tvRead.text = item.durChapterTitle\n            tvLast.text = item.latestChapterTitle\n            ivCover.load(item.getDisplayCover(), item.name, item.author, false, item.origin)\n            upRefresh(binding, item)\n            upLastUpdateTime(binding, item)\n        } else {\n            for (i in payloads.indices) {\n                val bundle = payloads[i] as Bundle\n                bundle.keySet().forEach {\n                    when (it) {\n                        \"name\" -> tvName.text = item.name\n                        \"author\" -> tvAuthor.text = item.author\n                        \"dur\" -> tvRead.text = item.durChapterTitle\n                        \"last\" -> tvLast.text = item.latestChapterTitle\n                        \"cover\" -> ivCover.load(\n                            item.getDisplayCover(),\n                            item.name,\n                            item.author,\n                            false,\n                            item.origin,\n                            fragment,\n                            lifecycle\n                        )\n\n                        \"refresh\" -> upRefresh(binding, item)\n                        \"lastUpdateTime\" -> upLastUpdateTime(binding, item)\n                    }\n                }\n            }\n        }\n    }\n\n    private fun upRefresh(binding: ItemBookshelfListBinding, item: Book) {\n        if (!item.isLocal && callBack.isUpdate(item.bookUrl)) {\n            binding.bvUnread.invisible()\n            binding.rlLoading.visible()\n        } else {\n            binding.rlLoading.gone()\n            if (AppConfig.showUnread) {\n                binding.bvUnread.setHighlight(item.lastCheckCount > 0)\n                binding.bvUnread.setBadgeCount(item.getUnreadChapterNum())\n            } else {\n                binding.bvUnread.invisible()\n            }\n        }\n    }\n\n    private fun upLastUpdateTime(binding: ItemBookshelfListBinding, item: Book) {\n        if (AppConfig.showLastUpdateTime && !item.isLocal) {\n            val time = item.latestChapterTime.toTimeAgo()\n            if (binding.tvLastUpdateTime.text != time) {\n                binding.tvLastUpdateTime.text = time\n            }\n        } else {\n            binding.tvLastUpdateTime.text = \"\"\n        }\n    }\n\n    override fun registerListener(holder: ItemViewHolder, binding: ItemBookshelfListBinding) {\n        holder.itemView.apply {\n            setOnClickListener {\n                getItem(holder.layoutPosition)?.let {\n                    callBack.open(it)\n                }\n            }\n\n            onLongClick {\n                getItem(holder.layoutPosition)?.let {\n                    callBack.openBookInfo(it)\n                }\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/main/bookshelf/style1/books/BooksFragment.kt",
    "content": "package io.legado.app.ui.main.bookshelf.style1.books\n\nimport android.annotation.SuppressLint\nimport android.os.Bundle\nimport android.view.View\nimport android.view.ViewConfiguration\nimport androidx.core.view.isGone\nimport androidx.fragment.app.activityViewModels\nimport androidx.lifecycle.Lifecycle\nimport androidx.lifecycle.lifecycleScope\nimport androidx.lifecycle.repeatOnLifecycle\nimport androidx.recyclerview.widget.GridLayoutManager\nimport androidx.recyclerview.widget.LinearLayoutManager\nimport androidx.recyclerview.widget.RecyclerView\nimport androidx.recyclerview.widget.RecyclerView.Adapter.StateRestorationPolicy\nimport io.legado.app.R\nimport io.legado.app.base.BaseFragment\nimport io.legado.app.constant.AppLog\nimport io.legado.app.constant.EventBus\nimport io.legado.app.data.AppDatabase\nimport io.legado.app.data.appDb\nimport io.legado.app.data.entities.Book\nimport io.legado.app.data.entities.BookGroup\nimport io.legado.app.databinding.FragmentBooksBinding\nimport io.legado.app.help.config.AppConfig\nimport io.legado.app.lib.theme.accentColor\nimport io.legado.app.lib.theme.primaryColor\nimport io.legado.app.ui.book.info.BookInfoActivity\nimport io.legado.app.ui.main.MainViewModel\nimport io.legado.app.utils.cnCompare\nimport io.legado.app.utils.flowWithLifecycleAndDatabaseChangeFirst\nimport io.legado.app.utils.observeEvent\nimport io.legado.app.utils.setEdgeEffectColor\nimport io.legado.app.utils.startActivity\nimport io.legado.app.utils.startActivityForBook\nimport io.legado.app.utils.viewbindingdelegate.viewBinding\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.Job\nimport kotlinx.coroutines.delay\nimport kotlinx.coroutines.flow.catch\nimport kotlinx.coroutines.flow.conflate\nimport kotlinx.coroutines.flow.flowOn\nimport kotlinx.coroutines.flow.map\nimport kotlinx.coroutines.isActive\nimport kotlinx.coroutines.launch\nimport kotlin.math.max\n\n/**\n * 书架界面\n */\nclass BooksFragment() : BaseFragment(R.layout.fragment_books),\n    BaseBooksAdapter.CallBack {\n\n    constructor(position: Int, group: BookGroup) : this() {\n        val bundle = Bundle()\n        bundle.putInt(\"position\", position)\n        bundle.putLong(\"groupId\", group.groupId)\n        bundle.putInt(\"bookSort\", group.getRealBookSort())\n        bundle.putBoolean(\"enableRefresh\", group.enableRefresh)\n        arguments = bundle\n    }\n\n    private val binding by viewBinding(FragmentBooksBinding::bind)\n    private val activityViewModel by activityViewModels<MainViewModel>()\n    private val bookshelfLayout by lazy { AppConfig.bookshelfLayout }\n    private val booksAdapter: BaseBooksAdapter<*> by lazy {\n        if (bookshelfLayout == 0) {\n            BooksAdapterList(requireContext(), this, this, viewLifecycleOwner.lifecycle)\n        } else {\n            BooksAdapterGrid(requireContext(), this)\n        }\n    }\n    private var booksFlowJob: Job? = null\n    var position = 0\n        private set\n    var groupId = -1L\n        private set\n    var bookSort = 0\n        private set\n    private var upLastUpdateTimeJob: Job? = null\n    private var enableRefresh = true\n\n    override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) {\n        arguments?.let {\n            position = it.getInt(\"position\", 0)\n            groupId = it.getLong(\"groupId\", -1)\n            bookSort = it.getInt(\"bookSort\", 0)\n            enableRefresh = it.getBoolean(\"enableRefresh\", true)\n            binding.refreshLayout.isEnabled = enableRefresh\n        }\n        initRecyclerView()\n        upRecyclerData()\n    }\n\n    private fun initRecyclerView() {\n        binding.rvBookshelf.setEdgeEffectColor(primaryColor)\n        upFastScrollerBar()\n        binding.refreshLayout.setColorSchemeColors(accentColor)\n        binding.refreshLayout.setOnRefreshListener {\n            binding.refreshLayout.isRefreshing = false\n            activityViewModel.upToc(booksAdapter.getItems())\n        }\n        if (bookshelfLayout == 0) {\n            binding.rvBookshelf.layoutManager = LinearLayoutManager(context)\n        } else {\n            binding.rvBookshelf.layoutManager = GridLayoutManager(context, bookshelfLayout + 2)\n        }\n        if (bookshelfLayout == 0) {\n            binding.rvBookshelf.setRecycledViewPool(activityViewModel.booksListRecycledViewPool)\n        } else {\n            binding.rvBookshelf.setRecycledViewPool(activityViewModel.booksGridRecycledViewPool)\n        }\n        booksAdapter.stateRestorationPolicy = StateRestorationPolicy.PREVENT_WHEN_EMPTY\n        binding.rvBookshelf.adapter = booksAdapter\n        booksAdapter.registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() {\n            override fun onItemRangeInserted(positionStart: Int, itemCount: Int) {\n                val layoutManager = binding.rvBookshelf.layoutManager\n                if (positionStart == 0 && itemCount == 1 && layoutManager is LinearLayoutManager) {\n                    val scrollTo = layoutManager.findFirstVisibleItemPosition() - itemCount\n                    binding.rvBookshelf.scrollToPosition(max(0, scrollTo))\n                }\n            }\n\n            override fun onItemRangeMoved(fromPosition: Int, toPosition: Int, itemCount: Int) {\n                val layoutManager = binding.rvBookshelf.layoutManager\n                if (toPosition == 0 && itemCount == 1 && layoutManager is LinearLayoutManager) {\n                    val scrollTo = layoutManager.findFirstVisibleItemPosition() - itemCount\n                    binding.rvBookshelf.scrollToPosition(max(0, scrollTo))\n                }\n            }\n        })\n        startLastUpdateTimeJob()\n    }\n\n    private fun upFastScrollerBar() {\n        val showBookshelfFastScroller = AppConfig.showBookshelfFastScroller\n        binding.rvBookshelf.setFastScrollEnabled(showBookshelfFastScroller)\n        if (showBookshelfFastScroller) {\n            binding.rvBookshelf.scrollBarSize = 0\n        } else {\n            binding.rvBookshelf.scrollBarSize =\n                ViewConfiguration.get(requireContext()).scaledScrollBarSize\n        }\n    }\n\n    fun upBookSort(sort: Int) {\n        binding.root.post {\n            arguments?.putInt(\"bookSort\", sort)\n            bookSort = sort\n            upRecyclerData()\n        }\n    }\n\n    fun setEnableRefresh(enable: Boolean) {\n        enableRefresh = enable\n        binding.refreshLayout.isEnabled = enable\n    }\n\n    /**\n     * 更新书籍列表信息\n     */\n    private fun upRecyclerData() {\n        booksFlowJob?.cancel()\n        booksFlowJob = viewLifecycleOwner.lifecycleScope.launch {\n            appDb.bookDao.flowByGroup(groupId).map { list ->\n                //排序\n                when (bookSort) {\n                    1 -> list.sortedByDescending { it.latestChapterTime }\n                    2 -> list.sortedWith { o1, o2 ->\n                        o1.name.cnCompare(o2.name)\n                    }\n\n                    3 -> list.sortedBy { it.order }\n\n                    // 综合排序 issue #3192\n                    4 -> list.sortedByDescending {\n                        max(it.latestChapterTime, it.durChapterTime)\n                    }\n                    // 按作者排序\n                    5 -> list.sortedWith { o1, o2 ->\n                        o1.author.cnCompare(o2.author)\n                    }\n\n                    else -> list.sortedByDescending { it.durChapterTime }\n                }\n            }.flowWithLifecycleAndDatabaseChangeFirst(\n                viewLifecycleOwner.lifecycle,\n                Lifecycle.State.RESUMED,\n                AppDatabase.BOOK_TABLE_NAME\n            ).catch {\n                AppLog.put(\"书架更新出错\", it)\n            }.conflate().flowOn(Dispatchers.Default).collect { list ->\n                binding.tvEmptyMsg.isGone = list.isNotEmpty()\n                binding.refreshLayout.isEnabled = enableRefresh && list.isNotEmpty()\n                booksAdapter.setItems(list)\n                delay(100)\n            }\n        }\n    }\n\n    private fun startLastUpdateTimeJob() {\n        upLastUpdateTimeJob?.cancel()\n        if (!AppConfig.showLastUpdateTime || bookshelfLayout != 0) {\n            return\n        }\n        upLastUpdateTimeJob = viewLifecycleOwner.lifecycleScope.launch {\n            repeatOnLifecycle(Lifecycle.State.RESUMED) {\n                while (isActive) {\n                    booksAdapter.upLastUpdateTime()\n                    delay(30 * 1000)\n                }\n            }\n        }\n    }\n\n    fun getBooks(): List<Book> {\n        return booksAdapter.getItems()\n    }\n\n    fun gotoTop() {\n        if (AppConfig.isEInkMode) {\n            binding.rvBookshelf.scrollToPosition(0)\n        } else {\n            binding.rvBookshelf.smoothScrollToPosition(0)\n        }\n    }\n\n    fun getBooksCount(): Int {\n        return booksAdapter.itemCount\n    }\n\n    override fun onDestroyView() {\n        super.onDestroyView()\n        /**\n         * 将 RecyclerView 中的视图全部回收到 RecycledViewPool 中\n         */\n        binding.rvBookshelf.setItemViewCacheSize(0)\n        binding.rvBookshelf.adapter = null\n    }\n\n    override fun open(book: Book) {\n        startActivityForBook(book)\n    }\n\n    override fun openBookInfo(book: Book) {\n        startActivity<BookInfoActivity> {\n            putExtra(\"name\", book.name)\n            putExtra(\"author\", book.author)\n        }\n    }\n\n    override fun isUpdate(bookUrl: String): Boolean {\n        return activityViewModel.isUpdate(bookUrl)\n    }\n\n    @SuppressLint(\"NotifyDataSetChanged\")\n    override fun observeLiveBus() {\n        super.observeLiveBus()\n        observeEvent<String>(EventBus.UP_BOOKSHELF) {\n            booksAdapter.notification(it)\n        }\n        observeEvent<String>(EventBus.BOOKSHELF_REFRESH) {\n            booksAdapter.notifyDataSetChanged()\n            startLastUpdateTimeJob()\n            upFastScrollerBar()\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/main/bookshelf/style2/BaseBooksAdapter.kt",
    "content": "package io.legado.app.ui.main.bookshelf.style2\n\nimport android.content.Context\nimport android.view.LayoutInflater\nimport androidx.core.os.bundleOf\nimport androidx.recyclerview.widget.AsyncListDiffer\nimport androidx.recyclerview.widget.DiffUtil\nimport androidx.recyclerview.widget.RecyclerView\nimport io.legado.app.data.entities.Book\nimport io.legado.app.data.entities.BookGroup\n\nabstract class BaseBooksAdapter<VH : RecyclerView.ViewHolder>(\n    val context: Context,\n    val callBack: CallBack\n) : RecyclerView.Adapter<VH>() {\n\n    protected val inflater: LayoutInflater = LayoutInflater.from(context)\n\n    private val diffItemCallback = object : DiffUtil.ItemCallback<Any>() {\n\n        override fun areItemsTheSame(oldItem: Any, newItem: Any): Boolean {\n            return when {\n                oldItem is Book && newItem is Book -> {\n                    oldItem.name == newItem.name\n                            && oldItem.author == newItem.author\n                }\n\n                oldItem is BookGroup && newItem is BookGroup -> {\n                    oldItem.groupId == newItem.groupId\n                }\n\n                else -> false\n            }\n        }\n\n        override fun areContentsTheSame(oldItem: Any, newItem: Any): Boolean {\n            return when {\n                oldItem is Book && newItem is Book -> {\n                    oldItem.durChapterTime == newItem.durChapterTime &&\n                            oldItem.name == newItem.name &&\n                            oldItem.author == newItem.author &&\n                            oldItem.durChapterTitle == newItem.durChapterTitle &&\n                            oldItem.latestChapterTitle == newItem.latestChapterTitle &&\n                            oldItem.lastCheckCount == newItem.lastCheckCount &&\n                            oldItem.getDisplayCover() == newItem.getDisplayCover() &&\n                            oldItem.getUnreadChapterNum() == newItem.getUnreadChapterNum()\n                }\n\n                oldItem is BookGroup && newItem is BookGroup -> {\n                    oldItem.groupName == newItem.groupName &&\n                            oldItem.cover == newItem.cover\n                }\n\n                else -> false\n            }\n        }\n\n        override fun getChangePayload(oldItem: Any, newItem: Any): Any? {\n            val bundle = bundleOf()\n            when {\n                oldItem is Book && newItem is Book -> {\n                    if (oldItem.name != newItem.name) {\n                        bundle.putString(\"name\", newItem.name)\n                    }\n                    if (oldItem.author != newItem.author) {\n                        bundle.putString(\"author\", newItem.author)\n                    }\n                    if (oldItem.durChapterTitle != newItem.durChapterTitle) {\n                        bundle.putString(\"dur\", newItem.durChapterTitle)\n                    }\n                    if (oldItem.latestChapterTitle != newItem.latestChapterTitle) {\n                        bundle.putString(\"last\", newItem.latestChapterTitle)\n                    }\n                    if (oldItem.getDisplayCover() != newItem.getDisplayCover()) {\n                        bundle.putString(\"cover\", newItem.getDisplayCover())\n                    }\n                    if (oldItem.lastCheckCount != newItem.lastCheckCount\n                        || oldItem.durChapterTime != newItem.durChapterTime\n                        || oldItem.getUnreadChapterNum() != newItem.getUnreadChapterNum()\n                        || oldItem.lastCheckCount != newItem.lastCheckCount\n                    ) {\n                        bundle.putBoolean(\"refresh\", true)\n                    }\n                }\n\n                oldItem is BookGroup && newItem is BookGroup -> {\n                    if (oldItem.groupName != newItem.groupName) {\n                        bundle.putString(\"groupName\", newItem.groupName)\n                    }\n                    if (oldItem.cover != newItem.cover) {\n                        bundle.putString(\"cover\", newItem.cover)\n                    }\n                }\n            }\n            if (bundle.isEmpty) return null\n            return bundle\n        }\n    }\n\n    private val asyncListDiffer by lazy {\n        AsyncListDiffer(this, diffItemCallback)\n    }\n\n    fun updateItems() {\n        asyncListDiffer.submitList(callBack.getItems())\n    }\n\n    fun notification(bookUrl: String) {\n        for (i in 0 until itemCount) {\n            getItem(i).let {\n                if (it is Book && it.bookUrl == bookUrl) {\n                    notifyItemChanged(i, bundleOf(Pair(\"refresh\", null)))\n                    return\n                }\n            }\n        }\n    }\n\n    fun getItems() = asyncListDiffer.currentList\n\n    fun getItem(position: Int) = getItems().getOrNull(position)\n\n    override fun getItemCount(): Int {\n        return getItems().size\n    }\n\n    override fun getItemViewType(position: Int): Int {\n        if (getItem(position) is BookGroup) {\n            return 1\n        }\n        return 0\n    }\n\n    final override fun onBindViewHolder(holder: VH, position: Int) {}\n\n\n    interface CallBack {\n        fun onItemClick(item: Any)\n        fun onItemLongClick(item: Any)\n        fun isUpdate(bookUrl: String): Boolean\n        fun getItems(): List<Any>\n    }\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/main/bookshelf/style2/BooksAdapterGrid.kt",
    "content": "package io.legado.app.ui.main.bookshelf.style2\n\nimport android.content.Context\nimport android.os.Bundle\nimport android.view.ViewGroup\nimport androidx.recyclerview.widget.RecyclerView\nimport io.legado.app.data.entities.Book\nimport io.legado.app.data.entities.BookGroup\nimport io.legado.app.databinding.ItemBookshelfGridBinding\nimport io.legado.app.databinding.ItemBookshelfGridGroupBinding\nimport io.legado.app.help.book.isLocal\nimport io.legado.app.help.config.AppConfig\nimport io.legado.app.utils.invisible\nimport splitties.views.onLongClick\n\n@Suppress(\"UNUSED_PARAMETER\")\nclass BooksAdapterGrid(context: Context, callBack: CallBack) :\n    BaseBooksAdapter<RecyclerView.ViewHolder>(context, callBack) {\n\n    override fun onCreateViewHolder(\n        parent: ViewGroup,\n        viewType: Int\n    ): RecyclerView.ViewHolder {\n        return when (viewType) {\n            1 -> GroupViewHolder(ItemBookshelfGridGroupBinding.inflate(inflater, parent, false))\n            else -> BookViewHolder(ItemBookshelfGridBinding.inflate(inflater, parent, false))\n        }\n    }\n\n    override fun onBindViewHolder(\n        holder: RecyclerView.ViewHolder,\n        position: Int,\n        payloads: MutableList<Any>\n    ) {\n        when (holder) {\n            is BookViewHolder -> (getItem(position) as? Book)?.let {\n                holder.registerListener(it)\n                holder.onBind(it, position, payloads)\n            }\n\n            is GroupViewHolder -> (getItem(position) as? BookGroup)?.let {\n                holder.registerListener(it)\n                holder.onBind(it, position, payloads)\n            }\n        }\n    }\n\n    inner class BookViewHolder(val binding: ItemBookshelfGridBinding) :\n        RecyclerView.ViewHolder(binding.root) {\n\n        fun onBind(item: Book, position: Int) = binding.run {\n            tvName.text = item.name\n            ivCover.load(item.getDisplayCover(), item.name, item.author, false, item.origin)\n            upRefresh(this, item)\n        }\n\n        fun onBind(item: Book, position: Int, payloads: MutableList<Any>) = binding.run {\n            if (payloads.isEmpty()) {\n                onBind(item, position)\n            } else {\n                for (i in payloads.indices) {\n                    val bundle = payloads[i] as Bundle\n                    bundle.keySet().forEach {\n                        when (it) {\n                            \"name\" -> tvName.text = item.name\n                            \"cover\" -> ivCover.load(\n                                item.getDisplayCover(),\n                                item.name,\n                                item.author,\n                                false,\n                                item.origin\n                            )\n\n                            \"refresh\" -> upRefresh(this, item)\n                        }\n                    }\n                }\n            }\n        }\n\n        fun registerListener(item: Any) {\n            binding.root.setOnClickListener {\n                callBack.onItemClick(item)\n            }\n            binding.root.onLongClick {\n                callBack.onItemLongClick(item)\n            }\n        }\n\n        private fun upRefresh(binding: ItemBookshelfGridBinding, item: Book) {\n            if (!item.isLocal && callBack.isUpdate(item.bookUrl)) {\n                binding.bvUnread.invisible()\n                binding.rlLoading.visible()\n            } else {\n                binding.rlLoading.inVisible()\n                if (AppConfig.showUnread) {\n                    binding.bvUnread.setBadgeCount(item.getUnreadChapterNum())\n                    binding.bvUnread.setHighlight(item.lastCheckCount > 0)\n                } else {\n                    binding.bvUnread.invisible()\n                }\n            }\n        }\n\n    }\n\n    inner class GroupViewHolder(val binding: ItemBookshelfGridGroupBinding) :\n        RecyclerView.ViewHolder(binding.root) {\n\n        fun onBind(item: BookGroup, position: Int) = binding.run {\n            tvName.text = item.groupName\n            ivCover.load(item.cover)\n        }\n\n        fun onBind(item: BookGroup, position: Int, payloads: MutableList<Any>) = binding.run {\n            if (payloads.isEmpty()) {\n                onBind(item, position)\n            } else {\n                for (i in payloads.indices) {\n                    val bundle = payloads[i] as Bundle\n                    bundle.keySet().forEach {\n                        when (it) {\n                            \"groupName\" -> tvName.text = item.groupName\n                            \"cover\" -> ivCover.load(item.cover)\n                        }\n                    }\n                }\n            }\n        }\n\n        fun registerListener(item: Any) {\n            binding.root.setOnClickListener {\n                callBack.onItemClick(item)\n            }\n            binding.root.onLongClick {\n                callBack.onItemLongClick(item)\n            }\n        }\n\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/main/bookshelf/style2/BooksAdapterList.kt",
    "content": "package io.legado.app.ui.main.bookshelf.style2\n\nimport android.content.Context\nimport android.os.Bundle\nimport android.view.ViewGroup\nimport androidx.recyclerview.widget.RecyclerView\nimport io.legado.app.data.entities.Book\nimport io.legado.app.data.entities.BookGroup\nimport io.legado.app.databinding.ItemBookshelfListBinding\nimport io.legado.app.databinding.ItemBookshelfListGroupBinding\nimport io.legado.app.help.book.isLocal\nimport io.legado.app.help.config.AppConfig\nimport io.legado.app.utils.gone\nimport io.legado.app.utils.invisible\nimport io.legado.app.utils.visible\nimport splitties.views.onLongClick\n\n@Suppress(\"UNUSED_PARAMETER\")\nclass BooksAdapterList(context: Context, callBack: CallBack) :\n    BaseBooksAdapter<RecyclerView.ViewHolder>(context, callBack) {\n\n    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {\n        return when (viewType) {\n            1 -> GroupViewHolder(ItemBookshelfListGroupBinding.inflate(inflater, parent, false))\n            else -> BookViewHolder(ItemBookshelfListBinding.inflate(inflater, parent, false))\n        }\n    }\n\n    override fun onBindViewHolder(\n        holder: RecyclerView.ViewHolder,\n        position: Int,\n        payloads: MutableList<Any>\n    ) {\n        when (holder) {\n            is BookViewHolder -> (getItem(position) as? Book)?.let {\n                holder.registerListener(it)\n                holder.onBind(it, position, payloads)\n            }\n\n            is GroupViewHolder -> (getItem(position) as? BookGroup)?.let {\n                holder.registerListener(it)\n                holder.onBind(it, position, payloads)\n            }\n        }\n    }\n\n    inner class BookViewHolder(val binding: ItemBookshelfListBinding) :\n        RecyclerView.ViewHolder(binding.root) {\n\n        fun onBind(item: Book, position: Int) = binding.run {\n            tvName.text = item.name\n            tvAuthor.text = item.author\n            tvRead.text = item.durChapterTitle\n            tvLast.text = item.latestChapterTitle\n            ivCover.load(item.getDisplayCover(), item.name, item.author, false, item.origin)\n            flHasNew.visible()\n            ivAuthor.visible()\n            ivLast.visible()\n            ivRead.visible()\n            upRefresh(this, item)\n        }\n\n        fun onBind(item: Book, position: Int, payloads: MutableList<Any>) = binding.run {\n            if (payloads.isEmpty()) {\n                onBind(item, position)\n            } else {\n                for (i in payloads.indices) {\n                    val bundle = payloads[i] as Bundle\n                    bundle.keySet().forEach {\n                        when (it) {\n                            \"name\" -> tvName.text = item.name\n                            \"author\" -> tvAuthor.text = item.author\n                            \"dur\" -> tvRead.text = item.durChapterTitle\n                            \"last\" -> tvLast.text = item.latestChapterTitle\n                            \"cover\" -> ivCover.load(\n                                item.getDisplayCover(),\n                                item.name,\n                                item.author,\n                                false,\n                                item.origin\n                            )\n\n                            \"refresh\" -> upRefresh(this, item)\n                        }\n                    }\n                }\n            }\n        }\n\n        fun registerListener(item: Any) {\n            binding.root.setOnClickListener {\n                callBack.onItemClick(item)\n            }\n            binding.root.onLongClick {\n                callBack.onItemLongClick(item)\n            }\n        }\n\n        private fun upRefresh(binding: ItemBookshelfListBinding, item: Book) {\n            if (!item.isLocal && callBack.isUpdate(item.bookUrl)) {\n                binding.bvUnread.invisible()\n                binding.rlLoading.visible()\n            } else {\n                binding.rlLoading.gone()\n                if (AppConfig.showUnread) {\n                    binding.bvUnread.setHighlight(item.lastCheckCount > 0)\n                    binding.bvUnread.setBadgeCount(item.getUnreadChapterNum())\n                } else {\n                    binding.bvUnread.invisible()\n                }\n            }\n        }\n\n    }\n\n    inner class GroupViewHolder(val binding: ItemBookshelfListGroupBinding) :\n        RecyclerView.ViewHolder(binding.root) {\n\n        fun onBind(item: BookGroup, position: Int) = binding.run {\n            tvName.text = item.groupName\n            ivCover.load(item.cover)\n            flHasNew.gone()\n            ivAuthor.gone()\n            ivLast.gone()\n            ivRead.gone()\n            tvAuthor.gone()\n            tvLast.gone()\n            tvRead.gone()\n        }\n\n        fun onBind(item: BookGroup, position: Int, payloads: MutableList<Any>) = binding.run {\n            if (payloads.isEmpty()) {\n                onBind(item, position)\n            } else {\n                for (i in payloads.indices) {\n                    val bundle = payloads[i] as Bundle\n                    bundle.keySet().forEach {\n                        when (it) {\n                            \"groupName\" -> tvName.text = item.groupName\n                            \"cover\" -> ivCover.load(item.cover)\n                        }\n                    }\n                }\n            }\n        }\n\n        fun registerListener(item: Any) {\n            binding.root.setOnClickListener {\n                callBack.onItemClick(item)\n            }\n            binding.root.onLongClick {\n                callBack.onItemLongClick(item)\n            }\n        }\n\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/main/bookshelf/style2/BookshelfFragment2.kt",
    "content": "package io.legado.app.ui.main.bookshelf.style2\n\nimport android.annotation.SuppressLint\nimport android.os.Bundle\nimport android.view.View\nimport androidx.appcompat.widget.SearchView\nimport androidx.core.view.isGone\nimport androidx.lifecycle.Lifecycle\nimport androidx.lifecycle.lifecycleScope\nimport androidx.recyclerview.widget.GridLayoutManager\nimport androidx.recyclerview.widget.LinearLayoutManager\nimport androidx.recyclerview.widget.RecyclerView\nimport io.legado.app.R\nimport io.legado.app.constant.AppLog\nimport io.legado.app.constant.EventBus\nimport io.legado.app.data.AppDatabase\nimport io.legado.app.data.appDb\nimport io.legado.app.data.entities.Book\nimport io.legado.app.data.entities.BookGroup\nimport io.legado.app.databinding.FragmentBookshelf2Binding\nimport io.legado.app.help.config.AppConfig\nimport io.legado.app.lib.theme.accentColor\nimport io.legado.app.lib.theme.primaryColor\nimport io.legado.app.ui.book.group.GroupEditDialog\nimport io.legado.app.ui.book.info.BookInfoActivity\nimport io.legado.app.ui.book.search.SearchActivity\nimport io.legado.app.ui.main.bookshelf.BaseBookshelfFragment\nimport io.legado.app.utils.cnCompare\nimport io.legado.app.utils.flowWithLifecycleAndDatabaseChangeFirst\nimport io.legado.app.utils.observeEvent\nimport io.legado.app.utils.setEdgeEffectColor\nimport io.legado.app.utils.showDialogFragment\nimport io.legado.app.utils.startActivity\nimport io.legado.app.utils.startActivityForBook\nimport io.legado.app.utils.viewbindingdelegate.viewBinding\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.Job\nimport kotlinx.coroutines.delay\nimport kotlinx.coroutines.flow.catch\nimport kotlinx.coroutines.flow.conflate\nimport kotlinx.coroutines.flow.flowOn\nimport kotlinx.coroutines.flow.map\nimport kotlinx.coroutines.launch\nimport kotlin.math.max\n\n/**\n * 书架界面\n */\nclass BookshelfFragment2() : BaseBookshelfFragment(R.layout.fragment_bookshelf2),\n    SearchView.OnQueryTextListener,\n    BaseBooksAdapter.CallBack {\n\n    constructor(position: Int) : this() {\n        val bundle = Bundle()\n        bundle.putInt(\"position\", position)\n        arguments = bundle\n    }\n\n    private val binding by viewBinding(FragmentBookshelf2Binding::bind)\n    private val bookshelfLayout by lazy { AppConfig.bookshelfLayout }\n    private val booksAdapter: BaseBooksAdapter<*> by lazy {\n        if (bookshelfLayout == 0) {\n            BooksAdapterList(requireContext(), this)\n        } else {\n            BooksAdapterGrid(requireContext(), this)\n        }\n    }\n    private var bookGroups: List<BookGroup> = emptyList()\n    private var booksFlowJob: Job? = null\n    override var groupId = BookGroup.IdRoot\n    override var books: List<Book> = emptyList()\n    private var enableRefresh = true\n\n    override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) {\n        setSupportToolbar(binding.titleBar.toolbar)\n        initRecyclerView()\n        initBookGroupData()\n        initBooksData()\n    }\n\n    private fun initRecyclerView() {\n        binding.rvBookshelf.setEdgeEffectColor(primaryColor)\n        binding.refreshLayout.setColorSchemeColors(accentColor)\n        binding.refreshLayout.setOnRefreshListener {\n            binding.refreshLayout.isRefreshing = false\n            activityViewModel.upToc(books)\n        }\n        if (bookshelfLayout == 0) {\n            binding.rvBookshelf.layoutManager = LinearLayoutManager(context)\n        } else {\n            binding.rvBookshelf.layoutManager = GridLayoutManager(context, bookshelfLayout + 2)\n        }\n        binding.rvBookshelf.itemAnimator = null\n        binding.rvBookshelf.adapter = booksAdapter\n        booksAdapter.registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() {\n            override fun onItemRangeInserted(positionStart: Int, itemCount: Int) {\n                val layoutManager = binding.rvBookshelf.layoutManager\n                if (positionStart == 0 && layoutManager is LinearLayoutManager) {\n                    val scrollTo = layoutManager.findFirstVisibleItemPosition() - itemCount\n                    binding.rvBookshelf.scrollToPosition(max(0, scrollTo))\n                }\n            }\n\n            override fun onItemRangeMoved(fromPosition: Int, toPosition: Int, itemCount: Int) {\n                val layoutManager = binding.rvBookshelf.layoutManager\n                if (toPosition == 0 && layoutManager is LinearLayoutManager) {\n                    val scrollTo = layoutManager.findFirstVisibleItemPosition() - itemCount\n                    binding.rvBookshelf.scrollToPosition(max(0, scrollTo))\n                }\n            }\n        })\n    }\n\n    override fun upGroup(data: List<BookGroup>) {\n        if (data != bookGroups) {\n            bookGroups = data\n            booksAdapter.updateItems()\n            binding.tvEmptyMsg.isGone = getItemCount() > 0\n            binding.refreshLayout.isEnabled = enableRefresh && getItemCount() > 0\n        }\n    }\n\n    override fun upSort() {\n        initBooksData()\n    }\n\n    private fun initBooksData() {\n        if (groupId == BookGroup.IdRoot) {\n            if (isAdded) {\n                binding.titleBar.title = getString(R.string.bookshelf)\n                binding.refreshLayout.isEnabled = true\n                enableRefresh = true\n            }\n        } else {\n            bookGroups.firstOrNull {\n                groupId == it.groupId\n            }?.let {\n                binding.titleBar.title = \"${getString(R.string.bookshelf)}(${it.groupName})\"\n                binding.refreshLayout.isEnabled = it.enableRefresh\n                enableRefresh = it.enableRefresh\n            }\n        }\n        booksFlowJob?.cancel()\n        booksFlowJob = viewLifecycleOwner.lifecycleScope.launch {\n            appDb.bookDao.flowByGroup(groupId).map { list ->\n                //排序\n                when (AppConfig.getBookSortByGroupId(groupId)) {\n                    1 -> list.sortedByDescending {\n                        it.latestChapterTime\n                    }\n\n                    2 -> list.sortedWith { o1, o2 ->\n                        o1.name.cnCompare(o2.name)\n                    }\n\n                    3 -> list.sortedBy {\n                        it.order\n                    }\n\n                    4 -> list.sortedByDescending {\n                        max(it.latestChapterTime, it.durChapterTime)\n                    }\n\n                    else -> list.sortedByDescending {\n                        it.durChapterTime\n                    }\n                }\n            }.flowWithLifecycleAndDatabaseChangeFirst(\n                viewLifecycleOwner.lifecycle,\n                Lifecycle.State.RESUMED,\n                AppDatabase.BOOK_TABLE_NAME\n            ).catch {\n                AppLog.put(\"书架更新出错\", it)\n            }.conflate().flowOn(Dispatchers.Default).collect { list ->\n                books = list\n                booksAdapter.updateItems()\n                binding.tvEmptyMsg.isGone = getItemCount() > 0\n                binding.refreshLayout.isEnabled = enableRefresh && getItemCount() > 0\n                delay(100)\n            }\n        }\n    }\n\n    fun back(): Boolean {\n        if (groupId != BookGroup.IdRoot) {\n            groupId = BookGroup.IdRoot\n            initBooksData()\n            return true\n        }\n        return false\n    }\n\n    override fun onQueryTextSubmit(query: String?): Boolean {\n        SearchActivity.start(requireContext(), query)\n        return false\n    }\n\n    override fun onQueryTextChange(newText: String?): Boolean {\n        return false\n    }\n\n    override fun gotoTop() {\n        if (AppConfig.isEInkMode) {\n            binding.rvBookshelf.scrollToPosition(0)\n        } else {\n            binding.rvBookshelf.smoothScrollToPosition(0)\n        }\n    }\n\n    override fun onItemClick(item: Any) {\n        when (item) {\n            is Book -> startActivityForBook(item)\n\n            is BookGroup -> {\n                groupId = item.groupId\n                initBooksData()\n            }\n        }\n    }\n\n    override fun onItemLongClick(item: Any) {\n        when (item) {\n            is Book -> startActivity<BookInfoActivity> {\n                putExtra(\"name\", item.name)\n                putExtra(\"author\", item.author)\n            }\n\n            is BookGroup -> showDialogFragment(GroupEditDialog(item))\n        }\n    }\n\n    override fun isUpdate(bookUrl: String): Boolean {\n        return activityViewModel.isUpdate(bookUrl)\n    }\n\n    fun getItemCount(): Int {\n        return if (groupId == BookGroup.IdRoot) {\n            bookGroups.size + books.size\n        } else {\n            books.size\n        }\n    }\n\n    override fun getItems(): List<Any> {\n        if (groupId != BookGroup.IdRoot) {\n            return books\n        }\n        return bookGroups + books\n    }\n\n    @SuppressLint(\"NotifyDataSetChanged\")\n    override fun observeLiveBus() {\n        super.observeLiveBus()\n        observeEvent<String>(EventBus.UP_BOOKSHELF) {\n            booksAdapter.notification(it)\n        }\n        observeEvent<String>(EventBus.BOOKSHELF_REFRESH) {\n            booksAdapter.notifyDataSetChanged()\n        }\n    }\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/main/explore/ExploreAdapter.kt",
    "content": "package io.legado.app.ui.main.explore\n\nimport android.content.Context\nimport android.view.View\nimport android.view.ViewGroup\nimport android.widget.PopupMenu\nimport android.widget.TextView\nimport androidx.core.view.children\nimport com.google.android.flexbox.FlexboxLayout\nimport io.legado.app.R\nimport io.legado.app.base.adapter.ItemViewHolder\nimport io.legado.app.base.adapter.RecyclerAdapter\nimport io.legado.app.data.entities.BookSourcePart\nimport io.legado.app.data.entities.rule.ExploreKind\nimport io.legado.app.databinding.ItemFilletTextBinding\nimport io.legado.app.databinding.ItemFindBookBinding\nimport io.legado.app.help.coroutine.Coroutine\nimport io.legado.app.help.source.clearExploreKindsCache\nimport io.legado.app.help.source.exploreKinds\nimport io.legado.app.lib.theme.accentColor\nimport io.legado.app.ui.login.SourceLoginActivity\nimport io.legado.app.ui.widget.dialog.TextDialog\nimport io.legado.app.utils.activity\nimport io.legado.app.utils.dpToPx\nimport io.legado.app.utils.gone\nimport io.legado.app.utils.removeLastElement\nimport io.legado.app.utils.showDialogFragment\nimport io.legado.app.utils.startActivity\nimport io.legado.app.utils.visible\nimport kotlinx.coroutines.CoroutineScope\nimport splitties.views.onLongClick\n\nclass ExploreAdapter(context: Context, val callBack: CallBack) :\n    RecyclerAdapter<BookSourcePart, ItemFindBookBinding>(context) {\n\n    private val recycler = arrayListOf<View>()\n    private var exIndex = -1\n    private var scrollTo = -1\n\n    override fun getViewBinding(parent: ViewGroup): ItemFindBookBinding {\n        return ItemFindBookBinding.inflate(inflater, parent, false)\n    }\n\n    override fun convert(\n        holder: ItemViewHolder,\n        binding: ItemFindBookBinding,\n        item: BookSourcePart,\n        payloads: MutableList<Any>\n    ) {\n        binding.run {\n            if (holder.layoutPosition == itemCount - 1) {\n                root.setPadding(16.dpToPx(), 12.dpToPx(), 16.dpToPx(), 12.dpToPx())\n            } else {\n                root.setPadding(16.dpToPx(), 12.dpToPx(), 16.dpToPx(), 0)\n            }\n            if (payloads.isEmpty()) {\n                tvName.text = item.bookSourceName\n            }\n            if (exIndex == holder.layoutPosition) {\n                ivStatus.setImageResource(R.drawable.ic_arrow_down)\n                rotateLoading.loadingColor = context.accentColor\n                rotateLoading.visible()\n                if (scrollTo >= 0) {\n                    callBack.scrollTo(scrollTo)\n                }\n                Coroutine.async(callBack.scope) {\n                    item.exploreKinds()\n                }.onSuccess { kindList ->\n                    upKindList(flexbox, item.bookSourceUrl, kindList)\n                }.onFinally {\n                    rotateLoading.gone()\n                    if (scrollTo >= 0) {\n                        callBack.scrollTo(scrollTo)\n                        scrollTo = -1\n                    }\n                }\n            } else kotlin.runCatching {\n                ivStatus.setImageResource(R.drawable.ic_arrow_right)\n                rotateLoading.gone()\n                recyclerFlexbox(flexbox)\n                flexbox.gone()\n            }\n        }\n    }\n\n    private fun upKindList(flexbox: FlexboxLayout, sourceUrl: String, kinds: List<ExploreKind>) {\n        if (kinds.isNotEmpty()) kotlin.runCatching {\n            recyclerFlexbox(flexbox)\n            flexbox.visible()\n            kinds.forEach { kind ->\n                val tv = getFlexboxChild(flexbox)\n                flexbox.addView(tv)\n                tv.text = kind.title\n                kind.style().apply(tv)\n                if (kind.url.isNullOrBlank()) {\n                    tv.setOnClickListener(null)\n                } else {\n                    tv.setOnClickListener {\n                        if (kind.title.startsWith(\"ERROR:\")) {\n                            it.activity?.showDialogFragment(TextDialog(\"ERROR\", kind.url))\n                        } else {\n                            callBack.openExplore(sourceUrl, kind.title, kind.url)\n                        }\n                    }\n                }\n            }\n        }\n    }\n\n    @Synchronized\n    private fun getFlexboxChild(flexbox: FlexboxLayout): TextView {\n        return if (recycler.isEmpty()) {\n            ItemFilletTextBinding.inflate(inflater, flexbox, false).root\n        } else {\n            recycler.removeLastElement() as TextView\n        }\n    }\n\n    @Synchronized\n    private fun recyclerFlexbox(flexbox: FlexboxLayout) {\n        recycler.addAll(flexbox.children)\n        flexbox.removeAllViews()\n    }\n\n    override fun registerListener(holder: ItemViewHolder, binding: ItemFindBookBinding) {\n        binding.apply {\n            llTitle.setOnClickListener {\n                val position = holder.layoutPosition\n                val oldEx = exIndex\n                exIndex = if (exIndex == position) -1 else position\n                notifyItemChanged(oldEx, false)\n                if (exIndex != -1) {\n                    scrollTo = position\n                    callBack.scrollTo(position)\n                    notifyItemChanged(position, false)\n                }\n            }\n            llTitle.onLongClick {\n                showMenu(llTitle, holder.layoutPosition)\n            }\n        }\n    }\n\n    fun compressExplore(): Boolean {\n        return if (exIndex < 0) {\n            false\n        } else {\n            val oldExIndex = exIndex\n            exIndex = -1\n            notifyItemChanged(oldExIndex)\n            true\n        }\n    }\n\n    private fun showMenu(view: View, position: Int): Boolean {\n        val source = getItem(position) ?: return true\n        val popupMenu = PopupMenu(context, view)\n        popupMenu.inflate(R.menu.explore_item)\n        popupMenu.menu.findItem(R.id.menu_login).isVisible = source.hasLoginUrl\n        popupMenu.setOnMenuItemClickListener {\n            when (it.itemId) {\n                R.id.menu_edit -> callBack.editSource(source.bookSourceUrl)\n                R.id.menu_top -> callBack.toTop(source)\n                R.id.menu_search -> callBack.searchBook(source)\n                R.id.menu_login -> context.startActivity<SourceLoginActivity> {\n                    putExtra(\"type\", \"bookSource\")\n                    putExtra(\"key\", source.bookSourceUrl)\n                }\n\n                R.id.menu_refresh -> Coroutine.async(callBack.scope) {\n                    source.clearExploreKindsCache()\n                }.onSuccess {\n                    notifyItemChanged(position)\n                }\n\n                R.id.menu_del -> callBack.deleteSource(source)\n            }\n            true\n        }\n        popupMenu.show()\n        return true\n    }\n\n    interface CallBack {\n        val scope: CoroutineScope\n        fun scrollTo(pos: Int)\n        fun openExplore(sourceUrl: String, title: String, exploreUrl: String?)\n        fun editSource(sourceUrl: String)\n        fun toTop(source: BookSourcePart)\n        fun deleteSource(source: BookSourcePart)\n        fun searchBook(bookSource: BookSourcePart)\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/main/explore/ExploreDiffItemCallBack.kt",
    "content": "package io.legado.app.ui.main.explore\n\nimport androidx.recyclerview.widget.DiffUtil\nimport io.legado.app.data.entities.BookSourcePart\n\n\nclass ExploreDiffItemCallBack : DiffUtil.ItemCallback<BookSourcePart>() {\n\n    override fun areItemsTheSame(oldItem: BookSourcePart, newItem: BookSourcePart): Boolean {\n        return oldItem == newItem\n    }\n\n    override fun areContentsTheSame(oldItem: BookSourcePart, newItem: BookSourcePart): Boolean {\n        return oldItem.bookSourceName == newItem.bookSourceName\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/main/explore/ExploreFragment.kt",
    "content": "package io.legado.app.ui.main.explore\n\nimport android.os.Bundle\nimport android.view.Menu\nimport android.view.MenuItem\nimport android.view.SubMenu\nimport android.view.View\nimport androidx.appcompat.widget.SearchView\nimport androidx.core.view.isGone\nimport androidx.fragment.app.viewModels\nimport androidx.lifecycle.Lifecycle\nimport androidx.lifecycle.lifecycleScope\nimport androidx.recyclerview.widget.LinearLayoutManager\nimport androidx.recyclerview.widget.RecyclerView\nimport io.legado.app.R\nimport io.legado.app.base.VMBaseFragment\nimport io.legado.app.constant.AppLog\nimport io.legado.app.data.AppDatabase\nimport io.legado.app.data.appDb\nimport io.legado.app.data.entities.BookSourcePart\nimport io.legado.app.databinding.FragmentExploreBinding\nimport io.legado.app.help.config.AppConfig\nimport io.legado.app.lib.dialogs.alert\nimport io.legado.app.lib.theme.primaryColor\nimport io.legado.app.lib.theme.primaryTextColor\nimport io.legado.app.ui.book.explore.ExploreShowActivity\nimport io.legado.app.ui.book.search.SearchActivity\nimport io.legado.app.ui.book.search.SearchScope\nimport io.legado.app.ui.book.source.edit.BookSourceEditActivity\nimport io.legado.app.ui.main.MainFragmentInterface\nimport io.legado.app.utils.applyTint\nimport io.legado.app.utils.flowWithLifecycleAndDatabaseChange\nimport io.legado.app.utils.setEdgeEffectColor\nimport io.legado.app.utils.startActivity\nimport io.legado.app.utils.transaction\nimport io.legado.app.utils.viewbindingdelegate.viewBinding\nimport kotlinx.coroutines.CoroutineScope\nimport kotlinx.coroutines.Dispatchers.IO\nimport kotlinx.coroutines.Job\nimport kotlinx.coroutines.delay\nimport kotlinx.coroutines.flow.catch\nimport kotlinx.coroutines.flow.conflate\nimport kotlinx.coroutines.flow.distinctUntilChanged\nimport kotlinx.coroutines.flow.flowOn\nimport kotlinx.coroutines.launch\n\n/**\n * 发现界面\n */\nclass ExploreFragment() : VMBaseFragment<ExploreViewModel>(R.layout.fragment_explore),\n    MainFragmentInterface,\n    ExploreAdapter.CallBack {\n\n    constructor(position: Int) : this() {\n        val bundle = Bundle()\n        bundle.putInt(\"position\", position)\n        arguments = bundle\n    }\n\n    override val position: Int? get() = arguments?.getInt(\"position\")\n\n    override val viewModel by viewModels<ExploreViewModel>()\n    private val binding by viewBinding(FragmentExploreBinding::bind)\n    private val adapter by lazy { ExploreAdapter(requireContext(), this) }\n    private val linearLayoutManager by lazy { LinearLayoutManager(context) }\n    private val searchView: SearchView by lazy {\n        binding.titleBar.findViewById(R.id.search_view)\n    }\n    private val diffItemCallBack = ExploreDiffItemCallBack()\n    private val groups = linkedSetOf<String>()\n    private var exploreFlowJob: Job? = null\n    private var groupsMenu: SubMenu? = null\n\n    override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) {\n        setSupportToolbar(binding.titleBar.toolbar)\n        initSearchView()\n        initRecyclerView()\n        initGroupData()\n        upExploreData()\n    }\n\n    override fun onCompatCreateOptionsMenu(menu: Menu) {\n        super.onCompatCreateOptionsMenu(menu)\n        menuInflater.inflate(R.menu.main_explore, menu)\n        groupsMenu = menu.findItem(R.id.menu_group)?.subMenu\n        upGroupsMenu()\n    }\n\n    override fun onPause() {\n        super.onPause()\n        searchView.clearFocus()\n    }\n\n    private fun initSearchView() {\n        searchView.applyTint(primaryTextColor)\n        searchView.isSubmitButtonEnabled = true\n        searchView.queryHint = getString(R.string.screen_find)\n        searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener {\n            override fun onQueryTextSubmit(query: String?): Boolean {\n                return false\n            }\n\n            override fun onQueryTextChange(newText: String?): Boolean {\n                upExploreData(newText)\n                return false\n            }\n        })\n    }\n\n    private fun initRecyclerView() {\n        binding.rvFind.setEdgeEffectColor(primaryColor)\n        binding.rvFind.layoutManager = linearLayoutManager\n        binding.rvFind.adapter = adapter\n        adapter.registerAdapterDataObserver(object : RecyclerView.AdapterDataObserver() {\n\n            override fun onItemRangeInserted(positionStart: Int, itemCount: Int) {\n                super.onItemRangeInserted(positionStart, itemCount)\n                if (positionStart == 0) {\n                    binding.rvFind.scrollToPosition(0)\n                }\n            }\n        })\n    }\n\n    private fun initGroupData() {\n        viewLifecycleOwner.lifecycleScope.launch {\n            appDb.bookSourceDao.flowExploreGroups()\n                .flowWithLifecycleAndDatabaseChange(\n                    viewLifecycleOwner.lifecycle,\n                    Lifecycle.State.RESUMED,\n                    AppDatabase.BOOK_SOURCE_TABLE_NAME\n                )\n                .conflate()\n                .distinctUntilChanged()\n                .collect {\n                    groups.clear()\n                    groups.addAll(it)\n                    upGroupsMenu()\n                    delay(500)\n                }\n        }\n    }\n\n    private fun upExploreData(searchKey: String? = null) {\n        exploreFlowJob?.cancel()\n        exploreFlowJob = viewLifecycleOwner.lifecycleScope.launch {\n            when {\n                searchKey.isNullOrBlank() -> {\n                    appDb.bookSourceDao.flowExplore()\n                }\n\n                searchKey.startsWith(\"group:\") -> {\n                    val key = searchKey.substringAfter(\"group:\")\n                    appDb.bookSourceDao.flowGroupExplore(key)\n                }\n\n                else -> {\n                    appDb.bookSourceDao.flowExplore(searchKey)\n                }\n            }.flowWithLifecycleAndDatabaseChange(\n                viewLifecycleOwner.lifecycle,\n                Lifecycle.State.RESUMED,\n                AppDatabase.BOOK_SOURCE_TABLE_NAME\n            ).catch {\n                AppLog.put(\"发现界面更新数据出错\", it)\n            }.conflate().flowOn(IO).collect {\n                binding.tvEmptyMsg.isGone = it.isNotEmpty() || searchView.query.isNotEmpty()\n                adapter.setItems(it, diffItemCallBack)\n                delay(500)\n            }\n        }\n    }\n\n    private fun upGroupsMenu() = groupsMenu?.transaction { subMenu ->\n        subMenu.removeGroup(R.id.menu_group_text)\n        groups.forEach {\n            subMenu.add(R.id.menu_group_text, Menu.NONE, Menu.NONE, it)\n        }\n    }\n\n    override val scope: CoroutineScope\n        get() = viewLifecycleOwner.lifecycleScope\n\n    override fun onCompatOptionsItemSelected(item: MenuItem) {\n        super.onCompatOptionsItemSelected(item)\n        if (item.groupId == R.id.menu_group_text) {\n            searchView.setQuery(\"group:${item.title}\", true)\n        }\n    }\n\n    override fun scrollTo(pos: Int) {\n        (binding.rvFind.layoutManager as LinearLayoutManager).scrollToPositionWithOffset(pos, 0)\n    }\n\n    override fun openExplore(sourceUrl: String, title: String, exploreUrl: String?) {\n        if (exploreUrl.isNullOrBlank()) return\n        startActivity<ExploreShowActivity> {\n            putExtra(\"exploreName\", title)\n            putExtra(\"sourceUrl\", sourceUrl)\n            putExtra(\"exploreUrl\", exploreUrl)\n        }\n    }\n\n    override fun editSource(sourceUrl: String) {\n        startActivity<BookSourceEditActivity> {\n            putExtra(\"sourceUrl\", sourceUrl)\n        }\n    }\n\n    override fun toTop(source: BookSourcePart) {\n        viewModel.topSource(source)\n    }\n\n    override fun deleteSource(source: BookSourcePart) {\n        alert(R.string.draw) {\n            setMessage(getString(R.string.sure_del) + \"\\n\" + source.bookSourceName)\n            noButton()\n            yesButton {\n                viewModel.deleteSource(source)\n            }\n        }\n    }\n\n    override fun searchBook(bookSource: BookSourcePart) {\n        startActivity<SearchActivity> {\n            putExtra(\"searchScope\", SearchScope(bookSource).toString())\n        }\n    }\n\n    fun compressExplore() {\n        if (!adapter.compressExplore()) {\n            if (AppConfig.isEInkMode) {\n                binding.rvFind.scrollToPosition(0)\n            } else {\n                binding.rvFind.smoothScrollToPosition(0)\n            }\n        }\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/main/explore/ExploreViewModel.kt",
    "content": "package io.legado.app.ui.main.explore\n\nimport android.app.Application\nimport io.legado.app.base.BaseViewModel\nimport io.legado.app.data.appDb\nimport io.legado.app.data.entities.BookSourcePart\nimport io.legado.app.help.config.SourceConfig\nimport io.legado.app.help.source.SourceHelp\n\nclass ExploreViewModel(application: Application) : BaseViewModel(application) {\n\n    fun topSource(bookSource: BookSourcePart) {\n        execute {\n            val minXh = appDb.bookSourceDao.minOrder\n            bookSource.customOrder = minXh - 1\n            appDb.bookSourceDao.upOrder(bookSource)\n        }\n    }\n\n    fun deleteSource(source: BookSourcePart) {\n        execute {\n            SourceHelp.deleteBookSource(source.bookSourceUrl)\n        }\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/main/my/MyFragment.kt",
    "content": "package io.legado.app.ui.main.my\n\nimport android.content.SharedPreferences\nimport android.os.Bundle\nimport android.view.Menu\nimport android.view.MenuItem\nimport android.view.View\nimport androidx.preference.Preference\nimport io.legado.app.R\nimport io.legado.app.base.BaseFragment\nimport io.legado.app.constant.EventBus\nimport io.legado.app.constant.PreferKey\nimport io.legado.app.databinding.FragmentMyConfigBinding\nimport io.legado.app.help.config.ThemeConfig\nimport io.legado.app.lib.dialogs.selector\nimport io.legado.app.lib.prefs.NameListPreference\nimport io.legado.app.lib.prefs.SwitchPreference\nimport io.legado.app.lib.prefs.fragment.PreferenceFragment\nimport io.legado.app.lib.theme.primaryColor\nimport io.legado.app.service.WebService\nimport io.legado.app.ui.about.AboutActivity\nimport io.legado.app.ui.about.ReadRecordActivity\nimport io.legado.app.ui.book.bookmark.AllBookmarkActivity\nimport io.legado.app.ui.book.source.manage.BookSourceActivity\nimport io.legado.app.ui.book.toc.rule.TxtTocRuleActivity\nimport io.legado.app.ui.config.ConfigActivity\nimport io.legado.app.ui.config.ConfigTag\nimport io.legado.app.ui.dict.rule.DictRuleActivity\nimport io.legado.app.ui.file.FileManageActivity\nimport io.legado.app.ui.main.MainFragmentInterface\nimport io.legado.app.ui.replace.ReplaceRuleActivity\nimport io.legado.app.utils.LogUtils\nimport io.legado.app.utils.getPrefBoolean\nimport io.legado.app.utils.observeEventSticky\nimport io.legado.app.utils.openUrl\nimport io.legado.app.utils.putPrefBoolean\nimport io.legado.app.utils.sendToClip\nimport io.legado.app.utils.setEdgeEffectColor\nimport io.legado.app.utils.showHelp\nimport io.legado.app.utils.startActivity\nimport io.legado.app.utils.viewbindingdelegate.viewBinding\n\nclass MyFragment() : BaseFragment(R.layout.fragment_my_config), MainFragmentInterface {\n\n    constructor(position: Int) : this() {\n        val bundle = Bundle()\n        bundle.putInt(\"position\", position)\n        arguments = bundle\n    }\n\n    override val position: Int? get() = arguments?.getInt(\"position\")\n\n    private val binding by viewBinding(FragmentMyConfigBinding::bind)\n\n    override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) {\n        setSupportToolbar(binding.titleBar.toolbar)\n        val fragmentTag = \"prefFragment\"\n        var preferenceFragment = childFragmentManager.findFragmentByTag(fragmentTag)\n        if (preferenceFragment == null) preferenceFragment = MyPreferenceFragment()\n        childFragmentManager.beginTransaction()\n            .replace(R.id.pre_fragment, preferenceFragment, fragmentTag).commit()\n    }\n\n    override fun onCompatCreateOptionsMenu(menu: Menu) {\n        menuInflater.inflate(R.menu.main_my, menu)\n    }\n\n    override fun onCompatOptionsItemSelected(item: MenuItem) {\n        when (item.itemId) {\n            R.id.menu_help -> showHelp(\"appHelp\")\n        }\n    }\n\n    /**\n     * 配置\n     */\n    class MyPreferenceFragment : PreferenceFragment(),\n        SharedPreferences.OnSharedPreferenceChangeListener {\n\n        override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {\n            putPrefBoolean(PreferKey.webService, WebService.isRun)\n            addPreferencesFromResource(R.xml.pref_main)\n            findPreference<SwitchPreference>(\"webService\")?.onLongClick {\n                if (!WebService.isRun) {\n                    return@onLongClick false\n                }\n                context?.selector(arrayListOf(\"复制地址\", \"浏览器打开\")) { _, i ->\n                    when (i) {\n                        0 -> context?.sendToClip(it.summary.toString())\n                        1 -> context?.openUrl(it.summary.toString())\n                    }\n                }\n                true\n            }\n            observeEventSticky<String>(EventBus.WEB_SERVICE) {\n                findPreference<SwitchPreference>(PreferKey.webService)?.let {\n                    it.isChecked = WebService.isRun\n                    it.summary = if (WebService.isRun) {\n                        WebService.hostAddress\n                    } else {\n                        getString(R.string.web_service_desc)\n                    }\n                }\n            }\n            findPreference<NameListPreference>(PreferKey.themeMode)?.let {\n                it.setOnPreferenceChangeListener { _, _ ->\n                    view?.post { ThemeConfig.applyDayNight(requireContext()) }\n                    true\n                }\n            }\n        }\n\n        override fun onViewCreated(view: View, savedInstanceState: Bundle?) {\n            super.onViewCreated(view, savedInstanceState)\n            listView.setEdgeEffectColor(primaryColor)\n        }\n\n        override fun onResume() {\n            super.onResume()\n            preferenceManager.sharedPreferences?.registerOnSharedPreferenceChangeListener(this)\n        }\n\n        override fun onPause() {\n            preferenceManager.sharedPreferences?.unregisterOnSharedPreferenceChangeListener(this)\n            super.onPause()\n        }\n\n        override fun onSharedPreferenceChanged(\n            sharedPreferences: SharedPreferences?,\n            key: String?\n        ) {\n            when (key) {\n                PreferKey.webService -> {\n                    if (requireContext().getPrefBoolean(\"webService\")) {\n                        WebService.start(requireContext())\n                    } else {\n                        WebService.stop(requireContext())\n                    }\n                }\n\n                \"recordLog\" -> LogUtils.upLevel()\n            }\n        }\n\n        override fun onPreferenceTreeClick(preference: Preference): Boolean {\n            when (preference.key) {\n                \"bookSourceManage\" -> startActivity<BookSourceActivity>()\n                \"replaceManage\" -> startActivity<ReplaceRuleActivity>()\n                \"dictRuleManage\" -> startActivity<DictRuleActivity>()\n                \"txtTocRuleManage\" -> startActivity<TxtTocRuleActivity>()\n                \"bookmark\" -> startActivity<AllBookmarkActivity>()\n                \"setting\" -> startActivity<ConfigActivity> {\n                    putExtra(\"configTag\", ConfigTag.OTHER_CONFIG)\n                }\n\n                \"web_dav_setting\" -> startActivity<ConfigActivity> {\n                    putExtra(\"configTag\", ConfigTag.BACKUP_CONFIG)\n                }\n\n                \"theme_setting\" -> startActivity<ConfigActivity> {\n                    putExtra(\"configTag\", ConfigTag.THEME_CONFIG)\n                }\n\n                \"fileManage\" -> startActivity<FileManageActivity>()\n                \"readRecord\" -> startActivity<ReadRecordActivity>()\n                \"about\" -> startActivity<AboutActivity>()\n                \"exit\" -> activity?.finish()\n            }\n            return super.onPreferenceTreeClick(preference)\n        }\n\n\n    }\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/main/rss/RssAdapter.kt",
    "content": "package io.legado.app.ui.main.rss\n\nimport android.content.Context\nimport android.view.View\nimport android.view.ViewGroup\nimport androidx.appcompat.widget.PopupMenu\nimport androidx.fragment.app.Fragment\nimport androidx.lifecycle.Lifecycle\nimport com.bumptech.glide.request.RequestOptions\nimport io.legado.app.R\nimport io.legado.app.base.adapter.ItemViewHolder\nimport io.legado.app.base.adapter.RecyclerAdapter\nimport io.legado.app.data.entities.RssSource\nimport io.legado.app.databinding.ItemRssBinding\nimport io.legado.app.help.glide.ImageLoader\nimport io.legado.app.help.glide.OkHttpModelLoader\nimport splitties.views.onLongClick\n\nclass RssAdapter(\n    context: Context,\n    private val fragment: Fragment,\n    private val callBack: CallBack,\n    private val lifecycle: Lifecycle\n) : RecyclerAdapter<RssSource, ItemRssBinding>(context) {\n\n    override fun getViewBinding(parent: ViewGroup): ItemRssBinding {\n        return ItemRssBinding.inflate(inflater, parent, false)\n    }\n\n    override fun convert(\n        holder: ItemViewHolder,\n        binding: ItemRssBinding,\n        item: RssSource,\n        payloads: MutableList<Any>\n    ) {\n        binding.apply {\n            tvName.text = item.sourceName\n            val options = RequestOptions()\n                .set(OkHttpModelLoader.sourceOriginOption, item.sourceUrl)\n            ImageLoader.load(fragment, lifecycle, item.sourceIcon)\n                .apply(options)\n                .centerCrop()\n                .placeholder(R.drawable.image_rss)\n                .error(R.drawable.image_rss)\n                .into(ivIcon)\n        }\n    }\n\n    override fun registerListener(holder: ItemViewHolder, binding: ItemRssBinding) {\n        binding.apply {\n            root.setOnClickListener {\n                getItemByLayoutPosition(holder.layoutPosition)?.let {\n                    callBack.openRss(it)\n                }\n            }\n            root.onLongClick {\n                getItemByLayoutPosition(holder.layoutPosition)?.let {\n                    showMenu(ivIcon, it)\n                }\n            }\n        }\n    }\n\n    private fun showMenu(view: View, rssSource: RssSource) {\n        val popupMenu = PopupMenu(context, view)\n        popupMenu.inflate(R.menu.rss_main_item)\n        popupMenu.setOnMenuItemClickListener {\n            when (it.itemId) {\n                R.id.menu_top -> callBack.toTop(rssSource)\n                R.id.menu_edit -> callBack.edit(rssSource)\n                R.id.menu_del -> callBack.del(rssSource)\n                R.id.menu_disable -> callBack.disable(rssSource)\n            }\n            true\n        }\n        popupMenu.show()\n    }\n\n    interface CallBack {\n        fun openRss(rssSource: RssSource)\n        fun toTop(rssSource: RssSource)\n        fun edit(rssSource: RssSource)\n        fun del(rssSource: RssSource)\n        fun disable(rssSource: RssSource)\n    }\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/main/rss/RssFragment.kt",
    "content": "package io.legado.app.ui.main.rss\n\nimport android.os.Bundle\nimport android.view.Menu\nimport android.view.MenuItem\nimport android.view.SubMenu\nimport android.view.View\nimport androidx.appcompat.widget.SearchView\nimport androidx.fragment.app.viewModels\nimport androidx.lifecycle.Lifecycle\nimport androidx.lifecycle.lifecycleScope\nimport io.legado.app.R\nimport io.legado.app.base.VMBaseFragment\nimport io.legado.app.constant.AppLog\nimport io.legado.app.data.AppDatabase\nimport io.legado.app.data.appDb\nimport io.legado.app.data.entities.RssSource\nimport io.legado.app.databinding.FragmentRssBinding\nimport io.legado.app.databinding.ItemRssBinding\nimport io.legado.app.lib.dialogs.alert\nimport io.legado.app.lib.theme.primaryColor\nimport io.legado.app.lib.theme.primaryTextColor\nimport io.legado.app.ui.main.MainFragmentInterface\nimport io.legado.app.ui.rss.article.RssSortActivity\nimport io.legado.app.ui.rss.favorites.RssFavoritesActivity\nimport io.legado.app.ui.rss.read.ReadRssActivity\nimport io.legado.app.ui.rss.source.edit.RssSourceEditActivity\nimport io.legado.app.ui.rss.source.manage.RssSourceActivity\nimport io.legado.app.ui.rss.subscription.RuleSubActivity\nimport io.legado.app.utils.applyTint\nimport io.legado.app.utils.flowWithLifecycleAndDatabaseChange\nimport io.legado.app.utils.openUrl\nimport io.legado.app.utils.setEdgeEffectColor\nimport io.legado.app.utils.startActivity\nimport io.legado.app.utils.transaction\nimport io.legado.app.utils.viewbindingdelegate.viewBinding\nimport kotlinx.coroutines.Dispatchers.IO\nimport kotlinx.coroutines.Job\nimport kotlinx.coroutines.flow.catch\nimport kotlinx.coroutines.flow.conflate\nimport kotlinx.coroutines.flow.flowOn\nimport kotlinx.coroutines.launch\n\n\n/**\n * 订阅界面\n */\nclass RssFragment() : VMBaseFragment<RssViewModel>(R.layout.fragment_rss),\n    MainFragmentInterface,\n    RssAdapter.CallBack {\n\n    constructor(position: Int) : this() {\n        val bundle = Bundle()\n        bundle.putInt(\"position\", position)\n        arguments = bundle\n    }\n\n    override val position: Int? get() = arguments?.getInt(\"position\")\n\n    private val binding by viewBinding(FragmentRssBinding::bind)\n    override val viewModel by viewModels<RssViewModel>()\n    private val adapter by lazy {\n        RssAdapter(requireContext(), this, this, viewLifecycleOwner.lifecycle)\n    }\n    private val searchView: SearchView by lazy {\n        binding.titleBar.findViewById(R.id.search_view)\n    }\n    private var groupsFlowJob: Job? = null\n    private var rssFlowJob: Job? = null\n    private val groups = linkedSetOf<String>()\n    private var groupsMenu: SubMenu? = null\n\n    override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) {\n        setSupportToolbar(binding.titleBar.toolbar)\n        initSearchView()\n        initRecyclerView()\n        initGroupData()\n        upRssFlowJob()\n    }\n\n    override fun onCompatCreateOptionsMenu(menu: Menu) {\n        menuInflater.inflate(R.menu.main_rss, menu)\n        groupsMenu = menu.findItem(R.id.menu_group)?.subMenu\n        upGroupsMenu()\n    }\n\n    override fun onCompatOptionsItemSelected(item: MenuItem) {\n        super.onCompatOptionsItemSelected(item)\n        when (item.itemId) {\n            R.id.menu_rss_config -> startActivity<RssSourceActivity>()\n            R.id.menu_rss_star -> startActivity<RssFavoritesActivity>()\n            else -> if (item.groupId == R.id.menu_group_text) {\n                searchView.setQuery(\"group:${item.title}\", true)\n            }\n        }\n    }\n\n    override fun onPause() {\n        super.onPause()\n        searchView.clearFocus()\n    }\n\n    private fun upGroupsMenu() = groupsMenu?.transaction { subMenu ->\n        subMenu.removeGroup(R.id.menu_group_text)\n        groups.forEach {\n            subMenu.add(R.id.menu_group_text, Menu.NONE, Menu.NONE, it)\n        }\n    }\n\n    private fun initSearchView() {\n        searchView.applyTint(primaryTextColor)\n        searchView.isSubmitButtonEnabled = true\n        searchView.queryHint = getString(R.string.rss)\n        searchView.setOnQueryTextListener(object : SearchView.OnQueryTextListener {\n            override fun onQueryTextSubmit(query: String?): Boolean {\n                return false\n            }\n\n            override fun onQueryTextChange(newText: String?): Boolean {\n                upRssFlowJob(newText)\n                return false\n            }\n        })\n    }\n\n    private fun initRecyclerView() {\n        binding.recyclerView.setEdgeEffectColor(primaryColor)\n        binding.recyclerView.adapter = adapter\n        adapter.addHeaderView {\n            ItemRssBinding.inflate(layoutInflater, it, false).apply {\n                tvName.setText(R.string.rule_subscription)\n                ivIcon.setImageResource(R.drawable.image_legado)\n                root.setOnClickListener {\n                    startActivity<RuleSubActivity>()\n                }\n            }\n        }\n    }\n\n    private fun initGroupData() {\n        groupsFlowJob?.cancel()\n        groupsFlowJob = viewLifecycleOwner.lifecycleScope.launch {\n            appDb.rssSourceDao.flowEnabledGroups().catch {\n                AppLog.put(\"订阅界面获取分组数据失败\\n${it.localizedMessage}\", it)\n            }.flowWithLifecycleAndDatabaseChange(\n                viewLifecycleOwner.lifecycle,\n                Lifecycle.State.RESUMED,\n                AppDatabase.RSS_SOURCE_TABLE_NAME\n            ).conflate().collect {\n                groups.clear()\n                groups.addAll(it)\n                upGroupsMenu()\n            }\n        }\n    }\n\n    private fun upRssFlowJob(searchKey: String? = null) {\n        rssFlowJob?.cancel()\n        rssFlowJob = viewLifecycleOwner.lifecycleScope.launch {\n            when {\n                searchKey.isNullOrEmpty() -> appDb.rssSourceDao.flowEnabled()\n                searchKey.startsWith(\"group:\") -> {\n                    val key = searchKey.substringAfter(\"group:\")\n                    appDb.rssSourceDao.flowEnabledByGroup(key)\n                }\n\n                else -> appDb.rssSourceDao.flowEnabled(searchKey)\n            }.flowWithLifecycleAndDatabaseChange(\n                viewLifecycleOwner.lifecycle,\n                Lifecycle.State.RESUMED,\n                AppDatabase.RSS_SOURCE_TABLE_NAME\n            ).catch {\n                AppLog.put(\"订阅界面更新数据出错\", it)\n            }.flowOn(IO).collect {\n                adapter.setItems(it)\n            }\n        }\n    }\n\n    override fun openRss(rssSource: RssSource) {\n        if (rssSource.singleUrl) {\n            viewModel.getSingleUrl(rssSource) { url ->\n                if (url.startsWith(\"http\", true)) {\n                    startActivity<ReadRssActivity> {\n                        putExtra(\"title\", rssSource.sourceName)\n                        putExtra(\"origin\", url)\n                    }\n                } else {\n                    context?.openUrl(url)\n                }\n            }\n        } else {\n            startActivity<RssSortActivity> {\n                putExtra(\"url\", rssSource.sourceUrl)\n            }\n        }\n    }\n\n    override fun toTop(rssSource: RssSource) {\n        viewModel.topSource(rssSource)\n    }\n\n    override fun edit(rssSource: RssSource) {\n        startActivity<RssSourceEditActivity> {\n            putExtra(\"sourceUrl\", rssSource.sourceUrl)\n        }\n    }\n\n    override fun del(rssSource: RssSource) {\n        alert(R.string.draw) {\n            setMessage(getString(R.string.sure_del) + \"\\n\" + rssSource.sourceName)\n            noButton()\n            yesButton {\n                viewModel.del(rssSource)\n            }\n        }\n    }\n\n    override fun disable(rssSource: RssSource) {\n        viewModel.disable(rssSource)\n    }\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/main/rss/RssViewModel.kt",
    "content": "package io.legado.app.ui.main.rss\n\nimport android.app.Application\nimport com.script.rhino.runScriptWithContext\nimport io.legado.app.base.BaseViewModel\nimport io.legado.app.data.appDb\nimport io.legado.app.data.entities.RssSource\nimport io.legado.app.help.source.SourceHelp\nimport io.legado.app.utils.toastOnUi\n\nclass RssViewModel(application: Application) : BaseViewModel(application) {\n\n    fun topSource(vararg sources: RssSource) {\n        execute {\n            sources.sortBy { it.customOrder }\n            val minOrder = appDb.rssSourceDao.minOrder - 1\n            val array = Array(sources.size) {\n                sources[it].copy(customOrder = minOrder - it)\n            }\n            appDb.rssSourceDao.update(*array)\n        }\n    }\n\n    fun bottomSource(vararg sources: RssSource) {\n        execute {\n            sources.sortBy { it.customOrder }\n            val maxOrder = appDb.rssSourceDao.maxOrder + 1\n            val array = Array(sources.size) {\n                sources[it].copy(customOrder = maxOrder + it)\n            }\n            appDb.rssSourceDao.update(*array)\n        }\n    }\n\n    fun del(vararg rssSource: RssSource) {\n        execute {\n            SourceHelp.deleteRssSources(rssSource.toList())\n        }\n    }\n\n    fun disable(rssSource: RssSource) {\n        execute {\n            rssSource.enabled = false\n            appDb.rssSourceDao.update(rssSource)\n        }\n    }\n\n    fun getSingleUrl(rssSource: RssSource, onSuccess: (url: String) -> Unit) {\n        execute {\n            var sortUrl = rssSource.sortUrl\n            if (!sortUrl.isNullOrBlank()) {\n                if (sortUrl.startsWith(\"<js>\", false)\n                    || sortUrl.startsWith(\"@js:\", false)\n                ) {\n                    val jsStr = if (sortUrl.startsWith(\"@\")) {\n                        sortUrl.substring(4)\n                    } else {\n                        sortUrl.substring(4, sortUrl.lastIndexOf(\"<\"))\n                    }\n                    val result = runScriptWithContext {\n                        rssSource.evalJS(jsStr)?.toString()\n                    }\n                    if (!result.isNullOrBlank()) {\n                        sortUrl = result\n                    }\n                }\n                if (sortUrl.contains(\"::\")) {\n                    return@execute sortUrl.split(\"::\")[1]\n                } else {\n                    return@execute sortUrl\n                }\n            }\n            rssSource.sourceUrl\n        }.timeout(10000)\n            .onSuccess {\n                onSuccess.invoke(it)\n            }.onError {\n                context.toastOnUi(it.localizedMessage)\n            }\n    }\n\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/qrcode/QrCodeActivity.kt",
    "content": "package io.legado.app.ui.qrcode\n\nimport android.content.Intent\nimport android.graphics.BitmapFactory\nimport android.os.Bundle\nimport android.view.Menu\nimport android.view.MenuItem\nimport com.google.zxing.Result\nimport io.legado.app.R\nimport io.legado.app.base.BaseActivity\nimport io.legado.app.databinding.ActivityQrcodeCaptureBinding\nimport io.legado.app.ui.file.HandleFileContract\nimport io.legado.app.utils.QRCodeUtils\nimport io.legado.app.utils.readBytes\nimport io.legado.app.utils.viewbindingdelegate.viewBinding\n\nclass QrCodeActivity : BaseActivity<ActivityQrcodeCaptureBinding>(), ScanResultCallback {\n\n    override val binding by viewBinding(ActivityQrcodeCaptureBinding::inflate)\n\n    private val selectQrImage = registerForActivityResult(HandleFileContract()) {\n        it.uri?.readBytes(this)?.let { bytes ->\n            val bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.size)\n            onScanResultCallback(QRCodeUtils.parseCodeResult(bitmap))\n        }\n    }\n\n    override fun onActivityCreated(savedInstanceState: Bundle?) {\n        val fTag = \"qrCodeFragment\"\n        val qrCodeFragment = QrCodeFragment()\n        supportFragmentManager.beginTransaction()\n            .replace(R.id.fl_content, qrCodeFragment, fTag)\n            .commit()\n    }\n\n    override fun onCompatCreateOptionsMenu(menu: Menu): Boolean {\n        menuInflater.inflate(R.menu.qr_code_scan, menu)\n        return super.onCompatCreateOptionsMenu(menu)\n    }\n\n    override fun onCompatOptionsItemSelected(item: MenuItem): Boolean {\n        when (item.itemId) {\n            R.id.action_choose_from_gallery -> selectQrImage.launch {\n                mode = HandleFileContract.IMAGE\n            }\n        }\n        return super.onCompatOptionsItemSelected(item)\n    }\n\n    override fun onScanResultCallback(result: Result?) {\n        val intent = Intent()\n        intent.putExtra(\"result\", result?.text)\n        setResult(RESULT_OK, intent)\n        finish()\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/qrcode/QrCodeFragment.kt",
    "content": "package io.legado.app.ui.qrcode\n\nimport com.google.zxing.Result\nimport com.king.camera.scan.AnalyzeResult\nimport com.king.camera.scan.CameraScan\nimport com.king.zxing.BarcodeCameraScanFragment\nimport com.king.zxing.DecodeConfig\nimport com.king.zxing.DecodeFormatManager\nimport com.king.zxing.analyze.MultiFormatAnalyzer\n\nclass QrCodeFragment : BarcodeCameraScanFragment() {\n\n    override fun initCameraScan(cameraScan: CameraScan<Result>) {\n        super.initCameraScan(cameraScan)\n        //初始化解码配置\n        val decodeConfig = DecodeConfig()\n        //如果只有识别二维码的需求，这样设置效率会更高，不设置默认为DecodeFormatManager.DEFAULT_HINTS\n        decodeConfig.hints = DecodeFormatManager.QR_CODE_HINTS\n        //设置是否全区域识别，默认false\n        decodeConfig.isFullAreaScan = true\n        //设置识别区域比例，默认0.8，设置的比例最终会在预览区域裁剪基于此比例的一个矩形进行扫码识别\n        decodeConfig.areaRectRatio = 0.8f\n\n        //在启动预览之前，设置分析器，只识别二维码\n        cameraScan.setAnalyzer(MultiFormatAnalyzer(decodeConfig))\n    }\n\n    override fun onScanResultCallback(result: AnalyzeResult<Result>) {\n        cameraScan.setAnalyzeImage(false)\n        (activity as? QrCodeActivity)?.onScanResultCallback(result.result)\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/qrcode/QrCodeResult.kt",
    "content": "package io.legado.app.ui.qrcode\n\nimport android.app.Activity.RESULT_OK\nimport android.content.Context\nimport android.content.Intent\nimport androidx.activity.result.contract.ActivityResultContract\n\nclass QrCodeResult : ActivityResultContract<Unit?, String?>() {\n\n    override fun createIntent(context: Context, input: Unit?): Intent {\n        return Intent(context, QrCodeActivity::class.java)\n    }\n\n    override fun parseResult(resultCode: Int, intent: Intent?): String? {\n        if (resultCode == RESULT_OK) {\n            intent?.getStringExtra(\"result\")?.let {\n                return it\n            }\n        }\n        return null\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/qrcode/ScanResultCallback.kt",
    "content": "package io.legado.app.ui.qrcode\n\nimport com.google.zxing.Result\n\ninterface ScanResultCallback {\n\n    fun onScanResultCallback(result: Result?)\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/replace/GroupManageDialog.kt",
    "content": "package io.legado.app.ui.replace\n\nimport android.annotation.SuppressLint\nimport android.content.Context\nimport android.os.Bundle\nimport android.view.MenuItem\nimport android.view.View\nimport android.view.ViewGroup\nimport androidx.appcompat.widget.Toolbar\nimport androidx.fragment.app.activityViewModels\nimport androidx.lifecycle.lifecycleScope\nimport androidx.recyclerview.widget.LinearLayoutManager\nimport io.legado.app.R\nimport io.legado.app.base.BaseDialogFragment\nimport io.legado.app.base.adapter.ItemViewHolder\nimport io.legado.app.base.adapter.RecyclerAdapter\nimport io.legado.app.data.appDb\nimport io.legado.app.databinding.DialogEditTextBinding\nimport io.legado.app.databinding.DialogRecyclerViewBinding\nimport io.legado.app.databinding.ItemGroupManageBinding\nimport io.legado.app.lib.dialogs.alert\nimport io.legado.app.lib.theme.backgroundColor\nimport io.legado.app.lib.theme.primaryColor\nimport io.legado.app.ui.widget.recycler.VerticalDivider\nimport io.legado.app.utils.applyTint\nimport io.legado.app.utils.requestInputMethod\nimport io.legado.app.utils.setLayout\nimport io.legado.app.utils.viewbindingdelegate.viewBinding\nimport kotlinx.coroutines.flow.conflate\nimport kotlinx.coroutines.launch\n\n\nclass GroupManageDialog : BaseDialogFragment(R.layout.dialog_recycler_view),\n    Toolbar.OnMenuItemClickListener {\n\n    private val viewModel: ReplaceRuleViewModel by activityViewModels()\n    private val binding by viewBinding(DialogRecyclerViewBinding::bind)\n    private val adapter by lazy { GroupAdapter(requireContext()) }\n\n    override fun onStart() {\n        super.onStart()\n        setLayout(0.9f, 0.9f)\n    }\n\n    override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) {\n        view.setBackgroundColor(backgroundColor)\n        binding.toolBar.setBackgroundColor(primaryColor)\n        initView()\n        initData()\n    }\n\n    private fun initView() = binding.run {\n        toolBar.title = getString(R.string.group_manage)\n        toolBar.inflateMenu(R.menu.group_manage)\n        toolBar.menu.applyTint(requireContext())\n        toolBar.setOnMenuItemClickListener(this@GroupManageDialog)\n        recyclerView.layoutManager = LinearLayoutManager(requireContext())\n        recyclerView.addItemDecoration(VerticalDivider(requireContext()))\n        recyclerView.adapter = adapter\n    }\n\n    private fun initData() {\n        lifecycleScope.launch {\n            appDb.replaceRuleDao.flowGroups().conflate().collect {\n                adapter.setItems(it)\n            }\n        }\n    }\n\n    override fun onMenuItemClick(item: MenuItem?): Boolean {\n        when (item?.itemId) {\n            R.id.menu_add -> addGroup()\n        }\n        return true\n    }\n\n    @SuppressLint(\"InflateParams\")\n    private fun addGroup() {\n        alert(title = getString(R.string.add_group)) {\n            val alertBinding = DialogEditTextBinding.inflate(layoutInflater).apply {\n                editView.setHint(R.string.group_name)\n            }\n            customView { alertBinding.root }\n            okButton {\n                alertBinding.editView.text?.toString()?.let {\n                    if (it.isNotBlank()) {\n                        viewModel.addGroup(it)\n                    }\n                }\n            }\n            cancelButton()\n        }.requestInputMethod()\n    }\n\n    @SuppressLint(\"InflateParams\")\n    private fun editGroup(group: String) {\n        alert(title = getString(R.string.group_edit)) {\n            val alertBinding = DialogEditTextBinding.inflate(layoutInflater).apply {\n                editView.setHint(R.string.group_name)\n                editView.setText(group)\n            }\n            customView { alertBinding.root }\n            okButton {\n                viewModel.upGroup(group, alertBinding.editView.text?.toString())\n            }\n            cancelButton()\n        }.requestInputMethod()\n    }\n\n    private inner class GroupAdapter(context: Context) :\n        RecyclerAdapter<String, ItemGroupManageBinding>(context) {\n\n        override fun getViewBinding(parent: ViewGroup): ItemGroupManageBinding {\n            return ItemGroupManageBinding.inflate(inflater, parent, false)\n        }\n\n        override fun convert(\n            holder: ItemViewHolder,\n            binding: ItemGroupManageBinding,\n            item: String,\n            payloads: MutableList<Any>\n        ) {\n            binding.run {\n                root.setBackgroundColor(context.backgroundColor)\n                tvGroup.text = item\n            }\n        }\n\n        override fun registerListener(holder: ItemViewHolder, binding: ItemGroupManageBinding) {\n            binding.apply {\n                tvEdit.setOnClickListener {\n                    getItem(holder.layoutPosition)?.let {\n                        editGroup(it)\n                    }\n                }\n\n                tvDel.setOnClickListener {\n                    getItem(holder.layoutPosition)?.let { viewModel.delGroup(it) }\n                }\n            }\n        }\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/replace/ReplaceRuleActivity.kt",
    "content": "package io.legado.app.ui.replace\n\nimport android.annotation.SuppressLint\nimport android.app.Activity\nimport android.os.Bundle\nimport android.view.Menu\nimport android.view.MenuItem\nimport android.view.SubMenu\nimport androidx.activity.result.contract.ActivityResultContracts\nimport androidx.activity.viewModels\nimport androidx.appcompat.widget.PopupMenu\nimport androidx.appcompat.widget.SearchView\nimport androidx.lifecycle.lifecycleScope\nimport androidx.recyclerview.widget.ItemTouchHelper\nimport androidx.recyclerview.widget.LinearLayoutManager\nimport io.legado.app.R\nimport io.legado.app.base.VMBaseActivity\nimport io.legado.app.constant.AppLog\nimport io.legado.app.data.appDb\nimport io.legado.app.data.entities.ReplaceRule\nimport io.legado.app.databinding.ActivityReplaceRuleBinding\nimport io.legado.app.databinding.DialogEditTextBinding\nimport io.legado.app.help.DirectLinkUpload\nimport io.legado.app.help.book.ContentProcessor\nimport io.legado.app.help.coroutine.Coroutine\nimport io.legado.app.lib.dialogs.alert\nimport io.legado.app.lib.theme.primaryColor\nimport io.legado.app.lib.theme.primaryTextColor\nimport io.legado.app.ui.association.ImportReplaceRuleDialog\nimport io.legado.app.ui.file.HandleFileContract\nimport io.legado.app.ui.qrcode.QrCodeResult\nimport io.legado.app.ui.replace.edit.ReplaceEditActivity\nimport io.legado.app.ui.widget.SelectActionBar\nimport io.legado.app.ui.widget.recycler.DragSelectTouchHelper\nimport io.legado.app.ui.widget.recycler.ItemTouchCallback\nimport io.legado.app.ui.widget.recycler.VerticalDivider\nimport io.legado.app.utils.ACache\nimport io.legado.app.utils.GSON\nimport io.legado.app.utils.applyTint\nimport io.legado.app.utils.isAbsUrl\nimport io.legado.app.utils.launch\nimport io.legado.app.utils.sendToClip\nimport io.legado.app.utils.setEdgeEffectColor\nimport io.legado.app.utils.showDialogFragment\nimport io.legado.app.utils.showHelp\nimport io.legado.app.utils.splitNotBlank\nimport io.legado.app.utils.transaction\nimport io.legado.app.utils.viewbindingdelegate.viewBinding\nimport kotlinx.coroutines.Dispatchers.IO\nimport kotlinx.coroutines.Job\nimport kotlinx.coroutines.delay\nimport kotlinx.coroutines.flow.catch\nimport kotlinx.coroutines.flow.conflate\nimport kotlinx.coroutines.flow.flowOn\nimport kotlinx.coroutines.launch\n\n/**\n * 替换规则管理\n */\nclass ReplaceRuleActivity : VMBaseActivity<ActivityReplaceRuleBinding, ReplaceRuleViewModel>(),\n    SearchView.OnQueryTextListener,\n    PopupMenu.OnMenuItemClickListener,\n    SelectActionBar.CallBack,\n    ReplaceRuleAdapter.CallBack {\n    override val binding by viewBinding(ActivityReplaceRuleBinding::inflate)\n    override val viewModel by viewModels<ReplaceRuleViewModel>()\n    private val importRecordKey = \"replaceRuleRecordKey\"\n    private val adapter by lazy { ReplaceRuleAdapter(this, this) }\n    private val searchView: SearchView by lazy {\n        binding.titleBar.findViewById(R.id.search_view)\n    }\n    private var groups = arrayListOf<String>()\n    private var groupMenu: SubMenu? = null\n    private var replaceRuleFlowJob: Job? = null\n    private var dataInit = false\n    private val qrCodeResult = registerForActivityResult(QrCodeResult()) {\n        it ?: return@registerForActivityResult\n        showDialogFragment(ImportReplaceRuleDialog(it))\n    }\n    private val editActivity =\n        registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {\n            if (it.resultCode == RESULT_OK) {\n                setResult(RESULT_OK)\n            }\n        }\n    private val importDoc = registerForActivityResult(HandleFileContract()) {\n        it.uri?.let { uri ->\n            showDialogFragment(ImportReplaceRuleDialog(uri.toString()))\n        }\n    }\n    private val exportResult = registerForActivityResult(HandleFileContract()) {\n        it.uri?.let { uri ->\n            alert(R.string.export_success) {\n                if (uri.toString().isAbsUrl()) {\n                    setMessage(DirectLinkUpload.getSummary())\n                }\n                val alertBinding = DialogEditTextBinding.inflate(layoutInflater).apply {\n                    editView.hint = getString(R.string.path)\n                    editView.setText(uri.toString())\n                }\n                customView { alertBinding.root }\n                okButton {\n                    sendToClip(uri.toString())\n                }\n            }\n        }\n    }\n\n    override fun onActivityCreated(savedInstanceState: Bundle?) {\n        initRecyclerView()\n        initSearchView()\n        initSelectActionView()\n        observeReplaceRuleData()\n        observeGroupData()\n    }\n\n    override fun onCompatCreateOptionsMenu(menu: Menu): Boolean {\n        menuInflater.inflate(R.menu.replace_rule, menu)\n        return super.onCompatCreateOptionsMenu(menu)\n    }\n\n    override fun onPrepareOptionsMenu(menu: Menu): Boolean {\n        groupMenu = menu.findItem(R.id.menu_group)?.subMenu\n        upGroupMenu()\n        return super.onPrepareOptionsMenu(menu)\n    }\n\n    private fun initRecyclerView() {\n        binding.recyclerView.setEdgeEffectColor(primaryColor)\n        binding.recyclerView.layoutManager = LinearLayoutManager(this)\n        binding.recyclerView.adapter = adapter\n        binding.recyclerView.addItemDecoration(VerticalDivider(this))\n        val itemTouchCallback = ItemTouchCallback(adapter)\n        itemTouchCallback.isCanDrag = true\n        val dragSelectTouchHelper: DragSelectTouchHelper =\n            DragSelectTouchHelper(adapter.dragSelectCallback).setSlideArea(16, 50)\n        dragSelectTouchHelper.attachToRecyclerView(binding.recyclerView)\n        // When this page is opened, it is in selection mode\n        dragSelectTouchHelper.activeSlideSelect()\n\n        // Note: need judge selection first, so add ItemTouchHelper after it.\n        ItemTouchHelper(itemTouchCallback).attachToRecyclerView(binding.recyclerView)\n    }\n\n    private fun initSearchView() {\n        searchView.applyTint(primaryTextColor)\n        searchView.queryHint = getString(R.string.replace_purify_search)\n        searchView.setOnQueryTextListener(this)\n    }\n\n    override fun selectAll(selectAll: Boolean) {\n        if (selectAll) {\n            adapter.selectAll()\n        } else {\n            adapter.revertSelection()\n        }\n    }\n\n    override fun revertSelection() {\n        adapter.revertSelection()\n    }\n\n    override fun onClickSelectBarMainAction() {\n        alert(titleResource = R.string.draw, messageResource = R.string.sure_del) {\n            yesButton { viewModel.delSelection(adapter.selection) }\n            noButton()\n        }\n    }\n\n    private fun initSelectActionView() {\n        binding.selectActionBar.setMainActionText(R.string.delete)\n        binding.selectActionBar.inflateMenu(R.menu.replace_rule_sel)\n        binding.selectActionBar.setOnMenuItemClickListener(this)\n        binding.selectActionBar.setCallBack(this)\n    }\n\n    private fun observeReplaceRuleData(searchKey: String? = null) {\n        dataInit = false\n        replaceRuleFlowJob?.cancel()\n        replaceRuleFlowJob = lifecycleScope.launch {\n            when {\n                searchKey.isNullOrEmpty() -> {\n                    appDb.replaceRuleDao.flowAll()\n                }\n\n                searchKey == getString(R.string.no_group) -> {\n                    appDb.replaceRuleDao.flowNoGroup()\n                }\n\n                searchKey.startsWith(\"group:\") -> {\n                    val key = searchKey.substringAfter(\"group:\")\n                    appDb.replaceRuleDao.flowGroupSearch(\"%$key%\")\n                }\n\n                else -> {\n                    appDb.replaceRuleDao.flowSearch(\"%$searchKey%\")\n                }\n            }.catch {\n                AppLog.put(\"替换规则管理界面更新数据出错\", it)\n            }.flowOn(IO).conflate().collect {\n                if (dataInit) {\n                    setResult(Activity.RESULT_OK)\n                }\n                adapter.setItems(it, adapter.diffItemCallBack)\n                dataInit = true\n                delay(100)\n            }\n        }\n    }\n\n    private fun observeGroupData() {\n        lifecycleScope.launch {\n            appDb.replaceRuleDao.flowGroups().collect {\n                groups.clear()\n                groups.addAll(it)\n                upGroupMenu()\n            }\n        }\n    }\n\n    override fun onCompatOptionsItemSelected(item: MenuItem): Boolean {\n        when (item.itemId) {\n            R.id.menu_add_replace_rule ->\n                editActivity.launch(ReplaceEditActivity.startIntent(this))\n\n            R.id.menu_group_manage -> showDialogFragment<GroupManageDialog>()\n            R.id.menu_del_selection -> viewModel.delSelection(adapter.selection)\n            R.id.menu_import_onLine -> showImportDialog()\n            R.id.menu_import_local -> importDoc.launch {\n                mode = HandleFileContract.FILE\n                allowExtensions = arrayOf(\"txt\", \"json\")\n            }\n\n            R.id.menu_import_qr -> qrCodeResult.launch()\n            R.id.menu_help -> showHelp(\"replaceRuleHelp\")\n            R.id.menu_group_null -> {\n                searchView.setQuery(getString(R.string.no_group), true)\n            }\n\n            else -> if (item.groupId == R.id.replace_group) {\n                searchView.setQuery(\"group:${item.title}\", true)\n            }\n        }\n        return super.onCompatOptionsItemSelected(item)\n    }\n\n    override fun onMenuItemClick(item: MenuItem?): Boolean {\n        when (item?.itemId) {\n            R.id.menu_enable_selection -> viewModel.enableSelection(adapter.selection)\n            R.id.menu_disable_selection -> viewModel.disableSelection(adapter.selection)\n            R.id.menu_top_sel -> viewModel.topSelect(adapter.selection)\n            R.id.menu_bottom_sel -> viewModel.bottomSelect(adapter.selection)\n            R.id.menu_export_selection -> exportResult.launch {\n                mode = HandleFileContract.EXPORT\n                fileData = HandleFileContract.FileData(\n                    \"exportReplaceRule.json\",\n                    GSON.toJson(adapter.selection).toByteArray(),\n                    \"application/json\"\n                )\n            }\n        }\n        return false\n    }\n\n    private fun upGroupMenu() = groupMenu?.transaction { menu ->\n        menu.removeGroup(R.id.replace_group)\n        groups.forEach {\n            menu.add(R.id.replace_group, Menu.NONE, Menu.NONE, it)\n        }\n    }\n\n    @SuppressLint(\"InflateParams\")\n    private fun showImportDialog() {\n        val aCache = ACache.get(cacheDir = false)\n        val cacheUrls: MutableList<String> = aCache\n            .getAsString(importRecordKey)\n            ?.splitNotBlank(\",\")\n            ?.toMutableList() ?: mutableListOf()\n        alert(titleResource = R.string.import_on_line) {\n            val alertBinding = DialogEditTextBinding.inflate(layoutInflater).apply {\n                editView.hint = \"url\"\n                editView.setFilterValues(cacheUrls)\n                editView.delCallBack = {\n                    cacheUrls.remove(it)\n                    aCache.put(importRecordKey, cacheUrls.joinToString(\",\"))\n                }\n            }\n            customView { alertBinding.root }\n            okButton {\n                val text = alertBinding.editView.text?.toString()\n                text?.let {\n                    if (it.isAbsUrl() && !cacheUrls.contains(it)) {\n                        cacheUrls.add(0, it)\n                        aCache.put(importRecordKey, cacheUrls.joinToString(\",\"))\n                    }\n                    showDialogFragment(\n                        ImportReplaceRuleDialog(it)\n                    )\n                }\n            }\n            cancelButton()\n        }\n    }\n\n    override fun onQueryTextChange(newText: String?): Boolean {\n        observeReplaceRuleData(newText)\n        return false\n    }\n\n    override fun onQueryTextSubmit(query: String?): Boolean {\n        return false\n    }\n\n    override fun onDestroy() {\n        super.onDestroy()\n        Coroutine.async { ContentProcessor.upReplaceRules() }\n    }\n\n    override fun upCountView() {\n        binding.selectActionBar.upCountView(\n            adapter.selection.size,\n            adapter.itemCount\n        )\n    }\n\n    override fun update(vararg rule: ReplaceRule) {\n        setResult(RESULT_OK)\n        viewModel.update(*rule)\n    }\n\n    override fun delete(rule: ReplaceRule) {\n        alert(R.string.draw) {\n            setMessage(getString(R.string.sure_del) + \"\\n\" + rule.name)\n            noButton()\n            yesButton {\n                setResult(RESULT_OK)\n                viewModel.delete(rule)\n            }\n        }\n    }\n\n    override fun edit(rule: ReplaceRule) {\n        setResult(RESULT_OK)\n        editActivity.launch(ReplaceEditActivity.startIntent(this, rule.id))\n    }\n\n    override fun toTop(rule: ReplaceRule) {\n        setResult(RESULT_OK)\n        viewModel.toTop(rule)\n    }\n\n    override fun toBottom(rule: ReplaceRule) {\n        setResult(RESULT_OK)\n        viewModel.toBottom(rule)\n    }\n\n    override fun upOrder() {\n        setResult(RESULT_OK)\n        viewModel.upOrder()\n    }\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/replace/ReplaceRuleAdapter.kt",
    "content": "package io.legado.app.ui.replace\n\nimport android.content.Context\nimport android.os.Bundle\nimport android.view.View\nimport android.view.ViewGroup\nimport android.widget.PopupMenu\nimport androidx.core.os.bundleOf\nimport androidx.recyclerview.widget.DiffUtil\nimport androidx.recyclerview.widget.RecyclerView\nimport io.legado.app.R\nimport io.legado.app.base.adapter.ItemViewHolder\nimport io.legado.app.base.adapter.RecyclerAdapter\nimport io.legado.app.data.entities.ReplaceRule\nimport io.legado.app.databinding.ItemReplaceRuleBinding\nimport io.legado.app.lib.theme.backgroundColor\nimport io.legado.app.ui.widget.recycler.DragSelectTouchHelper\nimport io.legado.app.ui.widget.recycler.ItemTouchCallback\nimport io.legado.app.utils.ColorUtils\n\n\nclass ReplaceRuleAdapter(context: Context, var callBack: CallBack) :\n    RecyclerAdapter<ReplaceRule, ItemReplaceRuleBinding>(context),\n    ItemTouchCallback.Callback {\n\n    private val selected = linkedSetOf<ReplaceRule>()\n\n    val selection: List<ReplaceRule>\n        get() {\n            return getItems().filter {\n                selected.contains(it)\n            }\n        }\n\n    val diffItemCallBack = object : DiffUtil.ItemCallback<ReplaceRule>() {\n\n        override fun areItemsTheSame(oldItem: ReplaceRule, newItem: ReplaceRule): Boolean {\n            return oldItem.id == newItem.id\n        }\n\n        override fun areContentsTheSame(oldItem: ReplaceRule, newItem: ReplaceRule): Boolean {\n            if (oldItem.name != newItem.name) {\n                return false\n            }\n            if (oldItem.group != newItem.group) {\n                return false\n            }\n            if (oldItem.isEnabled != newItem.isEnabled) {\n                return false\n            }\n            return true\n        }\n\n        override fun getChangePayload(oldItem: ReplaceRule, newItem: ReplaceRule): Any? {\n            val payload = Bundle()\n            if (oldItem.name != newItem.name\n                || oldItem.group != newItem.group\n            ) {\n                payload.putBoolean(\"upName\", true)\n            }\n            if (oldItem.isEnabled != newItem.isEnabled) {\n                payload.putBoolean(\"enabled\", newItem.isEnabled)\n            }\n            if (payload.isEmpty) {\n                return null\n            }\n            return payload\n        }\n    }\n\n    fun selectAll() {\n        getItems().forEach {\n            selected.add(it)\n        }\n        notifyItemRangeChanged(0, itemCount, bundleOf(Pair(\"selected\", null)))\n        callBack.upCountView()\n    }\n\n    fun revertSelection() {\n        getItems().forEach {\n            if (selected.contains(it)) {\n                selected.remove(it)\n            } else {\n                selected.add(it)\n            }\n        }\n        notifyItemRangeChanged(0, itemCount, bundleOf(Pair(\"selected\", null)))\n        callBack.upCountView()\n    }\n\n    override fun getViewBinding(parent: ViewGroup): ItemReplaceRuleBinding {\n        return ItemReplaceRuleBinding.inflate(inflater, parent, false)\n    }\n\n    override fun onCurrentListChanged() {\n        callBack.upCountView()\n    }\n\n    override fun convert(\n        holder: ItemViewHolder,\n        binding: ItemReplaceRuleBinding,\n        item: ReplaceRule,\n        payloads: MutableList<Any>\n    ) {\n        binding.run {\n            if (payloads.isEmpty()) {\n                root.setBackgroundColor(ColorUtils.withAlpha(context.backgroundColor, 0.5f))\n                cbName.text = item.getDisplayNameGroup()\n                swtEnabled.isChecked = item.isEnabled\n                cbName.isChecked = selected.contains(item)\n            } else {\n                for (i in payloads.indices) {\n                    val bundle = payloads[i] as Bundle\n                    bundle.keySet().forEach {\n                        when (it) {\n                            \"selected\" -> cbName.isChecked = selected.contains(item)\n                            \"upName\" -> cbName.text = item.getDisplayNameGroup()\n                            \"enabled\" -> swtEnabled.isChecked = item.isEnabled\n                        }\n                    }\n                }\n            }\n        }\n    }\n\n    override fun registerListener(holder: ItemViewHolder, binding: ItemReplaceRuleBinding) {\n        binding.apply {\n            swtEnabled.setOnUserCheckedChangeListener { isChecked ->\n                getItem(holder.layoutPosition)?.let {\n                    it.isEnabled = isChecked\n                    callBack.update(it)\n                }\n            }\n            ivEdit.setOnClickListener {\n                getItem(holder.layoutPosition)?.let {\n                    callBack.edit(it)\n                }\n            }\n            cbName.setOnClickListener {\n                getItem(holder.layoutPosition)?.let {\n                    if (cbName.isChecked) {\n                        selected.add(it)\n                    } else {\n                        selected.remove(it)\n                    }\n                }\n                callBack.upCountView()\n            }\n            ivMenuMore.setOnClickListener {\n                showMenu(ivMenuMore, holder.layoutPosition)\n            }\n        }\n    }\n\n    private fun showMenu(view: View, position: Int) {\n        val item = getItem(position) ?: return\n        val popupMenu = PopupMenu(context, view)\n        popupMenu.inflate(R.menu.replace_rule_item)\n        popupMenu.setOnMenuItemClickListener { menuItem ->\n            when (menuItem.itemId) {\n                R.id.menu_top -> callBack.toTop(item)\n                R.id.menu_bottom -> callBack.toBottom(item)\n                R.id.menu_del -> {\n                    callBack.delete(item)\n                    selected.remove(item)\n                }\n            }\n            true\n        }\n        popupMenu.show()\n    }\n\n    override fun swap(srcPosition: Int, targetPosition: Int): Boolean {\n        val srcItem = getItem(srcPosition)\n        val targetItem = getItem(targetPosition)\n        if (srcItem != null && targetItem != null) {\n            if (srcItem.order == targetItem.order) {\n                callBack.upOrder()\n            } else {\n                val srcOrder = srcItem.order\n                srcItem.order = targetItem.order\n                targetItem.order = srcOrder\n                movedItems.add(srcItem)\n                movedItems.add(targetItem)\n            }\n        }\n        swapItem(srcPosition, targetPosition)\n        return true\n    }\n\n    private val movedItems = linkedSetOf<ReplaceRule>()\n\n    override fun onClearView(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder) {\n        if (movedItems.isNotEmpty()) {\n            callBack.update(*movedItems.toTypedArray())\n            movedItems.clear()\n        }\n    }\n\n    val dragSelectCallback: DragSelectTouchHelper.Callback =\n        object : DragSelectTouchHelper.AdvanceCallback<ReplaceRule>(Mode.ToggleAndReverse) {\n            override fun currentSelectedId(): MutableSet<ReplaceRule> {\n                return selected\n            }\n\n            override fun getItemId(position: Int): ReplaceRule {\n                return getItem(position)!!\n            }\n\n            override fun updateSelectState(position: Int, isSelected: Boolean): Boolean {\n                getItem(position)?.let {\n                    if (isSelected) {\n                        selected.add(it)\n                    } else {\n                        selected.remove(it)\n                    }\n                    notifyItemChanged(position, bundleOf(Pair(\"selected\", null)))\n                    callBack.upCountView()\n                    return true\n                }\n                return false\n            }\n        }\n\n    interface CallBack {\n        fun update(vararg rule: ReplaceRule)\n        fun delete(rule: ReplaceRule)\n        fun edit(rule: ReplaceRule)\n        fun toTop(rule: ReplaceRule)\n        fun toBottom(rule: ReplaceRule)\n        fun upOrder()\n        fun upCountView()\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/replace/ReplaceRuleViewModel.kt",
    "content": "package io.legado.app.ui.replace\n\nimport android.app.Application\nimport android.text.TextUtils\nimport io.legado.app.base.BaseViewModel\nimport io.legado.app.data.appDb\nimport io.legado.app.data.entities.ReplaceRule\nimport io.legado.app.utils.splitNotBlank\n\n/**\n * 替换规则数据修改\n * 修改数据要copy,直接修改会导致界面不刷新\n */\nclass ReplaceRuleViewModel(application: Application) : BaseViewModel(application) {\n\n    fun update(vararg rule: ReplaceRule) {\n        execute {\n            appDb.replaceRuleDao.update(*rule)\n        }\n    }\n\n    fun delete(rule: ReplaceRule) {\n        execute {\n            appDb.replaceRuleDao.delete(rule)\n        }\n    }\n\n    fun toTop(rule: ReplaceRule) {\n        execute {\n            rule.order = appDb.replaceRuleDao.minOrder - 1\n            appDb.replaceRuleDao.update(rule)\n        }\n    }\n\n    fun topSelect(rules: List<ReplaceRule>) {\n        execute {\n            var minOrder = appDb.replaceRuleDao.minOrder - rules.size\n            rules.forEach {\n                it.order = ++minOrder\n            }\n            appDb.replaceRuleDao.update(*rules.toTypedArray())\n        }\n    }\n\n    fun toBottom(rule: ReplaceRule) {\n        execute {\n            rule.order = appDb.replaceRuleDao.maxOrder + 1\n            appDb.replaceRuleDao.update(rule)\n        }\n    }\n\n    fun bottomSelect(rules: List<ReplaceRule>) {\n        execute {\n            var maxOrder = appDb.replaceRuleDao.maxOrder\n            rules.forEach {\n                it.order = maxOrder++\n            }\n            appDb.replaceRuleDao.update(*rules.toTypedArray())\n        }\n    }\n\n    fun upOrder() {\n        execute {\n            val rules = appDb.replaceRuleDao.all\n            for ((index, rule) in rules.withIndex()) {\n                rule.order = index + 1\n            }\n            appDb.replaceRuleDao.update(*rules.toTypedArray())\n        }\n    }\n\n    fun enableSelection(rules: List<ReplaceRule>) {\n        execute {\n            val array = Array(rules.size) {\n                rules[it].copy(isEnabled = true)\n            }\n            appDb.replaceRuleDao.update(*array)\n        }\n    }\n\n    fun disableSelection(rules: List<ReplaceRule>) {\n        execute {\n            val array = Array(rules.size) {\n                rules[it].copy(isEnabled = false)\n            }\n            appDb.replaceRuleDao.update(*array)\n        }\n    }\n\n    fun delSelection(rules: List<ReplaceRule>) {\n        execute {\n            appDb.replaceRuleDao.delete(*rules.toTypedArray())\n        }\n    }\n\n    fun addGroup(group: String) {\n        execute {\n            val sources = appDb.replaceRuleDao.noGroup\n            sources.forEach { source ->\n                source.group = group\n            }\n            appDb.replaceRuleDao.update(*sources.toTypedArray())\n        }\n    }\n\n    fun upGroup(oldGroup: String, newGroup: String?) {\n        execute {\n            val sources = appDb.replaceRuleDao.getByGroup(oldGroup)\n            sources.forEach { source ->\n                source.group?.splitNotBlank(\",\")?.toHashSet()?.let {\n                    it.remove(oldGroup)\n                    if (!newGroup.isNullOrEmpty())\n                        it.add(newGroup)\n                    source.group = TextUtils.join(\",\", it)\n                }\n            }\n            appDb.replaceRuleDao.update(*sources.toTypedArray())\n        }\n    }\n\n    fun delGroup(group: String) {\n        execute {\n            execute {\n                val sources = appDb.replaceRuleDao.getByGroup(group)\n                sources.forEach { source ->\n                    source.group?.splitNotBlank(\",\")?.toHashSet()?.let {\n                        it.remove(group)\n                        source.group = TextUtils.join(\",\", it)\n                    }\n                }\n                appDb.replaceRuleDao.update(*sources.toTypedArray())\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/replace/edit/ReplaceEditActivity.kt",
    "content": "package io.legado.app.ui.replace.edit\n\nimport android.content.Context\nimport android.content.Intent\nimport android.os.Bundle\nimport android.view.Menu\nimport android.view.MenuItem\nimport android.widget.EditText\nimport androidx.activity.viewModels\nimport androidx.lifecycle.lifecycleScope\nimport io.legado.app.R\nimport io.legado.app.base.VMBaseActivity\nimport io.legado.app.data.entities.ReplaceRule\nimport io.legado.app.databinding.ActivityReplaceEditBinding\nimport io.legado.app.lib.dialogs.SelectItem\nimport io.legado.app.ui.widget.keyboard.KeyboardToolPop\nimport io.legado.app.utils.GSON\nimport io.legado.app.utils.imeHeight\nimport io.legado.app.utils.sendToClip\nimport io.legado.app.utils.setOnApplyWindowInsetsListenerCompat\nimport io.legado.app.utils.showHelp\nimport io.legado.app.utils.viewbindingdelegate.viewBinding\n\n/**\n * 编辑替换规则\n */\nclass ReplaceEditActivity :\n    VMBaseActivity<ActivityReplaceEditBinding, ReplaceEditViewModel>(),\n    KeyboardToolPop.CallBack {\n\n    companion object {\n\n        fun startIntent(\n            context: Context,\n            id: Long = -1,\n            pattern: String? = null,\n            isRegex: Boolean = false,\n            scope: String? = null\n        ): Intent {\n            val intent = Intent(context, ReplaceEditActivity::class.java)\n            intent.putExtra(\"id\", id)\n            intent.putExtra(\"pattern\", pattern)\n            intent.putExtra(\"isRegex\", isRegex)\n            intent.putExtra(\"scope\", scope)\n            return intent\n        }\n\n    }\n\n    override val binding by viewBinding(ActivityReplaceEditBinding::inflate)\n    override val viewModel by viewModels<ReplaceEditViewModel>()\n\n    private val softKeyboardTool by lazy {\n        KeyboardToolPop(this, lifecycleScope, binding.root, this)\n    }\n\n    override fun onActivityCreated(savedInstanceState: Bundle?) {\n        softKeyboardTool.attachToWindow(window)\n        initView()\n        viewModel.initData(intent) {\n            upReplaceView(it)\n        }\n    }\n\n    override fun onCompatCreateOptionsMenu(menu: Menu): Boolean {\n        menuInflater.inflate(R.menu.replace_edit, menu)\n        return super.onCompatCreateOptionsMenu(menu)\n    }\n\n    override fun onCompatOptionsItemSelected(item: MenuItem): Boolean {\n        when (item.itemId) {\n            R.id.menu_save -> viewModel.save(getReplaceRule()) {\n                setResult(RESULT_OK)\n                finish()\n            }\n\n            R.id.menu_copy_rule -> sendToClip(GSON.toJson(getReplaceRule()))\n            R.id.menu_paste_rule -> viewModel.pasteRule {\n                upReplaceView(it)\n            }\n        }\n        return true\n    }\n\n    override fun onDestroy() {\n        super.onDestroy()\n        softKeyboardTool.dismiss()\n    }\n\n    private fun initView() {\n        binding.ivHelp.setOnClickListener {\n            showHelp(\"regexHelp\")\n        }\n        binding.root.setOnApplyWindowInsetsListenerCompat { _, windowInsets ->\n            softKeyboardTool.initialPadding = windowInsets.imeHeight\n            windowInsets\n        }\n    }\n\n    private fun upReplaceView(replaceRule: ReplaceRule) = binding.run {\n        etName.setText(replaceRule.name)\n        etGroup.setText(replaceRule.group)\n        etReplaceRule.setText(replaceRule.pattern)\n        cbUseRegex.isChecked = replaceRule.isRegex\n        etReplaceTo.setText(replaceRule.replacement)\n        cbScopeTitle.isChecked = replaceRule.scopeTitle\n        cbScopeContent.isChecked = replaceRule.scopeContent\n        etScope.setText(replaceRule.scope)\n        etExcludeScope.setText(replaceRule.excludeScope)\n        etTimeout.setText(replaceRule.timeoutMillisecond.toString())\n    }\n\n    private fun getReplaceRule(): ReplaceRule = binding.run {\n        val replaceRule: ReplaceRule = viewModel.replaceRule ?: ReplaceRule()\n        replaceRule.name = etName.text.toString()\n        replaceRule.group = etGroup.text.toString()\n        replaceRule.pattern = etReplaceRule.text.toString()\n        replaceRule.isRegex = cbUseRegex.isChecked\n        replaceRule.replacement = etReplaceTo.text.toString()\n        replaceRule.scopeTitle = cbScopeTitle.isChecked\n        replaceRule.scopeContent = cbScopeContent.isChecked\n        replaceRule.scope = etScope.text.toString()\n        replaceRule.excludeScope = etExcludeScope.text.toString()\n        replaceRule.timeoutMillisecond = etTimeout.text.toString().ifEmpty { \"3000\" }.toLong()\n        return replaceRule\n    }\n\n    override fun helpActions(): List<SelectItem<String>> {\n        return arrayListOf(\n            SelectItem(\"正则教程\", \"regexHelp\")\n        )\n    }\n\n    override fun onHelpActionSelect(action: String) {\n        when (action) {\n            \"regexHelp\" -> showHelp(\"regexHelp\")\n        }\n    }\n\n    override fun sendText(text: String) {\n        if (text.isBlank()) return\n        val view = window?.decorView?.findFocus()\n        if (view is EditText) {\n            val start = view.selectionStart\n            val end = view.selectionEnd\n            //获取EditText的文字\n            val edit = view.editableText\n            if (start < 0 || start >= edit.length) {\n                edit.append(text)\n            } else if (start > end) {\n                edit.replace(end, start, text)\n            } else {\n                //光标所在位置插入文字\n                edit.replace(start, end, text)\n            }\n        }\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/replace/edit/ReplaceEditViewModel.kt",
    "content": "package io.legado.app.ui.replace.edit\n\nimport android.app.Application\nimport android.content.Intent\nimport io.legado.app.base.BaseViewModel\nimport io.legado.app.data.appDb\nimport io.legado.app.data.entities.ReplaceRule\nimport io.legado.app.exception.NoStackTraceException\nimport io.legado.app.utils.*\nimport kotlinx.coroutines.Dispatchers\n\nclass ReplaceEditViewModel(application: Application) : BaseViewModel(application) {\n\n    var replaceRule: ReplaceRule? = null\n\n    fun initData(intent: Intent, finally: (replaceRule: ReplaceRule) -> Unit) {\n        execute {\n            val id = intent.getLongExtra(\"id\", -1)\n            replaceRule = if (id > 0) {\n                appDb.replaceRuleDao.findById(id)\n            } else {\n                val pattern = intent.getStringExtra(\"pattern\") ?: \"\"\n                val isRegex = intent.getBooleanExtra(\"isRegex\", false)\n                val scope = intent.getStringExtra(\"scope\")\n                ReplaceRule(\n                    name = pattern,\n                    pattern = pattern,\n                    isRegex = isRegex,\n                    scope = scope\n                )\n            }\n        }.onFinally {\n            replaceRule?.let {\n                finally(it)\n            }\n        }\n    }\n\n    fun pasteRule(success: (ReplaceRule) -> Unit) {\n        execute(context = Dispatchers.Main) {\n            val text = context.getClipText()\n            if (text.isNullOrBlank()) {\n                throw NoStackTraceException(\"剪贴板为空\")\n            }\n            GSON.fromJsonObject<ReplaceRule>(text).getOrNull()\n                ?: throw NoStackTraceException(\"格式不对\")\n        }.onSuccess {\n            success.invoke(it)\n        }.onError {\n            context.toastOnUi(it.localizedMessage ?: \"Error\")\n            it.printOnDebug()\n        }\n    }\n\n    fun save(replaceRule: ReplaceRule, success: () -> Unit) {\n        execute {\n            replaceRule.checkValid()\n            if (replaceRule.order == Int.MIN_VALUE) {\n                replaceRule.order = appDb.replaceRuleDao.maxOrder + 1\n            }\n            appDb.replaceRuleDao.insert(replaceRule)\n        }.onSuccess {\n            success()\n        }.onError {\n            context.toastOnUi(\"save error, ${it.localizedMessage}\")\n        }\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/rss/article/BaseRssArticlesAdapter.kt",
    "content": "package io.legado.app.ui.rss.article\n\nimport android.content.Context\nimport androidx.viewbinding.ViewBinding\nimport io.legado.app.base.adapter.RecyclerAdapter\nimport io.legado.app.data.entities.RssArticle\n\n\nabstract class BaseRssArticlesAdapter<VB : ViewBinding>(context: Context, val callBack: CallBack) :\n    RecyclerAdapter<RssArticle, VB>(context) {\n\n    interface CallBack {\n        val isGridLayout: Boolean\n        fun readRss(rssArticle: RssArticle)\n    }\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/rss/article/ReadRecordDialog.kt",
    "content": "package io.legado.app.ui.rss.article\n\nimport android.content.Context\nimport android.os.Bundle\nimport android.view.MenuItem\nimport android.view.View\nimport android.view.ViewGroup\nimport androidx.appcompat.widget.Toolbar\nimport androidx.fragment.app.viewModels\nimport androidx.recyclerview.widget.LinearLayoutManager\nimport io.legado.app.R\nimport io.legado.app.base.BaseDialogFragment\nimport io.legado.app.base.adapter.ItemViewHolder\nimport io.legado.app.base.adapter.RecyclerAdapter\nimport io.legado.app.data.entities.RssReadRecord\nimport io.legado.app.databinding.DialogRecyclerViewBinding\nimport io.legado.app.databinding.ItemRssReadRecordBinding\nimport io.legado.app.lib.dialogs.alert\nimport io.legado.app.lib.theme.primaryColor\nimport io.legado.app.utils.setLayout\nimport io.legado.app.utils.viewbindingdelegate.viewBinding\n\nclass ReadRecordDialog : BaseDialogFragment(R.layout.dialog_recycler_view),\n    Toolbar.OnMenuItemClickListener {\n\n    private val viewModel by viewModels<RssSortViewModel>()\n    private val binding by viewBinding(DialogRecyclerViewBinding::bind)\n    private val adapter by lazy {\n        ReadRecordAdapter(requireContext())\n    }\n\n    override fun onStart() {\n        super.onStart()\n        setLayout(0.9f, ViewGroup.LayoutParams.WRAP_CONTENT)\n    }\n\n    override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) {\n        binding.run {\n            toolBar.setBackgroundColor(primaryColor)\n            toolBar.setTitle(R.string.read_record)\n            toolBar.inflateMenu(R.menu.rss_read_record)\n            toolBar.setOnMenuItemClickListener(this@ReadRecordDialog)\n            recyclerView.layoutManager = LinearLayoutManager(requireContext())\n            recyclerView.adapter = adapter\n        }\n        adapter.setItems(viewModel.getRecords())\n    }\n\n    override fun onMenuItemClick(item: MenuItem?): Boolean {\n        when (item?.itemId) {\n            R.id.menu_clear -> {\n                alert(R.string.draw) {\n                    val countRead = viewModel.countRecords()\n                    setMessage(getString(R.string.sure_del) + \"\\n\" + countRead + \" \" + getString(R.string.read_record))\n                    noButton()\n                    yesButton {\n                        viewModel.deleteAllRecord()\n                        adapter.clearItems()\n                    }\n                }\n            }\n        }\n        return true\n    }\n\n    inner class ReadRecordAdapter(context: Context) :\n        RecyclerAdapter<RssReadRecord, ItemRssReadRecordBinding>(context) {\n\n        override fun getViewBinding(parent: ViewGroup): ItemRssReadRecordBinding {\n            return ItemRssReadRecordBinding.inflate(inflater, parent, false)\n        }\n\n        override fun convert(\n            holder: ItemViewHolder,\n            binding: ItemRssReadRecordBinding,\n            item: RssReadRecord,\n            payloads: MutableList<Any>\n        ) {\n            binding.textTitle.text = item.title\n            binding.textRecord.text = item.record\n        }\n\n        override fun registerListener(holder: ItemViewHolder, binding: ItemRssReadRecordBinding) {\n        }\n\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/rss/article/RssArticlesAdapter.kt",
    "content": "package io.legado.app.ui.rss.article\n\nimport android.annotation.SuppressLint\nimport android.content.Context\nimport android.graphics.drawable.Drawable\nimport android.view.ViewGroup\nimport com.bumptech.glide.load.DataSource\nimport com.bumptech.glide.load.engine.GlideException\nimport com.bumptech.glide.request.RequestListener\nimport com.bumptech.glide.request.RequestOptions\nimport com.bumptech.glide.request.target.Target\nimport io.legado.app.R\nimport io.legado.app.base.adapter.ItemViewHolder\nimport io.legado.app.data.entities.RssArticle\nimport io.legado.app.databinding.ItemRssArticleBinding\nimport io.legado.app.help.glide.ImageLoader\nimport io.legado.app.help.glide.OkHttpModelLoader\nimport io.legado.app.utils.getCompatColor\nimport io.legado.app.utils.gone\nimport io.legado.app.utils.visible\n\n\nclass RssArticlesAdapter(context: Context, callBack: CallBack) :\n    BaseRssArticlesAdapter<ItemRssArticleBinding>(context, callBack) {\n\n    override fun getViewBinding(parent: ViewGroup): ItemRssArticleBinding {\n        return ItemRssArticleBinding.inflate(inflater, parent, false)\n    }\n\n    @SuppressLint(\"CheckResult\")\n    override fun convert(\n        holder: ItemViewHolder,\n        binding: ItemRssArticleBinding,\n        item: RssArticle,\n        payloads: MutableList<Any>\n    ) {\n        binding.run {\n            tvTitle.text = item.title\n            tvPubDate.text = item.pubDate\n            if (item.image.isNullOrBlank() && !callBack.isGridLayout) {\n                imageView.gone()\n            } else {\n                val options =\n                    RequestOptions().set(OkHttpModelLoader.sourceOriginOption, item.origin)\n                ImageLoader.load(context, item.image).apply(options).apply {\n                    if (callBack.isGridLayout) {\n                        placeholder(R.drawable.image_rss_article)\n                    } else {\n                        addListener(object : RequestListener<Drawable> {\n                            override fun onLoadFailed(\n                                e: GlideException?,\n                                model: Any?,\n                                target: Target<Drawable>,\n                                isFirstResource: Boolean\n                            ): Boolean {\n                                imageView.gone()\n                                return false\n                            }\n\n                            override fun onResourceReady(\n                                resource: Drawable,\n                                model: Any,\n                                target: Target<Drawable>?,\n                                dataSource: DataSource,\n                                isFirstResource: Boolean\n                            ): Boolean {\n                                imageView.visible()\n                                return false\n                            }\n\n                        })\n                    }\n                }.into(imageView)\n            }\n            if (item.read) {\n                tvTitle.setTextColor(context.getCompatColor(R.color.tv_text_summary))\n            } else {\n                tvTitle.setTextColor(context.getCompatColor(R.color.primaryText))\n            }\n        }\n    }\n\n    override fun registerListener(holder: ItemViewHolder, binding: ItemRssArticleBinding) {\n        holder.itemView.setOnClickListener {\n            getItem(holder.layoutPosition)?.let {\n                callBack.readRss(it)\n            }\n        }\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/rss/article/RssArticlesAdapter1.kt",
    "content": "package io.legado.app.ui.rss.article\n\nimport android.annotation.SuppressLint\nimport android.content.Context\nimport android.graphics.drawable.Drawable\nimport android.view.ViewGroup\nimport com.bumptech.glide.load.DataSource\nimport com.bumptech.glide.load.engine.GlideException\nimport com.bumptech.glide.request.RequestListener\nimport com.bumptech.glide.request.RequestOptions\nimport com.bumptech.glide.request.target.Target\nimport io.legado.app.R\nimport io.legado.app.base.adapter.ItemViewHolder\nimport io.legado.app.data.entities.RssArticle\nimport io.legado.app.databinding.ItemRssArticle1Binding\nimport io.legado.app.help.glide.ImageLoader\nimport io.legado.app.help.glide.OkHttpModelLoader\nimport io.legado.app.utils.getCompatColor\nimport io.legado.app.utils.gone\nimport io.legado.app.utils.visible\n\n\nclass RssArticlesAdapter1(context: Context, callBack: CallBack) :\n    BaseRssArticlesAdapter<ItemRssArticle1Binding>(context, callBack) {\n\n    override fun getViewBinding(parent: ViewGroup): ItemRssArticle1Binding {\n        return ItemRssArticle1Binding.inflate(inflater, parent, false)\n    }\n\n    @SuppressLint(\"CheckResult\")\n    override fun convert(\n        holder: ItemViewHolder,\n        binding: ItemRssArticle1Binding,\n        item: RssArticle,\n        payloads: MutableList<Any>\n    ) {\n        binding.run {\n            tvTitle.text = item.title\n            tvPubDate.text = item.pubDate\n            if (item.image.isNullOrBlank() && !callBack.isGridLayout) {\n                imageView.gone()\n            } else {\n                val options =\n                    RequestOptions().set(OkHttpModelLoader.sourceOriginOption, item.origin)\n                ImageLoader.load(context, item.image).apply(options).apply {\n                    if (callBack.isGridLayout) {\n                        placeholder(R.drawable.image_rss_article)\n                    } else {\n                        addListener(object : RequestListener<Drawable> {\n                            override fun onLoadFailed(\n                                e: GlideException?,\n                                model: Any?,\n                                target: Target<Drawable>,\n                                isFirstResource: Boolean\n                            ): Boolean {\n                                imageView.gone()\n                                return false\n                            }\n\n                            override fun onResourceReady(\n                                resource: Drawable,\n                                model: Any,\n                                target: Target<Drawable>?,\n                                dataSource: DataSource,\n                                isFirstResource: Boolean\n                            ): Boolean {\n                                imageView.visible()\n                                return false\n                            }\n\n                        })\n                    }\n                }.into(imageView)\n            }\n            if (item.read) {\n                tvTitle.setTextColor(context.getCompatColor(R.color.tv_text_summary))\n            } else {\n                tvTitle.setTextColor(context.getCompatColor(R.color.primaryText))\n            }\n        }\n    }\n\n    override fun registerListener(holder: ItemViewHolder, binding: ItemRssArticle1Binding) {\n        holder.itemView.setOnClickListener {\n            getItem(holder.layoutPosition)?.let {\n                callBack.readRss(it)\n            }\n        }\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/rss/article/RssArticlesAdapter2.kt",
    "content": "package io.legado.app.ui.rss.article\n\nimport android.annotation.SuppressLint\nimport android.content.Context\nimport android.graphics.drawable.Drawable\nimport android.view.ViewGroup\nimport com.bumptech.glide.load.DataSource\nimport com.bumptech.glide.load.engine.GlideException\nimport com.bumptech.glide.request.RequestListener\nimport com.bumptech.glide.request.RequestOptions\nimport com.bumptech.glide.request.target.Target\nimport io.legado.app.R\nimport io.legado.app.base.adapter.ItemViewHolder\nimport io.legado.app.data.entities.RssArticle\nimport io.legado.app.databinding.ItemRssArticle2Binding\nimport io.legado.app.help.glide.ImageLoader\nimport io.legado.app.help.glide.OkHttpModelLoader\nimport io.legado.app.utils.getCompatColor\nimport io.legado.app.utils.gone\nimport io.legado.app.utils.visible\n\n\nclass RssArticlesAdapter2(context: Context, callBack: CallBack) :\n    BaseRssArticlesAdapter<ItemRssArticle2Binding>(context, callBack) {\n\n    override fun getViewBinding(parent: ViewGroup): ItemRssArticle2Binding {\n        return ItemRssArticle2Binding.inflate(inflater, parent, false)\n    }\n\n    @SuppressLint(\"CheckResult\")\n    override fun convert(\n        holder: ItemViewHolder,\n        binding: ItemRssArticle2Binding,\n        item: RssArticle,\n        payloads: MutableList<Any>\n    ) {\n        binding.run {\n            tvTitle.text = item.title\n            tvPubDate.text = item.pubDate\n            if (item.image.isNullOrBlank() && !callBack.isGridLayout) {\n                imageView.gone()\n            } else {\n                val options =\n                    RequestOptions().set(OkHttpModelLoader.sourceOriginOption, item.origin)\n                ImageLoader.load(context, item.image).apply(options).apply {\n                    if (callBack.isGridLayout) {\n                        placeholder(R.drawable.image_rss_article)\n                    } else {\n                        addListener(object : RequestListener<Drawable> {\n                            override fun onLoadFailed(\n                                e: GlideException?,\n                                model: Any?,\n                                target: Target<Drawable>,\n                                isFirstResource: Boolean\n                            ): Boolean {\n                                imageView.gone()\n                                return false\n                            }\n\n                            override fun onResourceReady(\n                                resource: Drawable,\n                                model: Any,\n                                target: Target<Drawable>?,\n                                dataSource: DataSource,\n                                isFirstResource: Boolean\n                            ): Boolean {\n                                imageView.visible()\n                                return false\n                            }\n\n                        })\n                    }\n                }.into(imageView)\n            }\n            if (item.read) {\n                tvTitle.setTextColor(context.getCompatColor(R.color.tv_text_summary))\n            } else {\n                tvTitle.setTextColor(context.getCompatColor(R.color.primaryText))\n            }\n        }\n    }\n\n    override fun registerListener(holder: ItemViewHolder, binding: ItemRssArticle2Binding) {\n        holder.itemView.setOnClickListener {\n            getItem(holder.layoutPosition)?.let {\n                callBack.readRss(it)\n            }\n        }\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/rss/article/RssArticlesFragment.kt",
    "content": "package io.legado.app.ui.rss.article\n\n\nimport android.os.Bundle\nimport android.view.View\nimport androidx.fragment.app.activityViewModels\nimport androidx.fragment.app.viewModels\nimport androidx.lifecycle.Lifecycle\nimport androidx.lifecycle.lifecycleScope\nimport androidx.lifecycle.repeatOnLifecycle\nimport androidx.recyclerview.widget.GridLayoutManager\nimport androidx.recyclerview.widget.LinearLayoutManager\nimport androidx.recyclerview.widget.RecyclerView\nimport io.legado.app.R\nimport io.legado.app.base.VMBaseFragment\nimport io.legado.app.constant.AppLog\nimport io.legado.app.data.appDb\nimport io.legado.app.data.entities.RssArticle\nimport io.legado.app.databinding.FragmentRssArticlesBinding\nimport io.legado.app.databinding.ViewLoadMoreBinding\nimport io.legado.app.lib.theme.accentColor\nimport io.legado.app.lib.theme.primaryColor\nimport io.legado.app.ui.rss.read.ReadRssActivity\nimport io.legado.app.ui.widget.recycler.LoadMoreView\nimport io.legado.app.ui.widget.recycler.VerticalDivider\nimport io.legado.app.utils.applyNavigationBarPadding\nimport io.legado.app.utils.setEdgeEffectColor\nimport io.legado.app.utils.startActivity\nimport io.legado.app.utils.viewbindingdelegate.viewBinding\nimport kotlinx.coroutines.Dispatchers.IO\nimport kotlinx.coroutines.Job\nimport kotlinx.coroutines.cancel\nimport kotlinx.coroutines.flow.catch\nimport kotlinx.coroutines.flow.flowOn\nimport kotlinx.coroutines.launch\n\nclass RssArticlesFragment() : VMBaseFragment<RssArticlesViewModel>(R.layout.fragment_rss_articles),\n    BaseRssArticlesAdapter.CallBack {\n\n    constructor(sortName: String, sortUrl: String) : this() {\n        arguments = Bundle().apply {\n            putString(\"sortName\", sortName)\n            putString(\"sortUrl\", sortUrl)\n        }\n    }\n\n    private val binding by viewBinding(FragmentRssArticlesBinding::bind)\n    private val activityViewModel by activityViewModels<RssSortViewModel>()\n    override val viewModel by viewModels<RssArticlesViewModel>()\n    private val adapter: BaseRssArticlesAdapter<*> by lazy {\n        when (activityViewModel.rssSource?.articleStyle) {\n            1 -> RssArticlesAdapter1(requireContext(), this@RssArticlesFragment)\n            2 -> RssArticlesAdapter2(requireContext(), this@RssArticlesFragment)\n            else -> RssArticlesAdapter(requireContext(), this@RssArticlesFragment)\n        }\n    }\n    private val loadMoreView: LoadMoreView by lazy {\n        LoadMoreView(requireContext())\n    }\n    private var articlesFlowJob: Job? = null\n    override val isGridLayout: Boolean\n        get() = activityViewModel.isGridLayout\n\n    override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) {\n        viewModel.init(arguments)\n        initView()\n        initData()\n    }\n\n    private fun initView() = binding.run {\n        refreshLayout.setColorSchemeColors(accentColor)\n        recyclerView.setEdgeEffectColor(primaryColor)\n        recyclerView.applyNavigationBarPadding()\n        loadMoreView.setOnClickListener {\n            if (!loadMoreView.isLoading) {\n                scrollToBottom(true)\n            }\n        }\n        recyclerView.layoutManager = if (activityViewModel.isGridLayout) {\n            recyclerView.setPadding(8, 0, 8, 0)\n            GridLayoutManager(requireContext(), 2)\n        } else {\n            recyclerView.addItemDecoration(VerticalDivider(requireContext()))\n            LinearLayoutManager(requireContext())\n        }\n        recyclerView.adapter = adapter\n        adapter.addFooterView {\n            ViewLoadMoreBinding.bind(loadMoreView)\n        }\n        refreshLayout.setOnRefreshListener {\n            loadArticles()\n        }\n        recyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() {\n            override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {\n                super.onScrolled(recyclerView, dx, dy)\n                if (!recyclerView.canScrollVertically(1)) {\n                    scrollToBottom()\n                }\n            }\n        })\n        viewLifecycleOwner.lifecycleScope.launch {\n            repeatOnLifecycle(Lifecycle.State.RESUMED) {\n                refreshLayout.isRefreshing = true\n                loadArticles()\n                this@launch.cancel()\n            }\n        }\n    }\n\n    private fun initData() {\n        val rssUrl = activityViewModel.url ?: return\n        articlesFlowJob?.cancel()\n        articlesFlowJob = viewLifecycleOwner.lifecycleScope.launch {\n            appDb.rssArticleDao.flowByOriginSort(rssUrl, viewModel.sortName).catch {\n                AppLog.put(\"订阅文章界面获取数据失败\\n${it.localizedMessage}\", it)\n            }.flowOn(IO).collect {\n                adapter.setItems(it)\n            }\n        }\n    }\n\n    private fun loadArticles() {\n        activityViewModel.rssSource?.let {\n            viewModel.loadArticles(it)\n        }\n    }\n\n    private fun scrollToBottom(forceLoad: Boolean = false) {\n        if (viewModel.isLoading) return\n        if ((loadMoreView.hasMore && adapter.getActualItemCount() > 0) || forceLoad) {\n            loadMoreView.hasMore()\n            activityViewModel.rssSource?.let {\n                viewModel.loadMore(it)\n            }\n        }\n    }\n\n    override fun observeLiveBus() {\n        viewModel.loadErrorLiveData.observe(viewLifecycleOwner) {\n            loadMoreView.error(it)\n        }\n        viewModel.loadFinallyLiveData.observe(viewLifecycleOwner) { hasMore ->\n            binding.refreshLayout.isRefreshing = false\n            if (!hasMore) {\n                loadMoreView.noMore()\n            }\n        }\n    }\n\n    override fun readRss(rssArticle: RssArticle) {\n        activityViewModel.read(rssArticle)\n        startActivity<ReadRssActivity> {\n            putExtra(\"title\", rssArticle.title)\n            putExtra(\"origin\", rssArticle.origin)\n            putExtra(\"link\", rssArticle.link)\n        }\n    }\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/rss/article/RssArticlesViewModel.kt",
    "content": "package io.legado.app.ui.rss.article\n\nimport android.app.Application\nimport android.os.Bundle\nimport androidx.lifecycle.MutableLiveData\nimport androidx.lifecycle.viewModelScope\nimport io.legado.app.base.BaseViewModel\nimport io.legado.app.constant.AppLog\nimport io.legado.app.data.appDb\nimport io.legado.app.data.entities.RssArticle\nimport io.legado.app.data.entities.RssSource\nimport io.legado.app.model.rss.Rss\nimport io.legado.app.utils.stackTraceStr\nimport kotlinx.coroutines.Dispatchers.IO\n\n\nclass RssArticlesViewModel(application: Application) : BaseViewModel(application) {\n    val loadFinallyLiveData = MutableLiveData<Boolean>()\n    val loadErrorLiveData = MutableLiveData<String>()\n    var isLoading = true\n    var order = System.currentTimeMillis()\n    private var nextPageUrl: String? = null\n    var sortName: String = \"\"\n    var sortUrl: String = \"\"\n    var page = 1\n\n    fun init(bundle: Bundle?) {\n        bundle?.let {\n            sortName = it.getString(\"sortName\") ?: \"\"\n            sortUrl = it.getString(\"sortUrl\") ?: \"\"\n        }\n    }\n\n    fun loadArticles(rssSource: RssSource) {\n        isLoading = true\n        page = 1\n        order = System.currentTimeMillis()\n        Rss.getArticles(viewModelScope, sortName, sortUrl, rssSource, page).onSuccess(IO) {\n            nextPageUrl = it.second\n            val articles = it.first\n            articles.forEach { rssArticle ->\n                rssArticle.order = order--\n            }\n            appDb.rssArticleDao.insert(*articles.toTypedArray())\n            if (!rssSource.ruleNextPage.isNullOrEmpty()) {\n                appDb.rssArticleDao.clearOld(rssSource.sourceUrl, sortName, order)\n            }\n            val hasMore = articles.isNotEmpty() && !rssSource.ruleNextPage.isNullOrEmpty()\n            loadFinallyLiveData.postValue(hasMore)\n            isLoading = false\n        }.onError {\n            loadFinallyLiveData.postValue(false)\n            AppLog.put(\"rss获取内容失败\", it)\n            loadErrorLiveData.postValue(it.stackTraceStr)\n        }\n    }\n\n    fun loadMore(rssSource: RssSource) {\n        isLoading = true\n        page++\n        val pageUrl = nextPageUrl\n        if (pageUrl.isNullOrEmpty()) {\n            loadFinallyLiveData.postValue(false)\n            return\n        }\n        Rss.getArticles(viewModelScope, sortName, pageUrl, rssSource, page).onSuccess(IO) {\n            nextPageUrl = it.second\n            loadMoreSuccess(it.first)\n            isLoading = false\n        }.onError {\n            loadFinallyLiveData.postValue(false)\n            AppLog.put(\"rss获取内容失败\", it)\n            loadErrorLiveData.postValue(it.stackTraceStr)\n        }\n    }\n\n    private fun loadMoreSuccess(articles: MutableList<RssArticle>) {\n        if (articles.isEmpty()) {\n            loadFinallyLiveData.postValue(false)\n            return\n        }\n        val firstArticle = articles.first()\n        val dbFirstArticle = appDb.rssArticleDao.get(firstArticle.origin, firstArticle.link)\n        val lastArticle = articles.last()\n        val dbLastArticle = appDb.rssArticleDao.get(lastArticle.origin, lastArticle.link)\n        if (dbFirstArticle != null && dbLastArticle != null) {\n            loadFinallyLiveData.postValue(false)\n        } else {\n            articles.forEach {\n                it.order = order--\n            }\n            appDb.rssArticleDao.append(*articles.toTypedArray())\n        }\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/rss/article/RssSortActivity.kt",
    "content": "@file:Suppress(\"DEPRECATION\")\n\npackage io.legado.app.ui.rss.article\n\nimport android.os.Bundle\nimport android.view.Menu\nimport android.view.MenuItem\nimport android.view.ViewGroup\nimport androidx.activity.viewModels\nimport androidx.fragment.app.Fragment\nimport androidx.fragment.app.FragmentStatePagerAdapter\nimport androidx.lifecycle.lifecycleScope\nimport io.legado.app.R\nimport io.legado.app.base.VMBaseActivity\nimport io.legado.app.databinding.ActivityRssArtivlesBinding\nimport io.legado.app.help.source.sortUrls\nimport io.legado.app.lib.theme.accentColor\nimport io.legado.app.ui.login.SourceLoginActivity\nimport io.legado.app.ui.rss.source.edit.RssSourceEditActivity\nimport io.legado.app.ui.widget.dialog.VariableDialog\nimport io.legado.app.utils.*\nimport io.legado.app.utils.viewbindingdelegate.viewBinding\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.launch\nimport kotlinx.coroutines.withContext\n\nclass RssSortActivity : VMBaseActivity<ActivityRssArtivlesBinding, RssSortViewModel>(),\n    VariableDialog.Callback {\n\n    override val binding by viewBinding(ActivityRssArtivlesBinding::inflate)\n    override val viewModel by viewModels<RssSortViewModel>()\n    private val adapter by lazy { TabFragmentPageAdapter() }\n    private val sortList = mutableListOf<Pair<String, String>>()\n    private val fragmentMap = hashMapOf<String, Fragment>()\n    private val editSourceResult = registerForActivityResult(\n        StartActivityContract(RssSourceEditActivity::class.java)\n    ) {\n        if (it.resultCode == RESULT_OK) {\n            viewModel.initData(intent) {\n                upFragments()\n            }\n        }\n    }\n\n    override fun onActivityCreated(savedInstanceState: Bundle?) {\n        binding.viewPager.adapter = adapter\n        binding.tabLayout.setupWithViewPager(binding.viewPager)\n        binding.tabLayout.setSelectedTabIndicatorColor(accentColor)\n        viewModel.titleLiveData.observe(this) {\n            binding.titleBar.title = it\n        }\n        viewModel.initData(intent) {\n            upFragments()\n        }\n    }\n\n    override fun onCompatCreateOptionsMenu(menu: Menu): Boolean {\n        menuInflater.inflate(R.menu.rss_articles, menu)\n        return super.onCompatCreateOptionsMenu(menu)\n    }\n\n    override fun onMenuOpened(featureId: Int, menu: Menu): Boolean {\n        menu.findItem(R.id.menu_login)?.isVisible =\n            !viewModel.rssSource?.loginUrl.isNullOrBlank()\n        return super.onMenuOpened(featureId, menu)\n    }\n\n    override fun onCompatOptionsItemSelected(item: MenuItem): Boolean {\n        when (item.itemId) {\n            R.id.menu_login -> startActivity<SourceLoginActivity> {\n                putExtra(\"type\", \"rssSource\")\n                putExtra(\"key\", viewModel.rssSource?.sourceUrl)\n            }\n\n            R.id.menu_refresh_sort -> viewModel.clearSortCache { upFragments() }\n            R.id.menu_set_source_variable -> setSourceVariable()\n            R.id.menu_edit_source -> viewModel.rssSource?.sourceUrl?.let {\n                editSourceResult.launch {\n                    putExtra(\"sourceUrl\", it)\n                }\n            }\n\n            R.id.menu_clear -> {\n                viewModel.url?.let {\n                    viewModel.clearArticles()\n                }\n            }\n\n            R.id.menu_switch_layout -> {\n                viewModel.switchLayout()\n                upFragments()\n            }\n\n            R.id.menu_read_record -> {\n                showDialogFragment<ReadRecordDialog>()\n            }\n        }\n        return super.onCompatOptionsItemSelected(item)\n    }\n\n    private fun upFragments() {\n        lifecycleScope.launch {\n            viewModel.rssSource?.sortUrls()?.let {\n                sortList.clear()\n                sortList.addAll(it)\n            }\n            if (sortList.size == 1) {\n                binding.tabLayout.gone()\n            } else {\n                binding.tabLayout.visible()\n            }\n            adapter.notifyDataSetChanged()\n        }\n    }\n\n    private fun setSourceVariable() {\n        lifecycleScope.launch {\n            val source = viewModel.rssSource\n            if (source == null) {\n                toastOnUi(\"源不存在\")\n                return@launch\n            }\n            val comment =\n                source.getDisplayVariableComment(\"源变量可在js中通过source.getVariable()获取\")\n            val variable = withContext(Dispatchers.IO) { source.getVariable() }\n            showDialogFragment(\n                VariableDialog(\n                    getString(R.string.set_source_variable),\n                    source.getKey(),\n                    variable,\n                    comment\n                )\n            )\n        }\n    }\n\n    override fun setVariable(key: String, variable: String?) {\n        viewModel.rssSource?.setVariable(variable)\n    }\n\n    private inner class TabFragmentPageAdapter :\n        FragmentStatePagerAdapter(supportFragmentManager, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {\n\n        override fun getItemPosition(`object`: Any): Int {\n            return POSITION_NONE\n        }\n\n        override fun getPageTitle(position: Int): CharSequence {\n            return sortList[position].first\n        }\n\n        override fun getItem(position: Int): Fragment {\n            val sort = sortList[position]\n            return RssArticlesFragment(sort.first, sort.second)\n        }\n\n        override fun getCount(): Int {\n            return sortList.size\n        }\n\n        override fun instantiateItem(container: ViewGroup, position: Int): Any {\n            val fragment = super.instantiateItem(container, position) as Fragment\n            fragmentMap[sortList[position].first] = fragment\n            return fragment\n        }\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/rss/article/RssSortViewModel.kt",
    "content": "package io.legado.app.ui.rss.article\n\nimport android.app.Application\nimport android.content.Intent\nimport androidx.lifecycle.MutableLiveData\nimport io.legado.app.base.BaseViewModel\nimport io.legado.app.data.appDb\nimport io.legado.app.data.entities.RssArticle\nimport io.legado.app.data.entities.RssReadRecord\nimport io.legado.app.data.entities.RssSource\nimport io.legado.app.help.source.removeSortCache\n\n\nclass RssSortViewModel(application: Application) : BaseViewModel(application) {\n    var url: String? = null\n    var rssSource: RssSource? = null\n    val titleLiveData = MutableLiveData<String>()\n    var order = System.currentTimeMillis()\n    val isGridLayout get() = rssSource?.articleStyle == 2\n\n    fun initData(intent: Intent, finally: () -> Unit) {\n        execute {\n            url = intent.getStringExtra(\"url\")\n            url?.let { url ->\n                rssSource = appDb.rssSourceDao.getByKey(url)\n                rssSource?.let {\n                    titleLiveData.postValue(it.sourceName)\n                } ?: let {\n                    rssSource = RssSource(sourceUrl = url)\n                }\n            }\n        }.onFinally {\n            finally()\n        }\n    }\n\n    fun switchLayout() {\n        rssSource?.let {\n            if (it.articleStyle < 2) {\n                it.articleStyle += 1\n            } else {\n                it.articleStyle = 0\n            }\n            execute {\n                appDb.rssSourceDao.update(it)\n            }\n        }\n    }\n\n    fun read(rssArticle: RssArticle) {\n        execute {\n            val rssReadRecord = RssReadRecord(\n                record = rssArticle.link,\n                title = rssArticle.title,\n                readTime = System.currentTimeMillis()\n            )\n            appDb.rssReadRecordDao.insertRecord(rssReadRecord)\n        }\n    }\n\n    fun clearArticles() {\n        execute {\n            url?.let {\n                appDb.rssArticleDao.delete(it)\n            }\n            order = System.currentTimeMillis()\n        }.onSuccess {\n\n        }\n    }\n\n    fun clearSortCache(onFinally: () -> Unit) {\n        execute {\n            rssSource?.removeSortCache()\n        }.onFinally {\n            onFinally.invoke()\n        }\n    }\n\n    fun getRecords(): List<RssReadRecord> {\n        return appDb.rssReadRecordDao.getRecords()\n    }\n\n    fun countRecords() : Int {\n        return appDb.rssReadRecordDao.countRecords\n    }\n\n    fun deleteAllRecord() {\n        execute {\n            appDb.rssReadRecordDao.deleteAllRecord()\n        }\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/rss/favorites/RssFavoritesActivity.kt",
    "content": "@file:Suppress(\"DEPRECATION\")\n\npackage io.legado.app.ui.rss.favorites\n\nimport android.os.Bundle\nimport android.view.Menu\nimport android.view.MenuItem\nimport android.view.SubMenu\nimport androidx.fragment.app.Fragment\nimport androidx.fragment.app.FragmentStatePagerAdapter\nimport androidx.lifecycle.lifecycleScope\nimport androidx.viewpager.widget.ViewPager\nimport io.legado.app.R\nimport io.legado.app.base.BaseActivity\nimport io.legado.app.constant.AppLog\nimport io.legado.app.data.appDb\nimport io.legado.app.databinding.ActivityRssFavoritesBinding\nimport io.legado.app.lib.dialogs.alert\nimport io.legado.app.lib.theme.accentColor\nimport io.legado.app.utils.gone\nimport io.legado.app.utils.viewbindingdelegate.viewBinding\nimport io.legado.app.utils.visible\nimport kotlinx.coroutines.Dispatchers.IO\nimport kotlinx.coroutines.delay\nimport kotlinx.coroutines.flow.catch\nimport kotlinx.coroutines.flow.distinctUntilChanged\nimport kotlinx.coroutines.flow.flowOn\nimport kotlinx.coroutines.launch\n\n/**\n * 收藏夹\n */\nclass RssFavoritesActivity : BaseActivity<ActivityRssFavoritesBinding>() {\n\n    override val binding by viewBinding(ActivityRssFavoritesBinding::inflate)\n    private val adapter by lazy { TabFragmentPageAdapter() }\n    private var groupList = mutableListOf<String>()\n    private var groupsMenu: SubMenu? = null\n    private var currentGroup = \"\"\n\n    override fun onActivityCreated(savedInstanceState: Bundle?) {\n        initView()\n        upFragments()\n    }\n\n    override fun onResume() {\n        super.onResume()\n        //从ReadRssActivity退出时，判断是否需要重新定位tabLayout选中项\n        if (currentGroup.isNotEmpty() && groupList.isNotEmpty()) {\n            var item = groupList.indexOf(currentGroup)\n            val currentItem = binding.viewPager.currentItem\n            //如果坐标没有变化，则结束\n            if (item == currentItem) {\n                return\n            }\n            if (item == -1) {\n                item = currentItem\n            }\n            lifecycleScope.launch {\n                delay(100)\n                binding.tabLayout.getTabAt(item)?.select()\n            }\n        }\n    }\n\n    private fun initView() {\n        binding.viewPager.adapter = adapter\n        binding.viewPager.addOnPageChangeListener(object : ViewPager.OnPageChangeListener {\n            override fun onPageScrolled(\n                position: Int,\n                positionOffset: Float,\n                positionOffsetPixels: Int\n            ) {\n            }\n\n            override fun onPageSelected(position: Int) {\n                currentGroup = groupList[position]\n            }\n\n            override fun onPageScrollStateChanged(state: Int) {}\n\n        })\n        binding.tabLayout.setupWithViewPager(binding.viewPager)\n        binding.tabLayout.setSelectedTabIndicatorColor(accentColor)\n    }\n\n    override fun onCompatCreateOptionsMenu(menu: Menu): Boolean {\n        menuInflater.inflate(R.menu.rss_favorites, menu)\n        groupsMenu = menu.findItem(R.id.menu_group)?.subMenu\n        upGroupsMenu()\n        return super.onCompatCreateOptionsMenu(menu)\n    }\n\n    private fun upGroupsMenu() = groupsMenu?.let { subMenu ->\n        subMenu.removeGroup(R.id.menu_group)\n        groupList.forEachIndexed { index, it ->\n            subMenu.add(R.id.menu_group, Menu.NONE, index, it)\n        }\n    }\n\n    override fun onCompatOptionsItemSelected(item: MenuItem): Boolean {\n        if (item.groupId == R.id.menu_group) {\n            binding.viewPager.setCurrentItem(item.order)\n        } else {\n            when (item.itemId) {\n                R.id.menu_del_group -> deleteGroup()\n                R.id.menu_del_all -> deleteAll()\n            }\n        }\n        return super.onCompatOptionsItemSelected(item)\n    }\n\n    private fun upFragments() {\n        lifecycleScope.launch {\n            appDb.rssStarDao.flowGroups().catch {\n                AppLog.put(\"订阅分组数据获取失败\\n${it.localizedMessage}\", it)\n            }.distinctUntilChanged().flowOn(IO).collect {\n                groupList.clear()\n                groupList.addAll(it)\n                if (groupList.size == 1) {\n                    binding.tabLayout.gone()\n                } else {\n                    binding.tabLayout.visible()\n                }\n                if (groupsMenu != null) {\n                    upGroupsMenu()\n                }\n                adapter.notifyDataSetChanged()\n            }\n        }\n    }\n\n    private fun deleteGroup() {\n        if (groupList.isEmpty()) {\n            return\n        }\n        alert(R.string.draw) {\n            val item = binding.viewPager.currentItem\n            val group = groupList[item]\n            setMessage(\n                getString(R.string.sure_del) + \"\\n<\" + group + \">\" + getString(R.string.group)\n            )\n            noButton()\n            yesButton {\n                appDb.rssStarDao.deleteByGroup(group)\n            }\n        }\n    }\n\n    private fun deleteAll() {\n        alert(R.string.draw) {\n            setMessage(\n                getString(R.string.sure_del) + \"\\n<\" + getString(R.string.all) + \">\"\n                        + getString(R.string.favorite)\n            )\n            noButton()\n            yesButton {\n                appDb.rssStarDao.deleteAll()\n            }\n        }\n    }\n\n    private inner class TabFragmentPageAdapter :\n        FragmentStatePagerAdapter(supportFragmentManager, BEHAVIOR_RESUME_ONLY_CURRENT_FRAGMENT) {\n\n        override fun getItemPosition(`object`: Any): Int {\n            return POSITION_NONE\n        }\n\n        override fun getPageTitle(position: Int): CharSequence {\n            return groupList[position]\n        }\n\n        override fun getItem(position: Int): Fragment {\n            val group = groupList[position]\n            return RssFavoritesFragment(group)\n        }\n\n        override fun getCount(): Int {\n            return groupList.size\n        }\n\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/rss/favorites/RssFavoritesAdapter.kt",
    "content": "package io.legado.app.ui.rss.favorites\n\nimport android.content.Context\nimport android.graphics.drawable.Drawable\nimport android.view.ViewGroup\nimport com.bumptech.glide.load.DataSource\nimport com.bumptech.glide.load.engine.GlideException\nimport com.bumptech.glide.request.RequestListener\nimport com.bumptech.glide.request.RequestOptions\nimport com.bumptech.glide.request.target.Target\nimport io.legado.app.base.adapter.ItemViewHolder\nimport io.legado.app.base.adapter.RecyclerAdapter\nimport io.legado.app.data.entities.RssStar\nimport io.legado.app.databinding.ItemRssArticleBinding\nimport io.legado.app.help.glide.ImageLoader\nimport io.legado.app.help.glide.OkHttpModelLoader\nimport io.legado.app.utils.gone\nimport io.legado.app.utils.visible\n\n\nclass RssFavoritesAdapter(context: Context, val callBack: CallBack) :\n    RecyclerAdapter<RssStar, ItemRssArticleBinding>(context) {\n\n    override fun getViewBinding(parent: ViewGroup): ItemRssArticleBinding {\n        return ItemRssArticleBinding.inflate(inflater, parent, false)\n    }\n\n    override fun convert(\n        holder: ItemViewHolder,\n        binding: ItemRssArticleBinding,\n        item: RssStar,\n        payloads: MutableList<Any>\n    ) {\n        binding.run {\n            tvTitle.text = item.title\n            tvPubDate.text = item.pubDate\n            if (item.image.isNullOrBlank()) {\n                imageView.gone()\n            } else {\n                val options =\n                    RequestOptions().set(OkHttpModelLoader.sourceOriginOption, item.origin)\n                ImageLoader.load(context, item.image)\n                    .apply(options)\n                    .addListener(object : RequestListener<Drawable> {\n                        override fun onLoadFailed(\n                            e: GlideException?,\n                            model: Any?,\n                            target: Target<Drawable>,\n                            isFirstResource: Boolean\n                        ): Boolean {\n                            imageView.gone()\n                            return false\n                        }\n\n                        override fun onResourceReady(\n                            resource: Drawable,\n                            model: Any,\n                            target: Target<Drawable>?,\n                            dataSource: DataSource,\n                            isFirstResource: Boolean\n                        ): Boolean {\n                            imageView.visible()\n                            return false\n                        }\n\n                    })\n                    .into(imageView)\n            }\n        }\n    }\n\n    override fun registerListener(holder: ItemViewHolder, binding: ItemRssArticleBinding) {\n        holder.itemView.setOnClickListener {\n            getItem(holder.layoutPosition)?.let {\n                callBack.readRss(it)\n            }\n        }\n        holder.itemView.setOnLongClickListener {\n            getItem(holder.layoutPosition)?.let {\n                callBack.delStar(it)\n            }\n            true\n        }\n    }\n\n    interface CallBack {\n        fun readRss(rssStar: RssStar)\n\n        fun delStar(rssStar: RssStar)\n    }\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/rss/favorites/RssFavoritesDialog.kt",
    "content": "package io.legado.app.ui.rss.favorites\n\nimport android.os.Bundle\nimport android.view.View\nimport android.view.ViewGroup\nimport io.legado.app.R\nimport io.legado.app.base.BaseDialogFragment\nimport io.legado.app.data.entities.RssArticle\nimport io.legado.app.databinding.DialogRssFavoriteConfigBinding\nimport io.legado.app.lib.theme.primaryColor\nimport io.legado.app.utils.setLayout\nimport io.legado.app.utils.viewbindingdelegate.viewBinding\n\nclass RssFavoritesDialog() : BaseDialogFragment(R.layout.dialog_rss_favorite_config, true) {\n\n    constructor(rssArticle: RssArticle) : this() {\n        arguments = Bundle().apply {\n            putString(\"title\", rssArticle.title)\n            putString(\"group\", rssArticle.group)\n        }\n    }\n\n    private val binding by viewBinding(DialogRssFavoriteConfigBinding::bind)\n\n    override fun onStart() {\n        super.onStart()\n        setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)\n    }\n\n    override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) {\n        binding.toolBar.setBackgroundColor(primaryColor)\n        val arguments = arguments ?: let {\n            dismiss()\n            return\n        }\n\n        var title = arguments.getString(\"title\")\n        var group = arguments.getString(\"group\")\n        binding.run {\n            editTitle.setText(title)\n            editGroup.setText(group)\n            tvCancel.setOnClickListener {\n                dismiss()\n            }\n            tvOk.setOnClickListener {\n                val editTitle = editTitle.text.toString()\n                if (editTitle.isNotBlank()) {\n                    title = editTitle\n                }\n                val editGroup = editGroup.text.toString()\n                if (editGroup.isNotBlank()) {\n                    group = editGroup\n                }\n                callback?.updateFavorite(title, group)\n                dismiss()\n            }\n            tvFooterLeft.setOnClickListener {\n                callback?.deleteFavorite()\n                dismiss()\n            }\n        }\n    }\n\n    val callback get() = (parentFragment as? Callback) ?: (activity as? Callback)\n\n    interface Callback {\n\n        fun updateFavorite(title: String?, group: String?)\n\n        fun deleteFavorite()\n\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/rss/favorites/RssFavoritesFragment.kt",
    "content": "package io.legado.app.ui.rss.favorites\n\n\nimport android.os.Bundle\nimport android.view.View\nimport androidx.fragment.app.viewModels\nimport androidx.lifecycle.lifecycleScope\nimport androidx.recyclerview.widget.LinearLayoutManager\nimport io.legado.app.R\nimport io.legado.app.base.VMBaseFragment\nimport io.legado.app.constant.AppLog\nimport io.legado.app.data.appDb\nimport io.legado.app.data.entities.RssStar\nimport io.legado.app.databinding.FragmentRssArticlesBinding\nimport io.legado.app.lib.dialogs.alert\nimport io.legado.app.lib.theme.primaryColor\nimport io.legado.app.ui.rss.read.ReadRssActivity\nimport io.legado.app.ui.widget.recycler.VerticalDivider\nimport io.legado.app.utils.applyNavigationBarPadding\nimport io.legado.app.utils.setEdgeEffectColor\nimport io.legado.app.utils.startActivity\nimport io.legado.app.utils.viewbindingdelegate.viewBinding\nimport kotlinx.coroutines.Dispatchers.IO\nimport kotlinx.coroutines.flow.catch\nimport kotlinx.coroutines.flow.flowOn\nimport kotlinx.coroutines.launch\n\nclass RssFavoritesFragment() : VMBaseFragment<RssFavoritesViewModel>(R.layout.fragment_rss_articles),\n    RssFavoritesAdapter.CallBack {\n\n    constructor(group: String) : this() {\n        arguments = Bundle().apply {\n            putString(\"group\", group)\n        }\n    }\n\n    private val binding by viewBinding(FragmentRssArticlesBinding::bind)\n    override val viewModel by viewModels<RssFavoritesViewModel>()\n    private val adapter: RssFavoritesAdapter by lazy {\n        RssFavoritesAdapter(requireContext(), this@RssFavoritesFragment)\n    }\n\n    override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) {\n        initView()\n        loadArticles()\n    }\n\n    private fun initView() = binding.run {\n        refreshLayout.isEnabled = false\n        recyclerView.setEdgeEffectColor(primaryColor)\n        recyclerView.layoutManager = run {\n            recyclerView.addItemDecoration(VerticalDivider(requireContext()))\n            LinearLayoutManager(requireContext())\n        }\n        recyclerView.adapter = adapter\n        recyclerView.applyNavigationBarPadding()\n    }\n\n    private fun loadArticles() {\n        lifecycleScope.launch {\n            val group = arguments?.getString(\"group\") ?: \"默认分组\"\n            appDb.rssStarDao.flowByGroup(group).catch {\n                AppLog.put(\"订阅文章界面获取数据失败\\n${it.localizedMessage}\", it)\n            }.flowOn(IO).collect {\n                adapter.setItems(it)\n            }\n        }\n    }\n\n    override fun readRss(rssStar: RssStar) {\n        startActivity<ReadRssActivity> {\n            putExtra(\"title\", rssStar.title)\n            putExtra(\"origin\", rssStar.origin)\n            putExtra(\"link\", rssStar.link)\n        }\n    }\n\n    override fun delStar(rssStar: RssStar) {\n        alert(R.string.draw) {\n            setMessage(getString(R.string.sure_del) + \"\\n<\" + rssStar.title + \">\")\n            noButton()\n            yesButton {\n                appDb.rssStarDao.delete(rssStar.origin, rssStar.link)\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/rss/favorites/RssFavoritesViewModel.kt",
    "content": "package io.legado.app.ui.rss.favorites\n\nimport android.app.Application\nimport io.legado.app.base.BaseViewModel\n\n\nclass RssFavoritesViewModel(application: Application) : BaseViewModel(application) {\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/rss/read/ReadRssActivity.kt",
    "content": "package io.legado.app.ui.rss.read\n\nimport android.annotation.SuppressLint\nimport android.content.pm.ActivityInfo\nimport android.content.res.Configuration\nimport android.net.Uri\nimport android.net.http.SslError\nimport android.os.Bundle\nimport android.os.SystemClock\nimport android.view.Menu\nimport android.view.MenuItem\nimport android.view.View\nimport android.view.WindowManager\nimport android.webkit.JavascriptInterface\nimport android.webkit.SslErrorHandler\nimport android.webkit.URLUtil\nimport android.webkit.WebChromeClient\nimport android.webkit.WebResourceRequest\nimport android.webkit.WebResourceResponse\nimport android.webkit.WebSettings\nimport android.webkit.WebView\nimport android.webkit.WebViewClient\nimport androidx.activity.addCallback\nimport androidx.activity.viewModels\nimport androidx.core.view.WindowInsetsCompat\nimport androidx.core.view.size\nimport androidx.lifecycle.lifecycleScope\nimport com.script.rhino.runScriptWithContext\nimport io.legado.app.R\nimport io.legado.app.base.VMBaseActivity\nimport io.legado.app.constant.AppConst\nimport io.legado.app.constant.AppConst.imagePathKey\nimport io.legado.app.constant.AppLog\nimport io.legado.app.data.entities.RssSource\nimport io.legado.app.databinding.ActivityRssReadBinding\nimport io.legado.app.help.config.AppConfig\nimport io.legado.app.help.http.CookieManager\nimport io.legado.app.lib.dialogs.SelectItem\nimport io.legado.app.lib.dialogs.selector\nimport io.legado.app.lib.theme.accentColor\nimport io.legado.app.lib.theme.primaryTextColor\nimport io.legado.app.model.Download\nimport io.legado.app.ui.association.OnLineImportActivity\nimport io.legado.app.ui.file.HandleFileContract\nimport io.legado.app.ui.login.SourceLoginActivity\nimport io.legado.app.ui.rss.favorites.RssFavoritesDialog\nimport io.legado.app.utils.ACache\nimport io.legado.app.utils.NetworkUtils\nimport io.legado.app.utils.gone\nimport io.legado.app.utils.invisible\nimport io.legado.app.utils.isTrue\nimport io.legado.app.utils.keepScreenOn\nimport io.legado.app.utils.longSnackbar\nimport io.legado.app.utils.openUrl\nimport io.legado.app.utils.setDarkeningAllowed\nimport io.legado.app.utils.setOnApplyWindowInsetsListenerCompat\nimport io.legado.app.utils.setTintMutate\nimport io.legado.app.utils.share\nimport io.legado.app.utils.showDialogFragment\nimport io.legado.app.utils.splitNotBlank\nimport io.legado.app.utils.startActivity\nimport io.legado.app.utils.textArray\nimport io.legado.app.utils.toastOnUi\nimport io.legado.app.utils.toggleSystemBar\nimport io.legado.app.utils.viewbindingdelegate.viewBinding\nimport io.legado.app.utils.visible\nimport kotlinx.coroutines.launch\nimport org.apache.commons.text.StringEscapeUtils\nimport org.jsoup.Jsoup\nimport splitties.views.bottomPadding\nimport java.io.ByteArrayInputStream\nimport java.net.URLDecoder\nimport java.util.regex.PatternSyntaxException\n\n/**\n * rss阅读界面\n */\nclass ReadRssActivity : VMBaseActivity<ActivityRssReadBinding, ReadRssViewModel>(),\n    RssFavoritesDialog.Callback {\n\n    override val binding by viewBinding(ActivityRssReadBinding::inflate)\n    override val viewModel by viewModels<ReadRssViewModel>()\n\n    private var starMenuItem: MenuItem? = null\n    private var ttsMenuItem: MenuItem? = null\n    private var customWebViewCallback: WebChromeClient.CustomViewCallback? = null\n    private val selectImageDir = registerForActivityResult(HandleFileContract()) {\n        it.uri?.let { uri ->\n            ACache.get().put(imagePathKey, uri.toString())\n            viewModel.saveImage(it.value, uri)\n        }\n    }\n    private val rssJsExtensions by lazy { RssJsExtensions(this) }\n\n    fun getSource(): RssSource? {\n        return viewModel.rssSource\n    }\n\n    override fun onActivityCreated(savedInstanceState: Bundle?) {\n        viewModel.upStarMenuData.observe(this) { upStarMenu() }\n        viewModel.upTtsMenuData.observe(this) { upTtsMenu(it) }\n        binding.titleBar.title = intent.getStringExtra(\"title\")\n        initView()\n        initWebView()\n        initLiveData()\n        viewModel.initData(intent)\n        onBackPressedDispatcher.addCallback(this) {\n            if (binding.customWebView.size > 0) {\n                customWebViewCallback?.onCustomViewHidden()\n                return@addCallback\n            } else if (binding.webView.canGoBack()\n                && binding.webView.copyBackForwardList().size > 1\n            ) {\n                binding.webView.goBack()\n                return@addCallback\n            }\n            finish()\n        }\n    }\n\n    @Suppress(\"DEPRECATION\")\n    @SuppressLint(\"SwitchIntDef\")\n    override fun onConfigurationChanged(newConfig: Configuration) {\n        super.onConfigurationChanged(newConfig)\n        when (newConfig.orientation) {\n            Configuration.ORIENTATION_LANDSCAPE -> {\n                window.clearFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN)\n                window.addFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN)\n            }\n\n            Configuration.ORIENTATION_PORTRAIT -> {\n                window.clearFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN)\n                window.addFlags(WindowManager.LayoutParams.FLAG_FORCE_NOT_FULLSCREEN)\n            }\n        }\n    }\n\n    override fun onCompatCreateOptionsMenu(menu: Menu): Boolean {\n        menuInflater.inflate(R.menu.rss_read, menu)\n        return super.onCompatCreateOptionsMenu(menu)\n    }\n\n    override fun onPrepareOptionsMenu(menu: Menu): Boolean {\n        starMenuItem = menu.findItem(R.id.menu_rss_star)\n        ttsMenuItem = menu.findItem(R.id.menu_aloud)\n        upStarMenu()\n        return super.onPrepareOptionsMenu(menu)\n    }\n\n    override fun onMenuOpened(featureId: Int, menu: Menu): Boolean {\n        menu.findItem(R.id.menu_login)?.isVisible = !viewModel.rssSource?.loginUrl.isNullOrBlank()\n        return super.onMenuOpened(featureId, menu)\n    }\n\n    override fun onCompatOptionsItemSelected(item: MenuItem): Boolean {\n        when (item.itemId) {\n            R.id.menu_rss_refresh -> viewModel.refresh {\n                binding.webView.reload()\n            }\n\n            R.id.menu_rss_star -> {\n                viewModel.addFavorite()\n                viewModel.rssArticle?.let {\n                    showDialogFragment(RssFavoritesDialog(it))\n                }\n            }\n\n            R.id.menu_share_it -> {\n                binding.webView.url?.let {\n                    share(it)\n                } ?: viewModel.rssArticle?.let {\n                    share(it.link)\n                } ?: toastOnUi(R.string.null_url)\n            }\n\n            R.id.menu_aloud -> readAloud()\n            R.id.menu_login -> startActivity<SourceLoginActivity> {\n                putExtra(\"type\", \"rssSource\")\n                putExtra(\"key\", viewModel.rssSource?.sourceUrl)\n            }\n\n            R.id.menu_browser_open -> binding.webView.url?.let {\n                openUrl(it)\n            } ?: toastOnUi(\"url null\")\n        }\n        return super.onCompatOptionsItemSelected(item)\n    }\n\n    override fun updateFavorite(title: String?, group: String?) {\n        viewModel.rssArticle?.let {\n            if (title != null) {\n                it.title = title\n            }\n            if (group != null) {\n                it.group = group\n            }\n        }\n        viewModel.updateFavorite()\n    }\n\n    override fun deleteFavorite() {\n        viewModel.delFavorite()\n    }\n\n    @JavascriptInterface\n    fun isNightTheme(): Boolean {\n        return AppConfig.isNightTheme\n    }\n\n    private fun initView() {\n        binding.root.setOnApplyWindowInsetsListenerCompat { view, windowInsets ->\n            val typeMask = WindowInsetsCompat.Type.systemBars() or WindowInsetsCompat.Type.ime()\n            val insets = windowInsets.getInsets(typeMask)\n            view.bottomPadding = insets.bottom\n            windowInsets\n        }\n    }\n\n    @SuppressLint(\"SetJavaScriptEnabled\", \"JavascriptInterface\")\n    private fun initWebView() {\n        binding.progressBar.fontColor = accentColor\n        binding.webView.webChromeClient = CustomWebChromeClient()\n        binding.webView.webViewClient = CustomWebViewClient()\n        binding.webView.settings.apply {\n            mixedContentMode = WebSettings.MIXED_CONTENT_ALWAYS_ALLOW\n            domStorageEnabled = true\n            allowContentAccess = true\n            builtInZoomControls = true\n            displayZoomControls = false\n            setDarkeningAllowed(AppConfig.isNightTheme)\n        }\n        binding.webView.addJavascriptInterface(this, \"thisActivity\")\n        binding.webView.setOnLongClickListener {\n            val hitTestResult = binding.webView.hitTestResult\n            if (hitTestResult.type == WebView.HitTestResult.IMAGE_TYPE ||\n                hitTestResult.type == WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE\n            ) {\n                hitTestResult.extra?.let { webPic ->\n                    selector(\n                        arrayListOf(\n                            SelectItem(getString(R.string.action_save), \"save\"),\n                            SelectItem(getString(R.string.select_folder), \"selectFolder\")\n                        )\n                    ) { _, charSequence, _ ->\n                        when (charSequence.value) {\n                            \"save\" -> saveImage(webPic)\n                            \"selectFolder\" -> selectSaveFolder(null)\n                        }\n                    }\n                    return@setOnLongClickListener true\n                }\n            }\n            return@setOnLongClickListener false\n        }\n        binding.webView.setDownloadListener { url, _, contentDisposition, _, _ ->\n            var fileName = URLUtil.guessFileName(url, contentDisposition, null)\n            fileName = URLDecoder.decode(fileName, \"UTF-8\")\n            binding.llView.longSnackbar(fileName, getString(R.string.action_download)) {\n                Download.start(this, url, fileName)\n            }\n        }\n\n    }\n\n    private fun saveImage(webPic: String) {\n        val path = ACache.get().getAsString(imagePathKey)\n        if (path.isNullOrEmpty()) {\n            selectSaveFolder(webPic)\n        } else {\n            viewModel.saveImage(webPic, Uri.parse(path))\n        }\n    }\n\n    private fun selectSaveFolder(webPic: String?) {\n        val default = arrayListOf<SelectItem<Int>>()\n        val path = ACache.get().getAsString(imagePathKey)\n        if (!path.isNullOrEmpty()) {\n            default.add(SelectItem(path, -1))\n        }\n        selectImageDir.launch {\n            otherActions = default\n            value = webPic\n        }\n    }\n\n    @SuppressLint(\"SetJavaScriptEnabled\")\n    private fun initLiveData() {\n        viewModel.contentLiveData.observe(this) { content ->\n            viewModel.rssArticle?.let {\n                upJavaScriptEnable()\n                val url = NetworkUtils.getAbsoluteURL(it.origin, it.link)\n                val html = viewModel.clHtml(content)\n                binding.webView.settings.userAgentString =\n                    viewModel.headerMap[AppConst.UA_NAME] ?: AppConfig.userAgent\n                if (viewModel.rssSource?.loadWithBaseUrl == true) {\n                    binding.webView\n                        .loadDataWithBaseURL(url, html, \"text/html\", \"utf-8\", url)//不想用baseUrl进else\n                } else {\n                    binding.webView\n                        .loadDataWithBaseURL(null, html, \"text/html;charset=utf-8\", \"utf-8\", url)\n                }\n            }\n        }\n        viewModel.urlLiveData.observe(this) {\n            upJavaScriptEnable()\n            CookieManager.applyToWebView(it.url)\n            binding.webView.settings.userAgentString = it.getUserAgent()\n            binding.webView.loadUrl(it.url, it.headerMap)\n        }\n    }\n\n    @SuppressLint(\"SetJavaScriptEnabled\")\n    private fun upJavaScriptEnable() {\n        if (viewModel.rssSource?.enableJs == true) {\n            binding.webView.settings.javaScriptEnabled = true\n        }\n    }\n\n    private fun upStarMenu() {\n        starMenuItem?.isVisible = viewModel.rssArticle != null\n        if (viewModel.rssStar != null) {\n            starMenuItem?.setIcon(R.drawable.ic_star)\n            starMenuItem?.setTitle(R.string.in_favorites)\n        } else {\n            starMenuItem?.setIcon(R.drawable.ic_star_border)\n            starMenuItem?.setTitle(R.string.out_favorites)\n        }\n        starMenuItem?.icon?.setTintMutate(primaryTextColor)\n    }\n\n    private fun upTtsMenu(isPlaying: Boolean) {\n        lifecycleScope.launch {\n            if (isPlaying) {\n                ttsMenuItem?.setIcon(R.drawable.ic_stop_black_24dp)\n                ttsMenuItem?.setTitle(R.string.aloud_stop)\n            } else {\n                ttsMenuItem?.setIcon(R.drawable.ic_volume_up)\n                ttsMenuItem?.setTitle(R.string.read_aloud)\n            }\n            ttsMenuItem?.icon?.setTintMutate(primaryTextColor)\n        }\n    }\n\n    @SuppressLint(\"SetJavaScriptEnabled\")\n    private fun readAloud() {\n        if (viewModel.tts?.isSpeaking == true) {\n            viewModel.tts?.stop()\n            upTtsMenu(false)\n        } else {\n            binding.webView.settings.javaScriptEnabled = true\n            binding.webView.evaluateJavascript(\"document.documentElement.outerHTML\") {\n                val html = StringEscapeUtils.unescapeJson(it)\n                    .replace(\"^\\\"|\\\"$\".toRegex(), \"\")\n                viewModel.readAloud(\n                    Jsoup.parse(html)\n                        .textArray()\n                        .joinToString(\"\\n\")\n                )\n            }\n        }\n    }\n\n    override fun onDestroy() {\n        super.onDestroy()\n        binding.webView.destroy()\n    }\n\n    inner class CustomWebChromeClient : WebChromeClient() {\n\n        override fun onProgressChanged(view: WebView?, newProgress: Int) {\n            super.onProgressChanged(view, newProgress)\n            binding.progressBar.setDurProgress(newProgress)\n            binding.progressBar.gone(newProgress == 100)\n        }\n\n        override fun onShowCustomView(view: View?, callback: CustomViewCallback?) {\n            requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_SENSOR\n            binding.llView.invisible()\n            binding.customWebView.addView(view)\n            customWebViewCallback = callback\n            keepScreenOn(true)\n            toggleSystemBar(false)\n        }\n\n        override fun onHideCustomView() {\n            binding.customWebView.removeAllViews()\n            binding.llView.visible()\n            requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED\n            keepScreenOn(false)\n            toggleSystemBar(true)\n        }\n    }\n\n    inner class CustomWebViewClient : WebViewClient() {\n\n        override fun shouldOverrideUrlLoading(\n            view: WebView,\n            request: WebResourceRequest\n        ): Boolean {\n            return shouldOverrideUrlLoading(request.url)\n        }\n\n        @Suppress(\"DEPRECATION\", \"OVERRIDE_DEPRECATION\", \"KotlinRedundantDiagnosticSuppress\")\n        override fun shouldOverrideUrlLoading(view: WebView, url: String): Boolean {\n            return shouldOverrideUrlLoading(Uri.parse(url))\n        }\n\n        /**\n         * 如果有黑名单,黑名单匹配返回空白,\n         * 没有黑名单再判断白名单,在白名单中的才通过,\n         * 都没有不做处理\n         */\n        override fun shouldInterceptRequest(\n            view: WebView,\n            request: WebResourceRequest\n        ): WebResourceResponse? {\n            val url = request.url.toString()\n            val source = viewModel.rssSource ?: return super.shouldInterceptRequest(view, request)\n            val blacklist = source.contentBlacklist?.splitNotBlank(\",\")\n            if (!blacklist.isNullOrEmpty()) {\n                blacklist.forEach {\n                    try {\n                        if (url.startsWith(it) || url.matches(it.toRegex())) {\n                            return createEmptyResource()\n                        }\n                    } catch (e: PatternSyntaxException) {\n                        AppLog.put(\"黑名单规则正则语法错误 源名称:${source.sourceName} 正则:$it\", e)\n                    }\n                }\n            } else {\n                val whitelist = source.contentWhitelist?.splitNotBlank(\",\")\n                if (!whitelist.isNullOrEmpty()) {\n                    whitelist.forEach {\n                        try {\n                            if (url.startsWith(it) || url.matches(it.toRegex())) {\n                                return super.shouldInterceptRequest(view, request)\n                            }\n                        } catch (e: PatternSyntaxException) {\n                            val msg = \"白名单规则正则语法错误 源名称:${source.sourceName} 正则:$it\"\n                            AppLog.put(msg, e)\n                        }\n                    }\n                    return createEmptyResource()\n                }\n            }\n            return super.shouldInterceptRequest(view, request)\n        }\n\n        override fun onPageFinished(view: WebView, url: String) {\n            super.onPageFinished(view, url)\n            view.title?.let { title ->\n                if (title != url\n                    && title != view.url\n                    && title.isNotBlank()\n                    && url != \"about:blank\"\n                    && !url.contains(title)\n                ) {\n                    binding.titleBar.title = title\n                } else {\n                    binding.titleBar.title = intent.getStringExtra(\"title\")\n                }\n            }\n            viewModel.rssSource?.injectJs?.let {\n                if (it.isNotBlank()) {\n                    view.evaluateJavascript(it, null)\n                }\n            }\n        }\n\n        private fun createEmptyResource(): WebResourceResponse {\n            return WebResourceResponse(\n                \"text/plain\",\n                \"utf-8\",\n                ByteArrayInputStream(\"\".toByteArray())\n            )\n        }\n\n        private fun shouldOverrideUrlLoading(url: Uri): Boolean {\n            val source = viewModel.rssSource\n            val js = source?.shouldOverrideUrlLoading\n            if (!js.isNullOrBlank()) {\n                val t = SystemClock.uptimeMillis()\n                val result = kotlin.runCatching {\n                    runScriptWithContext(lifecycleScope.coroutineContext) {\n                        source.evalJS(js) {\n                            put(\"java\", rssJsExtensions)\n                            put(\"url\", url.toString())\n                        }.toString()\n                    }\n                }.onFailure {\n                    AppLog.put(\"${source.getTag()}: url跳转拦截js出错\", it)\n                }.getOrNull()\n                if (SystemClock.uptimeMillis() - t > 30) {\n                    AppLog.put(\"${source.getTag()}: url跳转拦截js执行耗时过长\")\n                }\n                if (result.isTrue()) {\n                    return true\n                }\n            }\n            when (url.scheme) {\n                \"http\", \"https\", \"jsbridge\" -> {\n                    return false\n                }\n\n                \"legado\", \"yuedu\" -> {\n                    startActivity<OnLineImportActivity> {\n                        data = url\n                    }\n                    return true\n                }\n\n                else -> {\n                    binding.root.longSnackbar(R.string.jump_to_another_app, R.string.confirm) {\n                        openUrl(url)\n                    }\n                    return true\n                }\n            }\n        }\n\n        @SuppressLint(\"WebViewClientOnReceivedSslError\")\n        override fun onReceivedSslError(\n            view: WebView?,\n            handler: SslErrorHandler?,\n            error: SslError?\n        ) {\n            handler?.proceed()\n        }\n\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/rss/read/ReadRssViewModel.kt",
    "content": "package io.legado.app.ui.rss.read\n\nimport android.app.Application\nimport android.content.Intent\nimport android.net.Uri\nimport android.util.Base64\nimport android.webkit.URLUtil\nimport androidx.lifecycle.MutableLiveData\nimport androidx.lifecycle.viewModelScope\nimport com.script.rhino.runScriptWithContext\nimport io.legado.app.base.BaseViewModel\nimport io.legado.app.constant.AppConst\nimport io.legado.app.constant.AppConst.imagePathKey\nimport io.legado.app.data.appDb\nimport io.legado.app.data.entities.RssArticle\nimport io.legado.app.data.entities.RssSource\nimport io.legado.app.data.entities.RssStar\nimport io.legado.app.exception.NoStackTraceException\nimport io.legado.app.help.TTS\nimport io.legado.app.help.http.newCallResponseBody\nimport io.legado.app.help.http.okHttpClient\nimport io.legado.app.model.analyzeRule.AnalyzeUrl\nimport io.legado.app.model.rss.Rss\nimport io.legado.app.utils.ACache\nimport io.legado.app.utils.toastOnUi\nimport io.legado.app.utils.writeBytes\nimport kotlinx.coroutines.Dispatchers.IO\nimport splitties.init.appCtx\nimport java.util.Date\nimport kotlin.coroutines.coroutineContext\n\n\nclass ReadRssViewModel(application: Application) : BaseViewModel(application) {\n    var rssSource: RssSource? = null\n    var rssArticle: RssArticle? = null\n    var tts: TTS? = null\n    val contentLiveData = MutableLiveData<String>()\n    val urlLiveData = MutableLiveData<AnalyzeUrl>()\n    var rssStar: RssStar? = null\n    val upTtsMenuData = MutableLiveData<Boolean>()\n    val upStarMenuData = MutableLiveData<Boolean>()\n    var headerMap: Map<String, String> = emptyMap()\n\n    fun initData(intent: Intent) {\n        execute {\n            val origin = intent.getStringExtra(\"origin\") ?: return@execute\n            val link = intent.getStringExtra(\"link\")\n            rssSource = appDb.rssSourceDao.getByKey(origin)\n            headerMap = runScriptWithContext {\n                rssSource?.getHeaderMap() ?: emptyMap()\n            }\n            if (link != null) {\n                rssStar = appDb.rssStarDao.get(origin, link)\n                rssArticle = rssStar?.toRssArticle() ?: appDb.rssArticleDao.get(origin, link)\n                val rssArticle = rssArticle ?: return@execute\n                if (!rssArticle.description.isNullOrBlank()) {\n                    contentLiveData.postValue(rssArticle.description!!)\n                } else {\n                    rssSource?.let {\n                        val ruleContent = it.ruleContent\n                        if (!ruleContent.isNullOrBlank()) {\n                            loadContent(rssArticle, ruleContent)\n                        } else {\n                            loadUrl(rssArticle.link, rssArticle.origin)\n                        }\n                    } ?: loadUrl(rssArticle.link, rssArticle.origin)\n                }\n            } else {\n                val ruleContent = rssSource?.ruleContent\n                if (ruleContent.isNullOrBlank()) {\n                    loadUrl(origin, origin)\n                } else {\n                    val rssArticle = RssArticle()\n                    rssArticle.origin = origin\n                    rssArticle.link = origin\n                    rssArticle.title = rssSource!!.sourceName\n                    loadContent(rssArticle, ruleContent)\n                }\n            }\n        }.onFinally {\n            upStarMenuData.postValue(true)\n        }\n    }\n\n    private suspend fun loadUrl(url: String, baseUrl: String) {\n        val analyzeUrl = AnalyzeUrl(\n            mUrl = url,\n            baseUrl = baseUrl,\n            source = rssSource,\n            coroutineContext = coroutineContext,\n            hasLoginHeader = false\n        )\n        urlLiveData.postValue(analyzeUrl)\n    }\n\n    private fun loadContent(rssArticle: RssArticle, ruleContent: String) {\n        val source = rssSource ?: return\n        Rss.getContent(viewModelScope, rssArticle, ruleContent, source)\n            .onSuccess(IO) { body ->\n                rssArticle.description = body\n                appDb.rssArticleDao.insert(rssArticle)\n                rssStar?.let {\n                    it.description = body\n                    appDb.rssStarDao.insert(it)\n                }\n                contentLiveData.postValue(body)\n            }.onError {\n                contentLiveData.postValue(\"加载正文失败\\n${it.stackTraceToString()}\")\n            }\n    }\n\n    fun refresh(finish: () -> Unit) {\n        val rssArticle = rssArticle ?: return finish.invoke()\n        if (!rssArticle.description.isNullOrBlank()) {\n            return finish.invoke()\n        }\n        val rssSource = rssSource ?: let {\n            appCtx.toastOnUi(\"订阅源不存在\")\n            return finish.invoke()\n        }\n        val ruleContent = rssSource.ruleContent\n        if (!ruleContent.isNullOrBlank()) {\n            loadContent(rssArticle, ruleContent)\n        } else {\n            finish.invoke()\n        }\n    }\n\n    fun favorite() {\n        execute {\n            rssStar?.let {\n                appDb.rssStarDao.delete(it.origin, it.link)\n                rssStar = null\n            } ?: rssArticle?.toStar()?.let {\n                appDb.rssStarDao.insert(it)\n                rssStar = it\n            }\n        }.onSuccess {\n            upStarMenuData.postValue(true)\n        }\n    }\n\n    fun addFavorite() {\n        execute {\n            rssStar ?: rssArticle?.toStar()?.let {\n                appDb.rssStarDao.insert(it)\n                rssStar = it\n            }\n        }.onSuccess {\n            upStarMenuData.postValue(true)\n        }\n    }\n\n    fun updateFavorite() {\n        execute {\n            rssArticle?.toStar()?.let {\n                appDb.rssStarDao.update(it)\n                rssStar = it\n            }\n        }.onSuccess {\n            upStarMenuData.postValue(true)\n        }\n    }\n\n    fun delFavorite() {\n        execute {\n            rssStar?.let {\n                appDb.rssStarDao.delete(it.origin, it.link)\n                rssStar = null\n            }\n        }.onSuccess {\n            upStarMenuData.postValue(true)\n        }\n    }\n\n    fun saveImage(webPic: String?, uri: Uri) {\n        webPic ?: return\n        execute {\n            val fileName = \"${AppConst.fileNameFormat.format(Date(System.currentTimeMillis()))}.jpg\"\n            val byteArray = webData2bitmap(webPic) ?: throw NoStackTraceException(\"NULL\")\n            uri.writeBytes(context, fileName, byteArray)\n        }.onError {\n            ACache.get().remove(imagePathKey)\n            context.toastOnUi(\"保存图片失败:${it.localizedMessage}\")\n        }.onSuccess {\n            context.toastOnUi(\"保存成功\")\n        }\n    }\n\n    private suspend fun webData2bitmap(data: String): ByteArray? {\n        return if (URLUtil.isValidUrl(data)) {\n            okHttpClient.newCallResponseBody {\n                url(data)\n            }.bytes()\n        } else {\n            Base64.decode(data.split(\",\").toTypedArray()[1], Base64.DEFAULT)\n        }\n    }\n\n    fun clHtml(content: String): String {\n        return when {\n            !rssSource?.style.isNullOrEmpty() -> {\n                \"\"\"\n                    <style>\n                        ${rssSource?.style}\n                    </style>\n                    $content\n                \"\"\".trimIndent()\n            }\n\n            content.contains(\"<style>\".toRegex()) -> {\n                content\n            }\n\n            else -> {\n                \"\"\"\n                    <style>\n                        img{max-width:100% !important; width:auto; height:auto;}\n                        video{object-fit:fill; max-width:100% !important; width:auto; height:auto;}\n                        body{word-wrap:break-word; height:auto;max-width: 100%; width:auto;}\n                    </style>\n                    $content\n                \"\"\".trimIndent()\n            }\n        }\n    }\n\n    @Synchronized\n    fun readAloud(text: String) {\n        if (tts == null) {\n            tts = TTS().apply {\n                setSpeakStateListener(object : TTS.SpeakStateListener {\n                    override fun onStart() {\n                        upTtsMenuData.postValue(true)\n                    }\n\n                    override fun onDone() {\n                        upTtsMenuData.postValue(false)\n                    }\n                })\n            }\n        }\n        tts?.speak(text)\n    }\n\n    override fun onCleared() {\n        super.onCleared()\n        tts?.clearTts()\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/rss/read/RssJsExtensions.kt",
    "content": "package io.legado.app.ui.rss.read\n\nimport io.legado.app.data.entities.BaseSource\nimport io.legado.app.help.JsExtensions\nimport io.legado.app.ui.association.AddToBookshelfDialog\nimport io.legado.app.ui.book.search.SearchActivity\nimport io.legado.app.utils.showDialogFragment\n\n@Suppress(\"unused\")\nclass RssJsExtensions(private val activity: ReadRssActivity) : JsExtensions {\n\n    override fun getSource(): BaseSource? {\n        return activity.getSource()\n    }\n\n    fun searchBook(key: String) {\n        SearchActivity.start(activity, key)\n    }\n\n    fun addBook(bookUrl: String) {\n        activity.showDialogFragment(AddToBookshelfDialog(bookUrl))\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/rss/read/VisibleWebView.kt",
    "content": "package io.legado.app.ui.rss.read\n\nimport android.content.Context\nimport android.util.AttributeSet\nimport android.view.View\nimport android.webkit.WebView\n\nclass VisibleWebView(\n    context: Context,\n    attrs: AttributeSet? = null\n) : WebView(context, attrs) {\n\n    override fun onWindowVisibilityChanged(visibility: Int) {\n        super.onWindowVisibilityChanged(View.VISIBLE)\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/rss/source/debug/RssSourceDebugActivity.kt",
    "content": "package io.legado.app.ui.rss.source.debug\n\nimport android.os.Bundle\nimport android.view.Menu\nimport android.view.MenuItem\nimport android.widget.SearchView\nimport androidx.activity.viewModels\nimport androidx.lifecycle.lifecycleScope\nimport io.legado.app.R\nimport io.legado.app.base.VMBaseActivity\nimport io.legado.app.databinding.ActivitySourceDebugBinding\nimport io.legado.app.lib.theme.accentColor\nimport io.legado.app.lib.theme.primaryColor\nimport io.legado.app.ui.widget.dialog.TextDialog\nimport io.legado.app.utils.applyNavigationBarPadding\nimport io.legado.app.utils.gone\nimport io.legado.app.utils.setEdgeEffectColor\nimport io.legado.app.utils.showDialogFragment\nimport io.legado.app.utils.toastOnUi\nimport io.legado.app.utils.viewbindingdelegate.viewBinding\nimport kotlinx.coroutines.launch\n\n\nclass RssSourceDebugActivity : VMBaseActivity<ActivitySourceDebugBinding, RssSourceDebugModel>() {\n\n    override val binding by viewBinding(ActivitySourceDebugBinding::inflate)\n    override val viewModel by viewModels<RssSourceDebugModel>()\n\n    private val adapter by lazy { RssSourceDebugAdapter(this) }\n\n    override fun onActivityCreated(savedInstanceState: Bundle?) {\n        initRecyclerView()\n        initSearchView()\n        viewModel.observe { state, msg ->\n            lifecycleScope.launch {\n                adapter.addItem(msg)\n                if (state == -1 || state == 1000) {\n                    binding.rotateLoading.gone()\n                }\n            }\n        }\n        viewModel.initData(intent.getStringExtra(\"key\")) {\n            startDebug()\n        }\n    }\n\n    override fun onCompatCreateOptionsMenu(menu: Menu): Boolean {\n        menuInflater.inflate(R.menu.rss_source_debug, menu)\n        return super.onCompatCreateOptionsMenu(menu)\n    }\n\n    override fun onCompatOptionsItemSelected(item: MenuItem): Boolean {\n        when (item.itemId) {\n            R.id.menu_list_src -> showDialogFragment(TextDialog(\"Html\", viewModel.listSrc))\n            R.id.menu_content_src -> showDialogFragment(TextDialog(\"Html\", viewModel.contentSrc))\n        }\n        return super.onCompatOptionsItemSelected(item)\n    }\n\n    private fun initRecyclerView() {\n        binding.recyclerView.setEdgeEffectColor(primaryColor)\n        binding.recyclerView.adapter = adapter\n        binding.recyclerView.applyNavigationBarPadding()\n        binding.rotateLoading.loadingColor = accentColor\n    }\n\n    private fun initSearchView() {\n        binding.titleBar.findViewById<SearchView>(R.id.search_view).gone()\n    }\n\n    private fun startDebug() {\n        adapter.clearItems()\n        viewModel.rssSource?.let {\n            binding.rotateLoading.visible()\n            viewModel.startDebug(it)\n        } ?: toastOnUi(R.string.error_no_source)\n    }\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/rss/source/debug/RssSourceDebugAdapter.kt",
    "content": "package io.legado.app.ui.rss.source.debug\n\nimport android.content.Context\nimport android.view.View\nimport android.view.ViewGroup\nimport io.legado.app.R\nimport io.legado.app.base.adapter.ItemViewHolder\nimport io.legado.app.base.adapter.RecyclerAdapter\nimport io.legado.app.databinding.ItemLogBinding\n\nclass RssSourceDebugAdapter(context: Context) :\n    RecyclerAdapter<String, ItemLogBinding>(context) {\n\n    override fun getViewBinding(parent: ViewGroup): ItemLogBinding {\n        return ItemLogBinding.inflate(inflater, parent, false)\n    }\n\n    override fun convert(\n        holder: ItemViewHolder,\n        binding: ItemLogBinding,\n        item: String,\n        payloads: MutableList<Any>\n    ) {\n        binding.apply {\n            if (textView.getTag(R.id.tag1) == null) {\n                val listener = object : View.OnAttachStateChangeListener {\n                    override fun onViewAttachedToWindow(v: View) {\n                        textView.isCursorVisible = false\n                        textView.isCursorVisible = true\n                    }\n\n                    override fun onViewDetachedFromWindow(v: View) {}\n                }\n                textView.addOnAttachStateChangeListener(listener)\n                textView.setTag(R.id.tag1, listener)\n            }\n            textView.text = item\n        }\n    }\n\n    override fun registerListener(holder: ItemViewHolder, binding: ItemLogBinding) {\n        //nothing\n    }\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/rss/source/debug/RssSourceDebugModel.kt",
    "content": "package io.legado.app.ui.rss.source.debug\n\nimport android.app.Application\nimport io.legado.app.base.BaseViewModel\nimport io.legado.app.data.appDb\nimport io.legado.app.data.entities.RssSource\nimport io.legado.app.model.Debug\n\nclass RssSourceDebugModel(application: Application) : BaseViewModel(application),\n    Debug.Callback {\n    var rssSource: RssSource? = null\n    private var callback: ((Int, String) -> Unit)? = null\n    var listSrc: String? = null\n    var contentSrc: String? = null\n\n    fun initData(sourceUrl: String?, finally: () -> Unit) {\n        sourceUrl?.let {\n            execute {\n                rssSource = appDb.rssSourceDao.getByKey(sourceUrl)\n            }.onFinally {\n                finally()\n            }\n        }\n    }\n\n    fun observe(callback: (Int, String) -> Unit) {\n        this.callback = callback\n    }\n\n    fun startDebug(source: RssSource) {\n        execute {\n            Debug.callback = this@RssSourceDebugModel\n            Debug.startDebug(this, source)\n        }\n    }\n\n    override fun printLog(state: Int, msg: String) {\n        when (state) {\n            10 -> listSrc = msg\n            20 -> contentSrc = msg\n            else -> callback?.invoke(state, msg)\n        }\n    }\n\n    override fun onCleared() {\n        super.onCleared()\n        Debug.cancelDebug(true)\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/rss/source/edit/RssSourceEditActivity.kt",
    "content": "package io.legado.app.ui.rss.source.edit\n\nimport android.os.Bundle\nimport android.view.Menu\nimport android.view.MenuItem\nimport android.widget.EditText\nimport androidx.activity.viewModels\nimport androidx.lifecycle.lifecycleScope\nimport com.google.android.material.tabs.TabLayout\nimport com.google.zxing.qrcode.decoder.ErrorCorrectionLevel\nimport io.legado.app.R\nimport io.legado.app.base.VMBaseActivity\nimport io.legado.app.data.entities.RssSource\nimport io.legado.app.databinding.ActivityRssSourceEditBinding\nimport io.legado.app.help.config.LocalConfig\nimport io.legado.app.lib.dialogs.SelectItem\nimport io.legado.app.lib.dialogs.alert\nimport io.legado.app.lib.theme.accentColor\nimport io.legado.app.lib.theme.backgroundColor\nimport io.legado.app.lib.theme.primaryColor\nimport io.legado.app.ui.file.HandleFileContract\nimport io.legado.app.ui.login.SourceLoginActivity\nimport io.legado.app.ui.qrcode.QrCodeResult\nimport io.legado.app.ui.rss.source.debug.RssSourceDebugActivity\nimport io.legado.app.ui.widget.dialog.UrlOptionDialog\nimport io.legado.app.ui.widget.dialog.VariableDialog\nimport io.legado.app.ui.widget.keyboard.KeyboardToolPop\nimport io.legado.app.ui.widget.text.EditEntity\nimport io.legado.app.utils.GSON\nimport io.legado.app.utils.imeHeight\nimport io.legado.app.utils.isContentScheme\nimport io.legado.app.utils.isTrue\nimport io.legado.app.utils.launch\nimport io.legado.app.utils.navigationBarHeight\nimport io.legado.app.utils.sendToClip\nimport io.legado.app.utils.setEdgeEffectColor\nimport io.legado.app.utils.setOnApplyWindowInsetsListenerCompat\nimport io.legado.app.utils.share\nimport io.legado.app.utils.shareWithQr\nimport io.legado.app.utils.showDialogFragment\nimport io.legado.app.utils.showHelp\nimport io.legado.app.utils.startActivity\nimport io.legado.app.utils.viewbindingdelegate.viewBinding\nimport kotlinx.coroutines.Dispatchers\nimport kotlinx.coroutines.launch\nimport kotlinx.coroutines.withContext\nimport splitties.views.bottomPadding\n\nclass RssSourceEditActivity :\n    VMBaseActivity<ActivityRssSourceEditBinding, RssSourceEditViewModel>(),\n    KeyboardToolPop.CallBack,\n    VariableDialog.Callback {\n\n    override val binding by viewBinding(ActivityRssSourceEditBinding::inflate)\n    override val viewModel by viewModels<RssSourceEditViewModel>()\n    private val softKeyboardTool by lazy {\n        KeyboardToolPop(this, lifecycleScope, binding.root, this)\n    }\n    private val adapter by lazy { RssSourceEditAdapter() }\n    private val sourceEntities: ArrayList<EditEntity> = ArrayList()\n    private val listEntities: ArrayList<EditEntity> = ArrayList()\n    private val webViewEntities: ArrayList<EditEntity> = ArrayList()\n    private val selectDoc = registerForActivityResult(HandleFileContract()) {\n        it.uri?.let { uri ->\n            if (uri.isContentScheme()) {\n                sendText(uri.toString())\n            } else {\n                sendText(uri.path.toString())\n            }\n        }\n    }\n    private val qrCodeResult = registerForActivityResult(QrCodeResult()) {\n        it?.let {\n            viewModel.importSource(it) { source: RssSource ->\n                upSourceView(source)\n            }\n        }\n    }\n\n    override fun onActivityCreated(savedInstanceState: Bundle?) {\n        softKeyboardTool.attachToWindow(window)\n        initView()\n        viewModel.initData(intent) {\n            upSourceView(viewModel.rssSource)\n        }\n    }\n\n    override fun onPostCreate(savedInstanceState: Bundle?) {\n        super.onPostCreate(savedInstanceState)\n        if (!LocalConfig.ruleHelpVersionIsLast) {\n            showHelp(\"ruleHelp\")\n        }\n    }\n\n    override fun finish() {\n        val source = getRssSource()\n        if (!source.equal(viewModel.rssSource ?: RssSource())) {\n            alert(R.string.exit) {\n                setMessage(R.string.exit_no_save)\n                positiveButton(R.string.yes)\n                negativeButton(R.string.no) {\n                    super.finish()\n                }\n            }\n        } else {\n            super.finish()\n        }\n    }\n\n    override fun onDestroy() {\n        super.onDestroy()\n        softKeyboardTool.dismiss()\n    }\n\n    override fun onCompatCreateOptionsMenu(menu: Menu): Boolean {\n        menuInflater.inflate(R.menu.source_edit, menu)\n        return super.onCompatCreateOptionsMenu(menu)\n    }\n\n    override fun onMenuOpened(featureId: Int, menu: Menu): Boolean {\n        menu.findItem(R.id.menu_login)?.isVisible = !viewModel.rssSource?.loginUrl.isNullOrBlank()\n        menu.findItem(R.id.menu_auto_complete)?.isChecked = viewModel.autoComplete\n        return super.onMenuOpened(featureId, menu)\n    }\n\n    override fun onCompatOptionsItemSelected(item: MenuItem): Boolean {\n        when (item.itemId) {\n            R.id.menu_save -> viewModel.save(getRssSource()) {\n                setResult(RESULT_OK)\n                finish()\n            }\n\n            R.id.menu_debug_source -> viewModel.save(getRssSource()) { source ->\n                startActivity<RssSourceDebugActivity> {\n                    putExtra(\"key\", source.sourceUrl)\n                }\n            }\n\n            R.id.menu_login -> viewModel.save(getRssSource()) {\n                startActivity<SourceLoginActivity> {\n                    putExtra(\"type\", \"rssSource\")\n                    putExtra(\"key\", it.sourceUrl)\n                }\n            }\n\n            R.id.menu_set_source_variable -> setSourceVariable()\n            R.id.menu_clear_cookie -> viewModel.clearCookie(getRssSource().sourceUrl)\n            R.id.menu_auto_complete -> viewModel.autoComplete = !viewModel.autoComplete\n            R.id.menu_copy_source -> sendToClip(GSON.toJson(getRssSource()))\n            R.id.menu_qr_code_camera -> qrCodeResult.launch()\n            R.id.menu_paste_source -> viewModel.pasteSource { upSourceView(it) }\n            R.id.menu_share_str -> share(GSON.toJson(getRssSource()))\n            R.id.menu_share_qr -> shareWithQr(\n                GSON.toJson(getRssSource()),\n                getString(R.string.share_rss_source),\n                ErrorCorrectionLevel.L\n            )\n\n            R.id.menu_help -> showHelp(\"ruleHelp\")\n        }\n        return super.onCompatOptionsItemSelected(item)\n    }\n\n    private fun initView() {\n        binding.tabLayout.addTab(binding.tabLayout.newTab().apply {\n            setText(R.string.source_tab_base)\n        })\n        binding.tabLayout.addTab(binding.tabLayout.newTab().apply {\n            setText(R.string.source_tab_list)\n        })\n        binding.tabLayout.addTab(binding.tabLayout.newTab().apply {\n            text = \"WEB_VIEW\"\n        })\n        binding.recyclerView.setEdgeEffectColor(primaryColor)\n        binding.recyclerView.adapter = adapter\n        binding.tabLayout.setBackgroundColor(backgroundColor)\n        binding.tabLayout.setSelectedTabIndicatorColor(accentColor)\n        binding.tabLayout.addOnTabSelectedListener(object : TabLayout.OnTabSelectedListener {\n            override fun onTabReselected(tab: TabLayout.Tab?) {\n\n            }\n\n            override fun onTabUnselected(tab: TabLayout.Tab?) {\n\n            }\n\n            override fun onTabSelected(tab: TabLayout.Tab?) {\n                setEditEntities(tab?.position)\n            }\n        })\n        binding.recyclerView.setOnApplyWindowInsetsListenerCompat { view, windowInsets ->\n            val navigationBarHeight = windowInsets.navigationBarHeight\n            val imeHeight = windowInsets.imeHeight\n            view.bottomPadding = if (imeHeight == 0) navigationBarHeight else 0\n            softKeyboardTool.initialPadding = imeHeight\n            windowInsets\n        }\n    }\n\n    private fun setEditEntities(tabPosition: Int?) {\n        when (tabPosition) {\n            1 -> adapter.editEntities = listEntities\n            2 -> adapter.editEntities = webViewEntities\n            else -> adapter.editEntities = sourceEntities\n        }\n        binding.recyclerView.scrollToPosition(0)\n    }\n\n    private fun upSourceView(rssSource: RssSource?) {\n        val rs = rssSource ?: RssSource()\n        rs.let {\n            binding.cbIsEnable.isChecked = rs.enabled\n            binding.cbSingleUrl.isChecked = rs.singleUrl\n            binding.cbIsEnableCookie.isChecked = rs.enabledCookieJar == true\n        }\n        sourceEntities.clear()\n        sourceEntities.apply {\n            add(EditEntity(\"sourceName\", rs.sourceName, R.string.source_name))\n            add(EditEntity(\"sourceUrl\", rs.sourceUrl, R.string.source_url))\n            add(EditEntity(\"sourceIcon\", rs.sourceIcon, R.string.source_icon))\n            add(EditEntity(\"sourceGroup\", rs.sourceGroup, R.string.source_group))\n            add(EditEntity(\"sourceComment\", rs.sourceComment, R.string.comment))\n            add(EditEntity(\"sortUrl\", rs.sortUrl, R.string.sort_url))\n            add(EditEntity(\"loginUrl\", rs.loginUrl, R.string.login_url))\n            add(EditEntity(\"loginUi\", rs.loginUi, R.string.login_ui))\n            add(EditEntity(\"loginCheckJs\", rs.loginCheckJs, R.string.login_check_js))\n            add(EditEntity(\"coverDecodeJs\", rs.coverDecodeJs, R.string.cover_decode_js))\n            add(EditEntity(\"header\", rs.header, R.string.source_http_header))\n            add(EditEntity(\"variableComment\", rs.variableComment, R.string.variable_comment))\n            add(EditEntity(\"concurrentRate\", rs.concurrentRate, R.string.concurrent_rate))\n            add(EditEntity(\"jsLib\", rs.jsLib, \"jsLib\"))\n        }\n        listEntities.clear()\n        listEntities.apply {\n            add(EditEntity(\"ruleArticles\", rs.ruleArticles, R.string.r_articles))\n            add(EditEntity(\"ruleNextPage\", rs.ruleNextPage, R.string.r_next))\n            add(EditEntity(\"ruleTitle\", rs.ruleTitle, R.string.r_title))\n            add(EditEntity(\"rulePubDate\", rs.rulePubDate, R.string.r_date))\n            add(EditEntity(\"ruleDescription\", rs.ruleDescription, R.string.r_description))\n            add(EditEntity(\"ruleImage\", rs.ruleImage, R.string.r_image))\n            add(EditEntity(\"ruleLink\", rs.ruleLink, R.string.r_link))\n        }\n        webViewEntities.clear()\n        webViewEntities.apply {\n            add(\n                EditEntity(\n                    \"enableJs\",\n                    rs.enableJs.toString(),\n                    R.string.enable_js,\n                    EditEntity.ViewType.checkBox\n                )\n            )\n            add(\n                EditEntity(\n                    \"loadWithBaseUrl\",\n                    rs.loadWithBaseUrl.toString(),\n                    R.string.load_with_base_url,\n                    EditEntity.ViewType.checkBox\n                )\n            )\n            add(EditEntity(\"ruleContent\", rs.ruleContent, R.string.r_content))\n            add(EditEntity(\"style\", rs.style, R.string.r_style))\n            add(EditEntity(\"injectJs\", rs.injectJs, R.string.r_inject_js))\n            add(EditEntity(\"contentWhitelist\", rs.contentWhitelist, R.string.c_whitelist))\n            add(EditEntity(\"contentBlacklist\", rs.contentBlacklist, R.string.c_blacklist))\n            add(\n                EditEntity(\n                    \"shouldOverrideUrlLoading\",\n                    rs.shouldOverrideUrlLoading,\n                    \"url跳转拦截(js, 返回true拦截,js变量url,可以通过js打开url,比如调用阅读搜索,添加书架等,简化规则写法,不用webView js注入)\"\n                )\n            )\n        }\n        binding.tabLayout.selectTab(binding.tabLayout.getTabAt(0))\n        setEditEntities(0)\n    }\n\n    private fun getRssSource(): RssSource {\n        val source = viewModel.rssSource?.copy() ?: RssSource()\n        source.enabled = binding.cbIsEnable.isChecked\n        source.singleUrl = binding.cbSingleUrl.isChecked\n        source.enabledCookieJar = binding.cbIsEnableCookie.isChecked\n        sourceEntities.forEach {\n            it.value = it.value?.takeIf { s -> s.isNotBlank() }\n            when (it.key) {\n                \"sourceName\" -> source.sourceName = it.value ?: \"\"\n                \"sourceUrl\" -> source.sourceUrl = it.value ?: \"\"\n                \"sourceIcon\" -> source.sourceIcon = it.value ?: \"\"\n                \"sourceGroup\" -> source.sourceGroup = it.value\n                \"sourceComment\" -> source.sourceComment = it.value\n                \"loginUrl\" -> source.loginUrl = it.value\n                \"loginUi\" -> source.loginUi = it.value\n                \"loginCheckJs\" -> source.loginCheckJs = it.value\n                \"coverDecodeJs\" -> source.coverDecodeJs = it.value\n                \"header\" -> source.header = it.value\n                \"variableComment\" -> source.variableComment = it.value\n                \"concurrentRate\" -> source.concurrentRate = it.value\n                \"sortUrl\" -> source.sortUrl = it.value\n                \"jsLib\" -> source.jsLib = it.value\n            }\n        }\n        listEntities.forEach {\n            it.value = it.value?.takeIf { s -> s.isNotBlank() }\n            when (it.key) {\n                \"ruleArticles\" -> source.ruleArticles = it.value\n                \"ruleNextPage\" -> source.ruleNextPage =\n                    viewModel.ruleComplete(it.value, source.ruleArticles, 2)\n\n                \"ruleTitle\" -> source.ruleTitle =\n                    viewModel.ruleComplete(it.value, source.ruleArticles)\n\n                \"rulePubDate\" -> source.rulePubDate =\n                    viewModel.ruleComplete(it.value, source.ruleArticles)\n\n                \"ruleDescription\" -> source.ruleDescription =\n                    viewModel.ruleComplete(it.value, source.ruleArticles)\n\n                \"ruleImage\" -> source.ruleImage =\n                    viewModel.ruleComplete(it.value, source.ruleArticles, 3)\n\n                \"ruleLink\" -> source.ruleLink =\n                    viewModel.ruleComplete(it.value, source.ruleArticles)\n            }\n        }\n        webViewEntities.forEach {\n            it.value = it.value?.takeIf { s -> s.isNotBlank() }\n            when (it.key) {\n                \"enableJs\" -> source.enableJs = it.value.isTrue()\n                \"loadWithBaseUrl\" -> source.loadWithBaseUrl = it.value.isTrue()\n                \"ruleContent\" -> source.ruleContent =\n                    viewModel.ruleComplete(it.value, source.ruleArticles)\n\n                \"style\" -> source.style = it.value\n                \"injectJs\" -> source.injectJs = it.value\n                \"contentWhitelist\" -> source.contentWhitelist = it.value\n                \"contentBlacklist\" -> source.contentBlacklist = it.value\n                \"shouldOverrideUrlLoading\" -> source.shouldOverrideUrlLoading = it.value\n            }\n        }\n        return source\n    }\n\n    private fun setSourceVariable() {\n        viewModel.save(getRssSource()) { source ->\n            lifecycleScope.launch {\n                val comment =\n                    source.getDisplayVariableComment(\"源变量可在js中通过source.getVariable()获取\")\n                val variable = withContext(Dispatchers.IO) { source.getVariable() }\n                showDialogFragment(\n                    VariableDialog(\n                        getString(R.string.set_source_variable),\n                        source.getKey(),\n                        variable,\n                        comment\n                    )\n                )\n            }\n        }\n    }\n\n    override fun setVariable(key: String, variable: String?) {\n        viewModel.rssSource?.setVariable(variable)\n    }\n\n    override fun helpActions(): List<SelectItem<String>> {\n        return arrayListOf(\n            SelectItem(\"插入URL参数\", \"urlOption\"),\n            SelectItem(\"订阅源教程\", \"ruleHelp\"),\n            SelectItem(\"js教程\", \"jsHelp\"),\n            SelectItem(\"正则教程\", \"regexHelp\"),\n            SelectItem(\"选择文件\", \"selectFile\"),\n        )\n    }\n\n    override fun onHelpActionSelect(action: String) {\n        when (action) {\n            \"urlOption\" -> UrlOptionDialog(this) {\n                sendText(it)\n            }.show()\n\n            \"ruleHelp\" -> showHelp(\"ruleHelp\")\n            \"jsHelp\" -> showHelp(\"jsHelp\")\n            \"regexHelp\" -> showHelp(\"regexHelp\")\n            \"selectFile\" -> selectDoc.launch {\n                mode = HandleFileContract.FILE\n            }\n        }\n    }\n\n    override fun sendText(text: String) {\n        if (text.isBlank()) return\n        val view = window.decorView.findFocus()\n        if (view is EditText) {\n            val start = view.selectionStart\n            val end = view.selectionEnd\n            val edit = view.editableText//获取EditText的文字\n            if (start < 0 || start >= edit.length) {\n                edit.append(text)\n            } else if (start > end) {\n                edit.replace(end, start, text)\n            } else {\n                edit.replace(start, end, text)//光标所在位置插入文字\n            }\n        }\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/rss/source/edit/RssSourceEditAdapter.kt",
    "content": "package io.legado.app.ui.rss.source.edit\n\nimport android.annotation.SuppressLint\nimport android.text.Editable\nimport android.text.TextWatcher\nimport android.view.LayoutInflater\nimport android.view.View\nimport android.view.ViewGroup\nimport androidx.recyclerview.widget.RecyclerView\nimport io.legado.app.R\nimport io.legado.app.databinding.ItemSourceEditBinding\nimport io.legado.app.databinding.ItemSourceEditCheckBoxBinding\nimport io.legado.app.help.config.AppConfig\nimport io.legado.app.ui.widget.code.addJsPattern\nimport io.legado.app.ui.widget.code.addJsonPattern\nimport io.legado.app.ui.widget.code.addLegadoPattern\nimport io.legado.app.ui.widget.text.EditEntity\nimport io.legado.app.utils.isTrue\n\nclass RssSourceEditAdapter : RecyclerView.Adapter<RecyclerView.ViewHolder>() {\n\n    val editEntityMaxLine = AppConfig.sourceEditMaxLine\n\n    var editEntities: ArrayList<EditEntity> = ArrayList()\n        @SuppressLint(\"NotifyDataSetChanged\")\n        set(value) {\n            field = value\n            notifyDataSetChanged()\n        }\n\n    override fun getItemViewType(position: Int): Int {\n        val item = editEntities[position]\n        return item.viewType\n    }\n\n    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {\n        return when (viewType) {\n            EditEntity.ViewType.checkBox -> {\n                val binding = ItemSourceEditCheckBoxBinding\n                    .inflate(LayoutInflater.from(parent.context), parent, false)\n                CheckBoxViewHolder(binding)\n            }\n\n            else -> {\n                val binding = ItemSourceEditBinding\n                    .inflate(LayoutInflater.from(parent.context), parent, false)\n                binding.editText.addLegadoPattern()\n                binding.editText.addJsonPattern()\n                binding.editText.addJsPattern()\n                EditTextViewHolder(binding)\n            }\n        }\n    }\n\n    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {\n        when (holder) {\n            is CheckBoxViewHolder -> holder.bind(editEntities[position])\n            is EditTextViewHolder -> holder.bind(editEntities[position])\n        }\n    }\n\n    override fun getItemCount(): Int {\n        return editEntities.size\n    }\n\n    inner class EditTextViewHolder(val binding: ItemSourceEditBinding) :\n        RecyclerView.ViewHolder(binding.root) {\n\n        fun bind(editEntity: EditEntity) = binding.run {\n            editText.maxLines = editEntityMaxLine\n            if (editText.getTag(R.id.tag1) == null) {\n                val listener = object : View.OnAttachStateChangeListener {\n                    override fun onViewAttachedToWindow(v: View) {\n                        editText.isCursorVisible = false\n                        editText.isCursorVisible = true\n                        editText.isFocusable = true\n                        editText.isFocusableInTouchMode = true\n                    }\n\n                    override fun onViewDetachedFromWindow(v: View) {\n\n                    }\n                }\n                editText.addOnAttachStateChangeListener(listener)\n                editText.setTag(R.id.tag1, listener)\n            }\n            editText.getTag(R.id.tag2)?.let {\n                if (it is TextWatcher) {\n                    editText.removeTextChangedListener(it)\n                }\n            }\n            editText.setText(editEntity.value)\n            textInputLayout.hint = editEntity.hint\n            val textWatcher = object : TextWatcher {\n                override fun beforeTextChanged(\n                    s: CharSequence,\n                    start: Int,\n                    count: Int,\n                    after: Int\n                ) {\n\n                }\n\n                override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {\n\n                }\n\n                override fun afterTextChanged(s: Editable?) {\n                    editEntity.value = (s?.toString())\n                }\n            }\n            editText.addTextChangedListener(textWatcher)\n            editText.setTag(R.id.tag2, textWatcher)\n        }\n    }\n\n    class CheckBoxViewHolder(val binding: ItemSourceEditCheckBoxBinding) :\n        RecyclerView.ViewHolder(binding.root) {\n\n        fun bind(editEntity: EditEntity) = binding.run {\n            checkBox.text = editEntity.hint\n            checkBox.isChecked = editEntity.value.isTrue()\n            checkBox.setOnUserCheckedChangeListener { isChecked ->\n                editEntity.value = isChecked.toString()\n            }\n        }\n\n    }\n\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/rss/source/edit/RssSourceEditViewModel.kt",
    "content": "package io.legado.app.ui.rss.source.edit\n\nimport android.app.Application\nimport android.content.Intent\nimport io.legado.app.R\nimport io.legado.app.base.BaseViewModel\nimport io.legado.app.data.appDb\nimport io.legado.app.data.entities.RssSource\nimport io.legado.app.exception.NoStackTraceException\nimport io.legado.app.help.AppCacheManager\nimport io.legado.app.help.RuleComplete\nimport io.legado.app.help.http.CookieStore\nimport io.legado.app.help.source.removeSortCache\nimport io.legado.app.model.SharedJsScope\nimport io.legado.app.utils.GSON\nimport io.legado.app.utils.fromJsonObject\nimport io.legado.app.utils.getClipText\nimport io.legado.app.utils.printOnDebug\nimport io.legado.app.utils.stackTraceStr\nimport io.legado.app.utils.toastOnUi\nimport kotlinx.coroutines.Dispatchers\n\n\nclass RssSourceEditViewModel(application: Application) : BaseViewModel(application) {\n    var autoComplete = false\n    var rssSource: RssSource? = null\n\n    fun initData(intent: Intent, onFinally: () -> Unit) {\n        execute {\n            val key = intent.getStringExtra(\"sourceUrl\")\n            if (key != null) {\n                appDb.rssSourceDao.getByKey(key)?.let {\n                    rssSource = it\n                }\n            }\n        }.onFinally {\n            onFinally()\n        }\n    }\n\n    fun save(source: RssSource, success: ((RssSource) -> Unit)) {\n        execute {\n            if (source.sourceUrl.isBlank() || source.sourceName.isBlank()) {\n                throw NoStackTraceException(context.getString(R.string.non_null_name_url))\n            }\n            val oldSource = rssSource ?: RssSource()\n            if (!source.equal(oldSource)) {\n                source.lastUpdateTime = System.currentTimeMillis()\n                if (oldSource.sortUrl != source.sortUrl) {\n                    oldSource.removeSortCache()\n                }\n                if (oldSource.jsLib != source.jsLib) {\n                    SharedJsScope.remove(oldSource.jsLib)\n                }\n            }\n            rssSource?.let {\n                appDb.rssSourceDao.delete(it)\n                //更新收藏的源地址\n                if (it.sourceUrl != source.sourceUrl) {\n                    appDb.rssStarDao.updateOrigin(source.sourceUrl, it.sourceUrl)\n                    appDb.rssArticleDao.updateOrigin(source.sourceUrl, it.sourceUrl)\n                    appDb.cacheDao.deleteSourceVariables(it.sourceUrl)\n                    AppCacheManager.clearSourceVariables()\n                }\n            }\n            appDb.rssSourceDao.insert(source)\n            rssSource = source\n            source\n        }.onSuccess {\n            success(it)\n        }.onError {\n            context.toastOnUi(it.localizedMessage)\n            it.printOnDebug()\n        }\n    }\n\n    fun pasteSource(onSuccess: (source: RssSource) -> Unit) {\n        execute(context = Dispatchers.Main) {\n            var source: RssSource? = null\n            context.getClipText()?.let { json ->\n                source = GSON.fromJsonObject<RssSource>(json).getOrThrow()\n            }\n            source\n        }.onError {\n            context.toastOnUi(it.localizedMessage)\n        }.onSuccess {\n            if (it != null) {\n                onSuccess(it)\n            } else {\n                context.toastOnUi(\"格式不对\")\n            }\n        }\n    }\n\n    fun importSource(text: String, finally: (source: RssSource) -> Unit) {\n        execute {\n            val text1 = text.trim()\n            GSON.fromJsonObject<RssSource>(text1).getOrThrow().let {\n                finally.invoke(it)\n            }\n        }.onError {\n            context.toastOnUi(it.stackTraceStr)\n        }\n    }\n\n    fun clearCookie(url: String) {\n        execute {\n            CookieStore.removeCookie(url)\n        }\n    }\n\n    fun ruleComplete(rule: String?, preRule: String? = null, type: Int = 1): String? {\n        if (autoComplete) {\n            return RuleComplete.autoComplete(rule, preRule, type)\n        }\n        return rule\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/rss/source/manage/GroupManageDialog.kt",
    "content": "package io.legado.app.ui.rss.source.manage\n\nimport android.annotation.SuppressLint\nimport android.content.Context\nimport android.os.Bundle\nimport android.view.MenuItem\nimport android.view.View\nimport android.view.ViewGroup\nimport androidx.appcompat.widget.Toolbar\nimport androidx.fragment.app.activityViewModels\nimport androidx.lifecycle.lifecycleScope\nimport androidx.recyclerview.widget.LinearLayoutManager\nimport io.legado.app.R\nimport io.legado.app.base.BaseDialogFragment\nimport io.legado.app.base.adapter.ItemViewHolder\nimport io.legado.app.base.adapter.RecyclerAdapter\nimport io.legado.app.data.appDb\nimport io.legado.app.databinding.DialogEditTextBinding\nimport io.legado.app.databinding.DialogRecyclerViewBinding\nimport io.legado.app.databinding.ItemGroupManageBinding\nimport io.legado.app.lib.dialogs.alert\nimport io.legado.app.lib.theme.accentColor\nimport io.legado.app.lib.theme.backgroundColor\nimport io.legado.app.lib.theme.primaryColor\nimport io.legado.app.ui.widget.recycler.VerticalDivider\nimport io.legado.app.utils.applyTint\nimport io.legado.app.utils.requestInputMethod\nimport io.legado.app.utils.setLayout\nimport io.legado.app.utils.viewbindingdelegate.viewBinding\nimport io.legado.app.utils.visible\nimport kotlinx.coroutines.flow.conflate\nimport kotlinx.coroutines.launch\n\n\nclass GroupManageDialog : BaseDialogFragment(R.layout.dialog_recycler_view),\n    Toolbar.OnMenuItemClickListener {\n\n    private val viewModel: RssSourceViewModel by activityViewModels()\n    private val binding by viewBinding(DialogRecyclerViewBinding::bind)\n    private val adapter by lazy { GroupAdapter(requireContext()) }\n\n    override fun onStart() {\n        super.onStart()\n        setLayout(0.9f, 0.9f)\n    }\n\n    override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) = binding.run {\n        toolBar.setBackgroundColor(primaryColor)\n        toolBar.title = getString(R.string.group_manage)\n        toolBar.inflateMenu(R.menu.group_manage)\n        toolBar.menu.applyTint(requireContext())\n        toolBar.setOnMenuItemClickListener(this@GroupManageDialog)\n        recyclerView.layoutManager = LinearLayoutManager(requireContext())\n        recyclerView.addItemDecoration(VerticalDivider(requireContext()))\n        recyclerView.adapter = adapter\n        tvOk.setTextColor(requireContext().accentColor)\n        tvOk.visible()\n        tvOk.setOnClickListener {\n            dismissAllowingStateLoss()\n        }\n        initData()\n    }\n\n    private fun initData() {\n        lifecycleScope.launch {\n            appDb.rssSourceDao.flowGroups().conflate().collect {\n                adapter.setItems(it)\n            }\n        }\n    }\n\n    override fun onMenuItemClick(item: MenuItem?): Boolean {\n        when (item?.itemId) {\n            R.id.menu_add -> addGroup()\n        }\n        return true\n    }\n\n    @SuppressLint(\"InflateParams\")\n    private fun addGroup() {\n        alert(title = getString(R.string.add_group)) {\n            val alertBinding = DialogEditTextBinding.inflate(layoutInflater).apply {\n                editView.setHint(R.string.group_name)\n            }\n            customView { alertBinding.root }\n            okButton {\n                alertBinding.editView.text?.toString()?.let {\n                    if (it.isNotBlank()) {\n                        viewModel.addGroup(it)\n                    }\n                }\n            }\n            cancelButton()\n        }.requestInputMethod()\n    }\n\n    @SuppressLint(\"InflateParams\")\n    private fun editGroup(group: String) {\n        alert(title = getString(R.string.group_edit)) {\n            val alertBinding = DialogEditTextBinding.inflate(layoutInflater).apply {\n                editView.setHint(R.string.group_name)\n                editView.setText(group)\n            }\n            customView { alertBinding.root }\n            okButton {\n                viewModel.upGroup(group, alertBinding.editView.text?.toString())\n            }\n            cancelButton()\n        }.requestInputMethod()\n    }\n\n    private inner class GroupAdapter(context: Context) :\n        RecyclerAdapter<String, ItemGroupManageBinding>(context) {\n\n        override fun getViewBinding(parent: ViewGroup): ItemGroupManageBinding {\n            return ItemGroupManageBinding.inflate(inflater, parent, false)\n        }\n\n        override fun convert(\n            holder: ItemViewHolder,\n            binding: ItemGroupManageBinding,\n            item: String,\n            payloads: MutableList<Any>\n        ) {\n            binding.run {\n                root.setBackgroundColor(context.backgroundColor)\n                tvGroup.text = item\n            }\n        }\n\n        override fun registerListener(holder: ItemViewHolder, binding: ItemGroupManageBinding) {\n            binding.apply {\n                tvEdit.setOnClickListener {\n                    getItem(holder.layoutPosition)?.let {\n                        editGroup(it)\n                    }\n                }\n\n                tvDel.setOnClickListener {\n                    getItem(holder.layoutPosition)?.let {\n                        viewModel.delGroup(it)\n                    }\n                }\n            }\n        }\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/rss/source/manage/RssSourceActivity.kt",
    "content": "package io.legado.app.ui.rss.source.manage\n\nimport android.annotation.SuppressLint\nimport android.os.Bundle\nimport android.view.Menu\nimport android.view.MenuItem\nimport android.view.SubMenu\nimport androidx.activity.viewModels\nimport androidx.appcompat.widget.PopupMenu\nimport androidx.appcompat.widget.SearchView\nimport androidx.lifecycle.lifecycleScope\nimport androidx.recyclerview.widget.ItemTouchHelper\nimport io.legado.app.R\nimport io.legado.app.base.VMBaseActivity\nimport io.legado.app.constant.AppLog\nimport io.legado.app.data.appDb\nimport io.legado.app.data.entities.RssSource\nimport io.legado.app.databinding.ActivityRssSourceBinding\nimport io.legado.app.databinding.DialogEditTextBinding\nimport io.legado.app.help.DirectLinkUpload\nimport io.legado.app.lib.dialogs.alert\nimport io.legado.app.lib.theme.primaryColor\nimport io.legado.app.lib.theme.primaryTextColor\nimport io.legado.app.ui.association.ImportRssSourceDialog\nimport io.legado.app.ui.file.HandleFileContract\nimport io.legado.app.ui.qrcode.QrCodeResult\nimport io.legado.app.ui.rss.source.edit.RssSourceEditActivity\nimport io.legado.app.ui.widget.SelectActionBar\nimport io.legado.app.ui.widget.recycler.DragSelectTouchHelper\nimport io.legado.app.ui.widget.recycler.ItemTouchCallback\nimport io.legado.app.ui.widget.recycler.VerticalDivider\nimport io.legado.app.utils.ACache\nimport io.legado.app.utils.applyTint\nimport io.legado.app.utils.dpToPx\nimport io.legado.app.utils.isAbsUrl\nimport io.legado.app.utils.launch\nimport io.legado.app.utils.sendToClip\nimport io.legado.app.utils.setEdgeEffectColor\nimport io.legado.app.utils.share\nimport io.legado.app.utils.showDialogFragment\nimport io.legado.app.utils.showHelp\nimport io.legado.app.utils.splitNotBlank\nimport io.legado.app.utils.startActivity\nimport io.legado.app.utils.transaction\nimport io.legado.app.utils.viewbindingdelegate.viewBinding\nimport kotlinx.coroutines.Dispatchers.IO\nimport kotlinx.coroutines.Job\nimport kotlinx.coroutines.delay\nimport kotlinx.coroutines.flow.catch\nimport kotlinx.coroutines.flow.conflate\nimport kotlinx.coroutines.flow.flowOn\nimport kotlinx.coroutines.launch\n\n/**\n * 订阅源管理\n */\nclass RssSourceActivity : VMBaseActivity<ActivityRssSourceBinding, RssSourceViewModel>(),\n    PopupMenu.OnMenuItemClickListener,\n    SelectActionBar.CallBack,\n    RssSourceAdapter.CallBack {\n\n    override val binding by viewBinding(ActivityRssSourceBinding::inflate)\n    override val viewModel by viewModels<RssSourceViewModel>()\n    private val importRecordKey = \"rssSourceRecordKey\"\n    private val adapter by lazy { RssSourceAdapter(this, this) }\n    private val searchView: SearchView by lazy {\n        binding.titleBar.findViewById(R.id.search_view)\n    }\n    private var sourceFlowJob: Job? = null\n    private var groups = arrayListOf<String>()\n    private var groupMenu: SubMenu? = null\n    private val qrCodeResult = registerForActivityResult(QrCodeResult()) {\n        it ?: return@registerForActivityResult\n        showDialogFragment(ImportRssSourceDialog(it))\n    }\n    private val importDoc = registerForActivityResult(HandleFileContract()) {\n        it.uri?.let { uri ->\n            showDialogFragment(ImportRssSourceDialog(uri.toString()))\n        }\n    }\n    private val exportResult = registerForActivityResult(HandleFileContract()) {\n        it.uri?.let { uri ->\n            alert(R.string.export_success) {\n                if (uri.toString().isAbsUrl()) {\n                    setMessage(DirectLinkUpload.getSummary())\n                }\n                val alertBinding = DialogEditTextBinding.inflate(layoutInflater).apply {\n                    editView.hint = getString(R.string.path)\n                    editView.setText(uri.toString())\n                }\n                customView { alertBinding.root }\n                okButton {\n                    sendToClip(uri.toString())\n                }\n            }\n        }\n    }\n\n    override fun onActivityCreated(savedInstanceState: Bundle?) {\n        initRecyclerView()\n        initSearchView()\n        initGroupFlow()\n        upSourceFlow()\n        initSelectActionBar()\n    }\n\n    override fun onCompatCreateOptionsMenu(menu: Menu): Boolean {\n        menuInflater.inflate(R.menu.rss_source, menu)\n        return super.onCompatCreateOptionsMenu(menu)\n    }\n\n    override fun onPrepareOptionsMenu(menu: Menu): Boolean {\n        groupMenu = menu.findItem(R.id.menu_group)?.subMenu\n        upGroupMenu()\n        return super.onPrepareOptionsMenu(menu)\n    }\n\n    override fun onCompatOptionsItemSelected(item: MenuItem): Boolean {\n        when (item.itemId) {\n            R.id.menu_add -> startActivity<RssSourceEditActivity>()\n            R.id.menu_import_local -> importDoc.launch {\n                mode = HandleFileContract.FILE\n                allowExtensions = arrayOf(\"txt\", \"json\")\n            }\n\n            R.id.menu_import_onLine -> showImportDialog()\n            R.id.menu_import_qr -> qrCodeResult.launch()\n            R.id.menu_group_manage -> showDialogFragment<GroupManageDialog>()\n            R.id.menu_import_default -> viewModel.importDefault()\n            R.id.menu_enabled_group -> {\n                searchView.setQuery(getString(R.string.enabled), true)\n            }\n\n            R.id.menu_disabled_group -> {\n                searchView.setQuery(getString(R.string.disabled), true)\n            }\n\n            R.id.menu_group_login -> {\n                searchView.setQuery(getString(R.string.need_login), true)\n            }\n\n            R.id.menu_group_null -> {\n                searchView.setQuery(getString(R.string.no_group), true)\n            }\n\n            R.id.menu_help -> showHelp(\"SourceMRssHelp\")\n            else -> if (item.groupId == R.id.source_group) {\n                searchView.setQuery(\"group:${item.title}\", true)\n            }\n        }\n        return super.onCompatOptionsItemSelected(item)\n    }\n\n    override fun onMenuItemClick(item: MenuItem?): Boolean {\n        when (item?.itemId) {\n            R.id.menu_enable_selection -> viewModel.enableSelection(adapter.selection)\n            R.id.menu_disable_selection -> viewModel.disableSelection(adapter.selection)\n            R.id.menu_add_group -> selectionAddToGroups()\n            R.id.menu_remove_group -> selectionRemoveFromGroups()\n            R.id.menu_top_sel -> viewModel.topSource(*adapter.selection.toTypedArray())\n            R.id.menu_bottom_sel -> viewModel.bottomSource(*adapter.selection.toTypedArray())\n            R.id.menu_export_selection -> viewModel.saveToFile(adapter.selection) { file ->\n                exportResult.launch {\n                    mode = HandleFileContract.EXPORT\n                    fileData = HandleFileContract.FileData(\n                        \"exportRssSource.json\", file, \"application/json\"\n                    )\n                }\n            }\n\n            R.id.menu_share_source -> viewModel.saveToFile(adapter.selection) {\n                share(it)\n            }\n\n            R.id.menu_check_selected_interval -> adapter.checkSelectedInterval()\n        }\n        return true\n    }\n\n    private fun initRecyclerView() {\n        binding.recyclerView.setEdgeEffectColor(primaryColor)\n        binding.recyclerView.addItemDecoration(VerticalDivider(this))\n        binding.recyclerView.adapter = adapter\n        // When this page is opened, it is in selection mode\n        val dragSelectTouchHelper: DragSelectTouchHelper =\n            DragSelectTouchHelper(adapter.dragSelectCallback).setSlideArea(16, 50)\n        dragSelectTouchHelper.attachToRecyclerView(binding.recyclerView)\n        dragSelectTouchHelper.activeSlideSelect()\n        // Note: need judge selection first, so add ItemTouchHelper after it.\n        val itemTouchCallback = ItemTouchCallback(adapter)\n        itemTouchCallback.isCanDrag = true\n        ItemTouchHelper(itemTouchCallback).attachToRecyclerView(binding.recyclerView)\n    }\n\n    private fun initSearchView() {\n        binding.titleBar.findViewById<SearchView>(R.id.search_view).let {\n            it.applyTint(primaryTextColor)\n            it.onActionViewExpanded()\n            it.queryHint = getString(R.string.search_rss_source)\n            it.clearFocus()\n            it.setOnQueryTextListener(object : SearchView.OnQueryTextListener {\n                override fun onQueryTextSubmit(query: String?): Boolean {\n                    return false\n                }\n\n                override fun onQueryTextChange(newText: String?): Boolean {\n                    upSourceFlow(newText)\n                    return false\n                }\n            })\n        }\n    }\n\n    private fun initSelectActionBar() {\n        binding.selectActionBar.setMainActionText(R.string.delete)\n        binding.selectActionBar.inflateMenu(R.menu.rss_source_sel)\n        binding.selectActionBar.setOnMenuItemClickListener(this)\n        binding.selectActionBar.setCallBack(this)\n    }\n\n    private fun initGroupFlow() {\n        lifecycleScope.launch {\n            appDb.rssSourceDao.flowGroups().conflate().collect {\n                groups.clear()\n                groups.addAll(it)\n                upGroupMenu()\n            }\n        }\n    }\n\n    @SuppressLint(\"InflateParams\")\n    private fun selectionAddToGroups() {\n        alert(titleResource = R.string.add_group) {\n            val alertBinding = DialogEditTextBinding.inflate(layoutInflater).apply {\n                editView.setHint(R.string.group_name)\n                editView.setFilterValues(groups.toList())\n                editView.dropDownHeight = 180.dpToPx()\n            }\n            customView { alertBinding.root }\n            okButton {\n                alertBinding.editView.text?.toString()?.let {\n                    if (it.isNotEmpty()) {\n                        viewModel.selectionAddToGroups(adapter.selection, it)\n                    }\n                }\n            }\n            cancelButton()\n        }\n    }\n\n    @SuppressLint(\"InflateParams\")\n    private fun selectionRemoveFromGroups() {\n        alert(titleResource = R.string.remove_group) {\n            val alertBinding = DialogEditTextBinding.inflate(layoutInflater).apply {\n                editView.setHint(R.string.group_name)\n                editView.setFilterValues(groups.toList())\n                editView.dropDownHeight = 180.dpToPx()\n            }\n            customView { alertBinding.root }\n            okButton {\n                alertBinding.editView.text?.toString()?.let {\n                    if (it.isNotEmpty()) {\n                        viewModel.selectionRemoveFromGroups(adapter.selection, it)\n                    }\n                }\n            }\n            cancelButton()\n        }\n    }\n\n    override fun selectAll(selectAll: Boolean) {\n        if (selectAll) {\n            adapter.selectAll()\n        } else {\n            adapter.revertSelection()\n        }\n    }\n\n    override fun revertSelection() {\n        adapter.revertSelection()\n    }\n\n    override fun onClickSelectBarMainAction() {\n        delSourceDialog()\n    }\n\n    private fun delSourceDialog() {\n        alert(titleResource = R.string.draw, messageResource = R.string.sure_del) {\n            yesButton { viewModel.del(*adapter.selection.toTypedArray()) }\n            noButton()\n        }\n    }\n\n    private fun upGroupMenu() = groupMenu?.transaction { menu ->\n        menu.removeGroup(R.id.source_group)\n        groups.forEach {\n            menu.add(R.id.source_group, Menu.NONE, Menu.NONE, it)\n        }\n    }\n\n    private fun upSourceFlow(searchKey: String? = null) {\n        sourceFlowJob?.cancel()\n        sourceFlowJob = lifecycleScope.launch {\n            when {\n                searchKey.isNullOrBlank() -> {\n                    appDb.rssSourceDao.flowAll()\n                }\n\n                searchKey == getString(R.string.enabled) -> {\n                    appDb.rssSourceDao.flowEnabled()\n                }\n\n                searchKey == getString(R.string.disabled) -> {\n                    appDb.rssSourceDao.flowDisabled()\n                }\n\n                searchKey == getString(R.string.need_login) -> {\n                    appDb.rssSourceDao.flowLogin()\n                }\n\n                searchKey == getString(R.string.no_group) -> {\n                    appDb.rssSourceDao.flowNoGroup()\n                }\n\n                searchKey.startsWith(\"group:\") -> {\n                    val key = searchKey.substringAfter(\"group:\")\n                    appDb.rssSourceDao.flowGroupSearch(key)\n                }\n\n                else -> {\n                    appDb.rssSourceDao.flowSearch(searchKey)\n                }\n            }.catch {\n                AppLog.put(\"订阅源管理界面更新数据出错\", it)\n            }.flowOn(IO).conflate().collect {\n                adapter.setItems(it, adapter.diffItemCallback)\n                delay(100)\n            }\n        }\n    }\n\n    override fun upCountView() {\n        binding.selectActionBar.upCountView(\n            adapter.selection.size,\n            adapter.itemCount\n        )\n    }\n\n    @SuppressLint(\"InflateParams\")\n    private fun showImportDialog() {\n        val aCache = ACache.get(cacheDir = false)\n        val cacheUrls: MutableList<String> = aCache\n            .getAsString(importRecordKey)\n            ?.splitNotBlank(\",\")\n            ?.toMutableList() ?: mutableListOf()\n        alert(titleResource = R.string.import_on_line) {\n            val alertBinding = DialogEditTextBinding.inflate(layoutInflater).apply {\n                editView.hint = \"url\"\n                editView.setFilterValues(cacheUrls)\n                editView.delCallBack = {\n                    cacheUrls.remove(it)\n                    aCache.put(importRecordKey, cacheUrls.joinToString(\",\"))\n                }\n            }\n            customView { alertBinding.root }\n            okButton {\n                val text = alertBinding.editView.text?.toString()\n                text?.let {\n                    if (it.isAbsUrl() && !cacheUrls.contains(it)) {\n                        cacheUrls.add(0, it)\n                        aCache.put(importRecordKey, cacheUrls.joinToString(\",\"))\n                    }\n                    showDialogFragment(\n                        ImportRssSourceDialog(it)\n                    )\n                }\n            }\n            cancelButton()\n        }\n    }\n\n    override fun del(source: RssSource) {\n        alert(R.string.draw) {\n            setMessage(getString(R.string.sure_del) + \"\\n\" + source.sourceName)\n            noButton()\n            yesButton {\n                viewModel.del(source)\n            }\n        }\n    }\n\n    override fun edit(source: RssSource) {\n        startActivity<RssSourceEditActivity> {\n            putExtra(\"sourceUrl\", source.sourceUrl)\n        }\n    }\n\n    override fun update(vararg source: RssSource) {\n        viewModel.update(*source)\n    }\n\n    override fun toTop(source: RssSource) {\n        viewModel.topSource(source)\n    }\n\n    override fun toBottom(source: RssSource) {\n        viewModel.bottomSource(source)\n    }\n\n    override fun upOrder() {\n        viewModel.upOrder()\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/rss/source/manage/RssSourceAdapter.kt",
    "content": "package io.legado.app.ui.rss.source.manage\n\nimport android.content.Context\nimport android.os.Bundle\nimport android.view.View\nimport android.view.ViewGroup\nimport android.widget.PopupMenu\nimport androidx.core.os.bundleOf\nimport androidx.recyclerview.widget.DiffUtil\nimport androidx.recyclerview.widget.RecyclerView\nimport io.legado.app.R\nimport io.legado.app.base.adapter.ItemViewHolder\nimport io.legado.app.base.adapter.RecyclerAdapter\nimport io.legado.app.data.entities.RssSource\nimport io.legado.app.databinding.ItemRssSourceBinding\nimport io.legado.app.lib.theme.backgroundColor\nimport io.legado.app.ui.widget.recycler.DragSelectTouchHelper\nimport io.legado.app.ui.widget.recycler.ItemTouchCallback\nimport io.legado.app.utils.ColorUtils\nimport java.util.Collections\n\n\nclass RssSourceAdapter(context: Context, val callBack: CallBack) :\n    RecyclerAdapter<RssSource, ItemRssSourceBinding>(context),\n    ItemTouchCallback.Callback {\n\n    private val selected = linkedSetOf<RssSource>()\n\n    val selection: List<RssSource>\n        get() {\n            return getItems().filter {\n                selected.contains(it)\n            }\n        }\n\n    val diffItemCallback = object : DiffUtil.ItemCallback<RssSource>() {\n\n        override fun areItemsTheSame(oldItem: RssSource, newItem: RssSource): Boolean {\n            return oldItem.sourceUrl == newItem.sourceUrl\n        }\n\n        override fun areContentsTheSame(oldItem: RssSource, newItem: RssSource): Boolean {\n            return oldItem.sourceName == newItem.sourceName\n                    && oldItem.sourceGroup == newItem.sourceGroup\n                    && oldItem.enabled == newItem.enabled\n        }\n\n        override fun getChangePayload(oldItem: RssSource, newItem: RssSource): Any? {\n            val payload = Bundle()\n            if (oldItem.sourceName != newItem.sourceName\n                || oldItem.sourceGroup != newItem.sourceGroup\n            ) {\n                payload.putBoolean(\"upName\", true)\n            }\n            if (oldItem.enabled != newItem.enabled) {\n                payload.putBoolean(\"enabled\", newItem.enabled)\n            }\n            if (payload.isEmpty) {\n                return null\n            }\n            return payload\n        }\n    }\n\n    override fun getViewBinding(parent: ViewGroup): ItemRssSourceBinding {\n        return ItemRssSourceBinding.inflate(inflater, parent, false)\n    }\n\n    override fun convert(\n        holder: ItemViewHolder,\n        binding: ItemRssSourceBinding,\n        item: RssSource,\n        payloads: MutableList<Any>\n    ) {\n        binding.run {\n            if (payloads.isEmpty()) {\n                root.setBackgroundColor(ColorUtils.withAlpha(context.backgroundColor, 0.5f))\n                cbSource.text = item.getDisplayNameGroup()\n                swtEnabled.isChecked = item.enabled\n                cbSource.isChecked = selected.contains(item)\n            } else {\n                for (i in payloads.indices) {\n                    val bundle = payloads[i] as Bundle\n                    bundle.keySet().forEach {\n                        when (it) {\n                            \"upName\" -> cbSource.text = item.getDisplayNameGroup()\n                            \"enabled\" -> swtEnabled.isChecked = bundle.getBoolean(\"enabled\")\n                            \"selected\" -> cbSource.isChecked = selected.contains(item)\n                        }\n                    }\n                }\n            }\n        }\n    }\n\n    override fun registerListener(holder: ItemViewHolder, binding: ItemRssSourceBinding) {\n        binding.apply {\n            swtEnabled.setOnUserCheckedChangeListener { checked ->\n                getItem(holder.layoutPosition)?.let {\n                    it.enabled = checked\n                    callBack.update(it)\n                }\n            }\n            cbSource.setOnUserCheckedChangeListener { checked ->\n                getItem(holder.layoutPosition)?.let {\n                    if (checked) {\n                        selected.add(it)\n                    } else {\n                        selected.remove(it)\n                    }\n                    callBack.upCountView()\n                }\n            }\n            ivEdit.setOnClickListener {\n                getItem(holder.layoutPosition)?.let {\n                    callBack.edit(it)\n                }\n            }\n            ivMenuMore.setOnClickListener {\n                showMenu(ivMenuMore, holder.layoutPosition)\n            }\n        }\n    }\n\n    override fun onCurrentListChanged() {\n        callBack.upCountView()\n    }\n\n    fun selectAll() {\n        getItems().forEach {\n            selected.add(it)\n        }\n        notifyItemRangeChanged(0, itemCount, bundleOf(Pair(\"selected\", null)))\n        callBack.upCountView()\n    }\n\n    fun revertSelection() {\n        getItems().forEach {\n            if (selected.contains(it)) {\n                selected.remove(it)\n            } else {\n                selected.add(it)\n            }\n        }\n        notifyItemRangeChanged(0, itemCount, bundleOf(Pair(\"selected\", null)))\n        callBack.upCountView()\n    }\n\n    fun checkSelectedInterval() {\n        val selectedPosition = linkedSetOf<Int>()\n        getItems().forEachIndexed { index, it ->\n            if (selected.contains(it)) {\n                selectedPosition.add(index)\n            }\n        }\n        val minPosition = Collections.min(selectedPosition)\n        val maxPosition = Collections.max(selectedPosition)\n        val itemCount = maxPosition - minPosition + 1\n        for (i in minPosition..maxPosition) {\n            getItem(i)?.let {\n                selected.add(it)\n            }\n        }\n        notifyItemRangeChanged(minPosition, itemCount, bundleOf(Pair(\"selected\", null)))\n        callBack.upCountView()\n    }\n\n    private fun showMenu(view: View, position: Int) {\n        val source = getItem(position) ?: return\n        val popupMenu = PopupMenu(context, view)\n        popupMenu.inflate(R.menu.rss_source_item)\n        popupMenu.setOnMenuItemClickListener { menuItem ->\n            when (menuItem.itemId) {\n                R.id.menu_top -> callBack.toTop(source)\n                R.id.menu_bottom -> callBack.toBottom(source)\n                R.id.menu_del -> {\n                    callBack.del(source)\n                    selected.remove(source)\n                }\n            }\n            true\n        }\n        popupMenu.show()\n    }\n\n    override fun swap(srcPosition: Int, targetPosition: Int): Boolean {\n        val srcItem = getItem(srcPosition)\n        val targetItem = getItem(targetPosition)\n        if (srcItem != null && targetItem != null) {\n            if (srcItem.customOrder == targetItem.customOrder) {\n                callBack.upOrder()\n            } else {\n                val srcOrder = srcItem.customOrder\n                srcItem.customOrder = targetItem.customOrder\n                targetItem.customOrder = srcOrder\n                movedItems.add(srcItem)\n                movedItems.add(targetItem)\n            }\n        }\n        swapItem(srcPosition, targetPosition)\n        return true\n    }\n\n    private val movedItems = hashSetOf<RssSource>()\n\n    override fun onClearView(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder) {\n        if (movedItems.isNotEmpty()) {\n            callBack.update(*movedItems.toTypedArray())\n            movedItems.clear()\n        }\n    }\n\n    val dragSelectCallback: DragSelectTouchHelper.Callback =\n        object : DragSelectTouchHelper.AdvanceCallback<RssSource>(Mode.ToggleAndReverse) {\n            override fun currentSelectedId(): MutableSet<RssSource> {\n                return selected\n            }\n\n            override fun getItemId(position: Int): RssSource {\n                return getItem(position)!!\n            }\n\n            override fun updateSelectState(position: Int, isSelected: Boolean): Boolean {\n                getItem(position)?.let {\n                    if (isSelected) {\n                        selected.add(it)\n                    } else {\n                        selected.remove(it)\n                    }\n                    notifyItemChanged(position, bundleOf(Pair(\"selected\", null)))\n                    callBack.upCountView()\n                    return true\n                }\n                return false\n            }\n        }\n\n    interface CallBack {\n        fun del(source: RssSource)\n        fun edit(source: RssSource)\n        fun update(vararg source: RssSource)\n        fun toTop(source: RssSource)\n        fun toBottom(source: RssSource)\n        fun upOrder()\n        fun upCountView()\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/rss/source/manage/RssSourceViewModel.kt",
    "content": "package io.legado.app.ui.rss.source.manage\n\nimport android.app.Application\nimport android.text.TextUtils\nimport io.legado.app.base.BaseViewModel\nimport io.legado.app.data.appDb\nimport io.legado.app.data.entities.RssSource\nimport io.legado.app.help.DefaultData\nimport io.legado.app.help.source.SourceHelp\nimport io.legado.app.utils.FileUtils\nimport io.legado.app.utils.GSON\nimport io.legado.app.utils.splitNotBlank\nimport io.legado.app.utils.stackTraceStr\nimport io.legado.app.utils.toastOnUi\nimport java.io.File\n\n/**\n * 订阅源管理数据修改\n * 修改数据要copy,直接修改会导致界面不刷新\n */\nclass RssSourceViewModel(application: Application) : BaseViewModel(application) {\n\n    fun topSource(vararg sources: RssSource) {\n        execute {\n            sources.sortBy { it.customOrder }\n            val minOrder = appDb.rssSourceDao.minOrder - 1\n            val array = Array(sources.size) {\n                sources[it].copy(customOrder = minOrder - it)\n            }\n            appDb.rssSourceDao.update(*array)\n        }\n    }\n\n    fun bottomSource(vararg sources: RssSource) {\n        execute {\n            sources.sortBy { it.customOrder }\n            val maxOrder = appDb.rssSourceDao.maxOrder + 1\n            val array = Array(sources.size) {\n                sources[it].copy(customOrder = maxOrder + it)\n            }\n            appDb.rssSourceDao.update(*array)\n        }\n    }\n\n    fun del(vararg rssSource: RssSource) {\n        execute {\n            SourceHelp.deleteRssSources(rssSource.toList())\n        }\n    }\n\n    fun update(vararg rssSource: RssSource) {\n        execute { appDb.rssSourceDao.update(*rssSource) }\n    }\n\n    fun upOrder() {\n        execute {\n            val sources = appDb.rssSourceDao.all\n            for ((index: Int, source: RssSource) in sources.withIndex()) {\n                source.customOrder = index + 1\n            }\n            appDb.rssSourceDao.update(*sources.toTypedArray())\n        }\n    }\n\n    fun enableSelection(sources: List<RssSource>) {\n        execute {\n            val array = Array(sources.size) {\n                sources[it].copy(enabled = true)\n            }\n            appDb.rssSourceDao.update(*array)\n        }\n    }\n\n    fun disableSelection(sources: List<RssSource>) {\n        execute {\n            val array = Array(sources.size) {\n                sources[it].copy(enabled = false)\n            }\n            appDb.rssSourceDao.update(*array)\n        }\n    }\n\n    fun saveToFile(sources: List<RssSource>, success: (file: File) -> Unit) {\n        execute {\n            val path = \"${context.filesDir}/shareRssSource.json\"\n            FileUtils.delete(path)\n            val file = FileUtils.createFileWithReplace(path)\n            file.writeText(GSON.toJson(sources))\n            file\n        }.onSuccess {\n            success.invoke(it)\n        }.onError {\n            context.toastOnUi(it.stackTraceStr)\n        }\n    }\n\n    fun selectionAddToGroups(sources: List<RssSource>, groups: String) {\n        execute {\n            val array = Array(sources.size) {\n                sources[it].copy().addGroup(groups)\n            }\n            appDb.rssSourceDao.update(*array)\n        }\n    }\n\n    fun selectionRemoveFromGroups(sources: List<RssSource>, groups: String) {\n        execute {\n            val array = Array(sources.size) {\n                sources[it].copy().removeGroup(groups)\n            }\n            appDb.rssSourceDao.update(*array)\n        }\n    }\n\n    fun addGroup(group: String) {\n        execute {\n            val sources = appDb.rssSourceDao.noGroup\n            sources.forEach { source ->\n                source.sourceGroup = group\n            }\n            appDb.rssSourceDao.update(*sources.toTypedArray())\n        }\n    }\n\n    fun upGroup(oldGroup: String, newGroup: String?) {\n        execute {\n            val sources = appDb.rssSourceDao.getByGroup(oldGroup)\n            sources.forEach { source ->\n                source.sourceGroup?.splitNotBlank(\",\")?.toHashSet()?.let {\n                    it.remove(oldGroup)\n                    if (!newGroup.isNullOrEmpty())\n                        it.add(newGroup)\n                    source.sourceGroup = TextUtils.join(\",\", it)\n                }\n            }\n            appDb.rssSourceDao.update(*sources.toTypedArray())\n        }\n    }\n\n    fun delGroup(group: String) {\n        execute {\n            execute {\n                val sources = appDb.rssSourceDao.getByGroup(group)\n                sources.forEach { source ->\n                    source.sourceGroup?.splitNotBlank(\",\")?.toHashSet()?.let {\n                        it.remove(group)\n                        source.sourceGroup = TextUtils.join(\",\", it)\n                    }\n                }\n                appDb.rssSourceDao.update(*sources.toTypedArray())\n            }\n        }\n    }\n\n    fun importDefault() {\n        execute {\n            DefaultData.importDefaultRssSources()\n        }\n    }\n\n    fun disable(rssSource: RssSource) {\n        execute {\n            rssSource.enabled = false\n            appDb.rssSourceDao.update(rssSource)\n        }\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/rss/subscription/RuleSubActivity.kt",
    "content": "package io.legado.app.ui.rss.subscription\n\nimport android.os.Bundle\nimport android.view.Menu\nimport android.view.MenuItem\nimport androidx.core.view.isGone\nimport androidx.lifecycle.lifecycleScope\nimport androidx.recyclerview.widget.ItemTouchHelper\nimport io.legado.app.R\nimport io.legado.app.base.BaseActivity\nimport io.legado.app.constant.AppLog\nimport io.legado.app.data.appDb\nimport io.legado.app.data.entities.RuleSub\nimport io.legado.app.databinding.ActivityRuleSubBinding\nimport io.legado.app.databinding.DialogRuleSubEditBinding\nimport io.legado.app.lib.dialogs.alert\nimport io.legado.app.ui.association.ImportBookSourceDialog\nimport io.legado.app.ui.association.ImportReplaceRuleDialog\nimport io.legado.app.ui.association.ImportRssSourceDialog\nimport io.legado.app.ui.widget.recycler.ItemTouchCallback\nimport io.legado.app.utils.applyNavigationBarPadding\nimport io.legado.app.utils.showDialogFragment\nimport io.legado.app.utils.toastOnUi\nimport io.legado.app.utils.viewbindingdelegate.viewBinding\nimport kotlinx.coroutines.Dispatchers.IO\nimport kotlinx.coroutines.flow.catch\nimport kotlinx.coroutines.flow.conflate\nimport kotlinx.coroutines.flow.flowOn\nimport kotlinx.coroutines.launch\nimport kotlinx.coroutines.withContext\n\n/**\n * 规则订阅界面\n */\nclass RuleSubActivity : BaseActivity<ActivityRuleSubBinding>(),\n    RuleSubAdapter.Callback {\n\n    override val binding by viewBinding(ActivityRuleSubBinding::inflate)\n    private val adapter by lazy { RuleSubAdapter(this, this) }\n\n    override fun onActivityCreated(savedInstanceState: Bundle?) {\n        initView()\n        initData()\n    }\n\n    override fun onCompatCreateOptionsMenu(menu: Menu): Boolean {\n        menuInflater.inflate(R.menu.source_subscription, menu)\n        return super.onCompatCreateOptionsMenu(menu)\n    }\n\n    override fun onCompatOptionsItemSelected(item: MenuItem): Boolean {\n        when (item.itemId) {\n            R.id.menu_add -> {\n                val order = appDb.ruleSubDao.maxOrder + 1\n                editSubscription(RuleSub(customOrder = order))\n            }\n        }\n        return super.onCompatOptionsItemSelected(item)\n    }\n\n    private fun initView() {\n        binding.recyclerView.adapter = adapter\n        binding.recyclerView.applyNavigationBarPadding()\n        val itemTouchCallback = ItemTouchCallback(adapter)\n        itemTouchCallback.isCanDrag = true\n        ItemTouchHelper(itemTouchCallback).attachToRecyclerView(binding.recyclerView)\n    }\n\n    private fun initData() {\n        lifecycleScope.launch {\n            appDb.ruleSubDao.flowAll().catch {\n                AppLog.put(\"规则订阅界面获取数据失败\\n${it.localizedMessage}\", it)\n            }.flowOn(IO).conflate().collect {\n                binding.tvEmptyMsg.isGone = it.isNotEmpty()\n                adapter.setItems(it)\n            }\n        }\n    }\n\n    override fun openSubscription(ruleSub: RuleSub) {\n        when (ruleSub.type) {\n            0 -> showDialogFragment(\n                ImportBookSourceDialog(ruleSub.url)\n            )\n            1 -> showDialogFragment(\n                ImportRssSourceDialog(ruleSub.url)\n            )\n            2 -> showDialogFragment(\n                ImportReplaceRuleDialog(ruleSub.url)\n            )\n        }\n    }\n\n    override fun editSubscription(ruleSub: RuleSub) {\n        alert(R.string.rule_subscription) {\n            val alertBinding = DialogRuleSubEditBinding.inflate(layoutInflater).apply {\n                if (ruleSub.type !in 0..<spType.count) {\n                    ruleSub.type = 0\n                }\n                spType.setSelection(ruleSub.type)\n                etName.setText(ruleSub.name)\n                etUrl.setText(ruleSub.url)\n            }\n            customView { alertBinding.root }\n            okButton {\n                lifecycleScope.launch {\n                    ruleSub.type = alertBinding.spType.selectedItemPosition\n                    ruleSub.name = alertBinding.etName.text?.toString() ?: \"\"\n                    ruleSub.url = alertBinding.etUrl.text?.toString() ?: \"\"\n                    val rs = withContext(IO) {\n                        appDb.ruleSubDao.findByUrl(ruleSub.url)\n                    }\n                    if (rs != null && rs.id != ruleSub.id) {\n                        toastOnUi(\"${getString(R.string.url_already)}(${rs.name})\")\n                        return@launch\n                    }\n                    withContext(IO) {\n                        appDb.ruleSubDao.insert(ruleSub)\n                    }\n                }\n            }\n            cancelButton()\n        }\n    }\n\n    override fun delSubscription(ruleSub: RuleSub) {\n        lifecycleScope.launch(IO) {\n            appDb.ruleSubDao.delete(ruleSub)\n        }\n    }\n\n    override fun updateSourceSub(vararg ruleSub: RuleSub) {\n        lifecycleScope.launch(IO) {\n            appDb.ruleSubDao.update(*ruleSub)\n        }\n    }\n\n    override fun upOrder() {\n        lifecycleScope.launch(IO) {\n            val sourceSubs = appDb.ruleSubDao.all\n            for ((index: Int, ruleSub: RuleSub) in sourceSubs.withIndex()) {\n                ruleSub.customOrder = index + 1\n            }\n            appDb.ruleSubDao.update(*sourceSubs.toTypedArray())\n        }\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/rss/subscription/RuleSubAdapter.kt",
    "content": "package io.legado.app.ui.rss.subscription\n\nimport android.content.Context\nimport android.view.View\nimport android.view.ViewGroup\nimport android.widget.PopupMenu\nimport androidx.recyclerview.widget.RecyclerView\nimport io.legado.app.R\nimport io.legado.app.base.adapter.ItemViewHolder\nimport io.legado.app.base.adapter.RecyclerAdapter\nimport io.legado.app.data.entities.RuleSub\nimport io.legado.app.databinding.ItemRuleSubBinding\nimport io.legado.app.ui.widget.recycler.ItemTouchCallback\n\n\nclass RuleSubAdapter(context: Context, val callBack: Callback) :\n    RecyclerAdapter<RuleSub, ItemRuleSubBinding>(context),\n    ItemTouchCallback.Callback {\n\n    private val typeArray = context.resources.getStringArray(R.array.rule_type)\n\n    override fun convert(\n        holder: ItemViewHolder,\n        binding: ItemRuleSubBinding,\n        item: RuleSub,\n        payloads: MutableList<Any>\n    ) {\n        binding.tvType.text = typeArray[item.type]\n        binding.tvName.text = item.name\n        binding.tvUrl.text = item.url\n    }\n\n    override fun registerListener(holder: ItemViewHolder, binding: ItemRuleSubBinding) {\n        binding.root.setOnClickListener {\n            callBack.openSubscription(getItem(holder.layoutPosition)!!)\n        }\n        binding.ivEdit.setOnClickListener {\n            callBack.editSubscription(getItem(holder.layoutPosition)!!)\n        }\n        binding.ivMenuMore.setOnClickListener {\n            showMenu(binding.ivMenuMore, holder.layoutPosition)\n        }\n    }\n\n    private fun showMenu(view: View, position: Int) {\n        val source = getItem(position) ?: return\n        val popupMenu = PopupMenu(context, view)\n        popupMenu.inflate(R.menu.source_sub_item)\n        popupMenu.setOnMenuItemClickListener { menuItem ->\n            when (menuItem.itemId) {\n                R.id.menu_del -> callBack.delSubscription(source)\n            }\n            true\n        }\n        popupMenu.show()\n    }\n\n    override fun getViewBinding(parent: ViewGroup): ItemRuleSubBinding {\n        return ItemRuleSubBinding.inflate(inflater, parent, false)\n    }\n\n    override fun swap(srcPosition: Int, targetPosition: Int): Boolean {\n        val srcItem = getItem(srcPosition)\n        val targetItem = getItem(targetPosition)\n        if (srcItem != null && targetItem != null) {\n            if (srcItem.customOrder == targetItem.customOrder) {\n                callBack.upOrder()\n            } else {\n                val srcOrder = srcItem.customOrder\n                srcItem.customOrder = targetItem.customOrder\n                targetItem.customOrder = srcOrder\n                movedItems.add(srcItem)\n                movedItems.add(targetItem)\n            }\n        }\n        swapItem(srcPosition, targetPosition)\n        return true\n    }\n\n    private val movedItems = hashSetOf<RuleSub>()\n\n    override fun onClearView(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder) {\n        if (movedItems.isNotEmpty()) {\n            callBack.updateSourceSub(*movedItems.toTypedArray())\n            movedItems.clear()\n        }\n    }\n\n    interface Callback {\n        fun openSubscription(ruleSub: RuleSub)\n        fun editSubscription(ruleSub: RuleSub)\n        fun delSubscription(ruleSub: RuleSub)\n        fun updateSourceSub(vararg ruleSub: RuleSub)\n        fun upOrder()\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/welcome/WelcomeActivity.kt",
    "content": "package io.legado.app.ui.welcome\n\nimport android.content.Intent\nimport android.graphics.drawable.BitmapDrawable\nimport android.os.Bundle\nimport androidx.core.view.postDelayed\nimport io.legado.app.base.BaseActivity\nimport io.legado.app.constant.PreferKey\nimport io.legado.app.constant.Theme\nimport io.legado.app.data.appDb\nimport io.legado.app.databinding.ActivityWelcomeBinding\nimport io.legado.app.help.config.AppConfig\nimport io.legado.app.help.config.ThemeConfig\nimport io.legado.app.lib.theme.accentColor\nimport io.legado.app.lib.theme.backgroundColor\nimport io.legado.app.ui.book.read.ReadBookActivity\nimport io.legado.app.ui.main.MainActivity\nimport io.legado.app.utils.BitmapUtils\nimport io.legado.app.utils.fullScreen\nimport io.legado.app.utils.getPrefBoolean\nimport io.legado.app.utils.getPrefString\nimport io.legado.app.utils.setStatusBarColorAuto\nimport io.legado.app.utils.startActivity\nimport io.legado.app.utils.viewbindingdelegate.viewBinding\nimport io.legado.app.utils.visible\nimport io.legado.app.utils.windowSize\n\nopen class WelcomeActivity : BaseActivity<ActivityWelcomeBinding>() {\n\n    override val binding by viewBinding(ActivityWelcomeBinding::inflate)\n\n    override fun onActivityCreated(savedInstanceState: Bundle?) {\n        binding.ivBook.setColorFilter(accentColor)\n        binding.vwTitleLine.setBackgroundColor(accentColor)\n        // 避免从桌面启动程序后，会重新实例化入口类的activity\n        if (intent.flags and Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT != 0) {\n            finish()\n        } else {\n            binding.root.postDelayed(600) { startMainActivity() }\n        }\n    }\n\n    override fun setupSystemBar() {\n        fullScreen()\n        setStatusBarColorAuto(backgroundColor, true, fullScreen)\n        upNavigationBarColor()\n    }\n\n    override fun upBackgroundImage() {\n        if (getPrefBoolean(PreferKey.customWelcome)) {\n            kotlin.runCatching {\n                when (ThemeConfig.getTheme()) {\n                    Theme.Dark -> getPrefString(PreferKey.welcomeImageDark)?.let { path ->\n                        val size = windowManager.windowSize\n                        BitmapUtils.decodeBitmap(path, size.widthPixels, size.heightPixels).let {\n                            binding.tvLegado.visible(AppConfig.welcomeShowTextDark)\n                            binding.ivBook.visible(AppConfig.welcomeShowIconDark)\n                            binding.tvGzh.visible(AppConfig.welcomeShowTextDark)\n                            window.decorView.background = BitmapDrawable(resources, it)\n                            return\n                        }\n                    }\n\n                    else -> getPrefString(PreferKey.welcomeImage)?.let { path ->\n                        val size = windowManager.windowSize\n                        BitmapUtils.decodeBitmap(path, size.widthPixels, size.heightPixels).let {\n                            binding.tvLegado.visible(AppConfig.welcomeShowText)\n                            binding.ivBook.visible(AppConfig.welcomeShowIcon)\n                            binding.tvGzh.visible(AppConfig.welcomeShowText)\n                            window.decorView.background = BitmapDrawable(resources, it)\n                            return\n                        }\n                    }\n                }\n            }\n        }\n        super.upBackgroundImage()\n    }\n\n    private fun startMainActivity() {\n        startActivity<MainActivity>()\n        if (getPrefBoolean(PreferKey.defaultToRead) && appDb.bookDao.lastReadBook != null) {\n            startActivity<ReadBookActivity>()\n        }\n        finish()\n    }\n\n}\n\nclass Launcher1 : WelcomeActivity()\nclass Launcher2 : WelcomeActivity()\nclass Launcher3 : WelcomeActivity()\nclass Launcher4 : WelcomeActivity()\nclass Launcher5 : WelcomeActivity()\nclass Launcher6 : WelcomeActivity()"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/widget/BatteryView.kt",
    "content": "package io.legado.app.ui.widget\n\nimport android.annotation.SuppressLint\nimport android.content.Context\nimport android.graphics.Canvas\nimport android.graphics.Paint\nimport android.graphics.Rect\nimport android.graphics.Typeface\nimport android.os.Build\nimport android.text.StaticLayout\nimport android.util.AttributeSet\nimport androidx.annotation.ColorInt\nimport androidx.appcompat.widget.AppCompatTextView\nimport io.legado.app.help.config.AppConfig\nimport io.legado.app.utils.canvasrecorder.CanvasRecorderFactory\nimport io.legado.app.utils.canvasrecorder.recordIfNeededThenDraw\nimport io.legado.app.utils.dpToPx\n\nclass BatteryView @JvmOverloads constructor(\n    context: Context,\n    attrs: AttributeSet? = null\n) : AppCompatTextView(context, attrs) {\n    private val batteryTypeface by lazy {\n        Typeface.createFromAsset(context.assets, \"font/number.ttf\")\n    }\n    private val batteryPaint = Paint()\n    private val outFrame = Rect()\n    private val polar = Rect()\n    private val canvasRecorder = CanvasRecorderFactory.create()\n    var isBattery = false\n        set(value) {\n            field = value\n            if (value && !isInEditMode) {\n                super.setTypeface(batteryTypeface)\n                postInvalidate()\n            }\n        }\n    private var battery: Int = 0\n\n    init {\n        setPadding(4.dpToPx(), 3.dpToPx(), 6.dpToPx(), 3.dpToPx())\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {\n            isFallbackLineSpacing = false\n        }\n        batteryPaint.strokeWidth = 1f.dpToPx()\n        batteryPaint.isAntiAlias = true\n        batteryPaint.color = paint.color\n    }\n\n    override fun setTypeface(tf: Typeface?) {\n        if (!isBattery) {\n            super.setTypeface(tf)\n        }\n    }\n\n    fun setColor(@ColorInt color: Int) {\n        setTextColor(color)\n        batteryPaint.color = color\n        invalidate()\n    }\n\n    @SuppressLint(\"SetTextI18n\")\n    fun setBattery(battery: Int, text: String? = null) {\n        this.battery = battery\n        if (text.isNullOrEmpty()) {\n            setText(battery.toString())\n        } else {\n            setText(\"$text  $battery\")\n        }\n    }\n\n    override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {\n        super.onLayout(changed, left, top, right, bottom)\n        canvasRecorder.invalidate()\n    }\n\n    override fun onDraw(canvas: Canvas) {\n        if (AppConfig.optimizeRender) {\n            canvasRecorder.recordIfNeededThenDraw(canvas, width, height) {\n                super.onDraw(this)\n                drawBattery(this)\n            }\n        } else {\n            super.onDraw(canvas)\n            drawBattery(canvas)\n        }\n    }\n\n    private fun drawBattery(canvas: Canvas) {\n        if (!isBattery) return\n        layout.getLineBounds(0, outFrame)\n        val batteryStart = layout\n            .getPrimaryHorizontal(text.length - battery.toString().length)\n            .toInt() + 2.dpToPx()\n        val batteryEnd = batteryStart +\n                StaticLayout.getDesiredWidth(battery.toString(), paint).toInt() + 4.dpToPx()\n        outFrame.set(\n            batteryStart,\n            2.dpToPx(),\n            batteryEnd,\n            height - 2.dpToPx()\n        )\n        val dj = (outFrame.bottom - outFrame.top) / 3\n        polar.set(\n            batteryEnd,\n            outFrame.top + dj,\n            batteryEnd + 2.dpToPx(),\n            outFrame.bottom - dj\n        )\n        batteryPaint.style = Paint.Style.STROKE\n        canvas.drawRect(outFrame, batteryPaint)\n        batteryPaint.style = Paint.Style.FILL\n        canvas.drawRect(polar, batteryPaint)\n    }\n\n    @Suppress(\"UNNECESSARY_SAFE_CALL\")\n    override fun invalidate() {\n        super.invalidate()\n        canvasRecorder?.invalidate()\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/widget/DetailSeekBar.kt",
    "content": "package io.legado.app.ui.widget\n\nimport android.content.Context\nimport android.graphics.PorterDuff\nimport android.util.AttributeSet\nimport android.view.LayoutInflater\nimport android.widget.FrameLayout\nimport android.widget.SeekBar\nimport androidx.appcompat.widget.TooltipCompat\nimport io.legado.app.R\nimport io.legado.app.databinding.ViewDetailSeekBarBinding\nimport io.legado.app.lib.theme.bottomBackground\nimport io.legado.app.lib.theme.getPrimaryTextColor\nimport io.legado.app.ui.widget.seekbar.SeekBarChangeListener\nimport io.legado.app.utils.ColorUtils\nimport io.legado.app.utils.progressAdd\n\n\nclass DetailSeekBar @JvmOverloads constructor(\n    context: Context,\n    attrs: AttributeSet? = null\n) : FrameLayout(context, attrs),\n    SeekBarChangeListener {\n    private var binding: ViewDetailSeekBarBinding =\n        ViewDetailSeekBarBinding.inflate(LayoutInflater.from(context), this, true)\n    private val isBottomBackground: Boolean\n\n    var valueFormat: ((progress: Int) -> String)? = null\n    var onChanged: ((progress: Int) -> Unit)? = null\n    var progress: Int\n        get() = binding.seekBar.progress\n        set(value) {\n            binding.seekBar.progress = value\n            upValue()\n        }\n    var max: Int\n        get() = binding.seekBar.max\n        set(value) {\n            binding.seekBar.max = value\n        }\n\n    init {\n        val typedArray = context.obtainStyledAttributes(attrs, R.styleable.DetailSeekBar)\n        isBottomBackground =\n            typedArray.getBoolean(R.styleable.DetailSeekBar_isBottomBackground, false)\n        val title = typedArray.getText(R.styleable.DetailSeekBar_title)\n        binding.tvSeekTitle.apply {\n            text = title\n            TooltipCompat.setTooltipText(this, title)\n        }\n        binding.seekBar.max = typedArray.getInteger(R.styleable.DetailSeekBar_max, 0)\n        typedArray.recycle()\n        if (isBottomBackground && !isInEditMode) {\n            val isLight = ColorUtils.isColorLight(context.bottomBackground)\n            val textColor = context.getPrimaryTextColor(isLight)\n            binding.tvSeekTitle.setTextColor(textColor)\n            binding.ivSeekPlus.setColorFilter(textColor, PorterDuff.Mode.SRC_IN)\n            binding.ivSeekReduce.setColorFilter(textColor, PorterDuff.Mode.SRC_IN)\n            binding.tvSeekValue.setTextColor(textColor)\n        }\n        binding.ivSeekPlus.setOnClickListener {\n            binding.seekBar.progressAdd(1)\n            onChanged?.invoke(binding.seekBar.progress)\n        }\n        binding.ivSeekReduce.setOnClickListener {\n            binding.seekBar.progressAdd(-1)\n            onChanged?.invoke(binding.seekBar.progress)\n        }\n        binding.seekBar.setOnSeekBarChangeListener(this)\n    }\n\n    private fun upValue(progress: Int = binding.seekBar.progress) {\n        valueFormat?.let {\n            binding.tvSeekValue.text = it.invoke(progress)\n        } ?: let {\n            binding.tvSeekValue.text = progress.toString()\n        }\n    }\n\n    override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {\n        upValue(progress)\n    }\n\n    override fun onStartTrackingTouch(seekBar: SeekBar) {\n\n    }\n\n    override fun onStopTrackingTouch(seekBar: SeekBar) {\n        onChanged?.invoke(binding.seekBar.progress)\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/widget/LabelsBar.kt",
    "content": "package io.legado.app.ui.widget\n\nimport android.content.Context\nimport android.util.AttributeSet\nimport android.widget.LinearLayout\nimport android.widget.TextView\nimport io.legado.app.ui.widget.text.AccentBgTextView\nimport io.legado.app.utils.dpToPx\n\n@Suppress(\"unused\", \"MemberVisibilityCanBePrivate\")\nclass LabelsBar @JvmOverloads constructor(\n    context: Context,\n    attrs: AttributeSet? = null\n) : LinearLayout(context, attrs) {\n\n    private val unUsedViews = arrayListOf<TextView>()\n    private val usedViews = arrayListOf<TextView>()\n    var textSize = 12f\n\n    fun setLabels(labels: Array<String>) {\n        clear()\n        labels.forEach {\n            addLabel(it)\n        }\n    }\n\n    fun setLabels(labels: List<String>) {\n        clear()\n        labels.forEach {\n            addLabel(it)\n        }\n    }\n\n    fun clear() {\n        unUsedViews.addAll(usedViews)\n        usedViews.clear()\n        removeAllViews()\n    }\n\n    fun addLabel(label: String) {\n        val tv = if (unUsedViews.isEmpty()) {\n            AccentBgTextView(context, null).apply {\n                setPadding(3.dpToPx(), 0, 3.dpToPx(), 0)\n                setRadius(2)\n                val lp = LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT)\n                lp.setMargins(0, 0, 2.dpToPx(), 0)\n                layoutParams = lp\n                text = label\n                maxLines = 1\n                usedViews.add(this)\n            }\n        } else {\n            unUsedViews.last().apply {\n                usedViews.add(this)\n                unUsedViews.removeAt(unUsedViews.lastIndex)\n            }\n        }\n        tv.textSize = textSize\n        tv.text = label\n        addView(tv)\n    }\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/widget/NoChildScrollNestedScrollView.kt",
    "content": "package io.legado.app.ui.widget\n\nimport android.content.Context\nimport android.graphics.Rect\nimport android.util.AttributeSet\nimport android.view.View\nimport androidx.core.widget.NestedScrollView\n\nclass NoChildScrollNestedScrollView @JvmOverloads constructor(\n    context: Context,\n    attrs: AttributeSet? = null,\n) : NestedScrollView(context, attrs) {\n\n    private var isRequestChildFocus = false\n\n    override fun requestChildFocus(child: View?, focused: View?) {\n        isRequestChildFocus = true\n        super.requestChildFocus(child, focused)\n        isRequestChildFocus = false\n    }\n\n    override fun computeScrollDeltaToGetChildRectOnScreen(rect: Rect?): Int {\n        if (isRequestChildFocus) {\n            return 0\n        }\n        return super.computeScrollDeltaToGetChildRectOnScreen(rect)\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/widget/PopupAction.kt",
    "content": "package io.legado.app.ui.widget\n\nimport android.content.Context\nimport android.view.ViewGroup\nimport android.widget.PopupWindow\nimport io.legado.app.base.adapter.ItemViewHolder\nimport io.legado.app.base.adapter.RecyclerAdapter\nimport io.legado.app.databinding.ItemTextBinding\nimport io.legado.app.databinding.PopupActionBinding\nimport io.legado.app.lib.dialogs.SelectItem\nimport splitties.systemservices.layoutInflater\n\nclass PopupAction(private val context: Context) :\n    PopupWindow(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT) {\n\n    val binding = PopupActionBinding.inflate(context.layoutInflater)\n    val adapter by lazy {\n        Adapter(context).apply {\n            setHasStableIds(true)\n        }\n    }\n    var onActionClick: ((action: String) -> Unit)? = null\n\n    init {\n        contentView = binding.root\n\n        isTouchable = true\n        isOutsideTouchable = false\n        isFocusable = true\n\n        binding.recyclerView.adapter = adapter\n    }\n\n    fun setItems(items: List<SelectItem<String>>) {\n        adapter.setItems(items)\n    }\n\n    inner class Adapter(context: Context) :\n        RecyclerAdapter<SelectItem<String>, ItemTextBinding>(context) {\n\n        override fun getItemId(position: Int): Long {\n            return position.toLong()\n        }\n\n        override fun getViewBinding(parent: ViewGroup): ItemTextBinding {\n            return ItemTextBinding.inflate(inflater, parent, false)\n        }\n\n        override fun convert(\n            holder: ItemViewHolder,\n            binding: ItemTextBinding,\n            item: SelectItem<String>,\n            payloads: MutableList<Any>\n        ) {\n            with(binding) {\n                textView.text = item.title\n            }\n        }\n\n        override fun registerListener(holder: ItemViewHolder, binding: ItemTextBinding) {\n            holder.itemView.setOnClickListener {\n                getItem(holder.layoutPosition)?.let { item ->\n                    onActionClick?.invoke(item.value)\n                }\n            }\n        }\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/widget/ReaderInfoBarView.kt",
    "content": "package io.legado.app.ui.widget\n\nimport android.content.BroadcastReceiver\nimport android.content.Context\nimport android.content.Intent\nimport android.content.IntentFilter\nimport android.graphics.Canvas\nimport android.graphics.Color\nimport android.graphics.Paint\nimport android.graphics.Rect\nimport android.util.AttributeSet\nimport android.view.View\nimport androidx.annotation.AttrRes\nimport androidx.core.content.ContextCompat\nimport androidx.core.content.res.use\nimport androidx.core.graphics.ColorUtils\nimport androidx.core.view.WindowInsetsCompat\nimport io.legado.app.utils.dpToPx\nimport io.legado.app.utils.setOnApplyWindowInsetsListenerCompat\nimport java.text.SimpleDateFormat\nimport java.util.Date\nimport kotlin.math.min\nimport com.google.android.material.R as materialR\n\nclass ReaderInfoBarView @JvmOverloads constructor(\n    context: Context,\n    attrs: AttributeSet? = null,\n    @AttrRes defStyleAttr: Int = 0,\n) : View(context, attrs, defStyleAttr) {\n\n    companion object {\n        const val ALIGN_LEFT = 0\n        const val ALIGN_CENTER = 1\n    }\n\n    private val paint = Paint(Paint.ANTI_ALIAS_FLAG)\n    private val textBounds = Rect()\n    private val timeFormat = SimpleDateFormat.getTimeInstance(SimpleDateFormat.SHORT)\n    private val timeReceiver = TimeReceiver()\n    private var insetLeft: Int = 0\n    private var insetRight: Int = 0\n    private var insetTop: Int = 0\n    private var cutoutInsetLeft = 0\n    private var cutoutInsetRight = 0\n    private val colorText = ColorUtils.setAlphaComponent(\n        context.obtainStyledAttributes(intArrayOf(materialR.attr.colorOnSurface)).use {\n            it.getColor(0, Color.BLACK)\n        },\n        200,\n    )\n    private val colorOutline = ColorUtils.setAlphaComponent(\n        context.obtainStyledAttributes(intArrayOf(materialR.attr.colorSurface)).use {\n            it.getColor(0, Color.WHITE)\n        },\n        200,\n    )\n\n    var textInfoAlignment: Int = ALIGN_CENTER\n        set(value) {\n            field = value\n            updateTextSize()\n            invalidate()\n        }\n    private var timeText = timeFormat.format(Date())\n    private var text: String = \"\"\n    private val innerHeight\n        get() = height - paddingTop - paddingBottom - insetTop\n    private val innerWidth\n        get() = width - paddingLeft - paddingRight - insetLeft - insetRight\n\n    init {\n        val insetStart = 10.dpToPx()\n        val insetEnd = 10.dpToPx()\n        paint.strokeWidth = 2f.dpToPx()\n        paint.setShadowLayer(2f, 1f, 1f, Color.GRAY)\n        insetLeft = insetStart\n        insetRight = insetEnd\n        insetTop = minOf(insetLeft, insetRight)\n        setOnApplyWindowInsetsListenerCompat { _, windowInsets ->\n            val insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())\n            if (insets.left >= paddingLeft) {\n                cutoutInsetLeft = insets.left\n            }\n            if (insets.right >= paddingRight) {\n                cutoutInsetRight = insets.right\n            }\n            windowInsets\n        }\n    }\n\n    override fun onDraw(canvas: Canvas) {\n        super.onDraw(canvas)\n        val ty = innerHeight / 2f + textBounds.height() / 2f - textBounds.bottom\n\n        val textX = when (textInfoAlignment) {\n            ALIGN_CENTER -> {\n                val textWidth = paint.measureText(text)\n                (width / 2f).coerceIn(\n                    paddingLeft + insetLeft + cutoutInsetLeft + textWidth / 2,\n                    width - paddingRight - insetRight - cutoutInsetRight - textWidth / 2\n                )\n            }\n\n            else -> (paddingLeft + insetLeft + cutoutInsetLeft).toFloat()\n        }\n        paint.textAlign = when (textInfoAlignment) {\n            ALIGN_CENTER -> Paint.Align.CENTER\n            else -> Paint.Align.LEFT\n        }\n\n        canvas.drawTextOutline(\n            text,\n            textX,\n            paddingTop + insetTop + ty,\n        )\n\n        paint.textAlign = Paint.Align.RIGHT\n        canvas.drawTextOutline(\n            timeText,\n            (width - paddingRight - insetRight - cutoutInsetRight).toFloat(),\n            paddingTop + insetTop + ty,\n        )\n    }\n\n    override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {\n        super.onSizeChanged(w, h, oldw, oldh)\n        updateTextSize()\n    }\n\n    override fun onAttachedToWindow() {\n        super.onAttachedToWindow()\n        ContextCompat.registerReceiver(\n            context,\n            timeReceiver,\n            IntentFilter(Intent.ACTION_TIME_TICK),\n            ContextCompat.RECEIVER_EXPORTED,\n        )\n    }\n\n    override fun onDetachedFromWindow() {\n        super.onDetachedFromWindow()\n        context.unregisterReceiver(timeReceiver)\n    }\n\n    fun update(label: String) {\n        text = label\n        updateTextSize()\n        invalidate()\n    }\n\n    private fun updateTextSize() {\n        val testTextSize = 48f\n        paint.textSize = testTextSize\n        paint.getTextBounds(text, 0, text.length, textBounds)\n\n        val maxTextHeight = innerHeight * 0.8f\n        val scaleFactor = min(\n            maxTextHeight / textBounds.height(),\n            calculateMaxWidthScale()\n        )\n        paint.textSize = testTextSize * scaleFactor\n\n        paint.getTextBounds(text, 0, text.length, textBounds)\n    }\n\n    private fun calculateMaxWidthScale(): Float {\n        return when (textInfoAlignment) {\n            ALIGN_CENTER -> {\n                val availableWidth = innerWidth - cutoutInsetLeft - cutoutInsetRight\n                val requiredWidth = paint.measureText(text)\n                if (requiredWidth > availableWidth) availableWidth / requiredWidth else 1f\n            }\n\n            else -> 1f\n        }\n    }\n\n    private fun Canvas.drawTextOutline(text: String, x: Float, y: Float) {\n        paint.color = colorOutline\n        paint.style = Paint.Style.STROKE\n        drawText(text, x, y, paint)\n        paint.color = colorText\n        paint.style = Paint.Style.FILL\n        drawText(text, x, y, paint)\n    }\n\n    private inner class TimeReceiver : BroadcastReceiver() {\n\n        override fun onReceive(context: Context?, intent: Intent?) {\n            timeText = timeFormat.format(Date())\n            invalidate()\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/widget/SearchView.kt",
    "content": "package io.legado.app.ui.widget\n\nimport android.annotation.SuppressLint\nimport android.app.SearchableInfo\nimport android.content.Context\nimport android.graphics.Canvas\nimport android.graphics.Paint\nimport android.graphics.drawable.Drawable\nimport android.os.Build\nimport android.text.Spannable\nimport android.text.SpannableStringBuilder\nimport android.text.style.ImageSpan\nimport android.util.AttributeSet\nimport android.util.TypedValue\nimport android.view.Gravity\nimport android.widget.TextView\nimport androidx.appcompat.widget.SearchView\nimport io.legado.app.R\nimport io.legado.app.utils.printOnDebug\n\n\nclass SearchView @JvmOverloads constructor(\n    context: Context,\n    attrs: AttributeSet? = null\n) : SearchView(context, attrs) {\n    private var mSearchHintIcon: Drawable? = null\n    private var textView: TextView? = null\n\n    @SuppressLint(\"UseCompatLoadingForDrawables\")\n    override fun onLayout(\n        changed: Boolean,\n        left: Int,\n        top: Int,\n        right: Int,\n        bottom: Int\n    ) {\n        super.onLayout(changed, left, top, right, bottom)\n        try {\n            if (textView == null) {\n                textView = findViewById(androidx.appcompat.R.id.search_src_text)\n                mSearchHintIcon = this.context.getDrawable(R.drawable.ic_search_hint)\n            }\n            // 改变字体\n            textView!!.setTextSize(TypedValue.COMPLEX_UNIT_SP, 14f)\n            textView!!.gravity = Gravity.CENTER_VERTICAL\n            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM) {\n                textView!!.isLocalePreferredLineHeightForMinimumUsed = false\n            }\n            updateQueryHint()\n        } catch (e: Exception) {\n            e.printOnDebug()\n        }\n    }\n\n    private fun getDecoratedHint(hintText: CharSequence): CharSequence {\n        // If the field is always expanded or we don't have a search hint icon,\n        // then don't add the search icon to the hint.\n        if (mSearchHintIcon == null) {\n            return hintText\n        }\n        val textSize = textView!!.textSize.toInt()\n        mSearchHintIcon!!.setBounds(0, 0, textSize, textSize)\n        val ssb = SpannableStringBuilder(\"   \")\n        ssb.setSpan(CenteredImageSpan(mSearchHintIcon), 1, 2, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE)\n        ssb.append(hintText)\n        return ssb\n    }\n\n    private fun updateQueryHint() {\n        textView?.let {\n            it.hint = getDecoratedHint(queryHint ?: \"\")\n        }\n    }\n\n    override fun setIconifiedByDefault(iconified: Boolean) {\n        super.setIconifiedByDefault(iconified)\n        updateQueryHint()\n    }\n\n    override fun setSearchableInfo(searchable: SearchableInfo?) {\n        super.setSearchableInfo(searchable)\n        searchable?.let {\n            updateQueryHint()\n        }\n    }\n\n    override fun setQueryHint(hint: CharSequence?) {\n        super.setQueryHint(hint)\n        updateQueryHint()\n    }\n\n    internal class CenteredImageSpan(drawable: Drawable?) : ImageSpan(drawable!!) {\n        override fun draw(\n            canvas: Canvas, text: CharSequence,\n            start: Int, end: Int, x: Float,\n            top: Int, y: Int, bottom: Int, paint: Paint\n        ) {\n            // image to draw\n            val b = drawable\n            // font metrics of text to be replaced\n            val fm = paint.fontMetricsInt\n            val transY = ((y + fm.descent + y + fm.ascent) / 2\n                    - b.bounds.bottom / 2)\n            canvas.save()\n            canvas.translate(x, transY.toFloat())\n            b.draw(canvas)\n            canvas.restore()\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/widget/SelectActionBar.kt",
    "content": "package io.legado.app.ui.widget\n\nimport android.content.Context\nimport android.graphics.PorterDuff\nimport android.util.AttributeSet\nimport android.view.LayoutInflater\nimport android.view.Menu\nimport android.widget.FrameLayout\nimport androidx.annotation.MenuRes\nimport androidx.annotation.StringRes\nimport androidx.appcompat.widget.PopupMenu\nimport io.legado.app.R\nimport io.legado.app.databinding.ViewSelectActionBarBinding\nimport io.legado.app.lib.theme.TintHelper\nimport io.legado.app.lib.theme.accentColor\nimport io.legado.app.lib.theme.bottomBackground\nimport io.legado.app.lib.theme.elevation\nimport io.legado.app.lib.theme.getPrimaryTextColor\nimport io.legado.app.lib.theme.getSecondaryDisabledTextColor\nimport io.legado.app.utils.ColorUtils\nimport io.legado.app.utils.applyNavigationBarPadding\nimport io.legado.app.utils.visible\n\n\n@Suppress(\"unused\")\nclass SelectActionBar @JvmOverloads constructor(\n    context: Context,\n    attrs: AttributeSet? = null\n) : FrameLayout(context, attrs) {\n\n    private val bgIsLight = ColorUtils.isColorLight(context.bottomBackground)\n    private val primaryTextColor = context.getPrimaryTextColor(bgIsLight)\n    private val disabledColor = context.getSecondaryDisabledTextColor(bgIsLight)\n\n    private var callBack: CallBack? = null\n    private var selMenu: PopupMenu? = null\n    private val binding = ViewSelectActionBarBinding\n        .inflate(LayoutInflater.from(context), this, true)\n\n    init {\n        if (!isInEditMode) {\n            setBackgroundColor(context.bottomBackground)\n            elevation = context.elevation\n            binding.cbSelectedAll.setTextColor(primaryTextColor)\n            TintHelper.setTint(binding.cbSelectedAll, context.accentColor, !bgIsLight)\n            binding.ivMenuMore.setColorFilter(disabledColor, PorterDuff.Mode.SRC_IN)\n            binding.cbSelectedAll.setOnUserCheckedChangeListener { isChecked ->\n                callBack?.selectAll(isChecked)\n            }\n            binding.btnRevertSelection.setOnClickListener { callBack?.revertSelection() }\n            binding.btnSelectActionMain.setOnClickListener { callBack?.onClickSelectBarMainAction() }\n            binding.ivMenuMore.setOnClickListener { selMenu?.show() }\n            applyNavigationBarPadding()\n        }\n    }\n\n    fun setMainActionText(text: String) = binding.run {\n        btnSelectActionMain.text = text\n        btnSelectActionMain.visible()\n    }\n\n    fun setMainActionText(@StringRes id: Int) = binding.run {\n        btnSelectActionMain.setText(id)\n        btnSelectActionMain.visible()\n    }\n\n    fun inflateMenu(@MenuRes resId: Int): Menu? {\n        selMenu = PopupMenu(context, binding.ivMenuMore)\n        selMenu?.inflate(resId)\n        binding.ivMenuMore.visible()\n        return selMenu?.menu\n    }\n\n    fun setCallBack(callBack: CallBack) {\n        this.callBack = callBack\n    }\n\n    fun setOnMenuItemClickListener(listener: PopupMenu.OnMenuItemClickListener) {\n        selMenu?.setOnMenuItemClickListener(listener)\n    }\n\n    fun upCountView(selectCount: Int, allCount: Int) = binding.run {\n        if (selectCount == 0) {\n            cbSelectedAll.isChecked = false\n        } else {\n            cbSelectedAll.isChecked = selectCount >= allCount\n        }\n\n        //重置全选的文字\n        if (cbSelectedAll.isChecked) {\n            cbSelectedAll.text = context.getString(\n                R.string.select_cancel_count,\n                selectCount,\n                allCount\n            )\n        } else {\n            cbSelectedAll.text = context.getString(\n                R.string.select_all_count,\n                selectCount,\n                allCount\n            )\n        }\n        setMenuClickable(selectCount > 0)\n    }\n\n    private fun setMenuClickable(isClickable: Boolean) = binding.run {\n        btnRevertSelection.isEnabled = isClickable\n        btnRevertSelection.isClickable = isClickable\n        btnSelectActionMain.isEnabled = isClickable\n        btnSelectActionMain.isClickable = isClickable\n        if (isClickable) {\n            ivMenuMore.setColorFilter(primaryTextColor, PorterDuff.Mode.SRC_IN)\n        } else {\n            ivMenuMore.setColorFilter(disabledColor, PorterDuff.Mode.SRC_IN)\n        }\n        ivMenuMore.isEnabled = isClickable\n        ivMenuMore.isClickable = isClickable\n    }\n\n    interface CallBack {\n\n        fun selectAll(selectAll: Boolean)\n\n        fun revertSelection()\n\n        fun onClickSelectBarMainAction() {}\n    }\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/widget/ShadowLayout.kt",
    "content": "package io.legado.app.ui.widget\n\nimport android.content.Context\nimport android.graphics.Canvas\nimport android.graphics.Color\nimport android.graphics.Paint\nimport android.graphics.RectF\nimport android.util.AttributeSet\nimport android.view.View\nimport android.widget.RelativeLayout\nimport io.legado.app.R\nimport io.legado.app.utils.getCompatColor\n\n/**\n * ShadowLayout.java\n *\n * Created by lijiankun on 17/8/11.\n */\n@Suppress(\"unused\")\nclass ShadowLayout @JvmOverloads constructor(\n    context: Context,\n    attrs: AttributeSet? = null\n) : RelativeLayout(context, attrs) {\n    private val mPaint =\n        Paint(Paint.ANTI_ALIAS_FLAG)\n    private val mRectF = RectF()\n\n    /**\n     * 阴影的颜色\n     */\n    private var mShadowColor = Color.TRANSPARENT\n\n    /**\n     * 阴影的大小范围\n     */\n    private var mShadowRadius = 0f\n\n    /**\n     * 阴影 x 轴的偏移量\n     */\n    private var mShadowDx = 0f\n\n    /**\n     * 阴影 y 轴的偏移量\n     */\n    private var mShadowDy = 0f\n\n    /**\n     * 阴影显示的边界\n     */\n    private var mShadowSide = ALL\n\n    /**\n     * 阴影的形状，圆形/矩形\n     */\n    private var mShadowShape = SHAPE_RECTANGLE\n\n\n    init {\n        setLayerType(View.LAYER_TYPE_SOFTWARE, null) // 关闭硬件加速\n        setWillNotDraw(false) // 调用此方法后，才会执行 onDraw(Canvas) 方法\n        val typedArray =\n            context.obtainStyledAttributes(attrs, R.styleable.ShadowLayout)\n        mShadowColor = typedArray.getColor(\n            R.styleable.ShadowLayout_shadowColor,\n            context.getCompatColor(android.R.color.black)\n        )\n        mShadowRadius =\n            typedArray.getDimension(R.styleable.ShadowLayout_shadowRadius, dip2px(0f))\n        mShadowDx = typedArray.getDimension(R.styleable.ShadowLayout_shadowDx, dip2px(0f))\n        mShadowDy = typedArray.getDimension(R.styleable.ShadowLayout_shadowDy, dip2px(0f))\n        mShadowSide =\n            typedArray.getInt(R.styleable.ShadowLayout_shadowSide, ALL)\n        mShadowShape = typedArray.getInt(\n            R.styleable.ShadowLayout_shadowShape,\n            SHAPE_RECTANGLE\n        )\n        typedArray.recycle()\n\n        setUpShadowPaint()\n    }\n\n    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {\n        super.onMeasure(widthMeasureSpec, heightMeasureSpec)\n        val effect = mShadowRadius + dip2px(5f)\n        var rectLeft = 0f\n        var rectTop = 0f\n        var rectRight = this.measuredWidth.toFloat()\n        var rectBottom = this.measuredHeight.toFloat()\n        var paddingLeft = 0\n        var paddingTop = 0\n        var paddingRight = 0\n        var paddingBottom = 0\n        this.width\n        if (mShadowSide and LEFT == LEFT) {\n            rectLeft = effect\n            paddingLeft = effect.toInt()\n        }\n        if (mShadowSide and TOP == TOP) {\n            rectTop = effect\n            paddingTop = effect.toInt()\n        }\n        if (mShadowSide and RIGHT == RIGHT) {\n            rectRight = this.measuredWidth - effect\n            paddingRight = effect.toInt()\n        }\n        if (mShadowSide and BOTTOM == BOTTOM) {\n            rectBottom = this.measuredHeight - effect\n            paddingBottom = effect.toInt()\n        }\n        if (mShadowDy != 0.0f) {\n            rectBottom -= mShadowDy\n            paddingBottom += mShadowDy.toInt()\n        }\n        if (mShadowDx != 0.0f) {\n            rectRight -= mShadowDx\n            paddingRight += mShadowDx.toInt()\n        }\n        mRectF.left = rectLeft\n        mRectF.top = rectTop\n        mRectF.right = rectRight\n        mRectF.bottom = rectBottom\n        setPadding(paddingLeft, paddingTop, paddingRight, paddingBottom)\n    }\n\n    /**\n     * 真正绘制阴影的方法\n     */\n    override fun onDraw(canvas: Canvas) {\n        super.onDraw(canvas)\n        setUpShadowPaint()\n        if (mShadowShape == SHAPE_RECTANGLE) {\n            canvas.drawRect(mRectF, mPaint)\n        } else if (mShadowShape == SHAPE_OVAL) {\n            canvas.drawCircle(\n                mRectF.centerX(),\n                mRectF.centerY(),\n                mRectF.width().coerceAtMost(mRectF.height()) / 2,\n                mPaint\n            )\n        }\n    }\n\n    fun setShadowColor(shadowColor: Int) {\n        mShadowColor = shadowColor\n        requestLayout()\n        postInvalidate()\n    }\n\n    fun setShadowRadius(shadowRadius: Float) {\n        mShadowRadius = shadowRadius\n        requestLayout()\n        postInvalidate()\n    }\n\n    private fun setUpShadowPaint() {\n        mPaint.reset()\n        mPaint.isAntiAlias = true\n        mPaint.color = Color.TRANSPARENT\n        mPaint.setShadowLayer(mShadowRadius, mShadowDx, mShadowDy, mShadowColor)\n    }\n\n    /**\n     * dip2px dp 值转 px 值\n     *\n     * @param dpValue dp 值\n     * @return px 值\n     */\n    private fun dip2px(dpValue: Float): Float {\n        val dm = context.resources.displayMetrics\n        val scale = dm.density\n        return dpValue * scale + 0.5f\n    }\n\n    companion object {\n        const val ALL = 0x1111\n        const val LEFT = 0x0001\n        const val TOP = 0x0010\n        const val RIGHT = 0x0100\n        const val BOTTOM = 0x1000\n        const val SHAPE_RECTANGLE = 0x0001\n        const val SHAPE_OVAL = 0x0010\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/widget/TitleBar.kt",
    "content": "package io.legado.app.ui.widget\n\nimport android.content.Context\nimport android.content.res.ColorStateList\nimport android.graphics.PorterDuff\nimport android.graphics.PorterDuffColorFilter\nimport android.graphics.drawable.ColorDrawable\nimport android.graphics.drawable.Drawable\nimport android.util.AttributeSet\nimport android.view.Menu\nimport android.view.View\nimport android.widget.ImageView\nimport androidx.annotation.ColorInt\nimport androidx.annotation.StyleRes\nimport androidx.appcompat.widget.Toolbar\nimport androidx.core.graphics.alpha\nimport androidx.core.view.WindowInsetsCompat\nimport androidx.core.view.children\nimport com.google.android.material.appbar.AppBarLayout\nimport io.legado.app.R\nimport io.legado.app.help.config.AppConfig\nimport io.legado.app.lib.theme.elevation\nimport io.legado.app.lib.theme.primaryColor\nimport io.legado.app.utils.activity\nimport io.legado.app.utils.setOnApplyWindowInsetsListenerCompat\nimport splitties.views.bottomPadding\nimport splitties.views.topPadding\n\n@Suppress(\"unused\", \"MemberVisibilityCanBePrivate\")\nclass TitleBar @JvmOverloads constructor(\n    context: Context,\n    attrs: AttributeSet? = null\n) : AppBarLayout(context, attrs) {\n\n    val toolbar: Toolbar\n    val menu: Menu\n        get() = toolbar.menu\n\n    var title: CharSequence?\n        get() = toolbar.title\n        set(title) {\n            if (toolbar.title != title) {\n                toolbar.title = title\n            }\n        }\n\n    var subtitle: CharSequence?\n        get() = toolbar.subtitle\n        set(subtitle) {\n            if (toolbar.subtitle != subtitle) {\n                toolbar.subtitle = subtitle\n            }\n        }\n\n    private val displayHomeAsUp: Boolean\n    private val navigationIconTint: ColorStateList?\n    private val navigationIconTintMode: Int\n    private val fitStatusBar: Boolean\n    private val fitNavigationBar: Boolean\n    private val attachToActivity: Boolean\n\n    init {\n        val a = context.obtainStyledAttributes(\n            attrs, R.styleable.TitleBar,\n            R.attr.titleBarStyle, 0\n        )\n        navigationIconTint = a.getColorStateList(R.styleable.TitleBar_navigationIconTint)\n        navigationIconTintMode = a.getInt(R.styleable.TitleBar_navigationIconTintMode, 9)\n        attachToActivity = a.getBoolean(R.styleable.TitleBar_attachToActivity, true)\n        displayHomeAsUp = a.getBoolean(R.styleable.TitleBar_displayHomeAsUp, true)\n        fitStatusBar = a.getBoolean(R.styleable.TitleBar_fitStatusBar, true)\n        fitNavigationBar = a.getBoolean(R.styleable.TitleBar_fitNavigationBar, false)\n\n        val navigationIcon = a.getDrawable(R.styleable.TitleBar_navigationIcon)\n        val navigationContentDescription =\n            a.getText(R.styleable.TitleBar_navigationContentDescription)\n        val titleText = a.getString(R.styleable.TitleBar_title)\n        val subtitleText = a.getString(R.styleable.TitleBar_subtitle)\n\n        when (a.getInt(R.styleable.TitleBar_themeMode, 0)) {\n            1 -> inflate(context, R.layout.view_title_bar_dark, this)\n            else -> inflate(context, R.layout.view_title_bar, this)\n        }\n        toolbar = findViewById(R.id.toolbar)\n\n        toolbar.apply {\n            navigationIcon?.let {\n                this.navigationIcon = it\n                this.navigationContentDescription = navigationContentDescription\n            }\n\n            if (a.hasValue(R.styleable.TitleBar_titleTextAppearance)) {\n                this.setTitleTextAppearance(\n                    context,\n                    a.getResourceId(R.styleable.TitleBar_titleTextAppearance, 0)\n                )\n            }\n\n            if (a.hasValue(R.styleable.TitleBar_titleTextColor)) {\n                this.setTitleTextColor(a.getColor(R.styleable.TitleBar_titleTextColor, -0x1))\n            }\n\n            if (a.hasValue(R.styleable.TitleBar_subtitleTextAppearance)) {\n                this.setSubtitleTextAppearance(\n                    context,\n                    a.getResourceId(R.styleable.TitleBar_subtitleTextAppearance, 0)\n                )\n            }\n\n            if (a.hasValue(R.styleable.TitleBar_subtitleTextColor)) {\n                this.setSubtitleTextColor(a.getColor(R.styleable.TitleBar_subtitleTextColor, -0x1))\n            }\n\n\n            if (a.hasValue(R.styleable.TitleBar_contentInsetLeft)\n                || a.hasValue(R.styleable.TitleBar_contentInsetRight)\n            ) {\n                this.setContentInsetsAbsolute(\n                    a.getDimensionPixelSize(R.styleable.TitleBar_contentInsetLeft, 0),\n                    a.getDimensionPixelSize(R.styleable.TitleBar_contentInsetRight, 0)\n                )\n            }\n\n            if (a.hasValue(R.styleable.TitleBar_contentInsetStart)\n                || a.hasValue(R.styleable.TitleBar_contentInsetEnd)\n            ) {\n                this.setContentInsetsRelative(\n                    a.getDimensionPixelSize(R.styleable.TitleBar_contentInsetStart, 0),\n                    a.getDimensionPixelSize(R.styleable.TitleBar_contentInsetEnd, 0)\n                )\n            }\n\n            if (a.hasValue(R.styleable.TitleBar_contentInsetStartWithNavigation)) {\n                this.contentInsetStartWithNavigation = a.getDimensionPixelOffset(\n                    R.styleable.TitleBar_contentInsetStartWithNavigation, 0\n                )\n            }\n\n            if (a.hasValue(R.styleable.TitleBar_contentInsetEndWithActions)) {\n                this.contentInsetEndWithActions = a.getDimensionPixelOffset(\n                    R.styleable.TitleBar_contentInsetEndWithActions, 0\n                )\n            }\n\n            if (!titleText.isNullOrBlank()) {\n                this.title = titleText\n            }\n\n            if (!subtitleText.isNullOrBlank()) {\n                this.subtitle = subtitleText\n            }\n\n            if (a.hasValue(R.styleable.TitleBar_contentLayout)) {\n                inflate(context, a.getResourceId(R.styleable.TitleBar_contentLayout, 0), this)\n            }\n        }\n\n        if (!isInEditMode) {\n//            if (fitStatusBar) {\n//                setPadding(paddingLeft, context.statusBarHeight, paddingRight, paddingBottom)\n//            }\n//\n//            if (fitNavigationBar) {\n//                setPadding(paddingLeft, paddingTop, paddingRight, context.navigationBarHeight)\n//            }\n\n            if (fitStatusBar || fitNavigationBar) {\n                setOnApplyWindowInsetsListenerCompat { _, windowInsets ->\n                    val insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())\n                    if (fitStatusBar) {\n                        topPadding = insets.top\n                    }\n                    if (fitNavigationBar) {\n                        bottomPadding = insets.bottom\n                    }\n                    windowInsets\n                }\n            }\n\n            if (AppConfig.isEInkMode) {\n                setBackgroundResource(R.drawable.bg_eink_border_bottom)\n            } else {\n                setBackgroundColor(context.primaryColor)\n            }\n\n            stateListAnimator = null\n            elevation = context.elevation\n        }\n        a.recycle()\n    }\n\n    override fun onAttachedToWindow() {\n        super.onAttachedToWindow()\n        attachToActivity()\n    }\n\n    fun setNavigationOnClickListener(clickListener: ((View) -> Unit)) {\n        toolbar.setNavigationOnClickListener(clickListener)\n    }\n\n    fun setTitle(titleId: Int) {\n        toolbar.setTitle(titleId)\n    }\n\n    fun setSubTitle(subtitleId: Int) {\n        toolbar.setSubtitle(subtitleId)\n    }\n\n    fun setTitleTextColor(@ColorInt color: Int) {\n        toolbar.setTitleTextColor(color)\n    }\n\n    fun setTitleTextAppearance(@StyleRes resId: Int) {\n        toolbar.setTitleTextAppearance(context, resId)\n    }\n\n    fun setSubTitleTextColor(@ColorInt color: Int) {\n        toolbar.setSubtitleTextColor(color)\n    }\n\n    fun setSubTitleTextAppearance(@StyleRes resId: Int) {\n        toolbar.setSubtitleTextAppearance(context, resId)\n    }\n\n    fun setTextColor(@ColorInt color: Int) {\n        setTitleTextColor(color)\n        setSubTitleTextColor(color)\n    }\n\n    fun setColorFilter(@ColorInt color: Int) {\n        val colorFilter = PorterDuffColorFilter(color, PorterDuff.Mode.SRC_ATOP)\n        toolbar.children.firstOrNull { it is ImageView }?.background?.colorFilter = colorFilter\n        toolbar.navigationIcon?.colorFilter = colorFilter\n        toolbar.overflowIcon?.colorFilter = colorFilter\n        toolbar.menu.children.forEach {\n            it.icon?.colorFilter = colorFilter\n        }\n    }\n\n    override fun setBackgroundColor(color: Int) {\n        if (color.alpha < 255) {\n            //这里不能改为0f,改为0f在横屏模式下文字和图标颜色会变\n            elevation = 0.1f\n        }\n        super.setBackgroundColor(color)\n    }\n\n    override fun setBackground(background: Drawable?) {\n        if (background is ColorDrawable) {\n            if (background.alpha < 255) {\n                //这里不能改为0f,改为0f在横屏模式下文字和图标颜色会变\n                elevation = 0.1f\n            }\n        }\n        super.setBackground(background)\n    }\n\n    fun onMultiWindowModeChanged(isInMultiWindowMode: Boolean, fullScreen: Boolean) {\n//        if (fitStatusBar) {\n//            val topPadding = if (!isInMultiWindowMode && fullScreen) context.statusBarHeight else 0\n//            setPadding(paddingLeft, topPadding, paddingRight, paddingBottom)\n//        }\n    }\n\n    private fun attachToActivity() {\n        if (attachToActivity) {\n            activity?.let {\n                it.setSupportActionBar(toolbar)\n                it.supportActionBar?.setDisplayHomeAsUpEnabled(displayHomeAsUp)\n            }\n        }\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/widget/anima/RefreshProgressBar.kt",
    "content": "package io.legado.app.ui.widget.anima\n\nimport android.content.Context\nimport android.graphics.Canvas\nimport android.graphics.Paint\nimport android.graphics.Rect\nimport android.graphics.RectF\nimport android.os.Looper\nimport android.util.AttributeSet\nimport android.view.View\n\nimport io.legado.app.R\n\n@Suppress(\"unused\", \"MemberVisibilityCanBePrivate\")\nclass RefreshProgressBar @JvmOverloads constructor(\n    context: Context,\n    attrs: AttributeSet? = null\n) : View(context, attrs) {\n    private var a = 1\n    private var durProgress = 0\n    private var secondDurProgress = 0\n    var maxProgress = 100\n    var secondMaxProgress = 100\n    var bgColor = 0x00000000\n    var secondColor = -0x3e3e3f\n    var fontColor = -0xc9c9ca\n    var speed = 2\n    var secondFinalProgress = 0\n        private set\n    private var paint: Paint = Paint()\n    private val bgRect = Rect()\n    private val secondRect = Rect()\n    private val fontRectF = RectF()\n\n    var isAutoLoading: Boolean = false\n        set(loading) {\n            field = loading\n            if (!loading) {\n                secondDurProgress = 0\n                secondFinalProgress = 0\n            }\n            maxProgress = 0\n\n            invalidate()\n        }\n\n    init {\n        paint.style = Paint.Style.FILL\n\n        val a = context.obtainStyledAttributes(attrs, R.styleable.RefreshProgressBar)\n        speed = a.getDimensionPixelSize(R.styleable.RefreshProgressBar_speed, speed)\n        maxProgress = a.getInt(R.styleable.RefreshProgressBar_max_progress, maxProgress)\n        durProgress = a.getInt(R.styleable.RefreshProgressBar_dur_progress, durProgress)\n        secondDurProgress = a.getDimensionPixelSize(\n            R.styleable.RefreshProgressBar_second_dur_progress,\n            secondDurProgress\n        )\n        secondFinalProgress = secondDurProgress\n        secondMaxProgress = a.getDimensionPixelSize(\n            R.styleable.RefreshProgressBar_second_max_progress,\n            secondMaxProgress\n        )\n        bgColor = a.getColor(R.styleable.RefreshProgressBar_bg_color, bgColor)\n        secondColor = a.getColor(R.styleable.RefreshProgressBar_second_color, secondColor)\n        fontColor = a.getColor(R.styleable.RefreshProgressBar_font_color, fontColor)\n        a.recycle()\n    }\n\n    override fun onDraw(canvas: Canvas) {\n        super.onDraw(canvas)\n\n        paint.color = bgColor\n        bgRect.set(0, 0, measuredWidth, measuredHeight)\n        canvas.drawRect(bgRect, paint)\n\n        if (secondDurProgress > 0 && secondMaxProgress > 0) {\n            var secondDur = secondDurProgress\n            if (secondDur > secondMaxProgress) {\n                secondDur = secondMaxProgress\n            }\n            paint.color = secondColor\n            val tempW =\n                (measuredWidth.toFloat() * 1.0f * (secondDur * 1.0f / secondMaxProgress)).toInt()\n            secondRect.set(\n                measuredWidth / 2 - tempW / 2,\n                0,\n                measuredWidth / 2 + tempW / 2,\n                measuredHeight\n            )\n            canvas.drawRect(secondRect, paint)\n        }\n\n        if (durProgress > 0 && maxProgress > 0) {\n            paint.color = fontColor\n            fontRectF.set(\n                0f,\n                0f,\n                measuredWidth.toFloat() * 1.0f * (durProgress * 1.0f / maxProgress),\n                measuredHeight.toFloat()\n            )\n            canvas.drawRect(fontRectF, paint)\n        }\n\n        if (this.isAutoLoading) {\n            if (secondDurProgress >= secondMaxProgress) {\n                a = -1\n            } else if (secondDurProgress <= 0) {\n                a = 1\n            }\n            secondDurProgress += a * speed\n            if (secondDurProgress < 0)\n                secondDurProgress = 0\n            else if (secondDurProgress > secondMaxProgress)\n                secondDurProgress = secondMaxProgress\n            secondFinalProgress = secondDurProgress\n            invalidate()\n        } else {\n            if (secondDurProgress != secondFinalProgress) {\n                if (secondDurProgress > secondFinalProgress) {\n                    secondDurProgress -= speed\n                    if (secondDurProgress < secondFinalProgress) {\n                        secondDurProgress = secondFinalProgress\n                    }\n                } else {\n                    secondDurProgress += speed\n                    if (secondDurProgress > secondFinalProgress) {\n                        secondDurProgress = secondFinalProgress\n                    }\n                }\n                this.invalidate()\n            }\n        }\n    }\n\n    fun getDurProgress(): Int {\n        return durProgress\n    }\n\n    fun setDurProgress(durProgress: Int) {\n        var durProgress1 = durProgress\n        if (durProgress1 < 0) {\n            durProgress1 = 0\n        }\n        if (durProgress1 > maxProgress) {\n            durProgress1 = maxProgress\n        }\n        this.durProgress = durProgress1\n        if (Looper.myLooper() == Looper.getMainLooper()) {\n            this.invalidate()\n        } else {\n            this.postInvalidate()\n        }\n    }\n\n    fun getSecondDurProgress(): Int {\n        return secondDurProgress\n    }\n\n    fun setSecondDurProgress(secondDur: Int) {\n        this.secondDurProgress = secondDur\n        this.secondFinalProgress = secondDurProgress\n        if (Looper.myLooper() == Looper.getMainLooper()) {\n            this.invalidate()\n        } else {\n            this.postInvalidate()\n        }\n    }\n\n    fun setSecondDurProgressWithAnim(secondDur: Int) {\n        var secondDur1 = secondDur\n        if (secondDur1 < 0) {\n            secondDur1 = 0\n        }\n        if (secondDur1 > secondMaxProgress) {\n            secondDur1 = secondMaxProgress\n        }\n        this.secondFinalProgress = secondDur1\n        if (Looper.myLooper() == Looper.getMainLooper()) {\n            this.invalidate()\n        } else {\n            this.postInvalidate()\n        }\n    }\n\n    fun clean() {\n        durProgress = 0\n        secondDurProgress = 0\n        secondFinalProgress = 0\n        if (Looper.myLooper() == Looper.getMainLooper()) {\n            this.invalidate()\n        } else {\n            this.postInvalidate()\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/widget/anima/RotateLoading.kt",
    "content": "package io.legado.app.ui.widget.anima\n\nimport android.animation.Animator\nimport android.animation.AnimatorListenerAdapter\nimport android.content.Context\nimport android.graphics.Canvas\nimport android.graphics.Color\nimport android.graphics.Paint\nimport android.graphics.RectF\nimport android.util.AttributeSet\nimport android.view.View\nimport io.legado.app.R\nimport io.legado.app.lib.theme.accentColor\nimport io.legado.app.utils.dpToPx\n\n/**\n * RotateLoading\n * Created by Victor on 2015/4/28.\n */\n@Suppress(\"MemberVisibilityCanBePrivate\")\nclass RotateLoading @JvmOverloads constructor(\n    context: Context,\n    attrs: AttributeSet? = null\n) : View(context, attrs) {\n\n    private var mPaint: Paint\n\n    private var loadingRectF: RectF? = null\n    private var shadowRectF: RectF? = null\n\n    private var topDegree = 10\n    private var bottomDegree = 190\n\n    private var arc: Float = 0.toFloat()\n\n    private var thisWidth: Int = 0\n\n    private var changeBigger = true\n\n    private var shadowPosition: Int = 0\n\n    private var hideMode = GONE\n\n    var isStarted = false\n        private set\n\n    var loadingColor: Int = 0\n        set(value) {\n            field = value\n            invalidate()\n        }\n\n    private var speedOfDegree: Int = 0\n\n    private var speedOfArc: Float = 0.toFloat()\n\n    private val shown = Runnable { this.startInternal() }\n\n    private val hidden = Runnable { this.stopInternal() }\n\n    init {\n        loadingColor = context.accentColor\n        thisWidth = DEFAULT_WIDTH.dpToPx()\n        shadowPosition = DEFAULT_SHADOW_POSITION.dpToPx()\n        speedOfDegree = DEFAULT_SPEED_OF_DEGREE\n\n        if (null != attrs) {\n            val typedArray = context.obtainStyledAttributes(attrs, R.styleable.RotateLoading)\n            loadingColor =\n                typedArray.getColor(R.styleable.RotateLoading_loading_color, loadingColor)\n            thisWidth = typedArray.getDimensionPixelSize(\n                R.styleable.RotateLoading_loading_width,\n                DEFAULT_WIDTH.dpToPx()\n            )\n            shadowPosition = typedArray.getInt(\n                R.styleable.RotateLoading_shadow_position,\n                DEFAULT_SHADOW_POSITION\n            )\n            speedOfDegree =\n                typedArray.getInt(R.styleable.RotateLoading_loading_speed, DEFAULT_SPEED_OF_DEGREE)\n            typedArray.recycle()\n        }\n        speedOfArc = (speedOfDegree / 4).toFloat()\n        mPaint = Paint()\n        mPaint.color = loadingColor\n        mPaint.isAntiAlias = true\n        mPaint.style = Paint.Style.STROKE\n        mPaint.strokeWidth = thisWidth.toFloat()\n        mPaint.strokeCap = Paint.Cap.ROUND\n    }\n\n    override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {\n        super.onSizeChanged(w, h, oldw, oldh)\n\n        arc = 10f\n\n        loadingRectF =\n            RectF(\n                (2 * thisWidth).toFloat(),\n                (2 * thisWidth).toFloat(),\n                (w - 2 * thisWidth).toFloat(),\n                (h - 2 * thisWidth).toFloat()\n            )\n        shadowRectF = RectF(\n            (2 * thisWidth + shadowPosition).toFloat(),\n            (2 * thisWidth + shadowPosition).toFloat(),\n            (w - 2 * thisWidth + shadowPosition).toFloat(),\n            (h - 2 * thisWidth + shadowPosition).toFloat()\n        )\n    }\n\n\n    override fun onDraw(canvas: Canvas) {\n        super.onDraw(canvas)\n\n        if (!isStarted) {\n            return\n        }\n\n        mPaint.color = Color.parseColor(\"#1a000000\")\n        shadowRectF?.let {\n            canvas.drawArc(it, topDegree.toFloat(), arc, false, mPaint)\n            canvas.drawArc(it, bottomDegree.toFloat(), arc, false, mPaint)\n        }\n\n        mPaint.color = loadingColor\n        loadingRectF?.let {\n            canvas.drawArc(it, topDegree.toFloat(), arc, false, mPaint)\n            canvas.drawArc(it, bottomDegree.toFloat(), arc, false, mPaint)\n        }\n\n        topDegree += speedOfDegree\n        bottomDegree += speedOfDegree\n        if (topDegree > 360) {\n            topDegree -= 360\n        }\n        if (bottomDegree > 360) {\n            bottomDegree -= 360\n        }\n\n        if (changeBigger) {\n            if (arc < 160) {\n                arc += speedOfArc\n                invalidate()\n            }\n        } else {\n            if (arc > speedOfDegree) {\n                arc -= 2 * speedOfArc\n                invalidate()\n            }\n        }\n        if (arc >= 160 || arc <= 10) {\n            changeBigger = !changeBigger\n            invalidate()\n        }\n    }\n\n    override fun onAttachedToWindow() {\n        super.onAttachedToWindow()\n        if (visibility == VISIBLE) {\n            startInternal()\n        }\n    }\n\n    override fun onDetachedFromWindow() {\n        super.onDetachedFromWindow()\n        isStarted = false\n        animate().cancel()\n        removeCallbacks(shown)\n        removeCallbacks(hidden)\n    }\n\n    fun visible() {\n        removeCallbacks(shown)\n        removeCallbacks(hidden)\n        post(shown)\n    }\n\n    fun inVisible() {\n        hideMode = INVISIBLE\n        removeCallbacks(shown)\n        removeCallbacks(hidden)\n        stopInternal()\n    }\n\n    fun gone() {\n        hideMode = GONE\n        removeCallbacks(shown)\n        removeCallbacks(hidden)\n        stopInternal()\n    }\n\n    private fun startInternal() {\n        startAnimator()\n        isStarted = true\n        invalidate()\n    }\n\n    private fun stopInternal() {\n        stopAnimator()\n        invalidate()\n    }\n\n    private fun startAnimator() {\n        animate().cancel()\n        animate().scaleX(1.0f)\n            .scaleY(1.0f)\n            .setListener(object : AnimatorListenerAdapter() {\n                override fun onAnimationStart(animation: Animator) {\n                    visibility = VISIBLE\n                }\n            })\n            .start()\n    }\n\n    private fun stopAnimator() {\n        animate().cancel()\n        isStarted = false\n        this.visibility = hideMode\n    }\n\n    companion object {\n        private const val DEFAULT_WIDTH = 6\n        private const val DEFAULT_SHADOW_POSITION = 2\n        private const val DEFAULT_SPEED_OF_DEGREE = 10\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/widget/anima/explosion_field/ExplosionAnimator.kt",
    "content": "/*\n * Copyright (C) 2015 tyrantgit\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.legado.app.ui.widget.anima.explosion_field\n\nimport android.animation.ValueAnimator\nimport android.annotation.SuppressLint\nimport android.graphics.*\nimport android.view.View\nimport android.view.animation.AccelerateInterpolator\nimport java.util.*\nimport kotlin.math.pow\n\n@SuppressLint(\"Recycle\")\nclass ExplosionAnimator(private val mContainer: View, bitmap: Bitmap, bound: Rect) :\n    ValueAnimator() {\n\n    private val mPaint: Paint = Paint()\n    private val mParticles: Array<Particle?>\n    private val mBound: Rect = Rect(bound)\n\n    init {\n        val partLen = 15\n        mParticles = arrayOfNulls(partLen * partLen)\n        val random = Random(System.currentTimeMillis())\n        val w = bitmap.width / (partLen + 2)\n        val h = bitmap.height / (partLen + 2)\n        for (i in 0 until partLen) {\n            for (j in 0 until partLen) {\n                mParticles[i * partLen + j] =\n                    generateParticle(bitmap.getPixel((j + 1) * w, (i + 1) * h), random)\n            }\n        }\n        setFloatValues(0f, END_VALUE)\n        interpolator = DEFAULT_INTERPOLATOR\n        duration = DEFAULT_DURATION\n    }\n\n    private fun generateParticle(color: Int, random: Random): Particle {\n        val particle = Particle()\n        particle.color = color\n        particle.radius = V\n        if (random.nextFloat() < 0.2f) {\n            particle.baseRadius = V + (X - V) * random.nextFloat()\n        } else {\n            particle.baseRadius = W + (V - W) * random.nextFloat()\n        }\n        val nextFloat = random.nextFloat()\n        particle.top = mBound.height() * (0.18f * random.nextFloat() + 0.2f)\n        particle.top =\n            if (nextFloat < 0.2f) particle.top else particle.top + particle.top * 0.2f * random.nextFloat()\n        particle.bottom = mBound.height() * (random.nextFloat() - 0.5f) * 1.8f\n        var f =\n            if (nextFloat < 0.2f) particle.bottom else if (nextFloat < 0.8f) particle.bottom * 0.6f else particle.bottom * 0.3f\n        particle.bottom = f\n        particle.mag = 4.0f * particle.top / particle.bottom\n        particle.neg = -particle.mag / particle.bottom\n        f = mBound.centerX() + Y * (random.nextFloat() - 0.5f)\n        particle.baseCx = f\n        particle.cx = f\n        f = mBound.centerY() + Y * (random.nextFloat() - 0.5f)\n        particle.baseCy = f\n        particle.cy = f\n        particle.life = END_VALUE / 10 * random.nextFloat()\n        particle.overflow = 0.4f * random.nextFloat()\n        particle.alpha = 1f\n        return particle\n    }\n\n    fun draw(canvas: Canvas): Boolean {\n        if (!isStarted) {\n            return false\n        }\n        for (particle in mParticles) {\n            particle?.let {\n                particle.advance(animatedValue as Float)\n                if (particle.alpha > 0f) {\n                    mPaint.color = particle.color\n                    mPaint.alpha = (Color.alpha(particle.color) * particle.alpha).toInt()\n                    canvas.drawCircle(particle.cx, particle.cy, particle.radius, mPaint)\n                }\n            }\n        }\n        mContainer.invalidate()\n        return true\n    }\n\n    override fun start() {\n        super.start()\n        mContainer.invalidate()\n    }\n\n    private inner class Particle {\n        var alpha: Float = 0.toFloat()\n        var color: Int = 0\n        var cx: Float = 0.toFloat()\n        var cy: Float = 0.toFloat()\n        var radius: Float = 0.toFloat()\n        var baseCx: Float = 0.toFloat()\n        var baseCy: Float = 0.toFloat()\n        var baseRadius: Float = 0.toFloat()\n        var top: Float = 0.toFloat()\n        var bottom: Float = 0.toFloat()\n        var mag: Float = 0.toFloat()\n        var neg: Float = 0.toFloat()\n        var life: Float = 0.toFloat()\n        var overflow: Float = 0.toFloat()\n\n\n        fun advance(factor: Float) {\n            var f = 0f\n            var normalization = factor / END_VALUE\n            if (normalization < life || normalization > 1f - overflow) {\n                alpha = 0f\n                return\n            }\n            normalization = (normalization - life) / (1f - life - overflow)\n            val f2 = normalization * END_VALUE\n            if (normalization >= 0.7f) {\n                f = (normalization - 0.7f) / 0.3f\n            }\n            alpha = 1f - f\n            f = bottom * f2\n            cx = baseCx + f\n            cy = (baseCy - this.neg * f.toDouble().pow(2.0)).toFloat() - f * mag\n            radius = V + (baseRadius - V) * f2\n        }\n    }\n\n    companion object {\n\n        internal var DEFAULT_DURATION: Long = 0x400\n        private val DEFAULT_INTERPOLATOR = AccelerateInterpolator(0.6f)\n        private const val END_VALUE = 1.4f\n        private val X = Utils.dp2Px(5).toFloat()\n        private val Y = Utils.dp2Px(20).toFloat()\n        private val V = Utils.dp2Px(2).toFloat()\n        private val W = Utils.dp2Px(1).toFloat()\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/widget/anima/explosion_field/ExplosionField.kt",
    "content": "package io.legado.app.ui.widget.anima.explosion_field\n\nimport android.app.Activity\nimport android.view.View\nimport android.view.ViewGroup\nimport android.view.Window\n\nobject ExplosionField {\n\n    fun attach2Window(activity: Activity): ExplosionView {\n        val rootView = activity.findViewById<View>(Window.ID_ANDROID_CONTENT) as ViewGroup\n        val explosionField = ExplosionView(activity)\n        rootView.addView(\n            explosionField, ViewGroup.LayoutParams(\n                ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT\n            )\n        )\n        return explosionField\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/widget/anima/explosion_field/ExplosionView.kt",
    "content": "/*\n * Copyright (C) 2015 tyrantgit\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.legado.app.ui.widget.anima.explosion_field\n\nimport android.animation.Animator\nimport android.animation.AnimatorListenerAdapter\nimport android.animation.ValueAnimator\nimport android.content.Context\nimport android.graphics.Bitmap\nimport android.graphics.Canvas\nimport android.graphics.Rect\nimport android.media.MediaPlayer\nimport android.util.AttributeSet\nimport android.view.View\nimport io.legado.app.utils.DebugLog\n\nimport java.util.*\n\n\n@Suppress(\"unused\")\nclass ExplosionView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) :\n    View(context, attrs) {\n\n    private var customDuration = ExplosionAnimator.DEFAULT_DURATION\n    private var idPlayAnimationEffect = 0\n    private var mZAnimatorListener: OnAnimatorListener? = null\n    private var mOnClickListener: OnClickListener? = null\n\n    private val mExplosions = ArrayList<ExplosionAnimator>()\n    private val mExpandInset = IntArray(2)\n\n    init {\n        Arrays.fill(mExpandInset, Utils.dp2Px(32))\n    }\n\n    override fun onDraw(canvas: Canvas) {\n        super.onDraw(canvas)\n        for (explosion in mExplosions) {\n            explosion.draw(canvas)\n        }\n    }\n\n    fun playSoundAnimationEffect(id: Int) {\n        this.idPlayAnimationEffect = id\n    }\n\n    fun setCustomDuration(customDuration: Long) {\n        this.customDuration = customDuration\n    }\n\n    fun addActionEvent(iEvents: OnAnimatorListener) {\n        this.mZAnimatorListener = iEvents\n    }\n\n\n    fun expandExplosionBound(dx: Int, dy: Int) {\n        mExpandInset[0] = dx\n        mExpandInset[1] = dy\n    }\n\n    @JvmOverloads\n    fun explode(bitmap: Bitmap?, bound: Rect, startDelay: Long, view: View? = null) {\n        val currentDuration = customDuration\n        val explosion = ExplosionAnimator(this, bitmap!!, bound)\n        explosion.addListener(object : AnimatorListenerAdapter() {\n            override fun onAnimationEnd(animation: Animator) {\n                mExplosions.remove(animation)\n                view?.let {\n                    view.scaleX = 1f\n                    view.scaleY = 1f\n                    view.alpha = 1f\n                    //view.setOnClickListener(mOnClickListener)//set event\n                }\n            }\n        })\n        explosion.startDelay = startDelay\n        explosion.duration = currentDuration\n        mExplosions.add(explosion)\n        explosion.start()\n    }\n\n    @JvmOverloads\n    fun explode(view: View, restartState: Boolean? = false) {\n\n        val r = Rect()\n        view.getGlobalVisibleRect(r)\n        val location = IntArray(2)\n        getLocationOnScreen(location)\n        r.offset(-location[0], -location[1])\n        r.inset(-mExpandInset[0], -mExpandInset[1])\n        val startDelay = 100\n        val animator = ValueAnimator.ofFloat(0f, 1f).setDuration(150)\n        animator.addUpdateListener(object : ValueAnimator.AnimatorUpdateListener {\n\n            var random = Random()\n\n            override fun onAnimationUpdate(animation: ValueAnimator) {\n                view.translationX = (random.nextFloat() - 0.5f) * view.width.toFloat() * 0.05f\n                view.translationY = (random.nextFloat() - 0.5f) * view.height.toFloat() * 0.05f\n            }\n        })\n\n        animator.addListener(object : Animator.AnimatorListener {\n            override fun onAnimationStart(animator: Animator) {\n                if (idPlayAnimationEffect != 0)\n                    MediaPlayer.create(context, idPlayAnimationEffect).start()\n            }\n\n            override fun onAnimationEnd(animator: Animator) {\n                if (mZAnimatorListener != null) {\n                    mZAnimatorListener!!.onAnimationEnd(animator, this@ExplosionView)\n                }\n            }\n\n            override fun onAnimationCancel(animator: Animator) {\n                DebugLog.i(javaClass.name, \"CANCEL\")\n            }\n\n            override fun onAnimationRepeat(animator: Animator) {\n                DebugLog.i(javaClass.name, \"REPEAT\")\n            }\n        })\n\n        animator.start()\n        view.animate().setDuration(150).setStartDelay(startDelay.toLong()).scaleX(0f).scaleY(0f)\n            .alpha(0f).start()\n        if (restartState!!)\n            explode(Utils.createBitmapFromView(view), r, startDelay.toLong(), view)\n        else\n            explode(Utils.createBitmapFromView(view), r, startDelay.toLong())\n\n    }\n\n    fun clear() {\n        mExplosions.clear()\n        invalidate()\n    }\n\n    override fun setOnClickListener(mOnClickListener: OnClickListener?) {\n        this.mOnClickListener = mOnClickListener\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/widget/anima/explosion_field/OnAnimatorListener.kt",
    "content": "package io.legado.app.ui.widget.anima.explosion_field\n\nimport android.animation.Animator\nimport android.view.View\n\ninterface OnAnimatorListener {\n    fun onAnimationEnd(animator: Animator, view: View)\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/widget/anima/explosion_field/Utils.kt",
    "content": "/*\n * Copyright (C) 2015 tyrantgit\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.legado.app.ui.widget.anima.explosion_field\n\n\nimport android.content.res.Resources\nimport android.graphics.Bitmap\nimport android.graphics.Canvas\nimport android.graphics.drawable.BitmapDrawable\nimport android.view.View\nimport android.widget.ImageView\nimport io.legado.app.utils.printOnDebug\n\n\nimport kotlin.math.roundToInt\n\nobject Utils {\n\n    private val DENSITY = Resources.getSystem().displayMetrics.density\n    private val sCanvas = Canvas()\n\n    fun dp2Px(dp: Int): Int {\n        return (dp * DENSITY).roundToInt()\n    }\n\n    fun createBitmapFromView(view: View): Bitmap? {\n        if (view is ImageView) {\n            val drawable = view.drawable\n            if (drawable != null && drawable is BitmapDrawable) {\n                return drawable.bitmap\n            }\n        }\n        view.clearFocus()\n        val bitmap = createBitmapSafely(\n            view.width,\n            view.height, Bitmap.Config.ARGB_8888, 1\n        )\n        if (bitmap != null) {\n            synchronized(sCanvas) {\n                val canvas = sCanvas\n                canvas.setBitmap(bitmap)\n                view.draw(canvas)\n                canvas.setBitmap(null)\n            }\n        }\n        return bitmap\n    }\n\n    private fun createBitmapSafely(\n        width: Int,\n        height: Int,\n        config: Bitmap.Config,\n        retryCount: Int\n    ): Bitmap? {\n        try {\n            return Bitmap.createBitmap(width, height, config)\n        } catch (e: OutOfMemoryError) {\n            e.printOnDebug()\n            if (retryCount > 0) {\n                System.gc()\n                return createBitmapSafely(width, height, config, retryCount - 1)\n            }\n            return null\n        }\n\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/widget/checkbox/SmoothCheckBox.kt",
    "content": "package io.legado.app.ui.widget.checkbox\n\nimport android.animation.ValueAnimator\nimport android.content.Context\nimport android.graphics.*\nimport android.util.AttributeSet\nimport android.view.View\nimport android.view.animation.LinearInterpolator\nimport android.widget.Checkable\nimport androidx.core.view.postDelayed\nimport io.legado.app.R\nimport io.legado.app.lib.theme.ThemeStore\nimport io.legado.app.utils.dpToPx\nimport io.legado.app.utils.getCompatColor\nimport kotlin.math.min\nimport kotlin.math.pow\nimport kotlin.math.roundToInt\nimport kotlin.math.sqrt\n\nclass SmoothCheckBox @JvmOverloads constructor(\n    context: Context,\n    attrs: AttributeSet? = null\n) : View(context, attrs), Checkable {\n    private var mPaint: Paint\n    private var mTickPaint: Paint\n    private var mFloorPaint: Paint\n    private var mTickPoints: Array<Point>\n    private var mCenterPoint: Point\n    private var mTickPath: Path\n    private var mLeftLineDistance = 0f\n    private var mRightLineDistance = 0f\n    private var mDrewDistance = 0f\n    private var mScaleVal = 1.0f\n    private var mFloorScale = 1.0f\n    private var mWidth = 0\n    private var mAnimDuration = 0\n    private var mStrokeWidth = 0\n    private var mCheckedColor = 0\n    private var mUnCheckedColor = 0\n    private var mFloorColor = 0\n    private var mFloorUnCheckedColor = 0\n    private var mChecked = false\n    private var mTickDrawing = false\n    var onCheckedChangeListener: ((checkBox: SmoothCheckBox, isChecked: Boolean) -> Unit)? = null\n\n    init {\n        val ta = context.obtainStyledAttributes(attrs, R.styleable.SmoothCheckBox)\n        var tickColor = ThemeStore.accentColor(context)\n        mCheckedColor = context.getCompatColor(R.color.background_menu)\n        mUnCheckedColor = context.getCompatColor(R.color.background_menu)\n        mFloorColor = context.getCompatColor(R.color.transparent30)\n        tickColor = ta.getColor(R.styleable.SmoothCheckBox_color_tick, tickColor)\n        mAnimDuration = ta.getInt(R.styleable.SmoothCheckBox_duration, DEF_ANIM_DURATION)\n        mFloorColor = ta.getColor(R.styleable.SmoothCheckBox_color_unchecked_stroke, mFloorColor)\n        mCheckedColor = ta.getColor(R.styleable.SmoothCheckBox_color_checked, mCheckedColor)\n        mUnCheckedColor = ta.getColor(R.styleable.SmoothCheckBox_color_unchecked, mUnCheckedColor)\n        mStrokeWidth = ta.getDimensionPixelSize(R.styleable.SmoothCheckBox_stroke_width, 0)\n        ta.recycle()\n        mFloorUnCheckedColor = mFloorColor\n        mTickPaint = Paint(Paint.ANTI_ALIAS_FLAG)\n        mTickPaint.style = Paint.Style.STROKE\n        mTickPaint.strokeCap = Paint.Cap.ROUND\n        mTickPaint.color = tickColor\n        mFloorPaint = Paint(Paint.ANTI_ALIAS_FLAG)\n        mFloorPaint.style = Paint.Style.FILL\n        mFloorPaint.color = mFloorColor\n        mPaint = Paint(Paint.ANTI_ALIAS_FLAG)\n        mPaint.style = Paint.Style.FILL\n        mPaint.color = mCheckedColor\n        mTickPath = Path()\n        mCenterPoint = Point()\n        mTickPoints = arrayOf(Point(), Point(), Point())\n        setOnClickListener {\n            toggle()\n            mTickDrawing = false\n            mDrewDistance = 0f\n            if (isChecked) {\n                startCheckedAnimation()\n            } else {\n                startUnCheckedAnimation()\n            }\n        }\n    }\n\n    override fun isChecked(): Boolean {\n        return mChecked\n    }\n\n    override fun setChecked(checked: Boolean) {\n        mChecked = checked\n        reset()\n        invalidate()\n        onCheckedChangeListener?.invoke(this@SmoothCheckBox, mChecked)\n    }\n\n    override fun toggle() {\n        this.isChecked = !isChecked\n    }\n\n    /**\n     * checked with animation\n     *\n     * @param checked checked\n     * @param animate change with animation\n     */\n    fun setChecked(checked: Boolean, animate: Boolean) {\n        if (animate) {\n            mTickDrawing = false\n            mChecked = checked\n            mDrewDistance = 0f\n            if (checked) {\n                startCheckedAnimation()\n            } else {\n                startUnCheckedAnimation()\n            }\n            onCheckedChangeListener?.invoke(this@SmoothCheckBox, mChecked)\n        } else {\n            this.isChecked = checked\n        }\n    }\n\n    private fun reset() {\n        mTickDrawing = true\n        mFloorScale = 1.0f\n        mScaleVal = if (isChecked) 0f else 1.0f\n        mFloorColor = if (isChecked) mCheckedColor else mFloorUnCheckedColor\n        mDrewDistance = if (isChecked) mLeftLineDistance + mRightLineDistance else 0f\n    }\n\n    private fun measureSize(measureSpec: Int): Int {\n        val defSize: Int = DEF_DRAW_SIZE.dpToPx()\n        val specSize = MeasureSpec.getSize(measureSpec)\n        val specMode = MeasureSpec.getMode(measureSpec)\n        var result = 0\n        when (specMode) {\n            MeasureSpec.UNSPECIFIED, MeasureSpec.AT_MOST -> result = min(defSize, specSize)\n            MeasureSpec.EXACTLY -> result = specSize\n        }\n        return result\n    }\n\n    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {\n        super.onMeasure(widthMeasureSpec, heightMeasureSpec)\n        setMeasuredDimension(measureSize(widthMeasureSpec), measureSize(heightMeasureSpec))\n    }\n\n    override fun onLayout(\n        changed: Boolean,\n        left: Int,\n        top: Int,\n        right: Int,\n        bottom: Int\n    ) {\n        mWidth = measuredWidth\n        mStrokeWidth = if (mStrokeWidth == 0) measuredWidth / 10 else mStrokeWidth\n        mStrokeWidth =\n            if (mStrokeWidth > measuredWidth / 5) measuredWidth / 5 else mStrokeWidth\n        mStrokeWidth = if (mStrokeWidth < 3) 3 else mStrokeWidth\n        mCenterPoint.x = mWidth / 2\n        mCenterPoint.y = measuredHeight / 2\n        mTickPoints[0].x = (measuredWidth.toFloat() / 30 * 7).roundToInt()\n        mTickPoints[0].y = (measuredHeight.toFloat() / 30 * 14).roundToInt()\n        mTickPoints[1].x = (measuredWidth.toFloat() / 30 * 13).roundToInt()\n        mTickPoints[1].y = (measuredHeight.toFloat() / 30 * 20).roundToInt()\n        mTickPoints[2].x = (measuredWidth.toFloat() / 30 * 22).roundToInt()\n        mTickPoints[2].y = (measuredHeight.toFloat() / 30 * 10).roundToInt()\n        mLeftLineDistance = sqrt(\n            (mTickPoints[1].x - mTickPoints[0].x.toDouble()).pow(2.0) +\n                    (mTickPoints[1].y - mTickPoints[0].y.toDouble()).pow(2.0)\n        ).toFloat()\n        mRightLineDistance = sqrt(\n            (mTickPoints[2].x - mTickPoints[1].x.toDouble()).pow(2.0) +\n                    (mTickPoints[2].y - mTickPoints[1].y.toDouble()).pow(2.0)\n        ).toFloat()\n        mTickPaint.strokeWidth = mStrokeWidth.toFloat()\n    }\n\n    override fun onDraw(canvas: Canvas) {\n        drawBorder(canvas)\n        drawCenter(canvas)\n        drawTick(canvas)\n    }\n\n    private fun drawCenter(canvas: Canvas) {\n        mPaint.color = mUnCheckedColor\n        val radius = (mCenterPoint.x - mStrokeWidth) * mScaleVal\n        canvas.drawCircle(mCenterPoint.x.toFloat(), mCenterPoint.y.toFloat(), radius, mPaint)\n    }\n\n    private fun drawBorder(canvas: Canvas) {\n        mFloorPaint.color = mFloorColor\n        val radius = mCenterPoint.x\n        canvas.drawCircle(\n            mCenterPoint.x.toFloat(),\n            mCenterPoint.y.toFloat(),\n            radius * mFloorScale,\n            mFloorPaint\n        )\n    }\n\n    private fun drawTick(canvas: Canvas) {\n        if (mTickDrawing && isChecked) {\n            drawTickPath(canvas)\n        }\n    }\n\n    private fun drawTickPath(canvas: Canvas) {\n        mTickPath.reset()\n        // draw left of the tick\n        if (mDrewDistance < mLeftLineDistance) {\n            val step: Float = if (mWidth / 20.0f < 3) 3f else mWidth / 20.0f\n            mDrewDistance += step\n            val stopX =\n                mTickPoints[0].x + (mTickPoints[1].x - mTickPoints[0].x) * mDrewDistance / mLeftLineDistance\n            val stopY =\n                mTickPoints[0].y + (mTickPoints[1].y - mTickPoints[0].y) * mDrewDistance / mLeftLineDistance\n            mTickPath.moveTo(mTickPoints[0].x.toFloat(), mTickPoints[0].y.toFloat())\n            mTickPath.lineTo(stopX, stopY)\n            canvas.drawPath(mTickPath, mTickPaint)\n            if (mDrewDistance > mLeftLineDistance) {\n                mDrewDistance = mLeftLineDistance\n            }\n        } else {\n            mTickPath.moveTo(mTickPoints[0].x.toFloat(), mTickPoints[0].y.toFloat())\n            mTickPath.lineTo(mTickPoints[1].x.toFloat(), mTickPoints[1].y.toFloat())\n            canvas.drawPath(mTickPath, mTickPaint)\n            // draw right of the tick\n            if (mDrewDistance < mLeftLineDistance + mRightLineDistance) {\n                val stopX =\n                    mTickPoints[1].x + (mTickPoints[2].x - mTickPoints[1].x) * (mDrewDistance - mLeftLineDistance) / mRightLineDistance\n                val stopY =\n                    mTickPoints[1].y - (mTickPoints[1].y - mTickPoints[2].y) * (mDrewDistance - mLeftLineDistance) / mRightLineDistance\n                mTickPath.reset()\n                mTickPath.moveTo(mTickPoints[1].x.toFloat(), mTickPoints[1].y.toFloat())\n                mTickPath.lineTo(stopX, stopY)\n                canvas.drawPath(mTickPath, mTickPaint)\n                val step: Float = if (mWidth / 20f < 3) 3f else mWidth / 20f\n                mDrewDistance += step\n            } else {\n                mTickPath.reset()\n                mTickPath.moveTo(mTickPoints[1].x.toFloat(), mTickPoints[1].y.toFloat())\n                mTickPath.lineTo(mTickPoints[2].x.toFloat(), mTickPoints[2].y.toFloat())\n                canvas.drawPath(mTickPath, mTickPaint)\n            }\n        }\n        // invalidate\n        if (mDrewDistance < mLeftLineDistance + mRightLineDistance) {\n            postDelayed(10) { this.postInvalidate() }\n        }\n    }\n\n    private fun startCheckedAnimation() {\n        val animator = ValueAnimator.ofFloat(1.0f, 0f)\n        animator.duration = mAnimDuration / 3 * 2.toLong()\n        animator.interpolator = LinearInterpolator()\n        animator.addUpdateListener { animation: ValueAnimator ->\n            mScaleVal = animation.animatedValue as Float\n            mFloorColor = getGradientColor(\n                mUnCheckedColor,\n                mCheckedColor,\n                1 - mScaleVal\n            )\n            postInvalidate()\n        }\n        animator.start()\n        val floorAnimator = ValueAnimator.ofFloat(1.0f, 0.8f, 1.0f)\n        floorAnimator.duration = mAnimDuration.toLong()\n        floorAnimator.interpolator = LinearInterpolator()\n        floorAnimator.addUpdateListener { animation: ValueAnimator ->\n            mFloorScale = animation.animatedValue as Float\n            postInvalidate()\n        }\n        floorAnimator.start()\n        drawTickDelayed()\n    }\n\n    private fun startUnCheckedAnimation() {\n        val animator = ValueAnimator.ofFloat(0f, 1.0f)\n        animator.duration = mAnimDuration.toLong()\n        animator.interpolator = LinearInterpolator()\n        animator.addUpdateListener { animation: ValueAnimator ->\n            mScaleVal = animation.animatedValue as Float\n            mFloorColor = getGradientColor(\n                mCheckedColor,\n                mFloorUnCheckedColor,\n                mScaleVal\n            )\n            postInvalidate()\n        }\n        animator.start()\n        val floorAnimator = ValueAnimator.ofFloat(1.0f, 0.8f, 1.0f)\n        floorAnimator.duration = mAnimDuration.toLong()\n        floorAnimator.interpolator = LinearInterpolator()\n        floorAnimator.addUpdateListener { animation: ValueAnimator ->\n            mFloorScale = animation.animatedValue as Float\n            postInvalidate()\n        }\n        floorAnimator.start()\n    }\n\n    private fun drawTickDelayed() {\n        postDelayed(mAnimDuration.toLong()) {\n            mTickDrawing = true\n            postInvalidate()\n        }\n    }\n\n    companion object {\n        private const val DEF_DRAW_SIZE = 25\n        private const val DEF_ANIM_DURATION = 300\n        private fun getGradientColor(startColor: Int, endColor: Int, percent: Float): Int {\n            val startA = Color.alpha(startColor)\n            val startR = Color.red(startColor)\n            val startG = Color.green(startColor)\n            val startB = Color.blue(startColor)\n            val endA = Color.alpha(endColor)\n            val endR = Color.red(endColor)\n            val endG = Color.green(endColor)\n            val endB = Color.blue(endColor)\n            val currentA = (startA * (1 - percent) + endA * percent).toInt()\n            val currentR = (startR * (1 - percent) + endR * percent).toInt()\n            val currentG = (startG * (1 - percent) + endG * percent).toInt()\n            val currentB = (startB * (1 - percent) + endB * percent).toInt()\n            return Color.argb(currentA, currentR, currentG, currentB)\n        }\n    }\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/widget/code/CodeView.kt",
    "content": "package io.legado.app.ui.widget.code\n\nimport android.content.Context\nimport android.graphics.Canvas\nimport android.graphics.Paint\nimport android.graphics.Paint.FontMetricsInt\nimport android.graphics.Rect\nimport android.os.Handler\nimport android.os.Looper\nimport android.text.*\nimport android.text.style.BackgroundColorSpan\nimport android.text.style.ForegroundColorSpan\nimport android.text.style.ReplacementSpan\nimport android.util.AttributeSet\nimport androidx.annotation.ColorInt\nimport io.legado.app.ui.widget.text.ScrollMultiAutoCompleteTextView\nimport java.util.*\nimport java.util.regex.Matcher\nimport java.util.regex.Pattern\nimport kotlin.math.roundToInt\n\n@Suppress(\"unused\")\nclass CodeView @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) :\n    ScrollMultiAutoCompleteTextView(context, attrs) {\n\n    private var tabWidth = 0\n    private var tabWidthInCharacters = 0\n    private var mUpdateDelayTime = 500\n    private var modified = true\n    private var highlightWhileTextChanging = true\n    private var hasErrors = false\n    private var mRemoveErrorsWhenTextChanged = true\n    private val mUpdateHandler = Handler(Looper.getMainLooper())\n    private var mAutoCompleteTokenizer: Tokenizer? = null\n    private val displayDensity = resources.displayMetrics.density\n    private val mErrorHashSet: SortedMap<Int, Int> = TreeMap()\n    private val mSyntaxPatternMap: MutableMap<Pattern, Int> = HashMap()\n    private var mIndentCharacterList = mutableListOf('{', '+', '-', '*', '/', '=')\n\n    private val mUpdateRunnable = Runnable {\n        val source = text\n        highlightWithoutChange(source)\n    }\n\n    private val mEditorTextWatcher: TextWatcher = object : TextWatcher {\n        private var start = 0\n        private var count = 0\n        override fun beforeTextChanged(\n            charSequence: CharSequence,\n            start: Int,\n            before: Int,\n            count: Int\n        ) {\n            this.start = start\n            this.count = count\n        }\n\n        override fun onTextChanged(\n            charSequence: CharSequence,\n            start: Int,\n            before: Int,\n            count: Int\n        ) {\n            if (!modified) return\n            if (highlightWhileTextChanging) {\n                if (mSyntaxPatternMap.isNotEmpty()) {\n                    convertTabs(editableText, start, count)\n                    mUpdateHandler.postDelayed(mUpdateRunnable, mUpdateDelayTime.toLong())\n                }\n            }\n            if (mRemoveErrorsWhenTextChanged) removeAllErrorLines()\n        }\n\n        override fun afterTextChanged(editable: Editable) {\n            if (!highlightWhileTextChanging) {\n                if (!modified) return\n                cancelHighlighterRender()\n                if (mSyntaxPatternMap.isNotEmpty()) {\n                    convertTabs(editableText, start, count)\n                    mUpdateHandler.postDelayed(mUpdateRunnable, mUpdateDelayTime.toLong())\n                }\n            }\n        }\n    }\n\n    init {\n        if (mAutoCompleteTokenizer == null) {\n            mAutoCompleteTokenizer = KeywordTokenizer()\n        }\n        setTokenizer(mAutoCompleteTokenizer)\n        filters = arrayOf(\n            InputFilter { source, start, end, dest, dStart, dEnd ->\n                if (modified && end - start == 1 && start < source.length && dStart < dest.length) {\n                    val c = source[start]\n                    if (c == '\\n') {\n                        return@InputFilter autoIndent(source, dest, dStart, dEnd)\n                    }\n                }\n                source\n            }\n        )\n        addTextChangedListener(mEditorTextWatcher)\n    }\n\n    override fun showDropDown() {\n        val screenPoint = IntArray(2)\n        getLocationOnScreen(screenPoint)\n        val displayFrame = Rect()\n        getWindowVisibleDisplayFrame(displayFrame)\n        val position = selectionStart\n        val layout = layout\n        val line = layout.getLineForOffset(position)\n        val verticalDistanceInDp = (750 + 140 * line) / displayDensity\n        dropDownVerticalOffset = verticalDistanceInDp.toInt()\n        val horizontalDistanceInDp = layout.getPrimaryHorizontal(position) / displayDensity\n        dropDownHorizontalOffset = horizontalDistanceInDp.toInt()\n        super.showDropDown()\n    }\n\n    private fun autoIndent(\n        source: CharSequence,\n        dest: Spanned,\n        dStart: Int,\n        dEnd: Int\n    ): CharSequence {\n        var indent = \"\"\n        var iStart = dStart - 1\n        var dataBefore = false\n        var pt = 0\n        while (iStart > -1) {\n            val c = dest[iStart]\n            if (c == '\\n') break\n            if (c != ' ' && c != '\\t') {\n                if (!dataBefore) {\n                    if (mIndentCharacterList.contains(c)) --pt\n                    dataBefore = true\n                }\n                if (c == '(') {\n                    --pt\n                } else if (c == ')') {\n                    ++pt\n                }\n            }\n            --iStart\n        }\n        if (iStart > -1) {\n            val charAtCursor = dest[dStart]\n            var iEnd: Int = ++iStart\n            while (iEnd < dEnd) {\n                val c = dest[iEnd]\n                if (charAtCursor != '\\n' && c == '/' && iEnd + 1 < dEnd && dest[iEnd] == c) {\n                    iEnd += 2\n                    break\n                }\n                if (c != ' ' && c != '\\t') {\n                    break\n                }\n                ++iEnd\n            }\n            indent += dest.subSequence(iStart, iEnd)\n        }\n        if (pt < 0) {\n            indent += \"\\t\"\n        }\n        return source.toString() + indent\n    }\n\n    private fun highlightSyntax(editable: Editable) {\n        if (mSyntaxPatternMap.isEmpty()) return\n        for (pattern in mSyntaxPatternMap.keys) {\n            val color = mSyntaxPatternMap[pattern]!!\n            val m = pattern.matcher(editable)\n            while (m.find()) {\n                createForegroundColorSpan(editable, m, color)\n            }\n        }\n    }\n\n    private fun highlightErrorLines(editable: Editable) {\n        if (mErrorHashSet.isEmpty()) return\n        val maxErrorLineValue = mErrorHashSet.lastKey()\n        var lineNumber = 0\n        val matcher = PATTERN_LINE.matcher(editable)\n        while (matcher.find()) {\n            if (mErrorHashSet.containsKey(lineNumber)) {\n                val color = mErrorHashSet[lineNumber]!!\n                createBackgroundColorSpan(editable, matcher, color)\n            }\n            lineNumber += 1\n            if (lineNumber > maxErrorLineValue) break\n        }\n    }\n\n    private fun createForegroundColorSpan(\n        editable: Editable,\n        matcher: Matcher,\n        @ColorInt color: Int\n    ) {\n        editable.setSpan(\n            ForegroundColorSpan(color),\n            matcher.start(), matcher.end(),\n            Spannable.SPAN_EXCLUSIVE_EXCLUSIVE\n        )\n    }\n\n    private fun createBackgroundColorSpan(\n        editable: Editable,\n        matcher: Matcher,\n        @ColorInt color: Int\n    ) {\n        editable.setSpan(\n            BackgroundColorSpan(color),\n            matcher.start(), matcher.end(),\n            Spannable.SPAN_EXCLUSIVE_EXCLUSIVE\n        )\n    }\n\n    private fun highlight(editable: Editable): Editable {\n        // if (editable.isEmpty() || editable.length > 1024) return editable\n        if (editable.length !in 1..1024) {\n            return editable\n        }\n        try {\n            clearSpans(editable)\n            highlightErrorLines(editable)\n            highlightSyntax(editable)\n        } catch (e: IllegalStateException) {\n            e.printStackTrace()\n        }\n        return editable\n    }\n\n    private fun highlightWithoutChange(editable: Editable) {\n        modified = false\n        highlight(editable)\n        modified = true\n    }\n\n    fun setTextHighlighted(text: CharSequence?) {\n        if (text.isNullOrEmpty()) return\n        cancelHighlighterRender()\n        removeAllErrorLines()\n        modified = false\n        setText(highlight(SpannableStringBuilder(text)))\n        modified = true\n    }\n\n    fun setTabWidth(characters: Int) {\n        if (tabWidthInCharacters == characters) return\n        tabWidthInCharacters = characters\n        tabWidth = (paint.measureText(\"m\") * characters).roundToInt()\n    }\n\n    private fun clearSpans(editable: Editable) {\n        val length = editable.length\n        val foregroundSpans = editable.getSpans(\n            0, length, ForegroundColorSpan::class.java\n        )\n        run {\n            var i = foregroundSpans.size\n            while (i-- > 0) {\n                editable.removeSpan(foregroundSpans[i])\n            }\n        }\n        val backgroundSpans = editable.getSpans(\n            0, length, BackgroundColorSpan::class.java\n        )\n        var i = backgroundSpans.size\n        while (i-- > 0) {\n            editable.removeSpan(backgroundSpans[i])\n        }\n    }\n\n    fun cancelHighlighterRender() {\n        mUpdateHandler.removeCallbacks(mUpdateRunnable)\n    }\n\n    private fun convertTabs(editable: Editable, start: Int, count: Int) {\n        var startIndex = start\n        if (tabWidth < 1) return\n        val s = editable.toString()\n        val stop = startIndex + count\n        while (s.indexOf(\"\\t\", startIndex).also { startIndex = it } > -1 && startIndex < stop) {\n            editable.setSpan(\n                TabWidthSpan(),\n                startIndex,\n                startIndex + 1, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE\n            )\n            ++startIndex\n        }\n    }\n\n    fun setSyntaxPatternsMap(syntaxPatterns: Map<Pattern, Int>?) {\n        if (mSyntaxPatternMap.isNotEmpty()) mSyntaxPatternMap.clear()\n        mSyntaxPatternMap.putAll(syntaxPatterns!!)\n    }\n\n    fun addSyntaxPattern(pattern: Pattern, @ColorInt Color: Int) {\n        mSyntaxPatternMap[pattern] = Color\n    }\n\n    fun removeSyntaxPattern(pattern: Pattern) {\n        mSyntaxPatternMap.remove(pattern)\n    }\n\n    fun getSyntaxPatternsSize(): Int {\n        return mSyntaxPatternMap.size\n    }\n\n    fun resetSyntaxPatternList() {\n        mSyntaxPatternMap.clear()\n    }\n\n    fun setAutoIndentCharacterList(characterList: MutableList<Char>) {\n        mIndentCharacterList = characterList\n    }\n\n    fun clearAutoIndentCharacterList() {\n        mIndentCharacterList.clear()\n    }\n\n    fun getAutoIndentCharacterList(): List<Char> {\n        return mIndentCharacterList\n    }\n\n    fun addErrorLine(lineNum: Int, color: Int) {\n        mErrorHashSet[lineNum] = color\n        hasErrors = true\n    }\n\n    fun removeErrorLine(lineNum: Int) {\n        mErrorHashSet.remove(lineNum)\n        hasErrors = mErrorHashSet.size > 0\n    }\n\n    fun removeAllErrorLines() {\n        mErrorHashSet.clear()\n        hasErrors = false\n    }\n\n    fun getErrorsSize(): Int {\n        return mErrorHashSet.size\n    }\n\n    fun getTextWithoutTrailingSpace(): String {\n        return PATTERN_TRAILING_WHITE_SPACE\n            .matcher(text)\n            .replaceAll(\"\")\n    }\n\n    fun setAutoCompleteTokenizer(tokenizer: Tokenizer?) {\n        mAutoCompleteTokenizer = tokenizer\n    }\n\n    fun setRemoveErrorsWhenTextChanged(removeErrors: Boolean) {\n        mRemoveErrorsWhenTextChanged = removeErrors\n    }\n\n    fun reHighlightSyntax() {\n        highlightSyntax(editableText)\n    }\n\n    fun reHighlightErrors() {\n        highlightErrorLines(editableText)\n    }\n\n    fun isHasError(): Boolean {\n        return hasErrors\n    }\n\n    fun setUpdateDelayTime(time: Int) {\n        mUpdateDelayTime = time\n    }\n\n    fun getUpdateDelayTime(): Int {\n        return mUpdateDelayTime\n    }\n\n    fun setHighlightWhileTextChanging(updateWhileTextChanging: Boolean) {\n        highlightWhileTextChanging = updateWhileTextChanging\n    }\n\n    private inner class TabWidthSpan : ReplacementSpan() {\n        override fun getSize(\n            paint: Paint,\n            text: CharSequence,\n            start: Int,\n            end: Int,\n            fm: FontMetricsInt?\n        ): Int {\n            return tabWidth\n        }\n\n        override fun draw(\n            canvas: Canvas,\n            text: CharSequence,\n            start: Int,\n            end: Int,\n            x: Float,\n            top: Int,\n            y: Int,\n            bottom: Int,\n            paint: Paint\n        ) {\n        }\n    }\n\n    companion object {\n        private val PATTERN_LINE = Pattern.compile(\"(^.+$)+\", Pattern.MULTILINE)\n        private val PATTERN_TRAILING_WHITE_SPACE = Pattern.compile(\"[\\\\t ]+$\", Pattern.MULTILINE)\n    }\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/widget/code/CodeViewExtensions.kt",
    "content": "@file:Suppress(\"unused\")\n\npackage io.legado.app.ui.widget.code\n\nimport android.content.Context\nimport android.widget.ArrayAdapter\nimport io.legado.app.R\nimport splitties.init.appCtx\nimport splitties.resources.color\nimport java.util.regex.Pattern\n\nval legadoPattern: Pattern = Pattern.compile(\"\\\\|\\\\||&&|%%|@js:|@Json:|@css:|@@|@XPath:\")\nval jsonPattern: Pattern = Pattern.compile(\"\\\"[A-Za-z0-9]*?\\\"\\\\:|\\\"|\\\\{|\\\\}|\\\\[|\\\\]\")\nval wrapPattern: Pattern = Pattern.compile(\"\\\\\\\\n\")\nval operationPattern: Pattern =\n    Pattern.compile(\":|==|>|<|!=|>=|<=|->|=|%|-|-=|%=|\\\\+|\\\\-|\\\\-=|\\\\+=|\\\\^|\\\\&|\\\\|::|\\\\?|\\\\*\")\nval jsPattern: Pattern = Pattern.compile(\"var\")\n\nfun CodeView.addLegadoPattern() {\n    addSyntaxPattern(legadoPattern, appCtx.color(R.color.md_orange_900))\n}\n\nfun CodeView.addJsonPattern() {\n    addSyntaxPattern(jsonPattern, appCtx.color(R.color.md_blue_800))\n}\n\nfun CodeView.addJsPattern() {\n    addSyntaxPattern(wrapPattern, appCtx.color(R.color.md_blue_grey_500))\n    addSyntaxPattern(operationPattern, appCtx.color(R.color.md_orange_900))\n    addSyntaxPattern(jsPattern, appCtx.color(R.color.md_light_blue_600))\n}\n\nfun Context.arrayAdapter(keywords: Array<String>): ArrayAdapter<String> {\n    return ArrayAdapter(this, R.layout.item_1line_text_and_del, R.id.text_view, keywords)\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/widget/code/KeywordTokenizer.kt",
    "content": "package io.legado.app.ui.widget.code\n\nimport android.widget.MultiAutoCompleteTextView\nimport kotlin.math.max\n\nclass KeywordTokenizer : MultiAutoCompleteTextView.Tokenizer {\n    override fun findTokenStart(charSequence: CharSequence, cursor: Int): Int {\n        var sequenceStr = charSequence.toString()\n        sequenceStr = sequenceStr.substring(0, cursor)\n        val spaceIndex = sequenceStr.lastIndexOf(\" \")\n        val lineIndex = sequenceStr.lastIndexOf(\"\\n\")\n        val bracketIndex = sequenceStr.lastIndexOf(\"(\")\n        val index = max(0, max(spaceIndex, max(lineIndex, bracketIndex)))\n        if (index == 0) return 0\n        return if (index + 1 < charSequence.length) index + 1 else index\n    }\n\n    override fun findTokenEnd(charSequence: CharSequence, cursor: Int): Int {\n        return charSequence.length\n    }\n\n    override fun terminateToken(charSequence: CharSequence): CharSequence {\n        return charSequence\n    }\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/widget/dialog/CodeDialog.kt",
    "content": "package io.legado.app.ui.widget.dialog\n\nimport android.os.Bundle\nimport android.view.View\nimport android.view.ViewGroup\nimport io.legado.app.R\nimport io.legado.app.base.BaseDialogFragment\nimport io.legado.app.databinding.DialogCodeViewBinding\nimport io.legado.app.help.IntentData\nimport io.legado.app.lib.theme.primaryColor\nimport io.legado.app.ui.widget.code.addJsPattern\nimport io.legado.app.ui.widget.code.addJsonPattern\nimport io.legado.app.ui.widget.code.addLegadoPattern\nimport io.legado.app.utils.applyTint\nimport io.legado.app.utils.disableEdit\nimport io.legado.app.utils.setLayout\nimport io.legado.app.utils.viewbindingdelegate.viewBinding\n\nclass CodeDialog() : BaseDialogFragment(R.layout.dialog_code_view) {\n\n    constructor(code: String, disableEdit: Boolean = true, requestId: String? = null) : this() {\n        arguments = Bundle().apply {\n            putBoolean(\"disableEdit\", disableEdit)\n            putString(\"code\", IntentData.put(code))\n            putString(\"requestId\", requestId)\n        }\n    }\n\n    val binding by viewBinding(DialogCodeViewBinding::bind)\n\n    override fun onStart() {\n        super.onStart()\n        setLayout(1f, ViewGroup.LayoutParams.MATCH_PARENT)\n    }\n\n    override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) {\n        binding.toolBar.setBackgroundColor(primaryColor)\n        if (arguments?.getBoolean(\"disableEdit\") == true) {\n            binding.toolBar.title = \"code view\"\n            binding.codeView.disableEdit()\n        } else {\n            initMenu()\n        }\n        binding.codeView.addLegadoPattern()\n        binding.codeView.addJsonPattern()\n        binding.codeView.addJsPattern()\n        arguments?.getString(\"code\")?.let {\n            binding.codeView.text = IntentData.get(it)\n        }\n    }\n\n    private fun initMenu() {\n        binding.toolBar.inflateMenu(R.menu.code_edit)\n        binding.toolBar.menu.applyTint(requireContext())\n        binding.toolBar.setOnMenuItemClickListener {\n            when (it.itemId) {\n                R.id.menu_save -> {\n                    binding.codeView.text?.toString()?.let { code ->\n                        val requestId = arguments?.getString(\"requestId\")\n                        (parentFragment as? Callback)?.onCodeSave(code, requestId)\n                            ?: (activity as? Callback)?.onCodeSave(code, requestId)\n                    }\n                    dismiss()\n                }\n            }\n            return@setOnMenuItemClickListener true\n        }\n    }\n\n\n    interface Callback {\n\n        fun onCodeSave(code: String, requestId: String?)\n\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/widget/dialog/PhotoDialog.kt",
    "content": "package io.legado.app.ui.widget.dialog\n\nimport android.annotation.SuppressLint\nimport android.os.Bundle\nimport android.view.View\nimport android.view.ViewGroup\nimport com.bumptech.glide.load.engine.DiskCacheStrategy\nimport com.bumptech.glide.load.resource.bitmap.DownsampleStrategy\nimport com.bumptech.glide.request.RequestOptions\nimport io.legado.app.R\nimport io.legado.app.base.BaseDialogFragment\nimport io.legado.app.databinding.DialogPhotoViewBinding\nimport io.legado.app.help.book.BookHelp\nimport io.legado.app.help.glide.ImageLoader\nimport io.legado.app.help.glide.OkHttpModelLoader\nimport io.legado.app.model.BookCover\nimport io.legado.app.model.ImageProvider\nimport io.legado.app.model.ReadBook\nimport io.legado.app.utils.setLayout\nimport io.legado.app.utils.viewbindingdelegate.viewBinding\n\n/**\n * 显示图片\n */\nclass PhotoDialog() : BaseDialogFragment(R.layout.dialog_photo_view) {\n\n    constructor(src: String, sourceOrigin: String? = null) : this() {\n        arguments = Bundle().apply {\n            putString(\"src\", src)\n            putString(\"sourceOrigin\", sourceOrigin)\n        }\n    }\n\n    private val binding by viewBinding(DialogPhotoViewBinding::bind)\n\n    override fun onStart() {\n        super.onStart()\n        setLayout(1f, ViewGroup.LayoutParams.MATCH_PARENT)\n    }\n\n    @SuppressLint(\"CheckResult\")\n    override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) {\n        val arguments = arguments ?: return\n        val src = arguments.getString(\"src\") ?: return\n        ImageProvider.get(src)?.let {\n            binding.photoView.setImageBitmap(it)\n            return\n        }\n        val file = ReadBook.book?.let { book ->\n            BookHelp.getImage(book, src)\n        }\n        if (file?.exists() == true) {\n            ImageLoader.load(requireContext(), file)\n                .error(R.drawable.image_loading_error)\n                .dontTransform()\n                .downsample(DownsampleStrategy.NONE)\n                .diskCacheStrategy(DiskCacheStrategy.NONE)\n                .into(binding.photoView)\n        } else {\n            ImageLoader.load(requireContext(), src).apply {\n                arguments.getString(\"sourceOrigin\")?.let { sourceOrigin ->\n                    apply(RequestOptions().set(OkHttpModelLoader.sourceOriginOption, sourceOrigin))\n                }\n            }.error(BookCover.defaultDrawable)\n                .dontTransform()\n                .downsample(DownsampleStrategy.NONE)\n                .into(binding.photoView)\n        }\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/widget/dialog/TextDialog.kt",
    "content": "package io.legado.app.ui.widget.dialog\n\nimport android.os.Build\nimport android.os.Bundle\nimport android.view.View\nimport android.view.ViewGroup\nimport android.view.textclassifier.TextClassifier\nimport androidx.lifecycle.lifecycleScope\nimport io.legado.app.R\nimport io.legado.app.base.BaseDialogFragment\nimport io.legado.app.databinding.DialogTextViewBinding\nimport io.legado.app.help.IntentData\nimport io.legado.app.lib.theme.primaryColor\nimport io.legado.app.utils.applyTint\nimport io.legado.app.utils.setHtml\nimport io.legado.app.utils.setLayout\nimport io.legado.app.utils.viewbindingdelegate.viewBinding\nimport io.noties.markwon.Markwon\nimport io.noties.markwon.ext.tables.TablePlugin\nimport io.noties.markwon.html.HtmlPlugin\nimport io.noties.markwon.image.glide.GlideImagesPlugin\nimport kotlinx.coroutines.Dispatchers.IO\nimport kotlinx.coroutines.delay\nimport kotlinx.coroutines.launch\nimport kotlinx.coroutines.withContext\n\n\nclass TextDialog() : BaseDialogFragment(R.layout.dialog_text_view) {\n\n    enum class Mode {\n        MD, HTML, TEXT\n    }\n\n    constructor(\n        title: String,\n        content: String?,\n        mode: Mode = Mode.TEXT,\n        time: Long = 0,\n        autoClose: Boolean = false\n    ) : this() {\n        arguments = Bundle().apply {\n            putString(\"title\", title)\n            putString(\"content\", IntentData.put(content))\n            putString(\"mode\", mode.name)\n            putLong(\"time\", time)\n        }\n        isCancelable = false\n        this.autoClose = autoClose\n    }\n\n    private val binding by viewBinding(DialogTextViewBinding::bind)\n    private var time = 0L\n    private var autoClose: Boolean = false\n\n    override fun onStart() {\n        super.onStart()\n        setLayout(ViewGroup.LayoutParams.MATCH_PARENT, 0.9f)\n    }\n\n    override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) {\n        binding.toolBar.setBackgroundColor(primaryColor)\n        binding.toolBar.inflateMenu(R.menu.dialog_text)\n        binding.toolBar.menu.applyTint(requireContext())\n        binding.toolBar.setOnMenuItemClickListener {\n            when (it.itemId) {\n                R.id.menu_close -> dismissAllowingStateLoss()\n            }\n            true\n        }\n        arguments?.let {\n            binding.toolBar.title = it.getString(\"title\")\n            val content = IntentData.get(it.getString(\"content\")) ?: \"\"\n            when (it.getString(\"mode\")) {\n                Mode.MD.name -> viewLifecycleOwner.lifecycleScope.launch {\n                    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {\n                        binding.textView.setTextClassifier(TextClassifier.NO_OP)\n                    }\n                    val markwon: Markwon\n                    val markdown = withContext(IO) {\n                        markwon = Markwon.builder(requireContext())\n                            .usePlugin(GlideImagesPlugin.create(requireContext()))\n                            .usePlugin(HtmlPlugin.create())\n                            .usePlugin(TablePlugin.create(requireContext()))\n                            .build()\n                        markwon.toMarkdown(content)\n                    }\n                    markwon.setParsedMarkdown(binding.textView, markdown)\n                }\n\n                Mode.HTML.name -> binding.textView.setHtml(content)\n                else -> {\n                    if (content.length >= 32 * 1024) {\n                        val truncatedContent =\n                            content.substring(0, 32 * 1024) + \"\\n\\n数据太大，无法全部显示…\"\n                        binding.textView.text = truncatedContent\n                    } else {\n                        binding.textView.text = content\n                    }\n                }\n            }\n            time = it.getLong(\"time\", 0L)\n        }\n        if (time > 0) {\n            binding.badgeView.setBadgeCount((time / 1000).toInt())\n            lifecycleScope.launch {\n                while (time > 0) {\n                    delay(1000)\n                    time -= 1000\n                    binding.badgeView.setBadgeCount((time / 1000).toInt())\n                    if (time <= 0) {\n                        view.post {\n                            dialog?.setCancelable(true)\n                            if (autoClose) dialog?.cancel()\n                        }\n                    }\n                }\n            }\n        } else {\n            view.post {\n                dialog?.setCancelable(true)\n            }\n        }\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/widget/dialog/TextListDialog.kt",
    "content": "package io.legado.app.ui.widget.dialog\n\nimport android.content.Context\nimport android.os.Bundle\nimport android.view.View\nimport android.view.ViewGroup\nimport androidx.recyclerview.widget.LinearLayoutManager\nimport io.legado.app.R\nimport io.legado.app.base.BaseDialogFragment\nimport io.legado.app.base.adapter.ItemViewHolder\nimport io.legado.app.base.adapter.RecyclerAdapter\nimport io.legado.app.databinding.DialogRecyclerViewBinding\nimport io.legado.app.databinding.ItemLogBinding\nimport io.legado.app.utils.setLayout\nimport io.legado.app.utils.viewbindingdelegate.viewBinding\n\n@Suppress(\"unused\")\nclass TextListDialog() : BaseDialogFragment(R.layout.dialog_recycler_view) {\n\n    constructor(title: String, values: ArrayList<String>) : this() {\n        arguments = Bundle().apply {\n            putString(\"title\", title)\n            putStringArrayList(\"values\", values)\n        }\n    }\n\n    private val binding by viewBinding(DialogRecyclerViewBinding::bind)\n    private val adapter by lazy { TextAdapter(requireContext()) }\n    private var values: ArrayList<String>? = null\n\n    override fun onStart() {\n        super.onStart()\n        setLayout(0.9f, 0.9f)\n    }\n\n    override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) = binding.run {\n        arguments?.let {\n            toolBar.title = it.getString(\"title\")\n            values = it.getStringArrayList(\"values\")\n        }\n        recyclerView.layoutManager = LinearLayoutManager(requireContext())\n        recyclerView.adapter = adapter\n        adapter.setItems(values)\n    }\n\n    class TextAdapter(context: Context) :\n        RecyclerAdapter<String, ItemLogBinding>(context) {\n\n        override fun getViewBinding(parent: ViewGroup): ItemLogBinding {\n            return ItemLogBinding.inflate(inflater, parent, false)\n        }\n\n        override fun convert(\n            holder: ItemViewHolder,\n            binding: ItemLogBinding,\n            item: String,\n            payloads: MutableList<Any>\n        ) {\n            binding.apply {\n                if (textView.getTag(R.id.tag1) == null) {\n                    val listener = object : View.OnAttachStateChangeListener {\n                        override fun onViewAttachedToWindow(v: View) {\n                            textView.isCursorVisible = false\n                            textView.isCursorVisible = true\n                        }\n\n                        override fun onViewDetachedFromWindow(v: View) {}\n                    }\n                    textView.addOnAttachStateChangeListener(listener)\n                    textView.setTag(R.id.tag1, listener)\n                }\n                textView.text = item\n            }\n        }\n\n        override fun registerListener(holder: ItemViewHolder, binding: ItemLogBinding) {\n            //nothing\n        }\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/widget/dialog/UrlOptionDialog.kt",
    "content": "package io.legado.app.ui.widget.dialog\n\nimport android.app.Dialog\nimport android.content.Context\nimport android.os.Bundle\nimport android.view.ViewGroup\nimport io.legado.app.R\nimport io.legado.app.constant.AppConst\nimport io.legado.app.databinding.DialogUrlOptionEditBinding\nimport io.legado.app.model.analyzeRule.AnalyzeUrl\nimport io.legado.app.utils.GSON\nimport io.legado.app.utils.setLayout\n\nclass UrlOptionDialog(context: Context, private val success: (String) -> Unit) : Dialog(context) {\n\n    val binding = DialogUrlOptionEditBinding.inflate(layoutInflater)\n\n    override fun onStart() {\n        super.onStart()\n        setLayout(1f, ViewGroup.LayoutParams.MATCH_PARENT)\n        window?.setBackgroundDrawableResource(R.color.transparent)\n    }\n\n    override fun onCreate(savedInstanceState: Bundle?) {\n        super.onCreate(savedInstanceState)\n        setContentView(binding.root)\n        binding.root.setOnClickListener { dismiss() }\n        binding.vwBg.setOnClickListener(null)\n        binding.editMethod.setFilterValues(\"POST\", \"GET\")\n        binding.editCharset.setFilterValues(AppConst.charsets)\n        binding.tvOk.setOnClickListener {\n            success.invoke(GSON.toJson(getUrlOption()))\n            dismiss()\n        }\n    }\n\n    private fun getUrlOption(): AnalyzeUrl.UrlOption {\n        val urlOption = AnalyzeUrl.UrlOption()\n        urlOption.useWebView(binding.cbUseWebView.isChecked)\n        urlOption.setMethod(binding.editMethod.text.toString())\n        urlOption.setCharset(binding.editCharset.text.toString())\n        urlOption.setHeaders(binding.editHeaders.text.toString())\n        urlOption.setBody(binding.editBody.text.toString())\n        urlOption.setRetry(binding.editRetry.text.toString())\n        urlOption.setType(binding.editType.text.toString())\n        urlOption.setWebJs(binding.editWebJs.text.toString())\n        urlOption.setJs(binding.editJs.text.toString())\n        return urlOption\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/widget/dialog/VariableDialog.kt",
    "content": "package io.legado.app.ui.widget.dialog\n\nimport android.app.Application\nimport android.os.Bundle\nimport android.view.MenuItem\nimport android.view.View\nimport android.view.ViewGroup\nimport androidx.appcompat.widget.Toolbar\nimport androidx.fragment.app.viewModels\nimport io.legado.app.R\nimport io.legado.app.base.BaseDialogFragment\nimport io.legado.app.base.BaseViewModel\nimport io.legado.app.databinding.DialogVariableBinding\nimport io.legado.app.lib.theme.primaryColor\nimport io.legado.app.utils.applyTint\nimport io.legado.app.utils.setLayout\nimport io.legado.app.utils.viewbindingdelegate.viewBinding\n\nclass VariableDialog() : BaseDialogFragment(R.layout.dialog_variable, true),\n    Toolbar.OnMenuItemClickListener {\n\n    private val binding by viewBinding(DialogVariableBinding::bind)\n    private val viewModel by viewModels<ViewModel>()\n\n    constructor(title: String, key: String, variable: String?, comment: String) : this() {\n        arguments = Bundle().apply {\n            putString(\"title\", title)\n            putString(\"key\", key)\n            putString(\"variable\", variable)\n            putString(\"comment\", comment)\n        }\n    }\n\n    override fun onStart() {\n        super.onStart()\n        setLayout(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT)\n    }\n\n    override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) {\n        binding.toolBar.setBackgroundColor(primaryColor)\n        arguments?.let {\n            binding.toolBar.title = it.getString(\"title\")\n            viewModel.init(it) {\n                binding.tvComment.text = viewModel.comment\n                binding.tvVariable.setText(viewModel.variable)\n            }\n        } ?: let {\n            dismiss()\n            return\n        }\n        binding.toolBar.inflateMenu(R.menu.save)\n        binding.toolBar.menu.applyTint(requireContext())\n        binding.toolBar.setOnMenuItemClickListener(this)\n    }\n\n    override fun onMenuItemClick(item: MenuItem?): Boolean {\n        when (item?.itemId) {\n            R.id.menu_save -> {\n                callback?.setVariable(\n                    viewModel.key ?: \"\",\n                    binding.tvVariable.text?.toString()\n                )\n                dismissAllowingStateLoss()\n            }\n        }\n        return true\n    }\n\n    val callback get() = (parentFragment as? Callback) ?: (activity as? Callback)\n\n    class ViewModel(application: Application) : BaseViewModel(application) {\n\n        var key: String? = null\n        var comment: String? = null\n        var variable: String? = null\n\n        fun init(arguments: Bundle, onFinally: () -> Unit) {\n            if (key != null) return\n            execute {\n                key = arguments.getString(\"key\")\n                comment = arguments.getString(\"comment\")\n                variable = arguments.getString(\"variable\")\n            }.onFinally {\n                onFinally.invoke()\n            }\n        }\n\n    }\n\n    interface Callback {\n\n        fun setVariable(key: String, variable: String?)\n\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/widget/dialog/WaitDialog.kt",
    "content": "package io.legado.app.ui.widget.dialog\n\nimport android.app.Dialog\nimport android.content.Context\nimport io.legado.app.databinding.DialogWaitBinding\n\n\n@Suppress(\"unused\")\nclass WaitDialog(context: Context) : Dialog(context) {\n\n    val binding = DialogWaitBinding.inflate(layoutInflater)\n\n    init {\n        setCanceledOnTouchOutside(false)\n        setContentView(binding.root)\n    }\n\n    fun setText(text: String): WaitDialog {\n        binding.tvMsg.text = text\n        return this\n    }\n\n    fun setText(res: Int): WaitDialog {\n        binding.tvMsg.setText(res)\n        return this\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/widget/dynamiclayout/DynamicFrameLayout.kt",
    "content": "package io.legado.app.ui.widget.dynamiclayout\n\nimport android.content.Context\nimport android.graphics.drawable.Drawable\nimport android.util.AttributeSet\nimport android.view.View\nimport android.view.ViewStub\nimport android.widget.FrameLayout\nimport android.widget.ProgressBar\nimport androidx.appcompat.widget.AppCompatButton\nimport androidx.appcompat.widget.AppCompatImageView\nimport androidx.appcompat.widget.AppCompatTextView\nimport io.legado.app.R\n\n@Suppress(\"unused\")\nclass DynamicFrameLayout @JvmOverloads constructor(\n    context: Context,\n    attrs: AttributeSet? = null\n) : FrameLayout(context, attrs), ViewSwitcher {\n\n    private var errorView: View? = null\n    private var errorImage: AppCompatImageView? = null\n    private var errorTextView: AppCompatTextView? = null\n    private var actionBtn: AppCompatButton? = null\n\n    private var progressView: View? = null\n    private var progressBar: ProgressBar? = null\n\n    private var contentView: View? = null\n\n    private var errorIcon: Drawable? = null\n    private var emptyIcon: Drawable? = null\n\n    private var errorActionDescription: CharSequence? = null\n    private var emptyActionDescription: CharSequence? = null\n    private var emptyDescription: CharSequence? = null\n\n    private var errorAction: Action? = null\n    private var emptyAction: Action? = null\n\n    private var changeListener: OnVisibilityChangeListener? = null\n\n    init {\n        View.inflate(context, R.layout.view_dynamic, this)\n\n        val a = context.obtainStyledAttributes(attrs, R.styleable.DynamicFrameLayout)\n        errorIcon = a.getDrawable(R.styleable.DynamicFrameLayout_errorSrc)\n        emptyIcon = a.getDrawable(R.styleable.DynamicFrameLayout_emptySrc)\n\n        emptyActionDescription = a.getText(R.styleable.DynamicFrameLayout_emptyActionDescription)\n        emptyDescription = a.getText(R.styleable.DynamicFrameLayout_emptyDescription)\n\n        errorActionDescription = a.getText(R.styleable.DynamicFrameLayout_errorActionDescription)\n        if (errorActionDescription == null) {\n            errorActionDescription = context.getString(R.string.dynamic_click_retry)\n        }\n        a.recycle()\n    }\n\n    override fun onFinishInflate() {\n        super.onFinishInflate()\n        if (childCount > 2) {\n            contentView = getChildAt(2)\n        }\n    }\n\n    override fun showErrorView(message: CharSequence) {\n        ensureErrorView()\n\n        setViewVisible(errorView, true)\n        setViewVisible(contentView, false)\n        setViewVisible(progressView, false)\n\n        errorTextView?.text = message\n        errorImage?.setImageDrawable(errorIcon)\n\n        actionBtn?.let {\n            it.tag = ACTION_WHEN_ERROR\n            it.visibility = View.VISIBLE\n            if (errorActionDescription != null) {\n                it.text = errorActionDescription\n            }\n        }\n\n        dispatchVisibilityChanged(ViewSwitcher.SHOW_ERROR_VIEW)\n    }\n\n    override fun showErrorView(messageId: Int) {\n        showErrorView(resources.getText(messageId))\n    }\n\n    override fun showEmptyView() {\n        ensureErrorView()\n\n        setViewVisible(errorView, true)\n        setViewVisible(contentView, false)\n        setViewVisible(progressView, false)\n\n        errorTextView?.text = emptyDescription\n        errorImage?.setImageDrawable(emptyIcon)\n\n        actionBtn?.let {\n            it.tag = ACTION_WHEN_EMPTY\n            if (errorActionDescription != null) {\n                it.visibility = View.VISIBLE\n                it.text = errorActionDescription\n            } else {\n                it.visibility = View.INVISIBLE\n            }\n        }\n\n        dispatchVisibilityChanged(ViewSwitcher.SHOW_EMPTY_VIEW)\n    }\n\n    override fun showProgressView() {\n        ensureProgressView()\n\n        setViewVisible(errorView, false)\n        setViewVisible(contentView, false)\n        setViewVisible(progressView, true)\n\n        dispatchVisibilityChanged(ViewSwitcher.SHOW_PROGRESS_VIEW)\n    }\n\n    override fun showContentView() {\n        setViewVisible(errorView, false)\n        setViewVisible(contentView, true)\n        setViewVisible(progressView, false)\n\n        dispatchVisibilityChanged(ViewSwitcher.SHOW_CONTENT_VIEW)\n    }\n\n    fun setOnVisibilityChangeListener(listener: OnVisibilityChangeListener) {\n        changeListener = listener\n    }\n\n    fun setErrorAction(action: Action) {\n        errorAction = action\n    }\n\n    fun setEmptyAction(action: Action) {\n        emptyAction = action\n    }\n\n    private fun setViewVisible(view: View?, visible: Boolean) {\n        view?.let {\n            it.visibility = if (visible) View.VISIBLE else View.INVISIBLE\n        }\n    }\n\n    private fun ensureErrorView() {\n        if (errorView == null) {\n            errorView = findViewById<ViewStub>(R.id.error_view_stub).inflate()\n            errorImage = errorView?.findViewById(R.id.iv_error_image)\n            errorTextView = errorView?.findViewById(R.id.tv_error_message)\n            actionBtn = errorView?.findViewById(R.id.btn_error_retry)\n\n            actionBtn?.setOnClickListener {\n                when (it.tag) {\n                    ACTION_WHEN_EMPTY -> emptyAction?.onAction(this@DynamicFrameLayout)\n                    ACTION_WHEN_ERROR -> errorAction?.onAction(this@DynamicFrameLayout)\n                }\n            }\n        }\n    }\n\n    private fun ensureProgressView() {\n        if (progressView == null) {\n            progressView = findViewById<ViewStub>(R.id.progress_view_stub).inflate()\n            progressBar = progressView?.findViewById(R.id.loading_progress)\n        }\n    }\n\n    private fun dispatchVisibilityChanged(@ViewSwitcher.Visibility visibility: Int) {\n        changeListener?.onVisibilityChanged(visibility)\n    }\n\n    interface Action {\n        fun onAction(switcher: ViewSwitcher)\n    }\n\n\n    interface OnVisibilityChangeListener {\n\n        fun onVisibilityChanged(@ViewSwitcher.Visibility visibility: Int)\n    }\n\n    companion object {\n        private const val ACTION_WHEN_ERROR = \"ACTION_WHEN_ERROR\"\n        private const val ACTION_WHEN_EMPTY = \"ACTION_WHEN_EMPTY\"\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/widget/dynamiclayout/ViewSwitcher.kt",
    "content": "package io.legado.app.ui.widget.dynamiclayout\n\nimport androidx.annotation.IntDef\nimport androidx.annotation.StringRes\n\ninterface ViewSwitcher {\n\n    companion object {\n        const val SHOW_CONTENT_VIEW = 0\n        const val SHOW_ERROR_VIEW = 1\n        const val SHOW_EMPTY_VIEW = 2\n        const val SHOW_PROGRESS_VIEW = 3\n    }\n\n    @Retention(AnnotationRetention.SOURCE)\n    @IntDef(SHOW_CONTENT_VIEW, SHOW_ERROR_VIEW, SHOW_EMPTY_VIEW, SHOW_PROGRESS_VIEW)\n    annotation class Visibility\n\n    fun showErrorView(message: CharSequence)\n\n    fun showErrorView(@StringRes messageId: Int)\n\n    fun showEmptyView()\n\n    fun showProgressView()\n\n    fun showContentView()\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/widget/image/ArcView.kt",
    "content": "package io.legado.app.ui.widget.image\n\nimport android.content.Context\nimport android.graphics.*\nimport android.util.AttributeSet\nimport android.view.View\nimport io.legado.app.R\n\n/**\n * 弧形View\n */\nclass ArcView @JvmOverloads constructor(\n    context: Context,\n    attrs: AttributeSet? = null\n) : View(context, attrs) {\n    private var mWidth = 0\n    private var mHeight = 0\n\n    //弧形高度\n    private val mArcHeight: Int\n\n    //背景颜色\n    private var mBgColor: Int\n    private val mPaint: Paint = Paint().apply {\n        isAntiAlias = true\n    }\n    private val mDirectionTop: Boolean\n    val rect = Rect()\n    val path = Path()\n\n    init {\n        val typedArray = context.obtainStyledAttributes(attrs, R.styleable.ArcView)\n        mArcHeight = typedArray.getDimensionPixelSize(R.styleable.ArcView_arcHeight, 0)\n        mBgColor = typedArray.getColor(\n            R.styleable.ArcView_bgColor,\n            Color.parseColor(\"#303F9F\")\n        )\n        mDirectionTop = typedArray.getBoolean(R.styleable.ArcView_arcDirectionTop, false)\n        typedArray.recycle()\n    }\n\n    override fun onDraw(canvas: Canvas) {\n        super.onDraw(canvas)\n        mPaint.style = Paint.Style.FILL\n        mPaint.color = mBgColor\n        if (mDirectionTop) {\n            rect.set(0, mArcHeight, mWidth, mHeight)\n            canvas.drawRect(rect, mPaint)\n            path.reset()\n            path.moveTo(0f, mArcHeight.toFloat())\n            path.quadTo(mWidth / 2.toFloat(), 0f, mWidth.toFloat(), mArcHeight.toFloat())\n            canvas.drawPath(path, mPaint)\n        } else {\n            rect.set(0, 0, mWidth, mHeight - mArcHeight)\n            canvas.drawRect(rect, mPaint)\n            path.reset()\n            path.moveTo(0f, mHeight - mArcHeight.toFloat())\n            path.quadTo(\n                mWidth / 2.toFloat(),\n                mHeight.toFloat(),\n                mWidth.toFloat(),\n                mHeight - mArcHeight.toFloat()\n            )\n            canvas.drawPath(path, mPaint)\n        }\n    }\n\n    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {\n        super.onMeasure(widthMeasureSpec, heightMeasureSpec)\n        val widthSize = MeasureSpec.getSize(widthMeasureSpec)\n        val widthMode = MeasureSpec.getMode(widthMeasureSpec)\n        val heightSize = MeasureSpec.getSize(heightMeasureSpec)\n        val heightMode = MeasureSpec.getMode(heightMeasureSpec)\n        if (widthMode == MeasureSpec.EXACTLY) {\n            mWidth = widthSize\n        }\n        if (heightMode == MeasureSpec.EXACTLY) {\n            mHeight = heightSize\n        }\n        setMeasuredDimension(mWidth, mHeight)\n    }\n\n    fun setBgColor(color: Int) {\n        mBgColor = color\n        invalidate()\n    }\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/widget/image/CircleImageView.kt",
    "content": "package io.legado.app.ui.widget.image\n\nimport android.annotation.SuppressLint\nimport android.content.Context\nimport android.graphics.*\nimport android.graphics.drawable.BitmapDrawable\nimport android.graphics.drawable.ColorDrawable\nimport android.graphics.drawable.Drawable\nimport android.net.Uri\nimport android.text.TextPaint\nimport android.util.AttributeSet\nimport android.view.MotionEvent\nimport android.view.View\nimport android.view.ViewOutlineProvider\nimport androidx.annotation.ColorInt\nimport androidx.annotation.ColorRes\nimport androidx.annotation.DrawableRes\nimport androidx.appcompat.widget.AppCompatImageView\nimport io.legado.app.R\nimport io.legado.app.utils.getCompatColor\nimport io.legado.app.utils.printOnDebug\nimport io.legado.app.utils.spToPx\nimport kotlin.math.min\nimport kotlin.math.pow\n\n@Suppress(\"unused\", \"MemberVisibilityCanBePrivate\")\nclass CircleImageView @JvmOverloads constructor(\n    context: Context,\n    attrs: AttributeSet? = null\n) : AppCompatImageView(context, attrs) {\n\n    private val mDrawableRect = RectF()\n    private val mBorderRect = RectF()\n\n    private val mShaderMatrix = Matrix()\n    private val mBitmapPaint = Paint()\n    private val mBorderPaint = Paint()\n    private val mCircleBackgroundPaint = Paint()\n    private val textPaint by lazy {\n        val textPaint = TextPaint()\n        textPaint.isAntiAlias = true\n        textPaint.textAlign = Paint.Align.CENTER\n        textPaint\n    }\n\n    private var mBorderColor = DEFAULT_BORDER_COLOR\n    private var mBorderWidth = DEFAULT_BORDER_WIDTH\n    private var mCircleBackgroundColor = DEFAULT_CIRCLE_BACKGROUND_COLOR\n\n    private var mBitmap: Bitmap? = null\n    private var mBitmapShader: BitmapShader? = null\n    private var mBitmapWidth: Int = 0\n    private var mBitmapHeight: Int = 0\n\n    private var mDrawableRadius: Float = 0.toFloat()\n    private var mBorderRadius: Float = 0.toFloat()\n\n    private var mColorFilter: ColorFilter? = null\n\n    private var mReady: Boolean = false\n    private var mSetupPending: Boolean = false\n    private var mBorderOverlay: Boolean = false\n    var isDisableCircularTransformation: Boolean = false\n        set(disableCircularTransformation) {\n            if (field == disableCircularTransformation) {\n                return\n            }\n            field = disableCircularTransformation\n            initializeBitmap()\n        }\n\n    var borderColor: Int\n        get() = mBorderColor\n        set(@ColorInt borderColor) {\n            if (borderColor == mBorderColor) {\n                return\n            }\n\n            mBorderColor = borderColor\n            mBorderPaint.color = mBorderColor\n            invalidate()\n        }\n\n    var circleBackgroundColor: Int\n        get() = mCircleBackgroundColor\n        set(@ColorInt circleBackgroundColor) {\n            if (circleBackgroundColor == mCircleBackgroundColor) {\n                return\n            }\n            mCircleBackgroundColor = circleBackgroundColor\n            mCircleBackgroundPaint.color = circleBackgroundColor\n            invalidate()\n        }\n\n    var borderWidth: Int\n        get() = mBorderWidth\n        set(borderWidth) {\n            if (borderWidth == mBorderWidth) {\n                return\n            }\n\n            mBorderWidth = borderWidth\n            setup()\n        }\n\n    var isBorderOverlay: Boolean\n        get() = mBorderOverlay\n        set(borderOverlay) {\n            if (borderOverlay == mBorderOverlay) {\n                return\n            }\n\n            mBorderOverlay = borderOverlay\n            setup()\n        }\n\n    private var text: String? = null\n\n    private var textColor = context.getCompatColor(R.color.primaryText)\n    private var textBold = false\n    var isInView = false\n\n    init {\n        val a = context.obtainStyledAttributes(attrs, R.styleable.CircleImageView)\n        mBorderWidth =\n            a.getDimensionPixelSize(\n                R.styleable.CircleImageView_civ_border_width,\n                DEFAULT_BORDER_WIDTH\n            )\n        mBorderColor =\n            a.getColor(R.styleable.CircleImageView_civ_border_color, DEFAULT_BORDER_COLOR)\n        mBorderOverlay =\n            a.getBoolean(R.styleable.CircleImageView_civ_border_overlay, DEFAULT_BORDER_OVERLAY)\n        mCircleBackgroundColor =\n            a.getColor(\n                R.styleable.CircleImageView_civ_circle_background_color,\n                DEFAULT_CIRCLE_BACKGROUND_COLOR\n            )\n        text = a.getString(R.styleable.CircleImageView_text)\n        contentDescription = text\n        if (a.hasValue(R.styleable.CircleImageView_textColor)) {\n            textColor = a.getColor(\n                R.styleable.CircleImageView_textColor,\n                context.getCompatColor(R.color.primaryText)\n            )\n        }\n        a.recycle()\n\n        mReady = true\n\n        if (mSetupPending) {\n            setup()\n            mSetupPending = false\n        }\n    }\n\n    override fun setAdjustViewBounds(adjustViewBounds: Boolean) {\n        if (adjustViewBounds) {\n            throw IllegalArgumentException(\"adjustViewBounds not supported.\")\n        }\n    }\n\n    override fun onDraw(canvas: Canvas) {\n        if (isDisableCircularTransformation) {\n            super.onDraw(canvas)\n            return\n        }\n        if (mBitmap == null) {\n            return\n        }\n\n        if (mCircleBackgroundColor != Color.TRANSPARENT) {\n            canvas.drawCircle(\n                mDrawableRect.centerX(),\n                mDrawableRect.centerY(),\n                mDrawableRadius,\n                mCircleBackgroundPaint\n            )\n        }\n        canvas.drawCircle(\n            mDrawableRect.centerX(),\n            mDrawableRect.centerY(),\n            mDrawableRadius,\n            mBitmapPaint\n        )\n        if (mBorderWidth > 0) {\n            canvas.drawCircle(\n                mBorderRect.centerX(),\n                mBorderRect.centerY(),\n                mBorderRadius,\n                mBorderPaint\n            )\n        }\n        drawText(canvas)\n    }\n\n    private fun drawText(canvas: Canvas) {\n        text?.let {\n            textPaint.color = textColor\n            textPaint.isFakeBoldText = textBold\n            textPaint.textSize = 15f.spToPx()\n            val fm = textPaint.fontMetrics\n            canvas.drawText(\n                it,\n                width * 0.5f,\n                (height * 0.5f + (fm.bottom - fm.top) * 0.5f - fm.bottom),\n                textPaint\n            )\n        }\n    }\n\n    fun setText(text: String?) {\n        this.text = text\n        contentDescription = text\n        invalidate()\n    }\n\n    fun setTextColor(@ColorInt textColor: Int) {\n        this.textColor = textColor\n        invalidate()\n    }\n\n    fun setTextBold(bold: Boolean) {\n        this.textBold = bold\n        invalidate()\n    }\n\n    override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {\n        super.onSizeChanged(w, h, oldw, oldh)\n        setup()\n    }\n\n    override fun setPadding(left: Int, top: Int, right: Int, bottom: Int) {\n        super.setPadding(left, top, right, bottom)\n        setup()\n    }\n\n    override fun setPaddingRelative(start: Int, top: Int, end: Int, bottom: Int) {\n        super.setPaddingRelative(start, top, end, bottom)\n        setup()\n    }\n\n    fun setCircleBackgroundColorResource(@ColorRes circleBackgroundRes: Int) {\n        circleBackgroundColor = context.getCompatColor(circleBackgroundRes)\n    }\n\n    override fun setImageBitmap(bm: Bitmap) {\n        super.setImageBitmap(bm)\n        initializeBitmap()\n    }\n\n    override fun setImageDrawable(drawable: Drawable?) {\n        super.setImageDrawable(drawable)\n        initializeBitmap()\n    }\n\n    override fun setImageResource(@DrawableRes resId: Int) {\n        super.setImageResource(resId)\n        initializeBitmap()\n    }\n\n    override fun setImageURI(uri: Uri?) {\n        super.setImageURI(uri)\n        initializeBitmap()\n    }\n\n    override fun setColorFilter(cf: ColorFilter) {\n        if (cf === mColorFilter) {\n            return\n        }\n\n        mColorFilter = cf\n        applyColorFilter()\n        invalidate()\n    }\n\n    override fun getColorFilter(): ColorFilter? {\n        return mColorFilter\n    }\n\n    private fun applyColorFilter() {\n        mBitmapPaint.colorFilter = mColorFilter\n    }\n\n    private fun getBitmapFromDrawable(drawable: Drawable?): Bitmap? {\n        if (drawable == null) {\n            return null\n        }\n\n        if (drawable is BitmapDrawable) {\n            return drawable.bitmap\n        }\n\n        return try {\n            val bitmap: Bitmap = if (drawable is ColorDrawable) {\n                Bitmap.createBitmap(\n                    COLOR_DRAWABLE_DIMENSION,\n                    COLOR_DRAWABLE_DIMENSION,\n                    BITMAP_CONFIG\n                )\n            } else {\n                Bitmap.createBitmap(\n                    drawable.intrinsicWidth,\n                    drawable.intrinsicHeight,\n                    BITMAP_CONFIG\n                )\n            }\n\n            val canvas = Canvas(bitmap)\n            drawable.setBounds(0, 0, canvas.width, canvas.height)\n            drawable.draw(canvas)\n            bitmap\n        } catch (e: Exception) {\n            e.printOnDebug()\n            null\n        }\n\n    }\n\n    private fun initializeBitmap() {\n        mBitmap = if (isDisableCircularTransformation) {\n            null\n        } else {\n            getBitmapFromDrawable(drawable)\n        }\n        setup()\n    }\n\n    private fun setup() {\n        if (!mReady) {\n            mSetupPending = true\n            return\n        }\n\n        if (width == 0 && height == 0) {\n            return\n        }\n\n        if (mBitmap == null) {\n            invalidate()\n            return\n        }\n\n        mBitmapShader = BitmapShader(mBitmap!!, Shader.TileMode.CLAMP, Shader.TileMode.CLAMP)\n\n        mBitmapPaint.isAntiAlias = true\n        mBitmapPaint.shader = mBitmapShader\n\n        mBorderPaint.style = Paint.Style.STROKE\n        mBorderPaint.isAntiAlias = true\n        mBorderPaint.color = mBorderColor\n        mBorderPaint.strokeWidth = mBorderWidth.toFloat()\n\n        mCircleBackgroundPaint.style = Paint.Style.FILL\n        mCircleBackgroundPaint.isAntiAlias = true\n        mCircleBackgroundPaint.color = mCircleBackgroundColor\n\n        mBitmapHeight = mBitmap!!.height\n        mBitmapWidth = mBitmap!!.width\n\n        mBorderRect.set(calculateBounds())\n        mBorderRadius =\n            min(\n                (mBorderRect.height() - mBorderWidth) / 2.0f,\n                (mBorderRect.width() - mBorderWidth) / 2.0f\n            )\n\n        mDrawableRect.set(mBorderRect)\n        if (!mBorderOverlay && mBorderWidth > 0) {\n            mDrawableRect.inset(mBorderWidth - 1.0f, mBorderWidth - 1.0f)\n        }\n        mDrawableRadius = min(mDrawableRect.height() / 2.0f, mDrawableRect.width() / 2.0f)\n\n        applyColorFilter()\n        updateShaderMatrix()\n        invalidate()\n    }\n\n    private fun calculateBounds(): RectF {\n        val availableWidth = width - paddingLeft - paddingRight\n        val availableHeight = height - paddingTop - paddingBottom\n\n        val sideLength = min(availableWidth, availableHeight)\n\n        val left = paddingLeft + (availableWidth - sideLength) / 2f\n        val top = paddingTop + (availableHeight - sideLength) / 2f\n\n        return RectF(left, top, left + sideLength, top + sideLength)\n    }\n\n    private fun updateShaderMatrix() {\n        val scale: Float\n        var dx = 0f\n        var dy = 0f\n\n        mShaderMatrix.set(null)\n\n        if (mBitmapWidth * mDrawableRect.height() > mDrawableRect.width() * mBitmapHeight) {\n            scale = mDrawableRect.height() / mBitmapHeight.toFloat()\n            dx = (mDrawableRect.width() - mBitmapWidth * scale) * 0.5f\n        } else {\n            scale = mDrawableRect.width() / mBitmapWidth.toFloat()\n            dy = (mDrawableRect.height() - mBitmapHeight * scale) * 0.5f\n        }\n\n        mShaderMatrix.setScale(scale, scale)\n        mShaderMatrix.postTranslate(\n            (dx + 0.5f).toInt() + mDrawableRect.left,\n            (dy + 0.5f).toInt() + mDrawableRect.top\n        )\n\n        mBitmapShader!!.setLocalMatrix(mShaderMatrix)\n    }\n\n    @SuppressLint(\"ClickableViewAccessibility\")\n    override fun onTouchEvent(event: MotionEvent): Boolean {\n        when (event.action) {\n            MotionEvent.ACTION_DOWN -> {\n                isInView = (inTouchableArea(event.x, event.y))\n            }\n        }\n        return super.onTouchEvent(event)\n    }\n\n    private fun inTouchableArea(x: Float, y: Float): Boolean {\n        return (x - mBorderRect.centerX()).toDouble()\n            .pow(2.0) + (y - mBorderRect.centerY()).toDouble()\n            .pow(2.0) <= mBorderRadius.toDouble().pow(2.0)\n    }\n\n    private inner class OutlineProvider : ViewOutlineProvider() {\n\n        override fun getOutline(view: View, outline: Outline) {\n            val bounds = Rect()\n            mBorderRect.roundOut(bounds)\n            outline.setRoundRect(bounds, bounds.width() / 2.0f)\n        }\n\n    }\n\n    companion object {\n        private val BITMAP_CONFIG = Bitmap.Config.ARGB_8888\n        private const val COLOR_DRAWABLE_DIMENSION = 2\n        private const val DEFAULT_BORDER_WIDTH = 0\n        private const val DEFAULT_BORDER_COLOR = Color.BLACK\n        private const val DEFAULT_CIRCLE_BACKGROUND_COLOR = Color.TRANSPARENT\n        private const val DEFAULT_BORDER_OVERLAY = false\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/widget/image/CoverImageView.kt",
    "content": "package io.legado.app.ui.widget.image\n\nimport android.content.Context\nimport android.graphics.Canvas\nimport android.graphics.Color\nimport android.graphics.Paint\nimport android.graphics.Path\nimport android.graphics.Typeface\nimport android.graphics.drawable.Drawable\nimport android.text.TextPaint\nimport android.util.AttributeSet\nimport android.view.ViewGroup\nimport androidx.appcompat.widget.AppCompatImageView\nimport androidx.fragment.app.Fragment\nimport androidx.lifecycle.Lifecycle\nimport com.bumptech.glide.load.DataSource\nimport com.bumptech.glide.load.engine.GlideException\nimport com.bumptech.glide.request.RequestListener\nimport com.bumptech.glide.request.RequestOptions\nimport com.bumptech.glide.request.target.Target\nimport io.legado.app.constant.AppPattern\nimport io.legado.app.help.config.AppConfig\nimport io.legado.app.help.glide.ImageLoader\nimport io.legado.app.help.glide.OkHttpModelLoader\nimport io.legado.app.lib.theme.accentColor\nimport io.legado.app.model.BookCover\nimport io.legado.app.utils.textHeight\nimport io.legado.app.utils.toStringArray\n\n/**\n * 封面\n */\n@Suppress(\"unused\")\nclass CoverImageView @JvmOverloads constructor(\n    context: Context,\n    attrs: AttributeSet? = null\n) : AppCompatImageView(context, attrs) {\n    private var filletPath = Path()\n    private var viewWidth: Float = 0f\n    private var viewHeight: Float = 0f\n    private var defaultCover = true\n    var bitmapPath: String? = null\n        private set\n    private var name: String? = null\n    private var author: String? = null\n    private var nameHeight = 0f\n    private var authorHeight = 0f\n    private val namePaint by lazy {\n        val textPaint = TextPaint()\n        textPaint.typeface = Typeface.DEFAULT_BOLD\n        textPaint.isAntiAlias = true\n        textPaint.textAlign = Paint.Align.CENTER\n        textPaint\n    }\n    private val authorPaint by lazy {\n        val textPaint = TextPaint()\n        textPaint.typeface = Typeface.DEFAULT\n        textPaint.isAntiAlias = true\n        textPaint.textAlign = Paint.Align.CENTER\n        textPaint\n    }\n\n    override fun setLayoutParams(params: ViewGroup.LayoutParams?) {\n        if (params != null) {\n            val width = params.width\n            if (width >= 0) {\n                params.height = width * 7 / 5\n            } else {\n                params.height = ViewGroup.LayoutParams.WRAP_CONTENT\n            }\n        }\n        super.setLayoutParams(params)\n    }\n\n    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {\n        val measuredWidth = MeasureSpec.getSize(widthMeasureSpec)\n        val measuredHeight = measuredWidth * 7 / 5\n        super.onMeasure(\n            widthMeasureSpec,\n            MeasureSpec.makeMeasureSpec(measuredHeight, MeasureSpec.EXACTLY)\n        )\n    }\n\n    override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {\n        super.onLayout(changed, left, top, right, bottom)\n        viewWidth = width.toFloat()\n        viewHeight = height.toFloat()\n        filletPath.reset()\n        if (width > 10 && viewHeight > 10) {\n            filletPath.apply {\n                moveTo(10f, 0f)\n                lineTo(viewWidth - 10, 0f)\n                quadTo(viewWidth, 0f, viewWidth, 10f)\n                lineTo(viewWidth, viewHeight - 10)\n                quadTo(viewWidth, viewHeight, viewWidth - 10, viewHeight)\n                lineTo(10f, viewHeight)\n                quadTo(0f, viewHeight, 0f, viewHeight - 10)\n                lineTo(0f, 10f)\n                quadTo(0f, 0f, 10f, 0f)\n                close()\n            }\n        }\n    }\n\n    override fun onDraw(canvas: Canvas) {\n        if (!filletPath.isEmpty) {\n            canvas.clipPath(filletPath)\n        }\n        super.onDraw(canvas)\n        if (defaultCover && !isInEditMode) {\n            drawNameAuthor(canvas)\n        }\n    }\n\n    private fun drawNameAuthor(canvas: Canvas) {\n        if (!BookCover.drawBookName) return\n        var startX = width * 0.2f\n        var startY = viewHeight * 0.2f\n        name?.toStringArray()?.let { name ->\n            namePaint.textSize = viewWidth / 6\n            namePaint.strokeWidth = namePaint.textSize / 5\n            name.forEachIndexed { index, char ->\n                namePaint.color = Color.WHITE\n                namePaint.style = Paint.Style.STROKE\n                canvas.drawText(char, startX, startY, namePaint)\n                namePaint.color = context.accentColor\n                namePaint.style = Paint.Style.FILL\n                canvas.drawText(char, startX, startY, namePaint)\n                startY += namePaint.textHeight\n                if (startY > viewHeight * 0.8) {\n                    startX += namePaint.textSize\n                    namePaint.textSize = viewWidth / 10\n                    startY = (viewHeight - (name.size - index - 1) * namePaint.textHeight) / 2\n                }\n            }\n        }\n        if (!BookCover.drawBookAuthor) return\n        author?.toStringArray()?.let { author ->\n            authorPaint.textSize = viewWidth / 10\n            authorPaint.strokeWidth = authorPaint.textSize / 5\n            startX = width * 0.8f\n            startY = viewHeight * 0.95f - author.size * authorPaint.textHeight\n            startY = maxOf(startY, viewHeight * 0.3f)\n            author.forEach {\n                authorPaint.color = Color.WHITE\n                authorPaint.style = Paint.Style.STROKE\n                canvas.drawText(it, startX, startY, authorPaint)\n                authorPaint.color = context.accentColor\n                authorPaint.style = Paint.Style.FILL\n                canvas.drawText(it, startX, startY, authorPaint)\n                startY += authorPaint.textHeight\n                if (startY > viewHeight * 0.95) {\n                    return@let\n                }\n            }\n        }\n    }\n\n    fun setHeight(height: Int) {\n        val width = height * 5 / 7\n        minimumWidth = width\n    }\n\n    private val glideListener by lazy {\n        object : RequestListener<Drawable> {\n\n            override fun onLoadFailed(\n                e: GlideException?,\n                model: Any?,\n                target: Target<Drawable>,\n                isFirstResource: Boolean\n            ): Boolean {\n                defaultCover = true\n                return false\n            }\n\n            override fun onResourceReady(\n                resource: Drawable,\n                model: Any,\n                target: Target<Drawable>?,\n                dataSource: DataSource,\n                isFirstResource: Boolean\n            ): Boolean {\n                defaultCover = false\n                return false\n            }\n\n        }\n    }\n\n    fun load(\n        path: String? = null,\n        name: String? = null,\n        author: String? = null,\n        loadOnlyWifi: Boolean = false,\n        sourceOrigin: String? = null,\n        fragment: Fragment? = null,\n        lifecycle: Lifecycle? = null,\n        onLoadFinish: (() -> Unit)? = null\n    ) {\n        this.bitmapPath = path\n        this.name = name?.replace(AppPattern.bdRegex, \"\")?.trim()\n        this.author = author?.replace(AppPattern.bdRegex, \"\")?.trim()\n        defaultCover = true\n        invalidate()\n        if (AppConfig.useDefaultCover) {\n            ImageLoader.load(context, BookCover.defaultDrawable)\n                .centerCrop()\n                .into(this)\n        } else {\n            var options = RequestOptions().set(OkHttpModelLoader.loadOnlyWifiOption, loadOnlyWifi)\n            if (sourceOrigin != null) {\n                options = options.set(OkHttpModelLoader.sourceOriginOption, sourceOrigin)\n            }\n            var builder = if (fragment != null && lifecycle != null) {\n                ImageLoader.load(fragment, lifecycle, path)\n            } else {\n                ImageLoader.load(context, path)//Glide自动识别http://,content://和file://\n            }\n            builder = builder.apply(options)\n                .placeholder(BookCover.defaultDrawable)\n                .error(BookCover.defaultDrawable)\n                .listener(glideListener)\n            if (onLoadFinish != null) {\n                builder = builder.addListener(object : RequestListener<Drawable> {\n                    override fun onLoadFailed(\n                        e: GlideException?,\n                        model: Any?,\n                        target: Target<Drawable?>,\n                        isFirstResource: Boolean\n                    ): Boolean {\n                        onLoadFinish.invoke()\n                        return false\n                    }\n\n                    override fun onResourceReady(\n                        resource: Drawable,\n                        model: Any,\n                        target: Target<Drawable?>?,\n                        dataSource: DataSource,\n                        isFirstResource: Boolean\n                    ): Boolean {\n                        onLoadFinish.invoke()\n                        return false\n                    }\n                })\n            }\n            builder\n                .centerCrop()\n                .into(this)\n        }\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/widget/image/FilletImageView.kt",
    "content": "package io.legado.app.ui.widget.image\n\nimport android.annotation.SuppressLint\nimport android.content.Context\nimport android.graphics.Canvas\nimport android.graphics.Path\nimport android.util.AttributeSet\nimport androidx.appcompat.widget.AppCompatImageView\nimport io.legado.app.R\nimport io.legado.app.utils.dpToPx\nimport kotlin.math.max\n\nclass FilletImageView @JvmOverloads constructor(\n    context: Context,\n    attrs: AttributeSet? = null\n) : AppCompatImageView(context, attrs) {\n    internal var width: Float = 0.toFloat()\n    internal var height: Float = 0.toFloat()\n    private var leftTopRadius: Int = 0\n    private var rightTopRadius: Int = 0\n    private var rightBottomRadius: Int = 0\n    private var leftBottomRadius: Int = 0\n\n    init {\n        // 读取配置\n        val array = context.obtainStyledAttributes(attrs, R.styleable.FilletImageView)\n        val defaultRadius = 5.dpToPx()\n        val radius =\n            array.getDimensionPixelOffset(R.styleable.FilletImageView_radius, defaultRadius)\n        leftTopRadius = array.getDimensionPixelOffset(\n            R.styleable.FilletImageView_left_top_radius,\n            defaultRadius\n        )\n        rightTopRadius = array.getDimensionPixelOffset(\n            R.styleable.FilletImageView_right_top_radius,\n            defaultRadius\n        )\n        rightBottomRadius =\n            array.getDimensionPixelOffset(\n                R.styleable.FilletImageView_right_bottom_radius,\n                defaultRadius\n            )\n        leftBottomRadius = array.getDimensionPixelOffset(\n            R.styleable.FilletImageView_left_bottom_radius,\n            defaultRadius\n        )\n\n        //如果四个角的值没有设置，那么就使用通用的radius的值。\n        if (defaultRadius == leftTopRadius) {\n            leftTopRadius = radius\n        }\n        if (defaultRadius == rightTopRadius) {\n            rightTopRadius = radius\n        }\n        if (defaultRadius == rightBottomRadius) {\n            rightBottomRadius = radius\n        }\n        if (defaultRadius == leftBottomRadius) {\n            leftBottomRadius = radius\n        }\n        array.recycle()\n    }\n\n    override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {\n        super.onLayout(changed, left, top, right, bottom)\n        width = getWidth().toFloat()\n        height = getHeight().toFloat()\n    }\n\n    override fun onDraw(canvas: Canvas) {\n        //这里做下判断，只有图片的宽高大于设置的圆角距离的时候才进行裁剪\n        val maxLeft = max(leftTopRadius, leftBottomRadius)\n        val maxRight = max(rightTopRadius, rightBottomRadius)\n        val minWidth = maxLeft + maxRight\n        val maxTop = max(leftTopRadius, rightTopRadius)\n        val maxBottom = max(leftBottomRadius, rightBottomRadius)\n        val minHeight = maxTop + maxBottom\n        if (width >= minWidth && height > minHeight) {\n            @SuppressLint(\"DrawAllocation\") val path = Path()\n            //四个角：右上，右下，左下，左上\n            path.moveTo(leftTopRadius.toFloat(), 0f)\n            path.lineTo(width - rightTopRadius, 0f)\n            path.quadTo(width, 0f, width, rightTopRadius.toFloat())\n\n            path.lineTo(width, height - rightBottomRadius)\n            path.quadTo(width, height, width - rightBottomRadius, height)\n\n            path.lineTo(leftBottomRadius.toFloat(), height)\n            path.quadTo(0f, height, 0f, height - leftBottomRadius)\n\n            path.lineTo(0f, leftTopRadius.toFloat())\n            path.quadTo(0f, 0f, leftTopRadius.toFloat(), 0f)\n\n            canvas.clipPath(path)\n        }\n        super.onDraw(canvas)\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/widget/image/ImageButton.kt",
    "content": "package io.legado.app.ui.widget.image\n\nimport android.content.Context\nimport android.util.AttributeSet\nimport androidx.appcompat.widget.AppCompatImageView\n\nclass ImageButton @JvmOverloads constructor(\n    context: Context,\n    attrs: AttributeSet? = null\n) : AppCompatImageView(context, attrs) {\n\n\n    override fun setEnabled(enabled: Boolean) {\n        if (isEnabled != enabled) {\n            imageAlpha = if (enabled) 0xFF else 0x3F\n        }\n        super.setEnabled(enabled)\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/widget/image/PhotoView.kt",
    "content": "package io.legado.app.ui.widget.image\n\nimport android.annotation.SuppressLint\nimport android.content.Context\nimport android.graphics.Canvas\nimport android.graphics.Matrix\nimport android.graphics.PointF\nimport android.graphics.RectF\nimport android.graphics.drawable.Drawable\nimport android.util.AttributeSet\nimport android.view.GestureDetector\nimport android.view.GestureDetector.SimpleOnGestureListener\nimport android.view.MotionEvent\nimport android.view.ScaleGestureDetector\nimport android.view.ScaleGestureDetector.OnScaleGestureListener\nimport android.view.View\nimport android.view.ViewGroup\nimport android.view.ViewParent\nimport android.view.animation.DecelerateInterpolator\nimport android.view.animation.Interpolator\nimport android.widget.ImageView\nimport android.widget.OverScroller\nimport android.widget.Scroller\nimport androidx.appcompat.widget.AppCompatImageView\nimport io.legado.app.ui.widget.image.photo.Info\nimport io.legado.app.ui.widget.image.photo.OnRotateListener\nimport io.legado.app.ui.widget.image.photo.RotateGestureDetector\nimport kotlin.math.abs\nimport kotlin.math.roundToInt\n\n@Suppress(\"UNUSED_PARAMETER\", \"unused\", \"MemberVisibilityCanBePrivate\", \"PropertyName\")\nclass PhotoView @JvmOverloads constructor(\n    context: Context,\n    attrs: AttributeSet? = null\n) : AppCompatImageView(context, attrs) {\n    val MIN_ROTATE = 35\n    val ANIMA_DURING = 340\n    val MAX_SCALE = 2.5f\n\n    private var mMinRotate = 0\n    var mAnimaDuring = 0\n    private var mMaxScale = 0f\n\n    var MAX_OVER_SCROLL = 0\n    var MAX_FLING_OVER_SCROLL = 0\n    var MAX_OVER_RESISTANCE = 0\n    var MAX_ANIM_FROM_WAITE = 500\n\n    private val mBaseMatrix: Matrix = Matrix()\n    private val mAnimMatrix: Matrix = Matrix()\n    private val mSynthesisMatrix: Matrix = Matrix()\n    private val mTmpMatrix: Matrix = Matrix()\n\n    private val mRotateDetector: RotateGestureDetector\n    private val mDetector: GestureDetector\n    private val mScaleDetector: ScaleGestureDetector\n    private var mClickListener: OnClickListener? = null\n\n    private var mScaleType: ScaleType? = null\n\n    private var hasMultiTouch = false\n    private var hasDrawable = false\n    private var isKnowSize = false\n    private var hasOverTranslate = false\n\n    //缩放\n    var isEnable = true\n\n    //旋转\n    var isRotateEnable = false\n    private var isInit = false\n    private var mAdjustViewBounds = false\n\n    // 当前是否处于放大状态\n    private var isZoonUp = false\n    private var canRotate = false\n\n    private var imgLargeWidth = false\n    private var imgLargeHeight = false\n\n    private var mRotateFlag = 0f\n    private var mDegrees = 0f\n    private var mScale = 1.0f\n    private var mTranslateX = 0\n    private var mTranslateY = 0\n\n    private var mHalfBaseRectWidth = 0f\n    private var mHalfBaseRectHeight = 0f\n\n    private val mWidgetRect = RectF()\n    private val mBaseRect = RectF()\n    private val mImgRect = RectF()\n    private val mTmpRect = RectF()\n    private val mCommonRect = RectF()\n\n    private val mScreenCenter = PointF()\n    private val mScaleCenter = PointF()\n    private val mRotateCenter = PointF()\n\n    private val mTranslate: Transform = Transform()\n\n    private var mClip: RectF? = null\n    private var mFromInfo: Info? = null\n    private var mInfoTime: Long = 0\n    private var mCompleteCallBack: Runnable? = null\n\n    private var mLongClick: OnLongClickListener? = null\n\n    private val mRotateListener = RotateListener()\n    private val mGestureListener = GestureListener()\n    private val mScaleListener = ScaleGestureListener()\n\n    init {\n        super.setScaleType(ScaleType.MATRIX)\n        if (mScaleType == null) mScaleType = ScaleType.CENTER_INSIDE\n        mRotateDetector = RotateGestureDetector(mRotateListener)\n        mDetector = GestureDetector(context, mGestureListener)\n        mScaleDetector = ScaleGestureDetector(context, mScaleListener)\n        val density = resources.displayMetrics.density\n        MAX_OVER_SCROLL = (density * 30).toInt()\n        MAX_FLING_OVER_SCROLL = (density * 30).toInt()\n        MAX_OVER_RESISTANCE = (density * 140).toInt()\n        mMinRotate = MIN_ROTATE\n        mAnimaDuring = ANIMA_DURING\n        mMaxScale = MAX_SCALE\n    }\n\n    /**\n     * 获取默认的动画持续时间\n     */\n    fun getDefaultAnimDuring(): Int {\n        return ANIMA_DURING\n    }\n\n    override fun setOnClickListener(l: OnClickListener?) {\n        super.setOnClickListener(l)\n        mClickListener = l\n    }\n\n    override fun setScaleType(scaleType: ScaleType) {\n        if (scaleType == ScaleType.MATRIX) return\n        if (scaleType != mScaleType) {\n            mScaleType = scaleType\n            if (isInit) {\n                initBase()\n            }\n        }\n    }\n\n    override fun setOnLongClickListener(l: OnLongClickListener?) {\n        mLongClick = l\n    }\n\n    /**\n     * 设置动画的插入器\n     */\n    fun setInterpolator(interpolator: Interpolator?) {\n        mTranslate.setInterpolator(interpolator)\n    }\n\n    /**\n     * 获取动画持续时间\n     */\n    fun getAnimDuring(): Int {\n        return mAnimaDuring\n    }\n\n    /**\n     * 设置动画的持续时间\n     */\n    fun setAnimDuring(during: Int) {\n        mAnimaDuring = during\n    }\n\n    /**\n     * 设置最大可以缩放的倍数\n     */\n    fun setMaxScale(maxScale: Float) {\n        mMaxScale = maxScale\n    }\n\n    /**\n     * 获取最大可以缩放的倍数\n     */\n    fun getMaxScale(): Float {\n        return mMaxScale\n    }\n\n    /**\n     */\n    fun setMaxAnimFromWaiteTime(wait: Int) {\n        MAX_ANIM_FROM_WAITE = wait\n    }\n\n    @SuppressLint(\"UseCompatLoadingForDrawables\")\n    override fun setImageResource(resId: Int) {\n        val drawable: Drawable? = kotlin.runCatching {\n            resources.getDrawable(resId, null)\n        }.getOrNull()\n        setImageDrawable(drawable)\n    }\n\n    override fun setImageDrawable(drawable: Drawable?) {\n        super.setImageDrawable(drawable)\n        if (drawable == null) {\n            hasDrawable = false\n            return\n        }\n        if (!hasSize(drawable)) return\n        if (!hasDrawable) {\n            hasDrawable = true\n        }\n        initBase()\n    }\n\n    private fun hasSize(d: Drawable): Boolean {\n        return !((d.intrinsicHeight <= 0 || d.intrinsicWidth <= 0)\n                && (d.minimumWidth <= 0 || d.minimumHeight <= 0)\n                && (d.bounds.width() <= 0 || d.bounds.height() <= 0))\n    }\n\n    private fun getDrawableWidth(d: Drawable): Int {\n        var width = d.intrinsicWidth\n        if (width <= 0) width = d.minimumWidth\n        if (width <= 0) width = d.bounds.width()\n        return width\n    }\n\n    private fun getDrawableHeight(d: Drawable): Int {\n        var height = d.intrinsicHeight\n        if (height <= 0) height = d.minimumHeight\n        if (height <= 0) height = d.bounds.height()\n        return height\n    }\n\n    private fun initBase() {\n        if (!hasDrawable) return\n        if (!isKnowSize) return\n        mBaseMatrix.reset()\n        mAnimMatrix.reset()\n        isZoonUp = false\n        val img = drawable\n        val w = width\n        val h = height\n        val imgW = getDrawableWidth(img)\n        val imgH = getDrawableHeight(img)\n        mBaseRect[0f, 0f, imgW.toFloat()] = imgH.toFloat()\n\n        // 以图片中心点居中位移\n        val tx = (w - imgW) / 2\n        val ty = (h - imgH) / 2\n        var sx = 1f\n        var sy = 1f\n\n        // 缩放，默认不超过屏幕大小\n        if (imgW > w) {\n            sx = w.toFloat() / imgW\n        }\n        if (imgH > h) {\n            sy = h.toFloat() / imgH\n        }\n        val scale = if (sx < sy) sx else sy\n        mBaseMatrix.reset()\n        mBaseMatrix.postTranslate(tx.toFloat(), ty.toFloat())\n        mBaseMatrix.postScale(scale, scale, mScreenCenter.x, mScreenCenter.y)\n        mBaseMatrix.mapRect(mBaseRect)\n        mHalfBaseRectWidth = mBaseRect.width() / 2\n        mHalfBaseRectHeight = mBaseRect.height() / 2\n        mScaleCenter.set(mScreenCenter)\n        mRotateCenter.set(mScaleCenter)\n        executeTranslate()\n        when (mScaleType) {\n            ScaleType.CENTER -> initCenter()\n            ScaleType.CENTER_CROP -> initCenterCrop()\n            ScaleType.CENTER_INSIDE -> initCenterInside()\n            ScaleType.FIT_CENTER -> initFitCenter()\n            ScaleType.FIT_START -> initFitStart()\n            ScaleType.FIT_END -> initFitEnd()\n            ScaleType.FIT_XY -> initFitXY()\n            else -> {\n            }\n        }\n        isInit = true\n        mFromInfo?.let {\n            if (System.currentTimeMillis() - mInfoTime < MAX_ANIM_FROM_WAITE) {\n                animaFrom(it)\n            }\n        }\n        mFromInfo = null\n    }\n\n    private fun initCenter() {\n        if (!hasDrawable) return\n        if (!isKnowSize) return\n        val img = drawable\n        val imgW = getDrawableWidth(img)\n        val imgH = getDrawableHeight(img)\n        if (imgW > mWidgetRect.width() || imgH > mWidgetRect.height()) {\n            val scaleX = imgW / mImgRect.width()\n            val scaleY = imgH / mImgRect.height()\n            mScale = if (scaleX > scaleY) scaleX else scaleY\n            mAnimMatrix.postScale(mScale, mScale, mScreenCenter.x, mScreenCenter.y)\n            executeTranslate()\n            resetBase()\n        }\n    }\n\n    private fun initCenterCrop() {\n        if (mImgRect.width() < mWidgetRect.width() || mImgRect.height() < mWidgetRect.height()) {\n            val scaleX = mWidgetRect.width() / mImgRect.width()\n            val scaleY = mWidgetRect.height() / mImgRect.height()\n            mScale = if (scaleX > scaleY) scaleX else scaleY\n            mAnimMatrix.postScale(mScale, mScale, mScreenCenter.x, mScreenCenter.y)\n            executeTranslate()\n            resetBase()\n        }\n    }\n\n    private fun initCenterInside() {\n        if (mImgRect.width() > mWidgetRect.width() || mImgRect.height() > mWidgetRect.height()) {\n            val scaleX = mWidgetRect.width() / mImgRect.width()\n            val scaleY = mWidgetRect.height() / mImgRect.height()\n            mScale = if (scaleX < scaleY) scaleX else scaleY\n            mAnimMatrix.postScale(mScale, mScale, mScreenCenter.x, mScreenCenter.y)\n            executeTranslate()\n            resetBase()\n        }\n    }\n\n    private fun initFitCenter() {\n        if (mImgRect.width() < mWidgetRect.width()) {\n            mScale = mWidgetRect.width() / mImgRect.width()\n            mAnimMatrix.postScale(mScale, mScale, mScreenCenter.x, mScreenCenter.y)\n            executeTranslate()\n            resetBase()\n        }\n    }\n\n    private fun initFitStart() {\n        initFitCenter()\n        val ty = -mImgRect.top\n        mAnimMatrix.postTranslate(0f, ty)\n        executeTranslate()\n        resetBase()\n        mTranslateY += ty.toInt()\n    }\n\n    private fun initFitEnd() {\n        initFitCenter()\n        val ty = mWidgetRect.bottom - mImgRect.bottom\n        mTranslateY += ty.toInt()\n        mAnimMatrix.postTranslate(0f, ty)\n        executeTranslate()\n        resetBase()\n    }\n\n    private fun initFitXY() {\n        val scaleX = mWidgetRect.width() / mImgRect.width()\n        val scaleY = mWidgetRect.height() / mImgRect.height()\n        mAnimMatrix.postScale(scaleX, scaleY, mScreenCenter.x, mScreenCenter.y)\n        executeTranslate()\n        resetBase()\n    }\n\n    private fun resetBase() {\n        val img = drawable\n        val imgW = getDrawableWidth(img)\n        val imgH = getDrawableHeight(img)\n        mBaseRect[0f, 0f, imgW.toFloat()] = imgH.toFloat()\n        mBaseMatrix.set(mSynthesisMatrix)\n        mBaseMatrix.mapRect(mBaseRect)\n        mHalfBaseRectWidth = mBaseRect.width() / 2\n        mHalfBaseRectHeight = mBaseRect.height() / 2\n        mScale = 1f\n        mTranslateX = 0\n        mTranslateY = 0\n        mAnimMatrix.reset()\n    }\n\n    private fun executeTranslate() {\n        mSynthesisMatrix.set(mBaseMatrix)\n        mSynthesisMatrix.postConcat(mAnimMatrix)\n        imageMatrix = mSynthesisMatrix\n        mAnimMatrix.mapRect(mImgRect, mBaseRect)\n        imgLargeWidth = mImgRect.width() > mWidgetRect.width()\n        imgLargeHeight = mImgRect.height() > mWidgetRect.height()\n    }\n\n    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {\n        if (!hasDrawable) {\n            super.onMeasure(widthMeasureSpec, heightMeasureSpec)\n            return\n        }\n        val d = drawable\n        val drawableW = getDrawableWidth(d)\n        val drawableH = getDrawableHeight(d)\n        val pWidth = MeasureSpec.getSize(widthMeasureSpec)\n        val pHeight = MeasureSpec.getSize(heightMeasureSpec)\n        val widthMode = MeasureSpec.getMode(widthMeasureSpec)\n        val heightMode = MeasureSpec.getMode(heightMeasureSpec)\n        var width: Int\n        var height: Int\n        var p = layoutParams\n        if (p == null) {\n            p = ViewGroup.LayoutParams(\n                ViewGroup.LayoutParams.WRAP_CONTENT,\n                ViewGroup.LayoutParams.WRAP_CONTENT\n            )\n        }\n        width = if (p.width == ViewGroup.LayoutParams.MATCH_PARENT) {\n            if (widthMode == MeasureSpec.UNSPECIFIED) {\n                drawableW\n            } else {\n                pWidth\n            }\n        } else {\n            if (widthMode == MeasureSpec.EXACTLY) {\n                pWidth\n            } else if (widthMode == MeasureSpec.AT_MOST) {\n                if (drawableW > pWidth) pWidth else drawableW\n            } else {\n                drawableW\n            }\n        }\n        height = if (p.height == ViewGroup.LayoutParams.MATCH_PARENT) {\n            if (heightMode == MeasureSpec.UNSPECIFIED) {\n                drawableH\n            } else {\n                pHeight\n            }\n        } else {\n            if (heightMode == MeasureSpec.EXACTLY) {\n                pHeight\n            } else if (heightMode == MeasureSpec.AT_MOST) {\n                if (drawableH > pHeight) pHeight else drawableH\n            } else {\n                drawableH\n            }\n        }\n        if (mAdjustViewBounds && drawableW.toFloat() / drawableH != width.toFloat() / height) {\n            val hScale = height.toFloat() / drawableH\n            val wScale = width.toFloat() / drawableW\n            val scale = if (hScale < wScale) hScale else wScale\n            width =\n                if (p.width == ViewGroup.LayoutParams.MATCH_PARENT) width else (drawableW * scale).toInt()\n            height =\n                if (p.height == ViewGroup.LayoutParams.MATCH_PARENT) height else (drawableH * scale).toInt()\n        }\n        setMeasuredDimension(width, height)\n    }\n\n    override fun setAdjustViewBounds(adjustViewBounds: Boolean) {\n        super.setAdjustViewBounds(adjustViewBounds)\n        mAdjustViewBounds = adjustViewBounds\n    }\n\n    override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {\n        super.onSizeChanged(w, h, oldw, oldh)\n        mWidgetRect[0f, 0f, w.toFloat()] = h.toFloat()\n        mScreenCenter[w / 2.toFloat()] = h / 2.toFloat()\n        if (!isKnowSize) {\n            isKnowSize = true\n            initBase()\n        }\n    }\n\n    override fun draw(canvas: Canvas) {\n        mClip?.let {\n            canvas.clipRect(it)\n            mClip = null\n        }\n        super.draw(canvas)\n    }\n\n    override fun dispatchTouchEvent(event: MotionEvent): Boolean {\n        return if (isEnable) {\n            val action = event.actionMasked\n            if (event.pointerCount >= 2) hasMultiTouch = true\n            mDetector.onTouchEvent(event)\n            if (isRotateEnable) {\n                mRotateDetector.onTouchEvent(event)\n            }\n            mScaleDetector.onTouchEvent(event)\n            if (action == MotionEvent.ACTION_UP || action == MotionEvent.ACTION_CANCEL) onUp()\n            true\n        } else {\n            super.dispatchTouchEvent(event)\n        }\n    }\n\n    private fun onUp() {\n        if (mTranslate.isRunning) return\n        if (canRotate || mDegrees % 90 != 0f) {\n            var toDegrees = (mDegrees / 90).toInt() * 90.toFloat()\n            val remainder = mDegrees % 90\n            if (remainder > 45) toDegrees += 90f else if (remainder < -45) toDegrees -= 90f\n            mTranslate.withRotate(mDegrees.toInt(), toDegrees.toInt())\n            mDegrees = toDegrees\n        }\n        var scale = mScale\n        if (mScale < 1) {\n            scale = 1f\n            mTranslate.withScale(mScale, 1F)\n        } else if (mScale > mMaxScale) {\n            scale = mMaxScale\n            mTranslate.withScale(mScale, mMaxScale)\n        }\n        val cx = mImgRect.left + mImgRect.width() / 2\n        val cy = mImgRect.top + mImgRect.height() / 2\n        mScaleCenter[cx] = cy\n        mRotateCenter[cx] = cy\n        mTranslateX = 0\n        mTranslateY = 0\n        mTmpMatrix.reset()\n        mTmpMatrix.postTranslate(-mBaseRect.left, -mBaseRect.top)\n        mTmpMatrix.postTranslate(cx - mHalfBaseRectWidth, cy - mHalfBaseRectHeight)\n        mTmpMatrix.postScale(scale, scale, cx, cy)\n        mTmpMatrix.postRotate(mDegrees, cx, cy)\n        mTmpMatrix.mapRect(mTmpRect, mBaseRect)\n        doTranslateReset(mTmpRect)\n        mTranslate.start()\n    }\n\n    private fun doTranslateReset(imgRect: RectF) {\n        var tx = 0\n        var ty = 0\n        if (imgRect.width() <= mWidgetRect.width()) {\n            if (!isImageCenterWidth(imgRect)) tx =\n                (-((mWidgetRect.width() - imgRect.width()) / 2 - imgRect.left)).toInt()\n        } else {\n            if (imgRect.left > mWidgetRect.left) {\n                tx = (imgRect.left - mWidgetRect.left).toInt()\n            } else if (imgRect.right < mWidgetRect.right) {\n                tx = (imgRect.right - mWidgetRect.right).toInt()\n            }\n        }\n        if (imgRect.height() <= mWidgetRect.height()) {\n            if (!isImageCenterHeight(imgRect)) ty =\n                (-((mWidgetRect.height() - imgRect.height()) / 2 - imgRect.top)).toInt()\n        } else {\n            if (imgRect.top > mWidgetRect.top) {\n                ty = (imgRect.top - mWidgetRect.top).toInt()\n            } else if (imgRect.bottom < mWidgetRect.bottom) {\n                ty = (imgRect.bottom - mWidgetRect.bottom).toInt()\n            }\n        }\n        if (tx != 0 || ty != 0) {\n            if (!mTranslate.mFlingScroller.isFinished) mTranslate.mFlingScroller.abortAnimation()\n            mTranslate.withTranslate(mTranslateX, mTranslateY, -tx, -ty)\n        }\n    }\n\n    private fun isImageCenterHeight(rect: RectF): Boolean {\n        return abs(rect.top.roundToInt() - (mWidgetRect.height() - rect.height()) / 2) < 1\n    }\n\n    private fun isImageCenterWidth(rect: RectF): Boolean {\n        return abs(rect.left.roundToInt() - (mWidgetRect.width() - rect.width()) / 2) < 1\n    }\n\n    private fun resistanceScrollByX(\n        overScroll: Float,\n        detalX: Float\n    ): Float {\n        return detalX * (abs(abs(overScroll) - MAX_OVER_RESISTANCE) / MAX_OVER_RESISTANCE.toFloat())\n    }\n\n    private fun resistanceScrollByY(\n        overScroll: Float,\n        detalY: Float\n    ): Float {\n        return detalY * (abs(abs(overScroll) - MAX_OVER_RESISTANCE) / MAX_OVER_RESISTANCE.toFloat())\n    }\n\n    /**\n     * 匹配两个Rect的共同部分输出到out，若无共同部分则输出0，0，0，0\n     */\n    private fun mapRect(r1: RectF, r2: RectF, out: RectF) {\n        val l: Float = if (r1.left > r2.left) r1.left else r2.left\n        val r: Float = if (r1.right < r2.right) r1.right else r2.right\n        if (l > r) {\n            out[0f, 0f, 0f] = 0f\n            return\n        }\n        val t: Float = if (r1.top > r2.top) r1.top else r2.top\n        val b: Float = if (r1.bottom < r2.bottom) r1.bottom else r2.bottom\n        if (t > b) {\n            out[0f, 0f, 0f] = 0f\n            return\n        }\n        out[l, t, r] = b\n    }\n\n    private fun checkRect() {\n        if (!hasOverTranslate) {\n            mapRect(mWidgetRect, mImgRect, mCommonRect)\n        }\n    }\n\n    private val mClickRunnable = Runnable {\n        mClickListener?.onClick(this)\n    }\n\n    fun canScrollHorizontallySelf(direction: Float): Boolean {\n        if (mImgRect.width() <= mWidgetRect.width())\n            return false\n        if (direction < 0 && mImgRect.left.roundToInt() - direction >= mWidgetRect.left)\n            return false\n        return !(direction > 0 && mImgRect.right.roundToInt() - direction <= mWidgetRect.right)\n    }\n\n    fun canScrollVerticallySelf(direction: Float): Boolean {\n        if (mImgRect.height() <= mWidgetRect.height())\n            return false\n        if (direction < 0 && mImgRect.top.roundToInt() - direction >= mWidgetRect.top)\n            return false\n        return !(direction > 0 && mImgRect.bottom.roundToInt() - direction <= mWidgetRect.bottom)\n    }\n\n    override fun canScrollHorizontally(direction: Int): Boolean {\n        return if (hasMultiTouch) true else canScrollHorizontallySelf(direction.toFloat())\n    }\n\n    override fun canScrollVertically(direction: Int): Boolean {\n        return if (hasMultiTouch) true else canScrollVerticallySelf(direction.toFloat())\n    }\n\n    private inner class InterpolatorProxy : Interpolator {\n        private var mTarget: Interpolator?\n\n        init {\n            mTarget = DecelerateInterpolator()\n        }\n\n        fun setTargetInterpolator(interpolator: Interpolator?) {\n            mTarget = interpolator\n        }\n\n        override fun getInterpolation(input: Float): Float {\n            return mTarget?.getInterpolation(input) ?: input\n        }\n\n    }\n\n    private inner class Transform : Runnable {\n        var isRunning = false\n        var mTranslateScroller: OverScroller\n        var mFlingScroller: OverScroller\n        var mScaleScroller: Scroller\n        var mClipScroller: Scroller\n        var mRotateScroller: Scroller\n        var c: ClipCalculate? = null\n        var mLastFlingX = 0\n        var mLastFlingY = 0\n        var mLastTranslateX = 0\n        var mLastTranslateY = 0\n        var mClipRect = RectF()\n        var mInterpolatorProxy = InterpolatorProxy()\n\n        fun setInterpolator(interpolator: Interpolator?) {\n            mInterpolatorProxy.setTargetInterpolator(interpolator)\n        }\n\n        init {\n            val ctx: Context = context\n            mTranslateScroller = OverScroller(ctx, mInterpolatorProxy)\n            mScaleScroller = Scroller(ctx, mInterpolatorProxy)\n            mFlingScroller = OverScroller(ctx, mInterpolatorProxy)\n            mClipScroller = Scroller(ctx, mInterpolatorProxy)\n            mRotateScroller = Scroller(ctx, mInterpolatorProxy)\n        }\n\n        fun withTranslate(startX: Int, startY: Int, deltaX: Int, deltaY: Int) {\n            mLastTranslateX = 0\n            mLastTranslateY = 0\n            mTranslateScroller.startScroll(0, 0, deltaX, deltaY, mAnimaDuring)\n        }\n\n        fun withScale(form: Float, to: Float) {\n            mScaleScroller.startScroll(\n                (form * 10000).toInt(),\n                0,\n                ((to - form) * 10000).toInt(),\n                0,\n                mAnimaDuring\n            )\n        }\n\n        fun withClip(\n            fromX: Float,\n            fromY: Float,\n            deltaX: Float,\n            deltaY: Float,\n            d: Int,\n            c: ClipCalculate?\n        ) {\n            mClipScroller.startScroll(\n                (fromX * 10000).toInt(),\n                (fromY * 10000).toInt(),\n                (deltaX * 10000).toInt(),\n                (deltaY * 10000).toInt(),\n                d\n            )\n            this.c = c\n        }\n\n        fun withRotate(fromDegrees: Int, toDegrees: Int) {\n            mRotateScroller.startScroll(fromDegrees, 0, toDegrees - fromDegrees, 0, mAnimaDuring)\n        }\n\n        fun withRotate(fromDegrees: Int, toDegrees: Int, during: Int) {\n            mRotateScroller.startScroll(fromDegrees, 0, toDegrees - fromDegrees, 0, during)\n        }\n\n        fun withFling(velocityX: Float, velocityY: Float) {\n            mLastFlingX = if (velocityX < 0) Int.MAX_VALUE else 0\n            var distanceX =\n                (if (velocityX > 0) abs(mImgRect.left) else mImgRect.right - mWidgetRect.right).toInt()\n            distanceX = if (velocityX < 0) Int.MAX_VALUE - distanceX else distanceX\n            var minX = if (velocityX < 0) distanceX else 0\n            var maxX = if (velocityX < 0) Int.MAX_VALUE else distanceX\n            val overX = if (velocityX < 0) Int.MAX_VALUE - minX else distanceX\n            mLastFlingY = if (velocityY < 0) Int.MAX_VALUE else 0\n            var distanceY =\n                (if (velocityY > 0) abs(mImgRect.top) else mImgRect.bottom - mWidgetRect.bottom).toInt()\n            distanceY = if (velocityY < 0) Int.MAX_VALUE - distanceY else distanceY\n            var minY = if (velocityY < 0) distanceY else 0\n            var maxY = if (velocityY < 0) Int.MAX_VALUE else distanceY\n            val overY = if (velocityY < 0) Int.MAX_VALUE - minY else distanceY\n            if (velocityX == 0f) {\n                maxX = 0\n                minX = 0\n            }\n            if (velocityY == 0f) {\n                maxY = 0\n                minY = 0\n            }\n            mFlingScroller.fling(\n                mLastFlingX,\n                mLastFlingY,\n                velocityX.toInt(),\n                velocityY.toInt(),\n                minX,\n                maxX,\n                minY,\n                maxY,\n                if (abs(overX) < MAX_FLING_OVER_SCROLL * 2) 0 else MAX_FLING_OVER_SCROLL,\n                if (abs(overY) < MAX_FLING_OVER_SCROLL * 2) 0 else MAX_FLING_OVER_SCROLL\n            )\n        }\n\n        fun start() {\n            isRunning = true\n            postExecute()\n        }\n\n        fun stop() {\n            removeCallbacks(this)\n            mTranslateScroller.abortAnimation()\n            mScaleScroller.abortAnimation()\n            mFlingScroller.abortAnimation()\n            mRotateScroller.abortAnimation()\n            isRunning = false\n        }\n\n        override fun run() {\n\n            // if (!isRuning) return;\n            var endAnima = true\n            if (mScaleScroller.computeScrollOffset()) {\n                mScale = mScaleScroller.currX / 10000f\n                endAnima = false\n            }\n            if (mTranslateScroller.computeScrollOffset()) {\n                val tx = mTranslateScroller.currX - mLastTranslateX\n                val ty = mTranslateScroller.currY - mLastTranslateY\n                mTranslateX += tx\n                mTranslateY += ty\n                mLastTranslateX = mTranslateScroller.currX\n                mLastTranslateY = mTranslateScroller.currY\n                endAnima = false\n            }\n            if (mFlingScroller.computeScrollOffset()) {\n                val x = mFlingScroller.currX - mLastFlingX\n                val y = mFlingScroller.currY - mLastFlingY\n                mLastFlingX = mFlingScroller.currX\n                mLastFlingY = mFlingScroller.currY\n                mTranslateX += x\n                mTranslateY += y\n                endAnima = false\n            }\n            if (mRotateScroller.computeScrollOffset()) {\n                mDegrees = mRotateScroller.currX.toFloat()\n                endAnima = false\n            }\n            if (mClipScroller.computeScrollOffset() || mClip != null) {\n                val sx = mClipScroller.currX / 10000f\n                val sy = mClipScroller.currY / 10000f\n                mTmpMatrix.setScale(\n                    sx,\n                    sy,\n                    (mImgRect.left + mImgRect.right) / 2,\n                    c!!.calculateTop()\n                )\n                mTmpMatrix.mapRect(mClipRect, mImgRect)\n                if (sx == 1f) {\n                    mClipRect.left = mWidgetRect.left\n                    mClipRect.right = mWidgetRect.right\n                }\n                if (sy == 1f) {\n                    mClipRect.top = mWidgetRect.top\n                    mClipRect.bottom = mWidgetRect.bottom\n                }\n                mClip = mClipRect\n            }\n            if (!endAnima) {\n                applyAnima()\n                postExecute()\n            } else {\n                isRunning = false\n\n                // 修复动画结束后边距有些空隙，\n                var needFix = false\n                if (imgLargeWidth) {\n                    if (mImgRect.left > 0) {\n                        mTranslateX -= mImgRect.left.toInt()\n                    } else if (mImgRect.right < mWidgetRect.width()) {\n                        mTranslateX -= (mWidgetRect.width() - mImgRect.right).toInt()\n                    }\n                    needFix = true\n                }\n                if (imgLargeHeight) {\n                    if (mImgRect.top > 0) {\n                        mTranslateY -= mImgRect.top.toInt()\n                    } else if (mImgRect.bottom < mWidgetRect.height()) {\n                        mTranslateY -= (mWidgetRect.height() - mImgRect.bottom).toInt()\n                    }\n                    needFix = true\n                }\n                if (needFix) {\n                    applyAnima()\n                }\n                invalidate()\n                mCompleteCallBack?.let {\n                    it.run()\n                    mCompleteCallBack = null\n                }\n            }\n        }\n\n        private fun applyAnima() {\n            mAnimMatrix.reset()\n            mAnimMatrix.postTranslate(-mBaseRect.left, -mBaseRect.top)\n            mAnimMatrix.postTranslate(mRotateCenter.x, mRotateCenter.y)\n            mAnimMatrix.postTranslate(-mHalfBaseRectWidth, -mHalfBaseRectHeight)\n            mAnimMatrix.postRotate(mDegrees, mRotateCenter.x, mRotateCenter.y)\n            mAnimMatrix.postScale(mScale, mScale, mScaleCenter.x, mScaleCenter.y)\n            mAnimMatrix.postTranslate(mTranslateX.toFloat(), mTranslateY.toFloat())\n            executeTranslate()\n        }\n\n        private fun postExecute() {\n            if (isRunning) post(this)\n        }\n\n    }\n\n    fun getInfo(): Info {\n        val rect = RectF()\n        val p = IntArray(2)\n        getLocation(this, p)\n        rect[p[0] + mImgRect.left, p[1] + mImgRect.top, p[0] + mImgRect.right] =\n            p[1] + mImgRect.bottom\n        return Info(\n            rect,\n            mImgRect,\n            mWidgetRect,\n            mBaseRect,\n            mScreenCenter,\n            mScale,\n            mDegrees,\n            mScaleType\n        )\n    }\n\n    fun getImageViewInfo(imgView: ImageView): Info {\n        val p = IntArray(2)\n        getLocation(imgView, p)\n        val drawable: Drawable = imgView.drawable\n        val matrix: Matrix = imgView.imageMatrix\n        val width = getDrawableWidth(drawable)\n        val height = getDrawableHeight(drawable)\n        val imgRect = RectF(0F, 0F, width.toFloat(), height.toFloat())\n        matrix.mapRect(imgRect)\n        val rect = RectF(\n            p[0] + imgRect.left,\n            p[1] + imgRect.top,\n            p[0] + imgRect.right,\n            p[1] + imgRect.bottom\n        )\n        val widgetRect = RectF(0F, 0F, imgView.width.toFloat(), imgView.height.toFloat())\n        val baseRect = RectF(widgetRect)\n        val screenCenter = PointF(widgetRect.width() / 2, widgetRect.height() / 2)\n        return Info(\n            rect,\n            imgRect,\n            widgetRect,\n            baseRect,\n            screenCenter,\n            1F,\n            0F,\n            imgView.scaleType\n        )\n    }\n\n    private fun getLocation(target: View, position: IntArray) {\n        position[0] += target.left\n        position[1] += target.top\n        var viewParent: ViewParent = target.parent\n        while (viewParent is View) {\n            val view: View = viewParent\n            if (view.id == android.R.id.content) return\n            position[0] -= view.scrollX\n            position[1] -= view.scrollY\n            position[0] += view.left\n            position[1] += view.top\n            viewParent = view.parent\n        }\n        position[0] = (position[0] + 0.5f).toInt()\n        position[1] = (position[1] + 0.5f).toInt()\n    }\n\n    private fun reset() {\n        mAnimMatrix.reset()\n        executeTranslate()\n        mScale = 1f\n        mTranslateX = 0\n        mTranslateY = 0\n    }\n\n    interface ClipCalculate {\n        fun calculateTop(): Float\n    }\n\n    inner class START : ClipCalculate {\n        override fun calculateTop(): Float {\n            return mImgRect.top\n        }\n    }\n\n    inner class END : ClipCalculate {\n        override fun calculateTop(): Float {\n            return mImgRect.bottom\n        }\n    }\n\n    inner class OTHER : ClipCalculate {\n        override fun calculateTop(): Float {\n            return (mImgRect.top + mImgRect.bottom) / 2\n        }\n    }\n\n    /**\n     * 在PhotoView内部还没有图片的时候同样可以调用该方法\n     *\n     *\n     * 此时并不会播放动画，当给PhotoView设置图片后会自动播放动画。\n     *\n     *\n     * 若等待时间过长也没有给控件设置图片，则会忽略该动画，若要再次播放动画则需要重新调用该方法\n     * (等待的时间默认500毫秒，可以通过setMaxAnimFromWaiteTime(int)设置最大等待时间)\n     */\n    fun animaFrom(info: Info) {\n        if (isInit) {\n            reset()\n            val mine = getInfo()\n            val scaleX = info.mImgRect.width() / mine.mImgRect.width()\n            val scaleY = info.mImgRect.height() / mine.mImgRect.height()\n            val scale = if (scaleX < scaleY) scaleX else scaleY\n            val ocx = info.mRect.left + info.mRect.width() / 2\n            val ocy = info.mRect.top + info.mRect.height() / 2\n            val mcx = mine.mRect.left + mine.mRect.width() / 2\n            val mcy = mine.mRect.top + mine.mRect.height() / 2\n            mAnimMatrix.reset()\n            // mAnimaMatrix.postTranslate(-mBaseRect.left, -mBaseRect.top);\n            mAnimMatrix.postTranslate(ocx - mcx, ocy - mcy)\n            mAnimMatrix.postScale(scale, scale, ocx, ocy)\n            mAnimMatrix.postRotate(info.mDegrees, ocx, ocy)\n            executeTranslate()\n            mScaleCenter[ocx] = ocy\n            mRotateCenter[ocx] = ocy\n            mTranslate.withTranslate(0, 0, (-(ocx - mcx)).toInt(), (-(ocy - mcy)).toInt())\n            mTranslate.withScale(scale, 1F)\n            mTranslate.withRotate(info.mDegrees.toInt(), 0)\n            if (info.mWidgetRect.width() < info.mImgRect.width() || info.mWidgetRect.height() < info.mImgRect.height()) {\n                var clipX = info.mWidgetRect.width() / info.mImgRect.width()\n                var clipY = info.mWidgetRect.height() / info.mImgRect.height()\n                clipX = if (clipX > 1) 1F else clipX\n                clipY = if (clipY > 1) 1F else clipY\n                val c =\n                    if (info.mScaleType == ScaleType.FIT_START) START() else if (info.mScaleType == ScaleType.FIT_END) END() else OTHER()\n                mTranslate.withClip(clipX, clipY, 1 - clipX, 1 - clipY, mAnimaDuring / 3, c)\n                mTmpMatrix.setScale(\n                    clipX,\n                    clipY,\n                    (mImgRect.left + mImgRect.right) / 2,\n                    c.calculateTop()\n                )\n                mTmpMatrix.mapRect(mTranslate.mClipRect, mImgRect)\n                mClip = mTranslate.mClipRect\n            }\n            mTranslate.start()\n        } else {\n            mFromInfo = info\n            mInfoTime = System.currentTimeMillis()\n        }\n    }\n\n    fun animaTo(\n        info: Info,\n        completeCallBack: Runnable\n    ) {\n        if (isInit) {\n            mTranslate.stop()\n            mTranslateX = 0\n            mTranslateY = 0\n            val tcx = info.mRect.left + info.mRect.width() / 2\n            val tcy = info.mRect.top + info.mRect.height() / 2\n            mScaleCenter[mImgRect.left + mImgRect.width() / 2] =\n                mImgRect.top + mImgRect.height() / 2\n            mRotateCenter.set(mScaleCenter)\n\n            // 将图片旋转回正常位置，用以计算\n            mAnimMatrix.postRotate(-mDegrees, mScaleCenter.x, mScaleCenter.y)\n            mAnimMatrix.mapRect(mImgRect, mBaseRect)\n\n            // 缩放\n            val scaleX = info.mImgRect.width() / mBaseRect.width()\n            val scaleY = info.mImgRect.height() / mBaseRect.height()\n            val scale = if (scaleX > scaleY) scaleX else scaleY\n            mAnimMatrix.postRotate(mDegrees, mScaleCenter.x, mScaleCenter.y)\n            mAnimMatrix.mapRect(mImgRect, mBaseRect)\n            mDegrees %= 360\n            mTranslate.withTranslate(\n                0,\n                0,\n                (tcx - mScaleCenter.x).toInt(),\n                (tcy - mScaleCenter.y).toInt()\n            )\n            mTranslate.withScale(mScale, scale)\n            mTranslate.withRotate(mDegrees.toInt(), info.mDegrees.toInt(), mAnimaDuring * 2 / 3)\n            if (info.mWidgetRect.width() < info.mRect.width() || info.mWidgetRect.height() < info.mRect.height()) {\n                var clipX = info.mWidgetRect.width() / info.mRect.width()\n                var clipY = info.mWidgetRect.height() / info.mRect.height()\n                clipX = if (clipX > 1) 1F else clipX\n                clipY = if (clipY > 1) 1F else clipY\n                val cx = clipX\n                val cy = clipY\n                val c =\n                    if (info.mScaleType == ScaleType.FIT_START) START() else if (info.mScaleType == ScaleType.FIT_END) END() else OTHER()\n                postDelayed(\n                    { mTranslate.withClip(1F, 1F, -1 + cx, -1 + cy, mAnimaDuring / 2, c) },\n                    mAnimaDuring / 2.toLong()\n                )\n            }\n            mCompleteCallBack = completeCallBack\n            mTranslate.start()\n        }\n    }\n\n    fun rotate(degrees: Float) {\n        mDegrees += degrees\n        val centerX = (mWidgetRect.left + mWidgetRect.width() / 2).toInt()\n        val centerY = (mWidgetRect.top + mWidgetRect.height() / 2).toInt()\n        mAnimMatrix.postRotate(degrees, centerX.toFloat(), centerY.toFloat())\n        executeTranslate()\n    }\n\n    inner class RotateListener : OnRotateListener {\n        override fun onRotate(\n            degrees: Float,\n            focusX: Float,\n            focusY: Float\n        ) {\n            mRotateFlag += degrees\n            if (canRotate) {\n                mDegrees += degrees\n                mAnimMatrix.postRotate(degrees, focusX, focusY)\n            } else {\n                if (abs(mRotateFlag) >= mMinRotate) {\n                    canRotate = true\n                    mRotateFlag = 0f\n                }\n            }\n        }\n    }\n\n    inner class GestureListener : SimpleOnGestureListener() {\n        override fun onLongPress(e: MotionEvent) {\n            mLongClick?.onLongClick(this@PhotoView)\n        }\n\n        override fun onDown(e: MotionEvent): Boolean {\n            hasOverTranslate = false\n            hasMultiTouch = false\n            canRotate = false\n            removeCallbacks(mClickRunnable)\n            return false\n        }\n\n        override fun onFling(\n            e1: MotionEvent?,\n            e2: MotionEvent,\n            velocityX: Float,\n            velocityY: Float\n        ): Boolean {\n            if (hasMultiTouch) return false\n            if (!imgLargeWidth && !imgLargeHeight) return false\n            if (mTranslate.isRunning) return false\n            var vx = velocityX\n            var vy = velocityY\n            if (mImgRect.left.roundToInt() >= mWidgetRect.left\n                || mImgRect.right.roundToInt() <= mWidgetRect.right\n            ) {\n                vx = 0f\n            }\n            if (mImgRect.top.roundToInt() >= mWidgetRect.top\n                || mImgRect.bottom.roundToInt() <= mWidgetRect.bottom\n            ) {\n                vy = 0f\n            }\n            if (canRotate || mDegrees % 90 != 0f) {\n                var toDegrees = (mDegrees / 90).toInt() * 90.toFloat()\n                val remainder = mDegrees % 90\n                if (remainder > 45) toDegrees += 90f else if (remainder < -45) toDegrees -= 90f\n                mTranslate.withRotate(mDegrees.toInt(), toDegrees.toInt())\n                mDegrees = toDegrees\n            }\n            doTranslateReset(mImgRect)\n            mTranslate.withFling(vx, vy)\n            mTranslate.start()\n            // onUp(e2);\n            return super.onFling(e1, e2, velocityX, velocityY)\n        }\n\n        override fun onScroll(\n            e1: MotionEvent?,\n            e2: MotionEvent,\n            distanceX: Float,\n            distanceY: Float\n        ): Boolean {\n            var x = distanceX\n            var y = distanceY\n            if (mTranslate.isRunning) {\n                mTranslate.stop()\n            }\n            if (canScrollHorizontallySelf(x)) {\n                if (x < 0 && mImgRect.left - x > mWidgetRect.left)\n                    x = mImgRect.left\n                if (x > 0 && mImgRect.right - x < mWidgetRect.right)\n                    x = mImgRect.right - mWidgetRect.right\n                mAnimMatrix.postTranslate(-x, 0f)\n                mTranslateX -= x.toInt()\n            } else if (imgLargeWidth || hasMultiTouch || hasOverTranslate) {\n                checkRect()\n                if (!hasMultiTouch) {\n                    if (x < 0 && mImgRect.left - x > mCommonRect.left) x =\n                        resistanceScrollByX(mImgRect.left - mCommonRect.left, x)\n                    if (x > 0 && mImgRect.right - x < mCommonRect.right) x =\n                        resistanceScrollByX(mImgRect.right - mCommonRect.right, x)\n                }\n                mTranslateX -= x.toInt()\n                mAnimMatrix.postTranslate(-x, 0f)\n                hasOverTranslate = true\n            }\n            if (canScrollVerticallySelf(y)) {\n                if (y < 0 && mImgRect.top - y > mWidgetRect.top) y =\n                    mImgRect.top\n                if (y > 0 && mImgRect.bottom - y < mWidgetRect.bottom) y =\n                    mImgRect.bottom - mWidgetRect.bottom\n                mAnimMatrix.postTranslate(0f, -y)\n                mTranslateY -= y.toInt()\n            } else if (imgLargeHeight || hasOverTranslate || hasMultiTouch) {\n                checkRect()\n                if (!hasMultiTouch) {\n                    if (y < 0 && mImgRect.top - y > mCommonRect.top) y =\n                        resistanceScrollByY(mImgRect.top - mCommonRect.top, y)\n                    if (y > 0 && mImgRect.bottom - y < mCommonRect.bottom) y =\n                        resistanceScrollByY(mImgRect.bottom - mCommonRect.bottom, y)\n                }\n                mAnimMatrix.postTranslate(0f, -y)\n                mTranslateY -= y.toInt()\n                hasOverTranslate = true\n            }\n            executeTranslate()\n            return true\n        }\n\n        override fun onSingleTapUp(e: MotionEvent): Boolean {\n            postDelayed(mClickRunnable, 250)\n            return false\n        }\n\n        override fun onDoubleTap(e: MotionEvent): Boolean {\n            mTranslate.stop()\n            val from: Float\n            val to: Float\n            val imgCx = mImgRect.left + mImgRect.width() / 2\n            val imgCy = mImgRect.top + mImgRect.height() / 2\n            mScaleCenter[imgCx] = imgCy\n            mRotateCenter[imgCx] = imgCy\n            mTranslateX = 0\n            mTranslateY = 0\n            if (isZoonUp) {\n                from = mScale\n                to = 1f\n            } else {\n                from = mScale\n                to = mMaxScale\n                mScaleCenter[e.x] = e.y\n            }\n            mTmpMatrix.reset()\n            mTmpMatrix.postTranslate(-mBaseRect.left, -mBaseRect.top)\n            mTmpMatrix.postTranslate(mRotateCenter.x, mRotateCenter.y)\n            mTmpMatrix.postTranslate(-mHalfBaseRectWidth, -mHalfBaseRectHeight)\n            mTmpMatrix.postRotate(mDegrees, mRotateCenter.x, mRotateCenter.y)\n            mTmpMatrix.postScale(to, to, mScaleCenter.x, mScaleCenter.y)\n            mTmpMatrix.postTranslate(mTranslateX.toFloat(), mTranslateY.toFloat())\n            mTmpMatrix.mapRect(mTmpRect, mBaseRect)\n            doTranslateReset(mTmpRect)\n            isZoonUp = !isZoonUp\n            mTranslate.withScale(from, to)\n            mTranslate.start()\n            return false\n        }\n    }\n\n    inner class ScaleGestureListener : OnScaleGestureListener {\n        override fun onScale(detector: ScaleGestureDetector): Boolean {\n            val scaleFactor = detector.scaleFactor\n            if (java.lang.Float.isNaN(scaleFactor) || java.lang.Float.isInfinite(scaleFactor)) return false\n            mScale *= scaleFactor\n            //mScaleCenter.set(detector.getFocusX(), detector.getFocusY());\n            mAnimMatrix.postScale(\n                scaleFactor,\n                scaleFactor,\n                detector.focusX,\n                detector.focusY\n            )\n            executeTranslate()\n            return true\n        }\n\n        override fun onScaleBegin(detector: ScaleGestureDetector): Boolean {\n            return true\n        }\n\n        override fun onScaleEnd(detector: ScaleGestureDetector) {}\n    }\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/widget/image/photo/Info.kt",
    "content": "package io.legado.app.ui.widget.image.photo\n\nimport android.graphics.PointF\n\nimport android.graphics.RectF\nimport android.widget.ImageView\n\n\n@Suppress(\"MemberVisibilityCanBePrivate\")\nclass Info(\n    rect: RectF,\n    img: RectF,\n    widget: RectF,\n    base: RectF,\n    screenCenter: PointF,\n    scale: Float,\n    degrees: Float,\n    scaleType: ImageView.ScaleType?\n) {\n    // 内部图片在整个手机界面的位置\n    var mRect = RectF()\n\n    // 控件在窗口的位置\n    var mImgRect = RectF()\n\n    var mWidgetRect = RectF()\n\n    var mBaseRect = RectF()\n\n    var mScreenCenter = PointF()\n\n    var mScale = 0f\n\n    var mDegrees = 0f\n\n    var mScaleType: ImageView.ScaleType? = null\n\n    init {\n        mRect.set(rect)\n        mImgRect.set(img)\n        mWidgetRect.set(widget)\n        mScale = scale\n        mScaleType = scaleType\n        mDegrees = degrees\n        mBaseRect.set(base)\n        mScreenCenter.set(screenCenter)\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/widget/image/photo/RotateGestureDetector.kt",
    "content": "package io.legado.app.ui.widget.image.photo\n\nimport android.view.MotionEvent\nimport kotlin.math.abs\nimport kotlin.math.atan\n\nclass RotateGestureDetector(private val mListener: OnRotateListener) {\n\n    private val MAX_DEGREES_STEP = 120\n\n    private var mPrevSlope = 0f\n    private var mCurrSlope = 0f\n\n    private val x1 = 0f\n    private val y1 = 0f\n    private val x2 = 0f\n    private val y2 = 0f\n\n    fun onTouchEvent(event: MotionEvent) {\n\n        when (event.actionMasked) {\n            MotionEvent.ACTION_POINTER_DOWN,\n            MotionEvent.ACTION_POINTER_UP -> {\n                if (event.pointerCount == 2) mPrevSlope = calculateSlope(event)\n            }\n            MotionEvent.ACTION_MOVE -> if (event.pointerCount > 1) {\n                mCurrSlope = calculateSlope(event)\n\n                val currDegrees = Math.toDegrees(atan(mCurrSlope.toDouble()))\n                val prevDegrees = Math.toDegrees(atan(mPrevSlope.toDouble()))\n\n                val deltaSlope = currDegrees - prevDegrees\n\n                if (abs(deltaSlope) <= MAX_DEGREES_STEP) {\n                    mListener.onRotate(deltaSlope.toFloat(), (x2 + x1) / 2, (y2 + y1) / 2)\n                }\n                mPrevSlope = mCurrSlope\n            }\n        }\n\n    }\n\n    private fun calculateSlope(event: MotionEvent): Float {\n        val x1 = event.getX(0)\n        val y1 = event.getY(0)\n        val x2 = event.getX(1)\n        val y2 = event.getY(1)\n        return (y2 - y1) / (x2 - x1)\n    }\n}\n\ninterface OnRotateListener {\n    fun onRotate(degrees: Float, focusX: Float, focusY: Float)\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/widget/keyboard/KeyboardAssistsConfig.kt",
    "content": "package io.legado.app.ui.widget.keyboard\n\nimport android.content.Context\nimport android.os.Bundle\nimport android.view.MenuItem\nimport android.view.View\nimport android.view.ViewGroup\nimport androidx.appcompat.widget.Toolbar\nimport androidx.core.view.setPadding\nimport androidx.lifecycle.lifecycleScope\nimport androidx.recyclerview.widget.ItemTouchHelper\nimport androidx.recyclerview.widget.LinearLayoutManager\nimport androidx.recyclerview.widget.RecyclerView\nimport io.legado.app.R\nimport io.legado.app.base.BaseDialogFragment\nimport io.legado.app.base.adapter.ItemViewHolder\nimport io.legado.app.base.adapter.RecyclerAdapter\nimport io.legado.app.constant.AppLog\nimport io.legado.app.data.appDb\nimport io.legado.app.data.entities.KeyboardAssist\nimport io.legado.app.databinding.DialogMultipleEditTextBinding\nimport io.legado.app.databinding.DialogRecyclerViewBinding\nimport io.legado.app.databinding.Item1lineTextAndDelBinding\nimport io.legado.app.lib.dialogs.alert\nimport io.legado.app.lib.theme.backgroundColor\nimport io.legado.app.lib.theme.primaryColor\nimport io.legado.app.ui.widget.recycler.ItemTouchCallback\nimport io.legado.app.ui.widget.recycler.VerticalDivider\nimport io.legado.app.utils.applyTint\nimport io.legado.app.utils.dpToPx\nimport io.legado.app.utils.setLayout\nimport io.legado.app.utils.viewbindingdelegate.viewBinding\nimport io.legado.app.utils.visible\nimport kotlinx.coroutines.Dispatchers.IO\nimport kotlinx.coroutines.flow.catch\nimport kotlinx.coroutines.flow.flowOn\nimport kotlinx.coroutines.launch\n\n/**\n * 辅助按键配置\n */\nclass KeyboardAssistsConfig : BaseDialogFragment(R.layout.dialog_recycler_view),\n    Toolbar.OnMenuItemClickListener {\n\n    private val binding by viewBinding(DialogRecyclerViewBinding::bind)\n    private val adapter by lazy { KeyAdapter(requireContext()) }\n\n    override fun onStart() {\n        super.onStart()\n        setLayout(0.9f, 0.9f)\n    }\n\n    override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) {\n        binding.toolBar.setBackgroundColor(primaryColor)\n        binding.toolBar.setTitle(R.string.assists_key_config)\n        initView()\n        initMenu()\n        initData()\n    }\n\n    private fun initView() {\n        binding.recyclerView.layoutManager = LinearLayoutManager(requireContext())\n        binding.recyclerView.addItemDecoration(VerticalDivider(requireContext()))\n        binding.recyclerView.adapter = adapter\n        val itemTouchCallback = ItemTouchCallback(adapter)\n        itemTouchCallback.isCanDrag = true\n        ItemTouchHelper(itemTouchCallback).attachToRecyclerView(binding.recyclerView)\n\n    }\n\n    private fun initMenu() {\n        binding.toolBar.setOnMenuItemClickListener(this)\n        binding.toolBar.inflateMenu(R.menu.keyboard_assists_config)\n        binding.toolBar.menu.applyTint(requireContext())\n    }\n\n    private fun initData() {\n        lifecycleScope.launch {\n            appDb.keyboardAssistsDao.flowAll.catch {\n                AppLog.put(\"辅助按键配置获取数据失败\\n${it.localizedMessage}\", it)\n            }.flowOn(IO).collect {\n                adapter.setItems(it)\n            }\n        }\n    }\n\n    override fun onMenuItemClick(item: MenuItem?): Boolean {\n        when (item?.itemId) {\n            R.id.menu_add -> editKey(null)\n        }\n        return false\n    }\n\n    private fun editKey(keyboardAssist: KeyboardAssist?) {\n        alert {\n            setTitle(\"辅助按键\")\n            val alertBinding = DialogMultipleEditTextBinding.inflate(layoutInflater).apply {\n                layout1.hint = \"key\"\n                edit1.setText(keyboardAssist?.key)\n                layout2.hint = \"value\"\n                layout2.visible()\n                edit2.setText(keyboardAssist?.value)\n            }\n            setCustomView(alertBinding.root)\n            cancelButton()\n            okButton {\n                lifecycleScope.launch(IO) {\n                    val newKeyboardAssist = KeyboardAssist(\n                        key = alertBinding.edit1.text.toString(),\n                        value = alertBinding.edit2.text.toString()\n                    )\n                    if (keyboardAssist == null) {\n                        newKeyboardAssist.serialNo = appDb.keyboardAssistsDao.maxSerialNo + 1\n                        appDb.keyboardAssistsDao.insert(newKeyboardAssist)\n                    } else {\n                        newKeyboardAssist.serialNo = keyboardAssist.serialNo\n                        appDb.keyboardAssistsDao.delete(keyboardAssist)\n                        appDb.keyboardAssistsDao.insert(newKeyboardAssist)\n                    }\n                }\n            }\n        }\n    }\n\n    private inner class KeyAdapter(context: Context) :\n        RecyclerAdapter<KeyboardAssist, Item1lineTextAndDelBinding>(context),\n        ItemTouchCallback.Callback {\n\n        private var isMoved = false\n\n        override fun getViewBinding(parent: ViewGroup): Item1lineTextAndDelBinding {\n            return Item1lineTextAndDelBinding.inflate(inflater, parent, false).apply {\n                root.setPadding(16.dpToPx())\n                ivDelete.visible()\n            }\n        }\n\n        override fun convert(\n            holder: ItemViewHolder,\n            binding: Item1lineTextAndDelBinding,\n            item: KeyboardAssist,\n            payloads: MutableList<Any>\n        ) {\n            binding.root.setBackgroundColor(context.backgroundColor)\n            binding.textView.text = item.key\n        }\n\n        override fun registerListener(holder: ItemViewHolder, binding: Item1lineTextAndDelBinding) {\n            binding.root.setOnClickListener {\n                getItemByLayoutPosition(holder.layoutPosition)?.let { keyboardAssist ->\n                    editKey(keyboardAssist)\n                }\n            }\n            binding.ivDelete.setOnClickListener {\n                getItemByLayoutPosition(holder.layoutPosition)?.let { keyboardAssist ->\n                    lifecycleScope.launch(IO) {\n                        appDb.keyboardAssistsDao.delete(keyboardAssist)\n                    }\n                }\n            }\n        }\n\n        override fun swap(srcPosition: Int, targetPosition: Int): Boolean {\n            swapItem(srcPosition, targetPosition)\n            isMoved = true\n            return true\n        }\n\n        override fun onClearView(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder) {\n            if (isMoved) {\n                for ((index, item) in getItems().withIndex()) {\n                    item.serialNo = index + 1\n                }\n                lifecycleScope.launch(IO) {\n                    appDb.keyboardAssistsDao.update(*getItems().toTypedArray())\n                }\n            }\n            isMoved = false\n        }\n    }\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/widget/keyboard/KeyboardToolPop.kt",
    "content": "package io.legado.app.ui.widget.keyboard\n\nimport android.content.Context\nimport android.graphics.Rect\nimport android.view.Gravity\nimport android.view.LayoutInflater\nimport android.view.View\nimport android.view.ViewGroup\nimport android.view.ViewTreeObserver\nimport android.view.Window\nimport android.widget.PopupWindow\nimport io.legado.app.R\nimport io.legado.app.base.adapter.ItemViewHolder\nimport io.legado.app.base.adapter.RecyclerAdapter\nimport io.legado.app.constant.AppLog\nimport io.legado.app.data.appDb\nimport io.legado.app.data.entities.KeyboardAssist\nimport io.legado.app.databinding.ItemFilletTextBinding\nimport io.legado.app.databinding.PopupKeyboardToolBinding\nimport io.legado.app.lib.dialogs.SelectItem\nimport io.legado.app.lib.dialogs.selector\nimport io.legado.app.utils.activity\nimport io.legado.app.utils.showDialogFragment\nimport io.legado.app.utils.windowSize\nimport kotlinx.coroutines.CoroutineScope\nimport kotlinx.coroutines.Dispatchers.IO\nimport kotlinx.coroutines.flow.catch\nimport kotlinx.coroutines.flow.flowOn\nimport kotlinx.coroutines.launch\nimport splitties.systemservices.layoutInflater\nimport splitties.systemservices.windowManager\nimport kotlin.math.abs\n\n/**\n * 键盘帮助浮窗\n */\nclass KeyboardToolPop(\n    private val context: Context,\n    private val scope: CoroutineScope,\n    private val rootView: View,\n    private val callBack: CallBack\n) : PopupWindow(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT),\n    ViewTreeObserver.OnGlobalLayoutListener {\n\n    private val helpChar = \"❓\"\n\n    private val binding = PopupKeyboardToolBinding.inflate(LayoutInflater.from(context))\n    private val adapter = Adapter(context)\n    private var mIsSoftKeyBoardShowing = false\n    var initialPadding = 0\n\n    init {\n        contentView = binding.root\n\n        isTouchable = true\n        isOutsideTouchable = false\n        isFocusable = false\n        inputMethodMode = INPUT_METHOD_NEEDED //解决遮盖输入法\n        initRecyclerView()\n        upAdapterData()\n    }\n\n    fun attachToWindow(window: Window) {\n        window.decorView.viewTreeObserver.addOnGlobalLayoutListener(this)\n        contentView.measure(\n            View.MeasureSpec.UNSPECIFIED,\n            View.MeasureSpec.UNSPECIFIED,\n        )\n    }\n\n    override fun onGlobalLayout() {\n        val rect = Rect()\n        // 获取当前页面窗口的显示范围\n        rootView.getWindowVisibleDisplayFrame(rect)\n        val screenHeight = windowManager.windowSize.heightPixels\n        val keyboardHeight = screenHeight - rect.bottom // 输入法的高度\n        val preShowing = mIsSoftKeyBoardShowing\n        if (abs(keyboardHeight) > screenHeight / 5) {\n            mIsSoftKeyBoardShowing = true // 超过屏幕五分之一则表示弹出了输入法\n            rootView.setPadding(0, 0, 0, initialPadding + contentView.measuredHeight)\n            if (!isShowing) {\n                showAtLocation(rootView, Gravity.BOTTOM, 0, 0)\n            }\n        } else {\n            mIsSoftKeyBoardShowing = false\n            rootView.setPadding(0, 0, 0, 0)\n            if (preShowing) {\n                dismiss()\n            }\n        }\n    }\n\n    private fun initRecyclerView() {\n        binding.recyclerView.adapter = adapter\n        adapter.addHeaderView {\n            ItemFilletTextBinding.inflate(context.layoutInflater, it, false).apply {\n                textView.text = helpChar\n                root.setOnClickListener {\n                    helpAlert()\n                }\n            }\n        }\n    }\n\n    @Suppress(\"MemberVisibilityCanBePrivate\")\n    fun upAdapterData() {\n        scope.launch {\n            appDb.keyboardAssistsDao.flowByType(0).catch {\n                AppLog.put(\"键盘帮助浮窗获取数据失败\\n${it.localizedMessage}\", it)\n            }.flowOn(IO).collect {\n                adapter.setItems(it)\n            }\n        }\n    }\n\n    private fun helpAlert() {\n        val items = arrayListOf(\n            SelectItem(context.getString(R.string.assists_key_config), \"keyConfig\")\n        )\n        items.addAll(callBack.helpActions())\n        context.selector(context.getString(R.string.help), items) { _, selectItem, _ ->\n            when (selectItem.value) {\n                \"keyConfig\" -> config()\n                else -> callBack.onHelpActionSelect(selectItem.value)\n            }\n        }\n    }\n\n    private fun config() {\n        contentView.activity?.showDialogFragment<KeyboardAssistsConfig>()\n    }\n\n    inner class Adapter(context: Context) :\n        RecyclerAdapter<KeyboardAssist, ItemFilletTextBinding>(context) {\n\n        override fun getViewBinding(parent: ViewGroup): ItemFilletTextBinding {\n            return ItemFilletTextBinding.inflate(inflater, parent, false)\n        }\n\n        override fun convert(\n            holder: ItemViewHolder,\n            binding: ItemFilletTextBinding,\n            item: KeyboardAssist,\n            payloads: MutableList<Any>\n        ) {\n            binding.run {\n                textView.text = item.key\n            }\n        }\n\n        override fun registerListener(holder: ItemViewHolder, binding: ItemFilletTextBinding) {\n            holder.itemView.apply {\n                setOnClickListener {\n                    getItemByLayoutPosition(holder.layoutPosition)?.let {\n                        callBack.sendText(it.value)\n                    }\n                }\n            }\n        }\n    }\n\n    interface CallBack {\n\n        fun helpActions(): List<SelectItem<String>> = arrayListOf()\n\n        fun onHelpActionSelect(action: String)\n\n        fun sendText(text: String)\n\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/widget/number/NumberPickerDialog.kt",
    "content": "package io.legado.app.ui.widget.number\n\nimport android.content.Context\nimport android.widget.NumberPicker\nimport androidx.appcompat.app.AlertDialog\nimport io.legado.app.R\nimport io.legado.app.utils.applyTint\nimport io.legado.app.utils.hideSoftInput\n\n\nclass NumberPickerDialog(context: Context) {\n    private val builder = AlertDialog.Builder(context)\n    private var numberPicker: NumberPicker? = null\n    private var maxValue: Int? = null\n    private var minValue: Int? = null\n    private var value: Int? = null\n\n    init {\n        builder.setView(R.layout.dialog_number_picker)\n    }\n\n    fun setTitle(title: String): NumberPickerDialog {\n        builder.setTitle(title)\n        return this\n    }\n\n    fun setMaxValue(value: Int): NumberPickerDialog {\n        maxValue = value\n        return this\n    }\n\n    fun setMinValue(value: Int): NumberPickerDialog {\n        minValue = value\n        return this\n    }\n\n    fun setValue(value: Int): NumberPickerDialog {\n        this.value = value\n        return this\n    }\n\n    fun setCustomButton(textId: Int, listener: (() -> Unit)?): NumberPickerDialog {\n        builder.setNeutralButton(textId) { _, _ ->\n            numberPicker?.let {\n                it.clearFocus()\n                it.hideSoftInput()\n                listener?.invoke()\n            }\n        }\n        return this\n    }\n\n    fun show(callBack: ((value: Int) -> Unit)?) {\n        builder.setPositiveButton(R.string.ok) { _, _ ->\n            numberPicker?.let {\n                it.clearFocus()\n                it.hideSoftInput()\n                callBack?.invoke(it.value)\n            }\n        }\n        builder.setNegativeButton(R.string.cancel, null)\n        val dialog = builder.show().applyTint()\n        numberPicker = dialog.findViewById(R.id.number_picker)\n        numberPicker?.let { np ->\n            minValue?.let {\n                np.minValue = it\n            }\n            maxValue?.let {\n                np.maxValue = it\n            }\n            value?.let {\n                np.value = it\n            }\n        }\n    }\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/widget/recycler/DividerNoLast.kt",
    "content": "package io.legado.app.ui.widget.recycler\n\nimport android.content.Context\nimport android.graphics.Canvas\nimport android.graphics.Rect\nimport android.graphics.drawable.Drawable\nimport android.view.View\nimport android.widget.LinearLayout\nimport androidx.recyclerview.widget.RecyclerView\nimport io.legado.app.utils.DebugLog\n\nimport kotlin.math.roundToInt\n\n\n/**\n * 不画最后一条分隔线\n */\n@Suppress(\"MemberVisibilityCanBePrivate\", \"RedundantRequireNotNullCall\", \"unused\")\nclass DividerNoLast(context: Context, orientation: Int) :\n    RecyclerView.ItemDecoration() {\n\n    companion object {\n        const val HORIZONTAL = LinearLayout.HORIZONTAL\n        const val VERTICAL = LinearLayout.VERTICAL\n    }\n\n    private val attrs = intArrayOf(android.R.attr.listDivider)\n\n    private var mDivider: Drawable? = null\n\n    /**\n     * Current orientation. Either [.HORIZONTAL] or [.VERTICAL].\n     */\n    private var mOrientation = 0\n\n    private val mBounds = Rect()\n\n    init {\n        val a = context.obtainStyledAttributes(attrs)\n        mDivider = a.getDrawable(0)\n        if (mDivider == null) {\n            DebugLog.w(\n                javaClass.name,\n                \"@android:attr/listDivider was not set in the theme used for this DividerItemDecoration. Please set that attribute all call setDrawable()\"\n            )\n        }\n        a.recycle()\n        setOrientation(orientation)\n    }\n\n    /**\n     * Sets the orientation for this divider. This should be called if\n     * [RecyclerView.LayoutManager] changes orientation.\n     *\n     * @param orientation [.HORIZONTAL] or [.VERTICAL]\n     */\n    fun setOrientation(orientation: Int) {\n        require(!(orientation != HORIZONTAL && orientation != VERTICAL)) { \"Invalid orientation. It should be either HORIZONTAL or VERTICAL\" }\n        mOrientation = orientation\n    }\n\n    /**\n     * Sets the [Drawable] for this divider.\n     *\n     * @param drawable Drawable that should be used as a divider.\n     */\n    fun setDrawable(drawable: Drawable) {\n        requireNotNull(drawable) { \"Drawable cannot be null.\" }\n        mDivider = drawable\n    }\n\n    /**\n     * @return the [Drawable] for this divider.\n     */\n    fun getDrawable(): Drawable? {\n        return mDivider\n    }\n\n    override fun onDraw(c: Canvas, parent: RecyclerView, state: RecyclerView.State) {\n        if (parent.layoutManager == null || mDivider == null) {\n            return\n        }\n        if (mOrientation == VERTICAL) {\n            drawVertical(c, parent)\n        } else {\n            drawHorizontal(c, parent)\n        }\n    }\n\n    private fun drawVertical(\n        canvas: Canvas,\n        parent: RecyclerView\n    ) {\n        canvas.save()\n        val left: Int\n        val right: Int\n        if (parent.clipToPadding) {\n            left = parent.paddingLeft\n            right = parent.width - parent.paddingRight\n            canvas.clipRect(\n                left, parent.paddingTop, right,\n                parent.height - parent.paddingBottom\n            )\n        } else {\n            left = 0\n            right = parent.width\n        }\n        val childCount = parent.childCount\n        for (i in 0 until childCount - 1) {\n            val child = parent.getChildAt(i)\n            parent.getDecoratedBoundsWithMargins(child, mBounds)\n            val bottom = mBounds.bottom + child.translationY.roundToInt()\n            val top = bottom - mDivider!!.intrinsicHeight\n            mDivider!!.setBounds(left, top, right, bottom)\n            mDivider!!.draw(canvas)\n        }\n        canvas.restore()\n    }\n\n    private fun drawHorizontal(canvas: Canvas, parent: RecyclerView) {\n        canvas.save()\n        val top: Int\n        val bottom: Int\n        if (parent.clipToPadding) {\n            top = parent.paddingTop\n            bottom = parent.height - parent.paddingBottom\n            canvas.clipRect(\n                parent.paddingLeft, top,\n                parent.width - parent.paddingRight, bottom\n            )\n        } else {\n            top = 0\n            bottom = parent.height\n        }\n        val childCount = parent.childCount\n        for (i in 0 until childCount - 1) {\n            val child = parent.getChildAt(i)\n            parent.layoutManager!!.getDecoratedBoundsWithMargins(child, mBounds)\n            val right = mBounds.right + child.translationX.roundToInt()\n            val left = right - mDivider!!.intrinsicWidth\n            mDivider!!.setBounds(left, top, right, bottom)\n            mDivider!!.draw(canvas)\n        }\n        canvas.restore()\n    }\n\n    override fun getItemOffsets(\n        outRect: Rect, view: View, parent: RecyclerView,\n        state: RecyclerView.State\n    ) {\n        if (mDivider == null) {\n            outRect[0, 0, 0] = 0\n            return\n        }\n\n        if (mOrientation == VERTICAL) {\n            outRect[0, 0, 0] = mDivider!!.intrinsicHeight\n        } else {\n            val childAdapterPosition = parent.getChildAdapterPosition(view)\n            val lastCount = parent.adapter!!.itemCount - 1\n            //如果不是最后一条 正常赋值 如果是最后一条 赋值为0\n            if (childAdapterPosition != lastCount) {\n                outRect[0, 0, mDivider!!.intrinsicWidth] = 0\n            } else {\n                outRect[0, 0, 0] = 0\n            }\n        }\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/widget/recycler/DragSelectTouchHelper.kt",
    "content": "/*\n * Copyright 2020 Mupceet\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n *     http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage io.legado.app.ui.widget.recycler\n\nimport android.content.res.Resources\nimport android.text.TextUtils\nimport android.util.DisplayMetrics\nimport android.util.TypedValue\nimport android.view.MotionEvent\nimport android.view.View\nimport androidx.core.view.ViewCompat\nimport androidx.recyclerview.widget.GridLayoutManager\nimport androidx.recyclerview.widget.RecyclerView\nimport androidx.recyclerview.widget.RecyclerView.OnItemTouchListener\nimport io.legado.app.BuildConfig\nimport io.legado.app.ui.widget.recycler.DragSelectTouchHelper.AdvanceCallback.Mode\nimport io.legado.app.utils.DebugLog\nimport java.util.Locale\nimport kotlin.math.max\nimport kotlin.math.min\n\n/**\n * @author mupceet\n *                        !autoChangeMode           +-------------------+     inactiveSelect()\n *           +------------------------------------> |                   | <--------------------+\n *           |                                      |      Normal       |                      |\n *           |        activeDragSelect(position)    |                   | activeSlideSelect()  |\n *           |      +------------------------------ |                   | ----------+          |\n *           |      v                               +-------------------+           v          |\n *  +-------------------+                              autoChangeMode     +-----------------------+\n *  | Drag From Disable | ----------------------------------------------> |                       |\n *  +-------------------+                                                 |                       |\n *  |                   |                                                 |                       |\n *  |                   | activeDragSelect(position) && allowDragInSlide  |        Slide          |\n *  |                   | <---------------------------------------------- |                       |\n *  |  Drag From Slide  |                                                 |                       |\n *  |                   |                                                 |                       |\n *  |                   | ----------------------------------------------> |                       |\n *  +-------------------+                                                 +-----------------------+\n */\n@Suppress(\"unused\", \"MemberVisibilityCanBePrivate\", \"DEPRECATION\")\nclass DragSelectTouchHelper(\n    /**\n     * Developer callback which controls the behavior of DragSelectTouchHelper.\n     */\n    private val mCallback: Callback,\n) {\n\n    companion object {\n        private const val TAG = \"DSTH\"\n        private const val MAX_HOTSPOT_RATIO = 0.5f\n        private val DEFAULT_EDGE_TYPE = EdgeType.INSIDE_EXTEND\n        private const val DEFAULT_HOTSPOT_RATIO = 0.2f\n        private const val DEFAULT_HOTSPOT_OFFSET = 0\n        private const val DEFAULT_MAX_SCROLL_VELOCITY = 10\n        private const val SELECT_STATE_NORMAL = 0x00\n        private const val SELECT_STATE_SLIDE = 0x01\n        private const val SELECT_STATE_DRAG_FROM_NORMAL = 0x10\n        private const val SELECT_STATE_DRAG_FROM_SLIDE = 0x11\n    }\n\n    private val mDisplayMetrics: DisplayMetrics = Resources.getSystem().displayMetrics\n\n    /**\n     * Start of the slide area.\n     */\n    private var mSlideAreaLeft = 0f\n\n    /**\n     * End of the slide area.\n     */\n    private var mSlideAreaRight = 0f\n\n    /**\n     * The hotspot height by the ratio of RecyclerView.\n     */\n    private var mHotspotHeightRatio = 0f\n\n    /**\n     * The hotspot height.\n     */\n    private var mHotspotHeight = 0f\n\n    /**\n     * The hotspot offset.\n     */\n    private var mHotspotOffset = 0f\n\n    /**\n     * Whether should continue scrolling when move outside top hotspot region.\n     */\n    private var mScrollAboveTopRegion = false\n\n    /**\n     * Whether should continue scrolling when move outside bottom hotspot region.\n     */\n    private var mScrollBelowBottomRegion = false\n\n    /**\n     * The maximum velocity of auto scrolling.\n     */\n    private var mMaximumVelocity = 0\n\n    /**\n     * Whether should auto enter slide mode after drag select finished.\n     */\n    private var mShouldAutoChangeState = false\n\n    /**\n     * Whether can drag selection in slide select mode.\n     */\n    private var mIsAllowDragInSlideState = false\n    private var mRecyclerView: RecyclerView? = null\n\n    /**\n     * The coordinate of hotspot area.\n     */\n    private var mTopRegionFrom = -1f\n    private var mTopRegionTo = -1f\n    private var mBottomRegionFrom = -1f\n    private var mBottomRegionTo = -1f\n    private val mOnLayoutChangeListener =\n        View.OnLayoutChangeListener { v, left, top, right, bottom, oldLeft, oldTop, oldRight, oldBottom ->\n            if (oldLeft != left || oldRight != right || oldTop != top || oldBottom != bottom) {\n                if (v === mRecyclerView) {\n                    Logger.i(\n                        \"onLayoutChange:new: \"\n                                + left + \" \" + top + \" \" + right + \" \" + bottom\n                    )\n                    Logger.i(\n                        \"onLayoutChange:old: \"\n                                + oldLeft + \" \" + oldTop + \" \" + oldRight + \" \" + oldBottom\n                    )\n                    init(bottom - top)\n                }\n            }\n        }\n\n    /**\n     * The current mode of selection.\n     */\n    private var mSelectState = SELECT_STATE_NORMAL\n\n    /**\n     * Whether is in top hotspot area.\n     */\n    private var mIsInTopHotspot = false\n\n    /**\n     * Whether is in bottom hotspot area.\n     */\n    private var mIsInBottomHotspot = false\n\n    /**\n     * Indicates automatically scroll.\n     */\n    private var mIsScrolling = false\n\n    /**\n     * The actual speed of the current moment.\n     */\n    private var mScrollDistance = 0\n\n    /**\n     * The reference coordinate for the action start, used to avoid reverse scrolling.\n     */\n    private var mDownY = Float.MIN_VALUE\n\n    /**\n     * The reference coordinates for the last action.\n     */\n    private var mLastX = Float.MIN_VALUE\n    private var mLastY = Float.MIN_VALUE\n\n    /**\n     * The selected items position.\n     */\n    private var mStart = RecyclerView.NO_POSITION\n    private var mEnd = RecyclerView.NO_POSITION\n    private var mLastRealStart = RecyclerView.NO_POSITION\n    private var mLastRealEnd = RecyclerView.NO_POSITION\n    private var mSlideStateStartPosition = RecyclerView.NO_POSITION\n    private var mHaveCalledSelectStart = false\n    private val mScrollRunnable: Runnable by lazy {\n        object : Runnable {\n            override fun run() {\n                if (mIsScrolling) {\n                    scrollBy(mScrollDistance)\n                    ViewCompat.postOnAnimation(mRecyclerView!!, this)\n                }\n            }\n        }\n    }\n    private val mOnItemTouchListener: OnItemTouchListener by lazy {\n        object : OnItemTouchListener {\n            override fun onInterceptTouchEvent(rv: RecyclerView, e: MotionEvent): Boolean {\n                Logger.d(\n                    \"onInterceptTouchEvent: x:\" + e.x + \",y:\" + e.y\n                            + \", \" + MotionEvent.actionToString(e.action)\n                )\n                val adapter = rv.adapter\n                if (adapter == null || adapter.itemCount == 0) {\n                    return false\n                }\n                var intercept = false\n                val action = e.action\n                when (action and MotionEvent.ACTION_MASK) {\n                    MotionEvent.ACTION_DOWN -> {\n                        mDownY = e.y\n                        // call the selection start's callback before moving\n                        if (mSelectState == SELECT_STATE_SLIDE && isInSlideArea(e)) {\n                            mSlideStateStartPosition = getItemPosition(rv, e)\n                            if (mSlideStateStartPosition != RecyclerView.NO_POSITION) {\n                                mCallback.onSelectStart(mSlideStateStartPosition)\n                                mHaveCalledSelectStart = true\n                            }\n                            intercept = true\n                        }\n                    }\n                    MotionEvent.ACTION_MOVE -> if (mSelectState == SELECT_STATE_DRAG_FROM_NORMAL\n                        || mSelectState == SELECT_STATE_DRAG_FROM_SLIDE\n                    ) {\n                        Logger.i(\"onInterceptTouchEvent: drag mode move\")\n                        intercept = true\n                    }\n                    MotionEvent.ACTION_UP -> {\n                        if (mSelectState == SELECT_STATE_DRAG_FROM_NORMAL\n                            || mSelectState == SELECT_STATE_DRAG_FROM_SLIDE\n                        ) {\n                            intercept = true\n                        }\n                        // finger is lifted before moving\n                        if (mSlideStateStartPosition != RecyclerView.NO_POSITION) {\n                            selectFinished(mSlideStateStartPosition)\n                            mSlideStateStartPosition = RecyclerView.NO_POSITION\n                        }\n                        // selection has triggered\n                        if (mStart != RecyclerView.NO_POSITION) {\n                            selectFinished(mEnd)\n                        }\n                    }\n                    MotionEvent.ACTION_CANCEL -> {\n                        if (mSlideStateStartPosition != RecyclerView.NO_POSITION) {\n                            selectFinished(mSlideStateStartPosition)\n                            mSlideStateStartPosition = RecyclerView.NO_POSITION\n                        }\n                        if (mStart != RecyclerView.NO_POSITION) {\n                            selectFinished(mEnd)\n                        }\n                    }\n                    else -> {\n                    }\n                }\n                // Intercept only when the selection is triggered\n                Logger.d(\"intercept result: $intercept\")\n                return intercept\n            }\n\n            override fun onTouchEvent(rv: RecyclerView, e: MotionEvent) {\n                if (!isActivated) {\n                    return\n                }\n                Logger.d(\n                    \"onTouchEvent: x:\" + e.x + \",y:\" + e.y\n                            + \", \" + MotionEvent.actionToString(e.action)\n                )\n                val action = e.action\n                when (action and MotionEvent.ACTION_MASK) {\n                    MotionEvent.ACTION_MOVE -> {\n                        if (mSlideStateStartPosition != RecyclerView.NO_POSITION) {\n                            selectFirstItem(mSlideStateStartPosition)\n                            // selection is triggered\n                            mSlideStateStartPosition = RecyclerView.NO_POSITION\n                            Logger.i(\"onTouchEvent: after slide mode down\")\n                        }\n                        processAutoScroll(e)\n                        if (!mIsInTopHotspot && !mIsInBottomHotspot) {\n                            updateSelectedRange(rv, e)\n                        }\n                    }\n                    MotionEvent.ACTION_CANCEL, MotionEvent.ACTION_UP -> {\n                        if (mSlideStateStartPosition != RecyclerView.NO_POSITION) {\n                            selectFirstItem(mSlideStateStartPosition)\n                            // selection is triggered\n                            mSlideStateStartPosition = RecyclerView.NO_POSITION\n                            Logger.i(\"onTouchEvent: after slide mode down\")\n                        }\n                        if (!mIsInTopHotspot && !mIsInBottomHotspot) {\n                            updateSelectedRange(rv, e)\n                        }\n                        selectFinished(mEnd)\n                    }\n                }\n            }\n\n            override fun onRequestDisallowInterceptTouchEvent(disallowIntercept: Boolean) {\n                if (disallowIntercept) {\n                    inactiveSelect()\n                }\n            }\n        }\n    }\n\n    init {\n        setHotspotRatio(DEFAULT_HOTSPOT_RATIO)\n        setHotspotOffset(DEFAULT_HOTSPOT_OFFSET)\n        setMaximumVelocity(DEFAULT_MAX_SCROLL_VELOCITY)\n        setEdgeType(DEFAULT_EDGE_TYPE)\n        setAutoEnterSlideState(false)\n        setAllowDragInSlideState(false)\n        setSlideArea(0, 0)\n    }\n\n    /**\n     * Attaches the DragSelectTouchHelper to the provided RecyclerView. If TouchHelper is already\n     * attached to a RecyclerView, it will first detach from the previous one. You can call this\n     * method with `null` to detach it from the current RecyclerView.\n     *\n     * @param recyclerView The RecyclerView instance to which you want to add this helper or\n     * `null` if you want to remove DragSelectTouchHelper from the\n     * current RecyclerView.\n     */\n    fun attachToRecyclerView(recyclerView: RecyclerView?) {\n        if (mRecyclerView === recyclerView) {\n            return  // nothing to do\n        }\n        mRecyclerView?.removeOnItemTouchListener(mOnItemTouchListener)\n        mRecyclerView = recyclerView\n        mRecyclerView?.let {\n            it.addOnItemTouchListener(mOnItemTouchListener)\n            it.addOnLayoutChangeListener(mOnLayoutChangeListener)\n        }\n    }\n\n    /**\n     * Activate the slide selection mode.\n     */\n    fun activeSlideSelect() {\n        activeSelectInternal(RecyclerView.NO_POSITION)\n    }\n\n    /**\n     * Activate the selection mode with selected item position. Normally called on long press.\n     *\n     * @param position Indicates the position of selected item.\n     */\n    fun activeDragSelect(position: Int) {\n        activeSelectInternal(position)\n    }\n\n    /**\n     * Exit the selection mode.\n     */\n    fun inactiveSelect() {\n        if (isActivated) {\n            selectFinished(mEnd)\n        } else {\n            selectFinished(RecyclerView.NO_POSITION)\n        }\n        Logger.logSelectStateChange(mSelectState, SELECT_STATE_NORMAL)\n        mSelectState = SELECT_STATE_NORMAL\n    }\n\n    /**\n     * To determine whether it is in the selection mode.\n     *\n     * @return true if is in the selection mode.\n     */\n    val isActivated: Boolean\n        get() = mSelectState != SELECT_STATE_NORMAL\n\n    /**\n     * Sets hotspot height by ratio of RecyclerView.\n     *\n     * @param ratio range (0, 0.5).\n     * @return The select helper, which may used to chain setter calls.\n     */\n    fun setHotspotRatio(ratio: Float): DragSelectTouchHelper {\n        mHotspotHeightRatio = ratio\n        return this\n    }\n\n    /**\n     * Sets hotspot height.\n     *\n     * @param hotspotHeight hotspot height which unit is dp.\n     * @return The select helper, which may used to chain setter calls.\n     */\n    fun setHotspotHeight(hotspotHeight: Int): DragSelectTouchHelper {\n        mHotspotHeight = dp2px(hotspotHeight.toFloat()).toFloat()\n        return this\n    }\n\n    /**\n     * Sets hotspot offset. It don't need to be set if no special requirement.\n     *\n     * @param hotspotOffset hotspot offset which unit is dp.\n     * @return The select helper, which may used to chain setter calls.\n     */\n    fun setHotspotOffset(hotspotOffset: Int): DragSelectTouchHelper {\n        mHotspotOffset = dp2px(hotspotOffset.toFloat()).toFloat()\n        return this\n    }\n\n    /**\n     * Sets the activation edge type, one of:\n     *\n     *  * [EdgeType.INSIDE] for edges that respond to touches inside\n     * the bounds of the host view. If touch moves outside the bounds, scrolling\n     * will stop.\n     *  * [EdgeType.INSIDE_EXTEND] for inside edges that continued to\n     * scroll when touch moves outside the bounds of the host view.\n     *\n     *\n     * @param type The type of edge to use.\n     * @return The select helper, which may used to chain setter calls.\n     */\n    fun setEdgeType(type: EdgeType?): DragSelectTouchHelper {\n        when (type) {\n            EdgeType.INSIDE -> {\n                mScrollAboveTopRegion = false\n                mScrollBelowBottomRegion = false\n            }\n            EdgeType.INSIDE_EXTEND -> {\n                mScrollAboveTopRegion = true\n                mScrollBelowBottomRegion = true\n            }\n            else -> {\n                mScrollAboveTopRegion = true\n                mScrollBelowBottomRegion = true\n            }\n        }\n        return this\n    }\n\n    /**\n     * Sets sliding area's start and end, has been considered RTL situation\n     *\n     * @param startDp The start of the sliding area\n     * @param endDp   The end of the sliding area\n     * @return The select helper, which may used to chain setter calls.\n     */\n    fun setSlideArea(startDp: Int, endDp: Int): DragSelectTouchHelper {\n        if (!isRtl) {\n            mSlideAreaLeft = dp2px(startDp.toFloat()).toFloat()\n            mSlideAreaRight = dp2px(endDp.toFloat()).toFloat()\n        } else {\n            val displayWidth = mDisplayMetrics.widthPixels\n            mSlideAreaLeft = displayWidth - dp2px(endDp.toFloat()).toFloat()\n            mSlideAreaRight = displayWidth - dp2px(startDp.toFloat()).toFloat()\n        }\n        return this\n    }\n\n    /**\n     * Sets the maximum velocity for scrolling\n     *\n     * @param velocity maximum velocity\n     * @return The select helper, which may used to chain setter calls.\n     */\n    fun setMaximumVelocity(velocity: Int): DragSelectTouchHelper {\n        mMaximumVelocity = (velocity * mDisplayMetrics.density + 0.5f).toInt()\n        return this\n    }\n\n    /**\n     * Sets whether should auto enter slide mode after drag select finished.\n     * It's usefully for LinearLayout RecyclerView.\n     *\n     * @param autoEnterSlideState should auto enter slide mode\n     * @return The select helper, which may used to chain setter calls.\n     */\n    fun setAutoEnterSlideState(autoEnterSlideState: Boolean): DragSelectTouchHelper {\n        mShouldAutoChangeState = autoEnterSlideState\n        return this\n    }\n\n    /**\n     * Sets whether can drag selection in slide select mode.\n     * It's usefully for LinearLayout RecyclerView.\n     *\n     * @param allowDragInSlideState allow drag selection in slide select mode\n     * @return The select helper, which may used to chain setter calls.\n     */\n    fun setAllowDragInSlideState(allowDragInSlideState: Boolean): DragSelectTouchHelper {\n        mIsAllowDragInSlideState = allowDragInSlideState\n        return this\n    }\n\n    private fun init(rvHeight: Int) {\n        if (mHotspotOffset >= rvHeight * MAX_HOTSPOT_RATIO) {\n            mHotspotOffset = rvHeight * MAX_HOTSPOT_RATIO\n        }\n        // The height of hotspot area is not set, using (RV height x ratio)\n        if (mHotspotHeight <= 0) {\n            if (mHotspotHeightRatio <= 0 || mHotspotHeightRatio >= MAX_HOTSPOT_RATIO) {\n                mHotspotHeightRatio = DEFAULT_HOTSPOT_RATIO\n            }\n            mHotspotHeight = rvHeight * mHotspotHeightRatio\n        } else {\n            if (mHotspotHeight >= rvHeight * MAX_HOTSPOT_RATIO) {\n                mHotspotHeight = rvHeight * MAX_HOTSPOT_RATIO\n            }\n        }\n        mTopRegionFrom = mHotspotOffset\n        mTopRegionTo = mTopRegionFrom + mHotspotHeight\n        mBottomRegionTo = rvHeight - mHotspotOffset\n        mBottomRegionFrom = mBottomRegionTo - mHotspotHeight\n        if (mTopRegionTo > mBottomRegionFrom) {\n            mBottomRegionFrom = (rvHeight shr 1.toFloat().toInt()).toFloat()\n            mTopRegionTo = mBottomRegionFrom\n        }\n        Logger.d(\n            \"Hotspot: [\" + mTopRegionFrom + \", \" + mTopRegionTo + \"], [\"\n                    + mBottomRegionFrom + \", \" + mBottomRegionTo + \"]\"\n        )\n    }\n\n    private fun activeSelectInternal(position: Int) {\n        // We should initialize the hotspot here, because its data may be delayed load\n        mRecyclerView?.let {\n            init(it.height)\n        }\n        if (position == RecyclerView.NO_POSITION) {\n            Logger.logSelectStateChange(mSelectState, SELECT_STATE_SLIDE)\n            mSelectState = SELECT_STATE_SLIDE\n        } else {\n            if (!mHaveCalledSelectStart) {\n                mCallback.onSelectStart(position)\n                mHaveCalledSelectStart = true\n            }\n            if (mSelectState == SELECT_STATE_SLIDE) {\n                if (mIsAllowDragInSlideState && selectFirstItem(position)) {\n                    Logger.logSelectStateChange(mSelectState, SELECT_STATE_DRAG_FROM_SLIDE)\n                    mSelectState = SELECT_STATE_DRAG_FROM_SLIDE\n                }\n            } else if (mSelectState == SELECT_STATE_NORMAL) {\n                if (selectFirstItem(position)) {\n                    Logger.logSelectStateChange(mSelectState, SELECT_STATE_DRAG_FROM_NORMAL)\n                    mSelectState = SELECT_STATE_DRAG_FROM_NORMAL\n                }\n            } else {\n                Logger.e(\"activeSelect in unexpected state: $mSelectState\")\n            }\n        }\n    }\n\n    private fun selectFirstItem(position: Int): Boolean {\n        val selectFirstItemSucceed = mCallback.onSelectChange(position, true)\n        // The drag select feature is only available if the first item is available for selection\n        if (selectFirstItemSucceed) {\n            mStart = position\n            mEnd = position\n            mLastRealStart = position\n            mLastRealEnd = position\n        }\n        return selectFirstItemSucceed\n    }\n\n    private fun updateSelectedRange(rv: RecyclerView, e: MotionEvent) {\n        updateSelectedRange(rv, e.x, e.y)\n    }\n\n    private fun updateSelectedRange(rv: RecyclerView, x: Float, y: Float) {\n        val position = getItemPosition(rv, x, y)\n        if (position != RecyclerView.NO_POSITION && mEnd != position) {\n            mEnd = position\n            notifySelectRangeChange()\n        }\n    }\n\n    private fun notifySelectRangeChange() {\n        if (mStart == RecyclerView.NO_POSITION || mEnd == RecyclerView.NO_POSITION) {\n            return\n        }\n        val newStart: Int = min(mStart, mEnd)\n        val newEnd: Int = max(mStart, mEnd)\n        if (mLastRealStart == RecyclerView.NO_POSITION || mLastRealEnd == RecyclerView.NO_POSITION) {\n            if (newEnd - newStart == 1) {\n                notifySelectChange(newStart, newStart, true)\n            } else {\n                notifySelectChange(newStart, newEnd, true)\n            }\n        } else {\n            if (newStart > mLastRealStart) {\n                notifySelectChange(mLastRealStart, newStart - 1, false)\n            } else if (newStart < mLastRealStart) {\n                notifySelectChange(newStart, mLastRealStart - 1, true)\n            }\n            if (newEnd > mLastRealEnd) {\n                notifySelectChange(mLastRealEnd + 1, newEnd, true)\n            } else if (newEnd < mLastRealEnd) {\n                notifySelectChange(newEnd + 1, mLastRealEnd, false)\n            }\n        }\n        mLastRealStart = newStart\n        mLastRealEnd = newEnd\n    }\n\n    private fun notifySelectChange(start: Int, end: Int, newState: Boolean) {\n        for (i in start..end) {\n            mCallback.onSelectChange(i, newState)\n        }\n    }\n\n    private fun selectFinished(lastItem: Int) {\n        if (lastItem != RecyclerView.NO_POSITION) {\n            mCallback.onSelectEnd(lastItem)\n        }\n        mStart = RecyclerView.NO_POSITION\n        mEnd = RecyclerView.NO_POSITION\n        mLastRealStart = RecyclerView.NO_POSITION\n        mLastRealEnd = RecyclerView.NO_POSITION\n        mHaveCalledSelectStart = false\n        mIsInTopHotspot = false\n        mIsInBottomHotspot = false\n        stopAutoScroll()\n        when (mSelectState) {\n            SELECT_STATE_DRAG_FROM_NORMAL -> mSelectState = if (mShouldAutoChangeState) {\n                Logger.logSelectStateChange(\n                    mSelectState,\n                    SELECT_STATE_SLIDE\n                )\n                SELECT_STATE_SLIDE\n            } else {\n                Logger.logSelectStateChange(\n                    mSelectState,\n                    SELECT_STATE_NORMAL\n                )\n                SELECT_STATE_NORMAL\n            }\n            SELECT_STATE_DRAG_FROM_SLIDE -> {\n                Logger.logSelectStateChange(mSelectState, SELECT_STATE_SLIDE)\n                mSelectState = SELECT_STATE_SLIDE\n            }\n            else -> {\n            }\n        }\n    }\n\n    /**\n     * Process motion event, according to the location to determine whether to scroll\n     */\n    private fun processAutoScroll(e: MotionEvent) {\n        val y = e.y\n        if (y in mTopRegionFrom..mTopRegionTo && y < mDownY) {\n            mLastX = e.x\n            mLastY = e.y\n            val scrollDistanceFactor = (y - mTopRegionTo) / mHotspotHeight\n            mScrollDistance = (mMaximumVelocity * scrollDistanceFactor).toInt()\n            if (!mIsInTopHotspot) {\n                mIsInTopHotspot = true\n                startAutoScroll()\n                mDownY = mTopRegionTo\n            }\n        } else if (mScrollAboveTopRegion && y < mTopRegionFrom && mIsInTopHotspot) {\n            mLastX = e.x\n            mLastY = mTopRegionFrom\n            // Use the maximum speed\n            mScrollDistance = mMaximumVelocity * -1\n            startAutoScroll()\n        } else if (y in mBottomRegionFrom..mBottomRegionTo && y > mDownY) {\n            mLastX = e.x\n            mLastY = e.y\n            val scrollDistanceFactor = (y - mBottomRegionFrom) / mHotspotHeight\n            mScrollDistance = (mMaximumVelocity * scrollDistanceFactor).toInt()\n            if (!mIsInBottomHotspot) {\n                mIsInBottomHotspot = true\n                startAutoScroll()\n                mDownY = mBottomRegionFrom\n            }\n        } else if (mScrollBelowBottomRegion && y > mBottomRegionTo && mIsInBottomHotspot) {\n            mLastX = e.x\n            mLastY = mBottomRegionTo\n            // Use the maximum speed\n            mScrollDistance = mMaximumVelocity\n            startAutoScroll()\n        } else {\n            mIsInTopHotspot = false\n            mIsInBottomHotspot = false\n            mLastX = Float.MIN_VALUE\n            mLastY = Float.MIN_VALUE\n            stopAutoScroll()\n        }\n    }\n\n    private fun startAutoScroll() {\n        if (!mIsScrolling) {\n            mIsScrolling = true\n            mRecyclerView!!.removeCallbacks(mScrollRunnable)\n            ViewCompat.postOnAnimation(mRecyclerView!!, mScrollRunnable)\n        }\n    }\n\n    private fun stopAutoScroll() {\n        if (mIsScrolling) {\n            mIsScrolling = false\n            mRecyclerView?.removeCallbacks(mScrollRunnable)\n        }\n    }\n\n    private fun scrollBy(distance: Int) {\n        val scrollDistance: Int =\n            if (distance > 0) {\n                min(distance, mMaximumVelocity)\n            } else {\n                max(distance, -mMaximumVelocity)\n            }\n        mRecyclerView!!.scrollBy(0, scrollDistance)\n        if (mLastX != Float.MIN_VALUE && mLastY != Float.MIN_VALUE) {\n            updateSelectedRange(mRecyclerView!!, mLastX, mLastY)\n        }\n    }\n\n    private fun dp2px(dpVal: Float): Int {\n        return TypedValue.applyDimension(\n            TypedValue.COMPLEX_UNIT_DIP,\n            dpVal, mDisplayMetrics\n        ).toInt()\n    }\n\n    private val isRtl: Boolean\n        get() = (TextUtils.getLayoutDirectionFromLocale(Locale.getDefault())\n                == View.LAYOUT_DIRECTION_RTL)\n\n    private fun isInSlideArea(e: MotionEvent): Boolean {\n        val x = e.x\n        return x > mSlideAreaLeft && x < mSlideAreaRight\n    }\n\n    private fun getItemPosition(rv: RecyclerView, e: MotionEvent): Int {\n        return getItemPosition(rv, e.x, e.y)\n    }\n\n    private fun getItemPosition(rv: RecyclerView, x: Float, y: Float): Int {\n        val v = rv.findChildViewUnder(x, y)\n        if (v == null) {\n            val layoutManager = rv.layoutManager\n            if (layoutManager is GridLayoutManager) {\n                val lastVisibleItemPosition = layoutManager.findLastVisibleItemPosition()\n                val lastItemPosition = layoutManager.getItemCount() - 1\n                if (lastItemPosition == lastVisibleItemPosition) {\n                    return lastItemPosition\n                }\n            }\n            return RecyclerView.NO_POSITION\n        }\n        return rv.getChildAdapterPosition(v)\n    }\n\n    /**\n     * Edge type that specifies an activation area starting at the view bounds and extending inward.\n     */\n    enum class EdgeType {\n        /**\n         * After activation begins, moving outside the view bounds will stop scrolling.\n         */\n        INSIDE,\n\n        /**\n         * After activation begins, moving outside the view bounds will continue scrolling.\n         */\n        INSIDE_EXTEND\n    }\n\n    /**\n     * This class is the contract between DragSelectTouchHelper and your application. It lets you\n     * update adapter when selection start/end and state changed.\n     */\n    abstract class Callback {\n        /**\n         * Called when changing item state.\n         *\n         * @param position   this item want to change the state to new state.\n         * @param isSelected true if the position should be selected, false otherwise.\n         * @return Whether to set the new state successfully.\n         */\n        abstract fun onSelectChange(position: Int, isSelected: Boolean): Boolean\n\n        /**\n         * Called when selection start.\n         *\n         * @param start the first selected item.\n         */\n        open fun onSelectStart(start: Int) {}\n\n        /**\n         * Called when selection end.\n         *\n         * @param end the last selected item.\n         */\n        open fun onSelectEnd(end: Int) {}\n    }\n\n    /**\n     * An advance Callback which provide 4 useful selection modes [Mode].\n     *\n     *\n     * Note: Since the state of item may be repeatedly set, in order to improve efficiency,\n     * please process it in the Adapter\n     */\n    abstract class AdvanceCallback<T> : Callback {\n        private var mMode: Mode? = null\n        private var mOriginalSelection: MutableSet<T> = mutableSetOf()\n        private var mFirstWasSelected = false\n\n        /**\n         * Creates a SimpleCallback with default [Mode.SelectAndReverse]# mode.\n         *\n         * @see Mode\n         */\n        constructor() {\n            setMode(Mode.SelectAndReverse)\n        }\n\n        /**\n         * Creates a SimpleCallback with select mode.\n         *\n         * @param mode the initial select mode\n         * @see Mode\n         */\n        constructor(mode: Mode?) {\n            setMode(mode)\n        }\n\n        /**\n         * Sets the select mode.\n         *\n         * @param mode The type of select mode.\n         * @see Mode\n         */\n        fun setMode(mode: Mode?) {\n            mMode = mode\n        }\n\n        override fun onSelectStart(start: Int) {\n            mOriginalSelection.clear()\n            val selected = currentSelectedId()\n            if (selected != null) {\n                mOriginalSelection.addAll(selected)\n            }\n            mFirstWasSelected = mOriginalSelection.contains(getItemId(start))\n        }\n\n        override fun onSelectEnd(end: Int) {\n            mOriginalSelection.clear()\n        }\n\n        override fun onSelectChange(position: Int, isSelected: Boolean): Boolean {\n            return when (mMode) {\n                Mode.SelectAndKeep -> {\n                    updateSelectState(position, true)\n                }\n                Mode.SelectAndReverse -> {\n                    updateSelectState(position, isSelected)\n                }\n                Mode.SelectAndUndo -> {\n                    if (isSelected) {\n                        updateSelectState(position, true)\n                    } else {\n                        updateSelectState(\n                            position,\n                            mOriginalSelection.contains(getItemId(position))\n                        )\n                    }\n                }\n                Mode.ToggleAndKeep -> {\n                    updateSelectState(position, !mFirstWasSelected)\n                }\n                Mode.ToggleAndReverse -> {\n                    if (isSelected) {\n                        updateSelectState(position, !mFirstWasSelected)\n                    } else {\n                        updateSelectState(position, mFirstWasSelected)\n                    }\n                }\n                Mode.ToggleAndUndo -> {\n                    if (isSelected) {\n                        updateSelectState(position, !mFirstWasSelected)\n                    } else {\n                        updateSelectState(\n                            position,\n                            mOriginalSelection.contains(getItemId(position))\n                        )\n                    }\n                }\n                else ->                     // SelectAndReverse Mode\n                    updateSelectState(position, isSelected)\n            }\n        }\n\n        /**\n         * Get the currently selected items when selecting first item.\n         *\n         * @return the currently selected item's id set.\n         */\n        abstract fun currentSelectedId(): Set<T>?\n\n        /**\n         * Get the ID of the item.\n         *\n         * @param position item position to be judged.\n         * @return item's identity.\n         */\n        abstract fun getItemId(position: Int): T\n\n        /**\n         * Update the selection status of the position.\n         *\n         * @param position   the position who's selection state changed.\n         * @param isSelected true if the position should be selected, false otherwise.\n         * @return Whether to set the state successfully.\n         */\n        abstract fun updateSelectState(position: Int, isSelected: Boolean): Boolean\n\n        /**\n         * Different existing selection modes\n         */\n        enum class Mode {\n            /**\n             * Selects the first item and applies the same state to each item you go by\n             * and keep the state on move back\n             */\n            SelectAndKeep,\n\n            /**\n             * Selects the first item and applies the same state to each item you go by\n             * and applies inverted state on move back\n             */\n            SelectAndReverse,\n\n            /**\n             * Selects the first item and applies the same state to each item you go by\n             * and reverts to the original state on move back\n             */\n            SelectAndUndo,\n\n            /**\n             * Toggles the first item and applies the same state to each item you go by\n             * and keep the state on move back\n             */\n            ToggleAndKeep,\n\n            /**\n             * Toggles the first item and applies the same state to each item you go by\n             * and applies inverted state on move back\n             */\n            ToggleAndReverse,\n\n            /**\n             * Toggles the first item and applies the same state to each item you go by\n             * and reverts to the original state on move back\n             */\n            ToggleAndUndo\n        }\n    }\n\n    private object Logger {\n        private val DEBUG = BuildConfig.DEBUG\n        fun d(msg: String) {\n            DebugLog.d(javaClass.name, msg)\n        }\n\n        fun e(msg: String) {\n            DebugLog.e(javaClass.name, msg)\n        }\n\n        fun i(msg: String) {\n            DebugLog.i(javaClass.name, msg)\n        }\n\n        fun logSelectStateChange(before: Int, after: Int) {\n            i(\"Select state changed: \" + stateName(before) + \" --> \" + stateName(after))\n        }\n\n        private fun stateName(state: Int): String {\n            return when (state) {\n                SELECT_STATE_NORMAL -> \"NormalState\"\n                SELECT_STATE_SLIDE -> \"SlideState\"\n                SELECT_STATE_DRAG_FROM_NORMAL -> \"DragFromNormal\"\n                SELECT_STATE_DRAG_FROM_SLIDE -> \"DragFromSlide\"\n                else -> \"Unknown\"\n            }\n        }\n    }\n\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/widget/recycler/HeaderAdapterDataObserver.kt",
    "content": "package io.legado.app.ui.widget.recycler\n\nimport androidx.recyclerview.widget.RecyclerView\n\n@Suppress(\"unused\")\ninternal class HeaderAdapterDataObserver(\n    private var adapterDataObserver: RecyclerView.AdapterDataObserver,\n    private var headerCount: Int\n) : RecyclerView.AdapterDataObserver() {\n    override fun onChanged() {\n        adapterDataObserver.onChanged()\n    }\n\n    override fun onItemRangeChanged(positionStart: Int, itemCount: Int) {\n        adapterDataObserver.onItemRangeChanged(positionStart + headerCount, itemCount)\n    }\n\n    override fun onItemRangeChanged(positionStart: Int, itemCount: Int, payload: Any?) {\n        adapterDataObserver.onItemRangeChanged(positionStart + headerCount, itemCount, payload)\n    }\n\n    // 当第n个数据被获取，更新第n+1个position\n    override fun onItemRangeInserted(positionStart: Int, itemCount: Int) {\n        adapterDataObserver.onItemRangeInserted(positionStart + headerCount, itemCount)\n    }\n\n    override fun onItemRangeRemoved(positionStart: Int, itemCount: Int) {\n        adapterDataObserver.onItemRangeRemoved(positionStart + headerCount, itemCount)\n    }\n\n    override fun onItemRangeMoved(fromPosition: Int, toPosition: Int, itemCount: Int) {\n        super.onItemRangeMoved(fromPosition + headerCount, toPosition + headerCount, itemCount)\n    }\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/widget/recycler/ItemTouchCallback.kt",
    "content": "package io.legado.app.ui.widget.recycler\n\n\nimport androidx.recyclerview.widget.GridLayoutManager\nimport androidx.recyclerview.widget.ItemTouchHelper\nimport androidx.recyclerview.widget.LinearLayoutManager\nimport androidx.recyclerview.widget.RecyclerView\nimport androidx.swiperefreshlayout.widget.SwipeRefreshLayout\n\n/**\n * Created by GKF on 2018/3/16.\n */\n@Suppress(\"MemberVisibilityCanBePrivate\")\nclass ItemTouchCallback(private val callback: Callback) : ItemTouchHelper.Callback() {\n\n    private var swipeRefreshLayout: SwipeRefreshLayout? = null\n\n    /**\n     * 是否可以拖拽\n     */\n    var isCanDrag = false\n\n    /**\n     * 是否可以被滑动\n     */\n    var isCanSwipe = false\n\n    /**\n     * 当Item被长按的时候是否可以被拖拽\n     */\n    override fun isLongPressDragEnabled(): Boolean {\n        return isCanDrag\n    }\n\n    /**\n     * Item是否可以被滑动(H：左右滑动，V：上下滑动)\n     */\n    override fun isItemViewSwipeEnabled(): Boolean {\n        return isCanSwipe\n    }\n\n    /**\n     * 当用户拖拽或者滑动Item的时候需要我们告诉系统滑动或者拖拽的方向\n     */\n    override fun getMovementFlags(\n        recyclerView: RecyclerView,\n        viewHolder: RecyclerView.ViewHolder\n    ): Int {\n        val layoutManager = recyclerView.layoutManager\n        if (layoutManager is GridLayoutManager) {// GridLayoutManager\n            // flag如果值是0，相当于这个功能被关闭\n            val dragFlag =\n                ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT or ItemTouchHelper.UP or ItemTouchHelper.DOWN\n            val swipeFlag = 0\n            // create make\n            return makeMovementFlags(dragFlag, swipeFlag)\n        } else if (layoutManager is LinearLayoutManager) {// linearLayoutManager\n            val linearLayoutManager = layoutManager as LinearLayoutManager?\n            val orientation = linearLayoutManager!!.orientation\n\n            var dragFlag = 0\n            var swipeFlag = 0\n\n            // 为了方便理解，相当于分为横着的ListView和竖着的ListView\n            if (orientation == LinearLayoutManager.HORIZONTAL) {// 如果是横向的布局\n                swipeFlag = ItemTouchHelper.UP or ItemTouchHelper.DOWN\n                dragFlag = ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT\n            } else if (orientation == LinearLayoutManager.VERTICAL) {// 如果是竖向的布局，相当于ListView\n                dragFlag = ItemTouchHelper.UP or ItemTouchHelper.DOWN\n                swipeFlag = ItemTouchHelper.LEFT or ItemTouchHelper.RIGHT\n            }\n            return makeMovementFlags(dragFlag, swipeFlag)\n        }\n        return 0\n    }\n\n    /**\n     * 当Item被拖拽的时候被回调\n     *\n     * @param recyclerView     recyclerView\n     * @param srcViewHolder    拖拽的ViewHolder\n     * @param targetViewHolder 目的地的viewHolder\n     */\n    override fun onMove(\n        recyclerView: RecyclerView,\n        srcViewHolder: RecyclerView.ViewHolder,\n        targetViewHolder: RecyclerView.ViewHolder\n    ): Boolean {\n        val fromPosition: Int = srcViewHolder.bindingAdapterPosition\n        val toPosition: Int = targetViewHolder.bindingAdapterPosition\n        if (fromPosition < toPosition) {\n            for (i in fromPosition until toPosition) {\n                callback.swap(i, i + 1)\n            }\n        } else {\n            for (i in fromPosition downTo toPosition + 1) {\n                callback.swap(i, i - 1)\n            }\n        }\n        return true\n    }\n\n    override fun onSwiped(viewHolder: RecyclerView.ViewHolder, direction: Int) {\n        callback.onSwiped(viewHolder.bindingAdapterPosition)\n    }\n\n    override fun onSelectedChanged(viewHolder: RecyclerView.ViewHolder?, actionState: Int) {\n        super.onSelectedChanged(viewHolder, actionState)\n        val swiping = actionState == ItemTouchHelper.ACTION_STATE_DRAG\n        swipeRefreshLayout?.isEnabled = !swiping\n    }\n\n    override fun clearView(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder) {\n        super.clearView(recyclerView, viewHolder)\n        callback.onClearView(recyclerView, viewHolder)\n    }\n\n    interface Callback {\n\n        /**\n         * 当某个Item被滑动删除的时候\n         *\n         * @param adapterPosition item的position\n         */\n        fun onSwiped(adapterPosition: Int) {\n\n        }\n\n        /**\n         * 当两个Item位置互换的时候被回调\n         *\n         * @param srcPosition    拖拽的item的position\n         * @param targetPosition 目的地的Item的position\n         * @return 开发者处理了操作应该返回true，开发者没有处理就返回false\n         */\n        fun swap(srcPosition: Int, targetPosition: Int): Boolean {\n            return true\n        }\n\n        /**\n         * 手指松开\n         */\n        fun onClearView(recyclerView: RecyclerView, viewHolder: RecyclerView.ViewHolder) {\n\n        }\n\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/widget/recycler/LoadMoreView.kt",
    "content": "package io.legado.app.ui.widget.recycler\n\nimport android.content.Context\nimport android.util.AttributeSet\nimport android.view.LayoutInflater\nimport android.view.View\nimport android.widget.FrameLayout\nimport androidx.annotation.ColorRes\nimport io.legado.app.R\nimport io.legado.app.databinding.ViewLoadMoreBinding\nimport io.legado.app.lib.dialogs.alert\nimport io.legado.app.utils.getCompatColor\nimport io.legado.app.utils.invisible\nimport io.legado.app.utils.visible\n\n@Suppress(\"unused\")\nclass LoadMoreView(context: Context, attrs: AttributeSet? = null) : FrameLayout(context, attrs) {\n    private val binding = ViewLoadMoreBinding.inflate(LayoutInflater.from(context), this)\n    private var errorMsg = \"\"\n\n    private var onClickListener: OnClickListener? = null\n\n    var isLoading = false\n        private set\n\n    var hasMore = true\n        private set\n\n    init {\n        super.setOnClickListener {\n            if (!showErrorDialog(it)) {\n                onClickListener?.onClick(it)\n            }\n        }\n    }\n\n    override fun setOnClickListener(l: OnClickListener?) {\n        this.onClickListener = l\n    }\n\n    override fun onAttachedToWindow() {\n        super.onAttachedToWindow()\n        layoutParams.width = LayoutParams.MATCH_PARENT\n    }\n\n    fun startLoad() {\n        isLoading = true\n        binding.tvText.invisible()\n        binding.rotateLoading.visible()\n    }\n\n    fun stopLoad() {\n        isLoading = false\n        binding.rotateLoading.inVisible()\n    }\n\n    fun hasMore() {\n        errorMsg = \"\"\n        hasMore = true\n        startLoad()\n    }\n\n    fun noMore(msg: String? = null) {\n        stopLoad()\n        errorMsg = \"\"\n        hasMore = false\n        if (msg != null) {\n            binding.tvText.text = msg\n        } else {\n            binding.tvText.setText(R.string.bottom_line)\n        }\n        binding.tvText.visible()\n    }\n\n    fun error(msg: String?, text: String = \"\") {\n        stopLoad()\n        hasMore = false\n        errorMsg = msg ?: \"\"\n        binding.tvText.text =\n            text.ifEmpty { context.getString(R.string.error_load_msg, \"点击查看详情\") }\n        binding.tvText.visible()\n    }\n\n    fun setLoadingColor(@ColorRes color: Int) {\n        binding.rotateLoading.loadingColor = context.getCompatColor(color)\n    }\n\n    fun setLoadingTextColor(@ColorRes color: Int) {\n        binding.tvText.setTextColor(context.getCompatColor(color))\n    }\n\n    private fun showErrorDialog(view: View): Boolean {\n        if (errorMsg.isBlank()) {\n            return false\n        }\n        context.alert(R.string.error) {\n            setMessage(errorMsg)\n            if (onClickListener != null) {\n                neutralButton(R.string.retry) {\n                    onClickListener?.onClick(view)\n                }\n            }\n        }\n        return true\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/widget/recycler/NoChildScrollLinearLayoutManager.kt",
    "content": "package io.legado.app.ui.widget.recycler\n\nimport android.content.Context\nimport android.util.AttributeSet\nimport android.view.View\nimport androidx.recyclerview.widget.LinearLayoutManager\nimport androidx.recyclerview.widget.RecyclerView\n\nclass NoChildScrollLinearLayoutManager @JvmOverloads constructor(\n    context: Context,\n    attrs: AttributeSet? = null,\n    defStyleAttr: Int = 0,\n    defStyleRes: Int = 0\n) : LinearLayoutManager(context, attrs, defStyleAttr, defStyleRes) {\n\n    override fun onRequestChildFocus(\n        parent: RecyclerView,\n        state: RecyclerView.State,\n        child: View,\n        focused: View?\n    ): Boolean {\n        return true\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/widget/recycler/RecyclerViewAtPager2.kt",
    "content": "package io.legado.app.ui.widget.recycler\n\nimport android.content.Context\nimport android.util.AttributeSet\nimport android.view.MotionEvent\nimport androidx.recyclerview.widget.RecyclerView\nimport kotlin.math.abs\n\nclass RecyclerViewAtPager2 : RecyclerView {\n\n    constructor(context: Context) : super(context)\n    constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)\n    constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int) : super(\n        context,\n        attrs,\n        defStyleAttr\n    )\n\n    private var startX = 0\n    private var startY = 0\n\n    override fun dispatchTouchEvent(ev: MotionEvent): Boolean {\n        when (ev.action) {\n            MotionEvent.ACTION_DOWN -> {\n                startX = ev.x.toInt()\n                startY = ev.y.toInt()\n                parent.requestDisallowInterceptTouchEvent(true)\n            }\n            MotionEvent.ACTION_MOVE -> {\n                val endX = ev.x.toInt()\n                val endY = ev.y.toInt()\n                val disX = abs(endX - startX)\n                val disY = abs(endY - startY)\n                if (disX > disY) {\n                    if (disX > 50) {\n                        parent.requestDisallowInterceptTouchEvent(false)\n                    }\n                } else {\n                    parent.requestDisallowInterceptTouchEvent(true)\n                }\n            }\n            MotionEvent.ACTION_UP,\n            MotionEvent.ACTION_CANCEL -> parent.requestDisallowInterceptTouchEvent(false)\n        }\n        return super.dispatchTouchEvent(ev)\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/widget/recycler/UpLinearLayoutManager.kt",
    "content": "package io.legado.app.ui.widget.recycler\n\nimport android.content.Context\nimport androidx.recyclerview.widget.LinearLayoutManager\nimport androidx.recyclerview.widget.LinearSmoothScroller\n\n@Suppress(\"MemberVisibilityCanBePrivate\", \"unused\")\nclass UpLinearLayoutManager(val context: Context) : LinearLayoutManager(context) {\n\n    fun smoothScrollToPosition(position: Int) {\n        smoothScrollToPosition(position, 0)\n    }\n\n    fun smoothScrollToPosition(position: Int, offset: Int) {\n        val scroller = UpLinearSmoothScroller(context)\n        scroller.targetPosition = position\n        scroller.offset = offset\n        startSmoothScroll(scroller)\n    }\n\n    class UpLinearSmoothScroller(context: Context?) : LinearSmoothScroller(context) {\n        var offset = 0\n\n        override fun getVerticalSnapPreference(): Int {\n            return SNAP_TO_START\n        }\n\n        override fun getHorizontalSnapPreference(): Int {\n            return SNAP_TO_START\n        }\n\n        override fun calculateDtToFit(\n            viewStart: Int,\n            viewEnd: Int,\n            boxStart: Int,\n            boxEnd: Int,\n            snapPreference: Int\n        ): Int {\n            if (snapPreference == SNAP_TO_START) {\n                return boxStart - viewStart + offset\n            }\n            throw IllegalArgumentException(\"snap preference should be SNAP_TO_START\")\n        }\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/widget/recycler/VerticalDivider.kt",
    "content": "package io.legado.app.ui.widget.recycler\n\nimport android.content.Context\nimport androidx.core.content.ContextCompat\nimport androidx.recyclerview.widget.DividerItemDecoration\nimport io.legado.app.R\n\nclass VerticalDivider(context: Context) : DividerItemDecoration(context, VERTICAL) {\n\n    init {\n        ContextCompat.getDrawable(context, R.drawable.ic_divider)?.let {\n            this.setDrawable(it)\n        }\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/widget/recycler/ViewPager2Container.kt",
    "content": "package io.legado.app.ui.widget.recycler\n\nimport android.content.Context\nimport android.util.AttributeSet\nimport android.view.MotionEvent\nimport android.widget.RelativeLayout\nimport androidx.viewpager2.widget.ViewPager2\nimport kotlin.math.abs\n\n@Suppress(\"unused\")\nclass ViewPager2Container @JvmOverloads constructor(\n    context: Context,\n    attrs: AttributeSet? = null,\n    defStyleAttr: Int = 0\n) : RelativeLayout(context, attrs, defStyleAttr) {\n\n    private var mViewPager2: ViewPager2? = null\n    private var disallowParentInterceptDownEvent = true\n    private var startX = 0\n    private var startY = 0\n\n    override fun onFinishInflate() {\n        super.onFinishInflate()\n        for (i in 0 until childCount) {\n            val childView = getChildAt(i)\n            if (childView is ViewPager2) {\n                mViewPager2 = childView\n                break\n            }\n        }\n        if (mViewPager2 == null) {\n            throw IllegalStateException(\"The root child of ViewPager2Container must contains a ViewPager2\")\n        }\n    }\n\n    override fun onInterceptTouchEvent(ev: MotionEvent): Boolean {\n        val doNotNeedIntercept = (!mViewPager2!!.isUserInputEnabled\n                || (mViewPager2?.adapter != null\n                && mViewPager2?.adapter!!.itemCount <= 1))\n        if (doNotNeedIntercept) {\n            return super.onInterceptTouchEvent(ev)\n        }\n        when (ev.action) {\n            MotionEvent.ACTION_DOWN -> {\n                startX = ev.x.toInt()\n                startY = ev.y.toInt()\n                parent.requestDisallowInterceptTouchEvent(!disallowParentInterceptDownEvent)\n            }\n            MotionEvent.ACTION_MOVE -> {\n                val endX = ev.x.toInt()\n                val endY = ev.y.toInt()\n                val disX = abs(endX - startX)\n                val disY = abs(endY - startY)\n                if (mViewPager2!!.orientation == ViewPager2.ORIENTATION_VERTICAL) {\n                    onVerticalActionMove(endY, disX, disY)\n                } else if (mViewPager2!!.orientation == ViewPager2.ORIENTATION_HORIZONTAL) {\n                    onHorizontalActionMove(endX, disX, disY)\n                }\n            }\n            MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> parent.requestDisallowInterceptTouchEvent(\n                false\n            )\n        }\n        return super.onInterceptTouchEvent(ev)\n    }\n\n    private fun onHorizontalActionMove(endX: Int, disX: Int, disY: Int) {\n        if (mViewPager2?.adapter == null) {\n            return\n        }\n        if (disX > disY) {\n            val currentItem = mViewPager2?.currentItem\n            val itemCount = mViewPager2?.adapter!!.itemCount\n            if (currentItem == 0 && endX - startX > 0) {\n                parent.requestDisallowInterceptTouchEvent(false)\n            } else {\n                parent.requestDisallowInterceptTouchEvent(\n                    currentItem != itemCount - 1\n                            || endX - startX >= 0\n                )\n            }\n        } else if (disY > disX) {\n            parent.requestDisallowInterceptTouchEvent(false)\n        }\n    }\n\n    private fun onVerticalActionMove(endY: Int, disX: Int, disY: Int) {\n        if (mViewPager2?.adapter == null) {\n            return\n        }\n        val currentItem = mViewPager2?.currentItem\n        val itemCount = mViewPager2?.adapter!!.itemCount\n        if (disY > disX) {\n            if (currentItem == 0 && endY - startY > 0) {\n                parent.requestDisallowInterceptTouchEvent(false)\n            } else {\n                parent.requestDisallowInterceptTouchEvent(\n                    currentItem != itemCount - 1\n                            || endY - startY >= 0\n                )\n            }\n        } else if (disX > disY) {\n            parent.requestDisallowInterceptTouchEvent(false)\n        }\n    }\n\n    /**\n     * 设置是否允许在当前View的{@link MotionEvent#ACTION_DOWN}事件中禁止父View对事件的拦截，该方法\n     * 用于解决CoordinatorLayout+CollapsingToolbarLayout在嵌套ViewPager2Container时引起的滑动冲突问题。\n     *\n     * 设置是否允许在ViewPager2Container的{@link MotionEvent#ACTION_DOWN}事件中禁止父View对事件的拦截，该方法\n     * 用于解决CoordinatorLayout+CollapsingToolbarLayout在嵌套ViewPager2Container时引起的滑动冲突问题。\n     *\n     * @param disallowParentInterceptDownEvent 是否允许ViewPager2Container在{@link MotionEvent#ACTION_DOWN}事件中禁止父View拦截事件，默认值为false\n     *   true 不允许ViewPager2Container在{@link MotionEvent#ACTION_DOWN}时间中禁止父View的时间拦截，\n     *   设置disallowIntercept为true可以解决CoordinatorLayout+CollapsingToolbarLayout的滑动冲突\n     *   false 允许ViewPager2Container在{@link MotionEvent#ACTION_DOWN}时间中禁止父View的时间拦截，\n     */\n    fun disallowParentInterceptDownEvent(disallowParentInterceptDownEvent: Boolean) {\n        this.disallowParentInterceptDownEvent = disallowParentInterceptDownEvent\n    }\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/widget/recycler/scroller/FastScrollRecyclerView.kt",
    "content": "package io.legado.app.ui.widget.recycler.scroller\n\nimport android.content.Context\nimport android.util.AttributeSet\nimport android.view.ViewGroup\nimport android.widget.FrameLayout\nimport android.widget.RelativeLayout\nimport androidx.annotation.ColorInt\nimport androidx.constraintlayout.widget.ConstraintLayout\nimport androidx.coordinatorlayout.widget.CoordinatorLayout\nimport androidx.recyclerview.widget.RecyclerView\nimport io.legado.app.R\n\n@Suppress(\"MemberVisibilityCanBePrivate\", \"unused\")\nclass FastScrollRecyclerView : RecyclerView {\n\n    private lateinit var mFastScroller: FastScroller\n\n    constructor(context: Context) : super(context) {\n        layout(context, null)\n        layoutParams =\n            LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT)\n    }\n\n    @JvmOverloads\n    constructor(\n        context: Context,\n        attrs: AttributeSet,\n        defStyleAttr: Int = 0\n    ) : super(context, attrs, defStyleAttr) {\n        layout(context, attrs)\n    }\n\n    private fun layout(context: Context, attrs: AttributeSet?) {\n        mFastScroller = FastScroller(context, attrs)\n        mFastScroller.id = R.id.fast_scroller\n    }\n\n    override fun setAdapter(adapter: Adapter<*>?) {\n        super.setAdapter(adapter)\n        if (adapter is FastScroller.SectionIndexer) {\n            setSectionIndexer(adapter as FastScroller.SectionIndexer?)\n        } else if (adapter == null) {\n            setSectionIndexer(null)\n        }\n    }\n\n\n    override fun setVisibility(visibility: Int) {\n        super.setVisibility(visibility)\n        mFastScroller.visibility = visibility\n    }\n\n\n    /**\n     * Set the [FastScroller.SectionIndexer] for the [FastScroller].\n     *\n     * @param sectionIndexer The SectionIndexer that provides section text for the FastScroller\n     */\n    fun setSectionIndexer(sectionIndexer: FastScroller.SectionIndexer?) {\n        mFastScroller.setSectionIndexer(sectionIndexer)\n    }\n\n\n    /**\n     * Set the enabled state of fast scrolling.\n     *\n     * @param enabled True to enable fast scrolling, false otherwise\n     */\n    fun setFastScrollEnabled(enabled: Boolean) {\n        mFastScroller.isEnabled = enabled\n    }\n\n\n    /**\n     * Hide the scrollbar when not scrolling.\n     *\n     * @param hideScrollbar True to hide the scrollbar, false to show\n     */\n    fun setHideScrollbar(hideScrollbar: Boolean) {\n        mFastScroller.setFadeScrollbar(hideScrollbar)\n    }\n\n    /**\n     * Display a scroll track while scrolling.\n     *\n     * @param visible True to show scroll track, false to hide\n     */\n    fun setTrackVisible(visible: Boolean) {\n        mFastScroller.setTrackVisible(visible)\n    }\n\n    /**\n     * Set the color of the scroll track.\n     *\n     * @param color The color for the scroll track\n     */\n    fun setTrackColor(@ColorInt color: Int) {\n        mFastScroller.setTrackColor(color)\n    }\n\n\n    /**\n     * Set the color for the scroll handle.\n     *\n     * @param color The color for the scroll handle\n     */\n    fun setHandleColor(@ColorInt color: Int) {\n        mFastScroller.setHandleColor(color)\n    }\n\n\n    /**\n     * Show the section bubble while scrolling.\n     *\n     * @param visible True to show the bubble, false to hide\n     */\n    fun setBubbleVisible(visible: Boolean) {\n        mFastScroller.setBubbleVisible(visible)\n    }\n\n\n    /**\n     * Set the background color of the index bubble.\n     *\n     * @param color The background color for the index bubble\n     */\n    fun setBubbleColor(@ColorInt color: Int) {\n        mFastScroller.setBubbleColor(color)\n    }\n\n\n    /**\n     * Set the text color of the index bubble.\n     *\n     * @param color The text color for the index bubble\n     */\n    fun setBubbleTextColor(@ColorInt color: Int) {\n        mFastScroller.setBubbleTextColor(color)\n    }\n\n\n    /**\n     * Set the fast scroll state change listener.\n     *\n     * @param fastScrollStateChangeListener The interface that will listen to fastscroll state change events\n     */\n    fun setFastScrollStateChangeListener(fastScrollStateChangeListener: FastScrollStateChangeListener) {\n        mFastScroller.setFastScrollStateChangeListener(fastScrollStateChangeListener)\n    }\n\n\n    override fun onAttachedToWindow() {\n        super.onAttachedToWindow()\n        mFastScroller.attachRecyclerView(this)\n        var parent = parent\n        while (parent != null) {\n            when (parent) {\n                is ConstraintLayout, is CoordinatorLayout, is FrameLayout, is RelativeLayout -> break\n                else -> parent = parent.parent\n            }\n        }\n        if (parent is ViewGroup && parent.indexOfChild(mFastScroller) == -1) {\n            parent.addView(mFastScroller)\n            mFastScroller.setLayoutParams(parent)\n        }\n    }\n\n\n    override fun onDetachedFromWindow() {\n        mFastScroller.detachRecyclerView()\n        super.onDetachedFromWindow()\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/widget/recycler/scroller/FastScrollStateChangeListener.kt",
    "content": "package io.legado.app.ui.widget.recycler.scroller\n\ninterface FastScrollStateChangeListener {\n\n    /**\n     * Called when fast scrolling begins\n     */\n    fun onFastScrollStart(fastScroller: FastScroller)\n\n    /**\n     * Called when fast scrolling ends\n     */\n    fun onFastScrollStop(fastScroller: FastScroller)\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/widget/recycler/scroller/FastScroller.kt",
    "content": "package io.legado.app.ui.widget.recycler.scroller\n\nimport android.animation.Animator\nimport android.animation.AnimatorListenerAdapter\nimport android.annotation.SuppressLint\nimport android.content.Context\nimport android.graphics.Color\nimport android.graphics.drawable.Drawable\nimport android.util.AttributeSet\nimport android.view.MotionEvent\nimport android.view.View\nimport android.view.ViewGroup\nimport android.view.ViewPropertyAnimator\nimport android.widget.*\nimport androidx.annotation.ColorInt\nimport androidx.annotation.IdRes\nimport androidx.constraintlayout.widget.ConstraintLayout\nimport androidx.constraintlayout.widget.ConstraintSet\nimport androidx.coordinatorlayout.widget.CoordinatorLayout\nimport androidx.core.content.ContextCompat\nimport androidx.core.graphics.drawable.DrawableCompat\nimport androidx.core.view.GravityCompat\nimport androidx.core.view.ViewCompat\nimport androidx.core.view.isVisible\nimport androidx.recyclerview.widget.LinearLayoutManager\nimport androidx.recyclerview.widget.RecyclerView\nimport androidx.recyclerview.widget.StaggeredGridLayoutManager\nimport io.legado.app.R\nimport io.legado.app.lib.theme.accentColor\nimport io.legado.app.utils.ColorUtils\nimport io.legado.app.utils.getCompatColor\nimport kotlin.math.max\nimport kotlin.math.min\nimport kotlin.math.roundToInt\n\n\n@Suppress(\"SameParameterValue\")\nclass FastScroller : LinearLayout {\n    @ColorInt\n    private var mBubbleColor: Int = 0\n\n    @ColorInt\n    private var mHandleColor: Int = 0\n    private var mBubbleHeight: Int = 0\n    private var mHandleHeight: Int = 0\n    private var mViewHeight: Int = 0\n    private var mFadeScrollbar: Boolean = false\n    private var mShowBubble: Boolean = false\n    private var mSectionIndexer: SectionIndexer? = null\n    private var mScrollbarAnimator: ViewPropertyAnimator? = null\n    private var mBubbleAnimator: ViewPropertyAnimator? = null\n    private var mRecyclerView: RecyclerView? = null\n    private lateinit var mBubbleView: TextView\n    private lateinit var mHandleView: ImageView\n    private lateinit var mTrackView: ImageView\n    private lateinit var mScrollbar: View\n    private var mBubbleImage: Drawable? = null\n    private var mHandleImage: Drawable? = null\n    private var mTrackImage: Drawable? = null\n    private var mFastScrollStateChangeListener: FastScrollStateChangeListener? = null\n    private val mScrollbarHider = Runnable { this.hideScrollbar() }\n\n    private val mScrollListener = object : RecyclerView.OnScrollListener() {\n        override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {\n            if (!mHandleView.isSelected && isEnabled) {\n                setViewPositions(getScrollProportion(recyclerView))\n            }\n        }\n\n        override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {\n            super.onScrollStateChanged(recyclerView, newState)\n            if (isEnabled) {\n                when (newState) {\n                    RecyclerView.SCROLL_STATE_DRAGGING -> {\n                        handler.removeCallbacks(mScrollbarHider)\n                        cancelAnimation(mScrollbarAnimator)\n                        if (!isViewVisible(mScrollbar)) {\n                            showScrollbar()\n                        }\n                    }\n                    RecyclerView.SCROLL_STATE_IDLE -> if (mFadeScrollbar && !mHandleView.isSelected) {\n                        handler.postDelayed(mScrollbarHider, sScrollbarHideDelay.toLong())\n                    }\n                }\n            }\n        }\n    }\n\n    constructor(context: Context) : super(context) {\n        layout(context, null)\n        layoutParams = LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.MATCH_PARENT)\n    }\n\n    @JvmOverloads\n    constructor(context: Context, attrs: AttributeSet?, defStyleAttr: Int = 0) : super(\n        context,\n        attrs,\n        defStyleAttr\n    ) {\n        layout(context, attrs)\n        layoutParams = generateLayoutParams(attrs)\n    }\n\n    override fun setLayoutParams(params: ViewGroup.LayoutParams) {\n        params.width = LayoutParams.WRAP_CONTENT\n        super.setLayoutParams(params)\n    }\n\n    fun setLayoutParams(viewGroup: ViewGroup) {\n        @IdRes val recyclerViewId = mRecyclerView?.id ?: View.NO_ID\n        val marginTop = resources.getDimensionPixelSize(R.dimen.fastscroll_scrollbar_margin_top)\n        val marginBottom =\n            resources.getDimensionPixelSize(R.dimen.fastscroll_scrollbar_margin_bottom)\n        require(recyclerViewId != View.NO_ID) { \"RecyclerView must have a view ID\" }\n        when (viewGroup) {\n            is ConstraintLayout -> {\n                val constraintSet = ConstraintSet()\n                @IdRes val layoutId = id\n                constraintSet.clone(viewGroup)\n                constraintSet.connect(\n                    layoutId,\n                    ConstraintSet.TOP,\n                    recyclerViewId,\n                    ConstraintSet.TOP\n                )\n                constraintSet.connect(\n                    layoutId,\n                    ConstraintSet.BOTTOM,\n                    recyclerViewId,\n                    ConstraintSet.BOTTOM\n                )\n                constraintSet.connect(\n                    layoutId,\n                    ConstraintSet.END,\n                    recyclerViewId,\n                    ConstraintSet.END\n                )\n                constraintSet.applyTo(viewGroup)\n                val layoutParams = layoutParams as ConstraintLayout.LayoutParams\n                layoutParams.setMargins(0, marginTop, 0, marginBottom)\n                setLayoutParams(layoutParams)\n            }\n            is CoordinatorLayout -> {\n                val layoutParams = layoutParams as CoordinatorLayout.LayoutParams\n                layoutParams.anchorId = recyclerViewId\n                layoutParams.anchorGravity = GravityCompat.END\n                layoutParams.setMargins(0, marginTop, 0, marginBottom)\n                setLayoutParams(layoutParams)\n            }\n            is FrameLayout -> {\n                val layoutParams = layoutParams as FrameLayout.LayoutParams\n                layoutParams.gravity = GravityCompat.END\n                layoutParams.setMargins(0, marginTop, 0, marginBottom)\n                setLayoutParams(layoutParams)\n            }\n            is RelativeLayout -> {\n                val layoutParams = layoutParams as RelativeLayout.LayoutParams\n                val endRule = RelativeLayout.ALIGN_END\n                layoutParams.addRule(RelativeLayout.ALIGN_TOP, recyclerViewId)\n                layoutParams.addRule(RelativeLayout.ALIGN_BOTTOM, recyclerViewId)\n                layoutParams.addRule(endRule, recyclerViewId)\n                layoutParams.setMargins(0, marginTop, 0, marginBottom)\n                setLayoutParams(layoutParams)\n            }\n            else -> throw IllegalArgumentException(\"Parent ViewGroup must be a ConstraintLayout, CoordinatorLayout, FrameLayout, or RelativeLayout\")\n        }\n        updateViewHeights()\n    }\n\n    fun setSectionIndexer(sectionIndexer: SectionIndexer?) {\n        mSectionIndexer = sectionIndexer\n    }\n\n    fun attachRecyclerView(recyclerView: RecyclerView) {\n        mRecyclerView = recyclerView\n        mRecyclerView!!.addOnScrollListener(mScrollListener)\n        post {\n            // set initial positions for bubble and handle\n            setViewPositions(getScrollProportion(mRecyclerView))\n        }\n    }\n\n    fun detachRecyclerView() {\n        if (mRecyclerView != null) {\n            mRecyclerView!!.removeOnScrollListener(mScrollListener)\n            mRecyclerView = null\n        }\n    }\n\n    /**\n     * Hide the scrollbar when not scrolling.\n     *\n     * @param fadeScrollbar True to hide the scrollbar, false to show\n     */\n    fun setFadeScrollbar(fadeScrollbar: Boolean) {\n        mFadeScrollbar = fadeScrollbar\n        mScrollbar.visibility = if (fadeScrollbar) View.INVISIBLE else View.VISIBLE\n    }\n\n    /**\n     * Show the section bubble while scrolling.\n     *\n     * @param visible True to show the bubble, false to hide\n     */\n    fun setBubbleVisible(visible: Boolean) {\n        mShowBubble = visible\n    }\n\n    /**\n     * Display a scroll track while scrolling.\n     *\n     * @param visible True to show scroll track, false to hide\n     */\n    fun setTrackVisible(visible: Boolean) {\n        mTrackView.visibility = if (visible) View.VISIBLE else View.INVISIBLE\n    }\n\n    /**\n     * Set the color of the scroll track.\n     *\n     * @param color The color for the scroll track\n     */\n    fun setTrackColor(@ColorInt color: Int) {\n        if (mTrackImage == null) {\n            val drawable = ContextCompat.getDrawable(context, R.drawable.fastscroll_track)\n            if (drawable != null) {\n                mTrackImage = DrawableCompat.wrap(drawable)\n            }\n        }\n        DrawableCompat.setTint(mTrackImage!!, color)\n        mTrackView.setImageDrawable(mTrackImage)\n    }\n\n    /**\n     * Set the color for the scroll handle.\n     *\n     * @param color The color for the scroll handle\n     */\n    fun setHandleColor(@ColorInt color: Int) {\n        mHandleColor = color\n        if (mHandleImage == null) {\n            val drawable = ContextCompat.getDrawable(context, R.drawable.fastscroll_handle)\n            if (drawable != null) {\n                mHandleImage = DrawableCompat.wrap(drawable)\n            }\n        }\n        DrawableCompat.setTint(mHandleImage!!, mHandleColor)\n        mHandleView.setImageDrawable(mHandleImage)\n    }\n\n    /**\n     * Set the background color of the index bubble.\n     *\n     * @param color The background color for the index bubble\n     */\n    fun setBubbleColor(@ColorInt color: Int) {\n        mBubbleColor = color\n        if (mBubbleImage == null) {\n            val drawable = ContextCompat.getDrawable(context, R.drawable.fastscroll_bubble)\n            if (drawable != null) {\n                mBubbleImage = DrawableCompat.wrap(drawable)\n            }\n        }\n        DrawableCompat.setTint(mBubbleImage!!, mBubbleColor)\n        mBubbleView.background = mBubbleImage\n    }\n\n    /**\n     * Set the text color of the index bubble.\n     *\n     * @param color The text color for the index bubble\n     */\n    fun setBubbleTextColor(@ColorInt color: Int) {\n        mBubbleView.setTextColor(color)\n    }\n\n    /**\n     * Set the fast scroll state change listener.\n     *\n     * @param fastScrollStateChangeListener The interface that will listen to fastscroll state change events\n     */\n    fun setFastScrollStateChangeListener(fastScrollStateChangeListener: FastScrollStateChangeListener) {\n        mFastScrollStateChangeListener = fastScrollStateChangeListener\n    }\n\n    override fun setEnabled(enabled: Boolean) {\n        super.setEnabled(enabled)\n        visibility = if (enabled) View.VISIBLE else View.INVISIBLE\n    }\n\n    @Suppress(\"DEPRECATION\")\n    @SuppressLint(\"ClickableViewAccessibility\")\n    override fun onTouchEvent(event: MotionEvent): Boolean {\n        when (event.action) {\n            MotionEvent.ACTION_DOWN -> {\n                if (event.x < mHandleView.x - ViewCompat.getPaddingStart(mHandleView)) {\n                    return false\n                }\n                if (!mScrollbar.isVisible) {\n                    return false\n                }\n                requestDisallowInterceptTouchEvent(true)\n                setHandleSelected(true)\n                handler.removeCallbacks(mScrollbarHider)\n                cancelAnimation(mScrollbarAnimator)\n                cancelAnimation(mBubbleAnimator)\n                if (mShowBubble && mSectionIndexer != null) {\n                    showBubble()\n                }\n                if (mFastScrollStateChangeListener != null) {\n                    mFastScrollStateChangeListener!!.onFastScrollStart(this)\n                }\n                val y = event.y\n                setViewPositions(y)\n                setRecyclerViewPosition(y)\n                return true\n            }\n            MotionEvent.ACTION_MOVE -> {\n                val y = event.y\n                setViewPositions(y)\n                setRecyclerViewPosition(y)\n                return true\n            }\n            MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> {\n                requestDisallowInterceptTouchEvent(false)\n                setHandleSelected(false)\n                if (mFadeScrollbar) {\n                    handler.postDelayed(mScrollbarHider, sScrollbarHideDelay.toLong())\n                }\n                hideBubble()\n                if (mFastScrollStateChangeListener != null) {\n                    mFastScrollStateChangeListener!!.onFastScrollStop(this)\n                }\n                return true\n            }\n        }\n        return super.onTouchEvent(event)\n    }\n\n    override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {\n        super.onSizeChanged(w, h, oldw, oldh)\n        mViewHeight = h\n    }\n\n    private fun setRecyclerViewPosition(y: Float) {\n        mRecyclerView?.adapter?.let { adapter ->\n            val itemCount = adapter.itemCount\n            val proportion: Float = when {\n                mHandleView.y == 0f -> 0f\n                mHandleView.y + mHandleHeight >= mViewHeight - sTrackSnapRange -> 1f\n                else -> y / mViewHeight.toFloat()\n            }\n            var scrolledItemCount = (proportion * itemCount).roundToInt()\n            if (isLayoutReversed(mRecyclerView?.layoutManager)) {\n                scrolledItemCount = itemCount - scrolledItemCount\n            }\n            val targetPos = getValueInRange(0, itemCount - 1, scrolledItemCount)\n            mRecyclerView?.layoutManager?.scrollToPosition(targetPos)\n            mSectionIndexer?.let { sectionIndexer ->\n                if (mShowBubble) {\n                    mBubbleView.text = sectionIndexer.getSectionText(targetPos)\n                }\n            }\n        }\n    }\n\n    private fun getScrollProportion(recyclerView: RecyclerView?): Float {\n        recyclerView ?: return 0f\n        val verticalScrollOffset = recyclerView.computeVerticalScrollOffset()\n        val verticalScrollRange = recyclerView.computeVerticalScrollRange()\n        val rangeDiff = (verticalScrollRange - mViewHeight).toFloat()\n        val proportion = verticalScrollOffset.toFloat() / if (rangeDiff > 0) rangeDiff else 1f\n        return mViewHeight * proportion\n    }\n\n    private fun getValueInRange(min: Int, max: Int, value: Int): Int {\n        val minimum = max(min, value)\n        return min(minimum, max)\n    }\n\n    private fun setViewPositions(y: Float) {\n        mBubbleHeight = mBubbleView.height\n        mHandleHeight = mHandleView.height\n        val bubbleY = getValueInRange(\n            0,\n            mViewHeight - mBubbleHeight - mHandleHeight / 2,\n            (y - mBubbleHeight).toInt()\n        )\n        val handleY =\n            getValueInRange(0, mViewHeight - mHandleHeight, (y - mHandleHeight / 2).toInt())\n        if (mShowBubble) {\n            mBubbleView.y = bubbleY.toFloat()\n        }\n        mHandleView.y = handleY.toFloat()\n    }\n\n    private fun updateViewHeights() {\n        val measureSpec =\n            MeasureSpec.makeMeasureSpec(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED)\n        mBubbleView.measure(measureSpec, measureSpec)\n        mBubbleHeight = mBubbleView.measuredHeight\n        mHandleView.measure(measureSpec, measureSpec)\n        mHandleHeight = mHandleView.measuredHeight\n    }\n\n    private fun isLayoutReversed(layoutManager: RecyclerView.LayoutManager?): Boolean {\n        if (layoutManager is LinearLayoutManager) {\n            return layoutManager.reverseLayout\n        } else if (layoutManager is StaggeredGridLayoutManager) {\n            return layoutManager.reverseLayout\n        }\n        return false\n    }\n\n    private fun isViewVisible(view: View?): Boolean {\n        return view != null && view.visibility == View.VISIBLE\n    }\n\n    private fun cancelAnimation(animator: ViewPropertyAnimator?) {\n        animator?.cancel()\n    }\n\n    private fun showBubble() {\n        if (!isViewVisible(mBubbleView)) {\n            mBubbleView.visibility = View.VISIBLE\n            mBubbleAnimator = mBubbleView.animate().alpha(1f)\n                .setDuration(sBubbleAnimDuration.toLong())\n                .setListener(object : AnimatorListenerAdapter() {\n\n                    // adapter required for new alpha value to stick\n                })\n        }\n    }\n\n    private fun hideBubble() {\n        if (isViewVisible(mBubbleView)) {\n            mBubbleAnimator = mBubbleView.animate().alpha(0f)\n                .setDuration(sBubbleAnimDuration.toLong())\n                .setListener(object : AnimatorListenerAdapter() {\n                    override fun onAnimationEnd(animation: Animator) {\n                        super.onAnimationEnd(animation)\n                        mBubbleView.visibility = View.INVISIBLE\n                        mBubbleAnimator = null\n                    }\n\n                    override fun onAnimationCancel(animation: Animator) {\n                        super.onAnimationCancel(animation)\n                        mBubbleView.visibility = View.INVISIBLE\n                        mBubbleAnimator = null\n                    }\n                })\n        }\n    }\n\n    private fun showScrollbar() {\n        mRecyclerView?.let { mRecyclerView ->\n            if (mRecyclerView.computeVerticalScrollRange() - mViewHeight > 0) {\n                val transX =\n                    resources.getDimensionPixelSize(R.dimen.fastscroll_scrollbar_padding_end)\n                        .toFloat()\n                mScrollbar.translationX = transX\n                mScrollbar.visibility = View.VISIBLE\n                mScrollbarAnimator = mScrollbar.animate().translationX(0f).alpha(1f)\n                    .setDuration(sScrollbarAnimDuration.toLong())\n                    .setListener(object : AnimatorListenerAdapter() {\n\n                        // adapter required for new alpha value to stick\n                    })\n            }\n        }\n    }\n\n    private fun hideScrollbar() {\n        val transX =\n            resources.getDimensionPixelSize(R.dimen.fastscroll_scrollbar_padding_end).toFloat()\n        mScrollbarAnimator = mScrollbar.animate().translationX(transX).alpha(0f)\n            .setDuration(sScrollbarAnimDuration.toLong())\n            .setListener(object : AnimatorListenerAdapter() {\n                override fun onAnimationEnd(animation: Animator) {\n                    super.onAnimationEnd(animation)\n                    mScrollbar.visibility = View.INVISIBLE\n                    mScrollbarAnimator = null\n                }\n\n                override fun onAnimationCancel(animation: Animator) {\n                    super.onAnimationCancel(animation)\n                    mScrollbar.visibility = View.INVISIBLE\n                    mScrollbarAnimator = null\n                }\n            })\n    }\n\n    private fun setHandleSelected(selected: Boolean) {\n        mHandleView.isSelected = selected\n        DrawableCompat.setTint(mHandleImage!!, if (selected) mBubbleColor else mHandleColor)\n    }\n\n    private fun layout(context: Context, attrs: AttributeSet?) {\n        View.inflate(context, R.layout.view_fastscroller, this)\n        clipChildren = false\n        orientation = HORIZONTAL\n        mBubbleView = findViewById(R.id.fastscroll_bubble)\n        mHandleView = findViewById(R.id.fastscroll_handle)\n        mTrackView = findViewById(R.id.fastscroll_track)\n        mScrollbar = findViewById(R.id.fastscroll_scrollbar)\n        @ColorInt var bubbleColor = ColorUtils.adjustAlpha(context.accentColor, 0.8f)\n        @ColorInt var handleColor = context.accentColor\n        @ColorInt var trackColor = context.getCompatColor(R.color.transparent30)\n        @ColorInt var textColor =\n            if (ColorUtils.isColorLight(bubbleColor)) Color.BLACK else Color.WHITE\n        var fadeScrollbar = true\n        var showBubble = false\n        var showTrack = true\n        if (attrs != null) {\n            val typedArray = context.obtainStyledAttributes(attrs, R.styleable.FastScroller, 0, 0)\n            try {\n                bubbleColor = typedArray.getColor(R.styleable.FastScroller_bubbleColor, bubbleColor)\n                handleColor = typedArray.getColor(R.styleable.FastScroller_handleColor, handleColor)\n                trackColor = typedArray.getColor(R.styleable.FastScroller_trackColor, trackColor)\n                textColor = typedArray.getColor(R.styleable.FastScroller_bubbleTextColor, textColor)\n                fadeScrollbar =\n                    typedArray.getBoolean(R.styleable.FastScroller_fadeScrollbar, fadeScrollbar)\n                showBubble = typedArray.getBoolean(R.styleable.FastScroller_showBubble, showBubble)\n                showTrack = typedArray.getBoolean(R.styleable.FastScroller_showTrack, showTrack)\n            } finally {\n                typedArray.recycle()\n            }\n        }\n        setTrackColor(trackColor)\n        setHandleColor(handleColor)\n        setBubbleColor(bubbleColor)\n        setBubbleTextColor(textColor)\n        setFadeScrollbar(fadeScrollbar)\n        setBubbleVisible(showBubble)\n        setTrackVisible(showTrack)\n    }\n\n    interface SectionIndexer {\n        fun getSectionText(position: Int): String\n    }\n\n    companion object {\n        private const val sBubbleAnimDuration = 100\n        private const val sScrollbarAnimDuration = 300\n        private const val sScrollbarHideDelay = 1000\n        private const val sTrackSnapRange = 5\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/widget/seekbar/SeekBarChangeListener.kt",
    "content": "package io.legado.app.ui.widget.seekbar\n\nimport android.widget.SeekBar\n\ninterface SeekBarChangeListener : SeekBar.OnSeekBarChangeListener {\n\n    override fun onProgressChanged(seekBar: SeekBar, progress: Int, fromUser: Boolean) {\n\n    }\n\n    override fun onStartTrackingTouch(seekBar: SeekBar) {\n\n    }\n\n    override fun onStopTrackingTouch(seekBar: SeekBar) {\n\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/widget/seekbar/VerticalSeekBar.kt",
    "content": "package io.legado.app.ui.widget.seekbar\n\nimport android.annotation.SuppressLint\nimport android.content.Context\nimport android.graphics.Canvas\nimport android.graphics.drawable.Drawable\nimport android.util.AttributeSet\nimport android.view.KeyEvent\nimport android.view.MotionEvent\nimport android.widget.ProgressBar\nimport androidx.appcompat.widget.AppCompatSeekBar\nimport androidx.core.view.ViewCompat\nimport io.legado.app.R\nimport io.legado.app.lib.theme.accentColor\nimport io.legado.app.utils.applyTint\nimport java.lang.reflect.InvocationTargetException\nimport java.lang.reflect.Method\n\n@Suppress(\"SameParameterValue\")\nclass VerticalSeekBar @JvmOverloads constructor(context: Context, attrs: AttributeSet? = null) :\n    AppCompatSeekBar(context, attrs) {\n\n    private var mIsDragging: Boolean = false\n    private var mThumb: Drawable? = null\n    private var mMethodSetProgressFromUser: Method? = null\n    private var mRotationAngle = ROTATION_ANGLE_CW_90\n\n    var rotationAngle: Int\n        get() = mRotationAngle\n        set(angle) {\n            require(isValidRotationAngle(angle)) { \"Invalid angle specified :$angle\" }\n\n            if (mRotationAngle == angle) {\n                return\n            }\n\n            mRotationAngle = angle\n\n            if (useViewRotation()) {\n                val wrapper = wrapper\n                wrapper?.applyViewRotation()\n            } else {\n                requestLayout()\n            }\n        }\n\n    private val wrapper: VerticalSeekBarWrapper?\n        get() {\n            val parent = parent\n\n            return if (parent is VerticalSeekBarWrapper) {\n                parent\n            } else {\n                null\n            }\n        }\n\n    init {\n        if (!isInEditMode) {\n            applyTint(context.accentColor)\n        }\n        @Suppress(\"DEPRECATION\")\n        ViewCompat.setLayoutDirection(this, ViewCompat.LAYOUT_DIRECTION_LTR)\n\n        if (attrs != null) {\n            val a = context.obtainStyledAttributes(attrs, R.styleable.VerticalSeekBar)\n            val rotationAngle = a.getInteger(R.styleable.VerticalSeekBar_seekBarRotation, 0)\n            if (isValidRotationAngle(rotationAngle)) {\n                mRotationAngle = rotationAngle\n            }\n            a.recycle()\n        }\n    }\n\n    override fun setThumb(thumb: Drawable?) {\n        mThumb = thumb\n        super.setThumb(thumb)\n    }\n\n    @SuppressLint(\"ClickableViewAccessibility\")\n    override fun onTouchEvent(event: MotionEvent): Boolean {\n        return if (useViewRotation()) {\n            onTouchEventUseViewRotation(event)\n        } else {\n            onTouchEventTraditionalRotation(event)\n        }\n    }\n\n    private fun onTouchEventTraditionalRotation(event: MotionEvent): Boolean {\n        if (!isEnabled) {\n            return false\n        }\n\n        when (event.action) {\n            MotionEvent.ACTION_DOWN -> {\n                isPressed = true\n                onStartTrackingTouch()\n                trackTouchEvent(event)\n                attemptClaimDrag(true)\n                invalidate()\n            }\n\n            MotionEvent.ACTION_MOVE -> if (mIsDragging) {\n                trackTouchEvent(event)\n            }\n\n            MotionEvent.ACTION_UP -> {\n                if (mIsDragging) {\n                    trackTouchEvent(event)\n                    onStopTrackingTouch()\n                    isPressed = false\n                } else {\n                    // Touch up when we never crossed the touch slop threshold\n                    // should\n                    // be interpreted as a tap-seek to that location.\n                    onStartTrackingTouch()\n                    trackTouchEvent(event)\n                    onStopTrackingTouch()\n                    attemptClaimDrag(false)\n                }\n                // ProgressBar doesn't know to repaint the thumb drawable\n                // in its inactive state when the touch stops (because the\n                // value has not apparently changed)\n                invalidate()\n            }\n\n            MotionEvent.ACTION_CANCEL -> {\n                if (mIsDragging) {\n                    onStopTrackingTouch()\n                    isPressed = false\n                }\n                invalidate() // see above explanation\n            }\n        }\n        return true\n    }\n\n    private fun onTouchEventUseViewRotation(event: MotionEvent): Boolean {\n        val handled = super.onTouchEvent(event)\n\n        if (handled) {\n            when (event.action) {\n                MotionEvent.ACTION_DOWN -> attemptClaimDrag(true)\n\n                MotionEvent.ACTION_UP, MotionEvent.ACTION_CANCEL -> attemptClaimDrag(false)\n            }\n        }\n\n        return handled\n    }\n\n    private fun trackTouchEvent(event: MotionEvent) {\n        val paddingLeft = super.getPaddingLeft()\n        val paddingRight = super.getPaddingRight()\n        val height = height\n\n        val available = height - paddingLeft - paddingRight\n        val y = event.y.toInt()\n\n        val scale: Float\n        var value = 0f\n\n        when (mRotationAngle) {\n            ROTATION_ANGLE_CW_90 -> value = (y - paddingLeft).toFloat()\n            ROTATION_ANGLE_CW_270 -> value = (height - paddingLeft - y).toFloat()\n        }\n\n        scale = if (value < 0 || available == 0) {\n            0.0f\n        } else if (value > available) {\n            1.0f\n        } else {\n            value / available.toFloat()\n        }\n\n        val max = max\n        val progress = scale * max\n\n        setProgressFromUser(progress.toInt(), true)\n    }\n\n    /**\n     * Tries to claim the user's drag motion, and requests disallowing any\n     * ancestors from stealing events in the drag.\n     */\n    private fun attemptClaimDrag(active: Boolean) {\n        val parent = parent\n        parent?.requestDisallowInterceptTouchEvent(active)\n    }\n\n    /**\n     * This is called when the user has started touching this widget.\n     */\n    private fun onStartTrackingTouch() {\n        mIsDragging = true\n    }\n\n    /**\n     * This is called when the user either releases his touch or the touch is\n     * canceled.\n     */\n    private fun onStopTrackingTouch() {\n        mIsDragging = false\n    }\n\n    override fun onKeyDown(keyCode: Int, event: KeyEvent): Boolean {\n        if (isEnabled) {\n            val handled: Boolean\n            var direction = 0\n\n            when (keyCode) {\n                KeyEvent.KEYCODE_DPAD_DOWN -> {\n                    direction = if (mRotationAngle == ROTATION_ANGLE_CW_90) 1 else -1\n                    handled = true\n                }\n\n                KeyEvent.KEYCODE_DPAD_UP -> {\n                    direction = if (mRotationAngle == ROTATION_ANGLE_CW_270) 1 else -1\n                    handled = true\n                }\n\n                KeyEvent.KEYCODE_DPAD_LEFT, KeyEvent.KEYCODE_DPAD_RIGHT ->\n                    // move view focus to previous/next view\n                    return false\n\n                else -> handled = false\n            }\n\n            if (handled) {\n                val keyProgressIncrement = keyProgressIncrement\n                var progress = progress\n\n                progress += direction * keyProgressIncrement\n\n                if (progress in 0..max) {\n                    setProgressFromUser(progress, true)\n                }\n\n                return true\n            }\n        }\n\n        return super.onKeyDown(keyCode, event)\n    }\n\n    @Synchronized\n    override fun setProgress(progress: Int) {\n        super.setProgress(progress)\n        if (!useViewRotation()) {\n            refreshThumb()\n        }\n    }\n\n    @Synchronized\n    private fun setProgressFromUser(progress: Int, fromUser: Boolean) {\n        if (mMethodSetProgressFromUser == null) {\n            try {\n                val m: Method = ProgressBar::class.java.getDeclaredMethod(\n                    \"setProgress\",\n                    Int::class.javaPrimitiveType,\n                    Boolean::class.javaPrimitiveType\n                )\n                m.isAccessible = true\n                mMethodSetProgressFromUser = m\n            } catch (_: NoSuchMethodException) {\n            }\n\n        }\n\n        if (mMethodSetProgressFromUser != null) {\n            try {\n                mMethodSetProgressFromUser!!.invoke(this, progress, fromUser)\n            } catch (_: IllegalArgumentException) {\n            } catch (_: IllegalAccessException) {\n            } catch (_: InvocationTargetException) {\n            }\n\n        } else {\n            super.setProgress(progress)\n        }\n        refreshThumb()\n    }\n\n    @Synchronized\n    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {\n        if (useViewRotation()) {\n            super.onMeasure(widthMeasureSpec, heightMeasureSpec)\n        } else {\n            super.onMeasure(heightMeasureSpec, widthMeasureSpec)\n\n            val lp = layoutParams\n\n            if (isInEditMode && lp != null && lp.height >= 0) {\n                setMeasuredDimension(super.getMeasuredHeight(), lp.height)\n            } else {\n                setMeasuredDimension(super.getMeasuredHeight(), super.getMeasuredWidth())\n            }\n        }\n    }\n\n    override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {\n        if (useViewRotation()) {\n            super.onSizeChanged(w, h, oldw, oldh)\n        } else {\n            super.onSizeChanged(h, w, oldh, oldw)\n        }\n    }\n\n    @Synchronized\n    override fun onDraw(canvas: Canvas) {\n        if (!useViewRotation()) {\n            when (mRotationAngle) {\n                ROTATION_ANGLE_CW_90 -> {\n                    canvas.rotate(90f)\n                    canvas.translate(0f, (-super.getWidth()).toFloat())\n                }\n\n                ROTATION_ANGLE_CW_270 -> {\n                    canvas.rotate(-90f)\n                    canvas.translate((-super.getHeight()).toFloat(), 0f)\n                }\n            }\n        }\n\n        super.onDraw(canvas)\n    }\n\n    // refresh thumb position\n    private fun refreshThumb() {\n        onSizeChanged(super.getWidth(), super.getHeight(), 0, 0)\n    }\n\n    /*package*/\n    internal fun useViewRotation(): Boolean {\n        return !isInEditMode\n    }\n\n    companion object {\n        const val ROTATION_ANGLE_CW_90 = 90\n        const val ROTATION_ANGLE_CW_270 = 270\n\n        private fun isValidRotationAngle(angle: Int): Boolean {\n            return angle == ROTATION_ANGLE_CW_90 || angle == ROTATION_ANGLE_CW_270\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/widget/seekbar/VerticalSeekBarWrapper.kt",
    "content": "package io.legado.app.ui.widget.seekbar\n\nimport android.annotation.SuppressLint\nimport android.content.Context\nimport android.util.AttributeSet\nimport android.view.Gravity\nimport android.view.View\nimport android.view.ViewGroup\nimport android.widget.FrameLayout\n\nimport androidx.core.view.ViewCompat\nimport kotlin.math.max\n\nclass VerticalSeekBarWrapper @JvmOverloads constructor(\n    context: Context,\n    attrs: AttributeSet? = null\n) : FrameLayout(context, attrs) {\n\n    private val childSeekBar: VerticalSeekBar?\n        get() {\n            val child = if (childCount > 0) getChildAt(0) else null\n            return if (child is VerticalSeekBar) child else null\n        }\n\n    override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {\n        if (useViewRotation()) {\n            onSizeChangedUseViewRotation(w, h, oldw, oldh)\n        } else {\n            onSizeChangedTraditionalRotation(w, h, oldw, oldh)\n        }\n    }\n\n    @SuppressLint(\"RtlHardcoded\")\n    private fun onSizeChangedTraditionalRotation(w: Int, h: Int, oldw: Int, oldh: Int) {\n        val seekBar = childSeekBar\n\n        if (seekBar != null) {\n            val hPadding = paddingLeft + paddingRight\n            val vPadding = paddingTop + paddingBottom\n            val lp = seekBar.layoutParams as LayoutParams\n\n            lp.width = ViewGroup.LayoutParams.WRAP_CONTENT\n            lp.height = max(0, h - vPadding)\n            seekBar.layoutParams = lp\n\n            seekBar.measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED)\n\n            val seekBarMeasuredWidth = seekBar.measuredWidth\n            seekBar.measure(\n                MeasureSpec.makeMeasureSpec(\n                    max(0, w - hPadding),\n                    MeasureSpec.AT_MOST\n                ),\n                MeasureSpec.makeMeasureSpec(\n                    max(0, h - vPadding),\n                    MeasureSpec.EXACTLY\n                )\n            )\n\n            lp.gravity = Gravity.TOP or Gravity.LEFT\n            lp.leftMargin = (max(0, w - hPadding) - seekBarMeasuredWidth) / 2\n            seekBar.layoutParams = lp\n        }\n\n        super.onSizeChanged(w, h, oldw, oldh)\n    }\n\n    private fun onSizeChangedUseViewRotation(w: Int, h: Int, oldw: Int, oldh: Int) {\n        val seekBar = childSeekBar\n\n        if (seekBar != null) {\n            val hPadding = paddingLeft + paddingRight\n            val vPadding = paddingTop + paddingBottom\n            seekBar.measure(\n                MeasureSpec.makeMeasureSpec(\n                    max(0, h - vPadding),\n                    MeasureSpec.EXACTLY\n                ),\n                MeasureSpec.makeMeasureSpec(\n                    max(0, w - hPadding),\n                    MeasureSpec.AT_MOST\n                )\n            )\n        }\n\n        applyViewRotation(w, h)\n        super.onSizeChanged(w, h, oldw, oldh)\n    }\n\n    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {\n        val seekBar = childSeekBar\n        val widthMode = MeasureSpec.getMode(widthMeasureSpec)\n        val heightMode = MeasureSpec.getMode(heightMeasureSpec)\n        val widthSize = MeasureSpec.getSize(widthMeasureSpec)\n        val heightSize = MeasureSpec.getSize(heightMeasureSpec)\n\n        if (seekBar != null && widthMode != MeasureSpec.EXACTLY) {\n            val seekBarWidth: Int\n            val seekBarHeight: Int\n            val hPadding = paddingLeft + paddingRight\n            val vPadding = paddingTop + paddingBottom\n            val innerContentWidthMeasureSpec =\n                MeasureSpec.makeMeasureSpec(max(0, widthSize - hPadding), widthMode)\n            val innerContentHeightMeasureSpec =\n                MeasureSpec.makeMeasureSpec(max(0, heightSize - vPadding), heightMode)\n\n            if (useViewRotation()) {\n                seekBar.measure(innerContentHeightMeasureSpec, innerContentWidthMeasureSpec)\n                seekBarWidth = seekBar.measuredHeight\n                seekBarHeight = seekBar.measuredWidth\n            } else {\n                seekBar.measure(innerContentWidthMeasureSpec, innerContentHeightMeasureSpec)\n                seekBarWidth = seekBar.measuredWidth\n                seekBarHeight = seekBar.measuredHeight\n            }\n\n            val measuredWidth =\n                View.resolveSizeAndState(seekBarWidth + hPadding, widthMeasureSpec, 0)\n            val measuredHeight =\n                View.resolveSizeAndState(seekBarHeight + vPadding, heightMeasureSpec, 0)\n\n            setMeasuredDimension(measuredWidth, measuredHeight)\n        } else {\n            super.onMeasure(widthMeasureSpec, heightMeasureSpec)\n        }\n    }\n\n    /*package*/\n    internal fun applyViewRotation() {\n        applyViewRotation(width, height)\n    }\n\n    @Suppress(\"DEPRECATION\")\n    private fun applyViewRotation(w: Int, h: Int) {\n        val seekBar = childSeekBar\n\n        if (seekBar != null) {\n            val isLTR = ViewCompat.getLayoutDirection(this) == ViewCompat.LAYOUT_DIRECTION_LTR\n            val rotationAngle = seekBar.rotationAngle\n            val seekBarMeasuredWidth = seekBar.measuredWidth\n            val seekBarMeasuredHeight = seekBar.measuredHeight\n            val hPadding = paddingLeft + paddingRight\n            val vPadding = paddingTop + paddingBottom\n            val hOffset = (max(0, w - hPadding) - seekBarMeasuredHeight) * 0.5f\n            val lp = seekBar.layoutParams\n\n            lp.width = max(0, h - vPadding)\n            lp.height = ViewGroup.LayoutParams.WRAP_CONTENT\n\n            seekBar.layoutParams = lp\n\n            seekBar.pivotX = (if (isLTR) 0 else max(0, h - vPadding)).toFloat()\n            seekBar.pivotY = 0f\n\n            when (rotationAngle) {\n                VerticalSeekBar.ROTATION_ANGLE_CW_90 -> {\n                    seekBar.rotation = 90f\n                    if (isLTR) {\n                        seekBar.translationX = seekBarMeasuredHeight + hOffset\n                        seekBar.translationY = 0f\n                    } else {\n                        seekBar.translationX = -hOffset\n                        seekBar.translationY = seekBarMeasuredWidth.toFloat()\n                    }\n                }\n                VerticalSeekBar.ROTATION_ANGLE_CW_270 -> {\n                    seekBar.rotation = 270f\n                    if (isLTR) {\n                        seekBar.translationX = hOffset\n                        seekBar.translationY = seekBarMeasuredWidth.toFloat()\n                    } else {\n                        seekBar.translationX = -(seekBarMeasuredHeight + hOffset)\n                        seekBar.translationY = 0f\n                    }\n                }\n            }\n        }\n    }\n\n    private fun useViewRotation(): Boolean {\n        val seekBar = childSeekBar\n        return seekBar?.useViewRotation() ?: false\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/widget/text/AccentBgTextView.kt",
    "content": "package io.legado.app.ui.widget.text\n\nimport android.content.Context\nimport android.graphics.Color\nimport android.util.AttributeSet\nimport androidx.appcompat.widget.AppCompatTextView\nimport io.legado.app.R\nimport io.legado.app.lib.theme.Selector\nimport io.legado.app.lib.theme.ThemeStore\nimport io.legado.app.utils.ColorUtils\nimport io.legado.app.utils.dpToPx\nimport io.legado.app.utils.getCompatColor\n\nclass AccentBgTextView @JvmOverloads constructor(\n    context: Context,\n    attrs: AttributeSet? = null\n) : AppCompatTextView(context, attrs) {\n\n    private var radius = 0\n\n    init {\n        val typedArray = context.obtainStyledAttributes(attrs, R.styleable.AccentBgTextView)\n        radius = typedArray.getDimensionPixelOffset(R.styleable.AccentBgTextView_radius, radius)\n        typedArray.recycle()\n        upBackground()\n    }\n\n    fun setRadius(radius: Int) {\n        this.radius = radius.dpToPx()\n        upBackground()\n    }\n\n    private fun upBackground() {\n        val accentColor = if (isInEditMode) {\n            context.getCompatColor(R.color.accent)\n        } else {\n            ThemeStore.accentColor(context)\n        }\n        background = Selector.shapeBuild()\n            .setCornerRadius(radius)\n            .setDefaultBgColor(accentColor)\n            .setPressedBgColor(ColorUtils.darkenColor(accentColor))\n            .create()\n        setTextColor(\n            if (ColorUtils.isColorLight(accentColor)) {\n                Color.BLACK\n            } else {\n                Color.WHITE\n            }\n        )\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/widget/text/AccentStrokeTextView.kt",
    "content": "package io.legado.app.ui.widget.text\n\nimport android.content.Context\nimport android.util.AttributeSet\nimport androidx.appcompat.widget.AppCompatTextView\nimport io.legado.app.R\nimport io.legado.app.lib.theme.Selector\nimport io.legado.app.lib.theme.ThemeStore\nimport io.legado.app.lib.theme.bottomBackground\nimport io.legado.app.utils.ColorUtils\nimport io.legado.app.utils.dpToPx\nimport io.legado.app.utils.getCompatColor\n\nclass AccentStrokeTextView(context: Context, attrs: AttributeSet) :\n    AppCompatTextView(context, attrs) {\n\n    private var radius = 3.dpToPx()\n    private val isBottomBackground: Boolean\n\n    init {\n        val typedArray = context.obtainStyledAttributes(attrs, R.styleable.AccentStrokeTextView)\n        radius = typedArray.getDimensionPixelOffset(R.styleable.StrokeTextView_radius, radius)\n        isBottomBackground =\n            typedArray.getBoolean(R.styleable.StrokeTextView_isBottomBackground, false)\n        typedArray.recycle()\n        upStyle()\n    }\n\n    private fun upStyle() {\n        val isLight = ColorUtils.isColorLight(context.bottomBackground)\n        val disableColor = if (isBottomBackground) {\n            if (isLight) {\n                context.getCompatColor(R.color.md_light_disabled)\n            } else {\n                context.getCompatColor(R.color.md_dark_disabled)\n            }\n        } else {\n            context.getCompatColor(R.color.disabled)\n        }\n        val accentColor = if (isInEditMode) {\n            context.getCompatColor(R.color.accent)\n        } else {\n            ThemeStore.accentColor(context)\n        }\n        background = Selector.shapeBuild()\n            .setCornerRadius(radius)\n            .setStrokeWidth(1.dpToPx())\n            .setDisabledStrokeColor(disableColor)\n            .setDefaultStrokeColor(accentColor)\n            .setPressedBgColor(context.getCompatColor(R.color.transparent30))\n            .create()\n        setTextColor(\n            Selector.colorBuild()\n                .setDefaultColor(accentColor)\n                .setDisabledColor(disableColor)\n                .create()\n        )\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/widget/text/AccentTextView.kt",
    "content": "package io.legado.app.ui.widget.text\n\nimport android.content.Context\nimport android.util.AttributeSet\nimport androidx.appcompat.widget.AppCompatTextView\nimport io.legado.app.R\nimport io.legado.app.lib.theme.accentColor\nimport io.legado.app.utils.getCompatColor\n\nclass AccentTextView(context: Context, attrs: AttributeSet?) :\n    AppCompatTextView(context, attrs) {\n\n    init {\n        if (!isInEditMode) {\n            setTextColor(context.accentColor)\n        } else {\n            setTextColor(context.getCompatColor(R.color.accent))\n        }\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/widget/text/AutoCompleteTextView.kt",
    "content": "package io.legado.app.ui.widget.text\n\nimport android.annotation.SuppressLint\nimport android.content.Context\nimport android.os.Build\nimport android.util.AttributeSet\nimport android.view.LayoutInflater\nimport android.view.MotionEvent\nimport android.view.View\nimport android.view.ViewGroup\nimport android.widget.ArrayAdapter\nimport android.widget.ImageView\nimport android.widget.TextView\nimport androidx.appcompat.widget.AppCompatAutoCompleteTextView\nimport io.legado.app.R\nimport io.legado.app.lib.theme.accentColor\nimport io.legado.app.utils.applyTint\nimport io.legado.app.utils.gone\nimport io.legado.app.utils.visible\n\n\n@Suppress(\"unused\")\nclass AutoCompleteTextView @JvmOverloads constructor(\n    context: Context,\n    attrs: AttributeSet? = null\n) : AppCompatAutoCompleteTextView(context, attrs) {\n\n    var delCallBack: ((value: String) -> Unit)? = null\n\n    init {\n        applyTint(context.accentColor)\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM) {\n            isLocalePreferredLineHeightForMinimumUsed = false\n        }\n    }\n\n    override fun enoughToFilter(): Boolean {\n        return true\n    }\n\n    @SuppressLint(\"ClickableViewAccessibility\")\n    override fun onTouchEvent(event: MotionEvent?): Boolean {\n        if (event?.action == MotionEvent.ACTION_DOWN) {\n            showDropDown()\n        }\n        return super.onTouchEvent(event)\n    }\n\n    fun setFilterValues(values: List<String>?) {\n        values?.let {\n            setAdapter(MyAdapter(context, values))\n        }\n    }\n\n    fun setFilterValues(vararg value: String) {\n        setAdapter(MyAdapter(context, value.toMutableList()))\n    }\n\n    inner class MyAdapter(context: Context, values: List<String>) :\n        ArrayAdapter<String>(context, android.R.layout.simple_dropdown_item_1line, values) {\n\n        override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {\n            val view = convertView ?: LayoutInflater.from(context)\n                .inflate(R.layout.item_1line_text_and_del, parent, false)\n            val textView = view.findViewById<TextView>(R.id.text_view)\n            textView.text = getItem(position)\n            val ivDelete = view.findViewById<ImageView>(R.id.iv_delete)\n            if (delCallBack != null) ivDelete.visible() else ivDelete.gone()\n            ivDelete.setOnClickListener {\n                getItem(position)?.let {\n                    remove(it)\n                    delCallBack?.invoke(it)\n                    showDropDown()\n                }\n            }\n            return view\n        }\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/widget/text/BadgeView.kt",
    "content": "package io.legado.app.ui.widget.text\n\nimport android.content.Context\nimport android.graphics.Color\nimport android.graphics.drawable.ShapeDrawable\nimport android.graphics.drawable.shapes.RoundRectShape\nimport android.text.TextUtils\nimport android.util.AttributeSet\nimport android.util.TypedValue\nimport android.view.Gravity\nimport android.view.View\nimport android.view.ViewGroup\nimport android.widget.FrameLayout\nimport android.widget.FrameLayout.LayoutParams\nimport androidx.appcompat.widget.AppCompatTextView\nimport io.legado.app.R\nimport io.legado.app.lib.theme.accentColor\nimport io.legado.app.utils.ColorUtils\nimport io.legado.app.utils.getCompatColor\nimport io.legado.app.utils.invisible\nimport io.legado.app.utils.visible\n\n\n/**\n * Created by milad heydari on 5/6/2016.\n */\n@Suppress(\"MemberVisibilityCanBePrivate\", \"unused\")\nclass BadgeView @JvmOverloads constructor(\n    context: Context,\n    attrs: AttributeSet? = null\n) : AppCompatTextView(context, attrs) {\n\n    var isHideOnNull = true\n        set(hideOnNull) {\n            field = hideOnNull\n            text = text\n        }\n    private var radius: Float = 0.toFloat()\n    private var flatangle: Boolean\n\n    val badgeCount: Int?\n        get() {\n            if (text == null) {\n                return null\n            }\n            val text = text.toString()\n            return kotlin.runCatching {\n                Integer.parseInt(text)\n            }.getOrNull()\n        }\n\n    var badgeGravity: Int\n        get() {\n            val params = layoutParams as LayoutParams\n            return params.gravity\n        }\n        set(gravity) {\n            val params = layoutParams as LayoutParams\n            params.gravity = gravity\n            layoutParams = params\n        }\n\n    val badgeMargin: IntArray\n        get() {\n            val params = layoutParams as LayoutParams\n            return intArrayOf(\n                params.leftMargin,\n                params.topMargin,\n                params.rightMargin,\n                params.bottomMargin\n            )\n        }\n\n    init {\n        val typedArray = context.obtainStyledAttributes(attrs, R.styleable.BadgeView)\n        val radios =\n            typedArray.getDimensionPixelOffset(R.styleable.BadgeView_radius, 8)\n        flatangle =\n            typedArray.getBoolean(R.styleable.BadgeView_up_flat_angle, false)\n        typedArray.recycle()\n\n        if (layoutParams !is LayoutParams) {\n            val layoutParams = LayoutParams(\n                ViewGroup.LayoutParams.WRAP_CONTENT,\n                ViewGroup.LayoutParams.WRAP_CONTENT,\n                Gravity.CENTER\n            )\n            setLayoutParams(layoutParams)\n        }\n\n        //setTypeface(Typeface.DEFAULT_BOLD);\n        setTextSize(TypedValue.COMPLEX_UNIT_SP, 11f)\n        setPadding(dip2Px(5f), dip2Px(1f), dip2Px(5f), dip2Px(1f))\n        radius = radios.toFloat()\n\n        // set default background\n        setBackground(radius, context.accentColor)\n\n        gravity = Gravity.CENTER\n\n        // default values\n        isHideOnNull = true\n        setBadgeCount(0)\n        minWidth = dip2Px(16f)\n        minHeight = dip2Px(16f)\n    }\n\n    override fun setBackgroundColor(color: Int) {\n        val background = background\n        if (background is ShapeDrawable && background.paint.color == color) {\n            return\n        }\n        setBackground(radius, color)\n    }\n\n    fun setBackground(dipRadius: Float, badgeColor: Int) {\n        val radius = dip2Px(dipRadius).toFloat()\n        val radiusArray =\n            floatArrayOf(radius, radius, radius, radius, radius, radius, radius, radius)\n        if (flatangle) {\n            radiusArray.fill(0f, 0, 3)\n        }\n\n        val roundRect = RoundRectShape(radiusArray, null, null)\n        val bgDrawable = ShapeDrawable(roundRect)\n        bgDrawable.paint.color = badgeColor\n        background = bgDrawable\n        setTextColor(\n            if (ColorUtils.isColorLight(badgeColor)) {\n                Color.BLACK\n            } else {\n                Color.WHITE\n            }\n        )\n    }\n\n    /**\n     * @see android.widget.TextView.setText\n     */\n    override fun setText(text: CharSequence, type: BufferType) {\n        if (isHideOnNull && TextUtils.isEmpty(text)) {\n            invisible()\n        } else {\n            visible()\n        }\n        super.setText(text, type)\n    }\n\n    fun setBadgeCount(count: Int) {\n        text = if (count == 0) \"\" else count.toString()\n    }\n\n    fun setHighlight(highlight: Boolean) {\n        if (highlight) {\n            setBackgroundColor(context.accentColor)\n        } else {\n            setBackgroundColor(context.getCompatColor(R.color.darker_gray))\n        }\n    }\n\n    fun setBadgeMargin(dipMargin: Int) {\n        setBadgeMargin(dipMargin, dipMargin, dipMargin, dipMargin)\n    }\n\n    fun setBadgeMargin(\n        leftDipMargin: Int,\n        topDipMargin: Int,\n        rightDipMargin: Int,\n        bottomDipMargin: Int\n    ) {\n        val params = layoutParams as LayoutParams\n        params.leftMargin = dip2Px(leftDipMargin.toFloat())\n        params.topMargin = dip2Px(topDipMargin.toFloat())\n        params.rightMargin = dip2Px(rightDipMargin.toFloat())\n        params.bottomMargin = dip2Px(bottomDipMargin.toFloat())\n        layoutParams = params\n    }\n\n    fun incrementBadgeCount(increment: Int) {\n        val count = badgeCount\n        if (count == null) {\n            setBadgeCount(increment)\n        } else {\n            setBadgeCount(increment + count)\n        }\n    }\n\n    fun decrementBadgeCount(decrement: Int) {\n        incrementBadgeCount(-decrement)\n    }\n\n    /**\n     * Attach the BadgeView to the target view\n     * @param target the view to attach the BadgeView\n     */\n    fun setTargetView(target: View?) {\n        if (parent != null) {\n            (parent as ViewGroup).removeView(this)\n        }\n\n        if (target == null) {\n            return\n        }\n\n        if (target.parent is FrameLayout) {\n            (target.parent as FrameLayout).addView(this)\n\n        } else if (target.parent is ViewGroup) {\n            // use a new FrameLayout container for adding badge\n            val parentContainer = target.parent as ViewGroup\n            val groupIndex = parentContainer.indexOfChild(target)\n            parentContainer.removeView(target)\n\n            val badgeContainer = FrameLayout(context)\n            val parentLayoutParams = target.layoutParams\n\n            badgeContainer.layoutParams = parentLayoutParams\n            target.layoutParams = ViewGroup.LayoutParams(\n                ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT\n            )\n\n            parentContainer.addView(badgeContainer, groupIndex, parentLayoutParams)\n            badgeContainer.addView(target)\n\n            badgeContainer.addView(this)\n        }\n\n    }\n\n    /**\n     * converts dip to px\n     */\n    private fun dip2Px(dip: Float): Int {\n        return (dip * context.resources.displayMetrics.density + 0.5f).toInt()\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/widget/text/BevelLabelView.kt",
    "content": "package io.legado.app.ui.widget.text\n\nimport android.content.Context\nimport android.graphics.Canvas\nimport android.graphics.Color\nimport android.graphics.Paint\nimport android.graphics.Path\nimport android.util.AttributeSet\nimport android.util.TypedValue\nimport android.view.View\nimport androidx.annotation.ColorInt\nimport androidx.annotation.IntDef\nimport io.legado.app.R\nimport io.legado.app.lib.theme.accentColor\n\n/**\n * 斜角标签\n */\n@Suppress(\"unused\")\nclass BevelLabelView @JvmOverloads constructor(\n    context: Context,\n    attrs: AttributeSet? = null\n) : View(context, attrs) {\n\n    companion object {\n        const val MODE_LEFT_TOP = 0\n        const val MODE_RIGHT_TOP = 1\n        const val MODE_LEFT_BOTTOM = 2\n        const val MODE_RIGHT_BOTTOM = 3\n        const val MODE_LEFT_TOP_FILL = 4\n        const val MODE_RIGHT_TOP_FILL = 5\n        const val MODE_LEFT_BOTTOM_FILL = 6\n        const val MODE_RIGHT_BOTTOM_FILL = 7\n    }\n\n    private var mBgColor: Int\n    private var mText: String\n    private var mTextSize: Int\n    private var mTextColor: Int\n    private var mLength: Int\n    private var mCorner: Int\n    private var mMode: Int\n    private var mPaint: Paint = Paint(Paint.ANTI_ALIAS_FLAG)\n    private var path: Path = Path()\n    private var mWidth = 0\n    private var mHeight: Int = 0\n    private var mRotate = 45 //因为默认模式是1，所以这时是45度\n\n    private var mX: Int = 0\n    private var mY: Int = 0\n\n    init {\n        val typedArray = context.obtainStyledAttributes(attrs, R.styleable.BevelLabelView)\n        mBgColor = typedArray.getColor(\n            R.styleable.BevelLabelView_label_bg_color,\n            context.accentColor\n        ) //默认红色\n        mText = typedArray.getString(R.styleable.BevelLabelView_label_text) ?: \"\"\n        mTextSize =\n            typedArray.getDimensionPixelOffset(\n                R.styleable.BevelLabelView_label_text_size,\n                sp2px(11)\n            )\n        mTextColor = typedArray.getColor(R.styleable.BevelLabelView_label_text_color, Color.WHITE)\n        mLength =\n            typedArray.getDimensionPixelOffset(R.styleable.BevelLabelView_label_length, dip2px(40))\n        mCorner = typedArray.getDimensionPixelOffset(R.styleable.BevelLabelView_label_corner, 0)\n        mMode = typedArray.getInt(R.styleable.BevelLabelView_label_mode, 1)\n        mPaint.isAntiAlias = true\n        typedArray.recycle()\n    }\n\n    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {\n        super.onMeasure(widthMeasureSpec, heightMeasureSpec)\n        mWidth = MeasureSpec.getSize(widthMeasureSpec)\n        mHeight = mWidth\n    }\n\n    override fun onDraw(canvas: Canvas) {\n        mPaint.color = mBgColor\n        drawBackgroundText(canvas)\n    }\n\n    fun setMode(@BevelLabelMode mode: Int) {\n        mMode = mode\n        invalidate()\n    }\n\n    fun setTextColor(@ColorInt color: Int) {\n        mTextColor = color\n        invalidate()\n    }\n\n    fun setBgColor(@ColorInt color: Int) {\n        mBgColor = color\n        invalidate()\n    }\n\n    private fun drawBackgroundText(canvas: Canvas) {\n        check(mWidth == mHeight) {\n            \"width must equal to height\" //标签view 是一个正方形，\n        }\n        when (mMode) {\n            MODE_LEFT_TOP -> {\n                mCorner = 0 //没有铺满的时候mCorner要归零；\n                leftTopMeasure()\n                getLeftTop()\n            }\n            MODE_RIGHT_TOP -> {\n                mCorner = 0\n                rightTopMeasure()\n                getRightTop()\n            }\n            MODE_LEFT_BOTTOM -> {\n                mCorner = 0\n                leftBottomMeasure()\n                getLeftBottom()\n            }\n            MODE_RIGHT_BOTTOM -> {\n                mCorner = 0\n                rightBottomMeasure()\n                getRightBottom()\n            }\n            MODE_LEFT_TOP_FILL -> {\n                leftTopMeasure()\n                getLeftTopFill()\n                if (mCorner != 0) {\n                    canvas.drawPath(path, mPaint)\n                    getLeftTop()\n                }\n            }\n            MODE_RIGHT_TOP_FILL -> {\n                rightTopMeasure()\n                getRightTopFill()\n                if (mCorner != 0) {\n                    canvas.drawPath(path, mPaint)\n                    getRightTop()\n                }\n            }\n            MODE_LEFT_BOTTOM_FILL -> {\n                leftBottomMeasure()\n                getLeftBottomFill()\n                if (mCorner != 0) {\n                    canvas.drawPath(path, mPaint)\n                    getLeftBottom()\n                }\n            }\n            MODE_RIGHT_BOTTOM_FILL -> {\n                rightBottomMeasure()\n                getRightBottomFill()\n                if (mCorner != 0) {\n                    canvas.drawPath(path, mPaint)\n                    getRightBottom()\n                }\n            }\n            else -> {}\n        }\n        canvas.drawPath(path, mPaint)\n        mPaint.textSize = mTextSize.toFloat()\n        mPaint.textAlign = Paint.Align.CENTER\n        mPaint.color = mTextColor\n        canvas.translate(mX.toFloat(), mY.toFloat())\n        canvas.rotate(mRotate.toFloat())\n        val baseLineY = (-(mPaint.descent() + mPaint.ascent())).toInt() / 2 //基线中间点的y轴计算公式\n        canvas.drawText(mText, 0f, baseLineY.toFloat(), mPaint)\n    }\n\n    private fun rightBottomMeasure() {\n        mRotate = -45\n        mX = mWidth / 2 + mLength / 4\n        mY = mX\n    }\n\n    private fun leftBottomMeasure() {\n        mRotate = 45\n        mX = mWidth / 2 - mLength / 4\n        mY = mHeight / 2 + mLength / 4\n    }\n\n    private fun rightTopMeasure() {\n        mRotate = 45\n        mX = mWidth / 2 + mLength / 4\n        mY = mHeight / 2 - mLength / 4\n    }\n\n    private fun leftTopMeasure() {\n        mRotate = -45\n        mX = mWidth / 2 - mLength / 4\n        mY = mX\n    }\n\n    //左上角铺满\n    private fun getLeftTopFill() {\n        if (mCorner != 0) {\n            path.addRoundRect(\n                0f,\n                0f,\n                (mWidth / 2).toFloat(),\n                (mHeight / 2).toFloat(),\n                floatArrayOf(mCorner.toFloat(), mCorner.toFloat(), 0f, 0f, 0f, 0f, 0f, 0f),\n                Path.Direction.CW\n            )\n        } else {\n            path.moveTo(0f, 0f)\n            path.lineTo(mWidth.toFloat(), 0f)\n            path.lineTo(0f, mHeight.toFloat())\n            path.close()\n        }\n    }\n\n    //左上角不铺满\n    private fun getLeftTop() {\n        path.moveTo(if (mCorner != 0) mCorner.toFloat() else (mWidth - mLength).toFloat(), 0f)\n        path.lineTo(mWidth.toFloat(), 0f)\n        path.lineTo(0f, mHeight.toFloat())\n        path.lineTo(0f, if (mCorner != 0) mCorner.toFloat() else (mHeight - mLength).toFloat())\n        path.close()\n    }\n\n    //左下角铺满\n    private fun getLeftBottomFill() {\n        if (mCorner != 0) {\n            path.addRoundRect(\n                0f,\n                (mHeight / 2).toFloat(),\n                (mWidth / 2).toFloat(),\n                mHeight.toFloat(),\n                floatArrayOf(0f, 0f, 0f, 0f, 0f, 0f, mCorner.toFloat(), mCorner.toFloat()),\n                Path.Direction.CW\n            )\n        } else {\n            path.moveTo(0f, 0f)\n            path.lineTo(mWidth.toFloat(), mHeight.toFloat())\n            path.lineTo(0f, mHeight.toFloat())\n            path.close()\n        }\n    }\n\n\n    //左下角不铺满\n    private fun getLeftBottom() {\n        path.moveTo(0f, 0f)\n        path.lineTo(mWidth.toFloat(), mHeight.toFloat())\n        path.lineTo(\n            if (mCorner != 0) mCorner.toFloat() else (mWidth - mLength).toFloat(),\n            mHeight.toFloat()\n        )\n        path.lineTo(0f, if (mCorner != 0) (mHeight - mCorner).toFloat() else mLength.toFloat())\n        path.close()\n    }\n\n    //右上角铺满\n    private fun getRightTopFill() {\n        if (mCorner != 0) {\n            path.addRoundRect(\n                (mWidth / 2).toFloat(),\n                0f,\n                mWidth.toFloat(),\n                (mHeight / 2).toFloat(),\n                floatArrayOf(0f, 0f, mCorner.toFloat(), mCorner.toFloat(), 0f, 0f, 0f, 0f),\n                Path.Direction.CW\n            )\n        } else {\n            path.moveTo(0f, 0f)\n            path.lineTo(mWidth.toFloat(), 0f)\n            path.lineTo(mWidth.toFloat(), mHeight.toFloat())\n            path.close()\n        }\n    }\n\n    //右上角不铺满\n    private fun getRightTop() {\n        path.moveTo(0f, 0f)\n        path.lineTo(if (mCorner != 0) (mWidth - mCorner).toFloat() else mLength.toFloat(), 0f)\n        path.lineTo(\n            mWidth.toFloat(),\n            if (mCorner != 0) mCorner.toFloat() else (mHeight - mLength).toFloat()\n        )\n        path.lineTo(mWidth.toFloat(), mHeight.toFloat())\n        path.close()\n    }\n\n    //右下角铺满\n    private fun getRightBottomFill() {\n        if (mCorner != 0) {\n            path.addRoundRect(\n                (mWidth / 2).toFloat(),\n                (mHeight / 2).toFloat(),\n                mWidth.toFloat(),\n                mHeight.toFloat(),\n                floatArrayOf(0f, 0f, 0f, 0f, mCorner.toFloat(), mCorner.toFloat(), 0f, 0f),\n                Path.Direction.CW\n            )\n        } else {\n            path.moveTo(mWidth.toFloat(), 0f)\n            path.lineTo(mWidth.toFloat(), mHeight.toFloat())\n            path.lineTo(0f, mHeight.toFloat())\n            path.close()\n        }\n    }\n\n    //右下角不铺满\n    private fun getRightBottom() {\n        path.moveTo(mWidth.toFloat(), 0f)\n        path.lineTo(\n            mWidth.toFloat(),\n            if (mCorner != 0) (mHeight - mCorner).toFloat() else mLength.toFloat()\n        )\n        path.lineTo(\n            if (mCorner != 0) (mWidth - mCorner).toFloat() else mLength.toFloat(),\n            mHeight.toFloat()\n        )\n        path.lineTo(0f, mHeight.toFloat())\n        path.close()\n    }\n\n\n    /**\n     * @param sp 转换大小\n     */\n    @Suppress(\"SameParameterValue\")\n    private fun sp2px(sp: Int): Int {\n        return TypedValue.applyDimension(\n            TypedValue.COMPLEX_UNIT_SP,\n            sp.toFloat(),\n            resources.displayMetrics\n        )\n            .toInt()\n    }\n\n    /**\n     * @param dip 转换大小\n     */\n    @Suppress(\"SameParameterValue\")\n    private fun dip2px(dip: Int): Int {\n        return TypedValue.applyDimension(\n            TypedValue.COMPLEX_UNIT_DIP,\n            dip.toFloat(),\n            resources.displayMetrics\n        )\n            .toInt()\n    }\n\n    @Target(AnnotationTarget.VALUE_PARAMETER)\n    @Retention(AnnotationRetention.SOURCE)\n    @IntDef(\n        MODE_LEFT_BOTTOM, MODE_LEFT_BOTTOM_FILL, MODE_LEFT_TOP, MODE_LEFT_TOP_FILL,\n        MODE_RIGHT_BOTTOM, MODE_RIGHT_BOTTOM_FILL, MODE_RIGHT_TOP, MODE_RIGHT_TOP_FILL\n    )\n    annotation class BevelLabelMode\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/widget/text/EditEntity.kt",
    "content": "package io.legado.app.ui.widget.text\n\nimport splitties.init.appCtx\n\ndata class EditEntity(\n    var key: String,\n    var value: String?,\n    var hint: String,\n    val viewType: Int = 0\n) {\n\n    constructor(\n        key: String,\n        value: String?,\n        hint: Int,\n        viewType: Int = 0\n    ) : this(\n        key,\n        value,\n        appCtx.getString(hint),\n        viewType\n    )\n\n    @Suppress(\"unused\")\n    object ViewType {\n\n        const val checkBox = 1\n\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/widget/text/MultilineTextView.kt",
    "content": "package io.legado.app.ui.widget.text\n\nimport android.content.Context\nimport android.os.Build\nimport android.util.AttributeSet\nimport androidx.appcompat.widget.AppCompatTextView\n\nclass MultilineTextView(context: Context, attrs: AttributeSet?) :\n    AppCompatTextView(context, attrs) {\n\n    init {\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {\n            isFallbackLineSpacing = false\n        }\n    }\n\n    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {\n        val heightSize = MeasureSpec.getSize(heightMeasureSpec)\n        calculateLines(heightSize)\n        super.onMeasure(widthMeasureSpec, heightMeasureSpec)\n    }\n\n    private fun calculateLines(measuredHeight: Int) {\n        val lHeight = lineHeight\n        val lines = measuredHeight / lHeight\n        setLines(lines)\n    }\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/widget/text/PrimaryTextView.kt",
    "content": "package io.legado.app.ui.widget.text\n\nimport android.content.Context\nimport android.util.AttributeSet\nimport androidx.appcompat.widget.AppCompatTextView\nimport io.legado.app.lib.theme.ThemeStore\n\n/**\n * @author Aidan Follestad (afollestad)\n */\n@Suppress(\"unused\")\nclass PrimaryTextView(context: Context, attrs: AttributeSet) :\n    AppCompatTextView(context, attrs) {\n\n    init {\n        setTextColor(ThemeStore.textColorPrimary(context))\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/widget/text/ScrollMultiAutoCompleteTextView.kt",
    "content": "package io.legado.app.ui.widget.text\n\nimport android.annotation.SuppressLint\nimport android.content.Context\nimport android.os.Build\nimport android.util.AttributeSet\nimport android.view.GestureDetector\nimport android.view.MotionEvent\nimport android.view.VelocityTracker\nimport android.view.ViewConfiguration\nimport android.view.animation.Interpolator\nimport android.widget.OverScroller\nimport androidx.appcompat.widget.AppCompatMultiAutoCompleteTextView\nimport androidx.core.view.ViewCompat\nimport kotlin.math.abs\nimport kotlin.math.max\nimport kotlin.math.min\n\n/**\n * 嵌套惯性滚动 MultiAutoCompleteTextView\n */\nopen class ScrollMultiAutoCompleteTextView @JvmOverloads constructor(\n    context: Context,\n    attrs: AttributeSet? = null\n) : AppCompatMultiAutoCompleteTextView(context, attrs) {\n\n    //是否到顶或者到底的标志\n    private var disallowIntercept = true\n\n    private val scrollStateIdle = 0\n    private val scrollStateDragging = 1\n    val scrollStateSettling = 2\n\n    private val mViewFling: ViewFling by lazy { ViewFling() }\n    private val velocityTracker: VelocityTracker by lazy { VelocityTracker.obtain() }\n    private var mScrollState = scrollStateIdle\n    private var mLastTouchY: Int = 0\n    private var mTouchSlop: Int = 0\n    private var mMinFlingVelocity: Int = 0\n    private var mMaxFlingVelocity: Int = 0\n\n    //滑动距离的最大边界\n    private var mOffsetHeight: Int = 0\n\n    //f(x) = (x-1)^5 + 1\n    private val sQuinticInterpolator = Interpolator {\n        var t = it\n        t -= 1.0f\n        t * t * t * t * t + 1.0f\n    }\n\n    private val gestureDetector = GestureDetector(context,\n        object : GestureDetector.SimpleOnGestureListener() {\n\n            override fun onDown(e: MotionEvent): Boolean {\n                disallowIntercept = true\n                return super.onDown(e)\n            }\n\n            override fun onScroll(\n                e1: MotionEvent?,\n                e2: MotionEvent,\n                distanceX: Float,\n                distanceY: Float\n            ): Boolean {\n                val y = scrollY + distanceY\n                if (y < 0 || y > mOffsetHeight) {\n                    disallowIntercept = false\n                    //这里触发父布局或祖父布局的滑动事件\n                    parent.requestDisallowInterceptTouchEvent(false)\n                } else {\n                    disallowIntercept = true\n                }\n                return true\n            }\n\n        })\n\n    init {\n        val vc = ViewConfiguration.get(context)\n        mTouchSlop = vc.scaledTouchSlop\n        mMinFlingVelocity = vc.scaledMinimumFlingVelocity\n        mMaxFlingVelocity = vc.scaledMaximumFlingVelocity\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM) {\n            isLocalePreferredLineHeightForMinimumUsed = false\n        }\n    }\n\n    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {\n        super.onMeasure(widthMeasureSpec, heightMeasureSpec)\n        initOffsetHeight()\n    }\n\n    override fun onTextChanged(\n        text: CharSequence,\n        start: Int,\n        lengthBefore: Int,\n        lengthAfter: Int\n    ) {\n        super.onTextChanged(text, start, lengthBefore, lengthAfter)\n        initOffsetHeight()\n    }\n\n    override fun dispatchTouchEvent(event: MotionEvent): Boolean {\n        if (lineCount > maxLines) {\n            gestureDetector.onTouchEvent(event)\n        }\n        velocityTracker.addMovement(event)\n        when (event.action) {\n            MotionEvent.ACTION_DOWN -> {\n                setScrollState(scrollStateIdle)\n                mLastTouchY = (event.y + 0.5f).toInt()\n            }\n            MotionEvent.ACTION_MOVE -> {\n                val y = (event.y + 0.5f).toInt()\n                var dy = mLastTouchY - y\n                if (mScrollState != scrollStateDragging) {\n                    var startScroll = false\n\n                    if (abs(dy) > mTouchSlop) {\n                        if (dy > 0) {\n                            dy -= mTouchSlop\n                        } else {\n                            dy += mTouchSlop\n                        }\n                        startScroll = true\n                    }\n                    if (startScroll) {\n                        setScrollState(scrollStateDragging)\n                    }\n                }\n                if (mScrollState == scrollStateDragging) {\n                    mLastTouchY = y\n                }\n            }\n            MotionEvent.ACTION_UP -> {\n                velocityTracker.computeCurrentVelocity(1000, mMaxFlingVelocity.toFloat())\n                val yVelocity = velocityTracker.yVelocity\n                if (abs(yVelocity) > mMinFlingVelocity) {\n                    mViewFling.fling(-yVelocity.toInt())\n                } else {\n                    setScrollState(scrollStateIdle)\n                }\n                resetTouch()\n            }\n            MotionEvent.ACTION_CANCEL -> {\n                resetTouch()\n            }\n        }\n        return super.dispatchTouchEvent(event)\n    }\n\n    @SuppressLint(\"ClickableViewAccessibility\")\n    override fun onTouchEvent(event: MotionEvent): Boolean {\n        val result = super.onTouchEvent(event)\n        //如果是需要拦截，则再拦截，这个方法会在onScrollChanged方法之后再调用一次\n        if (disallowIntercept && lineCount > maxLines) {\n            parent.requestDisallowInterceptTouchEvent(true)\n        }\n\n        return result\n    }\n\n    override fun scrollTo(x: Int, y: Int) {\n        super.scrollTo(x, min(y, mOffsetHeight))\n    }\n\n    private fun initOffsetHeight() {\n        val mLayoutHeight: Int\n\n        //获得内容面板\n        val mLayout = layout ?: return\n        //获得内容面板的高度\n        mLayoutHeight = mLayout.height\n        //获取上内边距\n        val paddingTop: Int = totalPaddingTop\n        //获取下内边距\n        val paddingBottom: Int = totalPaddingBottom\n\n        //获得控件的实际高度\n        val mHeight: Int = measuredHeight\n\n        //计算滑动距离的边界\n        mOffsetHeight = mLayoutHeight + paddingTop + paddingBottom - mHeight\n        if (mOffsetHeight <= 0) {\n            scrollTo(0, 0)\n        }\n    }\n\n    private fun resetTouch() {\n        velocityTracker.clear()\n    }\n\n    private fun setScrollState(state: Int) {\n        if (state == mScrollState) {\n            return\n        }\n        mScrollState = state\n        if (state != scrollStateSettling) {\n            mViewFling.stop()\n        }\n    }\n\n    /**\n     * 惯性滚动\n     */\n    private inner class ViewFling : Runnable {\n\n        private var mLastFlingY = 0\n        private val mScroller: OverScroller = OverScroller(context, sQuinticInterpolator)\n        private var mEatRunOnAnimationRequest = false\n        private var mReSchedulePostAnimationCallback = false\n\n        override fun run() {\n            disableRunOnAnimationRequests()\n            val scroller = mScroller\n            if (scroller.computeScrollOffset()) {\n                val y = scroller.currY\n                val dy = y - mLastFlingY\n                mLastFlingY = y\n                if (dy < 0 && scrollY > 0) {\n                    scrollBy(0, max(dy, -scrollY))\n                } else if (dy > 0 && scrollY < mOffsetHeight) {\n                    scrollBy(0, min(dy, mOffsetHeight - scrollY))\n                }\n                postOnAnimation()\n            }\n            enableRunOnAnimationRequests()\n        }\n\n        fun fling(velocityY: Int) {\n            mLastFlingY = 0\n            setScrollState(scrollStateSettling)\n            mScroller.fling(\n                0,\n                0,\n                0,\n                velocityY,\n                Integer.MIN_VALUE,\n                Integer.MAX_VALUE,\n                Integer.MIN_VALUE,\n                Integer.MAX_VALUE\n            )\n            postOnAnimation()\n        }\n\n        fun stop() {\n            removeCallbacks(this)\n            mScroller.abortAnimation()\n        }\n\n        private fun disableRunOnAnimationRequests() {\n            mReSchedulePostAnimationCallback = false\n            mEatRunOnAnimationRequest = true\n        }\n\n        private fun enableRunOnAnimationRequests() {\n            mEatRunOnAnimationRequest = false\n            if (mReSchedulePostAnimationCallback) {\n                postOnAnimation()\n            }\n        }\n\n        @Suppress(\"DEPRECATION\")\n        fun postOnAnimation() {\n            if (mEatRunOnAnimationRequest) {\n                mReSchedulePostAnimationCallback = true\n            } else {\n                removeCallbacks(this)\n                ViewCompat.postOnAnimation(this@ScrollMultiAutoCompleteTextView, this)\n            }\n        }\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/widget/text/ScrollTextView.kt",
    "content": "package io.legado.app.ui.widget.text\n\nimport android.annotation.SuppressLint\nimport android.content.Context\nimport android.text.method.LinkMovementMethod\nimport android.util.AttributeSet\nimport android.view.GestureDetector\nimport android.view.MotionEvent\nimport android.view.VelocityTracker\nimport android.view.ViewConfiguration\nimport android.view.animation.Interpolator\nimport android.widget.OverScroller\nimport androidx.appcompat.widget.AppCompatTextView\nimport androidx.core.view.ViewCompat\nimport kotlin.math.abs\nimport kotlin.math.max\nimport kotlin.math.min\n\n\n/**\n * 嵌套惯性滚动 TextView\n */\nclass ScrollTextView(context: Context, attrs: AttributeSet?) :\n    AppCompatTextView(context, attrs) {\n\n    //是否到顶或者到底的标志\n    private var disallowIntercept = true\n\n    private val scrollStateIdle = 0\n    private val scrollStateDragging = 1\n    val scrollStateSettling = 2\n\n    private val mViewFling: ViewFling by lazy { ViewFling() }\n    private val velocityTracker: VelocityTracker by lazy { VelocityTracker.obtain() }\n    private var mScrollState = scrollStateIdle\n    private var mLastTouchY: Int = 0\n    private var mTouchSlop: Int = 0\n    private var mMinFlingVelocity: Int = 0\n    private var mMaxFlingVelocity: Int = 0\n\n    //滑动距离的最大边界\n    private var mOffsetHeight: Int = 0\n\n    //f(x) = (x-1)^5 + 1\n    private val sQuinticInterpolator = Interpolator {\n        var t = it\n        t -= 1.0f\n        t * t * t * t * t + 1.0f\n    }\n\n    private val gestureDetector = GestureDetector(context,\n        object : GestureDetector.SimpleOnGestureListener() {\n\n            override fun onDown(e: MotionEvent): Boolean {\n                disallowIntercept = true\n                return super.onDown(e)\n            }\n\n            override fun onScroll(\n                e1: MotionEvent?,\n                e2: MotionEvent,\n                distanceX: Float,\n                distanceY: Float\n            ): Boolean {\n                val y = scrollY + distanceY\n                if (y < 0 || y > mOffsetHeight) {\n                    disallowIntercept = false\n                    //这里触发父布局或祖父布局的滑动事件\n                    parent.requestDisallowInterceptTouchEvent(false)\n                } else {\n                    disallowIntercept = true\n                }\n                return true\n            }\n\n        })\n\n    init {\n        val vc = ViewConfiguration.get(context)\n        mTouchSlop = vc.scaledTouchSlop\n        mMinFlingVelocity = vc.scaledMinimumFlingVelocity\n        mMaxFlingVelocity = vc.scaledMaximumFlingVelocity\n        movementMethod = LinkMovementMethod.getInstance()\n    }\n\n    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {\n        super.onMeasure(widthMeasureSpec, heightMeasureSpec)\n        initOffsetHeight()\n    }\n\n    override fun onTextChanged(\n        text: CharSequence,\n        start: Int,\n        lengthBefore: Int,\n        lengthAfter: Int\n    ) {\n        super.onTextChanged(text, start, lengthBefore, lengthAfter)\n        initOffsetHeight()\n    }\n\n    override fun dispatchTouchEvent(event: MotionEvent): Boolean {\n        if (lineCount > maxLines) {\n            gestureDetector.onTouchEvent(event)\n        }\n        velocityTracker.addMovement(event)\n        when (event.action) {\n            MotionEvent.ACTION_DOWN -> {\n                setScrollState(scrollStateIdle)\n                mLastTouchY = (event.y + 0.5f).toInt()\n            }\n            MotionEvent.ACTION_MOVE -> {\n                val y = (event.y + 0.5f).toInt()\n                var dy = mLastTouchY - y\n                if (mScrollState != scrollStateDragging) {\n                    var startScroll = false\n\n                    if (abs(dy) > mTouchSlop) {\n                        if (dy > 0) {\n                            dy -= mTouchSlop\n                        } else {\n                            dy += mTouchSlop\n                        }\n                        startScroll = true\n                    }\n                    if (startScroll) {\n                        setScrollState(scrollStateDragging)\n                    }\n                }\n                if (mScrollState == scrollStateDragging) {\n                    mLastTouchY = y\n                }\n            }\n            MotionEvent.ACTION_UP -> {\n                velocityTracker.computeCurrentVelocity(1000, mMaxFlingVelocity.toFloat())\n                val yVelocity = velocityTracker.yVelocity\n                if (abs(yVelocity) > mMinFlingVelocity) {\n                    mViewFling.fling(-yVelocity.toInt())\n                } else {\n                    setScrollState(scrollStateIdle)\n                }\n                resetTouch()\n            }\n            MotionEvent.ACTION_CANCEL -> {\n                resetTouch()\n            }\n        }\n        return super.dispatchTouchEvent(event)\n    }\n\n    @SuppressLint(\"ClickableViewAccessibility\")\n    override fun onTouchEvent(event: MotionEvent): Boolean {\n        val result = super.onTouchEvent(event)\n        //如果是需要拦截，则再拦截，这个方法会在onScrollChanged方法之后再调用一次\n        if (disallowIntercept && lineCount > maxLines) {\n            parent.requestDisallowInterceptTouchEvent(true)\n        }\n\n        return result\n    }\n\n    override fun scrollTo(x: Int, y: Int) {\n        super.scrollTo(x, min(y, mOffsetHeight))\n    }\n\n    private fun initOffsetHeight() {\n        val mLayoutHeight: Int\n\n        //获得内容面板\n        val mLayout = layout ?: return\n        //获得内容面板的高度\n        mLayoutHeight = mLayout.height\n        //获取上内边距\n        val paddingTop: Int = totalPaddingTop\n        //获取下内边距\n        val paddingBottom: Int = totalPaddingBottom\n\n        //获得控件的实际高度\n        val mHeight: Int = measuredHeight\n\n        //计算滑动距离的边界\n        mOffsetHeight = mLayoutHeight + paddingTop + paddingBottom - mHeight\n        if (mOffsetHeight <= 0) {\n            scrollTo(0, 0)\n        }\n    }\n\n    private fun resetTouch() {\n        velocityTracker.clear()\n    }\n\n    private fun setScrollState(state: Int) {\n        if (state == mScrollState) {\n            return\n        }\n        mScrollState = state\n        if (state != scrollStateSettling) {\n            mViewFling.stop()\n        }\n    }\n\n    /**\n     * 惯性滚动\n     */\n    private inner class ViewFling : Runnable {\n\n        private var mLastFlingY = 0\n        private val mScroller: OverScroller = OverScroller(context, sQuinticInterpolator)\n        private var mEatRunOnAnimationRequest = false\n        private var mReSchedulePostAnimationCallback = false\n\n        override fun run() {\n            disableRunOnAnimationRequests()\n            val scroller = mScroller\n            if (scroller.computeScrollOffset()) {\n                val y = scroller.currY\n                val dy = y - mLastFlingY\n                mLastFlingY = y\n                if (dy < 0 && scrollY > 0) {\n                    scrollBy(0, max(dy, -scrollY))\n                } else if (dy > 0 && scrollY < mOffsetHeight) {\n                    scrollBy(0, min(dy, mOffsetHeight - scrollY))\n                }\n                postOnAnimation()\n            }\n            enableRunOnAnimationRequests()\n        }\n\n        fun fling(velocityY: Int) {\n            mLastFlingY = 0\n            setScrollState(scrollStateSettling)\n            mScroller.fling(\n                0,\n                0,\n                0,\n                velocityY,\n                Integer.MIN_VALUE,\n                Integer.MAX_VALUE,\n                Integer.MIN_VALUE,\n                Integer.MAX_VALUE\n            )\n            postOnAnimation()\n        }\n\n        fun stop() {\n            removeCallbacks(this)\n            mScroller.abortAnimation()\n        }\n\n        private fun disableRunOnAnimationRequests() {\n            mReSchedulePostAnimationCallback = false\n            mEatRunOnAnimationRequest = true\n        }\n\n        private fun enableRunOnAnimationRequests() {\n            mEatRunOnAnimationRequest = false\n            if (mReSchedulePostAnimationCallback) {\n                postOnAnimation()\n            }\n        }\n\n        @Suppress(\"DEPRECATION\")\n        fun postOnAnimation() {\n            if (mEatRunOnAnimationRequest) {\n                mReSchedulePostAnimationCallback = true\n            } else {\n                removeCallbacks(this)\n                ViewCompat.postOnAnimation(this@ScrollTextView, this)\n            }\n        }\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/widget/text/SecondaryTextView.kt",
    "content": "package io.legado.app.ui.widget.text\n\nimport android.content.Context\nimport android.util.AttributeSet\nimport androidx.appcompat.widget.AppCompatTextView\nimport io.legado.app.lib.theme.secondaryTextColor\n\n/**\n * @author Aidan Follestad (afollestad)\n */\n@Suppress(\"unused\")\nclass SecondaryTextView(context: Context, attrs: AttributeSet) :\n    AppCompatTextView(context, attrs) {\n\n    init {\n        setTextColor(context.secondaryTextColor)\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/widget/text/StrokeTextView.kt",
    "content": "package io.legado.app.ui.widget.text\n\nimport android.content.Context\nimport android.util.AttributeSet\nimport androidx.appcompat.widget.AppCompatTextView\nimport io.legado.app.R\nimport io.legado.app.lib.theme.*\nimport io.legado.app.utils.ColorUtils\nimport io.legado.app.utils.dpToPx\nimport io.legado.app.utils.getCompatColor\n\n@Suppress(\"unused\")\nopen class StrokeTextView(context: Context, attrs: AttributeSet?) :\n    AppCompatTextView(context, attrs) {\n\n    private var radius = 1.dpToPx()\n    private val isBottomBackground: Boolean\n\n    init {\n        val typedArray = context.obtainStyledAttributes(attrs, R.styleable.StrokeTextView)\n        radius = typedArray.getDimensionPixelOffset(R.styleable.StrokeTextView_radius, radius)\n        isBottomBackground =\n            typedArray.getBoolean(R.styleable.StrokeTextView_isBottomBackground, false)\n        typedArray.recycle()\n        upBackground()\n    }\n\n    fun setRadius(radius: Int) {\n        this.radius = radius.dpToPx()\n        upBackground()\n    }\n\n    private fun upBackground() {\n        when {\n            isInEditMode -> {\n                background = Selector.shapeBuild()\n                    .setCornerRadius(radius)\n                    .setStrokeWidth(1.dpToPx())\n                    .setDisabledStrokeColor(context.getCompatColor(R.color.md_grey_500))\n                    .setDefaultStrokeColor(context.getCompatColor(R.color.secondaryText))\n                    .setSelectedStrokeColor(context.getCompatColor(R.color.accent))\n                    .setPressedBgColor(context.getCompatColor(R.color.transparent30))\n                    .create()\n                setTextColor(\n                    Selector.colorBuild()\n                        .setDefaultColor(context.getCompatColor(R.color.secondaryText))\n                        .setSelectedColor(context.getCompatColor(R.color.accent))\n                        .setDisabledColor(context.getCompatColor(R.color.md_grey_500))\n                        .create()\n                )\n            }\n            isBottomBackground -> {\n                val isLight = ColorUtils.isColorLight(context.bottomBackground)\n                background = Selector.shapeBuild()\n                    .setCornerRadius(radius)\n                    .setStrokeWidth(1.dpToPx())\n                    .setDisabledStrokeColor(context.getCompatColor(R.color.md_grey_500))\n                    .setDefaultStrokeColor(context.getPrimaryTextColor(isLight))\n                    .setSelectedStrokeColor(context.accentColor)\n                    .setPressedBgColor(context.getCompatColor(R.color.transparent30))\n                    .create()\n                setTextColor(\n                    Selector.colorBuild()\n                        .setDefaultColor(context.getPrimaryTextColor(isLight))\n                        .setSelectedColor(context.accentColor)\n                        .setDisabledColor(context.getCompatColor(R.color.md_grey_500))\n                        .create()\n                )\n            }\n            else -> {\n                background = Selector.shapeBuild()\n                    .setCornerRadius(radius)\n                    .setStrokeWidth(1.dpToPx())\n                    .setDisabledStrokeColor(context.getCompatColor(R.color.md_grey_500))\n                    .setDefaultStrokeColor(ThemeStore.textColorSecondary(context))\n                    .setSelectedStrokeColor(ThemeStore.accentColor(context))\n                    .setPressedBgColor(context.getCompatColor(R.color.transparent30))\n                    .create()\n                setTextColor(\n                    Selector.colorBuild()\n                        .setDefaultColor(ThemeStore.textColorSecondary(context))\n                        .setSelectedColor(ThemeStore.accentColor(context))\n                        .setDisabledColor(context.getCompatColor(R.color.md_grey_500))\n                        .create()\n                )\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/ui/widget/text/TextInputLayout.kt",
    "content": "package io.legado.app.ui.widget.text\n\nimport android.content.Context\nimport android.util.AttributeSet\nimport com.google.android.material.textfield.TextInputLayout\nimport io.legado.app.lib.theme.Selector\nimport io.legado.app.lib.theme.ThemeStore\n\nclass TextInputLayout(context: Context, attrs: AttributeSet?) : TextInputLayout(context, attrs) {\n\n    init {\n        if (!isInEditMode) {\n            defaultHintTextColor =\n                Selector.colorBuild().setDefaultColor(ThemeStore.accentColor(context)).create()\n        }\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/utils/ACache.kt",
    "content": "//Copyright (c) 2017. 章钦豪. All rights reserved.\npackage io.legado.app.utils\n\nimport android.graphics.Bitmap\nimport android.graphics.BitmapFactory\nimport android.graphics.Canvas\nimport android.graphics.PixelFormat\nimport android.graphics.drawable.BitmapDrawable\nimport android.graphics.drawable.Drawable\nimport org.json.JSONArray\nimport org.json.JSONObject\nimport splitties.init.appCtx\nimport java.io.ByteArrayInputStream\nimport java.io.ByteArrayOutputStream\nimport java.io.File\nimport java.io.IOException\nimport java.io.ObjectInputStream\nimport java.io.ObjectOutputStream\nimport java.io.Serializable\nimport java.util.Collections\nimport java.util.concurrent.atomic.AtomicInteger\nimport java.util.concurrent.atomic.AtomicLong\nimport kotlin.math.min\n\n\n/**\n * 本地缓存\n */\n@Suppress(\"unused\", \"MemberVisibilityCanBePrivate\")\nclass ACache private constructor(cacheDir: File, max_size: Long, max_count: Int) {\n\n    companion object {\n        const val TIME_HOUR = 60 * 60\n        const val TIME_DAY = TIME_HOUR * 24\n        private const val MAX_SIZE = 1000 * 1000 * 50 // 50 mb\n        private const val MAX_COUNT = Integer.MAX_VALUE // 不限制存放数据的数量\n        private val mInstanceMap = HashMap<String, ACache>()\n\n        @JvmOverloads\n        fun get(\n            cacheName: String = \"ACache\",\n            maxSize: Long = MAX_SIZE.toLong(),\n            maxCount: Int = MAX_COUNT,\n            cacheDir: Boolean = true\n        ): ACache {\n            val f =\n                if (cacheDir) File(appCtx.cacheDir, cacheName) else File(appCtx.filesDir, cacheName)\n            return get(f, maxSize, maxCount)\n        }\n\n        @JvmOverloads\n        fun get(\n            cacheDir: File,\n            maxSize: Long = MAX_SIZE.toLong(),\n            maxCount: Int = MAX_COUNT\n        ): ACache {\n            synchronized(this) {\n                var manager = mInstanceMap[cacheDir.absoluteFile.toString() + myPid()]\n                if (manager == null) {\n                    manager = ACache(cacheDir, maxSize, maxCount)\n                    mInstanceMap[cacheDir.absolutePath + myPid()] = manager\n                }\n                return manager\n            }\n        }\n\n        private fun myPid(): String {\n            return \"_\" + android.os.Process.myPid()\n        }\n    }\n\n    private var mCache: ACacheManager? = null\n\n    init {\n        try {\n            if (!cacheDir.exists() && !cacheDir.mkdirs()) {\n                DebugLog.i(javaClass.name, \"can't make dirs in %s\" + cacheDir.absolutePath)\n            }\n            mCache = ACacheManager(cacheDir, max_size, max_count)\n        } catch (e: Exception) {\n            e.printOnDebug()\n        }\n\n    }\n\n    // =======================================\n    // ============ String数据 读写 ==============\n    // =======================================\n\n    /**\n     * 保存 String数据 到 缓存中\n     *\n     * @param key   保存的key\n     * @param value 保存的String数据\n     */\n    fun put(key: String, value: String) {\n        mCache?.let { mCache ->\n            try {\n                val file = mCache.newFile(key)\n                file.writeText(value)\n                mCache.put(file)\n            } catch (e: Exception) {\n                e.printOnDebug()\n            }\n        }\n    }\n\n    /**\n     * 保存 String数据 到 缓存中\n     *\n     * @param key      保存的key\n     * @param value    保存的String数据\n     * @param saveTime 保存的时间，单位：秒\n     */\n    fun put(key: String, value: String, saveTime: Int) {\n        if (saveTime == 0) put(key, value) else put(\n            key,\n            Utils.newStringWithDateInfo(saveTime, value)\n        )\n    }\n\n    /**\n     * 读取 String数据\n     *\n     * @return String 数据\n     */\n    fun getAsString(key: String): String? {\n        mCache?.let { mCache ->\n            val file = mCache[key]\n            if (!file.exists())\n                return null\n            var removeFile = false\n            try {\n                val text = file.readText()\n                if (!Utils.isDue(text)) {\n                    return Utils.clearDateInfo(text)\n                } else {\n                    removeFile = true\n                }\n            } catch (e: IOException) {\n                e.printOnDebug()\n            } finally {\n                if (removeFile)\n                    remove(key)\n            }\n        }\n        return null\n    }\n\n    // =======================================\n    // ========== JSONObject 数据 读写 =========\n    // =======================================\n\n    /**\n     * 保存 JSONObject数据 到 缓存中\n     *\n     * @param key   保存的key\n     * @param value 保存的JSON数据\n     */\n    fun put(key: String, value: JSONObject) {\n        put(key, value.toString())\n    }\n\n    /**\n     * 保存 JSONObject数据 到 缓存中\n     *\n     * @param key      保存的key\n     * @param value    保存的JSONObject数据\n     * @param saveTime 保存的时间，单位：秒\n     */\n    fun put(key: String, value: JSONObject, saveTime: Int) {\n        put(key, value.toString(), saveTime)\n    }\n\n    /**\n     * 读取JSONObject数据\n     *\n     * @return JSONObject数据\n     */\n    fun getAsJSONObject(key: String): JSONObject? {\n        val json = getAsString(key) ?: return null\n        return try {\n            JSONObject(json)\n        } catch (e: Exception) {\n            null\n        }\n    }\n\n    // =======================================\n    // ============ JSONArray 数据 读写 =============\n    // =======================================\n\n    /**\n     * 保存 JSONArray数据 到 缓存中\n     *\n     * @param key   保存的key\n     * @param value 保存的JSONArray数据\n     */\n    fun put(key: String, value: JSONArray) {\n        put(key, value.toString())\n    }\n\n    /**\n     * 保存 JSONArray数据 到 缓存中\n     *\n     * @param key      保存的key\n     * @param value    保存的JSONArray数据\n     * @param saveTime 保存的时间，单位：秒\n     */\n    fun put(key: String, value: JSONArray, saveTime: Int) {\n        put(key, value.toString(), saveTime)\n    }\n\n    /**\n     * 读取JSONArray数据\n     *\n     * @return JSONArray数据\n     */\n    fun getAsJSONArray(key: String): JSONArray? {\n        val json = getAsString(key)\n        return try {\n            JSONArray(json)\n        } catch (e: Exception) {\n            null\n        }\n\n    }\n\n    // =======================================\n    // ============== byte 数据 读写 =============\n    // =======================================\n\n    /**\n     * 保存 byte数据 到 缓存中\n     *\n     * @param key   保存的key\n     * @param value 保存的数据\n     */\n    fun put(key: String, value: ByteArray) {\n        mCache?.let { mCache ->\n            val file = mCache.newFile(key)\n            file.writeBytes(value)\n            mCache.put(file)\n        }\n    }\n\n    /**\n     * 保存 byte数据 到 缓存中\n     *\n     * @param key      保存的key\n     * @param value    保存的数据\n     * @param saveTime 保存的时间，单位：秒\n     */\n    fun put(key: String, value: ByteArray, saveTime: Int) {\n        if (saveTime == 0) put(key, value)\n        else put(key, Utils.newByteArrayWithDateInfo(saveTime, value))\n    }\n\n    /**\n     * 获取 byte 数据\n     *\n     * @return byte 数据\n     */\n    fun getAsBinary(key: String): ByteArray? {\n        mCache?.let { mCache ->\n            var removeFile = false\n            try {\n                val file = mCache[key]\n                if (!file.exists())\n                    return null\n\n                val byteArray = file.readBytes()\n                return if (!Utils.isDue(byteArray)) {\n                    Utils.clearDateInfo(byteArray)\n                } else {\n                    removeFile = true\n                    null\n                }\n            } catch (e: Exception) {\n                e.printOnDebug()\n            } finally {\n                if (removeFile)\n                    remove(key)\n            }\n        }\n        return null\n    }\n\n    /**\n     * 保存 Serializable数据到 缓存中\n     *\n     * @param key      保存的key\n     * @param value    保存的value\n     * @param saveTime 保存的时间，单位：秒\n     */\n    @JvmOverloads\n    fun put(key: String, value: Serializable, saveTime: Int = -1) {\n        try {\n            val byteArrayOutputStream = ByteArrayOutputStream()\n            ObjectOutputStream(byteArrayOutputStream).use { oos ->\n                oos.writeObject(value)\n                val data = byteArrayOutputStream.toByteArray()\n                if (saveTime != -1) {\n                    put(key, data, saveTime)\n                } else {\n                    put(key, data)\n                }\n            }\n        } catch (e: Exception) {\n            e.printOnDebug()\n        }\n    }\n\n    /**\n     * 读取 Serializable数据\n     *\n     * @return Serializable 数据\n     */\n    fun getAsObject(key: String): Any? {\n        val data = getAsBinary(key)\n        if (data != null) {\n            var bis: ByteArrayInputStream? = null\n            var ois: ObjectInputStream? = null\n            try {\n                bis = ByteArrayInputStream(data)\n                ois = ObjectInputStream(bis)\n                return ois.readObject()\n            } catch (e: Exception) {\n                e.printOnDebug()\n            } finally {\n                try {\n                    bis?.close()\n                } catch (e: IOException) {\n                    e.printOnDebug()\n                }\n\n                try {\n                    ois?.close()\n                } catch (e: IOException) {\n                    e.printOnDebug()\n                }\n\n            }\n        }\n        return null\n\n    }\n\n    // =======================================\n    // ============== bitmap 数据 读写 =============\n    // =======================================\n\n    /**\n     * 保存 bitmap 到 缓存中\n     *\n     * @param key   保存的key\n     * @param value 保存的bitmap数据\n     */\n    fun put(key: String, value: Bitmap) {\n        put(key, Utils.bitmap2Bytes(value))\n    }\n\n    /**\n     * 保存 bitmap 到 缓存中\n     *\n     * @param key      保存的key\n     * @param value    保存的 bitmap 数据\n     * @param saveTime 保存的时间，单位：秒\n     */\n    fun put(key: String, value: Bitmap, saveTime: Int) {\n        put(key, Utils.bitmap2Bytes(value), saveTime)\n    }\n\n    /**\n     * 读取 bitmap 数据\n     *\n     * @return bitmap 数据\n     */\n    fun getAsBitmap(key: String): Bitmap? {\n        return if (getAsBinary(key) == null) {\n            null\n        } else Utils.bytes2Bitmap(getAsBinary(key)!!)\n    }\n\n    // =======================================\n    // ============= drawable 数据 读写 =============\n    // =======================================\n\n    /**\n     * 保存 drawable 到 缓存中\n     *\n     * @param key   保存的key\n     * @param value 保存的drawable数据\n     */\n    fun put(key: String, value: Drawable) {\n        put(key, Utils.drawable2Bitmap(value))\n    }\n\n    /**\n     * 保存 drawable 到 缓存中\n     *\n     * @param key      保存的key\n     * @param value    保存的 drawable 数据\n     * @param saveTime 保存的时间，单位：秒\n     */\n    fun put(key: String, value: Drawable, saveTime: Int) {\n        put(key, Utils.drawable2Bitmap(value), saveTime)\n    }\n\n    /**\n     * 读取 Drawable 数据\n     *\n     * @return Drawable 数据\n     */\n    fun getAsDrawable(key: String): Drawable? {\n        return if (getAsBinary(key) == null) {\n            null\n        } else Utils.bitmap2Drawable(\n            Utils.bytes2Bitmap(\n                getAsBinary(key)!!\n            )\n        )\n    }\n\n    /**\n     * 获取缓存文件\n     *\n     * @return value 缓存的文件\n     */\n    fun file(key: String): File? {\n        mCache?.let { mCache ->\n            try {\n                val f = mCache.newFile(key)\n                if (f.exists()) {\n                    return f\n                }\n            } catch (e: Exception) {\n                e.printOnDebug()\n            }\n        }\n        return null\n    }\n\n    /**\n     * 移除某个key\n     *\n     * @return 是否移除成功\n     */\n    fun remove(key: String): Boolean {\n        return mCache?.remove(key) == true\n    }\n\n    /**\n     * 清除所有数据\n     */\n    fun clear() {\n        mCache?.clear()\n    }\n\n    /**\n     * @author 杨福海（michael） www.yangfuhai.com\n     * @version 1.0\n     * title 时间计算工具类\n     */\n    private object Utils {\n\n        @Suppress(\"ConstPropertyName\")\n        private const val mSeparator = ' '\n\n        /**\n         * 判断缓存的String数据是否到期\n         *\n         * @return true：到期了 false：还没有到期\n         */\n        fun isDue(str: String): Boolean {\n            return isDue(str.toByteArray())\n        }\n\n        /**\n         * 判断缓存的byte数据是否到期\n         *\n         * @return true：到期了 false：还没有到期\n         */\n        fun isDue(data: ByteArray): Boolean {\n            try {\n                val text = getDateInfoFromDate(data)\n                if (text != null && text.size == 2) {\n                    var saveTimeStr = text[0]\n                    while (saveTimeStr.startsWith(\"0\")) {\n                        saveTimeStr = saveTimeStr\n                            .substring(1)\n                    }\n                    val saveTime = java.lang.Long.valueOf(saveTimeStr)\n                    val deleteAfter = java.lang.Long.valueOf(text[1])\n                    if (System.currentTimeMillis() > saveTime + deleteAfter * 1000) {\n                        return true\n                    }\n                }\n            } catch (e: Exception) {\n                e.printOnDebug()\n            }\n\n            return false\n        }\n\n        fun newStringWithDateInfo(second: Int, strInfo: String): String {\n            return createDateInfo(second) + strInfo\n        }\n\n        fun newByteArrayWithDateInfo(second: Int, data2: ByteArray): ByteArray {\n            val data1 = createDateInfo(second).toByteArray()\n            val retData = ByteArray(data1.size + data2.size)\n            System.arraycopy(data1, 0, retData, 0, data1.size)\n            System.arraycopy(data2, 0, retData, data1.size, data2.size)\n            return retData\n        }\n\n        fun clearDateInfo(strInfo: String?): String? {\n            strInfo?.let {\n                if (hasDateInfo(strInfo.toByteArray())) {\n                    return strInfo.substring(strInfo.indexOf(mSeparator) + 1)\n                }\n            }\n            return strInfo\n        }\n\n        fun clearDateInfo(data: ByteArray): ByteArray {\n            return if (hasDateInfo(data)) {\n                copyOfRange(\n                    data, indexOf(data, mSeparator) + 1,\n                    data.size\n                )\n            } else data\n        }\n\n        fun hasDateInfo(data: ByteArray?): Boolean {\n            return (data != null && data.size > 15 && data[13] == '-'.code.toByte()\n                    && indexOf(data, mSeparator) > 14)\n        }\n\n        fun getDateInfoFromDate(data: ByteArray): Array<String>? {\n            if (hasDateInfo(data)) {\n                val saveDate = String(copyOfRange(data, 0, 13))\n                val deleteAfter = String(\n                    copyOfRange(\n                        data, 14,\n                        indexOf(data, mSeparator)\n                    )\n                )\n                return arrayOf(saveDate, deleteAfter)\n            }\n            return null\n        }\n\n        @Suppress(\"SameParameterValue\")\n        private fun indexOf(data: ByteArray, c: Char): Int {\n            for (i in data.indices) {\n                if (data[i] == c.code.toByte()) {\n                    return i\n                }\n            }\n            return -1\n        }\n\n        private fun copyOfRange(original: ByteArray, from: Int, to: Int): ByteArray {\n            val newLength = to - from\n            require(newLength >= 0) { \"$from > $to\" }\n            val copy = ByteArray(newLength)\n            System.arraycopy(\n                original, from, copy, 0,\n                min(original.size - from, newLength)\n            )\n            return copy\n        }\n\n        private fun createDateInfo(second: Int): String {\n            val currentTime = StringBuilder(System.currentTimeMillis().toString() + \"\")\n            while (currentTime.length < 13) {\n                currentTime.insert(0, \"0\")\n            }\n            return \"$currentTime-$second$mSeparator\"\n        }\n\n        /*\n         * Bitmap → byte[]\n         */\n        fun bitmap2Bytes(bm: Bitmap): ByteArray {\n            val byteArrayOutputStream = ByteArrayOutputStream()\n            bm.compress(Bitmap.CompressFormat.PNG, 100, byteArrayOutputStream)\n            return byteArrayOutputStream.toByteArray()\n        }\n\n        /*\n         * byte[] → Bitmap\n         */\n        fun bytes2Bitmap(b: ByteArray): Bitmap? {\n            return if (b.isEmpty()) {\n                null\n            } else BitmapFactory.decodeByteArray(b, 0, b.size)\n        }\n\n        /*\n         * Drawable → Bitmap\n         */\n        fun drawable2Bitmap(drawable: Drawable): Bitmap {\n            // 取 drawable 的长宽\n            val w = drawable.intrinsicWidth\n            val h = drawable.intrinsicHeight\n            // 取 drawable 的颜色格式\n            @Suppress(\"DEPRECATION\")\n            val config = if (drawable.opacity != PixelFormat.OPAQUE)\n                Bitmap.Config.ARGB_8888\n            else\n                Bitmap.Config.RGB_565\n            // 建立对应 bitmap\n            val bitmap = Bitmap.createBitmap(w, h, config)\n            // 建立对应 bitmap 的画布\n            val canvas = Canvas(bitmap)\n            drawable.setBounds(0, 0, w, h)\n            // 把 drawable 内容画到画布中\n            drawable.draw(canvas)\n            return bitmap\n        }\n\n        /*\n         * Bitmap → Drawable\n         */\n        fun bitmap2Drawable(bm: Bitmap?): Drawable? {\n            return if (bm == null) {\n                null\n            } else BitmapDrawable(appCtx.resources, bm)\n        }\n    }\n\n    /**\n     * @author 杨福海（michael） www.yangfuhai.com\n     * @version 1.0\n     * title 缓存管理器\n     */\n    open inner class ACacheManager(\n        private var cacheDir: File,\n        private val sizeLimit: Long,\n        private val countLimit: Int\n    ) {\n        private val cacheSize: AtomicLong = AtomicLong()\n        private val cacheCount: AtomicInteger = AtomicInteger()\n        private val lastUsageDates = Collections\n            .synchronizedMap(HashMap<File, Long>())\n\n        init {\n            calculateCacheSizeAndCacheCount()\n        }\n\n        /**\n         * 计算 cacheSize和cacheCount\n         */\n        private fun calculateCacheSizeAndCacheCount() {\n            Thread {\n\n                try {\n                    var size = 0\n                    var count = 0\n                    val cachedFiles = cacheDir.listFiles()\n                    if (cachedFiles != null) {\n                        for (cachedFile in cachedFiles) {\n                            size += calculateSize(cachedFile).toInt()\n                            count += 1\n                            lastUsageDates[cachedFile] = cachedFile.lastModified()\n                        }\n                        cacheSize.set(size.toLong())\n                        cacheCount.set(count)\n                    }\n                } catch (e: Exception) {\n                    e.printOnDebug()\n                }\n\n\n            }.start()\n        }\n\n        fun put(file: File) {\n\n            try {\n                var curCacheCount = cacheCount.get()\n                while (curCacheCount + 1 > countLimit) {\n                    val freedSize = removeNext()\n                    cacheSize.addAndGet(-freedSize)\n\n                    curCacheCount = cacheCount.addAndGet(-1)\n                }\n                cacheCount.addAndGet(1)\n\n                val valueSize = calculateSize(file)\n                var curCacheSize = cacheSize.get()\n                while (curCacheSize + valueSize > sizeLimit) {\n                    val freedSize = removeNext()\n                    curCacheSize = cacheSize.addAndGet(-freedSize)\n                }\n                cacheSize.addAndGet(valueSize)\n\n                val currentTime = System.currentTimeMillis()\n                file.setLastModified(currentTime)\n                lastUsageDates[file] = currentTime\n            } catch (e: Exception) {\n                e.printOnDebug()\n            }\n\n        }\n\n        operator fun get(key: String): File {\n            val file = newFile(key)\n            val currentTime = System.currentTimeMillis()\n            file.setLastModified(currentTime)\n            lastUsageDates[file] = currentTime\n\n            return file\n        }\n\n        fun newFile(key: String): File {\n            return File(cacheDir, key.hashCode().toString() + \"\")\n        }\n\n        fun remove(key: String): Boolean {\n            val image = get(key)\n            return image.delete()\n        }\n\n        fun clear() {\n            try {\n                lastUsageDates.clear()\n                cacheSize.set(0)\n                val files = cacheDir.listFiles()\n                if (files != null) {\n                    for (f in files) {\n                        f.delete()\n                    }\n                }\n            } catch (e: Exception) {\n                e.printOnDebug()\n            }\n\n        }\n\n        /**\n         * 移除旧的文件\n         */\n        private fun removeNext(): Long {\n            try {\n                if (lastUsageDates.isEmpty()) {\n                    return 0\n                }\n\n                var oldestUsage: Long? = null\n                var mostLongUsedFile: File? = null\n                val entries = lastUsageDates.entries\n                synchronized(lastUsageDates) {\n                    for ((key, lastValueUsage) in entries) {\n                        if (mostLongUsedFile == null) {\n                            mostLongUsedFile = key\n                            oldestUsage = lastValueUsage\n                        } else {\n                            if (lastValueUsage < oldestUsage!!) {\n                                oldestUsage = lastValueUsage\n                                mostLongUsedFile = key\n                            }\n                        }\n                    }\n                }\n\n                var fileSize: Long = 0\n                if (mostLongUsedFile != null) {\n                    fileSize = calculateSize(mostLongUsedFile)\n                    if (mostLongUsedFile.delete()) {\n                        lastUsageDates.remove(mostLongUsedFile)\n                    }\n                }\n                return fileSize\n            } catch (e: Exception) {\n                e.printOnDebug()\n                return 0\n            }\n\n        }\n\n        private fun calculateSize(file: File): Long {\n            return file.length()\n        }\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/utils/ActivityExtensions.kt",
    "content": "package io.legado.app.utils\n\nimport android.annotation.SuppressLint\nimport android.app.Activity\nimport android.graphics.Color\nimport android.os.Build\nimport android.os.Bundle\nimport android.util.DisplayMetrics\nimport android.view.Gravity\nimport android.view.View\nimport android.view.ViewGroup\nimport android.view.WindowInsets\nimport android.view.WindowInsetsController\nimport android.view.WindowManager\nimport android.view.WindowMetrics\nimport android.widget.FrameLayout\nimport androidx.annotation.ColorInt\nimport androidx.appcompat.app.AppCompatActivity\nimport androidx.core.view.WindowCompat\nimport androidx.core.view.WindowInsetsCompat\nimport androidx.core.view.WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE\nimport androidx.fragment.app.DialogFragment\nimport io.legado.app.R\nimport io.legado.app.ui.widget.dialog.TextDialog\n\ninline fun <reified T : DialogFragment> AppCompatActivity.showDialogFragment(\n    arguments: Bundle.() -> Unit = {}\n) {\n    @Suppress(\"DEPRECATION\")\n    val dialog = T::class.java.newInstance()\n    val bundle = Bundle()\n    bundle.apply(arguments)\n    dialog.arguments = bundle\n    dialog.show(supportFragmentManager, T::class.simpleName)\n}\n\ninline fun <reified T : DialogFragment> AppCompatActivity.dismissDialogFragment() {\n    supportFragmentManager.fragments.forEach {\n        if (it is T) {\n            it.dismissAllowingStateLoss()\n        }\n    }\n}\n\nfun AppCompatActivity.showDialogFragment(dialogFragment: DialogFragment) {\n    dialogFragment.show(supportFragmentManager, dialogFragment::class.simpleName)\n}\n\nval WindowManager.windowSize: DisplayMetrics\n    get() {\n        val displayMetrics = DisplayMetrics()\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {\n            val windowMetrics: WindowMetrics = currentWindowMetrics\n            val insets = windowMetrics.windowInsets\n                .getInsetsIgnoringVisibility(\n                    WindowInsets.Type.systemBars() or WindowInsets.Type.displayCutout()\n                )\n            val windowWidth = windowMetrics.bounds.width()\n            val windowHeight = windowMetrics.bounds.height()\n            var insetsWidth = insets.left + insets.right\n            var insetsHeight = insets.top + insets.bottom\n            if (windowWidth > windowHeight) {\n                val tmp = insetsWidth\n                insetsWidth = insetsHeight\n                insetsHeight = tmp\n            }\n            displayMetrics.widthPixels = windowWidth - insetsWidth\n            displayMetrics.heightPixels = windowHeight - insetsHeight\n        } else {\n            @Suppress(\"DEPRECATION\")\n            defaultDisplay.getMetrics(displayMetrics)\n        }\n        return displayMetrics\n    }\n\n@Suppress(\"DEPRECATION\")\nfun Activity.fullScreen() {\n    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {\n        window.setDecorFitsSystemWindows(true)\n    }\n    window.decorView.systemUiVisibility =\n        View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN or View.SYSTEM_UI_FLAG_LAYOUT_STABLE\n    window.clearFlags(\n        WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS\n                or WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION\n    )\n    window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS)\n}\n\n/**\n * 设置状态栏颜色\n */\n@Suppress(\"DEPRECATION\")\nfun Activity.setStatusBarColorAuto(\n    @ColorInt color: Int,\n    isTransparent: Boolean,\n    fullScreen: Boolean\n) {\n    val isLightBar = ColorUtils.isColorLight(color)\n    if (fullScreen) {\n        if (isTransparent) {\n            window.statusBarColor = Color.TRANSPARENT\n        } else {\n            window.statusBarColor = getCompatColor(R.color.status_bar_bag)\n        }\n    } else {\n        window.statusBarColor = color\n    }\n    setLightStatusBar(isLightBar)\n}\n\n@SuppressLint(\"ObsoleteSdkInt\")\nfun Activity.setLightStatusBar(isLightBar: Boolean) {\n    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {\n        window.insetsController?.let {\n            if (isLightBar) {\n                it.setSystemBarsAppearance(\n                    WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS,\n                    WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS\n                )\n            } else {\n                it.setSystemBarsAppearance(\n                    0,\n                    WindowInsetsController.APPEARANCE_LIGHT_STATUS_BARS\n                )\n            }\n        }\n    }\n    @Suppress(\"DEPRECATION\")\n    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {\n        val decorView = window.decorView\n        val systemUiVisibility = decorView.systemUiVisibility\n        if (isLightBar) {\n            decorView.systemUiVisibility =\n                systemUiVisibility or View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR\n        } else {\n            decorView.systemUiVisibility =\n                systemUiVisibility and View.SYSTEM_UI_FLAG_LIGHT_STATUS_BAR.inv()\n        }\n    }\n}\n\n/**\n * 设置导航栏颜色\n */\n@Suppress(\"DEPRECATION\")\nfun Activity.setNavigationBarColorAuto(@ColorInt color: Int) {\n    val isLightBor = ColorUtils.isColorLight(color)\n    window.navigationBarColor = color\n    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {\n        window.insetsController?.let {\n            if (isLightBor) {\n                it.setSystemBarsAppearance(\n                    WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS,\n                    WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS\n                )\n            } else {\n                it.setSystemBarsAppearance(\n                    0,\n                    WindowInsetsController.APPEARANCE_LIGHT_NAVIGATION_BARS\n                )\n            }\n        }\n    }\n    @Suppress(\"DEPRECATION\")\n    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {\n        val decorView = window.decorView\n        var systemUiVisibility = decorView.systemUiVisibility\n        systemUiVisibility = if (isLightBor) {\n            systemUiVisibility or View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR\n        } else {\n            systemUiVisibility and View.SYSTEM_UI_FLAG_LIGHT_NAVIGATION_BAR.inv()\n        }\n        decorView.systemUiVisibility = systemUiVisibility\n    }\n}\n\nfun Activity.keepScreenOn(on: Boolean) {\n    val isScreenOn =\n        (window.attributes.flags and WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) != 0\n    if (on == isScreenOn) return\n    if (on) {\n        window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)\n    } else {\n        window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)\n    }\n}\n\nfun Activity.toggleSystemBar(show: Boolean) {\n    WindowCompat.getInsetsController(window, window.decorView).run {\n        if (show) {\n            show(WindowInsetsCompat.Type.systemBars())\n        } else {\n            hide(WindowInsetsCompat.Type.systemBars())\n            systemBarsBehavior = BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE\n        }\n    }\n}\n\n/////以下方法需要在View完全被绘制出来之后调用，否则判断不了,在比如 onWindowFocusChanged（）方法中可以得到正确的结果/////\n\n/**\n * 返回NavigationBar\n */\nval Activity.navigationBar: View?\n    get() {\n        val viewGroup = (window.decorView as? ViewGroup) ?: return null\n        for (i in 0 until viewGroup.childCount) {\n            val child = viewGroup.getChildAt(i)\n            val childId = child.id\n            if (childId != View.NO_ID\n                && resources.getResourceEntryName(childId) == \"navigationBarBackground\"\n            ) {\n                return child\n            }\n        }\n        return null\n    }\n\n/**\n * 返回NavigationBar是否存在\n */\nval Activity.isNavigationBarExist: Boolean\n    get() = navigationBar != null\n\n/**\n * 返回NavigationBar高度\n */\nval Activity.navigationBarHeight: Int\n    @SuppressLint(\"InternalInsetResource\", \"DiscouragedApi\")\n    get() {\n        if (isNavigationBarExist) {\n            val resourceId = resources.getIdentifier(\"navigation_bar_height\", \"dimen\", \"android\")\n            return resources.getDimensionPixelSize(resourceId)\n        }\n        return 0\n    }\n\n/**\n * 返回navigationBar位置\n */\nval Activity.navigationBarGravity: Int\n    get() {\n        val gravity = (navigationBar?.layoutParams as? FrameLayout.LayoutParams)?.gravity\n        return gravity ?: Gravity.BOTTOM\n    }\n\n/**\n * 显示目录help下的帮助文档\n */\nfun AppCompatActivity.showHelp(fileName: String) {\n    val mdText = String(assets.open(\"web/help/md/${fileName}.md\").readBytes())\n    showDialogFragment(TextDialog(getString(R.string.help), mdText, TextDialog.Mode.MD))\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/utils/ActivityResult.kt",
    "content": "package io.legado.app.utils\n\nimport androidx.activity.result.contract.ActivityResultContract\nimport androidx.appcompat.app.AppCompatActivity\nimport androidx.core.app.ActivityOptionsCompat\nimport kotlinx.coroutines.CancellableContinuation\nimport kotlinx.coroutines.suspendCancellableCoroutine\nimport kotlin.coroutines.resume\n\nfun <I, O> AppCompatActivity.registerForActivityResult(contract: ActivityResultContract<I, O>): ActivityResultLauncherAwait<I, O> {\n    lateinit var cout: CancellableContinuation<O>\n    val launcher = registerForActivityResult(contract) {\n        if (cout.isActive) {\n            cout.resume(it)\n        }\n    }\n    return object : ActivityResultLauncherAwait<I, O>() {\n        override suspend fun launch(input: I, options: ActivityOptionsCompat?): O {\n            return suspendCancellableCoroutine {\n                cout = it\n                launcher.launch(input, options)\n            }\n        }\n\n        override fun unregister() {\n            launcher.unregister()\n        }\n\n        override fun getContract(): ActivityResultContract<I, *> {\n            return launcher.contract\n        }\n    }\n}\n\nabstract class ActivityResultLauncherAwait<I, O> {\n\n    suspend fun launch(input: I): O {\n        return launch(input, null)\n    }\n\n    abstract suspend fun launch(input: I, options: ActivityOptionsCompat?): O\n\n    abstract fun unregister()\n\n    abstract fun getContract(): ActivityResultContract<I, *>\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/utils/ActivityResultContracts.kt",
    "content": "package io.legado.app.utils\n\nimport android.app.Activity.RESULT_OK\nimport android.content.Context\nimport android.content.Intent\nimport android.net.Uri\nimport androidx.activity.result.ActivityResult\nimport androidx.activity.result.ActivityResultLauncher\nimport androidx.activity.result.PickVisualMediaRequest\nimport androidx.activity.result.contract.ActivityResultContract\nimport androidx.activity.result.contract.ActivityResultContracts\nimport splitties.init.appCtx\n\nfun <T> ActivityResultLauncher<T?>.launch() {\n    launch(null)\n}\n\nclass SelectImageContract : ActivityResultContract<Int?, SelectImageContract.Result>() {\n\n    private val delegate = ActivityResultContracts.PickVisualMedia()\n    private var requestCode: Int? = null\n    private var useFallback = false\n\n    override fun createIntent(context: Context, input: Int?): Intent {\n        requestCode = input\n        val intent = Intent(Intent.ACTION_GET_CONTENT)\n            .addCategory(Intent.CATEGORY_OPENABLE)\n            .setType(\"image/*\")\n        if (intent.resolveActivity(appCtx.packageManager) == null) {\n            useFallback = true\n            val request = PickVisualMediaRequest(ActivityResultContracts.PickVisualMedia.ImageOnly)\n            return delegate.createIntent(context, request)\n        }\n        return intent\n    }\n\n    override fun parseResult(resultCode: Int, intent: Intent?): Result {\n        val uri = if (useFallback) {\n            delegate.parseResult(resultCode, intent)\n        } else if (resultCode == RESULT_OK) {\n            intent?.data\n        } else {\n            null\n        }\n        return Result(requestCode, uri)\n    }\n\n    data class Result(\n        val requestCode: Int?,\n        val uri: Uri? = null\n    )\n\n}\n\nclass StartActivityContract(private val cls: Class<*>) :\n    ActivityResultContract<(Intent.() -> Unit)?, ActivityResult>() {\n\n    override fun createIntent(context: Context, input: (Intent.() -> Unit)?): Intent {\n        val intent = Intent(context, cls)\n        input?.let {\n            intent.apply(input)\n        }\n        return intent\n    }\n\n    override fun parseResult(\n        resultCode: Int, intent: Intent?\n    ): ActivityResult {\n        return ActivityResult(resultCode, intent)\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/utils/AlphanumComparator.kt",
    "content": "package io.legado.app.utils\n\n\n/**\n * 排序比较\n */\nobject AlphanumComparator : Comparator<String> {\n\n\n    override fun compare(s1: String, s2: String): Int {\n        var thisMarker = 0\n        var thatMarker = 0\n        val s1Length = s1.length\n        val s2Length = s2.length\n\n        while (thisMarker < s1Length && thatMarker < s2Length) {\n            val thisChunk = getChunk(s1, s1Length, thisMarker)\n            thisMarker += thisChunk.length\n\n            val thatChunk = getChunk(s2, s2Length, thatMarker)\n            thatMarker += thatChunk.length\n\n            // If both chunks contain numeric characters, sort them numerically.\n            var result: Int\n            if (isDigit(thisChunk[0]) && isDigit(thatChunk[0])) {\n                // Simple chunk comparison by length.\n                val thisChunkLength = thisChunk.length\n                result = thisChunkLength - thatChunk.length\n                // If equal, the first different number counts.\n                if (result == 0) {\n                    for (i in 0 until thisChunkLength) {\n                        result = thisChunk[i] - thatChunk[i]\n                        if (result != 0) {\n                            return result\n                        }\n                    }\n                }\n            } else {\n                result = thisChunk.compareTo(thatChunk)\n            }\n\n            if (result != 0) {\n                return result\n            }\n        }\n\n        return s1Length - s2Length\n    }\n\n    private fun getChunk(string: String, length: Int, marker: Int): String {\n        var current = marker\n        val chunk = StringBuilder()\n        var c = string[current]\n        chunk.append(c)\n        current++\n        if (isDigit(c)) {\n            while (current < length) {\n                c = string[current]\n                if (!isDigit(c)) {\n                    break\n                }\n                chunk.append(c)\n                current++\n            }\n        } else {\n            while (current < length) {\n                c = string[current]\n                if (isDigit(c)) {\n                    break\n                }\n                chunk.append(c)\n                current++\n            }\n        }\n        return chunk.toString()\n    }\n\n    private fun isDigit(ch: Char): Boolean {\n        return ch in '0'..'9'\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/utils/AnimationExtensions.kt",
    "content": "package io.legado.app.utils\n\nimport android.content.Context\nimport android.view.animation.Animation\nimport android.view.animation.AnimationUtils\nimport androidx.annotation.AnimRes\nimport io.legado.app.help.config.AppConfig\n\nfun loadAnimation(context: Context, @AnimRes id: Int): Animation {\n    val animation = AnimationUtils.loadAnimation(context, id)\n    if (AppConfig.isEInkMode) {\n        animation.duration = 0\n    }\n    return animation\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/utils/ArchiveUtils.kt",
    "content": "package io.legado.app.utils\n\nimport android.net.Uri\nimport androidx.documentfile.provider.DocumentFile\nimport io.legado.app.constant.AppPattern.archiveFileRegex\nimport io.legado.app.utils.compress.LibArchiveUtils\nimport splitties.init.appCtx\nimport java.io.File\n\n/* 自动判断压缩文件后缀 然后再调用具体的实现 */\n@Suppress(\"unused\", \"MemberVisibilityCanBePrivate\")\nobject ArchiveUtils {\n\n    const val TEMP_FOLDER_NAME = \"ArchiveTemp\"\n\n    // 临时目录 下次启动自动删除\n    val TEMP_PATH: String by lazy {\n        appCtx.externalCache.getFile(TEMP_FOLDER_NAME).createFolderReplace().absolutePath\n    }\n\n    fun deCompress(\n        archiveUri: Uri,\n        path: String = TEMP_PATH,\n        filter: ((String) -> Boolean)? = null\n    ): List<File> {\n        return deCompress(FileDoc.fromUri(archiveUri, false), path, filter)\n    }\n\n    fun deCompress(\n        archivePath: String,\n        path: String = TEMP_PATH,\n        filter: ((String) -> Boolean)? = null\n    ): List<File> {\n        return deCompress(Uri.parse(archivePath), path, filter)\n    }\n\n    fun deCompress(\n        archiveFile: File,\n        path: String = TEMP_PATH,\n        filter: ((String) -> Boolean)? = null\n    ): List<File> {\n        return deCompress(FileDoc.fromFile(archiveFile), path, filter)\n    }\n\n    fun deCompress(\n        archiveDoc: DocumentFile,\n        path: String = TEMP_PATH,\n        filter: ((String) -> Boolean)? = null\n    ): List<File> {\n        return deCompress(FileDoc.fromDocumentFile(archiveDoc), path, filter)\n    }\n\n    fun deCompress(\n        archiveFileDoc: FileDoc,\n        path: String = TEMP_PATH,\n        filter: ((String) -> Boolean)? = null\n    ): List<File> {\n        if (archiveFileDoc.isDir) throw IllegalArgumentException(\"Unexpected Folder input\")\n        val name = archiveFileDoc.name\n        checkAchieve(name)\n        val workPathFileDoc = getCacheFolderFileDoc(name, path)\n        val workPath = workPathFileDoc.toString()\n\n        return archiveFileDoc.openReadPfd().getOrThrow().use {\n            LibArchiveUtils.unArchive(it, File(workPath), filter)\n        }\n\n    }\n\n    /* 遍历目录获取文件名 */\n    fun getArchiveFilesName(fileUri: Uri, filter: ((String) -> Boolean)? = null): List<String> =\n        getArchiveFilesName(FileDoc.fromUri(fileUri, false), filter)\n\n\n    fun getArchiveFilesName(\n        fileDoc: FileDoc,\n        filter: ((String) -> Boolean)? = null\n    ): List<String> {\n        val name = fileDoc.name\n        checkAchieve(name)\n\n        return fileDoc.openReadPfd().getOrThrow().use {\n            try {\n                LibArchiveUtils.getFilesName(it, filter)\n            } catch (e: Exception) {\n                emptyList()\n            }\n\n        }\n\n\n    }\n\n    fun isArchive(name: String): Boolean {\n        return archiveFileRegex.matches(name)\n    }\n\n    private fun checkAchieve(name: String) {\n        if (!isArchive(name))\n            throw IllegalArgumentException(\"Unexpected file suffix: Only 7z rar zip Accepted\")\n    }\n\n    private fun getCacheFolderFileDoc(\n        archiveName: String,\n        workPath: String\n    ): FileDoc {\n        return FileDoc.fromUri(Uri.parse(workPath), true)\n            .createFolderIfNotExist(MD5Utils.md5Encode16(archiveName))\n    }\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/utils/AsyncFileHandler.kt",
    "content": "package io.legado.app.utils\n\nimport io.legado.app.help.globalExecutor\nimport java.util.logging.FileHandler\nimport java.util.logging.LogRecord\n\nclass AsyncFileHandler(pattern: String) : FileHandler(pattern) {\n\n    override fun publish(record: LogRecord?) {\n        if (!isLoggable(record)) {\n            return\n        }\n        globalExecutor.execute {\n            super.publish(record)\n        }\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/utils/BitmapUtils.kt",
    "content": "@file:Suppress(\"unused\")\n\npackage io.legado.app.utils\n\nimport android.content.Context\nimport android.graphics.Bitmap\nimport android.graphics.Bitmap.Config\nimport android.graphics.BitmapFactory\nimport android.graphics.Color\nimport com.google.android.renderscript.Toolkit\nimport java.io.*\nimport kotlin.math.*\n\n\n@Suppress(\"WeakerAccess\", \"MemberVisibilityCanBePrivate\")\nobject BitmapUtils {\n\n    /**\n     * 从path中获取图片信息,在通过BitmapFactory.decodeFile(String path)方法将突破转成Bitmap时，\n     * 遇到大一些的图片，我们经常会遇到OOM(Out Of Memory)的问题。所以用到了我们上面提到的BitmapFactory.Options这个类。\n     *\n     * @param path   文件路径\n     * @param width  想要显示的图片的宽度\n     * @param height 想要显示的图片的高度\n     * @return\n     */\n    @Throws(IOException::class)\n    fun decodeBitmap(path: String, width: Int, height: Int? = null): Bitmap? {\n        val fis = FileInputStream(path)\n        return fis.use {\n            val op = BitmapFactory.Options()\n            // inJustDecodeBounds如果设置为true,仅仅返回图片实际的宽和高,宽和高是赋值给opts.outWidth,opts.outHeight;\n            op.inJustDecodeBounds = true\n            BitmapFactory.decodeFileDescriptor(fis.fd, null, op)\n            op.inSampleSize = calculateInSampleSize(op, width, height)\n            op.inJustDecodeBounds = false\n            BitmapFactory.decodeFileDescriptor(fis.fd, null, op)\n        }\n    }\n\n    /**\n     *计算 InSampleSize。缺省返回1\n     * @param options BitmapFactory.Options,\n     * @param width  想要显示的图片的宽度\n     * @param height 想要显示的图片的高度\n     * @return\n     */\n    private fun calculateInSampleSize(\n        options: BitmapFactory.Options,\n        width: Int? = null,\n        height: Int? = null\n    ): Int {\n        //获取比例大小\n        val wRatio = width?.let { options.outWidth / it } ?: -1\n        val hRatio = height?.let { options.outHeight / it } ?: -1\n        //如果超出指定大小，则缩小相应的比例\n        return when {\n            wRatio > 1 && hRatio > 1 -> max(wRatio, hRatio)\n            wRatio > 1 -> wRatio\n            hRatio > 1 -> hRatio\n            else -> 1\n        }\n    }\n\n    /** 从path中获取Bitmap图片\n     * @param path 图片路径\n     * @return\n     */\n    @Throws(IOException::class)\n    fun decodeBitmap(path: String): Bitmap? {\n        val fis = FileInputStream(path)\n        return fis.use {\n            val opts = BitmapFactory.Options()\n            opts.inJustDecodeBounds = true\n\n            BitmapFactory.decodeFileDescriptor(fis.fd, null, opts)\n            opts.inSampleSize = computeSampleSize(opts, -1, 128 * 128)\n            opts.inJustDecodeBounds = false\n            BitmapFactory.decodeFileDescriptor(fis.fd, null, opts)\n        }\n    }\n\n    /**\n     * 以最省内存的方式读取本地资源的图片\n     * @param context 设备上下文\n     * @param resId 资源ID\n     * @return\n     */\n    fun decodeBitmap(context: Context, resId: Int): Bitmap? {\n        val opt = BitmapFactory.Options()\n        opt.inPreferredConfig = Config.RGB_565\n        return BitmapFactory.decodeResource(context.resources, resId, opt)\n    }\n\n    /**\n     * @param context 设备上下文\n     * @param resId 资源ID\n     * @param width\n     * @param height\n     * @return\n     */\n    fun decodeBitmap(context: Context, resId: Int, width: Int, height: Int): Bitmap? {\n        val op = BitmapFactory.Options()\n        // inJustDecodeBounds如果设置为true,仅仅返回图片实际的宽和高,宽和高是赋值给opts.outWidth,opts.outHeight;\n        op.inJustDecodeBounds = true\n        BitmapFactory.decodeResource(context.resources, resId, op) //获取尺寸信息\n        op.inSampleSize = calculateInSampleSize(op, width, height)\n        op.inJustDecodeBounds = false\n        return BitmapFactory.decodeResource(context.resources, resId, op)\n    }\n\n    /**\n     * @param context 设备上下文\n     * @param fileNameInAssets Assets里面文件的名称\n     * @param width 图片的宽度\n     * @param height 图片的高度\n     * @return Bitmap\n     * @throws IOException\n     */\n    @Throws(IOException::class)\n    fun decodeAssetsBitmap(\n        context: Context,\n        fileNameInAssets: String,\n        width: Int,\n        height: Int\n    ): Bitmap? {\n        var inputStream = context.assets.open(fileNameInAssets)\n        return inputStream.use {\n            val op = BitmapFactory.Options()\n            // inJustDecodeBounds如果设置为true,仅仅返回图片实际的宽和高,宽和高是赋值给opts.outWidth,opts.outHeight;\n            op.inJustDecodeBounds = true\n            BitmapFactory.decodeStream(inputStream, null, op) //获取尺寸信息\n            op.inSampleSize = calculateInSampleSize(op, width, height)\n            inputStream = context.assets.open(fileNameInAssets)\n            op.inJustDecodeBounds = false\n            BitmapFactory.decodeStream(inputStream, null, op)\n        }\n    }\n\n    /**\n     * @param options\n     * @param minSideLength\n     * @param maxNumOfPixels\n     * @return\n     * 设置恰当的inSampleSize是解决该问题的关键之一。BitmapFactory.Options提供了另一个成员inJustDecodeBounds。\n     * 设置inJustDecodeBounds为true后，decodeFile并不分配空间，但可计算出原始图片的长度和宽度，即opts.width和opts.height。\n     * 有了这两个参数，再通过一定的算法，即可得到一个恰当的inSampleSize。\n     * 查看Android源码，Android提供了下面这种动态计算的方法。\n     */\n    fun computeSampleSize(\n        options: BitmapFactory.Options,\n        minSideLength: Int,\n        maxNumOfPixels: Int\n    ): Int {\n        val initialSize = computeInitialSampleSize(options, minSideLength, maxNumOfPixels)\n        var roundedSize: Int\n        if (initialSize <= 8) {\n            roundedSize = 1\n            while (roundedSize < initialSize) {\n                roundedSize = roundedSize shl 1\n            }\n        } else {\n            roundedSize = (initialSize + 7) / 8 * 8\n        }\n        return roundedSize\n    }\n\n\n    private fun computeInitialSampleSize(\n        options: BitmapFactory.Options,\n        minSideLength: Int,\n        maxNumOfPixels: Int\n    ): Int {\n\n        val w = options.outWidth.toDouble()\n        val h = options.outHeight.toDouble()\n\n        val lowerBound = when (maxNumOfPixels) {\n            -1 -> 1\n            else -> ceil(sqrt(w * h / maxNumOfPixels)).toInt()\n        }\n\n        val upperBound = when (minSideLength) {\n            -1 -> 128\n            else -> min(\n                floor(w / minSideLength),\n                floor(h / minSideLength)\n            ).toInt()\n        }\n\n        if (upperBound < lowerBound) {\n            // return the larger one when there is no overlapping zone.\n            return lowerBound\n        }\n\n        return when {\n            maxNumOfPixels == -1 && minSideLength == -1 -> {\n                1\n            }\n            minSideLength == -1 -> {\n                lowerBound\n            }\n            else -> {\n                upperBound\n            }\n        }\n    }\n\n    /**\n     * 将Bitmap转换成InputStream\n     *\n     * @param bitmap\n     * @return\n     */\n    fun toInputStream(bitmap: Bitmap): InputStream {\n        val bos = ByteArrayOutputStream()\n        bitmap.compress(Bitmap.CompressFormat.JPEG, 90 /*ignored for PNG*/, bos)\n        return ByteArrayInputStream(bos.toByteArray()).also { bos.close() }\n    }\n\n}\n\n/**\n * 获取指定宽高的图片\n */\nfun Bitmap.resizeAndRecycle(newWidth: Int, newHeight: Int): Bitmap {\n    //获取新的bitmap\n    val bitmap = Toolkit.resize(this, newWidth, newHeight)\n    recycle()\n    return bitmap\n}\n\n/**\n * 高斯模糊\n */\nfun Bitmap.stackBlur(radius: Int = 8): Bitmap {\n    return Toolkit.blur(this, radius)\n}\n\n/**\n * 取平均色\n */\nfun Bitmap.getMeanColor(): Int {\n    val width: Int = this.width\n    val height: Int = this.height\n    var pixel: Int\n    var pixelSumRed = 0\n    var pixelSumBlue = 0\n    var pixelSumGreen = 0\n    for (i in 0..99) {\n        for (j in 70..99) {\n            pixel = this.getPixel(\n                (i * width / 100.toFloat()).roundToInt(),\n                (j * height / 100.toFloat()).roundToInt()\n            )\n            pixelSumRed += Color.red(pixel)\n            pixelSumGreen += Color.green(pixel)\n            pixelSumBlue += Color.blue(pixel)\n        }\n    }\n    val averagePixelRed = pixelSumRed / 3000\n    val averagePixelBlue = pixelSumBlue / 3000\n    val averagePixelGreen = pixelSumGreen / 3000\n    return Color.rgb(\n        averagePixelRed + 3,\n        averagePixelGreen + 3,\n        averagePixelBlue + 3\n    )\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/utils/BookChapterExtensions.kt",
    "content": "package io.legado.app.utils\n\nimport io.legado.app.data.entities.BookChapter\n\nfun BookChapter.internString() {\n    title = title.intern()\n    bookUrl = bookUrl.intern()\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/utils/ByteArrayExtensions.kt",
    "content": "package io.legado.app.utils\n\n\n/**\n * Search the data byte array for the first occurrence\n * of the byte array pattern.\n */\nfun ByteArray.indexOf(pattern: ByteArray, start: Int = 0, stop: Int = size): Int {\n    val data = this\n    val failure: IntArray = computeFailure(pattern)\n\n    var j = 0\n\n    for (i in start until stop) {\n        while (j > 0 && pattern[j] != data[i]) {\n            j = failure[j - 1]\n        }\n        if (pattern[j] == data[i]) {\n            j++\n        }\n        if (j == pattern.size) {\n            return i - pattern.size + 1\n        }\n    }\n    return -1\n}\n\n/**\n * Computes the failure function using a boot-strapping process,\n * where the pattern is matched against itself.\n */\nprivate fun computeFailure(pattern: ByteArray): IntArray {\n    val failure = IntArray(pattern.size)\n    var j = 0\n    for (i in 1 until pattern.size) {\n        while (j > 0 && pattern[j] != pattern[i]) {\n            j = failure[j - 1]\n        }\n        if (pattern[j] == pattern[i]) {\n            j++\n        }\n        failure[i] = j\n    }\n    return failure\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/utils/ChineseUtils.kt",
    "content": "package io.legado.app.utils\n\nimport com.github.liuyueyi.quick.transfer.ChineseUtils\nimport com.github.liuyueyi.quick.transfer.constants.TransType\n\nobject ChineseUtils {\n\n    private var fixed = false\n\n    fun s2t(content: String): String {\n        return ChineseUtils.s2t(content)\n    }\n\n    fun t2s(content: String): String {\n        if (!fixed) {\n            fixT2sDict()\n        }\n        return ChineseUtils.t2s(content)\n    }\n\n    fun preLoad(async: Boolean, vararg transType: TransType) {\n        ChineseUtils.preLoad(async, *transType)\n    }\n\n    fun unLoad(vararg transType: TransType) {\n        ChineseUtils.unLoad(*transType)\n    }\n\n    fun fixT2sDict() {\n        fixed = true\n        val excludeList = listOf(\n            \"槃\",\n            \"划槳\", \"列根\", \"雪梨\", \"雪糕\", \"多士\", \"起司\", \"芝士\", \"沙芬\", \"母音\",\n            \"华乐\", \"民乐\", \"晶元\", \"晶片\", \"映像\", \"明覆\", \"明瞭\", \"新力\", \"新喻\",\n            \"零錢\", \"零钱\", \"離線\", \"碟片\", \"模組\", \"桌球\", \"案頭\", \"機車\", \"電漿\",\n            \"鳳梨\", \"魔戒\", \"載入\", \"菲林\", \"整合\", \"變數\", \"解碼\", \"散钱\", \"插水\",\n            \"房屋\", \"房价\", \"快取\", \"德士\", \"建立\", \"常式\", \"席丹\", \"布殊\", \"布希\",\n            \"巴哈\", \"巨集\", \"夜学\", \"向量\", \"半形\", \"加彭\", \"列印\", \"函式\", \"全形\",\n            \"光碟\", \"介面\", \"乳酪\", \"沈船\", \"永珍\", \"演化\", \"牛油\", \"相容\", \"磁碟\",\n            \"菲林\", \"規則\", \"酵素\", \"雷根\", \"饭盒\",\n            \"路易斯\", \"非同步\", \"出租车\", \"周杰倫\", \"马铃薯\", \"馬鈴薯\", \"機械人\", \"電單車\",\n            \"電扶梯\", \"音效卡\", \"飆車族\", \"點陣圖\", \"個入球\", \"顆進球\", \"沃尓沃\", \"晶片集\",\n            \"斯瓦巴\", \"斜角巷\", \"战列舰\", \"快速面\", \"希特拉\", \"太空梭\", \"吐瓦魯\", \"吉布堤\",\n            \"吉布地\", \"史太林\", \"南冰洋\", \"区域网\", \"波札那\", \"解析度\", \"酷洛米\", \"金夏沙\",\n            \"魔獸紀元\", \"高空彈跳\", \"铁达尼号\", \"太空战士\", \"埃及妖后\", \"吉里巴斯\", \"附加元件\",\n            \"魔鬼終結者\", \"純文字檔案\", \"奇幻魔法Melody\", \"列支敦斯登\"\n        )\n        ChineseUtils.loadExcludeDict(TransType.TRADITIONAL_TO_SIMPLE, excludeList)\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/utils/CollectionExtensions.kt",
    "content": "package io.legado.app.utils\n\nfun List<Float>.fastSum(): Float {\n    var sum = 0f\n    for (i in indices) {\n        sum += this[i]\n    }\n    return sum\n}\n\ninline fun <T> List<T>.fastBinarySearch(\n    fromIndex: Int = 0,\n    toIndex: Int = size,\n    comparison: (T) -> Int\n): Int {\n    when {\n        fromIndex > toIndex ->\n            throw IllegalArgumentException(\n                \"fromIndex ($fromIndex) is greater than toIndex ($toIndex).\"\n            )\n\n        fromIndex < 0 ->\n            throw IndexOutOfBoundsException(\"fromIndex ($fromIndex) is less than zero.\")\n\n        toIndex > size ->\n            throw IndexOutOfBoundsException(\"toIndex ($toIndex) is greater than size ($size).\")\n    }\n\n    var low = fromIndex\n    var high = toIndex - 1\n\n    while (low <= high) {\n        val mid = (low + high).ushr(1) // safe from overflows\n        val midVal = get(mid)\n        val cmp = comparison(midVal)\n\n        if (cmp < 0)\n            low = mid + 1\n        else if (cmp > 0)\n            high = mid - 1\n        else\n            return mid // key found\n    }\n    return -(low + 1)  // key not found\n}\n\ninline fun <T, K : Comparable<K>> List<T>.fastBinarySearchBy(\n    key: K?,\n    fromIndex: Int = 0,\n    toIndex: Int = size,\n    crossinline selector: (T) -> K?\n): Int = fastBinarySearch(fromIndex, toIndex) { compareValues(selector(it), key) }\n\nfun <T> MutableList<T>.removeLastElement(): T {\n    return if (isEmpty()) {\n        throw NoSuchElementException(\"List is empty.\")\n    } else {\n        removeAt(lastIndex)\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/utils/ColorUtils.kt",
    "content": "package io.legado.app.utils\n\nimport android.graphics.Color\nimport androidx.annotation.ColorInt\nimport androidx.annotation.FloatRange\nimport androidx.core.graphics.ColorUtils\nimport kotlin.math.max\nimport kotlin.math.min\nimport kotlin.math.pow\nimport kotlin.math.roundToInt\nimport kotlin.math.sqrt\n\n@Suppress(\"unused\", \"MemberVisibilityCanBePrivate\")\nobject ColorUtils {\n\n    fun isColorLight(@ColorInt color: Int): Boolean {\n        return ColorUtils.calculateLuminance(color) >= 0.5\n    }\n\n    fun intToString(intColor: Int): String {\n        return String.format(\"#%06X\", 0xFFFFFF and intColor)\n    }\n\n    fun stripAlpha(@ColorInt color: Int): Int {\n        return -0x1000000 or color\n    }\n\n    @ColorInt\n    fun shiftColor(@ColorInt color: Int, @FloatRange(from = 0.0, to = 2.0) by: Float): Int {\n        if (by == 1f) return color\n        val alpha = Color.alpha(color)\n        val hsv = FloatArray(3)\n        Color.colorToHSV(color, hsv)\n        hsv[2] *= by // value component\n        return (alpha shl 24) + (0x00ffffff and Color.HSVToColor(hsv))\n    }\n\n    @ColorInt\n    fun darkenColor(@ColorInt color: Int): Int {\n        return shiftColor(color, 0.9f)\n    }\n\n    @ColorInt\n    fun lightenColor(@ColorInt color: Int): Int {\n        return shiftColor(color, 1.1f)\n    }\n\n    @ColorInt\n    fun invertColor(@ColorInt color: Int): Int {\n        val r = 255 - Color.red(color)\n        val g = 255 - Color.green(color)\n        val b = 255 - Color.blue(color)\n        return Color.argb(Color.alpha(color), r, g, b)\n    }\n\n    @ColorInt\n    fun adjustAlpha(@ColorInt color: Int, @FloatRange(from = 0.0, to = 1.0) factor: Float): Int {\n        val alpha = (Color.alpha(color) * factor).roundToInt()\n        val red = Color.red(color)\n        val green = Color.green(color)\n        val blue = Color.blue(color)\n        return Color.argb(alpha, red, green, blue)\n    }\n\n    @ColorInt\n    fun withAlpha(@ColorInt baseColor: Int, @FloatRange(from = 0.0, to = 1.0) alpha: Float): Int {\n        val a = min(255, max(0, (alpha * 255).toInt())) shl 24\n        val rgb = 0x00ffffff and baseColor\n        return a + rgb\n    }\n\n    /**\n     * Taken from CollapsingToolbarLayout's CollapsingTextHelper class.\n     */\n    fun blendColors(color1: Int, color2: Int, @FloatRange(from = 0.0, to = 1.0) ratio: Float): Int {\n        val inverseRatio = 1f - ratio\n        val a = Color.alpha(color1) * inverseRatio + Color.alpha(color2) * ratio\n        val r = Color.red(color1) * inverseRatio + Color.red(color2) * ratio\n        val g = Color.green(color1) * inverseRatio + Color.green(color2) * ratio\n        val b = Color.blue(color1) * inverseRatio + Color.blue(color2) * ratio\n        return Color.argb(a.toInt(), r.toInt(), g.toInt(), b.toInt())\n    }\n\n    fun argb(r: Int, g: Int, b: Int): Int {\n        return argb(Byte.MAX_VALUE.toInt(), r, g, b)\n    }\n\n    fun argb(alpha: Int, r: Int, g: Int, b: Int): Int {\n        val colorByteArr =\n            byteArrayOf(alpha.toByte(), r.toByte(), g.toByte(), b.toByte())\n        return byteArrToInt(colorByteArr)\n    }\n\n    fun rgb(argb: Int): IntArray {\n        return intArrayOf(argb shr 16 and 0xFF, argb shr 8 and 0xFF, argb and 0xFF)\n    }\n\n    fun byteArrToInt(colorByteArr: ByteArray): Int {\n        return ((colorByteArr[0].toInt() shl 24) + (colorByteArr[1].toInt() and 0xFF shl 16)\n                + (colorByteArr[2].toInt() and 0xFF shl 8) + (colorByteArr[3].toInt() and 0xFF))\n    }\n\n    /**\n     * Computes the difference between two RGB colors by converting them to the L*a*b scale and\n     * comparing them using the CIE76 algorithm { http://en.wikipedia.org/wiki/Color_difference#CIE76}\n     */\n    fun getColorDifference(a: Int, b: Int): Double {\n        val lab1 = DoubleArray(3)\n        val lab2 = DoubleArray(3)\n        ColorUtils.colorToLAB(a, lab1)\n        ColorUtils.colorToLAB(b, lab2)\n        return sqrt(\n            (lab2[0] - lab1[0])\n                .pow(2.0) + (lab2[1] - lab1[1])\n                .pow(2.0) + (lab2[2] - lab1[2])\n                .pow(2.0)\n        )\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/utils/ConfigurationExtensions.kt",
    "content": "@file:Suppress(\"unused\")\n\npackage io.legado.app.utils\n\nimport android.content.res.Configuration\nimport android.content.res.Resources\n\nval sysConfiguration: Configuration = Resources.getSystem().configuration\n\nval Configuration.isNightMode: Boolean\n    get() {\n        val mode = uiMode and Configuration.UI_MODE_NIGHT_MASK\n        return mode == Configuration.UI_MODE_NIGHT_YES\n    }"
  },
  {
    "path": "app/src/main/java/io/legado/app/utils/ConflateLiveData.kt",
    "content": "package io.legado.app.utils\n\nimport android.os.Handler\nimport android.os.Looper\nimport androidx.lifecycle.LiveData\n\n/**\n * 合并发送,只发送最新数据\n * @param delay 发送时间间隔\n */\nclass ConflateLiveData<T>(val delay: Int) : LiveData<T>() {\n    private val handler = Handler(Looper.getMainLooper())\n    private val sendRunnable = Runnable { sendData() }\n    private var postTime = 0L\n    private var data: T? = null\n\n    private fun sendData() {\n        data?.let {\n            super.postValue(it)\n        }\n    }\n\n    @Synchronized\n    public override fun postValue(value: T) {\n        data = value\n        val postDelay = postTime + delay - System.currentTimeMillis()\n        if (postDelay > 0) {\n            handler.removeCallbacks(sendRunnable)\n            handler.postDelayed(sendRunnable, postDelay)\n        } else {\n            handler.removeCallbacks(sendRunnable)\n            postTime = System.currentTimeMillis()\n            super.postValue(value)\n        }\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/utils/ConstraintModify.kt",
    "content": "package io.legado.app.utils\n\nimport androidx.annotation.IdRes\nimport androidx.constraintlayout.widget.ConstraintLayout\nimport androidx.constraintlayout.widget.ConstraintSet\nimport androidx.transition.TransitionManager\n\n@Suppress(\"unused\")\nfun ConstraintLayout.modifyBegin(withAnim: Boolean = false): ConstraintModify.ConstraintBegin {\n    val begin = ConstraintModify(this).begin\n    if (withAnim) {\n        TransitionManager.beginDelayedTransition(this)\n    }\n    return begin\n}\n\n@Suppress(\"MemberVisibilityCanBePrivate\", \"unused\")\nclass ConstraintModify(private val constraintLayout: ConstraintLayout) {\n\n    val begin: ConstraintBegin by lazy {\n        applyConstraintSet.clone(constraintLayout)\n        ConstraintBegin(constraintLayout, applyConstraintSet)\n    }\n    private val applyConstraintSet = ConstraintSet()\n    private val resetConstraintSet = ConstraintSet()\n\n    init {\n        resetConstraintSet.clone(constraintLayout)\n    }\n\n    /**\n     * 带动画的修改\n     * @return\n     */\n    fun beginWithAnim(): ConstraintBegin {\n        TransitionManager.beginDelayedTransition(constraintLayout)\n        return begin\n    }\n\n    /**\n     * 重置\n     */\n    fun reSet() {\n        resetConstraintSet.applyTo(constraintLayout)\n    }\n\n    /**\n     * 带动画的重置\n     */\n    fun reSetWidthAnim() {\n        TransitionManager.beginDelayedTransition(constraintLayout)\n        resetConstraintSet.applyTo(constraintLayout)\n    }\n\n\n    @Suppress(\"unused\", \"MemberVisibilityCanBePrivate\")\n    class ConstraintBegin(\n        private val constraintLayout: ConstraintLayout,\n        private val applyConstraintSet: ConstraintSet\n    ) {\n\n        /**\n         * 清除关系,这里不仅仅会清除关系，还会清除对应控件的宽高为 w:0,h:0\n         * @param viewId 视图ID\n         * @return\n         */\n        fun clear(viewId: Int): ConstraintBegin {\n            applyConstraintSet.clear(viewId)\n            return this\n        }\n\n        /**\n         * 清除某个控件的，某个关系\n         * @param viewId 控件ID\n         * @param anchor 要解除的关系\n         * @return\n         */\n        fun clear(viewId: Int, anchor: Anchor): ConstraintBegin {\n            applyConstraintSet.clear(viewId, anchor.toInt())\n            return this\n        }\n\n        fun setHorizontalWeight(viewId: Int, weight: Float): ConstraintBegin {\n            applyConstraintSet.setHorizontalWeight(viewId, weight)\n            return this\n        }\n\n        fun setVerticalWeight(viewId: Int, weight: Float): ConstraintBegin {\n            applyConstraintSet.setVerticalWeight(viewId, weight)\n            return this\n        }\n\n        /**\n         * 为某个控件设置 margin\n         * @param viewId 某个控件ID\n         * @param left marginLeft\n         * @param top   marginTop\n         * @param right marginRight\n         * @param bottom marginBottom\n         * @return\n         */\n        fun setMargin(\n            @IdRes viewId: Int,\n            left: Int,\n            top: Int,\n            right: Int,\n            bottom: Int\n        ): ConstraintBegin {\n            setMarginLeft(viewId, left)\n            setMarginTop(viewId, top)\n            setMarginRight(viewId, right)\n            setMarginBottom(viewId, bottom)\n            return this\n        }\n\n        /**\n         * 为某个控件设置 marginLeft\n         * @param viewId 某个控件ID\n         * @param left marginLeft\n         * @return\n         */\n        fun setMarginLeft(@IdRes viewId: Int, left: Int): ConstraintBegin {\n            applyConstraintSet.setMargin(viewId, ConstraintSet.LEFT, left)\n            return this\n        }\n\n        /**\n         * 为某个控件设置 marginRight\n         * @param viewId 某个控件ID\n         * @param right marginRight\n         * @return\n         */\n        fun setMarginRight(@IdRes viewId: Int, right: Int): ConstraintBegin {\n            applyConstraintSet.setMargin(viewId, ConstraintSet.RIGHT, right)\n            return this\n        }\n\n        /**\n         * 为某个控件设置 marginTop\n         * @param viewId 某个控件ID\n         * @param top marginTop\n         * @return\n         */\n        fun setMarginTop(@IdRes viewId: Int, top: Int): ConstraintBegin {\n            applyConstraintSet.setMargin(viewId, ConstraintSet.TOP, top)\n            return this\n        }\n\n        /**\n         * 为某个控件设置marginBottom\n         * @param viewId 某个控件ID\n         * @param bottom marginBottom\n         * @return\n         */\n        fun setMarginBottom(@IdRes viewId: Int, bottom: Int): ConstraintBegin {\n            applyConstraintSet.setMargin(viewId, ConstraintSet.BOTTOM, bottom)\n            return this\n        }\n\n        /**\n         * 为某个控件设置关联关系 left_to_left_of\n         * @param startId\n         * @param endId\n         * @return\n         */\n        fun leftToLeftOf(@IdRes startId: Int, @IdRes endId: Int): ConstraintBegin {\n            applyConstraintSet.connect(startId, ConstraintSet.LEFT, endId, ConstraintSet.LEFT)\n            return this\n        }\n\n        /**\n         * 为某个控件设置关联关系 left_to_right_of\n         * @param startId\n         * @param endId\n         * @return\n         */\n        fun leftToRightOf(@IdRes startId: Int, @IdRes endId: Int): ConstraintBegin {\n            applyConstraintSet.connect(startId, ConstraintSet.LEFT, endId, ConstraintSet.RIGHT)\n            return this\n        }\n\n        /**\n         * 为某个控件设置关联关系 top_to_top_of\n         * @param startId\n         * @param endId\n         * @return\n         */\n        fun topToTopOf(@IdRes startId: Int, @IdRes endId: Int): ConstraintBegin {\n            applyConstraintSet.connect(startId, ConstraintSet.TOP, endId, ConstraintSet.TOP)\n            return this\n        }\n\n        /**\n         * 为某个控件设置关联关系 top_to_bottom_of\n         * @param startId\n         * @param endId\n         * @return\n         */\n        fun topToBottomOf(@IdRes startId: Int, @IdRes endId: Int): ConstraintBegin {\n            applyConstraintSet.connect(startId, ConstraintSet.TOP, endId, ConstraintSet.BOTTOM)\n            return this\n        }\n\n        /**\n         * 为某个控件设置关联关系 right_to_left_of\n         * @param startId\n         * @param endId\n         * @return\n         */\n        fun rightToLeftOf(@IdRes startId: Int, @IdRes endId: Int): ConstraintBegin {\n            applyConstraintSet.connect(startId, ConstraintSet.RIGHT, endId, ConstraintSet.LEFT)\n            return this\n        }\n\n        /**\n         * 为某个控件设置关联关系 right_to_right_of\n         * @param startId\n         * @param endId\n         * @return\n         */\n        fun rightToRightOf(@IdRes startId: Int, @IdRes endId: Int): ConstraintBegin {\n            applyConstraintSet.connect(startId, ConstraintSet.RIGHT, endId, ConstraintSet.RIGHT)\n            return this\n        }\n\n        /**\n         * 为某个控件设置关联关系 bottom_to_bottom_of\n         * @param startId\n         * @param endId\n         * @return\n         */\n        fun bottomToBottomOf(@IdRes startId: Int, @IdRes endId: Int): ConstraintBegin {\n            applyConstraintSet.connect(startId, ConstraintSet.BOTTOM, endId, ConstraintSet.BOTTOM)\n            return this\n        }\n\n        /**\n         * 为某个控件设置关联关系 bottom_to_top_of\n         * @param startId\n         * @param endId\n         * @return\n         */\n        fun bottomToTopOf(@IdRes startId: Int, @IdRes endId: Int): ConstraintBegin {\n            applyConstraintSet.connect(startId, ConstraintSet.BOTTOM, endId, ConstraintSet.TOP)\n            return this\n        }\n\n        /**\n         * 为某个控件设置宽度\n         * @param viewId\n         * @param width\n         * @return\n         */\n        fun setWidth(@IdRes viewId: Int, width: Int): ConstraintBegin {\n            applyConstraintSet.constrainWidth(viewId, width)\n            return this\n        }\n\n        /**\n         * 某个控件设置高度\n         * @param viewId\n         * @param height\n         * @return\n         */\n        fun setHeight(@IdRes viewId: Int, height: Int): ConstraintBegin {\n            applyConstraintSet.constrainHeight(viewId, height)\n            return this\n        }\n\n        /**\n         * 提交应用生效\n         */\n        fun commit() {\n            constraintLayout.post {\n                applyConstraintSet.applyTo(constraintLayout)\n            }\n        }\n    }\n\n    enum class Anchor {\n        LEFT, RIGHT, TOP, BOTTOM, BASELINE, START, END, CIRCLE_REFERENCE;\n\n        fun toInt(): Int {\n            return when (this) {\n                LEFT -> ConstraintSet.LEFT\n                RIGHT -> ConstraintSet.RIGHT\n                TOP -> ConstraintSet.TOP\n                BOTTOM -> ConstraintSet.BOTTOM\n                BASELINE -> ConstraintSet.BASELINE\n                START -> ConstraintSet.START\n                END -> ConstraintSet.END\n                CIRCLE_REFERENCE -> ConstraintSet.CIRCLE_REFERENCE\n            }\n        }\n\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/utils/ContextExtensions.kt",
    "content": "@file:Suppress(\"unused\", \"UnusedReceiverParameter\")\n\npackage io.legado.app.utils\n\nimport android.annotation.SuppressLint\nimport android.app.Activity\nimport android.app.PendingIntent\nimport android.app.PendingIntent.FLAG_MUTABLE\nimport android.app.PendingIntent.FLAG_UPDATE_CURRENT\nimport android.app.PendingIntent.getActivity\nimport android.app.PendingIntent.getBroadcast\nimport android.app.PendingIntent.getService\nimport android.app.Service\nimport android.content.BroadcastReceiver\nimport android.content.ClipData\nimport android.content.Context\nimport android.content.Intent\nimport android.content.IntentFilter\nimport android.content.SharedPreferences\nimport android.content.pm.ApplicationInfo\nimport android.content.pm.PackageManager\nimport android.content.res.ColorStateList\nimport android.content.res.Configuration\nimport android.graphics.Bitmap\nimport android.graphics.drawable.Drawable\nimport android.net.ConnectivityManager\nimport android.net.Uri\nimport android.os.BatteryManager\nimport android.os.Build\nimport android.os.Process\nimport android.provider.Settings\nimport androidx.annotation.ColorRes\nimport androidx.annotation.DrawableRes\nimport androidx.core.content.ContextCompat\nimport androidx.core.content.FileProvider\nimport androidx.core.content.edit\nimport androidx.preference.PreferenceManager\nimport com.google.zxing.qrcode.decoder.ErrorCorrectionLevel\nimport io.legado.app.R\nimport io.legado.app.constant.AppConst\nimport io.legado.app.data.entities.Book\nimport io.legado.app.help.IntentHelp\nimport io.legado.app.help.book.isAudio\nimport io.legado.app.help.book.isImage\nimport io.legado.app.help.book.isLocal\nimport io.legado.app.help.config.AppConfig\nimport io.legado.app.ui.book.audio.AudioPlayActivity\nimport io.legado.app.ui.book.manga.ReadMangaActivity\nimport io.legado.app.ui.book.read.ReadBookActivity\nimport splitties.systemservices.clipboardManager\nimport splitties.systemservices.connectivityManager\nimport splitties.systemservices.uiModeManager\nimport java.io.File\nimport java.io.FileOutputStream\nimport kotlin.system.exitProcess\n\ninline fun <reified A : Activity> Context.startActivity(configIntent: Intent.() -> Unit = {}) {\n    val intent = Intent(this, A::class.java)\n    intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)\n    intent.apply(configIntent)\n    startActivity(intent)\n}\n\nfun Context.startActivityForBook(\n    book: Book,\n    configIntent: Intent.() -> Unit = {},\n) {\n    val cls = when {\n        book.isAudio -> AudioPlayActivity::class.java\n        !book.isLocal && book.isImage && AppConfig.showMangaUi -> ReadMangaActivity::class.java\n        else -> ReadBookActivity::class.java\n    }\n    val intent = Intent(this, cls)\n    intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)\n    intent.putExtra(\"bookUrl\", book.bookUrl)\n    intent.apply(configIntent)\n    startActivity(intent)\n}\n\n\ninline fun <reified T : Service> Context.startService(configIntent: Intent.() -> Unit = {}) {\n    startService(Intent(this, T::class.java).apply(configIntent))\n}\n\ninline fun <reified T : Service> Context.stopService() {\n    stopService(Intent(this, T::class.java))\n}\n\n@SuppressLint(\"UnspecifiedImmutableFlag\")\ninline fun <reified T : Service> Context.servicePendingIntent(\n    action: String,\n    requestCode: Int = 0,\n    configIntent: Intent.() -> Unit = {},\n): PendingIntent? {\n    val intent = Intent(this, T::class.java)\n    intent.action = action\n    configIntent.invoke(intent)\n    val flags = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {\n        FLAG_UPDATE_CURRENT or FLAG_MUTABLE\n    } else {\n        FLAG_UPDATE_CURRENT\n    }\n    return getService(this, requestCode, intent, flags)\n}\n\n@SuppressLint(\"UnspecifiedImmutableFlag\")\nfun Context.activityPendingIntent(\n    intent: Intent,\n    action: String,\n): PendingIntent? {\n    intent.action = action\n    val flags = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {\n        FLAG_UPDATE_CURRENT or FLAG_MUTABLE\n    } else {\n        FLAG_UPDATE_CURRENT\n    }\n    return getActivity(this, 0, intent, flags)\n}\n\n@SuppressLint(\"UnspecifiedImmutableFlag\")\ninline fun <reified T : Activity> Context.activityPendingIntent(\n    action: String,\n    configIntent: Intent.() -> Unit = {},\n): PendingIntent? {\n    val intent = Intent(this, T::class.java)\n    intent.action = action\n    configIntent.invoke(intent)\n    val flags = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {\n        FLAG_UPDATE_CURRENT or FLAG_MUTABLE\n    } else {\n        FLAG_UPDATE_CURRENT\n    }\n    return getActivity(this, 0, intent, flags)\n}\n\n@SuppressLint(\"UnspecifiedImmutableFlag\")\ninline fun <reified T : BroadcastReceiver> Context.broadcastPendingIntent(\n    action: String,\n    configIntent: Intent.() -> Unit = {},\n): PendingIntent? {\n    val intent = Intent(this, T::class.java)\n    intent.action = action\n    configIntent.invoke(intent)\n    val flags = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {\n        FLAG_UPDATE_CURRENT or FLAG_MUTABLE\n    } else {\n        FLAG_UPDATE_CURRENT\n    }\n    return getBroadcast(this, 0, intent, flags)\n}\n\nfun Context.startForegroundServiceCompat(intent: Intent) {\n    try {\n        startService(intent)\n    } catch (e: IllegalStateException) {\n        ContextCompat.startForegroundService(this, intent)\n    }\n}\n\nval Context.defaultSharedPreferences: SharedPreferences\n    get() = PreferenceManager.getDefaultSharedPreferences(this)\n\nfun Context.getPrefBoolean(key: String, defValue: Boolean = false) =\n    defaultSharedPreferences.getBoolean(key, defValue)\n\nfun Context.putPrefBoolean(key: String, value: Boolean = false) =\n    defaultSharedPreferences.edit { putBoolean(key, value) }\n\nfun Context.getPrefInt(key: String, defValue: Int = 0) =\n    defaultSharedPreferences.getInt(key, defValue)\n\nfun Context.putPrefInt(key: String, value: Int) =\n    defaultSharedPreferences.edit { putInt(key, value) }\n\nfun Context.getPrefLong(key: String, defValue: Long = 0L) =\n    defaultSharedPreferences.getLong(key, defValue)\n\nfun Context.putPrefLong(key: String, value: Long) =\n    defaultSharedPreferences.edit { putLong(key, value) }\n\nfun Context.getPrefString(key: String, defValue: String? = null) =\n    defaultSharedPreferences.getString(key, defValue)\n\nfun Context.putPrefString(key: String, value: String?) =\n    defaultSharedPreferences.edit { putString(key, value) }\n\nfun Context.getPrefStringSet(\n    key: String,\n    defValue: MutableSet<String>? = null,\n): MutableSet<String>? = defaultSharedPreferences.getStringSet(key, defValue)\n\nfun Context.putPrefStringSet(key: String, value: MutableSet<String>) =\n    defaultSharedPreferences.edit { putStringSet(key, value) }\n\nfun Context.removePref(key: String) =\n    defaultSharedPreferences.edit { remove(key) }\n\n\nfun Context.getCompatColor(@ColorRes id: Int): Int = ContextCompat.getColor(this, id)\n\nfun Context.getCompatDrawable(@DrawableRes id: Int): Drawable? = ContextCompat.getDrawable(this, id)\n\nfun Context.getCompatColorStateList(@ColorRes id: Int): ColorStateList? =\n    ContextCompat.getColorStateList(this, id)\n\nfun Context.checkSelfUriPermission(uri: Uri, modeFlags: Int): Int =\n    checkUriPermission(uri, Process.myPid(), Process.myUid(), modeFlags)\n\nfun Context.restart() {\n    val intent: Intent? = packageManager.getLaunchIntentForPackage(packageName)\n    intent?.let {\n        intent.addFlags(\n            Intent.FLAG_ACTIVITY_NEW_TASK\n                    or Intent.FLAG_ACTIVITY_CLEAR_TASK\n                    or Intent.FLAG_ACTIVITY_CLEAR_TOP\n        )\n        startActivity(intent)\n        //杀掉以前进程\n        Process.killProcess(Process.myPid())\n        exitProcess(0)\n    }\n}\n\n/**\n * 系统息屏时间\n */\nval Context.sysScreenOffTime: Int\n    get() {\n        return kotlin.runCatching {\n            Settings.System.getInt(contentResolver, Settings.System.SCREEN_OFF_TIMEOUT)\n        }.onFailure {\n            it.printOnDebug()\n        }.getOrDefault(0)\n    }\n\nval Context.statusBarHeight: Int\n    @SuppressLint(\"DiscouragedApi\", \"InternalInsetResource\")\n    get() {\n        if (Build.BOARD == \"windows\") {\n            return 0\n        }\n        val resourceId = resources.getIdentifier(\"status_bar_height\", \"dimen\", \"android\")\n        return resources.getDimensionPixelSize(resourceId)\n    }\n\nval Context.navigationBarHeight: Int\n    @SuppressLint(\"DiscouragedApi\", \"InternalInsetResource\")\n    get() {\n        val resourceId = resources.getIdentifier(\"navigation_bar_height\", \"dimen\", \"android\")\n        return resources.getDimensionPixelSize(resourceId)\n    }\n\nfun Context.share(text: String, title: String = getString(R.string.share)) {\n    kotlin.runCatching {\n        val intent = Intent(Intent.ACTION_SEND)\n        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)\n        intent.putExtra(Intent.EXTRA_SUBJECT, title)\n        intent.putExtra(Intent.EXTRA_TEXT, text)\n        intent.type = \"text/plain\"\n        startActivity(Intent.createChooser(intent, title))\n    }\n}\n\nfun Context.share(file: File, type: String = \"text/*\") {\n    val fileUri = FileProvider.getUriForFile(this, AppConst.authority, file)\n    val intent = Intent(Intent.ACTION_SEND)\n    intent.type = type\n    intent.putExtra(Intent.EXTRA_STREAM, fileUri)\n    intent.flags = Intent.FLAG_GRANT_READ_URI_PERMISSION\n    intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)\n    startActivity(\n        Intent.createChooser(\n            intent,\n            getString(R.string.share_selected_source)\n        )\n    )\n}\n\n@SuppressLint(\"SetWorldReadable\")\nfun Context.shareWithQr(\n    text: String,\n    title: String = getString(R.string.share),\n    errorCorrectionLevel: ErrorCorrectionLevel = ErrorCorrectionLevel.H,\n) {\n    val bitmap = QRCodeUtils.createQRCode(text, errorCorrectionLevel = errorCorrectionLevel)\n    if (bitmap == null) {\n        toastOnUi(R.string.text_too_long_qr_error)\n    } else {\n        try {\n            val file = File(externalCacheDir, \"qr.png\")\n            val fOut = FileOutputStream(file)\n            bitmap.compress(Bitmap.CompressFormat.PNG, 100, fOut)\n            fOut.flush()\n            fOut.close()\n            file.setReadable(true, false)\n            val contentUri = FileProvider.getUriForFile(this, AppConst.authority, file)\n            val intent = Intent(Intent.ACTION_SEND)\n            intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)\n            intent.putExtra(Intent.EXTRA_STREAM, contentUri)\n            intent.type = \"image/png\"\n            startActivity(Intent.createChooser(intent, title))\n        } catch (e: Exception) {\n            toastOnUi(e.localizedMessage ?: \"ERROR\")\n        }\n    }\n}\n\nfun Context.sendToClip(text: String) {\n    val clipData = ClipData.newPlainText(null, text)\n    clipboardManager.setPrimaryClip(clipData)\n    longToastOnUi(R.string.copy_complete)\n}\n\nfun Context.getClipText(): String? {\n    clipboardManager.primaryClip?.let {\n        if (it.itemCount > 0) {\n            return it.getItemAt(0).text.toString().trim()\n        }\n    }\n    return null\n}\n\nfun Context.sendMail(mail: String) {\n    try {\n        val intent = Intent(Intent.ACTION_SENDTO)\n        intent.data = Uri.parse(\"mailto:$mail\")\n        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)\n        startActivity(intent)\n    } catch (e: Exception) {\n        toastOnUi(e.localizedMessage ?: \"Error\")\n    }\n}\n\n/**\n * 获取电量\n */\nval Context.sysBattery: Int\n    get() {\n        val iFilter = IntentFilter(Intent.ACTION_BATTERY_CHANGED)\n        val batteryStatus = registerReceiver(null, iFilter)\n        return batteryStatus?.getIntExtra(BatteryManager.EXTRA_LEVEL, -1) ?: -1\n    }\n\nval Context.externalFiles: File\n    get() = this.getExternalFilesDir(null) ?: this.filesDir\n\nval Context.externalCache: File\n    get() = this.externalCacheDir ?: this.cacheDir\n\nfun Context.openUrl(url: String) {\n    try {\n        startActivity(IntentHelp.getBrowserIntent(url))\n    } catch (e: Exception) {\n        toastOnUi(e.localizedMessage ?: \"open url error\")\n        e.printOnDebug()\n    }\n}\n\nfun Context.openUrl(uri: Uri) {\n    try {\n        startActivity(IntentHelp.getBrowserIntent(uri))\n    } catch (e: Exception) {\n        toastOnUi(e.localizedMessage ?: \"open url error\")\n        e.printOnDebug()\n    }\n}\n\n@SuppressLint(\"ObsoleteSdkInt\")\nfun Context.openFileUri(uri: Uri, type: String? = null) {\n    val intent = Intent()\n    intent.action = Intent.ACTION_VIEW\n    intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)\n    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {\n        //7.0版本以上\n        intent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)\n    }\n    val uri = if (uri.isContentScheme()) uri\n    else FileProvider.getUriForFile(this, AppConst.authority, File(uri.path!!))\n    intent.setDataAndType(uri, type ?: IntentType.from(uri))\n    try {\n        startActivity(intent)\n    } catch (e: Exception) {\n        toastOnUi(e.stackTraceStr)\n        e.printOnDebug()\n    }\n}\n\n@Suppress(\"DEPRECATION\")\nval Context.isWifiConnect: Boolean\n    @SuppressLint(\"MissingPermission\")\n    get() {\n        val info = connectivityManager.getNetworkInfo(ConnectivityManager.TYPE_WIFI)\n        return info?.isConnected == true\n    }\n\nval Context.isPad: Boolean\n    get() {\n        return (resources.configuration.screenLayout and Configuration.SCREENLAYOUT_SIZE_MASK) >= Configuration.SCREENLAYOUT_SIZE_LARGE\n    }\n\nval Context.isTv: Boolean\n    get() = uiModeManager.currentModeType == Configuration.UI_MODE_TYPE_TELEVISION\n\nval Context.channel: String\n    get() {\n        try {\n            val pm = packageManager\n            val appInfo = pm.getApplicationInfo(packageName, PackageManager.GET_META_DATA)\n            return appInfo.metaData.getString(\"channel\") ?: \"\"\n        } catch (e: Exception) {\n            e.printOnDebug()\n        }\n        return \"\"\n    }\n\nval Context.isDebuggable: Boolean\n    get() = applicationInfo.flags and ApplicationInfo.FLAG_DEBUGGABLE != 0\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/utils/ConvertExtensions.kt",
    "content": "@file:Suppress(\"unused\")\n\npackage io.legado.app.utils\n\nimport android.content.res.Resources\nimport android.graphics.Bitmap\nimport android.graphics.BitmapFactory\nimport android.graphics.drawable.BitmapDrawable\nimport android.graphics.drawable.Drawable\nimport java.io.BufferedReader\nimport java.io.InputStream\nimport java.io.InputStreamReader\nimport java.text.DecimalFormat\nimport kotlin.math.log10\nimport kotlin.math.pow\n\n/**\n * 数据类型转换、单位转换\n *\n * @author 李玉江[QQ:1023694760]\n * @since 2014-4-18\n */\n@Suppress(\"MemberVisibilityCanBePrivate\")\nobject ConvertUtils {\n    const val GB: Long = 1073741824\n    const val MB: Long = 1048576\n    const val KB: Long = 1024\n\n    fun toInt(obj: Any): Int {\n        return kotlin.runCatching {\n            Integer.parseInt(obj.toString())\n        }.getOrDefault(-1)\n    }\n\n    fun toInt(bytes: ByteArray): Int {\n        var result = 0\n        var byte: Byte\n        for (i in bytes.indices) {\n            byte = bytes[i]\n            result += (byte.toInt() and 0xFF).shl(8 * i)\n        }\n        return result\n    }\n\n    fun toFloat(obj: Any): Float {\n        return kotlin.runCatching {\n            java.lang.Float.parseFloat(obj.toString())\n        }.getOrDefault(-1f)\n    }\n\n    fun toString(objects: Array<Any>, tag: String): String {\n        val sb = StringBuilder()\n        for (`object` in objects) {\n            sb.append(`object`)\n            sb.append(tag)\n        }\n        return sb.toString()\n    }\n\n    @JvmOverloads\n    fun toBitmap(bytes: ByteArray, width: Int = -1, height: Int = -1): Bitmap? {\n        var bitmap: Bitmap? = null\n        if (bytes.isNotEmpty()) {\n            kotlin.runCatching {\n                val options = BitmapFactory.Options()\n                // 设置让解码器以最佳方式解码\n                options.inPreferredConfig = null\n                if (width > 0 && height > 0) {\n                    options.outWidth = width\n                    options.outHeight = height\n                }\n                bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.size, options)\n                bitmap!!.density = 96// 96 dpi\n            }\n        }\n        return bitmap\n    }\n\n    private fun toDrawable(bitmap: Bitmap?): Drawable? {\n        return if (bitmap == null) null else BitmapDrawable(Resources.getSystem(), bitmap)\n    }\n\n    fun toDrawable(bytes: ByteArray): Drawable? {\n        return toDrawable(toBitmap(bytes))\n    }\n\n    fun formatFileSize(length: Long): String {\n        if (length <= 0) return \"0\"\n        val units = arrayOf(\"b\", \"kb\", \"M\", \"G\", \"T\")\n        //计算单位的，原理是利用lg,公式是 lg(1024^n) = nlg(1024)，最后 nlg(1024)/lg(1024) = n。\n        val digitGroups = (log10(length.toDouble()) / log10(1024.0)).toInt()\n        //计算原理是，size/单位值。单位值指的是:比如说b = 1024,KB = 1024^2\n        return DecimalFormat(\"#,##0.##\").format(length / 1024.0.pow(digitGroups.toDouble())) + \" \" + units[digitGroups]\n    }\n\n    @JvmOverloads\n    fun toString(`is`: InputStream, charset: String = \"utf-8\"): String {\n        val sb = StringBuilder()\n        kotlin.runCatching {\n            val reader = BufferedReader(InputStreamReader(`is`, charset))\n            while (true) {\n                val line = reader.readLine()\n                if (line == null) {\n                    break\n                } else {\n                    sb.append(line).append(\"\\n\")\n                }\n            }\n            reader.close()\n            `is`.close()\n        }\n        return sb.toString()\n    }\n\n\n}\n\nval Int.hexString: String\n    get() = Integer.toHexString(this)\n\nfun Int.dpToPx(): Int = this.toFloat().dpToPx().toInt()\n\nfun Int.spToPx(): Int = this.toFloat().spToPx().toInt()\n\nfun Float.dpToPx(): Float = android.util.TypedValue.applyDimension(\n    android.util.TypedValue.COMPLEX_UNIT_DIP, this, Resources.getSystem().displayMetrics\n)\n\nfun Float.spToPx(): Float = android.util.TypedValue.applyDimension(\n    android.util.TypedValue.COMPLEX_UNIT_SP, this, Resources.getSystem().displayMetrics\n)"
  },
  {
    "path": "app/src/main/java/io/legado/app/utils/CookieManagerExtensions.kt",
    "content": "@file:Suppress(\"UnusedReceiverParameter\")\n\npackage io.legado.app.utils\n\nimport android.webkit.CookieManager\n\n\n@Suppress(\"unused\")\nfun CookieManager.removeCookie(url: String) {\n    val cm = CookieManager.getInstance()\n    val domains = arrayOf(\n        NetworkUtils.getDomain(url),\n        NetworkUtils.getSubDomain(url)\n    )\n    domains.forEach { dm ->\n        val cookieGlob: String? = cm.getCookie(dm)\n        cookieGlob?.splitNotBlank(\";\")?.forEach {\n            val cookieName = it.substringBefore(\"=\")\n            cm.setCookie(dm, \"$cookieName=; Expires=Wed, 31 Dec 2000 23:59:59 GMT\")\n        }\n    }\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/utils/CoroutineExtensions.kt",
    "content": "package io.legado.app.utils\n\nimport io.legado.app.help.coroutine.Coroutine\nimport kotlinx.coroutines.CancellationException\nimport kotlinx.coroutines.CoroutineScope\nimport kotlinx.coroutines.delay\nimport kotlinx.coroutines.launch\nimport kotlinx.coroutines.suspendCancellableCoroutine\nimport kotlin.coroutines.resume\nimport kotlin.coroutines.resumeWithException\n\nclass TimeoutCancellationException(msg: String) : CancellationException(msg)\n\nsuspend fun <T> withTimeoutAsync(delayMillis: Long, block: suspend CoroutineScope.() -> T): T {\n    return suspendCancellableCoroutine { cout ->\n        Coroutine.async(context = cout.context) {\n            launch {\n                delay(delayMillis)\n                if (!cout.isCompleted) {\n                    cout.resumeWithException(TimeoutCancellationException(\"Timed out waiting for $delayMillis ms\"))\n                }\n            }\n            val result = block()\n            if (!cout.isCompleted) {\n                cout.resume(result)\n            }\n        }\n    }\n}\n\nsuspend fun <T> withTimeoutOrNullAsync(delayMillis: Long, block: suspend CoroutineScope.() -> T): T? {\n    return try {\n        withTimeoutAsync(delayMillis, block)\n    } catch (e: TimeoutCancellationException) {\n        null\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/utils/CustomExportUtils.kt",
    "content": "package io.legado.app.utils\n\nimport io.legado.app.help.config.AppConfig\n\n// 匹配待“输入的章节”字符串\nprivate val regexEpisode = Regex(\"\\\\d+(-\\\\d+)?(,\\\\d+(-\\\\d+)?)*\")\n\n/**\n * 是否启用自定义导出\n *\n * @author Discut\n */\nfun enableCustomExport(): Boolean {\n    return AppConfig.enableCustomExport && AppConfig.exportType == 1\n}\n\n/**\n * 验证 输入的范围 是否正确\n *\n * @since 1.0.0\n * @author Discut\n * @param text 输入的范围 字符串\n * @return 是否正确\n */\nfun verificationField(text: String): Boolean {\n    return text.matches(regexEpisode)\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/utils/Debounce.kt",
    "content": "package io.legado.app.utils\n\nimport android.os.SystemClock\nimport kotlin.math.max\n\n@Suppress(\"MemberVisibilityCanBePrivate\")\nopen class Debounce<T>(\n    var wait: Long = 0L,\n    var maxWait: Long = -1L,\n    var leading: Boolean = false,\n    var trailing: Boolean = true,\n    private val func: () -> T\n) {\n    companion object {\n        private val handler by lazy { buildMainHandler() }\n    }\n\n    private var lastCallTime = -1L\n    private var lastInvokeTime = 0L\n    private val maxing get() = maxWait != -1L\n    private var result: T? = null\n    private var hasTimer = false\n    private val timerExpiredRunnable = Runnable {\n        timerExpired()\n    }\n\n    init {\n        maxWait = if (maxing) max(maxWait, wait) else maxWait\n    }\n\n    private fun invokeFunc(time: Long): T {\n        lastInvokeTime = time\n        return func.invoke().also { result = it }\n    }\n\n    private fun startTimer(wait: Long) {\n        hasTimer = true\n        handler.postDelayed(timerExpiredRunnable, wait)\n    }\n\n    private fun cancelTimer() {\n        handler.removeCallbacks(timerExpiredRunnable)\n    }\n\n    private fun leadingEdge(time: Long): T? {\n        lastInvokeTime = time\n        startTimer(wait)\n        return if (leading) invokeFunc(time) else result\n    }\n\n    private fun trailingEdge(time: Long): T? {\n        hasTimer = false\n        return if (trailing) invokeFunc(time) else result\n    }\n\n    private fun remainingWait(time: Long): Long {\n        val timeSinceLastCall = time - lastCallTime\n        val timeSinceLastInvoke = time - lastInvokeTime\n        val timeWaiting = wait - timeSinceLastCall\n\n        return if (maxing) timeWaiting.coerceAtMost(maxWait - timeSinceLastInvoke) else timeWaiting\n    }\n\n    private fun shouldInvoke(time: Long): Boolean {\n        val timeSinceLastCall = time - lastCallTime\n        val timeSinceLastInvoke = time - lastInvokeTime\n\n        return lastCallTime == -1L\n                || timeSinceLastCall >= wait\n                || timeSinceLastCall < 0\n                || maxing && timeSinceLastInvoke >= maxWait\n    }\n\n    private fun timerExpired() {\n        val time = SystemClock.uptimeMillis()\n        if (shouldInvoke(time)) {\n            trailingEdge(time)\n        } else {\n            startTimer(remainingWait(time))\n        }\n    }\n\n    fun cancel() {\n        if (hasTimer) {\n            cancelTimer()\n        }\n        lastInvokeTime = 0\n        lastCallTime = -1L\n        hasTimer = false\n    }\n\n    fun flush(): T? {\n        return if (hasTimer) trailingEdge(SystemClock.uptimeMillis()) else result\n    }\n\n    fun pending(): Boolean = hasTimer\n\n    operator fun invoke(): T? {\n        val time = SystemClock.uptimeMillis()\n        val isInvoking = shouldInvoke(time)\n\n        lastCallTime = time\n\n        if (isInvoking) {\n            if (!hasTimer) {\n                return leadingEdge(lastCallTime)\n            }\n            if (maxing) {\n                startTimer(wait)\n                return invokeFunc(lastCallTime)\n            }\n        }\n\n        if (!hasTimer) {\n            startTimer(wait)\n        }\n\n        return result\n    }\n\n}\n\nfun <T> debounce(\n    wait: Long = 0L,\n    maxWait: Long = -1L,\n    leading: Boolean = false,\n    trailing: Boolean = true,\n    func: () -> T\n) = Debounce(wait, maxWait, leading, trailing, func)\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/utils/DebugLog.kt",
    "content": "package io.legado.app.utils\n\nimport android.util.Log\nimport io.legado.app.BuildConfig\n\nobject DebugLog {\n\n    fun e(tag: String, throwable: Throwable) {\n        if (BuildConfig.DEBUG) {\n            Log.e(tag, throwable.stackTraceToString())\n        }\n    }\n\n    fun e(tag: String, msg: String, throwable: Throwable? = null) {\n        if (BuildConfig.DEBUG) {\n            if (throwable == null) {\n                Log.e(tag, msg)\n            } else {\n                Log.e(tag, msg, throwable)\n            }\n        }\n    }\n\n    fun d(tag: String, msg: String, throwable: Throwable? = null) {\n        if (BuildConfig.DEBUG) {\n            if (throwable == null) {\n                Log.d(tag, msg)\n            } else {\n                Log.d(tag, msg, throwable)\n            }\n        }\n    }\n\n    fun i(tag: String, msg: String, throwable: Throwable? = null) {\n        if (BuildConfig.DEBUG) {\n            if (throwable == null) {\n                Log.i(tag, msg)\n            } else {\n                Log.i(tag, msg, throwable)\n            }\n        }\n    }\n\n    fun w(tag: String, msg: String, throwable: Throwable? = null) {\n        if (BuildConfig.DEBUG) {\n            if (throwable == null) {\n                Log.w(tag, msg)\n            } else {\n                Log.w(tag, msg, throwable)\n            }\n        }\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/utils/DialogExtensions.kt",
    "content": "package io.legado.app.utils\n\nimport android.app.Dialog\nimport android.view.WindowManager\nimport androidx.appcompat.app.AlertDialog\nimport androidx.core.view.forEach\nimport androidx.fragment.app.DialogFragment\nimport io.legado.app.lib.theme.Selector\nimport io.legado.app.lib.theme.ThemeStore\nimport io.legado.app.lib.theme.accentColor\nimport io.legado.app.lib.theme.filletBackground\nimport splitties.systemservices.windowManager\n\nfun AlertDialog.applyTint(): AlertDialog {\n    window?.setBackgroundDrawable(context.filletBackground)\n    val colorStateList = Selector.colorBuild()\n        .setDefaultColor(ThemeStore.accentColor(context))\n        .setPressedColor(ColorUtils.darkenColor(ThemeStore.accentColor(context)))\n        .create()\n    if (getButton(AlertDialog.BUTTON_NEGATIVE) != null) {\n        getButton(AlertDialog.BUTTON_NEGATIVE).setTextColor(colorStateList)\n    }\n    if (getButton(AlertDialog.BUTTON_POSITIVE) != null) {\n        getButton(AlertDialog.BUTTON_POSITIVE).setTextColor(colorStateList)\n    }\n    if (getButton(AlertDialog.BUTTON_NEUTRAL) != null) {\n        getButton(AlertDialog.BUTTON_NEUTRAL).setTextColor(colorStateList)\n    }\n    window?.decorView?.post {\n        listView?.forEach {\n            it.applyTint(context.accentColor)\n        }\n    }\n    return this\n}\n\nfun AlertDialog.requestInputMethod() {\n    window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE)\n}\n\nfun DialogFragment.setLayout(widthMix: Float, heightMix: Float) {\n    dialog?.setLayout(widthMix, heightMix)\n}\n\nfun Dialog.setLayout(widthMix: Float, heightMix: Float) {\n    val dm = context.windowManager.windowSize\n    window?.setLayout(\n        (dm.widthPixels * widthMix).toInt(),\n        (dm.heightPixels * heightMix).toInt()\n    )\n}\n\nfun DialogFragment.setLayout(width: Int, heightMix: Float) {\n    dialog?.setLayout(width, heightMix)\n}\n\nfun Dialog.setLayout(width: Int, heightMix: Float) {\n    val dm = context.windowManager.windowSize\n    window?.setLayout(\n        width,\n        (dm.heightPixels * heightMix).toInt()\n    )\n}\n\nfun DialogFragment.setLayout(widthMix: Float, height: Int) {\n    dialog?.setLayout(widthMix, height)\n}\n\nfun Dialog.setLayout(widthMix: Float, height: Int) {\n    val dm = context.windowManager.windowSize\n    window?.setLayout(\n        (dm.widthPixels * widthMix).toInt(),\n        height\n    )\n}\n\nfun DialogFragment.setLayout(width: Int, height: Int) {\n    dialog?.setLayout(width, height)\n}\n\nfun Dialog.setLayout(width: Int, height: Int) {\n    window?.setLayout(width, height)\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/utils/DocumentUtils.kt",
    "content": "package io.legado.app.utils\n\nimport androidx.documentfile.provider.DocumentFile\n\n@Suppress(\"MemberVisibilityCanBePrivate\")\nobject DocumentUtils {\n\n    fun exists(root: DocumentFile, fileName: String, vararg subDirs: String): Boolean {\n        val parent = getDirDocument(root, *subDirs) ?: return false\n        return parent.findFile(fileName)?.exists() ?: false\n    }\n\n    fun delete(root: DocumentFile, fileName: String, vararg subDirs: String) {\n        val parent: DocumentFile? = createFolderIfNotExist(root, *subDirs)\n        parent?.findFile(fileName)?.delete()\n    }\n\n    fun createFileIfNotExist(\n        root: DocumentFile,\n        fileName: String,\n        vararg subDirs: String\n    ): DocumentFile? {\n        val parent: DocumentFile? = createFolderIfNotExist(root, *subDirs)\n        return parent?.findFile(fileName) ?: parent?.createFile(\"\", fileName)\n    }\n\n    fun createFolderIfNotExist(root: DocumentFile, vararg subDirs: String): DocumentFile? {\n        var parent: DocumentFile? = root\n        for (subDirName in subDirs) {\n            val subDir = parent?.findFile(subDirName)\n                ?: parent?.createDirectory(subDirName)\n            parent = subDir\n        }\n        return parent\n    }\n\n    fun getDirDocument(root: DocumentFile, vararg subDirs: String): DocumentFile? {\n        var parent = root\n        for (subDirName in subDirs) {\n            val subDir = parent.findFile(subDirName)\n            parent = subDir ?: return null\n        }\n        return parent\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/utils/DrawableUtils.kt",
    "content": "@file:Suppress(\"unused\")\n\npackage io.legado.app.utils\n\nimport android.content.res.ColorStateList\nimport android.graphics.PorterDuff\nimport android.graphics.drawable.ColorDrawable\nimport android.graphics.drawable.Drawable\nimport android.graphics.drawable.TransitionDrawable\nimport androidx.annotation.ColorInt\nimport androidx.core.graphics.drawable.DrawableCompat\n\n/**\n * @author Karim Abou Zeid (kabouzeid)\n */\n@Suppress(\"unused\")\nobject DrawableUtils {\n\n    fun createTransitionDrawable(\n        @ColorInt startColor: Int,\n        @ColorInt endColor: Int\n    ): TransitionDrawable {\n        return createTransitionDrawable(ColorDrawable(startColor), ColorDrawable(endColor))\n    }\n\n    fun createTransitionDrawable(start: Drawable, end: Drawable): TransitionDrawable {\n        val drawables = arrayOfNulls<Drawable>(2)\n\n        drawables[0] = start\n        drawables[1] = end\n\n        return TransitionDrawable(drawables)\n    }\n\n}\n\nfun Drawable.setTintListMutate(\n    tint: ColorStateList,\n    tintMode: PorterDuff.Mode = PorterDuff.Mode.SRC_ATOP\n) {\n    val wrappedDrawable = DrawableCompat.wrap(this)\n    wrappedDrawable.mutate()\n    DrawableCompat.setTintMode(wrappedDrawable, tintMode)\n    DrawableCompat.setTintList(wrappedDrawable, tint)\n}\n\nfun Drawable.setTintMutate(\n    @ColorInt tint: Int,\n    tintMode: PorterDuff.Mode = PorterDuff.Mode.SRC_ATOP\n) {\n    val wrappedDrawable = DrawableCompat.wrap(this)\n    wrappedDrawable.mutate()\n    DrawableCompat.setTintMode(wrappedDrawable, tintMode)\n    DrawableCompat.setTint(wrappedDrawable, tint)\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/utils/EncoderUtils.kt",
    "content": "package io.legado.app.utils\n\nimport android.util.Base64\n\n/**\n * 编码工具 escape base64\n */\n@Suppress(\"unused\")\nobject EncoderUtils {\n\n    fun escape(src: String): String {\n        val tmp = StringBuilder()\n        for (char in src) {\n            val charCode = char.code\n            if (charCode in 48..57 || charCode in 65..90 || charCode in 97..122) {\n                tmp.append(char)\n                continue\n            }\n\n            val prefix = when {\n                charCode < 16 -> \"%0\"\n                charCode < 256 -> \"%\"\n                else -> \"%u\"\n            }\n            tmp.append(prefix).append(charCode.toString(16))\n        }\n        return tmp.toString()\n    }\n\n    @JvmOverloads\n    fun base64Decode(str: String, flags: Int = Base64.DEFAULT): String {\n        val bytes = Base64.decode(str, flags)\n        return String(bytes)\n    }\n\n    @JvmOverloads\n    fun base64Encode(str: String, flags: Int = Base64.NO_WRAP): String? {\n        return Base64.encodeToString(str.toByteArray(), flags)\n    }\n\n    @JvmOverloads\n    fun base64Encode(bytes: ByteArray, flags: Int = Base64.NO_WRAP): String {\n        return Base64.encodeToString(bytes, flags)\n    }\n    \n    @JvmOverloads\n    fun base64DecodeToByteArray(str: String, flags: Int = Base64.DEFAULT): ByteArray {\n        return Base64.decode(str, flags)\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/utils/EncodingDetect.kt",
    "content": "package io.legado.app.utils\n\nimport android.text.TextUtils\nimport io.legado.app.lib.icu4j.CharsetDetector\nimport org.jsoup.Jsoup\nimport java.io.File\n\n/**\n * 自动获取文件的编码\n * */\n@Suppress(\"MemberVisibilityCanBePrivate\", \"unused\")\nobject EncodingDetect {\n\n    private val headTagRegex = \"(?i)<head>[\\\\s\\\\S]*?</head>\".toRegex()\n    private val headOpenBytes = \"<head>\".toByteArray()\n    private val headCloseBytes = \"</head>\".toByteArray()\n\n    fun getHtmlEncode(bytes: ByteArray): String {\n        try {\n            var head: String? = null\n            val startIndex = bytes.indexOf(headOpenBytes)\n            if (startIndex > -1) {\n                val endIndex = bytes.indexOf(headCloseBytes, startIndex)\n                if (endIndex > -1) {\n                    head = String(bytes.copyOfRange(startIndex, endIndex + headCloseBytes.size))\n                }\n            }\n            val doc = Jsoup.parseBodyFragment(head ?: headTagRegex.find(String(bytes))!!.value)\n            val metaTags = doc.getElementsByTag(\"meta\")\n            var charsetStr: String\n            for (metaTag in metaTags) {\n                charsetStr = metaTag.attr(\"charset\")\n                if (!TextUtils.isEmpty(charsetStr)) {\n                    return charsetStr\n                }\n                val httpEquiv = metaTag.attr(\"http-equiv\")\n                if (httpEquiv.equals(\"content-type\", true)) {\n                    val content = metaTag.attr(\"content\")\n                    val idx = content.indexOf(\"charset=\", ignoreCase = true)\n                    charsetStr = if (idx > -1) {\n                        content.substring(idx + \"charset=\".length)\n                    } else {\n                        content.substringAfter(\";\")\n                    }\n                    if (!TextUtils.isEmpty(charsetStr)) {\n                        return charsetStr\n                    }\n                }\n            }\n        } catch (ignored: Exception) {\n        }\n        return getEncode(bytes)\n    }\n\n    fun getEncode(bytes: ByteArray): String {\n        val match = CharsetDetector().setText(bytes).detect()\n        return match?.name ?: \"UTF-8\"\n    }\n\n    /**\n     * 得到文件的编码\n     */\n    fun getEncode(filePath: String): String {\n        return getEncode(File(filePath))\n    }\n\n    /**\n     * 得到文件的编码\n     */\n    fun getEncode(file: File): String {\n        val tempByte = getFileBytes(file)\n        if (tempByte.isEmpty()) {\n            return \"UTF-8\"\n        }\n        return getEncode(tempByte)\n    }\n\n    private fun getFileBytes(file: File): ByteArray {\n        val byteArray = ByteArray(8000)\n        var pos = 0\n        try {\n            file.inputStream().buffered().use {\n                while (pos < byteArray.size) {\n                    val n = it.read(byteArray, pos, 1)\n                    if (n == -1) {\n                        break\n                    }\n                    if (byteArray[pos] < 0) {\n                        pos++\n                    }\n                }\n            }\n        } catch (e: Exception) {\n            System.err.println(\"Error: $e\")\n        }\n        return byteArray.copyOf(pos)\n    }\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/utils/EventBusExtensions.kt",
    "content": "@file:Suppress(\"unused\")\n\npackage io.legado.app.utils\n\nimport androidx.appcompat.app.AppCompatActivity\nimport androidx.fragment.app.Fragment\nimport androidx.lifecycle.LifecycleService\nimport androidx.lifecycle.Observer\nimport com.jeremyliao.liveeventbus.LiveEventBus\nimport com.jeremyliao.liveeventbus.core.Observable\n\ninline fun <reified EVENT> eventObservable(tag: String): Observable<EVENT> {\n    return LiveEventBus.get(tag, EVENT::class.java)\n}\n\ninline fun <reified EVENT> postEvent(tag: String, event: EVENT) {\n    LiveEventBus.get<EVENT>(tag).post(event)\n}\n\ninline fun <reified EVENT> postEventDelay(tag: String, event: EVENT, delay: Long) {\n    LiveEventBus.get<EVENT>(tag).postDelay(event, delay)\n}\n\ninline fun <reified EVENT> postEventOrderly(tag: String, event: EVENT) {\n    LiveEventBus.get<EVENT>(tag).postOrderly(event)\n}\n\ninline fun <reified EVENT> AppCompatActivity.observeEvent(\n    vararg tags: String,\n    noinline observer: (EVENT) -> Unit\n) {\n    val o = Observer<EVENT> {\n        observer(it)\n    }\n    tags.forEach {\n        eventObservable<EVENT>(it).observe(this, o)\n    }\n}\n\ninline fun <reified EVENT> AppCompatActivity.observeEventSticky(\n    vararg tags: String,\n    noinline observer: (EVENT) -> Unit\n) {\n    val o = Observer<EVENT> {\n        observer(it)\n    }\n    tags.forEach {\n        eventObservable<EVENT>(it).observeSticky(this, o)\n    }\n}\n\ninline fun <reified EVENT> Fragment.observeEvent(\n    vararg tags: String,\n    noinline observer: (EVENT) -> Unit\n) {\n    val o = Observer<EVENT> {\n        observer(it)\n    }\n    tags.forEach {\n        eventObservable<EVENT>(it).observe(this, o)\n    }\n}\n\ninline fun <reified EVENT> Fragment.observeEventSticky(\n    vararg tags: String,\n    noinline observer: (EVENT) -> Unit\n) {\n    val o = Observer<EVENT> {\n        observer(it)\n    }\n    tags.forEach {\n        eventObservable<EVENT>(it).observeSticky(this, o)\n    }\n}\n\ninline fun <reified EVENT> LifecycleService.observeEvent(\n    vararg tags: String,\n    noinline observer: (EVENT) -> Unit\n) {\n    val o = Observer<EVENT> {\n        observer(it)\n    }\n    tags.forEach {\n        eventObservable<EVENT>(it).observe(this, o)\n    }\n}\n\ninline fun <reified EVENT> LifecycleService.observeEventSticky(\n    vararg tags: String,\n    noinline observer: (EVENT) -> Unit\n) {\n    val o = Observer<EVENT> {\n        observer(it)\n    }\n    tags.forEach {\n        eventObservable<EVENT>(it).observeSticky(this, o)\n    }\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/utils/FileDocExtensions.kt",
    "content": "@file:Suppress(\"unused\")\n\npackage io.legado.app.utils\n\nimport android.app.DownloadManager\nimport android.content.Context\nimport android.database.Cursor\nimport android.net.Uri\nimport android.os.ParcelFileDescriptor\nimport android.provider.DocumentsContract\nimport androidx.core.net.toUri\nimport androidx.documentfile.provider.DocumentFile\nimport io.legado.app.exception.NoStackTraceException\nimport splitties.init.appCtx\nimport splitties.systemservices.downloadManager\nimport java.io.File\nimport java.io.InputStream\nimport java.io.OutputStream\nimport java.nio.charset.Charset\nimport java.util.concurrent.atomic.AtomicInteger\n\n\ndata class FileDoc(\n    val name: String,\n    val isDir: Boolean,\n    val size: Long,\n    val lastModified: Long,\n    val uri: Uri\n) {\n\n    override fun toString(): String {\n        return if (uri.isContentScheme()) uri.toString() else uri.path!!\n    }\n\n    val isContentScheme get() = uri.isContentScheme()\n\n    fun readBytes(): ByteArray {\n        return uri.readBytes(appCtx)\n    }\n\n    fun readText(): String {\n        return uri.readText(appCtx)\n    }\n\n    fun asDocumentFile(): DocumentFile? {\n        if (isContentScheme) {\n            return if (isDir) {\n                treeDocumentFileConstructor.newInstance(null, appCtx, uri) as DocumentFile\n            } else {\n                DocumentFile.fromSingleUri(appCtx, uri)\n            }\n        }\n        return null\n    }\n\n    fun asFile(): File? {\n        if (isContentScheme) {\n            return null\n        }\n        return File(uri.path!!)\n    }\n\n    companion object {\n\n        private val treeDocumentFileConstructor by lazy {\n            Class.forName(\"androidx.documentfile.provider.TreeDocumentFile\")\n                .getDeclaredConstructor(\n                    DocumentFile::class.java,\n                    Context::class.java,\n                    Uri::class.java\n                ).apply {\n                    isAccessible = true\n                }\n        }\n\n        fun fromDir(path: String): FileDoc {\n            return fromUri(path.toUri(), true)\n        }\n\n        fun fromFile(path: String): FileDoc {\n            return fromUri(path.toUri(), false)\n        }\n\n        fun fromDir(uri: Uri): FileDoc {\n            return fromUri(uri, true)\n        }\n\n        fun fromUri(uri: Uri, isDir: Boolean): FileDoc {\n            if (uri.isContentScheme()) {\n                val doc = if (isDir) {\n                    DocumentFile.fromTreeUri(appCtx, uri)!!\n                } else if (uri.host == \"downloads\") {\n                    val query = DownloadManager.Query()\n                    query.setFilterById(uri.lastPathSegment!!.toLong())\n                    downloadManager.query(query).use {\n                        if (it.moveToFirst()) {\n                            val lUriColum = it.getColumnIndex(DownloadManager.COLUMN_LOCAL_URI)\n                            val lUri = it.getString(lUriColum)\n                            DocumentFile.fromSingleUri(appCtx, Uri.parse(lUri))!!\n                        } else {\n                            DocumentFile.fromSingleUri(appCtx, uri)!!\n                        }\n                    }\n                } else {\n                    DocumentFile.fromSingleUri(appCtx, uri)!!\n                }\n                return FileDoc(doc.name ?: \"\", isDir, doc.length(), doc.lastModified(), doc.uri)\n            }\n            val file = File(uri.path!!)\n            return FileDoc(file.name, isDir, file.length(), file.lastModified(), uri)\n        }\n\n        fun fromDocumentFile(doc: DocumentFile): FileDoc {\n            return FileDoc(\n                name = doc.name ?: \"\",\n                isDir = doc.isDirectory,\n                size = doc.length(),\n                lastModified = doc.lastModified(),\n                uri = doc.uri\n            )\n        }\n\n        fun fromFile(file: File): FileDoc {\n            return FileDoc(\n                name = file.name,\n                isDir = file.isDirectory,\n                size = file.length(),\n                lastModified = file.lastModified(),\n                uri = Uri.fromFile(file)\n            )\n        }\n\n    }\n}\n\n/**\n * 过滤器\n */\ntypealias FileDocFilter = (file: FileDoc) -> Boolean\n\nprivate val projection by lazy {\n    arrayOf(\n        DocumentsContract.Document.COLUMN_DOCUMENT_ID,\n        DocumentsContract.Document.COLUMN_DISPLAY_NAME,\n        DocumentsContract.Document.COLUMN_LAST_MODIFIED,\n        DocumentsContract.Document.COLUMN_SIZE,\n        DocumentsContract.Document.COLUMN_MIME_TYPE\n    )\n}\n\n/**\n * 返回子文件列表,如果不是文件夹则返回null\n */\nfun FileDoc.list(filter: FileDocFilter? = null): ArrayList<FileDoc>? {\n    if (isDir) {\n        if (uri.isContentScheme()) {\n            /**\n             * DocumentFile 的 listFiles() 非常的慢,所以这里直接从数据库查询\n             */\n            val childrenUri = DocumentsContract\n                .buildChildDocumentsUriUsingTree(uri, DocumentsContract.getDocumentId(uri))\n            val docList = arrayListOf<FileDoc>()\n            var cursor: Cursor? = null\n            try {\n                cursor = appCtx.contentResolver.query(\n                    childrenUri,\n                    projection,\n                    null,\n                    null,\n                    DocumentsContract.Document.COLUMN_DISPLAY_NAME\n                )\n                cursor?.let {\n                    val ici = cursor.getColumnIndex(DocumentsContract.Document.COLUMN_DOCUMENT_ID)\n                    val nci = cursor.getColumnIndex(DocumentsContract.Document.COLUMN_DISPLAY_NAME)\n                    val sci = cursor.getColumnIndex(DocumentsContract.Document.COLUMN_SIZE)\n                    val mci = cursor.getColumnIndex(DocumentsContract.Document.COLUMN_MIME_TYPE)\n                    val dci = cursor.getColumnIndex(DocumentsContract.Document.COLUMN_LAST_MODIFIED)\n                    if (cursor.moveToFirst()) {\n                        do {\n                            val item = FileDoc(\n                                name = cursor.getString(nci),\n                                isDir = cursor.getString(mci) ==\n                                        DocumentsContract.Document.MIME_TYPE_DIR,\n                                size = cursor.getLong(sci),\n                                lastModified = cursor.getLong(dci),\n                                uri = DocumentsContract.buildDocumentUriUsingTree(\n                                    uri,\n                                    cursor.getString(ici)\n                                )\n                            )\n                            if (filter == null || filter.invoke(item)) {\n                                docList.add(item)\n                            }\n                        } while (cursor.moveToNext())\n                    }\n                }\n            } finally {\n                cursor?.close()\n            }\n            return docList\n        } else {\n            return File(uri.path!!).listFileDocs(filter)\n        }\n    }\n    return null\n}\n\n/**\n * 查找文档, 如果存在则返回文档,如果不存在返回空\n * @param name 文件名\n * @param depth 查找文件夹深度\n */\nfun FileDoc.find(name: String, depth: Int = 0): FileDoc? {\n    val list = list()\n    list?.forEach {\n        if (it.name == name) {\n            return it\n        }\n    }\n    if (depth > 0) {\n        list?.forEach {\n            if (it.isDir) {\n                val fileDoc = it.find(name, depth - 1)\n                if (fileDoc != null) {\n                    return fileDoc\n                }\n            }\n        }\n    }\n    return null\n}\n\n/**\n * 查找文档, 如果存在则返回文档,如果不存在返回空\n * @param name 文件名\n * @param depth 查找文件夹深度\n * @param maxFinds 最大查找文件夹数量\n */\nfun FileDoc.find(name: String, depth: Int = 0, maxFinds: Int = Int.MAX_VALUE): FileDoc? {\n    return find(name, depth, AtomicInteger(maxFinds))\n}\n\nprivate fun FileDoc.find(name: String, depth: Int, maxFinds: AtomicInteger): FileDoc? {\n    if (maxFinds.getAndDecrement() <= 0) {\n        return null\n    }\n    val list = list()\n    list?.forEach {\n        if (it.name == name) {\n            return it\n        }\n    }\n    if (depth > 0) {\n        list?.forEach {\n            if (it.isDir) {\n                val fileDoc = it.find(name, depth - 1, maxFinds)\n                if (fileDoc != null) {\n                    return fileDoc\n                }\n            }\n        }\n    }\n    return null\n}\n\nfun FileDoc.createFileIfNotExist(\n    fileName: String,\n    vararg subDirs: String\n): FileDoc {\n    return if (uri.isContentScheme()) {\n        val documentFile = asDocumentFile()!!\n        val tmp = DocumentUtils.createFileIfNotExist(documentFile, fileName, *subDirs)!!\n        FileDoc.fromDocumentFile(tmp)\n    } else {\n        val path = FileUtils.getPath(uri.path!!, *subDirs) + File.separator + fileName\n        val tmp = FileUtils.createFileIfNotExist(path)\n        FileDoc.fromFile(tmp)\n    }\n}\n\nfun FileDoc.createFolderIfNotExist(\n    vararg subDirs: String\n): FileDoc {\n    return if (uri.isContentScheme()) {\n        val documentFile = asDocumentFile()!!\n        val tmp = DocumentUtils.createFolderIfNotExist(documentFile, *subDirs)!!\n        FileDoc.fromDocumentFile(tmp)\n    } else {\n        val path = FileUtils.getPath(uri.path!!, *subDirs)\n        val tmp = FileUtils.createFolderIfNotExist(path)\n        FileDoc.fromFile(tmp)\n    }\n}\n\nfun FileDoc.openInputStream(): Result<InputStream> {\n    return uri.inputStream(appCtx)\n}\n\nfun FileDoc.openOutputStream(): Result<OutputStream> {\n    return uri.outputStream(appCtx)\n}\n\nfun FileDoc.openReadPfd(): Result<ParcelFileDescriptor> {\n    return uri.toReadPfd(appCtx)\n}\n\nfun FileDoc.openWritePfd(): Result<ParcelFileDescriptor> {\n    return uri.toWritePfd(appCtx)\n}\n\nfun FileDoc.exists(\n    fileName: String,\n    vararg subDirs: String\n): Boolean {\n    return if (uri.isContentScheme()) {\n        DocumentUtils.exists(asDocumentFile()!!, fileName, *subDirs)\n    } else {\n        val path = FileUtils.getPath(uri.path!!, *subDirs) + File.separator + fileName\n        FileUtils.exist(path)\n    }\n}\n\nfun FileDoc.exists(): Boolean {\n    return if (uri.isContentScheme()) {\n        asDocumentFile()!!.exists()\n    } else {\n        FileUtils.exist(uri.path!!)\n    }\n}\n\nfun FileDoc.writeText(text: String) {\n    if (uri.isContentScheme()) {\n        uri.writeText(appCtx, text)\n    } else {\n        File(uri.path!!).writeText(text)\n    }\n}\n\nfun FileDoc.writeFile(file: File) {\n    openOutputStream().getOrThrow().use { out ->\n        file.inputStream().use {\n            it.copyTo(out)\n        }\n    }\n}\n\nfun FileDoc.delete() {\n    asFile()?.let {\n        FileUtils.delete(it, true)\n    }\n    asDocumentFile()?.delete()\n}\n\nfun FileDoc.checkWrite(): Boolean {\n    if (!isDir) {\n        throw NoStackTraceException(\"只能检查目录\")\n    }\n    asFile()?.let {\n        return it.checkWrite()\n    }\n    return asDocumentFile()!!.checkWrite()\n}\n\n/**\n * DocumentFile 的 listFiles() 非常的慢,尽量不要使用\n */\nfun DocumentFile.listFileDocs(filter: FileDocFilter? = null): ArrayList<FileDoc>? {\n    return FileDoc.fromDocumentFile(this).list(filter)\n}\n\n@Throws(Exception::class)\nfun DocumentFile.openInputStream(): InputStream? {\n    return appCtx.contentResolver.openInputStream(uri)\n}\n\n@Throws(Exception::class)\nfun DocumentFile.openOutputStream(): OutputStream? {\n    return appCtx.contentResolver.openOutputStream(uri)\n}\n\n@Throws(Exception::class)\nfun DocumentFile.writeText(context: Context, data: String, charset: Charset = Charsets.UTF_8) {\n    uri.writeText(context, data, charset)\n}\n\n@Throws(Exception::class)\nfun DocumentFile.writeBytes(context: Context, data: ByteArray) {\n    uri.writeBytes(context, data)\n}\n\n@Throws(Exception::class)\nfun DocumentFile.readText(context: Context): String {\n    return String(readBytes(context))\n}\n\n@Throws(Exception::class)\nfun DocumentFile.readBytes(context: Context): ByteArray {\n    return context.contentResolver.openInputStream(uri)?.let {\n        val len: Int = it.available()\n        val buffer = ByteArray(len)\n        it.read(buffer)\n        it.close()\n        return buffer\n    } ?: throw NoStackTraceException(\"打开文件失败\\n${uri}\")\n}\n\nfun DocumentFile.checkWrite(): Boolean {\n    var file: DocumentFile? = null\n    return try {\n        val filename = System.currentTimeMillis().toString()\n        file = createFile(FileUtils.getMimeType(filename), filename)\n        file?.openOutputStream()?.let { out ->\n            out.bufferedWriter().use { it.write(filename) }\n            file.openInputStream()?.let { input ->\n                input.bufferedReader().use {\n                    return it.readText() == filename\n                }\n            }\n        }\n        false\n    } catch (e: Exception) {\n        false\n    } finally {\n        file?.delete()\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/utils/FileExtensions.kt",
    "content": "@file:Suppress(\"unused\")\n\npackage io.legado.app.utils\n\nimport android.net.Uri\nimport java.io.File\nimport java.io.FileOutputStream\n\nfun File.getFile(vararg subDirFiles: String): File {\n    val path = FileUtils.getPath(this, *subDirFiles)\n    return File(path)\n}\n\nfun File.exists(vararg subDirFiles: String): Boolean {\n    return getFile(*subDirFiles).exists()\n}\n\n@Throws(Exception::class)\nfun File.listFileDocs(filter: FileDocFilter? = null): ArrayList<FileDoc> {\n    val docList = arrayListOf<FileDoc>()\n    listFiles()?.forEach {\n        val item = FileDoc(\n            it.name,\n            it.isDirectory,\n            it.length(),\n            it.lastModified(),\n            Uri.fromFile(it)\n        )\n        if (filter == null || filter.invoke(item)) {\n            docList.add(item)\n        }\n    }\n    return docList\n}\n\nfun File.createFileIfNotExist(): File {\n    if (!exists()) {\n        parentFile?.createFolderIfNotExist()\n        createNewFile()\n    }\n    return this\n}\n\nfun File.createFileReplace(): File {\n    if (!exists()) {\n        parent?.let {\n            File(it).mkdirs()\n        }\n        createNewFile()\n    } else {\n        delete()\n        createNewFile()\n    }\n    return this\n}\n\nfun File.createFolderIfNotExist(): File {\n    if (!exists()) {\n        mkdirs()\n    }\n    return this\n}\n\nfun File.createFolderReplace(): File {\n    if (exists()) {\n        FileUtils.delete(this, true)\n    }\n    mkdirs()\n    return this\n}\n\nfun File.checkWrite(): Boolean {\n    var file: File? = null\n    return try {\n        val filename = System.currentTimeMillis().toString()\n        file = FileUtils.createFileIfNotExist(this, filename)\n        file.outputStream().bufferedWriter().use { it.write(filename) }\n        file.inputStream().bufferedReader().use { it.readText() == filename }\n    } catch (e: Exception) {\n        false\n    } finally {\n        file?.delete()\n    }\n}\n\nfun File.outputStream(append: Boolean = false): FileOutputStream {\n    return FileOutputStream(this, append)\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/utils/FileUtils.kt",
    "content": "package io.legado.app.utils\n\nimport android.os.Environment\nimport android.webkit.MimeTypeMap\nimport androidx.annotation.IntDef\nimport splitties.init.appCtx\nimport java.io.*\nimport java.nio.charset.Charset\nimport java.text.SimpleDateFormat\nimport java.util.*\nimport java.util.regex.Pattern\n\n@Suppress(\"unused\", \"MemberVisibilityCanBePrivate\")\nobject FileUtils {\n\n    fun createFileIfNotExist(root: File, vararg subDirFiles: String): File {\n        val filePath = getPath(root, *subDirFiles)\n        return createFileIfNotExist(filePath)\n    }\n\n    fun createFolderIfNotExist(root: File, vararg subDirs: String): File {\n        val filePath = getPath(root, *subDirs)\n        return createFolderIfNotExist(filePath)\n    }\n\n    fun createFolderIfNotExist(filePath: String): File {\n        val file = File(filePath)\n        //如果文件夹不存在，就创建它\n        if (!file.exists()) {\n            file.mkdirs()\n        }\n        return file\n    }\n\n    @Synchronized\n    fun createFileIfNotExist(filePath: String): File {\n        val file = File(filePath)\n        try {\n            if (!file.exists()) {\n                //创建父类文件夹\n                file.parent?.let {\n                    createFolderIfNotExist(it)\n                }\n                //创建文件\n                file.createNewFile()\n            }\n        } catch (e: IOException) {\n            e.printOnDebug()\n        }\n        return file\n    }\n\n    fun createFileWithReplace(filePath: String): File {\n        val file = File(filePath)\n        if (!file.exists()) {\n            //创建父类文件夹\n            file.parent?.let {\n                createFolderIfNotExist(it)\n            }\n            //创建文件\n            file.createNewFile()\n        } else {\n            file.delete()\n            file.createNewFile()\n        }\n        return file\n    }\n\n    fun getPath(rootPath: String, vararg subDirFiles: String): String {\n        val path = StringBuilder(rootPath)\n        subDirFiles.forEach {\n            if (it.isNotEmpty()) {\n                if (!path.endsWith(File.separator)) {\n                    path.append(File.separator)\n                }\n                path.append(it)\n            }\n        }\n        return path.toString()\n    }\n\n    fun getPath(root: File, vararg subDirFiles: String): String {\n        val path = StringBuilder(root.absolutePath)\n        subDirFiles.forEach {\n            if (it.isNotEmpty()) {\n                path.append(File.separator).append(it)\n            }\n        }\n        return path.toString()\n    }\n\n    fun getCachePath(): String {\n        return appCtx.externalCache.absolutePath\n    }\n\n    fun getSdCardPath(): String {\n        var sdCardDirectory = Environment.getExternalStorageDirectory().absolutePath\n        try {\n            sdCardDirectory = File(sdCardDirectory).canonicalPath\n        } catch (e: IOException) {\n            e.printOnDebug()\n        }\n        return sdCardDirectory\n    }\n\n    const val BY_NAME_ASC = 0\n    const val BY_NAME_DESC = 1\n    const val BY_TIME_ASC = 2\n    const val BY_TIME_DESC = 3\n    const val BY_SIZE_ASC = 4\n    const val BY_SIZE_DESC = 5\n    const val BY_EXTENSION_ASC = 6\n    const val BY_EXTENSION_DESC = 7\n\n    @IntDef(value = [BY_NAME_ASC, BY_NAME_DESC, BY_TIME_ASC, BY_TIME_DESC, BY_SIZE_ASC, BY_SIZE_DESC, BY_EXTENSION_ASC, BY_EXTENSION_DESC])\n    @Retention(AnnotationRetention.SOURCE)\n    annotation class SortType\n\n    /**\n     * 将目录分隔符统一为平台默认的分隔符，并为目录结尾添加分隔符\n     */\n    fun separator(path: String): String {\n        var path1 = path\n        val separator = File.separator\n        path1 = path1.replace(\"\\\\\", separator)\n        if (!path1.endsWith(separator)) {\n            path1 += separator\n        }\n        return path1\n    }\n\n    fun closeSilently(c: Closeable?) {\n        if (c == null) {\n            return\n        }\n        try {\n            c.close()\n        } catch (ignored: IOException) {\n        }\n\n    }\n\n    /**\n     * 列出指定目录下的所有子目录\n     */\n    @JvmOverloads\n    fun listDirs(\n        startDirPath: String,\n        excludeDirs: Array<String>? = null, @SortType sortType: Int = BY_NAME_ASC\n    ): Array<File> {\n        var excludeDirs1 = excludeDirs\n        val dirList = ArrayList<File>()\n        val startDir = File(startDirPath)\n        if (!startDir.isDirectory) {\n            return arrayOf()\n        }\n        val dirs = startDir.listFiles(FileFilter { f ->\n            if (f == null) {\n                return@FileFilter false\n            }\n            f.isDirectory\n        }) ?: return arrayOf()\n        if (excludeDirs1 == null) {\n            excludeDirs1 = arrayOf()\n        }\n        for (dir in dirs) {\n            val file = dir.absoluteFile\n            if (!excludeDirs1.contentDeepToString().contains(file.name)) {\n                dirList.add(file)\n            }\n        }\n        when (sortType) {\n            BY_NAME_ASC -> Collections.sort(dirList, SortByName())\n            BY_NAME_DESC -> {\n                Collections.sort(dirList, SortByName())\n                dirList.reverse()\n            }\n            BY_TIME_ASC -> Collections.sort(dirList, SortByTime())\n            BY_TIME_DESC -> {\n                Collections.sort(dirList, SortByTime())\n                dirList.reverse()\n            }\n            BY_SIZE_ASC -> Collections.sort(dirList, SortBySize())\n            BY_SIZE_DESC -> {\n                Collections.sort(dirList, SortBySize())\n                dirList.reverse()\n            }\n            BY_EXTENSION_ASC -> Collections.sort(dirList, SortByExtension())\n            BY_EXTENSION_DESC -> {\n                Collections.sort(dirList, SortByExtension())\n                dirList.reverse()\n            }\n        }\n        return dirList.toTypedArray()\n    }\n\n    /**\n     * 列出指定目录下的所有子目录及所有文件\n     */\n    @JvmOverloads\n    fun listDirsAndFiles(\n        startDirPath: String,\n        allowExtensions: Array<String>? = null\n    ): Array<File>? {\n        val dirs: Array<File>?\n        val files: Array<File>? = if (allowExtensions == null) {\n            listFiles(startDirPath)\n        } else {\n            listFiles(startDirPath, allowExtensions)\n        }\n        dirs = listDirs(startDirPath)\n        if (files == null) {\n            return null\n        }\n        return dirs + files\n    }\n\n    /**\n     * 列出指定目录下的所有文件\n     */\n    @JvmOverloads\n    fun listFiles(\n        startDirPath: String,\n        filterPattern: Pattern? = null, @SortType sortType: Int = BY_NAME_ASC\n    ): Array<File> {\n        val fileList = ArrayList<File>()\n        val f = File(startDirPath)\n        if (!f.isDirectory) {\n            return arrayOf()\n        }\n        val files = f.listFiles(FileFilter { file ->\n            if (file == null) {\n                return@FileFilter false\n            }\n            if (file.isDirectory) {\n                return@FileFilter false\n            }\n\n            filterPattern?.matcher(file.name)?.find() ?: true\n        })\n            ?: return arrayOf()\n        for (file in files) {\n            fileList.add(file.absoluteFile)\n        }\n        when (sortType) {\n            BY_NAME_ASC -> Collections.sort(fileList, SortByName())\n            BY_NAME_DESC -> {\n                Collections.sort(fileList, SortByName())\n                fileList.reverse()\n            }\n            BY_TIME_ASC -> Collections.sort(fileList, SortByTime())\n            BY_TIME_DESC -> {\n                Collections.sort(fileList, SortByTime())\n                fileList.reverse()\n            }\n            BY_SIZE_ASC -> Collections.sort(fileList, SortBySize())\n            BY_SIZE_DESC -> {\n                Collections.sort(fileList, SortBySize())\n                fileList.reverse()\n            }\n            BY_EXTENSION_ASC -> Collections.sort(fileList, SortByExtension())\n            BY_EXTENSION_DESC -> {\n                Collections.sort(fileList, SortByExtension())\n                fileList.reverse()\n            }\n        }\n        return fileList.toTypedArray()\n    }\n\n    /**\n     * 列出指定目录下的所有文件\n     */\n    fun listFiles(startDirPath: String, allowExtensions: Array<String>?): Array<File>? {\n        val file = File(startDirPath)\n        return file.listFiles { _, name ->\n            //返回当前目录所有以某些扩展名结尾的文件\n            val extension = getExtension(name)\n            allowExtensions?.contentDeepToString()?.contains(extension) == true\n                    || allowExtensions == null\n        }\n    }\n\n    /**\n     * 列出指定目录下的所有文件\n     */\n    fun listFiles(startDirPath: String, allowExtension: String?): Array<File>? {\n        return if (allowExtension == null)\n            listFiles(startDirPath, allowExtension = null)\n        else\n            listFiles(startDirPath, arrayOf(allowExtension))\n    }\n\n    /**\n     * 判断文件或目录是否存在\n     */\n    fun exist(path: String): Boolean {\n        val file = File(path)\n        return file.exists()\n    }\n\n    /**\n     * 删除文件或目录\n     */\n    @JvmOverloads\n    fun delete(file: File, deleteRootDir: Boolean = false): Boolean {\n        var result = false\n        if (file.isFile) {\n            //是文件\n            result = deleteResolveEBUSY(file)\n        } else {\n            //是目录\n            val files = file.listFiles() ?: return false\n            if (files.isEmpty()) {\n                result = deleteRootDir && deleteResolveEBUSY(file)\n            } else {\n                for (f in files) {\n                    delete(f, deleteRootDir)\n                    result = deleteResolveEBUSY(f)\n                }\n            }\n            if (deleteRootDir) {\n                result = deleteResolveEBUSY(file)\n            }\n        }\n        return result\n    }\n\n    /**\n     * bug: open failed: EBUSY (Device or resource busy)\n     * fix: http://stackoverflow.com/questions/11539657/open-failed-ebusy-device-or-resource-busy\n     */\n    private fun deleteResolveEBUSY(file: File): Boolean {\n        // Before you delete a Directory or File: rename it!\n        val to = File(file.absolutePath + System.currentTimeMillis())\n\n        file.renameTo(to)\n        return to.delete()\n    }\n\n    /**\n     * 删除文件或目录\n     */\n    @JvmOverloads\n    fun delete(path: String, deleteRootDir: Boolean = true): Boolean {\n        val file = File(path)\n\n        return if (file.exists()) {\n            delete(file, deleteRootDir)\n        } else false\n    }\n\n    /**\n     * 复制文件为另一个文件，或复制某目录下的所有文件及目录到另一个目录下\n     */\n    fun copy(src: String, tar: String): Boolean {\n        val srcFile = File(src)\n        return srcFile.exists() && copy(srcFile, File(tar))\n    }\n\n    /**\n     * 复制文件或目录\n     */\n    fun copy(src: File, tar: File): Boolean {\n        try {\n            if (src.isFile) {\n                val inputStream = FileInputStream(src)\n                val outputStream = FileOutputStream(tar)\n                inputStream.use {\n                    outputStream.use {\n                        inputStream.copyTo(outputStream)\n                        outputStream.flush()\n                    }\n                }\n            } else if (src.isDirectory) {\n                tar.mkdirs()\n                src.listFiles()?.forEach { file ->\n                    copy(file.absoluteFile, File(tar.absoluteFile, file.name))\n                }\n            }\n            return true\n        } catch (e: Exception) {\n            return false\n        }\n\n    }\n\n    /**\n     * 移动文件或目录\n     */\n    fun move(src: String, tar: String): Boolean {\n        return move(File(src), File(tar))\n    }\n\n    /**\n     * 移动文件或目录\n     */\n    fun move(src: File, tar: File): Boolean {\n        return rename(src, tar)\n    }\n\n    /**\n     * 文件重命名\n     */\n    fun rename(oldPath: String, newPath: String): Boolean {\n        return rename(File(oldPath), File(newPath))\n    }\n\n    /**\n     * 文件重命名\n     */\n    fun rename(src: File, tar: File): Boolean {\n        return src.renameTo(tar)\n    }\n\n    /**\n     * 读取文本文件, 失败将返回空串\n     */\n    @JvmOverloads\n    fun readText(filepath: String, charset: String = \"utf-8\"): String {\n        try {\n            val data = readBytes(filepath)\n            if (data != null) {\n                return String(data, Charset.forName(charset)).trim { it <= ' ' }\n            }\n        } catch (ignored: UnsupportedEncodingException) {\n        }\n\n        return \"\"\n    }\n\n    /**\n     * 读取文件内容, 失败将返回空串\n     */\n    fun readBytes(filepath: String): ByteArray? {\n        var fis: FileInputStream? = null\n        try {\n            fis = FileInputStream(filepath)\n            val outputStream = ByteArrayOutputStream()\n            val buffer = ByteArray(1024)\n            while (true) {\n                val len = fis.read(buffer, 0, buffer.size)\n                if (len == -1) {\n                    break\n                } else {\n                    outputStream.write(buffer, 0, len)\n                }\n            }\n            val data = outputStream.toByteArray()\n            outputStream.close()\n            return data\n        } catch (e: IOException) {\n            return null\n        } finally {\n            closeSilently(fis)\n        }\n    }\n\n    /**\n     * 保存文本内容\n     */\n    @JvmOverloads\n    fun writeText(filepath: String, content: String, charset: String = \"utf-8\"): Boolean {\n        return try {\n            writeBytes(filepath, content.toByteArray(charset(charset)))\n        } catch (e: UnsupportedEncodingException) {\n            false\n        }\n\n    }\n\n    /**\n     * 保存文件内容\n     */\n    fun writeBytes(filepath: String, data: ByteArray): Boolean {\n        val file = File(filepath)\n        var fos: FileOutputStream? = null\n        return try {\n            if (!file.exists()) {\n                file.parentFile?.mkdirs()\n                file.createNewFile()\n            }\n            fos = FileOutputStream(filepath)\n            fos.write(data)\n            true\n        } catch (e: IOException) {\n            false\n        } finally {\n            closeSilently(fos)\n        }\n    }\n\n    /**\n     * 保存文件内容\n     */\n    fun writeInputStream(filepath: String, data: InputStream): Boolean {\n        val file = File(filepath)\n        return writeInputStream(file, data)\n    }\n\n    /**\n     * 保存文件内容\n     */\n    fun writeInputStream(file: File, data: InputStream): Boolean {\n        return try {\n            if (!file.exists()) {\n                file.parentFile?.mkdirs()\n                file.createNewFile()\n            }\n            data.use {\n                FileOutputStream(file).use { fos ->\n                    data.copyTo(fos)\n                    fos.flush()\n                }\n            }\n            true\n        } catch (e: IOException) {\n            false\n        }\n    }\n\n    /**\n     * 追加文本内容\n     */\n    fun appendText(path: String, content: String): Boolean {\n        val file = File(path)\n        var writer: FileWriter? = null\n        return try {\n            if (!file.exists()) {\n                file.createNewFile()\n            }\n            writer = FileWriter(file, true)\n            writer.write(content)\n            true\n        } catch (e: IOException) {\n            false\n        } finally {\n            closeSilently(writer)\n        }\n    }\n\n    /**\n     * 获取文件大小\n     */\n    fun getLength(path: String): Long {\n        val file = File(path)\n        return if (!file.isFile || !file.exists()) {\n            0\n        } else file.length()\n    }\n\n    /**\n     * 获取文件或网址的名称（包括后缀）\n     */\n    fun getName(path: String?): String {\n        if (path == null) {\n            return \"\"\n        }\n        val pos = path.lastIndexOf(File.separator)\n        return if (0 <= pos) {\n            path.substring(pos + 1)\n        } else {\n            path\n        }\n    }\n\n    /**\n     * 获取文件名（不包括扩展名）\n     */\n    fun getNameExcludeExtension(path: String): String {\n        return try {\n            var fileName = File(path).name\n            val lastIndexOf = fileName.lastIndexOf(\".\")\n            if (lastIndexOf != -1) {\n                fileName = fileName.substring(0, lastIndexOf)\n            }\n            fileName\n        } catch (e: Exception) {\n            \"\"\n        }\n\n    }\n\n    /**\n     * 获取格式化后的文件大小\n     */\n    fun getSize(path: String): String {\n        val fileSize = getLength(path)\n        return ConvertUtils.formatFileSize(fileSize)\n    }\n\n    /**\n     * 获取文件后缀,不包括“.”\n     */\n    fun getExtension(pathOrUrl: String): String {\n        val dotPos = pathOrUrl.lastIndexOf('.')\n        return if (0 <= dotPos) {\n            pathOrUrl.substring(dotPos + 1)\n        } else {\n            \"ext\"\n        }\n    }\n\n    /**\n     * 获取文件的MIME类型\n     */\n    fun getMimeType(pathOrUrl: String): String {\n        val ext = getExtension(pathOrUrl)\n        val map = MimeTypeMap.getSingleton()\n        return map.getMimeTypeFromExtension(ext) ?: \"*/*\"\n    }\n\n    /**\n     * 获取格式化后的文件/目录创建或最后修改时间\n     */\n    @JvmOverloads\n    fun getDateTime(path: String, format: String = \"yyyy年MM月dd日HH:mm\"): String {\n        val file = File(path)\n        return getDateTime(file, format)\n    }\n\n    /**\n     * 获取格式化后的文件/目录创建或最后修改时间\n     */\n    fun getDateTime(file: File, format: String): String {\n        val cal = Calendar.getInstance()\n        cal.timeInMillis = file.lastModified()\n        return SimpleDateFormat(format, Locale.PRC).format(cal.time)\n    }\n\n    /**\n     * 比较两个文件的最后修改时间\n     */\n    fun compareLastModified(path1: String, path2: String): Int {\n        val stamp1 = File(path1).lastModified()\n        val stamp2 = File(path2).lastModified()\n        return when {\n            stamp1 > stamp2 -> 1\n            stamp1 < stamp2 -> -1\n            else -> 0\n        }\n    }\n\n    /**\n     * 创建多级别的目录\n     */\n    fun makeDirs(path: String): Boolean {\n        return makeDirs(File(path))\n    }\n\n    /**\n     * 创建多级别的目录\n     */\n    fun makeDirs(file: File): Boolean {\n        return file.mkdirs()\n    }\n\n    class SortByExtension : Comparator<File> {\n\n        override fun compare(f1: File?, f2: File?): Int {\n            return if (f1 == null || f2 == null) {\n                if (f1 == null) -1 else 1\n            } else {\n                if (f1.isDirectory && f2.isFile) {\n                    -1\n                } else if (f1.isFile && f2.isDirectory) {\n                    1\n                } else {\n                    f1.name.compareTo(f2.name, ignoreCase = true)\n                }\n            }\n        }\n\n    }\n\n    class SortByName : Comparator<File> {\n        private var caseSensitive: Boolean = false\n\n        constructor(caseSensitive: Boolean) {\n            this.caseSensitive = caseSensitive\n        }\n\n        constructor() {\n            this.caseSensitive = false\n        }\n\n        override fun compare(f1: File?, f2: File?): Int {\n            if (f1 == null || f2 == null) {\n                return if (f1 == null) {\n                    -1\n                } else {\n                    1\n                }\n            } else {\n                return if (f1.isDirectory && f2.isFile) {\n                    -1\n                } else if (f1.isFile && f2.isDirectory) {\n                    1\n                } else {\n                    val s1 = f1.name\n                    val s2 = f2.name\n                    if (caseSensitive) {\n                        s1.cnCompare(s2)\n                    } else {\n                        s1.compareTo(s2, ignoreCase = true)\n                    }\n                }\n            }\n        }\n\n    }\n\n    class SortBySize : Comparator<File> {\n\n        override fun compare(f1: File?, f2: File?): Int {\n            return if (f1 == null || f2 == null) {\n                if (f1 == null) {\n                    -1\n                } else {\n                    1\n                }\n            } else {\n                if (f1.isDirectory && f2.isFile) {\n                    -1\n                } else if (f1.isFile && f2.isDirectory) {\n                    1\n                } else {\n                    if (f1.length() < f2.length()) {\n                        -1\n                    } else {\n                        1\n                    }\n                }\n            }\n        }\n\n    }\n\n    class SortByTime : Comparator<File> {\n\n        override fun compare(f1: File?, f2: File?): Int {\n            return if (f1 == null || f2 == null) {\n                if (f1 == null) {\n                    -1\n                } else {\n                    1\n                }\n            } else {\n                if (f1.isDirectory && f2.isFile) {\n                    -1\n                } else if (f1.isFile && f2.isDirectory) {\n                    1\n                } else {\n                    if (f1.lastModified() > f2.lastModified()) {\n                        -1\n                    } else {\n                        1\n                    }\n                }\n            }\n        }\n\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/utils/FlowExtensions.kt",
    "content": "package io.legado.app.utils\n\nimport androidx.lifecycle.Lifecycle\nimport androidx.lifecycle.repeatOnLifecycle\nimport io.legado.app.data.appDb\nimport kotlinx.coroutines.ExperimentalCoroutinesApi\nimport kotlinx.coroutines.async\nimport kotlinx.coroutines.currentCoroutineContext\nimport kotlinx.coroutines.ensureActive\nimport kotlinx.coroutines.flow.Flow\nimport kotlinx.coroutines.flow.FlowCollector\nimport kotlinx.coroutines.flow.buffer\nimport kotlinx.coroutines.flow.callbackFlow\nimport kotlinx.coroutines.flow.channelFlow\nimport kotlinx.coroutines.flow.conflate\nimport kotlinx.coroutines.flow.filterNotNull\nimport kotlinx.coroutines.flow.firstOrNull\nimport kotlinx.coroutines.flow.flatMapMerge\nimport kotlinx.coroutines.flow.flow\nimport kotlinx.coroutines.flow.map\nimport kotlinx.coroutines.flow.onEach\nimport kotlinx.coroutines.flow.produceIn\nimport kotlinx.coroutines.sync.Semaphore\n\n@OptIn(ExperimentalCoroutinesApi::class)\ninline fun <T> Flow<T>.onEachParallel(\n    concurrency: Int,\n    crossinline action: suspend (T) -> Unit\n): Flow<T> = flatMapMerge(concurrency) { value ->\n    flow {\n        action(value)\n        emit(value)\n    }\n}.buffer(0)\n\n@OptIn(ExperimentalCoroutinesApi::class)\ninline fun <T> Flow<T>.onEachParallelSafe(\n    concurrency: Int,\n    crossinline action: suspend (T) -> Unit\n): Flow<T> = flatMapMerge(concurrency) { value ->\n    flow {\n        try {\n            action(value)\n        } catch (e: Throwable) {\n            currentCoroutineContext().ensureActive()\n        }\n        emit(value)\n    }\n}.buffer(0)\n\n@OptIn(ExperimentalCoroutinesApi::class)\ninline fun <T, R> Flow<T>.mapParallel(\n    concurrency: Int,\n    crossinline transform: suspend (T) -> R,\n): Flow<R> = flatMapMerge(concurrency) { value -> flow { emit(transform(value)) } }.buffer(0)\n\n\n@OptIn(ExperimentalCoroutinesApi::class)\ninline fun <T, R> Flow<T>.mapParallelSafe(\n    concurrency: Int,\n    crossinline transform: suspend (T) -> R,\n): Flow<R> = flatMapMerge(concurrency) { value ->\n    flow {\n        try {\n            emit(transform(value))\n        } catch (_: Throwable) {\n            currentCoroutineContext().ensureActive()\n        }\n    }\n}.buffer(0)\n\n@OptIn(ExperimentalCoroutinesApi::class)\ninline fun <T, R> Flow<T>.transformParallelSafe(\n    concurrency: Int,\n    crossinline transform: suspend FlowCollector<R>.(T) -> R,\n): Flow<R> = flatMapMerge(concurrency) { value ->\n    flow {\n        try {\n            transform(value)\n        } catch (e: Throwable) {\n            currentCoroutineContext().ensureActive()\n        }\n    }\n}.buffer(0)\n\ninline fun <T, R> Flow<T>.mapNotNullParallel(\n    concurrency: Int,\n    crossinline transform: suspend (T) -> R?,\n): Flow<R> = mapParallel(concurrency, transform).filterNotNull()\n\ninline fun <T> Flow<T>.onEachIndexed(\n    crossinline action: suspend (index: Int, T) -> Unit,\n): Flow<T> = flow {\n    var index = 0\n    collect { value ->\n        action(index++, value)\n        emit(value)\n    }\n}\n\ninline fun <T, R> Flow<T>.mapIndexed(\n    crossinline action: suspend (index: Int, T) -> R,\n): Flow<R> = flow {\n    var index = 0\n    collect { value ->\n        emit(action(index++, value))\n    }\n}\n\ninline fun <T, R> Flow<T>.mapAsync(\n    concurrency: Int,\n    crossinline transform: suspend (T) -> R\n): Flow<R> = if (concurrency == 1) {\n    map { transform(it) }\n} else {\n    Semaphore(concurrency).let { semaphore ->\n        channelFlow {\n            collect {\n                semaphore.acquire()\n                send(async { transform(it) })\n            }\n        }.map {\n            it.await()\n        }.onEach { semaphore.release() }\n    }.buffer(0)\n}\n\ninline fun <T, R> Flow<T>.mapAsyncIndexed(\n    concurrency: Int,\n    crossinline transform: suspend (index: Int, T) -> R\n): Flow<R> = if (concurrency == 1) {\n    mapIndexed { index, value ->\n        transform(index, value)\n    }\n} else {\n    Semaphore(concurrency).let { semaphore ->\n        channelFlow {\n            var index = 0\n            collect {\n                semaphore.acquire()\n                val i = index++\n                send(async { transform(i, it) })\n            }\n        }.map {\n            it.await()\n        }.onEach { semaphore.release() }\n    }.buffer(0)\n}\n\ninline fun <T> Flow<T>.onEachAsync(\n    concurrency: Int,\n    crossinline action: suspend (T) -> Unit\n): Flow<T> = if (concurrency == 1) {\n    onEach { action(it) }\n} else {\n    Semaphore(concurrency).let { semaphore ->\n        channelFlow {\n            collect {\n                semaphore.acquire()\n                send(async {\n                    action(it)\n                    it\n                })\n            }\n        }.map {\n            it.await()\n        }.onEach { semaphore.release() }\n    }.buffer(0)\n}\n\ninline fun <T> Flow<T>.onEachAsyncIndexed(\n    concurrency: Int,\n    crossinline action: suspend (index: Int, T) -> Unit\n): Flow<T> = if (concurrency == 1) {\n    onEachIndexed { index, value ->\n        action(index, value)\n    }\n} else {\n    Semaphore(concurrency).let { semaphore ->\n        channelFlow {\n            var index = 0\n            collect {\n                semaphore.acquire()\n                val i = index++\n                send(async {\n                    action(i, it)\n                    it\n                })\n            }\n        }.map {\n            it.await()\n        }.onEach { semaphore.release() }\n    }.buffer(0)\n}\n\nfun <T> Flow<T>.flowWithLifecycleFirst(\n    lifecycle: Lifecycle,\n    minActiveState: Lifecycle.State = Lifecycle.State.STARTED\n): Flow<T> = callbackFlow {\n    if (!lifecycle.currentState.isAtLeast(minActiveState)) {\n        firstOrNull()?.let {\n            send(it)\n        }\n    }\n    lifecycle.repeatOnLifecycle(minActiveState) {\n        this@flowWithLifecycleFirst.collect {\n            send(it)\n        }\n    }\n    close()\n}\n\nfun <T> Flow<T>.flowWithLifecycleAndDatabaseChange(\n    lifecycle: Lifecycle,\n    minActiveState: Lifecycle.State = Lifecycle.State.STARTED,\n    table: String\n): Flow<T> = callbackFlow {\n    var update = 0\n    val channel = appDb.invalidationTracker\n        .createFlow(table)\n        .conflate()\n        .onEach { update++ }\n        .produceIn(this)\n    lifecycle.repeatOnLifecycle(minActiveState) {\n        if (update == 0) {\n            channel.receive()\n        }\n        this@flowWithLifecycleAndDatabaseChange.collect {\n            update = 0\n            send(it)\n        }\n    }\n    close()\n}\n\nfun <T> Flow<T>.flowWithLifecycleAndDatabaseChangeFirst(\n    lifecycle: Lifecycle,\n    minActiveState: Lifecycle.State = Lifecycle.State.STARTED,\n    table: String\n): Flow<T> = callbackFlow {\n    var update = 0\n    val isActive = lifecycle.currentState.isAtLeast(minActiveState)\n    val channel = appDb.invalidationTracker\n        .createFlow(table, emitInitialState = isActive)\n        .conflate()\n        .onEach { update++ }\n        .produceIn(this)\n    if (!isActive) {\n        firstOrNull()?.let {\n            send(it)\n        }\n    }\n    lifecycle.repeatOnLifecycle(minActiveState) {\n        if (update == 0) {\n            channel.receive()\n        }\n        this@flowWithLifecycleAndDatabaseChangeFirst.collect {\n            update = 0\n            send(it)\n        }\n    }\n    close()\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/utils/FragmentExtensions.kt",
    "content": "@file:Suppress(\"unused\")\n\npackage io.legado.app.utils\n\nimport android.app.Activity\nimport android.content.Intent\nimport android.content.res.ColorStateList\nimport android.graphics.drawable.Drawable\nimport android.os.Bundle\nimport androidx.annotation.ColorRes\nimport androidx.annotation.DrawableRes\nimport androidx.core.content.edit\nimport androidx.fragment.app.DialogFragment\nimport androidx.fragment.app.Fragment\nimport androidx.lifecycle.Lifecycle\nimport io.legado.app.R\nimport io.legado.app.data.entities.Book\nimport io.legado.app.help.book.isAudio\nimport io.legado.app.help.book.isImage\nimport io.legado.app.help.book.isLocal\nimport io.legado.app.help.config.AppConfig\nimport io.legado.app.ui.book.audio.AudioPlayActivity\nimport io.legado.app.ui.book.manga.ReadMangaActivity\nimport io.legado.app.ui.book.read.ReadBookActivity\nimport io.legado.app.ui.widget.dialog.TextDialog\n\ninline fun <reified T : DialogFragment> Fragment.showDialogFragment(\n    arguments: Bundle.() -> Unit = {}\n) {\n    @Suppress(\"DEPRECATION\")\n    val dialog = T::class.java.newInstance()\n    val bundle = Bundle()\n    bundle.apply(arguments)\n    dialog.arguments = bundle\n    dialog.show(childFragmentManager, T::class.simpleName)\n}\n\nfun Fragment.showDialogFragment(dialogFragment: DialogFragment) {\n    dialogFragment.show(childFragmentManager, dialogFragment::class.simpleName)\n}\n\nfun Fragment.getPrefBoolean(key: String, defValue: Boolean = false) =\n    requireContext().defaultSharedPreferences.getBoolean(key, defValue)\n\nfun Fragment.putPrefBoolean(key: String, value: Boolean = false) =\n    requireContext().defaultSharedPreferences.edit { putBoolean(key, value) }\n\nfun Fragment.getPrefInt(key: String, defValue: Int = 0) =\n    requireContext().defaultSharedPreferences.getInt(key, defValue)\n\nfun Fragment.putPrefInt(key: String, value: Int) =\n    requireContext().defaultSharedPreferences.edit { putInt(key, value) }\n\nfun Fragment.getPrefLong(key: String, defValue: Long = 0L) =\n    requireContext().defaultSharedPreferences.getLong(key, defValue)\n\nfun Fragment.putPrefLong(key: String, value: Long) =\n    requireContext().defaultSharedPreferences.edit { putLong(key, value) }\n\nfun Fragment.getPrefString(key: String, defValue: String? = null) =\n    requireContext().defaultSharedPreferences.getString(key, defValue)\n\nfun Fragment.putPrefString(key: String, value: String) =\n    requireContext().defaultSharedPreferences.edit { putString(key, value) }\n\nfun Fragment.getPrefStringSet(\n    key: String,\n    defValue: MutableSet<String>? = null\n): MutableSet<String>? =\n    requireContext().defaultSharedPreferences.getStringSet(key, defValue)\n\nfun Fragment.putPrefStringSet(key: String, value: MutableSet<String>) =\n    requireContext().defaultSharedPreferences.edit { putStringSet(key, value) }\n\nfun Fragment.removePref(key: String) =\n    requireContext().defaultSharedPreferences.edit { remove(key) }\n\nfun Fragment.getCompatColor(@ColorRes id: Int): Int = requireContext().getCompatColor(id)\n\nfun Fragment.getCompatDrawable(@DrawableRes id: Int): Drawable? =\n    requireContext().getCompatDrawable(id)\n\nfun Fragment.getCompatColorStateList(@ColorRes id: Int): ColorStateList? =\n    requireContext().getCompatColorStateList(id)\n\ninline fun <reified T : Activity> Fragment.startActivity(\n    configIntent: Intent.() -> Unit = {}\n) {\n    startActivity(Intent(requireContext(), T::class.java).apply(configIntent))\n}\n\nfun Fragment.startActivityForBook(\n    book: Book,\n    configIntent: Intent.() -> Unit = {},\n) {\n    val cls = when {\n        book.isAudio -> AudioPlayActivity::class.java\n        !book.isLocal && book.isImage && AppConfig.showMangaUi -> ReadMangaActivity::class.java\n        else -> ReadBookActivity::class.java\n    }\n    val intent = Intent(requireActivity(), cls)\n    intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)\n    intent.putExtra(\"bookUrl\", book.bookUrl)\n    intent.apply(configIntent)\n    startActivity(intent)\n}\n\nfun Fragment.showHelp(fileName: String) {\n    val mdText = String(requireContext().assets.open(\"web/help/md/${fileName}.md\").readBytes())\n    showDialogFragment(TextDialog(getString(R.string.help), mdText, TextDialog.Mode.MD))\n}\n\nval Fragment.isCreated\n    get() = lifecycle.currentState.isAtLeast(Lifecycle.State.CREATED)\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/utils/GsonExtensions.kt",
    "content": "package io.legado.app.utils\n\nimport com.google.gson.Gson\nimport com.google.gson.GsonBuilder\nimport com.google.gson.JsonDeserializationContext\nimport com.google.gson.JsonDeserializer\nimport com.google.gson.JsonElement\nimport com.google.gson.JsonParseException\nimport com.google.gson.JsonSyntaxException\nimport com.google.gson.Strictness\nimport com.google.gson.ToNumberPolicy\nimport com.google.gson.internal.LinkedTreeMap\nimport com.google.gson.reflect.TypeToken\nimport com.google.gson.stream.JsonWriter\nimport io.legado.app.data.entities.rule.BookInfoRule\nimport io.legado.app.data.entities.rule.ContentRule\nimport io.legado.app.data.entities.rule.ExploreRule\nimport io.legado.app.data.entities.rule.ReviewRule\nimport io.legado.app.data.entities.rule.SearchRule\nimport io.legado.app.data.entities.rule.TocRule\nimport java.io.InputStream\nimport java.io.InputStreamReader\nimport java.io.OutputStream\nimport java.io.OutputStreamWriter\nimport java.lang.reflect.Type\nimport kotlin.math.ceil\n\nval INITIAL_GSON: Gson by lazy {\n    GsonBuilder()\n        .registerTypeAdapter(\n            object : TypeToken<Map<String?, Any?>?>() {}.type,\n            MapDeserializerDoubleAsIntFix()\n        )\n        .registerTypeAdapter(Int::class.java, IntJsonDeserializer())\n        .registerTypeAdapter(String::class.java, StringJsonDeserializer())\n        .setObjectToNumberStrategy(ToNumberPolicy.LONG_OR_DOUBLE)\n        .disableHtmlEscaping()\n        .setPrettyPrinting()\n        .create()\n}\n\nval GSON: Gson by lazy {\n    INITIAL_GSON.newBuilder()\n        .registerTypeAdapter(ExploreRule::class.java, ExploreRule.jsonDeserializer)\n        .registerTypeAdapter(SearchRule::class.java, SearchRule.jsonDeserializer)\n        .registerTypeAdapter(BookInfoRule::class.java, BookInfoRule.jsonDeserializer)\n        .registerTypeAdapter(TocRule::class.java, TocRule.jsonDeserializer)\n        .registerTypeAdapter(ContentRule::class.java, ContentRule.jsonDeserializer)\n        .registerTypeAdapter(ReviewRule::class.java, ReviewRule.jsonDeserializer)\n        .create()\n}\n\nval GSONStrict: Gson by lazy {\n    GSON.newBuilder()\n        .setStrictness(Strictness.STRICT)\n        .create()\n}\n\ninline fun <reified T> genericType(): Type = object : TypeToken<T>() {}.type\n\ninline fun <reified T> Gson.fromJsonObject(json: String?): Result<T> {\n    return kotlin.runCatching {\n        if (json == null) {\n            throw JsonSyntaxException(\"解析字符串为空\")\n        }\n        fromJson(json, genericType<T>()) as T\n    }\n}\n\ninline fun <reified T> Gson.fromJsonArray(json: String?): Result<List<T>> {\n    return kotlin.runCatching {\n        if (json == null) {\n            throw JsonSyntaxException(\"解析字符串为空\")\n        }\n        val type = TypeToken.getParameterized(List::class.java, T::class.java).type\n        val list = fromJson(json, type) as List<T?>\n        if (list.contains(null)) {\n            throw JsonSyntaxException(\n                \"列表不能存在null元素，可能是json格式错误，通常为列表存在多余的逗号所致\"\n            )\n        }\n        @Suppress(\"UNCHECKED_CAST\")\n        list as List<T>\n    }\n}\n\ninline fun <reified T> Gson.fromJsonObject(inputStream: InputStream?): Result<T> {\n    return kotlin.runCatching {\n        if (inputStream == null) {\n            throw JsonSyntaxException(\"解析流为空\")\n        }\n        val reader = InputStreamReader(inputStream)\n        fromJson(reader, genericType<T>()) as T\n    }\n}\n\ninline fun <reified T> Gson.fromJsonArray(inputStream: InputStream?): Result<List<T>> {\n    return kotlin.runCatching {\n        if (inputStream == null) {\n            throw JsonSyntaxException(\"解析流为空\")\n        }\n        val reader = InputStreamReader(inputStream)\n        val type = TypeToken.getParameterized(List::class.java, T::class.java).type\n        val list = fromJson(reader, type) as List<T?>\n        if (list.contains(null)) {\n            throw JsonSyntaxException(\n                \"列表不能存在null元素，可能是json格式错误，通常为列表存在多余的逗号所致\"\n            )\n        }\n        @Suppress(\"UNCHECKED_CAST\")\n        list as List<T>\n    }\n}\n\nfun Gson.writeToOutputStream(out: OutputStream, any: Any) {\n    val writer = JsonWriter(OutputStreamWriter(out, \"UTF-8\"))\n    writer.setIndent(\"  \")\n    if (any is List<*>) {\n        writer.beginArray()\n        any.forEach {\n            it?.let {\n                toJson(it, it::class.java, writer)\n            }\n        }\n        writer.endArray()\n    } else {\n        toJson(any, any::class.java, writer)\n    }\n    writer.close()\n}\n\n/**\n *\n */\nclass StringJsonDeserializer : JsonDeserializer<String?> {\n\n    override fun deserialize(\n        json: JsonElement,\n        typeOfT: Type,\n        context: JsonDeserializationContext?\n    ): String? {\n        return when {\n            json.isJsonPrimitive -> json.asString\n            json.isJsonNull -> null\n            else -> json.toString()\n        }\n    }\n\n}\n\n/**\n * int类型转化失败时跳过\n */\nclass IntJsonDeserializer : JsonDeserializer<Int?> {\n\n    override fun deserialize(\n        json: JsonElement,\n        typeOfT: Type?,\n        context: JsonDeserializationContext?\n    ): Int? {\n        return when {\n            json.isJsonPrimitive -> {\n                val prim = json.asJsonPrimitive\n                if (prim.isNumber) {\n                    prim.asNumber.toInt()\n                } else {\n                    null\n                }\n            }\n\n            else -> null\n        }\n    }\n\n}\n\n/**\n * 修复Int变为Double的问题\n */\nclass MapDeserializerDoubleAsIntFix :\n    JsonDeserializer<Map<String, Any?>?> {\n\n    @Throws(JsonParseException::class)\n    override fun deserialize(\n        jsonElement: JsonElement,\n        type: Type,\n        jsonDeserializationContext: JsonDeserializationContext\n    ): Map<String, Any?>? {\n        @Suppress(\"unchecked_cast\")\n        return read(jsonElement) as? Map<String, Any?>\n    }\n\n    fun read(json: JsonElement): Any? {\n        when {\n            json.isJsonArray -> {\n                val list: MutableList<Any?> = ArrayList()\n                val arr = json.asJsonArray\n                for (anArr in arr) {\n                    list.add(read(anArr))\n                }\n                return list\n            }\n\n            json.isJsonObject -> {\n                val map: MutableMap<String, Any?> =\n                    LinkedTreeMap()\n                val obj = json.asJsonObject\n                val entitySet =\n                    obj.entrySet()\n                for ((key, value) in entitySet) {\n                    map[key] = read(value)\n                }\n                return map\n            }\n\n            json.isJsonPrimitive -> {\n                val prim = json.asJsonPrimitive\n                when {\n                    prim.isBoolean -> {\n                        return prim.asBoolean\n                    }\n\n                    prim.isString -> {\n                        return prim.asString\n                    }\n\n                    prim.isNumber -> {\n                        val num: Number = prim.asNumber\n                        // here you can handle double int/long values\n                        // and return any type you want\n                        // this solution will transform 3.0 float to long values\n                        return if (ceil(num.toDouble()) == num.toLong().toDouble()) {\n                            num.toLong()\n                        } else {\n                            num.toDouble()\n                        }\n                    }\n                }\n            }\n        }\n        return null\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/utils/HandlerUtils.kt",
    "content": "@file:Suppress(\"unused\")\n\npackage io.legado.app.utils\n\nimport android.os.Build.VERSION.SDK_INT\nimport android.os.Handler\nimport android.os.Looper\nimport kotlinx.coroutines.CoroutineScope\nimport kotlinx.coroutines.Dispatchers.IO\nimport kotlinx.coroutines.launch\n\n/** This main looper cache avoids synchronization overhead when accessed repeatedly. */\nprivate val mainLooper: Looper = Looper.getMainLooper()\n\nprivate val mainThread: Thread = mainLooper.thread\n\nval isMainThread: Boolean get() = mainThread === Thread.currentThread()\n\nfun buildMainHandler(): Handler {\n    return if (SDK_INT >= 28) Handler.createAsync(mainLooper) else try {\n        Handler::class.java.getDeclaredConstructor(\n            Looper::class.java,\n            Handler.Callback::class.java,\n            Boolean::class.javaPrimitiveType // async\n        ).newInstance(mainLooper, null, true)\n    } catch (ignored: NoSuchMethodException) {\n        // Hidden constructor absent. Fall back to non-async constructor.\n        Handler(mainLooper)\n    }\n}\n\nprivate val mainHandler by lazy { buildMainHandler() }\n\nfun runOnUI(function: () -> Unit) {\n    if (isMainThread) {\n        function()\n    } else {\n        mainHandler.post(function)\n    }\n}\n\nfun CoroutineScope.runOnIO(function: () -> Unit) {\n    if (isMainThread) {\n        launch(IO) {\n            function()\n        }\n    } else {\n        function()\n    }\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/utils/HtmlFormatter.kt",
    "content": "package io.legado.app.utils\n\nimport io.legado.app.model.analyzeRule.AnalyzeUrl\nimport java.net.URL\nimport java.util.regex.Pattern\n\n@Suppress(\"RegExpRedundantEscape\")\nobject HtmlFormatter {\n    private val nbspRegex = \"(&nbsp;)+\".toRegex()\n    private val espRegex = \"(&ensp;|&emsp;)\".toRegex()\n    private val noPrintRegex = \"(&thinsp;|&zwnj;|&zwj;|\\u2009|\\u200C|\\u200D)\".toRegex()\n    private val wrapHtmlRegex = \"</?(?:div|p|br|hr|h\\\\d|article|dd|dl)[^>]*>\".toRegex()\n    private val commentRegex = \"<!--[^>]*-->\".toRegex() //注释\n    private val notImgHtmlRegex = \"</?(?!img)[a-zA-Z]+(?=[ >])[^<>]*>\".toRegex()\n    private val otherHtmlRegex = \"</?[a-zA-Z]+(?=[ >])[^<>]*>\".toRegex()\n    private val formatImagePattern = Pattern.compile(\n        \"<img[^>]*\\\\ssrc\\\\s*=\\\\s*['\\\"]([^'\\\"{>]*\\\\{(?:[^{}]|\\\\{[^}>]+\\\\})+\\\\})['\\\"][^>]*>|<img[^>]*\\\\s(?:data-src|src)\\\\s*=\\\\s*['\\\"]([^'\\\">]+)['\\\"][^>]*>|<img[^>]*\\\\sdata-[^=>]*=\\\\s*['\\\"]([^'\\\">]*)['\\\"][^>]*>\",\n        Pattern.CASE_INSENSITIVE\n    )\n    private val indent1Regex = \"\\\\s*\\\\n+\\\\s*\".toRegex()\n    private val indent2Regex = \"^[\\\\n\\\\s]+\".toRegex()\n    private val lastRegex = \"[\\\\n\\\\s]+$\".toRegex()\n\n    fun format(html: String?, otherRegex: Regex = otherHtmlRegex): String {\n        html ?: return \"\"\n        return html.replace(nbspRegex, \" \")\n            .replace(espRegex, \" \")\n            .replace(noPrintRegex, \"\")\n            .replace(wrapHtmlRegex, \"\\n\")\n            .replace(commentRegex, \"\")\n            .replace(otherRegex, \"\")\n            .replace(indent1Regex, \"\\n　　\")\n            .replace(indent2Regex, \"　　\")\n            .replace(lastRegex, \"\")\n    }\n\n    fun formatKeepImg(html: String?, redirectUrl: URL? = null): String {\n        html ?: return \"\"\n        val keepImgHtml = format(html, notImgHtmlRegex)\n\n        //正则的“|”处于顶端而不处于（）中时，具有类似||的熔断效果，故以此机制简化原来的代码\n        val matcher = formatImagePattern.matcher(keepImgHtml)\n        var appendPos = 0\n        val sb = StringBuilder()\n        while (matcher.find()) {\n            var param = \"\"\n            sb.append(\n                keepImgHtml.substring(appendPos, matcher.start()), \"<img src=\\\"${\n                    NetworkUtils.getAbsoluteURL(\n                        redirectUrl,\n                        matcher.group(1)?.let {\n                            val urlMatcher = AnalyzeUrl.paramPattern.matcher(it)\n                            if (urlMatcher.find()) {\n                                param = ',' + it.substring(urlMatcher.end())\n                                it.substring(0, urlMatcher.start())\n                            } else it\n                        } ?: matcher.group(2) ?: matcher.group(3)!!\n                    ) + param\n                }\\\">\"\n            )\n            appendPos = matcher.end()\n        }\n        if (appendPos < keepImgHtml.length) sb.append(\n            keepImgHtml.substring(\n                appendPos,\n                keepImgHtml.length\n            )\n        )\n        return sb.toString()\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/utils/ImageUtils.kt",
    "content": "package io.legado.app.utils\n\nimport io.legado.app.constant.AppLog\nimport io.legado.app.data.entities.BaseSource\nimport io.legado.app.data.entities.Book\nimport io.legado.app.data.entities.BookSource\nimport io.legado.app.data.entities.RssSource\nimport java.io.ByteArrayInputStream\nimport java.io.InputStream\n\n/**\n * 加密图片解密工具\n */\nobject ImageUtils {\n\n    /**\n     * @param isCover 根据这个执行书源中不同的解密规则\n     * @return 解密失败返回Null 解密规则为空不处理\n     */\n    fun decode(\n        src: String, bytes: ByteArray, isCover: Boolean,\n        source: BaseSource?, book: Book? = null\n    ): ByteArray? {\n        val ruleJs = getRuleJs(source, isCover)\n        if (ruleJs.isNullOrBlank()) return bytes\n        //解密库hutool.crypto ByteArray|InputStream -> ByteArray\n        return kotlin.runCatching {\n            source?.evalJS(ruleJs) {\n                put(\"book\", book)\n                put(\"result\", bytes)\n                put(\"src\", src)\n            } as ByteArray\n        }.onFailure {\n            AppLog.putDebug(\"${src}解密错误\", it)\n        }.getOrNull()\n    }\n\n    fun decode(\n        src: String, inputStream: InputStream, isCover: Boolean,\n        source: BaseSource?, book: Book? = null\n    ): InputStream? {\n        val ruleJs = getRuleJs(source, isCover)\n        if (ruleJs.isNullOrBlank()) return inputStream\n        //解密库hutool.crypto ByteArray|InputStream -> ByteArray\n        return kotlin.runCatching {\n            val bytes = source?.evalJS(ruleJs) {\n                put(\"book\", book)\n                put(\"result\", inputStream)\n                put(\"src\", src)\n            } as ByteArray\n            ByteArrayInputStream(bytes)\n        }.onFailure {\n            AppLog.putDebug(\"${src}解密错误\", it)\n        }.getOrNull()\n    }\n\n    fun skipDecode(source: BaseSource?, isCover: Boolean): Boolean {\n        return getRuleJs(source, isCover).isNullOrBlank()\n    }\n\n    private fun getRuleJs(\n        source: BaseSource?, isCover: Boolean\n    ): String? {\n        return when (source) {\n            is BookSource ->\n                if (isCover) source.coverDecodeJs\n                else source.getContentRule().imageDecode\n\n            is RssSource -> source.coverDecodeJs\n            else -> null\n        }\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/utils/InputStreamExtensions.kt",
    "content": "package io.legado.app.utils\n\nimport java.io.InputStream\nimport java.util.*\n\nfun InputStream?.isJson(): Boolean {\n    this ?: return false\n    this.use {\n        val byteArray = ByteArray(128)\n        it.read(byteArray)\n        val a = String(byteArray).trim()\n        it.skip(it.available() - 128L)\n        it.read(byteArray)\n        val b = String(byteArray).trim()\n        return (a + b).isJson()\n    }\n}\n\nfun InputStream?.contains(str: String): Boolean {\n    this ?: return false\n    this.use {\n        val scanner = Scanner(it)\n        return scanner.findWithinHorizon(str, 0) != null\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/utils/IntentExtensions.kt",
    "content": "@file:Suppress(\"unused\")\n\npackage io.legado.app.utils\n\nimport android.content.Intent\n\nfun Intent.putJson(key: String, any: Any?) {\n    any?.let {\n        putExtra(key, GSON.toJson(any))\n    }\n}\n\ninline fun <reified T> Intent.getJsonObject(key: String): T? {\n    val value = getStringExtra(key)\n    return GSON.fromJsonObject<T>(value).getOrNull()\n}\n\ninline fun <reified T> Intent.getJsonArray(key: String): List<T>? {\n    val value = getStringExtra(key)\n    return GSON.fromJsonArray<T>(value).getOrNull()\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/utils/IntentType.kt",
    "content": "package io.legado.app.utils\n\nimport android.net.Uri\nimport java.io.File\n\nobject IntentType {\n\n    fun from(uri: Uri): String {\n        return from(uri.toString())\n    }\n\n    fun from(file: File): String {\n        return from(file.absolutePath)\n    }\n\n    fun from(path: String?): String {\n        val suffix = path\n            ?.substringAfterLast(File.separator)\n            ?.substringAfterLast(\".\", \"\")\n            ?.lowercase()\n        return when (suffix) {\n            \"m4a\", \"mp3\", \"mid\", \"xmf\", \"ogg\", \"wav\" -> \"video/*\"\n            \"3gp\", \"mp4\" -> \"audio/*\"\n            \"jpg\", \"gif\", \"png\", \"jpeg\", \"bmp\" -> \"image/*\"\n            \"\", \"txt\", \"json\", \"log\" -> \"text/plain\"\n            \"apk\" -> \"application/vnd.android.package-archive\"\n            else -> \"*/*\"\n        }\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/utils/JsURL.kt",
    "content": "package io.legado.app.utils\n\nimport androidx.annotation.Keep\nimport java.net.URL\nimport java.net.URLDecoder\n\n@Keep\n@Suppress(\"MemberVisibilityCanBePrivate\")\nclass JsURL(url: String, baseUrl: String? = null) {\n\n    val searchParams: Map<String, String>?\n    val host: String\n    val origin: String\n    val pathname: String\n\n    init {\n        val mUrl = if (!baseUrl.isNullOrEmpty()) {\n            val base = URL(baseUrl)\n            URL(base, url)\n        } else {\n            URL(url)\n        }\n        host = mUrl.host\n        origin = if (mUrl.port > 0) {\n            \"${mUrl.protocol}://$host:${mUrl.port}\"\n        } else {\n            \"${mUrl.protocol}://$host\"\n        }\n        pathname = mUrl.path\n        val query = mUrl.query\n        searchParams = query?.let { _ ->\n            val map = hashMapOf<String, String>()\n            query.split(\"&\").forEach {\n                val x = it.split(\"=\", limit = 2)\n                if (x.size == 2) {\n                    map[x[0]] = URLDecoder.decode(x[1], \"utf-8\")\n                }\n            }\n            map\n        }\n    }\n\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/utils/JsonExtensions.kt",
    "content": "package io.legado.app.utils\n\nimport com.jayway.jsonpath.*\n\nval jsonPath: ParseContext by lazy {\n    JsonPath.using(\n        Configuration.builder()\n            .options(Option.SUPPRESS_EXCEPTIONS)\n            .build()\n    )\n}\n\nfun ReadContext.readString(path: String): String? = this.read(path, String::class.java)\n\nfun ReadContext.readBool(path: String): Boolean? = this.read(path, Boolean::class.java)\n\nfun ReadContext.readInt(path: String): Int? = this.read(path, Int::class.java)\n\nfun ReadContext.readLong(path: String): Long? = this.read(path, Long::class.java)\n\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/utils/JsoupExtensions.kt",
    "content": "package io.legado.app.utils\n\nimport org.jsoup.internal.StringUtil\nimport org.jsoup.nodes.CDataNode\nimport org.jsoup.nodes.Element\nimport org.jsoup.nodes.Node\nimport org.jsoup.nodes.TextNode\nimport org.jsoup.select.Elements\nimport org.jsoup.select.NodeTraversor\nimport org.jsoup.select.NodeVisitor\n\n\nfun Element.textArray(): Array<String> {\n    val sb = StringUtil.borrowBuilder()\n    NodeTraversor.traverse(object : NodeVisitor {\n        override fun head(node: Node, depth: Int) {\n            if (node is TextNode) {\n                appendNormalisedText(sb, node)\n            } else if (node is Element) {\n                if (sb.isNotEmpty() &&\n                    (node.isBlock || node.tag().name == \"br\") &&\n                    !lastCharIsWhitespace(sb)\n                ) sb.append(\"\\n\")\n            }\n        }\n\n        override fun tail(node: Node, depth: Int) {\n            if (node is Element) {\n                if (node.isBlock && node.nextSibling() is TextNode\n                    && !lastCharIsWhitespace(sb)\n                ) {\n                    sb.append(\"\\n\")\n                }\n            }\n        }\n    }, this)\n    val text = StringUtil.releaseBuilder(sb).trim { it <= ' ' }\n    return text.splitNotBlank(\"\\n\")\n}\n\nfun Element.findNS(tag: String, namespace: HashSet<String>): Elements {\n    return select(\"*|$tag\").filter { el ->\n        namespace.contains(el.tagName().substringBefore(\":\"))\n    }.toElements()\n}\n\nfun Element.findNSPrefix(namespaceURI: String): HashSet<String> {\n    return select(\"[^xmlns:]\").map { element ->\n        element.attributes().filter { it.value == namespaceURI }.map { it.key.substring(6) }\n    }.flatten().toHashSet()\n}\n\nfun List<Element>.toElements() = Elements(this)\n\nprivate fun appendNormalisedText(sb: StringBuilder, textNode: TextNode) {\n    val text = textNode.wholeText\n    if (preserveWhitespace(textNode.parentNode()) || textNode is CDataNode)\n        sb.append(text)\n    else StringUtil.appendNormalisedWhitespace(sb, text, lastCharIsWhitespace(sb))\n}\n\nprivate fun preserveWhitespace(node: Node?): Boolean {\n    if (node is Element) {\n        var el = node as Element?\n        var i = 0\n        do {\n            if (el!!.tag().preserveWhitespace()) return true\n            el = el.parent()\n            i++\n        } while (i < 6 && el != null)\n    }\n    return false\n}\n\nprivate fun lastCharIsWhitespace(sb: java.lang.StringBuilder): Boolean {\n    return sb.isNotEmpty() && sb[sb.length - 1] == ' '\n}\n\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/utils/LogUtils.kt",
    "content": "@file:Suppress(\"unused\")\n\npackage io.legado.app.utils\n\nimport android.annotation.SuppressLint\nimport android.content.Context\nimport android.os.Build\nimport android.webkit.WebSettings\nimport io.legado.app.BuildConfig\nimport io.legado.app.constant.AppConst\nimport io.legado.app.constant.AppLog\nimport io.legado.app.help.config.AppConfig\nimport io.legado.app.help.globalExecutor\nimport splitties.init.appCtx\nimport java.text.SimpleDateFormat\nimport java.util.Date\nimport java.util.logging.FileHandler\nimport java.util.logging.Level\nimport java.util.logging.LogRecord\nimport java.util.logging.Logger\nimport kotlin.time.Duration.Companion.days\n\n@SuppressLint(\"SimpleDateFormat\")\n@Suppress(\"unused\")\nobject LogUtils {\n    const val TIME_PATTERN = \"yy-MM-dd HH:mm:ss.SSS\"\n    val logTimeFormat by lazy { SimpleDateFormat(TIME_PATTERN) }\n\n    fun init(context: Context) {\n        fileHandler = createFileHandler(context)?.also {\n            logger.addHandler(it)\n        }\n    }\n\n    @JvmStatic\n    fun d(tag: String, msg: String) {\n        logger.log(Level.INFO, \"$tag $msg\")\n    }\n\n    inline fun d(tag: String, lazyMsg: () -> String) {\n        if (logger.isLoggable(Level.INFO)) {\n            logger.log(Level.INFO, \"$tag ${lazyMsg()}\")\n        }\n    }\n\n    @JvmStatic\n    fun e(tag: String, msg: String) {\n        logger.log(Level.WARNING, \"$tag $msg\")\n    }\n\n    val logger: Logger by lazy {\n        Logger.getLogger(\"Legado\")\n    }\n\n    private var fileHandler: FileHandler? = null\n\n    private fun createFileHandler(context: Context): FileHandler? {\n        try {\n            val root = context.externalCacheDir ?: return null\n            val logFolder = FileUtils.createFolderIfNotExist(root, \"logs\")\n            globalExecutor.execute {\n                val expiredTime = System.currentTimeMillis() - 7.days.inWholeMilliseconds\n                logFolder.listFiles()?.forEach {\n                    if (it.lastModified() < expiredTime || it.name.endsWith(\".lck\")) {\n                        it.delete()\n                    }\n                }\n            }\n            val date = getCurrentDateStr(TIME_PATTERN).replace(\" \", \"_\").replace(\":\", \"-\")\n            val logPath = FileUtils.getPath(root = logFolder, \"appLog-$date.txt\")\n            return AsyncFileHandler(logPath).apply {\n                formatter = object : java.util.logging.Formatter() {\n                    override fun format(record: LogRecord): String {\n                        // 设置文件输出格式\n                        return getCurrentDateStr(TIME_PATTERN) + \": \" + record.message + \"\\n\"\n                    }\n                }\n                level = if (AppConfig.recordLog) {\n                    Level.INFO\n                } else {\n                    Level.OFF\n                }\n            }\n        } catch (e: Exception) {\n            e.printStackTrace()\n            AppLog.putNotSave(\"创建fileHandler出错\\n$e\", e)\n            return null\n        }\n    }\n\n    fun upLevel() {\n        val level = if (AppConfig.recordLog) {\n            Level.INFO\n        } else {\n            Level.OFF\n        }\n        fileHandler?.level = level\n    }\n\n    /**\n     * 获取当前时间\n     */\n    @SuppressLint(\"SimpleDateFormat\")\n    fun getCurrentDateStr(pattern: String): String {\n        val date = Date()\n        val sdf = SimpleDateFormat(pattern)\n        return sdf.format(date)\n    }\n\n    fun logDeviceInfo() {\n        d(\"DeviceInfo\") {\n            buildString {\n                kotlin.runCatching {\n                    //获取系统信息\n                    append(\"MANUFACTURER=\").append(Build.MANUFACTURER).append(\"\\n\")\n                    append(\"BRAND=\").append(Build.BRAND).append(\"\\n\")\n                    append(\"MODEL=\").append(Build.MODEL).append(\"\\n\")\n                    append(\"SDK_INT=\").append(Build.VERSION.SDK_INT).append(\"\\n\")\n                    append(\"RELEASE=\").append(Build.VERSION.RELEASE).append(\"\\n\")\n                    val userAgent = try {\n                        WebSettings.getDefaultUserAgent(appCtx)\n                    } catch (e: Throwable) {\n                        e.toString()\n                    }\n                    append(\"WebViewUserAgent=\").append(userAgent).append(\"\\n\")\n                    append(\"packageName=\").append(appCtx.packageName).append(\"\\n\")\n                    append(\"heapSize=\").append(Runtime.getRuntime().maxMemory()).append(\"\\n\")\n                    //获取app版本信息\n                    AppConst.appInfo.let {\n                        append(\"versionName=\").append(it.versionName).append(\"\\n\")\n                        append(\"versionCode=\").append(it.versionCode).append(\"\\n\")\n                    }\n                }\n            }\n        }\n    }\n\n}\n\nfun Throwable.printOnDebug() {\n    if (BuildConfig.DEBUG) {\n        printStackTrace()\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/utils/MD5Utils.kt",
    "content": "package io.legado.app.utils\n\nimport cn.hutool.crypto.digest.DigestUtil\nimport cn.hutool.crypto.digest.Digester\nimport java.io.InputStream\nimport kotlin.concurrent.getOrSet\n\n/**\n * 将字符串转化为MD5\n */\n@Suppress(\"unused\")\nobject MD5Utils {\n\n    private val threadLocal = ThreadLocal<Digester>()\n\n    private val MD5Digester\n        get() = threadLocal.getOrSet {\n            DigestUtil.digester(\"MD5\")\n        }\n\n    fun md5Encode(str: String?): String {\n        return MD5Digester.digestHex(str)\n    }\n\n    fun md5Encode(inputStream: InputStream): String {\n        return MD5Digester.digestHex(inputStream)\n    }\n\n    fun md5Encode16(str: String): String {\n        var reStr = md5Encode(str)\n        reStr = reStr.substring(8, 24)\n        return reStr\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/utils/MapExtensions.kt",
    "content": "package io.legado.app.utils\n\nfun HashMap<String, *>.has(key: String, ignoreCase: Boolean = false): Boolean {\n    for (item in this) {\n        if (key.equals(item.key, ignoreCase)) {\n            return true\n        }\n    }\n    return false\n}\n\nfun <T> HashMap<String, T>.get(key: String, ignoreCase: Boolean = false): T? {\n    for (item in this) {\n        if (key.equals(item.key, ignoreCase)) {\n            return item.value\n        }\n    }\n    return null\n}\n\ninline fun <K, V> MutableMap<K, V>.getOrPutLimit(key: K, maxSize: Int, defaultValue: () -> V): V {\n    var value = get(key)\n    if (containsKey(key)) {\n        @Suppress(\"UNCHECKED_CAST\")\n        return value as V\n    }\n    value = defaultValue()\n    if (size < maxSize) {\n        put(key, value)\n    }\n    return value\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/utils/MenuExtensions.kt",
    "content": "@file:Suppress(\"unused\")\n\npackage io.legado.app.utils\n\nimport android.annotation.SuppressLint\nimport android.content.Context\nimport android.view.Menu\nimport android.view.MenuItem\nimport android.view.View\nimport android.widget.ImageButton\nimport androidx.appcompat.view.menu.MenuBuilder\nimport androidx.appcompat.view.menu.MenuItemImpl\nimport androidx.appcompat.view.menu.SubMenuBuilder\nimport androidx.core.view.forEach\nimport io.legado.app.R\nimport io.legado.app.constant.Theme\nimport io.legado.app.lib.theme.primaryTextColor\nimport java.lang.reflect.Method\n\n@SuppressLint(\"RestrictedApi\")\n@Suppress(\"UsePropertyAccessSyntax\")\nfun Menu.applyTint(context: Context, theme: Theme = Theme.Auto): Menu = this.let { menu ->\n    if (menu is MenuBuilder) {\n        menu.setOptionalIconsVisible(true)\n    }\n    val defaultTextColor = context.getCompatColor(R.color.primaryText)\n    val tintColor = MenuExtensions.getMenuColor(context, theme)\n    menu.forEach { item ->\n        (item as MenuItemImpl).let { impl ->\n            //overflow：展开的item\n            impl.icon?.setTintMutate(\n                if (impl.requiresOverflow()) defaultTextColor else tintColor\n            )\n        }\n    }\n    return menu\n}\n\n@SuppressLint(\"RestrictedApi\")\nfun Menu.applyOpenTint(context: Context) {\n    //展开菜单显示图标\n    if (this.javaClass.simpleName.equals(\"MenuBuilder\", ignoreCase = true)) {\n        val defaultTextColor = context.getCompatColor(R.color.primaryText)\n        kotlin.runCatching {\n            var method: Method =\n                this.javaClass.getDeclaredMethod(\"setOptionalIconsVisible\", java.lang.Boolean.TYPE)\n            method.isAccessible = true\n            method.invoke(this, true)\n            method = this.javaClass.getDeclaredMethod(\"getNonActionItems\")\n            val menuItems = method.invoke(this)\n            if (menuItems is ArrayList<*>) {\n                for (menuItem in menuItems) {\n                    if (menuItem is MenuItem) {\n                        menuItem.icon?.setTintMutate(defaultTextColor)\n                    }\n                }\n            }\n        }\n    } else if (this.javaClass.simpleName.equals(\"SubMenuBuilder\", ignoreCase = true)) {\n        val defaultTextColor = context.getCompatColor(R.color.primaryText)\n        (this as? SubMenuBuilder)?.forEach { item: MenuItem ->\n            item.icon?.setTintMutate(defaultTextColor)\n        }\n    }\n}\n\nfun Menu.iconItemOnLongClick(id: Int, function: (view: View) -> Unit) {\n    findItem(id)?.let { item ->\n        item.setActionView(R.layout.view_action_button)\n        item.actionView?.run {\n            contentDescription = item.title\n            findViewById<ImageButton>(R.id.item).setImageDrawable(item.icon)\n            setOnLongClickListener {\n                function.invoke(this)\n                true\n            }\n            setOnClickListener {\n                performIdentifierAction(id, 0)\n            }\n        }\n    }\n}\n\n@SuppressLint(\"RestrictedApi\")\ninline fun Menu.transaction(block: (Menu) -> Unit) {\n    val menuBuilder = this as? MenuBuilder\n    menuBuilder?.stopDispatchingItemsChanged()\n    try {\n        block(this)\n    } finally {\n        menuBuilder?.startDispatchingItemsChanged()\n    }\n}\n\nobject MenuExtensions {\n\n    fun getMenuColor(\n        context: Context,\n        theme: Theme = Theme.Auto,\n        requiresOverflow: Boolean = false\n    ): Int {\n        val defaultTextColor = context.getCompatColor(R.color.primaryText)\n        if (requiresOverflow)\n            return defaultTextColor\n        val primaryTextColor = context.primaryTextColor\n        return when (theme) {\n            Theme.Dark -> context.getCompatColor(R.color.md_white_1000)\n            Theme.Light -> context.getCompatColor(R.color.md_black_1000)\n            else -> primaryTextColor\n        }\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/utils/MenuItemExtensions.kt",
    "content": "package io.legado.app.utils\n\nimport android.view.MenuItem\nimport android.widget.ImageButton\nimport androidx.annotation.DrawableRes\nimport io.legado.app.R\n\nfun MenuItem.setIconCompat(@DrawableRes iconRes: Int) {\n    setIcon(iconRes)\n    actionView?.findViewById<ImageButton>(R.id.item)?.setImageDrawable(icon)\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/utils/MutableLiveDataExtensions.kt",
    "content": "package io.legado.app.utils\n\nimport android.os.Handler\nimport android.os.Looper\nimport androidx.lifecycle.MutableLiveData\n\nprivate val mainHandler by lazy { Handler(Looper.getMainLooper()) }\n\nfun <T> MutableLiveData<T>.sendValue(value: T) {\n    mainHandler.post {\n        this@sendValue.value = value\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/utils/NavigationViewUtils.kt",
    "content": "@file:Suppress(\"unused\")\n\npackage io.legado.app.utils\n\nimport android.content.res.ColorStateList\nimport androidx.annotation.ColorInt\nimport com.google.android.material.internal.NavigationMenuView\nimport com.google.android.material.navigation.NavigationView\n\nfun NavigationView.setItemIconColors(\n    @ColorInt normalColor: Int,\n    @ColorInt selectedColor: Int\n) {\n    val iconSl = ColorStateList(\n        arrayOf(\n            intArrayOf(-android.R.attr.state_checked),\n            intArrayOf(android.R.attr.state_checked)\n        ),\n        intArrayOf(normalColor, selectedColor)\n    )\n    itemIconTintList = iconSl\n}\n\nfun NavigationView.setItemTextColors(\n    @ColorInt normalColor: Int,\n    @ColorInt selectedColor: Int\n) {\n    val textSl = ColorStateList(\n        arrayOf(\n            intArrayOf(-android.R.attr.state_checked),\n            intArrayOf(android.R.attr.state_checked)\n        ),\n        intArrayOf(normalColor, selectedColor)\n    )\n    itemTextColor = textSl\n}\n\nfun NavigationView.disableScrollbar() {\n    val navigationMenuView = getChildAt(0) as? NavigationMenuView\n    navigationMenuView?.isVerticalScrollBarEnabled = false\n}\n\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/utils/NetworkUtils.kt",
    "content": "package io.legado.app.utils\n\nimport android.annotation.SuppressLint\nimport android.net.ConnectivityManager\nimport android.net.NetworkCapabilities\nimport android.os.Build\nimport cn.hutool.core.lang.Validator\nimport io.legado.app.constant.AppLog\nimport okhttp3.internal.publicsuffix.PublicSuffixDatabase\nimport splitties.systemservices.connectivityManager\nimport java.net.InetAddress\nimport java.net.NetworkInterface\nimport java.net.SocketException\nimport java.net.URL\nimport java.util.BitSet\nimport java.util.Enumeration\n\n@Suppress(\"unused\", \"MemberVisibilityCanBePrivate\")\nobject NetworkUtils {\n\n    /**\n     * 判断是否联网\n     */\n    @SuppressLint(\"ObsoleteSdkInt\")\n    @Suppress(\"DEPRECATION\")\n    fun isAvailable(): Boolean {\n        if (Build.VERSION.SDK_INT < 23) {\n            val mWiFiNetworkInfo = connectivityManager.activeNetworkInfo\n            if (mWiFiNetworkInfo != null) {\n                // WIFI\n                return mWiFiNetworkInfo.type == ConnectivityManager.TYPE_WIFI ||\n                        // 移动数据\n                        mWiFiNetworkInfo.type == ConnectivityManager.TYPE_MOBILE ||\n                        // 以太网\n                        mWiFiNetworkInfo.type == ConnectivityManager.TYPE_ETHERNET ||\n                        // VPN\n                        mWiFiNetworkInfo.type == ConnectivityManager.TYPE_VPN\n            }\n        } else {\n            val network = connectivityManager.activeNetwork\n            if (network != null) {\n                val nc = connectivityManager.getNetworkCapabilities(network)\n                if (nc != null) {\n                    // WIFI\n                    return nc.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) ||\n                            // 移动数据\n                            nc.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) ||\n                            // 以太网\n                            nc.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET) ||\n                            // VPN\n                            nc.hasTransport(NetworkCapabilities.TRANSPORT_VPN)\n                }\n            }\n        }\n        return false\n    }\n\n    private val notNeedEncodingQuery: BitSet by lazy {\n        val bitSet = BitSet(256)\n        for (i in 'a'.code..'z'.code) {\n            bitSet.set(i)\n        }\n        for (i in 'A'.code..'Z'.code) {\n            bitSet.set(i)\n        }\n        for (i in '0'.code..'9'.code) {\n            bitSet.set(i)\n        }\n        for (char in \"!$&()*+,-./:;=?@[\\\\]^_`{|}~\") {\n            bitSet.set(char.code)\n        }\n        return@lazy bitSet\n    }\n\n    private val notNeedEncodingForm: BitSet by lazy {\n        val bitSet = BitSet(256)\n        for (i in 'a'.code..'z'.code) {\n            bitSet.set(i)\n        }\n        for (i in 'A'.code..'Z'.code) {\n            bitSet.set(i)\n        }\n        for (i in '0'.code..'9'.code) {\n            bitSet.set(i)\n        }\n        for (char in \"*-._\") {\n            bitSet.set(char.code)\n        }\n        return@lazy bitSet\n    }\n\n    /**\n     * 支持JAVA的URLEncoder.encode出来的string做判断。 即: 将' '转成'+'\n     * 0-9a-zA-Z保留 <br></br>\n     * ! * ' ( ) ; : @ & = + $ , / ? # [ ] 保留\n     * 其他字符转成%XX的格式，X是16进制的大写字符，范围是[0-9A-F]\n     */\n    fun encodedQuery(str: String): Boolean {\n        var needEncode = false\n        var i = 0\n        while (i < str.length) {\n            val c = str[i]\n            if (notNeedEncodingQuery.get(c.code)) {\n                i++\n                continue\n            }\n            if (c == '%' && i + 2 < str.length) {\n                // 判断是否符合urlEncode规范\n                val c1 = str[++i]\n                val c2 = str[++i]\n                if (isDigit16Char(c1) && isDigit16Char(c2)) {\n                    i++\n                    continue\n                }\n            }\n            // 其他字符，肯定需要urlEncode\n            needEncode = true\n            break\n        }\n\n        return !needEncode\n    }\n\n    fun encodedForm(str: String): Boolean {\n        var needEncode = false\n        var i = 0\n        while (i < str.length) {\n            val c = str[i]\n            if (notNeedEncodingForm.get(c.code)) {\n                i++\n                continue\n            }\n            if (c == '%' && i + 2 < str.length) {\n                // 判断是否符合urlEncode规范\n                val c1 = str[++i]\n                val c2 = str[++i]\n                if (isDigit16Char(c1) && isDigit16Char(c2)) {\n                    i++\n                    continue\n                }\n            }\n            // 其他字符，肯定需要urlEncode\n            needEncode = true\n            break\n        }\n\n        return !needEncode\n    }\n\n    /**\n     * 判断c是否是16进制的字符\n     */\n    private fun isDigit16Char(c: Char): Boolean {\n        return c in '0'..'9' || c in 'A'..'F' || c in 'a'..'f'\n    }\n\n    /**\n     * 获取绝对地址\n     */\n    fun getAbsoluteURL(baseURL: String?, relativePath: String): String {\n        if (baseURL.isNullOrEmpty()) return relativePath.trim()\n        var absoluteUrl: URL? = null\n        try {\n            absoluteUrl = URL(baseURL.substringBefore(\",\"))\n        } catch (e: Exception) {\n            e.printOnDebug()\n        }\n        return getAbsoluteURL(absoluteUrl, relativePath)\n    }\n\n    /**\n     * 获取绝对地址\n     */\n    fun getAbsoluteURL(baseURL: URL?, relativePath: String): String {\n        val relativePathTrim = relativePath.trim()\n        if (baseURL == null) return relativePathTrim\n        if (relativePathTrim.isAbsUrl()) return relativePathTrim\n        if (relativePathTrim.isDataUrl()) return relativePathTrim\n        if (relativePathTrim.startsWith(\"javascript\")) return \"\"\n        var relativeUrl = relativePathTrim\n        try {\n            val parseUrl = URL(baseURL, relativePath)\n            relativeUrl = parseUrl.toString()\n            return relativeUrl\n        } catch (e: Exception) {\n            AppLog.put(\"网址拼接出错\\n${e.localizedMessage}\", e)\n        }\n        return relativeUrl\n    }\n\n    fun getBaseUrl(url: String?): String? {\n        url ?: return null\n        if (url.startsWith(\"http://\", true)\n            || url.startsWith(\"https://\", true)\n        ) {\n            val index = url.indexOf(\"/\", 9)\n            return if (index == -1) {\n                url\n            } else url.substring(0, index)\n        }\n        return null\n    }\n\n    /**\n     * 获取域名，供cookie保存和读取，处理失败返回传入的url\n     * http://1.2.3.4 => 1.2.3.4\n     * https://www.example.com =>  example.com\n     * http://www.biquge.com.cn => biquge.com.cn\n     * http://www.content.example.com => example.com\n     */\n    fun getSubDomain(url: String): String {\n        val baseUrl = getBaseUrl(url) ?: return url\n        return kotlin.runCatching {\n            val mURL = URL(baseUrl)\n            val host: String = mURL.host\n            //mURL.scheme https/http\n            //判断是否为ip\n            if (isIPAddress(host)) return host\n            //PublicSuffixDatabase处理域名\n            PublicSuffixDatabase.get().getEffectiveTldPlusOne(host) ?: host\n        }.getOrDefault(baseUrl)\n    }\n\n    fun getSubDomainOrNull(url: String): String? {\n        val baseUrl = getBaseUrl(url) ?: return null\n        return kotlin.runCatching {\n            val mURL = URL(baseUrl)\n            val host: String = mURL.host\n            //mURL.scheme https/http\n            //判断是否为ip\n            if (isIPAddress(host)) return host\n            //PublicSuffixDatabase处理域名\n            PublicSuffixDatabase.get().getEffectiveTldPlusOne(host) ?: host\n        }.getOrDefault(null)\n    }\n\n    fun getDomain(url: String): String {\n        val baseUrl = getBaseUrl(url) ?: return url\n        return kotlin.runCatching {\n            URL(baseUrl).host\n        }.getOrDefault(baseUrl)\n    }\n\n    /**\n     * Get local Ip address.\n     */\n    fun getLocalIPAddress(): List<InetAddress> {\n        val enumeration: Enumeration<NetworkInterface>\n        try {\n            enumeration = NetworkInterface.getNetworkInterfaces()\n        } catch (e: SocketException) {\n            e.printOnDebug()\n            return emptyList()\n        }\n\n        val addressList = mutableListOf<InetAddress>()\n\n        while (enumeration.hasMoreElements()) {\n            val nif = enumeration.nextElement()\n            val addresses = nif.inetAddresses ?: continue\n            while (addresses.hasMoreElements()) {\n                val address = addresses.nextElement()\n                if (!address.isLoopbackAddress && isIPv4Address(address.hostAddress)) {\n                    addressList.add(address)\n                }\n            }\n        }\n        return addressList\n    }\n\n    /**\n     * Check if valid IPV4 address.\n     *\n     * @param input the address string to check for validity.\n     * @return True if the input parameter is a valid IPv4 address.\n     */\n    fun isIPv4Address(input: String?): Boolean {\n        return input != null && input.isNotEmpty()\n                && input[0] in '1'..'9'\n                && input.count { it == '.' } == 3\n                && Validator.isIpv4(input)\n    }\n\n    /**\n     * Check if valid IPV6 address.\n     */\n    fun isIPv6Address(input: String?): Boolean {\n        return input != null && input.contains(\":\") && Validator.isIpv6(input)\n    }\n\n    /**\n     * Check if valid IP address.\n     */\n    fun isIPAddress(input: String?): Boolean {\n        return isIPv4Address(input) || isIPv6Address(input)\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/utils/PaintExtensions.kt",
    "content": "package io.legado.app.utils\n\nimport android.os.Build\nimport android.text.TextPaint\n\nval TextPaint.textHeight: Float\n    get() = fontMetrics.run { descent - ascent + leading }\n\nfun TextPaint.getTextWidthsCompat(text: String, widths: FloatArray) {\n    getTextWidths(text, widths)\n    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.VANILLA_ICE_CREAM) {\n        val letterSpacing = letterSpacing * textSize\n        val letterSpacingHalf = letterSpacing * 0.5f\n        for (i in widths.indices) {\n            if (widths[i] > 0) {\n                widths[i] += letterSpacingHalf\n                break\n            }\n        }\n        for (i in text.lastIndex downTo 0) {\n            if (widths[i] > 0) {\n                widths[i] += letterSpacingHalf\n                break\n            }\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/utils/ParcelFileDescriptorChannel.kt",
    "content": "package io.legado.app.utils\n\nimport android.annotation.SuppressLint\nimport android.os.ParcelFileDescriptor\nimport android.system.ErrnoException\nimport android.system.Os\nimport android.system.OsConstants\nimport java.io.IOException\nimport java.nio.ByteBuffer\nimport java.nio.channels.SeekableByteChannel\n\n@Suppress(\"unused\")\n@SuppressLint(\"NewApi\")\nclass ParcelFileDescriptorChannel(private val pfd: ParcelFileDescriptor) : SeekableByteChannel {\n    @Throws(IOException::class)\n    override fun read(dst: ByteBuffer): Int {\n        return try {\n            Os.read(pfd.fileDescriptor, dst)\n        } catch (e: ErrnoException) {\n            throw RuntimeException(e)\n        }\n    }\n\n    @Throws(IOException::class)\n    override fun write(src: ByteBuffer): Int {\n        return try {\n            Os.write(pfd.fileDescriptor, src)\n        } catch (e: ErrnoException) {\n            throw RuntimeException(e)\n        }\n    }\n\n    @Throws(IOException::class)\n    override fun position(): Long {\n        return try {\n            Os.lseek(pfd.fileDescriptor, 0, OsConstants.SEEK_CUR)\n        } catch (e: ErrnoException) {\n            throw RuntimeException(e)\n        }\n    }\n\n    @Throws(IOException::class)\n    override fun position(newPosition: Long): SeekableByteChannel {\n        try {\n            Os.lseek(pfd.fileDescriptor, newPosition, OsConstants.SEEK_SET)\n        } catch (e: ErrnoException) {\n            throw RuntimeException(e)\n        }\n        return this\n    }\n\n    @Throws(IOException::class)\n    override fun size(): Long {\n        return try {\n            Os.fstat(pfd.fileDescriptor).st_size\n        } catch (e: ErrnoException) {\n            throw RuntimeException(e)\n        }\n    }\n\n    @Throws(IOException::class)\n    override fun truncate(newSize: Long): SeekableByteChannel {\n        require(!(newSize < 0L || newSize > Int.MAX_VALUE)) { \"Size has to be in range 0.. \" + Int.MAX_VALUE }\n        try {\n            Os.ftruncate(pfd.fileDescriptor, newSize)\n        } catch (e: ErrnoException) {\n            throw RuntimeException(e)\n        }\n        return this\n    }\n\n    override fun isOpen(): Boolean {\n        return pfd.fileDescriptor.valid()\n    }\n\n    @Throws(IOException::class)\n    override fun close() {\n        pfd.close()\n    }\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/utils/PreferencesExtensions.kt",
    "content": "@file:Suppress(\"unused\")\n\npackage io.legado.app.utils\n\nimport android.annotation.SuppressLint\nimport android.app.Activity\nimport android.content.Context\nimport android.content.ContextWrapper\nimport android.content.SharedPreferences\nimport androidx.core.content.edit\nimport androidx.lifecycle.DefaultLifecycleObserver\nimport androidx.lifecycle.LifecycleOwner\nimport splitties.init.appCtx\nimport java.io.File\n\n/**\n * 获取自定义路径的SharedPreferences, 用反射生成 SharedPreferences\n * @param dir 目录路径\n * @param fileName 文件名,不需要 '.xml' 后缀\n * @return SharedPreferences\n */\n@SuppressLint(\"DiscouragedPrivateApi\")\nfun Context.getSharedPreferences(\n    dir: String,\n    fileName: String\n): SharedPreferences? {\n    try {\n        // 获取 ContextWrapper对象中的mBase变量。该变量保存了 ContextImpl 对象\n        val fieldMBase = ContextWrapper::class.java.getDeclaredField(\"mBase\")\n        fieldMBase.isAccessible = true\n        // 获取 mBase变量\n        val objMBase = fieldMBase.get(this)\n        // 获取 ContextImpl。mPreferencesDir变量，该变量保存了数据文件的保存路径\n        val fieldMPreferencesDir = objMBase.javaClass.getDeclaredField(\"mPreferencesDir\")\n        fieldMPreferencesDir.isAccessible = true\n        // 创建自定义路径\n        val file = File(dir)\n        // 修改mPreferencesDir变量的值\n        fieldMPreferencesDir.set(objMBase, file)\n        // 返回修改路径以后的 SharedPreferences :%FILE_PATH%/%fileName%.xml\n        return getSharedPreferences(fileName, Activity.MODE_PRIVATE)\n    } catch (e: NoSuchFieldException) {\n        e.printOnDebug()\n    } catch (e: IllegalArgumentException) {\n        e.printOnDebug()\n    } catch (e: IllegalAccessException) {\n        e.printOnDebug()\n    }\n    return null\n}\n\nfun SharedPreferences.getString(key: String): String? {\n    return getString(key, null)\n}\n\nfun SharedPreferences.putString(key: String, value: String) {\n    edit {\n        putString(key, value)\n    }\n}\n\nfun SharedPreferences.getBoolean(key: String): Boolean {\n    return getBoolean(key, false)\n}\n\nfun SharedPreferences.putBoolean(key: String, value: Boolean) {\n    edit {\n        putBoolean(key, value)\n    }\n}\n\nfun SharedPreferences.getInt(key: String): Int {\n    return getInt(key, 0)\n}\n\nfun SharedPreferences.putInt(key: String, value: Int) {\n    edit {\n        putInt(key, value)\n    }\n}\n\nfun SharedPreferences.getLong(key: String): Long {\n    return getLong(key, 0)\n}\n\nfun SharedPreferences.putLong(key: String, value: Long) {\n    edit {\n        putLong(key, value)\n    }\n}\n\nfun SharedPreferences.getFloat(key: String): Float {\n    return getFloat(key, 0f)\n}\n\nfun SharedPreferences.putFloat(key: String, value: Float) {\n    edit {\n        putFloat(key, value)\n    }\n}\n\nfun SharedPreferences.remove(key: String) {\n    edit {\n        remove(key)\n    }\n}\n\nfun LifecycleOwner.observeSharedPreferences(\n    prefs: SharedPreferences = appCtx.defaultSharedPreferences,\n    l: SharedPreferences.OnSharedPreferenceChangeListener\n) {\n    val observer = object : DefaultLifecycleObserver {\n        override fun onCreate(owner: LifecycleOwner) {\n            prefs.registerOnSharedPreferenceChangeListener(l)\n        }\n\n        override fun onDestroy(owner: LifecycleOwner) {\n            prefs.unregisterOnSharedPreferenceChangeListener(l)\n            lifecycle.removeObserver(this)\n        }\n\n        override fun onPause(owner: LifecycleOwner) {\n            prefs.unregisterOnSharedPreferenceChangeListener(l)\n        }\n\n        override fun onResume(owner: LifecycleOwner) {\n            prefs.registerOnSharedPreferenceChangeListener(l)\n        }\n    }\n    lifecycle.addObserver(observer)\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/utils/QRCodeUtils.kt",
    "content": "package io.legado.app.utils\n\nimport android.graphics.Bitmap\nimport android.graphics.BitmapFactory\nimport android.graphics.Canvas\nimport android.graphics.Color\nimport android.graphics.Paint\nimport android.text.TextPaint\nimport android.text.TextUtils\nimport androidx.annotation.ColorInt\nimport androidx.annotation.FloatRange\nimport com.google.zxing.BarcodeFormat\nimport com.google.zxing.BinaryBitmap\nimport com.google.zxing.DecodeHintType\nimport com.google.zxing.EncodeHintType\nimport com.google.zxing.LuminanceSource\nimport com.google.zxing.MultiFormatReader\nimport com.google.zxing.MultiFormatWriter\nimport com.google.zxing.RGBLuminanceSource\nimport com.google.zxing.Result\nimport com.google.zxing.WriterException\nimport com.google.zxing.common.GlobalHistogramBinarizer\nimport com.google.zxing.common.HybridBinarizer\nimport com.google.zxing.qrcode.QRCodeWriter\nimport com.google.zxing.qrcode.decoder.ErrorCorrectionLevel\nimport com.king.zxing.DecodeFormatManager\nimport java.util.EnumMap\nimport kotlin.math.max\n\n\n@Suppress(\"MemberVisibilityCanBePrivate\", \"unused\")\nobject QRCodeUtils {\n\n    const val DEFAULT_REQ_WIDTH = 480\n    const val DEFAULT_REQ_HEIGHT = 640\n\n    /**\n     * 生成二维码\n     * @param content 二维码的内容\n     * @param heightPix 二维码的高\n     * @param logo 二维码中间的logo\n     * @param ratio  logo所占比例 因为二维码的最大容错率为30%，所以建议ratio的范围小于0.3\n     * @param errorCorrectionLevel\n     */\n    fun createQRCode(\n        content: String,\n        heightPix: Int = DEFAULT_REQ_HEIGHT,\n        logo: Bitmap? = null,\n        @FloatRange(from = 0.0, to = 1.0) ratio: Float = 0.2f,\n        errorCorrectionLevel: ErrorCorrectionLevel = ErrorCorrectionLevel.H\n    ): Bitmap? {\n        //配置参数\n        val hints: MutableMap<EncodeHintType, Any> = EnumMap(EncodeHintType::class.java)\n        hints[EncodeHintType.CHARACTER_SET] = \"utf-8\"\n        //容错级别\n        hints[EncodeHintType.ERROR_CORRECTION] = errorCorrectionLevel\n        //设置空白边距的宽度\n        hints[EncodeHintType.MARGIN] = 1 //default is 4\n        return createQRCode(content, heightPix, logo, ratio, hints)\n    }\n\n    /**\n     * 生成二维码\n     * @param content 二维码的内容\n     * @param heightPix 二维码的高\n     * @param logo 二维码中间的logo\n     * @param ratio  logo所占比例 因为二维码的最大容错率为30%，所以建议ratio的范围小于0.3\n     * @param hints\n     * @param codeColor 二维码的颜色\n     * @return\n     */\n    fun createQRCode(\n        content: String?,\n        heightPix: Int,\n        logo: Bitmap?,\n        @FloatRange(from = 0.0, to = 1.0) ratio: Float = 0.2f,\n        hints: Map<EncodeHintType, *>,\n        codeColor: Int = Color.BLACK\n    ): Bitmap? {\n        try {\n            // 图像数据转换，使用了矩阵转换\n            val bitMatrix =\n                QRCodeWriter().encode(content, BarcodeFormat.QR_CODE, heightPix, heightPix, hints)\n            val pixels = IntArray(heightPix * heightPix)\n            // 下面这里按照二维码的算法，逐个生成二维码的图片，\n            // 两个for循环是图片横列扫描的结果\n            for (y in 0 until heightPix) {\n                for (x in 0 until heightPix) {\n                    if (bitMatrix[x, y]) {\n                        pixels[y * heightPix + x] = codeColor\n                    } else {\n                        pixels[y * heightPix + x] = Color.WHITE\n                    }\n                }\n            }\n\n            // 生成二维码图片的格式\n            var bitmap: Bitmap? = Bitmap.createBitmap(heightPix, heightPix, Bitmap.Config.ARGB_8888)\n            bitmap!!.setPixels(pixels, 0, heightPix, 0, 0, heightPix, heightPix)\n            if (logo != null) {\n                bitmap = addLogo(bitmap, logo, ratio)\n            }\n            return bitmap\n        } catch (e: WriterException) {\n            e.printOnDebug()\n        }\n        return null\n    }\n\n    /**\n     * 在二维码中间添加Logo图案\n     * @param src\n     * @param logo\n     * @param ratio  logo所占比例 因为二维码的最大容错率为30%，所以建议ratio的范围小于0.3\n     * @return\n     */\n    private fun addLogo(\n        src: Bitmap?,\n        logo: Bitmap?,\n        @FloatRange(from = 0.0, to = 1.0) ratio: Float\n    ): Bitmap? {\n        if (src == null) {\n            return null\n        }\n        if (logo == null) {\n            return src\n        }\n\n        //获取图片的宽高\n        val srcWidth = src.width\n        val srcHeight = src.height\n        val logoWidth = logo.width\n        val logoHeight = logo.height\n        if (srcWidth == 0 || srcHeight == 0) {\n            return null\n        }\n        if (logoWidth == 0 || logoHeight == 0) {\n            return src\n        }\n\n        //logo大小为二维码整体大小\n        val scaleFactor = srcWidth * ratio / logoWidth\n        var bitmap: Bitmap? = Bitmap.createBitmap(srcWidth, srcHeight, Bitmap.Config.ARGB_8888)\n        try {\n            val canvas = Canvas(bitmap!!)\n            canvas.drawBitmap(src, 0f, 0f, null)\n            canvas.scale(\n                scaleFactor,\n                scaleFactor,\n                (srcWidth / 2).toFloat(),\n                (srcHeight / 2).toFloat()\n            )\n            canvas.drawBitmap(\n                logo,\n                ((srcWidth - logoWidth) / 2).toFloat(),\n                ((srcHeight - logoHeight) / 2).toFloat(),\n                null\n            )\n            canvas.save()\n            canvas.restore()\n        } catch (e: Exception) {\n            bitmap = null\n            e.printOnDebug()\n        }\n        return bitmap\n    }\n\n    /**\n     * 解析一维码/二维码图片\n     * @param bitmap 解析的图片\n     * @param hints 解析编码类型\n     * @return\n     */\n    fun parseCode(\n        bitmap: Bitmap,\n        reqWidth: Int = DEFAULT_REQ_WIDTH,\n        reqHeight: Int = DEFAULT_REQ_HEIGHT,\n        hints: Map<DecodeHintType?, Any?> = DecodeFormatManager.ALL_HINTS\n    ): String? {\n        val result = parseCodeResult(bitmap, reqWidth, reqHeight, hints)\n        return result?.text\n    }\n\n    /**\n     * 解析一维码/二维码图片\n     * @param bitmap 解析的图片\n     * @param hints 解析编码类型\n     * @return\n     */\n    fun parseCodeResult(\n        bitmap: Bitmap,\n        reqWidth: Int = DEFAULT_REQ_WIDTH,\n        reqHeight: Int = DEFAULT_REQ_HEIGHT,\n        hints: Map<DecodeHintType?, Any?> = DecodeFormatManager.ALL_HINTS\n    ): Result? {\n        if (bitmap.width > reqWidth || bitmap.height > reqHeight) {\n            val bm = bitmap.resizeAndRecycle(reqWidth, reqHeight)\n            return parseCodeResult(getRGBLuminanceSource(bm), hints)\n        }\n        return parseCodeResult(getRGBLuminanceSource(bitmap), hints)\n    }\n\n    /**\n     * 解析一维码/二维码图片\n     * @param source\n     * @param hints\n     * @return\n     */\n    fun parseCodeResult(source: LuminanceSource?, hints: Map<DecodeHintType?, Any?>?): Result? {\n        var result: Result? = null\n        val reader = MultiFormatReader()\n        try {\n            reader.setHints(hints)\n            if (source != null) {\n                result = decodeInternal(reader, source)\n                if (result == null) {\n                    result = decodeInternal(reader, source.invert())\n                }\n                if (result == null && source.isRotateSupported) {\n                    result = decodeInternal(reader, source.rotateCounterClockwise())\n                }\n            }\n        } catch (e: java.lang.Exception) {\n            e.printOnDebug()\n        } finally {\n            reader.reset()\n        }\n        return result\n    }\n\n    /**\n     * 解析二维码图片\n     * @param bitmapPath 需要解析的图片路径\n     * @return\n     */\n    fun parseQRCode(bitmapPath: String?): String? {\n        val result = parseQRCodeResult(bitmapPath)\n        return result?.text\n    }\n\n    /**\n     * 解析二维码图片\n     * @param bitmapPath 需要解析的图片路径\n     * @param reqWidth 请求目标宽度，如果实际图片宽度大于此值，会自动进行压缩处理，当 reqWidth 和 reqHeight都小于或等于0时，则不进行压缩处理\n     * @param reqHeight 请求目标高度，如果实际图片高度大于此值，会自动进行压缩处理，当 reqWidth 和 reqHeight都小于或等于0时，则不进行压缩处理\n     * @return\n     */\n    fun parseQRCodeResult(\n        bitmapPath: String?,\n        reqWidth: Int = DEFAULT_REQ_WIDTH,\n        reqHeight: Int = DEFAULT_REQ_HEIGHT\n    ): Result? {\n        return parseCodeResult(bitmapPath, reqWidth, reqHeight, DecodeFormatManager.QR_CODE_HINTS)\n    }\n\n    /**\n     * 解析一维码/二维码图片\n     * @param bitmapPath 需要解析的图片路径\n     * @return\n     */\n    fun parseCode(\n        bitmapPath: String?,\n        reqWidth: Int = DEFAULT_REQ_WIDTH,\n        reqHeight: Int = DEFAULT_REQ_HEIGHT,\n        hints: Map<DecodeHintType?, Any?> = DecodeFormatManager.ALL_HINTS\n    ): String? {\n        return parseCodeResult(bitmapPath, reqWidth, reqHeight, hints)?.text\n    }\n\n    /**\n     * 解析一维码/二维码图片\n     * @param bitmapPath 需要解析的图片路径\n     * @param reqWidth 请求目标宽度，如果实际图片宽度大于此值，会自动进行压缩处理，当 reqWidth 和 reqHeight都小于或等于0时，则不进行压缩处理\n     * @param reqHeight 请求目标高度，如果实际图片高度大于此值，会自动进行压缩处理，当 reqWidth 和 reqHeight都小于或等于0时，则不进行压缩处理\n     * @param hints 解析编码类型\n     * @return\n     */\n    fun parseCodeResult(\n        bitmapPath: String?,\n        reqWidth: Int = DEFAULT_REQ_WIDTH,\n        reqHeight: Int = DEFAULT_REQ_HEIGHT,\n        hints: Map<DecodeHintType?, Any?> = DecodeFormatManager.ALL_HINTS\n    ): Result? {\n        var result: Result? = null\n        val reader = MultiFormatReader()\n        try {\n            reader.setHints(hints)\n            val source = getRGBLuminanceSource(compressBitmap(bitmapPath, reqWidth, reqHeight))\n            result = decodeInternal(reader, source)\n            if (result == null) {\n                result = decodeInternal(reader, source.invert())\n            }\n            if (result == null && source.isRotateSupported) {\n                result = decodeInternal(reader, source.rotateCounterClockwise())\n            }\n        } catch (e: Exception) {\n            e.printOnDebug()\n        } finally {\n            reader.reset()\n        }\n        return result\n    }\n\n    private fun decodeInternal(reader: MultiFormatReader, source: LuminanceSource): Result? {\n        var result: Result? = null\n        try {\n            try {\n                //采用HybridBinarizer解析\n                result = reader.decodeWithState(BinaryBitmap(HybridBinarizer(source)))\n            } catch (_: Exception) {\n            }\n            if (result == null) {\n                //如果没有解析成功，再采用GlobalHistogramBinarizer解析一次\n                result = reader.decodeWithState(BinaryBitmap(GlobalHistogramBinarizer(source)))\n            }\n        } catch (_: Exception) {\n        }\n        return result\n    }\n\n\n    /**\n     * 压缩图片\n     * @param path\n     * @return\n     */\n    private fun compressBitmap(path: String?, reqWidth: Int, reqHeight: Int): Bitmap {\n        if (reqWidth > 0 && reqHeight > 0) { //都大于进行判断是否压缩\n            val newOpts = BitmapFactory.Options()\n            // 开始读入图片，此时把options.inJustDecodeBounds 设回true了\n            newOpts.inJustDecodeBounds = true //获取原始图片大小\n            BitmapFactory.decodeFile(path, newOpts) // 此时返回bm为空\n            val width = newOpts.outWidth.toFloat()\n            val height = newOpts.outHeight.toFloat()\n            // 缩放比，由于是固定比例缩放，只用高或者宽其中一个数据进行计算即可\n            var wSize = 1 // wSize=1表示不缩放\n            if (width > reqWidth) { // 如果宽度大的话根据宽度固定大小缩放\n                wSize = (width / reqWidth).toInt()\n            }\n            var hSize = 1 // wSize=1表示不缩放\n            if (height > reqHeight) { // 如果高度高的话根据宽度固定大小缩放\n                hSize = (height / reqHeight).toInt()\n            }\n            var size = max(wSize, hSize)\n            if (size <= 0) size = 1\n            newOpts.inSampleSize = size // 设置缩放比例\n            // 重新读入图片，注意此时已经把options.inJustDecodeBounds 设回false了\n            newOpts.inJustDecodeBounds = false\n            return BitmapFactory.decodeFile(path, newOpts)\n        }\n        return BitmapFactory.decodeFile(path)\n    }\n\n\n    /**\n     * 获取RGBLuminanceSource\n     * @param bitmap\n     * @return\n     */\n    private fun getRGBLuminanceSource(bitmap: Bitmap): RGBLuminanceSource {\n        val width = bitmap.width\n        val height = bitmap.height\n        val pixels = IntArray(width * height)\n        bitmap.getPixels(pixels, 0, bitmap.width, 0, 0, bitmap.width, bitmap.height)\n        return RGBLuminanceSource(width, height, pixels)\n    }\n\n    /**\n     * 生成条形码\n     * @param content\n     * @param format\n     * @param desiredWidth\n     * @param desiredHeight\n     * @param hints\n     * @param isShowText\n     * @param textSize\n     * @param codeColor\n     * @return\n     */\n    fun createBarCode(\n        content: String?,\n        desiredWidth: Int,\n        desiredHeight: Int,\n        format: BarcodeFormat = BarcodeFormat.CODE_128,\n        hints: Map<EncodeHintType?, *>? = null,\n        isShowText: Boolean = true,\n        textSize: Int = 40,\n        @ColorInt codeColor: Int = Color.BLACK\n    ): Bitmap? {\n        if (TextUtils.isEmpty(content)) {\n            return null\n        }\n        val writer = MultiFormatWriter()\n        try {\n            val result = writer.encode(\n                content, format, desiredWidth,\n                desiredHeight, hints\n            )\n            val width = result.width\n            val height = result.height\n            val pixels = IntArray(width * height)\n            // All are 0, or black, by default\n            for (y in 0 until height) {\n                val offset = y * width\n                for (x in 0 until width) {\n                    pixels[offset + x] = if (result[x, y]) codeColor else Color.WHITE\n                }\n            }\n            val bitmap = Bitmap.createBitmap(\n                width, height,\n                Bitmap.Config.ARGB_8888\n            )\n            bitmap.setPixels(pixels, 0, width, 0, 0, width, height)\n            return if (isShowText) {\n                addCode(bitmap, content, textSize, codeColor, textSize / 2)\n            } else bitmap\n        } catch (e: WriterException) {\n            e.printOnDebug()\n        }\n        return null\n    }\n\n    /**\n     * 条形码下面添加文本信息\n     * @param src\n     * @param code\n     * @param textSize\n     * @param textColor\n     * @return\n     */\n    private fun addCode(\n        src: Bitmap?,\n        code: String?,\n        textSize: Int,\n        @ColorInt textColor: Int,\n        offset: Int\n    ): Bitmap? {\n        if (src == null) {\n            return null\n        }\n        if (TextUtils.isEmpty(code)) {\n            return src\n        }\n\n        //获取图片的宽高\n        val srcWidth = src.width\n        val srcHeight = src.height\n        if (srcWidth <= 0 || srcHeight <= 0) {\n            return null\n        }\n        var bitmap: Bitmap? = Bitmap.createBitmap(\n            srcWidth,\n            srcHeight + textSize + offset * 2,\n            Bitmap.Config.ARGB_8888\n        )\n        try {\n            val canvas = Canvas(bitmap!!)\n            canvas.drawBitmap(src, 0f, 0f, null)\n            val paint = TextPaint()\n            paint.textSize = textSize.toFloat()\n            paint.color = textColor\n            paint.textAlign = Paint.Align.CENTER\n            canvas.drawText(\n                code!!,\n                (srcWidth / 2).toFloat(),\n                (srcHeight + textSize / 2 + offset).toFloat(),\n                paint\n            )\n            canvas.save()\n            canvas.restore()\n        } catch (e: Exception) {\n            bitmap = null\n            e.printOnDebug()\n        }\n        return bitmap\n    }\n\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/utils/RandomColor.kt",
    "content": "package io.legado.app.utils\n\nimport android.graphics.Color\nimport java.util.*\n\n@Suppress(\"unused\")\nclass RandomColor(alpha: Int, lower: Int, upper: Int) {\n\n    constructor() : this(255, 80, 200)\n\n    private var alpha: Int = 0\n    private var lower: Int = 0\n    private var upper: Int = 0\n\n    init {\n        require(upper > lower) { \"must be lower < upper\" }\n        setAlpha(alpha)\n        setLower(lower)\n        setUpper(upper)\n    }\n\n    //随机数是前闭  后开\n    fun build(): Int {\n        val red = getLower() + Random().nextInt(getUpper() - getLower() + 1)\n        val green = getLower() + Random().nextInt(getUpper() - getLower() + 1)\n        val blue = getLower() + Random().nextInt(getUpper() - getLower() + 1)\n        return Color.argb(getAlpha(), red, green, blue)\n    }\n\n    private fun getAlpha(): Int {\n        return alpha\n    }\n\n    private fun setAlpha(alpha: Int) {\n        var alpha1 = alpha\n        if (alpha1 > 255) alpha1 = 255\n        if (alpha1 < 0) alpha1 = 0\n        this.alpha = alpha1\n    }\n\n    private fun getLower(): Int {\n        return lower\n    }\n\n    private fun setLower(lower: Int) {\n        var lower1 = lower\n        if (lower1 < 0) lower1 = 0\n        this.lower = lower1\n    }\n\n    private fun getUpper(): Int {\n        return upper\n    }\n\n    private fun setUpper(upper: Int) {\n        var upper1 = upper\n        if (upper1 > 255) upper1 = 255\n        this.upper = upper1\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/utils/RealPathUtil.kt",
    "content": "package io.legado.app.utils\n\nimport android.annotation.SuppressLint\nimport android.content.ContentUris\nimport android.content.Context\nimport android.database.Cursor\nimport android.net.Uri\nimport android.os.Build\nimport android.os.Environment\nimport android.provider.DocumentsContract\nimport android.provider.MediaStore\nimport androidx.core.provider.DocumentsContractCompat\n\nimport java.io.File\nimport java.io.FileInputStream\nimport java.io.FileOutputStream\nimport java.io.IOException\n\n@Suppress(\"unused\")\nobject RealPathUtil {\n    /**\n     * Method for return file path of Gallery image\n     * @return path of the selected image file from gallery\n     */\n    private var filePathUri: Uri? = null\n\n    fun getPath(context: Context, uri: Uri): String? {\n        //check here to KITKAT or new version\n        @SuppressLint(\"ObsoleteSdkInt\")\n        val isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT\n        filePathUri = uri\n        // DocumentProvider\n        if (isKitKat && DocumentsContract.isDocumentUri(context, uri)) { // ExternalStorageProvider\n            if (isExternalStorageDocument(uri)) {\n                val docId = DocumentsContract.getDocumentId(uri)\n                val split = docId.split(\":\")\n                val type = split[0]\n                if (\"primary\".equals(type, ignoreCase = true)) {\n                    return Environment.getExternalStorageDirectory().toString() + \"/\" + split[1]\n                }\n            } else if (isDownloadsDocument(uri)) {\n                val id = DocumentsContract.getDocumentId(uri)\n                val contentUri = ContentUris.withAppendedId(\n                    Uri.parse(\"content://downloads/public_downloads\"),\n                    java.lang.Long.valueOf(id)\n                )\n                //return getDataColumn(context, uri, null, null);\n                return getDataColumn(context, contentUri, null, null)\n            } else if (isMediaDocument(uri)) {\n                val docId = DocumentsContract.getDocumentId(uri)\n                val split = docId.split(\":\").toTypedArray()\n                val type = split[0]\n                var contentUri: Uri? = null\n                when (type) {\n                    \"image\" -> {\n                        contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI\n                    }\n\n                    \"video\" -> {\n                        contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI\n                    }\n\n                    \"audio\" -> {\n                        contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI\n                    }\n                }\n                val selection = \"_id=?\"\n                val selectionArgs = arrayOf(split[1])\n                return getDataColumn(context, contentUri, selection, selectionArgs)\n            }\n        } else if (DocumentsContractCompat.isTreeUri(uri)) {\n            if (isExternalStorageDocument(uri)) {\n                val docId = DocumentsContract.getTreeDocumentId(uri)\n                val split = docId.split(\":\")\n                val type = split[0]\n                if (\"primary\".equals(type, ignoreCase = true)) {\n                    return Environment.getExternalStorageDirectory().toString() + \"/\" + split[1]\n                }\n            }\n        } else if (\"content\".equals(uri.scheme, ignoreCase = true)) { // Return the remote address\n            return if (isGooglePhotosUri(uri)) uri.lastPathSegment\n            else getDataColumn(context, uri, null, null)\n        } else if (\"file\".equals(uri.scheme, ignoreCase = true)) {\n            return uri.path\n        }\n        return uri.path\n    }\n\n    fun getTreePath(uri: Uri): String? {\n        if (!DocumentsContractCompat.isTreeUri(uri) || !isExternalStorageDocument(uri)) {\n            return null\n        }\n        val docId = DocumentsContract.getTreeDocumentId(uri)\n        val split = docId.split(\":\")\n        if (split.size < 2) {\n            return null\n        }\n        val type = split[0]\n        if (\"primary\".equals(type, ignoreCase = true)) {\n            return Environment.getExternalStorageDirectory().toString() + \"/\" + split[1]\n        }\n        return null\n    }\n\n    /**\n     * Get the value of the data column for this Uri. This is useful for\n     * MediaStore Uris, and other file-based ContentProviders.\n     *\n     * @param context The context.\n     * @param uri The Uri to query.\n     * @param selection (Optional) Filter used in the query.\n     * @param selectionArgs (Optional) Selection arguments used in the query.\n     * @return The value of the _data column, which is typically a file path.\n     */\n    private fun getDataColumn(\n        context: Context, uri: Uri?, selection: String?,\n        selectionArgs: Array<String>?\n    ): String? {\n        var cursor: Cursor? = null\n        val column = \"_data\"\n        val projection = arrayOf(\n            column\n        )\n        try {\n            cursor =\n                context.contentResolver.query(uri!!, projection, selection, selectionArgs, null)\n            if (cursor != null && cursor.moveToFirst()) {\n                val index = cursor.getColumnIndexOrThrow(column)\n                return cursor.getString(index)\n            }\n        } catch (e: IllegalArgumentException) {\n            e.printOnDebug()\n            val file = File(context.cacheDir, \"tmp\")\n            val filePath = file.absolutePath\n            try {\n                return context.contentResolver.openFileDescriptor(filePathUri!!, \"r\")?.use {\n                    val fd = it.fileDescriptor\n                    FileInputStream(fd).use { fis ->\n                        FileOutputStream(filePath).use { fos ->\n                            fis.copyTo(fos)\n                        }\n                    }\n                    File(filePath).absolutePath\n                }\n            } catch (e: IOException) {\n                e.printStackTrace()\n            }\n        } finally {\n            cursor?.close()\n        }\n        return null\n    }\n\n    /**\n     * @param uri The Uri to check.\n     * @return Whether the Uri authority is ExternalStorageProvider.\n     */\n    private fun isExternalStorageDocument(uri: Uri): Boolean {\n        return \"com.android.externalstorage.documents\" == uri.authority\n    }\n\n    /**\n     * @param uri The Uri to check.\n     * @return Whether the Uri authority is DownloadsProvider.\n     */\n    private fun isDownloadsDocument(uri: Uri): Boolean {\n        return \"com.android.providers.downloads.documents\" == uri.authority\n    }\n\n    /**\n     * @param uri The Uri to check.\n     * @return Whether the Uri authority is MediaProvider.\n     */\n    private fun isMediaDocument(uri: Uri): Boolean {\n        return \"com.android.providers.media.documents\" == uri.authority\n    }\n\n    /**\n     * @param uri The Uri to check.\n     * @return Whether the Uri authority is Google Photos.\n     */\n    private fun isGooglePhotosUri(uri: Uri): Boolean {\n        return \"com.google.android.apps.photos.content\" == uri.authority\n    }\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/utils/RecyclerViewExtensions.kt",
    "content": "package io.legado.app.utils\n\nimport androidx.recyclerview.widget.LinearLayoutManager\nimport androidx.recyclerview.widget.RecyclerView\n\nfun RecyclerView.findCenterViewPosition(): Int {\n    return getChildAdapterPosition(\n        findChildViewUnder(width / 2f, height / 2f) ?: return RecyclerView.NO_POSITION\n    )\n}\n\nfun RecyclerView.findViewPosition(x: Float, y: Float): Int {\n    return getChildAdapterPosition(findChildViewUnder(x, y) ?: return RecyclerView.NO_POSITION)\n}\n\nfun RecyclerView.findFirstVisibleViewPosition(): Int {\n    var pos = -1\n    if (layoutManager is LinearLayoutManager) {\n        pos = (layoutManager as LinearLayoutManager).findFirstVisibleItemPosition()\n    }\n    return pos\n}\n\nfun RecyclerView.findLastVisibleViewPosition(): Int {\n    var pos = -1\n    if (layoutManager is LinearLayoutManager) {\n        pos = (layoutManager as LinearLayoutManager).findLastVisibleItemPosition()\n    }\n    return pos\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/utils/RegexExtensions.kt",
    "content": "package io.legado.app.utils\n\nimport com.script.ScriptBindings\nimport com.script.rhino.RhinoScriptEngine\nimport io.legado.app.exception.RegexTimeoutException\nimport io.legado.app.help.CrashHandler\nimport io.legado.app.help.coroutine.Coroutine\nimport kotlinx.coroutines.Dispatchers.IO\nimport kotlinx.coroutines.ExperimentalCoroutinesApi\nimport kotlinx.coroutines.launch\nimport kotlinx.coroutines.runBlocking\nimport kotlinx.coroutines.selects.onTimeout\nimport kotlinx.coroutines.selects.select\nimport kotlinx.coroutines.suspendCancellableCoroutine\nimport splitties.init.appCtx\nimport java.util.regex.Matcher\nimport kotlin.coroutines.resume\nimport kotlin.coroutines.resumeWithException\n\nprivate val handler by lazy { buildMainHandler() }\n\n/**\n * 带有超时检测的正则替换\n */\n@OptIn(ExperimentalCoroutinesApi::class)\nfun CharSequence.replace(regex: Regex, replacement: String, timeout: Long): String {\n    val charSequence = this@replace\n    val isJs = replacement.startsWith(\"@js:\")\n    val replacement1 = if (isJs) replacement.substring(4) else replacement\n    return runBlocking {\n        suspendCancellableCoroutine { block ->\n            Coroutine.async(executeContext = IO) {\n                val job = launch {\n                    try {\n                        val pattern = regex.toPattern()\n                        val matcher = pattern.matcher(charSequence)\n                        val stringBuffer = StringBuffer()\n                        while (matcher.find()) {\n                            if (isJs) {\n                                val jsResult = RhinoScriptEngine.run {\n                                    val bindings = ScriptBindings()\n                                    bindings[\"result\"] = matcher.group()\n                                    eval(replacement1, bindings)\n                                }.toString()\n                                val quotedResult = Matcher.quoteReplacement(jsResult)\n                                matcher.appendReplacement(stringBuffer, quotedResult)\n                            } else {\n                                matcher.appendReplacement(stringBuffer, replacement1)\n                            }\n                        }\n                        matcher.appendTail(stringBuffer)\n                        block.resume(stringBuffer.toString())\n                    } catch (e: Exception) {\n                        block.resumeWithException(e)\n                    }\n                }\n                select {\n                    job.onJoin {}\n                    onTimeout(timeout) {\n                        val timeoutMsg =\n                            \"替换超时,3秒后还未结束将重启应用\\n替换规则$regex\\n替换内容:$charSequence\"\n                        val exception = RegexTimeoutException(timeoutMsg)\n                        block.cancel(exception)\n                        appCtx.longToastOnUi(timeoutMsg)\n                        CrashHandler.saveCrashInfo2File(exception)\n                        select {\n                            job.onJoin {}\n                            onTimeout(3000) {\n                                appCtx.restart()\n                            }\n                        }\n                    }\n                }\n            }\n        }\n    }\n}\n\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/utils/RequestManagerExtensions.kt",
    "content": "package io.legado.app.utils\n\nimport androidx.lifecycle.DefaultLifecycleObserver\nimport androidx.lifecycle.Lifecycle\nimport androidx.lifecycle.LifecycleOwner\nimport com.bumptech.glide.Glide\nimport com.bumptech.glide.RequestManager\nimport splitties.init.appCtx\n\nprivate val applicationRequestManager by lazy {\n    Glide.with(appCtx)\n}\n\nfun RequestManager.lifecycle(lifecycle: Lifecycle): RequestManager {\n    if (this === applicationRequestManager) {\n        return this\n    }\n\n    val observer = object : DefaultLifecycleObserver {\n        override fun onResume(owner: LifecycleOwner) = onStart()\n        override fun onPause(owner: LifecycleOwner) = onStop()\n        override fun onDestroy(owner: LifecycleOwner) {\n            owner.lifecycle.removeObserver(this)\n        }\n    }\n\n    lifecycle.addObserver(observer)\n\n    return this\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/utils/Snackbars.kt",
    "content": "@file:Suppress(\"unused\")\n\npackage io.legado.app.utils\n\nimport android.view.View\nimport androidx.annotation.StringRes\nimport com.google.android.material.snackbar.Snackbar\n\n/**\n * Display the Snackbar with the [Snackbar.LENGTH_SHORT] duration.\n *\n * @param message the message text resource.\n */\n@JvmName(\"snackbar2\")\nfun View.snackbar(\n    @StringRes message: Int\n) = Snackbar\n    .make(this, message, Snackbar.LENGTH_SHORT)\n    .apply { show() }\n\n/**\n * Display Snackbar with the [Snackbar.LENGTH_LONG] duration.\n *\n * @param message the message text resource.\n */\n@JvmName(\"longSnackbar2\")\nfun View.longSnackbar(\n    @StringRes message: Int\n) = Snackbar\n    .make(this, message, Snackbar.LENGTH_LONG)\n    .apply { show() }\n\n/**\n * Display Snackbar with the [Snackbar.LENGTH_INDEFINITE] duration.\n *\n * @param message the message text resource.\n */\n@JvmName(\"indefiniteSnackbar2\")\nfun View.indefiniteSnackbar(\n    @StringRes message: Int\n) = Snackbar\n    .make(this, message, Snackbar.LENGTH_INDEFINITE)\n    .apply { show() }\n\n/**\n * Display the Snackbar with the [Snackbar.LENGTH_SHORT] duration.\n *\n * @param message the message text.\n */\n@JvmName(\"snackbar2\")\nfun View.snackbar(\n    message: CharSequence\n) = Snackbar\n    .make(this, message, Snackbar.LENGTH_SHORT)\n    .apply { show() }\n\n/**\n * Display Snackbar with the [Snackbar.LENGTH_LONG] duration.\n *\n * @param message the message text.\n */\n@JvmName(\"longSnackbar2\")\nfun View.longSnackbar(\n    message: CharSequence\n) = Snackbar\n    .make(this, message, Snackbar.LENGTH_LONG)\n    .apply { show() }\n\n/**\n * Display Snackbar with the [Snackbar.LENGTH_INDEFINITE] duration.\n *\n * @param message the message text.\n */\n@JvmName(\"indefiniteSnackbar2\")\nfun View.indefiniteSnackbar(\n    message: CharSequence\n) = Snackbar\n    .make(this, message, Snackbar.LENGTH_INDEFINITE)\n    .apply { show() }\n\n/**\n * Display the Snackbar with the [Snackbar.LENGTH_SHORT] duration.\n *\n * @param message the message text resource.\n */\n@JvmName(\"snackbar2\")\nfun View.snackbar(\n    message: Int,\n    @StringRes actionText:\n    Int, action: (View) -> Unit\n) = Snackbar\n    .make(this, message, Snackbar.LENGTH_SHORT)\n    .setAction(actionText, action)\n    .apply { show() }\n\n/**\n * Display Snackbar with the [Snackbar.LENGTH_LONG] duration.\n *\n * @param message the message text resource.\n */\n@JvmName(\"longSnackbar2\")\nfun View.longSnackbar(\n    @StringRes message: Int,\n    @StringRes actionText: Int,\n    action: (View) -> Unit\n) = Snackbar\n    .make(this, message, Snackbar.LENGTH_LONG)\n    .setAction(actionText, action)\n    .apply { show() }\n\n/**\n * Display Snackbar with the [Snackbar.LENGTH_INDEFINITE] duration.\n *\n * @param message the message text resource.\n */\n@JvmName(\"indefiniteSnackbar2\")\nfun View.indefiniteSnackbar(\n    @StringRes message: Int,\n    @StringRes actionText: Int,\n    action: (View) -> Unit\n) = Snackbar\n    .make(this, message, Snackbar.LENGTH_INDEFINITE)\n    .setAction(actionText, action)\n    .apply { show() }\n\n/**\n * Display the Snackbar with the [Snackbar.LENGTH_SHORT] duration.\n *\n * @param message the message text.\n */\n@JvmName(\"snackbar2\")\nfun View.snackbar(\n    message: CharSequence,\n    actionText: CharSequence,\n    action: (View) -> Unit\n) = Snackbar\n    .make(this, message, Snackbar.LENGTH_SHORT)\n    .setAction(actionText, action)\n    .apply { show() }\n\n/**\n * Display Snackbar with the [Snackbar.LENGTH_LONG] duration.\n *\n * @param message the message text.\n */\n@JvmName(\"longSnackbar2\")\nfun View.longSnackbar(\n    message: CharSequence,\n    actionText: CharSequence,\n    action: (View) -> Unit\n) = Snackbar\n    .make(this, message, Snackbar.LENGTH_LONG)\n    .setAction(actionText, action)\n    .apply { show() }\n\n/**\n * Display Snackbar with the [Snackbar.LENGTH_INDEFINITE] duration.\n *\n * @param message the message text.\n */\n@JvmName(\"indefiniteSnackbar2\")\nfun View.indefiniteSnackbar(\n    message: CharSequence,\n    actionText: CharSequence,\n    action: (View) -> Unit\n) = Snackbar\n    .make(this, message, Snackbar.LENGTH_INDEFINITE)\n    .setAction(actionText, action)\n    .apply { show() }"
  },
  {
    "path": "app/src/main/java/io/legado/app/utils/StringExtensions.kt",
    "content": "@file:Suppress(\"unused\")\n\npackage io.legado.app.utils\n\nimport android.annotation.SuppressLint\nimport android.icu.text.Collator\nimport android.icu.util.ULocale\nimport android.net.Uri\nimport android.text.Editable\nimport cn.hutool.core.net.URLEncodeUtil\nimport io.legado.app.constant.AppPattern\nimport io.legado.app.constant.AppPattern.dataUriRegex\nimport java.io.File\nimport java.lang.Character.codePointCount\nimport java.lang.Character.offsetByCodePoints\nimport java.util.Locale\nimport java.util.regex.Pattern\n\nfun String?.safeTrim() = if (this.isNullOrBlank()) null else this.trim()\n\nfun String?.isContentScheme(): Boolean = this?.startsWith(\"content://\") == true\n\nfun String.toEditable(): Editable = Editable.Factory.getInstance().newEditable(this)\n\nfun String.parseToUri(): Uri {\n    return if (isUri()) Uri.parse(this) else {\n        Uri.fromFile(File(this))\n    }\n}\n\nfun String?.isUri(): Boolean {\n    this ?: return false\n    return this.startsWith(\"file://\", true) || isContentScheme()\n}\n\nfun String?.isAbsUrl() =\n    this?.let {\n        it.startsWith(\"http://\", true) || it.startsWith(\"https://\", true)\n    } ?: false\n\nfun String?.isDataUrl() =\n    this?.let {\n        dataUriRegex.matches(it)\n    } ?: false\n\nfun String?.isJson(): Boolean =\n    this?.run {\n        val str = this.trim()\n        when {\n            str.startsWith(\"{\") && str.endsWith(\"}\") -> true\n            str.startsWith(\"[\") && str.endsWith(\"]\") -> true\n            else -> false\n        }\n    } ?: false\n\nfun String?.isJsonObject(): Boolean =\n    this?.run {\n        val str = this.trim()\n        str.startsWith(\"{\") && str.endsWith(\"}\")\n    } ?: false\n\nfun String?.isJsonArray(): Boolean =\n    this?.run {\n        val str = this.trim()\n        str.startsWith(\"[\") && str.endsWith(\"]\")\n    } ?: false\n\nfun String?.isXml(): Boolean =\n    this?.run {\n        val str = this.trim()\n        str.startsWith(\"<\") && str.endsWith(\">\")\n    } ?: false\n\nfun String?.isTrue(nullIsTrue: Boolean = false): Boolean {\n    if (this.isNullOrBlank() || this == \"null\") {\n        return nullIsTrue\n    }\n    return !this.trim().matches(\"(?i)^(false|no|not|0)$\".toRegex())\n}\n\nfun String.isHex(): Boolean {\n    return all {c ->\n        c in '0'..'9' || c in 'A'..'F' || c in 'a'..'f'\n    }\n}\n\nfun String.splitNotBlank(vararg delimiter: String, limit: Int = 0): Array<String> = run {\n    this.split(*delimiter, limit = limit).map { it.trim() }.filterNot { it.isBlank() }\n        .toTypedArray()\n}\n\nfun String.splitNotBlank(regex: Regex, limit: Int = 0): Array<String> = run {\n    this.split(regex, limit).map { it.trim() }.filterNot { it.isBlank() }.toTypedArray()\n}\n\n@SuppressLint(\"ObsoleteSdkInt\")\nfun String.cnCompare(other: String): Int {\n    return if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.N) {\n        Collator.getInstance(ULocale.SIMPLIFIED_CHINESE).compare(this, other)\n    } else {\n        java.text.Collator.getInstance(Locale.CHINA).compare(this, other)\n    }\n}\n\n/**\n * 字符串所占内存大小\n */\nfun String?.memorySize(): Int {\n    this ?: return 0\n    return 40 + 2 * length\n}\n\n/**\n * 是否中文\n */\nfun String.isChinese(): Boolean {\n    val p = Pattern.compile(\"[\\u4e00-\\u9fa5]\")\n    val m = p.matcher(this)\n    return m.find()\n}\n\n/**\n * 将字符串拆分为单个字符,包含emoji\n */\nfun CharSequence.toStringArray(): Array<String> {\n    var codePointIndex = 0\n    return try {\n        Array(codePointCount(this, 0, length)) {\n            val start = codePointIndex\n            codePointIndex = offsetByCodePoints(this, start, 1)\n            substring(start, codePointIndex)\n        }\n    } catch (e: Exception) {\n        split(\"\").toTypedArray()\n    }\n}\n\nfun String.escapeRegex(): String {\n    return replace(AppPattern.regexCharRegex, \"\\\\\\\\$0\")\n}\n\nfun String.encodeURI(): String = URLEncodeUtil.encodeQuery(this)\n\nfun String.normalizeFileName(): String {\n    return replace(AppPattern.fileNameRegex2, \"_\")\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/utils/StringUtils.kt",
    "content": "package io.legado.app.utils\n\nimport android.annotation.SuppressLint\nimport android.text.TextUtils.isEmpty\nimport android.util.Base64\nimport java.io.ByteArrayInputStream\nimport java.io.ByteArrayOutputStream\nimport java.io.IOException\nimport java.text.DecimalFormat\nimport java.text.SimpleDateFormat\nimport java.util.Calendar\nimport java.util.Locale\nimport java.util.regex.Matcher\nimport java.util.regex.Pattern\nimport java.util.zip.GZIPInputStream\nimport java.util.zip.GZIPOutputStream\nimport kotlin.math.abs\n\n\n@Suppress(\"unused\", \"MemberVisibilityCanBePrivate\")\nobject StringUtils {\n    private const val HOUR_OF_DAY = 24\n    private const val DAY_OF_YESTERDAY = 2\n    private const val TIME_UNIT = 60\n    private val ChnMap = chnMap\n    private val wordCountFormatter by lazy {\n        DecimalFormat(\"#.#\")\n    }\n\n    private val chnMap: HashMap<Char, Int>\n        get() {\n            val map = HashMap<Char, Int>()\n            var cnStr = \"零一二三四五六七八九十\"\n            var c = cnStr.toCharArray()\n            for (i in 0..10) {\n                map[c[i]] = i\n            }\n            cnStr = \"〇壹贰叁肆伍陆柒捌玖拾\"\n            c = cnStr.toCharArray()\n            for (i in 0..10) {\n                map[c[i]] = i\n            }\n            map['两'] = 2\n            map['百'] = 100\n            map['佰'] = 100\n            map['千'] = 1000\n            map['仟'] = 1000\n            map['万'] = 10000\n            map['亿'] = 100000000\n            return map\n        }\n\n    /**\n     * 将日期转换成昨天、今天、明天\n     */\n    fun dateConvert(source: String, pattern: String): String {\n        val format = SimpleDateFormat(pattern, Locale.getDefault())\n        val calendar = Calendar.getInstance()\n        kotlin.runCatching {\n            val date = format.parse(source) ?: return \"\"\n            val curTime = calendar.timeInMillis\n            calendar.time = date\n            //将MISC 转换成 sec\n            val difSec = abs((curTime - date.time) / 1000)\n            val difMin = difSec / 60\n            val difHour = difMin / 60\n            val difDate = difHour / 60\n            val oldHour = calendar.get(Calendar.HOUR)\n            //如果没有时间\n            if (oldHour == 0) {\n                //比日期:昨天今天和明天\n                return when {\n                    difDate == 0L -> \"今天\"\n                    difDate < DAY_OF_YESTERDAY -> \"昨天\"\n                    else -> {\n                        @SuppressLint(\"SimpleDateFormat\")\n                        val convertFormat = SimpleDateFormat(\"yyyy-MM-dd\")\n                        convertFormat.format(date)\n                    }\n                }\n            }\n\n            return when {\n                difSec < TIME_UNIT -> difSec.toString() + \"秒前\"\n                difMin < TIME_UNIT -> difMin.toString() + \"分钟前\"\n                difHour < HOUR_OF_DAY -> difHour.toString() + \"小时前\"\n                difDate < DAY_OF_YESTERDAY -> \"昨天\"\n                else -> {\n                    @SuppressLint(\"SimpleDateFormat\")\n                    val convertFormat = SimpleDateFormat(\"yyyy-MM-dd\")\n                    convertFormat.format(date)\n                }\n            }\n        }.onFailure {\n            it.printOnDebug()\n        }\n        return \"\"\n    }\n\n    /**\n     * 首字母大写\n     */\n    @SuppressLint(\"DefaultLocale\")\n    fun toFirstCapital(str: String): String {\n        return str.substring(0, 1).uppercase(Locale.getDefault()) + str.substring(1)\n    }\n\n    /**\n     * 将文本中的半角字符，转换成全角字符\n     */\n    fun halfToFull(input: String): String {\n        val c = input.toCharArray()\n        for (i in c.indices) {\n            if (c[i].code == 32)\n            //半角空格\n            {\n                c[i] = 12288.toChar()\n                continue\n            }\n            //根据实际情况，过滤不需要转换的符号\n            //if (c[i] == 46) //半角点号，不转换\n            // continue;\n\n            if (c[i].code in 33..126)\n            //其他符号都转换为全角\n                c[i] = (c[i].code + 65248).toChar()\n        }\n        return String(c)\n    }\n\n    /**\n     * 字符串全角转换为半角\n     */\n    fun fullToHalf(input: String): String {\n        val c = input.toCharArray()\n        for (i in c.indices) {\n            if (c[i].code == 12288)\n            //全角空格\n            {\n                c[i] = 32.toChar()\n                continue\n            }\n\n            if (c[i].code in 65281..65374)\n                c[i] = (c[i].code - 65248).toChar()\n        }\n        return String(c)\n    }\n\n    /**\n     * 中文大写数字转数字\n     */\n    fun chineseNumToInt(chNum: String): Int {\n        var result = 0\n        var tmp = 0\n        var billion = 0\n        val cn = chNum.toCharArray()\n\n        // \"一零二五\" 形式\n        if (cn.size > 1 && chNum.matches(\"^[〇零一二三四五六七八九壹贰叁肆伍陆柒捌玖]$\".toRegex())) {\n            for (i in cn.indices) {\n                cn[i] = (48 + ChnMap[cn[i]]!!).toChar()\n            }\n            return Integer.parseInt(String(cn))\n        }\n\n        // \"一千零二十五\", \"一千二\" 形式\n        return kotlin.runCatching {\n            for (i in cn.indices) {\n                val tmpNum = ChnMap[cn[i]]!!\n                when {\n                    tmpNum == 100000000 -> {\n                        result += tmp\n                        result *= tmpNum\n                        billion = billion * 100000000 + result\n                        result = 0\n                        tmp = 0\n                    }\n\n                    tmpNum == 10000 -> {\n                        result += tmp\n                        result *= tmpNum\n                        tmp = 0\n                    }\n\n                    tmpNum >= 10 -> {\n                        if (tmp == 0)\n                            tmp = 1\n                        result += tmpNum * tmp\n                        tmp = 0\n                    }\n\n                    else -> {\n                        tmp = if (i >= 2 && i == cn.size - 1 && ChnMap[cn[i - 1]]!! > 10)\n                            tmpNum * ChnMap[cn[i - 1]]!! / 10\n                        else\n                            tmp * 10 + tmpNum\n                    }\n                }\n            }\n            result += tmp + billion\n            result\n        }.getOrDefault(-1)\n    }\n\n    /**\n     * 字符串转数字\n     */\n    fun stringToInt(str: String?): Int {\n        if (str != null) {\n            val num = fullToHalf(str).replace(\"\\\\s+\".toRegex(), \"\")\n            return kotlin.runCatching {\n                Integer.parseInt(num)\n            }.getOrElse {\n                chineseNumToInt(num)\n            }\n        }\n        return -1\n    }\n\n    /**\n     * 是否包含数字\n     */\n    fun isContainNumber(company: String): Boolean {\n        val p = Pattern.compile(\"[0-9]+\")\n        val m = p.matcher(company)\n        return m.find()\n    }\n\n    /**\n     * 是否数字\n     */\n    fun isNumeric(str: String): Boolean {\n        val pattern = Pattern.compile(\"-?[0-9]+\")\n        val isNum = pattern.matcher(str)\n        return isNum.matches()\n    }\n\n    fun wordCountFormat(words: Int): String {\n        var wordsS = \"\"\n        if (words > 0) {\n            if (words > 10000) {\n                val df = wordCountFormatter\n                wordsS = df.format(words * 1.0f / 10000f.toDouble()) + \"万字\"\n            } else {\n                wordsS = words.toString() + \"字\"\n            }\n        }\n        return wordsS\n    }\n\n    fun wordCountFormat(wc: String?): String {\n        if (wc == null) return \"\"\n        var wordsS = \"\"\n        if (isNumeric(wc)) {\n            val words: Int = wc.toInt()\n            if (words > 0) {\n                if (words > 10000) {\n                    val df = wordCountFormatter\n                    wordsS = df.format(words * 1.0f / 10000f.toDouble()) + \"万字\"\n                } else {\n                    wordsS = words.toString() + \"字\"\n                }\n            }\n        } else {\n            wordsS = wc\n        }\n        return wordsS\n    }\n\n    /**\n     * 移除字符串首尾空字符的高效方法(利用ASCII值判断,包括全角空格)\n     */\n    fun trim(s: String): String {\n        if (isEmpty(s)) return \"\"\n        var start = 0\n        val len = s.length\n        var end = len - 1\n        while (start < end && (s[start].code <= 0x20 || s[start] == '　')) {\n            ++start\n        }\n        while (start < end && (s[end].code <= 0x20 || s[end] == '　')) {\n            --end\n        }\n        ++end\n        return if (start > 0 || end < len) s.substring(start, end) else s\n    }\n\n    /**\n     * 重复字符串\n     */\n    fun repeat(str: String, n: Int): String {\n        val stringBuilder = StringBuilder()\n        for (i in 0 until n) {\n            stringBuilder.append(str)\n        }\n        return stringBuilder.toString()\n    }\n\n    /**\n     * 移除UTF头\n     */\n    fun removeUTFCharacters(data: String?): String? {\n        if (data == null) return null\n        val p = Pattern.compile(\"\\\\\\\\u(\\\\p{XDigit}{4})\")\n        val m = p.matcher(data)\n        val buf = StringBuffer(data.length)\n        while (m.find()) {\n            val ch = Integer.parseInt(m.group(1)!!, 16).toChar().toString()\n            m.appendReplacement(buf, Matcher.quoteReplacement(ch))\n        }\n        m.appendTail(buf)\n        return buf.toString()\n    }\n\n    /**\n     * 压缩字符串\n     */\n    fun compress(str: String): Result<String> {\n        return kotlin.runCatching {\n            if (str.isEmpty()) {\n                return@runCatching str\n            }\n            val out = ByteArrayOutputStream()\n            var gzip: GZIPOutputStream? = null\n            return@runCatching try {\n                gzip = GZIPOutputStream(out)\n                gzip.write(str.toByteArray())\n                Base64.encodeToString(out.toByteArray(), Base64.NO_WRAP)\n            } finally {\n                gzip?.runCatching {\n                    close()\n                }\n                out.runCatching {\n                    close()\n                }\n            }\n        }\n    }\n\n    /**\n     * 解压字符串\n     */\n    @Throws(IOException::class)\n    fun unCompress(str: String): Result<String> {\n        return kotlin.runCatching {\n            val outputStream = ByteArrayOutputStream()\n            var inputStream: ByteArrayInputStream? = null\n            var ginZip: GZIPInputStream? = null\n            return@runCatching try {\n                val compressed = Base64.decode(str, Base64.NO_WRAP)\n                inputStream = ByteArrayInputStream(compressed)\n                ginZip = GZIPInputStream(inputStream)\n                ginZip.copyTo(outputStream)\n                outputStream.toString()\n            } finally {\n                ginZip?.runCatching {\n                    close()\n                }\n                inputStream?.runCatching {\n                    close()\n                }\n                outputStream.runCatching {\n                    close()\n                }\n            }\n        }\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/utils/SvgUtils.kt",
    "content": "package io.legado.app.utils\n\nimport android.graphics.Canvas\nimport android.graphics.Bitmap\nimport android.graphics.RectF\nimport android.util.Size\nimport java.io.FileInputStream\nimport java.io.InputStream\nimport com.caverock.androidsvg.SVG\nimport kotlin.math.max\n\n@Suppress(\"WeakerAccess\", \"MemberVisibilityCanBePrivate\")\nobject SvgUtils {\n\n    /**\n     * 从Svg中解码bitmap\n     */\n    \n    fun createBitmap(filePath: String, width: Int, height: Int? = null): Bitmap? {\n        return kotlin.runCatching {\n            val inputStream = FileInputStream(filePath)\n            createBitmap(inputStream, width, height)\n        }.getOrNull()\n    }\n\n    fun createBitmap(inputStream: InputStream, width: Int, height: Int? = null): Bitmap? {\n        return kotlin.runCatching {\n            val svg = SVG.getFromInputStream(inputStream)\n            createBitmap(svg, width, height)\n        }.getOrNull()\n    }\n\n    //获取svg图片大小\n    fun getSize(filePath: String): Size? {\n        return kotlin.runCatching {\n            val inputStream = FileInputStream(filePath)\n            getSize(inputStream)\n        }.getOrNull()\n    }\n\n    fun getSize(inputStream: InputStream): Size? {\n        return kotlin.runCatching {\n            val svg = SVG.getFromInputStream(inputStream)\n            getSize(svg)\n        }.getOrNull()\n    }\n\n    /////// private method\n    private fun createBitmap(svg: SVG, width: Int? = null, height: Int? = null): Bitmap {\n        val size = getSize(svg)\n        val wRatio = width?.let { size.width / it } ?: -1\n        val hRatio = height?.let { size.height / it } ?: -1\n        //如果超出指定大小，则缩小相应的比例\n        val ratio = when {\n            wRatio > 1 && hRatio > 1 -> max(wRatio, hRatio)\n            wRatio > 1 -> wRatio\n            hRatio > 1 -> hRatio\n            else -> 1\n        }\n\n        val viewBox: RectF? = svg.documentViewBox\n        if (viewBox == null && size.width > 0 && size.height > 0) {\n            svg.setDocumentViewBox(0f, 0f, svg.documentWidth, svg.documentHeight)\n        }\n\n        svg.setDocumentWidth(\"100%\")\n        svg.setDocumentHeight(\"100%\")\n\n        val bitmapWidth = size.width / ratio\n        val bitmapHeight = size.height / ratio\n        val bitmap = Bitmap.createBitmap(bitmapWidth, bitmapHeight, Bitmap.Config.ARGB_8888)\n\n        svg.renderToCanvas(Canvas(bitmap))\n        return bitmap\n    }\n\n    private fun getSize(svg: SVG): Size {\n        val width = svg.documentWidth.toInt().takeIf { it > 0 }\n            ?: (svg.documentViewBox.right - svg.documentViewBox.left).toInt()\n        val height = svg.documentHeight.toInt().takeIf { it > 0 }\n            ?: (svg.documentViewBox.bottom - svg.documentViewBox.top).toInt()\n        return Size(width, height)      \n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/utils/SyncedRenderer.kt",
    "content": "package io.legado.app.utils\n\nimport android.view.Choreographer\n\nclass SyncedRenderer(val doFrame: (frameTime: Double) -> Unit) {\n\n    private var callback: (Long) -> Unit = {}\n\n    fun start() {\n        var currTime = System.nanoTime() / 1000000.0\n        callback = {\n            val currTimeMs = it / 1000000.0\n            val frameTime = currTimeMs - currTime\n            currTime = currTimeMs\n            doFrame(frameTime)\n            Choreographer.getInstance().postFrameCallback(callback)\n        }\n        Choreographer.getInstance().postFrameCallback(callback)\n    }\n\n    fun stop() {\n        Choreographer.getInstance().removeFrameCallback(callback)\n        callback = {}\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/utils/SystemUtils.kt",
    "content": "package io.legado.app.utils\n\nimport android.annotation.SuppressLint\nimport android.app.Activity\nimport android.content.Intent\nimport android.net.Uri\nimport android.provider.Settings\nimport android.view.Display\nimport splitties.init.appCtx\nimport splitties.systemservices.displayManager\nimport splitties.systemservices.powerManager\n\n\n@Suppress(\"unused\")\nobject SystemUtils {\n\n    @SuppressLint(\"ObsoleteSdkInt\")\n    fun ignoreBatteryOptimization(activity: Activity) {\n        if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.M) return\n\n        val hasIgnored = powerManager.isIgnoringBatteryOptimizations(activity.packageName)\n        //  判断当前APP是否有加入电池优化的白名单，如果没有，弹出加入电池优化的白名单的设置对话框。\n        if (!hasIgnored) {\n            try {\n                @SuppressLint(\"BatteryLife\")\n                val intent = Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS)\n                intent.data = Uri.parse(\"package:\" + activity.packageName)\n                activity.startActivity(intent)\n            } catch (ignored: Throwable) {\n            }\n\n        }\n    }\n\n    fun isScreenOn(): Boolean {\n        return displayManager.displays.filterNotNull().any {\n            it.state != Display.STATE_OFF\n        }\n    }\n\n    /**\n     * 屏幕像素宽度\n     */\n    val screenWidthPx by lazy {\n        appCtx.resources.displayMetrics.widthPixels\n    }\n\n    /**\n     * 屏幕像素高度\n     */\n    val screenHeightPx by lazy {\n        appCtx.resources.displayMetrics.heightPixels\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/utils/Throttle.kt",
    "content": "package io.legado.app.utils\n\n@Suppress(\"unused\")\nclass Throttle<T>(\n    wait: Long = 0L,\n    leading: Boolean = true,\n    trailing: Boolean = true,\n    func: () -> T\n) : Debounce<T>(wait, wait, leading, trailing, func)\n\nfun <T> throttle(\n    wait: Long = 0L,\n    leading: Boolean = true,\n    trailing: Boolean = true,\n    func: () -> T\n) = Throttle(wait, leading, trailing, func)\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/utils/ThrowableExtensions.kt",
    "content": "package io.legado.app.utils\n\nimport java.io.IOException\n\nval Throwable.stackTraceStr: String\n    get() {\n        val stackTrace = stackTraceToString()\n        val lMsg = this.localizedMessage ?: \"noErrorMsg\"\n        return when {\n            stackTrace.isNotEmpty() -> stackTrace\n            else -> lMsg\n        }\n    }\n\nfun Throwable.asIOException(): IOException {\n    val newException = IOException(this.message)\n    newException.initCause(this)\n    return newException\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/utils/TimeUtils.kt",
    "content": "package io.legado.app.utils\n\nimport kotlin.math.abs\n\nfun Long.toTimeAgo(): String {\n    val curTime = System.currentTimeMillis()\n    val time = this\n    val seconds = abs(System.currentTimeMillis() - time) / 1000f\n    val end = if (time < curTime) \"前\" else \"后\"\n\n    val start = when {\n        seconds < 60 -> \"${seconds.toInt()}秒\"\n        seconds < 3600 -> {\n            val minutes = seconds / 60f\n            \"${minutes.toInt()}分钟\"\n        }\n        seconds < 86400 -> {\n            val hours = seconds / 3600f\n            \"${hours.toInt()}小时\"\n        }\n        seconds < 604800 -> {\n            val days = seconds / 86400f\n            \"${days.toInt()}天\"\n        }\n        seconds < 2_628_000 -> {\n            val weeks = seconds / 604800f\n            \"${weeks.toInt()}周\"\n        }\n        seconds < 31_536_000 -> {\n            val months = seconds / 2_628_000f\n            \"${months.toInt()}月\"\n        }\n        else -> {\n            val years = seconds / 31_536_000f\n            \"${years.toInt()}年\"\n        }\n    }\n    return start + end\n}\n\nfun Int.toDurationTime(): String {\n    val totalSeconds = this / 1000\n    val hours = totalSeconds / 3600\n    val minutes = (totalSeconds % 3600) / 60\n    val seconds = totalSeconds % 60\n\n    return if (hours > 0) {\n        \"%d:%02d:%02d\".format(hours, minutes, seconds)\n    } else {\n        \"%02d:%02d\".format(minutes, seconds)\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/utils/ToastUtils.kt",
    "content": "@file:Suppress(\"unused\")\n\npackage io.legado.app.utils\n\nimport android.annotation.SuppressLint\nimport android.content.Context\nimport android.widget.Toast\nimport androidx.fragment.app.Fragment\nimport io.legado.app.BuildConfig\nimport io.legado.app.databinding.ViewToastBinding\nimport io.legado.app.help.config.AppConfig\nimport io.legado.app.lib.theme.bottomBackground\nimport io.legado.app.lib.theme.getPrimaryTextColor\nimport splitties.systemservices.layoutInflater\n\nprivate var toast: Toast? = null\n\nprivate var toastLegacy: Toast? = null\n\nfun Context.toastOnUi(message: Int, duration: Int = Toast.LENGTH_SHORT) {\n    toastOnUi(getString(message), duration)\n}\n\n@SuppressLint(\"InflateParams\")\n@Suppress(\"DEPRECATION\")\nfun Context.toastOnUi(message: CharSequence?, duration: Int = Toast.LENGTH_SHORT) {\n    runOnUI {\n        kotlin.runCatching {\n            toast?.cancel()\n            toast = Toast(this)\n            val isLight = ColorUtils.isColorLight(bottomBackground)\n            ViewToastBinding.inflate(layoutInflater).run {\n                toast?.view = root\n                cvToast.setCardBackgroundColor(bottomBackground)\n                tvText.setTextColor(getPrimaryTextColor(isLight))\n                tvText.text = message\n            }\n            toast?.duration = duration\n            toast?.show()\n        }\n    }\n}\n\nfun Context.toastOnUiLegacy(message: CharSequence) {\n    runOnUI {\n        kotlin.runCatching {\n            if (toastLegacy == null || BuildConfig.DEBUG || AppConfig.recordLog) {\n                toastLegacy = Toast.makeText(this, message, Toast.LENGTH_SHORT)\n            } else {\n                toastLegacy?.setText(message)\n                toastLegacy?.duration = Toast.LENGTH_SHORT\n            }\n            toastLegacy?.show()\n        }\n    }\n}\n\nfun Context.longToastOnUi(message: Int) {\n    toastOnUi(message, Toast.LENGTH_LONG)\n}\n\nfun Context.longToastOnUi(message: CharSequence?) {\n    toastOnUi(message, Toast.LENGTH_LONG)\n}\n\nfun Context.longToastOnUiLegacy(message: CharSequence) {\n    runOnUI {\n        kotlin.runCatching {\n            if (toastLegacy == null || BuildConfig.DEBUG || AppConfig.recordLog) {\n                toastLegacy = Toast.makeText(this, message, Toast.LENGTH_LONG)\n            } else {\n                toastLegacy?.setText(message)\n                toastLegacy?.duration = Toast.LENGTH_LONG\n            }\n            toastLegacy?.show()\n        }\n    }\n}\n\nfun Fragment.toastOnUi(message: Int) = requireActivity().toastOnUi(message)\n\nfun Fragment.toastOnUi(message: CharSequence) = requireActivity().toastOnUi(message)\n\nfun Fragment.longToast(message: Int) = requireContext().longToastOnUi(message)\n\nfun Fragment.longToast(message: CharSequence) = requireContext().longToastOnUi(message)\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/utils/ToolBarExtensions.kt",
    "content": "@file:Suppress(\"unused\")\n\npackage io.legado.app.utils\n\nimport android.annotation.SuppressLint\nimport android.graphics.PorterDuff\nimport android.graphics.PorterDuffColorFilter\nimport android.os.Build\nimport android.widget.Toolbar\nimport androidx.core.content.ContextCompat\nimport io.legado.app.R\n\n/**\n * 设置toolBar更多图标颜色\n */\n@SuppressLint(\"ObsoleteSdkInt\")\nfun Toolbar.setMoreIconColor(color: Int) {\n    val moreIcon = ContextCompat.getDrawable(context, R.drawable.ic_more)\n    if (moreIcon != null && Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {\n        moreIcon.colorFilter = PorterDuffColorFilter(color, PorterDuff.Mode.SRC_ATOP)\n        overflowIcon = moreIcon\n    }\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/utils/UriExtensions.kt",
    "content": "package io.legado.app.utils\n\nimport android.content.Context\nimport android.content.Intent\nimport android.content.pm.PackageManager\nimport android.net.Uri\nimport android.os.ParcelFileDescriptor\nimport androidx.appcompat.app.AppCompatActivity\nimport androidx.documentfile.provider.DocumentFile\nimport androidx.fragment.app.Fragment\nimport io.legado.app.R\nimport io.legado.app.constant.AppLog\nimport io.legado.app.exception.NoStackTraceException\nimport io.legado.app.lib.permission.Permissions\nimport io.legado.app.lib.permission.PermissionsCompat\nimport okhttp3.MediaType\nimport okhttp3.RequestBody\nimport okio.BufferedSink\nimport okio.source\nimport splitties.init.appCtx\nimport java.io.File\nimport java.io.FileInputStream\nimport java.io.FileOutputStream\nimport java.io.InputStream\nimport java.io.OutputStream\nimport java.nio.charset.Charset\n\nfun Uri.isContentScheme() = this.scheme == \"content\"\n\nfun Uri.isFileScheme() = this.scheme == \"file\"\n\n/**\n * 读取URI\n */\nfun AppCompatActivity.readUri(\n    uri: Uri?,\n    success: (fileDoc: FileDoc, inputStream: InputStream) -> Unit\n) {\n    uri ?: return\n    try {\n        if (uri.isContentScheme()) {\n            val doc = DocumentFile.fromSingleUri(this, uri)\n            doc ?: throw NoStackTraceException(\"未获取到文件\")\n            val fileDoc = FileDoc.fromDocumentFile(doc)\n            contentResolver.openInputStream(uri)!!.use { inputStream ->\n                success.invoke(fileDoc, inputStream)\n            }\n        } else {\n            PermissionsCompat.Builder()\n                .addPermissions(*Permissions.Group.STORAGE)\n                .rationale(R.string.get_storage_per)\n                .onGranted {\n                    RealPathUtil.getPath(this, uri)?.let { path ->\n                        val file = File(path)\n                        val fileDoc = FileDoc.fromFile(file)\n                        FileInputStream(file).use { inputStream ->\n                            success.invoke(fileDoc, inputStream)\n                        }\n                    }\n                }\n                .request()\n        }\n    } catch (e: Exception) {\n        e.printOnDebug()\n        AppLog.put(\"读取Uri出错\\n$uri\\n$e\", e, true)\n        if (e is SecurityException) {\n            throw e\n        }\n    }\n}\n\n/**\n * 读取URI\n */\nfun Fragment.readUri(uri: Uri?, success: (fileDoc: FileDoc, inputStream: InputStream) -> Unit) {\n    uri ?: return\n    try {\n        if (uri.isContentScheme()) {\n            val doc = DocumentFile.fromSingleUri(requireContext(), uri)\n            doc ?: throw NoStackTraceException(\"未获取到文件\")\n            val fileDoc = FileDoc.fromDocumentFile(doc)\n            requireContext().contentResolver.openInputStream(uri)!!.use { inputStream ->\n                success.invoke(fileDoc, inputStream)\n            }\n        } else {\n            PermissionsCompat.Builder()\n                .addPermissions(*Permissions.Group.STORAGE)\n                .rationale(R.string.get_storage_per)\n                .onGranted {\n                    RealPathUtil.getPath(requireContext(), uri)?.let { path ->\n                        val file = File(path)\n                        val fileDoc = FileDoc.fromFile(file)\n                        FileInputStream(file).use { inputStream ->\n                            success.invoke(fileDoc, inputStream)\n                        }\n                    }\n                }\n                .request()\n        }\n    } catch (e: Exception) {\n        e.printOnDebug()\n        AppLog.put(\"读取Uri出错\\n$uri\\n$e\", e, true)\n    }\n}\n\n@Throws(Exception::class)\nfun Uri.readBytes(context: Context): ByteArray {\n    return if (this.isContentScheme()) {\n        context.contentResolver.openInputStream(this)?.let {\n            val len: Int = it.available()\n            val buffer = ByteArray(len)\n            it.read(buffer)\n            it.close()\n            return buffer\n        } ?: throw NoStackTraceException(\"打开文件失败\\n${this}\")\n    } else {\n        val path = RealPathUtil.getPath(context, this)\n        if (path?.isNotEmpty() == true) {\n            File(path).readBytes()\n        } else {\n            throw NoStackTraceException(\"获取文件真实地址失败\\n${this.path}\")\n        }\n    }\n}\n\n@Throws(Exception::class)\nfun Uri.readText(context: Context): String {\n    readBytes(context).let {\n        return String(it)\n    }\n}\n\n@Throws(Exception::class)\nfun Uri.writeBytes(\n    context: Context,\n    byteArray: ByteArray\n): Boolean {\n    if (this.isContentScheme()) {\n        context.contentResolver.openOutputStream(this)?.let {\n            it.write(byteArray)\n            it.close()\n            return true\n        }\n        return false\n    } else {\n        val path = RealPathUtil.getPath(context, this)\n        if (path?.isNotEmpty() == true) {\n            File(path).writeBytes(byteArray)\n            return true\n        }\n    }\n    return false\n}\n\n@Throws(Exception::class)\nfun Uri.writeText(context: Context, text: String, charset: Charset = Charsets.UTF_8): Boolean {\n    return writeBytes(context, text.toByteArray(charset))\n}\n\nfun Uri.writeBytes(\n    context: Context,\n    fileName: String,\n    byteArray: ByteArray\n): Boolean {\n    if (this.isContentScheme()) {\n        DocumentFile.fromTreeUri(context, this)?.let { pDoc ->\n            DocumentUtils.createFileIfNotExist(pDoc, fileName)?.let {\n                return it.uri.writeBytes(context, byteArray)\n            }\n        }\n    } else {\n        FileUtils.createFileWithReplace(path + File.separatorChar + fileName)\n            .writeBytes(byteArray)\n        return true\n    }\n    return false\n}\n\nfun Uri.inputStream(context: Context): Result<InputStream> {\n    val uri = this\n    return kotlin.runCatching {\n        try {\n            if (isContentScheme()) {\n                DocumentFile.fromSingleUri(context, uri)\n                    ?: throw NoStackTraceException(\"未获取到文件\")\n                return@runCatching context.contentResolver.openInputStream(uri)!!\n            } else {\n                val path = RealPathUtil.getPath(context, uri)\n                    ?: throw NoStackTraceException(\"未获取到文件\")\n                val file = File(path)\n                if (file.exists()) {\n                    return@runCatching FileInputStream(file)\n                } else {\n                    throw NoStackTraceException(\"文件不存在\")\n                }\n            }\n        } catch (e: Exception) {\n            e.printOnDebug()\n            AppLog.put(\"读取inputStream失败：${e.localizedMessage}\", e)\n            throw e\n        }\n    }\n}\n\nfun Uri.outputStream(context: Context): Result<OutputStream> {\n    val uri = this\n    return kotlin.runCatching {\n        try {\n            if (isContentScheme()) {\n                DocumentFile.fromSingleUri(context, uri)\n                    ?: throw NoStackTraceException(\"未获取到文件\")\n                return@runCatching context.contentResolver.openOutputStream(uri)!!\n            } else {\n                val path = RealPathUtil.getPath(context, uri)\n                    ?: throw NoStackTraceException(\"未获取到文件\")\n                val file = File(path)\n                if (file.exists()) {\n                    return@runCatching FileOutputStream(file)\n                } else {\n                    throw NoStackTraceException(\"文件不存在\")\n                }\n            }\n        } catch (e: Exception) {\n            e.printOnDebug()\n            AppLog.put(\"读取inputStream失败：${e.localizedMessage}\", e)\n            throw e\n        }\n    }\n}\n\nfun Uri.toReadPfd(context: Context): Result<ParcelFileDescriptor> {\n    val uri = this\n    return kotlin.runCatching {\n        try {\n            if (isContentScheme()) {\n                DocumentFile.fromSingleUri(context, uri)\n                    ?: throw NoStackTraceException(\"未获取到文件\")\n                return@runCatching context.contentResolver.openFileDescriptor(uri, \"r\")!!\n            } else {\n                val path = RealPathUtil.getPath(context, uri)\n                    ?: throw NoStackTraceException(\"未获取到文件\")\n                val file = File(path)\n                if (file.exists()) {\n                    return@runCatching ParcelFileDescriptor.open(\n                        file,\n                        ParcelFileDescriptor.MODE_READ_ONLY\n                    )\n                } else {\n                    throw NoStackTraceException(\"文件不存在\")\n                }\n            }\n\n\n        } catch (e: Exception) {\n            e.printOnDebug()\n            AppLog.put(\"读取inputStream失败：${e.localizedMessage}\", e)\n            throw e\n        }\n    }\n}\n\nfun Uri.toWritePfd(context: Context): Result<ParcelFileDescriptor> {\n    val uri = this\n    return kotlin.runCatching {\n        try {\n            if (isContentScheme()) {\n                DocumentFile.fromSingleUri(context, uri)\n                    ?: throw NoStackTraceException(\"未获取到文件\")\n                return@runCatching context.contentResolver.openFileDescriptor(uri, \"w\")!!\n            } else {\n                val path = RealPathUtil.getPath(context, uri)\n                    ?: throw NoStackTraceException(\"未获取到文件\")\n                val file = File(path)\n                if (file.exists()) {\n                    return@runCatching ParcelFileDescriptor.open(\n                        file,\n                        ParcelFileDescriptor.MODE_WRITE_ONLY\n                    )\n                } else {\n                    throw NoStackTraceException(\"文件不存在\")\n                }\n            }\n\n\n        } catch (e: Exception) {\n            e.printOnDebug()\n            AppLog.put(\"读取inputStream失败：${e.localizedMessage}\", e)\n            throw e\n        }\n    }\n}\n\nfun Uri.toRequestBody(contentType: MediaType? = null): RequestBody {\n    val uri = this\n    return object : RequestBody() {\n        override fun contentType() = contentType\n\n        override fun contentLength(): Long {\n            val length = uri.inputStream(appCtx).getOrThrow().available().toLong()\n            return if (length > 0) length else -1\n        }\n\n        override fun writeTo(sink: BufferedSink) {\n            uri.inputStream(appCtx).getOrThrow().source().use { source ->\n                sink.writeAll(source)\n            }\n        }\n    }\n}\n\nfun Uri.canRead(): Boolean {\n    return appCtx.checkSelfUriPermission(\n        this,\n        Intent.FLAG_GRANT_READ_URI_PERMISSION\n    ) == PackageManager.PERMISSION_GRANTED\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/utils/UrlUtil.kt",
    "content": "package io.legado.app.utils\n\nimport io.legado.app.BuildConfig\nimport io.legado.app.constant.AppLog\nimport io.legado.app.constant.AppPattern.semicolonRegex\nimport io.legado.app.help.config.AppConfig\nimport io.legado.app.model.analyzeRule.AnalyzeUrl\nimport io.legado.app.model.analyzeRule.CustomUrl\nimport java.net.HttpURLConnection\nimport java.net.URL\nimport java.net.URLDecoder\n\nobject UrlUtil {\n\n    // 有时候文件名在query里，截取path会截到其他内容\n    // https://www.example.com/download.php?filename=文件.txt\n    // https://www.example.com/txt/文件.txt?token=123456\n    private val unExpectFileSuffixs = arrayOf(\n        \"php\", \"html\"\n    )\n\n    fun replaceReservedChar(text: String): String {\n        return text.replace(\"%\", \"%25\")\n            .replace(\" \", \"%20\")\n            .replace(\"\\\"\", \"%22\")\n            .replace(\"#\", \"%23\")\n            .replace(\"&\", \"%26\")\n            .replace(\"(\", \"%28\")\n            .replace(\")\", \"%29\")\n            .replace(\"+\", \"%2B\")\n            .replace(\",\", \"%2C\")\n            .replace(\"/\", \"%2F\")\n            .replace(\":\", \"%3A\")\n            .replace(\";\", \"%3B\")\n            .replace(\"<\", \"%3C\")\n            .replace(\"=\", \"%3D\")\n            .replace(\">\", \"%3E\")\n            .replace(\"?\", \"%3F\")\n            .replace(\"@\", \"%40\")\n            .replace(\"\\\\\", \"%5C\")\n            .replace(\"|\", \"%7C\")\n    }\n\n\n    /* 阅读定义的url,{urlOption} */\n    fun getFileName(analyzeUrl: AnalyzeUrl): String? {\n        return getFileName(analyzeUrl.url, analyzeUrl.headerMap)\n    }\n\n    /**\n     * 根据网络url获取文件信息 文件名\n     */\n    @Suppress(\"MemberVisibilityCanBePrivate\")\n    fun getFileName(fileUrl: String, headerMap: Map<String, String>? = null): String? {\n        return kotlin.runCatching {\n            val url = URL(fileUrl)\n            var fileName: String? = getFileNameFromPath(url)\n            if (fileName == null) {\n                fileName = getFileNameFromResponseHeader(url, headerMap)\n            }\n            fileName\n        }.getOrNull()\n    }\n\n    @Suppress(\"MemberVisibilityCanBePrivate\")\n    private fun getFileNameFromResponseHeader(\n        url: URL,\n        headerMap: Map<String, String>? = null\n    ): String? {\n        // HEAD方式获取链接响应头信息\n        val conn: HttpURLConnection = url.openConnection() as HttpURLConnection\n        conn.requestMethod = \"HEAD\"\n        // 下载链接可能还需要header才能成功访问\n        headerMap?.forEach { (key, value) ->\n            conn.setRequestProperty(key, value)\n        }\n        // 禁止重定向 否则获取不到响应头返回的Location\n        conn.instanceFollowRedirects = false\n        conn.connect()\n\n        if (AppConfig.recordLog || BuildConfig.DEBUG) {\n            val headers = conn.headerFields\n            val headersString = buildString {\n                headers.forEach { (key, value) ->\n                   value.forEach {\n                       append(key)\n                       append(\": \")\n                       append(it)\n                       append(\"\\n\")\n                   }\n               }\n            }\n            AppLog.put(\"$url response header:\\n$headersString\")\n        }\n\n        // val fileSize = conn.getContentLengthLong() / 1024\n        /** Content-Disposition 存在三种情况 文件名应该用引号 有些用空格\n         * filename=\"filename\"\n         * filename*=\"charset''filename\"\n         */\n        val raw: String? = conn.getHeaderField(\"Content-Disposition\")\n        // Location跳转到实际链接\n        val redirectUrl: String? = conn.getHeaderField(\"Location\")\n\n        return if (raw != null) {\n            val fileNames = raw.split(semicolonRegex).filter { it.contains(\"filename\") }\n            val names = hashSetOf<String>()\n            fileNames.forEach {\n                val fileName = it.substringAfter(\"=\")\n                    .trim()\n                    .replace(\"^\\\"\".toRegex(), \"\")\n                    .replace(\"\\\"$\".toRegex(), \"\")\n                if (it.contains(\"filename*\")) {\n                    val data = fileName.split(\"''\")\n                    names.add(URLDecoder.decode(data[1], data[0]))\n                } else {\n                    names.add(fileName)\n                    /* 好像不用这样\n                    names.add(\n                            String(\n                            fileName.toByteArray(StandardCharsets.ISO_8859_1),\n                            StandardCharsets.UTF_8\n                        )\n                    )\n                    */\n                }\n           }\n           names.firstOrNull()\n        } else if (redirectUrl != null) {\n            val newUrl= URL(URLDecoder.decode(redirectUrl, \"UTF-8\"))\n            getFileNameFromPath(newUrl)\n        } else {\n            AppLog.put(\"Cannot obtain URL file name, enable recordLog for response header\")\n            null\n        }\n    }\n    \n    private fun getFileNameFromPath(fileUrl: URL): String? {\n        val path = fileUrl.path ?: return null\n        val suffix = getSuffix(path, \"\")\n        return if (\n           suffix != \"\" && !unExpectFileSuffixs.contains(suffix)\n        ) {\n            path.substringAfterLast(\"/\")\n        } else {\n            AppLog.put(\"getFileNameFromPath: Unexpected file suffix: $suffix\")\n            null\n        }\n    }\n\n    private val fileSuffixRegex = Regex(\"^[a-z\\\\d]+$\", RegexOption.IGNORE_CASE)\n\n    /* 获取合法的文件后缀 */\n    fun getSuffix(str: String, default: String? = null): String {\n        val suffix = CustomUrl(str).getUrl()\n            .substringAfterLast(\"/\")\n            .substringBefore(\"?\")\n            .substringBefore(\"#\")\n            .substringAfterLast(\".\", \"\")\n        //检查截取的后缀字符是否合法 [a-zA-Z0-9]\n        return if (suffix.length > 5 || !suffix.matches(fileSuffixRegex)) {\n            if (default == null) {\n                AppLog.put(\"Cannot find legal suffix:\\n target: $str\\n suffix: $suffix\")\n            }\n            default ?: \"ext\"\n        } else {\n            suffix\n        }\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/utils/Utf8BomUtils.kt",
    "content": "package io.legado.app.utils\n\n@Suppress(\"unused\")\nobject Utf8BomUtils {\n    private val UTF8_BOM_BYTES = byteArrayOf(0xEF.toByte(), 0xBB.toByte(), 0xBF.toByte())\n\n    fun removeUTF8BOM(xmlText: String): String {\n        val bytes = xmlText.toByteArray()\n        val containsBOM = (bytes.size > 3\n                && bytes[0] == UTF8_BOM_BYTES[0]\n                && bytes[1] == UTF8_BOM_BYTES[1]\n                && bytes[2] == UTF8_BOM_BYTES[2])\n        if (containsBOM) {\n            return String(bytes, 3, bytes.size - 3)\n        }\n        return xmlText\n    }\n\n    fun removeUTF8BOM(bytes: ByteArray): ByteArray {\n        val containsBOM = (bytes.size > 3\n                && bytes[0] == UTF8_BOM_BYTES[0]\n                && bytes[1] == UTF8_BOM_BYTES[1]\n                && bytes[2] == UTF8_BOM_BYTES[2])\n        if (containsBOM) {\n            val copy = ByteArray(bytes.size - 3)\n            System.arraycopy(bytes, 3, copy, 0, bytes.size - 3)\n            return copy\n        }\n        return bytes\n    }\n\n    fun hasBom(bytes: ByteArray): Boolean {\n        return (bytes.size > 3\n                && bytes[0] == UTF8_BOM_BYTES[0]\n                && bytes[1] == UTF8_BOM_BYTES[1]\n                && bytes[2] == UTF8_BOM_BYTES[2])\n    }\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/utils/ViewExtensions.kt",
    "content": "@file:Suppress(\"unused\")\n\npackage io.legado.app.utils\n\nimport android.annotation.SuppressLint\nimport android.content.Context\nimport android.graphics.Bitmap\nimport android.graphics.Canvas\nimport android.graphics.Color\nimport android.graphics.Picture\nimport android.os.Build\nimport android.text.Html\nimport android.view.MotionEvent\nimport android.view.View\nimport android.view.View.GONE\nimport android.view.View.IMPORTANT_FOR_AUTOFILL_NO_EXCLUDE_DESCENDANTS\nimport android.view.View.INVISIBLE\nimport android.view.View.VISIBLE\nimport android.view.ViewGroup\nimport android.view.inputmethod.InputMethodManager\nimport android.widget.EdgeEffect\nimport android.widget.EditText\nimport android.widget.RadioGroup\nimport android.widget.SeekBar\nimport android.widget.TextView\nimport androidx.annotation.ColorInt\nimport androidx.annotation.DrawableRes\nimport androidx.appcompat.app.AppCompatActivity\nimport androidx.appcompat.view.menu.MenuPopupHelper\nimport androidx.appcompat.widget.PopupMenu\nimport androidx.core.graphics.record\nimport androidx.core.graphics.withTranslation\nimport androidx.core.view.ViewCompat\nimport androidx.core.view.WindowInsetsCompat\nimport androidx.core.view.get\nimport androidx.core.view.marginBottom\nimport androidx.core.view.updateLayoutParams\nimport androidx.recyclerview.widget.RecyclerView\nimport androidx.viewpager.widget.ViewPager\nimport io.legado.app.help.config.AppConfig\nimport io.legado.app.lib.theme.TintHelper\nimport io.legado.app.utils.canvasrecorder.CanvasRecorder\nimport io.legado.app.utils.canvasrecorder.record\nimport splitties.systemservices.inputMethodManager\nimport splitties.views.bottomPadding\nimport splitties.views.topPadding\nimport java.lang.reflect.Field\n\n\nprivate tailrec fun getCompatActivity(context: Context?): AppCompatActivity? {\n    return when (context) {\n        is AppCompatActivity -> context\n        is androidx.appcompat.view.ContextThemeWrapper -> getCompatActivity(context.baseContext)\n        is android.view.ContextThemeWrapper -> getCompatActivity(context.baseContext)\n        else -> null\n    }\n}\n\nval View.activity: AppCompatActivity?\n    get() = getCompatActivity(context)\n\nfun View.hideSoftInput() = run {\n    inputMethodManager.hideSoftInputFromWindow(this.windowToken, 0)\n}\n\nfun EditText.showSoftInput() = run {\n    requestFocus()\n    inputMethodManager.showSoftInput(this, InputMethodManager.RESULT_SHOWN)\n}\n\nfun View.disableAutoFill() = run {\n    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {\n        this.importantForAutofill = IMPORTANT_FOR_AUTOFILL_NO_EXCLUDE_DESCENDANTS\n    }\n}\n\nfun View.applyTint(\n    @ColorInt color: Int,\n    isDark: Boolean = AppConfig.isNightTheme\n) {\n    TintHelper.setTintAuto(this, color, false, isDark)\n}\n\nfun View.applyBackgroundTint(\n    @ColorInt color: Int,\n    isDark: Boolean = AppConfig.isNightTheme\n) {\n    if (background == null) {\n        setBackgroundColor(color)\n    } else {\n        TintHelper.setTintAuto(this, color, true, isDark)\n    }\n}\n\nfun RecyclerView.setEdgeEffectColor(@ColorInt color: Int) {\n    edgeEffectFactory = object : RecyclerView.EdgeEffectFactory() {\n        override fun createEdgeEffect(view: RecyclerView, direction: Int): EdgeEffect {\n            val edgeEffect = super.createEdgeEffect(view, direction)\n            edgeEffect.color = color\n            return edgeEffect\n        }\n    }\n}\n\nfun ViewPager.setEdgeEffectColor(@ColorInt color: Int) {\n    try {\n        val clazz = ViewPager::class.java\n        for (name in arrayOf(\"mLeftEdge\", \"mRightEdge\")) {\n            val field = clazz.getDeclaredField(name)\n            field.isAccessible = true\n            val edge = field.get(this)\n            (edge as EdgeEffect).color = color\n        }\n    } catch (ignored: Exception) {\n    }\n}\n\nfun EditText.disableEdit() {\n    keyListener = null\n}\n\nfun View.gone() {\n    if (visibility != GONE) {\n        visibility = GONE\n    }\n}\n\nfun View.gone(gone: Boolean) {\n    if (gone) {\n        gone()\n    } else {\n        visibility = VISIBLE\n    }\n}\n\nfun View.invisible() {\n    if (visibility != INVISIBLE) {\n        visibility = INVISIBLE\n    }\n}\n\nfun View.visible() {\n    if (visibility != VISIBLE) {\n        visibility = VISIBLE\n    }\n}\n\nfun View.visible(visible: Boolean) {\n    if (visible && visibility != VISIBLE) {\n        visibility = VISIBLE\n    } else if (!visible && visibility == VISIBLE) {\n        visibility = INVISIBLE\n    }\n}\n\nfun View.screenshot(bitmap: Bitmap? = null, canvas: Canvas? = null): Bitmap? {\n    return if (width > 0 && height > 0) {\n        val screenshot = if (bitmap != null && bitmap.width == width && bitmap.height == height) {\n            bitmap.eraseColor(Color.TRANSPARENT)\n            bitmap\n        } else {\n            bitmap?.recycle()\n            Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)\n        }\n        val c = canvas ?: Canvas()\n        c.setBitmap(screenshot)\n        c.save()\n        c.translate(-scrollX.toFloat(), -scrollY.toFloat())\n        this.draw(c)\n        c.restore()\n        c.setBitmap(null)\n        screenshot.prepareToDraw()\n        screenshot\n    } else {\n        null\n    }\n}\n\nfun View.screenshot(picture: Picture) {\n    if (width > 0 && height > 0) {\n        picture.record(width, height) {\n            withTranslation(-scrollX.toFloat(), -scrollY.toFloat()) {\n                draw(this)\n            }\n        }\n    }\n}\n\nfun View.screenshot(canvasRecorder: CanvasRecorder) {\n    if (width > 0 && height > 0) {\n        canvasRecorder.record(width, height) {\n            draw(this)\n        }\n    }\n}\n\nfun View.setPaddingBottom(bottom: Int) {\n    setPadding(paddingLeft, paddingTop, paddingRight, bottom)\n}\n\nfun SeekBar.progressAdd(int: Int) {\n    progress += int\n}\n\nfun RadioGroup.getIndexById(id: Int): Int {\n    for (i in 0 until this.childCount) {\n        if (id == get(i).id) {\n            return i\n        }\n    }\n    return 0\n}\n\nfun RadioGroup.getCheckedIndex(): Int {\n    for (i in 0 until this.childCount) {\n        if (checkedRadioButtonId == get(i).id) {\n            return i\n        }\n    }\n    return 0\n}\n\nfun RadioGroup.checkByIndex(index: Int) {\n    check(get(index).id)\n}\n\n@SuppressLint(\"ObsoleteSdkInt\")\nfun TextView.setHtml(html: String) {\n    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {\n        text = Html.fromHtml(html, Html.FROM_HTML_MODE_COMPACT)\n    } else {\n        @Suppress(\"DEPRECATION\")\n        text = Html.fromHtml(html)\n    }\n}\n\nfun TextView.setTextIfNotEqual(charSequence: CharSequence?) {\n    if (text != charSequence) {\n        text = charSequence\n    }\n}\n\n@SuppressLint(\"RestrictedApi\")\nfun PopupMenu.show(x: Int, y: Int) {\n    kotlin.runCatching {\n        val field: Field = this.javaClass.getDeclaredField(\"mPopup\")\n        field.isAccessible = true\n        (field.get(this) as MenuPopupHelper).show(x, y)\n    }.onFailure {\n        it.printOnDebug()\n    }\n}\n\nfun View.shouldHideSoftInput(event: MotionEvent): Boolean {\n    if (this is EditText) {\n        val l = intArrayOf(0, 0)\n        getLocationInWindow(l)\n        val left = l[0]\n        val top = l[1]\n        val bottom = top + getHeight()\n        val right = left + getWidth()\n        return !(event.x > left && event.x < right && event.y > top && event.y < bottom)\n    }\n    return false\n}\n\nfun View.applyStatusBarPadding(withInitialPadding: Boolean = false) {\n    val initialPadding = if (withInitialPadding) topPadding else 0\n    setOnApplyWindowInsetsListenerCompat { _, windowInsets ->\n        val insets = windowInsets.getInsets(WindowInsetsCompat.Type.statusBars())\n        topPadding = initialPadding + insets.top\n        windowInsets\n    }\n}\n\nfun View.applyNavigationBarPadding(withInitialPadding: Boolean = false) {\n    val initialPadding = if (withInitialPadding) bottomPadding else 0\n    setOnApplyWindowInsetsListenerCompat { _, windowInsets ->\n        bottomPadding = initialPadding + windowInsets.navigationBarHeight\n        windowInsets\n    }\n}\n\nfun View.applyNavigationBarMargin(withInitialMargin: Boolean = false) {\n    val initialMargin = if (withInitialMargin) marginBottom else 0\n    setOnApplyWindowInsetsListenerCompat { _, windowInsets ->\n        updateLayoutParams<ViewGroup.MarginLayoutParams> {\n            bottomMargin = initialMargin + windowInsets.navigationBarHeight\n        }\n        windowInsets\n    }\n}\n\nfun View.setBackgroundKeepPadding(@DrawableRes backgroundResId: Int) {\n    val paddingLeft = paddingLeft\n    val paddingTop = paddingTop\n    val paddingRight = paddingRight\n    val paddingBottom = paddingBottom\n    setBackgroundResource(backgroundResId)\n    setPadding(paddingLeft, paddingTop, paddingRight, paddingBottom)\n}\n\nfun View.canScroll(direction: Int): Boolean {\n    return canScrollVertically(direction) || canScrollHorizontally(direction)\n}\n\nprivate val requestLayoutBroken = Build.VERSION.SDK_INT <= Build.VERSION_CODES.M\n        || Build.VERSION.SDK_INT in Build.VERSION_CODES.O..Build.VERSION_CODES.Q\n\nfun View.setOnApplyWindowInsetsListenerCompat(listener: (View, WindowInsetsCompat) -> WindowInsetsCompat) {\n    ViewCompat.setOnApplyWindowInsetsListener(this) { view, insets ->\n        val windowInsets = listener(view, insets)\n        if (requestLayoutBroken && isLayoutRequested) {\n            post {\n                requestLayout()\n            }\n        }\n        windowInsets\n    }\n}\n\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/utils/WebSettingsExtensions.kt",
    "content": "package io.legado.app.utils\n\nimport android.annotation.SuppressLint\nimport android.os.Build\nimport android.webkit.WebSettings\nimport androidx.webkit.WebSettingsCompat\nimport androidx.webkit.WebViewFeature\nimport io.legado.app.help.config.AppConfig\n\n/**\n * 设置是否夜间模式\n */\n@SuppressLint(\"RequiresFeature\")\nfun WebSettings.setDarkeningAllowed(allow: Boolean) {\n    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {\n        kotlin.runCatching {\n            WebSettingsCompat.setAlgorithmicDarkeningAllowed(this, allow)\n            return\n        }.onFailure {\n            it.printOnDebug()\n        }\n    }\n    if (AppConfig.isNightTheme) {\n        if (WebViewFeature.isFeatureSupported(WebViewFeature.FORCE_DARK_STRATEGY)) {\n            @Suppress(\"DEPRECATION\")\n            WebSettingsCompat.setForceDarkStrategy(\n                this,\n                WebSettingsCompat.DARK_STRATEGY_PREFER_WEB_THEME_OVER_USER_AGENT_DARKENING\n            )\n        }\n        if (WebViewFeature.isFeatureSupported(WebViewFeature.FORCE_DARK)) {\n            @Suppress(\"DEPRECATION\")\n            WebSettingsCompat.setForceDark(\n                this,\n                WebSettingsCompat.FORCE_DARK_ON\n            )\n        }\n    }\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/utils/WindowInsetsExtensions.kt",
    "content": "package io.legado.app.utils\n\nimport androidx.core.view.WindowInsetsCompat\n\nval WindowInsetsCompat.navigationBarHeight\n    get() = (getInsets(WindowInsetsCompat.Type.systemBars()).bottom - imeHeight).coerceAtLeast(0)\n\nval WindowInsetsCompat.imeHeight\n    get() = getInsets(WindowInsetsCompat.Type.ime()).bottom\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/utils/canvasrecorder/BaseCanvasRecorder.kt",
    "content": "package io.legado.app.utils.canvasrecorder\n\nimport androidx.annotation.CallSuper\n\nabstract class BaseCanvasRecorder : CanvasRecorder {\n\n    @JvmField\n    protected var isDirty = true\n\n    override fun invalidate() {\n        isDirty = true\n    }\n\n    @CallSuper\n    override fun recycle() {\n        isDirty = true\n    }\n\n    @CallSuper\n    override fun endRecording() {\n        isDirty = false\n    }\n\n    override fun isDirty(): Boolean {\n        return isDirty\n    }\n\n    override fun isLocked(): Boolean {\n        return false\n    }\n\n    override fun needRecord(): Boolean {\n        return isDirty() && !isLocked()\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/utils/canvasrecorder/CanvasRecorder.kt",
    "content": "package io.legado.app.utils.canvasrecorder\n\nimport android.graphics.Canvas\n\ninterface CanvasRecorder {\n\n    val width: Int\n\n    val height: Int\n\n    fun beginRecording(width: Int, height: Int): Canvas\n\n    fun endRecording()\n\n    fun draw(canvas: Canvas)\n\n    fun invalidate()\n\n    fun recycle()\n\n    fun isDirty(): Boolean\n\n    fun isLocked(): Boolean\n\n    fun needRecord(): Boolean\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/utils/canvasrecorder/CanvasRecorderApi23Impl.kt",
    "content": "package io.legado.app.utils.canvasrecorder\n\nimport android.graphics.Canvas\nimport android.graphics.Picture\nimport io.legado.app.utils.canvasrecorder.pools.PicturePool\nimport io.legado.app.utils.objectpool.synchronized\n\nclass CanvasRecorderApi23Impl : BaseCanvasRecorder() {\n\n    private var picture: Picture? = null\n\n    override val width get() = picture?.width ?: -1\n    override val height get() = picture?.height ?: -1\n\n    private fun initPicture() {\n        if (picture == null) {\n            picture = picturePool.obtain()\n        }\n    }\n\n    override fun beginRecording(width: Int, height: Int): Canvas {\n        initPicture()\n        return picture!!.beginRecording(width, height)\n    }\n\n    override fun endRecording() {\n        picture!!.endRecording()\n        super.endRecording()\n    }\n\n    override fun draw(canvas: Canvas) {\n        if (picture == null) return\n        canvas.drawPicture(picture!!)\n    }\n\n    override fun recycle() {\n        super.recycle()\n        if (picture == null) return\n        picturePool.recycle(picture!!)\n        picture = null\n    }\n\n    companion object {\n        private val picturePool = PicturePool().synchronized()\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/utils/canvasrecorder/CanvasRecorderApi29Impl.kt",
    "content": "package io.legado.app.utils.canvasrecorder\n\nimport android.graphics.Canvas\nimport android.graphics.Picture\nimport android.graphics.RenderNode\nimport android.os.Build\nimport androidx.annotation.RequiresApi\nimport io.legado.app.utils.objectpool.synchronized\nimport io.legado.app.utils.canvasrecorder.pools.PicturePool\nimport io.legado.app.utils.canvasrecorder.pools.RenderNodePool\n\n@RequiresApi(Build.VERSION_CODES.Q)\nclass CanvasRecorderApi29Impl : BaseCanvasRecorder() {\n\n    private var renderNode: RenderNode? = null\n    private var picture: Picture? = null\n\n    override val width get() = renderNode?.width ?: -1\n    override val height get() = renderNode?.height ?: -1\n\n    private fun init() {\n        if (renderNode == null) {\n            renderNode = renderNodePool.obtain()\n        }\n        if (picture == null) {\n            picture = picturePool.obtain()\n        }\n    }\n\n    private fun flushRenderNode() {\n        val rc = renderNode!!.beginRecording()\n        rc.drawPicture(picture!!)\n        renderNode!!.endRecording()\n    }\n\n    override fun beginRecording(width: Int, height: Int): Canvas {\n        init()\n        renderNode!!.setPosition(0, 0, width, height)\n        return picture!!.beginRecording(width, height)\n    }\n\n    override fun endRecording() {\n        picture!!.endRecording()\n        flushRenderNode()\n        super.endRecording()\n    }\n\n    override fun draw(canvas: Canvas) {\n        if (renderNode == null || picture == null) {\n            return\n        }\n        if (canvas.isHardwareAccelerated) {\n            if (!renderNode!!.hasDisplayList()) {\n                flushRenderNode()\n            }\n            canvas.drawRenderNode(renderNode!!)\n        } else {\n            canvas.drawPicture(picture!!)\n        }\n    }\n\n    override fun recycle() {\n        super.recycle()\n        if (renderNode == null || picture == null) return\n        renderNodePool.recycle(renderNode!!)\n        renderNode = null\n        picturePool.recycle(picture!!)\n        picture = null\n    }\n\n    companion object {\n        private val picturePool = PicturePool().synchronized()\n        private val renderNodePool = RenderNodePool().synchronized()\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/utils/canvasrecorder/CanvasRecorderExtensions.kt",
    "content": "package io.legado.app.utils.canvasrecorder\n\nimport android.graphics.Canvas\nimport android.view.View\nimport androidx.core.graphics.withSave\n\ninline fun CanvasRecorder.recordIfNeeded(\n    width: Int,\n    height: Int,\n    block: Canvas.() -> Unit\n): Boolean {\n    if (!needRecord()) return false\n    record(width, height, block)\n    return true\n}\n\nfun CanvasRecorder.recordIfNeeded(view: View): Boolean {\n    if (!needRecord()) return false\n    record(view.width, view.height) {\n        view.draw(this)\n    }\n    return true\n}\n\ninline fun CanvasRecorder.record(width: Int, height: Int, block: Canvas.() -> Unit) {\n    val canvas = beginRecording(width, height)\n    try {\n        canvas.withSave {\n            block()\n        }\n    } finally {\n        endRecording()\n    }\n}\n\ninline fun CanvasRecorder.recordIfNeededThenDraw(\n    canvas: Canvas,\n    width: Int,\n    height: Int,\n    block: Canvas.() -> Unit\n) {\n    recordIfNeeded(width, height, block)\n    draw(canvas)\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/utils/canvasrecorder/CanvasRecorderFactory.kt",
    "content": "package io.legado.app.utils.canvasrecorder\n\nimport android.os.Build\nimport io.legado.app.help.config.AppConfig\n\nobject CanvasRecorderFactory {\n\n    private val atLeastApi24 = Build.VERSION.SDK_INT >= Build.VERSION_CODES.N\n    private val atLeastApi29 = Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q\n    val isSupport = atLeastApi24\n\n    // issue 3868\n    fun create(locked: Boolean = false): CanvasRecorder {\n        val impl = when {\n            !AppConfig.optimizeRender -> CanvasRecorderImpl()\n            atLeastApi29 -> CanvasRecorderApi29Impl()\n            atLeastApi24 -> CanvasRecorderApi23Impl()\n            else -> CanvasRecorderImpl()\n        }\n        return if (locked) {\n            CanvasRecorderLocked(impl)\n        } else {\n            impl\n        }\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/utils/canvasrecorder/CanvasRecorderImpl.kt",
    "content": "package io.legado.app.utils.canvasrecorder\n\nimport android.graphics.Bitmap\nimport android.graphics.Canvas\nimport android.graphics.Color\nimport com.bumptech.glide.Glide\nimport io.legado.app.utils.canvasrecorder.pools.CanvasPool\nimport splitties.init.appCtx\n\nclass CanvasRecorderImpl : BaseCanvasRecorder() {\n\n    var bitmap: Bitmap? = null\n    var canvas: Canvas? = null\n\n    override val width get() = bitmap?.width ?: -1\n    override val height get() = bitmap?.height ?: -1\n\n    private fun init(width: Int, height: Int) {\n        if (width <= 0 || height <= 0) {\n            return\n        }\n        if (bitmap == null) {\n            bitmap = bitmapPool.get(width, height, Bitmap.Config.ARGB_8888)\n        }\n        if (bitmap!!.width != width || bitmap!!.height != height) {\n            if (bitmap!!.isMutable && canReconfigure(width, height)) {\n                bitmap!!.reconfigure(width, height, Bitmap.Config.ARGB_8888)\n            } else {\n                bitmapPool.put(bitmap!!)\n                bitmap = bitmapPool.get(width, height, Bitmap.Config.ARGB_8888)\n            }\n        }\n    }\n\n    private fun canReconfigure(width: Int, height: Int): Boolean {\n        return bitmap!!.allocationByteCount >= width * height * 4\n    }\n\n    override fun beginRecording(width: Int, height: Int): Canvas {\n        init(width, height)\n        bitmap?.eraseColor(Color.TRANSPARENT)\n        canvas = canvasPool.obtain().apply { setBitmap(bitmap) }\n        return canvas!!\n    }\n\n    override fun endRecording() {\n        bitmap?.prepareToDraw()\n        super.endRecording()\n        canvasPool.recycle(canvas!!)\n        canvas = null\n    }\n\n    override fun draw(canvas: Canvas) {\n        if (bitmap == null) return\n        canvas.drawBitmap(bitmap!!, 0f, 0f, null)\n    }\n\n    override fun recycle() {\n        super.recycle()\n        val bitmap = bitmap ?: return\n        bitmapPool.put(bitmap)\n        this.bitmap = null\n    }\n\n    companion object {\n        private val canvasPool = CanvasPool(2)\n        private val bitmapPool = Glide.get(appCtx).bitmapPool\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/utils/canvasrecorder/CanvasRecorderLocked.kt",
    "content": "package io.legado.app.utils.canvasrecorder\n\nimport android.graphics.Canvas\nimport java.util.concurrent.locks.ReentrantLock\n\nclass CanvasRecorderLocked(private val delegate: CanvasRecorder) :\n    CanvasRecorder by delegate {\n\n    var lock: ReentrantLock? = ReentrantLock()\n\n    private fun initLock() {\n        if (lock == null) {\n            synchronized(this) {\n                if (lock == null) {\n                    lock = ReentrantLock()\n                }\n            }\n        }\n    }\n\n    override fun beginRecording(width: Int, height: Int): Canvas {\n        initLock()\n        lock!!.lock()\n        return delegate.beginRecording(width, height)\n    }\n\n    override fun endRecording() {\n        delegate.endRecording()\n        lock!!.unlock()\n    }\n\n    override fun draw(canvas: Canvas) {\n        if (lock == null) {\n            return\n        }\n        lock!!.lock()\n        try {\n            delegate.draw(canvas)\n        } finally {\n            lock!!.unlock()\n        }\n    }\n\n    override fun isLocked(): Boolean {\n        if (lock == null) {\n            return false\n        }\n        return lock!!.isLocked\n    }\n\n    override fun recycle() {\n        if (lock == null) {\n            return\n        }\n        lock!!.lock()\n        try {\n            delegate.recycle()\n        } finally {\n            lock!!.unlock()\n        }\n        lock = null\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/utils/canvasrecorder/pools/CanvasPool.kt",
    "content": "package io.legado.app.utils.canvasrecorder.pools\n\nimport android.graphics.Canvas\nimport androidx.core.util.Pools\n\nclass CanvasPool(size: Int) {\n\n    private val pool = Pools.SynchronizedPool<Canvas>(size)\n\n    fun obtain(): Canvas {\n        val canvas = pool.acquire() ?: Canvas()\n        return canvas\n    }\n\n    fun recycle(canvas: Canvas) {\n        canvas.setBitmap(null)\n        canvas.restoreToCount(1)\n        pool.release(canvas)\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/utils/canvasrecorder/pools/PicturePool.kt",
    "content": "package io.legado.app.utils.canvasrecorder.pools\n\nimport android.graphics.Picture\nimport io.legado.app.utils.objectpool.BaseObjectPool\n\nclass PicturePool : BaseObjectPool<Picture>(64) {\n\n    override fun create(): Picture = Picture()\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/utils/canvasrecorder/pools/RenderNodePool.kt",
    "content": "package io.legado.app.utils.canvasrecorder.pools\n\nimport android.graphics.RenderNode\nimport android.os.Build\nimport androidx.annotation.RequiresApi\nimport io.legado.app.utils.objectpool.BaseObjectPool\n\n@RequiresApi(Build.VERSION_CODES.Q)\nclass RenderNodePool : BaseObjectPool<RenderNode>(64) {\n\n    override fun recycle(target: RenderNode) {\n        target.discardDisplayList()\n        super.recycle(target)\n    }\n\n    override fun create(): RenderNode = RenderNode(\"CanvasRecorder\")\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/utils/compress/LibArchiveUtils.kt",
    "content": "package io.legado.app.utils.compress\n\nimport android.os.ParcelFileDescriptor\nimport android.system.ErrnoException\nimport android.system.Os\nimport android.system.OsConstants\nimport android.system.OsConstants.S_ISDIR\nimport io.legado.app.lib.icu4j.CharsetDetector\nimport me.zhanghai.android.libarchive.Archive\nimport me.zhanghai.android.libarchive.ArchiveEntry\nimport me.zhanghai.android.libarchive.ArchiveException\nimport okio.Buffer\nimport java.io.File\nimport java.io.FileDescriptor\nimport java.io.IOException\nimport java.io.InputStream\nimport java.io.InterruptedIOException\nimport java.nio.ByteBuffer\nimport java.nio.channels.SeekableByteChannel\nimport java.nio.charset.Charset\nimport java.nio.charset.StandardCharsets\n\n\nobject LibArchiveUtils {\n\n    @Throws(ArchiveException::class)\n    fun openArchive(\n        inputStream: InputStream,\n    ): Long {\n        val archive: Long = Archive.readNew()\n        var successful = false\n        try {\n            Archive.setCharset(archive, StandardCharsets.UTF_8.name().toByteArray())\n            Archive.readSupportFilterAll(archive)\n            Archive.readSupportFormatAll(archive)\n            Archive.readSetCallbackData(archive, null)\n            val buffer = ByteBuffer.allocate(DEFAULT_BUFFER_SIZE)\n            Archive.readSetReadCallback<Any?>(archive) { _, _ ->\n                buffer.clear()\n                val bytesRead = try {\n                    inputStream.read(buffer.array())\n                } catch (e: IOException) {\n                    throw ArchiveException(Archive.ERRNO_FATAL, \"InputStream.read\", e)\n                }\n                if (bytesRead != -1) {\n                    buffer.limit(bytesRead)\n                    buffer\n                } else {\n                    null\n                }\n            }\n            Archive.readSetSkipCallback<Any?>(archive) { _, _, request ->\n                try {\n                    inputStream.skip(request)\n                } catch (e: IOException) {\n                    throw ArchiveException(Archive.ERRNO_FATAL, \"InputStream.skip\", e)\n                }\n            }\n            Archive.readOpen1(archive)\n            successful = true\n            return archive\n        } finally {\n            if (!successful) {\n                Archive.free(archive)\n            }\n        }\n\n    }\n\n\n    @Throws(ArchiveException::class)\n    private fun openArchive(\n        channel: SeekableByteChannel,\n    ): Long {\n        val archive: Long = Archive.readNew()\n        var successful = false\n        try {\n            Archive.setCharset(archive, StandardCharsets.UTF_8.name().toByteArray())\n            Archive.readSupportFilterAll(archive)\n            Archive.readSupportFormatAll(archive)\n            Archive.readSetCallbackData(archive, null)\n            val buffer = ByteBuffer.allocateDirect(DEFAULT_BUFFER_SIZE)\n            Archive.readSetReadCallback<Any?>(archive) { _, _ ->\n                buffer.clear()\n                val bytesRead = try {\n                    channel.read(buffer)\n                } catch (e: IOException) {\n                    throw ArchiveException(Archive.ERRNO_FATAL, \"SeekableByteChannel.read\", e)\n                }\n                if (bytesRead != -1) {\n                    buffer.flip()\n                    buffer\n                } else {\n                    null\n                }\n            }\n            Archive.readSetSkipCallback<Any?>(archive) { _, _, request ->\n                try {\n                    channel.position(channel.position() + request)\n                } catch (e: IOException) {\n                    throw ArchiveException(Archive.ERRNO_FATAL, \"SeekableByteChannel.position\", e)\n                }\n                request\n            }\n            Archive.readSetSeekCallback<Any?>(archive) { _, _, offset, whence ->\n                val newPosition: Long\n                try {\n                    newPosition = when (whence) {\n                        OsConstants.SEEK_SET -> offset\n                        OsConstants.SEEK_CUR -> channel.position() + offset\n                        OsConstants.SEEK_END -> channel.size() + offset\n                        else -> throw ArchiveException(\n                            Archive.ERRNO_FATAL,\n                            \"Unknown whence $whence\"\n                        )\n                    }\n                    channel.position(newPosition)\n                } catch (e: IOException) {\n                    throw ArchiveException(Archive.ERRNO_FATAL, \"SeekableByteChannel.position\", e)\n                }\n                newPosition\n            }\n            Archive.readOpen1(archive)\n            successful = true\n            return archive\n\n\n        } finally {\n            if (!successful) {\n                Archive.free(archive)\n            }\n        }\n\n    }\n\n\n    @Throws(ArchiveException::class)\n    private fun openArchive(\n        pfd: ParcelFileDescriptor,\n        useCb: Boolean = true\n    ): Long {\n        val archive: Long = Archive.readNew()\n        var successful = false\n        try {\n            Archive.setCharset(archive, StandardCharsets.UTF_8.name().toByteArray())\n            Archive.readSupportFilterAll(archive)\n            Archive.readSupportFormatAll(archive)\n            if (useCb) {\n                Archive.readSetCallbackData(archive, pfd.fileDescriptor)\n                val buffer = ByteBuffer.allocateDirect(DEFAULT_BUFFER_SIZE)\n                Archive.readSetReadCallback<Any>(\n                    archive\n                ) { _: Long, fd: Any? ->\n                    buffer.clear()\n                    try {\n                        Os.read(fd as FileDescriptor?, buffer)\n                    } catch (e: ErrnoException) {\n                        throw ArchiveException(Archive.ERRNO_FATAL, \"Os.read\", e)\n                    } catch (e: InterruptedIOException) {\n                        throw ArchiveException(Archive.ERRNO_FATAL, \"Os.read\", e)\n                    }\n                    buffer.flip()\n                    buffer\n                }\n                Archive.readSetSkipCallback<Any>(\n                    archive\n                ) { _: Long, fd: Any?, request: Long ->\n                    try {\n                        Os.lseek(\n                            fd as FileDescriptor?, request, OsConstants.SEEK_CUR\n                        )\n                    } catch (e: ErrnoException) {\n                        throw ArchiveException(Archive.ERRNO_FATAL, \"Os.lseek\", e)\n                    }\n                    request\n                }\n                Archive.readSetSeekCallback<Any>(\n                    archive\n                ) { _: Long, fd: Any?, offset: Long, whence: Int ->\n                    try {\n                        return@readSetSeekCallback Os.lseek(\n                            fd as FileDescriptor?, offset, whence\n                        )\n                    } catch (e: ErrnoException) {\n                        throw ArchiveException(Archive.ERRNO_FATAL, \"Os.lseek\", e)\n                    }\n                }\n                Archive.readOpen1(archive)\n\n            } else {\n                Archive.readOpenFd(archive, pfd.fd, DEFAULT_BUFFER_SIZE.toLong())\n            }\n\n            successful = true\n            return archive\n\n\n        } finally {\n            if (!successful) {\n                Archive.free(archive)\n            }\n        }\n\n\n    }\n\n    /**\n     * 解压文件\n     */\n    @Throws(NullPointerException::class, SecurityException::class)\n    fun unArchive(\n        pfd: ParcelFileDescriptor,\n        destDir: File,\n        filter: ((String) -> Boolean)? = null\n    ): List<File> {\n        return unArchive(openArchive(pfd), destDir, filter)\n    }\n\n    /**\n     * 解压\n     */\n    @Throws(NullPointerException::class, SecurityException::class)\n    private fun unArchive(\n        archive: Long,\n        destDir: File?,\n        filter: ((String) -> Boolean)? = null\n    ): List<File> {\n        destDir ?: throw NullPointerException(\"解压路径不能为空\")\n        val files = arrayListOf<File>()\n\n\n        try {\n            var entry: Long\n\n            while (Archive.readNextHeader(archive).also { entry = it } != 0L) {\n                val entryName =\n                    getEntryString(ArchiveEntry.pathnameUtf8(entry), ArchiveEntry.pathname(entry))\n                        ?: continue\n                val entryFile = File(destDir, entryName)\n                if (!entryFile.canonicalPath.startsWith(destDir.canonicalPath)) {\n                    throw SecurityException(\"压缩文件只能解压到指定路径\")\n                }\n                val entryStat = ArchiveEntry.stat(entry)\n\n                //判断是否是文件夹\n                if (entryStat.isDir()) {\n                    if (!entryFile.exists()) {\n                        entryFile.mkdirs()\n                    }\n                    continue\n                }\n\n\n\n                if (entryFile.parentFile?.exists() != true) {\n                    entryFile.parentFile?.mkdirs()\n                }\n                if (filter != null && !filter.invoke(entryName)) continue\n                if (!entryFile.exists()) {\n                    entryFile.createNewFile()\n                    entryFile.setReadable(true)\n                    entryFile.setExecutable(true)\n                }\n\n                ParcelFileDescriptor.open(entryFile, ParcelFileDescriptor.MODE_WRITE_ONLY).use {\n                    Archive.readDataIntoFd(archive, it.fd)\n                    files.add(entryFile)\n                }\n\n\n            }\n        } finally {\n            Archive.free(archive)\n        }\n\n        return files\n\n    }\n\n    fun getFilesName(pfd: ParcelFileDescriptor, filter: ((String) -> Boolean)?): List<String> {\n        return getFilesName(openArchive(pfd), filter)\n    }\n\n    fun getByteArrayContent(inputStream: InputStream, path: String): ByteArray? {\n        val archive = openArchive(inputStream)\n        try {\n            var entry: Long\n            while (Archive.readNextHeader(archive).also { entry = it } != 0L) {\n                val entryName =\n                    getEntryString(ArchiveEntry.pathnameUtf8(entry), ArchiveEntry.pathname(entry))\n                        ?: continue\n\n                val entryStat = ArchiveEntry.stat(entry)\n\n                //判断是否是文件夹\n                if (entryStat.isDir()) {\n                    continue\n                }\n\n                if (entryName == path) {\n                    val byteBuffer = ByteBuffer.allocateDirect(DEFAULT_BUFFER_SIZE)\n                    val buffer = Buffer()\n                    while (true) {\n                        Archive.readData(archive, byteBuffer)\n                        byteBuffer.flip()\n                        if (!byteBuffer.hasRemaining()) {\n                            return buffer.readByteArray()\n                        }\n                        buffer.write(byteBuffer)\n                        byteBuffer.clear()\n                    }\n                }\n\n\n            }\n        } finally {\n            Archive.free(archive)\n        }\n        return null\n    }\n\n    @Throws(SecurityException::class)\n    private fun getFilesName(\n        archive: Long,\n        filter: ((String) -> Boolean)? = null\n    ): List<String> {\n        val fileNames = mutableListOf<String>()\n        try {\n\n\n            var entry: Long\n\n            while (Archive.readNextHeader(archive).also { entry = it } != 0L) {\n                val fileName =\n                    getEntryString(ArchiveEntry.pathnameUtf8(entry), ArchiveEntry.pathname(entry))\n                        ?: continue\n\n\n                val entryStat = ArchiveEntry.stat(entry)\n\n                if (entryStat.isDir()) {\n                    continue\n                }\n\n                if (filter != null && filter.invoke(fileName))\n                    fileNames.add(fileName)\n\n\n            }\n        } finally {\n            Archive.free(archive)\n        }\n\n        return fileNames\n    }\n\n    private fun ArchiveEntry.StructStat.isDir() = S_ISDIR(this.stMode)\n\n    private fun getEntryString(utf8: String?, bytes: ByteArray?): String? {\n        return utf8 ?: newStringFromBytes(bytes)\n    }\n\n    private fun newStringFromBytes(bytes: ByteArray?): String? {\n        bytes ?: return null\n        val cd = CharsetDetector()\n        cd.setText(bytes)\n        val c = cd.detectAll().first().name\n        return String(bytes, Charset.forName(c))\n\n    }\n\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/utils/compress/ZipUtils.kt",
    "content": "package io.legado.app.utils.compress\n\nimport android.annotation.SuppressLint\nimport io.legado.app.utils.DebugLog\nimport io.legado.app.utils.compress.ZipUtils.zipFile\nimport io.legado.app.utils.printOnDebug\nimport kotlinx.coroutines.Dispatchers.IO\nimport kotlinx.coroutines.withContext\nimport java.io.BufferedInputStream\nimport java.io.ByteArrayOutputStream\nimport java.io.File\nimport java.io.FileInputStream\nimport java.io.FileOutputStream\nimport java.io.IOException\nimport java.io.InputStream\nimport java.util.zip.GZIPOutputStream\nimport java.util.zip.ZipEntry\nimport java.util.zip.ZipFile\nimport java.util.zip.ZipInputStream\nimport java.util.zip.ZipOutputStream\n\n@SuppressLint(\"ObsoleteSdkInt\")\n@Suppress(\"unused\", \"MemberVisibilityCanBePrivate\")\nobject ZipUtils {\n\n    fun gzipByteArray(byteArray: ByteArray): ByteArray {\n        val byteOut = ByteArrayOutputStream()\n        val zip = GZIPOutputStream(byteOut)\n        return zip.use {\n            it.write(byteArray)\n            byteOut.use {\n                byteOut.toByteArray()\n            }\n        }\n    }\n\n    fun zipByteArray(byteArray: ByteArray, fileName: String): ByteArray {\n        val byteOut = ByteArrayOutputStream()\n        val zipOutputStream = ZipOutputStream(byteOut)\n        zipOutputStream.putNextEntry(ZipEntry(fileName))\n        zipOutputStream.write(byteArray)\n        zipOutputStream.closeEntry()\n        zipOutputStream.finish()\n        return zipOutputStream.use {\n            byteOut.use {\n                byteOut.toByteArray()\n            }\n        }\n    }\n\n    /**\n     * Zip the files.\n     *\n     * @param srcFiles    The source of files.\n     * @param zipFilePath The path of ZIP file.\n     * @return `true`: success<br></br>`false`: fail\n     * @throws IOException if an I/O error has occurred\n     */\n    suspend fun zipFiles(\n        srcFiles: Collection<String>,\n        zipFilePath: String\n    ): Boolean {\n        return zipFiles(srcFiles, zipFilePath, null)\n    }\n\n    /**\n     * Zip the files.\n     *\n     * @param srcFilePaths The paths of source files.\n     * @param zipFilePath  The path of ZIP file.\n     * @param comment      The comment.\n     * @return `true`: success<br></br>`false`: fail\n     * @throws IOException if an I/O error has occurred\n     */\n    suspend fun zipFiles(\n        srcFilePaths: Collection<String>?,\n        zipFilePath: String?,\n        comment: String?\n    ): Boolean = withContext(IO) {\n        if (srcFilePaths == null || zipFilePath == null) return@withContext false\n        ZipOutputStream(FileOutputStream(zipFilePath)).use {\n            for (srcFile in srcFilePaths) {\n                if (!zipFile(getFileByPath(srcFile)!!, \"\", it, comment))\n                    return@withContext false\n            }\n            return@withContext true\n        }\n    }\n\n    /**\n     * Zip the files.\n     *\n     * @param srcFiles The source of files.\n     * @param zipFile  The ZIP file.\n     * @param comment  The comment.\n     * @return `true`: success<br></br>`false`: fail\n     * @throws IOException if an I/O error has occurred\n     */\n    @Throws(IOException::class)\n    @JvmOverloads\n    fun zipFiles(\n        srcFiles: Collection<File>?,\n        zipFile: File?,\n        comment: String? = null\n    ): Boolean {\n        if (srcFiles == null || zipFile == null) return false\n        ZipOutputStream(FileOutputStream(zipFile)).use {\n            for (srcFile in srcFiles) {\n                if (!zipFile(srcFile, \"\", it, comment)) return false\n            }\n            return true\n        }\n    }\n\n    /**\n     * Zip the file.\n     *\n     * @param srcFilePath The path of source file.\n     * @param zipFilePath The path of ZIP file.\n     * @return `true`: success<br></br>`false`: fail\n     * @throws IOException if an I/O error has occurred\n     */\n    @Throws(IOException::class)\n    fun zipFile(\n        srcFilePath: String,\n        zipFilePath: String\n    ): Boolean {\n        return zipFile(getFileByPath(srcFilePath), getFileByPath(zipFilePath), null)\n    }\n\n    /**\n     * Zip the file.\n     *\n     * @param srcFilePath The path of source file.\n     * @param zipFilePath The path of ZIP file.\n     * @param comment     The comment.\n     * @return `true`: success<br></br>`false`: fail\n     * @throws IOException if an I/O error has occurred\n     */\n    @Throws(IOException::class)\n    fun zipFile(\n        srcFilePath: String,\n        zipFilePath: String,\n        comment: String\n    ): Boolean {\n        return zipFile(getFileByPath(srcFilePath), getFileByPath(zipFilePath), comment)\n    }\n\n    /**\n     * Zip the file.\n     *\n     * @param srcFile The source of file.\n     * @param zipFile The ZIP file.\n     * @param comment The comment.\n     * @return `true`: success<br></br>`false`: fail\n     * @throws IOException if an I/O error has occurred\n     */\n    @Throws(IOException::class)\n    @JvmOverloads\n    fun zipFile(\n        srcFile: File?,\n        zipFile: File?,\n        comment: String? = null\n    ): Boolean {\n        if (srcFile == null || zipFile == null) return false\n        ZipOutputStream(FileOutputStream(zipFile)).use { zos ->\n            return zipFile(srcFile, \"\", zos, comment)\n        }\n    }\n\n    @Throws(IOException::class)\n    private fun zipFile(\n        srcFile: File,\n        rootPath: String,\n        zos: ZipOutputStream,\n        comment: String?\n    ): Boolean {\n        var rootPath1 = rootPath\n        if (!srcFile.exists()) return true\n        rootPath1 = rootPath1 + (if (isSpace(rootPath1)) \"\" else File.separator) + srcFile.name\n        if (srcFile.isDirectory) {\n            val fileList = srcFile.listFiles()\n            if (fileList == null || fileList.isEmpty()) {\n                val entry = ZipEntry(\"$rootPath1/\")\n                entry.comment = comment\n                zos.putNextEntry(entry)\n                zos.closeEntry()\n            } else {\n                for (file in fileList) {\n                    if (!zipFile(file, rootPath1, zos, comment)) return false\n                }\n            }\n        } else {\n            BufferedInputStream(FileInputStream(srcFile)).use {\n                val entry = ZipEntry(rootPath1)\n                entry.comment = comment\n                zos.putNextEntry(entry)\n                it.copyTo(zos)\n                zos.closeEntry()\n            }\n        }\n        return true\n    }\n\n    @Throws(SecurityException::class)\n    fun unZipToPath(file: File, path: String, filter: ((String) -> Boolean)? = null): List<File> {\n        return FileInputStream(file).use {\n            unZipToPath(it, path, filter)\n        }\n    }\n\n    @Throws(SecurityException::class)\n    fun unZipToPath(file: File, dir: File, filter: ((String) -> Boolean)? = null): List<File> {\n        return FileInputStream(file).use {\n            unZipToPath(it, dir, filter)\n        }\n    }\n\n    @Throws(SecurityException::class)\n    fun unZipToPath(\n        inputStream: InputStream,\n        path: String,\n        filter: ((String) -> Boolean)? = null\n    ): List<File> {\n        return ZipInputStream(inputStream).use {\n            unZipToPath(it, File(path), filter)\n        }\n    }\n\n    @Throws(SecurityException::class)\n    fun unZipToPath(\n        inputStream: InputStream,\n        dir: File,\n        filter: ((String) -> Boolean)? = null\n    ): List<File> {\n        return ZipInputStream(inputStream).use {\n            unZipToPath(it, dir, filter)\n        }\n    }\n\n    @Throws(SecurityException::class)\n    private fun unZipToPath(\n        zipInputStream: ZipInputStream,\n        dir: File,\n        filter: ((String) -> Boolean)? = null\n    ): List<File> {\n        val files = arrayListOf<File>()\n        var entry: ZipEntry?\n        while (zipInputStream.nextEntry.also { entry = it } != null) {\n            val entryName = entry!!.name\n            val entryFile = File(dir, entryName)\n            if (!entryFile.canonicalPath.startsWith(dir.canonicalPath)) {\n                throw SecurityException(\"压缩文件只能解压到指定路径\")\n            }\n            if (entry.isDirectory) {\n                if (!entryFile.exists()) {\n                    entryFile.mkdirs()\n                }\n                continue\n            }\n            if (entryFile.parentFile?.exists() != true) {\n                entryFile.parentFile?.mkdirs()\n            }\n            if (filter != null && !filter.invoke(entryName)) continue\n            if (!entryFile.exists()) {\n                entryFile.createNewFile()\n                entryFile.setReadable(true)\n                entryFile.setExecutable(true)\n            }\n            FileOutputStream(entryFile).use {\n                zipInputStream.copyTo(it)\n                files.add(entryFile)\n            }\n        }\n        return files\n    }\n\n    /* 遍历目录获取所有文件名 */\n    @Throws(SecurityException::class)\n    fun getFilesName(\n        inputStream: InputStream,\n        filter: ((String) -> Boolean)? = null\n    ): List<String> {\n        return ZipInputStream(inputStream).use {\n            getFilesName(it, filter)\n        }\n    }\n\n    @Throws(SecurityException::class)\n    private fun getFilesName(\n        zipInputStream: ZipInputStream,\n        filter: ((String) -> Boolean)? = null\n    ): List<String> {\n        val fileNames = mutableListOf<String>()\n        var entry: ZipEntry?\n        while (zipInputStream.nextEntry.also { entry = it } != null) {\n            if (entry!!.isDirectory) {\n                continue\n            }\n            val fileName = entry.name\n            if (filter != null && filter.invoke(fileName))\n                fileNames.add(fileName)\n        }\n        return fileNames\n    }\n\n    /**\n     * Return the files' path in ZIP file.\n     *\n     * @param zipFilePath The path of ZIP file.\n     * @return the files' path in ZIP file\n     * @throws IOException if an I/O error has occurred\n     */\n    @Throws(IOException::class)\n    fun getFilesPath(zipFilePath: String): List<String>? {\n        return getFilesPath(getFileByPath(zipFilePath))\n    }\n\n    /**\n     * Return the files' path in ZIP file.\n     *\n     * @param zipFile The ZIP file.\n     * @return the files' path in ZIP file\n     * @throws IOException if an I/O error has occurred\n     */\n    @Throws(IOException::class)\n    fun getFilesPath(zipFile: File?): List<String>? {\n        if (zipFile == null) return null\n        val paths = ArrayList<String>()\n        val zip = ZipFile(zipFile)\n        val entries = zip.entries()\n        while (entries.hasMoreElements()) {\n            val entryName = (entries.nextElement() as ZipEntry).name\n            if (entryName.contains(\"../\")) {\n                DebugLog.e(javaClass.name, \"entryName: $entryName is dangerous!\")\n                paths.add(entryName)\n            } else {\n                paths.add(entryName)\n            }\n        }\n        zip.close()\n        return paths\n    }\n\n    /**\n     * Return the files' comment in ZIP file.\n     *\n     * @param zipFilePath The path of ZIP file.\n     * @return the files' comment in ZIP file\n     * @throws IOException if an I/O error has occurred\n     */\n    @Throws(IOException::class)\n    fun getComments(zipFilePath: String): List<String>? {\n        return getComments(getFileByPath(zipFilePath))\n    }\n\n    /**\n     * Return the files' comment in ZIP file.\n     *\n     * @param zipFile The ZIP file.\n     * @return the files' comment in ZIP file\n     * @throws IOException if an I/O error has occurred\n     */\n    @Throws(IOException::class)\n    fun getComments(zipFile: File?): List<String>? {\n        if (zipFile == null) return null\n        val comments = ArrayList<String>()\n        val zip = ZipFile(zipFile)\n        val entries = zip.entries()\n        while (entries.hasMoreElements()) {\n            val entry = entries.nextElement() as ZipEntry\n            comments.add(entry.comment)\n        }\n        zip.close()\n        return comments\n    }\n\n    private fun createOrExistsDir(file: File?): Boolean {\n        return file != null && if (file.exists()) file.isDirectory else file.mkdirs()\n    }\n\n    private fun createOrExistsFile(file: File?): Boolean {\n        if (file == null) return false\n        if (file.exists()) return file.isFile\n        if (!createOrExistsDir(file.parentFile)) return false\n        return try {\n            file.createNewFile()\n        } catch (e: IOException) {\n            e.printOnDebug()\n            false\n        }\n    }\n\n    private fun getFileByPath(filePath: String): File? {\n        return if (isSpace(filePath)) null else File(filePath)\n    }\n\n    private fun isSpace(s: String?): Boolean {\n        if (s == null) return true\n        var i = 0\n        val len = s.length\n        while (i < len) {\n            if (!Character.isWhitespace(s[i])) {\n                return false\n            }\n            ++i\n        }\n        return true\n    }\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/utils/objectpool/BaseObjectPool.kt",
    "content": "package io.legado.app.utils.objectpool\n\nimport androidx.annotation.CallSuper\nimport androidx.core.util.Pools\n\nabstract class BaseObjectPool<T : Any>(size: Int) : ObjectPool<T> {\n\n    open val pool = Pools.SimplePool<T>(size)\n\n    override fun obtain(): T {\n        return pool.acquire() ?: create()\n    }\n\n    @CallSuper\n    override fun recycle(target: T) {\n        pool.release(target)\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/utils/objectpool/BaseSafeObjectPool.kt",
    "content": "package io.legado.app.utils.objectpool\n\nimport androidx.core.util.Pools\n\nabstract class BaseSafeObjectPool<T : Any>(size: Int): BaseObjectPool<T>(size) {\n\n    override val pool = Pools.SynchronizedPool<T>(size)\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/utils/objectpool/ObjectPool.kt",
    "content": "package io.legado.app.utils.objectpool\n\ninterface ObjectPool<T> {\n\n    fun obtain(): T\n\n    fun recycle(target: T)\n\n    fun create(): T\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/utils/objectpool/ObjectPoolExtensions.kt",
    "content": "package io.legado.app.utils.objectpool\n\nfun <T> ObjectPool<T>.synchronized(): ObjectPool<T> = ObjectPoolLocked(this)\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/utils/objectpool/ObjectPoolLocked.kt",
    "content": "package io.legado.app.utils.objectpool\n\nclass ObjectPoolLocked<T>(private val delegate: ObjectPool<T>) : ObjectPool<T> by delegate {\n\n    @Synchronized\n    override fun obtain(): T {\n        return delegate.obtain()\n    }\n\n    @Synchronized\n    override fun recycle(target: T) {\n        return delegate.recycle(target)\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/utils/viewbindingdelegate/ActivityViewBindings.kt",
    "content": "@file:Suppress(\"RedundantVisibilityModifier\", \"unused\")\n\npackage io.legado.app.utils.viewbindingdelegate\n\nimport android.view.LayoutInflater\nimport androidx.core.app.ComponentActivity\nimport androidx.viewbinding.ViewBinding\n\n/**\n * Create new [ViewBinding] associated with the [ComponentActivity]\n */\n@JvmName(\"viewBindingActivity\")\ninline fun <T : ViewBinding> ComponentActivity.viewBinding(\n    crossinline bindingInflater: (LayoutInflater) -> T,\n    setContentView: Boolean = false\n) = lazy(LazyThreadSafetyMode.SYNCHRONIZED) {\n    val binding = bindingInflater.invoke(layoutInflater)\n    if (setContentView) {\n        setContentView(binding.root)\n    }\n    binding\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/utils/viewbindingdelegate/FragmentViewBindings.kt",
    "content": "@file:Suppress(\"RedundantVisibilityModifier\", \"unused\", \"UnusedReceiverParameter\")\n@file:JvmName(\"ReflectionFragmentViewBindings\")\n\npackage io.legado.app.utils.viewbindingdelegate\n\nimport android.view.View\nimport androidx.annotation.IdRes\nimport androidx.fragment.app.Fragment\nimport androidx.viewbinding.ViewBinding\n\nprivate class FragmentViewBindingProperty<F : Fragment, T : ViewBinding>(\n    viewBinder: (F) -> T\n) : ViewBindingProperty<F, T>(viewBinder) {\n\n    override fun getLifecycleOwner(thisRef: F) = thisRef.viewLifecycleOwner\n}\n\n/**\n * Create new [ViewBinding] associated with the [Fragment]\n */\n@JvmName(\"viewBindingFragment\")\npublic fun <F : Fragment, T : ViewBinding> Fragment.viewBinding(viewBinder: (F) -> T): ViewBindingProperty<F, T> {\n    return FragmentViewBindingProperty(viewBinder)\n}\n\n/**\n * Create new [ViewBinding] associated with the [Fragment]\n *\n * @param vbFactory Function that create new instance of [ViewBinding]. `MyViewBinding::bind` can be used\n * @param viewProvider Provide a [View] from the Fragment. By default call [Fragment.requireView]\n */\n@JvmName(\"viewBindingFragment\")\npublic inline fun <F : Fragment, T : ViewBinding> Fragment.viewBinding(\n    crossinline vbFactory: (View) -> T,\n    crossinline viewProvider: (F) -> View = Fragment::requireView\n): ViewBindingProperty<F, T> {\n    return viewBinding { fragment: F -> vbFactory(viewProvider(fragment)) }\n}\n\n/**\n * Create new [ViewBinding] associated with the [Fragment]\n *\n * @param vbFactory Function that create new instance of [ViewBinding]. `MyViewBinding::bind` can be used\n * @param viewBindingRootId Root view's id that will be used as root for the view binding\n */\n@JvmName(\"viewBindingFragment\")\npublic inline fun <T : ViewBinding> Fragment.viewBinding(\n    crossinline vbFactory: (View) -> T,\n    @IdRes viewBindingRootId: Int\n): ViewBindingProperty<Fragment, T> {\n    return viewBinding(vbFactory) { fragment: Fragment ->\n        fragment.requireView().findViewById(viewBindingRootId)\n    }\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/utils/viewbindingdelegate/ViewBindingProperty.kt",
    "content": "@file:Suppress(\"RedundantVisibilityModifier\")\n\npackage io.legado.app.utils.viewbindingdelegate\n\nimport android.os.Handler\nimport android.os.Looper\nimport androidx.annotation.MainThread\nimport androidx.lifecycle.DefaultLifecycleObserver\nimport androidx.lifecycle.Lifecycle\nimport androidx.lifecycle.LifecycleOwner\nimport androidx.viewbinding.ViewBinding\nimport kotlin.properties.ReadOnlyProperty\nimport kotlin.reflect.KProperty\n\npublic abstract class ViewBindingProperty<in R : Any, T : ViewBinding>(\n    private val viewBinder: (R) -> T\n) : ReadOnlyProperty<R, T> {\n\n    private var viewBinding: T? = null\n    private val lifecycleObserver = ClearOnDestroyLifecycleObserver()\n    private var thisRef: R? = null\n\n    protected abstract fun getLifecycleOwner(thisRef: R): LifecycleOwner\n\n    @MainThread\n    public override fun getValue(thisRef: R, property: KProperty<*>): T {\n        viewBinding?.let { return it }\n\n        this.thisRef = thisRef\n        val lifecycle = getLifecycleOwner(thisRef).lifecycle\n        if (lifecycle.currentState == Lifecycle.State.DESTROYED) {\n            mainHandler.post { viewBinding = null }\n        } else {\n            lifecycle.addObserver(lifecycleObserver)\n        }\n        return viewBinder(thisRef).also { viewBinding = it }\n    }\n\n    @MainThread\n    public fun clear() {\n        val thisRef = thisRef ?: return\n        this.thisRef = null\n        getLifecycleOwner(thisRef).lifecycle.removeObserver(lifecycleObserver)\n        mainHandler.post { viewBinding = null }\n    }\n\n    private inner class ClearOnDestroyLifecycleObserver : DefaultLifecycleObserver {\n\n        @MainThread\n        override fun onDestroy(owner: LifecycleOwner): Unit = clear()\n    }\n\n    private companion object {\n\n        private val mainHandler = Handler(Looper.getMainLooper())\n    }\n}"
  },
  {
    "path": "app/src/main/java/io/legado/app/web/HttpServer.kt",
    "content": "package io.legado.app.web\n\nimport android.graphics.Bitmap\nimport fi.iki.elonen.NanoHTTPD\nimport io.legado.app.api.ReturnData\nimport io.legado.app.api.controller.BookController\nimport io.legado.app.api.controller.BookSourceController\nimport io.legado.app.api.controller.ReplaceRuleController\nimport io.legado.app.api.controller.RssSourceController\nimport io.legado.app.help.coroutine.Coroutine\nimport io.legado.app.service.WebService\nimport io.legado.app.utils.GSON\nimport io.legado.app.utils.LogUtils\nimport io.legado.app.utils.stackTraceStr\nimport io.legado.app.web.utils.AssetsWeb\nimport kotlinx.coroutines.runBlocking\nimport okio.Pipe\nimport okio.buffer\nimport java.io.ByteArrayInputStream\nimport java.io.ByteArrayOutputStream\n\nclass HttpServer(port: Int) : NanoHTTPD(port) {\n    private val assetsWeb = AssetsWeb(\"web\")\n\n    override fun serve(session: IHTTPSession): Response {\n        WebService.serve()\n        var returnData: ReturnData? = null\n        val ct = ContentType(session.headers[\"content-type\"]).tryUTF8()\n        session.headers[\"content-type\"] = ct.contentTypeHeader\n        var uri = session.uri\n\n        val startAt = System.currentTimeMillis()\n        LogUtils.d(TAG) {\n            \"${session.method.name} - $uri - ${session.queryParameterString} - Start($startAt)\"\n        }\n\n        try {\n            when (session.method) {\n                Method.OPTIONS -> {\n                    val response = newFixedLengthResponse(\"\")\n                    response.addHeader(\"Access-Control-Allow-Methods\", \"POST\")\n                    response.addHeader(\"Access-Control-Allow-Headers\", \"content-type\")\n                    response.addHeader(\"Access-Control-Allow-Origin\", session.headers[\"origin\"])\n                    //response.addHeader(\"Access-Control-Max-Age\", \"3600\");\n                    return response\n                }\n\n                Method.POST -> {\n                    val files = HashMap<String, String>()\n                    session.parseBody(files)\n                    val postData = files[\"postData\"]\n\n                    returnData = runBlocking {\n                        when (uri) {\n                            \"/saveBookSource\" -> BookSourceController.saveSource(postData)\n                            \"/saveBookSources\" -> BookSourceController.saveSources(postData)\n                            \"/deleteBookSources\" -> BookSourceController.deleteSources(postData)\n                            \"/saveBook\" -> BookController.saveBook(postData)\n                            \"/deleteBook\" -> BookController.deleteBook(postData)\n                            \"/saveBookProgress\" -> BookController.saveBookProgress(postData)\n                            \"/addLocalBook\" -> BookController.addLocalBook(session.parameters, files)\n                            \"/saveReadConfig\" -> BookController.saveWebReadConfig(postData)\n                            \"/saveRssSource\" -> RssSourceController.saveSource(postData)\n                            \"/saveRssSources\" -> RssSourceController.saveSources(postData)\n                            \"/deleteRssSources\" -> RssSourceController.deleteSources(postData)\n                            \"/saveReplaceRule\" -> ReplaceRuleController.saveRule(postData)\n                            \"/deleteReplaceRule\" -> ReplaceRuleController.delete(postData)\n                            \"/testReplaceRule\" -> ReplaceRuleController.testRule(postData)\n                            else -> null\n                        }\n                    }\n                }\n\n                Method.GET -> {\n                    val parameters = session.parameters\n\n                    returnData = when (uri) {\n                        \"/getBookSource\" -> BookSourceController.getSource(parameters)\n                        \"/getBookSources\" -> BookSourceController.sources\n                        \"/getBookshelf\" -> BookController.bookshelf\n                        \"/getChapterList\" -> BookController.getChapterList(parameters)\n                        \"/refreshToc\" -> BookController.refreshToc(parameters)\n                        \"/getBookContent\" -> BookController.getBookContent(parameters)\n                        \"/cover\" -> BookController.getCover(parameters)\n                        \"/image\" -> BookController.getImg(parameters)\n                        \"/getReadConfig\" -> BookController.getWebReadConfig()\n                        \"/getRssSource\" -> RssSourceController.getSource(parameters)\n                        \"/getRssSources\" -> RssSourceController.sources\n                        \"/getReplaceRules\" -> ReplaceRuleController.allRules\n                        else -> null\n                    }\n                }\n\n                else -> Unit\n            }\n\n            if (returnData == null) {\n                if (uri.endsWith(\"/\"))\n                    uri += \"index.html\"\n                return assetsWeb.getResponse(uri)\n            }\n\n            val response = if (returnData.data is Bitmap) {\n                val outputStream = ByteArrayOutputStream()\n                (returnData.data as Bitmap).compress(Bitmap.CompressFormat.PNG, 100, outputStream)\n                val byteArray = outputStream.toByteArray()\n                outputStream.close()\n                val inputStream = ByteArrayInputStream(byteArray)\n                newFixedLengthResponse(\n                    Response.Status.OK,\n                    \"image/png\",\n                    inputStream,\n                    byteArray.size.toLong()\n                )\n            } else {\n                val data = returnData.data\n                if (data is List<*> && data.size > 3000) {\n                    val pipe = Pipe(16 * 1024)\n                    Coroutine.async {\n                        pipe.sink.buffer().outputStream().bufferedWriter(Charsets.UTF_8).use {\n                            GSON.toJson(returnData, it)\n                        }\n                    }\n                    newChunkedResponse(\n                        Response.Status.OK,\n                        \"application/json\",\n                        pipe.source.buffer().inputStream()\n                    )\n                } else {\n                    newFixedLengthResponse(GSON.toJson(returnData))\n                }\n            }\n            response.addHeader(\"Access-Control-Allow-Methods\", \"GET, POST\")\n            response.addHeader(\"Access-Control-Allow-Origin\", session.headers[\"origin\"])\n            LogUtils.d(TAG) {\n                \"${session.method.name} - $uri - ${session.queryParameterString} - End($startAt)\"\n            }\n            return response\n        } catch (e: Exception) {\n            LogUtils.d(TAG) {\n                \"${session.method.name} - $uri - ${session.queryParameterString} - Error End($startAt)\\n$e\\n${e.stackTraceStr}\"\n            }\n            return newFixedLengthResponse(e.message)\n        }\n\n    }\n\n    companion object {\n        private const val TAG = \"HttpServer\"\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/web/ReadMe.md",
    "content": "# web服务\n\n* controller 数据操作\n* HttpServer http服务\n* WebSocketServer 持续通讯服务"
  },
  {
    "path": "app/src/main/java/io/legado/app/web/WebSocketServer.kt",
    "content": "package io.legado.app.web\n\nimport fi.iki.elonen.NanoWSD\nimport io.legado.app.service.WebService\nimport io.legado.app.web.socket.*\n\nclass WebSocketServer(port: Int) : NanoWSD(port) {\n\n    override fun openWebSocket(handshake: IHTTPSession): WebSocket? {\n        WebService.serve()\n        return when (handshake.uri) {\n            \"/bookSourceDebug\" -> {\n                BookSourceDebugWebSocket(handshake)\n            }\n            \"/rssSourceDebug\" -> {\n                RssSourceDebugWebSocket(handshake)\n            }\n            \"/searchBook\" -> {\n                BookSearchWebSocket(handshake)\n            }\n            else -> null\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/web/socket/BookSearchWebSocket.kt",
    "content": "package io.legado.app.web.socket\n\nimport fi.iki.elonen.NanoHTTPD\nimport fi.iki.elonen.NanoWSD\nimport io.legado.app.R\nimport io.legado.app.data.entities.SearchBook\nimport io.legado.app.help.config.AppConfig\nimport io.legado.app.model.webBook.SearchModel\nimport io.legado.app.ui.book.search.SearchScope\nimport io.legado.app.utils.GSON\nimport io.legado.app.utils.fromJsonObject\nimport io.legado.app.utils.isJson\nimport kotlinx.coroutines.CoroutineScope\nimport kotlinx.coroutines.Dispatchers.IO\nimport kotlinx.coroutines.MainScope\nimport kotlinx.coroutines.cancel\nimport kotlinx.coroutines.delay\nimport kotlinx.coroutines.launch\nimport splitties.init.appCtx\nimport java.io.IOException\n\nclass BookSearchWebSocket(handshakeRequest: NanoHTTPD.IHTTPSession) :\n    NanoWSD.WebSocket(handshakeRequest),\n    CoroutineScope by MainScope(),\n    SearchModel.CallBack {\n\n    private val normalClosure = NanoWSD.WebSocketFrame.CloseCode.NormalClosure\n    private val searchModel = SearchModel(this, this)\n\n    private val SEARCH_FINISH = \"Search finish\"\n\n    override fun onOpen() {\n        launch(IO) {\n            kotlin.runCatching {\n                while (isOpen) {\n                    ping(\"ping\".toByteArray())\n                    delay(30000)\n                }\n            }\n        }\n    }\n\n    override fun onClose(\n        code: NanoWSD.WebSocketFrame.CloseCode,\n        reason: String,\n        initiatedByRemote: Boolean\n    ) {\n        cancel()\n        searchModel.close()\n    }\n\n    override fun onMessage(message: NanoWSD.WebSocketFrame) {\n        launch(IO) {\n            kotlin.runCatching {\n                if (!message.textPayload.isJson()) {\n                    send(\"数据必须为Json格式\")\n                    close(normalClosure, SEARCH_FINISH, false)\n                    return@launch\n                }\n                val searchMap =\n                    GSON.fromJsonObject<Map<String, String>>(message.textPayload).getOrNull()\n                if (searchMap != null) {\n                    val key = searchMap[\"key\"]\n                    if (key.isNullOrBlank()) {\n                        send(appCtx.getString(R.string.cannot_empty))\n                        close(normalClosure, SEARCH_FINISH, false)\n                        return@launch\n                    }\n                    searchModel.search(System.currentTimeMillis(), key)\n                }\n            }\n        }\n    }\n\n    override fun onPong(pong: NanoWSD.WebSocketFrame) {\n\n    }\n\n    override fun onException(exception: IOException) {\n\n    }\n\n    override fun getSearchScope(): SearchScope = SearchScope(AppConfig.searchScope)\n\n    override fun onSearchStart() {\n\n    }\n\n    override fun onSearchSuccess(searchBooks: List<SearchBook>) {\n        send(GSON.toJson(searchBooks))\n    }\n\n    override fun onSearchFinish(isEmpty: Boolean, hasMore: Boolean) = close(normalClosure, SEARCH_FINISH, false)\n\n    override fun onSearchCancel(exception: Throwable?) = close(normalClosure, exception?.toString() ?: SEARCH_FINISH, false)\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/web/socket/BookSourceDebugWebSocket.kt",
    "content": "package io.legado.app.web.socket\n\n\nimport fi.iki.elonen.NanoHTTPD\nimport fi.iki.elonen.NanoWSD\nimport io.legado.app.R\nimport io.legado.app.data.appDb\nimport io.legado.app.model.Debug\nimport io.legado.app.utils.*\nimport kotlinx.coroutines.*\nimport kotlinx.coroutines.Dispatchers.IO\nimport splitties.init.appCtx\n\nimport java.io.IOException\n\n/**\n * web端书源调试\n */\nclass BookSourceDebugWebSocket(handshakeRequest: NanoHTTPD.IHTTPSession) :\n    NanoWSD.WebSocket(handshakeRequest),\n    CoroutineScope by MainScope(),\n    Debug.Callback {\n\n    private val notPrintState = arrayOf(10, 20, 30, 40)\n\n    override fun onOpen() {\n        launch(IO) {\n            kotlin.runCatching {\n                while (isOpen) {\n                    ping(\"ping\".toByteArray())\n                    delay(30000)\n                }\n            }\n        }\n    }\n\n    override fun onClose(\n        code: NanoWSD.WebSocketFrame.CloseCode,\n        reason: String,\n        initiatedByRemote: Boolean\n    ) {\n        cancel()\n        Debug.cancelDebug(true)\n    }\n\n    override fun onMessage(message: NanoWSD.WebSocketFrame) {\n        launch(IO) {\n            kotlin.runCatching {\n                if (!message.textPayload.isJson()) {\n                    send(\"数据必须为Json格式\")\n                    close(NanoWSD.WebSocketFrame.CloseCode.NormalClosure, \"调试结束\", false)\n                    return@launch\n                }\n                val debugBean =\n                    GSON.fromJsonObject<Map<String, String>>(message.textPayload).getOrNull()\n                if (debugBean != null) {\n                    val tag = debugBean[\"tag\"]\n                    val key = debugBean[\"key\"]\n                    if (tag.isNullOrBlank() || key.isNullOrBlank()) {\n                        send(appCtx.getString(R.string.cannot_empty))\n                        close(NanoWSD.WebSocketFrame.CloseCode.NormalClosure, \"调试结束\", false)\n                        return@launch\n                    }\n                    appDb.bookSourceDao.getBookSource(tag)?.let {\n                        Debug.callback = this@BookSourceDebugWebSocket\n                        Debug.startDebug(this, it, key)\n                    }\n                } else {\n                    send(\"数据必须为Json格式\")\n                    close(NanoWSD.WebSocketFrame.CloseCode.NormalClosure, \"调试结束\", false)\n                    return@launch\n                }\n            }\n        }\n    }\n\n    override fun onPong(pong: NanoWSD.WebSocketFrame) {\n\n    }\n\n    override fun onException(exception: IOException) {\n        Debug.cancelDebug(true)\n    }\n\n    override fun printLog(state: Int, msg: String) {\n        if (state in notPrintState) {\n            return\n        }\n        runOnIO {\n            runCatching {\n                send(msg)\n                if (state == -1 || state == 1000) {\n                    Debug.cancelDebug(true)\n                    close(NanoWSD.WebSocketFrame.CloseCode.NormalClosure, \"调试结束\", false)\n                }\n            }.onFailure {\n                it.printOnDebug()\n            }\n        }\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/web/socket/RssSourceDebugWebSocket.kt",
    "content": "package io.legado.app.web.socket\n\n\nimport fi.iki.elonen.NanoHTTPD\nimport fi.iki.elonen.NanoWSD\nimport io.legado.app.R\nimport io.legado.app.data.appDb\nimport io.legado.app.model.Debug\nimport io.legado.app.utils.*\nimport kotlinx.coroutines.*\nimport kotlinx.coroutines.Dispatchers.IO\nimport splitties.init.appCtx\n\nimport java.io.IOException\n\n/**\n * web端订阅源调试\n */\nclass RssSourceDebugWebSocket(handshakeRequest: NanoHTTPD.IHTTPSession) :\n    NanoWSD.WebSocket(handshakeRequest),\n    CoroutineScope by MainScope(),\n    Debug.Callback {\n\n    private val notPrintState = arrayOf(10, 20, 30, 40)\n\n    override fun onOpen() {\n        launch(IO) {\n            kotlin.runCatching {\n                while (isOpen) {\n                    ping(\"ping\".toByteArray())\n                    delay(30000)\n                }\n            }\n        }\n    }\n\n    override fun onClose(\n        code: NanoWSD.WebSocketFrame.CloseCode,\n        reason: String,\n        initiatedByRemote: Boolean\n    ) {\n        cancel()\n        Debug.cancelDebug(true)\n    }\n\n    override fun onMessage(message: NanoWSD.WebSocketFrame) {\n        launch(IO) {\n            kotlin.runCatching {\n                if (!message.textPayload.isJson()) {\n                    send(\"数据必须为Json格式\")\n                    close(NanoWSD.WebSocketFrame.CloseCode.NormalClosure, \"调试结束\", false)\n                    return@launch\n                }\n                val debugBean =\n                    GSON.fromJsonObject<Map<String, String>>(message.textPayload).getOrNull()\n                if (debugBean != null) {\n                    val tag = debugBean[\"tag\"]\n                    if (tag.isNullOrBlank()) {\n                        send(appCtx.getString(R.string.cannot_empty))\n                        close(NanoWSD.WebSocketFrame.CloseCode.NormalClosure, \"调试结束\", false)\n                        return@launch\n                    }\n                    appDb.rssSourceDao.getByKey(tag)?.let {\n                        Debug.callback = this@RssSourceDebugWebSocket\n                        Debug.startDebug(this, it)\n                    }\n                }\n            }\n        }\n    }\n\n    override fun onPong(pong: NanoWSD.WebSocketFrame) {\n\n    }\n\n    override fun onException(exception: IOException) {\n        Debug.cancelDebug(true)\n    }\n\n    override fun printLog(state: Int, msg: String) {\n        if (state in notPrintState) {\n            return\n        }\n        runOnIO {\n            runCatching {\n                send(msg)\n                if (state == -1 || state == 1000) {\n                    Debug.cancelDebug(true)\n                    close(NanoWSD.WebSocketFrame.CloseCode.NormalClosure, \"调试结束\", false)\n                }\n            }.onFailure {\n                it.printOnDebug()\n            }\n        }\n    }\n\n}\n"
  },
  {
    "path": "app/src/main/java/io/legado/app/web/utils/AssetsWeb.kt",
    "content": "package io.legado.app.web.utils\n\nimport android.content.res.AssetManager\nimport android.text.TextUtils\nimport fi.iki.elonen.NanoHTTPD\nimport splitties.init.appCtx\nimport java.io.File\nimport java.io.IOException\n\n\nclass AssetsWeb(rootPath: String) {\n    private val assetManager: AssetManager = appCtx.assets\n    private var rootPath = \"web\"\n\n    init {\n        if (!TextUtils.isEmpty(rootPath)) {\n            this.rootPath = rootPath\n        }\n    }\n\n    @Throws(IOException::class)\n    fun getResponse(path: String): NanoHTTPD.Response {\n        var path1 = path\n        path1 = (rootPath + path1).replace(\"/+\".toRegex(), File.separator)\n        val inputStream = assetManager.open(path1)\n        return NanoHTTPD.newChunkedResponse(\n            NanoHTTPD.Response.Status.OK,\n            getMimeType(path1),\n            inputStream\n        )\n    }\n\n    private fun getMimeType(path: String): String {\n        val suffix = path.substring(path.lastIndexOf(\".\"))\n        return when {\n            suffix.equals(\".html\", ignoreCase = true)\n                    || suffix.equals(\".htm\", ignoreCase = true) -> \"text/html\"\n            suffix.equals(\".js\", ignoreCase = true) -> \"text/javascript\"\n            suffix.equals(\".css\", ignoreCase = true) -> \"text/css\"\n            suffix.equals(\".ico\", ignoreCase = true) -> \"image/x-icon\"\n            suffix.equals(\".jpg\", ignoreCase = true) -> \"image/jpg\"\n            else -> \"text/html\"\n        }\n    }\n}\n"
  },
  {
    "path": "app/src/main/res/anim/anim_none.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<set>\n\n</set>"
  },
  {
    "path": "app/src/main/res/anim/anim_readbook_bottom_in.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<set xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:duration=\"150\">\n    <translate\n        android:fromYDelta=\"100%\"\n        android:toYDelta=\"0\" />\n</set>"
  },
  {
    "path": "app/src/main/res/anim/anim_readbook_bottom_out.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<set xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:duration=\"200\">\n    <translate\n        android:fromYDelta=\"0%\"\n        android:toYDelta=\"100%\" />\n</set>"
  },
  {
    "path": "app/src/main/res/anim/anim_readbook_top_in.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<set xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:duration=\"200\">\n    <translate\n        android:fromYDelta=\"-100%\"\n        android:toYDelta=\"0\" />\n</set>"
  },
  {
    "path": "app/src/main/res/anim/anim_readbook_top_out.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<set xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:duration=\"200\">\n    <translate\n        android:fromYDelta=\"0\"\n        android:toYDelta=\"-100%\" />\n</set>"
  },
  {
    "path": "app/src/main/res/color/selector_image.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <item android:state_enabled=\"false\" android:color=\"@color/disabled\" />\n    <item android:state_pressed=\"true\" android:color=\"@color/btn_bg_press\" />\n    <item android:state_focused=\"true\" android:color=\"@color/btn_bg_press_2\" />\n    <item android:color=\"@color/primaryText\" />\n</selector>"
  },
  {
    "path": "app/src/main/res/drawable/bg_chapter_item_divider.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:shape=\"line\">\n    <stroke\n        android:width=\"1px\"\n        android:color=\"@color/bg_divider_line\"\n        android:dashWidth=\"3dp\"\n        android:dashGap=\"3dp\" />\n\n</shape>"
  },
  {
    "path": "app/src/main/res/drawable/bg_edit.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:shape=\"rectangle\">\n    <stroke\n        android:width=\"1dp\"\n        android:color=\"@color/btn_bg_press\" />\n</shape>"
  },
  {
    "path": "app/src/main/res/drawable/bg_eink_border_bottom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<layer-list xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <item>\n        <shape>\n            <!--    边框颜色    -->\n            <solid android:color=\"@color/divider\" />\n        </shape>\n    </item>\n    <!--    边框位置和粗细    -->\n    <item android:bottom=\"1dp\">\n        <shape>\n            <!--    背景颜色    -->\n            <solid android:color=\"@color/white\" />\n        </shape>\n    </item>\n</layer-list>"
  },
  {
    "path": "app/src/main/res/drawable/bg_eink_border_dialog.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <solid android:color=\"@color/white\" />\n    <stroke\n        android:width=\"1dp\"\n        android:color=\"@color/divider\" />\n    <corners android:radius=\"2dp\" />\n</shape>"
  },
  {
    "path": "app/src/main/res/drawable/bg_eink_border_top.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<layer-list xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <item>\n        <shape>\n            <!--    边框颜色    -->\n            <solid android:color=\"@color/divider\" />\n        </shape>\n    </item>\n    <!--    边框位置和粗细    -->\n    <item android:top=\"1dp\">\n        <shape>\n            <!--    背景颜色    -->\n            <solid android:color=\"@color/white\" />\n        </shape>\n    </item>\n</layer-list>"
  },
  {
    "path": "app/src/main/res/drawable/bg_find_book_group.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\" >\n    <!-- 角度 -->\n    <corners android:radius=\"6dp\"/>\n    <!-- 填充色 -->\n    <solid android:color=\"@color/transparent10\" />\n</shape>"
  },
  {
    "path": "app/src/main/res/drawable/bg_gradient.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <!--实现应用背景颜色渐变-->\n    <gradient\n        android:startColor=\"#000000\"\n        android:endColor=\"#00000000\"\n        android:angle=\"90\" />\n</shape>"
  },
  {
    "path": "app/src/main/res/drawable/bg_img_border.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <solid android:color=\"@color/background\" />\n    <stroke\n        android:width=\"1dp\"\n        android:color=\"@color/bg_divider_line\" />\n    <corners android:radius=\"1dp\" />\n    <padding\n        android:left=\"0dp\"\n        android:top=\"0dp\"\n        android:right=\"0dp\"\n        android:bottom=\"0dp\" />\n</shape>"
  },
  {
    "path": "app/src/main/res/drawable/bg_item_focused_on_tv.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <item android:drawable=\"@color/btn_bg_press\" android:state_focused=\"true\" />\n</selector>"
  },
  {
    "path": "app/src/main/res/drawable/bg_popup_menu.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\"\n       android:shape=\"rectangle\">\n\n    <solid android:color=\"@color/background_menu\"/>\n\n    <corners android:radius=\"2dp\"/>\n\n</shape>"
  },
  {
    "path": "app/src/main/res/drawable/bg_prefs_color.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <item android:drawable=\"@color/btn_bg_press\" android:state_focused=\"true\" />\n    <item android:drawable=\"@color/btn_bg_press\" android:state_pressed=\"true\" />\n</selector>"
  },
  {
    "path": "app/src/main/res/drawable/bg_searchview.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\" >\n    <!-- 角度 -->\n    <corners android:radius=\"35dp\"/>\n    <!-- 填充色 -->\n    <solid android:color=\"@color/transparent10\" />\n    <!-- 描边 设置线宽及颜色 -->\n    <stroke\n        android:color=\"@color/transparent10\"\n        android:width=\"0.5dp\" />\n</shape>"
  },
  {
    "path": "app/src/main/res/drawable/bg_shadow_bottom.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <!--实现应用背景颜色渐变-->\n    <gradient\n        android:startColor=\"#17000000\"\n        android:endColor=\"#00000000\"\n        android:angle=\"90\" />\n</shape>"
  },
  {
    "path": "app/src/main/res/drawable/bg_shadow_bottom_night.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <!--实现应用背景颜色渐变-->\n    <gradient\n        android:startColor=\"#17303030\"\n        android:endColor=\"#00FFFFFF\"\n        android:angle=\"90\" />\n</shape>"
  },
  {
    "path": "app/src/main/res/drawable/bg_shadow_top.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <!--实现应用背景颜色渐变-->\n    <gradient\n        android:startColor=\"#17000000\"\n        android:endColor=\"#00000000\"\n        android:angle=\"270\" />\n</shape>"
  },
  {
    "path": "app/src/main/res/drawable/bg_shadow_top_night.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <!--实现应用背景颜色渐变-->\n    <gradient\n        android:startColor=\"#17FFFFFF\"\n        android:endColor=\"#00FFFFFF\"\n        android:angle=\"270\" />\n</shape>"
  },
  {
    "path": "app/src/main/res/drawable/bg_textfield_search.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<selector xmlns:android=\"http://schemas.android.com/apk/res/android\" xmlns:tools=\"http://schemas.android.com/tools\" tools:ignore=\"PrivateResource\">\n    <item android:drawable=\"@drawable/abc_textfield_search_activated_mtrl_alpha\" android:state_enabled=\"true\" android:state_focused=\"true\" />\n    <item android:drawable=\"@drawable/abc_textfield_search_activated_mtrl_alpha\" android:state_activated=\"true\" android:state_enabled=\"true\" />\n    <item android:drawable=\"@drawable/abc_textfield_search_default_mtrl_alpha\" android:state_enabled=\"true\" />\n    <item android:drawable=\"@drawable/abc_textfield_search_default_mtrl_alpha\" />\n</selector>"
  },
  {
    "path": "app/src/main/res/drawable/fastscroll_bubble.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?><!--\n  ~ Copyright 2016 L4 Digital LLC. All rights reserved.\n  ~\n  ~ Licensed under the Apache License, Version 2.0 (the \"License\");\n  ~ you may not use this file except in compliance with the License.\n  ~ You may obtain a copy of the License at\n  ~\n  ~      http://www.apache.org/licenses/LICENSE-2.0\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License.\n  -->\n\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:shape=\"rectangle\">\n\n    <tools:solid android:color=\"#777777\" />\n\n    <corners\n        android:topLeftRadius=\"@dimen/fastscroll_bubble_radius\"\n        android:topRightRadius=\"@dimen/fastscroll_bubble_radius\"\n        android:bottomLeftRadius=\"@dimen/fastscroll_bubble_radius\"\n        android:bottomRightRadius=\"0dp\" />\n\n    <size\n        android:height=\"@dimen/fastscroll_bubble_size\"\n        android:width=\"@dimen/fastscroll_bubble_size\" />\n\n    <padding\n        android:left=\"@dimen/fastscroll_bubble_padding\"\n        android:right=\"@dimen/fastscroll_bubble_padding\" />\n\n</shape>\n"
  },
  {
    "path": "app/src/main/res/drawable/fastscroll_handle.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?><!--\n  ~ Copyright 2016 L4 Digital LLC. All rights reserved.\n  ~\n  ~ Licensed under the Apache License, Version 2.0 (the \"License\");\n  ~ you may not use this file except in compliance with the License.\n  ~ You may obtain a copy of the License at\n  ~\n  ~      http://www.apache.org/licenses/LICENSE-2.0\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License.\n  -->\n\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:shape=\"rectangle\">\n\n    <tools:solid android:color=\"#555555\" />\n\n    <corners android:radius=\"@dimen/fastscroll_handle_radius\" />\n\n    <size\n        android:height=\"@dimen/fastscroll_handle_height\"\n        android:width=\"@dimen/fastscroll_handle_width\" />\n\n</shape>\n"
  },
  {
    "path": "app/src/main/res/drawable/fastscroll_track.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?><!--\n  ~ Copyright 2016 L4 Digital LLC. All rights reserved.\n  ~\n  ~ Licensed under the Apache License, Version 2.0 (the \"License\");\n  ~ you may not use this file except in compliance with the License.\n  ~ You may obtain a copy of the License at\n  ~\n  ~      http://www.apache.org/licenses/LICENSE-2.0\n  ~\n  ~ Unless required by applicable law or agreed to in writing, software\n  ~ distributed under the License is distributed on an \"AS IS\" BASIS,\n  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n  ~ See the License for the specific language governing permissions and\n  ~ limitations under the License.\n  -->\n\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:shape=\"rectangle\">\n\n    <tools:solid android:color=\"#CCCCCC\" />\n\n    <size android:width=\"@dimen/fastscroll_track_width\" />\n\n</shape>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_add.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\r\n    android:width=\"24dp\"\r\n    android:height=\"24dp\"\r\n    android:viewportWidth=\"24\"\r\n    android:viewportHeight=\"24\">\r\n\r\n    <path\r\n        android:fillColor=\"#595757\"\r\n        android:pathData=\"M12.822,20 L11.177,20 L11.177,12.821 L4,12.821 L4,11.178 L11.178,11.178 L11.178,4 L12.823,4 L12.823,11.179 L20,11.179 L20,12.822 L12.822,12.822 L12.822,20 Z\" />\r\n</vector>"
  },
  {
    "path": "app/src/main/res/drawable/ic_add_online.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\r\n    android:width=\"24dp\"\r\n    android:height=\"24dp\"\r\n    android:viewportWidth=\"24\"\r\n    android:viewportHeight=\"24\">\r\n\r\n    <path\r\n        android:fillColor=\"#595757\"\r\n        android:pathData=\"M5.689,20 C5.164,20,4.74,19.844,4.432,19.537 C3.242,18.345,4.772,15.546,5.863,13.892 C5.683,13.308,5.595,12.688,5.595,12 C5.595,8.469,8.467,5.596,11.998,5.596 C12.684,5.596,13.308,5.685,13.898,5.868 C15.013,5.131,16.948,4,18.339,4 C18.84,4,19.242,4.146,19.531,4.435 C20.308,5.212,20.118,6.648,18.965,8.697 C18.724,9.127,18.433,9.588,18.099,10.076 C18.296,10.703,18.396,11.345,18.396,11.986 C18.396,15.524,15.526,18.403,11.998,18.403 C11.349,18.403,10.7,18.304,10.067,18.103 C8.774,18.991,7.013,20,5.689,20 Z M6.51,15.317 C5.331,17.292,5.196,18.449,5.357,18.611 C5.418,18.673,5.543,18.706,5.709,18.706 C6.337,18.706,7.422,18.249,8.669,17.469 C7.791,16.935,7.046,16.193,6.51,15.317 Z M11.473,17.068 C11.651,17.087,11.826,17.096,11.998,17.096 C14.806,17.096,17.089,14.803,17.089,11.985 C17.089,11.82,17.081,11.651,17.063,11.482 C16.282,12.473,15.398,13.461,14.428,14.43 C13.48,15.38,12.47,16.28,11.473,17.068 Z M11.998,6.901 C9.188,6.901,6.902,9.19,6.902,12 C6.902,14.017,8.066,15.818,9.885,16.641 C11.084,15.764,12.301,14.711,13.504,13.508 C14.686,12.326,15.765,11.076,16.635,9.883 C15.811,8.068,14.011,6.901,11.998,6.901 Z M15.317,6.516 C16.19,7.051,16.929,7.791,17.461,8.666 C17.598,8.448,17.72,8.247,17.827,8.055 C18.848,6.24,18.729,5.482,18.607,5.36 C18.605,5.357,18.549,5.304,18.335,5.304 C18.084,5.304,17.163,5.396,15.317,6.516 Z\" />\r\n</vector>"
  },
  {
    "path": "app/src/main/res/drawable/ic_arrange.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\r\n    android:width=\"24dp\"\r\n    android:height=\"24dp\"\r\n    android:viewportWidth=\"24\"\r\n    android:viewportHeight=\"24\">\r\n\r\n    <path\r\n        android:fillColor=\"#595757\"\r\n        android:pathData=\"M12.167,4h-2.025c-0.604,0-1.08,0.529-1.08,1.206v13.588c0,0.677,0.476,1.206,1.08,1.206h2.025 c0.605,0,1.08-0.529,1.08-1.206V5.206C13.247,4.529,12.772,4,12.167,4z M11.792,5.454v9.531h-1.273V5.454H11.792z M10.519,18.546 v-2.106h1.273v2.106H10.519z\" />\r\n    <path\r\n        android:fillColor=\"#595757\"\r\n        android:pathData=\"M7.104,6.693H5.079c-0.596,0-1.078,0.501-1.078,1.117v11.072C4.001,19.499,4.483,20,5.079,20h2.025 c0.596,0,1.081-0.501,1.081-1.117V7.811C8.186,7.195,7.7,6.693,7.104,6.693z M6.729,8.148v6.837H5.455V8.148H6.729z M5.455,18.546 v-2.106h1.274v2.106H5.455z\" />\r\n    <path\r\n        android:fillColor=\"#595757\"\r\n        android:pathData=\"M17.976,7.433c-0.098-0.534-0.551-0.921-1.075-0.921l-2.183,0.383 c-0.584,0.108-0.969,0.689-0.858,1.294l2.006,10.89c0.099,0.534,0.55,0.92,1.074,0.92l2.183-0.383 c0.585-0.108,0.969-0.689,0.859-1.294L17.976,7.433z M15.354,8.256l1.25-0.234l1.289,7.004l-1.256,0.209L15.354,8.256z M17.235,18.484L16.9,16.667l1.257-0.209l0.33,1.797L17.235,18.484z\" />\r\n</vector>"
  },
  {
    "path": "app/src/main/res/drawable/ic_arrow_back.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"24.0\"\n    android:viewportHeight=\"24.0\">\n    <path\n        android:fillColor=\"#FF000000\"\n        android:pathData=\"M20,11H7.83l5.59,-5.59L12,4l-8,8 8,8 1.41,-1.41L7.83,13H20v-2z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_arrow_down.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\r\n    android:width=\"64dp\"\r\n    android:height=\"64dp\"\r\n    android:viewportWidth=\"1024\"\r\n    android:viewportHeight=\"1024\">\r\n  <path\r\n      android:pathData=\"M512,685.25l-278.62,-278.62 45.25,-45.25L512,594.75l233.38,-233.38 45.25,45.25z\"\r\n      android:fillColor=\"#181818\"/>\r\n</vector>\r\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_arrow_drop_down.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"24.0\"\n    android:viewportHeight=\"24.0\">\n    <path\n        android:fillColor=\"#FF000000\"\n        android:pathData=\"M7,10l5,5 5,-5z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_arrow_drop_up.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"24.0\"\n    android:viewportHeight=\"24.0\">\n    <path\n        android:fillColor=\"#FF000000\"\n        android:pathData=\"M7,14l5,-5 5,5z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_arrow_right.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\r\n    android:width=\"64dp\"\r\n    android:height=\"64dp\"\r\n    android:viewportWidth=\"1024\"\r\n    android:viewportHeight=\"1024\">\r\n  <path\r\n      android:pathData=\"M434.94,790.62l-45.25,-45.25L623.04,512l-233.38,-233.38 45.25,-45.25L713.57,512z\"\r\n      android:fillColor=\"#181818\"/>\r\n</vector>\r\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_author.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"16dp\"\n    android:height=\"16dp\"\n    android:viewportWidth=\"24\"\n    android:viewportHeight=\"24\">\n    <path\n        android:pathData=\"M12,3c-4.963,0 -9,4.037 -9,9s4.037,9 9,9s9,-4.037 9,-9S16.963,3 12,3zM12,13.826c-2.277,0 -4.371,1.177 -5.542,3.092C5.245,15.554 4.581,13.82 4.581,12c0,-4.091 3.328,-7.419 7.419,-7.419S19.419,7.909 19.419,12c0,1.82 -0.664,3.554 -1.877,4.918C16.371,15.003 14.277,13.826 12,13.826zM12,15.407c1.811,0 3.489,1.013 4.33,2.598c-1.27,0.926 -2.762,1.414 -4.33,1.414c-1.568,0 -3.062,-0.488 -4.332,-1.414C8.511,16.42 10.189,15.407 12,15.407z\"\n        android:fillColor=\"#4A4B4A\" />\n    <path\n        android:pathData=\"M12,6.014c-2.178,0 -3.951,1.746 -3.951,3.893c0,2.149 1.772,3.899 3.951,3.899s3.951,-1.75 3.951,-3.899C15.951,7.76 14.178,6.014 12,6.014zM12,12.225c-1.307,0 -2.37,-1.041 -2.37,-2.318c0,-1.274 1.063,-2.312 2.37,-2.312s2.37,1.037 2.37,2.312C14.37,11.184 13.307,12.225 12,12.225z\"\n        android:fillColor=\"#4A4B4A\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_auto_page.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"24\"\n    android:viewportHeight=\"24\">\n\n    <path\n        android:fillColor=\"#595757\"\n        android:pathData=\"M19.109,6.212h-2.16l-0.002-0.817c0-0.366-0.164-0.66-0.44-0.788 c-0.133-0.062-0.278-0.091-0.445-0.091c-0.295,0-0.595,0.092-0.885,0.181l-5.121,1.516H4.891c-0.569,0-1.016,0.407-1.016,0.927 v9.907c0,0.52,0.446,0.926,1.016,0.926h5.283c0.693,0.19,4.949,1.359,5.308,1.463c0.115,0.033,0.229,0.05,0.339,0.05 c0.529,0,1.083-0.422,1.098-0.84l0.002-0.673h2.189c0.569,0,1.016-0.406,1.016-0.926V7.139C20.125,6.619,19.679,6.212,19.109,6.212 z M18.687,7.65v8.883h-1.763l0.023-8.883H18.687z M15.516,17.95c-0.723-0.204-2.282-0.65-3.459-0.988 c-0.794-0.228-1.414-0.405-1.485-0.425l-5.258-0.004V7.65h5.225l4.972-1.524L15.516,17.95z\" />\n    <path\n        android:fillColor=\"#595757\"\n        android:pathData=\"M 9.544 8.915 L 6.366 12.092 L 9.544 15.269 L 10.562 14.252 L 9.12 12.811 L 14.217 12.811 L 14.217 11.372 L 9.12 11.372 L 10.562 9.932 Z\" />\n</vector>"
  },
  {
    "path": "app/src/main/res/drawable/ic_auto_page_stop.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"24\"\n    android:viewportHeight=\"24\">\n\n    <path\n        android:fillColor=\"#595757\"\n        android:pathData=\"M19.109,6.212h-2.16l-0.002-0.817c0-0.366-0.164-0.66-0.44-0.788 c-0.133-0.062-0.278-0.091-0.445-0.091c-0.295,0-0.595,0.092-0.885,0.181l-5.121,1.516H4.891c-0.569,0-1.016,0.407-1.016,0.927 v9.907c0,0.52,0.446,0.926,1.016,0.926h5.283c0.693,0.19,4.949,1.359,5.308,1.463c0.115,0.033,0.229,0.05,0.339,0.05 c0.529,0,1.083-0.422,1.098-0.84l0.002-0.673h2.189c0.569,0,1.016-0.406,1.016-0.926V7.139C20.125,6.619,19.679,6.212,19.109,6.212 z M12.057,16.962c-0.794-0.228-1.414-0.405-1.485-0.425l-5.258-0.004V7.65h5.225l4.972-1.524l0.006,11.824 C14.793,17.746,13.233,17.3,12.057,16.962z M18.687,16.533h-1.763l0.023-8.883h1.739V16.533z\" />\n    <path\n        android:fillColor=\"#595757\"\n        android:pathData=\"M 8.208 9.5 H 9.646 V 14.5 H 8.208 V 9.5 Z\" />\n    <path\n        android:fillColor=\"#595757\"\n        android:pathData=\"M 11.396 9.5 H 12.834 V 14.5 H 11.396 V 9.5 Z\" />\n</vector>"
  },
  {
    "path": "app/src/main/res/drawable/ic_backup.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"24.0\"\n    android:viewportHeight=\"24.0\">\n    <path\n        android:fillColor=\"#FF000000\"\n        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.96zM14,13v4h-4v-4H7l5,-5 5,5h-3z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_baseline_close.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"24\"\n    android:viewportHeight=\"24\"\n    android:tint=\"?attr/colorControlNormal\">\n  <path\n      android:fillColor=\"@android:color/white\"\n      android:pathData=\"M19,6.41L17.59,5 12,10.59 6.41,5 5,6.41 10.59,12 5,17.59 6.41,19 12,13.41 17.59,19 19,17.59 13.41,12z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_baseline_sort_24.xml",
    "content": "<vector android:height=\"24dp\" android:tint=\"#000000\"\n    android:viewportHeight=\"24\" android:viewportWidth=\"24\"\n    android:width=\"24dp\" xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <path android:fillColor=\"@android:color/white\" android:pathData=\"M3,18h6v-2L3,16v2zM3,6v2h18L21,6L3,6zM3,13h12v-2L3,11v2z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_book_has.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportHeight=\"680.31\"\n    android:viewportWidth=\"680.31\">\n\n    <path\n        android:fillColor=\"#2C2C2C\"\n        android:pathData=\"M339.865,79.038c-147.014,0-266.518,119.198-266.518,266.519 c0,147.011,119.198,266.519,266.518,266.519c147.013,0,266.52-119.201,266.52-266.519 C606.078,198.542,486.878,79.038,339.865,79.038L339.865,79.038z M339.865,587.929c-133.871,0-242.373-108.505-242.373-242.373 c0-133.872,108.502-242.066,242.373-242.066c133.868,0,242.372,108.5,242.372,242.373 C582.237,479.731,473.432,587.929,339.865,587.929L339.865,587.929z M339.865,587.929\" />\n    <path\n        android:fillColor=\"#2C2C2C\"\n        android:pathData=\"M305.329,491.346c20.782,0,28.118,33.619,28.118,33.619h12.836c0,0,7.337-33.619,28.12-33.619h133.869 c7.95,0,14.673-6.725,14.673-14.671V242.25c0-33.316-33.316-33.316-33.316-33.316H361.261c-10.697,0-21.396,13.449-21.396,20.479 c0-7.03-10.391-20.479-21.088-20.479H190.103c0,0-33.317,0-33.317,33.316v234.426c0,8.253,6.418,14.671,14.67,14.671H305.329z M348.117,236.75c0-6.115,4.89-11.31,11.005-11.31h139.065c3.973,0,7.336,3.361,7.336,7.64l0.607,235.647 c0,4.279-3.36,7.644-7.333,7.644H372.873c-14.06,0-24.756,15.589-24.756,15.589V236.75z M173.902,233.08 c0-4.279,3.364-7.64,7.336-7.64h139.066c6.112,0,11.003,4.891,11.003,11.31v254.903c0,0-11.003-15.589-24.759-15.589H180.627 c-3.974,0-7.337-3.36-7.337-7.639L173.902,233.08z M173.902,233.08\" />\n</vector>"
  },
  {
    "path": "app/src/main/res/drawable/ic_book_last.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"24\"\n    android:viewportHeight=\"24\">\n    <path\n        android:pathData=\"M12,12m-0.756,0a0.756,0.756 0,1 1,1.512 0a0.756,0.756 0,1 1,-1.512 0\"\n        android:fillColor=\"#4A4B4A\" />\n    <path\n        android:pathData=\"M7.038,16.963l6.94,-2.985l2.984,-6.939l-6.938,2.985L7.038,16.963zM14.195,9.804l-1.321,3.071l-3.069,1.322l1.321,-3.07L14.195,9.804z\"\n        android:fillColor=\"#4A4B4A\" />\n    <path\n        android:pathData=\"M12,3c-4.963,0 -9,4.037 -9,9s4.037,9 9,9s9,-4.037 9,-9S16.963,3 12,3zM12,19.419c-4.091,0 -7.419,-3.328 -7.419,-7.419S7.909,4.581 12,4.581S19.419,7.909 19.419,12S16.091,19.419 12,19.419z\"\n        android:fillColor=\"#4A4B4A\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_bookmark.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"24\"\n    android:viewportHeight=\"24\">\n\n    <path\n        android:fillColor=\"#595757\"\n        android:pathData=\"M5.938,4v16h1.414l4.648-2.789L16.649,20h1.411V4H5.938z M16.606,18.278l-4.605-2.763l-4.609,2.764 V5.454h9.215V18.278z\" />\n</vector>"
  },
  {
    "path": "app/src/main/res/drawable/ic_bottom_books.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <item android:drawable=\"@drawable/ic_bottom_books_e\" android:state_checked=\"false\" />\n    <item android:drawable=\"@drawable/ic_bottom_books_s\" android:state_checked=\"true\" />\n</selector>"
  },
  {
    "path": "app/src/main/res/drawable/ic_bottom_books_e.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"24\"\n    android:viewportHeight=\"24\">\n    <path\n        android:pathData=\"M23,3V2.5a0.5,0.5 0,0 0,-0.5 -0.5H5.69A4.63,4.63 0,0 0,1 6.06,4.51 4.51,0 0,0 5.5,11h17a0.5,0.5 0,0 0,0.5 -0.5V10a0.5,0.5 0,0 0,-0.41 -0.5,3 3,0 0,1 0,-5.94A0.5,0.5 0,0 0,23 3ZM2.56,5.9A3.09,3.09 0,0 1,5.62 3.5H19.67a4.43,4.43 0,0 0,0 6H5.5A3,3 0,0 1,2.56 5.9Z\"\n        android:fillColor=\"#2f45a6\" />\n    <path\n        android:pathData=\"M1,21v0.53a0.5,0.5 0,0 0,0.5 0.5H18.31A4.63,4.63 0,0 0,23 17.94,4.51 4.51,0 0,0 18.5,13H1.5a0.5,0.5 0,0 0,-0.5 0.5V14a0.5,0.5 0,0 0,0.41 0.5,3 3,0 0,1 0,5.94A0.5,0.5 0,0 0,1 21ZM21.44,18.1a3.09,3.09 0,0 1,-3.06 2.4h-14a4.43,4.43 0,0 0,0 -6H18.5A3,3 0,0 1,21.44 18.1Z\"\n        android:fillColor=\"#2f45a6\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_bottom_books_s.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"24\"\n    android:viewportHeight=\"24\">\n  <path\n      android:pathData=\"M20,6.5a3,3 0,0 1,2.59 -3A0.5,0.5 0,0 0,23 3V2.5a0.5,0.5 0,0 0,-0.5 -0.5H5.69A4.63,4.63 0,0 0,1 6.06,4.51 4.51,0 0,0 5.5,11h17a0.5,0.5 0,0 0,0.5 -0.5V10a0.5,0.5 0,0 0,-0.41 -0.5A3,3 0,0 1,20 6.5Z\"\n      android:fillColor=\"#2f45a6\" />\n  <path\n      android:pathData=\"M4,17.5a3,3 0,0 1,-2.59 3A0.5,0.5 0,0 0,1 21v0.53a0.5,0.5 0,0 0,0.5 0.5H18.31A4.63,4.63 0,0 0,23 17.94,4.51 4.51,0 0,0 18.5,13H1.5a0.5,0.5 0,0 0,-0.5 0.5V14a0.5,0.5 0,0 0,0.41 0.5A3,3 0,0 1,4 17.5Z\"\n      android:fillColor=\"#2f45a6\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_bottom_explore.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <item android:drawable=\"@drawable/ic_bottom_explore_e\" android:state_checked=\"false\" />\n    <item android:drawable=\"@drawable/ic_bottom_explore_s\" android:state_checked=\"true\" />\n</selector>"
  },
  {
    "path": "app/src/main/res/drawable/ic_bottom_explore_e.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"24\"\n    android:viewportHeight=\"24\">\n  <path\n      android:pathData=\"M12,2.5A9.5,9.5 0,1 1,2.5 12,9.51 9.51,0 0,1 12,2.5M12,1A11,11 0,1 0,23 12,11 11,0 0,0 12,1Z\"\n      android:fillColor=\"#2f45a6\" />\n  <path\n      android:pathData=\"M13.93,10.07l-1.29,2.57 -2.57,1.29 1.29,-2.57 2.57,-1.29M16.85,7a0.18,0.18 0,0 0,-0.1 0l-6.51,3.26L7,16.75a0.2,0.2 0,0 0,0.17 0.29,0.18 0.18,0 0,0 0.1,0l6.51,-3.26L17,7.25A0.2,0.2 0,0 0,16.85 7Z\"\n      android:fillColor=\"#2f45a6\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_bottom_explore_s.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"24\"\n    android:viewportHeight=\"24\">\n  <path\n      android:pathData=\"M12,1A11,11 0,1 0,23 12,11 11,0 0,0 12,1ZM17,7.25 L13.74,13.76L7.25,17A0.2,0.2 0,0 1,7 16.75l3.26,-6.51L16.75,7A0.2,0.2 0,0 1,17 7.25Z\"\n      android:fillColor=\"#2f45a6\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_bottom_person.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <item android:drawable=\"@drawable/ic_bottom_person_e\" android:state_checked=\"false\" />\n    <item android:drawable=\"@drawable/ic_bottom_person_s\" android:state_checked=\"true\" />\n</selector>"
  },
  {
    "path": "app/src/main/res/drawable/ic_bottom_person_e.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"24\"\n    android:viewportHeight=\"24\">\n    <path\n        android:pathData=\"M12,2.5A4.5,4.5 0,1 1,7.5 7,4.51 4.51,0 0,1 12,2.5M12,1a6,6 0,1 0,6 6,6 6,0 0,0 -6,-6Z\"\n        android:fillColor=\"#2f45a6\" />\n    <path\n        android:pathData=\"M3.5,21A4.49,4.49 0,0 1,8 16.5h8A4.49,4.49 0,0 1,20.5 21v2H21a1,1 0,0 0,1 -1V21a6,6 0,0 0,-6 -6H8a6,6 0,0 0,-6 6v1a1,1 0,0 0,1 1h0.5Z\"\n        android:fillColor=\"#2f45a6\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_bottom_person_s.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"24\"\n    android:viewportHeight=\"24\">\n    <path\n        android:pathData=\"M12,7m-6,0a6,6 0,1 1,12 0a6,6 0,1 1,-12 0\"\n        android:fillColor=\"#2f45a6\" />\n    <path\n        android:pathData=\"M8,15h8a6,6 0,0 1,6 6v1a1,1 0,0 1,-1 1H3a1,1 0,0 1,-1 -1V21A6,6 0,0 1,8 15Z\"\n        android:fillColor=\"#2f45a6\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_bottom_rss_feed.xml",
    "content": "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n<selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <item android:drawable=\"@drawable/ic_bottom_rss_feed_e\" android:state_checked=\"false\" />\n    <item android:drawable=\"@drawable/ic_bottom_rss_feed_s\" android:state_checked=\"true\" />\n</selector>"
  },
  {
    "path": "app/src/main/res/drawable/ic_bottom_rss_feed_e.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"24\"\n    android:viewportHeight=\"24\">\n  <path\n      android:pathData=\"M19,2.5a0.5,0.5 0,0 1,0.5 0.5V20.45l-6.77,-3.76L12,16.28l-0.73,0.41L4.5,20.45V3A0.5,0.5 0,0 1,5 2.5H19M19,1H5A2,2 0,0 0,3 3V21.3a1,1 0,0 0,1.49 0.87L12,18l7.51,4.17A1,1 0,0 0,21 21.3V3a2,2 0,0 0,-2 -2Z\"\n      android:fillColor=\"#2f45a6\" />\n  <path\n      android:pathData=\"M15.5,8.75H12.75V6a0.5,0.5 0,0 0,-0.5 -0.5h-0.5a0.5,0.5 0,0 0,-0.5 0.5V8.75H8.5a0.5,0.5 0,0 0,-0.5 0.5v0.5a0.5,0.5 0,0 0,0.5 0.5h2.75V13a0.5,0.5 0,0 0,0.5 0.5h0.5a0.5,0.5 0,0 0,0.5 -0.5V10.25H15.5a0.5,0.5 0,0 0,0.5 -0.5v-0.5A0.5,0.5 0,0 0,15.5 8.75Z\"\n      android:fillColor=\"#2f45a6\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_bottom_rss_feed_s.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"24\"\n    android:viewportHeight=\"24\">\n    <path\n        android:pathData=\"M19,1H5A2,2 0,0 0,3 3V21.3a1,1 0,0 0,1.49 0.87L12,18l7.51,4.17A1,1 0,0 0,21 21.3V3A2,2 0,0 0,19 1ZM16,9.75a0.5,0.5 0,0 1,-0.5 0.5H12.75V13a0.5,0.5 0,0 1,-0.5 0.5h-0.5a0.5,0.5 0,0 1,-0.5 -0.5V10.25H8.5a0.5,0.5 0,0 1,-0.5 -0.5v-0.5a0.5,0.5 0,0 1,0.5 -0.5h2.75V6a0.5,0.5 0,0 1,0.5 -0.5h0.5a0.5,0.5 0,0 1,0.5 0.5V8.75H15.5a0.5,0.5 0,0 1,0.5 0.5Z\"\n        android:fillColor=\"#2f45a6\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_brightness.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"24\"\n    android:viewportHeight=\"24\">\n\n    <path\n        android:fillColor=\"#595757\"\n        android:pathData=\"M12,20 C7.59,20,4,16.412,4,12 S7.59,4,12,4 L12.848,4 L12.698,4.835 C12.693,4.862,12.221,7.656,13.793,9.529 C14.782,10.711,16.402,11.306,18.606,11.307 L18.606,11.307 C18.816,11.307,19.032,11.304,19.255,11.29 L20,11.256 L20,12 C20,16.412,16.411,20,12,20 Z M11.215,5.463 C7.955,5.854,5.418,8.635,5.418,12 C5.418,15.631,8.371,18.583,12,18.583 C15.384,18.583,18.18,16.015,18.543,12.727 C15.925,12.713,13.958,11.943,12.698,10.43 C11.305,8.762,11.165,6.641,11.215,5.463 Z\" />\n</vector>"
  },
  {
    "path": "app/src/main/res/drawable/ic_brightness_auto.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"1024\"\n    android:viewportHeight=\"1024\">\n\n    <path\n        android:fillColor=\"#FFFFFF\"\n        android:pathData=\"M462.933333 539.733333h98.133334L512 384l-49.066667 155.733333z m390.4-170.666666V170.666667h-200.106666L512 29.44 370.773333 170.666667H170.666667v200.106666L29.44 512 170.666667 653.226667V853.333333h200.106666L512 994.56 653.226667 853.333333H853.333333v-200.106666L994.56 512 853.333333 370.773333zM610.133333 682.666667l-29.866666-85.333334h-136.533334l-29.866666 85.333334H332.8L469.333333 298.666667h85.333334l136.533333 384z\" />\n</vector>"
  },
  {
    "path": "app/src/main/res/drawable/ic_bubble_chart.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"24\"\n    android:viewportHeight=\"24\"\n    android:tint=\"?attr/colorControlNormal\">\n  <path\n      android:fillColor=\"@android:color/white\"\n      android:pathData=\"M16.5,12c1.93,0 3.5,-1.57 3.5,-3.5S18.43,5 16.5,5 13,6.57 13,8.5s1.57,3.5 3.5,3.5z\"\n      android:strokeAlpha=\"0.3\"\n      android:fillAlpha=\"0.3\" />\n  <path\n      android:fillColor=\"@android:color/white\"\n      android:pathData=\"M15.01,18m-1,0a1,1 0,1 1,2 0a1,1 0,1 1,-2 0\"\n      android:strokeAlpha=\"0.3\"\n      android:fillAlpha=\"0.3\" />\n  <path\n      android:fillColor=\"@android:color/white\"\n      android:pathData=\"M7,14m-2,0a2,2 0,1 1,4 0a2,2 0,1 1,-4 0\"\n      android:strokeAlpha=\"0.3\"\n      android:fillAlpha=\"0.3\" />\n  <path\n      android:fillColor=\"@android:color/white\"\n      android:pathData=\"M7,18c2.21,0 4,-1.79 4,-4s-1.79,-4 -4,-4 -4,1.79 -4,4 1.79,4 4,4zM7,12c1.1,0 2,0.9 2,2s-0.9,2 -2,2 -2,-0.9 -2,-2 0.9,-2 2,-2zM18.01,18c0,-1.65 -1.35,-3 -3,-3s-3,1.35 -3,3 1.35,3 3,3 3,-1.35 3,-3zM14.01,18c0,-0.55 0.45,-1 1,-1s1,0.45 1,1 -0.45,1 -1,1 -1,-0.45 -1,-1zM16.5,14c3.03,0 5.5,-2.47 5.5,-5.5S19.53,3 16.5,3 11,5.47 11,8.5s2.47,5.5 5.5,5.5zM16.5,5C18.43,5 20,6.57 20,8.5S18.43,12 16.5,12 13,10.43 13,8.5 14.57,5 16.5,5z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_bug_report.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n        android:width=\"24dp\"\n        android:height=\"24dp\"\n        android:viewportWidth=\"24.0\"\n        android:viewportHeight=\"24.0\">\n    <path\n        android:fillColor=\"#000000\"\n        android:pathData=\"M20,8h-2.81c-0.45,-0.78 -1.07,-1.45 -1.82,-1.96L17,4.41 15.59,3l-2.17,2.17C12.96,5.06 12.49,5 12,5c-0.49,0 -0.96,0.06 -1.41,0.17L8.41,3 7,4.41l1.62,1.63C7.88,6.55 7.26,7.22 6.81,8L4,8v2h2.09c-0.05,0.33 -0.09,0.66 -0.09,1v1L4,12v2h2v1c0,0.34 0.04,0.67 0.09,1L4,16v2h2.81c1.04,1.79 2.97,3 5.19,3s4.15,-1.21 5.19,-3L20,18v-2h-2.09c0.05,-0.33 0.09,-0.66 0.09,-1v-1h2v-2h-2v-1c0,-0.34 -0.04,-0.67 -0.09,-1L20,10L20,8zM14,16h-4v-2h4v2zM14,12h-4v-2h4v2z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_cfg_about.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\r\n    android:width=\"24dp\"\r\n    android:height=\"24dp\"\r\n    android:viewportWidth=\"24\"\r\n    android:viewportHeight=\"24\">\r\n  <path\r\n      android:fillColor=\"#FF000000\"\r\n      android:pathData=\"M12,4.5A7.5,7.5 0,1 1,4.5 12,7.5 7.5,0 0,1 12,4.5M12,3a9,9 0,1 0,9 9,9 9,0 0,0 -9,-9Z\"/>\r\n  <path\r\n      android:fillColor=\"#FF000000\"\r\n      android:pathData=\"M11.25,10.5h1.5v6h-1.5z\"/>\r\n  <path\r\n      android:fillColor=\"#FF000000\"\r\n      android:pathData=\"M12,8.5m-1,0a1,1 0,1 1,2 0a1,1 0,1 1,-2 0\"/>\r\n</vector>\r\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_cfg_backup.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\r\n    android:width=\"24dp\"\r\n    android:height=\"24dp\"\r\n    android:viewportWidth=\"24\"\r\n    android:viewportHeight=\"24\">\r\n  <path\r\n      android:fillColor=\"#FF000000\"\r\n      android:pathData=\"M9.94,4H5A2,2 0,0 0,3 6V18a2,2 0,0 0,2 2H19a2,2 0,0 0,2 -2V8a2,2 0,0 0,-2 -2H13.23a1.49,1.49 0,0 1,-1 -0.42L11,4.42a1.49,1.49 0,0 0,-1 -0.42ZM4.5,9.25V6A0.5,0.5 0,0 1,5 5.5H9.94l1.21,1.16a3,3 0,0 0,2.08 0.84H19a0.5,0.5 0,0 1,0.5 0.5V9.25ZM5,18.5a0.5,0.5 0,0 1,-0.5 -0.5V10.75h15V18a0.5,0.5 0,0 1,-0.5 0.5Z\"/>\r\n</vector>\r\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_cfg_donate.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"24\"\n    android:viewportHeight=\"24\">\n  <path\n      android:fillColor=\"#FF000000\"\n      android:pathData=\"M12,4.5A7.5,7.5 0,1 1,4.5 12,7.5 7.5,0 0,1 12,4.5M12,3a9,9 0,1 0,9 9,9 9,0 0,0 -9,-9Z\"/>\n  <path\n      android:fillColor=\"#FF000000\"\n      android:pathData=\"M15.01,7.25l-1.49,0l-1.51,2.59l-1.5,-2.59l-1.5,0l1.6,2.75l-1.6,0l0,1.5l2.24,0l0,1l-2.24,0l0,1.5l2.24,0l0,2.75l1.5,0l0,-2.75l2.24,0l0,-1.5l-2.24,0l0,-1l2.24,0l0,-1.5l-1.58,0l1.6,-2.75z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_cfg_other.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\r\n    android:width=\"24dp\"\r\n    android:height=\"24dp\"\r\n    android:viewportWidth=\"24\"\r\n    android:viewportHeight=\"24\">\r\n  <path\r\n      android:fillColor=\"#FF000000\"\r\n      android:pathData=\"M12,10.5A1.5,1.5 0,1 1,10.5 12,1.5 1.5,0 0,1 12,10.5M12,9a3,3 0,1 0,3 3,3 3,0 0,0 -3,-3Z\"/>\r\n  <path\r\n      android:fillColor=\"#FF000000\"\r\n      android:pathData=\"M15.62,5.5h0L19.28,12l-3.66,6.5H8.38L4.72,12 8.38,5.5h7.24m0,-1.5H8.38a1.5,1.5 0,0 0,-1.31 0.77L3.41,11.26a1.55,1.55 0,0 0,0 1.48l3.66,6.49A1.5,1.5 0,0 0,8.38 20h7.24a1.5,1.5 0,0 0,1.31 -0.77l3.66,-6.49a1.55,1.55 0,0 0,0 -1.48L16.93,4.77A1.5,1.5 0,0 0,15.62 4Z\"/>\r\n</vector>\r\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_cfg_replace.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\r\n    android:width=\"24dp\"\r\n    android:height=\"24dp\"\r\n    android:viewportWidth=\"24\"\r\n    android:viewportHeight=\"24\">\r\n  <path\r\n      android:fillColor=\"#FF000000\"\r\n      android:pathData=\"M10.84,19.2V17.8a5.1,5.1 0,0 1,-5.13 -4.2,0.64 0.64,0 0,1 0.68,-0.7 0.87,0.87 0,0 1,0.82 0.69,3.66 3.66,0 0,0 3.63,2.81V15l3,2.1 -3,2.1Z\"/>\r\n  <path\r\n      android:fillColor=\"#FF000000\"\r\n      android:pathData=\"M5.93,3 L3,10.52L4.61,10.52l0.62,-1.71h3l0.66,1.71h1.65L7.53,3ZM5.7,7.54l1,-2.79 1,2.79ZM20.63,17.69A2,2 0,0 0,19.57 17a1.76,1.76 0,0 0,0.78 -0.67,1.79 1.79,0 0,0 0,-1.84 2,2 0,0 0,-0.58 -0.63,1.91 1.91,0 0,0 -0.78,-0.31 8.48,8.48 0,0 0,-1.33 -0.08h-3L14.66,21h2.56c1,0 1.58,0 1.83,-0.05a2.21,2.21 0,0 0,1 -0.36,2 2,0 0,0 0.65,-0.77 2.23,2.23 0,0 0,0.24 -1A1.78,1.78 0,0 0,20.63 17.69ZM16.22,14.69h0.87c0.72,0 1.15,0 1.3,0A0.9,0.9 0,0 1,19 15a0.84,0.84 0,0 1,0.19 0.57,0.8 0.8,0 0,1 -0.22,0.59 1,1 0,0 1,-0.61 0.27c-0.14,0 -0.51,0 -1.1,0h-1ZM19.22,19.35a0.94,0.94 0,0 1,-0.55 0.3,7.41 7.41,0 0,1 -1,0L16.22,19.65v-2h1.23a5.11,5.11 0,0 1,1.34 0.11,1 1,0 0,1 0.48,0.34 1,1 0,0 1,0.16 0.58,0.93 0.93,0 0,1 -0.21,0.64Z\"/>\r\n</vector>\r\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_cfg_source.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\r\n    android:width=\"24dp\"\r\n    android:height=\"24dp\"\r\n    android:viewportWidth=\"24\"\r\n    android:viewportHeight=\"24\">\r\n  <path\r\n      android:fillColor=\"#FF000000\"\r\n      android:pathData=\"M20.59,4.2A15,15 0,0 0,18.12 4,20 20,0 0,0 12,5 20,20 0,0 0,5.88 4a15,15 0,0 0,-2.47 0.2c-0.24,0 -0.41,0.25 -0.41,1.1L3,18.68a0.48,0.48 0,0 0,0.49 0.48h0.08A14.08,14.08 0,0 1,5.88 19a19.58,19.58 0,0 1,5.39 0.8L12,20l0.66,-0.2A19.85,19.85 0,0 1,18.12 19a14.08,14.08 0,0 1,2.31 0.18h0.08a0.48,0.48 0,0 0,0.49 -0.49L21,4.7A0.5,0.5 0,0 0,20.59 4.2ZM11.27,18.2a21.47,21.47 0,0 0,-5.39 -0.74c-0.48,0 -1,0 -1.42,0.06v-12A13.24,13.24 0,0 1,5.88 5.5a19,19 0,0 1,5.39 0.85ZM19.54,17.52c-0.46,0 -0.94,-0.06 -1.42,-0.06a21.27,21.27 0,0 0,-5.39 0.74L12.73,6.35a19.14,19.14 0,0 1,5.39 -0.85,13.11 13.11,0 0,1 1.42,0.07Z\"/>\r\n  <path\r\n      android:fillColor=\"#FF000000\"\r\n      android:pathData=\"M18.12,8.45l0.5,0V7l-0.5,0a19.49,19.49 0,0 0,-4.5 0.56V9.05A18.3,18.3 0,0 1,18.12 8.45Z\"/>\r\n</vector>\r\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_cfg_theme.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\r\n    android:width=\"24dp\"\r\n    android:height=\"24dp\"\r\n    android:viewportWidth=\"24\"\r\n    android:viewportHeight=\"24\">\r\n  <path\r\n      android:fillColor=\"#FF000000\"\r\n      android:pathData=\"M20.37,4.75 L16,3H14.29A2.5,2.5 0,0 1,9.71 3H8L3.63,4.75A1,1 0,0 0,3 5.68V10a1.05,1.05 0,0 0,1 1.05,1 1,0 0,0 0.3,-0.05L6.5,10v9a2,2 0,0 0,2 2h7a2,2 0,0 0,2 -2V10l2.18,1A1,1 0,0 0,21 10V5.68A1,1 0,0 0,20.37 4.75ZM19.5,9.27 L16.72,8a0.51,0.51 0,0 0,-0.72 0.46V19a0.5,0.5 0,0 1,-0.5 0.5h-7A0.5,0.5 0,0 1,8 19V8.45A0.5,0.5 0,0 0,7.29 8L4.5,9.27V6L8.29,4.5H8.9a4,4 0,0 0,6.2 0h0.61L19.5,6Z\"/>\r\n</vector>\r\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_cfg_web.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\r\n    android:width=\"24dp\"\r\n    android:height=\"24dp\"\r\n    android:viewportWidth=\"24\"\r\n    android:viewportHeight=\"24\">\r\n  <path\r\n      android:fillColor=\"#FF000000\"\r\n      android:pathData=\"M19.62,7.23A8.83,8.83 0,0 0,18.72 6,9 9,0 0,0 13.24,3.1 3.86,3.86 0,0 0,12.75 3c-0.25,0 -0.5,0 -0.75,0s-0.5,0 -0.75,0a3.86,3.86 0,0 0,-0.49 0.06A9,9 0,0 0,5.28 6a8.83,8.83 0,0 0,-0.9 1.21,8.93 8.93,0 0,0 0,9.54A8.83,8.83 0,0 0,5.28 18a9,9 0,0 0,5.48 2.92,3.86 3.86,0 0,0 0.49,0.06c0.25,0 0.5,0 0.75,0s0.5,0 0.75,0a3.86,3.86 0,0 0,0.49 -0.06A9,9 0,0 0,18.72 18a8.83,8.83 0,0 0,0.9 -1.21,8.93 8.93,0 0,0 0,-9.54ZM19.46,11.23L16,11.23A13.84,13.84 0,0 0,15.67 9a13.7,13.7 0,0 0,2.65 -1A7.36,7.36 0,0 1,19.46 11.25ZM17.37,6.77a12.57,12.57 0,0 1,-2.1 0.78A13.83,13.83 0,0 0,14 4.77,7.56 7.56,0 0,1 17.37,6.77ZM12,14.5a14.51,14.51 0,0 0,-2.2 0.19,12.41 12.41,0 0,1 -0.26,-1.94h4.92a12.41,12.41 0,0 1,-0.26 1.94A14.51,14.51 0,0 0,12 14.5ZM13.78,16.14A12.69,12.69 0,0 1,12 19.47a12.69,12.69 0,0 1,-1.78 -3.33A13,13 0,0 1,12 16,13 13,0 0,1 13.78,16.14ZM9.54,11.25A12.41,12.41 0,0 1,9.8 9.31,14.51 14.51,0 0,0 12,9.5a14.51,14.51 0,0 0,2.2 -0.19,12.41 12.41,0 0,1 0.26,1.94ZM10.22,7.86A12.69,12.69 0,0 1,12 4.53a12.69,12.69 0,0 1,1.78 3.33A13,13 0,0 1,12 8,13 13,0 0,1 10.22,7.86ZM10,4.77a13.83,13.83 0,0 0,-1.3 2.78,12.57 12.57,0 0,1 -2.1,-0.78A7.56,7.56 0,0 1,10 4.77ZM8.33,9A13.84,13.84 0,0 0,8 11.25L4.54,11.25A7.36,7.36 0,0 1,5.68 8,13.7 13.7,0 0,0 8.33,9ZM4.54,12.75L8,12.75A13.84,13.84 0,0 0,8.33 15a13.7,13.7 0,0 0,-2.65 1A7.36,7.36 0,0 1,4.54 12.75ZM6.63,17.23a12.57,12.57 0,0 1,2.1 -0.78A13.83,13.83 0,0 0,10 19.23,7.56 7.56,0 0,1 6.63,17.23ZM13.97,19.23a13.83,13.83 0,0 0,1.3 -2.78,12.57 12.57,0 0,1 2.1,0.78A7.56,7.56 0,0 1,14 19.23ZM15.67,15A13.84,13.84 0,0 0,16 12.75h3.5A7.36,7.36 0,0 1,18.32 16,13.7 13.7,0 0,0 15.67,15Z\"/>\r\n</vector>\r\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_chapter_list.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"24\"\n    android:viewportHeight=\"24\">\n\n    <path\n        android:fillColor=\"#595757\"\n        android:pathData=\"M20,7.046H4V5.522h16V7.046z\" />\n    <path\n        android:fillColor=\"#595757\"\n        android:pathData=\"M20,10.855H4V9.334h16V10.855z\" />\n    <path\n        android:fillColor=\"#595757\"\n        android:pathData=\"M20,14.666H4v-1.521h16V14.666z\" />\n    <path\n        android:fillColor=\"#595757\"\n        android:pathData=\"M20,18.477H4v-1.523h16V18.477z\" />\n</vector>"
  },
  {
    "path": "app/src/main/res/drawable/ic_check.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"24.0\"\n    android:viewportHeight=\"24.0\">\n    <path\n        android:fillColor=\"#FF000000\"\n        android:pathData=\"M9,16.17L4.83,12l-1.42,1.41L9,19 21,7l-1.41,-1.41z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_check_source.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"24\"\n    android:viewportHeight=\"24\">\n\n    <path\n        android:fillColor=\"#595757\"\n        android:pathData=\"M20,6.182h-1.454v1.249C17.067,5.315,14.646,4,12,4c-4.412,0-8,3.589-8,8s3.588,8,8,8 c3.789,0,7.086-2.691,7.84-6.401l-1.426-0.289C17.798,16.344,15.1,18.546,12,18.546c-3.608,0-6.546-2.937-6.546-6.546 S8.392,5.454,12,5.454c2.399,0,4.576,1.32,5.721,3.395h-1.842v1.454H20V6.182z\" />\n    <path\n        android:fillColor=\"#595757\"\n        android:pathData=\"M11.273,8.363c-1.604,0-2.909,1.305-2.909,2.91c0,1.604,1.305,2.909,2.909,2.909 c0.537,0,1.035-0.157,1.468-0.412l2.14,2.139l1.027-1.029l-2.139-2.139c0.256-0.432,0.412-0.93,0.412-1.468 C14.182,9.668,12.877,8.363,11.273,8.363z M9.818,11.273c0-0.802,0.652-1.455,1.455-1.455c0.801,0,1.453,0.653,1.453,1.455 c0,0.801-0.652,1.454-1.453,1.454C10.471,12.727,9.818,12.074,9.818,11.273z\" />\n</vector>"
  },
  {
    "path": "app/src/main/res/drawable/ic_clear_all.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"24\"\n    android:viewportHeight=\"24\">\n\n    <path\n        android:fillColor=\"#595757\"\n        android:pathData=\"M14.434,20 L9.566,20 C7.535,20,5.863,18.665,5.761,16.961 L5.18,7.199 L4,7.199 L4,5.919 L8.402,5.919 L8.644,5.315 C8.957,4.526,9.827,4,10.813,4 L13.188,4 C14.172,4,15.043,4.526,15.355,5.313 L15.598,5.918 L20,5.918 L20,7.198 L18.82,7.198 L18.238,16.96 C18.137,18.665,16.466,20,14.434,20 Z M6.706,7.199 L7.283,16.898 C7.344,17.917,8.347,18.719,9.566,18.719 L14.433,18.719 C15.653,18.719,16.656,17.916,16.716,16.898 L17.293,7.199 L6.706,7.199 Z M10.01,5.919 L13.99,5.919 L13.91,5.718 C13.806,5.457,13.515,5.28,13.187,5.28 L10.812,5.28 C10.484,5.28,10.193,5.457,10.089,5.718 L10.01,5.919 Z\" />\n    <path\n        android:fillColor=\"#595757\"\n        android:pathData=\"M9.542,16.509 L9.127,8.83 L10.648,8.773 L11.064,16.449 L9.542,16.509 Z\" />\n    <path\n        android:fillColor=\"#595757\"\n        android:pathData=\"M14.457,16.509 L12.936,16.449 L13.352,8.773 L14.873,8.83 L14.457,16.509 Z\" />\n</vector>"
  },
  {
    "path": "app/src/main/res/drawable/ic_copy.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"24\"\n    android:viewportHeight=\"24\">\n\n    <path\n        android:fillColor=\"#595757\"\n        android:pathData=\"M 18.303 8.091 L 14.939 8.091 L 14.939 4.727 L 15.848 4.727 L 15.848 7.182 L 18.303 7.182 Z\" />\n    <path\n        android:fillColor=\"#595757\"\n        android:pathData=\"M19.03,18.061H6.91V4h8.785l3.335,3.335V18.061z M8.363,16.606h9.213V7.938l-2.483-2.484H8.363 V16.606z\" />\n    <path\n        android:fillColor=\"#595757\"\n        android:pathData=\"M 17.09 20 L 4.969 20 L 4.969 5.939 L 7.636 5.939 L 7.636 7.394 L 6.424 7.394 L 6.424 18.546 L 15.636 18.546 L 15.636 17.333 L 17.09 17.333 Z\" />\n</vector>"
  },
  {
    "path": "app/src/main/res/drawable/ic_create_folder_outline.xml",
    "content": "<vector android:height=\"24dp\"\n    android:tint=\"#000000\"\n    android:viewportHeight=\"24\"\n    android:viewportWidth=\"24\"\n    android:width=\"24dp\"\n    xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <path\n        android:fillColor=\"@android:color/white\"\n        android:pathData=\"M20,6h-8l-2,-2L4,4c-1.11,0 -1.99,0.89 -1.99,2L2,18c0,1.11 0.89,2 2,2h16c1.11,0 2,-0.89 2,-2L22,8c0,-1.11 -0.89,-2 -2,-2zM20,18L4,18L4,6h5.17l2,2L20,8v10zM12,14h2v2h2v-2h2v-2h-2v-2h-2v2h-2z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_cursor_left.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"24\"\n    android:viewportHeight=\"24\">\n\n    <path\n        android:fillColor=\"#595757\"\n        android:pathData=\"M12,4h8v8c0,4.418-3.582,8-8,8s-8-3.582-8-8S7.582,4,12,4z\" />\n</vector>"
  },
  {
    "path": "app/src/main/res/drawable/ic_cursor_right.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"24\"\n    android:viewportHeight=\"24\">\n\n    <path\n        android:fillColor=\"#595757\"\n        android:pathData=\"M12,4.208H4v8c0,4.418,3.582,8,8,8s8-3.582,8-8S16.418,4.208,12,4.208z\" />\n</vector>"
  },
  {
    "path": "app/src/main/res/drawable/ic_daytime.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"24\"\n    android:viewportHeight=\"24\">\n\n    <path\n        android:fillColor=\"#595757\"\n        android:pathData=\"M16.638,8.494l1.585-1.586l-1.132-1.131l-1.585,1.586C15.933,7.687,16.313,8.066,16.638,8.494z\" />\n    <path\n        android:fillColor=\"#595757\"\n        android:pathData=\"M8.495,7.363L6.909,5.777L5.778,6.909l1.584,1.585C7.687,8.067,8.067,7.687,8.495,7.363z\" />\n    <path\n        android:fillColor=\"#595757\"\n        android:pathData=\"M12.001,6.181c0.271,0,0.537,0.025,0.799,0.062V4h-1.6v2.242 C11.462,6.206,11.728,6.181,12.001,6.181z\" />\n    <path\n        android:fillColor=\"#595757\"\n        android:pathData=\"M6.181,12c0-0.271,0.025-0.537,0.062-0.8H4v1.6h2.242C6.206,12.538,6.181,12.272,6.181,12z\" />\n    <path\n        android:fillColor=\"#595757\"\n        android:pathData=\"M15.506,16.638l1.585,1.585l1.131-1.131l-1.584-1.586C16.313,15.934,15.933,16.313,15.506,16.638z\" />\n    <path\n        android:fillColor=\"#595757\"\n        android:pathData=\"M17.759,11.2c0.035,0.263,0.061,0.528,0.061,0.8c0,0.272-0.025,0.538-0.061,0.8H20v-1.6H17.759z\" />\n    <path\n        android:fillColor=\"#595757\"\n        android:pathData=\"M7.363,15.506l-1.585,1.586l1.131,1.131l1.585-1.586C8.067,16.313,7.687,15.934,7.363,15.506z\" />\n    <path\n        android:fillColor=\"#595757\"\n        android:pathData=\"M12.001,17.819c-0.273,0-0.539-0.024-0.801-0.062V20h1.6v-2.242 C12.538,17.795,12.272,17.819,12.001,17.819z\" />\n    <path\n        android:fillColor=\"#595757\"\n        android:pathData=\"M12,16.6c-2.536,0-4.6-2.063-4.6-4.6S9.464,7.4,12,7.4s4.6,2.063,4.6,4.6S14.536,16.6,12,16.6z M12,9 c-1.654,0-3,1.346-3,3s1.346,3,3,3s3-1.346,3-3S13.654,9,12,9z\" />\n</vector>"
  },
  {
    "path": "app/src/main/res/drawable/ic_divider.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:shape=\"rectangle\">\n    <size android:height=\"1px\" />\n    <solid android:color=\"@color/bg_divider_line\" />\n</shape>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_download.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"24.0\"\n    android:viewportHeight=\"24.0\">\n    <path\n        android:fillColor=\"#FFFFFFFF\"\n        android:pathData=\"M19,9h-4V3H9v6H5l7,7 7,-7zM5,18v2h14v-2H5z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_download_line.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\r\n    android:width=\"24dp\"\r\n    android:height=\"24dp\"\r\n    android:viewportWidth=\"24\"\r\n    android:viewportHeight=\"24\">\r\n\r\n  <path\r\n      android:fillColor=\"#595757\"\r\n      android:pathData=\"M16.712,20 L7.288,20 C5.474,20,4,18.633,4,16.952 L4,14.287 L5.524,14.287 L5.524,16.952 C5.524,17.792,6.316,18.476,7.288,18.476 L16.713,18.476 C17.685,18.476,18.477,17.791,18.477,16.952 L18.477,14.287 L20,14.287 L20,16.952 C20,18.633,18.526,20,16.712,20 Z\" />\r\n  <path\r\n      android:fillColor=\"#595757\"\r\n      android:pathData=\"M12,15.742 L7.382,11.127 L8.459,10.05 L11.239,12.827 L11.239,4 L12.761,4 L12.761,12.827 L15.541,10.05 L16.618,11.127 Z\" />\r\n</vector>"
  },
  {
    "path": "app/src/main/res/drawable/ic_edit.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"24\"\n    android:viewportHeight=\"24\">\n\n    <path\n        android:fillColor=\"#595757\"\n        android:pathData=\"M5.6,20.009c-0.881,0-1.6-0.789-1.6-1.758V5.749C4,4.78,4.719,3.991,5.6,3.991h10.62 c0.882,0,1.599,0.789,1.599,1.758v1.258h-1.483l-0.076-1.258c0-0.122-0.062-0.172-0.062-0.172L5.6,5.582 c0.002,0.015-0.039,0.069-0.039,0.167v12.502c0,0.107,0.051,0.164,0.063,0.172l10.596-0.005c-0.002-0.014,0.039-0.067,0.039-0.167 l0.016-4.739h1.469l0.075,4.739c0,0.969-0.717,1.758-1.599,1.758H5.6z\" />\n    <path\n        android:fillColor=\"#595757\"\n        android:pathData=\"M 12.549 13.354 L 13.738 12.323 L 13.738 12.323 L 18.967 7.553 L 20 8.646 L 14.658 13.514 L 13.54 14.515 Z\" />\n</vector>"
  },
  {
    "path": "app/src/main/res/drawable/ic_exchange.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"24\"\n    android:viewportHeight=\"24\">\n\n    <path\n        android:fillColor=\"#595757\"\n        android:pathData=\"M17.139,8.073l-1.06,1.059l1.058,1.058L20,7.326l-2.863-2.863L16.079,5.52l1.06,1.06h-3.801 c-1.465,0-2.613,1.423-2.613,3.24v1.869v0.624v0.626v1.243c0,0.946-0.514,1.746-1.121,1.746H4v1.494h5.604 c1.467,0,2.614-1.423,2.614-3.24v-1.869v-0.624V9.819c0-0.946,0.515-1.746,1.12-1.746H17.139z\" />\n    <path\n        android:fillColor=\"#595757\"\n        android:pathData=\"M 16.079 14.867 L 17.139 15.926 L 13.338 15.926 L 13.338 17.42 L 17.139 17.42 L 16.079 18.48 L 17.137 19.537 L 20 16.673 L 17.137 13.81 Z\" />\n    <path\n        android:fillColor=\"#595757\"\n        android:pathData=\"M 4 6.586 H 9.604 V 8.08 H 4 V 6.586 Z\" />\n</vector>"
  },
  {
    "path": "app/src/main/res/drawable/ic_exchange_order.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"1112\"\n    android:viewportHeight=\"1024\">\n\n    <path\n        android:fillColor=\"#595757\"\n        android:pathData=\"M385.722775 1023.999431a42.664285 42.664285 0 0 1-42.664285-42.664284V138.573312L72.851354 381.190877a42.664285 42.664285 0 1 1-56.885712-63.42757L357.279918 10.864887A42.664285 42.664285 0 0 1 428.387059 42.720886v938.614261a42.664285 42.664285 0 0 1-42.664284 42.664284zM727.037051 1023.999431a41.52657 41.52657 0 0 1-17.350142-3.697571A42.664285 42.664285 0 0 1 684.372767 981.335147v-938.614261a42.664285 42.664285 0 0 1 85.328569 0v842.761835l270.207135-242.617565a42.664285 42.664285 0 1 1 56.885713 63.427569l-341.314276 306.898421a42.664285 42.664285 0 0 1-28.442857 10.808285z\" />\n\n</vector>"
  },
  {
    "path": "app/src/main/res/drawable/ic_exit.xml",
    "content": "<vector android:autoMirrored=\"true\"\n    android:height=\"24dp\"\n    android:tint=\"#000000\"\n    android:viewportHeight=\"24\"\n    android:viewportWidth=\"24\"\n    android:width=\"24dp\"\n    xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <path\n        android:fillColor=\"@android:color/white\"\n        android:pathData=\"M17,7l-1.41,1.41L18.17,11H8v2h10.17l-2.58,2.58L17,17l5,-5zM4,5h8V3H4c-1.1,0 -2,0.9 -2,2v14c0,1.1 0.9,2 2,2h8v-2H4V5z\" />\n</vector>"
  },
  {
    "path": "app/src/main/res/drawable/ic_expand_less.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"24\"\n    android:viewportHeight=\"24\">\n    <path\n        android:fillColor=\"#FF000000\"\n        android:pathData=\"M12,8l-6,6 1.41,1.41L12,10.83l4.59,4.58L18,14l-6,-6z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_expand_more.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"24\"\n    android:viewportHeight=\"24\">\n    <path\n        android:fillColor=\"#FF000000\"\n        android:pathData=\"M16.59,8.59L12,13.17 7.41,8.59 6,10l6,6 6,-6 -1.41,-1.41z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_export.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"28dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"1280\"\n    android:viewportHeight=\"1024\">\n\n    <path\n        android:fillColor=\"#4A4B4A\"\n        android:pathData=\"M526 780H380.8c-19.2 0-34.8-15.6-34.8-34.8V267.8c0-19.2 15.6-34.8 34.8-34.8h477.4c19.2 0 34.8 15.6 34.8 34.8V413c0 18.8 15.2 34 34 34s34-15.2 34-34V234.1c0-38.2-31-69.1-69.1-69.1H347.1c-38.2 0-69.1 31-69.1 69.1v544.7c0 38.2 31 69.1 69.1 69.1H526c18.8 0 34-15.2 34-34 0-18.7-15.2-33.9-34-33.9z\" />\n    <path\n        android:fillColor=\"#4A4B4A\"\n        android:pathData=\"M950.9 654.8l-0.1-0.1L817.1 521c-13.3-13.3-34.8-13.3-48.1 0-13.3 13.3-13.3 34.8 0 48.1l75.9 75.9H632c-18.8 0-34 15.2-34 34s15.2 34 34 34h212.7L769 788.7c-13.3 13.3-13.3 34.8 0 48.1 13.3 13.3 34.8 13.3 48.1 0l131.6-131.6c7.5-6.2 12.3-15.6 12.3-26.1 0-9.6-3.9-18.1-10.1-24.3z\" />\n</vector>"
  },
  {
    "path": "app/src/main/res/drawable/ic_fast_forward.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"24.0\"\n    android:viewportHeight=\"24.0\">\n    <path\n        android:fillColor=\"#FF000000\"\n        android:pathData=\"M4,18l8.5,-6L4,6v12zM13,6v12l8.5,-6L13,6z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_fast_rewind.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"24.0\"\n    android:viewportHeight=\"24.0\">\n    <path\n        android:fillColor=\"#FF000000\"\n        android:pathData=\"M11,18L11,6l-8.5,6 8.5,6zM11.5,12l8.5,6L20,6l-8.5,6z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_find_replace.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"24\"\n    android:viewportHeight=\"24\">\n\n    <path\n        android:fillColor=\"#595757\"\n        android:pathData=\"M8.829,10.126 L7.502,11.451 C7.447,11.162,7.415,10.868,7.415,10.57 C7.415,7.997,9.506,5.907,12.077,5.907 C13.573,5.907,14.99,6.633,15.864,7.851 L16.947,7.071 C15.822,5.508,14.002,4.574,12.077,4.574 C8.772,4.574,6.083,7.263,6.083,10.57 C6.083,10.684,6.107,10.795,6.113,10.909 L5.33,10.126 L4.388,11.068 L7.078,13.757 L9.769,11.069 L8.829,10.126 Z\" />\n    <path\n        android:fillColor=\"#595757\"\n        android:pathData=\"M20.388,18.483 L16.501,14.596 C17.472,13.531,18.07,12.12,18.07,10.57 C18.07,10.536,18.064,10.503,18.064,10.468 L18.955,11.359 L19.897,10.417 L17.206,7.727 L14.515,10.416 L15.455,11.359 L16.708,10.107 C16.723,10.26,16.737,10.415,16.737,10.57 C16.737,13.138,14.647,15.228,12.076,15.228 C10.742,15.228,9.469,14.656,8.584,13.656 L7.586,14.539 C8.724,15.823,10.36,16.561,12.076,16.561 C13.349,16.561,14.528,16.159,15.5,15.48 L19.446,19.425 L20.388,18.483 Z\" />\n</vector>"
  },
  {
    "path": "app/src/main/res/drawable/ic_folder.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:tint=\"#707070\"\n    android:viewportHeight=\"24.0\"\n    android:viewportWidth=\"24.0\">\n    <path\n        android:fillColor=\"#FF000000\"\n        android:pathData=\"M10,4H4c-1.1,0 -1.99,0.9 -1.99,2L2,18c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2V8c0,-1.1 -0.9,-2 -2,-2h-8l-2,-2z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_folder_open.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"24.0\"\n    android:viewportHeight=\"24.0\">\n    <path\n        android:fillColor=\"#FF000000\"\n        android:pathData=\"M20,6h-8l-2,-2L4,4c-1.1,0 -1.99,0.9 -1.99,2L2,18c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2L22,8c0,-1.1 -0.9,-2 -2,-2zM20,18L4,18L4,8h16v10z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_folder_outline.xml",
    "content": "<vector android:height=\"24dp\"\n    android:tint=\"#000000\"\n    android:viewportHeight=\"24\"\n    android:viewportWidth=\"24\"\n    android:width=\"24dp\"\n    xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <path\n        android:fillColor=\"@android:color/white\"\n        android:pathData=\"M9.17,6l2,2H20v10H4V6h5.17M10,4H4c-1.1,0 -1.99,0.9 -1.99,2L2,18c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2V8c0,-1.1 -0.9,-2 -2,-2h-8l-2,-2z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_groups.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\r\n    android:width=\"24dp\"\r\n    android:height=\"24dp\"\r\n    android:viewportWidth=\"24\"\r\n    android:viewportHeight=\"24\">\r\n\r\n    <path\r\n        android:fillColor=\"#595757\"\r\n        android:pathData=\"M 11.221 5.832 L 11.221 6.608 L 11.221 11.094 L 9.57 11.094 L 9.57 12.647 L 11.221 12.647 L 11.221 17.909 L 11.229 17.909 L 11.229 18.686 L 13.939 18.686 L 13.939 17.132 L 12.773 17.132 L 12.773 6.608 L 13.939 6.608 L 13.939 5.055 L 11.221 5.055 Z\" />\r\n    <path\r\n        android:fillColor=\"#595757\"\r\n        android:pathData=\"M6.707,7.27H4.414C3.396,7.27,2.6,7.963,2.6,8.848v6.044c0,0.885,0.797,1.578,1.814,1.578h2.293 c1.019,0,1.816-0.693,1.816-1.578V8.848C8.523,7.963,7.726,7.27,6.707,7.27z M6.707,14.917H4.414c-0.131,0-0.215-0.041-0.246-0.041 c-0.01,0-0.014,0.003-0.015,0.012L4.137,8.913C4.15,8.895,4.24,8.823,4.414,8.823h2.293c0.17,0,0.26,0.067,0.263,0.029l0.017,5.975 C6.973,14.845,6.881,14.917,6.707,14.917z\" />\r\n    <path\r\n        android:fillColor=\"#595757\"\r\n        android:pathData=\"M16.91,8.793h2.805c0.86,0,1.561-0.699,1.561-1.559V4.429c0-0.86-0.7-1.559-1.561-1.559H16.91 c-0.859,0-1.559,0.699-1.559,1.559v2.805C15.352,8.094,16.051,8.793,16.91,8.793z M16.91,4.423l2.812,0.006L19.717,7.24 c0,0,0,0-0.002,0l-2.81-0.006L16.91,4.423z\" />\r\n    <path\r\n        android:fillColor=\"#595757\"\r\n        android:pathData=\"M19.84,14.947h-2.805c-0.859,0-1.558,0.699-1.558,1.559v2.805c0,0.86,0.698,1.559,1.558,1.559h2.805 c0.861,0,1.561-0.699,1.561-1.559v-2.805C21.4,15.646,20.701,14.947,19.84,14.947z M19.848,19.312c0,0-0.004,0.005-0.008,0.005 l-2.809-0.006l0.004-2.811l2.813,0.006V19.312z\" />\r\n</vector>"
  },
  {
    "path": "app/src/main/res/drawable/ic_help.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\r\n    android:width=\"24dp\"\r\n    android:height=\"24dp\"\r\n    android:viewportWidth=\"1024\"\r\n    android:viewportHeight=\"1024\">\r\n  <path\r\n      android:fillColor=\"#000000\"\r\n      android:pathData=\"M474.84,721.55c0,21.68 17.55,38.71 38.71,38.71 21.68,0 38.71,-17.55 38.71,-38.71 0,-13.94 -7.23,-26.84 -19.61,-33.55 -11.87,-6.71 -26.84,-6.71 -38.71,0s-19.1,19.61 -19.1,33.55zM474.84,721.55M509.94,263.74C433.55,263.74 371.61,325.16 371.61,401.03c0,17.03 13.94,31.48 31.48,31.48 17.03,0 30.97,-13.94 31.48,-31.48 0,-41.81 33.03,-74.32 75.87,-74.32 41.81,0 75.87,34.06 75.87,76.39 0,16 -22.71,38.71 -42.84,58.84 -29.94,29.94 -64,64 -64,109.94v45.42c0.52,16.52 14.45,29.94 31.48,29.94s30.45,-13.42 30.97,-29.94v-45.42c0,-19.61 24.26,-44.39 45.94,-65.55 29.94,-29.94 60.9,-60.9 60.9,-102.71 -1.03,-77.42 -62.97,-139.87 -138.84,-139.87zM509.94,263.74\"/>\r\n  <path\r\n      android:fillColor=\"#000000\"\r\n      android:pathData=\"M512,157.42c195.61,0 354.06,158.97 354.58,354.58 0,195.61 -158.97,354.58 -354.58,354.58s-354.58,-158.97 -354.58,-354.58 159.48,-354.58 354.58,-354.58m0,-68.65c-233.81,0 -423.23,189.42 -423.23,423.23s189.42,423.23 423.23,423.23 423.23,-189.42 423.23,-423.23 -189.42,-423.23 -423.23,-423.23zM512,88.77\"/>\r\n</vector>\r\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_history.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"24\"\n    android:viewportHeight=\"24\">\n    <path\n        android:pathData=\"M12,3c-4.963,0 -9,4.037 -9,9s4.037,9 9,9s9,-4.037 9,-9S16.963,3 12,3zM12,19.419c-4.091,0 -7.419,-3.328 -7.419,-7.419S7.909,4.581 12,4.581S19.419,7.909 19.419,12S16.091,19.419 12,19.419z\"\n        android:fillColor=\"#4A4B4A\" />\n    <path\n        android:pathData=\"M12.082,12.53l0,-5.545l-1.582,0l0,6.456l4.441,2.559l0.788,-1.372z\"\n        android:fillColor=\"#4a4b4a\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_image.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"1024\"\n    android:viewportHeight=\"1024\">\n\n    <path\n        android:fillColor=\"#000000\"\n        android:pathData=\"M938.667 0H85.333A85.333 85.333 0 0 0 0 85.333v853.334A85.333 85.333 0 0 0 85.333 1024h853.334A85.333 85.333 0 0 0 1024 938.667V85.333A85.333 85.333 0 0 0 938.667 0zM85.333 42.667h853.334a42.667 42.667 0 0 1 42.666 42.666V640L746.667 405.333a21.333 21.333 0 0 0-30.08 0L480 641.92 321.707 483.627a21.333 21.333 0 0 0-30.08 0l-248.96 248.96V85.333a42.667 42.667 0 0 1 42.666-42.666z m-42.666 896v-145.92l264.106-263.894 452.48 452.48H85.333a42.667 42.667 0 0 1-42.666-42.666z m896 42.666H819.413L510.08 672l221.867-221.867L981.333 699.52v239.147a42.667 42.667 0 0 1-42.666 42.666z\" />\n    <path\n        android:fillColor=\"#000000\"\n        android:pathData=\"M298.667 405.333A106.667 106.667 0 1 0 192 298.667a106.667 106.667 0 0 0 106.667 106.666z m0-170.666a64 64 0 1 1-64 64 64 64 0 0 1 64-64z\" />\n</vector>"
  },
  {
    "path": "app/src/main/res/drawable/ic_import.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"28dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"1280\"\n    android:viewportHeight=\"1024\">\n\n    <path\n        android:fillColor=\"#000000\"\n        android:pathData=\"M394 376V230.8c0-19.2 15.6-34.8 34.8-34.8h477.4c19.2 0 34.8 15.6 34.8 34.8v477.4c0 19.2-15.6 34.8-34.8 34.8H761c-18.8 0-34 15.2-34 34s15.2 34 34 34h178.9c38.2 0 69.1-31 69.1-69.1V197.1c0-38.2-31-69.1-69.1-69.1H395.1c-38.2 0-69.1 31-69.1 69.1V376c0 18.8 15.2 34 34 34s34-15.2 34-34z\" />\n    <path\n        android:fillColor=\"#000000\"\n        android:pathData=\"M678.9 618.8l-0.1-0.1L545.1 485c-13.3-13.3-34.8-13.3-48.1 0-13.3 13.3-13.3 34.8 0 48.1l75.9 75.9H360c-18.8 0-34 15.2-34 34s15.2 34 34 34h212.7L497 752.7c-13.3 13.3-13.3 34.8 0 48.1 13.3 13.3 34.8 13.3 48.1 0l131.6-131.6c7.5-6.2 12.3-15.6 12.3-26.1 0-9.6-3.9-18.1-10.1-24.3z\" />\n\n</vector>"
  },
  {
    "path": "app/src/main/res/drawable/ic_interface_setting.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"24\"\n    android:viewportHeight=\"24\">\n\n    <path\n        android:fillColor=\"#595757\"\n        android:pathData=\"M8.348,3.999L4,19.977h1.59l1.244-4.841h4.732l1.321,4.841h1.706L9.96,3.999H8.348z M7.266,13.414 l1.243-4.687c0.263-1.01,0.466-2.026,0.61-3.052c0.174,0.865,0.443,1.97,0.803,3.313l1.184,4.426H7.266z\" />\n    <path\n        android:fillColor=\"#595757\"\n        android:pathData=\"M20.261,19.962l-0.107-0.231c-0.116-0.251-0.195-0.515-0.235-0.783 c-0.027-0.2-0.06-0.721-0.06-2.064v-1.581c0-0.516-0.019-0.905-0.055-1.131c-0.065-0.358-0.176-0.647-0.338-0.883 c-0.167-0.243-0.425-0.445-0.765-0.599c-0.339-0.153-0.763-0.229-1.297-0.229c-0.529,0-1.001,0.088-1.399,0.263 c-0.418,0.18-0.731,0.435-0.961,0.779c-0.212,0.322-0.354,0.74-0.422,1.227c-0.005,0.015-0.009,0.028-0.014,0.045l-0.047,0.208 l1.34,0.181l0.032-0.153c0.096-0.444,0.244-0.766,0.43-0.929c0.187-0.163,0.48-0.242,0.898-0.242c0.444,0,0.766,0.111,0.983,0.34 c0.146,0.155,0.225,0.462,0.225,0.882l-0.001,0.175c-0.362,0.133-0.898,0.251-1.599,0.352c-0.409,0.059-0.707,0.119-0.909,0.187 c-0.284,0.094-0.544,0.235-0.773,0.422c-0.233,0.188-0.424,0.441-0.568,0.753c-0.14,0.311-0.21,0.654-0.21,1.021 c0,0.634,0.19,1.163,0.566,1.574c0.38,0.416,0.926,0.627,1.623,0.627c0.41,0,0.804-0.084,1.17-0.249 c0.269-0.122,0.549-0.329,0.826-0.61c0.042,0.226,0.105,0.423,0.193,0.605l0.048,0.1l0.848-0.032h0.563l-0.01-0.021L20.261,19.962z M18.464,16.601v0.136c0,0.494-0.049,0.869-0.145,1.111c-0.127,0.315-0.317,0.556-0.581,0.733c-0.26,0.176-0.563,0.265-0.904,0.265 c-0.333,0-0.565-0.081-0.733-0.255c-0.163-0.173-0.243-0.386-0.243-0.653c0-0.176,0.038-0.333,0.117-0.473 c0.068-0.134,0.168-0.233,0.302-0.303c0.15-0.077,0.421-0.15,0.803-0.217C17.645,16.848,18.109,16.732,18.464,16.601z\" />\n</vector>"
  },
  {
    "path": "app/src/main/res/drawable/ic_launcher1.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"108dp\"\n    android:height=\"108dp\"\n    android:viewportWidth=\"108\"\n    android:viewportHeight=\"108\">\n    <path\n        android:pathData=\"M36.77,46.15l-1.87,1.83l0,-18.23l1.98,0.72l-0.11,15.68z\"\n        android:fillColor=\"#fff\" />\n    <path\n        android:pathData=\"M40.09,28.44l-1.4,1.7l-1.77,-3.33l3.17,1.63z\"\n        android:fillColor=\"#fff\" />\n    <path\n        android:pathData=\"M40.13,30.58l2.44,1.62l-1.1,1.27l-1.34,-2.89z\"\n        android:fillColor=\"#fff\" />\n    <path\n        android:pathData=\"M51.49,30.1l-0.04,16.01l-1.88,1.89l-2.67,-2.66l2.67,0.55l0,-16.67l-7.69,0l-0.44,-0.43l8.04,0l0.97,-1.09l1.78,1.33l-0.74,1.07z\"\n        android:fillColor=\"#fff\" />\n    <path\n        android:pathData=\"M43.91,45.12l1.55,-0.67h3.33l-1.33,-1.66 -0.83,1.1L45.46,43.89L45.46,38.35l-1.09,-0.56h3L47.37,34.73l0.54,-0.7L46.15,32.9l-0.78,1L44,33.9l2.82,-3.28 -2,-0.66 -1.14,3.94h-3l-1.57,-0.67L39.11,39l1.57,-1.2h0.59L38,45.52l5.09,-7.17 -1.37,-0.56h2.08l0.06,6.1ZM40.65,37.27v-3h4.72v3Z\"\n        android:fillColor=\"#fff\" />\n    <path\n        android:pathData=\"M57.78,46.56l-1.67,-1.67c0.82,-0.4 1.53,-0.83 1.53,-0.83V34.21H55l-0.45,-0.53H57.4l1,-1.13 1.86,1.14 -0.55,0.78v8.65l2.77,-1.56Z\"\n        android:fillColor=\"#fff\" />\n    <path\n        android:pathData=\"M68.24,40.26l3.58,0l1.23,-1.51l2.15,2.04l-6.94,0.04l-6.27,7.14l4.33,-7.14l-5.07,0l-0.53,-0.57l5.57,0l0,-6.14l1.95,0.68l0,5.46z\"\n        android:fillColor=\"#fff\" />\n    <path\n        android:pathData=\"M68.06,29.92l2.28,0l1.05,-1.05l1.7,1.6l-5.03,0l0,2.6l3.92,-0.05l0.45,-0.91l2.05,1.87l-3.5,1.82l0.78,-2.22l-10.3,0.01l-0.48,-0.52l5.16,0l0,-2.6l-3.8,0l-0.48,-0.55l4.28,0l0,-3.14l1.92,0.58l0,2.56z\"\n        android:fillColor=\"#fff\" />\n    <path\n        android:pathData=\"M63.08,34.38l2.69,0.97l-0.89,1.55l-1.8,-2.52z\"\n        android:fillColor=\"#fff\" />\n    <path\n        android:pathData=\"M61.63,36.71l3.25,1.19l-1.09,1.78l-2.16,-2.97z\"\n        android:fillColor=\"#fff\" />\n    <path\n        android:pathData=\"M73.64,45.23l-1.53,2.13l-4.13,-4.85l5.66,2.72z\"\n        android:fillColor=\"#fff\" />\n    <path\n        android:pathData=\"M59.55,29.14l-1.66,1.88l-1.78,-3.66l3.44,1.78z\"\n        android:fillColor=\"#fff\" />\n    <path\n        android:pathData=\"M39,50.55H37.18a0.18,0.18 0,0 0,-0.18 0.18v4.78a0.18,0.18 0,0 0,0.18 0.19h0.19a0.18,0.18 0,0 0,0.18 -0.19V51.1H39Z\"\n        android:fillColor=\"#fff\" />\n    <path\n        android:pathData=\"M39.35,53.45a1.47,1.47 0,0 0,-0.33 -2.9v0.55a0.92,0.92 0,0 1,0.92 0.92,0.91 0.91,0 0,1 -0.87,0.91h-0.6l1.4,2.64a0.23,0.23 0,0 0,0.2 0.12h0.5Z\"\n        android:fillColor=\"#fff\" />\n    <path\n        android:pathData=\"M50,52.2v0.35A1.8,1.8 0,0 0,48.53 52,1.84 1.84,0 1,0 50,55.17v0.34a0.18,0.18 0,0 0,0.18 0.19h0.18a0.18,0.18 0,0 0,0.19 -0.19L50.55,52.2a0.18,0.18 0,0 0,-0.19 -0.18h-0.18A0.18,0.18 0,0 0,50 52.2ZM49,55.12a1.28,1.28 0,0 1,-1.51 -1.51,1.26 1.26,0 0,1 1,-1A1.28,1.28 0,0 1,50 54.11,1.26 1.26,0 0,1 49,55.12Z\"\n        android:fillColor=\"#fff\" />\n    <path\n        android:pathData=\"M55,50.73v1.82A1.8,1.8 0,0 0,53.51 52a1.84,1.84 0,0 0,0.18 3.67A1.82,1.82 0,0 0,55 55.17v0.34a0.18,0.18 0,0 0,0.18 0.19h0.19a0.18,0.18 0,0 0,0.18 -0.19L55.55,50.73a0.18,0.18 0,0 0,-0.18 -0.18h-0.19A0.18,0.18 0,0 0,55 50.73ZM54,55.12a1.28,1.28 0,0 1,-1.51 -1.51,1.26 1.26,0 0,1 1,-1A1.28,1.28 0,0 1,55 54.11,1.26 1.26,0 0,1 53.94,55.12Z\"\n        android:fillColor=\"#fff\" />\n    <path\n        android:pathData=\"M43.73,52a1.84,1.84 0,0 0,-1.84 1.9,1.86 1.86,0 0,0 1.84,1.78 1.83,1.83 0,0 0,1.64 -1h-0.56a0.2,0.2 0,0 0,-0.16 0.07,1.29 1.29,0 0,1 -2.18,-0.63h3.07a1.24,1.24 0,0 0,0 -0.27A1.84,1.84 0,0 0,43.73 52ZM43.73,52.55a1.31,1.31 0,0 1,1.26 1L42.47,53.55A1.3,1.3 0,0 1,43.73 52.57Z\"\n        android:fillColor=\"#fff\" />\n    <path\n        android:pathData=\"M60.12,49.98L70.5,49.98A1.5,1.5 0,0 1,72 51.48L72,54.66A1.5,1.5 0,0 1,70.5 56.16L60.12,56.16A1.5,1.5 0,0 1,58.62 54.66L58.62,51.48A1.5,1.5 0,0 1,60.12 49.98z\"\n        android:fillColor=\"#ff6161\" />\n    <path\n        android:pathData=\"M63.69,54.71a0.41,0.41 0,0 1,-0.41 0.41H60.36v-0.51H63.1V53.28H60.36v-0.5H63.1V51.53H60.36V51h2.92a0.41,0.41 0,0 1,0.41 0.41Z\"\n        android:fillColor=\"#fff\" />\n    <path\n        android:pathData=\"M65.55,55h-0.77v-0.74h0.77Z\"\n        android:fillColor=\"#fff\" />\n    <path\n        android:pathData=\"M69.54,55.12L67,55.12a0.41,0.41 0,0 1,-0.41 -0.42L66.59,51.43A0.41,0.41 0,0 1,67 51h2.55a0.41,0.41 0,0 1,0.42 0.41L69.97,54.7A0.42,0.42 0,0 1,69.54 55.12ZM69.39,54.61L69.39,51.53L67.15,51.53v3.08Z\"\n        android:fillColor=\"#fff\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_launcher1_b.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:aapt=\"http://schemas.android.com/aapt\"\n    android:width=\"108dp\"\n    android:height=\"108dp\"\n    android:viewportWidth=\"108\"\n    android:viewportHeight=\"108\">\n    <path\n        android:fillColor=\"#4d4d4d\"\n        android:pathData=\"M0,0h108v108h-108z\" />\n    <path android:pathData=\"M0,108H54V65c0,-12 -54,-5 -54,-5Z\">\n        <aapt:attr name=\"android:fillColor\">\n            <gradient\n                android:endX=\"53.06\"\n                android:endY=\"88.57\"\n                android:startX=\"0.62\"\n                android:startY=\"79.9\"\n                android:type=\"linear\">\n                <item\n                    android:color=\"#FFFFFFFF\"\n                    android:offset=\"0\" />\n                <item\n                    android:color=\"#FFCCCCCC\"\n                    android:offset=\"1\" />\n            </gradient>\n        </aapt:attr>\n    </path>\n    <path\n        android:fillColor=\"#f2f2f2\"\n        android:pathData=\"M108,108H54V65c0,-12 54,-5 54,-5Z\" />\n    <path android:pathData=\"M71.5,69 L78,75V58a94.89,94.89 0,0 0,-13 0.75V75Z\">\n        <aapt:attr name=\"android:fillColor\">\n            <gradient\n                android:endX=\"71.5\"\n                android:endY=\"75\"\n                android:startX=\"71.5\"\n                android:startY=\"58.1\"\n                android:type=\"linear\">\n                <item\n                    android:color=\"#FFFFFFFF\"\n                    android:offset=\"0\" />\n                <item\n                    android:color=\"#FFFFE1DD\"\n                    android:offset=\"0.24\" />\n                <item\n                    android:color=\"#FFFFBEB6\"\n                    android:offset=\"0.58\" />\n                <item\n                    android:color=\"#FFFFA89D\"\n                    android:offset=\"0.84\" />\n                <item\n                    android:color=\"#FFFFA094\"\n                    android:offset=\"1\" />\n            </gradient>\n        </aapt:attr>\n    </path>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_launcher2.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"108dp\"\n    android:height=\"108dp\"\n    android:viewportWidth=\"108\"\n    android:viewportHeight=\"108\">\n    <path\n        android:pathData=\"M30.79,64.69l0,-23.38l46.76,-0l0,23.38z\"\n        android:strokeWidth=\"0.5\"\n        android:fillColor=\"#fff\"\n        android:strokeColor=\"#000\" />\n    <path\n        android:pathData=\"M26.33,66.36l0,-26.72l4.45,-0l0,26.72z\"\n        android:strokeWidth=\"0.5\"\n        android:fillColor=\"#fff\"\n        android:strokeColor=\"#000\" />\n    <path\n        android:pathData=\"M26.34,37.41l0,-3.34l4.45,-0l0,3.34z\"\n        android:strokeWidth=\"0.5\"\n        android:fillColor=\"#fff\"\n        android:strokeColor=\"#000\" />\n    <path\n        android:pathData=\"M27.44,39.64l0,-2.23l2.23,-0l0,2.23z\"\n        android:strokeWidth=\"0.5\"\n        android:fillColor=\"#fff\"\n        android:strokeColor=\"#000\" />\n    <path\n        android:pathData=\"M26.33,71.93l0,-3.34l4.45,-0l0,3.34z\"\n        android:strokeWidth=\"0.5\"\n        android:fillColor=\"#fff\"\n        android:strokeColor=\"#000\" />\n    <path\n        android:pathData=\"M27.45,68.58l0,-2.23l2.23,-0l0,2.23z\"\n        android:strokeWidth=\"0.5\"\n        android:fillColor=\"#fff\"\n        android:strokeColor=\"#000\" />\n    <path\n        android:pathData=\"M77.54,66.36l0,-26.72l4.45,-0l0,26.72z\"\n        android:strokeWidth=\"0.5\"\n        android:fillColor=\"#fff\"\n        android:strokeColor=\"#000\" />\n    <path\n        android:pathData=\"M77.55,37.42l0,-3.34l4.45,-0l0,3.34z\"\n        android:strokeWidth=\"0.5\"\n        android:fillColor=\"#fff\"\n        android:strokeColor=\"#000\" />\n    <path\n        android:pathData=\"M78.66,39.64l0,-2.23l2.23,-0l0,2.23z\"\n        android:strokeWidth=\"0.5\"\n        android:fillColor=\"#fff\"\n        android:strokeColor=\"#000\" />\n    <path\n        android:pathData=\"M77.55,71.93l0,-3.34l4.45,-0l0,3.34z\"\n        android:strokeWidth=\"0.5\"\n        android:fillColor=\"#fff\"\n        android:strokeColor=\"#000\" />\n    <path\n        android:pathData=\"M78.66,68.59l0,-2.23l2.23,-0l0,2.23z\"\n        android:strokeWidth=\"0.5\"\n        android:fillColor=\"#fff\"\n        android:strokeColor=\"#000\" />\n    <path\n        android:pathData=\"M37.65,47.8l-0.08,11.84a4.53,4.53 0,0 1,-1.23 2.54c-0.2,0 -0.33,0 -0.38,-0.16L36,56.41l0.07,-8L36,46.19l1.76,0.61c0.16,0.05 0.18,0.15 0.08,0.31ZM44.95,53.8c0.11,0.05 0.11,0.13 0,0.23l-0.23,0.46 -0.46,2.23c-0.1,0.41 0,0.64 0.31,0.69a4.31,4.31 0,0 0,1.61 -0.15c0.36,0 0.64,-0.34 0.85,-1a0.22,0.22 0,0 1,0.15 -0.08s0.08,0 0.08,0.15c-0.1,0.62 0,1 0.23,1.08 0.56,0.67 0.39,1.15 -0.54,1.46a5.1,5.1 0,0 1,-2.38 0.15c-0.92,-0.25 -1.36,-1.07 -1.31,-2.46l0.08,-1.92L43.34,53.26L41.8,53.26l1.08,0.46c0.1,0.05 0.1,0.13 0,0.23l-0.23,0.31a17.39,17.39 0,0 1,-4.92 6.15c-0.16,0.05 -0.18,0 -0.08,-0.16a17.22,17.22 0,0 0,3.69 -7h-0.61a3,3 0,0 1,-0.93 1.23c-0.25,0.15 -0.41,0.13 -0.46,-0.08l0.08,-2.15L39.42,51.18c0,-0.25 0,-0.66 -0.08,-1.23v-0.69s0,-0.08 0.16,-0.08l1.3,0.7 2.54,-0.08a14.23,14.23 0,0 0,1 -3.46l1.69,0.54c0.1,0 0.1,0.15 0,0.31s-0.44,0.46 -0.85,0.92L43.65,49.8h1.23l0.69,-0.85 1.54,0.85q0.15,0 0,0.15l-0.39,0.62 -0.07,2.3 0.15,0.23c0,0.11 0,0.16 -0.08,0.16L43.8,53.26ZM38,43.57a0.66,0.66 0,0 1,0.24 0.08q2.53,0.84 2.46,1.38c0.05,0.41 -0.24,0.9 -0.85,1.46 -0.36,0.16 -0.59,0.08 -0.69,-0.23a6.91,6.91 0,0 0,-1.31 -2.53C37.83,43.57 37.88,43.52 38,43.57ZM40.39,46.8c1.28,0.57 1.94,1.08 2,1.54a1.29,1.29 0,0 1,-0.77 1.07c-0.31,0.06 -0.49,0 -0.54,-0.3A2.79,2.79 0,0 0,40.34 47C40.29,46.8 40.32,46.75 40.42,46.8ZM40.7,50.26v2.61l4.15,-0.07L44.85,50.18ZM44.39,45.88A18.47,18.47 0,0 0,42 46l-0.61,-0.46 6.69,-0.23 0.84,-0.92 1.46,1h0.08s0,0.13 -0.08,0.23l-0.54,0.84L50,54.18l0.15,4.23A3.81,3.81 0,0 1,47.72 62a0.62,0.62 0,0 1,-0.23 0.08c-0.05,-0.05 -0.05,-0.13 0,-0.23a2.13,2.13 0,0 0,-0.77 -1.62,0.21 0.21,0 0,1 -0.07,-0.15c0,-0.05 0,-0.08 0.07,-0.08 1.08,0 1.56,-0.2 1.46,-0.76l-0.07,-13.54Z\"\n        android:fillColor=\"#002747\" />\n    <path\n        android:pathData=\"M60.87,56.64c0.15,0 0.18,0 0.08,0.16l-2.7,2.76A20.06,20.06 0,0 0,56.79 61c0,0.11 -0.13,0.11 -0.23,0l-1.15,-1.15c-0.15,-0.1 -0.18,-0.18 -0.08,-0.23 0.72,-0.36 1.26,-0.62 1.62,-0.77l-0.08,-8.76 -1.31,0.07a5.48,5.48 0,0 0,-1.53 0.16l-0.7,-0.62 3.31,-0.07 0.85,-1 1.53,0.92c0.1,0 0.1,0.13 0,0.23l-0.38,0.54 -0.54,7.84ZM55.64,44q2.23,0.86 2.61,1.62a1.67,1.67 0,0 1,-0.84 1.61c-0.36,0.16 -0.62,0.08 -0.77,-0.23a5.55,5.55 0,0 0,-1.15 -2.84C55.43,44 55.49,44 55.64,44ZM64.87,50 L66.94,50.69c0.16,0.05 0.18,0.18 0.08,0.39l-0.39,0.53 -0.23,2.54a7.72,7.72 0,0 1,-0.23 1.23l3.15,-0.08L70.4,54.1l1.54,1.39c0.1,0.1 0.08,0.15 -0.08,0.15l-5.76,0.23Q65.56,60.34 56,62.48c-0.15,0 -0.23,0 -0.23,-0.07a0.23,0.23 0,0 1,0.16 -0.23q7.92,-2.16 8.53,-6.31l-2.3,0.16a12,12 0,0 0,-1.77 0.15,1.41 1.41,0 0,1 -0.39,0.08l-0.46,-0.62 5,-0.15a4.91,4.91 0,0 0,0.15 -1.15l0.16,-1.54a16.55,16.55 0,0 0,0 -2.23ZM63.17,47c-0.71,0.06 -1.38,0.13 -2,0.24l-0.62,-0.62 3.77,-0.08L64.32,46c0,-1 0,-1.82 -0.08,-2.39l2.08,0.54c0.1,0 0.15,0 0.15,0.08a0.24,0.24 0,0 1,-0.08 0.3,1.68 1.68,0 0,0 -0.3,1.16l-0.08,0.77 2,-0.08 0.92,-0.92 1.23,1.07 0.08,0.16a0.23,0.23 0,0 1,-0.16 0.07L66,46.88v2.3l3.07,-0.07 0.77,-0.85 1.85,1.62a0.24,0.24 0,0 1,0.15 0.23,0.91 0.91,0 0,1 -0.23,0.15 5.08,5.08 0,0 0,-2.92 1.23c-0.15,0 -0.21,0 -0.15,-0.15l0.53,-1.77 -6.38,0.15c-0.1,0 -0.33,0 -0.69,0.08 -0.82,0.1 -1.36,0.18 -1.61,0.23l-0.62,-0.62 4.54,-0.15L64.31,47ZM60.48,52.39c1.59,0.41 2.44,0.82 2.54,1.23a1.28,1.28 0,0 1,-0.46 1.23c-0.31,0.25 -0.54,0.2 -0.69,-0.16a7.7,7.7 0,0 0,-1.46 -2.07C60.3,52.41 60.33,52.34 60.48,52.34ZM61.64,50.31c1.38,0.31 2.1,0.62 2.15,0.92a1.38,1.38 0,0 1,-0.38 1.16c-0.31,0.2 -0.52,0.15 -0.62,-0.16a3.29,3.29 0,0 0,-1.15 -1.77Q61.4,50.34 61.64,50.26ZM66.02,57.31c2.25,0.71 3.69,1.43 4.3,2.15 0.21,0.41 0.08,1 -0.38,1.77 -0.21,0.2 -0.36,0.3 -0.46,0.3s-0.23,-0.12 -0.39,-0.38a10.24,10.24 0,0 0,-3.15 -3.69S65.92,57.31 66,57.26Z\"\n        android:fillColor=\"#002747\" />\n    <path\n        android:pathData=\"M72.91,59.62a1.67,2.23 0,1 0,3.34 0a1.67,2.23 0,1 0,-3.34 0z\"\n        android:fillColor=\"#ed1c24\" />\n    <path\n        android:pathData=\"M74.13,58.36v1a0.39,0.39 0,0 1,-0.11 0.21s0,0 0,0L74.02,58.23l0.15,0.05s0,0 0,0ZM74.75,58.87s0,0 0,0l0,0 0,0.19s0,0.05 0,0.06h0.14s0,0 0.07,-0.09h0s0,0 0,0 0,0.08 0,0.09 0,0.1 0,0.12a0.53,0.53 0,0 1,-0.2 0,0.18 0.18,0 0,1 -0.11,-0.21v-0.28h-0.13l0.1,0v0l0,0a1.46,1.46 0,0 1,-0.42 0.52s0,0 0,0a1.34,1.34 0,0 0,0.31 -0.59h0a0.22,0.22 0,0 1,-0.08 0.1s0,0 0,0v-0.43s0,0 0,0l0.11,0.06h0.21a0.88,0.88 0,0 0,0.09 -0.29l0.14,0s0,0 0,0l-0.07,0.08 -0.13,0.14h0.1l0.06,-0.07 0.13,0.07s0,0 0,0l0,0.05v0.19l0,0s0,0 0,0h-0.25ZM74.16,58h0q0.22,0.08 0.21,0.12s0,0.07 -0.07,0.12 0,0 -0.06,0a0.68,0.68 0,0 0,-0.11 -0.21S74.15,58 74.16,58ZM74.37,58.28c0.11,0 0.16,0.09 0.17,0.13s0,0.06 -0.07,0.09 0,0 0,0a0.25,0.25 0,0 0,-0.07 -0.18S74.36,58.27 74.37,58.28ZM74.37,58.57v0.22h0.35v-0.22ZM74.69,58.2L74.5,58.2l0,0 0.57,0 0.07,-0.07 0.12,0.08h0s0,0 0,0l0,0.07v1a0.33,0.33 0,0 1,-0.2 0.31h0s0,0 0,0a0.21,0.21 0,0 0,-0.07 -0.14h0c0.09,0 0.13,0 0.13,-0.06L75.12,58.19Z\"\n        android:fillColor=\"#fff\" />\n    <path\n        android:pathData=\"M74.43,60.62s0,0 0,0l-0.23,0.23 -0.12,0.13h0l-0.1,-0.1s0,0 0,0l0.14,-0.06v-0.75h-0.24l-0.06,0h0.28l0.07,-0.08 0.14,0.08v0l0,0 0,0.67ZM74,59.55a0.41,0.41 0,0 1,0.22 0.13,0.15 0.15,0 0,1 -0.07,0.14s0,0 -0.07,0a0.46,0.46 0,0 0,-0.09 -0.24S74,59.54 74,59.55ZM74.78,60.06 L74.96,60.06s0,0 0,0l0,0 0,0.22a0.3,0.3 0,0 1,0 0.1h0.27l0.09,-0.11 0.13,0.12s0,0 0,0l-0.48,0c0,0.25 -0.32,0.44 -0.86,0.56h0v0c0.45,-0.13 0.69,-0.3 0.73,-0.54h-0.2l-0.15,0h0l0,0h0.42a0.44,0.44 0,0 0,0 -0.1v-0.13a0.6,0.6 0,0 0,0 -0.19ZM74.64,59.79 L74.47,59.79 74.47,59.74h0.32v0c0,-0.09 0,-0.15 0,-0.2l0.17,0h0a0,0 0,0 1,0 0s0,0 0,0.09v0.07L75,59.7l0.08,-0.08 0.1,0.1h-0.35L74.83,60h0.26l0.07,-0.07 0.15,0.13s0,0 0,0v0a0.39,0.39 0,0 0,-0.25 0.1h0l0,-0.15h-0.6l-0.14,0 0,-0.06h0.39v-0.2ZM74.41,60.25c0.13,0 0.21,0.07 0.21,0.11a0.1,0.1 0,0 1,0 0.1s0,0 -0.06,0a0.76,0.76 0,0 0,-0.13 -0.18S74.39,60.25 74.4,60.25ZM74.51,60.08c0.11,0 0.18,0.05 0.18,0.07a0.15,0.15 0,0 1,0 0.1s-0.05,0 -0.06,0a0.27,0.27 0,0 0,-0.09 -0.15S74.48,60.08 74.5,60.08ZM74.88,60.67a0.83,0.83 0,0 1,0.36 0.18,0.15 0.15,0 0,1 0,0.15l0,0 0,0a0.83,0.83 0,0 0,-0.27 -0.32Z\"\n        android:fillColor=\"#fff\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_launcher3.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"108dp\"\n    android:height=\"108dp\"\n    android:viewportWidth=\"108\"\n    android:viewportHeight=\"108\">\n    <path\n        android:pathData=\"M24.8,42h0.1c-0.1,-0.2 -0.1,-0.4 0.1,-0.5v-0.1c-0.8,-0.4 1.9,-3.3 2.3,-3.4c-0.1,-0.2 -0.1,-0.3 0.1,-0.4c-0.2,0 1.2,-1.9 1.2,-1.9c0,-0.1 0,-0.2 0,-0.2l0.1,-0.1l0.1,0.2l0.7,-0.7c0,0 1.8,-2.1 1.9,-2.3c0.1,0 0.2,0.1 0.3,0.2c0.3,-0.2 0.4,-0.4 0.4,-0.8c0.3,-0.3 0.7,-0.6 1.1,-0.9l0,0l0.6,-0.2c-0.2,-1.1 3.2,-2.8 4.2,-3.2l0.5,-0.6l0.4,0.2v-0.8c0,0 0.9,-0.4 0.9,-0.4c0.1,0 0.2,0 0.3,0L40,26.5v0.3c0.4,-0.1 0.6,-0.3 0.8,-0.7c0,0 0.1,0 0.1,-0.1l0.1,0.2c0.7,-0.5 1.4,-0.9 2.1,-1.2l0.1,0.2c0.6,-0.2 2.9,-0.9 3.3,-0.6c0.3,-0.2 0.5,-0.4 0.8,-0.5c0.4,0.1 0.8,0 1.2,-0.1c0.1,0 0.2,0.1 0.2,0.1c0.2,-0.1 0.4,-0.2 0.5,-0.4c0.1,0 1.8,-0.1 1.8,0c0,0 0.1,0.1 0.1,0.1c0.5,-0.3 0.8,-0.3 1.1,0c0.4,0 0.7,-0.1 1,-0.4c0.6,0.6 1.1,0.8 1.7,0.4c0.4,-0.1 0.8,-0.1 1.2,-0.1c0.1,0.1 0.2,0.2 0.3,0.4l0.3,-0.2c0,0.4 2.3,0.6 2.7,0.7c0.2,0.4 0.7,0.6 1.1,0.5c0.3,0.2 0.6,0.2 1,0.1c0.1,0.1 1.8,1.7 2.2,1.7c0,-0.1 0,-0.2 0,-0.2c0.1,-0.1 0.2,-0.1 0.3,-0.1c1,0.6 1.9,1 2.7,1.7c0.8,-0.6 1.6,1.1 2.2,0.8c0,0 0.1,0 0.1,0c0.8,0.5 2.8,1.7 2,2.8c1.3,0.7 1.2,-0.6 1.8,1.1c0.7,-0.6 0.7,0 1.1,0.4c-0.1,0.1 -0.8,0.7 -1,0.7l0,-0.3L73.1,34h0.7c0,-0.3 -1,-1.1 -1.3,-0.8c-0.1,0.5 -0.6,-1.4 -0.5,-1.4c-0.2,0.1 -0.3,0.3 -0.5,0.4c0,0 -0.1,-0.1 -0.2,-0.2h-0.4l0.2,-0.3c0.2,-0.6 0,-0.9 -0.7,-0.7c0,-0.6 0.2,-0.9 -0.4,-1.1v1h-0.1c-0.1,-0.1 -0.6,-0.7 -1,-0.3l-0.3,-0.1l-0.2,0.2c0,-0.2 -0.5,-0.5 0,-0.7L68,29.9v0.7c-1,-0.4 0.4,-1.9 -0.6,-2.1c0.1,0.5 0,0.8 -0.4,1.1l-0.3,-0.1c-0.1,0.1 -0.1,0.2 0.1,0.3c-0.5,0.4 -0.7,-1.7 -0.8,-2c0,-0.1 -0.2,-0.1 -0.3,-0.1c-0.2,0.4 -0.2,0.9 0,1.4H65v0.3c0,-0.4 -0.1,-0.7 -0.3,-1c-0.1,0.1 -0.2,0.3 -0.3,0.5c-0.1,-0.3 -0.3,-0.5 -0.6,-0.6c0,0.1 0,0.3 0,0.4c-0.7,0.3 -1.4,-1.4 -1.6,0.5h-0.1l-0.1,-0.8c-0.5,-0.1 -1,-0.2 -1.5,-0.3L60.5,28c-1.3,-0.3 -2.4,0.2 -3.6,-0.1l-0.1,0.3L56.6,28h-0.1l0.2,1h-0.4c0.1,-0.1 0.1,-0.2 0.2,-0.3c0.3,0.4 0.1,1.3 -0.6,1c0,0 -0.1,-0.2 0,-0.2c0.1,-0.4 -0.1,-0.6 -0.4,-0.7c-0.2,0.5 -0.9,1.2 -0.4,0l0.4,-0.2c0,-0.2 -0.2,-0.3 -0.4,-0.2l-0.1,0c-0.1,0 -4.1,3 -5,1.3c-0.7,0.8 -0.4,-0.6 -0.6,-0.9c-0.1,0.7 0.2,1.3 -0.5,1.5c-0.4,-0.7 -1.5,0.5 -1.6,0.8c-0.1,0.1 -0.3,0.1 -0.4,0.2L46.8,31l0.4,-0.1c0.1,-0.4 -0.1,-0.7 -0.4,-0.7c-0.6,1.6 -0.4,-0.9 -1.4,-0.2c0,0 -0.7,0 -0.8,0l0.1,0.3l-0.2,0.2c0.3,0.4 0.4,1.7 -0.8,1.7l0.1,-0.2c-0.3,-0.1 -0.6,-0.1 -0.8,0.2c-0.4,0 -0.8,0.2 -1,0.5c-0.3,0.4 -0.7,0.5 -1.3,0.3c0.1,0.1 0.1,0.3 0,0.4c-0.2,-0.1 -0.3,-0.1 -0.5,-0.1c0,0 0,0.1 0,0.1c0.1,0.1 0.2,0.2 0.2,0.3c-0.1,0.2 -0.2,0.2 -0.4,0c-0.1,0 -0.2,-0.1 -0.2,-0.1c-0.1,0.2 -0.2,0.4 -0.2,0.6L39.2,34h-0.1l0,0c-0.1,0.2 -0.3,0.3 -0.2,0.6c0,0.1 -0.1,0.2 -0.1,0.4l-0.3,-0.3l-0.8,0.7c-0.3,0.1 -2.6,2.4 -2.8,2.3c-0.5,0.5 -0.5,1.2 -0.9,1.7c-0.1,0 -0.1,0 -0.2,0c-0.3,-0.3 -0.3,0 -0.4,0.2c0.5,0.4 0,1.2 -0.3,1.5c-0.1,0 -0.2,0 -0.2,0c0,-0.1 -0.1,-0.1 -0.2,-0.2c-0.3,0.3 -0.3,0.7 -0.6,1c0,0 -1.4,3.2 -1.8,3.5c0,0 0,0.1 -0.1,0.1l0.7,0.1v0.1c-2.7,0 -2.2,9 -2,10.2c0.2,1.2 0.7,6.7 2,7.3c-0.1,0 1.2,2.1 1.3,1.8H33v1c0.6,0.2 0.5,0.7 0.6,1.2l0.2,-0.1c0.7,0 1.4,2.6 2.2,2.9c0.6,0 1.6,1.6 2.1,2c0,0.9 5.6,3.7 6.5,3.9h-0.1c1.5,0.5 4.8,1.7 6.3,1c0.6,0.4 1.6,-0.7 1.9,-0.8l0.2,0.3c0.8,-0.2 2.5,0.6 3,0.6h0.5c0,0 0.4,-1.2 0.7,-0.9l0.1,-0.2C57.5,76 57.7,76 58,76v0.7c1.2,-0.1 0.6,-1.7 1.4,-1.8c-0.1,-0.1 -0.3,-0.2 -0.4,-0.3c0.1,-0.1 0.2,-0.2 0.3,-0.3c0.3,0 0.4,0.2 0.5,0.5l0.2,-0.2l0.2,0.3c0.2,-0.1 0.3,0 0.3,0.2l0.7,-0.5l0.1,0.4h0.1c0,-0.3 0.2,-0.5 0.4,-0.6l0.4,-0.1L61.8,74c0,0 0,0 0,0c0.6,0.5 0.5,-0.6 0.3,-0.8c0.1,-0.1 0.2,-0.2 0.3,-0.2c0.1,0 0.1,0 0.2,0c0.4,0.1 0.8,0 1.1,-0.3c0.1,0.2 -0.3,0.4 0,0.6c0.1,-0.3 0.2,-0.5 0.4,-0.7c0,0.1 0,0.3 0,0.4c0.1,0 0.2,0 0.3,0c0,0.7 0.2,1 1,1.2L65.3,73h0.1l0.1,0.9l0.5,-0.3l-0.2,-0.2l0.5,-0.4h0.1l0.7,0.3l-0.1,0.3c0.7,-0.3 0.7,-0.6 0.2,-1.1l0,0.1v-0.1l0.1,-0.2l0.3,-0.2l0.2,0.1c-0.1,-0.6 0.2,-0.8 0.8,-0.6l0.2,-0.4L68.4,71c0.5,0.1 0.2,0.4 0.2,0.7l0.3,-0.2l0.2,0.3c0,-0.3 0.1,-0.5 0.3,-0.7L69,71.3v0.1l0.9,-0.1L69.8,71c0.1,0 0.1,0 0.2,0c0.1,0.1 0.9,-1.4 1.9,-1.2l-0.2,0.3l0.1,0.1L72,70c0.3,-0.2 0.5,-0.4 0.7,-0.6c-0.1,-0.2 -0.3,-1 0,-1.1c0.1,0.1 0.2,0.2 0.2,0.4c0.4,-0.6 0.5,-0.1 1,0.4c0.1,0 1.7,-0.3 1.8,-0.2l0.3,-0.1c0,0.3 0.2,0.6 0.4,0.2c0.3,0 0.6,-0.1 0.8,-0.3c0.3,0 0.4,-0.4 0.5,-0.7c0.2,-0.1 0.6,0.2 0.6,0.2c0.5,1.2 0.4,2.5 0.5,3.8c0.2,0 0.7,0.6 0.4,0.8L78.7,72h-0.1c0.5,1.3 0.2,2.7 -0.7,4h-0.2c-0.5,-0.5 -0.3,0 -0.3,0.5c-0.5,0.2 -0.8,0.5 -0.9,1.1c0,0.1 0,0.3 0,0.4L76,77.8c-0.4,-0.1 -0.8,0 -1.1,0.3l0.1,0.2c-0.2,0.1 -0.5,0.1 -0.4,0.5L74,78.5V79l-0.2,0l0.1,0.5c-0.3,-0.1 -0.5,0 -0.7,0.3c-0.7,0.4 -1.7,1.3 -1.8,2.1l-0.3,-0.2L71,82c-0.2,-0.3 -0.5,-0.5 -0.8,-0.7c0,0.5 0.2,0.8 -0.4,0.5L69.4,82c0.3,0.3 0,0.6 0,0.9L68.6,82c-0.5,0.5 -1.1,0.7 -1.6,1.2c-0.2,-0.1 -0.4,0.1 -0.5,0.4l-0.2,-0.3l-0.2,0.3c-0.4,-0.2 -0.7,-0.2 -1.1,0.2l0.1,-0.4L63.8,84l-0.1,-0.3L63.2,84H63v-0.2c-0.6,-0.1 -0.9,0.3 -0.7,0.9l-0.4,-0.3c-0.3,0.3 -0.3,0.8 -0.8,0.9l-0.5,-0.5c-0.2,0 -0.5,0 -0.7,0.2l0.1,0.2v0.6l-0.4,-0.2c-0.3,0.3 -0.6,0.1 -0.8,0c0,0.1 -1.1,0.3 -1.2,0.5c-0.5,0 -1.1,0.1 -1.6,0.1L56,86h-0.5l-0.8,-0.4c-0.1,0.5 -0.4,0.5 -0.7,0.2c0,0.1 0,0.2 0,0.2s0,0 0,0.1c-0.2,-0.1 -0.2,-0.3 -0.2,-0.6c-0.4,-0.1 -0.8,0 -1,-0.4c-0.2,-0.1 -0.3,-0.2 -0.5,-0.2L52,84h-0.4c-0.1,0.8 -0.2,0.8 -0.2,0.8l0,0.2c0.2,0 -0.1,0.8 -0.1,0.2l0,-0.7L51,84.7v0.1l-0.4,-0.3l-0.2,0.3c-0.7,-0.5 -2.8,0.2 -3.3,-0.6L47,84.4v-0.1c-0.2,0 -2.3,-0.9 -2.3,-0.9c-0.3,-0.7 -1.1,-0.5 -1.8,-0.8c-0.3,-0.3 -0.5,-0.6 -0.8,-0.9c-0.5,0.2 -1,0.2 -1.4,-0.3c0,0 -0.1,0 -0.1,-0.1c-0.3,-0.4 -1.6,-0.6 -1.8,-1.3c-0.5,-0.1 -1,-0.1 -1.5,-0.1c-0.1,-1.1 -0.8,-1.2 -1.8,-1.6l-0.1,-0.3l-0.3,0.1c-0.3,-0.7 -1.6,-1.5 -2.3,-1.8l-0.1,0.1c-0.1,-0.3 -1.3,-1.3 -1.2,-1.4c-0.4,-0.5 -1,-1.3 -1.6,-1.5c-1,0 -1.5,-1.8 -1.8,-2.5c0.1,-0.1 0,-0.2 -0.2,-0.3V71h0.4c0,0 -0.3,-0.4 -0.6,-0.5c-0.2,0 -0.1,-1.5 -0.7,-1.1c-0.1,-0.3 -0.2,-0.6 -0.3,-0.8c0,-0.7 -0.7,-1.6 -1.2,-2l-0.2,-1.2c-0.5,-0.3 -0.9,-0.8 -1.1,-1.4c-0.1,-0.3 -0.2,-0.6 -0.3,-1c0,-0.3 0,-0.6 0,-0.8c-1,-0.9 -0.9,-3.3 -1.5,-4.4C23,57 22.8,56.4 22,56v-0.1c0,-0.4 0.3,-2.4 1,-2.7V53l-0.7,0.2c0.1,-0.3 0.1,-0.7 0.1,-1c-0.1,-0.5 0.2,-1.1 -0.2,-1.6c0.3,-0.7 0.7,-2 0.3,-2.8c0.2,0 0.4,0 0.4,0.1h-0.2c0,-0.4 -0.1,-0.5 -0.1,-0.6c0.5,-0.3 1.3,-3.1 1.3,-3.7C23.5,42.7 24.6,42 24.8,42z\"\n        android:fillColor=\"#980824\" />\n    <path\n        android:pathData=\"M29.3,61.2c-0.2,0.3 -0.3,0.7 -0.2,1.1l0.4,0.5L30,62C29,62 29.9,61.5 29.3,61.2z\"\n        android:fillColor=\"#980824\" />\n    <path\n        android:pathData=\"M52.6,28c-0.1,0 -0.2,0 -0.2,0c-0.4,0.1 -0.4,0.4 -0.1,0.7c0.2,0 0.5,0 0.7,-0.1v-0.2C52.7,28.4 52.6,28.2 52.6,28z\"\n        android:fillColor=\"#980824\" />\n    <path\n        android:pathData=\"M48,29.8C49,29 46.5,28.8 48,29.8L48,29.8z\"\n        android:fillColor=\"#980824\" />\n    <path\n        android:pathData=\"M72,31.2c-0.3,-0.2 -0.6,-0.3 -1,-0.3c-0.2,0.2 -0.2,0.3 0.2,0.4l0,0C71.5,31.4 71.8,31.4 72,31.2L72,31.2z\"\n        android:fillColor=\"#980824\" />\n    <path\n        android:pathData=\"M27.4,53.8c0.6,0.1 0.6,-0.2 0.4,-0.8h-0.1L27.4,53.8z\"\n        android:fillColor=\"#980824\" />\n    <path\n        android:pathData=\"M64.9,28.1c0.3,0 0.6,-0.2 0.6,-0.5c-0.1,-0.1 -0.3,-0.1 -0.4,-0.1C65,27.7 64.9,27.9 64.9,28.1z\"\n        android:fillColor=\"#980824\" />\n    <path\n        android:pathData=\"M30.6,64.1c-0.6,-0.2 -0.8,0 -0.1,0.4L30.6,64.1z\"\n        android:fillColor=\"#980824\" />\n    <path\n        android:pathData=\"M28.6,60C28.4,60.4 29.8,60.8 28.6,60L28.6,60z\"\n        android:fillColor=\"#980824\" />\n    <path\n        android:pathData=\"M28.5,60.4l-0.1,0.1l0.5,0.6c0.1,0 0.2,0 0.1,-0.1L28.5,60.4z\"\n        android:fillColor=\"#980824\" />\n    <path\n        android:pathData=\"M28.9,61l0.1,0l0,1l0.2,0z\"\n        android:fillColor=\"#980824\" />\n    <path\n        android:pathData=\"M35.4,71l0.3,0.6C35.7,71.2 35.6,71 35.4,71z\"\n        android:fillColor=\"#980824\" />\n    <path\n        android:pathData=\"M69.5,29.6c-0.2,-0.2 -0.3,-0.2 -0.5,-0.1c0,0 0,0.1 0,0.2C69.2,29.7 69.4,29.8 69.5,29.6z\"\n        android:fillColor=\"#980824\" />\n    <path\n        android:pathData=\"M27.8,52.6L27.8,52.6c-0.3,0.1 -0.3,0.3 -0.1,0.4h0.1L27.8,52.6z\"\n        android:fillColor=\"#980824\" />\n    <path\n        android:pathData=\"M39,32.2c-0.3,0.3 -0.3,0.4 0,0.5V32.2z\"\n        android:fillColor=\"#980824\" />\n    <path\n        android:pathData=\"M42.4,31l0.3,-0.2c-0.1,-0.2 -0.3,-0.2 -0.5,0L42.4,31z\"\n        android:fillColor=\"#980824\" />\n    <path\n        android:pathData=\"M47,29.3c-0.1,-0.2 -0.2,-0.2 -0.4,0C46.7,29.5 46.8,29.5 47,29.3L47,29.3z\"\n        android:fillColor=\"#980824\" />\n    <path\n        android:pathData=\"M67.9,71.6l0,0.1l0.4,0.3l0.1,0z\"\n        android:fillColor=\"#980824\" />\n    <path\n        android:pathData=\"M75.8,35.9l-0.4,-0.8c0,0.1 -0.1,0.2 -0.1,0.3c-0.1,0.1 -0.1,0.1 -0.2,0.2c0,-0.1 -0.1,-0.1 -0.1,-0.2c0,-0.3 0,-0.6 0,-0.8c0,0 0.1,-0.1 0.1,-0.1c0.4,-0.1 0.4,-0.2 0.1,-0.5c0.1,-0.2 0.2,-0.2 0.4,0c0.3,0.2 0.5,0.6 0.6,0.9c0.1,0.1 0.1,0.2 0.3,0.2s0.5,0.4 0.4,0.6s0.1,0.3 0.3,0.3s0.5,0.5 0.5,0.6c-0.1,0.1 -0.1,0.2 -0.2,0.3c-0.1,0.1 -0.1,0.3 -0.2,0.4c-0.3,-0.2 -0.4,-0.4 -0.2,-0.7c-0.3,-0.3 -0.4,0.3 -0.7,0.1c0,-0.2 -0.1,-0.4 -0.1,-0.6c0,-0.1 0.1,-0.2 0.1,-0.3l0.1,-0.3c-0.1,0 -0.2,0 -0.3,0C76.2,35.7 76.1,35.8 75.8,35.9z\"\n        android:fillColor=\"#980824\" />\n    <path\n        android:pathData=\"M77.7,38.4H78c0,-0.1 -0.1,-0.2 -0.1,-0.2c-0.2,-0.1 -0.3,-0.3 -0.2,-0.5c0,-0.1 0.2,-0.2 0.3,-0.3h0.1c0.4,-0.1 0.5,-0.1 0.4,0.4c0,0.2 0,0.2 0.2,0.3c0.3,0 0.4,0.3 0.6,0.5s-0.1,0.3 -0.3,0.2s-0.2,0 -0.3,0.2c-0.2,0.2 -0.4,0.3 -0.6,0.5l-0.3,-0.3c0.1,0 0.2,-0.1 0.2,-0.1c0.3,0 0.4,-0.3 0.5,-0.5s-0.1,-0.2 -0.1,-0.3c-0.1,0.1 -0.1,0.1 -0.2,0.2c-0.1,0.1 -0.2,0.2 -0.3,0.3C77.7,38.6 77.7,38.5 77.7,38.4z\"\n        android:fillColor=\"#980824\" />\n    <path\n        android:pathData=\"M60.5,28.5c0,0.4 0,0.8 -0.1,1.2c0,0.1 -0.1,0.2 -0.2,0.3c-0.1,-0.1 -0.2,-0.1 -0.3,-0.2s-0.3,-0.2 -0.4,-0.1c-0.2,-0.1 -0.2,-0.3 -0.1,-0.6c0.1,0.1 0.2,0.2 0.3,0.3c0.1,0 0.1,0 0.2,0c0,-0.1 0,-0.1 0,-0.2c0,0 -0.1,-0.1 -0.2,-0.2c0,0 0.1,0 0.1,-0.1c0,-0.1 0.1,-0.1 0.1,-0.2c-0.3,-0.1 0,-0.3 -0.1,-0.4c0,0 0,-0.1 0.1,-0.1s0.1,0 0.1,0L60.5,28.5z\"\n        android:fillColor=\"#980824\" />\n    <path\n        android:pathData=\"M58.8,29.2l-0.2,-0.4l0.1,-0.2h0.1l0.3,0.9c-0.2,0 -0.4,-0.1 -0.6,-0.1c-0.2,-0.1 -0.3,0 -0.5,0.1s-0.3,0 -0.3,0s-0.4,-0.3 -0.2,-0.5s0.1,-0.5 0,-0.8c0.1,0 0.2,0 0.3,0s0.1,0.1 0.1,0.2s0,0.3 0,0.3c0.2,0.2 0.4,0.3 0.6,0.5c0.1,0 0.2,0.1 0.2,0.1L58.8,29.2z\"\n        android:fillColor=\"#980824\" />\n    <path\n        android:pathData=\"M67.4,32.3l-0.2,0.3L66.7,32c0.1,0 0.2,0 0.2,0c0.5,-0.2 0.7,0.2 1,0.3c0.1,0 0.1,0.1 0.2,0.1c0.3,0.1 0.2,0.3 0.2,0.4C68.2,33 68.1,33.1 68,33c0,0 -0.1,0 -0.1,-0.1C67.7,32.8 67.6,32.5 67.4,32.3z\"\n        android:fillColor=\"#980824\" />\n    <path\n        android:pathData=\"M77.4,66.8c0.2,0.4 -0.1,0.6 -0.3,0.9c-0.1,0.2 -0.2,0.3 -0.3,0.4c-0.1,0 -0.2,0 -0.2,0c0,0 0,-0.2 0,-0.2c0.1,-0.3 0.2,-0.5 0.5,-0.7L77.4,66.8z\"\n        android:fillColor=\"#980824\" />\n    <path\n        android:pathData=\"M79.6,40.2v0.4h-0.1c-0.1,-0.2 -0.3,-0.4 -0.4,-0.6c0,0 0.1,-0.2 0.2,-0.2c0.1,0 0.2,0 0.4,0c0.1,0.3 0.5,0.4 0.4,0.8L79.6,40.2L79.6,40.2z\"\n        android:fillColor=\"#980824\" />\n    <path\n        android:pathData=\"M62.8,30l-0.4,0.5L62,30.1v0.3c-0.2,0 -0.4,-0.1 -0.4,-0.3c0,0 0,-0.1 0,-0.1c0,-0.1 0,-0.2 -0.1,-0.2c-0.1,-0.1 -0.1,-0.1 0,-0.3c0.2,0.1 0.3,0.3 0.5,0.4s0.5,0.1 0.7,-0.1L62.8,30z\"\n        android:fillColor=\"#980824\" />\n    <path\n        android:pathData=\"M74.3,68.4c-0.1,-0.3 -0.2,-0.3 -0.4,-0.1c-0.1,0.1 -0.2,0.1 -0.3,0.2l-0.1,-0.1l0.3,-0.3c0.1,-0.1 0.2,-0.2 0.3,-0.2c0.1,-0.1 0.2,-0.1 0.4,-0.1c0.1,0 0.1,0.4 0,0.5C74.4,68.2 74.3,68.3 74.3,68.4z\"\n        android:fillColor=\"#980824\" />\n    <path\n        android:pathData=\"M70.1,32.9c0.1,0 0.2,-0.1 0.2,-0.1s0.1,0.1 0.1,0.2s0.1,0.2 0.1,0.3c0,0.1 0.1,0.2 0.1,0.3c0,0.3 0.1,0.5 0.1,0.7c0,0.1 0,0.1 0,0.2h-0.1c-0.2,-0.3 -0.3,-0.6 -0.3,-0.9C70.4,33.3 70.3,33.1 70.1,32.9z\"\n        android:fillColor=\"#980824\" />\n    <path\n        android:pathData=\"M66,32c-0.1,-0.3 -0.1,-0.5 -0.2,-0.8c0,0 0.2,-0.1 0.2,-0.1s0.3,0.3 0.4,0.4S66.2,31.9 66,32z\"\n        android:fillColor=\"#980824\" />\n    <path\n        android:pathData=\"M72.2,35.9c0.2,0.2 0.3,0.1 0.5,0l0.2,0.1c0.1,0.1 0.2,0.2 0.1,0.3c-0.1,0.1 -0.3,0.2 -0.4,0.1c0,0 0,0 -0.1,-0.1C72.4,36.2 72.3,36.1 72.2,35.9z\"\n        android:fillColor=\"#980824\" />\n    <path\n        android:pathData=\"M69.1,33.6l0.4,-0.7l0.2,0.7c0,0 -0.1,0 -0.1,0L69.1,33.6z\"\n        android:fillColor=\"#980824\" />\n    <path\n        android:pathData=\"M30,47.9c0,-0.4 0.1,-0.7 0.1,-1.1c0,0 0.1,-0.1 0.1,-0.1c0,0 0.1,0.1 0.1,0.1L30,47.9L30,47.9z\"\n        android:fillColor=\"#980824\" />\n    <path\n        android:pathData=\"M67.1,31.2c0.4,-0.3 0.6,-0.3 0.8,0.2L67.1,31.2z\"\n        android:fillColor=\"#980824\" />\n    <path\n        android:pathData=\"M75.2,38.9l-0.3,-0.5l0.3,-0.3C75.3,38.5 75.3,38.8 75.2,38.9z\"\n        android:fillColor=\"#980824\" />\n    <path\n        android:pathData=\"M71.8,34.5c-0.2,0.1 -0.3,0.2 -0.4,0.2s-0.1,-0.1 -0.2,-0.1l0.4,-0.4L71.8,34.5z\"\n        android:fillColor=\"#980824\" />\n    <path\n        android:pathData=\"M76,39.9c-0.4,-0.2 -0.1,-0.5 -0.2,-0.7C76.2,39.3 76,39.6 76,39.9z\"\n        android:fillColor=\"#980824\" />\n    <path\n        android:pathData=\"M63.6,30.4l-0.3,0.4c-0.1,-0.2 -0.2,-0.3 -0.2,-0.5c0.1,-0.1 0.1,-0.1 0.2,-0.1C63.4,30.3 63.5,30.4 63.6,30.4z\"\n        android:fillColor=\"#980824\" />\n    <path\n        android:pathData=\"M69.1,32.2l-0.7,-0.5C68.7,31.6 69,31.8 69.1,32.2C69.1,32.2 69.1,32.2 69.1,32.2z\"\n        android:fillColor=\"#980824\" />\n    <path\n        android:pathData=\"M79,40.2c-0.3,0 -0.5,0 -0.5,-0.3c0,0 0.1,-0.1 0.1,-0.1s0.1,0 0.1,0.1L79,40.2z\"\n        android:fillColor=\"#980824\" />\n    <path\n        android:pathData=\"M72.4,35.8c-0.2,-0.1 -0.3,-0.1 -0.5,-0.2s0,-0.2 0,-0.3l0.5,0.3L72.4,35.8z\"\n        android:fillColor=\"#980824\" />\n    <path\n        android:pathData=\"M32.5,42.1c0,-0.1 0,-0.2 0,-0.2c0.1,-0.1 0.2,-0.2 0.3,-0.3c0,0 0.1,0 0.1,0.1c0,0.1 0,0.1 -0.1,0.2L32.5,42.1z\"\n        android:fillColor=\"#980824\" />\n    <path\n        android:pathData=\"M74,35.8l0.4,0.6l-0.1,0.1l-0.5,-0.6L74,35.8z\"\n        android:fillColor=\"#980824\" />\n    <path\n        android:pathData=\"M70.2,34.3c-0.1,-0.1 -0.2,-0.2 -0.2,-0.3c0,-0.1 0,-0.2 0.1,-0.2L70.2,34.3L70.2,34.3z\"\n        android:fillColor=\"#980824\" />\n    <path\n        android:pathData=\"M76.7,37.7l0.3,-0.4c0,0 0.1,0.1 0.1,0.2c0,0.1 0,0.2 -0.1,0.3C76.9,37.8 76.7,37.7 76.7,37.7z\"\n        android:fillColor=\"#980824\" />\n    <path\n        android:pathData=\"M44.8,30.1c0.1,-0.1 0.2,-0.1 0.3,-0.1c0.1,0 0.1,0.1 0.1,0.2c0,0 -0.1,0.2 -0.1,0.1C44.9,30.3 44.8,30.2 44.8,30.1z\"\n        android:fillColor=\"#980824\" />\n    <path\n        android:fillColor=\"#FF000000\"\n        android:pathData=\"M55,63.7c0.2,0.8 0.2,1.5 0.2,2.3c-0.2,1.3 -0.5,2.6 -1,3.8c-0.5,1 -1.5,1.1 -2.2,0c-0.9,-1.4 -1.8,-2.6 -0.9,-4.6c0.7,-1.6 0.4,-3.8 0.3,-5.6c-0.1,-1.2 -0.9,-0.8 -1.6,-0.3c-2.8,1.7 -5.7,3.3 -8.5,5c-0.4,0.3 -0.9,0.5 -1.3,0.7c-0.2,-0.1 -0.3,-0.2 -0.3,-0.2c-2.2,-4.9 -2.7,-4.6 2.1,-6.8c3.7,-1.7 6.3,-4.8 9,-7.9c-4.1,3 -7,0.8 -9.9,-1.7c2.5,-1.2 4.9,-2.3 7.2,-3.6c0.4,-0.2 0.5,-1 0.6,-1.5c0.3,-1.5 0.5,-3.1 0.9,-4.6c0.5,-1.9 1.5,-2.3 4,-1.7c2.2,0.5 2.9,1.3 2.7,3.3c-0.1,0.7 0,1.4 0,2.3c2.2,-0.9 3.7,0.4 5.2,1.5c1.1,0.8 1,1.7 -0.3,2.5c-2.7,1.6 -5.2,3.4 -5.6,7.1c1.2,-0.2 2.3,-0.3 3.3,-0.5c3.3,-0.6 6.7,0.4 9.1,2.8c1.7,1.5 1.7,2.2 0.3,3.9c-2,2.6 -4.8,4.5 -7.9,5.4C58.3,65.9 56.8,65.4 55,63.7zM54.9,61.3c1,0.3 2.1,1 2.8,0.7c2.4,-0.9 4.7,-2.1 5.8,-4.6c0.6,-1.4 0.3,-1.9 -1.3,-1.7c-1,0.1 -2.1,0.3 -3.1,0.5C55.3,57.1 55.3,57.1 54.9,61.3zM51.7,39l-1.2,-0.2c-0.3,1.3 -0.7,2.6 -0.8,3.9c0,0.3 0.6,1 0.9,1c0.4,0 1.1,-0.5 1.1,-0.9C51.8,41.5 51.8,40.3 51.7,39z\" />\n    <path\n        android:fillColor=\"#FF000000\"\n        android:pathData=\"M59.9,40.6c2.1,-3.2 7,-4.1 9.6,-1.8c0.8,0.7 1.6,1.5 0.7,2.7s-3.2,1.3 -3.8,0.4C64.7,39.6 62.6,39.1 59.9,40.6z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_launcher4.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"108dp\"\n    android:height=\"108dp\"\n    android:viewportWidth=\"108\"\n    android:viewportHeight=\"108\">\n    <path\n        android:pathData=\"M55.659,79.068c-1.258,0 -2.521,-0.094 -3.753,-0.279c-0.773,-0.115 -1.307,-0.838 -1.19,-1.611s0.841,-1.311 1.612,-1.191c1.093,0.164 2.214,0.248 3.331,0.248c12.261,0 22.235,-9.975 22.235,-22.235c0,-12.26 -9.975,-22.234 -22.235,-22.234c-10.531,0 -19.695,7.479 -21.79,17.781c-0.155,0.768 -0.902,1.262 -1.671,1.107c-0.767,-0.156 -1.263,-0.904 -1.106,-1.672c2.361,-11.618 12.693,-20.051 24.567,-20.051c13.823,0 25.069,11.246 25.069,25.068C80.729,67.822 69.482,79.068 55.659,79.068z\"\n        android:fillColor=\"#568FCC\" />\n    <path\n        android:pathData=\"M53.445,48.757m-3.5,0a3.5,3.5 0,1 1,7 0a3.5,3.5 0,1 1,-7 0\"\n        android:fillColor=\"#568FCC\" />\n    <path\n        android:pathData=\"M66.861,48.757m-3.5,0a3.5,3.5 0,1 1,7 0a3.5,3.5 0,1 1,-7 0\"\n        android:fillColor=\"#568FCC\" />\n    <path\n        android:pathData=\"M30.986,53.461c0,0 -3.656,-1.06 -3.656,1.636c0,2.693 0,15.295 0,15.295s-0.674,2.598 2.598,3.369c3.271,0.77 7.697,2.404 7.697,2.404s3.464,0.387 5.58,-0.479c2.116,-0.867 7.505,-2.215 7.505,-2.215s2.213,-0.383 2.213,-3.656c0,-3.271 0,-14.24 0,-14.24s-0.771,-3.366 -4.233,-1.922c-3.464,1.444 -5.869,2.118 -5.869,2.118s-4.33,0.48 -6.157,-0.385C34.834,54.519 30.986,53.461 30.986,53.461z M39.758,74.008c-0.783,0 -1.418,-0.635 -1.418,-1.418v-12.5c0,-0.783 0.635,-1.416 1.418,-1.416s1.418,0.633 1.418,1.416v12.5C41.176,73.373 40.541,74.008 39.758,74.008z\"\n        android:fillColor=\"#568FCC\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_launcher4_b.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"108dp\"\n    android:height=\"108dp\"\n    android:viewportWidth=\"108\"\n    android:viewportHeight=\"108\">\n    <path\n        android:pathData=\"M0,0h108v108h-108z\"\n        android:fillColor=\"#FFFFFF\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_launcher5.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"108dp\"\n    android:height=\"108dp\"\n    android:viewportWidth=\"108\"\n    android:viewportHeight=\"108\">\n    <path\n        android:pathData=\"M46.12,61.71A1.72,1.72 0,0 0,44.4 60H42.26a0.21,0.21 0,0 0,-0.21 0.21v5.58a0.21,0.21 0,0 0,0.21 0.21h0.21a0.22,0.22 0,0 0,0.22 -0.21V60.64H44.4a1.07,1.07 0,0 1,0.06 2.14h-0.7l1.63,3.07a0.24,0.24 0,0 0,0.23 0.14h0.58l-1.41,-2.62A1.71,1.71 0,0 0,46.12 61.71Z\"\n        android:fillColor=\"#EDEDED\" />\n    <path\n        android:pathData=\"M59.5,61.71h-0.22a0.21,0.21 0,0 0,-0.21 0.22v0.4a2.13,2.13 0,0 0,-1.71 -0.61,2.15 2.15,0 1,0 1.71,3.67v0.4a0.21,0.21 0,0 0,0.21 0.21h0.22a0.22,0.22 0,0 0,0.21 -0.21V61.93A0.22,0.22 0,0 0,59.5 61.71ZM59,64.15a1.5,1.5 0,1 1,-2.94 -0.59,1.47 1.47,0 0,1 1.17,-1.17A1.5,1.5 0,0 1,59 64.15Z\"\n        android:fillColor=\"#EDEDED\" />\n    <path\n        android:pathData=\"M66.24,60L66,60a0.22,0.22 0,0 0,-0.22 0.21v2.12a2.13,2.13 0,0 0,-1.71 -0.61A2.15,2.15 0,0 0,64.31 66a2.12,2.12 0,0 0,1.5 -0.61v0.4A0.22,0.22 0,0 0,66 66h0.21a0.21,0.21 0,0 0,0.21 -0.21L66.42,60.21A0.21,0.21 0,0 0,66.24 60ZM65.78,64.15A1.5,1.5 0,1 1,64 62.39,1.49 1.49,0 0,1 65.78,64.15Z\"\n        android:fillColor=\"#EDEDED\" />\n    <path\n        android:pathData=\"M50.83,61.71a2.15,2.15 0,0 0,-2.15 2.21A2.18,2.18 0,0 0,50.83 66a2.15,2.15 0,0 0,1.91 -1.18h-0.65a0.23,0.23 0,0 0,-0.19 0.09,1.56 1.56,0 0,1 -1.07,0.45 1.51,1.51 0,0 1,-1.47 -1.18h3.58a1.73,1.73 0,0 0,0 -0.32A2.14,2.14 0,0 0,50.83 61.71ZM49.36,63.54a1.5,1.5 0,0 1,2.93 0Z\"\n        android:fillColor=\"#EDEDED\" />\n    <path\n        android:pathData=\"M30.79,56.55l2.2,-2.13l0.13,-18.37l-2.33,-0.85l0,21.35z\"\n        android:fillColor=\"#EDEDED\" />\n    <path\n        android:pathData=\"M36.88,33.67l-3.72,-1.91l2.08,3.9l1.64,-1.99z\"\n        android:fillColor=\"#EDEDED\" />\n    <path\n        android:pathData=\"M39.78,38.07l-2.85,-1.89l1.56,3.38l1.29,-1.49z\"\n        android:fillColor=\"#EDEDED\" />\n    <path\n        android:pathData=\"M50.23,35.61l0.87,-1.25l-2.08,-1.56l-1.14,1.28l-9.43,0l0.52,0.51l9.01,0l0,19.53l-3.12,-0.65l3.12,3.11l2.21,-2.2l0.04,-18.77z\"\n        android:fillColor=\"#EDEDED\" />\n    <path\n        android:pathData=\"M47.07,52.42l-1.56,-1.94 -1,1.3H43.17v-6.5l-1.28,-0.66h3.5V41l0.64,-0.82L44,38.89 43.06,40H41.42l3.31,-3.84 -2.39,-0.77L41,40H37.53l-1.84,-0.78V46l1.84,-1.41h0.69l-3.74,9.06 6,-8.4 -1.61,-0.66h2.44l0.07,7.15V53.2l1.82,-0.78ZM37.53,44V40.53h5.53V44Z\"\n        android:fillColor=\"#EDEDED\" />\n    <path\n        android:pathData=\"M57.43,52s-0.83,0.5 -1.79,1l2,2 5.46,-5.84 -3.23,1.82V40.73l0.63,-0.91 -2.17,-1.34 -1.13,1.33H53.79l0.53,0.62h3.11Z\"\n        android:fillColor=\"#EDEDED\" />\n    <path\n        android:pathData=\"M67.6,48.19l-5.07,8.36l7.35,-8.36l8.12,-0.05l-2.52,-2.39l-1.43,1.77l-4.2,0l0,-6.4l-2.28,-0.8l0,7.2l-6.52,0l0.61,0.67l5.94,0z\"\n        android:fillColor=\"#EDEDED\" />\n    <path\n        android:pathData=\"M61.35,39.09l0.56,0.62l12.06,-0.02l-0.91,2.6l4.11,-2.12l-2.41,-2.2l-0.53,1.07l-4.59,0.05l0,-3.04l5.89,0l-1.99,-1.87l-1.23,1.23l-2.67,0l0,-3l-2.25,-0.69l0,3.69l-5.01,0l0.56,0.64l4.45,0l0,3.04l-6.04,0z\"\n        android:fillColor=\"#EDEDED\" />\n    <path\n        android:pathData=\"M66.95,41.77l-3.14,-1.14l2.1,2.96l1.04,-1.82z\"\n        android:fillColor=\"#EDEDED\" />\n    <path\n        android:pathData=\"M64.64,46.84l1.27,-2.08l-3.8,-1.4l2.53,3.48z\"\n        android:fillColor=\"#EDEDED\" />\n    <path\n        android:pathData=\"M74.38,55.84l1.8,-2.5l-6.64,-3.19l4.84,5.69z\"\n        android:fillColor=\"#EDEDED\" />\n    <path\n        android:pathData=\"M59.67,34.49l-4.02,-2.08l2.07,4.29l1.95,-2.21z\"\n        android:fillColor=\"#EDEDED\" />\n    <path\n        android:pathData=\"M17.9,75v-3.3c11.5,-0.7 25,-8.2 36.5,7.3c11.6,-15.6 25,-8 36.5,-7.3V75l0,0v5.2c-10.2,-1.1 -24.3,-3.5 -32.9,-0.1l-0.8,0.4h0.1c-0.4,0.2 -0.7,0.6 -0.7,1l0,0c-0.1,0.7 -0.7,1.2 -1.5,1.2h-1.4c-0.7,0 -1.4,-0.6 -1.4,-1.3l0,0c-0.1,-0.3 -0.3,-0.6 -0.6,-0.8l-0.1,-0.1c-0.3,-0.2 -0.5,-0.3 -0.8,-0.5c-8.6,-3.4 -22.8,-1 -33,0.1v-3.7\"\n        android:fillColor=\"#BDD5EA\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_launcher5_b.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"108dp\"\n    android:height=\"108dp\"\n    android:viewportWidth=\"108\"\n    android:viewportHeight=\"108\">\n    <path\n        android:pathData=\"M0,0h108v108h-108z\"\n        android:fillColor=\"#495867\" />\n\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_launcher6.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"108dp\"\n    android:height=\"108dp\"\n    android:viewportWidth=\"108\"\n    android:viewportHeight=\"108\">\n    <path\n        android:pathData=\"M50.8,56.9c-0.2,2.4 -2.4,4.2 -4.8,4c-1.2,-0.1 -2.3,-0.7 -3.1,-1.7l8.8,-5.1l0,0l0.5,-0.3c-0.1,-0.2 -0.2,-0.4 -0.3,-0.6c-1.8,-3.1 -5.7,-4.1 -8.7,-2.3c-3.1,1.8 -4.1,5.7 -2.3,8.7c1.8,3.1 5.7,4.1 8.7,2.3c1.8,-1.1 3,-3 3.2,-5.1H50.8zM44.1,52.6c1.7,-1 3.9,-0.7 5.3,0.6L42,57.5C41.6,55.6 42.4,53.6 44.1,52.6z\"\n        android:fillColor=\"#333333\" />\n    <path\n        android:pathData=\"M34.1,55c2.7,-0.6 4.4,-3.4 3.8,-6.1C37.4,46.6 35.4,45 33,45h-7v17.8h1.9V46.9H33c1.8,0 3.1,1.5 3.1,3.3c0,1.6 -1.3,3 -2.9,3.1h-2.1l5.1,9.5h2.2L34.1,55z\"\n        android:fillColor=\"#2F45A6\" />\n    <path\n        android:pathData=\"M61,50.1c-3.5,0.1 -6.3,3.1 -6.1,6.6c0.1,3.3 2.8,6 6.1,6.1h3.2v-1.9H61c-2.5,0 -4.5,-2 -4.5,-4.4c0,-2.5 2,-4.5 4.4,-4.5c2.5,0 4.5,2 4.5,4.4c0,0 0,0 0,0v6.4h1.9v-6.4C67.4,52.9 64.5,50.1 61,50.1L61,50.1z\"\n        android:fillColor=\"#333333\" />\n    <path\n        android:pathData=\"M80.1,45v11.5c0,2.5 -2,4.5 -4.5,4.5c-2.5,0 -4.5,-2 -4.5,-4.5c0,-2.5 2,-4.5 4.4,-4.5c0,0 0,0 0,0h3.2v-1.9h-3.2c-3.5,0 -6.4,2.8 -6.4,6.4c0,3.5 2.8,6.4 6.4,6.4c1.7,0 3.3,-0.7 4.5,-1.8v1.8H82V45H80.1z\"\n        android:fillColor=\"#333333\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_launcher7.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"108dp\"\n    android:height=\"108dp\"\n    android:viewportWidth=\"108\"\n    android:viewportHeight=\"108\">\n    <path\n        android:pathData=\"M46.12,61.71A1.72,1.72 0,0 0,44.4 60H42.26a0.21,0.21 0,0 0,-0.21 0.21v5.58a0.21,0.21 0,0 0,0.21 0.21h0.21a0.22,0.22 0,0 0,0.22 -0.21V60.64H44.4a1.07,1.07 0,0 1,0.06 2.14h-0.7l1.63,3.07a0.24,0.24 0,0 0,0.23 0.14h0.58l-1.41,-2.62A1.71,1.71 0,0 0,46.12 61.71Z\"\n        android:fillColor=\"#2F45A6\" />\n    <path\n        android:pathData=\"M59.5,61.71h-0.22a0.21,0.21 0,0 0,-0.21 0.22v0.4a2.13,2.13 0,0 0,-1.71 -0.61,2.15 2.15,0 1,0 1.71,3.67v0.4a0.21,0.21 0,0 0,0.21 0.21h0.22a0.22,0.22 0,0 0,0.21 -0.21V61.93A0.22,0.22 0,0 0,59.5 61.71ZM59,64.15a1.5,1.5 0,1 1,-2.94 -0.59,1.47 1.47,0 0,1 1.17,-1.17A1.5,1.5 0,0 1,59 64.15Z\"\n        android:fillColor=\"#2F45A6\" />\n    <path\n        android:pathData=\"M66.24,60L66,60a0.22,0.22 0,0 0,-0.22 0.21v2.12a2.13,2.13 0,0 0,-1.71 -0.61A2.15,2.15 0,0 0,64.31 66a2.12,2.12 0,0 0,1.5 -0.61v0.4A0.22,0.22 0,0 0,66 66h0.21a0.21,0.21 0,0 0,0.21 -0.21L66.42,60.21A0.21,0.21 0,0 0,66.24 60ZM65.78,64.15A1.5,1.5 0,1 1,64 62.39,1.49 1.49,0 0,1 65.78,64.15Z\"\n        android:fillColor=\"#2F45A6\" />\n    <path\n        android:pathData=\"M50.83,61.71a2.15,2.15 0,0 0,-2.15 2.21A2.18,2.18 0,0 0,50.83 66a2.15,2.15 0,0 0,1.91 -1.18h-0.65a0.23,0.23 0,0 0,-0.19 0.09,1.56 1.56,0 0,1 -1.07,0.45 1.51,1.51 0,0 1,-1.47 -1.18h3.58a1.73,1.73 0,0 0,0 -0.32A2.14,2.14 0,0 0,50.83 61.71ZM49.36,63.54a1.5,1.5 0,0 1,2.93 0Z\"\n        android:fillColor=\"#2F45A6\" />\n    <path\n        android:pathData=\"M30.79,56.55l2.2,-2.13l0.13,-18.37l-2.33,-0.85l0,21.35z\"\n        android:fillColor=\"#2F45A6\" />\n    <path\n        android:pathData=\"M36.88,33.67l-3.72,-1.91l2.08,3.9l1.64,-1.99z\"\n        android:fillColor=\"#2F45A6\" />\n    <path\n        android:pathData=\"M39.78,38.07l-2.85,-1.89l1.56,3.38l1.29,-1.49z\"\n        android:fillColor=\"#2F45A6\" />\n    <path\n        android:pathData=\"M50.23,35.61l0.87,-1.25l-2.08,-1.56l-1.14,1.28l-9.43,0l0.52,0.51l9.01,0l0,19.53l-3.12,-0.65l3.12,3.11l2.21,-2.2l0.04,-18.77z\"\n        android:fillColor=\"#2F45A6\" />\n    <path\n        android:pathData=\"M47.07,52.42l-1.56,-1.94 -1,1.3H43.17v-6.5l-1.28,-0.66h3.5V41l0.64,-0.82L44,38.89 43.06,40H41.42l3.31,-3.84 -2.39,-0.77L41,40H37.53l-1.84,-0.78V46l1.84,-1.41h0.69l-3.74,9.06 6,-8.4 -1.61,-0.66h2.44l0.07,7.15V53.2l1.82,-0.78ZM37.53,44V40.53h5.53V44Z\"\n        android:fillColor=\"#2F45A6\" />\n    <path\n        android:pathData=\"M57.43,52s-0.83,0.5 -1.79,1l2,2 5.46,-5.84 -3.23,1.82V40.73l0.63,-0.91 -2.17,-1.34 -1.13,1.33H53.79l0.53,0.62h3.11Z\"\n        android:fillColor=\"#2F45A6\" />\n    <path\n        android:pathData=\"M67.6,48.19l-5.07,8.36l7.35,-8.36l8.12,-0.05l-2.52,-2.39l-1.43,1.77l-4.2,0l0,-6.4l-2.28,-0.8l0,7.2l-6.52,0l0.61,0.67l5.94,0z\"\n        android:fillColor=\"#2F45A6\" />\n    <path\n        android:pathData=\"M61.35,39.09l0.56,0.62l12.06,-0.02l-0.91,2.6l4.11,-2.12l-2.41,-2.2l-0.53,1.07l-4.59,0.05l0,-3.04l5.89,0l-1.99,-1.87l-1.23,1.23l-2.67,0l0,-3l-2.25,-0.69l0,3.69l-5.01,0l0.56,0.64l4.45,0l0,3.04l-6.04,0z\"\n        android:fillColor=\"#2F45A6\" />\n    <path\n        android:pathData=\"M66.95,41.77l-3.14,-1.14l2.1,2.96l1.04,-1.82z\"\n        android:fillColor=\"#2F45A6\" />\n    <path\n        android:pathData=\"M64.64,46.84l1.27,-2.08l-3.8,-1.4l2.53,3.48z\"\n        android:fillColor=\"#2F45A6\" />\n    <path\n        android:pathData=\"M74.38,55.84l1.8,-2.5l-6.64,-3.19l4.84,5.69z\"\n        android:fillColor=\"#2F45A6\" />\n    <path\n        android:pathData=\"M59.67,34.49l-4.02,-2.08l2.07,4.29l1.95,-2.21z\"\n        android:fillColor=\"#2F45A6\" />\n    <path\n        android:pathData=\"M17.9,75v-3.3c11.5,-0.7 25,-8.2 36.5,7.3c11.6,-15.6 25,-8 36.5,-7.3V75l0,0v5.2c-10.2,-1.1 -24.3,-3.5 -32.9,-0.1l-0.8,0.4h0.1c-0.4,0.2 -0.7,0.6 -0.7,1l0,0c-0.1,0.7 -0.7,1.2 -1.5,1.2h-1.4c-0.7,0 -1.4,-0.6 -1.4,-1.3l0,0c-0.1,-0.3 -0.3,-0.6 -0.6,-0.8l-0.1,-0.1c-0.3,-0.2 -0.5,-0.3 -0.8,-0.5c-8.6,-3.4 -22.8,-1 -33,0.1v-3.7\"\n        android:fillColor=\"#FFBF00\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_launcher7_b.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"108dp\"\n    android:height=\"108dp\"\n    android:viewportWidth=\"108\"\n    android:viewportHeight=\"108\">\n    <path\n        android:pathData=\"M0,0h108v108h-108z\"\n        android:fillColor=\"#EFEFEF\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_lock_outline.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\" android:height=\"24dp\" android:width=\"24dp\" android:viewportWidth=\"24\" android:viewportHeight=\"24\"><path android:fillColor=\"#000000\" android:pathData=\"M12,17C10.89,17 10,16.1 10,15C10,13.89 10.89,13 12,13A2,2 0 0,1 14,15A2,2 0 0,1 12,17M18,20V10H6V20H18M18,8A2,2 0 0,1 20,10V20A2,2 0 0,1 18,22H6C4.89,22 4,21.1 4,20V10C4,8.89 4.89,8 6,8H7V6A5,5 0 0,1 12,1A5,5 0 0,1 17,6V8H18M12,3A3,3 0 0,0 9,6V8H15V6A3,3 0 0,0 12,3Z\" /></vector>"
  },
  {
    "path": "app/src/main/res/drawable/ic_menu.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"24.0\"\n    android:viewportHeight=\"24.0\">\n    <path\n        android:fillColor=\"#FF000000\"\n        android:pathData=\"M3,18h18v-2L3,16v2zM3,13h18v-2L3,11v2zM3,6v2h18L21,6L3,6z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_more.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\r\n    android:width=\"24dp\"\r\n    android:height=\"24dp\"\r\n    android:viewportWidth=\"24\"\r\n    android:viewportHeight=\"24\">\r\n  <path\r\n      android:pathData=\"M12,5.5m-1.5,0a1.5,1.5 0,1 1,3 0a1.5,1.5 0,1 1,-3 0\"\r\n      android:fillColor=\"#202020\"/>\r\n  <path\r\n      android:pathData=\"M12,12m-1.5,0a1.5,1.5 0,1 1,3 0a1.5,1.5 0,1 1,-3 0\"\r\n      android:fillColor=\"#202020\"/>\r\n  <path\r\n      android:pathData=\"M12,18.5m-1.5,0a1.5,1.5 0,1 1,3 0a1.5,1.5 0,1 1,-3 0\"\r\n      android:fillColor=\"#202020\"/>\r\n</vector>\r\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_more_vert.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"24.0\"\n    android:viewportHeight=\"24.0\">\n    <path\n        android:fillColor=\"#FF000000\"\n        android:pathData=\"M12,8c1.1,0 2,-0.9 2,-2s-0.9,-2 -2,-2 -2,0.9 -2,2 0.9,2 2,2zM12,10c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2zM12,16c-1.1,0 -2,0.9 -2,2s0.9,2 2,2 2,-0.9 2,-2 -0.9,-2 -2,-2z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_network_check.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"24.0\"\n    android:viewportHeight=\"24.0\">\n    <path\n        android:fillColor=\"#FFFFFFFF\"\n        android:pathData=\"M15.9,5c-0.17,0 -0.32,0.09 -0.41,0.23l-0.07,0.15 -5.18,11.65c-0.16,0.29 -0.26,0.61 -0.26,0.96 0,1.11 0.9,2.01 2.01,2.01 0.96,0 1.77,-0.68 1.96,-1.59l0.01,-0.03L16.4,5.5c0,-0.28 -0.22,-0.5 -0.5,-0.5zM1,9l2,2c2.88,-2.88 6.79,-4.08 10.53,-3.62l1.19,-2.68C9.89,3.84 4.74,5.27 1,9zM21,11l2,-2c-1.64,-1.64 -3.55,-2.82 -5.59,-3.57l-0.53,2.82c1.5,0.62 2.9,1.53 4.12,2.75zM17,15l2,-2c-0.8,-0.8 -1.7,-1.42 -2.66,-1.89l-0.55,2.92c0.42,0.27 0.83,0.59 1.21,0.97zM5,13l2,2c1.13,-1.13 2.56,-1.79 4.03,-2l1.28,-2.88c-2.63,-0.08 -5.3,0.87 -7.31,2.88z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_outline_cloud_24.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"24\"\n    android:viewportHeight=\"24\"\n    android:tint=\"?attr/colorControlNormal\">\n    <path\n        android:fillColor=\"@android:color/white\"\n        android:pathData=\"M12,6c2.62,0 4.88,1.86 5.39,4.43l0.3,1.5 1.53,0.11c1.56,0.1 2.78,1.41 2.78,2.96 0,1.65 -1.35,3 -3,3H6c-2.21,0 -4,-1.79 -4,-4 0,-2.05 1.53,-3.76 3.56,-3.97l1.07,-0.11 0.5,-0.95C8.08,7.14 9.94,6 12,6m0,-2C9.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.96C18.67,6.59 15.64,4 12,4z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_outline_delete.xml",
    "content": "<vector android:height=\"24dp\"\n    android:tint=\"#000000\"\n    android:viewportHeight=\"24\"\n    android:viewportWidth=\"24\"\n    android:width=\"24dp\"\n    xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <path\n        android:fillColor=\"@android:color/white\"\n        android:pathData=\"M14.12,10.47L12,12.59l-2.13,-2.12 -1.41,1.41L10.59,14l-2.12,2.12 1.41,1.41L12,15.41l2.12,2.12 1.41,-1.41L13.41,14l2.12,-2.12zM15.5,4l-1,-1h-5l-1,1H5v2h14V4zM6,19c0,1.1 0.9,2 2,2h8c1.1,0 2,-0.9 2,-2V7H6v12zM8,9h8v10H8V9z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_pause_24dp.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"24.0\"\n    android:viewportHeight=\"24.0\">\n    <path\n        android:fillColor=\"#FFFFFFFF\"\n        android:pathData=\"M6,19h4L10,5L6,5v14zM14,5v14h4L18,5h-4z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_pause_outline_24dp.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"24\"\n    android:viewportHeight=\"24\">\n\n    <path\n        android:fillColor=\"#595757\"\n        android:pathData=\"M 8 6 H 9.6 V 18 H 8 V 6 Z\" />\n    <path\n        android:fillColor=\"#595757\"\n        android:pathData=\"M 14.4 6 H 16 V 18 H 14.4 V 6 Z\" />\n</vector>"
  },
  {
    "path": "app/src/main/res/drawable/ic_play_24dp.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"24.0\"\n    android:viewportHeight=\"24.0\">\n    <path\n        android:fillColor=\"#FFFFFFFF\"\n        android:pathData=\"M8,5v14l11,-7z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_play_mode_list_end_stop.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n        android:width=\"24dp\"\n        android:height=\"24dp\"\n        android:viewportWidth=\"24.0\"\n        android:viewportHeight=\"24.0\">\n    <path\n            android:fillColor=\"#FF000000\"\n            android:pathData=\"M3.196,2.147l0.084,0.073l18.5,18.5a0.75,0.75 0,0,1 -0.976,1.133l-0.084,-0.073l-3.509,-3.508q-0.655,0.18 -1.358,0.219l-0.353,0.009H8.564l1.9,1.9l0.066,0.076a0.75,0.75 0,0,1 -1.043,1.056l-0.084,-0.072l-3.182,-3.182l-0.067,-0.077a0.75,0.75 0,0,1 -0.006,-0.899l0.073,-0.085l3.182,-3.182l0.077,-0.067a0.75,0.75 0,0,1 0.899,-0.006l0.084,0.073l0.068,0.077a0.75,0.75 0,0,1 0.005,0.899l-0.073,0.085L8.558,17H15.5q0.213,0 0.422,-0.017L6.4,7.46a5,5 0,0,0 -1.598,7.904a0.75,0.75 0,1,1 -1.11,1.007A6.47,6.47 0,0,1 2,12a6.5,6.5 0,0,1 3.287,-5.652L2.22,3.28a0.75,0.75 0,0,1 0.976,-1.133\" />\n    <path\n            android:fillColor=\"#FF000000\"\n            android:pathData=\"M19.75,7.378c0.22,0 0.416,0.094 0.553,0.244A6.47,6.47 0,0,1 22,12c0,2.057 -0.955,3.89 -2.446,5.081l-1.069,-1.07A5,5 0,0,0 20.5,12c0,-1.306 -0.5,-2.495 -1.32,-3.386a0.75,0.75 0,0,1 0.57,-1.236m-5.217,-4.975l0.077,0.067l3.182,3.182a0.75,0.75 0,0,1 0.067,0.984l-0.067,0.076l-3.182,3.182a0.75,0.75 0,0,1 -1.128,-0.984l0.068,-0.076L15.38,7H9.473l-1.48,-1.48a7,7 0,0,1 0.274,-0.016L8.5,5.5h7.021L13.55,3.53a0.75,0.75 0,0,1 -0.067,-0.984l0.068,-0.076a0.75,0.75 0,0,1 0.983,-0.067\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_play_mode_list_loop.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n        android:width=\"24dp\"\n        android:height=\"24dp\"\n        android:viewportWidth=\"24.0\"\n        android:viewportHeight=\"24.0\">\n    <path\n            android:fillColor=\"#FF000000\"\n            android:pathData=\"m14.61 2.47l-0.077-0.067a0.75 0.75 0 0 0-0.983 0.067l-0.068 0.078a0.755 0.755 0 0 0 0.068 0.987l1.971 1.977H8.5l-0.233 0.004C4.785 5.639 2 8.51 2 12.036c0 1.69 0.64 3.23 1.692 4.39l0.072 0.069a0.751 0.751 0 0 0 1.08-1.033l-0.2-0.231A5 5 0 0 1 3.5 12.035c0-2.771 2.239-5.018 5-5.018h6.881l-1.832 1.84l-0.067 0.078a0.755 0.755 0 0 0 0.068 0.987a0.75 0.75 0 0 0 1.06 0l3.182-3.193l0.067-0.078a0.755 0.755 0 0 0-0.067-0.987\" />\n    <path\n            android:fillColor=\"#FF000000\"\n            android:pathData=\"M20.23,7.571a0.751,0.751 0 0 0-1.05 1.066a5 5 0 0 1 1.32 3.398c0 2.772-2.239 5.019-5 5.019H8.558l1.905-1.911l0.074-0.086a0.755 0.755 0 0 0-0.007-0.902l-0.067-0.077l-0.084-0.073a0.75 0.75 0 0 0-0.9 0.006l-0.076 0.067l-3.182 3.194l-0.073 0.085a0.755 0.755 0 0 0 0.006 0.902l0.067 0.077l3.182 3.194l0.084 0.072c0.293 0.22 0.71 0.195 0.976-0.073a0.755 0.755 0 0 0 0.068-0.987l-0.068-0.077l-1.899-1.906H15.5l0.233-0.004C19.215 18.432 22 15.56 22 12.035a6.5 6.5 0 0 0-1.697-4.395z\"/>\n</vector>"
  },
  {
    "path": "app/src/main/res/drawable/ic_play_mode_random.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n        android:width=\"24dp\"\n        android:height=\"24dp\"\n        android:viewportWidth=\"24.0\"\n        android:viewportHeight=\"24.0\">\n    <path\n            android:fillColor=\"#FF000000\"\n            android:pathData=\"M19.207,4.293a1,1 0,1,0 -1.414,1.414l0.801,0.802c-3.809,0.161 -6.169,2.59 -8.226,4.706l-0.085,0.088C8.057,13.593 6.147,15.5 3,15.5a1,1 0,1,0 0,2c4.05,0 6.503,-2.525 8.632,-4.715l0.085,-0.088c2.124,-2.184 3.96,-4.02 6.857,-4.185l-0.781,0.78a1,1 0,0,0 1.414,1.415l2.5,-2.5a1,1 0,0,0 0,-1.414\" />\n    <path\n            android:fillColor=\"#FF000000\"\n            android:pathData=\"M3,6.5c3.229,0 5.443,1.605 7.287,3.366q-0.295,0.3 -0.574,0.588l-0.147,0.152q-0.349,0.36 -0.68,0.693C7.186,9.68 5.476,8.5 3,8.5a1,1 0,1,1 0,-2\" />\n    <path\n            android:fillColor=\"#FF000000\"\n            android:pathData=\"M18.791,17.584c-3.01,-0.128 -5.115,-1.671 -6.881,-3.357q0.295,-0.3 0.574,-0.589l0.147,-0.151q0.349,-0.36 0.68,-0.693c1.601,1.524 3.21,2.66 5.46,2.787l-0.781,-0.78a1,1 0,0,1 1.414,-1.415l2.5,2.5a1,1 0,0,1 0,1.414l-2.5,2.5a1,1 0,0,1 -1.414,-1.414\" />\n</vector>"
  },
  {
    "path": "app/src/main/res/drawable/ic_play_mode_single_loop.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n        android:width=\"24dp\"\n        android:height=\"24dp\"\n        android:viewportWidth=\"24.0\"\n        android:viewportHeight=\"24.0\">\n    <path\n            android:fillColor=\"#FF000000\"\n            android:pathData=\"m14.61 2.47l-0.077-0.067a0.75 0.75 0 0 0-0.983 0.067l-0.068 0.078a0.755 0.755 0 0 0 0.068 0.987l1.971 1.977H8.5l-0.233 0.004C4.785 5.639 2 8.51 2 12.036c0 1.69 0.64 3.23 1.692 4.39l0.072 0.069a0.751 0.751 0 0 0 1.08-1.033l-0.2-0.231A5 5 0 0 1 3.5 12.035c0-2.771 2.239-5.018 5-5.018h6.881l-1.832 1.84l-0.067 0.078a0.755 0.755 0 0 0 0.068 0.987a0.75 0.75 0 0 0 1.06 0l3.182-3.193l0.067-0.078a0.755 0.755 0 0 0-0.067-0.987\" />\n    <path\n            android:fillColor=\"#FF000000\"\n            android:pathData=\"M20.23,7.571a0.751,0.751 0,0,0 -1.05,1.066a5,5 0,0,1 1.31,3.09c0.539,0.28 1.032,0.632 1.47,1.044q0.04,-0.363 0.04,-0.736a6.5,6.5 0,0,0 -1.697,-4.395\" />\n    <path\n            android:fillColor=\"#FF000000\"\n            android:pathData=\"M8.558,17.054h2.457a6.6,6.6 0,0,0 0.07,1.505h-2.52l1.9,1.906l0.066,0.077a0.755,0.755 0,0,1 -0.067,0.987a0.75,0.75 0,0,1 -0.976,0.073l-0.084,-0.073l-3.182,-3.193l-0.067,-0.077a0.755,0.755 0,0,1 -0.006,-0.902l0.073,-0.085l3.182,-3.194l0.077,-0.067a0.75,0.75 0,0,1 0.899,-0.006l0.084,0.073l0.068,0.077a0.755,0.755 0,0,1 0.005,0.902l-0.073,0.085\" />\n    <path\n            android:fillColor=\"#FF000000\"\n            android:pathData=\"M23,17.5a5.5,5.5 0,1,1 -11,0a5.5,5.5 0,0,1 11,0m-4.885,-3.487a0.5,0.5 0,0,0 -0.562,0.263a4.8,4.8 0,0,1 -0.613,0.828c-0.334,0.373 -0.744,0.739 -1.164,0.949a0.5,0.5 0,1,0 0.448,0.894c0.489,-0.244 0.926,-0.616 1.276,-0.977v4.53a0.5,0.5 0,0,0 1,0v-6a0.5,0.5 0,0,0 -0.385,-0.487\" />\n</vector>"
  },
  {
    "path": "app/src/main/res/drawable/ic_play_outline_24dp.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"24\"\n    android:viewportHeight=\"24\">\n\n    <path\n        android:fillColor=\"#595757\"\n        android:pathData=\"M6.262,20C6.131,20,6,19.966,5.882,19.897c-0.237-0.137-0.384-0.392-0.384-0.667V5.77 c0-0.275,0.146-0.53,0.384-0.667C6.119,4.965,6.41,4.966,6.648,5.104l11.471,6.73c0.234,0.139,0.379,0.392,0.379,0.665 s-0.145,0.526-0.379,0.665l-11.471,6.73C6.528,19.965,6.396,20,6.262,20z M7.027,7.108v10.783l9.188-5.392L7.027,7.108z\" />\n</vector>"
  },
  {
    "path": "app/src/main/res/drawable/ic_praise.xml",
    "content": "<vector android:height=\"24dp\" android:viewportHeight=\"1024\"\n    android:viewportWidth=\"1024\" android:width=\"24dp\" xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <path android:fillColor=\"#FF000000\" android:pathData=\"M939.4,423.4c-23,-37.3 -62.9,-60.8 -107,-63.2 -2.8,-0.4 -5.5,-0.6 -8.3,-0.6l-152.2,-0.1c6.6,-29 10,-58.7 10,-88.7 0,-28.2 -3.2,-57.1 -9.6,-86 -0.4,-2 -1.1,-4 -1.9,-5.9 -15.8,-57.3 -67.4,-96.8 -127.3,-96.8 -72.8,0 -132.1,59.3 -132.1,132.1 0,3.3 0.1,6.7 0.4,10 -2.2,57.2 -32.1,109.9 -80.3,141.4 -14.4,9.4 -18.5,28.8 -9.1,43.2 9.4,14.4 28.8,18.5 43.2,9.1 65.8,-42.9 106.4,-115.3 108.7,-193.7 0,-1.3 -0,-2.7 -0.1,-4 -0.2,-2 -0.3,-4 -0.3,-6 0,-38.4 31.2,-69.6 69.6,-69.6 32.6,0 60.5,22.2 67.8,54 0.4,1.5 0.8,3 1.4,4.5 4.7,22.8 7.1,45.6 7.1,67.7 0,37.5 -6.2,74.4 -18.5,109.6 -3.3,9.5 -1.8,20.1 4,28.3 5.9,8.2 15.3,13.1 25.4,13.2l193.2,0.1c1.4,0.3 2.9,0.4 4.3,0.5 24.1,1 45.9,13.6 58.6,34.1 7.8,12.3 11.4,26.8 10.4,41.9 -0.1,0.9 -0.1,1.9 -0.1,2.9 0,0.9 0,1.7 0.1,2.5 0,0.3 -0,0.7 -0.1,1 -0.1,0.4 -0.2,0.8 -0.3,1.2l-75,330.4c-0.9,1.3 -1.7,2.6 -2.4,4 -5.9,11.8 -14.7,21.5 -25.3,28.2 -10.7,6.8 -23.1,10.6 -35.8,11.1 -0.9,-0.1 -513.8,-0.2 -513.8,-0.2 -0.5,-0 -0.9,-0 -1.4,-0 0,0 -111,0.2 -112.7,0.5 -1.9,0 -3.4,-1.5 -3.4,-3.4l0.3,-416.3c0,-1.9 1.5,-3.4 3,-3.5l1.2,0.1c1.2,0.1 2.7,0.3 3.5,0.2l83.8,-0.2 0,339.4c0,17.3 14,31.2 31.2,31.2s31.2,-14 31.2,-31.2L281,435.4c0,-1.8 0,-8.6 0,-10.4 0,-17.3 -14,-30.9 -31.2,-30.9 -1.5,0 -117.5,0.3 -119.4,0.3 -36.3,0 -65.9,29.6 -65.9,65.9l-0.3,416.3c0,36.3 29.6,65.9 65.9,65.9 2.5,0 111.4,-0.5 111.4,-0.5 0.5,0 0.9,0 1.4,0 0,0 511.5,0.3 512.5,0.3 25.5,0 50.3,-7.2 71.6,-20.7 19.6,-12.3 35.8,-29.9 46.8,-51 3.7,-5.6 6.4,-11.9 8.3,-18.6 0.1,-0.4 0.2,-0.8 0.3,-1.2l74.9,-330.3c1.6,-6.2 2.3,-12.6 2,-19C960.9,473.4 953.9,446.3 939.4,423.4z\"/>\n    <path android:fillColor=\"#FF000000\" android:pathData=\"M450,518.7c-17.3,0 -31.2,14 -31.2,31.2l0,30.5c0,17.3 14,31.2 31.2,31.2 17.3,0 31.2,-14 31.2,-31.2l0,-30.5C481.3,532.6 467.3,518.7 450,518.7z\"/>\n    <path android:fillColor=\"#FF000000\" android:pathData=\"M693.8,518.7c-17.3,0 -31.2,14 -31.2,31.2l0,30.5c0,17.3 14,31.2 31.2,31.2 17.3,0 31.2,-14 31.2,-31.2l0,-30.5C725,532.6 711.1,518.7 693.8,518.7z\"/>\n    <path android:fillColor=\"#FF000000\" android:pathData=\"M648.9,660.1c-14.3,-9.4 -33.6,-5.4 -43.2,8.8 -0.1,0.2 -13.6,19.8 -34.2,19.8 -20,0 -32.4,-18.1 -33.3,-19.4 -9.2,-14.4 -28.3,-18.8 -42.8,-9.8 -14.7,9.1 -19.2,28.4 -10.1,43 11.2,18 42,48.6 86.2,48.6 44,0 75.1,-30.3 86.6,-48.2C667.4,688.5 663.2,669.5 648.9,660.1z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_read_aloud.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"24\"\n    android:viewportHeight=\"24\">\n\n    <path\n        android:fillColor=\"#595757\"\n        android:pathData=\"M12,4c-4.412,0-8,3.559-8,7.933v1.269v2.117v2.874C4,19.189,4.816,20,5.818,20h2.424 c1.002,0,1.818-0.811,1.818-1.808v-4.991c0-0.997-0.816-1.809-1.818-1.809H5.818c-0.116,0-0.229,0.014-0.339,0.034 C5.742,8.091,8.564,5.454,12,5.454s6.258,2.637,6.52,5.973c-0.109-0.021-0.222-0.034-0.338-0.034h-2.424 c-1.002,0-1.818,0.812-1.818,1.809v4.991c0,0.997,0.816,1.808,1.818,1.808h2.424C19.186,20,20,19.189,20,18.192v-2.874v-2.117 v-1.269C20,7.559,16.412,4,12,4z M5.818,12.847h2.424c0.201,0,0.363,0.159,0.363,0.354v4.991c0,0.194-0.162,0.354-0.363,0.354H5.818 c-0.201,0-0.364-0.159-0.364-0.354v-2.874v-2.117C5.454,13.006,5.617,12.847,5.818,12.847z M18.546,18.192 c0,0.194-0.163,0.354-0.364,0.354h-2.424c-0.201,0-0.365-0.159-0.365-0.354v-4.991c0-0.195,0.164-0.354,0.365-0.354h2.424 c0.201,0,0.364,0.159,0.364,0.354v2.117V18.192z\" />\n</vector>"
  },
  {
    "path": "app/src/main/res/drawable/ic_reduce.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"24.0\"\n    android:viewportHeight=\"24.0\">\n    <path\n        android:fillColor=\"#FF000000\"\n        android:pathData=\"M19,13H5v-2h14v2z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_refresh_black_24dp.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"24\"\n    android:viewportHeight=\"24\">\n\n    <path\n        android:fillColor=\"#595757\"\n        android:pathData=\"M20,10.302 L15.879,10.302 L15.879,8.848 L18.546,8.848 L18.546,6.182 L20,6.182 Z\" />\n    <path\n        android:fillColor=\"#595757\"\n        android:pathData=\"M12,20 C7.588,20,4,16.411,4,12 S7.588,4,12,4 C15.183,4,18.063,5.884,19.336,8.801 L18.003,9.384 C16.961,6.997,14.604,5.454,12,5.454 C8.392,5.454,5.454,8.391,5.454,12 S8.392,18.546,12,18.546 C15.1,18.546,17.798,16.344,18.414,13.31 L19.84,13.6 C19.086,17.309,15.789,20,12,20 Z\" />\n</vector>"
  },
  {
    "path": "app/src/main/res/drawable/ic_refresh_white_24dp.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"24.0\"\n    android:viewportHeight=\"24.0\">\n    <path\n        android:fillColor=\"#ffffff\"\n        android:pathData=\"M17.65,6.35C16.2,4.9 14.21,4 12,4c-4.42,0 -7.99,3.58 -7.99,8s3.57,8 7.99,8c3.73,0 6.84,-2.55 7.73,-6h-2.08c-0.82,2.33 -3.04,4 -5.65,4 -3.31,0 -6,-2.69 -6,-6s2.69,-6 6,-6c1.66,0 3.14,0.69 4.22,1.78L13,11h7V4l-2.35,2.35z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_restore.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"24.0\"\n    android:viewportHeight=\"24.0\">\n    <path\n        android:fillColor=\"#FF000000\"\n        android:pathData=\"M13,3c-4.97,0 -9,4.03 -9,9L1,12l3.89,3.89 0.07,0.14L9,12L6,12c0,-3.87 3.13,-7 7,-7s7,3.13 7,7 -3.13,7 -7,7c-1.93,0 -3.68,-0.79 -4.94,-2.06l-1.42,1.42C8.27,19.99 10.51,21 13,21c4.97,0 9,-4.03 9,-9s-4.03,-9 -9,-9zM12,8v5l4.28,2.54 0.72,-1.21 -3.5,-2.08L13.5,8L12,8z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_save.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"24\"\n    android:viewportHeight=\"24\">\n\n    <path\n        android:fillColor=\"#595757\"\n        android:pathData=\"M19.2,3.2h-3.417H8.212H4.8c-0.882,0-1.6,0.718-1.6,1.6v14.4c0,0.882,0.718,1.6,1.6,1.6h14.4 c0.882,0,1.6-0.718,1.6-1.6V4.8C20.8,3.918,20.082,3.2,19.2,3.2z M15.798,4.8L15.8,8.993C15.799,8.995,15.794,9,15.783,9H8.197 l0.015-4.2H15.798z M19.199,19.2H4.8V4.8h1.798V9c0,0.882,0.724,1.6,1.614,1.6h7.571c0.891,0,1.614-0.718,1.614-1.6V4.8H19.2 L19.199,19.2z M19.2,20v-0.8h0.001L19.2,20z\" />\n    <path\n        android:fillColor=\"#595757\"\n        android:pathData=\"M 13.291 5.354 H 14.541 V 8.354 H 13.291 V 5.354 Z\" />\n</vector>"
  },
  {
    "path": "app/src/main/res/drawable/ic_scan.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"1024\"\n    android:viewportHeight=\"1024\">\n\n    <path\n        android:fillColor=\"#000000\"\n        android:pathData=\"M725.333333 128h149.184C886.421333 128 896 137.578667 896 149.482667V298.666667a42.666667 42.666667 0 1 0 85.333333 0V149.482667A106.752 106.752 0 0 0 874.517333 42.666667H725.333333a42.666667 42.666667 0 1 0 0 85.333333z m170.666667 597.333333v149.184c0 11.904-9.578667 21.482667-21.482667 21.482667H725.333333a42.666667 42.666667 0 1 0 0 85.333333h149.184A106.752 106.752 0 0 0 981.333333 874.517333V725.333333a42.666667 42.666667 0 1 0-85.333333 0z m-597.333333 170.666667H149.482667A21.418667 21.418667 0 0 1 128 874.517333V725.333333a42.666667 42.666667 0 1 0-85.333333 0v149.184A106.752 106.752 0 0 0 149.482667 981.333333H298.666667a42.666667 42.666667 0 1 0 0-85.333333zM128 298.666667a42.666667 42.666667 0 1 1-85.333333 0V149.482667A106.752 106.752 0 0 1 149.482667 42.666667H298.666667a42.666667 42.666667 0 1 1 0 85.333333H149.482667C137.578667 128 128 137.578667 128 149.482667V298.666667zM85.333333 554.666667a42.666667 42.666667 0 1 1 0-85.333334h853.333334a42.666667 42.666667 0 1 1 0 85.333334H85.333333z\"\n        tools:ignore=\"VectorPath\" />\n\n</vector>"
  },
  {
    "path": "app/src/main/res/drawable/ic_scoring.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"24\"\n    android:viewportHeight=\"24\">\n\n    <path\n        android:fillColor=\"#595757\"\n        android:pathData=\"M16.948,19.437L12,16.838l-4.946,2.599L8,13.932l-4-3.9l5.528-0.808L12,4.219l2.472,5.005L20,10.031 l-4,3.9L16.948,19.437z M12,15.349l3.198,1.679l-0.613-3.557l2.584-2.52l-3.572-0.52L12,7.192l-1.599,3.239l-3.572,0.52l2.588,2.52 l-0.613,3.557L12,15.349z\" />\n</vector>"
  },
  {
    "path": "app/src/main/res/drawable/ic_screen.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"1024\"\n    android:viewportHeight=\"1024\">\n\n    <path\n        android:fillColor=\"#FFFFFF\"\n        android:pathData=\"M512 720.457143v-109.714286H201.216v109.714286H512z m0 204.8v-109.714286H201.216v109.714286H512zM324.754286 445.147429v40.886857h109.714285V429.348571a54.857143 54.857143 0 0 0-8.411428-29.110857L273.846857 157.257143h497.444572l-152.137143 242.980571a54.857143 54.857143 0 0 0-8.411429 29.110857v542.72h109.714286V445.074286l196.461714-313.636572A54.857143 54.857143 0 0 0 870.4 47.542857H174.811429a54.857143 54.857143 0 0 0-46.518858 83.968l196.388572 313.636572z\" />\n</vector>"
  },
  {
    "path": "app/src/main/res/drawable/ic_search.xml",
    "content": "<vector android:autoMirrored=\"true\"\r\n    android:height=\"24dp\"\r\n    android:viewportHeight=\"48\"\r\n    android:viewportWidth=\"48\"\r\n    android:width=\"24dp\"\r\n    xmlns:android=\"http://schemas.android.com/apk/res/android\">\r\n    <path\r\n        android:fillColor=\"#39393A\"\r\n        android:pathData=\"M20,32.7c-6.9,0 -12.6,-5.6 -12.6,-12.6c0,-6.9 5.6,-12.6 12.6,-12.6c6.9,0 12.6,5.6 12.6,12.6C32.6,27 27,32.7 20,32.7M20,9C13.9,9 8.9,14 8.9,20.1c0,6.1 5,11.1 11.1,11.1c6.1,0 11.1,-5 11.1,-11.1C31.1,14 26.2,9 20,9\"\r\n        android:strokeColor=\"#39393A\"\r\n        android:strokeWidth=\"2\" />\r\n    <path\r\n        android:fillColor=\"#39393A\"\r\n        android:pathData=\"M39.8,40.4c-0.2,0 -0.4,-0.1 -0.5,-0.2L28.1,29.1c-0.3,-0.3 -0.3,-0.7 0,-1c0.3,-0.3 0.7,-0.3 1,0l11.1,11.1c0.3,0.3 0.3,0.7 0,1C40.2,40.4 40,40.4 39.8,40.4\"\r\n        android:strokeColor=\"#39393A\"\r\n        android:strokeWidth=\"2\" />\r\n</vector>\r\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_search_hint.xml",
    "content": "<vector android:height=\"8dp\"\n    android:viewportHeight=\"1024\"\n    android:viewportWidth=\"1024\"\n    android:width=\"8dp\"\n    xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <path android:fillColor=\"#c0c0c0\" android:pathData=\"M462.43,894.32c-242.22,0 -439.3,-197.03 -439.3,-439.3S220.16,15.78 462.43,15.78s439.3,197.03 439.3,439.3 -197.07,439.25 -439.3,439.25zM462.43,62.32c-216.58,0 -392.75,176.17 -392.75,392.75s176.17,392.75 392.75,392.75c216.53,0 392.75,-176.17 392.75,-392.75s-176.22,-392.75 -392.75,-392.75z\"/>\n    <path android:fillColor=\"#c0c0c0\" android:pathData=\"M977.64,991.98a23.37,23.37 0,0 1,-16.48 -6.8l-211.22,-211.22a23.27,23.27 0,0 1,32.91 -32.91l211.22,211.22a23.27,23.27 0,0 1,-16.43 39.7z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_settings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"24\"\n    android:viewportHeight=\"24\">\n\n    <path\n        android:fillColor=\"#595757\"\n        android:pathData=\"M12,15.43 C10.109,15.43,8.572,13.891,8.572,12 S10.109,8.57,12,8.57 S15.428,10.109,15.428,12 S13.891,15.43,12,15.43 Z M12,10.096 C10.949,10.096,10.096,10.95,10.096,12 S10.95,13.904,12,13.904 S13.904,13.05,13.904,12 S13.051,10.096,12,10.096 Z\" />\n    <path\n        android:fillColor=\"#595757\"\n        android:pathData=\"M13.735,20 L10.261,20 L10.12,18.065 C10.102,17.807,9.944,17.585,9.721,17.492 C9.472,17.387,9.214,17.429,9.024,17.592 L7.568,18.848 L5.113,16.391 L6.369,14.937 C6.531,14.749,6.573,14.49,6.477,14.258 C6.376,14.013,6.166,13.857,5.913,13.836 L4,13.697 L4,10.226 L5.916,10.085 C6.164,10.066,6.378,9.913,6.473,9.683 C6.574,9.438,6.536,9.185,6.371,8.992 L5.113,7.539 L7.573,5.094 L9.026,6.376 C9.222,6.544,9.495,6.605,9.703,6.522 L9.844,6.461 C10.026,6.273,10.107,6.141,10.119,5.974 L10.262,4 L13.734,4 L13.877,5.894 C13.895,6.139,14.041,6.335,14.277,6.43 C14.527,6.537,14.786,6.494,14.975,6.332 L16.427,5.073 L18.886,7.531 L17.629,8.984 C17.467,9.172,17.426,9.431,17.521,9.664 C17.622,9.909,17.833,10.066,18.085,10.085 L20,10.227 L20,13.698 L18.085,13.837 C17.837,13.858,17.623,14.011,17.528,14.242 C17.426,14.487,17.465,14.747,17.63,14.94 L18.886,16.391 L16.429,18.85 L14.975,17.594 C14.799,17.444,14.544,17.398,14.322,17.479 L14.295,17.493 C14.051,17.594,13.894,17.816,13.876,18.068 L13.735,20 Z M11.561,18.604 L12.437,18.604 L12.482,17.963 C12.538,17.216,13.011,16.538,13.691,16.232 L13.737,16.211 C14.526,15.883,15.308,16.04,15.886,16.54 L16.358,16.945 L16.98,16.321 L16.574,15.852 C16.064,15.26,15.933,14.446,16.231,13.725 C16.535,12.993,17.206,12.502,17.982,12.446 L18.604,12.399 L18.604,11.524 L17.984,11.477 C17.207,11.421,16.536,10.938,16.239,10.215 C15.935,9.485,16.065,8.664,16.574,8.07 L16.98,7.601 L16.358,6.979 L15.886,7.386 C15.308,7.885,14.472,8.022,13.761,7.729 C13.029,7.428,12.54,6.763,12.482,5.996 L12.438,5.397 L11.558,5.397 L11.514,6 C11.457,6.773,10.973,7.438,10.252,7.733 L10.091,7.789 C9.416,8.008,8.652,7.856,8.112,7.392 L7.639,6.982 L7.02,7.602 L7.426,8.075 C7.936,8.665,8.067,9.479,7.77,10.202 C7.467,10.931,6.796,11.422,6.02,11.477 L5.397,11.524 L5.397,12.399 L6.019,12.446 C6.796,12.503,7.465,12.987,7.762,13.71 C8.066,14.44,7.935,15.261,7.426,15.852 L7.02,16.32 L7.64,16.941 L8.112,16.535 C8.692,16.035,9.528,15.898,10.239,16.194 C10.958,16.488,11.458,17.181,11.515,17.961 L11.561,18.604 Z\"\n        tools:ignore=\"VectorPath\" />\n</vector>"
  },
  {
    "path": "app/src/main/res/drawable/ic_share.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"20dp\"\n    android:height=\"20dp\"\n    android:viewportWidth=\"24\"\n    android:viewportHeight=\"24\">\n\n  <path\n      android:pathData=\"M18,16.08c-0.76,0 -1.44,0.3 -1.96,0.77L8.91,12.7c0.05,-0.23 0.09,-0.46 0.09,-0.7s-0.04,-0.47 -0.09,-0.7l7.05,-4.11c0.54,0.5 1.25,0.81 2.04,0.81 1.66,0 3,-1.34 3,-3s-1.34,-3 -3,-3 -3,1.34 -3,3c0,0.24 0.04,0.47 0.09,0.7L8.04,9.81C7.5,9.31 6.79,9 6,9c-1.66,0 -3,1.34 -3,3s1.34,3 3,3c0.79,0 1.5,-0.31 2.04,-0.81l7.12,4.16c-0.05,0.21 -0.08,0.43 -0.08,0.65 0,1.61 1.31,2.92 2.92,2.92 1.61,0 2.92,-1.31 2.92,-2.92s-1.31,-2.92 -2.92,-2.92z\"\n      android:fillColor=\"#000000\" />\n\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_skip_next.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"24.0\"\n    android:viewportHeight=\"24.0\">\n    <path\n        android:fillColor=\"#FF000000\"\n        android:pathData=\"M6,18l8.5,-6L6,6v12zM16,6v12h2V6h-2z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_skip_previous.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"24.0\"\n    android:viewportHeight=\"24.0\">\n    <path\n        android:fillColor=\"#FF000000\"\n        android:pathData=\"M6,6h2v12L6,18zM9.5,12l8.5,6L18,6z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_sort.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"24\"\n    android:viewportHeight=\"24\"\n    android:tint=\"?attr/colorControlNormal\">\n  <path\n      android:fillColor=\"@android:color/white\"\n      android:pathData=\"M14.94,4.66h-4.72l2.36,-2.36zM10.25,19.37h4.66l-2.33,2.33zM6.1,6.27L1.6,17.73h1.84l0.92,-2.45h5.11l0.92,2.45h1.84L7.74,6.27L6.1,6.27zM4.97,13.64l1.94,-5.18 1.94,5.18L4.97,13.64zM15.73,16.14h6.12v1.59h-8.53v-1.29l5.92,-8.56h-5.88v-1.6h8.3v1.26l-5.93,8.6z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_star.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"24.0\"\n    android:viewportHeight=\"24.0\">\n    <path\n        android:fillColor=\"#FF000000\"\n        android:pathData=\"M12,17.27L18.18,21l-1.64,-7.03L22,9.24l-7.19,-0.61L12,2 9.19,8.63 2,9.24l5.46,4.73L5.82,21z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_star_border.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"24.0\"\n    android:viewportHeight=\"24.0\">\n    <path\n        android:fillColor=\"#FF000000\"\n        android:pathData=\"M22,9.24l-7.19,-0.62L12,2 9.19,8.63 2,9.24l5.46,4.73L5.82,21 12,17.27 18.18,21l-1.63,-7.03L22,9.24zM12,15.4l-3.76,2.27 1,-4.28 -3.32,-2.88 4.38,-0.38L12,6.1l1.71,4.04 4.38,0.38 -3.32,2.88 1,4.28L12,15.4z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_stop_black_24dp.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"24.0\"\n    android:viewportHeight=\"24.0\">\n    <path\n        android:fillColor=\"#FFFFFFFF\"\n        android:pathData=\"M6,6h12v12H6z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_storage_black_24dp.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n        android:width=\"24dp\"\n        android:height=\"24dp\"\n        android:viewportWidth=\"24.0\"\n        android:viewportHeight=\"24.0\">\n    <path\n            android:fillColor=\"#FF000000\"\n            android:pathData=\"M2,20h20v-4L2,16v4zM4,17h2v2L4,19v-2zM2,4v4h20L22,4L2,4zM6,7L4,7L4,5h2v2zM2,14h20v-4L2,10v4zM4,11h2v2L4,13v-2z\"/>\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_swap_horiz.xml",
    "content": "<vector android:height=\"24dp\"\n    android:tint=\"#000000\"\n    android:viewportHeight=\"24\"\n    android:viewportWidth=\"24\"\n    android:width=\"24dp\"\n    xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <path\n        android:fillColor=\"@android:color/white\"\n        android:pathData=\"M6.99,11L3,15l3.99,4v-3H14v-2H6.99v-3zM21,9l-3.99,-4v3H10v2h7.01v3L21,9z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_time_add_24dp.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:width=\"24.0dp\"\n    android:height=\"24.0dp\"\n    android:viewportWidth=\"1000.0\"\n    android:viewportHeight=\"1000.0\">\n\n    <path\n        android:fillColor=\"#FFFFFFFF\"\n        android:pathData=\"M698.235505586 166.666668C698.235505586 166.666668 869.372215288 309.497277476 869.372215288 309.497277476C869.372215288 309.497277476 821.823923241 366.518082099 821.823923241 366.518082099C821.823923241 366.518082099 650.575796871 223.76176429 650.575796871 223.76176429C650.575796871 223.76176429 698.235505586 166.666668 698.235505586 166.666668M301.727348247 166.666668C301.727348247 166.666668 349.349936128 223.724618456 349.349936128 223.724618456C349.349936128 223.724618456 178.176084759 366.518082099 178.176084759 366.518082099C178.176084759 366.518082099 130.627792712 309.460131642 130.627792712 309.460131642C130.627792712 309.460131642 301.727348247 166.666668 301.727348247 166.666668M499.981420667 248.018822817C315.360335856 248.018822817 165.657509659 397.721628182 165.657509659 582.342754659C165.657509659 766.963839469 315.360335856 916.666674 499.981420667 916.666674C684.602547143 916.666674 834.305381674 766.963839469 834.305381674 582.342754659C834.305381674 397.721628182 684.602547143 248.018822817 499.981420667 248.018822817C499.981420667 248.018822817 499.981420667 248.018822817 499.981420667 248.018822817M499.981420667 842.372465072C356.593619519 842.372465072 239.95171442 725.730547473 239.95171442 582.342754659C239.95171442 438.954920178 356.593619519 322.313027579 499.981420667 322.313027579C643.369255147 322.313027579 760.011172747 438.954920178 760.011172747 582.342754659C760.011172747 725.730547473 643.369255147 842.372465072 499.981420667 842.372465072C499.981420667 842.372465072 499.981420667 842.372465072 499.981420667 842.372465072M537.128545964 433.754336803C537.128545964 433.754336803 462.834337036 433.754336803 462.834337036 433.754336803C462.834337036 433.754336803 462.834337036 545.195629362 462.834337036 545.195629362C462.834337036 545.195629362 351.393023644 545.195629362 351.393023644 545.195629362C351.393023644 545.195629362 351.393023644 619.489838289 351.393023644 619.489838289C351.393023644 619.489838289 462.834337036 619.489838289 462.834337036 619.489838289C462.834337036 619.489838289 462.834337036 730.931172514 462.834337036 730.931172514C462.834337036 730.931172514 537.128545964 730.931172514 537.128545964 730.931172514C537.128545964 730.931172514 537.128545964 619.489838289 537.128545964 619.489838289C537.128545964 619.489838289 648.569838522 619.489838289 648.569838522 619.489838289C648.569838522 619.489838289 648.569838522 545.195629362 648.569838522 545.195629362C648.569838522 545.195629362 537.128545964 545.195629362 537.128545964 545.195629362C537.128545964 545.195629362 537.128545964 433.754336803 537.128545964 433.754336803\"\n        tools:ignore=\"VectorPath\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_timer_black_24dp.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"24\"\n    android:viewportHeight=\"24\">\n\n    <path\n        android:fillColor=\"#595757\"\n        android:pathData=\"M 12.742 9.076 L 11.258 9.076 L 11.258 13.009 L 14.492 13.009 L 14.492 11.522 L 12.742 11.522 Z\" />\n    <path\n        android:fillColor=\"#595757\"\n        android:pathData=\"M12.094,5.522c-3.719,0-6.742,3.024-6.742,6.742c0,2.38,1.241,4.472,3.107,5.672L7.77,19.016 l1.252,0.801l0.777-1.218c0.717,0.261,1.488,0.41,2.295,0.41c0.918,0,1.792-0.187,2.592-0.52l0.848,1.327l1.252-0.801l-0.803-1.252 c1.725-1.224,2.854-3.229,2.854-5.499C18.836,8.547,15.811,5.522,12.094,5.522z M12.094,17.523c-2.898,0-5.258-2.358-5.258-5.259 c0-2.897,2.359-5.256,5.258-5.256s5.257,2.358,5.257,5.256C17.351,15.165,14.992,17.523,12.094,17.523z\" />\n    <path\n        android:fillColor=\"#595757\"\n        android:pathData=\"M5.725,6.557c0-0.336,0.13-0.65,0.367-0.888c0.49-0.49,1.289-0.491,1.779,0.001l1.05-1.053 c-1.069-1.068-2.811-1.068-3.879,0C4.523,5.135,4.238,5.823,4.237,6.556c0,0.733,0.286,1.423,0.805,1.941l1.05-1.05 C5.854,7.209,5.723,6.893,5.725,6.557z\" />\n    <path\n        android:fillColor=\"#595757\"\n        android:pathData=\"M18.958,4.617c-1.071-1.069-2.812-1.068-3.88,0.002l1.051,1.05c0.489-0.49,1.288-0.49,1.779,0 c0.489,0.489,0.489,1.287-0.002,1.778l1.053,1.05C20.027,7.427,20.027,5.687,18.958,4.617z\" />\n</vector>"
  },
  {
    "path": "app/src/main/res/drawable/ic_toc.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"24.0\"\n    android:viewportHeight=\"24.0\">\n    <path\n        android:fillColor=\"#FF000000\"\n        android:pathData=\"M3,9h14L17,7L3,7v2zM3,13h14v-2L3,11v2zM3,17h14v-2L3,15v2zM19,17h2v-2h-2v2zM19,7v2h2L21,7h-2zM19,13h2v-2h-2v2z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_translate.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"24\"\n    android:viewportHeight=\"24\">\n\n    <path\n        android:fillColor=\"#595757\"\n        android:pathData=\"M16.424,10.47h-1.258L11.676,20h1.428l0.984-2.77h3.459L18.572,20H20L16.424,10.47z M17.057,15.917 h-2.481l1.241-3.535L17.057,15.917z\" />\n    <path\n        android:fillColor=\"#595757\"\n        android:pathData=\"M12.361,15.597l0.488-1.354l-0.191-0.082c-0.73-0.313-1.342-0.657-1.824-1.024 c1.232-1.382,2.061-3.222,2.465-5.476h2.135v-1.38h-4.7l0.25-0.146l-0.098-0.201c-0.413-0.843-0.722-1.424-0.946-1.775L9.839,4 L8.656,4.654l0.098,0.204c0.309,0.644,0.543,1.119,0.701,1.423h-5.25v1.38H6.34c0.38,2.138,1.211,3.978,2.475,5.476 c-0.93,0.749-2.461,1.466-4.555,2.133L4,15.352l0.143,0.254c0.153,0.271,0.322,0.595,0.508,0.969l0.085,0.171L4.9,16.678 c2.273-0.928,3.928-1.79,4.916-2.563c0.566,0.456,1.355,0.925,2.353,1.393L12.361,15.597z M11.912,7.661 c-0.287,1.778-0.975,3.294-2.045,4.513c-1.131-1.274-1.877-2.79-2.223-4.513H11.912z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_update.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"24\"\n    android:viewportHeight=\"24\">\n\n    <path\n        android:fillColor=\"#595757\"\n        android:pathData=\"M15.879,10.302H20V6.182h-1.454V7.43C17.066,5.315,14.646,4,12,4c-4.412,0-8,3.589-8,8s3.588,8,8,8 c3.789,0,7.086-2.691,7.84-6.401l-1.426-0.29C17.798,16.344,15.1,18.546,12,18.546c-3.608,0-6.546-2.937-6.546-6.546 S8.392,5.454,12,5.454c2.398,0,4.576,1.32,5.721,3.394h-1.842V10.302z\" />\n    <path\n        android:fillColor=\"#595757\"\n        android:pathData=\"M 12.727 12.105 L 12.727 7.469 L 11.273 7.469 L 11.273 12.708 L 14.014 15.448 L 15.042 14.42 Z\" />\n</vector>"
  },
  {
    "path": "app/src/main/res/drawable/ic_view_quilt.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\r\n    android:width=\"24dp\"\r\n    android:height=\"24dp\"\r\n    android:viewportWidth=\"24\"\r\n    android:viewportHeight=\"24\">\r\n\r\n    <path\r\n        android:fillColor=\"#595757\"\r\n        android:pathData=\"M18,4 L6,4 C4.896,4,4,4.896,4,6 L4,18 C4,19.104,4.896,20,6,20 L18,20 C19.104,20,20,19.104,20,18 L20,6 C20,4.896,19.104,4,18,4 Z M18.568,18.568 L5.432,18.568 L5.432,5.432 L18.568,5.432 L18.568,18.568 Z\" />\r\n    <path\r\n        android:strokeColor=\"#595757\"\r\n        android:strokeWidth=\"1.435\"\r\n        android:strokeMiterLimit=\"10\"\r\n        android:pathData=\"M5.234,8.946 L18.734,8.946\" />\r\n    <path\r\n        android:strokeColor=\"#595757\"\r\n        android:strokeWidth=\"1.435\"\r\n        android:strokeMiterLimit=\"10\"\r\n        android:pathData=\"M8.559,9.422 L8.559,18.616\" />\r\n    <path\r\n        android:strokeColor=\"#595757\"\r\n        android:strokeWidth=\"1.435\"\r\n        android:strokeMiterLimit=\"10\"\r\n        android:pathData=\"M18.734,13.482 L8.969,13.482\" />\r\n</vector>"
  },
  {
    "path": "app/src/main/res/drawable/ic_visibility_off.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"24.0\"\n    android:viewportHeight=\"24.0\">\n    <path\n        android:fillColor=\"#FF000000\"\n        android:pathData=\"M12,7c2.76,0 5,2.24 5,5 0,0.65 -0.13,1.26 -0.36,1.83l2.92,2.92c1.51,-1.26 2.7,-2.89 3.43,-4.75 -1.73,-4.39 -6,-7.5 -11,-7.5 -1.4,0 -2.74,0.25 -3.98,0.7l2.16,2.16C10.74,7.13 11.35,7 12,7zM2,4.27l2.28,2.28 0.46,0.46C3.08,8.3 1.78,10.02 1,12c1.73,4.39 6,7.5 11,7.5 1.55,0 3.03,-0.3 4.38,-0.84l0.42,0.42L19.73,22 21,20.73 3.27,3 2,4.27zM7.53,9.8l1.55,1.55c-0.05,0.21 -0.08,0.43 -0.08,0.65 0,1.66 1.34,3 3,3 0.22,0 0.44,-0.03 0.65,-0.08l1.55,1.55c-0.67,0.33 -1.41,0.53 -2.2,0.53 -2.76,0 -5,-2.24 -5,-5 0,-0.79 0.2,-1.53 0.53,-2.2zM11.84,9.02l3.15,3.15 0.02,-0.16c0,-1.66 -1.34,-3 -3,-3l-0.17,0.01z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_volume_up.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"24.0\"\n    android:viewportHeight=\"24.0\">\n    <path\n        android:fillColor=\"#FFFFFFFF\"\n        android:pathData=\"M3,9v6h4l5,5L12,4L7,9L3,9zM16.5,12c0,-1.77 -1.02,-3.29 -2.5,-4.03v8.05c1.48,-0.73 2.5,-2.25 2.5,-4.02zM14,3.23v2.06c2.89,0.86 5,3.54 5,6.71s-2.11,5.85 -5,6.71v2.06c4.01,-0.91 7,-4.49 7,-8.77s-2.99,-7.86 -7,-8.77z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_web_outline.xml",
    "content": "<vector xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:width=\"24dp\"\n    android:height=\"24dp\"\n    android:viewportWidth=\"24\"\n    android:viewportHeight=\"24\">\n    <path\n        android:pathData=\"m12.23,3.5468c4.0323,5.6914 4.6425,11.2543 0,16.6202l0,0m-0.4599,-16.6202c-4.0323,5.6914 -4.6425,11.2543 0,16.6202l0,0m-8.27,-8.167 l17,0 0,0m-0.2485,0a8.2515,8.2515 72.5893,0 1,-8.2515 8.2516,8.2515 8.2515,72.5893 0,1 -8.2515,-8.2516 8.2515,8.2515 72.5893,0 1,8.2515 -8.2516,8.2515 8.2515,72.5893 0,1 8.2515,8.2516z\"\n        android:strokeAlpha=\"1\"\n        android:strokeLineJoin=\"miter\"\n        android:strokeWidth=\"1.5\"\n        android:strokeColor=\"#4A4B4A\"\n        android:strokeLineCap=\"butt\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/ic_web_service_noti.xml",
    "content": "<vector android:height=\"24dp\"\n    android:tint=\"#FFFFFF\"\n    android:viewportHeight=\"24.0\"\n    android:viewportWidth=\"24.0\"\n    android:width=\"24dp\"\n    xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <path\n        android:fillColor=\"#FF000000\"\n        android:pathData=\"M7,7.07L8.43,8.5c0.91,-0.91 2.18,-1.48 3.57,-1.48s2.66,0.57 3.57,1.48L17,7.07C15.72,5.79 13.95,5 12,5s-3.72,0.79 -5,2.07zM12,1C8.98,1 6.24,2.23 4.25,4.21l1.41,1.41C7.28,4 9.53,3 12,3s4.72,1 6.34,2.62l1.41,-1.41C17.76,2.23 15.02,1 12,1zM14.86,10.01L9.14,10C8.51,10 8,10.51 8,11.14v9.71c0,0.63 0.51,1.14 1.14,1.14h5.71c0.63,0 1.14,-0.51 1.14,-1.14v-9.71c0.01,-0.63 -0.5,-1.13 -1.13,-1.13zM15,20L9,20v-8h6v8z\" />\n</vector>\n"
  },
  {
    "path": "app/src/main/res/drawable/recyclerview_divider_horizontal.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<inset xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <shape>\n        <size android:width=\"0.5dp\" />\n        <!--分割线的颜色-->\n        <solid android:color=\"@color/bg_divider_line\" />\n    </shape>\n\n</inset>"
  },
  {
    "path": "app/src/main/res/drawable/recyclerview_divider_vertical.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<inset xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:insetLeft=\"10dp\">\n    <!--android:insetLeft=\"10dp\" 分割线距离左侧10dp-->\n    <shape>\n        <!--分割线的高度，横向的RecyclerView,这里设置宽度即可-->\n        <size android:height=\"1px\" />\n        <!--分割线的颜色-->\n        <solid android:color=\"@color/bg_divider_line\" />\n    </shape>\n\n</inset>"
  },
  {
    "path": "app/src/main/res/drawable/selector_btn_accent_bg.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <item android:state_pressed=\"false\" android:drawable=\"@color/accent\" />\n    <item android:state_pressed=\"true\" android:drawable=\"@color/btn_bg_press\" />\n</selector>"
  },
  {
    "path": "app/src/main/res/drawable/selector_circle_btn_bg.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <item android:state_pressed=\"true\" android:drawable=\"@drawable/shape_circle\" />\n</selector>"
  },
  {
    "path": "app/src/main/res/drawable/selector_common_bg.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <item android:state_pressed=\"true\" android:drawable=\"@color/btn_bg_press\" />\n    <item android:state_selected=\"true\" android:drawable=\"@color/btn_bg_press\" />\n\n    <item android:drawable=\"@color/background\" />\n</selector>"
  },
  {
    "path": "app/src/main/res/drawable/selector_fillet_btn_bg.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <item android:state_pressed=\"false\" android:drawable=\"@drawable/shape_fillet_btn\" />\n    <item android:state_pressed=\"true\" android:drawable=\"@drawable/shape_fillet_btn_press\" />\n</selector>"
  },
  {
    "path": "app/src/main/res/drawable/selector_tv_black.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<selector xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <item android:color=\"@color/tv_btn_normal_black\" android:state_pressed=\"false\" android:state_enabled=\"true\" />\n    <item android:color=\"@color/tv_btn_press_black\" android:state_pressed=\"true\" android:state_enabled=\"true\" />\n    <item android:color=\"@color/tv_btn_press_black\" android:state_enabled=\"false\" />\n</selector>"
  },
  {
    "path": "app/src/main/res/drawable/shape_card_view.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:shape=\"rectangle\">\n    <corners android:radius=\"3dp\" />\n    <solid android:color=\"@color/background_card\" />\n</shape>"
  },
  {
    "path": "app/src/main/res/drawable/shape_circle.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:shape=\"oval\">\n\n    <solid android:color=\"#60666666\" />\n\n    <size\n        android:width=\"120dp\"\n        android:height=\"120dp\" />\n\n</shape>"
  },
  {
    "path": "app/src/main/res/drawable/shape_fillet_btn.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:shape=\"rectangle\">\n    <corners android:radius=\"16dp\" />\n    <solid android:color=\"@color/btn_bg_press\" />\n</shape>"
  },
  {
    "path": "app/src/main/res/drawable/shape_fillet_btn_press.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:shape=\"rectangle\">\n    <corners android:radius=\"16dp\" />\n    <solid android:color=\"@color/btn_bg_press_2\" />\n</shape>"
  },
  {
    "path": "app/src/main/res/drawable/shape_pop_checkaddshelf_bg.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:shape=\"rectangle\">\n    <corners android:radius=\"15dp\" />\n    <solid android:color=\"#0000\" />\n</shape>"
  },
  {
    "path": "app/src/main/res/drawable/shape_radius_10dp.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <corners android:radius=\"16dp\" />\n    <solid android:color=\"#00ffffff\" />\n    <stroke\n        android:width=\"1dp\"\n        android:color=\"@color/secondaryText\" />\n</shape>"
  },
  {
    "path": "app/src/main/res/drawable/shape_radius_1dp.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <corners android:radius=\"1dp\" />\n    <solid android:color=\"#00ffffff\" />\n    <stroke\n        android:width=\"1dp\"\n        android:color=\"@color/secondaryText\" />\n</shape>"
  },
  {
    "path": "app/src/main/res/drawable/shape_space_divider.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n    <size\n        android:width=\"8dp\"\n        android:height=\"8dp\" />\n\n</shape>"
  },
  {
    "path": "app/src/main/res/drawable/shape_text_cursor.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:shape=\"rectangle\">\n    <size android:width=\"1.5dp\" />\n    <solid android:color=\"#3B96FC\" />\n    <corners android:radius=\"1dp\" />\n</shape>"
  },
  {
    "path": "app/src/main/res/drawable/shape_translucent_card.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<shape xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:shape=\"rectangle\">\n    <corners android:radius=\"3dp\" />\n    <solid android:color=\"@color/translucent\" />\n</shape>"
  },
  {
    "path": "app/src/main/res/layout/activity_about.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:orientation=\"vertical\"\n    tools:context=\"io.legado.app.ui.about.AboutActivity\">\n\n    <io.legado.app.ui.widget.TitleBar\n        android:id=\"@+id/title_bar\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        app:title=\"@string/about\">\n\n        <LinearLayout\n            android:id=\"@+id/ll_about\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:background=\"@drawable/shape_card_view\"\n            android:orientation=\"vertical\"\n            android:layout_margin=\"6dp\"\n            android:padding=\"10dp\">\n\n            <TextView\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:layout_gravity=\"center\"\n                android:text=\"@string/app_name\"\n                android:textSize=\"20sp\"\n                android:textStyle=\"bold\" />\n\n            <TextView\n                android:id=\"@+id/tv_app_summary\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:text=\"@string/about_description\" />\n        </LinearLayout>\n\n    </io.legado.app.ui.widget.TitleBar>\n\n    <FrameLayout\n        android:id=\"@+id/fl_fragment\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\" />\n\n</LinearLayout>\n"
  },
  {
    "path": "app/src/main/res/layout/activity_all_bookmark.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\">\n\n    <io.legado.app.ui.widget.TitleBar\n        android:id=\"@+id/title_bar\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        app:layout_constraintTop_toTopOf=\"parent\"\n        app:title=\"@string/all_bookmark\" />\n\n    <androidx.recyclerview.widget.RecyclerView\n        android:id=\"@+id/recycler_view\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"0dp\"\n        android:clipToPadding=\"false\"\n        app:layoutManager=\"androidx.recyclerview.widget.LinearLayoutManager\"\n        app:layout_constraintBottom_toBottomOf=\"parent\"\n        app:layout_constraintTop_toBottomOf=\"@+id/title_bar\" />\n\n</androidx.constraintlayout.widget.ConstraintLayout>"
  },
  {
    "path": "app/src/main/res/layout/activity_arrange_book.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:orientation=\"vertical\">\n\n    <io.legado.app.ui.widget.TitleBar\n        android:id=\"@+id/title_bar\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        app:contentInsetStartWithNavigation=\"0dp\"\n        app:contentLayout=\"@layout/view_search\"\n        app:layout_constraintTop_toTopOf=\"parent\"\n        app:title=\"@string/bookshelf_management\" />\n\n    <io.legado.app.ui.widget.recycler.scroller.FastScrollRecyclerView\n        android:id=\"@+id/recycler_view\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"0dp\"\n        android:scrollbars=\"none\"\n        app:layout_constraintBottom_toTopOf=\"@id/select_action_bar\"\n        app:layout_constraintTop_toBottomOf=\"@id/title_bar\" />\n\n    <io.legado.app.ui.widget.SelectActionBar\n        android:id=\"@+id/select_action_bar\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        app:layout_constraintBottom_toBottomOf=\"parent\" />\n\n</androidx.constraintlayout.widget.ConstraintLayout>"
  },
  {
    "path": "app/src/main/res/layout/activity_audio_play.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:background=\"@color/background\"\n    android:orientation=\"vertical\"\n    tools:context=\".ui.book.audio.AudioPlayActivity\">\n\n    <ImageView\n        android:id=\"@+id/iv_bg\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:scaleType=\"centerCrop\"\n        tools:ignore=\"ContentDescription,ImageContrastCheck\" />\n\n    <View\n        android:id=\"@+id/vw_bg\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:background=\"#50000000\" />\n\n    <io.legado.app.ui.widget.TitleBar\n        android:id=\"@+id/title_bar\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        app:layout_constraintTop_toTopOf=\"parent\"\n        app:themeMode=\"dark\" />\n\n    <TextView\n        android:id=\"@+id/tv_timer\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_margin=\"16dp\"\n        android:background=\"@drawable/shape_fillet_btn_press\"\n        android:paddingLeft=\"6dp\"\n        android:paddingRight=\"6dp\"\n        android:textColor=\"@color/md_white_1000\"\n        android:visibility=\"invisible\"\n        app:drawableLeftCompat=\"@drawable/ic_timer_black_24dp\"\n        app:drawableTint=\"@color/md_white_1000\"\n        app:layout_constraintLeft_toLeftOf=\"parent\"\n        app:layout_constraintTop_toBottomOf=\"@id/title_bar\" />\n\n    <TextView\n        android:id=\"@+id/tv_speed\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_margin=\"16dp\"\n        android:background=\"@drawable/shape_fillet_btn_press\"\n        android:paddingLeft=\"6dp\"\n        android:paddingRight=\"6dp\"\n        android:textColor=\"@color/md_white_1000\"\n        android:visibility=\"invisible\"\n        app:layout_constraintRight_toRightOf=\"parent\"\n        app:layout_constraintTop_toBottomOf=\"@id/title_bar\" />\n\n    <io.legado.app.ui.widget.image.CircleImageView\n        android:id=\"@+id/iv_cover\"\n        android:layout_width=\"260dp\"\n        android:layout_height=\"260dp\"\n        android:contentDescription=\"@string/img_cover\"\n        android:scaleType=\"centerCrop\"\n        app:civ_border_color=\"@color/accent\"\n        app:civ_border_width=\"2dp\"\n        app:layout_constraintBottom_toTopOf=\"@+id/ll_player_progress\"\n        app:layout_constraintLeft_toLeftOf=\"parent\"\n        app:layout_constraintRight_toRightOf=\"parent\"\n        app:layout_constraintTop_toBottomOf=\"@+id/title_bar\" />\n\n    <io.legado.app.ui.widget.image.ImageButton\n            android:id=\"@+id/iv_play_mode\"\n            android:layout_width=\"46dp\"\n            android:layout_height=\"46dp\"\n            android:background=\"@drawable/selector_circle_btn_bg\"\n            android:contentDescription=\"@string/play_mode\"\n            android:padding=\"5dp\"\n            android:src=\"@drawable/ic_play_mode_list_end_stop\"\n            android:layout_marginStart=\"16dp\"\n            app:tint=\"@color/md_white_1000\"\n            app:layout_constraintBottom_toTopOf=\"@+id/tv_sub_title\"\n            app:layout_constraintStart_toStartOf=\"parent\"\n            tools:ignore=\"ImageContrastCheck\" />\n\n    <TextView\n        android:id=\"@+id/tv_sub_title\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:gravity=\"center\"\n        android:paddingStart=\"16dp\"\n        android:paddingTop=\"6dp\"\n        android:paddingEnd=\"16dp\"\n        android:paddingBottom=\"6dp\"\n        android:textColor=\"@color/md_white_1000\"\n        app:layout_constraintBottom_toTopOf=\"@+id/ll_player_progress\" />\n\n    <LinearLayout\n        android:id=\"@+id/ll_player_progress\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:gravity=\"center_vertical\"\n        android:orientation=\"horizontal\"\n        android:paddingStart=\"16dp\"\n        android:paddingEnd=\"16dp\"\n        app:layout_constraintBottom_toTopOf=\"@+id/ll_play_menu\">\n\n        <TextView\n            android:id=\"@+id/tv_dur_time\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:text=\"00:00\"\n            android:textColor=\"@color/md_white_1000\"\n            tools:ignore=\"HardcodedText,RtlSymmetry,TextContrastCheck\" />\n\n        <io.legado.app.lib.theme.view.ThemeSeekBar\n            android:id=\"@+id/player_progress\"\n            android:layout_width=\"0dp\"\n            android:layout_height=\"25dp\"\n            android:layout_gravity=\"center_vertical\"\n            android:layout_weight=\"1\"\n            android:progressBackgroundTint=\"@color/md_dark_secondary\" />\n\n        <TextView\n            android:id=\"@+id/tv_all_time\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:text=\"00:00\"\n            android:textColor=\"@color/md_white_1000\"\n            tools:ignore=\"HardcodedText,TextContrastCheck\" />\n\n    </LinearLayout>\n\n    <LinearLayout\n        android:id=\"@+id/ll_play_menu\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:gravity=\"center_vertical\"\n        android:orientation=\"horizontal\"\n        android:paddingHorizontal=\"6dp\"\n        app:layout_constraintBottom_toBottomOf=\"parent\">\n\n        <io.legado.app.ui.widget.image.ImageButton\n            android:id=\"@+id/iv_timer\"\n            android:layout_width=\"46dp\"\n            android:layout_height=\"46dp\"\n            android:background=\"@drawable/selector_circle_btn_bg\"\n            android:contentDescription=\"@string/set_timer\"\n            android:padding=\"5dp\"\n            android:src=\"@drawable/ic_timer_black_24dp\"\n            app:tint=\"@color/md_white_1000\"\n            tools:ignore=\"ImageContrastCheck\" />\n\n        <View\n            android:layout_width=\"0dp\"\n            android:layout_height=\"1dp\"\n            android:layout_weight=\"1\" />\n\n        <io.legado.app.ui.widget.image.ImageButton\n            android:id=\"@+id/iv_fast_rewind\"\n            android:layout_width=\"46dp\"\n            android:layout_height=\"46dp\"\n            android:background=\"@drawable/selector_circle_btn_bg\"\n            android:contentDescription=\"@string/skip_previous\"\n            android:padding=\"5dp\"\n            android:src=\"@drawable/ic_fast_rewind\"\n            app:tint=\"@color/md_white_1000\"\n            tools:ignore=\"ImageContrastCheck\" />\n\n        <View\n            android:layout_width=\"0dp\"\n            android:layout_height=\"1dp\"\n            android:layout_weight=\"1\" />\n\n        <io.legado.app.ui.widget.image.ImageButton\n            android:id=\"@+id/iv_skip_previous\"\n            android:layout_width=\"46dp\"\n            android:layout_height=\"46dp\"\n            android:background=\"@drawable/selector_circle_btn_bg\"\n            android:contentDescription=\"@string/skip_previous\"\n            android:padding=\"5dp\"\n            android:src=\"@drawable/ic_skip_previous\"\n            app:tint=\"@color/md_white_1000\"\n            tools:ignore=\"ImageContrastCheck\" />\n\n        <androidx.constraintlayout.widget.ConstraintLayout\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\">\n\n            <com.google.android.material.floatingactionbutton.FloatingActionButton\n                android:id=\"@+id/fab_play_stop\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:layout_margin=\"10dp\"\n                android:contentDescription=\"@string/audio_play\"\n                android:src=\"@drawable/ic_play_24dp\"\n                android:tint=\"@color/md_black_1000\"\n                app:backgroundTint=\"@color/md_white_1000\"\n                app:elevation=\"2dp\"\n                app:fabSize=\"normal\"\n                app:layout_constraintStart_toStartOf=\"parent\"\n                app:layout_constraintEnd_toEndOf=\"parent\"\n                app:layout_constraintBottom_toBottomOf=\"parent\"\n                app:layout_constraintTop_toTopOf=\"parent\"\n                app:pressedTranslationZ=\"2dp\"\n                tools:ignore=\"ImageContrastCheck\" />\n\n            <io.legado.app.lib.theme.view.ThemeProgressBar\n                android:id=\"@+id/progress_loading\"\n                android:layout_width=\"0dp\"\n                android:layout_height=\"0dp\"\n                android:visibility=\"invisible\"\n                app:layout_constraintWidth=\"parent\"\n                app:layout_constraintHeight=\"parent\"\n                app:layout_constraintBottom_toBottomOf=\"parent\"\n                app:layout_constraintEnd_toEndOf=\"parent\"\n                app:layout_constraintStart_toStartOf=\"parent\"\n                app:layout_constraintTop_toTopOf=\"parent\" />\n\n        </androidx.constraintlayout.widget.ConstraintLayout>\n\n        <io.legado.app.ui.widget.image.ImageButton\n            android:id=\"@+id/iv_skip_next\"\n            android:layout_width=\"46dp\"\n            android:layout_height=\"46dp\"\n            android:background=\"@drawable/selector_circle_btn_bg\"\n            android:contentDescription=\"@string/skip_next\"\n            android:padding=\"5dp\"\n            android:src=\"@drawable/ic_skip_next\"\n            app:tint=\"@color/md_white_1000\"\n            tools:ignore=\"ImageContrastCheck\" />\n\n        <View\n            android:layout_width=\"0dp\"\n            android:layout_height=\"1dp\"\n            android:layout_weight=\"1\" />\n\n        <io.legado.app.ui.widget.image.ImageButton\n            android:id=\"@+id/iv_fast_forward\"\n            android:layout_width=\"46dp\"\n            android:layout_height=\"46dp\"\n            android:background=\"@drawable/selector_circle_btn_bg\"\n            android:contentDescription=\"@string/skip_next\"\n            android:padding=\"5dp\"\n            android:src=\"@drawable/ic_fast_forward\"\n            app:tint=\"@color/md_white_1000\"\n            tools:ignore=\"ImageContrastCheck\" />\n\n        <View\n            android:layout_width=\"0dp\"\n            android:layout_height=\"1dp\"\n            android:layout_weight=\"1\" />\n\n        <io.legado.app.ui.widget.image.ImageButton\n            android:id=\"@+id/iv_chapter\"\n            android:layout_width=\"46dp\"\n            android:layout_height=\"46dp\"\n            android:background=\"@drawable/selector_circle_btn_bg\"\n            android:contentDescription=\"@string/chapter_list\"\n            android:padding=\"5dp\"\n            android:src=\"@drawable/ic_chapter_list\"\n            app:tint=\"@color/md_white_1000\"\n            tools:ignore=\"ImageContrastCheck\" />\n\n    </LinearLayout>\n\n</androidx.constraintlayout.widget.ConstraintLayout>\n"
  },
  {
    "path": "app/src/main/res/layout/activity_book_info.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:orientation=\"vertical\">\n\n    <ImageView\n        android:id=\"@+id/bg_book\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:contentDescription=\"@string/bg_image\"\n        android:scaleType=\"centerCrop\"\n        app:layout_constraintTop_toTopOf=\"parent\" />\n\n    <LinearLayout\n        android:id=\"@+id/vw_bg\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:background=\"#50000000\"\n        android:orientation=\"vertical\">\n\n        <io.legado.app.ui.widget.TitleBar\n            android:id=\"@+id/title_bar\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            app:layout_constraintTop_toTopOf=\"parent\"\n            app:themeMode=\"dark\"\n            app:title=\"@string/book_info\" />\n\n        <androidx.swiperefreshlayout.widget.SwipeRefreshLayout\n            android:id=\"@+id/refresh_layout\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"0dp\"\n            android:layout_weight=\"1\">\n\n            <io.legado.app.ui.widget.NoChildScrollNestedScrollView\n                android:id=\"@+id/scroll_view\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"0dp\"\n                android:layout_weight=\"1.0\"\n                android:fillViewport=\"true\"\n                android:fitsSystemWindows=\"false\"\n                android:focusable=\"true\"\n                android:padding=\"0dp\"\n                android:scrollbarStyle=\"outsideOverlay\"\n                android:scrollbars=\"vertical\">\n\n                <LinearLayout\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"0dp\"\n                    android:orientation=\"vertical\">\n\n                    <RelativeLayout\n                        android:layout_width=\"match_parent\"\n                        android:layout_height=\"wrap_content\"\n                        android:layout_marginTop=\"8dp\">\n\n                        <io.legado.app.ui.widget.image.ArcView\n                            android:id=\"@+id/arc_view\"\n                            android:layout_width=\"match_parent\"\n                            android:layout_height=\"78dp\"\n                            android:layout_marginTop=\"90dp\"\n                            app:arcDirectionTop=\"true\"\n                            app:arcHeight=\"36dp\"\n                            app:bgColor=\"@color/background\" />\n\n                        <androidx.cardview.widget.CardView\n                            android:layout_width=\"wrap_content\"\n                            android:layout_height=\"wrap_content\"\n                            android:layout_centerHorizontal=\"true\"\n                            android:layout_margin=\"3dp\"\n                            app:cardCornerRadius=\"5dp\"\n                            app:cardElevation=\"8dp\">\n\n                            <io.legado.app.ui.widget.image.CoverImageView\n                                android:id=\"@+id/iv_cover\"\n                                android:layout_width=\"110dp\"\n                                android:layout_height=\"160dp\"\n                                android:contentDescription=\"@string/img_cover\"\n                                android:scaleType=\"centerCrop\"\n                                android:src=\"@drawable/image_cover_default\" />\n\n                        </androidx.cardview.widget.CardView>\n\n                    </RelativeLayout>\n\n                    <LinearLayout\n                        android:id=\"@+id/ll_info\"\n                        android:layout_width=\"match_parent\"\n                        android:layout_height=\"match_parent\"\n                        android:background=\"@color/background\"\n                        android:orientation=\"vertical\"\n                        android:paddingLeft=\"8dp\"\n                        android:paddingRight=\"8dp\"\n                        android:paddingBottom=\"8dp\">\n\n                        <LinearLayout\n                            android:layout_width=\"match_parent\"\n                            android:layout_height=\"wrap_content\"\n                            android:orientation=\"vertical\"\n                            android:paddingLeft=\"10dp\"\n                            android:paddingTop=\"8dp\"\n                            android:paddingRight=\"10dp\"\n                            android:paddingBottom=\"3dp\">\n\n                            <HorizontalScrollView\n                                android:layout_width=\"wrap_content\"\n                                android:layout_height=\"wrap_content\"\n                                android:layout_gravity=\"center\"\n                                android:layout_marginBottom=\"6dp\"\n                                android:scrollbars=\"none\">\n\n                                <TextView\n                                    android:id=\"@+id/tv_name\"\n                                    android:layout_width=\"wrap_content\"\n                                    android:layout_height=\"wrap_content\"\n                                    android:includeFontPadding=\"false\"\n                                    android:singleLine=\"true\"\n                                    android:text=\"@string/book_name\"\n                                    android:textColor=\"@color/primaryText\"\n                                    android:textSize=\"18sp\"\n                                    tools:ignore=\"RtlHardcoded\" />\n                            </HorizontalScrollView>\n\n                            <HorizontalScrollView\n                                android:layout_width=\"wrap_content\"\n                                android:layout_height=\"wrap_content\"\n                                android:layout_gravity=\"center\"\n                                android:layout_marginBottom=\"6dp\"\n                                android:scrollbars=\"none\">\n\n                                <io.legado.app.ui.widget.LabelsBar\n                                    android:id=\"@+id/lb_kind\"\n                                    android:layout_width=\"wrap_content\"\n                                    android:layout_height=\"wrap_content\"\n                                    android:visibility=\"gone\" />\n                            </HorizontalScrollView>\n\n                        </LinearLayout>\n\n                        <LinearLayout\n                            android:layout_width=\"match_parent\"\n                            android:layout_height=\"wrap_content\"\n                            android:orientation=\"vertical\"\n                            android:paddingLeft=\"8dp\"\n                            android:paddingRight=\"8dp\"\n                            android:paddingBottom=\"8dp\">\n\n                            <LinearLayout\n                                android:layout_width=\"match_parent\"\n                                android:layout_height=\"wrap_content\"\n                                android:gravity=\"center_vertical\"\n                                android:orientation=\"horizontal\"\n                                android:paddingTop=\"3dp\"\n                                android:paddingBottom=\"3dp\"\n                                tools:ignore=\"UseCompoundDrawables\">\n\n                                <ImageView\n                                    android:layout_width=\"18sp\"\n                                    android:layout_height=\"18sp\"\n                                    android:contentDescription=\"@string/author\"\n                                    android:paddingRight=\"2dp\"\n                                    android:src=\"@drawable/ic_author\"\n                                    app:tint=\"@color/tv_text_summary\"\n                                    tools:ignore=\"RtlHardcoded,RtlSymmetry\" />\n\n                                <TextView\n                                    android:id=\"@+id/tv_author\"\n                                    android:layout_width=\"0dp\"\n                                    android:layout_height=\"wrap_content\"\n                                    android:layout_weight=\"1\"\n                                    android:ellipsize=\"end\"\n                                    android:includeFontPadding=\"false\"\n                                    android:paddingRight=\"6dp\"\n                                    android:singleLine=\"true\"\n                                    android:text=\"@string/author\"\n                                    android:textColor=\"@color/tv_text_summary\"\n                                    android:textSize=\"13sp\"\n                                    tools:ignore=\"NestedWeights,RtlHardcoded,RtlSymmetry\" />\n\n                            </LinearLayout>\n\n                            <LinearLayout\n                                android:layout_width=\"match_parent\"\n                                android:layout_height=\"wrap_content\"\n                                android:gravity=\"center_vertical\"\n                                android:orientation=\"horizontal\"\n                                android:paddingTop=\"3dp\"\n                                android:paddingBottom=\"3dp\">\n\n                                <ImageView\n                                    android:id=\"@+id/iv_web\"\n                                    android:layout_width=\"18sp\"\n                                    android:layout_height=\"18sp\"\n                                    android:contentDescription=\"@string/origin_format\"\n                                    android:paddingRight=\"2dp\"\n                                    android:src=\"@drawable/ic_web_outline\"\n                                    app:tint=\"@color/tv_text_summary\"\n                                    tools:ignore=\"RtlHardcoded,RtlSymmetry\" />\n\n                                <TextView\n                                    android:id=\"@+id/tv_origin\"\n                                    android:layout_width=\"0dp\"\n                                    android:layout_height=\"wrap_content\"\n                                    android:layout_weight=\"1\"\n                                    android:ellipsize=\"end\"\n                                    android:includeFontPadding=\"false\"\n                                    android:paddingRight=\"6dp\"\n                                    android:singleLine=\"true\"\n                                    android:textColor=\"@color/tv_text_summary\"\n                                    android:textSize=\"13sp\"\n                                    tools:ignore=\"NestedWeights,RtlHardcoded,RtlSymmetry\"\n                                    tools:text=\"@string/origin_format\" />\n\n                                <io.legado.app.ui.widget.text.AccentBgTextView\n                                    android:id=\"@+id/tv_change_source\"\n                                    android:layout_width=\"wrap_content\"\n                                    android:layout_height=\"wrap_content\"\n                                    android:layout_marginStart=\"8dp\"\n                                    android:paddingLeft=\"5dp\"\n                                    android:paddingRight=\"5dp\"\n                                    android:text=\"@string/change_origin\"\n                                    android:textSize=\"13sp\"\n                                    app:radius=\"2dp\" />\n\n                            </LinearLayout>\n\n                            <LinearLayout\n                                android:layout_width=\"match_parent\"\n                                android:layout_height=\"wrap_content\"\n                                android:gravity=\"center_vertical\"\n                                android:orientation=\"horizontal\"\n                                android:paddingTop=\"3dp\"\n                                android:paddingBottom=\"3dp\"\n                                tools:ignore=\"UseCompoundDrawables\">\n\n                                <ImageView\n                                    android:id=\"@+id/ic_book_last\"\n                                    android:layout_width=\"18sp\"\n                                    android:layout_height=\"18sp\"\n                                    android:contentDescription=\"@string/read_dur_progress\"\n                                    android:paddingRight=\"2dp\"\n                                    android:src=\"@drawable/ic_book_last\"\n                                    app:tint=\"@color/tv_text_summary\"\n                                    tools:ignore=\"RtlHardcoded,RtlSymmetry\" />\n\n                                <TextView\n                                    android:id=\"@+id/tv_lasted\"\n                                    android:layout_width=\"0dp\"\n                                    android:layout_height=\"wrap_content\"\n                                    android:layout_weight=\"1\"\n                                    android:ellipsize=\"end\"\n                                    android:includeFontPadding=\"false\"\n                                    android:paddingRight=\"6dp\"\n                                    android:singleLine=\"true\"\n                                    android:textColor=\"@color/tv_text_summary\"\n                                    android:textSize=\"13sp\"\n                                    tools:ignore=\"NestedWeights,RtlHardcoded,RtlSymmetry\"\n                                    tools:text=\"@string/read_dur_progress\" />\n\n                            </LinearLayout>\n\n                            <LinearLayout\n                                android:layout_width=\"match_parent\"\n                                android:layout_height=\"wrap_content\"\n                                android:gravity=\"center_vertical\"\n                                android:orientation=\"horizontal\"\n                                android:paddingTop=\"3dp\"\n                                android:paddingBottom=\"3dp\">\n\n                                <ImageView\n                                    android:layout_width=\"18sp\"\n                                    android:layout_height=\"18sp\"\n                                    android:contentDescription=\"@string/read_dur_progress\"\n                                    android:paddingRight=\"2dp\"\n                                    android:src=\"@drawable/ic_groups\"\n                                    app:tint=\"@color/tv_text_summary\"\n                                    tools:ignore=\"RtlHardcoded,RtlSymmetry\" />\n\n                                <TextView\n                                    android:id=\"@+id/tv_group\"\n                                    android:layout_width=\"0dp\"\n                                    android:layout_height=\"wrap_content\"\n                                    android:layout_weight=\"1\"\n                                    android:ellipsize=\"end\"\n                                    android:includeFontPadding=\"false\"\n                                    android:paddingRight=\"6dp\"\n                                    android:singleLine=\"true\"\n                                    android:textColor=\"@color/tv_text_summary\"\n                                    android:textSize=\"13sp\"\n                                    tools:ignore=\"NestedWeights,RtlHardcoded,RtlSymmetry\"\n                                    tools:text=\"@string/group_s\" />\n\n                                <io.legado.app.ui.widget.text.AccentBgTextView\n                                    android:id=\"@+id/tv_change_group\"\n                                    android:layout_width=\"wrap_content\"\n                                    android:layout_height=\"wrap_content\"\n                                    android:layout_marginStart=\"8dp\"\n                                    android:paddingLeft=\"5dp\"\n                                    android:paddingRight=\"5dp\"\n                                    android:text=\"@string/change_group\"\n                                    android:textSize=\"13sp\"\n                                    app:radius=\"2dp\" />\n\n                            </LinearLayout>\n\n                            <LinearLayout\n                                android:id=\"@+id/ll_toc\"\n                                android:layout_width=\"match_parent\"\n                                android:layout_height=\"wrap_content\"\n                                android:gravity=\"center_vertical\"\n                                android:orientation=\"horizontal\"\n                                android:paddingTop=\"3dp\"\n                                android:paddingBottom=\"3dp\">\n\n                                <ImageView\n                                    android:layout_width=\"18sp\"\n                                    android:layout_height=\"18sp\"\n                                    android:contentDescription=\"@string/read_dur_progress\"\n                                    android:paddingRight=\"2dp\"\n                                    android:src=\"@drawable/ic_folder_open\"\n                                    app:tint=\"@color/tv_text_summary\"\n                                    tools:ignore=\"RtlHardcoded,RtlSymmetry\" />\n\n                                <TextView\n                                    android:id=\"@+id/tv_toc\"\n                                    android:layout_width=\"0dp\"\n                                    android:layout_height=\"wrap_content\"\n                                    android:layout_weight=\"1\"\n                                    android:ellipsize=\"end\"\n                                    android:includeFontPadding=\"false\"\n                                    android:paddingRight=\"6dp\"\n                                    android:singleLine=\"true\"\n                                    android:textColor=\"@color/tv_text_summary\"\n                                    android:textSize=\"13sp\"\n                                    tools:ignore=\"NestedWeights,RtlHardcoded,RtlSymmetry\"\n                                    tools:text=\"@string/toc_s\" />\n\n                                <io.legado.app.ui.widget.text.AccentBgTextView\n                                    android:id=\"@+id/tv_toc_view\"\n                                    android:layout_width=\"wrap_content\"\n                                    android:layout_height=\"wrap_content\"\n                                    android:layout_marginStart=\"8dp\"\n                                    android:paddingLeft=\"5dp\"\n                                    android:paddingRight=\"5dp\"\n                                    android:text=\"@string/view_toc\"\n                                    android:textSize=\"13sp\"\n                                    app:radius=\"2dp\" />\n\n                            </LinearLayout>\n\n                        </LinearLayout>\n\n                        <io.legado.app.ui.widget.text.ScrollTextView\n                            android:id=\"@+id/tv_intro\"\n                            android:layout_width=\"match_parent\"\n                            android:layout_height=\"wrap_content\"\n                            android:layout_marginTop=\"8dp\"\n                            android:clickable=\"true\"\n                            android:focusable=\"true\"\n                            android:minHeight=\"48dp\"\n                            android:paddingLeft=\"8dp\"\n                            android:paddingBottom=\"8dp\"\n                            android:text=\"@string/book_intro\"\n                            android:textColor=\"@color/secondaryText\"\n                            android:textIsSelectable=\"true\"\n                            android:textSize=\"14sp\"\n                            android:visibility=\"visible\"\n                            tools:ignore=\"RtlHardcoded,RtlSymmetry\" />\n\n                    </LinearLayout>\n\n                </LinearLayout>\n\n            </io.legado.app.ui.widget.NoChildScrollNestedScrollView>\n\n        </androidx.swiperefreshlayout.widget.SwipeRefreshLayout>\n\n        <View\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"1px\"\n            android:background=\"@color/bg_divider_line\" />\n\n        <LinearLayout\n            android:id=\"@+id/fl_action\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:background=\"@color/background_menu\"\n            android:orientation=\"horizontal\"\n            app:layout_constraintBottom_toBottomOf=\"parent\">\n\n            <TextView\n                android:id=\"@+id/tv_shelf\"\n                android:layout_width=\"0dp\"\n                android:layout_height=\"50dp\"\n                android:layout_weight=\"1\"\n                android:background=\"?attr/selectableItemBackground\"\n                android:clickable=\"true\"\n                android:focusable=\"true\"\n                android:gravity=\"center\"\n                android:includeFontPadding=\"false\"\n                android:text=\"@string/remove_from_bookshelf\"\n                android:textColor=\"@color/primaryText\"\n                android:textSize=\"15sp\" />\n\n            <io.legado.app.ui.widget.text.AccentBgTextView\n                android:id=\"@+id/tv_read\"\n                android:layout_width=\"0dp\"\n                android:layout_height=\"50dp\"\n                android:layout_weight=\"1\"\n                android:gravity=\"center\"\n                android:includeFontPadding=\"false\"\n                android:text=\"@string/reading\"\n                android:textSize=\"15sp\" />\n\n        </LinearLayout>\n\n    </LinearLayout>\n\n</androidx.constraintlayout.widget.ConstraintLayout>\n"
  },
  {
    "path": "app/src/main/res/layout/activity_book_info_edit.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:orientation=\"vertical\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\">\n\n    <io.legado.app.ui.widget.TitleBar\n        android:id=\"@+id/title_bar\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        app:title=\"@string/book_info_edit\" />\n\n    <LinearLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:orientation=\"horizontal\"\n        android:padding=\"5dp\">\n\n        <io.legado.app.ui.widget.image.CoverImageView\n            android:id=\"@+id/iv_cover\"\n            android:layout_width=\"90dp\"\n            android:layout_height=\"130dp\"\n            android:contentDescription=\"@string/img_cover\"\n            android:scaleType=\"centerCrop\"\n            android:src=\"@drawable/image_cover_default\" />\n\n        <LinearLayout\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:orientation=\"vertical\"\n            android:padding=\"5dp\">\n\n            <io.legado.app.ui.widget.text.TextInputLayout\n                android:id=\"@+id/til_book_name\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:hint=\"@string/book_name\">\n\n                <io.legado.app.lib.theme.view.ThemeEditText\n                    android:id=\"@+id/tie_book_name\"\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"wrap_content\"\n                    android:singleLine=\"true\" />\n            </io.legado.app.ui.widget.text.TextInputLayout>\n\n            <io.legado.app.ui.widget.text.TextInputLayout\n                android:id=\"@+id/til_book_author\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:hint=\"@string/author\">\n\n                <io.legado.app.lib.theme.view.ThemeEditText\n                    android:id=\"@+id/tie_book_author\"\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"wrap_content\"\n                    android:singleLine=\"true\" />\n            </io.legado.app.ui.widget.text.TextInputLayout>\n\n        </LinearLayout>\n    </LinearLayout>\n\n    <io.legado.app.ui.widget.NoChildScrollNestedScrollView\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\">\n\n        <LinearLayout\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:orientation=\"vertical\">\n\n            <LinearLayout\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:orientation=\"horizontal\"\n                android:layout_gravity=\"center_vertical\">\n\n                <TextView\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"wrap_content\"\n                    android:text=\"@string/book_type\"\n                    android:layout_marginLeft=\"12dp\"\n                    android:layout_marginRight=\"3dp\"\n                    tools:ignore=\"RtlHardcoded\" />\n\n                <androidx.appcompat.widget.AppCompatSpinner\n                    android:id=\"@+id/sp_type\"\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"wrap_content\"\n                    android:entries=\"@array/book_type\"\n                    android:theme=\"@style/Spinner\" />\n\n            </LinearLayout>\n\n            <io.legado.app.ui.widget.text.TextInputLayout\n                android:id=\"@+id/til_cover_url\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginTop=\"5dp\"\n                android:hint=\"@string/cover_path\">\n\n                <io.legado.app.lib.theme.view.ThemeEditText\n                    android:id=\"@+id/tie_cover_url\"\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"wrap_content\" />\n            </io.legado.app.ui.widget.text.TextInputLayout>\n\n            <LinearLayout\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:orientation=\"horizontal\"\n                android:paddingStart=\"5dp\"\n                android:paddingEnd=\"5dp\">\n\n                <io.legado.app.ui.widget.text.StrokeTextView\n                    android:id=\"@+id/tv_select_cover\"\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"wrap_content\"\n                    android:padding=\"5dp\"\n                    android:text=\"@string/select_local_image\" />\n\n                <io.legado.app.ui.widget.text.StrokeTextView\n                    android:id=\"@+id/tv_change_cover\"\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"wrap_content\"\n                    android:layout_marginLeft=\"10dp\"\n                    android:padding=\"5dp\"\n                    android:text=\"@string/change_cover_source\"\n                    tools:ignore=\"RtlHardcoded\" />\n\n                <io.legado.app.ui.widget.text.StrokeTextView\n                    android:id=\"@+id/tv_refresh_cover\"\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"wrap_content\"\n                    android:layout_marginLeft=\"10dp\"\n                    android:padding=\"5dp\"\n                    android:text=\"@string/refresh_cover\"\n                    tools:ignore=\"RtlHardcoded\" />\n\n            </LinearLayout>\n\n            <io.legado.app.ui.widget.text.TextInputLayout\n                android:id=\"@+id/til_book_jj\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginTop=\"10dp\"\n                android:hint=\"@string/book_intro\">\n\n                <io.legado.app.lib.theme.view.ThemeEditText\n                    android:id=\"@+id/tie_book_intro\"\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"wrap_content\" />\n            </io.legado.app.ui.widget.text.TextInputLayout>\n\n        </LinearLayout>\n    </io.legado.app.ui.widget.NoChildScrollNestedScrollView>\n\n</LinearLayout>"
  },
  {
    "path": "app/src/main/res/layout/activity_book_read.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<FrameLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:orientation=\"vertical\">\n\n    <io.legado.app.ui.book.read.page.ReadView\n        android:id=\"@+id/read_view\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\" />\n\n    <View\n        android:id=\"@+id/text_menu_position\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"0dp\"\n        android:visibility=\"invisible\" />\n\n    <ImageView\n        android:id=\"@+id/cursor_left\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:contentDescription=\"@string/select_start\"\n        android:src=\"@drawable/ic_cursor_left\"\n        android:visibility=\"invisible\" />\n\n    <ImageView\n        android:id=\"@+id/cursor_right\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:contentDescription=\"@string/select_end\"\n        android:src=\"@drawable/ic_cursor_right\"\n        android:visibility=\"invisible\" />\n\n    <io.legado.app.ui.book.read.ReadMenu\n        android:id=\"@+id/read_menu\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:visibility=\"gone\" />\n\n    <io.legado.app.ui.book.read.SearchMenu\n        android:id=\"@+id/search_menu\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:visibility=\"gone\" />\n\n    <View\n        android:id=\"@+id/navigation_bar\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"0dp\"\n        android:background=\"@color/background_menu\"\n        android:layout_gravity=\"bottom\" />\n\n</FrameLayout>"
  },
  {
    "path": "app/src/main/res/layout/activity_book_search.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    tools:context=\".ui.book.search.SearchActivity\">\n\n    <io.legado.app.ui.widget.TitleBar\n        android:id=\"@+id/title_bar\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        app:contentInsetStartWithNavigation=\"0dp\"\n        app:contentLayout=\"@layout/view_search\"\n        app:layout_constraintTop_toTopOf=\"parent\"\n        app:title=\"搜索\" />\n\n    <io.legado.app.ui.widget.anima.RefreshProgressBar\n        android:id=\"@+id/refresh_progress_bar\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"2dp\"\n        app:layout_constraintTop_toBottomOf=\"@id/title_bar\" />\n\n    <io.legado.app.ui.widget.dynamiclayout.DynamicFrameLayout\n        android:id=\"@+id/content_view\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"0dp\"\n        app:layout_constraintBottom_toBottomOf=\"parent\"\n        app:layout_constraintTop_toBottomOf=\"@id/refresh_progress_bar\">\n\n        <androidx.recyclerview.widget.RecyclerView\n            android:id=\"@+id/recycler_view\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"match_parent\"\n            android:clipToPadding=\"false\"\n            tools:listitem=\"@layout/item_search\" />\n\n    </io.legado.app.ui.widget.dynamiclayout.DynamicFrameLayout>\n\n    <LinearLayout\n        android:id=\"@+id/ll_input_help\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"0dp\"\n        android:background=\"@color/background\"\n        android:clickable=\"true\"\n        android:focusable=\"true\"\n        android:orientation=\"vertical\"\n        android:visibility=\"gone\"\n        app:layout_constraintBottom_toBottomOf=\"parent\"\n        app:layout_constraintTop_toBottomOf=\"@id/title_bar\">\n\n        <TextView\n            android:id=\"@+id/tv_book_show\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:padding=\"6dp\"\n            android:text=\"@string/bookshelf\"\n            android:visibility=\"gone\" />\n\n        <androidx.recyclerview.widget.RecyclerView\n            android:id=\"@+id/rv_bookshelf_search\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:visibility=\"gone\" />\n\n        <LinearLayout\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:orientation=\"horizontal\">\n\n            <TextView\n                android:id=\"@+id/tv_history\"\n                android:layout_width=\"0dp\"\n                android:layout_height=\"wrap_content\"\n                android:layout_weight=\"1\"\n                android:padding=\"6dp\"\n                android:text=\"@string/searchHistory\" />\n\n            <TextView\n                android:id=\"@+id/tv_clear_history\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:background=\"?attr/selectableItemBackground\"\n                android:padding=\"6dp\"\n                android:text=\"@string/clear\" />\n\n        </LinearLayout>\n\n        <androidx.recyclerview.widget.RecyclerView\n            android:id=\"@+id/rv_history_key\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"match_parent\" />\n\n    </LinearLayout>\n\n    <com.google.android.material.floatingactionbutton.FloatingActionButton\n        android:id=\"@+id/fb_start_stop\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_margin=\"16dp\"\n        android:contentDescription=\"@string/stop\"\n        android:src=\"@drawable/ic_stop_black_24dp\"\n        android:visibility=\"invisible\"\n        app:fabSize=\"mini\"\n        app:layout_constraintBottom_toBottomOf=\"parent\"\n        app:layout_constraintRight_toRightOf=\"parent\" />\n\n</androidx.constraintlayout.widget.ConstraintLayout>\n\n"
  },
  {
    "path": "app/src/main/res/layout/activity_book_source.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:orientation=\"vertical\">\n\n    <io.legado.app.ui.widget.TitleBar\n        android:id=\"@+id/title_bar\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        app:contentInsetStartWithNavigation=\"0dp\"\n        app:contentLayout=\"@layout/view_search\"\n        app:displayHomeAsUp=\"true\"\n        app:title=\"@string/book_source\" />\n\n    <FrameLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"0dp\"\n        android:layout_weight=\"1\">\n\n        <io.legado.app.ui.widget.recycler.scroller.FastScrollRecyclerView\n            android:id=\"@+id/recycler_view\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"match_parent\"\n            app:layoutManager=\"androidx.recyclerview.widget.LinearLayoutManager\" />\n\n    </FrameLayout>\n\n    <io.legado.app.ui.widget.SelectActionBar\n        android:id=\"@+id/select_action_bar\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\" />\n\n</LinearLayout>"
  },
  {
    "path": "app/src/main/res/layout/activity_book_source_edit.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:orientation=\"vertical\">\n\n    <io.legado.app.ui.widget.TitleBar\n        android:id=\"@+id/title_bar\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        app:title=\"@string/edit_book_source\" />\n\n    <HorizontalScrollView\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:scrollbars=\"none\">\n\n        <LinearLayout\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_gravity=\"center_vertical\"\n            android:gravity=\"center_vertical\"\n            android:orientation=\"horizontal\"\n            android:paddingHorizontal=\"8dp\"\n            tools:ignore=\"VisualLintBounds\">\n\n            <TextView\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"match_parent\"\n                android:gravity=\"center\"\n                android:text=\"@string/book_type\"\n                tools:ignore=\"RtlHardcoded\" />\n\n            <androidx.appcompat.widget.AppCompatSpinner\n                android:id=\"@+id/sp_type\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:entries=\"@array/book_type\"\n                android:theme=\"@style/Spinner\"\n                tools:ignore=\"TouchTargetSizeCheck\" />\n\n            <io.legado.app.lib.theme.view.ThemeCheckBox\n                android:id=\"@+id/cb_is_enable\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:checked=\"true\"\n                android:text=\"@string/is_enable\" />\n\n            <io.legado.app.lib.theme.view.ThemeCheckBox\n                android:id=\"@+id/cb_is_enable_explore\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:checked=\"true\"\n                android:text=\"@string/discovery\" />\n\n            <io.legado.app.lib.theme.view.ThemeCheckBox\n                android:id=\"@+id/cb_is_enable_cookie\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:checked=\"true\"\n                android:text=\"@string/auto_save_cookie\" />\n\n            <io.legado.app.lib.theme.view.ThemeCheckBox\n                android:id=\"@+id/cb_is_enable_review\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:checked=\"false\"\n                android:text=\"@string/review\"\n                android:visibility=\"gone\" />\n\n        </LinearLayout>\n\n    </HorizontalScrollView>\n\n    <com.google.android.material.tabs.TabLayout\n        android:id=\"@+id/tab_layout\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"36dp\"\n        android:background=\"@color/background\"\n        android:elevation=\"3dp\"\n        tools:ignore=\"SpeakableTextPresentCheck\" />\n\n    <androidx.recyclerview.widget.RecyclerView\n        android:id=\"@+id/recycler_view\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:clipToPadding=\"false\"\n        tools:listitem=\"@layout/item_source_edit\" />\n\n</LinearLayout>"
  },
  {
    "path": "app/src/main/res/layout/activity_cache_book.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:orientation=\"vertical\">\n\n    <io.legado.app.ui.widget.TitleBar\n        android:id=\"@+id/title_bar\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        app:title=\"@string/offline_cache\" />\n\n    <androidx.recyclerview.widget.RecyclerView\n        android:id=\"@+id/recycler_view\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:clipToPadding=\"false\" />\n\n</LinearLayout>"
  },
  {
    "path": "app/src/main/res/layout/activity_chapter_list.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:orientation=\"vertical\">\n\n    <io.legado.app.ui.widget.TitleBar\n        android:id=\"@+id/title_bar\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        app:contentInsetStartWithNavigation=\"0dp\"\n        app:contentLayout=\"@layout/view_tab_layout\" />\n\n    <androidx.viewpager.widget.ViewPager\n        android:id=\"@+id/view_pager\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        tools:ignore=\"SpeakableTextPresentCheck\" />\n\n</LinearLayout>"
  },
  {
    "path": "app/src/main/res/layout/activity_config.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:orientation=\"vertical\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\">\n\n    <io.legado.app.ui.widget.TitleBar\n        android:id=\"@+id/title_bar\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        app:layout_constraintTop_toTopOf=\"parent\"\n        app:title=\"设置\" />\n\n    <LinearLayout\n        android:id=\"@+id/configFrameLayout\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:orientation=\"vertical\" />\n\n</LinearLayout>"
  },
  {
    "path": "app/src/main/res/layout/activity_dict_rule.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:orientation=\"vertical\"\n    tools:context=\".ui.replace.ReplaceRuleActivity\">\n\n    <io.legado.app.ui.widget.TitleBar\n        android:id=\"@+id/title_bar\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        app:title=\"@string/dict_rule\" />\n\n    <FrameLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"0dp\"\n        android:layout_weight=\"1\">\n\n        <io.legado.app.ui.widget.recycler.scroller.FastScrollRecyclerView\n            android:id=\"@+id/recycler_view\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"match_parent\"\n            tools:ignore=\"SpeakableTextPresentCheck\" />\n\n    </FrameLayout>\n\n    <io.legado.app.ui.widget.SelectActionBar\n        android:id=\"@+id/select_action_bar\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\" />\n\n</LinearLayout>\n"
  },
  {
    "path": "app/src/main/res/layout/activity_donate.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:id=\"@+id/ll_content\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:orientation=\"vertical\">\n\n    <io.legado.app.ui.widget.TitleBar\n        android:id=\"@+id/title_bar\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        app:subtitle=\"您的支持是我更新的动力\"\n        app:title=\"@string/donate\" />\n\n    <FrameLayout\n        android:id=\"@+id/fl_fragment\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\" />\n\n</LinearLayout>\n"
  },
  {
    "path": "app/src/main/res/layout/activity_explore_show.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    tools:context=\".ui.book.search.SearchActivity\">\n\n    <io.legado.app.ui.widget.TitleBar\n        android:id=\"@+id/title_bar\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        app:contentInsetStartWithNavigation=\"0dp\"\n        app:layout_constraintTop_toTopOf=\"parent\"\n        app:title=\"@string/discovery\" />\n\n    <io.legado.app.ui.widget.dynamiclayout.DynamicFrameLayout\n        android:id=\"@+id/content_view\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"0dp\"\n        app:layout_constraintBottom_toBottomOf=\"parent\"\n        app:layout_constraintTop_toBottomOf=\"@id/title_bar\">\n\n\n        <androidx.recyclerview.widget.RecyclerView\n            android:id=\"@+id/recycler_view\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"match_parent\"\n            android:clipToPadding=\"false\"\n            app:layoutManager=\"androidx.recyclerview.widget.LinearLayoutManager\"\n            tools:listitem=\"@layout/item_search\" />\n\n    </io.legado.app.ui.widget.dynamiclayout.DynamicFrameLayout>\n\n</androidx.constraintlayout.widget.ConstraintLayout>"
  },
  {
    "path": "app/src/main/res/layout/activity_file_manage.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\">\n\n    <io.legado.app.ui.widget.TitleBar\n        android:id=\"@+id/titleBar\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        app:contentInsetRight=\"24dp\"\n        app:contentLayout=\"@layout/view_search\"\n        app:layout_constraintTop_toTopOf=\"parent\" />\n\n    <!--path-->\n    <androidx.recyclerview.widget.RecyclerView\n        android:id=\"@+id/rv_path\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"24dp\"\n        android:background=\"@color/background_card\"\n        android:elevation=\"5dp\"\n        android:paddingLeft=\"10dp\"\n        android:paddingRight=\"10dp\"\n        app:layout_constraintTop_toBottomOf=\"@+id/titleBar\" />\n\n    <FrameLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"0dp\"\n        android:layout_weight=\"1\"\n        app:layout_constraintBottom_toBottomOf=\"parent\"\n        app:layout_constraintTop_toBottomOf=\"@id/rv_path\">\n\n        <io.legado.app.ui.widget.recycler.scroller.FastScrollRecyclerView\n            android:id=\"@+id/recycler_view\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"match_parent\"\n            android:clipToPadding=\"false\"\n            tools:ignore=\"SpeakableTextPresentCheck\" />\n\n        <TextView\n            android:id=\"@+id/tv_empty_msg\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_gravity=\"center\"\n            android:layout_margin=\"16dp\"\n            android:gravity=\"center\"\n            android:text=\"@string/empty\"\n            android:visibility=\"gone\"\n            tools:text=\"TextView\" />\n\n    </FrameLayout>\n\n</androidx.constraintlayout.widget.ConstraintLayout>"
  },
  {
    "path": "app/src/main/res/layout/activity_import_book.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\">\n\n    <io.legado.app.ui.widget.TitleBar\n        android:id=\"@+id/titleBar\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        app:layout_constraintTop_toTopOf=\"parent\"\n        app:contentInsetStartWithNavigation=\"0dp\"\n        app:contentLayout=\"@layout/view_search\" />\n \n    <!--path-->\n    <LinearLayout\n        android:id=\"@+id/lay_top\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:background=\"@color/background\"\n        android:elevation=\"1dp\"\n        android:gravity=\"center_vertical\"\n        android:minHeight=\"36dp\"\n        android:orientation=\"horizontal\"\n        android:padding=\"8dp\"\n        app:layout_constraintTop_toBottomOf=\"@id/titleBar\">\n\n        <TextView\n            android:id=\"@+id/tv_path\"\n            android:layout_width=\"0dp\"\n            android:layout_height=\"wrap_content\"\n            android:layout_weight=\"1\"\n            android:focusable=\"true\"\n            android:gravity=\"center_vertical\"\n            android:singleLine=\"true\"\n            android:textColor=\"@color/secondaryText\"\n            android:textSize=\"13sp\"\n            tools:text=\"/\" />\n\n        <io.legado.app.ui.widget.text.StrokeTextView\n            android:id=\"@+id/tv_go_back\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_gravity=\"center\"\n            android:layout_margin=\"4dp\"\n            android:gravity=\"center\"\n            android:paddingLeft=\"16dp\"\n            android:paddingRight=\"16dp\"\n            android:text=\"@string/go_back\"\n            android:textFontWeight=\"800\"\n            android:textSize=\"14sp\"\n            app:cornerRadius=\"5dp\"\n            tools:ignore=\"UnusedAttribute\" />\n    </LinearLayout>\n\n    <io.legado.app.ui.widget.anima.RefreshProgressBar\n        android:id=\"@+id/refresh_progress_bar\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"2dp\"\n        app:layout_constraintTop_toBottomOf=\"@id/lay_top\" />\n\n    <FrameLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"0dp\"\n        android:layout_weight=\"1\"\n        app:layout_constraintBottom_toTopOf=\"@id/select_action_bar\"\n        app:layout_constraintTop_toBottomOf=\"@id/refresh_progress_bar\">\n\n        <io.legado.app.ui.widget.recycler.scroller.FastScrollRecyclerView\n            android:id=\"@+id/recycler_view\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"match_parent\"\n            tools:ignore=\"SpeakableTextPresentCheck\" />\n\n        <TextView\n            android:id=\"@+id/tv_empty_msg\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_gravity=\"center\"\n            android:layout_margin=\"16dp\"\n            android:gravity=\"center\"\n            android:text=\"@string/empty\"\n            android:visibility=\"gone\"\n            tools:text=\"TextView\" />\n\n    </FrameLayout>\n\n    <io.legado.app.ui.widget.SelectActionBar\n        android:id=\"@+id/select_action_bar\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        app:layout_constraintBottom_toBottomOf=\"parent\" />\n\n</androidx.constraintlayout.widget.ConstraintLayout>"
  },
  {
    "path": "app/src/main/res/layout/activity_main.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:orientation=\"vertical\">\n\n    <androidx.viewpager.widget.ViewPager\n        android:id=\"@+id/view_pager_main\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"0dp\"\n        android:layout_weight=\"1\"\n        tools:ignore=\"SpeakableTextPresentCheck\" />\n\n    <io.legado.app.lib.theme.view.ThemeBottomNavigationVIew\n        android:id=\"@+id/bottom_navigation_view\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:background=\"@color/background\"\n        android:minHeight=\"50dp\"\n        app:itemActiveIndicatorStyle=\"@color/transparent\"\n        app:labelVisibilityMode=\"unlabeled\"\n        app:menu=\"@menu/main_bnv\" />\n\n</LinearLayout>"
  },
  {
    "path": "app/src/main/res/layout/activity_manga.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<FrameLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\">\n\n    <io.legado.app.ui.book.manga.recyclerview.WebtoonFrame\n        android:id=\"@+id/webtoon_frame\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\">\n\n        <io.legado.app.ui.book.manga.recyclerview.WebtoonRecyclerView\n            android:id=\"@+id/recycler_view\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"match_parent\" />\n\n    </io.legado.app.ui.book.manga.recyclerview.WebtoonFrame>\n\n    <io.legado.app.ui.widget.ReaderInfoBarView\n        android:id=\"@+id/infobar\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"20dp\"\n        android:layout_gravity=\"bottom\"\n        android:layout_marginBottom=\"10dp\"\n        android:paddingStart=\"10dp\"\n        android:paddingEnd=\"10dp\"\n        android:visibility=\"gone\"\n        tools:visibility=\"visible\" />\n\n    <io.legado.app.ui.book.read.MangaMenu\n        android:id=\"@+id/manga_menu\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:visibility=\"gone\"\n        tools:visibility=\"visible\" />\n\n    <FrameLayout\n        android:id=\"@+id/fl_loading\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:background=\"@color/background\">\n\n        <LinearLayout\n            android:id=\"@+id/ll_loading\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:layout_gravity=\"center\"\n            android:gravity=\"center\"\n            android:orientation=\"vertical\">\n\n            <ProgressBar\n                android:id=\"@+id/pb_loading\"\n                style=\"@style/Widget.Material3.CircularProgressIndicator.Small\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:indeterminate=\"true\"\n                app:indicatorColor=\"@color/white\"\n                app:indicatorSize=\"36dp\"\n                app:trackCornerRadius=\"99dp\"\n                app:trackThickness=\"3dp\" />\n\n            <TextView\n                android:id=\"@+id/tv_loading_message\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginTop=\"10dp\"\n                android:text=\"@string/loading\"\n                android:textColor=\"@color/primaryText\" />\n        </LinearLayout>\n\n        <LinearLayout\n            android:id=\"@+id/ll_retry\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"match_parent\"\n            android:gravity=\"center\"\n            android:orientation=\"vertical\"\n            android:visibility=\"gone\">\n\n            <TextView\n                android:id=\"@+id/tv_msg\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:textColor=\"@color/primaryText\" />\n\n            <io.legado.app.ui.widget.text.AccentTextView\n                android:id=\"@+id/tv_retry\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:layout_margin=\"16dp\"\n                android:text=\"重新加载\"\n                android:textSize=\"18sp\"\n                tools:visibility=\"visible\" />\n        </LinearLayout>\n\n    </FrameLayout>\n\n</FrameLayout>\n"
  },
  {
    "path": "app/src/main/res/layout/activity_qrcode_capture.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:orientation=\"vertical\">\n\n    <io.legado.app.ui.widget.TitleBar\n        android:id=\"@+id/title_bar\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        app:title=\"@string/scan_qr_code\" />\n\n    <FrameLayout\n        android:id=\"@+id/fl_content\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\" />\n\n</LinearLayout>"
  },
  {
    "path": "app/src/main/res/layout/activity_read_record.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:orientation=\"vertical\">\n\n    <io.legado.app.ui.widget.TitleBar\n        android:id=\"@+id/title_bar\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        app:contentLayout=\"@layout/view_search\"\n        app:title=\"@string/read_record\" />\n\n    <androidx.constraintlayout.widget.ConstraintLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:paddingLeft=\"10dp\"\n        android:paddingRight=\"10dp\">\n\n        <TextView\n            android:id=\"@+id/tv_book_name\"\n            android:layout_width=\"0dp\"\n            android:layout_height=\"wrap_content\"\n            android:padding=\"6dp\"\n            android:singleLine=\"true\"\n            android:textColor=\"@color/primaryText\"\n            app:layout_constraintLeft_toLeftOf=\"parent\"\n            app:layout_constraintRight_toLeftOf=\"@+id/tv_remove\"\n            app:layout_constraintTop_toTopOf=\"parent\"\n            tools:ignore=\"RtlHardcoded\"\n            tools:text=\"name\" />\n\n        <TextView\n            android:id=\"@+id/tv_reading_time\"\n            android:layout_width=\"0dp\"\n            android:layout_height=\"wrap_content\"\n            android:padding=\"6dp\"\n            android:singleLine=\"true\"\n            android:textColor=\"@color/secondaryText\"\n            app:layout_constraintLeft_toLeftOf=\"parent\"\n            app:layout_constraintRight_toLeftOf=\"@+id/tv_remove\"\n            app:layout_constraintTop_toBottomOf=\"@id/tv_book_name\"\n            tools:text=\"readingTime\" />\n\n        <TextView\n            android:id=\"@+id/tv_remove\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:background=\"?attr/selectableItemBackground\"\n            android:padding=\"6dp\"\n            android:text=\"@string/clear\"\n            android:textColor=\"@color/primaryText\"\n            app:layout_constraintBottom_toBottomOf=\"parent\"\n            app:layout_constraintRight_toRightOf=\"parent\"\n            app:layout_constraintTop_toTopOf=\"parent\" />\n\n    </androidx.constraintlayout.widget.ConstraintLayout>\n\n    <androidx.recyclerview.widget.RecyclerView\n        android:id=\"@+id/recycler_view\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:clipToPadding=\"false\"\n        app:layoutManager=\"androidx.recyclerview.widget.LinearLayoutManager\" />\n\n</LinearLayout>"
  },
  {
    "path": "app/src/main/res/layout/activity_replace_edit.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:id=\"@+id/root_view\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:orientation=\"vertical\">\n\n    <io.legado.app.ui.widget.TitleBar\n        android:id=\"@+id/title_bar\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        app:contentInsetStartWithNavigation=\"0dp\"\n        app:displayHomeAsUp=\"true\"\n        app:title=\"@string/replace_rule_edit\" />\n\n    <io.legado.app.ui.widget.NoChildScrollNestedScrollView\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\">\n\n        <LinearLayout\n            android:id=\"@+id/ll_content\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:orientation=\"vertical\"\n            android:padding=\"10dp\">\n\n            <io.legado.app.ui.widget.text.TextInputLayout\n                android:id=\"@+id/til_name\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:hint=\"@string/replace_rule_summary\">\n\n                <io.legado.app.lib.theme.view.ThemeEditText\n                    android:id=\"@+id/et_name\"\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"wrap_content\"\n                    tools:ignore=\"SpeakableTextPresentCheck,TouchTargetSizeCheck\" />\n            </io.legado.app.ui.widget.text.TextInputLayout>\n\n            <io.legado.app.ui.widget.text.TextInputLayout\n                android:id=\"@+id/til_group\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:hint=\"@string/group\">\n\n                <io.legado.app.lib.theme.view.ThemeEditText\n                    android:id=\"@+id/et_group\"\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"wrap_content\"\n                    tools:ignore=\"SpeakableTextPresentCheck,TouchTargetSizeCheck\" />\n            </io.legado.app.ui.widget.text.TextInputLayout>\n\n            <io.legado.app.ui.widget.text.TextInputLayout\n                android:id=\"@+id/til_replace_rule\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:hint=\"@string/replace_rule\">\n\n                <io.legado.app.lib.theme.view.ThemeEditText\n                    android:id=\"@+id/et_replace_rule\"\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"wrap_content\"\n                    tools:ignore=\"SpeakableTextPresentCheck,TouchTargetSizeCheck\" />\n            </io.legado.app.ui.widget.text.TextInputLayout>\n\n            <LinearLayout\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:orientation=\"horizontal\"\n                android:gravity=\"center_vertical\">\n\n                <io.legado.app.lib.theme.view.ThemeCheckBox\n                    android:id=\"@+id/cb_use_regex\"\n                    android:layout_width=\"0dp\"\n                    android:layout_height=\"wrap_content\"\n                    android:layout_weight=\"1\"\n                    android:text=\"@string/use_regex\"\n                    tools:ignore=\"TouchTargetSizeCheck\" />\n\n                <ImageView\n                    android:id=\"@+id/iv_help\"\n                    android:layout_width=\"24dp\"\n                    android:layout_height=\"24dp\"\n                    android:src=\"@drawable/ic_help\"\n                    app:tint=\"@color/primaryText\"\n                    android:contentDescription=\"@string/help\" />\n\n            </LinearLayout>\n\n            <io.legado.app.ui.widget.text.TextInputLayout\n                android:id=\"@+id/til_replace_to\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:hint=\"@string/replace_to\">\n\n                <io.legado.app.lib.theme.view.ThemeEditText\n                    android:id=\"@+id/et_replace_to\"\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"wrap_content\"\n                    tools:ignore=\"SpeakableTextPresentCheck,TouchTargetSizeCheck\" />\n            </io.legado.app.ui.widget.text.TextInputLayout>\n\n            <androidx.appcompat.widget.LinearLayoutCompat\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\">\n\n                <io.legado.app.lib.theme.view.ThemeCheckBox\n                    android:id=\"@+id/cb_scope_title\"\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"wrap_content\"\n                    android:checked=\"false\"\n                    android:text=\"@string/scope_title\"\n                    tools:ignore=\"TouchTargetSizeCheck\" />\n\n                <io.legado.app.lib.theme.view.ThemeCheckBox\n                    android:id=\"@+id/cb_scope_content\"\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"wrap_content\"\n                    android:layout_marginLeft=\"10dp\"\n                    android:checked=\"true\"\n                    android:text=\"@string/scope_content\"\n                    tools:ignore=\"RtlHardcoded,TouchTargetSizeCheck\" />\n\n            </androidx.appcompat.widget.LinearLayoutCompat>\n\n            <io.legado.app.ui.widget.text.TextInputLayout\n                android:id=\"@+id/til_scope\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:hint=\"@string/replace_scope\">\n\n                <io.legado.app.lib.theme.view.ThemeEditText\n                    android:id=\"@+id/et_scope\"\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"wrap_content\"\n                    tools:ignore=\"SpeakableTextPresentCheck,TouchTargetSizeCheck\" />\n            </io.legado.app.ui.widget.text.TextInputLayout>\n\n            <io.legado.app.ui.widget.text.TextInputLayout\n                android:id=\"@+id/til_exclude_scope\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:hint=\"@string/replace_exclude_scope\">\n\n                <io.legado.app.lib.theme.view.ThemeEditText\n                    android:id=\"@+id/et_exclude_scope\"\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"wrap_content\"\n                    tools:ignore=\"SpeakableTextPresentCheck,TouchTargetSizeCheck\" />\n            </io.legado.app.ui.widget.text.TextInputLayout>\n\n            <io.legado.app.ui.widget.text.TextInputLayout\n                android:id=\"@+id/til_timeout\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:hint=\"@string/timeout_millisecond\">\n\n                <io.legado.app.lib.theme.view.ThemeEditText\n                    android:id=\"@+id/et_timeout\"\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"wrap_content\"\n                    android:inputType=\"number\"\n                    tools:ignore=\"SpeakableTextPresentCheck,TouchTargetSizeCheck\" />\n            </io.legado.app.ui.widget.text.TextInputLayout>\n\n        </LinearLayout>\n    </io.legado.app.ui.widget.NoChildScrollNestedScrollView>\n\n</LinearLayout>"
  },
  {
    "path": "app/src/main/res/layout/activity_replace_rule.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:orientation=\"vertical\"\n    tools:context=\".ui.replace.ReplaceRuleActivity\">\n\n    <io.legado.app.ui.widget.TitleBar\n        android:id=\"@+id/title_bar\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        app:contentInsetStartWithNavigation=\"0dp\"\n        app:contentLayout=\"@layout/view_search\"\n        app:title=\"@string/replace_purify\" />\n\n    <FrameLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"0dp\"\n        android:layout_weight=\"1\">\n\n        <io.legado.app.ui.widget.recycler.scroller.FastScrollRecyclerView\n            android:id=\"@+id/recycler_view\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"match_parent\"\n            tools:ignore=\"SpeakableTextPresentCheck\" />\n\n    </FrameLayout>\n\n    <io.legado.app.ui.widget.SelectActionBar\n        android:id=\"@+id/select_action_bar\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\" />\n\n</LinearLayout>\n"
  },
  {
    "path": "app/src/main/res/layout/activity_rss_artivles.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:orientation=\"vertical\">\n\n    <io.legado.app.ui.widget.TitleBar\n        android:id=\"@+id/title_bar\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\" />\n\n    <com.google.android.material.tabs.TabLayout\n        android:id=\"@+id/tab_layout\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        app:tabMode=\"scrollable\" />\n\n    <androidx.viewpager.widget.ViewPager\n        android:id=\"@+id/view_pager\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        tools:ignore=\"SpeakableTextPresentCheck\" />\n\n</LinearLayout>"
  },
  {
    "path": "app/src/main/res/layout/activity_rss_favorites.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:orientation=\"vertical\">\n\n    <io.legado.app.ui.widget.TitleBar\n        android:id=\"@+id/title_bar\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        app:title=\"@string/favorites\" />\n\n    <com.google.android.material.tabs.TabLayout\n        android:id=\"@+id/tab_layout\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        app:tabMode=\"scrollable\" />\n\n    <androidx.viewpager.widget.ViewPager\n        android:id=\"@+id/view_pager\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        tools:ignore=\"SpeakableTextPresentCheck\" />\n\n</LinearLayout>"
  },
  {
    "path": "app/src/main/res/layout/activity_rss_read.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<FrameLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:orientation=\"vertical\">\n\n    <androidx.constraintlayout.widget.ConstraintLayout\n        android:id=\"@+id/ll_view\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:orientation=\"vertical\">\n\n        <io.legado.app.ui.widget.TitleBar\n            android:id=\"@+id/title_bar\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            app:layout_constraintTop_toTopOf=\"parent\" />\n\n        <io.legado.app.ui.rss.read.VisibleWebView\n            android:id=\"@+id/web_view\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"0dp\"\n            app:layout_constraintTop_toBottomOf=\"@+id/title_bar\"\n            app:layout_constraintBottom_toBottomOf=\"parent\" />\n\n        <io.legado.app.ui.widget.anima.RefreshProgressBar\n            android:id=\"@+id/progress_bar\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"1dp\"\n            app:max_progress=\"100\"\n            app:layout_constraintTop_toTopOf=\"@id/web_view\" />\n\n    </androidx.constraintlayout.widget.ConstraintLayout>\n\n    <FrameLayout\n        android:id=\"@+id/custom_web_view\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\" />\n\n</FrameLayout>"
  },
  {
    "path": "app/src/main/res/layout/activity_rss_source.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:orientation=\"vertical\">\n\n    <io.legado.app.ui.widget.TitleBar\n        android:id=\"@+id/title_bar\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        app:contentInsetStartWithNavigation=\"0dp\"\n        app:contentLayout=\"@layout/view_search\"\n        app:displayHomeAsUp=\"true\"\n        app:title=\"@string/rss_source\" />\n\n    <FrameLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"0dp\"\n        android:layout_weight=\"1\">\n\n        <io.legado.app.ui.widget.recycler.scroller.FastScrollRecyclerView\n            android:id=\"@+id/recycler_view\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"match_parent\"\n            app:layoutManager=\"androidx.recyclerview.widget.LinearLayoutManager\" />\n\n    </FrameLayout>\n\n    <io.legado.app.ui.widget.SelectActionBar\n        android:id=\"@+id/select_action_bar\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\" />\n\n</LinearLayout>"
  },
  {
    "path": "app/src/main/res/layout/activity_rss_source_edit.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:orientation=\"vertical\">\n\n    <io.legado.app.ui.widget.TitleBar\n        android:id=\"@+id/title_bar\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        app:contentInsetStartWithNavigation=\"0dp\"\n        app:displayHomeAsUp=\"true\"\n        app:title=\"@string/rss_source_edit\" />\n\n    <HorizontalScrollView\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:scrollbars=\"none\">\n\n        <LinearLayout\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_gravity=\"center_vertical\"\n            android:orientation=\"horizontal\"\n            android:paddingHorizontal=\"8dp\"\n            tools:ignore=\"VisualLintBounds\">\n\n            <io.legado.app.lib.theme.view.ThemeCheckBox\n                android:id=\"@+id/cb_is_enable\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:checked=\"true\"\n                android:text=\"@string/is_enable\"\n                tools:ignore=\"TouchTargetSizeCheck\" />\n\n            <io.legado.app.lib.theme.view.ThemeCheckBox\n                android:id=\"@+id/cb_single_url\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:checked=\"false\"\n                android:text=\"@string/single_url\"\n                tools:ignore=\"TouchTargetSizeCheck\" />\n\n            <io.legado.app.lib.theme.view.ThemeCheckBox\n                android:id=\"@+id/cb_is_enable_cookie\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:checked=\"true\"\n                android:text=\"@string/auto_save_cookie\"\n                tools:ignore=\"TouchTargetSizeCheck\" />\n\n        </LinearLayout>\n\n    </HorizontalScrollView>\n\n    <com.google.android.material.tabs.TabLayout\n        android:id=\"@+id/tab_layout\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"36dp\"\n        android:background=\"@color/background\"\n        android:elevation=\"3dp\"\n        tools:ignore=\"SpeakableTextPresentCheck\" />\n\n    <androidx.recyclerview.widget.RecyclerView\n        android:id=\"@+id/recycler_view\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:clipToPadding=\"false\"\n        app:layoutManager=\"io.legado.app.ui.widget.recycler.NoChildScrollLinearLayoutManager\"\n        tools:listitem=\"@layout/item_source_edit\" />\n\n</LinearLayout>"
  },
  {
    "path": "app/src/main/res/layout/activity_rule_sub.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:orientation=\"vertical\">\n\n    <io.legado.app.ui.widget.TitleBar\n        android:id=\"@+id/title_bar\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        app:title=\"@string/rule_subscription\" />\n\n    <FrameLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\">\n\n        <androidx.recyclerview.widget.RecyclerView\n            android:id=\"@+id/recycler_view\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"match_parent\"\n            android:clipToPadding=\"false\"\n            app:layoutManager=\"androidx.recyclerview.widget.LinearLayoutManager\"\n            tools:listitem=\"@layout/item_rule_sub\" />\n\n        <TextView\n            android:id=\"@+id/tv_empty_msg\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_gravity=\"center\"\n            android:layout_margin=\"16dp\"\n            android:gravity=\"center\"\n            android:text=\"@string/rule_sub_empty_msg\"\n            android:visibility=\"gone\"\n            app:layout_constraintBottom_toBottomOf=\"parent\"\n            app:layout_constraintLeft_toLeftOf=\"parent\"\n            app:layout_constraintRight_toRightOf=\"parent\"\n            app:layout_constraintTop_toBottomOf=\"@+id/title_bar\"\n            tools:text=\"TextView\" />\n\n    </FrameLayout>\n\n</LinearLayout>"
  },
  {
    "path": "app/src/main/res/layout/activity_search_content.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\">\n\n    <io.legado.app.ui.widget.TitleBar\n        android:id=\"@+id/title_bar\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        app:contentLayout=\"@layout/view_search\"\n        app:contentInsetRight=\"24dp\"\n        app:layout_constraintTop_toTopOf=\"parent\" />\n\n    <io.legado.app.ui.widget.anima.RefreshProgressBar\n        android:id=\"@+id/refresh_progress_bar\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"2dp\"\n        app:layout_constraintTop_toBottomOf=\"@id/title_bar\" />\n\n    <io.legado.app.ui.widget.recycler.scroller.FastScrollRecyclerView\n        android:id=\"@+id/recycler_view\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"0dp\"\n        android:overScrollMode=\"never\"\n        app:layout_constraintBottom_toTopOf=\"@+id/ll_search_base_info\"\n        app:layout_constraintTop_toBottomOf=\"@id/refresh_progress_bar\"\n        tools:ignore=\"SpeakableTextPresentCheck\" />\n\n    <LinearLayout\n        android:id=\"@+id/ll_search_base_info\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"36dp\"\n        android:background=\"@color/background\"\n        android:elevation=\"5dp\"\n        android:gravity=\"center_vertical\"\n        android:orientation=\"horizontal\"\n        android:paddingLeft=\"10dp\"\n        android:paddingRight=\"10dp\"\n        app:layout_constraintBottom_toBottomOf=\"parent\">\n\n        <TextView\n            android:id=\"@+id/tv_current_search_info\"\n            android:layout_width=\"0dp\"\n            android:layout_height=\"match_parent\"\n            android:layout_weight=\"1\"\n            android:background=\"?android:attr/selectableItemBackgroundBorderless\"\n            android:ellipsize=\"middle\"\n            android:gravity=\"center_vertical\"\n            android:paddingLeft=\"10dp\"\n            android:paddingRight=\"10dp\"\n            android:singleLine=\"true\"\n            android:textColor=\"@color/primaryText\"\n            android:textSize=\"12sp\" />\n\n        <Space\n            android:layout_width=\"20dp\"\n            android:layout_height=\"1dp\" />\n\n        <androidx.appcompat.widget.AppCompatImageView\n            android:id=\"@+id/iv_search_content_top\"\n            android:layout_width=\"36dp\"\n            android:layout_height=\"match_parent\"\n            android:background=\"?android:attr/selectableItemBackgroundBorderless\"\n            android:contentDescription=\"@string/go_to_top\"\n            android:src=\"@drawable/ic_arrow_drop_up\"\n            android:tooltipText=\"@string/go_to_top\"\n            app:tint=\"@color/primaryText\"\n            tools:ignore=\"UnusedAttribute\" />\n\n        <androidx.appcompat.widget.AppCompatImageView\n            android:id=\"@+id/iv_search_content_bottom\"\n            android:layout_width=\"36dp\"\n            android:layout_height=\"match_parent\"\n            android:background=\"?android:attr/selectableItemBackgroundBorderless\"\n            android:contentDescription=\"@string/go_to_bottom\"\n            android:src=\"@drawable/ic_arrow_drop_down\"\n            android:tooltipText=\"@string/go_to_bottom\"\n            app:tint=\"@color/primaryText\"\n            tools:ignore=\"UnusedAttribute\" />\n    </LinearLayout>\n\n    <com.google.android.material.floatingactionbutton.FloatingActionButton\n        android:id=\"@+id/fb_stop\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_margin=\"16dp\"\n        android:src=\"@drawable/ic_stop_black_24dp\"\n        android:visibility=\"invisible\"\n        android:contentDescription=\"@string/stop\"\n        app:fabSize=\"mini\"\n        app:layout_constraintBottom_toTopOf=\"@+id/ll_search_base_info\"\n        app:layout_constraintRight_toRightOf=\"parent\" />\n\n</androidx.constraintlayout.widget.ConstraintLayout>"
  },
  {
    "path": "app/src/main/res/layout/activity_source_debug.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:id=\"@+id/ll_content\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:orientation=\"vertical\">\n\n    <io.legado.app.ui.widget.TitleBar\n        android:id=\"@+id/title_bar\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        app:contentInsetStartWithNavigation=\"0dp\"\n        app:contentLayout=\"@layout/view_search\"\n        app:layout_constraintTop_toTopOf=\"parent\"\n        app:title=\"@string/debug_source\" />\n\n    <androidx.recyclerview.widget.RecyclerView\n        android:id=\"@+id/recycler_view\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"0dp\"\n        android:clipToPadding=\"false\"\n        app:layoutManager=\"io.legado.app.ui.widget.recycler.NoChildScrollLinearLayoutManager\"\n        app:layout_constraintBottom_toBottomOf=\"parent\"\n        app:layout_constraintTop_toBottomOf=\"@+id/title_bar\" />\n\n    <io.legado.app.ui.widget.anima.RotateLoading\n        android:id=\"@+id/rotate_loading\"\n        android:layout_width=\"36dp\"\n        android:layout_height=\"36dp\"\n        android:layout_margin=\"6dp\"\n        android:visibility=\"gone\"\n        app:layout_constraintLeft_toLeftOf=\"parent\"\n        app:layout_constraintRight_toRightOf=\"parent\"\n        app:layout_constraintTop_toBottomOf=\"@+id/title_bar\"\n        app:loading_width=\"2dp\" />\n\n    <io.legado.app.ui.widget.NoChildScrollNestedScrollView\n        android:id=\"@+id/help\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:visibility=\"gone\"\n        app:layout_constraintTop_toBottomOf=\"@+id/title_bar\">\n\n        <LinearLayout\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:background=\"@color/background\"\n            android:orientation=\"vertical\"\n            android:padding=\"16dp\">\n\n            <TextView\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:text=\"调试搜索>>输入关键字，如：\"\n                android:textColor=\"@color/secondaryText\"\n                tools:ignore=\"HardcodedText\" />\n\n            <LinearLayout\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:orientation=\"horizontal\">\n\n                <TextView\n                    android:id=\"@+id/text_my\"\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"wrap_content\"\n                    android:layout_margin=\"3dp\"\n                    android:background=\"@drawable/selector_fillet_btn_bg\"\n                    android:ellipsize=\"end\"\n                    android:gravity=\"center\"\n                    android:maxLines=\"1\"\n                    android:paddingLeft=\"12dp\"\n                    android:paddingTop=\"4dp\"\n                    android:paddingRight=\"12dp\"\n                    android:paddingBottom=\"4dp\"\n                    android:text=\"我的\"\n                    android:textColor=\"@color/primaryText\"\n                    android:textSize=\"14sp\"\n                    tools:ignore=\"HardcodedText,UnusedAttribute\" />\n\n                <TextView\n                    android:id=\"@+id/text_xt\"\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"wrap_content\"\n                    android:layout_margin=\"3dp\"\n                    android:background=\"@drawable/selector_fillet_btn_bg\"\n                    android:ellipsize=\"end\"\n                    android:gravity=\"center\"\n                    android:maxLines=\"1\"\n                    android:paddingLeft=\"12dp\"\n                    android:paddingTop=\"4dp\"\n                    android:paddingRight=\"12dp\"\n                    android:paddingBottom=\"4dp\"\n                    android:text=\"系统\"\n                    android:textColor=\"@color/primaryText\"\n                    android:textSize=\"14sp\"\n                    tools:ignore=\"HardcodedText,UnusedAttribute\" />\n            </LinearLayout>\n\n            <TextView\n                android:id=\"@+id/text_fx_title\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:text=\"调试发现>>输入发现URL，如：\"\n                android:textColor=\"@color/secondaryText\"\n                tools:ignore=\"HardcodedText\" />\n\n            <TextView\n                android:id=\"@+id/text_fx\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:layout_margin=\"3dp\"\n                android:background=\"@drawable/selector_fillet_btn_bg\"\n                android:ellipsize=\"end\"\n                android:gravity=\"center\"\n                android:maxLines=\"1\"\n                android:paddingLeft=\"12dp\"\n                android:paddingTop=\"4dp\"\n                android:paddingRight=\"12dp\"\n                android:paddingBottom=\"4dp\"\n                android:text=\"系统::http://xxx\"\n                android:textColor=\"@color/primaryText\"\n                android:textSize=\"14sp\"\n                tools:ignore=\"HardcodedText,UnusedAttribute\" />\n\n            <TextView\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:text=\"调试详情页>>输入详情页URL，如：\"\n                android:textColor=\"@color/secondaryText\"\n                tools:ignore=\"HardcodedText\" />\n\n            <TextView\n                android:id=\"@+id/text_info\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:layout_margin=\"3dp\"\n                android:background=\"@drawable/selector_fillet_btn_bg\"\n                android:ellipsize=\"end\"\n                android:gravity=\"center\"\n                android:maxLines=\"1\"\n                android:paddingLeft=\"12dp\"\n                android:paddingTop=\"4dp\"\n                android:paddingRight=\"12dp\"\n                android:paddingBottom=\"4dp\"\n                android:text=\"https://m.qidian.com/book/1015609210\"\n                android:textColor=\"@color/primaryText\"\n                android:textSize=\"14sp\"\n                tools:ignore=\"HardcodedText,UnusedAttribute\" />\n\n            <TextView\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:text=\"调试目录页>>输入目录页URL，如：\"\n                android:textColor=\"@color/secondaryText\"\n                tools:ignore=\"HardcodedText\" />\n\n            <TextView\n                android:id=\"@+id/text_toc\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:layout_margin=\"3dp\"\n                android:background=\"@drawable/selector_fillet_btn_bg\"\n                android:ellipsize=\"end\"\n                android:gravity=\"center\"\n                android:maxLines=\"1\"\n                android:paddingLeft=\"12dp\"\n                android:paddingTop=\"4dp\"\n                android:paddingRight=\"12dp\"\n                android:paddingBottom=\"4dp\"\n                android:text=\"++https://www.zhaishuyuan.com/read/30394\"\n                android:textColor=\"@color/primaryText\"\n                android:textSize=\"14sp\"\n                tools:ignore=\"HardcodedText,UnusedAttribute\" />\n\n            <TextView\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:text=\"调试正文页>>输入正文页URL，如：\"\n                android:textColor=\"@color/secondaryText\"\n                tools:ignore=\"HardcodedText\" />\n\n            <TextView\n                android:id=\"@+id/text_content\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:layout_margin=\"3dp\"\n                android:background=\"@drawable/selector_fillet_btn_bg\"\n                android:ellipsize=\"end\"\n                android:gravity=\"center\"\n                android:maxLines=\"1\"\n                android:paddingLeft=\"12dp\"\n                android:paddingTop=\"4dp\"\n                android:paddingRight=\"12dp\"\n                android:paddingBottom=\"4dp\"\n                android:text=\"--https://www.zhaishuyuan.com/chapter/30394/20940996\"\n                android:textColor=\"@color/primaryText\"\n                android:textSize=\"14sp\"\n                tools:ignore=\"HardcodedText,UnusedAttribute\" />\n\n        </LinearLayout>\n\n    </io.legado.app.ui.widget.NoChildScrollNestedScrollView>\n\n</androidx.constraintlayout.widget.ConstraintLayout>"
  },
  {
    "path": "app/src/main/res/layout/activity_source_login.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:id=\"@+id/fl_fragment\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:orientation=\"vertical\"\n    android:background=\"@color/transparent50\">\n\n</LinearLayout>"
  },
  {
    "path": "app/src/main/res/layout/activity_translucence.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:background=\"@color/transparent50\">\n\n    <io.legado.app.ui.widget.anima.RotateLoading\n        android:id=\"@+id/rotate_loading\"\n        android:layout_width=\"36dp\"\n        android:layout_height=\"36dp\"\n        android:layout_margin=\"6dp\"\n        android:visibility=\"gone\"\n        app:layout_constraintBottom_toBottomOf=\"parent\"\n        app:layout_constraintLeft_toLeftOf=\"parent\"\n        app:layout_constraintRight_toRightOf=\"parent\"\n        app:layout_constraintTop_toTopOf=\"parent\"\n        app:loading_width=\"2dp\" />\n\n</androidx.constraintlayout.widget.ConstraintLayout>"
  },
  {
    "path": "app/src/main/res/layout/activity_txt_toc_rule.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:orientation=\"vertical\">\n\n    <io.legado.app.ui.widget.TitleBar\n        android:id=\"@+id/title_bar\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        app:title=\"@string/txt_toc_rule\" />\n\n    <FrameLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"0dp\"\n        android:layout_weight=\"1\">\n\n        <io.legado.app.ui.widget.recycler.scroller.FastScrollRecyclerView\n            android:id=\"@+id/recycler_view\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"match_parent\"\n            app:layoutManager=\"androidx.recyclerview.widget.LinearLayoutManager\" />\n\n    </FrameLayout>\n\n    <io.legado.app.ui.widget.SelectActionBar\n        android:id=\"@+id/select_action_bar\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\" />\n\n</LinearLayout>"
  },
  {
    "path": "app/src/main/res/layout/activity_web_view.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<FrameLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:orientation=\"vertical\">\n\n    <androidx.constraintlayout.widget.ConstraintLayout\n        android:id=\"@+id/ll_view\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:orientation=\"vertical\">\n\n        <io.legado.app.ui.widget.TitleBar\n            android:id=\"@+id/title_bar\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            app:layout_constraintTop_toTopOf=\"parent\" />\n\n        <io.legado.app.ui.rss.read.VisibleWebView\n            android:id=\"@+id/web_view\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"0dp\"\n            app:layout_constraintTop_toBottomOf=\"@+id/title_bar\"\n            app:layout_constraintBottom_toBottomOf=\"parent\" />\n\n        <io.legado.app.ui.widget.anima.RefreshProgressBar\n            android:id=\"@+id/progress_bar\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"1dp\"\n            app:max_progress=\"100\"\n            app:layout_constraintTop_toTopOf=\"@id/web_view\" />\n\n    </androidx.constraintlayout.widget.ConstraintLayout>\n\n    <FrameLayout\n        android:id=\"@+id/custom_web_view\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\" />\n\n</FrameLayout>"
  },
  {
    "path": "app/src/main/res/layout/activity_welcome.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:orientation=\"vertical\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:gravity=\"center_horizontal\">\n\n    <androidx.constraintlayout.widget.ConstraintLayout\n        android:id=\"@+id/tv_legado\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        app:layout_constraintVertical_bias=\"0.4\"\n        app:layout_constraintLeft_toLeftOf=\"parent\"\n        app:layout_constraintRight_toRightOf=\"parent\"\n        app:layout_constraintTop_toTopOf=\"parent\"\n        app:layout_constraintBottom_toTopOf=\"@+id/iv_book\">\n\n        <View\n            android:id=\"@+id/vw_title_line\"\n            android:layout_width=\"6dp\"\n            android:layout_height=\"0dp\"\n            android:background=\"@color/accent\"\n            app:layout_constraintTop_toTopOf=\"@+id/tv_title\"\n            app:layout_constraintBottom_toBottomOf=\"@+id/tv_title\"\n            app:layout_constraintLeft_toLeftOf=\"parent\" />\n\n        <io.legado.app.ui.widget.text.AccentTextView\n            android:id=\"@+id/tv_title\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginLeft=\"6dp\"\n            android:ems=\"1\"\n            android:text=\"阅读\"\n            android:textSize=\"49sp\"\n            app:layout_constraintRight_toLeftOf=\"@+id/tv_sub_title\"\n            app:layout_constraintLeft_toRightOf=\"@+id/vw_title_line\"\n            app:layout_constraintTop_toTopOf=\"parent\"\n            tools:ignore=\"HardcodedText,RtlHardcoded\" />\n\n        <io.legado.app.ui.widget.text.AccentTextView\n            android:id=\"@+id/tv_sub_title\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginTop=\"60dp\"\n            android:layout_marginLeft=\"6dp\"\n            android:ems=\"1\"\n            android:text=\"享受美好时光\"\n            android:textSize=\"16sp\"\n            app:layout_constraintTop_toTopOf=\"@+id/tv_title\"\n            app:layout_constraintLeft_toRightOf=\"@id/tv_title\"\n            tools:ignore=\"HardcodedText,RtlHardcoded\" />\n\n\n    </androidx.constraintlayout.widget.ConstraintLayout>\n\n    <ImageView\n        android:id=\"@+id/iv_book\"\n        android:layout_width=\"120dp\"\n        android:layout_height=\"120dp\"\n        android:layout_marginBottom=\"32dp\"\n        android:src=\"@drawable/icon_read_book\"\n        android:scaleType=\"fitCenter\"\n        android:contentDescription=\"@string/welcome\"\n        app:layout_constraintBottom_toTopOf=\"@+id/tv_gzh\"\n        app:layout_constraintLeft_toLeftOf=\"parent\"\n        app:layout_constraintRight_toRightOf=\"parent\" />\n\n    <io.legado.app.ui.widget.text.AccentTextView\n        android:id=\"@+id/tv_gzh\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:text=\"关注公众号[开源阅读]\\n看文章点广告支持作者\"\n        android:layout_marginBottom=\"32dp\"\n        android:gravity=\"center_horizontal\"\n        app:layout_constraintBottom_toBottomOf=\"parent\"\n        app:layout_constraintLeft_toLeftOf=\"parent\"\n        app:layout_constraintRight_toRightOf=\"parent\"\n        tools:ignore=\"HardcodedText\" />\n\n</androidx.constraintlayout.widget.ConstraintLayout>"
  },
  {
    "path": "app/src/main/res/layout/dialog_add_to_bookshelf.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\">\n\n    <TextView\n        android:id=\"@+id/tv_title\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:padding=\"18dp\"\n        android:text=\"@string/add_to_bookshelf\"\n        android:textColor=\"@color/primaryText\"\n        android:textSize=\"16sp\"\n        android:textStyle=\"bold\"\n        app:layout_constraintTop_toTopOf=\"parent\" />\n\n    <androidx.constraintlayout.widget.ConstraintLayout\n        android:id=\"@+id/book_info\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginHorizontal=\"16dp\"\n        android:visibility=\"invisible\"\n        app:layout_constraintTop_toBottomOf=\"@id/tv_title\">\n\n        <TextView\n            android:id=\"@+id/tv_name\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:textSize=\"16sp\"\n            app:layout_constraintTop_toTopOf=\"parent\" />\n\n        <ImageView\n            android:id=\"@+id/iv_author\"\n            android:layout_width=\"18sp\"\n            android:layout_height=\"18sp\"\n            android:contentDescription=\"@string/author\"\n            android:paddingRight=\"2dp\"\n            android:src=\"@drawable/ic_author\"\n            app:layout_constraintBottom_toBottomOf=\"@+id/tv_author\"\n            app:layout_constraintLeft_toLeftOf=\"parent\"\n            app:layout_constraintTop_toTopOf=\"@id/tv_author\"\n            app:tint=\"@color/tv_text_summary\"\n            tools:ignore=\"RtlHardcoded,RtlSymmetry\" />\n\n        <TextView\n            android:id=\"@+id/tv_author\"\n            android:layout_width=\"0dp\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginLeft=\"2dp\"\n            app:layout_constraintLeft_toRightOf=\"@+id/iv_author\"\n            app:layout_constraintRight_toRightOf=\"parent\"\n            app:layout_constraintTop_toBottomOf=\"@+id/tv_name\"\n            tools:ignore=\"RtlHardcoded\" />\n\n        <ImageView\n            android:id=\"@+id/iv_origin\"\n            android:layout_width=\"18sp\"\n            android:layout_height=\"18sp\"\n            android:contentDescription=\"@string/origin_format\"\n            android:paddingRight=\"2dp\"\n            android:src=\"@drawable/ic_web_outline\"\n            app:layout_constraintBottom_toBottomOf=\"@+id/tv_origin\"\n            app:layout_constraintLeft_toLeftOf=\"parent\"\n            app:layout_constraintTop_toTopOf=\"@id/tv_origin\"\n            app:tint=\"@color/tv_text_summary\"\n            tools:ignore=\"RtlHardcoded,RtlSymmetry\" />\n\n        <TextView\n            android:id=\"@+id/tv_origin\"\n            android:layout_width=\"0dp\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginLeft=\"2dp\"\n            app:layout_constraintLeft_toRightOf=\"@+id/iv_origin\"\n            app:layout_constraintRight_toRightOf=\"parent\"\n            app:layout_constraintTop_toBottomOf=\"@+id/tv_author\"\n            tools:ignore=\"RtlHardcoded\" />\n\n    </androidx.constraintlayout.widget.ConstraintLayout>\n\n\n    <io.legado.app.ui.widget.anima.RotateLoading\n        android:id=\"@+id/rotate_loading\"\n        android:layout_width=\"48dp\"\n        android:layout_height=\"48dp\"\n        app:layout_constraintBottom_toBottomOf=\"@+id/book_info\"\n        app:layout_constraintLeft_toLeftOf=\"parent\"\n        app:layout_constraintRight_toRightOf=\"parent\"\n        app:layout_constraintTop_toTopOf=\"@id/book_info\" />\n\n    <io.legado.app.ui.widget.text.AccentTextView\n        android:id=\"@+id/tv_ok\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:padding=\"16dp\"\n        android:text=\"@string/ok\"\n        android:visibility=\"gone\"\n        app:layout_constraintBottom_toBottomOf=\"parent\"\n        app:layout_constraintRight_toRightOf=\"parent\"\n        app:layout_constraintTop_toBottomOf=\"@+id/book_info\" />\n\n    <TextView\n        android:id=\"@+id/tv_cancel\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:padding=\"16dp\"\n        android:text=\"@string/cancel\"\n        android:textColor=\"@color/secondaryText\"\n        app:layout_constraintBottom_toBottomOf=\"parent\"\n        app:layout_constraintRight_toLeftOf=\"@+id/tv_ok\"\n        app:layout_constraintTop_toBottomOf=\"@+id/book_info\" />\n\n    <io.legado.app.ui.widget.text.AccentTextView\n        android:id=\"@+id/tv_read\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:padding=\"16dp\"\n        android:text=\"@string/reading\"\n        android:visibility=\"invisible\"\n        app:layout_constraintBottom_toBottomOf=\"parent\"\n        app:layout_constraintLeft_toLeftOf=\"parent\" />\n\n</androidx.constraintlayout.widget.ConstraintLayout>"
  },
  {
    "path": "app/src/main/res/layout/dialog_auto_read.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:background=\"@color/background_menu\"\n    android:paddingLeft=\"16dp\"\n    android:paddingRight=\"16dp\"\n    android:paddingTop=\"6dp\"\n    android:paddingBottom=\"6dp\"\n    android:orientation=\"vertical\">\n\n    <LinearLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:orientation=\"vertical\">\n\n        <LinearLayout\n            android:id=\"@+id/ll_tts_SpeechRate\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:layout_gravity=\"center_vertical\"\n            android:orientation=\"horizontal\"\n            android:padding=\"8dp\">\n\n            <TextView\n                android:id=\"@+id/tv_read_speed_title\"\n                android:layout_width=\"0dp\"\n                android:layout_height=\"wrap_content\"\n                android:layout_gravity=\"center\"\n                android:layout_weight=\"1\"\n                android:text=\"@string/auto_page_speed\"\n                android:textColor=\"@color/primaryText\"\n                android:textSize=\"14sp\" />\n\n            <TextView\n                android:id=\"@+id/tv_read_speed\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\" />\n\n        </LinearLayout>\n\n        <io.legado.app.lib.theme.view.ThemeSeekBar\n            android:id=\"@+id/seek_auto_read\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginStart=\"15dp\"\n            android:layout_marginEnd=\"15dp\"\n            android:min=\"1\"\n            android:max=\"120\"\n            tools:ignore=\"UnusedAttribute\" />\n\n    </LinearLayout>\n\n    <LinearLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"50dp\"\n        android:layout_marginTop=\"8dp\"\n        android:baselineAligned=\"false\"\n        android:orientation=\"horizontal\">\n\n        <!--目录按钮-->\n        <LinearLayout\n            android:id=\"@+id/ll_catalog\"\n            android:layout_width=\"50dp\"\n            android:layout_height=\"50dp\"\n            android:background=\"?android:attr/selectableItemBackgroundBorderless\"\n            android:clickable=\"true\"\n            android:contentDescription=\"@string/chapter_list\"\n            android:focusable=\"true\"\n            android:orientation=\"vertical\"\n            android:paddingBottom=\"7dp\">\n\n            <androidx.appcompat.widget.AppCompatImageView\n                android:id=\"@+id/iv_catalog\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"0dp\"\n                android:layout_weight=\"1\"\n                android:contentDescription=\"@string/chapter_list\"\n                android:src=\"@drawable/ic_toc\"\n                app:tint=\"@color/primaryText\"\n                tools:ignore=\"NestedWeights\" />\n\n            <TextView\n                android:id=\"@+id/tv_catalog\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:layout_gravity=\"center_horizontal\"\n                android:layout_marginTop=\"3dp\"\n                android:text=\"@string/chapter_list\"\n                android:maxLines=\"1\"\n                android:textColor=\"@color/primaryText\"\n                android:textSize=\"12sp\" />\n        </LinearLayout>\n\n        <View\n            android:layout_width=\"0dp\"\n            android:layout_height=\"match_parent\"\n            android:layout_weight=\"2\" />\n        <!--调节按钮-->\n        <LinearLayout\n            android:id=\"@+id/ll_main_menu\"\n            android:layout_width=\"50dp\"\n            android:layout_height=\"50dp\"\n            android:background=\"?android:attr/selectableItemBackgroundBorderless\"\n            android:clickable=\"true\"\n            android:contentDescription=\"@string/main_menu\"\n            android:focusable=\"true\"\n            android:orientation=\"vertical\"\n            android:paddingBottom=\"7dp\">\n\n            <androidx.appcompat.widget.AppCompatImageView\n                android:id=\"@+id/iv_main_menu\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"0dp\"\n                android:layout_weight=\"1\"\n                android:contentDescription=\"@string/main_menu\"\n                android:src=\"@drawable/ic_menu\"\n                app:tint=\"@color/primaryText\"\n                tools:ignore=\"NestedWeights\" />\n\n            <TextView\n                android:id=\"@+id/tv_main_menu\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:layout_gravity=\"center_horizontal\"\n                android:layout_marginTop=\"3dp\"\n                android:text=\"@string/main_menu\"\n                android:maxLines=\"1\"\n                android:textColor=\"@color/primaryText\"\n                android:textSize=\"12sp\" />\n        </LinearLayout>\n\n        <View\n            android:layout_width=\"0dp\"\n            android:layout_height=\"match_parent\"\n            android:layout_weight=\"2\" />\n        <!--界面按钮-->\n        <LinearLayout\n            android:id=\"@+id/ll_auto_page_stop\"\n            android:layout_width=\"50dp\"\n            android:layout_height=\"50dp\"\n            android:background=\"?android:attr/selectableItemBackgroundBorderless\"\n            android:clickable=\"true\"\n            android:contentDescription=\"@string/stop\"\n            android:focusable=\"true\"\n            android:orientation=\"vertical\"\n            android:paddingBottom=\"7dp\">\n\n            <androidx.appcompat.widget.AppCompatImageView\n                android:id=\"@+id/iv_auto_page_stop\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"0dp\"\n                android:layout_weight=\"1\"\n                android:contentDescription=\"@string/stop\"\n                android:src=\"@drawable/ic_auto_page_stop\"\n                app:tint=\"@color/primaryText\"\n                tools:ignore=\"NestedWeights\" />\n\n            <TextView\n                android:id=\"@+id/tv_auto_page_stop\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:layout_gravity=\"center_horizontal\"\n                android:layout_marginTop=\"3dp\"\n                android:text=\"@string/stop\"\n                android:maxLines=\"1\"\n                android:textColor=\"@color/primaryText\"\n                android:textSize=\"12sp\" />\n        </LinearLayout>\n\n        <View\n            android:layout_width=\"0dp\"\n            android:layout_height=\"match_parent\"\n            android:layout_weight=\"2\" />\n        <!--设置按钮-->\n        <LinearLayout\n            android:id=\"@+id/ll_setting\"\n            android:layout_width=\"50dp\"\n            android:layout_height=\"50dp\"\n            android:background=\"?android:attr/selectableItemBackgroundBorderless\"\n            android:clickable=\"true\"\n            android:contentDescription=\"@string/setting\"\n            android:focusable=\"true\"\n            android:orientation=\"vertical\"\n            android:paddingBottom=\"7dp\">\n\n            <androidx.appcompat.widget.AppCompatImageView\n                android:id=\"@+id/iv_setting\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"0dp\"\n                android:layout_weight=\"1\"\n                android:contentDescription=\"@string/setting\"\n                android:src=\"@drawable/ic_settings\"\n                app:tint=\"@color/primaryText\"\n                tools:ignore=\"NestedWeights\" />\n\n            <TextView\n                android:id=\"@+id/tv_setting\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:layout_gravity=\"center_horizontal\"\n                android:layout_marginTop=\"3dp\"\n                android:text=\"@string/setting\"\n                android:maxLines=\"1\"\n                android:textColor=\"@color/primaryText\"\n                android:textSize=\"12sp\" />\n        </LinearLayout>\n\n    </LinearLayout>\n\n</LinearLayout>"
  },
  {
    "path": "app/src/main/res/layout/dialog_book_change_source.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:background=\"@color/background\">\n\n    <androidx.appcompat.widget.Toolbar\n        android:id=\"@+id/tool_bar\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:background=\"@color/background_menu\"\n        android:elevation=\"5dp\"\n        android:theme=\"?attr/actionBarStyle\"\n        app:contentInsetStartWithNavigation=\"0dp\"\n        app:displayHomeAsUp=\"false\"\n        app:fitStatusBar=\"false\"\n        app:layout_constraintTop_toTopOf=\"parent\"\n        app:popupTheme=\"@style/AppTheme.PopupOverlay\"\n        app:titleTextAppearance=\"@style/ToolbarTitle\" />\n\n    <io.legado.app.ui.widget.anima.RefreshProgressBar\n        android:id=\"@+id/refresh_progress_bar\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"2dp\"\n        app:layout_constraintTop_toBottomOf=\"@+id/tool_bar\" />\n\n    <io.legado.app.ui.widget.recycler.scroller.FastScrollRecyclerView\n        android:id=\"@+id/recycler_view\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"0dp\"\n        app:layoutManager=\"androidx.recyclerview.widget.LinearLayoutManager\"\n        app:layout_constraintBottom_toTopOf=\"@+id/ll_bottom_bar\"\n        app:layout_constraintTop_toBottomOf=\"@+id/refresh_progress_bar\" />\n\n    <LinearLayout\n        android:id=\"@+id/ll_bottom_bar\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"36dp\"\n        android:background=\"@color/background\"\n        android:gravity=\"center_vertical\"\n        android:orientation=\"horizontal\"\n        android:paddingLeft=\"10dp\"\n        android:paddingRight=\"10dp\"\n        app:layout_constraintBottom_toBottomOf=\"parent\">\n\n        <TextView\n            android:id=\"@+id/tv_dur\"\n            android:layout_width=\"0dp\"\n            android:layout_height=\"match_parent\"\n            android:layout_weight=\"1\"\n            android:background=\"?android:attr/selectableItemBackgroundBorderless\"\n            android:ellipsize=\"end\"\n            android:gravity=\"center_vertical\"\n            android:paddingLeft=\"10dp\"\n            android:paddingRight=\"10dp\"\n            android:singleLine=\"true\"\n            android:textColor=\"@color/primaryText\"\n            android:textSize=\"12sp\" />\n\n        <androidx.appcompat.widget.AppCompatImageView\n            android:id=\"@+id/iv_top\"\n            android:layout_width=\"36dp\"\n            android:layout_height=\"match_parent\"\n            android:background=\"?android:attr/selectableItemBackgroundBorderless\"\n            android:contentDescription=\"@string/go_to_top\"\n            android:src=\"@drawable/ic_arrow_drop_up\"\n            android:tooltipText=\"@string/go_to_top\"\n            app:tint=\"@color/primaryText\"\n            tools:ignore=\"UnusedAttribute\" />\n\n        <androidx.appcompat.widget.AppCompatImageView\n            android:id=\"@+id/iv_bottom\"\n            android:layout_width=\"36dp\"\n            android:layout_height=\"match_parent\"\n            android:background=\"?android:attr/selectableItemBackgroundBorderless\"\n            android:contentDescription=\"@string/go_to_bottom\"\n            android:src=\"@drawable/ic_arrow_drop_down\"\n            android:tooltipText=\"@string/go_to_bottom\"\n            app:tint=\"@color/primaryText\"\n            tools:ignore=\"UnusedAttribute\" />\n    </LinearLayout>\n\n\n</androidx.constraintlayout.widget.ConstraintLayout>"
  },
  {
    "path": "app/src/main/res/layout/dialog_book_group_edit.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:orientation=\"vertical\">\n\n    <androidx.appcompat.widget.Toolbar\n        android:id=\"@+id/tool_bar\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:background=\"@color/background_menu\"\n        android:elevation=\"5dp\"\n        android:theme=\"?attr/actionBarStyle\"\n        app:displayHomeAsUp=\"false\"\n        app:fitStatusBar=\"false\"\n        app:popupTheme=\"@style/AppTheme.PopupOverlay\"\n        app:title=\"@string/group_edit\"\n        app:titleTextAppearance=\"@style/ToolbarTitle\" />\n\n    <androidx.constraintlayout.widget.ConstraintLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:background=\"@color/background_menu\">\n\n        <io.legado.app.ui.widget.image.CoverImageView\n            android:id=\"@+id/iv_cover\"\n            android:layout_width=\"90dp\"\n            android:layout_height=\"126dp\"\n            android:layout_margin=\"6dp\"\n            android:contentDescription=\"@string/img_cover\"\n            android:scaleType=\"centerCrop\"\n            android:src=\"@drawable/image_cover_default\"\n            app:layout_constraintBottom_toBottomOf=\"parent\"\n            app:layout_constraintLeft_toLeftOf=\"parent\"\n            app:layout_constraintTop_toTopOf=\"parent\"\n            tools:ignore=\"ImageContrastCheck\" />\n\n\n        <io.legado.app.ui.widget.text.TextInputLayout\n            android:id=\"@+id/til_group_name\"\n            android:layout_width=\"0dp\"\n            android:layout_height=\"wrap_content\"\n            android:layout_margin=\"6dp\"\n            android:hint=\"@string/group_name\"\n            app:layout_constraintBottom_toTopOf=\"@+id/tv_sort\"\n            app:layout_constraintLeft_toRightOf=\"@+id/iv_cover\"\n            app:layout_constraintRight_toRightOf=\"parent\"\n            app:layout_constraintTop_toTopOf=\"parent\">\n\n            <io.legado.app.lib.theme.view.ThemeEditText\n                android:id=\"@+id/tie_group_name\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:maxLines=\"2\"\n                tools:ignore=\"TouchTargetSizeCheck,SpeakableTextPresentCheck\" />\n        </io.legado.app.ui.widget.text.TextInputLayout>\n\n        <io.legado.app.ui.widget.text.AccentTextView\n            android:id=\"@+id/tv_sort\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:padding=\"3dp\"\n            android:text=\"@string/sort\"\n            android:textColor=\"@color/primaryText\"\n            app:layout_constraintLeft_toLeftOf=\"@id/til_group_name\"\n            app:layout_constraintTop_toBottomOf=\"@id/til_group_name\" />\n\n        <androidx.appcompat.widget.AppCompatSpinner\n            android:id=\"@+id/sp_sort\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginLeft=\"6dp\"\n            android:entries=\"@array/book_sort\"\n            app:layout_constraintBottom_toBottomOf=\"@id/tv_sort\"\n            app:layout_constraintLeft_toRightOf=\"@+id/tv_sort\"\n            app:layout_constraintTop_toTopOf=\"@id/tv_sort\"\n            android:theme=\"@style/Spinner\"\n            tools:ignore=\"RtlHardcoded\" />\n\n        <io.legado.app.lib.theme.view.ThemeCheckBox\n            android:id=\"@+id/cb_enable_refresh\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:text=\"@string/allow_drop_down_refresh\"\n            android:checked=\"true\"\n            app:layout_constraintTop_toBottomOf=\"@+id/tv_sort\"\n            app:layout_constraintLeft_toLeftOf=\"@id/tv_sort\" />\n\n    </androidx.constraintlayout.widget.ConstraintLayout>\n\n    <LinearLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:orientation=\"horizontal\"\n        android:gravity=\"center_vertical\">\n\n        <io.legado.app.ui.widget.text.AccentTextView\n            android:id=\"@+id/btn_delete\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_margin=\"12dp\"\n            android:padding=\"6dp\"\n            android:text=\"@string/delete\"\n            tools:ignore=\"RtlHardcoded\" />\n\n        <Space\n            android:layout_width=\"0dp\"\n            android:layout_height=\"1dp\"\n            android:layout_weight=\"1\" />\n\n        <io.legado.app.ui.widget.text.AccentTextView\n            android:id=\"@+id/btn_cancel\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_margin=\"12dp\"\n            android:padding=\"6dp\"\n            android:text=\"@string/cancel\" />\n\n        <io.legado.app.ui.widget.text.AccentTextView\n            android:id=\"@+id/btn_ok\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_margin=\"12dp\"\n            android:padding=\"6dp\"\n            android:text=\"@string/ok\"\n            tools:ignore=\"RtlHardcoded\" />\n\n    </LinearLayout>\n</LinearLayout>\n"
  },
  {
    "path": "app/src/main/res/layout/dialog_book_group_picker.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:orientation=\"vertical\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\">\n\n    <androidx.appcompat.widget.Toolbar\n        android:id=\"@+id/tool_bar\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:theme=\"?attr/actionBarStyle\"\n        app:titleTextAppearance=\"@style/ToolbarTitle\"\n        app:popupTheme=\"@style/AppTheme.PopupOverlay\" />\n\n    <androidx.recyclerview.widget.RecyclerView\n        android:id=\"@+id/recycler_view\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"0dp\"\n        android:layout_weight=\"1\" />\n\n    <LinearLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\">\n\n        <Space\n            android:layout_width=\"0dp\"\n            android:layout_height=\"wrap_content\"\n            android:layout_weight=\"1\" />\n\n        <TextView\n            android:id=\"@+id/tv_cancel\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginRight=\"16dp\"\n            android:padding=\"12dp\"\n            android:text=\"@string/cancel\"\n            tools:ignore=\"RtlHardcoded\" />\n\n        <TextView\n            android:id=\"@+id/tv_ok\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginRight=\"16dp\"\n            android:padding=\"12dp\"\n            android:text=\"@string/ok\"\n            tools:ignore=\"RtlHardcoded\" />\n\n    </LinearLayout>\n</LinearLayout>"
  },
  {
    "path": "app/src/main/res/layout/dialog_bookmark.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<FrameLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:padding=\"16dp\">\n\n    <LinearLayout\n        android:id=\"@+id/vw_bg\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:layout_gravity=\"center\"\n        android:background=\"@drawable/shape_card_view\"\n        android:orientation=\"vertical\"\n        tools:ignore=\"UselessParent\">\n\n        <androidx.appcompat.widget.Toolbar\n            android:id=\"@+id/tool_bar\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:theme=\"?attr/actionBarStyle\"\n            app:popupTheme=\"@style/AppTheme.PopupOverlay\"\n            app:title=\"@string/bookmark\"\n            app:titleTextAppearance=\"@style/ToolbarTitle\" />\n\n        <TextView\n            android:id=\"@+id/tv_chapter_name\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:paddingLeft=\"16dp\"\n            android:paddingRight=\"16dp\"\n            android:textColor=\"@color/primaryText\" />\n\n        <androidx.core.widget.NestedScrollView\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"0dp\"\n            android:layout_weight=\"1\"\n            android:overScrollMode=\"ifContentScrolls\">\n\n            <LinearLayout\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:orientation=\"vertical\"\n                android:padding=\"16dp\">\n\n                <io.legado.app.ui.widget.text.TextInputLayout\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"wrap_content\"\n                    android:paddingTop=\"3dp\">\n\n                    <io.legado.app.lib.theme.view.ThemeEditText\n                        android:id=\"@+id/edit_book_text\"\n                        android:layout_width=\"match_parent\"\n                        android:layout_height=\"wrap_content\"\n                        android:hint=\"@string/content\"\n                        tools:ignore=\"SpeakableTextPresentCheck,TouchTargetSizeCheck\" />\n\n                </io.legado.app.ui.widget.text.TextInputLayout>\n\n                <io.legado.app.ui.widget.text.TextInputLayout\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"wrap_content\"\n                    android:paddingTop=\"3dp\">\n\n                    <io.legado.app.lib.theme.view.ThemeEditText\n                        android:id=\"@+id/edit_content\"\n                        android:layout_width=\"match_parent\"\n                        android:layout_height=\"wrap_content\"\n                        android:hint=\"@string/note_content\"\n                        tools:ignore=\"SpeakableTextPresentCheck,TouchTargetSizeCheck\" />\n\n                </io.legado.app.ui.widget.text.TextInputLayout>\n\n            </LinearLayout>\n\n        </androidx.core.widget.NestedScrollView>\n\n        <com.google.android.flexbox.FlexboxLayout\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:paddingLeft=\"12dp\"\n            android:paddingRight=\"12dp\"\n            app:flexWrap=\"wrap\"\n            app:justifyContent=\"space_between\">\n\n            <io.legado.app.ui.widget.text.AccentTextView\n                android:id=\"@+id/tv_footer_left\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:padding=\"12dp\"\n                android:text=\"@string/delete\"\n                android:visibility=\"invisible\"\n                tools:ignore=\"RtlHardcoded\" />\n\n            <LinearLayout\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:orientation=\"horizontal\">\n\n                <io.legado.app.ui.widget.text.AccentTextView\n                    android:id=\"@+id/tv_cancel\"\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"wrap_content\"\n                    android:padding=\"12dp\"\n                    android:text=\"@string/cancel\"\n                    tools:ignore=\"RtlHardcoded\" />\n\n                <io.legado.app.ui.widget.text.AccentTextView\n                    android:id=\"@+id/tv_ok\"\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"wrap_content\"\n                    android:padding=\"12dp\"\n                    android:text=\"@string/ok\"\n                    tools:ignore=\"RtlHardcoded\" />\n\n            </LinearLayout>\n\n        </com.google.android.flexbox.FlexboxLayout>\n    </LinearLayout>\n</FrameLayout>"
  },
  {
    "path": "app/src/main/res/layout/dialog_bookshelf_config.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:padding=\"16dp\">\n\n    <LinearLayout\n        android:id=\"@+id/ll_group_style\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:orientation=\"horizontal\"\n        app:layout_constraintTop_toTopOf=\"parent\">\n\n        <TextView\n            android:layout_width=\"0dp\"\n            android:layout_height=\"wrap_content\"\n            android:layout_weight=\"1\"\n            android:padding=\"6dp\"\n            android:text=\"@string/group_style\"\n            android:textColor=\"@color/primaryText\" />\n\n        <androidx.appcompat.widget.AppCompatSpinner\n            android:id=\"@+id/sp_group_style\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:entries=\"@array/group_style\"\n            android:theme=\"@style/Spinner\"\n            tools:ignore=\"TouchTargetSizeCheck\" />\n\n    </LinearLayout>\n\n    <io.legado.app.lib.theme.view.ThemeSwitch\n        android:id=\"@+id/sw_show_unread\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:padding=\"6dp\"\n        android:text=\"@string/show_unread\"\n        app:layout_constraintTop_toBottomOf=\"@+id/ll_group_style\"\n        tools:ignore=\"TouchTargetSizeCheck\" />\n\n    <io.legado.app.lib.theme.view.ThemeSwitch\n        android:id=\"@+id/sw_show_last_update_time\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:padding=\"6dp\"\n        android:text=\"@string/show_last_update_time\"\n        app:layout_constraintTop_toBottomOf=\"@+id/sw_show_unread\"\n        tools:ignore=\"TouchTargetSizeCheck\" />\n\n    <io.legado.app.lib.theme.view.ThemeSwitch\n        android:id=\"@+id/sw_show_wait_up_books\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:padding=\"6dp\"\n        android:text=\"@string/show_wait_up_count\"\n        app:layout_constraintTop_toBottomOf=\"@+id/sw_show_last_update_time\"\n        tools:ignore=\"TouchTargetSizeCheck\" />\n\n    <io.legado.app.lib.theme.view.ThemeSwitch\n        android:id=\"@+id/sw_show_bookshelf_fast_scroller\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:padding=\"6dp\"\n        android:text=\"@string/show_bookshelf_fast_scroller\"\n        app:layout_constraintTop_toBottomOf=\"@+id/sw_show_wait_up_books\"\n        tools:ignore=\"TouchTargetSizeCheck\" />\n\n    <LinearLayout\n        android:id=\"@+id/ll_layout\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"wrap_content\"\n        android:orientation=\"vertical\"\n        app:layout_constraintRight_toLeftOf=\"@+id/ll_sort\"\n        app:layout_constraintTop_toBottomOf=\"@+id/sw_show_bookshelf_fast_scroller\"\n        app:layout_constraintLeft_toLeftOf=\"parent\">\n\n        <io.legado.app.ui.widget.text.AccentTextView\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"match_parent\"\n            android:padding=\"6dp\"\n            android:text=\"@string/view\"\n            android:textSize=\"16sp\" />\n\n        <RadioGroup\n            android:id=\"@+id/rg_layout\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:orientation=\"vertical\">\n\n            <RadioButton\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:text=\"@string/layout_list\"\n                tools:ignore=\"TouchTargetSizeCheck\" />\n\n            <RadioButton\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:text=\"@string/layout_grid3\"\n                tools:ignore=\"TouchTargetSizeCheck\" />\n\n            <RadioButton\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:text=\"@string/layout_grid4\"\n                tools:ignore=\"TouchTargetSizeCheck\" />\n\n            <RadioButton\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:text=\"@string/layout_grid5\"\n                tools:ignore=\"TouchTargetSizeCheck\" />\n\n            <RadioButton\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:text=\"@string/layout_grid6\"\n                tools:ignore=\"TouchTargetSizeCheck\" />\n        </RadioGroup>\n\n    </LinearLayout>\n\n    <LinearLayout\n        android:id=\"@+id/ll_sort\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"wrap_content\"\n        android:orientation=\"vertical\"\n        app:layout_constraintLeft_toRightOf=\"@+id/ll_layout\"\n        app:layout_constraintTop_toBottomOf=\"@id/sw_show_bookshelf_fast_scroller\"\n        app:layout_constraintRight_toRightOf=\"parent\">\n\n        <io.legado.app.ui.widget.text.AccentTextView\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"match_parent\"\n            android:padding=\"6dp\"\n            android:text=\"@string/sort\"\n            android:textSize=\"16sp\" />\n\n        <RadioGroup\n            android:id=\"@+id/rg_sort\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:orientation=\"vertical\">\n\n            <RadioButton\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:text=\"@string/bookshelf_px_0\"\n                tools:ignore=\"TouchTargetSizeCheck\" />\n\n            <RadioButton\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:text=\"@string/bookshelf_px_1\"\n                tools:ignore=\"TouchTargetSizeCheck\" />\n\n            <RadioButton\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:text=\"@string/bookshelf_px_2\"\n                tools:ignore=\"TouchTargetSizeCheck\" />\n\n            <RadioButton\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:text=\"@string/bookshelf_px_3\"\n                tools:ignore=\"TouchTargetSizeCheck\" />\n\n            <RadioButton\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:text=\"@string/bookshelf_px_4\"\n                tools:ignore=\"TouchTargetSizeCheck\" />\n\n            <RadioButton\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:text=\"@string/bookshelf_px_5\"\n                tools:ignore=\"TouchTargetSizeCheck\" />\n\n        </RadioGroup>\n\n    </LinearLayout>\n\n</androidx.constraintlayout.widget.ConstraintLayout>"
  },
  {
    "path": "app/src/main/res/layout/dialog_change_cover.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:orientation=\"vertical\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\">\n\n    <androidx.appcompat.widget.Toolbar\n        android:id=\"@+id/tool_bar\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:elevation=\"5dp\"\n        android:theme=\"?attr/actionBarStyle\"\n        app:titleTextAppearance=\"@style/ToolbarTitle\"\n        app:popupTheme=\"@style/AppTheme.PopupOverlay\"\n        app:displayHomeAsUp=\"false\"\n        app:fitStatusBar=\"false\" />\n\n    <io.legado.app.ui.widget.anima.RefreshProgressBar\n        android:id=\"@+id/refresh_progress_bar\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"2dp\" />\n\n    <androidx.recyclerview.widget.RecyclerView\n        android:id=\"@+id/recycler_view\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\" />\n\n</LinearLayout>"
  },
  {
    "path": "app/src/main/res/layout/dialog_chapter_change_source.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:background=\"@color/background\">\n\n    <androidx.appcompat.widget.Toolbar\n        android:id=\"@+id/tool_bar\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:background=\"@color/background_menu\"\n        android:elevation=\"5dp\"\n        android:theme=\"?attr/actionBarStyle\"\n        app:displayHomeAsUp=\"false\"\n        app:fitStatusBar=\"false\"\n        app:layout_constraintTop_toTopOf=\"parent\"\n        app:popupTheme=\"@style/AppTheme.PopupOverlay\"\n        app:titleTextAppearance=\"@style/ToolbarTitle\" />\n\n    <io.legado.app.ui.widget.anima.RefreshProgressBar\n        android:id=\"@+id/refresh_progress_bar\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"2dp\"\n        app:layout_constraintTop_toBottomOf=\"@+id/tool_bar\" />\n\n    <io.legado.app.ui.widget.recycler.scroller.FastScrollRecyclerView\n        android:id=\"@+id/recycler_view\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"0dp\"\n        app:layoutManager=\"androidx.recyclerview.widget.LinearLayoutManager\"\n        app:layout_constraintBottom_toTopOf=\"@+id/ll_bottom_bar\"\n        app:layout_constraintTop_toBottomOf=\"@+id/refresh_progress_bar\" />\n\n    <LinearLayout\n        android:id=\"@+id/ll_bottom_bar\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"36dp\"\n        android:background=\"@color/background\"\n        android:gravity=\"center_vertical\"\n        android:orientation=\"horizontal\"\n        android:paddingLeft=\"10dp\"\n        android:paddingRight=\"10dp\"\n        app:layout_constraintBottom_toBottomOf=\"parent\">\n\n        <TextView\n            android:id=\"@+id/tv_dur\"\n            android:layout_width=\"0dp\"\n            android:layout_height=\"match_parent\"\n            android:layout_weight=\"1\"\n            android:background=\"?android:attr/selectableItemBackgroundBorderless\"\n            android:ellipsize=\"middle\"\n            android:gravity=\"center_vertical\"\n            android:paddingLeft=\"10dp\"\n            android:paddingRight=\"10dp\"\n            android:singleLine=\"true\"\n            android:textColor=\"@color/primaryText\"\n            android:textSize=\"12sp\" />\n\n        <androidx.appcompat.widget.AppCompatImageView\n            android:id=\"@+id/iv_top\"\n            android:layout_width=\"36dp\"\n            android:layout_height=\"match_parent\"\n            android:background=\"?android:attr/selectableItemBackgroundBorderless\"\n            android:contentDescription=\"@string/go_to_top\"\n            android:src=\"@drawable/ic_arrow_drop_up\"\n            android:tooltipText=\"@string/go_to_top\"\n            app:tint=\"@color/primaryText\"\n            tools:ignore=\"UnusedAttribute\" />\n\n        <androidx.appcompat.widget.AppCompatImageView\n            android:id=\"@+id/iv_bottom\"\n            android:layout_width=\"36dp\"\n            android:layout_height=\"match_parent\"\n            android:background=\"?android:attr/selectableItemBackgroundBorderless\"\n            android:contentDescription=\"@string/go_to_bottom\"\n            android:src=\"@drawable/ic_arrow_drop_down\"\n            android:tooltipText=\"@string/go_to_bottom\"\n            app:tint=\"@color/primaryText\"\n            tools:ignore=\"UnusedAttribute\" />\n    </LinearLayout>\n\n    <androidx.constraintlayout.widget.ConstraintLayout\n        android:id=\"@+id/cl_toc\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"0dp\"\n        android:background=\"@color/background\"\n        android:visibility=\"gone\"\n        app:layout_constraintBottom_toBottomOf=\"parent\"\n        app:layout_constraintTop_toBottomOf=\"@+id/tool_bar\">\n\n        <FrameLayout\n            android:id=\"@+id/fl_hide_toc\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:background=\"@color/background\"\n            android:elevation=\"4dp\"\n            app:layout_constraintTop_toTopOf=\"parent\">\n\n            <androidx.constraintlayout.utils.widget.ImageFilterView\n                android:id=\"@+id/iv_hide_toc\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"36dp\"\n                android:background=\"?android:attr/selectableItemBackground\"\n                android:src=\"@drawable/ic_arrow_down\"\n                app:tint=\"@color/primaryText\" />\n\n        </FrameLayout>\n\n        <androidx.recyclerview.widget.RecyclerView\n            android:id=\"@+id/recycler_view_toc\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"0dp\"\n            app:layoutManager=\"androidx.recyclerview.widget.LinearLayoutManager\"\n            app:layout_constraintBottom_toBottomOf=\"parent\"\n            app:layout_constraintTop_toBottomOf=\"@+id/fl_hide_toc\" />\n\n        <io.legado.app.ui.widget.anima.RotateLoading\n            android:id=\"@+id/loading_toc\"\n            android:layout_width=\"48dp\"\n            android:layout_height=\"48dp\"\n            android:visibility=\"gone\"\n            app:layout_constraintBottom_toBottomOf=\"@+id/recycler_view_toc\"\n            app:layout_constraintLeft_toLeftOf=\"@+id/recycler_view_toc\"\n            app:layout_constraintRight_toRightOf=\"@+id/recycler_view_toc\"\n            app:layout_constraintTop_toTopOf=\"@+id/recycler_view_toc\" />\n\n    </androidx.constraintlayout.widget.ConstraintLayout>\n\n</androidx.constraintlayout.widget.ConstraintLayout>"
  },
  {
    "path": "app/src/main/res/layout/dialog_check_source_config.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:orientation=\"vertical\">\n\n    <androidx.appcompat.widget.Toolbar\n        android:id=\"@+id/tool_bar\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:theme=\"?attr/actionBarStyle\"\n        app:title=\"@string/check_source_config\"\n        app:popupTheme=\"@style/AppTheme.PopupOverlay\"\n        app:titleTextAppearance=\"@style/ToolbarTitle\" />\n\n    <io.legado.app.ui.widget.text.TextInputLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:paddingLeft=\"12dp\"\n        android:paddingRight=\"12dp\"\n        android:paddingTop=\"3dp\">\n\n        <io.legado.app.lib.theme.view.ThemeEditText\n            android:id=\"@+id/check_source_timeout\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:hint=\"@string/check_source_timeout\"\n            android:digits=\"1234567890\"\n            tools:ignore=\"SpeakableTextPresentCheck,TouchTargetSizeCheck\" />\n\n    </io.legado.app.ui.widget.text.TextInputLayout>\n\n    <io.legado.app.ui.widget.text.AccentTextView\n        android:paddingLeft=\"12dp\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:text=\"@string/check_source_item\" />\n\n    <com.google.android.flexbox.FlexboxLayout\n        android:layout_width=\"match_parent\"\n        android:paddingLeft=\"12dp\"\n        android:paddingRight=\"12dp\"\n        android:layout_height=\"wrap_content\"\n        app:flexWrap=\"wrap\"\n        app:justifyContent=\"space_between\">\n\n        <io.legado.app.lib.theme.view.ThemeCheckBox\n            android:id=\"@+id/check_search\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:text=\"@string/search\" />\n        <io.legado.app.lib.theme.view.ThemeCheckBox\n            android:id=\"@+id/check_discovery\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:text=\"@string/discovery\" />\n        <io.legado.app.lib.theme.view.ThemeCheckBox\n            android:id=\"@+id/check_info\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:text=\"@string/source_tab_info\" />\n        <io.legado.app.lib.theme.view.ThemeCheckBox\n            android:id=\"@+id/check_category\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:text=\"@string/chapter_list\" />\n        <io.legado.app.lib.theme.view.ThemeCheckBox\n            android:id=\"@+id/check_content\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:text=\"@string/main_body\" />\n\n    </com.google.android.flexbox.FlexboxLayout>\n\n    <com.google.android.flexbox.FlexboxLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:paddingLeft=\"12dp\"\n        android:paddingRight=\"12dp\"\n        app:justifyContent=\"flex_end\">\n\n        <io.legado.app.ui.widget.text.AccentTextView\n            android:id=\"@+id/tv_cancel\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:padding=\"12dp\"\n            android:text=\"@string/cancel\"\n            tools:ignore=\"RtlHardcoded\" />\n\n        <io.legado.app.ui.widget.text.AccentTextView\n            android:id=\"@+id/tv_ok\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:padding=\"12dp\"\n            android:text=\"@string/ok\"\n            tools:ignore=\"RtlHardcoded\" />\n\n    </com.google.android.flexbox.FlexboxLayout>\n</LinearLayout>\n"
  },
  {
    "path": "app/src/main/res/layout/dialog_click_action_config.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:id=\"@+id/root_view\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:background=\"@color/translucent\"\n    android:padding=\"3dp\"\n    android:orientation=\"vertical\"\n    tools:ignore=\"NestedWeights\">\n\n    <LinearLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:background=\"@drawable/shape_translucent_card\"\n        android:gravity=\"center_vertical\"\n        android:orientation=\"horizontal\"\n        android:layout_margin=\"3dp\"\n        android:paddingLeft=\"16dp\"\n        android:paddingTop=\"6dp\"\n        android:paddingRight=\"16dp\"\n        android:paddingBottom=\"6dp\"\n        tools:ignore=\"UseCompoundDrawables\">\n\n        <TextView\n            android:layout_width=\"0dp\"\n            android:layout_height=\"wrap_content\"\n            android:layout_weight=\"1\"\n            android:text=\"@string/click_regional_config\"\n            android:textColor=\"@color/white\" />\n\n        <ImageView\n            android:id=\"@+id/iv_close\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:contentDescription=\"@string/close\"\n            android:src=\"@drawable/ic_baseline_close\"\n            app:tint=\"@color/white\" />\n\n    </LinearLayout>\n\n    <LinearLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"0dp\"\n        android:layout_weight=\"1\"\n        android:orientation=\"horizontal\">\n\n        <TextView\n            android:id=\"@+id/tv_top_left\"\n            android:layout_width=\"0dp\"\n            android:layout_height=\"match_parent\"\n            android:layout_margin=\"3dp\"\n            android:layout_weight=\"1\"\n            android:background=\"@drawable/shape_translucent_card\"\n            android:gravity=\"center\"\n            android:textColor=\"@color/white\"\n            tools:text=\"上一页\" />\n\n        <TextView\n            android:id=\"@+id/tv_top_center\"\n            android:layout_width=\"0dp\"\n            android:layout_height=\"match_parent\"\n            android:layout_margin=\"3dp\"\n            android:layout_weight=\"1\"\n            android:background=\"@drawable/shape_translucent_card\"\n            android:gravity=\"center\"\n            android:textColor=\"@color/white\"\n            tools:text=\"上一页\" />\n\n        <TextView\n            android:id=\"@+id/tv_top_right\"\n            android:layout_width=\"0dp\"\n            android:layout_height=\"match_parent\"\n            android:layout_margin=\"3dp\"\n            android:layout_weight=\"1\"\n            android:background=\"@drawable/shape_translucent_card\"\n            android:gravity=\"center\"\n            android:textColor=\"@color/white\"\n            tools:text=\"下一页\" />\n\n    </LinearLayout>\n\n    <LinearLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"0dp\"\n        android:layout_weight=\"1\"\n        android:orientation=\"horizontal\">\n\n        <TextView\n            android:id=\"@+id/tv_middle_left\"\n            android:layout_width=\"0dp\"\n            android:layout_height=\"match_parent\"\n            android:layout_margin=\"3dp\"\n            android:layout_weight=\"1\"\n            android:background=\"@drawable/shape_translucent_card\"\n            android:gravity=\"center\"\n            android:textColor=\"@color/white\"\n            tools:text=\"上一页\" />\n\n        <TextView\n            android:id=\"@+id/tv_middle_center\"\n            android:layout_width=\"0dp\"\n            android:layout_height=\"match_parent\"\n            android:layout_margin=\"3dp\"\n            android:layout_weight=\"1\"\n            android:background=\"@drawable/shape_translucent_card\"\n            android:gravity=\"center\"\n            android:text=\"@string/menu\"\n            android:textColor=\"@color/white\" />\n\n        <TextView\n            android:id=\"@+id/tv_middle_right\"\n            android:layout_width=\"0dp\"\n            android:layout_height=\"match_parent\"\n            android:layout_margin=\"3dp\"\n            android:layout_weight=\"1\"\n            android:background=\"@drawable/shape_translucent_card\"\n            android:gravity=\"center\"\n            android:textColor=\"@color/white\"\n            tools:text=\"下一页\" />\n\n    </LinearLayout>\n\n    <LinearLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"0dp\"\n        android:layout_weight=\"1\"\n        android:orientation=\"horizontal\">\n\n        <TextView\n            android:id=\"@+id/tv_bottom_left\"\n            android:layout_width=\"0dp\"\n            android:layout_height=\"match_parent\"\n            android:layout_margin=\"3dp\"\n            android:layout_weight=\"1\"\n            android:background=\"@drawable/shape_translucent_card\"\n            android:gravity=\"center\"\n            android:textColor=\"@color/white\"\n            tools:ignore=\"NestedWeights\"\n            tools:text=\"上一页\" />\n\n        <TextView\n            android:id=\"@+id/tv_bottom_center\"\n            android:layout_width=\"0dp\"\n            android:layout_height=\"match_parent\"\n            android:layout_margin=\"3dp\"\n            android:layout_weight=\"1\"\n            android:background=\"@drawable/shape_translucent_card\"\n            android:gravity=\"center\"\n            android:textColor=\"@color/white\"\n            tools:text=\"下一页\" />\n\n        <TextView\n            android:id=\"@+id/tv_bottom_right\"\n            android:layout_width=\"0dp\"\n            android:layout_height=\"match_parent\"\n            android:layout_margin=\"3dp\"\n            android:layout_weight=\"1\"\n            android:background=\"@drawable/shape_translucent_card\"\n            android:gravity=\"center\"\n            android:textColor=\"@color/white\"\n            tools:text=\"下一页\" />\n\n    </LinearLayout>\n\n</LinearLayout>"
  },
  {
    "path": "app/src/main/res/layout/dialog_code_view.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:background=\"@color/background\"\n    android:orientation=\"vertical\">\n\n    <androidx.appcompat.widget.Toolbar\n        android:id=\"@+id/tool_bar\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:theme=\"?attr/actionBarStyle\"\n        app:title=\"code edit\"\n        app:popupTheme=\"@style/AppTheme.PopupOverlay\"\n        app:titleTextAppearance=\"@style/ToolbarTitle\" />\n\n    <io.legado.app.ui.widget.code.CodeView\n        android:id=\"@+id/code_view\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:padding=\"12dp\" />\n\n</LinearLayout>\n\n\n"
  },
  {
    "path": "app/src/main/res/layout/dialog_content_edit.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:background=\"@color/background\"\n    android:orientation=\"vertical\">\n\n    <androidx.appcompat.widget.Toolbar\n        android:id=\"@+id/tool_bar\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:theme=\"?attr/actionBarStyle\"\n        app:popupTheme=\"@style/AppTheme.PopupOverlay\"\n        app:title=\"edit\"\n        app:titleTextAppearance=\"@style/ToolbarTitle\"\n        app:layout_constraintTop_toTopOf=\"parent\" />\n\n    <io.legado.app.lib.theme.view.ThemeEditText\n        android:id=\"@+id/content_view\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"0dp\"\n        android:padding=\"12dp\"\n        android:importantForAutofill=\"no\"\n        app:layout_constraintTop_toBottomOf=\"@+id/tool_bar\"\n        app:layout_constraintBottom_toBottomOf=\"parent\" />\n\n    <io.legado.app.ui.widget.anima.RotateLoading\n        android:id=\"@+id/rl_loading\"\n        android:layout_width=\"60dp\"\n        android:layout_height=\"60dp\"\n        android:visibility=\"invisible\"\n        app:layout_constraintTop_toBottomOf=\"@+id/tool_bar\"\n        app:layout_constraintBottom_toBottomOf=\"parent\"\n        app:layout_constraintLeft_toLeftOf=\"parent\"\n        app:layout_constraintRight_toRightOf=\"parent\" />\n\n</androidx.constraintlayout.widget.ConstraintLayout>"
  },
  {
    "path": "app/src/main/res/layout/dialog_cover_rule_config.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:orientation=\"vertical\">\n\n    <androidx.appcompat.widget.Toolbar\n        android:id=\"@+id/tool_bar\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:theme=\"?attr/actionBarStyle\"\n        app:title=\"@string/cover_config\"\n        app:popupTheme=\"@style/AppTheme.PopupOverlay\"\n        app:titleTextAppearance=\"@style/ToolbarTitle\" />\n\n    <androidx.core.widget.NestedScrollView\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"0dp\"\n        android:layout_weight=\"1\">\n\n        <LinearLayout\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:orientation=\"vertical\"\n            android:padding=\"16dp\">\n\n            <io.legado.app.lib.theme.view.ThemeCheckBox\n                android:id=\"@+id/cb_enable\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:text=\"@string/enable\" />\n\n            <io.legado.app.ui.widget.text.TextInputLayout\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:paddingTop=\"3dp\">\n\n                <io.legado.app.lib.theme.view.ThemeEditText\n                    android:id=\"@+id/edit_search_url\"\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"wrap_content\"\n                    android:hint=\"@string/r_search_url\"\n                    tools:ignore=\"SpeakableTextPresentCheck,TouchTargetSizeCheck\" />\n\n            </io.legado.app.ui.widget.text.TextInputLayout>\n\n            <io.legado.app.ui.widget.text.TextInputLayout\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:paddingTop=\"3dp\">\n\n                <io.legado.app.lib.theme.view.ThemeEditText\n                    android:id=\"@+id/edit_cover_url_rule\"\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"wrap_content\"\n                    android:hint=\"@string/rule_cover_url\"\n                    tools:ignore=\"SpeakableTextPresentCheck,TouchTargetSizeCheck\" />\n\n            </io.legado.app.ui.widget.text.TextInputLayout>\n\n        </LinearLayout>\n\n    </androidx.core.widget.NestedScrollView>\n\n    <com.google.android.flexbox.FlexboxLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:paddingLeft=\"12dp\"\n        android:paddingRight=\"12dp\"\n        app:flexWrap=\"wrap\"\n        app:justifyContent=\"space_between\">\n\n        <io.legado.app.ui.widget.text.AccentTextView\n            android:id=\"@+id/tv_footer_left\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:padding=\"12dp\"\n            android:text=\"@string/btn_default_s\"\n            tools:ignore=\"RtlHardcoded\" />\n\n        <LinearLayout\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:orientation=\"horizontal\">\n\n            <TextView\n                android:id=\"@+id/tv_cancel\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:padding=\"12dp\"\n                android:text=\"@string/cancel\"\n                android:textColor=\"@color/secondaryText\"\n                tools:ignore=\"RtlHardcoded\" />\n\n            <io.legado.app.ui.widget.text.AccentTextView\n                android:id=\"@+id/tv_ok\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:padding=\"12dp\"\n                android:text=\"@string/ok\"\n                tools:ignore=\"RtlHardcoded\" />\n\n        </LinearLayout>\n\n    </com.google.android.flexbox.FlexboxLayout>\n\n</LinearLayout>"
  },
  {
    "path": "app/src/main/res/layout/dialog_custom_group.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.core.widget.NestedScrollView xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:paddingLeft=\"20dp\"\n    android:paddingRight=\"20dp\"\n    android:paddingTop=\"12dp\"\n    android:overScrollMode=\"ifContentScrolls\">\n\n    <LinearLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:orientation=\"vertical\">\n\n        <androidx.constraintlayout.widget.ConstraintLayout\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:paddingTop=\"6dp\"\n            android:paddingBottom=\"6dp\">\n\n            <TextView\n                android:id=\"@+id/tv_add_group_title\"\n                android:layout_width=\"0dp\"\n                android:layout_height=\"wrap_content\"\n                android:text=\"@string/add_group\"\n                android:textColor=\"@color/primaryText\"\n                app:layout_constraintTop_toTopOf=\"parent\"\n                app:layout_constraintLeft_toLeftOf=\"parent\"\n                app:layout_constraintRight_toLeftOf=\"@+id/sw_add_group\" />\n\n            <TextView\n                android:id=\"@+id/tv_add_group_s\"\n                android:layout_width=\"0dp\"\n                android:layout_height=\"wrap_content\"\n                android:text=\"@string/custom_group_summary\"\n                android:textColor=\"@color/secondaryText\"\n                app:layout_constraintTop_toBottomOf=\"@+id/tv_add_group_title\"\n                app:layout_constraintLeft_toLeftOf=\"parent\"\n                app:layout_constraintRight_toLeftOf=\"@+id/sw_add_group\" />\n\n            <io.legado.app.lib.theme.view.ThemeSwitch\n                android:id=\"@+id/sw_add_group\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:textOff=\"@string/replace\"\n                android:textOn=\"@string/add\"\n                app:layout_constraintBottom_toBottomOf=\"parent\"\n                app:layout_constraintRight_toRightOf=\"parent\"\n                app:layout_constraintTop_toTopOf=\"parent\"\n                tools:ignore=\"SpeakableTextPresentCheck\" />\n\n        </androidx.constraintlayout.widget.ConstraintLayout>\n\n        <io.legado.app.ui.widget.text.TextInputLayout\n            android:id=\"@+id/text_input_layout\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:paddingTop=\"6dp\">\n\n            <io.legado.app.ui.widget.text.AutoCompleteTextView\n                android:id=\"@+id/edit_view\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                tools:ignore=\"SpeakableTextPresentCheck,TouchTargetSizeCheck\" />\n\n        </io.legado.app.ui.widget.text.TextInputLayout>\n\n    </LinearLayout>\n\n</androidx.core.widget.NestedScrollView>"
  },
  {
    "path": "app/src/main/res/layout/dialog_dict.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\">\n\n    <com.google.android.material.tabs.TabLayout\n        android:id=\"@+id/tab_layout\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:paddingBottom=\"16dp\"\n        app:layout_constraintTop_toTopOf=\"parent\"\n        tools:ignore=\"SpeakableTextPresentCheck\" />\n\n    <io.legado.app.ui.widget.text.ScrollTextView\n        android:id=\"@+id/tv_dict\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:padding=\"16dp\"\n        android:textColor=\"@color/secondaryText\"\n        app:layout_constraintBottom_toBottomOf=\"parent\"\n        app:layout_constraintTop_toBottomOf=\"@id/tab_layout\" />\n\n    <io.legado.app.ui.widget.anima.RotateLoading\n        android:id=\"@+id/rotate_loading\"\n        android:layout_width=\"60dp\"\n        android:layout_height=\"60dp\"\n        android:layout_gravity=\"center\"\n        app:layout_constraintLeft_toLeftOf=\"parent\"\n        app:layout_constraintRight_toRightOf=\"parent\"\n        app:layout_constraintTop_toBottomOf=\"@id/tab_layout\" />\n\n</androidx.constraintlayout.widget.ConstraintLayout>"
  },
  {
    "path": "app/src/main/res/layout/dialog_dict_rule_edit.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<FrameLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:padding=\"16dp\">\n\n    <LinearLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:layout_gravity=\"center\"\n        android:background=\"@drawable/shape_card_view\"\n        android:orientation=\"vertical\"\n        tools:ignore=\"UselessParent\">\n\n        <androidx.appcompat.widget.Toolbar\n            android:id=\"@+id/tool_bar\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:background=\"@color/background_menu\"\n            android:elevation=\"5dp\"\n            android:theme=\"?attr/actionBarStyle\"\n            app:displayHomeAsUp=\"false\"\n            app:fitStatusBar=\"false\"\n            app:popupTheme=\"@style/AppTheme.PopupOverlay\"\n            app:title=\"@string/dict_rule\"\n            app:titleTextAppearance=\"@style/ToolbarTitle\" />\n\n        <androidx.core.widget.NestedScrollView\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:paddingTop=\"6dp\"\n            android:overScrollMode=\"ifContentScrolls\">\n\n            <LinearLayout\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:orientation=\"vertical\">\n\n                <io.legado.app.ui.widget.text.TextInputLayout\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"wrap_content\"\n                    android:hint=\"@string/name\">\n\n                    <io.legado.app.lib.theme.view.ThemeEditText\n                        android:id=\"@+id/tv_rule_name\"\n                        android:layout_width=\"match_parent\"\n                        android:layout_height=\"wrap_content\"\n                        tools:ignore=\"SpeakableTextPresentCheck,TouchTargetSizeCheck\" />\n\n                </io.legado.app.ui.widget.text.TextInputLayout>\n\n                <io.legado.app.ui.widget.text.TextInputLayout\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"wrap_content\"\n                    android:hint=\"@string/url_rule\">\n\n                    <io.legado.app.lib.theme.view.ThemeEditText\n                        android:id=\"@+id/tv_url_rule\"\n                        android:layout_width=\"match_parent\"\n                        android:layout_height=\"wrap_content\"\n                        tools:ignore=\"SpeakableTextPresentCheck,TouchTargetSizeCheck\" />\n\n                </io.legado.app.ui.widget.text.TextInputLayout>\n\n                <io.legado.app.ui.widget.text.TextInputLayout\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"wrap_content\"\n                    android:hint=\"@string/show_rule\">\n\n                    <io.legado.app.lib.theme.view.ThemeEditText\n                        android:id=\"@+id/tv_show_rule\"\n                        android:layout_width=\"match_parent\"\n                        android:layout_height=\"wrap_content\"\n                        tools:ignore=\"SpeakableTextPresentCheck,TouchTargetSizeCheck\" />\n\n                </io.legado.app.ui.widget.text.TextInputLayout>\n\n            </LinearLayout>\n\n        </androidx.core.widget.NestedScrollView>\n\n    </LinearLayout>\n\n</FrameLayout>"
  },
  {
    "path": "app/src/main/res/layout/dialog_direct_link_upload_config.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:orientation=\"vertical\">\n\n    <androidx.appcompat.widget.Toolbar\n        android:id=\"@+id/tool_bar\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:theme=\"?attr/actionBarStyle\"\n        app:title=\"@string/direct_link_upload_config\"\n        app:popupTheme=\"@style/AppTheme.PopupOverlay\"\n        app:titleTextAppearance=\"@style/ToolbarTitle\" />\n\n    <androidx.core.widget.NestedScrollView\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"0dp\"\n        android:layout_weight=\"1\">\n\n        <LinearLayout\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:orientation=\"vertical\"\n            android:padding=\"16dp\">\n\n            <io.legado.app.ui.widget.text.TextInputLayout\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:paddingTop=\"3dp\">\n\n                <io.legado.app.lib.theme.view.ThemeEditText\n                    android:id=\"@+id/edit_upload_url\"\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"wrap_content\"\n                    android:hint=\"@string/upload_url\"\n                    tools:ignore=\"SpeakableTextPresentCheck,TouchTargetSizeCheck\" />\n\n            </io.legado.app.ui.widget.text.TextInputLayout>\n\n            <io.legado.app.ui.widget.text.TextInputLayout\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:paddingTop=\"3dp\">\n\n                <io.legado.app.lib.theme.view.ThemeEditText\n                    android:id=\"@+id/edit_download_url_rule\"\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"wrap_content\"\n                    android:hint=\"@string/download_url_rule\"\n                    tools:ignore=\"SpeakableTextPresentCheck,TouchTargetSizeCheck\" />\n\n            </io.legado.app.ui.widget.text.TextInputLayout>\n\n            <io.legado.app.ui.widget.text.TextInputLayout\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:paddingTop=\"3dp\">\n\n                <io.legado.app.lib.theme.view.ThemeEditText\n                    android:id=\"@+id/edit_summary\"\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"wrap_content\"\n                    android:hint=\"@string/summary\"\n                    tools:ignore=\"SpeakableTextPresentCheck,TouchTargetSizeCheck\" />\n\n            </io.legado.app.ui.widget.text.TextInputLayout>\n\n            <io.legado.app.lib.theme.view.ThemeCheckBox\n                android:id=\"@+id/cb_compress\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:minHeight=\"48dp\"\n                android:text=\"@string/is_compress\" />\n\n        </LinearLayout>\n\n    </androidx.core.widget.NestedScrollView>\n\n    <com.google.android.flexbox.FlexboxLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:paddingLeft=\"12dp\"\n        android:paddingRight=\"12dp\"\n        app:flexWrap=\"wrap\"\n        app:justifyContent=\"space_between\">\n\n        <io.legado.app.ui.widget.text.AccentTextView\n            android:id=\"@+id/tv_footer_left\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:padding=\"12dp\"\n            android:text=\"@string/test\"\n            tools:ignore=\"RtlHardcoded\" />\n\n        <LinearLayout\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:orientation=\"horizontal\">\n\n            <io.legado.app.ui.widget.text.AccentTextView\n                android:id=\"@+id/tv_cancel\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:padding=\"12dp\"\n                android:text=\"@string/cancel\"\n                tools:ignore=\"RtlHardcoded\" />\n\n            <io.legado.app.ui.widget.text.AccentTextView\n                android:id=\"@+id/tv_ok\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:padding=\"12dp\"\n                android:text=\"@string/ok\"\n                tools:ignore=\"RtlHardcoded\" />\n\n        </LinearLayout>\n\n    </com.google.android.flexbox.FlexboxLayout>\n</LinearLayout>\n"
  },
  {
    "path": "app/src/main/res/layout/dialog_download_choice.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:id=\"@+id/ll_content\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:gravity=\"center\"\n    android:orientation=\"horizontal\"\n    android:padding=\"16dp\">\n\n    <View\n        android:layout_width=\"0dp\"\n        android:layout_height=\"0dp\"\n        android:layout_weight=\"1\" />\n\n    <TextView\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_gravity=\"center_vertical\"\n        android:layout_marginRight=\"5dp\"\n        android:text=\"@string/chapter\"\n        android:textColor=\"@color/primaryText\"\n        android:textSize=\"16sp\"\n        tools:ignore=\"RtlHardcoded\" />\n\n    <EditText\n        android:id=\"@+id/edit_start\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_gravity=\"center_vertical\"\n        android:background=\"@drawable/bg_edit\"\n        android:hint=\"@string/start\"\n        android:importantForAutofill=\"no\"\n        android:inputType=\"number\"\n        android:lines=\"1\"\n        android:maxLength=\"5\"\n        android:minWidth=\"60dp\"\n        android:paddingLeft=\"5dp\"\n        android:paddingTop=\"4dp\"\n        android:paddingRight=\"5dp\"\n        android:paddingBottom=\"4dp\"\n        android:textColor=\"@color/primaryText\"\n        android:textCursorDrawable=\"@drawable/shape_text_cursor\"\n        android:textSize=\"14sp\"\n        tools:ignore=\"TouchTargetSizeCheck,TextContrastCheck\" />\n\n    <TextView\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_gravity=\"center_vertical\"\n        android:layout_marginLeft=\"5dp\"\n        android:layout_marginRight=\"5dp\"\n        android:text=\"@string/to\"\n        android:textColor=\"@color/primaryText\"\n        android:textSize=\"16sp\" />\n\n    <EditText\n        android:id=\"@+id/edit_end\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_gravity=\"center_vertical\"\n        android:background=\"@drawable/bg_edit\"\n        android:hint=\"@string/end\"\n        android:importantForAutofill=\"no\"\n        android:inputType=\"number\"\n        android:lines=\"1\"\n        android:maxLength=\"5\"\n        android:minWidth=\"60dp\"\n        android:paddingLeft=\"5dp\"\n        android:paddingTop=\"4dp\"\n        android:paddingRight=\"5dp\"\n        android:paddingBottom=\"4dp\"\n        android:textColor=\"@color/primaryText\"\n        android:textCursorDrawable=\"@drawable/shape_text_cursor\"\n        android:textSize=\"14sp\"\n        tools:ignore=\"TouchTargetSizeCheck,SpeakableTextPresentCheck,TextContrastCheck\" />\n\n    <View\n        android:layout_width=\"0dp\"\n        android:layout_height=\"0dp\"\n        android:layout_weight=\"1\" />\n\n</LinearLayout>\n"
  },
  {
    "path": "app/src/main/res/layout/dialog_edit_text.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.core.widget.NestedScrollView xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:overScrollMode=\"ifContentScrolls\"\n    android:paddingLeft=\"16dp\"\n    android:paddingRight=\"16dp\">\n\n    <io.legado.app.ui.widget.text.AutoCompleteTextView\n        android:id=\"@+id/edit_view\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        tools:ignore=\"SpeakableTextPresentCheck,TouchTargetSizeCheck\" />\n\n</androidx.core.widget.NestedScrollView>"
  },
  {
    "path": "app/src/main/res/layout/dialog_file_chooser.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:orientation=\"vertical\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:background=\"@color/background\">\n\n    <androidx.appcompat.widget.Toolbar\n        android:id=\"@+id/tool_bar\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:theme=\"?attr/actionBarStyle\"\n        app:titleTextAppearance=\"@style/ToolbarTitle\"\n        app:popupTheme=\"@style/AppTheme.PopupOverlay\" />\n\n    <androidx.recyclerview.widget.RecyclerView\n        android:id=\"@+id/rv_path\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"24dp\"\n        android:paddingLeft=\"10dp\"\n        android:paddingRight=\"10dp\"\n        android:background=\"@color/background_card\"\n        android:elevation=\"5dp\" />\n\n    <FrameLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"0dp\"\n        android:layout_weight=\"1\"\n        android:background=\"@color/background_card\">\n\n        <androidx.recyclerview.widget.RecyclerView\n            android:id=\"@+id/rv_file\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"match_parent\" />\n\n        <TextView\n            android:id=\"@+id/tv_empty\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"match_parent\"\n            android:gravity=\"center\"\n            android:visibility=\"gone\" />\n\n    </FrameLayout>\n\n    <io.legado.app.ui.widget.text.AccentBgTextView\n        android:id=\"@+id/tv_ok\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:padding=\"10dp\"\n        android:layout_margin=\"3dp\"\n        android:text=\"@string/ok\"\n        android:gravity=\"center\" />\n\n</LinearLayout>"
  },
  {
    "path": "app/src/main/res/layout/dialog_font_select.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:orientation=\"vertical\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\">\n\n    <androidx.appcompat.widget.Toolbar\n        android:id=\"@+id/tool_bar\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:theme=\"?attr/actionBarStyle\"\n        app:titleTextAppearance=\"@style/ToolbarTitle\"\n        app:popupTheme=\"@style/AppTheme.PopupOverlay\" />\n\n    <androidx.recyclerview.widget.RecyclerView\n        android:id=\"@+id/recycler_view\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\" />\n\n</LinearLayout>"
  },
  {
    "path": "app/src/main/res/layout/dialog_http_tts_edit.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<FrameLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:padding=\"16dp\">\n\n    <LinearLayout\n        android:id=\"@+id/vw_bg\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:layout_gravity=\"center\"\n        android:background=\"@drawable/shape_card_view\"\n        android:orientation=\"vertical\"\n        tools:ignore=\"UselessParent\">\n\n        <androidx.appcompat.widget.Toolbar\n            android:id=\"@+id/tool_bar\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:theme=\"?attr/actionBarStyle\"\n            app:title=\"@string/speak_engine\"\n            app:popupTheme=\"@style/AppTheme.PopupOverlay\"\n            app:titleTextAppearance=\"@style/ToolbarTitle\" />\n\n        <androidx.core.widget.NestedScrollView\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"match_parent\"\n            android:overScrollMode=\"ifContentScrolls\">\n\n            <LinearLayout\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:orientation=\"vertical\">\n\n                <io.legado.app.ui.widget.text.TextInputLayout\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"wrap_content\"\n                    android:hint=\"@string/name\">\n\n                    <io.legado.app.lib.theme.view.ThemeEditText\n                        android:id=\"@+id/tv_name\"\n                        android:layout_width=\"match_parent\"\n                        android:layout_height=\"wrap_content\"\n                        tools:ignore=\"SpeakableTextPresentCheck,TouchTargetSizeCheck\" />\n\n                </io.legado.app.ui.widget.text.TextInputLayout>\n\n                <io.legado.app.ui.widget.text.TextInputLayout\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"wrap_content\"\n                    android:hint=\"url\"\n                    tools:ignore=\"HardcodedText\">\n\n                    <io.legado.app.ui.widget.code.CodeView\n                        android:id=\"@+id/tv_url\"\n                        android:layout_width=\"match_parent\"\n                        android:layout_height=\"wrap_content\"\n                        tools:ignore=\"TouchTargetSizeCheck,SpeakableTextPresentCheck\" />\n\n                </io.legado.app.ui.widget.text.TextInputLayout>\n\n                <io.legado.app.ui.widget.text.TextInputLayout\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"wrap_content\"\n                    android:hint=\"Content-Type\"\n                    tools:ignore=\"HardcodedText\">\n\n                    <io.legado.app.ui.widget.code.CodeView\n                        android:id=\"@+id/tv_content_type\"\n                        android:layout_width=\"match_parent\"\n                        android:layout_height=\"wrap_content\"\n                        tools:ignore=\"TouchTargetSizeCheck,SpeakableTextPresentCheck\" />\n\n                </io.legado.app.ui.widget.text.TextInputLayout>\n\n                <io.legado.app.ui.widget.text.TextInputLayout\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"wrap_content\"\n                    android:hint=\"@string/concurrent_rate\"\n                    tools:ignore=\"HardcodedText\">\n\n                    <io.legado.app.ui.widget.code.CodeView\n                        android:id=\"@+id/tv_concurrent_rate\"\n                        android:layout_width=\"match_parent\"\n                        android:layout_height=\"wrap_content\"\n                        tools:ignore=\"TouchTargetSizeCheck,SpeakableTextPresentCheck\" />\n\n                </io.legado.app.ui.widget.text.TextInputLayout>\n\n                <io.legado.app.ui.widget.text.TextInputLayout\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"wrap_content\"\n                    android:hint=\"@string/login_url\"\n                    tools:ignore=\"HardcodedText\">\n\n                    <io.legado.app.ui.widget.code.CodeView\n                        android:id=\"@+id/tv_login_url\"\n                        android:layout_width=\"match_parent\"\n                        android:layout_height=\"wrap_content\"\n                        tools:ignore=\"SpeakableTextPresentCheck,TouchTargetSizeCheck\" />\n\n                </io.legado.app.ui.widget.text.TextInputLayout>\n\n                <io.legado.app.ui.widget.text.TextInputLayout\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"wrap_content\"\n                    android:hint=\"@string/login_ui\"\n                    tools:ignore=\"HardcodedText\">\n\n                    <io.legado.app.ui.widget.code.CodeView\n                        android:id=\"@+id/tv_login_ui\"\n                        android:layout_width=\"match_parent\"\n                        android:layout_height=\"wrap_content\"\n                        tools:ignore=\"SpeakableTextPresentCheck,TouchTargetSizeCheck\" />\n\n                </io.legado.app.ui.widget.text.TextInputLayout>\n\n                <io.legado.app.ui.widget.text.TextInputLayout\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"wrap_content\"\n                    android:hint=\"@string/login_check_js\"\n                    tools:ignore=\"HardcodedText\">\n\n                    <io.legado.app.ui.widget.code.CodeView\n                        android:id=\"@+id/tv_login_check_js\"\n                        android:layout_width=\"match_parent\"\n                        android:layout_height=\"wrap_content\"\n                        tools:ignore=\"TouchTargetSizeCheck,SpeakableTextPresentCheck\" />\n\n                </io.legado.app.ui.widget.text.TextInputLayout>\n\n                <io.legado.app.ui.widget.text.TextInputLayout\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"wrap_content\"\n                    android:hint=\"@string/source_http_header\"\n                    tools:ignore=\"HardcodedText\">\n\n                    <io.legado.app.ui.widget.code.CodeView\n                        android:id=\"@+id/tv_headers\"\n                        android:layout_width=\"match_parent\"\n                        android:layout_height=\"wrap_content\"\n                        tools:ignore=\"TouchTargetSizeCheck,SpeakableTextPresentCheck\" />\n\n                </io.legado.app.ui.widget.text.TextInputLayout>\n\n            </LinearLayout>\n\n        </androidx.core.widget.NestedScrollView>\n\n    </LinearLayout>\n</FrameLayout>"
  },
  {
    "path": "app/src/main/res/layout/dialog_image_blurring.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:orientation=\"vertical\"\n    android:padding=\"10dp\">\n\n    <TextView\n        android:id=\"@+id/text_view_value\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:padding=\"6dp\"\n        android:layout_gravity=\"center_horizontal\"\n        android:textColor=\"@color/primaryText\"\n        android:textSize=\"17sp\" />\n\n    <androidx.appcompat.widget.AppCompatSeekBar\n        android:id=\"@+id/seek_bar\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:padding=\"6dp\"\n        android:max=\"25\" />\n\n    <TextView\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:padding=\"6dp\"\n        android:text=\"@string/background_image_hint\" />\n\n</LinearLayout>"
  },
  {
    "path": "app/src/main/res/layout/dialog_login.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<FrameLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:padding=\"16dp\">\n\n    <LinearLayout\n        android:id=\"@+id/vw_bg\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:layout_gravity=\"center\"\n        android:background=\"@drawable/shape_card_view\"\n        android:orientation=\"vertical\"\n        tools:ignore=\"UselessParent\">\n\n        <androidx.appcompat.widget.Toolbar\n            android:id=\"@+id/tool_bar\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:background=\"@color/background_menu\"\n            android:elevation=\"5dp\"\n            android:theme=\"?attr/actionBarStyle\"\n            app:title=\"@string/login\"\n            app:displayHomeAsUp=\"false\"\n            app:fitStatusBar=\"false\"\n            app:popupTheme=\"@style/AppTheme.PopupOverlay\"\n            app:titleTextAppearance=\"@style/ToolbarTitle\" />\n\n        <androidx.core.widget.NestedScrollView\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:overScrollMode=\"ifContentScrolls\">\n\n            <com.google.android.flexbox.FlexboxLayout\n                android:id=\"@+id/flexbox\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginTop=\"8dp\"\n                android:clipToPadding=\"false\"\n                android:padding=\"3dp\"\n                app:dividerDrawable=\"@drawable/shape_space_divider\"\n                app:flexDirection=\"row\"\n                app:flexWrap=\"wrap\"\n                app:showDivider=\"middle\" />\n\n        </androidx.core.widget.NestedScrollView>\n\n    </LinearLayout>\n</FrameLayout>"
  },
  {
    "path": "app/src/main/res/layout/dialog_manga_color_filter.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.core.widget.NestedScrollView xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:padding=\"16dp\">\n\n    <LinearLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:orientation=\"vertical\">\n\n        <io.legado.app.ui.widget.text.AccentTextView\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:paddingBottom=\"8dp\"\n            android:text=\"@string/manga_color_filter\"\n            android:textSize=\"18sp\" />\n\n        <io.legado.app.ui.widget.DetailSeekBar\n            android:id=\"@+id/dsb_brightness\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            app:max=\"255\"\n            app:title=\"@string/brightness\" />\n\n        <io.legado.app.ui.widget.DetailSeekBar\n            android:id=\"@+id/dsb_r\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            app:max=\"255\"\n            app:title=\"R\" />\n\n        <io.legado.app.ui.widget.DetailSeekBar\n            android:id=\"@+id/dsb_g\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            app:max=\"255\"\n            app:title=\"G\" />\n\n        <io.legado.app.ui.widget.DetailSeekBar\n            android:id=\"@+id/dsb_b\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            app:max=\"255\"\n            app:title=\"B\" />\n\n        <io.legado.app.ui.widget.DetailSeekBar\n            android:id=\"@+id/dsb_a\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            app:max=\"255\"\n            app:title=\"A\" />\n\n    </LinearLayout>\n\n</androidx.core.widget.NestedScrollView>\n"
  },
  {
    "path": "app/src/main/res/layout/dialog_manga_epaper.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.core.widget.NestedScrollView xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:padding=\"16dp\">\n\n    <LinearLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:orientation=\"vertical\">\n\n        <io.legado.app.ui.widget.text.AccentTextView\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:paddingBottom=\"8dp\"\n            android:text=\"@string/manga_epaper_stting\"\n            android:textSize=\"18sp\" />\n\n        <io.legado.app.ui.widget.DetailSeekBar\n            android:id=\"@+id/dsb_epaper\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            app:max=\"255\"\n            app:title=\"@string/manga_epaper_value\" />\n\n    </LinearLayout>\n\n</androidx.core.widget.NestedScrollView>\n"
  },
  {
    "path": "app/src/main/res/layout/dialog_manga_footer_setting.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.core.widget.NestedScrollView xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:padding=\"16dp\">\n\n    <LinearLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:orientation=\"vertical\">\n\n        <io.legado.app.ui.widget.text.AccentTextView\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:text=\"@string/manga_footer_config\"\n            android:textSize=\"18sp\" />\n\n        <TextView\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginTop=\"16dp\"\n            android:text=\"@string/manga_header_chapter\"\n            android:textColor=\"@color/primaryText\"\n            android:textSize=\"14sp\" />\n\n        <LinearLayout\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginStart=\"16dp\"\n            android:layout_marginTop=\"8dp\">\n\n            <io.legado.app.lib.theme.view.ThemeCheckBox\n                android:id=\"@+id/cb_chapter_label\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:gravity=\"center\"\n                android:text=\"@string/manga_check_chapter_label\" />\n\n            <io.legado.app.lib.theme.view.ThemeCheckBox\n                android:id=\"@+id/cb_chapter\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginStart=\"10dp\"\n                android:gravity=\"center\"\n                android:text=\"@string/manga_check_chapter\" />\n\n            <io.legado.app.lib.theme.view.ThemeCheckBox\n                android:id=\"@+id/cb_chapter_name\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginStart=\"10dp\"\n                android:gravity=\"center\"\n                android:text=\"@string/manga_check_chapter_name\" />\n        </LinearLayout>\n\n        <TextView\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginTop=\"16dp\"\n            android:text=\"@string/manga_header_page\"\n            android:textColor=\"@color/primaryText\"\n            android:textSize=\"14sp\" />\n\n        <LinearLayout\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginStart=\"16dp\"\n            android:layout_marginTop=\"8dp\">\n\n            <io.legado.app.lib.theme.view.ThemeCheckBox\n                android:id=\"@+id/cb_page_number_label\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:gravity=\"center\"\n                android:text=\"@string/manga_check_page_label\" />\n\n            <io.legado.app.lib.theme.view.ThemeCheckBox\n                android:id=\"@+id/cb_page_number\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginStart=\"10dp\"\n                android:gravity=\"center\"\n                android:text=\"@string/manga_check_page_number\" />\n        </LinearLayout>\n\n        <TextView\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginTop=\"16dp\"\n            android:text=\"@string/manga_header_progress\"\n            android:textColor=\"@color/primaryText\"\n            android:textSize=\"14sp\" />\n\n        <LinearLayout\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginStart=\"16dp\"\n            android:layout_marginTop=\"8dp\">\n\n            <io.legado.app.lib.theme.view.ThemeCheckBox\n                android:id=\"@+id/cb_progress_ratio_label\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:gravity=\"center\"\n                android:text=\"@string/manga_check_progress_label\" />\n\n            <io.legado.app.lib.theme.view.ThemeCheckBox\n                android:id=\"@+id/cb_progress_ratio\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginStart=\"10dp\"\n                android:gravity=\"center\"\n                android:text=\"@string/manga_check_progress\" />\n        </LinearLayout>\n\n        <TextView\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginTop=\"16dp\"\n            android:text=\"@string/manga_header_footer\"\n            android:textColor=\"@color/primaryText\"\n            android:textSize=\"14sp\" />\n\n        <RadioGroup\n            android:id=\"@+id/rg_footer\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginStart=\"16dp\"\n            android:layout_marginTop=\"8dp\"\n            android:orientation=\"horizontal\">\n\n            <io.legado.app.lib.theme.view.ThemeRadioButton\n                android:id=\"@+id/rb_show\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:gravity=\"center\"\n                android:text=\"@string/show\" />\n\n            <io.legado.app.lib.theme.view.ThemeRadioButton\n                android:id=\"@+id/rb_hide\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginStart=\"10dp\"\n                android:gravity=\"center\"\n                android:text=\"@string/hide\" />\n        </RadioGroup>\n\n        <RadioGroup\n            android:id=\"@+id/rg_footer_orientation\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginStart=\"16dp\"\n            android:layout_marginTop=\"8dp\"\n            android:orientation=\"horizontal\">\n\n            <io.legado.app.lib.theme.view.ThemeRadioButton\n                android:id=\"@+id/rb_left\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:gravity=\"center\"\n                android:text=\"@string/manga_radio_left\" />\n\n            <io.legado.app.lib.theme.view.ThemeRadioButton\n                android:id=\"@+id/rb_center\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginStart=\"10dp\"\n                android:gravity=\"center\"\n                android:text=\"@string/manga_radio_center\" />\n        </RadioGroup>\n\n    </LinearLayout>\n\n</androidx.core.widget.NestedScrollView>\n"
  },
  {
    "path": "app/src/main/res/layout/dialog_multiple_edit_text.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.core.widget.NestedScrollView xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:overScrollMode=\"ifContentScrolls\"\n    android:padding=\"16dp\">\n\n    <LinearLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:orientation=\"vertical\">\n\n        <io.legado.app.ui.widget.text.TextInputLayout\n            android:id=\"@+id/layout_1\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\">\n\n            <io.legado.app.ui.widget.text.AutoCompleteTextView\n                android:id=\"@+id/edit_1\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                tools:ignore=\"SpeakableTextPresentCheck,TouchTargetSizeCheck\" />\n\n        </io.legado.app.ui.widget.text.TextInputLayout>\n\n        <io.legado.app.ui.widget.text.TextInputLayout\n            android:id=\"@+id/layout_2\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:visibility=\"gone\">\n\n            <io.legado.app.ui.widget.text.AutoCompleteTextView\n                android:id=\"@+id/edit_2\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                tools:ignore=\"SpeakableTextPresentCheck,TouchTargetSizeCheck\" />\n\n        </io.legado.app.ui.widget.text.TextInputLayout>\n\n\n    </LinearLayout>\n\n</androidx.core.widget.NestedScrollView>"
  },
  {
    "path": "app/src/main/res/layout/dialog_number_picker.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<FrameLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\">\n\n    <NumberPicker\n        android:id=\"@+id/number_picker\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_gravity=\"center\"\n        android:scrollbars=\"none\" />\n\n</FrameLayout>"
  },
  {
    "path": "app/src/main/res/layout/dialog_open_url_confirm.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:background=\"@drawable/shape_card_view\"\n    android:orientation=\"vertical\">\n\n    <androidx.appcompat.widget.Toolbar\n        android:id=\"@+id/tool_bar\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:elevation=\"5dp\"\n        android:theme=\"?attr/actionBarStyle\"\n        app:popupTheme=\"@style/AppTheme.PopupOverlay\"\n        app:title=\"跳转确认\"\n        app:titleTextAppearance=\"@style/ToolbarTitle\" />\n\n    <TextView\n        android:id=\"@+id/message\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:padding=\"16dp\"\n        android:textColor=\"@color/primaryText\"\n        android:textSize=\"16sp\" />\n\n    <com.google.android.flexbox.FlexboxLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        app:flexDirection=\"row\"\n        app:justifyContent=\"flex_end\">\n\n        <io.legado.app.ui.widget.text.AccentTextView\n            android:id=\"@+id/btn_negative\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:padding=\"16dp\"\n            android:text=\"@string/cancel\" />\n\n        <io.legado.app.ui.widget.text.AccentTextView\n            android:id=\"@+id/btn_positive\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:padding=\"16dp\"\n            android:text=\"@string/ok\" />\n\n    </com.google.android.flexbox.FlexboxLayout>\n</LinearLayout>\n"
  },
  {
    "path": "app/src/main/res/layout/dialog_page_key.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:id=\"@+id/content_view\"\n    android:orientation=\"vertical\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:background=\"@color/background\"\n    android:padding=\"16dp\">\n\n    <TextView\n        android:id=\"@+id/tv_title\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:layout_margin=\"5dp\"\n        android:textSize=\"18sp\"\n        android:textStyle=\"bold\"\n        android:text=\"@string/custom_page_key\" />\n\n    <io.legado.app.ui.widget.text.TextInputLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:layout_margin=\"5dp\"\n        android:hint=\"@string/prev_page_key\">\n\n        <io.legado.app.lib.theme.view.ThemeEditText\n            android:id=\"@+id/et_prev\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:singleLine=\"true\" />\n\n    </io.legado.app.ui.widget.text.TextInputLayout>\n\n    <io.legado.app.ui.widget.text.TextInputLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:layout_margin=\"5dp\"\n        android:hint=\"@string/next_page_key\">\n\n        <io.legado.app.lib.theme.view.ThemeEditText\n            android:id=\"@+id/et_next\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:singleLine=\"true\" />\n\n    </io.legado.app.ui.widget.text.TextInputLayout>\n\n    <TextView\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:layout_margin=\"5dp\"\n        android:textColor=\"@color/secondaryText\"\n        android:text=\"@string/page_key_set_help\" />\n\n    <LinearLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:layout_margin=\"5dp\"\n        android:orientation=\"horizontal\">\n\n        <TextView\n            android:id=\"@+id/tv_reset\"\n            android:layout_width=\"0dp\"\n            android:layout_height=\"wrap_content\"\n            android:layout_weight=\"1\"\n            android:background=\"@drawable/selector_fillet_btn_bg\"\n            android:clickable=\"true\"\n            android:focusable=\"true\"\n            android:gravity=\"center\"\n            android:paddingTop=\"5dp\"\n            android:paddingBottom=\"5dp\"\n            android:text=\"@string/reset\"\n            android:textColor=\"@color/primaryText\" />\n\n        <Space\n            android:layout_width=\"3dp\"\n            android:layout_height=\"wrap_content\" />\n\n        <TextView\n            android:id=\"@+id/tv_ok\"\n            android:layout_width=\"0dp\"\n            android:layout_height=\"wrap_content\"\n            android:layout_weight=\"1\"\n            android:background=\"@drawable/selector_fillet_btn_bg\"\n            android:clickable=\"true\"\n            android:focusable=\"true\"\n            android:gravity=\"center\"\n            android:paddingTop=\"5dp\"\n            android:paddingBottom=\"5dp\"\n            android:text=\"@string/ok\"\n            android:textColor=\"@color/primaryText\" />\n\n    </LinearLayout>\n\n</LinearLayout>"
  },
  {
    "path": "app/src/main/res/layout/dialog_photo_view.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:background=\"@color/background\">\n\n    <io.legado.app.ui.widget.image.PhotoView\n        android:id=\"@+id/photo_view\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:scaleType=\"centerInside\" />\n\n</androidx.constraintlayout.widget.ConstraintLayout>\n\n\n"
  },
  {
    "path": "app/src/main/res/layout/dialog_progressbar_view.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:orientation=\"vertical\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\">\n\n    <io.legado.app.lib.theme.view.ThemeProgressBar\n        android:id=\"@+id/ck_progress\"\n        style=\"?android:attr/progressBarStyleHorizontal\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:padding=\"20dp\"\n        android:layout_marginRight=\"60dp\"\n        app:layout_constraintTop_toTopOf=\"parent\" />\n\n    <androidx.appcompat.widget.AppCompatTextView\n        android:id=\"@+id/ck_progress_text\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginRight=\"20dp\"\n        app:layout_constraintBottom_toBottomOf=\"@+id/ck_progress\"\n        app:layout_constraintEnd_toEndOf=\"@+id/ck_progress\" />\n\n    <io.legado.app.ui.widget.text.AccentTextView\n        android:id=\"@+id/tv_footer_left\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:padding=\"12dp\"\n        android:text=\"@string/cancel\"\n        app:layout_constraintBottom_toBottomOf=\"parent\"\n        app:layout_constraintStart_toEndOf=\"@id/ck_progress\"\n        app:layout_constraintTop_toTopOf=\"parent\"\n        tools:ignore=\"RtlHardcoded\" />\n</androidx.constraintlayout.widget.ConstraintLayout>"
  },
  {
    "path": "app/src/main/res/layout/dialog_read_aloud.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:id=\"@+id/root_view\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:background=\"@color/background_menu\"\n    android:orientation=\"vertical\"\n    android:paddingLeft=\"16dp\"\n    android:paddingTop=\"6dp\"\n    android:paddingRight=\"16dp\"\n    android:paddingBottom=\"6dp\">\n\n    <LinearLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:gravity=\"center\"\n        android:orientation=\"horizontal\">\n\n        <TextView\n            android:id=\"@+id/tv_pre\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginStart=\"10dp\"\n            android:layout_marginEnd=\"10dp\"\n            android:background=\"?android:attr/selectableItemBackgroundBorderless\"\n            android:clickable=\"true\"\n            android:focusable=\"true\"\n            android:paddingTop=\"10dp\"\n            android:paddingBottom=\"10dp\"\n            android:text=\"@string/previous_chapter\"\n            android:textColor=\"@color/primaryText\"\n            android:textSize=\"14sp\"\n            tools:ignore=\"TouchTargetSizeCheck\" />\n\n        <View\n            android:layout_width=\"0dp\"\n            android:layout_height=\"1dp\"\n            android:layout_weight=\"1\" />\n\n        <ImageView\n            android:id=\"@+id/iv_play_prev\"\n            android:layout_width=\"30dp\"\n            android:layout_height=\"30dp\"\n            android:layout_marginLeft=\"8dp\"\n            android:layout_marginRight=\"8dp\"\n            android:background=\"?android:attr/selectableItemBackgroundBorderless\"\n            android:contentDescription=\"@string/prev_sentence\"\n            android:src=\"@drawable/ic_skip_previous\"\n            android:tooltipText=\"@string/prev_sentence\"\n            app:tint=\"@color/primaryText\"\n            tools:ignore=\"UnusedAttribute\" />\n\n        <ImageView\n            android:id=\"@+id/iv_play_pause\"\n            android:layout_width=\"30dp\"\n            android:layout_height=\"30dp\"\n            android:layout_marginLeft=\"8dp\"\n            android:layout_marginRight=\"8dp\"\n            android:background=\"?android:attr/selectableItemBackgroundBorderless\"\n            android:contentDescription=\"@string/audio_play\"\n            android:src=\"@drawable/ic_play_24dp\"\n            android:tooltipText=\"@string/audio_play\"\n            app:tint=\"@color/primaryText\"\n            tools:ignore=\"UnusedAttribute\" />\n\n        <ImageView\n            android:id=\"@+id/iv_stop\"\n            android:layout_width=\"30dp\"\n            android:layout_height=\"30dp\"\n            android:layout_marginLeft=\"8dp\"\n            android:layout_marginRight=\"8dp\"\n            android:background=\"?android:attr/selectableItemBackgroundBorderless\"\n            android:contentDescription=\"@string/stop\"\n            android:src=\"@drawable/ic_stop_black_24dp\"\n            android:tooltipText=\"@string/stop\"\n            app:tint=\"@color/primaryText\"\n            tools:ignore=\"UnusedAttribute\" />\n\n        <ImageView\n            android:id=\"@+id/iv_play_next\"\n            android:layout_width=\"30dp\"\n            android:layout_height=\"30dp\"\n            android:layout_marginLeft=\"8dp\"\n            android:layout_marginRight=\"8dp\"\n            android:background=\"?android:attr/selectableItemBackgroundBorderless\"\n            android:contentDescription=\"@string/next_sentence\"\n            android:src=\"@drawable/ic_skip_next\"\n            android:tooltipText=\"@string/next_sentence\"\n            app:tint=\"@color/primaryText\"\n            tools:ignore=\"UnusedAttribute\" />\n\n        <View\n            android:layout_width=\"0dp\"\n            android:layout_height=\"1dp\"\n            android:layout_weight=\"1\" />\n\n        <TextView\n            android:id=\"@+id/tv_next\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginStart=\"10dp\"\n            android:layout_marginEnd=\"10dp\"\n            android:background=\"?android:attr/selectableItemBackgroundBorderless\"\n            android:clickable=\"true\"\n            android:focusable=\"true\"\n            android:paddingTop=\"10dp\"\n            android:paddingBottom=\"10dp\"\n            android:text=\"@string/next_chapter\"\n            android:textColor=\"@color/primaryText\"\n            android:textSize=\"14sp\"\n            tools:ignore=\"TouchTargetSizeCheck\" />\n\n    </LinearLayout>\n\n    <LinearLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:gravity=\"center\"\n        android:orientation=\"horizontal\"\n        android:padding=\"6dp\">\n\n        <androidx.appcompat.widget.AppCompatImageView\n            android:id=\"@+id/iv_timer\"\n            android:layout_width=\"30dp\"\n            android:layout_height=\"30dp\"\n            android:background=\"?android:attr/selectableItemBackgroundBorderless\"\n            android:contentDescription=\"@string/set_timer\"\n            android:src=\"@drawable/ic_time_add_24dp\"\n            android:tint=\"@color/primaryText\"\n            android:tooltipText=\"@string/set_timer\"\n            tools:ignore=\"UnusedAttribute\" />\n\n        <io.legado.app.lib.theme.view.ThemeSeekBar\n            android:id=\"@+id/seek_timer\"\n            android:layout_width=\"0dp\"\n            android:layout_height=\"wrap_content\"\n            android:layout_weight=\"1\"\n            android:max=\"180\" />\n\n        <TextView\n            android:id=\"@+id/tv_timer\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:background=\"?android:attr/selectableItemBackgroundBorderless\"\n            android:text=\"@string/timer_m\"\n            android:textColor=\"@color/primaryText\" />\n\n    </LinearLayout>\n\n    <LinearLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:orientation=\"vertical\">\n\n        <LinearLayout\n            android:id=\"@+id/ll_tts_SpeechRate\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:layout_gravity=\"center_vertical\"\n            android:orientation=\"horizontal\"\n            android:padding=\"8dp\">\n\n            <TextView\n                android:id=\"@+id/tv_tts_speed\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:layout_gravity=\"center\"\n                android:text=\"@string/read_aloud_speed\"\n                android:textColor=\"@color/primaryText\"\n                android:textSize=\"14sp\" />\n\n            <TextView\n                android:id=\"@+id/tv_tts_speed_value\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:layout_gravity=\"center\"\n                android:paddingHorizontal=\"3dp\"\n                android:textColor=\"@color/primaryText\"\n                android:textSize=\"14sp\" />\n\n            <Space\n                android:layout_width=\"0dp\"\n                android:layout_height=\"1dp\"\n                android:layout_gravity=\"center\"\n                android:layout_weight=\"1\" />\n\n            <io.legado.app.lib.theme.view.ThemeSwitch\n                android:id=\"@+id/cb_tts_follow_sys\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:layout_gravity=\"center_vertical\"\n                android:contentDescription=\"@string/flow_sys\"\n                android:text=\"@string/flow_sys\"\n                tools:ignore=\"RtlHardcoded,TouchTargetSizeCheck\" />\n\n        </LinearLayout>\n\n        <LinearLayout\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:gravity=\"center\"\n            android:orientation=\"horizontal\"\n            android:paddingLeft=\"6dp\"\n            android:paddingRight=\"6dp\">\n\n            <androidx.appcompat.widget.AppCompatImageView\n                android:id=\"@+id/iv_tts_speech_reduce\"\n                android:layout_width=\"30dp\"\n                android:layout_height=\"30dp\"\n                android:contentDescription=\"@string/tts_speech_reduce\"\n                android:src=\"@drawable/ic_reduce\"\n                android:tint=\"@color/primaryText\"\n                android:tooltipText=\"@string/tts_speech_reduce\"\n                tools:ignore=\"UnusedAttribute\" />\n\n            <io.legado.app.lib.theme.view.ThemeSeekBar\n                android:id=\"@+id/seek_tts_speechRate\"\n                android:layout_width=\"0dp\"\n                android:layout_height=\"wrap_content\"\n                android:layout_weight=\"1\"\n                android:max=\"45\" />\n\n            <androidx.appcompat.widget.AppCompatImageView\n                android:id=\"@+id/iv_tts_speech_add\"\n                android:layout_width=\"30dp\"\n                android:layout_height=\"30dp\"\n                android:contentDescription=\"@string/tts_speech_add\"\n                android:src=\"@drawable/ic_add\"\n                android:tint=\"@color/primaryText\"\n                android:tooltipText=\"@string/tts_speech_add\"\n                tools:ignore=\"UnusedAttribute\" />\n\n        </LinearLayout>\n\n    </LinearLayout>\n\n    <LinearLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginTop=\"8dp\"\n        android:baselineAligned=\"false\"\n        android:orientation=\"horizontal\">\n\n        <!--目录按钮-->\n        <LinearLayout\n            android:id=\"@+id/ll_catalog\"\n            android:layout_width=\"60dp\"\n            android:layout_height=\"wrap_content\"\n            android:background=\"?android:attr/selectableItemBackgroundBorderless\"\n            android:clickable=\"true\"\n            android:contentDescription=\"@string/chapter_list\"\n            android:focusable=\"true\"\n            android:orientation=\"vertical\"\n            android:paddingBottom=\"7dp\">\n\n            <androidx.appcompat.widget.AppCompatImageView\n                android:id=\"@+id/iv_catalog\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"0dp\"\n                android:layout_weight=\"1\"\n                android:adjustViewBounds=\"true\"\n                android:contentDescription=\"@string/chapter_list\"\n                android:maxHeight=\"20dp\"\n                android:src=\"@drawable/ic_toc\"\n                app:tint=\"@color/primaryText\"\n                tools:ignore=\"NestedWeights\" />\n\n            <TextView\n                android:id=\"@+id/tv_catalog\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:layout_gravity=\"center_horizontal\"\n                android:layout_marginTop=\"3dp\"\n                android:ellipsize=\"middle\"\n                android:singleLine=\"true\"\n                android:text=\"@string/chapter_list\"\n                android:textColor=\"@color/primaryText\"\n                android:textSize=\"12sp\" />\n        </LinearLayout>\n\n        <View\n            android:layout_width=\"0dp\"\n            android:layout_height=\"match_parent\"\n            android:layout_weight=\"2\" />\n        <!--调节按钮-->\n        <LinearLayout\n            android:id=\"@+id/ll_main_menu\"\n            android:layout_width=\"60dp\"\n            android:layout_height=\"wrap_content\"\n            android:background=\"?android:attr/selectableItemBackgroundBorderless\"\n            android:clickable=\"true\"\n            android:contentDescription=\"@string/main_menu\"\n            android:focusable=\"true\"\n            android:orientation=\"vertical\"\n            android:paddingBottom=\"7dp\"\n            tools:ignore=\"TextSizeCheck\">\n\n            <androidx.appcompat.widget.AppCompatImageView\n                android:id=\"@+id/iv_main_menu\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"0dp\"\n                android:layout_weight=\"1\"\n                android:adjustViewBounds=\"true\"\n                android:contentDescription=\"@string/main_menu\"\n                android:maxHeight=\"20dp\"\n                android:src=\"@drawable/ic_menu\"\n                app:tint=\"@color/primaryText\"\n                tools:ignore=\"NestedWeights\" />\n\n            <TextView\n                android:id=\"@+id/tv_main_menu\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:layout_gravity=\"center_horizontal\"\n                android:layout_marginTop=\"3dp\"\n                android:ellipsize=\"middle\"\n                android:singleLine=\"true\"\n                android:text=\"@string/main_menu\"\n                android:textColor=\"@color/primaryText\"\n                android:textSize=\"12sp\" />\n        </LinearLayout>\n\n        <View\n            android:layout_width=\"0dp\"\n            android:layout_height=\"match_parent\"\n            android:layout_weight=\"2\" />\n        <!--界面按钮-->\n        <LinearLayout\n            android:id=\"@+id/ll_to_backstage\"\n            android:layout_width=\"60dp\"\n            android:layout_height=\"wrap_content\"\n            android:background=\"?android:attr/selectableItemBackgroundBorderless\"\n            android:clickable=\"true\"\n            android:contentDescription=\"@string/to_backstage\"\n            android:focusable=\"true\"\n            android:orientation=\"vertical\"\n            android:paddingBottom=\"7dp\">\n\n            <androidx.appcompat.widget.AppCompatImageView\n                android:id=\"@+id/iv_to_backstage\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"0dp\"\n                android:layout_weight=\"1\"\n                android:adjustViewBounds=\"true\"\n                android:contentDescription=\"@string/to_backstage\"\n                android:maxHeight=\"20dp\"\n                android:src=\"@drawable/ic_visibility_off\"\n                app:tint=\"@color/primaryText\"\n                tools:ignore=\"NestedWeights\" />\n\n            <TextView\n                android:id=\"@+id/tv_to_backstage\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:layout_gravity=\"center_horizontal\"\n                android:layout_marginTop=\"3dp\"\n                android:ellipsize=\"middle\"\n                android:singleLine=\"true\"\n                android:text=\"@string/to_backstage\"\n                android:textColor=\"@color/primaryText\"\n                android:textSize=\"12sp\" />\n        </LinearLayout>\n\n        <View\n            android:layout_width=\"0dp\"\n            android:layout_height=\"match_parent\"\n            android:layout_weight=\"2\" />\n        <!--设置按钮-->\n        <LinearLayout\n            android:id=\"@+id/ll_setting\"\n            android:layout_width=\"60dp\"\n            android:layout_height=\"wrap_content\"\n            android:background=\"?android:attr/selectableItemBackgroundBorderless\"\n            android:clickable=\"true\"\n            android:contentDescription=\"@string/setting\"\n            android:focusable=\"true\"\n            android:orientation=\"vertical\"\n            android:paddingBottom=\"7dp\">\n\n            <androidx.appcompat.widget.AppCompatImageView\n                android:id=\"@+id/iv_setting\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"0dp\"\n                android:layout_weight=\"1\"\n                android:adjustViewBounds=\"true\"\n                android:contentDescription=\"@string/setting\"\n                android:maxHeight=\"20dp\"\n                android:src=\"@drawable/ic_settings\"\n                app:tint=\"@color/primaryText\"\n                tools:ignore=\"NestedWeights\" />\n\n            <TextView\n                android:id=\"@+id/tv_setting\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:layout_gravity=\"center_horizontal\"\n                android:layout_marginTop=\"3dp\"\n                android:ellipsize=\"middle\"\n                android:singleLine=\"true\"\n                android:text=\"@string/setting\"\n                android:textColor=\"@color/primaryText\"\n                android:textSize=\"12sp\" />\n        </LinearLayout>\n\n    </LinearLayout>\n\n</LinearLayout>"
  },
  {
    "path": "app/src/main/res/layout/dialog_read_bg_text.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:id=\"@+id/root_view\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:background=\"@color/background\"\n    android:orientation=\"vertical\"\n    android:padding=\"10dp\">\n\n    <LinearLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:gravity=\"center_vertical\"\n        android:padding=\"6dp\">\n\n        <TextView\n            android:id=\"@+id/tv_name_title\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:text=\"@string/style_name\"\n            android:textColor=\"@color/primaryText\"\n            android:textSize=\"16sp\" />\n\n        <TextView\n            android:id=\"@+id/tv_name\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:paddingLeft=\"6dp\"\n            android:paddingRight=\"6dp\"\n            android:textColor=\"@color/secondaryText\"\n            tools:text=\"文字\" />\n\n        <ImageView\n            android:id=\"@+id/iv_edit\"\n            android:layout_width=\"20dp\"\n            android:layout_height=\"20dp\"\n            android:contentDescription=\"@string/edit\"\n            android:src=\"@drawable/ic_edit\"\n            app:tint=\"@color/secondaryText\" />\n\n        <Space\n            android:layout_width=\"0dp\"\n            android:layout_height=\"wrap_content\"\n            android:layout_weight=\"1\" />\n\n        <TextView\n            android:id=\"@+id/tv_restore\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:text=\"@string/restore\"\n            android:textColor=\"@color/primaryText\" />\n\n    </LinearLayout>\n\n    <io.legado.app.lib.theme.view.ThemeSwitch\n        android:id=\"@+id/sw_dark_status_icon\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:padding=\"6dp\"\n        android:text=\"@string/dark_status_icon\"\n        tools:ignore=\"TouchTargetSizeCheck\" />\n\n    <io.legado.app.lib.theme.view.ThemeSwitch\n        android:id=\"@+id/sw_underline\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:padding=\"6dp\"\n        android:text=\"@string/text_underline\"\n        tools:ignore=\"TouchTargetSizeCheck\" />\n\n    <LinearLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:gravity=\"center_vertical\"\n        android:orientation=\"horizontal\"\n        android:padding=\"6dp\">\n\n        <io.legado.app.ui.widget.text.StrokeTextView\n            android:id=\"@+id/tv_text_color\"\n            android:layout_width=\"0dp\"\n            android:layout_height=\"wrap_content\"\n            android:layout_gravity=\"center\"\n            android:layout_marginLeft=\"6dp\"\n            android:layout_marginRight=\"6dp\"\n            android:layout_weight=\"5\"\n            android:background=\"?attr/selectableItemBackground\"\n            android:gravity=\"center\"\n            android:padding=\"6dp\"\n            android:singleLine=\"true\"\n            android:text=\"@string/text_color\"\n            android:textSize=\"14sp\"\n            app:isBottomBackground=\"true\"\n            tools:ignore=\"HardcodedText\" />\n\n        <io.legado.app.ui.widget.text.StrokeTextView\n            android:id=\"@+id/tv_bg_color\"\n            android:layout_width=\"0dp\"\n            android:layout_height=\"wrap_content\"\n            android:layout_gravity=\"center\"\n            android:layout_marginLeft=\"6dp\"\n            android:layout_marginRight=\"6dp\"\n            android:layout_weight=\"5\"\n            android:background=\"?attr/selectableItemBackground\"\n            android:gravity=\"center\"\n            android:padding=\"6dp\"\n            android:singleLine=\"true\"\n            android:text=\"@string/bg_color\"\n            android:textSize=\"14sp\"\n            app:isBottomBackground=\"true\"\n            tools:ignore=\"HardcodedText\" />\n\n        <ImageView\n            android:id=\"@+id/iv_import\"\n            android:layout_width=\"36dp\"\n            android:layout_height=\"36dp\"\n            android:layout_marginLeft=\"6dp\"\n            android:layout_marginRight=\"6dp\"\n            android:background=\"?attr/selectableItemBackground\"\n            android:contentDescription=\"@string/import_str\"\n            android:src=\"@drawable/ic_import\"\n            android:tooltipText=\"@string/import_str\"\n            tools:ignore=\"UnusedAttribute\" />\n\n        <ImageView\n            android:id=\"@+id/iv_export\"\n            android:layout_width=\"36dp\"\n            android:layout_height=\"36dp\"\n            android:layout_marginLeft=\"6dp\"\n            android:layout_marginRight=\"6dp\"\n            android:background=\"?attr/selectableItemBackground\"\n            android:contentDescription=\"@string/import_str\"\n            android:src=\"@drawable/ic_export\"\n            android:tooltipText=\"@string/export_str\"\n            tools:ignore=\"UnusedAttribute\" />\n\n        <ImageView\n            android:id=\"@+id/iv_delete\"\n            android:layout_width=\"36dp\"\n            android:layout_height=\"36dp\"\n            android:layout_marginLeft=\"6dp\"\n            android:layout_marginRight=\"6dp\"\n            android:background=\"?attr/selectableItemBackground\"\n            android:contentDescription=\"@string/delete\"\n            android:src=\"@drawable/ic_clear_all\"\n            android:tooltipText=\"@string/delete\"\n            tools:ignore=\"UnusedAttribute\" />\n\n    </LinearLayout>\n\n    <TextView\n        android:id=\"@+id/tv_bg_alpha\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:paddingLeft=\"6dp\"\n        android:paddingRight=\"6dp\"\n        android:text=\"@string/bg_alpha\" />\n\n    <io.legado.app.lib.theme.view.ThemeSeekBar\n        android:id=\"@+id/sb_bg_alpha\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:max=\"100\"\n        android:padding=\"6dp\"\n        tools:ignore=\"UnusedAttribute\" />\n\n    <TextView\n        android:id=\"@+id/tv_bg_image\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:paddingLeft=\"6dp\"\n        android:paddingRight=\"6dp\"\n        android:text=\"@string/bg_image\" />\n\n    <androidx.recyclerview.widget.RecyclerView\n        android:id=\"@+id/recycler_view\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"100dp\"\n        android:orientation=\"horizontal\"\n        android:padding=\"6dp\"\n        app:layoutManager=\"androidx.recyclerview.widget.LinearLayoutManager\"\n        tools:listitem=\"@layout/item_bg_image\" />\n\n</LinearLayout>"
  },
  {
    "path": "app/src/main/res/layout/dialog_read_book_style.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tool=\"http://schemas.android.com/tools\"\n    android:id=\"@+id/root_view\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:background=\"@color/background\"\n    android:orientation=\"vertical\">\n\n    <LinearLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginTop=\"16dp\"\n        android:gravity=\"center_vertical\"\n        android:orientation=\"horizontal\"\n        android:paddingHorizontal=\"16dp\">\n\n        <io.legado.app.ui.book.read.config.TextFontWeightConverter\n            android:id=\"@+id/text_font_weight_converter\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:gravity=\"center\"\n            android:paddingLeft=\"6dp\"\n            android:paddingTop=\"4dp\"\n            android:paddingRight=\"6dp\"\n            android:paddingBottom=\"4dp\"\n            android:textSize=\"14sp\"\n            app:isBottomBackground=\"true\"\n            app:radius=\"3dp\" />\n\n        <Space\n            android:layout_width=\"0dp\"\n            android:layout_height=\"wrap_content\"\n            android:layout_weight=\"1\" />\n\n        <io.legado.app.ui.widget.text.StrokeTextView\n            android:id=\"@+id/tv_text_font\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:gravity=\"center\"\n            android:paddingLeft=\"6dp\"\n            android:paddingTop=\"4dp\"\n            android:paddingRight=\"6dp\"\n            android:paddingBottom=\"4dp\"\n            android:text=\"@string/text_font\"\n            android:textSize=\"14sp\"\n            app:isBottomBackground=\"true\"\n            app:radius=\"3dp\" />\n\n        <Space\n            android:layout_width=\"0dp\"\n            android:layout_height=\"wrap_content\"\n            android:layout_weight=\"1\" />\n\n        <io.legado.app.ui.widget.text.StrokeTextView\n            android:id=\"@+id/tv_text_indent\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:gravity=\"center\"\n            android:paddingLeft=\"6dp\"\n            android:paddingTop=\"4dp\"\n            android:paddingRight=\"6dp\"\n            android:paddingBottom=\"4dp\"\n            android:text=\"@string/text_indent\"\n            android:textSize=\"14sp\"\n            app:isBottomBackground=\"true\"\n            app:radius=\"3dp\" />\n\n        <Space\n            android:layout_width=\"0dp\"\n            android:layout_height=\"wrap_content\"\n            android:layout_weight=\"1\" />\n\n        <io.legado.app.ui.book.read.config.ChineseConverter\n            android:id=\"@+id/chinese_converter\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:gravity=\"center\"\n            android:paddingLeft=\"6dp\"\n            android:paddingTop=\"4dp\"\n            android:paddingRight=\"6dp\"\n            android:paddingBottom=\"4dp\"\n            android:textSize=\"14sp\"\n            app:isBottomBackground=\"true\"\n            app:radius=\"3dp\" />\n\n        <Space\n            android:layout_width=\"0dp\"\n            android:layout_height=\"wrap_content\"\n            android:layout_weight=\"1\" />\n\n        <io.legado.app.ui.widget.text.StrokeTextView\n            android:id=\"@+id/tv_padding\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:gravity=\"center\"\n            android:paddingLeft=\"6dp\"\n            android:paddingTop=\"4dp\"\n            android:paddingRight=\"6dp\"\n            android:paddingBottom=\"4dp\"\n            android:text=\"@string/padding\"\n            android:textSize=\"14sp\"\n            app:isBottomBackground=\"true\"\n            app:radius=\"3dp\" />\n\n        <Space\n            android:layout_width=\"0dp\"\n            android:layout_height=\"wrap_content\"\n            android:layout_weight=\"1\" />\n\n        <io.legado.app.ui.widget.text.StrokeTextView\n            android:id=\"@+id/tv_tip\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:gravity=\"center\"\n            android:paddingLeft=\"6dp\"\n            android:paddingTop=\"4dp\"\n            android:paddingRight=\"6dp\"\n            android:paddingBottom=\"4dp\"\n            android:text=\"@string/information\"\n            android:textSize=\"14sp\"\n            app:isBottomBackground=\"true\"\n            app:radius=\"3dp\" />\n\n    </LinearLayout>\n\n    <io.legado.app.ui.widget.DetailSeekBar\n        android:id=\"@+id/dsb_text_size\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginHorizontal=\"16dp\"\n        android:layout_marginTop=\"8dp\"\n        app:isBottomBackground=\"true\"\n        app:max=\"45\"\n        app:title=\"@string/text_size\" />\n\n    <io.legado.app.ui.widget.DetailSeekBar\n        android:id=\"@+id/dsb_text_letter_spacing\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginHorizontal=\"16dp\"\n        app:isBottomBackground=\"true\"\n        app:max=\"100\"\n        app:title=\"@string/text_letter_spacing\" />\n\n    <io.legado.app.ui.widget.DetailSeekBar\n        android:id=\"@+id/dsb_line_size\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginHorizontal=\"16dp\"\n        app:isBottomBackground=\"true\"\n        app:max=\"20\"\n        app:title=\"@string/line_size\" />\n\n    <io.legado.app.ui.widget.DetailSeekBar\n        android:id=\"@+id/dsb_paragraph_spacing\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginHorizontal=\"16dp\"\n        app:isBottomBackground=\"true\"\n        app:max=\"20\"\n        app:title=\"@string/paragraph_size\" />\n\n    <View\n        android:id=\"@+id/vw_bg_fg\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"0.8dp\"\n        android:layout_marginHorizontal=\"16dp\"\n        android:layout_marginVertical=\"8dp\"\n        android:background=\"@color/btn_bg_press\" />\n\n    <TextView\n        android:id=\"@+id/tv_page_anim\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginHorizontal=\"16dp\"\n        android:layout_marginBottom=\"4dp\"\n        android:alpha=\"0.75\"\n        android:text=\"@string/page_anim\"\n        android:textSize=\"12sp\" />\n\n    <RadioGroup\n        android:id=\"@+id/rg_page_anim\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginHorizontal=\"11dp\"\n        android:gravity=\"center\"\n        android:orientation=\"horizontal\">\n\n        <io.legado.app.lib.theme.view.ThemeRadioNoButton\n            android:id=\"@+id/rb_anim0\"\n            android:layout_width=\"0dp\"\n            android:layout_height=\"wrap_content\"\n            android:layout_margin=\"4dp\"\n            android:layout_weight=\"1\"\n            android:button=\"@null\"\n            android:gravity=\"center\"\n            android:padding=\"5dp\"\n            android:singleLine=\"true\"\n            android:text=\"@string/page_anim_cover\"\n            app:isBottomBackground=\"true\" />\n\n        <io.legado.app.lib.theme.view.ThemeRadioNoButton\n            android:id=\"@+id/rb_anim1\"\n            android:layout_width=\"0dp\"\n            android:layout_height=\"wrap_content\"\n            android:layout_margin=\"4dp\"\n            android:layout_weight=\"1\"\n            android:button=\"@null\"\n            android:gravity=\"center\"\n            android:padding=\"5dp\"\n            android:singleLine=\"true\"\n            android:text=\"@string/page_anim_slide\"\n            app:isBottomBackground=\"true\" />\n\n        <io.legado.app.lib.theme.view.ThemeRadioNoButton\n            android:id=\"@+id/rb_simulation_anim\"\n            android:layout_width=\"0dp\"\n            android:layout_height=\"wrap_content\"\n            android:layout_margin=\"4dp\"\n            android:layout_weight=\"1\"\n            android:button=\"@null\"\n            android:gravity=\"center\"\n            android:padding=\"5dp\"\n            android:singleLine=\"true\"\n            android:text=\"@string/page_anim_simulation\"\n            app:isBottomBackground=\"true\" />\n\n        <io.legado.app.lib.theme.view.ThemeRadioNoButton\n            android:id=\"@+id/rb_scroll_anim\"\n            android:layout_width=\"0dp\"\n            android:layout_height=\"wrap_content\"\n            android:layout_margin=\"4dp\"\n            android:layout_weight=\"1\"\n            android:button=\"@null\"\n            android:gravity=\"center\"\n            android:padding=\"5dp\"\n            android:singleLine=\"true\"\n            android:text=\"@string/page_anim_scroll\"\n            app:isBottomBackground=\"true\" />\n\n        <io.legado.app.lib.theme.view.ThemeRadioNoButton\n            android:id=\"@+id/rb_no_anim\"\n            android:layout_width=\"0dp\"\n            android:layout_height=\"wrap_content\"\n            android:layout_margin=\"4dp\"\n            android:layout_weight=\"1\"\n            android:button=\"@null\"\n            android:gravity=\"center\"\n            android:padding=\"5dp\"\n            android:singleLine=\"true\"\n            android:text=\"@string/page_anim_none\"\n            app:isBottomBackground=\"true\" />\n\n    </RadioGroup>\n\n    <View\n        android:id=\"@+id/vw_bg_fg1\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"0.8dp\"\n        android:layout_marginHorizontal=\"16dp\"\n        android:layout_marginVertical=\"8dp\"\n        android:background=\"@color/btn_bg_press\" />\n\n    <LinearLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:gravity=\"center_vertical\"\n        android:orientation=\"horizontal\">\n\n        <TextView\n            android:id=\"@+id/tv_bg_ts\"\n            android:layout_width=\"0dp\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginStart=\"16dp\"\n            android:layout_weight=\"1\"\n            android:alpha=\"0.75\"\n            android:text=\"@string/text_bg_style\"\n            android:textSize=\"12sp\" />\n\n        <TextView\n            android:id=\"@+id/tv_share_layout\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:text=\"@string/share_layout\" />\n\n        <io.legado.app.ui.widget.checkbox.SmoothCheckBox\n            android:id=\"@+id/cb_share_layout\"\n            android:layout_width=\"20dp\"\n            android:layout_height=\"20dp\"\n            android:layout_marginStart=\"6dp\"\n            android:layout_marginEnd=\"16dp\" />\n\n    </LinearLayout>\n\n    <androidx.recyclerview.widget.RecyclerView\n        android:id=\"@+id/rv_style\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginTop=\"8dp\"\n        android:layout_marginBottom=\"16dp\"\n        android:clipToPadding=\"false\"\n        android:orientation=\"horizontal\"\n        android:paddingHorizontal=\"12dp\"\n        app:layoutManager=\"androidx.recyclerview.widget.LinearLayoutManager\"\n        tool:listitem=\"@layout/item_read_style\" />\n\n</LinearLayout>"
  },
  {
    "path": "app/src/main/res/layout/dialog_read_padding.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.core.widget.NestedScrollView xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\">\n\n    <LinearLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:orientation=\"vertical\"\n        android:padding=\"10dp\">\n\n        <LinearLayout\n            android:id=\"@+id/ll_header_padding\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:orientation=\"vertical\">\n\n            <LinearLayout\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:gravity=\"center_vertical\"\n                android:orientation=\"horizontal\"\n                android:paddingBottom=\"10dp\">\n\n                <io.legado.app.ui.widget.text.AccentTextView\n                    android:id=\"@+id/tv_header_padding\"\n                    android:layout_width=\"0dp\"\n                    android:layout_height=\"wrap_content\"\n                    android:layout_weight=\"1\"\n                    android:text=\"@string/header\"\n                    android:textSize=\"18sp\" />\n\n                <TextView\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"wrap_content\"\n                    android:text=\"@string/showLine\" />\n\n                <io.legado.app.ui.widget.checkbox.SmoothCheckBox\n                    android:id=\"@+id/cb_show_top_line\"\n                    android:layout_width=\"20dp\"\n                    android:layout_height=\"20dp\"\n                    android:layout_margin=\"6dp\" />\n\n            </LinearLayout>\n\n            <io.legado.app.ui.widget.DetailSeekBar\n                android:id=\"@+id/dsb_header_padding_top\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                app:max=\"100\"\n                app:title=\"@string/padding_top\" />\n\n            <io.legado.app.ui.widget.DetailSeekBar\n                android:id=\"@+id/dsb_header_padding_bottom\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                app:max=\"100\"\n                app:title=\"@string/padding_bottom\" />\n\n            <io.legado.app.ui.widget.DetailSeekBar\n                android:id=\"@+id/dsb_header_padding_left\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                app:max=\"100\"\n                app:title=\"@string/padding_left\" />\n\n            <io.legado.app.ui.widget.DetailSeekBar\n                android:id=\"@+id/dsb_header_padding_right\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                app:max=\"100\"\n                app:title=\"@string/padding_right\" />\n\n        </LinearLayout>\n\n        <io.legado.app.ui.widget.text.AccentTextView\n            android:id=\"@+id/tv_body_padding\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:paddingTop=\"10dp\"\n            android:paddingBottom=\"10dp\"\n            android:text=\"@string/main_body\"\n            android:textSize=\"18sp\" />\n\n        <io.legado.app.ui.widget.DetailSeekBar\n            android:id=\"@+id/dsb_padding_top\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            app:max=\"200\"\n            app:title=\"@string/padding_top\" />\n\n        <io.legado.app.ui.widget.DetailSeekBar\n            android:id=\"@+id/dsb_padding_bottom\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            app:max=\"100\"\n            app:title=\"@string/padding_bottom\" />\n\n        <io.legado.app.ui.widget.DetailSeekBar\n            android:id=\"@+id/dsb_padding_left\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            app:max=\"100\"\n            app:title=\"@string/padding_left\" />\n\n        <io.legado.app.ui.widget.DetailSeekBar\n            android:id=\"@+id/dsb_padding_right\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            app:max=\"100\"\n            app:title=\"@string/padding_right\" />\n\n        <LinearLayout\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:gravity=\"center_vertical\"\n            android:orientation=\"horizontal\">\n\n            <io.legado.app.ui.widget.text.AccentTextView\n                android:layout_width=\"0dp\"\n                android:layout_height=\"wrap_content\"\n                android:layout_weight=\"1\"\n                android:paddingTop=\"10dp\"\n                android:paddingBottom=\"10dp\"\n                android:text=\"@string/footer\"\n                android:textSize=\"18sp\" />\n\n            <TextView\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:text=\"@string/showLine\" />\n\n            <io.legado.app.ui.widget.checkbox.SmoothCheckBox\n                android:id=\"@+id/cb_show_bottom_line\"\n                android:layout_width=\"20dp\"\n                android:layout_height=\"20dp\"\n                android:layout_margin=\"6dp\" />\n\n        </LinearLayout>\n\n        <io.legado.app.ui.widget.DetailSeekBar\n            android:id=\"@+id/dsb_footer_padding_top\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            app:max=\"100\"\n            app:title=\"@string/padding_top\" />\n\n        <io.legado.app.ui.widget.DetailSeekBar\n            android:id=\"@+id/dsb_footer_padding_bottom\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            app:max=\"100\"\n            app:title=\"@string/padding_bottom\" />\n\n        <io.legado.app.ui.widget.DetailSeekBar\n            android:id=\"@+id/dsb_footer_padding_left\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            app:max=\"100\"\n            app:title=\"@string/padding_left\" />\n\n        <io.legado.app.ui.widget.DetailSeekBar\n            android:id=\"@+id/dsb_footer_padding_right\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            app:max=\"100\"\n            app:title=\"@string/padding_right\" />\n\n    </LinearLayout>\n\n</androidx.core.widget.NestedScrollView>\n"
  },
  {
    "path": "app/src/main/res/layout/dialog_recycler_view.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:orientation=\"vertical\">\n\n    <androidx.appcompat.widget.Toolbar\n        android:id=\"@+id/tool_bar\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:theme=\"?attr/actionBarStyle\"\n        app:popupTheme=\"@style/AppTheme.PopupOverlay\"\n        app:titleTextAppearance=\"@style/ToolbarTitle\" />\n\n    <FrameLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"0dp\"\n        android:layout_weight=\"1\">\n\n        <io.legado.app.ui.widget.recycler.scroller.FastScrollRecyclerView\n            android:id=\"@+id/recycler_view\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"match_parent\"\n            android:scrollbars=\"none\"\n            tools:ignore=\"SpeakableTextPresentCheck\" />\n\n        <io.legado.app.ui.widget.anima.RotateLoading\n            android:id=\"@+id/rotate_loading\"\n            android:layout_width=\"36dp\"\n            android:layout_height=\"36dp\"\n            android:layout_margin=\"6dp\"\n            android:visibility=\"gone\"\n            android:layout_gravity=\"center\"\n            app:loading_width=\"2dp\" />\n\n        <TextView\n            android:id=\"@+id/tv_msg\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:padding=\"16dp\"\n            android:layout_gravity=\"center\"\n            android:visibility=\"gone\"\n            android:textColor=\"@color/secondaryText\" />\n\n    </FrameLayout>\n\n    <com.google.android.flexbox.FlexboxLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:paddingLeft=\"12dp\"\n        android:paddingRight=\"12dp\"\n        app:flexWrap=\"wrap\"\n        app:justifyContent=\"space_between\">\n\n        <io.legado.app.ui.widget.text.AccentTextView\n            android:id=\"@+id/tv_footer_left\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:padding=\"12dp\"\n            android:visibility=\"gone\"\n            tools:ignore=\"RtlHardcoded\" />\n\n        <Space\n            android:layout_width=\"0dp\"\n            android:layout_height=\"0dp\" />\n\n        <LinearLayout\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:orientation=\"horizontal\">\n\n            <TextView\n                android:id=\"@+id/tv_cancel\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:padding=\"12dp\"\n                android:text=\"@string/cancel\"\n                android:textColor=\"@color/secondaryText\"\n                android:visibility=\"gone\"\n                tools:ignore=\"RtlHardcoded\" />\n\n            <io.legado.app.ui.widget.text.AccentTextView\n                android:id=\"@+id/tv_ok\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:padding=\"12dp\"\n                android:text=\"@string/ok\"\n                android:visibility=\"gone\"\n                tools:ignore=\"RtlHardcoded\" />\n\n        </LinearLayout>\n\n    </com.google.android.flexbox.FlexboxLayout>\n</LinearLayout>"
  },
  {
    "path": "app/src/main/res/layout/dialog_rss_favorite_config.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<FrameLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:padding=\"16dp\">\n\n    <LinearLayout\n        android:id=\"@+id/vw_bg\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:layout_gravity=\"center\"\n        android:background=\"@drawable/shape_card_view\"\n        android:orientation=\"vertical\"\n        tools:ignore=\"UselessParent\">\n\n        <androidx.appcompat.widget.Toolbar\n            android:id=\"@+id/tool_bar\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:theme=\"?attr/actionBarStyle\"\n            app:popupTheme=\"@style/AppTheme.PopupOverlay\"\n            app:title=\"@string/favorite\"\n            app:titleTextAppearance=\"@style/ToolbarTitle\" />\n\n        <androidx.core.widget.NestedScrollView\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"0dp\"\n            android:layout_weight=\"1\"\n            android:overScrollMode=\"ifContentScrolls\">\n\n            <LinearLayout\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:orientation=\"vertical\"\n                android:padding=\"16dp\">\n\n                <io.legado.app.ui.widget.text.TextInputLayout\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"wrap_content\"\n                    android:paddingTop=\"3dp\">\n\n                    <io.legado.app.lib.theme.view.ThemeEditText\n                        android:id=\"@+id/edit_title\"\n                        android:layout_width=\"match_parent\"\n                        android:layout_height=\"wrap_content\"\n                        android:hint=\"@string/title\"\n                        tools:ignore=\"SpeakableTextPresentCheck,TouchTargetSizeCheck\" />\n\n                </io.legado.app.ui.widget.text.TextInputLayout>\n\n                <io.legado.app.ui.widget.text.TextInputLayout\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"wrap_content\"\n                    android:paddingTop=\"3dp\">\n\n                    <io.legado.app.lib.theme.view.ThemeEditText\n                        android:id=\"@+id/edit_group\"\n                        android:layout_width=\"match_parent\"\n                        android:layout_height=\"wrap_content\"\n                        android:hint=\"@string/group_name\"\n                        tools:ignore=\"SpeakableTextPresentCheck,TouchTargetSizeCheck\" />\n\n                </io.legado.app.ui.widget.text.TextInputLayout>\n\n            </LinearLayout>\n\n        </androidx.core.widget.NestedScrollView>\n\n        <com.google.android.flexbox.FlexboxLayout\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:paddingLeft=\"12dp\"\n            android:paddingRight=\"12dp\"\n            app:flexWrap=\"wrap\"\n            app:justifyContent=\"space_between\">\n\n            <io.legado.app.ui.widget.text.AccentTextView\n                android:id=\"@+id/tv_footer_left\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:padding=\"12dp\"\n                android:text=\"@string/delete\"\n                tools:ignore=\"RtlHardcoded\" />\n\n            <LinearLayout\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:orientation=\"horizontal\">\n\n                <io.legado.app.ui.widget.text.AccentTextView\n                    android:id=\"@+id/tv_cancel\"\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"wrap_content\"\n                    android:padding=\"12dp\"\n                    android:text=\"@string/cancel\"\n                    tools:ignore=\"RtlHardcoded\" />\n\n                <io.legado.app.ui.widget.text.AccentTextView\n                    android:id=\"@+id/tv_ok\"\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"wrap_content\"\n                    android:padding=\"12dp\"\n                    android:text=\"@string/ok\"\n                    tools:ignore=\"RtlHardcoded\" />\n\n            </LinearLayout>\n\n        </com.google.android.flexbox.FlexboxLayout>\n    </LinearLayout>\n</FrameLayout>"
  },
  {
    "path": "app/src/main/res/layout/dialog_rule_sub_edit.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:orientation=\"vertical\"\n    android:padding=\"16dp\">\n\n    <LinearLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:orientation=\"horizontal\">\n\n        <io.legado.app.ui.widget.text.AccentTextView\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:text=\"@string/book_type\"\n            android:padding=\"3dp\"\n            tools:ignore=\"RtlHardcoded\" />\n\n        <androidx.appcompat.widget.AppCompatSpinner\n            android:id=\"@+id/sp_type\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:entries=\"@array/rule_type\"\n            android:theme=\"@style/Spinner\" />\n\n    </LinearLayout>\n\n    <io.legado.app.ui.widget.text.TextInputLayout\n        android:id=\"@+id/til_name\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:hint=\"@string/name\">\n\n        <io.legado.app.lib.theme.view.ThemeEditText\n            android:id=\"@+id/et_name\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\" />\n    </io.legado.app.ui.widget.text.TextInputLayout>\n\n    <io.legado.app.ui.widget.text.TextInputLayout\n        android:id=\"@+id/til_url\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:hint=\"Url\"\n        tools:ignore=\"HardcodedText\">\n\n        <io.legado.app.lib.theme.view.ThemeEditText\n            android:id=\"@+id/et_url\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\" />\n    </io.legado.app.ui.widget.text.TextInputLayout>\n\n</LinearLayout>"
  },
  {
    "path": "app/src/main/res/layout/dialog_search_scope.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:background=\"@color/background\">\n\n    <androidx.appcompat.widget.Toolbar\n        android:id=\"@+id/tool_bar\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:background=\"@color/background_menu\"\n        android:elevation=\"5dp\"\n        android:theme=\"?attr/actionBarStyle\"\n        app:displayHomeAsUp=\"false\"\n        app:fitStatusBar=\"false\"\n        app:layout_constraintTop_toTopOf=\"parent\"\n        app:popupTheme=\"@style/AppTheme.PopupOverlay\"\n        app:title=\"@string/search_scope\"\n        app:titleTextAppearance=\"@style/ToolbarTitle\" />\n\n    <RadioGroup\n        android:id=\"@+id/rg_scope\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginHorizontal=\"11dp\"\n        android:gravity=\"center\"\n        android:orientation=\"horizontal\"\n        app:layout_constraintTop_toBottomOf=\"@+id/tool_bar\">\n\n        <io.legado.app.lib.theme.view.ThemeRadioNoButton\n            android:id=\"@+id/rb_group\"\n            android:layout_width=\"0dp\"\n            android:layout_height=\"wrap_content\"\n            android:layout_margin=\"4dp\"\n            android:layout_weight=\"1\"\n            android:button=\"@null\"\n            android:gravity=\"center\"\n            android:padding=\"5dp\"\n            android:singleLine=\"true\"\n            android:checked=\"true\"\n            android:text=\"@string/group\" />\n\n        <io.legado.app.lib.theme.view.ThemeRadioNoButton\n            android:id=\"@+id/rb_source\"\n            android:layout_width=\"0dp\"\n            android:layout_height=\"wrap_content\"\n            android:layout_margin=\"4dp\"\n            android:layout_weight=\"1\"\n            android:button=\"@null\"\n            android:gravity=\"center\"\n            android:padding=\"5dp\"\n            android:singleLine=\"true\"\n            android:text=\"@string/book_source\" />\n\n    </RadioGroup>\n\n    <io.legado.app.ui.widget.recycler.scroller.FastScrollRecyclerView\n        android:id=\"@+id/recycler_view\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"0dp\"\n        app:layoutManager=\"androidx.recyclerview.widget.LinearLayoutManager\"\n        app:layout_constraintBottom_toTopOf=\"@+id/ll_bottom_bar\"\n        app:layout_constraintTop_toBottomOf=\"@+id/rg_scope\" />\n\n    <LinearLayout\n        android:id=\"@+id/ll_bottom_bar\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:background=\"@color/background\"\n        android:gravity=\"center_vertical\"\n        android:orientation=\"horizontal\"\n        android:paddingLeft=\"10dp\"\n        android:paddingRight=\"10dp\"\n        app:layout_constraintBottom_toBottomOf=\"parent\">\n\n        <io.legado.app.ui.widget.text.AccentTextView\n            android:id=\"@+id/tv_all_source\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginRight=\"16dp\"\n            android:padding=\"12dp\"\n            android:text=\"@string/all_source\"\n            tools:ignore=\"RtlHardcoded\" />\n\n        <Space\n            android:layout_width=\"0dp\"\n            android:layout_height=\"wrap_content\"\n            android:layout_weight=\"1\" />\n\n        <TextView\n            android:id=\"@+id/tv_cancel\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginRight=\"16dp\"\n            android:padding=\"12dp\"\n            android:text=\"@string/cancel\"\n            android:textColor=\"@color/secondaryText\"\n            tools:ignore=\"RtlHardcoded\" />\n\n        <io.legado.app.ui.widget.text.AccentTextView\n            android:id=\"@+id/tv_ok\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginRight=\"16dp\"\n            android:padding=\"12dp\"\n            android:text=\"@string/ok\"\n            tools:ignore=\"RtlHardcoded\" />\n\n    </LinearLayout>\n\n\n</androidx.constraintlayout.widget.ConstraintLayout>"
  },
  {
    "path": "app/src/main/res/layout/dialog_select_section_export.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:padding=\"16dp\">\n\n    <LinearLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:orientation=\"vertical\">\n\n        <LinearLayout\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginTop=\"12dp\"\n            android:orientation=\"horizontal\">\n\n            <io.legado.app.ui.widget.checkbox.SmoothCheckBox\n                android:id=\"@+id/cb_all_export\"\n                android:layout_width=\"20dp\"\n                android:layout_height=\"20dp\"\n                android:layout_gravity=\"center\"\n                android:layout_marginStart=\"6dp\"\n                android:layout_marginEnd=\"16dp\" />\n\n            <TextView\n                android:id=\"@+id/tv_all_export\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:text=\"@string/export_all\"\n                android:textSize=\"18sp\" />\n\n        </LinearLayout>\n\n        <LinearLayout\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginTop=\"12dp\"\n            android:orientation=\"horizontal\">\n\n            <io.legado.app.ui.widget.checkbox.SmoothCheckBox\n                android:id=\"@+id/cb_select_export\"\n                android:layout_width=\"20dp\"\n                android:layout_height=\"20dp\"\n                android:layout_gravity=\"center\"\n                android:layout_marginStart=\"6dp\"\n                android:layout_marginEnd=\"16dp\" />\n\n            <TextView\n                android:id=\"@+id/tv_select_export\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:text=\"@string/custom_export\"\n                android:textSize=\"18sp\" />\n\n        </LinearLayout>\n\n        <io.legado.app.ui.widget.text.TextInputLayout\n            android:id=\"@+id/ly_et_epub_filename\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginTop=\"12dp\"\n            android:hint=\"@string/export_file_name\"\n            app:endIconCheckable=\"true\"\n            app:endIconContentDescription=\"Execute script\"\n            app:endIconDrawable=\"@drawable/ic_play_24dp\"\n            app:endIconMode=\"custom\"\n            app:helperText=\"Variable: name, author, epubIndex\">\n\n            <io.legado.app.lib.theme.view.ThemeEditText\n                android:id=\"@+id/et_epub_filename\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:enabled=\"false\"\n                android:inputType=\"text\"\n                tools:ignore=\"SpeakableTextPresentCheck,TouchTargetSizeCheck\" />\n\n        </io.legado.app.ui.widget.text.TextInputLayout>\n\n\n        <io.legado.app.ui.widget.text.TextInputLayout\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginTop=\"12dp\"\n            android:hint=\"@string/file_contains_number\">\n\n            <io.legado.app.lib.theme.view.ThemeEditText\n                android:id=\"@+id/et_epub_size\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:inputType=\"number\"\n                android:maxLength=\"6\"\n                tools:ignore=\"SpeakableTextPresentCheck,TouchTargetSizeCheck\" />\n\n        </io.legado.app.ui.widget.text.TextInputLayout>\n\n        <io.legado.app.ui.widget.text.TextInputLayout\n            android:id=\"@+id/ly_et_input_scope\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:hint=\"@string/export_chapter_index\">\n\n            <io.legado.app.lib.theme.view.ThemeEditText\n                android:id=\"@+id/et_input_scope\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                tools:ignore=\"SpeakableTextPresentCheck,TouchTargetSizeCheck\" />\n\n        </io.legado.app.ui.widget.text.TextInputLayout>\n\n    </LinearLayout>\n\n\n</androidx.constraintlayout.widget.ConstraintLayout>"
  },
  {
    "path": "app/src/main/res/layout/dialog_simulated_reading.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:id=\"@+id/ll_content\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:gravity=\"center\"\n    android:padding=\"20dp\"\n    android:orientation=\"vertical\">\n\n    <LinearLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"60dp\"\n        android:gravity=\"center\"\n        android:orientation=\"horizontal\"\n        android:padding=\"16dp\">\n\n        <TextView\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_gravity=\"center_vertical\"\n            android:layout_marginRight=\"2dp\"\n            android:text=\"@string/switch_on\"\n            android:textColor=\"@color/primaryText\"\n            android:textSize=\"16sp\"\n            tools:ignore=\"RtlHardcoded\"\n            android:minWidth=\"88dp\" />\n\n        <LinearLayout\n            android:orientation=\"horizontal\"\n            android:layout_width=\"53dp\"\n            android:layout_height=\"match_parent\"\n            android:gravity=\"center_vertical\">\n\n            <androidx.appcompat.widget.SwitchCompat\n                android:layout_height=\"wrap_content\"\n                android:id=\"@+id/sr_enabled\"\n                android:layout_weight=\"1\"\n                android:gravity=\"start|center_vertical\"\n                style=\"@style/Widget.AppCompat.CompoundButton.Switch\"\n                android:layout_width=\"match_parent\"\n                android:width=\"20dp\" />\n        </LinearLayout>\n\n        <View\n            android:layout_width=\"0dp\"\n            android:layout_height=\"0dp\"\n            android:layout_weight=\"1\" />\n\n        <View\n            android:layout_width=\"0dp\"\n            android:layout_height=\"0dp\"\n            android:layout_weight=\"1\" />\n\n    </LinearLayout>\n\n    <LinearLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"65dp\"\n        android:orientation=\"horizontal\"\n        android:padding=\"16dp\"\n        android:gravity=\"start|center_vertical\">\n\n        <TextView\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_gravity=\"center_vertical\"\n            android:text=\"@string/start_from\"\n            android:textColor=\"@color/primaryText\"\n            android:textSize=\"16sp\"\n            android:gravity=\"center_vertical\"\n            android:minWidth=\"100dp\" />\n\n        <View\n            android:layout_width=\"0dp\"\n            android:layout_height=\"0dp\"\n            android:layout_weight=\"1\" />\n\n        <EditText\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"49dp\"\n            android:inputType=\"none\"\n            android:ems=\"10\"\n            android:id=\"@+id/start_date\"\n            android:hint=\"Select date\"\n            android:layout_weight=\"1\"\n            android:clickable=\"true\"\n            android:textSize=\"16sp\"\n            android:layout_marginRight=\"68dp\" />\n\n        <View\n            android:layout_width=\"0dp\"\n            android:layout_height=\"0dp\"\n            android:layout_weight=\"1\" />\n\n    </LinearLayout>\n\n    <LinearLayout\n        android:orientation=\"horizontal\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"63dp\"\n        android:gravity=\"center_horizontal|left\"\n        android:padding=\"16dp\">\n\n        <LinearLayout\n            android:orientation=\"horizontal\"\n            android:layout_width=\"100dp\"\n            android:layout_height=\"match_parent\"\n            android:minWidth=\"100dp\">\n\n            <TextView\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:layout_gravity=\"center_vertical\"\n                android:layout_marginRight=\"2dp\"\n                android:text=\"@string/start_chapter\"\n                android:textColor=\"@color/primaryText\"\n                android:textSize=\"16sp\"\n                tools:ignore=\"RtlHardcoded\" />\n\n        </LinearLayout>\n\n        <EditText\n            android:id=\"@+id/edit_start\"\n            android:layout_width=\"47dp\"\n            android:layout_height=\"wrap_content\"\n            android:layout_gravity=\"center\"\n            android:background=\"@drawable/bg_edit\"\n            android:hint=\"@string/start\"\n            android:importantForAutofill=\"no\"\n            android:inputType=\"number\"\n            android:lines=\"1\"\n            android:maxLength=\"5\"\n            android:minWidth=\"60dp\"\n            android:paddingLeft=\"5dp\"\n            android:paddingTop=\"4dp\"\n            android:paddingRight=\"5dp\"\n            android:paddingBottom=\"4dp\"\n            android:textColor=\"@color/primaryText\"\n            android:textCursorDrawable=\"@drawable/shape_text_cursor\"\n            android:textSize=\"14sp\"\n            tools:ignore=\"TouchTargetSizeCheck,TextContrastCheck\" />\n\n        <LinearLayout\n            android:orientation=\"horizontal\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"match_parent\"\n            android:gravity=\"end\">\n\n            <TextView\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:layout_gravity=\"center_vertical\"\n                android:layout_marginLeft=\"25dp\"\n                android:layout_marginRight=\"5dp\"\n                android:text=\"@string/daily_chapters\"\n                android:textColor=\"@color/primaryText\"\n                android:textSize=\"16sp\" />\n\n            <EditText\n                android:id=\"@+id/edit_num\"\n                android:layout_width=\"42dp\"\n                android:layout_height=\"wrap_content\"\n                android:layout_gravity=\"center\"\n                android:background=\"@drawable/bg_edit\"\n                android:hint=\"3\"\n                android:importantForAutofill=\"no\"\n                android:inputType=\"number\"\n                android:lines=\"1\"\n                android:maxLength=\"5\"\n                android:minWidth=\"60dp\"\n                android:paddingLeft=\"5dp\"\n                android:paddingTop=\"4dp\"\n                android:paddingRight=\"5dp\"\n                android:paddingBottom=\"4dp\"\n                android:textColor=\"@color/primaryText\"\n                android:textCursorDrawable=\"@drawable/shape_text_cursor\"\n                android:textSize=\"14sp\"\n                tools:ignore=\"TouchTargetSizeCheck,SpeakableTextPresentCheck,TextContrastCheck\"\n                android:textAlignment=\"center\" />\n        </LinearLayout>\n\n    </LinearLayout>\n\n</LinearLayout>\n"
  },
  {
    "path": "app/src/main/res/layout/dialog_source_picker.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:orientation=\"vertical\">\n\n    <io.legado.app.ui.widget.TitleBar\n        android:id=\"@+id/tool_bar\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:theme=\"?attr/actionBarStyle\"\n        app:attachToActivity=\"false\"\n        app:contentLayout=\"@layout/view_search\"\n        app:fitStatusBar=\"false\"\n        app:popupTheme=\"@style/AppTheme.PopupOverlay\"\n        app:titleTextAppearance=\"@style/ToolbarTitle\"\n        tools:ignore=\"RtlHardcoded,RtlSymmetry\" />\n\n    <FrameLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"0dp\"\n        android:layout_weight=\"1\">\n\n        <io.legado.app.ui.widget.recycler.scroller.FastScrollRecyclerView\n            android:id=\"@+id/recycler_view\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"match_parent\"\n            android:scrollbars=\"none\"\n            tools:ignore=\"SpeakableTextPresentCheck\" />\n\n        <io.legado.app.ui.widget.anima.RotateLoading\n            android:id=\"@+id/rotate_loading\"\n            android:layout_width=\"36dp\"\n            android:layout_height=\"36dp\"\n            android:layout_gravity=\"center\"\n            android:layout_margin=\"6dp\"\n            android:visibility=\"gone\"\n            app:loading_width=\"2dp\" />\n\n        <TextView\n            android:id=\"@+id/tv_msg\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:layout_gravity=\"center\"\n            android:padding=\"16dp\"\n            android:textColor=\"@color/secondaryText\"\n            android:visibility=\"gone\" />\n\n    </FrameLayout>\n\n    <com.google.android.flexbox.FlexboxLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:paddingLeft=\"12dp\"\n        android:paddingRight=\"12dp\"\n        app:flexWrap=\"wrap\"\n        app:justifyContent=\"space_between\">\n\n        <io.legado.app.ui.widget.text.AccentTextView\n            android:id=\"@+id/tv_footer_left\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:padding=\"12dp\"\n            android:visibility=\"gone\"\n            tools:ignore=\"RtlHardcoded\" />\n\n        <Space\n            android:layout_width=\"0dp\"\n            android:layout_height=\"0dp\" />\n\n        <LinearLayout\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:orientation=\"horizontal\">\n\n            <io.legado.app.ui.widget.text.AccentTextView\n                android:id=\"@+id/tv_cancel\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:padding=\"12dp\"\n                android:text=\"@string/cancel\"\n                android:visibility=\"gone\"\n                tools:ignore=\"RtlHardcoded\" />\n\n            <io.legado.app.ui.widget.text.AccentTextView\n                android:id=\"@+id/tv_ok\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:padding=\"12dp\"\n                android:text=\"@string/ok\"\n                android:visibility=\"gone\"\n                tools:ignore=\"RtlHardcoded\" />\n\n        </LinearLayout>\n\n    </com.google.android.flexbox.FlexboxLayout>\n</LinearLayout>"
  },
  {
    "path": "app/src/main/res/layout/dialog_text_view.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:background=\"@color/background\">\n\n    <androidx.appcompat.widget.Toolbar\n        android:id=\"@+id/tool_bar\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:theme=\"?attr/actionBarStyle\"\n        app:popupTheme=\"@style/AppTheme.PopupOverlay\"\n        app:titleTextAppearance=\"@style/ToolbarTitle\"\n        app:layout_constraintTop_toTopOf=\"parent\" />\n\n    <io.legado.app.ui.widget.text.ScrollTextView\n        android:id=\"@+id/text_view\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"0dp\"\n        android:padding=\"12dp\"\n        android:textColor=\"@color/secondaryText\"\n        android:scrollbarStyle=\"outsideOverlay\"\n        android:textIsSelectable=\"true\"\n        app:layout_constraintTop_toBottomOf=\"@+id/tool_bar\"\n        app:layout_constraintBottom_toBottomOf=\"parent\"/>\n\n    <io.legado.app.ui.widget.text.BadgeView\n        android:id=\"@+id/badge_view\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_margin=\"3dp\"\n        android:visibility=\"invisible\"\n        app:layout_constraintRight_toRightOf=\"parent\"\n        app:layout_constraintTop_toTopOf=\"parent\" />\n\n</androidx.constraintlayout.widget.ConstraintLayout>\n\n\n"
  },
  {
    "path": "app/src/main/res/layout/dialog_tip_config.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.core.widget.NestedScrollView xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\">\n\n    <LinearLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:orientation=\"vertical\"\n        android:padding=\"16dp\">\n\n        <io.legado.app.ui.widget.text.AccentTextView\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:text=\"@string/body_title\"\n            android:textSize=\"18sp\" />\n\n        <RadioGroup\n            android:id=\"@+id/rg_title_mode\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:orientation=\"horizontal\">\n\n            <RadioButton\n                android:id=\"@+id/rb_title_mode1\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:padding=\"3dp\"\n                android:text=\"@string/title_left\" />\n\n            <RadioButton\n                android:id=\"@+id/rb_title_mode2\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:padding=\"3dp\"\n                android:text=\"@string/title_center\" />\n\n            <RadioButton\n                android:id=\"@+id/rb_title_mode3\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:padding=\"3dp\"\n                android:text=\"@string/title_hide\" />\n        </RadioGroup>\n\n        <io.legado.app.ui.widget.DetailSeekBar\n            android:id=\"@+id/dsb_title_size\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:padding=\"3dp\"\n            app:max=\"10\"\n            app:title=\"@string/title_font_size\" />\n\n        <io.legado.app.ui.widget.DetailSeekBar\n            android:id=\"@+id/dsb_title_top\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:padding=\"3dp\"\n            app:max=\"100\"\n            app:title=\"@string/title_margin_top\" />\n\n        <io.legado.app.ui.widget.DetailSeekBar\n            android:id=\"@+id/dsb_title_bottom\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:padding=\"3dp\"\n            app:max=\"100\"\n            app:title=\"@string/title_margin_bottom\" />\n\n        <io.legado.app.ui.widget.text.AccentTextView\n            android:id=\"@+id/tv_header_padding\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:text=\"@string/header\"\n            android:textSize=\"18sp\" />\n\n        <LinearLayout\n            android:id=\"@+id/ll_header_show\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:gravity=\"center_vertical\"\n            android:orientation=\"horizontal\"\n            tools:ignore=\"RtlHardcoded,RtlSymmetry\">\n\n            <TextView\n                android:layout_width=\"0dp\"\n                android:layout_height=\"wrap_content\"\n                android:layout_weight=\"1\"\n                android:padding=\"6dp\"\n                android:text=\"@string/show_hide\"\n                android:textColor=\"@color/primaryText\" />\n\n            <TextView\n                android:id=\"@+id/tv_header_show\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:padding=\"6dp\"\n                android:textColor=\"@color/primaryText\"\n                tools:text=\"@string/show_hide\" />\n\n        </LinearLayout>\n\n        <LinearLayout\n            android:id=\"@+id/ll_header_left\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:gravity=\"center_vertical\"\n            android:orientation=\"horizontal\"\n            tools:ignore=\"RtlHardcoded,RtlSymmetry\">\n\n            <TextView\n                android:layout_width=\"0dp\"\n                android:layout_height=\"wrap_content\"\n                android:layout_weight=\"1\"\n                android:padding=\"6dp\"\n                android:text=\"@string/left\"\n                android:textColor=\"@color/primaryText\" />\n\n            <TextView\n                android:id=\"@+id/tv_header_left\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:padding=\"6dp\"\n                android:textColor=\"@color/primaryText\"\n                tools:text=\"@string/title\" />\n\n        </LinearLayout>\n\n        <LinearLayout\n            android:id=\"@+id/ll_header_middle\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:gravity=\"center_vertical\"\n            android:orientation=\"horizontal\"\n            tools:ignore=\"RtlHardcoded,RtlSymmetry\">\n\n            <TextView\n                android:layout_width=\"0dp\"\n                android:layout_height=\"wrap_content\"\n                android:layout_weight=\"1\"\n                android:padding=\"6dp\"\n                android:text=\"@string/middle\"\n                android:textColor=\"@color/primaryText\" />\n\n            <TextView\n                android:id=\"@+id/tv_header_middle\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:padding=\"6dp\"\n                android:textColor=\"@color/primaryText\"\n                tools:text=\"@string/title\" />\n\n        </LinearLayout>\n\n        <LinearLayout\n            android:id=\"@+id/ll_header_right\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:gravity=\"center_vertical\"\n            android:orientation=\"horizontal\"\n            tools:ignore=\"RtlHardcoded,RtlSymmetry\">\n\n            <TextView\n                android:layout_width=\"0dp\"\n                android:layout_height=\"wrap_content\"\n                android:layout_weight=\"1\"\n                android:padding=\"6dp\"\n                android:text=\"@string/right\"\n                android:textColor=\"@color/primaryText\" />\n\n            <TextView\n                android:id=\"@+id/tv_header_right\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:padding=\"6dp\"\n                android:textColor=\"@color/primaryText\"\n                tools:text=\"@string/title\" />\n\n        </LinearLayout>\n\n        <io.legado.app.ui.widget.text.AccentTextView\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:text=\"@string/footer\"\n            android:textSize=\"18sp\" />\n\n        <LinearLayout\n            android:id=\"@+id/ll_footer_show\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:gravity=\"center_vertical\"\n            android:orientation=\"horizontal\"\n            tools:ignore=\"RtlHardcoded,RtlSymmetry\">\n\n            <TextView\n                android:layout_width=\"0dp\"\n                android:layout_height=\"wrap_content\"\n                android:layout_weight=\"1\"\n                android:padding=\"6dp\"\n                android:text=\"@string/show_hide\"\n                android:textColor=\"@color/primaryText\" />\n\n            <TextView\n                android:id=\"@+id/tv_footer_show\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:padding=\"6dp\"\n                android:textColor=\"@color/primaryText\"\n                tools:text=\"@string/show_hide\" />\n\n        </LinearLayout>\n\n        <LinearLayout\n            android:id=\"@+id/ll_footer_left\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:gravity=\"center_vertical\"\n            android:orientation=\"horizontal\"\n            tools:ignore=\"RtlHardcoded,RtlSymmetry\">\n\n            <TextView\n                android:layout_width=\"0dp\"\n                android:layout_height=\"wrap_content\"\n                android:layout_weight=\"1\"\n                android:padding=\"6dp\"\n                android:text=\"@string/left\"\n                android:textColor=\"@color/primaryText\" />\n\n            <TextView\n                android:id=\"@+id/tv_footer_left\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:padding=\"6dp\"\n                android:textColor=\"@color/primaryText\"\n                tools:text=\"@string/title\" />\n\n        </LinearLayout>\n\n        <LinearLayout\n            android:id=\"@+id/ll_footer_middle\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:gravity=\"center_vertical\"\n            android:orientation=\"horizontal\"\n            tools:ignore=\"RtlHardcoded,RtlSymmetry\">\n\n            <TextView\n                android:layout_width=\"0dp\"\n                android:layout_height=\"wrap_content\"\n                android:layout_weight=\"1\"\n                android:padding=\"6dp\"\n                android:text=\"@string/middle\"\n                android:textColor=\"@color/primaryText\" />\n\n            <TextView\n                android:id=\"@+id/tv_footer_middle\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:padding=\"6dp\"\n                android:textColor=\"@color/primaryText\"\n                tools:text=\"@string/title\" />\n\n        </LinearLayout>\n\n        <LinearLayout\n            android:id=\"@+id/ll_footer_right\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:gravity=\"center_vertical\"\n            android:orientation=\"horizontal\"\n            tools:ignore=\"RtlHardcoded,RtlSymmetry\">\n\n            <TextView\n                android:layout_width=\"0dp\"\n                android:layout_height=\"wrap_content\"\n                android:layout_weight=\"1\"\n                android:padding=\"6dp\"\n                android:text=\"@string/right\"\n                android:textColor=\"@color/primaryText\" />\n\n            <TextView\n                android:id=\"@+id/tv_footer_right\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:padding=\"6dp\"\n                android:textColor=\"@color/primaryText\"\n                tools:text=\"@string/title\" />\n\n        </LinearLayout>\n\n        <io.legado.app.ui.widget.text.AccentTextView\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:text=\"@string/header_footer\"\n            android:textSize=\"18sp\" />\n\n        <LinearLayout\n            android:id=\"@+id/ll_tip_color\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:gravity=\"center_vertical\"\n            android:orientation=\"horizontal\"\n            tools:ignore=\"RtlHardcoded,RtlSymmetry\">\n\n            <TextView\n                android:layout_width=\"0dp\"\n                android:layout_height=\"wrap_content\"\n                android:layout_weight=\"1\"\n                android:padding=\"6dp\"\n                android:text=\"@string/text_color\"\n                android:textColor=\"@color/primaryText\" />\n\n            <TextView\n                android:id=\"@+id/tv_tip_color\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:padding=\"6dp\"\n                android:textColor=\"@color/primaryText\"\n                tools:text=\"@string/text_color\" />\n\n        </LinearLayout>\n\n        <LinearLayout\n            android:id=\"@+id/ll_tip_divider_color\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:gravity=\"center_vertical\"\n            android:orientation=\"horizontal\"\n            tools:ignore=\"RtlHardcoded,RtlSymmetry\">\n\n            <TextView\n                android:layout_width=\"0dp\"\n                android:layout_height=\"wrap_content\"\n                android:layout_weight=\"1\"\n                android:padding=\"6dp\"\n                android:text=\"@string/tip_divider_color\"\n                android:textColor=\"@color/primaryText\" />\n\n            <TextView\n                android:id=\"@+id/tv_tip_divider_color\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:padding=\"6dp\"\n                android:textColor=\"@color/primaryText\"\n                tools:text=\"@string/tip_divider_color\" />\n\n        </LinearLayout>\n\n    </LinearLayout>\n\n</androidx.core.widget.NestedScrollView>"
  },
  {
    "path": "app/src/main/res/layout/dialog_toc_regex.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:orientation=\"vertical\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\">\n\n    <androidx.appcompat.widget.Toolbar\n        android:id=\"@+id/tool_bar\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:theme=\"?attr/actionBarStyle\"\n        app:titleTextAppearance=\"@style/ToolbarTitle\"\n        app:popupTheme=\"@style/AppTheme.PopupOverlay\" />\n\n    <androidx.recyclerview.widget.RecyclerView\n        android:id=\"@+id/recycler_view\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"0dp\"\n        android:layout_weight=\"1\"\n        app:layoutManager=\"androidx.recyclerview.widget.LinearLayoutManager\" />\n\n    <LinearLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\">\n\n        <Space\n            android:layout_width=\"0dp\"\n            android:layout_height=\"wrap_content\"\n            android:layout_weight=\"1\" />\n\n        <TextView\n            android:id=\"@+id/tv_cancel\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginRight=\"16dp\"\n            android:padding=\"12dp\"\n            android:text=\"@string/cancel\"\n            android:textColor=\"@color/secondaryText\"\n            tools:ignore=\"RtlHardcoded\" />\n\n        <io.legado.app.ui.widget.text.AccentTextView\n            android:id=\"@+id/tv_ok\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginRight=\"16dp\"\n            android:padding=\"12dp\"\n            android:text=\"@string/ok\"\n            tools:ignore=\"RtlHardcoded\" />\n\n    </LinearLayout>\n</LinearLayout>"
  },
  {
    "path": "app/src/main/res/layout/dialog_toc_regex_edit.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<FrameLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:padding=\"16dp\">\n\n    <LinearLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:layout_gravity=\"center\"\n        android:background=\"@drawable/shape_card_view\"\n        android:orientation=\"vertical\"\n        tools:ignore=\"UselessParent\">\n\n        <androidx.appcompat.widget.Toolbar\n            android:id=\"@+id/tool_bar\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:background=\"@color/background_menu\"\n            android:elevation=\"5dp\"\n            android:theme=\"?attr/actionBarStyle\"\n            app:displayHomeAsUp=\"false\"\n            app:fitStatusBar=\"false\"\n            app:popupTheme=\"@style/AppTheme.PopupOverlay\"\n            app:title=\"@string/txt_toc_rule\"\n            app:titleTextAppearance=\"@style/ToolbarTitle\" />\n\n        <androidx.core.widget.NestedScrollView\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:paddingTop=\"6dp\"\n            android:overScrollMode=\"ifContentScrolls\">\n\n            <LinearLayout\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:orientation=\"vertical\">\n\n                <io.legado.app.ui.widget.text.TextInputLayout\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"wrap_content\"\n                    android:hint=\"@string/name\">\n\n                    <io.legado.app.lib.theme.view.ThemeEditText\n                        android:id=\"@+id/tv_rule_name\"\n                        android:layout_width=\"match_parent\"\n                        android:layout_height=\"wrap_content\"\n                        tools:ignore=\"SpeakableTextPresentCheck,TouchTargetSizeCheck\" />\n\n                </io.legado.app.ui.widget.text.TextInputLayout>\n\n                <io.legado.app.ui.widget.text.TextInputLayout\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"wrap_content\"\n                    android:hint=\"@string/regex\">\n\n                    <io.legado.app.lib.theme.view.ThemeEditText\n                        android:id=\"@+id/tv_rule_regex\"\n                        android:layout_width=\"match_parent\"\n                        android:layout_height=\"wrap_content\"\n                        tools:ignore=\"SpeakableTextPresentCheck,TouchTargetSizeCheck\" />\n\n                </io.legado.app.ui.widget.text.TextInputLayout>\n\n                <io.legado.app.ui.widget.text.TextInputLayout\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"wrap_content\"\n                    android:hint=\"@string/example\">\n\n                    <io.legado.app.lib.theme.view.ThemeEditText\n                        android:id=\"@+id/tv_rule_example\"\n                        android:layout_width=\"match_parent\"\n                        android:layout_height=\"wrap_content\"\n                        tools:ignore=\"SpeakableTextPresentCheck,TouchTargetSizeCheck\" />\n\n                </io.legado.app.ui.widget.text.TextInputLayout>\n\n            </LinearLayout>\n\n        </androidx.core.widget.NestedScrollView>\n\n    </LinearLayout>\n\n</FrameLayout>"
  },
  {
    "path": "app/src/main/res/layout/dialog_update.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:orientation=\"vertical\">\n\n    <androidx.appcompat.widget.Toolbar\n        android:id=\"@+id/tool_bar\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:theme=\"?attr/actionBarStyle\"\n        app:popupTheme=\"@style/AppTheme.PopupOverlay\"\n        app:titleTextAppearance=\"@style/ToolbarTitle\" />\n\n    <io.legado.app.ui.widget.text.ScrollTextView\n        android:id=\"@+id/text_view\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:padding=\"12dp\"\n        android:textColor=\"@color/secondaryText\"\n        android:textIsSelectable=\"true\" />\n\n</LinearLayout>"
  },
  {
    "path": "app/src/main/res/layout/dialog_url_option_edit.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<FrameLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:padding=\"16dp\">\n\n    <LinearLayout\n        android:id=\"@+id/vw_bg\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:background=\"@drawable/shape_card_view\"\n        android:orientation=\"vertical\"\n        android:layout_gravity=\"center\"\n        android:padding=\"16dp\"\n        tools:ignore=\"UselessParent\">\n\n        <LinearLayout\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:gravity=\"center_vertical\"\n            android:orientation=\"horizontal\"\n            android:paddingBottom=\"10dp\">\n\n            <TextView\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:text=\"@string/url_option\"\n                android:textSize=\"18sp\"\n                android:textStyle=\"bold\" />\n\n            <Space\n                android:layout_width=\"0dp\"\n                android:layout_height=\"1dp\"\n                android:layout_weight=\"1\" />\n\n            <TextView\n                android:id=\"@+id/tv_ok\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:background=\"?android:attr/selectableItemBackgroundBorderless\"\n                android:padding=\"12dp\"\n                android:text=\"@string/ok\" />\n\n        </LinearLayout>\n\n        <androidx.core.widget.NestedScrollView\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:overScrollMode=\"ifContentScrolls\">\n\n            <LinearLayout\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:orientation=\"vertical\">\n\n                <io.legado.app.lib.theme.view.ThemeCheckBox\n                    android:id=\"@+id/cb_use_web_view\"\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"wrap_content\"\n                    android:text=\"useWebView\"\n                    tools:ignore=\"HardcodedText\" />\n\n                <io.legado.app.ui.widget.text.TextInputLayout\n                    android:id=\"@+id/layout_method\"\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"wrap_content\">\n\n                    <io.legado.app.ui.widget.text.AutoCompleteTextView\n                        android:id=\"@+id/edit_method\"\n                        android:layout_width=\"match_parent\"\n                        android:layout_height=\"wrap_content\"\n                        android:hint=\"method\"\n                        tools:ignore=\"HardcodedText,SpeakableTextPresentCheck,TouchTargetSizeCheck\" />\n\n                </io.legado.app.ui.widget.text.TextInputLayout>\n\n                <io.legado.app.ui.widget.text.TextInputLayout\n                    android:id=\"@+id/layout_charset\"\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"wrap_content\">\n\n                    <io.legado.app.ui.widget.text.AutoCompleteTextView\n                        android:id=\"@+id/edit_charset\"\n                        android:layout_width=\"match_parent\"\n                        android:layout_height=\"wrap_content\"\n                        android:hint=\"charset\"\n                        tools:ignore=\"HardcodedText,SpeakableTextPresentCheck,TouchTargetSizeCheck\" />\n\n                </io.legado.app.ui.widget.text.TextInputLayout>\n\n                <io.legado.app.ui.widget.text.TextInputLayout\n                    android:id=\"@+id/layout_headers\"\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"wrap_content\">\n\n                    <io.legado.app.ui.widget.text.AutoCompleteTextView\n                        android:id=\"@+id/edit_headers\"\n                        android:layout_width=\"match_parent\"\n                        android:layout_height=\"wrap_content\"\n                        android:hint=\"headers\"\n                        tools:ignore=\"HardcodedText,SpeakableTextPresentCheck,TouchTargetSizeCheck\" />\n\n                </io.legado.app.ui.widget.text.TextInputLayout>\n\n                <io.legado.app.ui.widget.text.TextInputLayout\n                    android:id=\"@+id/layout_body\"\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"wrap_content\">\n\n                    <io.legado.app.ui.widget.text.AutoCompleteTextView\n                        android:id=\"@+id/edit_body\"\n                        android:layout_width=\"match_parent\"\n                        android:layout_height=\"wrap_content\"\n                        android:hint=\"body\"\n                        tools:ignore=\"HardcodedText,SpeakableTextPresentCheck,TouchTargetSizeCheck\" />\n\n                </io.legado.app.ui.widget.text.TextInputLayout>\n\n                <io.legado.app.ui.widget.text.TextInputLayout\n                    android:id=\"@+id/layout_type\"\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"wrap_content\">\n\n                    <io.legado.app.ui.widget.text.AutoCompleteTextView\n                        android:id=\"@+id/edit_type\"\n                        android:layout_width=\"match_parent\"\n                        android:layout_height=\"wrap_content\"\n                        android:hint=\"type\"\n                        tools:ignore=\"HardcodedText,SpeakableTextPresentCheck,TouchTargetSizeCheck\" />\n\n                </io.legado.app.ui.widget.text.TextInputLayout>\n\n                <io.legado.app.ui.widget.text.TextInputLayout\n                    android:id=\"@+id/layout_retry\"\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"wrap_content\">\n\n                    <io.legado.app.ui.widget.text.AutoCompleteTextView\n                        android:id=\"@+id/edit_retry\"\n                        android:layout_width=\"match_parent\"\n                        android:layout_height=\"wrap_content\"\n                        android:hint=\"retry\"\n                        android:inputType=\"number\"\n                        tools:ignore=\"HardcodedText,SpeakableTextPresentCheck,TouchTargetSizeCheck\" />\n\n                </io.legado.app.ui.widget.text.TextInputLayout>\n\n                <io.legado.app.ui.widget.text.TextInputLayout\n                    android:id=\"@+id/layout_web_js\"\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"wrap_content\">\n\n                    <io.legado.app.ui.widget.text.AutoCompleteTextView\n                        android:id=\"@+id/edit_web_js\"\n                        android:layout_width=\"match_parent\"\n                        android:layout_height=\"wrap_content\"\n                        android:hint=\"webJs\"\n                        tools:ignore=\"HardcodedText,SpeakableTextPresentCheck,TouchTargetSizeCheck\" />\n\n                </io.legado.app.ui.widget.text.TextInputLayout>\n\n                <io.legado.app.ui.widget.text.TextInputLayout\n                    android:id=\"@+id/layout_js\"\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"wrap_content\">\n\n                    <io.legado.app.ui.widget.text.AutoCompleteTextView\n                        android:id=\"@+id/edit_js\"\n                        android:layout_width=\"match_parent\"\n                        android:layout_height=\"wrap_content\"\n                        android:hint=\"js\"\n                        tools:ignore=\"HardcodedText,SpeakableTextPresentCheck,TouchTargetSizeCheck\" />\n\n                </io.legado.app.ui.widget.text.TextInputLayout>\n\n            </LinearLayout>\n\n        </androidx.core.widget.NestedScrollView>\n\n\n    </LinearLayout>\n\n</FrameLayout>"
  },
  {
    "path": "app/src/main/res/layout/dialog_variable.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<FrameLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:padding=\"16dp\">\n\n    <LinearLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:layout_gravity=\"center\"\n        android:background=\"@drawable/shape_card_view\"\n        android:orientation=\"vertical\"\n        tools:ignore=\"UselessParent\">\n\n        <androidx.appcompat.widget.Toolbar\n            android:id=\"@+id/tool_bar\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:background=\"@color/background_menu\"\n            android:elevation=\"5dp\"\n            android:theme=\"?attr/actionBarStyle\"\n            app:displayHomeAsUp=\"false\"\n            app:fitStatusBar=\"false\"\n            app:title=\"Set variable\"\n            app:popupTheme=\"@style/AppTheme.PopupOverlay\"\n            app:titleTextAppearance=\"@style/ToolbarTitle\" />\n\n        <io.legado.app.ui.widget.text.AccentTextView\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:padding=\"3dp\"\n            android:text=\"variable\"\n            tools:ignore=\"HardcodedText\" />\n\n        <io.legado.app.lib.theme.view.ThemeEditText\n            android:id=\"@+id/tv_variable\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            tools:ignore=\"SpeakableTextPresentCheck,TouchTargetSizeCheck\" />\n\n        <androidx.core.widget.NestedScrollView\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n\n            android:overScrollMode=\"ifContentScrolls\">\n\n            <LinearLayout\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:orientation=\"vertical\">\n\n                <io.legado.app.ui.widget.text.AccentTextView\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"wrap_content\"\n                    android:padding=\"3dp\"\n                    android:text=\"@string/variable_comment\" />\n\n                <TextView\n                    android:id=\"@+id/tv_comment\"\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"wrap_content\"\n                    android:padding=\"3dp\"\n                    android:textColor=\"@color/secondaryText\"\n                    android:textIsSelectable=\"true\" />\n\n            </LinearLayout>\n\n        </androidx.core.widget.NestedScrollView>\n\n    </LinearLayout>\n\n</FrameLayout>"
  },
  {
    "path": "app/src/main/res/layout/dialog_verification_code_view.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:orientation=\"vertical\">\n\n    <androidx.appcompat.widget.Toolbar\n        android:id=\"@+id/tool_bar\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:background=\"@color/background_menu\"\n        android:elevation=\"5dp\"\n        android:theme=\"?attr/actionBarStyle\"\n        app:popupTheme=\"@style/AppTheme.PopupOverlay\"\n        app:title=\"@string/input_verification_code\"\n        app:titleTextAppearance=\"@style/ToolbarTitle\" />\n\n    <androidx.appcompat.widget.AppCompatImageView\n        android:id=\"@+id/verification_code_image_view\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"100dp\"\n        android:background=\"?android:attr/selectableItemBackgroundBorderless\"\n        android:scaleType=\"fitCenter\"\n        tools:ignore=\"UnusedAttribute\" />\n\n    <io.legado.app.ui.widget.text.TextInputLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginTop=\"8dp\"\n        android:padding=\"3dp\">\n\n        <io.legado.app.lib.theme.view.ThemeEditText\n            android:id=\"@+id/verification_code\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:hint=\"@string/verification_code\"\n            tools:ignore=\"SpeakableTextPresentCheck,TouchTargetSizeCheck\" />\n\n    </io.legado.app.ui.widget.text.TextInputLayout>\n\n</LinearLayout>"
  },
  {
    "path": "app/src/main/res/layout/dialog_wait.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"wrap_content\"\n    android:background=\"@color/background\"\n    android:gravity=\"center\"\n    android:padding=\"20dp\"\n    android:orientation=\"horizontal\">\n\n    <io.legado.app.lib.theme.view.ThemeProgressBar\n        android:id=\"@+id/pb\"\n        android:layout_width=\"30dp\"\n        android:layout_height=\"30dp\"\n        android:indeterminateBehavior=\"repeat\"\n        android:indeterminateOnly=\"true\" />\n\n    <TextView\n        android:id=\"@+id/tv_msg\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:padding=\"6dp\"\n        android:gravity=\"center\"\n        android:text=\"@string/loading\"\n        android:textColor=\"@color/primaryText\" />\n</LinearLayout>"
  },
  {
    "path": "app/src/main/res/layout/dialog_webdav_server.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<FrameLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:padding=\"16dp\">\n\n    <LinearLayout\n        android:id=\"@+id/vw_bg\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:layout_gravity=\"center\"\n        android:background=\"@drawable/shape_card_view\"\n        android:orientation=\"vertical\"\n        tools:ignore=\"UselessParent\">\n\n        <androidx.appcompat.widget.Toolbar\n            android:id=\"@+id/tool_bar\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginBottom=\"3dp\"\n            android:background=\"@color/background_menu\"\n            android:elevation=\"5dp\"\n            android:theme=\"?attr/actionBarStyle\"\n            app:title=\"@string/server_config\"\n            app:displayHomeAsUp=\"false\"\n            app:fitStatusBar=\"false\"\n            app:popupTheme=\"@style/AppTheme.PopupOverlay\"\n            app:titleTextAppearance=\"@style/ToolbarTitle\" />\n\n        <io.legado.app.ui.widget.text.TextInputLayout\n            android:id=\"@+id/til_name\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:hint=\"@string/name\">\n\n            <io.legado.app.lib.theme.view.ThemeEditText\n                android:id=\"@+id/et_name\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                tools:ignore=\"SpeakableTextPresentCheck,TouchTargetSizeCheck\" />\n        </io.legado.app.ui.widget.text.TextInputLayout>\n\n        <LinearLayout\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:orientation=\"horizontal\"\n            android:gravity=\"center_vertical\">\n\n            <io.legado.app.ui.widget.text.AccentTextView\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:padding=\"6dp\"\n                android:text=\"TYPE\"\n                tools:ignore=\"HardcodedText\" />\n\n            <androidx.appcompat.widget.AppCompatSpinner\n                android:id=\"@+id/sp_type\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:entries=\"@array/server_type\"\n                android:theme=\"@style/Spinner\"\n                tools:ignore=\"TouchTargetSizeCheck\" />\n\n        </LinearLayout>\n\n        <androidx.core.widget.NestedScrollView\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:overScrollMode=\"ifContentScrolls\"\n            tools:ignore=\"SpeakableTextPresentCheck\">\n\n            <com.google.android.flexbox.FlexboxLayout\n                android:id=\"@+id/flexbox\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginTop=\"8dp\"\n                android:clipToPadding=\"false\"\n                android:padding=\"3dp\"\n                app:dividerDrawable=\"@drawable/shape_space_divider\"\n                app:flexDirection=\"row\"\n                app:flexWrap=\"wrap\"\n                app:showDivider=\"middle\" />\n\n        </androidx.core.widget.NestedScrollView>\n\n    </LinearLayout>\n</FrameLayout>"
  },
  {
    "path": "app/src/main/res/layout/fragment_bookmark.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:descendantFocusability=\"blocksDescendants\">\n\n    <io.legado.app.ui.widget.recycler.scroller.FastScrollRecyclerView\n        android:id=\"@+id/recycler_view\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:clipToPadding=\"false\"\n        android:overScrollMode=\"never\" />\n\n</androidx.constraintlayout.widget.ConstraintLayout>"
  },
  {
    "path": "app/src/main/res/layout/fragment_books.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<FrameLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\">\n\n    <androidx.swiperefreshlayout.widget.SwipeRefreshLayout\n        android:id=\"@+id/refresh_layout\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\">\n\n        <io.legado.app.ui.widget.recycler.scroller.FastScrollRecyclerView\n            android:id=\"@+id/rv_bookshelf\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"match_parent\"\n            tools:listitem=\"@layout/item_bookshelf_list\" />\n\n    </androidx.swiperefreshlayout.widget.SwipeRefreshLayout>\n\n    <TextView\n        android:id=\"@+id/tv_empty_msg\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_gravity=\"center\"\n        android:layout_margin=\"16dp\"\n        android:gravity=\"center\"\n        android:text=\"@string/bookshelf_empty\"\n        android:visibility=\"gone\"\n        tools:text=\"TextView\" />\n\n</FrameLayout>\n\n"
  },
  {
    "path": "app/src/main/res/layout/fragment_bookshelf1.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:orientation=\"vertical\">\n\n    <io.legado.app.ui.widget.TitleBar\n        android:id=\"@+id/title_bar\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        app:attachToActivity=\"false\"\n        app:contentLayout=\"@layout/view_tab_layout_min\"\n        app:title=\"@string/bookshelf\" />\n\n    <androidx.viewpager.widget.ViewPager\n        android:id=\"@+id/view_pager_bookshelf\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\" />\n\n</LinearLayout>"
  },
  {
    "path": "app/src/main/res/layout/fragment_bookshelf2.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:orientation=\"vertical\">\n\n    <io.legado.app.ui.widget.TitleBar\n        android:id=\"@+id/title_bar\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        app:attachToActivity=\"false\"\n        app:layout_constraintTop_toTopOf=\"parent\"\n        app:title=\"@string/bookshelf\" />\n\n    <androidx.swiperefreshlayout.widget.SwipeRefreshLayout\n        android:id=\"@+id/refresh_layout\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"0dp\"\n        app:layout_constraintTop_toBottomOf=\"@+id/title_bar\"\n        app:layout_constraintBottom_toBottomOf=\"parent\">\n\n        <io.legado.app.ui.widget.recycler.RecyclerViewAtPager2\n            android:id=\"@+id/rv_bookshelf\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"match_parent\"\n            tools:listitem=\"@layout/item_bookshelf_list\" />\n\n    </androidx.swiperefreshlayout.widget.SwipeRefreshLayout>\n\n    <TextView\n        android:id=\"@+id/tv_empty_msg\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_gravity=\"center\"\n        android:layout_margin=\"16dp\"\n        android:gravity=\"center\"\n        android:text=\"@string/bookshelf_empty\"\n        android:visibility=\"gone\"\n        app:layout_constraintTop_toBottomOf=\"@+id/title_bar\"\n        app:layout_constraintBottom_toBottomOf=\"parent\"\n        app:layout_constraintLeft_toLeftOf=\"parent\"\n        app:layout_constraintRight_toRightOf=\"parent\"\n        tools:text=\"TextView\" />\n\n</androidx.constraintlayout.widget.ConstraintLayout>"
  },
  {
    "path": "app/src/main/res/layout/fragment_chapter_list.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:descendantFocusability=\"blocksDescendants\">\n\n    <io.legado.app.ui.widget.recycler.scroller.FastScrollRecyclerView\n        android:id=\"@+id/recycler_view\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"0dp\"\n        android:overScrollMode=\"never\"\n        app:layout_constraintBottom_toTopOf=\"@+id/ll_chapter_base_info\"\n        app:layout_constraintTop_toTopOf=\"parent\" />\n\n    <LinearLayout\n        android:id=\"@+id/ll_chapter_base_info\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:background=\"@color/background\"\n        android:elevation=\"5dp\"\n        android:gravity=\"center_vertical\"\n        android:orientation=\"horizontal\"\n        android:paddingLeft=\"10dp\"\n        android:paddingRight=\"10dp\"\n        app:layout_constraintBottom_toBottomOf=\"parent\">\n\n        <TextView\n            android:id=\"@+id/tv_current_chapter_info\"\n            android:layout_width=\"0dp\"\n            android:layout_height=\"36dp\"\n            android:layout_weight=\"1\"\n            android:background=\"?android:attr/selectableItemBackgroundBorderless\"\n            android:ellipsize=\"middle\"\n            android:gravity=\"center_vertical\"\n            android:paddingLeft=\"10dp\"\n            android:paddingRight=\"10dp\"\n            android:singleLine=\"true\"\n            android:textColor=\"@color/primaryText\"\n            android:textSize=\"12sp\" />\n\n        <androidx.appcompat.widget.AppCompatImageView\n            android:id=\"@+id/iv_chapter_top\"\n            android:layout_width=\"36dp\"\n            android:layout_height=\"36dp\"\n            android:background=\"?android:attr/selectableItemBackgroundBorderless\"\n            android:contentDescription=\"@string/go_to_top\"\n            android:src=\"@drawable/ic_arrow_drop_up\"\n            android:tooltipText=\"@string/go_to_top\"\n            app:tint=\"@color/primaryText\"\n            tools:ignore=\"UnusedAttribute\" />\n\n        <androidx.appcompat.widget.AppCompatImageView\n            android:id=\"@+id/iv_chapter_bottom\"\n            android:layout_width=\"36dp\"\n            android:layout_height=\"36dp\"\n            android:background=\"?android:attr/selectableItemBackgroundBorderless\"\n            android:contentDescription=\"@string/go_to_bottom\"\n            android:src=\"@drawable/ic_arrow_drop_down\"\n            android:tooltipText=\"@string/go_to_bottom\"\n            app:tint=\"@color/primaryText\"\n            tools:ignore=\"UnusedAttribute\" />\n    </LinearLayout>\n\n</androidx.constraintlayout.widget.ConstraintLayout>"
  },
  {
    "path": "app/src/main/res/layout/fragment_explore.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:orientation=\"vertical\">\n\n    <io.legado.app.ui.widget.TitleBar\n        android:id=\"@+id/title_bar\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        app:attachToActivity=\"false\"\n        app:contentLayout=\"@layout/view_search\"\n        app:layout_constraintTop_toTopOf=\"parent\"\n        app:title=\"@string/discovery\" />\n\n    <io.legado.app.ui.widget.recycler.RecyclerViewAtPager2\n        android:id=\"@+id/rv_find\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"0dp\"\n        app:layout_constraintTop_toBottomOf=\"@+id/title_bar\"\n        app:layout_constraintBottom_toBottomOf=\"parent\"\n        tools:listitem=\"@layout/item_find_book\" />\n\n    <TextView\n        android:id=\"@+id/tv_empty_msg\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_margin=\"16dp\"\n        android:gravity=\"center\"\n        android:visibility=\"gone\"\n        android:text=\"@string/explore_empty\"\n        app:layout_constraintTop_toBottomOf=\"@+id/title_bar\"\n        app:layout_constraintBottom_toBottomOf=\"parent\"\n        app:layout_constraintLeft_toLeftOf=\"parent\"\n        app:layout_constraintRight_toRightOf=\"parent\"\n        tools:text=\"TextView\" />\n\n</androidx.constraintlayout.widget.ConstraintLayout>"
  },
  {
    "path": "app/src/main/res/layout/fragment_my_config.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:orientation=\"vertical\">\n\n    <io.legado.app.ui.widget.TitleBar\n        android:id=\"@+id/title_bar\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        app:attachToActivity=\"false\"\n        app:title=\"@string/my\" />\n\n    <LinearLayout\n        android:id=\"@+id/pre_fragment\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:orientation=\"vertical\" />\n\n</LinearLayout>"
  },
  {
    "path": "app/src/main/res/layout/fragment_rss.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:orientation=\"vertical\">\n\n    <io.legado.app.ui.widget.TitleBar\n        android:id=\"@+id/title_bar\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        app:attachToActivity=\"false\"\n        app:contentLayout=\"@layout/view_search\"\n        app:layout_constraintTop_toTopOf=\"parent\"\n        app:title=\"@string/rss\" />\n\n    <io.legado.app.ui.widget.recycler.RecyclerViewAtPager2\n        android:id=\"@+id/recycler_view\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"0dp\"\n        app:layout_constraintTop_toBottomOf=\"@id/title_bar\"\n        app:layout_constraintBottom_toBottomOf=\"parent\"\n        app:layoutManager=\"androidx.recyclerview.widget.GridLayoutManager\"\n        app:spanCount=\"4\"\n        tools:listitem=\"@layout/item_rss\" />\n\n    <TextView\n        android:id=\"@+id/tv_empty_msg\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_margin=\"16dp\"\n        android:gravity=\"center\"\n        android:visibility=\"gone\"\n        android:text=\"@string/rss_source_empty\"\n        app:layout_constraintTop_toBottomOf=\"@+id/title_bar\"\n        app:layout_constraintBottom_toBottomOf=\"parent\"\n        app:layout_constraintLeft_toLeftOf=\"parent\"\n        app:layout_constraintRight_toRightOf=\"parent\"\n        tools:text=\"TextView\" />\n\n</androidx.constraintlayout.widget.ConstraintLayout>"
  },
  {
    "path": "app/src/main/res/layout/fragment_rss_articles.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.swiperefreshlayout.widget.SwipeRefreshLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:id=\"@+id/refresh_layout\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\">\n\n    <io.legado.app.ui.widget.recycler.RecyclerViewAtPager2\n        android:id=\"@+id/recycler_view\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:clipToPadding=\"false\" />\n\n</androidx.swiperefreshlayout.widget.SwipeRefreshLayout>"
  },
  {
    "path": "app/src/main/res/layout/fragment_web_view_login.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:orientation=\"vertical\">\n\n    <io.legado.app.ui.widget.TitleBar\n        android:id=\"@+id/title_bar\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        app:attachToActivity=\"false\"\n        app:title=\"@string/login\"\n        app:layout_constraintTop_toTopOf=\"parent\" />\n\n    <WebView\n        android:id=\"@+id/web_view\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"0dp\"\n        app:layout_constraintTop_toBottomOf=\"@+id/title_bar\"\n        app:layout_constraintBottom_toBottomOf=\"parent\" />\n\n    <io.legado.app.ui.widget.anima.RefreshProgressBar\n        android:id=\"@+id/progress_bar\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"1dp\"\n        app:max_progress=\"100\"\n        app:layout_constraintTop_toTopOf=\"@id/web_view\" />\n\n</androidx.constraintlayout.widget.ConstraintLayout>"
  },
  {
    "path": "app/src/main/res/layout/item_1line_text.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:gravity=\"center_vertical\"\n    android:orientation=\"horizontal\"\n    android:padding=\"6dp\"\n    android:background=\"?attr/selectableItemBackground\"\n    tools:ignore=\"UseCompoundDrawables\">\n\n    <TextView\n        android:id=\"@+id/text_view\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"wrap_content\"\n        android:layout_weight=\"1\"\n        android:singleLine=\"true\"\n        android:textColor=\"@color/primaryText\" />\n\n</LinearLayout>"
  },
  {
    "path": "app/src/main/res/layout/item_1line_text_and_del.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:gravity=\"center_vertical\"\n    android:orientation=\"horizontal\"\n    android:padding=\"6dp\"\n    tools:ignore=\"UseCompoundDrawables\">\n\n    <TextView\n        android:id=\"@+id/text_view\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"wrap_content\"\n        android:layout_weight=\"1\"\n        android:singleLine=\"true\"\n        android:textColor=\"@color/primaryText\" />\n\n    <ImageView\n        android:id=\"@+id/iv_delete\"\n        android:layout_width=\"24dp\"\n        android:layout_height=\"24dp\"\n        android:contentDescription=\"@string/delete\"\n        android:src=\"@drawable/ic_clear_all\"\n        android:visibility=\"gone\"\n        app:tint=\"@color/primaryText\" />\n\n</LinearLayout>"
  },
  {
    "path": "app/src/main/res/layout/item_app_log.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:padding=\"6dp\"\n    android:background=\"?android:attr/selectableItemBackground\"\n    android:orientation=\"vertical\">\n\n    <TextView\n        android:id=\"@+id/text_time\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\" />\n\n    <TextView\n        android:id=\"@+id/text_message\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:contentDescription=\"@string/log\"\n        android:textIsSelectable=\"true\"\n        android:autoLink=\"web\" />\n\n</LinearLayout>"
  },
  {
    "path": "app/src/main/res/layout/item_arrange_book.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:orientation=\"vertical\"\n    android:padding=\"6dp\">\n\n    <io.legado.app.lib.theme.view.ThemeCheckBox\n        android:id=\"@+id/checkbox\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_margin=\"10dp\"\n        android:gravity=\"center\"\n        app:layout_constraintBottom_toBottomOf=\"parent\"\n        app:layout_constraintLeft_toLeftOf=\"parent\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toTopOf=\"parent\" />\n\n    <TextView\n        android:id=\"@+id/tv_name\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:padding=\"3dp\"\n        android:singleLine=\"true\"\n        android:text=\"@string/book_name\"\n        android:textColor=\"@color/primaryText\"\n        android:textSize=\"15sp\"\n        app:layout_constraintBottom_toTopOf=\"@id/tv_group_s\"\n        app:layout_constraintLeft_toRightOf=\"@id/checkbox\"\n        app:layout_constraintTop_toTopOf=\"parent\" />\n\n    <TextView\n        android:id=\"@+id/tv_author\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"wrap_content\"\n        android:paddingLeft=\"12dp\"\n        android:paddingRight=\"8dp\"\n        android:singleLine=\"true\"\n        android:text=\"@string/author\"\n        android:textColor=\"@color/tv_text_summary\"\n        android:textSize=\"12sp\"\n        app:layout_constraintBottom_toBottomOf=\"@+id/tv_name\"\n        app:layout_constraintLeft_toRightOf=\"@+id/tv_name\"\n        app:layout_constraintRight_toRightOf=\"parent\"\n        app:layout_constraintTop_toTopOf=\"@+id/tv_name\"\n        tools:ignore=\"RtlHardcoded\" />\n\n    <TextView\n        android:id=\"@+id/tv_origin\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginTop=\"8dp\"\n        android:padding=\"3dp\"\n        android:textColor=\"@color/tv_text_summary\"\n        android:textSize=\"12sp\"\n        app:layout_constraintBottom_toBottomOf=\"parent\"\n        app:layout_constraintLeft_toRightOf=\"@+id/checkbox\"\n        app:layout_constraintTop_toBottomOf=\"@id/tv_author\"\n        tools:text=\"书源\" />\n\n    <TextView\n        android:id=\"@+id/tv_group_s\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginTop=\"8dp\"\n        android:hint=\"@string/no_group\"\n        android:padding=\"3dp\"\n        android:singleLine=\"true\"\n        android:textColor=\"@color/secondaryText\"\n        android:textSize=\"12sp\"\n        app:layout_constraintBottom_toBottomOf=\"parent\"\n        app:layout_constraintLeft_toRightOf=\"@+id/tv_origin\"\n        app:layout_constraintRight_toLeftOf=\"@+id/tv_group\"\n        app:layout_constraintTop_toBottomOf=\"@id/tv_author\" />\n\n    <TextView\n        android:id=\"@+id/tv_group\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:background=\"?attr/selectableItemBackgroundBorderless\"\n        android:padding=\"10dp\"\n        android:text=\"@string/group\"\n        android:textColor=\"@color/secondaryText\"\n        app:layout_constraintBottom_toBottomOf=\"parent\"\n        app:layout_constraintRight_toLeftOf=\"@+id/tv_delete\"\n        app:layout_constraintTop_toBottomOf=\"@id/tv_author\" />\n\n    <TextView\n        android:id=\"@+id/tv_delete\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:background=\"?attr/selectableItemBackgroundBorderless\"\n        android:padding=\"10dp\"\n        android:text=\"@string/delete\"\n        android:textColor=\"@color/secondaryText\"\n        app:layout_constraintBottom_toBottomOf=\"parent\"\n        app:layout_constraintRight_toRightOf=\"parent\"\n        app:layout_constraintTop_toBottomOf=\"@id/tv_author\" />\n\n</androidx.constraintlayout.widget.ConstraintLayout>"
  },
  {
    "path": "app/src/main/res/layout/item_bg_image.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"66dp\"\n    android:layout_height=\"88dp\"\n    xmlns:tool=\"http://schemas.android.com/tools\"\n    android:padding=\"2dp\"\n    android:orientation=\"vertical\">\n\n    <ImageView\n        android:id=\"@+id/iv_bg\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"0dp\"\n        android:layout_weight=\"1\"\n        android:scaleType=\"fitCenter\"\n        android:contentDescription=\"@string/bg_image\"\n        tool:src=\"@drawable/ic_image\" />\n\n    <TextView\n        android:id=\"@+id/tv_name\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:singleLine=\"true\"\n        android:gravity=\"center\" />\n\n</LinearLayout>"
  },
  {
    "path": "app/src/main/res/layout/item_book_file_import.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:padding=\"8dp\">\n\n    <TextView\n        android:id=\"@+id/cb_file_name\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:textColor=\"@color/primaryText\"\n        android:textSize=\"14sp\"\n        app:layout_constrainedWidth=\"true\"\n        app:layout_constraintBottom_toBottomOf=\"parent\"\n        app:layout_constraintHorizontal_bias=\"0\"\n        app:layout_constraintHorizontal_chainStyle=\"packed\"\n        app:layout_constraintLeft_toLeftOf=\"parent\"\n        app:layout_constraintTop_toTopOf=\"parent\"\n        tools:ignore=\"TouchTargetSizeCheck\" />\n\n</androidx.constraintlayout.widget.ConstraintLayout>\n"
  },
  {
    "path": "app/src/main/res/layout/item_book_group_manage.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:background=\"@color/background\"\n    android:gravity=\"center_vertical\"\n    android:orientation=\"horizontal\"\n    android:padding=\"8dp\">\n\n    <TextView\n        android:id=\"@+id/tv_group\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"wrap_content\"\n        android:layout_weight=\"1\"\n        android:textColor=\"@color/secondaryText\"\n        tools:text=\"group\" />\n\n    <TextView\n        android:id=\"@+id/tv_edit\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:background=\"?attr/selectableItemBackground\"\n        android:padding=\"8dp\"\n        android:text=\"@string/edit\"\n        android:textColor=\"@color/secondaryText\" />\n\n    <io.legado.app.lib.theme.view.ThemeSwitch\n        android:id=\"@+id/sw_show\"\n        android:minWidth=\"48dp\"\n        android:minHeight=\"48dp\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\" />\n\n</LinearLayout>"
  },
  {
    "path": "app/src/main/res/layout/item_book_manga_edge.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<FrameLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"96dp\"\n    android:background=\"@color/book_ant_10\">\n\n    <TextView\n        android:id=\"@+id/text\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_gravity=\"center\"\n        android:textColor=\"@android:color/white\"\n        android:textStyle=\"bold\"\n        tools:text=\"上一章节：第一话\" />\n</FrameLayout>\n"
  },
  {
    "path": "app/src/main/res/layout/item_book_manga_page.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<FrameLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:id=\"@+id/rootView\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:background=\"@color/book_ant_10\"\n    android:contentDescription=\"@null\">\n\n    <androidx.appcompat.widget.AppCompatImageView\n        android:id=\"@+id/image\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:adjustViewBounds=\"true\"\n        android:scaleType=\"fitXY\" />\n\n    <FrameLayout\n        android:id=\"@+id/fl_progress\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:background=\"@color/book_ant_10\">\n\n        <ProgressBar\n            android:id=\"@+id/loading\"\n            style=\"@style/Widget.Material3.CircularProgressIndicator.Small\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_gravity=\"center\"\n            android:indeterminate=\"true\"\n            android:indeterminateTint=\"@color/white\"\n            android:progressTint=\"@color/white\"\n            app:indicatorColor=\"@color/white\"\n            app:indicatorSize=\"36dp\"\n            app:trackCornerRadius=\"99dp\"\n            app:trackThickness=\"3dp\" />\n\n\n        <Button\n            android:id=\"@+id/retry\"\n            style=\"@style/Widget.Material3.Button.TonalButton\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_gravity=\"center\"\n            android:text=\"重新加载\"\n            android:textColor=\"@color/white\"\n            android:textSize=\"18sp\"\n            android:visibility=\"gone\"\n            app:backgroundTint=\"@color/white\"\n            app:cornerRadius=\"10dp\"\n            tools:visibility=\"visible\" />\n\n        <TextView\n            android:id=\"@+id/progress\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_gravity=\"center\"\n            android:text=\"0%\"\n            android:textColor=\"@color/white\"\n            android:visibility=\"gone\"\n            tools:visibility=\"visible\" />\n    </FrameLayout>\n\n</FrameLayout>"
  },
  {
    "path": "app/src/main/res/layout/item_book_source.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:background=\"?android:attr/selectableItemBackground\"\n    android:clipToPadding=\"false\"\n    android:padding=\"16dp\"\n    android:scrollbars=\"none\">\n\n    <io.legado.app.ui.widget.text.AccentTextView\n        android:id=\"@+id/tv_host_text\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginTop=\"-12dp\"\n        android:layout_marginBottom=\"16dp\"\n        android:scrollbars=\"none\"\n        android:singleLine=\"true\"\n        android:textSize=\"16sp\"\n        android:visibility=\"gone\"\n        app:layout_constraintBottom_toTopOf=\"@id/barrier\"\n        app:layout_constraintLeft_toLeftOf=\"parent\"\n        app:layout_constraintTop_toTopOf=\"parent\"\n        tools:text=\"host\"\n        tools:visibility=\"visible\" />\n\n    <androidx.constraintlayout.widget.Barrier\n        android:id=\"@+id/barrier\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        app:barrierDirection=\"top\"\n        app:constraint_referenced_ids=\"cb_book_source,swt_enabled,iv_edit,iv_menu_more\" />\n\n    <io.legado.app.lib.theme.view.ThemeCheckBox\n        android:id=\"@+id/cb_book_source\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"wrap_content\"\n        android:layout_gravity=\"center\"\n        android:scrollbars=\"none\"\n        android:singleLine=\"true\"\n        android:textColor=\"@color/primaryText\"\n        app:layout_constraintBottom_toTopOf=\"@id/iv_debug_text\"\n        app:layout_constraintLeft_toLeftOf=\"parent\"\n        app:layout_constraintRight_toLeftOf=\"@id/swt_enabled\"\n        app:layout_constraintTop_toBottomOf=\"@id/barrier\"\n        tools:ignore=\"TouchTargetSizeCheck\"\n        tools:text=\"@string/book_source\" />\n\n    <io.legado.app.lib.theme.view.ThemeSwitch\n        android:id=\"@+id/swt_enabled\"\n        android:name=\"@string/enable\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginRight=\"6dp\"\n        android:scrollbars=\"none\"\n        app:layout_constraintBottom_toTopOf=\"@id/iv_debug_text\"\n        app:layout_constraintRight_toLeftOf=\"@id/iv_edit\"\n        app:layout_constraintTop_toBottomOf=\"@id/barrier\"\n        tools:ignore=\"RtlHardcoded,RtlSymmetry,TouchTargetSizeCheck\" />\n\n    <androidx.appcompat.widget.AppCompatImageView\n        android:id=\"@+id/iv_edit\"\n        android:layout_width=\"36dp\"\n        android:layout_height=\"36dp\"\n        android:layout_gravity=\"center\"\n        android:background=\"?android:attr/selectableItemBackgroundBorderless\"\n        android:contentDescription=\"@string/edit\"\n        android:padding=\"6dp\"\n        android:scrollbars=\"none\"\n        android:src=\"@drawable/ic_edit\"\n        android:tint=\"@color/primaryText\"\n        app:layout_constraintBottom_toTopOf=\"@id/iv_debug_text\"\n        app:layout_constraintRight_toLeftOf=\"@id/iv_menu_more\"\n        app:layout_constraintTop_toBottomOf=\"@id/barrier\" />\n\n    <androidx.appcompat.widget.AppCompatImageView\n        android:id=\"@+id/iv_menu_more\"\n        android:layout_width=\"36dp\"\n        android:layout_height=\"36dp\"\n        android:background=\"?attr/selectableItemBackgroundBorderless\"\n        android:contentDescription=\"@string/more_menu\"\n        android:padding=\"6dp\"\n        android:scrollbars=\"none\"\n        android:src=\"@drawable/ic_more_vert\"\n        android:tint=\"@color/primaryText\"\n        app:layout_constraintBottom_toTopOf=\"@id/iv_debug_text\"\n        app:layout_constraintRight_toRightOf=\"parent\"\n        app:layout_constraintTop_toBottomOf=\"@id/barrier\"\n        tools:ignore=\"RtlHardcoded\" />\n\n    <io.legado.app.ui.widget.image.CircleImageView\n        android:id=\"@+id/iv_explore\"\n        android:layout_width=\"8dp\"\n        android:layout_height=\"8dp\"\n        android:scaleType=\"centerCrop\"\n        android:scrollbars=\"none\"\n        android:src=\"@color/md_green_600\"\n        app:layout_constraintRight_toRightOf=\"@id/iv_menu_more\"\n        app:layout_constraintTop_toTopOf=\"@id/iv_menu_more\" />\n\n    <TextView\n        android:id=\"@+id/iv_debug_text\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginTop=\"6dp\"\n        android:scrollbars=\"none\"\n        android:singleLine=\"true\"\n        android:visibility=\"gone\"\n        app:layout_constraintBottom_toBottomOf=\"parent\"\n        app:layout_constraintLeft_toLeftOf=\"parent\"\n        app:layout_constraintRight_toLeftOf=\"@id/iv_progressBar\"\n        app:layout_constraintTop_toBottomOf=\"@id/cb_book_source\"\n        tools:text=\"@string/debug_hint\"\n        tools:visibility=\"visible\" />\n\n    <io.legado.app.lib.theme.view.ThemeProgressBar\n        android:id=\"@+id/iv_progressBar\"\n        android:layout_width=\"25dp\"\n        android:layout_height=\"25dp\"\n        android:layout_gravity=\"start|center_vertical\"\n        android:animationResolution=\"1000\"\n        android:indeterminate=\"true\"\n        android:indeterminateBehavior=\"repeat\"\n        android:scrollbars=\"none\"\n        android:visibility=\"gone\"\n        app:layout_constraintBottom_toBottomOf=\"@id/iv_debug_text\"\n        app:layout_constraintRight_toRightOf=\"parent\"\n        app:layout_constraintTop_toTopOf=\"@id/iv_debug_text\"\n        tools:visibility=\"visible\" />\n\n</androidx.constraintlayout.widget.ConstraintLayout>"
  },
  {
    "path": "app/src/main/res/layout/item_bookmark.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:orientation=\"vertical\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:padding=\"8dp\"\n    android:background=\"?android:attr/selectableItemBackground\">\n\n    <TextView\n        android:id=\"@+id/tv_chapter_name\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:padding=\"4dp\"\n        android:singleLine=\"true\" />\n\n    <TextView\n        android:id=\"@+id/tv_book_text\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:padding=\"4dp\"\n        android:textSize=\"12sp\"\n        android:singleLine=\"true\" />\n\n    <TextView\n        android:id=\"@+id/tv_content\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:padding=\"4dp\"\n        android:textSize=\"12sp\"\n        android:singleLine=\"true\" />\n\n</LinearLayout>"
  },
  {
    "path": "app/src/main/res/layout/item_bookshelf_grid.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:id=\"@+id/cv_content\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:background=\"@drawable/bg_item_focused_on_tv\"\n    android:clickable=\"true\"\n    android:focusable=\"true\"\n    android:scrollbars=\"none\"\n    tools:ignore=\"UnusedAttribute\">\n\n\n    <LinearLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:orientation=\"vertical\"\n        android:paddingLeft=\"8dp\"\n        android:paddingTop=\"8dp\"\n        android:paddingRight=\"8dp\"\n        android:scrollbars=\"none\"\n        app:layout_constraintTop_toTopOf=\"parent\">\n\n        <androidx.constraintlayout.widget.ConstraintLayout\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:scrollbars=\"none\">\n\n            <io.legado.app.ui.widget.image.CoverImageView\n                android:id=\"@+id/iv_cover\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:layout_margin=\"8dp\"\n                android:scaleType=\"centerCrop\"\n                android:scrollbars=\"none\"\n                android:src=\"@drawable/image_cover_default\"\n                android:transitionName=\"img_cover\"\n                app:layout_constraintTop_toTopOf=\"parent\"\n                tools:ignore=\"UnusedAttribute,ImageContrastCheck\"\n                tools:layout_editor_absoluteX=\"8dp\" />\n\n            <io.legado.app.ui.widget.text.BadgeView\n                android:id=\"@+id/bv_unread\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:layout_gravity=\"right\"\n                android:includeFontPadding=\"false\"\n                android:scrollbars=\"none\"\n                app:layout_constraintRight_toRightOf=\"parent\"\n                app:layout_constraintTop_toTopOf=\"parent\"\n                tools:ignore=\"RtlHardcoded\" />\n\n            <io.legado.app.ui.widget.anima.RotateLoading\n                android:id=\"@+id/rl_loading\"\n                android:layout_width=\"22dp\"\n                android:layout_height=\"22dp\"\n                android:layout_gravity=\"right\"\n                android:scrollbars=\"none\"\n                android:visibility=\"invisible\"\n                app:layout_constraintRight_toRightOf=\"parent\"\n                app:layout_constraintTop_toTopOf=\"parent\"\n                app:loading_width=\"2dp\"\n                tools:ignore=\"RtlHardcoded\" />\n\n        </androidx.constraintlayout.widget.ConstraintLayout>\n\n        <TextView\n            android:id=\"@+id/tv_name\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginTop=\"6dp\"\n            android:ellipsize=\"end\"\n            android:gravity=\"top|center_horizontal\"\n            android:includeFontPadding=\"false\"\n            android:lines=\"2\"\n            android:scrollbars=\"none\"\n            android:text=\"@string/book_name\"\n            android:textColor=\"@color/primaryText\"\n            android:textSize=\"12sp\"\n            tools:ignore=\"RtlHardcoded,RtlSymmetry\" />\n\n    </LinearLayout>\n\n    <View\n        android:id=\"@+id/vw_foreground\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"0dp\"\n        android:background=\"?android:attr/selectableItemBackground\"\n        android:scrollbars=\"none\"\n        app:layout_constraintBottom_toBottomOf=\"parent\"\n        app:layout_constraintTop_toTopOf=\"parent\"\n        app:layout_constraintVertical_bias=\"0.0\"\n        tools:layout_editor_absoluteX=\"0dp\" />\n\n</androidx.constraintlayout.widget.ConstraintLayout>\n\n\n\n"
  },
  {
    "path": "app/src/main/res/layout/item_bookshelf_grid_group.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:id=\"@+id/cv_content\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:clickable=\"true\"\n    android:focusable=\"true\"\n    tools:ignore=\"UnusedAttribute\">\n\n\n    <LinearLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:orientation=\"vertical\"\n        android:paddingTop=\"8dp\"\n        android:paddingLeft=\"8dp\"\n        android:paddingRight=\"8dp\"\n        app:layout_constraintTop_toTopOf=\"parent\">\n\n        <androidx.constraintlayout.widget.ConstraintLayout\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\">\n\n            <io.legado.app.ui.widget.image.CoverImageView\n                android:id=\"@+id/iv_cover\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:layout_margin=\"8dp\"\n                android:scaleType=\"centerCrop\"\n                android:src=\"@drawable/image_cover_default\"\n                android:transitionName=\"img_cover\"\n                app:layout_constraintTop_toTopOf=\"parent\"\n                tools:ignore=\"UnusedAttribute,ImageContrastCheck\"\n                tools:layout_editor_absoluteX=\"8dp\" />\n\n            <io.legado.app.ui.widget.text.BadgeView\n                android:id=\"@+id/bv_unread\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:layout_gravity=\"right\"\n                android:includeFontPadding=\"false\"\n                app:layout_constraintRight_toRightOf=\"parent\"\n                app:layout_constraintTop_toTopOf=\"parent\"\n                tools:ignore=\"RtlHardcoded\" />\n\n            <io.legado.app.ui.widget.anima.RotateLoading\n                android:id=\"@+id/rl_loading\"\n                android:layout_width=\"22dp\"\n                android:layout_height=\"22dp\"\n                android:layout_gravity=\"right\"\n                android:visibility=\"invisible\"\n                app:layout_constraintRight_toRightOf=\"parent\"\n                app:layout_constraintTop_toTopOf=\"parent\"\n                app:loading_width=\"2dp\"\n                tools:ignore=\"RtlHardcoded\" />\n\n        </androidx.constraintlayout.widget.ConstraintLayout>\n\n        <TextView\n            android:id=\"@+id/tv_name\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginTop=\"6dp\"\n            android:ellipsize=\"end\"\n            android:gravity=\"top|center_horizontal\"\n            android:includeFontPadding=\"false\"\n            android:lines=\"2\"\n            android:text=\"@string/book_name\"\n            android:textColor=\"@color/primaryText\"\n            android:textSize=\"12sp\"\n            tools:ignore=\"RtlHardcoded,RtlSymmetry\" />\n\n    </LinearLayout>\n\n    <View\n        android:id=\"@+id/vw_foreground\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"0dp\"\n        android:background=\"?android:attr/selectableItemBackground\"\n        app:layout_constraintBottom_toBottomOf=\"parent\"\n        app:layout_constraintTop_toTopOf=\"parent\"\n        app:layout_constraintVertical_bias=\"0.0\"\n        tools:layout_editor_absoluteX=\"0dp\" />\n\n</androidx.constraintlayout.widget.ConstraintLayout>\n\n\n\n"
  },
  {
    "path": "app/src/main/res/layout/item_bookshelf_list.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:id=\"@+id/cv_content\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:background=\"@drawable/bg_item_focused_on_tv\"\n    android:clickable=\"true\"\n    android:focusable=\"true\"\n    android:scrollbars=\"none\"\n    tools:ignore=\"UnusedAttribute\">\n\n    <io.legado.app.ui.widget.image.CoverImageView\n        android:id=\"@+id/iv_cover\"\n        android:layout_width=\"66dp\"\n        android:layout_height=\"90dp\"\n        android:layout_marginStart=\"16dp\"\n        android:layout_marginTop=\"16dp\"\n        android:layout_marginBottom=\"12dp\"\n        android:contentDescription=\"@string/img_cover\"\n        android:scaleType=\"centerCrop\"\n        android:src=\"@drawable/image_cover_default\"\n        android:transitionName=\"img_cover\"\n        app:layout_constraintBottom_toBottomOf=\"parent\"\n        app:layout_constraintLeft_toLeftOf=\"parent\"\n        app:layout_constraintTop_toTopOf=\"parent\"\n        tools:ignore=\"UnusedAttribute\" />\n\n    <FrameLayout\n        android:id=\"@+id/fl_has_new\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginEnd=\"16dp\"\n        app:layout_constraintBottom_toBottomOf=\"@+id/tv_name\"\n        app:layout_constraintRight_toRightOf=\"parent\"\n        app:layout_constraintTop_toTopOf=\"@+id/tv_name\">\n\n        <io.legado.app.ui.widget.text.BadgeView\n            android:id=\"@+id/bv_unread\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_gravity=\"right\"\n            android:layout_margin=\"5dp\"\n            android:includeFontPadding=\"false\"\n            android:scrollbars=\"none\"\n            tools:ignore=\"RtlHardcoded\" />\n\n        <io.legado.app.ui.widget.anima.RotateLoading\n            android:id=\"@+id/rl_loading\"\n            android:layout_width=\"26dp\"\n            android:layout_height=\"26dp\"\n            android:layout_gravity=\"right\"\n            android:visibility=\"invisible\"\n            app:loading_width=\"2dp\"\n            tools:ignore=\"RtlHardcoded\" />\n    </FrameLayout>\n\n    <TextView\n        android:id=\"@+id/tv_name\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginLeft=\"10dp\"\n        android:layout_marginTop=\"12dp\"\n        android:includeFontPadding=\"false\"\n        android:paddingLeft=\"2dp\"\n        android:paddingBottom=\"4dp\"\n        android:scrollbars=\"none\"\n        android:singleLine=\"true\"\n        android:text=\"@string/book_name\"\n        android:textColor=\"@color/primaryText\"\n        android:textSize=\"16sp\"\n        app:layout_constraintBottom_toTopOf=\"@+id/tv_author\"\n        app:layout_constraintLeft_toRightOf=\"@+id/iv_cover\"\n        app:layout_constraintRight_toLeftOf=\"@id/fl_has_new\"\n        app:layout_constraintTop_toTopOf=\"parent\"\n        tools:ignore=\"RtlHardcoded,RtlSymmetry\" />\n\n    <androidx.appcompat.widget.AppCompatImageView\n        android:id=\"@+id/iv_author\"\n        android:layout_width=\"@dimen/desc_icon_size\"\n        android:layout_height=\"@dimen/desc_icon_size\"\n        android:contentDescription=\"@string/author\"\n        android:paddingStart=\"2dp\"\n        android:paddingEnd=\"2dp\"\n        android:scrollbars=\"none\"\n        android:src=\"@drawable/ic_author\"\n        app:layout_constraintBottom_toBottomOf=\"@+id/tv_author\"\n        app:layout_constraintLeft_toLeftOf=\"@+id/tv_name\"\n        app:layout_constraintTop_toTopOf=\"@+id/tv_author\"\n        app:tint=\"@color/tv_text_summary\"\n        tools:ignore=\"RtlHardcoded,RtlSymmetry\" />\n\n    <TextView\n        android:id=\"@+id/tv_author\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"wrap_content\"\n        android:includeFontPadding=\"false\"\n        android:maxLines=\"1\"\n        android:paddingEnd=\"6dp\"\n        android:scrollbars=\"none\"\n        android:singleLine=\"true\"\n        android:text=\"@string/author\"\n        android:textColor=\"@color/tv_text_summary\"\n        android:textSize=\"13sp\"\n        app:layout_constraintBottom_toTopOf=\"@+id/tv_read\"\n        app:layout_constraintLeft_toRightOf=\"@+id/iv_author\"\n        app:layout_constraintRight_toLeftOf=\"@id/tv_last_update_time\"\n        app:layout_constraintTop_toBottomOf=\"@+id/tv_name\"\n        tools:ignore=\"RtlSymmetry,TextContrastCheck\" />\n\n    <TextView\n        android:id=\"@+id/tv_last_update_time\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:includeFontPadding=\"false\"\n        android:maxLines=\"1\"\n        android:paddingEnd=\"6dp\"\n        android:scrollbars=\"none\"\n        android:singleLine=\"true\"\n        android:text=\"\"\n        android:textColor=\"@color/tv_text_summary\"\n        android:textSize=\"13sp\"\n        app:layout_constraintBottom_toTopOf=\"@+id/tv_read\"\n        app:layout_constraintHorizontal_bias=\"0.0\"\n        app:layout_constraintLeft_toRightOf=\"@+id/tv_author\"\n        app:layout_constraintRight_toRightOf=\"@id/fl_has_new\"\n        app:layout_constraintTop_toBottomOf=\"@+id/tv_name\"\n        tools:ignore=\"RtlSymmetry,TextContrastCheck\" />\n\n    <androidx.appcompat.widget.AppCompatImageView\n        android:id=\"@+id/iv_read\"\n        android:layout_width=\"@dimen/desc_icon_size\"\n        android:layout_height=\"@dimen/desc_icon_size\"\n        android:contentDescription=\"@string/read_dur_progress\"\n        android:paddingStart=\"2dp\"\n        android:paddingEnd=\"2dp\"\n        android:scrollbars=\"none\"\n        android:src=\"@drawable/ic_history\"\n        app:layout_constraintBottom_toBottomOf=\"@+id/tv_read\"\n        app:layout_constraintLeft_toLeftOf=\"@+id/tv_name\"\n        app:layout_constraintTop_toTopOf=\"@+id/tv_read\"\n        app:tint=\"@color/tv_text_summary\"\n        tools:ignore=\"RtlHardcoded,RtlSymmetry\" />\n\n    <TextView\n        android:id=\"@+id/tv_read\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"wrap_content\"\n        android:includeFontPadding=\"false\"\n        android:scrollbars=\"none\"\n        android:singleLine=\"true\"\n        android:text=\"@string/read_dur_progress\"\n        android:textColor=\"@color/tv_text_summary\"\n        android:textSize=\"13sp\"\n        app:layout_constraintBottom_toTopOf=\"@id/tv_last\"\n        app:layout_constraintLeft_toRightOf=\"@+id/iv_read\"\n        app:layout_constraintRight_toRightOf=\"@+id/fl_has_new\"\n        app:layout_constraintTop_toBottomOf=\"@+id/tv_author\"\n        tools:ignore=\"TextContrastCheck\" />\n\n    <androidx.appcompat.widget.AppCompatImageView\n        android:id=\"@+id/iv_last\"\n        android:layout_width=\"@dimen/desc_icon_size\"\n        android:layout_height=\"@dimen/desc_icon_size\"\n        android:contentDescription=\"@string/lasted_show\"\n        android:paddingStart=\"2dp\"\n        android:paddingEnd=\"2dp\"\n        android:scrollbars=\"none\"\n        android:src=\"@drawable/ic_book_last\"\n        app:layout_constraintBottom_toBottomOf=\"@+id/tv_last\"\n        app:layout_constraintLeft_toLeftOf=\"@+id/tv_name\"\n        app:layout_constraintTop_toTopOf=\"@+id/tv_last\"\n        app:tint=\"@color/tv_text_summary\"\n        tools:ignore=\"RtlHardcoded,RtlSymmetry\" />\n\n    <TextView\n        android:id=\"@+id/tv_last\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginBottom=\"8dp\"\n        android:scrollbars=\"none\"\n        android:singleLine=\"true\"\n        android:text=\"@string/lasted_show\"\n        android:textColor=\"@color/tv_text_summary\"\n        android:textSize=\"13sp\"\n        app:layout_constraintBottom_toBottomOf=\"parent\"\n        app:layout_constraintLeft_toRightOf=\"@+id/iv_last\"\n        app:layout_constraintRight_toRightOf=\"@id/fl_has_new\"\n        app:layout_constraintTop_toBottomOf=\"@+id/tv_read\"\n        tools:ignore=\"TextContrastCheck\" />\n\n    <View\n        android:id=\"@+id/vw_foreground\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"0dp\"\n        android:background=\"?android:attr/selectableItemBackground\"\n        android:scrollbars=\"none\"\n        app:layout_constraintBottom_toBottomOf=\"parent\"\n        app:layout_constraintTop_toTopOf=\"parent\"\n        app:layout_constraintVertical_bias=\"0.0\"\n        tools:layout_editor_absoluteX=\"0dp\" />\n\n</androidx.constraintlayout.widget.ConstraintLayout>"
  },
  {
    "path": "app/src/main/res/layout/item_bookshelf_list_group.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:id=\"@+id/cv_content\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:clickable=\"true\"\n    android:focusable=\"true\"\n    tools:ignore=\"UnusedAttribute\">\n\n    <io.legado.app.ui.widget.image.CoverImageView\n        android:id=\"@+id/iv_cover\"\n        android:layout_width=\"66dp\"\n        android:layout_height=\"90dp\"\n        android:layout_marginStart=\"16dp\"\n        android:layout_marginTop=\"16dp\"\n        android:layout_marginBottom=\"12dp\"\n        android:contentDescription=\"@string/img_cover\"\n        android:scaleType=\"centerCrop\"\n        android:src=\"@drawable/image_cover_default\"\n        android:transitionName=\"img_cover\"\n        app:layout_constraintBottom_toBottomOf=\"parent\"\n        app:layout_constraintLeft_toLeftOf=\"parent\"\n        app:layout_constraintTop_toTopOf=\"parent\"\n        tools:ignore=\"UnusedAttribute\" />\n\n    <FrameLayout\n        android:id=\"@+id/fl_has_new\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginEnd=\"16dp\"\n        app:layout_constraintRight_toRightOf=\"parent\"\n        app:layout_constraintTop_toTopOf=\"@+id/iv_cover\">\n\n        <io.legado.app.ui.widget.text.BadgeView\n            android:id=\"@+id/bv_unread\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_gravity=\"right\"\n            android:layout_margin=\"5dp\"\n            android:includeFontPadding=\"false\"\n            tools:ignore=\"RtlHardcoded\" />\n\n        <io.legado.app.ui.widget.anima.RotateLoading\n            android:id=\"@+id/rl_loading\"\n            android:layout_width=\"26dp\"\n            android:layout_height=\"26dp\"\n            android:layout_gravity=\"right\"\n            android:visibility=\"invisible\"\n            app:loading_width=\"2dp\"\n            tools:ignore=\"RtlHardcoded\" />\n    </FrameLayout>\n\n    <TextView\n        android:id=\"@+id/tv_name\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginLeft=\"10dp\"\n        android:layout_marginTop=\"12dp\"\n        android:includeFontPadding=\"false\"\n        android:paddingBottom=\"4dp\"\n        android:paddingLeft=\"2dp\"\n        android:singleLine=\"true\"\n        android:text=\"@string/book_name\"\n        android:textColor=\"@color/primaryText\"\n        android:textSize=\"16sp\"\n        app:layout_constraintBottom_toTopOf=\"@+id/tv_author\"\n        app:layout_constraintLeft_toRightOf=\"@+id/iv_cover\"\n        app:layout_constraintRight_toLeftOf=\"@id/fl_has_new\"\n        app:layout_constraintTop_toTopOf=\"parent\"\n        tools:ignore=\"RtlHardcoded,RtlSymmetry\" />\n\n    <androidx.appcompat.widget.AppCompatImageView\n        android:id=\"@+id/iv_author\"\n        android:layout_width=\"@dimen/desc_icon_size\"\n        android:layout_height=\"@dimen/desc_icon_size\"\n        android:contentDescription=\"@string/author\"\n        android:paddingStart=\"2dp\"\n        android:paddingEnd=\"2dp\"\n        android:src=\"@drawable/ic_author\"\n        app:layout_constraintBottom_toBottomOf=\"@+id/tv_author\"\n        app:layout_constraintLeft_toLeftOf=\"@+id/tv_name\"\n        app:layout_constraintTop_toTopOf=\"@+id/tv_author\"\n        app:tint=\"@color/tv_text_summary\"\n        tools:ignore=\"RtlHardcoded,RtlSymmetry\" />\n\n    <TextView\n        android:id=\"@+id/tv_author\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"wrap_content\"\n        android:includeFontPadding=\"false\"\n        android:maxLines=\"1\"\n        android:paddingEnd=\"6dp\"\n        android:text=\"@string/author\"\n        android:textColor=\"@color/tv_text_summary\"\n        android:textSize=\"13sp\"\n        app:layout_constraintBottom_toTopOf=\"@+id/tv_read\"\n        app:layout_constraintLeft_toRightOf=\"@+id/iv_author\"\n        app:layout_constraintRight_toRightOf=\"@id/fl_has_new\"\n        app:layout_constraintTop_toBottomOf=\"@+id/tv_name\"\n        tools:ignore=\"RtlSymmetry,TextContrastCheck\" />\n\n    <androidx.appcompat.widget.AppCompatImageView\n        android:id=\"@+id/iv_read\"\n        android:layout_width=\"@dimen/desc_icon_size\"\n        android:layout_height=\"@dimen/desc_icon_size\"\n        android:contentDescription=\"@string/read_dur_progress\"\n        android:paddingStart=\"2dp\"\n        android:paddingEnd=\"2dp\"\n        android:src=\"@drawable/ic_history\"\n        app:layout_constraintBottom_toBottomOf=\"@+id/tv_read\"\n        app:layout_constraintLeft_toLeftOf=\"@+id/tv_name\"\n        app:layout_constraintTop_toTopOf=\"@+id/tv_read\"\n        app:tint=\"@color/tv_text_summary\"\n        tools:ignore=\"RtlHardcoded,RtlSymmetry\" />\n\n    <TextView\n        android:id=\"@+id/tv_read\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"wrap_content\"\n        android:includeFontPadding=\"false\"\n        android:singleLine=\"true\"\n        android:text=\"@string/read_dur_progress\"\n        android:textColor=\"@color/tv_text_summary\"\n        android:textSize=\"13sp\"\n        app:layout_constraintBottom_toTopOf=\"@id/tv_last\"\n        app:layout_constraintLeft_toRightOf=\"@+id/iv_read\"\n        app:layout_constraintRight_toRightOf=\"@+id/fl_has_new\"\n        app:layout_constraintTop_toBottomOf=\"@+id/tv_author\"\n        tools:ignore=\"TextContrastCheck\" />\n\n    <androidx.appcompat.widget.AppCompatImageView\n        android:id=\"@+id/iv_last\"\n        android:layout_width=\"@dimen/desc_icon_size\"\n        android:layout_height=\"@dimen/desc_icon_size\"\n        android:contentDescription=\"@string/lasted_show\"\n        android:paddingStart=\"2dp\"\n        android:paddingEnd=\"2dp\"\n        android:src=\"@drawable/ic_book_last\"\n        app:layout_constraintBottom_toBottomOf=\"@+id/tv_last\"\n        app:layout_constraintLeft_toLeftOf=\"@+id/tv_name\"\n        app:layout_constraintTop_toTopOf=\"@+id/tv_last\"\n        app:tint=\"@color/tv_text_summary\"\n        tools:ignore=\"RtlHardcoded,RtlSymmetry\" />\n\n    <TextView\n        android:id=\"@+id/tv_last\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginBottom=\"8dp\"\n        android:singleLine=\"true\"\n        android:text=\"@string/lasted_show\"\n        android:textColor=\"@color/tv_text_summary\"\n        android:textSize=\"13sp\"\n        app:layout_constraintBottom_toBottomOf=\"parent\"\n        app:layout_constraintLeft_toRightOf=\"@+id/iv_last\"\n        app:layout_constraintRight_toRightOf=\"@id/fl_has_new\"\n        app:layout_constraintTop_toBottomOf=\"@+id/tv_read\"\n        tools:ignore=\"TextContrastCheck\" />\n\n    <View\n        android:id=\"@+id/vw_foreground\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"0dp\"\n        android:background=\"?android:attr/selectableItemBackground\"\n        app:layout_constraintBottom_toBottomOf=\"parent\"\n        app:layout_constraintTop_toTopOf=\"parent\"\n        app:layout_constraintVertical_bias=\"0.0\"\n        tools:layout_editor_absoluteX=\"0dp\" />\n\n</androidx.constraintlayout.widget.ConstraintLayout>"
  },
  {
    "path": "app/src/main/res/layout/item_change_source.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:background=\"?android:attr/selectableItemBackground\"\n    tools:ignore=\"RtlHardcoded,RtlSymmetry\">\n\n    <androidx.appcompat.widget.AppCompatImageView\n        android:id=\"@+id/iv_good\"\n        android:layout_width=\"30dp\"\n        android:layout_height=\"0dp\"\n        android:layout_marginTop=\"5dp\"\n        android:padding=\"5dp\"\n        android:src=\"@drawable/ic_praise\"\n        android:contentDescription=\"@string/like_source\"\n        app:layout_constraintBottom_toTopOf=\"@+id/iv_bad\"\n        app:layout_constraintLeft_toLeftOf=\"parent\"\n        app:layout_constraintTop_toTopOf=\"parent\"\n        app:tint=\"@color/md_red_A200\" />\n\n    <androidx.appcompat.widget.AppCompatImageView\n        android:id=\"@+id/iv_bad\"\n        android:layout_width=\"30dp\"\n        android:layout_height=\"0dp\"\n        android:layout_marginBottom=\"5dp\"\n        android:padding=\"5dp\"\n        android:rotationX=\"180\"\n        android:src=\"@drawable/ic_praise\"\n        android:contentDescription=\"@string/not_like_source\"\n        app:layout_constraintBottom_toBottomOf=\"parent\"\n        app:layout_constraintLeft_toLeftOf=\"parent\"\n        app:layout_constraintTop_toBottomOf=\"@+id/iv_good\"\n        app:tint=\"@color/md_blue_A200\" />\n\n    <TextView\n        android:id=\"@+id/tv_origin\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginLeft=\"30dp\"\n        android:layout_marginTop=\"10dp\"\n        android:singleLine=\"true\"\n        android:textColor=\"@color/primaryText\"\n        tools:text=\"bookSourceName\"\n        app:layout_constraintLeft_toLeftOf=\"parent\"\n        app:layout_constraintRight_toLeftOf=\"@+id/tv_author\"\n        app:layout_constraintTop_toTopOf=\"parent\" />\n\n    <TextView\n        android:id=\"@+id/tv_author\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginTop=\"10dp\"\n        android:maxWidth=\"160dp\"\n        android:singleLine=\"true\"\n        tools:text=\"author\"\n        android:textColor=\"@color/secondaryText\"\n        app:layout_constraintRight_toLeftOf=\"@+id/iv_checked\"\n        app:layout_constraintTop_toTopOf=\"parent\" />\n\n    <TextView\n        android:id=\"@+id/tv_last\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginLeft=\"30dp\"\n        android:layout_marginBottom=\"10dp\"\n        android:singleLine=\"true\"\n        tools:text=\"latest chapter name\"\n        android:textColor=\"@color/secondaryText\"\n        app:layout_constraintLeft_toLeftOf=\"parent\"\n        app:layout_constraintRight_toLeftOf=\"@+id/iv_checked\"\n        app:layout_constraintTop_toBottomOf=\"@+id/tv_origin\" />\n\n    <TextView\n        android:id=\"@+id/tv_current_chapter_word_count\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginLeft=\"30dp\"\n        android:layout_marginBottom=\"10dp\"\n        android:singleLine=\"false\"\n        tools:text=\"word count\"\n        android:textColor=\"@color/secondaryText\"\n        app:layout_constraintLeft_toLeftOf=\"parent\"\n        app:layout_constraintRight_toLeftOf=\"@+id/iv_checked\"\n        app:layout_constraintTop_toBottomOf=\"@+id/tv_last\" />\n\n    <TextView\n        android:id=\"@+id/tv_respond_time\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginLeft=\"30dp\"\n        android:layout_marginBottom=\"10dp\"\n        android:singleLine=\"false\"\n        tools:text=\"respond time\"\n        android:textColor=\"@color/secondaryText\"\n        app:layout_constraintBottom_toBottomOf=\"parent\"\n        app:layout_constraintLeft_toLeftOf=\"parent\"\n        app:layout_constraintRight_toLeftOf=\"@+id/iv_checked\"\n        app:layout_constraintTop_toBottomOf=\"@+id/tv_current_chapter_word_count\" />\n\n    <androidx.appcompat.widget.AppCompatImageView\n        android:id=\"@+id/iv_checked\"\n        android:layout_width=\"40dp\"\n        android:layout_height=\"40dp\"\n        android:layout_marginTop=\"10dp\"\n        android:layout_marginBottom=\"10dp\"\n        android:padding=\"8dp\"\n        android:src=\"@drawable/ic_check\"\n        android:visibility=\"invisible\"\n        app:layout_constraintBottom_toBottomOf=\"parent\"\n        app:layout_constraintRight_toRightOf=\"parent\"\n        app:layout_constraintTop_toTopOf=\"parent\"\n        app:tint=\"@color/primaryText\"\n        tools:visibility=\"visible\" />\n\n\n</androidx.constraintlayout.widget.ConstraintLayout>"
  },
  {
    "path": "app/src/main/res/layout/item_chapter_list.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:id=\"@+id/tv_chapter_item\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:background=\"?android:attr/selectableItemBackground\"\n    android:orientation=\"vertical\"\n    android:padding=\"12dp\">\n\n    <ImageView\n        android:id=\"@+id/iv_locked\"\n        android:layout_width=\"24dp\"\n        android:layout_height=\"24dp\"\n        android:paddingEnd=\"8dp\"\n        android:src=\"@drawable/ic_lock_outline\"\n        android:visibility=\"gone\"\n        app:layout_constraintBottom_toBottomOf=\"parent\"\n        app:layout_constraintLeft_toLeftOf=\"parent\"\n        app:layout_constraintTop_toTopOf=\"parent\"\n        app:tint=\"@color/secondaryText\" />\n\n    <TextView\n        android:id=\"@+id/tv_chapter_name\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"wrap_content\"\n        android:singleLine=\"true\"\n        app:layout_constraintBottom_toTopOf=\"@id/barrier\"\n        app:layout_constraintLeft_toRightOf=\"@+id/iv_locked\"\n        app:layout_constraintRight_toLeftOf=\"@+id/iv_checked\"\n        app:layout_constraintTop_toTopOf=\"parent\" />\n\n    <androidx.constraintlayout.widget.Barrier\n        android:id=\"@+id/barrier\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        app:barrierDirection=\"top\"\n        app:constraint_referenced_ids=\"tv_word_count,tv_tag\" />\n\n    <TextView\n        android:id=\"@+id/tv_word_count\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:paddingEnd=\"18dp\"\n        android:singleLine=\"true\"\n        android:textSize=\"12sp\"\n        android:visibility=\"gone\"\n        app:layout_constraintBottom_toBottomOf=\"parent\"\n        app:layout_constraintLeft_toRightOf=\"@+id/iv_locked\"\n        app:layout_constraintTop_toBottomOf=\"@+id/barrier\"\n        tools:ignore=\"RtlSymmetry\" />\n\n    <TextView\n        android:id=\"@+id/tv_tag\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"wrap_content\"\n        android:singleLine=\"true\"\n        android:textSize=\"12sp\"\n        android:visibility=\"gone\"\n        app:layout_constraintBottom_toBottomOf=\"parent\"\n        app:layout_constraintLeft_toRightOf=\"@id/tv_word_count\"\n        app:layout_constraintRight_toLeftOf=\"@+id/iv_checked\"\n        app:layout_constraintTop_toBottomOf=\"@+id/barrier\" />\n\n    <ImageView\n        android:id=\"@+id/iv_checked\"\n        android:layout_width=\"24dp\"\n        android:layout_height=\"24dp\"\n        android:contentDescription=\"@string/success\"\n        android:padding=\"4dp\"\n        android:src=\"@drawable/ic_check\"\n        android:visibility=\"invisible\"\n        app:layout_constraintBottom_toBottomOf=\"parent\"\n        app:layout_constraintRight_toRightOf=\"parent\"\n        app:layout_constraintTop_toTopOf=\"parent\"\n        app:tint=\"@color/secondaryText\" />\n\n</androidx.constraintlayout.widget.ConstraintLayout>"
  },
  {
    "path": "app/src/main/res/layout/item_check_box.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<FrameLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:layout_gravity=\"center\"\n    android:paddingHorizontal=\"16dp\"\n    android:paddingVertical=\"8dp\">\n\n    <io.legado.app.lib.theme.view.ThemeCheckBox\n        android:id=\"@+id/check_box\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        tools:ignore=\"TouchTargetSizeCheck\" />\n\n</FrameLayout>"
  },
  {
    "path": "app/src/main/res/layout/item_cover.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:padding=\"12dp\"\n    android:clickable=\"true\"\n    android:focusable=\"true\"\n    android:orientation=\"vertical\">\n\n    <io.legado.app.ui.widget.image.CoverImageView\n        android:id=\"@+id/iv_cover\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"100dp\"\n        android:contentDescription=\"@string/img_cover\"\n        android:scaleType=\"centerCrop\"\n        android:transitionName=\"img_cover\"\n        tools:ignore=\"UnusedAttribute\" />\n\n    <TextView\n        android:id=\"@+id/tv_source\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginTop=\"8dp\"\n        android:gravity=\"center\"\n        android:singleLine=\"true\"\n        android:text=\"@string/book_source\"\n        android:textColor=\"@color/primaryText\"\n        android:textSize=\"16sp\"\n        tools:ignore=\"RtlHardcoded,RtlSymmetry\" />\n\n</LinearLayout>"
  },
  {
    "path": "app/src/main/res/layout/item_dict_rule.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:gravity=\"center\"\n    android:orientation=\"horizontal\"\n    android:padding=\"8dp\">\n\n    <io.legado.app.lib.theme.view.ThemeCheckBox\n        android:id=\"@+id/cb_name\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"wrap_content\"\n        android:layout_weight=\"1\"\n        tools:ignore=\"RtlSymmetry\" />\n\n    <io.legado.app.lib.theme.view.ThemeSwitch\n        android:id=\"@+id/swt_enabled\"\n        android:name=\"@string/enable\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginLeft=\"6dp\"\n        android:layout_marginRight=\"6dp\"\n        tools:ignore=\"RtlSymmetry\" />\n\n    <androidx.appcompat.widget.AppCompatImageView\n        android:id=\"@+id/iv_edit\"\n        android:layout_width=\"36dp\"\n        android:layout_height=\"36dp\"\n        android:layout_gravity=\"center\"\n        android:background=\"?android:attr/selectableItemBackgroundBorderless\"\n        android:contentDescription=\"@string/edit\"\n        android:padding=\"6dp\"\n        android:src=\"@drawable/ic_edit\"\n        android:tint=\"@color/primaryText\" />\n\n    <androidx.appcompat.widget.AppCompatImageView\n        android:id=\"@+id/iv_delete\"\n        android:layout_width=\"36dp\"\n        android:layout_height=\"36dp\"\n        android:padding=\"6dp\"\n        android:background=\"?attr/selectableItemBackgroundBorderless\"\n        android:src=\"@drawable/ic_outline_delete\"\n        android:tint=\"@color/primaryText\"\n        android:contentDescription=\"@string/delete\"\n        tools:ignore=\"RtlHardcoded\" />\n\n</LinearLayout>\n"
  },
  {
    "path": "app/src/main/res/layout/item_download.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:padding=\"16dp\">\n\n    <TextView\n        android:id=\"@+id/tv_name\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"wrap_content\"\n        android:singleLine=\"true\"\n        android:textColor=\"@color/primaryText\"\n        app:layout_constraintLeft_toLeftOf=\"parent\"\n        app:layout_constraintRight_toLeftOf=\"@+id/iv_download\"\n        app:layout_constraintTop_toTopOf=\"parent\"\n        tools:text=\"@string/name\" />\n\n    <TextView\n        android:id=\"@+id/tv_author\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"wrap_content\"\n        android:singleLine=\"true\"\n        android:textColor=\"@color/primaryText\"\n        app:layout_constraintLeft_toLeftOf=\"parent\"\n        app:layout_constraintRight_toLeftOf=\"@+id/iv_download\"\n        app:layout_constraintTop_toBottomOf=\"@+id/tv_name\"\n        tools:text=\"@string/author\" />\n\n    <TextView\n        android:id=\"@+id/tv_download\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"wrap_content\"\n        android:singleLine=\"true\"\n        android:textColor=\"@color/secondaryText\"\n        app:layout_constraintLeft_toLeftOf=\"parent\"\n        app:layout_constraintRight_toLeftOf=\"@+id/iv_download\"\n        app:layout_constraintTop_toBottomOf=\"@id/tv_author\"\n        tools:text=\"@string/action_download\" />\n\n    <ImageView\n        android:id=\"@+id/iv_download\"\n        android:layout_width=\"36dp\"\n        android:layout_height=\"36dp\"\n        android:background=\"?attr/selectableItemBackgroundBorderless\"\n        android:contentDescription=\"@string/start\"\n        android:padding=\"6dp\"\n        android:src=\"@drawable/ic_play_24dp\"\n        app:layout_constraintBottom_toBottomOf=\"@+id/tv_download\"\n        app:layout_constraintRight_toLeftOf=\"@+id/tv_export\"\n        app:layout_constraintTop_toTopOf=\"@id/tv_name\"\n        app:tint=\"@color/primaryText\" />\n\n    <TextView\n        android:id=\"@+id/tv_export\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:background=\"?attr/selectableItemBackground\"\n        android:padding=\"10dp\"\n        android:text=\"@string/export\"\n        android:textColor=\"@color/primaryText\"\n        app:layout_constraintBottom_toBottomOf=\"@+id/tv_download\"\n        app:layout_constraintRight_toRightOf=\"parent\"\n        app:layout_constraintTop_toTopOf=\"@id/tv_name\" />\n\n    <TextView\n        android:id=\"@+id/tv_msg\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:textColor=\"@color/secondaryText\"\n        android:visibility=\"gone\"\n        app:layout_constraintTop_toBottomOf=\"@id/tv_download\" />\n\n    <io.legado.app.lib.theme.view.ThemeProgressBar\n        android:id=\"@+id/progress_export\"\n        style=\"?android:attr/progressBarStyleHorizontal\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:visibility=\"gone\"\n        app:layout_constraintTop_toBottomOf=\"@id/tv_download\" />\n\n</androidx.constraintlayout.widget.ConstraintLayout>"
  },
  {
    "path": "app/src/main/res/layout/item_file.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:gravity=\"center_vertical\"\n    android:orientation=\"horizontal\"\n    android:padding=\"5dp\"\n    android:background=\"?attr/selectableItemBackground\"\n    tools:ignore=\"UseCompoundDrawables\">\n\n    <ImageView\n        android:id=\"@+id/image_view\"\n        android:layout_width=\"24dp\"\n        android:layout_height=\"24dp\"\n        tools:ignore=\"ContentDescription\" />\n\n    <TextView\n        android:id=\"@+id/text_view\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"match_parent\"\n        android:gravity=\"center_vertical\" />\n\n</LinearLayout>"
  },
  {
    "path": "app/src/main/res/layout/item_file_picker.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<FrameLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\">\n\n    <LinearLayout\n        android:orientation=\"horizontal\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:gravity=\"center_vertical\"\n        android:background=\"?attr/selectableItemBackground\"\n        android:padding=\"5dp\"\n        tools:ignore=\"UseCompoundDrawables,UselessParent\">\n\n        <ImageView\n            android:id=\"@+id/image_view\"\n            android:layout_width=\"24dp\"\n            android:layout_height=\"24dp\"\n            tools:ignore=\"ContentDescription\" />\n\n        <TextView\n            android:id=\"@+id/text_view\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"match_parent\"\n            android:gravity=\"center_vertical\" />\n\n    </LinearLayout>\n\n</FrameLayout>"
  },
  {
    "path": "app/src/main/res/layout/item_fillet_text.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<TextView xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:id=\"@+id/text_view\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"wrap_content\"\n    android:layout_margin=\"3dp\"\n    android:background=\"@drawable/selector_fillet_btn_bg\"\n    android:ellipsize=\"end\"\n    android:gravity=\"center\"\n    android:paddingTop=\"4dp\"\n    android:paddingBottom=\"4dp\"\n    android:paddingLeft=\"12dp\"\n    android:paddingRight=\"12dp\"\n    android:maxLines=\"1\"\n    android:textColor=\"@color/primaryText\"\n    android:textSize=\"14sp\"\n    tools:ignore=\"UnusedAttribute\" />\n"
  },
  {
    "path": "app/src/main/res/layout/item_find_book.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:gravity=\"center\"\n    android:orientation=\"vertical\"\n    android:focusable=\"true\"\n    android:background=\"@drawable/bg_item_focused_on_tv\">\n\n    <LinearLayout\n        android:id=\"@+id/ll_title\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:paddingLeft=\"10dp\"\n        android:paddingRight=\"10dp\"\n        android:paddingTop=\"6dp\"\n        android:paddingBottom=\"6dp\"\n        android:background=\"@drawable/bg_find_book_group\"\n        android:orientation=\"horizontal\"\n        android:gravity=\"center_vertical\">\n\n        <TextView\n            android:id=\"@+id/tv_name\"\n            android:layout_width=\"0dp\"\n            android:layout_height=\"wrap_content\"\n            android:layout_weight=\"1\"\n            android:textColor=\"@color/primaryText\"\n            tools:text=\"起点中文\" />\n\n        <io.legado.app.ui.widget.anima.RotateLoading\n            android:id=\"@+id/rotate_loading\"\n            android:layout_width=\"20dp\"\n            android:layout_height=\"20dp\"\n            android:visibility=\"gone\"\n            android:layout_gravity=\"center\"\n            android:layout_marginRight=\"4dp\"\n            app:loading_width=\"1dp\"\n            tools:ignore=\"RtlHardcoded\" />\n\n        <ImageView\n            android:id=\"@+id/iv_status\"\n            android:layout_width=\"20dp\"\n            android:layout_height=\"20dp\"\n            android:src=\"@drawable/ic_arrow_right\"\n            app:tint=\"@color/secondaryText\"\n            tools:ignore=\"ContentDescription\" />\n\n    </LinearLayout>\n\n    <FrameLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:paddingLeft=\"8dp\"\n        android:paddingRight=\"8dp\"\n        android:layout_gravity=\"center\">\n\n        <com.google.android.flexbox.FlexboxLayout\n            android:id=\"@+id/flexbox\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:clipToPadding=\"false\"\n            android:overScrollMode=\"never\"\n            android:padding=\"3dp\"\n            android:layout_marginTop=\"8dp\"\n            android:visibility=\"gone\"\n            app:dividerDrawable=\"@drawable/shape_space_divider\"\n            app:flexDirection=\"row\"\n            app:flexWrap=\"wrap\"\n            app:showDivider=\"middle\" />\n\n    </FrameLayout>\n\n</LinearLayout>"
  },
  {
    "path": "app/src/main/res/layout/item_font.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:background=\"?android:attr/selectableItemBackground\"\n    android:gravity=\"center_vertical\"\n    android:orientation=\"horizontal\"\n    android:padding=\"5dp\">\n\n    <TextView\n        android:id=\"@+id/tv_font\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"wrap_content\"\n        android:layout_weight=\"1\"\n        android:padding=\"5dp\"\n        android:textColor=\"@color/primaryText\" />\n\n    <androidx.appcompat.widget.AppCompatImageView\n        android:id=\"@+id/iv_checked\"\n        android:layout_width=\"24dp\"\n        android:layout_height=\"24dp\"\n        android:src=\"@drawable/ic_check\"\n        android:visibility=\"invisible\"\n        app:tint=\"@color/primaryText\" />\n\n</LinearLayout>"
  },
  {
    "path": "app/src/main/res/layout/item_group_manage.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:background=\"@color/background\"\n    android:gravity=\"center_vertical\"\n    android:orientation=\"horizontal\"\n    android:padding=\"8dp\">\n\n    <TextView\n        android:id=\"@+id/tv_group\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"wrap_content\"\n        android:layout_weight=\"1\"\n        android:textColor=\"@color/primaryText\" />\n\n    <TextView\n        android:id=\"@+id/tv_edit\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:background=\"?attr/selectableItemBackground\"\n        android:padding=\"8dp\"\n        android:text=\"@string/edit\"\n        android:textColor=\"@color/primaryText\" />\n\n    <TextView\n        android:id=\"@+id/tv_del\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginEnd=\"12dp\"\n        android:background=\"?attr/selectableItemBackground\"\n        android:padding=\"8dp\"\n        android:text=\"@string/delete\"\n        android:textColor=\"@color/primaryText\" />\n\n</LinearLayout>"
  },
  {
    "path": "app/src/main/res/layout/item_group_select.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:background=\"@color/background\"\n    android:padding=\"8dp\"\n    android:gravity=\"center_vertical\"\n    android:orientation=\"horizontal\">\n\n    <io.legado.app.lib.theme.view.ThemeCheckBox\n        android:id=\"@+id/cb_group\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"wrap_content\"\n        android:layout_gravity=\"center\"\n        android:layout_weight=\"1\"\n        android:singleLine=\"true\"\n        android:textColor=\"@color/primaryText\" />\n\n    <TextView\n        android:id=\"@+id/tv_edit\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:background=\"?attr/selectableItemBackground\"\n        android:padding=\"8dp\"\n        android:text=\"@string/edit\" />\n\n</LinearLayout>"
  },
  {
    "path": "app/src/main/res/layout/item_http_tts.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:gravity=\"center\"\n    android:padding=\"8dp\">\n\n    <io.legado.app.lib.theme.view.ThemeRadioButton\n        android:id=\"@+id/cb_name\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"wrap_content\"\n        app:layout_constraintBottom_toBottomOf=\"parent\"\n        app:layout_constraintLeft_toLeftOf=\"parent\"\n        app:layout_constraintRight_toLeftOf=\"@id/iv_edit\"\n        app:layout_constraintTop_toTopOf=\"parent\" />\n\n    <androidx.appcompat.widget.AppCompatImageView\n        android:id=\"@+id/iv_edit\"\n        android:layout_width=\"36dp\"\n        android:layout_height=\"36dp\"\n        android:layout_gravity=\"center\"\n        android:background=\"?android:attr/selectableItemBackgroundBorderless\"\n        android:contentDescription=\"@string/edit\"\n        android:padding=\"6dp\"\n        android:src=\"@drawable/ic_edit\"\n        android:tint=\"@color/primaryText\"\n        app:layout_constraintBottom_toBottomOf=\"parent\"\n        app:layout_constraintRight_toLeftOf=\"@+id/iv_menu_delete\"\n        app:layout_constraintTop_toTopOf=\"parent\" />\n\n    <androidx.appcompat.widget.AppCompatImageView\n        android:id=\"@+id/iv_menu_delete\"\n        android:layout_width=\"36dp\"\n        android:layout_height=\"36dp\"\n        android:background=\"?attr/selectableItemBackgroundBorderless\"\n        android:padding=\"6dp\"\n        android:src=\"@drawable/ic_clear_all\"\n        android:tint=\"@color/primaryText\"\n        app:layout_constraintBottom_toBottomOf=\"parent\"\n        app:layout_constraintRight_toRightOf=\"parent\"\n        app:layout_constraintTop_toTopOf=\"parent\"\n        app:layout_constraintVertical_bias=\"1.0\"\n        tools:ignore=\"RtlHardcoded\" />\n\n    <io.legado.app.ui.widget.text.BevelLabelView\n        android:id=\"@+id/label_sys\"\n        android:layout_width=\"36dp\"\n        android:layout_height=\"36dp\"\n        android:visibility=\"gone\"\n        app:label_mode=\"right_top\"\n        app:label_length=\"24dp\"\n        app:label_text=\"SYS\"\n        app:layout_constraintRight_toRightOf=\"parent\"\n        app:layout_constraintTop_toTopOf=\"parent\" />\n\n</androidx.constraintlayout.widget.ConstraintLayout>"
  },
  {
    "path": "app/src/main/res/layout/item_icon_preference.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<RelativeLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"?android:attr/listPreferredItemHeight\"\n    android:padding=\"6dip\">\n\n    <ImageView\n        android:id=\"@+id/icon\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"match_parent\"\n        android:adjustViewBounds=\"true\"\n        android:contentDescription=\"ICON\"\n        android:padding=\"10dip\"\n        tools:ignore=\"HardcodedText\" />\n\n    <CheckedTextView\n        android:id=\"@+id/label\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:paddingRight=\"10dp\"\n        android:layout_alignParentEnd=\"true\"\n        android:layout_toRightOf=\"@+id/icon\"\n        android:checkMark=\"?android:attr/listChoiceIndicatorSingle\"\n        android:ellipsize=\"marquee\"\n        android:gravity=\"center_vertical\"\n        android:minHeight=\"?android:attr/listPreferredItemHeight\"\n        android:singleLine=\"true\"\n        android:textAppearance=\"?android:attr/textAppearanceMedium\"\n        android:textColor=\"?android:attr/textColorAlertDialogListItem\"\n        android:textIsSelectable=\"false\"\n        tools:ignore=\"RtlHardcoded,RtlSymmetry\" />\n\n</RelativeLayout>"
  },
  {
    "path": "app/src/main/res/layout/item_import_book.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"60dp\"\n    android:background=\"?attr/selectableItemBackground\"\n    android:orientation=\"horizontal\"\n    android:baselineAligned=\"false\">\n\n    <FrameLayout\n        android:layout_width=\"60dp\"\n        android:layout_height=\"match_parent\">\n        <!--选择是否添加文件-->\n        <io.legado.app.lib.theme.view.ThemeCheckBox\n            android:id=\"@+id/cb_select\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_gravity=\"center\"\n            android:clickable=\"false\"\n            android:enabled=\"true\"\n            android:focusable=\"true\" />\n        <!--文件夹标识或已选文件标识-->\n        <androidx.appcompat.widget.AppCompatImageView\n            android:id=\"@+id/iv_icon\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"match_parent\"\n            android:scaleType=\"center\"\n            android:visibility=\"invisible\"\n            android:src=\"@drawable/ic_folder\"\n            app:tint=\"@color/primaryText\" />\n    </FrameLayout>\n\n    <LinearLayout\n        android:layout_width=\"0dp\"\n        android:layout_height=\"match_parent\"\n        android:layout_weight=\"1\"\n        android:paddingTop=\"5dp\"\n        android:paddingBottom=\"5dp\"\n        android:orientation=\"vertical\">\n\n        <TextView\n            android:id=\"@+id/tv_name\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"0dp\"\n            android:layout_weight=\"1\"\n            android:singleLine=\"true\"\n            android:text=\"@string/app_name\"\n            android:textSize=\"16sp\"\n            android:gravity=\"center_vertical\"\n            tools:ignore=\"NestedWeights\" />\n\n        <LinearLayout\n            android:id=\"@+id/ll_brief\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"0dp\"\n            android:layout_weight=\"1\"\n            android:gravity=\"center_vertical\"\n            android:orientation=\"horizontal\">\n\n            <io.legado.app.ui.widget.text.AccentBgTextView\n                android:id=\"@+id/tv_tag\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginRight=\"15dp\"\n                android:paddingStart=\"5dp\"\n                android:paddingEnd=\"5dp\"\n                android:text=\"TXT\"\n                android:maxLines=\"1\"\n                android:maxWidth=\"50dp\"\n                app:radius=\"2dp\"\n                tools:ignore=\"HardcodedText,RtlHardcoded\" />\n\n            <TextView\n                android:id=\"@+id/tv_size\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginRight=\"15dp\"\n                tools:ignore=\"RtlHardcoded\"\n                tools:text=\"324kb\" />\n\n            <TextView\n                android:id=\"@+id/tv_date\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                tools:text=\"2017-05-22\" />\n        </LinearLayout>\n\n    </LinearLayout>\n</LinearLayout>"
  },
  {
    "path": "app/src/main/res/layout/item_log.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<TextView xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:id=\"@+id/text_view\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:textIsSelectable=\"true\"\n    android:autoLink=\"web\" />"
  },
  {
    "path": "app/src/main/res/layout/item_path_picker.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:orientation=\"horizontal\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"match_parent\"\n    android:clickable=\"true\"\n    android:focusable=\"true\">\n\n    <TextView\n        android:id=\"@+id/text_view\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"match_parent\"\n        android:gravity=\"center_vertical\" />\n\n    <ImageView\n        android:id=\"@+id/image_view\"\n        android:layout_width=\"20dp\"\n        android:layout_height=\"match_parent\"\n        tools:ignore=\"ContentDescription\" />\n\n</LinearLayout>"
  },
  {
    "path": "app/src/main/res/layout/item_radio_button.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<FrameLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:layout_gravity=\"center\"\n    android:paddingHorizontal=\"16dp\"\n    android:paddingVertical=\"8dp\">\n\n    <io.legado.app.lib.theme.view.ThemeRadioButton\n        android:id=\"@+id/radio_button\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\" />\n\n</FrameLayout>"
  },
  {
    "path": "app/src/main/res/layout/item_read_record.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:background=\"?attr/selectableItemBackground\"\n    android:paddingLeft=\"10dp\"\n    android:paddingRight=\"10dp\">\n\n    <TextView\n        android:id=\"@+id/tv_book_name\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"wrap_content\"\n        android:padding=\"6dp\"\n        android:textColor=\"@color/primaryText\"\n        android:singleLine=\"true\"\n        app:layout_constraintLeft_toLeftOf=\"parent\"\n        app:layout_constraintTop_toTopOf=\"parent\"\n        app:layout_constraintRight_toLeftOf=\"@+id/tv_remove\"\n        tools:text=\"name\"\n        tools:ignore=\"RtlHardcoded\" />\n\n    <TextView\n        android:id=\"@+id/tv_reading_time_tag\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:padding=\"6dp\"\n        android:singleLine=\"true\"\n        android:text=\"@string/reading_time_tag\"\n        android:textColor=\"@color/secondaryText\"\n        app:layout_constraintLeft_toLeftOf=\"parent\"\n        app:layout_constraintTop_toBottomOf=\"@id/tv_book_name\" />\n\n    <TextView\n        android:id=\"@+id/tv_reading_time\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"wrap_content\"\n        android:padding=\"6dp\"\n        android:singleLine=\"true\"\n        android:textColor=\"@color/secondaryText\"\n        app:layout_constraintLeft_toRightOf=\"@+id/tv_reading_time_tag\"\n        app:layout_constraintTop_toBottomOf=\"@id/tv_book_name\"\n        app:layout_constraintRight_toLeftOf=\"@+id/tv_remove\"\n        tools:text=\"readingTime\" />\n\n    <TextView\n        android:id=\"@+id/tv_last_read_time_tag\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:padding=\"6dp\"\n        android:singleLine=\"true\"\n        android:text=\"@string/last_read_time_tag\"\n        android:textColor=\"@color/secondaryText\"\n        app:layout_constraintLeft_toLeftOf=\"parent\"\n        app:layout_constraintTop_toBottomOf=\"@id/tv_reading_time_tag\" />\n\n    <TextView\n        android:id=\"@+id/tv_last_read_time\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"wrap_content\"\n        android:padding=\"6dp\"\n        android:singleLine=\"true\"\n        android:textColor=\"@color/secondaryText\"\n        app:layout_constraintLeft_toRightOf=\"@+id/tv_last_read_time_tag\"\n        app:layout_constraintTop_toBottomOf=\"@id/tv_reading_time_tag\"\n        app:layout_constraintRight_toLeftOf=\"@+id/tv_remove\"\n        tools:text=\"readingTime\" />\n\n    <TextView\n        android:id=\"@+id/tv_remove\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:padding=\"6dp\"\n        android:text=\"@string/clear\"\n        android:textColor=\"@color/primaryText\"\n        android:background=\"?attr/selectableItemBackground\"\n        app:layout_constraintRight_toRightOf=\"parent\"\n        app:layout_constraintTop_toTopOf=\"parent\"\n        app:layout_constraintBottom_toBottomOf=\"parent\" />\n\n</androidx.constraintlayout.widget.ConstraintLayout>"
  },
  {
    "path": "app/src/main/res/layout/item_read_style.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<io.legado.app.ui.widget.image.CircleImageView xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:id=\"@+id/iv_style\"\n    android:layout_width=\"48dp\"\n    android:layout_height=\"48dp\"\n    android:layout_marginLeft=\"6dp\"\n    android:layout_marginRight=\"6dp\"\n    android:scaleType=\"centerCrop\"\n    android:src=\"@drawable/image_cover_default\"\n    app:civ_border_color=\"@color/primaryText\"\n    app:civ_border_width=\"1dp\"\n    app:text=\"@string/text\" />"
  },
  {
    "path": "app/src/main/res/layout/item_replace_rule.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:gravity=\"center\"\n    android:orientation=\"horizontal\"\n    android:padding=\"8dp\"\n    android:paddingStart=\"16dp\"\n    android:paddingEnd=\"16dp\">\n\n    <io.legado.app.lib.theme.view.ThemeCheckBox\n        android:id=\"@+id/cb_name\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"wrap_content\"\n        android:scrollbars=\"none\"\n        android:singleLine=\"true\"\n        app:layout_constraintBottom_toBottomOf=\"parent\"\n        app:layout_constraintLeft_toLeftOf=\"parent\"\n        app:layout_constraintRight_toLeftOf=\"@+id/swt_enabled\"\n        app:layout_constraintTop_toTopOf=\"parent\"\n        tools:ignore=\"RtlSymmetry\" />\n\n    <io.legado.app.lib.theme.view.ThemeSwitch\n        android:id=\"@+id/swt_enabled\"\n        android:name=\"@string/enable\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginLeft=\"6dp\"\n        android:layout_marginRight=\"6dp\"\n        app:layout_constraintBottom_toBottomOf=\"parent\"\n        app:layout_constraintRight_toLeftOf=\"@id/iv_edit\"\n        app:layout_constraintTop_toTopOf=\"parent\"\n        tools:ignore=\"RtlSymmetry\" />\n\n    <androidx.appcompat.widget.AppCompatImageView\n        android:id=\"@+id/iv_edit\"\n        android:layout_width=\"36dp\"\n        android:layout_height=\"36dp\"\n        android:layout_gravity=\"center\"\n        android:background=\"?android:attr/selectableItemBackgroundBorderless\"\n        android:contentDescription=\"@string/edit\"\n        android:padding=\"6dp\"\n        android:src=\"@drawable/ic_edit\"\n        android:tint=\"@color/primaryText\"\n        app:layout_constraintBottom_toBottomOf=\"parent\"\n        app:layout_constraintRight_toLeftOf=\"@id/iv_menu_more\"\n        app:layout_constraintTop_toTopOf=\"parent\" />\n\n    <androidx.appcompat.widget.AppCompatImageView\n        android:id=\"@+id/iv_menu_more\"\n        android:layout_width=\"36dp\"\n        android:layout_height=\"36dp\"\n        android:background=\"?attr/selectableItemBackgroundBorderless\"\n        android:contentDescription=\"@string/more_menu\"\n        android:padding=\"6dp\"\n        android:src=\"@drawable/ic_more_vert\"\n        android:tint=\"@color/primaryText\"\n        app:layout_constraintBottom_toBottomOf=\"parent\"\n        app:layout_constraintRight_toRightOf=\"parent\"\n        app:layout_constraintTop_toTopOf=\"parent\"\n        tools:ignore=\"RtlHardcoded\" />\n\n</androidx.constraintlayout.widget.ConstraintLayout>\n"
  },
  {
    "path": "app/src/main/res/layout/item_rss.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:background=\"@drawable/bg_item_focused_on_tv\"\n    android:focusable=\"true\"\n    android:foreground=\"?android:attr/selectableItemBackground\"\n    android:padding=\"16dp\"\n    android:scrollbars=\"none\">\n\n    <io.legado.app.ui.widget.image.FilletImageView\n        android:id=\"@+id/iv_icon\"\n        android:layout_width=\"50dp\"\n        android:layout_height=\"50dp\"\n        android:scaleType=\"centerCrop\"\n        android:scrollbars=\"none\"\n        app:layout_constraintLeft_toLeftOf=\"parent\"\n        app:layout_constraintRight_toRightOf=\"parent\"\n        app:layout_constraintTop_toTopOf=\"parent\"\n        app:radius=\"12dp\" />\n\n    <TextView\n        android:id=\"@+id/tv_name\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginTop=\"16dp\"\n        android:ellipsize=\"end\"\n        android:gravity=\"top|center_horizontal\"\n        android:lines=\"2\"\n        android:scrollbars=\"none\"\n        android:textColor=\"@color/secondaryText\"\n        android:textSize=\"13sp\"\n        app:layout_constraintLeft_toLeftOf=\"parent\"\n        app:layout_constraintRight_toRightOf=\"parent\"\n        app:layout_constraintTop_toBottomOf=\"@+id/iv_icon\"\n        tools:text=\"RSS\" />\n\n\n</androidx.constraintlayout.widget.ConstraintLayout>"
  },
  {
    "path": "app/src/main/res/layout/item_rss_article.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"100dp\"\n    android:padding=\"16dp\">\n\n    <TextView\n        android:id=\"@+id/tv_title\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"0dp\"\n        android:maxLines=\"2\"\n        android:ellipsize=\"end\"\n        android:text=\"@string/app_name\"\n        android:textColor=\"@color/primaryText\"\n        android:textSize=\"16sp\"\n        android:textStyle=\"bold\"\n        app:layout_constraintBottom_toTopOf=\"@+id/tv_pub_date\"\n        app:layout_constraintLeft_toLeftOf=\"parent\"\n        app:layout_constraintRight_toLeftOf=\"@+id/image_view\"\n        app:layout_constraintTop_toTopOf=\"parent\" />\n\n    <TextView\n        android:id=\"@+id/tv_pub_date\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"wrap_content\"\n        android:layout_gravity=\"center\"\n        android:layout_marginTop=\"8dp\"\n        android:text=\"@string/app_name\"\n        android:textSize=\"12sp\"\n        android:textStyle=\"italic\"\n        android:maxLines=\"1\"\n        app:layout_constraintBottom_toBottomOf=\"parent\"\n        app:layout_constraintLeft_toLeftOf=\"parent\"\n        app:layout_constraintRight_toLeftOf=\"@+id/image_view\" />\n\n    <ImageView\n        android:id=\"@+id/image_view\"\n        android:layout_width=\"110dp\"\n        android:layout_height=\"68dp\"\n        android:contentDescription=\"@string/img_cover\"\n        android:paddingLeft=\"16dp\"\n        android:visibility=\"gone\"\n        android:scaleType=\"centerCrop\"\n        android:src=\"@drawable/image_rss_article\"\n        app:layout_constraintRight_toRightOf=\"parent\"\n        app:layout_constraintTop_toTopOf=\"parent\"\n        tools:ignore=\"RtlHardcoded,RtlSymmetry\" />\n\n</androidx.constraintlayout.widget.ConstraintLayout>"
  },
  {
    "path": "app/src/main/res/layout/item_rss_article_1.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\">\n\n    <ImageView\n        android:id=\"@+id/image_view\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"220dp\"\n        android:contentDescription=\"@string/loading\"\n        android:visibility=\"visible\"\n        android:scaleType=\"centerCrop\"\n        android:paddingTop=\"12dp\"\n        android:paddingRight=\"12dp\"\n        android:paddingLeft=\"12dp\"\n        app:layout_constraintTop_toTopOf=\"parent\"\n        app:layout_constraintLeft_toLeftOf=\"parent\"\n        app:layout_constraintRight_toRightOf=\"parent\"\n        tools:ignore=\"RtlHardcoded,RtlSymmetry\" />\n\n    <TextView\n        android:id=\"@+id/tv_title\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"wrap_content\"\n        android:maxLines=\"2\"\n        android:ellipsize=\"end\"\n        android:text=\"@string/app_name\"\n        android:textColor=\"@color/primaryText\"\n        android:textSize=\"15sp\"\n        android:textStyle=\"bold\"\n        android:paddingLeft=\"12dp\"\n        android:paddingRight=\"12dp\"\n        android:paddingTop=\"12dp\"\n        app:layout_constraintTop_toBottomOf=\"@+id/image_view\"\n        app:layout_constraintLeft_toLeftOf=\"parent\"\n        app:layout_constraintRight_toRightOf=\"parent\" />\n\n    <TextView\n        android:id=\"@+id/tv_pub_date\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"wrap_content\"\n        android:layout_gravity=\"center\"\n        android:text=\"@string/app_name\"\n        android:textSize=\"11sp\"\n        android:maxLines=\"1\"\n        android:paddingLeft=\"12dp\"\n        android:paddingRight=\"12dp\"\n        android:paddingTop=\"10dp\"\n        app:layout_constraintTop_toBottomOf=\"@+id/tv_title\"\n        app:layout_constraintLeft_toLeftOf=\"parent\"\n        app:layout_constraintRight_toRightOf=\"parent\"\n        tools:ignore=\"SmallSp\" />\n\n    <View\n        android:layout_marginTop=\"12dp\"\n        app:layout_constraintTop_toBottomOf=\"@+id/tv_pub_date\"\n        android:layout_height=\"8dp\"\n        android:layout_width=\"match_parent\"\n        android:background=\"@color/bg_divider_line\" />\n\n\n</androidx.constraintlayout.widget.ConstraintLayout>"
  },
  {
    "path": "app/src/main/res/layout/item_rss_article_2.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:paddingLeft=\"4dp\"\n    android:paddingTop=\"8dp\"\n    android:paddingBottom=\"6dp\"\n    android:paddingRight=\"4dp\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\">\n\n    <ImageView\n        android:id=\"@+id/image_view\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"272dp\"\n        android:contentDescription=\"@string/loading\"\n        android:visibility=\"visible\"\n        android:scaleType=\"centerCrop\"\n        android:background=\"@drawable/bg_img_border\"\n        app:layout_constraintTop_toTopOf=\"parent\"\n        app:layout_constraintLeft_toLeftOf=\"parent\"\n        app:layout_constraintRight_toRightOf=\"parent\"\n        tools:ignore=\"RtlHardcoded,RtlSymmetry\" />\n\n    <TextView\n        android:id=\"@+id/tv_title\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"wrap_content\"\n        android:maxLines=\"2\"\n        android:ellipsize=\"end\"\n        android:text=\"@string/app_name\"\n        android:textColor=\"@color/primaryText\"\n        android:textSize=\"13sp\"\n        android:paddingTop=\"10dp\"\n        app:layout_constraintTop_toBottomOf=\"@+id/image_view\"\n        app:layout_constraintLeft_toLeftOf=\"parent\"\n        app:layout_constraintRight_toRightOf=\"parent\" />\n\n    <TextView\n        android:id=\"@+id/tv_pub_date\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"wrap_content\"\n        android:layout_gravity=\"center\"\n        android:text=\"@string/app_name\"\n        android:textSize=\"11sp\"\n        android:maxLines=\"1\"\n        android:paddingTop=\"8dp\"\n        android:paddingBottom=\"2dp\"\n        app:layout_constraintTop_toBottomOf=\"@+id/tv_title\"\n        app:layout_constraintLeft_toLeftOf=\"parent\"\n        app:layout_constraintRight_toRightOf=\"parent\"\n        tools:ignore=\"SmallSp\" />\n\n</androidx.constraintlayout.widget.ConstraintLayout>"
  },
  {
    "path": "app/src/main/res/layout/item_rss_read_record.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:padding=\"6dp\"\n    android:background=\"?android:attr/selectableItemBackground\"\n    android:orientation=\"vertical\">\n\n    <HorizontalScrollView\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:scrollbars=\"none\">\n        <TextView\n            android:id=\"@+id/text_title\"\n            android:text=\"@string/title\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:singleLine=\"true\"\n            android:ellipsize=\"none\" />\n    </HorizontalScrollView>\n\n    <HorizontalScrollView\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:scrollbars=\"none\">\n        <TextView\n            android:id=\"@+id/text_record\"\n            android:text=\"@string/read_record\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:autoLink=\"web\"\n            android:singleLine=\"true\"\n            android:ellipsize=\"none\" />\n    </HorizontalScrollView>\n\n</LinearLayout>"
  },
  {
    "path": "app/src/main/res/layout/item_rss_source.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:background=\"?android:attr/selectableItemBackground\"\n    android:gravity=\"center_vertical\"\n    android:orientation=\"horizontal\"\n    android:padding=\"16dp\">\n\n    <io.legado.app.lib.theme.view.ThemeCheckBox\n        android:id=\"@+id/cb_source\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"wrap_content\"\n        android:layout_gravity=\"center\"\n        android:layout_weight=\"1\"\n        android:singleLine=\"true\"\n        android:textColor=\"@color/primaryText\" />\n\n    <io.legado.app.lib.theme.view.ThemeSwitch\n        android:id=\"@+id/swt_enabled\"\n        android:name=\"@string/enable\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginLeft=\"6dp\"\n        android:layout_marginRight=\"6dp\"\n        tools:ignore=\"RtlSymmetry\" />\n\n    <androidx.appcompat.widget.AppCompatImageView\n        android:id=\"@+id/iv_edit\"\n        android:layout_width=\"36dp\"\n        android:layout_height=\"36dp\"\n        android:layout_gravity=\"center\"\n        android:background=\"?android:attr/selectableItemBackgroundBorderless\"\n        android:contentDescription=\"@string/edit\"\n        android:padding=\"6dp\"\n        android:src=\"@drawable/ic_edit\"\n        android:tint=\"@color/primaryText\" />\n\n    <androidx.appcompat.widget.AppCompatImageView\n        android:id=\"@+id/iv_menu_more\"\n        android:layout_width=\"36dp\"\n        android:layout_height=\"36dp\"\n        android:background=\"?attr/selectableItemBackgroundBorderless\"\n        android:contentDescription=\"@string/more_menu\"\n        android:padding=\"6dp\"\n        android:src=\"@drawable/ic_more_vert\"\n        android:tint=\"@color/primaryText\"\n        tools:ignore=\"RtlHardcoded\" />\n\n</LinearLayout>"
  },
  {
    "path": "app/src/main/res/layout/item_rule_sub.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:padding=\"12dp\">\n\n    <TextView\n        android:id=\"@+id/tv_name\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"wrap_content\"\n        android:padding=\"3dp\"\n        android:textSize=\"16sp\"\n        android:textColor=\"@color/primaryText\"\n        app:layout_constraintTop_toTopOf=\"parent\"\n        app:layout_constraintLeft_toLeftOf=\"parent\"\n        app:layout_constraintRight_toLeftOf=\"@id/iv_edit\"\n        tools:text=\"name\" />\n\n    <io.legado.app.ui.widget.text.AccentBgTextView\n        android:id=\"@+id/tv_type\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:padding=\"3dp\"\n        android:textSize=\"12sp\"\n        app:radius=\"2dp\"\n        app:layout_constraintTop_toBottomOf=\"@+id/tv_name\"\n        app:layout_constraintLeft_toLeftOf=\"parent\"\n        tools:text=\"type\" />\n\n    <TextView\n        android:id=\"@+id/tv_url\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"wrap_content\"\n        android:padding=\"3dp\"\n        android:textSize=\"12sp\"\n        android:textColor=\"@color/secondaryText\"\n        android:ellipsize=\"middle\"\n        android:singleLine=\"true\"\n        app:layout_constraintTop_toBottomOf=\"@+id/tv_name\"\n        app:layout_constraintLeft_toRightOf=\"@+id/tv_type\"\n        app:layout_constraintRight_toLeftOf=\"@+id/iv_edit\"\n        tools:text=\"url\" />\n\n    <androidx.appcompat.widget.AppCompatImageView\n        android:id=\"@+id/iv_edit\"\n        android:layout_width=\"36dp\"\n        android:layout_height=\"36dp\"\n        android:layout_gravity=\"center\"\n        android:background=\"?attr/selectableItemBackgroundBorderless\"\n        android:contentDescription=\"@string/edit\"\n        android:padding=\"6dp\"\n        android:src=\"@drawable/ic_edit\"\n        android:tint=\"@color/primaryText\"\n        app:layout_constraintRight_toLeftOf=\"@id/iv_menu_more\"\n        app:layout_constraintTop_toTopOf=\"parent\"\n        app:layout_constraintBottom_toBottomOf=\"parent\" />\n\n    <androidx.appcompat.widget.AppCompatImageView\n        android:id=\"@+id/iv_menu_more\"\n        android:layout_width=\"36dp\"\n        android:layout_height=\"36dp\"\n        android:background=\"?attr/selectableItemBackgroundBorderless\"\n        android:padding=\"6dp\"\n        android:src=\"@drawable/ic_more_vert\"\n        android:tint=\"@color/primaryText\"\n        android:contentDescription=\"@string/more_menu\"\n        app:layout_constraintRight_toRightOf=\"parent\"\n        app:layout_constraintTop_toTopOf=\"parent\"\n        app:layout_constraintBottom_toBottomOf=\"parent\"\n        tools:ignore=\"RtlHardcoded\" />\n\n</androidx.constraintlayout.widget.ConstraintLayout>"
  },
  {
    "path": "app/src/main/res/layout/item_search.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:scrollbars=\"none\">\n\n    <io.legado.app.ui.widget.image.CoverImageView\n        android:id=\"@+id/iv_cover\"\n        android:layout_width=\"80dp\"\n        android:layout_height=\"110dp\"\n        android:layout_margin=\"8dp\"\n        android:contentDescription=\"@string/img_cover\"\n        android:scaleType=\"centerCrop\"\n        android:scrollbars=\"none\"\n        android:src=\"@drawable/image_cover_default\"\n        android:transitionName=\"img_cover\"\n        app:layout_constraintBottom_toBottomOf=\"parent\"\n        app:layout_constraintLeft_toLeftOf=\"parent\"\n        app:layout_constraintTop_toTopOf=\"parent\"\n        tools:ignore=\"UnusedAttribute\" />\n\n    <io.legado.app.ui.widget.image.CircleImageView\n        android:id=\"@+id/iv_in_bookshelf\"\n        android:layout_width=\"8dp\"\n        android:layout_height=\"8dp\"\n        android:layout_margin=\"8dp\"\n        android:scaleType=\"centerCrop\"\n        android:scrollbars=\"none\"\n        android:src=\"@color/md_green_600\"\n        android:visibility=\"invisible\"\n        app:layout_constraintLeft_toRightOf=\"@id/iv_cover\"\n        app:layout_constraintTop_toTopOf=\"@id/tv_name\" />\n\n    <io.legado.app.ui.widget.text.BadgeView\n        android:id=\"@+id/bv_originCount\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_margin=\"8dp\"\n        android:scrollbars=\"none\"\n        app:layout_constraintRight_toRightOf=\"parent\"\n        app:layout_constraintTop_toTopOf=\"parent\" />\n\n    <TextView\n        android:id=\"@+id/tv_name\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"wrap_content\"\n        android:layout_margin=\"8dp\"\n        android:scrollbars=\"none\"\n        android:singleLine=\"true\"\n        android:text=\"@string/app_name\"\n        android:textColor=\"@color/primaryText\"\n        android:textSize=\"16sp\"\n        app:layout_constraintEnd_toStartOf=\"@id/bv_originCount\"\n        app:layout_constraintStart_toEndOf=\"@+id/iv_in_bookshelf\"\n        app:layout_constraintTop_toTopOf=\"parent\" />\n\n    <LinearLayout\n        android:layout_width=\"0dp\"\n        android:layout_height=\"0dp\"\n        android:layout_marginStart=\"8dp\"\n        android:layout_marginTop=\"3dp\"\n        android:orientation=\"vertical\"\n        android:scrollbars=\"none\"\n        app:layout_constraintBottom_toBottomOf=\"@id/iv_cover\"\n        app:layout_constraintLeft_toRightOf=\"@+id/iv_cover\"\n        app:layout_constraintRight_toRightOf=\"@id/tv_name\"\n        app:layout_constraintTop_toBottomOf=\"@+id/tv_name\">\n\n        <TextView\n            android:id=\"@+id/tv_author\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:ellipsize=\"end\"\n            android:lines=\"1\"\n            android:scrollbars=\"none\"\n            android:text=\"@string/author\"\n            android:textColor=\"@color/primaryText\"\n            android:textSize=\"12sp\" />\n\n        <io.legado.app.ui.widget.LabelsBar\n            android:id=\"@+id/ll_kind\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:orientation=\"horizontal\"\n            android:scrollbars=\"none\" />\n\n        <TextView\n            android:id=\"@+id/tv_lasted\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:ellipsize=\"end\"\n            android:lines=\"1\"\n            android:scrollbars=\"none\"\n            android:text=\"@string/last_read\"\n            android:textColor=\"@color/primaryText\"\n            android:textSize=\"12sp\" />\n\n        <io.legado.app.ui.widget.text.MultilineTextView\n            android:id=\"@+id/tv_introduce\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"match_parent\"\n            android:ellipsize=\"end\"\n            android:scrollbars=\"none\"\n            android:text=\"@string/book_intro\"\n            android:textColor=\"@color/primaryText\"\n            android:textSize=\"12sp\" />\n\n    </LinearLayout>\n\n\n</androidx.constraintlayout.widget.ConstraintLayout>"
  },
  {
    "path": "app/src/main/res/layout/item_search_list.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:orientation=\"vertical\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:background=\"?android:attr/selectableItemBackground\"\n    android:padding=\"12dp\">\n\n    <TextView\n        android:id=\"@+id/tv_search_result\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:singleLine=\"false\"\n        app:layout_constraintTop_toTopOf=\"parent\"\n        app:layout_constraintLeft_toLeftOf=\"parent\"\n        app:layout_constraintRight_toLeftOf=\"parent\" />\n\n</androidx.constraintlayout.widget.ConstraintLayout>"
  },
  {
    "path": "app/src/main/res/layout/item_server_select.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:background=\"@color/background\"\n    android:padding=\"8dp\"\n    android:gravity=\"center_vertical\"\n    android:orientation=\"horizontal\">\n\n    <io.legado.app.lib.theme.view.ThemeRadioButton\n        android:id=\"@+id/rb_server\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"wrap_content\"\n        android:layout_gravity=\"center\"\n        android:layout_weight=\"1\"\n        android:singleLine=\"true\"\n        android:textColor=\"@color/primaryText\" />\n\n\n    <androidx.appcompat.widget.AppCompatImageView\n        android:id=\"@+id/iv_edit\"\n        android:layout_width=\"36dp\"\n        android:layout_height=\"36dp\"\n        android:layout_gravity=\"center\"\n        android:background=\"?android:attr/selectableItemBackgroundBorderless\"\n        android:contentDescription=\"@string/edit\"\n        android:padding=\"6dp\"\n        android:src=\"@drawable/ic_edit\"\n        android:tint=\"@color/primaryText\" />\n\n    <androidx.appcompat.widget.AppCompatImageView\n        android:id=\"@+id/iv_delete\"\n        android:layout_width=\"36dp\"\n        android:layout_height=\"36dp\"\n        android:padding=\"6dp\"\n        android:background=\"?attr/selectableItemBackgroundBorderless\"\n        android:src=\"@drawable/ic_outline_delete\"\n        android:tint=\"@color/primaryText\"\n        android:contentDescription=\"@string/delete\"\n        tools:ignore=\"RtlHardcoded\" />\n\n</LinearLayout>"
  },
  {
    "path": "app/src/main/res/layout/item_source_edit.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<io.legado.app.ui.widget.text.TextInputLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:id=\"@+id/textInputLayout\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:paddingTop=\"3dp\">\n\n    <io.legado.app.ui.widget.code.CodeView\n        android:id=\"@+id/editText\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:inputType=\"textMultiLine\"\n        tools:ignore=\"SpeakableTextPresentCheck,TouchTargetSizeCheck\" />\n\n</io.legado.app.ui.widget.text.TextInputLayout>"
  },
  {
    "path": "app/src/main/res/layout/item_source_edit_check_box.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:id=\"@+id/root_view\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:orientation=\"vertical\"\n    android:padding=\"3dp\">\n\n    <io.legado.app.lib.theme.view.ThemeCheckBox\n        android:id=\"@+id/check_box\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:paddingVertical=\"12dp\"\n        tools:ignore=\"TouchTargetSizeCheck,VisualLintButtonSize\" />\n\n    <View\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"1dp\"\n        android:background=\"@color/secondaryText\"/>\n\n</LinearLayout>"
  },
  {
    "path": "app/src/main/res/layout/item_source_import.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:padding=\"8dp\">\n\n    <io.legado.app.lib.theme.view.ThemeCheckBox\n        android:id=\"@+id/cb_source_name\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:maxLines=\"1\"\n        android:singleLine=\"true\"\n        app:layout_constrainedWidth=\"true\"\n        app:layout_constraintBottom_toBottomOf=\"parent\"\n        app:layout_constraintHorizontal_bias=\"0\"\n        app:layout_constraintHorizontal_chainStyle=\"packed\"\n        app:layout_constraintLeft_toLeftOf=\"parent\"\n        app:layout_constraintRight_toLeftOf=\"@+id/tv_source_state\"\n        app:layout_constraintTop_toTopOf=\"parent\"\n        tools:ignore=\"TouchTargetSizeCheck\" />\n\n    <TextView\n        android:id=\"@+id/tv_source_state\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:padding=\"8dp\"\n        android:singleLine=\"true\"\n        android:textColor=\"@color/secondaryText\"\n        app:layout_constraintBottom_toBottomOf=\"parent\"\n        app:layout_constraintHorizontal_chainStyle=\"packed\"\n        app:layout_constraintLeft_toRightOf=\"@id/cb_source_name\"\n        app:layout_constraintRight_toLeftOf=\"@id/tv_open\"\n        app:layout_constraintTop_toTopOf=\"parent\" />\n\n    <TextView\n        android:id=\"@+id/tv_open\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginEnd=\"12dp\"\n        android:background=\"?attr/selectableItemBackground\"\n        android:padding=\"8dp\"\n        android:text=\"@string/open\"\n        android:textColor=\"@color/secondaryText\"\n        app:layout_constraintBottom_toBottomOf=\"parent\"\n        app:layout_constraintRight_toRightOf=\"parent\"\n        app:layout_constraintTop_toTopOf=\"parent\" />\n\n</androidx.constraintlayout.widget.ConstraintLayout>"
  },
  {
    "path": "app/src/main/res/layout/item_text.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<TextView xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:id=\"@+id/text_view\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"wrap_content\"\n    android:background=\"?attr/selectableItemBackground\"\n    android:ellipsize=\"end\"\n    android:gravity=\"center\"\n    android:padding=\"5dp\"\n    android:maxLines=\"1\"\n    android:scrollbars=\"none\"\n    android:textColor=\"@color/primaryText\"\n    android:textSize=\"14sp\"\n    tools:ignore=\"UnusedAttribute\" />\n"
  },
  {
    "path": "app/src/main/res/layout/item_theme_config.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:padding=\"8dp\"\n    android:gravity=\"center_vertical\"\n    android:orientation=\"horizontal\">\n\n    <TextView\n        android:id=\"@+id/tv_name\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"wrap_content\"\n        android:layout_weight=\"1\"\n        android:maxLines=\"1\"\n        android:textColor=\"@color/primaryText\"\n        tools:text=\"theme\" />\n\n    <androidx.appcompat.widget.AppCompatImageView\n        android:id=\"@+id/iv_share\"\n        android:layout_width=\"36dp\"\n        android:layout_height=\"36dp\"\n        android:layout_gravity=\"center\"\n        android:background=\"?android:attr/selectableItemBackgroundBorderless\"\n        android:contentDescription=\"@string/edit\"\n        android:padding=\"6dp\"\n        android:tooltipText=\"@string/edit\"\n        android:src=\"@drawable/ic_share\"\n        android:tint=\"@color/primaryText\"\n        tools:ignore=\"UnusedAttribute\" />\n\n    <androidx.appcompat.widget.AppCompatImageView\n        android:id=\"@+id/iv_delete\"\n        android:layout_width=\"36dp\"\n        android:layout_height=\"36dp\"\n        android:background=\"?attr/selectableItemBackgroundBorderless\"\n        android:contentDescription=\"@string/delete\"\n        android:tooltipText=\"@string/delete\"\n        android:padding=\"6dp\"\n        android:src=\"@drawable/ic_clear_all\"\n        android:tint=\"@color/primaryText\"\n        tools:ignore=\"UnusedAttribute\" />\n\n</LinearLayout>"
  },
  {
    "path": "app/src/main/res/layout/item_toc_regex.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:padding=\"16dp\">\n\n    <io.legado.app.lib.theme.view.ThemeRadioButton\n        android:id=\"@+id/rb_regex_name\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"wrap_content\"\n        android:layout_gravity=\"center\"\n        android:maxLines=\"1\"\n        app:layout_constraintBottom_toTopOf=\"@id/title_example\"\n        app:layout_constraintLeft_toLeftOf=\"parent\"\n        app:layout_constraintRight_toLeftOf=\"@id/swt_enabled\"\n        app:layout_constraintTop_toTopOf=\"parent\" />\n\n    <io.legado.app.lib.theme.view.ThemeSwitch\n        android:id=\"@+id/swt_enabled\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        app:layout_constraintBottom_toTopOf=\"@id/title_example\"\n        app:layout_constraintRight_toLeftOf=\"@id/iv_edit\"\n        app:layout_constraintTop_toTopOf=\"parent\" />\n\n    <androidx.appcompat.widget.AppCompatImageView\n        android:id=\"@+id/iv_edit\"\n        android:layout_width=\"36dp\"\n        android:layout_height=\"36dp\"\n        android:layout_gravity=\"center\"\n        android:background=\"?android:attr/selectableItemBackgroundBorderless\"\n        android:contentDescription=\"@string/edit\"\n        android:padding=\"6dp\"\n        android:src=\"@drawable/ic_edit\"\n        android:tint=\"@color/primaryText\"\n        android:tooltipText=\"@string/edit\"\n        app:layout_constraintBottom_toTopOf=\"@id/title_example\"\n        app:layout_constraintRight_toLeftOf=\"@id/iv_delete\"\n        app:layout_constraintTop_toTopOf=\"parent\"\n        tools:ignore=\"UnusedAttribute\" />\n\n    <androidx.appcompat.widget.AppCompatImageView\n        android:id=\"@+id/iv_delete\"\n        android:layout_width=\"36dp\"\n        android:layout_height=\"36dp\"\n        android:background=\"?attr/selectableItemBackgroundBorderless\"\n        android:contentDescription=\"@string/delete\"\n        android:padding=\"6dp\"\n        android:src=\"@drawable/ic_clear_all\"\n        android:tint=\"@color/primaryText\"\n        android:tooltipText=\"@string/delete\"\n        app:layout_constraintBottom_toTopOf=\"@id/title_example\"\n        app:layout_constraintRight_toRightOf=\"parent\"\n        app:layout_constraintTop_toTopOf=\"parent\"\n        tools:ignore=\"UnusedAttribute\" />\n\n    <TextView\n        android:id=\"@+id/title_example\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:textSize=\"12sp\"\n        app:layout_constraintBottom_toBottomOf=\"parent\"\n        app:layout_constraintLeft_toLeftOf=\"parent\" />\n\n</androidx.constraintlayout.widget.ConstraintLayout>"
  },
  {
    "path": "app/src/main/res/layout/item_txt_toc_rule.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:background=\"?android:attr/selectableItemBackground\"\n    android:padding=\"16dp\">\n\n    <io.legado.app.lib.theme.view.ThemeCheckBox\n        android:id=\"@+id/cb_source\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"wrap_content\"\n        android:layout_gravity=\"center\"\n        android:singleLine=\"true\"\n        android:textColor=\"@color/primaryText\"\n        app:layout_constraintBottom_toTopOf=\"@id/title_example\"\n        app:layout_constraintLeft_toLeftOf=\"parent\"\n        app:layout_constraintRight_toLeftOf=\"@id/swt_enabled\"\n        app:layout_constraintTop_toTopOf=\"parent\"\n        tools:ignore=\"TouchTargetSizeCheck,DuplicateSpeakableTextCheck\" />\n\n    <io.legado.app.lib.theme.view.ThemeSwitch\n        android:id=\"@+id/swt_enabled\"\n        android:name=\"@string/enable\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_marginLeft=\"6dp\"\n        android:layout_marginRight=\"6dp\"\n        app:layout_constraintBottom_toTopOf=\"@id/title_example\"\n        app:layout_constraintRight_toLeftOf=\"@id/iv_edit\"\n        app:layout_constraintTop_toTopOf=\"parent\"\n        tools:ignore=\"RtlSymmetry,TouchTargetSizeCheck\" />\n\n    <androidx.appcompat.widget.AppCompatImageView\n        android:id=\"@+id/iv_edit\"\n        android:layout_width=\"36dp\"\n        android:layout_height=\"36dp\"\n        android:layout_gravity=\"center\"\n        android:background=\"?android:attr/selectableItemBackgroundBorderless\"\n        android:contentDescription=\"@string/edit\"\n        android:padding=\"6dp\"\n        android:src=\"@drawable/ic_edit\"\n        android:tint=\"@color/primaryText\"\n        app:layout_constraintBottom_toTopOf=\"@id/title_example\"\n        app:layout_constraintRight_toLeftOf=\"@id/iv_menu_more\"\n        app:layout_constraintTop_toTopOf=\"parent\" />\n\n    <androidx.appcompat.widget.AppCompatImageView\n        android:id=\"@+id/iv_menu_more\"\n        android:layout_width=\"36dp\"\n        android:layout_height=\"36dp\"\n        android:background=\"?attr/selectableItemBackgroundBorderless\"\n        android:contentDescription=\"@string/more_menu\"\n        android:padding=\"6dp\"\n        android:src=\"@drawable/ic_more_vert\"\n        android:tint=\"@color/primaryText\"\n        app:layout_constraintBottom_toTopOf=\"@id/title_example\"\n        app:layout_constraintRight_toRightOf=\"parent\"\n        app:layout_constraintTop_toTopOf=\"parent\"\n        tools:ignore=\"RtlHardcoded\" />\n\n    <TextView\n        android:id=\"@+id/title_example\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:textSize=\"12sp\"\n        app:layout_constraintBottom_toBottomOf=\"parent\"\n        app:layout_constraintLeft_toLeftOf=\"parent\" />\n\n</androidx.constraintlayout.widget.ConstraintLayout>"
  },
  {
    "path": "app/src/main/res/layout/popup_action.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"wrap_content\"\n    android:background=\"@drawable/shape_card_view\"\n    android:orientation=\"vertical\"\n    android:padding=\"5dp\">\n\n    <androidx.recyclerview.widget.RecyclerView\n        android:id=\"@+id/recycler_view\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_gravity=\"center\"\n        android:orientation=\"horizontal\"\n        app:flexDirection=\"row\"\n        app:flexWrap=\"wrap\"\n        app:layoutManager=\"com.google.android.flexbox.FlexboxLayoutManager\" />\n\n</LinearLayout>\n\n"
  },
  {
    "path": "app/src/main/res/layout/popup_action_menu.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"wrap_content\"\n    android:background=\"@drawable/shape_card_view\"\n    android:orientation=\"vertical\"\n    android:padding=\"5dp\">\n\n    <LinearLayout\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:gravity=\"center_vertical\"\n        android:orientation=\"horizontal\">\n\n        <androidx.recyclerview.widget.RecyclerView\n            android:id=\"@+id/recycler_view\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_gravity=\"center\"\n            android:orientation=\"horizontal\"\n            app:flexDirection=\"row\"\n            app:flexWrap=\"wrap\"\n            app:layoutManager=\"com.google.android.flexbox.FlexboxLayoutManager\" />\n\n        <androidx.appcompat.widget.AppCompatImageView\n            android:id=\"@+id/iv_menu_more\"\n            android:layout_width=\"24dp\"\n            android:layout_height=\"24dp\"\n            android:background=\"?attr/selectableItemBackgroundBorderless\"\n            android:contentDescription=\"@string/more_menu\"\n            android:src=\"@drawable/ic_more_vert\"\n            android:tint=\"@color/primaryText\"\n            android:visibility=\"gone\" />\n\n    </LinearLayout>\n\n    <androidx.recyclerview.widget.RecyclerView\n        android:id=\"@+id/recycler_view_more\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_gravity=\"center\"\n        android:orientation=\"vertical\"\n        android:visibility=\"gone\"\n        app:layoutManager=\"androidx.recyclerview.widget.LinearLayoutManager\" />\n\n</LinearLayout>\n\n"
  },
  {
    "path": "app/src/main/res/layout/popup_keyboard_tool.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.recyclerview.widget.RecyclerView xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:id=\"@+id/recycler_view\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:layout_gravity=\"center\"\n    android:background=\"@color/background_card\"\n    android:orientation=\"horizontal\"\n    android:padding=\"5dp\"\n    app:layoutManager=\"androidx.recyclerview.widget.LinearLayoutManager\" />\n"
  },
  {
    "path": "app/src/main/res/layout/popup_seek_bar.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:background=\"@drawable/shape_card_view\"\n    android:padding=\"8dp\"\n    android:orientation=\"vertical\"\n    android:gravity=\"center_horizontal\">\n\n    <TextView\n        android:id=\"@+id/tv_seek_value\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:textColor=\"@color/secondaryText\" />\n\n    <io.legado.app.lib.theme.view.ThemeSeekBar\n        android:id=\"@+id/seek_bar\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\" />\n\n</LinearLayout>"
  },
  {
    "path": "app/src/main/res/layout/view_action_button.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<ImageButton xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:id=\"@+id/item\"\n    style=\"?android:attr/actionButtonStyle\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"wrap_content\"\n    tools:ignore=\"ContentDescription\" />"
  },
  {
    "path": "app/src/main/res/layout/view_book_page.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:id=\"@+id/vw_root\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:background=\"@color/background\"\n    android:orientation=\"vertical\">\n\n    <FrameLayout\n        android:id=\"@+id/vw_status_bar\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        app:layout_constraintTop_toTopOf=\"parent\" />\n\n    <androidx.constraintlayout.widget.ConstraintLayout\n        android:id=\"@+id/ll_header\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:gravity=\"center_vertical\"\n        app:layout_constraintTop_toBottomOf=\"@id/vw_status_bar\">\n\n        <io.legado.app.ui.widget.BatteryView\n            android:id=\"@+id/tv_header_left\"\n            android:layout_width=\"0dp\"\n            android:layout_height=\"wrap_content\"\n            android:ellipsize=\"end\"\n            android:singleLine=\"true\"\n            android:textSize=\"12sp\"\n            app:layout_constraintBottom_toBottomOf=\"parent\"\n            app:layout_constraintHorizontal_weight=\"1\"\n            app:layout_constraintLeft_toLeftOf=\"parent\"\n            app:layout_constraintRight_toLeftOf=\"@+id/tv_header_right\"\n            app:layout_constraintTop_toTopOf=\"parent\" />\n\n        <io.legado.app.ui.widget.BatteryView\n            android:id=\"@+id/tv_header_middle\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:ellipsize=\"end\"\n            android:singleLine=\"true\"\n            android:textSize=\"12sp\"\n            android:visibility=\"gone\"\n            app:layout_constraintBottom_toBottomOf=\"parent\"\n            app:layout_constraintLeft_toLeftOf=\"parent\"\n            app:layout_constraintRight_toRightOf=\"parent\"\n            app:layout_constraintTop_toTopOf=\"parent\" />\n\n        <io.legado.app.ui.widget.BatteryView\n            android:id=\"@+id/tv_header_right\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginLeft=\"3dp\"\n            android:ellipsize=\"end\"\n            android:singleLine=\"true\"\n            android:textSize=\"12sp\"\n            app:layout_constraintBottom_toBottomOf=\"parent\"\n            app:layout_constraintRight_toRightOf=\"parent\"\n            app:layout_constraintTop_toTopOf=\"parent\"\n            tools:ignore=\"RtlHardcoded,RtlSymmetry\" />\n\n    </androidx.constraintlayout.widget.ConstraintLayout>\n\n    <View\n        android:id=\"@+id/vw_top_divider\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"0.5dp\"\n        android:background=\"@color/divider\"\n        android:visibility=\"invisible\"\n        app:layout_constraintTop_toBottomOf=\"@id/ll_header\" />\n\n    <io.legado.app.ui.book.read.page.ContentTextView\n        android:id=\"@+id/content_text_view\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"0dp\"\n        android:layout_weight=\"1\"\n        app:layout_constraintBottom_toTopOf=\"@id/vw_bottom_divider\"\n        app:layout_constraintTop_toBottomOf=\"@id/vw_top_divider\" />\n\n    <View\n        android:id=\"@+id/vw_bottom_divider\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"0.5dp\"\n        android:background=\"@color/divider\"\n        android:visibility=\"invisible\"\n        app:layout_constraintBottom_toTopOf=\"@id/ll_footer\" />\n\n    <androidx.constraintlayout.widget.ConstraintLayout\n        android:id=\"@+id/ll_footer\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:gravity=\"center_vertical\"\n        app:layout_constraintBottom_toTopOf=\"@id/vw_navigation_bar\">\n\n        <io.legado.app.ui.widget.BatteryView\n            android:id=\"@+id/tv_footer_left\"\n            android:layout_width=\"0dp\"\n            android:layout_height=\"wrap_content\"\n            android:ellipsize=\"end\"\n            android:singleLine=\"true\"\n            android:textSize=\"12sp\"\n            app:layout_constraintBottom_toBottomOf=\"parent\"\n            app:layout_constraintHorizontal_weight=\"1\"\n            app:layout_constraintLeft_toLeftOf=\"parent\"\n            app:layout_constraintRight_toLeftOf=\"@+id/tv_footer_right\"\n            app:layout_constraintTop_toTopOf=\"parent\" />\n\n        <io.legado.app.ui.widget.BatteryView\n            android:id=\"@+id/tv_footer_middle\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:ellipsize=\"end\"\n            android:singleLine=\"true\"\n            android:textSize=\"12sp\"\n            android:visibility=\"gone\"\n            app:layout_constraintBottom_toBottomOf=\"parent\"\n            app:layout_constraintLeft_toLeftOf=\"parent\"\n            app:layout_constraintRight_toRightOf=\"parent\"\n            app:layout_constraintTop_toTopOf=\"parent\" />\n\n        <io.legado.app.ui.widget.BatteryView\n            android:id=\"@+id/tv_footer_right\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginLeft=\"3dp\"\n            android:ellipsize=\"end\"\n            android:singleLine=\"true\"\n            android:textSize=\"12sp\"\n            app:layout_constraintBottom_toBottomOf=\"parent\"\n            app:layout_constraintRight_toRightOf=\"parent\"\n            app:layout_constraintTop_toTopOf=\"parent\"\n            tools:ignore=\"RtlHardcoded,RtlSymmetry,SmallSp\" />\n\n    </androidx.constraintlayout.widget.ConstraintLayout>\n\n    <FrameLayout\n        android:id=\"@+id/vw_navigation_bar\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        app:layout_constraintBottom_toBottomOf=\"parent\" />\n\n</androidx.constraintlayout.widget.ConstraintLayout>\n"
  },
  {
    "path": "app/src/main/res/layout/view_detail_seek_bar.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:orientation=\"horizontal\"\n    android:gravity=\"center\">\n\n    <TextView\n        android:id=\"@+id/tv_seek_title\"\n        android:layout_width=\"60dp\"\n        android:layout_height=\"wrap_content\"\n        android:singleLine=\"true\"\n        android:text=\"seek\"\n        android:textColor=\"@color/primaryText\"\n        tools:ignore=\"HardcodedText\" />\n\n    <androidx.appcompat.widget.AppCompatImageView\n        android:id=\"@+id/iv_seek_reduce\"\n        android:layout_width=\"24dp\"\n        android:layout_height=\"24dp\"\n        android:contentDescription=\"@string/reduce\"\n        android:src=\"@drawable/ic_reduce\"\n        android:background=\"?android:attr/selectableItemBackgroundBorderless\"\n        android:tint=\"@color/primaryText\" />\n\n    <io.legado.app.lib.theme.view.ThemeSeekBar\n        android:id=\"@+id/seek_bar\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"25dp\"\n        android:layout_gravity=\"center_vertical\"\n        android:layout_weight=\"4\"\n        android:max=\"10\" />\n\n    <androidx.appcompat.widget.AppCompatImageView\n        android:id=\"@+id/iv_seek_plus\"\n        android:layout_width=\"24dp\"\n        android:layout_height=\"24dp\"\n        android:contentDescription=\"@string/plus\"\n        android:src=\"@drawable/ic_add\"\n        android:background=\"?android:attr/selectableItemBackgroundBorderless\"\n        android:tint=\"@color/primaryText\" />\n\n    <TextView\n        android:id=\"@+id/tv_seek_value\"\n        android:layout_width=\"60dp\"\n        android:layout_height=\"wrap_content\"\n        android:gravity=\"right\"\n        android:singleLine=\"true\"\n        android:text=\"0\"\n        android:textColor=\"@color/primaryText\"\n        tools:ignore=\"HardcodedText,RtlHardcoded\" />\n\n</LinearLayout>"
  },
  {
    "path": "app/src/main/res/layout/view_dynamic.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<merge xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\">\n\n    <ViewStub\n        android:id=\"@+id/error_view_stub\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_gravity=\"center\"\n        android:layout=\"@layout/view_error\" />\n\n    <ViewStub\n        android:id=\"@+id/progress_view_stub\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_gravity=\"center\"\n        android:layout=\"@layout/view_loading\" />\n\n</merge>"
  },
  {
    "path": "app/src/main/res/layout/view_error.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"wrap_content\"\n    android:gravity=\"center_horizontal\"\n    android:orientation=\"vertical\">\n\n    <androidx.appcompat.widget.AppCompatImageView\n        android:id=\"@+id/iv_error_image\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\" />\n\n    <androidx.appcompat.widget.AppCompatTextView\n        android:id=\"@+id/tv_error_message\"\n        style=\"@style/Style.Text.Primary.Normal\"\n        android:paddingTop=\"16dp\"\n        android:paddingBottom=\"16dp\" />\n\n    <androidx.appcompat.widget.AppCompatButton\n        android:id=\"@+id/btn_error_retry\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:backgroundTint=\"@color/accent\"\n        android:minWidth=\"96dp\"\n        android:text=\"@string/dynamic_click_retry\" />\n\n</LinearLayout>"
  },
  {
    "path": "app/src/main/res/layout/view_fastscroller.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n\n<merge xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\">\n\n    <TextView\n        android:id=\"@+id/fastscroll_bubble\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_gravity=\"end\"\n        android:gravity=\"center\"\n        android:maxLines=\"1\"\n        android:textSize=\"@dimen/fastscroll_bubble_textsize\"\n        android:visibility=\"gone\"\n        tools:background=\"@drawable/fastscroll_bubble\"\n        tools:text=\"A\"\n        tools:textColor=\"#ffffff\"\n        tools:visibility=\"visible\" />\n\n    <FrameLayout\n        android:id=\"@+id/fastscroll_scrollbar\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"match_parent\"\n        android:visibility=\"gone\"\n        tools:visibility=\"visible\">\n\n        <ImageView\n            android:id=\"@+id/fastscroll_track\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"match_parent\"\n            android:layout_gravity=\"center_horizontal\"\n            tools:ignore=\"ContentDescription\"\n            tools:src=\"@drawable/fastscroll_track\" />\n\n        <ImageView\n            android:id=\"@+id/fastscroll_handle\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:layout_gravity=\"center_horizontal\"\n            android:paddingEnd=\"@dimen/fastscroll_scrollbar_padding_end\"\n            android:paddingStart=\"@dimen/fastscroll_scrollbar_padding_start\"\n            tools:ignore=\"ContentDescription\"\n            tools:src=\"@drawable/fastscroll_handle\" />\n\n    </FrameLayout>\n\n</merge>"
  },
  {
    "path": "app/src/main/res/layout/view_icon.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<ImageView android:id=\"@+id/preview\"\n    xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"48dp\"\n    android:layout_height=\"48dp\" />"
  },
  {
    "path": "app/src/main/res/layout/view_load_more.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<merge xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\">\n\n    <io.legado.app.ui.widget.anima.RotateLoading\n        android:id=\"@+id/rotate_loading\"\n        android:layout_width=\"36dp\"\n        android:layout_height=\"36dp\"\n        android:layout_margin=\"6dp\"\n        android:layout_gravity=\"center\"\n        android:visibility=\"invisible\"\n        app:loading_width=\"2dp\" />\n\n    <TextView\n        android:id=\"@+id/tv_text\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:padding=\"10dp\"\n        android:layout_gravity=\"center\"\n        android:gravity=\"center\"\n        android:textColor=\"?android:attr/textColorSecondary\"\n        android:textSize=\"14sp\"\n        android:background=\"?attr/selectableItemBackground\"\n        android:visibility=\"invisible\"\n        android:singleLine=\"true\"\n        tools:text=\"加载状态\"\n        tools:visibility=\"visible\" />\n\n</merge>"
  },
  {
    "path": "app/src/main/res/layout/view_loading.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    android:layout_width=\"wrap_content\"\n    android:layout_height=\"wrap_content\"\n    android:gravity=\"center_horizontal\"\n    android:orientation=\"vertical\">\n\n    <androidx.core.widget.ContentLoadingProgressBar\n        android:id=\"@+id/loading_progress\"\n        android:layout_width=\"48dp\"\n        android:layout_height=\"48dp\"\n        style=\"@style/Widget.AppCompat.ProgressBar\" />\n\n    <androidx.appcompat.widget.AppCompatTextView\n        android:id=\"@+id/tv_loading_message\"\n        style=\"@style/Style.Text.Second.Normal.Wrap\"\n        android:paddingTop=\"16dp\"\n        android:text=\"@string/dynamic_loading\" />\n</LinearLayout>"
  },
  {
    "path": "app/src/main/res/layout/view_manga_menu.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:id=\"@+id/vw_menu_root\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:orientation=\"vertical\">\n\n    <View\n        android:id=\"@+id/vw_menu_bg\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:contentDescription=\"@string/content\"\n        tools:layout_editor_absoluteX=\"0dp\"\n        tools:layout_editor_absoluteY=\"0dp\" />\n\n    <io.legado.app.ui.widget.TitleBar\n        android:id=\"@+id/title_bar\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:theme=\"?attr/actionBarStyle\"\n        app:layout_constraintTop_toTopOf=\"parent\">\n\n        <androidx.constraintlayout.widget.ConstraintLayout\n            android:id=\"@+id/title_bar_addition\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\">\n\n            <TextView\n                android:id=\"@+id/tv_chapter_name\"\n                android:layout_width=\"0dp\"\n                android:layout_height=\"wrap_content\"\n                android:paddingLeft=\"10dp\"\n                android:paddingRight=\"10dp\"\n                android:singleLine=\"true\"\n                android:visibility=\"gone\"\n                app:layout_constraintLeft_toLeftOf=\"parent\"\n                app:layout_constraintRight_toLeftOf=\"@+id/tv_source_action\"\n                app:layout_constraintTop_toTopOf=\"parent\"\n                tools:visibility=\"visible\" />\n\n            <TextView\n                android:id=\"@+id/tv_chapter_url\"\n                android:layout_width=\"0dp\"\n                android:layout_height=\"wrap_content\"\n                android:paddingLeft=\"10dp\"\n                android:paddingRight=\"10dp\"\n                android:singleLine=\"true\"\n                android:visibility=\"gone\"\n                app:layout_constraintLeft_toLeftOf=\"parent\"\n                app:layout_constraintRight_toLeftOf=\"@+id/tv_source_action\"\n                app:layout_constraintTop_toBottomOf=\"@+id/tv_chapter_name\"\n                tools:visibility=\"visible\" />\n\n            <io.legado.app.ui.widget.text.AccentBgTextView\n                android:id=\"@+id/tv_source_action\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"0dp\"\n                android:layout_margin=\"1dp\"\n                android:gravity=\"center\"\n                android:maxWidth=\"120dp\"\n                android:paddingLeft=\"6dp\"\n                android:paddingRight=\"6dp\"\n                android:text=\"@string/book_source\"\n                app:layout_constraintBottom_toBottomOf=\"@+id/tv_chapter_url\"\n                app:layout_constraintRight_toRightOf=\"parent\"\n                app:layout_constraintTop_toTopOf=\"parent\"\n                app:radius=\"2dp\"\n                tools:visibility=\"visible\" />\n\n        </androidx.constraintlayout.widget.ConstraintLayout>\n\n    </io.legado.app.ui.widget.TitleBar>\n\n    <LinearLayout\n        android:id=\"@+id/bottom_menu\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:background=\"@color/background_menu\"\n        android:importantForAccessibility=\"no\"\n        android:orientation=\"vertical\"\n        app:layout_constraintBottom_toBottomOf=\"parent\">\n\n        <LinearLayout\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:layout_marginLeft=\"20dp\"\n            android:layout_marginTop=\"10dp\"\n            android:layout_marginRight=\"20dp\"\n            android:layout_marginBottom=\"10dp\"\n            android:importantForAccessibility=\"no\"\n            android:orientation=\"horizontal\">\n\n            <TextView\n                android:id=\"@+id/tv_pre\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginStart=\"10dp\"\n                android:layout_marginEnd=\"10dp\"\n                android:background=\"?android:attr/selectableItemBackgroundBorderless\"\n                android:paddingTop=\"10dp\"\n                android:paddingBottom=\"10dp\"\n                android:text=\"@string/previous_chapter\"\n                android:textColor=\"@color/primaryText\"\n                android:textSize=\"14sp\"\n                tools:ignore=\"TouchTargetSizeCheck\" />\n\n            <io.legado.app.lib.theme.view.ThemeSeekBar\n                android:id=\"@+id/seek_read_page\"\n                android:layout_width=\"0dp\"\n                android:layout_height=\"25dp\"\n                android:layout_gravity=\"center_vertical\"\n                android:layout_weight=\"1\" />\n\n            <TextView\n                android:id=\"@+id/tv_next\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginStart=\"10dp\"\n                android:layout_marginEnd=\"10dp\"\n                android:background=\"?android:attr/selectableItemBackgroundBorderless\"\n                android:paddingTop=\"10dp\"\n                android:paddingBottom=\"10dp\"\n                android:text=\"@string/next_chapter\"\n                android:textColor=\"@color/primaryText\"\n                android:textSize=\"14sp\"\n                tools:ignore=\"TouchTargetSizeCheck\" />\n        </LinearLayout>\n    </LinearLayout>\n\n</androidx.constraintlayout.widget.ConstraintLayout>"
  },
  {
    "path": "app/src/main/res/layout/view_navigation_badge.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<FrameLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\">\n\n    <io.legado.app.ui.widget.text.BadgeView\n        android:id=\"@+id/view_badge\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"18dp\"\n        android:layout_marginLeft=\"12dp\"\n        android:layout_marginTop=\"2dp\"\n        android:layout_gravity=\"center_horizontal\"\n        tools:ignore=\"RtlHardcoded\" />\n\n</FrameLayout>"
  },
  {
    "path": "app/src/main/res/layout/view_preference.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\r\n    xmlns:tools=\"http://schemas.android.com/tools\"\r\n    android:background=\"@drawable/bg_prefs_color\"\r\n    android:layout_width=\"match_parent\"\r\n    android:layout_height=\"wrap_content\"\r\n    android:paddingLeft=\"16dp\"\r\n    android:paddingTop=\"10dp\"\r\n    android:paddingRight=\"16dp\"\r\n    android:paddingBottom=\"10dp\"\r\n    android:minHeight=\"60dp\"\r\n    android:clickable=\"true\"\r\n    android:orientation=\"horizontal\"\r\n    android:gravity=\"center_vertical\"\r\n    android:focusable=\"true\">\r\n\r\n    <ImageView\r\n        android:id=\"@+id/preference_icon\"\r\n        android:layout_marginRight=\"16dp\"\r\n        android:scaleType=\"fitCenter\"\r\n        android:visibility=\"visible\"\r\n        android:paddingTop=\"1dp\"\r\n        android:layout_width=\"24dp\"\r\n        android:layout_height=\"24dp\"\r\n        tools:ignore=\"ContentDescription,RtlHardcoded\" />\r\n\r\n    <LinearLayout\r\n        android:orientation=\"vertical\"\r\n        android:layout_weight=\"1.0\"\r\n        android:layout_width=\"0dp\"\r\n        android:layout_height=\"wrap_content\">\r\n\r\n        <TextView\r\n            android:id=\"@+id/preference_title\"\r\n            android:singleLine=\"true\"\r\n            android:textSize=\"16sp\"\r\n            android:text=\"@string/title\"\r\n            android:textColor=\"@color/primaryText\"\r\n            android:layout_width=\"match_parent\"\r\n            android:layout_height=\"wrap_content\" />\r\n\r\n        <TextView\r\n            android:id=\"@+id/preference_desc\"\r\n            android:textSize=\"14sp\"\r\n            android:textColor=\"@color/tv_text_summary\"\r\n            android:text=\"@string/default1\"\r\n            android:layout_width=\"match_parent\"\r\n            android:layout_height=\"wrap_content\"\r\n            android:layout_marginTop=\"8dp\" />\r\n\r\n    </LinearLayout>\r\n\r\n    <FrameLayout\r\n        android:id=\"@+id/preference_widget\"\r\n        android:orientation=\"vertical\"\r\n        android:layout_gravity=\"center\"\r\n        android:layout_marginLeft=\"8dp\"\r\n        android:layout_marginTop=\"8dp\"\r\n        android:layout_marginBottom=\"8dp\"\r\n        android:visibility=\"gone\"\r\n        android:gravity=\"right|center_vertical\"\r\n        android:layout_width=\"wrap_content\"\r\n        android:layout_height=\"wrap_content\"\r\n        tools:ignore=\"RtlHardcoded\" />\r\n\r\n</LinearLayout>"
  },
  {
    "path": "app/src/main/res/layout/view_preference_category.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\r\n<LinearLayout\r\n    xmlns:android=\"http://schemas.android.com/apk/res/android\"\r\n    xmlns:tools=\"http://schemas.android.com/tools\"\r\n    android:layout_width=\"match_parent\"\r\n    android:layout_height=\"wrap_content\"\r\n    android:orientation=\"vertical\" >\r\n\r\n    <View\r\n        android:id=\"@+id/preference_divider_above\"\r\n        android:layout_width=\"match_parent\"\r\n        android:layout_height=\"8dp\" />\r\n\r\n    <TextView\r\n        android:id=\"@+id/preference_title\"\r\n        android:paddingTop=\"16dp\"\r\n        android:paddingBottom=\"8dp\"\r\n        android:background=\"@drawable/bg_prefs_color\"\r\n        android:paddingLeft=\"16dp\"\r\n        android:layout_width=\"match_parent\"\r\n        android:layout_height=\"wrap_content\"\r\n        android:textColor=\"@color/accent\"\r\n        tools:ignore=\"RtlHardcoded,RtlSymmetry\" />\r\n\r\n    <View\r\n        android:id=\"@+id/preference_divider_below\"\r\n        android:layout_width=\"match_parent\"\r\n        android:visibility=\"gone\"\r\n        android:layout_height=\"8dp\" />\r\n\r\n</LinearLayout>"
  },
  {
    "path": "app/src/main/res/layout/view_read_menu.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:id=\"@+id/vw_menu_root\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:orientation=\"vertical\">\n\n    <View\n        android:id=\"@+id/vw_menu_bg\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:contentDescription=\"@string/content\"\n        tools:layout_editor_absoluteX=\"0dp\"\n        tools:layout_editor_absoluteY=\"0dp\" />\n\n    <io.legado.app.ui.widget.TitleBar\n        android:id=\"@+id/title_bar\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:theme=\"?attr/actionBarStyle\"\n        app:layout_constraintTop_toTopOf=\"parent\">\n\n        <androidx.constraintlayout.widget.ConstraintLayout\n            android:id=\"@+id/title_bar_addition\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\">\n\n            <TextView\n                android:id=\"@+id/tv_chapter_name\"\n                android:layout_width=\"0dp\"\n                android:layout_height=\"wrap_content\"\n                android:paddingLeft=\"10dp\"\n                android:paddingRight=\"10dp\"\n                android:singleLine=\"true\"\n                android:visibility=\"gone\"\n                app:layout_constraintLeft_toLeftOf=\"parent\"\n                app:layout_constraintRight_toLeftOf=\"@+id/tv_source_action\"\n                app:layout_constraintTop_toTopOf=\"parent\" />\n\n            <TextView\n                android:id=\"@+id/tv_chapter_url\"\n                android:layout_width=\"0dp\"\n                android:layout_height=\"wrap_content\"\n                android:paddingLeft=\"10dp\"\n                android:paddingRight=\"10dp\"\n                android:singleLine=\"true\"\n                android:visibility=\"gone\"\n                app:layout_constraintLeft_toLeftOf=\"parent\"\n                app:layout_constraintRight_toLeftOf=\"@+id/tv_source_action\"\n                app:layout_constraintTop_toBottomOf=\"@+id/tv_chapter_name\" />\n\n            <io.legado.app.ui.widget.text.AccentBgTextView\n                android:id=\"@+id/tv_source_action\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"0dp\"\n                android:layout_margin=\"1dp\"\n                android:gravity=\"center\"\n                android:maxWidth=\"120dp\"\n                android:paddingLeft=\"6dp\"\n                android:paddingRight=\"6dp\"\n                android:text=\"@string/book_source\"\n                app:layout_constraintBottom_toBottomOf=\"@+id/tv_chapter_url\"\n                app:layout_constraintRight_toRightOf=\"parent\"\n                app:layout_constraintTop_toTopOf=\"parent\"\n                app:radius=\"2dp\" />\n\n        </androidx.constraintlayout.widget.ConstraintLayout>\n\n    </io.legado.app.ui.widget.TitleBar>\n\n    <LinearLayout\n        android:id=\"@+id/ll_brightness\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"0dp\"\n        android:layout_marginHorizontal=\"16dp\"\n        android:layout_marginTop=\"16dp\"\n        android:background=\"@color/transparent30\"\n        android:gravity=\"center_horizontal\"\n        android:orientation=\"vertical\"\n        app:layout_constraintBottom_toTopOf=\"@+id/bottom_menu\"\n        app:layout_constraintLeft_toLeftOf=\"parent\"\n        app:layout_constraintTop_toBottomOf=\"@+id/title_bar\"\n        tools:ignore=\"RtlHardcoded\">\n\n        <ImageView\n            android:id=\"@+id/iv_brightness_auto\"\n            android:layout_width=\"24dp\"\n            android:layout_height=\"24dp\"\n            android:layout_margin=\"8dp\"\n            android:contentDescription=\"@string/brightness_auto\"\n            android:src=\"@drawable/ic_brightness_auto\"\n            tools:ignore=\"ImageContrastCheck\" />\n\n        <io.legado.app.ui.widget.seekbar.VerticalSeekBarWrapper\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"0dp\"\n            android:layout_weight=\"1\">\n\n            <io.legado.app.ui.widget.seekbar.VerticalSeekBar\n                android:id=\"@+id/seek_brightness\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"match_parent\"\n                android:max=\"255\"\n                app:seekBarRotation=\"CW270\" />\n\n        </io.legado.app.ui.widget.seekbar.VerticalSeekBarWrapper>\n\n        <androidx.appcompat.widget.AppCompatImageView\n            android:id=\"@+id/vw_brightness_pos_adjust\"\n            android:layout_width=\"24dp\"\n            android:layout_height=\"24dp\"\n            android:layout_margin=\"8dp\"\n            android:contentDescription=\"@string/adjust_pos\"\n            android:src=\"@drawable/ic_swap_horiz\"\n            app:tint=\"@color/white\" />\n\n    </LinearLayout>\n\n    <LinearLayout\n        android:id=\"@+id/bottom_menu\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:orientation=\"vertical\"\n        app:layout_constraintBottom_toBottomOf=\"parent\">\n\n        <LinearLayout\n            android:id=\"@+id/ll_floating_button\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:gravity=\"center_vertical\"\n            android:orientation=\"horizontal\"\n            android:paddingStart=\"16dp\"\n            android:paddingEnd=\"16dp\">\n\n            <com.google.android.material.floatingactionbutton.FloatingActionButton\n                android:id=\"@+id/fabSearch\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:layout_margin=\"16dp\"\n                android:contentDescription=\"@string/search_content\"\n                android:src=\"@drawable/ic_search\"\n                android:tint=\"@color/primaryText\"\n                android:tooltipText=\"@string/search_content\"\n                app:backgroundTint=\"@color/background_menu\"\n                app:elevation=\"2dp\"\n                app:fabSize=\"mini\"\n                app:pressedTranslationZ=\"2dp\"\n                tools:ignore=\"UnusedAttribute\" />\n\n            <Space\n                android:layout_width=\"0dp\"\n                android:layout_height=\"wrap_content\"\n                android:layout_weight=\"1\" />\n\n            <com.google.android.material.floatingactionbutton.FloatingActionButton\n                android:id=\"@+id/fabAutoPage\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:layout_margin=\"16dp\"\n                android:contentDescription=\"@string/auto_next_page\"\n                android:src=\"@drawable/ic_auto_page\"\n                android:tint=\"@color/primaryText\"\n                android:tooltipText=\"@string/auto_next_page\"\n                app:backgroundTint=\"@color/background_menu\"\n                app:elevation=\"2dp\"\n                app:fabSize=\"mini\"\n                app:pressedTranslationZ=\"2dp\"\n                tools:ignore=\"UnusedAttribute\" />\n\n            <Space\n                android:layout_width=\"0dp\"\n                android:layout_height=\"wrap_content\"\n                android:layout_weight=\"1\" />\n\n            <com.google.android.material.floatingactionbutton.FloatingActionButton\n                android:id=\"@+id/fabReplaceRule\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:layout_margin=\"16dp\"\n                android:contentDescription=\"@string/replace_rule_title\"\n                android:src=\"@drawable/ic_find_replace\"\n                android:tint=\"@color/primaryText\"\n                android:tooltipText=\"@string/replace_rule_title\"\n                app:backgroundTint=\"@color/background_menu\"\n                app:elevation=\"2dp\"\n                app:fabSize=\"mini\"\n                app:pressedTranslationZ=\"2dp\"\n                tools:ignore=\"UnusedAttribute\" />\n\n            <Space\n                android:layout_width=\"0dp\"\n                android:layout_height=\"wrap_content\"\n                android:layout_weight=\"1\" />\n\n            <com.google.android.material.floatingactionbutton.FloatingActionButton\n                android:id=\"@+id/fabNightTheme\"\n                android:layout_width=\"wrap_content\"\n                android:layout_height=\"wrap_content\"\n                android:layout_margin=\"16dp\"\n                android:contentDescription=\"@string/dark_theme\"\n                android:src=\"@drawable/ic_brightness\"\n                android:tint=\"@color/primaryText\"\n                android:tooltipText=\"@string/dark_theme\"\n                app:backgroundTint=\"@color/background_menu\"\n                app:elevation=\"2dp\"\n                app:fabSize=\"mini\"\n                app:pressedTranslationZ=\"2dp\"\n                tools:ignore=\"UnusedAttribute\" />\n\n        </LinearLayout>\n\n        <!--底部设置栏-->\n        <LinearLayout\n            android:id=\"@+id/ll_bottom_bg\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:background=\"@color/background_menu\"\n            android:importantForAccessibility=\"no\"\n            android:orientation=\"vertical\">\n            <!--章节设置-->\n            <LinearLayout\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginLeft=\"20dp\"\n                android:layout_marginTop=\"5dp\"\n                android:layout_marginRight=\"20dp\"\n                android:layout_marginBottom=\"5dp\"\n                android:importantForAccessibility=\"no\"\n                android:orientation=\"horizontal\">\n\n                <TextView\n                    android:id=\"@+id/tv_pre\"\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"wrap_content\"\n                    android:layout_marginStart=\"10dp\"\n                    android:layout_marginEnd=\"10dp\"\n                    android:background=\"?android:attr/selectableItemBackgroundBorderless\"\n                    android:clickable=\"true\"\n                    android:enabled=\"false\"\n                    android:focusable=\"true\"\n                    android:paddingTop=\"10dp\"\n                    android:paddingBottom=\"10dp\"\n                    android:text=\"@string/previous_chapter\"\n                    android:textColor=\"@color/primaryText\"\n                    android:textSize=\"14sp\"\n                    tools:ignore=\"TouchTargetSizeCheck\" />\n\n                <io.legado.app.lib.theme.view.ThemeSeekBar\n                    android:id=\"@+id/seek_read_page\"\n                    android:layout_width=\"0dp\"\n                    android:layout_height=\"25dp\"\n                    android:layout_gravity=\"center_vertical\"\n                    android:layout_weight=\"1\" />\n\n                <TextView\n                    android:id=\"@+id/tv_next\"\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"wrap_content\"\n                    android:layout_marginStart=\"10dp\"\n                    android:layout_marginEnd=\"10dp\"\n                    android:background=\"?android:attr/selectableItemBackgroundBorderless\"\n                    android:clickable=\"true\"\n                    android:enabled=\"false\"\n                    android:focusable=\"true\"\n                    android:paddingTop=\"10dp\"\n                    android:paddingBottom=\"10dp\"\n                    android:text=\"@string/next_chapter\"\n                    android:textColor=\"@color/primaryText\"\n                    android:textSize=\"14sp\"\n                    tools:ignore=\"TouchTargetSizeCheck\" />\n            </LinearLayout>\n\n            <LinearLayout\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:baselineAligned=\"false\"\n                android:importantForAccessibility=\"no\"\n                android:orientation=\"horizontal\">\n\n                <View\n                    android:layout_width=\"0dp\"\n                    android:layout_height=\"match_parent\"\n                    android:layout_weight=\"1\"\n                    android:importantForAccessibility=\"no\" />\n\n                <!--目录按钮-->\n                <LinearLayout\n                    android:id=\"@+id/ll_catalog\"\n                    android:layout_width=\"60dp\"\n                    android:layout_height=\"wrap_content\"\n                    android:background=\"?android:attr/selectableItemBackgroundBorderless\"\n                    android:clickable=\"true\"\n                    android:contentDescription=\"@string/chapter_list\"\n                    android:focusable=\"true\"\n                    android:orientation=\"vertical\"\n                    android:paddingBottom=\"7dp\"\n                    tools:ignore=\"VisualLintBounds\">\n\n                    <androidx.appcompat.widget.AppCompatImageView\n                        android:id=\"@+id/iv_catalog\"\n                        android:layout_width=\"match_parent\"\n                        android:layout_height=\"0dp\"\n                        android:layout_weight=\"1\"\n                        android:adjustViewBounds=\"true\"\n                        android:contentDescription=\"@string/chapter_list\"\n                        android:maxHeight=\"20dp\"\n                        android:src=\"@drawable/ic_toc\"\n                        app:tint=\"@color/primaryText\"\n                        tools:ignore=\"NestedWeights\" />\n\n                    <TextView\n                        android:id=\"@+id/tv_catalog\"\n                        android:layout_width=\"wrap_content\"\n                        android:layout_height=\"wrap_content\"\n                        android:layout_gravity=\"center_horizontal\"\n                        android:layout_marginTop=\"3dp\"\n                        android:ellipsize=\"middle\"\n                        android:singleLine=\"true\"\n                        android:text=\"@string/chapter_list\"\n                        android:textColor=\"@color/primaryText\"\n                        android:textSize=\"12sp\" />\n                </LinearLayout>\n\n                <View\n                    android:layout_width=\"0dp\"\n                    android:layout_height=\"match_parent\"\n                    android:layout_weight=\"2\"\n                    android:importantForAccessibility=\"no\" />\n\n                <!--调节按钮-->\n                <LinearLayout\n                    android:id=\"@+id/ll_read_aloud\"\n                    android:layout_width=\"60dp\"\n                    android:layout_height=\"wrap_content\"\n                    android:background=\"?android:attr/selectableItemBackgroundBorderless\"\n                    android:clickable=\"true\"\n                    android:contentDescription=\"@string/read_aloud\"\n                    android:focusable=\"true\"\n                    android:orientation=\"vertical\"\n                    android:paddingBottom=\"7dp\"\n                    tools:ignore=\"VisualLintBounds\">\n\n                    <androidx.appcompat.widget.AppCompatImageView\n                        android:id=\"@+id/iv_read_aloud\"\n                        android:layout_width=\"match_parent\"\n                        android:layout_height=\"0dp\"\n                        android:layout_weight=\"1\"\n                        android:adjustViewBounds=\"true\"\n                        android:contentDescription=\"@string/read_aloud\"\n                        android:maxHeight=\"20dp\"\n                        android:src=\"@drawable/ic_read_aloud\"\n                        app:tint=\"@color/primaryText\"\n                        tools:ignore=\"NestedWeights\" />\n\n                    <TextView\n                        android:id=\"@+id/tv_read_aloud\"\n                        android:layout_width=\"wrap_content\"\n                        android:layout_height=\"wrap_content\"\n                        android:layout_gravity=\"center_horizontal\"\n                        android:layout_marginTop=\"3dp\"\n                        android:ellipsize=\"middle\"\n                        android:singleLine=\"true\"\n                        android:text=\"@string/read_aloud\"\n                        android:textColor=\"@color/primaryText\"\n                        android:textSize=\"12sp\" />\n                </LinearLayout>\n\n                <View\n                    android:layout_width=\"0dp\"\n                    android:layout_height=\"match_parent\"\n                    android:layout_weight=\"2\"\n                    android:importantForAccessibility=\"no\" />\n\n                <!--界面按钮-->\n                <LinearLayout\n                    android:id=\"@+id/ll_font\"\n                    android:layout_width=\"60dp\"\n                    android:layout_height=\"wrap_content\"\n                    android:background=\"?android:attr/selectableItemBackgroundBorderless\"\n                    android:clickable=\"true\"\n                    android:contentDescription=\"@string/interface_setting\"\n                    android:focusable=\"true\"\n                    android:orientation=\"vertical\"\n                    android:paddingBottom=\"7dp\"\n                    tools:ignore=\"VisualLintBounds\">\n\n                    <androidx.appcompat.widget.AppCompatImageView\n                        android:id=\"@+id/iv_font\"\n                        android:layout_width=\"match_parent\"\n                        android:layout_height=\"0dp\"\n                        android:layout_weight=\"1\"\n                        android:adjustViewBounds=\"true\"\n                        android:contentDescription=\"@string/interface_setting\"\n                        android:maxHeight=\"20dp\"\n                        android:src=\"@drawable/ic_interface_setting\"\n                        app:tint=\"@color/primaryText\"\n                        tools:ignore=\"NestedWeights\" />\n\n                    <TextView\n                        android:id=\"@+id/tv_font\"\n                        android:layout_width=\"wrap_content\"\n                        android:layout_height=\"wrap_content\"\n                        android:layout_gravity=\"center_horizontal\"\n                        android:layout_marginTop=\"3dp\"\n                        android:ellipsize=\"middle\"\n                        android:singleLine=\"true\"\n                        android:text=\"@string/interface_setting\"\n                        android:textColor=\"@color/primaryText\"\n                        android:textSize=\"12sp\" />\n                </LinearLayout>\n\n                <View\n                    android:layout_width=\"0dp\"\n                    android:layout_height=\"match_parent\"\n                    android:layout_weight=\"2\"\n                    android:importantForAccessibility=\"no\" />\n\n                <!--设置按钮-->\n                <LinearLayout\n                    android:id=\"@+id/ll_setting\"\n                    android:layout_width=\"60dp\"\n                    android:layout_height=\"wrap_content\"\n                    android:background=\"?android:attr/selectableItemBackgroundBorderless\"\n                    android:clickable=\"true\"\n                    android:contentDescription=\"@string/setting\"\n                    android:focusable=\"true\"\n                    android:orientation=\"vertical\"\n                    android:paddingBottom=\"7dp\"\n                    tools:ignore=\"VisualLintBounds\">\n\n                    <androidx.appcompat.widget.AppCompatImageView\n                        android:id=\"@+id/iv_setting\"\n                        android:layout_width=\"match_parent\"\n                        android:layout_height=\"0dp\"\n                        android:layout_weight=\"1\"\n                        android:adjustViewBounds=\"true\"\n                        android:contentDescription=\"@string/setting\"\n                        android:maxHeight=\"20dp\"\n                        android:src=\"@drawable/ic_settings\"\n                        app:tint=\"@color/primaryText\"\n                        tools:ignore=\"NestedWeights\" />\n\n                    <TextView\n                        android:id=\"@+id/tv_setting\"\n                        android:layout_width=\"wrap_content\"\n                        android:layout_height=\"wrap_content\"\n                        android:layout_gravity=\"center_horizontal\"\n                        android:layout_marginTop=\"3dp\"\n                        android:ellipsize=\"middle\"\n                        android:singleLine=\"true\"\n                        android:text=\"@string/setting\"\n                        android:textColor=\"@color/primaryText\"\n                        android:textSize=\"12sp\" />\n                </LinearLayout>\n\n                <View\n                    android:layout_width=\"0dp\"\n                    android:layout_height=\"match_parent\"\n                    android:layout_weight=\"1\"\n                    android:importantForAccessibility=\"no\" />\n\n            </LinearLayout>\n        </LinearLayout>\n    </LinearLayout>\n\n</androidx.constraintlayout.widget.ConstraintLayout>"
  },
  {
    "path": "app/src/main/res/layout/view_refresh_recycler.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<merge xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\">\n\n    <io.legado.app.ui.widget.anima.RefreshProgressBar\n        android:id=\"@+id/refresh_progress_bar\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"2dp\"\n        app:layout_constraintTop_toBottomOf=\"@id/title_bar\" />\n\n    <androidx.recyclerview.widget.RecyclerView\n        android:id=\"@+id/recycler_view\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\" />\n\n</merge>"
  },
  {
    "path": "app/src/main/res/layout/view_search.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<io.legado.app.ui.widget.SearchView\n        xmlns:android=\"http://schemas.android.com/apk/res/android\"\n        xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n        android:id=\"@+id/search_view\"\n        android:theme=\"?attr/actionBarStyle\"\n        android:background=\"@drawable/bg_searchview\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"30dp\"\n        android:layout_marginTop=\"10dp\"\n        android:layout_marginBottom=\"10dp\"\n        android:imeOptions=\"actionSearch\"\n        android:focusable=\"false\"\n        app:searchIcon=\"@null\"\n        app:queryBackground=\"@null\"\n        app:submitBackground=\"@null\"\n        app:searchHintIcon=\"@drawable/ic_search_hint\"\n        app:iconifiedByDefault=\"false\"\n        app:defaultQueryHint=\"搜索\"/>"
  },
  {
    "path": "app/src/main/res/layout/view_search_menu.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:orientation=\"vertical\">\n\n    <View\n        android:id=\"@+id/vw_menu_bg\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:contentDescription=\"@string/content\"\n        tools:layout_editor_absoluteX=\"0dp\"\n        tools:layout_editor_absoluteY=\"0dp\"\n        tools:visibility=\"invisible\" />\n\n    <com.google.android.material.floatingactionbutton.FloatingActionButton\n        android:id=\"@+id/fabLeft\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_margin=\"16dp\"\n        android:contentDescription=\"上个结果\"\n        android:rotation=\"180\"\n        android:src=\"@drawable/ic_arrow_right\"\n        android:tint=\"@color/primaryText\"\n        android:tooltipText=\"@string/search_content\"\n        app:backgroundTint=\"@color/background_menu\"\n        app:elevation=\"2dp\"\n        app:fabSize=\"mini\"\n        app:layout_constraintBottom_toBottomOf=\"parent\"\n        app:layout_constraintStart_toStartOf=\"parent\"\n        app:layout_constraintTop_toTopOf=\"parent\"\n        app:pressedTranslationZ=\"2dp\"\n        tools:ignore=\"UnusedAttribute\" />\n\n    <com.google.android.material.floatingactionbutton.FloatingActionButton\n        android:id=\"@+id/fabRight\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_margin=\"16dp\"\n        android:contentDescription=\"下个结果\"\n        android:src=\"@drawable/ic_arrow_right\"\n        android:tint=\"@color/primaryText\"\n        android:tooltipText=\"@string/search_content\"\n        app:backgroundTint=\"@color/background_menu\"\n        app:elevation=\"2dp\"\n        app:fabSize=\"mini\"\n        app:layout_constraintBottom_toBottomOf=\"parent\"\n        app:layout_constraintEnd_toEndOf=\"parent\"\n        app:layout_constraintTop_toTopOf=\"parent\"\n        app:pressedTranslationZ=\"2dp\"\n        tools:ignore=\"UnusedAttribute\" />\n\n    <LinearLayout\n        android:id=\"@+id/ll_bottom_menu\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        android:orientation=\"vertical\"\n        app:layout_constraintBottom_toBottomOf=\"parent\">\n\n        <LinearLayout\n            android:id=\"@+id/ll_search_base_info\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"36dp\"\n            android:background=\"@color/background_menu\"\n            android:gravity=\"center_vertical\"\n            android:orientation=\"horizontal\"\n            android:paddingLeft=\"10dp\"\n            android:paddingRight=\"10dp\">\n\n            <androidx.appcompat.widget.AppCompatImageView\n                android:id=\"@+id/iv_search_content_up\"\n                android:layout_width=\"36dp\"\n                android:layout_height=\"match_parent\"\n                android:background=\"?android:attr/selectableItemBackgroundBorderless\"\n                android:contentDescription=\"@string/go_to_top\"\n                android:src=\"@drawable/ic_arrow_drop_up\"\n                android:tooltipText=\"@string/go_to_top\"\n                app:tint=\"@color/primaryText\"\n                tools:ignore=\"UnusedAttribute\" />\n\n            <androidx.appcompat.widget.AppCompatImageView\n                android:id=\"@+id/iv_search_content_down\"\n                android:layout_width=\"36dp\"\n                android:layout_height=\"match_parent\"\n                android:background=\"?android:attr/selectableItemBackgroundBorderless\"\n                android:contentDescription=\"@string/go_to_bottom\"\n                android:src=\"@drawable/ic_arrow_drop_down\"\n                android:tooltipText=\"@string/go_to_bottom\"\n                app:tint=\"@color/primaryText\"\n                tools:ignore=\"UnusedAttribute\" />\n\n            <TextView\n                android:id=\"@+id/tv_current_search_info\"\n                android:layout_width=\"0dp\"\n                android:layout_height=\"match_parent\"\n                android:layout_weight=\"1\"\n                android:background=\"?android:attr/selectableItemBackgroundBorderless\"\n                android:ellipsize=\"middle\"\n                android:gravity=\"center_vertical\"\n                android:paddingLeft=\"10dp\"\n                android:paddingRight=\"10dp\"\n                android:singleLine=\"true\"\n                android:textColor=\"@color/primaryText\"\n                android:textSize=\"12sp\" />\n        </LinearLayout>\n\n        <LinearLayout\n            android:id=\"@+id/ll_bottom_bg\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"wrap_content\"\n            android:baselineAligned=\"false\"\n            android:orientation=\"horizontal\">\n\n            <View\n                android:layout_width=\"0dp\"\n                android:layout_height=\"match_parent\"\n                android:layout_weight=\"1\"\n                android:importantForAccessibility=\"no\" />\n\n            <!--结果按钮-->\n            <LinearLayout\n                android:id=\"@+id/ll_search_results\"\n                android:layout_width=\"60dp\"\n                android:layout_height=\"wrap_content\"\n                android:background=\"?android:attr/selectableItemBackgroundBorderless\"\n                android:clickable=\"true\"\n                android:contentDescription=\"结果\"\n                android:focusable=\"true\"\n                android:orientation=\"vertical\"\n                android:paddingBottom=\"7dp\">\n\n\n                <androidx.appcompat.widget.AppCompatImageView\n                    android:id=\"@+id/iv_search_results\"\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"0dp\"\n                    android:layout_weight=\"1\"\n                    android:adjustViewBounds=\"true\"\n                    android:contentDescription=\"结果\"\n                    android:maxHeight=\"20dp\"\n                    android:src=\"@drawable/ic_toc\"\n                    app:tint=\"@color/primaryText\"\n                    tools:ignore=\"NestedWeights\" />\n\n                <TextView\n                    android:id=\"@+id/tv_search_results\"\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"wrap_content\"\n                    android:layout_gravity=\"center_horizontal\"\n                    android:layout_marginTop=\"3dp\"\n                    android:maxLines=\"1\"\n                    android:text=\"结果\"\n                    android:textColor=\"@color/primaryText\"\n                    android:textSize=\"12sp\" />\n            </LinearLayout>\n\n\n            <View\n                android:layout_width=\"0dp\"\n                android:layout_height=\"match_parent\"\n                android:layout_weight=\"2\" />\n            <!--调节按钮-->\n            <LinearLayout\n                android:id=\"@+id/ll_main_menu\"\n                android:layout_width=\"60dp\"\n                android:layout_height=\"wrap_content\"\n                android:background=\"?android:attr/selectableItemBackgroundBorderless\"\n                android:clickable=\"true\"\n                android:contentDescription=\"@string/main_menu\"\n                android:focusable=\"true\"\n                android:orientation=\"vertical\"\n                android:paddingBottom=\"7dp\">\n\n                <androidx.appcompat.widget.AppCompatImageView\n                    android:id=\"@+id/iv_main_menu\"\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"0dp\"\n                    android:layout_weight=\"1\"\n                    android:adjustViewBounds=\"true\"\n                    android:contentDescription=\"@string/main_menu\"\n                    android:maxHeight=\"20dp\"\n                    android:src=\"@drawable/ic_menu\"\n                    app:tint=\"@color/primaryText\"\n                    tools:ignore=\"NestedWeights\" />\n\n                <TextView\n                    android:id=\"@+id/tv_main_menu\"\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"wrap_content\"\n                    android:layout_gravity=\"center_horizontal\"\n                    android:layout_marginTop=\"3dp\"\n                    android:maxLines=\"1\"\n                    android:text=\"@string/main_menu\"\n                    android:textColor=\"@color/primaryText\"\n                    android:textSize=\"12sp\" />\n            </LinearLayout>\n\n            <View\n                android:layout_width=\"0dp\"\n                android:layout_height=\"match_parent\"\n                android:layout_weight=\"2\" />\n            <!--界面按钮-->\n            <LinearLayout\n                android:id=\"@+id/ll_search_exit\"\n                android:layout_width=\"60dp\"\n                android:layout_height=\"wrap_content\"\n                android:background=\"?android:attr/selectableItemBackgroundBorderless\"\n                android:clickable=\"true\"\n                android:contentDescription=\"退出\"\n                android:focusable=\"true\"\n                android:orientation=\"vertical\"\n                android:paddingBottom=\"7dp\">\n\n                <androidx.appcompat.widget.AppCompatImageView\n                    android:id=\"@+id/iv_search_exit\"\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"0dp\"\n                    android:layout_weight=\"1\"\n                    android:adjustViewBounds=\"true\"\n                    android:contentDescription=\"退出\"\n                    android:maxHeight=\"20dp\"\n                    android:src=\"@drawable/ic_auto_page_stop\"\n                    app:tint=\"@color/primaryText\"\n                    tools:ignore=\"NestedWeights\" />\n\n                <TextView\n                    android:id=\"@+id/tv_search_exit\"\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"wrap_content\"\n                    android:layout_gravity=\"center_horizontal\"\n                    android:layout_marginTop=\"3dp\"\n                    android:maxLines=\"1\"\n                    android:text=\"退出\"\n                    android:textColor=\"@color/primaryText\"\n                    android:textSize=\"12sp\" />\n            </LinearLayout>\n\n            <!--        <View-->\n            <!--            android:layout_width=\"0dp\"-->\n            <!--            android:layout_height=\"match_parent\"-->\n            <!--            android:layout_weight=\"2\" />-->\n            <!--        &lt;!&ndash;设置按钮&ndash;&gt;-->\n            <!--        <LinearLayout-->\n            <!--            android:id=\"@+id/ll_setting\"-->\n            <!--            android:layout_width=\"50dp\"-->\n            <!--            android:layout_height=\"50dp\"-->\n            <!--            android:background=\"?android:attr/selectableItemBackgroundBorderless\"-->\n            <!--            android:clickable=\"true\"-->\n            <!--            android:contentDescription=\"@string/setting\"-->\n            <!--            android:focusable=\"true\"-->\n            <!--            android:orientation=\"vertical\"-->\n            <!--            android:paddingBottom=\"7dp\">-->\n\n            <!--            <androidx.appcompat.widget.AppCompatImageView-->\n            <!--                android:id=\"@+id/iv_setting\"-->\n            <!--                android:layout_width=\"match_parent\"-->\n            <!--                android:layout_height=\"0dp\"-->\n            <!--                android:layout_weight=\"1\"-->\n            <!--                android:contentDescription=\"@string/aloud_config\"-->\n            <!--                android:src=\"@drawable/ic_settings\"-->\n            <!--                app:tint=\"@color/primaryText\"-->\n            <!--                tools:ignore=\"NestedWeights\" />-->\n\n            <!--            <TextView-->\n            <!--                android:id=\"@+id/tv_setting\"-->\n            <!--                android:layout_width=\"wrap_content\"-->\n            <!--                android:layout_height=\"wrap_content\"-->\n            <!--                android:layout_gravity=\"center_horizontal\"-->\n            <!--                android:layout_marginTop=\"3dp\"-->\n            <!--                android:maxLines=\"1\"-->\n            <!--                android:text=\"@string/setting\"-->\n            <!--                android:textColor=\"@color/primaryText\"-->\n            <!--                android:textSize=\"12sp\" />-->\n            <!--        </LinearLayout>-->\n\n            <View\n                android:layout_width=\"0dp\"\n                android:layout_height=\"match_parent\"\n                android:layout_weight=\"1\"\n                android:importantForAccessibility=\"no\" />\n\n        </LinearLayout>\n\n    </LinearLayout>\n\n</androidx.constraintlayout.widget.ConstraintLayout>"
  },
  {
    "path": "app/src/main/res/layout/view_select_action_bar.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:elevation=\"2dp\"\n    android:gravity=\"center_vertical\"\n    android:orientation=\"horizontal\"\n    android:paddingLeft=\"16dp\"\n    android:paddingTop=\"6dp\"\n    android:paddingRight=\"8dp\"\n    android:paddingBottom=\"6dp\"\n    tools:ignore=\"RtlHardcoded\">\n\n    <io.legado.app.lib.theme.view.ThemeCheckBox\n        android:id=\"@+id/cb_selected_all\"\n        android:layout_width=\"0dp\"\n        android:layout_height=\"wrap_content\"\n        android:layout_weight=\"1\"\n        android:padding=\"5dp\"\n        android:text=\"@string/select_all\" />\n\n    <io.legado.app.ui.widget.text.AccentStrokeTextView\n        android:id=\"@+id/btn_revert_selection\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_margin=\"5dp\"\n        android:gravity=\"center\"\n        android:minWidth=\"72dp\"\n        android:padding=\"5dp\"\n        android:text=\"@string/revert_selection\"\n        app:isBottomBackground=\"true\" />\n\n    <io.legado.app.ui.widget.text.AccentStrokeTextView\n        android:id=\"@+id/btn_select_action_main\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        android:layout_margin=\"5dp\"\n        android:gravity=\"center\"\n        android:minWidth=\"72dp\"\n        android:padding=\"5dp\"\n        android:text=\"@string/app_name\"\n        android:visibility=\"gone\"\n        app:isBottomBackground=\"true\" />\n\n    <androidx.appcompat.widget.AppCompatImageView\n        android:id=\"@+id/iv_menu_more\"\n        android:layout_width=\"36dp\"\n        android:layout_height=\"36dp\"\n        android:background=\"?attr/selectableItemBackgroundBorderless\"\n        android:contentDescription=\"@string/more_menu\"\n        android:padding=\"6dp\"\n        android:src=\"@drawable/ic_more_vert\"\n        android:tint=\"@color/primaryText\"\n        android:tooltipText=\"@string/more_menu\"\n        android:visibility=\"gone\"\n        tools:ignore=\"RtlHardcoded,UnusedAttribute\" />\n\n</LinearLayout>"
  },
  {
    "path": "app/src/main/res/layout/view_tab_layout.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<com.google.android.material.tabs.TabLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:id=\"@+id/tab_layout\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:background=\"@android:color/transparent\"\n    app:tabBackground=\"@android:color/transparent\"\n    app:tabRippleColor=\"@android:color/transparent\"\n    android:theme=\"?attr/actionBarStyle\" />"
  },
  {
    "path": "app/src/main/res/layout/view_tab_layout_min.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<com.google.android.material.tabs.TabLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:id=\"@+id/tab_layout\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:theme=\"?attr/actionBarStyle\"\n    android:background=\"@android:color/transparent\"\n    app:tabBackground=\"@android:color/transparent\"\n    app:tabRippleColor=\"@android:color/transparent\"\n    app:tabMaxWidth=\"100dp\"\n    app:tabMinWidth=\"10dp\" />"
  },
  {
    "path": "app/src/main/res/layout/view_title_bar.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.appcompat.widget.Toolbar xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:id=\"@+id/toolbar\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:theme=\"?attr/actionBarStyle\"\n    app:titleTextAppearance=\"@style/ToolbarTitle\"\n    app:popupTheme=\"@style/AppTheme.PopupOverlay\"/>\n"
  },
  {
    "path": "app/src/main/res/layout/view_title_bar_dark.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.appcompat.widget.Toolbar xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:id=\"@+id/toolbar\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"wrap_content\"\n    android:theme=\"@style/AppTheme.AppBarOverlay.Dark\"\n    app:titleTextAppearance=\"@style/ToolbarTitle\"\n    app:popupTheme=\"@style/AppTheme.PopupOverlay\" />\n"
  },
  {
    "path": "app/src/main/res/layout/view_toast.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<LinearLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:orientation=\"vertical\">\n\n    <androidx.cardview.widget.CardView\n        android:id=\"@+id/cv_toast\"\n        android:layout_width=\"wrap_content\"\n        android:layout_height=\"wrap_content\"\n        app:cardCornerRadius=\"10dp\"\n        app:cardElevation=\"2dp\"\n        app:cardUseCompatPadding=\"true\">\n\n        <TextView\n            android:id=\"@+id/tv_text\"\n            android:layout_width=\"wrap_content\"\n            android:layout_height=\"wrap_content\"\n            android:gravity=\"center_horizontal\"\n            android:paddingHorizontal=\"16dp\"\n            android:paddingTop=\"8dp\"\n            android:paddingBottom=\"10dp\"/>\n\n    </androidx.cardview.widget.CardView>\n\n</LinearLayout>\n"
  },
  {
    "path": "app/src/main/res/layout-land/activity_book_info.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.constraintlayout.widget.ConstraintLayout xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    android:layout_width=\"match_parent\"\n    android:layout_height=\"match_parent\"\n    android:orientation=\"vertical\">\n\n    <ImageView\n        android:id=\"@+id/bg_book\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:contentDescription=\"@string/bg_image\"\n        android:scaleType=\"centerCrop\"\n        app:layout_constraintTop_toTopOf=\"parent\"\n        tools:ignore=\"ImageContrastCheck\" />\n\n    <View\n        android:id=\"@+id/vw_bg\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"match_parent\"\n        android:background=\"#50000000\" />\n\n    <io.legado.app.ui.widget.TitleBar\n        android:id=\"@+id/title_bar\"\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"wrap_content\"\n        app:layout_constraintTop_toTopOf=\"parent\"\n        app:title=\"@string/book_info\"\n        app:themeMode=\"dark\" />\n\n    <LinearLayout\n        android:layout_width=\"match_parent\"\n        android:layout_height=\"0dp\"\n        android:orientation=\"horizontal\"\n        app:layout_constraintTop_toBottomOf=\"@id/title_bar\"\n        app:layout_constraintBottom_toBottomOf=\"parent\"\n        tools:ignore=\"DisableBaselineAlignment\">\n\n        <LinearLayout\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"match_parent\"\n            android:layout_weight=\"1.0\"\n            android:layout_gravity=\"center\"\n            android:gravity=\"center\"\n            android:orientation=\"vertical\">\n\n            <RelativeLayout\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"wrap_content\"\n                android:layout_marginTop=\"30dp\">\n\n                <io.legado.app.ui.widget.image.ArcView\n                    android:id=\"@+id/arc_view\"\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"78dp\"\n                    android:layout_marginTop=\"128dp\"\n                    app:arcDirectionTop=\"true\"\n                    app:arcHeight=\"36dp\"\n                    app:bgColor=\"@color/background\" />\n\n                <androidx.cardview.widget.CardView\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"wrap_content\"\n                    android:layout_centerHorizontal=\"true\"\n                    android:layout_margin=\"3dp\"\n                    app:cardCornerRadius=\"5dp\"\n                    app:cardElevation=\"8dp\">\n\n                    <io.legado.app.ui.widget.image.CoverImageView\n                        android:id=\"@+id/iv_cover\"\n                        android:layout_width=\"130dp\"\n                        android:layout_height=\"200dp\"\n                        android:contentDescription=\"@string/img_cover\"\n                        android:scaleType=\"centerCrop\"\n                        android:src=\"@drawable/image_cover_default\"\n                        tools:ignore=\"ImageContrastCheck\" />\n\n                </androidx.cardview.widget.CardView>\n\n            </RelativeLayout>\n\n            <LinearLayout\n                android:id=\"@+id/ll_info\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"match_parent\"\n                android:background=\"@color/background\"\n                android:orientation=\"vertical\"\n                android:paddingLeft=\"10dp\"\n                android:paddingTop=\"8dp\"\n                android:paddingRight=\"16dp\"\n                android:paddingBottom=\"3dp\"\n                tools:ignore=\"RtlHardcoded\">\n\n                <TextView\n                    android:id=\"@+id/tv_name\"\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"wrap_content\"\n                    android:layout_marginBottom=\"6dp\"\n                    android:gravity=\"center\"\n                    android:includeFontPadding=\"false\"\n                    android:singleLine=\"true\"\n                    android:text=\"@string/book_name\"\n                    android:textColor=\"@color/primaryText\"\n                    android:textSize=\"18sp\"\n                    tools:ignore=\"RtlHardcoded\" />\n\n                <io.legado.app.ui.widget.LabelsBar\n                    android:id=\"@+id/lb_kind\"\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"wrap_content\"\n                    android:layout_gravity=\"center\"\n                    android:layout_marginBottom=\"6dp\"\n                    android:gravity=\"center\"\n                    android:visibility=\"gone\" />\n\n                <LinearLayout\n                    android:layout_width=\"wrap_content\"\n                    android:layout_height=\"match_parent\"\n                    android:gravity=\"center\"\n                    android:layout_gravity=\"center\"\n                    android:orientation=\"horizontal\"\n                    android:paddingTop=\"3dp\"\n                    android:paddingBottom=\"3dp\"\n                    tools:ignore=\"UseCompoundDrawables\">\n\n                    <ImageView\n                        android:layout_width=\"18sp\"\n                        android:layout_height=\"18sp\"\n                        android:contentDescription=\"@string/origin_format\"\n                        android:paddingRight=\"4dp\"\n                        android:src=\"@drawable/ic_author\"\n                        app:tint=\"@color/tv_text_summary\"\n                        tools:ignore=\"RtlHardcoded,RtlSymmetry\" />\n\n                    <TextView\n                        android:id=\"@+id/tv_author\"\n                        android:layout_width=\"wrap_content\"\n                        android:layout_height=\"wrap_content\"\n                        android:ellipsize=\"end\"\n                        android:includeFontPadding=\"false\"\n                        android:singleLine=\"true\"\n                        android:text=\"@string/author\"\n                        android:textColor=\"@color/tv_text_summary\"\n                        android:textSize=\"13sp\"\n                        tools:ignore=\"NestedWeights,TextContrastCheck\" />\n\n                </LinearLayout>\n\n            </LinearLayout>\n\n\n        </LinearLayout>\n\n\n        <LinearLayout\n            android:layout_weight=\"1.0\"\n            android:orientation=\"vertical\"\n            android:layout_width=\"match_parent\"\n            android:layout_height=\"match_parent\">\n\n            <androidx.core.widget.NestedScrollView\n                android:id=\"@+id/scroll_view\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"0dp\"\n                android:layout_weight=\"1\"\n                android:background=\"@color/background\"\n                android:fillViewport=\"true\"\n                android:fitsSystemWindows=\"true\"\n                android:focusable=\"true\"\n                android:padding=\"0dp\"\n                android:scrollbarStyle=\"outsideInset\"\n                android:scrollbars=\"vertical\"\n                tools:ignore=\"NestedWeights\">\n\n                <LinearLayout\n                    android:layout_width=\"match_parent\"\n                    android:layout_height=\"wrap_content\"\n                    android:orientation=\"vertical\"\n                    android:paddingTop=\"16dp\"\n                    android:paddingLeft=\"8dp\"\n                    android:paddingRight=\"8dp\"\n                    android:paddingBottom=\"8dp\">\n\n                    <LinearLayout\n                        android:layout_width=\"match_parent\"\n                        android:layout_height=\"wrap_content\"\n                        android:orientation=\"vertical\"\n                        android:paddingLeft=\"8dp\"\n                        android:paddingRight=\"8dp\"\n                        android:paddingBottom=\"8dp\">\n\n                        <LinearLayout\n                            android:layout_width=\"match_parent\"\n                            android:layout_height=\"wrap_content\"\n                            android:gravity=\"center_vertical\"\n                            android:orientation=\"horizontal\"\n                            android:paddingTop=\"3dp\"\n                            android:paddingBottom=\"3dp\">\n\n                            <ImageView\n                                android:id=\"@+id/iv_web\"\n                                android:layout_width=\"18sp\"\n                                android:layout_height=\"18sp\"\n                                android:contentDescription=\"@string/origin_format\"\n                                android:paddingRight=\"2dp\"\n                                android:src=\"@drawable/ic_web_outline\"\n                                app:tint=\"@color/tv_text_summary\"\n                                tools:ignore=\"RtlHardcoded,RtlSymmetry\" />\n\n                            <TextView\n                                android:id=\"@+id/tv_origin\"\n                                android:layout_width=\"0dp\"\n                                android:layout_height=\"wrap_content\"\n                                android:layout_weight=\"1\"\n                                android:ellipsize=\"end\"\n                                android:includeFontPadding=\"false\"\n                                android:paddingRight=\"6dp\"\n                                android:singleLine=\"true\"\n                                android:textColor=\"@color/tv_text_summary\"\n                                android:textSize=\"13sp\"\n                                tools:ignore=\"NestedWeights,RtlHardcoded,RtlSymmetry,TextContrastCheck\"\n                                tools:text=\"@string/origin_format\" />\n\n                            <io.legado.app.ui.widget.text.AccentBgTextView\n                                android:id=\"@+id/tv_change_source\"\n                                android:layout_width=\"wrap_content\"\n                                android:layout_height=\"wrap_content\"\n                                android:layout_marginStart=\"8dp\"\n                                android:paddingLeft=\"5dp\"\n                                android:paddingRight=\"5dp\"\n                                android:text=\"@string/change_origin\"\n                                android:textSize=\"13sp\"\n                                app:radius=\"2dp\" />\n\n                        </LinearLayout>\n\n                        <LinearLayout\n                            android:layout_width=\"match_parent\"\n                            android:layout_height=\"wrap_content\"\n                            android:gravity=\"center_vertical\"\n                            android:orientation=\"horizontal\"\n                            android:paddingTop=\"3dp\"\n                            android:paddingBottom=\"3dp\"\n                            tools:ignore=\"UseCompoundDrawables\">\n\n                            <ImageView\n                                android:id=\"@+id/ic_book_last\"\n                                android:layout_width=\"18sp\"\n                                android:layout_height=\"18sp\"\n                                android:contentDescription=\"@string/read_dur_progress\"\n                                android:paddingRight=\"2dp\"\n                                android:src=\"@drawable/ic_book_last\"\n                                app:tint=\"@color/tv_text_summary\"\n                                tools:ignore=\"RtlHardcoded,RtlSymmetry\" />\n\n                            <TextView\n                                android:id=\"@+id/tv_lasted\"\n                                android:layout_width=\"0dp\"\n                                android:layout_height=\"wrap_content\"\n                                android:layout_weight=\"1\"\n                                android:ellipsize=\"end\"\n                                android:includeFontPadding=\"false\"\n                                android:paddingRight=\"6dp\"\n                                android:singleLine=\"true\"\n                                android:textColor=\"@color/tv_text_summary\"\n                                android:textSize=\"13sp\"\n                                tools:ignore=\"NestedWeights,RtlHardcoded,RtlSymmetry,TextContrastCheck\"\n                                tools:text=\"@string/read_dur_progress\" />\n\n                        </LinearLayout>\n\n                        <LinearLayout\n                            android:layout_width=\"match_parent\"\n                            android:layout_height=\"wrap_content\"\n                            android:gravity=\"center_vertical\"\n                            android:orientation=\"horizontal\"\n                            android:paddingTop=\"3dp\"\n                            android:paddingBottom=\"3dp\">\n\n                            <ImageView\n                                android:layout_width=\"18sp\"\n                                android:layout_height=\"18sp\"\n                                android:contentDescription=\"@string/read_dur_progress\"\n                                android:paddingRight=\"2dp\"\n                                android:src=\"@drawable/ic_groups\"\n                                app:tint=\"@color/tv_text_summary\"\n                                tools:ignore=\"RtlHardcoded,RtlSymmetry\" />\n\n                            <TextView\n                                android:id=\"@+id/tv_group\"\n                                android:layout_width=\"0dp\"\n                                android:layout_height=\"wrap_content\"\n                                android:layout_weight=\"1\"\n                                android:ellipsize=\"end\"\n                                android:includeFontPadding=\"false\"\n                                android:paddingRight=\"6dp\"\n                                android:singleLine=\"true\"\n                                android:textColor=\"@color/tv_text_summary\"\n                                android:textSize=\"13sp\"\n                                tools:ignore=\"NestedWeights,RtlHardcoded,RtlSymmetry,TextContrastCheck\"\n                                tools:text=\"@string/group_s\" />\n\n                            <io.legado.app.ui.widget.text.AccentBgTextView\n                                android:id=\"@+id/tv_change_group\"\n                                android:layout_width=\"wrap_content\"\n                                android:layout_height=\"wrap_content\"\n                                android:layout_marginStart=\"8dp\"\n                                android:paddingLeft=\"5dp\"\n                                android:paddingRight=\"5dp\"\n                                android:text=\"@string/change_group\"\n                                android:textSize=\"13sp\"\n                                app:radius=\"2dp\" />\n\n                        </LinearLayout>\n\n                        <LinearLayout\n                            android:layout_width=\"match_parent\"\n                            android:layout_height=\"wrap_content\"\n                            android:gravity=\"center_vertical\"\n                            android:orientation=\"horizontal\"\n                            android:paddingTop=\"3dp\"\n                            android:paddingBottom=\"3dp\">\n\n                            <ImageView\n                                android:layout_width=\"18sp\"\n                                android:layout_height=\"18sp\"\n                                android:contentDescription=\"@string/read_dur_progress\"\n                                android:paddingRight=\"2dp\"\n                                android:src=\"@drawable/ic_folder_open\"\n                                app:tint=\"@color/tv_text_summary\"\n                                tools:ignore=\"RtlHardcoded,RtlSymmetry\" />\n\n                            <TextView\n                                android:id=\"@+id/tv_toc\"\n                                android:layout_width=\"0dp\"\n                                android:layout_height=\"wrap_content\"\n                                android:layout_weight=\"1\"\n                                android:ellipsize=\"end\"\n                                android:includeFontPadding=\"false\"\n                                android:paddingRight=\"6dp\"\n                                android:singleLine=\"true\"\n                                android:textColor=\"@color/tv_text_summary\"\n                                android:textSize=\"13sp\"\n                                tools:ignore=\"NestedWeights,RtlHardcoded,RtlSymmetry,TextContrastCheck\"\n                                tools:text=\"@string/toc_s\" />\n\n                            <io.legado.app.ui.widget.text.AccentBgTextView\n                                android:id=\"@+id/tv_toc_view\"\n                                android:layout_width=\"wrap_content\"\n                                android:layout_height=\"wrap_content\"\n                                android:layout_marginStart=\"8dp\"\n                                android:paddingLeft=\"5dp\"\n                                android:paddingRight=\"5dp\"\n                                android:text=\"@string/view_toc\"\n                                android:textSize=\"13sp\"\n                                app:radius=\"2dp\" />\n\n                        </LinearLayout>\n\n                    </LinearLayout>\n\n                    <io.legado.app.ui.widget.text.ScrollTextView\n                        android:id=\"@+id/tv_intro\"\n                        android:layout_width=\"match_parent\"\n                        android:layout_height=\"wrap_content\"\n                        android:layout_marginTop=\"8dp\"\n                        android:clickable=\"true\"\n                        android:focusable=\"true\"\n                        android:minHeight=\"48dp\"\n                        android:paddingLeft=\"8dp\"\n                        android:paddingBottom=\"8dp\"\n                        android:text=\"@string/book_intro\"\n                        android:textColor=\"@color/secondaryText\"\n                        android:textIsSelectable=\"true\"\n                        android:textSize=\"14sp\"\n                        android:visibility=\"visible\"\n                        tools:ignore=\"RtlHardcoded,RtlSymmetry\" />\n\n                </LinearLayout>\n\n            </androidx.core.widget.NestedScrollView>\n\n            <LinearLayout\n                android:id=\"@+id/fl_action\"\n                android:layout_width=\"match_parent\"\n                android:layout_height=\"50dp\"\n                android:background=\"@color/background_menu\"\n                android:orientation=\"horizontal\"\n                app:layout_constraintBottom_toBottomOf=\"parent\">\n\n                <TextView\n                    android:id=\"@+id/tv_shelf\"\n                    android:layout_width=\"0dp\"\n                    android:layout_height=\"match_parent\"\n                    android:layout_weight=\"1\"\n                    android:background=\"?attr/selectableItemBackground\"\n                    android:clickable=\"true\"\n                    android:focusable=\"true\"\n                    android:gravity=\"center\"\n                    android:includeFontPadding=\"false\"\n                    android:text=\"@string/remove_from_bookshelf\"\n                    android:textColor=\"@color/primaryText\"\n                    android:textSize=\"15sp\" />\n\n                <io.legado.app.ui.widget.text.AccentBgTextView\n                    android:id=\"@+id/tv_read\"\n                    android:layout_width=\"0dp\"\n                    android:layout_height=\"match_parent\"\n                    android:layout_weight=\"1\"\n                    android:gravity=\"center\"\n                    android:includeFontPadding=\"false\"\n                    android:text=\"@string/reading\"\n                    android:textSize=\"15sp\" />\n\n            </LinearLayout>\n\n        </LinearLayout>\n\n    </LinearLayout>\n\n\n</androidx.constraintlayout.widget.ConstraintLayout>\n"
  },
  {
    "path": "app/src/main/res/menu/about.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu xmlns:tools=\"http://schemas.android.com/tools\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n    <item\n        android:id=\"@+id/menu_share_it\"\n        android:title=\"@string/share\"\n        android:icon=\"@drawable/ic_share\"\n        app:showAsAction=\"always\"\n        tools:ignore=\"AlwaysShowAction\" />\n\n    <item\n        android:id=\"@+id/menu_scoring\"\n        android:title=\"@string/scoring\"\n        android:icon=\"@drawable/ic_scoring\"\n        app:showAsAction=\"always\" />\n</menu>"
  },
  {
    "path": "app/src/main/res/menu/app_log.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    tools:ignore=\"AlwaysShowAction\">\n\n    <item\n        android:id=\"@+id/menu_clear\"\n        android:title=\"@string/clear\"\n        app:showAsAction=\"always\" />\n\n</menu>"
  },
  {
    "path": "app/src/main/res/menu/app_update.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\">\n\n    <item\n        android:id=\"@+id/menu_download\"\n        android:title=\"@string/action_download\"\n        app:showAsAction=\"always\" />\n\n</menu>"
  },
  {
    "path": "app/src/main/res/menu/audio_play.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    tools:ignore=\"AlwaysShowAction\">\n\n    <item\n        android:id=\"@+id/menu_change_source\"\n        android:icon=\"@drawable/ic_exchange\"\n        android:title=\"@string/change_origin\"\n        app:showAsAction=\"always\" />\n\n    <item\n        android:id=\"@+id/menu_login\"\n        android:title=\"@string/login\"\n        app:showAsAction=\"never\" />\n\n    <item\n        android:id=\"@+id/menu_copy_audio_url\"\n        android:title=\"@string/copy_play_url\"\n        app:showAsAction=\"never\" />\n\n    <item\n        android:id=\"@+id/menu_edit_source\"\n        android:title=\"@string/edit_book_source\"\n        app:showAsAction=\"never\" />\n\n    <item\n        android:id=\"@+id/menu_wake_lock\"\n        android:title=\"@string/audio_play_wake_lock\"\n        android:checkable=\"true\"\n        app:showAsAction=\"never\" />\n\n\n    <item\n        android:id=\"@+id/menu_log\"\n        android:title=\"@string/log\"\n        app:showAsAction=\"never\" />\n\n</menu>"
  },
  {
    "path": "app/src/main/res/menu/backup_restore.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    tools:ignore=\"AlwaysShowAction\">\n\n    <item\n        android:id=\"@+id/menu_help\"\n        android:icon=\"@drawable/ic_help\"\n        android:title=\"@string/help\"\n        app:showAsAction=\"always\" />\n\n    <item\n        android:id=\"@+id/menu_log\"\n        android:title=\"@string/log\"\n        app:showAsAction=\"never\" />\n\n</menu>\n"
  },
  {
    "path": "app/src/main/res/menu/book_cache.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    tools:ignore=\"AlwaysShowAction\">\n\n    <item\n        android:id=\"@+id/menu_download\"\n        android:icon=\"@drawable/ic_play_24dp\"\n        android:title=\"@string/action_download\"\n        app:showAsAction=\"always\" />\n\n    <item\n        android:id=\"@+id/menu_book_group\"\n        android:icon=\"@drawable/ic_groups\"\n        android:title=\"@string/group\"\n        app:showAsAction=\"always\">\n\n        <menu />\n\n    </item>\n\n    <item\n        android:id=\"@+id/menu_export_all\"\n        android:title=\"@string/export_all\"\n        app:showAsAction=\"never\" />\n\n    <item\n        android:id=\"@+id/menu_enable_replace\"\n        android:checkable=\"true\"\n        android:title=\"@string/replace_purify\"\n        app:showAsAction=\"never\" />\n\n    <!--自定义导出章节 选择菜单-->\n    <item\n        android:id=\"@+id/menu_enable_custom_export\"\n        android:checkable=\"true\"\n        android:title=\"@string/custom_export_section\"\n        app:showAsAction=\"never\" />\n\n    <item\n        android:id=\"@+id/menu_export_web_dav\"\n        android:checkable=\"true\"\n        android:title=\"@string/export_to_web_dav\"\n        app:showAsAction=\"never\" />\n\n    <item\n        android:id=\"@+id/menu_export_no_chapter_name\"\n        android:checkable=\"true\"\n        android:title=\"@string/export_no_chapter_name\"\n        app:showAsAction=\"never\" />\n\n    <item\n        android:id=\"@+id/menu_export_pics_file\"\n        android:checkable=\"true\"\n        android:title=\"@string/export_pics_file\"\n        app:showAsAction=\"never\" />\n\n    <item\n        android:id=\"@+id/menu_parallel_export\"\n        android:checkable=\"true\"\n        android:title=\"@string/parallel_export_book\"\n        app:showAsAction=\"never\" />\n\n    <item\n        android:id=\"@+id/menu_export_folder\"\n        android:title=\"@string/export_folder\"\n        app:showAsAction=\"never\" />\n\n    <item\n        android:id=\"@+id/menu_export_file_name\"\n        android:title=\"@string/export_file_name\"\n        app:showAsAction=\"never\" />\n\n    <item\n        android:id=\"@+id/menu_export_type\"\n        android:title=\"@string/export_type\"\n        app:showAsAction=\"never\" />\n\n    <item\n        android:id=\"@+id/menu_export_charset\"\n        android:title=\"@string/export_charset\"\n        app:showAsAction=\"never\" />\n\n    <item\n        android:id=\"@+id/menu_log\"\n        android:title=\"@string/log\"\n        app:showAsAction=\"never\" />\n\n</menu>"
  },
  {
    "path": "app/src/main/res/menu/book_cache_download.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\">\n\n    <item\n        android:id=\"@+id/menu_download_after\"\n        android:icon=\"@drawable/ic_bubble_chart\"\n        android:title=\"@string/menu_download_after\"\n        app:showAsAction=\"never\" />\n\n    <item\n        android:id=\"@+id/menu_download_all\"\n        android:icon=\"@drawable/ic_bubble_chart\"\n        android:title=\"@string/menu_download_all\"\n        app:showAsAction=\"never\" />\n\n</menu>"
  },
  {
    "path": "app/src/main/res/menu/book_group_manage.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu xmlns:tools=\"http://schemas.android.com/tools\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n    <item\n        android:id=\"@+id/menu_add\"\n        android:icon=\"@drawable/ic_add\"\n        android:title=\"@string/add_group\"\n        app:showAsAction=\"always\"\n        tools:ignore=\"AlwaysShowAction\" />\n\n</menu>"
  },
  {
    "path": "app/src/main/res/menu/book_info.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\">\n\n    <item\n        android:id=\"@+id/menu_edit\"\n        android:icon=\"@drawable/ic_edit\"\n        android:title=\"@string/edit\"\n        app:showAsAction=\"ifRoom\" />\n\n    <item\n        android:id=\"@+id/menu_share_it\"\n        android:icon=\"@drawable/ic_share\"\n        android:title=\"@string/share\"\n        app:showAsAction=\"ifRoom\" />\n\n    <item\n        android:id=\"@+id/menu_upload\"\n        android:title=\"@string/upload_to_remote\"\n        app:showAsAction=\"never\" />\n\n    <item\n        android:id=\"@+id/menu_refresh\"\n        android:title=\"@string/refresh\"\n        app:showAsAction=\"never\" />\n\n    <item\n        android:id=\"@+id/menu_login\"\n        android:title=\"@string/login\"\n        app:showAsAction=\"never\" />\n\n    <item\n        android:id=\"@+id/menu_top\"\n        android:title=\"@string/to_top\"\n        app:showAsAction=\"never\" />\n\n    <item\n        android:id=\"@+id/menu_set_source_variable\"\n        android:title=\"@string/set_source_variable\"\n        app:showAsAction=\"never\" />\n\n    <item\n        android:id=\"@+id/menu_set_book_variable\"\n        android:title=\"@string/set_book_variable\"\n        app:showAsAction=\"never\" />\n\n    <item\n        android:id=\"@+id/menu_copy_book_url\"\n        android:title=\"@string/copy_book_url\"\n        app:showAsAction=\"never\" />\n\n    <item\n        android:id=\"@+id/menu_copy_toc_url\"\n        android:title=\"@string/copy_toc_url\"\n        app:showAsAction=\"never\" />\n\n    <item\n        android:id=\"@+id/menu_can_update\"\n        android:title=\"@string/allow_update\"\n        android:checkable=\"true\"\n        app:showAsAction=\"never\" />\n\n    <item\n        android:id=\"@+id/menu_split_long_chapter\"\n        android:title=\"@string/split_long_chapter\"\n        android:checkable=\"true\"\n        app:showAsAction=\"never\" />\n\n    <item\n        android:id=\"@+id/menu_delete_alert\"\n        android:title=\"@string/delete_alert\"\n        android:checkable=\"true\"\n        app:showAsAction=\"never\" />\n\n    <item\n        android:id=\"@+id/menu_clear_cache\"\n        android:title=\"@string/clear_cache\"\n        app:showAsAction=\"never\" />\n\n    <item\n        android:id=\"@+id/menu_log\"\n        android:title=\"@string/log\"\n        app:showAsAction=\"never\" />\n\n</menu>"
  },
  {
    "path": "app/src/main/res/menu/book_info_edit.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\">\n\n    <item\n        android:id=\"@+id/menu_save\"\n        android:icon=\"@drawable/ic_save\"\n        android:title=\"@string/action_save\"\n        app:showAsAction=\"ifRoom\" />\n\n</menu>"
  },
  {
    "path": "app/src/main/res/menu/book_manga.xml",
    "content": "<menu xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    tools:context=\".ui.main.MainActivity\"\n    tools:ignore=\"AlwaysShowAction\">\n\n    <group android:id=\"@+id/menu_group_on_line\">\n        <item\n            android:id=\"@+id/menu_change_source\"\n            android:icon=\"@drawable/ic_exchange\"\n            android:title=\"@string/change_origin\"\n            app:showAsAction=\"always\" />\n\n        <item\n            android:id=\"@+id/menu_refresh\"\n            android:icon=\"@drawable/ic_refresh_black_24dp\"\n            android:title=\"@string/refresh\"\n            app:showAsAction=\"always\" />\n\n        <item\n            android:id=\"@+id/menu_catalog\"\n            android:icon=\"@drawable/ic_toc\"\n            android:title=\"@string/chapter_list\"\n            app:showAsAction=\"always\" />\n\n    </group>\n\n    <item\n        android:id=\"@+id/menu_pre_manga_number\"\n        android:title=\"@string/pre_download\"\n        app:showAsAction=\"never\" />\n\n    <item\n        android:id=\"@+id/menu_disable_manga_scale\"\n        android:checkable=\"true\"\n        android:checked=\"false\"\n        android:title=\"@string/disable_manga_scale\"\n        app:showAsAction=\"never\" />\n\n    <item\n        android:id=\"@+id/menu_disable_click_scroll\"\n        android:checkable=\"true\"\n        android:checked=\"false\"\n        android:title=\"@string/disable_manga_click_scroll\"\n        app:showAsAction=\"never\" />\n\n    <item\n        android:id=\"@+id/menu_enable_auto_page\"\n        android:checkable=\"true\"\n        android:checked=\"false\"\n        android:title=\"@string/enable_auto_page_scroll\"\n        app:showAsAction=\"never\" />\n\n    <item\n        android:id=\"@+id/menu_enable_auto_scroll\"\n        android:checkable=\"true\"\n        android:checked=\"false\"\n        android:title=\"@string/enable_auto_scroll\"\n        app:showAsAction=\"never\" />\n\n    <item\n        android:id=\"@+id/menu_manga_auto_page_speed\"\n        android:title=\"@string/manga_auto_page_speed\"\n        android:visible=\"false\"\n        app:showAsAction=\"never\" />\n\n    <item\n        android:id=\"@+id/menu_enable_horizontal_scroll\"\n        android:checkable=\"true\"\n        android:checked=\"false\"\n        android:title=\"@string/enable_manga_horizontal_scroll\"\n        app:showAsAction=\"never\" />\n\n    <item\n        android:id=\"@+id/menu_disable_horizontal_page_snap\"\n        android:checkable=\"true\"\n        android:checked=\"false\"\n        android:visible=\"false\"\n        android:title=\"@string/disable_horizontal_page_snap\"\n        app:showAsAction=\"never\" />\n\n    <item\n        android:id=\"@+id/menu_disable_manga_page_anim\"\n        android:checkable=\"true\"\n        android:checked=\"false\"\n        android:title=\"@string/disable_manga_page_anim\"\n        app:showAsAction=\"never\" />\n\n    <item\n        android:id=\"@+id/menu_manga_footer_config\"\n        android:title=\"@string/manga_footer_config\"\n        app:showAsAction=\"never\" />\n\n    <item\n        android:id=\"@+id/menu_manga_color_filter\"\n        android:title=\"@string/manga_color_filter\"\n        app:showAsAction=\"never\" />\n\n    <item\n        android:id=\"@+id/menu_hide_manga_title\"\n        android:checkable=\"true\"\n        android:checked=\"false\"\n        android:title=\"@string/hide_manga_title\"\n        app:showAsAction=\"never\" />\n\n    <item\n        android:id=\"@+id/menu_epaper_manga\"\n        android:checkable=\"true\"\n        android:checked=\"false\"\n        android:title=\"@string/manga_epaper\"\n        app:showAsAction=\"never\" />\n\n    <item\n        android:id=\"@+id/menu_epaper_manga_setting\"\n        android:title=\"@string/manga_epaper_stting\"\n        android:visible=\"false\"\n        app:showAsAction=\"never\" />\n\n    <item\n        android:id=\"@+id/menu_gray_manga\"\n        android:checkable=\"true\"\n        android:checked=\"false\"\n        android:title=\"@string/enable_manga_gray\"\n        app:showAsAction=\"never\"/>\n\n</menu>\n"
  },
  {
    "path": "app/src/main/res/menu/book_read.xml",
    "content": "<menu xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    tools:context=\".ui.main.MainActivity\"\n    tools:ignore=\"AlwaysShowAction\">\n\n    <group android:id=\"@+id/menu_group_on_line\">\n        <item\n            android:id=\"@+id/menu_change_source\"\n            android:icon=\"@drawable/ic_exchange\"\n            android:title=\"@string/change_origin\"\n            app:showAsAction=\"always\" />\n\n        <item\n            android:id=\"@+id/menu_refresh\"\n            android:icon=\"@drawable/ic_refresh_black_24dp\"\n            android:title=\"@string/refresh\"\n            app:showAsAction=\"always\" />\n\n        <item\n            android:id=\"@+id/menu_download\"\n            android:icon=\"@drawable/ic_download_line\"\n            android:title=\"@string/offline_cache\"\n            app:showAsAction=\"always\" />\n\n    </group>\n\n    <group android:id=\"@+id/menu_group_text\">\n        <item\n            android:id=\"@+id/menu_toc_regex\"\n            android:icon=\"@drawable/ic_exchange\"\n            android:title=\"@string/txt_toc_rule\"\n            android:visible=\"false\"\n            app:showAsAction=\"always\" />\n    </group>\n\n    <group android:id=\"@+id/menu_group_local\">\n\n        <item\n            android:id=\"@+id/menu_set_charset\"\n            android:icon=\"@drawable/ic_translate\"\n            android:title=\"@string/set_charset\"\n            app:showAsAction=\"always\" />\n\n    </group>\n\n    <item\n        android:id=\"@+id/menu_add_bookmark\"\n        android:title=\"@string/bookmark_add\"\n        app:showAsAction=\"never\" />\n\n    <item\n        android:id=\"@+id/menu_edit_content\"\n        android:title=\"@string/edit_content\"\n        app:showAsAction=\"never\" />\n\n    <item\n        android:id=\"@+id/menu_page_anim\"\n        android:title=\"@string/book_page_anim\"\n        app:showAsAction=\"never\" />\n\n    <item\n        android:id=\"@+id/menu_get_progress\"\n        android:title=\"@string/get_book_progress\"\n        android:visible=\"false\"\n        app:showAsAction=\"never\" />\n\n    <item\n        android:id=\"@+id/menu_cover_progress\"\n        android:title=\"@string/cover_book_progress\"\n        android:visible=\"false\"\n        app:showAsAction=\"never\" />\n\n    <item\n        android:id=\"@+id/menu_reverse_content\"\n        android:title=\"@string/reverse_content\"\n        app:showAsAction=\"never\" />\n\n    <item\n        android:id=\"@+id/menu_simulated_reading\"\n        android:title=\"@string/simulated_reading\"\n        app:showAsAction=\"never\" />\n\n    <item\n        android:id=\"@+id/menu_enable_replace\"\n        android:checkable=\"true\"\n        android:checked=\"true\"\n        android:title=\"@string/replace_rule_title\"\n        app:showAsAction=\"never\" />\n\n    <item\n        android:id=\"@+id/menu_same_title_removed\"\n        android:checkable=\"true\"\n        android:title=\"@string/same_title_removed\"\n        app:showAsAction=\"never\" />\n\n    <item\n        android:id=\"@+id/menu_re_segment\"\n        android:checkable=\"true\"\n        android:checked=\"false\"\n        android:title=\"@string/re_segment\"\n        app:showAsAction=\"never\" />\n\n    <item\n        android:id=\"@+id/menu_enable_review\"\n        android:checkable=\"true\"\n        android:checked=\"false\"\n        android:title=\"@string/review\"\n        android:visible=\"false\"\n        app:showAsAction=\"never\" />\n\n    <group android:id=\"@+id/menu_group_epub\">\n\n        <item\n            android:id=\"@+id/menu_del_ruby_tag\"\n            android:checkable=\"true\"\n            android:checked=\"false\"\n            android:title=\"@string/del_ruby_tag\"\n            app:showAsAction=\"never\" />\n\n        <item\n            android:id=\"@+id/menu_del_h_tag\"\n            android:checkable=\"true\"\n            android:checked=\"false\"\n            android:title=\"@string/del_h_tag\"\n            app:showAsAction=\"never\" />\n\n    </group>\n\n    <item\n        android:id=\"@+id/menu_image_style\"\n        android:title=\"@string/image_style\"\n        app:showAsAction=\"never\" />\n\n    <item\n        android:id=\"@+id/menu_update_toc\"\n        android:title=\"@string/update_toc\"\n        app:showAsAction=\"never\" />\n\n    <item\n        android:id=\"@+id/menu_effective_replaces\"\n        android:title=\"@string/effective_replaces\"\n        app:showAsAction=\"never\" />\n\n    <item\n        android:id=\"@+id/menu_log\"\n        android:title=\"@string/log\"\n        app:showAsAction=\"never\" />\n\n    <item\n        android:id=\"@+id/menu_help\"\n        android:title=\"@string/help\"\n        app:showAsAction=\"never\" />\n\n</menu>\n"
  },
  {
    "path": "app/src/main/res/menu/book_read_change_source.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\">\n\n    <item\n        android:id=\"@+id/menu_chapter_change_source\"\n        android:icon=\"@drawable/ic_bubble_chart\"\n        android:title=\"@string/chapter_change_source\"\n        app:showAsAction=\"never\" />\n\n    <item\n        android:id=\"@+id/menu_book_change_source\"\n        android:icon=\"@drawable/ic_bubble_chart\"\n        android:title=\"@string/book_change_source\"\n        app:showAsAction=\"never\" />\n\n</menu>"
  },
  {
    "path": "app/src/main/res/menu/book_read_record.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\">\n\n    <item\n        android:id=\"@+id/menu_sort\"\n        android:icon=\"@drawable/ic_baseline_sort_24\"\n        android:title=\"@string/sort\"\n        app:showAsAction=\"always\"\n        tools:ignore=\"AlwaysShowAction\">\n\n        <menu>\n            <group\n                android:id=\"@+id/menu_group_sort\"\n                android:checkableBehavior=\"single\">\n\n                <item\n                    android:id=\"@+id/menu_sort_name\"\n                    android:checked=\"true\"\n                    android:title=\"@string/sort_by_name\" />\n\n                <item\n                    android:id=\"@+id/menu_sort_read_long\"\n                    android:title=\"@string/reading_time_sort\"\n                    app:showAsAction=\"never\" />\n\n                <item\n                    android:id=\"@+id/menu_sort_read_time\"\n                    android:title=\"@string/last_read_time_sort\"\n                    app:showAsAction=\"never\" />\n\n            </group>\n\n        </menu>\n\n    </item>\n\n    <item\n        android:id=\"@+id/menu_enable_record\"\n        android:checkable=\"true\"\n        android:title=\"@string/enable_record\"\n        app:showAsAction=\"never\" />\n\n</menu>"
  },
  {
    "path": "app/src/main/res/menu/book_read_refresh.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\">\n\n    <item\n        android:id=\"@+id/menu_refresh_dur\"\n        android:icon=\"@drawable/ic_bubble_chart\"\n        android:title=\"@string/menu_refresh_dur\"\n        app:showAsAction=\"never\" />\n\n    <item\n        android:id=\"@+id/menu_refresh_after\"\n        android:icon=\"@drawable/ic_bubble_chart\"\n        android:title=\"@string/menu_refresh_after\"\n        app:showAsAction=\"never\" />\n\n    <item\n        android:id=\"@+id/menu_refresh_all\"\n        android:icon=\"@drawable/ic_bubble_chart\"\n        android:title=\"@string/menu_refresh_all\"\n        app:showAsAction=\"never\" />\n\n</menu>"
  },
  {
    "path": "app/src/main/res/menu/book_read_source.xml",
    "content": "<menu xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    tools:context=\".ui.main.MainActivity\"\n    tools:ignore=\"AlwaysShowAction\">\n\n    <item\n        android:id=\"@+id/menu_login\"\n        android:title=\"@string/login\"\n        app:showAsAction=\"never\" />\n\n    <item\n        android:id=\"@+id/menu_chapter_pay\"\n        android:title=\"@string/chapter_pay\"\n        app:showAsAction=\"never\" />\n\n    <item\n        android:id=\"@+id/menu_edit_source\"\n        android:title=\"@string/edit_book_source\"\n        app:showAsAction=\"never\" />\n\n    <item\n        android:id=\"@+id/menu_disable_source\"\n        android:title=\"@string/disable_book_source\"\n        app:showAsAction=\"never\" />\n\n</menu>\n"
  },
  {
    "path": "app/src/main/res/menu/book_remote.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    tools:ignore=\"AlwaysShowAction\">\n\n    <item\n        android:id=\"@+id/menu_refresh\"\n        android:icon=\"@drawable/ic_refresh_black_24dp\"\n        android:title=\"@string/refresh\"\n        app:showAsAction=\"always\" />\n    <item\n        android:id=\"@+id/menu_sort\"\n        android:icon=\"@drawable/ic_baseline_sort_24\"\n        android:title=\"@string/sort\"\n        app:showAsAction=\"always\"\n        tools:ignore=\"AlwaysShowAction\">\n        <menu>\n            <group\n                android:id=\"@+id/menu_group_sort\"\n                android:checkableBehavior=\"single\">\n\n                <item\n                    android:id=\"@+id/menu_sort_name\"\n                    android:checkable=\"true\"\n                    android:title=\"@string/sort_by_name\" />\n\n                <item\n                    android:id=\"@+id/menu_sort_time\"\n                    android:checkable=\"true\"\n                    android:title=\"@string/sort_by_lastUpdateTime\" />\n\n            </group>\n\n        </menu>\n\n    </item>\n\n    <item\n        android:id=\"@+id/menu_server_config\"\n        android:icon=\"@drawable/ic_cfg_other\"\n        android:title=\"@string/server_config\"\n        app:showAsAction=\"never\" />\n\n    <item\n        android:id=\"@+id/menu_help\"\n        android:icon=\"@drawable/ic_help\"\n        android:title=\"@string/help\"\n        app:showAsAction=\"never\" />\n    <item\n        android:id=\"@+id/menu_log\"\n        android:icon=\"@drawable/ic_cfg_about\"\n        android:title=\"@string/log\"\n        app:showAsAction=\"never\" />\n\n</menu>"
  },
  {
    "path": "app/src/main/res/menu/book_search.xml",
    "content": "<menu xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    tools:ignore=\"AlwaysShowAction\">\n\n    <item\n        android:id=\"@+id/menu_precision_search\"\n        android:checkable=\"true\"\n        android:title=\"@string/precision_search\"\n        app:showAsAction=\"never\" />\n\n    <item\n        android:id=\"@+id/menu_source_manage\"\n        android:title=\"@string/book_source_manage\"\n        app:showAsAction=\"never\" />\n\n    <item\n        android:id=\"@+id/menu_search_scope\"\n        android:title=\"@string/groups_or_source\"\n        app:showAsAction=\"never\" />\n\n    <item\n        android:id=\"@+id/menu_log\"\n        android:title=\"@string/log\"\n        android:orderInCategory=\"9999\"\n        app:showAsAction=\"never\" />\n\n</menu>\n"
  },
  {
    "path": "app/src/main/res/menu/book_search_scope.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu xmlns:tools=\"http://schemas.android.com/tools\"\n    xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\">\n\n    <item\n        android:id=\"@+id/menu_screen\"\n        android:title=\"@string/screen\"\n        android:icon=\"@drawable/ic_screen\"\n        android:visible=\"false\"\n        app:actionViewClass=\"androidx.appcompat.widget.SearchView\"\n        app:showAsAction=\"always\"\n        tools:ignore=\"AlwaysShowAction\" />\n\n</menu>"
  },
  {
    "path": "app/src/main/res/menu/book_source.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\">\n\n    <item\n        android:id=\"@+id/action_sort\"\n        android:title=\"@string/sort\"\n        android:icon=\"@drawable/ic_sort\"\n        app:showAsAction=\"always\"\n        tools:ignore=\"AlwaysShowAction\">\n\n        <menu>\n\n            <item\n                android:id=\"@+id/menu_sort_desc\"\n                android:checkable=\"true\"\n                android:title=\"@string/sort_desc\" />\n\n            <group\n                android:id=\"@+id/menu_group_sort\"\n                android:checkableBehavior=\"single\">\n\n                <item\n                    android:id=\"@+id/menu_sort_manual\"\n                    android:checkable=\"true\"\n                    android:checked=\"true\"\n                    android:title=\"@string/sort_manual\" />\n\n                <item\n                    android:id=\"@+id/menu_sort_auto\"\n                    android:checkable=\"true\"\n                    android:title=\"@string/sort_auto\" />\n\n                <item\n                    android:id=\"@+id/menu_sort_name\"\n                    android:checkable=\"true\"\n                    android:title=\"@string/sort_by_name\" />\n\n                <item\n                    android:id=\"@+id/menu_sort_url\"\n                    android:checkable=\"true\"\n                    android:title=\"@string/sort_by_url\" />\n\n                <item\n                    android:id=\"@+id/menu_sort_time\"\n                    android:checkable=\"true\"\n                    android:title=\"@string/sort_by_lastUpdateTime\" />\n\n                <item\n                    android:id=\"@+id/menu_sort_respondTime\"\n                    android:checkable=\"true\"\n                    android:title=\"@string/sort_by_respondTime\" />\n\n                <item\n                    android:id=\"@+id/menu_sort_enable\"\n                    android:checkable=\"true\"\n                    android:title=\"@string/is_enabled\" />\n\n            </group>\n\n        </menu>\n\n    </item>\n\n    <item\n        android:id=\"@+id/menu_group\"\n        android:icon=\"@drawable/ic_groups\"\n        android:title=\"@string/menu_action_group\"\n        app:showAsAction=\"always\"\n        tools:ignore=\"AlwaysShowAction\">\n\n        <menu>\n\n            <item\n                android:id=\"@+id/menu_group_manage\"\n                android:title=\"@string/group_manage\" />\n\n            <item\n                android:id=\"@+id/menu_enabled_group\"\n                android:title=\"@string/enabled\" />\n\n            <item\n                android:id=\"@+id/menu_disabled_group\"\n                android:title=\"@string/disabled\" />\n\n            <item\n                android:id=\"@+id/menu_group_login\"\n                android:title=\"@string/need_login\" />\n\n            <item\n                android:id=\"@+id/menu_group_null\"\n                android:title=\"@string/no_group\" />\n\n            <item\n                android:id=\"@+id/menu_enabled_explore_group\"\n                android:title=\"@string/enabled_explore\" />\n\n            <item\n                android:id=\"@+id/menu_disabled_explore_group\"\n                android:title=\"@string/disabled_explore\" />\n\n            <group android:id=\"@+id/source_group\">\n\n            </group>\n\n        </menu>\n\n    </item>\n\n    <item\n        android:id=\"@+id/menu_add_book_source\"\n        android:icon=\"@drawable/ic_add\"\n        android:title=\"@string/add_book_source\"\n        app:showAsAction=\"never\" />\n\n    <item\n        android:id=\"@+id/menu_import_local\"\n        android:icon=\"@drawable/ic_import\"\n        android:title=\"@string/import_local\"\n        app:showAsAction=\"never\" />\n\n    <item\n        android:id=\"@+id/menu_import_onLine\"\n        android:icon=\"@drawable/ic_import\"\n        android:title=\"@string/import_on_line\"\n        app:showAsAction=\"never\" />\n\n    <item\n        android:id=\"@+id/menu_import_qr\"\n        android:icon=\"@drawable/ic_import\"\n        android:title=\"@string/import_by_qr_code\"\n        app:showAsAction=\"never\" />\n\n    <item\n        android:id=\"@+id/menu_group_sources_by_domain\"\n        android:checkable=\"true\"\n        android:title=\"@string/group_sources_by_domain\"\n        app:showAsAction=\"never\" />\n\n    <item\n        android:id=\"@+id/menu_help\"\n        android:icon=\"@drawable/ic_help\"\n        android:title=\"@string/help\"\n        app:showAsAction=\"never\" />\n\n</menu>\n"
  },
  {
    "path": "app/src/main/res/menu/book_source_debug.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    tools:ignore=\"AlwaysShowAction\">\n\n    <item\n        android:id=\"@+id/menu_scan\"\n        android:icon=\"@drawable/ic_scan\"\n        android:title=\"@string/scan_qr_code\"\n        app:showAsAction=\"always\" />\n\n    <item\n        android:id=\"@+id/menu_search_src\"\n        android:title=\"@string/search_src\"\n        app:showAsAction=\"never\" />\n\n    <item\n        android:id=\"@+id/menu_book_src\"\n        android:title=\"@string/boo_src\"\n        app:showAsAction=\"never\" />\n\n    <item\n        android:id=\"@+id/menu_toc_src\"\n        android:title=\"@string/toc_src\"\n        app:showAsAction=\"never\" />\n\n    <item\n        android:id=\"@+id/menu_content_src\"\n        android:title=\"@string/content_src\"\n        app:showAsAction=\"never\" />\n\n    <item\n        android:id=\"@+id/menu_refresh_explore\"\n        android:title=\"@string/refresh_explore\"\n        app:showAsAction=\"never\" />\n\n    <item\n        android:id=\"@+id/menu_help\"\n        android:title=\"@string/help\"\n        app:showAsAction=\"never\" />\n\n</menu>"
  },
  {
    "path": "app/src/main/res/menu/book_source_item.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n    <item\n        android:id=\"@+id/menu_top\"\n        android:title=\"@string/to_top\" />\n\n    <item\n        android:id=\"@+id/menu_bottom\"\n        android:title=\"@string/to_bottom\" />\n\n    <item\n        android:id=\"@+id/menu_login\"\n        android:title=\"@string/login\" />\n\n    <item\n        android:id=\"@+id/menu_search\"\n        android:title=\"@string/search\" />\n\n    <item\n        android:id=\"@+id/menu_debug_source\"\n        android:title=\"@string/debug\" />\n\n    <item\n        android:id=\"@+id/menu_del\"\n        android:title=\"@string/delete\" />\n\n    <item\n        android:id=\"@+id/menu_enable_explore\"\n        android:title=\"@string/enable_explore\" />\n\n</menu>"
  },
  {
    "path": "app/src/main/res/menu/book_source_sel.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\">\n\n    <item\n        android:id=\"@+id/menu_enable_selection\"\n        android:title=\"@string/enable_selection\"\n        app:showAsAction=\"never\" />\n\n    <item\n        android:id=\"@+id/menu_disable_selection\"\n        android:title=\"@string/disable_selection\"\n        app:showAsAction=\"never\" />\n\n    <item\n        android:id=\"@+id/menu_add_group\"\n        android:title=\"@string/add_group\"\n        app:showAsAction=\"never\" />\n        \n    <item\n        android:id=\"@+id/menu_remove_group\"\n        android:title=\"@string/remove_group\"\n        app:showAsAction=\"never\" />\n\n    <item\n        android:id=\"@+id/menu_enable_explore\"\n        android:title=\"@string/enable_explore\"\n        app:showAsAction=\"never\" />\n\n    <item\n        android:id=\"@+id/menu_disable_explore\"\n        android:title=\"@string/disable_explore\"\n        app:showAsAction=\"never\" />\n\n    <item\n        android:id=\"@+id/menu_top_sel\"\n        android:title=\"@string/selection_to_top\"\n        app:showAsAction=\"never\" />\n\n    <item\n        android:id=\"@+id/menu_bottom_sel\"\n        android:title=\"@string/selection_to_bottom\"\n        app:showAsAction=\"never\" />\n\n    <item\n        android:id=\"@+id/menu_export_selection\"\n        android:title=\"@string/export_selection\"\n        app:showAsAction=\"never\" />\n\n    <item\n        android:id=\"@+id/menu_share_source\"\n        android:title=\"@string/share_selected_source\"\n        app:showAsAction=\"never\" />\n\n    <item\n        android:id=\"@+id/menu_check_source\"\n        android:icon=\"@drawable/ic_check_source\"\n        android:title=\"@string/check_select_source\"\n        app:showAsAction=\"never\" />\n\n    <item\n        android:id=\"@+id/menu_check_selected_interval\"\n        android:title=\"@string/check_selected_interval\"\n        app:showAsAction=\"never\" />\n\n</menu>\n"
  },
  {
    "path": "app/src/main/res/menu/book_toc.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    tools:ignore=\"AlwaysShowAction\">\n\n    <item\n        android:id=\"@+id/menu_search\"\n        android:title=\"@string/search\"\n        app:actionViewClass=\"androidx.appcompat.widget.SearchView\"\n        app:showAsAction=\"always\" />\n\n    <group\n        android:id=\"@+id/menu_group_text\"\n        android:visible=\"false\">\n\n        <item\n            android:id=\"@+id/menu_toc_regex\"\n            android:title=\"@string/txt_toc_rule\"\n            app:showAsAction=\"never\" />\n\n        <item\n            android:id=\"@+id/menu_split_long_chapter\"\n            android:checkable=\"true\"\n            android:title=\"@string/split_long_chapter\"\n            app:showAsAction=\"never\" />\n\n    </group>\n\n    <group\n        android:id=\"@+id/menu_group_toc\"\n        android:visible=\"true\">\n\n        <item\n            android:id=\"@+id/menu_reverse_toc\"\n            android:title=\"@string/reverse_toc\"\n            app:showAsAction=\"never\" />\n\n        <item\n            android:id=\"@+id/menu_use_replace\"\n            android:checkable=\"true\"\n            android:title=\"@string/use_replace\"\n            app:showAsAction=\"never\" />\n\n        <item\n            android:id=\"@+id/menu_load_word_count\"\n            android:checkable=\"true\"\n            android:title=\"@string/load_word_count\"\n            app:showAsAction=\"never\" />\n\n    </group>\n\n    <group\n        android:id=\"@+id/menu_group_bookmark\"\n        android:visible=\"false\">\n\n        <item\n            android:id=\"@+id/menu_export_bookmark\"\n            android:title=\"@string/export\"\n            app:showAsAction=\"never\" />\n\n        <item\n            android:id=\"@+id/menu_export_md\"\n            android:title=\"@string/export_md\"\n            app:showAsAction=\"never\" />\n\n    </group>\n\n    <item\n        android:id=\"@+id/menu_log\"\n        android:title=\"@string/log\"\n        app:showAsAction=\"never\" />\n\n</menu>"
  },
  {
    "path": "app/src/main/res/menu/bookmark.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n    <item\n        android:id=\"@+id/menu_export\"\n        android:title=\"@string/export\" />\n\n    <item\n        android:id=\"@+id/menu_export_md\"\n        android:title=\"@string/export_md\" />\n\n</menu>"
  },
  {
    "path": "app/src/main/res/menu/bookshelf_manage.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    tools:ignore=\"AlwaysShowAction\">\n\n    <item\n        android:id=\"@+id/menu_book_group\"\n        android:icon=\"@drawable/ic_groups\"\n        android:title=\"@string/group\"\n        app:showAsAction=\"always\">\n\n        <menu>\n\n            <item\n                android:id=\"@+id/menu_group_manage\"\n                android:title=\"@string/group_manage\" />\n\n        </menu>\n\n    </item>\n\n    <item\n        android:id=\"@+id/menu_export_all_use_book_source\"\n        android:title=\"@string/export_all_use_book_source\" />\n\n    <item\n        android:id=\"@+id/menu_open_book_info_by_click_title\"\n        android:checkable=\"true\"\n        android:title=\"@string/open_book_info_by_click_title\" />\n\n</menu>\n"
  },
  {
    "path": "app/src/main/res/menu/bookshelf_menage_sel.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n    <item\n        android:id=\"@+id/menu_del_selection\"\n        android:title=\"@string/delete\" />\n\n    <item\n        android:id=\"@+id/menu_update_enable\"\n        android:title=\"@string/allow_update\" />\n\n    <item\n        android:id=\"@+id/menu_update_disable\"\n        android:title=\"@string/disable_update\" />\n\n    <item\n        android:id=\"@+id/menu_add_to_group\"\n        android:title=\"@string/add_to_group\" />\n\n    <item\n        android:id=\"@+id/menu_change_source\"\n        android:title=\"@string/change_source_batch\" />\n\n    <item\n        android:id=\"@+id/menu_clear_cache\"\n        android:title=\"@string/clear_cache\" />\n\n    <item\n        android:id=\"@+id/menu_check_selected_interval\"\n        android:title=\"@string/check_selected_interval\" />\n\n</menu>"
  },
  {
    "path": "app/src/main/res/menu/change_cover.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu xmlns:tools=\"http://schemas.android.com/tools\"\n    xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\">\n\n    <item\n        android:id=\"@+id/menu_start_stop\"\n        android:title=\"@string/stop\"\n        android:icon=\"@drawable/ic_refresh_white_24dp\"\n        app:showAsAction=\"always\"\n        tools:ignore=\"AlwaysShowAction\" />\n\n</menu>"
  },
  {
    "path": "app/src/main/res/menu/change_source.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu xmlns:tools=\"http://schemas.android.com/tools\"\n    xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\">\n\n    <item\n        android:id=\"@+id/menu_screen\"\n        android:title=\"@string/screen\"\n        android:icon=\"@drawable/ic_screen\"\n        app:actionViewClass=\"androidx.appcompat.widget.SearchView\"\n        app:showAsAction=\"always\"\n        tools:ignore=\"AlwaysShowAction\" />\n\n    <item\n        android:id=\"@+id/menu_start_stop\"\n        android:title=\"@string/stop\"\n        android:icon=\"@drawable/ic_refresh_white_24dp\"\n        app:showAsAction=\"always\"\n        tools:ignore=\"AlwaysShowAction\" />\n\n    <item\n        android:id=\"@+id/menu_source_manage\"\n        android:title=\"@string/book_source_manage\"\n        app:showAsAction=\"never\" />\n\n    <item\n        android:id=\"@+id/menu_refresh_list\"\n        android:title=\"@string/refresh_list\"\n        app:showAsAction=\"never\" />\n\n    <item\n        android:id=\"@+id/menu_check_author\"\n        android:title=\"@string/checkAuthor\"\n        android:checkable=\"true\"\n        app:showAsAction=\"never\" />\n\n    <item\n        android:id=\"@+id/menu_load_word_count\"\n        android:title=\"@string/load_word_count\"\n        android:checkable=\"true\"\n        app:showAsAction=\"never\" />\n\n    <item\n        android:id=\"@+id/menu_load_info\"\n        android:title=\"@string/load_info\"\n        android:checkable=\"true\"\n        app:showAsAction=\"never\" />\n\n    <item\n        android:id=\"@+id/menu_load_toc\"\n        android:title=\"@string/load_toc\"\n        android:checkable=\"true\"\n        app:showAsAction=\"never\" />\n    <item\n        android:id=\"@+id/menu_group\"\n        android:title=\"@string/group\">\n\n        <menu>\n            <group\n                android:id=\"@+id/source_group\"\n                android:checkableBehavior=\"single\">\n            </group>\n\n        </menu>\n\n    </item>\n\n    <item\n        android:id=\"@+id/menu_close\"\n        android:title=\"@string/close\"\n        app:showAsAction=\"never\" />\n</menu>"
  },
  {
    "path": "app/src/main/res/menu/change_source_item.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n    <item\n        android:id=\"@+id/menu_top_source\"\n        android:title=\"@string/to_top\" />\n\n    <item\n        android:id=\"@+id/menu_bottom_source\"\n        android:title=\"@string/to_bottom\" />\n\n    <item\n        android:id=\"@+id/menu_edit_source\"\n        android:title=\"@string/edit_source\" />\n\n    <item\n        android:id=\"@+id/menu_disable_source\"\n        android:title=\"@string/disable_source\" />\n\n    <item\n        android:id=\"@+id/menu_delete_source\"\n        android:title=\"@string/delete_source\" />\n\n</menu>"
  },
  {
    "path": "app/src/main/res/menu/code_edit.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    tools:ignore=\"AlwaysShowAction\">\n\n    <item\n        android:id=\"@+id/menu_save\"\n        android:icon=\"@drawable/ic_save\"\n        android:title=\"@string/action_save\"\n        app:showAsAction=\"always\" />\n\n</menu>"
  },
  {
    "path": "app/src/main/res/menu/content_edit.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    tools:ignore=\"AlwaysShowAction\">\n\n    <item\n        android:id=\"@+id/menu_save\"\n        android:icon=\"@drawable/ic_save\"\n        android:title=\"@string/action_save\"\n        app:showAsAction=\"always\" />\n\n    <item\n        android:id=\"@+id/menu_reset\"\n        android:title=\"@string/reset\"\n        app:showAsAction=\"never\" />\n\n    <item\n        android:id=\"@+id/menu_copy_all\"\n        android:title=\"@string/copy_all\"\n        app:showAsAction=\"never\" />\n\n</menu>"
  },
  {
    "path": "app/src/main/res/menu/content_search.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    tools:ignore=\"AlwaysShowAction\">\n\n    <item\n        android:id=\"@+id/menu_enable_replace\"\n        android:title=\"@string/replace\"\n        android:checkable=\"true\"\n        app:showAsAction=\"never\" />\n\n</menu>"
  },
  {
    "path": "app/src/main/res/menu/content_select_action.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\">\n\n    <item\n        android:id=\"@+id/menu_replace\"\n        android:title=\"@string/replace\" />\n\n    <item\n        android:id=\"@+id/menu_copy\"\n        android:title=\"@android:string/copy\" />\n\n    <item\n        android:id=\"@+id/menu_bookmark\"\n        android:title=\"@string/bookmark\" />\n\n    <item\n        android:id=\"@+id/menu_aloud\"\n        android:title=\"@string/read_aloud\" />\n\n    <item\n        android:id=\"@+id/menu_dict\"\n        android:title=\"@string/dict\" />\n\n    <item\n        android:id=\"@+id/menu_search_content\"\n        android:title=\"@string/search_content\"\n        app:showAsAction=\"never\" />\n\n    <item\n        android:id=\"@+id/menu_browser\"\n        android:title=\"@string/browser\"\n        app:showAsAction=\"never\" />\n\n    <item\n        android:id=\"@+id/menu_share_str\"\n        android:title=\"@string/share\"\n        app:showAsAction=\"never\" />\n</menu>"
  },
  {
    "path": "app/src/main/res/menu/crash_log.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    tools:ignore=\"AlwaysShowAction\">\n\n    <item\n        android:id=\"@+id/menu_clear\"\n        android:title=\"@string/clear\"\n        app:showAsAction=\"always\" />\n\n</menu>"
  },
  {
    "path": "app/src/main/res/menu/dialog_text.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n    <item\n        android:id=\"@+id/menu_close\"\n        android:title=\"@string/close\"\n        app:showAsAction=\"always\"/>\n</menu>"
  },
  {
    "path": "app/src/main/res/menu/dict_rule.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    tools:ignore=\"AlwaysShowAction\">\n\n    <item\n        android:id=\"@+id/menu_add\"\n        android:icon=\"@drawable/ic_add\"\n        android:title=\"@string/create\"\n        app:showAsAction=\"always\" />\n\n    <item\n        android:id=\"@+id/menu_import_local\"\n        android:icon=\"@drawable/ic_import\"\n        android:title=\"@string/import_local\"\n        app:showAsAction=\"never\" />\n\n    <item\n        android:id=\"@+id/menu_import_onLine\"\n        android:icon=\"@drawable/ic_import\"\n        android:title=\"@string/import_on_line\"\n        app:showAsAction=\"never\" />\n\n    <item\n        android:id=\"@+id/menu_import_qr\"\n        android:icon=\"@drawable/ic_import\"\n        android:title=\"@string/import_by_qr_code\"\n        app:showAsAction=\"never\" />\n\n    <item\n        android:id=\"@+id/menu_import_default\"\n        android:icon=\"@drawable/ic_import\"\n        android:title=\"@string/import_default_rule\"\n        app:showAsAction=\"never\" />\n\n    <item\n        android:id=\"@+id/menu_help\"\n        android:icon=\"@drawable/ic_help\"\n        android:title=\"@string/help\"\n        app:showAsAction=\"never\" />\n\n</menu>\n"
  },
  {
    "path": "app/src/main/res/menu/dict_rule_edit.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    tools:ignore=\"AlwaysShowAction\">\n\n    <item\n        android:id=\"@+id/menu_save\"\n        android:icon=\"@drawable/ic_save\"\n        android:title=\"@string/action_save\"\n        app:showAsAction=\"always\" />\n\n    <item\n        android:id=\"@+id/menu_copy_rule\"\n        android:title=\"@string/copy_rule\"\n        app:showAsAction=\"never\" />\n\n    <item\n        android:id=\"@+id/menu_paste_rule\"\n        android:title=\"@string/paste_rule\"\n        app:showAsAction=\"never\" />\n\n</menu>\n"
  },
  {
    "path": "app/src/main/res/menu/dict_rule_sel.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\">\n\n    <item\n        android:id=\"@+id/menu_enable_selection\"\n        android:title=\"@string/enable_selection\"\n        app:showAsAction=\"never\" />\n\n    <item\n        android:id=\"@+id/menu_disable_selection\"\n        android:title=\"@string/disable_selection\"\n        app:showAsAction=\"never\" />\n\n    <item\n        android:id=\"@+id/menu_export_selection\"\n        android:title=\"@string/export_selection\"\n        app:showAsAction=\"never\" />\n\n</menu>\n"
  },
  {
    "path": "app/src/main/res/menu/direct_link_upload_config.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\">\n\n    <item\n        android:id=\"@+id/menu_copy_rule\"\n        android:title=\"@string/copy_rule\"\n        app:showAsAction=\"never\" />\n\n    <item\n        android:id=\"@+id/menu_paste_rule\"\n        android:title=\"@string/paste_rule\"\n        app:showAsAction=\"never\" />\n\n    <item\n        android:id=\"@+id/menu_import_default\"\n        android:title=\"@string/import_default_rule\"\n        app:showAsAction=\"never\" />\n\n</menu>"
  },
  {
    "path": "app/src/main/res/menu/explore_item.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n    <item\n        android:id=\"@+id/menu_edit\"\n        android:title=\"@string/edit\" />\n\n    <item\n        android:id=\"@+id/menu_top\"\n        android:title=\"@string/to_top\" />\n\n    <item\n        android:id=\"@+id/menu_login\"\n        android:title=\"@string/login\" />\n\n    <item\n        android:id=\"@+id/menu_search\"\n        android:title=\"@string/search\" />\n\n    <item\n        android:id=\"@+id/menu_refresh\"\n        android:title=\"@string/refresh\" />\n\n    <item\n        android:id=\"@+id/menu_del\"\n        android:title=\"@string/delete\" />\n\n</menu>"
  },
  {
    "path": "app/src/main/res/menu/file_chooser.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n    <item\n        android:id=\"@+id/menu_create\"\n        android:title=\"@string/create_folder\"\n        android:icon=\"@drawable/ic_create_folder_outline\"\n        app:showAsAction=\"always\" />\n\n</menu>"
  },
  {
    "path": "app/src/main/res/menu/file_long_click.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n    <item\n        android:id=\"@+id/menu_del\"\n        android:title=\"@string/delete\" />\n\n</menu>"
  },
  {
    "path": "app/src/main/res/menu/font_select.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\">\n\n    <item\n        android:id=\"@+id/menu_default\"\n        android:title=\"@string/default_font\"\n        app:showAsAction=\"ifRoom\" />\n\n    <item\n        android:id=\"@+id/menu_other\"\n        android:title=\"@string/other_folder\"\n        app:showAsAction=\"never\" />\n\n</menu>"
  },
  {
    "path": "app/src/main/res/menu/group_manage.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n    <item\n        android:id=\"@+id/menu_add\"\n        android:icon=\"@drawable/ic_add\"\n        android:title=\"@string/add_group\"\n        app:showAsAction=\"always\" />\n</menu>"
  },
  {
    "path": "app/src/main/res/menu/import_book.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    tools:ignore=\"AlwaysShowAction\">\n\n    <item\n        android:id=\"@+id/menu_select_folder\"\n        android:icon=\"@drawable/ic_folder_open\"\n        android:title=\"@string/select_folder\"\n        app:showAsAction=\"always\" />\n\n    <item\n        android:id=\"@+id/menu_sort\"\n        android:title=\"@string/sort\"\n        android:icon=\"@drawable/ic_sort\"\n        app:showAsAction=\"always\">\n\n        <menu>\n\n            <item\n                android:id=\"@+id/menu_sort_name\"\n                android:checkable=\"true\"\n                android:title=\"@string/sort_by_name\"\n                app:showAsAction=\"never\" />\n\n            <item\n                android:id=\"@+id/menu_sort_size\"\n                android:checkable=\"true\"\n                android:title=\"@string/sort_by_size\"\n                app:showAsAction=\"never\" />\n\n            <item\n                android:id=\"@+id/menu_sort_time\"\n                android:checkable=\"true\"\n                android:title=\"@string/sort_by_time\"\n                app:showAsAction=\"never\" />\n\n        </menu>\n\n    </item>\n\n    <item\n        android:id=\"@+id/menu_scan_folder\"\n        android:title=\"@string/scan_folder\"\n        app:showAsAction=\"never\" />\n\n    <item\n        android:id=\"@+id/menu_import_file_name\"\n        android:title=\"@string/import_file_name\"\n        app:showAsAction=\"never\" />\n\n</menu>\n"
  },
  {
    "path": "app/src/main/res/menu/import_book_sel.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n    <item\n        android:id=\"@+id/menu_del_selection\"\n        android:title=\"@string/delete\" />\n\n</menu>"
  },
  {
    "path": "app/src/main/res/menu/import_replace.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu xmlns:tools=\"http://schemas.android.com/tools\"\n    xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\">\n\n    <item\n        android:id=\"@+id/menu_new_group\"\n        android:title=\"@string/diy_source_group\"\n        app:showAsAction=\"always\"\n        tools:ignore=\"AlwaysShowAction\" />\n\n</menu>"
  },
  {
    "path": "app/src/main/res/menu/import_source.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu xmlns:tools=\"http://schemas.android.com/tools\"\n    xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\">\n\n    <item\n        android:id=\"@+id/menu_new_group\"\n        android:title=\"@string/diy_source_group\"\n        app:showAsAction=\"always\"\n        tools:ignore=\"AlwaysShowAction\" />\n\n    <item\n        android:id=\"@+id/menu_select_new_source\"\n        android:title=\"@string/select_new_source\"\n        app:showAsAction=\"never\" />\n\n    <item\n        android:id=\"@+id/menu_select_update_source\"\n        android:title=\"@string/select_update_source\"\n        app:showAsAction=\"never\" />\n\n    <item\n        android:id=\"@+id/menu_keep_original_name\"\n        android:title=\"@string/keep_original_name\"\n        android:checkable=\"true\"\n        app:showAsAction=\"never\" />\n\n    <item\n        android:id=\"@+id/menu_keep_group\"\n        android:title=\"@string/keep_group\"\n        android:checkable=\"true\"\n        app:showAsAction=\"never\" />\n\n    <item\n        android:id=\"@+id/menu_keep_enable\"\n        android:title=\"@string/keep_enable\"\n        android:checkable=\"true\"\n        app:showAsAction=\"never\" />\n\n</menu>\n"
  },
  {
    "path": "app/src/main/res/menu/keyboard_assists_config.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\">\n\n    <item\n        android:id=\"@+id/menu_add\"\n        android:icon=\"@drawable/ic_add\"\n        android:title=\"@string/add\"\n        app:showAsAction=\"always\" />\n\n</menu>"
  },
  {
    "path": "app/src/main/res/menu/main_bnv.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    tools:showIn=\"bottom_navigation_view\">\n\n    <item\n        android:id=\"@+id/menu_bookshelf\"\n        android:icon=\"@drawable/ic_bottom_books\"\n        android:title=\"@string/bookshelf\" />\n    <item\n        android:id=\"@+id/menu_discovery\"\n        android:icon=\"@drawable/ic_bottom_explore\"\n        android:title=\"@string/discovery\" />\n    <item\n        android:id=\"@+id/menu_rss\"\n        android:icon=\"@drawable/ic_bottom_rss_feed\"\n        android:title=\"@string/rss\" />\n    <item\n        android:id=\"@+id/menu_my_config\"\n        android:icon=\"@drawable/ic_bottom_person\"\n        android:title=\"@string/my\" />\n\n</menu>\n"
  },
  {
    "path": "app/src/main/res/menu/main_bookshelf.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    tools:ignore=\"AlwaysShowAction\">\n\n    <item\n        android:id=\"@+id/menu_search\"\n        android:icon=\"@drawable/ic_search\"\n        android:title=\"@string/search\"\n        app:showAsAction=\"always\" />\n\n    <item\n        android:id=\"@+id/menu_update_toc\"\n        android:icon=\"@drawable/ic_refresh_black_24dp\"\n        android:title=\"@string/update_toc\"\n        app:showAsAction=\"never\" />\n\n    <item\n        android:id=\"@+id/menu_add_local\"\n        android:icon=\"@drawable/ic_add\"\n        android:title=\"@string/book_local\"\n        app:showAsAction=\"never\" />\n\n    <item\n        android:id=\"@+id/menu_remote\"\n        android:icon=\"@drawable/ic_add\"\n        android:title=\"@string/add_remote_book\"\n        app:showAsAction=\"never\" />\n\n    <item\n        android:id=\"@+id/menu_add_url\"\n        android:icon=\"@drawable/ic_add_online\"\n        android:title=\"@string/add_url\"\n        app:showAsAction=\"never\" />\n\n    <item\n        android:id=\"@+id/menu_bookshelf_manage\"\n        android:icon=\"@drawable/ic_arrange\"\n        android:title=\"@string/bookshelf_management\"\n        app:showAsAction=\"never\" />\n\n    <item\n        android:id=\"@+id/menu_download\"\n        android:icon=\"@drawable/ic_download_line\"\n        android:title=\"@string/cache_export\"\n        app:showAsAction=\"never\" />\n\n    <item\n        android:id=\"@+id/menu_group_manage\"\n        android:icon=\"@drawable/ic_groups\"\n        android:title=\"@string/group_manage\"\n        app:showAsAction=\"never\" />\n\n    <item\n        android:id=\"@+id/menu_bookshelf_layout\"\n        android:icon=\"@drawable/ic_view_quilt\"\n        android:title=\"@string/bookshelf_layout\"\n        app:showAsAction=\"never\" />\n\n    <item\n        android:id=\"@+id/menu_export_bookshelf\"\n        android:icon=\"@drawable/ic_export\"\n        android:title=\"@string/export_bookshelf\"\n        app:showAsAction=\"never\" />\n\n    <item\n        android:id=\"@+id/menu_import_bookshelf\"\n        android:icon=\"@drawable/ic_import\"\n        android:title=\"@string/import_bookshelf\"\n        app:showAsAction=\"never\" />\n\n    <item\n        android:id=\"@+id/menu_log\"\n        android:icon=\"@drawable/ic_cfg_about\"\n        android:title=\"@string/log\"\n        app:showAsAction=\"never\" />\n\n</menu>"
  },
  {
    "path": "app/src/main/res/menu/main_explore.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\">\n\n    <item\n        android:id=\"@+id/menu_group\"\n        android:title=\"@string/group\"\n        android:icon=\"@drawable/ic_groups\"\n        app:showAsAction=\"always\">\n\n        <menu />\n\n    </item>\n\n</menu>"
  },
  {
    "path": "app/src/main/res/menu/main_my.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu xmlns:tools=\"http://schemas.android.com/tools\"\n    xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\">\n\n    <item\n        android:id=\"@+id/menu_help\"\n        android:title=\"@string/help\"\n        android:icon=\"@drawable/ic_help\"\n        app:showAsAction=\"always\"\n        tools:ignore=\"AlwaysShowAction\" />\n\n</menu>\n"
  },
  {
    "path": "app/src/main/res/menu/main_rss.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu xmlns:tools=\"http://schemas.android.com/tools\"\n    xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\">\n\n    <item\n        android:id=\"@+id/menu_rss_star\"\n        android:title=\"@string/favorite\"\n        android:icon=\"@drawable/ic_star\"\n        app:showAsAction=\"always\" />\n\n    <item\n        android:id=\"@+id/menu_group\"\n        android:title=\"@string/group\"\n        android:icon=\"@drawable/ic_groups\"\n        app:showAsAction=\"always\">\n\n        <menu />\n\n    </item>\n\n    <item\n        android:id=\"@+id/menu_rss_config\"\n        android:title=\"@string/setting\"\n        android:icon=\"@drawable/ic_settings\"\n        app:showAsAction=\"always\"\n        tools:ignore=\"AlwaysShowAction\" />\n\n</menu>"
  },
  {
    "path": "app/src/main/res/menu/open_url_confirm.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    tools:ignore=\"AlwaysShowAction\">\n\n    <item\n        android:id=\"@+id/menu_disable_source\"\n        android:title=\"@string/disable_source\"\n        app:showAsAction=\"never\" />\n\n    <item\n        android:id=\"@+id/menu_delete_source\"\n        android:title=\"@string/delete_source\"\n        app:showAsAction=\"never\" />\n\n</menu>\n"
  },
  {
    "path": "app/src/main/res/menu/qr_code_scan.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\">\n\n    <item\n        android:id=\"@+id/action_choose_from_gallery\"\n        android:title=\"@string/gallery\"\n        app:showAsAction=\"always\" />\n\n</menu>"
  },
  {
    "path": "app/src/main/res/menu/replace_edit.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu xmlns:tools=\"http://schemas.android.com/tools\"\n    xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    tools:ignore=\"AlwaysShowAction\">\n\n    <item\n        android:id=\"@+id/menu_save\"\n        android:icon=\"@drawable/ic_save\"\n        android:title=\"@string/action_save\"\n        app:showAsAction=\"always\" />\n\n    <item\n        android:id=\"@+id/menu_copy_rule\"\n        android:title=\"@string/copy_rule\"\n        app:showAsAction=\"never\" />\n\n    <item\n        android:id=\"@+id/menu_paste_rule\"\n        android:title=\"@string/paste_rule\"\n        app:showAsAction=\"never\" />\n\n</menu>"
  },
  {
    "path": "app/src/main/res/menu/replace_rule.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\">\n\n    <item\n        android:id=\"@+id/menu_group\"\n        android:icon=\"@drawable/ic_groups\"\n        android:title=\"@string/menu_action_group\"\n        app:showAsAction=\"always\"\n        tools:ignore=\"AlwaysShowAction\">\n\n        <menu>\n\n            <item\n                android:id=\"@+id/menu_group_manage\"\n                android:title=\"@string/group_manage\" />\n\n            <item\n                android:id=\"@+id/menu_group_null\"\n                android:title=\"@string/no_group\" />\n\n            <group android:id=\"@+id/replace_group\">\n\n            </group>\n\n        </menu>\n\n    </item>\n\n    <item\n        android:id=\"@+id/menu_add_replace_rule\"\n        android:icon=\"@drawable/ic_add\"\n        android:title=\"@string/add_replace_rule\"\n        app:showAsAction=\"never\" />\n\n    <item\n        android:id=\"@+id/menu_import_local\"\n        android:icon=\"@drawable/ic_import\"\n        android:title=\"@string/import_local\"\n        app:showAsAction=\"never\" />\n\n    <item\n        android:id=\"@+id/menu_import_onLine\"\n        android:icon=\"@drawable/ic_import\"\n        android:title=\"@string/import_on_line\"\n        app:showAsAction=\"never\" />\n\n    <item\n        android:id=\"@+id/menu_import_qr\"\n        android:icon=\"@drawable/ic_import\"\n        android:title=\"@string/import_by_qr_code\"\n        app:showAsAction=\"never\" />\n\n    <item\n        android:id=\"@+id/menu_help\"\n        android:icon=\"@drawable/ic_help\"\n        android:title=\"@string/help\"\n        app:showAsAction=\"never\" />\n\n</menu>\n"
  },
  {
    "path": "app/src/main/res/menu/replace_rule_item.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n    <item\n        android:id=\"@+id/menu_top\"\n        android:title=\"@string/to_top\" />\n\n    <item\n        android:id=\"@+id/menu_bottom\"\n        android:title=\"@string/to_bottom\" />\n\n    <item\n        android:id=\"@+id/menu_del\"\n        android:title=\"@string/delete\" />\n\n</menu>"
  },
  {
    "path": "app/src/main/res/menu/replace_rule_sel.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\">\n\n    <item\n        android:id=\"@+id/menu_enable_selection\"\n        android:title=\"@string/enable_selection\"\n        app:showAsAction=\"never\" />\n\n    <item\n        android:id=\"@+id/menu_disable_selection\"\n        android:title=\"@string/disable_selection\"\n        app:showAsAction=\"never\" />\n\n    <item\n        android:id=\"@+id/menu_top_sel\"\n        android:title=\"@string/selection_to_top\"\n        app:showAsAction=\"never\" />\n\n    <item\n        android:id=\"@+id/menu_bottom_sel\"\n        android:title=\"@string/selection_to_bottom\"\n        app:showAsAction=\"never\" />\n\n    <item\n        android:id=\"@+id/menu_export_selection\"\n        android:title=\"@string/export_selection\"\n        app:showAsAction=\"never\" />\n\n</menu>\n"
  },
  {
    "path": "app/src/main/res/menu/rss_articles.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\">\n\n    <item\n        android:id=\"@+id/menu_login\"\n        android:title=\"@string/login\"\n        app:showAsAction=\"never\" />\n\n    <item\n        android:id=\"@+id/menu_refresh_sort\"\n        android:title=\"@string/refresh_sort\"\n        app:showAsAction=\"never\" />\n\n    <item\n        android:id=\"@+id/menu_set_source_variable\"\n        android:title=\"@string/set_source_variable\"\n        app:showAsAction=\"never\" />\n\n    <item\n        android:id=\"@+id/menu_edit_source\"\n        android:title=\"@string/edit_source\"\n        app:showAsAction=\"never\" />\n\n    <item\n        android:id=\"@+id/menu_switch_layout\"\n        android:title=\"@string/switchLayout\"\n        app:showAsAction=\"never\" />\n\n    <item\n        android:id=\"@+id/menu_read_record\"\n        android:title=\"@string/read_record\"\n        app:showAsAction=\"never\" />\n\n    <item\n        android:id=\"@+id/menu_clear\"\n        android:title=\"@string/clear\"\n        app:showAsAction=\"never\" />\n\n</menu>"
  },
  {
    "path": "app/src/main/res/menu/rss_favorites.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    tools:ignore=\"AlwaysShowAction\">\n\n    <item\n        android:id=\"@+id/menu_group\"\n        android:title=\"@string/group\"\n        android:icon=\"@drawable/ic_groups\"\n        app:showAsAction=\"always\">\n\n        <menu />\n\n    </item>\n    <item\n        android:id=\"@+id/menu_del_group\"\n        android:title=\"@string/delete_select_group\"\n        app:showAsAction=\"never\" />\n\n    <item\n        android:id=\"@+id/menu_del_all\"\n        android:title=\"@string/delete_all\"\n        app:showAsAction=\"never\" />\n\n</menu>\n"
  },
  {
    "path": "app/src/main/res/menu/rss_main_item.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n    <item\n        android:id=\"@+id/menu_top\"\n        android:title=\"@string/to_top\" />\n\n    <item\n        android:id=\"@+id/menu_edit\"\n        android:title=\"@string/edit\" />\n\n    <item\n        android:id=\"@+id/menu_disable\"\n        android:title=\"@string/disable_source\" />\n\n    <item\n        android:id=\"@+id/menu_del\"\n        android:title=\"@string/delete\" />\n\n</menu>"
  },
  {
    "path": "app/src/main/res/menu/rss_read.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\">\n    <item\n        android:id=\"@+id/menu_rss_refresh\"\n        android:title=\"@string/refresh\"\n        android:icon=\"@drawable/ic_refresh_black_24dp\"\n        app:showAsAction=\"always\" />\n\n    <item\n        android:id=\"@+id/menu_rss_star\"\n        android:title=\"@string/favorite\"\n        android:icon=\"@drawable/ic_star_border\"\n        app:showAsAction=\"always\" />\n\n    <item\n        android:id=\"@+id/menu_share_it\"\n        android:title=\"@string/share\"\n        android:icon=\"@drawable/ic_share\"\n        app:showAsAction=\"ifRoom\" />\n\n    <item\n        android:id=\"@+id/menu_aloud\"\n        android:title=\"@string/read_aloud\"\n        android:icon=\"@drawable/ic_volume_up\"\n        app:showAsAction=\"ifRoom\" />\n\n    <item\n        android:id=\"@+id/menu_login\"\n        android:title=\"@string/login\"\n        app:showAsAction=\"never\" />\n\n    <item\n        android:id=\"@+id/menu_browser_open\"\n        android:title=\"@string/open_in_browser\"\n        app:showAsAction=\"never\" />\n\n</menu>"
  },
  {
    "path": "app/src/main/res/menu/rss_read_record.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    tools:ignore=\"AlwaysShowAction\">\n\n    <item\n        android:id=\"@+id/menu_clear\"\n        android:title=\"@string/clear\"\n        app:showAsAction=\"always\" />\n\n</menu>"
  },
  {
    "path": "app/src/main/res/menu/rss_source.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\">\n\n    <item\n        android:id=\"@+id/menu_group\"\n        android:icon=\"@drawable/ic_groups\"\n        android:title=\"@string/menu_action_group\"\n        app:showAsAction=\"always\"\n        tools:ignore=\"AlwaysShowAction\">\n\n        <menu>\n\n            <item\n                android:id=\"@+id/menu_group_manage\"\n                android:title=\"@string/group_manage\" />\n\n            <item\n                android:id=\"@+id/menu_enabled_group\"\n                android:title=\"@string/enabled\" />\n\n            <item\n                android:id=\"@+id/menu_disabled_group\"\n                android:title=\"@string/disabled\" />\n\n            <item\n                android:id=\"@+id/menu_group_login\"\n                android:title=\"@string/need_login\" />\n\n            <item\n                android:id=\"@+id/menu_group_null\"\n                android:title=\"@string/no_group\" />\n\n            <group android:id=\"@+id/source_group\">\n\n            </group>\n\n        </menu>\n\n    </item>\n\n    <item\n        android:id=\"@+id/menu_add\"\n        android:icon=\"@drawable/ic_add\"\n        android:title=\"@string/add_rss_source\"\n        app:showAsAction=\"never\" />\n\n    <item\n        android:id=\"@+id/menu_import_local\"\n        android:icon=\"@drawable/ic_import\"\n        android:title=\"@string/import_local\"\n        app:showAsAction=\"never\" />\n\n    <item\n        android:id=\"@+id/menu_import_onLine\"\n        android:icon=\"@drawable/ic_import\"\n        android:title=\"@string/import_on_line\"\n        app:showAsAction=\"never\" />\n\n    <item\n        android:id=\"@+id/menu_import_qr\"\n        android:icon=\"@drawable/ic_import\"\n        android:title=\"@string/import_by_qr_code\"\n        app:showAsAction=\"never\" />\n\n    <item\n        android:id=\"@+id/menu_import_default\"\n        android:icon=\"@drawable/ic_import\"\n        android:title=\"@string/import_default_rule\"\n        app:showAsAction=\"never\" />\n\n    <item\n        android:id=\"@+id/menu_help\"\n        android:icon=\"@drawable/ic_help\"\n        android:title=\"@string/help\"\n        app:showAsAction=\"never\" />\n\n</menu>\n"
  },
  {
    "path": "app/src/main/res/menu/rss_source_debug.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    tools:ignore=\"AlwaysShowAction\">\n\n    <item\n        android:id=\"@+id/menu_list_src\"\n        android:title=\"@string/list_src\"\n        app:showAsAction=\"never\" />\n\n    <item\n        android:id=\"@+id/menu_content_src\"\n        android:title=\"@string/content_src\"\n        app:showAsAction=\"never\" />\n\n</menu>"
  },
  {
    "path": "app/src/main/res/menu/rss_source_item.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n    <item\n        android:id=\"@+id/menu_top\"\n        android:title=\"@string/to_top\" />\n\n    <item\n        android:id=\"@+id/menu_bottom\"\n        android:title=\"@string/to_bottom\" />\n\n    <item\n        android:id=\"@+id/menu_del\"\n        android:title=\"@string/delete\" />\n\n</menu>"
  },
  {
    "path": "app/src/main/res/menu/rss_source_sel.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\">\n\n    <item\n        android:id=\"@+id/menu_enable_selection\"\n        android:title=\"@string/enable_selection\"\n        app:showAsAction=\"never\"/>\n\n    <item\n        android:id=\"@+id/menu_disable_selection\"\n        android:title=\"@string/disable_selection\"\n        app:showAsAction=\"never\"/>\n\n    <item\n        android:id=\"@+id/menu_add_group\"\n        android:title=\"@string/add_group\"\n        app:showAsAction=\"never\"/>\n\n    <item\n        android:id=\"@+id/menu_remove_group\"\n        android:title=\"@string/remove_group\"\n        app:showAsAction=\"never\"/>\n\n    <item\n        android:id=\"@+id/menu_top_sel\"\n        android:title=\"@string/selection_to_top\"\n        app:showAsAction=\"never\"/>\n\n    <item\n        android:id=\"@+id/menu_bottom_sel\"\n        android:title=\"@string/selection_to_bottom\"\n        app:showAsAction=\"never\"/>\n\n    <item\n        android:id=\"@+id/menu_export_selection\"\n        android:title=\"@string/export_selection\"\n        app:showAsAction=\"never\" />\n\n    <item\n        android:id=\"@+id/menu_share_source\"\n        android:title=\"@string/share_selected_source\"\n        app:showAsAction=\"never\" />\n\n    <item\n        android:id=\"@+id/menu_check_selected_interval\"\n        android:title=\"@string/check_selected_interval\"\n        app:showAsAction=\"never\" />\n\n</menu>\n"
  },
  {
    "path": "app/src/main/res/menu/save.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu xmlns:tools=\"http://schemas.android.com/tools\"\n    xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    tools:ignore=\"AlwaysShowAction\">\n\n    <item\n        android:id=\"@+id/menu_save\"\n        android:icon=\"@drawable/ic_save\"\n        android:title=\"@string/action_save\"\n        app:showAsAction=\"always\" />\n\n</menu>"
  },
  {
    "path": "app/src/main/res/menu/search_view.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\">\n\n    <item\n        android:id=\"@+id/menu_search\"\n        android:title=\"@string/search\"\n        app:actionViewClass=\"androidx.appcompat.widget.SearchView\"\n        app:showAsAction=\"always\" />\n\n</menu>"
  },
  {
    "path": "app/src/main/res/menu/server_config.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\">\n\n    <item\n        android:id=\"@+id/menu_save\"\n        android:icon=\"@drawable/ic_save\"\n        android:title=\"@string/action_save\"\n        app:showAsAction=\"always\" />\n\n</menu>"
  },
  {
    "path": "app/src/main/res/menu/servers.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\">\n\n    <item\n        android:id=\"@+id/menu_add\"\n        android:icon=\"@drawable/ic_add\"\n        android:title=\"@string/create\"\n        app:showAsAction=\"always\" />\n\n</menu>"
  },
  {
    "path": "app/src/main/res/menu/source_edit.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    tools:ignore=\"AlwaysShowAction\">\n\n    <item\n        android:id=\"@+id/menu_save\"\n        android:icon=\"@drawable/ic_save\"\n        android:title=\"@string/action_save\"\n        app:showAsAction=\"always\" />\n\n    <item\n        android:id=\"@+id/menu_debug_source\"\n        android:icon=\"@drawable/ic_bug_report\"\n        android:title=\"@string/debug_source\"\n        app:showAsAction=\"always\" />\n\n    <item\n        android:id=\"@+id/menu_login\"\n        android:title=\"@string/login\"\n        app:showAsAction=\"never\" />\n\n    <item\n        android:id=\"@+id/menu_search\"\n        android:title=\"@string/search\"\n        app:showAsAction=\"never\" />\n\n    <item\n        android:id=\"@+id/menu_clear_cookie\"\n        android:title=\"@string/cookie\"\n        app:showAsAction=\"never\" />\n\n    <item\n        android:id=\"@+id/menu_auto_complete\"\n        android:checkable=\"true\"\n        android:title=\"@string/auto_complete\"\n        app:showAsAction=\"never\" />\n\n    <item\n        android:id=\"@+id/menu_copy_source\"\n        android:title=\"@string/copy_source\"\n        app:showAsAction=\"never\" />\n\n    <item\n        android:id=\"@+id/menu_paste_source\"\n        android:title=\"@string/paste_source\"\n        app:showAsAction=\"never\" />\n\n    <item\n        android:id=\"@+id/menu_set_source_variable\"\n        android:title=\"@string/set_source_variable\"\n        app:showAsAction=\"never\" />\n\n    <item\n        android:id=\"@+id/menu_qr_code_camera\"\n        android:title=\"@string/import_by_qr_code\"\n        app:showAsAction=\"never\" />\n\n    <item\n        android:id=\"@+id/menu_share_qr\"\n        android:title=\"@string/qr_share\"\n        app:showAsAction=\"never\" />\n\n    <item\n        android:id=\"@+id/menu_share_str\"\n        android:title=\"@string/str_share\"\n        app:showAsAction=\"never\" />\n\n    <item\n        android:id=\"@+id/menu_help\"\n        android:title=\"@string/help\"\n        app:showAsAction=\"never\" />\n\n</menu>"
  },
  {
    "path": "app/src/main/res/menu/source_login.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu xmlns:tools=\"http://schemas.android.com/tools\"\n    xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    tools:ignore=\"AlwaysShowAction\">\n\n    <item\n        android:id=\"@+id/menu_ok\"\n        android:icon=\"@drawable/ic_check\"\n        android:title=\"@string/ok\"\n        app:showAsAction=\"always\" />\n\n    <item\n        android:id=\"@+id/menu_show_login_header\"\n        android:title=\"@string/show_login_header\"\n        app:showAsAction=\"never\" />\n\n    <item\n        android:id=\"@+id/menu_del_login_header\"\n        android:title=\"@string/del_login_header\"\n        app:showAsAction=\"never\" />\n\n    <item\n        android:id=\"@+id/menu_log\"\n        android:title=\"@string/log\"\n        app:showAsAction=\"never\" />\n\n</menu>"
  },
  {
    "path": "app/src/main/res/menu/source_picker.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n    <item\n        android:id=\"@+id/menu_change_source_delay\"\n        android:title=\"@string/change_source_delay\" />\n\n</menu>\n"
  },
  {
    "path": "app/src/main/res/menu/source_sub_item.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n    <item\n        android:id=\"@+id/menu_del\"\n        android:title=\"@string/delete\" />\n\n</menu>"
  },
  {
    "path": "app/src/main/res/menu/source_subscription.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\">\n\n    <item\n        android:id=\"@+id/menu_add\"\n        android:icon=\"@drawable/ic_add\"\n        android:title=\"@string/add\"\n        app:showAsAction=\"always\" />\n\n</menu>"
  },
  {
    "path": "app/src/main/res/menu/source_webview_login.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu xmlns:tools=\"http://schemas.android.com/tools\"\n    xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    tools:ignore=\"AlwaysShowAction\">\n\n    <item\n        android:id=\"@+id/menu_ok\"\n        android:icon=\"@drawable/ic_check\"\n        android:title=\"@string/ok\"\n        app:showAsAction=\"always\" />\n\n</menu>"
  },
  {
    "path": "app/src/main/res/menu/speak_engine.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu xmlns:tools=\"http://schemas.android.com/tools\"\n    xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\">\n\n    <item\n        android:id=\"@+id/menu_add\"\n        android:icon=\"@drawable/ic_add\"\n        android:title=\"@string/add\"\n        app:showAsAction=\"always\"\n        tools:ignore=\"AlwaysShowAction\" />\n\n    <item\n        android:id=\"@+id/menu_default\"\n        android:title=\"@string/import_default_rule\"\n        app:showAsAction=\"never\" />\n\n    <item\n        android:id=\"@+id/menu_import_local\"\n        android:title=\"@string/import_local\"\n        app:showAsAction=\"never\" />\n\n    <item\n        android:id=\"@+id/menu_import_onLine\"\n        android:title=\"@string/import_on_line\"\n        app:showAsAction=\"never\" />\n\n    <item\n        android:id=\"@+id/menu_export\"\n        android:title=\"@string/export\"\n        app:showAsAction=\"never\" />\n\n</menu>"
  },
  {
    "path": "app/src/main/res/menu/speak_engine_edit.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    xmlns:tools=\"http://schemas.android.com/tools\"\n    tools:ignore=\"AlwaysShowAction\">\n\n    <item\n        android:id=\"@+id/menu_save\"\n        android:icon=\"@drawable/ic_save\"\n        android:title=\"@string/action_save\"\n        app:showAsAction=\"always\" />\n\n    <item\n        android:id=\"@+id/menu_login\"\n        android:title=\"@string/login\"\n        app:showAsAction=\"never\" />\n\n    <item\n        android:id=\"@+id/menu_show_login_header\"\n        android:title=\"@string/show_login_header\"\n        app:showAsAction=\"never\" />\n\n    <item\n        android:id=\"@+id/menu_del_login_header\"\n        android:title=\"@string/del_login_header\"\n        app:showAsAction=\"never\" />\n\n    <item\n        android:id=\"@+id/menu_copy_source\"\n        android:title=\"@string/copy_source\"\n        app:showAsAction=\"never\" />\n\n    <item\n        android:id=\"@+id/menu_paste_source\"\n        android:title=\"@string/paste_source\"\n        app:showAsAction=\"never\" />\n\n    <item\n        android:id=\"@+id/menu_log\"\n        android:title=\"@string/log\"\n        app:showAsAction=\"never\" />\n\n    <item\n        android:id=\"@+id/menu_help\"\n        android:title=\"@string/help\"\n        app:showAsAction=\"never\" />\n\n</menu>"
  },
  {
    "path": "app/src/main/res/menu/theme_config.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\">\n\n    <item\n        android:id=\"@+id/menu_theme_mode\"\n        android:title=\"@string/theme_mode\"\n        app:showAsAction=\"always\" />\n\n</menu>"
  },
  {
    "path": "app/src/main/res/menu/theme_list.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\">\n\n    <item\n        android:id=\"@+id/menu_import\"\n        android:title=\"剪贴板导入\"\n        app:showAsAction=\"always\" />\n\n</menu>"
  },
  {
    "path": "app/src/main/res/menu/txt_toc_rule.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu xmlns:tools=\"http://schemas.android.com/tools\"\n    xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\">\n\n    <item\n        android:id=\"@+id/menu_add\"\n        android:title=\"@string/add\"\n        android:icon=\"@drawable/ic_add\"\n        app:showAsAction=\"always\"\n        tools:ignore=\"AlwaysShowAction\" />\n\n    <item\n        android:id=\"@+id/menu_import_local\"\n        android:icon=\"@drawable/ic_import\"\n        android:title=\"@string/import_local\"\n        app:showAsAction=\"never\" />\n\n    <item\n        android:id=\"@+id/menu_import_onLine\"\n        android:title=\"@string/import_on_line\"\n        android:icon=\"@drawable/ic_import\"\n        app:showAsAction=\"never\" />\n\n    <item\n        android:id=\"@+id/menu_import_qr\"\n        android:icon=\"@drawable/ic_import\"\n        android:title=\"@string/import_by_qr_code\"\n        app:showAsAction=\"never\" />\n\n    <item\n        android:id=\"@+id/menu_import_default\"\n        android:title=\"@string/import_default_rule\"\n        android:icon=\"@drawable/ic_import\"\n        app:showAsAction=\"never\" />\n\n    <item\n        android:id=\"@+id/menu_help\"\n        android:icon=\"@drawable/ic_help\"\n        android:title=\"@string/help\"\n        app:showAsAction=\"never\" />\n\n</menu>"
  },
  {
    "path": "app/src/main/res/menu/txt_toc_rule_edit.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\">\n\n    <item\n        android:id=\"@+id/menu_save\"\n        android:title=\"@string/action_save\"\n        android:icon=\"@drawable/ic_save\"\n        app:showAsAction=\"always\" />\n\n    <item\n        android:id=\"@+id/menu_copy_rule\"\n        android:title=\"@string/copy_rule\" />\n\n    <item\n        android:id=\"@+id/menu_paste_rule\"\n        android:title=\"@string/paste_rule\" />\n\n</menu>"
  },
  {
    "path": "app/src/main/res/menu/txt_toc_rule_item.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu xmlns:android=\"http://schemas.android.com/apk/res/android\">\n\n    <item\n        android:id=\"@+id/menu_top\"\n        android:title=\"@string/to_top\" />\n\n    <item\n        android:id=\"@+id/menu_bottom\"\n        android:title=\"@string/to_bottom\" />\n\n    <item\n        android:id=\"@+id/menu_del\"\n        android:title=\"@string/delete\" />\n\n</menu>"
  },
  {
    "path": "app/src/main/res/menu/txt_toc_rule_sel.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\">\n\n    <item\n        android:id=\"@+id/menu_enable_selection\"\n        android:title=\"@string/enable_selection\"\n        app:showAsAction=\"never\" />\n\n    <item\n        android:id=\"@+id/menu_disable_selection\"\n        android:title=\"@string/disable_selection\"\n        app:showAsAction=\"never\" />\n\n    <item\n        android:id=\"@+id/menu_export_selection\"\n        android:title=\"@string/export_selection\"\n        app:showAsAction=\"never\" />\n\n</menu>\n"
  },
  {
    "path": "app/src/main/res/menu/verification_code.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu xmlns:tools=\"http://schemas.android.com/tools\"\n    xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    tools:ignore=\"AlwaysShowAction\">\n\n    <item\n        android:id=\"@+id/menu_ok\"\n        android:icon=\"@drawable/ic_check\"\n        android:title=\"@string/ok\"\n        app:showAsAction=\"always\" />\n\n    <item\n        android:id=\"@+id/menu_disable_source\"\n        android:title=\"@string/disable_source\"\n        app:showAsAction=\"never\" />\n\n    <item\n        android:id=\"@+id/menu_delete_source\"\n        android:title=\"@string/delete_source\"\n        app:showAsAction=\"never\" />\n\n</menu>"
  },
  {
    "path": "app/src/main/res/menu/web_view.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<menu xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\">\n\n    <item\n        android:id=\"@+id/menu_ok\"\n        android:icon=\"@drawable/ic_check\"\n        android:title=\"@string/ok\"\n        app:showAsAction=\"always\" />\n\n    <item\n        android:id=\"@+id/menu_open_in_browser\"\n        android:title=\"@string/open_in_browser\"\n        app:showAsAction=\"never\" />\n\n    <item\n        android:id=\"@+id/menu_copy_url\"\n        android:title=\"@string/copy_url\"\n        app:showAsAction=\"never\" />\n\n    <item\n        android:id=\"@+id/menu_full_screen\"\n        android:title=\"@string/full_screen\"\n        app:showAsAction=\"never\" />\n\n    <item\n        android:id=\"@+id/menu_disable_source\"\n        android:title=\"@string/disable_source\"\n        android:visible=\"false\"\n        app:showAsAction=\"never\" />\n\n    <item\n        android:id=\"@+id/menu_delete_source\"\n        android:title=\"@string/delete_source\"\n        android:visible=\"false\"\n        app:showAsAction=\"never\" />\n\n</menu>"
  },
  {
    "path": "app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<adaptive-icon xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <background android:drawable=\"@drawable/ic_launcher1_b\" />\n    <foreground android:drawable=\"@drawable/ic_launcher1\" />\n    <monochrome android:drawable=\"@drawable/ic_launcher4\" />\n</adaptive-icon>"
  },
  {
    "path": "app/src/main/res/mipmap-anydpi-v26/launcher1.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<adaptive-icon xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <background android:drawable=\"@color/md_yellow_600\" />\n    <foreground android:drawable=\"@drawable/ic_launcher2\" />\n    <monochrome android:drawable=\"@drawable/ic_launcher4\" />\n</adaptive-icon>"
  },
  {
    "path": "app/src/main/res/mipmap-anydpi-v26/launcher2.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<adaptive-icon xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <background android:drawable=\"@drawable/ic_launcher5_b\" />\n    <foreground android:drawable=\"@drawable/ic_launcher5\" />\n    <monochrome android:drawable=\"@drawable/ic_launcher5\" />\n</adaptive-icon>"
  },
  {
    "path": "app/src/main/res/mipmap-anydpi-v26/launcher3.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<adaptive-icon xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <background android:drawable=\"@color/md_grey_50\" />\n    <foreground android:drawable=\"@drawable/ic_launcher3\" />\n    <monochrome android:drawable=\"@drawable/ic_launcher3\" />\n</adaptive-icon>"
  },
  {
    "path": "app/src/main/res/mipmap-anydpi-v26/launcher4.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<adaptive-icon xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <background android:drawable=\"@drawable/ic_launcher4_b\" />\n    <foreground android:drawable=\"@drawable/ic_launcher4\" />\n    <monochrome android:drawable=\"@drawable/ic_launcher4\" />\n</adaptive-icon>"
  },
  {
    "path": "app/src/main/res/mipmap-anydpi-v26/launcher5.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<adaptive-icon xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <background android:drawable=\"@color/md_grey_50\" />\n    <foreground android:drawable=\"@drawable/ic_launcher6\" />\n    <monochrome android:drawable=\"@drawable/ic_launcher6\" />\n</adaptive-icon>"
  },
  {
    "path": "app/src/main/res/mipmap-anydpi-v26/launcher6.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<adaptive-icon xmlns:android=\"http://schemas.android.com/apk/res/android\">\n    <background android:drawable=\"@drawable/ic_launcher7_b\" />\n    <foreground android:drawable=\"@drawable/ic_launcher7\" />\n    <monochrome android:drawable=\"@drawable/ic_launcher7\" />\n</adaptive-icon>"
  },
  {
    "path": "app/src/main/res/values/array_values.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources xmlns:tools=\"http://schemas.android.com/tools\" tools:ignore=\"Typos\">\n\n    <string-array name=\"icons\">\n        <item>ic_launcher</item>\n        <item>launcher1</item>\n        <item>launcher2</item>\n        <item>launcher3</item>\n        <item>launcher4</item>\n        <item>launcher5</item>\n        <item>launcher6</item>\n    </string-array>\n\n    <string-array name=\"screen_time_out_value\">\n        <item>0</item>\n        <item>60</item>\n        <item>300</item>\n        <item>600</item>\n        <item>-1</item>\n    </string-array>\n\n    <string-array name=\"theme_mode_v\">\n        <item>0</item>\n        <item>1</item>\n        <item>2</item>\n        <item>3</item>\n    </string-array>\n\n    <string-array name=\"screen_direction_value\">\n        <item>0</item>\n        <item>1</item>\n        <item>2</item>\n        <item>3</item>\n        <item>4</item>\n    </string-array>\n\n    <string-array name=\"double_page_value\">\n        <item>0</item>\n        <item>1</item>\n        <item>2</item>\n        <item>3</item>\n    </string-array>\n\n    <string-array name=\"progress_bar_behavior_value\">\n        <item>page</item>\n        <item>chapter</item>\n    </string-array>\n\n    <string-array name=\"language_value\">\n        <item>auto</item>\n        <item>zh</item>\n        <item>tw</item>\n        <item>en</item>\n    </string-array>\n\n</resources>"
  },
  {
    "path": "app/src/main/res/values/arrays.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources xmlns:tools=\"http://schemas.android.com/tools\">\n\n\t<string-array name=\"server_type\">\n\t\t<item>WEBDAV</item>\n\t</string-array>\n\n\t<string-array name=\"book_type\">\n\t\t<item>Text</item>\n\t\t<item>Audio</item>\n\t\t<item>Image</item>\n\t\t<item>File</item>\n\t</string-array>\n\n\t<string-array name=\"group_style\">\n\t\t<item>Tab</item>\n\t\t<item>Folder</item>\n\t</string-array>\n\n\t<string-array name=\"book_sort\">\n\t\t<item>@string/text_default</item>\n\t\t<item>@string/bookshelf_px_0</item>\n\t\t<item>@string/bookshelf_px_1</item>\n\t\t<item>@string/bookshelf_px_2</item>\n\t\t<item>@string/bookshelf_px_3</item>\n\t\t<item>@string/bookshelf_px_4</item>\n\t\t<item>@string/bookshelf_px_5</item>\n\t</string-array>\n\n\t<string-array name=\"indent\">\n\t\t<item>@string/indent_0</item>\n\t\t<item>@string/indent_1</item>\n\t\t<item>@string/indent_2</item>\n\t\t<item>@string/indent_3</item>\n\t\t<item>@string/indent_4</item>\n\t</string-array>\n\n\t<string-array name=\"text_suffix\">\n\t\t<item>.txt</item>\n\t\t<item>.json</item>\n\t\t<item>.xml</item>\n\t</string-array>\n\n\t<string-array name=\"convert_s\">\n\t\t<item>@string/jf_convert_o</item>\n\t\t<item>@string/jf_convert_j</item>\n\t\t<item>@string/jf_convert_f</item>\n\t</string-array>\n\n\t<string-array name=\"theme_mode\">\n\t\t<item>Follow system</item>\n\t\t<item>Bright mode</item>\n\t\t<item>Dark mode</item>\n\t\t<item>E-Ink mode</item>\n\t</string-array>\n\n\t<string-array name=\"NavBarColors\">\n\t\t<item>Auto</item>\n\t\t<item>Black</item>\n\t\t<item>White</item>\n\t\t<item>Following</item>\n\t</string-array>\n\n\t<string-array name=\"screen_time_out\">\n\t\t<item>Default</item>\n\t\t<item>1 min</item>\n\t\t<item>5 min</item>\n\t\t<item>10 min</item>\n\t\t<item>Always</item>\n\t</string-array>\n\n\t<string-array name=\"screen_direction_title\">\n\t\t<item>@string/screen_unspecified</item>\n\t\t<item>@string/screen_portrait</item>\n\t\t<item>@string/screen_landscape</item>\n\t\t<item>@string/screen_sensor</item>\n\t\t<item>@string/screen_portrait_reversed</item>\n\t</string-array>\n\n\t<string-array name=\"double_page_title\">\n\t\t<item>Single page</item>\n\t\t<item>Double page</item>\n\t\t<item>Double page in landscape</item>\n\t\t<item>Double page on tablet or landscape</item>\n\t</string-array>\n\n\t<string-array name=\"progress_bar_behavior_title\">\n\t\t<item>@string/adjust_chapter_page</item>\n\t\t<item>@string/adjust_chapter_index</item>\n\t</string-array>\n\n\t<string-array name=\"icon_names\" tools:ignore=\"Typos\">\n\t\t<item>iconMain</item>\n\t\t<item>icon1</item>\n\t\t<item>icon2</item>\n\t\t<item>icon3</item>\n\t\t<item>icon4</item>\n\t\t<item>icon5</item>\n\t\t<item>icon6</item>\n\t</string-array>\n\n\t<string-array name=\"chinese_mode\">\n\t\t<item>Off</item>\n\t\t<item>Traditional to Simplified</item>\n\t\t<item>Simplified to Traditional</item>\n\t</string-array>\n\n\t<string-array name=\"system_typefaces\">\n\t\t<item>Default font</item>\n\t\t<item>Serif font</item>\n\t\t<item>Monospaced font</item>\n\t</string-array>\n\n\t<string-array name=\"read_tip\">\n\t\t<item>Blank</item>\n\t\t<item>Book name</item>\n\t\t<item>Title</item>\n\t\t<item>Time</item>\n\t\t<item>Battery</item>\n\t\t<item>Battery%</item>\n\t\t<item>Pages</item>\n\t\t<item>Progress(%)</item>\n\t\t<item>Progress(xx/yyy)</item>\n\t\t<item>Pages and progress</item>\n\t\t<item>Time and Battery</item>\n\t\t<item>Time and Battery%</item>\n\t</string-array>\n\n\t<string-array name=\"text_font_weight\">\n\t\t<item>Normal</item>\n\t\t<item>Bold</item>\n\t\t<item>Light</item>\n\t</string-array>\n\n\t<string-array name=\"language\">\n\t\t<item>Auto</item>\n\t\t<item>Simplified_Chinese</item>\n\t\t<item>Traditional_Chinese</item>\n\t\t<item>English</item>\n\t</string-array>\n\n\t<string-array name=\"rule_type\">\n\t\t<item>BookSource</item>\n\t\t<item>RssSource</item>\n\t\t<item>ReplaceRule</item>\n\t</string-array>\n\n\t<string-array name=\"default_home_page\">\n\t\t<item>@string/bookshelf</item>\n\t\t<item>@string/discovery</item>\n\t\t<item>@string/rss</item>\n\t\t<item>@string/my</item>\n\t</string-array>\n\n\n\t<string-array name=\"default_app_variant\">\n\t\t<item>@string/default_version</item>\n\t\t<item>@string/official_version</item>\n\t\t<item>@string/beta_release_version</item>\n\t\t<item>@string/beta_releaseA_version</item>\n\t</string-array>\n\n\t<string-array name=\"default_home_page_value\">\n\t\t<item>bookshelf</item>\n\t\t<item>explore</item>\n\t\t<item>rss</item>\n\t\t<item>my</item>\n\t</string-array>\n\n\t<string-array name=\"default_app_variant_value\">\n\t\t<item>default_version</item>\n\t\t<item>official_version</item>\n\t\t<item>beta_release_version</item>\n\t\t<item>beta_releaseA_version</item>\n\t</string-array>\n\n\t<string-array name=\"tip_color\">\n\t\t<item>Same color as content</item>\n\t\t<item>Customize</item>\n\t</string-array>\n\n\t<string-array name=\"tip_divider_color\">\n\t\t<item>Default</item>\n\t\t<item>Same color as content</item>\n\t\t<item>Customize</item>\n\t</string-array>\n\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values/attrs.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\n    <attr name=\"radius\" format=\"dimension\" />\n    <attr name=\"isBottomBackground\" format=\"boolean\" />\n    <attr name=\"themeMode\" format=\"enum\">\n        <enum name=\"dark\" value=\"1\" />\n        <enum name=\"light\" value=\"2\" />\n        <enum name=\"auto\" value=\"0\" />\n    </attr>\n\n    <declare-styleable name=\"TitleBar\">\n        <attr name=\"title\" />\n        <attr name=\"subtitle\" />\n        <attr name=\"titleTextAppearance\" />\n        <attr name=\"titleTextColor\" />\n        <attr name=\"subtitleTextAppearance\" />\n        <attr name=\"subtitleTextColor\" />\n        <attr name=\"contentInsetEnd\" />\n        <attr name=\"contentInsetEndWithActions\" />\n        <attr name=\"contentInsetStart\" />\n        <attr name=\"contentInsetStartWithNavigation\" />\n        <attr name=\"contentInsetLeft\" />\n        <attr name=\"contentInsetRight\" />\n        <attr name=\"contentLayout\" format=\"reference\" />\n        <attr name=\"attachToActivity\" format=\"boolean\" />\n        <attr name=\"displayHomeAsUp\" format=\"boolean\" />\n        <attr name=\"navigationIcon\" format=\"reference\" />\n        <attr name=\"fitStatusBar\" format=\"boolean\" />\n        <attr name=\"fitNavigationBar\" format=\"boolean\" />\n        <attr name=\"navigationContentDescription\" format=\"reference|string\" />\n        <attr name=\"navigationIconTint\" format=\"color|reference\" />\n        <attr name=\"themeMode\" />\n        <attr name=\"navigationIconTintMode\" format=\"enum\">\n            <enum name=\"clear\" value=\"0\" />\n            <enum name=\"src\" value=\"1\" />\n            <enum name=\"dst\" value=\"2\" />\n            <enum name=\"src_over\" value=\"3\" />\n            <enum name=\"dst_over\" value=\"4\" />\n            <enum name=\"src_in\" value=\"5\" />\n            <enum name=\"dst_in\" value=\"6\" />\n            <enum name=\"src_out\" value=\"7\" />\n            <enum name=\"dst_out\" value=\"8\" />\n            <enum name=\"src_atop\" value=\"9\" />\n            <enum name=\"dst_atop\" value=\"10\" />\n            <enum name=\"xor\" value=\"11\" />\n            <enum name=\"darken\" value=\"16\" />\n            <enum name=\"lighten\" value=\"17\" />\n            <enum name=\"multiply\" value=\"13\" />\n            <enum name=\"screen\" value=\"14\" />\n            <enum name=\"add\" value=\"12\" />\n            <enum name=\"overlay\" value=\"15\" />\n        </attr>\n    </declare-styleable>\n\n    <attr name=\"titleBarStyle\" format=\"reference\" />\n\n    <declare-styleable name=\"DynamicFrameLayout\">\n        <attr name=\"errorSrc\" format=\"reference\" />\n        <attr name=\"emptySrc\" format=\"reference\" />\n        <attr name=\"errorActionDescription\" format=\"string|reference\" />\n        <attr name=\"emptyActionDescription\" format=\"string|reference\" />\n        <attr name=\"emptyDescription\" format=\"string|reference\" />\n    </declare-styleable>\n\n    <declare-styleable name=\"RefreshProgressBar\">\n        <attr name=\"max_progress\" format=\"integer\" />\n        <attr name=\"dur_progress\" format=\"integer\" />\n        <attr name=\"second_dur_progress\" format=\"dimension\" />\n        <attr name=\"second_max_progress\" format=\"dimension\" />\n        <attr name=\"bg_color\" format=\"color\" />\n        <attr name=\"second_color\" format=\"color\" />\n        <attr name=\"font_color\" format=\"color\" />\n        <attr name=\"speed\" format=\"dimension\" />\n    </declare-styleable>\n\n    <declare-styleable name=\"DetailSeekBar\">\n        <attr name=\"title\" format=\"string\" />\n        <attr name=\"max\" format=\"integer\" />\n        <attr name=\"isBottomBackground\" />\n    </declare-styleable>\n\n    <declare-styleable name=\"SmoothCheckBox\">\n        <attr name=\"duration\" format=\"integer\" />\n        <attr name=\"stroke_width\" format=\"dimension\" />\n        <attr name=\"color_tick\" format=\"color\" />\n        <attr name=\"color_checked\" format=\"color\" />\n        <attr name=\"color_unchecked\" format=\"color\" />\n        <attr name=\"color_unchecked_stroke\" format=\"color\" />\n    </declare-styleable>\n\n    <declare-styleable name=\"NumberPickerPreference\">\n        <attr name=\"MinValue\" format=\"integer\" />\n        <attr name=\"MaxValue\" format=\"integer\" />\n        <attr name=\"android:summary\" />\n    </declare-styleable>\n\n    <declare-styleable name=\"RefreshLayout\">\n        <attr name=\"layout_refresh_empty\" format=\"reference\" />\n        <attr name=\"layout_refresh_error\" format=\"reference\" />\n        <attr name=\"layout_refresh_loading\" format=\"reference\" />\n    </declare-styleable>\n\n    <declare-styleable name=\"FastScroller\">\n        <attr name=\"fadeScrollbar\" format=\"boolean\" />\n        <attr name=\"showBubble\" format=\"boolean\" />\n        <attr name=\"showTrack\" format=\"boolean\" />\n        <attr name=\"trackColor\" format=\"color\" />\n        <attr name=\"handleColor\" format=\"color\" />\n        <attr name=\"bubbleColor\" format=\"color\" />\n        <attr name=\"bubbleTextColor\" format=\"color\" />\n    </declare-styleable>\n\n    <declare-styleable name=\"FilletImageView\">\n        <attr name=\"radius\" />\n        <attr name=\"left_top_radius\" format=\"dimension\" />\n        <attr name=\"right_top_radius\" format=\"dimension\" />\n        <attr name=\"right_bottom_radius\" format=\"dimension\" />\n        <attr name=\"left_bottom_radius\" format=\"dimension\" />\n    </declare-styleable>\n\n    <declare-styleable name=\"IconListPreference\">\n        <attr name=\"icons\" format=\"reference\" />\n    </declare-styleable>\n\n    <declare-styleable name=\"RotateLoading\">\n        <attr name=\"loading_width\" format=\"dimension\" />\n        <attr name=\"loading_color\" format=\"color\" />\n        <attr name=\"shadow_position\" format=\"integer\" />\n        <attr name=\"loading_speed\" format=\"integer\" />\n    </declare-styleable>\n\n    <declare-styleable name=\"CircleImageView\">\n        <attr name=\"civ_border_width\" format=\"dimension\" />\n        <attr name=\"civ_border_color\" format=\"color\" />\n        <attr name=\"civ_border_overlay\" format=\"boolean\" />\n        <attr name=\"civ_circle_background_color\" format=\"color\" />\n        <attr name=\"text\" format=\"string\" />\n        <attr name=\"textColor\" format=\"color\" />\n    </declare-styleable>\n\n    <declare-styleable name=\"Battery\">\n        <attr name=\"batteryOrientation\">\n            <enum name=\"horizontal\" value=\"0\" />\n            <enum name=\"vertical\" value=\"1\" />\n        </attr>\n        <attr name=\"batteryColor\" format=\"color\" />\n        <attr name=\"batteryPower\" format=\"integer\" />\n    </declare-styleable>\n\n    <declare-styleable name=\"VerticalSeekBar\">\n        <attr name=\"seekBarRotation\">\n            <!-- Clock wise - 90 deg; top = min, bottom = max -->\n            <enum name=\"CW90\" value=\"90\" />\n            <!-- Clock wise - 270 deg; bottom = min, top = max -->\n            <enum name=\"CW270\" value=\"270\" />\n        </attr>\n    </declare-styleable>\n\n    <declare-styleable name=\"AccentBgTextView\">\n        <attr name=\"radius\" />\n    </declare-styleable>\n\n    <declare-styleable name=\"AccentStrokeTextView\">\n        <attr name=\"radius\" />\n        <attr name=\"isBottomBackground\" />\n    </declare-styleable>\n\n    <declare-styleable name=\"StrokeTextView\">\n        <attr name=\"radius\" />\n        <attr name=\"isBottomBackground\" />\n    </declare-styleable>\n\n    <declare-styleable name=\"BadgeView\">\n        <attr name=\"radius\" />\n        <attr name=\"up_flat_angle\" format=\"boolean\" />\n    </declare-styleable>\n\n    <declare-styleable name=\"ArcView\">\n        <attr name=\"arcHeight\" format=\"dimension\" />\n        <attr name=\"bgColor\" format=\"color\" />\n        <attr name=\"arcDirectionTop\" format=\"boolean\" />\n    </declare-styleable>\n\n    <declare-styleable name=\"ShadowLayout\">\n        <attr name=\"shadowColor\" format=\"color\" />\n        <attr name=\"shadowRadius\" format=\"dimension\" />\n        <attr name=\"shadowDx\" format=\"dimension\" />\n        <attr name=\"shadowDy\" format=\"dimension\" />\n        <attr name=\"shadowShape\">\n            <flag name=\"rectangle\" value=\"0x0001\" />\n            <flag name=\"oval\" value=\"0x0010\" />\n        </attr>\n        <attr name=\"shadowSide\">\n            <flag name=\"all\" value=\"0x1111\" />\n            <flag name=\"left\" value=\"0x0001\" />\n            <flag name=\"top\" value=\"0x0010\" />\n            <flag name=\"right\" value=\"0x0100\" />\n            <flag name=\"bottom\" value=\"0x1000\" />\n        </attr>\n    </declare-styleable>\n\n    <declare-styleable name=\"ThemeRadioNoButton\">\n        <attr name=\"isBottomBackground\" />\n    </declare-styleable>\n\n    <declare-styleable name=\"Preference\">\n        <attr name=\"isBottomBackground\" />\n    </declare-styleable>\n\n    <!--斜角标签-->\n    <declare-styleable name=\"BevelLabelView\">\n        <!--背景颜色-->\n        <attr name=\"label_bg_color\" format=\"color|reference\" />\n        <!--文字-->\n        <attr name=\"label_text\" format=\"string\" />\n        <!-- 文字颜色-->\n        <attr name=\"label_text_color\" format=\"color|reference\" />\n        <!--文字大小-->\n        <attr name=\"label_text_size\" format=\"dimension\" />\n\n        <attr name=\"label_length\" format=\"dimension\" />\n        <!--   圆角-->\n        <attr name=\"label_corner\" format=\"dimension\" />\n\n        <attr name=\"label_mode\">\n            <!--   fill是沾满整个，-->\n            <enum name=\"left_top\" value=\"0\" />\n            <enum name=\"right_top\" value=\"1\" />\n            <enum name=\"left_bottom\" value=\"2\" />\n            <enum name=\"right_bottom\" value=\"3\" />\n            <enum name=\"left_top_fill\" value=\"4\" />\n            <enum name=\"right_top_fill\" value=\"5\" />\n            <enum name=\"left_bottom_fill\" value=\"6\" />\n            <enum name=\"right_bottom_fill\" value=\"7\" />\n        </attr>\n    </declare-styleable>\n</resources>"
  },
  {
    "path": "app/src/main/res/values/colors.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <color name=\"primary\">@color/md_light_blue_600</color>\n    <color name=\"primaryDark\">@color/md_light_blue_700</color>\n    <color name=\"accent\">@color/md_pink_800</color>\n\n    <color name=\"disabled\">@color/md_light_disabled</color>\n\n    <color name=\"divider\">#66666666</color>\n\n    <color name=\"lightBlue_color\">#FF578FCC</color>\n\n    <color name=\"error\">#eb4333</color>\n    <color name=\"success\">#439b53</color>\n\n    <color name=\"night_mask\">#00000000</color>\n\n    <color name=\"background\">@color/md_grey_50</color>\n    <color name=\"background_card\">@color/md_grey_100</color>\n    <color name=\"background_menu\">@color/md_grey_200</color>\n    <color name=\"background_prefs\">#7fffffff</color>\n\n    <color name=\"transparent\">#00000000</color>\n\n    <color name=\"transparent10\">#10000000</color>\n    <color name=\"transparent20\">#20000000</color>\n    <color name=\"transparent30\">#30000000</color>\n    <color name=\"transparent50\">#50000000</color>\n\n    <color name=\"highlight\">#d3321b</color>\n\n    <color name=\"btn_bg_press\">#63ACACAC</color>\n    <color name=\"btn_bg_press_2\">#63858585</color>\n    <color name=\"btn_bg_press_tp\">#632C2C2C</color>\n\n    <color name=\"tv_btn_normal_black\">#737373</color>\n    <color name=\"tv_btn_press_black\">#adadad</color>\n\n    <color name=\"primaryText\">#de000000</color>\n    <color name=\"secondaryText\">#8a000000</color>\n    <color name=\"tv_text_summary\">#8A2C2C2C</color>\n    <color name=\"tv_text_book_detail\">#dfdfdf</color>\n    <color name=\"menu_color_default\">#383838</color>\n\n\n    <color name=\"tv_text_button_nor\">#efefef</color>\n\n    <color name=\"light_translucent\">#23000000</color>\n    <color name=\"common_gray\">#EEEEEE</color>\n    <color name=\"darker_gray\">#aaaaaaaa</color>\n\n    <!-- 分格线背景色 -->\n    <color name=\"bg_divider_line\">#8fe0e0e0</color>\n    <!--statusBarBackground-->\n    <color name=\"status_bar_bag\">#19000000</color>\n    <color name=\"navigation_bar_bag\">#f4f4f4</color>\n\n    <!--base-color-->\n    <color name=\"translucent\">#99343434</color>\n    <color name=\"black\">#000000</color>\n    <color name=\"white\">#ffffff</color>\n\n    <!-- btn -->\n    <color name=\"btn_write\">#ffffffff</color>\n    <color name=\"btn_write_press\">#efe0e0e0</color>\n\n    <!--\"control\" means checkbox / radio button-->\n\n    <!--LIGHT-->\n    <!--12%-->\n    <color name=\"ate_switch_track_disabled_light\">#1F000000</color>\n    <color name=\"ate_button_disabled_light\">#1F000000</color>\n    <!-- 26% -->\n    <color name=\"ate_control_disabled_light\">#43000000</color>\n    <color name=\"ate_switch_track_normal_light\">#43000000</color>\n    <color name=\"ate_button_text_disabled_light\">#43000000</color>\n    <!-- 38% -->\n    <color name=\"ate_text_disabled_light\">#61000000</color>\n    <!-- 54% -->\n    <color name=\"ate_secondary_text_light\">#8A000000</color>\n    <color name=\"ate_icon_light\">#8A000000</color>\n    <color name=\"ate_control_normal_light\">#8A000000</color>\n    <!-- 87% -->\n    <color name=\"ate_primary_text_light\">#DE000000</color>\n    <!--Grey 50, #FAFAFA, Opacity  100%-->\n    <color name=\"ate_switch_thumb_normal_light\">#FFFAFAFA</color>\n    <!--Grey 400, #BDBDBD, Opacity  100%-->\n    <color name=\"ate_switch_thumb_disabled_light\">#FFBDBDBD</color>\n    <!---->\n    <color name=\"ate_navigation_drawer_selected_light\">#E8E8E8</color>\n\n    <!--DARK-->\n    <!--10%-->\n    <color name=\"ate_switch_track_disabled_dark\">#1AFFFFFF</color>\n    <!--12%-->\n    <color name=\"ate_button_disabled_dark\">#1F000000</color>\n    <!-- 30% -->\n    <color name=\"ate_control_disabled_dark\">#4DFFFFFF</color>\n    <color name=\"ate_switch_track_normal_dark\">#4DFFFFFF</color>\n    <color name=\"ate_button_text_disabled_dark\">#4DFFFFFF</color>\n    <color name=\"ate_text_disabled_dark\">#4DFFFFFF</color>\n    <!-- 70% -->\n    <color name=\"ate_secondary_text_dark\">#B3FFFFFF</color>\n    <color name=\"ate_icon_dark\">#B3FFFFFF</color>\n    <color name=\"ate_control_normal_dark\">#B3FFFFFF</color>\n    <!-- 100% -->\n    <color name=\"ate_primary_text_dark\">#FFFFFFFF</color>\n    <!--Grey 400, #BDBDBD, Opacity  100%-->\n    <color name=\"ate_switch_thumb_normal_dark\">#FFBDBDBD</color>\n    <!--Grey 800, #424242, Opacity 100%-->\n    <color name=\"ate_switch_thumb_disabled_dark\">#FF424242</color>\n    <!---->\n    <color name=\"ate_navigation_drawer_selected_dark\">#202020</color>\n\n    <color name=\"book_ant_10\">#141414</color>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values/colors_material_design.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\n    <!-- Google's Material Design colors from\n    https://www.google.com/design/spec/style/color.html#color-color-palette -->\n\n    <!-- Items Light -->\n    <color name=\"md_light_background\">@color/md_grey_50</color>\n    <!-- 12% -->\n    <color name=\"md_light_dividers\">#1F000000</color>\n    <!-- 38% -->\n    <color name=\"md_light_disabled\">#61000000</color>\n    <!-- 54% -->\n    <color name=\"md_light_secondary\">#8A000000</color>\n    <color name=\"md_light_primary_icon\">#8A000000</color>\n    <!-- 87% -->\n    <color name=\"md_light_primary_text\">#DE000000</color>\n    <!-- other specifications -->\n    <color name=\"md_light_statusbar\">@color/md_grey_300</color>\n    <color name=\"md_light_appbar\">@color/md_grey_100</color>\n    <color name=\"md_light_cards\">@color/md_white_1000</color>\n    <color name=\"md_light_dialogs\">@color/md_white_1000</color>\n\n    <!-- Items Dark -->\n    <color name=\"md_dark_background\">@color/md_grey_850</color>\n    <!-- 12% -->\n    <color name=\"md_dark_dividers\">#1FFFFFFF</color>\n    <!-- 30% -->\n    <color name=\"md_dark_disabled\">#4DFFFFFF</color>\n    <!-- 70% -->\n    <color name=\"md_dark_secondary\">#B3FFFFFF</color>\n    <color name=\"md_dark_primary_icon\">#B3FFFFFF</color>\n    <!-- 100% -->\n    <color name=\"md_dark_primary_text\">#FFFFFFFF</color>\n    <!-- other specifications -->\n    <color name=\"md_dark_statusbar\">@color/md_black_1000</color>\n    <color name=\"md_dark_appbar\">@color/md_grey_900</color>\n    <color name=\"md_dark_cards\">@color/md_grey_800</color>\n    <color name=\"md_dark_dialogs\">@color/md_grey_800</color>\n\n    <!-- Red -->\n    <color name=\"md_red_50\">#FFEBEE</color>\n    <color name=\"md_red_100\">#FFCDD2</color>\n    <color name=\"md_red_200\">#EF9A9A</color>\n    <color name=\"md_red_300\">#E57373</color>\n    <color name=\"md_red_400\">#EF5350</color>\n    <color name=\"md_red_500\">#F44336</color>\n    <color name=\"md_red_600\">#E53935</color>\n    <color name=\"md_red_700\">#D32F2F</color>\n    <color name=\"md_red_800\">#C62828</color>\n    <color name=\"md_red_900\">#B71C1C</color>\n    <color name=\"md_red_A100\">#FF8A80</color>\n    <color name=\"md_red_A200\">#FF5252</color>\n    <color name=\"md_red_A400\">#FF1744</color>\n    <color name=\"md_red_A700\">#D50000</color>\n\n    <!-- Pink -->\n    <color name=\"md_pink_50\">#FCE4EC</color>\n    <color name=\"md_pink_100\">#F8BBD0</color>\n    <color name=\"md_pink_200\">#F48FB1</color>\n    <color name=\"md_pink_300\">#F06292</color>\n    <color name=\"md_pink_400\">#EC407A</color>\n    <color name=\"md_pink_500\">#E91E63</color>\n    <color name=\"md_pink_600\">#D81B60</color>\n    <color name=\"md_pink_700\">#C2185B</color>\n    <color name=\"md_pink_800\">#AD1457</color>\n    <color name=\"md_pink_900\">#880E4F</color>\n    <color name=\"md_pink_A100\">#FF80AB</color>\n    <color name=\"md_pink_A200\">#FF4081</color>\n    <color name=\"md_pink_A400\">#F50057</color>\n    <color name=\"md_pink_A700\">#C51162</color>\n\n    <!-- Purple -->\n    <color name=\"md_purple_50\">#F3E5F5</color>\n    <color name=\"md_purple_100\">#E1BEE7</color>\n    <color name=\"md_purple_200\">#CE93D8</color>\n    <color name=\"md_purple_300\">#BA68C8</color>\n    <color name=\"md_purple_400\">#AB47BC</color>\n    <color name=\"md_purple_500\">#9C27B0</color>\n    <color name=\"md_purple_600\">#8E24AA</color>\n    <color name=\"md_purple_700\">#7B1FA2</color>\n    <color name=\"md_purple_800\">#6A1B9A</color>\n    <color name=\"md_purple_900\">#4A148C</color>\n    <color name=\"md_purple_A100\">#EA80FC</color>\n    <color name=\"md_purple_A200\">#E040FB</color>\n    <color name=\"md_purple_A400\">#D500F9</color>\n    <color name=\"md_purple_A700\">#AA00FF</color>\n\n    <!-- Deep Purple -->\n    <color name=\"md_deep_purple_50\">#EDE7F6</color>\n    <color name=\"md_deep_purple_100\">#D1C4E9</color>\n    <color name=\"md_deep_purple_200\">#B39DDB</color>\n    <color name=\"md_deep_purple_300\">#9575CD</color>\n    <color name=\"md_deep_purple_400\">#7E57C2</color>\n    <color name=\"md_deep_purple_500\">#673AB7</color>\n    <color name=\"md_deep_purple_600\">#5E35B1</color>\n    <color name=\"md_deep_purple_700\">#512DA8</color>\n    <color name=\"md_deep_purple_800\">#4527A0</color>\n    <color name=\"md_deep_purple_900\">#311B92</color>\n    <color name=\"md_deep_purple_A100\">#B388FF</color>\n    <color name=\"md_deep_purple_A200\">#7C4DFF</color>\n    <color name=\"md_deep_purple_A400\">#651FFF</color>\n    <color name=\"md_deep_purple_A700\">#6200EA</color>\n\n    <!-- Indigo -->\n    <color name=\"md_indigo_50\">#E8EAF6</color>\n    <color name=\"md_indigo_100\">#C5CAE9</color>\n    <color name=\"md_indigo_200\">#9FA8DA</color>\n    <color name=\"md_indigo_300\">#7986CB</color>\n    <color name=\"md_indigo_400\">#5C6BC0</color>\n    <color name=\"md_indigo_500\">#3F51B5</color>\n    <color name=\"md_indigo_600\">#3949AB</color>\n    <color name=\"md_indigo_700\">#303F9F</color>\n    <color name=\"md_indigo_800\">#283593</color>\n    <color name=\"md_indigo_900\">#1A237E</color>\n    <color name=\"md_indigo_A100\">#8C9EFF</color>\n    <color name=\"md_indigo_A200\">#536DFE</color>\n    <color name=\"md_indigo_A400\">#3D5AFE</color>\n    <color name=\"md_indigo_A700\">#304FFE</color>\n\n    <!-- Blue -->\n    <color name=\"md_blue_50\">#E3F2FD</color>\n    <color name=\"md_blue_100\">#BBDEFB</color>\n    <color name=\"md_blue_200\">#90CAF9</color>\n    <color name=\"md_blue_300\">#64B5F6</color>\n    <color name=\"md_blue_400\">#42A5F5</color>\n    <color name=\"md_blue_500\">#2196F3</color>\n    <color name=\"md_blue_600\">#1E88E5</color>\n    <color name=\"md_blue_700\">#1976D2</color>\n    <color name=\"md_blue_800\">#1565C0</color>\n    <color name=\"md_blue_900\">#0D47A1</color>\n    <color name=\"md_blue_A100\">#82B1FF</color>\n    <color name=\"md_blue_A200\">#448AFF</color>\n    <color name=\"md_blue_A400\">#2979FF</color>\n    <color name=\"md_blue_A700\">#2962FF</color>\n\n    <!-- Light Blue -->\n    <color name=\"md_light_blue_50\">#E1F5FE</color>\n    <color name=\"md_light_blue_100\">#B3E5FC</color>\n    <color name=\"md_light_blue_200\">#81D4FA</color>\n    <color name=\"md_light_blue_300\">#4FC3F7</color>\n    <color name=\"md_light_blue_400\">#29B6F6</color>\n    <color name=\"md_light_blue_500\">#03A9F4</color>\n    <color name=\"md_light_blue_600\">#039BE5</color>\n    <color name=\"md_light_blue_700\">#0288D1</color>\n    <color name=\"md_light_blue_800\">#0277BD</color>\n    <color name=\"md_light_blue_900\">#01579B</color>\n    <color name=\"md_light_blue_A100\">#80D8FF</color>\n    <color name=\"md_light_blue_A200\">#40C4FF</color>\n    <color name=\"md_light_blue_A400\">#00B0FF</color>\n    <color name=\"md_light_blue_A700\">#0091EA</color>\n\n    <!-- Cyan -->\n    <color name=\"md_cyan_50\">#E0F7FA</color>\n    <color name=\"md_cyan_100\">#B2EBF2</color>\n    <color name=\"md_cyan_200\">#80DEEA</color>\n    <color name=\"md_cyan_300\">#4DD0E1</color>\n    <color name=\"md_cyan_400\">#26C6DA</color>\n    <color name=\"md_cyan_500\">#00BCD4</color>\n    <color name=\"md_cyan_600\">#00ACC1</color>\n    <color name=\"md_cyan_700\">#0097A7</color>\n    <color name=\"md_cyan_800\">#00838F</color>\n    <color name=\"md_cyan_900\">#006064</color>\n    <color name=\"md_cyan_A100\">#84FFFF</color>\n    <color name=\"md_cyan_A200\">#18FFFF</color>\n    <color name=\"md_cyan_A400\">#00E5FF</color>\n    <color name=\"md_cyan_A700\">#00B8D4</color>\n\n    <!-- Teal -->\n    <color name=\"md_teal_50\">#E0F2F1</color>\n    <color name=\"md_teal_100\">#B2DFDB</color>\n    <color name=\"md_teal_200\">#80CBC4</color>\n    <color name=\"md_teal_300\">#4DB6AC</color>\n    <color name=\"md_teal_400\">#26A69A</color>\n    <color name=\"md_teal_500\">#009688</color>\n    <color name=\"md_teal_600\">#00897B</color>\n    <color name=\"md_teal_700\">#00796B</color>\n    <color name=\"md_teal_800\">#00695C</color>\n    <color name=\"md_teal_900\">#004D40</color>\n    <color name=\"md_teal_A100\">#A7FFEB</color>\n    <color name=\"md_teal_A200\">#64FFDA</color>\n    <color name=\"md_teal_A400\">#1DE9B6</color>\n    <color name=\"md_teal_A700\">#00BFA5</color>\n\n    <!-- Green -->\n    <color name=\"md_green_50\">#E8F5E9</color>\n    <color name=\"md_green_100\">#C8E6C9</color>\n    <color name=\"md_green_200\">#A5D6A7</color>\n    <color name=\"md_green_300\">#81C784</color>\n    <color name=\"md_green_400\">#66BB6A</color>\n    <color name=\"md_green_500\">#4CAF50</color>\n    <color name=\"md_green_600\">#43A047</color>\n    <color name=\"md_green_700\">#388E3C</color>\n    <color name=\"md_green_800\">#2E7D32</color>\n    <color name=\"md_green_900\">#1B5E20</color>\n    <color name=\"md_green_A100\">#B9F6CA</color>\n    <color name=\"md_green_A200\">#69F0AE</color>\n    <color name=\"md_green_A400\">#00E676</color>\n    <color name=\"md_green_A700\">#00C853</color>\n\n    <!-- Light Green -->\n    <color name=\"md_light_green_50\">#F1F8E9</color>\n    <color name=\"md_light_green_100\">#DCEDC8</color>\n    <color name=\"md_light_green_200\">#C5E1A5</color>\n    <color name=\"md_light_green_300\">#AED581</color>\n    <color name=\"md_light_green_400\">#9CCC65</color>\n    <color name=\"md_light_green_500\">#8BC34A</color>\n    <color name=\"md_light_green_600\">#7CB342</color>\n    <color name=\"md_light_green_700\">#689F38</color>\n    <color name=\"md_light_green_800\">#558B2F</color>\n    <color name=\"md_light_green_900\">#33691E</color>\n    <color name=\"md_light_green_A100\">#CCFF90</color>\n    <color name=\"md_light_green_A200\">#B2FF59</color>\n    <color name=\"md_light_green_A400\">#76FF03</color>\n    <color name=\"md_light_green_A700\">#64DD17</color>\n\n    <!-- Lime -->\n    <color name=\"md_lime_50\">#F9FBE7</color>\n    <color name=\"md_lime_100\">#F0F4C3</color>\n    <color name=\"md_lime_200\">#E6EE9C</color>\n    <color name=\"md_lime_300\">#DCE775</color>\n    <color name=\"md_lime_400\">#D4E157</color>\n    <color name=\"md_lime_500\">#CDDC39</color>\n    <color name=\"md_lime_600\">#C0CA33</color>\n    <color name=\"md_lime_700\">#AFB42B</color>\n    <color name=\"md_lime_800\">#9E9D24</color>\n    <color name=\"md_lime_900\">#827717</color>\n    <color name=\"md_lime_A100\">#F4FF81</color>\n    <color name=\"md_lime_A200\">#EEFF41</color>\n    <color name=\"md_lime_A400\">#C6FF00</color>\n    <color name=\"md_lime_A700\">#AEEA00</color>\n\n    <!-- Yellow -->\n    <color name=\"md_yellow_50\">#FFFDE7</color>\n    <color name=\"md_yellow_100\">#FFF9C4</color>\n    <color name=\"md_yellow_200\">#FFF59D</color>\n    <color name=\"md_yellow_300\">#FFF176</color>\n    <color name=\"md_yellow_400\">#FFEE58</color>\n    <color name=\"md_yellow_500\">#FFEB3B</color>\n    <color name=\"md_yellow_600\">#FDD835</color>\n    <color name=\"md_yellow_700\">#FBC02D</color>\n    <color name=\"md_yellow_800\">#F9A825</color>\n    <color name=\"md_yellow_900\">#F57F17</color>\n    <color name=\"md_yellow_A100\">#FFFF8D</color>\n    <color name=\"md_yellow_A200\">#FFFF00</color>\n    <color name=\"md_yellow_A400\">#FFEA00</color>\n    <color name=\"md_yellow_A700\">#FFD600</color>\n\n    <!-- Amber -->\n    <color name=\"md_amber_50\">#FFF8E1</color>\n    <color name=\"md_amber_100\">#FFECB3</color>\n    <color name=\"md_amber_200\">#FFE082</color>\n    <color name=\"md_amber_300\">#FFD54F</color>\n    <color name=\"md_amber_400\">#FFCA28</color>\n    <color name=\"md_amber_500\">#FFC107</color>\n    <color name=\"md_amber_600\">#FFB300</color>\n    <color name=\"md_amber_700\">#FFA000</color>\n    <color name=\"md_amber_800\">#FF8F00</color>\n    <color name=\"md_amber_900\">#FF6F00</color>\n    <color name=\"md_amber_A100\">#FFE57F</color>\n    <color name=\"md_amber_A200\">#FFD740</color>\n    <color name=\"md_amber_A400\">#FFC400</color>\n    <color name=\"md_amber_A700\">#FFAB00</color>\n\n    <!-- Orange -->\n    <color name=\"md_orange_50\">#FFF3E0</color>\n    <color name=\"md_orange_100\">#FFE0B2</color>\n    <color name=\"md_orange_200\">#FFCC80</color>\n    <color name=\"md_orange_300\">#FFB74D</color>\n    <color name=\"md_orange_400\">#FFA726</color>\n    <color name=\"md_orange_500\">#FF9800</color>\n    <color name=\"md_orange_600\">#FB8C00</color>\n    <color name=\"md_orange_700\">#F57C00</color>\n    <color name=\"md_orange_800\">#EF6C00</color>\n    <color name=\"md_orange_900\">#E65100</color>\n    <color name=\"md_orange_A100\">#FFD180</color>\n    <color name=\"md_orange_A200\">#FFAB40</color>\n    <color name=\"md_orange_A400\">#FF9100</color>\n    <color name=\"md_orange_A700\">#FF6D00</color>\n\n    <!-- Deep Orange -->\n    <color name=\"md_deep_orange_50\">#FBE9E7</color>\n    <color name=\"md_deep_orange_100\">#FFCCBC</color>\n    <color name=\"md_deep_orange_200\">#FFAB91</color>\n    <color name=\"md_deep_orange_300\">#FF8A65</color>\n    <color name=\"md_deep_orange_400\">#FF7043</color>\n    <color name=\"md_deep_orange_500\">#FF5722</color>\n    <color name=\"md_deep_orange_600\">#F4511E</color>\n    <color name=\"md_deep_orange_700\">#E64A19</color>\n    <color name=\"md_deep_orange_800\">#D84315</color>\n    <color name=\"md_deep_orange_900\">#BF360C</color>\n    <color name=\"md_deep_orange_A100\">#FF9E80</color>\n    <color name=\"md_deep_orange_A200\">#FF6E40</color>\n    <color name=\"md_deep_orange_A400\">#FF3D00</color>\n    <color name=\"md_deep_orange_A700\">#DD2C00</color>\n\n    <!-- Brown -->\n    <color name=\"md_brown_50\">#EFEBE9</color>\n    <color name=\"md_brown_100\">#D7CCC8</color>\n    <color name=\"md_brown_200\">#BCAAA4</color>\n    <color name=\"md_brown_300\">#A1887F</color>\n    <color name=\"md_brown_400\">#8D6E63</color>\n    <color name=\"md_brown_500\">#795548</color>\n    <color name=\"md_brown_600\">#6D4C41</color>\n    <color name=\"md_brown_700\">#5D4037</color>\n    <color name=\"md_brown_800\">#4E342E</color>\n    <color name=\"md_brown_900\">#3E2723</color>\n\n    <!-- Grey -->\n    <color name=\"md_grey_50\">#FAFAFA</color>\n    <color name=\"md_grey_100\">#F5F5F5</color>\n    <color name=\"md_grey_200\">#EEEEEE</color>\n    <color name=\"md_grey_300\">#E0E0E0</color>\n    <color name=\"md_grey_400\">#BDBDBD</color>\n    <color name=\"md_grey_500\">#9E9E9E</color>\n    <color name=\"md_grey_600\">#757575</color>\n    <color name=\"md_grey_700\">#616161</color>\n    <color name=\"md_grey_800\">#424242</color>\n    <color name=\"md_grey_850\">#303030</color>\n    <color name=\"md_grey_900\">#212121</color>\n\n    <!-- Blue Grey -->\n    <color name=\"md_blue_grey_50\">#ECEFF1</color>\n    <color name=\"md_blue_grey_100\">#CFD8DC</color>\n    <color name=\"md_blue_grey_200\">#B0BEC5</color>\n    <color name=\"md_blue_grey_300\">#90A4AE</color>\n    <color name=\"md_blue_grey_400\">#78909C</color>\n    <color name=\"md_blue_grey_500\">#607D8B</color>\n    <color name=\"md_blue_grey_600\">#546E7A</color>\n    <color name=\"md_blue_grey_700\">#455A64</color>\n    <color name=\"md_blue_grey_800\">#37474F</color>\n    <color name=\"md_blue_grey_900\">#263238</color>\n\n    <!-- Black & White -->\n    <color name=\"md_black_1000\">#000000</color>\n    <color name=\"md_white_1000\">#FFFFFF</color>\n\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values/dimens.xml",
    "content": "<resources>\n    <!-- Default screen margins, per the Android Design guidelines. -->\n    <dimen name=\"activity_horizontal_margin\">16dp</dimen>\n    <dimen name=\"activity_vertical_margin\">16dp</dimen>\n    <dimen name=\"nav_header_vertical_spacing\">8dp</dimen>\n    <dimen name=\"nav_header_height\">176dp</dimen>\n    <dimen name=\"fab_margin\">16dp</dimen>\n\n    <dimen name=\"font_size_normal\">14sp</dimen>\n    <dimen name=\"font_size_middle\">16sp</dimen>\n    <dimen name=\"font_size_large\">18sp</dimen>\n\n    <dimen name=\"fast_scroll_min_thumb_height\">24dp</dimen>\n\n    <dimen name=\"line_height\">0.8dp</dimen>\n    <dimen name=\"shadow_height\">10dp</dimen>\n\n    <dimen name=\"desc_icon_size\">18sp</dimen>\n\n    <dimen name=\"toolbar_elevation\">4dp</dimen>\n\n    <dimen name=\"fastscroll_bubble_radius\">44dp</dimen>\n    <dimen name=\"fastscroll_bubble_size\">88dp</dimen>\n    <dimen name=\"fastscroll_bubble_textsize\">48sp</dimen>\n    <dimen name=\"fastscroll_bubble_padding\">16dp</dimen>\n\n    <dimen name=\"fastscroll_handle_height\">40dp</dimen>\n    <dimen name=\"fastscroll_handle_width\">8dp</dimen>\n    <dimen name=\"fastscroll_handle_radius\">0dp</dimen>\n\n    <dimen name=\"fastscroll_track_width\">2dp</dimen>\n\n    <dimen name=\"fastscroll_scrollbar_margin_top\">8dp</dimen>\n    <dimen name=\"fastscroll_scrollbar_margin_bottom\">8dp</dimen>\n    <dimen name=\"fastscroll_scrollbar_padding_start\">8dp</dimen>\n    <dimen name=\"fastscroll_scrollbar_padding_end\">8dp</dimen>\n\n    <dimen name=\"fixed_height_bottom_padding\">8dp</dimen>\n</resources>"
  },
  {
    "path": "app/src/main/res/values/ids.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\n    <item name=\"tag\" type=\"id\" />\n    <item name=\"tag1\" type=\"id\" />\n    <item name=\"tag2\" type=\"id\" />\n\n    <item name=\"menu_group_1\" type=\"id\" />\n    <item name=\"menu_group_2\" type=\"id\" />\n\n    <item name=\"menu_1\" type=\"id\" />\n\n    <item name=\"fast_scroller\" type=\"id\" />\n\n    <item name=\"bettermovementmethod_highlight_background_span\" type=\"id\" />\n\n</resources>"
  },
  {
    "path": "app/src/main/res/values/non_translat.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources xmlns:tools=\"http://schemas.android.com/tools\" tools:ignore=\"MissingTranslation\">\n    <string name=\"pk_bookshelf_px\" translatable=\"false\">bookshelf_px</string>\n    <string name=\"legado_gzh\" translatable=\"false\">开源阅读</string>\n    <string name=\"email\" translatable=\"false\">gekunfei@live.com</string>\n\n    <string name=\"contributors_url\" translatable=\"false\">https://github.com/gedoor/legado/graphs/contributors</string>\n\n    <string name=\"http_ip\" translatable=\"false\">http://%1$s:%2$d</string>\n    <string name=\"git_hub\" translatable=\"false\">GitHub</string>\n    <string name=\"diy_edit_source_group_title\" translatable=\"false\">【%s】</string>\n    <string name=\"vip_title\" translatable=\"false\">🔒%s</string>\n    <string name=\"payed_title\" translatable=\"false\">🔓%s</string>\n\n    <string name=\"separator\" translatable=\"false\">丨</string>\n\n</resources>"
  },
  {
    "path": "app/src/main/res/values/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <!--App-->\n    <string name=\"app_name\">Legado</string>\n    <string name=\"app_name_a\">Legado·A</string>\n    <string name=\"receiving_shared_label\">Legado·search</string>\n    <string name=\"tip_perm_request_storage\">Legado needs storage access to find and read books. please go \"App Settings\" to allow \"Storage permission\".</string>\n\n    <!--Other-->\n    <string name=\"menu_backup\">Home</string>\n    <string name=\"menu_restore\">Restore</string>\n    <string name=\"menu_import_old\">Import Legado data</string>\n    <string name=\"webdav_cache_backup\">Offline cache book backup</string>\n    <string name=\"webdav_cache_backup_s\">Export to local and back up to the exports directory under the legado folder</string>\n    <string name=\"backup_path\">Backup to</string>\n    <string name=\"select_backup_path\">Please select a backup path.</string>\n    <string name=\"menu_import_old_version\">Import legacy data</string>\n    <string name=\"menu_import_github\">Import github data</string>\n    <string name=\"menu_replace_rule\">Replacement</string>\n    <string name=\"menu_send\">Send</string>\n\n    <string name=\"dialog_title\">Prompt</string>\n    <string name=\"dialog_cancel\">Cancel</string>\n    <string name=\"dialog_confirm\">Confirm</string>\n    <string name=\"dialog_setting\">Go to Settings</string>\n    <string name=\"tip_cannot_jump_setting_page\">Cannot jump to Settings.</string>\n\n    <string name=\"manual_input\">Manual Input</string>\n    <string name=\"enter_directory_path\">Enter directory path</string>\n    <string name=\"invalid_directory\">Invalid directory path</string>\n    <string name=\"empty_directory_input\">Directory path cannot be empty</string>\n\n    <string name=\"dynamic_click_retry\">Retry</string>\n    <string name=\"dynamic_loading\">Loading</string>\n    <string name=\"draw\">Warning</string>\n    <string name=\"edit\">Edit</string>\n    <string name=\"delete\">Delete</string>\n    <string name=\"delete_select_group\">Delete Select Group</string>\n    <string name=\"delete_all\">Delete all</string>\n    <string name=\"custom_export_section\">Custom export chapter of epub</string>\n    <string name=\"replace\">Replace</string>\n    <string name=\"replace_purify\">Replacement</string>\n    <string name=\"replace_purify_desc\">Configure replacement rules</string>\n    <string name=\"not_available\">Not available now</string>\n    <string name=\"enable\">Enable</string>\n    <string name=\"replace_purify_search\">Search Replacement</string>\n    <string name=\"bookshelf\">Bookshelf</string>\n    <string name=\"favorites\">Favorites</string>\n    <string name=\"favorite\">Favorite</string>\n    <string name=\"in_favorites\">in Favorites</string>\n    <string name=\"out_favorites\">Not in Favorites</string>\n    <string name=\"rss\">RSS feeds</string>\n    <string name=\"all\">All</string>\n    <string name=\"recent_reading\">Recent reading</string>\n    <string name=\"last_read\">Last reading</string>\n    <string name=\"update_log\">What\\'s new</string>\n    <string name=\"bookshelf_empty\">The bookshelf is still empty. Search for books or add them from discovery!</string>\n    <string name=\"action_search\">Search</string>\n    <string name=\"action_download\">Download</string>\n    <string name=\"layout_list\">List</string>\n    <string name=\"layout_grid3\">Grid-3</string>\n    <string name=\"layout_grid4\">Grid-4</string>\n    <string name=\"layout_grid5\">Grid-5</string>\n    <string name=\"layout_grid6\">Grid-6</string>\n    <string name=\"bookshelf_layout\">Layout</string>\n    <string name=\"view\">View</string>\n    <string name=\"book_library\">Library</string>\n    <string name=\"book_local\">Import books</string>\n    <string name=\"book_source\">Book sources</string>\n    <string name=\"book_source_manage\">Sources management</string>\n    <string name=\"book_source_manage_desc\">Create/Import/Edit/Manage Book sources</string>\n    <string name=\"setting\">Settings</string>\n    <string name=\"theme_setting\">Theme settings</string>\n    <string name=\"theme_setting_s\">Some settings related to interface or color</string>\n    <string name=\"other_setting\">Other settings</string>\n    <string name=\"other_setting_s\">Some function-related settings</string>\n    <string name=\"about\">About</string>\n    <string name=\"donate\">Donations</string>\n    <string name=\"exit\">Exit</string>\n    <string name=\"exit_no_save\">It has not been saved. Do you want to continue editing?</string>\n    <string name=\"read_style\">Book styles</string>\n    <string name=\"version\">Version</string>\n    <string name=\"local\">Local</string>\n    <string name=\"search\">Search</string>\n    <string name=\"origin_format\">Origin: %s</string>\n    <string name=\"read_dur_progress\">Origin: %s</string>\n    <string name=\"book_name\">Title</string>\n    <string name=\"lasted_show\">Latest: %s</string>\n    <string name=\"check_add_bookshelf\">Would you like to add %s to your Bookshelf?</string>\n    <string name=\"import_books_count\">%s text file(s) in total</string>\n    <string name=\"is_loading\">Loading…</string>\n    <string name=\"retry\">Retry</string>\n    <string name=\"web_service\">Web service</string>\n    <string name=\"web_service_desc\">Web edit source and read book</string>\n    <string name=\"web_edit_source\">Edit book sources on the web</string>\n    <string name=\"offline_cache\">Offline cache</string>\n    <string name=\"offline_cache_t\">Offline cache</string>\n    <string name=\"offline_cache_s\">cache the selected chapter(s) to Storage</string>\n    <string name=\"change_origin\">Change Origin</string>\n    <string name=\"about_description\">\n        \\u3000\\u3000 This is an open source reading software newly developed by Kotlin, welcome to join us.\n    </string>\n    <string name=\"app_share_description\">\n        Legado (YueDu 3.0) download link：\\n https://github.com/gedoor/legado/releases\n    </string>\n    <string name=\"version_name\">Version %s</string>\n    <string name=\"pt_background_verification\">Background-verification</string>\n    <string name=\"ps_background_verification\">you can operate freely when verifying the book source</string>\n    <string name=\"pt_auto_refresh\">Auto-refresh</string>\n    <string name=\"ps_auto_refresh\">Update books automatically when opening the software</string>\n    <string name=\"pt_auto_download\">Auto-download</string>\n    <string name=\"ps_auto_download\">Download the latest chapters automatically  when updating books</string>\n    <string name=\"backup_restore\">Backup and restore</string>\n    <string name=\"web_dav_set\">WebDav settings</string>\n    <string name=\"web_dav_set_import_old\">WebDav settings/Import legacy data</string>\n    <string name=\"backup\">Backup</string>\n    <string name=\"restore\">Restore</string>\n    <string name=\"backup_permission\">Backup needs storage permission</string>\n    <string name=\"restore_permission\">Restore needs storage permission</string>\n    <string name=\"ok\">OK</string>\n    <string name=\"cancel\">Cancel</string>\n    <string name=\"backup_confirmation\">Backup confirmation</string>\n    <string name=\"backup_message\">The new backup files will replace the original.\\n Backup folder: YueDu</string>\n    <string name=\"restore_confirmation\">Restore confirmation</string>\n    <string name=\"restore_message\">Restoring the bookshelf data will overwrite the current Bookshelf.</string>\n    <string name=\"backup_success\">Backup succeed</string>\n    <string name=\"backup_fail\">Backup failed\\n%s</string>\n    <string name=\"on_restore\">Restoring</string>\n    <string name=\"restore_success\">Restore succeed</string>\n    <string name=\"restore_fail\">Backup failed</string>\n    <string name=\"screen_direction\">Screen orientation</string>\n    <string name=\"screen_sensor\">Auto(sensor)</string>\n    <string name=\"screen_landscape\">Landscape</string>\n    <string name=\"screen_portrait\">Portrait</string>\n    <string name=\"screen_unspecified\">Follow system</string>\n    <string name=\"disclaimer\">Disclaimer</string>\n    <string name=\"all_chapter_num\">%d chapters</string>\n    <string name=\"interface_setting\">Interface</string>\n    <string name=\"brightness\">Brightness</string>\n    <string name=\"chapter_list\">Chapters</string>\n    <string name=\"next_chapter\">Next</string>\n    <string name=\"previous_chapter\">Prior</string>\n    <string name=\"pt_hide_status_bar\">Hide status bar</string>\n    <string name=\"ps_hide_status_bar\">Hide system navigation bar when reading</string>\n    <string name=\"read_aloud\">Speech</string>\n    <string name=\"read_aloud_t\">Speaking</string>\n    <string name=\"read_aloud_s\">Click to open the Reading</string>\n    <string name=\"audio_play\">Play</string>\n    <string name=\"audio_play_t\">Playing</string>\n    <string name=\"audio_play_s\">Click to open the Playing</string>\n    <string name=\"audio_pause\">Pause</string>\n    <string name=\"text_return\">Back</string>\n    <string name=\"refresh\">Refresh</string>\n    <string name=\"start\">Start</string>\n    <string name=\"stop\">Stop</string>\n    <string name=\"pause\">Pause</string>\n    <string name=\"resume\">Resume</string>\n    <string name=\"set_timer\">Timer</string>\n    <string name=\"read_aloud_pause\">Speak Paused</string>\n    <string name=\"read_aloud_timer\">Speaking(%d min left)</string>\n    <string name=\"playing_timer\">Playing(%d min left)</string>\n    <string name=\"ps_hide_navigation_bar\">Hide virtual buttons when reading</string>\n    <string name=\"pt_hide_navigation_bar\">Hide navigation bar</string>\n    <string name=\"re_navigation_bar_color\">Navigation bar color</string>\n    <string name=\"scoring\">Rating</string>\n    <string name=\"send_mail\">Email</string>\n    <string name=\"can_not_open\">Open failed</string>\n    <string name=\"can_not_share\">Share failed</string>\n    <string name=\"no_chapter\">No chapters</string>\n    <string name=\"add_url\">Add url</string>\n    <string name=\"add_book_url\">Add book url</string>\n    <string name=\"background\">Background</string>\n    <string name=\"author\">Author</string>\n    <string name=\"author_show\">Author: %s</string>\n    <string name=\"aloud_stop\">Speak Stopped</string>\n    <string name=\"clear_cache\">Clear cache</string>\n    <string name=\"clear_cache_success\">Cache cleared</string>\n    <string name=\"action_save\">Save</string>\n    <string name=\"edit_source\">Edit source</string>\n    <string name=\"edit_book_source\">Edit Book source</string>\n    <string name=\"disable_book_source\">Disable Book source</string>\n    <string name=\"add_book_source\">Add Book source</string>\n    <string name=\"add_rss_source\">Add RSS source</string>\n    <string name=\"book_file_selector\">Add books</string>\n    <string name=\"scan_book_source\">Scan</string>\n    <string name=\"copy_source\">Copy source</string>\n    <string name=\"paste_source\">Paste source</string>\n    <string name=\"source_rule_s\">Source rules description</string>\n    <string name=\"check_update\">Check for Updates</string>\n    <string name=\"camera_scan\">Scan QR code</string>\n    <string name=\"scan_image\">Scan local images</string>\n    <string name=\"rule_summary\">Rules description</string>\n    <string name=\"share\">Share</string>\n    <string name=\"share_app\">Share to</string>\n    <string name=\"flow_sys\">Follow system</string>\n    <string name=\"add\">Add</string>\n    <string name=\"import_book_source\">Import book sources</string>\n    <string name=\"import_local\">Import local</string>\n    <string name=\"import_on_line\">Import online</string>\n    <string name=\"replace_rule_title\">Replacement</string>\n    <string name=\"replace_rule_edit\">Edit replacement rule</string>\n    <string name=\"replace_rule\">Pattern</string>\n    <string name=\"replace_to\">Replacement</string>\n    <string name=\"img_cover\">Cover</string>\n    <string name=\"book\">Book</string>\n    <string name=\"volume_key_page\">Volume keys to turn page</string>\n    <string name=\"mouse_wheel_page\">Mouse wheel to turn page</string>\n    <string name=\"click_turn_page\">Tap screen to turn page</string>\n    <string name=\"page_anim\">Flip animation</string>\n    <string name=\"book_page_anim\">Flip animation (book)</string>\n    <string name=\"disable_manga_page_anim\">Disable flip animation</string>\n    <string name=\"keep_light\">Keep screen awake</string>\n    <string name=\"back\">Back</string>\n    <string name=\"menu\">Menu</string>\n    <string name=\"adjust\">Adjust</string>\n    <string name=\"scroll_bar\">Scroll bar</string>\n    <string name=\"clear_all_content\">Clearing the cache will delete all saved chapters. Are you sure to delete it?</string>\n    <string name=\"book_source_share_url\">Book sources sharing</string>\n    <string name=\"replace_rule_summary\">Rule name</string>\n    <string name=\"replace_rule_invalid\">Pattern rule is empty or does not conform the regex specification.</string>\n    <string name=\"select_action\">Selection action</string>\n    <string name=\"select_all\">Select all</string>\n    <string name=\"select_all_count\">Select all(%1$d/%2$d)</string>\n    <string name=\"select_cancel_count\">Cancel select all(%1$d/%2$d)</string>\n    <string name=\"dark_theme\">Dark mode</string>\n    <string name=\"welcome\">Welcome page</string>\n    <string name=\"download_start\">Download start</string>\n    <string name=\"download_cancel\">Download cancel</string>\n    <string name=\"no_download\">No download</string>\n    <string name=\"download_count\">Downloaded %1$d/%2$d</string>\n    <string name=\"import_select_book\">Import selected book(s)</string>\n    <string name=\"threads_num_title\">Number of concurrent tasks</string>\n    <string name=\"change_icon\">Change icon</string>\n    <string name=\"remove_from_bookshelf\">Remove</string>\n    <string name=\"start_read\">Start reading</string>\n    <string name=\"data_loading\">Loading…</string>\n    <string name=\"load_error_retry\">Load failed, tap to retry</string>\n    <string name=\"book_intro\">Book description</string>\n    <string name=\"intro_show\">Description:%s</string>\n    <string name=\"intro_show_null\">Description: no introduction</string>\n    <string name=\"open_from_other\">Open external book</string>\n    <string name=\"origin_show\">Origin: %s</string>\n    <string name=\"import_replace_rule\">Import replace rules</string>\n    <string name=\"import_replace_rule_on_line\">Import online rules</string>\n    <string name=\"check_update_interval\">Check interval for updates</string>\n    <string name=\"bookshelf_px_0\">By recent list</string>\n    <string name=\"bookshelf_px_1\">By update time</string>\n    <string name=\"bookshelf_px_2\">By book title</string>\n    <string name=\"bookshelf_px_3\">By sort manually</string>\n    <string name=\"read_type\">Reading strategy</string>\n    <string name=\"compose_type\">Typesetting</string>\n    <string name=\"del_select\">Delete selected</string>\n    <string name=\"del_msg\">Are you sure to delete?</string>\n    <string name=\"clear_font\">Default font</string>\n    <string name=\"find_on_www\">Discovery</string>\n    <string name=\"find_source_manage\">Discovery</string>\n    <string name=\"find_empty\">No content.Go to Sources management to add it!</string>\n    <string name=\"del_all\">Delete all</string>\n    <string name=\"searchHistory\">Search history</string>\n    <string name=\"clear\">Clear</string>\n    <string name=\"showTitle\">Display book title on text</string>\n    <string name=\"refresh_default\">Book Sources sync</string>\n    <string name=\"no_last_chapter\">No latest chapter.</string>\n    <string name=\"showTimeBattery\">Display time and battery</string>\n    <string name=\"showLine\">Display divider</string>\n    <string name=\"dark_status_icon\">Darken the status bar\\'s icon color</string>\n    <string name=\"content\">Content</string>\n    <string name=\"copy_text\">Copy</string>\n    <string name=\"download_all\">Download all</string>\n    <string name=\"content_sl\">This is a test text, \\n\\u3000\\u3000 just to show you the effect</string>\n    <string name=\"text_bg_style\">Color and background (long tap to customize)</string>\n    <string name=\"immersion_status_bar\">Immersive status bar</string>\n    <string name=\"un_download\">%d chapter(s) left</string>\n    <string name=\"long_click_input_color\">Long tap to input color value</string>\n    <string name=\"loading\">Loading…</string>\n    <string name=\"group_zg\">Awaiting</string>\n    <string name=\"group_yf\">Awaiting more</string>\n    <string name=\"bookmark\">Bookmarks</string>\n    <string name=\"bookmark_add\">Add to Bookmarks</string>\n    <string name=\"action_del\">Delete</string>\n    <string name=\"load_over_time\">Loading timeout</string>\n    <string name=\"join_group\">Follow:%s</string>\n    <string name=\"copy_complete\">Copied successfully</string>\n    <string name=\"bookshelf_management\">Bookshelf management</string>\n    <string name=\"clear_bookshelf_s\">It will delete all books. Be careful,please.</string>\n    <string name=\"search_book_source\">Search book sources</string>\n    <string name=\"search_rss_source\">Search RSS sources</string>\n    <string name=\"search_book_source_num\">Search( %d sources in total)</string>\n    <string name=\"chapter_list_size\">Chapters(%d)</string>\n    <string name=\"text_bold\">Bold</string>\n    <string name=\"text_font\">Font</string>\n    <string name=\"text\">Text</string>\n    <string name=\"home_page\">Home page</string>\n    <string name=\"right\">Right</string>\n    <string name=\"left\">Left</string>\n    <string name=\"bottom\">Bottom</string>\n    <string name=\"top\">Top</string>\n    <string name=\"padding\">Padding</string>\n    <string name=\"padding_top\">Top</string>\n    <string name=\"padding_bottom\">Bottom</string>\n    <string name=\"padding_left\">Left</string>\n    <string name=\"padding_right\">Right</string>\n    <string name=\"check_book_source\">Check book sources</string>\n    <string name=\"check_select_source\">Check the selected source</string>\n    <string name=\"progress_show\">%1$s      Progress %2$d/%3$d</string>\n    <string name=\"tts_fix\">Please install and select Chinese TTS!</string>\n    <string name=\"tts_init_failed\">TTS initialization failed!</string>\n    <string name=\"jf_convert\">Simplified conversion</string>\n    <string name=\"jf_convert_o\">Off</string>\n    <string name=\"jf_convert_f\">Simplified to traditional</string>\n    <string name=\"jf_convert_j\">Traditional to simplified</string>\n    <string name=\"page_mode\">Flipping mode</string>\n    <string name=\"nb_file_sub_count\">%1$d items</string>\n    <string name=\"nb_file_path\">Storage:</string>\n    <string name=\"nb_file_add_shelf\">Add to Bookshelf</string>\n    <string name=\"nb_file_add_shelves\">Add to Bookshelf(%1$d)</string>\n    <string name=\"nb_file_add_succeed\">%1$d books added successfully</string>\n    <string name=\"fonts_folder\">Please put the font files in the Fonts folder of the storage root directory and reselect</string>\n    <string name=\"default_font\">Default font</string>\n    <string name=\"select_font\">Select fonts</string>\n    <string name=\"text_size\">Text size</string>\n    <string name=\"line_size\">Line spacing</string>\n    <string name=\"paragraph_size\">Paragraph spacing</string>\n    <string name=\"to_top\">To Top</string>\n    <string name=\"selection_to_top\">Selection To Top</string>\n    <string name=\"to_bottom\">To Bottom</string>\n    <string name=\"selection_to_bottom\">Selection To Bottom</string>\n    <string name=\"auto_expand_find\">Auto expand Discovery</string>\n    <string name=\"default_expand_first\">Default expand the first Discovery.</string>\n    <string name=\"threads_num\">Current threads %s</string>\n    <string name=\"read_aloud_speed\">Speech rate</string>\n    <string name=\"auto_next_page\">Auto scroll</string>\n    <string name=\"auto_next_page_stop\">Stop Auto scroll</string>\n    <string name=\"auto_next_page_speed\">Auto scroll speed</string>\n    <string name=\"book_info\">Book information</string>\n    <string name=\"book_info_edit\">Edit book information</string>\n    <string name=\"ps_default_read\">Use Bookshelf as start page</string>\n    <string name=\"pt_default_read\">Auto jump to Recent list</string>\n    <string name=\"replace_scope\">Replacement object. Book name or source url is available</string>\n    <string name=\"menu_action_group\">Groups</string>\n    <string name=\"download_path\">Cache path</string>\n    <string name=\"sys_file_picker\">System file picker</string>\n    <string name=\"new_version\">New version</string>\n    <string name=\"download_update\">Download updates</string>\n    <string name=\"volume_key_page_on_play\">Volume keys to turn page when reading</string>\n    <string name=\"tip_margin_change\">Margin adjustment</string>\n    <string name=\"allow_update\">Enable update</string>\n    <string name=\"disable_update\">Disable update</string>\n    <string name=\"split_long_chapter\">Split extra-long chapters</string>\n    <string name=\"need_more_time_load_content\">When the body length is too long, it may take more time to load the text.</string>\n    <string name=\"revert_selection\">Inverse</string>\n    <string name=\"search_book_key\">Search book name/author</string>\n    <string name=\"debug_hint\">Book name,Author,URL</string>\n    <string name=\"faq\">FAQ</string>\n    <string name=\"pt_show_all_find\">Display all Discovery</string>\n    <string name=\"ps_show_all_find\">Display the selected origin\\'s Discovery if closed</string>\n    <string name=\"update_toc\">Update chapters</string>\n    <string name=\"txt_toc_rule\">Txt Catalog Rule</string>\n    <string name=\"select_section_export\">Choose some chapters to be exported</string>\n    <string name=\"set_charset\">Text encoding</string>\n    <string name=\"swap_sort\">Ascending/Descending order</string>\n    <string name=\"sort\">Sort</string>\n    <string name=\"sort_auto\">Sort automatically</string>\n    <string name=\"sort_manual\">Sort manually</string>\n    <string name=\"sort_default\">Sort default</string>\n    <string name=\"sort_by_name\">Sort by name</string>\n    <string name=\"go_to_top\">Scroll to the top</string>\n    <string name=\"go_to_bottom\">Scroll to the bottom</string>\n    <string name=\"read_y\">Read: %s</string>\n    <string name=\"pursue_more\">Awaiting update</string>\n    <string name=\"fattening\">Awaiting more</string>\n    <string name=\"finish\">Finished</string>\n    <string name=\"all_book\">All</string>\n    <string name=\"pursue_more_book\">Awaiting update books</string>\n    <string name=\"fattening_book\">Awaiting more chapters books</string>\n    <string name=\"finish_book\">Finished books</string>\n    <string name=\"local_book\">Local books</string>\n    <string name=\"status_bar_immersion\">The status bar color becomes transparent</string>\n    <string name=\"imm_navigation_bar\">immersion navigation bar</string>\n    <string name=\"imm_navigation_bar_s\">The navigation bar becomes transparent</string>\n    <string name=\"add_to_bookshelf\">Add to Bookshelf</string>\n    <string name=\"continue_read\">Continue reading</string>\n    <string name=\"cover_path\">Cover path</string>\n    <string name=\"page_anim_cover\">Cover</string>\n    <string name=\"page_anim_slide\">Slide</string>\n    <string name=\"page_anim_simulation\">Simulation</string>\n    <string name=\"page_anim_scroll\">Scroll</string>\n    <string name=\"page_anim_none\">None</string>\n    <string name=\"up_change_source_last_chapter_t\">Update the latest chapter after changed origin in the background</string>\n    <string name=\"up_change_source_last_chapter_s\">if enabled,the update will start 1 minute later when the software is run</string>\n    <string name=\"behavior_main_t\">Auto hide ToolBar</string>\n    <string name=\"behavior_main_s\">The toolbar will be hidden automatically when scroll the Bookshelf</string>\n    <string name=\"login\">Login</string>\n    <string name=\"login_source\">Login%s</string>\n    <string name=\"success\">Success</string>\n    <string name=\"source_no_login\">The current source has not configured with a login address</string>\n    <string name=\"no_prev_page\">No prior page</string>\n    <string name=\"no_next_page\">No next page</string>\n\n    <!-- source start-->\n    <string name=\"source_name\">Source Name(sourceName)</string>\n    <string name=\"source_url\">Source url(sourceUrl)</string>\n    <string name=\"source_group\">Source group(sourceGroup)</string>\n    <string name=\"diy_source_group\">Custom source grouping</string>\n    <string name=\"diy_edit_source_group\">Enter the custom source group name</string>\n    <string name=\"concurrent_rate\">Concurrency rate(concurrentRate)</string>\n    <string name=\"sort_url\">Sort url(sortUrl)</string>\n    <string name=\"login_url\">Log in url(loginUrl)</string>\n    <string name=\"login_ui\">Login UI(loginUi)</string>\n    <string name=\"login_check_js\">Login check js(loginCheckJs)</string>\n    <string name=\"comment\">Source comment(sourceComment)</string>\n    <string name=\"r_search_url\">Search url(url)</string>\n    <string name=\"r_find_url\">Discover url(url)</string>\n    <string name=\"r_book_list\">Book list(bookList)</string>\n    <string name=\"r_book_name\">Book title(name)</string>\n    <string name=\"r_book_url\">Details page url(bookUrl)</string>\n    <string name=\"r_author\">Author(author)</string>\n    <string name=\"rule_book_kind\">Classification(kind)</string>\n    <string name=\"rule_book_intro\">Intro(intro)</string>\n    <string name=\"rule_cover_url\">CoverUrl(coverUrl)</string>\n    <string name=\"rule_last_chapter\">Last chapter(lastChapter)</string>\n    <string name=\"rule_word_count\">Word count(wordCount)</string>\n    <string name=\"book_url_pattern\">Book url pattern(bookUrlPattern)</string>\n    <string name=\"rule_book_info_init\">Book info init(bookInfoInit)</string>\n    <string name=\"rule_toc_url\">TOC url(tocUrl)</string>\n    <string name=\"rule_can_re_name\">Can re name(canReName)</string>\n    <string name=\"rule_next_toc_url\">Next toc url(nextTocUrl)</string>\n    <string name=\"rule_chapter_list\">Chapter list(chapterList)</string>\n    <string name=\"rule_chapter_name\">Chapter name(chapterName)</string>\n    <string name=\"rule_chapter_url\">Chapter url(chapterUrl)</string>\n    <string name=\"rule_is_volume\">Is volume(isVolume)</string>\n    <string name=\"rule_is_vip\">Is vip(isVip)</string>\n    <string name=\"rule_update_time\">Chapter info(ChapterInfo)</string>\n    <string name=\"pre_update_js\">Js before update(preUpdateJs)</string>\n    <string name=\"rule_book_content\">Content(content)</string>\n    <string name=\"rule_next_content\">Next content url(nextContentUrl)</string>\n    <string name=\"rule_web_js\">Web view js(webJs)</string>\n    <string name=\"rule_source_regex\">Source regex(sourceRegex)</string>\n    <string name=\"rule_replace_regex\">Replace regex(replaceRegex)</string>\n    <string name=\"rule_image_style\">Image style(imageStyle)</string>\n    <string name=\"rule_pay_action\">Pay action(payAction)</string>\n\n    <string name=\"source_icon\">Source icon(sourceIcon)</string>\n    <string name=\"r_articles\">Rule articles(ruleArticles)</string>\n    <string name=\"r_next\">Rule next articles(ruleNextArticles)</string>\n    <string name=\"r_title\">Rule title(ruleTitle)</string>\n    <string name=\"r_guid\">Rule guid(ruleGuid)</string>\n    <string name=\"r_date\">Rule pub date(rulePubDate)</string>\n    <string name=\"r_categories\">Rule categories(ruleCategories)</string>\n    <string name=\"r_description\">Rule description(ruleDescription)</string>\n    <string name=\"r_image\">Rule image(ruleImage)</string>\n    <string name=\"r_content\">Rule content(ruleContent)</string>\n    <string name=\"r_style\">Style(style)</string>\n    <string name=\"r_link\">Rule link(ruleLink)</string>\n    <string name=\"r_inject_js\">Inject js(injectJs)</string>\n    <string name=\"check_key_word\">Check key word(checkKeyWord)</string>\n    <string name=\"rule_actions\">Actions(actions)</string>\n    <string name=\"rule_is_pay\">Is pay(isPay)</string>\n    <!-- source end-->\n\n    <!--error string start-->\n    <string name=\"error_no_source\">No source</string>\n    <string name=\"error_get_book_info\">Failed to obtain book information</string>\n    <string name=\"error_get_content\">Failed to obtain content</string>\n    <string name=\"error_get_chapter_list\">Failed to obtain chapters list</string>\n    <string name=\"error_get_web_content\">Failed to access website:%s</string>\n    <string name=\"error_read_file\">Failed to read file</string>\n    <string name=\"error_load_toc\">Failed to load chapters list</string>\n    <string name=\"error_get_data\">Failed to get data</string>\n    <string name=\"error_load_msg\">Failed to load\\n%s</string>\n    <string name=\"net_error_10001\">No network</string>\n    <string name=\"net_error_10002\">Network connection timeout</string>\n    <string name=\"net_error_10003\">Data parsing failed</string>\n    <!--error string end-->\n\n    <string name=\"source_http_header\">HTTP Header</string>\n    <string name=\"debug_source\">Debug source</string>\n    <string name=\"import_by_qr_code\">Import from QR code</string>\n    <string name=\"share_selected_source\">Share selected sources</string>\n    <string name=\"scan_qr_code\">Scan QR code</string>\n    <string name=\"click_on_selected_show_menu\">Tap to display Menu when selected </string>\n    <string name=\"theme\">Theme</string>\n    <string name=\"theme_mode\">Theme mode</string>\n    <string name=\"theme_mode_desc\">Select a theme you want</string>\n    <string name=\"join_qq_group\">Join QQ group</string>\n    <string name=\"bg_image_per\">Set the background image requires storage permission</string>\n    <string name=\"input_book_source_url\">Input book source address</string>\n    <string name=\"del_file\">Delete file</string>\n    <string name=\"del_file_success\">Deleted file</string>\n    <string name=\"sure_del_file\">Are you sure to delete this file?</string>\n    <string name=\"files_tree\">Directory</string>\n    <string name=\"intelligent_import\">Intelligent import</string>\n    <string name=\"discovery\">Discovery</string>\n    <string name=\"switch_display_style\">Switch display styles</string>\n    <string name=\"import_per\">Import local books requires storage permission</string>\n    <string name=\"night_theme\">Night Theme</string>\n    <string name=\"eink_theme\">E-Ink</string>\n    <string name=\"eink_theme_desc\">Optimization for E-ink devices</string>\n    <string name=\"get_storage_per\">requires storage permission</string>\n    <string name=\"double_click_exit\">Tap again to exit the program</string>\n    <string name=\"import_book_per\">Import local books requires storage permission</string>\n    <string name=\"network_connection_unavailable\">Network connection is not available</string>\n    <string name=\"yes\">Yes</string>\n    <string name=\"no\">No</string>\n    <string name=\"sure\">OK</string>\n    <string name=\"sure_del\">Are you sure to delete it?</string>\n    <string name=\"sure_del_any\">Are you sure to delete %s?</string>\n    <string name=\"sure_del_all_book\">Are you sure to delete all books？</string>\n    <string name=\"sure_del_download_book\">Do you want to delete the downloaded book chapters at the same time?</string>\n    <string name=\"qr_per\">Scan QR code requires Camera permissions</string>\n    <string name=\"aloud_can_not_auto_page\">Speech is running, cannot turn pages automatically</string>\n    <string name=\"input_charset\">Input encoding</string>\n    <string name=\"text_chapter_list_rule\">Txt Chapters Regex</string>\n    <string name=\"open_local_book_per\">Open local books requires storage permission</string>\n    <string name=\"no_book_name\">No bookName</string>\n    <string name=\"input_replace_url\">Input replacement rule URL</string>\n    <string name=\"get_book_list_success\">Search list obtained successfully%d</string>\n    <string name=\"non_null_name_url\">name and URL cannot be empty</string>\n    <string name=\"gallery\">Gallery</string>\n    <string name=\"get_ali_pay_hb\">get AliPay red envelopes</string>\n    <string name=\"non_update_url\">No update address</string>\n    <string name=\"check_host_cookie\">Opening the homepage, it will return to start page automatically after success</string>\n    <string name=\"click_check_after_success\">After successful login, please tap the icon on the upper right corner to test the homepage access</string>\n    <string name=\"chapter\">Chapter</string>\n    <string name=\"to\">To</string>\n    <string name=\"use_regex\">Using Regex</string>\n    <string name=\"text_indent\">Indent</string>\n    <string name=\"indent_0\">None</string>\n    <string name=\"indent_1\">Indent with 1 chars</string>\n    <string name=\"indent_2\">Indent with 2 chars</string>\n    <string name=\"indent_3\">Indent with 3 chars</string>\n    <string name=\"indent_4\">Indent with 4 chars</string>\n    <string name=\"select_folder\">Select a folder</string>\n    <string name=\"select_file\">Select a file</string>\n    <string name=\"no_find\">No Discovery, you can add it in BookSource</string>\n    <string name=\"restore_default\">Restore default</string>\n    <string name=\"set_download_per\">Custom cache path requires Storage permission</string>\n    <string name=\"black\">Black</string>\n    <string name=\"content_empty\">No content</string>\n    <string name=\"on_change_source\">Changing source, wait please</string>\n    <string name=\"chapter_list_empty\">Chapters is empty</string>\n    <string name=\"text_letter_spacing\">Word spacing</string>\n\n    <string name=\"source_tab_base\">Basic</string>\n    <string name=\"source_tab_search\">Search</string>\n    <string name=\"source_tab_find\">Discovery</string>\n    <string name=\"source_tab_info\">Information</string>\n    <string name=\"source_tab_toc\">Chapters</string>\n    <string name=\"source_tab_content\">Content</string>\n\n    <string name=\"e_ink_mode\">E-Ink mode</string>\n    <string name=\"e_ink_mode_detail\">Remove animations and optimize the experience of using E-paper books</string>\n    <string name=\"web_menu\">Web service</string>\n    <string name=\"web_port_title\">Web port</string>\n    <string name=\"web_port_summary\">Current port %s</string>\n    <string name=\"qr_share\">QR code sharing</string>\n    <string name=\"str_share\">Strings sharing</string>\n    <string name=\"wifi_share\">Wifi sharing</string>\n    <string name=\"please_grant_storage_permission\">Please grant Storage Permission</string>\n    <string name=\"fast_rewind\">Speed down</string>\n    <string name=\"fast_forward\">Speed up</string>\n    <string name=\"skip_previous\">Prior</string>\n    <string name=\"skip_next\">Next</string>\n    <string name=\"music\">Music</string>\n    <string name=\"audio\">Audio</string>\n    <string name=\"is_enable\">Enable</string>\n    <string name=\"enable_js\">Enable js</string>\n    <string name=\"load_with_base_url\">Load BaseUrl</string>\n    <string name=\"all_source\">All Sources</string>\n    <string name=\"cannot_empty\">The input content cannot be empty</string>\n    <string name=\"clear_find_cache\">Clear Discovery cache</string>\n    <string name=\"edit_find\">Edit Discovery</string>\n    <string name=\"change_icon_summary\">Switch the software icon displayed on the desktop</string>\n    <string name=\"help\">Help</string>\n    <string name=\"my\">Me</string>\n    <string name=\"reading\">Read</string>\n    <string name=\"battery_show\">%d%%</string>\n    <string name=\"timer_m\">%d min</string>\n    <string name=\"brightness_auto\">Auto-Brightness %s</string>\n    <string name=\"read_aloud_by_page\">Speak by pages</string>\n    <string name=\"speak_engine\">Speak Engine</string>\n    <string name=\"bg_image\">Background images</string>\n    <string name=\"bg_color\">Background color</string>\n    <string name=\"text_color\">Text color</string>\n    <string name=\"select_image\">Select a picture</string>\n    <string name=\"group_manage\">Group management</string>\n    <string name=\"group_select\">Group selection</string>\n    <string name=\"group_edit\">Group editing</string>\n    <string name=\"move_to_group\">Move to group</string>\n    <string name=\"add_group\">Add to Groups</string>\n    <string name=\"remove_group\">Remove from Groups</string>\n    <string name=\"add_replace_rule\">New replacement</string>\n    <string name=\"group\">Group</string>\n    <string name=\"group_s\">Group: %s</string>\n    <string name=\"toc_s\">Chapters: %s</string>\n    <string name=\"enable_explore\">Enable Discovery</string>\n    <string name=\"disable_explore\">Disable Discovery</string>\n    <string name=\"enable_selection\">Enable selected</string>\n    <string name=\"disable_selection\">Disable selected</string>\n    <string name=\"export_selection\">Export selected</string>\n    <string name=\"export\">Export</string>\n    <string name=\"load_toc\">Load chapters</string>\n    <string name=\"load_info\">Load book detail</string>\n    <string name=\"tts\">TTS</string>\n    <string name=\"web_dav_pw\">WebDav password</string>\n    <string name=\"web_dav_pw_s\">Input you WebDav authorized password</string>\n    <string name=\"web_dav_url_s\">Input you server address</string>\n    <string name=\"web_dav_url\">WebDav server address</string>\n    <string name=\"web_dav_account\">WebDav account</string>\n    <string name=\"web_dav_account_s\">Input your WebDav account</string>\n    <string name=\"rss_source\">RSS source</string>\n    <string name=\"rss_source_edit\">Edit RSS source</string>\n    <string name=\"screen\">Filter</string>\n    <string name=\"screen_find\">Search Discovery sources</string>\n    <string name=\"dur_pos\">Current location：</string>\n    <string name=\"precision_search\">Precise search</string>\n    <string name=\"service_starting\">Starting service</string>\n    <string name=\"empty\">Empty</string>\n    <string name=\"file_chooser\">File selection</string>\n    <string name=\"folder_chooser\">Folder selection</string>\n    <string name=\"bottom_line\">I AM OVER!</string>\n    <string name=\"uri_to_path_fail\">Uri To Path failed</string>\n    <string name=\"refresh_cover\">Refresh cover</string>\n    <string name=\"change_cover_source\">Change origin</string>\n    <string name=\"select_local_image\">Local image</string>\n    <string name=\"book_type\">Type:</string>\n    <string name=\"to_backstage\">Background</string>\n    <string name=\"importing\">Importing</string>\n    <string name=\"exporting\">Exporting</string>\n    <string name=\"custom_page_key\">Set page-turning buttons</string>\n    <string name=\"prev_page_key\">Page up button</string>\n    <string name=\"next_page_key\">Page down button</string>\n    <string name=\"no_group\">Ungrouped</string>\n    <string name=\"prev_sentence\">Prior sentence</string>\n    <string name=\"next_sentence\">Next sentence</string>\n    <string name=\"other_folder\">Other folder</string>\n    <string name=\"text_too_long_qr_error\">There are too many words to create a QR code</string>\n    <string name=\"share_rss_source\">RSS sources sharing</string>\n    <string name=\"share_book_source\">Book sources sharing</string>\n    <string name=\"auto_dark_mode\">Automatic switching dark mode</string>\n    <string name=\"auto_dark_mode_s\">Following system dark mode</string>\n    <string name=\"go_back\">Go back</string>\n    <string name=\"tone_colour\">Online Speech tone</string>\n    <string name=\"select_count\">(%1$d/%2$d)</string>\n    <string name=\"show_rss\">Show RSS feeds</string>\n    <string name=\"service_stop\">Service stopped</string>\n    <string name=\"service_start\">Starting service\\nChecking notification bar for details</string>\n    <string name=\"default_path\">Default path</string>\n    <string name=\"sys_folder_picker\">System folder picker</string>\n    <string name=\"app_folder_picker\">App folder picker</string>\n    <string name=\"app_file_picker\">App file picker</string>\n    <string name=\"a10_permission_toast\">Android 10+ unable to read and write file due to permission restrictions</string>\n    <string name=\"add_to_text_context_menu_s\">Long tap to display Legado·Search in the operation menu</string>\n    <string name=\"add_to_text_context_menu_t\">Text operation display Search</string>\n    <string name=\"record_log\">Record log</string>\n    <string name=\"log\">Log</string>\n    <string name=\"chinese_converter\">Simplified conversion</string>\n    <string name=\"change_icon_error\">The icon is a vector icon, which was not supported before Android 8.0</string>\n    <string name=\"aloud_config\">Speech settings</string>\n    <string name=\"main_activity\">Start page</string>\n    <string name=\"selectText\">Long Tap to select text</string>\n    <string name=\"header\">Header</string>\n    <string name=\"main_body\">Content</string>\n    <string name=\"footer\">Footer</string>\n    <string name=\"select_end\">Select end</string>\n    <string name=\"select_start\">Select start</string>\n    <string name=\"share_layout\">Shared layout</string>\n    <string name=\"browser\">Browser</string>\n    <string name=\"import_default_rule\">Import default rules</string>\n    <string name=\"name\">Name</string>\n    <string name=\"regex\">Regex</string>\n    <string name=\"example\">Example</string>\n    <string name=\"more_menu\">More menu</string>\n    <string name=\"reduce\">Minus</string>\n    <string name=\"plus\">Plus</string>\n    <string name=\"system_typeface\">System typeface</string>\n    <string name=\"delete_book_file\">Delete source file</string>\n    <string name=\"text_default\">Default</string>\n    <string name=\"default1\">Default-1</string>\n    <string name=\"default2\">Default-2</string>\n    <string name=\"default3\">Default-3</string>\n    <string name=\"title\">Title</string>\n    <string name=\"title_left\">Left</string>\n    <string name=\"title_center\">Center</string>\n    <string name=\"title_hide\">Hide</string>\n    <string name=\"add_to_group\">Add to Group</string>\n    <string name=\"save_image\">Save image</string>\n    <string name=\"no_default_path\">No default path</string>\n    <string name=\"change_group\">Group settings</string>\n    <string name=\"view_toc\">View Chapters</string>\n    <string name=\"bar_elevation\">Navigation bar shadow</string>\n    <string name=\"bar_elevation_s\">Current shadow size(elevation): %s</string>\n    <string name=\"btn_default_s\">Default</string>\n    <string name=\"main_menu\">Main menu</string>\n    <string name=\"request_permission\">Tap to grant permission</string>\n    <string name=\"tip_local_perm_request_storage\">Legado needs Storage permission, please tap the \"Grant Permission\" button below, or go to \"Settings\"-\"Application Permissions\"-to open the required permission. If the permission is still not work, please tap \"Select Folder\" in the upper right corner to use the system folder picker.</string>\n    <string name=\"alouding_disable\">The selected text cannot be spoken in full text speech</string>\n    <string name=\"read_body_to_lh\">Extend to cutout</string>\n    <string name=\"toc_updateing\">Updating Chapters</string>\n    <string name=\"media_button_on_exit_title\">Headset buttons are always available</string>\n    <string name=\"media_button_on_exit_summary\">Headset buttons are available even exit the app.</string>\n    <string name=\"contributors\">Contributors</string>\n    <string name=\"contact\">Contact</string>\n    <string name=\"license\">License</string>\n    <string name=\"other\">Other</string>\n    <string name=\"official_account\">开源阅读</string>\n    <string name=\"follow_official_account\">Follow WeChat Official Accounts</string>\n    <string name=\"wechat\">WeChat</string>\n    <string name=\"thanks\">Supporting me will be appreciated</string>\n    <string name=\"about_official_account\">Official Accounts[开源阅读]</string>\n    <string name=\"source_auto_changing\">Changing source</string>\n    <string name=\"click_to_apply\">Tap to join</string>\n    <string name=\"middle\">Middle</string>\n    <string name=\"information\">Information</string>\n    <string name=\"switchLayout\">Switch Layout</string>\n    <string name=\"text_font_weight_converter\">Text font weight switching</string>\n    <string name=\"full_screen_gestures_support\">Full screen gestures support</string>\n    <string name=\"disable_return_key\">Disable return key</string>\n\n    <!--color-->\n    <string name=\"primary\">Primary</string>\n    <string name=\"accent\">Accent</string>\n    <string name=\"background_color\">Background color</string>\n    <string name=\"navbar_color\">NavBar color</string>\n    <string name=\"day\">Day</string>\n    <string name=\"day_color_primary\">Day,Primary</string>\n    <string name=\"day_color_accent\">Day,Accent</string>\n    <string name=\"day_background_color\">Day,Background color</string>\n    <string name=\"day_navbar_color\">Day,NavBar color</string>\n    <string name=\"night\">Night</string>\n    <string name=\"night_primary\">Night,Primary</string>\n    <string name=\"night_accent\">Night,Accent</string>\n    <string name=\"night_background_color\">Night,Background color</string>\n    <string name=\"night_navbar_color\">Night,NavBar color</string>\n    <string name=\"auto_change_source\">Change source automatically</string>\n    <string name=\"text_full_justify\">Text justified</string>\n    <string name=\"text_bottom_justify\">Text align bottom</string>\n    <string name=\"auto_page_speed\">Auto scroll speed</string>\n    <string name=\"sort_by_url\">Sort by URL</string>\n    <string name=\"backup_summary\">Backup the local and WebDav simultaneously</string>\n    <string name=\"restore_summary\">Restore from WebDAV first, Restore form the local backup on long click</string>\n    <string name=\"import_old_summary\">Select a legacy backup folder</string>\n    <string name=\"enabled\">Enabled</string>\n    <string name=\"disabled\">Disabled</string>\n    <string name=\"enabled_explore\">Discovery Enabled</string>\n    <string name=\"disabled_explore\">Discovery Disabled</string>\n    <string name=\"starting_download\">Starting download</string>\n    <string name=\"already_in_download\">This book is already in Download list</string>\n    <string name=\"click_to_open\">Click to open</string>\n    <string name=\"follow_public_account_summary\">Follow [开源阅读] to support me by clicking on ads</string>\n    <string name=\"weChat_appreciation_code\">WeChat Tipping Code</string>\n    <string name=\"alipay\">AliPay</string>\n    <string name=\"alipay_red_envelope_search_code\">AliPay red envelope search code</string>\n    <string name=\"alipay_red_envelope_copy\">537954522 Click to copy</string>\n    <string name=\"alipay_red_envelope_qr_code\">AliPay red envelope QR code</string>\n    <string name=\"alipay_payment_qr_code\">AliPay QR code</string>\n    <string name=\"qq_collection_qr_code\">QQ Collection QR code</string>\n    <string name=\"contributors_summary\">gedoor,Invinciblelee,Xwite etc. Checking in github for details</string>\n    <string name=\"clear_cache_summary\">Clear the cache of the downloaded books and fonts</string>\n    <string name=\"default_cover\">Default cover</string>\n    <string name=\"restore_ignore\">Bypass list</string>\n    <string name=\"restore_ignore_summary\">Ignore some contents while restoring</string>\n    <string name=\"read_config\">Reading interface settings</string>\n    <string name=\"group_name\">Group name</string>\n    <string name=\"note_content\">Remarks section</string>\n    <string name=\"replace_enable_default_t\">Enable replace rule by default</string>\n    <string name=\"replace_enable_default_s\">For new added books</string>\n    <string name=\"select_restore_file\">Select restore file</string>\n    <string name=\"day_background_too_dark\">Day background can not be too dark!</string>\n    <string name=\"day_bottom_bar_too_dark\">Day bottom can not be too dark!</string>\n    <string name=\"night_background_too_light\">Night background can not be too bright!</string>\n    <string name=\"night_bottom_bar_too_light\">Night bottom can not be too bright!</string>\n    <string name=\"accent_background_diff\">Need Difference between accent and background color </string>\n    <string name=\"accent_text_diff\">Need Difference between accent and text color</string>\n    <string name=\"wrong_format\">Wrong format</string>\n    <string name=\"error\">Error</string>\n    <string name=\"show_brightness_view\">Show brightness widget</string>\n    <string name=\"language\">Language</string>\n    <string name=\"import_rss_source\">Import rss source</string>\n    <string name=\"donate_summary\">Your donation makes this app better</string>\n    <string name=\"about_summary\">Wechat official account [开源阅读软件]</string>\n    <string name=\"read_record\">Read record</string>\n    <string name=\"del_read_record\">Delete Read record</string>\n    <string name=\"read_record_summary\">Read record summary</string>\n    <string name=\"local_tts\">Local TTS</string>\n    <string name=\"thread_count\">Thread count</string>\n    <string name=\"all_read_time\">Total read time</string>\n    <string name=\"un_select_all\">Unselect all</string>\n    <string name=\"import_str\">Import</string>\n    <string name=\"export_str\">Export</string>\n    <string name=\"save_theme_config\">Save theme config</string>\n    <string name=\"save_day_theme_summary\">Save day theme config</string>\n    <string name=\"save_night_theme_summary\">Save night theme config</string>\n    <string name=\"theme_list\">Theme list</string>\n    <string name=\"theme_list_summary\">Save, Import, Share theme</string>\n    <string name=\"select_theme\">Switch default theme</string>\n    <string name=\"sort_by_lastUpdateTime\">Sort by update time</string>\n    <string name=\"sort_by_respondTime\">Sort by respond time</string>\n    <string name=\"search_content\">Search content</string>\n    <string name=\"rss_source_empty\">Empty now!</string>\n    <string name=\"explore_empty\">Empty now!</string>\n    <string name=\"page_key_set_help\">Focusing on the input box and pressing a physical key will automatically enter the value of the key, and multiple keys will be automatically separated by commas.</string>\n    <string name=\"theme_name\">Theme name</string>\n    <string name=\"auto_clear_expired\">\"Clear expired search histories automatically \"</string>\n    <string name=\"auto_clear_expired_summary\">Search histories more than one day</string>\n    <string name=\"re_segment\">Re-segment</string>\n    <string name=\"style_name\">Style name:</string>\n    <string name=\"empty_msg_import_book\">Click the folder icon in the upper right corner and select the folder</string>\n    <string name=\"scan_folder\">Intelligent scanning</string>\n    <string name=\"import_file_name\">Imported-file name</string>\n    <string name=\"no_book\">No books</string>\n    <string name=\"keep_original_name\">Keep the original name</string>\n    <string name=\"click_regional_config\">Screen touch control</string>\n    <string name=\"close\">Close</string>\n    <string name=\"next_page\">Next page</string>\n    <string name=\"prev_page\">Prior page</string>\n    <string name=\"non_action\">None</string>\n    <string name=\"body_title\">Title</string>\n    <string name=\"show_hide\">Show/Hide</string>\n    <string name=\"header_footer\">Footer <![CDATA[&]]> Header</string>\n    <string name=\"rule_subscription\">Rule Subscription</string>\n    <string name=\"rule_sub_empty_msg\">Add the rule import address provided by the bosses\\nClick to import rules after adding</string>\n    <string name=\"get_book_progress\">Pull the cloud  progress</string>\n    <string name=\"cover_book_progress\">Overlay Cloud Progress</string>\n    <string name=\"current_progress_exceeds_cloud\">The current progress exceeds the cloud progress. Do you want to synchronize?</string>\n    <string name=\"cloud_progress_exceeds_current\">The cloud progress exceeds the current progress. Do you want to synchronize?</string>\n    <string name=\"sync_book_progress_t\">Synchronous reading progress</string>\n    <string name=\"sync_book_progress_s\">Synchronize reading progress when entering / exiting the reading interface</string>\n    <string name=\"sync_book_progress_plus_t\">Sync Enhancement</string>\n    <string name=\"sync_book_progress_plus_s\">Sync cloud progress when re-entering the page (screen off, returning from background, etc.) or when the network becomes available. You will be prompted before syncing new progress.</string>\n    <string name=\"sync_book_progress_success\">Sync progress success</string>\n    <string name=\"create_bookmark_error\">Failed to create bookmark</string>\n    <string name=\"single_url\">Single URL</string>\n    <string name=\"export_bookshelf\">Export the list of books</string>\n    <string name=\"import_bookshelf\">Import the list of books</string>\n    <string name=\"pre_download\">Download in advance</string>\n    <string name=\"pre_download_m\">Pre-download %s pages</string>\n    <string name=\"pre_download_s\">Download %s chapters in advance</string>\n    <string name=\"is_enabled\">Is enabled</string>\n    <string name=\"background_image\">Background image</string>\n    <string name=\"background_image_blurring\">Background image blurring</string>\n    <string name=\"background_image_blurring_radius\">Blurring radius</string>\n    <string name=\"background_image_hint\">Disabled when 0, enable range from 1 to 25\\nThe greater the radius, the stronger the effect of blurring</string>\n    <string name=\"copy_book_url\">Copy book URL</string>\n    <string name=\"copy_toc_url\">Copy chapters URL</string>\n    <string name=\"export_folder\">Export folder</string>\n    <string name=\"export_charset\">Exported text coding</string>\n    <string name=\"export_to_web_dav\">Export to WebDav</string>\n    <string name=\"reverse_content\">Reverse content</string>\n    <string name=\"debug\">Debug</string>\n    <string name=\"crash_log\">Crash log</string>\n    <string name=\"use_zh_layout\">Custom Chinese line feed</string>\n    <string name=\"image_style\">Style of Images</string>\n    <string name=\"system_tts\">System tts</string>\n    <string name=\"export_type\">Exported file format</string>\n    <string name=\"checkAuthor\">Check by author</string>\n    <string name=\"url_already\">This URL has subscribed</string>\n    <string name=\"high_brush_title\">High screen refresh rate</string>\n    <string name=\"high_brush_summary\">Use maximum screen refresh rate</string>\n    <string name=\"export_all\">Export all</string>\n    <string name=\"complete\">Finished</string>\n    <string name=\"show_unread\">Show unread flag</string>\n    <string name=\"use_default_cover\">Always show default cover</string>\n    <string name=\"use_default_cover_s\">Always show the default cover, do not show the network cover</string>\n    <string name=\"search_src\">Search source code</string>\n    <string name=\"boo_src\">Book source code</string>\n    <string name=\"toc_src\">Chapters source code</string>\n    <string name=\"content_src\">Content source code</string>\n    <string name=\"list_src\">List source code</string>\n    <string name=\"title_font_size\">Font size</string>\n    <string name=\"title_margin_top\">Margin top</string>\n    <string name=\"title_margin_bottom\">Margin bottom</string>\n    <string name=\"show\">Show</string>\n    <string name=\"hide\">Hide</string>\n    <string name=\"hide_when_status_bar_show\">Hide when status bar show</string>\n    <string name=\"reverse_toc\">Reverse toc</string>\n    <string name=\"show_discovery\">Show Discovery</string>\n    <string name=\"style\">Style</string>\n    <string name=\"group_style\">Group style</string>\n    <string name=\"export_file_name\">Export file name</string>\n    <string name=\"reset\">Reset</string>\n    <string name=\"null_url\">Null url</string>\n    <string name=\"dict\">dict</string>\n    <string name=\"unknown_error\">unknown error</string>\n    <string name=\"export_no_chapter_name\">No export chapter names</string>\n    <string name=\"autobackup_fail\">Autobackup failed\\n%s</string>\n    <string name=\"end\">end</string>\n    <string name=\"custom_group_summary\">Turn off Replace Group / Turn on Add Group</string>\n    <string name=\"pref_media_button_per_next\">Media Buttons • Previous | Next</string>\n    <string name=\"pref_media_button_per_next_summary\">Previous Paragraph|Next Paragraph/Previous Chapter|Next Chapter</string>\n    <string name=\"read_aloud_by_page_summary\">Turning pages in time, with a pause when the page is turned</string>\n    <string name=\"check_source_show_debug_message\">Check book source shows debug message</string>\n    <string name=\"check_source_show_debug_message_summary\">Show network status and timestamp during source checking</string>\n    <string name=\"need_login\">need login</string>\n    <string name=\"pref_cronet_summary\">use Cronet access network</string>\n    <string name=\"anti_alias\">Anti-Aliasing</string>\n    <string name=\"pref_anti_alias_summary\">Anti-Aliasing when draw picture</string>\n    <string name=\"upload_url\">upload url</string>\n    <string name=\"download_url_rule\">downloadUrlRule(downloadUrls)</string>\n    <string name=\"export_success\">export success</string>\n    <string name=\"path\">path</string>\n    <string name=\"direct_link_upload_rule\">Straight chain upload rule</string>\n    <string name=\"direct_link_upload_rule_summary\">Used to generate direct link url when exporting book source, book list</string>\n    <string name=\"direct_link_upload_config\">Direct link upload configuration</string>\n    <string name=\"copy_play_url\">Copy Play Url</string>\n    <string name=\"set_source_variable\">Set source variable</string>\n    <string name=\"set_book_variable\">Set book variable</string>\n    <string name=\"summary\">summary</string>\n    <string name=\"cover_config\">cover config</string>\n    <string name=\"cover_config_summary\">Generic Cover Rules and Default Cover Style</string>\n    <string name=\"cover_show_name\">show name</string>\n    <string name=\"cover_show_name_summary\">Book title shown on cover</string>\n    <string name=\"cover_show_author\">show author</string>\n    <string name=\"cover_show_author_summary\">Author shown on cover</string>\n    <string name=\"read_aloud_prev_paragraph\">Read aloud the previous paragraph</string>\n    <string name=\"read_aloud_next_paragraph\">Read aloud the next paragraph</string>\n    <string name=\"wait_download\">wait download</string>\n    <string name=\"download_success\">download success</string>\n    <string name=\"download_error\">download failure</string>\n    <string name=\"downloading\">downloading</string>\n    <string name=\"unknown_state\">unknown</string>\n    <string name=\"disable_source\">disable source</string>\n    <string name=\"delete_source\">delete source</string>\n    <string name=\"chapter_pay\">pay</string>\n    <string name=\"double_page_horizontal\">Tablet/Landscape Dual Page</string>\n    <string name=\"open_in_browser\">open in browser</string>\n    <string name=\"copy_url\">copy url</string>\n    <string name=\"full_screen\">full screen</string>\n    <string name=\"open_fun\">open function</string>\n    <string name=\"use_browser_open\">Is it opened in an external browser?</string>\n    <string name=\"see\">see</string>\n    <string name=\"open\">open</string>\n    <string name=\"del_login_header\">del login header</string>\n    <string name=\"show_login_header\">show login header</string>\n    <string name=\"login_header\">login header</string>\n    <string name=\"font_scale\">font scale</string>\n    <string name=\"font_scale_summary\">font scale:%.1f</string>\n    <string name=\"search_content_size\">search result</string>\n    <string name=\"search_content_empty\">Empty search result, check conversation settings</string>\n    <string name=\"tts_speech_reduce\">Decrease speech speed</string>\n    <string name=\"tts_speech_add\">Increase speech speed</string>\n    <string name=\"open_sys_dir_picker_error\">Error opening system folder selector, automatically open application folder selector</string>\n    <string name=\"open_sys_doc_picker_error\">Error opening system file selector, automatically open application file selector</string>\n    <string name=\"expand_text_menu\">Expand text selection menu</string>\n    <string name=\"book_tree_uri_t\">Book Save Location</string>\n    <string name=\"book_tree_uri_s\">Save location for books opened from other apps</string>\n    <string name=\"select_book_folder\">Select the folder where the books are saved</string>\n    <string name=\"user_agent\">User agent</string>\n    <string name=\"bg_alpha\">Background alpha</string>\n\n    <!-- check source config string -->\n    <string name=\"check_source_config\">Check setting</string>\n    <string name=\"check_source_item\">Check item</string>\n    <string name=\"check_source_timeout\">Single book source check timeout (seconds)</string>\n    <string name=\"timeout\">Timeout</string>\n    <string name=\"seconds\">Seconds</string>\n    <string name=\"less_than\">Less than</string>\n    <string name=\"check_source_config_summary\">Check timeout: %1$ss\\nCheck item:%2$s</string>\n    <string name=\"record_debug_log\">Record debug log</string>\n    <string name=\"sub_dir\">Sub dir</string>\n    <string name=\"general\">General</string>\n    <string name=\"use_replace\">Use replace</string>\n    <string name=\"scope_title\">Act on the title</string>\n    <string name=\"scope_content\">Act on the content</string>\n    <string name=\"join_qq_channel\">Join QQ channel</string>\n    <string name=\"qq_channel_summary\">点击加入阅读QQ频道</string>\n    <string name=\"menu_refresh_dur\">Refresh the current chapter</string>\n    <string name=\"menu_refresh_after\">Refresh the chapter after</string>\n    <string name=\"menu_refresh_all\">Refresh all chapters</string>\n    <string name=\"edit_content\">Edit Content</string>\n    <string name=\"chapter_change_source\">Single chapter source switching</string>\n    <string name=\"book_change_source\">Change the source of the whole book</string>\n    <string name=\"sort_by_time\">Sort by time</string>\n    <string name=\"enable_record\">Enable record</string>\n    <string name=\"copy_all\">Copy all</string>\n    <string name=\"auto_complete\">Automatic completion</string>\n    <string name=\"sort_by_size\">Sort by size</string>\n    <string name=\"welcome_style\">Launch interface style</string>\n    <string name=\"welcome_style_summary\">Startup screen image and whether to display text, etc.</string>\n    <string name=\"show_welcome_text\">Display text</string>\n    <string name=\"welcome_text\">阅读|享受美好时光</string>\n    <string name=\"custom_welcome\">Customize the welcome page</string>\n    <string name=\"custom_welcome_summary\">Whether to use a customized welcome page</string>\n    <string name=\"show_icon\">Show icon</string>\n    <string name=\"show_default_book_icon\">Show default book icon</string>\n    <string name=\"cache_export\">Cache / Export</string>\n    <string name=\"assists_key_config\">Secondary button configuration</string>\n    <string name=\"url_option\">Url parameter</string>\n    <string name=\"only_wifi\">WIFI only</string>\n    <string name=\"only_wifi_summary\">Load network cover only under wifi</string>\n    <string name=\"cover_rule\">Cover rule</string>\n    <string name=\"cover_rule_summary\">Use the cover rule to retrieve the cover when entering the details page</string>\n    <string name=\"scroll_to_dur_source\">Navigate to the current book source</string>\n    <string name=\"sys_tts_config\">System tts Settings</string>\n    <string name=\"sys_tts_config_summary\">Open the system tts settings interface</string>\n    <string name=\"cannot_timed_non_playback\">When the non-playback state is not legal</string>\n    <string name=\"all_bookmark\">All bookmarks</string>\n    <string name=\"change_source_batch\">Batch source switching</string>\n    <string name=\"book_type_different\">Different types of books</string>\n    <string name=\"soure_change_source\">Whether to confirm the source change</string>\n    <string name=\"input_verification_code\">Enter the CAPTCHA</string>\n    <string name=\"verification_code\">Verification code</string>\n    <string name=\"timeout_millisecond\">Timed out milliseconds</string>\n    <string name=\"file_not_supported\">Continue to open although %1$s is not supported ?</string>\n    <string name=\"import_tts\">Import TTS</string>\n    <string name=\"import_theme\">Import theme</string>\n    <string name=\"import_txt_toc_rule\">Import txt directory rules</string>\n    <string name=\"auto_save_cookie\">CookieJar</string>\n    <string name=\"cookie\">Clear cookie</string>\n    <string name=\"download_and_import_file\">Import online book files</string>\n    <string name=\"upload_book_success\">Upload Success</string>\n    <string name=\"upload_book_fail\">Upload Fail</string>\n    <string name=\"download_book_success\">Download Success</string>\n    <string name=\"download_book_fail\">Download Fail</string>\n    <string name=\"upload_to_remote\">Upload</string>\n    <string name=\"add_remote_book\">RemoteBook</string>\n    <string name=\"bitmap_cache_size_summary\">Current cache max size %1$s MB</string>\n    <string name=\"bitmap_cache_size\">bitmap cache size</string>\n    <string name=\"image_retain_number_summary\">Keep the number of chapters read %s</string>\n    <string name=\"image_retain_number\">Number of manga reservations</string>\n    <string name=\"export_pics_file\">Export Picture Files</string>\n    <!-- string end -->\n    <string name=\"error_decode_bitmap\">Fail to decode bitmap</string>\n    <string name=\"error_image_url_empty\">Image url is empty, check replacement rules</string>\n    <string name=\"variable_comment\">变量说明(variableComment)</string>\n    <string name=\"remote_book\">Remote book</string>\n    <string name=\"reading_time_sort\">Sort by reading duration</string>\n    <string name=\"last_read_time_sort\">Sort by read time</string>\n    <string name=\"reading_time_tag\">Reading time:</string>\n    <string name=\"last_read_time_tag\">Last read time:</string>\n    <string name=\"page_touch_slop_title\">Sliding Page Threshold</string>\n    <string name=\"page_touch_slop_dialog_title\">Sliding page turn threshold (0 = system default)</string>\n    <string name=\"page_touch_slop_summary\">How far to slide to trigger a page slide (system default %s px)</string>\n    <string name=\"check_selected_interval\">Select zones</string>\n    <string name=\"show_add_to_shelf_alert_title\">Prompt to add to bookshelf when return</string>\n    <string name=\"show_add_to_shelf_alert_summary\">Prompt to add to bookshelf when return from reading books that are not on the bookshelf</string>\n    <string name=\"review\">Review</string>\n    <string name=\"rule_avatar\">段评发布者头像（avatarRule）</string>\n    <string name=\"rule_review_url\">段评URL（reviewUrl）</string>\n    <string name=\"rule_review_content\">Paragraph review content（contentRule）</string>\n    <string name=\"rule_post_time\">Release time of paragraph comments（postTimeRule）</string>\n    <string name=\"rule_review_quote\">Paragraph comment replied to URL（reviewQuoteUrl）</string>\n    <string name=\"review_vote_down\">Not like URL（voteDownUrl）</string>\n    <string name=\"review_vote_up\">Like URL（voteUpUrl）</string>\n    <string name=\"post_review_url\">Send a reply to URL（postReviewUrl）</string>\n    <string name=\"post_quote_url\">Send a reply paragraph comment URL（postQuoteUrl）</string>\n    <string name=\"delete_review_url\">Delete paragraph comment URL（deleteUrl）</string>\n    <string name=\"tag_explore_enabled\">Flag: found enabled</string>\n    <string name=\"tag_explore_disabled\">Flag: found disabled</string>\n    <string name=\"show_read_title_addition\">show read title addition area</string>\n    <string name=\"read_bar_style_follow_page\">read bar style follow page</string>\n    <string name=\"rule_image_decode\">Decode Image(imageDecode)</string>\n    <string name=\"like_source\">Like</string>\n    <string name=\"not_like_source\">Not like</string>\n    <string name=\"async_load_image\">Load pictures asynchronously</string>\n    <string name=\"ignore_audio_focus_title\">Ignore audio focus</string>\n    <string name=\"ignore_audio_focus_summary\">Allow audio to be played at the same time as other applications</string>\n    <string name=\"refresh_sort\">Refresh sort</string>\n    <string name=\"cover_decode_js\">Decode Cover Js(coverDecodeJs)</string>\n    <string name=\"net_no_group\">Network ungrouped</string>\n    <string name=\"local_no_group\">Local ungrouped</string>\n    <string name=\"parallel_export_book\">Multithreaded export</string>\n    <string name=\"progress_bar_behavior\">Progress bar behavior</string>\n    <string name=\"source_edit_text_max_line\">Maximum number of rows in the source edit box</string>\n    <string name=\"source_edit_max_line_summary\">%s,Setting the number of rows less than the maximum number of rows that can be displayed on the screen makes it easier to slide to other fields for editing.</string>\n    <string name=\"restore_last_book_process\">Whether to return to the reading progress before the jump？</string>\n    <string name=\"search_scope\">Search scope</string>\n    <string name=\"toggle_search_scope\">Handover</string>\n    <string name=\"sure_clear_search_history\">Are you sure to clear all search history</string>\n    <string name=\"no_anim_scroll_page\">Disable scroll click animation</string>\n    <string name=\"webdav_device_name\">Device name</string>\n    <string name=\"web_service_wake_lock\">WebService Wake up Lock</string>\n    <string name=\"web_service_wake_lock_summary\">The wake-up lock is enabled when the web service is enabled, and some phones will be killed when the wake-up lock is enabled.</string>\n    <string name=\"read_aloud_wake_lock\">Read aloud service wake-up lock</string>\n    <string name=\"read_aloud_wake_lock_summary\">The wake-up lock is enabled when reading aloud is enabled, and the wake-up lock on some mobile phones will be killed in the background.</string>\n    <string name=\"audio_play_wake_lock\">Audio service wake-up lock</string>\n    <string name=\"audio_play_wake_lock_summary\">The wake-up lock is enabled when playing audio, and some phones will be killed when the wake-up lock is turned on.</string>\n    <string name=\"change_search_scope\">Toggle search scope</string>\n    <string name=\"copy_rule\">Copy Rule</string>\n    <string name=\"paste_rule\">Paste Rule</string>\n    <string name=\"groups_or_source\">Multi-group / book source</string>\n    <string name=\"replace_state_change\">Replace (enable / disable)</string>\n    <string name=\"show_last_update_time\">Show last updated time</string>\n    <string name=\"refresh_list\">Refresh list</string>\n    <string name=\"tip_divider_color\">Divider color</string>\n    <string name=\"same_title_removed\">Remove duplicate title</string>\n    <string name=\"update_book_fail\">Update failed</string>\n    <string name=\"notification_permission_rationale\">Reading requires sending notifications to show reading control and download progress</string>\n    <string name=\"webdav_after_local_restore_confirm\">The webDav book source is newer than the local one, whether to restore it or not</string>\n    <string name=\"c_whitelist\">白名单(contentWhitelist)</string>\n    <string name=\"c_blacklist\">黑名单(contentBlacklist)</string>\n    <string name=\"confirm\">Confirm</string>\n    <string name=\"jump_to_another_app\">Jump to another app</string>\n    <string name=\"clear_webview_data\">Clear WebView Data</string>\n    <string name=\"clear_webview_data_summary\">Clear all data in WebView</string>\n    <string name=\"source_tab_list\">List</string>\n    <string name=\"dict_rule\">Dict rule</string>\n    <string name=\"config_dict_rule\">Config dict rule</string>\n    <string name=\"create\">Create</string>\n    <string name=\"url_rule\">url规则(urlRule)</string>\n    <string name=\"show_rule\">显示规则(showRule)</string>\n    <string name=\"config_txt_toc_rule\">Configure txt catalog rules</string>\n    <string name=\"import_dict_rule\">Import dict rule</string>\n    <string name=\"keep_group\">Keep group</string>\n    <string name=\"server_config\">Server Configuration</string>\n    <string name=\"sure_upload\">Remote webDav url exists, Continue?</string>\n    <string name=\"unsupport_archivefile_entry\">Cannot find supported files in archive</string>\n    <string name=\"no_books_dir\">There is no book save location set!</string>\n    <string name=\"delete_alert\">Delete Alert</string>\n    <string name=\"no_book_found_bookshelf\">No book found in bookshelf, import again ?</string>\n    <string name=\"archive_not_found\">Can not find archive selected, continue to download ?</string>\n    <string name=\"privacy_policy\">User Privacy and Agreement</string>\n    <string name=\"agree\">Agree</string>\n    <string name=\"refuse\">Refuse</string>\n    <string name=\"file_manage\">File management</string>\n    <string name=\"file_manage_summary\">Managing files in the application\\'s private folder</string>\n    <string name=\"create_folder\">Create Folder</string>\n    <string name=\"allow_drop_down_refresh\">Allow drop-down refresh</string>\n    <string name=\"text_underline\">Text underline</string>\n    <string name=\"select_new_source\">Select new source</string>\n    <string name=\"select_update_source\">Select update source</string>\n    <string name=\"set_local_password\">Setting the local password</string>\n    <string name=\"set_local_password_summary\">The local password is used to encrypt and decrypt the sensitive information of the backup, if you need to synchronize between different devices, the local password should be the same.</string>\n    <string name=\"only_latest_backup_t\">Keep only the latest backup</string>\n    <string name=\"only_latest_backup_s\">Local backup keeps only the latest backup file</string>\n    <string name=\"webdav_application_authorization_error\">Fail to authorize WebDav application</string>\n    <string name=\"respondTime\">respondTime: %1$d ms</string>\n    <string name=\"load_word_count\">Load word count</string>\n    <string name=\"replace_exclude_scope\">Exclude range, optional title or source URL</string>\n    <string name=\"format_js_rule\">格式化规则(formatJs)</string>\n    <string name=\"adjust_pos\">Adjustment position</string>\n    <string name=\"error_scope_input\">Please enter the correct range</string>\n    <string name=\"custom_export\">Custom Export</string>\n    <string name=\"file_contains_number\">The number of chapters contained in each file</string>\n    <string name=\"export_chapter_index\">The section index that needs to be exported</string>\n    <string name=\"shrink_database\">Shrink database</string>\n    <string name=\"shrink_database_summary\">Reduce the size of the database file</string>\n    <string name=\"is_compress\">Compressed or not</string>\n    <string name=\"sort_desc\">Descend order</string>\n    <string name=\"test\">Test</string>\n    <string name=\"show_wait_up_count\">Display the number of pending updates</string>\n    <string name=\"exit_app\">Exit</string>\n    <string name=\"result_analyzed\">Analyzed</string>\n    <string name=\"bookshelf_px_4\">Comprehensive</string>\n    <string name=\"bookshelf_px_5\">Sort by author</string>\n    <string name=\"effective_replaces\">Effective replacement</string>\n    <string name=\"export_book\">Export Book</string>\n    <string name=\"export_book_notification_content\">Exporting (%1$s), with %2$d to be exported.</string>\n    <string name=\"export_md\">Export(MD)</string>\n    <string name=\"change_source_delay\">Change source delay</string>\n    <string name=\"change_source_progress\">Result %1$d, Progress %2$d / %3$d: %4$s</string>\n    <string name=\"open_book_info_by_click_title\">Click on the book title to open details</string>\n    <string name=\"export_wait\">Waiting for export</string>\n    <string name=\"show_bookshelf_fast_scroller\">Show quick scroll bar</string>\n    <string name=\"export_all_use_book_source\">Export book source for all books</string>\n    <string name=\"keep_enable\">Retain Enabled Status</string>\n    <string name=\"preview_image_by_click\">Click to preview image</string>\n    <string name=\"default_home_page\">Default HomePage</string>\n    <string name=\"screen_portrait_reversed\">Portrait Reversed</string>\n    <string name=\"del_ruby_tag\">Delete ruby tag</string>\n    <string name=\"del_h_tag\">Delete h tag</string>\n    <string name=\"adjust_chapter_page\">Adjust of chapter page</string>\n    <string name=\"adjust_chapter_index\">Adjust of chapter index</string>\n    <string name=\"clear_webview_data_success\">Cleared successfully, automatically restarts the application after 3 seconds</string>\n    <string name=\"key_page_on_long_press\">Press and hold the key to turn the page</string>\n    <string name=\"save_log\">Save logs</string>\n    <string name=\"create_heap_dump\">Create heap dump</string>\n    <string name=\"record_heap_dump_s\">Saving a heap dump when the application crashes by OOM</string>\n    <string name=\"record_heap_dump_t\">Capture heap dump</string>\n    <string name=\"font_weight_text\">N/B/L</string>\n    <string name=\"keep_swipe_tip\">Keep swiping to load the next chapter…</string>\n    <string name=\"enable_optimize_render\">启用绘制优化</string>\n    <string name=\"ignore_battery_permission_rationale\">阅读需要请求后台权限以保持服务正常运行</string>\n    <string name=\"simulated_reading\">Simulated Reading</string>\n    <string name=\"switch_on\">Switch</string>\n    <string name=\"start_from\">Start from</string>\n    <string name=\"daily_chapters\">Daily Chapters</string>\n    <string name=\"start_chapter\">Start Chapter</string>\n    <string name=\"update_to_variant_title\">检查更新查找版本</string>\n    <string name=\"update_to_variant_summary\">检查更新时查找其他签名版本</string>\n    <string name=\"default_version\">当前</string>\n    <string name=\"official_version\">正式版</string>\n    <string name=\"beta_release_version\">测试版</string>\n    <string name=\"beta_releaseA_version\">共存版</string>\n    <string name=\"stream_read_aloud_audio\">流式播放音频</string>\n    <string name=\"stream_read_aloud_audio_summary\">即边下边播，网络不好时播放会断断续续，仅TTS源有效</string>\n    <string name=\"pause_read_aloud_while_phone_calls_title\">来电期间暂停朗读</string>\n    <string name=\"pause_read_aloud_while_phone_calls_summary\">在通话期间暂停朗读，需要读取手机状态权限</string>\n    <string name=\"read_aloud_read_phone_state_permission_rationale\">阅读需要读取手机状态实现来电期间暂停朗读功能</string>\n    <string name=\"read_aloud_by_media_button_title\">耳机按键启动朗读</string>\n    <string name=\"read_aloud_by_media_button_summary\">通过耳机按键来启动朗读</string>\n    <string name=\"group_sources_by_domain\">按域名分组显示</string>\n    <string name=\"theme_config\">主题配置</string>\n    <string name=\"show_manga_ui\">漫画浏览</string>\n    <string name=\"disable_manga_scale\">禁用漫画缩放</string>\n    <string name=\"disable_manga_click_scroll\">禁用点击翻页</string>\n    <string name=\"enable_auto_page_scroll\">开启自动翻页</string>\n    <string name=\"manga_auto_page_speed\">翻页速度 %s</string>\n    <string name=\"enable_auto_scroll\">开启滚动</string>\n    <string name=\"manga_footer_config\">页脚配置</string>\n    <string name=\"setting_manga_auto_page_speed\">设置自动翻页速度</string>\n    <string name=\"book_reader_info_bar\">页数. %1$d/%2$d  章节. %3$d/%4$d -> %5$s%%</string>\n    <string name=\"menu_download_after\">Download the chapter after</string>\n    <string name=\"menu_download_all\">Download all chapter</string>\n    <string name=\"manga_header_chapter\">《章节和文案》隐藏</string>\n    <string name=\"manga_check_chapter_label\">《章节.》文案</string>\n    <string name=\"manga_check_chapter\">章节</string>\n    <string name=\"manga_check_chapter_name\">章节名称</string>\n    <string name=\"manga_header_page\">《页数和文案》隐藏</string>\n    <string name=\"manga_check_page_label\">《页数.》文案</string>\n    <string name=\"manga_check_page_number\">页数</string>\n    <string name=\"manga_header_progress\">《总进度和文案》隐藏</string>\n    <string name=\"manga_check_progress_label\">《总进度.》文案</string>\n    <string name=\"manga_check_progress\">总进度</string>\n    <string name=\"manga_header_footer\">页脚</string>\n    <string name=\"manga_radio_left\">靠左</string>\n    <string name=\"manga_radio_center\">居中</string>\n    <string name=\"enable_manga_horizontal_scroll\">水平滚动</string>\n    <string name=\"manga_color_filter\">滤镜</string>\n    <string name=\"hide_manga_title\">隐藏漫画列表标题</string>\n    <string name=\"refresh_explore\">刷新发现</string>\n    <string name=\"padding_display_cutouts\">Padding display cutouts</string>\n    <string name=\"system_media_control_compatibility_change\">System Media Control Compatibility Change</string>\n    <string name=\"system_media_control_compatibility_change_summary\">If system media controls do not appear on the lock screen, you can try enabling this, for example on OneUI 7.0 or Vivo devices.</string>\n    <string name=\"read_aloud_pause_resume\">Pause/Resume Speech</string>\n    <string name=\"manga_epaper\">墨水屏</string>\n    <string name=\"manga_epaper_stting\">墨水屏设置</string>\n    <string name=\"manga_epaper_value\">阈值</string>\n    <string name=\"disable_horizontal_page_snap\">禁用水平翻页效果</string>\n    <string name=\"enable_manga_gray\">开启图片灰色</string>\n    <string name=\"play_mode\">播放模式</string>\n    <string name=\"sure_cache_book\">是否确认开始缓存？</string>\n    <string name=\"auto_check_new_backup_t\">自动检查新备份</string>\n    <string name=\"auto_check_new_backup_s\">打开软件时检查是否有新备份，有新备份时提示是否更新</string>\n    <string name=\"sys_image_picker\">系统图片选择器</string>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values/styles.xml",
    "content": "<resources>\n\n    //**************************************************************Theme******************************************************************************//\n\n    <!-- 亮色主题 -->\n    <style name=\"AppTheme.Light\" parent=\"Base.AppTheme\">\n        <item name=\"actionBarStyle\">@style/AppTheme.AppBarOverlay.Light</item>\n    </style>\n\n    <!-- 暗色主题 -->\n    <style name=\"AppTheme.Dark\" parent=\"Base.AppTheme\">\n        <item name=\"actionBarStyle\">@style/AppTheme.AppBarOverlay.Dark</item>\n    </style>\n\n    <!-- 透明主题 -->\n    <style name=\"AppTheme.Transparent\" parent=\"Base.AppTheme\">\n        <item name=\"android:windowIsTranslucent\">true</item>\n        <item name=\"android:windowBackground\">@android:color/transparent</item>\n    </style>\n\n    <!-- Welcome主题 -->\n    <style name=\"AppTheme.Welcome\" parent=\"Base.AppTheme\">\n        <item name=\"android:windowNoTitle\">true</item>\n    </style>\n\n    <!-- Base application theme. -->\n    <style name=\"Base.AppTheme\" parent=\"Theme.AppCompat.DayNight.NoActionBar\">\n        <item name=\"colorPrimary\">@color/primary</item>\n        <item name=\"colorPrimaryDark\">@color/primaryDark</item>\n        <item name=\"colorAccent\">@color/accent</item>\n        <item name=\"actionOverflowMenuStyle\">@style/Style.PopupMenu</item>\n        <item name=\"android:itemTextAppearance\">@style/Style.MenuItemText</item>\n        <item name=\"android:popupMenuStyle\">@style/Style.PopupMenu</item>\n        <item name=\"android:statusBarColor\">@color/transparent</item>\n        <item name=\"android:windowDrawsSystemBarBackgrounds\">true</item>\n    </style>\n\n    <style name=\"AppTheme.AppBarOverlay.Light\" parent=\"ThemeOverlay.AppCompat.Light\">\n        <item name=\"colorAccent\">@color/md_grey_900</item>\n    </style>\n\n    <style name=\"AppTheme.AppBarOverlay.Dark\" parent=\"ThemeOverlay.AppCompat.Dark\">\n        <item name=\"colorAccent\">@color/md_grey_50</item>\n    </style>\n\n    <style name=\"AppTheme.PopupOverlay\" parent=\"ThemeOverlay.AppCompat.Light\">\n        <item name=\"overlapAnchor\">false</item>\n        <item name=\"colorAccent\">@color/md_grey_900</item>\n    </style>\n\n    <style name=\"Spinner\" parent=\"android:Theme.Holo.Light\" />\n\n    //**********System Style******************//\n\n    <style name=\"Style.NavigationView\" parent=\"android:Widget\">\n        <item name=\"android:listPreferredItemHeightSmall\">44dp</item>\n    </style>\n\n    <style name=\"Style.Toolbar.NoPadding\" parent=\"Base.Widget.AppCompat.Toolbar\">\n        <item name=\"contentInsetStartWithNavigation\">0dp</item>\n    </style>\n\n    <style name=\"Style.MenuItemText\" parent=\"@android:style/TextAppearance.Widget.IconMenu.Item\">\n        <item name=\"android:textColor\">@color/menu_color_default</item>\n        <item name=\"android:textSize\">16sp</item>\n    </style>\n\n    <style name=\"Style.PopupMenu\" parent=\"Widget.AppCompat.PopupMenu\">\n        <item name=\"android:popupBackground\">@drawable/bg_popup_menu</item>\n    </style>\n\n\n    <style name=\"Activity.Permission\" parent=\"Theme.AppCompat.Light.NoActionBar\">\n        <item name=\"android:windowBackground\">@android:color/transparent</item>\n        <item name=\"android:windowDisablePreview\">true</item>\n        <item name=\"android:windowIsTranslucent\">true</item>\n        <item name=\"android:windowTranslucentStatus\">true</item>\n        <item name=\"android:windowAnimationStyle\">@null</item>\n    </style>\n\n    <style name=\"ToolbarTitle\" parent=\"@style/TextAppearance.Widget.AppCompat.Toolbar.Title\">\n        <item name=\"android:textSize\">20sp</item>\n    </style>\n\n    //*******************Widget Style**********************************//\n\n    <style name=\"Style.Shadow.Top\" parent=\"android:Widget\">\n        <item name=\"android:layout_width\">match_parent</item>\n        <item name=\"android:layout_height\">@dimen/shadow_height</item>\n        <item name=\"android:background\">@drawable/bg_shadow_top</item>\n    </style>\n\n    <style name=\"Style.Shadow.Bottom\" parent=\"android:Widget\">\n        <item name=\"android:layout_width\">match_parent</item>\n        <item name=\"android:layout_height\">@dimen/shadow_height</item>\n        <item name=\"android:background\">@drawable/bg_shadow_bottom</item>\n    </style>\n\n\n    <style name=\"Style.Line\" parent=\"android:Widget\">\n        <item name=\"android:layout_width\">match_parent</item>\n        <item name=\"android:layout_height\">0.5dp</item>\n        <item name=\"android:background\">@color/divider</item>\n    </style>\n\n    <style name=\"Style.Text.Primary.Normal\" parent=\"android:Widget\">\n        <item name=\"android:layout_width\">match_parent</item>\n        <item name=\"android:layout_height\">wrap_content</item>\n        <item name=\"android:textSize\">@dimen/font_size_normal</item>\n        <item name=\"android:textColor\">#000</item>\n        <item name=\"android:includeFontPadding\">false</item>\n    </style>\n\n    <style name=\"Style.Text.Primary.Normal.Wrap\">\n        <item name=\"android:layout_width\">wrap_content</item>\n    </style>\n\n    <style name=\"Style.Text.Second.Normal\" parent=\"android:Widget\">\n        <item name=\"android:layout_width\">match_parent</item>\n        <item name=\"android:layout_height\">wrap_content</item>\n        <item name=\"android:textSize\">@dimen/font_size_normal</item>\n        <item name=\"android:textColor\">#676767</item>\n        <item name=\"android:includeFontPadding\">false</item>\n    </style>\n\n    <style name=\"Style.Text.Second.Normal.Wrap\">\n        <item name=\"android:layout_width\">wrap_content</item>\n    </style>\n\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values-es-rES/arrays.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\t<string-array name=\"book_type\">\n\t\t<item>Texto</item>\n\t\t<item>Audio</item>\n\t\t<item>Image</item>\n\t\t<item>File</item>\n\t</string-array>\n\n\t<string-array name=\"group_style\">\n\t\t<item>Pestaña</item>\n\t\t<item>Carpeta</item>\n\t</string-array>\n\n\t<string-array name=\"indent\">\n\t\t<item>@string/indent_0</item>\n\t\t<item>@string/indent_1</item>\n\t\t<item>@string/indent_2</item>\n\t\t<item>@string/indent_3</item>\n\t\t<item>@string/indent_4</item>\n\t</string-array>\n\n\t<string-array name=\"text_suffix\">\n\t\t<item>.txt</item>\n\t\t<item>.json</item>\n\t\t<item>.xml</item>\n\t</string-array>\n\n\t<string-array name=\"convert_s\">\n\t\t<item>@string/jf_convert_o</item>\n\t\t<item>@string/jf_convert_j</item>\n\t\t<item>@string/jf_convert_f</item>\n\t</string-array>\n\n\t<string-array name=\"theme_mode\">\n\t\t<item>Adaptar do sistema</item>\n\t\t<item>Tema Claro</item>\n\t\t<item>Tema Oscuro</item>\n\t\t<item>Tema E-Ink</item>\n\t</string-array>\n\n\t<string-array name=\"NavBarColors\">\n\t\t<item>Autom.</item>\n\t\t<item>Oscuro</item>\n\t\t<item>Claro</item>\n\t\t<item>Adaptado</item>\n\t</string-array>\n\n\t<string-array name=\"screen_time_out\">\n\t\t<item>Predeterminado</item>\n\t\t<item>1 min</item>\n\t\t<item>5 min</item>\n\t\t<item>10 min</item>\n\t\t<item>Siempre</item>\n\t</string-array>\n\n\t<string-array name=\"screen_direction_title\">\n\t\t<item>@string/screen_unspecified</item>\n\t\t<item>@string/screen_portrait</item>\n\t\t<item>@string/screen_landscape</item>\n\t\t<item>@string/screen_sensor</item>\n\t\t<item>@string/screen_portrait_reversed</item>\n\t</string-array>\n\n\t<string-array name=\"icon_names\">\n\t\t<item>íconePrincipal</item>\n\t\t<item>ícone1</item>\n\t\t<item>ícone2</item>\n\t\t<item>ícone3</item>\n\t\t<item>ícone4</item>\n\t\t<item>ícone5</item>\n\t\t<item>ícone6</item>\n\t</string-array>\n\n\t<string-array name=\"chinese_mode\">\n\t\t<item>Desativado</item>\n\t\t<item>Tradicional a Simplificado</item>\n\t\t<item>Simplificado a Tradicional</item>\n\t</string-array>\n\n\t<string-array name=\"system_typefaces\">\n\t\t<item>Fuente predeterminado</item>\n\t\t<item>Fuente Serif</item>\n\t\t<item>Fuente Monoespaciado</item>\n\t</string-array>\n\n\t<string-array name=\"read_tip\">\n\t\t<item>En blanco</item>\n\t\t<item>Nombre del libro</item>\n\t\t<item>Título</item>\n\t\t<item>Tiempo</item>\n\t\t<item>Batería</item>\n\t\t<item>Batería%</item>\n\t\t<item>Páginas</item>\n\t\t<item>Avance(%)</item>\n\t\t<item>Avance(xx/yyy)</item>\n\t\t<item>Páginas y avance</item>\n\t\t<item>Tiempo y Batería</item>\n\t\t<item>Tiempo y Batería%</item>\n\t</string-array>\n\n\t<string-array name=\"text_font_weight\">\n\t\t<item>Normal</item>\n\t\t<item>Oscuro</item>\n\t\t<item>Claro</item>\n\t</string-array>\n\n\t<string-array name=\"language\">\n\t\t<item>Autom.</item>\n\t\t<item>Chino simplificado</item>\n\t\t<item>Chino tradicional</item>\n\t\t<item>Inglés</item>\n\t</string-array>\n\n\t<string-array name=\"rule_type\">\n\t\t<item>FuenteLibro</item>\n\t\t<item>FuenteRSS</item>\n\t\t<item>SustituirRegla</item>\n\t</string-array>\n\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values-es-rES/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <!--App-->\n    <string name=\"app_name\">Legado</string>\n    <string name=\"app_name_a\">Legado·A</string>\n    <string name=\"receiving_shared_label\">Legado·buscador</string>\n    <string name=\"tip_perm_request_storage\">Legado necesita de acceso de almacenamineto para buscar y leer libros. Por favor, diríjase a los \"Ajustes de la aplicación\" para conceder el \"Permiso de almacenamineto\".</string>\n\n    <!--Other-->\n    <string name=\"menu_backup\">Inicio</string>\n    <string name=\"menu_restore\">Restaurar</string>\n    <string name=\"menu_import_old\">Importar datos de Legado</string>\n    <string name=\"webdav_cache_backup\">Respaldo de caché de libros sin conexión</string>\n    <string name=\"webdav_cache_backup_s\">Exporta localmente lo respalda para su exportación</string>\n    <string name=\"backup_path\">Respaldar para</string>\n    <string name=\"select_backup_path\">Por favor, seleccione una carpeta de respaldo.</string>\n    <string name=\"menu_import_old_version\">Importar desde legado</string>\n    <string name=\"menu_import_github\">Importar datos de Github</string>\n    <string name=\"menu_replace_rule\">Reemplazo</string>\n    <string name=\"menu_send\">Enviar</string>\n\n    <string name=\"dialog_title\">Aviso</string>\n    <string name=\"dialog_cancel\">Cancelar</string>\n    <string name=\"dialog_confirm\">Confirmar</string>\n    <string name=\"dialog_setting\">Ir a Ajustes</string>\n    <string name=\"tip_cannot_jump_setting_page\">No se puede ir a los ajustes.</string>\n\n    <string name=\"manual_input\">Entrada manual</string>\n    <string name=\"enter_directory_path\">Introduzca la ruta del directorio</string>\n    <string name=\"invalid_directory\">Ruta de directorio no válida</string>\n    <string name=\"empty_directory_input\">La ruta del directorio no puede estar vacía</string>\n\n    <string name=\"dynamic_click_retry\">Reintentar</string>\n    <string name=\"dynamic_loading\">Cargando</string>\n    <string name=\"draw\">Advertencia</string>\n    <string name=\"edit\">Editar</string>\n    <string name=\"delete\">Borrar</string>\n    <string name=\"delete_select_group\">Eliminar grupo seleccionado</string>\n    <string name=\"delete_all\">Borrar todo</string>\n    <string name=\"replace\">Reemplazar</string>\n    <string name=\"replace_purify\">Reemplazo</string>\n    <string name=\"replace_purify_desc\">Configurar reglas de reemplazo</string>\n    <string name=\"custom_export_section\">Custom export chapter of epub</string>\n    <string name=\"not_available\">No disponible</string>\n    <string name=\"enable\">Activar</string>\n    <string name=\"replace_purify_search\">Buscar reemplazo</string>\n    <string name=\"bookshelf\">Estantería</string>\n    <string name=\"favorites\">Favoritos</string>\n    <string name=\"favorite\">Favorito</string>\n    <string name=\"in_favorites\">en Favoritos</string>\n    <string name=\"out_favorites\">No está en Favoritos</string>\n    <string name=\"rss\">Suscripción</string>\n    <string name=\"all\">Todo</string>\n    <string name=\"recent_reading\">Lecturas recientes</string>\n    <string name=\"last_read\">Última lectura</string>\n    <string name=\"update_log\">Novedades</string>\n    <string name=\"bookshelf_empty\">La estantería está vacía. ¡Busque libros o añádalos desde una fuente pública!</string>\n    <string name=\"action_search\">Buscar</string>\n    <string name=\"action_download\">Descargar</string>\n    <string name=\"layout_list\">Lista</string>\n    <string name=\"layout_grid3\">Grid-3</string>\n    <string name=\"layout_grid4\">Grid-4</string>\n    <string name=\"layout_grid5\">Grid-5</string>\n    <string name=\"layout_grid6\">Grid-6</string>\n    <string name=\"bookshelf_layout\">Layout</string>\n    <string name=\"view\">Vista</string>\n    <string name=\"book_library\">Biblioteca</string>\n    <string name=\"book_local\">Importar libros</string>\n    <string name=\"book_source\">Fuente de libros</string>\n    <string name=\"book_source_manage\">Gestionar fuentes</string>\n    <string name=\"book_source_manage_desc\">Crear/Importar/Editar/Gestionar fuentes de libros</string>\n    <string name=\"setting\">Ajustes</string>\n    <string name=\"theme_setting\">Ajustes de Temas</string>\n    <string name=\"theme_setting_s\">Configuraciones relacionadas a los temas visuales</string>\n    <string name=\"other_setting\">Otros ajustes</string>\n    <string name=\"other_setting_s\">Algunas configuraciones relacionadas a su funcionamiento</string>\n    <string name=\"about\">Acerca de</string>\n    <string name=\"donate\">Donaciones</string>\n    <string name=\"exit\">Sair</string>\n    <string name=\"exit_no_save\">Todavía se guardó. ¿Desea continuar editando?</string>\n    <string name=\"read_style\">Tipos de libros</string>\n    <string name=\"version\">Versión</string>\n    <string name=\"local\">Local</string>\n    <string name=\"search\">Buscar</string>\n    <string name=\"origin_format\">Origen: %s</string>\n    <string name=\"read_dur_progress\">Origen: %s</string>\n    <string name=\"book_name\">Título</string>\n    <string name=\"lasted_show\">Última vez: %s</string>\n    <string name=\"check_add_bookshelf\">¿Desea añadir %s a su Estantería?</string>\n    <string name=\"import_books_count\">%s archivos(s) de texto en total</string>\n    <string name=\"is_loading\">Cargando…</string>\n    <string name=\"retry\">Reintentar</string>\n    <string name=\"web_service\">Servicio Web</string>\n    <string name=\"web_service_desc\">Fuente de edición web y lectura de libros</string>\n    <string name=\"web_edit_source\">Editar fuentes de libros en la web</string>\n    <string name=\"offline_cache\">Caché sin conexión</string>\n    <string name=\"offline_cache_t\">Caché sin conexión</string>\n    <string name=\"offline_cache_s\">precarga el o los capítulos(s) seleccionado(s) en Almacenamiento en caché</string>\n    <string name=\"change_origin\">Cambiar origen</string>\n    <string name=\"about_description\">\\u3000\\u3000 Este es una aplicación de lectura bajo software libre, desarrollado en Kotlin, en que podrás participar. </string>\n    <string name=\"app_share_description\">Legado (YueDu 3.0) disponible para descargar en：\\n https://github.com/gedoor/legado/releases</string>\n    <string name=\"version_name\">Versión %s</string>\n    <string name=\"pt_background_verification\">Verificación en segundo plano</string>\n    <string name=\"ps_background_verification\">podrá utilizar con libertad cuando se verifique la fuente del libro</string>\n    <string name=\"pt_auto_refresh\">Actualización automática</string>\n    <string name=\"ps_auto_refresh\">Actualiza los libros automáticamente al abrir la App</string>\n    <string name=\"pt_auto_download\">Descarga automática</string>\n    <string name=\"ps_auto_download\">Baja los últimos capítulos automaticamente al actualizar los libros</string>\n    <string name=\"backup_restore\">Respaldo y restauración</string>\n    <string name=\"web_dav_set\">Ajustes de WebDav</string>\n    <string name=\"web_dav_set_import_old\">Ajustes de WebDave importación de datos anteriores</string>\n    <string name=\"backup\">Respaldar</string>\n    <string name=\"restore\">Restaurar</string>\n    <string name=\"backup_permission\">El respaldo necesita de permisos de almacenamineto</string>\n    <string name=\"restore_permission\">La restauración necesita de permisos de almacenamineto</string>\n    <string name=\"ok\">Aceptar</string>\n    <string name=\"cancel\">Cancelar</string>\n    <string name=\"backup_confirmation\">Confirmar respaldo</string>\n    <string name=\"backup_message\">Los nuevos archivos de respaldo serán reemplazados con los anteriores.\\n Carpeta de respaldo: YueDu</string>\n    <string name=\"restore_confirmation\">Confirmar restauración</string>\n    <string name=\"restore_message\">La restauración de los datos de la estantería reemplazará a los datos de la actual.</string>\n    <string name=\"backup_success\">Respaldo completado</string>\n    <string name=\"backup_fail\">Error de respaldo\\n%s</string>\n    <string name=\"on_restore\">Restaurando</string>\n    <string name=\"restore_success\">Restauración completada</string>\n    <string name=\"restore_fail\">Restauración fallida</string>\n    <string name=\"screen_direction\">Orientación de pantalla</string>\n    <string name=\"screen_sensor\">Auto(sensor)</string>\n    <string name=\"screen_landscape\">Paisaje</string>\n    <string name=\"screen_portrait\">Retrato</string>\n    <string name=\"screen_unspecified\">Adaptado del sistema</string>\n    <string name=\"disclaimer\">Descargo de responsabilidad</string>\n    <string name=\"all_chapter_num\">%d capítulos</string>\n    <string name=\"interface_setting\">Interfaz</string>\n    <string name=\"brightness\">Brillo</string>\n    <string name=\"chapter_list\">Capítulos</string>\n    <string name=\"next_chapter\">Próximo</string>\n    <string name=\"previous_chapter\">Anterior</string>\n    <string name=\"pt_hide_status_bar\">Ocultar barra de estado</string>\n    <string name=\"ps_hide_status_bar\">Oculta la barra de navegación del sistema durante lectura</string>\n    <string name=\"read_aloud\">Voz</string>\n    <string name=\"read_aloud_t\">Hablando</string>\n    <string name=\"read_aloud_s\">Clic para abrir la lectura</string>\n    <string name=\"audio_play\">Reproducir</string>\n    <string name=\"audio_play_t\">Reproduciendo</string>\n    <string name=\"audio_play_s\">Clic para abrir la reproducción</string>\n    <string name=\"audio_pause\">Pausar</string>\n    <string name=\"text_return\">Regresar</string>\n    <string name=\"refresh\">Actualizar</string>\n    <string name=\"start\">Iniciar</string>\n    <string name=\"stop\">Detener</string>\n    <string name=\"pause\">Pausar</string>\n    <string name=\"resume\">Continuar</string>\n    <string name=\"set_timer\">Cronómetro</string>\n    <string name=\"read_aloud_pause\">Voz pausada</string>\n    <string name=\"read_aloud_timer\">Hablando (%d min restantes)</string>\n    <string name=\"playing_timer\">Reproduciendo (%d min restantes)</string>\n    <string name=\"ps_hide_navigation_bar\">Ocultar botones virtuales durante lectura</string>\n    <string name=\"pt_hide_navigation_bar\">Oculta barra de navegación</string>\n    <string name=\"re_navigation_bar_color\">Color de barra de navegación</string>\n    <string name=\"scoring\">Evaluación</string>\n    <string name=\"send_mail\">E-mail</string>\n    <string name=\"can_not_open\">Error al abrir</string>\n    <string name=\"can_not_share\">Error al compartir</string>\n    <string name=\"no_chapter\">Sin capítulos</string>\n    <string name=\"add_url\">Añadir Url</string>\n    <string name=\"add_book_url\">Añadir Url de libro</string>\n    <string name=\"background\">Segundo plano</string>\n    <string name=\"author\">Autor</string>\n    <string name=\"author_show\">Autor: %s</string>\n    <string name=\"aloud_stop\">Voz detelendia</string>\n    <string name=\"clear_cache\">Limpiar caché</string>\n    <string name=\"clear_cache_success\">Se limpió la caché</string>\n    <string name=\"action_save\">Guardar</string>\n    <string name=\"edit_source\">Editar fuente</string>\n    <string name=\"edit_book_source\">Editar fuente de libro</string>\n    <string name=\"disable_book_source\">Desactivar fuente de libro</string>\n    <string name=\"add_book_source\">Añadir fuente de libro</string>\n    <string name=\"add_rss_source\">Añadir fuente de suscripción</string>\n    <string name=\"book_file_selector\">Añadir libros</string>\n    <string name=\"scan_book_source\">Buscar</string>\n    <string name=\"copy_source\">Copiar fuente</string>\n    <string name=\"paste_source\">Pegar fuente</string>\n    <string name=\"source_rule_s\">Descripción de las reglas de fuente</string>\n    <string name=\"check_update\">Comprobar actualizaciones</string>\n    <string name=\"camera_scan\">Digitalizar código QR</string>\n    <string name=\"scan_image\">Digitalizar imágenes locales</string>\n    <string name=\"rule_summary\">Descripción de las reglas</string>\n    <string name=\"share\">Compartir</string>\n    <string name=\"share_app\">Compartir vía</string>\n    <string name=\"flow_sys\">Adaptar del sistema</string>\n    <string name=\"add\">Añadir</string>\n    <string name=\"import_book_source\">Importar fuentes del libro</string>\n    <string name=\"import_local\">Importar localmente</string>\n    <string name=\"import_on_line\">Importar en línea</string>\n    <string name=\"replace_rule_title\">Reemplazo</string>\n    <string name=\"replace_rule_edit\">Editar regla de reemplazo</string>\n    <string name=\"replace_rule\">Modelo</string>\n    <string name=\"replace_to\">Reemplazo</string>\n    <string name=\"img_cover\">Portada</string>\n    <string name=\"book\">Libro</string>\n    <string name=\"volume_key_page\">Botones de volumen para pasar página</string>\n    <string name=\"mouse_wheel_page\">Rueda del ratón para pasar página</string>\n    <string name=\"click_turn_page\">Toque la pantalla para pasar página</string>\n    <string name=\"page_anim\">Animación de hojeada</string>\n    <string name=\"book_page_anim\">Animación de hojeada (libro)</string>\n    <string name=\"keep_light\">Mantener pantalla encendida</string>\n    <string name=\"back\">Regresar</string>\n    <string name=\"menu\">Menú</string>\n    <string name=\"adjust\">Ajuste</string>\n    <string name=\"scroll_bar\">Barra de deslizamiento</string>\n    <string name=\"clear_all_content\">Limpiar la caché borrará todos los capítulos guardados. ¿Está seguro de limpiarlos?</string>\n    <string name=\"book_source_share_url\">Compartir fuentes de libros</string>\n    <string name=\"replace_rule_summary\">Nombre de regla</string>\n    <string name=\"replace_rule_invalid\">La regla está vacía o es incompatible con los lineamientos de Regex.</string>\n    <string name=\"select_action\">Acción de selección</string>\n    <string name=\"select_all\">Seleccionar todo</string>\n    <string name=\"select_all_count\">Selecionar todo (%1$d/%2$d)</string>\n    <string name=\"select_cancel_count\">Dejar de seleccionar todo (%1$d/%2$d)</string>\n    <string name=\"dark_theme\">Modo oscuro</string>\n    <string name=\"welcome\">Página de bienvenida</string>\n    <string name=\"download_start\">Iniciar descarga</string>\n    <string name=\"download_cancel\">Cancelar descarga</string>\n    <string name=\"no_download\">Excluir de descarga</string>\n    <string name=\"download_count\">Descargando %1$d/%2$d</string>\n    <string name=\"import_select_book\">Importar libro(s) seleccionado(s)</string>\n    <string name=\"threads_num_title\">Número de tareas en simultáneo</string>\n    <string name=\"change_icon\">Cambiar ícono</string>\n    <string name=\"remove_from_bookshelf\">Quitar</string>\n    <string name=\"start_read\">Iniciar lectura</string>\n    <string name=\"data_loading\">Cargando…</string>\n    <string name=\"load_error_retry\">Error cargando, toque para volver a iniciar</string>\n    <string name=\"book_intro\">Descripción de libro</string>\n    <string name=\"intro_show\">Descripción:%s</string>\n    <string name=\"intro_show_null\">Descripción: sin introducción</string>\n    <string name=\"open_from_other\">Abrir libro externo</string>\n    <string name=\"origin_show\">Origen: %s</string>\n    <string name=\"import_replace_rule\">Importar reglas replace</string>\n    <string name=\"import_replace_rule_on_line\">Importar reglas en línea</string>\n    <string name=\"check_update_interval\">Intervalo de actualizaciones</string>\n    <string name=\"bookshelf_px_0\">Lista de recientes</string>\n    <string name=\"bookshelf_px_1\">Fecha de actualización</string>\n    <string name=\"bookshelf_px_2\">Título del libro</string>\n    <string name=\"bookshelf_px_3\">Ordenar manualmente</string>\n    <string name=\"read_type\">Estrategia de lectura</string>\n    <string name=\"compose_type\">Tipografía</string>\n    <string name=\"del_select\">Quitar selecionados</string>\n    <string name=\"del_msg\">¿Desea quitarlos?</string>\n    <string name=\"clear_font\">Limpiar fuente</string>\n    <string name=\"find_on_www\">Descubrir</string>\n    <string name=\"find_source_manage\">Descubrir</string>\n    <string name=\"find_empty\">Sin contenido. Diríjase al Administrador de fuentes para añadirlo.</string>\n    <string name=\"del_all\">Quitar todo</string>\n    <string name=\"searchHistory\">Historial de búsqueda</string>\n    <string name=\"clear\">Limpiar</string>\n    <string name=\"showTitle\">Mostrar título de libro en texto</string>\n    <string name=\"refresh_default\">Sincronización de fuentes de libros</string>\n    <string name=\"no_last_chapter\">Sin último capítulo.</string>\n    <string name=\"showTimeBattery\">Mostrar tiempo y batería</string>\n    <string name=\"showLine\">Divisor de pantalla</string>\n    <string name=\"dark_status_icon\">Oscurecer ícono de barra de estado</string>\n    <string name=\"content\">Contenido</string>\n    <string name=\"copy_text\">Copiar</string>\n    <string name=\"download_all\">Bajar todo</string>\n    <string name=\"content_sl\">Este es un texto de prueba, \\n\\u3000\\u3000 sólo para mostrar el resultado</string>\n    <string name=\"text_bg_style\">Color de fondo (mantén pulsado para personalizar)</string>\n    <string name=\"immersion_status_bar\">Barra de estado inmersiva</string>\n    <string name=\"un_download\">%d capítulo(s) restante(s)</string>\n    <string name=\"long_click_input_color\">Mantén pulsado para introducir el valor de color</string>\n    <string name=\"loading\">Cargando…</string>\n    <string name=\"group_zg\">Preparando</string>\n    <string name=\"group_yf\">Preparando algo más</string>\n    <string name=\"bookmark\">Favoritos</string>\n    <string name=\"bookmark_add\">Añadir a Favoritos</string>\n    <string name=\"action_del\">Quitar</string>\n    <string name=\"load_over_time\">Tiempo límite de carga</string>\n    <string name=\"join_group\">Siga:%s</string>\n    <string name=\"copy_complete\">Copiado con éxito</string>\n    <string name=\"bookshelf_management\">Organización de estantería</string>\n    <string name=\"clear_bookshelf_s\">Esto borrará a todos los libros. Por favor, tenga cuidado.</string>\n    <string name=\"search_book_source\">Buscar fuentes de libros</string>\n    <string name=\"search_rss_source\">Buscar fuentes de suscripción</string>\n    <string name=\"search_book_source_num\">Búsqueda (%d fuentes en total)</string>\n    <string name=\"chapter_list_size\">Capítulos (%d)</string>\n    <string name=\"text_bold\">Negrito</string>\n    <string name=\"text_font\">Fuente</string>\n    <string name=\"text\">Texto</string>\n    <string name=\"home_page\">Página inicial</string>\n    <string name=\"right\">Derecha</string>\n    <string name=\"left\">Izquierda</string>\n    <string name=\"bottom\">Parte inferior</string>\n    <string name=\"top\">Parte superior</string>\n    <string name=\"padding\">Espaciado</string>\n    <string name=\"padding_top\">Espacio superior</string>\n    <string name=\"padding_bottom\">Espacio inferior</string>\n    <string name=\"padding_left\">Espacio derecho</string>\n    <string name=\"padding_right\">Espacio izquiedo</string>\n    <string name=\"check_book_source\">Verificar fuentes del libro</string>\n    <string name=\"check_select_source\">Verificar fuente seleccionada</string>\n    <string name=\"progress_show\">%1$s      Avance %2$d/%3$d</string>\n    <string name=\"tts_fix\">¡Por favor, instale y seleccione un TTS en chino!</string>\n    <string name=\"tts_init_failed\">¡Error al inicializar TTS!</string>\n    <string name=\"jf_convert\">Conversión a simplificada</string>\n    <string name=\"jf_convert_o\">Desactivado</string>\n    <string name=\"jf_convert_f\">Simplificado a tradicional</string>\n    <string name=\"jf_convert_j\">Tradicional a simplificado</string>\n    <string name=\"page_mode\">Modo de hojeada</string>\n    <string name=\"nb_file_sub_count\">%1$d elementos</string>\n    <string name=\"nb_file_path\">Almacenamiento:</string>\n    <string name=\"nb_file_add_shelf\">Adicionar ao Estante</string>\n    <string name=\"nb_file_add_shelves\">Añadir a Estantería (%1$d)</string>\n    <string name=\"nb_file_add_succeed\">%1$d libros añadidos con éxito</string>\n    <string name=\"fonts_folder\">Por favor, coloque sus archivos de fuente al almacenamiento raíz y vuelve a seleccionar</string>\n    <string name=\"default_font\">Fuente predeterminada</string>\n    <string name=\"select_font\">Seleccionar fuentes</string>\n    <string name=\"text_size\">Tamaño de texto</string>\n    <string name=\"line_size\">Espacio entre líneas</string>\n    <string name=\"paragraph_size\">Espacio entre párrafos</string>\n    <string name=\"to_top\">Encima</string>\n    <string name=\"selection_to_top\">Selection To Top</string>\n    <string name=\"to_bottom\">Debajo</string>\n    <string name=\"selection_to_bottom\">Selection To Bottom</string>\n    <string name=\"auto_expand_find\">Autoexpandir Descubrir</string>\n    <string name=\"default_expand_first\">Expande por defecto la primera parte de Descubrir.</string>\n    <string name=\"threads_num\">Líneas actuales %s</string>\n    <string name=\"read_aloud_speed\">Velocidad de voz</string>\n    <string name=\"auto_next_page\">Deslizamiento automático</string>\n    <string name=\"auto_next_page_stop\">Impedir deslizamiento automático</string>\n    <string name=\"auto_next_page_speed\">Velocidad de deslizamiento automático</string>\n    <string name=\"book_info\">Información sobre libro</string>\n    <string name=\"book_info_edit\">Editar información del libro</string>\n    <string name=\"ps_default_read\">Establecer la estantería como página de inicio</string>\n    <string name=\"pt_default_read\">Ir automáticamente a la lista de Recientes</string>\n    <string name=\"replace_scope\">Objeto sustituto. El nombre del libro o la URL de origen están disponibles</string>\n    <string name=\"menu_action_group\">Grupos</string>\n    <string name=\"download_path\">Carpeta de caché</string>\n    <string name=\"sys_file_picker\">Selector de archivos del sistema</string>\n    <string name=\"new_version\">Nueva versión</string>\n    <string name=\"download_update\">Descargar actualizaciones</string>\n    <string name=\"volume_key_page_on_play\">Botones de volumen para pasar las páginas mientras lee</string>\n    <string name=\"tip_margin_change\">Ajuste de margen</string>\n    <string name=\"allow_update\">Activar actualizaciones</string>\n    <string name=\"disable_update\">Desactivar actualizaciones</string>\n    <string name=\"split_long_chapter\">Dividir secciones largas</string>\n    <string name=\"need_more_time_load_content\">正文长度过长时，加载正文可能会花费更多时间</string>\n    <string name=\"revert_selection\">Invertir</string>\n    <string name=\"search_book_key\">Buscar libro por nombre o autor</string>\n    <string name=\"debug_hint\">Nombre del libro, autor, URL</string>\n    <string name=\"faq\">Preguntas frecuentes</string>\n    <string name=\"pt_show_all_find\">Mostrar todos los buscados</string>\n    <string name=\"ps_show_all_find\">Mostrar la fuente de búsqueda si Descubrir fue cerrado</string>\n    <string name=\"update_toc\">Actualizar capítulos</string>\n    <string name=\"txt_toc_rule\">Capítulos de Txt Rule</string>\n    <string name=\"set_charset\">Codificación de texto</string>\n    <string name=\"swap_sort\">Orden ascendente/descendente</string>\n    <string name=\"sort\">Ordenar</string>\n    <string name=\"sort_auto\">Ordenar automáticamente</string>\n    <string name=\"sort_default\">Ordenar por defecto</string>\n    <string name=\"sort_manual\">Ordenar manualmente</string>\n    <string name=\"sort_by_name\">Ordenar por nombre</string>\n    <string name=\"go_to_top\">Ir al principio</string>\n    <string name=\"go_to_bottom\">Ir al final</string>\n    <string name=\"read_y\">Leído: %s</string>\n    <string name=\"pursue_more\">Awaiting update</string>\n    <string name=\"fattening\">Preparando un poco más</string>\n    <string name=\"finish\">Listo</string>\n    <string name=\"all_book\">Todo</string>\n    <string name=\"pursue_more_book\">Preparando actualización de libros</string>\n    <string name=\"fattening_book\">Preparando más actualizaciones de libros</string>\n    <string name=\"finish_book\">Libros concluidos</string>\n    <string name=\"local_book\">Libros locales</string>\n    <string name=\"status_bar_immersion\">El color de la barra de estado se vuelve transparente</string>\n    <string name=\"imm_navigation_bar\">barra de navegación de inmersión</string>\n    <string name=\"imm_navigation_bar_s\">La barra de navegación se vuelve transparente</string>\n    <string name=\"add_to_bookshelf\">Agregar a la estantería</string>\n    <string name=\"continue_read\">Seguir leyendo</string>\n    <string name=\"cover_path\">Carpeta de portada</string>\n    <string name=\"page_anim_cover\">Portada</string>\n    <string name=\"page_anim_slide\">Diapositiva</string>\n    <string name=\"page_anim_simulation\">Simulación</string>\n    <string name=\"page_anim_scroll\">Desplazado</string>\n    <string name=\"page_anim_none\">Ninguno</string>\n    <string name=\"disable_manga_page_anim\">Desactivar animación de volteo</string>\n    <string name=\"up_change_source_last_chapter_t\">Actualiza el último capítulo después del cambio de fuente en segundo plano</string>\n    <string name=\"up_change_source_last_chapter_s\">si está habilitado, la actualización comenzará 1 minuto después de que se abra la aplicación</string>\n    <string name=\"behavior_main_t\">Ocultar automáticamente la barra de herramientas</string>\n    <string name=\"behavior_main_s\">La barra de herramientas se ocultará automáticamente cuando deslice la Librería</string>\n    <string name=\"login\">Iniciar sesión</string>\n    <string name=\"login_source\">Iniciar sesión %s</string>\n    <string name=\"success\">Completado</string>\n    <string name=\"source_no_login\">La fuente actual no se ha configurado con una dirección de inicio de sesión</string>\n    <string name=\"no_prev_page\">Sin página anterior</string>\n    <string name=\"no_next_page\">Sin página siguiente</string>\n\n    <!-- source start-->\n    <string name=\"source_name\">源名称(sourceName)</string>\n    <string name=\"source_url\">源URL(sourceUrl)</string>\n    <string name=\"source_group\">源分组(sourceGroup)</string>\n    <string name=\"diy_source_group\">自定义源分组</string>\n    <string name=\"diy_edit_source_group\">输入自定义源分组名称</string>\n    <string name=\"concurrent_rate\">并发率(concurrentRate)</string>\n    <string name=\"sort_url\">分类Url(sortUrl)</string>\n    <string name=\"login_url\">登录URL(loginUrl)</string>\n    <string name=\"login_ui\">登录UI(loginUi)</string>\n    <string name=\"login_check_js\">登录检查JS(loginCheckJs)</string>\n    <string name=\"comment\">源注释(sourceComment)</string>\n    <string name=\"r_search_url\">搜索地址(url)</string>\n    <string name=\"r_find_url\">发现地址规则(url)</string>\n    <string name=\"r_book_list\">书籍列表规则(bookList)</string>\n    <string name=\"r_book_name\">书名规则(name)</string>\n    <string name=\"r_book_url\">详情页url规则(bookUrl)</string>\n    <string name=\"r_author\">作者规则(author)</string>\n    <string name=\"rule_book_kind\">分类规则(kind)</string>\n    <string name=\"rule_book_intro\">简介规则(intro)</string>\n    <string name=\"rule_cover_url\">封面规则(coverUrl)</string>\n    <string name=\"rule_last_chapter\">最新章节规则(lastChapter)</string>\n    <string name=\"rule_word_count\">字数规则(wordCount)</string>\n    <string name=\"book_url_pattern\">书籍URL正则(bookUrlPattern)</string>\n    <string name=\"rule_book_info_init\">预处理规则(bookInfoInit)</string>\n    <string name=\"rule_toc_url\">目录URL规则(tocUrl)</string>\n    <string name=\"rule_can_re_name\">允许修改书名作者(canReName)</string>\n    <string name=\"rule_next_toc_url\">目录下一页规则(nextTocUrl)</string>\n    <string name=\"rule_chapter_list\">目录列表规则(chapterList)</string>\n    <string name=\"rule_chapter_name\">章节名称规则(ChapterName)</string>\n    <string name=\"rule_chapter_url\">章节URL规则(chapterUrl)</string>\n    <string name=\"rule_is_volume\">Volume mark(isVolume)</string>\n    <string name=\"rule_is_vip\">VIP标识(isVip)</string>\n    <string name=\"rule_update_time\">更新时间(ChapterInfo)</string>\n    <string name=\"pre_update_js\">更新之前Js(preUpdateJs)</string>\n    <string name=\"rule_book_content\">正文规则(content)</string>\n    <string name=\"rule_next_content\">正文下一页URL规则(nextContentUrl)</string>\n    <string name=\"rule_web_js\">WebViewJs(webJs)</string>\n    <string name=\"rule_source_regex\">资源正则(sourceRegex)</string>\n    <string name=\"rule_replace_regex\">替换规则(replaceRegex)</string>\n    <string name=\"rule_image_style\">图片样式(imageStyle)</string>\n    <string name=\"rule_pay_action\">pay action(payAction)</string>\n\n    <string name=\"source_icon\">图标(sourceIcon)</string>\n    <string name=\"r_articles\">列表规则(ruleArticles)</string>\n    <string name=\"r_next\">列表下一页规则(ruleNextArticles)</string>\n    <string name=\"r_title\">标题规则(ruleTitle)</string>\n    <string name=\"r_guid\">guid规则(ruleGuid)</string>\n    <string name=\"r_date\">时间规则(rulePubDate)</string>\n    <string name=\"r_categories\">类别规则(ruleCategories)</string>\n    <string name=\"r_description\">描述规则(ruleDescription)</string>\n    <string name=\"r_image\">图片url规则(ruleImage)</string>\n    <string name=\"r_content\">内容规则(ruleContent)</string>\n    <string name=\"r_style\">样式(style)</string>\n    <string name=\"r_inject_js\">注入Js(injectJs)</string>\n    <string name=\"r_link\">链接规则(ruleLink)</string>\n    <string name=\"check_key_word\">校验关键字(checkKeyWord)</string>\n    <string name=\"rule_actions\">操作(actions)</string>\n    <string name=\"rule_is_pay\">购买标识(isPay)</string>\n    <!-- source end-->\n\n    <!--error string start-->\n    <string name=\"error_no_source\">Sin fuentes</string>\n    <string name=\"error_get_book_info\">Error al obtener la información del libro</string>\n    <string name=\"error_get_content\">Error al obtener el contenido</string>\n    <string name=\"error_get_chapter_list\">Error al obtener la lista de capítulos</string>\n    <string name=\"error_get_web_content\">Error al acceder al sitio: %s</string>\n    <string name=\"error_read_file\">Error al leer el archivo</string>\n    <string name=\"error_load_toc\">Error al cargar la lista de capítulos</string>\n    <string name=\"error_get_data\">Error al obtener los datos</string>\n    <string name=\"error_load_msg\">Error al cargar\\n%s</string>\n    <string name=\"net_error_10001\">Sin conexión</string>\n    <string name=\"net_error_10002\">Tiempo de espera de conexión en línea</string>\n    <string name=\"net_error_10003\">Error en el procesamiento de datos</string>\n    <!--error string end-->\n\n    <string name=\"source_http_header\">Encabezado HTTP</string>\n    <string name=\"debug_source\">Fuente de depuración</string>\n    <string name=\"import_by_qr_code\">Importar desde código QR</string>\n    <string name=\"share_selected_source\">Compartir fuentes seleccionadas</string>\n    <string name=\"scan_qr_code\">Escanear código QR</string>\n    <string name=\"click_on_selected_show_menu\">Toque para mostrar el menú cuando esté seleccionado</string>\n    <string name=\"theme\">Tema</string>\n    <string name=\"theme_mode\">Tipo de tema</string>\n    <string name=\"theme_mode_desc\">Seleccione el tema que prefiera</string>\n    <string name=\"join_qq_group\">Únase al grupo QQ</string>\n    <string name=\"bg_image_per\">Para establecer la imagen de fondo requiere del permiso de almacenamiento</string>\n    <string name=\"input_book_source_url\">Ingrese la dirección de origen del libro</string>\n    <string name=\"del_file\">Borrar archivo</string>\n    <string name=\"del_file_success\">Archivo borrado</string>\n    <string name=\"sure_del_file\">¿Está seguro de que desea borrar este archivo?</string>\n    <string name=\"files_tree\">Carpeta</string>\n    <string name=\"intelligent_import\">Importación inteligente</string>\n    <string name=\"discovery\">Descubrir</string>\n    <string name=\"switch_display_style\">Cambiar modo de visualización</string>\n    <string name=\"import_per\">La importación de libros locales requiere permiso de almacenamiento</string>\n    <string name=\"night_theme\">Tema nocturno</string>\n    <string name=\"eink_theme\">E-Ink</string>\n    <string name=\"eink_theme_desc\">Optimización para dispositivos E-ink</string>\n    <string name=\"get_storage_per\">requiere permiso</string>\n    <string name=\"double_click_exit\">Toque nuevamente para salir de la aplicación</string>\n    <string name=\"import_book_per\">La importación de libros locales requiere permiso de almacenamiento</string>\n    <string name=\"network_connection_unavailable\">La conexión en línea no está disponible</string>\n    <string name=\"yes\">Sí</string>\n    <string name=\"no\">No</string>\n    <string name=\"sure\">Aceptar</string>\n    <string name=\"sure_del\">¿Está seguro que desea borrar?</string>\n    <string name=\"sure_del_any\">¿Está seguro de que desea borra %s?</string>\n    <string name=\"sure_del_all_book\">Está seguro de que desea eliminar todos los libros?</string>\n    <string name=\"sure_del_download_book\">¿También desea eliminar los capítulos de libros descargados?</string>\n    <string name=\"qr_per\">Escanear el código QR requiere permiso de la cámara</string>\n    <string name=\"aloud_can_not_auto_page\">La voz se está ejecutando, no se puede pasar las páginas automáticamente</string>\n    <string name=\"input_charset\">Codificación de entrada</string>\n    <string name=\"text_chapter_list_rule\">Capítulos de Txt Regex</string>\n    <string name=\"open_local_book_per\">Para abrir libros locales, se requiere permiso de almacenamiento</string>\n    <string name=\"no_book_name\">Libro sin nombre</string>\n    <string name=\"input_replace_url\">Ingrese la URL de la regla de reemplazo</string>\n    <string name=\"get_book_list_success\">La lista de búsqueda se obtuvo con éxito%d</string>\n    <string name=\"non_null_name_url\">el nombre y la URL no pueden estar vacíos</string>\n    <string name=\"gallery\">Galería</string>\n    <string name=\"get_ali_pay_hb\">obtenga sobres rojos de AliPay</string>\n    <string name=\"non_update_url\">Sin dirección de actualización</string>\n    <string name=\"check_host_cookie\">Abra la página de inicio, volverá automáticamente después de completarse</string>\n    <string name=\"click_check_after_success\">Después de iniciar sesión correctamente, toque el icono en la esquina superior derecha para probar el acceso a la página de inicio</string>\n    <string name=\"chapter\">Capítulo</string>\n    <string name=\"to\">Para</string>\n    <string name=\"use_regex\">Usando Regex</string>\n    <string name=\"text_indent\">Sangría</string>\n    <string name=\"indent_0\">Ninguno</string>\n    <string name=\"indent_1\">Sangría con 1 carácter</string>\n    <string name=\"indent_2\">Sangría con 2 carácter</string>\n    <string name=\"indent_3\">Sangría con 3 carácter</string>\n    <string name=\"indent_4\">Sangría con 4 carácter</string>\n    <string name=\"select_folder\">Seleccione una carpeta</string>\n    <string name=\"select_file\">Seleccione un archivo</string>\n    <string name=\"no_find\">Sin Descubrir, puede agregarlo a Fuentes de libros</string>\n    <string name=\"restore_default\">Restaurar valores predeterminados</string>\n    <string name=\"set_download_per\">La carpeta de caché personalizada requiere permiso de almacenamiento</string>\n    <string name=\"black\">Negro</string>\n    <string name=\"content_empty\">Sin contenido</string>\n    <string name=\"on_change_source\">Cambiando fuente, espere</string>\n    <string name=\"chapter_list_empty\">Capítulos sin contenido</string>\n    <string name=\"text_letter_spacing\">Espaciado de palabras</string>\n\n    <string name=\"source_tab_base\">Básico</string>\n    <string name=\"source_tab_search\">Buscar</string>\n    <string name=\"source_tab_find\">Descubrir</string>\n    <string name=\"source_tab_info\">Información</string>\n    <string name=\"source_tab_toc\">Capítulos</string>\n    <string name=\"source_tab_content\">Contenido</string>\n\n    <string name=\"e_ink_mode\">Modo E-Ink</string>\n    <string name=\"e_ink_mode_detail\">Elimina animaciones y optimiza la experiencia para libros en papel electrónico</string>\n    <string name=\"web_menu\">Servicio web</string>\n    <string name=\"web_port_title\">Puerto web</string>\n    <string name=\"web_port_summary\">Puerto actual %s</string>\n    <string name=\"qr_share\">Compartir código QR</string>\n    <string name=\"str_share\">Compartir cadenas</string>\n    <string name=\"wifi_share\">Compartir Wifi</string>\n    <string name=\"please_grant_storage_permission\">Conceda el permiso de almacenamiento</string>\n    <string name=\"fast_rewind\">Rebobinado rápido</string>\n    <string name=\"fast_forward\">Avance rápido</string>\n    <string name=\"skip_previous\">Anterior</string>\n    <string name=\"skip_next\">Siguiente</string>\n    <string name=\"music\">Música</string>\n    <string name=\"audio\">Audio</string>\n    <string name=\"is_enable\">Activar</string>\n    <string name=\"enable_js\">Activar js</string>\n    <string name=\"load_with_base_url\">Cargar URL base</string>\n    <string name=\"all_source\">Todas las fuentes</string>\n    <string name=\"cannot_empty\">El contenido introducido no puede estar vacío</string>\n    <string name=\"clear_find_cache\">Limpiar la caché de búsqueda</string>\n    <string name=\"edit_find\">Editar búsqueda</string>\n    <string name=\"change_icon_summary\">Cambie el icono de software que se muestra en el escritorio</string>\n    <string name=\"help\">Ayuda</string>\n    <string name=\"my\">Mío</string>\n    <string name=\"reading\">Lecturas</string>\n    <string name=\"battery_show\">%d%%</string>\n    <string name=\"timer_m\">%d min</string>\n    <string name=\"read_aloud_by_page\">Leer por páginas</string>\n    <string name=\"brightness_auto\">Auto-Brightness %s</string>\n    <string name=\"speak_engine\">Motor de voz</string>\n    <string name=\"bg_image\">Imágenes de fondo</string>\n    <string name=\"bg_color\">Color de fondo</string>\n    <string name=\"text_color\">Color del texto</string>\n    <string name=\"select_image\">Seleccione una foto</string>\n    <string name=\"group_manage\">Gestión de grupo</string>\n    <string name=\"group_select\">Selección de grupo</string>\n    <string name=\"group_edit\">Edición de grupo</string>\n    <string name=\"move_to_group\">Mover al grupo</string>\n    <string name=\"add_group\">Añadir a grupos</string>\n    <string name=\"remove_group\">Quitar de grupos</string>\n    <string name=\"add_replace_rule\">Nuevo reemplazo</string>\n    <string name=\"group\">Grupo</string>\n    <string name=\"group_s\">Grupo: %s</string>\n    <string name=\"toc_s\">Capítulos: %s</string>\n    <string name=\"enable_explore\">Activar Descubrir</string>\n    <string name=\"disable_explore\">Desactivar Descubrir</string>\n    <string name=\"enable_selection\">Activar selección</string>\n    <string name=\"disable_selection\">Desactivar selección</string>\n    <string name=\"export_selection\">Exportar seleccionados</string>\n    <string name=\"export\">Exportar</string>\n    <string name=\"load_toc\">Cargar capítulos</string>\n    <string name=\"load_info\">Cargar detalles del libro</string>\n    <string name=\"tts\">TTS</string>\n    <string name=\"web_dav_pw\">Contraseña de WebDav</string>\n    <string name=\"web_dav_pw_s\">Ingrese su contraseña de WebDav</string>\n    <string name=\"web_dav_url_s\">Ingrese la dirección de su servidor</string>\n    <string name=\"web_dav_url\">Dirección del servidor WebDav</string>\n    <string name=\"web_dav_account\">Cuenta WebDav</string>\n    <string name=\"web_dav_account_s\">Ingrese su cuenta WebDav</string>\n    <string name=\"rss_source\">Fuente de suscripción</string>\n    <string name=\"rss_source_edit\">Editar fuente de suscripción</string>\n    <string name=\"screen\">Filtro</string>\n    <string name=\"screen_find\">Search Discovery sources</string>\n    <string name=\"dur_pos\">Ubicación actual:</string>\n    <string name=\"precision_search\">Búsqueda precisa</string>\n    <string name=\"service_starting\">Iniciando servicio</string>\n    <string name=\"empty\">Vacío</string>\n    <string name=\"file_chooser\">Selección de archivos</string>\n    <string name=\"folder_chooser\">Selección de carpetas</string>\n    <string name=\"bottom_line\">¡Finalizado!</string>\n    <string name=\"uri_to_path_fail\">Error en la dirección de Uri</string>\n    <string name=\"refresh_cover\">Actualizar portada</string>\n    <string name=\"change_cover_source\">Cambiar fuente</string>\n    <string name=\"select_local_image\">Imagen local</string>\n    <string name=\"book_type\">Tipo:</string>\n    <string name=\"to_backstage\">Fondo</string>\n    <string name=\"importing\">Importando</string>\n    <string name=\"exporting\">Exportando</string>\n    <string name=\"custom_page_key\">Defina qué botones pasan de página</string>\n    <string name=\"prev_page_key\">Botón de página anterior</string>\n    <string name=\"next_page_key\">Botón de página siguiente</string>\n    <string name=\"no_group\">Sin grupo</string>\n    <string name=\"prev_sentence\">Oración anterior</string>\n    <string name=\"next_sentence\">Siguiente oración</string>\n    <string name=\"other_folder\">Otra carpeta</string>\n    <string name=\"text_too_long_qr_error\">Demasiadas palabras para crear un código QR</string>\n    <string name=\"share_rss_source\">Compartir fuentes de suscripción</string>\n    <string name=\"share_book_source\">Compartir fuentes de libros</string>\n    <string name=\"auto_dark_mode\">Cambio automático al modo oscuro</string>\n    <string name=\"auto_dark_mode_s\">Sigue el modo oscuro del sistema</string>\n    <string name=\"go_back\">Atrás</string>\n    <string name=\"tone_colour\">Tono de voz en línea</string>\n    <string name=\"select_count\">(%1$d/%2$d)</string>\n    <string name=\"show_rss\">Mostrar suscripción</string>\n    <string name=\"service_stop\">Servicio detenido</string>\n    <string name=\"service_start\">Iniciando el servicio\\nComprobando la barra de notificaciones para más detalles</string>\n    <string name=\"default_path\">Carpeta predeterminada</string>\n    <string name=\"sys_folder_picker\">Selector de carpetas del sistema</string>\n    <string name=\"app_folder_picker\">Selector de carpetas de aplicaciones</string>\n    <string name=\"app_file_picker\">Selector de archivos de la aplicación</string>\n    <string name=\"a10_permission_toast\">En Android 10+ no puede leer ni guardar archivos debido a restricciones de permisos</string>\n    <string name=\"add_to_text_context_menu_s\">Mantenga pulsado para mostrar Legado·Búsqueda en el menú de operaciones</string>\n    <string name=\"add_to_text_context_menu_t\">Buscar en pantalla de operación de texto</string>\n    <string name=\"record_log\">Registro</string>\n    <string name=\"log\">Registro</string>\n    <string name=\"chinese_converter\">Conversión a simplificada</string>\n    <string name=\"change_icon_error\">El ícono es un ícono vectorial, que no era compatible antes de Android 8.0</string>\n    <string name=\"aloud_config\">Speech settings</string>\n    <string name=\"main_activity\">Start page</string>\n    <string name=\"selectText\">Long Tap to select text</string>\n    <string name=\"header\">Header</string>\n    <string name=\"main_body\">Content</string>\n    <string name=\"footer\">Footer</string>\n    <string name=\"select_end\">Select end</string>\n    <string name=\"select_start\">Select start</string>\n    <string name=\"share_layout\">Shared layout</string>\n    <string name=\"browser\">Browser</string>\n    <string name=\"import_default_rule\">Import default rules</string>\n    <string name=\"name\">Name</string>\n    <string name=\"regex\">Regex</string>\n    <string name=\"more_menu\">More menu</string>\n    <string name=\"reduce\">Minus</string>\n    <string name=\"plus\">Plus</string>\n    <string name=\"system_typeface\">System typeface</string>\n    <string name=\"delete_book_file\">Delete source file</string>\n    <string name=\"text_default\">Default</string>\n    <string name=\"default1\">Default-1</string>\n    <string name=\"default2\">Default-2</string>\n    <string name=\"default3\">Default-3</string>\n    <string name=\"title\">Title</string>\n    <string name=\"title_left\">Left</string>\n    <string name=\"title_center\">Center</string>\n    <string name=\"title_hide\">Hide</string>\n    <string name=\"add_to_group\">Add to Group</string>\n    <string name=\"save_image\">Save image</string>\n    <string name=\"no_default_path\">No default path</string>\n    <string name=\"change_group\">Group settings</string>\n    <string name=\"view_toc\">View Chapters</string>\n    <string name=\"bar_elevation\">Navigation bar shadow</string>\n    <string name=\"bar_elevation_s\">Current shadow size(elevation): %s</string>\n    <string name=\"btn_default_s\">Default</string>\n    <string name=\"main_menu\">Main menu</string>\n    <string name=\"request_permission\">Tap to grant permission</string>\n    <string name=\"tip_local_perm_request_storage\">Legado needs Storage permission, please tap the \"Grant Permission\" button below, or go to \"Settings\"-\"Application Permissions\"-to open the required permission. If the permission is still not work, please tap \"Select Folder\" in the upper right corner to use the system folder picker.</string>\n    <string name=\"alouding_disable\">The selected text cannot be spoken in full text speech</string>\n    <string name=\"read_body_to_lh\">Extend to cutout</string>\n    <string name=\"toc_updateing\">Updating Chapters</string>\n\n    <string name=\"media_button_on_exit_title\">Headset buttons are always available</string>\n    <string name=\"media_button_on_exit_summary\">Headset buttons are available even exit the app.</string>\n    <string name=\"contributors\">Contributors</string>\n    <string name=\"contact\">Contact</string>\n    <string name=\"license\">License</string>\n    <string name=\"other\">Other</string>\n    <string name=\"official_account\">开源阅读</string>\n    <string name=\"follow_official_account\">Follow WeChat Official Accounts</string>\n    <string name=\"wechat\">WeChat</string>\n    <string name=\"thanks\">Supporting me will be appreciated</string>\n    <string name=\"about_official_account\">Official Accounts[开源阅读]</string>\n    <string name=\"source_auto_changing\">Changing source</string>\n    <string name=\"click_to_apply\">Tap to join</string>\n    <string name=\"middle\">Middle</string>\n    <string name=\"information\">Information</string>\n    <string name=\"switchLayout\">Switch Layout</string>\n    <string name=\"text_font_weight_converter\">Text font weight switching</string>\n    <string name=\"full_screen_gestures_support\">Full screen gestures support</string>\n    <string name=\"disable_return_key\">Disable return key</string>\n\n    <!--color-->\n    <string name=\"primary\">Primario</string>\n    <string name=\"accent\">Resalte</string>\n    <string name=\"background_color\">Color de fondo</string>\n    <string name=\"navbar_color\">Color de la barra de navegación</string>\n    <string name=\"day\">Día</string>\n    <string name=\"day_color_primary\">Día, primario</string>\n    <string name=\"day_color_accent\">Día, resalte</string>\n    <string name=\"day_background_color\">Día, color de fondo</string>\n    <string name=\"day_navbar_color\">Día, color de la barra de navegación</string>\n    <string name=\"night\">Noche</string>\n    <string name=\"night_primary\">Noche, primario</string>\n    <string name=\"night_accent\">Noche, resalte</string>\n    <string name=\"night_background_color\">Noche, color de fondo</string>\n    <string name=\"night_navbar_color\">Noche, color de la barra de navegación</string>\n    <string name=\"auto_change_source\">Cambiar la fuente automáticamente</string>\n    <string name=\"text_full_justify\">Texto justificado</string>\n    <string name=\"text_bottom_justify\">Texto alineado en la parte inferior</string>\n    <string name=\"auto_page_speed\">Velocidad de desplazamiento automático</string>\n    <string name=\"sort_by_url\">Ordenar por URL</string>\n    <string name=\"backup_summary\">Respalde en simultáneo vía local y WebDav</string>\n    <string name=\"restore_summary\">Restaure primero desde WebDAV, haga clic en restaurar desde la ubicación del respaldo</string>\n    <string name=\"import_old_summary\">Seleccione una carpeta de respaldo heredada</string>\n    <string name=\"enabled\">Activado</string>\n    <string name=\"disabled\">Desactivado</string>\n    <string name=\"enabled_explore\">Discovery Enabled</string>\n    <string name=\"disabled_explore\">Discovery Disabled</string>\n    <string name=\"starting_download\">Iniciando descarga</string>\n    <string name=\"already_in_download\">Este libro ya está en la lista de descargas</string>\n    <string name=\"click_to_open\">Haga clic para abrir</string>\n    <string name=\"follow_public_account_summary\">Siga [开源阅读] para obtener ayuda haciendo clic en los anuncios</string>\n    <string name=\"weChat_appreciation_code\">Código de sugerencia de WeChat</string>\n    <string name=\"alipay\">AliPay</string>\n    <string name=\"alipay_red_envelope_search_code\">Sobre rojo de AliPay, código de búsqueda</string>\n    <string name=\"alipay_red_envelope_copy\">537954522 Haga clic para copiar</string>\n    <string name=\"alipay_red_envelope_qr_code\">Código QR del sobre rojo de AliPay</string>\n    <string name=\"alipay_payment_qr_code\">Código QR de AliPay</string>\n    <string name=\"qq_collection_qr_code\">Código QR de la Colección QQ</string>\n    <string name=\"contributors_summary\">gedoor, Invinciblelee, Xwite, etc. Para obtener más detalles, vaya a Github</string>\n    <string name=\"clear_cache_summary\">Limpiar la caché de libros y fuentes descargados</string>\n    <string name=\"default_cover\">Portada predeterminada</string>\n    <string name=\"restore_ignore\">Lista de descartados</string>\n    <string name=\"restore_ignore_summary\">Ignore parte del contenido durante la restauración</string>\n    <string name=\"read_config\">Leer ajustes de interfaz</string>\n    <string name=\"group_name\">Nombre del grupo</string>\n    <string name=\"note_content\">Sección de notas</string>\n    <string name=\"replace_enable_default_t\">Activar regla de reemplazo por defecto</string>\n    <string name=\"replace_enable_default_s\">Para libros recién agregados</string>\n    <string name=\"select_restore_file\">Seleccione el archivo de restauración</string>\n    <string name=\"day_background_too_dark\">¡El fondo diurno no puede ser demasiado oscuro!</string>\n    <string name=\"day_bottom_bar_too_dark\">¡El fondo inferior diurno no puede ser demasiado oscuro!</string>\n    <string name=\"night_background_too_light\">¡El fondo nocturno no puede ser demasiado brillante!</string>\n    <string name=\"night_bottom_bar_too_light\">¡El fondo inferior nocturno no puede ser demasiado brillante!</string>\n    <string name=\"accent_background_diff\">Debe haber un contraste entre el color de resalte y el de fondo</string>\n    <string name=\"accent_text_diff\">Debe haber un contraste entre el resalte y el color del texto</string>\n    <string name=\"wrong_format\">Formato incorrecto</string>\n    <string name=\"error\">Error</string>\n    <string name=\"show_brightness_view\">Mostrar widget de brillo</string>\n    <string name=\"language\">Idioma</string>\n    <string name=\"import_rss_source\">Importar fuente RSS</string>\n    <string name=\"donate_summary\">Su donación mejora esta aplicación</string>\n    <string name=\"about_summary\">Cuenta oficial de Wechat [开源阅读软件]</string>\n    <string name=\"read_record\">Leer historial</string>\n    <string name=\"del_read_record\">Eliminar historial de lectura</string>\n    <string name=\"read_record_summary\">Leer resumen del historial</string>\n    <string name=\"local_tts\">TTS local</string>\n    <string name=\"thread_count\">Recuento de subprocesos</string>\n    <string name=\"all_read_time\">Tiempo total de lectura</string>\n    <string name=\"un_select_all\">Deseleccionar todo</string>\n    <string name=\"import_str\">Importar</string>\n    <string name=\"export_str\">Exportar</string>\n    <string name=\"save_theme_config\">Guardar configuración del tema</string>\n    <string name=\"save_day_theme_summary\">Guardar configuración del tema del día</string>\n    <string name=\"save_night_theme_summary\">Guardar configuración del tema nocturno</string>\n    <string name=\"theme_list\">Lista de temas</string>\n    <string name=\"theme_list_summary\">Guardar, importar, compartir tema</string>\n    <string name=\"select_theme\">Cambiar tema predeterminado</string>\n    <string name=\"sort_by_lastUpdateTime\">Ordenar por hora de actualización</string>\n    <string name=\"search_content\">Contenido de búsqueda</string>\n    <string name=\"rss_source_empty\">Empty now!</string>\n    <string name=\"explore_empty\">Actualmente no hay una fuente para Descubrir!</string>\n    <string name=\"page_key_set_help\">将焦点放到输入框按下物理按键会自动录入键值,多个按键会自动用英文逗号隔开.</string>\n    <string name=\"theme_name\">Nombre del tema</string>\n    <string name=\"auto_clear_expired\">Limpiar automáticamente los datos de búsqueda caducados</string>\n    <string name=\"auto_clear_expired_summary\">Historial de búsqueda de más de un día</string>\n    <string name=\"re_segment\">Volver a segmentar</string>\n    <string name=\"style_name\">Formato de nombre:</string>\n    <string name=\"empty_msg_import_book\">Haga clic en el icono de carpeta en la esquina superior derecha y seleccione la carpeta</string>\n    <string name=\"scan_folder\">Búsqueda inteligente</string>\n    <string name=\"import_file_name\">Nombre de archivo importado</string>\n    <string name=\"no_book\">No hay libros</string>\n    <string name=\"keep_original_name\">Conservar el nombre original</string>\n    <string name=\"click_regional_config\">El control de la pantalla táctil</string>\n    <string name=\"close\">Cerrar</string>\n    <string name=\"next_page\">Página siguiente</string>\n    <string name=\"prev_page\">Página anterior</string>\n    <string name=\"non_action\">Sin acciones</string>\n    <string name=\"body_title\">Título</string>\n    <string name=\"show_hide\">Mostrar/ocultar</string>\n    <string name=\"header_footer\">pie de página <![CDATA[&]]> encabezado</string>\n    <string name=\"rule_subscription\">Suscripción de reglsa</string>\n    <string name=\"rule_sub_empty_msg\">添加大佬们提供的规则导入地址\\n添加后点击可导入规则</string>\n    <string name=\"get_book_progress\">Obtener progreso en la nube</string>\n    <string name=\"cover_book_progress\">Cobertura del progreso en la nube</string>\n    <string name=\"current_progress_exceeds_cloud\">El progreso actual excede el progreso de la nube. ¿Quieres sincronizar?</string>\n    <string name=\"sync_book_progress_t\">Progreso de lectura sincronizado</string>\n    <string name=\"sync_book_progress_s\">Sincronizar el progreso de la lectura al entrar y salir de la pantalla de lectura</string>\n    <string name=\"sync_book_progress_plus_t\">Mejora de Sincronización</string>\n    <string name=\"sync_book_progress_plus_s\">Vuelve a sincronizar el progreso en la nube al regresar a la página (pantalla apagada, regreso desde segundo plano, etc.) o cuando la red esté disponible. Se pedirá confirmación antes de sincronizar el nuevo progreso.</string>\n    <string name=\"create_bookmark_error\">No se pudo marcar</string>\n    <string name=\"single_url\">URL única</string>\n    <string name=\"export_bookshelf\">Exportar lista de libros</string>\n    <string name=\"import_bookshelf\">Importar lista de libros</string>\n    <string name=\"pre_download\">Descarga anterior</string>\n    <string name=\"pre_download_m\">预下载%s页</string>\n    <string name=\"pre_download_s\">Descargue %s capítulos antes</string>\n    <string name=\"is_enabled\">Está habilitado</string>\n    <string name=\"background_image\">Imagen de fondo</string>\n    <string name=\"copy_book_url\">Copiar URL del libro</string>\n    <string name=\"copy_toc_url\">Copiar URL del capítulo</string>\n    <string name=\"export_folder\">Carpeta de exportación</string>\n    <string name=\"export_charset\">Codificación de texto exportado</string>\n    <string name=\"export_to_web_dav\">Exportar a WebDav</string>\n    <string name=\"reverse_content\">Contenido inverso</string>\n    <string name=\"debug\">Depurar</string>\n    <string name=\"crash_log\">Registro de fallos</string>\n    <string name=\"use_zh_layout\">Uso de ramas chinas personalizadas</string>\n    <string name=\"image_style\">Formato de imagen</string>\n    <string name=\"system_tts\">Sistema TTS</string>\n    <string name=\"export_type\">Formato de exportación</string>\n    <string name=\"checkAuthor\">Verificar por autor</string>\n    <string name=\"url_already\">Esta URL ha sido reemplazada</string>\n    <string name=\"high_brush_title\">Frecuencia de actualización de pantalla alta</string>\n    <string name=\"high_brush_summary\">Utilice la frecuencia de actualización de pantalla más alta</string>\n    <string name=\"export_all\">Exportar todo</string>\n    <string name=\"complete\">Listo</string>\n    <string name=\"show_unread\">Mostrar no leídos</string>\n    <string name=\"use_default_cover\">Mostrar siempre la portada predeterminada</string>\n    <string name=\"use_default_cover_s\">Mostrar siempre la cobertura predeterminada, no mostrar la cobertura de la red</string>\n    <string name=\"search_src\">Código fuente de búsqueda</string>\n    <string name=\"boo_src\">Código fuente del libro</string>\n    <string name=\"toc_src\">Código fuente de los capítulos</string>\n    <string name=\"content_src\">Código fuente del contenido</string>\n    <string name=\"list_src\">Listar el código fuente</string>\n    <string name=\"title_font_size\">Tamaño de fuente</string>\n    <string name=\"title_margin_top\">Borde superior</string>\n    <string name=\"title_margin_bottom\">Borde inferior</string>\n    <string name=\"show\">Mostrar</string>\n    <string name=\"hide\">Ocultar</string>\n    <string name=\"hide_when_status_bar_show\">Ocultar cuando se muestra la barra de estado</string>\n    <string name=\"reverse_toc\">TOC inverso</string>\n    <string name=\"show_discovery\">Mostrar</string>\n    <string name=\"style\">Formato</string>\n    <string name=\"group_style\">Formato de grupo</string>\n    <string name=\"export_file_name\">Exportar nombre de archivo</string>\n    <string name=\"reset\">Restablecer</string>\n    <string name=\"null_url\">URL nula</string>\n    <string name=\"dict\">字典</string>\n    <string name=\"unknown_error\">未知错误</string>\n    <string name=\"end\">end</string>\n    <string name=\"custom_group_summary\">关闭替换分组/开启添加分组</string>\n    <string name=\"pref_media_button_per_next\">媒体按钮•上一首|下一首</string>\n    <string name=\"pref_media_button_per_next_summary\">上一段|下一段/上一章|下一章</string>\n    <string name=\"read_aloud_by_page_summary\">及时翻页,翻页时会停顿一下</string>\n    <string name=\"check_source_show_debug_message\">La fuente del libro de cheques muestra un mensaje de depuración</string>\n    <string name=\"check_source_show_debug_message_summary\">Muestra los pasos y el tiempo de la solicitud de red durante la verificación de la fuente del libro</string>\n    <string name=\"export_no_chapter_name\">No export chapter names</string>\n    <string name=\"autobackup_fail\">Autobackup failed\\n%s</string>\n    <string name=\"background_image_blurring\">Background image blurring</string>\n    <string name=\"background_image_blurring_radius\">Blurring radius</string>\n    <string name=\"background_image_hint\">Disabled when 0, enable range from 1 to 25\\nThe greater the radius, the stronger the effect of blurring</string>\n    <string name=\"need_login\">需登录</string>\n    <string name=\"pref_cronet_summary\">使用Cronet网络组件</string>\n    <string name=\"anti_alias\">Anti-Aliasing</string>\n    <string name=\"pref_anti_alias_summary\">Anti-Aliasing when draw picture</string>\n    <string name=\"upload_url\">创建分享链接</string>\n    <string name=\"download_url_rule\">downloadUrlRule(downloadUrls)</string>\n    <string name=\"sort_by_respondTime\">Ordenar por tiempo de respuesta</string>\n    <string name=\"respondTime\">tiempo de respuesta: %1$d ms</string>\n    <string name=\"export_success\">导出成功</string>\n    <string name=\"path\">路径</string>\n    <string name=\"direct_link_upload_rule\">直链上传规则</string>\n    <string name=\"direct_link_upload_rule_summary\">用于导出书源书单时生成直链url</string>\n    <string name=\"direct_link_upload_config\">直链上传配置</string>\n    <string name=\"copy_play_url\">拷贝播放Url</string>\n    <string name=\"set_source_variable\">设置源变量</string>\n    <string name=\"set_book_variable\">设置书籍变量</string>\n    <string name=\"summary\">注释</string>\n    <string name=\"cover_config\">封面设置</string>\n    <string name=\"cover_config_summary\">通用封面规则及默认封面样式</string>\n    <string name=\"cover_show_name\">显示书名</string>\n    <string name=\"cover_show_name_summary\">封面上显示书名</string>\n    <string name=\"cover_show_author\">显示作者</string>\n    <string name=\"cover_show_author_summary\">封面上显示作者</string>\n    <string name=\"read_aloud_prev_paragraph\">朗读上一段</string>\n    <string name=\"read_aloud_next_paragraph\">朗读下一段</string>\n    <string name=\"wait_download\">待下载</string>\n    <string name=\"download_success\">下载完成</string>\n    <string name=\"download_error\">下载失败</string>\n    <string name=\"downloading\">下载中</string>\n    <string name=\"unknown_state\">未知状态</string>\n    <string name=\"disable_source\">禁用源</string>\n    <string name=\"delete_source\">删除源</string>\n    <string name=\"chapter_pay\">购买</string>\n    <string name=\"double_page_horizontal\">平板/横屏双页</string>\n    <string name=\"open_in_browser\">浏览器打开</string>\n    <string name=\"copy_url\">拷贝url</string>\n    <string name=\"full_screen\">全屏</string>\n    <string name=\"open_fun\">打开方式</string>\n    <string name=\"use_browser_open\">是否使用外部浏览器打开?</string>\n    <string name=\"see\">查看</string>\n    <string name=\"open\">打开</string>\n    <string name=\"del_login_header\">删除登录头</string>\n    <string name=\"show_login_header\">查看登录头</string>\n    <string name=\"login_header\">登录头</string>\n    <string name=\"font_scale\">字体大小</string>\n    <string name=\"font_scale_summary\">当前字体大小:%.1f</string>\n    <string name=\"search_content_size\">search result</string>\n    <string name=\"search_content_empty\">Empty search result, check conversation settings</string>\n    <string name=\"tts_speech_reduce\">语速减</string>\n    <string name=\"tts_speech_add\">语速加</string>\n    <string name=\"open_sys_dir_picker_error\">打开系统文件夹选择器出错,自动打开应用文件夹选择器</string>\n    <string name=\"open_sys_doc_picker_error\">打开系统文件选择器出错,自动打开应用文件选择器</string>\n    <string name=\"expand_text_menu\">展开文本选择菜单</string>\n    <string name=\"book_tree_uri_t\">书籍保存位置</string>\n    <string name=\"book_tree_uri_s\">从其它应用打开的书籍保存位置</string>\n    <string name=\"select_book_folder\">选择保存书籍的文件夹</string>\n    <string name=\"user_agent\">用户代理</string>\n    <string name=\"bg_alpha\">背景透明度</string>\n\n    <!-- check source config string -->\n    <string name=\"check_source_config\">校验设置</string>\n    <string name=\"check_source_item\">校验项目</string>\n    <string name=\"check_source_timeout\">单个书源校验超时(秒)</string>\n    <string name=\"timeout\">超时</string>\n    <string name=\"seconds\">秒</string>\n    <string name=\"less_than\">小于</string>\n    <string name=\"check_source_config_summary\">校验超时: %1$s秒\\n校验项目:%2$s</string>\n    <string name=\"record_debug_log\">记录调试日志</string>\n    <string name=\"sub_dir\">子文件夹</string>\n    <string name=\"general\">全局</string>\n    <string name=\"use_replace\">使用替换</string>\n    <string name=\"scope_title\">作用于标题</string>\n    <string name=\"scope_content\">作用于正文</string>\n    <string name=\"join_qq_channel\">加入QQ频道</string>\n    <string name=\"qq_channel_summary\">点击加入阅读QQ频道</string>\n    <string name=\"menu_refresh_dur\">刷新当前章节</string>\n    <string name=\"menu_refresh_after\">刷新之后章节</string>\n    <string name=\"menu_refresh_all\">刷新全部章节</string>\n    <string name=\"edit_content\">编辑内容</string>\n    <string name=\"chapter_change_source\">单章换源</string>\n    <string name=\"book_change_source\">整书换源</string>\n    <string name=\"sort_by_time\">时间排序</string>\n    <string name=\"enable_record\">开启记录</string>\n    <string name=\"copy_all\">拷贝所有</string>\n    <string name=\"auto_complete\">自动补全</string>\n    <string name=\"sort_by_size\">大小排序</string>\n    <string name=\"welcome_style\">启动界面样式</string>\n    <string name=\"welcome_style_summary\">启动界面图片和是否显示文字等</string>\n    <string name=\"show_welcome_text\">显示文字</string>\n    <string name=\"welcome_text\">阅读|享受美好时光</string>\n    <string name=\"custom_welcome\">自定义欢迎页</string>\n    <string name=\"custom_welcome_summary\">是否使用自定义欢迎页</string>\n    <string name=\"show_icon\">显示图标</string>\n    <string name=\"show_default_book_icon\">显示默认书籍图标</string>\n    <string name=\"cache_export\">缓存/导出</string>\n    <string name=\"assists_key_config\">辅助按键配置</string>\n    <string name=\"url_option\">Url参数</string>\n    <string name=\"only_wifi\">仅WIFI</string>\n    <string name=\"only_wifi_summary\">仅在wifi下加载网络封面</string>\n    <string name=\"cover_rule\">封面规则</string>\n    <string name=\"cover_rule_summary\">进入详情页时使用封面规则重新获取封面</string>\n    <string name=\"scroll_to_dur_source\">定位到当前书源</string>\n    <string name=\"sys_tts_config\">系统tts设置</string>\n    <string name=\"sys_tts_config_summary\">打开系统tts设置界面</string>\n    <string name=\"cannot_timed_non_playback\">非播放状态无法定时</string>\n    <string name=\"all_bookmark\">Todos los marcadores</string>\n    <string name=\"change_source_batch\">批量换源</string>\n    <string name=\"book_type_different\">书籍类型不一样</string>\n    <string name=\"soure_change_source\">是否确认换源</string>\n    <string name=\"input_verification_code\">输入验证码</string>\n    <string name=\"verification_code\">验证码</string>\n    <string name=\"timeout_millisecond\">超时毫秒数</string>\n    <string name=\"file_not_supported\">Continue to open although %1$s is not supported ?</string>\n    <string name=\"import_tts\">导入TTS</string>\n    <string name=\"import_theme\">导入主题</string>\n    <string name=\"import_txt_toc_rule\">导入txt目录规则</string>\n    <string name=\"auto_save_cookie\">CookieJar</string>\n    <string name=\"cookie\">清除cookie</string>\n    <string name=\"download_and_import_file\">导入在线书籍文件</string>\n    <string name=\"upload_book_success\">Upload Success</string>\n    <string name=\"upload_book_fail\">Upload Fail</string>\n    <string name=\"download_book_success\">Download Success</string>\n    <string name=\"download_book_fail\">Download Fail</string>\n    <string name=\"upload_to_remote\">Upload</string>\n    <string name=\"add_remote_book\">RemoteBook</string>\n    <string name=\"bitmap_cache_size_summary\">Current cache max size %1$s MB</string>\n    <string name=\"bitmap_cache_size\">bitmap cache size</string>\n    <string name=\"image_retain_number_summary\">Keep the number of chapters read %s</string>\n    <string name=\"image_retain_number\">Number of manga reservations</string>\n    <string name=\"export_pics_file\">Export Picture Files</string>\n    <!-- string end -->\n    <string name=\"error_decode_bitmap\">Fail to decode bitmap</string>\n    <string name=\"error_image_url_empty\">Image url is empty, check replacement rules</string>\n    <string name=\"variable_comment\">变量说明(variableComment)</string>\n    <string name=\"remote_book\">远程书籍</string>\n    <string name=\"reading_time_sort\">阅读时长排序</string>\n    <string name=\"last_read_time_sort\">阅读时间排序</string>\n    <string name=\"reading_time_tag\">阅读时长:</string>\n    <string name=\"last_read_time_tag\">最后阅读时间:</string>\n    <string name=\"page_touch_slop_title\">滑动翻页阈值</string>\n    <string name=\"page_touch_slop_dialog_title\">滑动翻页阈值（0 = 系统默认值）</string>\n    <string name=\"page_touch_slop_summary\">滑动多长距离才会触发滑动翻页（系统默认值 %s px）</string>\n    <string name=\"example\">Ejemplo</string>\n    <string name=\"check_selected_interval\">选中所选区间</string>\n    <string name=\"show_add_to_shelf_alert_title\">返回时提示放入书架</string>\n    <string name=\"show_add_to_shelf_alert_summary\">阅读未放入书架的书籍在返回时提示放入书架</string>\n    <string name=\"review\">Review</string>\n    <string name=\"rule_review_url\">段评URL（reviewUrl）</string>\n    <string name=\"rule_avatar\">段评发布者头像（avatarRule）</string>\n    <string name=\"rule_review_content\">段评内容（contentRule）</string>\n    <string name=\"rule_review_quote\">段评回复URL（reviewQuoteUrl）</string>\n    <string name=\"rule_post_time\">段评发布时间（postTimeRule）</string>\n    <string name=\"review_vote_down\">点踩URL（voteDownUrl）</string>\n    <string name=\"review_vote_up\">点赞URL（voteUpUrl）</string>\n    <string name=\"post_review_url\">发送回复URL（postReviewUrl）</string>\n    <string name=\"post_quote_url\">发送回复段评URL（postQuoteUrl）</string>\n    <string name=\"delete_review_url\">删除段评URL（deleteUrl）</string>\n    <string name=\"tag_explore_enabled\">标志:发现已启用</string>\n    <string name=\"tag_explore_disabled\">标志:发现已禁用</string>\n    <string name=\"show_read_title_addition\">show read title addition área</string>\n    <string name=\"read_bar_style_follow_page\">read bar style follow page</string>\n    <string name=\"rule_image_decode\">Decode Image(imageDecode)</string>\n    <string name=\"like_source\">赞</string>\n    <string name=\"not_like_source\">踩</string>\n    <string name=\"async_load_image\">异步加载图片</string>\n    <string name=\"ignore_audio_focus_title\">忽略音频焦点</string>\n    <string name=\"ignore_audio_focus_summary\">允许与其他应用同时播放音频</string>\n    <string name=\"refresh_sort\">刷新分类</string>\n    <string name=\"cover_decode_js\">Decode Cover Js(coverDecodeJs)</string>\n    <string name=\"net_no_group\">网络未分组</string>\n    <string name=\"local_no_group\">本地未分组</string>\n    <string name=\"parallel_export_book\">多线程导出</string>\n    <string name=\"progress_bar_behavior\">进度条行为</string>\n    <string name=\"source_edit_text_max_line\">源编辑框最大行数</string>\n    <string name=\"source_edit_max_line_summary\">%s,设置行数小于屏幕可显示的最大行数可以更方便的滑动到其他的字段进行编辑</string>\n    <string name=\"restore_last_book_process\">是否恢复到跳转前的阅读进度？</string>\n    <string name=\"search_scope\">搜索范围</string>\n    <string name=\"toggle_search_scope\">切换</string>\n    <string name=\"sure_clear_search_history\">是否确认清除所有搜索历史记录</string>\n    <string name=\"no_anim_scroll_page\">禁用滚动点击动画</string>\n    <string name=\"webdav_device_name\">设备名称</string>\n    <string name=\"web_service_wake_lock\">WebService唤醒锁</string>\n    <string name=\"web_service_wake_lock_summary\">开启web服务的时候启用唤醒锁,有些手机开启唤醒锁会被杀后台</string>\n    <string name=\"read_aloud_wake_lock\">朗读服务唤醒锁</string>\n    <string name=\"read_aloud_wake_lock_summary\">开启朗读的时候启用唤醒锁,有些手机开启唤醒锁会被杀后台</string>\n    <string name=\"audio_play_wake_lock\">音频服务唤醒锁</string>\n    <string name=\"audio_play_wake_lock_summary\">播放音频的时候启用唤醒锁,有些手机开启唤醒锁会被杀后台</string>\n    <string name=\"change_search_scope\">切换搜索范围</string>\n    <string name=\"copy_rule\">拷贝规则</string>\n    <string name=\"paste_rule\">粘贴规则</string>\n    <string name=\"groups_or_source\">多分组/书源</string>\n    <string name=\"replace_state_change\">替换(启用/禁用)</string>\n    <string name=\"show_last_update_time\">显示上次更新时间</string>\n    <string name=\"refresh_list\">刷新列表</string>\n    <string name=\"tip_divider_color\">分隔线颜色</string>\n    <string name=\"same_title_removed\">移除重复标题</string>\n    <string name=\"update_book_fail\">更新失败</string>\n    <string name=\"notification_permission_rationale\">阅读需要发送通知来显示朗读控制和下载进度</string>\n    <string name=\"webdav_after_local_restore_confirm\">webDav书源比本地新,是否恢复</string>\n    <string name=\"c_whitelist\">白名单(contentWhitelist)</string>\n    <string name=\"c_blacklist\">黑名单(contentBlacklist)</string>\n    <string name=\"confirm\">确认</string>\n    <string name=\"jump_to_another_app\">跳转其它应用</string>\n    <string name=\"clear_webview_data\">清除 WebView 数据</string>\n    <string name=\"clear_webview_data_summary\">清除内置浏览器所有数据</string>\n    <string name=\"source_tab_list\">列表</string>\n    <string name=\"dict_rule\">字典规则</string>\n    <string name=\"config_dict_rule\">配置字典规则</string>\n    <string name=\"create\">新建</string>\n    <string name=\"url_rule\">url规则(urlRule)</string>\n    <string name=\"show_rule\">显示规则(showRule)</string>\n    <string name=\"config_txt_toc_rule\">配置 TXT 目录规则</string>\n    <string name=\"import_dict_rule\">导入字典规则</string>\n    <string name=\"keep_group\">保留分组</string>\n    <string name=\"server_config\">服务器配置</string>\n    <string name=\"sure_upload\">Remote webDav url exists, Continue?</string>\n    <string name=\"unsupport_archivefile_entry\">Cannot find supported files in archive</string>\n    <string name=\"no_books_dir\">没有设置书籍保存位置!</string>\n    <string name=\"delete_alert\">删除提醒</string>\n    <string name=\"no_book_found_bookshelf\">No book found in bookshelf, import again ?</string>\n    <string name=\"archive_not_found\">Can not find archive selected, continue to download ?</string>\n    <string name=\"privacy_policy\">用户隐私与协议</string>\n    <string name=\"agree\">同意</string>\n    <string name=\"refuse\">拒绝</string>\n    <string name=\"file_manage\">文件管理</string>\n    <string name=\"file_manage_summary\">管理私有文件夹的文件</string>\n    <string name=\"create_folder\">创建文件夹</string>\n    <string name=\"allow_drop_down_refresh\">允许下拉刷新</string>\n    <string name=\"text_underline\">文字下划线</string>\n    <string name=\"select_new_source\">选中新增源</string>\n    <string name=\"select_update_source\">选中更新源</string>\n    <string name=\"set_local_password\">设置本地密码</string>\n    <string name=\"set_local_password_summary\">本地密码用来对备份的敏感信息加密和解密,如需在不同设备之间同步,本地密码需一致.</string>\n    <string name=\"only_latest_backup_t\">仅保留最新备份</string>\n    <string name=\"only_latest_backup_s\">本地备份仅保留最新备份文件</string>\n    <string name=\"webdav_application_authorization_error\">Fail to authorize WebDav application</string>\n    <string name=\"load_word_count\">Cargar palabras del libro</string>\n    <string name=\"replace_exclude_scope\">排除范围，选填书名或者书源 URL</string>\n    <string name=\"format_js_rule\">格式化规则(formatJs)</string>\n    <string name=\"adjust_pos\">调整位置</string>\n    <string name=\"select_section_export\">Choose some chapters to be exported</string>\n    <string name=\"error_scope_input\">Please enter the correct range</string>\n    <string name=\"custom_export\">Custom Export</string>\n    <string name=\"file_contains_number\">The number of chapters contained in each file</string>\n    <string name=\"export_chapter_index\">The section index that needs to be exported</string>\n    <string name=\"shrink_database\">压缩数据库</string>\n    <string name=\"shrink_database_summary\">减小数据库文件的大小</string>\n    <string name=\"is_compress\">是否压缩</string>\n    <string name=\"sort_desc\">反序</string>\n    <string name=\"test\">测试</string>\n    <string name=\"show_wait_up_count\">显示等待更新数量</string>\n    <string name=\"exit_app\">退出软件</string>\n    <string name=\"result_analyzed\">Analyzed</string>\n    <string name=\"bookshelf_px_4\">Comprehensive</string>\n    <string name=\"bookshelf_px_5\">Ordenar por autor</string>\n    <string name=\"effective_replaces\">起效的替换</string>\n    <string name=\"export_book\">导出书籍</string>\n    <string name=\"export_book_notification_content\">正在导出(%1$s),还有%2$d本待导出</string>\n    <string name=\"export_md\">导出(MD)</string>\n    <string name=\"change_source_delay\">换源间隔</string>\n    <string name=\"change_source_progress\">结果 %1$d, 当前进度 %2$d / %3$d: %4$s</string>\n    <string name=\"open_book_info_by_click_title\">点击书名打开详情</string>\n    <string name=\"export_wait\">等待导出</string>\n    <string name=\"default_home_page\">默认主页</string>\n    <string name=\"sync_book_progress_success\">\" Sincronización del progreso exitosa\"</string>\n    <string name=\"show_bookshelf_fast_scroller\">显示快速滚动条</string>\n    <string name=\"export_all_use_book_source\">导出所有书的书源</string>\n    <string name=\"cloud_progress_exceeds_current\">The cloud progress exceeds the current progress. Do you want to synchronize?</string>\n    <string name=\"keep_enable\">保留启用状态</string>\n    <string name=\"preview_image_by_click\">点击预览图片</string>\n    <string name=\"screen_portrait_reversed\">反向竖屏</string>\n    <string name=\"del_ruby_tag\">删除ruby标签</string>\n    <string name=\"del_h_tag\">删除h标签</string>\n    <string name=\"adjust_chapter_page\">调整本章页数</string>\n    <string name=\"adjust_chapter_index\">调整章节位置</string>\n    <string name=\"clear_webview_data_success\">清除成功，3秒后自动重启应用</string>\n    <string name=\"key_page_on_long_press\">按键长按翻页</string>\n    <string name=\"save_log\">保存日志</string>\n    <string name=\"create_heap_dump\">创建堆转储</string>\n    <string name=\"record_heap_dump_s\">当应用发生OOM崩溃时保存堆转储</string>\n    <string name=\"record_heap_dump_t\">记录堆转储</string>\n    <string name=\"font_weight_text\">中/粗/细</string>\n    <string name=\"keep_swipe_tip\">继续滑动以加载下一章…</string>\n    <string name=\"enable_optimize_render\">启用绘制优化</string>\n    <string name=\"ignore_battery_permission_rationale\">阅读需要请求后台权限以保持服务正常运行</string>\n    <string name=\"simulated_reading\">Lectura Ficticia</string>\n    <string name=\"switch_on\">Enc/Apag</string>\n    <string name=\"start_from\">Comenzar</string>\n    <string name=\"daily_chapters\">Cap. al día</string>\n    <string name=\"start_chapter\">Cap. inicio</string>\n    <string name=\"update_to_variant_title\">检查更新查找版本</string>\n    <string name=\"update_to_variant_summary\">检查更新时查找其他签名版本</string>\n    <string name=\"default_version\">当前</string>\n    <string name=\"official_version\">正式版</string>\n    <string name=\"beta_release_version\">测试版</string>\n    <string name=\"beta_releaseA_version\">共存版</string>\n    <string name=\"stream_read_aloud_audio\">流式播放音频</string>\n    <string name=\"stream_read_aloud_audio_summary\">即边下边播，网络不好时播放会断断续续，仅TTS源有效</string>\n    <string name=\"pause_read_aloud_while_phone_calls_title\">来电期间暂停朗读</string>\n    <string name=\"pause_read_aloud_while_phone_calls_summary\">在通话期间暂停朗读，需要读取手机状态权限</string>\n    <string name=\"read_aloud_read_phone_state_permission_rationale\">阅读需要读取手机状态实现来电期间暂停朗读功能</string>\n    <string name=\"read_aloud_by_media_button_title\">耳机按键启动朗读</string>\n    <string name=\"read_aloud_by_media_button_summary\">通过耳机按键来启动朗读</string>\n    <string name=\"group_sources_by_domain\">按域名分组显示</string>\n    <string name=\"theme_config\">主题配置</string>\n    <string name=\"show_manga_ui\">漫画浏览</string>\n    <string name=\"disable_manga_scale\">禁用漫画缩放</string>\n    <string name=\"disable_manga_click_scroll\">禁用点击翻页</string>\n    <string name=\"enable_auto_page_scroll\">开启自动翻页</string>\n    <string name=\"manga_auto_page_speed\">翻页速度 %s</string>\n    <string name=\"enable_auto_scroll\">开启滚动</string>\n    <string name=\"manga_footer_config\">页脚配置</string>\n    <string name=\"setting_manga_auto_page_speed\">设置自动翻页速度</string>\n    <string name=\"book_reader_info_bar\">页数. %1$d/%2$d  章节. %3$d/%4$d -> %5$s%%</string>\n    <string name=\"menu_download_after\">Descargar el siguiente capítulo</string>\n    <string name=\"menu_download_all\">Descargar todos los capítulos</string>\n    <string name=\"manga_header_chapter\">《章节和文案》隐藏</string>\n    <string name=\"manga_check_chapter_label\">《章节.》文案</string>\n    <string name=\"manga_check_chapter\">章节</string>\n    <string name=\"manga_check_chapter_name\">章节名称</string>\n    <string name=\"manga_header_page\">《页数和文案》隐藏</string>\n    <string name=\"manga_check_page_label\">《页数.》文案</string>\n    <string name=\"manga_check_page_number\">页数</string>\n    <string name=\"manga_header_progress\">《总进度和文案》隐藏</string>\n    <string name=\"manga_check_progress_label\">《总进度.》文案</string>\n    <string name=\"manga_check_progress\">总进度</string>\n    <string name=\"manga_header_footer\">页脚</string>\n    <string name=\"manga_radio_left\">靠左</string>\n    <string name=\"manga_radio_center\">居中</string>\n    <string name=\"enable_manga_horizontal_scroll\">水平滚动</string>\n    <string name=\"manga_color_filter\">滤镜</string>\n    <string name=\"hide_manga_title\">隐藏漫画列表标题</string>\n    <string name=\"refresh_explore\">刷新发现</string>\n    <string name=\"padding_display_cutouts\">Padding display cutouts</string>\n    <string name=\"manga_epaper\">墨水屏</string>\n    <string name=\"manga_epaper_stting\">墨水屏设置</string>\n    <string name=\"manga_epaper_value\">阈值</string>\n    <string name=\"disable_horizontal_page_snap\">禁用水平翻页效果</string>\n    <string name=\"enable_manga_gray\">开启图片灰色</string>\n    <string name=\"sure_cache_book\">是否确认开始缓存？</string>\n    <string name=\"auto_check_new_backup_t\">自动检查新备份</string>\n    <string name=\"auto_check_new_backup_s\">打开软件时检查是否有新备份，有新备份时提示是否更新</string>\n    <string name=\"sys_image_picker\">系统图片选择器</string>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values-ja-rJP/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <!--App-->\n    <string name=\"app_name\">Legado</string>\n    <string name=\"app_name_a\">Legado·A</string>\n    <string name=\"receiving_shared_label\">Legado·search</string>\n    <string name=\"tip_perm_request_storage\">Legado needs storage access to find and read books. please go \"App Settings\" to allow \"Storage permission\".</string>\n\n    <!--Other-->\n    <string name=\"menu_backup\">Home</string>\n    <string name=\"menu_restore\">Restore</string>\n    <string name=\"menu_import_old\">Import Legado data</string>\n    <string name=\"webdav_cache_backup\">Offline cache book backup</string>\n    <string name=\"webdav_cache_backup_s\">Export to local and back up to the exports directory under the legado folder</string>\n    <string name=\"backup_path\">Backup to</string>\n    <string name=\"select_backup_path\">Please select a backup path.</string>\n    <string name=\"menu_import_old_version\">Import legacy data</string>\n    <string name=\"menu_import_github\">Import github data</string>\n    <string name=\"menu_replace_rule\">Replacement</string>\n    <string name=\"menu_send\">Send</string>\n\n    <string name=\"dialog_title\">Prompt</string>\n    <string name=\"dialog_cancel\">Cancel</string>\n    <string name=\"dialog_confirm\">Confirm</string>\n    <string name=\"dialog_setting\">Go to Settings</string>\n    <string name=\"tip_cannot_jump_setting_page\">Cannot jump to Settings.</string>\n\n    <string name=\"manual_input\">Manual Input</string>\n    <string name=\"enter_directory_path\">Enter directory path</string>\n    <string name=\"invalid_directory\">Invalid directory path</string>\n    <string name=\"empty_directory_input\">Directory path cannot be empty</string>\n\n    <string name=\"dynamic_click_retry\">Retry</string>\n    <string name=\"dynamic_loading\">Loading</string>\n    <string name=\"draw\">Warning</string>\n    <string name=\"edit\">Edit</string>\n    <string name=\"delete\">Delete</string>\n    <string name=\"delete_select_group\">Delete Select Group</string>\n    <string name=\"delete_all\">Delete all</string>\n    <string name=\"replace\">Replace</string>\n    <string name=\"replace_purify\">Replacement</string>\n    <string name=\"replace_purify_desc\">Configure replacement rules</string>\n    <string name=\"not_available\">Not available now</string>\n    <string name=\"enable\">Enable</string>\n    <string name=\"replace_purify_search\">Search Replacement</string>\n    <string name=\"bookshelf\">Bookshelf</string>\n    <string name=\"favorites\">Favorites</string>\n    <string name=\"favorite\">Favorite</string>\n    <string name=\"in_favorites\">in Favorites</string>\n    <string name=\"out_favorites\">Not in Favorites</string>\n    <string name=\"rss\">Subscription</string>\n    <string name=\"all\">All</string>\n    <string name=\"recent_reading\">Recent reading</string>\n    <string name=\"custom_export_section\">カスタムEPUBエクスポートの章</string>\n    <string name=\"last_read\">Last reading</string>\n    <string name=\"update_log\">What\\'s new</string>\n    <string name=\"bookshelf_empty\">The bookshelf is still empty. Search for books or add them from discovery! \\n if you use it for the first time, please open legado.top get help!</string>\n    <string name=\"action_search\">Search</string>\n    <string name=\"action_download\">Download</string>\n    <string name=\"layout_list\">List</string>\n    <string name=\"layout_grid3\">Grid-3</string>\n    <string name=\"layout_grid4\">Grid-4</string>\n    <string name=\"layout_grid5\">Grid-5</string>\n    <string name=\"layout_grid6\">Grid-6</string>\n    <string name=\"bookshelf_layout\">Layout</string>\n    <string name=\"view\">View</string>\n    <string name=\"book_library\">Library</string>\n    <string name=\"book_local\">Import books</string>\n    <string name=\"book_source\">Book sources</string>\n    <string name=\"book_source_manage\">Sources management</string>\n    <string name=\"book_source_manage_desc\">Create/Import/Edit/Manage Book sources</string>\n    <string name=\"setting\">Settings</string>\n    <string name=\"theme_setting\">Theme settings</string>\n    <string name=\"theme_setting_s\">Some settings related to interface or color</string>\n    <string name=\"other_setting\">Other settings</string>\n    <string name=\"other_setting_s\">Some function-related settings</string>\n    <string name=\"about\">About</string>\n    <string name=\"donate\">Donations</string>\n    <string name=\"exit\">Exit</string>\n    <string name=\"exit_no_save\">It has not been saved. Do you want to continue editing?</string>\n    <string name=\"read_style\">Book styles</string>\n    <string name=\"version\">Version</string>\n    <string name=\"local\">Local</string>\n    <string name=\"search\">Search</string>\n    <string name=\"origin_format\">Origin: %s</string>\n    <string name=\"read_dur_progress\">Origin: %s</string>\n    <string name=\"book_name\">Title</string>\n    <string name=\"lasted_show\">Latest: %s</string>\n    <string name=\"check_add_bookshelf\">Would you like to add %s to your Bookshelf?</string>\n    <string name=\"import_books_count\">%s text file(s) in total</string>\n    <string name=\"is_loading\">Loading…</string>\n    <string name=\"retry\">Retry</string>\n    <string name=\"web_service\">Web service</string>\n    <string name=\"web_service_desc\">Web edit source and read book</string>\n    <string name=\"web_edit_source\">Edit book sources on the web</string>\n    <string name=\"offline_cache\">Offline cache</string>\n    <string name=\"offline_cache_t\">Offline cache</string>\n    <string name=\"offline_cache_s\">cache the selected chapter(s) to Storage</string>\n    <string name=\"change_origin\">Change Origin</string>\n    <string name=\"about_description\">\n        \\u3000\\u3000 This is an open source reading software newly developed by Kotlin, welcome to join us.\n    </string>\n    <string name=\"app_share_description\">\n        Legado (YueDu 3.0) download link：\\n https://github.com/gedoor/legado/releases\n    </string>\n    <string name=\"version_name\">Version %s</string>\n    <string name=\"pt_background_verification\">Background-verification</string>\n    <string name=\"ps_background_verification\">you can operate freely when verifying the book source</string>\n    <string name=\"pt_auto_refresh\">Auto-refresh</string>\n    <string name=\"ps_auto_refresh\">Update books automatically when opening the software</string>\n    <string name=\"pt_auto_download\">Auto-download</string>\n    <string name=\"ps_auto_download\">Download the latest chapters automatically  when updating books</string>\n    <string name=\"backup_restore\">Backup and restore</string>\n    <string name=\"web_dav_set\">WebDav settings</string>\n    <string name=\"web_dav_set_import_old\">WebDav settings/Import legacy data</string>\n    <string name=\"backup\">Backup</string>\n    <string name=\"restore\">Restore</string>\n    <string name=\"backup_permission\">Backup needs storage permission</string>\n    <string name=\"restore_permission\">Restore needs storage permission</string>\n    <string name=\"ok\">OK</string>\n    <string name=\"cancel\">Cancel</string>\n    <string name=\"backup_confirmation\">Backup confirmation</string>\n    <string name=\"backup_message\">The new backup files will replace the original.\\n Backup folder: YueDu</string>\n    <string name=\"restore_confirmation\">Restore confirmation</string>\n    <string name=\"restore_message\">Restoring the bookshelf data will overwrite the current Bookshelf.</string>\n    <string name=\"backup_success\">Backup succeed</string>\n    <string name=\"backup_fail\">Backup failed\\n%s</string>\n    <string name=\"on_restore\">Restoring</string>\n    <string name=\"restore_success\">Restore succeed</string>\n    <string name=\"restore_fail\">Backup failed</string>\n    <string name=\"screen_direction\">Screen orientation</string>\n    <string name=\"screen_sensor\">Auto(sensor)</string>\n    <string name=\"screen_landscape\">Landscape</string>\n    <string name=\"screen_portrait\">Portrait</string>\n    <string name=\"screen_unspecified\">Follow system</string>\n    <string name=\"disclaimer\">Disclaimer</string>\n    <string name=\"all_chapter_num\">%d chapters</string>\n    <string name=\"interface_setting\">Interface</string>\n    <string name=\"brightness\">Brightness</string>\n    <string name=\"chapter_list\">Chapters</string>\n    <string name=\"next_chapter\">Next</string>\n    <string name=\"previous_chapter\">Prior</string>\n    <string name=\"pt_hide_status_bar\">Hide status bar</string>\n    <string name=\"ps_hide_status_bar\">Hide system navigation bar when reading</string>\n    <string name=\"read_aloud\">Speech</string>\n    <string name=\"read_aloud_t\">Speaking</string>\n    <string name=\"read_aloud_s\">Click to open the Reading</string>\n    <string name=\"audio_play\">Play</string>\n    <string name=\"audio_play_t\">Playing</string>\n    <string name=\"audio_play_s\">Click to open the Playing</string>\n    <string name=\"audio_pause\">Pause</string>\n    <string name=\"text_return\">Back</string>\n    <string name=\"refresh\">Refresh</string>\n    <string name=\"start\">Start</string>\n    <string name=\"stop\">Stop</string>\n    <string name=\"pause\">Pause</string>\n    <string name=\"resume\">Resume</string>\n    <string name=\"set_timer\">Timer</string>\n    <string name=\"read_aloud_pause\">Speak Paused</string>\n    <string name=\"read_aloud_timer\">Speaking(%d min left)</string>\n    <string name=\"playing_timer\">Playing(%d min left)</string>\n    <string name=\"ps_hide_navigation_bar\">Hide virtual buttons when reading</string>\n    <string name=\"pt_hide_navigation_bar\">Hide navigation bar</string>\n    <string name=\"re_navigation_bar_color\">Navigation bar color</string>\n    <string name=\"scoring\">Rating</string>\n    <string name=\"send_mail\">Email</string>\n    <string name=\"can_not_open\">Open failed</string>\n    <string name=\"can_not_share\">Share failed</string>\n    <string name=\"no_chapter\">No chapters</string>\n    <string name=\"add_url\">Add url</string>\n    <string name=\"add_book_url\">Add book url</string>\n    <string name=\"background\">Background</string>\n    <string name=\"author\">Author</string>\n    <string name=\"author_show\">Author: %s</string>\n    <string name=\"aloud_stop\">Speak Stopped</string>\n    <string name=\"clear_cache\">Clear cache</string>\n    <string name=\"clear_cache_success\">Cache cleared</string>\n    <string name=\"action_save\">Save</string>\n    <string name=\"edit_source\">Edit source</string>\n    <string name=\"edit_book_source\">Edit Book source</string>\n    <string name=\"disable_book_source\">Disable Book source</string>\n    <string name=\"add_book_source\">Add Book source</string>\n    <string name=\"add_rss_source\">Add subscription source</string>\n    <string name=\"book_file_selector\">Add books</string>\n    <string name=\"scan_book_source\">Scan</string>\n    <string name=\"copy_source\">Copy source</string>\n    <string name=\"paste_source\">Paste source</string>\n    <string name=\"source_rule_s\">Source rules description</string>\n    <string name=\"check_update\">Check for Updates</string>\n    <string name=\"camera_scan\">Scan QR code</string>\n    <string name=\"scan_image\">Scan local images</string>\n    <string name=\"rule_summary\">Rules description</string>\n    <string name=\"share\">Share</string>\n    <string name=\"share_app\">Share to</string>\n    <string name=\"flow_sys\">Follow system</string>\n    <string name=\"add\">Add</string>\n    <string name=\"import_book_source\">Import book sources</string>\n    <string name=\"import_local\">Import local</string>\n    <string name=\"import_on_line\">Import online</string>\n    <string name=\"replace_rule_title\">Replacement</string>\n    <string name=\"replace_rule_edit\">Edit replacement rule</string>\n    <string name=\"replace_rule\">Pattern</string>\n    <string name=\"replace_to\">Replacement</string>\n    <string name=\"img_cover\">Cover</string>\n    <string name=\"book\">Book</string>\n    <string name=\"volume_key_page\">Volume keys to turn page</string>\n    <string name=\"mouse_wheel_page\">Mouse wheel to turn page</string>\n    <string name=\"click_turn_page\">Tap screen to turn page</string>\n    <string name=\"page_anim\">Flip animation</string>\n    <string name=\"book_page_anim\">Flip animation (book)</string>\n    <string name=\"keep_light\">Keep screen awake</string>\n    <string name=\"back\">Back</string>\n    <string name=\"menu\">Menu</string>\n    <string name=\"adjust\">Adjust</string>\n    <string name=\"scroll_bar\">Scroll bar</string>\n    <string name=\"clear_all_content\">Clearing the cache will delete all saved chapters. Are you sure to delete it?</string>\n    <string name=\"book_source_share_url\">Book sources sharing</string>\n    <string name=\"replace_rule_summary\">Rule name</string>\n    <string name=\"replace_rule_invalid\">Pattern rule is empty or does not conform the regex specification.</string>\n    <string name=\"select_action\">Selection action</string>\n    <string name=\"select_all\">Select all</string>\n    <string name=\"select_all_count\">Select all(%1$d/%2$d)</string>\n    <string name=\"select_cancel_count\">Cancel select all(%1$d/%2$d)</string>\n    <string name=\"dark_theme\">Dark mode</string>\n    <string name=\"welcome\">Welcome page</string>\n    <string name=\"download_start\">Download start</string>\n    <string name=\"download_cancel\">Download cancel</string>\n    <string name=\"no_download\">No download</string>\n    <string name=\"download_count\">Downloaded %1$d/%2$d</string>\n    <string name=\"import_select_book\">Import selected book(s)</string>\n    <string name=\"threads_num_title\">Number of concurrent tasks</string>\n    <string name=\"change_icon\">Change icon</string>\n    <string name=\"remove_from_bookshelf\">Remove</string>\n    <string name=\"start_read\">Start reading</string>\n    <string name=\"data_loading\">Loading…</string>\n    <string name=\"load_error_retry\">Load failed, tap to retry</string>\n    <string name=\"book_intro\">Book description</string>\n    <string name=\"intro_show\">Description:%s</string>\n    <string name=\"intro_show_null\">Description: no introduction</string>\n    <string name=\"open_from_other\">Open external book</string>\n    <string name=\"origin_show\">Origin: %s</string>\n    <string name=\"import_replace_rule\">Import replace rules</string>\n    <string name=\"import_replace_rule_on_line\">Import online rules</string>\n    <string name=\"check_update_interval\">Check interval for updates</string>\n    <string name=\"bookshelf_px_0\">By recent list</string>\n    <string name=\"bookshelf_px_1\">By update time</string>\n    <string name=\"bookshelf_px_2\">By book title</string>\n    <string name=\"bookshelf_px_3\">By sort manually</string>\n    <string name=\"read_type\">Reading strategy</string>\n    <string name=\"compose_type\">Typesetting</string>\n    <string name=\"del_select\">Delete selected</string>\n    <string name=\"del_msg\">Are you sure to delete?</string>\n    <string name=\"clear_font\">Default font</string>\n    <string name=\"find_on_www\">Discovery</string>\n    <string name=\"find_source_manage\">Discovery</string>\n    <string name=\"find_empty\">No content.Go to Sources management to add it!</string>\n    <string name=\"del_all\">Delete all</string>\n    <string name=\"searchHistory\">Search history</string>\n    <string name=\"clear\">Clear</string>\n    <string name=\"showTitle\">Display book title on text</string>\n    <string name=\"refresh_default\">Book Sources sync</string>\n    <string name=\"no_last_chapter\">No latest chapter.</string>\n    <string name=\"showTimeBattery\">Display time and battery</string>\n    <string name=\"showLine\">Display divider</string>\n    <string name=\"dark_status_icon\">Darken the status bar\\'s icon color</string>\n    <string name=\"content\">Content</string>\n    <string name=\"copy_text\">Copy</string>\n    <string name=\"download_all\">Download all</string>\n    <string name=\"content_sl\">This is a test text, \\n\\u3000\\u3000 just to show you the effect</string>\n    <string name=\"text_bg_style\">Color and background (long tap to customize)</string>\n    <string name=\"immersion_status_bar\">Immersive status bar</string>\n    <string name=\"un_download\">%d chapter(s) left</string>\n    <string name=\"long_click_input_color\">Long tap to input color value</string>\n    <string name=\"loading\">Loading…</string>\n    <string name=\"group_zg\">Awaiting</string>\n    <string name=\"group_yf\">Awaiting more</string>\n    <string name=\"bookmark\">Bookmarks</string>\n    <string name=\"bookmark_add\">Add to Bookmarks</string>\n    <string name=\"action_del\">Delete</string>\n    <string name=\"load_over_time\">Loading timeout</string>\n    <string name=\"join_group\">Follow:%s</string>\n    <string name=\"copy_complete\">Copied successfully</string>\n    <string name=\"bookshelf_management\">Bookshelf management</string>\n    <string name=\"clear_bookshelf_s\">It will delete all books. Be careful,please.</string>\n    <string name=\"search_book_source\">Search book sources</string>\n    <string name=\"search_rss_source\">Search subscription sources</string>\n    <string name=\"search_book_source_num\">Search( %d sources in total)</string>\n    <string name=\"chapter_list_size\">Chapters(%d)</string>\n    <string name=\"text_bold\">Bold</string>\n    <string name=\"text_font\">Font</string>\n    <string name=\"text\">Text</string>\n    <string name=\"home_page\">Home page</string>\n    <string name=\"right\">Right</string>\n    <string name=\"left\">Left</string>\n    <string name=\"bottom\">Bottom</string>\n    <string name=\"top\">Top</string>\n    <string name=\"padding\">Padding</string>\n    <string name=\"padding_top\">Padding top</string>\n    <string name=\"padding_bottom\">Padding bottom</string>\n    <string name=\"padding_left\">Padding left</string>\n    <string name=\"padding_right\">Padding right</string>\n    <string name=\"check_book_source\">Check book sources</string>\n    <string name=\"check_select_source\">Check the selected source</string>\n    <string name=\"progress_show\">%1$s      Progress %2$d/%3$d</string>\n    <string name=\"tts_fix\">Please install and select Chinese TTS!</string>\n    <string name=\"tts_init_failed\">TTS initialization failed!</string>\n    <string name=\"jf_convert\">Simplified conversion</string>\n    <string name=\"jf_convert_o\">Off</string>\n    <string name=\"jf_convert_f\">Simplified to traditional</string>\n    <string name=\"jf_convert_j\">Traditional to simplified</string>\n    <string name=\"page_mode\">Flipping mode</string>\n    <string name=\"nb_file_sub_count\">%1$d items</string>\n    <string name=\"nb_file_path\">Storage:</string>\n    <string name=\"nb_file_add_shelf\">Add to Bookshelf</string>\n    <string name=\"nb_file_add_shelves\">Add to Bookshelf(%1$d)</string>\n    <string name=\"nb_file_add_succeed\">%1$d books added successfully</string>\n    <string name=\"fonts_folder\">Please put the font files in the Fonts folder of the storage root directory and reselect</string>\n    <string name=\"default_font\">Default font</string>\n    <string name=\"select_font\">Select fonts</string>\n    <string name=\"text_size\">Text size</string>\n    <string name=\"line_size\">Line spacing</string>\n    <string name=\"paragraph_size\">Paragraph spacing</string>\n    <string name=\"to_top\">To Top</string>\n    <string name=\"selection_to_top\">Selection To Top</string>\n    <string name=\"to_bottom\">To Bottom</string>\n    <string name=\"selection_to_bottom\">Selection To Bottom</string>\n    <string name=\"auto_expand_find\">Auto expand Discovery</string>\n    <string name=\"default_expand_first\">Default expand the first Discovery.</string>\n    <string name=\"threads_num\">Current threads %s</string>\n    <string name=\"read_aloud_speed\">Speech rate</string>\n    <string name=\"auto_next_page\">Auto scroll</string>\n    <string name=\"auto_next_page_stop\">Stop Auto scroll</string>\n    <string name=\"auto_next_page_speed\">Auto scroll speed</string>\n    <string name=\"book_info\">Book information</string>\n    <string name=\"book_info_edit\">Edit book information</string>\n    <string name=\"ps_default_read\">Use Bookshelf as start page</string>\n    <string name=\"pt_default_read\">Auto jump to Recent list</string>\n    <string name=\"replace_scope\">Replacement object. Book name or source url is available</string>\n    <string name=\"menu_action_group\">Groups</string>\n    <string name=\"download_path\">Cache path</string>\n    <string name=\"sys_file_picker\">System file picker</string>\n    <string name=\"new_version\">New version</string>\n    <string name=\"download_update\">Download updates</string>\n    <string name=\"volume_key_page_on_play\">Volume keys to turn page when reading</string>\n    <string name=\"tip_margin_change\">Margin adjustment</string>\n    <string name=\"allow_update\">Enable update</string>\n    <string name=\"disable_update\">Disable update</string>\n    <string name=\"split_long_chapter\">拆分超长章节</string>\n    <string name=\"need_more_time_load_content\">正文长度过长时，加载正文可能会花费更多时间</string>\n    <string name=\"revert_selection\">Inverse</string>\n    <string name=\"search_book_key\">Search book name/author</string>\n    <string name=\"debug_hint\">Book name,Author,URL</string>\n    <string name=\"faq\">FAQ</string>\n    <string name=\"pt_show_all_find\">Display all Discovery</string>\n    <string name=\"ps_show_all_find\">Display the selected origin\\'s Discovery if closed</string>\n    <string name=\"update_toc\">Update chapters</string>\n    <string name=\"txt_toc_rule\">Txt Chapters Rule</string>\n    <string name=\"set_charset\">Text encoding</string>\n    <string name=\"swap_sort\">Ascending/Descending order</string>\n    <string name=\"sort\">Sort</string>\n    <string name=\"sort_auto\">Sort automatically</string>\n    <string name=\"sort_default\">Sort default</string>\n    <string name=\"sort_manual\">Sort manually</string>\n    <string name=\"sort_by_name\">Sort by name</string>\n    <string name=\"go_to_top\">Scroll to the top</string>\n    <string name=\"go_to_bottom\">Scroll to the bottom</string>\n    <string name=\"read_y\">Read: %s</string>\n    <string name=\"pursue_more\">Awaiting update</string>\n    <string name=\"fattening\">Awaiting more</string>\n    <string name=\"finish\">Finished</string>\n    <string name=\"all_book\">All</string>\n    <string name=\"pursue_more_book\">Awaiting update books</string>\n    <string name=\"fattening_book\">Awaiting more chapters books</string>\n    <string name=\"finish_book\">Finished books</string>\n    <string name=\"local_book\">Local books</string>\n    <string name=\"status_bar_immersion\">The status bar color becomes transparent</string>\n    <string name=\"imm_navigation_bar\">immersion navigation bar</string>\n    <string name=\"imm_navigation_bar_s\">The navigation bar becomes transparent</string>\n    <string name=\"add_to_bookshelf\">Add to Bookshelf</string>\n    <string name=\"continue_read\">Continue reading</string>\n    <string name=\"cover_path\">Cover path</string>\n    <string name=\"page_anim_cover\">Cover</string>\n    <string name=\"page_anim_slide\">Slide</string>\n    <string name=\"page_anim_simulation\">Simulation</string>\n    <string name=\"page_anim_scroll\">Scroll</string>\n    <string name=\"page_anim_none\">None</string>\n    <string name=\"disable_manga_page_anim\">ページめくりアニメーションを無効にする</string>\n    <string name=\"up_change_source_last_chapter_t\">Update the latest chapter after changed origin in the background</string>\n    <string name=\"up_change_source_last_chapter_s\">if enabled,the update will start 1 minute later when the software is run</string>\n    <string name=\"behavior_main_t\">Auto hide ToolBar</string>\n    <string name=\"behavior_main_s\">The toolbar will be hidden automatically when scroll the Bookshelf</string>\n    <string name=\"login\">Login</string>\n    <string name=\"login_source\">Login%s</string>\n    <string name=\"success\">Success</string>\n    <string name=\"source_no_login\">The current source has not configured with a login address</string>\n    <string name=\"no_prev_page\">No prior page</string>\n    <string name=\"no_next_page\">No next page</string>\n\n    <!-- source start-->\n    <string name=\"source_name\">源名称(sourceName)</string>\n    <string name=\"source_url\">源URL(sourceUrl)</string>\n    <string name=\"source_group\">源分组(sourceGroup)</string>\n    <string name=\"diy_source_group\">自定义源分组</string>\n    <string name=\"diy_edit_source_group\">输入自定义源分组名称</string>\n    <string name=\"concurrent_rate\">并发率(concurrentRate)</string>\n    <string name=\"sort_url\">分类Url(sortUrl)</string>\n    <string name=\"login_url\">登录URL(loginUrl)</string>\n    <string name=\"login_ui\">登录UI(loginUi)</string>\n    <string name=\"login_check_js\">登录检查JS(loginCheckJs)</string>\n    <string name=\"comment\">源注释(sourceComment)</string>\n    <string name=\"r_search_url\">搜索地址(url)</string>\n    <string name=\"r_find_url\">发现地址规则(url)</string>\n    <string name=\"r_book_list\">书籍列表规则(bookList)</string>\n    <string name=\"r_book_name\">书名规则(name)</string>\n    <string name=\"r_book_url\">详情页url规则(bookUrl)</string>\n    <string name=\"r_author\">作者规则(author)</string>\n    <string name=\"rule_book_kind\">分类规则(kind)</string>\n    <string name=\"rule_book_intro\">简介规则(intro)</string>\n    <string name=\"rule_cover_url\">封面规则(coverUrl)</string>\n    <string name=\"rule_last_chapter\">最新章节规则(lastChapter)</string>\n    <string name=\"rule_word_count\">字数规则(wordCount)</string>\n    <string name=\"book_url_pattern\">书籍URL正则(bookUrlPattern)</string>\n    <string name=\"rule_book_info_init\">预处理规则(bookInfoInit)</string>\n    <string name=\"rule_toc_url\">目录URL规则(tocUrl)</string>\n    <string name=\"rule_can_re_name\">允许修改书名作者(canReName)</string>\n    <string name=\"rule_next_toc_url\">目录下一页规则(nextTocUrl)</string>\n    <string name=\"rule_chapter_list\">目录列表规则(chapterList)</string>\n    <string name=\"rule_chapter_name\">章节名称规则(ChapterName)</string>\n    <string name=\"rule_chapter_url\">章节URL规则(chapterUrl)</string>\n    <string name=\"rule_is_volume\">Volume mark(isVolume)</string>\n    <string name=\"rule_is_vip\">VIP标识(isVip)</string>\n    <string name=\"rule_update_time\">更新时间(ChapterInfo)</string>\n    <string name=\"pre_update_js\">更新之前Js(preUpdateJs)</string>\n    <string name=\"rule_book_content\">正文规则(content)</string>\n    <string name=\"rule_next_content\">正文下一页URL规则(nextContentUrl)</string>\n    <string name=\"rule_web_js\">WebViewJs(webJs)</string>\n    <string name=\"rule_source_regex\">资源正则(sourceRegex)</string>\n    <string name=\"rule_replace_regex\">替换规则(replaceRegex)</string>\n    <string name=\"rule_image_style\">图片样式(imageStyle)</string>\n    <string name=\"rule_pay_action\">pay action(payAction)</string>\n\n    <string name=\"source_icon\">图标(sourceIcon)</string>\n    <string name=\"r_articles\">列表规则(ruleArticles)</string>\n    <string name=\"r_next\">列表下一页规则(ruleNextArticles)</string>\n    <string name=\"r_title\">标题规则(ruleTitle)</string>\n    <string name=\"r_guid\">guid规则(ruleGuid)</string>\n    <string name=\"r_date\">时间规则(rulePubDate)</string>\n    <string name=\"r_categories\">类别规则(ruleCategories)</string>\n    <string name=\"r_description\">描述规则(ruleDescription)</string>\n    <string name=\"r_image\">图片url规则(ruleImage)</string>\n    <string name=\"r_content\">内容规则(ruleContent)</string>\n    <string name=\"r_style\">样式(style)</string>\n    <string name=\"r_inject_js\">注入Js(injectJs)</string>\n    <string name=\"r_link\">链接规则(ruleLink)</string>\n    <string name=\"check_key_word\">校验关键字(checkKeyWord)</string>\n    <string name=\"rule_actions\">操作(actions)</string>\n    <string name=\"rule_is_pay\">购买标识(isPay)</string>\n    <!-- source end-->\n\n    <!--error string start-->\n    <string name=\"error_no_source\">No source</string>\n    <string name=\"error_get_book_info\">Failed to obtain book information</string>\n    <string name=\"error_get_content\">Failed to obtain content</string>\n    <string name=\"error_get_chapter_list\">Failed to obtain chapters list</string>\n    <string name=\"error_get_web_content\">Failed to access website:%s</string>\n    <string name=\"error_read_file\">Failed to read file</string>\n    <string name=\"error_load_toc\">Failed to load chapters list</string>\n    <string name=\"error_get_data\">Failed to get data</string>\n    <string name=\"error_load_msg\">Failed to load\\n%s</string>\n    <string name=\"net_error_10001\">No network</string>\n    <string name=\"net_error_10002\">Network connection timeout</string>\n    <string name=\"net_error_10003\">Data parsing failed</string>\n    <!--error string end-->\n\n    <string name=\"source_http_header\">HTTP Header</string>\n    <string name=\"debug_source\">Debug source</string>\n    <string name=\"import_by_qr_code\">Import from QR code</string>\n    <string name=\"share_selected_source\">Share selected sources</string>\n    <string name=\"scan_qr_code\">Scan QR code</string>\n    <string name=\"click_on_selected_show_menu\">Tap to display Menu when selected </string>\n    <string name=\"theme\">Theme</string>\n    <string name=\"theme_mode\">Theme mode</string>\n    <string name=\"theme_mode_desc\">Select a theme you want</string>\n    <string name=\"join_qq_group\">Join QQ group</string>\n    <string name=\"bg_image_per\">Set the background image requires storage permission</string>\n    <string name=\"input_book_source_url\">Input book source address</string>\n    <string name=\"del_file\">Delete file</string>\n    <string name=\"del_file_success\">Deleted file</string>\n    <string name=\"sure_del_file\">Are you sure to delete this file?</string>\n    <string name=\"files_tree\">Directory</string>\n    <string name=\"intelligent_import\">Intelligent import</string>\n    <string name=\"discovery\">Discovery</string>\n    <string name=\"switch_display_style\">Switch display styles</string>\n    <string name=\"import_per\">Import local books requires storage permission</string>\n    <string name=\"night_theme\">Night Theme</string>\n    <string name=\"eink_theme\">E-Ink</string>\n    <string name=\"eink_theme_desc\">Optimization for E-ink devices</string>\n    <string name=\"get_storage_per\">requires storage permission</string>\n    <string name=\"double_click_exit\">Tap again to exit the program</string>\n    <string name=\"import_book_per\">Import local books requires storage permission</string>\n    <string name=\"network_connection_unavailable\">Network connection is not available</string>\n    <string name=\"yes\">Yes</string>\n    <string name=\"no\">No</string>\n    <string name=\"sure\">OK</string>\n    <string name=\"sure_del\">Are you sure to delete it?</string>\n    <string name=\"sure_del_any\">Are you sure to delete %s?</string>\n    <string name=\"sure_del_all_book\">Are you sure to delete all books？</string>\n    <string name=\"sure_del_download_book\">Do you want to delete the downloaded book chapters at the same time?</string>\n    <string name=\"qr_per\">Scan QR code requires Camera permissions</string>\n    <string name=\"aloud_can_not_auto_page\">Speech is running, cannot turn pages automatically</string>\n    <string name=\"input_charset\">Input encoding</string>\n    <string name=\"text_chapter_list_rule\">Txt Chapters Regex</string>\n    <string name=\"open_local_book_per\">Open local books requires storage permission</string>\n    <string name=\"no_book_name\">No bookName</string>\n    <string name=\"input_replace_url\">Input replacement rule URL</string>\n    <string name=\"get_book_list_success\">Search list obtained successfully%d</string>\n    <string name=\"non_null_name_url\">name and URL cannot be empty</string>\n    <string name=\"gallery\">Gallery</string>\n    <string name=\"get_ali_pay_hb\">get AliPay red envelopes</string>\n    <string name=\"non_update_url\">No update address</string>\n    <string name=\"check_host_cookie\">Opening the homepage, it will return to start page automatically after success</string>\n    <string name=\"click_check_after_success\">After successful login, please tap the icon on the upper right corner to test the homepage access</string>\n    <string name=\"chapter\">Chapter</string>\n    <string name=\"to\">To</string>\n    <string name=\"use_regex\">Using Regex</string>\n    <string name=\"text_indent\">Indent</string>\n    <string name=\"indent_0\">None</string>\n    <string name=\"indent_1\">Indent with 1 chars</string>\n    <string name=\"indent_2\">Indent with 2 chars</string>\n    <string name=\"indent_3\">Indent with 3 chars</string>\n    <string name=\"indent_4\">Indent with 4 chars</string>\n    <string name=\"select_folder\">Select a folder</string>\n    <string name=\"select_file\">Select a file</string>\n    <string name=\"no_find\">No Discovery, you can add it in BookSource</string>\n    <string name=\"restore_default\">Restore default</string>\n    <string name=\"set_download_per\">Custom cache path requires Storage permission</string>\n    <string name=\"black\">Black</string>\n    <string name=\"content_empty\">No content</string>\n    <string name=\"on_change_source\">Changing source, wait please</string>\n    <string name=\"chapter_list_empty\">Chapters is empty</string>\n    <string name=\"text_letter_spacing\">Word spacing</string>\n\n    <string name=\"source_tab_base\">Basic</string>\n    <string name=\"source_tab_search\">Search</string>\n    <string name=\"source_tab_find\">Discovery</string>\n    <string name=\"source_tab_info\">Information</string>\n    <string name=\"source_tab_toc\">Chapters</string>\n    <string name=\"source_tab_content\">Content</string>\n\n    <string name=\"e_ink_mode\">E-Ink mode</string>\n    <string name=\"e_ink_mode_detail\">Remove animations and optimize the experience of using E-paper books</string>\n    <string name=\"web_menu\">Web service</string>\n    <string name=\"web_port_title\">Web port</string>\n    <string name=\"web_port_summary\">Current port %s</string>\n    <string name=\"qr_share\">QR code sharing</string>\n    <string name=\"str_share\">Strings sharing</string>\n    <string name=\"wifi_share\">Wifi sharing</string>\n    <string name=\"please_grant_storage_permission\">Please grant Storage Permission</string>\n    <string name=\"fast_rewind\">Speed down</string>\n    <string name=\"fast_forward\">Speed up</string>\n    <string name=\"skip_previous\">Prior</string>\n    <string name=\"skip_next\">Next</string>\n    <string name=\"music\">Music</string>\n    <string name=\"audio\">Audio</string>\n    <string name=\"is_enable\">Enable</string>\n    <string name=\"enable_js\">Enable js</string>\n    <string name=\"load_with_base_url\">Load BaseUrl</string>\n    <string name=\"all_source\">All Sources</string>\n    <string name=\"cannot_empty\">The input content cannot be empty</string>\n    <string name=\"clear_find_cache\">Clear Discovery cache</string>\n    <string name=\"edit_find\">Edit Discovery</string>\n    <string name=\"change_icon_summary\">Switch the software icon displayed on the desktop</string>\n    <string name=\"help\">Help</string>\n    <string name=\"my\">Me</string>\n    <string name=\"reading\">Read</string>\n    <string name=\"battery_show\">%d%%</string>\n    <string name=\"timer_m\">%d min</string>\n    <string name=\"brightness_auto\">Auto-Brightness %s</string>\n    <string name=\"read_aloud_by_page\">Speak by pages</string>\n    <string name=\"speak_engine\">Speak Engine</string>\n    <string name=\"bg_image\">Background images</string>\n    <string name=\"bg_color\">Background color</string>\n    <string name=\"text_color\">Text color</string>\n    <string name=\"select_image\">Select a picture</string>\n    <string name=\"group_manage\">Group management</string>\n    <string name=\"group_select\">Group selection</string>\n    <string name=\"group_edit\">Group editing</string>\n    <string name=\"move_to_group\">Move to group</string>\n    <string name=\"add_group\">Add to Groups</string>\n    <string name=\"remove_group\">Remove from Groups</string>\n    <string name=\"add_replace_rule\">New replacement</string>\n    <string name=\"group\">Group</string>\n    <string name=\"group_s\">Group: %s</string>\n    <string name=\"toc_s\">Chapters: %s</string>\n    <string name=\"enable_explore\">Enable Discovery</string>\n    <string name=\"disable_explore\">Disable Discovery</string>\n    <string name=\"enable_selection\">Enable selected</string>\n    <string name=\"disable_selection\">Disable selected</string>\n    <string name=\"export_selection\">Export selected</string>\n    <string name=\"export\">Export</string>\n    <string name=\"load_toc\">Load chapters</string>\n    <string name=\"load_info\">Load book detail</string>\n    <string name=\"tts\">TTS</string>\n    <string name=\"web_dav_pw\">WebDav password</string>\n    <string name=\"web_dav_pw_s\">Input you WebDav authorized password</string>\n    <string name=\"web_dav_url_s\">Input you server address</string>\n    <string name=\"web_dav_url\">WebDav server address</string>\n    <string name=\"web_dav_account\">WebDav account</string>\n    <string name=\"web_dav_account_s\">Input your WebDav account</string>\n    <string name=\"rss_source\">Subscription source</string>\n    <string name=\"rss_source_edit\">Edit Subscription source</string>\n    <string name=\"screen\">Filter</string>\n    <string name=\"screen_find\">Search Discovery sources</string>\n    <string name=\"dur_pos\">Current location：</string>\n    <string name=\"precision_search\">Precise search</string>\n    <string name=\"service_starting\">Starting service</string>\n    <string name=\"empty\">Empty</string>\n    <string name=\"file_chooser\">File selection</string>\n    <string name=\"folder_chooser\">Folder selection</string>\n    <string name=\"bottom_line\">I AM OVER!</string>\n    <string name=\"uri_to_path_fail\">Uri To Path failed</string>\n    <string name=\"refresh_cover\">Refresh cover</string>\n    <string name=\"change_cover_source\">Change origin</string>\n    <string name=\"select_local_image\">Local image</string>\n    <string name=\"book_type\">Type:</string>\n    <string name=\"to_backstage\">Background</string>\n    <string name=\"importing\">Importing</string>\n    <string name=\"exporting\">Exporting</string>\n    <string name=\"custom_page_key\">Set page-turning buttons</string>\n    <string name=\"prev_page_key\">Page up button</string>\n    <string name=\"next_page_key\">Page down button</string>\n    <string name=\"no_group\">Ungrouped</string>\n    <string name=\"prev_sentence\">Prior sentence</string>\n    <string name=\"next_sentence\">Next sentence</string>\n    <string name=\"other_folder\">Other folder</string>\n    <string name=\"text_too_long_qr_error\">There are too many words to create a QR code</string>\n    <string name=\"share_rss_source\">Subscription sources sharing</string>\n    <string name=\"share_book_source\">Book sources sharing</string>\n    <string name=\"auto_dark_mode\">Automatic switching dark mode</string>\n    <string name=\"auto_dark_mode_s\">Following system dark mode</string>\n    <string name=\"go_back\">Go back</string>\n    <string name=\"tone_colour\">Online Speech tone</string>\n    <string name=\"select_count\">(%1$d/%2$d)</string>\n    <string name=\"show_rss\">Display Subscription</string>\n    <string name=\"service_stop\">Service stopped</string>\n    <string name=\"service_start\">Starting service\\nChecking notification bar for details</string>\n    <string name=\"default_path\">Default path</string>\n    <string name=\"sys_folder_picker\">System folder picker</string>\n    <string name=\"app_folder_picker\">App folder picker</string>\n    <string name=\"app_file_picker\">App file picker</string>\n    <string name=\"a10_permission_toast\">Android 10+ unable to read and write file due to permission restrictions</string>\n    <string name=\"add_to_text_context_menu_s\">Long tap to display Legado·Search in the operation menu</string>\n    <string name=\"add_to_text_context_menu_t\">Text operation display Search</string>\n    <string name=\"record_log\">Record log</string>\n    <string name=\"log\">Log</string>\n    <string name=\"chinese_converter\">Simplified conversion</string>\n    <string name=\"change_icon_error\">The icon is a vector icon, which was not supported before Android 8.0</string>\n    <string name=\"aloud_config\">Speech settings</string>\n    <string name=\"main_activity\">Start page</string>\n    <string name=\"selectText\">Long Tap to select text</string>\n    <string name=\"header\">Header</string>\n    <string name=\"main_body\">Content</string>\n    <string name=\"footer\">Footer</string>\n    <string name=\"select_end\">Select end</string>\n    <string name=\"select_start\">Select start</string>\n    <string name=\"share_layout\">Shared layout</string>\n    <string name=\"browser\">Browser</string>\n    <string name=\"import_default_rule\">Import default rules</string>\n    <string name=\"name\">Name</string>\n    <string name=\"regex\">Regex</string>\n    <string name=\"more_menu\">More menu</string>\n    <string name=\"reduce\">Minus</string>\n    <string name=\"plus\">Plus</string>\n    <string name=\"system_typeface\">System typeface</string>\n    <string name=\"delete_book_file\">Delete source file</string>\n    <string name=\"text_default\">Default</string>\n    <string name=\"default1\">Default-1</string>\n    <string name=\"default2\">Default-2</string>\n    <string name=\"default3\">Default-3</string>\n    <string name=\"title\">Title</string>\n    <string name=\"title_left\">Left</string>\n    <string name=\"title_center\">Center</string>\n    <string name=\"title_hide\">Hide</string>\n    <string name=\"add_to_group\">Add to Group</string>\n    <string name=\"save_image\">Save image</string>\n    <string name=\"no_default_path\">No default path</string>\n    <string name=\"change_group\">Group settings</string>\n    <string name=\"view_toc\">View Chapters</string>\n    <string name=\"bar_elevation\">Navigation bar shadow</string>\n    <string name=\"bar_elevation_s\">Current shadow size(elevation): %s</string>\n    <string name=\"btn_default_s\">Default</string>\n    <string name=\"main_menu\">Main menu</string>\n    <string name=\"request_permission\">Tap to grant permission</string>\n    <string name=\"tip_local_perm_request_storage\">Legado needs Storage permission, please tap the \"Grant Permission\" button below, or go to \"Settings\"-\"Application Permissions\"-to open the required permission. If the permission is still not work, please tap \"Select Folder\" in the upper right corner to use the system folder picker.</string>\n    <string name=\"alouding_disable\">The selected text cannot be spoken in full text speech</string>\n    <string name=\"read_body_to_lh\">Extend to cutout</string>\n    <string name=\"toc_updateing\">Updating Chapters</string>\n    <string name=\"media_button_on_exit_title\">Headset buttons are always available</string>\n    <string name=\"media_button_on_exit_summary\">Headset buttons are available even exit the app.</string>\n    <string name=\"contributors\">Contributors</string>\n    <string name=\"contact\">Contact</string>\n    <string name=\"license\">License</string>\n    <string name=\"other\">Other</string>\n    <string name=\"official_account\">开源阅读</string>\n    <string name=\"follow_official_account\">Follow WeChat Official Accounts</string>\n    <string name=\"wechat\">WeChat</string>\n    <string name=\"thanks\">Supporting me will be appreciated</string>\n    <string name=\"about_official_account\">Official Accounts[开源阅读]</string>\n    <string name=\"source_auto_changing\">Changing source</string>\n    <string name=\"click_to_apply\">Tap to join</string>\n    <string name=\"middle\">Middle</string>\n    <string name=\"information\">Information</string>\n    <string name=\"switchLayout\">Switch Layout</string>\n    <string name=\"text_font_weight_converter\">Text font weight switching</string>\n    <string name=\"full_screen_gestures_support\">Full screen gestures support</string>\n    <string name=\"disable_return_key\">Disable return key</string>\n\n    <!--color-->\n    <string name=\"primary\">Primary</string>\n    <string name=\"accent\">Accent</string>\n    <string name=\"background_color\">Background color</string>\n    <string name=\"navbar_color\">NavBar color</string>\n    <string name=\"day\">Day</string>\n    <string name=\"day_color_primary\">Day,Primary</string>\n    <string name=\"day_color_accent\">Day,Accent</string>\n    <string name=\"day_background_color\">Day,Background color</string>\n    <string name=\"day_navbar_color\">Day,NavBar color</string>\n    <string name=\"night\">Night</string>\n    <string name=\"night_primary\">Night,Primary</string>\n    <string name=\"night_accent\">Night,Accent</string>\n    <string name=\"night_background_color\">Night,Background color</string>\n    <string name=\"night_navbar_color\">Night,NavBar color</string>\n    <string name=\"auto_change_source\">Change source automatically</string>\n    <string name=\"text_full_justify\">Text justified</string>\n    <string name=\"text_bottom_justify\">Text align bottom</string>\n    <string name=\"auto_page_speed\">Auto scroll speed</string>\n    <string name=\"sort_by_url\">Sort by URL</string>\n    <string name=\"backup_summary\">Backup the local and WebDav simultaneously</string>\n    <string name=\"restore_summary\">Restore from WebDAV first, Restore form the local backup on long click</string>\n    <string name=\"import_old_summary\">Select a legacy backup folder</string>\n    <string name=\"enabled\">Enabled</string>\n    <string name=\"disabled\">Disabled</string>\n    <string name=\"enabled_explore\">Discovery Enabled</string>\n    <string name=\"disabled_explore\">Discovery Disabled</string>\n    <string name=\"starting_download\">Starting download</string>\n    <string name=\"already_in_download\">This book is already in Download list</string>\n    <string name=\"click_to_open\">Click to open</string>\n    <string name=\"follow_public_account_summary\">Follow [开源阅读] to support me by clicking on ads</string>\n    <string name=\"weChat_appreciation_code\">WeChat Tipping Code</string>\n    <string name=\"alipay\">AliPay</string>\n    <string name=\"alipay_red_envelope_search_code\">AliPay red envelope search code</string>\n    <string name=\"alipay_red_envelope_copy\">537954522 Click to copy</string>\n    <string name=\"alipay_red_envelope_qr_code\">AliPay red envelope QR code</string>\n    <string name=\"alipay_payment_qr_code\">AliPay QR code</string>\n    <string name=\"qq_collection_qr_code\">QQ Collection QR code</string>\n    <string name=\"contributors_summary\">gedoor,Invinciblelee,Xwite etc. Checking in github for details</string>\n    <string name=\"clear_cache_summary\">Clear the cache of the downloaded books and fonts</string>\n    <string name=\"default_cover\">Default cover</string>\n    <string name=\"restore_ignore\">Bypass list</string>\n    <string name=\"restore_ignore_summary\">Ignore some contents while restoring</string>\n    <string name=\"read_config\">Reading interface settings</string>\n    <string name=\"group_name\">Group name</string>\n    <string name=\"note_content\">Remarks section</string>\n    <string name=\"replace_enable_default_t\">Enable replace rule by default</string>\n    <string name=\"replace_enable_default_s\">For new added books</string>\n    <string name=\"select_restore_file\">Select restore file</string>\n    <string name=\"day_background_too_dark\">Day background can not be too dark!</string>\n    <string name=\"day_bottom_bar_too_dark\">Day bottom can not be too dark!</string>\n    <string name=\"night_background_too_light\">Night background can not be too bright!</string>\n    <string name=\"night_bottom_bar_too_light\">Night bottom can not be too bright!</string>\n    <string name=\"accent_background_diff\">Need Difference between accent and background color </string>\n    <string name=\"accent_text_diff\">Need Difference between accent and text color</string>\n    <string name=\"wrong_format\">Wrong format</string>\n    <string name=\"error\">Error</string>\n    <string name=\"show_brightness_view\">Show brightness widget</string>\n    <string name=\"language\">Language</string>\n    <string name=\"import_rss_source\">Import rss source</string>\n    <string name=\"donate_summary\">Your donation makes this app better</string>\n    <string name=\"about_summary\">Wechat official account [开源阅读软件]</string>\n    <string name=\"read_record\">Read record</string>\n    <string name=\"del_read_record\">Delete Read record</string>\n    <string name=\"read_record_summary\">Read record summary</string>\n    <string name=\"local_tts\">Local TTS</string>\n    <string name=\"thread_count\">Thread count</string>\n    <string name=\"all_read_time\">Total read time</string>\n    <string name=\"un_select_all\">Unselect all</string>\n    <string name=\"import_str\">Import</string>\n    <string name=\"export_str\">Export</string>\n    <string name=\"save_theme_config\">Save theme config</string>\n    <string name=\"save_day_theme_summary\">Save day theme config</string>\n    <string name=\"save_night_theme_summary\">Save night theme config</string>\n    <string name=\"theme_list\">Theme list</string>\n    <string name=\"theme_list_summary\">Save, Import, Share theme</string>\n    <string name=\"select_theme\">Switch default theme</string>\n    <string name=\"sort_by_lastUpdateTime\">Sort by update time</string>\n    <string name=\"search_content\">Search content</string>\n    <string name=\"rss_source_empty\">Empty now!</string>\n    <string name=\"explore_empty\">Empty now!</string>\n    <string name=\"page_key_set_help\">将焦点放到输入框按下物理按键会自动录入键值,多个按键会自动用英文逗号隔开.</string>\n    <string name=\"theme_name\">Theme name</string>\n    <string name=\"auto_clear_expired\">\"Clear expired search histories automatically \"</string>\n    <string name=\"auto_clear_expired_summary\">Search histories more than one day</string>\n    <string name=\"re_segment\">Re-segment</string>\n    <string name=\"style_name\">Style name:</string>\n    <string name=\"empty_msg_import_book\">Click the folder icon in the upper right corner and select the folder</string>\n    <string name=\"scan_folder\">Intelligent scanning</string>\n    <string name=\"import_file_name\">Imported-file name</string>\n    <string name=\"no_book\">No books</string>\n    <string name=\"keep_original_name\">Keep the original name</string>\n    <string name=\"click_regional_config\">Screen touch control</string>\n    <string name=\"close\">Close</string>\n    <string name=\"next_page\">Next page</string>\n    <string name=\"prev_page\">Prior page</string>\n    <string name=\"non_action\">None</string>\n    <string name=\"body_title\">Title</string>\n    <string name=\"show_hide\">Show/Hide</string>\n    <string name=\"header_footer\">footer <![CDATA[&]]> header</string>\n    <string name=\"rule_subscription\">Rule Subscription</string>\n    <string name=\"rule_sub_empty_msg\">添加大佬们提供的规则导入地址\\n添加后点击可导入规则</string>\n    <string name=\"get_book_progress\">クラウド上の進捗状況のカバー</string>\n    <string name=\"cover_book_progress\">Cobertura do progresso na nuvem</string>\n    <string name=\"current_progress_exceeds_cloud\">The current progress exceeds the cloud progress. Do you want to synchronize?</string>\n    <string name=\"sync_book_progress_t\">Synchronous reading progress</string>\n    <string name=\"sync_book_progress_s\">Synchronize reading progress when entering / exiting the reading interface</string>\n    <string name=\"sync_book_progress_plus_t\">同期強化</string>\n    <string name=\"sync_book_progress_plus_s\">ページに再度入ったとき（画面オフ、バックグラウンドからの復帰など）やネットワークが利用可能になったときにクラウドの進捗を同期します。新しい進捗を同期する前に確認します。</string>\n    <string name=\"create_bookmark_error\">Failed to create bookmark</string>\n    <string name=\"single_url\">Single URL</string>\n    <string name=\"export_bookshelf\">Export the list of books</string>\n    <string name=\"import_bookshelf\">Import the list of books</string>\n    <string name=\"pre_download\">Download in advance</string>\n    <string name=\"pre_download_m\">预下载%s页</string>\n    <string name=\"pre_download_s\">Download %s chapters in advance</string>\n    <string name=\"is_enabled\">Is enabled</string>\n    <string name=\"background_image\">Background image</string>\n    <string name=\"copy_book_url\">Copy book URL</string>\n    <string name=\"copy_toc_url\">Copy chapters URL</string>\n    <string name=\"export_folder\">Export to a folder</string>\n    <string name=\"export_charset\">Exported text coding</string>\n    <string name=\"export_to_web_dav\">Export to WebDav</string>\n    <string name=\"reverse_content\">Reverse content</string>\n    <string name=\"debug\">Debug</string>\n    <string name=\"crash_log\">Crash log</string>\n    <string name=\"use_zh_layout\">Custom Chinese line feed</string>\n    <string name=\"image_style\">Style of Images</string>\n    <string name=\"system_tts\">System tts</string>\n    <string name=\"export_type\">Exported file format</string>\n    <string name=\"checkAuthor\">Check by author</string>\n    <string name=\"url_already\">This URL has subscribed</string>\n    <string name=\"high_brush_title\">High screen refresh rate</string>\n    <string name=\"high_brush_summary\">Use maximum screen refresh rate</string>\n    <string name=\"export_all\">Export all</string>\n    <string name=\"complete\">Finished</string>\n    <string name=\"show_unread\">Show unread flag</string>\n    <string name=\"use_default_cover\">Always show default cover</string>\n    <string name=\"use_default_cover_s\">Always show the default cover, do not show the network cover</string>\n    <string name=\"search_src\">Search source code</string>\n    <string name=\"boo_src\">Book source code</string>\n    <string name=\"toc_src\">Chapters source code</string>\n    <string name=\"content_src\">Content source code</string>\n    <string name=\"list_src\">List source code</string>\n    <string name=\"title_font_size\">Font size</string>\n    <string name=\"title_margin_top\">Margin top</string>\n    <string name=\"title_margin_bottom\">Marigin bottom</string>\n    <string name=\"show\">Show</string>\n    <string name=\"hide\">Hide</string>\n    <string name=\"hide_when_status_bar_show\">Hide when status bar show</string>\n    <string name=\"reverse_toc\">Reverse toc</string>\n    <string name=\"show_discovery\">显示发现</string>\n    <string name=\"style\">样式</string>\n    <string name=\"group_style\">分组样式</string>\n    <string name=\"export_file_name\">导出文件名</string>\n    <string name=\"reset\">重置</string>\n    <string name=\"null_url\">url为空</string>\n    <string name=\"dict\">字典</string>\n    <string name=\"unknown_error\">未知错误</string>\n    <string name=\"export_no_chapter_name\">No export chapter names</string>\n    <string name=\"end\">end</string>\n    <string name=\"custom_group_summary\">关闭替换分组/开启添加分组</string>\n    <string name=\"pref_media_button_per_next\">媒体按钮•上一首|下一首</string>\n    <string name=\"pref_media_button_per_next_summary\">上一段|下一段/上一章|下一章</string>\n    <string name=\"read_aloud_by_page_summary\">及时翻页,翻页时会停顿一下</string>\n    <string name=\"check_source_show_debug_message\">Check book source shows debug message</string>\n    <string name=\"check_source_show_debug_message_summary\">Show network status and timestamp during source checking</string>\n    <string name=\"autobackup_fail\">Autobackup failed\\n%s</string>\n    <string name=\"background_image_blurring\">Background image blurring</string>\n    <string name=\"background_image_blurring_radius\">Blurring radius</string>\n    <string name=\"background_image_hint\">Disabled when 0, enable range from 1 to 25\\nThe greater the radius, the stronger the effect of blurring</string>\n    <string name=\"need_login\">需登录</string>\n    <string name=\"pref_cronet_summary\">使用Cronet网络组件</string>\n    <string name=\"anti_alias\">Anti-Aliasing</string>\n    <string name=\"pref_anti_alias_summary\">Anti-Aliasing when draw picture</string>\n    <string name=\"upload_url\">创建分享链接</string>\n    <string name=\"download_url_rule\">downloadUrlRule(downloadUrls)</string>\n    <string name=\"sort_by_respondTime\">Sort by respond time</string>\n    <string name=\"respondTime\">respondTime: %1$d ms</string>\n    <string name=\"export_success\">导出成功</string>\n    <string name=\"path\">路径</string>\n    <string name=\"direct_link_upload_rule\">直链上传规则</string>\n    <string name=\"direct_link_upload_rule_summary\">用于导出书源书单时生成直链url</string>\n    <string name=\"direct_link_upload_config\">直链上传配置</string>\n    <string name=\"copy_play_url\">拷贝播放Url</string>\n    <string name=\"set_source_variable\">设置源变量</string>\n    <string name=\"set_book_variable\">设置书籍变量</string>\n    <string name=\"summary\">注释</string>\n    <string name=\"cover_config\">封面设置</string>\n    <string name=\"cover_config_summary\">通用封面规则及默认封面样式</string>\n    <string name=\"cover_show_name\">显示书名</string>\n    <string name=\"cover_show_name_summary\">封面上显示书名</string>\n    <string name=\"cover_show_author\">显示作者</string>\n    <string name=\"cover_show_author_summary\">封面上显示作者</string>\n    <string name=\"read_aloud_prev_paragraph\">朗读上一段</string>\n    <string name=\"read_aloud_next_paragraph\">朗读下一段</string>\n    <string name=\"wait_download\">待下载</string>\n    <string name=\"download_success\">下载完成</string>\n    <string name=\"download_error\">下载失败</string>\n    <string name=\"downloading\">下载中</string>\n    <string name=\"unknown_state\">未知状态</string>\n    <string name=\"disable_source\">禁用源</string>\n    <string name=\"delete_source\">删除源</string>\n    <string name=\"chapter_pay\">购买</string>\n    <string name=\"double_page_horizontal\">平板/横屏双页</string>\n    <string name=\"open_in_browser\">浏览器打开</string>\n    <string name=\"copy_url\">拷贝url</string>\n    <string name=\"full_screen\">全屏</string>\n    <string name=\"open_fun\">打开方式</string>\n    <string name=\"use_browser_open\">是否使用外部浏览器打开?</string>\n    <string name=\"see\">查看</string>\n    <string name=\"open\">打开</string>\n    <string name=\"del_login_header\">删除登录头</string>\n    <string name=\"show_login_header\">查看登录头</string>\n    <string name=\"login_header\">登录头</string>\n    <string name=\"font_scale\">字体大小</string>\n    <string name=\"font_scale_summary\">当前字体大小:%.1f</string>\n    <string name=\"search_content_size\">search result</string>\n    <string name=\"search_content_empty\">Empty search result, check conversation settings</string>\n    <string name=\"tts_speech_reduce\">语速减</string>\n    <string name=\"tts_speech_add\">语速加</string>\n    <string name=\"open_sys_dir_picker_error\">打开系统文件夹选择器出错,自动打开应用文件夹选择器</string>\n    <string name=\"open_sys_doc_picker_error\">打开系统文件选择器出错,自动打开应用文件选择器</string>\n    <string name=\"expand_text_menu\">展开文本选择菜单</string>\n    <string name=\"book_tree_uri_t\">书籍保存位置</string>\n    <string name=\"book_tree_uri_s\">从其它应用打开的书籍保存位置</string>\n    <string name=\"select_book_folder\">选择保存书籍的文件夹</string>\n    <string name=\"user_agent\">用户代理</string>\n    <string name=\"bg_alpha\">背景透明度</string>\n\n    <!-- check source config string -->\n    <string name=\"check_source_config\">校验设置</string>\n    <string name=\"check_source_item\">校验项目</string>\n    <string name=\"check_source_timeout\">单个书源校验超时(秒)</string>\n    <string name=\"timeout\">超时</string>\n    <string name=\"seconds\">秒</string>\n    <string name=\"less_than\">小于</string>\n    <string name=\"check_source_config_summary\">校验超时: %1$s秒\\n校验项目:%2$s</string>\n    <string name=\"record_debug_log\">记录调试日志</string>\n    <string name=\"sub_dir\">子文件夹</string>\n    <string name=\"general\">全局</string>\n    <string name=\"use_replace\">使用替换</string>\n    <string name=\"scope_title\">作用于标题</string>\n    <string name=\"scope_content\">作用于正文</string>\n    <string name=\"join_qq_channel\">加入QQ频道</string>\n    <string name=\"qq_channel_summary\">点击加入阅读QQ频道</string>\n    <string name=\"menu_refresh_dur\">刷新当前章节</string>\n    <string name=\"menu_refresh_after\">刷新之后章节</string>\n    <string name=\"menu_refresh_all\">刷新全部章节</string>\n    <string name=\"edit_content\">编辑内容</string>\n    <string name=\"chapter_change_source\">单章换源</string>\n    <string name=\"book_change_source\">整书换源</string>\n    <string name=\"sort_by_time\">时间排序</string>\n    <string name=\"enable_record\">开启记录</string>\n    <string name=\"copy_all\">拷贝所有</string>\n    <string name=\"auto_complete\">自动补全</string>\n    <string name=\"sort_by_size\">大小排序</string>\n    <string name=\"welcome_style\">启动界面样式</string>\n    <string name=\"welcome_style_summary\">启动界面图片和是否显示文字等</string>\n    <string name=\"show_welcome_text\">显示文字</string>\n    <string name=\"welcome_text\">阅读|享受美好时光</string>\n    <string name=\"custom_welcome\">自定义欢迎页</string>\n    <string name=\"custom_welcome_summary\">是否使用自定义欢迎页</string>\n    <string name=\"show_icon\">显示图标</string>\n    <string name=\"show_default_book_icon\">显示默认书籍图标</string>\n    <string name=\"cache_export\">缓存/导出</string>\n    <string name=\"assists_key_config\">辅助按键配置</string>\n    <string name=\"url_option\">Url参数</string>\n    <string name=\"only_wifi\">仅WIFI</string>\n    <string name=\"only_wifi_summary\">仅在wifi下加载网络封面</string>\n    <string name=\"cover_rule\">封面规则</string>\n    <string name=\"cover_rule_summary\">进入详情页时使用封面规则重新获取封面</string>\n    <string name=\"scroll_to_dur_source\">定位到当前书源</string>\n    <string name=\"sys_tts_config\">系统tts设置</string>\n    <string name=\"sys_tts_config_summary\">打开系统tts设置界面</string>\n    <string name=\"cannot_timed_non_playback\">非播放状态无法定时</string>\n    <string name=\"all_bookmark\">すべてのブックマーク</string>\n    <string name=\"change_source_batch\">批量换源</string>\n    <string name=\"book_type_different\">书籍类型不一样</string>\n    <string name=\"soure_change_source\">是否确认换源</string>\n    <string name=\"input_verification_code\">输入验证码</string>\n    <string name=\"verification_code\">验证码</string>\n    <string name=\"timeout_millisecond\">超时毫秒数</string>\n    <string name=\"file_not_supported\">Continue to open although %1$s is not supported ?</string>\n    <string name=\"import_tts\">导入TTS</string>\n    <string name=\"import_theme\">导入主题</string>\n    <string name=\"import_txt_toc_rule\">导入txt目录规则</string>\n    <string name=\"auto_save_cookie\">CookieJar</string>\n    <string name=\"cookie\">清除cookie</string>\n    <string name=\"download_and_import_file\">导入在线书籍文件</string>\n    <string name=\"upload_book_success\">Upload Success</string>\n    <string name=\"upload_book_fail\">Upload Fail</string>\n    <string name=\"download_book_success\">Download Success</string>\n    <string name=\"download_book_fail\">Download Fail</string>\n    <string name=\"upload_to_remote\">Upload</string>\n    <string name=\"add_remote_book\">RemoteBook</string>\n    <string name=\"bitmap_cache_size_summary\">Current cache max size %1$s MB</string>\n    <string name=\"bitmap_cache_size\">bitmap cache size</string>\n    <string name=\"image_retain_number_summary\">保留已读章节数量 %s</string>\n    <string name=\"image_retain_number\">漫画保留数量</string>\n    <string name=\"export_pics_file\">Export Picture Files</string>\n    <!-- string end -->\n    <string name=\"error_decode_bitmap\">Fail to decode bitmap</string>\n    <string name=\"error_image_url_empty\">Image url is empty, check replacement rules</string>\n    <string name=\"variable_comment\">变量说明(variableComment)</string>\n    <string name=\"remote_book\">远程书籍</string>\n    <string name=\"reading_time_sort\">阅读时长排序</string>\n    <string name=\"last_read_time_sort\">阅读时间排序</string>\n    <string name=\"reading_time_tag\">阅读时长:</string>\n    <string name=\"last_read_time_tag\">最后阅读时间:</string>\n    <string name=\"page_touch_slop_title\">滑动翻页阈值</string>\n    <string name=\"page_touch_slop_dialog_title\">滑动翻页阈值（0 = 系统默认值）</string>\n    <string name=\"page_touch_slop_summary\">滑动多长距离才会触发滑动翻页（系统默认值 %s px）</string>\n    <string name=\"example\">例</string>\n    <string name=\"check_selected_interval\">选中所选区间</string>\n    <string name=\"show_add_to_shelf_alert_title\">返回时提示放入书架</string>\n    <string name=\"show_add_to_shelf_alert_summary\">阅读未放入书架的书籍在返回时提示放入书架</string>\n    <string name=\"review\">Review</string>\n    <string name=\"rule_review_url\">段评URL（reviewUrl）</string>\n    <string name=\"rule_avatar\">段评发布者头像（avatarRule）</string>\n    <string name=\"rule_review_content\">段评内容（contentRule）</string>\n    <string name=\"rule_post_time\">段评发布时间（postTimeRule）</string>\n    <string name=\"rule_review_quote\">段评回复URL（reviewQuoteUrl）</string>\n    <string name=\"review_vote_down\">点踩URL（voteDownUrl）</string>\n    <string name=\"review_vote_up\">点赞URL（voteUpUrl）</string>\n    <string name=\"post_review_url\">发送回复URL（postReviewUrl）</string>\n    <string name=\"post_quote_url\">发送回复段评URL（postQuoteUrl）</string>\n    <string name=\"delete_review_url\">删除段评URL（deleteUrl）</string>\n    <string name=\"tag_explore_enabled\">标志:发现已启用</string>\n    <string name=\"tag_explore_disabled\">标志:发现已禁用</string>\n    <string name=\"show_read_title_addition\">show read title addition area</string>\n    <string name=\"read_bar_style_follow_page\">read bar style follow page</string>\n    <string name=\"rule_image_decode\">Decode Image(imageDecode)</string>\n    <string name=\"like_source\">赞</string>\n    <string name=\"not_like_source\">踩</string>\n    <string name=\"async_load_image\">异步加载图片</string>\n    <string name=\"ignore_audio_focus_title\">忽略音频焦点</string>\n    <string name=\"ignore_audio_focus_summary\">允许与其他应用同时播放音频</string>\n    <string name=\"refresh_sort\">刷新分类</string>\n    <string name=\"cover_decode_js\">Decode Cover Js(coverDecodeJs)</string>\n    <string name=\"net_no_group\">网络未分组</string>\n    <string name=\"local_no_group\">本地未分组</string>\n    <string name=\"parallel_export_book\">多线程导出</string>\n    <string name=\"progress_bar_behavior\">进度条行为</string>\n    <string name=\"source_edit_text_max_line\">源编辑框最大行数</string>\n    <string name=\"source_edit_max_line_summary\">%s,设置行数小于屏幕可显示的最大行数可以更方便的滑动到其他的字段进行编辑</string>\n    <string name=\"restore_last_book_process\">是否恢复到跳转前的阅读进度？</string>\n    <string name=\"search_scope\">搜索范围</string>\n    <string name=\"toggle_search_scope\">切换</string>\n    <string name=\"sure_clear_search_history\">是否确认清除所有搜索历史记录</string>\n    <string name=\"no_anim_scroll_page\">禁用滚动点击动画</string>\n    <string name=\"webdav_device_name\">设备名称</string>\n    <string name=\"web_service_wake_lock\">WebService唤醒锁</string>\n    <string name=\"web_service_wake_lock_summary\">开启web服务的时候启用唤醒锁,有些手机开启唤醒锁会被杀后台</string>\n    <string name=\"read_aloud_wake_lock\">朗读服务唤醒锁</string>\n    <string name=\"read_aloud_wake_lock_summary\">开启朗读的时候启用唤醒锁,有些手机开启唤醒锁会被杀后台</string>\n    <string name=\"audio_play_wake_lock\">音频服务唤醒锁</string>\n    <string name=\"audio_play_wake_lock_summary\">播放音频的时候启用唤醒锁,有些手机开启唤醒锁会被杀后台</string>\n    <string name=\"change_search_scope\">切换搜索范围</string>\n    <string name=\"copy_rule\">拷贝规则</string>\n    <string name=\"paste_rule\">粘贴规则</string>\n    <string name=\"groups_or_source\">多分组/书源</string>\n    <string name=\"replace_state_change\">替换(启用/禁用)</string>\n    <string name=\"show_last_update_time\">显示上次更新时间</string>\n    <string name=\"refresh_list\">刷新列表</string>\n    <string name=\"tip_divider_color\">分隔线颜色</string>\n    <string name=\"same_title_removed\">移除重复标题</string>\n    <string name=\"update_book_fail\">更新失败</string>\n    <string name=\"notification_permission_rationale\">阅读需要发送通知来显示朗读控制和下载进度</string>\n    <string name=\"webdav_after_local_restore_confirm\">webDav书源比本地新,是否恢复</string>\n    <string name=\"c_whitelist\">白名单(contentWhitelist)</string>\n    <string name=\"c_blacklist\">黑名单(contentBlacklist)</string>\n    <string name=\"confirm\">确认</string>\n    <string name=\"jump_to_another_app\">跳转其它应用</string>\n    <string name=\"clear_webview_data\">清除 WebView 数据</string>\n    <string name=\"clear_webview_data_summary\">清除内置浏览器所有数据</string>\n    <string name=\"source_tab_list\">列表</string>\n    <string name=\"dict_rule\">字典规则</string>\n    <string name=\"config_dict_rule\">配置字典规则</string>\n    <string name=\"create\">新建</string>\n    <string name=\"url_rule\">url规则(urlRule)</string>\n    <string name=\"show_rule\">显示规则(showRule)</string>\n    <string name=\"config_txt_toc_rule\">配置 TXT 目录规则</string>\n    <string name=\"import_dict_rule\">导入字典规则</string>\n    <string name=\"keep_group\">保留分组</string>\n    <string name=\"server_config\">服务器配置</string>\n    <string name=\"sure_upload\">Remote webDav url exists, Continue?</string>\n    <string name=\"unsupport_archivefile_entry\">Cannot find supported files in archive</string>\n    <string name=\"no_books_dir\">没有设置书籍保存位置!</string>\n    <string name=\"delete_alert\">删除提醒</string>\n    <string name=\"no_book_found_bookshelf\">No book found in bookshelf, import again ?</string>\n    <string name=\"archive_not_found\">Can not find archive selected, continue to download ?</string>\n    <string name=\"privacy_policy\">用户隐私与协议</string>\n    <string name=\"agree\">同意</string>\n    <string name=\"refuse\">拒绝</string>\n    <string name=\"file_manage\">文件管理</string>\n    <string name=\"file_manage_summary\">管理私有文件夹的文件</string>\n    <string name=\"create_folder\">创建文件夹</string>\n    <string name=\"allow_drop_down_refresh\">允许下拉刷新</string>\n    <string name=\"text_underline\">文字下划线</string>\n    <string name=\"select_new_source\">选中新增源</string>\n    <string name=\"select_update_source\">选中更新源</string>\n    <string name=\"set_local_password\">设置本地密码</string>\n    <string name=\"set_local_password_summary\">本地密码用来对备份的敏感信息加密和解密,如需在不同设备之间同步,本地密码需一致.</string>\n    <string name=\"only_latest_backup_t\">仅保留最新备份</string>\n    <string name=\"only_latest_backup_s\">本地备份仅保留最新备份文件</string>\n    <string name=\"webdav_application_authorization_error\">Fail to authorize WebDav application</string>\n    <string name=\"load_word_count\">Load word count</string>\n    <string name=\"replace_exclude_scope\">排除范围，选填书名或者书源 URL</string>\n    <string name=\"format_js_rule\">格式化规则(formatJs)</string>\n    <string name=\"adjust_pos\">调整位置</string>\n    <string name=\"select_section_export\">Choose some chapters to be exported</string>\n    <string name=\"error_scope_input\">Please enter the correct range</string>\n    <string name=\"custom_export\">Custom Export</string>\n    <string name=\"file_contains_number\">The number of chapters contained in each file</string>\n    <string name=\"export_chapter_index\">The section index that needs to be exported</string>\n    <string name=\"shrink_database\">压缩数据库</string>\n    <string name=\"shrink_database_summary\">减小数据库文件的大小</string>\n    <string name=\"is_compress\">是否压缩</string>\n    <string name=\"sort_desc\">反序</string>\n    <string name=\"test\">测试</string>\n    <string name=\"show_wait_up_count\">显示等待更新数量</string>\n    <string name=\"exit_app\">退出软件</string>\n    <string name=\"result_analyzed\">Analyzed</string>\n    <string name=\"bookshelf_px_4\">Comprehensive</string>\n    <string name=\"bookshelf_px_5\">著者別</string>\n    <string name=\"effective_replaces\">起效的替换</string>\n    <string name=\"export_book\">导出书籍</string>\n    <string name=\"export_book_notification_content\">正在导出(%1$s),还有%2$d本待导出</string>\n    <string name=\"export_md\">导出(MD)</string>\n    <string name=\"change_source_delay\">换源间隔</string>\n    <string name=\"change_source_progress\">結果 %1$d、現在の進行状況 %2$d / %3$d: %4$s</string>\n    <string name=\"open_book_info_by_click_title\">点击书名打开详情</string>\n    <string name=\"export_wait\">等待导出</string>\n    <string name=\"default_home_page\">默认主页</string>\n    <string name=\"sync_book_progress_success\">Sync progress success</string>\n    <string name=\"show_bookshelf_fast_scroller\">显示快速滚动条</string>\n    <string name=\"export_all_use_book_source\">导出所有书的书源</string>\n    <string name=\"cloud_progress_exceeds_current\">The cloud progress exceeds the current progress. Do you want to synchronize?</string>\n    <string name=\"keep_enable\">保留启用状态</string>\n    <string name=\"preview_image_by_click\">点击预览图片</string>\n    <string name=\"screen_portrait_reversed\">反向竖屏</string>\n    <string name=\"del_ruby_tag\">删除ruby标签</string>\n    <string name=\"del_h_tag\">删除h标签</string>\n    <string name=\"adjust_chapter_page\">调整本章页数</string>\n    <string name=\"adjust_chapter_index\">调整章节位置</string>\n    <string name=\"clear_webview_data_success\">清除成功，3秒后自动重启应用</string>\n    <string name=\"key_page_on_long_press\">按键长按翻页</string>\n    <string name=\"save_log\">保存日志</string>\n    <string name=\"create_heap_dump\">创建堆转储</string>\n    <string name=\"record_heap_dump_s\">当应用发生OOM崩溃时保存堆转储</string>\n    <string name=\"record_heap_dump_t\">记录堆转储</string>\n    <string name=\"font_weight_text\">中/粗/细</string>\n    <string name=\"keep_swipe_tip\">继续滑动以加载下一章…</string>\n    <string name=\"enable_optimize_render\">启用绘制优化</string>\n    <string name=\"ignore_battery_permission_rationale\">阅读需要请求后台权限以保持服务正常运行</string>\n    <string name=\"simulated_reading\">模擬追読</string>\n    <string name=\"switch_on\">スイッチ</string>\n    <string name=\"start_from\">開始日</string>\n    <string name=\"daily_chapters\">日更の章数</string>\n    <string name=\"start_chapter\">開始の章</string>\n    <string name=\"update_to_variant_title\">检查更新查找版本</string>\n    <string name=\"update_to_variant_summary\">检查更新时查找其他签名版本</string>\n    <string name=\"default_version\">当前</string>\n    <string name=\"official_version\">正式版</string>\n    <string name=\"beta_release_version\">测试版</string>\n    <string name=\"beta_releaseA_version\">共存版</string>\n    <string name=\"stream_read_aloud_audio\">流式播放音频</string>\n    <string name=\"stream_read_aloud_audio_summary\">即边下边播，网络不好时播放会断断续续，仅TTS源有效</string>\n    <string name=\"pause_read_aloud_while_phone_calls_title\">来电期间暂停朗读</string>\n    <string name=\"pause_read_aloud_while_phone_calls_summary\">在通话期间暂停朗读，需要读取手机状态权限</string>\n    <string name=\"read_aloud_read_phone_state_permission_rationale\">阅读需要读取手机状态实现来电期间暂停朗读功能</string>\n    <string name=\"read_aloud_by_media_button_title\">耳机按键启动朗读</string>\n    <string name=\"read_aloud_by_media_button_summary\">通过耳机按键来启动朗读</string>\n    <string name=\"group_sources_by_domain\">按域名分组显示</string>\n    <string name=\"theme_config\">主题配置</string>\n    <string name=\"show_manga_ui\">漫画浏览</string>\n    <string name=\"disable_manga_scale\">禁用漫画缩放</string>\n    <string name=\"disable_manga_click_scroll\">禁用点击翻页</string>\n    <string name=\"enable_auto_page_scroll\">开启自动翻页</string>\n    <string name=\"manga_auto_page_speed\">翻页速度 %s</string>\n    <string name=\"enable_auto_scroll\">开启滚动</string>\n    <string name=\"manga_footer_config\">页脚配置</string>\n    <string name=\"setting_manga_auto_page_speed\">设置自动翻页速度</string>\n    <string name=\"book_reader_info_bar\">页数. %1$d/%2$d  章节. %3$d/%4$d -> %5$s%%</string>\n    <string name=\"menu_download_after\">下载之后章节</string>\n    <string name=\"menu_download_all\">下载全部章节</string>\n    <string name=\"manga_header_chapter\">《章节和文案》隐藏</string>\n    <string name=\"manga_check_chapter_label\">《章节.》文案</string>\n    <string name=\"manga_check_chapter\">章节</string>\n    <string name=\"manga_check_chapter_name\">章节名称</string>\n    <string name=\"manga_header_page\">《页数和文案》隐藏</string>\n    <string name=\"manga_check_page_label\">《页数.》文案</string>\n    <string name=\"manga_check_page_number\">页数</string>\n    <string name=\"manga_header_progress\">《总进度和文案》隐藏</string>\n    <string name=\"manga_check_progress_label\">《总进度.》文案</string>\n    <string name=\"manga_check_progress\">总进度</string>\n    <string name=\"manga_header_footer\">页脚</string>\n    <string name=\"manga_radio_left\">靠左</string>\n    <string name=\"manga_radio_center\">居中</string>\n    <string name=\"enable_manga_horizontal_scroll\">水平滚动</string>\n    <string name=\"manga_color_filter\">滤镜</string>\n    <string name=\"hide_manga_title\">隐藏漫画列表标题</string>\n    <string name=\"refresh_explore\">刷新发现</string>\n    <string name=\"padding_display_cutouts\">Padding display cutouts</string>\n    <string name=\"manga_epaper\">墨水屏</string>\n    <string name=\"manga_epaper_stting\">墨水屏设置</string>\n    <string name=\"manga_epaper_value\">阈值</string>\n    <string name=\"disable_horizontal_page_snap\">禁用水平翻页效果</string>\n    <string name=\"enable_manga_gray\">开启图片灰色</string>\n    <string name=\"sure_cache_book\">是否确认开始缓存？</string>\n    <string name=\"auto_check_new_backup_t\">自动检查新备份</string>\n    <string name=\"auto_check_new_backup_s\">打开软件时检查是否有新备份，有新备份时提示是否更新</string>\n    <string name=\"sys_image_picker\">系统图片选择器</string>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values-night/colors.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <color name=\"primary\">@color/md_blue_grey_600</color>\n    <color name=\"primaryDark\">@color/md_blue_grey_700</color>\n    <color name=\"accent\">@color/md_deep_orange_800</color>\n\n    <color name=\"disabled\">@color/md_dark_disabled</color>\n\n    <color name=\"background\">@color/md_grey_900</color>\n    <color name=\"background_card\">@color/md_grey_850</color>\n    <color name=\"background_menu\">@color/md_grey_800</color>\n    <color name=\"background_prefs\">#10303030</color>\n\n    <color name=\"night_mask\">#69000000</color>\n\n    <color name=\"transparent10\">#10ffffff</color>\n    <color name=\"transparent20\">#20ffffff</color>\n    <color name=\"transparent30\">#30ffffff</color>\n    <color name=\"transparent50\">#50ffffff</color>\n\n    <color name=\"bg_divider_line\">#363636</color>\n\n    <color name=\"btn_bg_press\">#634D4D4D</color>\n    <color name=\"btn_bg_press_2\">#63686868</color>\n    <color name=\"btn_bg_press_tp\">#63C7C7C7</color>\n\n    <color name=\"darker_gray\">#66666666</color>\n\n    <color name=\"tv_btn_normal_black\">#737373</color>\n    <color name=\"tv_btn_press_black\">#565656</color>\n\n    <color name=\"primaryText\">#ffffffff</color>\n    <color name=\"secondaryText\">#b3ffffff</color>\n    <color name=\"tv_text_summary\">#B3B3B3</color>\n    <color name=\"menu_color_default\">#b7b7b7</color>\n\n\n    <color name=\"tv_text_button_nor\">#303030</color>\n\n    <!--statusBarBackground-->\n    <color name=\"navigation_bar_bag\">#222222</color>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values-night/styles.xml",
    "content": "<resources>\n\n    <style name=\"Spinner\" parent=\"android:Theme.Holo\" />\n\n    <style name=\"AppTheme.PopupOverlay\" parent=\"ThemeOverlay.AppCompat.Dark\">\n        <item name=\"overlapAnchor\">false</item>\n        <item name=\"colorAccent\">@color/md_grey_100</item>\n    </style>\n\n    <style name=\"Style.Shadow.Top\" parent=\"android:Widget\">\n        <item name=\"android:layout_width\">match_parent</item>\n        <item name=\"android:layout_height\">@dimen/shadow_height</item>\n        <item name=\"android:background\">@drawable/bg_shadow_top_night</item>\n    </style>\n\n    <style name=\"Style.Shadow.Bottom\" parent=\"android:Widget\">\n        <item name=\"android:layout_width\">match_parent</item>\n        <item name=\"android:layout_height\">@dimen/shadow_height</item>\n        <item name=\"android:background\">@drawable/bg_shadow_bottom_night</item>\n    </style>\n</resources>"
  },
  {
    "path": "app/src/main/res/values-pt-rBR/arrays.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\t<string-array name=\"book_type\">\n\t\t<item>Texto</item>\n\t\t<item>Áudio</item>\n\t\t<item>Image</item>\n\t\t<item>File</item>\n\t</string-array>\n\n\t<string-array name=\"group_style\">\n\t\t<item>Aba</item>\n\t\t<item>Pasta</item>\n\t</string-array>\n\n\t<string-array name=\"indent\">\n\t\t<item>@string/indent_0</item>\n\t\t<item>@string/indent_1</item>\n\t\t<item>@string/indent_2</item>\n\t\t<item>@string/indent_3</item>\n\t\t<item>@string/indent_4</item>\n\t</string-array>\n\n\t<string-array name=\"text_suffix\">\n\t\t<item>.txt</item>\n\t\t<item>.json</item>\n\t\t<item>.xml</item>\n\t</string-array>\n\n\t<string-array name=\"convert_s\">\n\t\t<item>@string/jf_convert_o</item>\n\t\t<item>@string/jf_convert_j</item>\n\t\t<item>@string/jf_convert_f</item>\n\t</string-array>\n\n\t<string-array name=\"theme_mode\">\n\t\t<item>Adaptar do sistema</item>\n\t\t<item>Tema Claro</item>\n\t\t<item>Tema Escuro</item>\n\t\t<item>Tema E-Ink</item>\n\t</string-array>\n\n\t<string-array name=\"NavBarColors\">\n\t\t<item>Autom.</item>\n\t\t<item>Escuro</item>\n\t\t<item>Claro</item>\n\t\t<item>Adaptado</item>\n\t</string-array>\n\n\t<string-array name=\"screen_time_out\">\n\t\t<item>Padrão</item>\n\t\t<item>1 min</item>\n\t\t<item>5 min</item>\n\t\t<item>10 min</item>\n\t\t<item>Sempre</item>\n\t</string-array>\n\n\t<string-array name=\"screen_direction_title\">\n\t\t<item>@string/screen_unspecified</item>\n\t\t<item>@string/screen_portrait</item>\n\t\t<item>@string/screen_landscape</item>\n\t\t<item>@string/screen_sensor</item>\n\t\t<item>@string/screen_portrait_reversed</item>\n\t</string-array>\n\n\t<string-array name=\"icon_names\">\n\t\t<item>íconePrincipal</item>\n\t\t<item>ícone1</item>\n\t\t<item>ícone2</item>\n\t\t<item>ícone3</item>\n\t\t<item>ícone4</item>\n\t\t<item>ícone5</item>\n\t\t<item>ícone6</item>\n\t</string-array>\n\n\t<string-array name=\"chinese_mode\">\n\t\t<item>Desativado</item>\n\t\t<item>Tradicional a Simplificado</item>\n\t\t<item>Simplificado a Tradicional</item>\n\t</string-array>\n\n\t<string-array name=\"system_typefaces\">\n\t\t<item>Fonte padrão</item>\n\t\t<item>Fonte Serif</item>\n\t\t<item>Fonte Monospaced</item>\n\t</string-array>\n\n\t<string-array name=\"read_tip\">\n\t\t<item>Em branco</item>\n\t\t<item>Nome do livro</item>\n\t\t<item>Título</item>\n\t\t<item>Tempo</item>\n\t\t<item>Bateria</item>\n\t\t<item>Bateria%</item>\n\t\t<item>Páginas</item>\n\t\t<item>Progresso(%)</item>\n\t\t<item>Progresso(xx/yyy)</item>\n\t\t<item>Páginas e progresso</item>\n\t\t<item>Tempo e Bateria</item>\n\t\t<item>Tempo e Bateria%</item>\n\t</string-array>\n\n\t<string-array name=\"text_font_weight\">\n\t\t<item>Normal</item>\n\t\t<item>Negrito</item>\n\t\t<item>Claro</item>\n\t</string-array>\n\n\t<string-array name=\"language\">\n\t\t<item>Autom.</item>\n\t\t<item>Chinês simplificado</item>\n\t\t<item>Chinês tradicional</item>\n\t\t<item>Inglês</item>\n\t</string-array>\n\n\t<string-array name=\"rule_type\">\n\t\t<item>FonteLivro</item>\n\t\t<item>FonteRSS</item>\n\t\t<item>SubstituirRegra</item>\n\t</string-array>\n\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values-pt-rBR/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <!--App-->\n    <string name=\"app_name\">Legado</string>\n    <string name=\"app_name_a\">Legado·A</string>\n    <string name=\"receiving_shared_label\">Legado·pesquisa</string>\n    <string name=\"tip_perm_request_storage\">Legado precisa de acesso ao armazenamento para encontrar e ler livros. Por favor, vá às \"Configurações do App\" para conceder \"Permissão de armazenamento\".</string>\n\n    <!--Other-->\n    <string name=\"menu_backup\">Início</string>\n    <string name=\"menu_restore\">Restaurar</string>\n    <string name=\"menu_import_old\">Importar dados ao Legado</string>\n    <string name=\"webdav_cache_backup\">Backup do cache dos livros para leitura off-line</string>\n    <string name=\"webdav_cache_backup_s\">Exportar localmente e fazer Backup à pasta de exportação</string>\n    <string name=\"backup_path\">Backup para</string>\n    <string name=\"select_backup_path\">Por favor, selecione uma pasta de Backup.</string>\n    <string name=\"menu_import_old_version\">Importar dados de Legado</string>\n    <string name=\"menu_import_github\">Importar dados de Github</string>\n    <string name=\"menu_replace_rule\">Substituição</string>\n    <string name=\"menu_send\">Enviar</string>\n\n    <string name=\"dialog_title\">Aviso</string>\n    <string name=\"dialog_cancel\">Cancelar</string>\n    <string name=\"dialog_confirm\">Confirmar</string>\n    <string name=\"dialog_setting\">Vá até às Configurações</string>\n    <string name=\"tip_cannot_jump_setting_page\">Não foi possível abrir Configurações.</string>\n\n    <string name=\"manual_input\">Entrada manual</string>\n    <string name=\"enter_directory_path\">Digite o caminho do diretório</string>\n    <string name=\"invalid_directory\">Caminho do diretório inválido</string>\n    <string name=\"empty_directory_input\">O caminho do diretório não pode estar vazio</string>\n\n    <string name=\"dynamic_click_retry\">Tentar novamente</string>\n    <string name=\"dynamic_loading\">Carregando</string>\n    <string name=\"draw\">Aviso</string>\n    <string name=\"edit\">Editar</string>\n    <string name=\"delete\">Excluir</string>\n    <string name=\"delete_select_group\">Excluir grupo selecionado</string>\n    <string name=\"delete_all\">Excluir tudo</string>\n    <string name=\"replace\">Substituir</string>\n    <string name=\"replace_purify\">Substituição</string>\n    <string name=\"replace_purify_desc\">Configurar regras de substituição</string>\n    <string name=\"custom_export_section\">Custom export chapter of epub</string>\n    <string name=\"not_available\">Indisponível</string>\n    <string name=\"enable\">Ativar</string>\n    <string name=\"replace_purify_search\">Procurar o substituto</string>\n    <string name=\"bookshelf\">Estante</string>\n    <string name=\"favorites\">Favoritos</string>\n    <string name=\"favorite\">Favorito</string>\n    <string name=\"in_favorites\">em Favoritos</string>\n    <string name=\"out_favorites\">Não está em Favoritos</string>\n    <string name=\"rss\">Assinatura</string>\n    <string name=\"all\">Tudo</string>\n    <string name=\"recent_reading\">Leitura recente</string>\n    <string name=\"last_read\">Última leitura</string>\n    <string name=\"update_log\">Novidades</string>\n    <string name=\"bookshelf_empty\">A estante está vazia. Procure por livros ou adicione-os via descoberta!</string>\n    <string name=\"action_search\">Pesquisar</string>\n    <string name=\"action_download\">Download</string>\n    <string name=\"layout_list\">Lista</string>\n    <string name=\"layout_grid3\">Grade-3</string>\n    <string name=\"layout_grid4\">Grade-4</string>\n    <string name=\"layout_grid5\">Grade-5</string>\n    <string name=\"layout_grid6\">Grade-6</string>\n    <string name=\"bookshelf_layout\">Layout</string>\n    <string name=\"view\">Vista</string>\n    <string name=\"book_library\">Biblioteca</string>\n    <string name=\"book_local\">Importar livros</string>\n    <string name=\"book_source\">Fontes de livros</string>\n    <string name=\"book_source_manage\">Gerenciar fontes</string>\n    <string name=\"book_source_manage_desc\">Criar/Importar/Editar/Gerenciar fontes de livros</string>\n    <string name=\"setting\">Configurações</string>\n    <string name=\"theme_setting\">Configurações de Temas</string>\n    <string name=\"theme_setting_s\">Alguns ajustes relacionados à interface ou cor</string>\n    <string name=\"other_setting\">Outras configurações</string>\n    <string name=\"other_setting_s\">Algumas configurações relacionadas à funcionamento</string>\n    <string name=\"about\">Sobre</string>\n    <string name=\"donate\">Doações</string>\n    <string name=\"exit\">Sair</string>\n    <string name=\"exit_no_save\">Não foi salvo. Você quer continuar editando?</string>\n    <string name=\"read_style\">Tipos de livros</string>\n    <string name=\"version\">Versão</string>\n    <string name=\"local\">Local</string>\n    <string name=\"search\">Procurar</string>\n    <string name=\"origin_format\">Origem: %s</string>\n    <string name=\"read_dur_progress\">Origem: %s</string>\n    <string name=\"book_name\">Título</string>\n    <string name=\"lasted_show\">Último: %s</string>\n    <string name=\"check_add_bookshelf\">Você gostaria de adicionar %s à sua Estante?</string>\n    <string name=\"import_books_count\">%s arquivo(s) de texto em total</string>\n    <string name=\"is_loading\">Carregando…</string>\n    <string name=\"retry\">Tentar novamente</string>\n    <string name=\"web_service\">Serviço Web</string>\n    <string name=\"web_service_desc\">Fonte de edição web e livro lidos</string>\n    <string name=\"web_edit_source\">Editar fontes de livros na web</string>\n    <string name=\"offline_cache\">Cache off-line</string>\n    <string name=\"offline_cache_t\">Cache off-line</string>\n    <string name=\"offline_cache_s\">salvar o(s) capítulo(s) selecionado(s) no Armazenamento em cache</string>\n    <string name=\"change_origin\">Alterar a origem</string>\n    <string name=\"about_description\">\n        \\u3000\\u3000 Este é um App de leitura de software livre, desenvolvido em Kotlin, você é bem-vindo a participar em projeto.\n    </string>\n    <string name=\"app_share_description\">\n        Legado (YueDu 3.0) link de download：\\n https://github.com/gedoor/legado/releases\n    </string>\n    <string name=\"version_name\">Versão %s</string>\n    <string name=\"pt_background_verification\">Verificação em segundo plano</string>\n    <string name=\"ps_background_verification\">Poderá usar a vontade ao verificar a fonte do livro</string>\n    <string name=\"pt_auto_refresh\">Atualização automática</string>\n    <string name=\"ps_auto_refresh\">Atualizar os livros automaticamente ao abrir o App</string>\n    <string name=\"pt_auto_download\">Download automático</string>\n    <string name=\"ps_auto_download\">Baixar automaticamente os últimos capítulos ao atualizar os livros</string>\n    <string name=\"backup_restore\">Backup e restauração</string>\n    <string name=\"web_dav_set\">Configurações de WebDav</string>\n    <string name=\"web_dav_set_import_old\">Configurações/importação WebDav de dados Legado </string>\n    <string name=\"backup\">Fazer Backup</string>\n    <string name=\"restore\">Restaurar</string>\n    <string name=\"backup_permission\">O Backup precisa de permissão de armazenamento</string>\n    <string name=\"restore_permission\">Para restaurar é necessária a permissão de armazenamento</string>\n    <string name=\"ok\">OK</string>\n    <string name=\"cancel\">Cancelar</string>\n    <string name=\"backup_confirmation\">Confirmação do Backup</string>\n    <string name=\"backup_message\">Os novos arquivos de Backup substituirão os originais.\\n Pasta de Backup: YueDu</string>\n    <string name=\"restore_confirmation\">Confirmação da Restauração</string>\n    <string name=\"restore_message\">A restauração dos dados da estante irá substituir a estante atual.</string>\n    <string name=\"backup_success\">Backup bem sucedido</string>\n    <string name=\"backup_fail\">O Backup falhou\\n%s</string>\n    <string name=\"on_restore\">Restaurando</string>\n    <string name=\"restore_success\">Restauração bem sucedida</string>\n    <string name=\"restore_fail\">A restauração falhou</string>\n    <string name=\"screen_direction\">Orientação da tela</string>\n    <string name=\"screen_sensor\">Auto(sensor)</string>\n    <string name=\"screen_landscape\">Paisagem</string>\n    <string name=\"screen_portrait\">Retrato</string>\n    <string name=\"screen_unspecified\">Adaptar do sistema</string>\n    <string name=\"disclaimer\">Isenção de responsabilidade</string>\n    <string name=\"all_chapter_num\">%d capítulos</string>\n    <string name=\"interface_setting\">Interface</string>\n    <string name=\"brightness\">Brilho</string>\n    <string name=\"chapter_list\">Capítulos</string>\n    <string name=\"next_chapter\">Próximo</string>\n    <string name=\"previous_chapter\">Anterior</string>\n    <string name=\"pt_hide_status_bar\">Ocultar barra de status</string>\n    <string name=\"ps_hide_status_bar\">Ocultar a barra de navegação do sistema durante leitura</string>\n    <string name=\"read_aloud\">Voz</string>\n    <string name=\"read_aloud_t\">Falando</string>\n    <string name=\"read_aloud_s\">Clique para abrir a leitura</string>\n    <string name=\"audio_play\">Reproduzir</string>\n    <string name=\"audio_play_t\">Reproduzindo</string>\n    <string name=\"audio_play_s\">Clique para abrir a reprodução</string>\n    <string name=\"audio_pause\">Pausar</string>\n    <string name=\"text_return\">Anterior</string>\n    <string name=\"refresh\">Atualizar</string>\n    <string name=\"start\">Começar</string>\n    <string name=\"stop\">Parar</string>\n    <string name=\"pause\">Pausar</string>\n    <string name=\"resume\">Retomar</string>\n    <string name=\"set_timer\">Timer</string>\n    <string name=\"read_aloud_pause\">Voz pausada</string>\n    <string name=\"read_aloud_timer\">Falando(%d min restantes)</string>\n    <string name=\"playing_timer\">Reproduzindo(%d min restantes)</string>\n    <string name=\"ps_hide_navigation_bar\">Esconder botões virtuais durante a leitura</string>\n    <string name=\"pt_hide_navigation_bar\">Ocultar a barra de navegação</string>\n    <string name=\"re_navigation_bar_color\">Cor da barra de navegação</string>\n    <string name=\"scoring\">Avaliação</string>\n    <string name=\"send_mail\">E-mail</string>\n    <string name=\"can_not_open\">Falha ao abrir</string>\n    <string name=\"can_not_share\">Falha ao compartilhar</string>\n    <string name=\"no_chapter\">Sem capítulos</string>\n    <string name=\"add_url\">Adicionar Url</string>\n    <string name=\"add_book_url\">Adicionar Url de livro</string>\n    <string name=\"background\">Segundo plano</string>\n    <string name=\"author\">Autor</string>\n    <string name=\"author_show\">Autor: %s</string>\n    <string name=\"aloud_stop\">Voz parada</string>\n    <string name=\"clear_cache\">Limpar o cache</string>\n    <string name=\"clear_cache_success\">O cache foi removido</string>\n    <string name=\"action_save\">Salvar</string>\n    <string name=\"edit_source\">Editar fonte</string>\n    <string name=\"edit_book_source\">Editar fonte do livro</string>\n    <string name=\"disable_book_source\">Desativar fonte do livro</string>\n    <string name=\"add_book_source\">Adicionar fonte de livro</string>\n    <string name=\"add_rss_source\">Adicionar fonte de assinatura</string>\n    <string name=\"book_file_selector\">Adicionar livros</string>\n    <string name=\"scan_book_source\">Pesquisar</string>\n    <string name=\"copy_source\">Copiar a fonte</string>\n    <string name=\"paste_source\">Colar a fonte</string>\n    <string name=\"source_rule_s\">A descrição de fonte das regras</string>\n    <string name=\"check_update\">Verificar por atualizações</string>\n    <string name=\"camera_scan\">Digitalizar código QR</string>\n    <string name=\"scan_image\">Digitalizar imagens locais</string>\n    <string name=\"rule_summary\">Descrição das regras</string>\n    <string name=\"share\">Compartilhar</string>\n    <string name=\"share_app\">Compartilhar via</string>\n    <string name=\"flow_sys\">Adaptar do sistema</string>\n    <string name=\"add\">Adicionar</string>\n    <string name=\"import_book_source\">Importar as fontes de livro</string>\n    <string name=\"import_local\">Importar localmente</string>\n    <string name=\"import_on_line\">Importar on-line</string>\n    <string name=\"replace_rule_title\">Substituição</string>\n    <string name=\"replace_rule_edit\">Editar regra de substituição</string>\n    <string name=\"replace_rule\">Modelo</string>\n    <string name=\"replace_to\">Substituição</string>\n    <string name=\"img_cover\">Capa</string>\n    <string name=\"book\">Livro</string>\n    <string name=\"volume_key_page\">Botões de volume para virar páginas</string>\n    <string name=\"mouse_wheel_page\">Rolagem do mouse para virar páginas</string>\n    <string name=\"click_turn_page\">Toque a tela para virar página</string>\n    <string name=\"page_anim\">Virar animação</string>\n    <string name=\"book_page_anim\">Virar animação (livro)</string>\n    <string name=\"keep_light\">Manter a tela ligada</string>\n    <string name=\"back\">Voltar</string>\n    <string name=\"menu\">Menu</string>\n    <string name=\"adjust\">Ajuste</string>\n    <string name=\"scroll_bar\">Barra de deslize</string>\n    <string name=\"clear_all_content\">Ao limpar o cache você excluirá todos os capítulos salvos. Você tem certeza disso?</string>\n    <string name=\"book_source_share_url\">Compartilhamento de fontes de livros</string>\n    <string name=\"replace_rule_summary\">Nome da regra</string>\n    <string name=\"replace_rule_invalid\">A regra padrão está vazia ou não está em conformidade com as especificações de Regex.</string>\n    <string name=\"select_action\">Ação de seleção</string>\n    <string name=\"select_all\">Selecionar tudo</string>\n    <string name=\"select_all_count\">Selecionar tudo(%1$d/%2$d)</string>\n    <string name=\"select_cancel_count\">Cancelar a seleção de todos(%1$d/%2$d)</string>\n    <string name=\"dark_theme\">Modo escuro</string>\n    <string name=\"welcome\">Página de boas-vindas</string>\n    <string name=\"download_start\">Iníciar o download</string>\n    <string name=\"download_cancel\">Cancelar o download</string>\n    <string name=\"no_download\">Nenhum download</string>\n    <string name=\"download_count\">Baixado %1$d/%2$d</string>\n    <string name=\"import_select_book\">Importar livro(s) selecionado(s)</string>\n    <string name=\"threads_num_title\">Número de tarefas simultâneas</string>\n    <string name=\"change_icon\">Alterar o ícone</string>\n    <string name=\"remove_from_bookshelf\">Excluir</string>\n    <string name=\"start_read\">Começar a leitura</string>\n    <string name=\"data_loading\">Carregando…</string>\n    <string name=\"load_error_retry\">Falha ao carregar, toque para tentar novamente</string>\n    <string name=\"book_intro\">Descrição do livro</string>\n    <string name=\"intro_show\">Descrição:%s</string>\n    <string name=\"intro_show_null\">Descrição: sem introdução</string>\n    <string name=\"open_from_other\">Abrir o livro externo</string>\n    <string name=\"origin_show\">Origem: %s</string>\n    <string name=\"import_replace_rule\">Import replace rule</string>\n    <string name=\"import_replace_rule_on_line\">Importar regras on-line</string>\n    <string name=\"check_update_interval\">Intervalo de atualizações</string>\n    <string name=\"bookshelf_px_0\">Por recentes</string>\n    <string name=\"bookshelf_px_1\">Por data de atualização</string>\n    <string name=\"bookshelf_px_2\">Por título do livro</string>\n    <string name=\"bookshelf_px_3\">Ordenar manualmente</string>\n    <string name=\"read_type\">Estratégia de leitura</string>\n    <string name=\"compose_type\">Tipografia</string>\n    <string name=\"del_select\">Excluir selecionados</string>\n    <string name=\"del_msg\">Você quer excluir?</string>\n    <string name=\"clear_font\">Fonte padrão</string>\n    <string name=\"find_on_www\">Descoberta</string>\n    <string name=\"find_source_manage\">Descoberta</string>\n    <string name=\"find_empty\">Sem conteúdo. Vá ao Gerenciador de fontes para adicioná-lo!</string>\n    <string name=\"del_all\">Excluir tudo</string>\n    <string name=\"searchHistory\">Histórico de busca</string>\n    <string name=\"clear\">Limpar</string>\n    <string name=\"showTitle\">Mostrar título do livro no texto</string>\n    <string name=\"refresh_default\">Sincronização de fontes dos livros</string>\n    <string name=\"no_last_chapter\">Não há último capítulo.</string>\n    <string name=\"showTimeBattery\">Mostrar o tempo e a bateria</string>\n    <string name=\"showLine\">Divisor da tela</string>\n    <string name=\"dark_status_icon\">Escurecer a cor do ícone na barra de status</string>\n    <string name=\"content\">Conteúdo</string>\n    <string name=\"copy_text\">Copiar</string>\n    <string name=\"download_all\">Baixar tudo</string>\n    <string name=\"content_sl\">Este é um texto de teste, \\n\\u3000\\u3000 apenas para mostrar o resultado</string>\n    <string name=\"text_bg_style\">Cor e fundo (toque longo para personalizar)</string>\n    <string name=\"immersion_status_bar\">Barra de status imersiva</string>\n    <string name=\"un_download\">%d capítulo(s) restante(s)</string>\n    <string name=\"long_click_input_color\">Clique longo, para introduzir o valor da cor</string>\n    <string name=\"loading\">Carregando…</string>\n    <string name=\"group_zg\">Aguardando</string>\n    <string name=\"group_yf\">Quase lá</string>\n    <string name=\"bookmark\">Favoritos</string>\n    <string name=\"bookmark_add\">Adicionar aos Favoritos</string>\n    <string name=\"action_del\">Excluir</string>\n    <string name=\"load_over_time\">Tempo limite de carregamento</string>\n    <string name=\"join_group\">Siga:%s</string>\n    <string name=\"copy_complete\">Copiado com sucesso</string>\n    <string name=\"bookshelf_management\">A organização da estante</string>\n    <string name=\"clear_bookshelf_s\">Isto excluirá todos os livros. Por favor, tenha cuidado.</string>\n    <string name=\"search_book_source\">Pesquisar fontes de livros</string>\n    <string name=\"search_rss_source\">Buscar fontes de assinatura</string>\n    <string name=\"search_book_source_num\">Pesquisa( %d fontes no total)</string>\n    <string name=\"chapter_list_size\">Capítulos(%d)</string>\n    <string name=\"text_bold\">Negrito</string>\n    <string name=\"text_font\">Fonte</string>\n    <string name=\"text\">Texto</string>\n    <string name=\"home_page\">Página inicial</string>\n    <string name=\"right\">Direita</string>\n    <string name=\"left\">Esquerda</string>\n    <string name=\"bottom\">Parte inferior</string>\n    <string name=\"top\">Parte superior</string>\n    <string name=\"padding\">Com espaço</string>\n    <string name=\"padding_top\">Espaço superior</string>\n    <string name=\"padding_bottom\">Espaço inferior</string>\n    <string name=\"padding_left\">Espaço da esquerda</string>\n    <string name=\"padding_right\">Espaço da direita</string>\n    <string name=\"check_book_source\">Verificar fontes do livro</string>\n    <string name=\"check_select_source\">Verificar a fonte selecionada</string>\n    <string name=\"progress_show\">%1$s      Progresso %2$d/%3$d</string>\n    <string name=\"tts_fix\">Favor instalar e selecionar o TTS chinês!</string>\n    <string name=\"tts_init_failed\">A inicialização do TTS falhou!</string>\n    <string name=\"jf_convert\">Conversão simplificada</string>\n    <string name=\"jf_convert_o\">Desligado</string>\n    <string name=\"jf_convert_f\">Simplificado ao tradicional</string>\n    <string name=\"jf_convert_j\">Tradicional ao simplificado</string>\n    <string name=\"page_mode\">Modo de virar</string>\n    <string name=\"nb_file_sub_count\">%1$d itens</string>\n    <string name=\"nb_file_path\">Armazenamento:</string>\n    <string name=\"nb_file_add_shelf\">Adicionar ao Estante</string>\n    <string name=\"nb_file_add_shelves\">Adicionar ao Estante(%1$d)</string>\n    <string name=\"nb_file_add_succeed\">%1$d livros adicionados com sucesso</string>\n    <string name=\"fonts_folder\">Favor colocar os arquivos de fontes na pasta Root de Fontes no armazenamento e selecionar novamente</string>\n    <string name=\"default_font\">Fonte padrão</string>\n    <string name=\"select_font\">Selecionar fontes</string>\n    <string name=\"text_size\">Tamanho do texto</string>\n    <string name=\"line_size\">Espaço entre linhas</string>\n    <string name=\"paragraph_size\">Espaço entre parágrafos</string>\n    <string name=\"to_top\">Na parte superior</string>\n    <string name=\"selection_to_top\">Seleção até o topo</string>\n    <string name=\"to_bottom\">Na parte inferior</string>\n    <string name=\"selection_to_bottom\">Seleção até o final</string>\n    <string name=\"auto_expand_find\">Expansão automática de Descoberta</string>\n    <string name=\"default_expand_first\">A expansão padrão da primeira Descoberta.</string>\n    <string name=\"threads_num\">Linhas atuais %s</string>\n    <string name=\"read_aloud_speed\">Velocidade da voz</string>\n    <string name=\"auto_next_page\">Deslize automático</string>\n    <string name=\"auto_next_page_stop\">Impedir deslize automático</string>\n    <string name=\"auto_next_page_speed\">Velocidade automática ao deslizar</string>\n    <string name=\"book_info\">Informações sobre livro</string>\n    <string name=\"book_info_edit\">Editar informações do livro</string>\n    <string name=\"ps_default_read\">Definir a Estante como página inicial</string>\n    <string name=\"pt_default_read\">Vá automaticamente à lista de Recentes</string>\n    <string name=\"replace_scope\">Objeto substituto. Nome do livro ou Url de origem estão disponíveis</string>\n    <string name=\"menu_action_group\">Grupos</string>\n    <string name=\"download_path\">Pasta de Cache</string>\n    <string name=\"sys_file_picker\">Seletor de arquivos do sistema</string>\n    <string name=\"new_version\">Nova versão</string>\n    <string name=\"download_update\">Baixar atualizações</string>\n    <string name=\"volume_key_page_on_play\">Botões de volume para virar páginas durante leitura</string>\n    <string name=\"tip_margin_change\">Ajuste de margem</string>\n    <string name=\"allow_update\">Ativar atualizações</string>\n    <string name=\"disable_update\">Desativar atualizações</string>\n    <string name=\"revert_selection\">Inverso</string>\n    <string name=\"search_book_key\">Pesquisar livro por nome/autor</string>\n    <string name=\"debug_hint\">Nome do livro,Autor,URL</string>\n    <string name=\"faq\">FAQ</string>\n    <string name=\"pt_show_all_find\">Mostrar todas as Descobertas</string>\n    <string name=\"ps_show_all_find\">Mostrar a fonte da Descoberta selecionada se encerrado</string>\n    <string name=\"update_toc\">Atualizar os capítulos</string>\n    <string name=\"txt_toc_rule\">Capítulos de Txt Rule</string>\n    <string name=\"set_charset\">Codificação de texto</string>\n    <string name=\"swap_sort\">Ordem ascendente/descendente</string>\n    <string name=\"sort\">Ordenar</string>\n    <string name=\"sort_auto\">Ordenar automaticamente</string>\n    <string name=\"sort_default\">Ordenar por defecto</string>\n    <string name=\"sort_manual\">Ordenar manualmente</string>\n    <string name=\"sort_by_name\">Ordenar por nome</string>\n    <string name=\"go_to_top\">Vá ao topo</string>\n    <string name=\"go_to_bottom\">Vá ao final</string>\n    <string name=\"read_y\">Ler: %s</string>\n    <string name=\"pursue_more\">Aguardando atualização</string>\n    <string name=\"fattening\">Quase lá</string>\n    <string name=\"finish\">Concluído</string>\n    <string name=\"all_book\">Tudo</string>\n    <string name=\"pursue_more_book\">Aguardando atualização dos livros</string>\n    <string name=\"fattening_book\">Aguardando mais capítulos de livros</string>\n    <string name=\"finish_book\">Livros concluídos</string>\n    <string name=\"local_book\">Livros locais</string>\n    <string name=\"status_bar_immersion\">A cor da barra de status fica transparente</string>\n    <string name=\"imm_navigation_bar\">barra de navegação de imersão</string>\n    <string name=\"imm_navigation_bar_s\">A barra de navegação fica transparente</string>\n    <string name=\"add_to_bookshelf\">Adicionar à Estante</string>\n    <string name=\"continue_read\">Continuar lendo</string>\n    <string name=\"cover_path\">Pasta das capas</string>\n    <string name=\"page_anim_cover\">Capa</string>\n    <string name=\"page_anim_slide\">Deslizar</string>\n    <string name=\"page_anim_simulation\">Simulação</string>\n    <string name=\"page_anim_scroll\">Deslizar</string>\n    <string name=\"page_anim_none\">Nenhum</string>\n    <string name=\"disable_manga_page_anim\">Desativar animação de virada</string>\n    <string name=\"up_change_source_last_chapter_t\">Atualizar o último capítulo após a alteração de origem em segundo plano</string>\n    <string name=\"up_change_source_last_chapter_s\">se ativado, a atualização será iniciada 1 minuto depois o App for aberto</string>\n    <string name=\"behavior_main_t\">Auto esconder a barra de ferramentas</string>\n    <string name=\"behavior_main_s\">A barra de ferramentas será escondida automaticamente ao deslizar a Estante</string>\n    <string name=\"login\">Login</string>\n    <string name=\"login_source\">Login%s</string>\n    <string name=\"success\">Sucesso</string>\n    <string name=\"source_no_login\">A fonte atual não foi configurada com um endereço de login</string>\n    <string name=\"no_prev_page\">Nenhuma página anterior</string>\n    <string name=\"no_next_page\">Nenhuma página seguinte</string>\n\n    <!-- source start-->\n    <string name=\"source_name\">源名称(Nome da fonte)</string>\n    <string name=\"source_url\">源URL(fonteUrl)</string>\n    <string name=\"source_group\">源分组(fonteGrupo)</string>\n    <string name=\"diy_source_group\">自定义源分组</string>\n    <string name=\"diy_edit_source_group\">输入自定义源分组名称</string>\n    <string name=\"concurrent_rate\">并发率(taxaSimultânea)</string>\n    <string name=\"sort_url\">分类Url(ordenarUrl)</string>\n    <string name=\"login_url\">登录URL(loginUrl)</string>\n    <string name=\"login_ui\">登UI(loginIU)</string>\n    <string name=\"login_check_js\">登录检查JS(loginVerifJs)</string>\n    <string name=\"comment\">源注释(fonteComentário)</string>\n    <string name=\"r_search_url\">搜索地址(url)</string>\n    <string name=\"r_find_url\">发现地址规则(url)</string>\n    <string name=\"r_book_list\">书籍列表规则(ListaLivros)</string>\n    <string name=\"r_book_name\">书名规则(nome)</string>\n    <string name=\"r_book_url\">详情页url规则(livroUrl)</string>\n    <string name=\"r_author\">作者规则(autor)</string>\n    <string name=\"rule_book_kind\">分类规则(tipo)</string>\n    <string name=\"rule_book_intro\">简介规则(intro)</string>\n    <string name=\"rule_cover_url\">封面规则(capaUrl)</string>\n    <string name=\"rule_last_chapter\">最新章节规则(últimoCapítulo)</string>\n    <string name=\"rule_word_count\">字数规则(contagemPalavras)</string>\n    <string name=\"book_url_pattern\">书籍URL正则(LivroModeloUrl)</string>\n    <string name=\"rule_book_info_init\">预处理规则(livroInfoInit)</string>\n    <string name=\"rule_toc_url\">目录URL规则(tocUrl)</string>\n    <string name=\"rule_can_re_name\">允许修改书名作者(podeRenomear)</string>\n    <string name=\"rule_next_toc_url\">目录下一页规则(proxTocUrl)</string>\n    <string name=\"rule_chapter_list\">目录列表规则(capítuloLista)</string>\n    <string name=\"rule_chapter_name\">章节名称规则(capítuloNome)</string>\n    <string name=\"rule_chapter_url\">章节URL规则(capítuloUrl)</string>\n    <string name=\"rule_is_volume\">卷标识(isVolume)</string>\n    <string name=\"rule_is_vip\">VIP标识(éVip)</string>\n    <string name=\"rule_update_time\">更新时间(capítuloInfo)</string>\n    <string name=\"pre_update_js\">更新之前Js(preUpdateJs)</string>\n    <string name=\"rule_book_content\">正文规则(conteúdo)</string>\n    <string name=\"rule_next_content\">正文下一页URL规则(proxConteúdoUrl)</string>\n    <string name=\"rule_web_js\">WebViewJs(webJs)</string>\n    <string name=\"rule_source_regex\">资源正则(fonteRegex)</string>\n    <string name=\"rule_replace_regex\">替换规则(substRegex)</string>\n    <string name=\"rule_image_style\">图片样式(imagemFormato)</string>\n    <string name=\"rule_pay_action\">购买操作(payAction)</string>\n\n    <string name=\"source_icon\">图标(fonteÍcone)</string>\n    <string name=\"r_articles\">列表规则(regrasArtigos)</string>\n    <string name=\"r_next\">列表下一页规则(regrasNextArtigos)</string>\n    <string name=\"r_title\">标题规则(regrasTítulo)</string>\n    <string name=\"r_guid\">guid规则(regraGuid)</string>\n    <string name=\"r_date\">时间规则(regraPubData)</string>\n    <string name=\"r_categories\">类别规则(regraCategorias)</string>\n    <string name=\"r_description\">描述规则(regraDescrição)</string>\n    <string name=\"r_image\">图片url规则(regraImagem)</string>\n    <string name=\"r_content\">内容规则(regraConteúdo)</string>\n    <string name=\"r_style\">样式(formato)</string>\n    <string name=\"r_inject_js\">注入Js(injectJs)</string>\n    <string name=\"r_link\">链接规则(regraLink)</string>\n    <string name=\"check_key_word\">校验关键字(checkKeyWord)</string>\n    <string name=\"rule_actions\">操作(actions)</string>\n    <string name=\"rule_is_pay\">购买标识(isPay)</string>\n    <!-- source end-->\n\n    <!--error string start-->\n    <string name=\"error_no_source\">Nenhum fonte</string>\n    <string name=\"error_get_book_info\">Falha ao obter informações sobre os livros</string>\n    <string name=\"error_get_content\">Falha ao obter o conteúdo</string>\n    <string name=\"error_get_chapter_list\">Falha ao obter a lista de capítulos</string>\n    <string name=\"error_get_web_content\">Falha ao acessar o site:%s</string>\n    <string name=\"error_read_file\">Falha na leitura do arquivo</string>\n    <string name=\"error_load_toc\">Falha ao carregar a lista de capítulos</string>\n    <string name=\"error_get_data\">Falha ao obter dados</string>\n    <string name=\"error_load_msg\">Falha ao carregar\\n%s</string>\n    <string name=\"net_error_10001\">Sem internet</string>\n    <string name=\"net_error_10002\">Tempo limite da conexão à rede</string>\n    <string name=\"net_error_10003\">O processamento de dados falhou</string>\n    <!--error string end-->\n\n    <string name=\"source_http_header\">Cabeçalho HTTP</string>\n    <string name=\"debug_source\">Fonte de depuração</string>\n    <string name=\"import_by_qr_code\">Importar de código QR</string>\n    <string name=\"share_selected_source\">Compartilhar fontes selecionadas</string>\n    <string name=\"scan_qr_code\">Digitalizar código QR</string>\n    <string name=\"click_on_selected_show_menu\">Toque para exibir o menu quando for selecionado</string>\n    <string name=\"theme\">Tema</string>\n    <string name=\"theme_mode\">Tipo de tema</string>\n    <string name=\"theme_mode_desc\">Selecionar tema que você preferir</string>\n    <string name=\"join_qq_group\">Junte-se ao grupo QQ</string>\n    <string name=\"bg_image_per\">Para definir a imagem de fundo é necessária permissão de armazenamento</string>\n    <string name=\"input_book_source_url\">Digite o endereço da fonte de livro</string>\n    <string name=\"del_file\">Excluir arquivo</string>\n    <string name=\"del_file_success\">Arquivo excluído</string>\n    <string name=\"sure_del_file\">Tem certeza que quer excluir este arquivo?</string>\n    <string name=\"files_tree\">Pasta</string>\n    <string name=\"intelligent_import\">Importação inteligente</string>\n    <string name=\"discovery\">Descoberta</string>\n    <string name=\"switch_display_style\">Alterar o modo de exibição</string>\n    <string name=\"import_per\">Importação de livros locais requer permissão de armazenamento</string>\n    <string name=\"night_theme\">Tema Noturno</string>\n    <string name=\"eink_theme\">E-Ink</string>\n    <string name=\"eink_theme_desc\">Otimização para dispositivos E-ink</string>\n    <string name=\"get_storage_per\">requer permissão</string>\n    <string name=\"double_click_exit\">Toque novamente para sair do App</string>\n    <string name=\"import_book_per\">Importação de livros locais requer permissão de armazenamento</string>\n    <string name=\"network_connection_unavailable\">A conexão de internet não está disponível</string>\n    <string name=\"yes\">Sim</string>\n    <string name=\"no\">Não</string>\n    <string name=\"sure\">OK</string>\n    <string name=\"sure_del\">Realmente quer excluir?</string>\n    <string name=\"sure_del_any\">Tem certeza que quer excluir %s?</string>\n    <string name=\"sure_del_all_book\">Você realmente quer excluir todos os livros？</string>\n    <string name=\"sure_del_download_book\">Você também quer excluir os capítulos de livros baixados?</string>\n    <string name=\"qr_per\">Para digitalizar o código QR é necessário ter permissão da câmera</string>\n    <string name=\"aloud_can_not_auto_page\">A voz está em execução, não pode virar páginas automaticamente</string>\n    <string name=\"input_charset\">Codificação de entrada</string>\n    <string name=\"text_chapter_list_rule\">Capítulos de Txt Regex</string>\n    <string name=\"open_local_book_per\">Para abrir livros locais é necessária permissão de armazenamento</string>\n    <string name=\"no_book_name\">Livro sem nome</string>\n    <string name=\"input_replace_url\">URL da regra de substituição de entrada</string>\n    <string name=\"get_book_list_success\">A lista procurada obtida com sucesso%d</string>\n    <string name=\"non_null_name_url\">nome e URL não podem estar vazios</string>\n    <string name=\"gallery\">Galeria</string>\n    <string name=\"get_ali_pay_hb\">obter envelopes vermelhos de AliPay</string>\n    <string name=\"non_update_url\">Nenhuma atualização no endereço</string>\n    <string name=\"check_host_cookie\">Abrindo a página inicial, ela voltará automaticamente após o sucesso</string>\n    <string name=\"click_check_after_success\">Após o login bem sucedido, por favor toque no ícone no canto superior direito para testar o acesso à página inicial</string>\n    <string name=\"chapter\">Capítulo</string>\n    <string name=\"to\">Para</string>\n    <string name=\"use_regex\">Usando Regex</string>\n    <string name=\"text_indent\">Indentação</string>\n    <string name=\"indent_0\">Nenhum</string>\n    <string name=\"indent_1\">Indentação com 1 caract.</string>\n    <string name=\"indent_2\">Indentação com 2 caract.</string>\n    <string name=\"indent_3\">Indentação com 3 caract.</string>\n    <string name=\"indent_4\">Indentação com 4 caract.</string>\n    <string name=\"select_folder\">Selecionar uma pasta</string>\n    <string name=\"select_file\">Selecionar um arquivo</string>\n    <string name=\"no_find\">Nenhuma Descoberta, você pode adicioná-la em Fontes de Livros</string>\n    <string name=\"restore_default\">Restaurar padrão</string>\n    <string name=\"set_download_per\">A pasta personalizada de cache requer permissão de armazenamento</string>\n    <string name=\"black\">Preto</string>\n    <string name=\"content_empty\">Sem conteúdo</string>\n    <string name=\"on_change_source\">Alteração de fonte, aguarde por favor</string>\n    <string name=\"chapter_list_empty\">Os capítulos sem conteúdo</string>\n    <string name=\"text_letter_spacing\">Espaço entre palavras</string>\n\n    <string name=\"source_tab_base\">Básico</string>\n    <string name=\"source_tab_search\">Pesquisar</string>\n    <string name=\"source_tab_find\">Descoberta</string>\n    <string name=\"source_tab_info\">Informação</string>\n    <string name=\"source_tab_toc\">Capítulos</string>\n    <string name=\"source_tab_content\">Conteúdo</string>\n\n    <string name=\"e_ink_mode\">Modo E-Ink</string>\n    <string name=\"e_ink_mode_detail\">Remover animações e otimizar a experiência com livros E-papel</string>\n    <string name=\"web_menu\">Serviço Web</string>\n    <string name=\"web_port_title\">Porta Web</string>\n    <string name=\"web_port_summary\">Porta atual %s</string>\n    <string name=\"qr_share\">Compartilhar o código QR</string>\n    <string name=\"str_share\">Compartilhar sequências</string>\n    <string name=\"wifi_share\">Compartilhar Wifi</string>\n    <string name=\"please_grant_storage_permission\">Por favor, conceda permissão de armazenamento</string>\n    <string name=\"fast_rewind\">Retroceder rápido</string>\n    <string name=\"fast_forward\">Avançar rápido</string>\n    <string name=\"skip_previous\">Anterior</string>\n    <string name=\"skip_next\">Próximo</string>\n    <string name=\"music\">Música</string>\n    <string name=\"audio\">Áudio</string>\n    <string name=\"is_enable\">Ativar</string>\n    <string name=\"enable_js\">Ativar js</string>\n    <string name=\"load_with_base_url\">Carregar a Url-base</string>\n    <string name=\"all_source\">Todas as fontes</string>\n    <string name=\"cannot_empty\">O conteúdo de entrada não pode estar vazio</string>\n    <string name=\"clear_find_cache\">Limpar o cache da Descoberta</string>\n    <string name=\"edit_find\">Editar Descoberta</string>\n    <string name=\"change_icon_summary\">Alternar o ícone do software exibido na área de trabalho</string>\n    <string name=\"help\">Ajuda</string>\n    <string name=\"my\">Minhas</string>\n    <string name=\"reading\">Leituras</string>\n    <string name=\"battery_show\">%d%%</string>\n    <string name=\"timer_m\">%d min</string>\n    <string name=\"brightness_auto\">Brilho autom. %s</string>\n    <string name=\"read_aloud_by_page\">Ler páginas em voz alta</string>\n    <string name=\"speak_engine\">Mecanismo de voz</string>\n    <string name=\"bg_image\">Imagens de fundo</string>\n    <string name=\"bg_color\">Cor de fundo</string>\n    <string name=\"text_color\">Cor de texto</string>\n    <string name=\"select_image\">Selecione uma foto</string>\n    <string name=\"group_manage\">Administração de grupo</string>\n    <string name=\"group_select\">Seleção de grupo</string>\n    <string name=\"group_edit\">Edição de grupo</string>\n    <string name=\"move_to_group\">Mover ao grupo</string>\n    <string name=\"add_group\">Adicionar aos grupos</string>\n    <string name=\"remove_group\">Remover dos grupos</string>\n    <string name=\"add_replace_rule\">Novo substituto</string>\n    <string name=\"group\">Grupo</string>\n    <string name=\"group_s\">Grupo: %s</string>\n    <string name=\"toc_s\">Capítulos: %s</string>\n    <string name=\"enable_explore\">Ativar a Descoberta</string>\n    <string name=\"disable_explore\">Desativar a Descoberta</string>\n    <string name=\"enable_selection\">Ativar selecionados</string>\n    <string name=\"disable_selection\">Desativar selecionados</string>\n    <string name=\"export_selection\">Exportar selecionados</string>\n    <string name=\"export\">Exportar</string>\n    <string name=\"load_toc\">Carregar os capítulos</string>\n    <string name=\"load_info\">Carregar os detalhes do livro</string>\n    <string name=\"tts\">TTS</string>\n    <string name=\"web_dav_pw\">Senha WebDav</string>\n    <string name=\"web_dav_pw_s\">Digite sua senha de autorização para WebDav</string>\n    <string name=\"web_dav_url_s\">Digite o endereço do seu servidor</string>\n    <string name=\"web_dav_url\">Endereço do servidor WebDav</string>\n    <string name=\"web_dav_account\">Conta WebDav</string>\n    <string name=\"web_dav_account_s\">Digite sua conta WebDav</string>\n    <string name=\"rss_source\">Fonte de assinatura</string>\n    <string name=\"rss_source_edit\">Editar a fonte de assinatura</string>\n    <string name=\"screen\">Filtrar</string>\n    <string name=\"screen_find\">Search Discovery sources</string>\n    <string name=\"dur_pos\">Localização atual：</string>\n    <string name=\"precision_search\">Busca precisa</string>\n    <string name=\"service_starting\">Iniciação do serviço</string>\n    <string name=\"empty\">Vazio</string>\n    <string name=\"file_chooser\">Seletor de arquivos</string>\n    <string name=\"folder_chooser\">Seletor de pastas</string>\n    <string name=\"bottom_line\">Concluído!</string>\n    <string name=\"uri_to_path_fail\">Endereço Uri falhou</string>\n    <string name=\"refresh_cover\">Atualizar capa</string>\n    <string name=\"change_cover_source\">Alterar a fonte</string>\n    <string name=\"select_local_image\">Imagem local</string>\n    <string name=\"book_type\">Tipo:</string>\n    <string name=\"to_backstage\">Segundo plano</string>\n    <string name=\"importing\">Importando</string>\n    <string name=\"exporting\">Exportando</string>\n    <string name=\"custom_page_key\">Definir que botões viram as páginas</string>\n    <string name=\"prev_page_key\">Botão de página anterior</string>\n    <string name=\"next_page_key\">Botão de página seguinte</string>\n    <string name=\"no_group\">Sem grupo</string>\n    <string name=\"prev_sentence\">Frase anterior</string>\n    <string name=\"next_sentence\">Frase seguinte</string>\n    <string name=\"other_folder\">Outra pasta</string>\n    <string name=\"text_too_long_qr_error\">Demasiadas palavras para criar um código QR</string>\n    <string name=\"share_rss_source\">Compartilhar fontes de assinatura</string>\n    <string name=\"share_book_source\">Compartilhar fontes de livros</string>\n    <string name=\"auto_dark_mode\">Alteração automática ao modo escuro</string>\n    <string name=\"auto_dark_mode_s\">Seguir o modo escuro do sistema</string>\n    <string name=\"go_back\">Voltar</string>\n    <string name=\"tone_colour\">Tom de voz online</string>\n    <string name=\"select_count\">(%1$d/%2$d)</string>\n    <string name=\"show_rss\">Exibir assinatura</string>\n    <string name=\"service_stop\">Serviço parou</string>\n    <string name=\"service_start\">Iniciando o serviço\\nVerificando a barra de notificações para detalhes</string>\n    <string name=\"default_path\">Pasta padrão</string>\n    <string name=\"sys_folder_picker\">Seletor de pastas do sistema</string>\n    <string name=\"app_folder_picker\">Seletor de pastas do App</string>\n    <string name=\"app_file_picker\">Seletor de arquivos do App</string>\n    <string name=\"a10_permission_toast\">Android 10+ não é capaz de ler e salvar arquivos devido a restrições de permissão</string>\n    <string name=\"add_to_text_context_menu_s\">Clique longo para exibir o Legado·Pesquisa no menu de operação</string>\n    <string name=\"add_to_text_context_menu_t\">Visualização da operação de texto na Pesquisa</string>\n    <string name=\"record_log\">Record log</string>\n    <string name=\"log\">Log</string>\n    <string name=\"chinese_converter\">Conversão simplificada</string>\n    <string name=\"change_icon_error\">O ícone é um ícone vetorial, que não era suportado antes de Android 8.0</string>\n    <string name=\"aloud_config\">Configurações da voz</string>\n    <string name=\"main_activity\">Página inicial</string>\n    <string name=\"selectText\">Clique longo para selecionar o texto</string>\n    <string name=\"header\">Cabeçalho</string>\n    <string name=\"main_body\">Conteúdo</string>\n    <string name=\"footer\">Rodapé</string>\n    <string name=\"select_end\">Selecionar o fim</string>\n    <string name=\"select_start\">Selecionar o início</string>\n    <string name=\"share_layout\">Layout compartilhado</string>\n    <string name=\"browser\">Navegador</string>\n    <string name=\"import_default_rule\">Importar regras padrão</string>\n    <string name=\"name\">Nome</string>\n    <string name=\"regex\">Regex</string>\n    <string name=\"more_menu\">Mais menu</string>\n    <string name=\"reduce\">Menos</string>\n    <string name=\"plus\">Mais</string>\n    <string name=\"system_typeface\">Tamanho e tipo da fonte</string>\n    <string name=\"delete_book_file\">Excluir arquivo fonte</string>\n    <string name=\"text_default\">Padrão</string>\n    <string name=\"default1\">Padrão-1</string>\n    <string name=\"default2\">Padrão-2</string>\n    <string name=\"default3\">Padrão-3</string>\n    <string name=\"title\">Título</string>\n    <string name=\"title_left\">Esquerda</string>\n    <string name=\"title_center\">Centro</string>\n    <string name=\"title_hide\">Esconder</string>\n    <string name=\"add_to_group\">Adicionar ao grupo</string>\n    <string name=\"save_image\">Salvar imagem</string>\n    <string name=\"no_default_path\">Nenhum pasta padrão</string>\n    <string name=\"change_group\">Configurações de grupo</string>\n    <string name=\"view_toc\">Ver os capítulos</string>\n    <string name=\"bar_elevation\">Sombra na barra de navegação</string>\n    <string name=\"bar_elevation_s\">Tamanho da sombra atual(elevação): %s</string>\n    <string name=\"btn_default_s\">Padrão</string>\n    <string name=\"main_menu\">Menu principal</string>\n    <string name=\"request_permission\">Toque para conceder permissão</string>\n    <string name=\"tip_local_perm_request_storage\">Legado precisa de permissão de armazenamento, por favor toque abaixo no botão \"Conceder Permissão\", ou vá às \"Configurações\"-\"Permissões do Apps\"-para abrir a permissão necessária. Se a permissão não funcionar, por favor, toque no \"Selecionar Pasta\" no canto superior direito para usar o seletor de pastas do sistema.</string>\n    <string name=\"alouding_disable\">O texto selecionado não pode ser falado como texto completo</string>\n    <string name=\"read_body_to_lh\">Ampliar para recortar</string>\n    <string name=\"toc_updateing\">Atualização de capítulos</string>\n    <string name=\"media_button_on_exit_title\">Os botões de fones de ouvido estão sempre disponíveis</string>\n    <string name=\"media_button_on_exit_summary\">Os botões de fones de ouvido estão disponíveis mesmo ao sair do App.</string>\n    <string name=\"contributors\">Colaboradores</string>\n    <string name=\"contact\">Contato</string>\n    <string name=\"license\">Licença</string>\n    <string name=\"other\">Outros</string>\n    <string name=\"official_account\">开源阅读</string>\n    <string name=\"follow_official_account\">Siga a conta oficial de WeChat</string>\n    <string name=\"wechat\">WeChat</string>\n    <string name=\"thanks\">Apoio ao meu trabalho é bem-vindo</string>\n    <string name=\"about_official_account\">Conta oficial[开源阅读]</string>\n    <string name=\"source_auto_changing\">Alteração de fonte</string>\n    <string name=\"click_to_apply\">Toque para participar</string>\n    <string name=\"middle\">Médio</string>\n    <string name=\"information\">Informação</string>\n    <string name=\"switchLayout\">Alterar o layout</string>\n    <string name=\"text_font_weight_converter\">Alteração de tamanho da fonte de texto</string>\n    <string name=\"full_screen_gestures_support\">Suporte aos gestos em tela completa</string>\n    <string name=\"disable_return_key\">禁用返回键</string>\n\n    <!--color-->\n    <string name=\"primary\">Primário</string>\n    <string name=\"accent\">Destaque</string>\n    <string name=\"background_color\">Cor de fundo</string>\n    <string name=\"navbar_color\">Cor de NavBar</string>\n    <string name=\"day\">Dia</string>\n    <string name=\"day_color_primary\">Dia,Principal</string>\n    <string name=\"day_color_accent\">Dia,Destaque</string>\n    <string name=\"day_background_color\">Dia,Cor de fundo</string>\n    <string name=\"day_navbar_color\">Dia,Cor de NavBar</string>\n    <string name=\"night\">Noite</string>\n    <string name=\"night_primary\">Noite,Principal</string>\n    <string name=\"night_accent\">Noite,Destaque</string>\n    <string name=\"night_background_color\">Noite,Cor de fundo</string>\n    <string name=\"night_navbar_color\">Noite,Cor de NavBar</string>\n    <string name=\"auto_change_source\">Alterar a fonte automaticamente</string>\n    <string name=\"text_full_justify\">Texto justificado</string>\n    <string name=\"text_bottom_justify\">Texto alinhado com parte inferior</string>\n    <string name=\"auto_page_speed\">Velocidade de rolagem automática</string>\n    <string name=\"sort_by_url\">Ordenar por URL</string>\n    <string name=\"backup_summary\">Faça backup local e de WebDav simultaneamente</string>\n    <string name=\"restore_summary\">Primeiro restaurar de WebDAV, ao clicar longo restaurar do local backup</string>\n    <string name=\"import_old_summary\">Selecione uma pasta Legado de Backup</string>\n    <string name=\"enabled\">Ativado</string>\n    <string name=\"disabled\">Desativado</string>\n    <string name=\"enabled_explore\">Discovery Enabled</string>\n    <string name=\"disabled_explore\">Discovery Disabled</string>\n    <string name=\"starting_download\">Iniciando o download</string>\n    <string name=\"already_in_download\">Este livro já está na lista de download</string>\n    <string name=\"click_to_open\">Clique para abrir</string>\n    <string name=\"follow_public_account_summary\">Siga [开源阅读] para ajudar, clicando nos anúncios</string>\n    <string name=\"weChat_appreciation_code\">Código de gorjeta no WeChat</string>\n    <string name=\"alipay\">AliPay</string>\n    <string name=\"alipay_red_envelope_search_code\">Envelope vermelho de AliPay, código de busca</string>\n    <string name=\"alipay_red_envelope_copy\">537954522 Clique para copiar</string>\n    <string name=\"alipay_red_envelope_qr_code\">Envelope vermelho de AliPay código QR</string>\n    <string name=\"alipay_payment_qr_code\">AliPay código QR</string>\n    <string name=\"qq_collection_qr_code\">Coleção QQ Código QR</string>\n    <string name=\"contributors_summary\">gedoor,Invinciblelee,Xwite etc. Para mais detalhes vá ao Github</string>\n    <string name=\"clear_cache_summary\">Limpar o cache dos livros e fontes baixados</string>\n    <string name=\"default_cover\">Capa padrão</string>\n    <string name=\"restore_ignore\">Lista de ignorados</string>\n    <string name=\"restore_ignore_summary\">Ignore alguns conteúdos durante restauração</string>\n    <string name=\"read_config\">Configuração da interface de leitura</string>\n    <string name=\"group_name\">Nome de grupo</string>\n    <string name=\"note_content\">Seção de observações</string>\n    <string name=\"replace_enable_default_t\">Ativar regra de substituição por padrão</string>\n    <string name=\"replace_enable_default_s\">Para livros recém adicionados</string>\n    <string name=\"select_restore_file\">Selecione o arquivo de restauração</string>\n    <string name=\"day_background_too_dark\">O fundo diurno não pode ser muito escuro!</string>\n    <string name=\"day_bottom_bar_too_dark\">A parte inferior diurno não pode ser muito escuro!</string>\n    <string name=\"night_background_too_light\">O fundo noturno não pode ser muito brilhante!</string>\n    <string name=\"night_bottom_bar_too_light\">A parte inferior noturno não pode ser muito brilhante!</string>\n    <string name=\"accent_background_diff\">Tem que haver diferença entre destaque e cor de fundo</string>\n    <string name=\"accent_text_diff\">Tem que haver diferença entre destaque e cor de texto</string>\n    <string name=\"wrong_format\">Formato incorreto</string>\n    <string name=\"error\">Erro</string>\n    <string name=\"show_brightness_view\">Mostrar o widget de brilho</string>\n    <string name=\"language\">Idioma</string>\n    <string name=\"import_rss_source\">Importar fonte de RSS</string>\n    <string name=\"donate_summary\">Sua doação tornará este App ainda melhor</string>\n    <string name=\"about_summary\">Conta oficial de Wechat [开源阅读软件]</string>\n    <string name=\"read_record\">Histórico de leitura</string>\n    <string name=\"del_read_record\">Excluir histórico de leitura</string>\n    <string name=\"read_record_summary\">Leia o resumo do histórico</string>\n    <string name=\"local_tts\">TTS local</string>\n    <string name=\"thread_count\">Contagem de tópicos</string>\n    <string name=\"all_read_time\">Tempo total da leitura</string>\n    <string name=\"un_select_all\">Desmarcar todos</string>\n    <string name=\"import_str\">Importar</string>\n    <string name=\"export_str\">Exportar</string>\n    <string name=\"save_theme_config\">Salvar configuração do tema</string>\n    <string name=\"save_day_theme_summary\">Salvar a configuração do tema diurno</string>\n    <string name=\"save_night_theme_summary\">Salvar a configuração do tema noturno</string>\n    <string name=\"theme_list\">Lista de temas</string>\n    <string name=\"theme_list_summary\">Salvar, Importar, Compartilhar tema</string>\n    <string name=\"select_theme\">Alterar o tema padrão</string>\n    <string name=\"sort_by_lastUpdateTime\">Ordenar por tempo de atualização</string>\n    <string name=\"search_content\">Pesquisar conteúdo</string>\n    <string name=\"rss_source_empty\">Empty now!</string>\n    <string name=\"explore_empty\">Atualmente não há nenhuma fonte!</string>\n    <string name=\"page_key_set_help\">将焦点放到输入框按下物理按键会自动录入键值,多个按键会自动用英文逗号隔开.</string>\n    <string name=\"theme_name\">Nome de tema</string>\n    <string name=\"auto_clear_expired\">Excluir automaticamente dados vencidos da busca</string>\n    <string name=\"auto_clear_expired_summary\">Histórico de busca mais de um dia</string>\n    <string name=\"re_segment\">Re-segmentar</string>\n    <string name=\"style_name\">Formato do nome:</string>\n    <string name=\"empty_msg_import_book\">Clique no ícone da pasta no canto superior direito e selecione a pasta</string>\n    <string name=\"scan_folder\">Busca inteligente</string>\n    <string name=\"import_file_name\">Nome do arquivo importado</string>\n    <string name=\"no_book\">Sem livros</string>\n    <string name=\"keep_original_name\">Manter o nome original</string>\n    <string name=\"click_regional_config\">O controle de toque da tela</string>\n    <string name=\"close\">Fechar</string>\n    <string name=\"next_page\">Próxima página</string>\n    <string name=\"prev_page\">Página anterior</string>\n    <string name=\"non_action\">Nenhuma Ação</string>\n    <string name=\"body_title\">Título</string>\n    <string name=\"show_hide\">Mostrar/ocultar</string>\n    <string name=\"header_footer\">rodapé <![CDATA[&]]> cabeçalho</string>\n    <string name=\"rule_subscription\">Assinatura de regras</string>\n    <string name=\"rule_sub_empty_msg\">添加大佬们提供的规则导入地址\\n添加后点击可导入规则</string>\n    <string name=\"get_book_progress\">Obter o progresso da nuvem</string>\n    <string name=\"cover_book_progress\">Phủ sóng tiến độ trên đám mây</string>\n    <string name=\"current_progress_exceeds_cloud\">O progresso atual excede o progresso da nuvem. Você quer sincronizar?</string>\n    <string name=\"sync_book_progress_t\">Progresso de leitura sincronizada</string>\n    <string name=\"sync_book_progress_s\">Sincronizar o progresso da leitura ao entrar e sair da tela de leitura</string>\n    <string name=\"sync_book_progress_plus_t\">Aprimoramento de Sincronização</string>\n    <string name=\"sync_book_progress_plus_s\">Ressincroniza o progresso na nuvem ao retornar à página (tela desligada, retorno do segundo plano, etc.) ou quando a rede estiver disponível. Será solicitada confirmação antes de sincronizar o novo progresso.</string>\n    <string name=\"create_bookmark_error\">Falha ao marcar como favorito</string>\n    <string name=\"single_url\">URL única</string>\n    <string name=\"export_bookshelf\">Exportar a lista de livros</string>\n    <string name=\"import_bookshelf\">Importar a lista de livros</string>\n    <string name=\"pre_download\">Baixar antecipadamente</string>\n    <string name=\"pre_download_m\">预下载%s页</string>\n    <string name=\"pre_download_s\">Baixar %s capítulos antecipadamente</string>\n    <string name=\"is_enabled\">Está ativado</string>\n    <string name=\"background_image\">Imagem de fundo</string>\n    <string name=\"copy_book_url\">Copiar a URL do livro</string>\n    <string name=\"copy_toc_url\">Copiar a URL dos capítulos</string>\n    <string name=\"export_folder\">Exportar a pasta</string>\n    <string name=\"export_charset\">Codificação de texto exportada</string>\n    <string name=\"export_to_web_dav\">Exportar para WebDav</string>\n    <string name=\"reverse_content\">Conteúdo reverso</string>\n    <string name=\"debug\">Depuração</string>\n    <string name=\"crash_log\">Registro de falhas</string>\n    <string name=\"use_zh_layout\">Utilização de layout chinesas personalizadas</string>\n    <string name=\"image_style\">Formato de imagem</string>\n    <string name=\"system_tts\">Sistema TTS</string>\n    <string name=\"export_type\">Formato de exportação</string>\n    <string name=\"checkAuthor\">Verificar por autor</string>\n    <string name=\"url_already\">Esta URL já foi assinada</string>\n    <string name=\"high_brush_title\">Alta taxa de atualização da tela</string>\n    <string name=\"high_brush_summary\">Use a maior taxa de atualização da tela</string>\n    <string name=\"export_all\">Exportar tudo</string>\n    <string name=\"complete\">Concluído</string>\n    <string name=\"show_unread\">Mostrar não lido</string>\n    <string name=\"use_default_cover\">Sempre mostrar capa padrão</string>\n    <string name=\"use_default_cover_s\">Sempre mostrar capa padrão, não mostrar capa da rede</string>\n    <string name=\"search_src\">Procurar código fonte</string>\n    <string name=\"boo_src\">Código fonte do livro</string>\n    <string name=\"toc_src\">Código fonte dos capítulos</string>\n    <string name=\"content_src\">Código fonte do conteúdo</string>\n    <string name=\"list_src\">Listar código fonte</string>\n    <string name=\"title_font_size\">Tamanho de letra</string>\n    <string name=\"title_margin_top\">Borda superior</string>\n    <string name=\"title_margin_bottom\">Borda inferior</string>\n    <string name=\"show\">Mostrar</string>\n    <string name=\"hide\">Ocultar</string>\n    <string name=\"hide_when_status_bar_show\">Ocultar quando a barra de status é mostrada</string>\n    <string name=\"reverse_toc\">TOC reverso</string>\n    <string name=\"show_discovery\">Mostrar</string>\n    <string name=\"style\">Formato</string>\n    <string name=\"group_style\">Formato de grupo</string>\n    <string name=\"export_file_name\">Exportar o nome do arquivo</string>\n    <string name=\"reset\">Resetar</string>\n    <string name=\"null_url\">Nenhuma url</string>\n    <string name=\"dict\">Dicionários</string>\n    <string name=\"unknown_error\">Erro desconhecido</string>\n    <string name=\"end\">fim</string>\n    <string name=\"custom_group_summary\">Desativar substituir agrupamento / Ativar adicionar agrupamento</string>\n    <string name=\"pref_media_button_per_next\">Botões de mídia - Anterior|Próximo</string>\n    <string name=\"pref_media_button_per_next_summary\">Anterior|Próximo Parágrafo/Anterior|Próximo Capítulo</string>\n    <string name=\"read_aloud_by_page_summary\">Virar as páginas durante tempo, com uma pausa ao virar as páginas</string>\n    <string name=\"check_source_show_debug_message\">Marcando a fonte do livro mostra uma mensagem de depuração</string>\n    <string name=\"check_source_show_debug_message_summary\">Mostrar o status da rede com a data e hora durante a verificação da fonte</string>\n    <string name=\"export_no_chapter_name\">Não há nomes de capítulos de exportação</string>\n    <string name=\"autobackup_fail\">Auto-Backup falhou\\n%s</string>\n    <string name=\"background_image_blurring\">Desfocagem da imagem de fundo</string>\n    <string name=\"background_image_blurring_radius\">Raio da desfocagem</string>\n    <string name=\"background_image_hint\">Desativado quando 0, Ativado entre 1 e 25\\n Quanto maior o raio, mais forte o efeito de desfocagem</string>\n    <string name=\"need_login\">Login necessário</string>\n    <string name=\"pref_cronet_summary\">Usando componentes de rede Cronet</string>\n    <string name=\"anti_alias\">Anti-Aliasing</string>\n    <string name=\"pref_anti_alias_summary\">Anti-Aliasing when draw picture</string>\n    <string name=\"upload_url\">创建分享链接</string>\n    <string name=\"download_url_rule\">downloadUrlRule(downloadUrls)</string>\n    <string name=\"sort_by_respondTime\">Classificar por tempo de resposta</string>\n    <string name=\"respondTime\">tempo de resposta: %1$d ms</string>\n    <string name=\"export_success\">导出成功</string>\n    <string name=\"path\">路径</string>\n    <string name=\"direct_link_upload_rule\">直链上传规则</string>\n    <string name=\"direct_link_upload_rule_summary\">用于导出书源书单时生成直链url</string>\n    <string name=\"direct_link_upload_config\">直链上传配置</string>\n    <string name=\"copy_play_url\">拷贝播放Url</string>\n    <string name=\"set_source_variable\">设置源变量</string>\n    <string name=\"set_book_variable\">设置书籍变量</string>\n    <string name=\"summary\">注释</string>\n    <string name=\"cover_config\">封面设置</string>\n    <string name=\"cover_config_summary\">通用封面规则及默认封面样式</string>\n    <string name=\"cover_show_name\">显示书名</string>\n    <string name=\"cover_show_name_summary\">封面上显示书名</string>\n    <string name=\"cover_show_author\">显示作者</string>\n    <string name=\"cover_show_author_summary\">封面上显示作者</string>\n    <string name=\"read_aloud_prev_paragraph\">朗读上一段</string>\n    <string name=\"read_aloud_next_paragraph\">朗读下一段</string>\n    <string name=\"wait_download\">待下载</string>\n    <string name=\"download_success\">下载完成</string>\n    <string name=\"download_error\">下载失败</string>\n    <string name=\"downloading\">下载中</string>\n    <string name=\"unknown_state\">未知状态</string>\n    <string name=\"disable_source\">禁用源</string>\n    <string name=\"delete_source\">删除源</string>\n    <string name=\"chapter_pay\">购买</string>\n    <string name=\"double_page_horizontal\">平板/横屏双页</string>\n    <string name=\"open_in_browser\">浏览器打开</string>\n    <string name=\"copy_url\">拷贝url</string>\n    <string name=\"full_screen\">全屏</string>\n    <string name=\"open_fun\">打开方式</string>\n    <string name=\"use_browser_open\">是否使用外部浏览器打开?</string>\n    <string name=\"see\">查看</string>\n    <string name=\"open\">打开</string>\n    <string name=\"del_login_header\">删除登录头</string>\n    <string name=\"show_login_header\">查看登录头</string>\n    <string name=\"login_header\">登录头</string>\n    <string name=\"font_scale\">字体大小</string>\n    <string name=\"font_scale_summary\">当前字体大小:%.1f</string>\n    <string name=\"search_content_size\">search result</string>\n    <string name=\"search_content_empty\">Empty search result, check conversation settings</string>\n    <string name=\"tts_speech_reduce\">语速减</string>\n    <string name=\"tts_speech_add\">语速加</string>\n    <string name=\"open_sys_dir_picker_error\">打开系统文件夹选择器出错,自动打开应用文件夹选择器</string>\n    <string name=\"open_sys_doc_picker_error\">打开系统文件选择器出错,自动打开应用文件选择器</string>\n    <string name=\"expand_text_menu\">展开文本选择菜单</string>\n    <string name=\"book_tree_uri_t\">书籍保存位置</string>\n    <string name=\"book_tree_uri_s\">从其它应用打开的书籍保存位置</string>\n    <string name=\"select_book_folder\">选择保存书籍的文件夹</string>\n    <string name=\"user_agent\">用户代理</string>\n    <string name=\"bg_alpha\">背景透明度</string>\n    <string name=\"split_long_chapter\">Dividir capítulos longos</string>\n    <string name=\"need_more_time_load_content\">正文长度过长时，加载正文可能会花费更多时间</string>\n\n    <!-- check source config string -->\n    <string name=\"check_source_config\">校验设置</string>\n    <string name=\"check_source_item\">校验项目</string>\n    <string name=\"check_source_timeout\">单个书源校验超时(秒)</string>\n    <string name=\"timeout\">超时</string>\n    <string name=\"seconds\">秒</string>\n    <string name=\"less_than\">小于</string>\n    <string name=\"check_source_config_summary\">校验超时: %1$s秒\\n校验项目:%2$s</string>\n    <string name=\"record_debug_log\">记录调试日志</string>\n    <string name=\"sub_dir\">子文件夹</string>\n    <string name=\"general\">全局</string>\n    <string name=\"use_replace\">使用替换</string>\n    <string name=\"scope_title\">作用于标题</string>\n    <string name=\"scope_content\">作用于正文</string>\n    <string name=\"join_qq_channel\">加入QQ频道</string>\n    <string name=\"qq_channel_summary\">点击加入阅读QQ频道</string>\n    <string name=\"menu_refresh_dur\">刷新当前章节</string>\n    <string name=\"menu_refresh_after\">刷新之后章节</string>\n    <string name=\"menu_refresh_all\">刷新全部章节</string>\n    <string name=\"edit_content\">编辑内容</string>\n    <string name=\"chapter_change_source\">单章换源</string>\n    <string name=\"book_change_source\">整书换源</string>\n    <string name=\"sort_by_time\">时间排序</string>\n    <string name=\"enable_record\">开启记录</string>\n    <string name=\"copy_all\">拷贝所有</string>\n    <string name=\"auto_complete\">自动补全</string>\n    <string name=\"sort_by_size\">大小排序</string>\n    <string name=\"welcome_style\">启动界面样式</string>\n    <string name=\"welcome_style_summary\">启动界面图片和是否显示文字等</string>\n    <string name=\"show_welcome_text\">显示文字</string>\n    <string name=\"welcome_text\">阅读|享受美好时光</string>\n    <string name=\"custom_welcome\">自定义欢迎页</string>\n    <string name=\"custom_welcome_summary\">是否使用自定义欢迎页</string>\n    <string name=\"show_icon\">显示图标</string>\n    <string name=\"show_default_book_icon\">显示默认书籍图标</string>\n    <string name=\"cache_export\">缓存/导出</string>\n    <string name=\"assists_key_config\">辅助按键配置</string>\n    <string name=\"url_option\">Url参数</string>\n    <string name=\"only_wifi\">仅WIFI</string>\n    <string name=\"only_wifi_summary\">仅在wifi下加载网络封面</string>\n    <string name=\"cover_rule\">封面规则</string>\n    <string name=\"cover_rule_summary\">进入详情页时使用封面规则重新获取封面</string>\n    <string name=\"scroll_to_dur_source\">定位到当前书源</string>\n    <string name=\"sys_tts_config\">系统tts设置</string>\n    <string name=\"sys_tts_config_summary\">打开系统tts设置界面</string>\n    <string name=\"cannot_timed_non_playback\">非播放状态无法定时</string>\n    <string name=\"all_bookmark\">Todos os marcadores</string>\n    <string name=\"change_source_batch\">批量换源</string>\n    <string name=\"book_type_different\">书籍类型不一样</string>\n    <string name=\"soure_change_source\">是否确认换源</string>\n    <string name=\"input_verification_code\">输入验证码</string>\n    <string name=\"verification_code\">验证码</string>\n    <string name=\"timeout_millisecond\">超时毫秒数</string>\n    <string name=\"file_not_supported\">Continue to open although %1$s is not supported ?</string>\n    <string name=\"import_tts\">导入TTS</string>\n    <string name=\"import_theme\">导入主题</string>\n    <string name=\"import_txt_toc_rule\">导入txt目录规则</string>\n    <string name=\"auto_save_cookie\">CookieJar</string>\n    <string name=\"cookie\">清除cookie</string>\n    <string name=\"download_and_import_file\">导入在线书籍文件</string>\n    <string name=\"upload_book_success\">Upload Success</string>\n    <string name=\"upload_book_fail\">Upload Fail</string>\n    <string name=\"download_book_success\">Download Success</string>\n    <string name=\"download_book_fail\">Download Fail</string>\n    <string name=\"upload_to_remote\">Upload</string>\n    <string name=\"add_remote_book\">RemoteBook</string>\n    <string name=\"bitmap_cache_size_summary\">Current cache max size %1$s MB</string>\n    <string name=\"bitmap_cache_size\">bitmap cache size</string>\n    <string name=\"image_retain_number_summary\">Keep the number of chapters read %s</string>\n    <string name=\"image_retain_number\">Number of manga reservations</string>\n    <string name=\"export_pics_file\">Export Picture Files</string>\n    <!-- string end -->\n    <string name=\"error_decode_bitmap\">Fail to decode bitmap</string>\n    <string name=\"error_image_url_empty\">Image url is empty, check replacement rules</string>\n    <string name=\"variable_comment\">变量说明(variableComment)</string>\n    <string name=\"remote_book\">远程书籍</string>\n    <string name=\"reading_time_sort\">阅读时长排序</string>\n    <string name=\"last_read_time_sort\">阅读时间排序</string>\n    <string name=\"reading_time_tag\">阅读时长:</string>\n    <string name=\"last_read_time_tag\">最后阅读时间:</string>\n    <string name=\"page_touch_slop_title\">滑动翻页阈值</string>\n    <string name=\"page_touch_slop_dialog_title\">滑动翻页阈值（0 = 系统默认值）</string>\n    <string name=\"page_touch_slop_summary\">滑动多长距离才会触发滑动翻页（系统默认值 %s px）</string>\n    <string name=\"example\">Exemplo</string>\n    <string name=\"check_selected_interval\">选中所选区间</string>\n    <string name=\"show_add_to_shelf_alert_title\">返回时提示放入书架</string>\n    <string name=\"show_add_to_shelf_alert_summary\">阅读未放入书架的书籍在返回时提示放入书架</string>\n    <string name=\"review\">Review</string>\n    <string name=\"rule_review_url\">段评URL（reviewUrl）</string>\n    <string name=\"rule_avatar\">段评发布者头像（avatarRule）</string>\n    <string name=\"rule_review_content\">段评内容（contentRule）</string>\n    <string name=\"rule_post_time\">段评发布时间（postTimeRule）</string>\n    <string name=\"rule_review_quote\">段评回复URL（reviewQuoteUrl）</string>\n    <string name=\"review_vote_down\">点踩URL（voteDownUrl）</string>\n    <string name=\"review_vote_up\">点赞URL（voteUpUrl）</string>\n    <string name=\"post_review_url\">发送回复URL（postReviewUrl）</string>\n    <string name=\"post_quote_url\">发送回复段评URL（postQuoteUrl）</string>\n    <string name=\"delete_review_url\">删除段评URL（deleteUrl）</string>\n    <string name=\"tag_explore_enabled\">标志:发现已启用</string>\n    <string name=\"tag_explore_disabled\">标志:发现已禁用</string>\n    <string name=\"show_read_title_addition\">show read title addition area</string>\n    <string name=\"read_bar_style_follow_page\">read bar style follow page</string>\n    <string name=\"rule_image_decode\">Decode Image(imageDecode)</string>\n    <string name=\"like_source\">赞</string>\n    <string name=\"not_like_source\">踩</string>\n    <string name=\"async_load_image\">异步加载图片</string>\n    <string name=\"ignore_audio_focus_title\">忽略音频焦点</string>\n    <string name=\"ignore_audio_focus_summary\">允许与其他应用同时播放音频</string>\n    <string name=\"refresh_sort\">刷新分类</string>\n    <string name=\"cover_decode_js\">Decode Cover Js(coverDecodeJs)</string>\n    <string name=\"net_no_group\">网络未分组</string>\n    <string name=\"local_no_group\">本地未分组</string>\n    <string name=\"parallel_export_book\">多线程导出</string>\n    <string name=\"progress_bar_behavior\">进度条行为</string>\n    <string name=\"source_edit_text_max_line\">源编辑框最大行数</string>\n    <string name=\"source_edit_max_line_summary\">%s,设置行数小于屏幕可显示的最大行数可以更方便的滑动到其他的字段进行编辑</string>\n    <string name=\"restore_last_book_process\">是否恢复到跳转前的阅读进度？</string>\n    <string name=\"search_scope\">搜索范围</string>\n    <string name=\"toggle_search_scope\">切换</string>\n    <string name=\"sure_clear_search_history\">是否确认清除所有搜索历史记录</string>\n    <string name=\"no_anim_scroll_page\">禁用滚动点击动画</string>\n    <string name=\"webdav_device_name\">设备名称</string>\n    <string name=\"web_service_wake_lock\">WebService唤醒锁</string>\n    <string name=\"web_service_wake_lock_summary\">开启web服务的时候启用唤醒锁,有些手机开启唤醒锁会被杀后台</string>\n    <string name=\"read_aloud_wake_lock\">朗读服务唤醒锁</string>\n    <string name=\"read_aloud_wake_lock_summary\">开启朗读的时候启用唤醒锁,有些手机开启唤醒锁会被杀后台</string>\n    <string name=\"audio_play_wake_lock\">音频服务唤醒锁</string>\n    <string name=\"audio_play_wake_lock_summary\">播放音频的时候启用唤醒锁,有些手机开启唤醒锁会被杀后台</string>\n    <string name=\"change_search_scope\">切换搜索范围</string>\n    <string name=\"copy_rule\">拷贝规则</string>\n    <string name=\"paste_rule\">粘贴规则</string>\n    <string name=\"groups_or_source\">多分组/书源</string>\n    <string name=\"replace_state_change\">替换(启用/禁用)</string>\n    <string name=\"show_last_update_time\">显示上次更新时间</string>\n    <string name=\"refresh_list\">刷新列表</string>\n    <string name=\"tip_divider_color\">分隔线颜色</string>\n    <string name=\"same_title_removed\">移除重复标题</string>\n    <string name=\"update_book_fail\">更新失败</string>\n    <string name=\"notification_permission_rationale\">阅读需要发送通知来显示朗读控制和下载进度</string>\n    <string name=\"webdav_after_local_restore_confirm\">webDav书源比本地新,是否恢复</string>\n    <string name=\"c_whitelist\">白名单(contentWhitelist)</string>\n    <string name=\"c_blacklist\">黑名单(contentBlacklist)</string>\n    <string name=\"confirm\">确认</string>\n    <string name=\"jump_to_another_app\">跳转其它应用</string>\n    <string name=\"clear_webview_data\">清除 WebView 数据</string>\n    <string name=\"clear_webview_data_summary\">清除内置浏览器所有数据</string>\n    <string name=\"source_tab_list\">列表</string>\n    <string name=\"dict_rule\">字典规则</string>\n    <string name=\"config_dict_rule\">配置字典规则</string>\n    <string name=\"create\">新建</string>\n    <string name=\"url_rule\">url规则(urlRule)</string>\n    <string name=\"show_rule\">显示规则(showRule)</string>\n    <string name=\"config_txt_toc_rule\">配置 TXT 目录规则</string>\n    <string name=\"import_dict_rule\">导入字典规则</string>\n    <string name=\"keep_group\">保留分组</string>\n    <string name=\"server_config\">服务器配置</string>\n    <string name=\"sure_upload\">Remote webDav url exists, Continue?</string>\n    <string name=\"unsupport_archivefile_entry\">Cannot find supported files in archive</string>\n    <string name=\"no_books_dir\">没有设置书籍保存位置!</string>\n    <string name=\"delete_alert\">删除提醒</string>\n    <string name=\"no_book_found_bookshelf\">No book found in bookshelf, import again ?</string>\n    <string name=\"archive_not_found\">Can not find archive selected, continue to download ?</string>\n    <string name=\"privacy_policy\">用户隐私与协议</string>\n    <string name=\"agree\">同意</string>\n    <string name=\"refuse\">拒绝</string>\n    <string name=\"file_manage\">文件管理</string>\n    <string name=\"file_manage_summary\">管理私有文件夹的文件</string>\n    <string name=\"create_folder\">创建文件夹</string>\n    <string name=\"allow_drop_down_refresh\">允许下拉刷新</string>\n    <string name=\"text_underline\">文字下划线</string>\n    <string name=\"select_new_source\">选中新增源</string>\n    <string name=\"select_update_source\">选中更新源</string>\n    <string name=\"set_local_password\">设置本地密码</string>\n    <string name=\"set_local_password_summary\">本地密码用来对备份的敏感信息加密和解密,如需在不同设备之间同步,本地密码需一致.</string>\n    <string name=\"only_latest_backup_t\">仅保留最新备份</string>\n    <string name=\"only_latest_backup_s\">本地备份仅保留最新备份文件</string>\n    <string name=\"webdav_application_authorization_error\">Fail to authorize WebDav application</string>\n    <string name=\"load_word_count\">Carregar os palavras do livro</string>\n    <string name=\"replace_exclude_scope\">排除范围，选填书名或者书源 URL</string>\n    <string name=\"format_js_rule\">格式化规则(formatJs)</string>\n    <string name=\"adjust_pos\">调整位置</string>\n    <string name=\"select_section_export\">Choose some chapters to be exported</string>\n    <string name=\"error_scope_input\">Please enter the correct range</string>\n    <string name=\"custom_export\">Custom Export</string>\n    <string name=\"file_contains_number\">The index of chapters contained in each file</string>\n    <string name=\"export_chapter_index\">The section index that needs to be exported</string>\n    <string name=\"shrink_database\">压缩数据库</string>\n    <string name=\"shrink_database_summary\">减小数据库文件的大小</string>\n    <string name=\"is_compress\">是否压缩</string>\n    <string name=\"sort_desc\">反序</string>\n    <string name=\"test\">测试</string>\n    <string name=\"show_wait_up_count\">显示等待更新数量</string>\n    <string name=\"exit_app\">退出软件</string>\n    <string name=\"result_analyzed\">Analyzed</string>\n    <string name=\"bookshelf_px_4\">Comprehensive</string>\n    <string name=\"bookshelf_px_5\">Ordenar por autor</string>\n    <string name=\"effective_replaces\">起效的替换</string>\n    <string name=\"export_book\">导出书籍</string>\n    <string name=\"export_book_notification_content\">正在导出(%1$s),还有%2$d本待导出</string>\n    <string name=\"export_md\">导出(MD)</string>\n    <string name=\"change_source_delay\">换源间隔</string>\n    <string name=\"change_source_progress\">\" Resultado %1$d, progresso atual %2$d / %3$d: %4$s\"</string>\n    <string name=\"open_book_info_by_click_title\">点击书名打开详情</string>\n    <string name=\"export_wait\">等待导出</string>\n    <string name=\"default_home_page\">默认主页</string>\n    <string name=\"sync_book_progress_success\">Sincronização de progresso bem-sucedida</string>\n    <string name=\"show_bookshelf_fast_scroller\">显示快速滚动条</string>\n    <string name=\"export_all_use_book_source\">导出所有书的书源</string>\n    <string name=\"cloud_progress_exceeds_current\">The cloud progress exceeds the current progress. Do you want to synchronize?</string>\n    <string name=\"keep_enable\">保留启用状态</string>\n    <string name=\"preview_image_by_click\">点击预览图片</string>\n    <string name=\"screen_portrait_reversed\">反向竖屏</string>\n    <string name=\"del_ruby_tag\">删除ruby标签</string>\n    <string name=\"del_h_tag\">删除h标签</string>\n    <string name=\"adjust_chapter_page\">调整本章页数</string>\n    <string name=\"adjust_chapter_index\">调整章节位置</string>\n    <string name=\"clear_webview_data_success\">清除成功，3秒后自动重启应用</string>\n    <string name=\"key_page_on_long_press\">按键长按翻页</string>\n    <string name=\"save_log\">保存日志</string>\n    <string name=\"create_heap_dump\">创建堆转储</string>\n    <string name=\"record_heap_dump_s\">当应用发生OOM崩溃时保存堆转储</string>\n    <string name=\"record_heap_dump_t\">记录堆转储</string>\n    <string name=\"font_weight_text\">中/粗/细</string>\n    <string name=\"keep_swipe_tip\">继续滑动以加载下一章…</string>\n    <string name=\"enable_optimize_render\">启用绘制优化</string>\n    <string name=\"ignore_battery_permission_rationale\">阅读需要请求后台权限以保持服务正常运行</string>\n    <string name=\"simulated_reading\">Leitura Fingida</string>\n    <string name=\"switch_on\">Lig/Desl</string>\n    <string name=\"start_from\">Começar</string>\n    <string name=\"daily_chapters\">Cap. por dia</string>\n    <string name=\"start_chapter\">Início</string>\n    <string name=\"update_to_variant_title\">检查更新查找版本</string>\n    <string name=\"update_to_variant_summary\">检查更新时查找其他签名版本</string>\n    <string name=\"default_version\">当前</string>\n    <string name=\"official_version\">正式版</string>\n    <string name=\"beta_release_version\">测试版</string>\n    <string name=\"beta_releaseA_version\">共存版</string>\n    <string name=\"stream_read_aloud_audio\">流式播放音频</string>\n    <string name=\"stream_read_aloud_audio_summary\">即边下边播，网络不好时播放会断断续续，仅TTS源有效</string>\n    <string name=\"pause_read_aloud_while_phone_calls_title\">来电期间暂停朗读</string>\n    <string name=\"pause_read_aloud_while_phone_calls_summary\">在通话期间暂停朗读，需要读取手机状态权限</string>\n    <string name=\"read_aloud_read_phone_state_permission_rationale\">阅读需要读取手机状态实现来电期间暂停朗读功能</string>\n    <string name=\"read_aloud_by_media_button_title\">耳机按键启动朗读</string>\n    <string name=\"read_aloud_by_media_button_summary\">通过耳机按键来启动朗读</string>\n    <string name=\"group_sources_by_domain\">按域名分组显示</string>\n    <string name=\"theme_config\">主题配置</string>\n    <string name=\"show_manga_ui\">漫画浏览</string>\n    <string name=\"disable_manga_scale\">禁用漫画缩放</string>\n    <string name=\"disable_manga_click_scroll\">禁用点击翻页</string>\n    <string name=\"enable_auto_page_scroll\">开启自动翻页</string>\n    <string name=\"manga_auto_page_speed\">翻页速度 %s</string>\n    <string name=\"enable_auto_scroll\">开启滚动</string>\n    <string name=\"manga_footer_config\">页脚配置</string>\n    <string name=\"setting_manga_auto_page_speed\">设置自动翻页速度</string>\n    <string name=\"book_reader_info_bar\">页数. %1$d/%2$d  章节. %3$d/%4$d -> %5$s%%</string>\n    <string name=\"menu_download_after\">Faça o download do próximo capítulo</string>\n    <string name=\"menu_download_all\">Download de todos os capítulos</string>\n    <string name=\"manga_header_chapter\">《章节和文案》隐藏</string>\n    <string name=\"manga_check_chapter_label\">《章节.》文案</string>\n    <string name=\"manga_check_chapter\">章节</string>\n    <string name=\"manga_check_chapter_name\">章节名称</string>\n    <string name=\"manga_header_page\">《页数和文案》隐藏</string>\n    <string name=\"manga_check_page_label\">《页数.》文案</string>\n    <string name=\"manga_check_page_number\">页数</string>\n    <string name=\"manga_header_progress\">《总进度和文案》隐藏</string>\n    <string name=\"manga_check_progress_label\">《总进度.》文案</string>\n    <string name=\"manga_check_progress\">总进度</string>\n    <string name=\"manga_header_footer\">页脚</string>\n    <string name=\"manga_radio_left\">靠左</string>\n    <string name=\"manga_radio_center\">居中</string>\n    <string name=\"enable_manga_horizontal_scroll\">水平滚动</string>\n    <string name=\"manga_color_filter\">滤镜</string>\n    <string name=\"hide_manga_title\">隐藏漫画列表标题</string>\n    <string name=\"refresh_explore\">刷新发现</string>\n    <string name=\"padding_display_cutouts\">Padding display cutouts</string>\n    <string name=\"manga_epaper\">墨水屏</string>\n    <string name=\"manga_epaper_stting\">墨水屏设置</string>\n    <string name=\"manga_epaper_value\">阈值</string>\n    <string name=\"disable_horizontal_page_snap\">禁用水平翻页效果</string>\n    <string name=\"enable_manga_gray\">开启图片灰色</string>\n    <string name=\"sure_cache_book\">是否确认开始缓存？</string>\n    <string name=\"auto_check_new_backup_t\">自动检查新备份</string>\n    <string name=\"auto_check_new_backup_s\">打开软件时检查是否有新备份，有新备份时提示是否更新</string>\n    <string name=\"sys_image_picker\">系统图片选择器</string>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values-vi/arrays.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\n    <string-array name=\"book_type\">\n        <item>Văn bản</item>\n        <item>Âm thanh</item>\n        <item>Hình ảnh</item>\n        <item>Tập tin</item>\n    </string-array>\n\t<string-array name=\"screen_direction_title\">\n\t\t<item>@string/screen_unspecified</item>\n\t\t<item>@string/screen_portrait</item>\n\t\t<item>@string/screen_landscape</item>\n\t\t<item>@string/screen_sensor</item>\n\t\t<item>@string/screen_portrait_reversed</item>\n\t</string-array>\n    <string-array name=\"group_style\">\n        <item>Thẻ</item>\n        <item>Thư mục</item>\n    </string-array>\n\n    <string-array name=\"theme_mode\">\n        <item>Theo hệ thống</item>\n        <item>Chế độ sáng</item>\n        <item>Chế độ tối</item>\n        <item>Chế độ E-Ink</item>\n    </string-array>\n\n    <string-array name=\"NavBarColors\">\n        <item>Tự động</item>\n        <item>Đen</item>\n        <item>Trắng</item>\n        <item>Theo nền</item>\n    </string-array>\n\n    <string-array name=\"screen_time_out\">\n        <item>Mặc định</item>\n        <item>1 phút</item>\n        <item>5 phút</item>\n        <item>10 phút</item>\n        <item>Luôn sáng</item>\n    </string-array>\n\n    <string-array name=\"chinese_mode\">\n        <item>Tắt</item>\n        <item>Phồn thể sang Giản thể</item>\n        <item>Giản thể sang Phồn thể</item>\n    </string-array>\n\t<string-array name=\"double_page_title\">\n\t\t<item>Toàn cục một trang</item>\n\t\t<item>Toàn cục hai trang</item>\n\t\t<item>Hai trang khi xoay ngang</item>\n\t\t<item>Máy tính bảng/Xoay ngang hai trang</item>\n\t</string-array>\n    <string-array name=\"progress_bar_behavior_title\">\n\t\t<item>@string/adjust_chapter_page</item>\n\t\t<item>@string/adjust_chapter_index</item>\n\t</string-array>\n    <string-array name=\"system_typefaces\">\n        <item>Phông chữ mặc định hệ thống</item>\n        <item>Phông chữ Serif hệ thống</item>\n        <item>Phông chữ Monospace hệ thống</item>\n    </string-array>\n\n    <string-array name=\"read_tip\">\n        <item>Trống</item>\n        <item>Tên sách</item>\n        <item>Tiêu đề</item>\n        <item>Thời gian</item>\n        <item>Pin</item>\n        <item>Pin %</item>\n        <item>Số trang</item>\n        <item>Tiến độ (%)</item>\n        <item>Tiến độ (xx/yyy)</item>\n        <item>Số trang và Tiến độ</item>\n        <item>Thời gian và Pin</item>\n        <item>Thời gian và Pin %</item>\n    </string-array>\n\n    <string-array name=\"text_font_weight\">\n        <item>Thường</item>\n        <item>Đậm</item>\n        <item>Nhạt</item>\n    </string-array>\n\n    <string-array name=\"language\">\n        <item>Theo hệ thống</item>\n        <item>Tiếng Trung (Giản thể)</item>\n        <item>Tiếng Trung (Phồn thể)</item>\n        <item>Tiếng Anh</item>\n    </string-array>\n\n    <string-array name=\"rule_type\">\n        <item>Nguồn sách</item>\n        <item>Nguồn RSS</item>\n        <item>Quy tắc thay thế</item>\n    </string-array>\n\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values-vi/strings.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <!--App-->\n    <string name=\"app_name\">Legado</string>\n    <string name=\"app_name_a\">Legado·A</string>\n    <string name=\"receiving_shared_label\">Legado·Tìm kiếm</string>\n    <string name=\"tip_perm_request_storage\">Legado cần quyền truy cập bộ nhớ để tìm và đọc sách. Vui lòng vào \"Cài đặt ứng dụng\" để cấp \"Quyền truy cập bộ nhớ\".</string>\n\n    <!--Other-->\n    <string name=\"menu_backup\">Sao lưu</string>\n    <string name=\"menu_restore\">Khôi phục</string>\n    <string name=\"menu_import_old\">Nhập dữ liệu Legado cũ</string>\n    <string name=\"webdav_cache_backup\">Sao lưu sách đệm ngoại tuyến</string>\n    <string name=\"webdav_cache_backup_s\">Xuất ra bộ nhớ cục bộ và sao lưu vào thư mục exports trong thư mục Legado</string>\n    <string name=\"backup_path\">Đường dẫn sao lưu</string>\n    <string name=\"select_backup_path\">Vui lòng chọn đường dẫn sao lưu.</string>\n    <string name=\"menu_import_old_version\">Nhập dữ liệu phiên bản cũ</string>\n    <string name=\"menu_import_github\">Nhập dữ liệu từ Github</string>\n    <string name=\"menu_replace_rule\">Thay thế</string>\n    <string name=\"menu_send\">Gửi</string>\n\n    <string name=\"dialog_title\">Thông báo</string>\n    <string name=\"dialog_cancel\">Hủy</string>\n    <string name=\"dialog_confirm\">Xác nhận</string>\n    <string name=\"dialog_setting\">Đến cài đặt</string>\n    <string name=\"tip_cannot_jump_setting_page\">Không thể chuyển đến cài đặt.</string>\n\n    <string name=\"manual_input\">Nhập thủ công</string>\n    <string name=\"enter_directory_path\">Nhập đường dẫn thư mục</string>\n    <string name=\"invalid_directory\">Đường dẫn thư mục không hợp lệ</string>\n    <string name=\"empty_directory_input\">Đường dẫn thư mục không thể để trống</string>\n\n    <string name=\"dynamic_click_retry\">Thử lại</string>\n    <string name=\"dynamic_loading\">Đang tải</string>\n    <string name=\"draw\">Cảnh báo</string>\n    <string name=\"edit\">Chỉnh sửa</string>\n    <string name=\"delete\">Xóa</string>\n    <string name=\"delete_select_group\">Xóa nhóm đã chọn</string>\n    <string name=\"delete_all\">Xóa tất cả</string>\n    <string name=\"replace\">Thay thế</string>\n    <string name=\"replace_purify\">Thay thế và lọc</string>\n    <string name=\"replace_purify_desc\">Cấu hình quy tắc thay thế</string>\n    <string name=\"not_available\">Chưa có</string>\n    <string name=\"enable\">Kích hoạt</string>\n    <string name=\"replace_purify_search\">Tìm kiếm thay thế</string>\n    <string name=\"bookshelf\">Tủ sách</string>\n    <string name=\"favorites\">Yêu thích</string>\n    <string name=\"favorite\">Yêu thích</string>\n    <string name=\"in_favorites\">Đã yêu thích</string>\n    <string name=\"out_favorites\">Chưa yêu thích</string>\n    <string name=\"rss\">Nguồn cấp RSS</string>\n    <string name=\"all\">Tất cả</string>\n    <string name=\"recent_reading\">Đọc gần đây</string>\n    <string name=\"last_read\">Lần đọc cuối</string>\n    <string name=\"update_log\">Nhật ký cập nhật</string>\n    <string name=\"bookshelf_empty\">Tủ sách vẫn còn trống. Hãy tìm sách hoặc thêm từ mục khám phá!</string>\n    <string name=\"action_search\">Tìm kiếm</string>\n    <string name=\"action_download\">Tải xuống</string>\n    <string name=\"layout_list\">Danh sách</string>\n    <string name=\"layout_grid3\">Lưới-3</string>\n    <string name=\"layout_grid4\">Lưới-4</string>\n    <string name=\"layout_grid5\">Lưới-5</string>\n    <string name=\"layout_grid6\">Lưới-6</string>\n    <string name=\"bookshelf_layout\">Bố cục tủ sách</string>\n    <string name=\"view\">Chế độ xem</string>\n    <string name=\"book_library\">Thư viện sách</string>\n    <string name=\"book_local\">Nhập sách cục bộ</string>\n    <string name=\"book_source\">Nguồn sách</string>\n    <string name=\"book_source_manage\">Quản lý nguồn</string>\n    <string name=\"book_source_manage_desc\">Tạo/Nhập/Sửa/Quản lý nguồn sách</string>\n    <string name=\"setting\">Cài đặt</string>\n    <string name=\"theme_setting\">Cài đặt chủ đề</string>\n    <string name=\"theme_setting_s\">Một số cài đặt liên quan đến giao diện hoặc màu sắc</string>\n    <string name=\"other_setting\">Cài đặt khác</string>\n    <string name=\"other_setting_s\">Một số cài đặt liên quan đến chức năng</string>\n    <string name=\"about\">Giới thiệu</string>\n    <string name=\"donate\">Ủng hộ</string>\n    <string name=\"exit\">Thoát</string>\n    <string name=\"exit_no_save\">Chưa được lưu. Bạn có muốn tiếp tục chỉnh sửa?</string>\n    <string name=\"read_style\">Kiểu đọc</string>\n    <string name=\"version\">Phiên bản</string>\n    <string name=\"local\">Cục bộ</string>\n    <string name=\"search\">Tìm kiếm</string>\n    <string name=\"origin_format\">Nguồn: %s</string>\n    <string name=\"read_dur_progress\">Đã đọc: %s</string>\n    <string name=\"book_name\">Tên sách</string>\n    <string name=\"lasted_show\">Mới nhất: %s</string>\n    <string name=\"check_add_bookshelf\">Bạn có muốn thêm «%s» vào tủ sách không?</string>\n    <string name=\"import_books_count\">Tổng cộng %s tệp văn bản</string>\n    <string name=\"is_loading\">Đang tải…</string>\n    <string name=\"retry\">Thử lại</string>\n    <string name=\"web_service\">Dịch vụ web</string>\n    <string name=\"web_service_desc\">Chỉnh sửa nguồn và đọc sách qua web</string>\n    <string name=\"web_edit_source\">Chỉnh sửa nguồn sách trên web</string>\n    <string name=\"offline_cache\">Bộ đệm ngoại tuyến</string>\n    <string name=\"offline_cache_t\">Bộ đệm ngoại tuyến</string>\n    <string name=\"offline_cache_s\">Lưu trữ các chương đã chọn vào bộ nhớ đệm</string>\n    <string name=\"change_origin\">Đổi nguồn</string>\n    <string name=\"about_description\">Đây là một phần mềm đọc mã nguồn mở được phát triển mới bằng Kotlin, chào mừng bạn tham gia.</string>\n    <string name=\"app_share_description\">Link tải Legado (YueDu 3.0):\\n https://github.com/gedoor/legado/releases</string>\n    <string name=\"version_name\">Phiên bản %s</string>\n    <string name=\"pt_background_verification\">Xác minh trong nền</string>\n    <string name=\"ps_background_verification\">Bạn có thể thao tác tự do khi đang xác minh nguồn sách</string>\n    <string name=\"pt_auto_refresh\">Tự động làm mới</string>\n    <string name=\"ps_auto_refresh\">Tự động cập nhật sách khi mở phần mềm</string>\n    <string name=\"pt_auto_download\">Tự động tải xuống</string>\n    <string name=\"ps_auto_download\">Tự động tải các chương mới nhất khi cập nhật sách</string>\n    <string name=\"backup_restore\">Sao lưu và khôi phục</string>\n    <string name=\"web_dav_set\">Cài đặt WebDav</string>\n    <string name=\"web_dav_set_import_old\">Cài đặt WebDav/Nhập dữ liệu cũ</string>\n    <string name=\"backup\">Sao lưu</string>\n    <string name=\"restore\">Khôi phục</string>\n    <string name=\"backup_permission\">Sao lưu cần quyền truy cập bộ nhớ</string>\n    <string name=\"restore_permission\">Khôi phục cần quyền truy cập bộ nhớ</string>\n    <string name=\"ok\">OK</string>\n    <string name=\"cancel\">Hủy</string>\n    <string name=\"backup_confirmation\">Xác nhận sao lưu</string>\n    <string name=\"backup_message\">Tệp sao lưu mới sẽ thay thế tệp cũ.\\n Thư mục sao lưu: YueDu</string>\n    <string name=\"restore_confirmation\">Xác nhận khôi phục</string>\n    <string name=\"restore_message\">Khôi phục dữ liệu tủ sách sẽ ghi đè lên tủ sách hiện tại.</string>\n    <string name=\"backup_success\">Sao lưu thành công</string>\n    <string name=\"backup_fail\">Sao lưu thất bại\\n%s</string>\n    <string name=\"on_restore\">Đang khôi phục</string>\n    <string name=\"restore_success\">Khôi phục thành công</string>\n    <string name=\"restore_fail\">Khôi phục thất bại</string>\n    <string name=\"screen_direction\">Hướng màn hình</string>\n    <string name=\"screen_sensor\">Tự động (cảm biến)</string>\n    <string name=\"screen_landscape\">Ngang</string>\n    <string name=\"screen_portrait\">Dọc</string>\n    <string name=\"screen_unspecified\">Theo hệ thống</string>\n    <string name=\"disclaimer\">Tuyên bố miễn trừ trách nhiệm</string>\n    <string name=\"all_chapter_num\">%d chương</string>\n    <string name=\"interface_setting\">Giao diện</string>\n    <string name=\"brightness\">Độ sáng</string>\n    <string name=\"chapter_list\">Danh sách chương</string>\n    <string name=\"next_chapter\">Chương sau</string>\n    <string name=\"previous_chapter\">Chương trước</string>\n    <string name=\"pt_hide_status_bar\">Ẩn thanh trạng thái</string>\n    <string name=\"ps_hide_status_bar\">Ẩn thanh điều hướng hệ thống khi đọc</string>\n    <string name=\"read_aloud\">Đọc thành tiếng</string>\n    <string name=\"read_aloud_t\">Đang đọc</string>\n    <string name=\"read_aloud_s\">Nhấn để mở trình đọc</string>\n    <string name=\"audio_play\">Phát</string>\n    <string name=\"audio_play_t\">Đang phát</string>\n    <string name=\"audio_play_s\">Nhấn để mở trình phát</string>\n    <string name=\"audio_pause\">Tạm dừng</string>\n    <string name=\"text_return\">Quay lại</string>\n    <string name=\"refresh\">Làm mới</string>\n    <string name=\"start\">Bắt đầu</string>\n    <string name=\"stop\">Dừng</string>\n    <string name=\"pause\">Tạm dừng</string>\n    <string name=\"resume\">Tiếp tục</string>\n    <string name=\"set_timer\">Hẹn giờ</string>\n    <string name=\"read_aloud_pause\">Tạm dừng đọc</string>\n    <string name=\"read_aloud_timer\">Đang đọc (còn %d phút)</string>\n    <string name=\"playing_timer\">Đang phát (còn %d phút)</string>\n    <string name=\"ps_hide_navigation_bar\">Ẩn các nút ảo khi đọc</string>\n    <string name=\"pt_hide_navigation_bar\">Ẩn thanh điều hướng</string>\n    <string name=\"re_navigation_bar_color\">Màu thanh điều hướng</string>\n    <string name=\"scoring\">Đánh giá</string>\n    <string name=\"send_mail\">Email</string>\n    <string name=\"can_not_open\">Mở thất bại</string>\n    <string name=\"can_not_share\">Chia sẻ thất bại</string>\n    <string name=\"no_chapter\">Không có chương</string>\n    <string name=\"add_url\">Thêm URL</string>\n    <string name=\"add_book_url\">Thêm URL sách</string>\n    <string name=\"background\">Nền</string>\n    <string name=\"author\">Tác giả</string>\n    <string name=\"author_show\">Tác giả: %s</string>\n    <string name=\"aloud_stop\">Dừng đọc</string>\n    <string name=\"clear_cache\">Xóa bộ đệm</string>\n    <string name=\"clear_cache_success\">Đã xóa bộ đệm</string>\n    <string name=\"action_save\">Lưu</string>\n    <string name=\"edit_source\">Sửa nguồn</string>\n    <string name=\"edit_book_source\">Sửa nguồn sách</string>\n    <string name=\"disable_book_source\">Vô hiệu hóa nguồn sách</string>\n    <string name=\"add_book_source\">Thêm nguồn sách</string>\n    <string name=\"add_rss_source\">Thêm nguồn RSS</string>\n    <string name=\"book_file_selector\">Thêm sách</string>\n    <string name=\"scan_book_source\">Quét</string>\n    <string name=\"copy_source\">Sao chép nguồn</string>\n    <string name=\"paste_source\">Dán nguồn</string>\n    <string name=\"source_rule_s\">Mô tả quy tắc nguồn</string>\n    <string name=\"check_update\">Kiểm tra cập nhật</string>\n    <string name=\"camera_scan\">Quét mã QR</string>\n    <string name=\"scan_image\">Quét ảnh cục bộ</string>\n    <string name=\"rule_summary\">Mô tả quy tắc</string>\n    <string name=\"share\">Chia sẻ</string>\n    <string name=\"share_app\">Chia sẻ tới</string>\n    <string name=\"flow_sys\">Theo hệ thống</string>\n    <string name=\"add\">Thêm</string>\n    <string name=\"import_book_source\">Nhập nguồn sách</string>\n    <string name=\"import_local\">Nhập từ máy</string>\n    <string name=\"import_on_line\">Nhập trực tuyến</string>\n    <string name=\"replace_rule_title\">Thay thế</string>\n    <string name=\"replace_rule_edit\">Sửa quy tắc thay thế</string>\n    <string name=\"replace_rule\">Mẫu</string>\n    <string name=\"replace_to\">Thay thế</string>\n    <string name=\"img_cover\">Bìa</string>\n    <string name=\"book\">Sách</string>\n    <string name=\"volume_key_page\">Phím âm lượng lật trang</string>\n    <string name=\"mouse_wheel_page\">Con lăn chuột lật trang</string>\n    <string name=\"click_turn_page\">Chạm màn hình lật trang</string>\n    <string name=\"page_anim\">Hiệu ứng lật trang</string>\n    <string name=\"book_page_anim\">Hiệu ứng lật trang (sách này)</string>\n    <string name=\"keep_light\">Giữ màn hình sáng</string>\n    <string name=\"back\">Quay lại</string>\n    <string name=\"menu\">Menu</string>\n    <string name=\"adjust\">Điều chỉnh</string>\n    <string name=\"scroll_bar\">Thanh cuộn</string>\n    <string name=\"clear_all_content\">Xóa bộ đệm sẽ xóa tất cả các chương đã lưu. Bạn có chắc chắn muốn xóa không?</string>\n    <string name=\"book_source_share_url\">Chia sẻ nguồn sách</string>\n    <string name=\"replace_rule_summary\">Tên quy tắc</string>\n    <string name=\"replace_rule_invalid\">Quy tắc mẫu trống hoặc không tuân thủ quy cách regex.</string>\n    <string name=\"select_action\">Hành động chọn</string>\n    <string name=\"select_all\">Chọn tất cả</string>\n    <string name=\"select_all_count\">Chọn tất cả (%1$d/%2$d)</string>\n    <string name=\"select_cancel_count\">Hủy chọn tất cả (%1$d/%2$d)</string>\n    <string name=\"dark_theme\">Chế độ tối</string>\n    <string name=\"welcome\">Trang chào mừng</string>\n    <string name=\"download_start\">Bắt đầu tải</string>\n    <string name=\"download_cancel\">Hủy tải</string>\n    <string name=\"no_download\">Không có tải xuống</string>\n    <string name=\"download_count\">Đã tải %1$d/%2$d</string>\n    <string name=\"import_select_book\">Nhập sách đã chọn</string>\n    <string name=\"threads_num_title\">Số luồng đồng thời</string>\n    <string name=\"change_icon\">Thay đổi biểu tượng</string>\n    <string name=\"remove_from_bookshelf\">Xóa khỏi tủ sách</string>\n    <string name=\"start_read\">Bắt đầu đọc</string>\n    <string name=\"data_loading\">Đang tải dữ liệu…</string>\n    <string name=\"load_error_retry\">Tải thất bại, chạm để thử lại</string>\n    <string name=\"book_intro\">Mô tả sách</string>\n    <string name=\"intro_show\">Mô tả: %s</string>\n    <string name=\"intro_show_null\">Mô tả: không có giới thiệu</string>\n    <string name=\"open_from_other\">Mở sách từ bên ngoài</string>\n    <string name=\"origin_show\">Nguồn: %s</string>\n    <string name=\"import_replace_rule\">Nhập quy tắc thay thế</string>\n    <string name=\"import_replace_rule_on_line\">Nhập quy tắc trực tuyến</string>\n    <string name=\"check_update_interval\">Khoảng thời gian kiểm tra cập nhật</string>\n    <string name=\"bookshelf_px_0\">Theo danh sách gần đây</string>\n    <string name=\"bookshelf_px_1\">Theo thời gian cập nhật</string>\n    <string name=\"bookshelf_px_2\">Theo tên sách</string>\n    <string name=\"bookshelf_px_3\">Sắp xếp thủ công</string>\n    <string name=\"read_type\">Chiến lược đọc</string>\n    <string name=\"compose_type\">Bố cục chữ</string>\n    <string name=\"del_select\">Xóa mục đã chọn</string>\n    <string name=\"del_msg\">Bạn có chắc chắn muốn xóa không?</string>\n    <string name=\"clear_font\">Phông chữ mặc định</string>\n    <string name=\"find_on_www\">Khám phá</string>\n    <string name=\"find_source_manage\">Quản lý khám phá</string>\n    <string name=\"find_empty\">Không có nội dung. Hãy vào Quản lý nguồn để thêm!</string>\n    <string name=\"searchHistory\">Lịch sử tìm kiếm</string>\n    <string name=\"clear\">Xóa</string>\n    <string name=\"showTitle\">Hiển thị tiêu đề sách trong văn bản</string>\n    <string name=\"refresh_default\">Đồng bộ nguồn sách</string>\n    <string name=\"no_last_chapter\">Không có chương mới nhất.</string>\n    <string name=\"showTimeBattery\">Hiển thị thời gian và pin</string>\n    <string name=\"showLine\">Hiển thị dải phân cách</string>\n    <string name=\"dark_status_icon\">Làm tối màu biểu tượng thanh trạng thái</string>\n    <string name=\"content\">Nội dung</string>\n    <string name=\"copy_text\">Sao chép</string>\n    <string name=\"download_all\">Tải tất cả</string>\n    <string name=\"content_sl\">Đây là một đoạn văn bản thử nghiệm, \\n\\u3000\\u3000 chỉ để cho bạn thấy hiệu ứng</string>\n    <string name=\"text_bg_style\">Màu và nền (nhấn giữ để tùy chỉnh)</string>\n    <string name=\"immersion_status_bar\">Thanh trạng thái chìm</string>\n    <string name=\"un_download\">Còn lại %d chương</string>\n    <string name=\"long_click_input_color\">Nhấn giữ để nhập giá trị màu</string>\n    <string name=\"loading\">Đang tải…</string>\n    <string name=\"group_zg\">Đang theo dõi</string>\n    <string name=\"group_yf\">Đang chờ</string>\n    <string name=\"bookmark\">Dấu trang</string>\n    <string name=\"bookmark_add\">Thêm vào dấu trang</string>\n    <string name=\"action_del\">Xóa</string>\n    <string name=\"load_over_time\">Hết thời gian tải</string>\n    <string name=\"join_group\">Theo dõi:%s</string>\n    <string name=\"copy_complete\">Sao chép thành công</string>\n    <string name=\"bookshelf_management\">Quản lý tủ sách</string>\n    <string name=\"clear_bookshelf_s\">Thao tác này sẽ xóa tất cả sách. Vui lòng cẩn thận.</string>\n    <string name=\"search_book_source\">Tìm kiếm nguồn sách</string>\n    <string name=\"search_rss_source\">Tìm kiếm nguồn RSS</string>\n    <string name=\"search_book_source_num\">Tìm kiếm (tổng cộng %d nguồn)</string>\n    <string name=\"chapter_list_size\">Chương (%d)</string>\n    <string name=\"text_bold\">In đậm</string>\n    <string name=\"text_font\">Phông chữ</string>\n    <string name=\"text\">Văn bản</string>\n    <string name=\"home_page\">Trang chủ</string>\n    <string name=\"right\">Phải</string>\n    <string name=\"left\">Trái</string>\n    <string name=\"bottom\">Dưới</string>\n    <string name=\"top\">Trên</string>\n    <string name=\"padding\">Đệm</string>\n    <string name=\"padding_top\">Đệm trên</string>\n    <string name=\"padding_bottom\">Đệm dưới</string>\n    <string name=\"padding_left\">Đệm trái</string>\n    <string name=\"padding_right\">Đệm phải</string>\n    <string name=\"check_book_source\">Kiểm tra nguồn sách</string>\n    <string name=\"check_select_source\">Kiểm tra nguồn đã chọn</string>\n    <string name=\"progress_show\">%1$s      Tiến độ %2$d/%3$d</string>\n    <string name=\"tts_fix\">Vui lòng cài đặt và chọn TTS tiếng Trung!</string>\n    <string name=\"tts_init_failed\">Khởi tạo TTS thất bại!</string>\n    <string name=\"jf_convert\">Chuyển đổi Giản thể</string>\n    <string name=\"jf_convert_o\">Tắt</string>\n    <string name=\"jf_convert_f\">Giản thể sang Phồn thể</string>\n    <string name=\"jf_convert_j\">Phồn thể sang Giản thể</string>\n    <string name=\"page_mode\">Chế độ lật trang</string>\n    <string name=\"nb_file_sub_count\">%1$d mục</string>\n    <string name=\"nb_file_path\">Bộ nhớ:</string>\n    <string name=\"nb_file_add_shelf\">Thêm vào tủ sách</string>\n    <string name=\"nb_file_add_shelves\">Thêm vào tủ sách (%1$d)</string>\n    <string name=\"nb_file_add_succeed\">Đã thêm thành công %1$d sách</string>\n    <string name=\"fonts_folder\">Vui lòng đặt tệp phông chữ vào thư mục Fonts của thư mục gốc bộ nhớ và chọn lại</string>\n    <string name=\"default_font\">Phông chữ mặc định</string>\n    <string name=\"select_font\">Chọn phông chữ</string>\n    <string name=\"text_size\">Cỡ chữ</string>\n    <string name=\"line_size\">Khoảng cách dòng</string>\n    <string name=\"paragraph_size\">Khoảng cách đoạn</string>\n    <string name=\"to_top\">Lên đầu</string>\n    <string name=\"selection_to_top\">Chọn lên đầu</string>\n    <string name=\"to_bottom\">Xuống cuối</string>\n    <string name=\"selection_to_bottom\">Chọn xuống cuối</string>\n    <string name=\"auto_expand_find\">Tự động mở rộng Khám phá</string>\n    <string name=\"default_expand_first\">Mặc định mở rộng Khám phá đầu tiên.</string>\n    <string name=\"threads_num\">Số luồng hiện tại %s</string>\n    <string name=\"read_aloud_speed\">Tốc độ đọc</string>\n    <string name=\"auto_next_page\">Tự động cuộn</string>\n    <string name=\"auto_next_page_stop\">Dừng tự động cuộn</string>\n    <string name=\"auto_next_page_speed\">Tốc độ tự động cuộn</string>\n    <string name=\"book_info\">Thông tin sách</string>\n    <string name=\"book_info_edit\">Sửa thông tin sách</string>\n    <string name=\"ps_default_read\">Sử dụng Tủ sách làm trang bắt đầu</string>\n    <string name=\"pt_default_read\">Tự động chuyển đến danh sách Gần đây</string>\n    <string name=\"replace_scope\">Đối tượng thay thế. Tên sách hoặc URL nguồn có sẵn</string>\n    <string name=\"menu_action_group\">Nhóm</string>\n    <string name=\"download_path\">Đường dẫn bộ đệm</string>\n    <string name=\"sys_file_picker\">Bộ chọn tệp hệ thống</string>\n    <string name=\"new_version\">Phiên bản mới</string>\n    <string name=\"download_update\">Tải cập nhật</string>\n    <string name=\"volume_key_page_on_play\">Phím âm lượng lật trang khi đọc</string>\n    <string name=\"tip_margin_change\">Điều chỉnh lề</string>\n    <string name=\"allow_update\">Kích hoạt cập nhật</string>\n    <string name=\"disable_update\">Vô hiệu hóa cập nhật</string>\n    <string name=\"split_long_chapter\">Chia chương quá dài</string>\n    <string name=\"need_more_time_load_content\">Khi nội dung quá dài, có thể mất nhiều thời gian hơn để tải văn bản.</string>\n    <string name=\"revert_selection\">Đảo ngược</string>\n    <string name=\"search_book_key\">Tìm tên sách/tác giả</string>\n    <string name=\"debug_hint\">Tên sách, Tác giả, URL</string>\n    <string name=\"faq\">Câu hỏi thường gặp</string>\n    <string name=\"pt_show_all_find\">Hiển thị tất cả Khám phá</string>\n    <string name=\"ps_show_all_find\">Hiển thị Khám phá của nguồn đã chọn nếu đóng</string>\n    <string name=\"update_toc\">Cập nhật chương</string>\n    <string name=\"txt_toc_rule\">Quy tắc chương Txt</string>\n    <string name=\"select_section_export\">Chọn một số chương để xuất</string>\n    <string name=\"set_charset\">Mã hóa văn bản</string>\n    <string name=\"swap_sort\">Thứ tự tăng/giảm dần</string>\n    <string name=\"sort\">Sắp xếp</string>\n    <string name=\"sort_auto\">Sắp xếp tự động</string>\n    <string name=\"sort_manual\">Sắp xếp thủ công</string>\n    <string name=\"sort_default\">Sắp xếp mặc định</string>\n    <string name=\"sort_by_name\">Sắp xếp theo tên</string>\n    <string name=\"go_to_top\">Cuộn lên đầu</string>\n    <string name=\"go_to_bottom\">Cuộn xuống cuối</string>\n    <string name=\"read_y\">Đã đọc: %s</string>\n    <string name=\"pursue_more\">Đang chờ cập nhật</string>\n    <string name=\"fattening\">Đang chờ thêm</string>\n    <string name=\"finish\">Đã hoàn thành</string>\n    <string name=\"all_book\">Tất cả</string>\n    <string name=\"pursue_more_book\">Sách đang chờ cập nhật</string>\n    <string name=\"fattening_book\">Sách đang chờ thêm chương</string>\n    <string name=\"finish_book\">Sách đã hoàn thành</string>\n    <string name=\"local_book\">Sách cục bộ</string>\n    <string name=\"status_bar_immersion\">Màu thanh trạng thái trở nên trong suốt</string>\n    <string name=\"imm_navigation_bar\">Thanh điều hướng chìm</string>\n    <string name=\"imm_navigation_bar_s\">Thanh điều hướng trở nên trong suốt</string>\n    <string name=\"add_to_bookshelf\">Thêm vào tủ sách</string>\n    <string name=\"continue_read\">Tiếp tục đọc</string>\n    <string name=\"cover_path\">Đường dẫn bìa</string>\n    <string name=\"page_anim_cover\">Che phủ</string>\n    <string name=\"page_anim_slide\">Trượt</string>\n    <string name=\"page_anim_simulation\">Mô phỏng</string>\n    <string name=\"page_anim_scroll\">Cuộn</string>\n    <string name=\"page_anim_none\">Không có</string>\n    <string name=\"disable_manga_page_anim\">Tắt hiệu ứng lật trang</string>\n    <string name=\"up_change_source_last_chapter_t\">Cập nhật chương mới nhất sau khi đổi nguồn trong nền</string>\n    <string name=\"up_change_source_last_chapter_s\">Nếu được kích hoạt, cập nhật sẽ bắt đầu sau 1 phút khi phần mềm chạy</string>\n    <string name=\"behavior_main_t\">Tự động ẩn thanh công cụ</string>\n    <string name=\"behavior_main_s\">Thanh công cụ sẽ tự động ẩn khi cuộn Tủ sách</string>\n    <string name=\"login\">Đăng nhập</string>\n    <string name=\"login_source\">Đăng nhập %s</string>\n    <string name=\"success\">Thành công</string>\n    <string name=\"source_no_login\">Nguồn hiện tại chưa được cấu hình với địa chỉ đăng nhập</string>\n    <string name=\"no_prev_page\">Không có trang trước</string>\n    <string name=\"no_next_page\">Không có trang sau</string>\n\n    <!-- source start-->\n    <string name=\"source_name\">Tên nguồn(sourceName)</string>\n    <string name=\"source_url\">URL nguồn(sourceUrl)</string>\n    <string name=\"source_group\">Nhóm nguồn(sourceGroup)</string>\n    <string name=\"diy_source_group\">Nhóm nguồn tùy chỉnh</string>\n    <string name=\"diy_edit_source_group\">Nhập tên nhóm nguồn tùy chỉnh</string>\n    <string name=\"concurrent_rate\">Tỷ lệ đồng thời(concurrentRate)</string>\n    <string name=\"sort_url\">URL sắp xếp(sortUrl)</string>\n    <string name=\"login_url\">URL đăng nhập(loginUrl)</string>\n    <string name=\"login_ui\">Giao diện đăng nhập(loginUi)</string>\n    <string name=\"login_check_js\">Kiểm tra đăng nhập js(loginCheckJs)</string>\n    <string name=\"comment\">Bình luận nguồn(sourceComment)</string>\n    <string name=\"r_search_url\">URL tìm kiếm(url)</string>\n    <string name=\"r_find_url\">URL khám phá(url)</string>\n    <string name=\"r_book_list\">Danh sách sách(bookList)</string>\n    <string name=\"r_book_name\">Tên sách(name)</string>\n    <string name=\"r_book_url\">URL trang chi tiết(bookUrl)</string>\n    <string name=\"r_author\">Tác giả(author)</string>\n    <string name=\"rule_book_kind\">Phân loại(kind)</string>\n    <string name=\"rule_book_intro\">Giới thiệu(intro)</string>\n    <string name=\"rule_cover_url\">URL bìa(coverUrl)</string>\n    <string name=\"rule_last_chapter\">Chương cuối(lastChapter)</string>\n    <string name=\"rule_word_count\">Số từ(wordCount)</string>\n    <string name=\"book_url_pattern\">Mẫu URL sách(bookUrlPattern)</string>\n    <string name=\"rule_book_info_init\">Khởi tạo thông tin sách(bookInfoInit)</string>\n    <string name=\"rule_toc_url\">URL mục lục(tocUrl)</string>\n    <string name=\"rule_can_re_name\">Có thể đổi tên(canReName)</string>\n    <string name=\"rule_next_toc_url\">URL mục lục tiếp theo(nextTocUrl)</string>\n    <string name=\"rule_chapter_list\">Danh sách chương(chapterList)</string>\n    <string name=\"rule_chapter_name\">Tên chương(chapterName)</string>\n    <string name=\"rule_chapter_url\">URL chương(chapterUrl)</string>\n    <string name=\"rule_is_volume\">Là tập(isVolume)</string>\n    <string name=\"rule_is_vip\">Là VIP(isVip)</string>\n    <string name=\"rule_update_time\">Thông tin chương(ChapterInfo)</string>\n    <string name=\"pre_update_js\">Js trước khi cập nhật(preUpdateJs)</string>\n    <string name=\"rule_book_content\">Nội dung(content)</string>\n    <string name=\"rule_next_content\">URL nội dung tiếp theo(nextContentUrl)</string>\n    <string name=\"rule_web_js\">WebView js(webJs)</string>\n    <string name=\"rule_source_regex\">Regex nguồn(sourceRegex)</string>\n    <string name=\"rule_replace_regex\">Regex thay thế(replaceRegex)</string>\n    <string name=\"rule_image_style\">Kiểu hình ảnh(imageStyle)</string>\n    <string name=\"rule_pay_action\">Hành động thanh toán(payAction)</string>\n    <string name=\"source_icon\">Biểu tượng nguồn(sourceIcon)</string>\n    <string name=\"r_articles\">Quy tắc bài viết(ruleArticles)</string>\n    <string name=\"r_next\">Quy tắc bài viết tiếp theo(ruleNextArticles)</string>\n    <string name=\"r_title\">Quy tắc tiêu đề(ruleTitle)</string>\n    <string name=\"r_guid\">Quy tắc guid(ruleGuid)</string>\n    <string name=\"r_date\">Quy tắc ngày xuất bản(rulePubDate)</string>\n    <string name=\"r_categories\">Quy tắc danh mục(ruleCategories)</string>\n    <string name=\"r_description\">Quy tắc mô tả(ruleDescription)</string>\n    <string name=\"r_image\">Quy tắc hình ảnh(ruleImage)</string>\n    <string name=\"r_content\">Quy tắc nội dung(ruleContent)</string>\n    <string name=\"r_style\">Kiểu(style)</string>\n    <string name=\"r_link\">Quy tắc liên kết(ruleLink)</string>\n    <string name=\"r_inject_js\">js tiêm vào(injectJs)</string>\n    <string name=\"check_key_word\">Kiểm tra từ khóa(checkKeyWord)</string>\n    <string name=\"rule_actions\">Hành động(actions)</string>\n    <string name=\"rule_is_pay\">Là trả phí(isPay)</string>\n    <!-- source end-->\n\n    <!--error string start-->\n    <string name=\"error_no_source\">Không có nguồn</string>\n    <string name=\"error_get_book_info\">Không lấy được thông tin sách</string>\n    <string name=\"error_get_content\">Không lấy được nội dung</string>\n    <string name=\"error_get_chapter_list\">Không lấy được danh sách chương</string>\n    <string name=\"error_get_web_content\">Không thể truy cập trang web: %s</string>\n    <string name=\"error_read_file\">Không thể đọc tệp</string>\n    <string name=\"error_load_toc\">Không thể tải danh sách chương</string>\n    <string name=\"error_get_data\">Không lấy được dữ liệu</string>\n    <string name=\"error_load_msg\">Không tải được\\n%s</string>\n    <string name=\"net_error_10001\">Không có mạng</string>\n    <string name=\"net_error_10002\">Hết thời gian chờ kết nối mạng</string>\n    <string name=\"net_error_10003\">Phân tích dữ liệu thất bại</string>\n    <!--error string end-->\n\n    <string name=\"source_http_header\">Tiêu đề HTTP</string>\n    <string name=\"debug_source\">Nguồn gỡ lỗi</string>\n    <string name=\"import_by_qr_code\">Nhập từ mã QR</string>\n    <string name=\"share_selected_source\">Chia sẻ nguồn đã chọn</string>\n    <string name=\"scan_qr_code\">Quét mã QR</string>\n    <string name=\"click_on_selected_show_menu\">Chạm để hiển thị Menu khi được chọn</string>\n    <string name=\"theme\">Chủ đề</string>\n    <string name=\"theme_mode\">Chế độ chủ đề</string>\n    <string name=\"theme_mode_desc\">Chọn chủ đề bạn muốn</string>\n    <string name=\"join_qq_group\">Tham gia nhóm QQ</string>\n    <string name=\"bg_image_per\">Đặt ảnh nền cần quyền truy cập bộ nhớ</string>\n    <string name=\"input_book_source_url\">Nhập địa chỉ nguồn sách</string>\n    <string name=\"del_file\">Xóa tệp</string>\n    <string name=\"del_file_success\">Đã xóa tệp</string>\n    <string name=\"sure_del_file\">Bạn có chắc chắn muốn xóa tệp này không?</string>\n    <string name=\"files_tree\">Thư mục</string>\n    <string name=\"intelligent_import\">Nhập thông minh</string>\n    <string name=\"discovery\">Khám phá</string>\n    <string name=\"switch_display_style\">Chuyển đổi kiểu hiển thị</string>\n    <string name=\"import_per\">Nhập sách cục bộ cần quyền truy cập bộ nhớ</string>\n    <string name=\"night_theme\">Chủ đề tối</string>\n    <string name=\"eink_theme\">Chế độ E-Ink</string>\n    <string name=\"eink_theme_desc\">Tối ưu hóa cho thiết bị E-ink</string>\n    <string name=\"get_storage_per\">cần quyền truy cập bộ nhớ</string>\n    <string name=\"double_click_exit\">Chạm lần nữa để thoát chương trình</string>\n    <string name=\"import_book_per\">Nhập sách cục bộ cần quyền truy cập bộ nhớ</string>\n    <string name=\"network_connection_unavailable\">Kết nối mạng không khả dụng</string>\n    <string name=\"yes\">Có</string>\n    <string name=\"no\">Không</string>\n    <string name=\"sure\">OK</string>\n    <string name=\"sure_del\">Bạn có chắc chắn muốn xóa không?</string>\n    <string name=\"sure_del_any\">Bạn có chắc chắn muốn xóa %s không?</string>\n    <string name=\"sure_del_all_book\">Bạn có chắc chắn muốn xóa tất cả sách không？</string>\n    <string name=\"sure_del_download_book\">Bạn có muốn xóa các chương sách đã tải xuống cùng lúc không?</string>\n    <string name=\"qr_per\">Quét mã QR cần quyền truy cập máy ảnh</string>\n    <string name=\"aloud_can_not_auto_page\">Đang đọc, không thể tự động lật trang</string>\n    <string name=\"input_charset\">Nhập mã hóa</string>\n    <string name=\"text_chapter_list_rule\">Quy tắc chương Txt Regex</string>\n    <string name=\"open_local_book_per\">Mở sách cục bộ cần quyền truy cập bộ nhớ</string>\n    <string name=\"no_book_name\">Không có tên sách</string>\n    <string name=\"input_replace_url\">Nhập URL quy tắc thay thế</string>\n    <string name=\"get_book_list_success\">Lấy danh sách tìm kiếm thành công %d</string>\n    <string name=\"non_null_name_url\">Tên và URL không được để trống</string>\n    <string name=\"gallery\">Thư viện</string>\n    <string name=\"get_ali_pay_hb\">Nhận bao lì xì AliPay</string>\n    <string name=\"non_update_url\">Không có địa chỉ cập nhật</string>\n    <string name=\"check_host_cookie\">Đang mở trang chủ, sẽ tự động quay lại trang bắt đầu sau khi thành công</string>\n    <string name=\"click_check_after_success\">Sau khi đăng nhập thành công, vui lòng chạm vào biểu tượng ở góc trên bên phải để kiểm tra truy cập trang chủ</string>\n    <string name=\"chapter\">Chương</string>\n    <string name=\"to\">Đến</string>\n    <string name=\"use_regex\">Sử dụng Regex</string>\n    <string name=\"text_indent\">Thụt lề</string>\n    <string name=\"indent_0\">Không có</string>\n    <string name=\"indent_1\">Thụt lề 1 ký tự</string>\n    <string name=\"indent_2\">Thụt lề 2 ký tự</string>\n    <string name=\"indent_3\">Thụt lề 3 ký tự</string>\n    <string name=\"indent_4\">Thụt lề 4 ký tự</string>\n    <string name=\"select_folder\">Chọn một thư mục</string>\n    <string name=\"select_file\">Chọn một tệp</string>\n    <string name=\"no_find\">Không có Khám phá, bạn có thể thêm nó trong Nguồn sách</string>\n    <string name=\"restore_default\">Khôi phục mặc định</string>\n    <string name=\"set_download_per\">Đường dẫn bộ đệm tùy chỉnh cần quyền truy cập bộ nhớ</string>\n    <string name=\"black\">Đen</string>\n    <string name=\"content_empty\">Không có nội dung</string>\n    <string name=\"on_change_source\">Đang đổi nguồn, vui lòng đợi</string>\n    <string name=\"chapter_list_empty\">Danh sách chương trống</string>\n    <string name=\"text_letter_spacing\">Khoảng cách chữ</string>\n\n    <string name=\"source_tab_base\">Cơ bản</string>\n    <string name=\"source_tab_search\">Tìm kiếm</string>\n    <string name=\"source_tab_find\">Khám phá</string>\n    <string name=\"source_tab_info\">Thông tin</string>\n    <string name=\"source_tab_toc\">Chương</string>\n    <string name=\"source_tab_content\">Nội dung</string>\n\n    <string name=\"e_ink_mode\">Chế độ E-Ink</string>\n    <string name=\"e_ink_mode_detail\">Xóa hoạt ảnh và tối ưu hóa trải nghiệm sử dụng sách E-paper</string>\n    <string name=\"web_menu\">Dịch vụ web</string>\n    <string name=\"web_port_title\">Cổng web</string>\n    <string name=\"web_port_summary\">Cổng hiện tại %s</string>\n    <string name=\"qr_share\">Chia sẻ mã QR</string>\n    <string name=\"str_share\">Chia sẻ chuỗi</string>\n    <string name=\"wifi_share\">Chia sẻ Wifi</string>\n    <string name=\"please_grant_storage_permission\">Vui lòng cấp quyền truy cập bộ nhớ</string>\n    <string name=\"fast_rewind\">Giảm tốc độ</string>\n    <string name=\"fast_forward\">Tăng tốc</string>\n    <string name=\"skip_previous\">Trước</string>\n    <string name=\"skip_next\">Sau</string>\n    <string name=\"music\">Âm nhạc</string>\n    <string name=\"audio\">Âm thanh</string>\n    <string name=\"is_enable\">Kích hoạt</string>\n    <string name=\"enable_js\">Kích hoạt js</string>\n    <string name=\"load_with_base_url\">Tải URL cơ sở</string>\n    <string name=\"all_source\">Tất cả nguồn</string>\n    <string name=\"cannot_empty\">Nội dung nhập không được để trống</string>\n    <string name=\"clear_find_cache\">Xóa bộ đệm Khám phá</string>\n    <string name=\"edit_find\">Sửa Khám phá</string>\n    <string name=\"change_icon_summary\">Chuyển đổi biểu tượng phần mềm hiển thị trên màn hình chính</string>\n    <string name=\"help\">Trợ giúp</string>\n    <string name=\"my\">Của tôi</string>\n    <string name=\"reading\">Đọc</string>\n    <string name=\"battery_show\">%d%%</string>\n    <string name=\"timer_m\">%d phút</string>\n    <string name=\"brightness_auto\">Độ sáng tự động %s</string>\n    <string name=\"read_aloud_by_page\">Đọc theo trang</string>\n    <string name=\"speak_engine\">Công cụ đọc</string>\n    <string name=\"bg_image\">Ảnh nền</string>\n    <string name=\"bg_color\">Màu nền</string>\n    <string name=\"text_color\">Màu chữ</string>\n    <string name=\"select_image\">Chọn một ảnh</string>\n    <string name=\"group_manage\">Quản lý nhóm</string>\n    <string name=\"group_select\">Chọn nhóm</string>\n    <string name=\"group_edit\">Sửa nhóm</string>\n    <string name=\"move_to_group\">Di chuyển đến nhóm</string>\n    <string name=\"add_group\">Thêm vào nhóm</string>\n    <string name=\"remove_group\">Xóa khỏi nhóm</string>\n    <string name=\"add_replace_rule\">Thay thế mới</string>\n    <string name=\"group\">Nhóm</string>\n    <string name=\"group_s\">Nhóm: %s</string>\n    <string name=\"toc_s\">Chương: %s</string>\n    <string name=\"enable_explore\">Kích hoạt Khám phá</string>\n    <string name=\"disable_explore\">Vô hiệu hóa Khám phá</string>\n    <string name=\"enable_selection\">Kích hoạt mục đã chọn</string>\n    <string name=\"disable_selection\">Vô hiệu hóa mục đã chọn</string>\n    <string name=\"export_selection\">Xuất mục đã chọn</string>\n    <string name=\"export\">Xuất</string>\n    <string name=\"load_toc\">Tải chương</string>\n    <string name=\"load_info\">Tải chi tiết sách</string>\n    <string name=\"tts\">TTS</string>\n    <string name=\"web_dav_pw\">Mật khẩu WebDav</string>\n    <string name=\"web_dav_pw_s\">Nhập mật khẩu WebDav của bạn</string>\n    <string name=\"web_dav_url_s\">Nhập địa chỉ máy chủ của bạn</string>\n    <string name=\"web_dav_url\">Địa chỉ máy chủ WebDav</string>\n    <string name=\"web_dav_account\">Tài khoản WebDav</string>\n    <string name=\"web_dav_account_s\">Nhập tài khoản WebDav của bạn</string>\n    <string name=\"rss_source\">Nguồn RSS</string>\n    <string name=\"rss_source_edit\">Sửa nguồn RSS</string>\n    <string name=\"screen\">Bộ lọc</string>\n    <string name=\"screen_find\">Search Discovery sources</string>\n    <string name=\"dur_pos\">Vị trí hiện tại：</string>\n    <string name=\"precision_search\">Tìm kiếm chính xác</string>\n    <string name=\"service_starting\">Đang khởi động dịch vụ</string>\n    <string name=\"empty\">Trống</string>\n    <string name=\"file_chooser\">Chọn tệp</string>\n    <string name=\"folder_chooser\">Chọn thư mục</string>\n    <string name=\"bottom_line\">HẾT RỒI!</string>\n    <string name=\"uri_to_path_fail\">Uri sang đường dẫn thất bại</string>\n    <string name=\"refresh_cover\">Làm mới bìa</string>\n    <string name=\"change_cover_source\">Đổi nguồn</string>\n    <string name=\"select_local_image\">Ảnh cục bộ</string>\n    <string name=\"book_type\">Loại:</string>\n    <string name=\"to_backstage\">Nền</string>\n    <string name=\"importing\">Đang nhập</string>\n    <string name=\"exporting\">Đang xuất</string>\n    <string name=\"custom_page_key\">Đặt nút lật trang</string>\n    <string name=\"prev_page_key\">Nút trang trước</string>\n    <string name=\"next_page_key\">Nút trang sau</string>\n    <string name=\"no_group\">Chưa nhóm</string>\n    <string name=\"prev_sentence\">Câu trước</string>\n    <string name=\"next_sentence\">Câu sau</string>\n    <string name=\"other_folder\">Thư mục khác</string>\n    <string name=\"text_too_long_qr_error\">Có quá nhiều từ để tạo mã QR</string>\n    <string name=\"share_rss_source\">Chia sẻ nguồn RSS</string>\n    <string name=\"share_book_source\">Chia sẻ nguồn sách</string>\n    <string name=\"auto_dark_mode\">Tự động chuyển chế độ tối</string>\n    <string name=\"auto_dark_mode_s\">Theo chế độ tối của hệ thống</string>\n    <string name=\"go_back\">Quay lại</string>\n    <string name=\"tone_colour\">Âm sắc đọc trực tuyến</string>\n    <string name=\"select_count\">(%1$d/%2$d)</string>\n    <string name=\"show_rss\">Hiển thị nguồn cấp RSS</string>\n    <string name=\"service_stop\">Dịch vụ đã dừng</string>\n    <string name=\"service_start\">Đang khởi động dịch vụ\\nKiểm tra thanh thông báo để biết chi tiết</string>\n    <string name=\"default_path\">Đường dẫn mặc định</string>\n    <string name=\"sys_folder_picker\">Bộ chọn thư mục hệ thống</string>\n    <string name=\"app_folder_picker\">Bộ chọn thư mục ứng dụng</string>\n    <string name=\"app_file_picker\">Bộ chọn tệp ứng dụng</string>\n    <string name=\"a10_permission_toast\">Android 10+ không thể đọc và ghi tệp do hạn chế quyền</string>\n    <string name=\"add_to_text_context_menu_s\">Nhấn giữ để hiển thị Legado·Tìm kiếm trong menu thao tác</string>\n    <string name=\"add_to_text_context_menu_t\">Thao tác văn bản hiển thị Tìm kiếm</string>\n    <string name=\"record_log\">Ghi nhật ký</string>\n    <string name=\"log\">Nhật ký</string>\n    <string name=\"chinese_converter\">Chuyển đổi Giản thể</string>\n    <string name=\"change_icon_error\">Biểu tượng là biểu tượng vector, không được hỗ trợ trước Android 8.0</string>\n    <string name=\"aloud_config\">Cài đặt đọc</string>\n    <string name=\"main_activity\">Trang bắt đầu</string>\n    <string name=\"selectText\">Nhấn giữ để chọn văn bản</string>\n    <string name=\"header\">Đầu trang</string>\n    <string name=\"main_body\">Nội dung</string>\n    <string name=\"footer\">Chân trang</string>\n    <string name=\"select_end\">Chọn kết thúc</string>\n    <string name=\"select_start\">Chọn bắt đầu</string>\n    <string name=\"share_layout\">Bố cục chia sẻ</string>\n    <string name=\"browser\">Trình duyệt</string>\n    <string name=\"import_default_rule\">Nhập quy tắc mặc định</string>\n    <string name=\"name\">Tên</string>\n    <string name=\"regex\">Regex</string>\n    <string name=\"example\">Ví dụ</string>\n    <string name=\"more_menu\">Menu khác</string>\n    <string name=\"reduce\">Trừ</string>\n    <string name=\"plus\">Cộng</string>\n    <string name=\"system_typeface\">Kiểu chữ hệ thống</string>\n    <string name=\"delete_book_file\">Xóa tệp nguồn</string>\n    <string name=\"text_default\">Mặc định</string>\n    <string name=\"default1\">Mặc định-1</string>\n    <string name=\"default2\">Mặc định-2</string>\n    <string name=\"default3\">Mặc định-3</string>\n    <string name=\"title\">Tiêu đề</string>\n    <string name=\"title_left\">Trái</string>\n    <string name=\"title_center\">Giữa</string>\n    <string name=\"title_hide\">Ẩn</string>\n    <string name=\"add_to_group\">Thêm vào nhóm</string>\n    <string name=\"save_image\">Lưu ảnh</string>\n    <string name=\"no_default_path\">Không có đường dẫn mặc định</string>\n    <string name=\"change_group\">Cài đặt nhóm</string>\n    <string name=\"view_toc\">Xem chương</string>\n    <string name=\"bar_elevation\">Bóng thanh điều hướng</string>\n    <string name=\"bar_elevation_s\">Kích thước bóng hiện tại (độ cao): %s</string>\n    <string name=\"btn_default_s\">Mặc định</string>\n    <string name=\"main_menu\">Menu chính</string>\n    <string name=\"request_permission\">Chạm để cấp quyền</string>\n    <string name=\"tip_local_perm_request_storage\">Legado cần quyền truy cập bộ nhớ, vui lòng chạm vào nút \"Cấp quyền\" bên dưới, hoặc vào \"Cài đặt\"-\"Quyền ứng dụng\"-để mở quyền yêu cầu. Nếu quyền vẫn không hoạt động, vui lòng chạm vào \"Chọn thư mục\" ở góc trên bên phải để sử dụng bộ chọn thư mục hệ thống.</string>\n    <string name=\"alouding_disable\">Không thể đọc văn bản đã chọn ở chế độ đọc toàn văn</string>\n    <string name=\"read_body_to_lh\">Mở rộng đến phần cắt</string>\n    <string name=\"toc_updateing\">Đang cập nhật chương</string>\n    <string name=\"media_button_on_exit_title\">Nút tai nghe luôn khả dụng</string>\n    <string name=\"media_button_on_exit_summary\">Nút tai nghe khả dụng ngay cả khi thoát ứng dụng.</string>\n    <string name=\"contributors\">Người đóng góp</string>\n    <string name=\"contact\">Liên hệ</string>\n    <string name=\"license\">Giấy phép</string>\n    <string name=\"other\">Khác</string>\n    <string name=\"official_account\">阅读</string>\n    <string name=\"follow_official_account\">Theo dõi tài khoản WeChat chính thức</string>\n    <string name=\"wechat\">WeChat</string>\n    <string name=\"thanks\">Sự ủng hộ của bạn là động lực để tôi cập nhật</string>\n    <string name=\"about_official_account\">Tài khoản chính thức [开源阅读]</string>\n    <string name=\"source_auto_changing\">Đang đổi nguồn</string>\n    <string name=\"click_to_apply\">Chạm để tham gia</string>\n    <string name=\"middle\">Giữa</string>\n    <string name=\"information\">Thông tin</string>\n    <string name=\"switchLayout\">Chuyển đổi bố cục</string>\n    <string name=\"text_font_weight_converter\">Chuyển đổi độ đậm của phông chữ</string>\n    <string name=\"full_screen_gestures_support\">Hỗ trợ cử chỉ toàn màn hình</string>\n    <string name=\"disable_return_key\">Vô hiệu hóa phím quay lại</string>\n\n    <!--color-->\n    <string name=\"primary\">Chính</string>\n    <string name=\"accent\">Nhấn</string>\n    <string name=\"background_color\">Màu nền</string>\n    <string name=\"navbar_color\">Màu thanh điều hướng</string>\n    <string name=\"day\">Ngày</string>\n    <string name=\"day_color_primary\">Ngày, Chính</string>\n    <string name=\"day_color_accent\">Ngày, Nhấn</string>\n    <string name=\"day_background_color\">Ngày, Màu nền</string>\n    <string name=\"day_navbar_color\">Ngày, Màu thanh điều hướng</string>\n    <string name=\"night\">Đêm</string>\n    <string name=\"night_primary\">Đêm, Chính</string>\n    <string name=\"night_accent\">Đêm, Nhấn</string>\n    <string name=\"night_background_color\">Đêm, Màu nền</string>\n    <string name=\"night_navbar_color\">Đêm, Màu thanh điều hướng</string>\n    <string name=\"auto_change_source\">Tự động đổi nguồn</string>\n    <string name=\"text_full_justify\">Văn bản căn đều</string>\n    <string name=\"text_bottom_justify\">Văn bản căn dưới</string>\n    <string name=\"auto_page_speed\">Tốc độ tự động cuộn</string>\n    <string name=\"sort_by_url\">Sắp xếp theo URL</string>\n    <string name=\"backup_summary\">Sao lưu cục bộ và WebDav đồng thời</string>\n    <string name=\"restore_summary\">Khôi phục từ WebDAV trước, nhấn giữ để khôi phục từ bản sao lưu cục bộ</string>\n    <string name=\"import_old_summary\">Chọn một thư mục sao lưu cũ</string>\n    <string name=\"enabled\">Đã bật</string>\n    <string name=\"disabled\">Đã tắt</string>\n    <string name=\"enabled_explore\">Đã bật khám phá</string>\n    <string name=\"disabled_explore\">Đã tắt khám phá</string>\n    <string name=\"starting_download\">Bắt đầu tải xuống</string>\n    <string name=\"already_in_download\">Sách này đã có trong danh sách tải xuống</string>\n    <string name=\"click_to_open\">Nhấn để mở</string>\n    <string name=\"follow_public_account_summary\">Theo dõi [开源阅读] để ủng hộ tôi bằng cách nhấp vào quảng cáo</string>\n    <string name=\"weChat_appreciation_code\">Mã boa WeChat</string>\n    <string name=\"alipay\">AliPay</string>\n    <string name=\"alipay_red_envelope_search_code\">Mã tìm kiếm bao lì xì AliPay</string>\n    <string name=\"alipay_red_envelope_copy\">537954522 Nhấn để sao chép</string>\n    <string name=\"alipay_red_envelope_qr_code\">Mã QR bao lì xì AliPay</string>\n    <string name=\"alipay_payment_qr_code\">Mã QR AliPay</string>\n    <string name=\"qq_collection_qr_code\">Mã QR bộ sưu tập QQ</string>\n    <string name=\"contributors_summary\">gedoor, Invinciblelee, Xwite, v.v. Kiểm tra trên github để biết chi tiết</string>\n    <string name=\"clear_cache_summary\">Xóa bộ đệm của sách và phông chữ đã tải xuống</string>\n    <string name=\"default_cover\">Bìa mặc định</string>\n    <string name=\"restore_ignore\">Danh sách bỏ qua</string>\n    <string name=\"restore_ignore_summary\">Bỏ qua một số nội dung khi khôi phục</string>\n    <string name=\"read_config\">Cài đặt giao diện đọc</string>\n    <string name=\"group_name\">Tên nhóm</string>\n    <string name=\"note_content\">Phần ghi chú</string>\n    <string name=\"replace_enable_default_t\">Kích hoạt quy tắc thay thế theo mặc định</string>\n    <string name=\"replace_enable_default_s\">Đối với sách mới được thêm vào</string>\n    <string name=\"select_restore_file\">Chọn tệp khôi phục</string>\n    <string name=\"day_background_too_dark\">Nền ban ngày không được quá tối!</string>\n    <string name=\"day_bottom_bar_too_dark\">Thanh dưới ban ngày không được quá tối!</string>\n    <string name=\"night_background_too_light\">Nền ban đêm không được quá sáng!</string>\n    <string name=\"night_bottom_bar_too_light\">Thanh dưới ban đêm không được quá sáng!</string>\n    <string name=\"accent_background_diff\">Cần có sự khác biệt giữa màu nhấn và màu nền</string>\n    <string name=\"accent_text_diff\">Cần có sự khác biệt giữa màu nhấn và màu chữ</string>\n    <string name=\"wrong_format\">Sai định dạng</string>\n    <string name=\"error\">Lỗi</string>\n    <string name=\"show_brightness_view\">Hiển thị widget độ sáng</string>\n    <string name=\"language\">Ngôn ngữ</string>\n    <string name=\"import_rss_source\">Nhập nguồn RSS</string>\n    <string name=\"donate_summary\">Sự ủng hộ của bạn làm cho ứng dụng này tốt hơn</string>\n    <string name=\"about_summary\">Tài khoản chính thức Wechat [开源阅读软件]</string>\n    <string name=\"read_record\">Lịch sử đọc</string>\n    <string name=\"del_read_record\">Xóa lịch sử đọc</string>\n    <string name=\"read_record_summary\">Tóm tắt lịch sử đọc</string>\n    <string name=\"local_tts\">TTS cục bộ</string>\n    <string name=\"thread_count\">Số luồng</string>\n    <string name=\"all_read_time\">Tổng thời gian đọc</string>\n    <string name=\"un_select_all\">Bỏ chọn tất cả</string>\n    <string name=\"import_str\">Nhập</string>\n    <string name=\"export_str\">Xuất</string>\n    <string name=\"save_theme_config\">Lưu cấu hình chủ đề</string>\n    <string name=\"save_day_theme_summary\">Lưu cấu hình chủ đề ban ngày</string>\n    <string name=\"save_night_theme_summary\">Lưu cấu hình chủ đề ban đêm</string>\n    <string name=\"theme_list\">Danh sách chủ đề</string>\n    <string name=\"theme_list_summary\">Lưu, Nhập, Chia sẻ chủ đề</string>\n    <string name=\"select_theme\">Chuyển đổi chủ đề mặc định</string>\n    <string name=\"sort_by_lastUpdateTime\">Sắp xếp theo thời gian cập nhật</string>\n    <string name=\"sort_by_respondTime\">Sắp xếp theo thời gian phản hồi</string>\n    <string name=\"search_content\">Tìm kiếm nội dung</string>\n    <string name=\"rss_source_empty\">Hiện tại trống!</string>\n    <string name=\"explore_empty\">Hiện tại trống!</string>\n    <string name=\"page_key_set_help\">Tập trung vào hộp nhập và nhấn một phím vật lý sẽ tự động nhập giá trị của phím, và nhiều phím sẽ được tự động phân tách bằng dấu phẩy.</string>\n    <string name=\"theme_name\">Tên chủ đề</string>\n    <string name=\"auto_clear_expired\">Tự động xóa lịch sử tìm kiếm đã hết hạn</string>\n    <string name=\"auto_clear_expired_summary\">Lịch sử tìm kiếm quá một ngày</string>\n    <string name=\"re_segment\">Phân đoạn lại</string>\n    <string name=\"style_name\">Tên kiểu:</string>\n    <string name=\"empty_msg_import_book\">Nhấp vào biểu tượng thư mục ở góc trên bên phải và chọn thư mục</string>\n    <string name=\"scan_folder\">Quét thông minh</string>\n    <string name=\"import_file_name\">Tên tệp đã nhập</string>\n    <string name=\"no_book\">Không có sách</string>\n    <string name=\"keep_original_name\">Giữ tên gốc</string>\n    <string name=\"click_regional_config\">Điều khiển cảm ứng màn hình</string>\n    <string name=\"close\">Đóng</string>\n    <string name=\"next_page\">Trang sau</string>\n    <string name=\"prev_page\">Trang trước</string>\n    <string name=\"non_action\">Không có</string>\n    <string name=\"body_title\">Tiêu đề</string>\n    <string name=\"show_hide\">Hiện/Ẩn</string>\n    <string name=\"header_footer\">Đầu trang &amp; Chân trang</string>\n    <string name=\"rule_subscription\">Đăng ký quy tắc</string>\n    <string name=\"rule_sub_empty_msg\">Thêm địa chỉ nhập quy tắc do các quản trị viên cung cấp\\nNhấn để nhập quy tắc sau khi thêm</string>\n    <string name=\"get_book_progress\">Kéo tiến độ từ đám mây</string>\n    <string name=\"cover_book_progress\">Ghi đè tiến độ trên đám mây</string>\n    <string name=\"current_progress_exceeds_cloud\">Tiến độ hiện tại vượt quá tiến độ trên đám mây. Bạn có muốn đồng bộ không?</string>\n    <string name=\"cloud_progress_exceeds_current\">Tiến độ trên đám mây vượt quá tiến độ hiện tại. Bạn có muốn đồng bộ không?</string>\n    <string name=\"sync_book_progress_t\">Đồng bộ tiến độ đọc</string>\n    <string name=\"sync_book_progress_s\">Đồng bộ tiến độ đọc khi vào/ra giao diện đọc</string>\n    <string name=\"sync_book_progress_plus_t\">Tăng cường đồng bộ</string>\n    <string name=\"sync_book_progress_plus_s\">Đồng bộ lại tiến trình trên đám mây khi quay lại trang (tắt màn hình, trở lại từ nền, v.v.) hoặc khi mạng khả dụng. Sẽ yêu cầu xác nhận trước khi đồng bộ tiến trình mới.</string>\n    <string name=\"sync_book_progress_success\">Đồng bộ tiến độ thành công</string>\n    <string name=\"create_bookmark_error\">Không thể tạo dấu trang</string>\n    <string name=\"single_url\">URL đơn</string>\n    <string name=\"export_bookshelf\">Xuất danh sách sách</string>\n    <string name=\"import_bookshelf\">Nhập danh sách sách</string>\n    <string name=\"pre_download\">Tải trước</string>\n    <string name=\"pre_download_m\">Tải trước %s trang</string>\n    <string name=\"pre_download_s\">Tải trước %s chương</string>\n    <string name=\"is_enabled\">Đã bật</string>\n    <string name=\"background_image\">Ảnh nền</string>\n    <string name=\"background_image_blurring\">Làm mờ ảnh nền</string>\n    <string name=\"background_image_blurring_radius\">Bán kính làm mờ</string>\n    <string name=\"background_image_hint\">Tắt khi là 0, phạm vi bật từ 1 đến 25\\nBán kính càng lớn, hiệu ứng làm mờ càng mạnh</string>\n    <string name=\"copy_book_url\">Sao chép URL sách</string>\n    <string name=\"copy_toc_url\">Sao chép URL chương</string>\n    <string name=\"export_folder\">Thư mục xuất</string>\n    <string name=\"export_charset\">Mã hóa văn bản xuất</string>\n    <string name=\"export_to_web_dav\">Xuất sang WebDav</string>\n    <string name=\"reverse_content\">Đảo ngược nội dung</string>\n    <string name=\"debug\">Gỡ lỗi</string>\n    <string name=\"crash_log\">Nhật ký sự cố</string>\n    <string name=\"use_zh_layout\">Sử dụng ngắt dòng tiếng Trung tùy chỉnh</string>\n    <string name=\"image_style\">Kiểu hình ảnh</string>\n    <string name=\"system_tts\">TTS hệ thống</string>\n    <string name=\"export_type\">Định dạng tệp xuất</string>\n    <string name=\"checkAuthor\">Kiểm tra theo tác giả</string>\n    <string name=\"url_already\">URL này đã được đăng ký</string>\n    <string name=\"high_brush_title\">Tốc độ làm mới màn hình cao</string>\n    <string name=\"high_brush_summary\">Sử dụng tốc độ làm mới màn hình tối đa</string>\n    <string name=\"export_all\">Xuất tất cả</string>\n    <string name=\"complete\">Hoàn thành</string>\n    <string name=\"show_unread\">Hiển thị cờ chưa đọc</string>\n    <string name=\"use_default_cover\">Luôn hiển thị bìa mặc định</string>\n    <string name=\"use_default_cover_s\">Luôn hiển thị bìa mặc định, không hiển thị bìa từ mạng</string>\n    <string name=\"search_src\">Tìm kiếm mã nguồn</string>\n    <string name=\"boo_src\">Mã nguồn sách</string>\n    <string name=\"toc_src\">Mã nguồn chương</string>\n    <string name=\"content_src\">Mã nguồn nội dung</string>\n    <string name=\"list_src\">Liệt kê mã nguồn</string>\n    <string name=\"title_font_size\">Cỡ chữ</string>\n    <string name=\"title_margin_top\">Lề trên</string>\n    <string name=\"title_margin_bottom\">Lề dưới</string>\n    <string name=\"show\">Hiển thị</string>\n    <string name=\"hide\">Ẩn</string>\n    <string name=\"hide_when_status_bar_show\">Ẩn khi thanh trạng thái hiển thị</string>\n    <string name=\"reverse_toc\">Đảo ngược mục lục</string>\n    <string name=\"show_discovery\">Hiển thị Khám phá</string>\n    <string name=\"style\">Kiểu</string>\n    <string name=\"group_style\">Kiểu nhóm</string>\n    <string name=\"export_file_name\">Xuất tên tệp</string>\n    <string name=\"reset\">Đặt lại</string>\n    <string name=\"null_url\">URL trống</string>\n    <string name=\"dict\">Từ điển</string>\n    <string name=\"unknown_error\">lỗi không xác định</string>\n    <string name=\"export_no_chapter_name\">Không xuất tên chương</string>\n    <string name=\"autobackup_fail\">Tự động sao lưu thất bại\\n%s</string>\n    <string name=\"end\">kết thúc</string>\n    <string name=\"custom_group_summary\">Tắt thay thế nhóm / Bật thêm nhóm</string>\n    <string name=\"pref_media_button_per_next\">Nút phương tiện • Trước | Sau</string>\n    <string name=\"pref_media_button_per_next_summary\">Đoạn trước|Đoạn sau/Chương trước|Chương sau</string>\n    <string name=\"read_aloud_by_page_summary\">Lật trang kịp thời, có tạm dừng khi lật trang</string>\n    <string name=\"check_source_show_debug_message\">Kiểm tra nguồn sách hiển thị thông báo gỡ lỗi</string>\n    <string name=\"check_source_show_debug_message_summary\">Hiển thị trạng thái mạng và dấu thời gian trong quá trình kiểm tra nguồn</string>\n    <string name=\"need_login\">cần đăng nhập</string>\n    <string name=\"pref_cronet_summary\">Sử dụng thành phần mạng Cronet</string>\n    <string name=\"anti_alias\">Chống răng cưa</string>\n    <string name=\"pref_anti_alias_summary\">Chống răng cưa khi vẽ ảnh</string>\n    <string name=\"upload_url\">tải lên url</string>\n    <string name=\"download_url_rule\">Quy tắc URL tải xuống(downloadUrls)</string>\n    <string name=\"respondTime\">Thời gian phản hồi: %1$d ms</string>\n    <string name=\"export_success\">xuất thành công</string>\n    <string name=\"path\">đường dẫn</string>\n    <string name=\"direct_link_upload_rule\">Quy tắc tải lên liên kết trực tiếp</string>\n    <string name=\"direct_link_upload_rule_summary\">Được sử dụng để tạo URL liên kết trực tiếp khi xuất nguồn sách, danh sách sách</string>\n    <string name=\"direct_link_upload_config\">Cấu hình tải lên liên kết trực tiếp</string>\n    <string name=\"copy_play_url\">Sao chép URL phát</string>\n    <string name=\"set_source_variable\">Đặt biến nguồn</string>\n    <string name=\"set_book_variable\">Đặt biến sách</string>\n    <string name=\"summary\">tóm tắt</string>\n    <string name=\"cover_config\">cấu hình bìa</string>\n    <string name=\"cover_config_summary\">Quy tắc bìa chung và kiểu bìa mặc định</string>\n    <string name=\"cover_show_name\">hiển thị tên</string>\n    <string name=\"cover_show_name_summary\">Tên sách hiển thị trên bìa</string>\n    <string name=\"cover_show_author\">hiển thị tác giả</string>\n    <string name=\"cover_show_author_summary\">Tác giả hiển thị trên bìa</string>\n    <string name=\"read_aloud_prev_paragraph\">Đọc to đoạn trước</string>\n    <string name=\"read_aloud_next_paragraph\">Đọc to đoạn sau</string>\n    <string name=\"wait_download\">đợi tải xuống</string>\n    <string name=\"download_success\">tải xuống thành công</string>\n    <string name=\"download_error\">tải xuống thất bại</string>\n    <string name=\"downloading\">đang tải xuống</string>\n    <string name=\"unknown_state\">không xác định</string>\n    <string name=\"disable_source\">vô hiệu hóa nguồn</string>\n    <string name=\"delete_source\">xóa nguồn</string>\n    <string name=\"chapter_pay\">trả phí</string>\n    <string name=\"double_page_horizontal\">Trang kép trên máy tính bảng/màn hình ngang</string>\n    <string name=\"open_in_browser\">mở trong trình duyệt</string>\n    <string name=\"copy_url\">sao chép url</string>\n    <string name=\"full_screen\">toàn màn hình</string>\n    <string name=\"open_fun\">chức năng mở</string>\n    <string name=\"use_browser_open\">Có mở bằng trình duyệt bên ngoài không?</string>\n    <string name=\"see\">xem</string>\n    <string name=\"open\">mở</string>\n    <string name=\"del_login_header\">xóa tiêu đề đăng nhập</string>\n    <string name=\"show_login_header\">hiển thị tiêu đề đăng nhập</string>\n    <string name=\"login_header\">tiêu đề đăng nhập</string>\n    <string name=\"font_scale\">tỷ lệ phông chữ</string>\n    <string name=\"font_scale_summary\">tỷ lệ phông chữ hiện tại:%.1f</string>\n    <string name=\"search_content_size\">kết quả tìm kiếm</string>\n    <string name=\"search_content_empty\">Kết quả tìm kiếm trống, kiểm tra cài đặt chuyển đổi</string>\n    <string name=\"tts_speech_reduce\">Giảm tốc độ đọc</string>\n    <string name=\"tts_speech_add\">Tăng tốc độ đọc</string>\n    <string name=\"open_sys_dir_picker_error\">Lỗi khi mở bộ chọn thư mục hệ thống, tự động mở bộ chọn thư mục ứng dụng</string>\n    <string name=\"open_sys_doc_picker_error\">Lỗi khi mở bộ chọn tệp hệ thống, tự động mở bộ chọn tệp ứng dụng</string>\n    <string name=\"expand_text_menu\">Mở rộng menu chọn văn bản</string>\n    <string name=\"book_tree_uri_t\">Vị trí lưu sách</string>\n    <string name=\"book_tree_uri_s\">Vị trí lưu sách được mở từ các ứng dụng khác</string>\n    <string name=\"select_book_folder\">Chọn thư mục lưu sách</string>\n    <string name=\"user_agent\">User agent</string>\n    <string name=\"bg_alpha\">Độ trong suốt của nền</string>\n    <string name=\"check_source_config\">Cài đặt xác minh</string>\n    <string name=\"check_source_item\">Mục xác minh</string>\n    <string name=\"check_source_timeout\">Thời gian chờ xác minh một nguồn sách (giây)</string>\n    <string name=\"timeout\">Hết giờ</string>\n    <string name=\"seconds\">giây</string>\n    <string name=\"less_than\">nhỏ hơn</string>\n    <string name=\"check_source_config_summary\">Thời gian chờ xác minh: %1$s giây\\nMục xác minh:%2$s</string>\n    <string name=\"record_debug_log\">Ghi nhật ký gỡ lỗi</string>\n    <string name=\"sub_dir\">Thư mục con</string>\n    <string name=\"general\">Toàn cục</string>\n    <string name=\"use_replace\">Sử dụng thay thế</string>\n    <string name=\"scope_title\">Áp dụng cho tiêu đề</string>\n    <string name=\"scope_content\">Áp dụng cho nội dung</string>\n    <string name=\"join_qq_channel\">Tham gia kênh QQ</string>\n    <string name=\"qq_channel_summary\">Nhấn để tham gia kênh QQ đọc</string>\n    <string name=\"menu_refresh_dur\">Làm mới chương hiện tại</string>\n    <string name=\"menu_refresh_after\">Làm mới các chương sau</string>\n    <string name=\"menu_refresh_all\">Làm mới tất cả các chương</string>\n    <string name=\"edit_content\">Sửa nội dung</string>\n    <string name=\"chapter_change_source\">Đổi nguồn một chương</string>\n    <string name=\"book_change_source\">Đổi nguồn cả sách</string>\n    <string name=\"sort_by_time\">Sắp xếp theo thời gian</string>\n    <string name=\"enable_record\">Bật ghi</string>\n    <string name=\"copy_all\">Sao chép tất cả</string>\n    <string name=\"auto_complete\">Tự động hoàn thành</string>\n    <string name=\"sort_by_size\">Sắp xếp theo kích thước</string>\n    <string name=\"welcome_style\">Kiểu giao diện khởi động</string>\n    <string name=\"welcome_style_summary\">Hình ảnh giao diện khởi động và có hiển thị văn bản hay không, v.v.</string>\n    <string name=\"show_welcome_text\">Hiển thị văn bản</string>\n    <string name=\"welcome_text\">Đọc|Tận hưởng thời gian tuyệt vời</string>\n    <string name=\"custom_welcome\">Tùy chỉnh trang chào mừng</string>\n    <string name=\"custom_welcome_summary\">Có sử dụng trang chào mừng tùy chỉnh hay không</string>\n    <string name=\"show_icon\">Hiển thị biểu tượng</string>\n    <string name=\"show_default_book_icon\">Hiển thị biểu tượng sách mặc định</string>\n    <string name=\"cache_export\">Bộ đệm/Xuất</string>\n    <string name=\"assists_key_config\">Cấu hình phím phụ</string>\n    <string name=\"url_option\">Tham số URL</string>\n    <string name=\"only_wifi\">Chỉ WIFI</string>\n    <string name=\"only_wifi_summary\">Chỉ tải bìa mạng khi có WIFI</string>\n    <string name=\"cover_rule\">Quy tắc bìa</string>\n    <string name=\"cover_rule_summary\">Sử dụng quy tắc bìa để lấy lại bìa khi vào trang chi tiết</string>\n    <string name=\"scroll_to_dur_source\">Định vị đến nguồn sách hiện tại</string>\n    <string name=\"sys_tts_config\">Cài đặt TTS hệ thống</string>\n    <string name=\"sys_tts_config_summary\">Mở giao diện cài đặt TTS hệ thống</string>\n    <string name=\"cannot_timed_non_playback\">Không thể hẹn giờ ở trạng thái không phát</string>\n    <string name=\"all_bookmark\">Tất cả dấu trang</string>\n    <string name=\"change_source_batch\">Đổi nguồn hàng loạt</string>\n    <string name=\"book_type_different\">Loại sách khác nhau</string>\n    <string name=\"soure_change_source\">Bạn có chắc chắn muốn đổi nguồn không</string>\n    <string name=\"input_verification_code\">Nhập mã xác minh</string>\n    <string name=\"verification_code\">Mã xác minh</string>\n    <string name=\"timeout_millisecond\">Số mili giây hết hạn</string>\n    <string name=\"file_not_supported\">Tệp %1$s không được hỗ trợ, bạn có muốn tiếp tục mở không?</string>\n    <string name=\"import_tts\">Nhập TTS</string>\n    <string name=\"import_theme\">Nhập chủ đề</string>\n    <string name=\"import_txt_toc_rule\">Nhập quy tắc thư mục txt</string>\n    <string name=\"auto_save_cookie\">CookieJar</string>\n    <string name=\"cookie\">Xóa cookie</string>\n    <string name=\"download_and_import_file\">Nhập tệp sách trực tuyến</string>\n    <string name=\"upload_book_success\">Tải lên thành công</string>\n    <string name=\"upload_book_fail\">Tải lên thất bại</string>\n    <string name=\"download_book_success\">Tải xuống thành công</string>\n    <string name=\"download_book_fail\">Tải xuống thất bại</string>\n    <string name=\"upload_to_remote\">Tải lên</string>\n    <string name=\"add_remote_book\">Sách từ xa</string>\n    <string name=\"bitmap_cache_size_summary\">Kích thước bộ đệm tối đa hiện tại %1$s MB</string>\n    <string name=\"bitmap_cache_size\">Kích thước bộ đệm bitmap</string>\n    <string name=\"image_retain_number_summary\">Giữ lại số chương đã đọc %s</string>\n    <string name=\"image_retain_number\">Số lượng đặt trước manga</string>\n    <string name=\"export_pics_file\">Xuất tệp ảnh</string>\n    <string name=\"error_decode_bitmap\">Giải mã bitmap thất bại</string>\n    <string name=\"error_image_url_empty\">URL hình ảnh trống, kiểm tra quy tắc thay thế</string>\n    <string name=\"variable_comment\">Mô tả biến (variableComment)</string>\n    <string name=\"remote_book\">Sách từ xa</string>\n    <string name=\"reading_time_sort\">Sắp xếp theo thời lượng đọc</string>\n    <string name=\"last_read_time_sort\">Sắp xếp theo thời gian đọc</string>\n    <string name=\"reading_time_tag\">Thời lượng đọc:</string>\n    <string name=\"last_read_time_tag\">Thời gian đọc cuối cùng:</string>\n    <string name=\"page_touch_slop_title\">Ngưỡng lật trang trượt</string>\n    <string name=\"page_touch_slop_dialog_title\">Ngưỡng lật trang trượt (0 = mặc định hệ thống)</string>\n    <string name=\"page_touch_slop_summary\">Trượt bao xa để kích hoạt lật trang (mặc định hệ thống %s px)</string>\n    <string name=\"check_selected_interval\">Chọn khoảng đã chọn</string>\n    <string name=\"show_add_to_shelf_alert_title\">Nhắc thêm vào tủ sách khi quay lại</string>\n    <string name=\"show_add_to_shelf_alert_summary\">Nhắc thêm vào tủ sách khi quay lại từ việc đọc sách không có trên tủ sách</string>\n    <string name=\"review\">Đánh giá</string>\n    <string name=\"rule_review_url\">URL đánh giá đoạn (reviewUrl)</string>\n    <string name=\"rule_avatar\">Avatar người đăng đánh giá đoạn (avatarRule)</string>\n    <string name=\"rule_review_content\">Nội dung đánh giá đoạn (contentRule)</string>\n    <string name=\"rule_post_time\">Thời gian đăng đánh giá đoạn (postTimeRule)</string>\n    <string name=\"rule_review_quote\">URL trả lời đánh giá đoạn (reviewQuoteUrl)</string>\n    <string name=\"review_vote_down\">URL không thích (voteDownUrl)</string>\n    <string name=\"review_vote_up\">URL thích (voteUpUrl)</string>\n    <string name=\"post_review_url\">URL gửi trả lời (postReviewUrl)</string>\n    <string name=\"post_quote_url\">URL gửi trả lời đánh giá đoạn (postQuoteUrl)</string>\n    <string name=\"delete_review_url\">URL xóa đánh giá đoạn (deleteUrl)</string>\n    <string name=\"tag_explore_enabled\">Cờ: đã bật khám phá</string>\n    <string name=\"tag_explore_disabled\">Cờ: đã tắt khám phá</string>\n    <string name=\"show_read_title_addition\">Hiển thị vùng bổ sung tiêu đề đã đọc</string>\n    <string name=\"read_bar_style_follow_page\">Kiểu thanh đọc theo trang</string>\n    <string name=\"rule_image_decode\">Giải mã hình ảnh (imageDecode)</string>\n    <string name=\"like_source\">Thích</string>\n    <string name=\"not_like_source\">Không thích</string>\n    <string name=\"async_load_image\">Tải ảnh không đồng bộ</string>\n    <string name=\"ignore_audio_focus_title\">Bỏ qua tiêu điểm âm thanh</string>\n    <string name=\"ignore_audio_focus_summary\">Cho phép phát âm thanh cùng lúc với các ứng dụng khác</string>\n    <string name=\"refresh_sort\">Làm mới sắp xếp</string>\n    <string name=\"cover_decode_js\">Giải mã JS bìa (coverDecodeJs)</string>\n    <string name=\"net_no_group\">Mạng chưa được nhóm</string>\n    <string name=\"local_no_group\">Cục bộ chưa được nhóm</string>\n    <string name=\"parallel_export_book\">Xuất đa luồng</string>\n    <string name=\"progress_bar_behavior\">Hành vi thanh tiến trình</string>\n    <string name=\"source_edit_text_max_line\">Số dòng tối đa trong hộp chỉnh sửa nguồn</string>\n    <string name=\"source_edit_max_line_summary\">%s, việc đặt số dòng nhỏ hơn số dòng tối đa có thể hiển thị trên màn hình giúp dễ dàng trượt đến các trường khác để chỉnh sửa.</string>\n    <string name=\"restore_last_book_process\">Có quay lại tiến độ đọc trước khi chuyển không?</string>\n    <string name=\"search_scope\">Phạm vi tìm kiếm</string>\n    <string name=\"toggle_search_scope\">Chuyển đổi</string>\n    <string name=\"sure_clear_search_history\">Bạn có chắc chắn muốn xóa tất cả lịch sử tìm kiếm không</string>\n    <string name=\"no_anim_scroll_page\">Vô hiệu hóa hoạt ảnh nhấp chuột cuộn</string>\n    <string name=\"webdav_device_name\">Tên thiết bị</string>\n    <string name=\"web_service_wake_lock\">Khóa đánh thức dịch vụ web</string>\n    <string name=\"web_service_wake_lock_summary\">Khóa đánh thức được bật khi dịch vụ web được bật và một số điện thoại sẽ bị tắt khi khóa đánh thức được bật.</string>\n    <string name=\"read_aloud_wake_lock\">Khóa đánh thức dịch vụ đọc thành tiếng</string>\n    <string name=\"read_aloud_wake_lock_summary\">Khóa đánh thức được bật khi đọc thành tiếng được bật và khóa đánh thức trên một số điện thoại di động sẽ bị tắt ở chế độ nền.</string>\n    <string name=\"audio_play_wake_lock\">Khóa đánh thức dịch vụ âm thanh</string>\n    <string name=\"audio_play_wake_lock_summary\">Khóa đánh thức được bật khi phát âm thanh và một số điện thoại sẽ bị tắt khi khóa đánh thức được bật.</string>\n    <string name=\"change_search_scope\">Chuyển đổi phạm vi tìm kiếm</string>\n    <string name=\"copy_rule\">Sao chép quy tắc</string>\n    <string name=\"paste_rule\">Dán quy tắc</string>\n    <string name=\"groups_or_source\">Đa nhóm / nguồn sách</string>\n    <string name=\"replace_state_change\">Thay thế (bật / tắt)</string>\n    <string name=\"show_last_update_time\">Hiển thị thời gian cập nhật lần cuối</string>\n    <string name=\"refresh_list\">Làm mới danh sách</string>\n    <string name=\"tip_divider_color\">Màu dải phân cách</string>\n    <string name=\"same_title_removed\">Xóa tiêu đề trùng lặp</string>\n    <string name=\"update_book_fail\">Cập nhật thất bại</string>\n    <string name=\"notification_permission_rationale\">Đọc cần gửi thông báo để hiển thị điều khiển đọc và tiến trình tải xuống</string>\n    <string name=\"webdav_after_local_restore_confirm\">Nguồn sách webDav mới hơn nguồn cục bộ, có khôi phục không</string>\n    <string name=\"c_whitelist\">Danh sách trắng(contentWhitelist)</string>\n    <string name=\"c_blacklist\">Danh sách đen(contentBlacklist)</string>\n    <string name=\"confirm\">Xác nhận</string>\n    <string name=\"jump_to_another_app\">Chuyển sang ứng dụng khác</string>\n    <string name=\"clear_webview_data\">Xóa dữ liệu WebView</string>\n    <string name=\"clear_webview_data_summary\">Xóa tất cả dữ liệu của trình duyệt tích hợp</string>\n    <string name=\"source_tab_list\">Danh sách</string>\n    <string name=\"dict_rule\">Quy tắc từ điển</string>\n    <string name=\"config_dict_rule\">Cấu hình quy tắc từ điển</string>\n    <string name=\"create\">Tạo mới</string>\n    <string name=\"url_rule\">quy tắc url(urlRule)</string>\n    <string name=\"show_rule\">hiển thị quy tắc(showRule)</string>\n    <string name=\"config_txt_toc_rule\">Cấu hình quy tắc thư mục TXT</string>\n    <string name=\"import_dict_rule\">Nhập quy tắc từ điển</string>\n    <string name=\"keep_group\">Giữ nhóm</string>\n    <string name=\"server_config\">Cấu hình máy chủ</string>\n    <string name=\"sure_upload\">URL webDav từ xa đã tồn tại, tiếp tục?</string>\n    <string name=\"unsupport_archivefile_entry\">Không thể tìm thấy các tệp được hỗ trợ trong kho lưu trữ</string>\n    <string name=\"no_books_dir\">Không có vị trí lưu sách nào được đặt!</string>\n    <string name=\"delete_alert\">Xóa cảnh báo</string>\n    <string name=\"no_book_found_bookshelf\">Không tìm thấy sách trong tủ sách, nhập lại?</string>\n    <string name=\"archive_not_found\">Không thể tìm thấy kho lưu trữ đã chọn, tiếp tục tải xuống?</string>\n    <string name=\"privacy_policy\">Quyền riêng tư và Thỏa thuận người dùng</string>\n    <string name=\"agree\">Đồng ý</string>\n    <string name=\"refuse\">Từ chối</string>\n    <string name=\"file_manage\">Quản lý tệp</string>\n    <string name=\"file_manage_summary\">Quản lý các tệp trong thư mục riêng của ứng dụng</string>\n    <string name=\"create_folder\">Tạo thư mục</string>\n    <string name=\"allow_drop_down_refresh\">Cho phép làm mới bằng cách kéo xuống</string>\n    <string name=\"text_underline\">Gạch chân văn bản</string>\n    <string name=\"select_new_source\">Chọn nguồn mới</string>\n    <string name=\"select_update_source\">Chọn nguồn cập nhật</string>\n    <string name=\"set_local_password\">Đặt mật khẩu cục bộ</string>\n    <string name=\"set_local_password_summary\">Mật khẩu cục bộ được sử dụng để mã hóa và giải mã thông tin nhạy cảm của bản sao lưu, nếu cần đồng bộ hóa giữa các thiết bị khác nhau, mật khẩu cục bộ phải nhất quán.</string>\n    <string name=\"only_latest_backup_t\">Chỉ giữ bản sao lưu mới nhất</string>\n    <string name=\"only_latest_backup_s\">Sao lưu cục bộ chỉ giữ tệp sao lưu mới nhất</string>\n    <string name=\"webdav_application_authorization_error\">Không thể ủy quyền ứng dụng WebDav</string>\n    <string name=\"load_word_count\">Tải số từ</string>\n    <string name=\"replace_exclude_scope\">Phạm vi loại trừ, tên sách tùy chọn hoặc URL nguồn sách</string>\n    <string name=\"format_js_rule\">Quy tắc định dạng (formatJs)</string>\n    <string name=\"adjust_pos\">Điều chỉnh vị trí</string>\n    <string name=\"error_scope_input\">Vui lòng nhập phạm vi chính xác</string>\n    <string name=\"custom_export\">Xuất tùy chỉnh</string>\n    <string name=\"file_contains_number\">Số chương chứa trong mỗi tệp</string>\n    <string name=\"export_chapter_index\">Chỉ mục phần cần xuất</string>\n    <string name=\"shrink_database\">Thu nhỏ cơ sở dữ liệu</string>\n    <string name=\"shrink_database_summary\">Giảm kích thước của tệp cơ sở dữ liệu</string>\n    <string name=\"is_compress\">Có nén không</string>\n    <string name=\"sort_desc\">Thứ tự giảm dần</string>\n    <string name=\"test\">Kiểm tra</string>\n    <string name=\"show_wait_up_count\">Hiển thị số lượng cập nhật đang chờ</string>\n    <string name=\"exit_app\">Thoát phần mềm</string>\n    <string name=\"result_analyzed\">Đã phân tích</string>\n    <string name=\"bookshelf_px_4\">Toàn diện</string>\n    <string name=\"bookshelf_px_5\">Sắp xếp theo tác giả</string>\n    <string name=\"effective_replaces\">Thay thế hiệu quả</string>\n    <string name=\"export_book\">Xuất sách</string>\n    <string name=\"export_book_notification_content\">Đang xuất (%1$s), còn %2$d cuốn cần xuất.</string>\n    <string name=\"export_md\">Xuất (MD)</string>\n    <string name=\"change_source_delay\">Độ trễ đổi nguồn</string>\n    <string name=\"change_source_progress\">Kết quả %1$d, Tiến độ %2$d / %3$d: %4$s</string>\n    <string name=\"open_book_info_by_click_title\">Nhấn vào tên sách để mở chi tiết</string>\n    <string name=\"export_wait\">Đang chờ xuất</string>\n    <string name=\"default_home_page\">Trang chủ mặc định</string>\n    <string name=\"show_bookshelf_fast_scroller\">Hiển thị thanh cuộn nhanh</string>\n    <string name=\"export_all_use_book_source\">Xuất nguồn sách cho tất cả các sách</string>\n    <string name=\"keep_enable\">Giữ trạng thái kích hoạt</string>\n    <string name=\"preview_image_by_click\">Nhấn để xem trước ảnh</string>\n    <string name=\"screen_portrait_reversed\">Dọc đảo ngược</string>\n    <string name=\"del_ruby_tag\">Xóa thẻ ruby</string>\n    <string name=\"del_h_tag\">Xóa thẻ h</string>\n    <string name=\"adjust_chapter_page\">Điều chỉnh số trang của chương</string>\n    <string name=\"adjust_chapter_index\">Điều chỉnh vị trí chương</string>\n    <string name=\"clear_webview_data_success\">Xóa thành công, tự động khởi động lại ứng dụng sau 3 giây</string>\n    <string name=\"key_page_on_long_press\">Nhấn giữ phím để lật trang</string>\n    <string name=\"save_log\">Lưu nhật ký</string>\n    <string name=\"create_heap_dump\">Tạo heap dump</string>\n    <string name=\"record_heap_dump_s\">Lưu heap dump khi ứng dụng gặp sự cố OOM</string>\n    <string name=\"record_heap_dump_t\">Ghi heap dump</string>\n    <string name=\"font_weight_text\">Thường/Đậm/Nhạt</string>\n    <string name=\"keep_swipe_tip\">Tiếp tục vuốt để tải chương tiếp theo…</string>\n    <string name=\"enable_optimize_render\">Bật tối ưu hóa vẽ</string>\n    <string name=\"ignore_battery_permission_rationale\">Đọc cần yêu cầu quyền chạy nền để giữ cho dịch vụ hoạt động bình thường</string>\n    <string name=\"simulated_reading\">Đọc mô phỏng</string>\n    <string name=\"switch_on\">Bật/Tắt</string>\n    <string name=\"start_from\">Bắt đầu từ</string>\n    <string name=\"daily_chapters\">Chương hàng ngày</string>\n    <string name=\"start_chapter\">Chương bắt đầu</string>\n    <string name=\"update_to_variant_title\">Kiểm tra cập nhật tìm phiên bản</string>\n    <string name=\"update_to_variant_summary\">Kiểm tra cập nhật khi tìm các phiên bản có chữ ký khác</string>\n    <string name=\"default_version\">Hiện tại</string>\n    <string name=\"official_version\">Phiên bản chính thức</string>\n    <string name=\"beta_release_version\">Phiên bản beta</string>\n    <string name=\"beta_releaseA_version\">Phiên bản song song</string>\n    <string name=\"stream_read_aloud_audio\">Phát âm thanh trực tuyến</string>\n    <string name=\"stream_read_aloud_audio_summary\">Tức là vừa tải vừa phát, khi mạng kém thì phát sẽ bị gián đoạn, chỉ có nguồn TTS mới có hiệu lực</string>\n    <string name=\"pause_read_aloud_while_phone_calls_title\">Tạm dừng đọc trong khi gọi điện</string>\n    <string name=\"pause_read_aloud_while_phone_calls_summary\">Tạm dừng đọc trong khi gọi điện, cần quyền đọc trạng thái điện thoại</string>\n    <string name=\"read_aloud_read_phone_state_permission_rationale\">Đọc cần đọc trạng thái điện thoại để thực hiện chức năng tạm dừng đọc trong khi gọi</string>\n    <string name=\"read_aloud_by_media_button_title\">Nút tai nghe bắt đầu đọc</string>\n    <string name=\"read_aloud_by_media_button_summary\">Bắt đầu đọc bằng nút tai nghe</string>\n    <string name=\"group_sources_by_domain\">按域名分组显示</string>\n    <string name=\"theme_config\">Cấu hình chủ đề</string>\n    <string name=\"show_manga_ui\">Duyệt manga</string>\n    <string name=\"disable_manga_scale\">Vô hiệu hóa thu phóng manga</string>\n    <string name=\"disable_manga_click_scroll\">Vô hiệu hóa lật trang bằng cách nhấn</string>\n    <string name=\"enable_auto_page_scroll\">Bật lật trang tự động</string>\n    <string name=\"manga_auto_page_speed\">Tốc độ lật trang %s</string>\n    <string name=\"enable_auto_scroll\">Bật cuộn</string>\n    <string name=\"manga_footer_config\">Cấu hình chân trang</string>\n    <string name=\"setting_manga_auto_page_speed\">Đặt tốc độ lật trang tự động</string>\n    <string name=\"book_reader_info_bar\">Trang. %1$d/%2$d  Chương. %3$d/%4$d -> %5$s%%</string>\n    <string name=\"menu_download_after\">Tải xuống chương tiếp theo</string>\n    <string name=\"menu_download_all\">Tải xuống tất cả các chương</string>\n    <string name=\"manga_header_chapter\">\"Chương và văn bản\" ẩn</string>\n    <string name=\"manga_check_chapter_label\">\"Chương.\" văn bản</string>\n    <string name=\"manga_check_chapter\">Chương</string>\n    <string name=\"manga_check_chapter_name\">Tên chương</string>\n    <string name=\"manga_header_page\">\"Số trang và văn bản\" ẩn</string>\n    <string name=\"manga_check_page_label\">\"Trang.\" văn bản</string>\n    <string name=\"manga_check_page_number\">Số trang</string>\n    <string name=\"manga_header_progress\">\"Tổng tiến độ và văn bản\" ẩn</string>\n    <string name=\"manga_check_progress_label\">\"Tổng tiến độ.\" văn bản</string>\n    <string name=\"manga_check_progress\">Tổng tiến độ</string>\n    <string name=\"manga_header_footer\">Chân trang</string>\n    <string name=\"manga_radio_left\">Căn trái</string>\n    <string name=\"manga_radio_center\">Căn giữa</string>\n    <string name=\"enable_manga_horizontal_scroll\">Cuộn ngang</string>\n    <string name=\"manga_color_filter\">Bộ lọc</string>\n    <string name=\"hide_manga_title\">Ẩn tiêu đề danh sách manga</string>\n    <string name=\"refresh_explore\">Làm mới khám phá</string>\n    <string name=\"padding_display_cutouts\">Đệm các phần cắt hiển thị</string>\n    <string name=\"manga_epaper\">Màn hình mực</string>\n    <string name=\"manga_epaper_stting\">Cài đặt màn hình mực</string>\n    <string name=\"manga_epaper_value\">Ngưỡng</string>\n    <string name=\"disable_horizontal_page_snap\">禁用水平翻页效果</string>\n    <string name=\"enable_manga_gray\">Bật ảnh xám</string>\n    <string name=\"play_mode\">Chế độ phát</string>\n    <string name=\"sure_cache_book\">是否确认开始缓存？</string>\n    <string name=\"auto_check_new_backup_t\">自动检查新备份</string>\n    <string name=\"auto_check_new_backup_s\">打开软件时检查是否有新备份，有新备份时提示是否更新</string>\n    <string name=\"sys_image_picker\">系统图片选择器</string>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values-zh/arrays.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <string-array name=\"book_type\">\n        <item>文本</item>\n        <item>音频</item>\n        <item>图片</item>\n        <item>文件</item>\n    </string-array>\n\n    <string-array name=\"group_style\">\n        <item>标签</item>\n        <item>文件夹</item>\n    </string-array>\n\n    <string-array name=\"text_suffix\">\n        <item>.txt</item>\n        <item>.json</item>\n        <item>.xml</item>\n    </string-array>\n\n    <string-array name=\"theme_mode\">\n        <item>跟随系统</item>\n        <item>亮色主题</item>\n        <item>暗色主题</item>\n        <item>E-Ink(墨水屏)</item>\n    </string-array>\n\n    <string-array name=\"double_page_title\">\n        <item>全局单页</item>\n        <item>全局双页</item>\n        <item>横屏双页</item>\n        <item>平板/横屏双页</item>\n    </string-array>\n\n    <string-array name=\"NavBarColors\">\n        <item>自动</item>\n        <item>黑色</item>\n        <item>白色</item>\n        <item>跟随背景</item>\n    </string-array>\n\n    <string-array name=\"screen_time_out\">\n        <item>默认</item>\n        <item>1分钟</item>\n        <item>5分钟</item>\n        <item>10分钟</item>\n        <item>常亮</item>\n    </string-array>\n\n    <string-array name=\"icon_names\">\n        <item>iconMain</item>\n        <item>icon1</item>\n        <item>icon2</item>\n        <item>icon3</item>\n        <item>icon4</item>\n        <item>icon5</item>\n        <item>icon6</item>\n    </string-array>\n\n    <string-array name=\"chinese_mode\">\n        <item>关闭</item>\n        <item>繁体转简体</item>\n        <item>简体转繁体</item>\n    </string-array>\n\n    <string-array name=\"system_typefaces\">\n        <item>系统默认字体</item>\n        <item>系统衬线字体</item>\n        <item>系统等宽字体</item>\n    </string-array>\n\n    <string-array name=\"read_tip\">\n        <item>无</item>\n        <item>书名</item>\n        <item>标题</item>\n        <item>时间</item>\n        <item>电量</item>\n        <item>电量%</item>\n        <item>页数</item>\n        <item>进度(%)</item>\n        <item>进度(xx/yyy)</item>\n        <item>页数及进度</item>\n        <item>时间及电量</item>\n        <item>时间及电量%</item>\n    </string-array>\n\n    <string-array name=\"text_font_weight\">\n        <item>正常</item>\n        <item>粗体</item>\n        <item>细体</item>\n    </string-array>\n\n    <string-array name=\"language\">\n        <item>跟随系统</item>\n        <item>简体中文</item>\n        <item>繁体中文</item>\n        <item>英文</item>\n    </string-array>\n\n    <string-array name=\"rule_type\">\n        <item>书源</item>\n        <item>订阅源</item>\n        <item>替换规则</item>\n    </string-array>\n\n    <string-array name=\"tip_color\">\n        <item>跟随内容</item>\n        <item>自定义</item>\n    </string-array>\n\n    <string-array name=\"tip_divider_color\">\n        <item>默认</item>\n        <item>跟随内容</item>\n        <item>自定义</item>\n    </string-array>\n\n</resources>"
  },
  {
    "path": "app/src/main/res/values-zh/strings.xml",
    "content": "<resources>\n    <!--App-->\n    <string name=\"app_name\">阅读</string>\n    <string name=\"app_name_a\">阅读·A</string>\n    <string name=\"receiving_shared_label\">阅读·搜索</string>\n    <string name=\"tip_perm_request_storage\">阅读需要访问存储卡权限，请前往“设置”—“应用权限”，打开所需权限</string>\n\n    <!--Other-->\n    <string name=\"menu_backup\">Home</string>\n    <string name=\"menu_restore\">恢复</string>\n    <string name=\"menu_import_old\">导入阅读数据</string>\n    <string name=\"webdav_cache_backup\">离线缓存书籍备份</string>\n    <string name=\"webdav_cache_backup_s\">导出本地同时备份到 legado 文件夹下的 exports 目录</string>\n    <string name=\"backup_path\">备份路径</string>\n    <string name=\"select_backup_path\">请选择备份路径</string>\n    <string name=\"menu_import_old_version\">导入旧版数据</string>\n    <string name=\"menu_import_github\">导入 Github 数据</string>\n    <string name=\"menu_replace_rule\">净化替换</string>\n    <string name=\"menu_send\">发送</string>\n\n    <string name=\"dialog_title\">提示</string>\n    <string name=\"dialog_cancel\">取消</string>\n    <string name=\"dialog_confirm\">确定</string>\n    <string name=\"dialog_setting\">去设置</string>\n    <string name=\"tip_cannot_jump_setting_page\">无法跳转至设置界面</string>\n\n    <string name=\"manual_input\">手动输入</string>\n    <string name=\"enter_directory_path\">请输入目录路径</string>\n    <string name=\"invalid_directory\">目录路径无效</string>\n    <string name=\"empty_directory_input\">目录路径不能为空</string>\n\n    <string name=\"dynamic_click_retry\">点击重试</string>\n    <string name=\"dynamic_loading\">正在加载</string>\n    <string name=\"draw\">提醒</string>\n    <string name=\"edit\">编辑</string>\n    <string name=\"delete\">删除</string>\n    <string name=\"delete_select_group\">删除当前分组</string>\n    <string name=\"delete_all\">删除所有</string>\n    <string name=\"replace\">替换</string>\n    <string name=\"replace_purify\">替换净化</string>\n    <string name=\"custom_export_section\">自定义Epub导出章节</string>\n    <string name=\"replace_purify_desc\">配置替换净化规则</string>\n    <string name=\"not_available\">暂无</string>\n    <string name=\"enable\">启用</string>\n    <string name=\"replace_purify_search\">替换净化-搜索</string>\n    <string name=\"bookshelf\">书架</string>\n    <string name=\"favorites\">收藏夹</string>\n    <string name=\"favorite\">收藏</string>\n    <string name=\"in_favorites\">已收藏</string>\n    <string name=\"out_favorites\">未收藏</string>\n    <string name=\"rss\">订阅</string>\n    <string name=\"all\">全部</string>\n    <string name=\"recent_reading\">最近阅读</string>\n    <string name=\"last_read\">最后阅读</string>\n    <string name=\"update_log\">更新日志</string>\n    <string name=\"bookshelf_empty\">书架还空着，先去搜索书籍或从发现里添加吧！</string>\n    <string name=\"action_search\">搜索</string>\n    <string name=\"action_download\">下载</string>\n    <string name=\"layout_list\">列表</string>\n    <string name=\"layout_grid3\">网格三列</string>\n    <string name=\"layout_grid4\">网格四列</string>\n    <string name=\"layout_grid5\">网格五列</string>\n    <string name=\"layout_grid6\">网格六列</string>\n    <string name=\"bookshelf_layout\">书架布局</string>\n    <string name=\"view\">视图</string>\n    <string name=\"book_library\">书城</string>\n    <string name=\"book_local\">添加本地</string>\n    <string name=\"book_source\">书源</string>\n    <string name=\"book_source_manage\">书源管理</string>\n    <string name=\"book_source_manage_desc\">新建、导入、编辑或管理书源</string>\n    <string name=\"setting\">设置</string>\n    <string name=\"theme_setting\">主题设置</string>\n    <string name=\"theme_setting_s\">与界面/颜色相关的一些设置</string>\n    <string name=\"other_setting\">其它设置</string>\n    <string name=\"other_setting_s\">与功能相关的一些设置</string>\n    <string name=\"about\">关于</string>\n    <string name=\"donate\">捐赠</string>\n    <string name=\"exit\">退出</string>\n    <string name=\"exit_no_save\">尚未保存，是否继续编辑</string>\n    <string name=\"read_style\">阅读样式设置</string>\n    <string name=\"version\">版本</string>\n    <string name=\"local\">本地</string>\n    <string name=\"search\">搜索</string>\n    <string name=\"origin_format\">来源：%s</string>\n    <string name=\"read_dur_progress\">最近：%s</string>\n    <string name=\"book_name\">书名</string>\n    <string name=\"lasted_show\">最新：%s</string>\n    <string name=\"check_add_bookshelf\">是否将《%s》放入书架？</string>\n    <string name=\"import_books_count\">共 %s 个文本文件</string>\n    <string name=\"is_loading\">加载中…</string>\n    <string name=\"retry\">重试</string>\n    <string name=\"web_service\">Web 服务</string>\n    <string name=\"web_service_desc\">用浏览器写源或看书</string>\n    <string name=\"web_edit_source\">Web 编辑书源</string>\n    <string name=\"offline_cache\">离线缓存</string>\n    <string name=\"offline_cache_t\">离线缓存</string>\n    <string name=\"offline_cache_s\">缓存选择的章节到本地</string>\n    <string name=\"change_origin\">换源</string>\n    <string name=\"about_description\">\n        \\u3000\\u3000这是一款使用 Kotlin 全新开发的开源的阅读软件，欢迎您的加入。\n    </string>\n    <string name=\"app_share_description\">\n        阅读 3.0 下载地址：\\nhttps://github.com/gedoor/legado/releases\n    </string>\n    <string name=\"version_name\">版本 %s</string>\n    <string name=\"pt_background_verification\">后台校验书源</string>\n    <string name=\"ps_background_verification\">打开后可以在校验书源时自由操作</string>\n    <string name=\"pt_auto_refresh\">自动刷新</string>\n    <string name=\"ps_auto_refresh\">打开软件时自动更新书籍</string>\n    <string name=\"pt_auto_download\">自动下载最新章节</string>\n    <string name=\"ps_auto_download\">更新书籍时自动下载最新章节</string>\n    <string name=\"backup_restore\">备份与恢复</string>\n    <string name=\"web_dav_set\">WebDav 设置</string>\n    <string name=\"web_dav_set_import_old\">WebDav 设置/导入旧版本数据</string>\n    <string name=\"backup\">备份</string>\n    <string name=\"restore\">恢复</string>\n    <string name=\"backup_permission\">备份请给予存储权限</string>\n    <string name=\"restore_permission\">恢复请给予存储权限</string>\n    <string name=\"ok\">确认</string>\n    <string name=\"cancel\">取消</string>\n    <string name=\"backup_confirmation\">确认备份吗？</string>\n    <string name=\"backup_message\">新备份会替换原有备份。\\n备份文件夹 YueDu</string>\n    <string name=\"restore_confirmation\">确认恢复吗？</string>\n    <string name=\"restore_message\">恢复书架会覆盖现有书架。</string>\n    <string name=\"backup_success\">备份成功</string>\n    <string name=\"backup_fail\">备份失败\\n%s</string>\n    <string name=\"on_restore\">正在恢复</string>\n    <string name=\"restore_success\">恢复成功</string>\n    <string name=\"restore_fail\">恢复失败</string>\n    <string name=\"screen_direction\">屏幕方向</string>\n    <string name=\"screen_sensor\">跟随传感器</string>\n    <string name=\"screen_landscape\">横向</string>\n    <string name=\"screen_portrait\">竖向</string>\n    <string name=\"screen_unspecified\">跟随系统</string>\n    <string name=\"disclaimer\">免责声明</string>\n    <string name=\"all_chapter_num\">共 %d 章</string>\n    <string name=\"interface_setting\">界面</string>\n    <string name=\"brightness\">亮度</string>\n    <string name=\"chapter_list\">目录</string>\n    <string name=\"next_chapter\">下一章</string>\n    <string name=\"previous_chapter\">上一章</string>\n    <string name=\"pt_hide_status_bar\">隐藏状态栏</string>\n    <string name=\"ps_hide_status_bar\">阅读界面隐藏状态栏</string>\n    <string name=\"read_aloud\">朗读</string>\n    <string name=\"read_aloud_t\">正在朗读</string>\n    <string name=\"read_aloud_s\">点击打开阅读界面</string>\n    <string name=\"audio_play\">播放</string>\n    <string name=\"audio_play_t\">正在播放</string>\n    <string name=\"audio_play_s\">点击打开播放界面</string>\n    <string name=\"audio_pause\">播放暂停</string>\n    <string name=\"text_return\">返回</string>\n    <string name=\"refresh\">刷新</string>\n    <string name=\"start\">开始</string>\n    <string name=\"stop\">停止</string>\n    <string name=\"pause\">暂停</string>\n    <string name=\"resume\">继续</string>\n    <string name=\"set_timer\">定时</string>\n    <string name=\"read_aloud_pause\">朗读暂停</string>\n    <string name=\"read_aloud_timer\">正在朗读（还剩 %d 分钟）</string>\n    <string name=\"playing_timer\">正在播放（还剩 %d 分钟）</string>\n    <string name=\"ps_hide_navigation_bar\">阅读界面隐藏虚拟按键</string>\n    <string name=\"pt_hide_navigation_bar\">隐藏导航栏</string>\n    <string name=\"re_navigation_bar_color\">导航栏颜色</string>\n    <string name=\"scoring\">评分</string>\n    <string name=\"send_mail\">发送邮件</string>\n    <string name=\"can_not_open\">无法打开</string>\n    <string name=\"can_not_share\">分享失败</string>\n    <string name=\"no_chapter\">无章节</string>\n    <string name=\"add_url\">添加网址</string>\n    <string name=\"add_book_url\">添加书籍网址</string>\n    <string name=\"background\">背景</string>\n    <string name=\"author\">作者</string>\n    <string name=\"author_show\">作者：%s</string>\n    <string name=\"aloud_stop\">朗读停止</string>\n    <string name=\"clear_cache\">清理缓存</string>\n    <string name=\"clear_cache_success\">成功清理缓存</string>\n    <string name=\"action_save\">保存</string>\n    <string name=\"edit_source\">编辑源</string>\n    <string name=\"edit_book_source\">编辑书源</string>\n    <string name=\"disable_book_source\">禁用书源</string>\n    <string name=\"add_book_source\">新建书源</string>\n    <string name=\"add_rss_source\">新建订阅源</string>\n    <string name=\"book_file_selector\">添加书籍</string>\n    <string name=\"scan_book_source\">扫描</string>\n    <string name=\"copy_source\">拷贝源</string>\n    <string name=\"paste_source\">粘贴源</string>\n    <string name=\"source_rule_s\">源规则说明</string>\n    <string name=\"check_update\">检查更新</string>\n    <string name=\"camera_scan\">扫描二维码</string>\n    <string name=\"scan_image\">扫描本地图片</string>\n    <string name=\"rule_summary\">规则说明</string>\n    <string name=\"share\">分享</string>\n    <string name=\"share_app\">软件分享</string>\n    <string name=\"flow_sys\">跟随系统</string>\n    <string name=\"add\">添加</string>\n    <string name=\"import_book_source\">导入书源</string>\n    <string name=\"import_local\">本地导入</string>\n    <string name=\"import_on_line\">网络导入</string>\n    <string name=\"replace_rule_title\">替换净化</string>\n    <string name=\"replace_rule_edit\">替换规则编辑</string>\n    <string name=\"replace_rule\">替换规则</string>\n    <string name=\"replace_to\">替换为</string>\n    <string name=\"img_cover\">封面</string>\n   <string name=\"book\">书</string>\n    <string name=\"volume_key_page\">音量键翻页</string>\n    <string name=\"mouse_wheel_page\">鼠标滚轮翻页</string>\n    <string name=\"click_turn_page\">点击翻页</string>\n    <string name=\"page_anim\">翻页动画</string>\n    <string name=\"book_page_anim\">翻页动画（本书）</string>\n    <string name=\"keep_light\">屏幕超时</string>\n    <string name=\"back\">返回</string>\n    <string name=\"menu\">菜单</string>\n    <string name=\"adjust\">调节</string>\n    <string name=\"scroll_bar\">滚动条</string>\n    <string name=\"clear_all_content\">清除缓存会删除所有已保存章节，是否确认删除？</string>\n    <string name=\"book_source_share_url\">书源共享</string>\n    <string name=\"replace_rule_summary\">替换规则名称</string>\n    <string name=\"replace_rule_invalid\">替换规则为空或者不满足正则表达式要求</string>\n    <string name=\"select_action\">选择操作</string>\n    <string name=\"select_all\">全选</string>\n    <string name=\"select_all_count\">全选（%1$d/%2$d）</string>\n    <string name=\"select_cancel_count\">取消全选（%1$d/%2$d）</string>\n    <string name=\"dark_theme\">深色模式</string>\n    <string name=\"welcome\">启动页</string>\n    <string name=\"download_start\">开始下载</string>\n    <string name=\"download_cancel\">取消下载</string>\n    <string name=\"no_download\">暂无任务</string>\n    <string name=\"download_count\">已下载 %1$d/%2$d</string>\n    <string name=\"import_select_book\">导入选择书籍</string>\n    <string name=\"threads_num_title\">更新和搜索线程数（太多会卡顿）</string>\n    <string name=\"change_icon\">切换图标</string>\n    <string name=\"remove_from_bookshelf\">删除书籍</string>\n    <string name=\"start_read\">开始阅读</string>\n    <string name=\"data_loading\">加载数据中…</string>\n    <string name=\"load_error_retry\">加载失败，点击重试</string>\n    <string name=\"book_intro\">内容简介</string>\n    <string name=\"intro_show\">简介：%s</string>\n    <string name=\"intro_show_null\">简介：暂无简介</string>\n    <string name=\"open_from_other\">打开外部书籍</string>\n    <string name=\"origin_show\">来源：%s</string>\n    <string name=\"import_replace_rule\">导入替换规则</string>\n    <string name=\"import_replace_rule_on_line\">导入在线规则</string>\n    <string name=\"check_update_interval\">检查更新间隔</string>\n    <string name=\"bookshelf_px_0\">按阅读时间</string>\n    <string name=\"bookshelf_px_1\">按更新时间</string>\n    <string name=\"bookshelf_px_2\">按书名</string>\n    <string name=\"bookshelf_px_3\">手动排序</string>\n    <string name=\"read_type\">阅读方式</string>\n    <string name=\"compose_type\">排版</string>\n    <string name=\"del_select\">删除所选</string>\n    <string name=\"del_msg\">是否确认删除？</string>\n    <string name=\"clear_font\">默认字体</string>\n    <string name=\"find_on_www\">发现</string>\n    <string name=\"find_source_manage\">发现管理</string>\n    <string name=\"find_empty\">还没有内容，去书源里自定义吧！</string>\n    <string name=\"del_all\">删除所有</string>\n    <string name=\"searchHistory\">搜索历史</string>\n    <string name=\"clear\">清除</string>\n    <string name=\"showTitle\">正文显示标题</string>\n    <string name=\"refresh_default\">书源同步</string>\n    <string name=\"no_last_chapter\">无最新章节信息</string>\n    <string name=\"showTimeBattery\">显示时间和电量</string>\n    <string name=\"showLine\">显示分隔线</string>\n    <string name=\"dark_status_icon\">深色状态栏图标</string>\n    <string name=\"content\">内容</string>\n    <string name=\"copy_text\">拷贝内容</string>\n    <string name=\"download_all\">一键缓存</string>\n    <string name=\"content_sl\">这是一段测试文字\\n\\u3000\\u3000只是让你看看效果的</string>\n    <string name=\"text_bg_style\">文字颜色和背景（长按自定义）</string>\n    <string name=\"immersion_status_bar\">沉浸式状态栏</string>\n    <string name=\"un_download\">还剩 %d 章未下载</string>\n    <string name=\"long_click_input_color\">长按输入颜色值</string>\n    <string name=\"loading\">加载中…</string>\n    <string name=\"group_zg\">追更区</string>\n    <string name=\"group_yf\">养肥区</string>\n    <string name=\"bookmark\">书签</string>\n    <string name=\"bookmark_add\">添加书签</string>\n    <string name=\"action_del\">删除</string>\n    <string name=\"load_over_time\">加载超时</string>\n    <string name=\"join_group\">关注：%s</string>\n    <string name=\"copy_complete\">已拷贝</string>\n    <string name=\"bookshelf_management\">书架管理</string>\n    <string name=\"clear_bookshelf_s\">这将会删除所有书籍，请谨慎操作。</string>\n    <string name=\"search_book_source\">搜索书源</string>\n    <string name=\"search_rss_source\">搜索订阅源</string>\n    <string name=\"search_book_source_num\">搜索（共 %d 个书源）</string>\n    <string name=\"chapter_list_size\">目录（%d）</string>\n    <string name=\"text_bold\">加粗</string>\n    <string name=\"text_font\">字体</string>\n    <string name=\"text\">文字</string>\n    <string name=\"home_page\">软件主页</string>\n    <string name=\"right\">右</string>\n    <string name=\"left\">左</string>\n    <string name=\"bottom\">下</string>\n    <string name=\"top\">上</string>\n    <string name=\"padding\">边距</string>\n    <string name=\"padding_top\">上边距</string>\n    <string name=\"padding_bottom\">下边距</string>\n    <string name=\"padding_left\">左边距</string>\n    <string name=\"padding_right\">右边距</string>\n    <string name=\"check_book_source\">校验书源</string>\n    <string name=\"check_select_source\">校验所选</string>\n    <string name=\"progress_show\">%1$s      进度 %2$d/%3$d</string>\n    <string name=\"tts_fix\">请安装并选择中文 TTS！</string>\n    <string name=\"tts_init_failed\">TTS 初始化失败！</string>\n    <string name=\"jf_convert\">简繁转换</string>\n    <string name=\"jf_convert_o\">关闭</string>\n    <string name=\"jf_convert_f\">简转繁</string>\n    <string name=\"jf_convert_j\">繁转简</string>\n    <string name=\"page_mode\">翻页模式</string>\n    <string name=\"nb_file_sub_count\">%1$d 项</string>\n    <string name=\"nb_file_path\">存储卡：</string>\n    <string name=\"nb_file_add_shelf\">加入书架</string>\n    <string name=\"nb_file_add_shelves\">加入书架（%1$d）</string>\n    <string name=\"nb_file_add_succeed\">成功添加 %1$d 本书</string>\n    <string name=\"fonts_folder\">请将字体文件放到存储根目录 Fonts 文件夹下后重新选择</string>\n    <string name=\"default_font\">默认字体</string>\n    <string name=\"select_font\">选择字体</string>\n    <string name=\"text_size\">字号</string>\n    <string name=\"line_size\">行距</string>\n    <string name=\"paragraph_size\">段距</string>\n    <string name=\"to_top\">置顶</string>\n    <string name=\"selection_to_top\">置顶所选</string>\n    <string name=\"to_bottom\">置底</string>\n    <string name=\"selection_to_bottom\">置底所选</string>\n    <string name=\"auto_expand_find\">自动展开发现</string>\n    <string name=\"default_expand_first\">默认展开第一组发现</string>\n    <string name=\"threads_num\">当前线程数 %s</string>\n    <string name=\"read_aloud_speed\">朗读语速</string>\n    <string name=\"auto_next_page\">自动翻页</string>\n    <string name=\"auto_next_page_stop\">停止自动翻页</string>\n    <string name=\"auto_next_page_speed\">自动翻页间隔</string>\n    <string name=\"book_info\">书籍信息</string>\n    <string name=\"book_info_edit\">书籍信息编辑</string>\n    <string name=\"ps_default_read\">默认打开书架</string>\n    <string name=\"pt_default_read\">自动跳转最近阅读</string>\n    <string name=\"replace_scope\">替换范围，选填书名或者书源 URL</string>\n    <string name=\"menu_action_group\">分组</string>\n    <string name=\"download_path\">内容缓存路径</string>\n    <string name=\"sys_file_picker\">系统文件选择器</string>\n    <string name=\"new_version\">新版本</string>\n    <string name=\"download_update\">下载更新</string>\n    <string name=\"volume_key_page_on_play\">朗读时音量键翻页</string>\n    <string name=\"tip_margin_change\">提示边距跟随页边距调整</string>\n    <string name=\"allow_update\">允许更新</string>\n    <string name=\"disable_update\">禁止更新</string>\n    <string name=\"split_long_chapter\">拆分超长章节</string>\n    <string name=\"need_more_time_load_content\">正文长度过长时，加载正文可能会花费更多时间</string>\n    <string name=\"revert_selection\">反选</string>\n    <string name=\"search_book_key\">搜索书名、作者</string>\n    <string name=\"debug_hint\">书名、作者、URL</string>\n    <string name=\"faq\">常见问题</string>\n    <string name=\"pt_show_all_find\">显示所有发现</string>\n    <string name=\"ps_show_all_find\">关闭则只显示勾选源的发现</string>\n    <string name=\"update_toc\">更新目录</string>\n    <string name=\"txt_toc_rule\">TXT 目录规则</string>\n    <string name=\"select_section_export\">选择待导出章节</string>\n    <string name=\"set_charset\">设置编码</string>\n    <string name=\"swap_sort\">倒序-顺序</string>\n    <string name=\"sort\">排序</string>\n    <string name=\"sort_auto\">智能排序</string>\n    <string name=\"sort_manual\">手动排序</string>\n    <string name=\"sort_default\">默认排序</string>\n    <string name=\"sort_by_name\">名称排序</string>\n    <string name=\"go_to_top\">滚动到顶部</string>\n    <string name=\"go_to_bottom\">滚动到底部</string>\n    <string name=\"read_y\">已读: %s</string>\n    <string name=\"pursue_more\">追更</string>\n    <string name=\"fattening\">养肥</string>\n    <string name=\"finish\">完结</string>\n    <string name=\"all_book\">所有书籍</string>\n    <string name=\"pursue_more_book\">追更书籍</string>\n    <string name=\"fattening_book\">养肥书籍</string>\n    <string name=\"finish_book\">完结书籍</string>\n    <string name=\"local_book\">本地书籍</string>\n    <string name=\"status_bar_immersion\">状态栏颜色透明</string>\n    <string name=\"imm_navigation_bar\">沉浸式导航栏</string>\n    <string name=\"imm_navigation_bar_s\">导航栏颜色透明</string>\n    <string name=\"add_to_bookshelf\">放入书架</string>\n    <string name=\"continue_read\">继续阅读</string>\n    <string name=\"cover_path\">封面地址</string>\n    <string name=\"page_anim_cover\">覆盖</string>\n    <string name=\"page_anim_slide\">滑动</string>\n    <string name=\"page_anim_simulation\">仿真</string>\n    <string name=\"page_anim_scroll\">滚动</string>\n    <string name=\"page_anim_none\">无动画</string>\n    <string name=\"disable_manga_page_anim\">禁用翻页动画</string>\n    <string name=\"up_change_source_last_chapter_t\">后台更新换源最新章节</string>\n    <string name=\"up_change_source_last_chapter_s\">开启则会在软件打开 1 分钟后开始更新</string>\n    <string name=\"behavior_main_t\">书架工具栏自动隐藏</string>\n    <string name=\"behavior_main_s\">滚动书架时工具栏自动隐藏与显示</string>\n    <string name=\"login\">登录</string>\n    <string name=\"login_source\">登录 %s</string>\n    <string name=\"success\">成功</string>\n    <string name=\"source_no_login\">当前源没有配置登陆地址</string>\n    <string name=\"no_prev_page\">没有上一页</string>\n    <string name=\"no_next_page\">没有下一页</string>\n\n    <!-- source start-->\n    <string name=\"source_name\">源名称（sourceName）</string>\n    <string name=\"source_url\">源 URL（sourceUrl）</string>\n    <string name=\"source_group\">源分组（sourceGroup）</string>\n    <string name=\"diy_source_group\">自定义源分组</string>\n    <string name=\"diy_edit_source_group\">输入自定义源分组名称</string>\n    <string name=\"concurrent_rate\">并发率（concurrentRate）</string>\n    <string name=\"sort_url\">分类 URL（sortUrl）</string>\n    <string name=\"login_url\">登录 URL(loginUrl)</string>\n    <string name=\"login_ui\">登录 UI（loginUi）</string>\n    <string name=\"login_check_js\">登录检查 JS（loginCheckJs）</string>\n    <string name=\"comment\">源注释（sourceComment）</string>\n    <string name=\"r_search_url\">搜索地址（url）</string>\n    <string name=\"r_find_url\">发现地址规则（url）</string>\n    <string name=\"r_book_list\">书籍列表规则（bookList）</string>\n    <string name=\"r_book_name\">书名规则（name）</string>\n    <string name=\"r_book_url\">详情页 URL 规则（bookUrl）</string>\n    <string name=\"r_author\">作者规则（author）</string>\n    <string name=\"rule_book_kind\">分类规则（kind）</string>\n    <string name=\"rule_book_intro\">简介规则（intro）</string>\n    <string name=\"rule_cover_url\">封面规则（coverUrl）</string>\n    <string name=\"rule_last_chapter\">最新章节规则（lastChapter）</string>\n    <string name=\"rule_word_count\">字数规则（wordCount）</string>\n    <string name=\"book_url_pattern\">书籍 URL 正则（bookUrlPattern）</string>\n    <string name=\"rule_book_info_init\">预处理规则（bookInfoInit）</string>\n    <string name=\"rule_toc_url\">目录 URL 规则（tocUrl）</string>\n    <string name=\"rule_can_re_name\">允许修改书名作者（canReName）</string>\n    <string name=\"rule_next_toc_url\">目录下一页规则（nextTocUrl）</string>\n    <string name=\"rule_chapter_list\">目录列表规则（chapterList）</string>\n    <string name=\"rule_chapter_name\">章节名称规则（ChapterName）</string>\n    <string name=\"rule_chapter_url\">章节 URL 规则（chapterUrl）</string>\n    <string name=\"rule_is_volume\">Volume 标识（isVolume）</string>\n    <string name=\"rule_is_vip\">VIP 标识（isVip）</string>\n    <string name=\"rule_update_time\">更新时间（ChapterInfo）</string>\n    <string name=\"pre_update_js\">更新之前 JS（preUpdateJs）</string>\n    <string name=\"rule_book_content\">正文规则（content）</string>\n    <string name=\"rule_next_content\">正文下一页 URL 规则（nextContentUrl）</string>\n    <string name=\"rule_web_js\">WebView JS（webJs）</string>\n    <string name=\"rule_image_style\">图片样式（imageStyle）</string>\n    <string name=\"rule_replace_regex\">替换规则（replaceRegex）</string>\n    <string name=\"rule_source_regex\">资源正则（sourceRegex）</string>\n    <string name=\"rule_pay_action\">购买操作（payAction）</string>\n\n    <string name=\"source_icon\">图标（sourceIcon）</string>\n    <string name=\"r_articles\">列表规则（ruleArticles）</string>\n    <string name=\"r_next\">列表下一页规则（ruleNextArticles）</string>\n    <string name=\"r_title\">标题规则（ruleTitle）</string>\n    <string name=\"r_guid\">GUID 规则（ruleGuid）</string>\n    <string name=\"r_date\">时间规则（rulePubDate）</string>\n    <string name=\"r_categories\">类别规则（ruleCategories）</string>\n    <string name=\"r_description\">描述规则（ruleDescription）</string>\n    <string name=\"r_image\">图片 URL 规则（ruleImage）</string>\n    <string name=\"r_content\">内容规则（ruleContent）</string>\n    <string name=\"r_style\">样式（style）</string>\n    <string name=\"r_inject_js\">注入Js(injectJs)</string>\n    <string name=\"r_link\">链接规则（ruleLink）</string>\n    <string name=\"check_key_word\">校验关键字（checkKeyWord）</string>\n    <string name=\"rule_actions\">操作（actions）</string>\n    <string name=\"rule_is_pay\">购买标识（isPay）</string>\n    <!-- source end-->\n\n    <!--error string start-->\n    <string name=\"error_no_source\">没有源</string>\n    <string name=\"error_get_book_info\">书籍信息获取失败</string>\n    <string name=\"error_get_content\">内容获取失败</string>\n    <string name=\"error_get_chapter_list\">目录获取失败</string>\n    <string name=\"error_get_web_content\">访问网站失败：%s</string>\n    <string name=\"error_read_file\">文件读取失败</string>\n    <string name=\"error_load_toc\">加载目录失败</string>\n    <string name=\"error_get_data\">获取数据失败！</string>\n    <string name=\"error_load_msg\">加载失败\\n%s</string>\n    <string name=\"net_error_10001\">没有网络</string>\n    <string name=\"net_error_10002\">网络连接超时</string>\n    <string name=\"net_error_10003\">数据解析失败</string>\n    <!--error string end-->\n\n    <string name=\"source_http_header\">请求头（header）</string>\n    <string name=\"debug_source\">调试源</string>\n    <string name=\"import_by_qr_code\">二维码导入</string>\n    <string name=\"share_selected_source\">分享选中源</string>\n    <string name=\"scan_qr_code\">扫描二维码</string>\n    <string name=\"click_on_selected_show_menu\">选中时点击可弹出菜单</string>\n    <string name=\"theme\">主题</string>\n    <string name=\"theme_mode\">主题模式</string>\n    <string name=\"theme_mode_desc\">选择主题模式</string>\n    <string name=\"join_qq_group\">加入 QQ 群</string>\n    <string name=\"bg_image_per\">获取背景图片需存储权限</string>\n    <string name=\"input_book_source_url\">输入书源网址</string>\n    <string name=\"del_file\">删除文件</string>\n    <string name=\"del_file_success\">删除文件成功</string>\n    <string name=\"sure_del_file\">确定删除文件吗？</string>\n    <string name=\"files_tree\">手机目录</string>\n    <string name=\"intelligent_import\">智能导入</string>\n    <string name=\"discovery\">发现</string>\n    <string name=\"switch_display_style\">切换显示样式</string>\n    <string name=\"import_per\">导入本地书籍需要存储权限</string>\n    <string name=\"night_theme\">夜间模式</string>\n    <string name=\"eink_theme\">E-Ink 模式</string>\n    <string name=\"eink_theme_desc\">电子墨水屏模式</string>\n    <string name=\"get_storage_per\">需要存储权限</string>\n    <string name=\"double_click_exit\">再按一次退出程序</string>\n    <string name=\"import_book_per\">导入本地书籍需要存储权限</string>\n    <string name=\"network_connection_unavailable\">网络连接不可用</string>\n    <string name=\"yes\">是</string>\n    <string name=\"no\">否</string>\n    <string name=\"sure\">确认</string>\n    <string name=\"sure_del\">是否确认删除？</string>\n    <string name=\"sure_del_any\">是否确认删除 %s？</string>\n    <string name=\"sure_del_all_book\">是否删除全部书籍？</string>\n    <string name=\"sure_del_download_book\">是否同时删除已下载的书籍目录？</string>\n    <string name=\"qr_per\">扫描二维码需相机权限</string>\n    <string name=\"aloud_can_not_auto_page\">朗读正在运行，不能自动翻页</string>\n    <string name=\"input_charset\">输入编码</string>\n    <string name=\"text_chapter_list_rule\">TXT 目录规则</string>\n    <string name=\"open_local_book_per\">打开外部书籍需要存储权限</string>\n    <string name=\"no_book_name\">未获取到书名</string>\n    <string name=\"input_replace_url\">输入替换规则网址</string>\n    <string name=\"get_book_list_success\">搜索列表获取成功 %d</string>\n    <string name=\"non_null_name_url\">名称和 URL 不能为空</string>\n    <string name=\"gallery\">图库</string>\n    <string name=\"get_ali_pay_hb\">领支付宝红包</string>\n    <string name=\"non_update_url\">没有获取到更新地址</string>\n    <string name=\"check_host_cookie\">正在打开首页，成功自动返回主界面</string>\n    <string name=\"click_check_after_success\">登录成功后请点击右上角图标进行首页访问测试</string>\n    <string name=\"chapter\">章</string>\n    <string name=\"to\">至</string>\n    <string name=\"use_regex\">使用正则表达式</string>\n    <string name=\"text_indent\">缩进</string>\n    <string name=\"indent_0\">无缩进</string>\n    <string name=\"indent_1\">一字符缩进</string>\n    <string name=\"indent_2\">二字符缩进</string>\n    <string name=\"indent_3\">三字符缩进</string>\n    <string name=\"indent_4\">四字符缩进</string>\n    <string name=\"select_folder\">选择文件夹</string>\n    <string name=\"select_file\">选择文件</string>\n    <string name=\"no_find\">没有发现，可以在书源里添加。</string>\n    <string name=\"restore_default\">恢复默认</string>\n    <string name=\"set_download_per\">自定义缓存路径需要存储权限</string>\n    <string name=\"black\">黑色</string>\n    <string name=\"content_empty\">文章内容为空</string>\n    <string name=\"on_change_source\">正在换源请等待…</string>\n    <string name=\"chapter_list_empty\">目录列表为空</string>\n    <string name=\"text_letter_spacing\">字距</string>\n\n    <string name=\"source_tab_base\">基本</string>\n    <string name=\"source_tab_search\">搜索</string>\n    <string name=\"source_tab_find\">发现</string>\n    <string name=\"source_tab_info\">详情</string>\n    <string name=\"source_tab_toc\">目录</string>\n    <string name=\"source_tab_content\">正文</string>\n\n    <string name=\"e_ink_mode\">E-Ink 模式</string>\n    <string name=\"e_ink_mode_detail\">去除动画，优化电纸书使用体验</string>\n    <string name=\"web_menu\">Web 服务</string>\n    <string name=\"web_port_title\">Web 端口</string>\n    <string name=\"web_port_summary\">当前端口 %s</string>\n    <string name=\"qr_share\">二维码分享</string>\n    <string name=\"str_share\">字符串分享</string>\n    <string name=\"wifi_share\">WiFi 分享</string>\n    <string name=\"please_grant_storage_permission\">请给予存储权限</string>\n    <string name=\"fast_rewind\">减速</string>\n    <string name=\"fast_forward\">加速</string>\n    <string name=\"skip_previous\">上一个</string>\n    <string name=\"skip_next\">下一个</string>\n    <string name=\"music\">音乐</string>\n    <string name=\"audio\">音频</string>\n    <string name=\"is_enable\">启用</string>\n    <string name=\"enable_js\">启用 JavaScript</string>\n    <string name=\"load_with_base_url\">加载 BaseUrl</string>\n    <string name=\"all_source\">全部书源</string>\n    <string name=\"cannot_empty\">输入不能为空</string>\n    <string name=\"clear_find_cache\">清空发现缓存</string>\n    <string name=\"edit_find\">编辑发现</string>\n    <string name=\"change_icon_summary\">切换软件显示在桌面的图标</string>\n    <string name=\"help\">帮助</string>\n    <string name=\"my\">我的</string>\n    <string name=\"reading\">阅读</string>\n    <string name=\"battery_show\">%d%%</string>\n    <string name=\"timer_m\">%d 分钟</string>\n    <string name=\"brightness_auto\">自动亮度 %s</string>\n    <string name=\"read_aloud_by_page\">按页朗读</string>\n    <string name=\"speak_engine\">朗读引擎</string>\n    <string name=\"bg_image\">背景图片</string>\n    <string name=\"bg_color\">背景颜色</string>\n    <string name=\"text_color\">文字颜色</string>\n    <string name=\"select_image\">选择图片</string>\n    <string name=\"group_manage\">分组管理</string>\n    <string name=\"group_select\">分组选择</string>\n    <string name=\"group_edit\">编辑分组</string>\n    <string name=\"move_to_group\">移入分组</string>\n    <string name=\"add_group\">添加分组</string>\n    <string name=\"remove_group\">移除分组</string>\n    <string name=\"add_replace_rule\">新建替换</string>\n    <string name=\"group\">分组</string>\n    <string name=\"group_s\">分组：%s</string>\n    <string name=\"toc_s\">目录：%s</string>\n    <string name=\"enable_explore\">启用发现</string>\n    <string name=\"disable_explore\">禁用发现</string>\n    <string name=\"enable_selection\">启用所选</string>\n    <string name=\"disable_selection\">禁用所选</string>\n    <string name=\"export_selection\">导出所选</string>\n    <string name=\"export\">导出</string>\n    <string name=\"load_toc\">加载目录</string>\n    <string name=\"load_info\">加载详情页</string>\n    <string name=\"tts\">TTS</string>\n    <string name=\"web_dav_pw\">WebDav 密码</string>\n    <string name=\"web_dav_pw_s\">输入你的 WebDav 授权密码</string>\n    <string name=\"web_dav_url_s\">输入你的服务器地址</string>\n    <string name=\"web_dav_url\">WebDav 服务器地址</string>\n    <string name=\"web_dav_account\">WebDav 账号</string>\n    <string name=\"web_dav_account_s\">输入你的 WebDav 账号</string>\n    <string name=\"rss_source\">订阅源</string>\n    <string name=\"rss_source_edit\">编辑订阅源</string>\n    <string name=\"screen\">筛选</string>\n    <string name=\"screen_find\">筛选发现源</string>\n    <string name=\"dur_pos\">当前位置：</string>\n    <string name=\"precision_search\">精准搜索</string>\n    <string name=\"service_starting\">正在启动服务</string>\n    <string name=\"empty\">空</string>\n    <string name=\"file_chooser\">文件选择</string>\n    <string name=\"folder_chooser\">文件夹选择</string>\n    <string name=\"bottom_line\">我是有底线的</string>\n    <string name=\"uri_to_path_fail\">URI 转 Path 失败</string>\n    <string name=\"refresh_cover\">刷新封面</string>\n    <string name=\"change_cover_source\">封面换源</string>\n    <string name=\"select_local_image\">选择本地图片</string>\n    <string name=\"book_type\">类型：</string>\n    <string name=\"to_backstage\">后台</string>\n    <string name=\"importing\">正在导入</string>\n    <string name=\"exporting\">正在导出</string>\n    <string name=\"custom_page_key\">自定义翻页按键</string>\n    <string name=\"prev_page_key\">上一页按键</string>\n    <string name=\"next_page_key\">下一页按键</string>\n    <string name=\"no_group\">未分组</string>\n    <string name=\"prev_sentence\">上一句</string>\n    <string name=\"next_sentence\">下一句</string>\n    <string name=\"other_folder\">其它目录</string>\n    <string name=\"text_too_long_qr_error\">文字太多，生成二维码失败</string>\n    <string name=\"share_rss_source\">分享 RSS 源</string>\n    <string name=\"share_book_source\">分享书源</string>\n    <string name=\"auto_dark_mode\">自动切换夜间模式</string>\n    <string name=\"auto_dark_mode_s\">夜间模式跟随系统</string>\n    <string name=\"go_back\">上级</string>\n    <string name=\"tone_colour\">在线朗读音色</string>\n    <string name=\"select_count\">（%1$d/%2$d）</string>\n    <string name=\"show_rss\">显示订阅</string>\n    <string name=\"service_stop\">服务已停止</string>\n    <string name=\"service_start\">正在启动服务\\n具体信息请查看通知栏</string>\n    <string name=\"default_path\">默认路径</string>\n    <string name=\"sys_folder_picker\">系统文件夹选择器</string>\n    <string name=\"app_folder_picker\">自带文件夹选择器</string>\n    <string name=\"app_file_picker\">自带文件选择器</string>\n    <string name=\"a10_permission_toast\">Android 10 以上因系统权限限制可能无法读写文件</string>\n    <string name=\"add_to_text_context_menu_s\">长按文字在操作菜单中显示阅读·搜索</string>\n    <string name=\"add_to_text_context_menu_t\">文字操作显示搜索</string>\n    <string name=\"record_log\">记录日志</string>\n    <string name=\"log\">日志</string>\n    <string name=\"chinese_converter\">中文简繁体转换</string>\n    <string name=\"change_icon_error\">图标为矢量图标，Android 8.0 以前不支持</string>\n    <string name=\"aloud_config\">朗读设置</string>\n    <string name=\"main_activity\">主界面</string>\n    <string name=\"selectText\">长按选择文本</string>\n    <string name=\"header\">页眉</string>\n    <string name=\"main_body\">正文</string>\n    <string name=\"footer\">页脚</string>\n    <string name=\"select_end\">文本选择结束位置</string>\n    <string name=\"select_start\">文本选择开始位置</string>\n    <string name=\"share_layout\">共用布局</string>\n    <string name=\"browser\">浏览器</string>\n    <string name=\"import_default_rule\">导入默认规则</string>\n    <string name=\"name\">名称</string>\n    <string name=\"regex\">正则</string>\n    <string name=\"more_menu\">更多菜单</string>\n    <string name=\"reduce\">减</string>\n    <string name=\"plus\">加</string>\n    <string name=\"system_typeface\">系统内置字体样式</string>\n    <string name=\"delete_book_file\">删除源文件</string>\n    <string name=\"text_default\">默认</string>\n    <string name=\"default1\">预设一</string>\n    <string name=\"default2\">预设二</string>\n    <string name=\"default3\">预设三</string>\n    <string name=\"title\">标题</string>\n    <string name=\"title_left\">靠左</string>\n    <string name=\"title_center\">居中</string>\n    <string name=\"title_hide\">隐藏</string>\n    <string name=\"add_to_group\">加入分组</string>\n    <string name=\"save_image\">保存图片</string>\n    <string name=\"no_default_path\">没有默认路径</string>\n    <string name=\"change_group\">设置分组</string>\n    <string name=\"view_toc\">查看目录</string>\n    <string name=\"bar_elevation\">导航栏阴影</string>\n    <string name=\"bar_elevation_s\">当前阴影大小（elevation）：%s</string>\n    <string name=\"btn_default_s\">默认</string>\n    <string name=\"main_menu\">主菜单</string>\n    <string name=\"request_permission\">点击授予权限</string>\n    <string name=\"tip_local_perm_request_storage\">阅读需要访问存储卡权限，请点击下方的“授予权限”按钮，或前往“设置”—“应用权限”，打开所需权限。如果授予权限后仍然不正常，请点击右上角的“选择文件夹”，使用系统文件夹选择器。</string>\n    <string name=\"alouding_disable\">全文朗读中不能朗读选中文字</string>\n    <string name=\"read_body_to_lh\">扩展到刘海</string>\n    <string name=\"toc_updateing\">更新目录中</string>\n    <string name=\"media_button_on_exit_title\">全程响应耳机按键</string>\n    <string name=\"media_button_on_exit_summary\">即使退出软件也响应耳机按键</string>\n    <string name=\"contributors\">开发人员</string>\n    <string name=\"contact\">联系我们</string>\n    <string name=\"license\">开源许可</string>\n    <string name=\"other\">其它</string>\n    <string name=\"official_account\">开源阅读</string>\n    <string name=\"follow_official_account\">关注公众号</string>\n    <string name=\"wechat\">微信</string>\n    <string name=\"thanks\">您的支持是我更新的动力</string>\n    <string name=\"about_official_account\">公众号【开源阅读】</string>\n    <string name=\"source_auto_changing\">正在自动换源</string>\n    <string name=\"click_to_apply\">点击加入</string>\n    <string name=\"middle\">中</string>\n    <string name=\"information\">信息</string>\n    <string name=\"switchLayout\">切换布局</string>\n    <string name=\"text_font_weight_converter\">文章字重切换</string>\n    <string name=\"full_screen_gestures_support\">全面屏手势优化</string>\n    <string name=\"disable_return_key\">禁用返回键</string>\n\n    <!--color-->\n    <string name=\"primary\">主色调</string>\n    <string name=\"accent\">强调色</string>\n    <string name=\"background_color\">背景色</string>\n    <string name=\"navbar_color\">底部操作栏颜色</string>\n    <string name=\"day\">白天</string>\n    <string name=\"day_color_primary\">白天，主色调</string>\n    <string name=\"day_color_accent\">白天，强调色</string>\n    <string name=\"day_background_color\">白天，背景色</string>\n    <string name=\"day_navbar_color\">白天，底栏色</string>\n    <string name=\"night\">夜间</string>\n    <string name=\"night_primary\">夜间，主色调</string>\n    <string name=\"night_accent\">夜间，强调色</string>\n    <string name=\"night_background_color\">夜间，背景色</string>\n    <string name=\"night_navbar_color\">夜间，底栏色</string>\n    <string name=\"auto_change_source\">自动换源</string>\n    <string name=\"text_full_justify\">文字两端对齐</string>\n    <string name=\"text_bottom_justify\">文字底部对齐</string>\n    <string name=\"auto_page_speed\">自动翻页速度</string>\n    <string name=\"sort_by_url\">地址排序</string>\n    <string name=\"backup_summary\">本地和 WebDav 一起备份</string>\n    <string name=\"restore_summary\">优先从 WebDav 恢复，长按从本地恢复</string>\n    <string name=\"import_old_summary\">选择旧版备份文件夹</string>\n    <string name=\"enabled\">已启用</string>\n    <string name=\"disabled\">已禁用</string>\n    <string name=\"enabled_explore\">已启用发现</string>\n    <string name=\"disabled_explore\">已禁用发现</string>\n    <string name=\"starting_download\">正在启动下载</string>\n    <string name=\"already_in_download\">该书已在下载列表</string>\n    <string name=\"click_to_open\">点击打开</string>\n    <string name=\"follow_public_account_summary\">关注【开源阅读】点击广告支持我</string>\n    <string name=\"weChat_appreciation_code\">微信赞赏码</string>\n    <string name=\"alipay\">支付宝</string>\n    <string name=\"alipay_red_envelope_search_code\">支付宝红包搜索码</string>\n    <string name=\"alipay_red_envelope_copy\">537954522 点击复制</string>\n    <string name=\"alipay_red_envelope_qr_code\">支付宝红包二维码</string>\n    <string name=\"alipay_payment_qr_code\">支付宝收款二维码</string>\n    <string name=\"qq_collection_qr_code\">QQ 收款二维码</string>\n    <string name=\"contributors_summary\">gedoor、Invinciblelee 和 Xwite 等，详情请在 GitHub 中查看</string>\n    <string name=\"clear_cache_summary\">清除已下载书籍和字体缓存</string>\n    <string name=\"default_cover\">默认封面</string>\n    <string name=\"restore_ignore\">恢复忽略列表</string>\n    <string name=\"restore_ignore_summary\">恢复时忽略一些内容不恢复，方便不同手机配置不同</string>\n    <string name=\"read_config\">阅读界面设置</string>\n    <string name=\"group_name\">分组名称</string>\n    <string name=\"note_content\">备注内容</string>\n    <string name=\"replace_enable_default_t\">默认启用替换净化</string>\n    <string name=\"replace_enable_default_s\">新加入书架的书是否启用替换净化</string>\n    <string name=\"select_restore_file\">选择恢复文件</string>\n    <string name=\"day_background_too_dark\">白天背景不能太暗</string>\n    <string name=\"day_bottom_bar_too_dark\">白天底栏不能太暗</string>\n    <string name=\"night_background_too_light\">夜间背景不能太亮</string>\n    <string name=\"night_bottom_bar_too_light\">夜间底栏不能太亮</string>\n    <string name=\"accent_background_diff\">强调色不能和背景颜色相似</string>\n    <string name=\"accent_text_diff\">强调色不能和文字颜色相似</string>\n    <string name=\"wrong_format\">格式不对</string>\n    <string name=\"error\">错误</string>\n    <string name=\"show_brightness_view\">显示亮度调节控件</string>\n    <string name=\"language\">语言</string>\n    <string name=\"import_rss_source\">导入订阅源</string>\n    <string name=\"donate_summary\">您的支持是我更新的动力</string>\n    <string name=\"about_summary\">公众号【开源阅读软件】</string>\n    <string name=\"read_record\">阅读记录</string>\n    <string name=\"del_read_record\">删除阅读记录</string>\n    <string name=\"read_record_summary\">阅读时间记录</string>\n    <string name=\"local_tts\">本地 TTS</string>\n    <string name=\"thread_count\">线程数</string>\n    <string name=\"all_read_time\">总阅读时间</string>\n    <string name=\"un_select_all\">全不选</string>\n    <string name=\"import_str\">导入</string>\n    <string name=\"export_str\">导出</string>\n    <string name=\"save_theme_config\">保存主题配置</string>\n    <string name=\"save_day_theme_summary\">保存白天主题配置以供调用和分享</string>\n    <string name=\"save_night_theme_summary\">保存夜间主题配置以供调用和分享</string>\n    <string name=\"theme_list\">主题列表</string>\n    <string name=\"theme_list_summary\">使用、保存、导入或分享主题</string>\n    <string name=\"select_theme\">切换默认主题</string>\n    <string name=\"sort_by_lastUpdateTime\">更新时间排序</string>\n    <string name=\"search_content\">全文搜索</string>\n    <string name=\"search_content_size\">搜索结果</string>\n    <string name=\"search_content_empty\">搜索内容为空，请检查净化或简繁设置</string>\n    <string name=\"rss_source_empty\">RSS 源目前为空！</string>\n    <string name=\"explore_empty\">当前没有发现源！</string>\n    <string name=\"page_key_set_help\">将焦点放到输入框按下物理按键会自动录入键值，多个按键会自动用英文逗号隔开。</string>\n    <string name=\"theme_name\">主题名称</string>\n    <string name=\"auto_clear_expired\">自动清除过期搜索数据</string>\n    <string name=\"auto_clear_expired_summary\">超过一天的搜索数据</string>\n    <string name=\"re_segment\">重新分段</string>\n    <string name=\"style_name\">样式名称：</string>\n    <string name=\"empty_msg_import_book\">点击右上角文件夹图标，选择文件夹</string>\n    <string name=\"scan_folder\">智能扫描</string>\n    <string name=\"import_file_name\">导入文件名</string>\n    <string name=\"copy_book_url\">拷贝书籍 URL</string>\n    <string name=\"copy_toc_url\">拷贝目录 URL</string>\n    <string name=\"no_book\">没有书籍</string>\n    <string name=\"keep_original_name\">保留原名</string>\n    <string name=\"click_regional_config\">点击区域设置</string>\n    <string name=\"close\">关闭</string>\n    <string name=\"next_page\">下一页</string>\n    <string name=\"prev_page\">上一页</string>\n    <string name=\"non_action\">无操作</string>\n    <string name=\"body_title\">正文标题</string>\n    <string name=\"show_hide\">显示/隐藏</string>\n    <string name=\"header_footer\">页眉<![CDATA[&]]>页脚</string>\n    <string name=\"rule_subscription\">规则订阅</string>\n    <string name=\"rule_sub_empty_msg\">添加大佬们提供的规则导入地址\\n添加后点击可导入规则</string>\n    <string name=\"get_book_progress\">拉取云端进度</string>\n    <string name=\"cover_book_progress\">覆盖云端进度</string>\n    <string name=\"current_progress_exceeds_cloud\">当前进度超过云端进度，是否同步？</string>\n    <string name=\"sync_book_progress_t\">同步阅读进度</string>\n    <string name=\"sync_book_progress_s\">进入退出阅读界面时同步阅读进度</string>\n    <string name=\"sync_book_progress_plus_t\">同步增强</string>\n    <string name=\"sync_book_progress_plus_s\">重新进入页面（息屏、后台返回等）或者网络变为可用时同步云端进度，同步新进度会询问</string>\n    <string name=\"create_bookmark_error\">创建书签失败</string>\n    <string name=\"single_url\">单 URL</string>\n    <string name=\"export_bookshelf\">导出书单</string>\n    <string name=\"import_bookshelf\">导入书单</string>\n    <string name=\"pre_download\">预下载</string>\n    <string name=\"pre_download_m\">预下载%s页</string>\n    <string name=\"pre_download_s\">预先下载 %s 章正文</string>\n    <string name=\"is_enabled\">是否启用</string>\n    <string name=\"background_image\">背景图片</string>\n    <string name=\"background_image_blurring\">背景图片虚化</string>\n    <string name=\"background_image_blurring_radius\">虚化半径</string>\n    <string name=\"background_image_hint\">0 为停用，启用范围 1~25\\n半径数值越大，虚化效果越高</string>\n    <string name=\"export_folder\">导出文件夹</string>\n    <string name=\"export_charset\">导出编码</string>\n    <string name=\"export_no_chapter_name\">TXT 不导出章节名</string>\n    <string name=\"export_to_web_dav\">导出到 WebDav</string>\n    <string name=\"reverse_content\">反转内容</string>\n    <string name=\"debug\">调试</string>\n    <string name=\"crash_log\">崩溃日志</string>\n    <string name=\"use_zh_layout\">使用自定义中文分行</string>\n    <string name=\"image_style\">图片样式</string>\n    <string name=\"system_tts\">系统 TTS</string>\n    <string name=\"export_type\">导出格式</string>\n    <string name=\"checkAuthor\">校验作者</string>\n    <string name=\"search_src\">搜索源码</string>\n    <string name=\"boo_src\">书籍源码</string>\n    <string name=\"toc_src\">目录源码</string>\n    <string name=\"content_src\">正文源码</string>\n    <string name=\"list_src\">列表源码</string>\n    <string name=\"url_already\">此 URL 已订阅</string>\n    <string name=\"high_brush_title\">高刷新率</string>\n    <string name=\"high_brush_summary\">使用屏幕最高刷新率</string>\n    <string name=\"export_all\">导出所有</string>\n    <string name=\"complete\">完成</string>\n    <string name=\"show_unread\">显示未读标志</string>\n    <string name=\"use_default_cover\">总是使用默认封面</string>\n    <string name=\"use_default_cover_s\">总是显示默认封面（不显示网络封面）</string>\n    <string name=\"title_font_size\">字号</string>\n    <string name=\"title_margin_top\">上边距</string>\n    <string name=\"title_margin_bottom\">下边距</string>\n    <string name=\"show\">显示</string>\n    <string name=\"hide\">隐藏</string>\n    <string name=\"hide_when_status_bar_show\">状态栏显示时隐藏</string>\n    <string name=\"reverse_toc\">反转目录</string>\n    <string name=\"show_discovery\">显示发现</string>\n    <string name=\"style\">样式</string>\n    <string name=\"group_style\">分组样式</string>\n    <string name=\"export_file_name\">导出文件名</string>\n    <string name=\"reset\">重置</string>\n    <string name=\"null_url\">URL 为空</string>\n    <string name=\"dict\">字典</string>\n    <string name=\"unknown_error\">未知错误</string>\n    <string name=\"autobackup_fail\">自动备份失败\\n%s</string>\n    <string name=\"end\">结束</string>\n    <string name=\"custom_group_summary\">关闭替换分组/开启添加分组</string>\n    <string name=\"pref_media_button_per_next\">媒体按钮•上一首|下一首</string>\n    <string name=\"pref_media_button_per_next_summary\">上一段|下一段/上一章|下一章</string>\n    <string name=\"read_aloud_by_page_summary\">及时翻页，翻页时会停顿一下</string>\n    <string name=\"check_source_show_debug_message\">校验显示详细信息</string>\n    <string name=\"check_source_show_debug_message_summary\">书源校验时显示网络请求步骤和时间</string>\n    <string name=\"need_login\">需要登录</string>\n    <string name=\"pref_cronet_summary\">使用 Cronet 网络组件</string>\n    <string name=\"anti_alias\">抗锯齿</string>\n    <string name=\"pref_anti_alias_summary\">绘制图片时抗锯齿</string>\n    <string name=\"upload_url\">上传 URL</string>\n    <string name=\"download_url_rule\">下载URL规则(downloadUrls)</string>\n    <string name=\"sort_by_respondTime\">响应时间排序</string>\n    <string name=\"respondTime\">响应时间：%1$d ms</string>\n    <string name=\"export_success\">导出成功</string>\n    <string name=\"path\">路径</string>\n    <string name=\"direct_link_upload_rule\">直链上传规则</string>\n    <string name=\"direct_link_upload_rule_summary\">用于导出书源书单时生成直链 URL</string>\n    <string name=\"direct_link_upload_config\">直链上传配置</string>\n    <string name=\"copy_play_url\">拷贝播放 URL</string>\n    <string name=\"set_source_variable\">设置源变量</string>\n    <string name=\"set_book_variable\">设置书籍变量</string>\n    <string name=\"summary\">注释</string>\n    <string name=\"cover_config\">封面设置</string>\n    <string name=\"cover_config_summary\">通用封面规则及默认封面样式</string>\n    <string name=\"cover_show_name\">显示书名</string>\n    <string name=\"cover_show_name_summary\">封面上显示书名</string>\n    <string name=\"cover_show_author\">显示作者</string>\n    <string name=\"cover_show_author_summary\">封面上显示作者</string>\n    <string name=\"read_aloud_prev_paragraph\">朗读上一段</string>\n    <string name=\"read_aloud_next_paragraph\">朗读下一段</string>\n    <string name=\"wait_download\">待下载</string>\n    <string name=\"download_success\">下载完成</string>\n    <string name=\"download_error\">下载失败</string>\n    <string name=\"downloading\">下载中</string>\n    <string name=\"unknown_state\">未知状态</string>\n    <string name=\"disable_source\">禁用源</string>\n    <string name=\"delete_source\">删除源</string>\n    <string name=\"chapter_pay\">购买</string>\n    <string name=\"double_page_horizontal\">平板/横屏双页</string>\n    <string name=\"open_in_browser\">浏览器打开</string>\n    <string name=\"copy_url\">拷贝 URL</string>\n    <string name=\"full_screen\">全屏</string>\n    <string name=\"open_fun\">打开方式</string>\n    <string name=\"use_browser_open\">是否使用外部浏览器打开？</string>\n    <string name=\"see\">查看</string>\n    <string name=\"open\">打开</string>\n    <string name=\"del_login_header\">删除登录头</string>\n    <string name=\"show_login_header\">查看登录头</string>\n    <string name=\"login_header\">登录头</string>\n    <string name=\"font_scale\">字体大小</string>\n    <string name=\"font_scale_summary\">当前字体大小：%.1f</string>\n    <string name=\"tts_speech_reduce\">语速减</string>\n    <string name=\"tts_speech_add\">语速加</string>\n    <string name=\"open_sys_dir_picker_error\">打开系统文件夹选择器出错，将自动打开应用文件夹选择器</string>\n    <string name=\"open_sys_doc_picker_error\">打开系统文件选择器出错，将自动打开应用文件选择器</string>\n    <string name=\"expand_text_menu\">展开文本选择菜单</string>\n    <string name=\"book_tree_uri_t\">书籍保存位置</string>\n    <string name=\"book_tree_uri_s\">从其它应用打开的书籍保存位置</string>\n    <string name=\"select_book_folder\">选择保存书籍的文件夹</string>\n    <string name=\"user_agent\">用户代理</string>\n    <string name=\"bg_alpha\">背景透明度</string>\n\n    <!-- check source config string -->\n    <string name=\"check_source_config\">校验设置</string>\n    <string name=\"check_source_item\">校验项目</string>\n    <string name=\"check_source_timeout\">单个书源校验超时（秒）</string>\n    <string name=\"timeout\">超时</string>\n    <string name=\"seconds\">秒</string>\n    <string name=\"less_than\">小于</string>\n    <string name=\"check_source_config_summary\">校验超时：%1$s秒\\n校验项目：%2$s</string>\n    <string name=\"record_debug_log\">记录调试日志</string>\n    <string name=\"sub_dir\">子文件夹</string>\n    <string name=\"general\">全局</string>\n    <string name=\"use_replace\">使用替换</string>\n    <string name=\"scope_title\">作用于标题</string>\n    <string name=\"scope_content\">作用于正文</string>\n    <string name=\"join_qq_channel\">加入 QQ 频道</string>\n    <string name=\"qq_channel_summary\">点击加入阅读 QQ 频道</string>\n    <string name=\"menu_refresh_dur\">刷新当前章节</string>\n    <string name=\"menu_refresh_after\">刷新之后章节</string>\n    <string name=\"menu_refresh_all\">刷新全部章节</string>\n    <string name=\"edit_content\">编辑内容</string>\n    <string name=\"chapter_change_source\">单章换源</string>\n    <string name=\"book_change_source\">整书换源</string>\n    <string name=\"sort_by_time\">时间排序</string>\n    <string name=\"enable_record\">开启记录</string>\n    <string name=\"copy_all\">拷贝所有</string>\n    <string name=\"auto_complete\">自动补全</string>\n    <string name=\"sort_by_size\">大小排序</string>\n    <string name=\"welcome_style\">启动界面样式</string>\n    <string name=\"welcome_style_summary\">启动界面图片和是否显示文字等</string>\n    <string name=\"show_welcome_text\">显示文字</string>\n    <string name=\"welcome_text\">阅读|享受美好时光</string>\n    <string name=\"custom_welcome\">自定义欢迎页</string>\n    <string name=\"custom_welcome_summary\">是否使用自定义欢迎页</string>\n    <string name=\"show_icon\">显示图标</string>\n    <string name=\"show_default_book_icon\">显示默认书籍图标</string>\n    <string name=\"cache_export\">缓存/导出</string>\n    <string name=\"assists_key_config\">辅助按键配置</string>\n    <string name=\"url_option\">URL 参数</string>\n    <string name=\"only_wifi\">仅 WiFi</string>\n    <string name=\"only_wifi_summary\">仅在 WiFi 下加载网络封面</string>\n    <string name=\"cover_rule\">封面规则</string>\n    <string name=\"cover_rule_summary\">进入详情页时使用封面规则重新获取封面</string>\n    <string name=\"scroll_to_dur_source\">定位到当前书源</string>\n    <string name=\"sys_tts_config\">系统 TTS 设置</string>\n    <string name=\"sys_tts_config_summary\">打开系统 TTS 设置界面</string>\n    <string name=\"cannot_timed_non_playback\">非播放状态无法定时</string>\n    <string name=\"all_bookmark\">所有书签</string>\n    <string name=\"change_source_batch\">批量换源</string>\n    <string name=\"book_type_different\">书籍类型不一样</string>\n    <string name=\"soure_change_source\">是否确认换源</string>\n    <string name=\"input_verification_code\">输入验证码</string>\n    <string name=\"verification_code\">验证码</string>\n    <string name=\"timeout_millisecond\">超时毫秒数</string>\n    <string name=\"file_not_supported\">文件 %1$s 不受支持，是否继续打开？</string>\n    <string name=\"import_tts\">导入 TTS</string>\n    <string name=\"import_theme\">导入主题</string>\n    <string name=\"import_txt_toc_rule\">导入 TXT 目录规则</string>\n    <string name=\"auto_save_cookie\">CookieJar</string>\n    <string name=\"cookie\">清除 Cookie</string>\n    <string name=\"download_and_import_file\">导入在线书籍文件</string>\n    <string name=\"upload_book_success\">上传成功</string>\n    <string name=\"upload_book_fail\">上传失败</string>\n    <string name=\"download_book_success\">下载成功</string>\n    <string name=\"download_book_fail\">下载失败</string>\n    <string name=\"upload_to_remote\">上传 WebDav</string>\n    <string name=\"add_remote_book\">远程书籍</string>\n    <string name=\"bitmap_cache_size_summary\">当前最大缓存 %1$s MB</string>\n    <string name=\"bitmap_cache_size\">图片绘制缓存</string>\n    <string name=\"image_retain_number_summary\">保留已读章节数量 %s</string>\n    <string name=\"image_retain_number\">漫画保留数量</string>\n    <string name=\"export_pics_file\">TXT 导出图片</string>\n    <!-- string end -->\n    <string name=\"error_decode_bitmap\">图片解码失败</string>\n    <string name=\"error_image_url_empty\">图片链接为空，检查替换净化规则</string>\n    <string name=\"variable_comment\">变量说明(variableComment)</string>\n    <string name=\"remote_book\">远程书籍</string>\n    <string name=\"reading_time_sort\">阅读时长排序</string>\n    <string name=\"last_read_time_sort\">阅读时间排序</string>\n    <string name=\"reading_time_tag\">阅读时长:</string>\n    <string name=\"last_read_time_tag\">最后阅读时间:</string>\n    <string name=\"page_touch_slop_title\">滑动翻页阈值</string>\n    <string name=\"page_touch_slop_dialog_title\">滑动翻页阈值（0 = 系统默认值）</string>\n    <string name=\"page_touch_slop_summary\">滑动多长距离才会触发滑动翻页（系统默认值 %s px）</string>\n    <string name=\"example\">示例</string>\n    <string name=\"check_selected_interval\">选中所选区间</string>\n    <string name=\"show_add_to_shelf_alert_title\">返回时提示放入书架</string>\n    <string name=\"show_add_to_shelf_alert_summary\">阅读未放入书架的书籍在返回时提示放入书架</string>\n    <string name=\"review\">段评</string>\n    <string name=\"rule_review_url\">段评URL（reviewUrl）</string>\n    <string name=\"rule_avatar\">段评发布者头像（avatarRule）</string>\n    <string name=\"rule_review_content\">段评内容（contentRule）</string>\n    <string name=\"rule_post_time\">段评发布时间（postTimeRule）</string>\n    <string name=\"rule_review_quote\">段评回复URL（reviewQuoteUrl）</string>\n    <string name=\"review_vote_down\">点踩URL（voteDownUrl）</string>\n    <string name=\"review_vote_up\">点赞URL（voteUpUrl）</string>\n    <string name=\"post_review_url\">发送回复URL（postReviewUrl）</string>\n    <string name=\"post_quote_url\">发送回复段评URL（postQuoteUrl）</string>\n    <string name=\"delete_review_url\">删除段评URL（deleteUrl）</string>\n    <string name=\"tag_explore_enabled\">标志:发现已启用</string>\n    <string name=\"tag_explore_disabled\">标志:发现已禁用</string>\n    <string name=\"show_read_title_addition\">展示顶部工具栏附加区域</string>\n    <string name=\"read_bar_style_follow_page\">工具栏样式跟随页面</string>\n    <string name=\"rule_image_decode\">图片解密（imageDecode）</string>\n    <string name=\"like_source\">赞</string>\n    <string name=\"not_like_source\">踩</string>\n    <string name=\"async_load_image\">异步加载图片</string>\n    <string name=\"ignore_audio_focus_title\">忽略音频焦点</string>\n    <string name=\"ignore_audio_focus_summary\">允许与其他应用同时播放音频</string>\n    <string name=\"refresh_sort\">刷新分类</string>\n    <string name=\"cover_decode_js\">封面解密（coverDecodeJs）</string>\n    <string name=\"net_no_group\">网络未分组</string>\n    <string name=\"local_no_group\">本地未分组</string>\n    <string name=\"parallel_export_book\">多线程导出</string>\n    <string name=\"progress_bar_behavior\">进度条行为</string>\n    <string name=\"source_edit_text_max_line\">源编辑框最大行数</string>\n    <string name=\"source_edit_max_line_summary\">%s,设置行数小于屏幕可显示的最大行数可以更方便的滑动到其他的字段进行编辑</string>\n    <string name=\"restore_last_book_process\">是否恢复到跳转前的阅读进度？</string>\n    <string name=\"search_scope\">搜索范围</string>\n    <string name=\"toggle_search_scope\">切换</string>\n    <string name=\"sure_clear_search_history\">是否确认清除所有搜索历史记录</string>\n    <string name=\"no_anim_scroll_page\">禁用滚动点击动画</string>\n    <string name=\"webdav_device_name\">设备名称</string>\n    <string name=\"web_service_wake_lock\">WebService唤醒锁</string>\n    <string name=\"web_service_wake_lock_summary\">开启web服务的时候启用唤醒锁,有些手机开启唤醒锁会被杀后台</string>\n    <string name=\"read_aloud_wake_lock\">朗读服务唤醒锁</string>\n    <string name=\"read_aloud_wake_lock_summary\">开启朗读的时候启用唤醒锁,有些手机开启唤醒锁会被杀后台</string>\n    <string name=\"audio_play_wake_lock\">音频服务唤醒锁</string>\n    <string name=\"audio_play_wake_lock_summary\">播放音频的时候启用唤醒锁,有些手机开启唤醒锁会被杀后台</string>\n    <string name=\"change_search_scope\">切换搜索范围</string>\n    <string name=\"copy_rule\">拷贝规则</string>\n    <string name=\"paste_rule\">粘贴规则</string>\n    <string name=\"groups_or_source\">多分组/书源</string>\n    <string name=\"replace_state_change\">替换(启用/禁用)</string>\n    <string name=\"show_last_update_time\">显示上次更新时间</string>\n    <string name=\"refresh_list\">刷新列表</string>\n    <string name=\"tip_divider_color\">分隔线颜色</string>\n    <string name=\"same_title_removed\">移除重复标题</string>\n    <string name=\"update_book_fail\">更新失败</string>\n    <string name=\"notification_permission_rationale\">阅读需要发送通知来显示朗读控制和下载进度</string>\n    <string name=\"webdav_after_local_restore_confirm\">webDav书源比本地新,是否恢复</string>\n    <string name=\"c_whitelist\">白名单(contentWhitelist)</string>\n    <string name=\"c_blacklist\">黑名单(contentBlacklist)</string>\n    <string name=\"confirm\">确认</string>\n    <string name=\"jump_to_another_app\">跳转其它应用</string>\n    <string name=\"clear_webview_data\">清除 WebView 数据</string>\n    <string name=\"clear_webview_data_summary\">清除内置浏览器所有数据</string>\n    <string name=\"source_tab_list\">列表</string>\n    <string name=\"dict_rule\">字典规则</string>\n    <string name=\"config_dict_rule\">配置字典规则</string>\n    <string name=\"create\">新建</string>\n    <string name=\"url_rule\">url规则(urlRule)</string>\n    <string name=\"show_rule\">显示规则(showRule)</string>\n    <string name=\"config_txt_toc_rule\">配置 TXT 目录规则</string>\n    <string name=\"import_dict_rule\">导入字典规则</string>\n    <string name=\"keep_group\">保留分组</string>\n    <string name=\"server_config\">服务器配置</string>\n    <string name=\"sure_upload\">远程webDav链接已存在，是否继续</string>\n    <string name=\"unsupport_archivefile_entry\">压缩文件内没有支持的文件</string>\n    <string name=\"no_books_dir\">没有设置书籍保存位置!</string>\n    <string name=\"delete_alert\">删除提醒</string>\n    <string name=\"no_book_found_bookshelf\">未在书架上找到所选书籍, 是否重新导入？</string>\n    <string name=\"archive_not_found\">未能在书籍保存目录找到压缩文件，是否重新导入？</string>\n    <string name=\"privacy_policy\">用户隐私与协议</string>\n    <string name=\"agree\">同意</string>\n    <string name=\"refuse\">拒绝</string>\n    <string name=\"file_manage\">文件管理</string>\n    <string name=\"file_manage_summary\">管理私有文件夹的文件</string>\n    <string name=\"create_folder\">创建文件夹</string>\n    <string name=\"allow_drop_down_refresh\">允许下拉刷新</string>\n    <string name=\"text_underline\">文字下划线</string>\n    <string name=\"select_new_source\">选中新增源</string>\n    <string name=\"select_update_source\">选中更新源</string>\n    <string name=\"set_local_password\">设置本地密码</string>\n    <string name=\"set_local_password_summary\">本地密码用来对备份的敏感信息加密和解密,如需在不同设备之间同步,本地密码需一致.</string>\n    <string name=\"only_latest_backup_t\">仅保留最新备份</string>\n    <string name=\"only_latest_backup_s\">本地备份仅保留最新备份文件</string>\n    <string name=\"webdav_application_authorization_error\">webDav应用验证失败</string>\n    <string name=\"load_word_count\">加载字数</string>\n    <string name=\"replace_exclude_scope\">排除范围，选填书名或者书源 URL</string>\n    <string name=\"format_js_rule\">格式化规则(formatJs)</string>\n    <string name=\"adjust_pos\">调整位置</string>\n    <string name=\"error_scope_input\">请输入正确的范围</string>\n    <string name=\"custom_export\">自定义导出</string>\n    <string name=\"file_contains_number\">每个文件包含的章节数量</string>\n    <string name=\"export_chapter_index\">需要输出的章节</string>\n    <string name=\"shrink_database\">压缩数据库</string>\n    <string name=\"shrink_database_summary\">减小数据库文件的大小</string>\n    <string name=\"is_compress\">是否压缩</string>\n    <string name=\"sort_desc\">反序</string>\n    <string name=\"test\">测试</string>\n    <string name=\"show_wait_up_count\">显示等待更新数量</string>\n    <string name=\"exit_app\">退出软件</string>\n    <string name=\"result_analyzed\">解析示例</string>\n    <string name=\"bookshelf_px_4\">综合排序</string>\n    <string name=\"bookshelf_px_5\">按作者</string>\n    <string name=\"effective_replaces\">起效的替换</string>\n    <string name=\"export_book\">导出书籍</string>\n    <string name=\"export_book_notification_content\">正在导出(%1$s),还有%2$d本待导出</string>\n    <string name=\"export_md\">导出(MD)</string>\n    <string name=\"change_source_delay\">换源间隔</string>\n    <string name=\"change_source_progress\">结果 %1$d, 当前进度 %2$d / %3$d: %4$s</string>\n    <string name=\"open_book_info_by_click_title\">点击书名打开详情</string>\n    <string name=\"export_wait\">等待导出</string>\n    <string name=\"sync_book_progress_success\">进度同步成功</string>\n    <string name=\"default_home_page\">默认主页</string>\n    <string name=\"show_bookshelf_fast_scroller\">显示快速滚动条</string>\n    <string name=\"export_all_use_book_source\">导出所有书的书源</string>\n    <string name=\"cloud_progress_exceeds_current\">云端进度超过当前进度，是否同步？</string>\n    <string name=\"keep_enable\">保留启用状态</string>\n    <string name=\"preview_image_by_click\">点击预览图片</string>\n    <string name=\"screen_portrait_reversed\">反向竖屏</string>\n    <string name=\"del_ruby_tag\">删除ruby标签</string>\n    <string name=\"del_h_tag\">删除h标签</string>\n    <string name=\"adjust_chapter_page\">调整本章页数</string>\n    <string name=\"adjust_chapter_index\">调整章节位置</string>\n    <string name=\"clear_webview_data_success\">清除成功，3秒后自动重启应用</string>\n    <string name=\"key_page_on_long_press\">按键长按翻页</string>\n    <string name=\"save_log\">保存日志</string>\n    <string name=\"create_heap_dump\">创建堆转储</string>\n    <string name=\"record_heap_dump_s\">当应用发生OOM崩溃时保存堆转储</string>\n    <string name=\"record_heap_dump_t\">记录堆转储</string>\n    <string name=\"font_weight_text\">中/粗/细</string>\n    <string name=\"keep_swipe_tip\">继续滑动以加载下一章…</string>\n    <string name=\"enable_optimize_render\">启用绘制优化</string>\n    <string name=\"ignore_battery_permission_rationale\">阅读需要请求后台权限以保持服务正常运行</string>\n    <string name=\"simulated_reading\">模拟追读</string>\n    <string name=\"switch_on\">开关</string>\n    <string name=\"start_from\">开始日期</string>\n    <string name=\"daily_chapters\">日更章数</string>\n    <string name=\"start_chapter\">起始章节</string>\n    <string name=\"update_to_variant_title\">检查更新查找版本</string>\n    <string name=\"update_to_variant_summary\">检查更新时查找其他签名版本</string>\n    <string name=\"default_version\">当前</string>\n    <string name=\"official_version\">正式版</string>\n    <string name=\"beta_release_version\">测试版</string>\n    <string name=\"beta_releaseA_version\">共存版</string>\n    <string name=\"stream_read_aloud_audio\">流式播放音频</string>\n    <string name=\"stream_read_aloud_audio_summary\">即边下边播，网络不好时播放会断断续续，仅TTS源有效</string>\n    <string name=\"pause_read_aloud_while_phone_calls_title\">来电期间暂停朗读</string>\n    <string name=\"pause_read_aloud_while_phone_calls_summary\">在通话期间暂停朗读，需要读取手机状态权限</string>\n    <string name=\"read_aloud_read_phone_state_permission_rationale\">阅读需要读取手机状态实现来电期间暂停朗读功能</string>\n    <string name=\"read_aloud_by_media_button_title\">耳机按键启动朗读</string>\n    <string name=\"read_aloud_by_media_button_summary\">通过耳机按键来启动朗读</string>\n    <string name=\"group_sources_by_domain\">按域名分组显示</string>\n    <string name=\"theme_config\">主题配置</string>\n    <string name=\"show_manga_ui\">漫画浏览</string>\n    <string name=\"disable_manga_scale\">禁用漫画缩放</string>\n    <string name=\"disable_manga_click_scroll\">禁用点击翻页</string>\n    <string name=\"enable_auto_page_scroll\">开启自动翻页</string>\n    <string name=\"manga_auto_page_speed\">翻页速度 %s</string>\n    <string name=\"enable_auto_scroll\">开启滚动</string>\n    <string name=\"manga_footer_config\">页脚配置</string>\n    <string name=\"setting_manga_auto_page_speed\">设置自动翻页速度</string>\n    <string name=\"book_reader_info_bar\">页数. %1$d/%2$d  章节. %3$d/%4$d -> %5$s%%</string>\n    <string name=\"menu_download_after\">下载之后章节</string>\n    <string name=\"menu_download_all\">下载全部章节</string>\n    <string name=\"manga_header_chapter\">《章节和文案》隐藏</string>\n    <string name=\"manga_check_chapter_label\">《章节.》文案</string>\n    <string name=\"manga_check_chapter\">章节</string>\n    <string name=\"manga_check_chapter_name\">章节名称</string>\n    <string name=\"manga_header_page\">《页数和文案》隐藏</string>\n    <string name=\"manga_check_page_label\">《页数.》文案</string>\n    <string name=\"manga_check_page_number\">页数</string>\n    <string name=\"manga_header_progress\">《总进度和文案》隐藏</string>\n    <string name=\"manga_check_progress_label\">《总进度.》文案</string>\n    <string name=\"manga_check_progress\">总进度</string>\n    <string name=\"manga_header_footer\">页脚</string>\n    <string name=\"manga_radio_left\">靠左</string>\n    <string name=\"manga_radio_center\">居中</string>\n    <string name=\"enable_manga_horizontal_scroll\">水平滚动</string>\n    <string name=\"manga_color_filter\">滤镜</string>\n    <string name=\"hide_manga_title\">隐藏漫画列表标题</string>\n    <string name=\"refresh_explore\">刷新发现</string>\n    <string name=\"padding_display_cutouts\">填充刘海区域</string>\n    <string name=\"system_media_control_compatibility_change\">系统媒体控件兼容性更改</string>\n    <string name=\"system_media_control_compatibility_change_summary\">当锁屏不显示系统媒体控件时可以尝试开启，比如oneui7.0或vivo等</string>\n    <string name=\"read_aloud_pause_resume\">朗读暂停/继续</string>\n    <string name=\"manga_epaper\">墨水屏</string>\n    <string name=\"manga_epaper_stting\">墨水屏设置</string>\n    <string name=\"manga_epaper_value\">阈值</string>\n    <string name=\"disable_horizontal_page_snap\">禁用水平翻页效果</string>\n    <string name=\"enable_manga_gray\">开启图片灰色</string>\n    <string name=\"sure_cache_book\">是否确认开始缓存？</string>\n    <string name=\"auto_check_new_backup_t\">自动检查新备份</string>\n    <string name=\"auto_check_new_backup_s\">打开软件时检查是否有新备份，有新备份时提示是否更新</string>\n    <string name=\"sys_image_picker\">系统图片选择器</string>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values-zh-rHK/arrays.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n\n    <string-array name=\"book_type\">\n        <item>文本</item>\n        <item>音頻</item>\n        <item>图片</item>\n        <item>文件</item>\n    </string-array>\n\n    <string-array name=\"group_style\">\n        <item>標籤</item>\n        <item>文件夾</item>\n    </string-array>\n\n    <string-array name=\"theme_mode\">\n        <item>跟隨系統</item>\n        <item>亮色主題</item>\n        <item>暗色主題</item>\n        <item>E-Ink(墨水螢幕)</item>\n    </string-array>\n\n    <string-array name=\"NavBarColors\">\n        <item>自動</item>\n        <item>黑色</item>\n        <item>白色</item>\n        <item>跟隨背景</item>\n    </string-array>\n\n    <string-array name=\"screen_time_out\">\n        <item>默認</item>\n        <item>1分鐘</item>\n        <item>5分鐘</item>\n        <item>10分鐘</item>\n        <item>常亮</item>\n    </string-array>\n\n    <string-array name=\"chinese_mode\">\n        <item>關閉</item>\n        <item>繁體轉簡體</item>\n        <item>簡體轉繁體</item>\n    </string-array>\n\n    <string-array name=\"system_typefaces\">\n        <item>系統默認字體</item>\n        <item>系統襯線字體</item>\n        <item>系統等寬字體</item>\n    </string-array>\n\n    <string-array name=\"read_tip\">\n        <item>無</item>\n        <item>書名</item>\n        <item>標題</item>\n        <item>時間</item>\n        <item>電量</item>\n        <item>電量%</item>\n        <item>頁數</item>\n        <item>進度(%)</item>\n        <item>進度(xx/yyy)</item>\n        <item>頁數同埋進度</item>\n        <item>時間同埋電量</item>\n        <item>時間同埋電量%</item>\n    </string-array>\n\n    <string-array name=\"text_font_weight\">\n        <item>正常</item>\n        <item>粗體</item>\n        <item>細體</item>\n    </string-array>\n\n    <string-array name=\"language\">\n        <item>跟隨系統</item>\n        <item>簡體中文</item>\n        <item>繁體中文</item>\n        <item>英文</item>\n    </string-array>\n\n    <string-array name=\"rule_type\">\n        <item>書源</item>\n        <item>訂閲源</item>\n        <item>替換規則</item>\n    </string-array>\n\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values-zh-rHK/strings.xml",
    "content": "<resources>\n    <!--App-->\n    <string name=\"app_name\">閲讀</string>\n    <string name=\"app_name_a\">閲讀·A</string>\n    <string name=\"receiving_shared_label\">閲讀·搜尋</string>\n    <string name=\"tip_perm_request_storage\">閲讀需要訪問存儲卡權限，請前往「設定」—「應用程式權限」—開啟所需要的權限</string>\n\n    <!--Other-->\n    <string name=\"menu_backup\">Home</string>\n    <string name=\"menu_restore\">還原</string>\n    <string name=\"menu_import_old\">導入閲讀數據</string>\n    <string name=\"webdav_cache_backup\">離線緩存書籍備份</string>\n    <string name=\"webdav_cache_backup_s\">導出本地同時備份到legado文件夾下exports目錄</string>\n    <string name=\"backup_path\">備份路徑</string>\n    <string name=\"menu_import_old_version\">導入舊版數據</string>\n    <string name=\"menu_import_github\">導入 Github 數據</string>\n    <string name=\"menu_replace_rule\">淨化替換</string>\n    <string name=\"menu_send\">Send</string>\n\n    <string name=\"dialog_title\">提示</string>\n    <string name=\"dialog_cancel\">取消</string>\n    <string name=\"dialog_confirm\">確認</string>\n    <string name=\"dialog_setting\">去設定</string>\n    <string name=\"tip_cannot_jump_setting_page\">無法轉跳至設定介面</string>\n\n    <string name=\"dynamic_click_retry\">點擊重試</string>\n    <string name=\"dynamic_loading\">正在加載</string>\n    <string name=\"draw\">提醒</string>\n    <string name=\"edit\">編輯</string>\n    <string name=\"delete\">刪除</string>\n    <string name=\"delete_select_group\">刪除當前分組</string>\n    <string name=\"replace\">替換</string>\n    <string name=\"replace_purify\">替換淨化</string>\n    <string name=\"replace_purify_desc\">配置替換淨化規則</string>\n    <string name=\"not_available\">暫無</string>\n    <string name=\"enable\">啟用</string>\n    <string name=\"replace_purify_search\">替換淨化-搜尋</string>\n    <string name=\"custom_export_section\">自定義Epub導出章節</string>\n    <string name=\"bookshelf\">書架</string>\n    <string name=\"favorites\">收藏夾</string>\n    <string name=\"favorite\">收藏</string>\n    <string name=\"in_favorites\">已收藏</string>\n    <string name=\"out_favorites\">未收藏</string>\n    <string name=\"rss\">訂閲</string>\n    <string name=\"all\">全部</string>\n    <string name=\"recent_reading\">最近閲讀</string>\n    <string name=\"last_read\">最後閲讀</string>\n    <string name=\"update_log\">更新日誌</string>\n    <string name=\"bookshelf_empty\">書架還空著，先去搜索書籍或從發現裏添加吧！</string>\n    <string name=\"action_search\">搜尋</string>\n    <string name=\"action_download\">下載</string>\n    <string name=\"layout_list\">列表</string>\n    <string name=\"layout_grid3\">網格三列</string>\n    <string name=\"layout_grid4\">網格四列</string>\n    <string name=\"layout_grid5\">網格五列</string>\n    <string name=\"layout_grid6\">網格六列</string>\n    <string name=\"bookshelf_layout\">書架佈局</string>\n    <string name=\"view\">視圖</string>\n    <string name=\"book_library\">書城</string>\n    <string name=\"book_local\">添加本地</string>\n    <string name=\"book_source\">書源</string>\n    <string name=\"book_source_manage\">書源管理</string>\n    <string name=\"book_source_manage_desc\">新建/導入/編輯/管理書源</string>\n    <string name=\"setting\">設定</string>\n    <string name=\"theme_setting\">主題設定</string>\n    <string name=\"theme_setting_s\">同主題/顏色相關的一些設定</string>\n    <string name=\"other_setting\">其它設定</string>\n    <string name=\"other_setting_s\">與功能相關的一些設定</string>\n    <string name=\"about\">關於</string>\n    <string name=\"donate\">捐贈</string>\n    <string name=\"exit\">退出</string>\n    <string name=\"exit_no_save\">尚未保存，是否繼續編輯</string>\n    <string name=\"read_style\">閲讀樣式設定</string>\n    <string name=\"version\">版本</string>\n    <string name=\"local\">本地</string>\n    <string name=\"search\">搜尋</string>\n    <string name=\"origin_format\">來源: %s</string>\n    <string name=\"read_dur_progress\">最近: %s</string>\n    <string name=\"book_name\">書名</string>\n    <string name=\"lasted_show\">最新: %s</string>\n    <string name=\"check_add_bookshelf\">是否將《%s》放入書架？</string>\n    <string name=\"import_books_count\">共 %s 個 Text 文件</string>\n    <string name=\"is_loading\">載入中…</string>\n    <string name=\"retry\">重試</string>\n    <string name=\"web_service\">Web 服務</string>\n    <string name=\"web_service_desc\">瀏覽器寫源,看書</string>\n    <string name=\"web_edit_source\">web 編輯書源</string>\n    <string name=\"offline_cache\">離線緩存</string>\n    <string name=\"offline_cache_t\">離線緩存</string>\n    <string name=\"offline_cache_s\">緩存已選擇的章節到本地</string>\n    <string name=\"change_origin\">換源</string>\n    <string name=\"about_description\">\n        \\u3000\\u3000這是一款使用 Kotlin 全新開發的開源的閲讀應用程式，歡迎你的加入。\n    </string>\n    <string name=\"app_share_description\">\n        閲讀3.0下載地址：\\nhttps://github.com/gedoor/legado/releases\n    </string>\n    <string name=\"version_name\">Version %s</string>\n    <string name=\"pt_background_verification\">後臺校驗書源</string>\n    <string name=\"ps_background_verification\">打開后可以在校驗書源時自由操作</string>\n    <string name=\"pt_auto_refresh\">自動刷新</string>\n    <string name=\"ps_auto_refresh\">打開程式時自動更新書輯</string>\n    <string name=\"pt_auto_download\">自動下載最新章節</string>\n    <string name=\"ps_auto_download\">更新書輯時自動下載最新章節</string>\n    <string name=\"backup_restore\">備份與還原</string>\n    <string name=\"web_dav_set\">WebDav 設定</string>\n    <string name=\"web_dav_set_import_old\">WebDav 設定/還原舊版本數據</string>\n    <string name=\"backup\">備份</string>\n    <string name=\"restore\">還原</string>\n    <string name=\"backup_permission\">備份請給予存儲權限</string>\n    <string name=\"restore_permission\">還原請給予存儲權限</string>\n    <string name=\"ok\">確認</string>\n    <string name=\"cancel\">取消</string>\n    <string name=\"backup_confirmation\">確認備份嗎？</string>\n    <string name=\"backup_message\">新備份會覆蓋原有備份。\\n備份路徑YueDu</string>\n    <string name=\"restore_confirmation\">確認還原嗎？</string>\n    <string name=\"restore_message\">還原成功會覆蓋原有書架。</string>\n    <string name=\"backup_success\">備份成功</string>\n    <string name=\"backup_fail\">備份失敗\\n%s</string>\n    <string name=\"on_restore\">正在還原</string>\n    <string name=\"restore_success\">還原成功</string>\n    <string name=\"restore_fail\">還原失敗</string>\n    <string name=\"screen_direction\">屏幕方向</string>\n    <string name=\"screen_sensor\">跟隨傳感器</string>\n    <string name=\"screen_landscape\">橫向</string>\n    <string name=\"screen_portrait\">豎向</string>\n    <string name=\"screen_unspecified\">跟隨系統</string>\n    <string name=\"disclaimer\">免責聲明</string>\n    <string name=\"all_chapter_num\">共%d章</string>\n    <string name=\"interface_setting\">介面</string>\n    <string name=\"brightness\">亮度</string>\n    <string name=\"chapter_list\">目錄</string>\n    <string name=\"next_chapter\">下一章</string>\n    <string name=\"previous_chapter\">上一章</string>\n    <string name=\"pt_hide_status_bar\">隱藏狀態欄</string>\n    <string name=\"ps_hide_status_bar\">閲讀介面隱藏狀態欄</string>\n    <string name=\"read_aloud\">朗讀</string>\n    <string name=\"read_aloud_t\">正在朗讀</string>\n    <string name=\"read_aloud_s\">點擊打開閲讀介面</string>\n    <string name=\"audio_play\">播放</string>\n    <string name=\"audio_play_t\">正在播放</string>\n    <string name=\"audio_play_s\">點擊打開播放介面</string>\n    <string name=\"audio_pause\">播放暫停</string>\n    <string name=\"text_return\">返回</string>\n    <string name=\"refresh\">刷新</string>\n    <string name=\"start\">開始</string>\n    <string name=\"stop\">停止</string>\n    <string name=\"pause\">暫停</string>\n    <string name=\"resume\">繼續</string>\n    <string name=\"set_timer\">定時</string>\n    <string name=\"read_aloud_pause\">朗讀暫停</string>\n    <string name=\"read_aloud_timer\">讀緊（剩餘 %d 分鐘）</string>\n    <string name=\"playing_timer\">播緊（剩餘 %d 分鐘）</string>\n    <string name=\"ps_hide_navigation_bar\">閲讀介面隱藏導航欄</string>\n    <string name=\"pt_hide_navigation_bar\">隱藏導航欄</string>\n    <string name=\"re_navigation_bar_color\">導航欄顏色</string>\n    <string name=\"scoring\">評分</string>\n    <string name=\"send_mail\">發送電子郵件</string>\n    <string name=\"can_not_open\">無法打開</string>\n    <string name=\"can_not_share\">分享失敗</string>\n    <string name=\"no_chapter\">無章節</string>\n    <string name=\"add_url\">添加網址</string>\n    <string name=\"add_book_url\">添加書輯網址</string>\n    <string name=\"background\">背景</string>\n    <string name=\"author\">作者</string>\n    <string name=\"author_show\">作者: %s</string>\n    <string name=\"aloud_stop\">朗讀停止</string>\n    <string name=\"clear_cache\">清理緩存</string>\n    <string name=\"clear_cache_success\">成功清理緩存</string>\n    <string name=\"action_save\">保存</string>\n    <string name=\"edit_source\">編輯源</string>\n    <string name=\"edit_book_source\">編輯書源</string>\n    <string name=\"disable_book_source\">禁用書源</string>\n    <string name=\"add_book_source\">新建書源</string>\n    <string name=\"add_rss_source\">新建訂閲源</string>\n    <string name=\"book_file_selector\">添加書輯</string>\n    <string name=\"scan_book_source\">掃描</string>\n    <string name=\"copy_source\">拷貝源</string>\n    <string name=\"paste_source\">粘帖源</string>\n    <string name=\"source_rule_s\">源規則説明</string>\n    <string name=\"check_update\">檢查更新</string>\n    <string name=\"camera_scan\">掃描 QR Code</string>\n    <string name=\"scan_image\">掃描本地圖片</string>\n    <string name=\"rule_summary\">規則説明</string>\n    <string name=\"share\">分享</string>\n    <string name=\"share_app\">應用程式分享</string>\n    <string name=\"flow_sys\">跟隨系統</string>\n    <string name=\"add\">添加</string>\n    <string name=\"import_book_source\">導入書源</string>\n    <string name=\"import_local\">本地導入</string>\n    <string name=\"import_on_line\">網絡導入</string>\n    <string name=\"replace_rule_title\">替換淨化</string>\n    <string name=\"replace_rule_edit\">替換規則編輯</string>\n    <string name=\"replace_rule\">替換規則</string>\n    <string name=\"replace_to\">替換為</string>\n    <string name=\"img_cover\">封面</string>\n    <string name=\"book\">書</string>\n    <string name=\"volume_key_page\">音量鍵翻頁</string>\n    <string name=\"mouse_wheel_page\">鼠标滚动翻页</string>\n    <string name=\"click_turn_page\">點擊翻頁</string>\n    <string name=\"page_anim\">翻頁動畫</string>\n    <string name=\"book_page_anim\">翻頁動畫(本書)</string>\n    <string name=\"keep_light\">屏幕超時</string>\n    <string name=\"back\">返回</string>\n    <string name=\"menu\">菜單</string>\n    <string name=\"adjust\">調節</string>\n    <string name=\"scroll_bar\">滾動條</string>\n    <string name=\"clear_all_content\">清除緩存會刪除所有已保存的章節，確認是否清除？</string>\n    <string name=\"book_source_share_url\">書源共享</string>\n    <string name=\"replace_rule_summary\">規則替換名稱</string>\n    <string name=\"replace_rule_invalid\">替換規則為空或不滿足正則表達式要求</string>\n    <string name=\"select_action\">選擇操作</string>\n    <string name=\"select_all\">全選</string>\n    <string name=\"select_all_count\">全選 (%1$d/%2$d)</string>\n    <string name=\"select_cancel_count\">取消全選 (%1$d/%2$d)</string>\n    <string name=\"dark_theme\">深色模式</string>\n    <string name=\"welcome\">啟動頁</string>\n    <string name=\"download_start\">開始下載</string>\n    <string name=\"download_cancel\">取消下載</string>\n    <string name=\"no_download\">暫無任務</string>\n    <string name=\"download_count\">已下載 %1$d/%2$d</string>\n    <string name=\"import_select_book\">導入選擇書輯</string>\n    <string name=\"threads_num_title\">更新/搜尋線程數，太多會卡頓</string>\n    <string name=\"change_icon\">切換圖標</string>\n    <string name=\"remove_from_bookshelf\">刪除書輯</string>\n    <string name=\"start_read\">開始閲讀</string>\n    <string name=\"data_loading\">數據載入中…</string>\n    <string name=\"load_error_retry\">載入失敗，點擊重試</string>\n    <string name=\"book_intro\">內容簡介</string>\n    <string name=\"intro_show\">簡介: %s</string>\n    <string name=\"intro_show_null\">簡介: 暫無簡介</string>\n    <string name=\"open_from_other\">打開外部書籍</string>\n    <string name=\"origin_show\">來源: %s</string>\n    <string name=\"import_replace_rule\">導入替換規則</string>\n    <string name=\"import_replace_rule_on_line\">導入在線規則</string>\n    <string name=\"check_update_interval\">檢查更新間隔</string>\n    <string name=\"bookshelf_px_0\">按閲讀時間</string>\n    <string name=\"bookshelf_px_1\">按更新時間</string>\n    <string name=\"bookshelf_px_2\">按書名</string>\n    <string name=\"bookshelf_px_3\">手動排序</string>\n    <string name=\"read_type\">閲讀方式</string>\n    <string name=\"compose_type\">排版</string>\n    <string name=\"del_select\">刪除所選</string>\n    <string name=\"del_msg\">是否確認刪除?</string>\n    <string name=\"clear_font\">默認字體</string>\n    <string name=\"find_on_www\">發現</string>\n    <string name=\"find_source_manage\">發現管理</string>\n    <string name=\"find_empty\">沒有內容,去書源裏自定義吧!</string>\n    <string name=\"del_all\">刪除所有</string>\n    <string name=\"searchHistory\">搜索歷史</string>\n    <string name=\"clear\">清除</string>\n    <string name=\"showTitle\">正文顯示標題</string>\n    <string name=\"refresh_default\">書源同步</string>\n    <string name=\"no_last_chapter\">無最新章節信息</string>\n    <string name=\"showTimeBattery\">顯示時間和電量</string>\n    <string name=\"showLine\">顯示分隔線</string>\n    <string name=\"dark_status_icon\">深色狀態欄圖標</string>\n    <string name=\"content\">內容</string>\n    <string name=\"copy_text\">拷貝內容</string>\n    <string name=\"download_all\">一鍵緩存</string>\n    <string name=\"content_sl\">這是一段測試文字\\n\\u3000\\u3000只是讓你看看效果的</string>\n    <string name=\"text_bg_style\">文字顏色和背景(長按自定義)</string>\n    <string name=\"immersion_status_bar\">沉浸式狀態欄</string>\n    <string name=\"un_download\">還剩 %d 章未下載</string>\n    <string name=\"long_click_input_color\">長按輸入顏色值</string>\n    <string name=\"loading\">加載中…</string>\n    <string name=\"group_zg\">追更區</string>\n    <string name=\"group_yf\">養肥區</string>\n    <string name=\"bookmark\">書籤</string>\n    <string name=\"bookmark_add\">添加書籤</string>\n    <string name=\"action_del\">刪除</string>\n    <string name=\"load_over_time\">加載超時</string>\n    <string name=\"join_group\">關注: %s</string>\n    <string name=\"copy_complete\">拷貝咗</string>\n    <string name=\"bookshelf_management\">書架管理</string>\n    <string name=\"clear_bookshelf_s\">這將會刪除所有書籍，請謹慎操作。</string>\n    <string name=\"search_book_source\">搜索書源</string>\n    <string name=\"search_rss_source\">搜索訂閲源</string>\n    <string name=\"search_book_source_num\">搜索(共 %d 個書源)</string>\n    <string name=\"chapter_list_size\">目錄(%d)</string>\n    <string name=\"text_bold\">加粗</string>\n    <string name=\"text_font\">字體</string>\n    <string name=\"text\">文字</string>\n    <string name=\"home_page\">軟件主頁</string>\n    <string name=\"right\">右</string>\n    <string name=\"left\">左</string>\n    <string name=\"bottom\">下</string>\n    <string name=\"top\">上</string>\n    <string name=\"padding\">邊距</string>\n    <string name=\"padding_top\">上邊距</string>\n    <string name=\"padding_bottom\">下邊距</string>\n    <string name=\"padding_left\">左邊距</string>\n    <string name=\"padding_right\">右邊距</string>\n    <string name=\"check_book_source\">校驗書源</string>\n    <string name=\"check_select_source\">校驗所選</string>\n    <string name=\"progress_show\">%1$s      進度 %2$d/%3$d</string>\n    <string name=\"tts_fix\">請安裝並選擇中文 TTS!</string>\n    <string name=\"tts_init_failed\">TTS 初始化失敗!</string>\n    <string name=\"jf_convert\">簡繁轉換</string>\n    <string name=\"jf_convert_o\">關閉</string>\n    <string name=\"jf_convert_f\">簡轉繁</string>\n    <string name=\"jf_convert_j\">繁轉簡</string>\n    <string name=\"page_mode\">翻頁模式</string>\n    <string name=\"nb_file_sub_count\">%1$d 項</string>\n    <string name=\"nb_file_path\">存儲咭:</string>\n    <string name=\"nb_file_add_shelf\">加入書架</string>\n    <string name=\"nb_file_add_shelves\">加入書架 (%1$d)</string>\n    <string name=\"nb_file_add_succeed\">成功添加 %1$d 本書</string>\n    <string name=\"fonts_folder\">請將字體文件放到 SD 根目錄 Fonts 文件夾下重新選擇</string>\n    <string name=\"default_font\">默認字體</string>\n    <string name=\"select_font\">選擇字體</string>\n    <string name=\"text_size\">字號</string>\n    <string name=\"line_size\">行距</string>\n    <string name=\"paragraph_size\">段距</string>\n    <string name=\"to_top\">置頂</string>\n    <string name=\"selection_to_top\">置頂所選</string>\n    <string name=\"to_bottom\">置底</string>\n    <string name=\"selection_to_bottom\">置底所選</string>\n    <string name=\"auto_expand_find\">自動展開發現</string>\n    <string name=\"default_expand_first\">默認展開第一組發現</string>\n    <string name=\"threads_num\">當前線程數 %s</string>\n    <string name=\"read_aloud_speed\">朗讀語速</string>\n    <string name=\"auto_next_page\">自動翻頁</string>\n    <string name=\"auto_next_page_stop\">停止自動翻頁</string>\n    <string name=\"auto_next_page_speed\">自動翻頁間隔</string>\n    <string name=\"book_info\">書籍信息</string>\n    <string name=\"book_info_edit\">書籍信息編輯</string>\n    <string name=\"ps_default_read\">默認打開書架</string>\n    <string name=\"pt_default_read\">自動跳轉最近閲讀</string>\n    <string name=\"replace_scope\">替換範圍，選填書名或者書源url</string>\n    <string name=\"menu_action_group\">分組</string>\n    <string name=\"download_path\">內容緩存路徑</string>\n    <string name=\"sys_file_picker\">系統文件選擇器</string>\n    <string name=\"new_version\">新版本</string>\n    <string name=\"download_update\">下載更新</string>\n    <string name=\"volume_key_page_on_play\">朗讀時音量鍵翻頁</string>\n    <string name=\"tip_margin_change\">Tip 邊距跟隨邊距調整</string>\n    <string name=\"allow_update\">允許更新</string>\n    <string name=\"disable_update\">禁止更新</string>\n    <string name=\"split_long_chapter\">拆分超长章节</string>\n    <string name=\"need_more_time_load_content\">正文长度过长时，加载正文可能会花费更多时间</string>\n    <string name=\"revert_selection\">反選</string>\n    <string name=\"search_book_key\">搜索書名、作者</string>\n    <string name=\"debug_hint\">書名、作者、URL</string>\n    <string name=\"faq\">常見問題</string>\n    <string name=\"pt_show_all_find\">顯示所有發現</string>\n    <string name=\"ps_show_all_find\">關閉則只顯示勾選源的發現</string>\n    <string name=\"update_toc\">更新目錄</string>\n    <string name=\"txt_toc_rule\">TXT 目錄規則</string>\n    <string name=\"set_charset\">設置編碼</string>\n    <string name=\"swap_sort\">倒序-順序</string>\n    <string name=\"sort\">排序</string>\n    <string name=\"sort_auto\">智能排序</string>\n    <string name=\"sort_default\">默認排序</string>\n    <string name=\"sort_manual\">手動排序</string>\n    <string name=\"sort_by_name\">名稱排序</string>\n    <string name=\"go_to_top\">滾動到頂部</string>\n    <string name=\"go_to_bottom\">滾動到底部</string>\n    <string name=\"read_y\">已讀: %s</string>\n    <string name=\"pursue_more\">追更</string>\n    <string name=\"fattening\">養肥</string>\n    <string name=\"finish\">完結</string>\n    <string name=\"all_book\">所有書籍</string>\n    <string name=\"pursue_more_book\">追更書籍</string>\n    <string name=\"fattening_book\">養肥書籍</string>\n    <string name=\"finish_book\">完結書籍</string>\n    <string name=\"local_book\">本地書籍</string>\n    <string name=\"status_bar_immersion\">狀態欄顏色透明</string>\n    <string name=\"imm_navigation_bar\">沉浸式导航栏</string>\n    <string name=\"imm_navigation_bar_s\">导航栏颜色透明</string>\n    <string name=\"add_to_bookshelf\">放入書架</string>\n    <string name=\"continue_read\">繼續閲讀</string>\n    <string name=\"cover_path\">封面地址</string>\n    <string name=\"page_anim_cover\">覆蓋</string>\n    <string name=\"page_anim_slide\">滑動</string>\n    <string name=\"page_anim_simulation\">仿真</string>\n    <string name=\"page_anim_scroll\">滾動</string>\n    <string name=\"page_anim_none\">無動畫</string>\n    <string name=\"disable_manga_page_anim\">禁用翻頁動畫</string>\n    <string name=\"up_change_source_last_chapter_t\">後台更新換源最新章節</string>\n    <string name=\"up_change_source_last_chapter_s\">開啟則會在軟件打開 1 分鐘後開始更新</string>\n    <string name=\"behavior_main_t\">書架 ToolBar 自動隱藏</string>\n    <string name=\"behavior_main_s\">滾動書架時 ToolBar 自動隱藏與顯示</string>\n    <string name=\"login\">登錄</string>\n    <string name=\"login_source\">登錄 %s</string>\n    <string name=\"success\">成功</string>\n    <string name=\"source_no_login\">當前源沒有配置登陸地址</string>\n    <string name=\"no_prev_page\">沒有上一頁</string>\n    <string name=\"no_next_page\">沒有下一頁</string>\n\n    <!-- source start-->\n    <string name=\"source_name\">源名稱 (sourceName)</string>\n    <string name=\"source_url\">源URL (sourceUrl)</string>\n    <string name=\"source_group\">源分組 (sourceGroup)</string>\n    <string name=\"sort_url\">分類 Url(sortUrl)</string>\n    <string name=\"concurrent_rate\">並發率(concurrentRate)</string>\n    <string name=\"login_url\">登錄 URL(loginUrl)</string>\n    <string name=\"login_ui\">登錄UI(loginUi)</string>\n    <string name=\"login_check_js\">登錄檢查JS(loginCheckJs)</string>\n    <string name=\"comment\">源注釋(sourceComment)</string>\n    <string name=\"r_search_url\">搜索地址 (url)</string>\n    <string name=\"r_find_url\">發現地址規則 (url)</string>\n    <string name=\"r_book_list\">書籍列表規則 (bookList)</string>\n    <string name=\"r_book_name\">書名規則 (name)</string>\n    <string name=\"r_book_url\">詳情頁 url 規則 (bookUrl)</string>\n    <string name=\"r_author\">作者規則 (author)</string>\n    <string name=\"rule_book_kind\">分類規則 (kind)</string>\n    <string name=\"rule_book_intro\">簡介規則 (intro)</string>\n    <string name=\"rule_cover_url\">封面規則 (coverUrl)</string>\n    <string name=\"rule_last_chapter\">最新章節規則 (lastChapter)</string>\n    <string name=\"rule_word_count\">字數規則 (wordCount)</string>\n    <string name=\"book_url_pattern\">書籍 URL 正則 (bookUrlPattern)</string>\n    <string name=\"rule_book_info_init\">預處理規則 (bookInfoInit)</string>\n    <string name=\"rule_toc_url\">目錄 URL 規則 (tocUrl)</string>\n    <string name=\"rule_can_re_name\">允許修改書名作者 (canReName)</string>\n    <string name=\"rule_next_toc_url\">目錄下一頁規則 (nextTocUrl)</string>\n    <string name=\"rule_chapter_list\">目錄列表規則 (chapterList)</string>\n    <string name=\"rule_chapter_name\">章節名稱規則 (ChapterName)</string>\n    <string name=\"rule_chapter_url\">章節 URL 規則 (chapterUrl)</string>\n    <string name=\"rule_is_volume\">Volume標識(isVolume)</string>\n    <string name=\"rule_is_vip\">VIP 標識 (isVip)</string>\n    <string name=\"rule_update_time\">更新時間 (ChapterInfo)</string>\n    <string name=\"pre_update_js\">更新之前Js(preUpdateJs)</string>\n    <string name=\"rule_book_content\">正文規則 (content)</string>\n    <string name=\"rule_next_content\">正文下一頁 URL 規則 (nextContentUrl)</string>\n    <string name=\"rule_web_js\">WebViewJs (webJs)</string>\n    <string name=\"rule_source_regex\">資源正則 (sourceRegex)</string>\n    <string name=\"rule_pay_action\">购买操作(payAction)</string>\n    <string name=\"source_icon\">圖標 (sourceIcon)</string>\n    <string name=\"r_articles\">列表規則 (ruleArticles)</string>\n    <string name=\"r_next\">列表下一頁規則 (ruleArticles)</string>\n    <string name=\"r_title\">標題規則 (ruleTitle)</string>\n    <string name=\"r_guid\">guid 規則 (ruleGuid)</string>\n    <string name=\"r_date\">時間規則 (rulePubDate)</string>\n    <string name=\"r_categories\">類別規則 (ruleCategories)</string>\n    <string name=\"r_description\">描述規則 (ruleDescription)</string>\n    <string name=\"r_image\">圖片 url 規則 (ruleImage)</string>\n    <string name=\"r_content\">內容規則 (ruleContent)</string>\n    <string name=\"r_style\">樣式 (style)</string>\n    <string name=\"r_inject_js\">注入Js(injectJs)</string>\n    <string name=\"r_link\">鏈接規則 (ruleLink)</string>\n    <string name=\"check_key_word\">校驗關鍵字(checkKeyWord)</string>\n    <string name=\"rule_actions\">操作(actions)</string>\n    <string name=\"rule_is_pay\">購買標誌(isPay)</string>\n    <!-- source end-->\n\n    <!--error string start-->\n    <string name=\"error_no_source\">沒有源</string>\n    <string name=\"error_get_book_info\">書籍信息獲取失敗</string>\n    <string name=\"error_get_content\">內容獲取失敗</string>\n    <string name=\"error_get_chapter_list\">目錄獲取失敗</string>\n    <string name=\"error_get_web_content\">訪問網站失敗: %s</string>\n    <string name=\"error_read_file\">文件讀取失敗</string>\n    <string name=\"error_load_toc\">加載目錄失敗</string>\n    <string name=\"error_get_data\">獲取數據失敗！</string>\n    <string name=\"error_load_msg\">加載失敗\\n%s</string>\n    <string name=\"net_error_10001\">沒有網絡</string>\n    <string name=\"net_error_10002\">網絡連接超時</string>\n    <string name=\"net_error_10003\">數據解析失敗</string>\n    <!--error string end-->\n\n    <string name=\"source_http_header\">請求頭 (header)</string>\n    <string name=\"debug_source\">調試源</string>\n    <string name=\"import_by_qr_code\">二維碼導入</string>\n    <string name=\"scan_qr_code\">掃描二維碼</string>\n    <string name=\"click_on_selected_show_menu\">選中時點擊可彈出菜單</string>\n    <string name=\"theme\">主題</string>\n    <string name=\"theme_mode\">主題模式</string>\n    <string name=\"theme_mode_desc\">選擇主題模式</string>\n    <string name=\"join_qq_group\">加入QQ羣</string>\n    <string name=\"bg_image_per\">獲取背景圖片需存儲權限</string>\n    <string name=\"input_book_source_url\">輸入書源網址</string>\n    <string name=\"del_file\">刪除文件</string>\n    <string name=\"del_file_success\">刪除文件成功</string>\n    <string name=\"sure_del_file\">確定刪除文件嗎?</string>\n    <string name=\"files_tree\">手機目錄</string>\n    <string name=\"intelligent_import\">智能導入</string>\n    <string name=\"discovery\">發現</string>\n    <string name=\"switch_display_style\">切換顯示樣式</string>\n    <string name=\"import_per\">導入本地書籍需存儲權限</string>\n    <string name=\"night_theme\">夜間模式</string>\n    <string name=\"eink_theme\">E-Ink 模式</string>\n    <string name=\"eink_theme_desc\">電子墨水屏模式</string>\n    <string name=\"get_storage_per\">需要存儲權限</string>\n    <string name=\"double_click_exit\">再按一次退出程式</string>\n    <string name=\"import_book_per\">導入本地書籍需存儲權限</string>\n    <string name=\"network_connection_unavailable\">網絡連接不可用</string>\n    <string name=\"yes\">係</string>\n    <string name=\"no\">唔係</string>\n    <string name=\"sure\">確認</string>\n    <string name=\"sure_del\">是否確認刪除？</string>\n    <string name=\"sure_del_any\">是否確認刪除 %s？</string>\n    <string name=\"sure_del_all_book\">是否刪除全部書籍？</string>\n    <string name=\"sure_del_download_book\">是否同時刪除已下載的書籍目錄？</string>\n    <string name=\"qr_per\">掃描二維碼需相機權限</string>\n    <string name=\"aloud_can_not_auto_page\">朗讀正在運行,不能自動翻頁</string>\n    <string name=\"input_charset\">輸入編碼</string>\n    <string name=\"text_chapter_list_rule\">TXT 目錄規則</string>\n    <string name=\"open_local_book_per\">打開外部書籍需獲取存儲權限</string>\n    <string name=\"no_book_name\">未獲取到書名</string>\n    <string name=\"input_replace_url\">輸入替換規則網址</string>\n    <string name=\"get_book_list_success\">搜索列表獲取成功%d</string>\n    <string name=\"non_null_name_url\">名稱和 URL 不能為空</string>\n    <string name=\"gallery\">圖庫</string>\n    <string name=\"get_ali_pay_hb\">領支付寶紅包</string>\n    <string name=\"non_update_url\">沒有獲取到更新地址</string>\n    <string name=\"check_host_cookie\">正在打開首頁，成功自動返回主界面</string>\n    <string name=\"click_check_after_success\">登錄成功後請點擊右上角圖標進行首頁訪問測試</string>\n    <string name=\"chapter\">章</string>\n    <string name=\"to\">至</string>\n    <string name=\"use_regex\">使用正則表達式</string>\n    <string name=\"text_indent\">縮進</string>\n    <string name=\"indent_0\">無縮進</string>\n    <string name=\"indent_1\">一字符縮進</string>\n    <string name=\"indent_2\">二字符縮進</string>\n    <string name=\"indent_3\">三字符縮進</string>\n    <string name=\"indent_4\">四字符縮進</string>\n    <string name=\"select_folder\">選擇文件夾</string>\n    <string name=\"select_file\">選擇文件</string>\n    <string name=\"no_find\">沒有發現，可以在書源裏添加。</string>\n    <string name=\"restore_default\">恢復默認</string>\n    <string name=\"set_download_per\">自定義緩存路徑需要存儲權限</string>\n    <string name=\"black\">黑色</string>\n    <string name=\"content_empty\">文章內容為空</string>\n    <string name=\"on_change_source\">正在換源請等待…</string>\n    <string name=\"chapter_list_empty\">目錄列表為空</string>\n    <string name=\"text_letter_spacing\">字距</string>\n\n    <string name=\"source_tab_base\">基本</string>\n    <string name=\"source_tab_search\">搜索</string>\n    <string name=\"source_tab_find\">發現</string>\n    <string name=\"source_tab_info\">詳情</string>\n    <string name=\"source_tab_toc\">目錄</string>\n    <string name=\"source_tab_content\">正文</string>\n\n    <string name=\"e_ink_mode\">E-Ink 模式</string>\n    <string name=\"e_ink_mode_detail\">去除動畫，優化電紙書使用體驗</string>\n    <string name=\"web_menu\">Web 服務</string>\n    <string name=\"web_port_title\">web 端口</string>\n    <string name=\"web_port_summary\">當前端口 %s</string>\n    <string name=\"qr_share\">二維碼分享</string>\n    <string name=\"str_share\">字符串分享</string>\n    <string name=\"wifi_share\">wifi 分享</string>\n    <string name=\"please_grant_storage_permission\">請給於存儲權限</string>\n    <string name=\"fast_rewind\">減速</string>\n    <string name=\"fast_forward\">加速</string>\n    <string name=\"skip_previous\">上一個</string>\n    <string name=\"skip_next\">下一個</string>\n    <string name=\"music\">音樂</string>\n    <string name=\"audio\">音頻</string>\n    <string name=\"is_enable\">啟用</string>\n    <string name=\"enable_js\">啟用 JS</string>\n    <string name=\"load_with_base_url\">加載 BaseUrl</string>\n    <string name=\"all_source\">全部書源</string>\n    <string name=\"cannot_empty\">輸入不能為空</string>\n    <string name=\"clear_find_cache\">清空發現緩存</string>\n    <string name=\"edit_find\">編輯發現</string>\n    <string name=\"change_icon_summary\">切換軟件顯示在桌面的圖標</string>\n    <string name=\"help\">幫助</string>\n    <string name=\"my\">我的</string>\n    <string name=\"reading\">閲讀</string>\n    <string name=\"battery_show\">%d%%</string>\n    <string name=\"timer_m\">%d 分鐘</string>\n    <string name=\"brightness_auto\">自動亮度 %s</string>\n    <string name=\"read_aloud_by_page\">按頁朗讀</string>\n    <string name=\"speak_engine\">朗讀引擎</string>\n    <string name=\"bg_image\">背景圖片</string>\n    <string name=\"bg_color\">背景顏色</string>\n    <string name=\"text_color\">文字顏色</string>\n    <string name=\"select_image\">選擇圖片</string>\n    <string name=\"group_manage\">分組管理</string>\n    <string name=\"group_select\">分組選擇</string>\n    <string name=\"group_edit\">編輯分組</string>\n    <string name=\"move_to_group\">移入分組</string>\n    <string name=\"add_group\">添加分組</string>\n    <string name=\"remove_group\">移除分組</string>\n    <string name=\"add_replace_rule\">新建替換</string>\n    <string name=\"group\">分組</string>\n    <string name=\"group_s\">分組: %s</string>\n    <string name=\"toc_s\">目錄: %s</string>\n    <string name=\"enable_explore\">啟用發現</string>\n    <string name=\"disable_explore\">禁用發現</string>\n    <string name=\"enable_selection\">啟用所選</string>\n    <string name=\"disable_selection\">禁用所選</string>\n    <string name=\"export_selection\">導出所選</string>\n    <string name=\"export\">導出</string>\n    <string name=\"load_toc\">加載目錄</string>\n    <string name=\"load_info\">加載詳情頁</string>\n    <string name=\"tts\">TTS</string>\n    <string name=\"web_dav_pw\">WebDav 密碼</string>\n    <string name=\"web_dav_pw_s\">輸入你的 WebDav 授權密碼</string>\n    <string name=\"web_dav_url_s\">輸入你的服務器地址</string>\n    <string name=\"web_dav_url\">WebDav 服務器地址</string>\n    <string name=\"web_dav_account\">WebDav 賬號</string>\n    <string name=\"web_dav_account_s\">輸入你的 WebDav 賬號</string>\n    <string name=\"rss_source\">訂閲源</string>\n    <string name=\"rss_source_edit\">編輯訂閲源</string>\n    <string name=\"screen\">篩選</string>\n    <string name=\"screen_find\">篩選發現源</string>\n    <string name=\"dur_pos\">當前位置：</string>\n    <string name=\"precision_search\">精準搜索</string>\n    <string name=\"service_starting\">正在啟動服務</string>\n    <string name=\"empty\">空</string>\n    <string name=\"file_chooser\">文件選擇</string>\n    <string name=\"folder_chooser\">文件夾選擇</string>\n    <string name=\"bottom_line\">我是有底線的</string>\n    <string name=\"uri_to_path_fail\">Uri 轉 Path 失敗</string>\n    <string name=\"refresh_cover\">刷新封面</string>\n    <string name=\"change_cover_source\">封面換源</string>\n    <string name=\"select_local_image\">選擇本地圖片</string>\n    <string name=\"book_type\">類型: </string>\n    <string name=\"to_backstage\">後台</string>\n    <string name=\"importing\">正在導入</string>\n    <string name=\"exporting\">正在導出</string>\n    <string name=\"custom_page_key\">自定義翻頁按鍵</string>\n    <string name=\"prev_page_key\">上一頁按鍵</string>\n    <string name=\"next_page_key\">下一頁按鍵</string>\n    <string name=\"no_group\">未分組</string>\n    <string name=\"prev_sentence\">上一句</string>\n    <string name=\"next_sentence\">下一句</string>\n    <string name=\"other_folder\">其它目錄</string>\n    <string name=\"text_too_long_qr_error\">文字太多，生成二維碼失敗</string>\n    <string name=\"share_rss_source\">分享RSS源</string>\n    <string name=\"share_book_source\">分享書源</string>\n    <string name=\"auto_dark_mode\">自動切換夜間模式</string>\n    <string name=\"auto_dark_mode_s\">夜間模式跟隨系統</string>\n    <string name=\"go_back\">上級</string>\n    <string name=\"tone_colour\">在線朗讀音色</string>\n    <string name=\"select_count\">(%1$d/%2$d)</string>\n    <string name=\"show_rss\">顯示訂閲</string>\n    <string name=\"service_stop\">服務已停止</string>\n    <string name=\"service_start\">正在啟動服務\\n具體信息查看通知欄</string>\n    <string name=\"default_path\">默認路徑</string>\n    <string name=\"sys_folder_picker\">系統文件夾選擇器</string>\n    <string name=\"app_folder_picker\">自帶文件夾選擇器</string>\n    <string name=\"app_file_picker\">自帶文件選擇器</string>\n    <string name=\"a10_permission_toast\">Android10 以上因權限限制可能無法讀寫文件</string>\n    <string name=\"add_to_text_context_menu_s\">長按文字在操作菜單中顯示閲讀·搜索</string>\n    <string name=\"add_to_text_context_menu_t\">文字操作顯示搜索</string>\n    <string name=\"record_log\">記錄日誌</string>\n    <string name=\"log\">日誌</string>\n    <string name=\"chinese_converter\">中文簡繁體轉換</string>\n    <string name=\"change_icon_error\">圖標為矢量圖標，Android8.0 以前不支持</string>\n    <string name=\"aloud_config\">朗讀設置</string>\n    <string name=\"main_activity\">主界面</string>\n    <string name=\"selectText\">長按選擇文本</string>\n    <string name=\"header\">頁眉</string>\n    <string name=\"main_body\">正文</string>\n    <string name=\"footer\">頁腳</string>\n    <string name=\"select_end\">文本選擇結束位置</string>\n    <string name=\"select_start\">文本選擇開始位置</string>\n    <string name=\"share_layout\">共用佈局</string>\n    <string name=\"browser\">瀏覽器</string>\n    <string name=\"import_default_rule\">導入默認規則</string>\n    <string name=\"name\">名稱</string>\n    <string name=\"regex\">正則</string>\n    <string name=\"more_menu\">更多菜單</string>\n    <string name=\"reduce\">減</string>\n    <string name=\"plus\">加</string>\n    <string name=\"system_typeface\">系統內置字體樣式</string>\n    <string name=\"delete_book_file\">刪除源文件</string>\n    <string name=\"text_default\">默认</string>\n    <string name=\"default1\">預設一</string>\n    <string name=\"default2\">預設二</string>\n    <string name=\"default3\">預設三</string>\n    <string name=\"title\">標題</string>\n    <string name=\"title_left\">靠左</string>\n    <string name=\"title_center\">居中</string>\n    <string name=\"title_hide\">隱藏</string>\n    <string name=\"add_to_group\">加入分組</string>\n    <string name=\"save_image\">保存圖片</string>\n    <string name=\"no_default_path\">沒有默認路徑</string>\n    <string name=\"change_group\">設置分組</string>\n    <string name=\"view_toc\">查看目錄</string>\n    <string name=\"bar_elevation\">導航欄陰影</string>\n    <string name=\"bar_elevation_s\">當前陰影大小 (elevation): %s</string>\n    <string name=\"btn_default_s\">默認</string>\n    <string name=\"main_menu\">主菜單</string>\n    <string name=\"request_permission\">點擊授予權限</string>\n    <string name=\"tip_local_perm_request_storage\">閲讀需要訪問存儲卡權限，請點擊下方的\"授予權限\"按鈕，或前往「設定」—「應用程式權限」—打開所需權限。如果授予權限後仍然不正常，請點擊右上角的「選擇文件夾」，使用系統文件夾選擇器。</string>\n    <string name=\"alouding_disable\">全文朗讀中不能朗讀選中文字</string>\n    <string name=\"read_body_to_lh\">擴展到劉海</string>\n    <string name=\"toc_updateing\">更新目錄中</string>\n    <string name=\"media_button_on_exit_title\">全程響應耳機按鍵</string>\n    <string name=\"media_button_on_exit_summary\">即使退出軟件也響應耳機按鍵</string>\n    <string name=\"contributors\">開發人員</string>\n    <string name=\"contact\">聯繫我們</string>\n    <string name=\"license\">開源許可</string>\n    <string name=\"follow_official_account\">關注公眾號</string>\n    <string name=\"wechat\">WeChat</string>\n    <string name=\"thanks\">你的支持是我更新的動力</string>\n    <string name=\"about_official_account\">公眾號[开源阅读]</string>\n    <string name=\"source_auto_changing\">正在自動換源</string>\n    <string name=\"click_to_apply\">點擊加入</string>\n    <string name=\"middle\">中</string>\n    <string name=\"information\">信息</string>\n    <string name=\"switchLayout\">切換佈局</string>\n    <string name=\"full_screen_gestures_support\">全面屏手勢優化</string>\n    <string name=\"disable_return_key\">禁用返回鍵</string>\n\n    <!--color-->\n    <string name=\"primary\">主色調</string>\n    <string name=\"accent\">強調色</string>\n    <string name=\"background_color\">背景色</string>\n    <string name=\"navbar_color\">導航欄顏色</string>\n    <string name=\"day\">白天</string>\n    <string name=\"day_color_primary\">白天，主色調</string>\n    <string name=\"day_color_accent\">白天，強調色</string>\n    <string name=\"day_background_color\">白天，背景色</string>\n    <string name=\"day_navbar_color\">白天，導航欄顏色</string>\n    <string name=\"night\">夜間</string>\n    <string name=\"night_primary\">夜間，主色調</string>\n    <string name=\"night_accent\">夜間，強調色</string>\n    <string name=\"night_background_color\">夜間，背景色</string>\n    <string name=\"night_navbar_color\">夜間，導航欄顏色</string>\n    <string name=\"auto_change_source\">自動換源</string>\n    <string name=\"text_full_justify\">文字兩端對齊</string>\n    <string name=\"auto_page_speed\">自動翻頁速度</string>\n    <string name=\"sort_by_url\">地址排序</string>\n    <string name=\"text_font_weight_converter\">文章字體轉換</string>\n    <string name=\"select_backup_path\">請選擇備份路徑</string>\n    <string name=\"other\">其它</string>\n    <string name=\"official_account\">legado-top</string>\n    <string name=\"backup_summary\">本地和WebDav壹起備份</string>\n    <string name=\"restore_summary\">優先從WebDav恢復,長按從本地恢復</string>\n    <string name=\"import_old_summary\">選擇舊版備份文件夾</string>\n    <string name=\"enabled\">已啓用</string>\n    <string name=\"disabled\">已禁用</string>\n    <string name=\"enabled_explore\">已啟用發現</string>\n    <string name=\"disabled_explore\">已禁用發現</string>\n    <string name=\"text_bottom_justify\">文字底部對齊</string>\n    <string name=\"starting_download\">正在啟動下載</string>\n    <string name=\"already_in_download\">該書已在下載列表</string>\n    <string name=\"click_to_open\">點擊打開</string>\n    <string name=\"follow_public_account_summary\">關注[legado-top]點擊廣告支持我</string>\n    <string name=\"weChat_appreciation_code\">微信讚賞碼</string>\n    <string name=\"alipay\">支付寶</string>\n    <string name=\"alipay_red_envelope_search_code\">支付寶紅包搜索碼</string>\n    <string name=\"alipay_red_envelope_copy\">537954522 點擊複製</string>\n    <string name=\"alipay_red_envelope_qr_code\">支付寶紅包二維碼</string>\n    <string name=\"alipay_payment_qr_code\">支付寶收款二維碼</string>\n    <string name=\"qq_collection_qr_code\">QQ收款二維碼</string>\n    <string name=\"contributors_summary\">gedoor,Invinciblelee,Xwite等,詳情請在github中查看</string>\n    <string name=\"clear_cache_summary\">清除已下載書籍和字體緩存</string>\n    <string name=\"default_cover\">默認封面</string>\n    <string name=\"restore_ignore\">恢復忽略列表</string>\n    <string name=\"restore_ignore_summary\">恢復時忽略一些內容不恢復,方便不同手機配置不同</string>\n    <string name=\"read_config\">閱讀界面設置</string>\n    <string name=\"rule_image_style\">圖片樣式 (imageStyle)</string>\n    <string name=\"rule_replace_regex\">替換規則 (replaceRegex)</string>\n    <string name=\"group_name\">分組名稱</string>\n    <string name=\"note_content\">備註內容</string>\n    <string name=\"replace_enable_default_t\">默認啟用替換淨化</string>\n    <string name=\"replace_enable_default_s\">新加入書架的書是否啟用替換淨化</string>\n    <string name=\"select_restore_file\">選擇恢復文件</string>\n    <string name=\"day_background_too_dark\">白天背景不能太暗</string>\n    <string name=\"day_bottom_bar_too_dark\">白天底欄不能太暗</string>\n    <string name=\"night_background_too_light\">夜間背景不能太亮</string>\n    <string name=\"night_bottom_bar_too_light\">夜間底欄不能太亮</string>\n    <string name=\"accent_background_diff\">強調色不能和背景顏色相似</string>\n    <string name=\"accent_text_diff\">強調色不能和文字顏色相似</string>\n    <string name=\"wrong_format\">格式不對</string>\n    <string name=\"error\">錯誤</string>\n    <string name=\"show_brightness_view\">顯示亮度調節控制項</string>\n    <string name=\"language\">語言</string>\n    <string name=\"import_rss_source\">匯入訂閱源</string>\n    <string name=\"donate_summary\">您嘅支援喺我更新嘅動力</string>\n    <string name=\"about_summary\">公眾號[开源阅读软件]</string>\n    <string name=\"read_record\">閲讀記錄</string>\n    <string name=\"del_read_record\">刪除閲讀記錄</string>\n    <string name=\"read_record_summary\">閱讀時間記錄</string>\n    <string name=\"local_tts\">本地TTS</string>\n    <string name=\"thread_count\">線程數</string>\n    <string name=\"all_read_time\">總閲讀時間</string>\n    <string name=\"un_select_all\">全部唔要</string>\n    <string name=\"delete_all\">刪除所有</string>\n    <string name=\"import_str\">導入</string>\n    <string name=\"export_str\">導出</string>\n    <string name=\"save_theme_config\">儲存主題配置</string>\n    <string name=\"save_day_theme_summary\">儲存白天主題配置以供使用同埋分享</string>\n    <string name=\"save_night_theme_summary\">儲存夜間主題配置以供使用同埋分享</string>\n    <string name=\"theme_list\">主題列表</string>\n    <string name=\"theme_list_summary\">使用儲存主題,匯入,分享主題</string>\n    <string name=\"select_theme\">切換默認主題</string>\n    <string name=\"share_selected_source\">分享選中源</string>\n    <string name=\"sort_by_lastUpdateTime\">更新時間排序</string>\n    <string name=\"search_content\">全文搜索</string>\n    <string name=\"rss_source_empty\">Empty now!</string>\n    <string name=\"explore_empty\">目前沒有發現源!</string>\n    <string name=\"page_key_set_help\">將焦點放到輸入框按下物理按鍵會自動輸入鍵值,多個按鍵會自動用英文逗號隔開.</string>\n    <string name=\"theme_name\">主題名</string>\n    <string name=\"auto_clear_expired\">自動清除過期搜尋資料</string>\n    <string name=\"auto_clear_expired_summary\">超過一天的搜尋資料</string>\n    <string name=\"re_segment\">重新分段</string>\n    <string name=\"style_name\">樣式名稱:</string>\n    <string name=\"empty_msg_import_book\">點擊右上角資料夾圖示,選擇資料夾</string>\n    <string name=\"scan_folder\">智能掃描</string>\n    <string name=\"import_file_name\">導入文件名</string>\n    <string name=\"copy_book_url\">複製書籍URL</string>\n    <string name=\"copy_toc_url\">複製目錄URL</string>\n    <string name=\"no_book\">冇書</string>\n    <string name=\"keep_original_name\">保留原名</string>\n    <string name=\"click_regional_config\">點擊區域設定</string>\n    <string name=\"close\">閂咗</string>\n    <string name=\"next_page\">下一頁</string>\n    <string name=\"prev_page\">上一頁</string>\n    <string name=\"non_action\">無操作</string>\n    <string name=\"body_title\">正文標題</string>\n    <string name=\"show_hide\">顯示/隱藏</string>\n    <string name=\"header_footer\">頁眉<![CDATA[&]]>頁腳</string>\n    <string name=\"rule_subscription\">規則訂閱</string>\n    <string name=\"rule_sub_empty_msg\">添加大佬們提供的規則匯入地址 添加後點擊可匯入規則</string>\n    <string name=\"get_book_progress\">拉取雲端進度</string>\n    <string name=\"cover_book_progress\">覆蓋雲端進度</string>\n    <string name=\"current_progress_exceeds_cloud\">目前進度超過雲端進度,係咪同步?</string>\n    <string name=\"sync_book_progress_t\">同步閱讀進度</string>\n    <string name=\"sync_book_progress_s\">進入退出閱讀介面時同步閱讀進度</string>\n    <string name=\"sync_book_progress_plus_t\">同步增強</string>\n    <string name=\"sync_book_progress_plus_s\">重新進入頁面（息屏、從背景返回等）或網路恢復可用時同步雲端進度，同步新進度時會詢問</string>\n    <string name=\"create_bookmark_error\">建立書籤失敗</string>\n    <string name=\"single_url\">單URL</string>\n    <string name=\"export_bookshelf\">導出書單</string>\n    <string name=\"import_bookshelf\">導入書單</string>\n    <string name=\"pre_download\">預下載</string>\n    <string name=\"pre_download_m\">預下載%s页</string>\n    <string name=\"pre_download_s\">預先下載%s章正文</string>\n    <string name=\"is_enabled\">係咪啟用</string>\n    <string name=\"background_image\">背景圖片</string>\n    <string name=\"background_image_blurring\">背景圖片虛化</string>\n    <string name=\"background_image_blurring_radius\">虛化半徑</string>\n    <string name=\"background_image_hint\">0為停用，啓用範圍1~25\\n半徑數值越大，虛化效果越高</string>\n    <string name=\"export_folder\">導出資料夾</string>\n    <string name=\"export_charset\">導出編碼</string>\n    <string name=\"export_to_web_dav\">導出到WebDav</string>\n    <string name=\"reverse_content\">反轉內容</string>\n    <string name=\"debug\">調試</string>\n    <string name=\"crash_log\">崩潰日誌</string>\n    <string name=\"use_zh_layout\">使用自訂中文分行</string>\n    <string name=\"image_style\">圖片樣式</string>\n    <string name=\"system_tts\">系統TTS</string>\n    <string name=\"export_type\">導出格式</string>\n    <string name=\"checkAuthor\">校驗作者</string>\n    <string name=\"search_src\">搜尋源碼</string>\n    <string name=\"boo_src\">書輯源碼</string>\n    <string name=\"toc_src\">目錄源碼</string>\n    <string name=\"content_src\">正文源碼</string>\n    <string name=\"list_src\">列表源碼</string>\n    <string name=\"url_already\">此url訂閱咗</string>\n    <string name=\"high_brush_title\">高刷</string>\n    <string name=\"high_brush_summary\">使用螢幕最高刷新率</string>\n    <string name=\"export_all\">導出所有</string>\n    <string name=\"complete\">完成</string>\n    <string name=\"show_unread\">顯示未讀標誌</string>\n    <string name=\"use_default_cover\">總是使用默認封面</string>\n    <string name=\"use_default_cover_s\">總是使用默認封面,唔顯示網路封面</string>\n    <string name=\"title_font_size\">字號</string>\n    <string name=\"title_margin_top\">上邊距</string>\n    <string name=\"title_margin_bottom\">下邊距</string>\n    <string name=\"show\">顯示</string>\n    <string name=\"hide\">隱藏</string>\n    <string name=\"hide_when_status_bar_show\">狀態欄顯示時隱藏</string>\n    <string name=\"diy_source_group\">自訂源分組</string>\n    <string name=\"diy_edit_source_group\">輸入自訂源分組名稱</string>\n    <string name=\"reverse_toc\">反轉目錄</string>\n    <string name=\"show_discovery\">顯示發現</string>\n    <string name=\"style\">樣式</string>\n    <string name=\"group_style\">分組樣式</string>\n    <string name=\"export_file_name\">導出文件名</string>\n    <string name=\"reset\">重置</string>\n    <string name=\"null_url\">url為空</string>\n    <string name=\"dict\">字典</string>\n    <string name=\"unknown_error\">未知錯誤</string>\n    <string name=\"export_no_chapter_name\">TXT不導出章節名</string>\n    <string name=\"end\">end</string>\n    <string name=\"custom_group_summary\">關閉替換分組/開啟添加分組</string>\n    <string name=\"pref_media_button_per_next\">媒體按鈕•上一首|下一首</string>\n    <string name=\"pref_media_button_per_next_summary\">上一段|下一段/上一章|下一章</string>\n    <string name=\"read_aloud_by_page_summary\">及時翻頁,翻頁時會停頓一下</string>\n    <string name=\"check_source_show_debug_message\">校驗書源顯示詳細信息</string>\n    <string name=\"check_source_show_debug_message_summary\">書源校驗時顯示網絡請求步驟和時間</string>\n    <string name=\"need_login\">需登錄</string>\n    <string name=\"pref_cronet_summary\">使用Cronet網絡組件</string>\n    <string name=\"anti_alias\">抗鋸齒</string>\n    <string name=\"pref_anti_alias_summary\">繪製圖片時抗鋸齒</string>\n    <string name=\"upload_url\">上傳URL</string>\n    <string name=\"download_url_rule\">下载URL规则(downloadUrls)</string>\n    <string name=\"sort_by_respondTime\">響應時間排序</string>\n    <string name=\"respondTime\">響應時間：%1$d ms</string>\n    <string name=\"export_success\">導出成功</string>\n    <string name=\"path\">路徑</string>\n    <string name=\"direct_link_upload_rule\">直鏈上傳規則</string>\n    <string name=\"direct_link_upload_rule_summary\">用於導出書源書單時生成直鏈url</string>\n    <string name=\"direct_link_upload_config\">直链上传配置</string>\n    <string name=\"copy_play_url\">複製播放Url</string>\n    <string name=\"set_source_variable\">設置源變量</string>\n    <string name=\"set_book_variable\">設置書籍變量</string>\n    <string name=\"summary\">注釋</string>\n    <string name=\"cover_config\">封面設置</string>\n    <string name=\"cover_config_summary\">通用封面规则及默认封面样式</string>\n    <string name=\"cover_show_name\">顯示書名</string>\n    <string name=\"cover_show_name_summary\">封面上顯示書名</string>\n    <string name=\"cover_show_author\">顯示作者</string>\n    <string name=\"cover_show_author_summary\">封面上顯示作者</string>\n    <string name=\"read_aloud_prev_paragraph\">朗讀上一段</string>\n    <string name=\"read_aloud_next_paragraph\">朗讀下一段</string>\n    <string name=\"wait_download\">待下載</string>\n    <string name=\"download_success\">下載完成</string>\n    <string name=\"download_error\">下載失敗</string>\n    <string name=\"downloading\">下載中</string>\n    <string name=\"unknown_state\">未知狀態</string>\n    <string name=\"disable_source\">禁用源</string>\n    <string name=\"delete_source\">刪除源</string>\n    <string name=\"chapter_pay\">購買</string>\n    <string name=\"double_page_horizontal\">平板/橫屏雙頁</string>\n    <string name=\"open_in_browser\">瀏覽器打開</string>\n    <string name=\"copy_url\">複製url</string>\n    <string name=\"full_screen\">全屏</string>\n    <string name=\"open_fun\">打開方式</string>\n    <string name=\"use_browser_open\">是否使用外部瀏覽器打開?</string>\n    <string name=\"see\">查看</string>\n    <string name=\"search_content_size\">搜索結果</string>\n    <string name=\"search_content_empty\">搜索内容为空，检查净化/简繁设置</string>\n    <string name=\"open\">打開</string>\n    <string name=\"del_login_header\">刪除登錄頭</string>\n    <string name=\"show_login_header\">查看登錄頭</string>\n    <string name=\"login_header\">登錄頭</string>\n    <string name=\"font_scale\">字體大小</string>\n    <string name=\"font_scale_summary\">當前字亂大小:%.1f</string>\n    <string name=\"tts_speech_reduce\">語速减</string>\n    <string name=\"tts_speech_add\">語速加</string>\n    <string name=\"open_sys_dir_picker_error\">打开系统文件夹选择器出错,自动打开应用文件夹选择器</string>\n    <string name=\"open_sys_doc_picker_error\">打开系统文件选择器出错,自动打开应用文件选择器</string>\n    <string name=\"expand_text_menu\">展开文本选择菜单</string>\n    <string name=\"book_tree_uri_t\">书籍保存位置</string>\n    <string name=\"book_tree_uri_s\">从其它应用打开的书籍保存位置</string>\n    <string name=\"select_book_folder\">选择保存书籍的文件夹</string>\n    <string name=\"user_agent\">用户代理</string>\n    <string name=\"bg_alpha\">背景透明度</string>\n\n    <!-- check source config string -->\n    <string name=\"check_source_config\">校验设置</string>\n    <string name=\"check_source_item\">校验项目</string>\n    <string name=\"check_source_timeout\">单个书源校验超时(秒)</string>\n    <string name=\"timeout\">超时</string>\n    <string name=\"seconds\">秒</string>\n    <string name=\"less_than\">小于</string>\n    <string name=\"check_source_config_summary\">校验超时: %1$s秒\\n校验项目:%2$s</string>\n    <string name=\"record_debug_log\">记录调试日志</string>\n    <string name=\"sub_dir\">子文件夹</string>\n    <string name=\"general\">全局</string>\n    <string name=\"use_replace\">使用替换</string>\n    <string name=\"scope_title\">作用于标题</string>\n    <string name=\"scope_content\">作用于正文</string>\n    <string name=\"join_qq_channel\">加入QQ频道</string>\n    <string name=\"qq_channel_summary\">点击加入阅读QQ频道</string>\n    <string name=\"menu_refresh_dur\">刷新当前章节</string>\n    <string name=\"menu_refresh_after\">刷新之后章节</string>\n    <string name=\"menu_refresh_all\">刷新全部章节</string>\n    <string name=\"edit_content\">编辑内容</string>\n    <string name=\"chapter_change_source\">单章换源</string>\n    <string name=\"book_change_source\">整书换源</string>\n    <string name=\"sort_by_time\">按时间排序</string>\n    <string name=\"enable_record\">开启记录</string>\n    <string name=\"copy_all\">拷贝所有</string>\n    <string name=\"auto_complete\">自动补全</string>\n    <string name=\"sort_by_size\">大小排序</string>\n    <string name=\"welcome_style\">启动界面样式</string>\n    <string name=\"welcome_style_summary\">启动界面图片和是否显示文字等</string>\n    <string name=\"show_welcome_text\">显示文字</string>\n    <string name=\"welcome_text\">阅读|享受美好时光</string>\n    <string name=\"custom_welcome\">自定义欢迎页</string>\n    <string name=\"custom_welcome_summary\">是否使用自定义欢迎页</string>\n    <string name=\"show_icon\">显示图标</string>\n    <string name=\"show_default_book_icon\">显示默认书籍图标</string>\n    <string name=\"cache_export\">缓存/导出</string>\n    <string name=\"assists_key_config\">辅助按键配置</string>\n    <string name=\"url_option\">Url参数</string>\n    <string name=\"only_wifi\">仅WIFI</string>\n    <string name=\"only_wifi_summary\">仅在wifi下加载网络封面</string>\n    <string name=\"cover_rule\">封面规则</string>\n    <string name=\"cover_rule_summary\">进入详情页时使用封面规则重新获取封面</string>\n    <string name=\"scroll_to_dur_source\">定位到当前书源</string>\n    <string name=\"sys_tts_config\">系统tts设置</string>\n    <string name=\"sys_tts_config_summary\">打开系统tts设置界面</string>\n    <string name=\"cannot_timed_non_playback\">非播放状态无法定时</string>\n    <string name=\"all_bookmark\">所有书签</string>\n    <string name=\"change_source_batch\">批量换源</string>\n    <string name=\"book_type_different\">书籍类型不一样</string>\n    <string name=\"soure_change_source\">是否确认换源</string>\n    <string name=\"input_verification_code\">输入验证码</string>\n    <string name=\"verification_code\">验证码</string>\n    <string name=\"timeout_millisecond\">超时毫秒数</string>\n    <string name=\"file_not_supported\">文件%1$s 不受支持，是否继续打开？</string>\n    <string name=\"import_tts\">导入TTS</string>\n    <string name=\"import_theme\">导入主题</string>\n    <string name=\"import_txt_toc_rule\">导入txt目录规则</string>\n    <string name=\"auto_save_cookie\">CookieJar</string>\n    <string name=\"cookie\">清除cookie</string>\n    <string name=\"download_and_import_file\">导入在线书籍文件</string>\n    <string name=\"upload_book_success\">Upload Success</string>\n    <string name=\"upload_book_fail\">Upload Fail</string>\n    <string name=\"download_book_success\">Download Success</string>\n    <string name=\"download_book_fail\">Download Fail</string>\n    <string name=\"upload_to_remote\">Upload</string>\n    <string name=\"add_remote_book\">远程书籍</string>\n    <string name=\"bitmap_cache_size_summary\">当前最大缓存 %1$s MB</string>\n    <string name=\"bitmap_cache_size\">图片绘制缓存</string>\n    <string name=\"image_retain_number_summary\">保留已讀章節數量 %s</string>\n    <string name=\"image_retain_number\">漫畫保留數量</string>\n    <string name=\"export_pics_file\">TXT导出图片</string>\n    <!-- string end -->\n    <string name=\"error_decode_bitmap\">图片解码失败</string>\n    <string name=\"error_image_url_empty\">图片链接为空，检查替换净化规则</string>\n    <string name=\"variable_comment\">变量说明(variableComment)</string>\n    <string name=\"remote_book\">远程书籍</string>\n    <string name=\"reading_time_sort\">阅读时长排序</string>\n    <string name=\"last_read_time_sort\">阅读时间排序</string>\n    <string name=\"reading_time_tag\">阅读时长:</string>\n    <string name=\"last_read_time_tag\">最后阅读时间:</string>\n    <string name=\"page_touch_slop_title\">滑动翻页阈值</string>\n    <string name=\"page_touch_slop_dialog_title\">滑动翻页阈值（0 = 系统默认值）</string>\n    <string name=\"page_touch_slop_summary\">滑动多长距离才会触发滑动翻页（系统默认值 %s px）</string>\n    <string name=\"example\">示例</string>\n    <string name=\"check_selected_interval\">选中所选区间</string>\n    <string name=\"show_add_to_shelf_alert_title\">返回时提示放入书架</string>\n    <string name=\"show_add_to_shelf_alert_summary\">阅读未放入书架的书籍在返回时提示放入书架</string>\n    <string name=\"review\">段评</string>\n    <string name=\"rule_review_url\">段评URL（reviewUrl）</string>\n    <string name=\"rule_avatar\">段评发布者头像（avatarRule）</string>\n    <string name=\"rule_review_content\">段评内容（contentRule）</string>\n    <string name=\"rule_post_time\">段评发布时间（postTimeRule）</string>\n    <string name=\"rule_review_quote\">段评回复URL（reviewQuoteUrl）</string>\n    <string name=\"review_vote_down\">点踩URL（voteDownUrl）</string>\n    <string name=\"review_vote_up\">点赞URL（voteUpUrl）</string>\n    <string name=\"post_review_url\">发送回复URL（postReviewUrl）</string>\n    <string name=\"post_quote_url\">发送回复段评URL（postQuoteUrl）</string>\n    <string name=\"delete_review_url\">删除段评URL（deleteUrl）</string>\n    <string name=\"tag_explore_enabled\">标志:发现已启用</string>\n    <string name=\"tag_explore_disabled\">标志:发现已禁用</string>\n    <string name=\"show_read_title_addition\">展示顶部工具栏附加区域</string>\n    <string name=\"read_bar_style_follow_page\">工具栏样式跟随页面</string>\n    <string name=\"rule_image_decode\">图片解密（imageDecode）</string>\n    <string name=\"like_source\">赞</string>\n    <string name=\"not_like_source\">踩</string>\n    <string name=\"async_load_image\">异步加载图片</string>\n    <string name=\"ignore_audio_focus_title\">忽略音频焦点</string>\n    <string name=\"ignore_audio_focus_summary\">允许与其他应用同时播放音频</string>\n    <string name=\"refresh_sort\">刷新分类</string>\n    <string name=\"cover_decode_js\">封面解密（coverDecodeJs）</string>\n    <string name=\"net_no_group\">网络未分组</string>\n    <string name=\"local_no_group\">本地未分组</string>\n    <string name=\"parallel_export_book\">多线程导出</string>\n    <string name=\"progress_bar_behavior\">进度条行为</string>\n    <string name=\"source_edit_text_max_line\">源编辑框最大行数</string>\n    <string name=\"source_edit_max_line_summary\">%s,设置行数小于屏幕可显示的最大行数可以更方便的滑动到其他的字段进行编辑</string>\n    <string name=\"restore_last_book_process\">是否恢复到跳转前的阅读进度？</string>\n    <string name=\"search_scope\">搜索范围</string>\n    <string name=\"toggle_search_scope\">切换</string>\n    <string name=\"sure_clear_search_history\">是否确认清除所有搜索历史记录</string>\n    <string name=\"no_anim_scroll_page\">禁用滚动点击动画</string>\n    <string name=\"webdav_device_name\">设备名称</string>\n    <string name=\"web_service_wake_lock\">WebService唤醒锁</string>\n    <string name=\"web_service_wake_lock_summary\">开启web服务的时候启用唤醒锁,有些手机开启唤醒锁会被杀后台</string>\n    <string name=\"read_aloud_wake_lock\">朗读服务唤醒锁</string>\n    <string name=\"read_aloud_wake_lock_summary\">开启朗读的时候启用唤醒锁,有些手机开启唤醒锁会被杀后台</string>\n    <string name=\"audio_play_wake_lock\">音频服务唤醒锁</string>\n    <string name=\"audio_play_wake_lock_summary\">播放音频的时候启用唤醒锁,有些手机开启唤醒锁会被杀后台</string>\n    <string name=\"change_search_scope\">切换搜索范围</string>\n    <string name=\"copy_rule\">拷贝规则</string>\n    <string name=\"paste_rule\">粘贴规则</string>\n    <string name=\"groups_or_source\">多分组/书源</string>\n    <string name=\"replace_state_change\">替换(启用/禁用)</string>\n    <string name=\"show_last_update_time\">显示上次更新时间</string>\n    <string name=\"refresh_list\">刷新列表</string>\n    <string name=\"tip_divider_color\">分隔线颜色</string>\n    <string name=\"same_title_removed\">移除重复标题</string>\n    <string name=\"update_book_fail\">更新失败</string>\n    <string name=\"notification_permission_rationale\">阅读需要发送通知来显示朗读控制和下载进度</string>\n    <string name=\"webdav_after_local_restore_confirm\">webDav书源比本地新,是否恢复</string>\n    <string name=\"c_whitelist\">白名单(contentWhitelist)</string>\n    <string name=\"c_blacklist\">黑名单(contentBlacklist)</string>\n    <string name=\"confirm\">确认</string>\n    <string name=\"jump_to_another_app\">跳转其它应用</string>\n    <string name=\"clear_webview_data\">清除 WebView 数据</string>\n    <string name=\"clear_webview_data_summary\">清除内置浏览器所有数据</string>\n    <string name=\"source_tab_list\">列表</string>\n    <string name=\"dict_rule\">字典规则</string>\n    <string name=\"config_dict_rule\">配置字典规则</string>\n    <string name=\"create\">新建</string>\n    <string name=\"url_rule\">url规则(urlRule)</string>\n    <string name=\"show_rule\">显示规则(showRule)</string>\n    <string name=\"config_txt_toc_rule\">配置 TXT 目录规则</string>\n    <string name=\"import_dict_rule\">导入字典规则</string>\n    <string name=\"keep_group\">保留分组</string>\n    <string name=\"server_config\">服务器配置</string>\n    <string name=\"sure_upload\">远程webDav链接已存在，是否继续</string>\n    <string name=\"unsupport_archivefile_entry\">压缩文件内没有支持的文件</string>\n    <string name=\"no_books_dir\">没有设置书籍保存位置!</string>\n    <string name=\"delete_alert\">删除提醒</string>\n    <string name=\"no_book_found_bookshelf\">未在书架上找到所选书籍, 是否重新导入？</string>\n    <string name=\"archive_not_found\">未能在书籍保存目录找到压缩文件，是否重新导入？</string>\n    <string name=\"privacy_policy\">用户隐私与协议</string>\n    <string name=\"agree\">同意</string>\n    <string name=\"refuse\">拒绝</string>\n    <string name=\"file_manage\">文件管理</string>\n    <string name=\"file_manage_summary\">管理私有文件夹的文件</string>\n    <string name=\"create_folder\">创建文件夹</string>\n    <string name=\"allow_drop_down_refresh\">允许下拉刷新</string>\n    <string name=\"text_underline\">文字下划线</string>\n    <string name=\"select_new_source\">选中新增源</string>\n    <string name=\"select_update_source\">选中更新源</string>\n    <string name=\"set_local_password\">设置本地密码</string>\n    <string name=\"set_local_password_summary\">本地密码用来对备份的敏感信息加密和解密,如需在不同设备之间同步,本地密码需一致.</string>\n    <string name=\"only_latest_backup_t\">仅保留最新备份</string>\n    <string name=\"only_latest_backup_s\">本地备份仅保留最新备份文件</string>\n    <string name=\"webdav_application_authorization_error\">webDav应用验证失败</string>\n    <string name=\"load_word_count\">加載字數</string>\n    <string name=\"replace_exclude_scope\">排除范围，选填书名或者书源 URL</string>\n    <string name=\"format_js_rule\">格式化规则(formatJs)</string>\n    <string name=\"adjust_pos\">调整位置</string>\n    <string name=\"select_section_export\">選擇待導出章節</string>\n    <string name=\"error_scope_input\">請輸入正確的範圍</string>\n    <string name=\"custom_export\">自定義導出</string>\n    <string name=\"file_contains_number\">每個文件包含的章節數量</string>\n    <string name=\"export_chapter_index\">需要輸出的章節</string>\n    <string name=\"shrink_database\">压缩数据库</string>\n    <string name=\"shrink_database_summary\">减小数据库文件的大小</string>\n    <string name=\"is_compress\">是否压缩</string>\n    <string name=\"sort_desc\">反序</string>\n    <string name=\"test\">测试</string>\n    <string name=\"show_wait_up_count\">显示等待更新数量</string>\n    <string name=\"exit_app\">退出软件</string>\n    <string name=\"result_analyzed\">解析示例</string>\n    <string name=\"bookshelf_px_4\">綜合排序</string>\n    <string name=\"bookshelf_px_5\">按作者</string>\n    <string name=\"effective_replaces\">起效的替换</string>\n    <string name=\"export_book\">导出书籍</string>\n    <string name=\"export_book_notification_content\">正在导出(%1$s),还有%2$d本待导出</string>\n    <string name=\"export_md\">导出(MD)</string>\n    <string name=\"change_source_delay\">换源间隔</string>\n    <string name=\"change_source_progress\">\" 結果 %1$d, 當前進度 %2$d / %3$d: %4$s\"</string>\n    <string name=\"open_book_info_by_click_title\">点击书名打开详情</string>\n    <string name=\"export_wait\">等待导出</string>\n    <string name=\"sync_book_progress_success\">進度同步成功</string>\n    <string name=\"default_home_page\">默认主页</string>\n    <string name=\"show_bookshelf_fast_scroller\">显示快速滚动条</string>\n    <string name=\"export_all_use_book_source\">导出所有书的书源</string>\n    <string name=\"cloud_progress_exceeds_current\">雲端進度超過目前進度，是否同步？</string>\n    <string name=\"keep_enable\">保留启用状态</string>\n    <string name=\"preview_image_by_click\">点击预览图片</string>\n    <string name=\"screen_portrait_reversed\">反向竖屏</string>\n    <string name=\"del_ruby_tag\">删除ruby标签</string>\n    <string name=\"del_h_tag\">删除h标签</string>\n    <string name=\"adjust_chapter_page\">调整本章页数</string>\n    <string name=\"adjust_chapter_index\">调整章节位置</string>\n    <string name=\"clear_webview_data_success\">清除成功，3秒后自动重启应用</string>\n    <string name=\"key_page_on_long_press\">按键长按翻页</string>\n    <string name=\"save_log\">保存日志</string>\n    <string name=\"create_heap_dump\">创建堆转储</string>\n    <string name=\"record_heap_dump_s\">当应用发生OOM崩溃时保存堆转储</string>\n    <string name=\"record_heap_dump_t\">记录堆转储</string>\n    <string name=\"font_weight_text\">中/粗/细</string>\n    <string name=\"keep_swipe_tip\">继续滑动以加载下一章…</string>\n    <string name=\"enable_optimize_render\">启用绘制优化</string>\n    <string name=\"ignore_battery_permission_rationale\">阅读需要请求后台权限以保持服务正常运行</string>\n    <string name=\"simulated_reading\">模擬追讀</string>\n    <string name=\"switch_on\">開關</string>\n    <string name=\"start_from\">起始日期</string>\n    <string name=\"daily_chapters\">日更章數</string>\n    <string name=\"start_chapter\">開始篇章</string>\n    <string name=\"update_to_variant_title\">检查更新查找版本</string>\n    <string name=\"update_to_variant_summary\">检查更新时查找其他签名版本</string>\n    <string name=\"default_version\">当前</string>\n    <string name=\"official_version\">正式版</string>\n    <string name=\"beta_release_version\">测试版</string>\n    <string name=\"beta_releaseA_version\">共存版</string>\n    <string name=\"stream_read_aloud_audio\">流式播放音频</string>\n    <string name=\"stream_read_aloud_audio_summary\">即边下边播，网络不好时播放会断断续续，仅TTS源有效</string>\n    <string name=\"pause_read_aloud_while_phone_calls_title\">来电期间暂停朗读</string>\n    <string name=\"pause_read_aloud_while_phone_calls_summary\">在通话期间暂停朗读，需要读取手机状态权限</string>\n    <string name=\"read_aloud_read_phone_state_permission_rationale\">阅读需要读取手机状态实现来电期间暂停朗读功能</string>\n    <string name=\"read_aloud_by_media_button_title\">耳机按键启动朗读</string>\n    <string name=\"read_aloud_by_media_button_summary\">通过耳机按键来启动朗读</string>\n    <string name=\"group_sources_by_domain\">按域名分组显示</string>\n    <string name=\"theme_config\">主题配置</string>\n    <string name=\"show_manga_ui\">漫画浏览</string>\n    <string name=\"disable_manga_scale\">禁用漫画缩放</string>\n    <string name=\"disable_manga_click_scroll\">禁用点击翻页</string>\n    <string name=\"enable_auto_page_scroll\">开启自动翻页</string>\n    <string name=\"manga_auto_page_speed\">翻页速度 %s</string>\n    <string name=\"enable_auto_scroll\">开启滚动</string>\n    <string name=\"manga_footer_config\">页脚配置</string>\n    <string name=\"setting_manga_auto_page_speed\">设置自动翻页速度</string>\n    <string name=\"book_reader_info_bar\">页数. %1$d/%2$d  章节. %3$d/%4$d -> %5$s%%</string>\n    <string name=\"menu_download_after\">下載之後章節</string>\n    <string name=\"menu_download_all\">下載全部章節</string>\n    <string name=\"manga_header_chapter\">《章节和文案》隐藏</string>\n    <string name=\"manga_check_chapter_label\">《章节.》文案</string>\n    <string name=\"manga_check_chapter\">章节</string>\n    <string name=\"manga_check_chapter_name\">章节名称</string>\n    <string name=\"manga_header_page\">《页数和文案》隐藏</string>\n    <string name=\"manga_check_page_label\">《页数.》文案</string>\n    <string name=\"manga_check_page_number\">页数</string>\n    <string name=\"manga_header_progress\">《总进度和文案》隐藏</string>\n    <string name=\"manga_check_progress_label\">《总进度.》文案</string>\n    <string name=\"manga_check_progress\">总进度</string>\n    <string name=\"manga_header_footer\">页脚</string>\n    <string name=\"manga_radio_left\">靠左</string>\n    <string name=\"manga_radio_center\">居中</string>\n    <string name=\"enable_manga_horizontal_scroll\">水平滚动</string>\n    <string name=\"manga_color_filter\">滤镜</string>\n    <string name=\"hide_manga_title\">隐藏漫画列表标题</string>\n    <string name=\"refresh_explore\">刷新发现</string>\n    <string name=\"padding_display_cutouts\">填充刘海区域</string>\n    <string name=\"system_media_control_compatibility_change\">系統媒體控件相容性更改</string>\n    <string name=\"system_media_control_compatibility_change_summary\">當鎖屏不顯示系統媒體控件時可以嘗試開啟，比如 OneUI7.0 或 vivo 等</string>\n    <string name=\"read_aloud_pause_resume\">朗讀暫停/繼續</string>\n    <string name=\"manga_epaper\">墨水屏</string>\n    <string name=\"manga_epaper_stting\">墨水屏设置</string>\n    <string name=\"manga_epaper_value\">阈值</string>\n    <string name=\"disable_horizontal_page_snap\">禁用水平翻页效果</string>\n    <string name=\"enable_manga_gray\">开启图片灰色</string>\n    <string name=\"sure_cache_book\">是否确认开始缓存？</string>\n    <string name=\"auto_check_new_backup_t\">自动检查新备份</string>\n    <string name=\"auto_check_new_backup_s\">打开软件时检查是否有新备份，有新备份时提示是否更新</string>\n    <string name=\"sys_image_picker\">系统图片选择器</string>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values-zh-rTW/arrays.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<resources>\n    <string-array name=\"book_type\">\n        <item>文字</item>\n        <item>音訊</item>\n        <item>圖片</item>\n        <item>資料</item>\n    </string-array>\n\n    <string-array name=\"group_style\">\n        <item>標籤</item>\n        <item>資料夾</item>\n    </string-array>\n\n    <string-array name=\"text_suffix\">\n        <item>.txt</item>\n        <item>.json</item>\n        <item>.xml</item>\n    </string-array>\n\n    <string-array name=\"theme_mode\">\n        <item>跟隨系統</item>\n        <item>亮色主題</item>\n        <item>暗色主題</item>\n        <item>E-Ink(墨水屏)</item>\n    </string-array>\n\n    <string-array name=\"NavBarColors\">\n        <item>自動</item>\n        <item>黑色</item>\n        <item>白色</item>\n        <item>跟隨背景</item>\n    </string-array>\n\n    <string-array name=\"screen_time_out\">\n        <item>預設</item>\n        <item>1分鐘</item>\n        <item>5分鐘</item>\n        <item>10分鐘</item>\n        <item>常亮</item>\n    </string-array>\n\n    <string-array name=\"screen_direction_title\">\n        <item>@string/screen_unspecified</item>\n        <item>@string/screen_portrait</item>\n        <item>@string/screen_landscape</item>\n        <item>@string/screen_sensor</item>\n        <item>反向直向</item>\n    </string-array>\n\n    <string-array name=\"double_page_title\">\n        <item>全域單頁</item>\n        <item>全域雙頁</item>\n        <item>橫向雙頁</item>\n        <item>平板/橫向雙页</item>\n    </string-array>\n\n    <string-array name=\"progress_bar_behavior_title\">\n        <item>調整本章頁數</item>\n        <item>調整章節位置</item>\n    </string-array>\n\n    <string-array name=\"icon_names\">\n        <item>iconMain</item>\n        <item>icon1</item>\n        <item>icon2</item>\n        <item>icon3</item>\n        <item>icon4</item>\n        <item>icon5</item>\n        <item>icon6</item>\n    </string-array>\n\n    <string-array name=\"chinese_mode\">\n        <item>關閉</item>\n        <item>繁體轉簡體</item>\n        <item>簡體轉繁體</item>\n    </string-array>\n\n    <string-array name=\"system_typefaces\">\n        <item>系統預設字體</item>\n        <item>系統襯線字體</item>\n        <item>系統等寬字體</item>\n    </string-array>\n\n    <string-array name=\"read_tip\">\n        <item>無</item>\n        <item>書名</item>\n        <item>標題</item>\n        <item>時間</item>\n        <item>電量</item>\n        <item>電量%</item>\n        <item>頁數</item>\n        <item>進度(%)</item>\n        <item>進度(xx/yyy)</item>\n        <item>頁數及進度</item>\n        <item>時間及電量</item>\n        <item>時間及電量%</item>\n    </string-array>\n\n    <string-array name=\"text_font_weight\">\n        <item>正常</item>\n        <item>粗體</item>\n        <item>細體</item>\n    </string-array>\n\n    <string-array name=\"language\">\n        <item>跟隨系統</item>\n        <item>簡體中文</item>\n        <item>繁體中文</item>\n        <item>英文</item>\n    </string-array>\n\n    <string-array name=\"rule_type\">\n        <item>書源</item>\n        <item>訂閱源</item>\n        <item>取代規則</item>\n    </string-array>\n\n</resources>\n"
  },
  {
    "path": "app/src/main/res/values-zh-rTW/strings.xml",
    "content": "<resources>\n    <!--App-->\n    <string name=\"app_name\">閱讀</string>\n    <string name=\"app_name_a\">閱讀·A</string>\n    <string name=\"receiving_shared_label\">閱讀·搜尋</string>\n    <string name=\"tip_perm_request_storage\">閱讀需要存取記憶卡權限，請前往「設定」—「應用程式權限」—打開所需權限</string>\n\n    <!--Other-->\n    <string name=\"menu_backup\">備份</string>\n    <string name=\"menu_restore\">復原</string>\n    <string name=\"menu_import_old\">匯入閱讀資料</string>\n    <string name=\"webdav_cache_backup\">離線快取書籍備份</string>\n    <string name=\"webdav_cache_backup_s\">匯出本機同時備份到legado資料夾下exports目錄</string>\n    <string name=\"backup_path\">備份路徑</string>\n    <string name=\"select_backup_path\">請選擇備份路徑</string>\n    <string name=\"menu_import_old_version\">匯入舊版資料</string>\n    <string name=\"menu_import_github\">匯入Github資料</string>\n    <string name=\"menu_replace_rule\">淨化取代</string>\n    <string name=\"menu_send\">傳送</string>\n\n    <string name=\"dialog_title\">提示</string>\n    <string name=\"dialog_cancel\">取消</string>\n    <string name=\"dialog_confirm\">確定</string>\n    <string name=\"dialog_setting\">去設定</string>\n    <string name=\"tip_cannot_jump_setting_page\">無法跳轉至設定介面</string>\n\n    <string name=\"dynamic_click_retry\">點擊重試</string>\n    <string name=\"dynamic_loading\">正在載入</string>\n    <string name=\"draw\">提醒</string>\n    <string name=\"edit\">編輯</string>\n    <string name=\"delete\">刪除</string>\n    <string name=\"delete_select_group\">刪除當前分組</string>\n    <string name=\"delete_all\">刪除所有</string>\n    <string name=\"replace\">取代</string>\n    <string name=\"replace_purify\">取代淨化</string>\n    <string name=\"replace_purify_desc\">配置取代淨化規則</string>\n    <string name=\"custom_export_section\">自定義Epub導出章節</string>\n    <string name=\"not_available\">暫無</string>\n    <string name=\"enable\">啟用</string>\n    <string name=\"replace_purify_search\">取代淨化-搜尋</string>\n    <string name=\"bookshelf\">書架</string>\n    <string name=\"favorites\">收藏夾</string>\n    <string name=\"favorite\">收藏</string>\n    <string name=\"in_favorites\">已收藏</string>\n    <string name=\"out_favorites\">未收藏</string>\n    <string name=\"rss\">訂閱</string>\n    <string name=\"all\">全部</string>\n    <string name=\"recent_reading\">最近閱讀</string>\n    <string name=\"last_read\">最後閱讀</string>\n    <string name=\"update_log\">更新日誌</string>\n    <string name=\"bookshelf_empty\">書架還空著，先去搜尋書籍或從發現裡新增吧！</string>\n    <string name=\"action_search\">搜尋</string>\n    <string name=\"action_download\">下載</string>\n    <string name=\"layout_list\">列表</string>\n    <string name=\"layout_grid3\">網格三列</string>\n    <string name=\"layout_grid4\">網格四列</string>\n    <string name=\"layout_grid5\">網格五列</string>\n    <string name=\"layout_grid6\">網格六列</string>\n    <string name=\"bookshelf_layout\">書架布局</string>\n    <string name=\"view\">檢視</string>\n    <string name=\"book_library\">書城</string>\n    <string name=\"book_local\">新增本機</string>\n    <string name=\"book_source\">書源</string>\n    <string name=\"book_source_manage\">書源管理</string>\n    <string name=\"book_source_manage_desc\">建立/匯入/編輯/管理書源</string>\n    <string name=\"setting\">設定</string>\n    <string name=\"theme_setting\">主題設定</string>\n    <string name=\"theme_setting_s\">與介面/顏色相關的一些設定</string>\n    <string name=\"other_setting\">其它設定</string>\n    <string name=\"other_setting_s\">與功能相關的一些設定</string>\n    <string name=\"about\">關於</string>\n    <string name=\"donate\">捐贈</string>\n    <string name=\"exit\">退出</string>\n    <string name=\"exit_no_save\">尚未儲存，是否繼續編輯</string>\n    <string name=\"read_style\">閱讀樣式設定</string>\n    <string name=\"version\">版本</string>\n    <string name=\"local\">本機</string>\n    <string name=\"search\">搜尋</string>\n    <string name=\"origin_format\">來源：%s</string>\n    <string name=\"read_dur_progress\">最近：%s</string>\n    <string name=\"book_name\">書名</string>\n    <string name=\"lasted_show\">最新：%s</string>\n    <string name=\"check_add_bookshelf\">是否將《%s》放入書架？</string>\n    <string name=\"import_books_count\">共%s個Text文件</string>\n    <string name=\"is_loading\">載入中…</string>\n    <string name=\"retry\">重試</string>\n    <string name=\"web_service\">Web 服務</string>\n    <string name=\"web_service_desc\">瀏覽器寫源、看書</string>\n    <string name=\"web_edit_source\">web編輯書源</string>\n    <string name=\"offline_cache\">離線快取</string>\n    <string name=\"offline_cache_t\">離線快取</string>\n    <string name=\"offline_cache_s\">快取選擇的章節到本機</string>\n    <string name=\"change_origin\">換源</string>\n    <string name=\"about_description\">\n        \\u3000\\u3000這是一款使用Kotlin全新開發的開源的閱讀軟體，歡迎您的加入。\n    </string>\n    <string name=\"app_share_description\">\n        閱讀3.0下載網址：\\nhttps://github.com/gedoor/legado/releases\n    </string>\n    <string name=\"version_name\">Version %s</string>\n    <string name=\"pt_background_verification\">後臺校驗書源</string>\n    <string name=\"ps_background_verification\">打開後可以在校驗書源時自由操作</string>\n    <string name=\"pt_auto_refresh\">自動重新整理</string>\n    <string name=\"ps_auto_refresh\">打開軟體時自動更新書籍</string>\n    <string name=\"pt_auto_download\">自動下載最新章節</string>\n    <string name=\"ps_auto_download\">更新書籍時自動下載最新章節</string>\n    <string name=\"backup_restore\">備份與復原</string>\n    <string name=\"web_dav_set\">WebDav設定</string>\n    <string name=\"web_dav_set_import_old\">WebDav設定/匯入舊版本資料</string>\n    <string name=\"backup\">備份</string>\n    <string name=\"restore\">復原</string>\n    <string name=\"backup_permission\">備份請給予儲存權限</string>\n    <string name=\"restore_permission\">復原請給予儲存權限</string>\n    <string name=\"ok\">確認</string>\n    <string name=\"cancel\">取消</string>\n    <string name=\"backup_confirmation\">確認備份嗎？</string>\n    <string name=\"backup_message\">新備份會取代原有備份。\\n備份資料夾YueDu</string>\n    <string name=\"restore_confirmation\">確認復原嗎？</string>\n    <string name=\"restore_message\">復原書架會覆蓋現有書架。</string>\n    <string name=\"backup_success\">備份成功</string>\n    <string name=\"backup_fail\">備份失敗\\n%s</string>\n    <string name=\"on_restore\">正在復原</string>\n    <string name=\"restore_success\">復原成功</string>\n    <string name=\"restore_fail\">復原失敗</string>\n    <string name=\"screen_direction\">螢幕方向</string>\n    <string name=\"screen_sensor\">跟隨感測器</string>\n    <string name=\"screen_landscape\">橫向</string>\n    <string name=\"screen_portrait\">豎向</string>\n    <string name=\"screen_unspecified\">跟隨系統</string>\n    <string name=\"disclaimer\">免責聲明</string>\n    <string name=\"all_chapter_num\">共%d章</string>\n    <string name=\"interface_setting\">介面</string>\n    <string name=\"brightness\">亮度</string>\n    <string name=\"chapter_list\">目錄</string>\n    <string name=\"next_chapter\">下一章</string>\n    <string name=\"previous_chapter\">上一章</string>\n    <string name=\"pt_hide_status_bar\">隱藏狀態欄</string>\n    <string name=\"ps_hide_status_bar\">閱讀介面隱藏狀態欄</string>\n    <string name=\"read_aloud\">朗讀</string>\n    <string name=\"read_aloud_t\">正在朗讀</string>\n    <string name=\"read_aloud_s\">點擊打開閱讀介面</string>\n    <string name=\"audio_play\">播放</string>\n    <string name=\"audio_play_t\">正在播放</string>\n    <string name=\"audio_play_s\">點擊打開播放介面</string>\n    <string name=\"audio_pause\">播放暫停</string>\n    <string name=\"text_return\">返回</string>\n    <string name=\"refresh\">重新整理</string>\n    <string name=\"start\">開始</string>\n    <string name=\"stop\">停止</string>\n    <string name=\"pause\">暫停</string>\n    <string name=\"resume\">繼續</string>\n    <string name=\"set_timer\">定時</string>\n    <string name=\"read_aloud_pause\">朗讀暫停</string>\n    <string name=\"read_aloud_timer\">正在朗讀(還剩%d分鐘)</string>\n    <string name=\"playing_timer\">正在播放(還剩%d分鐘)</string>\n    <string name=\"ps_hide_navigation_bar\">閱讀介面隱藏虛擬按鍵</string>\n    <string name=\"pt_hide_navigation_bar\">隱藏導航欄</string>\n    <string name=\"re_navigation_bar_color\">導航欄顏色</string>\n    <string name=\"scoring\">評分</string>\n    <string name=\"send_mail\">發送郵件</string>\n    <string name=\"can_not_open\">無法打開</string>\n    <string name=\"can_not_share\">分享失敗</string>\n    <string name=\"no_chapter\">無章節</string>\n    <string name=\"add_url\">新增網址</string>\n    <string name=\"add_book_url\">新增書籍網址</string>\n    <string name=\"background\">背景</string>\n    <string name=\"author\">作者</string>\n    <string name=\"author_show\">作者：%s</string>\n    <string name=\"aloud_stop\">朗讀停止</string>\n    <string name=\"clear_cache\">清理快取</string>\n    <string name=\"clear_cache_success\">成功清理快取</string>\n    <string name=\"action_save\">儲存</string>\n    <string name=\"edit_source\">編輯源</string>\n    <string name=\"edit_book_source\">編輯書源</string>\n    <string name=\"disable_book_source\">禁用書源</string>\n    <string name=\"add_book_source\">建立書源</string>\n    <string name=\"add_rss_source\">建立訂閱源</string>\n    <string name=\"book_file_selector\">新增書籍</string>\n    <string name=\"scan_book_source\">掃描</string>\n    <string name=\"copy_source\">複製源</string>\n    <string name=\"paste_source\">貼上源</string>\n    <string name=\"source_rule_s\">源規則說明</string>\n    <string name=\"check_update\">檢查更新</string>\n    <string name=\"camera_scan\">掃描二維碼</string>\n    <string name=\"scan_image\">掃描本機圖片</string>\n    <string name=\"rule_summary\">規則說明</string>\n    <string name=\"share\">分享</string>\n    <string name=\"share_app\">軟體分享</string>\n    <string name=\"flow_sys\">跟隨系統</string>\n    <string name=\"add\">新增</string>\n    <string name=\"import_book_source\">匯入書源</string>\n    <string name=\"import_local\">本機匯入</string>\n    <string name=\"import_on_line\">網路匯入</string>\n    <string name=\"replace_rule_title\">取代淨化</string>\n    <string name=\"replace_rule_edit\">取代規則編輯</string>\n    <string name=\"replace_rule\">取代規則</string>\n    <string name=\"replace_to\">取代為</string>\n    <string name=\"img_cover\">封面</string>\n    <string name=\"book\">書</string>\n    <string name=\"volume_key_page\">音量鍵翻頁</string>\n    <string name=\"mouse_wheel_page\">鼠标滚动翻页</string>\n    <string name=\"click_turn_page\">點擊翻頁</string>\n    <string name=\"page_anim\">翻頁動畫</string>\n    <string name=\"book_page_anim\">翻頁動畫(本書)</string>\n    <string name=\"keep_light\">螢幕超時</string>\n    <string name=\"back\">返回</string>\n    <string name=\"menu\">選單</string>\n    <string name=\"adjust\">調節</string>\n    <string name=\"scroll_bar\">滾動條</string>\n    <string name=\"clear_all_content\">清除快取會刪除所有已儲存章節，是否確認刪除？</string>\n    <string name=\"book_source_share_url\">書源共享</string>\n    <string name=\"replace_rule_summary\">取代規則名稱</string>\n    <string name=\"replace_rule_invalid\">取代規則為空或者不滿足正規表示式要求</string>\n    <string name=\"select_action\">選擇操作</string>\n    <string name=\"select_all\">全選</string>\n    <string name=\"select_all_count\">全選(%1$d/%2$d)</string>\n    <string name=\"select_cancel_count\">取消全選(%1$d/%2$d)</string>\n    <string name=\"dark_theme\">深色模式</string>\n    <string name=\"welcome\">啟動頁</string>\n    <string name=\"download_start\">開始下載</string>\n    <string name=\"download_cancel\">取消下載</string>\n    <string name=\"no_download\">暫無任務</string>\n    <string name=\"download_count\">已下載 %1$d/%2$d</string>\n    <string name=\"import_select_book\">匯入選擇書籍</string>\n    <string name=\"threads_num_title\">更新和搜尋執行緒數，太多會卡頓</string>\n    <string name=\"change_icon\">切換圖示</string>\n    <string name=\"remove_from_bookshelf\">刪除書籍</string>\n    <string name=\"start_read\">開始閱讀</string>\n    <string name=\"data_loading\">載入資料中…</string>\n    <string name=\"load_error_retry\">載入失敗，點擊重試</string>\n    <string name=\"book_intro\">內容簡介</string>\n    <string name=\"intro_show\">簡介：%s</string>\n    <string name=\"intro_show_null\">簡介：暫無簡介</string>\n    <string name=\"open_from_other\">打開外部書籍</string>\n    <string name=\"origin_show\">來源：%s</string>\n    <string name=\"import_replace_rule\">匯入取代規則</string>\n    <string name=\"import_replace_rule_on_line\">匯入線上規則</string>\n    <string name=\"check_update_interval\">檢查更新間隔</string>\n    <string name=\"bookshelf_px_0\">按閱讀時間</string>\n    <string name=\"bookshelf_px_1\">按更新時間</string>\n    <string name=\"bookshelf_px_2\">按書名</string>\n    <string name=\"bookshelf_px_3\">手動排序</string>\n    <string name=\"read_type\">閱讀方式</string>\n    <string name=\"compose_type\">排版</string>\n    <string name=\"del_select\">刪除所選</string>\n    <string name=\"del_msg\">是否確認刪除？</string>\n    <string name=\"clear_font\">預設字體</string>\n    <string name=\"find_on_www\">發現</string>\n    <string name=\"find_source_manage\">發現管理</string>\n    <string name=\"find_empty\">沒有內容，去書源裡自訂吧！</string>\n    <string name=\"del_all\">刪除所有</string>\n    <string name=\"searchHistory\">搜尋歷史</string>\n    <string name=\"clear\">清除</string>\n    <string name=\"showTitle\">正文顯示標題</string>\n    <string name=\"refresh_default\">書源同步</string>\n    <string name=\"no_last_chapter\">無最新章節訊息</string>\n    <string name=\"showTimeBattery\">顯示時間和電量</string>\n    <string name=\"showLine\">顯示分隔線</string>\n    <string name=\"dark_status_icon\">深色狀態欄圖示</string>\n    <string name=\"content\">內容</string>\n    <string name=\"copy_text\">複製內容</string>\n    <string name=\"download_all\">一鍵快取</string>\n    <string name=\"content_sl\">這是一段測試文字\\n\\u3000\\u3000只是讓你看看效果的</string>\n    <string name=\"text_bg_style\">文字顏色和背景(長按自訂)</string>\n    <string name=\"immersion_status_bar\">沉浸式狀態欄</string>\n    <string name=\"un_download\">還剩%d章未下載</string>\n    <string name=\"long_click_input_color\">長按輸入顏色值</string>\n    <string name=\"loading\">載入中…</string>\n    <string name=\"group_zg\">追更區</string>\n    <string name=\"group_yf\">養肥區</string>\n    <string name=\"bookmark\">書籤</string>\n    <string name=\"bookmark_add\">新增書籤</string>\n    <string name=\"action_del\">刪除</string>\n    <string name=\"load_over_time\">載入超時</string>\n    <string name=\"join_group\">關注：%s</string>\n    <string name=\"copy_complete\">已複製</string>\n    <string name=\"bookshelf_management\">書架管理</string>\n    <string name=\"clear_bookshelf_s\">這將會刪除所有書籍，請謹慎操作。</string>\n    <string name=\"search_book_source\">搜尋書源</string>\n    <string name=\"search_rss_source\">搜尋訂閱源</string>\n    <string name=\"search_book_source_num\">搜尋(共%d個書源)</string>\n    <string name=\"chapter_list_size\">目錄(%d)</string>\n    <string name=\"text_bold\">加粗</string>\n    <string name=\"text_font\">字體</string>\n    <string name=\"text\">文字</string>\n    <string name=\"home_page\">軟體首頁</string>\n    <string name=\"right\">右</string>\n    <string name=\"left\">左</string>\n    <string name=\"bottom\">下</string>\n    <string name=\"top\">上</string>\n    <string name=\"padding\">邊距</string>\n    <string name=\"padding_top\">上邊距</string>\n    <string name=\"padding_bottom\">下邊距</string>\n    <string name=\"padding_left\">左邊距</string>\n    <string name=\"padding_right\">右邊距</string>\n    <string name=\"check_book_source\">校驗書源</string>\n    <string name=\"check_select_source\">校驗所選</string>\n    <string name=\"progress_show\">%1$s      進度 %2$d/%3$d</string>\n    <string name=\"tts_fix\">請安裝並選擇中文TTS！</string>\n    <string name=\"tts_init_failed\">TTS初始化失敗！</string>\n    <string name=\"jf_convert\">簡繁轉換</string>\n    <string name=\"jf_convert_o\">關閉</string>\n    <string name=\"jf_convert_f\">簡轉繁</string>\n    <string name=\"jf_convert_j\">繁轉簡</string>\n    <string name=\"page_mode\">翻頁模式</string>\n    <string name=\"nb_file_sub_count\">%1$d 項</string>\n    <string name=\"nb_file_path\">記憶卡：</string>\n    <string name=\"nb_file_add_shelf\">加入書架</string>\n    <string name=\"nb_file_add_shelves\">加入書架(%1$d)</string>\n    <string name=\"nb_file_add_succeed\">成功新增%1$d本書</string>\n    <string name=\"fonts_folder\">請將字體檔案放到SD根目錄Fonts資料夾下重新選擇</string>\n    <string name=\"default_font\">預設字體</string>\n    <string name=\"select_font\">選擇字體</string>\n    <string name=\"text_size\">字號</string>\n    <string name=\"line_size\">行距</string>\n    <string name=\"paragraph_size\">段距</string>\n    <string name=\"to_top\">置頂</string>\n    <string name=\"selection_to_top\">置頂所選</string>\n    <string name=\"to_bottom\">置底</string>\n    <string name=\"selection_to_bottom\">置底所選</string>\n    <string name=\"auto_expand_find\">自動展開發現</string>\n    <string name=\"default_expand_first\">預設展開第一組發現</string>\n    <string name=\"threads_num\">目前執行緒數 %s</string>\n    <string name=\"read_aloud_speed\">朗讀語速</string>\n    <string name=\"auto_next_page\">自動翻頁</string>\n    <string name=\"auto_next_page_stop\">停止自動翻頁</string>\n    <string name=\"auto_next_page_speed\">自動翻頁間隔</string>\n    <string name=\"book_info\">書籍訊息</string>\n    <string name=\"book_info_edit\">書籍訊息編輯</string>\n    <string name=\"ps_default_read\">預設打開書架</string>\n    <string name=\"pt_default_read\">自動跳轉最近閱讀</string>\n    <string name=\"replace_scope\">取代範圍，選填書名或者書源url</string>\n    <string name=\"menu_action_group\">分組</string>\n    <string name=\"download_path\">內容快取路徑</string>\n    <string name=\"sys_file_picker\">系統檔案選擇器</string>\n    <string name=\"new_version\">新版本</string>\n    <string name=\"download_update\">下載更新</string>\n    <string name=\"volume_key_page_on_play\">朗讀時音量鍵翻頁</string>\n    <string name=\"tip_margin_change\">Tip邊距跟隨邊距調整</string>\n    <string name=\"allow_update\">允許更新</string>\n    <string name=\"disable_update\">禁止更新</string>\n    <string name=\"split_long_chapter\">分割超長章節</string>\n    <string name=\"need_more_time_load_content\">正文長度過長時，載入正文可能會花費更多時間</string>\n    <string name=\"revert_selection\">反選</string>\n    <string name=\"search_book_key\">搜尋書名、作者</string>\n    <string name=\"debug_hint\">書名、作者、URL</string>\n    <string name=\"faq\">常見問題</string>\n    <string name=\"pt_show_all_find\">顯示所有發現</string>\n    <string name=\"ps_show_all_find\">關閉則只顯示勾選源的發現</string>\n    <string name=\"update_toc\">更新目錄</string>\n    <string name=\"txt_toc_rule\">TXT 目錄規則</string>\n    <string name=\"set_charset\">設定編碼</string>\n    <string name=\"swap_sort\">倒序-順序</string>\n    <string name=\"sort\">排序</string>\n    <string name=\"sort_auto\">智慧排序</string>\n    <string name=\"sort_default\">默認排序</string>\n    <string name=\"sort_manual\">手動排序</string>\n    <string name=\"sort_by_name\">名稱排序</string>\n    <string name=\"go_to_top\">滾動到頂部</string>\n    <string name=\"go_to_bottom\">滾動到底部</string>\n    <string name=\"read_y\">已讀：%s</string>\n    <string name=\"pursue_more\">追更</string>\n    <string name=\"fattening\">養肥</string>\n    <string name=\"finish\">完結</string>\n    <string name=\"all_book\">所有書籍</string>\n    <string name=\"pursue_more_book\">追更書籍</string>\n    <string name=\"fattening_book\">養肥書籍</string>\n    <string name=\"finish_book\">完結書籍</string>\n    <string name=\"local_book\">本機書籍</string>\n    <string name=\"status_bar_immersion\">狀態欄顏色透明</string>\n    <string name=\"imm_navigation_bar\">沉浸式導航欄</string>\n    <string name=\"imm_navigation_bar_s\">導航欄顏色透明</string>\n    <string name=\"add_to_bookshelf\">放入書架</string>\n    <string name=\"continue_read\">繼續閱讀</string>\n    <string name=\"cover_path\">封面地址</string>\n    <string name=\"page_anim_cover\">覆蓋</string>\n    <string name=\"page_anim_slide\">滑動</string>\n    <string name=\"page_anim_simulation\">模擬</string>\n    <string name=\"page_anim_scroll\">滾動</string>\n    <string name=\"page_anim_none\">無動畫</string>\n    <string name=\"disable_manga_page_anim\">禁用翻頁動畫</string>\n    <string name=\"up_change_source_last_chapter_t\">後台更新換源最新章節</string>\n    <string name=\"up_change_source_last_chapter_s\">開啟則會在軟體打開1分鐘後開始更新</string>\n    <string name=\"behavior_main_t\">書架ToolBar自動隱藏</string>\n    <string name=\"behavior_main_s\">滾動書架時ToolBar自動隱藏與顯示</string>\n    <string name=\"login\">登入</string>\n    <string name=\"login_source\">登入%s</string>\n    <string name=\"success\">成功</string>\n    <string name=\"source_no_login\">目前源沒有配置登入地址</string>\n    <string name=\"no_prev_page\">沒有上一頁</string>\n    <string name=\"no_next_page\">沒有下一頁</string>\n\n    <!-- source start-->\n    <string name=\"source_name\">源名稱(sourceName)</string>\n    <string name=\"source_url\">源URL(sourceUrl)</string>\n    <string name=\"source_group\">源分組(sourceGroup)</string>\n    <string name=\"diy_source_group\">自訂源分組</string>\n    <string name=\"diy_edit_source_group\">輸入自訂源分組名稱</string>\n    <string name=\"concurrent_rate\">並發率(concurrentRate)</string>\n    <string name=\"sort_url\">分類Url(sortUrl)</string>\n    <string name=\"login_url\">登入URL(loginUrl)</string>\n    <string name=\"login_ui\">登入UI(loginUi)</string>\n    <string name=\"login_check_js\">登入檢查JS(loginCheckJs)</string>\n    <string name=\"comment\">源注釋(sourceComment)</string>\n    <string name=\"r_search_url\">搜尋地址(url)</string>\n    <string name=\"r_find_url\">發現地址規則(url)</string>\n    <string name=\"r_book_list\">書籍列表規則(bookList)</string>\n    <string name=\"r_book_name\">書名規則(name)</string>\n    <string name=\"r_book_url\">詳情頁url規則(bookUrl)</string>\n    <string name=\"r_author\">作者規則(author)</string>\n    <string name=\"rule_book_kind\">分類規則(kind)</string>\n    <string name=\"rule_book_intro\">簡介規則(intro)</string>\n    <string name=\"rule_cover_url\">封面規則(coverUrl)</string>\n    <string name=\"rule_last_chapter\">最新章節規則(lastChapter)</string>\n    <string name=\"rule_word_count\">字數規則(wordCount)</string>\n    <string name=\"book_url_pattern\">書籍URL正則(bookUrlPattern)</string>\n    <string name=\"rule_book_info_init\">預處理規則(bookInfoInit)</string>\n    <string name=\"rule_toc_url\">目錄URL規則(tocUrl)</string>\n    <string name=\"rule_can_re_name\">允許修改書名作者(canReName)</string>\n    <string name=\"rule_next_toc_url\">目錄下一頁規則(nextTocUrl)</string>\n    <string name=\"rule_chapter_list\">目錄列表規則(chapterList)</string>\n    <string name=\"rule_chapter_name\">章節名稱規則(ChapterName)</string>\n    <string name=\"rule_chapter_url\">章節URL規則(chapterUrl)</string>\n    <string name=\"rule_is_volume\">Volume標識(isVolume)</string>\n    <string name=\"rule_is_vip\">VIP標識(isVip)</string>\n    <string name=\"rule_update_time\">更新時間(ChapterInfo)</string>\n    <string name=\"pre_update_js\">更新之前Js(preUpdateJs)</string>\n    <string name=\"rule_book_content\">正文規則(content)</string>\n    <string name=\"rule_next_content\">正文下一頁URL規則(nextContentUrl)</string>\n    <string name=\"rule_web_js\">WebViewJs(webJs)</string>\n    <string name=\"rule_image_style\">圖片樣式(imageStyle)</string>\n    <string name=\"rule_replace_regex\">取代規則(replaceRegex)</string>\n    <string name=\"rule_source_regex\">資源正則(sourceRegex)</string>\n    <string name=\"rule_pay_action\">購買操作(payAction)</string>\n\n    <string name=\"source_icon\">圖示(sourceIcon)</string>\n    <string name=\"r_articles\">列表規則(ruleArticles)</string>\n    <string name=\"r_next\">列表下一頁規則(ruleNextArticles)</string>\n    <string name=\"r_title\">標題規則(ruleTitle)</string>\n    <string name=\"r_guid\">guid規則(ruleGuid)</string>\n    <string name=\"r_date\">時間規則(rulePubDate)</string>\n    <string name=\"r_categories\">類別規則(ruleCategories)</string>\n    <string name=\"r_description\">描述規則(ruleDescription)</string>\n    <string name=\"r_image\">圖片url規則(ruleImage)</string>\n    <string name=\"r_content\">內容規則(ruleContent)</string>\n    <string name=\"r_style\">樣式(style)</string>\n    <string name=\"r_inject_js\">注入Js(injectJs)</string>\n    <string name=\"r_link\">連結規則(ruleLink)</string>\n    <string name=\"check_key_word\">校驗關鍵字(checkKeyWord)</string>\n    <string name=\"rule_actions\">操作(actions)</string>\n    <string name=\"rule_is_pay\">購買標誌(isPay)</string>\n    <!-- source end-->\n\n    <!--error string start-->\n    <string name=\"error_no_source\">沒有源</string>\n    <string name=\"error_get_book_info\">書籍訊息獲取失敗</string>\n    <string name=\"error_get_content\">內容獲取失敗</string>\n    <string name=\"error_get_chapter_list\">目錄獲取失敗</string>\n    <string name=\"error_get_web_content\">瀏覽網站失敗：%s</string>\n    <string name=\"error_read_file\">文件讀取失敗</string>\n    <string name=\"error_load_toc\">載入目錄失敗</string>\n    <string name=\"error_get_data\">獲取資料失敗！</string>\n    <string name=\"error_load_msg\">載入失敗\\n%s</string>\n    <string name=\"net_error_10001\">沒有網路</string>\n    <string name=\"net_error_10002\">網路連接超時</string>\n    <string name=\"net_error_10003\">資料解析失敗</string>\n    <!--error string end-->\n\n    <string name=\"source_http_header\">請求頭(header)</string>\n    <string name=\"debug_source\">除錯源</string>\n    <string name=\"import_by_qr_code\">二維碼匯入</string>\n    <string name=\"share_selected_source\">分享選取源</string>\n    <string name=\"scan_qr_code\">掃描二維碼</string>\n    <string name=\"click_on_selected_show_menu\">選中時點擊可彈出選單</string>\n    <string name=\"theme\">主題</string>\n    <string name=\"theme_mode\">主題模式</string>\n    <string name=\"theme_mode_desc\">選擇主題模式</string>\n    <string name=\"join_qq_group\">加入QQ群</string>\n    <string name=\"bg_image_per\">獲取背景圖片需儲存權限</string>\n    <string name=\"input_book_source_url\">輸入書源網址</string>\n    <string name=\"del_file\">刪除文件</string>\n    <string name=\"del_file_success\">刪除文件成功</string>\n    <string name=\"sure_del_file\">確定刪除文件嗎？</string>\n    <string name=\"files_tree\">手機目錄</string>\n    <string name=\"intelligent_import\">智慧匯入</string>\n    <string name=\"discovery\">發現</string>\n    <string name=\"switch_display_style\">切換顯示樣式</string>\n    <string name=\"import_per\">匯入本機書籍需儲存權限</string>\n    <string name=\"night_theme\">夜間模式</string>\n    <string name=\"eink_theme\">E-Ink 模式</string>\n    <string name=\"eink_theme_desc\">電子墨水屏模式</string>\n    <string name=\"get_storage_per\">需要儲存權限</string>\n    <string name=\"double_click_exit\">再按一次退出程式</string>\n    <string name=\"import_book_per\">匯入本機書籍需儲存權限</string>\n    <string name=\"network_connection_unavailable\">網路連接不可用</string>\n    <string name=\"yes\">是</string>\n    <string name=\"no\">否</string>\n    <string name=\"sure\">確認</string>\n    <string name=\"sure_del\">是否確認刪除？</string>\n    <string name=\"sure_del_any\">是否確認刪除 %s？</string>\n    <string name=\"sure_del_all_book\">是否刪除全部書籍？</string>\n    <string name=\"sure_del_download_book\">是否同時刪除已下載的書籍目錄？</string>\n    <string name=\"qr_per\">掃描二維碼需相機權限</string>\n    <string name=\"aloud_can_not_auto_page\">朗讀正在執行，不能自動翻頁</string>\n    <string name=\"input_charset\">輸入編碼</string>\n    <string name=\"text_chapter_list_rule\">TXT目錄規則</string>\n    <string name=\"open_local_book_per\">打開外部書籍需獲取儲存權限</string>\n    <string name=\"no_book_name\">未獲取到書名</string>\n    <string name=\"input_replace_url\">輸入取代規則網址</string>\n    <string name=\"get_book_list_success\">搜尋列表獲取成功%d</string>\n    <string name=\"non_null_name_url\">名稱和URL不能為空</string>\n    <string name=\"gallery\">圖庫</string>\n    <string name=\"get_ali_pay_hb\">領支付寶紅包</string>\n    <string name=\"non_update_url\">沒有獲取到更新地址</string>\n    <string name=\"check_host_cookie\">正在打開首頁，成功自動返回主介面</string>\n    <string name=\"click_check_after_success\">登入成功後請點擊右上角圖示進行首頁訪問測試</string>\n    <string name=\"chapter\">章</string>\n    <string name=\"to\">至</string>\n    <string name=\"use_regex\">使用正規表示式</string>\n    <string name=\"text_indent\">縮排</string>\n    <string name=\"indent_0\">無縮排</string>\n    <string name=\"indent_1\">一字元縮排</string>\n    <string name=\"indent_2\">二字元縮排</string>\n    <string name=\"indent_3\">三字元縮排</string>\n    <string name=\"indent_4\">四字元縮排</string>\n    <string name=\"select_folder\">選擇資料夾</string>\n    <string name=\"select_file\">選擇文件</string>\n    <string name=\"no_find\">沒有發現，可以在書源裡新增。</string>\n    <string name=\"restore_default\">復原預設</string>\n    <string name=\"set_download_per\">自訂快取路徑需要儲存權限</string>\n    <string name=\"black\">黑色</string>\n    <string name=\"content_empty\">文章內容為空</string>\n    <string name=\"on_change_source\">正在換源請等待…</string>\n    <string name=\"chapter_list_empty\">目錄列表為空</string>\n    <string name=\"text_letter_spacing\">字距</string>\n\n    <string name=\"source_tab_base\">基本</string>\n    <string name=\"source_tab_search\">搜尋</string>\n    <string name=\"source_tab_find\">發現</string>\n    <string name=\"source_tab_info\">詳情</string>\n    <string name=\"source_tab_toc\">目錄</string>\n    <string name=\"source_tab_content\">正文</string>\n\n    <string name=\"e_ink_mode\">E-Ink 模式</string>\n    <string name=\"e_ink_mode_detail\">去除動畫，最佳化電紙書使用體驗</string>\n    <string name=\"web_menu\">Web服務</string>\n    <string name=\"web_port_title\">web埠</string>\n    <string name=\"web_port_summary\">目前埠 %s</string>\n    <string name=\"qr_share\">二維碼分享</string>\n    <string name=\"str_share\">字串分享</string>\n    <string name=\"wifi_share\">wifi分享</string>\n    <string name=\"please_grant_storage_permission\">請給於儲存權限</string>\n    <string name=\"fast_rewind\">減速</string>\n    <string name=\"fast_forward\">加速</string>\n    <string name=\"skip_previous\">上一個</string>\n    <string name=\"skip_next\">下一個</string>\n    <string name=\"music\">音樂</string>\n    <string name=\"audio\">音訊</string>\n    <string name=\"is_enable\">啟用</string>\n    <string name=\"enable_js\">啟用JS</string>\n    <string name=\"load_with_base_url\">載入BaseUrl</string>\n    <string name=\"all_source\">全部書源</string>\n    <string name=\"cannot_empty\">輸入不能為空</string>\n    <string name=\"clear_find_cache\">清空發現快取</string>\n    <string name=\"edit_find\">編輯發現</string>\n    <string name=\"change_icon_summary\">切換軟體顯示在桌面的圖示</string>\n    <string name=\"help\">幫助</string>\n    <string name=\"my\">我的</string>\n    <string name=\"reading\">閱讀</string>\n    <string name=\"battery_show\">%d%%</string>\n    <string name=\"timer_m\">%d分鐘</string>\n    <string name=\"brightness_auto\">自動亮度%s</string>\n    <string name=\"read_aloud_by_page\">按頁朗讀</string>\n    <string name=\"speak_engine\">朗讀引擎</string>\n    <string name=\"bg_image\">背景圖片</string>\n    <string name=\"bg_color\">背景顏色</string>\n    <string name=\"text_color\">文字顏色</string>\n    <string name=\"select_image\">選擇圖片</string>\n    <string name=\"group_manage\">分組管理</string>\n    <string name=\"group_select\">分組選擇</string>\n    <string name=\"group_edit\">編輯分組</string>\n    <string name=\"move_to_group\">移入分組</string>\n    <string name=\"add_group\">新增分組</string>\n    <string name=\"remove_group\">移除分組</string>\n    <string name=\"add_replace_rule\">建立取代</string>\n    <string name=\"group\">分組</string>\n    <string name=\"group_s\">分組：%s</string>\n    <string name=\"toc_s\">目錄：%s</string>\n    <string name=\"enable_explore\">啟用發現</string>\n    <string name=\"disable_explore\">禁用發現</string>\n    <string name=\"enable_selection\">啟用所選</string>\n    <string name=\"disable_selection\">禁用所選</string>\n    <string name=\"export_selection\">匯出所選</string>\n    <string name=\"export\">匯出</string>\n    <string name=\"load_toc\">載入目錄</string>\n    <string name=\"load_info\">載入詳情頁</string>\n    <string name=\"tts\">文字轉語音</string>\n    <string name=\"web_dav_pw\">WebDav 密碼</string>\n    <string name=\"web_dav_pw_s\">輸入你的WebDav授權密碼</string>\n    <string name=\"web_dav_url_s\">輸入你的伺服器地址</string>\n    <string name=\"web_dav_url\">WebDav 伺服器地址</string>\n    <string name=\"web_dav_account\">WebDav 帳號</string>\n    <string name=\"web_dav_account_s\">輸入你的WebDav帳號</string>\n    <string name=\"rss_source\">訂閱源</string>\n    <string name=\"rss_source_edit\">編輯訂閱源</string>\n    <string name=\"screen\">篩選</string>\n    <string name=\"screen_find\">篩選發現源</string>\n    <string name=\"dur_pos\">目前位置：</string>\n    <string name=\"precision_search\">精準搜尋</string>\n    <string name=\"service_starting\">正在啟動服務</string>\n    <string name=\"empty\">空</string>\n    <string name=\"file_chooser\">文件選擇</string>\n    <string name=\"folder_chooser\">資料夾選擇</string>\n    <string name=\"bottom_line\">我是有底線的</string>\n    <string name=\"uri_to_path_fail\">Uri轉Path失敗</string>\n    <string name=\"refresh_cover\">重新整理封面</string>\n    <string name=\"change_cover_source\">封面換源</string>\n    <string name=\"select_local_image\">選擇本機圖片</string>\n    <string name=\"book_type\">類型：</string>\n    <string name=\"to_backstage\">後台</string>\n    <string name=\"importing\">正在匯入</string>\n    <string name=\"exporting\">正在匯出</string>\n    <string name=\"custom_page_key\">自訂翻頁按鍵</string>\n    <string name=\"prev_page_key\">上一頁按鍵</string>\n    <string name=\"next_page_key\">下一頁按鍵</string>\n    <string name=\"no_group\">未分組</string>\n    <string name=\"prev_sentence\">上一句</string>\n    <string name=\"next_sentence\">下一句</string>\n    <string name=\"other_folder\">其它目錄</string>\n    <string name=\"text_too_long_qr_error\">文字太多，生成二維碼失敗</string>\n    <string name=\"share_rss_source\">分享RSS源</string>\n    <string name=\"share_book_source\">分享書源</string>\n    <string name=\"auto_dark_mode\">自動切換夜間模式</string>\n    <string name=\"auto_dark_mode_s\">夜間模式跟隨系統</string>\n    <string name=\"go_back\">上級</string>\n    <string name=\"tone_colour\">線上朗讀音色</string>\n    <string name=\"select_count\">(%1$d/%2$d)</string>\n    <string name=\"show_rss\">顯示訂閱</string>\n    <string name=\"service_stop\">服務已停止</string>\n    <string name=\"service_start\">正在啟動服務\\n具體訊息查看通知欄</string>\n    <string name=\"default_path\">預設路徑</string>\n    <string name=\"sys_folder_picker\">系統資料夾選擇器</string>\n    <string name=\"app_folder_picker\">內建資料夾選擇器</string>\n    <string name=\"app_file_picker\">內建資料夾選擇器</string>\n    <string name=\"a10_permission_toast\">Android10以上因權限限制可能無法讀寫文件</string>\n    <string name=\"add_to_text_context_menu_s\">長按文字在操作選單中顯示閱讀·搜尋</string>\n    <string name=\"add_to_text_context_menu_t\">文字操作顯示搜尋</string>\n    <string name=\"record_log\">記錄日誌</string>\n    <string name=\"log\">日誌</string>\n    <string name=\"chinese_converter\">中文簡繁體轉換</string>\n    <string name=\"change_icon_error\">圖示為向量圖示，Android8.0以前不支援</string>\n    <string name=\"aloud_config\">朗讀設定</string>\n    <string name=\"main_activity\">主介面</string>\n    <string name=\"selectText\">長按選擇文字</string>\n    <string name=\"header\">頁首</string>\n    <string name=\"main_body\">正文</string>\n    <string name=\"footer\">頁尾</string>\n    <string name=\"select_end\">文字選擇結束位置</string>\n    <string name=\"select_start\">文字選擇開始位置</string>\n    <string name=\"share_layout\">共用布局</string>\n    <string name=\"browser\">瀏覽器</string>\n    <string name=\"import_default_rule\">匯入預設規則</string>\n    <string name=\"name\">名稱</string>\n    <string name=\"regex\">正則</string>\n    <string name=\"more_menu\">更多選單</string>\n    <string name=\"reduce\">減</string>\n    <string name=\"plus\">加</string>\n    <string name=\"system_typeface\">系統內建字體樣式</string>\n    <string name=\"delete_book_file\">刪除來源文件</string>\n    <string name=\"text_default\">默认</string>\n    <string name=\"default1\">預設一</string>\n    <string name=\"default2\">預設二</string>\n    <string name=\"default3\">預設三</string>\n    <string name=\"title\">標題</string>\n    <string name=\"title_left\">靠左</string>\n    <string name=\"title_center\">居中</string>\n    <string name=\"title_hide\">隱藏</string>\n    <string name=\"add_to_group\">加入分組</string>\n    <string name=\"save_image\">儲存圖片</string>\n    <string name=\"no_default_path\">沒有預設路徑</string>\n    <string name=\"change_group\">設定分組</string>\n    <string name=\"view_toc\">查看目錄</string>\n    <string name=\"bar_elevation\">導航欄陰影</string>\n    <string name=\"bar_elevation_s\">目前陰影大小(elevation)：%s</string>\n    <string name=\"btn_default_s\">預設</string>\n    <string name=\"main_menu\">主選單</string>\n    <string name=\"request_permission\">點擊授予權限</string>\n    <string name=\"tip_local_perm_request_storage\">閱讀需要存取記憶卡權限，請點擊下方的\"授予權限\"按鈕，或前往「設定」—「應用權限」—打開所需權限。如果授予權限後仍然不正常，請點擊右上角的「選擇資料夾」，使用系統資料夾選擇器。</string>\n    <string name=\"alouding_disable\">全文朗讀中不能朗讀選中文字</string>\n    <string name=\"read_body_to_lh\">擴展到瀏海</string>\n    <string name=\"toc_updateing\">更新目錄中</string>\n    <string name=\"media_button_on_exit_title\">全程響應耳機按鍵</string>\n    <string name=\"media_button_on_exit_summary\">即使退出軟體也響應耳機按鍵</string>\n    <string name=\"contributors\">開發人員</string>\n    <string name=\"contact\">聯繫我們</string>\n    <string name=\"license\">開源許可</string>\n    <string name=\"other\">其它</string>\n    <string name=\"official_account\">legado-top</string>\n    <string name=\"follow_official_account\">關注公眾號</string>\n    <string name=\"wechat\">微信</string>\n    <string name=\"thanks\">您的支援是我更新的動力</string>\n    <string name=\"about_official_account\">公眾號[开源阅读]</string>\n    <string name=\"source_auto_changing\">正在自動換源</string>\n    <string name=\"click_to_apply\">點擊加入</string>\n    <string name=\"middle\">中</string>\n    <string name=\"information\">訊息</string>\n    <string name=\"switchLayout\">切換布局</string>\n    <string name=\"text_font_weight_converter\">文章字重轉換</string>\n    <string name=\"full_screen_gestures_support\">全面屏手勢最佳化</string>\n    <string name=\"disable_return_key\">禁用返回鍵</string>\n\n    <!--color-->\n    <string name=\"primary\">主色調</string>\n    <string name=\"accent\">強調色</string>\n    <string name=\"background_color\">背景色</string>\n    <string name=\"navbar_color\">底部操作欄顏色</string>\n    <string name=\"day\">白天</string>\n    <string name=\"day_color_primary\">白天，主色調</string>\n    <string name=\"day_color_accent\">白天，強調色</string>\n    <string name=\"day_background_color\">白天，背景色</string>\n    <string name=\"day_navbar_color\">白天，底欄色</string>\n    <string name=\"night\">夜間</string>\n    <string name=\"night_primary\">夜間，主色調</string>\n    <string name=\"night_accent\">夜間，強調色</string>\n    <string name=\"night_background_color\">夜間，背景色</string>\n    <string name=\"night_navbar_color\">夜間，底欄色</string>\n    <string name=\"auto_change_source\">自動換源</string>\n    <string name=\"text_full_justify\">文字兩端對齊</string>\n    <string name=\"text_bottom_justify\">文字底部對齊</string>\n    <string name=\"auto_page_speed\">自動翻頁速度</string>\n    <string name=\"sort_by_url\">地址排序</string>\n    <string name=\"backup_summary\">本機和 WebDav 一起備份</string>\n    <string name=\"restore_summary\">優先從 WebDav 復原，長按從本機復原</string>\n    <string name=\"import_old_summary\">選擇舊版備份資料夾</string>\n    <string name=\"enabled\">已啟用</string>\n    <string name=\"disabled\">已禁用</string>\n    <string name=\"enabled_explore\">已啟用發現</string>\n    <string name=\"disabled_explore\">已禁用發現</string>\n    <string name=\"starting_download\">正在啟動下載</string>\n    <string name=\"already_in_download\">該書已在下載列表</string>\n    <string name=\"click_to_open\">點擊打開</string>\n    <string name=\"follow_public_account_summary\">關注[legado-top]點擊廣告支持我</string>\n    <string name=\"weChat_appreciation_code\">微信讚賞碼</string>\n    <string name=\"alipay\">支付寶</string>\n    <string name=\"alipay_red_envelope_search_code\">支付寶紅包搜尋碼</string>\n    <string name=\"alipay_red_envelope_copy\">537954522 點擊複製</string>\n    <string name=\"alipay_red_envelope_qr_code\">支付寶紅包二維碼</string>\n    <string name=\"alipay_payment_qr_code\">支付寶收款二維碼</string>\n    <string name=\"qq_collection_qr_code\">QQ收款二維碼</string>\n    <string name=\"contributors_summary\">gedoor、Invinciblelee、Xwite等，詳情請在github中查看</string>\n    <string name=\"clear_cache_summary\">清除已下載書籍和字體快取</string>\n    <string name=\"default_cover\">預設封面</string>\n    <string name=\"restore_ignore\">復原忽略列表</string>\n    <string name=\"restore_ignore_summary\">復原時忽略一些內容不復原，方便不同手機配置不同</string>\n    <string name=\"read_config\">閱讀介面設定</string>\n    <string name=\"group_name\">分組名稱</string>\n    <string name=\"note_content\">備註內容</string>\n    <string name=\"replace_enable_default_t\">預設啟用取代淨化</string>\n    <string name=\"replace_enable_default_s\">新加入書架的書是否啟用取代淨化</string>\n    <string name=\"select_restore_file\">選擇復原文件</string>\n    <string name=\"day_background_too_dark\">白天背景不能太暗</string>\n    <string name=\"day_bottom_bar_too_dark\">白天底欄不能太暗</string>\n    <string name=\"night_background_too_light\">夜間背景不能太亮</string>\n    <string name=\"night_bottom_bar_too_light\">夜間底欄不能太亮</string>\n    <string name=\"accent_background_diff\">強調色不能和背景顏色相似</string>\n    <string name=\"accent_text_diff\">強調色不能和文字顏色相似</string>\n    <string name=\"wrong_format\">格式不對</string>\n    <string name=\"error\">錯誤</string>\n    <string name=\"show_brightness_view\">顯示亮度調節控制項</string>\n    <string name=\"language\">語言</string>\n    <string name=\"import_rss_source\">匯入訂閱源</string>\n    <string name=\"donate_summary\">您的支援是我更新的動力</string>\n    <string name=\"about_summary\">公眾號[开源阅读软件]</string>\n    <string name=\"read_record\">閱讀記錄</string>\n    <string name=\"del_read_record\">刪除閲讀記錄</string>\n    <string name=\"read_record_summary\">閱讀時間記錄</string>\n    <string name=\"local_tts\">本機TTS</string>\n    <string name=\"thread_count\">執行緒數</string>\n    <string name=\"all_read_time\">總閱讀時間</string>\n    <string name=\"un_select_all\">全不選</string>\n    <string name=\"import_str\">匯入</string>\n    <string name=\"export_str\">匯出</string>\n    <string name=\"save_theme_config\">儲存主題配置</string>\n    <string name=\"save_day_theme_summary\">儲存白天主題配置以供呼叫和分享</string>\n    <string name=\"save_night_theme_summary\">儲存夜間主題配置以供呼叫和分享</string>\n    <string name=\"theme_list\">主題列表</string>\n    <string name=\"theme_list_summary\">使用儲存主題，匯入、分享主題</string>\n    <string name=\"select_theme\">切換預設主題</string>\n    <string name=\"sort_by_lastUpdateTime\">更新時間排序</string>\n    <string name=\"search_content\">全文搜尋</string>\n    <string name=\"rss_source_empty\">這裡目前什麼都沒有！</string>\n    <string name=\"explore_empty\">目前沒有發現源！</string>\n    <string name=\"page_key_set_help\">將焦點放到輸入框按下物理按鍵會自動輸入鍵值，多個按鍵會自動用英文逗號隔開。</string>\n    <string name=\"theme_name\">主題名稱</string>\n    <string name=\"auto_clear_expired\">自動清除過期搜尋資料</string>\n    <string name=\"auto_clear_expired_summary\">超過一天的搜尋資料</string>\n    <string name=\"re_segment\">重新分段</string>\n    <string name=\"style_name\">樣式名稱：</string>\n    <string name=\"empty_msg_import_book\">點擊右上角資料夾圖示，選擇資料夾</string>\n    <string name=\"scan_folder\">智慧掃描</string>\n    <string name=\"import_file_name\">匯入檔案名</string>\n    <string name=\"copy_book_url\">複製書籍URL</string>\n    <string name=\"copy_toc_url\">複製目錄URL</string>\n    <string name=\"no_book\">沒有書籍</string>\n    <string name=\"keep_original_name\">保留原名</string>\n    <string name=\"click_regional_config\">點擊區域設定</string>\n    <string name=\"close\">關閉</string>\n    <string name=\"next_page\">下一頁</string>\n    <string name=\"prev_page\">上一頁</string>\n    <string name=\"non_action\">無操作</string>\n    <string name=\"body_title\">正文標題</string>\n    <string name=\"show_hide\">顯示/隱藏</string>\n    <string name=\"header_footer\">頁首<![CDATA[&]]>頁尾</string>\n    <string name=\"rule_subscription\">規則訂閱</string>\n    <string name=\"rule_sub_empty_msg\">新增大佬們提供的規則匯入地址\\n新增後點擊可匯入規則</string>\n    <string name=\"get_book_progress\">拉取雲端進度</string>\n    <string name=\"cover_book_progress\">覆蓋雲端進度</string>\n    <string name=\"current_progress_exceeds_cloud\">目前進度超過雲端進度，是否同步？</string>\n    <string name=\"sync_book_progress_t\">同步閱讀進度</string>\n    <string name=\"sync_book_progress_s\">進入退出閱讀介面時同步閱讀進度</string>\n    <string name=\"sync_book_progress_plus_t\">同步增強</string>\n    <string name=\"sync_book_progress_plus_s\">重新進入頁面（息屏、從背景返回等）或網路恢復可用時同步雲端進度，同步新進度時會詢問</string>\n    <string name=\"create_bookmark_error\">建立書籤失敗</string>\n    <string name=\"single_url\">單URL</string>\n    <string name=\"export_bookshelf\">匯出書單</string>\n    <string name=\"import_bookshelf\">匯入書單</string>\n    <string name=\"pre_download\">預下載</string>\n    <string name=\"pre_download_m\">预下载%s页</string>\n    <string name=\"pre_download_s\">預先下載%s章正文</string>\n    <string name=\"is_enabled\">是否啟用</string>\n    <string name=\"background_image\">背景圖片</string>\n    <string name=\"background_image_blurring\">背景圖片虛化</string>\n    <string name=\"background_image_blurring_radius\">虛化半徑</string>\n    <string name=\"background_image_hint\">0為停用，啟用範圍1~25\\n半徑數值越大，虛化效果越高</string>\n    <string name=\"export_folder\">匯出資料夾</string>\n    <string name=\"export_charset\">匯出編碼</string>\n    <string name=\"export_no_chapter_name\">TXT不匯出章節名</string>\n    <string name=\"export_to_web_dav\">匯出到WebDav</string>\n    <string name=\"reverse_content\">反轉內容</string>\n    <string name=\"debug\">除錯</string>\n    <string name=\"crash_log\">當機日誌</string>\n    <string name=\"use_zh_layout\">使用自訂中文分行</string>\n    <string name=\"image_style\">圖片樣式</string>\n    <string name=\"system_tts\">系統TTS</string>\n    <string name=\"export_type\">匯出格式</string>\n    <string name=\"checkAuthor\">校驗作者</string>\n    <string name=\"search_src\">搜尋原始碼</string>\n    <string name=\"boo_src\">書籍原始碼</string>\n    <string name=\"toc_src\">目錄原始碼</string>\n    <string name=\"content_src\">正文原始碼</string>\n    <string name=\"list_src\">列表原始碼</string>\n    <string name=\"url_already\">此url已訂閱</string>\n    <string name=\"high_brush_title\">高刷</string>\n    <string name=\"high_brush_summary\">使用螢幕最高更新率</string>\n    <string name=\"export_all\">匯出所有</string>\n    <string name=\"complete\">完成</string>\n    <string name=\"show_unread\">顯示未讀標誌</string>\n    <string name=\"use_default_cover\">總是使用預設封面</string>\n    <string name=\"use_default_cover_s\">總是顯示預設封面，不顯示網路封面</string>\n    <string name=\"title_font_size\">字號</string>\n    <string name=\"title_margin_top\">上邊距</string>\n    <string name=\"title_margin_bottom\">下邊距</string>\n    <string name=\"show\">顯示</string>\n    <string name=\"hide\">隱藏</string>\n    <string name=\"hide_when_status_bar_show\">狀態欄顯示時隱藏</string>\n    <string name=\"reverse_toc\">反轉目錄</string>\n    <string name=\"show_discovery\">顯示發現</string>\n    <string name=\"style\">樣式</string>\n    <string name=\"group_style\">分組樣式</string>\n    <string name=\"export_file_name\">匯出檔案名</string>\n    <string name=\"reset\">重設</string>\n    <string name=\"null_url\">url為空</string>\n    <string name=\"dict\">字典</string>\n    <string name=\"unknown_error\">未知錯誤</string>\n    <string name=\"autobackup_fail\">自動備份失敗\\n%s</string>\n    <string name=\"end\">結束</string>\n    <string name=\"custom_group_summary\">關閉取代分組/開啟新增分組</string>\n    <string name=\"pref_media_button_per_next\">媒體按鈕•上一首|下一首</string>\n    <string name=\"pref_media_button_per_next_summary\">上一段|下一段/上一章|下一章</string>\n    <string name=\"read_aloud_by_page_summary\">及時翻頁，翻頁時會停頓一下</string>\n    <string name=\"check_source_show_debug_message\">校驗書源顯示詳細資訊</string>\n    <string name=\"check_source_show_debug_message_summary\">書源校驗時顯示網路請求步驟和時間</string>\n    <string name=\"need_login\">需登入</string>\n    <string name=\"pref_cronet_summary\">使用Cronet網路元件</string>\n    <string name=\"anti_alias\">抗鋸齒</string>\n    <string name=\"pref_anti_alias_summary\">繪製圖片時抗鋸齒</string>\n    <string name=\"upload_url\">上傳URL</string>\n    <string name=\"download_url_rule\">下载URL规则(downloadUrls)</string>\n    <string name=\"sort_by_respondTime\">反應時間排序</string>\n    <string name=\"respondTime\">反應時間：%1$d ms</string>\n    <string name=\"export_success\">匯出成功</string>\n    <string name=\"path\">路徑</string>\n    <string name=\"direct_link_upload_rule\">直鏈上傳規則</string>\n    <string name=\"direct_link_upload_rule_summary\">用於匯出書源書單時生成直鏈url</string>\n    <string name=\"direct_link_upload_config\">直鏈上傳配置</string>\n    <string name=\"copy_play_url\">複製播放Url</string>\n    <string name=\"set_source_variable\">設定源變數</string>\n    <string name=\"set_book_variable\">設定書籍變數</string>\n    <string name=\"summary\">注釋</string>\n    <string name=\"cover_config\">封面設定</string>\n    <string name=\"cover_config_summary\">通用封面規則及預設封面樣式</string>\n    <string name=\"cover_show_name\">顯示書名</string>\n    <string name=\"cover_show_name_summary\">封面上顯示書名</string>\n    <string name=\"cover_show_author\">顯示作者</string>\n    <string name=\"cover_show_author_summary\">封面上顯示作者</string>\n    <string name=\"read_aloud_prev_paragraph\">朗讀上一段</string>\n    <string name=\"read_aloud_next_paragraph\">朗讀下一段</string>\n    <string name=\"wait_download\">待下載</string>\n    <string name=\"download_success\">下載完成</string>\n    <string name=\"download_error\">下載失敗</string>\n    <string name=\"downloading\">下載中</string>\n    <string name=\"unknown_state\">未知狀態</string>\n    <string name=\"disable_source\">禁用源</string>\n    <string name=\"delete_source\">刪除源</string>\n    <string name=\"chapter_pay\">購買</string>\n    <string name=\"double_page_horizontal\">平板/橫屏雙頁</string>\n    <string name=\"open_in_browser\">瀏覽器打開</string>\n    <string name=\"copy_url\">複製url</string>\n    <string name=\"full_screen\">全屏</string>\n    <string name=\"open_fun\">打開方式</string>\n    <string name=\"use_browser_open\">是否使用外部瀏覽器打開？</string>\n    <string name=\"see\">查看</string>\n    <string name=\"open\">打開</string>\n    <string name=\"del_login_header\">刪除登入頭</string>\n    <string name=\"show_login_header\">查看登入頭</string>\n    <string name=\"login_header\">登入頭</string>\n    <string name=\"font_scale\">字體大小</string>\n    <string name=\"font_scale_summary\">目前字體大小：%.1f</string>\n    <string name=\"tts_speech_reduce\">語速減</string>\n    <string name=\"tts_speech_add\">語速加</string>\n    <string name=\"open_sys_dir_picker_error\">打開系統資料夾選擇器出錯，自動打開應用程式資料夾選擇器</string>\n    <string name=\"open_sys_doc_picker_error\">打開系統檔案選擇器出錯，自動打開應用文件選擇器</string>\n    <string name=\"expand_text_menu\">展開文字選擇選單</string>\n    <string name=\"search_content_size\">搜尋結果</string>\n    <string name=\"search_content_empty\">搜索内容为空，检查净化/简繁设置</string>\n    <string name=\"book_tree_uri_t\">書籍儲存位置</string>\n    <string name=\"book_tree_uri_s\">從其它應用程式打開的書籍儲存位置</string>\n    <string name=\"select_book_folder\">選擇儲存書籍的資料夾</string>\n    <string name=\"user_agent\">使用者代理</string>\n    <string name=\"bg_alpha\">背景透明度</string>\n\n    <!-- check source config string -->\n    <string name=\"check_source_config\">校驗設定</string>\n    <string name=\"check_source_item\">校驗項目</string>\n    <string name=\"check_source_timeout\">單個書源校驗超時(秒)</string>\n    <string name=\"timeout\">超時</string>\n    <string name=\"seconds\">秒</string>\n    <string name=\"less_than\">小於</string>\n    <string name=\"check_source_config_summary\">校驗超時：%1$s秒\\n校驗項目：%2$s</string>\n    <string name=\"record_debug_log\">記錄除錯日誌</string>\n    <string name=\"sub_dir\">子資料夾</string>\n    <string name=\"general\">全域</string>\n    <string name=\"use_replace\">使用取代</string>\n    <string name=\"scope_title\">作用於標題</string>\n    <string name=\"scope_content\">作用於正文</string>\n    <string name=\"join_qq_channel\">加入QQ頻道</string>\n    <string name=\"qq_channel_summary\">點擊加入閱讀QQ頻道</string>\n    <string name=\"menu_refresh_dur\">重新整理目前章節</string>\n    <string name=\"menu_refresh_after\">重新整理之後章節</string>\n    <string name=\"menu_refresh_all\">重新整理全部章節</string>\n    <string name=\"edit_content\">編輯內容</string>\n    <string name=\"chapter_change_source\">單章換源</string>\n    <string name=\"book_change_source\">整書換源</string>\n    <string name=\"sort_by_time\">時間排序</string>\n    <string name=\"enable_record\">開啟記錄</string>\n    <string name=\"copy_all\">複製所有</string>\n    <string name=\"auto_complete\">自動補全</string>\n    <string name=\"sort_by_size\">大小排序</string>\n    <string name=\"welcome_style\">啟動介面樣式</string>\n    <string name=\"welcome_style_summary\">啟動介面圖片和是否顯示文字等</string>\n    <string name=\"show_welcome_text\">顯示文字</string>\n    <string name=\"welcome_text\">閱讀|享受美好時光</string>\n    <string name=\"custom_welcome\">自訂歡迎頁</string>\n    <string name=\"custom_welcome_summary\">是否使用自訂歡迎頁</string>\n    <string name=\"show_icon\">顯示圖示</string>\n    <string name=\"show_default_book_icon\">顯示預設書籍圖示</string>\n    <string name=\"cache_export\">快取/匯出</string>\n    <string name=\"assists_key_config\">輔助按鍵配置</string>\n    <string name=\"url_option\">Url參數</string>\n    <string name=\"only_wifi\">僅WIFI</string>\n    <string name=\"only_wifi_summary\">僅在wifi下載入網路封面</string>\n    <string name=\"cover_rule\">封面規則</string>\n    <string name=\"cover_rule_summary\">進入詳情頁時使用封面規則重新獲取封面</string>\n    <string name=\"scroll_to_dur_source\">定位到目前書源</string>\n    <string name=\"sys_tts_config\">系統tts設定</string>\n    <string name=\"sys_tts_config_summary\">打開系統tts設定介面</string>\n    <string name=\"cannot_timed_non_playback\">非播放狀態無法定時</string>\n    <string name=\"all_bookmark\">所有書籤</string>\n    <string name=\"change_source_batch\">批次換源</string>\n    <string name=\"book_type_different\">書籍類型不一樣</string>\n    <string name=\"soure_change_source\">是否確認換源</string>\n    <string name=\"input_verification_code\">輸入驗證碼</string>\n    <string name=\"verification_code\">驗證碼</string>\n    <string name=\"timeout_millisecond\">超時毫秒數</string>\n    <string name=\"file_not_supported\">文件%1$s 不受支援，是否繼續打開？</string>\n    <string name=\"import_tts\">匯入TTS</string>\n    <string name=\"import_theme\">匯入主題</string>\n    <string name=\"import_txt_toc_rule\">匯入txt目錄規則</string>\n    <string name=\"auto_save_cookie\">CookieJar</string>\n    <string name=\"cookie\">清除cookie</string>\n    <string name=\"download_and_import_file\">匯入線上書籍文件</string>\n    <string name=\"upload_book_success\">上傳成功</string>\n    <string name=\"upload_book_fail\">上傳失敗</string>\n    <string name=\"download_book_success\">下載成功</string>\n    <string name=\"download_book_fail\">下載失敗</string>\n    <string name=\"upload_to_remote\">上傳</string>\n    <string name=\"add_remote_book\">远程書籍</string>\n    <string name=\"bitmap_cache_size_summary\">目前最大快取 %1$s MB</string>\n    <string name=\"bitmap_cache_size\">圖片繪製快取</string>\n    <string name=\"image_retain_number_summary\">保留已讀章節數量 %s</string>\n    <string name=\"image_retain_number\">漫畫保留數量</string>\n    <string name=\"export_pics_file\">TXT匯出圖片</string>\n    <!-- string end -->\n    <string name=\"error_decode_bitmap\">圖片解碼失敗</string>\n    <string name=\"error_image_url_empty\">圖片連結為空，檢查取代淨化規則</string>\n    <string name=\"variable_comment\">變數說明(variableComment)</string>\n    <string name=\"remote_book\">遠端書籍</string>\n    <string name=\"reading_time_sort\">閱讀時長排序</string>\n    <string name=\"last_read_time_sort\">閱讀時間排序</string>\n    <string name=\"reading_time_tag\">閱讀時長：</string>\n    <string name=\"last_read_time_tag\">最後閱讀時間：</string>\n    <string name=\"page_touch_slop_title\">滑動翻頁閾值</string>\n    <string name=\"page_touch_slop_dialog_title\">滑動翻頁閾值（0 = 系統預設值）</string>\n    <string name=\"page_touch_slop_summary\">滑動多長距離才會觸發滑動翻頁（系統預設值 %s px）</string>\n    <string name=\"example\">範例</string>\n    <string name=\"check_selected_interval\">選中所選區間</string>\n    <string name=\"show_add_to_shelf_alert_title\">返回時提示放入書架</string>\n    <string name=\"show_add_to_shelf_alert_summary\">閱讀未放入書架的書籍在返回時提示放入書架</string>\n    <string name=\"review\">段評</string>\n    <string name=\"rule_review_url\">段評URL（reviewUrl）</string>\n    <string name=\"rule_avatar\">段評發布者大頭貼（avatarRule）</string>\n    <string name=\"rule_review_content\">段評內容（contentRule）</string>\n    <string name=\"rule_post_time\">段評發布時間（postTimeRule）</string>\n    <string name=\"rule_review_quote\">段評回復URL（reviewQuoteUrl）</string>\n    <string name=\"review_vote_down\">點踩URL（voteDownUrl）</string>\n    <string name=\"review_vote_up\">按讚URL（voteUpUrl）</string>\n    <string name=\"post_review_url\">發送回復URL（postReviewUrl）</string>\n    <string name=\"post_quote_url\">發送回復段評URL（postQuoteUrl）</string>\n    <string name=\"delete_review_url\">刪除段評URL（deleteUrl）</string>\n    <string name=\"tag_explore_enabled\">標誌：發現已啟用</string>\n    <string name=\"tag_explore_disabled\">標誌：發現已禁用</string>\n    <string name=\"show_read_title_addition\">展示頂部工具列附加區域</string>\n    <string name=\"read_bar_style_follow_page\">工具列樣式跟隨頁面</string>\n    <string name=\"rule_image_decode\">圖片解密（imageDecode）</string>\n    <string name=\"like_source\">讚</string>\n    <string name=\"not_like_source\">踩</string>\n    <string name=\"async_load_image\">非同步載入圖片</string>\n    <string name=\"ignore_audio_focus_title\">忽略音訊焦點</string>\n    <string name=\"ignore_audio_focus_summary\">允許與其他應用同時播放音訊</string>\n    <string name=\"refresh_sort\">重新整理分類</string>\n    <string name=\"cover_decode_js\">封面解密（coverDecodeJs）</string>\n    <string name=\"net_no_group\">網路未分組</string>\n    <string name=\"local_no_group\">本機未分組</string>\n    <string name=\"parallel_export_book\">多執行緒匯出</string>\n    <string name=\"progress_bar_behavior\">進度條行為</string>\n    <string name=\"source_edit_text_max_line\">源編輯框最大行數</string>\n    <string name=\"source_edit_max_line_summary\">%s，設定行數小於螢幕可顯示的最大行數可以更方便的滑動到其他的欄位進行編輯</string>\n    <string name=\"restore_last_book_process\">是否復原到跳轉前的閱讀進度？</string>\n    <string name=\"search_scope\">搜尋範圍</string>\n    <string name=\"toggle_search_scope\">切換</string>\n    <string name=\"sure_clear_search_history\">是否確認清除所有搜尋歷史記錄</string>\n    <string name=\"no_anim_scroll_page\">禁用滾動點擊動畫</string>\n    <string name=\"webdav_device_name\">裝置名稱</string>\n    <string name=\"web_service_wake_lock\">WebService喚醒鎖</string>\n    <string name=\"web_service_wake_lock_summary\">開啟web服務的時候啟用喚醒鎖，有些手機開啟喚醒鎖會被殺後台</string>\n    <string name=\"read_aloud_wake_lock\">朗讀服務喚醒鎖</string>\n    <string name=\"read_aloud_wake_lock_summary\">開啟朗讀的時候啟用喚醒鎖，有些手機開啟喚醒鎖會被殺後台</string>\n    <string name=\"audio_play_wake_lock\">音訊服務喚醒鎖</string>\n    <string name=\"audio_play_wake_lock_summary\">播放音訊的時候啟用喚醒鎖，有些手機開啟喚醒鎖會被殺後台</string>\n    <string name=\"change_search_scope\">切換搜尋範圍</string>\n    <string name=\"copy_rule\">複製規則</string>\n    <string name=\"paste_rule\">貼上規則</string>\n    <string name=\"groups_or_source\">多分組/書源</string>\n    <string name=\"replace_state_change\">取代(啟用/禁用)</string>\n    <string name=\"show_last_update_time\">顯示上次更新時間</string>\n    <string name=\"refresh_list\">刷新列表</string>\n    <string name=\"tip_divider_color\">分隔线颜色</string>\n    <string name=\"same_title_removed\">移除重复标题</string>\n    <string name=\"update_book_fail\">更新失败</string>\n    <string name=\"notification_permission_rationale\">阅读需要发送通知来显示朗读控制和下载进度</string>\n    <string name=\"webdav_after_local_restore_confirm\">webDav书源比本地新,是否恢复</string>\n    <string name=\"c_whitelist\">白名单(contentWhitelist)</string>\n    <string name=\"c_blacklist\">黑名单(contentBlacklist)</string>\n    <string name=\"confirm\">确认</string>\n    <string name=\"jump_to_another_app\">跳转其它应用</string>\n    <string name=\"clear_webview_data\">清除 WebView 数据</string>\n    <string name=\"clear_webview_data_summary\">清除内置浏览器所有数据</string>\n    <string name=\"source_tab_list\">列表</string>\n    <string name=\"dict_rule\">字典规则</string>\n    <string name=\"config_dict_rule\">配置字典规则</string>\n    <string name=\"create\">新建</string>\n    <string name=\"url_rule\">url规则(urlRule)</string>\n    <string name=\"show_rule\">显示规则(showRule)</string>\n    <string name=\"config_txt_toc_rule\">配置 TXT 目录规则</string>\n    <string name=\"import_dict_rule\">导入字典规则</string>\n    <string name=\"keep_group\">保留分组</string>\n    <string name=\"server_config\">服务器配置</string>\n    <string name=\"sure_upload\">远程webDav链接已存在，是否继续</string>\n    <string name=\"unsupport_archivefile_entry\">压缩文件内没有支持的文件</string>\n    <string name=\"no_books_dir\">没有设置书籍保存位置!</string>\n    <string name=\"delete_alert\">删除提醒</string>\n    <string name=\"no_book_found_bookshelf\">未在书架上找到所选书籍, 是否重新导入？</string>\n    <string name=\"archive_not_found\">未能在书籍保存目录找到压缩文件，是否重新导入？</string>\n    <string name=\"privacy_policy\">用户隐私与协议</string>\n    <string name=\"agree\">同意</string>\n    <string name=\"refuse\">拒绝</string>\n    <string name=\"file_manage\">文件管理</string>\n    <string name=\"file_manage_summary\">管理私有文件夹的文件</string>\n    <string name=\"create_folder\">创建文件夹</string>\n    <string name=\"allow_drop_down_refresh\">允许下拉刷新</string>\n    <string name=\"text_underline\">文字下划线</string>\n    <string name=\"select_new_source\">选中新增源</string>\n    <string name=\"select_update_source\">选中更新源</string>\n    <string name=\"set_local_password\">设置本地密码</string>\n    <string name=\"set_local_password_summary\">本地密码用来对备份的敏感信息加密和解密,如需在不同设备之间同步,本地密码需一致.</string>\n    <string name=\"only_latest_backup_t\">仅保留最新备份</string>\n    <string name=\"only_latest_backup_s\">本地备份仅保留最新备份文件</string>\n    <string name=\"webdav_application_authorization_error\">webDav应用验证失败</string>\n    <string name=\"load_word_count\">加載字數</string>\n    <string name=\"replace_exclude_scope\">排除范围，选填书名或者书源 URL</string>\n    <string name=\"format_js_rule\">格式化规则(formatJs)</string>\n    <string name=\"adjust_pos\">调整位置</string>\n    <string name=\"select_section_export\">選擇待導出章節</string>\n    <string name=\"error_scope_input\">請輸入正確的範圍</string>\n    <string name=\"custom_export\">自定義導出</string>\n    <string name=\"file_contains_number\">每個文件包含的章節數量</string>\n    <string name=\"export_chapter_index\">需要輸出的章節</string>\n    <string name=\"shrink_database\">压缩数据库</string>\n    <string name=\"shrink_database_summary\">减小数据库文件的大小</string>\n    <string name=\"is_compress\">是否压缩</string>\n    <string name=\"sort_desc\">反序</string>\n    <string name=\"test\">测试</string>\n    <string name=\"show_wait_up_count\">显示等待更新数量</string>\n    <string name=\"exit_app\">退出软件</string>\n    <string name=\"result_analyzed\">解析示例</string>\n    <string name=\"bookshelf_px_4\">綜合排序</string>\n    <string name=\"bookshelf_px_5\">按作者</string>\n    <string name=\"effective_replaces\">起效的替换</string>\n    <string name=\"export_book\">导出书籍</string>\n    <string name=\"export_book_notification_content\">正在导出(%1$s),还有%2$d本待导出</string>\n    <string name=\"export_md\">导出(MD)</string>\n    <string name=\"change_source_delay\">换源间隔</string>\n    <string name=\"change_source_progress\">\" 結果 %1$d, 當前進度 %2$d / %3$d: %4$s\"</string>\n    <string name=\"open_book_info_by_click_title\">点击书名打开详情</string>\n    <string name=\"export_wait\">等待导出</string>\n    <string name=\"sync_book_progress_success\">進度同步成功</string>\n    <string name=\"default_home_page\">默认主页</string>\n    <string name=\"show_bookshelf_fast_scroller\">显示快速滚动条</string>\n    <string name=\"export_all_use_book_source\">导出所有书的书源</string>\n    <string name=\"cloud_progress_exceeds_current\">雲端進度超過目前進度，是否同步？</string>\n    <string name=\"keep_enable\">保留启用状态</string>\n    <string name=\"preview_image_by_click\">点击预览图片</string>\n    <string name=\"screen_portrait_reversed\">反向竖屏</string>\n    <string name=\"del_ruby_tag\">删除ruby标签</string>\n    <string name=\"del_h_tag\">删除h标签</string>\n    <string name=\"adjust_chapter_page\">调整本章页数</string>\n    <string name=\"adjust_chapter_index\">调整章节位置</string>\n    <string name=\"clear_webview_data_success\">清除成功，3秒后自动重启应用</string>\n    <string name=\"key_page_on_long_press\">按键长按翻页</string>\n    <string name=\"save_log\">保存日志</string>\n    <string name=\"create_heap_dump\">创建堆转储</string>\n    <string name=\"record_heap_dump_s\">当应用发生OOM崩溃时保存堆转储</string>\n    <string name=\"record_heap_dump_t\">记录堆转储</string>\n    <string name=\"font_weight_text\">中/粗/细</string>\n    <string name=\"keep_swipe_tip\">继续滑动以加载下一章…</string>\n    <string name=\"enable_optimize_render\">启用绘制优化</string>\n    <string name=\"ignore_battery_permission_rationale\">阅读需要请求后台权限以保持服务正常运行</string>\n    <string name=\"simulated_reading\">模擬追讀</string>\n    <string name=\"switch_on\">開關</string>\n    <string name=\"start_from\">起始日期</string>\n    <string name=\"daily_chapters\">日更章數</string>\n    <string name=\"start_chapter\">開始篇章</string>\n    <string name=\"update_to_variant_title\">检查更新查找版本</string>\n    <string name=\"update_to_variant_summary\">检查更新时查找其他签名版本</string>\n    <string name=\"default_version\">当前</string>\n    <string name=\"official_version\">正式版</string>\n    <string name=\"beta_release_version\">测试版</string>\n    <string name=\"beta_releaseA_version\">共存版</string>\n    <string name=\"stream_read_aloud_audio\">流式播放音频</string>\n    <string name=\"stream_read_aloud_audio_summary\">即边下边播，网络不好时播放会断断续续，仅TTS源有效</string>\n    <string name=\"pause_read_aloud_while_phone_calls_title\">来电期间暂停朗读</string>\n    <string name=\"pause_read_aloud_while_phone_calls_summary\">在通话期间暂停朗读，需要读取手机状态权限</string>\n    <string name=\"read_aloud_read_phone_state_permission_rationale\">阅读需要读取手机状态实现来电期间暂停朗读功能</string>\n    <string name=\"read_aloud_by_media_button_title\">耳机按键启动朗读</string>\n    <string name=\"read_aloud_by_media_button_summary\">通过耳机按键来启动朗读</string>\n    <string name=\"group_sources_by_domain\">按域名分组显示</string>\n    <string name=\"theme_config\">主题配置</string>\n    <string name=\"show_manga_ui\">漫画浏览</string>\n    <string name=\"disable_manga_scale\">禁用漫画缩放</string>\n    <string name=\"disable_manga_click_scroll\">禁用点击翻页</string>\n    <string name=\"enable_auto_page_scroll\">开启自动翻页</string>\n    <string name=\"manga_auto_page_speed\">翻页速度 %s</string>\n    <string name=\"enable_auto_scroll\">开启滚动</string>\n    <string name=\"manga_footer_config\">页脚配置</string>\n    <string name=\"setting_manga_auto_page_speed\">设置自动翻页速度</string>\n    <string name=\"book_reader_info_bar\">页数. %1$d/%2$d  章节. %3$d/%4$d -> %5$s%%</string>\n    <string name=\"menu_download_after\">下載之後章節</string>\n    <string name=\"menu_download_all\">下載全部章節</string>\n    <string name=\"manga_header_chapter\">《章节和文案》隐藏</string>\n    <string name=\"manga_check_chapter_label\">《章节.》文案</string>\n    <string name=\"manga_check_chapter\">章节</string>\n    <string name=\"manga_check_chapter_name\">章节名称</string>\n    <string name=\"manga_header_page\">《页数和文案》隐藏</string>\n    <string name=\"manga_check_page_label\">《页数.》文案</string>\n    <string name=\"manga_check_page_number\">页数</string>\n    <string name=\"manga_header_progress\">《总进度和文案》隐藏</string>\n    <string name=\"manga_check_progress_label\">《总进度.》文案</string>\n    <string name=\"manga_check_progress\">总进度</string>\n    <string name=\"manga_header_footer\">页脚</string>\n    <string name=\"manga_radio_left\">靠左</string>\n    <string name=\"manga_radio_center\">居中</string>\n    <string name=\"enable_manga_horizontal_scroll\">水平滚动</string>\n    <string name=\"manga_color_filter\">滤镜</string>\n    <string name=\"hide_manga_title\">隐藏漫画列表标题</string>\n    <string name=\"refresh_explore\">刷新发现</string>\n    <string name=\"padding_display_cutouts\">填充刘海区域</string>\n    <string name=\"system_media_control_compatibility_change\">系統媒體控件相容性更改</string>\n    <string name=\"system_media_control_compatibility_change_summary\">當鎖屏不顯示系統媒體控件時可以嘗試開啟，比如 OneUI7.0 或 vivo 等</string>\n    <string name=\"read_aloud_pause_resume\">朗讀暫停/繼續</string>\n    <string name=\"manga_epaper\">墨水屏</string>\n    <string name=\"manga_epaper_value\">阈值</string>\n    <string name=\"disable_horizontal_page_snap\">禁用水平翻页效果</string>\n    <string name=\"enable_manga_gray\">开启图片灰色</string>\n    <string name=\"sure_cache_book\">是否确认开始缓存？</string>\n    <string name=\"auto_check_new_backup_t\">自动检查新备份</string>\n    <string name=\"auto_check_new_backup_s\">打开软件时检查是否有新备份，有新备份时提示是否更新</string>\n    <string name=\"sys_image_picker\">系统图片选择器</string>\n</resources>\n"
  },
  {
    "path": "app/src/main/res/xml/about.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.preference.PreferenceScreen xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\">\n\n    <io.legado.app.lib.prefs.Preference\n        android:key=\"contributors\"\n        android:summary=\"@string/contributors_summary\"\n        android:title=\"@string/contributors\"\n        app:iconSpaceReserved=\"false\" />\n\n    <io.legado.app.lib.prefs.Preference\n        android:key=\"update_log\"\n        android:title=\"@string/update_log\"\n        app:allowDividerAbove=\"false\"\n        app:allowDividerBelow=\"false\"\n        app:iconSpaceReserved=\"false\" />\n\n    <io.legado.app.lib.prefs.Preference\n        android:key=\"check_update\"\n        android:title=\"@string/check_update\"\n        app:allowDividerAbove=\"false\"\n        app:allowDividerBelow=\"false\"\n        app:iconSpaceReserved=\"false\" />\n\n    <io.legado.app.lib.prefs.PreferenceCategory\n        android:title=\"@string/other\"\n        app:allowDividerAbove=\"true\"\n        app:allowDividerBelow=\"false\"\n        app:iconSpaceReserved=\"false\"\n        app:layout=\"@layout/view_preference_category\">\n\n        <io.legado.app.lib.prefs.Preference\n            android:key=\"crashLog\"\n            android:title=\"@string/crash_log\"\n            app:iconSpaceReserved=\"false\" />\n\n        <io.legado.app.lib.prefs.Preference\n            android:key=\"saveLog\"\n            android:title=\"@string/save_log\"\n            app:iconSpaceReserved=\"false\" />\n\n        <io.legado.app.lib.prefs.Preference\n            android:key=\"createHeapDump\"\n            android:title=\"@string/create_heap_dump\"\n            app:iconSpaceReserved=\"false\" />\n\n        <io.legado.app.lib.prefs.Preference\n            android:key=\"privacyPolicy\"\n            android:title=\"@string/privacy_policy\"\n            app:iconSpaceReserved=\"false\" />\n\n        <io.legado.app.lib.prefs.Preference\n            android:key=\"license\"\n            android:title=\"@string/license\"\n            app:iconSpaceReserved=\"false\" />\n\n        <io.legado.app.lib.prefs.Preference\n            android:key=\"disclaimer\"\n            android:title=\"@string/disclaimer\"\n            app:iconSpaceReserved=\"false\" />\n\n    </io.legado.app.lib.prefs.PreferenceCategory>\n\n</androidx.preference.PreferenceScreen>"
  },
  {
    "path": "app/src/main/res/xml/file_paths.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<paths>\n    <external-path\n        name=\"files_cache\"\n        path=\"Android/data/com.kunfei.bookshelf/cache\" />\n    <external-path\n        name=\"external_storage_root\"\n        path=\".\" />\n    <files-path\n        name=\"share\"\n        path=\".\" />\n    <external-files-path\n        name=\"external_files\"\n        path=\".\" />\n    <cache-path\n        name=\"cache\"\n        path=\".\" />\n    <external-cache-path\n        name=\"external_cache\"\n        path=\".\" />\n    <files-path\n        name=\"files\"\n        path=\".\" />\n</paths>"
  },
  {
    "path": "app/src/main/res/xml/network_security_config.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<network-security-config xmlns:tools=\"http://schemas.android.com/tools\">\n    <base-config\n        cleartextTrafficPermitted=\"true\"\n        tools:ignore=\"InsecureBaseConfiguration\" />\n\n    <debug-overrides cleartextTrafficPermitted=\"true\">\n        <trust-anchors>\n            <certificates src=\"user\" />\n            <certificates src=\"system\"/>\n        </trust-anchors>\n    </debug-overrides>\n</network-security-config>"
  },
  {
    "path": "app/src/main/res/xml/pref_config_aloud.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.preference.PreferenceScreen xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\">\n\n    <io.legado.app.lib.prefs.PreferenceCategory\n        android:title=\"@string/aloud_config\"\n        app:allowDividerAbove=\"false\"\n        app:allowDividerBelow=\"false\"\n        app:iconSpaceReserved=\"false\">\n\n        <io.legado.app.lib.prefs.SwitchPreference\n            android:defaultValue=\"false\"\n            android:key=\"ignoreAudioFocus\"\n            android:summary=\"@string/ignore_audio_focus_summary\"\n            android:title=\"@string/ignore_audio_focus_title\" />\n\n        <io.legado.app.lib.prefs.SwitchPreference\n            android:defaultValue=\"false\"\n            android:key=\"pauseReadAloudWhilePhoneCalls\"\n            android:summary=\"@string/pause_read_aloud_while_phone_calls_summary\"\n            android:title=\"@string/pause_read_aloud_while_phone_calls_title\" />\n\n        <io.legado.app.lib.prefs.SwitchPreference\n            android:defaultValue=\"false\"\n            android:title=\"@string/read_aloud_wake_lock\"\n            android:summary=\"@string/read_aloud_wake_lock_summary\"\n            android:key=\"readAloudWakeLock\"\n            app:iconSpaceReserved=\"false\" />\n\n        <io.legado.app.lib.prefs.SwitchPreference\n            android:defaultValue=\"false\"\n            android:title=\"@string/system_media_control_compatibility_change\"\n            android:summary=\"@string/system_media_control_compatibility_change_summary\"\n            android:key=\"systemMediaControlCompatibilityChange\"\n            app:iconSpaceReserved=\"false\" />\n\n        <io.legado.app.lib.prefs.SwitchPreference\n            android:defaultValue=\"false\"\n            android:title=\"@string/pref_media_button_per_next\"\n            android:summary=\"@string/pref_media_button_per_next_summary\"\n            android:key=\"mediaButtonPerNext\"\n            app:iconSpaceReserved=\"false\" />\n\n        <io.legado.app.lib.prefs.SwitchPreference\n            android:defaultValue=\"false\"\n            android:title=\"@string/read_aloud_by_page\"\n            android:summary=\"@string/read_aloud_by_page_summary\"\n            android:key=\"readAloudByPage\"\n            app:iconSpaceReserved=\"false\" />\n\n        <io.legado.app.lib.prefs.SwitchPreference\n            android:defaultValue=\"false\"\n            android:title=\"@string/stream_read_aloud_audio\"\n            android:summary=\"@string/stream_read_aloud_audio_summary\"\n            android:key=\"streamReadAloudAudio\"\n            app:iconSpaceReserved=\"false\" />\n\n        <io.legado.app.lib.prefs.Preference\n            android:title=\"@string/speak_engine\"\n            android:summary=\"TTS\"\n            android:key=\"appTtsEngine\"\n            app:iconSpaceReserved=\"false\" />\n\n        <io.legado.app.lib.prefs.Preference\n            android:title=\"@string/sys_tts_config\"\n            android:summary=\"@string/sys_tts_config_summary\"\n            android:key=\"sysTtsConfig\"\n            app:iconSpaceReserved=\"false\" />\n\n    </io.legado.app.lib.prefs.PreferenceCategory>\n\n</androidx.preference.PreferenceScreen>"
  },
  {
    "path": "app/src/main/res/xml/pref_config_backup.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.preference.PreferenceScreen xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\">\n\n    <io.legado.app.lib.prefs.PreferenceCategory\n        android:title=\"@string/web_dav_set\"\n        app:allowDividerAbove=\"false\"\n        app:allowDividerBelow=\"false\"\n        app:iconSpaceReserved=\"false\"\n        app:layout=\"@layout/view_preference_category\">\n\n        <io.legado.app.lib.prefs.EditTextPreference\n            android:key=\"web_dav_url\"\n            android:summary=\"@string/web_dav_url_s\"\n            android:title=\"@string/web_dav_url\"\n            app:iconSpaceReserved=\"false\" />\n\n        <io.legado.app.lib.prefs.EditTextPreference\n            android:key=\"web_dav_account\"\n            android:summary=\"@string/web_dav_account_s\"\n            android:title=\"@string/web_dav_account\"\n            app:iconSpaceReserved=\"false\" />\n\n        <io.legado.app.lib.prefs.EditTextPreference\n            android:key=\"web_dav_password\"\n            android:summary=\"@string/web_dav_pw_s\"\n            android:title=\"@string/web_dav_pw\"\n            app:iconSpaceReserved=\"false\" />\n\n        <io.legado.app.lib.prefs.EditTextPreference\n            android:key=\"webDavDir\"\n            android:title=\"@string/sub_dir\"\n            app:allowDividerAbove=\"false\"\n            app:allowDividerBelow=\"false\"\n            app:iconSpaceReserved=\"false\" />\n\n        <io.legado.app.lib.prefs.EditTextPreference\n            android:key=\"webDavDeviceName\"\n            android:title=\"@string/webdav_device_name\"\n            app:allowDividerAbove=\"false\"\n            app:allowDividerBelow=\"false\"\n            app:iconSpaceReserved=\"false\" />\n\n        <io.legado.app.lib.prefs.SwitchPreference\n            android:defaultValue=\"true\"\n            android:key=\"syncBookProgress\"\n            android:summary=\"@string/sync_book_progress_s\"\n            android:title=\"@string/sync_book_progress_t\"\n            app:allowDividerAbove=\"false\"\n            app:allowDividerBelow=\"false\"\n            app:iconSpaceReserved=\"false\" />\n\n        <io.legado.app.lib.prefs.SwitchPreference\n            android:defaultValue=\"false\"\n            android:dependency=\"syncBookProgress\"\n            android:key=\"syncBookProgressPlus\"\n            android:summary=\"@string/sync_book_progress_plus_s\"\n            android:title=\"@string/sync_book_progress_plus_t\"\n            app:allowDividerAbove=\"false\"\n            app:allowDividerBelow=\"false\"\n            app:iconSpaceReserved=\"false\" />\n\n    </io.legado.app.lib.prefs.PreferenceCategory>\n\n    <io.legado.app.lib.prefs.PreferenceCategory\n        android:title=\"@string/backup_restore\"\n        app:allowDividerBelow=\"false\"\n        app:iconSpaceReserved=\"false\"\n        app:layout=\"@layout/view_preference_category\">\n\n        <io.legado.app.lib.prefs.Preference\n            android:key=\"backupUri\"\n            android:summary=\"@string/select_backup_path\"\n            android:title=\"@string/backup_path\"\n            app:iconSpaceReserved=\"false\" />\n\n        <io.legado.app.lib.prefs.Preference\n            android:key=\"web_dav_backup\"\n            android:summary=\"@string/backup_summary\"\n            android:title=\"@string/backup\"\n            app:iconSpaceReserved=\"false\" />\n\n        <io.legado.app.lib.prefs.Preference\n            android:key=\"web_dav_restore\"\n            android:summary=\"@string/restore_summary\"\n            android:title=\"@string/restore\"\n            app:iconSpaceReserved=\"false\" />\n\n        <io.legado.app.lib.prefs.Preference\n            android:key=\"restoreIgnore\"\n            android:summary=\"@string/restore_ignore_summary\"\n            android:title=\"@string/restore_ignore\"\n            app:iconSpaceReserved=\"false\" />\n\n        <io.legado.app.lib.prefs.Preference\n            android:key=\"import_old\"\n            android:summary=\"@string/import_old_summary\"\n            android:title=\"@string/menu_import_old_version\"\n            app:iconSpaceReserved=\"false\" />\n\n        <io.legado.app.lib.prefs.SwitchPreference\n            android:defaultValue=\"true\"\n            android:key=\"onlyLatestBackup\"\n            android:summary=\"@string/only_latest_backup_s\"\n            android:title=\"@string/only_latest_backup_t\"\n            app:iconSpaceReserved=\"false\" />\n\n        <io.legado.app.lib.prefs.SwitchPreference\n            android:defaultValue=\"true\"\n            android:key=\"autoCheckNewBackup\"\n            android:summary=\"@string/auto_check_new_backup_s\"\n            android:title=\"@string/auto_check_new_backup_t\"\n            app:allowDividerAbove=\"false\"\n            app:allowDividerBelow=\"false\"\n            app:iconSpaceReserved=\"false\" />\n\n    </io.legado.app.lib.prefs.PreferenceCategory>\n\n</androidx.preference.PreferenceScreen>\n"
  },
  {
    "path": "app/src/main/res/xml/pref_config_cover.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.preference.PreferenceScreen xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\">\n\n    <io.legado.app.lib.prefs.SwitchPreference\n        android:key=\"loadCoverOnlyWifi\"\n        android:defaultValue=\"false\"\n        android:title=\"@string/only_wifi\"\n        android:summary=\"@string/only_wifi_summary\" />\n\n    <io.legado.app.lib.prefs.Preference\n        android:key=\"coverRule\"\n        android:title=\"@string/cover_rule\"\n        android:summary=\"@string/cover_rule_summary\"\n        app:allowDividerAbove=\"false\"\n        app:allowDividerBelow=\"false\"\n        app:iconSpaceReserved=\"false\" />\n\n    <io.legado.app.lib.prefs.SwitchPreference\n        android:defaultValue=\"false\"\n        android:key=\"useDefaultCover\"\n        android:summary=\"@string/use_default_cover_s\"\n        android:title=\"@string/use_default_cover\"\n        app:allowDividerAbove=\"false\"\n        app:allowDividerBelow=\"false\"\n        app:iconSpaceReserved=\"false\" />\n\n    <io.legado.app.lib.prefs.PreferenceCategory\n        android:key=\"dayThemeCategory\"\n        android:title=\"@string/day\"\n        app:allowDividerAbove=\"true\"\n        app:allowDividerBelow=\"false\"\n        app:iconSpaceReserved=\"false\"\n        app:layout=\"@layout/view_preference_category\">\n\n        <io.legado.app.lib.prefs.Preference\n            android:key=\"defaultCover\"\n            android:title=\"@string/default_cover\"\n            app:allowDividerAbove=\"false\"\n            app:allowDividerBelow=\"false\"\n            app:iconSpaceReserved=\"false\" />\n\n        <io.legado.app.lib.prefs.SwitchPreference\n            android:defaultValue=\"true\"\n            android:key=\"coverShowName\"\n            android:title=\"@string/cover_show_name\"\n            android:summary=\"@string/cover_show_name_summary\"\n            app:allowDividerAbove=\"false\"\n            app:allowDividerBelow=\"false\"\n            app:iconSpaceReserved=\"false\" />\n\n        <io.legado.app.lib.prefs.SwitchPreference\n            android:defaultValue=\"true\"\n            android:key=\"coverShowAuthor\"\n            android:title=\"@string/cover_show_author\"\n            android:summary=\"@string/cover_show_author_summary\"\n            app:allowDividerAbove=\"false\"\n            app:allowDividerBelow=\"false\"\n            app:iconSpaceReserved=\"false\" />\n\n    </io.legado.app.lib.prefs.PreferenceCategory>\n\n    <io.legado.app.lib.prefs.PreferenceCategory\n        android:key=\"nightThemeCategory\"\n        android:title=\"@string/night\"\n        app:allowDividerAbove=\"true\"\n        app:allowDividerBelow=\"false\"\n        app:iconSpaceReserved=\"false\"\n        app:layout=\"@layout/view_preference_category\">\n\n        <io.legado.app.lib.prefs.Preference\n            android:key=\"defaultCoverDark\"\n            android:title=\"@string/default_cover\"\n            app:allowDividerAbove=\"false\"\n            app:allowDividerBelow=\"false\"\n            app:iconSpaceReserved=\"false\" />\n\n        <io.legado.app.lib.prefs.SwitchPreference\n            android:defaultValue=\"true\"\n            android:key=\"coverShowNameN\"\n            android:title=\"@string/cover_show_name\"\n            android:summary=\"@string/cover_show_name_summary\"\n            app:allowDividerAbove=\"false\"\n            app:allowDividerBelow=\"false\"\n            app:iconSpaceReserved=\"false\" />\n\n        <io.legado.app.lib.prefs.SwitchPreference\n            android:defaultValue=\"true\"\n            android:key=\"coverShowAuthorN\"\n            android:title=\"@string/cover_show_author\"\n            android:summary=\"@string/cover_show_author_summary\"\n            app:allowDividerAbove=\"false\"\n            app:allowDividerBelow=\"false\"\n            app:iconSpaceReserved=\"false\" />\n\n    </io.legado.app.lib.prefs.PreferenceCategory>\n\n</androidx.preference.PreferenceScreen>"
  },
  {
    "path": "app/src/main/res/xml/pref_config_other.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.preference.PreferenceScreen xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\">\n\n    <io.legado.app.lib.prefs.NameListPreference\n        android:defaultValue=\"auto\"\n        android:key=\"language\"\n        android:title=\"@string/language\"\n        app:entries=\"@array/language\"\n        app:entryValues=\"@array/language_value\" />\n\n    <io.legado.app.lib.prefs.PreferenceCategory\n        android:title=\"@string/main_activity\"\n        app:allowDividerAbove=\"false\"\n        app:allowDividerBelow=\"false\"\n        app:iconSpaceReserved=\"false\"\n        app:layout=\"@layout/view_preference_category\">\n\n        <io.legado.app.lib.prefs.SwitchPreference\n            android:defaultValue=\"false\"\n            android:key=\"auto_refresh\"\n            android:summary=\"@string/ps_auto_refresh\"\n            android:title=\"@string/pt_auto_refresh\"\n            app:iconSpaceReserved=\"false\" />\n\n        <io.legado.app.lib.prefs.SwitchPreference\n            android:defaultValue=\"false\"\n            android:key=\"defaultToRead\"\n            android:summary=\"@string/ps_default_read\"\n            android:title=\"@string/pt_default_read\"\n            app:iconSpaceReserved=\"false\" />\n\n        <io.legado.app.lib.prefs.SwitchPreference\n            android:defaultValue=\"true\"\n            android:key=\"showDiscovery\"\n            android:title=\"@string/show_discovery\"\n            app:iconSpaceReserved=\"false\" />\n\n        <io.legado.app.lib.prefs.SwitchPreference\n            android:defaultValue=\"true\"\n            android:key=\"showRss\"\n            android:title=\"@string/show_rss\"\n            app:iconSpaceReserved=\"false\" />\n\n        <io.legado.app.lib.prefs.NameListPreference\n            android:defaultValue=\"bookshelf\"\n            android:key=\"defaultHomePage\"\n            android:title=\"@string/default_home_page\"\n            app:allowDividerAbove=\"false\"\n            app:allowDividerBelow=\"false\"\n            app:entries=\"@array/default_home_page\"\n            app:entryValues=\"@array/default_home_page_value\" />\n\n    </io.legado.app.lib.prefs.PreferenceCategory>\n\n    <io.legado.app.lib.prefs.PreferenceCategory\n        android:title=\"@string/other_setting\"\n        app:allowDividerBelow=\"false\"\n        app:iconSpaceReserved=\"false\"\n        app:layout=\"@layout/view_preference_category\">\n\n        <io.legado.app.lib.prefs.Preference\n            android:key=\"localPassword\"\n            android:summary=\"@string/set_local_password_summary\"\n            android:title=\"@string/set_local_password\" />\n\n        <io.legado.app.lib.prefs.Preference\n            android:key=\"userAgent\"\n            android:title=\"@string/user_agent\" />\n\n        <io.legado.app.lib.prefs.SwitchPreference\n            android:defaultValue=\"false\"\n            android:key=\"webServiceWakeLock\"\n            android:summary=\"@string/web_service_wake_lock_summary\"\n            android:title=\"@string/web_service_wake_lock\" />\n\n        <io.legado.app.lib.prefs.Preference\n            android:key=\"defaultBookTreeUri\"\n            android:summary=\"@string/book_tree_uri_s\"\n            android:title=\"@string/book_tree_uri_t\" />\n\n        <io.legado.app.lib.prefs.Preference\n            android:key=\"sourceEditMaxLine\"\n            android:title=\"@string/source_edit_text_max_line\" />\n\n        <io.legado.app.lib.prefs.Preference\n            android:key=\"checkSource\"\n            android:title=\"@string/check_source_config\" />\n\n        <io.legado.app.lib.prefs.Preference\n            android:key=\"uploadRule\"\n            android:summary=\"@string/direct_link_upload_rule_summary\"\n            android:title=\"@string/direct_link_upload_rule\" />\n\n        <io.legado.app.lib.prefs.SwitchPreference\n            android:defaultValue=\"false\"\n            android:key=\"Cronet\"\n            android:summary=\"@string/pref_cronet_summary\"\n            android:title=\"Cronet\" />\n\n        <io.legado.app.lib.prefs.SwitchPreference\n            android:defaultValue=\"false\"\n            android:key=\"antiAlias\"\n            android:summary=\"@string/pref_anti_alias_summary\"\n            android:title=\"@string/anti_alias\" />\n\n        <io.legado.app.lib.prefs.Preference\n            android:key=\"bitmapCacheSize\"\n            android:summary=\"@string/bitmap_cache_size_summary\"\n            android:title=\"@string/bitmap_cache_size\" />\n\n        <io.legado.app.lib.prefs.Preference\n            android:key=\"imageRetainNum\"\n            android:summary=\"@string/image_retain_number_summary\"\n            android:title=\"@string/image_retain_number\" />\n\n        <io.legado.app.lib.prefs.Preference\n            android:key=\"preDownloadNum\"\n            android:summary=\"@string/pre_download_s\"\n            android:title=\"@string/pre_download\" />\n\n        <io.legado.app.lib.prefs.SwitchPreference\n            android:defaultValue=\"true\"\n            android:key=\"replaceEnableDefault\"\n            android:summary=\"@string/replace_enable_default_s\"\n            android:title=\"@string/replace_enable_default_t\"\n            app:iconSpaceReserved=\"false\" />\n\n        <io.legado.app.lib.prefs.SwitchPreference\n            android:defaultValue=\"true\"\n            android:key=\"mediaButtonOnExit\"\n            android:summary=\"@string/media_button_on_exit_summary\"\n            android:title=\"@string/media_button_on_exit_title\"\n            app:iconSpaceReserved=\"false\" />\n\n        <io.legado.app.lib.prefs.SwitchPreference\n            android:defaultValue=\"false\"\n            android:key=\"readAloudByMediaButton\"\n            android:summary=\"@string/read_aloud_by_media_button_summary\"\n            android:title=\"@string/read_aloud_by_media_button_title\"\n            app:iconSpaceReserved=\"false\" />\n\n        <io.legado.app.lib.prefs.SwitchPreference\n            android:defaultValue=\"false\"\n            android:key=\"ignoreAudioFocus\"\n            android:summary=\"@string/ignore_audio_focus_summary\"\n            android:title=\"@string/ignore_audio_focus_title\" />\n\n        <io.legado.app.lib.prefs.SwitchPreference\n            android:defaultValue=\"true\"\n            android:key=\"autoClearExpired\"\n            android:summary=\"@string/auto_clear_expired_summary\"\n            android:title=\"@string/auto_clear_expired\" />\n\n        <io.legado.app.lib.prefs.SwitchPreference\n            android:defaultValue=\"true\"\n            android:key=\"showAddToShelfAlert\"\n            android:summary=\"@string/show_add_to_shelf_alert_summary\"\n            android:title=\"@string/show_add_to_shelf_alert_title\" />\n\n        <io.legado.app.lib.prefs.NameListPreference\n            android:defaultValue=\"default_version\"\n            android:key=\"updateToVariant\"\n            android:summary=\"@string/update_to_variant_summary\"\n            android:title=\"@string/update_to_variant_title\"\n            app:allowDividerAbove=\"false\"\n            app:allowDividerBelow=\"false\"\n            app:entries=\"@array/default_app_variant\"\n            app:entryValues=\"@array/default_app_variant_value\" />\n\n        <io.legado.app.lib.prefs.SwitchPreference\n            android:defaultValue=\"true\"\n            android:key=\"showMangaUi\"\n            android:title=\"@string/show_manga_ui\"\n            app:iconSpaceReserved=\"false\" />\n\n        <io.legado.app.lib.prefs.Preference\n            android:key=\"webPort\"\n            android:title=\"@string/web_port_title\"\n            app:iconSpaceReserved=\"false\" />\n\n        <io.legado.app.lib.prefs.Preference\n            android:key=\"cleanCache\"\n            android:summary=\"@string/clear_cache_summary\"\n            android:title=\"@string/clear_cache\"\n            app:iconSpaceReserved=\"false\" />\n\n        <io.legado.app.lib.prefs.Preference\n            android:key=\"clearWebViewData\"\n            android:summary=\"@string/clear_webview_data_summary\"\n            android:title=\"@string/clear_webview_data\"\n            app:iconSpaceReserved=\"false\" />\n\n        <io.legado.app.lib.prefs.Preference\n            android:key=\"shrinkDatabase\"\n            android:summary=\"@string/shrink_database_summary\"\n            android:title=\"@string/shrink_database\"\n            app:iconSpaceReserved=\"false\" />\n\n        <io.legado.app.lib.prefs.Preference\n            android:key=\"threadCount\"\n            android:title=\"@string/threads_num_title\"\n            app:iconSpaceReserved=\"false\" />\n\n        <io.legado.app.lib.prefs.SwitchPreference\n            android:defaultValue=\"true\"\n            android:key=\"process_text\"\n            android:summary=\"@string/add_to_text_context_menu_s\"\n            android:title=\"@string/add_to_text_context_menu_t\"\n            app:iconSpaceReserved=\"false\" />\n\n        <io.legado.app.lib.prefs.SwitchPreference\n            android:defaultValue=\"false\"\n            android:key=\"recordLog\"\n            android:summary=\"@string/record_debug_log\"\n            android:title=\"@string/record_log\"\n            app:iconSpaceReserved=\"false\" />\n\n        <io.legado.app.lib.prefs.SwitchPreference\n            android:defaultValue=\"false\"\n            android:key=\"recordHeapDump\"\n            android:summary=\"@string/record_heap_dump_s\"\n            android:title=\"@string/record_heap_dump_t\"\n            app:iconSpaceReserved=\"false\" />\n\n    </io.legado.app.lib.prefs.PreferenceCategory>\n\n\n</androidx.preference.PreferenceScreen>"
  },
  {
    "path": "app/src/main/res/xml/pref_config_read.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.preference.PreferenceScreen xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\">\n\n    <io.legado.app.lib.prefs.NameListPreference\n        android:defaultValue=\"0\"\n        android:entries=\"@array/screen_direction_title\"\n        android:entryValues=\"@array/screen_direction_value\"\n        android:key=\"screenOrientation\"\n        android:title=\"@string/screen_direction\"\n        app:iconSpaceReserved=\"false\"\n        app:isBottomBackground=\"true\" />\n\n    <io.legado.app.lib.prefs.NameListPreference\n        android:defaultValue=\"0\"\n        android:entries=\"@array/screen_time_out\"\n        android:entryValues=\"@array/screen_time_out_value\"\n        android:key=\"keep_light\"\n        android:title=\"@string/keep_light\"\n        app:iconSpaceReserved=\"false\"\n        app:isBottomBackground=\"true\" />\n\n    <io.legado.app.lib.prefs.SwitchPreference\n        android:defaultValue=\"false\"\n        android:key=\"hideStatusBar\"\n        android:title=\"@string/pt_hide_status_bar\"\n        app:iconSpaceReserved=\"false\"\n        app:isBottomBackground=\"true\" />\n\n    <io.legado.app.lib.prefs.SwitchPreference\n        android:defaultValue=\"false\"\n        android:key=\"hideNavigationBar\"\n        android:title=\"@string/pt_hide_navigation_bar\"\n        app:iconSpaceReserved=\"false\"\n        app:isBottomBackground=\"true\" />\n\n    <io.legado.app.lib.prefs.SwitchPreference\n        android:defaultValue=\"true\"\n        android:key=\"readBodyToLh\"\n        android:title=\"@string/read_body_to_lh\"\n        app:iconSpaceReserved=\"false\"\n        app:isBottomBackground=\"true\" />\n\n    <io.legado.app.lib.prefs.SwitchPreference\n        android:defaultValue=\"false\"\n        android:key=\"paddingDisplayCutouts\"\n        android:title=\"@string/padding_display_cutouts\"\n        app:iconSpaceReserved=\"false\"\n        app:isBottomBackground=\"true\" />\n\n    <io.legado.app.lib.prefs.NameListPreference\n        android:defaultValue=\"0\"\n        android:entries=\"@array/double_page_title\"\n        android:entryValues=\"@array/double_page_value\"\n        android:key=\"doubleHorizontalPage\"\n        android:title=\"@string/double_page_horizontal\"\n        app:iconSpaceReserved=\"false\"\n        app:isBottomBackground=\"true\" />\n\n    <io.legado.app.lib.prefs.NameListPreference\n        android:defaultValue=\"page\"\n        android:entries=\"@array/progress_bar_behavior_title\"\n        android:entryValues=\"@array/progress_bar_behavior_value\"\n        android:key=\"progressBarBehavior\"\n        android:title=\"@string/progress_bar_behavior\"\n        app:iconSpaceReserved=\"false\"\n        app:isBottomBackground=\"true\" />\n\n    <io.legado.app.lib.prefs.SwitchPreference\n        android:defaultValue=\"false\"\n        android:key=\"useZhLayout\"\n        android:title=\"@string/use_zh_layout\"\n        app:iconSpaceReserved=\"false\"\n        app:isBottomBackground=\"true\" />\n\n    <io.legado.app.lib.prefs.SwitchPreference\n        android:defaultValue=\"true\"\n        android:key=\"textFullJustify\"\n        android:title=\"@string/text_full_justify\"\n        app:iconSpaceReserved=\"false\"\n        app:isBottomBackground=\"true\" />\n\n    <io.legado.app.lib.prefs.SwitchPreference\n        android:defaultValue=\"true\"\n        android:key=\"textBottomJustify\"\n        android:title=\"@string/text_bottom_justify\"\n        app:iconSpaceReserved=\"false\"\n        app:isBottomBackground=\"true\" />\n\n    <io.legado.app.lib.prefs.SwitchPreference\n        android:defaultValue=\"true\"\n        android:key=\"mouseWheelPage\"\n        android:title=\"@string/mouse_wheel_page\"\n        app:iconSpaceReserved=\"false\"\n        app:isBottomBackground=\"true\" />\n\n    <io.legado.app.lib.prefs.SwitchPreference\n        android:defaultValue=\"true\"\n        android:key=\"volumeKeyPage\"\n        android:title=\"@string/volume_key_page\"\n        app:iconSpaceReserved=\"false\"\n        app:isBottomBackground=\"true\" />\n\n    <io.legado.app.lib.prefs.SwitchPreference\n        android:defaultValue=\"false\"\n        android:key=\"volumeKeyPageOnPlay\"\n        android:title=\"@string/volume_key_page_on_play\"\n        app:iconSpaceReserved=\"false\"\n        app:isBottomBackground=\"true\" />\n\n    <io.legado.app.lib.prefs.SwitchPreference\n        android:defaultValue=\"false\"\n        android:key=\"keyPageOnLongPress\"\n        android:title=\"@string/key_page_on_long_press\"\n        app:iconSpaceReserved=\"false\"\n        app:isBottomBackground=\"true\" />\n\n    <io.legado.app.lib.prefs.Preference\n        android:key=\"pageTouchSlop\"\n        android:summary=\"@string/page_touch_slop_summary\"\n        android:title=\"@string/page_touch_slop_title\"\n        app:iconSpaceReserved=\"false\"\n        app:isBottomBackground=\"true\" />\n\n    <io.legado.app.lib.prefs.SwitchPreference\n        android:defaultValue=\"true\"\n        android:key=\"autoChangeSource\"\n        android:title=\"@string/auto_change_source\"\n        app:iconSpaceReserved=\"false\"\n        app:isBottomBackground=\"true\" />\n\n    <io.legado.app.lib.prefs.SwitchPreference\n        android:defaultValue=\"true\"\n        android:key=\"selectText\"\n        android:title=\"@string/selectText\"\n        app:iconSpaceReserved=\"false\"\n        app:isBottomBackground=\"true\" />\n\n    <io.legado.app.lib.prefs.SwitchPreference\n        android:defaultValue=\"true\"\n        android:key=\"showBrightnessView\"\n        android:title=\"@string/show_brightness_view\"\n        app:iconSpaceReserved=\"false\"\n        app:isBottomBackground=\"true\" />\n\n    <io.legado.app.lib.prefs.SwitchPreference\n        android:defaultValue=\"false\"\n        android:key=\"noAnimScrollPage\"\n        android:title=\"@string/no_anim_scroll_page\"\n        app:iconSpaceReserved=\"false\"\n        app:isBottomBackground=\"true\" />\n\n    <io.legado.app.lib.prefs.SwitchPreference\n        android:defaultValue=\"false\"\n        android:key=\"previewImageByClick\"\n        android:title=\"@string/preview_image_by_click\"\n        app:iconSpaceReserved=\"false\"\n        app:isBottomBackground=\"true\" />\n\n    <io.legado.app.lib.prefs.SwitchPreference\n        android:defaultValue=\"false\"\n        android:key=\"optimizeRender\"\n        android:title=\"@string/enable_optimize_render\"\n        app:iconSpaceReserved=\"false\"\n        app:isBottomBackground=\"true\" />\n\n    <io.legado.app.lib.prefs.Preference\n        android:key=\"clickRegionalConfig\"\n        android:title=\"@string/click_regional_config\"\n        app:iconSpaceReserved=\"false\"\n        app:isBottomBackground=\"true\" />\n\n    <io.legado.app.lib.prefs.SwitchPreference\n        android:defaultValue=\"false\"\n        android:key=\"disableReturnKey\"\n        android:title=\"@string/disable_return_key\"\n        app:iconSpaceReserved=\"false\"\n        app:isBottomBackground=\"true\" />\n\n    <io.legado.app.lib.prefs.Preference\n        android:key=\"customPageKey\"\n        android:title=\"@string/custom_page_key\"\n        app:iconSpaceReserved=\"false\"\n        app:isBottomBackground=\"true\" />\n\n    <io.legado.app.lib.prefs.SwitchPreference\n        android:defaultValue=\"false\"\n        android:key=\"expandTextMenu\"\n        android:title=\"@string/expand_text_menu\"\n        app:iconSpaceReserved=\"false\"\n        app:isBottomBackground=\"true\" />\n\n    <io.legado.app.lib.prefs.SwitchPreference\n        android:defaultValue=\"true\"\n        android:key=\"showReadTitleAddition\"\n        android:title=\"@string/show_read_title_addition\" />\n\n    <io.legado.app.lib.prefs.SwitchPreference\n        android:defaultValue=\"false\"\n        android:key=\"readBarStyleFollowPage\"\n        android:title=\"@string/read_bar_style_follow_page\" />\n</androidx.preference.PreferenceScreen>"
  },
  {
    "path": "app/src/main/res/xml/pref_config_theme.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.preference.PreferenceScreen xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\">\n\n    <io.legado.app.lib.prefs.IconListPreference\n        android:defaultValue=\"ic_launcher\"\n        android:entries=\"@array/icon_names\"\n        android:entryValues=\"@array/icons\"\n        android:key=\"launcherIcon\"\n        android:summary=\"@string/change_icon_summary\"\n        android:title=\"@string/change_icon\"\n        app:iconSpaceReserved=\"false\"\n        app:icons=\"@array/icons\" />\n\n    <io.legado.app.lib.prefs.Preference\n        android:key=\"welcomeStyle\"\n        android:title=\"@string/welcome_style\"\n        android:summary=\"@string/welcome_style_summary\" />\n\n    <io.legado.app.lib.prefs.SwitchPreference\n        android:defaultValue=\"true\"\n        android:key=\"transparentStatusBar\"\n        android:summary=\"@string/status_bar_immersion\"\n        android:title=\"@string/immersion_status_bar\"\n        app:iconSpaceReserved=\"false\" />\n\n    <io.legado.app.lib.prefs.SwitchPreference\n        android:defaultValue=\"true\"\n        android:key=\"immNavigationBar\"\n        android:summary=\"@string/imm_navigation_bar_s\"\n        android:title=\"@string/imm_navigation_bar\"\n        app:iconSpaceReserved=\"false\" />\n\n    <io.legado.app.lib.prefs.Preference\n        android:key=\"barElevation\"\n        android:summary=\"@string/bar_elevation_s\"\n        android:title=\"@string/bar_elevation\"\n        app:iconSpaceReserved=\"false\" />\n\n    <io.legado.app.lib.prefs.Preference\n        android:key=\"fontScale\"\n        android:summary=\"@string/font_scale_summary\"\n        android:title=\"@string/font_scale\"\n        app:iconSpaceReserved=\"false\" />\n\n    <io.legado.app.lib.prefs.Preference\n        android:key=\"coverConfig\"\n        android:title=\"@string/cover_config\"\n        android:summary=\"@string/cover_config_summary\"\n        app:allowDividerAbove=\"false\"\n        app:allowDividerBelow=\"false\"\n        app:iconSpaceReserved=\"false\" />\n\n    <io.legado.app.lib.prefs.Preference\n        android:key=\"themeList\"\n        android:summary=\"@string/theme_list_summary\"\n        android:title=\"@string/theme_list\"\n        app:allowDividerAbove=\"false\"\n        app:allowDividerBelow=\"false\"\n        app:iconSpaceReserved=\"false\" />\n\n    <io.legado.app.lib.prefs.PreferenceCategory\n        android:key=\"dayThemeCategory\"\n        android:title=\"@string/day\"\n        app:allowDividerAbove=\"true\"\n        app:allowDividerBelow=\"false\"\n        app:iconSpaceReserved=\"false\"\n        app:layout=\"@layout/view_preference_category\">\n\n        <io.legado.app.lib.prefs.ColorPreference\n            android:defaultValue=\"@color/md_brown_500\"\n            android:key=\"colorPrimary\"\n            android:summary=\"@string/day_color_primary\"\n            android:title=\"@string/primary\"\n            app:cpv_dialogType=\"preset\"\n            app:iconSpaceReserved=\"false\" />\n\n        <io.legado.app.lib.prefs.ColorPreference\n            android:defaultValue=\"@color/md_red_600\"\n            android:key=\"colorAccent\"\n            android:summary=\"@string/day_color_accent\"\n            android:title=\"@string/accent\"\n            app:cpv_dialogType=\"preset\"\n            app:iconSpaceReserved=\"false\" />\n\n        <io.legado.app.lib.prefs.ColorPreference\n            android:defaultValue=\"@color/md_grey_100\"\n            android:key=\"colorBackground\"\n            android:summary=\"@string/day_background_color\"\n            android:title=\"@string/background_color\"\n            app:cpv_dialogType=\"preset\"\n            app:iconSpaceReserved=\"false\" />\n\n        <io.legado.app.lib.prefs.ColorPreference\n            android:defaultValue=\"@color/md_grey_200\"\n            android:key=\"colorBottomBackground\"\n            android:summary=\"@string/day_navbar_color\"\n            android:title=\"@string/navbar_color\"\n            app:allowDividerAbove=\"false\"\n            app:allowDividerBelow=\"false\"\n            app:cpv_dialogType=\"preset\"\n            app:iconSpaceReserved=\"false\" />\n\n        <io.legado.app.lib.prefs.Preference\n            android:key=\"backgroundImage\"\n            android:title=\"@string/background_image\" />\n\n        <io.legado.app.lib.prefs.Preference\n            android:key=\"saveDayTheme\"\n            android:summary=\"@string/save_day_theme_summary\"\n            android:title=\"@string/save_theme_config\"\n            app:allowDividerAbove=\"false\"\n            app:allowDividerBelow=\"false\"\n            app:iconSpaceReserved=\"false\" />\n\n    </io.legado.app.lib.prefs.PreferenceCategory>\n\n    <io.legado.app.lib.prefs.PreferenceCategory\n        android:key=\"nightThemeCategory\"\n        android:title=\"@string/night\"\n        app:allowDividerAbove=\"true\"\n        app:allowDividerBelow=\"false\"\n        app:iconSpaceReserved=\"false\"\n        app:layout=\"@layout/view_preference_category\">\n\n        <io.legado.app.lib.prefs.ColorPreference\n            android:defaultValue=\"@color/md_blue_grey_600\"\n            android:key=\"colorPrimaryNight\"\n            android:summary=\"@string/night_primary\"\n            android:title=\"@string/primary\"\n            app:cpv_dialogType=\"preset\"\n            app:iconSpaceReserved=\"false\" />\n\n        <io.legado.app.lib.prefs.ColorPreference\n            android:defaultValue=\"@color/md_deep_orange_800\"\n            android:key=\"colorAccentNight\"\n            android:summary=\"@string/night_accent\"\n            android:title=\"@string/accent\"\n            app:cpv_dialogType=\"preset\"\n            app:iconSpaceReserved=\"false\" />\n\n        <io.legado.app.lib.prefs.ColorPreference\n            android:defaultValue=\"@color/md_grey_900\"\n            android:key=\"colorBackgroundNight\"\n            android:summary=\"@string/night_background_color\"\n            android:title=\"@string/background_color\"\n            app:cpv_dialogType=\"preset\"\n            app:iconSpaceReserved=\"false\" />\n\n        <io.legado.app.lib.prefs.ColorPreference\n            android:defaultValue=\"@color/md_grey_850\"\n            android:key=\"colorBottomBackgroundNight\"\n            android:summary=\"@string/night_navbar_color\"\n            android:title=\"@string/navbar_color\"\n            app:cpv_dialogType=\"preset\"\n            app:iconSpaceReserved=\"false\" />\n\n        <io.legado.app.lib.prefs.Preference\n            android:key=\"backgroundImageNight\"\n            android:title=\"@string/background_image\" />\n\n        <io.legado.app.lib.prefs.Preference\n            android:key=\"saveNightTheme\"\n            android:summary=\"@string/save_night_theme_summary\"\n            android:title=\"@string/save_theme_config\"\n            app:allowDividerAbove=\"false\"\n            app:allowDividerBelow=\"false\"\n            app:iconSpaceReserved=\"false\" />\n\n    </io.legado.app.lib.prefs.PreferenceCategory>\n\n</androidx.preference.PreferenceScreen>"
  },
  {
    "path": "app/src/main/res/xml/pref_config_welcome.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.preference.PreferenceScreen xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\">\n\n    <io.legado.app.lib.prefs.SwitchPreference\n        android:defaultValue=\"false\"\n        android:key=\"customWelcome\"\n        android:summary=\"@string/custom_welcome_summary\"\n        android:title=\"@string/custom_welcome\"\n        app:allowDividerAbove=\"false\"\n        app:allowDividerBelow=\"false\"\n        app:iconSpaceReserved=\"false\" />\n\n    <io.legado.app.lib.prefs.PreferenceCategory\n        android:key=\"dayThemeCategory\"\n        android:title=\"@string/day\"\n        app:allowDividerAbove=\"true\"\n        app:allowDividerBelow=\"false\"\n        app:iconSpaceReserved=\"false\"\n        app:layout=\"@layout/view_preference_category\">\n\n        <io.legado.app.lib.prefs.Preference\n            android:key=\"welcomeImagePath\"\n            android:title=\"@string/background_image\"\n            app:allowDividerAbove=\"false\"\n            app:allowDividerBelow=\"false\"\n            app:iconSpaceReserved=\"false\" />\n\n        <io.legado.app.lib.prefs.SwitchPreference\n            android:defaultValue=\"true\"\n            android:key=\"welcomeShowText\"\n            android:summary=\"@string/welcome_text\"\n            android:title=\"@string/show_welcome_text\"\n            app:allowDividerAbove=\"false\"\n            app:allowDividerBelow=\"false\"\n            app:iconSpaceReserved=\"false\" />\n\n        <io.legado.app.lib.prefs.SwitchPreference\n            android:defaultValue=\"true\"\n            android:key=\"welcomeShowIcon\"\n            android:summary=\"@string/show_default_book_icon\"\n            android:title=\"@string/show_icon\"\n            app:allowDividerAbove=\"false\"\n            app:allowDividerBelow=\"false\"\n            app:iconSpaceReserved=\"false\" />\n\n    </io.legado.app.lib.prefs.PreferenceCategory>\n\n    <io.legado.app.lib.prefs.PreferenceCategory\n        android:key=\"nightThemeCategory\"\n        android:title=\"@string/night\"\n        app:allowDividerAbove=\"true\"\n        app:allowDividerBelow=\"false\"\n        app:iconSpaceReserved=\"false\"\n        app:layout=\"@layout/view_preference_category\">\n\n        <io.legado.app.lib.prefs.Preference\n            android:key=\"welcomeImagePathDark\"\n            android:title=\"@string/background_image\"\n            app:allowDividerAbove=\"false\"\n            app:allowDividerBelow=\"false\"\n            app:iconSpaceReserved=\"false\" />\n\n        <io.legado.app.lib.prefs.SwitchPreference\n            android:defaultValue=\"true\"\n            android:key=\"welcomeShowTextDark\"\n            android:summary=\"@string/welcome_text\"\n            android:title=\"@string/show_welcome_text\"\n            app:allowDividerAbove=\"false\"\n            app:allowDividerBelow=\"false\"\n            app:iconSpaceReserved=\"false\" />\n\n        <io.legado.app.lib.prefs.SwitchPreference\n            android:defaultValue=\"true\"\n            android:key=\"welcomeShowIconDark\"\n            android:summary=\"@string/show_default_book_icon\"\n            android:title=\"@string/show_icon\"\n            app:allowDividerAbove=\"false\"\n            app:allowDividerBelow=\"false\"\n            app:iconSpaceReserved=\"false\" />\n\n    </io.legado.app.lib.prefs.PreferenceCategory>\n\n</androidx.preference.PreferenceScreen>"
  },
  {
    "path": "app/src/main/res/xml/pref_main.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<androidx.preference.PreferenceScreen xmlns:android=\"http://schemas.android.com/apk/res/android\"\n    xmlns:app=\"http://schemas.android.com/apk/res-auto\"\n    android:divider=\"@color/transparent\"\n    app:allowDividerBelow=\"false\">\n\n    <io.legado.app.lib.prefs.Preference\n        android:icon=\"@drawable/ic_cfg_source\"\n        android:key=\"bookSourceManage\"\n        android:summary=\"@string/book_source_manage_desc\"\n        android:title=\"@string/book_source_manage\"\n        app:iconSpaceReserved=\"false\" />\n\n    <io.legado.app.lib.prefs.Preference\n        android:icon=\"@drawable/ic_cfg_source\"\n        android:key=\"txtTocRuleManage\"\n        android:summary=\"@string/config_txt_toc_rule\"\n        android:title=\"@string/txt_toc_rule\"\n        app:iconSpaceReserved=\"false\" />\n\n    <io.legado.app.lib.prefs.Preference\n        android:icon=\"@drawable/ic_cfg_replace\"\n        android:key=\"replaceManage\"\n        android:summary=\"@string/replace_purify_desc\"\n        android:title=\"@string/replace_purify\"\n        app:iconSpaceReserved=\"false\" />\n\n    <io.legado.app.lib.prefs.Preference\n        android:icon=\"@drawable/ic_translate\"\n        android:key=\"dictRuleManage\"\n        android:summary=\"@string/config_dict_rule\"\n        android:title=\"@string/dict_rule\"\n        app:iconSpaceReserved=\"false\" />\n\n    <io.legado.app.lib.prefs.NameListPreference\n        android:defaultValue=\"0\"\n        android:entries=\"@array/theme_mode\"\n        android:entryValues=\"@array/theme_mode_v\"\n        android:icon=\"@drawable/ic_cfg_theme\"\n        android:key=\"themeMode\"\n        android:summary=\"@string/theme_mode_desc\"\n        android:title=\"@string/theme_mode\"\n        app:iconSpaceReserved=\"false\" />\n\n    <io.legado.app.lib.prefs.SwitchPreference\n        android:defaultValue=\"false\"\n        android:icon=\"@drawable/ic_cfg_web\"\n        android:key=\"webService\"\n        android:summary=\"@string/web_service_desc\"\n        android:title=\"@string/web_service\"\n        app:allowDividerBelow=\"false\"\n        app:iconSpaceReserved=\"false\" />\n\n    <io.legado.app.lib.prefs.PreferenceCategory\n        android:title=\"@string/setting\"\n        app:allowDividerAbove=\"true\"\n        app:allowDividerBelow=\"false\"\n        app:iconSpaceReserved=\"false\">\n\n        <io.legado.app.lib.prefs.Preference\n            android:icon=\"@drawable/ic_cfg_backup\"\n            android:key=\"web_dav_setting\"\n            android:summary=\"@string/web_dav_set_import_old\"\n            android:title=\"@string/backup_restore\"\n            app:iconSpaceReserved=\"false\" />\n\n        <io.legado.app.lib.prefs.Preference\n            android:icon=\"@drawable/ic_cfg_theme\"\n            android:key=\"theme_setting\"\n            android:summary=\"@string/theme_setting_s\"\n            android:title=\"@string/theme_setting\"\n            app:iconSpaceReserved=\"false\" />\n\n        <io.legado.app.lib.prefs.Preference\n            android:icon=\"@drawable/ic_cfg_other\"\n            android:key=\"setting\"\n            android:summary=\"@string/other_setting_s\"\n            android:title=\"@string/other_setting\"\n            app:allowDividerBelow=\"false\"\n            app:iconSpaceReserved=\"false\" />\n\n    </io.legado.app.lib.prefs.PreferenceCategory>\n\n    <io.legado.app.lib.prefs.PreferenceCategory\n        android:key=\"aboutCategory\"\n        android:title=\"@string/other\"\n        app:allowDividerAbove=\"true\"\n        app:allowDividerBelow=\"false\"\n        app:iconSpaceReserved=\"false\"\n        app:layout=\"@layout/view_preference_category\">\n\n        <io.legado.app.lib.prefs.Preference\n            android:icon=\"@drawable/ic_bookmark\"\n            android:key=\"bookmark\"\n            android:summary=\"@string/all_bookmark\"\n            android:title=\"@string/bookmark\"\n            app:iconSpaceReserved=\"false\" />\n\n        <io.legado.app.lib.prefs.Preference\n            android:icon=\"@drawable/ic_history\"\n            android:key=\"readRecord\"\n            android:summary=\"@string/read_record_summary\"\n            android:title=\"@string/read_record\"\n            app:iconSpaceReserved=\"false\" />\n\n        <io.legado.app.lib.prefs.Preference\n            android:icon=\"@drawable/ic_folder_outline\"\n            android:key=\"fileManage\"\n            android:summary=\"@string/file_manage_summary\"\n            android:title=\"@string/file_manage\"\n            app:iconSpaceReserved=\"false\" />\n\n        <io.legado.app.lib.prefs.Preference\n            android:icon=\"@drawable/ic_cfg_about\"\n            android:key=\"about\"\n            android:title=\"@string/about\"\n            app:iconSpaceReserved=\"false\" />\n\n        <io.legado.app.lib.prefs.Preference\n            android:icon=\"@drawable/ic_exit\"\n            android:key=\"exit\"\n            android:title=\"@string/exit\"\n            app:iconSpaceReserved=\"false\" />\n\n    </io.legado.app.lib.prefs.PreferenceCategory>\n\n</androidx.preference.PreferenceScreen>"
  },
  {
    "path": "app/src/main/res/xml/spen_remote_actions.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<remote-actions\n    actionset_label=\"@string/app_name\"\n    version=\"1.2\">\n    <action\n        id=\"next_page\"\n        label=\"@string/next_page\"\n        priority=\"1\"\n        repeatable=\"true\"\n        repeatable_interval=\"short\"\n        trigger_key=\"PAGE_DOWN\">\n        <preference name=\"gesture\" value=\"click\" />\n    </action>\n    <action\n        id=\"prev_page\"\n        label=\"@string/prev_page\"\n        priority=\"2\"\n        repeatable=\"true\"\n        repeatable_interval=\"short\"\n        trigger_key=\"PAGE_UP\">\n        <preference name=\"gesture\" value=\"double_click\" />\n    </action>\n</remote-actions>"
  },
  {
    "path": "app/src/test/java/io/legado/app/ExampleUnitTest.kt",
    "content": "package io.legado.app\n\nimport org.junit.Assert.assertEquals\nimport org.junit.Test\n\n\n/**\n * Example local unit test, which will execute on the development machine (host).\n *\n * See [testing documentation](http://d.android.com/tools/testing).\n */\nclass ExampleUnitTest {\n\n    @Test\n    fun addition_isCorrect() {\n        assertEquals(4, 2 + 2)\n    }\n\n}\n"
  },
  {
    "path": "app/src/test/java/io/legado/app/JsTest.kt",
    "content": "package io.legado.app\n\nimport com.script.ScriptBindings\nimport com.script.rhino.RhinoScriptEngine\nimport io.legado.app.data.entities.BookChapter\nimport org.intellij.lang.annotations.Language\nimport org.junit.Assert\nimport org.junit.Test\n\nclass JsTest {\n\n    @Language(\"js\")\n    private val printJs = \"\"\"\n        function print(str, newline) {\n            if (typeof(str) == 'undefined') {\n                str = 'undefined';\n            } else if (str == null) {\n                str = 'null';\n            } \n            java.lang.System.out.print(String(str));\n            if (newline) java.lang.System.out.print(\"\\n\");\n        }\n        function println(str) { \n            print(str, true);\n        }\n    \"\"\".trimIndent()\n\n    @Test\n    fun testMap() {\n        val map = hashMapOf(\"id\" to \"3242532321\")\n        val bindings = ScriptBindings()\n        bindings[\"result\"] = map\n        @Language(\"js\")\n        val jsMap = \"$=result;id=$.id;id\"\n        val result = RhinoScriptEngine.eval(jsMap, bindings)\n        Assert.assertEquals(\"3242532321\", result)\n        @Language(\"js\")\n        val jsMap1 = \"\"\"result.get(\"id\")\"\"\"\n        val result1 = RhinoScriptEngine.eval(jsMap1, bindings)\n        Assert.assertEquals(\"3242532321\", result1)\n    }\n\n    @Test\n    fun testFor() {\n        val scope = RhinoScriptEngine.run {\n            val scope = getRuntimeScope(ScriptBindings())\n            eval(printJs, scope)\n            scope\n        }\n\n        @Language(\"js\")\n        val jsFor = \"\"\"\n            let result = 0\n            let a=[1,2,3]\n            let l=a.length\n            for (let i = 0;i<l;i++){\n            \tresult = result + a[i]\n                println(i)\n            }\n            for (let o of a){\n            \tresult = result + o\n                println(o)\n            }\n            for (let o in a){\n            \tresult = result + o\n                println(o)\n            }\n            result\n        \"\"\".trimIndent()\n        val result = RhinoScriptEngine.eval(jsFor, scope)\n        Assert.assertEquals(\"12012\", result)\n    }\n\n    @Test\n    fun testReturnNull() {\n        val result = RhinoScriptEngine.eval(\"null\")\n        Assert.assertEquals(null, result)\n    }\n\n    @Test\n    fun testReplace() {\n        @Language(\"js\")\n        val js = \"\"\"\n            s=result.match(/(.{1,6}?)(第.*)/);\n            n=s[2].length-parseInt(6-s[1].length);\n            s[2].substr(0,n);\n        \"\"\".trimIndent()\n        val x = RhinoScriptEngine.run {\n            val bindings = ScriptBindings()\n            bindings[\"result\"] = \"筳彩涫第七百一十四章 人头树鮺舦綸\"\n            eval(js, bindings)\n        }\n        Assert.assertEquals(x, \"第七百一十四章 人头树\")\n    }\n\n\n    @Test\n    fun chapterText() {\n        val chapter = BookChapter(title = \"xxxyyy\")\n        val bindings = ScriptBindings()\n        bindings[\"chapter\"] = chapter\n        @Language(\"js\")\n        val js = \"chapter.title\"\n        val result = RhinoScriptEngine.eval(js, bindings)\n        Assert.assertEquals(result, \"xxxyyy\")\n    }\n\n    @Test\n    fun javaListForEach() {\n        val list = arrayListOf(1, 2, 3)\n        val bindings = ScriptBindings()\n        bindings[\"list\"] = list\n        @Language(\"js\")\n        val js = \"\"\"\n            var result = 0\n            list.forEach(item => {result = result + item})\n            result\n        \"\"\".trimIndent()\n        val result = RhinoScriptEngine.eval(js, bindings)\n        Assert.assertEquals(result, 6.0)\n    }\n\n    @Test\n    fun typeofString() {\n        val bindings = ScriptBindings()\n        @Language(\"js\")\n        val js = \"\"\"\n            s = \"\" + String()\n            typeof s\n        \"\"\".trimIndent()\n        val result = RhinoScriptEngine.eval(js, bindings)\n        Assert.assertEquals(result, \"string\")\n    }\n\n}"
  },
  {
    "path": "avd.bat",
    "content": "emulator -avd %1 -dns-server 8.8.8.8 -no-snapshot-load"
  },
  {
    "path": "avd.sh",
    "content": "emulator -avd $1 -no-snapshot-load"
  },
  {
    "path": "build.gradle",
    "content": "// Top-level build file where you can add configuration options common to all sub-projects/modules.\n\nbuildscript {\n    ext{\n        compile_sdk_version = 36\n//        build_tool_version = '34.0.0'\n//        kotlin_version = '1.9.22'\n//        ksp_version = \"1.0.17\"\n//        agp_version = '8.2.2'\n//        media3_version = \"1.2.1\"\n//        splitties_version = '3.0.0'\n//        room_version = '2.6.1'\n    }\n}\n\nplugins {\n    //id 'com.android.application' version \"$agp_version\" apply false\n    //id 'com.android.library' version \"$agp_version\" apply false\n    //id 'org.jetbrains.kotlin.android' version \"$kotlin_version\" apply false\n    //id 'com.google.devtools.ksp' version \"$kotlin_version-$ksp_version\" apply false\n    //id \"de.undercouch.download\" version \"5.5.0\" apply false\n    //id \"com.google.gms.google-services\" version \"4.4.0\" apply false\n\n    alias libs.plugins.android.application apply false\n    alias libs.plugins.android.library apply false\n    alias libs.plugins.kotlin.android apply false\n    alias libs.plugins.kotlin.parcelize apply false\n    alias libs.plugins.ksp apply false\n    alias libs.plugins.google.services apply false\n    alias libs.plugins.room apply false\n\n    alias libs.plugins.download apply false\n}\n\ntasks.register('clean', Delete) {\n    delete rootProject.layout.buildDirectory\n}\n"
  },
  {
    "path": "gradle/libs.versions.toml",
    "content": "[versions]\n\nkotlin = \"2.3.0\"\nksp = \"2.3.4\"\nagp = \"8.13.2\"\nappcompat = \"1.7.1\"\ncolorpicker = \"1.1.0\"\nconstraintlayout = \"2.2.1\"\ncore = \"1.17.0\"\nflexbox = \"3.0.0\"\nfragment = \"1.8.9\"\ndocumentfile = \"1.1.0\"\n\nlibarchive = \"1.1.6\"\nglide = \"5.0.5\"\ngson = \"2.13.2\"\njsonPath = \"2.10.0\"\njsoupxpath = \"2.5.3\"\ncoroutines = \"1.10.2\"\nliveeventbus = \"1.8.14\"\nmarkwon = \"4.6.2\"\nmaterial = \"1.13.0\"\nmedia = \"1.7.1\"\nnanoHttpd = \"2.3.1\"\nokhttp = \"5.3.2\"\npreference = \"1.2.1\"\nquickChineseTransfer = \"0.2.16\"\n\nsplitties = \"3.0.0\"\ndesugar = \"2.1.5\"\n\n#kotlinxSerialization = \"1.8.0\"\nswiperefreshlayout = \"1.2.0\"\ncollection = \"1.5.0\"\n\nzxingLite = \"3.3.0\"\n\n#不要更新版本\n#noinspection NewerVersionAvailable\nhutool = \"5.8.22\"\n\n#noinspection NewerVersionAvailable\nprotobufJavalite = \"4.26.1\"\n\n# issue #3811，不要更新版本，新版引入了一个破坏性变更（详见https://github.com/jhy/jsoup/pull/2017）\n# 若要升级请确保相关代码不会受此变更影响（如AnalyzeByJSoup.kt、JsoupXpath库等）\n#noinspection NewerVersionAvailable\njsoup = \"1.16.2\"\n\n# 新版本使用了 Android 6 以下缺少的 Arrays.setAll 方法，导致进入阅读界面会崩溃\n#noinspection GradleDependency,NewerVersionAvailable\ncommonsText = \"1.13.1\"\n\n# 新版本使用了 Android 8 以下无法编译的 VarHandle.compareAndExchange 方法\n#noinspection GradleDependency\nrhino = \"1.8.1\"\n\n#noinspection GradleDependency,NewerVersionAvailable\nmedia3 = \"1.8.0\"\n\n#noinspection GradleDependency\nwebkit = \"1.14.0\"\n#noinspection GradleDependency\nactivity = \"1.11.0\"\n#noinspection GradleDependency\nlifecycle = \"2.9.4\"\n#noinspection GradleDependency\nroom = \"2.7.1\"\n#noinspection GradleDependency\nrecyclerview = \"1.2.0\"\n#noinspection GradleDependency\nviewpager2 = \"1.0.0\"\n#noinspection GradleDependency\nfirebaseBom = \"33.2.0\"\n\n[libraries]\n\nactivity-activity = { module = \"androidx.activity:activity\", version.ref = \"activity\" }\nactivity-compose = { module = \"androidx.activity:activity-compose\", version.ref = \"activity\" }\nactivity-ktx = { module = \"androidx.activity:activity-ktx\", version.ref = \"activity\" }\n\nandroidx-annotation = { group = \"androidx.annotation\", name = \"annotation\", version = \"1.9.1\" }\n#androidx-annotation-experimental = { group = \"androidx.annotation\", name = \"annotation-experimental\", version = \"1.3.1\" }\n\nandroidx-collection = { module = \"androidx.collection:collection\", version.ref = \"collection\" }\nappcompat-appcompat = { module = \"androidx.appcompat:appcompat\", version.ref = \"appcompat\" }\nandroidx-constraintlayout = { module = \"androidx.constraintlayout:constraintlayout\", version.ref = \"constraintlayout\" }\n\n\ncore-core = { module = \"androidx.core:core\", version.ref = \"core\" }\ncore-ktx = { module = \"androidx.core:core-ktx\", version.ref = \"core\" }\n\nandroidx-espresso-core = { module = \"androidx.test.espresso:espresso-core\", version = \"3.6.1\" }\n\nfirebase-bom = { module = \"com.google.firebase:firebase-bom\", version.ref = \"firebaseBom\" }\nfirebase-analytics = { module = \"com.google.firebase:firebase-analytics\" }\nfirebase-perf = { module = \"com.google.firebase:firebase-perf\" }\n\n\nfragment-fragment = { module = \"androidx.fragment:fragment\", version.ref = \"fragment\" }\nfragment-ktx = { module = \"androidx.fragment:fragment-ktx\", version.ref = \"fragment\" }\nfragment-testing = { module = \"androidx.fragment:fragment-testing\", version.ref = \"fragment\" }\n\nandroidx-junit = { module = \"androidx.test.ext:junit\", version = \"1.2.1\" }\n\nhutool-crypto = { module = \"cn.hutool:hutool-crypto\", version.ref = \"hutool\" }\n\nlibarchive = { module = \"me.zhanghai.android.libarchive:library\", version.ref = \"libarchive\" }\nlifecycle-common-java8 = { module = \"androidx.lifecycle:lifecycle-common-java8\", version.ref = \"lifecycle\" }\nlifecycle-service = { module = \"androidx.lifecycle:lifecycle-service\", version.ref = \"lifecycle\" }\n\nmedia-media = { module = \"androidx.media:media\", version.ref = \"media\" }\nmedia3-datasource-okhttp = { module = \"androidx.media3:media3-datasource-okhttp\", version.ref = \"media3\" }\nmedia3-exoplayer = { module = \"androidx.media3:media3-exoplayer\", version.ref = \"media3\" }\nmedia3-session = { module = \"androidx.media3:media3-session\", version.ref = \"media3\" }\n\nmarkwon-core = { module = \"io.noties.markwon:core\", version.ref = \"markwon\" }\nmarkwon-image-glide = { module = \"io.noties.markwon:image-glide\", version.ref = \"markwon\" }\nmarkwon-ext-tables = { module = \"io.noties.markwon:ext-tables\", version.ref = \"markwon\" }\nmarkwon-html = { module = \"io.noties.markwon:html\", version.ref = \"markwon\" }\n\n\nnanohttpd-nanohttpd = { module = \"org.nanohttpd:nanohttpd\", version.ref = \"nanoHttpd\" }\nnanohttpd-websocket = { module = \"org.nanohttpd:nanohttpd-websocket\", version.ref = \"nanoHttpd\" }\n\n\npreference-preference = { module = \"androidx.preference:preference\", version.ref = \"preference\" }\npreference-ktx = { module = \"androidx.preference:preference-ktx\", version.ref = \"preference\" }\n\nokhttp = { module = \"com.squareup.okhttp3:okhttp\", version.ref = \"okhttp\" }\nprotobuf-javalite = { module = \"com.google.protobuf:protobuf-javalite\", version.ref = \"protobufJavalite\" }\n\nquick-chinese-transfer-core = { module = \"com.github.liuyueyi.quick-chinese-transfer:quick-transfer-core\", version.ref = \"quickChineseTransfer\" }\n\nroom-compiler = { module = \"androidx.room:room-compiler\", version.ref = \"room\" }\nroom-ktx = { module = \"androidx.room:room-ktx\", version.ref = \"room\" }\nroom-runtime = { module = \"androidx.room:room-runtime\", version.ref = \"room\" }\nroom-testing = { module = \"androidx.room:room-testing\", version.ref = \"room\" }\n\nglide-glide = { module = \"com.github.bumptech.glide:glide\", version.ref = \"glide\" }\nglide-okhttp = { module = \"com.github.bumptech.glide:okhttp3-integration\", version.ref = \"glide\" }\nglide-recyclerview = { module = \"com.github.bumptech.glide:recyclerview-integration\", version.ref = \"glide\" }\nglide-compiler = { module = \"com.github.bumptech.glide:compiler\", version.ref = \"glide\" }\nglide-compose = { module = \"com.github.bumptech.glide:compose\", version = \"1.0.0-beta01\" }\nglide-ksp = { module = \"com.github.bumptech.glide:ksp\", version.ref = \"glide\" }\nglide-avif = { module = \"com.github.bumptech.glide:avif-integration\", version.ref = \"glide\" }\navif = { module = \"org.aomedia.avif.android:avif\", version = \"1.1.1.14d8e3c4\" }\nglide-svg = { module = \"com.github.qoqa:glide-svg\", version = \"4.0.2\" }\nandroidsvg = { module = \"com.caverock:androidsvg-aar\", version = \"1.4\" }\n\nsplitties-appctx = { module = \"com.louiscad.splitties:splitties-appctx\", version.ref = \"splitties\" }\nsplitties-activities = { module = \"com.louiscad.splitties:splitties-activities\", version.ref = \"splitties\" }\nsplitties-systemservices = { module = \"com.louiscad.splitties:splitties-systemservices\", version.ref = \"splitties\" }\nsplitties-views = { module = \"com.louiscad.splitties:splitties-views\", version.ref = \"splitties\" }\n\nandroidx-runner = { module = \"androidx.test:runner\", version = \"1.6.2\" }\nandroidx-swiperefreshlayout = { module = \"androidx.swiperefreshlayout:swiperefreshlayout\", version.ref = \"swiperefreshlayout\" }\nandroidx-recyclerview = { module = \"androidx.recyclerview:recyclerview\", version.ref = \"recyclerview\" }\nandroidx-viewpager2 = { module = \"androidx.viewpager2:viewpager2\", version.ref = \"viewpager2\" }\nandroidx-webkit = { module = \"androidx.webkit:webkit\", version.ref = \"webkit\" }\nandroidx-documentfile = { module = \"androidx.documentfile:documentfile\", version.ref = \"documentfile\" }\ncolorpicker = { module = \"com.jaredrummler:colorpicker\", version.ref = \"colorpicker\" }\ncommons-text = { module = \"org.apache.commons:commons-text\", version.ref = \"commonsText\" }\ndesugar = { group = \"com.android.tools\", name = \"desugar_jdk_libs_nio\", version.ref = \"desugar\" }\n\nflexbox = { module = \"com.google.android.flexbox:flexbox\", version.ref = \"flexbox\" }\ngson = { module = \"com.google.code.gson:gson\", version.ref = \"gson\" }\njson-path = { module = \"com.jayway.jsonpath:json-path\", version.ref = \"jsonPath\" }\njsoup = { module = \"org.jsoup:jsoup\", version.ref = \"jsoup\" }\njsoupxpath = { module = \"cn.wanghaomiao:JsoupXpath\", version.ref = \"jsoupxpath\" }\njunit = { module = \"junit:junit\", version = \"4.13.2\" }\nkotlinx-coroutines-android = { module = \"org.jetbrains.kotlinx:kotlinx-coroutines-android\", version.ref = \"coroutines\" }\nkotlinx-coroutines-core = { module = \"org.jetbrains.kotlinx:kotlinx-coroutines-core\", version.ref = \"coroutines\" }\n\nkotlin-stdlib = { module = \"org.jetbrains.kotlin:kotlin-stdlib\", version.ref = \"kotlin\" }\nkotlin-reflect = { module = \"org.jetbrains.kotlin:kotlin-reflect\", version.ref = \"kotlin\" }\n\n\n#kotlinx-serialization-json = { module = \"org.jetbrains.kotlinx:kotlinx-serialization-json\", version.ref = \"kotlinxSerialization\" }\n#kotlinx-serialization-protobuf = { module = \"org.jetbrains.kotlinx:kotlinx-serialization-protobuf\", version.ref = \"kotlinxSerialization\" }\nliveeventbus = { module = \"com.github.michaellee123:LiveEventBus\", version.ref = \"liveeventbus\" }\nmaterial = { module = \"com.google.android.material:material\", version.ref = \"material\" }\n\nmozilla-rhino = { module = \"org.mozilla:rhino\", version.ref = \"rhino\" }\n\nrenderscript-intrinsics-replacement-toolkit = { module = \"com.github.TomasValenta:renderscript-intrinsics-replacement-toolkit\", version = \"8eaa829ddd\" }\nzxing-lite = { module = \"com.github.jenly1314:zxing-lite\", version.ref = \"zxingLite\" }\n\n[bundles]\ncoroutines = [\"kotlinx-coroutines-core\", \"kotlinx-coroutines-android\"]\nandroidTest = [\"androidx-espresso-core\", \"androidx-junit\", \"androidx-runner\"]\n\n[plugins]\n\nandroid-application = { id = \"com.android.application\", version.ref = \"agp\" }\nandroid-library = { id = \"com.android.library\", version.ref = \"agp\" }\nandroid-test = { id = \"com.android.test\", version.ref = \"agp\" }\n\n#androidx-benchmark = { id = \"androidx.benchmark\", version = \"1.3.3\" }\n\nkotlin-android = { id = \"org.jetbrains.kotlin.android\", version.ref = \"kotlin\" }\nkotlin-jvm = { id = \"org.jetbrains.kotlin.jvm\", version.ref = \"kotlin\" }\nkotlin-kapt = { id = \"org.jetbrains.kotlin.kapt\", version.ref = \"kotlin\" }\nkotlin-serialization = { id = \"org.jetbrains.kotlin.plugin.serialization\", version.ref = \"kotlin\" }\nkotlin-allopen = { id = \"org.jetbrains.kotlin.plugin.allopen\", version.ref = \"kotlin\" }\nkotlin-noarg = { id = \"org.jetbrains.kotlin.plugin.noarg\", version.ref = \"kotlin\" }\nkotlin-sam = { id = \"org.jetbrains.kotlin.plugin.sam.with.receiver\", version.ref = \"kotlin\" }\nkotlin-lombok = { id = \"org.jetbrains.kotlin.plugin.lombok\", version.ref = \"kotlin\" }\nkotlin-parcelize = { id = \"org.jetbrains.kotlin.plugin.parcelize\", version.ref = \"kotlin\" }\n\ngoogle-services = { id = \"com.google.gms.google-services\", version = \"4.4.2\" }\n\nksp = { id = \"com.google.devtools.ksp\", version.ref = \"ksp\" }\nroom = { id = \"androidx.room\", version.ref = \"room\" }\n\n#protobuf = { id = \"com.google.protobuf\", version = \"0.9.4\" }\n\ndownload = { id = \"de.undercouch.download\", version = \"5.6.0\" }"
  },
  {
    "path": "gradle/wrapper/gradle-wrapper.properties",
    "content": "#Tue Aug 27 18:19:17 CST 2024\ndistributionBase=GRADLE_USER_HOME\ndistributionPath=wrapper/dists\ndistributionUrl=https\\://services.gradle.org/distributions/gradle-8.13-bin.zip\nzipStoreBase=GRADLE_USER_HOME\nzipStorePath=wrapper/dists\n"
  },
  {
    "path": "gradle.properties",
    "content": "# Project-wide Gradle settings.\n# IDE (e.g. Android Studio) users:\n# Gradle settings configured through the IDE *will override*\n# any settings specified in this file.\n# For more details on how to configure your build environment visit\n# http://www.gradle.org/docs/current/userguide/build_environment.html\n# Specifies the JVM arguments used for the daemon process.\n# The setting is particularly useful for tweaking memory settings.\n# Default value: -Xmx512m -Xms256m -XX:MaxMetaspaceSize=256m -XX:+HeapDumpOnOutOfMemoryError\n# Bugs: This will override important JVM argument defaults. Include all important JVM arguments if you wan to change arguments. See https://github.com/gradle/gradle/issues/19750\norg.gradle.jvmargs=-XX:+UseParallelGC -Xmx6g -Xms256m -XX:MaxMetaspaceSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8\n# When configured, Gradle will run in incubating parallel mode.\n# This option should only be used with decoupled projects. More details, visit\n# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects\n# org.gradle.parallel=true\n# AndroidX package structure to make it clearer which packages are bundled with the\n# Android operating system, and which are packaged with your app's APK\n# https://developer.android.com/topic/libraries/support-library/androidx-rn\nandroid.useAndroidX=true\n# Automatically convert third-party libraries to use AndroidX\nandroid.enableJetifier=false\n# Kotlin code style for this project: \"official\" or \"obsolete\":\nkotlin.code.style=official\n# kotlin.incremental.useClasspathSnapshot=true\nandroid.enableResourceOptimizations=true\n# android.enableNewResourceShrinker' is deprecated.\n# It was removed in version 8.0 of the Android Gradle plugin.\n# android.enableNewResourceShrinker=true\nandroid.experimental.enableNewResourceShrinker.preciseShrinking=true\n# https://blog.gradle.org/introducing-file-system-watching\norg.gradle.vfs.watch=true\n# enable cache can not up app version\norg.gradle.unsafe.configuration-cache=false\n# Disable buildFeatures flags by default\nandroid.defaults.buildfeatures.aidl=false\nandroid.defaults.buildfeatures.buildconfig=false\nandroid.defaults.buildfeatures.renderscript=false\nandroid.defaults.buildfeatures.resvalues=false\nandroid.defaults.buildfeatures.shaders=false\n# enables namespacing of each library's R class so that its R class includes only the resources declared in the library itself\n# and none from the library's dependencies, thereby reducing the size of the R class for that library.\nandroid.nonTransitiveRClass=true\n# https://chromiumdash.appspot.com/releases?platform=Android\nCronetVersion=128.0.6613.40\nCronetMainVersion=128.0.0.0\nandroid.injected.testOnly=false\nandroid.nonFinalResIds=true\nandroid.injected.androidTest.leaveApksInstalledAfterRun=true\n"
  },
  {
    "path": "gradlew",
    "content": "#!/usr/bin/env sh\n\n##############################################################################\n##\n##  Gradle start up script for UN*X\n##\n##############################################################################\n\n# Attempt to set APP_HOME\n# Resolve links: $0 may be a link\nPRG=\"$0\"\n# Need this for relative symlinks.\nwhile [ -h \"$PRG\" ] ; do\n    ls=`ls -ld \"$PRG\"`\n    link=`expr \"$ls\" : '.*-> \\(.*\\)$'`\n    if expr \"$link\" : '/.*' > /dev/null; then\n        PRG=\"$link\"\n    else\n        PRG=`dirname \"$PRG\"`\"/$link\"\n    fi\ndone\nSAVED=\"`pwd`\"\ncd \"`dirname \\\"$PRG\\\"`/\" >/dev/null\nAPP_HOME=\"`pwd -P`\"\ncd \"$SAVED\" >/dev/null\n\nAPP_NAME=\"Gradle\"\nAPP_BASE_NAME=`basename \"$0\"`\n\n# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\nDEFAULT_JVM_OPTS=\"\"\n\n# Use the maximum available, or set MAX_FD != -1 to use that value.\nMAX_FD=\"maximum\"\n\nwarn () {\n    echo \"$*\"\n}\n\ndie () {\n    echo\n    echo \"$*\"\n    echo\n    exit 1\n}\n\n# OS specific support (must be 'true' or 'false').\ncygwin=false\nmsys=false\ndarwin=false\nnonstop=false\ncase \"`uname`\" in\n  CYGWIN* )\n    cygwin=true\n    ;;\n  Darwin* )\n    darwin=true\n    ;;\n  MINGW* )\n    msys=true\n    ;;\n  NONSTOP* )\n    nonstop=true\n    ;;\nesac\n\nCLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar\n\n# Determine the Java command to use to start the JVM.\nif [ -n \"$JAVA_HOME\" ] ; then\n    if [ -x \"$JAVA_HOME/jre/sh/java\" ] ; then\n        # IBM's JDK on AIX uses strange locations for the executables\n        JAVACMD=\"$JAVA_HOME/jre/sh/java\"\n    else\n        JAVACMD=\"$JAVA_HOME/bin/java\"\n    fi\n    if [ ! -x \"$JAVACMD\" ] ; then\n        die \"ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\n    fi\nelse\n    JAVACMD=\"java\"\n    which java >/dev/null 2>&1 || die \"ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\n\nPlease set the JAVA_HOME variable in your environment to match the\nlocation of your Java installation.\"\nfi\n\n# Increase the maximum file descriptors if we can.\nif [ \"$cygwin\" = \"false\" -a \"$darwin\" = \"false\" -a \"$nonstop\" = \"false\" ] ; then\n    MAX_FD_LIMIT=`ulimit -H -n`\n    if [ $? -eq 0 ] ; then\n        if [ \"$MAX_FD\" = \"maximum\" -o \"$MAX_FD\" = \"max\" ] ; then\n            MAX_FD=\"$MAX_FD_LIMIT\"\n        fi\n        ulimit -n $MAX_FD\n        if [ $? -ne 0 ] ; then\n            warn \"Could not set maximum file descriptor limit: $MAX_FD\"\n        fi\n    else\n        warn \"Could not query maximum file descriptor limit: $MAX_FD_LIMIT\"\n    fi\nfi\n\n# For Darwin, add options to specify how the application appears in the dock\nif $darwin; then\n    GRADLE_OPTS=\"$GRADLE_OPTS \\\"-Xdock:name=$APP_NAME\\\" \\\"-Xdock:icon=$APP_HOME/media/gradle.icns\\\"\"\nfi\n\n# For Cygwin, switch paths to Windows format before running java\nif $cygwin ; then\n    APP_HOME=`cygpath --path --mixed \"$APP_HOME\"`\n    CLASSPATH=`cygpath --path --mixed \"$CLASSPATH\"`\n    JAVACMD=`cygpath --unix \"$JAVACMD\"`\n\n    # We build the pattern for arguments to be converted via cygpath\n    ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`\n    SEP=\"\"\n    for dir in $ROOTDIRSRAW ; do\n        ROOTDIRS=\"$ROOTDIRS$SEP$dir\"\n        SEP=\"|\"\n    done\n    OURCYGPATTERN=\"(^($ROOTDIRS))\"\n    # Add a user-defined pattern to the cygpath arguments\n    if [ \"$GRADLE_CYGPATTERN\" != \"\" ] ; then\n        OURCYGPATTERN=\"$OURCYGPATTERN|($GRADLE_CYGPATTERN)\"\n    fi\n    # Now convert the arguments - kludge to limit ourselves to /bin/sh\n    i=0\n    for arg in \"$@\" ; do\n        CHECK=`echo \"$arg\"|egrep -c \"$OURCYGPATTERN\" -`\n        CHECK2=`echo \"$arg\"|egrep -c \"^-\"`                                 ### Determine if an option\n\n        if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then                    ### Added a condition\n            eval `echo args$i`=`cygpath --path --ignore --mixed \"$arg\"`\n        else\n            eval `echo args$i`=\"\\\"$arg\\\"\"\n        fi\n        i=$((i+1))\n    done\n    case $i in\n        (0) set -- ;;\n        (1) set -- \"$args0\" ;;\n        (2) set -- \"$args0\" \"$args1\" ;;\n        (3) set -- \"$args0\" \"$args1\" \"$args2\" ;;\n        (4) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" ;;\n        (5) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" ;;\n        (6) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" ;;\n        (7) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" ;;\n        (8) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" \"$args7\" ;;\n        (9) set -- \"$args0\" \"$args1\" \"$args2\" \"$args3\" \"$args4\" \"$args5\" \"$args6\" \"$args7\" \"$args8\" ;;\n    esac\nfi\n\n# Escape application args\nsave () {\n    for i do printf %s\\\\n \"$i\" | sed \"s/'/'\\\\\\\\''/g;1s/^/'/;\\$s/\\$/' \\\\\\\\/\" ; done\n    echo \" \"\n}\nAPP_ARGS=$(save \"$@\")\n\n# Collect all arguments for the java command, following the shell quoting and substitution rules\neval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS \"\\\"-Dorg.gradle.appname=$APP_BASE_NAME\\\"\" -classpath \"\\\"$CLASSPATH\\\"\" org.gradle.wrapper.GradleWrapperMain \"$APP_ARGS\"\n\n# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong\nif [ \"$(uname)\" = \"Darwin\" ] && [ \"$HOME\" = \"$PWD\" ]; then\n  cd \"$(dirname \"$0\")\"\nfi\n\nexec \"$JAVACMD\" \"$@\"\n"
  },
  {
    "path": "gradlew.bat",
    "content": "@if \"%DEBUG%\" == \"\" @echo off\n@rem ##########################################################################\n@rem\n@rem  Gradle startup script for Windows\n@rem\n@rem ##########################################################################\n\n@rem Set local scope for the variables with windows NT shell\nif \"%OS%\"==\"Windows_NT\" setlocal\n\nset DIRNAME=%~dp0\nif \"%DIRNAME%\" == \"\" set DIRNAME=.\nset APP_BASE_NAME=%~n0\nset APP_HOME=%DIRNAME%\n\n@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.\nset DEFAULT_JVM_OPTS=\n\n@rem Find java.exe\nif defined JAVA_HOME goto findJavaFromJavaHome\n\nset JAVA_EXE=java.exe\n%JAVA_EXE% -version >NUL 2>&1\nif \"%ERRORLEVEL%\" == \"0\" goto init\n\necho.\necho ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.\necho.\necho Please set the JAVA_HOME variable in your environment to match the\necho location of your Java installation.\n\ngoto fail\n\n:findJavaFromJavaHome\nset JAVA_HOME=%JAVA_HOME:\"=%\nset JAVA_EXE=%JAVA_HOME%/bin/java.exe\n\nif exist \"%JAVA_EXE%\" goto init\n\necho.\necho ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%\necho.\necho Please set the JAVA_HOME variable in your environment to match the\necho location of your Java installation.\n\ngoto fail\n\n:init\n@rem Get command-line arguments, handling Windows variants\n\nif not \"%OS%\" == \"Windows_NT\" goto win9xME_args\n\n:win9xME_args\n@rem Slurp the command line arguments.\nset CMD_LINE_ARGS=\nset _SKIP=2\n\n:win9xME_args_slurp\nif \"x%~1\" == \"x\" goto execute\n\nset CMD_LINE_ARGS=%*\n\n:execute\n@rem Setup the command line\n\nset CLASSPATH=%APP_HOME%\\gradle\\wrapper\\gradle-wrapper.jar\n\n@rem Execute Gradle\n\"%JAVA_EXE%\" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% \"-Dorg.gradle.appname=%APP_BASE_NAME%\" -classpath \"%CLASSPATH%\" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%\n\n:end\n@rem End local scope for the variables with windows NT shell\nif \"%ERRORLEVEL%\"==\"0\" goto mainEnd\n\n:fail\nrem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of\nrem the _cmd.exe /c_ return code!\nif  not \"\" == \"%GRADLE_EXIT_CONSOLE%\" exit 1\nexit /b 1\n\n:mainEnd\nif \"%OS%\"==\"Windows_NT\" endlocal\n\n:omega\n"
  },
  {
    "path": "modules/book/.gitignore",
    "content": "/build"
  },
  {
    "path": "modules/book/build.gradle",
    "content": "plugins {\n    alias libs.plugins.android.library\n    alias libs.plugins.kotlin.android\n}\n\nandroid {\n    compileSdk = compile_sdk_version\n    namespace = 'me.ag2s'\n    kotlin {\n        jvmToolchain {\n            languageVersion.set(JavaLanguageVersion.of(17))\n        }\n    }\n    defaultConfig {\n        minSdk 21\n        targetSdk 36\n\n        testInstrumentationRunner \"androidx.test.runner.AndroidJUnitRunner\"\n        consumerProguardFiles \"consumer-rules.pro\"\n    }\n\n    compileOptions {\n        sourceCompatibility JavaVersion.VERSION_17\n        targetCompatibility JavaVersion.VERSION_17\n    }\n    lint {\n        checkDependencies = true\n    }\n}\n\ndependencies {\n    implementation(libs.androidx.annotation)\n    //implementation 'androidx.annotation:annotation:1.7.1'\n}"
  },
  {
    "path": "modules/book/consumer-rules.pro",
    "content": ""
  },
  {
    "path": "modules/book/src/main/AndroidManifest.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest>\n\n</manifest>"
  },
  {
    "path": "modules/book/src/main/java/me/ag2s/base/PfdHelper.java",
    "content": "package me.ag2s.base;\n\nimport static me.ag2s.base.ThrowableUtils.rethrowAsIOException;\n\nimport android.os.ParcelFileDescriptor;\nimport android.system.ErrnoException;\nimport android.system.OsConstants;\n\nimport java.io.EOFException;\nimport java.io.IOException;\n\n/**\n * 读取ParcelFileDescriptor的工具类\n */\n@SuppressWarnings(\"unused\")\npublic final class PfdHelper {\n\n    /**\n     * 读取基本类型的buffer\n     */\n    private static final byte[] readBuffer = new byte[8];\n\n    public static void seek(ParcelFileDescriptor pfd, long pos) throws IOException {\n        try {\n            android.system.Os.lseek(pfd.getFileDescriptor(), pos, OsConstants.SEEK_SET);\n        } catch (ErrnoException e) {\n            throw rethrowAsIOException(e);\n        }\n\n    }\n\n    public static long getFilePointer(ParcelFileDescriptor pfd) throws IOException {\n        try {\n            return android.system.Os.lseek(pfd.getFileDescriptor(), 0, OsConstants.SEEK_CUR);\n        } catch (ErrnoException e) {\n            throw rethrowAsIOException(e);\n        }\n    }\n\n    public static long length(ParcelFileDescriptor pfd) throws IOException {\n        try {\n            return android.system.Os.fstat(pfd.getFileDescriptor()).st_size; //android.system.Os.lseek(pfd.getFileDescriptor(), 0, OsConstants.SEEK_END);\n        } catch (ErrnoException e) {\n            throw rethrowAsIOException(e);\n        }\n    }\n\n    private static int readBytes(ParcelFileDescriptor pfd, byte[] b, int off, int len) throws IOException {\n        try {\n            return android.system.Os.read(pfd.getFileDescriptor(), b, off, len);\n        } catch (ErrnoException e) {\n            throw rethrowAsIOException(e);\n        }\n    }\n\n    public static int read(ParcelFileDescriptor pfd) throws IOException {\n        return (read(pfd, readBuffer, 0, 1) != -1) ? readBuffer[0] & 0xff : -1;\n    }\n\n    public static int read(ParcelFileDescriptor pfd, byte[] b, int off, int len) throws IOException {\n        return readBytes(pfd, b, off, len);\n    }\n\n    public static int read(ParcelFileDescriptor pfd, byte[] b) throws IOException {\n        return readBytes(pfd, b, 0, b.length);\n    }\n\n    public static void readFully(ParcelFileDescriptor pfd, byte[] b) throws IOException {\n        readFully(pfd, b, 0, b.length);\n    }\n\n    public static void readFully(ParcelFileDescriptor pfd, byte[] b, int off, int len) throws IOException {\n        int n = 0;\n        do {\n            int count = read(pfd, b, off + n, len - n);\n            if (count < 0)\n                throw new EOFException();\n            n += count;\n        } while (n < len);\n    }\n\n\n    public static int skipBytes(ParcelFileDescriptor pfd, int n) throws IOException {\n        long pos;\n        long len;\n        long newpos;\n\n        if (n <= 0) {\n            return 0;\n        }\n        pos = getFilePointer(pfd);\n        len = length(pfd);\n        newpos = pos + n;\n        if (newpos > len) {\n            newpos = len;\n        }\n        seek(pfd, newpos);\n\n        /* return the actual number of bytes skipped */\n        return (int) (newpos - pos);\n    }\n}\n"
  },
  {
    "path": "modules/book/src/main/java/me/ag2s/base/ThrowableUtils.java",
    "content": "package me.ag2s.base;\n\nimport androidx.annotation.NonNull;\n\nimport java.io.IOException;\n\npublic class ThrowableUtils {\n\n\n    public static @NonNull\n    IOException rethrowAsIOException(Throwable throwable) throws IOException {\n        IOException newException = new IOException(throwable.getMessage(), throwable);\n        throw newException;\n    }\n}\n"
  },
  {
    "path": "modules/book/src/main/java/me/ag2s/epublib/Constants.java",
    "content": "package me.ag2s.epublib;\n\n\npublic interface Constants {\n\n    String CHARACTER_ENCODING = \"UTF-8\";\n    String DOCTYPE_XHTML = \"<!DOCTYPE HTML PUBLIC \\\"-//W3C//DTD XHTML 1.1//EN\\\" \\\"http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd\\\">\";\n    String NAMESPACE_XHTML = \"http://www.w3.org/1999/xhtml\";\n    String EPUB_GENERATOR_NAME = \"Ag2S EpubLib\";\n    String EPUB_DUOKAN_NAME = \"DK-SONGTI\";\n    char FRAGMENT_SEPARATOR_CHAR = '#';\n    String DEFAULT_TOC_ID = \"toc\";\n}\n"
  },
  {
    "path": "modules/book/src/main/java/me/ag2s/epublib/browsersupport/NavigationEvent.java",
    "content": "package me.ag2s.epublib.browsersupport;\n\nimport java.util.EventObject;\n\nimport me.ag2s.epublib.domain.EpubBook;\nimport me.ag2s.epublib.domain.Resource;\nimport me.ag2s.epublib.util.StringUtil;\n\n/**\n * Used to tell NavigationEventListener just what kind of navigation action\n * the user just did.\n *\n * @author paul\n */\n@SuppressWarnings(\"unused\")\npublic class NavigationEvent extends EventObject {\n\n    private static final long serialVersionUID = -6346750144308952762L;\n\n    private Resource oldResource;\n    private int oldSpinePos;\n    private Navigator navigator;\n    private EpubBook oldBook;\n    private int oldSectionPos;\n    private String oldFragmentId;\n\n    public NavigationEvent(Object source) {\n        super(source);\n    }\n\n    public NavigationEvent(Object source, Navigator navigator) {\n        super(source);\n        this.navigator = navigator;\n        this.oldBook = navigator.getBook();\n        this.oldFragmentId = navigator.getCurrentFragmentId();\n        this.oldSectionPos = navigator.getCurrentSectionPos();\n        this.oldResource = navigator.getCurrentResource();\n        this.oldSpinePos = navigator.getCurrentSpinePos();\n    }\n\n    /**\n     * The previous position within the section.\n     *\n     * @return The previous position within the section.\n     */\n    public int getOldSectionPos() {\n        return oldSectionPos;\n    }\n\n    public Navigator getNavigator() {\n        return navigator;\n    }\n\n    public String getOldFragmentId() {\n        return oldFragmentId;\n    }\n\n    // package\n    void setOldFragmentId(String oldFragmentId) {\n        this.oldFragmentId = oldFragmentId;\n    }\n\n    public EpubBook getOldBook() {\n        return oldBook;\n    }\n\n    // package\n    void setOldPagePos(int oldPagePos) {\n        this.oldSectionPos = oldPagePos;\n    }\n\n    public int getCurrentSectionPos() {\n        return navigator.getCurrentSectionPos();\n    }\n\n    public int getOldSpinePos() {\n        return oldSpinePos;\n    }\n\n    public int getCurrentSpinePos() {\n        return navigator.getCurrentSpinePos();\n    }\n\n    public String getCurrentFragmentId() {\n        return navigator.getCurrentFragmentId();\n    }\n\n    public boolean isBookChanged() {\n        if (oldBook == null) {\n            return true;\n        }\n        return oldBook != navigator.getBook();\n    }\n\n    public boolean isSpinePosChanged() {\n        return getOldSpinePos() != getCurrentSpinePos();\n    }\n\n    public boolean isFragmentChanged() {\n        return StringUtil.equals(getOldFragmentId(), getCurrentFragmentId());\n    }\n\n    public Resource getOldResource() {\n        return oldResource;\n    }\n\n    public Resource getCurrentResource() {\n        return navigator.getCurrentResource();\n    }\n\n    public void setOldResource(Resource oldResource) {\n        this.oldResource = oldResource;\n    }\n\n\n    public void setOldSpinePos(int oldSpinePos) {\n        this.oldSpinePos = oldSpinePos;\n    }\n\n\n    public void setNavigator(Navigator navigator) {\n        this.navigator = navigator;\n    }\n\n\n    public void setOldBook(EpubBook oldBook) {\n        this.oldBook = oldBook;\n    }\n\n    public EpubBook getCurrentBook() {\n        return getNavigator().getBook();\n    }\n\n    public boolean isResourceChanged() {\n        return oldResource != getCurrentResource();\n    }\n\n    @SuppressWarnings(\"NullableProblems\")\n    public String toString() {\n        return StringUtil.toString(\n                \"oldSectionPos\", oldSectionPos,\n                \"oldResource\", oldResource,\n                \"oldBook\", oldBook,\n                \"oldFragmentId\", oldFragmentId,\n                \"oldSpinePos\", oldSpinePos,\n                \"currentPagePos\", getCurrentSectionPos(),\n                \"currentResource\", getCurrentResource(),\n                \"currentBook\", getCurrentBook(),\n                \"currentFragmentId\", getCurrentFragmentId(),\n                \"currentSpinePos\", getCurrentSpinePos()\n        );\n    }\n\n    public boolean isSectionPosChanged() {\n        return oldSectionPos != getCurrentSectionPos();\n    }\n}\n"
  },
  {
    "path": "modules/book/src/main/java/me/ag2s/epublib/browsersupport/NavigationEventListener.java",
    "content": "package me.ag2s.epublib.browsersupport;\n\n/**\n * Implemented by classes that want to be notified if the user moves to\n * another location in the book.\n *\n * @author paul\n */\npublic interface NavigationEventListener {\n\n    /**\n     * Called whenever the user navigates to another position in the book.\n     *\n     * @param navigationEvent f\n     */\n    void navigationPerformed(NavigationEvent navigationEvent);\n}"
  },
  {
    "path": "modules/book/src/main/java/me/ag2s/epublib/browsersupport/NavigationHistory.java",
    "content": "package me.ag2s.epublib.browsersupport;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\nimport me.ag2s.epublib.domain.EpubBook;\nimport me.ag2s.epublib.domain.Resource;\n\n/**\n * A history of the user's locations with the epub.\n *\n * @author paul.siegmann\n */\npublic class NavigationHistory implements NavigationEventListener {\n\n    public static final int DEFAULT_MAX_HISTORY_SIZE = 1000;\n    private static final long DEFAULT_HISTORY_WAIT_TIME = 1000;\n\n    private static class Location {\n\n        private String href;\n\n        public Location(String href) {\n            super();\n            this.href = href;\n        }\n\n        @SuppressWarnings(\"unused\")\n        public void setHref(String href) {\n            this.href = href;\n        }\n\n        public String getHref() {\n            return href;\n        }\n    }\n\n    private long lastUpdateTime = 0;\n    private List<Location> locations = new ArrayList<>();\n    private final Navigator navigator;\n    private int currentPos = -1;\n    private int currentSize = 0;\n    private int maxHistorySize = DEFAULT_MAX_HISTORY_SIZE;\n    private long historyWaitTime = DEFAULT_HISTORY_WAIT_TIME;\n\n    public NavigationHistory(Navigator navigator) {\n        this.navigator = navigator;\n        navigator.addNavigationEventListener(this);\n        initBook(navigator.getBook());\n    }\n\n    public int getCurrentPos() {\n        return currentPos;\n    }\n\n\n    public int getCurrentSize() {\n        return currentSize;\n    }\n\n    public void initBook(EpubBook book) {\n        if (book == null) {\n            return;\n        }\n        locations = new ArrayList<>();\n        currentPos = -1;\n        currentSize = 0;\n        if (navigator.getCurrentResource() != null) {\n            addLocation(navigator.getCurrentResource().getHref());\n        }\n    }\n\n    /**\n     * If the time between a navigation event is less than the historyWaitTime\n     * then the new location is not added to the history.\n     * <p>\n     * When a user is rapidly viewing many pages using the slider we do not\n     * want all of them to be added to the history.\n     *\n     * @return the time we wait before adding the page to the history\n     */\n    public long getHistoryWaitTime() {\n        return historyWaitTime;\n    }\n\n    public void setHistoryWaitTime(long historyWaitTime) {\n        this.historyWaitTime = historyWaitTime;\n    }\n\n    public void addLocation(Resource resource) {\n        if (resource == null) {\n            return;\n        }\n        addLocation(resource.getHref());\n    }\n\n    /**\n     * Adds the location after the current position.\n     * If the currentposition is not the end of the list then the elements\n     * between the current element and the end of the list will be discarded.\n     * <p>\n     * Does nothing if the new location matches the current location.\n     * <br/>\n     * If this nr of locations becomes larger then the historySize then the\n     * first item(s) will be removed.\n     * v\n     *\n     * @param location d\n     */\n    public void addLocation(Location location) {\n        // do nothing if the new location matches the current location\n        if (!(locations.isEmpty()) &&\n                location.getHref().equals(locations.get(currentPos).getHref())) {\n            return;\n        }\n        currentPos++;\n        if (currentPos != currentSize) {\n            locations.set(currentPos, location);\n        } else {\n            locations.add(location);\n            checkHistorySize();\n        }\n        currentSize = currentPos + 1;\n    }\n\n    /**\n     * Removes all elements that are too much for the maxHistorySize\n     * out of the history.\n     */\n    private void checkHistorySize() {\n        while (locations.size() > maxHistorySize) {\n            locations.remove(0);\n            currentSize--;\n            currentPos--;\n        }\n    }\n\n    public void addLocation(String href) {\n        addLocation(new Location(href));\n    }\n\n    private String getLocationHref(int pos) {\n        if (pos < 0 || pos >= locations.size()) {\n            return null;\n        }\n        return locations.get(currentPos).getHref();\n    }\n\n    /**\n     * Moves the current positions delta positions.\n     * <p>\n     * move(-1) to go one position back in history.<br/>\n     * move(1) to go one position forward.<br/>发\n     *\n     * @param delta f\n     * @return Whether we actually moved. If the requested value is illegal\n     * it will return false, true otherwise.\n     */\n    public boolean move(int delta) {\n        if (((currentPos + delta) < 0)\n                || ((currentPos + delta) >= currentSize)) {\n            return false;\n        }\n        currentPos += delta;\n        navigator.gotoResource(getLocationHref(currentPos), this);\n        return true;\n    }\n\n\n    /**\n     * If this is not the source of the navigationEvent then the addLocation\n     * will be called with the href of the currentResource in the navigationEvent.\n     */\n    @Override\n    public void navigationPerformed(NavigationEvent navigationEvent) {\n        if (this == navigationEvent.getSource()) {\n            return;\n        }\n        if (navigationEvent.getCurrentResource() == null) {\n            return;\n        }\n\n        if ((System.currentTimeMillis() - this.lastUpdateTime) > historyWaitTime) {\n            // if the user scrolled rapidly through the pages then the last page\n            // will not be added to the history. We fix that here:\n            addLocation(navigationEvent.getOldResource());\n\n            addLocation(navigationEvent.getCurrentResource().getHref());\n        }\n        lastUpdateTime = System.currentTimeMillis();\n    }\n\n    public String getCurrentHref() {\n        if (currentPos < 0 || currentPos >= locations.size()) {\n            return null;\n        }\n        return locations.get(currentPos).getHref();\n    }\n\n    public void setMaxHistorySize(int maxHistorySize) {\n        this.maxHistorySize = maxHistorySize;\n    }\n\n    public int getMaxHistorySize() {\n        return maxHistorySize;\n    }\n}\n"
  },
  {
    "path": "modules/book/src/main/java/me/ag2s/epublib/browsersupport/Navigator.java",
    "content": "package me.ag2s.epublib.browsersupport;\n\nimport java.io.Serializable;\nimport java.util.ArrayList;\nimport java.util.List;\n\nimport me.ag2s.epublib.domain.EpubBook;\nimport me.ag2s.epublib.domain.Resource;\n\n/**\n * A helper class for epub browser applications.\n * <p>\n * It helps moving from one resource to the other, from one resource\n * to the other and keeping other elements of the application up-to-date\n * by calling the NavigationEventListeners.\n *\n * @author paul\n */\npublic class Navigator implements Serializable {\n\n    private static final long serialVersionUID = 1076126986424925474L;\n    private EpubBook book;\n    private int currentSpinePos;\n    private Resource currentResource;\n    private int currentPagePos;\n    private String currentFragmentId;\n\n    private final List<NavigationEventListener> eventListeners = new ArrayList<>();\n\n    public Navigator() {\n        this(null);\n    }\n\n    public Navigator(EpubBook book) {\n        this.book = book;\n        this.currentSpinePos = 0;\n        if (book != null) {\n            this.currentResource = book.getCoverPage();\n        }\n        this.currentPagePos = 0;\n    }\n\n    private synchronized void handleEventListeners(\n            NavigationEvent navigationEvent) {\n        for (int i = 0; i < eventListeners.size(); i++) {\n            NavigationEventListener navigationEventListener = eventListeners.get(i);\n            navigationEventListener.navigationPerformed(navigationEvent);\n        }\n    }\n\n    public boolean addNavigationEventListener(\n            NavigationEventListener navigationEventListener) {\n        return this.eventListeners.add(navigationEventListener);\n    }\n\n    public boolean removeNavigationEventListener(\n            NavigationEventListener navigationEventListener) {\n        return this.eventListeners.remove(navigationEventListener);\n    }\n\n    public int gotoFirstSpineSection(Object source) {\n        return gotoSpineSection(0, source);\n    }\n\n    public int gotoPreviousSpineSection(Object source) {\n        return gotoPreviousSpineSection(0, source);\n    }\n\n    public int gotoPreviousSpineSection(int pagePos, Object source) {\n        if (currentSpinePos < 0) {\n            return gotoSpineSection(0, pagePos, source);\n        } else {\n            return gotoSpineSection(currentSpinePos - 1, pagePos, source);\n        }\n    }\n\n    public boolean hasNextSpineSection() {\n        return (currentSpinePos < (book.getSpine().size() - 1));\n    }\n\n    public boolean hasPreviousSpineSection() {\n        return (currentSpinePos > 0);\n    }\n\n    public int gotoNextSpineSection(Object source) {\n        if (currentSpinePos < 0) {\n            return gotoSpineSection(0, source);\n        } else {\n            return gotoSpineSection(currentSpinePos + 1, source);\n        }\n    }\n\n    public int gotoResource(String resourceHref, Object source) {\n        Resource resource = book.getResources().getByHref(resourceHref);\n        return gotoResource(resource, source);\n    }\n\n\n    public int gotoResource(Resource resource, Object source) {\n        return gotoResource(resource, 0, null, source);\n    }\n\n    public int gotoResource(Resource resource, String fragmentId, Object source) {\n        return gotoResource(resource, 0, fragmentId, source);\n    }\n\n    public int gotoResource(Resource resource, int pagePos, Object source) {\n        return gotoResource(resource, pagePos, null, source);\n    }\n\n    public int gotoResource(Resource resource, int pagePos, String fragmentId,\n                            Object source) {\n        if (resource == null) {\n            return -1;\n        }\n        NavigationEvent navigationEvent = new NavigationEvent(source, this);\n        this.currentResource = resource;\n        this.currentSpinePos = book.getSpine().getResourceIndex(currentResource);\n        this.currentPagePos = pagePos;\n        this.currentFragmentId = fragmentId;\n        handleEventListeners(navigationEvent);\n\n        return currentSpinePos;\n    }\n\n    public int gotoResourceId(String resourceId, Object source) {\n        return gotoSpineSection(book.getSpine().findFirstResourceById(resourceId),\n                source);\n    }\n\n    public int gotoSpineSection(int newSpinePos, Object source) {\n        return gotoSpineSection(newSpinePos, 0, source);\n    }\n\n    /**\n     * Go to a specific section.\n     * Illegal spine positions are silently ignored.\n     *\n     * @param newSpinePos f\n     * @param source      f\n     * @return The current position within the spine\n     */\n    public int gotoSpineSection(int newSpinePos, int newPagePos, Object source) {\n        if (newSpinePos == currentSpinePos) {\n            return currentSpinePos;\n        }\n        if (newSpinePos < 0 || newSpinePos >= book.getSpine().size()) {\n            return currentSpinePos;\n        }\n        NavigationEvent navigationEvent = new NavigationEvent(source, this);\n        currentSpinePos = newSpinePos;\n        currentPagePos = newPagePos;\n        currentResource = book.getSpine().getResource(currentSpinePos);\n        handleEventListeners(navigationEvent);\n        return currentSpinePos;\n    }\n\n    public int gotoLastSpineSection(Object source) {\n        return gotoSpineSection(book.getSpine().size() - 1, source);\n    }\n\n    public void gotoBook(EpubBook book, Object source) {\n        NavigationEvent navigationEvent = new NavigationEvent(source, this);\n        this.book = book;\n        this.currentFragmentId = null;\n        this.currentPagePos = 0;\n        this.currentResource = null;\n        this.currentSpinePos = book.getSpine().getResourceIndex(currentResource);\n        handleEventListeners(navigationEvent);\n    }\n\n    /**\n     * The current position within the spine.\n     *\n     * @return something &lt; 0 if the current position is not within the spine.\n     */\n    public int getCurrentSpinePos() {\n        return currentSpinePos;\n    }\n\n    public Resource getCurrentResource() {\n        return currentResource;\n    }\n\n    /**\n     * Sets the current index and resource without calling the eventlisteners.\n     * <p>\n     * If you want the eventListeners called use gotoSection(index);\n     *\n     * @param currentIndex f\n     */\n    public void setCurrentSpinePos(int currentIndex) {\n        this.currentSpinePos = currentIndex;\n        this.currentResource = book.getSpine().getResource(currentIndex);\n    }\n\n    public EpubBook getBook() {\n        return book;\n    }\n\n    /**\n     * Sets the current index and resource without calling the eventlisteners.\n     * <p>\n     * If you want the eventListeners called use gotoSection(index);\n     */\n    public int setCurrentResource(Resource currentResource) {\n        this.currentSpinePos = book.getSpine().getResourceIndex(currentResource);\n        this.currentResource = currentResource;\n        return currentSpinePos;\n    }\n\n    public String getCurrentFragmentId() {\n        return currentFragmentId;\n    }\n\n    public int getCurrentSectionPos() {\n        return currentPagePos;\n    }\n}\n"
  },
  {
    "path": "modules/book/src/main/java/me/ag2s/epublib/browsersupport/package-info.java",
    "content": "/**\n * Provides classes that help make an epub reader application.\n * <p>\n * These classes have no dependencies on graphic toolkits, they're purely\n * to help with the browsing/navigation logic.\n */\npackage me.ag2s.epublib.browsersupport;\n"
  },
  {
    "path": "modules/book/src/main/java/me/ag2s/epublib/domain/Author.java",
    "content": "package me.ag2s.epublib.domain;\n\n\nimport java.io.Serializable;\n\nimport me.ag2s.epublib.util.StringUtil;\n\n/**\n * Represents one of the authors of the book\n *\n * @author paul\n */\npublic class Author implements Serializable {\n\n    private static final long serialVersionUID = 6663408501416574200L;\n\n    private String firstname;\n    private String lastname;\n    private Relator relator = Relator.AUTHOR;\n\n    public Author(String singleName) {\n        this(\"\", singleName);\n    }\n\n    public Author(String firstname, String lastname) {\n        this.firstname = firstname;\n        this.lastname = lastname;\n    }\n\n    public String getFirstname() {\n        return firstname;\n    }\n\n    public void setFirstname(String firstname) {\n        this.firstname = firstname;\n    }\n\n    public String getLastname() {\n        return lastname;\n    }\n\n    public void setLastname(String lastname) {\n        this.lastname = lastname;\n    }\n\n\n    @Override\n    @SuppressWarnings(\"NullableProblems\")\n    public String toString() {\n        return this.lastname + \", \" + this.firstname;\n    }\n\n    public int hashCode() {\n        return StringUtil.hashCode(firstname, lastname);\n    }\n\n    public boolean equals(Object authorObject) {\n        if (!(authorObject instanceof Author)) {\n            return false;\n        }\n        Author other = (Author) authorObject;\n        return StringUtil.equals(firstname, other.firstname)\n                && StringUtil.equals(lastname, other.lastname);\n    }\n\n    /**\n     * 设置贡献者的角色\n     *\n     * @param code 角色编号\n     */\n\n    public void setRole(String code) {\n        Relator result = Relator.byCode(code);\n        if (result == null) {\n            result = Relator.AUTHOR;\n        }\n        this.relator = result;\n    }\n\n    public Relator getRelator() {\n        return relator;\n    }\n\n\n    public void setRelator(Relator relator) {\n        this.relator = relator;\n    }\n}\n"
  },
  {
    "path": "modules/book/src/main/java/me/ag2s/epublib/domain/Date.java",
    "content": "package me.ag2s.epublib.domain;\n\nimport java.io.Serializable;\nimport java.text.SimpleDateFormat;\nimport java.util.Locale;\n\nimport me.ag2s.epublib.epub.PackageDocumentBase;\n\n/**\n * A Date used by the book's metadata.\n * <p>\n * Examples: creation-date, modification-date, etc\n *\n * @author paul\n */\npublic class Date implements Serializable {\n\n    private static final long serialVersionUID = 7533866830395120136L;\n\n    public enum Event {\n        PUBLICATION(\"publication\"),\n        MODIFICATION(\"modification\"),\n        CREATION(\"creation\");\n\n        private final String value;\n\n        Event(String v) {\n            value = v;\n        }\n\n        public static Event fromValue(String v) {\n            for (Event c : Event.values()) {\n                if (c.value.equals(v)) {\n                    return c;\n                }\n            }\n            return null;\n        }\n\n        @Override\n        @SuppressWarnings(\"NullableProblems\")\n        public String toString() {\n            return value;\n        }\n    }\n\n\n    private Event event;\n    private String dateString;\n\n    public Date() {\n        this(new java.util.Date(), Event.CREATION);\n    }\n\n    public Date(java.util.Date date) {\n        this(date, (Event) null);\n    }\n\n    public Date(String dateString) {\n        this(dateString, (Event) null);\n    }\n\n    public Date(java.util.Date date, Event event) {\n        this((new SimpleDateFormat(PackageDocumentBase.dateFormat, Locale.US)).format(date),\n                event);\n    }\n\n    public Date(String dateString, Event event) {\n        this.dateString = dateString;\n        this.event = event;\n    }\n\n    public Date(java.util.Date date, String event) {\n        this((new SimpleDateFormat(PackageDocumentBase.dateFormat, Locale.US)).format(date),\n                event);\n    }\n\n    public Date(String dateString, String event) {\n        this(checkDate(dateString), Event.fromValue(event));\n        this.dateString = dateString;\n    }\n\n    private static String checkDate(String dateString) {\n        if (dateString == null) {\n            throw new IllegalArgumentException(\n                    \"Cannot create a date from a blank string\");\n        }\n        return dateString;\n    }\n\n    public String getValue() {\n        return dateString;\n    }\n\n    public Event getEvent() {\n        return event;\n    }\n\n    public void setEvent(Event event) {\n        this.event = event;\n    }\n\n    @Override\n    @SuppressWarnings(\"NullableProblems\")\n    public String toString() {\n        if (event == null) {\n            return dateString;\n        }\n        return \"\" + event + \":\" + dateString;\n    }\n}\n\n"
  },
  {
    "path": "modules/book/src/main/java/me/ag2s/epublib/domain/EpubBook.java",
    "content": "package me.ag2s.epublib.domain;\n\n\nimport java.io.Serializable;\nimport java.util.ArrayList;\nimport java.util.LinkedHashMap;\nimport java.util.List;\nimport java.util.Map;\n\n/**\n * Representation of a Book.\n * <p>\n * All resources of a Book (html, css, xml, fonts, images) are represented\n * as Resources. See getResources() for access to these.<br/>\n * A Book as 3 indexes into these Resources, as per the epub specification.<br/>\n * <dl>\n * <dt>Spine</dt>\n * <dd>these are the Resources to be shown when a user reads the book from\n * start to finish.</dd>\n * <dt>Table of Contents<dt>\n * <dd>The table of contents. Table of Contents references may be in a\n * different order and contain different Resources than the spine, and often do.\n * <dt>Guide</dt>\n * <dd>The Guide has references to a set of special Resources like the\n * cover page, the Glossary, the copyright page, etc.\n * </dl>\n * <p/>\n * The complication is that these 3 indexes may and usually do point to\n * different pages.\n * A chapter may be split up in 2 pieces to fit it in to memory. Then the\n * spine will contain both pieces, but the Table of Contents only the first.\n * <p>\n * The Content page may be in the Table of Contents, the Guide, but not\n * in the Spine.\n * Etc.\n * <p/>\n * <p>\n * Please see the illustration at: doc/schema.svg\n *\n * @author paul\n * @author jake\n */\npublic class EpubBook implements Serializable {\n\n    private static final long serialVersionUID = 2068355170895770100L;\n\n    private Resources resources = new Resources();\n    private Metadata metadata = new Metadata();\n    private Spine spine = new Spine();\n    private TableOfContents tableOfContents = new TableOfContents();\n    private final Guide guide = new Guide();\n    private Resource opfResource;\n    private Resource ncxResource;\n    private Resource coverImage;\n\n\n    private String version = \"2.0\";\n\n    public String getVersion() {\n        return version;\n    }\n\n    public void setVersion(String version) {\n        this.version = version;\n    }\n\n    public boolean isEpub3() {\n        return this.version.startsWith(\"3.\");\n    }\n\n    @SuppressWarnings(\"UnusedReturnValue\")\n    public TOCReference addSection(\n            TOCReference parentSection, String sectionTitle, Resource resource) {\n        return addSection(parentSection, sectionTitle, resource, null);\n    }\n\n    /**\n     * Adds the resource to the table of contents of the book as a child\n     * section of the given parentSection\n     *\n     * @param parentSection parentSection\n     * @param sectionTitle  sectionTitle\n     * @param resource      resource\n     * @param fragmentId    fragmentId\n     * @return The table of contents\n     */\n    public TOCReference addSection(\n            TOCReference parentSection, String sectionTitle, Resource resource,\n            String fragmentId) {\n        getResources().add(resource);\n        if (spine.findFirstResourceById(resource.getId()) < 0) {\n            spine.addSpineReference(new SpineReference(resource));\n        }\n        return parentSection.addChildSection(\n                new TOCReference(sectionTitle, resource, fragmentId));\n    }\n\n    public TOCReference addSection(String title, Resource resource) {\n        return addSection(title, resource, null);\n    }\n\n    /**\n     * Adds a resource to the book's set of resources, table of contents and\n     * if there is no resource with the id in the spine also adds it to the spine.\n     *\n     * @param title      title\n     * @param resource   resource\n     * @param fragmentId fragmentId\n     * @return The table of contents\n     */\n    public TOCReference addSection(\n            String title, Resource resource, String fragmentId) {\n        getResources().add(resource);\n        TOCReference tocReference = tableOfContents\n                .addTOCReference(new TOCReference(title, resource, fragmentId));\n        if (spine.findFirstResourceById(resource.getId()) < 0) {\n            spine.addSpineReference(new SpineReference(resource));\n        }\n        return tocReference;\n    }\n\n    @SuppressWarnings(\"unused\")\n    public void generateSpineFromTableOfContents() {\n        Spine spine = new Spine(tableOfContents);\n\n        // in case the tocResource was already found and assigned\n        spine.setTocResource(this.spine.getTocResource());\n\n        this.spine = spine;\n    }\n\n    /**\n     * The Book's metadata (titles, authors, etc)\n     *\n     * @return The Book's metadata (titles, authors, etc)\n     */\n    public Metadata getMetadata() {\n        return metadata;\n    }\n\n    public void setMetadata(Metadata metadata) {\n        this.metadata = metadata;\n    }\n\n\n    public void setResources(Resources resources) {\n        this.resources = resources;\n    }\n\n    @SuppressWarnings(\"unused\")\n    public Resource addResource(Resource resource) {\n        return resources.add(resource);\n    }\n\n    /**\n     * The collection of all images, chapters, sections, xhtml files,\n     * stylesheets, etc that make up the book.\n     *\n     * @return The collection of all images, chapters, sections, xhtml files,\n     * stylesheets, etc that make up the book.\n     */\n    public Resources getResources() {\n        return resources;\n    }\n\n\n    /**\n     * The sections of the book that should be shown if a user reads the book\n     * from start to finish.\n     *\n     * @return The Spine\n     */\n    public Spine getSpine() {\n        return spine;\n    }\n\n\n    public void setSpine(Spine spine) {\n        this.spine = spine;\n    }\n\n\n    /**\n     * The Table of Contents of the book.\n     *\n     * @return The Table of Contents of the book.\n     */\n    public TableOfContents getTableOfContents() {\n        return tableOfContents;\n    }\n\n\n    public void setTableOfContents(TableOfContents tableOfContents) {\n        this.tableOfContents = tableOfContents;\n    }\n\n    /**\n     * The book's cover page as a Resource.\n     * An XHTML document containing a link to the cover image.\n     *\n     * @return The book's cover page as a Resource\n     */\n    public Resource getCoverPage() {\n        Resource coverPage = guide.getCoverPage();\n        if (coverPage == null) {\n            coverPage = spine.getResource(0);\n        }\n        return coverPage;\n    }\n\n\n    public void setCoverPage(Resource coverPage) {\n        if (coverPage == null) {\n            return;\n        }\n        if (resources.notContainsByHref(coverPage.getHref())) {\n            resources.add(coverPage);\n        }\n        guide.setCoverPage(coverPage);\n    }\n\n    /**\n     * Gets the first non-blank title from the book's metadata.\n     *\n     * @return the first non-blank title from the book's metadata.\n     */\n    public String getTitle() {\n        return getMetadata().getFirstTitle();\n    }\n\n\n    /**\n     * The book's cover image.\n     *\n     * @return The book's cover image.\n     */\n    public Resource getCoverImage() {\n        return coverImage;\n    }\n\n    public void setCoverImage(Resource coverImage) {\n        if (coverImage == null) {\n            return;\n        }\n        if (resources.notContainsByHref(coverImage.getHref())) {\n            resources.add(coverImage);\n        }\n        this.coverImage = coverImage;\n    }\n\n    /**\n     * The guide; contains references to special sections of the book like\n     * colophon, glossary, etc.\n     *\n     * @return The guide; contains references to special sections of the book\n     * like colophon, glossary, etc.\n     */\n    public Guide getGuide() {\n        return guide;\n    }\n\n    /**\n     * All Resources of the Book that can be reached via the Spine, the\n     * TableOfContents or the Guide.\n     * <p/>\n     * Consists of a list of \"reachable\" resources:\n     * <ul>\n     * <li>The coverpage</li>\n     * <li>The resources of the Spine that are not already in the result</li>\n     * <li>The resources of the Table of Contents that are not already in the\n     * result</li>\n     * <li>The resources of the Guide that are not already in the result</li>\n     * </ul>\n     * To get all html files that make up the epub file use\n     * {@link #getResources()}\n     *\n     * @return All Resources of the Book that can be reached via the Spine,\n     * the TableOfContents or the Guide.\n     */\n    public List<Resource> getContents() {\n        Map<String, Resource> result = new LinkedHashMap<>();\n        addToContentsResult(getCoverPage(), result);\n\n        for (SpineReference spineReference : getSpine().getSpineReferences()) {\n            addToContentsResult(spineReference.getResource(), result);\n        }\n\n        for (Resource resource : getTableOfContents().getAllUniqueResources()) {\n            addToContentsResult(resource, result);\n        }\n\n        for (GuideReference guideReference : getGuide().getReferences()) {\n            addToContentsResult(guideReference.getResource(), result);\n        }\n\n        return new ArrayList<>(result.values());\n    }\n\n    private static void addToContentsResult(Resource resource,\n                                            Map<String, Resource> allReachableResources) {\n        if (resource != null && (!allReachableResources\n                .containsKey(resource.getHref()))) {\n            allReachableResources.put(resource.getHref(), resource);\n        }\n    }\n\n    public Resource getOpfResource() {\n        return opfResource;\n    }\n\n    public void setOpfResource(Resource opfResource) {\n        this.opfResource = opfResource;\n    }\n\n    public void setNcxResource(Resource ncxResource) {\n        this.ncxResource = ncxResource;\n    }\n\n    public Resource getNcxResource() {\n        return ncxResource;\n    }\n}\n\n"
  },
  {
    "path": "modules/book/src/main/java/me/ag2s/epublib/domain/EpubResourceProvider.java",
    "content": "package me.ag2s.epublib.domain;\n\nimport androidx.annotation.NonNull;\n\nimport java.io.IOException;\nimport java.io.InputStream;\n\nimport me.ag2s.epublib.util.zip.ZipEntryWrapper;\nimport me.ag2s.epublib.util.zip.ZipFileWrapper;\n\n/**\n * @author jake\n */\npublic class EpubResourceProvider implements LazyResourceProvider {\n\n\n    private final ZipFileWrapper zipFileWrapper;\n\n\n    public EpubResourceProvider(ZipFileWrapper zipFileWrapper) {\n        this.zipFileWrapper = zipFileWrapper;\n    }\n\n\n    @Override\n    public InputStream getResourceStream(@NonNull String href) throws IOException {\n\n        //ZipFile zipFile = new ZipFile(epubFilename);\n        ZipEntryWrapper zipEntry = zipFileWrapper.getEntry(href);\n        if (zipEntry == null) {\n            //zipFile.close();\n            throw new IllegalStateException(\n                    \"Cannot find entry \" + href + \" in epub file \" + zipFileWrapper);\n        }\n        return new ResourceInputStream(zipFileWrapper.getInputStream(zipEntry));\n    }\n}\n"
  },
  {
    "path": "modules/book/src/main/java/me/ag2s/epublib/domain/FileResourceProvider.java",
    "content": "package me.ag2s.epublib.domain;\n\nimport java.io.File;\nimport java.io.FileInputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\n\n/**\n * 用于创建epub，添加大文件（如大量图片）时容易OOM，使用LazyResource，避免OOM.\n */\n\npublic class FileResourceProvider implements LazyResourceProvider {\n    //需要导入资源的父目录\n    String dir;\n\n    /**\n     * 创建一个文件夹里面文件夹的LazyResourceProvider，用于LazyResource。\n     *\n     * @param parentDir 文件的目录\n     */\n    public FileResourceProvider(String parentDir) {\n        this.dir = parentDir;\n    }\n\n    /**\n     * 创建一个文件夹里面文件夹的LazyResourceProvider，用于LazyResource。\n     *\n     * @param parentFile 文件夹\n     */\n    @SuppressWarnings(\"unused\")\n    public FileResourceProvider(File parentFile) {\n        this.dir = parentFile.getPath();\n    }\n\n    /**\n     * 根据子文件名href,再父目录下读取文件获取FileInputStream\n     *\n     * @param href 子文件名href\n     * @return 对应href的FileInputStream\n     * @throws IOException 抛出IOException\n     */\n    @Override\n    public InputStream getResourceStream(String href) throws IOException {\n        return new FileInputStream(new File(dir, href));\n    }\n}\n"
  },
  {
    "path": "modules/book/src/main/java/me/ag2s/epublib/domain/Guide.java",
    "content": "package me.ag2s.epublib.domain;\n\nimport java.io.Serializable;\nimport java.util.ArrayList;\nimport java.util.List;\n\n/**\n * The guide is a selection of special pages of the book.\n * Examples of these are the cover, list of illustrations, etc.\n * <p>\n * It is an optional part of an epub, and support for the various types\n * of references varies by reader.\n * <p>\n * The only part of this that is heavily used is the cover page.\n *\n * @author paul\n */\npublic class Guide implements Serializable {\n\n    /**\n     *\n     */\n    private static final long serialVersionUID = -6256645339915751189L;\n\n    public static final String DEFAULT_COVER_TITLE = GuideReference.COVER;\n\n    private List<GuideReference> references = new ArrayList<>();\n    private static final int COVERPAGE_NOT_FOUND = -1;\n    private static final int COVERPAGE_UNITIALIZED = -2;\n\n    private int coverPageIndex = -1;\n\n    public List<GuideReference> getReferences() {\n        return references;\n    }\n\n    public void setReferences(List<GuideReference> references) {\n        this.references = references;\n        uncheckCoverPage();\n    }\n\n    private void uncheckCoverPage() {\n        coverPageIndex = COVERPAGE_UNITIALIZED;\n    }\n\n    public GuideReference getCoverReference() {\n        checkCoverPage();\n        if (coverPageIndex >= 0) {\n            return references.get(coverPageIndex);\n        }\n        return null;\n    }\n\n    @SuppressWarnings(\"UnusedReturnValue\")\n    public int setCoverReference(GuideReference guideReference) {\n        if (coverPageIndex >= 0) {\n            references.set(coverPageIndex, guideReference);\n        } else {\n            references.add(0, guideReference);\n            coverPageIndex = 0;\n        }\n        return coverPageIndex;\n    }\n\n    private void checkCoverPage() {\n        if (coverPageIndex == COVERPAGE_UNITIALIZED) {\n            initCoverPage();\n        }\n    }\n\n\n    private void initCoverPage() {\n        int result = COVERPAGE_NOT_FOUND;\n        for (int i = 0; i < references.size(); i++) {\n            GuideReference guideReference = references.get(i);\n            if (guideReference.getType().equals(GuideReference.COVER)) {\n                result = i;\n                break;\n            }\n        }\n        coverPageIndex = result;\n    }\n\n    /**\n     * The coverpage of the book.\n     *\n     * @return The coverpage of the book.\n     */\n    public Resource getCoverPage() {\n        GuideReference guideReference = getCoverReference();\n        if (guideReference == null) {\n            return null;\n        }\n        return guideReference.getResource();\n    }\n\n    public void setCoverPage(Resource coverPage) {\n        GuideReference coverpageGuideReference = new GuideReference(coverPage,\n                GuideReference.COVER, DEFAULT_COVER_TITLE);\n        setCoverReference(coverpageGuideReference);\n    }\n\n    @SuppressWarnings(\"UnusedReturnValue\")\n    public ResourceReference addReference(GuideReference reference) {\n        this.references.add(reference);\n        uncheckCoverPage();\n        return reference;\n    }\n\n    /**\n     * A list of all GuideReferences that have the given\n     * referenceTypeName (ignoring case).\n     *\n     * @param referenceTypeName referenceTypeName\n     * @return A list of all GuideReferences that have the given\n     * referenceTypeName (ignoring case).\n     */\n    public List<GuideReference> getGuideReferencesByType(\n            String referenceTypeName) {\n        List<GuideReference> result = new ArrayList<>();\n        for (GuideReference guideReference : references) {\n            if (referenceTypeName.equalsIgnoreCase(guideReference.getType())) {\n                result.add(guideReference);\n            }\n        }\n        return result;\n    }\n}\n"
  },
  {
    "path": "modules/book/src/main/java/me/ag2s/epublib/domain/GuideReference.java",
    "content": "package me.ag2s.epublib.domain;\n\nimport java.io.Serializable;\n\nimport me.ag2s.epublib.util.StringUtil;\n\n\n/**\n * These are references to elements of the book's guide.\n *\n * @author paul\n * @see Guide\n */\npublic class GuideReference extends TitledResourceReference\n        implements Serializable {\n\n    private static final long serialVersionUID = -316179702440631834L;\n\n    /**\n     * the book cover(s), jacket information, etc.\n     */\n    public static final String COVER = \"cover\";\n\n    /**\n     * human-readable page with title, author, publisher, and other metadata\n     */\n    public static String TITLE_PAGE = \"title-page\";\n\n    /**\n     * Human-readable table of contents.\n     * Not to be confused the epub file table of contents\n     */\n    public static String TOC = \"toc\";\n\n    /**\n     * back-of-book style index\n     */\n    public static String INDEX = \"index\";\n    public static String GLOSSARY = \"glossary\";\n    public static String ACKNOWLEDGEMENTS = \"acknowledgements\";\n    public static String BIBLIOGRAPHY = \"bibliography\";\n    public static String COLOPHON = \"colophon\";\n    public static String COPYRIGHT_PAGE = \"copyright-page\";\n    public static String DEDICATION = \"dedication\";\n\n    /**\n     * an epigraph is a phrase, quotation, or poem that is set at the\n     * beginning of a document or component.\n     * <p>\n     * source: http://en.wikipedia.org/wiki/Epigraph_%28literature%29\n     */\n    public static String EPIGRAPH = \"epigraph\";\n\n    public static String FOREWORD = \"foreword\";\n\n    /**\n     * list of illustrations\n     */\n    public static String LOI = \"loi\";\n\n    /**\n     * list of tables\n     */\n    public static String LOT = \"lot\";\n    public static String NOTES = \"notes\";\n    public static String PREFACE = \"preface\";\n\n    /**\n     * A page of content (e.g. \"Chapter 1\")\n     */\n    public static String TEXT = \"text\";\n\n    private String type;\n\n    public GuideReference(Resource resource) {\n        this(resource, null);\n    }\n\n    public GuideReference(Resource resource, String title) {\n        super(resource, title);\n    }\n\n    public GuideReference(Resource resource, String type, String title) {\n        this(resource, type, title, null);\n    }\n\n    public GuideReference(Resource resource, String type, String title,\n                          String fragmentId) {\n        super(resource, title, fragmentId);\n        this.type = StringUtil.isNotBlank(type) ? type.toLowerCase() : null;\n    }\n\n    public String getType() {\n        return type;\n    }\n\n    public void setType(String type) {\n        this.type = type;\n    }\n}\n"
  },
  {
    "path": "modules/book/src/main/java/me/ag2s/epublib/domain/Identifier.java",
    "content": "package me.ag2s.epublib.domain;\n\nimport java.io.Serializable;\nimport java.util.List;\nimport java.util.UUID;\n\nimport me.ag2s.epublib.util.StringUtil;\n\n/**\n * A Book's identifier.\n * <p>\n * Defaults to a random UUID and scheme \"UUID\"\n *\n * @author paul\n */\npublic class Identifier implements Serializable {\n\n    private static final long serialVersionUID = 955949951416391810L;\n\n    @SuppressWarnings(\"unused\")\n    public interface Scheme {\n\n        String UUID = \"UUID\";\n        String ISBN = \"ISBN\";\n        String URL = \"URL\";\n        String URI = \"URI\";\n    }\n\n    private boolean bookId = false;\n    private String scheme;\n    private String value;\n\n    /**\n     * Creates an Identifier with as value a random UUID and scheme \"UUID\"\n     */\n    public Identifier() {\n        this(Scheme.UUID, UUID.randomUUID().toString());\n    }\n\n\n    public Identifier(String scheme, String value) {\n        this.scheme = scheme;\n        this.value = value;\n    }\n\n    /**\n     * The first identifier for which the bookId is true is made the\n     * bookId identifier.\n     * <p>\n     * If no identifier has bookId == true then the first bookId identifier\n     * is written as the primary.\n     *\n     * @param identifiers i\n     * @return The first identifier for which the bookId is true is made\n     * the bookId identifier.\n     */\n    public static Identifier getBookIdIdentifier(List<Identifier> identifiers) {\n        if (identifiers == null || identifiers.isEmpty()) {\n            return null;\n        }\n\n        Identifier result = null;\n        for (Identifier identifier : identifiers) {\n            if (identifier.isBookId()) {\n                result = identifier;\n                break;\n            }\n        }\n\n        if (result == null) {\n            result = identifiers.get(0);\n        }\n\n        return result;\n    }\n\n    public String getScheme() {\n        return scheme;\n    }\n\n    public void setScheme(String scheme) {\n        this.scheme = scheme;\n    }\n\n    public String getValue() {\n        return value;\n    }\n\n    public void setValue(String value) {\n        this.value = value;\n    }\n\n\n    public void setBookId(boolean bookId) {\n        this.bookId = bookId;\n    }\n\n\n    /**\n     * This bookId property allows the book creator to add multiple ids and\n     * tell the epubwriter which one to write out as the bookId.\n     * <p>\n     * The Dublin Core metadata spec allows multiple identifiers for a Book.\n     * The epub spec requires exactly one identifier to be marked as the book id.\n     *\n     * @return whether this is the unique book id.\n     */\n    public boolean isBookId() {\n        return bookId;\n    }\n\n    public int hashCode() {\n        return StringUtil.defaultIfNull(scheme).hashCode() ^ StringUtil\n                .defaultIfNull(value).hashCode();\n    }\n\n    public boolean equals(Object otherIdentifier) {\n        if (!(otherIdentifier instanceof Identifier)) {\n            return false;\n        }\n        return StringUtil.equals(scheme, ((Identifier) otherIdentifier).scheme)\n                && StringUtil.equals(value, ((Identifier) otherIdentifier).value);\n    }\n\n    @SuppressWarnings(\"NullableProblems\")\n    @Override\n    public String toString() {\n        if (StringUtil.isBlank(scheme)) {\n            return \"\" + value;\n        }\n        return \"\" + scheme + \":\" + value;\n    }\n}\n"
  },
  {
    "path": "modules/book/src/main/java/me/ag2s/epublib/domain/LazyResource.java",
    "content": "package me.ag2s.epublib.domain;\n\nimport android.util.Log;\n\nimport java.io.ByteArrayInputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\n\nimport me.ag2s.epublib.util.IOUtil;\n\n/**\n * A Resource that loads its data only on-demand from a EPUB book file.\n * This way larger books can fit into memory and can be opened faster.\n */\npublic class LazyResource extends Resource {\n\n    private static final long serialVersionUID = 5089400472352002866L;\n    private final String TAG = getClass().getName();\n\n    private final LazyResourceProvider resourceProvider;\n    private final long cachedSize;\n\n    /**\n     * Creates a lazy resource, when the size is unknown.\n     *\n     * @param resourceProvider The resource provider loads data on demand.\n     * @param href             The resource's href within the epub.\n     */\n    public LazyResource(LazyResourceProvider resourceProvider, String href) {\n        this(resourceProvider, -1, href);\n    }\n\n    public LazyResource(LazyResourceProvider resourceProvider, String href, String originalHref) {\n        this(resourceProvider, -1, href, originalHref);\n    }\n\n    /**\n     * Creates a Lazy resource, by not actually loading the data for this entry.\n     * <p>\n     * The data will be loaded on the first call to getData()\n     *\n     * @param resourceProvider The resource provider loads data on demand.\n     * @param size             The size of this resource.\n     * @param href             The resource's href within the epub.\n     */\n    public LazyResource(\n            LazyResourceProvider resourceProvider, long size, String href) {\n        super(null, null, href, MediaTypes.determineMediaType(href));\n        this.resourceProvider = resourceProvider;\n        this.cachedSize = size;\n    }\n\n    public LazyResource(\n            LazyResourceProvider resourceProvider, long size, String href, String originalHref) {\n        super(null, null, href, originalHref, MediaTypes.determineMediaType(href));\n        this.resourceProvider = resourceProvider;\n        this.cachedSize = size;\n    }\n\n    /**\n     * Gets the contents of the Resource as an InputStream.\n     *\n     * @return The contents of the Resource.\n     * @throws IOException IOException\n     */\n    public InputStream getInputStream() throws IOException {\n        if (isInitialized()) {\n            return new ByteArrayInputStream(getData());\n        } else {\n            return resourceProvider.getResourceStream(this.originalHref);\n        }\n    }\n\n    /**\n     * Initializes the resource by loading its data into memory.\n     *\n     * @throws IOException IOException\n     */\n    public void initialize() throws IOException {\n        getData();\n    }\n\n    /**\n     * The contents of the resource as a byte[]\n     * <p>\n     * If this resource was lazy-loaded and the data was not yet loaded,\n     * it will be loaded into memory at this point.\n     * This included opening the zip file, so expect a first load to be slow.\n     *\n     * @return The contents of the resource\n     */\n    public byte[] getData() throws IOException {\n\n        if (data == null) {\n\n            Log.d(TAG, \"Initializing lazy resource: \" + this.getHref());\n\n            InputStream in = resourceProvider.getResourceStream(this.originalHref);\n            byte[] readData = IOUtil.toByteArray(in, (int) this.cachedSize);\n            if (readData == null) {\n                throw new IOException(\n                        \"Could not load the contents of resource: \" + this.getHref());\n            } else {\n                this.data = readData;\n            }\n\n            in.close();\n        }\n\n        return data;\n    }\n\n    /**\n     * Tells this resource to release its cached data.\n     * <p>\n     * If this resource was not lazy-loaded, this is a no-op.\n     */\n    public void close() {\n        if (this.resourceProvider != null) {\n            this.data = null;\n        }\n    }\n\n    /**\n     * Returns if the data for this resource has been loaded into memory.\n     *\n     * @return true if data was loaded.\n     */\n    public boolean isInitialized() {\n        return data != null;\n    }\n\n    /**\n     * Returns the size of this resource in bytes.\n     *\n     * @return the size.\n     */\n    public long getSize() {\n        if (data != null) {\n            return data.length;\n        }\n\n        return cachedSize;\n    }\n}\n"
  },
  {
    "path": "modules/book/src/main/java/me/ag2s/epublib/domain/LazyResourceProvider.java",
    "content": "package me.ag2s.epublib.domain;\n\nimport java.io.IOException;\nimport java.io.InputStream;\n\n/**\n * @author jake\n */\npublic interface LazyResourceProvider {\n\n    InputStream getResourceStream(String href) throws IOException;\n}\n"
  },
  {
    "path": "modules/book/src/main/java/me/ag2s/epublib/domain/ManifestItemProperties.java",
    "content": "package me.ag2s.epublib.domain;\n\n@SuppressWarnings(\"unused\")\npublic enum ManifestItemProperties implements ManifestProperties {\n    COVER_IMAGE(\"cover-image\"),\n    MATHML(\"mathml\"),\n    NAV(\"nav\"),\n    REMOTE_RESOURCES(\"remote-resources\"),\n    SCRIPTED(\"scripted\"),\n    SVG(\"svg\"),\n    SWITCH(\"switch\");\n\n    private final String name;\n\n    ManifestItemProperties(String name) {\n        this.name = name;\n    }\n\n    public String getName() {\n        return name;\n    }\n}\n"
  },
  {
    "path": "modules/book/src/main/java/me/ag2s/epublib/domain/ManifestItemRefProperties.java",
    "content": "package me.ag2s.epublib.domain;\n\n@SuppressWarnings(\"unused\")\npublic enum ManifestItemRefProperties implements ManifestProperties {\n    PAGE_SPREAD_LEFT(\"page-spread-left\"),\n    PAGE_SPREAD_RIGHT(\"page-spread-right\");\n\n    private final String name;\n\n    ManifestItemRefProperties(String name) {\n        this.name = name;\n    }\n\n    public String getName() {\n        return name;\n    }\n}\n"
  },
  {
    "path": "modules/book/src/main/java/me/ag2s/epublib/domain/ManifestProperties.java",
    "content": "package me.ag2s.epublib.domain;\n\npublic interface ManifestProperties {\n\n    String getName();\n}\n"
  },
  {
    "path": "modules/book/src/main/java/me/ag2s/epublib/domain/MediaType.java",
    "content": "package me.ag2s.epublib.domain;\n\nimport java.io.Serializable;\nimport java.util.Arrays;\nimport java.util.Collection;\n\n/**\n * MediaType is used to tell the type of content a resource is.\n * <p>\n * Examples of mediatypes are image/gif, text/css and application/xhtml+xml\n * <p>\n * All allowed mediaTypes are maintained bye the MediaTypeService.\n *\n * @author paul\n * @see MediaTypes\n */\npublic class MediaType implements Serializable {\n\n    private static final long serialVersionUID = -7256091153727506788L;\n    private final String name;\n    private final String defaultExtension;\n    private final Collection<String> extensions;\n\n    public MediaType(String name, String defaultExtension) {\n        this(name, defaultExtension, new String[]{defaultExtension});\n    }\n\n    public MediaType(String name, String defaultExtension,\n                     String[] extensions) {\n        this(name, defaultExtension, Arrays.asList(extensions));\n    }\n\n    public int hashCode() {\n        if (name == null) {\n            return 0;\n        }\n        return name.hashCode();\n    }\n\n    public MediaType(String name, String defaultExtension,\n                     Collection<String> mextensions) {\n        super();\n        this.name = name;\n        this.defaultExtension = defaultExtension;\n        this.extensions = mextensions;\n    }\n\n    public String getName() {\n        return name;\n    }\n\n\n    public String getDefaultExtension() {\n        return defaultExtension;\n    }\n\n\n    public Collection<String> getExtensions() {\n        return extensions;\n    }\n\n    public boolean equals(Object otherMediaType) {\n        if (!(otherMediaType instanceof MediaType)) {\n            return false;\n        }\n        return name.equals(((MediaType) otherMediaType).getName());\n    }\n\n    @SuppressWarnings(\"NullableProblems\")\n    public String toString() {\n        return name;\n    }\n}\n"
  },
  {
    "path": "modules/book/src/main/java/me/ag2s/epublib/domain/MediaTypes.java",
    "content": "package me.ag2s.epublib.domain;\n\nimport java.util.LinkedHashMap;\nimport java.util.Map;\n\nimport me.ag2s.epublib.util.StringUtil;\n\n\n/**\n * Manages mediatypes that are used by epubs\n *\n * @author paul\n */\npublic class MediaTypes {\n\n    public static final MediaType XHTML = new MediaType(\"application/xhtml+xml\",\n            \".xhtml\", new String[]{\".htm\", \".html\", \".xhtml\"});\n    public static final MediaType EPUB = new MediaType(\"application/epub+zip\",\n            \".epub\");\n    public static final MediaType NCX = new MediaType(\"application/x-dtbncx+xml\",\n            \".ncx\");\n\n    public static final MediaType JAVASCRIPT = new MediaType(\"text/javascript\",\n            \".js\");\n    public static final MediaType CSS = new MediaType(\"text/css\", \".css\");\n\n    // images\n    public static final MediaType JPG = new MediaType(\"image/jpeg\", \".jpg\",\n            new String[]{\".jpg\", \".jpeg\"});\n    public static final MediaType PNG = new MediaType(\"image/png\", \".png\");\n    public static final MediaType GIF = new MediaType(\"image/gif\", \".gif\");\n\n    public static final MediaType SVG = new MediaType(\"image/svg+xml\", \".svg\");\n\n    public static final MediaType WEBP = new MediaType(\"image/webp\", \".webp\");\n\n    // fonts\n    public static final MediaType TTF = new MediaType(\n            \"application/x-truetype-font\", \".ttf\");\n    public static final MediaType OPENTYPE = new MediaType(\n            \"application/vnd.ms-opentype\", \".otf\");\n    public static final MediaType WOFF = new MediaType(\"application/font-woff\",\n            \".woff\");\n\n    // audio\n    public static final MediaType MP3 = new MediaType(\"audio/mpeg\", \".mp3\");\n    public static final MediaType OGG = new MediaType(\"audio/ogg\", \".ogg\");\n\n    // video\n    public static final MediaType MP4 = new MediaType(\"video/mp4\", \".mp4\");\n\n    public static final MediaType SMIL = new MediaType(\"application/smil+xml\",\n            \".smil\");\n    public static final MediaType XPGT = new MediaType(\n            \"application/adobe-page-template+xml\", \".xpgt\");\n    public static final MediaType PLS = new MediaType(\"application/pls+xml\",\n            \".pls\");\n    public static final MediaType UNKNOWN = new MediaType(\"application/octet-stream\", \"\");\n\n    public static final MediaType[] mediaTypes = new MediaType[]{\n            XHTML, EPUB, JPG, PNG, GIF, WEBP, CSS, SVG, TTF, NCX, XPGT, OPENTYPE, WOFF,\n            SMIL, PLS, JAVASCRIPT, MP3, MP4, OGG, UNKNOWN\n    };\n\n    public static final Map<String, MediaType> mediaTypesByName = new LinkedHashMap<>();\n\n    static {\n        for (MediaType mediaType : mediaTypes) {\n            mediaTypesByName.put(mediaType.getName(), mediaType);\n        }\n    }\n\n    public static boolean isBitmapImage(MediaType mediaType) {\n        return mediaType == JPG || mediaType == PNG || mediaType == GIF || mediaType == WEBP;\n    }\n\n    public static boolean isImage(MediaType mediaType) {\n        return mediaType == JPG || mediaType == PNG || mediaType == GIF || mediaType == SVG || mediaType == WEBP;\n    }\n\n    /**\n     * Gets the MediaType based on the file extension.\n     * Null of no matching extension found.\n     *\n     * @param filename filename\n     * @return the MediaType based on the file extension.\n     */\n    public static MediaType determineMediaType(String filename) {\n        for (MediaType mediaType : mediaTypesByName.values()) {\n            for (String extension : mediaType.getExtensions()) {\n                if (StringUtil.endsWithIgnoreCase(filename, extension)) {\n                    return mediaType;\n                }\n            }\n        }\n        return null;\n    }\n\n    public static MediaType getMediaTypeByName(String mediaTypeName) {\n        return mediaTypesByName.get(mediaTypeName);\n    }\n}\n"
  },
  {
    "path": "modules/book/src/main/java/me/ag2s/epublib/domain/Metadata.java",
    "content": "package me.ag2s.epublib.domain;\n\nimport java.io.Serializable;\nimport java.util.ArrayList;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\n\nimport javax.xml.namespace.QName;\n\nimport me.ag2s.epublib.util.StringUtil;\n\n/**\n * A Book's collection of Metadata.\n * In the future it should contain all Dublin Core attributes, for now\n * it contains a set of often-used ones.\n *\n * @author paul\n */\npublic class Metadata implements Serializable {\n\n    private static final long serialVersionUID = -2437262888962149444L;\n\n    public static final String DEFAULT_LANGUAGE = \"en\";\n\n    private boolean autoGeneratedId;//true;\n    private List<Author> authors = new ArrayList<>();\n    private List<Author> contributors = new ArrayList<>();\n    private List<Date> dates = new ArrayList<>();\n    private String language = DEFAULT_LANGUAGE;\n    private Map<QName, String> otherProperties = new HashMap<>();\n    private List<String> rights = new ArrayList<>();\n    private List<String> titles = new ArrayList<>();\n    private List<Identifier> identifiers = new ArrayList<>();\n    private List<String> subjects = new ArrayList<>();\n    private String format = MediaTypes.EPUB.getName();\n    private List<String> types = new ArrayList<>();\n    private List<String> descriptions = new ArrayList<>();\n    private List<String> publishers = new ArrayList<>();\n    private Map<String, String> metaAttributes = new HashMap<>();\n\n    public Metadata() {\n        identifiers.add(new Identifier());\n        autoGeneratedId = true;\n    }\n\n    @SuppressWarnings(\"unused\")\n    public boolean isAutoGeneratedId() {\n        return autoGeneratedId;\n    }\n\n    /**\n     * Metadata properties not hard-coded like the author, title, etc.\n     *\n     * @return Metadata properties not hard-coded like the author, title, etc.\n     */\n    public Map<QName, String> getOtherProperties() {\n        return otherProperties;\n    }\n\n    public void setOtherProperties(Map<QName, String> otherProperties) {\n        this.otherProperties = otherProperties;\n    }\n\n    @SuppressWarnings(\"unused\")\n    public Date addDate(Date date) {\n        this.dates.add(date);\n        return date;\n    }\n\n    public List<Date> getDates() {\n        return dates;\n    }\n\n    public void setDates(List<Date> dates) {\n        this.dates = dates;\n    }\n\n    @SuppressWarnings(\"UnusedReturnValue\")\n    public Author addAuthor(Author author) {\n        authors.add(author);\n        return author;\n    }\n\n    public List<Author> getAuthors() {\n        return authors;\n    }\n\n    public void setAuthors(List<Author> authors) {\n        this.authors = authors;\n    }\n\n    @SuppressWarnings(\"UnusedReturnValue\")\n    public Author addContributor(Author contributor) {\n        contributors.add(contributor);\n        return contributor;\n    }\n\n    public List<Author> getContributors() {\n        return contributors;\n    }\n\n    public void setContributors(List<Author> contributors) {\n        this.contributors = contributors;\n    }\n\n    public String getLanguage() {\n        return language;\n    }\n\n    public void setLanguage(String language) {\n        this.language = language;\n    }\n\n    public List<String> getSubjects() {\n        return subjects;\n    }\n\n    public void setSubjects(List<String> subjects) {\n        this.subjects = subjects;\n    }\n\n    public void setRights(List<String> rights) {\n        this.rights = rights;\n    }\n\n    public List<String> getRights() {\n        return rights;\n    }\n\n\n    /**\n     * Gets the first non-blank title of the book.\n     * Will return \"\" if no title found.\n     *\n     * @return the first non-blank title of the book.\n     */\n    public String getFirstTitle() {\n        if (titles == null || titles.isEmpty()) {\n            return \"\";\n        }\n        for (String title : titles) {\n            if (StringUtil.isNotBlank(title)) {\n                return title;\n            }\n        }\n        return \"\";\n    }\n\n    public String addTitle(String title) {\n        this.titles.add(title);\n        return title;\n    }\n\n    public void setTitles(List<String> titles) {\n        this.titles = titles;\n    }\n\n    public List<String> getTitles() {\n        return titles;\n    }\n\n    @SuppressWarnings(\"UnusedReturnValue\")\n    public String addPublisher(String publisher) {\n        this.publishers.add(publisher);\n        return publisher;\n    }\n\n    public void setPublishers(List<String> publishers) {\n        this.publishers = publishers;\n    }\n\n    public List<String> getPublishers() {\n        return publishers;\n    }\n\n    @SuppressWarnings(\"UnusedReturnValue\")\n    public String addDescription(String description) {\n        this.descriptions.add(description);\n        return description;\n    }\n\n    public void setDescriptions(List<String> descriptions) {\n        this.descriptions = descriptions;\n    }\n\n    public List<String> getDescriptions() {\n        return descriptions;\n    }\n\n    @SuppressWarnings(\"unused\")\n    public Identifier addIdentifier(Identifier identifier) {\n        if (autoGeneratedId && (!(identifiers.isEmpty()))) {\n            identifiers.set(0, identifier);\n        } else {\n            identifiers.add(identifier);\n        }\n        autoGeneratedId = false;\n        return identifier;\n    }\n\n    public void setIdentifiers(List<Identifier> identifiers) {\n        this.identifiers = identifiers;\n        autoGeneratedId = false;\n    }\n\n    public List<Identifier> getIdentifiers() {\n        return identifiers;\n    }\n\n    public void setFormat(String format) {\n        this.format = format;\n    }\n\n    public String getFormat() {\n        return format;\n    }\n\n    @SuppressWarnings(\"UnusedReturnValue\")\n    public String addType(String type) {\n        this.types.add(type);\n        return type;\n    }\n\n    public List<String> getTypes() {\n        return types;\n    }\n\n    public void setTypes(List<String> types) {\n        this.types = types;\n    }\n\n    @SuppressWarnings(\"unused\")\n    public String getMetaAttribute(String name) {\n        return metaAttributes.get(name);\n    }\n\n    public void setMetaAttributes(Map<String, String> metaAttributes) {\n        this.metaAttributes = metaAttributes;\n    }\n}\n"
  },
  {
    "path": "modules/book/src/main/java/me/ag2s/epublib/domain/Relator.java",
    "content": "package me.ag2s.epublib.domain;\n\n\n/**\n * A relator denotes which role a certain individual had in the creation/modification of the ebook.\n * <p>\n * Examples are 'creator', 'blurb writer', etc.\n * <p>\n * This is contains the complete Library of Concress relator list.\n *\n * @author paul\n * @see <a href=\"http://www.loc.gov/marc/relators/relaterm.html\">MARC Code List for Relators</a>\n */\npublic enum Relator {\n\n    /**\n     * Use for a person or organization who principally exhibits acting skills in a musical or dramatic presentation or entertainment.\n     */\n    ACTOR(\"act\", \"Actor\"),\n\n    /**\n     * Use for a person or organization who 1) reworks a musical composition, usually for a different medium, or 2) rewrites novels or stories for motion pictures or other audiovisual medium.\n     */\n    ADAPTER(\"adp\", \"Adapter\"),\n\n    /**\n     * Use for a person or organization that reviews, examines and interprets data or information in a specific area.\n     */\n    ANALYST(\"anl\", \"Analyst\"),\n\n    /**\n     * Use for a person or organization who draws the two-dimensional figures, manipulates the three dimensional objects and/or also programs the computer to move objects and images for the purpose of animated film processing. Animation cameras, stands, celluloid screens, transparencies and inks are some of the tools of the animator.\n     */\n    ANIMATOR(\"anm\", \"Animator\"),\n\n    /**\n     * Use for a person who writes manuscript annotations on a printed item.\n     */\n    ANNOTATOR(\"ann\", \"Annotator\"),\n\n    /**\n     * Use for a person or organization responsible for the submission of an application or who is named as eligible for the results of the processing of the application (e.g., bestowing of rights, reward, title, position).\n     */\n    APPLICANT(\"app\", \"Applicant\"),\n\n    /**\n     * Use for a person or organization who designs structures or oversees their construction.\n     */\n    ARCHITECT(\"arc\", \"Architect\"),\n\n    /**\n     * Use for a person or organization who transcribes a musical composition, usually for a different medium from that of the original; in an arrangement the musical substance remains essentially unchanged.\n     */\n    ARRANGER(\"arr\", \"Arranger\"),\n\n    /**\n     * Use for a person (e.g., a painter or sculptor) who makes copies of works of visual art.\n     */\n    ART_COPYIST(\"acp\", \"Art copyist\"),\n\n    /**\n     * Use for a person (e.g., a painter) or organization who conceives, and perhaps also implements, an original graphic design or work of art, if specific codes (e.g., [egr], [etr]) are not desired. For book illustrators, prefer Illustrator [ill].\n     */\n    ARTIST(\"art\", \"Artist\"),\n\n    /**\n     * Use for a person responsible for controlling the development of the artistic style of an entire production, including the choice of works to be presented and selection of senior production staff.\n     */\n    ARTISTIC_DIRECTOR(\"ard\", \"Artistic director\"),\n\n    /**\n     * Use for a person or organization to whom a license for printing or publishing has been transferred.\n     */\n    ASSIGNEE(\"asg\", \"Assignee\"),\n\n    /**\n     * Use for a person or organization associated with or found in an item or collection, which cannot be determined to be that of a Former owner [fmo] or other designated relator indicative of provenance.\n     */\n    ASSOCIATED_NAME(\"asn\", \"Associated name\"),\n\n    /**\n     * Use for an author, artist, etc., relating him/her to a work for which there is or once was substantial authority for designating that person as author, creator, etc. of the work.\n     */\n    ATTRIBUTED_NAME(\"att\", \"Attributed name\"),\n\n    /**\n     * Use for a person or organization in charge of the estimation and public auctioning of goods, particularly books, artistic works, etc.\n     */\n    AUCTIONEER(\"auc\", \"Auctioneer\"),\n\n    /**\n     * Use for a person or organization chiefly responsible for the intellectual or artistic content of a work, usually printed text. This term may also be used when more than one person or body bears such responsibility.\n     */\n    AUTHOR(\"aut\", \"Author\"),\n\n    /**\n     * Use for a person or organization whose work is largely quoted or extracted in works to which he or she did not contribute directly. Such quotations are found particularly in exhibition catalogs, collections of photographs, etc.\n     */\n    AUTHOR_IN_QUOTATIONS_OR_TEXT_EXTRACTS(\"aqt\",\n            \"Author in quotations or text extracts\"),\n\n    /**\n     * Use for a person or organization responsible for an afterword, postface, colophon, etc. but who is not the chief author of a work.\n     */\n    AUTHOR_OF_AFTERWORD_COLOPHON_ETC(\"aft\",\n            \"Author of afterword, colophon, etc.\"),\n\n    /**\n     * Use for a person or organization responsible for the dialog or spoken commentary for a screenplay or sound recording.\n     */\n    AUTHOR_OF_DIALOG(\"aud\", \"Author of dialog\"),\n\n    /**\n     * Use for a person or organization responsible for an introduction, preface, foreword, or other critical introductory matter, but who is not the chief author.\n     */\n    AUTHOR_OF_INTRODUCTION_ETC(\"aui\", \"Author of introduction, etc.\"),\n\n    /**\n     * Use for a person or organization responsible for a motion picture screenplay, dialog, spoken commentary, etc.\n     */\n    AUTHOR_OF_SCREENPLAY_ETC(\"aus\", \"Author of screenplay, etc.\"),\n\n    /**\n     * Use for a person or organization responsible for a work upon which the work represented by the catalog record is based. This may be appropriate for adaptations, sequels, continuations, indexes, etc.\n     */\n    BIBLIOGRAPHIC_ANTECEDENT(\"ant\", \"Bibliographic antecedent\"),\n\n    /**\n     * Use for a person or organization responsible for the binding of printed or manuscript materials.\n     */\n    BINDER(\"bnd\", \"Binder\"),\n\n    /**\n     * Use for a person or organization responsible for the binding design of a book, including the type of binding, the type of materials used, and any decorative aspects of the binding.\n     */\n    BINDING_DESIGNER(\"bdd\", \"Binding designer\"),\n\n    /**\n     * Use for the named entity responsible for writing a commendation or testimonial for a work, which appears on or within the publication itself, frequently on the back or dust jacket of print publications or on advertising material for all media.\n     */\n    BLURB_WRITER(\"blw\", \"Blurb writer\"),\n\n    /**\n     * Use for a person or organization responsible for the entire graphic design of a book, including arrangement of type and illustration, choice of materials, and process used.\n     */\n    BOOK_DESIGNER(\"bkd\", \"Book designer\"),\n\n    /**\n     * Use for a person or organization responsible for the production of books and other print media, if specific codes (e.g., [bkd], [egr], [tyd], [prt]) are not desired.\n     */\n    BOOK_PRODUCER(\"bkp\", \"Book producer\"),\n\n    /**\n     * Use for a person or organization responsible for the design of flexible covers designed for or published with a book, including the type of materials used, and any decorative aspects of the bookjacket.\n     */\n    BOOKJACKET_DESIGNER(\"bjd\", \"Bookjacket designer\"),\n\n    /**\n     * Use for a person or organization responsible for the design of a book owner's identification label that is most commonly pasted to the inside front cover of a book.\n     */\n    BOOKPLATE_DESIGNER(\"bpd\", \"Bookplate designer\"),\n\n    /**\n     * Use for a person or organization who makes books and other bibliographic materials available for purchase. Interest in the materials is primarily lucrative.\n     */\n    BOOKSELLER(\"bsl\", \"Bookseller\"),\n\n    /**\n     * Use for a person or organization who writes in an artistic hand, usually as a copyist and or engrosser.\n     */\n    CALLIGRAPHER(\"cll\", \"Calligrapher\"),\n\n    /**\n     * Use for a person or organization responsible for the creation of maps and other cartographic materials.\n     */\n    CARTOGRAPHER(\"ctg\", \"Cartographer\"),\n\n    /**\n     * Use for a censor, bowdlerizer, expurgator, etc., official or private.\n     */\n    CENSOR(\"cns\", \"Censor\"),\n\n    /**\n     * Use for a person or organization who composes or arranges dances or other movements (e.g., \"master of swords\") for a musical or dramatic presentation or entertainment.\n     */\n    CHOREOGRAPHER(\"chr\", \"Choreographer\"),\n\n    /**\n     * Use for a person or organization who is in charge of the images captured for a motion picture film. The cinematographer works under the supervision of a director, and may also be referred to as director of photography. Do not confuse with videographer.\n     */\n    CINEMATOGRAPHER(\"cng\", \"Cinematographer\"),\n\n    /**\n     * Use for a person or organization for whom another person or organization is acting.\n     */\n    CLIENT(\"cli\", \"Client\"),\n\n    /**\n     * Use for a person or organization that takes a limited part in the elaboration of a work of another person or organization that brings complements (e.g., appendices, notes) to the work.\n     */\n    COLLABORATOR(\"clb\", \"Collaborator\"),\n\n    /**\n     * Use for a person or organization who has brought together material from various sources that has been arranged, described, and cataloged as a collection. A collector is neither the creator of the material nor a person to whom manuscripts in the collection may have been addressed.\n     */\n    COLLECTOR(\"col\", \"Collector\"),\n\n    /**\n     * Use for a person or organization responsible for the production of photographic prints from film or other colloid that has ink-receptive and ink-repellent surfaces.\n     */\n    COLLOTYPER(\"clt\", \"Collotyper\"),\n\n    /**\n     * Use for the named entity responsible for applying color to drawings, prints, photographs, maps, moving images, etc.\n     */\n    COLORIST(\"clr\", \"Colorist\"),\n\n    /**\n     * Use for a person or organization who provides interpretation, analysis, or a discussion of the subject matter on a recording, motion picture, or other audiovisual medium.\n     */\n    COMMENTATOR(\"cmm\", \"Commentator\"),\n\n    /**\n     * Use for a person or organization responsible for the commentary or explanatory notes about a text. For the writer of manuscript annotations in a printed book, use Annotator [ann].\n     */\n    COMMENTATOR_FOR_WRITTEN_TEXT(\"cwt\", \"Commentator for written text\"),\n\n    /**\n     * Use for a person or organization who produces a work or publication by selecting and putting together material from the works of various persons or bodies.\n     */\n    COMPILER(\"com\", \"Compiler\"),\n\n    /**\n     * Use for the party who applies to the courts for redress, usually in an equity proceeding.\n     */\n    COMPLAINANT(\"cpl\", \"Complainant\"),\n\n    /**\n     * Use for a complainant who takes an appeal from one court or jurisdiction to another to reverse the judgment, usually in an equity proceeding.\n     */\n    COMPLAINANT_APPELLANT(\"cpt\", \"Complainant-appellant\"),\n\n    /**\n     * Use for a complainant against whom an appeal is taken from one court or jurisdiction to another to reverse the judgment, usually in an equity proceeding.\n     */\n    COMPLAINANT_APPELLEE(\"cpe\", \"Complainant-appellee\"),\n\n    /**\n     * Use for a person or organization who creates a musical work, usually a piece of music in manuscript or printed form.\n     */\n    COMPOSER(\"cmp\", \"Composer\"),\n\n    /**\n     * Use for a person or organization responsible for the creation of metal slug, or molds made of other materials, used to produce the text and images in printed matter.\n     */\n    COMPOSITOR(\"cmt\", \"Compositor\"),\n\n    /**\n     * Use for a person or organization responsible for the original idea on which a work is based, this includes the scientific author of an audio-visual item and the conceptor of an advertisement.\n     */\n    CONCEPTOR(\"ccp\", \"Conceptor\"),\n\n    /**\n     * Use for a person who directs a performing group (orchestra, chorus, opera, etc.) in a musical or dramatic presentation or entertainment.\n     */\n    CONDUCTOR(\"cnd\", \"Conductor\"),\n\n    /**\n     * Use for the named entity responsible for documenting, preserving, or treating printed or manuscript material, works of art, artifacts, or other media.\n     */\n    CONSERVATOR(\"con\", \"Conservator\"),\n\n    /**\n     * Use for a person or organization relevant to a resource, who is called upon for professional advice or services in a specialized field of knowledge or training.\n     */\n    CONSULTANT(\"csl\", \"Consultant\"),\n\n    /**\n     * Use for a person or organization relevant to a resource, who is engaged specifically to provide an intellectual overview of a strategic or operational task and by analysis, specification, or instruction, to create or propose a cost-effective course of action or solution.\n     */\n    CONSULTANT_TO_A_PROJECT(\"csp\", \"Consultant to a project\"),\n\n    /**\n     * Use for the party who opposes, resists, or disputes, in a court of law, a claim, decision, result, etc.\n     */\n    CONTESTANT(\"cos\", \"Contestant\"),\n\n    /**\n     * Use for a contestant who takes an appeal from one court of law or jurisdiction to another to reverse the judgment.\n     */\n    CONTESTANT_APPELLANT(\"cot\", \"Contestant-appellant\"),\n\n    /**\n     * Use for a contestant against whom an appeal is taken from one court of law or jurisdiction to another to reverse the judgment.\n     */\n    CONTESTANT_APPELLEE(\"coe\", \"Contestant-appellee\"),\n\n    /**\n     * Use for the party defending a claim, decision, result, etc. being opposed, resisted, or disputed in a court of law.\n     */\n    CONTESTEE(\"cts\", \"Contestee\"),\n\n    /**\n     * Use for a contestee who takes an appeal from one court or jurisdiction to another to reverse the judgment.\n     */\n    CONTESTEE_APPELLANT(\"ctt\", \"Contestee-appellant\"),\n\n    /**\n     * Use for a contestee against whom an appeal is taken from one court or jurisdiction to another to reverse the judgment.\n     */\n    CONTESTEE_APPELLEE(\"cte\", \"Contestee-appellee\"),\n\n    /**\n     * Use for a person or organization relevant to a resource, who enters into a contract with another person or organization to perform a specific task.\n     */\n    CONTRACTOR(\"ctr\", \"Contractor\"),\n\n    /**\n     * Use for a person or organization one whose work has been contributed to a larger work, such as an anthology, serial publication, or other compilation of individual works. Do not use if the sole function in relation to a work is as author, editor, compiler or translator.\n     */\n    CONTRIBUTOR(\"ctb\", \"Contributor\"),\n\n    /**\n     * Use for a person or organization listed as a copyright owner at the time of registration. Copyright can be granted or later transferred to another person or organization, at which time the claimant becomes the copyright holder.\n     */\n    COPYRIGHT_CLAIMANT(\"cpc\", \"Copyright claimant\"),\n\n    /**\n     * Use for a person or organization to whom copy and legal rights have been granted or transferred for the intellectual content of a work. The copyright holder, although not necessarily the creator of the work, usually has the exclusive right to benefit financially from the sale and use of the work to which the associated copyright protection applies.\n     */\n    COPYRIGHT_HOLDER(\"cph\", \"Copyright holder\"),\n\n    /**\n     * Use for a person or organization who is a corrector of manuscripts, such as the scriptorium official who corrected the work of a scribe. For printed matter, use Proofreader.\n     */\n    CORRECTOR(\"crr\", \"Corrector\"),\n\n    /**\n     * Use for a person or organization who was either the writer or recipient of a letter or other communication.\n     */\n    CORRESPONDENT(\"crp\", \"Correspondent\"),\n\n    /**\n     * Use for a person or organization who designs or makes costumes, fixes hair, etc., for a musical or dramatic presentation or entertainment.\n     */\n    COSTUME_DESIGNER(\"cst\", \"Costume designer\"),\n\n    /**\n     * Use for a person or organization responsible for the graphic design of a book cover, album cover, slipcase, box, container, etc. For a person or organization responsible for the graphic design of an entire book, use Book designer; for book jackets, use Bookjacket designer.\n     */\n    COVER_DESIGNER(\"cov\", \"Cover designer\"),\n\n    /**\n     * Use for a person or organization responsible for the intellectual or artistic content of a work.\n     */\n    CREATOR(\"cre\", \"Creator\"),\n\n    /**\n     * Use for a person or organization responsible for conceiving and organizing an exhibition.\n     */\n    CURATOR_OF_AN_EXHIBITION(\"cur\", \"Curator of an exhibition\"),\n\n    /**\n     * Use for a person or organization who principally exhibits dancing skills in a musical or dramatic presentation or entertainment.\n     */\n    DANCER(\"dnc\", \"Dancer\"),\n\n    /**\n     * Use for a person or organization that submits data for inclusion in a database or other collection of data.\n     */\n    DATA_CONTRIBUTOR(\"dtc\", \"Data contributor\"),\n\n    /**\n     * Use for a person or organization responsible for managing databases or other data sources.\n     */\n    DATA_MANAGER(\"dtm\", \"Data manager\"),\n\n    /**\n     * Use for a person or organization to whom a book, manuscript, etc., is dedicated (not the recipient of a gift).\n     */\n    DEDICATEE(\"dte\", \"Dedicatee\"),\n\n    /**\n     * Use for the author of a dedication, which may be a formal statement or in epistolary or verse form.\n     */\n    DEDICATOR(\"dto\", \"Dedicator\"),\n\n    /**\n     * Use for the party defending or denying allegations made in a suit and against whom relief or recovery is sought in the courts, usually in a legal action.\n     */\n    DEFENDANT(\"dfd\", \"Defendant\"),\n\n    /**\n     * Use for a defendant who takes an appeal from one court or jurisdiction to another to reverse the judgment, usually in a legal action.\n     */\n    DEFENDANT_APPELLANT(\"dft\", \"Defendant-appellant\"),\n\n    /**\n     * Use for a defendant against whom an appeal is taken from one court or jurisdiction to another to reverse the judgment, usually in a legal action.\n     */\n    DEFENDANT_APPELLEE(\"dfe\", \"Defendant-appellee\"),\n\n    /**\n     * Use for the organization granting a degree for which the thesis or dissertation described was presented.\n     */\n    DEGREE_GRANTOR(\"dgg\", \"Degree grantor\"),\n\n    /**\n     * Use for a person or organization executing technical drawings from others' designs.\n     */\n    DELINEATOR(\"dln\", \"Delineator\"),\n\n    /**\n     * Use for an entity depicted or portrayed in a work, particularly in a work of art.\n     */\n    DEPICTED(\"dpc\", \"Depicted\"),\n\n    /**\n     * Use for a person or organization placing material in the physical custody of a library or repository without transferring the legal title.\n     */\n    DEPOSITOR(\"dpt\", \"Depositor\"),\n\n    /**\n     * Use for a person or organization responsible for the design if more specific codes (e.g., [bkd], [tyd]) are not desired.\n     */\n    DESIGNER(\"dsr\", \"Designer\"),\n\n    /**\n     * Use for a person or organization who is responsible for the general management of a work or who supervises the production of a performance for stage, screen, or sound recording.\n     */\n    DIRECTOR(\"drt\", \"Director\"),\n\n    /**\n     * Use for a person who presents a thesis for a university or higher-level educational degree.\n     */\n    DISSERTANT(\"dis\", \"Dissertant\"),\n\n    /**\n     * Use for the name of a place from which a resource, e.g., a serial, is distributed.\n     */\n    DISTRIBUTION_PLACE(\"dbp\", \"Distribution place\"),\n\n    /**\n     * Use for a person or organization that has exclusive or shared marketing rights for an item.\n     */\n    DISTRIBUTOR(\"dst\", \"Distributor\"),\n\n    /**\n     * Use for a person or organization who is the donor of a book, manuscript, etc., to its present owner. Donors to previous owners are designated as Former owner [fmo] or Inscriber [ins].\n     */\n    DONOR(\"dnr\", \"Donor\"),\n\n    /**\n     * Use for a person or organization who prepares artistic or technical drawings.\n     */\n    DRAFTSMAN(\"drm\", \"Draftsman\"),\n\n    /**\n     * Use for a person or organization to which authorship has been dubiously or incorrectly ascribed.\n     */\n    DUBIOUS_AUTHOR(\"dub\", \"Dubious author\"),\n\n    /**\n     * Use for a person or organization who prepares for publication a work not primarily his/her own, such as by elucidating text, adding introductory or other critical matter, or technically directing an editorial staff.\n     */\n    EDITOR(\"edt\", \"Editor\"),\n\n    /**\n     * Use for a person responsible for setting up a lighting rig and focusing the lights for a production, and running the lighting at a performance.\n     */\n    ELECTRICIAN(\"elg\", \"Electrician\"),\n\n    /**\n     * Use for a person or organization who creates a duplicate printing surface by pressure molding and electrodepositing of metal that is then backed up with lead for printing.\n     */\n    ELECTROTYPER(\"elt\", \"Electrotyper\"),\n\n    /**\n     * Use for a person or organization that is responsible for technical planning and design, particularly with construction.\n     */\n    ENGINEER(\"eng\", \"Engineer\"),\n\n    /**\n     * Use for a person or organization who cuts letters, figures, etc. on a surface, such as a wooden or metal plate, for printing.\n     */\n    ENGRAVER(\"egr\", \"Engraver\"),\n\n    /**\n     * Use for a person or organization who produces text or images for printing by subjecting metal, glass, or some other surface to acid or the corrosive action of some other substance.\n     */\n    ETCHER(\"etr\", \"Etcher\"),\n\n    /**\n     * Use for the name of the place where an event such as a conference or a concert took place.\n     */\n    EVENT_PLACE(\"evp\", \"Event place\"),\n\n    /**\n     * Use for a person or organization in charge of the description and appraisal of the value of goods, particularly rare items, works of art, etc.\n     */\n    EXPERT(\"exp\", \"Expert\"),\n\n    /**\n     * Use for a person or organization that executed the facsimile.\n     */\n    FACSIMILIST(\"fac\", \"Facsimilist\"),\n\n    /**\n     * Use for a person or organization that manages or supervises the work done to collect raw data or do research in an actual setting or environment (typically applies to the natural and social sciences).\n     */\n    FIELD_DIRECTOR(\"fld\", \"Field director\"),\n\n    /**\n     * Use for a person or organization who is an editor of a motion picture film. This term is used regardless of the medium upon which the motion picture is produced or manufactured (e.g., acetate film, video tape).\n     */\n    FILM_EDITOR(\"flm\", \"Film editor\"),\n\n    /**\n     * Use for a person or organization who is identified as the only party or the party of the first part. In the case of transfer of right, this is the assignor, transferor, licensor, grantor, etc. Multiple parties can be named jointly as the first party\n     */\n    FIRST_PARTY(\"fpy\", \"First party\"),\n\n    /**\n     * Use for a person or organization who makes or imitates something of value or importance, especially with the intent to defraud.\n     */\n    FORGER(\"frg\", \"Forger\"),\n\n    /**\n     * Use for a person or organization who owned an item at any time in the past. Includes those to whom the material was once presented. A person or organization giving the item to the present owner is designated as Donor [dnr]\n     */\n    FORMER_OWNER(\"fmo\", \"Former owner\"),\n\n    /**\n     * Use for a person or organization that furnished financial support for the production of the work.\n     */\n    FUNDER(\"fnd\", \"Funder\"),\n\n    /**\n     * Use for a person responsible for geographic information system (GIS) development and integration with global positioning system data.\n     */\n    GEOGRAPHIC_INFORMATION_SPECIALIST(\"gis\", \"Geographic information specialist\"),\n\n    /**\n     * Use for a person or organization in memory or honor of whom a book, manuscript, etc. is donated.\n     */\n    HONOREE(\"hnr\", \"Honoree\"),\n\n    /**\n     * Use for a person who is invited or regularly leads a program (often broadcast) that includes other guests, performers, etc. (e.g., talk show host).\n     */\n    HOST(\"hst\", \"Host\"),\n\n    /**\n     * Use for a person or organization responsible for the decoration of a work (especially manuscript material) with precious metals or color, usually with elaborate designs and motifs.\n     */\n    ILLUMINATOR(\"ilu\", \"Illuminator\"),\n\n    /**\n     * Use for a person or organization who conceives, and perhaps also implements, a design or illustration, usually to accompany a written text.\n     */\n    ILLUSTRATOR(\"ill\", \"Illustrator\"),\n\n    /**\n     * Use for a person who signs a presentation statement.\n     */\n    INSCRIBER(\"ins\", \"Inscriber\"),\n\n    /**\n     * Use for a person or organization who principally plays an instrument in a musical or dramatic presentation or entertainment.\n     */\n    INSTRUMENTALIST(\"itr\", \"Instrumentalist\"),\n\n    /**\n     * Use for a person or organization who is interviewed at a consultation or meeting, usually by a reporter, pollster, or some other information gathering agent.\n     */\n    INTERVIEWEE(\"ive\", \"Interviewee\"),\n\n    /**\n     * Use for a person or organization who acts as a reporter, pollster, or other information gathering agent in a consultation or meeting involving one or more individuals.\n     */\n    INTERVIEWER(\"ivr\", \"Interviewer\"),\n\n    /**\n     * Use for a person or organization who first produces a particular useful item, or develops a new process for obtaining a known item or result.\n     */\n    INVENTOR(\"inv\", \"Inventor\"),\n\n    /**\n     * Use for an institution that provides scientific analyses of material samples.\n     */\n    LABORATORY(\"lbr\", \"Laboratory\"),\n\n    /**\n     * Use for a person or organization that manages or supervises work done in a controlled setting or environment.\n     */\n    LABORATORY_DIRECTOR(\"ldr\", \"Laboratory director\"),\n\n    /**\n     * Use for a person or organization whose work involves coordinating the arrangement of existing and proposed land features and structures.\n     */\n    LANDSCAPE_ARCHITECT(\"lsa\", \"Landscape architect\"),\n\n    /**\n     * Use to indicate that a person or organization takes primary responsibility for a particular activity or endeavor. Use with another relator term or code to show the greater importance this person or organization has regarding that particular role. If more than one relator is assigned to a heading, use the Lead relator only if it applies to all the relators.\n     */\n    LEAD(\"led\", \"Lead\"),\n\n    /**\n     * Use for a person or organization permitting the temporary use of a book, manuscript, etc., such as for photocopying or microfilming.\n     */\n    LENDER(\"len\", \"Lender\"),\n\n    /**\n     * Use for the party who files a libel in an ecclesiastical or admiralty case.\n     */\n    LIBELANT(\"lil\", \"Libelant\"),\n\n    /**\n     * Use for a libelant who takes an appeal from one ecclesiastical court or admiralty to another to reverse the judgment.\n     */\n    LIBELANT_APPELLANT(\"lit\", \"Libelant-appellant\"),\n\n    /**\n     * Use for a libelant against whom an appeal is taken from one ecclesiastical court or admiralty to another to reverse the judgment.\n     */\n    LIBELANT_APPELLEE(\"lie\", \"Libelant-appellee\"),\n\n    /**\n     * Use for a party against whom a libel has been filed in an ecclesiastical court or admiralty.\n     */\n    LIBELEE(\"lel\", \"Libelee\"),\n\n    /**\n     * Use for a libelee who takes an appeal from one ecclesiastical court or admiralty to another to reverse the judgment.\n     */\n    LIBELEE_APPELLANT(\"let\", \"Libelee-appellant\"),\n\n    /**\n     * Use for a libelee against whom an appeal is taken from one ecclesiastical court or admiralty to another to reverse the judgment.\n     */\n    LIBELEE_APPELLEE(\"lee\", \"Libelee-appellee\"),\n\n    /**\n     * Use for a person or organization who is a writer of the text of an opera, oratorio, etc.\n     */\n    LIBRETTIST(\"lbt\", \"Librettist\"),\n\n    /**\n     * Use for a person or organization who is an original recipient of the right to print or publish.\n     */\n    LICENSEE(\"lse\", \"Licensee\"),\n\n    /**\n     * Use for person or organization who is a signer of the license, imprimatur, etc.\n     */\n    LICENSOR(\"lso\", \"Licensor\"),\n\n    /**\n     * Use for a person or organization who designs the lighting scheme for a theatrical presentation, entertainment, motion picture, etc.\n     */\n    LIGHTING_DESIGNER(\"lgd\", \"Lighting designer\"),\n\n    /**\n     * Use for a person or organization who prepares the stone or plate for lithographic printing, including a graphic artist creating a design directly on the surface from which printing will be done.\n     */\n    LITHOGRAPHER(\"ltg\", \"Lithographer\"),\n\n    /**\n     * Use for a person or organization who is a writer of the text of a song.\n     */\n    LYRICIST(\"lyr\", \"Lyricist\"),\n\n    /**\n     * Use for a person or organization that makes an artifactual work (an object made or modified by one or more persons). Examples of artifactual works include vases, cannons or pieces of furniture.\n     */\n    MANUFACTURER(\"mfr\", \"Manufacturer\"),\n\n    /**\n     * Use for the named entity responsible for marbling paper, cloth, leather, etc. used in construction of a resource.\n     */\n    MARBLER(\"mrb\", \"Marbler\"),\n\n    /**\n     * Use for a person or organization performing the coding of SGML, HTML, or XML markup of metadata, text, etc.\n     */\n    MARKUP_EDITOR(\"mrk\", \"Markup editor\"),\n\n    /**\n     * Use for a person or organization primarily responsible for compiling and maintaining the original description of a metadata set (e.g., geospatial metadata set).\n     */\n    METADATA_CONTACT(\"mdc\", \"Metadata contact\"),\n\n    /**\n     * Use for a person or organization responsible for decorations, illustrations, letters, etc. cut on a metal surface for printing or decoration.\n     */\n    METAL_ENGRAVER(\"mte\", \"Metal-engraver\"),\n\n    /**\n     * Use for a person who leads a program (often broadcast) where topics are discussed, usually with participation of experts in fields related to the discussion.\n     */\n    MODERATOR(\"mod\", \"Moderator\"),\n\n    /**\n     * Use for a person or organization that supervises compliance with the contract and is responsible for the report and controls its distribution. Sometimes referred to as the grantee, or controlling agency.\n     */\n    MONITOR(\"mon\", \"Monitor\"),\n\n    /**\n     * Use for a person who transcribes or copies musical notation\n     */\n    MUSIC_COPYIST(\"mcp\", \"Music copyist\"),\n\n    /**\n     * Use for a person responsible for basic music decisions about a production, including coordinating the work of the composer, the sound editor, and sound mixers, selecting musicians, and organizing and/or conducting sound for rehearsals and performances.\n     */\n    MUSICAL_DIRECTOR(\"msd\", \"Musical director\"),\n\n    /**\n     * Use for a person or organization who performs music or contributes to the musical content of a work when it is not possible or desirable to identify the function more precisely.\n     */\n    MUSICIAN(\"mus\", \"Musician\"),\n\n    /**\n     * Use for a person who is a speaker relating the particulars of an act, occurrence, or course of events.\n     */\n    NARRATOR(\"nrt\", \"Narrator\"),\n\n    /**\n     * Use for a person or organization responsible for opposing a thesis or dissertation.\n     */\n    OPPONENT(\"opn\", \"Opponent\"),\n\n    /**\n     * Use for a person or organization responsible for organizing a meeting for which an item is the report or proceedings.\n     */\n    ORGANIZER_OF_MEETING(\"orm\", \"Organizer of meeting\"),\n\n    /**\n     * Use for a person or organization performing the work, i.e., the name of a person or organization associated with the intellectual content of the work. This category does not include the publisher or personal affiliation, or sponsor except where it is also the corporate author.\n     */\n    ORIGINATOR(\"org\", \"Originator\"),\n\n    /**\n     * Use for relator codes from other lists which have no equivalent in the MARC list or for terms which have not been assigned a code.\n     */\n    OTHER(\"oth\", \"Other\"),\n\n    /**\n     * Use for a person or organization that currently owns an item or collection.\n     */\n    OWNER(\"own\", \"Owner\"),\n\n    /**\n     * Use for a person or organization responsible for the production of paper, usually from wood, cloth, or other fibrous material.\n     */\n    PAPERMAKER(\"ppm\", \"Papermaker\"),\n\n    /**\n     * Use for a person or organization that applied for a patent.\n     */\n    PATENT_APPLICANT(\"pta\", \"Patent applicant\"),\n\n    /**\n     * Use for a person or organization that was granted the patent referred to by the item.\n     */\n    PATENT_HOLDER(\"pth\", \"Patent holder\"),\n\n    /**\n     * Use for a person or organization responsible for commissioning a work. Usually a patron uses his or her means or influence to support the work of artists, writers, etc. This includes those who commission and pay for individual works.\n     */\n    PATRON(\"pat\", \"Patron\"),\n\n    /**\n     * Use for a person or organization who exhibits musical or acting skills in a musical or dramatic presentation or entertainment, if specific codes for those functions ([act], [dnc], [itr], [voc], etc.) are not used. If specific codes are used, [prf] is used for a person whose principal skill is not known or specified.\n     */\n    PERFORMER(\"prf\", \"Performer\"),\n\n    /**\n     * Use for an authority (usually a government agency) that issues permits under which work is accomplished.\n     */\n    PERMITTING_AGENCY(\"pma\", \"Permitting agency\"),\n\n    /**\n     * Use for a person or organization responsible for taking photographs, whether they are used in their original form or as reproductions.\n     */\n    PHOTOGRAPHER(\"pht\", \"Photographer\"),\n\n    /**\n     * Use for the party who complains or sues in court in a personal action, usually in a legal proceeding.\n     */\n    PLAINTIFF(\"ptf\", \"Plaintiff\"),\n\n    /**\n     * Use for a plaintiff who takes an appeal from one court or jurisdiction to another to reverse the judgment, usually in a legal proceeding.\n     */\n    PLAINTIFF_APPELLANT(\"ptt\", \"Plaintiff-appellant\"),\n\n    /**\n     * Use for a plaintiff against whom an appeal is taken from one court or jurisdiction to another to reverse the judgment, usually in a legal proceeding.\n     */\n    PLAINTIFF_APPELLEE(\"pte\", \"Plaintiff-appellee\"),\n\n    /**\n     * Use for a person or organization responsible for the production of plates, usually for the production of printed images and/or text.\n     */\n    PLATEMAKER(\"plt\", \"Platemaker\"),\n\n    /**\n     * Use for a person or organization who prints texts, whether from type or plates.\n     */\n    PRINTER(\"prt\", \"Printer\"),\n\n    /**\n     * Use for a person or organization who prints illustrations from plates.\n     */\n    PRINTER_OF_PLATES(\"pop\", \"Printer of plates\"),\n\n    /**\n     * Use for a person or organization who makes a relief, intaglio, or planographic printing surface.\n     */\n    PRINTMAKER(\"prm\", \"Printmaker\"),\n\n    /**\n     * Use for a person or organization primarily responsible for performing or initiating a process, such as is done with the collection of metadata sets.\n     */\n    PROCESS_CONTACT(\"prc\", \"Process contact\"),\n\n    /**\n     * Use for a person or organization responsible for the making of a motion picture, including business aspects, management of the productions, and the commercial success of the work.\n     */\n    PRODUCER(\"pro\", \"Producer\"),\n\n    /**\n     * Use for a person responsible for all technical and business matters in a production.\n     */\n    PRODUCTION_MANAGER(\"pmn\", \"Production manager\"),\n\n    /**\n     * Use for a person or organization associated with the production (props, lighting, special effects, etc.) of a musical or dramatic presentation or entertainment.\n     */\n    PRODUCTION_PERSONNEL(\"prd\", \"Production personnel\"),\n\n    /**\n     * Use for a person or organization responsible for the creation and/or maintenance of computer program design documents, source code, and machine-executable digital files and supporting documentation.\n     */\n    PROGRAMMER(\"prg\", \"Programmer\"),\n\n    /**\n     * Use for a person or organization with primary responsibility for all essential aspects of a project, or that manages a very large project that demands senior level responsibility, or that has overall responsibility for managing projects, or provides overall direction to a project manager.\n     */\n    PROJECT_DIRECTOR(\"pdr\", \"Project director\"),\n\n    /**\n     * Use for a person who corrects printed matter. For manuscripts, use Corrector [crr].\n     */\n    PROOFREADER(\"pfr\", \"Proofreader\"),\n\n    /**\n     * Use for the name of the place where a resource is published.\n     */\n    PUBLICATION_PLACE(\"pup\", \"Publication place\"),\n\n    /**\n     * Use for a person or organization that makes printed matter, often text, but also printed music, artwork, etc. available to the public.\n     */\n    PUBLISHER(\"pbl\", \"Publisher\"),\n\n    /**\n     * Use for a person or organization who presides over the elaboration of a collective work to ensure its coherence or continuity. This includes editors-in-chief, literary editors, editors of series, etc.\n     */\n    PUBLISHING_DIRECTOR(\"pbd\", \"Publishing director\"),\n\n    /**\n     * Use for a person or organization who manipulates, controls, or directs puppets or marionettes in a musical or dramatic presentation or entertainment.\n     */\n    PUPPETEER(\"ppt\", \"Puppeteer\"),\n\n    /**\n     * Use for a person or organization to whom correspondence is addressed.\n     */\n    RECIPIENT(\"rcp\", \"Recipient\"),\n\n    /**\n     * Use for a person or organization who supervises the technical aspects of a sound or video recording session.\n     */\n    RECORDING_ENGINEER(\"rce\", \"Recording engineer\"),\n\n    /**\n     * Use for a person or organization who writes or develops the framework for an item without being intellectually responsible for its content.\n     */\n    REDACTOR(\"red\", \"Redactor\"),\n\n    /**\n     * Use for a person or organization who prepares drawings of architectural designs (i.e., renderings) in accurate, representational perspective to show what the project will look like when completed.\n     */\n    RENDERER(\"ren\", \"Renderer\"),\n\n    /**\n     * Use for a person or organization who writes or presents reports of news or current events on air or in print.\n     */\n    REPORTER(\"rpt\", \"Reporter\"),\n\n    /**\n     * Use for an agency that hosts data or material culture objects and provides services to promote long term, consistent and shared use of those data or objects.\n     */\n    REPOSITORY(\"rps\", \"Repository\"),\n\n    /**\n     * Use for a person who directed or managed a research project.\n     */\n    RESEARCH_TEAM_HEAD(\"rth\", \"Research team head\"),\n\n    /**\n     * Use for a person who participated in a research project but whose role did not involve direction or management of it.\n     */\n    RESEARCH_TEAM_MEMBER(\"rtm\", \"Research team member\"),\n\n    /**\n     * Use for a person or organization responsible for performing research.\n     */\n    RESEARCHER(\"res\", \"Researcher\"),\n\n    /**\n     * Use for the party who makes an answer to the courts pursuant to an application for redress, usually in an equity proceeding.\n     */\n    RESPONDENT(\"rsp\", \"Respondent\"),\n\n    /**\n     * Use for a respondent who takes an appeal from one court or jurisdiction to another to reverse the judgment, usually in an equity proceeding.\n     */\n    RESPONDENT_APPELLANT(\"rst\", \"Respondent-appellant\"),\n\n    /**\n     * Use for a respondent against whom an appeal is taken from one court or jurisdiction to another to reverse the judgment, usually in an equity proceeding.\n     */\n    RESPONDENT_APPELLEE(\"rse\", \"Respondent-appellee\"),\n\n    /**\n     * Use for a person or organization legally responsible for the content of the published material.\n     */\n    RESPONSIBLE_PARTY(\"rpy\", \"Responsible party\"),\n\n    /**\n     * Use for a person or organization, other than the original choreographer or director, responsible for restaging a choreographic or dramatic work and who contributes minimal new content.\n     */\n    RESTAGER(\"rsg\", \"Restager\"),\n\n    /**\n     * Use for a person or organization responsible for the review of a book, motion picture, performance, etc.\n     */\n    REVIEWER(\"rev\", \"Reviewer\"),\n\n    /**\n     * Use for a person or organization responsible for parts of a work, often headings or opening parts of a manuscript, that appear in a distinctive color, usually red.\n     */\n    RUBRICATOR(\"rbr\", \"Rubricator\"),\n\n    /**\n     * Use for a person or organization who is the author of a motion picture screenplay.\n     */\n    SCENARIST(\"sce\", \"Scenarist\"),\n\n    /**\n     * Use for a person or organization who brings scientific, pedagogical, or historical competence to the conception and realization on a work, particularly in the case of audio-visual items.\n     */\n    SCIENTIFIC_ADVISOR(\"sad\", \"Scientific advisor\"),\n\n    /**\n     * Use for a person who is an amanuensis and for a writer of manuscripts proper. For a person who makes pen-facsimiles, use Facsimilist [fac].\n     */\n    SCRIBE(\"scr\", \"Scribe\"),\n\n    /**\n     * Use for a person or organization who models or carves figures that are three-dimensional representations.\n     */\n    SCULPTOR(\"scl\", \"Sculptor\"),\n\n    /**\n     * Use for a person or organization who is identified as the party of the second part. In the case of transfer of right, this is the assignee, transferee, licensee, grantee, etc. Multiple parties can be named jointly as the second party.\n     */\n    SECOND_PARTY(\"spy\", \"Second party\"),\n\n    /**\n     * Use for a person or organization who is a recorder, redactor, or other person responsible for expressing the views of a organization.\n     */\n    SECRETARY(\"sec\", \"Secretary\"),\n\n    /**\n     * Use for a person or organization who translates the rough sketches of the art director into actual architectural structures for a theatrical presentation, entertainment, motion picture, etc. Set designers draw the detailed guides and specifications for building the set.\n     */\n    SET_DESIGNER(\"std\", \"Set designer\"),\n\n    /**\n     * Use for a person whose signature appears without a presentation or other statement indicative of provenance. When there is a presentation statement, use Inscriber [ins].\n     */\n    SIGNER(\"sgn\", \"Signer\"),\n\n    /**\n     * Use for a person or organization who uses his/her/their voice with or without instrumental accompaniment to produce music. A performance may or may not include actual words.\n     */\n    SINGER(\"sng\", \"Singer\"),\n\n    /**\n     * Use for a person who produces and reproduces the sound score (both live and recorded), the installation of microphones, the setting of sound levels, and the coordination of sources of sound for a production.\n     */\n    SOUND_DESIGNER(\"sds\", \"Sound designer\"),\n\n    /**\n     * Use for a person who participates in a program (often broadcast) and makes a formalized contribution or presentation generally prepared in advance.\n     */\n    SPEAKER(\"spk\", \"Speaker\"),\n\n    /**\n     * Use for a person or organization that issued a contract or under the auspices of which a work has been written, printed, published, etc.\n     */\n    SPONSOR(\"spn\", \"Sponsor\"),\n\n    /**\n     * Use for a person who is in charge of everything that occurs on a performance stage, and who acts as chief of all crews and assistant to a director during rehearsals.\n     */\n    STAGE_MANAGER(\"stm\", \"Stage manager\"),\n\n    /**\n     * Use for an organization responsible for the development or enforcement of a standard.\n     */\n    STANDARDS_BODY(\"stn\", \"Standards body\"),\n\n    /**\n     * Use for a person or organization who creates a new plate for printing by molding or copying another printing surface.\n     */\n    STEREOTYPER(\"str\", \"Stereotyper\"),\n\n    /**\n     * Use for a person relaying a story with creative and/or theatrical interpretation.\n     */\n    STORYTELLER(\"stl\", \"Storyteller\"),\n\n    /**\n     * Use for a person or organization that supports (by allocating facilities, staff, or other resources) a project, program, meeting, event, data objects, material culture objects, or other entities capable of support.\n     */\n    SUPPORTING_HOST(\"sht\", \"Supporting host\"),\n\n    /**\n     * Use for a person or organization who does measurements of tracts of land, etc. to determine location, forms, and boundaries.\n     */\n    SURVEYOR(\"srv\", \"Surveyor\"),\n\n    /**\n     * Use for a person who, in the context of a resource, gives instruction in an intellectual subject or demonstrates while teaching physical skills.\n     */\n    TEACHER(\"tch\", \"Teacher\"),\n\n    /**\n     * Use for a person who is ultimately in charge of scenery, props, lights and sound for a production.\n     */\n    TECHNICAL_DIRECTOR(\"tcd\", \"Technical director\"),\n\n    /**\n     * Use for a person under whose supervision a degree candidate develops and presents a thesis, mémoire, or text of a dissertation.\n     */\n    THESIS_ADVISOR(\"ths\", \"Thesis advisor\"),\n\n    /**\n     * Use for a person who prepares a handwritten or typewritten copy from original material, including from dictated or orally recorded material. For makers of pen-facsimiles, use Facsimilist [fac].\n     */\n    TRANSCRIBER(\"trc\", \"Transcriber\"),\n\n    /**\n     * Use for a person or organization who renders a text from one language into another, or from an older form of a language into the modern form.\n     */\n    TRANSLATOR(\"trl\", \"Translator\"),\n\n    /**\n     * Use for a person or organization who designed the type face used in a particular item.\n     */\n    TYPE_DESIGNER(\"tyd\", \"Type designer\"),\n\n    /**\n     * Use for a person or organization primarily responsible for choice and arrangement of type used in an item. If the typographer is also responsible for other aspects of the graphic design of a book (e.g., Book designer [bkd]), codes for both functions may be needed.\n     */\n    TYPOGRAPHER(\"tyg\", \"Typographer\"),\n\n    /**\n     * Use for the name of a place where a university that is associated with a resource is located, for example, a university where an academic dissertation or thesis was presented.\n     */\n    UNIVERSITY_PLACE(\"uvp\", \"University place\"),\n\n    /**\n     * Use for a person or organization in charge of a video production, e.g. the video recording of a stage production as opposed to a commercial motion picture. The videographer may be the camera operator or may supervise one or more camera operators. Do not confuse with cinematographer.\n     */\n    VIDEOGRAPHER(\"vdg\", \"Videographer\"),\n\n    /**\n     * Use for a person or organization who principally exhibits singing skills in a musical or dramatic presentation or entertainment.\n     */\n    VOCALIST(\"voc\", \"Vocalist\"),\n\n    /**\n     * Use for a person who verifies the truthfulness of an event or action.\n     */\n    WITNESS(\"wit\", \"Witness\"),\n\n    /**\n     * Use for a person or organization who makes prints by cutting the image in relief on the end-grain of a wood block.\n     */\n    WOOD_ENGRAVER(\"wde\", \"Wood-engraver\"),\n\n    /**\n     * Use for a person or organization who makes prints by cutting the image in relief on the plank side of a wood block.\n     */\n    WOODCUTTER(\"wdc\", \"Woodcutter\"),\n\n    /**\n     * Use for a person or organization who writes significant material which accompanies a sound recording or other audiovisual material.\n     */\n    WRITER_OF_ACCOMPANYING_MATERIAL(\"wam\", \"Writer of accompanying material\");\n\n    private final String code;\n    private final String name;\n\n    Relator(String code, String name) {\n        this.code = code;\n        this.name = name;\n    }\n\n    public String getCode() {\n        return code;\n    }\n\n    public String getName() {\n        return name;\n    }\n\n    public static Relator byCode(String code) {\n        for (Relator relator : Relator.values()) {\n            if (relator.getCode().equalsIgnoreCase(code)) {\n                return relator;\n            }\n        }\n        return null;\n    }\n\n}\n"
  },
  {
    "path": "modules/book/src/main/java/me/ag2s/epublib/domain/Resource.java",
    "content": "package me.ag2s.epublib.domain;\n\nimport java.io.ByteArrayInputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.Reader;\nimport java.io.Serializable;\n\nimport me.ag2s.epublib.Constants;\nimport me.ag2s.epublib.util.IOUtil;\nimport me.ag2s.epublib.util.StringUtil;\nimport me.ag2s.epublib.util.commons.io.XmlStreamReader;\n\n/**\n * Represents a resource that is part of the epub.\n * A resource can be a html file, image, xml, etc.\n *\n * @author paul\n */\npublic class Resource implements Serializable {\n\n    private static final long serialVersionUID = 1043946707835004037L;\n    private String id;\n    private String title;\n    private String href;\n\n\n    private String properties;\n    protected final String originalHref;\n    private MediaType mediaType;\n    private String inputEncoding;\n    protected byte[] data;\n\n    /**\n     * Creates an empty Resource with the given href.\n     * <p>\n     * Assumes that if the data is of a text type (html/css/etc) then the encoding will be UTF-8\n     *\n     * @param href The location of the resource within the epub. Example: \"chapter1.html\".\n     */\n    public Resource(String href) {\n        this(null, new byte[0], href, MediaTypes.determineMediaType(href));\n    }\n\n    /**\n     * Creates a Resource with the given data and MediaType.\n     * The href will be automatically generated.\n     * <p>\n     * Assumes that if the data is of a text type (html/css/etc) then the encoding will be UTF-8\n     *\n     * @param data      The Resource's contents\n     * @param mediaType The MediaType of the Resource\n     */\n    public Resource(byte[] data, MediaType mediaType) {\n        this(null, data, null, mediaType);\n    }\n\n    /**\n     * Creates a resource with the given data at the specified href.\n     * The MediaType will be determined based on the href extension.\n     * <p>\n     * Assumes that if the data is of a text type (html/css/etc) then the encoding will be UTF-8\n     *\n     * @param data The Resource's contents\n     * @param href The location of the resource within the epub. Example: \"chapter1.html\".\n     * @see MediaTypes#determineMediaType(String)\n     */\n    public Resource(byte[] data, String href) {\n        this(null, data, href, MediaTypes.determineMediaType(href),\n                Constants.CHARACTER_ENCODING);\n    }\n\n    /**\n     * Creates a resource with the data from the given Reader at the specified href.\n     * The MediaType will be determined based on the href extension.\n     *\n     * @param in   The Resource's contents\n     * @param href The location of the resource within the epub. Example: \"cover.jpg\".\n     * @see MediaTypes#determineMediaType(String)\n     */\n    public Resource(Reader in, String href) throws IOException {\n        this(null, IOUtil.toByteArray(in, Constants.CHARACTER_ENCODING), href,\n                MediaTypes.determineMediaType(href),\n                Constants.CHARACTER_ENCODING);\n    }\n\n    /**\n     * Creates a resource with the data from the given InputStream at the specified href.\n     * The MediaType will be determined based on the href extension.\n     *\n     * @param in   The Resource's contents\n     * @param href The location of the resource within the epub. Example: \"cover.jpg\".\n     * @see MediaTypes#determineMediaType(String)\n     * <p>\n     * Assumes that if the data is of a text type (html/css/etc) then the encoding will be UTF-8\n     * <p>\n     * It is recommended to us the {@link #Resource(Reader, String)} method for creating textual\n     * (html/css/etc) resources to prevent encoding problems.\n     * Use this method only for binary Resources like images, fonts, etc.\n     */\n    public Resource(InputStream in, String href) throws IOException {\n        this(null, IOUtil.toByteArray(in), href,\n                MediaTypes.determineMediaType(href));\n    }\n\n    /**\n     * Creates a resource with the given id, data, mediatype at the specified href.\n     * Assumes that if the data is of a text type (html/css/etc) then the encoding will be UTF-8\n     *\n     * @param id        The id of the Resource. Internal use only. Will be auto-generated if it has a null-value.\n     * @param data      The Resource's contents\n     * @param href      The location of the resource within the epub. Example: \"chapter1.html\".\n     * @param mediaType The resources MediaType\n     */\n    public Resource(String id, byte[] data, String href, MediaType mediaType) {\n        this(id, data, href, mediaType, Constants.CHARACTER_ENCODING);\n    }\n\n    public Resource(String id, byte[] data, String href, String originalHref, MediaType mediaType) {\n        this(id, data, href, originalHref, mediaType, Constants.CHARACTER_ENCODING);\n    }\n\n\n    /**\n     * Creates a resource with the given id, data, mediatype at the specified href.\n     * If the data is of a text type (html/css/etc) then it will use the given inputEncoding.\n     *\n     * @param id            The id of the Resource. Internal use only. Will be auto-generated if it has a null-value.\n     * @param data          The Resource's contents\n     * @param href          The location of the resource within the epub. Example: \"chapter1.html\".\n     * @param mediaType     The resources MediaType\n     * @param inputEncoding If the data is of a text type (html/css/etc) then it will use the given inputEncoding.\n     */\n    public Resource(String id, byte[] data, String href, MediaType mediaType,\n                    String inputEncoding) {\n        this.id = id;\n        this.href = href;\n        this.originalHref = href;\n        this.mediaType = mediaType;\n        this.inputEncoding = inputEncoding;\n        this.data = data;\n    }\n\n    public Resource(String id, byte[] data, String href, String originalHref, MediaType mediaType,\n                    String inputEncoding) {\n        this.id = id;\n        this.href = href;\n        this.originalHref = originalHref;\n        this.mediaType = mediaType;\n        this.inputEncoding = inputEncoding;\n        this.data = data;\n    }\n\n    /**\n     * Gets the contents of the Resource as an InputStream.\n     *\n     * @return The contents of the Resource.\n     * @throws IOException IOException\n     */\n    public InputStream getInputStream() throws IOException {\n        return new ByteArrayInputStream(getData());\n    }\n\n    /**\n     * The contents of the resource as a byte[]\n     *\n     * @return The contents of the resource\n     */\n    public byte[] getData() throws IOException {\n        return data;\n    }\n\n    /**\n     * Tells this resource to release its cached data.\n     * <p>\n     * If this resource was not lazy-loaded, this is a no-op.\n     */\n    public void close() {\n    }\n\n    /**\n     * Sets the data of the Resource.\n     * If the data is a of a different type then the original data then make sure to change the MediaType.\n     *\n     * @param data the data of the Resource\n     */\n    public void setData(byte[] data) {\n        this.data = data;\n    }\n\n    /**\n     * Returns the size of this resource in bytes.\n     *\n     * @return the size.\n     */\n    public long getSize() {\n        return data.length;\n    }\n\n    /**\n     * If the title is found by scanning the underlying html document then it is cached here.\n     *\n     * @return the title\n     */\n    public String getTitle() {\n        return title;\n    }\n\n    /**\n     * Sets the Resource's id: Make sure it is unique and a valid identifier.\n     *\n     * @param id Resource's id\n     */\n    public void setId(String id) {\n        this.id = id;\n    }\n\n    /**\n     * The resources Id.\n     * <p>\n     * Must be both unique within all the resources of this book and a valid identifier.\n     *\n     * @return The resources Id.\n     */\n    public String getId() {\n        return id;\n    }\n\n    /**\n     * The location of the resource within the contents folder of the epub file.\n     * <p>\n     * Example:<br/>\n     * images/cover.jpg<br/>\n     * content/chapter1.xhtml<br/>\n     *\n     * @return The location of the resource within the contents folder of the epub file.\n     */\n    public String getHref() {\n        return href;\n    }\n\n    /**\n     * Sets the Resource's href.\n     *\n     * @param href Resource's href.\n     */\n    public void setHref(String href) {\n        this.href = href;\n    }\n\n    /**\n     * The character encoding of the resource.\n     * Is allowed to be null for non-text resources like images.\n     *\n     * @return The character encoding of the resource.\n     */\n    public String getInputEncoding() {\n        return inputEncoding;\n    }\n\n    /**\n     * Sets the Resource's input character encoding.\n     *\n     * @param encoding Resource's input character encoding.\n     */\n    public void setInputEncoding(String encoding) {\n        this.inputEncoding = encoding;\n    }\n\n    /**\n     * Gets the contents of the Resource as Reader.\n     * <p>\n     * Does all sorts of smart things (courtesy of apache commons io XMLStreamREader) to handle encodings, byte order markers, etc.\n     *\n     * @return the contents of the Resource as Reader.\n     * @throws IOException IOException\n     */\n    public Reader getReader() throws IOException {\n        return new XmlStreamReader(new ByteArrayInputStream(getData()),\n                getInputEncoding());\n    }\n\n    /**\n     * Gets the hashCode of the Resource's href.\n     */\n    public int hashCode() {\n        return href.hashCode();\n    }\n\n    /**\n     * Checks to see of the given resourceObject is a resource and whether its href is equal to this one.\n     *\n     * @return whether the given resourceObject is a resource and whether its href is equal to this one.\n     */\n    public boolean equals(Object resourceObject) {\n        if (!(resourceObject instanceof Resource)) {\n            return false;\n        }\n        return href.equals(((Resource) resourceObject).getHref());\n    }\n\n    /**\n     * This resource's mediaType.\n     *\n     * @return This resource's mediaType.\n     */\n    public MediaType getMediaType() {\n        return mediaType;\n    }\n\n    public void setMediaType(MediaType mediaType) {\n        this.mediaType = mediaType;\n    }\n\n    public void setTitle(String title) {\n        this.title = title;\n    }\n\n    public String getProperties() {\n        return properties;\n    }\n\n    public void setProperties(String properties) {\n        this.properties = properties;\n    }\n\n    @SuppressWarnings(\"NullableProblems\")\n    public String toString() {\n        return StringUtil.toString(\"id\", id,\n                \"title\", title,\n                \"encoding\", inputEncoding,\n                \"mediaType\", mediaType,\n                \"href\", href,\n                \"size\", (data == null ? 0 : data.length));\n    }\n}\n"
  },
  {
    "path": "modules/book/src/main/java/me/ag2s/epublib/domain/ResourceInputStream.java",
    "content": "package me.ag2s.epublib.domain;\n\nimport java.io.FilterInputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\n\n/**\n * A wrapper class for closing a AndroidZipFile object when the InputStream derived\n * from it is closed.\n *\n * @author ttopalov\n */\npublic class ResourceInputStream extends FilterInputStream {\n\n    //private final ZipFile zipFile;\n\n    /**\n     * Constructor.\n     *\n     * @param in The InputStream object.\n     */\n    public ResourceInputStream(InputStream in) {\n        super(in);\n        //this.zipFile = zipFile;\n    }\n\n    @Override\n    public void close() throws IOException {\n        super.close();\n\n        //zipFile.close();\n    }\n}\n"
  },
  {
    "path": "modules/book/src/main/java/me/ag2s/epublib/domain/ResourceReference.java",
    "content": "package me.ag2s.epublib.domain;\n\nimport java.io.Serializable;\n\npublic class ResourceReference implements Serializable {\n\n    private static final long serialVersionUID = 2596967243557743048L;\n\n    protected Resource resource;\n\n    public ResourceReference(Resource resource) {\n        this.resource = resource;\n    }\n\n\n    public Resource getResource() {\n        return resource;\n    }\n\n    /**\n     * Besides setting the resource it also sets the fragmentId to null.\n     *\n     * @param resource resource\n     */\n    public void setResource(Resource resource) {\n        this.resource = resource;\n    }\n\n\n    /**\n     * The id of the reference referred to.\n     * <p>\n     * null of the reference is null or has a null id itself.\n     *\n     * @return The id of the reference referred to.\n     */\n    public String getResourceId() {\n        if (resource != null) {\n            return resource.getId();\n        }\n        return null;\n    }\n}\n"
  },
  {
    "path": "modules/book/src/main/java/me/ag2s/epublib/domain/Resources.java",
    "content": "package me.ag2s.epublib.domain;\n\nimport android.util.Base64;\n\nimport java.io.Serializable;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.Collection;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.regex.Matcher;\nimport java.util.regex.Pattern;\n\nimport me.ag2s.epublib.Constants;\nimport me.ag2s.epublib.util.StringUtil;\n\n/**\n * All the resources that make up the book.\n * XHTML files, images and epub xml documents must be here.\n *\n * @author paul\n */\npublic class Resources implements Serializable {\n\n    private static final long serialVersionUID = 2450876953383871451L;\n    private static final String IMAGE_PREFIX = \"image_\";\n    private static final String ITEM_PREFIX = \"item_\";\n    private static final Pattern dataUriRegex = Pattern.compile(\"data:([\\\\w/\\\\-\\\\.]+);base64,(.*)\");\n    private int lastId = 1;\n\n    private Map<String, Resource> resources = new HashMap<>();\n\n    private final Map<String, Resource> resourcesById = new HashMap<>();\n\n    /**\n     * Adds a resource to the resources.\n     * <p>\n     * Fixes the resources id and href if necessary.\n     *\n     * @param resource resource\n     * @return the newly added resource\n     */\n    public Resource add(Resource resource) {\n        fixResourceHref(resource);\n        fixResourceId(resource);\n        this.resources.put(resource.getHref(), resource);\n        resourcesById.put(resource.getId(), resource);\n        return resource;\n    }\n\n    /**\n     * Checks the id of the given resource and changes to a unique identifier if it isn't one already.\n     *\n     * @param resource resource\n     */\n    public void fixResourceId(Resource resource) {\n        String resourceId = resource.getId();\n\n        // first try and create a unique id based on the resource's href\n        if (StringUtil.isBlank(resource.getId())) {\n            resourceId = StringUtil.substringBeforeLast(resource.getHref(), '.');\n            resourceId = StringUtil.substringAfterLast(resourceId, '/');\n        }\n\n        resourceId = makeValidId(resourceId, resource);\n\n        // check if the id is unique. if not: create one from scratch\n        if (StringUtil.isBlank(resourceId) || containsId(resourceId)) {\n            resourceId = createUniqueResourceId(resource);\n        }\n        resource.setId(resourceId);\n    }\n\n    /**\n     * Check if the id is a valid identifier. if not: prepend with valid identifier\n     *\n     * @param resource resource\n     * @return a valid id\n     */\n    private String makeValidId(String resourceId, Resource resource) {\n        if (StringUtil.isNotBlank(resourceId) && !Character\n                .isJavaIdentifierStart(resourceId.charAt(0))) {\n            resourceId = getResourceItemPrefix(resource) + resourceId;\n        }\n        return resourceId;\n    }\n\n    private String getResourceItemPrefix(Resource resource) {\n        String result;\n        if (MediaTypes.isBitmapImage(resource.getMediaType())) {\n            result = IMAGE_PREFIX;\n        } else {\n            result = ITEM_PREFIX;\n        }\n        return result;\n    }\n\n    /**\n     * Creates a new resource id that is guaranteed to be unique for this set of Resources\n     *\n     * @param resource resource\n     * @return a new resource id that is guaranteed to be unique for this set of Resources\n     */\n    private String createUniqueResourceId(Resource resource) {\n        int counter = lastId;\n        if (counter == Integer.MAX_VALUE) {\n            if (resources.size() == Integer.MAX_VALUE) {\n                throw new IllegalArgumentException(\n                        \"Resources contains \" + Integer.MAX_VALUE\n                                + \" elements: no new elements can be added\");\n            } else {\n                counter = 1;\n            }\n        }\n        String prefix = getResourceItemPrefix(resource);\n        String result = prefix + counter;\n        while (containsId(result)) {\n            result = prefix + (++counter);\n        }\n        lastId = counter;\n        return result;\n    }\n\n    /**\n     * Whether the map of resources already contains a resource with the given id.\n     *\n     * @param id id\n     * @return Whether the map of resources already contains a resource with the given id.\n     */\n    public boolean containsId(String id) {\n        if (StringUtil.isBlank(id)) {\n            return false;\n        }\n        return resourcesById.containsKey(id);\n    }\n\n    /**\n     * Gets the resource with the given id.\n     *\n     * @param id id\n     * @return null if not found\n     */\n    public Resource getById(String id) {\n        if (StringUtil.isBlank(id)) {\n            return null;\n        }\n        return resourcesById.get(id);\n    }\n\n    public Resource getByProperties(String properties) {\n        if (StringUtil.isBlank(properties)) {\n            return null;\n        }\n        for (Resource resource : resources.values()) {\n            if (properties.equals(resource.getProperties())) {\n                return resource;\n            }\n        }\n        return null;\n    }\n\n    /**\n     * Remove the resource with the given href.\n     *\n     * @param href href\n     * @return the removed resource, null if not found\n     */\n    public Resource remove(String href) {\n        return resources.remove(href);\n    }\n\n    private void fixResourceHref(Resource resource) {\n        if (StringUtil.isNotBlank(resource.getHref())\n                && !resources.containsKey(resource.getHref())) {\n            return;\n        }\n        if (StringUtil.isBlank(resource.getHref())) {\n            if (resource.getMediaType() == null) {\n                throw new IllegalArgumentException(\n                        \"Resource must have either a MediaType or a href\");\n            }\n            int i = 1;\n            String href = createHref(resource.getMediaType(), i);\n            while (resources.containsKey(href)) {\n                href = createHref(resource.getMediaType(), (++i));\n            }\n            resource.setHref(href);\n        }\n    }\n\n    private String createHref(MediaType mediaType, int counter) {\n        if (MediaTypes.isBitmapImage(mediaType)) {\n            return IMAGE_PREFIX + counter + mediaType.getDefaultExtension();\n        } else {\n            return ITEM_PREFIX + counter + mediaType.getDefaultExtension();\n        }\n    }\n\n\n    public boolean isEmpty() {\n        return resources.isEmpty();\n    }\n\n    /**\n     * The number of resources\n     *\n     * @return The number of resources\n     */\n    public int size() {\n        return resources.size();\n    }\n\n    /**\n     * The resources that make up this book.\n     * Resources can be xhtml pages, images, xml documents, etc.\n     *\n     * @return The resources that make up this book.\n     */\n    @SuppressWarnings(\"unused\")\n    public Map<String, Resource> getResourceMap() {\n        return resources;\n    }\n\n    public Collection<Resource> getAll() {\n        return resources.values();\n    }\n\n\n    /**\n     * Whether there exists a resource with the given href\n     *\n     * @param href href\n     * @return Whether there exists a resource with the given href\n     */\n    public boolean notContainsByHref(String href) {\n        if (StringUtil.isBlank(href)) {\n            return true;\n        } else {\n            return !resources.containsKey(\n                    StringUtil.substringBefore(href, Constants.FRAGMENT_SEPARATOR_CHAR));\n        }\n    }\n\n    /**\n     * Whether there exists a resource with the given href\n     *\n     * @param href href\n     * @return Whether there exists a resource with the given href\n     */\n    @SuppressWarnings(\"unused\")\n    public boolean containsByHref(String href) {\n        return !notContainsByHref(href);\n    }\n\n    /**\n     * Sets the collection of Resources to the given collection of resources\n     *\n     * @param resources resources\n     */\n    public void set(Collection<Resource> resources) {\n        this.resources.clear();\n        resourcesById.clear();\n        addAll(resources);\n    }\n\n    /**\n     * Adds all resources from the given Collection of resources to the existing collection.\n     *\n     * @param resources resources\n     */\n    public void addAll(Collection<Resource> resources) {\n        for (Resource resource : resources) {\n            add(resource);\n        }\n    }\n\n    /**\n     * Sets the collection of Resources to the given collection of resources\n     *\n     * @param resources A map with as keys the resources href and as values the Resources\n     */\n    public void set(Map<String, Resource> resources) {\n        this.resources = new HashMap<>(resources);\n        resourcesById.clear();\n        for (Resource resource : resources.values()) {\n            resourcesById.put(resource.getId(), resource);\n        }\n    }\n\n\n    /**\n     * First tries to find a resource with as id the given idOrHref, if that\n     * fails it tries to find one with the idOrHref as href.\n     *\n     * @param idOrHref idOrHref\n     * @return the found Resource\n     */\n    public Resource getByIdOrHref(String idOrHref) {\n        Resource resource = getById(idOrHref);\n        if (resource == null) {\n            resource = getByHref(idOrHref);\n        }\n        return resource;\n    }\n\n\n    /**\n     * Gets the resource with the given href.\n     * If the given href contains a fragmentId then that fragment id will be ignored.\n     *\n     * @param href href\n     * @return null if not found.\n     */\n    public Resource getByHref(String href) {\n        if (StringUtil.isBlank(href)) {\n            return null;\n        }\n        href = StringUtil.substringBefore(href, Constants.FRAGMENT_SEPARATOR_CHAR);\n\n        if (!StringUtil.startsWithIgnoreCase(href, \"data\")) {\n            return resources.get(href);\n        }\n\n        Matcher dataUriMatcher = dataUriRegex.matcher(href);\n        if (dataUriMatcher.find()) {\n            String dataUriMediaTypeString = dataUriMatcher.group(1);\n            MediaType dataUriMediaType = new MediaType(dataUriMediaTypeString, \".\" + StringUtil.substringAfterLast(dataUriMediaTypeString, '/'));\n            byte[] dataUriData = Base64.decode(dataUriMatcher.group(2), Base64.DEFAULT);\n            return new Resource(dataUriData, dataUriMediaType);\n        } else {\n            return resources.get(href);\n        }\n    }\n\n    /**\n     * Gets the first resource (random order) with the give mediatype.\n     * <p>\n     * Useful for looking up the table of contents as it's supposed to be the only resource with NCX mediatype.\n     *\n     * @param mediaType mediaType\n     * @return the first resource (random order) with the give mediatype.\n     */\n    public Resource findFirstResourceByMediaType(MediaType mediaType) {\n        return findFirstResourceByMediaType(resources.values(), mediaType);\n    }\n\n    /**\n     * Gets the first resource (random order) with the give mediatype.\n     * <p>\n     * Useful for looking up the table of contents as it's supposed to be the only resource with NCX mediatype.\n     *\n     * @param mediaType mediaType\n     * @return the first resource (random order) with the give mediatype.\n     */\n    public static Resource findFirstResourceByMediaType(\n            Collection<Resource> resources, MediaType mediaType) {\n        for (Resource resource : resources) {\n            if (resource.getMediaType() == mediaType) {\n                return resource;\n            }\n        }\n        return null;\n    }\n\n    /**\n     * All resources that have the given MediaType.\n     *\n     * @param mediaType mediaType\n     * @return All resources that have the given MediaType.\n     */\n    public List<Resource> getResourcesByMediaType(MediaType mediaType) {\n        List<Resource> result = new ArrayList<>();\n        if (mediaType == null) {\n            return result;\n        }\n        for (Resource resource : getAll()) {\n            if (resource.getMediaType() == mediaType) {\n                result.add(resource);\n            }\n        }\n        return result;\n    }\n\n    /**\n     * All Resources that match any of the given list of MediaTypes\n     *\n     * @param mediaTypes mediaType\n     * @return All Resources that match any of the given list of MediaTypes\n     */\n    @SuppressWarnings(\"unused\")\n    public List<Resource> getResourcesByMediaTypes(MediaType[] mediaTypes) {\n        List<Resource> result = new ArrayList<>();\n        if (mediaTypes == null) {\n            return result;\n        }\n\n        // this is the fastest way of doing this according to\n        // http://stackoverflow.com/questions/1128723/in-java-how-can-i-test-if-an-array-contains-a-certain-value\n        List<MediaType> mediaTypesList = Arrays.asList(mediaTypes);\n        for (Resource resource : getAll()) {\n            if (mediaTypesList.contains(resource.getMediaType())) {\n                result.add(resource);\n            }\n        }\n        return result;\n    }\n\n\n    /**\n     * All resource hrefs\n     *\n     * @return all resource hrefs\n     */\n    public Collection<String> getAllHrefs() {\n        return resources.keySet();\n    }\n}\n"
  },
  {
    "path": "modules/book/src/main/java/me/ag2s/epublib/domain/Spine.java",
    "content": "package me.ag2s.epublib.domain;\n\nimport java.io.Serializable;\nimport java.util.ArrayList;\nimport java.util.Collection;\nimport java.util.List;\n\nimport me.ag2s.epublib.util.StringUtil;\n\n/**\n * The spine sections are the sections of the book in the order in which the book should be read.\n * <p>\n * This contrasts with the Table of Contents sections which is an index into the Book's sections.\n *\n * @author paul\n * @see TableOfContents\n */\npublic class Spine implements Serializable {\n\n    private static final long serialVersionUID = 3878483958947357246L;\n    private Resource tocResource;\n    private List<SpineReference> spineReferences;\n\n    public Spine() {\n        this(new ArrayList<>());\n    }\n\n    /**\n     * Creates a spine out of all the resources in the table of contents.\n     *\n     * @param tableOfContents tableOfContents\n     */\n    public Spine(TableOfContents tableOfContents) {\n        this.spineReferences = createSpineReferences(\n                tableOfContents.getAllUniqueResources());\n    }\n\n    public Spine(List<SpineReference> spineReferences) {\n        this.spineReferences = spineReferences;\n    }\n\n    public static List<SpineReference> createSpineReferences(\n            Collection<Resource> resources) {\n        List<SpineReference> result = new ArrayList<>(\n                resources.size());\n        for (Resource resource : resources) {\n            result.add(new SpineReference(resource));\n        }\n        return result;\n    }\n\n    public List<SpineReference> getSpineReferences() {\n        return spineReferences;\n    }\n\n    public void setSpineReferences(List<SpineReference> spineReferences) {\n        this.spineReferences = spineReferences;\n    }\n\n    /**\n     * Gets the resource at the given index.\n     * Null if not found.\n     *\n     * @param index index\n     * @return the resource at the given index.\n     */\n    public Resource getResource(int index) {\n        if (index < 0 || index >= spineReferences.size()) {\n            return null;\n        }\n        return spineReferences.get(index).getResource();\n    }\n\n    /**\n     * Finds the first resource that has the given resourceId.\n     * <p>\n     * Null if not found.\n     *\n     * @param resourceId resourceId\n     * @return the first resource that has the given resourceId.\n     */\n    public int findFirstResourceById(String resourceId) {\n        if (StringUtil.isBlank(resourceId)) {\n            return -1;\n        }\n\n        for (int i = 0; i < spineReferences.size(); i++) {\n            SpineReference spineReference = spineReferences.get(i);\n            if (resourceId.equals(spineReference.getResourceId())) {\n                return i;\n            }\n        }\n        return -1;\n    }\n\n    /**\n     * Adds the given spineReference to the spine references and returns it.\n     *\n     * @param spineReference spineReference\n     * @return the given spineReference\n     */\n    public SpineReference addSpineReference(SpineReference spineReference) {\n        if (spineReferences == null) {\n            this.spineReferences = new ArrayList<>();\n        }\n        spineReferences.add(spineReference);\n        return spineReference;\n    }\n\n    /**\n     * Adds the given resource to the spine references and returns it.\n     *\n     * @return the given spineReference\n     */\n    @SuppressWarnings(\"unused\")\n    public SpineReference addResource(Resource resource) {\n        return addSpineReference(new SpineReference(resource));\n    }\n\n    /**\n     * The number of elements in the spine.\n     *\n     * @return The number of elements in the spine.\n     */\n    public int size() {\n        return spineReferences.size();\n    }\n\n    /**\n     * As per the epub file format the spine officially maintains a reference to the Table of Contents.\n     * The epubwriter will look for it here first, followed by some clever tricks to find it elsewhere if not found.\n     * Put it here to be sure of the expected behaviours.\n     *\n     * @param tocResource tocResource\n     */\n    public void setTocResource(Resource tocResource) {\n        this.tocResource = tocResource;\n    }\n\n    /**\n     * The resource containing the XML for the tableOfContents.\n     * When saving an epub file this resource needs to be in this place.\n     *\n     * @return The resource containing the XML for the tableOfContents.\n     */\n    public Resource getTocResource() {\n        return tocResource;\n    }\n\n    /**\n     * The position within the spine of the given resource.\n     *\n     * @param currentResource currentResource\n     * @return something &lt; 0 if not found.\n     */\n    public int getResourceIndex(Resource currentResource) {\n        if (currentResource == null) {\n            return -1;\n        }\n        return getResourceIndex(currentResource.getHref());\n    }\n\n    /**\n     * The first position within the spine of a resource with the given href.\n     *\n     * @return something &lt; 0 if not found.\n     */\n    public int getResourceIndex(String resourceHref) {\n        int result = -1;\n        if (StringUtil.isBlank(resourceHref)) {\n            return result;\n        }\n        for (int i = 0; i < spineReferences.size(); i++) {\n            if (resourceHref.equals(spineReferences.get(i).getResource().getHref())) {\n                result = i;\n                break;\n            }\n        }\n        return result;\n    }\n\n    /**\n     * Whether the spine has any references\n     *\n     * @return Whether the spine has any references\n     */\n    public boolean isEmpty() {\n        return spineReferences.isEmpty();\n    }\n}\n"
  },
  {
    "path": "modules/book/src/main/java/me/ag2s/epublib/domain/SpineReference.java",
    "content": "package me.ag2s.epublib.domain;\n\nimport java.io.Serializable;\n\n\n/**\n * A Section of a book.\n * Represents both an item in the package document and a item in the index.\n *\n * @author paul\n */\npublic class SpineReference extends ResourceReference implements Serializable {\n\n    private static final long serialVersionUID = -7921609197351510248L;\n    private boolean linear;//default = true;\n\n    public SpineReference(Resource resource) {\n        this(resource, true);\n    }\n\n\n    public SpineReference(Resource resource, boolean linear) {\n        super(resource);\n        this.linear = linear;\n    }\n\n    /**\n     * Linear denotes whether the section is Primary or Auxiliary.\n     * Usually the cover page has linear set to false and all the other sections\n     * have it set to true.\n     * <p>\n     * It's an optional property that readers may also ignore.\n     *\n     * <blockquote>primary or auxiliary is useful for Reading Systems which\n     * opt to present auxiliary content differently than primary content.\n     * For example, a Reading System might opt to render auxiliary content in\n     * a popup window apart from the main window which presents the primary\n     * content. (For an example of the types of content that may be considered\n     * auxiliary, refer to the example below and the subsequent discussion.)</blockquote>\n     *\n     * @return whether the section is Primary or Auxiliary.\n     * @see <a href=\"http://www.idpf.org/epub/20/spec/OPF_2.0.1_draft.htm#Section2.4\">OPF Spine specification</a>\n     */\n    public boolean isLinear() {\n        return linear;\n    }\n\n    public void setLinear(boolean linear) {\n        this.linear = linear;\n    }\n\n}\n"
  },
  {
    "path": "modules/book/src/main/java/me/ag2s/epublib/domain/TOCReference.java",
    "content": "package me.ag2s.epublib.domain;\n\nimport java.io.Serializable;\nimport java.util.ArrayList;\nimport java.util.Comparator;\nimport java.util.List;\n\n/**\n * An item in the Table of Contents.\n *\n * @author paul\n * @see TableOfContents\n */\npublic class TOCReference extends TitledResourceReference\n        implements Serializable {\n\n    private static final long serialVersionUID = 5787958246077042456L;\n    private List<TOCReference> children;\n    private static final Comparator<TOCReference> COMPARATOR_BY_TITLE_IGNORE_CASE = (tocReference1, tocReference2) -> String.CASE_INSENSITIVE_ORDER.compare(tocReference1.getTitle(), tocReference2.getTitle());\n\n    @Deprecated\n    public TOCReference() {\n        this(null, null, null);\n    }\n\n    public TOCReference(String name, Resource resource) {\n        this(name, resource, null);\n    }\n\n    public TOCReference(String name, Resource resource, String fragmentId) {\n        this(name, resource, fragmentId, new ArrayList<>());\n    }\n\n    public TOCReference(String title, Resource resource, String fragmentId,\n                        List<TOCReference> children) {\n        super(resource, title, fragmentId);\n        this.children = children;\n    }\n\n    @SuppressWarnings(\"unused\")\n    public static Comparator<TOCReference> getComparatorByTitleIgnoreCase() {\n        return COMPARATOR_BY_TITLE_IGNORE_CASE;\n    }\n\n    public List<TOCReference> getChildren() {\n        return children;\n    }\n\n    public TOCReference addChildSection(TOCReference childSection) {\n        this.children.add(childSection);\n        return childSection;\n    }\n\n    public void setChildren(List<TOCReference> children) {\n        this.children = children;\n    }\n}\n"
  },
  {
    "path": "modules/book/src/main/java/me/ag2s/epublib/domain/TableOfContents.java",
    "content": "package me.ag2s.epublib.domain;\n\nimport java.io.Serializable;\nimport java.util.ArrayList;\nimport java.util.Collection;\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.Set;\n\n/**\n * The table of contents of the book.\n * The TableOfContents is a tree structure at the root it is a list of TOCReferences, each if which may have as children another list of TOCReferences.\n * <p>\n * The table of contents is used by epub as a quick index to chapters and sections within chapters.\n * It may contain duplicate entries, may decide to point not to certain chapters, etc.\n * <p>\n * See the spine for the complete list of sections in the order in which they should be read.\n *\n * @author paul\n * @see Spine\n */\npublic class TableOfContents implements Serializable {\n\n    private static final long serialVersionUID = -3147391239966275152L;\n\n    public static final String DEFAULT_PATH_SEPARATOR = \"/\";\n\n    private List<TOCReference> tocReferences;\n\n    public TableOfContents() {\n        this(new ArrayList<>());\n    }\n\n    public TableOfContents(List<TOCReference> tocReferences) {\n        this.tocReferences = tocReferences;\n    }\n\n    public List<TOCReference> getTocReferences() {\n        return tocReferences;\n    }\n\n    public void setTocReferences(List<TOCReference> tocReferences) {\n        this.tocReferences = tocReferences;\n    }\n\n    /**\n     * Calls addTOCReferenceAtLocation after splitting the path using the DEFAULT_PATH_SEPARATOR.\n     *\n     * @return the new TOCReference\n     */\n    @SuppressWarnings(\"unused\")\n    public TOCReference addSection(Resource resource, String path) {\n        return addSection(resource, path, DEFAULT_PATH_SEPARATOR);\n    }\n\n    /**\n     * Calls addTOCReferenceAtLocation after splitting the path using the given pathSeparator.\n     *\n     * @param resource      resource\n     * @param path          path\n     * @param pathSeparator pathSeparator\n     * @return the new TOCReference\n     */\n    public TOCReference addSection(Resource resource, String path,\n                                   String pathSeparator) {\n        String[] pathElements = path.split(pathSeparator);\n        return addSection(resource, pathElements);\n    }\n\n    /**\n     * Finds the first TOCReference in the given list that has the same title as the given Title.\n     *\n     * @param title         title\n     * @param tocReferences tocReferences\n     * @return null if not found.\n     */\n    private static TOCReference findTocReferenceByTitle(String title,\n                                                        List<TOCReference> tocReferences) {\n        for (TOCReference tocReference : tocReferences) {\n            if (title.equals(tocReference.getTitle())) {\n                return tocReference;\n            }\n        }\n        return null;\n    }\n\n    /**\n     * Adds the given Resources to the TableOfContents at the location specified by the pathElements.\n     * <p>\n     * Example:\n     * Calling this method with a Resource and new String[] {\"chapter1\", \"paragraph1\"} will result in the following:\n     * <ul>\n     * <li>a TOCReference with the title \"chapter1\" at the root level.<br/>\n     * If this TOCReference did not yet exist it will have been created and does not point to any resource</li>\n     * <li>A TOCReference that has the title \"paragraph1\". This TOCReference will be the child of TOCReference \"chapter1\" and\n     * will point to the given Resource</li>\n     * </ul>\n     *\n     * @param resource     resource\n     * @param pathElements pathElements\n     * @return the new TOCReference\n     */\n    public TOCReference addSection(Resource resource, String[] pathElements) {\n        if (pathElements == null || pathElements.length == 0) {\n            return null;\n        }\n        TOCReference result = null;\n        List<TOCReference> currentTocReferences = this.tocReferences;\n        for (String currentTitle : pathElements) {\n            result = findTocReferenceByTitle(currentTitle, currentTocReferences);\n            if (result == null) {\n                result = new TOCReference(currentTitle, null);\n                currentTocReferences.add(result);\n            }\n            currentTocReferences = result.getChildren();\n        }\n        result.setResource(resource);\n        return result;\n    }\n\n    /**\n     * Adds the given Resources to the TableOfContents at the location specified by the pathElements.\n     * <p>\n     * Example:\n     * Calling this method with a Resource and new int[] {0, 0} will result in the following:\n     * <ul>\n     * <li>a TOCReference at the root level.<br/>\n     * If this TOCReference did not yet exist it will have been created with a title of \"\" and does not point to any resource</li>\n     * <li>A TOCReference that points to the given resource and is a child of the previously created TOCReference.<br/>\n     * If this TOCReference didn't exist yet it will be created and have a title of \"\"</li>\n     * </ul>\n     *\n     * @param resource     resource\n     * @param pathElements pathElements\n     * @return the new TOCReference\n     */\n    @SuppressWarnings(\"unused\")\n    public TOCReference addSection(Resource resource, int[] pathElements,\n                                   String sectionTitlePrefix, String sectionNumberSeparator) {\n        if (pathElements == null || pathElements.length == 0) {\n            return null;\n        }\n        TOCReference result = null;\n        List<TOCReference> currentTocReferences = this.tocReferences;\n        for (int i = 0; i < pathElements.length; i++) {\n            int currentIndex = pathElements[i];\n            if (currentIndex > 0 && currentIndex < (currentTocReferences.size()\n                    - 1)) {\n                result = currentTocReferences.get(currentIndex);\n            } else {\n                result = null;\n            }\n            if (result == null) {\n                paddTOCReferences(currentTocReferences, pathElements, i,\n                        sectionTitlePrefix, sectionNumberSeparator);\n                result = currentTocReferences.get(currentIndex);\n            }\n            currentTocReferences = result.getChildren();\n        }\n        result.setResource(resource);\n        return result;\n    }\n\n    private void paddTOCReferences(List<TOCReference> currentTocReferences,\n                                   int[] pathElements, int pathPos, String sectionPrefix,\n                                   String sectionNumberSeparator) {\n        for (int i = currentTocReferences.size(); i <= pathElements[pathPos]; i++) {\n            String sectionTitle = createSectionTitle(pathElements, pathPos, i,\n                    sectionPrefix,\n                    sectionNumberSeparator);\n            currentTocReferences.add(new TOCReference(sectionTitle, null));\n        }\n    }\n\n    private String createSectionTitle(int[] pathElements, int pathPos,\n                                      int lastPos,\n                                      String sectionPrefix, String sectionNumberSeparator) {\n        StringBuilder title = new StringBuilder(sectionPrefix);\n        for (int i = 0; i < pathPos; i++) {\n            if (i > 0) {\n                title.append(sectionNumberSeparator);\n            }\n            title.append(pathElements[i] + 1);\n        }\n        if (pathPos > 0) {\n            title.append(sectionNumberSeparator);\n        }\n        title.append(lastPos + 1);\n        return title.toString();\n    }\n\n    public TOCReference addTOCReference(TOCReference tocReference) {\n        if (tocReferences == null) {\n            tocReferences = new ArrayList<>();\n        }\n        tocReferences.add(tocReference);\n        return tocReference;\n    }\n\n    /**\n     * All unique references (unique by href) in the order in which they are referenced to in the table of contents.\n     *\n     * @return All unique references (unique by href) in the order in which they are referenced to in the table of contents.\n     */\n    public List<Resource> getAllUniqueResources() {\n        Set<String> uniqueHrefs = new HashSet<>();\n        List<Resource> result = new ArrayList<>();\n        getAllUniqueResources(uniqueHrefs, result, tocReferences);\n        return result;\n    }\n\n    private static void getAllUniqueResources(\n            Set<String> uniqueHrefs,\n            List<Resource> result,\n            List<TOCReference> tocReferences\n    ) {\n        for (TOCReference tocReference : tocReferences) {\n            Resource resource = tocReference.getResource();\n            if (resource != null && !uniqueHrefs.contains(resource.getHref())) {\n                uniqueHrefs.add(resource.getHref());\n                result.add(resource);\n            }\n            getAllUniqueResources(uniqueHrefs, result, tocReference.getChildren());\n        }\n    }\n\n    /**\n     * The total number of references in this table of contents.\n     *\n     * @return The total number of references in this table of contents.\n     */\n    public int size() {\n        return getTotalSize(tocReferences);\n    }\n\n    private static int getTotalSize(Collection<TOCReference> tocReferences) {\n        int result = tocReferences.size();\n        for (TOCReference tocReference : tocReferences) {\n            result += getTotalSize(tocReference.getChildren());\n        }\n        return result;\n    }\n\n    /**\n     * The maximum depth of the reference tree\n     *\n     * @return The maximum depth of the reference tree\n     */\n    public int calculateDepth() {\n        return calculateDepth(tocReferences, 0);\n    }\n\n    private int calculateDepth(List<TOCReference> tocReferences,\n                               int currentDepth) {\n        int maxChildDepth = 0;\n        for (TOCReference tocReference : tocReferences) {\n            int childDepth = calculateDepth(tocReference.getChildren(), 1);\n            if (childDepth > maxChildDepth) {\n                maxChildDepth = childDepth;\n            }\n        }\n        return currentDepth + maxChildDepth;\n    }\n}\n"
  },
  {
    "path": "modules/book/src/main/java/me/ag2s/epublib/domain/TitledResourceReference.java",
    "content": "package me.ag2s.epublib.domain;\n\nimport java.io.Serializable;\n\nimport me.ag2s.epublib.Constants;\nimport me.ag2s.epublib.util.StringUtil;\n\npublic class TitledResourceReference extends ResourceReference\n        implements Serializable {\n\n    private static final long serialVersionUID = 3918155020095190080L;\n    private String fragmentId;\n    private String title;\n\n    /**\n     * 这会使title为null\n     *\n     * @param resource resource\n     */\n    @Deprecated\n    @SuppressWarnings(\"unused\")\n    public TitledResourceReference(Resource resource) {\n        this(resource, null);\n    }\n\n    public TitledResourceReference(Resource resource, String title) {\n        this(resource, title, null);\n    }\n\n    public TitledResourceReference(Resource resource, String title,\n                                   String fragmentId) {\n        super(resource);\n        this.title = title;\n        this.fragmentId = fragmentId;\n    }\n\n    public String getFragmentId() {\n        return fragmentId;\n    }\n\n    public void setFragmentId(String fragmentId) {\n        this.fragmentId = fragmentId;\n    }\n\n    public String getTitle() {\n        return title;\n    }\n\n    public void setTitle(String title) {\n        this.title = title;\n    }\n\n\n    /**\n     * If the fragmentId is blank it returns the resource href, otherwise\n     * it returns the resource href + '#' + the fragmentId.\n     *\n     * @return If the fragmentId is blank it returns the resource href,\n     * otherwise it returns the resource href + '#' + the fragmentId.\n     */\n    public String getCompleteHref() {\n        if (StringUtil.isBlank(fragmentId)) {\n            return resource.getHref();\n        } else {\n            return resource.getHref() + Constants.FRAGMENT_SEPARATOR_CHAR\n                    + fragmentId;\n        }\n    }\n\n    @Override\n    public Resource getResource() {\n        //resource为null时不设置标题\n        if (this.resource != null && this.title != null) {\n            resource.setTitle(title);\n        }\n\n        return resource;\n    }\n\n    public void setResource(Resource resource, String fragmentId) {\n        super.setResource(resource);\n        this.fragmentId = fragmentId;\n    }\n\n    /**\n     * Sets the resource to the given resource and sets the fragmentId to null.\n     */\n    public void setResource(Resource resource) {\n        setResource(resource, null);\n    }\n}\n"
  },
  {
    "path": "modules/book/src/main/java/me/ag2s/epublib/epub/BookProcessor.java",
    "content": "package me.ag2s.epublib.epub;\n\nimport me.ag2s.epublib.domain.EpubBook;\n\n/**\n * Post-processes a book.\n * <p>\n * Can be used to clean up a book after reading or before writing.\n *\n * @author paul\n */\npublic interface BookProcessor {\n\n    /**\n     * A BookProcessor that returns the input book unchanged.\n     */\n    BookProcessor IDENTITY_BOOKPROCESSOR = book -> book;\n\n    EpubBook processBook(EpubBook book);\n}\n"
  },
  {
    "path": "modules/book/src/main/java/me/ag2s/epublib/epub/BookProcessorPipeline.java",
    "content": "package me.ag2s.epublib.epub;\n\nimport android.util.Log;\n\nimport java.util.ArrayList;\nimport java.util.Collection;\nimport java.util.List;\n\nimport me.ag2s.epublib.domain.EpubBook;\n\n/**\n * A book processor that combines several other bookprocessors\n * <p>\n * Fixes coverpage/coverimage.\n * Cleans up the XHTML.\n *\n * @author paul.siegmann\n */\n@SuppressWarnings(\"unused declaration\")\npublic class BookProcessorPipeline implements BookProcessor {\n\n    private static final String TAG = BookProcessorPipeline.class.getName();\n    private List<BookProcessor> bookProcessors;\n\n    public BookProcessorPipeline() {\n        this(null);\n    }\n\n    public BookProcessorPipeline(List<BookProcessor> bookProcessingPipeline) {\n        this.bookProcessors = bookProcessingPipeline;\n    }\n\n    @Override\n    public EpubBook processBook(EpubBook book) {\n        if (bookProcessors == null) {\n            return book;\n        }\n        for (BookProcessor bookProcessor : bookProcessors) {\n            try {\n                book = bookProcessor.processBook(book);\n            } catch (Exception e) {\n                Log.e(TAG, e.getMessage(), e);\n            }\n        }\n        return book;\n    }\n\n    public void addBookProcessor(BookProcessor bookProcessor) {\n        if (this.bookProcessors == null) {\n            bookProcessors = new ArrayList<>();\n        }\n        this.bookProcessors.add(bookProcessor);\n    }\n\n    public void addBookProcessors(Collection<BookProcessor> bookProcessors) {\n        if (this.bookProcessors == null) {\n            this.bookProcessors = new ArrayList<>();\n        }\n        this.bookProcessors.addAll(bookProcessors);\n    }\n\n\n    public List<BookProcessor> getBookProcessors() {\n        return bookProcessors;\n    }\n\n\n    public void setBookProcessingPipeline(\n            List<BookProcessor> bookProcessingPipeline) {\n        this.bookProcessors = bookProcessingPipeline;\n    }\n\n}\n"
  },
  {
    "path": "modules/book/src/main/java/me/ag2s/epublib/epub/DOMUtil.java",
    "content": "package me.ag2s.epublib.epub;\n\nimport org.w3c.dom.Document;\nimport org.w3c.dom.Element;\nimport org.w3c.dom.Node;\nimport org.w3c.dom.NodeList;\nimport org.w3c.dom.Text;\n\nimport java.util.ArrayList;\nimport java.util.List;\n\nimport me.ag2s.epublib.util.StringUtil;\n\n/**\n * Utility methods for working with the DOM.\n *\n * @author paul\n */\n// package\nclass DOMUtil {\n\n    /**\n     * First tries to get the attribute value by doing an getAttributeNS on the element, if that gets an empty element it does a getAttribute without namespace.\n     *\n     * @param element   element\n     * @param namespace namespace\n     * @param attribute attribute\n     * @return String Attribute\n     */\n    public static String getAttribute(Element element, String namespace,\n                                      String attribute) {\n        String result = element.getAttributeNS(namespace, attribute);\n        if (StringUtil.isEmpty(result)) {\n            result = element.getAttribute(attribute);\n        }\n        return result;\n    }\n\n    /**\n     * Gets all descendant elements of the given parentElement with the given namespace and tagname and returns their text child as a list of String.\n     *\n     * @param parentElement parentElement\n     * @param namespace     namespace\n     * @param tagName       tagName\n     * @return List<String>\n     */\n    public static List<String> getElementsTextChild(Element parentElement,\n                                                    String namespace, String tagName) {\n        NodeList elements = parentElement\n                .getElementsByTagNameNS(namespace, tagName);\n        //ArrayList 初始化时指定长度提高性能\n        List<String> result = new ArrayList<>(elements.getLength());\n        for (int i = 0; i < elements.getLength(); i++) {\n            result.add(getTextChildrenContent((Element) elements.item(i)));\n        }\n        return result;\n    }\n\n    /**\n     * Finds in the current document the first element with the given namespace and elementName and with the given findAttributeName and findAttributeValue.\n     * It then returns the value of the given resultAttributeName.\n     *\n     * @param document            document\n     * @param namespace           namespace\n     * @param elementName         elementName\n     * @param findAttributeName   findAttributeName\n     * @param findAttributeValue  findAttributeValue\n     * @param resultAttributeName resultAttributeName\n     * @return String value\n     */\n    public static String getFindAttributeValue(Document document,\n                                               String namespace, String elementName, String findAttributeName,\n                                               String findAttributeValue, String resultAttributeName) {\n        NodeList metaTags = document.getElementsByTagNameNS(namespace, elementName);\n        for (int i = 0; i < metaTags.getLength(); i++) {\n            Element metaElement = (Element) metaTags.item(i);\n            if (findAttributeValue\n                    .equalsIgnoreCase(metaElement.getAttribute(findAttributeName))\n                    && StringUtil\n                    .isNotBlank(metaElement.getAttribute(resultAttributeName))) {\n                return metaElement.getAttribute(resultAttributeName);\n            }\n        }\n        return null;\n    }\n\n    /**\n     * Gets the first element that is a child of the parentElement and has the given namespace and tagName\n     *\n     * @param parentElement parentElement\n     * @param namespace     namespace\n     * @param tagName       tagName\n     * @return Element\n     */\n    public static NodeList getElementsByTagNameNS(Element parentElement,\n                                                  String namespace, String tagName) {\n        NodeList nodes = parentElement.getElementsByTagNameNS(namespace, tagName);\n        if (nodes.getLength() != 0) {\n            return nodes;\n        }\n        nodes = parentElement.getElementsByTagName(tagName);\n        if (nodes.getLength() == 0) {\n            return null;\n        }\n        return nodes;\n    }\n\n    /**\n     * Gets the first element that is a child of the parentElement and has the given namespace and tagName\n     *\n     * @param parentElement parentElement\n     * @param namespace     namespace\n     * @param tagName       tagName\n     * @return Element\n     */\n    public static NodeList getElementsByTagNameNS(Document parentElement,\n                                                  String namespace, String tagName) {\n        NodeList nodes = parentElement.getElementsByTagNameNS(namespace, tagName);\n        if (nodes.getLength() != 0) {\n            return nodes;\n        }\n        nodes = parentElement.getElementsByTagName(tagName);\n        if (nodes.getLength() == 0) {\n            return null;\n        }\n        return nodes;\n    }\n\n    /**\n     * Gets the first element that is a child of the parentElement and has the given namespace and tagName\n     *\n     * @param parentElement parentElement\n     * @param namespace     namespace\n     * @param tagName       tagName\n     * @return Element\n     */\n    public static Element getFirstElementByTagNameNS(Element parentElement,\n                                                     String namespace, String tagName) {\n        NodeList nodes = parentElement.getElementsByTagNameNS(namespace, tagName);\n        if (nodes.getLength() != 0) {\n            return (Element) nodes.item(0);\n        }\n        nodes = parentElement.getElementsByTagName(tagName);\n        if (nodes.getLength() == 0) {\n            return null;\n        }\n        return (Element) nodes.item(0);\n    }\n\n    /**\n     * The contents of all Text nodes that are children of the given parentElement.\n     * The result is trim()-ed.\n     * <p>\n     * The reason for this more complicated procedure instead of just returning the data of the firstChild is that\n     * when the text is Chinese characters then on Android each Characater is represented in the DOM as\n     * an individual Text node.\n     *\n     * @param parentElement parentElement\n     * @return String value\n     */\n    public static String getTextChildrenContent(Element parentElement) {\n        if (parentElement == null) {\n            return null;\n        }\n        StringBuilder result = new StringBuilder();\n        NodeList childNodes = parentElement.getChildNodes();\n        for (int i = 0; i < childNodes.getLength(); i++) {\n            Node node = childNodes.item(i);\n            if ((node == null) ||\n                    (node.getNodeType() != Node.TEXT_NODE)) {\n                continue;\n            }\n            result.append(((Text) node).getData());\n        }\n        return result.toString().trim();\n    }\n\n}\n"
  },
  {
    "path": "modules/book/src/main/java/me/ag2s/epublib/epub/EpubProcessorSupport.java",
    "content": "package me.ag2s.epublib.epub;\n\nimport android.util.Log;\n\nimport org.xml.sax.EntityResolver;\nimport org.xml.sax.InputSource;\nimport org.xmlpull.v1.XmlPullParserFactory;\nimport org.xmlpull.v1.XmlSerializer;\n\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.OutputStream;\nimport java.io.OutputStreamWriter;\nimport java.io.UnsupportedEncodingException;\nimport java.io.Writer;\nimport java.net.URL;\nimport java.util.Objects;\n\nimport javax.xml.parsers.DocumentBuilder;\nimport javax.xml.parsers.DocumentBuilderFactory;\nimport javax.xml.parsers.ParserConfigurationException;\n\nimport me.ag2s.epublib.Constants;\n\n/**\n * Various low-level support methods for reading/writing epubs.\n *\n * @author paul.siegmann\n */\npublic class EpubProcessorSupport {\n\n    private static final String TAG = EpubProcessorSupport.class.getName();\n\n    protected static DocumentBuilderFactory documentBuilderFactory;\n\n    static {\n        init();\n    }\n\n    static class EntityResolverImpl implements EntityResolver {\n\n        private String previousLocation;\n\n        @Override\n        public InputSource resolveEntity(String publicId, String systemId)\n                throws IOException {\n            String resourcePath;\n            if (systemId.startsWith(\"http:\")) {\n                URL url = new URL(systemId);\n                resourcePath = \"dtd/\" + url.getHost() + url.getPath();\n                previousLocation = resourcePath\n                        .substring(0, resourcePath.lastIndexOf('/'));\n            } else {\n                resourcePath =\n                        previousLocation + systemId.substring(systemId.lastIndexOf('/'));\n            }\n\n            if (Objects.requireNonNull(this.getClass().getClassLoader()).getResource(resourcePath) == null) {\n                throw new RuntimeException(\n                        \"remote resource is not cached : [\" + systemId\n                                + \"] cannot continue\");\n            }\n\n            InputStream in = Objects.requireNonNull(EpubProcessorSupport.class.getClassLoader())\n                    .getResourceAsStream(resourcePath);\n            return new InputSource(in);\n        }\n    }\n\n\n    private static void init() {\n        EpubProcessorSupport.documentBuilderFactory = DocumentBuilderFactory\n                .newInstance();\n        documentBuilderFactory.setNamespaceAware(true);\n        documentBuilderFactory.setValidating(false);\n    }\n\n    public static XmlSerializer createXmlSerializer(OutputStream out)\n            throws UnsupportedEncodingException {\n        return createXmlSerializer(\n                new OutputStreamWriter(out, Constants.CHARACTER_ENCODING));\n    }\n\n    public static XmlSerializer createXmlSerializer(Writer out) {\n        XmlSerializer result = null;\n        try {\n            /*\n             * Disable XmlPullParserFactory here before it doesn't work when\n             * building native image using GraalVM\n             */\n            XmlPullParserFactory factory = XmlPullParserFactory.newInstance();\n            factory.setValidating(true);\n            result = factory.newSerializer();\n\n            //result = new KXmlSerializer();\n            result.setFeature(\n                    \"http://xmlpull.org/v1/doc/features.html#indent-output\", true);\n            result.setOutput(out);\n        } catch (Exception e) {\n            Log.e(TAG,\n                    \"When creating XmlSerializer: \" + e.getClass().getName() + \": \" + e\n                            .getMessage());\n        }\n        return result;\n    }\n\n    /**\n     * Gets an EntityResolver that loads dtd's and such from the epub4j classpath.\n     * In order to enable the loading of relative urls the given EntityResolver contains the previousLocation.\n     * Because of a new EntityResolver is created every time this method is called.\n     * Fortunately the EntityResolver created uses up very little memory per instance.\n     *\n     * @return an EntityResolver that loads dtd's and such from the epub4j classpath.\n     */\n    public static EntityResolver getEntityResolver() {\n        return new EntityResolverImpl();\n    }\n\n    @SuppressWarnings(\"unused\")\n    public DocumentBuilderFactory getDocumentBuilderFactory() {\n        return documentBuilderFactory;\n    }\n\n    /**\n     * Creates a DocumentBuilder that looks up dtd's and schema's from epub4j's classpath.\n     *\n     * @return a DocumentBuilder that looks up dtd's and schema's from epub4j's classpath.\n     */\n    public static DocumentBuilder createDocumentBuilder() {\n        DocumentBuilder result = null;\n        try {\n            result = documentBuilderFactory.newDocumentBuilder();\n            result.setEntityResolver(getEntityResolver());\n        } catch (ParserConfigurationException e) {\n            Log.e(TAG, e.getMessage());\n        }\n        return result;\n    }\n}\n"
  },
  {
    "path": "modules/book/src/main/java/me/ag2s/epublib/epub/EpubReader.java",
    "content": "package me.ag2s.epublib.epub;\n\nimport android.util.Log;\n\nimport androidx.annotation.NonNull;\n\nimport org.w3c.dom.Document;\nimport org.w3c.dom.Element;\n\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.util.Arrays;\nimport java.util.List;\nimport java.util.zip.ZipFile;\nimport java.util.zip.ZipInputStream;\n\nimport me.ag2s.epublib.Constants;\nimport me.ag2s.epublib.domain.EpubBook;\nimport me.ag2s.epublib.domain.MediaType;\nimport me.ag2s.epublib.domain.MediaTypes;\nimport me.ag2s.epublib.domain.Resource;\nimport me.ag2s.epublib.domain.Resources;\nimport me.ag2s.epublib.util.ResourceUtil;\nimport me.ag2s.epublib.util.StringUtil;\nimport me.ag2s.epublib.util.zip.AndroidZipFile;\nimport me.ag2s.epublib.util.zip.ZipFileWrapper;\n\n/**\n * Reads an epub file.\n *\n * @author paul\n */\n@SuppressWarnings(\"ALL\")\npublic class EpubReader {\n\n    private static final String TAG = EpubReader.class.getName();\n    private final BookProcessor bookProcessor = BookProcessor.IDENTITY_BOOKPROCESSOR;\n\n    public EpubBook readEpub(InputStream in) throws IOException {\n        return readEpub(in, Constants.CHARACTER_ENCODING);\n    }\n\n    public EpubBook readEpub(ZipInputStream in) throws IOException {\n        return readEpub(in, Constants.CHARACTER_ENCODING);\n    }\n\n    public EpubBook readEpub(ZipFile zipfile) throws IOException {\n        return readEpub(zipfile, Constants.CHARACTER_ENCODING);\n    }\n\n    /**\n     * Read epub from inputstream\n     *\n     * @param in       the inputstream from which to read the epub\n     * @param encoding the encoding to use for the html files within the epub\n     * @return the Book as read from the inputstream\n     * @throws IOException IOException\n     */\n    public EpubBook readEpub(InputStream in, String encoding) throws IOException {\n        return readEpub(new ZipInputStream(in), encoding);\n    }\n\n\n    /**\n     * Reads this EPUB without loading any resources into memory.\n     *\n     * @param zipFile  the file to load\n     * @param encoding the encoding for XHTML files\n     * @return this Book without loading all resources into memory.\n     * @throws IOException IOException\n     */\n    public EpubBook readEpubLazy(@NonNull ZipFile zipFile, @NonNull String encoding)\n            throws IOException {\n        return readEpubLazy(zipFile, encoding, Arrays.asList(MediaTypes.mediaTypes));\n    }\n\n    public EpubBook readEpubLazy(@NonNull AndroidZipFile zipFile, @NonNull String encoding)\n            throws IOException {\n        return readEpubLazy(zipFile, encoding, Arrays.asList(MediaTypes.mediaTypes));\n    }\n\n    public EpubBook readEpub(@NonNull ZipInputStream in, @NonNull String encoding) throws IOException {\n        return readEpub(ResourcesLoader.loadResources(in, encoding));\n    }\n\n    public EpubBook readEpub(ZipFile in, String encoding) throws IOException {\n        return readEpub(ResourcesLoader.loadResources(new ZipFileWrapper(in), encoding));\n    }\n\n    /**\n     * Reads this EPUB without loading all resources into memory.\n     *\n     * @param zipFile         the file to load\n     * @param encoding        the encoding for XHTML files\n     * @param lazyLoadedTypes a list of the MediaType to load lazily\n     * @return this Book without loading all resources into memory.\n     * @throws IOException IOException\n     */\n    public EpubBook readEpubLazy(@NonNull ZipFile zipFile, @NonNull String encoding,\n                                 @NonNull List<MediaType> lazyLoadedTypes) throws IOException {\n        Resources resources = ResourcesLoader\n                .loadResources(new ZipFileWrapper(zipFile), encoding, lazyLoadedTypes);\n        return readEpub(resources);\n    }\n\n    public EpubBook readEpubLazy(@NonNull AndroidZipFile zipFile, @NonNull String encoding,\n                                 @NonNull List<MediaType> lazyLoadedTypes) throws IOException {\n        Resources resources = ResourcesLoader\n                .loadResources(new ZipFileWrapper(zipFile), encoding, lazyLoadedTypes);\n        return readEpub(resources);\n    }\n\n    public EpubBook readEpub(Resources resources) {\n        return readEpub(resources, new EpubBook());\n    }\n\n    public EpubBook readEpub(Resources resources, EpubBook result) {\n        if (result == null) {\n            result = new EpubBook();\n        }\n        handleMimeType(result, resources);\n        String packageResourceHref = getPackageResourceHref(resources);\n        Resource packageResource = processPackageResource(packageResourceHref, result, resources);\n        result.setOpfResource(packageResource);\n        Resource ncxResource = processNcxResource(packageResource, result);\n        result.setNcxResource(ncxResource);\n        result = postProcessBook(result);\n        return result;\n    }\n\n    private EpubBook postProcessBook(EpubBook book) {\n        if (bookProcessor != null) {\n            book = bookProcessor.processBook(book);\n        }\n        return book;\n    }\n\n    private Resource processNcxResource(Resource packageResource, EpubBook book) {\n        Log.d(TAG, \"OPF:getHref()\" + packageResource.getHref());\n        if (book.isEpub3()) {\n            return NCXDocumentV3.read(book, this);\n        } else {\n            return NCXDocumentV2.read(book, this);\n        }\n\n    }\n\n    private Resource processPackageResource(String packageResourceHref, EpubBook book,\n                                            Resources resources) {\n        Resource packageResource = resources.remove(packageResourceHref);\n        try {\n            PackageDocumentReader.read(packageResource, this, book, resources);\n        } catch (Exception e) {\n            Log.e(TAG, e.getMessage(), e);\n        }\n        return packageResource;\n    }\n\n    private String getPackageResourceHref(Resources resources) {\n        String defaultResult = \"OEBPS/content.opf\";\n        String result = defaultResult;\n\n        Resource containerResource = resources.remove(\"META-INF/container.xml\");\n        if (containerResource == null) {\n            return result;\n        }\n        try {\n            Document document = ResourceUtil.getAsDocument(containerResource);\n            Element rootFileElement = (Element) ((Element) document\n                    .getDocumentElement().getElementsByTagName(\"rootfiles\").item(0))\n                    .getElementsByTagName(\"rootfile\").item(0);\n            result = rootFileElement.getAttribute(\"full-path\");\n        } catch (Exception e) {\n            Log.e(TAG, e.getMessage(), e);\n        }\n        if (StringUtil.isBlank(result)) {\n            result = defaultResult;\n        }\n        return result;\n    }\n\n    private void handleMimeType(EpubBook result, Resources resources) {\n        resources.remove(\"mimetype\");\n        //result.setResources(resources);\n    }\n}\n"
  },
  {
    "path": "modules/book/src/main/java/me/ag2s/epublib/epub/EpubWriter.java",
    "content": "package me.ag2s.epublib.epub;\n\nimport android.util.Log;\n\nimport org.xmlpull.v1.XmlSerializer;\n\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.OutputStream;\nimport java.io.OutputStreamWriter;\nimport java.io.Writer;\nimport java.util.Objects;\nimport java.util.zip.CRC32;\nimport java.util.zip.ZipEntry;\nimport java.util.zip.ZipOutputStream;\n\nimport me.ag2s.epublib.domain.EpubBook;\nimport me.ag2s.epublib.domain.MediaTypes;\nimport me.ag2s.epublib.domain.Resource;\nimport me.ag2s.epublib.util.IOUtil;\n\n/**\n * Generates an epub file. Not thread-safe, single use object.\n *\n * @author paul\n */\npublic class EpubWriter {\n\n    // package\n    static final String EMPTY_NAMESPACE_PREFIX = \"\";\n    private static final String TAG = EpubWriter.class.getName();\n    private BookProcessor bookProcessor;\n\n    private EpubWriterProcessor epubWriterProcessor;\n\n    public EpubWriter() {\n        this(BookProcessor.IDENTITY_BOOKPROCESSOR);\n        this.epubWriterProcessor = new EpubWriterProcessor();\n        // 写入MimeType、Container，初始化TOCResource整体为1\n        // 写入PackageDocument 为1\n        // 关闭流 为1\n        this.epubWriterProcessor.setTotalProgress(3);\n    }\n\n\n    public EpubWriter(BookProcessor bookProcessor) {\n        this.bookProcessor = bookProcessor;\n    }\n\n    public EpubWriter setCallback(EpubWriterProcessor.Callback callback) {\n        epubWriterProcessor.setCallback(callback);\n        return this;\n    }\n\n    public void write(EpubBook book, OutputStream out) throws IOException {\n        if (Objects.nonNull(this.epubWriterProcessor.getCallback())) {\n            epubWriterProcessor.getCallback().onStart(book);\n        }\n        epubWriterProcessor.setTotalProgress(epubWriterProcessor.getTotalProgress() + book.getResources().size());\n        book = processBook(book);\n        ZipOutputStream resultStream = new ZipOutputStream(out);\n        //resultStream.setLevel(ZipOutputStream.STORED);\n        writeMimeType(resultStream);\n        writeContainer(resultStream);\n        initTOCResource(book);\n        epubWriterProcessor.updateCurrentProgress(1);\n        writeResources(book, resultStream);\n        writePackageDocument(book, resultStream);\n        epubWriterProcessor.updateCurrentProgress(epubWriterProcessor.getCurrentProgress() + 1);\n        resultStream.close();\n        epubWriterProcessor.updateCurrentProgress(epubWriterProcessor.getCurrentProgress() + 1);\n        if (Objects.nonNull(epubWriterProcessor.getCallback())) {\n            epubWriterProcessor.getCallback().onEnd(book);\n        }\n    }\n\n    private EpubBook processBook(EpubBook book) {\n        if (bookProcessor != null) {\n            book = bookProcessor.processBook(book);\n        }\n        return book;\n    }\n\n    private void initTOCResource(EpubBook book) {\n        Resource tocResource;\n        try {\n            if (book.isEpub3()) {\n                tocResource = NCXDocumentV3.createNCXResource(book);\n            } else {\n                tocResource = NCXDocumentV2.createNCXResource(book);\n            }\n\n            Resource currentTocResource = book.getSpine().getTocResource();\n            if (currentTocResource != null) {\n                book.getResources().remove(currentTocResource.getHref());\n            }\n            book.getSpine().setTocResource(tocResource);\n            book.getResources().add(tocResource);\n        } catch (Exception ex) {\n            Log.e(TAG, \"Error writing table of contents: \" + ex.getClass().getName() + \": \" + ex.getMessage(), ex);\n        }\n    }\n\n\n    private void writeResources(EpubBook book, ZipOutputStream resultStream) {\n        for (Resource resource : book.getResources().getAll()) {\n            writeResource(resource, resultStream);\n            epubWriterProcessor.updateCurrentProgress(epubWriterProcessor.getCurrentProgress() + 1);\n        }\n    }\n\n    /**\n     * Writes the resource to the resultStream.\n     *\n     * @param resource     resource\n     * @param resultStream resultStream\n     */\n    private void writeResource(Resource resource, ZipOutputStream resultStream) {\n        if (resource == null) {\n            return;\n        }\n        try {\n            resultStream.putNextEntry(new ZipEntry(\"OEBPS/\" + resource.getHref()));\n            InputStream inputStream = resource.getInputStream();\n\n            IOUtil.copy(inputStream, resultStream);\n            inputStream.close();\n        } catch (Exception e) {\n            Log.e(TAG, e.getMessage(), e);\n        }\n    }\n\n\n    private void writePackageDocument(EpubBook book, ZipOutputStream resultStream) throws IOException {\n        resultStream.putNextEntry(new ZipEntry(\"OEBPS/content.opf\"));\n        XmlSerializer xmlSerializer = EpubProcessorSupport.createXmlSerializer(resultStream);\n        PackageDocumentWriter.write(this, xmlSerializer, book);\n        xmlSerializer.flush();\n//\t\tString resultAsString = result.toString();\n//\t\tresultStream.write(resultAsString.getBytes(Constants.ENCODING));\n    }\n\n    /**\n     * Writes the META-INF/container.xml file.\n     *\n     * @param resultStream resultStream\n     * @throws IOException IOException\n     */\n    private void writeContainer(ZipOutputStream resultStream) throws IOException {\n        resultStream.putNextEntry(new ZipEntry(\"META-INF/container.xml\"));\n        Writer out = new OutputStreamWriter(resultStream);\n        out.write(\"<?xml version=\\\"1.0\\\"?>\\n\");\n        out.write(\"<container version=\\\"1.0\\\" xmlns=\\\"urn:oasis:names:tc:opendocument:xmlns:container\\\">\\n\");\n        out.write(\"\\t<rootfiles>\\n\");\n        out.write(\"\\t\\t<rootfile full-path=\\\"OEBPS/content.opf\\\" media-type=\\\"application/oebps-package+xml\\\"/>\\n\");\n        out.write(\"\\t</rootfiles>\\n\");\n        out.write(\"</container>\");\n        out.flush();\n    }\n\n    /**\n     * Stores the mimetype as an uncompressed file in the ZipOutputStream.\n     *\n     * @param resultStream resultStream\n     * @throws IOException IOException\n     */\n    private void writeMimeType(ZipOutputStream resultStream) throws IOException {\n        ZipEntry mimetypeZipEntry = new ZipEntry(\"mimetype\");\n        mimetypeZipEntry.setMethod(ZipEntry.STORED);\n        byte[] mimetypeBytes = MediaTypes.EPUB.getName().getBytes();\n        mimetypeZipEntry.setSize(mimetypeBytes.length);\n        mimetypeZipEntry.setCrc(calculateCrc(mimetypeBytes));\n        resultStream.putNextEntry(mimetypeZipEntry);\n        resultStream.write(mimetypeBytes);\n    }\n\n    private long calculateCrc(byte[] data) {\n        CRC32 crc = new CRC32();\n        crc.update(data);\n        return crc.getValue();\n    }\n\n    String getNcxId() {\n        return \"ncx\";\n    }\n\n    String getNcxHref() {\n        return \"toc.ncx\";\n    }\n\n    String getNcxMediaType() {\n        return MediaTypes.NCX.getName();\n    }\n\n\n    @SuppressWarnings(\"unused\")\n    public BookProcessor getBookProcessor() {\n        return bookProcessor;\n    }\n\n    @SuppressWarnings(\"unused\")\n    public void setBookProcessor(BookProcessor bookProcessor) {\n        this.bookProcessor = bookProcessor;\n    }\n\n}\n"
  },
  {
    "path": "modules/book/src/main/java/me/ag2s/epublib/epub/EpubWriterProcessor.java",
    "content": "package me.ag2s.epublib.epub;\n\nimport java.util.Objects;\n\nimport me.ag2s.epublib.domain.EpubBook;\n\n/**\n * epub导出进度管理器\n *\n * @author Discut\n */\npublic class EpubWriterProcessor {\n    private int totalProgress = 0;\n    private int currentProgress = 0;\n    private Callback callback;\n\n    public int getCurrentProgress() {\n        return currentProgress;\n    }\n\n    public int getTotalProgress() {\n        return totalProgress;\n    }\n\n    public void setTotalProgress(int totalProgress) {\n        this.totalProgress = totalProgress;\n    }\n\n    public void setCallback(Callback callback) {\n        this.callback = callback;\n    }\n\n    protected void updateCurrentProgress(int current) {\n        this.currentProgress = Math.min(current, totalProgress);\n        if (Objects.isNull(callback)) {\n            return;\n        }\n        callback.onProgressing(totalProgress, this.currentProgress);\n    }\n\n    protected Callback getCallback() {\n        return callback;\n    }\n\n    @SuppressWarnings(\"unused\")\n    public interface Callback {\n        default void onStart(EpubBook epubBook) {\n        }\n\n        default void onProgressing(int total, int progress) {\n        }\n\n        default void onEnd(EpubBook epubBook) {\n        }\n\n    }\n}\n"
  },
  {
    "path": "modules/book/src/main/java/me/ag2s/epublib/epub/HtmlProcessor.java",
    "content": "package me.ag2s.epublib.epub;\n\nimport java.io.OutputStream;\n\nimport me.ag2s.epublib.domain.Resource;\n\n@SuppressWarnings(\"unused\")\npublic interface HtmlProcessor {\n\n    void processHtmlResource(Resource resource, OutputStream out);\n}\n"
  },
  {
    "path": "modules/book/src/main/java/me/ag2s/epublib/epub/NCXDocumentV2.java",
    "content": "package me.ag2s.epublib.epub;\n\nimport android.util.Log;\n\nimport org.w3c.dom.Document;\nimport org.w3c.dom.Element;\nimport org.w3c.dom.Node;\nimport org.w3c.dom.NodeList;\nimport org.xmlpull.v1.XmlSerializer;\n\nimport java.io.ByteArrayOutputStream;\nimport java.io.IOException;\nimport java.io.UnsupportedEncodingException;\nimport java.net.URLDecoder;\nimport java.util.ArrayList;\nimport java.util.List;\nimport java.util.zip.ZipEntry;\nimport java.util.zip.ZipOutputStream;\n\nimport me.ag2s.epublib.Constants;\nimport me.ag2s.epublib.domain.Author;\nimport me.ag2s.epublib.domain.EpubBook;\nimport me.ag2s.epublib.domain.Identifier;\nimport me.ag2s.epublib.domain.MediaTypes;\nimport me.ag2s.epublib.domain.Resource;\nimport me.ag2s.epublib.domain.TOCReference;\nimport me.ag2s.epublib.domain.TableOfContents;\nimport me.ag2s.epublib.util.ResourceUtil;\nimport me.ag2s.epublib.util.StringUtil;\n\n/**\n * Writes the ncx document as defined by namespace http://www.daisy.org/z3986/2005/ncx/\n *\n * @author paul\n */\npublic class NCXDocumentV2 {\n\n    public static final String NAMESPACE_NCX = \"http://www.daisy.org/z3986/2005/ncx/\";\n    @SuppressWarnings(\"unused\")\n    public static final String PREFIX_NCX = \"ncx\";\n    public static final String NCX_ITEM_ID = \"ncx\";\n    public static final String DEFAULT_NCX_HREF = \"toc.ncx\";\n    public static final String PREFIX_DTB = \"dtb\";\n\n    private static final String TAG = NCXDocumentV2.class.getName();\n\n    private interface NCXTags {\n\n        String ncx = \"ncx\";\n        String meta = \"meta\";\n        String navPoint = \"navPoint\";\n        String navMap = \"navMap\";\n        String navLabel = \"navLabel\";\n        String content = \"content\";\n        String text = \"text\";\n        String docTitle = \"docTitle\";\n        String docAuthor = \"docAuthor\";\n        String head = \"head\";\n    }\n\n    private interface NCXAttributes {\n\n        String src = \"src\";\n        String name = \"name\";\n        String content = \"content\";\n        String id = \"id\";\n        String playOrder = \"playOrder\";\n        String clazz = \"class\";\n        String version = \"version\";\n    }\n\n    private interface NCXAttributeValues {\n\n        String chapter = \"chapter\";\n        String version = \"2005-1\";\n\n    }\n\n    @SuppressWarnings(\"unused\")\n    public static Resource read(EpubBook book, EpubReader epubReader) {\n        Resource ncxResource = null;\n        if (book.getSpine().getTocResource() == null) {\n            Log.e(TAG, \"Book does not contain a table of contents file\");\n            return null;\n        }\n        try {\n            ncxResource = book.getSpine().getTocResource();\n            if (ncxResource == null) {\n                return null;\n            }\n            Log.d(TAG, ncxResource.getHref());\n            Document ncxDocument = ResourceUtil.getAsDocument(ncxResource);\n            Element navMapElement = DOMUtil\n                    .getFirstElementByTagNameNS(ncxDocument.getDocumentElement(),\n                            NAMESPACE_NCX, NCXTags.navMap);\n            if (navMapElement == null) {\n                return null;\n            }\n\n            TableOfContents tableOfContents = new TableOfContents(\n                    readTOCReferences(navMapElement.getChildNodes(), book));\n            book.setTableOfContents(tableOfContents);\n        } catch (Exception e) {\n            Log.e(TAG, e.getMessage(), e);\n        }\n        return ncxResource;\n    }\n\n    static List<TOCReference> readTOCReferences(NodeList navpoints,\n                                                EpubBook book) {\n        if (navpoints == null) {\n            return new ArrayList<>();\n        }\n        List<TOCReference> result = new ArrayList<>(\n                navpoints.getLength());\n        for (int i = 0; i < navpoints.getLength(); i++) {\n            Node node = navpoints.item(i);\n            if (node.getNodeType() != Document.ELEMENT_NODE) {\n                continue;\n            }\n            if (!(node.getLocalName().equals(NCXTags.navPoint))) {\n                continue;\n            }\n            TOCReference tocReference = readTOCReference((Element) node, book);\n            result.add(tocReference);\n        }\n        return result;\n    }\n\n    static TOCReference readTOCReference(Element navpointElement, EpubBook book) {\n        String label = readNavLabel(navpointElement);\n        //Log.d(TAG,\"label:\"+label);\n        String tocResourceRoot = StringUtil\n                .substringBeforeLast(book.getSpine().getTocResource().getHref(), '/');\n        if (tocResourceRoot.length() == book.getSpine().getTocResource().getHref()\n                .length()) {\n            tocResourceRoot = \"\";\n        } else {\n            tocResourceRoot = tocResourceRoot + \"/\";\n        }\n        String reference = StringUtil\n                .collapsePathDots(tocResourceRoot + readNavReference(navpointElement));\n        String href = StringUtil\n                .substringBefore(reference, Constants.FRAGMENT_SEPARATOR_CHAR);\n        String fragmentId = StringUtil\n                .substringAfter(reference, Constants.FRAGMENT_SEPARATOR_CHAR);\n        Resource resource = book.getResources().getByHref(href);\n        if (resource == null) {\n            Log.e(TAG, \"Resource with href \" + href + \" in NCX document not found\");\n        }\n        //Log.v(TAG, \"label:\" + label);\n        //Log.v(TAG, \"href:\" + href);\n        //Log.v(TAG, \"fragmentId:\" + fragmentId);\n        TOCReference result = new TOCReference(label, resource, fragmentId);\n        List<TOCReference> childTOCReferences = readTOCReferences(\n                navpointElement.getChildNodes(), book);\n        result.setChildren(childTOCReferences);\n        return result;\n    }\n\n    private static String readNavReference(Element navpointElement) {\n        Element contentElement = DOMUtil\n                .getFirstElementByTagNameNS(navpointElement, NAMESPACE_NCX,\n                        NCXTags.content);\n        if (contentElement == null) {\n            return null;\n        }\n        String result = DOMUtil\n                .getAttribute(contentElement, NAMESPACE_NCX, NCXAttributes.src);\n        try {\n            result = URLDecoder.decode(result, Constants.CHARACTER_ENCODING);\n        } catch (UnsupportedEncodingException e) {\n            Log.e(TAG, e.getMessage());\n        }\n        return result;\n    }\n\n    private static String readNavLabel(Element navpointElement) {\n        //Log.d(TAG,navpointElement.getTagName());\n        Element navLabel = DOMUtil\n                .getFirstElementByTagNameNS(navpointElement, NAMESPACE_NCX,\n                        NCXTags.navLabel);\n        assert navLabel != null;\n        return DOMUtil.getTextChildrenContent(DOMUtil\n                .getFirstElementByTagNameNS(navLabel, NAMESPACE_NCX, NCXTags.text));\n    }\n\n    @SuppressWarnings(\"unused\")\n    public static void write(EpubWriter epubWriter, EpubBook book,\n                             ZipOutputStream resultStream) throws IOException {\n        resultStream\n                .putNextEntry(new ZipEntry(book.getSpine().getTocResource().getHref()));\n        XmlSerializer out = EpubProcessorSupport.createXmlSerializer(resultStream);\n        write(out, book);\n        out.flush();\n    }\n\n\n    /**\n     * Generates a resource containing an xml document containing the table of contents of the book in ncx format.\n     *\n     * @param xmlSerializer the serializer used\n     * @param book          the book to serialize\n     * @throws IOException              IOException\n     * @throws IllegalStateException    IllegalStateException\n     * @throws IllegalArgumentException IllegalArgumentException\n     */\n    public static void write(XmlSerializer xmlSerializer, EpubBook book)\n            throws IllegalArgumentException, IllegalStateException, IOException {\n        write(xmlSerializer, book.getMetadata().getIdentifiers(), book.getTitle(),\n                book.getMetadata().getAuthors(), book.getTableOfContents());\n    }\n\n    public static Resource createNCXResource(EpubBook book)\n            throws IllegalArgumentException, IllegalStateException, IOException {\n        return createNCXResource(book.getMetadata().getIdentifiers(),\n                book.getTitle(), book.getMetadata().getAuthors(),\n                book.getTableOfContents());\n    }\n\n    public static Resource createNCXResource(List<Identifier> identifiers,\n                                             String title, List<Author> authors, TableOfContents tableOfContents)\n            throws IllegalArgumentException, IllegalStateException, IOException {\n        ByteArrayOutputStream data = new ByteArrayOutputStream();\n        XmlSerializer out = EpubProcessorSupport.createXmlSerializer(data);\n        write(out, identifiers, title, authors, tableOfContents);\n        return new Resource(NCX_ITEM_ID, data.toByteArray(),\n                DEFAULT_NCX_HREF, MediaTypes.NCX);\n    }\n\n    public static void write(XmlSerializer serializer,\n                             List<Identifier> identifiers, String title, List<Author> authors,\n                             TableOfContents tableOfContents)\n            throws IllegalArgumentException, IllegalStateException, IOException {\n        serializer.startDocument(Constants.CHARACTER_ENCODING, false);\n        serializer.setPrefix(EpubWriter.EMPTY_NAMESPACE_PREFIX, NAMESPACE_NCX);\n        serializer.startTag(NAMESPACE_NCX, NCXTags.ncx);\n//\t\tserializer.writeNamespace(\"ncx\", NAMESPACE_NCX);\n//\t\tserializer.attribute(\"xmlns\", NAMESPACE_NCX);\n        serializer\n                .attribute(EpubWriter.EMPTY_NAMESPACE_PREFIX, NCXAttributes.version,\n                        NCXAttributeValues.version);\n        serializer.startTag(NAMESPACE_NCX, NCXTags.head);\n\n        for (Identifier identifier : identifiers) {\n            writeMetaElement(identifier.getScheme(), identifier.getValue(),\n                    serializer);\n        }\n\n        writeMetaElement(\"generator\", Constants.EPUB_GENERATOR_NAME, serializer);\n        writeMetaElement(\"depth\", String.valueOf(tableOfContents.calculateDepth()),\n                serializer);\n        writeMetaElement(\"totalPageCount\", \"0\", serializer);\n        writeMetaElement(\"maxPageNumber\", \"0\", serializer);\n\n        serializer.endTag(NAMESPACE_NCX, \"head\");\n\n        serializer.startTag(NAMESPACE_NCX, NCXTags.docTitle);\n        serializer.startTag(NAMESPACE_NCX, NCXTags.text);\n        // write the first title\n        serializer.text(StringUtil.defaultIfNull(title));\n        serializer.endTag(NAMESPACE_NCX, NCXTags.text);\n        serializer.endTag(NAMESPACE_NCX, NCXTags.docTitle);\n\n        for (Author author : authors) {\n            serializer.startTag(NAMESPACE_NCX, NCXTags.docAuthor);\n            serializer.startTag(NAMESPACE_NCX, NCXTags.text);\n            serializer.text(author.getLastname() + \", \" + author.getFirstname());\n            serializer.endTag(NAMESPACE_NCX, NCXTags.text);\n            serializer.endTag(NAMESPACE_NCX, NCXTags.docAuthor);\n        }\n\n        serializer.startTag(NAMESPACE_NCX, NCXTags.navMap);\n        writeNavPoints(tableOfContents.getTocReferences(), 1, serializer);\n        serializer.endTag(NAMESPACE_NCX, NCXTags.navMap);\n\n        serializer.endTag(NAMESPACE_NCX, \"ncx\");\n        serializer.endDocument();\n    }\n\n\n    private static void writeMetaElement(String dtbName, String content,\n                                         XmlSerializer serializer)\n            throws IllegalArgumentException, IllegalStateException, IOException {\n        serializer.startTag(NAMESPACE_NCX, NCXTags.meta);\n        serializer.attribute(EpubWriter.EMPTY_NAMESPACE_PREFIX, NCXAttributes.name,\n                PREFIX_DTB + \":\" + dtbName);\n        serializer\n                .attribute(EpubWriter.EMPTY_NAMESPACE_PREFIX, NCXAttributes.content,\n                        content);\n        serializer.endTag(NAMESPACE_NCX, NCXTags.meta);\n    }\n\n    private static int writeNavPoints(List<TOCReference> tocReferences,\n                                      int playOrder,\n                                      XmlSerializer serializer)\n            throws IllegalArgumentException, IllegalStateException, IOException {\n        for (TOCReference tocReference : tocReferences) {\n            if (tocReference.getResource() == null) {\n                playOrder = writeNavPoints(tocReference.getChildren(), playOrder,\n                        serializer);\n                continue;\n            }\n            writeNavPointStart(tocReference, playOrder, serializer);\n            playOrder++;\n            if (!tocReference.getChildren().isEmpty()) {\n                playOrder = writeNavPoints(tocReference.getChildren(), playOrder,\n                        serializer);\n            }\n            writeNavPointEnd(tocReference, serializer);\n        }\n        return playOrder;\n    }\n\n\n    private static void writeNavPointStart(TOCReference tocReference,\n                                           int playOrder, XmlSerializer serializer)\n            throws IllegalArgumentException, IllegalStateException, IOException {\n        serializer.startTag(NAMESPACE_NCX, NCXTags.navPoint);\n        serializer.attribute(EpubWriter.EMPTY_NAMESPACE_PREFIX, NCXAttributes.id,\n                \"navPoint-\" + playOrder);\n        serializer\n                .attribute(EpubWriter.EMPTY_NAMESPACE_PREFIX, NCXAttributes.playOrder,\n                        String.valueOf(playOrder));\n        serializer.attribute(EpubWriter.EMPTY_NAMESPACE_PREFIX, NCXAttributes.clazz,\n                NCXAttributeValues.chapter);\n        serializer.startTag(NAMESPACE_NCX, NCXTags.navLabel);\n        serializer.startTag(NAMESPACE_NCX, NCXTags.text);\n        serializer.text(tocReference.getTitle());\n        serializer.endTag(NAMESPACE_NCX, NCXTags.text);\n        serializer.endTag(NAMESPACE_NCX, NCXTags.navLabel);\n        serializer.startTag(NAMESPACE_NCX, NCXTags.content);\n        serializer.attribute(EpubWriter.EMPTY_NAMESPACE_PREFIX, NCXAttributes.src,\n                tocReference.getCompleteHref());\n        serializer.endTag(NAMESPACE_NCX, NCXTags.content);\n    }\n\n    @SuppressWarnings(\"unused\")\n    private static void writeNavPointEnd(TOCReference tocReference,\n                                         XmlSerializer serializer)\n            throws IllegalArgumentException, IllegalStateException, IOException {\n        serializer.endTag(NAMESPACE_NCX, NCXTags.navPoint);\n    }\n}\n"
  },
  {
    "path": "modules/book/src/main/java/me/ag2s/epublib/epub/NCXDocumentV3.java",
    "content": "package me.ag2s.epublib.epub;\n\nimport android.util.Log;\n\nimport org.w3c.dom.Document;\nimport org.w3c.dom.Element;\nimport org.w3c.dom.Node;\nimport org.w3c.dom.NodeList;\nimport org.xmlpull.v1.XmlSerializer;\n\nimport java.io.ByteArrayOutputStream;\nimport java.io.IOException;\nimport java.io.UnsupportedEncodingException;\nimport java.net.URLDecoder;\nimport java.util.ArrayList;\nimport java.util.List;\n\nimport me.ag2s.epublib.Constants;\nimport me.ag2s.epublib.domain.Author;\nimport me.ag2s.epublib.domain.EpubBook;\nimport me.ag2s.epublib.domain.Identifier;\nimport me.ag2s.epublib.domain.MediaType;\nimport me.ag2s.epublib.domain.MediaTypes;\nimport me.ag2s.epublib.domain.Resource;\nimport me.ag2s.epublib.domain.TOCReference;\nimport me.ag2s.epublib.domain.TableOfContents;\nimport me.ag2s.epublib.util.ResourceUtil;\nimport me.ag2s.epublib.util.StringUtil;\n\n/**\n * Writes the ncx document as defined by namespace http://www.daisy.org/z3986/2005/ncx/\n *\n * @author Ag2S20150909\n */\n\npublic class NCXDocumentV3 {\n    public static final String NAMESPACE_XHTML = \"http://www.w3.org/1999/xhtml\";\n    public static final String NAMESPACE_EPUB = \"http://www.idpf.org/2007/ops\";\n    public static final String LANGUAGE = \"en\";\n    @SuppressWarnings(\"unused\")\n    public static final String PREFIX_XHTML = \"html\";\n    public static final String NCX_ITEM_ID = \"htmltoc\";\n    public static final String DEFAULT_NCX_HREF = \"toc.xhtml\";\n    public static final String V3_NCX_PROPERTIES = \"nav\";\n    public static final MediaType V3_NCX_MEDIATYPE = MediaTypes.XHTML;\n\n    private static final String TAG = NCXDocumentV3.class.getName();\n\n    private interface XHTMLTgs {\n        String html = \"html\";\n        String head = \"head\";\n        String title = \"title\";\n        String meta = \"meta\";\n        String link = \"link\";\n        String body = \"body\";\n        String h1 = \"h1\";\n        String h2 = \"h2\";\n        String nav = \"nav\";\n        String ol = \"ol\";\n        String li = \"li\";\n        String a = \"a\";\n        String span = \"span\";\n    }\n\n    private interface XHTMLAttributes {\n        String xmlns = \"xmlns\";\n        String xmlns_epub = \"xmlns:epub\";\n        String lang = \"lang\";\n        String xml_lang = \"xml:lang\";\n        String rel = \"rel\";\n        String type = \"type\";\n        String epub_type = \"epub:type\";//nav的必须属性\n        String id = \"id\";\n        String role = \"role\";\n        String href = \"href\";\n        String http_equiv = \"http-equiv\";\n        String content = \"content\";\n\n    }\n\n    private interface XHTMLAttributeValues {\n        String Content_Type = \"Content-Type\";\n        String HTML_UTF8 = \"text/html; charset=utf-8\";\n        String lang = \"en\";\n        String epub_type = \"toc\";\n        String role_toc = \"doc-toc\";\n\n    }\n\n\n    /**\n     * 解析epub的目录文件\n     *\n     * @param book       Book\n     * @param epubReader epubreader\n     * @return Resource\n     */\n    @SuppressWarnings(\"unused\")\n    public static Resource read(EpubBook book, EpubReader epubReader) {\n        Resource ncxResource = null;\n        if (book.getSpine().getTocResource() == null) {\n            Log.e(TAG, \"Book does not contain a table of contents file\");\n            return null;\n        }\n        try {\n            ncxResource = book.getSpine().getTocResource();\n            if (ncxResource == null) {\n                return null;\n            }\n            //一些epub 3 文件没有按照epub3的标准使用删除掉ncx目录文件\n            if (ncxResource.getHref().endsWith(\".ncx\")) {\n                Log.v(TAG, \"该epub文件不标准，使用了epub2的目录文件\");\n                return NCXDocumentV2.read(book, epubReader);\n            }\n            Log.d(TAG, ncxResource.getHref());\n\n            Document ncxDocument = ResourceUtil.getAsDocument(ncxResource);\n            Log.d(TAG, ncxDocument.getNodeName());\n\n            Element navMapElement = (Element) ncxDocument.getElementsByTagName(XHTMLTgs.nav).item(0);\n            if (navMapElement == null) {\n                Log.d(TAG, \"epub3目录文件未发现nav节点，尝试使用epub2的规则解析\");\n                return NCXDocumentV2.read(book, epubReader);\n            }\n            navMapElement = (Element) navMapElement.getElementsByTagName(XHTMLTgs.ol).item(0);\n            Log.d(TAG, navMapElement.getTagName());\n\n            TableOfContents tableOfContents = new TableOfContents(\n                    readTOCReferences(navMapElement.getChildNodes(), book));\n            Log.d(TAG, tableOfContents.toString());\n            book.setTableOfContents(tableOfContents);\n        } catch (Exception e) {\n            Log.e(TAG, e.getMessage(), e);\n        }\n        return ncxResource;\n    }\n\n    private static List<TOCReference> doToc(Node n, EpubBook book) {\n        if (n == null || n.getNodeType() != Document.ELEMENT_NODE) {\n            return new ArrayList<>();\n        }\n\n        Element el = (Element) n;\n        Node node = el.getElementsByTagName(XHTMLTgs.ol).item(0);\n\n        if (node == null || node.getNodeType() != Document.ELEMENT_NODE) {\n            return new ArrayList<>();\n        }\n\n        return readTOCReferences(node.getChildNodes(), book);\n    }\n\n\n    static List<TOCReference> readTOCReferences(NodeList navpoints,\n                                                EpubBook book) {\n        if (navpoints == null) {\n            return new ArrayList<>();\n        }\n        //Log.d(TAG, \"readTOCReferences:navpoints.getLength()\" + navpoints.getLength());\n        List<TOCReference> result = new ArrayList<>(navpoints.getLength());\n        for (int i = 0; i < navpoints.getLength(); i++) {\n            Node node = navpoints.item(i);\n            //如果该node是null,或者不是Element,跳出本次循环\n            if (node == null || node.getNodeType() != Document.ELEMENT_NODE) {\n                continue;\n            }\n\n            Element el = (Element) node;\n            //如果该Element的name为”li“,将其添加到目录结果\n            if (el.getTagName().equals(XHTMLTgs.li)) {\n                result.add(readTOCReference(el, book));\n            }\n\n        }\n\n\n        return result;\n    }\n\n\n    static TOCReference readTOCReference(Element navpointElement, EpubBook book) {\n        //章节的名称\n        String label = readNavLabel(navpointElement);\n        //Log.d(TAG, \"label:\" + label);\n        String tocResourceRoot = StringUtil\n                .substringBeforeLast(book.getSpine().getTocResource().getHref(), '/');\n        if (tocResourceRoot.length() == book.getSpine().getTocResource().getHref()\n                .length()) {\n            tocResourceRoot = \"\";\n        } else {\n            tocResourceRoot = tocResourceRoot + \"/\";\n        }\n\n        String reference = StringUtil\n                .collapsePathDots(tocResourceRoot + readNavReference(navpointElement));\n        String href = StringUtil\n                .substringBefore(reference, Constants.FRAGMENT_SEPARATOR_CHAR);\n        String fragmentId = StringUtil\n                .substringAfter(reference, Constants.FRAGMENT_SEPARATOR_CHAR);\n        Resource resource = book.getResources().getByHref(href);\n        if (resource == null) {\n            Log.e(TAG, \"Resource with href \" + href + \" in NCX document not found\");\n        }\n//        Log.v(TAG, \"label:\" + label);\n//        Log.v(TAG, \"href:\" + href);\n//        Log.v(TAG, \"fragmentId:\" + fragmentId);\n\n        //父级目录\n        TOCReference result = new TOCReference(label, resource, fragmentId);\n        //解析子级目录\n        List<TOCReference> childTOCReferences = doToc(navpointElement, book);\n        //readTOCReferences(\n        //navpointElement.getChildNodes(), book);\n        result.setChildren(childTOCReferences);\n        return result;\n    }\n\n    /**\n     * 获取目录节点的href\n     *\n     * @param navpointElement navpointElement\n     * @return String\n     */\n    private static String readNavReference(Element navpointElement) {\n        //https://www.w3.org/publishing/epub/epub-packages.html#sec-package-nav\n        //父级节点必须是 \"li\"\n        //Log.d(TAG, \"readNavReference:\" + navpointElement.getTagName());\n\n        Element contentElement = DOMUtil\n                .getFirstElementByTagNameNS(navpointElement, \"\", XHTMLTgs.a);\n        if (contentElement == null) {\n            return null;\n        }\n        String result = DOMUtil\n                .getAttribute(contentElement, \"\", XHTMLAttributes.href);\n        try {\n            result = URLDecoder.decode(result, Constants.CHARACTER_ENCODING);\n        } catch (UnsupportedEncodingException e) {\n            Log.e(TAG, e.getMessage());\n        }\n\n        return result;\n\n    }\n\n    /**\n     * 获取目录节点里面的章节名\n     *\n     * @param navpointElement navpointElement\n     * @return String\n     */\n    private static String readNavLabel(Element navpointElement) {\n        //https://www.w3.org/publishing/epub/epub-packages.html#sec-package-nav\n        //父级节点必须是 \"li\"\n        //Log.d(TAG, \"readNavLabel:\" + navpointElement.getTagName());\n        String label;\n        Element labelElement = DOMUtil.getFirstElementByTagNameNS(navpointElement, \"\", \"a\");\n        assert labelElement != null;\n        label = labelElement.getTextContent();\n        if (StringUtil.isNotBlank(label)) {\n            return label;\n        } else {\n            labelElement = DOMUtil.getFirstElementByTagNameNS(navpointElement, \"\", \"span\");\n        }\n        assert labelElement != null;\n        label = labelElement.getTextContent();\n        //如果通过 a 标签无法获取章节列表,则是无href章节名\n        return label;\n\n    }\n\n    public static Resource createNCXResource(EpubBook book)\n            throws IllegalArgumentException, IllegalStateException, IOException {\n        return createNCXResource(book.getMetadata().getIdentifiers(),\n                book.getTitle(), book.getMetadata().getAuthors(),\n                book.getTableOfContents());\n    }\n\n    public static Resource createNCXResource(List<Identifier> identifiers,\n                                             String title, List<Author> authors, TableOfContents tableOfContents)\n            throws IllegalArgumentException, IllegalStateException, IOException {\n        ByteArrayOutputStream data = new ByteArrayOutputStream();\n        XmlSerializer out = EpubProcessorSupport.createXmlSerializer(data);\n        write(out, identifiers, title, authors, tableOfContents);\n\n        Resource resource = new Resource(NCX_ITEM_ID, data.toByteArray(),\n                DEFAULT_NCX_HREF, V3_NCX_MEDIATYPE);\n        resource.setProperties(V3_NCX_PROPERTIES);\n        return resource;\n    }\n\n    /**\n     * Generates a resource containing an xml document containing the table of contents of the book in ncx format.\n     *\n     * @param xmlSerializer the serializer used\n     * @param book          the book to serialize\n     * @throws IOException              IOException\n     * @throws IllegalStateException    IllegalStateException\n     * @throws IllegalArgumentException IllegalArgumentException\n     */\n    public static void write(XmlSerializer xmlSerializer, EpubBook book)\n            throws IllegalArgumentException, IllegalStateException, IOException {\n        write(xmlSerializer, book.getMetadata().getIdentifiers(), book.getTitle(),\n                book.getMetadata().getAuthors(), book.getTableOfContents());\n    }\n\n    /**\n     * 写入\n     *\n     * @param serializer      serializer\n     * @param identifiers     identifiers\n     * @param title           title\n     * @param authors         authors\n     * @param tableOfContents tableOfContents\n     */\n    @SuppressWarnings(\"unused\")\n    public static void write(XmlSerializer serializer,\n                             List<Identifier> identifiers, String title, List<Author> authors,\n                             TableOfContents tableOfContents) throws IllegalArgumentException, IllegalStateException, IOException {\n        serializer.startDocument(Constants.CHARACTER_ENCODING, false);\n        serializer.setPrefix(EpubWriter.EMPTY_NAMESPACE_PREFIX, NAMESPACE_XHTML);\n        serializer.startTag(NAMESPACE_XHTML, XHTMLTgs.html);\n        serializer.attribute(EpubWriter.EMPTY_NAMESPACE_PREFIX, XHTMLAttributes.xmlns_epub, NAMESPACE_EPUB);\n        serializer.attribute(EpubWriter.EMPTY_NAMESPACE_PREFIX, XHTMLAttributes.xml_lang, XHTMLAttributeValues.lang);\n        serializer.attribute(EpubWriter.EMPTY_NAMESPACE_PREFIX, XHTMLAttributes.lang, LANGUAGE);\n        //写入头部head标签\n        writeHead(title, serializer);\n        //body开始\n        serializer.startTag(NAMESPACE_XHTML, XHTMLTgs.body);\n        //h1开始\n        serializer.startTag(NAMESPACE_XHTML, XHTMLTgs.h1);\n        serializer.text(title);\n        serializer.endTag(NAMESPACE_XHTML, XHTMLTgs.h1);\n        //h1关闭\n        //nav开始\n        serializer.startTag(NAMESPACE_XHTML, XHTMLTgs.nav);\n        serializer.attribute(\"\", XHTMLAttributes.epub_type, XHTMLAttributeValues.epub_type);\n        serializer.attribute(\"\", XHTMLAttributes.id, XHTMLAttributeValues.epub_type);\n        serializer.attribute(\"\", XHTMLAttributes.role, XHTMLAttributeValues.role_toc);\n        //h2开始\n        serializer.startTag(NAMESPACE_XHTML, XHTMLTgs.h2);\n        serializer.text(\"目录\");\n        serializer.endTag(NAMESPACE_XHTML, XHTMLTgs.h2);\n\n\n        writeNavPoints(tableOfContents.getTocReferences(), 1, serializer);\n\n\n        serializer.endTag(NAMESPACE_XHTML, XHTMLTgs.nav);\n\n        //body关闭\n        serializer.endTag(NAMESPACE_XHTML, XHTMLTgs.body);\n\n\n        serializer.endTag(NAMESPACE_XHTML, XHTMLTgs.html);\n        serializer.endDocument();\n\n    }\n\n    private static int writeNavPoints(List<TOCReference> tocReferences,\n                                      int playOrder,\n                                      XmlSerializer serializer) throws IOException {\n        writeOlStart(serializer);\n        for (TOCReference tocReference : tocReferences) {\n            if (tocReference.getResource() == null) {\n                playOrder = writeNavPoints(tocReference.getChildren(), playOrder,\n                        serializer);\n                continue;\n            }\n\n\n            writeNavPointStart(tocReference, serializer);\n\n            playOrder++;\n            if (!tocReference.getChildren().isEmpty()) {\n                playOrder = writeNavPoints(tocReference.getChildren(), playOrder,\n                        serializer);\n            }\n\n            writeNavPointEnd(tocReference, serializer);\n        }\n        writeOlSEnd(serializer);\n        return playOrder;\n    }\n\n    private static void writeNavPointStart(TOCReference tocReference, XmlSerializer serializer) throws IOException {\n        writeLiStart(serializer);\n        String title = tocReference.getTitle();\n        String href = tocReference.getCompleteHref();\n        if (StringUtil.isNotBlank(href)) {\n            writeLabel(title, href, serializer);\n        } else {\n            writeLabel(title, serializer);\n        }\n    }\n\n    @SuppressWarnings(\"unused\")\n    private static void writeNavPointEnd(TOCReference tocReference,\n                                         XmlSerializer serializer) throws IOException {\n        writeLiEnd(serializer);\n    }\n\n    protected static void writeLabel(String title, String href, XmlSerializer serializer) throws IOException {\n        serializer.startTag(NAMESPACE_XHTML, XHTMLTgs.a);\n        serializer.attribute(\"\", XHTMLAttributes.href, href);\n        //attribute必须在Text之前设置。\n        serializer.text(title);\n        //serializer.attribute(NAMESPACE_XHTML, XHTMLAttributes.href, href);\n        serializer.endTag(NAMESPACE_XHTML, XHTMLTgs.a);\n    }\n\n    protected static void writeLabel(String title, XmlSerializer serializer) throws IOException {\n        serializer.startTag(NAMESPACE_XHTML, XHTMLTgs.span);\n        serializer.text(title);\n        serializer.endTag(NAMESPACE_XHTML, XHTMLTgs.span);\n    }\n\n    private static void writeLiStart(XmlSerializer serializer) throws IOException {\n        serializer.startTag(NAMESPACE_XHTML, XHTMLTgs.li);\n        Log.d(TAG, \"writeLiStart\");\n    }\n\n    private static void writeLiEnd(XmlSerializer serializer) throws IOException {\n        serializer.endTag(NAMESPACE_XHTML, XHTMLTgs.li);\n        Log.d(TAG, \"writeLiEND\");\n    }\n\n    private static void writeOlStart(XmlSerializer serializer) throws IOException {\n        serializer.startTag(NAMESPACE_XHTML, XHTMLTgs.ol);\n        Log.d(TAG, \"writeOlStart\");\n    }\n\n    private static void writeOlSEnd(XmlSerializer serializer) throws IOException {\n        serializer.endTag(NAMESPACE_XHTML, XHTMLTgs.ol);\n        Log.d(TAG, \"writeOlEnd\");\n    }\n\n    private static void writeHead(String title, XmlSerializer serializer) throws IOException {\n        serializer.startTag(NAMESPACE_XHTML, XHTMLTgs.head);\n        //title\n        serializer.startTag(NAMESPACE_XHTML, XHTMLTgs.title);\n        serializer.text(StringUtil.defaultIfNull(title));\n        serializer.endTag(NAMESPACE_XHTML, XHTMLTgs.title);\n        //link\n        serializer.startTag(NAMESPACE_XHTML, XHTMLTgs.link);\n        serializer.attribute(\"\", XHTMLAttributes.rel, \"stylesheet\");\n        serializer.attribute(\"\", XHTMLAttributes.type, \"text/css\");\n        serializer.attribute(\"\", XHTMLAttributes.href, \"css/style.css\");\n        serializer.endTag(NAMESPACE_XHTML, XHTMLTgs.link);\n\n        //meta\n        serializer.startTag(NAMESPACE_XHTML, XHTMLTgs.meta);\n        serializer.attribute(\"\", XHTMLAttributes.http_equiv, XHTMLAttributeValues.Content_Type);\n        serializer.attribute(\"\", XHTMLAttributes.content, XHTMLAttributeValues.HTML_UTF8);\n        serializer.endTag(NAMESPACE_XHTML, XHTMLTgs.meta);\n\n        serializer.endTag(NAMESPACE_XHTML, XHTMLTgs.head);\n    }\n\n\n}\n"
  },
  {
    "path": "modules/book/src/main/java/me/ag2s/epublib/epub/PackageDocumentBase.java",
    "content": "package me.ag2s.epublib.epub;\n\n\n/**\n * Functionality shared by the PackageDocumentReader and the PackageDocumentWriter\n *\n * @author paul\n */\npublic class PackageDocumentBase {\n\n    public static final String BOOK_ID_ID = \"duokan-book-id\";\n    public static final String NAMESPACE_OPF = \"http://www.idpf.org/2007/opf\";\n    public static final String NAMESPACE_DUBLIN_CORE = \"http://purl.org/dc/elements/1.1/\";\n    public static final String PREFIX_DUBLIN_CORE = \"dc\";\n    //public static final String PREFIX_OPF = \"opf\";\n    //在EPUB3标准中，packge前面没有opf头，一些epub阅读器也不支持opf头。\n    //Some Epub Reader not reconize op:packge,So just let it empty;\n    public static final String PREFIX_OPF = \"\";\n    //添加 version 变量来区分Epub文件的版本\n    //Add the version field to distinguish the version of EPUB file\n    public static final String version = \"version\";\n    public static final String dateFormat = \"yyyy-MM-dd\";\n\n    protected interface DCTags {\n\n        String title = \"title\";\n        String creator = \"creator\";\n        String subject = \"subject\";\n        String description = \"description\";\n        String publisher = \"publisher\";\n        String contributor = \"contributor\";\n        String date = \"date\";\n        String type = \"type\";\n        String format = \"format\";\n        String identifier = \"identifier\";\n        String source = \"source\";\n        String language = \"language\";\n        String relation = \"relation\";\n        String coverage = \"coverage\";\n        String rights = \"rights\";\n    }\n\n    protected interface DCAttributes {\n\n        String scheme = \"scheme\";\n        String id = \"id\";\n    }\n\n    protected interface OPFTags {\n\n        String metadata = \"metadata\";\n        String meta = \"meta\";\n        String manifest = \"manifest\";\n        String packageTag = \"package\";\n        String itemref = \"itemref\";\n        String spine = \"spine\";\n        String reference = \"reference\";\n        String guide = \"guide\";\n        String item = \"item\";\n    }\n\n    protected interface OPFAttributes {\n\n        String uniqueIdentifier = \"unique-identifier\";\n        String idref = \"idref\";\n        String name = \"name\";\n        String content = \"content\";\n        String type = \"type\";\n        String href = \"href\";\n        String linear = \"linear\";\n        String event = \"event\";\n        String role = \"role\";\n        String file_as = \"file-as\";\n        String id = \"id\";\n        String media_type = \"media-type\";\n        String title = \"title\";\n        String toc = \"toc\";\n        String version = \"version\";\n        String scheme = \"scheme\";\n        String property = \"property\";\n        //add for epub3\n        /**\n         * add for epub3\n         */\n        String properties = \"properties\";\n    }\n\n    protected interface OPFValues {\n\n        String meta_cover = \"cover\";\n        String reference_cover = \"cover\";\n        String no = \"no\";\n        String generator = \"generator\";\n        String duokan = \"duokan-body-font\";\n    }\n}"
  },
  {
    "path": "modules/book/src/main/java/me/ag2s/epublib/epub/PackageDocumentMetadataReader.java",
    "content": "package me.ag2s.epublib.epub;\n\nimport android.util.Log;\n\nimport org.w3c.dom.Document;\nimport org.w3c.dom.Element;\nimport org.w3c.dom.Node;\nimport org.w3c.dom.NodeList;\n\nimport java.util.ArrayList;\nimport java.util.HashMap;\nimport java.util.List;\nimport java.util.Map;\n\nimport javax.xml.namespace.QName;\n\nimport me.ag2s.epublib.domain.Author;\nimport me.ag2s.epublib.domain.Date;\nimport me.ag2s.epublib.domain.Identifier;\nimport me.ag2s.epublib.domain.Metadata;\nimport me.ag2s.epublib.util.StringUtil;\n\n/**\n * Reads the package document metadata.\n * <p>\n * In its own separate class because the PackageDocumentReader became a bit large and unwieldy.\n *\n * @author paul\n */\n// package\nclass PackageDocumentMetadataReader extends PackageDocumentBase {\n\n    private static final String TAG = PackageDocumentMetadataReader.class.getName();\n\n    public static Metadata readMetadata(Document packageDocument) {\n        Metadata result = new Metadata();\n        Element metadataElement = DOMUtil\n                .getFirstElementByTagNameNS(packageDocument.getDocumentElement(),\n                        NAMESPACE_OPF, OPFTags.metadata);\n        if (metadataElement == null) {\n            Log.e(TAG, \"Package does not contain element \" + OPFTags.metadata);\n            return result;\n        }\n        result.setTitles(DOMUtil\n                .getElementsTextChild(metadataElement, NAMESPACE_DUBLIN_CORE,\n                        DCTags.title));\n        result.setPublishers(DOMUtil\n                .getElementsTextChild(metadataElement, NAMESPACE_DUBLIN_CORE,\n                        DCTags.publisher));\n        result.setDescriptions(DOMUtil\n                .getElementsTextChild(metadataElement, NAMESPACE_DUBLIN_CORE,\n                        DCTags.description));\n        result.setRights(DOMUtil\n                .getElementsTextChild(metadataElement, NAMESPACE_DUBLIN_CORE,\n                        DCTags.rights));\n        result.setTypes(DOMUtil\n                .getElementsTextChild(metadataElement, NAMESPACE_DUBLIN_CORE,\n                        DCTags.type));\n        result.setSubjects(DOMUtil\n                .getElementsTextChild(metadataElement, NAMESPACE_DUBLIN_CORE,\n                        DCTags.subject));\n        result.setIdentifiers(readIdentifiers(metadataElement));\n        result.setAuthors(readCreators(metadataElement));\n        result.setContributors(readContributors(metadataElement));\n        result.setDates(readDates(metadataElement));\n        result.setOtherProperties(readOtherProperties(metadataElement));\n        result.setMetaAttributes(readMetaProperties(metadataElement));\n        Element languageTag = DOMUtil\n                .getFirstElementByTagNameNS(metadataElement, NAMESPACE_DUBLIN_CORE,\n                        DCTags.language);\n        if (languageTag != null) {\n            result.setLanguage(DOMUtil.getTextChildrenContent(languageTag));\n        }\n\n        return result;\n    }\n\n    /**\n     * consumes meta tags that have a property attribute as defined in the standard. For example:\n     * &lt;meta property=\"rendition:layout\"&gt;pre-paginated&lt;/meta&gt;\n     *\n     * @param metadataElement metadataElement\n     * @return Map<QName, String>\n     */\n    private static Map<QName, String> readOtherProperties(\n            Element metadataElement) {\n        Map<QName, String> result = new HashMap<>();\n\n        NodeList metaTags = metadataElement.getElementsByTagName(OPFTags.meta);\n        for (int i = 0; i < metaTags.getLength(); i++) {\n            Node metaNode = metaTags.item(i);\n            Node property = metaNode.getAttributes()\n                    .getNamedItem(OPFAttributes.property);\n            if (property != null) {\n                String name = property.getNodeValue();\n                String value = metaNode.getTextContent();\n                result.put(new QName(name), value);\n            }\n        }\n\n        return result;\n    }\n\n    /**\n     * consumes meta tags that have a property attribute as defined in the standard. For example:\n     * &lt;meta property=\"rendition:layout\"&gt;pre-paginated&lt;/meta&gt;\n     *\n     * @param metadataElement metadataElement\n     * @return Map<String, String>\n     */\n    private static Map<String, String> readMetaProperties(\n            Element metadataElement) {\n        Map<String, String> result = new HashMap<>();\n\n        NodeList metaTags = metadataElement.getElementsByTagName(OPFTags.meta);\n        for (int i = 0; i < metaTags.getLength(); i++) {\n            Element metaElement = (Element) metaTags.item(i);\n            String name = metaElement.getAttribute(OPFAttributes.name);\n            String value = metaElement.getAttribute(OPFAttributes.content);\n            result.put(name, value);\n        }\n\n        return result;\n    }\n\n    private static String getBookIdId(Document document) {\n        Element packageElement = DOMUtil\n                .getFirstElementByTagNameNS(document.getDocumentElement(),\n                        NAMESPACE_OPF, OPFTags.packageTag);\n        if (packageElement == null) {\n            return null;\n        }\n        return DOMUtil.getAttribute(packageElement, NAMESPACE_OPF, OPFAttributes.uniqueIdentifier);\n\n    }\n\n    private static List<Author> readCreators(Element metadataElement) {\n        return readAuthors(DCTags.creator, metadataElement);\n    }\n\n    private static List<Author> readContributors(Element metadataElement) {\n        return readAuthors(DCTags.contributor, metadataElement);\n    }\n\n    private static List<Author> readAuthors(String authorTag,\n                                            Element metadataElement) {\n        NodeList elements = metadataElement\n                .getElementsByTagNameNS(NAMESPACE_DUBLIN_CORE, authorTag);\n        List<Author> result = new ArrayList<>(elements.getLength());\n        for (int i = 0; i < elements.getLength(); i++) {\n            Element authorElement = (Element) elements.item(i);\n            Author author = createAuthor(authorElement);\n            if (author != null) {\n                result.add(author);\n            }\n        }\n        return result;\n\n    }\n\n    private static List<Date> readDates(Element metadataElement) {\n        NodeList elements = metadataElement\n                .getElementsByTagNameNS(NAMESPACE_DUBLIN_CORE, DCTags.date);\n        List<Date> result = new ArrayList<>(elements.getLength());\n        for (int i = 0; i < elements.getLength(); i++) {\n            Element dateElement = (Element) elements.item(i);\n            Date date;\n            try {\n                date = new Date(DOMUtil.getTextChildrenContent(dateElement),\n                        DOMUtil.getAttribute(dateElement, NAMESPACE_OPF, OPFAttributes.event));\n                result.add(date);\n            } catch (IllegalArgumentException e) {\n                Log.e(TAG, e.getMessage());\n            }\n        }\n        return result;\n\n    }\n\n    private static Author createAuthor(Element authorElement) {\n        String authorString = DOMUtil.getTextChildrenContent(authorElement);\n        if (StringUtil.isBlank(authorString)) {\n            return null;\n        }\n        int spacePos = authorString.lastIndexOf(' ');\n        Author result;\n        if (spacePos < 0) {\n            result = new Author(authorString);\n        } else {\n            result = new Author(authorString.substring(0, spacePos),\n                    authorString.substring(spacePos + 1));\n        }\n        result.setRole(\n                DOMUtil.getAttribute(authorElement, NAMESPACE_OPF, OPFAttributes.role));\n        return result;\n    }\n\n\n    private static List<Identifier> readIdentifiers(Element metadataElement) {\n        NodeList identifierElements = metadataElement\n                .getElementsByTagNameNS(NAMESPACE_DUBLIN_CORE, DCTags.identifier);\n        if (identifierElements.getLength() == 0) {\n            Log.e(TAG, \"Package does not contain element \" + DCTags.identifier);\n            return new ArrayList<>();\n        }\n        String bookIdId = getBookIdId(metadataElement.getOwnerDocument());\n        List<Identifier> result = new ArrayList<>(\n                identifierElements.getLength());\n        for (int i = 0; i < identifierElements.getLength(); i++) {\n            Element identifierElement = (Element) identifierElements.item(i);\n            String schemeName = DOMUtil.getAttribute(identifierElement, NAMESPACE_OPF, DCAttributes.scheme);\n            String identifierValue = DOMUtil\n                    .getTextChildrenContent(identifierElement);\n            if (StringUtil.isBlank(identifierValue)) {\n                continue;\n            }\n            Identifier identifier = new Identifier(schemeName, identifierValue);\n            if (identifierElement.getAttribute(\"id\").equals(bookIdId)) {\n                identifier.setBookId(true);\n            }\n            result.add(identifier);\n        }\n        return result;\n    }\n}\n"
  },
  {
    "path": "modules/book/src/main/java/me/ag2s/epublib/epub/PackageDocumentMetadataWriter.java",
    "content": "package me.ag2s.epublib.epub;\n\nimport org.xmlpull.v1.XmlSerializer;\n\nimport java.io.IOException;\nimport java.util.List;\nimport java.util.Map;\n\nimport javax.xml.namespace.QName;\n\nimport me.ag2s.epublib.Constants;\nimport me.ag2s.epublib.domain.Author;\nimport me.ag2s.epublib.domain.Date;\nimport me.ag2s.epublib.domain.EpubBook;\nimport me.ag2s.epublib.domain.Identifier;\nimport me.ag2s.epublib.util.StringUtil;\n\npublic class PackageDocumentMetadataWriter extends PackageDocumentBase {\n\n    /**\n     * Writes the book's metadata.\n     *\n     * @param book       book\n     * @param serializer serializer\n     * @throws IOException              IOException\n     * @throws IllegalStateException    IllegalStateException\n     * @throws IllegalArgumentException IllegalArgumentException\n     */\n    public static void writeMetaData(EpubBook book, XmlSerializer serializer)\n            throws IllegalArgumentException, IllegalStateException, IOException {\n        serializer.startTag(NAMESPACE_OPF, OPFTags.metadata);\n        serializer.setPrefix(PREFIX_DUBLIN_CORE, NAMESPACE_DUBLIN_CORE);\n        serializer.setPrefix(PREFIX_OPF, NAMESPACE_OPF);\n\n        writeIdentifiers(book.getMetadata().getIdentifiers(), serializer);\n        writeSimpleMetdataElements(DCTags.title, book.getMetadata().getTitles(),\n                serializer);\n        writeSimpleMetdataElements(DCTags.subject, book.getMetadata().getSubjects(),\n                serializer);\n        writeSimpleMetdataElements(DCTags.description,\n                book.getMetadata().getDescriptions(), serializer);\n        writeSimpleMetdataElements(DCTags.publisher,\n                book.getMetadata().getPublishers(), serializer);\n        writeSimpleMetdataElements(DCTags.type, book.getMetadata().getTypes(),\n                serializer);\n        writeSimpleMetdataElements(DCTags.rights, book.getMetadata().getRights(),\n                serializer);\n\n        // write authors\n        for (Author author : book.getMetadata().getAuthors()) {\n            serializer.startTag(NAMESPACE_DUBLIN_CORE, DCTags.creator);\n            serializer.attribute(NAMESPACE_OPF, OPFAttributes.role,\n                    author.getRelator().getCode());\n            serializer.attribute(NAMESPACE_OPF, OPFAttributes.file_as,\n                    author.getLastname() + \", \" + author.getFirstname());\n            serializer.text(author.getFirstname() + \" \" + author.getLastname());\n            serializer.endTag(NAMESPACE_DUBLIN_CORE, DCTags.creator);\n        }\n\n        // write contributors\n        for (Author author : book.getMetadata().getContributors()) {\n            serializer.startTag(NAMESPACE_DUBLIN_CORE, DCTags.contributor);\n            serializer.attribute(NAMESPACE_OPF, OPFAttributes.role,\n                    author.getRelator().getCode());\n            serializer.attribute(NAMESPACE_OPF, OPFAttributes.file_as,\n                    author.getLastname() + \", \" + author.getFirstname());\n            serializer.text(author.getFirstname() + \" \" + author.getLastname());\n            serializer.endTag(NAMESPACE_DUBLIN_CORE, DCTags.contributor);\n        }\n\n        // write dates\n        for (Date date : book.getMetadata().getDates()) {\n            serializer.startTag(NAMESPACE_DUBLIN_CORE, DCTags.date);\n            if (date.getEvent() != null) {\n                serializer.attribute(NAMESPACE_OPF, OPFAttributes.event,\n                        date.getEvent().toString());\n            }\n            serializer.text(date.getValue());\n            serializer.endTag(NAMESPACE_DUBLIN_CORE, DCTags.date);\n        }\n\n        // write language\n        if (StringUtil.isNotBlank(book.getMetadata().getLanguage())) {\n            serializer.startTag(NAMESPACE_DUBLIN_CORE, \"language\");\n            serializer.text(book.getMetadata().getLanguage());\n            serializer.endTag(NAMESPACE_DUBLIN_CORE, \"language\");\n        }\n\n        // write other properties\n        if (book.getMetadata().getOtherProperties() != null) {\n            for (Map.Entry<QName, String> mapEntry : book.getMetadata()\n                    .getOtherProperties().entrySet()) {\n                serializer.startTag(mapEntry.getKey().getNamespaceURI(), OPFTags.meta);\n                serializer.attribute(EpubWriter.EMPTY_NAMESPACE_PREFIX,\n                        OPFAttributes.property, mapEntry.getKey().getLocalPart());\n                serializer.text(mapEntry.getValue());\n                serializer.endTag(mapEntry.getKey().getNamespaceURI(), OPFTags.meta);\n\n            }\n        }\n\n        // write coverimage\n        if (book.getCoverImage() != null) { // write the cover image\n            serializer.startTag(NAMESPACE_OPF, OPFTags.meta);\n            serializer\n                    .attribute(EpubWriter.EMPTY_NAMESPACE_PREFIX, OPFAttributes.name,\n                            OPFValues.meta_cover);\n            serializer\n                    .attribute(EpubWriter.EMPTY_NAMESPACE_PREFIX, OPFAttributes.content,\n                            book.getCoverImage().getId());\n            serializer.endTag(NAMESPACE_OPF, OPFTags.meta);\n        }\n\n        // write generator\n        serializer.startTag(NAMESPACE_OPF, OPFTags.meta);\n        serializer.attribute(EpubWriter.EMPTY_NAMESPACE_PREFIX, OPFAttributes.name,\n                OPFValues.generator);\n        serializer\n                .attribute(EpubWriter.EMPTY_NAMESPACE_PREFIX, OPFAttributes.content,\n                        Constants.EPUB_GENERATOR_NAME);\n        serializer.endTag(NAMESPACE_OPF, OPFTags.meta);\n\n        // write duokan\n        serializer.startTag(NAMESPACE_OPF, OPFTags.meta);\n        serializer.attribute(EpubWriter.EMPTY_NAMESPACE_PREFIX, OPFAttributes.name,\n                OPFValues.duokan);\n        serializer\n                .attribute(EpubWriter.EMPTY_NAMESPACE_PREFIX, OPFAttributes.content,\n                        Constants.EPUB_DUOKAN_NAME);\n        serializer.endTag(NAMESPACE_OPF, OPFTags.meta);\n\n        serializer.endTag(NAMESPACE_OPF, OPFTags.metadata);\n    }\n\n    private static void writeSimpleMetdataElements(String tagName,\n                                                   List<String> values, XmlSerializer serializer)\n            throws IllegalArgumentException, IllegalStateException, IOException {\n        for (String value : values) {\n            if (StringUtil.isBlank(value)) {\n                continue;\n            }\n            serializer.startTag(NAMESPACE_DUBLIN_CORE, tagName);\n            serializer.text(value);\n            serializer.endTag(NAMESPACE_DUBLIN_CORE, tagName);\n        }\n    }\n\n\n    /**\n     * Writes out the complete list of Identifiers to the package document.\n     * The first identifier for which the bookId is true is made the bookId identifier.\n     * If no identifier has bookId == true then the first bookId identifier is written as the primary.\n     *\n     * @param identifiers identifiers\n     * @param serializer  serializer\n     * @throws IllegalStateException    e\n     * @throws IllegalArgumentException e\n     * @\n     */\n    private static void writeIdentifiers(List<Identifier> identifiers,\n                                         XmlSerializer serializer)\n            throws IllegalArgumentException, IllegalStateException, IOException {\n        Identifier bookIdIdentifier = Identifier.getBookIdIdentifier(identifiers);\n        if (bookIdIdentifier == null) {\n            return;\n        }\n\n        serializer.startTag(NAMESPACE_DUBLIN_CORE, DCTags.identifier);\n        serializer.attribute(EpubWriter.EMPTY_NAMESPACE_PREFIX, DCAttributes.id,\n                BOOK_ID_ID);\n        serializer.attribute(NAMESPACE_OPF, OPFAttributes.scheme,\n                bookIdIdentifier.getScheme());\n        serializer.text(bookIdIdentifier.getValue());\n        serializer.endTag(NAMESPACE_DUBLIN_CORE, DCTags.identifier);\n\n        for (Identifier identifier : identifiers.subList(1, identifiers.size())) {\n            if (identifier == bookIdIdentifier) {\n                continue;\n            }\n            serializer.startTag(NAMESPACE_DUBLIN_CORE, DCTags.identifier);\n            serializer.attribute(NAMESPACE_OPF, \"scheme\", identifier.getScheme());\n            serializer.text(identifier.getValue());\n            serializer.endTag(NAMESPACE_DUBLIN_CORE, DCTags.identifier);\n        }\n    }\n\n}\n"
  },
  {
    "path": "modules/book/src/main/java/me/ag2s/epublib/epub/PackageDocumentReader.java",
    "content": "package me.ag2s.epublib.epub;\n\nimport android.util.Log;\n\nimport org.w3c.dom.Document;\nimport org.w3c.dom.Element;\nimport org.w3c.dom.NodeList;\nimport org.xml.sax.SAXException;\n\nimport java.io.IOException;\nimport java.io.UnsupportedEncodingException;\nimport java.net.URI;\nimport java.net.URISyntaxException;\nimport java.net.URLDecoder;\nimport java.net.URLEncoder;\nimport java.util.ArrayList;\nimport java.util.Collection;\nimport java.util.Collections;\nimport java.util.HashMap;\nimport java.util.HashSet;\nimport java.util.List;\nimport java.util.Map;\nimport java.util.Set;\nimport java.util.regex.Pattern;\n\nimport me.ag2s.epublib.Constants;\nimport me.ag2s.epublib.domain.EpubBook;\nimport me.ag2s.epublib.domain.Guide;\nimport me.ag2s.epublib.domain.GuideReference;\nimport me.ag2s.epublib.domain.MediaType;\nimport me.ag2s.epublib.domain.MediaTypes;\nimport me.ag2s.epublib.domain.Resource;\nimport me.ag2s.epublib.domain.Resources;\nimport me.ag2s.epublib.domain.Spine;\nimport me.ag2s.epublib.domain.SpineReference;\nimport me.ag2s.epublib.util.ResourceUtil;\nimport me.ag2s.epublib.util.StringUtil;\nimport me.ag2s.epublib.util.URLEncodeUtil;\n\n/**\n * Reads the opf package document as defined by namespace http://www.idpf.org/2007/opf\n *\n * @author paul\n */\npublic class PackageDocumentReader extends PackageDocumentBase {\n\n    private static final String TAG = PackageDocumentReader.class.getName();\n    private static final String[] POSSIBLE_NCX_ITEM_IDS = new String[]{\"toc\",\n            \"ncx\", \"ncxtoc\", \"htmltoc\"};\n    private static final Pattern namespaceRegex = Pattern.compile(\" s?mlns=\\\"\");\n\n\n    public static void read(\n            Resource packageResource, EpubReader epubReader, EpubBook book,\n            Resources resources)\n            throws SAXException, IOException {\n        /*掌上书苑有很多自制书OPF的nameSpace格式不标准，强制修复成正确的格式*/\n        String string = namespaceRegex.matcher(new String(packageResource.getData()))\n                .replaceAll(\" xmlns=\\\"\");\n        packageResource.setData(string.getBytes());\n\n        Document packageDocument = ResourceUtil.getAsDocument(packageResource);\n        String packageHref = packageResource.getHref();\n\n        URI packagePath;\n        try {\n            packagePath = new URI(packageHref);\n        } catch (URISyntaxException e) {\n            throw new RuntimeException(e);\n        }\n\n        //resources = fixHrefs(packageHref, resources);\n        readGuide(packageDocument, epubReader, book, resources);\n\n        // Books sometimes use non-identifier ids. We map these here to legal ones\n        Map<String, String> idMapping = new HashMap<>();\n        String version = DOMUtil.getAttribute(packageDocument.getDocumentElement(), PREFIX_OPF, PackageDocumentBase.version);\n\n        resources = readManifest(packageDocument, packageHref, packagePath, epubReader,\n                resources, idMapping);\n        book.setResources(resources);\n        book.setVersion(version);\n        readCover(packageDocument, packagePath, book);\n        book.setMetadata(\n                PackageDocumentMetadataReader.readMetadata(packageDocument));\n        book.setSpine(readSpine(packageDocument, book.getResources(), idMapping));\n\n        // if we did not find a cover page then we make the first page of the book the cover page\n        if (book.getCoverPage() == null && book.getSpine().size() > 0) {\n            book.setCoverPage(book.getSpine().getResource(0));\n        }\n    }\n\n    /**\n     * 修复一些非标准epub格式由于 opf 文件内容不全而读取不到图片的问题\n     *\n     * @return 修复图片路径后的一个Element列表\n     * @author qianfanguojin\n     */\n    private static ArrayList<Element> ensureImageInfo(Resources resources,\n                                                      Element manifestElement,\n                                                      URI packagePath,\n                                                      Document packageDocument) {\n        ArrayList<Element> fixedElements = new ArrayList<>();\n        HashSet<String> originItemHrefSet = new HashSet<>();\n        //加入当前所有的 item 标签 并将 href 保存到集合中\n        NodeList originItemElements = manifestElement\n                .getElementsByTagNameNS(NAMESPACE_OPF, OPFTags.item);\n        for (int i = 0; i < originItemElements.getLength(); i++) {\n            Element itemElement = (Element) originItemElements.item(i).cloneNode(false);\n            String href = DOMUtil.getAttribute(itemElement, NAMESPACE_OPF, OPFAttributes.href);\n            String resolvedHref = resolvePath(packagePath, href);\n            itemElement.setAttribute(\"href\", resolvedHref);\n            fixedElements.add(itemElement);\n            try {\n                href = URLDecoder.decode(resolvedHref, Constants.CHARACTER_ENCODING);\n            } catch (UnsupportedEncodingException e) {\n                Log.e(TAG, e.getMessage());\n            }\n            originItemHrefSet.add(href);\n        }\n\n        //如果有图片资源未定义在 originItemElements ，则加入该图片信息得到 fixedElements 中\n        for (Resource resource : resources.getAll()) {\n            MediaType currentMediaType = resource.getMediaType();\n            if (!MediaTypes.isImage(currentMediaType)) {\n                continue;\n            }\n            String imageHref = resource.getHref();\n            //确保该图片信息 resource 在原 originItemHrefSet 集合中没有出现过\n            if (originItemHrefSet.contains(imageHref)) {\n                continue;\n            }\n            Element itemEl = packageDocument.createElement(\"item\");\n            itemEl.setAttribute(\"id\", resource.getId());\n            try {\n                imageHref = URLEncoder.encode(imageHref, Constants.CHARACTER_ENCODING);\n            } catch (UnsupportedEncodingException e) {\n                Log.e(TAG, e.getMessage());\n                continue;\n            }\n            itemEl.setAttribute(\"href\", imageHref.replace(\"+\", \"%20\"));\n            itemEl.setAttribute(\"media-type\", currentMediaType.getName());\n            fixedElements.add(itemEl);\n        }\n        return fixedElements;\n    }\n\n    /**\n     * Reads the manifest containing the resource ids, hrefs and mediatypes.\n     *\n     * @param packageDocument e\n     * @param packageHref     e\n     * @param epubReader      e\n     * @param resources       e\n     * @param idMapping       e\n     * @return a Map with resources, with their id's as key.\n     */\n    @SuppressWarnings(\"unused\")\n    private static Resources readManifest(Document packageDocument,\n                                          String packageHref,\n                                          URI packagePath,\n                                          EpubReader epubReader, Resources resources,\n                                          Map<String, String> idMapping) {\n        Element manifestElement = DOMUtil\n                .getFirstElementByTagNameNS(packageDocument.getDocumentElement(),\n                        NAMESPACE_OPF, OPFTags.manifest);\n        Resources result = new Resources();\n        if (manifestElement == null) {\n            Log.e(TAG,\n                    \"Package document does not contain element \" + OPFTags.manifest);\n            return result;\n        }\n        List<Element> ensuredElements = ensureImageInfo(resources, manifestElement, packagePath, packageDocument);\n        for (Element itemElement : ensuredElements) {\n//            Element itemElement = ;\n            String id = DOMUtil\n                    .getAttribute(itemElement, NAMESPACE_OPF, OPFAttributes.id);\n            String href = DOMUtil\n                    .getAttribute(itemElement, NAMESPACE_OPF, OPFAttributes.href);\n\n            try {\n                href = URLDecoder.decode(href, Constants.CHARACTER_ENCODING);\n            } catch (UnsupportedEncodingException e) {\n                Log.e(TAG, e.getMessage());\n            }\n            String mediaTypeName = DOMUtil\n                    .getAttribute(itemElement, NAMESPACE_OPF, OPFAttributes.media_type);\n            Resource resource = resources.remove(href);\n            if (resource == null) {\n                Log.e(TAG, \"resource with href '\" + href + \"' not found\");\n                continue;\n            }\n            resource.setId(id);\n            //for epub3\n            String properties = DOMUtil.getAttribute(itemElement, NAMESPACE_OPF, OPFAttributes.properties);\n            resource.setProperties(properties);\n\n            MediaType mediaType = MediaTypes.getMediaTypeByName(mediaTypeName);\n            if (mediaType != null) {\n                resource.setMediaType(mediaType);\n            }\n            result.add(resource);\n            idMapping.put(id, resource.getId());\n        }\n        return result;\n    }\n\n\n    /**\n     * Reads the book's guide.\n     * Here some more attempts are made at finding the cover page.\n     *\n     * @param packageDocument r\n     * @param epubReader      r\n     * @param book            r\n     * @param resources       g\n     */\n    @SuppressWarnings(\"unused\")\n    private static void readGuide(Document packageDocument,\n                                  EpubReader epubReader, EpubBook book, Resources resources) {\n        Element guideElement = DOMUtil\n                .getFirstElementByTagNameNS(packageDocument.getDocumentElement(),\n                        NAMESPACE_OPF, OPFTags.guide);\n        if (guideElement == null) {\n            return;\n        }\n        Guide guide = book.getGuide();\n        NodeList guideReferences = guideElement\n                .getElementsByTagNameNS(NAMESPACE_OPF, OPFTags.reference);\n        for (int i = 0; i < guideReferences.getLength(); i++) {\n            Element referenceElement = (Element) guideReferences.item(i);\n            String resourceHref = DOMUtil\n                    .getAttribute(referenceElement, NAMESPACE_OPF, OPFAttributes.href);\n            if (StringUtil.isBlank(resourceHref)) {\n                continue;\n            }\n            Resource resource = resources.getByHref(StringUtil\n                    .substringBefore(resourceHref, Constants.FRAGMENT_SEPARATOR_CHAR));\n            if (resource == null) {\n                Log.e(TAG, \"Guide is referencing resource with href \" + resourceHref\n                        + \" which could not be found\");\n                continue;\n            }\n            String type = DOMUtil\n                    .getAttribute(referenceElement, NAMESPACE_OPF, OPFAttributes.type);\n            if (StringUtil.isBlank(type)) {\n                Log.e(TAG, \"Guide is referencing resource with href \" + resourceHref\n                        + \" which is missing the 'type' attribute\");\n                continue;\n            }\n            String title = DOMUtil\n                    .getAttribute(referenceElement, NAMESPACE_OPF, OPFAttributes.title);\n            if (GuideReference.COVER.equalsIgnoreCase(type)) {\n                continue; // cover is handled elsewhere\n            }\n            GuideReference reference = new GuideReference(resource, type, title,\n                    StringUtil\n                            .substringAfter(resourceHref, Constants.FRAGMENT_SEPARATOR_CHAR));\n            guide.addReference(reference);\n        }\n    }\n\n\n    /**\n     * Strips off the package prefixes up to the href of the packageHref.\n     * <p>\n     * Example:\n     * If the packageHref is \"OEBPS/content.opf\" then a resource href like \"OEBPS/foo/bar.html\" will be turned into \"foo/bar.html\"\n     *\n     * @param packageHref     f\n     * @param resourcesByHref g\n     * @return The stripped package href\n     */\n    static Resources fixHrefs(String packageHref,\n                              Resources resourcesByHref) {\n        int lastSlashPos = packageHref.lastIndexOf('/');\n        if (lastSlashPos < 0) {\n            return resourcesByHref;\n        }\n        Resources result = new Resources();\n        for (Resource resource : resourcesByHref.getAll()) {\n            if (StringUtil.isNotBlank(resource.getHref())\n                    && resource.getHref().length() > lastSlashPos) {\n                resource.setHref(resource.getHref().substring(lastSlashPos + 1));\n            }\n            result.add(resource);\n        }\n        return result;\n    }\n\n    /**\n     * Reads the document's spine, containing all sections in reading order.\n     *\n     * @param packageDocument b\n     * @param resources       b\n     * @param idMapping       b\n     * @return the document's spine, containing all sections in reading order.\n     */\n    private static Spine readSpine(Document packageDocument, Resources resources,\n                                   Map<String, String> idMapping) {\n\n        Element spineElement = DOMUtil\n                .getFirstElementByTagNameNS(packageDocument.getDocumentElement(),\n                        NAMESPACE_OPF, OPFTags.spine);\n        if (spineElement == null) {\n            Log.e(TAG, \"Element \" + OPFTags.spine\n                    + \" not found in package document, generating one automatically\");\n            return generateSpineFromResources(resources);\n        }\n        Spine result = new Spine();\n        String tocResourceId = DOMUtil.getAttribute(spineElement, NAMESPACE_OPF, OPFAttributes.toc);\n        Log.v(TAG, tocResourceId);\n        result.setTocResource(findTableOfContentsResource(tocResourceId, resources));\n        NodeList spineNodes = DOMUtil.getElementsByTagNameNS(packageDocument, NAMESPACE_OPF, OPFTags.itemref);\n        if (spineNodes == null) {\n            Log.e(TAG, \"spineNodes is null\");\n            return result;\n        }\n        List<SpineReference> spineReferences = new ArrayList<>(spineNodes.getLength());\n        for (int i = 0; i < spineNodes.getLength(); i++) {\n            Element spineItem = (Element) spineNodes.item(i);\n            String itemref = DOMUtil.getAttribute(spineItem, NAMESPACE_OPF, OPFAttributes.idref);\n            if (StringUtil.isBlank(itemref)) {\n                Log.e(TAG, \"itemref with missing or empty idref\"); // XXX\n                continue;\n            }\n            String id = idMapping.get(itemref);\n            if (id == null) {\n                id = itemref;\n            }\n\n            Resource resource = resources.getByIdOrHref(id);\n            if (resource == null) {\n                Log.e(TAG, \"resource with id '\" + id + \"' not found\");\n                continue;\n            }\n\n            SpineReference spineReference = new SpineReference(resource);\n            if (OPFValues.no.equalsIgnoreCase(DOMUtil\n                    .getAttribute(spineItem, NAMESPACE_OPF, OPFAttributes.linear))) {\n                spineReference.setLinear(false);\n            }\n            spineReferences.add(spineReference);\n        }\n        result.setSpineReferences(spineReferences);\n        return result;\n    }\n\n    /**\n     * Creates a spine out of all resources in the resources.\n     * The generated spine consists of all XHTML pages in order of their href.\n     *\n     * @param resources f\n     * @return a spine created out of all resources in the resources.\n     */\n    private static Spine generateSpineFromResources(Resources resources) {\n        Spine result = new Spine();\n        List<String> resourceHrefs = new ArrayList<>(resources.getAllHrefs());\n        Collections.sort(resourceHrefs, String.CASE_INSENSITIVE_ORDER);\n        for (String resourceHref : resourceHrefs) {\n            Resource resource = resources.getByHref(resourceHref);\n            if (resource.getMediaType() == MediaTypes.NCX) {\n                result.setTocResource(resource);\n            } else if (resource.getMediaType() == MediaTypes.XHTML) {\n                result.addSpineReference(new SpineReference(resource));\n            }\n        }\n        return result;\n    }\n\n\n    /**\n     * The spine tag should contain a 'toc' attribute with as value the resource id of the table of contents resource.\n     * <p>\n     * Here we try several ways of finding this table of contents resource.\n     * We try the given attribute value, some often-used ones and finally look through all resources for the first resource with the table of contents mimetype.\n     *\n     * @param tocResourceId g\n     * @param resources     g\n     * @return the Resource containing the table of contents\n     */\n    static Resource findTableOfContentsResource(\n            String tocResourceId,\n            Resources resources\n    ) {\n        Resource tocResource;\n        //一些epub3的文件为了兼容epub2,保留的epub2的目录文件，这里优先选择epub3的xml目录\n        tocResource = resources.getByProperties(\"nav\");\n        if (tocResource != null) {\n            return tocResource;\n        }\n\n        if (StringUtil.isNotBlank(tocResourceId)) {\n            tocResource = resources.getByIdOrHref(tocResourceId);\n        }\n\n        if (tocResource != null) {\n            return tocResource;\n        }\n\n        // get the first resource with the NCX mediatype\n        tocResource = resources.findFirstResourceByMediaType(MediaTypes.NCX);\n\n        if (tocResource == null) {\n            for (String possibleNcxItemId : POSSIBLE_NCX_ITEM_IDS) {\n                tocResource = resources.getByIdOrHref(possibleNcxItemId);\n                if (tocResource != null) {\n                    break;\n                }\n                tocResource = resources\n                        .getByIdOrHref(possibleNcxItemId.toUpperCase());\n                if (tocResource != null) {\n                    break;\n                }\n            }\n        }\n\n\n        if (tocResource == null) {\n            Log.e(TAG,\n                    \"Could not find table of contents resource. Tried resource with id '\"\n                            + tocResourceId + \"', \" + Constants.DEFAULT_TOC_ID + \", \"\n                            + Constants.DEFAULT_TOC_ID.toUpperCase()\n                            + \" and any NCX resource.\");\n        }\n        return tocResource;\n    }\n\n\n    /**\n     * Find all resources that have something to do with the coverpage and the cover image.\n     * Search the meta tags and the guide references\n     *\n     * @param packageDocument s\n     * @return all resources that have something to do with the coverpage and the cover image.\n     */\n    // package\n    static Set<String> findCoverHrefs(Document packageDocument, URI packagePath) {\n\n        Set<String> result = new HashSet<>();\n\n        // try and find a meta tag with name = 'cover' and a non-blank id\n        String coverResourceId = DOMUtil\n                .getFindAttributeValue(packageDocument, NAMESPACE_OPF,\n                        OPFTags.meta, OPFAttributes.name, OPFValues.meta_cover,\n                        OPFAttributes.content);\n\n        if (StringUtil.isNotBlank(coverResourceId)) {\n            String coverHref = DOMUtil\n                    .getFindAttributeValue(packageDocument, NAMESPACE_OPF,\n                            OPFTags.item, OPFAttributes.id, coverResourceId,\n                            OPFAttributes.href);\n            if (StringUtil.isNotBlank(coverHref)) {\n                result.add(resolvePath(packagePath, coverHref));\n            } else {\n                String resolved = resolvePath(packagePath, coverResourceId);\n                result.add(\n                        resolved); // maybe there was a cover href put in the cover id attribute\n            }\n        }\n        // try and find a reference tag with type is 'cover' and reference is not blank\n        String coverHref = DOMUtil\n                .getFindAttributeValue(packageDocument, NAMESPACE_OPF,\n                        OPFTags.reference, OPFAttributes.type, OPFValues.reference_cover,\n                        OPFAttributes.href);\n        if (StringUtil.isNotBlank(coverHref)) {\n            result.add(resolvePath(packagePath, coverHref));\n        }\n        return result;\n    }\n\n    private static String resolvePath(URI parentPath, String href) {\n        href = URLEncodeUtil.encode(href);\n        String resolved = parentPath.resolve(href).toString();\n        try {\n            return URLDecoder.decode(resolved, Constants.CHARACTER_ENCODING);\n        } catch (UnsupportedEncodingException e) {\n            throw new RuntimeException(e);\n        }\n    }\n\n    /**\n     * Finds the cover resource in the packageDocument and adds it to the book if found.\n     * Keeps the cover resource in the resources map\n     *\n     * @param packageDocument s\n     * @param book            x\n     */\n    private static void readCover(Document packageDocument, URI packagePath, EpubBook book) {\n\n        Collection<String> coverHrefs = findCoverHrefs(packageDocument, packagePath);\n        for (String coverHref : coverHrefs) {\n            Resource resource = book.getResources().getByHref(coverHref);\n            if (resource == null) {\n                Log.e(TAG, \"Cover resource \" + coverHref + \" not found\");\n                continue;\n            }\n            if (resource.getMediaType() == MediaTypes.XHTML) {\n                book.setCoverPage(resource);\n            } else if (MediaTypes.isBitmapImage(resource.getMediaType())) {\n                book.setCoverImage(resource);\n            }\n        }\n    }\n\n\n}\n"
  },
  {
    "path": "modules/book/src/main/java/me/ag2s/epublib/epub/PackageDocumentWriter.java",
    "content": "package me.ag2s.epublib.epub;\n\nimport android.util.Log;\n\nimport org.xmlpull.v1.XmlSerializer;\n\nimport java.io.IOException;\nimport java.util.ArrayList;\nimport java.util.Collections;\nimport java.util.List;\n\nimport me.ag2s.epublib.Constants;\nimport me.ag2s.epublib.domain.EpubBook;\nimport me.ag2s.epublib.domain.Guide;\nimport me.ag2s.epublib.domain.GuideReference;\nimport me.ag2s.epublib.domain.MediaTypes;\nimport me.ag2s.epublib.domain.Resource;\nimport me.ag2s.epublib.domain.Spine;\nimport me.ag2s.epublib.domain.SpineReference;\nimport me.ag2s.epublib.util.StringUtil;\n\n/**\n * Writes the opf package document as defined by namespace http://www.idpf.org/2007/opf\n *\n * @author paul\n */\npublic class PackageDocumentWriter extends PackageDocumentBase {\n\n    private static final String TAG = PackageDocumentWriter.class.getName();\n\n    public static void write(EpubWriter epubWriter, XmlSerializer serializer,\n                             EpubBook book) {\n        try {\n            serializer.startDocument(Constants.CHARACTER_ENCODING, false);\n            serializer.setPrefix(PREFIX_OPF, NAMESPACE_OPF);\n            serializer.setPrefix(PREFIX_DUBLIN_CORE, NAMESPACE_DUBLIN_CORE);\n            serializer.startTag(NAMESPACE_OPF, OPFTags.packageTag);\n            serializer\n                    .attribute(EpubWriter.EMPTY_NAMESPACE_PREFIX, OPFAttributes.version,\n                            book.getVersion());\n            serializer.attribute(EpubWriter.EMPTY_NAMESPACE_PREFIX,\n                    OPFAttributes.uniqueIdentifier, BOOK_ID_ID);\n\n            PackageDocumentMetadataWriter.writeMetaData(book, serializer);\n\n            writeManifest(book, epubWriter, serializer);\n            writeSpine(book, epubWriter, serializer);\n            writeGuide(book, epubWriter, serializer);\n\n            serializer.endTag(NAMESPACE_OPF, OPFTags.packageTag);\n            serializer.endDocument();\n            serializer.flush();\n        } catch (IOException e) {\n            e.printStackTrace();\n        }\n    }\n\n    /**\n     * Writes the package's spine.\n     *\n     * @param book       e\n     * @param epubWriter g\n     * @param serializer g\n     * @throws IOException              g\n     * @throws IllegalStateException    g\n     * @throws IllegalArgumentException 1@throws XMLStreamException\n     */\n    @SuppressWarnings(\"unused\")\n    private static void writeSpine(EpubBook book, EpubWriter epubWriter,\n                                   XmlSerializer serializer)\n            throws IllegalArgumentException, IllegalStateException, IOException {\n        serializer.startTag(NAMESPACE_OPF, OPFTags.spine);\n        Resource tocResource = book.getSpine().getTocResource();\n        String tocResourceId = tocResource.getId();\n        serializer.attribute(EpubWriter.EMPTY_NAMESPACE_PREFIX, OPFAttributes.toc,\n                tocResourceId);\n\n        if (book.getCoverPage() != null // there is a cover page\n                && book.getSpine().findFirstResourceById(book.getCoverPage().getId())\n                < 0) { // cover page is not already in the spine\n            // write the cover html file\n            serializer.startTag(NAMESPACE_OPF, OPFTags.itemref);\n            serializer\n                    .attribute(EpubWriter.EMPTY_NAMESPACE_PREFIX, OPFAttributes.idref,\n                            book.getCoverPage().getId());\n            serializer\n                    .attribute(EpubWriter.EMPTY_NAMESPACE_PREFIX, OPFAttributes.linear,\n                            \"no\");\n            serializer.endTag(NAMESPACE_OPF, OPFTags.itemref);\n        }\n        writeSpineItems(book.getSpine(), serializer);\n        serializer.endTag(NAMESPACE_OPF, OPFTags.spine);\n    }\n\n\n    private static void writeManifest(EpubBook book, EpubWriter epubWriter,\n                                      XmlSerializer serializer)\n            throws IllegalArgumentException, IllegalStateException, IOException {\n        serializer.startTag(NAMESPACE_OPF, OPFTags.manifest);\n\n        serializer.startTag(NAMESPACE_OPF, OPFTags.item);\n\n        //For EPUB3\n        if (book.isEpub3()) {\n            serializer.attribute(EpubWriter.EMPTY_NAMESPACE_PREFIX, OPFAttributes.properties, NCXDocumentV3.V3_NCX_PROPERTIES);\n            serializer.attribute(EpubWriter.EMPTY_NAMESPACE_PREFIX, OPFAttributes.id, NCXDocumentV3.NCX_ITEM_ID);\n            serializer.attribute(EpubWriter.EMPTY_NAMESPACE_PREFIX, OPFAttributes.href, NCXDocumentV3.DEFAULT_NCX_HREF);\n            serializer.attribute(EpubWriter.EMPTY_NAMESPACE_PREFIX, OPFAttributes.media_type, NCXDocumentV3.V3_NCX_MEDIATYPE.getName());\n        } else {\n            serializer.attribute(EpubWriter.EMPTY_NAMESPACE_PREFIX, OPFAttributes.id,\n                    epubWriter.getNcxId());\n            serializer.attribute(EpubWriter.EMPTY_NAMESPACE_PREFIX, OPFAttributes.href, epubWriter.getNcxHref());\n            serializer.attribute(EpubWriter.EMPTY_NAMESPACE_PREFIX, OPFAttributes.media_type, epubWriter.getNcxMediaType());\n        }\n\n        serializer.endTag(NAMESPACE_OPF, OPFTags.item);\n\n//\t\twriteCoverResources(book, serializer);\n\n        for (Resource resource : getAllResourcesSortById(book)) {\n            writeItem(book, resource, serializer);\n        }\n\n        serializer.endTag(NAMESPACE_OPF, OPFTags.manifest);\n    }\n\n    private static List<Resource> getAllResourcesSortById(EpubBook book) {\n        List<Resource> allResources = new ArrayList<>(\n                book.getResources().getAll());\n        Collections.sort(allResources, (resource1, resource2) -> resource1.getId().compareToIgnoreCase(resource2.getId()));\n        return allResources;\n    }\n\n    /**\n     * Writes a resources as an item element\n     *\n     * @param resource   g\n     * @param serializer g\n     * @throws IOException              g\n     * @throws IllegalStateException    g\n     * @throws IllegalArgumentException 1@throws XMLStreamException\n     */\n    private static void writeItem(EpubBook book, Resource resource,\n                                  XmlSerializer serializer)\n            throws IllegalArgumentException, IllegalStateException, IOException {\n        if (resource == null ||\n                (resource.getMediaType() == MediaTypes.NCX\n                        && book.getSpine().getTocResource() != null)) {\n            return;\n        }\n        if (StringUtil.isBlank(resource.getId())) {\n            Log.e(TAG, \"resource id must not be empty (href: \" + resource.getHref()\n                    + \", mediatype:\" + resource.getMediaType() + \")\");\n            return;\n        }\n        if (StringUtil.isBlank(resource.getHref())) {\n            Log.e(TAG, \"resource href must not be empty (id: \" + resource.getId()\n                    + \", mediatype:\" + resource.getMediaType() + \")\");\n            return;\n        }\n        if (resource.getMediaType() == null) {\n            Log.e(TAG, \"resource mediatype must not be empty (id: \" + resource.getId()\n                    + \", href:\" + resource.getHref() + \")\");\n            return;\n        }\n        serializer.startTag(NAMESPACE_OPF, OPFTags.item);\n        serializer.attribute(EpubWriter.EMPTY_NAMESPACE_PREFIX, OPFAttributes.id,\n                resource.getId());\n        serializer.attribute(EpubWriter.EMPTY_NAMESPACE_PREFIX, OPFAttributes.href,\n                resource.getHref());\n        serializer\n                .attribute(EpubWriter.EMPTY_NAMESPACE_PREFIX, OPFAttributes.media_type,\n                        resource.getMediaType().getName());\n        serializer.endTag(NAMESPACE_OPF, OPFTags.item);\n    }\n\n    /**\n     * List all spine references\n     *\n     * @throws IOException              f\n     * @throws IllegalStateException    f\n     * @throws IllegalArgumentException f\n     */\n    @SuppressWarnings(\"unused\")\n    private static void writeSpineItems(Spine spine, XmlSerializer serializer)\n            throws IllegalArgumentException, IllegalStateException, IOException {\n        for (SpineReference spineReference : spine.getSpineReferences()) {\n            serializer.startTag(NAMESPACE_OPF, OPFTags.itemref);\n            serializer\n                    .attribute(EpubWriter.EMPTY_NAMESPACE_PREFIX, OPFAttributes.idref,\n                            spineReference.getResourceId());\n            if (!spineReference.isLinear()) {\n                serializer\n                        .attribute(EpubWriter.EMPTY_NAMESPACE_PREFIX, OPFAttributes.linear,\n                                OPFValues.no);\n            }\n            serializer.endTag(NAMESPACE_OPF, OPFTags.itemref);\n        }\n    }\n\n    private static void writeGuide(EpubBook book, EpubWriter epubWriter,\n                                   XmlSerializer serializer)\n            throws IllegalArgumentException, IllegalStateException, IOException {\n        serializer.startTag(NAMESPACE_OPF, OPFTags.guide);\n        ensureCoverPageGuideReferenceWritten(book.getGuide(), epubWriter,\n                serializer);\n        for (GuideReference reference : book.getGuide().getReferences()) {\n            writeGuideReference(reference, serializer);\n        }\n        serializer.endTag(NAMESPACE_OPF, OPFTags.guide);\n    }\n\n    @SuppressWarnings(\"unused\")\n    private static void ensureCoverPageGuideReferenceWritten(Guide guide,\n                                                             EpubWriter epubWriter, XmlSerializer serializer)\n            throws IllegalArgumentException, IllegalStateException, IOException {\n        if (!(guide.getGuideReferencesByType(GuideReference.COVER).isEmpty())) {\n            return;\n        }\n        Resource coverPage = guide.getCoverPage();\n        if (coverPage != null) {\n            writeGuideReference(\n                    new GuideReference(guide.getCoverPage(), GuideReference.COVER,\n                            GuideReference.COVER), serializer);\n        }\n    }\n\n\n    private static void writeGuideReference(GuideReference reference,\n                                            XmlSerializer serializer)\n            throws IllegalArgumentException, IllegalStateException, IOException {\n        if (reference == null) {\n            return;\n        }\n        serializer.startTag(NAMESPACE_OPF, OPFTags.reference);\n        serializer.attribute(EpubWriter.EMPTY_NAMESPACE_PREFIX, OPFAttributes.type,\n                reference.getType());\n        serializer.attribute(EpubWriter.EMPTY_NAMESPACE_PREFIX, OPFAttributes.href,\n                reference.getCompleteHref());\n        if (StringUtil.isNotBlank(reference.getTitle())) {\n            serializer\n                    .attribute(EpubWriter.EMPTY_NAMESPACE_PREFIX, OPFAttributes.title,\n                            reference.getTitle());\n        }\n        serializer.endTag(NAMESPACE_OPF, OPFTags.reference);\n    }\n}"
  },
  {
    "path": "modules/book/src/main/java/me/ag2s/epublib/epub/ResourcesLoader.java",
    "content": "package me.ag2s.epublib.epub;\n\nimport android.util.Log;\n\nimport java.io.IOException;\nimport java.util.ArrayList;\nimport java.util.Collection;\nimport java.util.Enumeration;\nimport java.util.List;\nimport java.util.zip.ZipEntry;\nimport java.util.zip.ZipException;\nimport java.util.zip.ZipInputStream;\n\nimport me.ag2s.epublib.domain.EpubResourceProvider;\nimport me.ag2s.epublib.domain.LazyResource;\nimport me.ag2s.epublib.domain.LazyResourceProvider;\nimport me.ag2s.epublib.domain.MediaType;\nimport me.ag2s.epublib.domain.MediaTypes;\nimport me.ag2s.epublib.domain.Resource;\nimport me.ag2s.epublib.domain.Resources;\nimport me.ag2s.epublib.util.CollectionUtil;\nimport me.ag2s.epublib.util.ResourceUtil;\nimport me.ag2s.epublib.util.zip.ZipEntryWrapper;\nimport me.ag2s.epublib.util.zip.ZipFileWrapper;\n\n\n/**\n * Loads Resources from inputStreams, ZipFiles, etc\n *\n * @author paul\n */\npublic class ResourcesLoader {\n\n    private static final String TAG = ResourcesLoader.class.getName();\n\n\n    /**\n     * Loads the entries of the zipFileWrapper as resources.\n     * <p>\n     * The MediaTypes that are in the lazyLoadedTypes will not get their\n     * contents loaded, but are stored as references to entries into the\n     * AndroidZipFile and are loaded on demand by the Resource system.\n     *\n     * @param zipFileWrapper      import epub zipfile\n     * @param defaultHtmlEncoding epub xhtml default encoding\n     * @param lazyLoadedTypes     lazyLoadedTypes\n     * @return Resources\n     * @throws IOException IOException\n     */\n    public static Resources loadResources(\n            ZipFileWrapper zipFileWrapper,\n            String defaultHtmlEncoding,\n            List<MediaType> lazyLoadedTypes\n    ) throws IOException {\n\n        LazyResourceProvider resourceProvider =\n                new EpubResourceProvider(zipFileWrapper);\n\n        Resources result = new Resources();\n        Enumeration entries = zipFileWrapper.entries();\n\n        while (entries.hasMoreElements()) {\n            ZipEntryWrapper zipEntry = new ZipEntryWrapper(entries.nextElement());\n\n            if (zipEntry == null || zipEntry.isDirectory()) {\n                continue;\n            }\n\n            String href = zipEntry.getName();\n\n            Resource resource;\n\n            if (shouldLoadLazy(href, lazyLoadedTypes)) {\n                resource = new LazyResource(resourceProvider, zipEntry.getSize(), href);\n            } else {\n                resource = ResourceUtil\n                        .createResource(zipEntry.getName(), zipFileWrapper.getInputStream(zipEntry));\n            }\n\n            if (resource.getMediaType() == MediaTypes.XHTML) {\n                resource.setInputEncoding(defaultHtmlEncoding);\n            }\n            result.add(resource);\n        }\n\n        return result;\n    }\n\n    /**\n     * Whether the given href will load a mediaType that is in the\n     * collection of lazilyLoadedMediaTypes.\n     *\n     * @param href                   href\n     * @param lazilyLoadedMediaTypes lazilyLoadedMediaTypes\n     * @return Whether the given href will load a mediaType that is\n     * in the collection of lazilyLoadedMediaTypes.\n     */\n    private static boolean shouldLoadLazy(String href,\n                                          Collection<MediaType> lazilyLoadedMediaTypes) {\n        if (CollectionUtil.isEmpty(lazilyLoadedMediaTypes)) {\n            return false;\n        }\n        MediaType mediaType = MediaTypes.determineMediaType(href);\n        return lazilyLoadedMediaTypes.contains(mediaType);\n    }\n\n    /**\n     * Loads all entries from the ZipInputStream as Resources.\n     * <p>\n     * Loads the contents of all ZipEntries into memory.\n     * Is fast, but may lead to memory problems when reading large books\n     * on devices with small amounts of memory.\n     *\n     * @param zipInputStream      zipInputStream\n     * @param defaultHtmlEncoding defaultHtmlEncoding\n     * @return Resources\n     * @throws IOException IOException\n     */\n    public static Resources loadResources(ZipInputStream zipInputStream,\n                                          String defaultHtmlEncoding) throws IOException {\n        Resources result = new Resources();\n        ZipEntry zipEntry;\n        do {\n            // get next valid zipEntry\n            zipEntry = getNextZipEntry(zipInputStream);\n            if ((zipEntry == null) || zipEntry.isDirectory()) {\n                continue;\n            }\n            //String href = zipEntry.getName();\n\n            // store resource\n            Resource resource = ResourceUtil.createResource(zipEntry.getName(), zipInputStream);\n            if (resource.getMediaType() == MediaTypes.XHTML) {\n                resource.setInputEncoding(defaultHtmlEncoding);\n            }\n            result.add(resource);\n        } while (zipEntry != null);\n\n        return result;\n    }\n\n\n    private static ZipEntry getNextZipEntry(ZipInputStream zipInputStream)\n            throws IOException {\n        try {\n            return zipInputStream.getNextEntry();\n        } catch (ZipException e) {\n            //see <a href=\"https://github.com/psiegman/epublib/issues/122\">Issue #122 Infinite loop</a>.\n            //when reading a file that is not a real zip archive or a zero length file, zipInputStream.getNextEntry()\n            //throws an exception and does not advance, so loadResources enters an infinite loop\n            //log.error(\"Invalid or damaged zip file.\", e);\n            Log.e(TAG, e.getLocalizedMessage());\n            try {\n                zipInputStream.closeEntry();\n            } catch (Exception ignored) {\n            }\n            throw e;\n        }\n    }\n\n    /**\n     * Loads all entries from the ZipInputStream as Resources.\n     * <p>\n     * Loads the contents of all ZipEntries into memory.\n     * Is fast, but may lead to memory problems when reading large books\n     * on devices with small amounts of memory.\n     *\n     * @param zipFile             zipFile\n     * @param defaultHtmlEncoding defaultHtmlEncoding\n     * @return Resources\n     * @throws IOException IOException\n     */\n    public static Resources loadResources(ZipFileWrapper zipFile, String defaultHtmlEncoding) throws IOException {\n        List<MediaType> ls = new ArrayList<>();\n        return loadResources(zipFile, defaultHtmlEncoding, ls);\n    }\n}\n"
  },
  {
    "path": "modules/book/src/main/java/me/ag2s/epublib/util/CollectionUtil.java",
    "content": "package me.ag2s.epublib.util;\n\nimport java.util.Collection;\nimport java.util.Enumeration;\nimport java.util.Iterator;\nimport java.util.List;\n\npublic class CollectionUtil {\n\n    /**\n     * Wraps an Enumeration around an Iterator\n     *\n     * @param <T>\n     * @author paul.siegmann\n     */\n    private static class IteratorEnumerationAdapter<T> implements Enumeration<T> {\n\n        private final Iterator<T> iterator;\n\n        public IteratorEnumerationAdapter(Iterator<T> iter) {\n            this.iterator = iter;\n        }\n\n        @Override\n        public boolean hasMoreElements() {\n            return iterator.hasNext();\n        }\n\n        @Override\n        public T nextElement() {\n            return iterator.next();\n        }\n    }\n\n    /**\n     * Creates an Enumeration out of the given Iterator.\n     *\n     * @param <T> g\n     * @param it  g\n     * @return an Enumeration created out of the given Iterator.\n     */\n    @SuppressWarnings(\"unused\")\n    public static <T> Enumeration<T> createEnumerationFromIterator(\n            Iterator<T> it) {\n        return new IteratorEnumerationAdapter<>(it);\n    }\n\n\n    /**\n     * Returns the first element of the list, null if the list is null or empty.\n     *\n     * @param <T>  f\n     * @param list f\n     * @return the first element of the list, null if the list is null or empty.\n     */\n    public static <T> T first(List<T> list) {\n        if (list == null || list.isEmpty()) {\n            return null;\n        }\n        return list.get(0);\n    }\n\n    /**\n     * Whether the given collection is null or has no elements.\n     *\n     * @param collection g\n     * @return Whether the given collection is null or has no elements.\n     */\n    public static boolean isEmpty(Collection<?> collection) {\n        return collection == null || collection.isEmpty();\n    }\n}\n"
  },
  {
    "path": "modules/book/src/main/java/me/ag2s/epublib/util/IOUtil.java",
    "content": "package me.ag2s.epublib.util;\n\nimport java.io.ByteArrayOutputStream;\nimport java.io.Closeable;\nimport java.io.EOFException;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.InputStreamReader;\nimport java.io.OutputStream;\nimport java.io.OutputStreamWriter;\nimport java.io.Reader;\nimport java.io.StringWriter;\nimport java.io.Writer;\nimport java.net.HttpURLConnection;\nimport java.net.URLConnection;\nimport java.nio.ByteBuffer;\nimport java.nio.CharBuffer;\nimport java.nio.channels.ReadableByteChannel;\nimport java.nio.charset.Charset;\n\nimport me.ag2s.epublib.util.commons.io.IOConsumer;\n\n/**\n * Most of the functions herein are re-implementations of the ones in\n * apache io IOUtils.\n * <p>\n * The reason for re-implementing this is that the functions are fairly simple\n * and using my own implementation saves the inclusion of a 200Kb jar file.\n */\npublic class IOUtil {\n    private static final String TAG = IOUtil.class.getName();\n\n    /**\n     * Represents the end-of-file (or stream).\n     *\n     * @since 2.5 (made public)\n     */\n    public static final int EOF = -1;\n\n\n    public static final int DEFAULT_BUFFER_SIZE = 1024 * 8;\n    private static final byte[] SKIP_BYTE_BUFFER = new byte[DEFAULT_BUFFER_SIZE];\n\n    // Allocated in the relevant skip method if necessary.\n    /*\n     * These buffers are static and are shared between threads.\n     * This is possible because the buffers are write-only - the contents are never read.\n     *\n     * N.B. there is no need to synchronize when creating these because:\n     * - we don't care if the buffer is created multiple times (the data is ignored)\n     * - we always use the same size buffer, so if it it is recreated it will still be OK\n     * (if the buffer size were variable, we would need to synch. to ensure some other thread\n     * did not create a smaller one)\n     */\n    private static char[] SKIP_CHAR_BUFFER;\n\n    /**\n     * Gets the contents of the Reader as a byte[], with the given character encoding.\n     *\n     * @param in       g\n     * @param encoding g\n     * @return the contents of the Reader as a byte[], with the given character encoding.\n     * @throws IOException g\n     */\n    public static byte[] toByteArray(Reader in, String encoding)\n            throws IOException {\n        StringWriter out = new StringWriter();\n        copy(in, out);\n        out.flush();\n        return out.toString().getBytes(encoding);\n    }\n\n    /**\n     * Returns the contents of the InputStream as a byte[]\n     *\n     * @param in f\n     * @return the contents of the InputStream as a byte[]\n     * @throws IOException f\n     */\n    public static byte[] toByteArray(InputStream in) throws IOException {\n        ByteArrayOutputStream result = new ByteArrayOutputStream();\n        copy(in, result);\n        result.flush();\n        return result.toByteArray();\n    }\n\n\n    /**\n     * Reads data from the InputStream, using the specified buffer size.\n     * <p>\n     * This is meant for situations where memory is tight, since\n     * it prevents buffer expansion.\n     *\n     * @param in   the stream to read data from\n     * @param size the size of the array to create\n     * @return the array, or null\n     * @throws IOException f\n     */\n    public static byte[] toByteArray(InputStream in, int size)\n            throws IOException {\n\n        try {\n            ByteArrayOutputStream result;\n\n            if (size > 0) {\n                result = new ByteArrayOutputStream(size);\n            } else {\n                result = new ByteArrayOutputStream();\n            }\n\n            copy(in, result);\n            result.flush();\n            return result.toByteArray();\n        } catch (OutOfMemoryError error) {\n            //Return null so it gets loaded lazily.\n            return null;\n        }\n\n    }\n\n\n    /**\n     * if totalNrRead &lt; 0 then totalNrRead is returned, if\n     * (nrRead + totalNrRead) &lt; Integer.MAX_VALUE then nrRead + totalNrRead\n     * is returned, -1 otherwise.\n     *\n     * @param nrRead       f\n     * @param totalNrNread f\n     * @return if totalNrRead &lt; 0 then totalNrRead is returned, if\n     * (nrRead + totalNrRead) &lt; Integer.MAX_VALUE then nrRead + totalNrRead\n     * is returned, -1 otherwise.\n     */\n    protected static int calcNewNrReadSize(int nrRead, int totalNrNread) {\n        if (totalNrNread < 0) {\n            return totalNrNread;\n        }\n        if (totalNrNread > (Integer.MAX_VALUE - nrRead)) {\n            return -1;\n        } else {\n            return (totalNrNread + nrRead);\n        }\n    }\n\n    //\n    public static void copy(InputStream in, OutputStream result) throws IOException {\n        copy(in, result, DEFAULT_BUFFER_SIZE);\n    }\n\n    /**\n     * Copies bytes from an <code>InputStream</code> to an <code>OutputStream</code> using an internal buffer of the\n     * given size.\n     * <p>\n     * This method buffers the input internally, so there is no need to use a <code>BufferedInputStream</code>.\n     * </p>\n     *\n     * @param input      the <code>InputStream</code> to read from\n     * @param output     the <code>OutputStream</code> to write to\n     * @param bufferSize the bufferSize used to copy from the input to the output\n     * @return the number of bytes copied. or {@code 0} if {@code input is null}.\n     * @throws NullPointerException if the output is null\n     * @throws IOException          if an I/O error occurs\n     * @since 2.5\n     */\n    public static long copy(final InputStream input, final OutputStream output, final int bufferSize)\n            throws IOException {\n        return copyLarge(input, output, new byte[bufferSize]);\n    }\n\n    /**\n     * Copies bytes from an <code>InputStream</code> to chars on a\n     * <code>Writer</code> using the default character encoding of the platform.\n     * <p>\n     * This method buffers the input internally, so there is no need to use a\n     * <code>BufferedInputStream</code>.\n     * <p>\n     * This method uses {@link InputStreamReader}.\n     *\n     * @param input  the <code>InputStream</code> to read from\n     * @param output the <code>Writer</code> to write to\n     * @throws NullPointerException if the input or output is null\n     * @throws IOException          if an I/O error occurs\n     * @since 1.1\n     * @deprecated 2.5 use {@link #copy(InputStream, Writer, Charset)} instead\n     */\n    @Deprecated\n    public static void copy(final InputStream input, final Writer output)\n            throws IOException {\n        copy(input, output, Charset.defaultCharset());\n    }\n\n    /**\n     * Copies bytes from an <code>InputStream</code> to chars on a\n     * <code>Writer</code> using the specified character encoding.\n     * <p>\n     * This method buffers the input internally, so there is no need to use a\n     * <code>BufferedInputStream</code>.\n     * <p>\n     * This method uses {@link InputStreamReader}.\n     *\n     * @param input        the <code>InputStream</code> to read from\n     * @param output       the <code>Writer</code> to write to\n     * @param inputCharset the charset to use for the input stream, null means platform default\n     * @throws NullPointerException if the input or output is null\n     * @throws IOException          if an I/O error occurs\n     * @since 2.3\n     */\n    public static void copy(final InputStream input, final Writer output, final Charset inputCharset)\n            throws IOException {\n        final InputStreamReader in = new InputStreamReader(input, inputCharset.name());\n        copy(in, output);\n    }\n\n    /**\n     * Copies bytes from an <code>InputStream</code> to chars on a\n     * <code>Writer</code> using the specified character encoding.\n     * <p>\n     * This method buffers the input internally, so there is no need to use a\n     * <code>BufferedInputStream</code>.\n     * <p>\n     * Character encoding names can be found at\n     * <a href=\"http://www.iana.org/assignments/character-sets\">IANA</a>.\n     * <p>\n     * This method uses {@link InputStreamReader}.\n     *\n     * @param input            the <code>InputStream</code> to read from\n     * @param output           the <code>Writer</code> to write to\n     * @param inputCharsetName the name of the requested charset for the InputStream, null means platform default\n     * @throws NullPointerException                         if the input or output is null\n     * @throws IOException                                  if an I/O error occurs\n     * @throws java.nio.charset.UnsupportedCharsetException thrown instead of {@link java.io\n     *                                                      .UnsupportedEncodingException} in version 2.2 if the\n     *                                                      encoding is not supported.\n     * @since 1.1\n     */\n    public static void copy(final InputStream input, final Writer output, final String inputCharsetName)\n            throws IOException {\n        copy(input, output, Charset.forName(inputCharsetName));\n    }\n\n    /**\n     * Copies chars from a <code>Reader</code> to a <code>Appendable</code>.\n     * <p>\n     * This method buffers the input internally, so there is no need to use a\n     * <code>BufferedReader</code>.\n     * <p>\n     * Large streams (over 2GB) will return a chars copied value of\n     * <code>-1</code> after the copy has completed since the correct\n     * number of chars cannot be returned as an int. For large streams\n     * use the <code>copyLarge(Reader, Writer)</code> method.\n     *\n     * @param input  the <code>Reader</code> to read from\n     * @param output the <code>Appendable</code> to write to\n     * @return the number of characters copied, or -1 if &gt; Integer.MAX_VALUE\n     * @throws NullPointerException if the input or output is null\n     * @throws IOException          if an I/O error occurs\n     * @since 2.7\n     */\n    public static long copy(final Reader input, final Appendable output) throws IOException {\n        return copy(input, output, CharBuffer.allocate(DEFAULT_BUFFER_SIZE));\n    }\n\n    /**\n     * Copies chars from a <code>Reader</code> to an <code>Appendable</code>.\n     * <p>\n     * This method uses the provided buffer, so there is no need to use a\n     * <code>BufferedReader</code>.\n     * </p>\n     *\n     * @param input  the <code>Reader</code> to read from\n     * @param output the <code>Appendable</code> to write to\n     * @param buffer the buffer to be used for the copy\n     * @return the number of characters copied\n     * @throws NullPointerException if the input or output is null\n     * @throws IOException          if an I/O error occurs\n     * @since 2.7\n     */\n    public static long copy(final Reader input, final Appendable output, final CharBuffer buffer) throws IOException {\n        long count = 0;\n        int n;\n        while (EOF != (n = input.read(buffer))) {\n            buffer.flip();\n            output.append(buffer, 0, n);\n            count += n;\n        }\n        return count;\n    }\n\n    /**\n     * Copies chars from a <code>Reader</code> to bytes on an\n     * <code>OutputStream</code> using the default character encoding of the\n     * platform, and calling flush.\n     * <p>\n     * This method buffers the input internally, so there is no need to use a\n     * <code>BufferedReader</code>.\n     * <p>\n     * Due to the implementation of OutputStreamWriter, this method performs a\n     * flush.\n     * <p>\n     * This method uses {@link OutputStreamWriter}.\n     *\n     * @param input  the <code>Reader</code> to read from\n     * @param output the <code>OutputStream</code> to write to\n     * @throws NullPointerException if the input or output is null\n     * @throws IOException          if an I/O error occurs\n     * @since 1.1\n     * @deprecated 2.5 use {@link #copy(Reader, OutputStream, Charset)} instead\n     */\n    @Deprecated\n    public static void copy(final Reader input, final OutputStream output)\n            throws IOException {\n        copy(input, output, Charset.defaultCharset());\n    }\n\n    /**\n     * Copies chars from a <code>Reader</code> to bytes on an\n     * <code>OutputStream</code> using the specified character encoding, and\n     * calling flush.\n     * <p>\n     * This method buffers the input internally, so there is no need to use a\n     * <code>BufferedReader</code>.\n     * </p>\n     * <p>\n     * Due to the implementation of OutputStreamWriter, this method performs a\n     * flush.\n     * </p>\n     * <p>\n     * This method uses {@link OutputStreamWriter}.\n     * </p>\n     *\n     * @param input         the <code>Reader</code> to read from\n     * @param output        the <code>OutputStream</code> to write to\n     * @param outputCharset the charset to use for the OutputStream, null means platform default\n     * @throws NullPointerException if the input or output is null\n     * @throws IOException          if an I/O error occurs\n     * @since 2.3\n     */\n    public static void copy(final Reader input, final OutputStream output, final Charset outputCharset)\n            throws IOException {\n        final OutputStreamWriter out = new OutputStreamWriter(output, outputCharset.name());\n        copy(input, out);\n        // XXX Unless anyone is planning on rewriting OutputStreamWriter,\n        // we have to flush here.\n        out.flush();\n    }\n\n    /**\n     * Copies chars from a <code>Reader</code> to bytes on an\n     * <code>OutputStream</code> using the specified character encoding, and\n     * calling flush.\n     * <p>\n     * This method buffers the input internally, so there is no need to use a\n     * <code>BufferedReader</code>.\n     * <p>\n     * Character encoding names can be found at\n     * <a href=\"http://www.iana.org/assignments/character-sets\">IANA</a>.\n     * <p>\n     * Due to the implementation of OutputStreamWriter, this method performs a\n     * flush.\n     * <p>\n     * This method uses {@link OutputStreamWriter}.\n     *\n     * @param input             the <code>Reader</code> to read from\n     * @param output            the <code>OutputStream</code> to write to\n     * @param outputCharsetName the name of the requested charset for the OutputStream, null means platform default\n     * @throws NullPointerException                         if the input or output is null\n     * @throws IOException                                  if an I/O error occurs\n     * @throws java.nio.charset.UnsupportedCharsetException thrown instead of {@link java.io\n     *                                                      .UnsupportedEncodingException} in version 2.2 if the\n     *                                                      encoding is not supported.\n     * @since 1.1\n     */\n    public static void copy(final Reader input, final OutputStream output, final String outputCharsetName)\n            throws IOException {\n        copy(input, output, Charset.forName(outputCharsetName));\n    }\n\n    /**\n     * Copies chars from a <code>Reader</code> to a <code>Writer</code>.\n     * <p>\n     * This method buffers the input internally, so there is no need to use a\n     * <code>BufferedReader</code>.\n     * <p>\n     * Large streams (over 2GB) will return a chars copied value of\n     * <code>-1</code> after the copy has completed since the correct\n     * number of chars cannot be returned as an int. For large streams\n     * use the <code>copyLarge(Reader, Writer)</code> method.\n     *\n     * @param input  the <code>Reader</code> to read from\n     * @param output the <code>Writer</code> to write to\n     * @return the number of characters copied, or -1 if &gt; Integer.MAX_VALUE\n     * @throws NullPointerException if the input or output is null\n     * @throws IOException          if an I/O error occurs\n     * @since 1.1\n     */\n    public static int copy(final Reader input, final Writer output) throws IOException {\n        final long count = copyLarge(input, output);\n        if (count > Integer.MAX_VALUE) {\n            return -1;\n        }\n        return (int) count;\n    }\n\n    /**\n     * Copies bytes from a large (over 2GB) <code>InputStream</code> to an\n     * <code>OutputStream</code>.\n     * <p>\n     * This method buffers the input internally, so there is no need to use a\n     * <code>BufferedInputStream</code>.\n     * </p>\n     * <p>\n     * The buffer size is given by {@link #DEFAULT_BUFFER_SIZE}.\n     * </p>\n     *\n     * @param input  the <code>InputStream</code> to read from\n     * @param output the <code>OutputStream</code> to write to\n     * @return the number of bytes copied. or {@code 0} if {@code input is null}.\n     * @throws NullPointerException if the output is null\n     * @throws IOException          if an I/O error occurs\n     * @since 1.3\n     */\n    public static long copyLarge(final InputStream input, final OutputStream output)\n            throws IOException {\n        return copy(input, output, DEFAULT_BUFFER_SIZE);\n    }\n\n    /**\n     * Copies bytes from a large (over 2GB) <code>InputStream</code> to an\n     * <code>OutputStream</code>.\n     * <p>\n     * This method uses the provided buffer, so there is no need to use a\n     * <code>BufferedInputStream</code>.\n     * </p>\n     *\n     * @param input  the <code>InputStream</code> to read from\n     * @param output the <code>OutputStream</code> to write to\n     * @param buffer the buffer to use for the copy\n     * @return the number of bytes copied. or {@code 0} if {@code input is null}.\n     * @throws IOException if an I/O error occurs\n     * @since 2.2\n     */\n    public static long copyLarge(final InputStream input, final OutputStream output, final byte[] buffer)\n            throws IOException {\n        long count = 0;\n        if (input != null) {\n            int n;\n            while (EOF != (n = input.read(buffer))) {\n                output.write(buffer, 0, n);\n                count += n;\n            }\n            //input.close();\n        }\n        return count;\n    }\n\n    /**\n     * Copies some or all bytes from a large (over 2GB) <code>InputStream</code> to an\n     * <code>OutputStream</code>, optionally skipping input bytes.\n     * <p>\n     * This method buffers the input internally, so there is no need to use a\n     * <code>BufferedInputStream</code>.\n     * </p>\n     * <p>\n     * Note that the implementation uses {@link #skip(InputStream, long)}.\n     * This means that the method may be considerably less efficient than using the actual skip implementation,\n     * this is done to guarantee that the correct number of characters are skipped.\n     * </p>\n     * The buffer size is given by {@link #DEFAULT_BUFFER_SIZE}.\n     *\n     * @param input       the <code>InputStream</code> to read from\n     * @param output      the <code>OutputStream</code> to write to\n     * @param inputOffset : number of bytes to skip from input before copying\n     *                    -ve values are ignored\n     * @param length      : number of bytes to copy. -ve means all\n     * @return the number of bytes copied\n     * @throws NullPointerException if the input or output is null\n     * @throws IOException          if an I/O error occurs\n     * @since 2.2\n     */\n    public static long copyLarge(final InputStream input, final OutputStream output, final long inputOffset,\n                                 final long length) throws IOException {\n        return copyLarge(input, output, inputOffset, length, new byte[DEFAULT_BUFFER_SIZE]);\n    }\n\n    /**\n     * Copies some or all bytes from a large (over 2GB) <code>InputStream</code> to an\n     * <code>OutputStream</code>, optionally skipping input bytes.\n     * <p>\n     * This method uses the provided buffer, so there is no need to use a\n     * <code>BufferedInputStream</code>.\n     * </p>\n     * <p>\n     * Note that the implementation uses {@link #skip(InputStream, long)}.\n     * This means that the method may be considerably less efficient than using the actual skip implementation,\n     * this is done to guarantee that the correct number of characters are skipped.\n     * </p>\n     *\n     * @param input       the <code>InputStream</code> to read from\n     * @param output      the <code>OutputStream</code> to write to\n     * @param inputOffset : number of bytes to skip from input before copying\n     *                    -ve values are ignored\n     * @param length      : number of bytes to copy. -ve means all\n     * @param buffer      the buffer to use for the copy\n     * @return the number of bytes copied\n     * @throws NullPointerException if the input or output is null\n     * @throws IOException          if an I/O error occurs\n     * @since 2.2\n     */\n    public static long copyLarge(final InputStream input, final OutputStream output,\n                                 final long inputOffset, final long length, final byte[] buffer) throws IOException {\n        if (inputOffset > 0) {\n            skipFully(input, inputOffset);\n        }\n        if (length == 0) {\n            return 0;\n        }\n        final int bufferLength = buffer.length;\n        int bytesToRead = bufferLength;\n        if (length > 0 && length < bufferLength) {\n            bytesToRead = (int) length;\n        }\n        int read;\n        long totalRead = 0;\n        while (bytesToRead > 0 && EOF != (read = input.read(buffer, 0, bytesToRead))) {\n            output.write(buffer, 0, read);\n            totalRead += read;\n            if (length > 0) { // only adjust length if not reading to the end\n                // Note the cast must work because buffer.length is an integer\n                bytesToRead = (int) Math.min(length - totalRead, bufferLength);\n            }\n        }\n        return totalRead;\n    }\n\n    /**\n     * Copies chars from a large (over 2GB) <code>Reader</code> to a <code>Writer</code>.\n     * <p>\n     * This method buffers the input internally, so there is no need to use a\n     * <code>BufferedReader</code>.\n     * <p>\n     * The buffer size is given by {@link #DEFAULT_BUFFER_SIZE}.\n     *\n     * @param input  the <code>Reader</code> to read from\n     * @param output the <code>Writer</code> to write to\n     * @return the number of characters copied\n     * @throws NullPointerException if the input or output is null\n     * @throws IOException          if an I/O error occurs\n     * @since 1.3\n     */\n    public static long copyLarge(final Reader input, final Writer output) throws IOException {\n        return copyLarge(input, output, new char[DEFAULT_BUFFER_SIZE]);\n    }\n\n    /**\n     * Copies chars from a large (over 2GB) <code>Reader</code> to a <code>Writer</code>.\n     * <p>\n     * This method uses the provided buffer, so there is no need to use a\n     * <code>BufferedReader</code>.\n     * <p>\n     *\n     * @param input  the <code>Reader</code> to read from\n     * @param output the <code>Writer</code> to write to\n     * @param buffer the buffer to be used for the copy\n     * @return the number of characters copied\n     * @throws NullPointerException if the input or output is null\n     * @throws IOException          if an I/O error occurs\n     * @since 2.2\n     */\n    public static long copyLarge(final Reader input, final Writer output, final char[] buffer) throws IOException {\n        long count = 0;\n        int n;\n        while (EOF != (n = input.read(buffer))) {\n            output.write(buffer, 0, n);\n            count += n;\n        }\n        return count;\n    }\n\n    /**\n     * Copies some or all chars from a large (over 2GB) <code>InputStream</code> to an\n     * <code>OutputStream</code>, optionally skipping input chars.\n     * <p>\n     * This method buffers the input internally, so there is no need to use a\n     * <code>BufferedReader</code>.\n     * <p>\n     * The buffer size is given by {@link #DEFAULT_BUFFER_SIZE}.\n     *\n     * @param input       the <code>Reader</code> to read from\n     * @param output      the <code>Writer</code> to write to\n     * @param inputOffset : number of chars to skip from input before copying\n     *                    -ve values are ignored\n     * @param length      : number of chars to copy. -ve means all\n     * @return the number of chars copied\n     * @throws NullPointerException if the input or output is null\n     * @throws IOException          if an I/O error occurs\n     * @since 2.2\n     */\n    public static long copyLarge(final Reader input, final Writer output, final long inputOffset, final long length)\n            throws IOException {\n        return copyLarge(input, output, inputOffset, length, new char[DEFAULT_BUFFER_SIZE]);\n    }\n\n    /**\n     * Copies some or all chars from a large (over 2GB) <code>InputStream</code> to an\n     * <code>OutputStream</code>, optionally skipping input chars.\n     * <p>\n     * This method uses the provided buffer, so there is no need to use a\n     * <code>BufferedReader</code>.\n     * <p>\n     *\n     * @param input       the <code>Reader</code> to read from\n     * @param output      the <code>Writer</code> to write to\n     * @param inputOffset : number of chars to skip from input before copying\n     *                    -ve values are ignored\n     * @param length      : number of chars to copy. -ve means all\n     * @param buffer      the buffer to be used for the copy\n     * @return the number of chars copied\n     * @throws NullPointerException if the input or output is null\n     * @throws IOException          if an I/O error occurs\n     * @since 2.2\n     */\n    public static long copyLarge(final Reader input, final Writer output, final long inputOffset, final long length,\n                                 final char[] buffer)\n            throws IOException {\n        if (inputOffset > 0) {\n            skipFully(input, inputOffset);\n        }\n        if (length == 0) {\n            return 0;\n        }\n        int bytesToRead = buffer.length;\n        if (length > 0 && length < buffer.length) {\n            bytesToRead = (int) length;\n        }\n        int read;\n        long totalRead = 0;\n        while (bytesToRead > 0 && EOF != (read = input.read(buffer, 0, bytesToRead))) {\n            output.write(buffer, 0, read);\n            totalRead += read;\n            if (length > 0) { // only adjust length if not reading to the end\n                // Note the cast must work because buffer.length is an integer\n                bytesToRead = (int) Math.min(length - totalRead, buffer.length);\n            }\n        }\n        return totalRead;\n    }\n\n    /**\n     * Skips bytes from an input byte stream.\n     * This implementation guarantees that it will read as many bytes\n     * as possible before giving up; this may not always be the case for\n     * skip() implementations in subclasses of {@link InputStream}.\n     * <p>\n     * Note that the implementation uses {@link InputStream#read(byte[], int, int)} rather\n     * than delegating to {@link InputStream#skip(long)}.\n     * This means that the method may be considerably less efficient than using the actual skip implementation,\n     * this is done to guarantee that the correct number of bytes are skipped.\n     * </p>\n     *\n     * @param input  byte stream to skip\n     * @param toSkip number of bytes to skip.\n     * @return number of bytes actually skipped.\n     * @throws IOException              if there is a problem reading the file\n     * @throws IllegalArgumentException if toSkip is negative\n     * @see InputStream#skip(long)\n     * @see <a href=\"https://issues.apache.org/jira/browse/IO-203\">IO-203 - Add skipFully() method for InputStreams</a>\n     * @since 2.0\n     */\n    public static long skip(final InputStream input, final long toSkip) throws IOException {\n        if (toSkip < 0) {\n            throw new IllegalArgumentException(\"Skip count must be non-negative, actual: \" + toSkip);\n        }\n        /*\n         * N.B. no need to synchronize access to SKIP_BYTE_BUFFER: - we don't care if the buffer is created multiple\n         * times (the data is ignored) - we always use the same size buffer, so if it it is recreated it will still be\n         * OK (if the buffer size were variable, we would need to synch. to ensure some other thread did not create a\n         * smaller one)\n         */\n        long remain = toSkip;\n        while (remain > 0) {\n            // See https://issues.apache.org/jira/browse/IO-203 for why we use read() rather than delegating to skip()\n            final long n = input.read(SKIP_BYTE_BUFFER, 0, (int) Math.min(remain, SKIP_BYTE_BUFFER.length));\n            if (n < 0) { // EOF\n                break;\n            }\n            remain -= n;\n        }\n        return toSkip - remain;\n    }\n\n    /**\n     * Skips bytes from a ReadableByteChannel.\n     * This implementation guarantees that it will read as many bytes\n     * as possible before giving up.\n     *\n     * @param input  ReadableByteChannel to skip\n     * @param toSkip number of bytes to skip.\n     * @return number of bytes actually skipped.\n     * @throws IOException              if there is a problem reading the ReadableByteChannel\n     * @throws IllegalArgumentException if toSkip is negative\n     * @since 2.5\n     */\n    public static long skip(final ReadableByteChannel input, final long toSkip) throws IOException {\n        if (toSkip < 0) {\n            throw new IllegalArgumentException(\"Skip count must be non-negative, actual: \" + toSkip);\n        }\n        final ByteBuffer skipByteBuffer = ByteBuffer.allocate((int) Math.min(toSkip, SKIP_BYTE_BUFFER.length));\n        long remain = toSkip;\n        while (remain > 0) {\n            skipByteBuffer.position(0);\n            skipByteBuffer.limit((int) Math.min(remain, SKIP_BYTE_BUFFER.length));\n            final int n = input.read(skipByteBuffer);\n            if (n == EOF) {\n                break;\n            }\n            remain -= n;\n        }\n        return toSkip - remain;\n    }\n\n    /**\n     * Skips characters from an input character stream.\n     * This implementation guarantees that it will read as many characters\n     * as possible before giving up; this may not always be the case for\n     * skip() implementations in subclasses of {@link Reader}.\n     * <p>\n     * Note that the implementation uses {@link Reader#read(char[], int, int)} rather\n     * than delegating to {@link Reader#skip(long)}.\n     * This means that the method may be considerably less efficient than using the actual skip implementation,\n     * this is done to guarantee that the correct number of characters are skipped.\n     * </p>\n     *\n     * @param input  character stream to skip\n     * @param toSkip number of characters to skip.\n     * @return number of characters actually skipped.\n     * @throws IOException              if there is a problem reading the file\n     * @throws IllegalArgumentException if toSkip is negative\n     * @see Reader#skip(long)\n     * @see <a href=\"https://issues.apache.org/jira/browse/IO-203\">IO-203 - Add skipFully() method for InputStreams</a>\n     * @since 2.0\n     */\n    public static long skip(final Reader input, final long toSkip) throws IOException {\n        if (toSkip < 0) {\n            throw new IllegalArgumentException(\"Skip count must be non-negative, actual: \" + toSkip);\n        }\n        /*\n         * N.B. no need to synchronize this because: - we don't care if the buffer is created multiple times (the data\n         * is ignored) - we always use the same size buffer, so if it it is recreated it will still be OK (if the buffer\n         * size were variable, we would need to synch. to ensure some other thread did not create a smaller one)\n         */\n        if (SKIP_CHAR_BUFFER == null) {\n            SKIP_CHAR_BUFFER = new char[SKIP_BYTE_BUFFER.length];\n        }\n        long remain = toSkip;\n        while (remain > 0) {\n            // See https://issues.apache.org/jira/browse/IO-203 for why we use read() rather than delegating to skip()\n            final long n = input.read(SKIP_CHAR_BUFFER, 0, (int) Math.min(remain, SKIP_BYTE_BUFFER.length));\n            if (n < 0) { // EOF\n                break;\n            }\n            remain -= n;\n        }\n        return toSkip - remain;\n    }\n\n    /**\n     * Skips the requested number of bytes or fail if there are not enough left.\n     * <p>\n     * This allows for the possibility that {@link InputStream#skip(long)} may\n     * not skip as many bytes as requested (most likely because of reaching EOF).\n     * <p>\n     * Note that the implementation uses {@link #skip(InputStream, long)}.\n     * This means that the method may be considerably less efficient than using the actual skip implementation,\n     * this is done to guarantee that the correct number of characters are skipped.\n     * </p>\n     *\n     * @param input  stream to skip\n     * @param toSkip the number of bytes to skip\n     * @throws IOException              if there is a problem reading the file\n     * @throws IllegalArgumentException if toSkip is negative\n     * @throws EOFException             if the number of bytes skipped was incorrect\n     * @see InputStream#skip(long)\n     * @since 2.0\n     */\n    public static void skipFully(final InputStream input, final long toSkip) throws IOException {\n        if (toSkip < 0) {\n            throw new IllegalArgumentException(\"Bytes to skip must not be negative: \" + toSkip);\n        }\n        final long skipped = skip(input, toSkip);\n        if (skipped != toSkip) {\n            throw new EOFException(\"Bytes to skip: \" + toSkip + \" actual: \" + skipped);\n        }\n    }\n\n    /**\n     * Skips the requested number of bytes or fail if there are not enough left.\n     *\n     * @param input  ReadableByteChannel to skip\n     * @param toSkip the number of bytes to skip\n     * @throws IOException              if there is a problem reading the ReadableByteChannel\n     * @throws IllegalArgumentException if toSkip is negative\n     * @throws EOFException             if the number of bytes skipped was incorrect\n     * @since 2.5\n     */\n    public static void skipFully(final ReadableByteChannel input, final long toSkip) throws IOException {\n        if (toSkip < 0) {\n            throw new IllegalArgumentException(\"Bytes to skip must not be negative: \" + toSkip);\n        }\n        final long skipped = skip(input, toSkip);\n        if (skipped != toSkip) {\n            throw new EOFException(\"Bytes to skip: \" + toSkip + \" actual: \" + skipped);\n        }\n    }\n\n    /**\n     * Skips the requested number of characters or fail if there are not enough left.\n     * <p>\n     * This allows for the possibility that {@link Reader#skip(long)} may\n     * not skip as many characters as requested (most likely because of reaching EOF).\n     * <p>\n     * Note that the implementation uses {@link #skip(Reader, long)}.\n     * This means that the method may be considerably less efficient than using the actual skip implementation,\n     * this is done to guarantee that the correct number of characters are skipped.\n     * </p>\n     *\n     * @param input  stream to skip\n     * @param toSkip the number of characters to skip\n     * @throws IOException              if there is a problem reading the file\n     * @throws IllegalArgumentException if toSkip is negative\n     * @throws EOFException             if the number of characters skipped was incorrect\n     * @see Reader#skip(long)\n     * @since 2.0\n     */\n    public static void skipFully(final Reader input, final long toSkip) throws IOException {\n        final long skipped = skip(input, toSkip);\n        if (skipped != toSkip) {\n            throw new EOFException(\"Chars to skip: \" + toSkip + \" actual: \" + skipped);\n        }\n    }\n\n    /**\n     * Returns the length of the given array in a null-safe manner.\n     *\n     * @param array an array or null\n     * @return the array length -- or 0 if the given array is null.\n     * @since 2.7\n     */\n    public static int length(final byte[] array) {\n        return array == null ? 0 : array.length;\n    }\n\n    /**\n     * Returns the length of the given array in a null-safe manner.\n     *\n     * @param array an array or null\n     * @return the array length -- or 0 if the given array is null.\n     * @since 2.7\n     */\n    public static int length(final char[] array) {\n        return array == null ? 0 : array.length;\n    }\n\n    /**\n     * Returns the length of the given CharSequence in a null-safe manner.\n     *\n     * @param csq a CharSequence or null\n     * @return the CharSequence length -- or 0 if the given CharSequence is null.\n     * @since 2.7\n     */\n    public static int length(final CharSequence csq) {\n        return csq == null ? 0 : csq.length();\n    }\n\n    /**\n     * Returns the length of the given array in a null-safe manner.\n     *\n     * @param array an array or null\n     * @return the array length -- or 0 if the given array is null.\n     * @since 2.7\n     */\n    public static int length(final Object[] array) {\n        return array == null ? 0 : array.length;\n    }\n\n    /**\n     * Closes the given {@link Closeable} as a null-safe operation.\n     *\n     * @param closeable The resource to close, may be null.\n     * @throws IOException if an I/O error occurs.\n     * @since 2.7\n     */\n    public static void close(final Closeable closeable) throws IOException {\n        if (closeable != null) {\n            closeable.close();\n        }\n    }\n\n    /**\n     * Closes the given {@link Closeable} as a null-safe operation.\n     *\n     * @param closeables The resource(s) to close, may be null.\n     * @throws IOException if an I/O error occurs.\n     * @since 2.8.0\n     */\n    public static void close(final Closeable... closeables) throws IOException {\n        if (closeables != null) {\n            for (final Closeable closeable : closeables) {\n                close(closeable);\n            }\n        }\n    }\n\n    /**\n     * Closes the given {@link Closeable} as a null-safe operation.\n     *\n     * @param closeable The resource to close, may be null.\n     * @param consumer  Consume the IOException thrown by {@link Closeable#close()}.\n     * @throws IOException if an I/O error occurs.\n     * @since 2.7\n     */\n    public static void close(final Closeable closeable, final IOConsumer<IOException> consumer) throws IOException {\n        if (closeable != null) {\n            try {\n                closeable.close();\n            } catch (final IOException e) {\n                if (consumer != null) {\n                    consumer.accept(e);\n                }\n            }\n        }\n    }\n\n    /**\n     * Closes a URLConnection.\n     *\n     * @param conn the connection to close.\n     * @since 2.4\n     */\n    public static void close(final URLConnection conn) {\n        if (conn instanceof HttpURLConnection) {\n            ((HttpURLConnection) conn).disconnect();\n        }\n    }\n\n    @SuppressWarnings(\"unused\")\n    public static String Stream2String(InputStream inputStream) {\n        ByteArrayOutputStream result = new ByteArrayOutputStream();\n        try {\n            byte[] buffer = new byte[DEFAULT_BUFFER_SIZE];\n            int length;\n            while ((length = inputStream.read(buffer)) != -1) {\n                result.write(buffer, 0, length);\n            }\n            return result.toString();\n        } catch (Exception e) {\n            return e.getLocalizedMessage();\n        }\n\n    }\n}\n"
  },
  {
    "path": "modules/book/src/main/java/me/ag2s/epublib/util/NoCloseOutputStream.java",
    "content": "package me.ag2s.epublib.util;\n\nimport java.io.IOException;\nimport java.io.OutputStream;\n\n/**\n * OutputStream with the close() disabled.\n * We write multiple documents to a ZipOutputStream.\n * Some of the formatters call a close() after writing their data.\n * We don't want them to do that, so we wrap regular OutputStreams in this NoCloseOutputStream.\n *\n * @author paul\n */\n@SuppressWarnings(\"unused\")\npublic class NoCloseOutputStream extends OutputStream {\n\n    private final OutputStream outputStream;\n\n    public NoCloseOutputStream(OutputStream outputStream) {\n        this.outputStream = outputStream;\n    }\n\n    @Override\n    public void write(int b) throws IOException {\n        outputStream.write(b);\n    }\n\n    /**\n     * A close() that does not call it's parent's close()\n     */\n    public void close() {\n    }\n}\n"
  },
  {
    "path": "modules/book/src/main/java/me/ag2s/epublib/util/NoCloseWriter.java",
    "content": "package me.ag2s.epublib.util;\n\nimport java.io.IOException;\nimport java.io.Writer;\n\n/**\n * Writer with the close() disabled.\n * We write multiple documents to a ZipOutputStream.\n * Some of the formatters call a close() after writing their data.\n * We don't want them to do that, so we wrap regular Writers in this NoCloseWriter.\n *\n * @author paul\n */\n@SuppressWarnings(\"unused\")\npublic class NoCloseWriter extends Writer {\n\n    private final Writer writer;\n\n    public NoCloseWriter(Writer writer) {\n        this.writer = writer;\n    }\n\n    @Override\n    public void close() {\n    }\n\n    @Override\n    public void flush() throws IOException {\n        writer.flush();\n    }\n\n    @Override\n    public void write(char[] cbuf, int off, int len) throws IOException {\n        writer.write(cbuf, off, len);\n    }\n}\n"
  },
  {
    "path": "modules/book/src/main/java/me/ag2s/epublib/util/ResourceUtil.java",
    "content": "package me.ag2s.epublib.util;\n\nimport org.w3c.dom.Document;\nimport org.xml.sax.InputSource;\nimport org.xml.sax.SAXException;\n\nimport java.io.File;\nimport java.io.FileInputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.Reader;\nimport java.io.UnsupportedEncodingException;\nimport java.util.zip.ZipInputStream;\n\nimport javax.xml.parsers.DocumentBuilder;\n\nimport me.ag2s.epublib.Constants;\nimport me.ag2s.epublib.domain.MediaType;\nimport me.ag2s.epublib.domain.MediaTypes;\nimport me.ag2s.epublib.domain.Resource;\nimport me.ag2s.epublib.epub.EpubProcessorSupport;\n\n/**\n * Various resource utility methods\n *\n * @author paul\n */\npublic class ResourceUtil {\n    /**\n     * 快速创建HTML类型的Resource\n     *\n     * @param title 章节的标题\n     * @param txt   章节的正文\n     * @param model html模板\n     * @return 返回Resource\n     */\n    public static Resource createChapterResource(String title, String txt, String model, String href) {\n        //String[] title_list = title.split(\"\\\\s+\", 2);\n        //String title_part1 = title_list[0];\n        //String title_part2 = \"\";\n        //if (title_list.length == 2) {\n        //    title_part2 = title_list[1];\n        //}\n        String ori_title = title;\n        title = title.replaceFirst(\"\\\\s+\", \"</span><br />\");\n        if (title.contains(\"</span>\")) {\n            title = \"<span class=\\\"chapter-sequence-number\\\">\" + title;\n        }\n        String html = model.replace(\"{title}\", title)\n                .replace(\"{ori_title}\", ori_title)\n                //.replace(\"{title_part1}\", title_part1)\n                //.replace(\"{title_part2}\", title_part2)\n                .replace(\"{content}\", StringUtil.formatHtml(txt));\n        return new Resource(html.getBytes(), href);\n    }\n\n    public static Resource createPublicResource(String name, String author, String intro, String kind, String wordCount, String model, String href) {\n        String html = model.replace(\"{name}\", name)\n                .replace(\"{author}\", author)\n                .replace(\"{kind}\", kind == null ? \"\" : kind)\n                .replace(\"{wordCount}\", wordCount == null ? \"\" : wordCount)\n                .replace(\"{intro}\", StringUtil.formatHtml(intro == null ? \"\" : intro));\n        return new Resource(html.getBytes(), href);\n    }\n\n    /**\n     * 快速从File创建Resource\n     *\n     * @param file File\n     * @return Resource\n     * @throws IOException IOException\n     */\n\n    @SuppressWarnings(\"unused\")\n    public static Resource createResource(File file) throws IOException {\n        if (file == null) {\n            return null;\n        }\n        MediaType mediaType = MediaTypes.determineMediaType(file.getName());\n        byte[] data = IOUtil.toByteArray(new FileInputStream(file));\n        return new Resource(data, mediaType);\n    }\n\n\n    /**\n     * 创建一个只带标题的HTMl类型的Resource,常用于封面页，大卷页\n     *\n     * @param title v\n     * @param href  v\n     * @return a resource with as contents a html page with the given title.\n     */\n    @SuppressWarnings(\"unused\")\n    public static Resource createResource(String title, String href) {\n        String content =\n                \"<html><head><title>\" + title + \"</title></head><body><h1>\" + title\n                        + \"</h1></body></html>\";\n        return new Resource(null, content.getBytes(), href, MediaTypes.XHTML,\n                Constants.CHARACTER_ENCODING);\n    }\n\n    /**\n     * Creates a resource out of the given zipEntry and zipInputStream.\n     *\n     * @param name           v\n     * @param zipInputStream v\n     * @return a resource created out of the given zipEntry and zipInputStream.\n     * @throws IOException v\n     */\n    public static Resource createResource(String name,\n                                          ZipInputStream zipInputStream) throws IOException {\n        return new Resource(zipInputStream, name);\n\n    }\n\n    public static Resource createResource(String name,\n                                          InputStream zipInputStream) throws IOException {\n        return new Resource(zipInputStream, name);\n\n    }\n\n    /**\n     * Converts a given string from given input character encoding to the requested output character encoding.\n     *\n     * @param inputEncoding  v\n     * @param outputEncoding v\n     * @param input          v\n     * @return the string from given input character encoding converted to the requested output character encoding.\n     * @throws UnsupportedEncodingException v\n     */\n    @SuppressWarnings(\"unused\")\n    public static byte[] recode(String inputEncoding, String outputEncoding,\n                                byte[] input) throws UnsupportedEncodingException {\n        return new String(input, inputEncoding).getBytes(outputEncoding);\n    }\n\n    /**\n     * Gets the contents of the Resource as an InputSource in a null-safe manner.\n     */\n    @SuppressWarnings(\"unused\")\n    public static InputSource getInputSource(Resource resource)\n            throws IOException {\n        if (resource == null) {\n            return null;\n        }\n        Reader reader = resource.getReader();\n        if (reader == null) {\n            return null;\n        }\n        return new InputSource(reader);\n    }\n\n\n    /**\n     * Reads parses the xml therein and returns the result as a Document\n     */\n    public static Document getAsDocument(Resource resource)\n            throws SAXException, IOException {\n        return getAsDocument(resource,\n                EpubProcessorSupport.createDocumentBuilder());\n    }\n\n    /**\n     * Reads the given resources inputstream, parses the xml therein and returns the result as a Document\n     *\n     * @param resource        v\n     * @param documentBuilder v\n     * @return the document created from the given resource\n     * @throws UnsupportedEncodingException v\n     * @throws SAXException                 v\n     * @throws IOException                  v\n     */\n    public static Document getAsDocument(Resource resource,\n                                         DocumentBuilder documentBuilder)\n            throws UnsupportedEncodingException, SAXException, IOException {\n        InputSource inputSource = getInputSource(resource);\n        if (inputSource == null) {\n            return null;\n        }\n        return documentBuilder.parse(inputSource);\n    }\n}\n"
  },
  {
    "path": "modules/book/src/main/java/me/ag2s/epublib/util/StringUtil.java",
    "content": "package me.ag2s.epublib.util;\n\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.List;\n\n/**\n * Various String utility functions.\n * <p>\n * Most of the functions herein are re-implementations of the ones in apache\n * commons StringUtils. The reason for re-implementing this is that the\n * functions are fairly simple and using my own implementation saves the\n * inclusion of a 200Kb jar file.\n *\n * @author paul.siegmann\n */\npublic class StringUtil {\n\n    /**\n     * Changes a path containing '..', '.' and empty dirs into a path that\n     * doesn't. X/foo/../Y is changed into 'X/Y', etc. Does not handle invalid\n     * paths like \"../\".\n     *\n     * @param path path\n     * @return the normalized path\n     */\n    public static String collapsePathDots(String path) {\n        String[] stringParts = path.split(\"/\");\n        List<String> parts = new ArrayList<>(Arrays.asList(stringParts));\n        for (int i = 0; i < parts.size() - 1; i++) {\n            String currentDir = parts.get(i);\n            if (currentDir.length() == 0 || currentDir.equals(\".\")) {\n                parts.remove(i);\n                i--;\n            } else if (currentDir.equals(\"..\")) {\n                parts.remove(i - 1);\n                parts.remove(i - 1);\n                i -= 2;\n            }\n        }\n        StringBuilder result = new StringBuilder();\n        if (path.startsWith(\"/\")) {\n            result.append('/');\n        }\n        for (int i = 0; i < parts.size(); i++) {\n            result.append(parts.get(i));\n            if (i < (parts.size() - 1)) {\n                result.append('/');\n            }\n        }\n        return result.toString();\n    }\n\n    /**\n     * Whether the String is not null, not zero-length and does not contain of\n     * only whitespace.\n     *\n     * @param text text\n     * @return Whether the String is not null, not zero-length and does not contain of\n     */\n    public static boolean isNotBlank(String text) {\n        return !isBlank(text);\n    }\n\n    /**\n     * Whether the String is null, zero-length and does contain only whitespace.\n     *\n     * @return Whether the String is null, zero-length and does contain only whitespace.\n     */\n    public static boolean isBlank(String text) {\n        if (isEmpty(text)) {\n            return true;\n        }\n        for (int i = 0; i < text.length(); i++) {\n            if (!Character.isWhitespace(text.charAt(i))) {\n                return false;\n            }\n        }\n        return true;\n    }\n\n    /**\n     * Whether the given string is null or zero-length.\n     *\n     * @param text the input for this method\n     * @return Whether the given string is null or zero-length.\n     */\n    public static boolean isEmpty(String text) {\n        return (text == null) || (text.length() == 0);\n    }\n\n    /**\n     * Whether the given source string ends with the given suffix, ignoring\n     * case.\n     *\n     * @param source source\n     * @param suffix suffix\n     * @return Whether the given source string ends with the given suffix, ignoring case.\n     */\n    public static boolean endsWithIgnoreCase(String source, String suffix) {\n        if (isEmpty(suffix)) {\n            return true;\n        }\n        if (isEmpty(source)) {\n            return false;\n        }\n        if (suffix.length() > source.length()) {\n            return false;\n        }\n        return source.substring(source.length() - suffix.length())\n                .toLowerCase().endsWith(suffix.toLowerCase());\n    }\n\n    public static boolean startsWithIgnoreCase(String source, String prefix) {\n        if (isEmpty(prefix)) {\n            return true;\n        }\n        if (isEmpty(source)) {\n            return false;\n        }\n        if (prefix.length() > source.length()) {\n            return false;\n        }\n        return source.substring(0, prefix.length())\n                .toLowerCase().startsWith(prefix.toLowerCase());\n    }\n\n    /**\n     * If the given text is null return \"\", the original text otherwise.\n     *\n     * @param text text\n     * @return If the given text is null \"\", the original text otherwise.\n     */\n    public static String defaultIfNull(String text) {\n        return defaultIfNull(text, \"\");\n    }\n\n    /**\n     * If the given text is null return \"\", the given defaultValue otherwise.\n     *\n     * @param text         d\n     * @param defaultValue d\n     * @return If the given text is null \"\", the given defaultValue otherwise.\n     */\n    public static String defaultIfNull(String text, String defaultValue) {\n        if (text == null) {\n            return defaultValue;\n        }\n        return text;\n    }\n\n    /**\n     * Null-safe string comparator\n     *\n     * @param text1 d\n     * @param text2 d\n     * @return whether the two strings are equal\n     */\n    public static boolean equals(String text1, String text2) {\n        if (text1 == null) {\n            return (text2 == null);\n        }\n        return text1.equals(text2);\n    }\n\n    /**\n     * Pretty toString printer.\n     *\n     * @param keyValues d\n     * @return a string representation of the input values\n     */\n    public static String toString(Object... keyValues) {\n        StringBuilder result = new StringBuilder();\n        result.append('[');\n        for (int i = 0; i < keyValues.length; i += 2) {\n            if (i > 0) {\n                result.append(\", \");\n            }\n            result.append(keyValues[i]);\n            result.append(\": \");\n            Object value = null;\n            if ((i + 1) < keyValues.length) {\n                value = keyValues[i + 1];\n            }\n            if (value == null) {\n                result.append(\"<null>\");\n            } else {\n                result.append('\\'');\n                result.append(value);\n                result.append('\\'');\n            }\n        }\n        result.append(']');\n        return result.toString();\n    }\n\n    public static int hashCode(String... values) {\n        int result = 31;\n        for (String value : values) {\n            result ^= String.valueOf(value).hashCode();\n        }\n        return result;\n    }\n\n    /**\n     * Gives the substring of the given text before the given separator.\n     * <p>\n     * If the text does not contain the given separator then the given text is\n     * returned.\n     *\n     * @param text      d\n     * @param separator d\n     * @return the substring of the given text before the given separator.\n     */\n    public static String substringBefore(String text, char separator) {\n        if (isEmpty(text)) {\n            return text;\n        }\n        int sepPos = text.indexOf(separator);\n        if (sepPos < 0) {\n            return text;\n        }\n        return text.substring(0, sepPos);\n    }\n\n    /**\n     * Gives the substring of the given text before the last occurrence of the\n     * given separator.\n     * <p>\n     * If the text does not contain the given separator then the given text is\n     * returned.\n     *\n     * @param text      d\n     * @param separator d\n     * @return the substring of the given text before the last occurrence of the given separator.\n     */\n    public static String substringBeforeLast(String text, char separator) {\n        if (isEmpty(text)) {\n            return text;\n        }\n        int cPos = text.lastIndexOf(separator);\n        if (cPos < 0) {\n            return text;\n        }\n        return text.substring(0, cPos);\n    }\n\n    /**\n     * Gives the substring of the given text after the last occurrence of the\n     * given separator.\n     * <p>\n     * If the text does not contain the given separator then \"\" is returned.\n     *\n     * @param text      d\n     * @param separator d\n     * @return the substring of the given text after the last occurrence of the given separator.\n     */\n    public static String substringAfterLast(String text, char separator) {\n        if (isEmpty(text)) {\n            return text;\n        }\n        int cPos = text.lastIndexOf(separator);\n        if (cPos < 0) {\n            return \"\";\n        }\n        return text.substring(cPos + 1);\n    }\n\n    /**\n     * Gives the substring of the given text after the given separator.\n     * <p>\n     * If the text does not contain the given separator then \"\" is returned.\n     *\n     * @param text the input text\n     * @param c    the separator char\n     * @return the substring of the given text after the given separator.\n     */\n    public static String substringAfter(String text, char c) {\n        if (isEmpty(text)) {\n            return text;\n        }\n        int cPos = text.indexOf(c);\n        if (cPos < 0) {\n            return \"\";\n        }\n        return text.substring(cPos + 1);\n    }\n\n    public static String formatHtml(String text) {\n        StringBuilder body = new StringBuilder();\n        for (String s : text.split(\"\\\\r?\\\\n\")) {\n            s = s.replaceAll(\"^\\\\s+|\\\\s+$\", \"\");\n            if (s.length() > 0) {\n                //段落为一张图片才认定为图片章节/漫画并启用多看单图优化，否则认定为普通文字夹杂着的图片文字。\n                if (s.matches(\"(?i)^<img\\\\s([^>]+)/?>$\")) {\n                    body.append(s.replaceAll(\"(?i)^<img\\\\s([^>]+)/?>$\",\n                            \"<div class=\\\"duokan-image-single\\\"><img class=\\\"picture-80\\\" $1/></div>\"));\n                } else {\n                    body.append(\"<p>\").append(s).append(\"</p>\");\n                }\n            }\n        }\n        return body.toString();\n    }\n}\n"
  },
  {
    "path": "modules/book/src/main/java/me/ag2s/epublib/util/URLEncodeUtil.java",
    "content": "package me.ag2s.epublib.util;\n\nimport java.io.CharArrayWriter;\nimport java.nio.charset.Charset;\nimport java.util.BitSet;\nimport java.util.Objects;\n\nimport kotlin.text.Charsets;\n\npublic class URLEncodeUtil {\n\n    static BitSet dontNeedEncoding;\n    static final int caseDiff = ('a' - 'A');\n\n    static {\n        dontNeedEncoding = new BitSet(256);\n        int i;\n        for (i = 'a'; i <= 'z'; i++) {\n            dontNeedEncoding.set(i);\n        }\n        for (i = 'A'; i <= 'Z'; i++) {\n            dontNeedEncoding.set(i);\n        }\n        for (i = '0'; i <= '9'; i++) {\n            dontNeedEncoding.set(i);\n        }\n        String mark = \"-_.!~*'()\";\n        for (i = 0; i < mark.length(); i++) {\n            dontNeedEncoding.set(mark.charAt(i));\n        }\n        String reversed = \";/?:@&=+$,#\";\n        for (i = 0; i < reversed.length(); i++) {\n            dontNeedEncoding.set(reversed.charAt(i));\n        }\n    }\n\n    public static String encode(String s, Charset charset) {\n        Objects.requireNonNull(charset, \"charset\");\n\n        boolean needToChange = false;\n        StringBuilder out = new StringBuilder(s.length());\n        CharArrayWriter charArrayWriter = new CharArrayWriter();\n\n        for (int i = 0; i < s.length(); ) {\n            int c = s.charAt(i);\n            //System.out.println(\"Examining character: \" + c);\n            if (dontNeedEncoding.get(c)) {\n                //System.out.println(\"Storing: \" + c);\n                out.append((char) c);\n                i++;\n            } else {\n                // convert to external encoding before hex conversion\n                do {\n                    charArrayWriter.write(c);\n                    /*\n                     * If this character represents the start of a Unicode\n                     * surrogate pair, then pass in two characters. It's not\n                     * clear what should be done if a byte reserved in the\n                     * surrogate pairs range occurs outside of a legal\n                     * surrogate pair. For now, just treat it as if it were\n                     * any other character.\n                     */\n                    if (c >= 0xD800 && c <= 0xDBFF) {\n                        /*\n                          System.out.println(Integer.toHexString(c)\n                          + \" is high surrogate\");\n                        */\n                        if ((i + 1) < s.length()) {\n                            int d = (int) s.charAt(i + 1);\n                            /*\n                              System.out.println(\"\\tExamining \"\n                              + Integer.toHexString(d));\n                            */\n                            if (d >= 0xDC00 && d <= 0xDFFF) {\n                                /*\n                                  System.out.println(\"\\t\"\n                                  + Integer.toHexString(d)\n                                  + \" is low surrogate\");\n                                */\n                                charArrayWriter.write(d);\n                                i++;\n                            }\n                        }\n                    }\n                    i++;\n                } while (i < s.length() && !dontNeedEncoding.get((c = (int) s.charAt(i))));\n\n                charArrayWriter.flush();\n                String str = new String(charArrayWriter.toCharArray());\n                byte[] ba = str.getBytes(charset);\n                for (int j = 0; j < ba.length; j++) {\n                    out.append('%');\n                    char ch = Character.forDigit((ba[j] >> 4) & 0xF, 16);\n                    // converting to use uppercase letter as part of\n                    // the hex value if ch is a letter.\n                    if (Character.isLetter(ch)) {\n                        ch -= caseDiff;\n                    }\n                    out.append(ch);\n                    ch = Character.forDigit(ba[j] & 0xF, 16);\n                    if (Character.isLetter(ch)) {\n                        ch -= caseDiff;\n                    }\n                    out.append(ch);\n                }\n                charArrayWriter.reset();\n                needToChange = true;\n            }\n        }\n\n        return (needToChange ? out.toString() : s);\n    }\n\n    public static String encode(String s) {\n        return encode(s, Charsets.UTF_8);\n    }\n\n}\n"
  },
  {
    "path": "modules/book/src/main/java/me/ag2s/epublib/util/commons/io/BOMInputStream.java",
    "content": "/*\n * Licensed to the Apache Software Foundation (ASF) under one or more\n * contributor license agreements.  See the NOTICE file distributed with\n * this work for additional information regarding copyright ownership.\n * The ASF licenses this file to You under the Apache License, Version 2.0\n * (the \"License\"); you may not use this file except in compliance with\n * the License.  You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\npackage me.ag2s.epublib.util.commons.io;\n\n\nimport static me.ag2s.epublib.util.IOUtil.EOF;\n\nimport android.os.Build;\n\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.util.Arrays;\nimport java.util.Comparator;\nimport java.util.List;\n\nimport me.ag2s.epublib.util.IOUtil;\n\n\n/**\n * This class is used to wrap a stream that includes an encoded {@link ByteOrderMark} as its first bytes.\n * <p>\n * This class detects these bytes and, if required, can automatically skip them and return the subsequent byte as the\n * first byte in the stream.\n * <p>\n * The {@link ByteOrderMark} implementation has the following pre-defined BOMs:\n * <ul>\n * <li>UTF-8 - {@link ByteOrderMark#UTF_8}</li>\n * <li>UTF-16BE - {@link ByteOrderMark#UTF_16LE}</li>\n * <li>UTF-16LE - {@link ByteOrderMark#UTF_16BE}</li>\n * <li>UTF-32BE - {@link ByteOrderMark#UTF_32LE}</li>\n * <li>UTF-32LE - {@link ByteOrderMark#UTF_32BE}</li>\n * </ul>\n *\n *\n * <h2>Example 1 - Detect and exclude a UTF-8 BOM</h2>\n *\n * <pre>\n * BOMInputStream bomIn = new BOMInputStream(in);\n * if (bomIn.hasBOM()) {\n *     // has a UTF-8 BOM\n * }\n * </pre>\n *\n * <h2>Example 2 - Detect a UTF-8 BOM (but don't exclude it)</h2>\n *\n * <pre>\n * boolean include = true;\n * BOMInputStream bomIn = new BOMInputStream(in, include);\n * if (bomIn.hasBOM()) {\n *     // has a UTF-8 BOM\n * }\n * </pre>\n *\n * <h2>Example 3 - Detect Multiple BOMs</h2>\n *\n * <pre>\n * BOMInputStream bomIn = new BOMInputStream(in,\n *   ByteOrderMark.UTF_16LE, ByteOrderMark.UTF_16BE,\n *   ByteOrderMark.UTF_32LE, ByteOrderMark.UTF_32BE\n *   );\n * if (bomIn.hasBOM() == false) {\n *     // No BOM found\n * } else if (bomIn.hasBOM(ByteOrderMark.UTF_16LE)) {\n *     // has a UTF-16LE BOM\n * } else if (bomIn.hasBOM(ByteOrderMark.UTF_16BE)) {\n *     // has a UTF-16BE BOM\n * } else if (bomIn.hasBOM(ByteOrderMark.UTF_32LE)) {\n *     // has a UTF-32LE BOM\n * } else if (bomIn.hasBOM(ByteOrderMark.UTF_32BE)) {\n *     // has a UTF-32BE BOM\n * }\n * </pre>\n *\n * @see ByteOrderMark\n * @see <a href=\"http://en.wikipedia.org/wiki/Byte_order_mark\">Wikipedia - Byte Order Mark</a>\n * @since 2.0\n */\npublic class BOMInputStream extends ProxyInputStream {\n    private final boolean include;\n    /**\n     * BOMs are sorted from longest to shortest.\n     */\n    private final List<ByteOrderMark> boms;\n    private ByteOrderMark byteOrderMark;\n    private int[] firstBytes;\n    private int fbLength;\n    private int fbIndex;\n    private int markFbIndex;\n    private boolean markedAtStart;\n\n    /**\n     * Constructs a new BOM InputStream that excludes a {@link ByteOrderMark#UTF_8} BOM.\n     *\n     * @param delegate the InputStream to delegate to\n     */\n    @SuppressWarnings(\"unused\")\n    public BOMInputStream(final InputStream delegate) {\n        this(delegate, false, ByteOrderMark.UTF_8);\n    }\n\n    /**\n     * Constructs a new BOM InputStream that detects a a {@link ByteOrderMark#UTF_8} and optionally includes it.\n     *\n     * @param delegate the InputStream to delegate to\n     * @param include  true to include the UTF-8 BOM or false to exclude it\n     */\n    @SuppressWarnings(\"unused\")\n    public BOMInputStream(final InputStream delegate, final boolean include) {\n        this(delegate, include, ByteOrderMark.UTF_8);\n    }\n\n    /**\n     * Constructs a new BOM InputStream that excludes the specified BOMs.\n     *\n     * @param delegate the InputStream to delegate to\n     * @param boms     The BOMs to detect and exclude\n     */\n    @SuppressWarnings(\"unused\")\n    public BOMInputStream(final InputStream delegate, final ByteOrderMark... boms) {\n        this(delegate, false, boms);\n    }\n\n    /**\n     * Compares ByteOrderMark objects in descending length order.\n     */\n    private static final Comparator<ByteOrderMark> ByteOrderMarkLengthComparator = (bom1, bom2) -> {\n        final int len1 = bom1.length();\n        final int len2 = bom2.length();\n        return Integer.compare(len2, len1);\n    };\n\n    /**\n     * Constructs a new BOM InputStream that detects the specified BOMs and optionally includes them.\n     *\n     * @param delegate the InputStream to delegate to\n     * @param include  true to include the specified BOMs or false to exclude them\n     * @param boms     The BOMs to detect and optionally exclude\n     */\n    public BOMInputStream(final InputStream delegate, final boolean include, final ByteOrderMark... boms) {\n        super(delegate);\n        if (IOUtil.length(boms) == 0) {\n            throw new IllegalArgumentException(\"No BOMs specified\");\n        }\n        this.include = include;\n        final List<ByteOrderMark> list = Arrays.asList(boms);\n        // Sort the BOMs to match the longest BOM first because some BOMs have the same starting two bytes.\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {\n            list.sort(ByteOrderMarkLengthComparator);\n        }\n        this.boms = list;\n\n    }\n\n    /**\n     * Indicates whether the stream contains one of the specified BOMs.\n     *\n     * @return true if the stream has one of the specified BOMs, otherwise false if it does not\n     * @throws IOException if an error reading the first bytes of the stream occurs\n     */\n    @SuppressWarnings(\"unused\")\n    public boolean hasBOM() throws IOException {\n        return getBOM() != null;\n    }\n\n    /**\n     * Indicates whether the stream contains the specified BOM.\n     *\n     * @param bom The BOM to check for\n     * @return true if the stream has the specified BOM, otherwise false if it does not\n     * @throws IllegalArgumentException if the BOM is not one the stream is configured to detect\n     * @throws IOException              if an error reading the first bytes of the stream occurs\n     */\n    @SuppressWarnings(\"unused\")\n    public boolean hasBOM(final ByteOrderMark bom) throws IOException {\n        if (!boms.contains(bom)) {\n            throw new IllegalArgumentException(\"Stream not configure to detect \" + bom);\n        }\n        getBOM();\n        return byteOrderMark != null && byteOrderMark.equals(bom);\n    }\n\n    /**\n     * Return the BOM (Byte Order Mark).\n     *\n     * @return The BOM or null if none\n     * @throws IOException if an error reading the first bytes of the stream occurs\n     */\n    public ByteOrderMark getBOM() throws IOException {\n        if (firstBytes == null) {\n            fbLength = 0;\n            // BOMs are sorted from longest to shortest\n            final int maxBomSize = boms.get(0).length();\n            firstBytes = new int[maxBomSize];\n            // Read first maxBomSize bytes\n            for (int i = 0; i < firstBytes.length; i++) {\n                firstBytes[i] = in.read();\n                fbLength++;\n                if (firstBytes[i] < 0) {\n                    break;\n                }\n            }\n            // match BOM in firstBytes\n            byteOrderMark = find();\n            if (byteOrderMark != null) {\n                if (!include) {\n                    if (byteOrderMark.length() < firstBytes.length) {\n                        fbIndex = byteOrderMark.length();\n                    } else {\n                        fbLength = 0;\n                    }\n                }\n            }\n        }\n        return byteOrderMark;\n    }\n\n    /**\n     * Return the BOM charset Name - {@link ByteOrderMark#getCharsetName()}.\n     *\n     * @return The BOM charset Name or null if no BOM found\n     * @throws IOException if an error reading the first bytes of the stream occurs\n     */\n    public String getBOMCharsetName() throws IOException {\n        getBOM();\n        return byteOrderMark == null ? null : byteOrderMark.getCharsetName();\n    }\n\n    /**\n     * This method reads and either preserves or skips the first bytes in the stream. It behaves like the single-byte\n     * <code>read()</code> method, either returning a valid byte or -1 to indicate that the initial bytes have been\n     * processed already.\n     *\n     * @return the byte read (excluding BOM) or -1 if the end of stream\n     * @throws IOException if an I/O error occurs\n     */\n    private int readFirstBytes() throws IOException {\n        getBOM();\n        return fbIndex < fbLength ? firstBytes[fbIndex++] : EOF;\n    }\n\n    /**\n     * Find a BOM with the specified bytes.\n     *\n     * @return The matched BOM or null if none matched\n     */\n    private ByteOrderMark find() {\n        for (final ByteOrderMark bom : boms) {\n            if (matches(bom)) {\n                return bom;\n            }\n        }\n        return null;\n    }\n\n    /**\n     * Check if the bytes match a BOM.\n     *\n     * @param bom The BOM\n     * @return true if the bytes match the bom, otherwise false\n     */\n    private boolean matches(final ByteOrderMark bom) {\n        // if (bom.length() != fbLength) {\n        // return false;\n        // }\n        // firstBytes may be bigger than the BOM bytes\n        for (int i = 0; i < bom.length(); i++) {\n            if (bom.get(i) != firstBytes[i]) {\n                return false;\n            }\n        }\n        return true;\n    }\n\n    // ----------------------------------------------------------------------------\n    // Implementation of InputStream\n    // ----------------------------------------------------------------------------\n\n    /**\n     * Invokes the delegate's <code>read()</code> method, detecting and optionally skipping BOM.\n     *\n     * @return the byte read (excluding BOM) or -1 if the end of stream\n     * @throws IOException if an I/O error occurs\n     */\n    @Override\n    public int read() throws IOException {\n        final int b = readFirstBytes();\n        return b >= 0 ? b : in.read();\n    }\n\n    /**\n     * Invokes the delegate's <code>read(byte[], int, int)</code> method, detecting and optionally skipping BOM.\n     *\n     * @param buf the buffer to read the bytes into\n     * @param off The start offset\n     * @param len The number of bytes to read (excluding BOM)\n     * @return the number of bytes read or -1 if the end of stream\n     * @throws IOException if an I/O error occurs\n     */\n    @Override\n    public int read(final byte[] buf, int off, int len) throws IOException {\n        int firstCount = 0;\n        int b = 0;\n        while (len > 0 && b >= 0) {\n            b = readFirstBytes();\n            if (b >= 0) {\n                buf[off++] = (byte) (b & 0xFF);\n                len--;\n                firstCount++;\n            }\n        }\n        final int secondCount = in.read(buf, off, len);\n        return secondCount < 0 ? firstCount > 0 ? firstCount : EOF : firstCount + secondCount;\n    }\n\n    /**\n     * Invokes the delegate's <code>read(byte[])</code> method, detecting and optionally skipping BOM.\n     *\n     * @param buf the buffer to read the bytes into\n     * @return the number of bytes read (excluding BOM) or -1 if the end of stream\n     * @throws IOException if an I/O error occurs\n     */\n    @Override\n    public int read(final byte[] buf) throws IOException {\n        return read(buf, 0, buf.length);\n    }\n\n    /**\n     * Invokes the delegate's <code>mark(int)</code> method.\n     *\n     * @param readlimit read ahead limit\n     */\n    @Override\n    public synchronized void mark(final int readlimit) {\n        markFbIndex = fbIndex;\n        markedAtStart = firstBytes == null;\n        in.mark(readlimit);\n    }\n\n    /**\n     * Invokes the delegate's <code>reset()</code> method.\n     *\n     * @throws IOException if an I/O error occurs\n     */\n    @Override\n    public synchronized void reset() throws IOException {\n        fbIndex = markFbIndex;\n        if (markedAtStart) {\n            firstBytes = null;\n        }\n\n        in.reset();\n    }\n\n    /**\n     * Invokes the delegate's <code>skip(long)</code> method, detecting and optionally skipping BOM.\n     *\n     * @param n the number of bytes to skip\n     * @return the number of bytes to skipped or -1 if the end of stream\n     * @throws IOException if an I/O error occurs\n     */\n    @Override\n    public long skip(final long n) throws IOException {\n        int skipped = 0;\n        while ((n > skipped) && (readFirstBytes() >= 0)) {\n            skipped++;\n        }\n        return in.skip(n - skipped) + skipped;\n    }\n}\n"
  },
  {
    "path": "modules/book/src/main/java/me/ag2s/epublib/util/commons/io/ByteOrderMark.java",
    "content": "package me.ag2s.epublib.util.commons.io;\n\n/*\n * Licensed to the Apache Software Foundation (ASF) under one or more\n * contributor license agreements.  See the NOTICE file distributed with\n * this work for additional information regarding copyright ownership.\n * The ASF licenses this file to You under the Apache License, Version 2.0\n * (the \"License\"); you may not use this file except in compliance with\n * the License.  You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport java.io.Serializable;\nimport java.util.Locale;\n\n/**\n * Byte Order Mark (BOM) representation - see {@link BOMInputStream}.\n *\n * @see BOMInputStream\n * @see <a href=\"http://en.wikipedia.org/wiki/Byte_order_mark\">Wikipedia: Byte Order Mark</a>\n * @see <a href=\"http://www.w3.org/TR/2006/REC-xml-20060816/#sec-guessing\">W3C: Autodetection of Character Encodings\n * (Non-Normative)</a>\n * @since 2.0\n */\npublic class ByteOrderMark implements Serializable {\n\n    private static final long serialVersionUID = 1L;\n\n    /**\n     * UTF-8 BOM\n     */\n    public static final ByteOrderMark UTF_8 = new ByteOrderMark(\"UTF-8\", 0xEF, 0xBB, 0xBF);\n\n    /**\n     * UTF-16BE BOM (Big-Endian)\n     */\n    public static final ByteOrderMark UTF_16BE = new ByteOrderMark(\"UTF-16BE\", 0xFE, 0xFF);\n\n    /**\n     * UTF-16LE BOM (Little-Endian)\n     */\n    public static final ByteOrderMark UTF_16LE = new ByteOrderMark(\"UTF-16LE\", 0xFF, 0xFE);\n\n    /**\n     * UTF-32BE BOM (Big-Endian)\n     *\n     * @since 2.2\n     */\n    public static final ByteOrderMark UTF_32BE = new ByteOrderMark(\"UTF-32BE\", 0x00, 0x00, 0xFE, 0xFF);\n\n    /**\n     * UTF-32LE BOM (Little-Endian)\n     *\n     * @since 2.2\n     */\n    public static final ByteOrderMark UTF_32LE = new ByteOrderMark(\"UTF-32LE\", 0xFF, 0xFE, 0x00, 0x00);\n\n    /**\n     * Unicode BOM character; external form depends on the encoding.\n     *\n     * @see <a href=\"http://unicode.org/faq/utf_bom.html#BOM\">Byte Order Mark (BOM) FAQ</a>\n     * @since 2.5\n     */\n    @SuppressWarnings(\"unused\")\n    public static final char UTF_BOM = '\\uFEFF';\n\n    private final String charsetName;\n    private final int[] bytes;\n\n    /**\n     * Construct a new BOM.\n     *\n     * @param charsetName The name of the charset the BOM represents\n     * @param bytes       The BOM's bytes\n     * @throws IllegalArgumentException if the charsetName is null or\n     *                                  zero length\n     * @throws IllegalArgumentException if the bytes are null or zero\n     *                                  length\n     */\n    public ByteOrderMark(final String charsetName, final int... bytes) {\n        if (charsetName == null || charsetName.isEmpty()) {\n            throw new IllegalArgumentException(\"No charsetName specified\");\n        }\n        if (bytes == null || bytes.length == 0) {\n            throw new IllegalArgumentException(\"No bytes specified\");\n        }\n        this.charsetName = charsetName;\n        this.bytes = new int[bytes.length];\n        System.arraycopy(bytes, 0, this.bytes, 0, bytes.length);\n    }\n\n    /**\n     * Return the name of the {@link java.nio.charset.Charset} the BOM represents.\n     *\n     * @return the character set name\n     */\n    public String getCharsetName() {\n        return charsetName;\n    }\n\n    /**\n     * Return the length of the BOM's bytes.\n     *\n     * @return the length of the BOM's bytes\n     */\n    public int length() {\n        return bytes.length;\n    }\n\n    /**\n     * The byte at the specified position.\n     *\n     * @param pos The position\n     * @return The specified byte\n     */\n    public int get(final int pos) {\n        return bytes[pos];\n    }\n\n    /**\n     * Return a copy of the BOM's bytes.\n     *\n     * @return a copy of the BOM's bytes\n     */\n    public byte[] getBytes() {\n        final byte[] copy = new byte[bytes.length];\n        for (int i = 0; i < bytes.length; i++) {\n            copy[i] = (byte) bytes[i];\n        }\n        return copy;\n    }\n\n    /**\n     * Indicates if this BOM's bytes equals another.\n     *\n     * @param obj The object to compare to\n     * @return true if the bom's bytes are equal, otherwise\n     * false\n     */\n    @Override\n    public boolean equals(final Object obj) {\n        if (!(obj instanceof ByteOrderMark)) {\n            return false;\n        }\n        final ByteOrderMark bom = (ByteOrderMark) obj;\n        if (bytes.length != bom.length()) {\n            return false;\n        }\n        for (int i = 0; i < bytes.length; i++) {\n            if (bytes[i] != bom.get(i)) {\n                return false;\n            }\n        }\n        return true;\n    }\n\n    /**\n     * Return the hashcode for this BOM.\n     *\n     * @return the hashcode for this BOM.\n     * @see java.lang.Object#hashCode()\n     */\n    @Override\n    public int hashCode() {\n        int hashCode = getClass().hashCode();\n        for (final int b : bytes) {\n            hashCode += b;\n        }\n        return hashCode;\n    }\n\n    /**\n     * Provide a String representation of the BOM.\n     *\n     * @return the length of the BOM's bytes\n     */\n    @Override\n    @SuppressWarnings(\"NullableProblems\")\n    public String toString() {\n        final StringBuilder builder = new StringBuilder();\n        builder.append(getClass().getSimpleName());\n        builder.append('[');\n        builder.append(charsetName);\n        builder.append(\": \");\n        for (int i = 0; i < bytes.length; i++) {\n            if (i > 0) {\n                builder.append(\",\");\n            }\n            builder.append(\"0x\");\n            builder.append(Integer.toHexString(0xFF & bytes[i]).toUpperCase(Locale.ROOT));\n        }\n        builder.append(']');\n        return builder.toString();\n    }\n\n}"
  },
  {
    "path": "modules/book/src/main/java/me/ag2s/epublib/util/commons/io/IOConsumer.java",
    "content": "/*\n * Licensed to the Apache Software Foundation (ASF) under one or more\n * contributor license agreements.  See the NOTICE file distributed with\n * this work for additional information regarding copyright ownership.\n * The ASF licenses this file to You under the Apache License, Version 2.0\n * (the \"License\"); you may not use this file except in compliance with\n * the License.  You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\npackage me.ag2s.epublib.util.commons.io;\n\nimport java.io.IOException;\nimport java.util.Objects;\nimport java.util.function.Consumer;\n\n/**\n * Like {@link Consumer} but throws {@link IOException}.\n *\n * @param <T> the type of the input to the operations.\n * @since 2.7\n */\n@FunctionalInterface\npublic interface IOConsumer<T> {\n\n    /**\n     * Performs this operation on the given argument.\n     *\n     * @param t the input argument\n     * @throws IOException if an I/O error occurs.\n     */\n    void accept(T t) throws IOException;\n\n    /**\n     * Returns a composed {@code IoConsumer} that performs, in sequence, this operation followed by the {@code after}\n     * operation. If performing either operation throws an exception, it is relayed to the caller of the composed\n     * operation. If performing this operation throws an exception, the {@code after} operation will not be performed.\n     *\n     * @param after the operation to perform after this operation\n     * @return a composed {@code Consumer} that performs in sequence this operation followed by the {@code after}\n     * operation\n     * @throws NullPointerException if {@code after} is null\n     */\n    @SuppressWarnings(\"unused\")\n    default IOConsumer<T> andThen(final IOConsumer<? super T> after) {\n        Objects.requireNonNull(after);\n        return (final T t) -> {\n            accept(t);\n            after.accept(t);\n        };\n    }\n}\n"
  },
  {
    "path": "modules/book/src/main/java/me/ag2s/epublib/util/commons/io/ProxyInputStream.java",
    "content": "package me.ag2s.epublib.util.commons.io;\n\n/*\n * Licensed to the Apache Software Foundation (ASF) under one or more\n * contributor license agreements.  See the NOTICE file distributed with\n * this work for additional information regarding copyright ownership.\n * The ASF licenses this file to You under the Apache License, Version 2.0\n * (the \"License\"); you may not use this file except in compliance with\n * the License.  You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n\nimport static me.ag2s.epublib.util.IOUtil.EOF;\n\nimport java.io.FilterInputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\n\nimport me.ag2s.epublib.util.IOUtil;\n\n\n/**\n * A Proxy stream which acts as expected, that is it passes the method\n * calls on to the proxied stream and doesn't change which methods are\n * being called.\n * <p>\n * It is an alternative base class to FilterInputStream\n * to increase reusability, because FilterInputStream changes the\n * methods being called, such as read(byte[]) to read(byte[], int, int).\n * </p>\n * <p>\n * See the protected methods for ways in which a subclass can easily decorate\n * a stream with custom pre-, post- or error processing functionality.\n * </p>\n */\npublic abstract class ProxyInputStream extends FilterInputStream {\n\n    /**\n     * Constructs a new ProxyInputStream.\n     *\n     * @param proxy the InputStream to delegate to\n     */\n    public ProxyInputStream(final InputStream proxy) {\n        super(proxy);\n        // the proxy is stored in a protected superclass variable named 'in'\n    }\n\n    /**\n     * Invokes the delegate's <code>read()</code> method.\n     *\n     * @return the byte read or -1 if the end of stream\n     * @throws IOException if an I/O error occurs\n     */\n    @Override\n    public int read() throws IOException {\n        try {\n            beforeRead(1);\n            final int b = in.read();\n            afterRead(b != EOF ? 1 : EOF);\n            return b;\n        } catch (final IOException e) {\n            handleIOException(e);\n            return EOF;\n        }\n    }\n\n    /**\n     * Invokes the delegate's <code>read(byte[])</code> method.\n     *\n     * @param bts the buffer to read the bytes into\n     * @return the number of bytes read or EOF if the end of stream\n     * @throws IOException if an I/O error occurs\n     */\n    @Override\n    public int read(final byte[] bts) throws IOException {\n        try {\n            beforeRead(IOUtil.length(bts));\n            final int n = in.read(bts);\n            afterRead(n);\n            return n;\n        } catch (final IOException e) {\n            handleIOException(e);\n            return EOF;\n        }\n    }\n\n    /**\n     * Invokes the delegate's <code>read(byte[], int, int)</code> method.\n     *\n     * @param bts the buffer to read the bytes into\n     * @param off The start offset\n     * @param len The number of bytes to read\n     * @return the number of bytes read or -1 if the end of stream\n     * @throws IOException if an I/O error occurs\n     */\n    @Override\n    public int read(final byte[] bts, final int off, final int len) throws IOException {\n        try {\n            beforeRead(len);\n            final int n = in.read(bts, off, len);\n            afterRead(n);\n            return n;\n        } catch (final IOException e) {\n            handleIOException(e);\n            return EOF;\n        }\n    }\n\n    /**\n     * Invokes the delegate's <code>skip(long)</code> method.\n     *\n     * @param ln the number of bytes to skip\n     * @return the actual number of bytes skipped\n     * @throws IOException if an I/O error occurs\n     */\n    @Override\n    public long skip(final long ln) throws IOException {\n        try {\n            return in.skip(ln);\n        } catch (final IOException e) {\n            handleIOException(e);\n            return 0;\n        }\n    }\n\n    /**\n     * Invokes the delegate's <code>available()</code> method.\n     *\n     * @return the number of available bytes\n     * @throws IOException if an I/O error occurs\n     */\n    @Override\n    public int available() throws IOException {\n        try {\n            return super.available();\n        } catch (final IOException e) {\n            handleIOException(e);\n            return 0;\n        }\n    }\n\n    /**\n     * Invokes the delegate's <code>close()</code> method.\n     *\n     * @throws IOException if an I/O error occurs\n     */\n    @Override\n    public void close() throws IOException {\n        IOUtil.close(in, this::handleIOException);\n    }\n\n    /**\n     * Invokes the delegate's <code>mark(int)</code> method.\n     *\n     * @param readlimit read ahead limit\n     */\n    @Override\n    public synchronized void mark(final int readlimit) {\n        in.mark(readlimit);\n    }\n\n    /**\n     * Invokes the delegate's <code>reset()</code> method.\n     *\n     * @throws IOException if an I/O error occurs\n     */\n    @Override\n    public synchronized void reset() throws IOException {\n        try {\n            in.reset();\n        } catch (final IOException e) {\n            handleIOException(e);\n        }\n    }\n\n    /**\n     * Invokes the delegate's <code>markSupported()</code> method.\n     *\n     * @return true if mark is supported, otherwise false\n     */\n    @Override\n    public boolean markSupported() {\n        return in.markSupported();\n    }\n\n    /**\n     * Invoked by the read methods before the call is proxied. The number\n     * of bytes that the caller wanted to read (1 for the {@link #read()}\n     * method, buffer length for {@link #read(byte[])}, etc.) is given as\n     * an argument.\n     * <p>\n     * Subclasses can override this method to add common pre-processing\n     * functionality without having to override all the read methods.\n     * The default implementation does nothing.\n     * <p>\n     * Note this method is <em>not</em> called from {@link #skip(long)} or\n     * {@link #reset()}. You need to explicitly override those methods if\n     * you want to add pre-processing steps also to them.\n     *\n     * @param n number of bytes that the caller asked to be read\n     * @since 2.0\n     */\n    @SuppressWarnings(\"unused\")\n\n    protected void beforeRead(final int n) {\n        // no-op\n    }\n\n    /**\n     * Invoked by the read methods after the proxied call has returned\n     * successfully. The number of bytes returned to the caller (or -1 if\n     * the end of stream was reached) is given as an argument.\n     * <p>\n     * Subclasses can override this method to add common post-processing\n     * functionality without having to override all the read methods.\n     * The default implementation does nothing.\n     * <p>\n     * Note this method is <em>not</em> called from {@link #skip(long)} or\n     * {@link #reset()}. You need to explicitly override those methods if\n     * you want to add post-processing steps also to them.\n     *\n     * @param n number of bytes read, or -1 if the end of stream was reached\n     * @since 2.0\n     */\n    @SuppressWarnings(\"unused\")\n    protected void afterRead(final int n) {\n        // no-op\n    }\n\n    /**\n     * Handle any IOExceptions thrown.\n     * <p>\n     * This method provides a point to implement custom exception\n     * handling. The default behavior is to re-throw the exception.\n     *\n     * @param e The IOException thrown\n     * @throws IOException if an I/O error occurs\n     * @since 2.0\n     */\n    protected void handleIOException(final IOException e) throws IOException {\n        throw e;\n    }\n\n}\n"
  },
  {
    "path": "modules/book/src/main/java/me/ag2s/epublib/util/commons/io/XmlStreamReader.java",
    "content": "package me.ag2s.epublib.util.commons.io;\n\n/*\n * Licensed to the Apache Software Foundation (ASF) under one or more\n * contributor license agreements.  See the NOTICE file distributed with\n * this work for additional information regarding copyright ownership.\n * The ASF licenses this file to You under the Apache License, Version 2.0\n * (the \"License\"); you may not use this file except in compliance with\n * the License.  You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport java.io.BufferedInputStream;\nimport java.io.BufferedReader;\nimport java.io.File;\nimport java.io.FileInputStream;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.io.InputStreamReader;\nimport java.io.Reader;\nimport java.io.StringReader;\nimport java.net.HttpURLConnection;\nimport java.net.URL;\nimport java.net.URLConnection;\nimport java.nio.charset.Charset;\nimport java.text.MessageFormat;\nimport java.util.Locale;\nimport java.util.Objects;\nimport java.util.regex.Matcher;\nimport java.util.regex.Pattern;\n\nimport me.ag2s.epublib.util.IOUtil;\n\n\n/**\n * Character stream that handles all the necessary Voodoo to figure out the\n * charset encoding of the XML document within the stream.\n * <p>\n * IMPORTANT: This class is not related in any way to the org.xml.sax.XMLReader.\n * This one IS a character stream.\n * </p>\n * <p>\n * All this has to be done without consuming characters from the stream, if not\n * the XML parser will not recognized the document as a valid XML. This is not\n * 100% true, but it's close enough (UTF-8 BOM is not handled by all parsers\n * right now, XmlStreamReader handles it and things work in all parsers).\n * </p>\n * <p>\n * The XmlStreamReader class handles the charset encoding of XML documents in\n * Files, raw streams and HTTP streams by offering a wide set of constructors.\n * </p>\n * <p>\n * By default the charset encoding detection is lenient, the constructor with\n * the lenient flag can be used for a script (following HTTP MIME and XML\n * specifications). All this is nicely explained by Mark Pilgrim in his blog, <a\n * href=\"http://diveintomark.org/archives/2004/02/13/xml-media-types\">\n * Determining the character encoding of a feed</a>.\n * </p>\n * <p>\n * Originally developed for <a href=\"http://rome.dev.java.net\">ROME</a> under\n * Apache License 2.0.\n * </p>\n * <p>\n * //@seerr XmlStreamWriter\n *\n * @since 2.0\n */\npublic class XmlStreamReader extends Reader {\n    private static final int BUFFER_SIZE = IOUtil.DEFAULT_BUFFER_SIZE;\n\n    private static final String UTF_8 = \"UTF-8\";\n\n    private static final String US_ASCII = \"US-ASCII\";\n\n    private static final String UTF_16BE = \"UTF-16BE\";\n\n    private static final String UTF_16LE = \"UTF-16LE\";\n\n    private static final String UTF_32BE = \"UTF-32BE\";\n\n    private static final String UTF_32LE = \"UTF-32LE\";\n\n    private static final String UTF_16 = \"UTF-16\";\n\n    private static final String UTF_32 = \"UTF-32\";\n\n    private static final String EBCDIC = \"CP1047\";\n\n    private static final ByteOrderMark[] BOMS = new ByteOrderMark[]{\n            ByteOrderMark.UTF_8,\n            ByteOrderMark.UTF_16BE,\n            ByteOrderMark.UTF_16LE,\n            ByteOrderMark.UTF_32BE,\n            ByteOrderMark.UTF_32LE\n    };\n\n    // UTF_16LE and UTF_32LE have the same two starting BOM bytes.\n    private static final ByteOrderMark[] XML_GUESS_BYTES = new ByteOrderMark[]{\n            new ByteOrderMark(UTF_8, 0x3C, 0x3F, 0x78, 0x6D),\n            new ByteOrderMark(UTF_16BE, 0x00, 0x3C, 0x00, 0x3F),\n            new ByteOrderMark(UTF_16LE, 0x3C, 0x00, 0x3F, 0x00),\n            new ByteOrderMark(UTF_32BE, 0x00, 0x00, 0x00, 0x3C,\n                    0x00, 0x00, 0x00, 0x3F, 0x00, 0x00, 0x00, 0x78, 0x00, 0x00, 0x00, 0x6D),\n            new ByteOrderMark(UTF_32LE, 0x3C, 0x00, 0x00, 0x00,\n                    0x3F, 0x00, 0x00, 0x00, 0x78, 0x00, 0x00, 0x00, 0x6D, 0x00, 0x00, 0x00),\n            new ByteOrderMark(EBCDIC, 0x4C, 0x6F, 0xA7, 0x94)\n    };\n\n    private final Reader reader;\n\n    private final String encoding;\n\n    private final String defaultEncoding;\n\n    /**\n     * Returns the default encoding to use if none is set in HTTP content-type,\n     * XML prolog and the rules based on content-type are not adequate.\n     * <p>\n     * If it is NULL the content-type based rules are used.\n     *\n     * @return the default encoding to use.\n     */\n    public String getDefaultEncoding() {\n        return defaultEncoding;\n    }\n\n    /**\n     * Creates a Reader for a File.\n     * <p>\n     * It looks for the UTF-8 BOM first, if none sniffs the XML prolog charset,\n     * if this is also missing defaults to UTF-8.\n     * <p>\n     * It does a lenient charset encoding detection, check the constructor with\n     * the lenient parameter for details.\n     *\n     * @param file File to create a Reader from.\n     * @throws IOException thrown if there is a problem reading the file.\n     */\n    @SuppressWarnings(\"unused\")\n    public XmlStreamReader(final File file) throws IOException {\n        this(new FileInputStream(Objects.requireNonNull(file)));\n    }\n\n    /**\n     * Creates a Reader for a raw InputStream.\n     * <p>\n     * It follows the same logic used for files.\n     * <p>\n     * It does a lenient charset encoding detection, check the constructor with\n     * the lenient parameter for details.\n     *\n     * @param inputStream InputStream to create a Reader from.\n     * @throws IOException thrown if there is a problem reading the stream.\n     */\n    public XmlStreamReader(final InputStream inputStream) throws IOException {\n        this(inputStream, true);\n    }\n\n    /**\n     * Creates a Reader for a raw InputStream.\n     * <p>\n     * It follows the same logic used for files.\n     * <p>\n     * If lenient detection is indicated and the detection above fails as per\n     * specifications it then attempts the following:\n     * <p>\n     * If the content type was 'text/html' it replaces it with 'text/xml' and\n     * tries the detection again.\n     * <p>\n     * Else if the XML prolog had a charset encoding that encoding is used.\n     * <p>\n     * Else if the content type had a charset encoding that encoding is used.\n     * <p>\n     * Else 'UTF-8' is used.\n     * <p>\n     * If lenient detection is indicated an XmlStreamReaderException is never\n     * thrown.\n     *\n     * @param inputStream InputStream to create a Reader from.\n     * @param lenient     indicates if the charset encoding detection should be\n     *                    relaxed.\n     * @throws IOException              thrown if there is a problem reading the stream.\n     * @throws XmlStreamReaderException thrown if the charset encoding could not\n     *                                  be determined according to the specs.\n     */\n    public XmlStreamReader(final InputStream inputStream, final boolean lenient) throws IOException {\n        this(inputStream, lenient, null);\n    }\n\n    /**\n     * Creates a Reader for a raw InputStream.\n     * <p>\n     * It follows the same logic used for files.\n     * <p>\n     * If lenient detection is indicated and the detection above fails as per\n     * specifications it then attempts the following:\n     * <p>\n     * If the content type was 'text/html' it replaces it with 'text/xml' and\n     * tries the detection again.\n     * <p>\n     * Else if the XML prolog had a charset encoding that encoding is used.\n     * <p>\n     * Else if the content type had a charset encoding that encoding is used.\n     * <p>\n     * Else 'UTF-8' is used.\n     * <p>\n     * If lenient detection is indicated an XmlStreamReaderException is never\n     * thrown.\n     *\n     * @param inputStream     InputStream to create a Reader from.\n     * @param lenient         indicates if the charset encoding detection should be\n     *                        relaxed.\n     * @param defaultEncoding The default encoding\n     * @throws IOException              thrown if there is a problem reading the stream.\n     * @throws XmlStreamReaderException thrown if the charset encoding could not\n     *                                  be determined according to the specs.\n     */\n    public XmlStreamReader(final InputStream inputStream, final boolean lenient, final String defaultEncoding)\n            throws IOException {\n        Objects.requireNonNull(inputStream, \"inputStream\");\n        this.defaultEncoding = defaultEncoding;\n        final BOMInputStream bom = new BOMInputStream(new BufferedInputStream(inputStream, BUFFER_SIZE), false, BOMS);\n        final BOMInputStream pis = new BOMInputStream(bom, true, XML_GUESS_BYTES);\n        this.encoding = doRawStream(bom, pis, lenient);\n        this.reader = new InputStreamReader(pis, encoding);\n    }\n\n    /**\n     * Creates a Reader using the InputStream of a URL.\n     * <p>\n     * If the URL is not of type HTTP and there is not 'content-type' header in\n     * the fetched data it uses the same logic used for Files.\n     * <p>\n     * If the URL is a HTTP Url or there is a 'content-type' header in the\n     * fetched data it uses the same logic used for an InputStream with\n     * content-type.\n     * <p>\n     * It does a lenient charset encoding detection, check the constructor with\n     * the lenient parameter for details.\n     *\n     * @param url URL to create a Reader from.\n     * @throws IOException thrown if there is a problem reading the stream of\n     *                     the URL.\n     */\n    @SuppressWarnings(\"unused\")\n    public XmlStreamReader(final URL url) throws IOException {\n        this(Objects.requireNonNull(url, \"url\").openConnection(), null);\n    }\n\n    /**\n     * Creates a Reader using the InputStream of a URLConnection.\n     * <p>\n     * If the URLConnection is not of type HttpURLConnection and there is not\n     * 'content-type' header in the fetched data it uses the same logic used for\n     * files.\n     * <p>\n     * If the URLConnection is a HTTP Url or there is a 'content-type' header in\n     * the fetched data it uses the same logic used for an InputStream with\n     * content-type.\n     * <p>\n     * It does a lenient charset encoding detection, check the constructor with\n     * the lenient parameter for details.\n     *\n     * @param conn            URLConnection to create a Reader from.\n     * @param defaultEncoding The default encoding\n     * @throws IOException thrown if there is a problem reading the stream of\n     *                     the URLConnection.\n     */\n    public XmlStreamReader(final URLConnection conn, final String defaultEncoding) throws IOException {\n        Objects.requireNonNull(conn, \"conm\");\n        this.defaultEncoding = defaultEncoding;\n        final boolean lenient = true;\n        final String contentType = conn.getContentType();\n        final InputStream inputStream = conn.getInputStream();\n        final BOMInputStream bom = new BOMInputStream(new BufferedInputStream(inputStream, BUFFER_SIZE), false, BOMS);\n        final BOMInputStream pis = new BOMInputStream(bom, true, XML_GUESS_BYTES);\n        if (conn instanceof HttpURLConnection || contentType != null) {\n            this.encoding = processHttpStream(bom, pis, contentType, lenient);\n        } else {\n            this.encoding = doRawStream(bom, pis, lenient);\n        }\n        this.reader = new InputStreamReader(pis, encoding);\n    }\n\n    /**\n     * Creates a Reader using an InputStream and the associated content-type\n     * header.\n     * <p>\n     * First it checks if the stream has BOM. If there is not BOM checks the\n     * content-type encoding. If there is not content-type encoding checks the\n     * XML prolog encoding. If there is not XML prolog encoding uses the default\n     * encoding mandated by the content-type MIME type.\n     * <p>\n     * It does a lenient charset encoding detection, check the constructor with\n     * the lenient parameter for details.\n     *\n     * @param inputStream     InputStream to create the reader from.\n     * @param httpContentType content-type header to use for the resolution of\n     *                        the charset encoding.\n     * @throws IOException thrown if there is a problem reading the file.\n     */\n    public XmlStreamReader(final InputStream inputStream, final String httpContentType)\n            throws IOException {\n        this(inputStream, httpContentType, true);\n    }\n\n    /**\n     * Creates a Reader using an InputStream and the associated content-type\n     * header. This constructor is lenient regarding the encoding detection.\n     * <p>\n     * First it checks if the stream has BOM. If there is not BOM checks the\n     * content-type encoding. If there is not content-type encoding checks the\n     * XML prolog encoding. If there is not XML prolog encoding uses the default\n     * encoding mandated by the content-type MIME type.\n     * <p>\n     * If lenient detection is indicated and the detection above fails as per\n     * specifications it then attempts the following:\n     * <p>\n     * If the content type was 'text/html' it replaces it with 'text/xml' and\n     * tries the detection again.\n     * <p>\n     * Else if the XML prolog had a charset encoding that encoding is used.\n     * <p>\n     * Else if the content type had a charset encoding that encoding is used.\n     * <p>\n     * Else 'UTF-8' is used.\n     * <p>\n     * If lenient detection is indicated an XmlStreamReaderException is never\n     * thrown.\n     *\n     * @param inputStream     InputStream to create the reader from.\n     * @param httpContentType content-type header to use for the resolution of\n     *                        the charset encoding.\n     * @param lenient         indicates if the charset encoding detection should be\n     *                        relaxed.\n     * @param defaultEncoding The default encoding\n     * @throws IOException              thrown if there is a problem reading the file.\n     * @throws XmlStreamReaderException thrown if the charset encoding could not\n     *                                  be determined according to the specs.\n     */\n    public XmlStreamReader(final InputStream inputStream, final String httpContentType,\n                           final boolean lenient, final String defaultEncoding) throws IOException {\n        Objects.requireNonNull(inputStream, \"inputStream\");\n        this.defaultEncoding = defaultEncoding;\n        final BOMInputStream bom = new BOMInputStream(new BufferedInputStream(inputStream, BUFFER_SIZE), false, BOMS);\n        final BOMInputStream pis = new BOMInputStream(bom, true, XML_GUESS_BYTES);\n        this.encoding = processHttpStream(bom, pis, httpContentType, lenient);\n        this.reader = new InputStreamReader(pis, encoding);\n    }\n\n    /**\n     * Creates a Reader using an InputStream and the associated content-type\n     * header. This constructor is lenient regarding the encoding detection.\n     * <p>\n     * First it checks if the stream has BOM. If there is not BOM checks the\n     * content-type encoding. If there is not content-type encoding checks the\n     * XML prolog encoding. If there is not XML prolog encoding uses the default\n     * encoding mandated by the content-type MIME type.\n     * <p>\n     * If lenient detection is indicated and the detection above fails as per\n     * specifications it then attempts the following:\n     * <p>\n     * If the content type was 'text/html' it replaces it with 'text/xml' and\n     * tries the detection again.\n     * <p>\n     * Else if the XML prolog had a charset encoding that encoding is used.\n     * <p>\n     * Else if the content type had a charset encoding that encoding is used.\n     * <p>\n     * Else 'UTF-8' is used.\n     * <p>\n     * If lenient detection is indicated an XmlStreamReaderException is never\n     * thrown.\n     *\n     * @param inputStream     InputStream to create the reader from.\n     * @param httpContentType content-type header to use for the resolution of\n     *                        the charset encoding.\n     * @param lenient         indicates if the charset encoding detection should be\n     *                        relaxed.\n     * @throws IOException              thrown if there is a problem reading the file.\n     * @throws XmlStreamReaderException thrown if the charset encoding could not\n     *                                  be determined according to the specs.\n     */\n    public XmlStreamReader(final InputStream inputStream, final String httpContentType,\n                           final boolean lenient) throws IOException {\n        this(inputStream, httpContentType, lenient, null);\n    }\n\n    /**\n     * Returns the charset encoding of the XmlStreamReader.\n     *\n     * @return charset encoding.\n     */\n    public String getEncoding() {\n        return encoding;\n    }\n\n    /**\n     * Invokes the underlying reader's <code>read(char[], int, int)</code> method.\n     *\n     * @param buf    the buffer to read the characters into\n     * @param offset The start offset\n     * @param len    The number of bytes to read\n     * @return the number of characters read or -1 if the end of stream\n     * @throws IOException if an I/O error occurs\n     */\n    @Override\n    public int read(final char[] buf, final int offset, final int len) throws IOException {\n        return reader.read(buf, offset, len);\n    }\n\n    /**\n     * Closes the XmlStreamReader stream.\n     *\n     * @throws IOException thrown if there was a problem closing the stream.\n     */\n    @Override\n    public void close() throws IOException {\n        reader.close();\n    }\n\n    /**\n     * Process the raw stream.\n     *\n     * @param bom     BOMInputStream to detect byte order marks\n     * @param pis     BOMInputStream to guess XML encoding\n     * @param lenient indicates if the charset encoding detection should be\n     *                relaxed.\n     * @return the encoding to be used\n     * @throws IOException thrown if there is a problem reading the stream.\n     */\n    private String doRawStream(final BOMInputStream bom, final BOMInputStream pis, final boolean lenient)\n            throws IOException {\n        final String bomEnc = bom.getBOMCharsetName();\n        final String xmlGuessEnc = pis.getBOMCharsetName();\n        final String xmlEnc = getXmlProlog(pis, xmlGuessEnc);\n        try {\n            return calculateRawEncoding(bomEnc, xmlGuessEnc, xmlEnc);\n        } catch (final XmlStreamReaderException ex) {\n            if (lenient) {\n                return doLenientDetection(null, ex);\n            }\n            throw ex;\n        }\n    }\n\n    /**\n     * Process a HTTP stream.\n     *\n     * @param bom             BOMInputStream to detect byte order marks\n     * @param pis             BOMInputStream to guess XML encoding\n     * @param httpContentType The HTTP content type\n     * @param lenient         indicates if the charset encoding detection should be\n     *                        relaxed.\n     * @return the encoding to be used\n     * @throws IOException thrown if there is a problem reading the stream.\n     */\n    private String processHttpStream(final BOMInputStream bom, final BOMInputStream pis, final String httpContentType,\n                                     final boolean lenient) throws IOException {\n        final String bomEnc = bom.getBOMCharsetName();\n        final String xmlGuessEnc = pis.getBOMCharsetName();\n        final String xmlEnc = getXmlProlog(pis, xmlGuessEnc);\n        try {\n            return calculateHttpEncoding(httpContentType, bomEnc, xmlGuessEnc, xmlEnc, lenient);\n        } catch (final XmlStreamReaderException ex) {\n            if (lenient) {\n                return doLenientDetection(httpContentType, ex);\n            }\n            throw ex;\n        }\n    }\n\n    /**\n     * Do lenient detection.\n     *\n     * @param httpContentType content-type header to use for the resolution of\n     *                        the charset encoding.\n     * @param ex              The thrown exception\n     * @return the encoding\n     * @throws IOException thrown if there is a problem reading the stream.\n     */\n    private String doLenientDetection(String httpContentType,\n                                      XmlStreamReaderException ex) throws IOException {\n        if (httpContentType != null && httpContentType.startsWith(\"text/html\")) {\n            httpContentType = httpContentType.substring(\"text/html\".length());\n            httpContentType = \"text/xml\" + httpContentType;\n            try {\n                return calculateHttpEncoding(httpContentType, ex.getBomEncoding(),\n                        ex.getXmlGuessEncoding(), ex.getXmlEncoding(), true);\n            } catch (final XmlStreamReaderException ex2) {\n                ex = ex2;\n            }\n        }\n        String encoding = ex.getXmlEncoding();\n        if (encoding == null) {\n            encoding = ex.getContentTypeEncoding();\n        }\n        if (encoding == null) {\n            encoding = defaultEncoding == null ? UTF_8 : defaultEncoding;\n        }\n        return encoding;\n    }\n\n    /**\n     * Calculate the raw encoding.\n     *\n     * @param bomEnc      BOM encoding\n     * @param xmlGuessEnc XML Guess encoding\n     * @param xmlEnc      XML encoding\n     * @return the raw encoding\n     * @throws IOException thrown if there is a problem reading the stream.\n     */\n    String calculateRawEncoding(final String bomEnc, final String xmlGuessEnc,\n                                final String xmlEnc) throws IOException {\n\n        // BOM is Null\n        if (bomEnc == null) {\n            if (xmlGuessEnc == null || xmlEnc == null) {\n                return defaultEncoding == null ? UTF_8 : defaultEncoding;\n            }\n            if (xmlEnc.equals(UTF_16) &&\n                    (xmlGuessEnc.equals(UTF_16BE) || xmlGuessEnc.equals(UTF_16LE))) {\n                return xmlGuessEnc;\n            }\n            return xmlEnc;\n        }\n\n        // BOM is UTF-8\n        if (bomEnc.equals(UTF_8)) {\n            if (xmlGuessEnc != null && !xmlGuessEnc.equals(UTF_8)) {\n                final String msg = MessageFormat.format(RAW_EX_1, bomEnc, xmlGuessEnc, xmlEnc);\n                throw new XmlStreamReaderException(msg, bomEnc, xmlGuessEnc, xmlEnc);\n            }\n            if (xmlEnc != null && !xmlEnc.equals(UTF_8)) {\n                final String msg = MessageFormat.format(RAW_EX_1, bomEnc, xmlGuessEnc, xmlEnc);\n                throw new XmlStreamReaderException(msg, bomEnc, xmlGuessEnc, xmlEnc);\n            }\n            return bomEnc;\n        }\n\n        // BOM is UTF-16BE or UTF-16LE\n        if (bomEnc.equals(UTF_16BE) || bomEnc.equals(UTF_16LE)) {\n            if (xmlGuessEnc != null && !xmlGuessEnc.equals(bomEnc)) {\n                final String msg = MessageFormat.format(RAW_EX_1, bomEnc, xmlGuessEnc, xmlEnc);\n                throw new XmlStreamReaderException(msg, bomEnc, xmlGuessEnc, xmlEnc);\n            }\n            if (xmlEnc != null && !xmlEnc.equals(UTF_16) && !xmlEnc.equals(bomEnc)) {\n                final String msg = MessageFormat.format(RAW_EX_1, bomEnc, xmlGuessEnc, xmlEnc);\n                throw new XmlStreamReaderException(msg, bomEnc, xmlGuessEnc, xmlEnc);\n            }\n            return bomEnc;\n        }\n\n        // BOM is UTF-32BE or UTF-32LE\n        if (bomEnc.equals(UTF_32BE) || bomEnc.equals(UTF_32LE)) {\n            if (xmlGuessEnc != null && !xmlGuessEnc.equals(bomEnc)) {\n                final String msg = MessageFormat.format(RAW_EX_1, bomEnc, xmlGuessEnc, xmlEnc);\n                throw new XmlStreamReaderException(msg, bomEnc, xmlGuessEnc, xmlEnc);\n            }\n            if (xmlEnc != null && !xmlEnc.equals(UTF_32) && !xmlEnc.equals(bomEnc)) {\n                final String msg = MessageFormat.format(RAW_EX_1, bomEnc, xmlGuessEnc, xmlEnc);\n                throw new XmlStreamReaderException(msg, bomEnc, xmlGuessEnc, xmlEnc);\n            }\n            return bomEnc;\n        }\n\n        // BOM is something else\n        final String msg = MessageFormat.format(RAW_EX_2, bomEnc, xmlGuessEnc, xmlEnc);\n        throw new XmlStreamReaderException(msg, bomEnc, xmlGuessEnc, xmlEnc);\n    }\n\n\n    /**\n     * Calculate the HTTP encoding.\n     *\n     * @param httpContentType The HTTP content type\n     * @param bomEnc          BOM encoding\n     * @param xmlGuessEnc     XML Guess encoding\n     * @param xmlEnc          XML encoding\n     * @param lenient         indicates if the charset encoding detection should be\n     *                        relaxed.\n     * @return the HTTP encoding\n     * @throws IOException thrown if there is a problem reading the stream.\n     */\n    String calculateHttpEncoding(final String httpContentType,\n                                 final String bomEnc, final String xmlGuessEnc, final String xmlEnc,\n                                 final boolean lenient) throws IOException {\n\n        // Lenient and has XML encoding\n        if (lenient && xmlEnc != null) {\n            return xmlEnc;\n        }\n\n        // Determine mime/encoding content types from HTTP Content Type\n        final String cTMime = getContentTypeMime(httpContentType);\n        final String cTEnc = getContentTypeEncoding(httpContentType);\n        final boolean appXml = isAppXml(cTMime);\n        final boolean textXml = isTextXml(cTMime);\n\n        // Mime type NOT \"application/xml\" or \"text/xml\"\n        if (!appXml && !textXml) {\n            final String msg = MessageFormat.format(HTTP_EX_3, cTMime, cTEnc, bomEnc, xmlGuessEnc, xmlEnc);\n            throw new XmlStreamReaderException(msg, cTMime, cTEnc, bomEnc, xmlGuessEnc, xmlEnc);\n        }\n\n        // No content type encoding\n        if (cTEnc == null) {\n            if (appXml) {\n                return calculateRawEncoding(bomEnc, xmlGuessEnc, xmlEnc);\n            }\n            return defaultEncoding == null ? US_ASCII : defaultEncoding;\n        }\n\n        // UTF-16BE or UTF-16LE content type encoding\n        if (cTEnc.equals(UTF_16BE) || cTEnc.equals(UTF_16LE)) {\n            if (bomEnc != null) {\n                final String msg = MessageFormat.format(HTTP_EX_1, cTMime, cTEnc, bomEnc, xmlGuessEnc, xmlEnc);\n                throw new XmlStreamReaderException(msg, cTMime, cTEnc, bomEnc, xmlGuessEnc, xmlEnc);\n            }\n            return cTEnc;\n        }\n\n        // UTF-16 content type encoding\n        if (cTEnc.equals(UTF_16)) {\n            if (bomEnc != null && bomEnc.startsWith(UTF_16)) {\n                return bomEnc;\n            }\n            final String msg = MessageFormat.format(HTTP_EX_2, cTMime, cTEnc, bomEnc, xmlGuessEnc, xmlEnc);\n            throw new XmlStreamReaderException(msg, cTMime, cTEnc, bomEnc, xmlGuessEnc, xmlEnc);\n        }\n\n        // UTF-32BE or UTF-132E content type encoding\n        if (cTEnc.equals(UTF_32BE) || cTEnc.equals(UTF_32LE)) {\n            if (bomEnc != null) {\n                final String msg = MessageFormat.format(HTTP_EX_1, cTMime, cTEnc, bomEnc, xmlGuessEnc, xmlEnc);\n                throw new XmlStreamReaderException(msg, cTMime, cTEnc, bomEnc, xmlGuessEnc, xmlEnc);\n            }\n            return cTEnc;\n        }\n\n        // UTF-32 content type encoding\n        if (cTEnc.equals(UTF_32)) {\n            if (bomEnc != null && bomEnc.startsWith(UTF_32)) {\n                return bomEnc;\n            }\n            final String msg = MessageFormat.format(HTTP_EX_2, cTMime, cTEnc, bomEnc, xmlGuessEnc, xmlEnc);\n            throw new XmlStreamReaderException(msg, cTMime, cTEnc, bomEnc, xmlGuessEnc, xmlEnc);\n        }\n\n        return cTEnc;\n    }\n\n    /**\n     * Returns MIME type or NULL if httpContentType is NULL.\n     *\n     * @param httpContentType the HTTP content type\n     * @return The mime content type\n     */\n    static String getContentTypeMime(final String httpContentType) {\n        String mime = null;\n        if (httpContentType != null) {\n            final int i = httpContentType.indexOf(\";\");\n            if (i >= 0) {\n                mime = httpContentType.substring(0, i);\n            } else {\n                mime = httpContentType;\n            }\n            mime = mime.trim();\n        }\n        return mime;\n    }\n\n    private static final Pattern CHARSET_PATTERN = Pattern\n            .compile(\"charset=[\\\"']?([.[^; \\\"']]*)[\\\"']?\");\n\n    /**\n     * Returns charset parameter value, NULL if not present, NULL if\n     * httpContentType is NULL.\n     *\n     * @param httpContentType the HTTP content type\n     * @return The content type encoding (upcased)\n     */\n    static String getContentTypeEncoding(final String httpContentType) {\n        String encoding = null;\n        if (httpContentType != null) {\n            final int i = httpContentType.indexOf(\";\");\n            if (i > -1) {\n                final String postMime = httpContentType.substring(i + 1);\n                final Matcher m = CHARSET_PATTERN.matcher(postMime);\n                encoding = m.find() ? m.group(1) : null;\n                encoding = encoding != null ? encoding.toUpperCase(Locale.ROOT) : null;\n            }\n        }\n        return encoding;\n    }\n\n    /**\n     * Pattern capturing the encoding of the \"xml\" processing instruction.\n     */\n    public static final Pattern ENCODING_PATTERN = Pattern.compile(\n            \"<\\\\?xml.*encoding[\\\\s]*=[\\\\s]*((?:\\\".[^\\\"]*\\\")|(?:'.[^']*'))\",\n            Pattern.MULTILINE);\n\n    /**\n     * Returns the encoding declared in the <?xml encoding=...?>, NULL if none.\n     *\n     * @param inputStream InputStream to create the reader from.\n     * @param guessedEnc  guessed encoding\n     * @return the encoding declared in the <?xml encoding=...?>\n     * @throws IOException thrown if there is a problem reading the stream.\n     */\n    private static String getXmlProlog(final InputStream inputStream, final String guessedEnc)\n            throws IOException {\n        String encoding = null;\n        if (guessedEnc != null) {\n            final byte[] bytes = new byte[BUFFER_SIZE];\n            inputStream.mark(BUFFER_SIZE);\n            int offset = 0;\n            int max = BUFFER_SIZE;\n            int c = inputStream.read(bytes, offset, max);\n            int firstGT = -1;\n            String xmlProlog = \"\"; // avoid possible NPE warning (cannot happen; this just silences the warning)\n            while (c != -1 && firstGT == -1 && offset < BUFFER_SIZE) {\n                offset += c;\n                max -= c;\n                c = inputStream.read(bytes, offset, max);\n                xmlProlog = new String(bytes, 0, offset, guessedEnc);\n                firstGT = xmlProlog.indexOf('>');\n            }\n            if (firstGT == -1) {\n                if (c == -1) {\n                    throw new IOException(\"Unexpected end of XML stream\");\n                }\n                throw new IOException(\n                        \"XML prolog or ROOT element not found on first \"\n                                + offset + \" bytes\");\n            }\n            final int bytesRead = offset;\n            if (bytesRead > 0) {\n                inputStream.reset();\n                final BufferedReader bReader = new BufferedReader(new StringReader(\n                        xmlProlog.substring(0, firstGT + 1)));\n                final StringBuffer prolog = new StringBuffer();\n                String line;\n                while ((line = bReader.readLine()) != null) {\n                    prolog.append(line);\n                }\n                final Matcher m = ENCODING_PATTERN.matcher(prolog);\n                if (m.find()) {\n                    encoding = Objects.requireNonNull(m.group(1)).toUpperCase(Locale.ROOT);\n                    encoding = encoding.substring(1, encoding.length() - 1);\n                }\n            }\n        }\n        boolean isSupportedEncoding;\n        try {\n            isSupportedEncoding = Charset.isSupported(encoding);\n        } catch (Exception e) {\n            return null;\n        }\n        if (isSupportedEncoding) {\n            return encoding;\n        } else {\n            return null;\n        }\n    }\n\n    /**\n     * Indicates if the MIME type belongs to the APPLICATION XML family.\n     *\n     * @param mime The mime type\n     * @return true if the mime type belongs to the APPLICATION XML family,\n     * otherwise false\n     */\n    static boolean isAppXml(final String mime) {\n        return mime != null &&\n                (mime.equals(\"application/xml\") ||\n                        mime.equals(\"application/xml-dtd\") ||\n                        mime.equals(\"application/xml-external-parsed-entity\") ||\n                        mime.startsWith(\"application/\") && mime.endsWith(\"+xml\"));\n    }\n\n    /**\n     * Indicates if the MIME type belongs to the TEXT XML family.\n     *\n     * @param mime The mime type\n     * @return true if the mime type belongs to the TEXT XML family,\n     * otherwise false\n     */\n    static boolean isTextXml(final String mime) {\n        return mime != null &&\n                (mime.equals(\"text/xml\") ||\n                        mime.equals(\"text/xml-external-parsed-entity\") ||\n                        mime.startsWith(\"text/\") && mime.endsWith(\"+xml\"));\n    }\n\n    private static final String RAW_EX_1 =\n            \"Invalid encoding, BOM [{0}] XML guess [{1}] XML prolog [{2}] encoding mismatch\";\n\n    private static final String RAW_EX_2 =\n            \"Invalid encoding, BOM [{0}] XML guess [{1}] XML prolog [{2}] unknown BOM\";\n\n    private static final String HTTP_EX_1 =\n            \"Invalid encoding, CT-MIME [{0}] CT-Enc [{1}] BOM [{2}] XML guess [{3}] XML prolog [{4}], BOM must be NULL\";\n\n    private static final String HTTP_EX_2 =\n            \"Invalid encoding, CT-MIME [{0}] CT-Enc [{1}] BOM [{2}] XML guess [{3}] XML prolog [{4}], encoding mismatch\";\n\n    private static final String HTTP_EX_3 =\n            \"Invalid encoding, CT-MIME [{0}] CT-Enc [{1}] BOM [{2}] XML guess [{3}] XML prolog [{4}], Invalid MIME\";\n\n}"
  },
  {
    "path": "modules/book/src/main/java/me/ag2s/epublib/util/commons/io/XmlStreamReaderException.java",
    "content": "package me.ag2s.epublib.util.commons.io;\n\n/*\n * Licensed to the Apache Software Foundation (ASF) under one or more\n * contributor license agreements.  See the NOTICE file distributed with\n * this work for additional information regarding copyright ownership.\n * The ASF licenses this file to You under the Apache License, Version 2.0\n * (the \"License\"); you may not use this file except in compliance with\n * the License.  You may obtain a copy of the License at\n *\n *      http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\nimport java.io.IOException;\n\n/**\n * The XmlStreamReaderException is thrown by the XmlStreamReader constructors if\n * the charset encoding can not be determined according to the XML 1.0\n * specification and RFC 3023.\n * <p>\n * The exception returns the unconsumed InputStream to allow the application to\n * do an alternate processing with the stream. Note that the original\n * InputStream given to the XmlStreamReader cannot be used as that one has been\n * already read.\n * </p>\n *\n * @since 2.0\n */\npublic class XmlStreamReaderException extends IOException {\n\n    private static final long serialVersionUID = 1L;\n\n    private final String bomEncoding;\n\n    private final String xmlGuessEncoding;\n\n    private final String xmlEncoding;\n\n    private final String contentTypeMime;\n\n    private final String contentTypeEncoding;\n\n    /**\n     * Creates an exception instance if the charset encoding could not be\n     * determined.\n     * <p>\n     * Instances of this exception are thrown by the XmlStreamReader.\n     * </p>\n     *\n     * @param msg         message describing the reason for the exception.\n     * @param bomEnc      BOM encoding.\n     * @param xmlGuessEnc XML guess encoding.\n     * @param xmlEnc      XML prolog encoding.\n     */\n    public XmlStreamReaderException(final String msg, final String bomEnc,\n                                    final String xmlGuessEnc, final String xmlEnc) {\n        this(msg, null, null, bomEnc, xmlGuessEnc, xmlEnc);\n    }\n\n    /**\n     * Creates an exception instance if the charset encoding could not be\n     * determined.\n     * <p>\n     * Instances of this exception are thrown by the XmlStreamReader.\n     * </p>\n     *\n     * @param msg         message describing the reason for the exception.\n     * @param ctMime      MIME type in the content-type.\n     * @param ctEnc       encoding in the content-type.\n     * @param bomEnc      BOM encoding.\n     * @param xmlGuessEnc XML guess encoding.\n     * @param xmlEnc      XML prolog encoding.\n     */\n    public XmlStreamReaderException(final String msg, final String ctMime, final String ctEnc,\n                                    final String bomEnc, final String xmlGuessEnc, final String xmlEnc) {\n        super(msg);\n        contentTypeMime = ctMime;\n        contentTypeEncoding = ctEnc;\n        bomEncoding = bomEnc;\n        xmlGuessEncoding = xmlGuessEnc;\n        xmlEncoding = xmlEnc;\n    }\n\n    /**\n     * Returns the BOM encoding found in the InputStream.\n     *\n     * @return the BOM encoding, null if none.\n     */\n    public String getBomEncoding() {\n        return bomEncoding;\n    }\n\n    /**\n     * Returns the encoding guess based on the first bytes of the InputStream.\n     *\n     * @return the encoding guess, null if it couldn't be guessed.\n     */\n    public String getXmlGuessEncoding() {\n        return xmlGuessEncoding;\n    }\n\n    /**\n     * Returns the encoding found in the XML prolog of the InputStream.\n     *\n     * @return the encoding of the XML prolog, null if none.\n     */\n    public String getXmlEncoding() {\n        return xmlEncoding;\n    }\n\n    /**\n     * Returns the MIME type in the content-type used to attempt determining the\n     * encoding.\n     *\n     * @return the MIME type in the content-type, null if there was not\n     * content-type or the encoding detection did not involve HTTP.\n     */\n    public String getContentTypeMime() {\n        return contentTypeMime;\n    }\n\n    /**\n     * Returns the encoding in the content-type used to attempt determining the\n     * encoding.\n     *\n     * @return the encoding in the content-type, null if there was not\n     * content-type, no encoding in it or the encoding detection did not\n     * involve HTTP.\n     */\n    public String getContentTypeEncoding() {\n        return contentTypeEncoding;\n    }\n}\n"
  },
  {
    "path": "modules/book/src/main/java/me/ag2s/epublib/util/zip/AndroidZipEntry.java",
    "content": "\n\n\npackage me.ag2s.epublib.util.zip;\n\nimport java.util.Calendar;\nimport java.util.Date;\nimport java.util.zip.ZipOutputStream;\n\n/**\n * This class represents a member of a zip archive.  ZipFile and\n * ZipInputStream will give you instances of this class as information\n * about the members in an archive.  On the other hand ZipOutputStream\n * needs an instance of this class to create a new member.\n *\n * @author Jochen Hoenicke\n */\npublic class AndroidZipEntry implements ZipConstants, Cloneable {\n    private static final int KNOWN_SIZE = 1;\n    private static final int KNOWN_CSIZE = 2;\n    private static final int KNOWN_CRC = 4;\n    private static final int KNOWN_TIME = 8;\n\n    private static Calendar cal;\n\n    private final String name;\n    private final int nameLen;\n    private int size;\n    private int compressedSize;\n    private int crc;\n    private int dostime;\n    private short known = 0;\n    private short method = -1;\n    private byte[] extra = null;\n    private String comment = null;\n\n    int flags;              /* used by ZipOutputStream */\n    int offset;             /* used by ZipFile and ZipOutputStream */\n\n\n    /**\n     * Compression method.  This method doesn't compress at all.\n     */\n    public final static int STORED = 0;\n    /**\n     * Compression method.  This method uses the Deflater.\n     */\n    public final static int DEFLATED = 8;\n\n    /**\n     * Creates a zip entry with the given name.\n     *\n     * @param name the name. May include directory components separated\n     *             by '/'.\n     * @throws NullPointerException     when name is null.\n     * @throws IllegalArgumentException when name is bigger then 65535 chars.\n     */\n    public AndroidZipEntry(String name, int nameLen) {\n        //int length = name.length();\n        this.nameLen = nameLen;\n        if (nameLen > 65535)\n            throw new IllegalArgumentException(\"name length is \" + nameLen);\n        this.name = name;\n    }\n\n\n    /**\n     * Creates a copy of the given zip entry.\n     *\n     * @param e the entry to copy.\n     */\n    public AndroidZipEntry(AndroidZipEntry e) {\n        name = e.name;\n        nameLen = e.nameLen;\n        known = e.known;\n        size = e.size;\n        compressedSize = e.compressedSize;\n        crc = e.crc;\n        dostime = e.dostime;\n        method = e.method;\n        extra = e.extra;\n        comment = e.comment;\n    }\n\n    final void setDOSTime(int dostime) {\n        this.dostime = dostime;\n        known |= KNOWN_TIME;\n    }\n\n    final int getDOSTime() {\n        if ((known & KNOWN_TIME) == 0)\n            return 0;\n        else\n            return dostime;\n    }\n\n    /**\n     * Creates a copy of this zip entry.\n     */\n    /**\n     * Clones the entry.\n     */\n    public Object clone() {\n        try {\n            // The JCL says that the `extra' field is also copied.\n            AndroidZipEntry clone = (AndroidZipEntry) super.clone();\n            if (extra != null)\n                clone.extra = extra.clone();\n            return clone;\n        } catch (CloneNotSupportedException ex) {\n            throw new InternalError();\n        }\n    }\n\n    /**\n     * Returns the entry name.  The path components in the entry are\n     * always separated by slashes ('/').\n     */\n    public String getName() {\n        return name;\n    }\n\n    public int getNameLen() {\n        return nameLen;\n    }\n\n    /**\n     * Sets the time of last modification of the entry.\n     *\n     * @time the time of last modification of the entry.\n     */\n    public void setTime(long time) {\n        Calendar cal = getCalendar();\n        synchronized (cal) {\n            cal.setTime(new Date(time * 1000L));\n            dostime = (cal.get(Calendar.YEAR) - 1980 & 0x7f) << 25\n                    | (cal.get(Calendar.MONTH) + 1) << 21\n                    | (cal.get(Calendar.DAY_OF_MONTH)) << 16\n                    | (cal.get(Calendar.HOUR_OF_DAY)) << 11\n                    | (cal.get(Calendar.MINUTE)) << 5\n                    | (cal.get(Calendar.SECOND)) >> 1;\n        }\n        dostime = (int) (dostime / 1000L);\n        this.known |= KNOWN_TIME;\n    }\n\n    /**\n     * Gets the time of last modification of the entry.\n     *\n     * @return the time of last modification of the entry, or -1 if unknown.\n     */\n    public long getTime() {\n        if ((known & KNOWN_TIME) == 0)\n            return -1;\n\n        int sec = 2 * (dostime & 0x1f);\n        int min = (dostime >> 5) & 0x3f;\n        int hrs = (dostime >> 11) & 0x1f;\n        int day = (dostime >> 16) & 0x1f;\n        int mon = ((dostime >> 21) & 0xf) - 1;\n        int year = ((dostime >> 25) & 0x7f) + 1980; /* since 1900 */\n\n        try {\n            cal = getCalendar();\n            synchronized (cal) {\n                cal.set(year, mon, day, hrs, min, sec);\n                return cal.getTime().getTime();\n            }\n        } catch (RuntimeException ex) {\n            /* Ignore illegal time stamp */\n            known &= ~KNOWN_TIME;\n            return -1;\n        }\n    }\n\n    private static synchronized Calendar getCalendar() {\n        if (cal == null)\n            cal = Calendar.getInstance();\n\n        return cal;\n    }\n\n    /**\n     * Sets the size of the uncompressed data.\n     *\n     * @throws IllegalArgumentException if size is not in 0..0xffffffffL\n     */\n    public void setSize(long size) {\n        if ((size & 0xffffffff00000000L) != 0)\n            throw new IllegalArgumentException();\n        this.size = (int) size;\n        this.known |= KNOWN_SIZE;\n    }\n\n    /**\n     * Gets the size of the uncompressed data.\n     *\n     * @return the size or -1 if unknown.\n     */\n    public long getSize() {\n        return (known & KNOWN_SIZE) != 0 ? size & 0xffffffffL : -1L;\n    }\n\n    /**\n     * Sets the size of the compressed data.\n     *\n     * @throws IllegalArgumentException if size is not in 0..0xffffffffL\n     */\n    public void setCompressedSize(long csize) {\n        if ((csize & 0xffffffff00000000L) != 0)\n            throw new IllegalArgumentException();\n        this.compressedSize = (int) csize;\n        this.known |= KNOWN_CSIZE;\n    }\n\n    /**\n     * Gets the size of the compressed data.\n     *\n     * @return the size or -1 if unknown.\n     */\n    public long getCompressedSize() {\n        return (known & KNOWN_CSIZE) != 0 ? compressedSize & 0xffffffffL : -1L;\n    }\n\n    /**\n     * Sets the crc of the uncompressed data.\n     *\n     * @throws IllegalArgumentException if crc is not in 0..0xffffffffL\n     */\n    public void setCrc(long crc) {\n        if ((crc & 0xffffffff00000000L) != 0)\n            throw new IllegalArgumentException();\n        this.crc = (int) crc;\n        this.known |= KNOWN_CRC;\n    }\n\n    /**\n     * Gets the crc of the uncompressed data.\n     *\n     * @return the crc or -1 if unknown.\n     */\n    public long getCrc() {\n        return (known & KNOWN_CRC) != 0 ? crc & 0xffffffffL : -1L;\n    }\n\n    /**\n     * Sets the compression method.  Only DEFLATED and STORED are\n     * supported.\n     *\n     * @throws IllegalArgumentException if method is not supported.\n     * @see ZipOutputStream#DEFLATED\n     * @see ZipOutputStream#STORED\n     */\n    public void setMethod(int method) {\n        if (method != ZipOutputStream.STORED\n                && method != ZipOutputStream.DEFLATED)\n            throw new IllegalArgumentException();\n        this.method = (short) method;\n    }\n\n    /**\n     * Gets the compression method.\n     *\n     * @return the compression method or -1 if unknown.\n     */\n    public int getMethod() {\n        return method;\n    }\n\n    /**\n     * Sets the extra data.\n     *\n     * @throws IllegalArgumentException if extra is longer than 0xffff bytes.\n     */\n    public void setExtra(byte[] extra) {\n        if (extra == null) {\n            this.extra = null;\n            return;\n        }\n\n        if (extra.length > 0xffff)\n            throw new IllegalArgumentException();\n        this.extra = extra;\n        try {\n            int pos = 0;\n            while (pos < extra.length) {\n                int sig = (extra[pos++] & 0xff)\n                        | (extra[pos++] & 0xff) << 8;\n                int len = (extra[pos++] & 0xff)\n                        | (extra[pos++] & 0xff) << 8;\n                if (sig == 0x5455) {\n                    /* extended time stamp */\n                    int flags = extra[pos];\n                    if ((flags & 1) != 0) {\n                        long time = ((extra[pos + 1] & 0xff)\n                                | (extra[pos + 2] & 0xff) << 8\n                                | (extra[pos + 3] & 0xff) << 16\n                                | (extra[pos + 4] & 0xff) << 24);\n                        setTime(time);\n                    }\n                }\n                pos += len;\n            }\n        } catch (ArrayIndexOutOfBoundsException ex) {\n            /* be lenient */\n            return;\n        }\n    }\n\n    /**\n     * Gets the extra data.\n     *\n     * @return the extra data or null if not set.\n     */\n    public byte[] getExtra() {\n        return extra;\n    }\n\n    /**\n     * Sets the entry comment.\n     *\n     * @throws IllegalArgumentException if comment is longer than 0xffff.\n     */\n    public void setComment(String comment) {\n        if (comment != null && comment.length() > 0xffff)\n            throw new IllegalArgumentException();\n        this.comment = comment;\n    }\n\n    /**\n     * Gets the comment.\n     *\n     * @return the comment or null if not set.\n     */\n    public String getComment() {\n        return comment;\n    }\n\n    /**\n     * Gets true, if the entry is a directory.  This is solely\n     * determined by the name, a trailing slash '/' marks a directory.\n     */\n    public boolean isDirectory() {\n        int nlen = name.length();\n        return nlen > 0 && name.charAt(nlen - 1) == '/';\n    }\n\n    /**\n     * Gets the string representation of this AndroidZipEntry.  This is just\n     * the name as returned by getName().\n     */\n    public String toString() {\n        return name;\n    }\n\n    /**\n     * Gets the hashCode of this AndroidZipEntry.  This is just the hashCode\n     * of the name.  Note that the equals method isn't changed, though.\n     */\n    public int hashCode() {\n        return name.hashCode();\n    }\n}\n"
  },
  {
    "path": "modules/book/src/main/java/me/ag2s/epublib/util/zip/AndroidZipFile.java",
    "content": "\n\npackage me.ag2s.epublib.util.zip;\n\nimport static me.ag2s.base.PfdHelper.seek;\n\nimport android.os.ParcelFileDescriptor;\n\nimport androidx.annotation.NonNull;\n\nimport java.io.BufferedInputStream;\nimport java.io.DataInput;\nimport java.io.EOFException;\nimport java.io.File;\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.util.Enumeration;\nimport java.util.HashMap;\nimport java.util.Iterator;\nimport java.util.NoSuchElementException;\nimport java.util.zip.Inflater;\nimport java.util.zip.InflaterInputStream;\nimport java.util.zip.ZipOutputStream;\n\nimport me.ag2s.base.PfdHelper;\n\n/**\n * This class represents a Zip archive.  You can ask for the contained\n * entries, or get an input stream for a file entry.  The entry is\n * automatically decompressed.\n * <p>\n * This class is thread safe:  You can open input streams for arbitrary\n * entries in different threads.\n *\n * @author Jochen Hoenicke\n * @author Artur Biesiadowski\n */\npublic class AndroidZipFile implements ZipConstants {\n\n    /**\n     * Mode flag to open a zip file for reading.\n     */\n    public static final int OPEN_READ = 0x1;\n\n    /**\n     * Mode flag to delete a zip file after reading.\n     */\n    public static final int OPEN_DELETE = 0x4;\n\n    // Name of this zip file.\n    private final String name;\n\n    // File from which zip entries are read.\n    //private final RandomAccessFile raf;\n\n    private final ParcelFileDescriptor pfd;\n\n    // The entries of this zip file when initialized and not yet closed.\n    private HashMap<String, AndroidZipEntry> entries;\n\n    private boolean closed = false;\n\n    /**\n     * Opens a Zip file with the given name for reading.\n     *\n     * @throws IOException  if a i/o error occured.\n     * @throws ZipException if the file doesn't contain a valid zip\n     *                      archive.\n     */\n    public AndroidZipFile(@NonNull ParcelFileDescriptor pfd, String name) throws ZipException, IOException {\n        this.pfd = pfd;\n        this.name = name;\n    }\n\n    /**\n     * Opens a Zip file reading the given File.\n     *\n     * @throws IOException  if a i/o error occured.\n     * @throws ZipException if the file doesn't contain a valid zip\n     *                      archive.\n     */\n    public AndroidZipFile(File file) throws ZipException, IOException {\n        this.pfd = ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY);\n        this.name = file.getPath();\n    }\n\n    /**\n     * Opens a Zip file reading the given File in the given mode.\n     * <p>\n     * If the OPEN_DELETE mode is specified, the zip file will be deleted at\n     * some time moment after it is opened. It will be deleted before the zip\n     * file is closed or the Virtual Machine exits.\n     * <p>\n     * The contents of the zip file will be accessible until it is closed.\n     * <p>\n     * The OPEN_DELETE mode is currently unimplemented in this library\n     *\n     * @param mode Must be one of OPEN_READ or OPEN_READ | OPEN_DELETE\n     * @throws IOException  if a i/o error occured.\n     * @throws ZipException if the file doesn't contain a valid zip\n     *                      archive.\n     * @since JDK1.3\n     */\n//    public AndroidZipFile(File file, int mode) throws ZipException, IOException {\n//        if ((mode & OPEN_DELETE) != 0) {\n//            throw new IllegalArgumentException\n//                    (\"OPEN_DELETE mode not supported yet in net.sf.jazzlib.AndroidZipFile\");\n//        }\n//        this.raf = new RandomAccessFile(file, \"r\");\n//        this.name = file.getPath();\n//    }\n\n    /**\n     * Read an unsigned short in little endian byte order from the given\n     * DataInput stream using the given byte buffer.\n     *\n     * @param di DataInput stream to read from.\n     * @param b  the byte buffer to read in (must be at least 2 bytes long).\n     * @return The value read.\n     * @throws IOException  if a i/o error occured.\n     * @throws EOFException if the file ends prematurely\n     */\n    private final int readLeShort(DataInput di, byte[] b) throws IOException {\n        di.readFully(b, 0, 2);\n        return (b[0] & 0xff) | (b[1] & 0xff) << 8;\n    }\n\n    private final int readLeShort(ParcelFileDescriptor pfd, byte[] b) throws IOException {\n        PfdHelper.readFully(pfd, b, 0, 2);//di.readFully(b, 0, 2);\n        return (b[0] & 0xff) | (b[1] & 0xff) << 8;\n    }\n\n    /**\n     * Read an int in little endian byte order from the given\n     * DataInput stream using the given byte buffer.\n     *\n     * @param di DataInput stream to read from.\n     * @param b  the byte buffer to read in (must be at least 4 bytes long).\n     * @return The value read.\n     * @throws IOException  if a i/o error occured.\n     * @throws EOFException if the file ends prematurely\n     */\n    private final int readLeInt(DataInput di, byte[] b) throws IOException {\n        di.readFully(b, 0, 4);\n        return ((b[0] & 0xff) | (b[1] & 0xff) << 8)\n                | ((b[2] & 0xff) | (b[3] & 0xff) << 8) << 16;\n    }\n\n    private final int readLeInt(ParcelFileDescriptor pfd, byte[] b) throws IOException {\n        PfdHelper.readFully(pfd, b, 0, 4);//di.readFully(b, 0, 4);\n        return ((b[0] & 0xff) | (b[1] & 0xff) << 8)\n                | ((b[2] & 0xff) | (b[3] & 0xff) << 8) << 16;\n    }\n\n\n    /**\n     * Read an unsigned short in little endian byte order from the given\n     * byte buffer at the given offset.\n     *\n     * @param b   the byte array to read from.\n     * @param off the offset to read from.\n     * @return The value read.\n     */\n    private final int readLeShort(byte[] b, int off) {\n        return (b[off] & 0xff) | (b[off + 1] & 0xff) << 8;\n    }\n\n    /**\n     * Read an int in little endian byte order from the given\n     * byte buffer at the given offset.\n     *\n     * @param b   the byte array to read from.\n     * @param off the offset to read from.\n     * @return The value read.\n     */\n    private final int readLeInt(byte[] b, int off) {\n        return ((b[off] & 0xff) | (b[off + 1] & 0xff) << 8)\n                | ((b[off + 2] & 0xff) | (b[off + 3] & 0xff) << 8) << 16;\n    }\n\n\n    /**\n     * Read the central directory of a zip file and fill the entries\n     * array.  This is called exactly once when first needed. It is called\n     * while holding the lock on <code>raf</code>.\n     *\n     * @throws IOException  if a i/o error occured.\n     * @throws ZipException if the central directory is malformed\n     */\n    private void readEntries() throws ZipException, IOException {\n        /* Search for the End Of Central Directory.  When a zip comment is\n         * present the directory may start earlier.\n         * FIXME: This searches the whole file in a very slow manner if the\n         * file isn't a zip file.\n         */\n        //long pos = raf.length() - ENDHDR;\n        long pos = PfdHelper.length(pfd) - ENDHDR;\n        byte[] ebs = new byte[CENHDR];\n\n        do {\n            if (pos < 0)\n                throw new ZipException\n                        (\"central directory not found, probably not a zip file: \" + name);\n            //raf.seek(pos--);\n            seek(pfd, pos--);\n        }\n        //while (readLeInt(raf, ebs) != ENDSIG);\n        while (readLeInt(pfd, ebs) != ENDSIG);\n\n        if (PfdHelper.skipBytes(pfd, ENDTOT - ENDNRD) != ENDTOT - ENDNRD)\n            throw new EOFException(name);\n        //int count = readLeShort(raf, ebs);\n        int count = readLeShort(pfd, ebs);\n        if (PfdHelper.skipBytes(pfd, ENDOFF - ENDSIZ) != ENDOFF - ENDSIZ)\n            throw new EOFException(name);\n        int centralOffset = readLeInt(pfd, ebs);\n\n        entries = new HashMap<>(count + count / 2);\n        //raf.seek(centralOffset);\n        seek(pfd, centralOffset);\n\n        byte[] buffer = new byte[16];\n        for (int i = 0; i < count; i++) {\n            //raf.readFully(ebs);\n            PfdHelper.readFully(pfd, ebs);\n            if (readLeInt(ebs, 0) != CENSIG)\n                throw new ZipException(\"Wrong Central Directory signature: \" + name);\n\n            int method = readLeShort(ebs, CENHOW);\n            int dostime = readLeInt(ebs, CENTIM);\n            int crc = readLeInt(ebs, CENCRC);\n            int csize = readLeInt(ebs, CENSIZ);\n            int size = readLeInt(ebs, CENLEN);\n            int nameLen = readLeShort(ebs, CENNAM);\n            int extraLen = readLeShort(ebs, CENEXT);\n            int commentLen = readLeShort(ebs, CENCOM);\n\n            int offset = readLeInt(ebs, CENOFF);\n\n            int needBuffer = Math.max(nameLen, commentLen);\n            if (buffer.length < needBuffer)\n                buffer = new byte[needBuffer];\n\n            PfdHelper.readFully(pfd, buffer, 0, nameLen);\n            String name = new String(buffer, 0, nameLen);\n\n            AndroidZipEntry entry = new AndroidZipEntry(name, nameLen);\n            entry.setMethod(method);\n            entry.setCrc(crc & 0xffffffffL);\n            entry.setSize(size & 0xffffffffL);\n            entry.setCompressedSize(csize & 0xffffffffL);\n            entry.setTime(dostime);\n            if (extraLen > 0) {\n                byte[] extra = new byte[extraLen];\n                PfdHelper.readFully(pfd, extra);\n                entry.setExtra(extra);\n            }\n            if (commentLen > 0) {\n                PfdHelper.readFully(pfd, buffer, 0, commentLen);\n                entry.setComment(new String(buffer, 0, commentLen));\n            }\n            entry.offset = offset;\n            //ZipEntryHelper.setOffset(entry,offset);\n\n            //entry. = offset;\n            entries.put(name, entry);\n        }\n    }\n\n    /**\n     * Closes the AndroidZipFile.  This also closes all input streams given by\n     * this class.  After this is called, no further method should be\n     * called.\n     *\n     * @throws IOException if a i/o error occured.\n     */\n    public void close() throws IOException {\n        synchronized (pfd) {\n            closed = true;\n            entries = null;\n            pfd.close();\n        }\n    }\n\n    /**\n     * Calls the <code>close()</code> method when this AndroidZipFile has not yet\n     * been explicitly closed.\n     */\n    protected void finalize() throws IOException {\n        if (!closed && pfd != null) close();\n    }\n\n    /**\n     * Returns an enumeration of all Zip entries in this Zip file.\n     */\n    public Enumeration<AndroidZipEntry> entries() {\n        try {\n            return new ZipEntryEnumeration(getEntries().values().iterator());\n        } catch (IOException ioe) {\n            return null;\n        }\n    }\n\n    /**\n     * Checks that the AndroidZipFile is still open and reads entries when necessary.\n     *\n     * @throws IllegalStateException when the AndroidZipFile has already been closed.\n     * @throws java,                 IOEexception          when the entries could not be read.\n     */\n    private HashMap<String, AndroidZipEntry> getEntries() throws IOException {\n        synchronized (pfd) {\n            if (closed)\n                throw new IllegalStateException(\"AndroidZipFile has closed: \" + name);\n\n            if (entries == null)\n                readEntries();\n\n            return entries;\n        }\n    }\n\n    /**\n     * Searches for a zip entry in this archive with the given name.\n     *\n     * @param name name. May contain directory components separated by\n     *             slashes ('/').\n     * @return the zip entry, or null if no entry with that name exists.\n     */\n    public AndroidZipEntry getEntry(String name) {\n        try {\n            HashMap<String, AndroidZipEntry> entries = getEntries();\n            AndroidZipEntry entry = entries.get(name);\n            return entry != null ? (AndroidZipEntry) entry.clone() : null;\n        } catch (IOException ioe) {\n            return null;\n        }\n    }\n\n\n    //access should be protected by synchronized(raf)\n    private final byte[] locBuf = new byte[LOCHDR];\n\n    /**\n     * Checks, if the local header of the entry at index i matches the\n     * central directory, and returns the offset to the data.\n     *\n     * @param entry to check.\n     * @return the start offset of the (compressed) data.\n     * @throws IOException  if a i/o error occured.\n     * @throws ZipException if the local header doesn't match the\n     *                      central directory header\n     */\n    private long checkLocalHeader(AndroidZipEntry entry) throws IOException {\n        synchronized (pfd) {\n            seek(pfd, entry.offset);\n            PfdHelper.readFully(pfd, locBuf);\n\n            if (readLeInt(locBuf, 0) != LOCSIG)\n                throw new ZipException(\"Wrong Local header signature: \" + name);\n\n            if (entry.getMethod() != readLeShort(locBuf, LOCHOW))\n                throw new ZipException(\"Compression method mismatch: \" + name);\n\n            if (entry.getNameLen() != readLeShort(locBuf, LOCNAM))\n                throw new ZipException(\"file name length mismatch: \" + name);\n\n            int extraLen = entry.getNameLen() + readLeShort(locBuf, LOCEXT);\n            return entry.offset + LOCHDR + extraLen;\n        }\n    }\n\n    /**\n     * Creates an input stream reading the given zip entry as\n     * uncompressed data.  Normally zip entry should be an entry\n     * returned by getEntry() or entries().\n     *\n     * @param entry the entry to create an InputStream for.\n     * @return the input stream.\n     * @throws IOException  if a i/o error occured.\n     * @throws ZipException if the Zip archive is malformed.\n     */\n    public InputStream getInputStream(AndroidZipEntry entry) throws IOException {\n        HashMap<String, AndroidZipEntry> entries = getEntries();\n        String name = entry.getName();\n        AndroidZipEntry zipEntry = entries.get(name);\n        if (zipEntry == null)\n            throw new NoSuchElementException(name);\n\n        long start = checkLocalHeader(zipEntry);\n        int method = zipEntry.getMethod();\n        InputStream is = new BufferedInputStream(new PartialInputStream\n                (pfd, start, zipEntry.getCompressedSize()));\n        switch (method) {\n            case ZipOutputStream.STORED:\n                return is;\n            case ZipOutputStream.DEFLATED:\n                return new InflaterInputStream(is, new Inflater(true));\n            default:\n                throw new ZipException(\"Unknown compression method \" + method);\n        }\n    }\n\n    /**\n     * Returns the (path) name of this zip file.\n     */\n    public String getName() {\n        return name;\n    }\n\n    /**\n     * Returns the number of entries in this zip file.\n     */\n    public int size() {\n        try {\n            return getEntries().size();\n        } catch (IOException ioe) {\n            return 0;\n        }\n    }\n\n    private static class ZipEntryEnumeration implements Enumeration<AndroidZipEntry> {\n        private final Iterator<AndroidZipEntry> elements;\n\n        public ZipEntryEnumeration(Iterator<AndroidZipEntry> elements) {\n            this.elements = elements;\n        }\n\n        public boolean hasMoreElements() {\n            return elements.hasNext();\n        }\n\n        public AndroidZipEntry nextElement() {\n            /* We return a clone, just to be safe that the user doesn't\n             * change the entry.\n             */\n            return (AndroidZipEntry) (elements.next()).clone();\n        }\n    }\n\n    private static class PartialInputStream extends InputStream {\n        private final ParcelFileDescriptor pfd;\n        long filepos, end;\n\n        public PartialInputStream(ParcelFileDescriptor pfd, long start, long len) {\n            this.pfd = pfd;\n            filepos = start;\n            end = start + len;\n        }\n\n        public int available() {\n            long amount = end - filepos;\n            if (amount > Integer.MAX_VALUE)\n                return Integer.MAX_VALUE;\n            return (int) amount;\n        }\n\n        public int read() throws IOException {\n            if (filepos == end)\n                return -1;\n            synchronized (pfd) {\n                seek(pfd, filepos++);\n                return PfdHelper.read(pfd);\n            }\n        }\n\n        public int read(byte[] b, int off, int len) throws IOException {\n            if (len > end - filepos) {\n                len = (int) (end - filepos);\n                if (len == 0)\n                    return -1;\n            }\n            synchronized (pfd) {\n                seek(pfd, filepos);\n                int count = PfdHelper.read(pfd, b, off, len);\n                if (count > 0)\n                    filepos += len;\n                return count;\n            }\n        }\n\n        public long skip(long amount) {\n            if (amount < 0)\n                throw new IllegalArgumentException();\n            if (amount > end - filepos)\n                amount = end - filepos;\n            filepos += amount;\n            return amount;\n        }\n    }\n}\n"
  },
  {
    "path": "modules/book/src/main/java/me/ag2s/epublib/util/zip/ZipConstants.java",
    "content": "\n\npackage me.ag2s.epublib.util.zip;\n\ninterface ZipConstants {\n    /* The local file header */\n    int LOCHDR = 30;\n    int LOCSIG = 'P' | ('K' << 8) | (3 << 16) | (4 << 24);\n\n    int LOCVER = 4;\n    int LOCFLG = 6;\n    int LOCHOW = 8;\n    int LOCTIM = 10;\n    int LOCCRC = 14;\n    int LOCSIZ = 18;\n    int LOCLEN = 22;\n    int LOCNAM = 26;\n    int LOCEXT = 28;\n\n    /* The Data descriptor */\n    int EXTSIG = 'P' | ('K' << 8) | (7 << 16) | (8 << 24);\n    int EXTHDR = 16;\n\n    int EXTCRC = 4;\n    int EXTSIZ = 8;\n    int EXTLEN = 12;\n\n    /* The central directory file header */\n    int CENSIG = 'P' | ('K' << 8) | (1 << 16) | (2 << 24);\n    int CENHDR = 46;\n\n    int CENVEM = 4;\n    int CENVER = 6;\n    int CENFLG = 8;\n    int CENHOW = 10;\n    int CENTIM = 12;\n    int CENCRC = 16;\n    int CENSIZ = 20;\n    int CENLEN = 24;\n    int CENNAM = 28;\n    int CENEXT = 30;\n    int CENCOM = 32;\n    int CENDSK = 34;\n    int CENATT = 36;\n    int CENATX = 38;\n    int CENOFF = 42;\n\n    /* The entries in the end of central directory */\n    int ENDSIG = 'P' | ('K' << 8) | (5 << 16) | (6 << 24);\n    int ENDHDR = 22;\n\n    /* The following two fields are missing in SUN JDK */\n    int ENDNRD = 4;\n    int ENDDCD = 6;\n    int ENDSUB = 8;\n    int ENDTOT = 10;\n    int ENDSIZ = 12;\n    int ENDOFF = 16;\n    int ENDCOM = 20;\n}\n\n"
  },
  {
    "path": "modules/book/src/main/java/me/ag2s/epublib/util/zip/ZipEntryWrapper.java",
    "content": "package me.ag2s.epublib.util.zip;\n\nimport androidx.annotation.NonNull;\n\nimport java.util.zip.ZipEntry;\n\npublic class ZipEntryWrapper {\n    @NonNull\n    private final Object zipEntry;\n\n    public void checkType() {\n\n        if (zipEntry instanceof java.util.zip.ZipEntry || zipEntry instanceof AndroidZipEntry) {\n        } else {\n            throw new RuntimeException(\"使用了不支持的类\");\n        }\n    }\n\n    public ZipEntryWrapper(@NonNull ZipEntry zipEntry) {\n        this.zipEntry = zipEntry;\n    }\n\n    public ZipEntryWrapper(@NonNull AndroidZipEntry zipEntry) {\n        this.zipEntry = zipEntry;\n    }\n\n    public ZipEntryWrapper(@NonNull Object element) {\n\n        this.zipEntry = element;\n        checkType();\n    }\n\n    public boolean isDirectory() {\n        checkType();\n        if (zipEntry instanceof ZipEntry) {\n            return ((ZipEntry) zipEntry).isDirectory();\n        }\n        if (zipEntry instanceof AndroidZipEntry) {\n            return ((AndroidZipEntry) zipEntry).isDirectory();\n        }\n        return true;\n    }\n\n    public ZipEntry getZipEntry() {\n        return (ZipEntry) zipEntry;\n    }\n\n    public AndroidZipEntry getAndroidZipEntry() {\n        return (AndroidZipEntry) zipEntry;\n    }\n\n    public String getName() {\n        checkType();\n        if (zipEntry instanceof ZipEntry) {\n            return ((ZipEntry) zipEntry).getName();\n        }\n        if (zipEntry instanceof AndroidZipEntry) {\n            return ((AndroidZipEntry) zipEntry).getName();\n        }\n        return null;\n    }\n\n    public long getSize() {\n        checkType();\n        if (zipEntry instanceof ZipEntry) {\n            return ((ZipEntry) zipEntry).getSize();\n        }\n        if (zipEntry instanceof AndroidZipEntry) {\n            return ((AndroidZipEntry) zipEntry).getSize();\n        }\n        return -1;\n    }\n\n\n}\n"
  },
  {
    "path": "modules/book/src/main/java/me/ag2s/epublib/util/zip/ZipException.java",
    "content": "\n\npackage me.ag2s.epublib.util.zip;\n\nimport java.io.IOException;\n\n/**\n * Thrown during the creation or input of a zip file.\n *\n * @author Jochen Hoenicke\n * @author Per Bothner\n * @status updated to 1.4\n */\npublic class ZipException extends IOException {\n    /**\n     * Compatible with JDK 1.0+.\n     */\n    private static final long serialVersionUID = 8000196834066748623L;\n\n    /**\n     * Create an exception without a message.\n     */\n    public ZipException() {\n    }\n\n    /**\n     * Create an exception with a message.\n     *\n     * @param msg the message\n     */\n    public ZipException(String msg) {\n        super(msg);\n    }\n}\n"
  },
  {
    "path": "modules/book/src/main/java/me/ag2s/epublib/util/zip/ZipFileWrapper.java",
    "content": "package me.ag2s.epublib.util.zip;\n\n\nimport androidx.annotation.NonNull;\n\nimport java.io.IOException;\nimport java.io.InputStream;\nimport java.util.Enumeration;\nimport java.util.zip.ZipFile;\n\n/**\n * 对ZipFile的包装\n */\n\npublic class ZipFileWrapper {\n    @NonNull\n    private final Object zipFile;\n\n\n    public void checkType() {\n        if (zipFile instanceof java.util.zip.ZipFile || zipFile instanceof AndroidZipFile) {\n        } else {\n            throw new RuntimeException(\"使用了不支持的类\");\n        }\n    }\n\n    public ZipFileWrapper(@NonNull ZipFile zipFile) {\n        this.zipFile = zipFile;\n        checkType();\n    }\n\n    public ZipFileWrapper(@NonNull AndroidZipFile zipFile) {\n        this.zipFile = zipFile;\n        checkType();\n    }\n\n    public String getName() {\n        checkType();\n        if (zipFile instanceof java.util.zip.ZipFile) {\n            return ((ZipFile) zipFile).getName();\n        } else if (zipFile instanceof AndroidZipFile) {\n            return ((AndroidZipFile) zipFile).getName();\n        } else {\n            return null;\n        }\n    }\n\n    public String getComment() {\n        checkType();\n        if (zipFile instanceof java.util.zip.ZipFile) {\n            return ((ZipFile) zipFile).getComment();\n        } else if (zipFile instanceof AndroidZipFile) {\n            return ((AndroidZipFile) zipFile).getName();\n        } else {\n            return null;\n        }\n    }\n\n    public ZipEntryWrapper getEntry(String name) {\n        checkType();\n        if (zipFile instanceof java.util.zip.ZipFile) {\n            return new ZipEntryWrapper(((ZipFile) zipFile).getEntry(name));\n        } else if (zipFile instanceof AndroidZipFile) {\n            return new ZipEntryWrapper(((AndroidZipFile) zipFile).getEntry(name));\n        } else {\n            return null;\n        }\n    }\n\n    public Enumeration entries() {\n        checkType();\n        if (zipFile instanceof java.util.zip.ZipFile) {\n            return ((ZipFile) zipFile).entries();\n\n        } else if (zipFile instanceof AndroidZipFile) {\n            return ((AndroidZipFile) zipFile).entries();\n        } else {\n            return null;\n        }\n    }\n\n    public InputStream getInputStream(ZipEntryWrapper entry) throws IOException {\n        checkType();\n        if (zipFile instanceof java.util.zip.ZipFile) {\n            return ((ZipFile) zipFile).getInputStream(entry.getZipEntry());\n        } else if (zipFile instanceof AndroidZipFile) {\n            return ((AndroidZipFile) zipFile).getInputStream(entry.getAndroidZipEntry());\n        } else {\n            return null;\n        }\n    }\n\n    public void close() throws IOException {\n        checkType();\n        if (zipFile instanceof java.util.zip.ZipFile) {\n            ((ZipFile) zipFile).close();\n        } else if (zipFile instanceof AndroidZipFile) {\n            ((AndroidZipFile) zipFile).close();\n        }\n    }\n\n\n}\n"
  },
  {
    "path": "modules/book/src/main/java/me/ag2s/umdlib/domain/UmdBook.java",
    "content": "package me.ag2s.umdlib.domain;\n\nimport java.io.IOException;\nimport java.io.OutputStream;\n\nimport me.ag2s.umdlib.tool.WrapOutputStream;\n\npublic class UmdBook {\n\n    public int getNum() {\n        return num;\n    }\n\n    public void setNum(int num) {\n        this.num = num;\n    }\n\n    private int num;\n\n\n    /**\n     * Header Part of UMD book\n     */\n    private UmdHeader header = new UmdHeader();\n    /**\n     * Detail chapters Part of UMD book\n     * (include Titles & Contents of each chapter)\n     */\n    private UmdChapters chapters = new UmdChapters();\n\n    /**\n     * Cover Part of UMD book (for example, and JPEG file)\n     */\n    private UmdCover cover = new UmdCover();\n\n    /**\n     * End Part of UMD book\n     */\n    private UmdEnd end = new UmdEnd();\n\n    /**\n     * Build the UMD file.\n     *\n     * @param os OutputStream\n     * @throws IOException\n     */\n    public void buildUmd(OutputStream os) throws IOException {\n        WrapOutputStream wos = new WrapOutputStream(os);\n\n        header.buildHeader(wos);\n        chapters.buildChapters(wos);\n        cover.buildCover(wos);\n        end.buildEnd(wos);\n    }\n\n    public UmdHeader getHeader() {\n        return header;\n    }\n\n    public void setHeader(UmdHeader header) {\n        this.header = header;\n    }\n\n    public UmdChapters getChapters() {\n        return chapters;\n    }\n\n    public void setChapters(UmdChapters chapters) {\n        this.chapters = chapters;\n    }\n\n    public UmdCover getCover() {\n        return cover;\n    }\n\n    public void setCover(UmdCover cover) {\n        this.cover = cover;\n    }\n\n    public UmdEnd getEnd() {\n        return end;\n    }\n\n    public void setEnd(UmdEnd end) {\n        this.end = end;\n    }\n\n}\n"
  },
  {
    "path": "modules/book/src/main/java/me/ag2s/umdlib/domain/UmdChapters.java",
    "content": "package me.ag2s.umdlib.domain;\n\nimport java.io.ByteArrayOutputStream;\nimport java.io.File;\nimport java.io.IOException;\nimport java.util.ArrayList;\nimport java.util.Arrays;\nimport java.util.List;\nimport java.util.zip.DeflaterOutputStream;\n\nimport me.ag2s.umdlib.tool.UmdUtils;\nimport me.ag2s.umdlib.tool.WrapOutputStream;\n\n/**\n * It includes all titles and contents of each chapter in the UMD file.\n * And the content has been compressed by zlib.\n *\n * @author Ray Liang (liangguanhui@qq.com)\n * 2009-12-20\n */\npublic class UmdChapters {\n\n    private static final int DEFAULT_CHUNK_INIT_SIZE = 32768;\n    private int TotalContentLen;\n\n    public List<byte[]> getTitles() {\n        return titles;\n    }\n\n    private final List<byte[]> titles = new ArrayList<>();\n    public List<Integer> contentLengths = new ArrayList<>();\n    public ByteArrayOutputStream contents = new ByteArrayOutputStream();\n\n    public void addTitle(String s) {\n        titles.add(UmdUtils.stringToUnicodeBytes(s));\n    }\n\n    public void addTitle(byte[] s) {\n        titles.add(s);\n    }\n\n    public void addContentLength(Integer integer) {\n        contentLengths.add(integer);\n    }\n\n    public int getContentLength(int index) {\n        return contentLengths.get(index);\n    }\n\n    public byte[] getContent(int index) {\n        int st = contentLengths.get(index);\n        byte[] b = contents.toByteArray();\n        int end = index + 1 < contentLengths.size() ? contentLengths.get(index + 1) : getTotalContentLen();\n        System.out.println(\"总长度:\" + contents.size());\n        System.out.println(\"起始值:\" + st);\n        System.out.println(\"结束值:\" + end);\n        byte[] bAr = new byte[end - st];\n        System.arraycopy(b, st, bAr, 0, bAr.length);\n        return bAr;\n\n    }\n\n    public String getContentString(int index) {\n        return UmdUtils.unicodeBytesToString(getContent(index)).replace((char) 0x2029, '\\n');\n\n    }\n\n    public String getTitle(int index) {\n        return UmdUtils.unicodeBytesToString(titles.get(index));\n    }\n\n\n    public void buildChapters(WrapOutputStream wos) throws IOException {\n        writeChaptersHead(wos);\n        writeChaptersContentOffset(wos);\n        writeChaptersTitles(wos);\n        writeChaptersChunks(wos);\n    }\n\n    private void writeChaptersHead(WrapOutputStream wos) throws IOException {\n        wos.writeBytes('#', 0x0b, 0, 0, 0x09);\n        wos.writeInt(contents.size());\n    }\n\n    private void writeChaptersContentOffset(WrapOutputStream wos) throws IOException {\n        wos.writeBytes('#', 0x83, 0, 0, 0x09);\n        byte[] rb = UmdUtils.genRandomBytes(4);\n        wos.writeBytes(rb); //random numbers\n        wos.write('$');\n        wos.writeBytes(rb); //random numbers\n\n        wos.writeInt(contentLengths.size() * 4 + 9);  // about the count of chapters\n        int offset = 0;\n        for (Integer n : contentLengths) {\n            wos.writeInt(offset);\n            offset += n;\n        }\n    }\n\n    private void writeChaptersTitles(WrapOutputStream wos) throws IOException {\n        wos.writeBytes('#', 0x84, 0, 0x01, 0x09);\n        byte[] rb = UmdUtils.genRandomBytes(4);\n        wos.writeBytes(rb); //random numbers\n        wos.write('$');\n        wos.writeBytes(rb); //random numbers\n\n        int totalTitlesLen = 0;\n        for (byte[] t : titles) {\n            totalTitlesLen += t.length;\n        }\n\n        // about the length of the titles\n        wos.writeInt(totalTitlesLen + titles.size() + 9);\n\n        for (byte[] t : titles) {\n            wos.writeByte(t.length);\n            wos.write(t);\n        }\n    }\n\n    private void writeChaptersChunks(WrapOutputStream wos) throws IOException {\n        byte[] allContents = contents.toByteArray();\n\n        byte[] zero16 = new byte[16];\n        Arrays.fill(zero16, 0, zero16.length, (byte) 0);\n\n        // write each package of content\n        int startPos = 0;\n        int len = 0;\n        int left = 0;\n        int chunkCnt = 0;\n        ByteArrayOutputStream bos = new ByteArrayOutputStream(DEFAULT_CHUNK_INIT_SIZE + 256);\n        List<byte[]> chunkRbList = new ArrayList<>();\n\n        while (startPos < allContents.length) {\n            left = allContents.length - startPos;\n            len = Math.min(DEFAULT_CHUNK_INIT_SIZE, left);\n\n            bos.reset();\n            DeflaterOutputStream zos = new DeflaterOutputStream(bos);\n            zos.write(allContents, startPos, len);\n            zos.close();\n            byte[] chunk = bos.toByteArray();\n\n            byte[] rb = UmdUtils.genRandomBytes(4);\n            wos.writeByte('$');\n            wos.writeBytes(rb);  // 4 random\n            chunkRbList.add(rb);\n            wos.writeInt(chunk.length + 9);\n            wos.write(chunk);\n\n            // end of each chunk\n            wos.writeBytes('#', 0xF1, 0, 0, 0x15);\n            wos.write(zero16);\n\n            startPos += len;\n            chunkCnt++;\n        }\n\n        // end of all chunks\n        wos.writeBytes('#', 0x81, 0, 0x01, 0x09);\n        wos.writeBytes(0, 0, 0, 0); //random numbers\n        wos.write('$');\n        wos.writeBytes(0, 0, 0, 0); //random numbers\n        wos.writeInt(chunkCnt * 4 + 9);\n        for (int i = chunkCnt - 1; i >= 0; i--) {\n            // random. They are as the same as random numbers in the begin of each chunk\n            // use desc order to output these random\n            wos.writeBytes(chunkRbList.get(i));\n        }\n    }\n\n    public void addChapter(String title, String content) {\n        titles.add(UmdUtils.stringToUnicodeBytes(title));\n        byte[] b = UmdUtils.stringToUnicodeBytes(content);\n        contentLengths.add(b.length);\n        try {\n            contents.write(b);\n        } catch (IOException e) {\n            throw new RuntimeException(e);\n        }\n    }\n\n    public void addFile(File f, String title) throws IOException {\n        byte[] temp = UmdUtils.readFile(f);\n        String s = new String(temp);\n        addChapter(title, s);\n    }\n\n    public void addFile(File f) throws IOException {\n        String s = f.getName();\n        int idx = s.lastIndexOf('.');\n        if (idx >= 0) {\n            s = s.substring(0, idx);\n        }\n        addFile(f, s);\n    }\n\n    public void clearChapters() {\n        titles.clear();\n        contentLengths.clear();\n        contents.reset();\n    }\n\n    public int getTotalContentLen() {\n        return TotalContentLen;\n    }\n\n    public void setTotalContentLen(int totalContentLen) {\n        TotalContentLen = totalContentLen;\n    }\n}\n"
  },
  {
    "path": "modules/book/src/main/java/me/ag2s/umdlib/domain/UmdCover.java",
    "content": "package me.ag2s.umdlib.domain;\n\n\nimport java.io.File;\nimport java.io.IOException;\n\nimport me.ag2s.umdlib.tool.UmdUtils;\nimport me.ag2s.umdlib.tool.WrapOutputStream;\n\n\n/**\n * This is the cover part of the UMD file.\n * <p>\n * NOTICE: if the \"coverData\" is empty, it will be skipped when building UMD file.\n * </P>\n * There are 3 ways to load the image data:\n * <ol>\n *     <li>new constructor function of UmdCover.</li>\n *     <li>use UmdCover.load function.</li>\n *     <li>use UmdCover.initDefaultCover, it will generate a simple image with text.</li>\n * </ol>\n *\n * @author Ray Liang (liangguanhui@qq.com)\n * 2009-12-20\n */\npublic class UmdCover {\n\n    private static final int DEFAULT_COVER_WIDTH = 120;\n    private static final int DEFAULT_COVER_HEIGHT = 160;\n\n    private byte[] coverData;\n\n    public UmdCover() {\n    }\n\n    public UmdCover(byte[] coverData) {\n        this.coverData = coverData;\n    }\n\n    public void load(File f) throws IOException {\n        this.coverData = UmdUtils.readFile(f);\n    }\n\n    public void load(String fileName) throws IOException {\n        load(new File(fileName));\n    }\n\n    public void initDefaultCover(String title) throws IOException {\n//\t\tBufferedImage img = new BufferedImage(DEFAULT_COVER_WIDTH, DEFAULT_COVER_HEIGHT, BufferedImage.TYPE_INT_RGB);\n//\t\tGraphics g = img.getGraphics();\n//\t\tg.setColor(Color.BLACK);\n//\t\tg.fillRect(0, 0, img.getWidth(), img.getHeight());\n//\t\tg.setColor(Color.WHITE);\n//\t\tg.setFont(new Font(\"����\", Font.PLAIN, 12));\n//\n//\t\tFontMetrics fm = g.getFontMetrics();\n//\t\tint ascent = fm.getAscent();\n//\t\tint descent = fm.getDescent();\n//\t\tint strWidth = fm.stringWidth(title);\n//\t\tint x = (img.getWidth() - strWidth) / 2;\n//\t\tint y = (img.getHeight() - ascent - descent) / 2;\n//\t\tg.drawString(title, x, y);\n//\t\tg.dispose();\n//\n//\t\tByteArrayOutputStream baos = new ByteArrayOutputStream();\n//\n//\t\tJPEGImageEncoder encoder = JPEGCodec.createJPEGEncoder(baos);\n//\t\tJPEGEncodeParam param = encoder.getDefaultJPEGEncodeParam(img);\n//\t\tparam.setQuality(0.5f, false);\n//\t\tencoder.setJPEGEncodeParam(param);\n//\t\tencoder.encode(img);\n//\n//\t\tcoverData = baos.toByteArray();\n    }\n\n    public void buildCover(WrapOutputStream wos) throws IOException {\n        if (coverData == null || coverData.length == 0) {\n            return;\n        }\n        wos.writeBytes('#', 0x82, 0, 0x01, 0x0A, 0x01);\n        byte[] rb = UmdUtils.genRandomBytes(4);\n        wos.writeBytes(rb); //random numbers\n        wos.write('$');\n        wos.writeBytes(rb); //random numbers\n        wos.writeInt(coverData.length + 9);\n        wos.write(coverData);\n    }\n\n    public byte[] getCoverData() {\n        return coverData;\n    }\n\n    public void setCoverData(byte[] coverData) {\n        this.coverData = coverData;\n    }\n\n}\n"
  },
  {
    "path": "modules/book/src/main/java/me/ag2s/umdlib/domain/UmdEnd.java",
    "content": "package me.ag2s.umdlib.domain;\n\nimport java.io.IOException;\n\nimport me.ag2s.umdlib.tool.WrapOutputStream;\n\n/**\n * End part of UMD book, nothing to be special\n *\n * @author Ray Liang (liangguanhui@qq.com)\n * 2009-12-20\n */\npublic class UmdEnd {\n\n    public void buildEnd(WrapOutputStream wos) throws IOException {\n        wos.writeBytes('#', 0x0C, 0, 0x01, 0x09);\n        wos.writeInt(wos.getWritten() + 4);\n    }\n\n}\n"
  },
  {
    "path": "modules/book/src/main/java/me/ag2s/umdlib/domain/UmdHeader.java",
    "content": "package me.ag2s.umdlib.domain;\n\n\nimport androidx.annotation.NonNull;\n\nimport java.io.IOException;\n\nimport me.ag2s.umdlib.tool.UmdUtils;\nimport me.ag2s.umdlib.tool.WrapOutputStream;\n\n/**\n * Header of UMD file.\n * It includes a lot of properties of header.\n * All the properties are String type.\n *\n * @author Ray Liang (liangguanhui@qq.com)\n * 2009-12-20\n */\npublic class UmdHeader {\n    public byte getUmdType() {\n        return umdType;\n    }\n\n    public void setUmdType(byte umdType) {\n        this.umdType = umdType;\n    }\n\n    private byte umdType;\n    private String title;\n\n    private String author;\n\n    private String year;\n\n    private String month;\n\n    private String day;\n\n    private String bookType;\n\n    private String bookMan;\n\n    private String shopKeeper;\n    private final static byte B_type_umd = (byte) 0x01;\n    private final static byte B_type_title = (byte) 0x02;\n    private final static byte B_type_author = (byte) 0x03;\n    private final static byte B_type_year = (byte) 0x04;\n    private final static byte B_type_month = (byte) 0x05;\n    private final static byte B_type_day = (byte) 0x06;\n    private final static byte B_type_bookType = (byte) 0x07;\n    private final static byte B_type_bookMan = (byte) 0x08;\n    private final static byte B_type_shopKeeper = (byte) 0x09;\n\n    public void buildHeader(WrapOutputStream wos) throws IOException {\n        wos.writeBytes(0x89, 0x9b, 0x9a, 0xde); // UMD file type flags\n        wos.writeByte('#');\n        wos.writeBytes(0x01, 0x00, 0x00, 0x08); // Unknown\n        wos.writeByte(0x01); //0x01 is text type; while 0x02 is Image type.\n        wos.writeBytes(UmdUtils.genRandomBytes(2)); //random number\n\n        // start properties output\n        buildType(wos, B_type_title, getTitle());\n        buildType(wos, B_type_author, getAuthor());\n        buildType(wos, B_type_year, getYear());\n        buildType(wos, B_type_month, getMonth());\n        buildType(wos, B_type_day, getDay());\n        buildType(wos, B_type_bookType, getBookType());\n        buildType(wos, B_type_bookMan, getBookMan());\n        buildType(wos, B_type_shopKeeper, getShopKeeper());\n    }\n\n    public void buildType(WrapOutputStream wos, byte type, String content) throws IOException {\n        if (content == null || content.length() == 0) {\n            return;\n        }\n\n        wos.writeBytes('#', type, 0, 0);\n\n        byte[] temp = UmdUtils.stringToUnicodeBytes(content);\n        wos.writeByte(temp.length + 5);\n        wos.write(temp);\n    }\n\n\n    public String getTitle() {\n        return title;\n    }\n\n    public void setTitle(String title) {\n        this.title = title;\n    }\n\n    public String getAuthor() {\n        return author;\n    }\n\n    public void setAuthor(String author) {\n        this.author = author;\n    }\n\n    public String getBookMan() {\n        return bookMan;\n    }\n\n    public void setBookMan(String bookMan) {\n        this.bookMan = bookMan;\n    }\n\n    public String getShopKeeper() {\n        return shopKeeper;\n    }\n\n    public void setShopKeeper(String shopKeeper) {\n        this.shopKeeper = shopKeeper;\n    }\n\n    public String getYear() {\n        return year;\n    }\n\n    public void setYear(String year) {\n        this.year = year;\n    }\n\n    public String getMonth() {\n        return month;\n    }\n\n    public void setMonth(String month) {\n        this.month = month;\n    }\n\n    public String getDay() {\n        return day;\n    }\n\n    public void setDay(String day) {\n        this.day = day;\n    }\n\n    public String getBookType() {\n        return bookType;\n    }\n\n    public void setBookType(String bookType) {\n        this.bookType = bookType;\n    }\n\n    @Override\n    @NonNull\n    public String toString() {\n        return \"UmdHeader{\" +\n                \"umdType=\" + umdType +\n                \", title='\" + title + '\\'' +\n                \", author='\" + author + '\\'' +\n                \", year='\" + year + '\\'' +\n                \", month='\" + month + '\\'' +\n                \", day='\" + day + '\\'' +\n                \", bookType='\" + bookType + '\\'' +\n                \", bookMan='\" + bookMan + '\\'' +\n                \", shopKeeper='\" + shopKeeper + '\\'' +\n                '}';\n    }\n}\n"
  },
  {
    "path": "modules/book/src/main/java/me/ag2s/umdlib/tool/StreamReader.java",
    "content": "package me.ag2s.umdlib.tool;\n\n\nimport java.io.IOException;\nimport java.io.InputStream;\n\npublic class StreamReader {\n    private final InputStream is;\n\n    public long getOffset() {\n        return offset;\n    }\n\n    public void setOffset(long offset) {\n        this.offset = offset;\n    }\n\n    public long getSize() {\n        return size;\n    }\n\n    public void setSize(long size) {\n        this.size = size;\n    }\n\n    private long offset;\n    private long size;\n\n    private void incCount(int value) {\n        int temp = (int) (offset + value);\n        if (temp < 0) {\n            temp = Integer.MAX_VALUE;\n        }\n        offset = temp;\n    }\n\n    public StreamReader(InputStream inputStream) throws IOException {\n        this.is = inputStream;\n        //this.size=inputStream.getChannel().size();\n    }\n\n    public short readUint8() throws IOException {\n        byte[] b = new byte[1];\n        is.read(b);\n        incCount(1);\n        return (short) ((b[0] & 0xFF));\n\n    }\n\n    public byte readByte() throws IOException {\n        byte[] b = new byte[1];\n        is.read(b);\n        incCount(1);\n        return b[0];\n    }\n\n    public byte[] readBytes(int len) throws IOException {\n        if (len < 0) {\n            System.out.println(len);\n            throw new IllegalArgumentException(\"Length must > 0: \" + len);\n        }\n        if (len==0){\n            return null;\n        }\n        byte[] b = new byte[len];\n        is.read(b);\n        incCount(len);\n        return b;\n    }\n\n    public String readHex(int len) throws IOException {\n        if (len < 1) {\n            System.out.println(len);\n            throw new IllegalArgumentException(\"Length must > 0: \" + len);\n        }\n        byte[] b = new byte[len];\n        is.read(b);\n        incCount(len);\n        return UmdUtils.toHex(b);\n    }\n\n    public short readShort() throws IOException {\n        byte[] b = new byte[2];\n        is.read(b);\n        incCount(2);\n        short x = (short) (((b[0] & 0xFF) << 8) | ((b[1] & 0xFF) << 0));\n        return x;\n    }\n\n    public short readShortLe() throws IOException {\n        byte[] b = new byte[2];\n        is.read(b);\n        incCount(2);\n        short x = (short) (((b[1] & 0xFF) << 8) | ((b[0] & 0xFF) << 0));\n        return x;\n    }\n\n    public int readInt() throws IOException {\n        byte[] b = new byte[4];\n        is.read(b);\n        incCount(4);\n        int x = ((b[0] & 0xFF) << 24) | ((b[1] & 0xFF) << 16) |\n                ((b[2] & 0xFF) << 8) | ((b[3] & 0xFF) << 0);\n        return x;\n    }\n\n    public int readIntLe() throws IOException {\n        byte[] b = new byte[4];\n        is.read(b);\n        incCount(4);\n        int x = ((b[3] & 0xFF) << 24) | ((b[2] & 0xFF) << 16) |\n                ((b[1] & 0xFF) << 8) | ((b[0] & 0xFF) << 0);\n        return x;\n    }\n\n    public void skip(int len) throws IOException {\n        readBytes(len);\n    }\n\n\n    public byte[] read(byte[] b) throws IOException {\n        is.read(b);\n        incCount(b.length);\n        return b;\n    }\n\n    public byte[] read(byte[] b, int off, int len) throws IOException {\n        is.read(b, off, len);\n        incCount(len);\n        return b;\n    }\n\n\n}\n"
  },
  {
    "path": "modules/book/src/main/java/me/ag2s/umdlib/tool/UmdUtils.java",
    "content": "\npackage me.ag2s.umdlib.tool;\n\nimport java.io.BufferedInputStream;\nimport java.io.BufferedOutputStream;\nimport java.io.ByteArrayOutputStream;\nimport java.io.File;\nimport java.io.FileInputStream;\nimport java.io.FileOutputStream;\nimport java.io.IOException;\nimport java.util.Random;\nimport java.util.zip.Inflater;\nimport java.nio.charset.StandardCharsets;\n\n\npublic class UmdUtils {\n\n    private static final int EOF = -1;\n    private static final int BUFFER_SIZE = 8 * 1024;\n\n\n    /**\n     * 将字符串编码成Unicode形式的byte[]\n     *\n     * @param s 要编码的字符串\n     * @return 编码好的byte[]\n     */\n    public static byte[] stringToUnicodeBytes(String s) {\n        if (s == null) {\n            throw new NullPointerException();\n        }\n\n        return s.getBytes(StandardCharsets.UTF_16LE);\n    }\n\n    /**\n     * 将编码成Unicode形式的byte[]解码成原始字符串\n     *\n     * @param bytes 编码成Unicode形式的byte[]\n     * @return 原始字符串\n     */\n    public static String unicodeBytesToString(byte[] bytes) {\n        //修复一些文件属性空值的问题\n        if (bytes==null){\n            return \"\";\n        }\n        return new String(bytes, StandardCharsets.UTF_16LE);\n    }\n\n    /**\n     * 将byte[]转化成Hex形式\n     *\n     * @param bArr byte[]\n     * @return 目标HEX字符串\n     */\n    public static String toHex(byte[] bArr) {\n        StringBuilder sb = new StringBuilder(bArr.length);\n        String sTmp;\n\n        for (int i = 0; i < bArr.length; i++) {\n            sTmp = Integer.toHexString(0xFF & bArr[i]);\n            if (sTmp.length() < 2)\n                sb.append(0);\n            sb.append(sTmp.toUpperCase());\n        }\n\n        return sb.toString();\n    }\n\n    /**\n     * 解压缩zip的byte[]\n     *\n     * @param compress zippered byte[]\n     * @return decompressed byte[]\n     * @throws Exception 解码时失败时\n     */\n    public static byte[] decompress(byte[] compress) throws Exception {\n        Inflater inflater = new Inflater();\n        inflater.reset();\n        inflater.setInput(compress);\n\n        ByteArrayOutputStream baos = new ByteArrayOutputStream(compress.length);\n        try (baos) {\n            byte[] buff = new byte[BUFFER_SIZE];\n            while (!inflater.finished()) {\n                int count = inflater.inflate(buff);\n                baos.write(buff, 0, count);\n            }\n        }\n        inflater.end();\n        return baos.toByteArray();\n    }\n\n\n    public static void saveFile(File f, byte[] content) throws IOException {\n        try (FileOutputStream fos = new FileOutputStream(f)) {\n            BufferedOutputStream bos = new BufferedOutputStream(fos);\n            bos.write(content);\n            bos.flush();\n        }\n    }\n\n    public static byte[] readFile(File f) throws IOException {\n        try (FileInputStream fis = new FileInputStream(f)) {\n            ByteArrayOutputStream baos = new ByteArrayOutputStream();\n            BufferedInputStream bis = new BufferedInputStream(fis);\n            int ch;\n            while ((ch = bis.read()) >= 0) {\n                baos.write(ch);\n            }\n            baos.flush();\n            return baos.toByteArray();\n        }\n    }\n\n    private static final Random random = new Random();\n\n    public static byte[] genRandomBytes(int len) {\n        if (len <= 0) {\n            throw new IllegalArgumentException(\"Length must > 0: \" + len);\n        }\n        byte[] ret = new byte[len];\n        for (int i = 0; i < ret.length; i++) {\n            ret[i] = (byte) random.nextInt(256);\n        }\n        return ret;\n    }\n\n}\n"
  },
  {
    "path": "modules/book/src/main/java/me/ag2s/umdlib/tool/WrapOutputStream.java",
    "content": "package me.ag2s.umdlib.tool;\n\nimport java.io.IOException;\nimport java.io.OutputStream;\n\npublic class WrapOutputStream extends OutputStream {\n\n    private final OutputStream os;\n    private int written;\n\n    public WrapOutputStream(OutputStream os) {\n        this.os = os;\n    }\n\n    private void incCount(int value) {\n        int temp = written + value;\n        if (temp < 0) {\n            temp = Integer.MAX_VALUE;\n        }\n        written = temp;\n    }\n\n    // it is different from the writeInt of DataOutputStream\n    public void writeInt(int v) throws IOException {\n        os.write((v >>> 0) & 0xFF);\n        os.write((v >>> 8) & 0xFF);\n        os.write((v >>> 16) & 0xFF);\n        os.write((v >>> 24) & 0xFF);\n        incCount(4);\n    }\n\n    public void writeByte(byte b) throws IOException {\n        write(b);\n    }\n\n    public void writeByte(int n) throws IOException {\n        write(n);\n    }\n\n    public void writeBytes(byte... bytes) throws IOException {\n        write(bytes);\n    }\n\n    public void writeBytes(int... vals) throws IOException {\n        for (int v : vals) {\n            write(v);\n        }\n    }\n\n    public void write(byte[] b, int off, int len) throws IOException {\n        os.write(b, off, len);\n        incCount(len);\n    }\n\n    public void write(byte[] b) throws IOException {\n        os.write(b);\n        incCount(b.length);\n    }\n\n    public void write(int b) throws IOException {\n        os.write(b);\n        incCount(1);\n    }\n\n    /////////////////////////////////////////////////\n\n    public void close() throws IOException {\n        os.close();\n    }\n\n    public void flush() throws IOException {\n        os.flush();\n    }\n\n    public boolean equals(Object obj) {\n        return os.equals(obj);\n    }\n\n    public int hashCode() {\n        return os.hashCode();\n    }\n\n    public String toString() {\n        return os.toString();\n    }\n\n    public int getWritten() {\n        return written;\n    }\n\n}\n"
  },
  {
    "path": "modules/book/src/main/java/me/ag2s/umdlib/umd/UmdReader.java",
    "content": "package me.ag2s.umdlib.umd;\n\n\nimport androidx.annotation.NonNull;\n\nimport java.io.IOException;\nimport java.io.InputStream;\n\nimport me.ag2s.umdlib.domain.UmdBook;\nimport me.ag2s.umdlib.domain.UmdCover;\nimport me.ag2s.umdlib.domain.UmdHeader;\nimport me.ag2s.umdlib.tool.StreamReader;\nimport me.ag2s.umdlib.tool.UmdUtils;\n\n/**\n * UMD格式的电子书解析\n * 格式规范参考：\n * http://blog.sina.com.cn/s/blog_7c8dc2d501018o5d.html\n * http://blog.sina.com.cn/s/blog_7c8dc2d501018o5l.html\n */\npublic class UmdReader {\n    UmdBook book;\n    InputStream inputStream;\n    int _AdditionalCheckNumber;\n    int _TotalContentLen;\n    boolean end = false;\n\n\n    public synchronized UmdBook read(InputStream inputStream) throws Exception {\n\n        book = new UmdBook();\n        this.inputStream = inputStream;\n        StreamReader reader = new StreamReader(inputStream);\n        UmdHeader umdHeader = new UmdHeader();\n        book.setHeader(umdHeader);\n        if (reader.readIntLe() != 0xde9a9b89) {\n            throw new IOException(\"Wrong header\");\n        }\n        short num1 = -1;\n        byte ch = reader.readByte();\n        while (ch == 35) {\n            //int num2=reader.readByte();\n            short segType = reader.readShortLe();\n            byte segFlag = reader.readByte();\n            short len = (short) (reader.readUint8() - 5);\n\n            System.out.println(\"块标识:\" + segType);\n            //short length1 = reader.readByte();\n            readSection(segType, segFlag, len, reader, umdHeader);\n\n            if ((int) segType == 241 || (int) segType == 10) {\n                segType = num1;\n            }\n            for (ch = reader.readByte(); ch == 36; ch = reader.readByte()) {\n                //int num3 = reader.readByte();\n                System.out.println(ch);\n                int additionalCheckNumber = reader.readIntLe();\n                int length2 = (reader.readIntLe() - 9);\n                readAdditionalSection(segType, additionalCheckNumber, length2, reader);\n            }\n            num1 = segType;\n\n        }\n        System.out.println(book.getHeader().toString());\n        return book;\n\n    }\n\n    private void readAdditionalSection(short segType, int additionalCheckNumber, int length, StreamReader reader) throws Exception {\n        switch (segType) {\n            case 14:\n                //this._TotalImageList.Add((object) Image.FromStream((Stream) new MemoryStream(reader.ReadBytes((int) length))));\n                break;\n            case 15:\n                //this._TotalImageList.Add((object) Image.FromStream((Stream) new MemoryStream(reader.ReadBytes((int) length))));\n                break;\n            case 129:\n                reader.readBytes(length);\n                break;\n            case 130:\n                //byte[] covers = reader.readBytes(length);\n                book.setCover(new UmdCover(reader.readBytes(length)));\n                //this._Book.Cover = BitmapImage.FromStream((Stream) new MemoryStream(reader.ReadBytes((int) length)));\n                break;\n            case 131:\n                System.out.println(length / 4);\n                book.setNum(length / 4);\n                for (int i = 0; i < length / 4; ++i) {\n                    book.getChapters().addContentLength(reader.readIntLe());\n                }\n                break;\n            case 132:\n                //System.out.println(length/4);\n                System.out.println(_AdditionalCheckNumber);\n                System.out.println(additionalCheckNumber);\n                if (this._AdditionalCheckNumber != additionalCheckNumber) {\n                    System.out.println(length);\n                    book.getChapters().contents.write(UmdUtils.decompress(reader.readBytes(length)));\n                    book.getChapters().contents.flush();\n                    break;\n                } else {\n                    for (int i = 0; i < book.getNum(); i++) {\n                        short len = reader.readUint8();\n                        byte[] title = reader.readBytes(len);\n                        //System.out.println(UmdUtils.unicodeBytesToString(title));\n                        book.getChapters().addTitle(title);\n                    }\n                }\n\n\n                break;\n            default:\n                    /*Console.WriteLine(\"未知内容\");\n                    Console.WriteLine(\"Seg Type = \" + (object) segType);\n                    Console.WriteLine(\"Seg Len = \" + (object) length);\n                    Console.WriteLine(\"content = \" + (object) reader.ReadBytes((int) length));*/\n                break;\n        }\n    }\n\n    public void readSection(short segType, byte segFlag, short length, StreamReader reader, UmdHeader header) throws IOException {\n        switch (segType) {\n            case 1://umd文件头 DCTS_CMD_ID_VERSION\n                header.setUmdType(reader.readByte());\n                reader.readBytes(2);//Random 2\n                System.out.println(\"UMD文件类型:\" + header.getUmdType());\n                break;\n            case 2://文件标题 DCTS_CMD_ID_TITLE\n                header.setTitle(UmdUtils.unicodeBytesToString(reader.readBytes(length)));\n                System.out.println(\"文件标题:\" + header.getTitle());\n                break;\n            case 3://作者\n                header.setAuthor(UmdUtils.unicodeBytesToString(reader.readBytes(length)));\n                System.out.println(\"作者:\" + header.getAuthor());\n                break;\n            case 4://年\n                header.setYear(UmdUtils.unicodeBytesToString(reader.readBytes(length)));\n                System.out.println(\"年:\" + header.getYear());\n                break;\n            case 5://月\n                header.setMonth(UmdUtils.unicodeBytesToString(reader.readBytes(length)));\n                System.out.println(\"月:\" + header.getMonth());\n                break;\n            case 6://日\n                header.setDay(UmdUtils.unicodeBytesToString(reader.readBytes(length)));\n                System.out.println(\"日:\" + header.getDay());\n                break;\n            case 7://小说类型\n                header.setBookType(UmdUtils.unicodeBytesToString(reader.readBytes(length)));\n                System.out.println(\"小说类型:\" + header.getBookType());\n                break;\n            case 8://出版商\n                header.setBookMan(UmdUtils.unicodeBytesToString(reader.readBytes(length)));\n                System.out.println(\"出版商:\" + header.getBookMan());\n                break;\n            case 9:// 零售商\n                header.setShopKeeper(UmdUtils.unicodeBytesToString(reader.readBytes(length)));\n                System.out.println(\"零售商:\" + header.getShopKeeper());\n                break;\n            case 10://CONTENT ID\n                System.out.println(\"CONTENT ID:\" + reader.readHex(length));\n                break;\n            case 11:\n                //内容长度 DCTS_CMD_ID_FILE_LENGTH\n                _TotalContentLen = reader.readIntLe();\n                book.getChapters().setTotalContentLen(_TotalContentLen);\n                System.out.println(\"内容长度:\" + _TotalContentLen);\n                break;\n            case 12://UMD文件结束\n                end = true;\n                int num2 = reader.readIntLe();\n                System.out.println(\"整个文件长度\" + num2);\n                break;\n            case 13:\n                break;\n            case 14:\n                int num3 = reader.readByte();\n                break;\n            case 15:\n                reader.readBytes(length);\n                break;\n            case 129://正文\n            case 131://章节偏移\n                _AdditionalCheckNumber = reader.readIntLe();\n                System.out.println(\"章节偏移:\" + _AdditionalCheckNumber);\n                break;\n            case 132://章节标题，正文\n                _AdditionalCheckNumber = reader.readIntLe();\n                System.out.println(\"章节标题，正文:\" + _AdditionalCheckNumber);\n                break;\n            case 130://封面（jpg）\n                int num4 = reader.readByte();\n                _AdditionalCheckNumber = reader.readIntLe();\n                break;\n            case 135://页面偏移（Page Offset）\n                reader.readUint8();//fontSize 一字节 字体大小\n                reader.readUint8();//screenWidth 屏幕宽度\n                reader.readBytes(4);//BlockRandom 指向一个页面偏移数据块\n                break;\n            case 240://CDS KEY\n                break;\n            case 241://许可证(LICENCE KEY)\n                //System.out.println(\"整个文件长度\" + length);\n                System.out.println(\"许可证(LICENCE KEY):\" + reader.readHex(16));\n                break;\n            default:\n                if (length > 0) {\n                    byte[] numArray = reader.readBytes(length);\n                }\n\n\n        }\n    }\n\n\n    @Override\n    @NonNull\n    public String toString() {\n        return \"UmdReader{\" +\n                \"book=\" + book +\n                '}';\n    }\n}\n"
  },
  {
    "path": "modules/book/src/main/resources/dtd/openebook.org/dtds/oeb-1.2/oeb12.ent",
    "content": "<!--\n\nTitle:\n\n     Mnemonic Character Entities For the Open eBook Publication\n     Structure Version 1.2\n\n\nVersion:\n\n     1.2\n\n\nRevision:\n\n     20020930-x  (supercedes 20020424-x)\n\n\nRevision History Note:\n\n     This revision, 20020930-x, which supercedes the prior revision\n     20020424-x, updates: 1) an email address within this comment\n     prologue, and 2) the Unicode version number referenced in various\n     comments throughout this document. No changes whatsoever were\n     made to the parsed content of this DTD fragment.\n\n\nPrevious Version:\n\n     1.0.1 (Revision of 22-November-2000, \"Character Entities for\n            the Open eBook Publication Structure Version 1.0.1\")\n\n\nAuthors:\n\n     Version 1.0; 1.0.1\n\n          Gunter Hille <hille@abc.de>\n          Ben Trafford <ben@legendary.org>\n          Garret Wilson <garret@globalmentor.com>\n\n\n     This Version 1.2 updated and edited by:\n\n          Jon Noring <jon@noring.name>\n\n\nUsage:\n\n     <!ENTITY % OEBEntities\n              PUBLIC \"+//ISBN 0-9673008-1-9//DTD OEB 1.2 Entities//EN\"\n              \"http://openebook.org/dtds/oeb-1.2/oeb12.ent\">\n\n     %OEBEntities;\n\n\nSummary:\n\n     This DTD fragment exactly duplicates, with some reorganization,\n     correction, and reformatting of the descriptive text, the 253\n     character entity declarations in the XHTML 1.1 DTD. Refer to:\n\n          http://www.w3.org/TR/xhtml1/DTD/xhtml-lat1.ent\n          http://www.w3.org/TR/xhtml1/DTD/xhtml-symbol.ent\n          http://www.w3.org/TR/xhtml1/DTD/xhtml-special.ent\n\n\nRelation to OEBPS Version 1.0.1:\n\n     The 253 character entities declared herein include all 249 from\n     Version 1.0.1 plus four of the five pre-defined XML 1.0 character\n     entities of &amp;, &lt;, &gt;, &quot; (the fifth pre-defined XML\n     character entity, &apos;, is one of the 249 character entities\n     already declared in Version 1.0.1.)\n\n     The five pre-defined XML 1.0 character entities are included for\n     completeness and interoperability as recommended by W3C, and to\n     follow XHTML practice. (Further information on the purpose and\n     usage of these five pre-defined XML character entities, and the\n     normative reference, is given in the Usage Note below.)\n\n\nRelation to Unicode 3.2.0 and ISO/IEC 10646:\n\n     The mnemonic character entities declared herein substitute for\n     numeric character references, the numeric values for the\n     associated characters specified by Unicode (in turn, the Unicode\n     Character Data Set conforms with the ISO/IEC 10646 character set\n     which XML 1.0 specifies.) The current version of Unicode is\n     3.2.0. General information on Unicode, including information on\n     the latest version, is found at\n\n          http://www.unicode.org/\n\n     In addition, Unicode has categorized the massive number of\n     characters in its Character Database using two different systems:\n     Character Blocks and Script Names. These two systems are used\n     herein for general categorization of the 253 character entities.\n     The text files listing the code points for these two systems are:\n\n          http://www.unicode.org/Public/UNIDATA/Blocks.txt\n          http://www.unicode.org/Public/UNIDATA/Scripts.txt\n\n\nTutorial Note to Document Authors: Character Entity Usage\n\n     To insert the desired special character into the content of an\n     OEBPS Document or Package file (which are XML documents), prefix\n     the associated mnemonic character entity with the '&' character\n     and terminate with the ';' character.\n\n     Example: to insert the \"em dash\" character (which has the\n     mnemonic 'mdash'), use &mdash; .\n\n     If preferred, the character can instead be inserted using the\n     direct (Unicode) numerical character reference, the codes of\n     which are given herein (see the above note on Unicode.) So, for\n     the \"em dash\" character one can use, instead of &mdash;, either\n     the decimal &#8212; or the hexadecimal equivalent &#x2014; .\n\n     Importantly note that within the content (PCDATA) of all OEBPS\n     documents and package files, the special XML characters '&' and\n     '<', when intended to be used literally, MUST be represented with\n     the mnemonic character entities of &amp; and &lt; (or the numerical\n     character entity equivalents), respectively. In addition, it is\n     considered good practice to use the &gt; (or numerical equivalent)\n     for the '>' symbol, although it is not necessary except in very\n     unusual and rare circumstances. The two other special XML character\n     entities, apostrophe (&apos;) and quote (&quot;), are only\n     necessary within element attribute values to literally represent\n     these characters, and for similar non-content purposes.\n\n     (The normative reference on the five XML pre-defined mnemonic\n     character entities is given in Sections 2.4 and 4.6 of the XML\n     1.0 Specification, Second Edition:\n\n          http://www.w3.org/TR/2000/REC-xml-20001006\n\n     )\n\n\n     +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n       Portions (C) International Organization for\n       Standardization 1986. Permission to copy in any\n       form is granted for use with conforming SGML\n       systems and applications as defined in ISO 8879,\n       provided this notice is included in all copies.\n     +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\n-->\n\n    <!--\n\n         +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n           XML 1.0 Pre-Defined Character Entities\n         +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+\n\n           Drawn From Unicode 3.2.0 Character Sets:\n\n                 Block Name(s):  Basic Latin              (U+0000 to U+007F)\n                Script Name(s):  (none)\n\n    -->\n\n\n    <!ENTITY quot       \"&#34;\" ><!-- quotation mark\n                                  APL quote\n                                  ==================== U+0022 ISOnum -->\n\n    <!ENTITY amp    \"&#38;#38;\" ><!-- ampersand\n                                  ==================== U+0026 ISOnum -->\n\n    <!ENTITY apos       \"&#39;\" ><!-- apostrophe mark\n                                  ==================== U+0027 ISOnum -->\n\n    <!ENTITY lt     \"&#38;#60;\" ><!-- less-than sign\n                                  ==================== U+003C ISOnum -->\n\n    <!ENTITY gt         \"&#62;\" ><!-- greater-than sign\n                                  ==================== U+003E ISOnum -->\n\n\n    <!--\n\n         +-+-+-+-+-+-+-+-+-+-+-+-+\n           Extended Latin Script\n         +-+-+-+-+-+-+-+-+-+-+-+-+\n\n           Drawn From Unicode 3.2.0 Character Sets:\n\n                 Block Name(s):  Latin-1 Supplement       (U+0080 to U+00FF)\n                                 Latin Extended-A         (U+0100 to U+017F)\n                                 Latin Extended-B         (U+0180 to U+024F)\n                Script Name(s):  Latin\n\n    -->\n\n\n    <!ENTITY ordf      \"&#170;\" ><!-- feminine ordinal indicator\n                                  ==================== U+00AA ISOnum -->\n\n    <!ENTITY ordm      \"&#186;\" ><!-- masculine ordinal indicator\n                                  ==================== U+00BA ISOnum -->\n\n    <!ENTITY Agrave    \"&#192;\" ><!-- Latin capital letter A with grave\n                                  Latin capital letter A grave\n                                  =================== U+00C0 ISOlat1 -->\n\n    <!ENTITY Aacute    \"&#193;\" ><!-- Latin capital letter A with acute\n                                  =================== U+00C1 ISOlat1 -->\n\n    <!ENTITY Acirc     \"&#194;\" ><!-- Latin capital letter A with circumflex\n                                  =================== U+00C2 ISOlat1 -->\n\n    <!ENTITY Atilde    \"&#195;\" ><!-- Latin capital letter A with tilde\n                                  =================== U+00C3 ISOlat1 -->\n\n    <!ENTITY Auml      \"&#196;\" ><!-- Latin capital letter A with diaeresis\n                                  =================== U+00C4 ISOlat1 -->\n\n    <!ENTITY Aring     \"&#197;\" ><!-- Latin capital letter A with ring above\n                                  Latin capital letter A ring\n                                  =================== U+00C5 ISOlat1 -->\n\n    <!ENTITY AElig     \"&#198;\" ><!-- Latin capital letter AE\n                                  Latin capital ligature AE\n                                  =================== U+00C6 ISOlat1 -->\n\n    <!ENTITY Ccedil    \"&#199;\" ><!-- Latin capital letter C with cedilla\n                                  =================== U+00C7 ISOlat1 -->\n\n    <!ENTITY Egrave    \"&#200;\" ><!-- Latin capital letter E with grave\n                                  =================== U+00C8 ISOlat1 -->\n\n    <!ENTITY Eacute    \"&#201;\" ><!-- Latin capital letter E with acute\n                                  =================== U+00C9 ISOlat1 -->\n\n    <!ENTITY Ecirc     \"&#202;\" ><!-- Latin capital letter E with circumflex\n                                  =================== U+00CA ISOlat1 -->\n\n    <!ENTITY Euml      \"&#203;\" ><!-- Latin capital letter E with diaeresis\n                                  =================== U+00CB ISOlat1 -->\n\n    <!ENTITY Igrave    \"&#204;\" ><!-- Latin capital letter I with grave\n                                  =================== U+00CC ISOlat1 -->\n\n    <!ENTITY Iacute    \"&#205;\" ><!-- Latin capital letter I with acute\n                                  =================== U+00CD ISOlat1 -->\n\n    <!ENTITY Icirc     \"&#206;\" ><!-- Latin capital letter I with circumflex\n                                  =================== U+00CE ISOlat1 -->\n\n    <!ENTITY Iuml      \"&#207;\" ><!-- Latin capital letter I with diaeresis\n                                  =================== U+00CF ISOlat1 -->\n\n    <!ENTITY ETH       \"&#208;\" ><!-- Latin capital letter ETH\n                                  =================== U+00D0 ISOlat1 -->\n\n    <!ENTITY Ntilde    \"&#209;\" ><!-- Latin capital letter N with tilde\n                                  =================== U+00D1 ISOlat1 -->\n\n    <!ENTITY Ograve    \"&#210;\" ><!-- Latin capital letter O with grave\n                                  =================== U+00D2 ISOlat1 -->\n\n    <!ENTITY Oacute    \"&#211;\" ><!-- Latin capital letter O with acute\n                                  =================== U+00D3 ISOlat1 -->\n\n    <!ENTITY Ocirc     \"&#212;\" ><!-- Latin capital letter O with circumflex\n                                  =================== U+00D4 ISOlat1 -->\n\n    <!ENTITY Otilde    \"&#213;\" ><!-- Latin capital letter O with tilde\n                                  =================== U+00D5 ISOlat1 -->\n\n    <!ENTITY Ouml      \"&#214;\" ><!-- Latin capital letter O with diaeresis\n                                  =================== U+00D6 ISOlat1 -->\n\n    <!ENTITY Oslash    \"&#216;\" ><!-- Latin capital letter O with stroke\n                                  Latin capital letter O slash\n                                  =================== U+00D8 ISOlat1 -->\n\n    <!ENTITY Ugrave    \"&#217;\" ><!-- Latin capital letter U with grave\n                                  =================== U+00D9 ISOlat1 -->\n\n    <!ENTITY Uacute    \"&#218;\" ><!-- Latin capital letter U with acute\n                                  =================== U+00DA ISOlat1 -->\n\n    <!ENTITY Ucirc     \"&#219;\" ><!-- Latin capital letter U with circumflex\n                                  =================== U+00DB ISOlat1 -->\n\n    <!ENTITY Uuml      \"&#220;\" ><!-- Latin capital letter U with diaeresis\n                                  =================== U+00DC ISOlat1 -->\n\n    <!ENTITY Yacute    \"&#221;\" ><!-- Latin capital letter Y with acute\n                                  =================== U+00DD ISOlat1 -->\n\n    <!ENTITY THORN     \"&#222;\" ><!-- Latin capital letter THORN\n                                  =================== U+00DE ISOlat1 -->\n\n    <!ENTITY szlig     \"&#223;\" ><!-- Latin small letter sharp s\n                                  ess-zed\n                                  =================== U+00DF ISOlat1 -->\n\n    <!ENTITY agrave    \"&#224;\" ><!-- Latin small letter a with grave\n                                  Latin small letter a grave\n                                  =================== U+00E0 ISOlat1 -->\n\n    <!ENTITY aacute    \"&#225;\" ><!-- Latin small letter a with acute\n                                  =================== U+00E1 ISOlat1 -->\n\n    <!ENTITY acirc     \"&#226;\" ><!-- Latin small letter a with circumflex\n                                  =================== U+00E2 ISOlat1 -->\n\n    <!ENTITY atilde    \"&#227;\" ><!-- Latin small letter a with tilde\n                                  =================== U+00E3 ISOlat1 -->\n\n    <!ENTITY auml      \"&#228;\" ><!-- Latin small letter a with diaeresis\n                                  =================== U+00E4 ISOlat1 -->\n\n    <!ENTITY aring     \"&#229;\" ><!-- Latin small letter a with ring above\n                                  Latin small letter a ring\n                                  =================== U+00E5 ISOlat1 -->\n\n    <!ENTITY aelig     \"&#230;\" ><!-- Latin small letter ae\n                                  Latin small ligature ae\n                                  =================== U+00E6 ISOlat1 -->\n\n    <!ENTITY ccedil    \"&#231;\" ><!-- Latin small letter c with cedilla\n                                  =================== U+00E7 ISOlat1 -->\n\n    <!ENTITY egrave    \"&#232;\" ><!-- Latin small letter e with grave\n                                  =================== U+00E8 ISOlat1 -->\n\n    <!ENTITY eacute    \"&#233;\" ><!-- Latin small letter e with acute\n                                  =================== U+00E9 ISOlat1 -->\n\n    <!ENTITY ecirc     \"&#234;\" ><!-- Latin small letter e with circumflex\n                                  =================== U+00EA ISOlat1 -->\n\n    <!ENTITY euml      \"&#235;\" ><!-- Latin small letter e with diaeresis\n                                  =================== U+00EB ISOlat1 -->\n\n    <!ENTITY igrave    \"&#236;\" ><!-- Latin small letter i with grave\n                                  =================== U+00EC ISOlat1 -->\n\n    <!ENTITY iacute    \"&#237;\" ><!-- Latin small letter i with acute\n                                  =================== U+00ED ISOlat1 -->\n\n    <!ENTITY icirc     \"&#238;\" ><!-- Latin small letter i with circumflex\n                                  =================== U+00EE ISOlat1 -->\n\n    <!ENTITY iuml      \"&#239;\" ><!-- Latin small letter i with diaeresis\n                                  =================== U+00EF ISOlat1 -->\n\n    <!ENTITY eth       \"&#240;\" ><!-- Latin small letter eth\n                                  =================== U+00F0 ISOlat1 -->\n\n    <!ENTITY ntilde    \"&#241;\" ><!-- Latin small letter n with tilde\n                                  =================== U+00F1 ISOlat1 -->\n\n    <!ENTITY ograve    \"&#242;\" ><!-- Latin small letter o with grave\n                                  =================== U+00F2 ISOlat1 -->\n\n    <!ENTITY oacute    \"&#243;\" ><!-- Latin small letter o with acute\n                                  =================== U+00F3 ISOlat1 -->\n\n    <!ENTITY ocirc     \"&#244;\" ><!-- Latin small letter o with circumflex\n                                  =================== U+00F4 ISOlat1 -->\n\n    <!ENTITY otilde    \"&#245;\" ><!-- Latin small letter o with tilde\n                                  =================== U+00F5 ISOlat1 -->\n\n    <!ENTITY ouml      \"&#246;\" ><!-- Latin small letter o with diaeresis\n                                  =================== U+00F6 ISOlat1 -->\n\n    <!ENTITY oslash    \"&#248;\" ><!-- Latin small letter o with stroke\n                                  Latin small letter o slash\n                                  =================== U+00F8 ISOlat1 -->\n\n    <!ENTITY ugrave    \"&#249;\" ><!-- Latin small letter u with grave\n                                  =================== U+00F9 ISOlat1 -->\n\n    <!ENTITY uacute    \"&#250;\" ><!-- Latin small letter u with acute\n                                  =================== U+00FA ISOlat1 -->\n\n    <!ENTITY ucirc     \"&#251;\" ><!-- Latin small letter u with circumflex\n                                  =================== U+00FB ISOlat1 -->\n\n    <!ENTITY uuml      \"&#252;\" ><!-- Latin small letter u with diaeresis\n                                  =================== U+00FC ISOlat1 -->\n\n    <!ENTITY yacute    \"&#253;\" ><!-- Latin small letter y with acute\n                                  =================== U+00FD ISOlat1 -->\n\n    <!ENTITY thorn     \"&#254;\" ><!-- Latin small letter thorn with\n                                  =================== U+00FE ISOlat1 -->\n\n    <!ENTITY yuml      \"&#255;\" ><!-- Latin small letter y with diaeresis\n                                  =================== U+00FF ISOlat1 -->\n\n    <!ENTITY OElig     \"&#338;\" ><!-- Latin capital ligature OE\n                                  =================== U+0152 ISOlat2 -->\n\n    <!ENTITY oelig     \"&#339;\" ><!-- Latin small ligature oe\n                                  =================== U+0153 ISOlat2 -->\n\n    <!ENTITY Scaron    \"&#352;\" ><!-- Latin capital letter S with caron\n                                  =================== U+0160 ISOlat2 -->\n\n    <!ENTITY scaron    \"&#353;\" ><!-- Latin small letter s with caron\n                                  =================== U+0161 ISOlat2 -->\n\n    <!ENTITY Yuml      \"&#376;\" ><!-- Latin capital letter Y with diaeresis\n                                  =================== U+0178 ISOlat2 -->\n\n    <!ENTITY fnof      \"&#402;\" ><!-- Latin small f with hook\n                                  function\n                                  florin\n                                  =================== U+0192 ISOtech -->\n\n\n    <!--\n\n         +-+-+-+-+-+-+-+\n           Greek Script\n         +-+-+-+-+-+-+-+\n\n           Drawn From Unicode 3.2.0 Character Sets:\n\n                 Block Name(s):  Greek                    (U+0370 to U+03FF)\n                Script Name(s):  Greek\n\n    -->\n\n\n    <!ENTITY Alpha     \"&#913;\" ><!-- Greek capital letter alpha\n                                  =========================== U+0391 -->\n\n    <!ENTITY Beta      \"&#914;\" ><!-- Greek capital letter beta\n                                  =========================== U+0392 -->\n\n    <!ENTITY Gamma     \"&#915;\" ><!-- Greek capital letter gamma\n                                  =================== U+0393 ISOgrk3 -->\n\n    <!ENTITY Delta     \"&#916;\" ><!-- Greek capital letter delta\n                                  =================== U+0394 ISOgrk3 -->\n\n    <!ENTITY Epsilon   \"&#917;\" ><!-- Greek capital letter epsilon\n                                  =========================== U+0395 -->\n\n    <!ENTITY Zeta      \"&#918;\" ><!-- Greek capital letter zeta\n                                  =========================== U+0396 -->\n\n    <!ENTITY Eta       \"&#919;\" ><!-- Greek capital letter eta\n                                  =========================== U+0397 -->\n\n    <!ENTITY Theta     \"&#920;\" ><!-- Greek capital letter theta\n                                  =================== U+0398 ISOgrk3 -->\n\n    <!ENTITY Iota      \"&#921;\" ><!-- Greek capital letter iota\n                                  =========================== U+0399 -->\n\n    <!ENTITY Kappa     \"&#922;\" ><!-- Greek capital letter kappa\n                                  =========================== U+039A -->\n\n    <!ENTITY Lambda    \"&#923;\" ><!-- Greek capital letter lambda\n                                  =================== U+039B ISOgrk3 -->\n\n    <!ENTITY Mu        \"&#924;\" ><!-- Greek capital letter mu\n                                  =========================== U+039C -->\n\n    <!ENTITY Nu        \"&#925;\" ><!-- Greek capital letter nu\n                                  =========================== U+039D -->\n\n    <!ENTITY Xi        \"&#926;\" ><!-- Greek capital letter xi\n                                  =================== U+039E ISOgrk3 -->\n\n    <!ENTITY Omicron   \"&#927;\" ><!-- Greek capital letter omicron\n                                  =========================== U+039F -->\n\n    <!ENTITY Pi        \"&#928;\" ><!-- Greek capital letter pi\n                                  =================== U+03A0 ISOgrk3 -->\n\n    <!ENTITY Rho       \"&#929;\" ><!-- Greek capital letter rho\n                                  =========================== U+03A1 -->\n\n    <!ENTITY Sigma     \"&#931;\" ><!-- Greek capital letter sigma\n                                  =================== U+03A3 ISOgrk3 -->\n\n    <!ENTITY Tau       \"&#932;\" ><!-- Greek capital letter tau\n                                  =========================== U+03A4 -->\n\n    <!ENTITY Upsilon   \"&#933;\" ><!-- Greek capital letter upsilon\n                                  =================== U+03A5 ISOgrk3 -->\n\n    <!ENTITY Phi       \"&#934;\" ><!-- Greek capital letter phi\n                                  =================== U+03A6 ISOgrk3 -->\n\n    <!ENTITY Chi       \"&#935;\" ><!-- Greek capital letter chi\n                                  =========================== U+03A7 -->\n\n    <!ENTITY Psi       \"&#936;\" ><!-- Greek capital letter psi\n                                  =================== U+03A8 ISOgrk3 -->\n\n    <!ENTITY Omega     \"&#937;\" ><!-- Greek capital letter omega\n                                  =================== U+03A9 ISOgrk3 -->\n\n    <!ENTITY alpha     \"&#945;\" ><!-- Greek small letter alpha\n                                  =================== U+03B1 ISOgrk3 -->\n\n    <!ENTITY beta      \"&#946;\" ><!-- Greek small letter beta\n                                  =================== U+03B2 ISOgrk3 -->\n\n    <!ENTITY gamma     \"&#947;\" ><!-- Greek small letter gamma\n                                  =================== U+03B3 ISOgrk3 -->\n\n    <!ENTITY delta     \"&#948;\" ><!-- Greek small letter delta\n                                  =================== U+03B4 ISOgrk3 -->\n\n    <!ENTITY epsilon   \"&#949;\" ><!-- Greek small letter epsilon\n                                  =================== U+03B5 ISOgrk3 -->\n\n    <!ENTITY zeta      \"&#950;\" ><!-- Greek small letter zeta\n                                  =================== U+03B6 ISOgrk3 -->\n\n    <!ENTITY eta       \"&#951;\" ><!-- Greek small letter eta\n                                  =================== U+03B7 ISOgrk3 -->\n\n    <!ENTITY theta     \"&#952;\" ><!-- Greek small letter theta\n                                  =================== U+03B8 ISOgrk3 -->\n\n    <!ENTITY iota      \"&#953;\" ><!-- Greek small letter iota\n                                  =================== U+03B9 ISOgrk3 -->\n\n    <!ENTITY kappa     \"&#954;\" ><!-- Greek small letter kappa\n                                  =================== U+03BA ISOgrk3 -->\n\n    <!ENTITY lambda    \"&#955;\" ><!-- Greek small letter lambda\n                                  =================== U+03BB ISOgrk3 -->\n\n    <!ENTITY mu        \"&#956;\" ><!-- Greek small letter mu\n                                  =================== U+03BC ISOgrk3 -->\n\n    <!ENTITY nu        \"&#957;\" ><!-- Greek small letter nu\n                                  =================== U+03BD ISOgrk3 -->\n\n    <!ENTITY xi        \"&#958;\" ><!-- Greek small letter xi\n                                  =================== U+03BE ISOgrk3 -->\n\n    <!ENTITY omicron   \"&#959;\" ><!-- Greek small letter omicron\n                                  ======================= U+03BF NEW -->\n\n    <!ENTITY pi        \"&#960;\" ><!-- Greek small letter pi\n                                  =================== U+03C0 ISOgrk3 -->\n\n    <!ENTITY rho       \"&#961;\" ><!-- Greek small letter rho\n                                  =================== U+03C1 ISOgrk3 -->\n\n    <!ENTITY sigmaf    \"&#962;\" ><!-- Greek small letter final sigma\n                                  =================== U+03C2 ISOgrk3 -->\n\n    <!ENTITY sigma     \"&#963;\" ><!-- Greek small letter sigma\n                                  =================== U+03C3 ISOgrk3 -->\n\n    <!ENTITY tau       \"&#964;\" ><!-- Greek small letter tau\n                                  =================== U+03C4 ISOgrk3 -->\n\n    <!ENTITY upsilon   \"&#965;\" ><!-- Greek small letter upsilon\n                                  =================== U+03C5 ISOgrk3 -->\n\n    <!ENTITY phi       \"&#966;\" ><!-- Greek small letter phi\n                                  =================== U+03C6 ISOgrk3 -->\n\n    <!ENTITY chi       \"&#967;\" ><!-- Greek small letter chi\n                                  =================== U+03C7 ISOgrk3 -->\n\n    <!ENTITY psi       \"&#968;\" ><!-- Greek small letter psi\n                                  =================== U+03C8 ISOgrk3 -->\n\n    <!ENTITY omega     \"&#969;\" ><!-- Greek small letter omega\n                                  =================== U+03C9 ISOgrk3 -->\n\n    <!ENTITY thetasym  \"&#977;\" ><!-- Greek small letter theta symbol\n                                  ======================= U+03D1 NEW -->\n\n    <!ENTITY upsih     \"&#978;\" ><!-- Greek upsilon with hook symbol\n                                  ======================= U+03D2 NEW -->\n\n    <!ENTITY piv       \"&#982;\" ><!-- Greek pi symbol\n                                  =================== U+03D6 ISOgrk3 -->\n\n\n    <!--\n\n         +-+-+-+-+-+-+-+-+-+-+-+\n           General Punctuation\n         +-+-+-+-+-+-+-+-+-+-+-+\n\n           Drawn From Unicode 3.2.0 Character Sets:\n\n                 Block Name(s):  General Punctuation      (U+2000 to U+206F)\n                Script Name(s):  (none)\n\n    -->\n\n\n    <!ENTITY ensp     \"&#8194;\" ><!-- en space\n                                  ==================== U+2002 ISOpub -->\n\n    <!ENTITY emsp     \"&#8195;\" ><!-- em space\n                                  ==================== U+2003 ISOpub -->\n\n    <!ENTITY thinsp   \"&#8201;\" ><!-- thin space\n                                  ==================== U+2009 ISOpub -->\n\n    <!ENTITY zwnj     \"&#8204;\" ><!-- zero width non-joiner\n                                  ============== U+200C NEW RFC 2070 -->\n\n    <!ENTITY zwj      \"&#8205;\" ><!-- zero width joiner\n                                  ============== U+200D NEW RFC 2070 -->\n\n    <!ENTITY lrm      \"&#8206;\" ><!-- left-to-right mark\n                                  ============== U+200E NEW RFC 2070 -->\n\n    <!ENTITY rlm      \"&#8207;\" ><!-- right-to-left mark\n                                  ============== U+200F NEW RFC 2070 -->\n\n    <!ENTITY ndash    \"&#8211;\" ><!-- en dash\n                                  ==================== U+2013 ISOpub -->\n\n    <!ENTITY mdash    \"&#8212;\" ><!-- em dash\n                                  ==================== U+2014 ISOpub -->\n\n    <!ENTITY lsquo    \"&#8216;\" ><!-- left single quotation mark\n                                  ==================== U+2018 ISOnum -->\n\n    <!ENTITY rsquo    \"&#8217;\" ><!-- right single quotation mark\n                                  ==================== U+2019 ISOnum -->\n\n    <!ENTITY sbquo    \"&#8218;\" ><!-- single low-9 quotation mark\n                                  ======================= U+201A NEW -->\n\n    <!ENTITY ldquo    \"&#8220;\" ><!-- left double quotation mark\n                                  ==================== U+201C ISOnum -->\n\n    <!ENTITY rdquo    \"&#8221;\" ><!-- right double quotation mark\n                                  ==================== U+201D ISOnum -->\n\n    <!ENTITY bdquo    \"&#8222;\" ><!-- double low-9 quotation mark\n                                  ======================= U+201E NEW -->\n\n    <!ENTITY dagger   \"&#8224;\" ><!-- dagger\n                                  ==================== U+2020 ISOpub -->\n\n    <!ENTITY Dagger   \"&#8225;\" ><!-- double dagger\n                                  ==================== U+2021 ISOpub -->\n\n    <!ENTITY bull     \"&#8226;\" ><!-- bullet\n                                  black small circle\n                                  ==================== U+2022 ISOpub -->\n    <!-- bullet is NOT the same as U+2219,\n        'bullet operator' -->\n\n    <!ENTITY hellip   \"&#8230;\" ><!-- horizontal ellipsis\n                                  three dot leader\n                                  ==================== U+2026 ISOpub -->\n\n    <!ENTITY permil   \"&#8240;\" ><!-- per mille sign\n                                  =================== U+2030 ISOtech -->\n\n    <!ENTITY prime    \"&#8242;\" ><!-- prime\n                                  minutes\n                                  feet\n                                  =================== U+2032 ISOtech -->\n\n    <!ENTITY Prime    \"&#8243;\" ><!-- double prime\n                                  seconds\n                                  inches\n                                  =================== U+2033 ISOtech -->\n\n    <!ENTITY lsaquo   \"&#8249;\" ><!-- single left-pointing angle quotation\n                                       mark\n                                  ============== U+2039 ISO proposed -->\n\n    <!ENTITY rsaquo   \"&#8250;\" ><!-- single right-pointing angle quotation\n                                  ============== U+203A ISO proposed -->\n\n    <!ENTITY oline    \"&#8254;\" ><!-- overline\n                                  spacing overscore\n                                  ======================= U+203E NEW -->\n\n    <!ENTITY frasl    \"&#8260;\" ><!-- fraction slash\n                                  ======================= U+2044 NEW -->\n\n\n    <!--\n\n         +-+-+-+-+-+-+-+-+-+-+\n           Spacing Modifiers\n         +-+-+-+-+-+-+-+-+-+-+\n\n           Drawn From Unicode 3.2.0 Character Sets:\n\n                 Block Name(s):  Spacing Modifier Letters (U+0280 to U+02FF)\n                Script Name(s):  (none)\n\n           Note: The Spacing Modifier Letters are an unusual class of\n                 characters. They are an assorted collection of small signs\n                 used to indicate modifications of the preceding or\n                 following character, and sometimes to be an independent\n                 character. They differ from diacritical marks in that they\n                 are treated as free-standing, independent characters, which\n                 form part of the word and do not break up the word. They\n                 have the \"letter\" property. Most of the characters are\n                 phonetic modifiers. For further information, refer to\n                 Section 7.8 of the Unicode 3.2 manual, an online version is\n                 at http://www.unicode.org/unicode/uni2book/ch07.pdf .\n\n    -->\n\n\n    <!ENTITY circ      \"&#710;\" ><!-- modifier letter circumflex accent\n                                  ==================== U+02C6 ISOpub -->\n\n    <!ENTITY tilde     \"&#732;\" ><!-- small tilde\n                                  ==================== U+02DC ISOdia -->\n\n\n    <!--\n\n         +-+-+-+-+-+-+-+-+-+\n           Various Symbols\n         +-+-+-+-+-+-+-+-+-+\n\n           Drawn From Unicode 3.2.0 Character Sets:\n\n                 Block Name(s):  Latin-1 Supplement       (U+0080 to U+00FF)\n                                 Currency Symbols         (U+20A0 to U+20CF)\n                                 Letterlike Symbols       (U+2100 to U+214F)\n                                 Arrows                   (U+2190 to U+21FF)\n                                 Mathematical Operators   (U+2200 to U+22FF)\n                                 Miscellaneous Technical  (U+2300 to U+23FF)\n                                 Geometric Shapes         (U+25A0 to U+25FF)\n                                 Miscellaneous Symbols    (U+2600 to U+26FF)\n                Script Name(s):  (none, except Greek for \"micro\", U+00B5)\n\n    -->\n\n\n    <!ENTITY nbsp      \"&#160;\" ><!-- no-break space\n                                  non-breaking space\n                                  ==================== U+00A0 ISOnum -->\n\n    <!ENTITY iexcl     \"&#161;\" ><!-- inverted exclamation mark\n                                  ==================== U+00A1 ISOnum -->\n\n    <!ENTITY cent      \"&#162;\" ><!-- cent sign\n                                  ==================== U+00A2 ISOnum -->\n\n    <!ENTITY pound     \"&#163;\" ><!-- pound sign\n                                  ==================== U+00A3 ISOnum -->\n\n    <!ENTITY curren    \"&#164;\" ><!-- currency sign\n                                  ==================== U+00A4 ISOnum -->\n\n    <!ENTITY yen       \"&#165;\" ><!-- yen sign\n                                  yuan sign\n                                  ==================== U+00A5 ISOnum -->\n\n    <!ENTITY brvbar    \"&#166;\" ><!-- broken bar\n                                  broken vertical bar\n                                  ==================== U+00A6 ISOnum -->\n\n    <!ENTITY sect      \"&#167;\" ><!-- section sign\n                                  ==================== U+00A7 ISOnum -->\n\n    <!ENTITY uml       \"&#168;\" ><!-- diaeresis\n                                  spacing diaeresis\n                                  ==================== U+00A8 ISOdia -->\n\n    <!ENTITY copy      \"&#169;\" ><!-- copyright sign\n                                  ==================== U+00A9 ISOnum -->\n\n    <!ENTITY laquo     \"&#171;\" ><!-- left-pointing double angle quotation\n                                       mark\n                                  left pointing guillemet\n                                  ==================== U+00AB ISOnum -->\n\n    <!ENTITY not       \"&#172;\" ><!-- not sign\n                                  ==================== U+00AC ISOnum -->\n\n    <!ENTITY shy       \"&#173;\" ><!-- soft hyphen\n                                  discretionary hyphen\n                                  ==================== U+00AD ISOnum -->\n\n    <!ENTITY reg       \"&#174;\" ><!-- registered sign\n                                  registered trade mark sign\n                                  ==================== U+00AE ISOnum -->\n\n    <!ENTITY macr      \"&#175;\" ><!-- macron\n                                  spacing macron\n                                  overline\n                                  APL overbar\n                                  ==================== U+00AF ISOdia -->\n\n    <!ENTITY deg       \"&#176;\" ><!-- degree sign\n                                  ==================== U+00B0 ISOnum -->\n\n    <!ENTITY plusmn    \"&#177;\" ><!-- plus-minus sign\n                                  plus-or-minus sign\n                                  ==================== U+00B1 ISOnum -->\n\n    <!ENTITY sup2      \"&#178;\" ><!-- superscript two\n                                  superscript digit two\n                                  squared\n                                  ==================== U+00B2 ISOnum -->\n\n    <!ENTITY sup3      \"&#179;\" ><!-- superscript three\n                                  superscript digit three\n                                  cubed\n                                  ==================== U+00B3 ISOnum -->\n\n    <!ENTITY acute     \"&#180;\" ><!-- acute accent\n                                  spacing acute\n                                  ==================== U+00B4 ISOdia -->\n\n    <!ENTITY micro     \"&#181;\" ><!-- micro sign\n                                  ==================== U+00B5 ISOnum -->\n\n    <!ENTITY para      \"&#182;\" ><!-- pilcrow sign\n                                  paragraph sign\n                                  ==================== U+00B6 ISOnum -->\n\n    <!ENTITY middot    \"&#183;\" ><!-- middle dot\n                                  Georgian comma\n                                  Greek middle dot\n                                  ==================== U+00B7 ISOnum -->\n\n    <!ENTITY cedil     \"&#184;\" ><!-- cedilla\n                                  spacing cedilla\n                                  ==================== U+00B8 ISOdia -->\n\n    <!ENTITY sup1      \"&#185;\" ><!-- superscript one\n                                  superscript digit one\n                                  ==================== U+00B9 ISOnum -->\n\n    <!ENTITY raquo     \"&#187;\" ><!-- right-pointing double angle quotation\n                                       mark\n                                  right pointing guillemet\n                                  ==================== U+00BB ISOnum -->\n\n    <!ENTITY frac14    \"&#188;\" ><!-- vulgar fraction one quarter\n                                  fraction one quarter\n                                  ==================== U+00BC ISOnum -->\n\n    <!ENTITY frac12    \"&#189;\" ><!-- vulgar fraction one half\n                                  fraction one half\n                                  ==================== U+00BD ISOnum -->\n\n    <!ENTITY frac34    \"&#190;\" ><!-- vulgar fraction three quarters\n                                  fraction three quarters\n                                  ==================== U+00BE ISOnum -->\n\n    <!ENTITY iquest    \"&#191;\" ><!-- inverted question mark\n                                  turned question mark\n                                  ==================== U+00BF ISOnum -->\n\n    <!ENTITY times     \"&#215;\" ><!-- multiplication sign\n                                  ==================== U+00D7 ISOnum -->\n\n    <!ENTITY divide    \"&#247;\" ><!-- division sign\n                                  ==================== U+00F7 ISOnum -->\n\n    <!ENTITY euro     \"&#8364;\" ><!-- euro sign\n                                  ======================= U+20AC NEW -->\n\n    <!ENTITY image    \"&#8465;\" ><!-- blackletter capital I\n                                  imaginary part\n                                  =================== U+2111 ISOamso -->\n\n    <!ENTITY weierp   \"&#8472;\" ><!-- script capital P\n                                  power set\n                                  Weierstrass p\n                                  =================== U+2118 ISOamso -->\n\n    <!ENTITY real     \"&#8476;\" ><!-- blackletter capital R\n                                  real part symbol\n                                  =================== U+211C ISOamso -->\n\n    <!ENTITY trade    \"&#8482;\" ><!-- trade mark sign\n                                  ==================== U+2122 ISOnum -->\n\n    <!ENTITY alefsym  \"&#8501;\" ><!-- alef symbol\n                                  first transfinite cardinal\n                                  ======================= U+2135 NEW -->\n    <!-- alef symbol is NOT the same as\n         U+05D0, 'Hebrew letter alef',\n         although the same glyph could be\n         used to represent both -->\n\n    <!ENTITY larr     \"&#8592;\" ><!-- leftwards arrow\n                                  ==================== U+2190 ISOnum -->\n\n    <!ENTITY uarr     \"&#8593;\" ><!-- upwards arrow\n                                  ==================== U+2191 ISOnum -->\n\n    <!ENTITY rarr     \"&#8594;\" ><!-- rightwards arrow\n                                  ==================== U+2192 ISOnum -->\n\n    <!ENTITY darr     \"&#8595;\" ><!-- downwards arrow\n                                  ==================== U+2193 ISOnum -->\n\n    <!ENTITY harr     \"&#8596;\" ><!-- left right arrow\n                                  =================== U+2194 ISOamsa -->\n\n    <!ENTITY crarr    \"&#8629;\" ><!-- downwards arrow with corner leftwards\n                                  carriage return\n                                  ======================= U+21B5 NEW -->\n\n    <!ENTITY lArr     \"&#8656;\" ><!-- leftwards double arrow\n                                  =================== U+21D0 ISOtech -->\n    <!-- Unicode does not say that lArr is\n         the same as the 'is implied by'\n         arrow, but also does not have any\n         other character for that function.\n         As ISOtech suggests, lArr can be\n         used for 'is implied by'. -->\n\n    <!ENTITY uArr     \"&#8657;\" ><!-- upwards double arrow\n                                  =================== U+21D1 ISOamsa -->\n\n    <!ENTITY rArr     \"&#8658;\" ><!-- rightwards double arrow\n                                  =================== U+21D2 ISOtech -->\n    <!-- Unicode does not say that rArr is\n         the same as the 'implies' arrow,\n         but also does not have any other\n         character for that function. As\n         ISOtech suggests, rArr can be used\n         for 'implies'. -->\n\n    <!ENTITY dArr     \"&#8659;\" ><!-- downwards double arrow\n                                  =================== U+21D3 ISOamsa -->\n\n    <!ENTITY hArr     \"&#8660;\" ><!-- left right double arrow\n                                  =================== U+21D4 ISOamsa -->\n\n    <!ENTITY forall   \"&#8704;\" ><!-- for all\n                                  =================== U+2200 ISOtech -->\n\n    <!ENTITY part     \"&#8706;\" ><!-- partial differential\n                                  =================== U+2202 ISOtech -->\n\n    <!ENTITY exist    \"&#8707;\" ><!-- there exists\n                                  =================== U+2203 ISOtech -->\n\n    <!ENTITY empty    \"&#8709;\" ><!-- empty set\n                                  null set\n                                  diameter\n                                  =================== U+2205 ISOamso -->\n\n    <!ENTITY nabla    \"&#8711;\" ><!-- nabla\n                                  backward difference\n                                  =================== U+2207 ISOtech -->\n\n    <!ENTITY isin     \"&#8712;\" ><!-- element of\n                                  =================== U+2208 ISOtech -->\n\n    <!ENTITY notin    \"&#8713;\" ><!-- not an element of\n                                  =================== U+2209 ISOtech -->\n\n    <!ENTITY ni       \"&#8715;\" ><!-- contains as member\n                                  =================== U+220B ISOtech -->\n\n    <!ENTITY prod     \"&#8719;\" ><!-- n-ary product\n                                  product sign\n                                  =================== U+220F ISOamsb -->\n    <!-- prod is NOT the same character as\n         U+03A0, 'Greek capital letter pi',\n         although the same glyph could be\n         used to represent both -->\n\n    <!ENTITY sum      \"&#8721;\" ><!-- n-ary summation\n                                  =================== U+2211 ISOamsb -->\n    <!-- sum is NOT the same character as\n         U+03A3, 'Greek capital letter sigma',\n         although the same glyph could be\n         used to represent both -->\n\n    <!ENTITY minus    \"&#8722;\" ><!-- minus sign\n                                  =================== U+2212 ISOtech -->\n\n    <!ENTITY lowast   \"&#8727;\" ><!-- asterisk operator\n                                  =================== U+2217 ISOtech -->\n\n    <!ENTITY radic    \"&#8730;\" ><!-- square root\n                                  radical sign\n                                  =================== U+221A ISOtech -->\n\n    <!ENTITY prop     \"&#8733;\" ><!-- proportional to\n                                  =================== U+221D ISOtech -->\n\n    <!ENTITY infin    \"&#8734;\" ><!-- infinity\n                                  =================== U+221E ISOtech -->\n\n    <!ENTITY ang      \"&#8736;\" ><!-- angle\n                                  =================== U+2220 ISOamso -->\n\n    <!ENTITY and      \"&#8743;\" ><!-- logical and\n                                  wedge\n                                  =================== U+2227 ISOtech -->\n\n    <!ENTITY or       \"&#8744;\" ><!-- logical or\n                                  vee\n                                  =================== U+2228 ISOtech -->\n\n    <!ENTITY cap      \"&#8745;\" ><!-- intersection\n                                  cap\n                                  =================== U+2229 ISOtech -->\n\n    <!ENTITY cup      \"&#8746;\" ><!-- union\n                                  cup\n                                  =================== U+222A ISOtech -->\n\n    <!ENTITY int      \"&#8747;\" ><!-- integral\n                                  =================== U+222B ISOtech -->\n\n    <!ENTITY there4   \"&#8756;\" ><!-- therefore\n                                  =================== U+2234 ISOtech -->\n\n    <!ENTITY sim      \"&#8764;\" ><!-- tilde operator\n                                  varies with\n                                  similar to\n                                  =================== U+223C ISOtech -->\n    <!-- tilde operator is NOT the same\n         character as U+007E, 'tilde',\n         although the same glyph could be\n         used to represent both -->\n\n    <!ENTITY cong     \"&#8773;\" ><!-- approximately equal to\n                                  =================== U+2245 ISOtech -->\n\n    <!ENTITY asymp    \"&#8776;\" ><!-- almost equal to\n                                  asymptotic to\n                                  =================== U+2248 ISOamsr -->\n\n    <!ENTITY ne       \"&#8800;\" ><!-- not equal to\n                                  =================== U+2260 ISOtech -->\n\n    <!ENTITY equiv    \"&#8801;\" ><!-- identical to\n                                  =================== U+2261 ISOtech -->\n\n    <!ENTITY le       \"&#8804;\" ><!-- less-than or equal to\n                                  =================== U+2264 ISOtech -->\n\n    <!ENTITY ge       \"&#8805;\" ><!-- greater-than or equal to\n                                  =================== U+2265 ISOtech -->\n\n    <!ENTITY sub      \"&#8834;\" ><!-- subset of\n                                  =================== U+2282 ISOtech -->\n\n    <!ENTITY sup      \"&#8835;\" ><!-- superset of\n                                  =================== U+2283 ISOtech -->\n\n    <!ENTITY nsub     \"&#8836;\" ><!-- not a subset of\n                                  =================== U+2284 ISOamsn -->\n\n    <!ENTITY sube     \"&#8838;\" ><!-- subset of or equal to\n                                  =================== U+2286 ISOtech -->\n\n    <!ENTITY supe     \"&#8839;\" ><!-- superset of or equal to\n                                  =================== U+2287 ISOtech -->\n\n    <!ENTITY oplus    \"&#8853;\" ><!-- circled plus\n                                  direct sum\n                                  =================== U+2295 ISOamsb -->\n\n    <!ENTITY otimes   \"&#8855;\" ><!-- circled times\n                                  vector product\n                                  =================== U+2297 ISOamsb -->\n\n    <!ENTITY perp     \"&#8869;\" ><!-- up tack\n                                  orthogonal to\n                                  perpendicular\n                                  =================== U+22A5 ISOtech -->\n\n    <!ENTITY sdot     \"&#8901;\" ><!-- dot operator\n                                  =================== U+22C5 ISOamsb -->\n    <!-- dot operator is NOT the same\n         character as U+00B7, 'middle dot' -->\n\n    <!ENTITY lceil    \"&#8968;\" ><!-- left ceiling\n                                  APL upstile\n                                  =================== U+2308 ISOamsc -->\n\n    <!ENTITY rceil    \"&#8969;\" ><!-- right ceiling\n                                  =================== U+2309 ISOamsc -->\n\n    <!ENTITY lfloor   \"&#8970;\" ><!-- left floor\n                                  APL downstile\n                                  =================== U+230A ISOamsc -->\n\n    <!ENTITY rfloor   \"&#8971;\" ><!-- right floor\n                                  =================== U+230B ISOamsc -->\n\n    <!ENTITY lang     \"&#9001;\" ><!-- left-pointing angle bracket\n                                  bra\n                                  =================== U+2329 ISOtech -->\n    <!-- lang is NOT the same character as\n         U+003C, 'less than', or U+2039,\n        'single left-pointing angle quotation\n         mark' -->\n\n    <!ENTITY rang     \"&#9002;\" ><!-- right-pointing angle bracket\n                                  ket\n                                  =================== U+232A ISOtech -->\n    <!-- rang is NOT the same character as\n         U+003E, 'greater than', or U+203A,\n        'single right-pointing angle quotation\n         mark' -->\n\n    <!ENTITY loz      \"&#9674;\" ><!-- lozenge\n                                  ==================== U+25CA ISOpub -->\n\n    <!ENTITY spades   \"&#9824;\" ><!-- black spade suit\n                                  ==================== U+2660 ISOpub -->\n\n    <!ENTITY clubs    \"&#9827;\" ><!-- black club suit\n                                  shamrock\n                                  ==================== U+2663 ISOpub -->\n\n    <!ENTITY hearts   \"&#9829;\" ><!-- black heart suit\n                                  valentine\n                                  ==================== U+2665 ISOpub -->\n\n    <!ENTITY diams    \"&#9830;\" ><!-- black diamond suit\n                                  ==================== U+2666 ISOpub -->\n"
  },
  {
    "path": "modules/book/src/main/resources/dtd/openebook.org/dtds/oeb-1.2/oebpkg12.dtd",
    "content": "<!--\n\nTitle:\n\n     The Package Document Type Definition (DTD) for the Open\n     eBook Publication Structure Version 1.2\n\n\nVersion:\n\n     1.2\n\n\nRevision:\n\n     20020930-x  (supercedes 20020605-x)\n\n\nRevision History Note:\n\n     This revision, 20020930-x, which supercedes the prior revision\n     20020605-x, solely updates the email addresses within this\n     comment prologue. No changes whatsover were made to the parsed\n     content of this DTD.\n\n\nPrevious Released Version:\n\n     1.0.1 (Revision of 01-February-2001, \"Document Type\n            Definition for the Open eBook package version\n            1.0.1\")\n\n\nAuthors:\n\n     Version 1.0; 1.0.1\n\n          Steve DeRose <sjd@stg.brown.edu>\n          Gunter Hille <hille@abc.de>\n          Ben Trafford <ben@legendary.org>\n          Garret Wilson <garret@globalmentor.com>\n\n     This Version 1.2 updated and edited by:\n\n          Jon Noring <jon@noring.name>\n          Benjamin Jung <benjamin.jung@deepx.com>\n\n\nUsage:\n\n     <?xml version=\"1.0\"?>\n     <!DOCTYPE package\n               PUBLIC \"+//ISBN 0-9673008-1-9//DTD OEB 1.2 Package//EN\"\n               \"http://openebook.org/dtds/oeb-1.2/oebpkg12.dtd\">\n     <package unique-identifier=\"foo\">\n          metadata\n          manifest\n          spine\n          tours\n          guide\n     </package>\n\n\nSummary Description:\n\n     This is the Package Document Type Definition (DTD) for\n     the Open eBook Publication Structure version 1.2.\n\n     Changes to this DTD from version 1.0.1 include:\n\n          a. Upgrading the <dc-metadata> content model to\n             fully conform with the OEBPS 1.2 specification\n             requirements. Specifically, <dc:Language> is\n             now required, while in OEBPS 1.0.1 it was\n             optional.\n\n          b. Updating the mnemonic character entity\n             declaration to refer to version 1.2.\n\n          c. Updating the xmlns:dc namespace to refer to\n             version 1.1 of the Dublin Core Metadata\n             Initiative.\n\n          d. Editing and updating the various non-parsed\n             comments.\n\n          e. Revising the layout (e.g., white space\n             alteration) to aid in readability.\n\n-->\n\n\n    <!-- *************************************************** -->\n\n    <!-- XHTML MNEMONIC CHARACTER ENTITIES ................. -->\n\n    <!ENTITY % OEBEntitiesPUBLIC \"+//ISBN 0-9673008-1-9//DTD OEB 1.2 Entities//EN\"\"http://openebook.org/dtds/oeb-1.2/oeb12.ent\">\n\n    %OEBEntities;\n\n    <!-- *************************************************** -->\n\n    <!-- DATATYPE ENTITIES ................................. -->\n\n    <!-- Uniform Resource Identifier (URI), per [RFC2396] -->\n\n    <!ENTITY % URI \"CDATA\">\n\n    <!-- Language code, per [RFC3066] -->\n\n    <!ENTITY % LanguageCode \"NMTOKEN\">\n\n    <!-- *************************************************** -->\n\n    <!-- NAMESPACE ENTITIES ................................ -->\n\n    <!ENTITY % dc.xmlns\"'http://purl.org/dc/elements/1.1/'\">\n\n    <!ENTITY % oebpk.xmlns\"'http://openebook.org/namespaces/oeb-package/1.0/'\">\n\n    <!-- *************************************************** -->\n\n    <!-- ELEMENT ENTITIES .................................. -->\n\n    <!-- The entity 'DCMetadataOpt' includes the 12 optional\n         <dc:Xxx> children elements of <dc-metadata>. It will\n         be used in the <dc-metadata> content model. -->\n\n    <!ENTITY % DCMetadataOpt\"dc:Contributor |\n      dc:Coverage    |\n      dc:Creator     |\n      dc:Date        |\n      dc:Description |\n      dc:Format      |\n      dc:Publisher   |\n      dc:Relation    |\n      dc:Rights      |\n      dc:Source      |\n      dc:Subject     |\n      dc:Type        \">\n\n    <!-- *************************************************** -->\n\n    <!-- ATTRIBUTE ENTITIES ................................ -->\n\n    <!ENTITY % CoreAttributes\"id                 ID              #IMPLIED\">\n\n    <!ENTITY % InternationalAttributes\"xml:lang           %LanguageCode;  #IMPLIED\">\n\n    <!ENTITY % CommonAttributes\"%CoreAttributes;\n      %InternationalAttributes;\">\n\n    <!-- 'DCNamespaceAttribute' is an attribute entity declaring\n         the Dublin Core namespace. Used on each <dc:Xxx> element\n         to accommodate XML parsers which unnecessarily require\n         this. -->\n\n    <!ENTITY % DCNamespaceAttribute\"xmlns:dc           %URI;           #FIXED %dc.xmlns;\">\n\n    <!-- *************************************************** -->\n\n    <!-- ELEMENTS AND ATTRIBUTES ........................... -->\n\n    <!-- <package> must have as children elements, in this order:\n         <metadata>, <manifest>, and <spine>, and optionally may\n         include <tours> and/or <guide>. The 'unique-identifier'\n         attribute is required for <package> (see comment for\n         <dc:Identifier>.) -->\n\n    <!ELEMENT package (metadata, manifest, spine, tours?, guide?)><!ATTLIST package%CommonAttributes;unique-identifier  IDREF           #REQUIREDxmlns              %URI;           #FIXED %oebpk.xmlns;>\n\n    <!-- <metadata> must contain one <dc-metadata>, and\n         optionally contain one <x-metadata>. There are no\n         attributes for <metadata>. -->\n\n    <!ELEMENT metadata (dc-metadata, x-metadata?)>\n\n    <!-- <dc-metadata> must contain at least one <dc:Title>,\n         one <dc:Identifier>, and one <dc:Language>, and may\n         contain one or more of each of the other twelve\n         optional <dc:XXX> elements, all in any order. -->\n\n    <!ELEMENT dc-metadata( (%DCMetadataOpt;)*,( (dc:Title, (%DCMetadataOpt; | dc:Title)*,( (dc:Identifier, (%DCMetadataOpt; | dc:Title | dc:Identifier)*,dc:Language) |(dc:Language, (%DCMetadataOpt; | dc:Title | dc:Language)*,dc:Identifier) ) ) |(dc:Identifier, (%DCMetadataOpt; | dc:Identifier)*,( (dc:Title, (%DCMetadataOpt; | dc:Identifier | dc:Title)*,dc:Language) |\n\n        (dc:Language, (%DCMetadataOpt; | dc:Identifier | dc:Language)*,dc:Title) ) ) |(dc:Language, (%DCMetadataOpt; | dc:Language)*,( (dc:Identifier, (%DCMetadataOpt; | dc:Language | dc:Identifier)*,dc:Title) |(dc:Title, (%DCMetadataOpt; | dc:Language | dc:Title)*,dc:Identifier) ) ) ),(%DCMetadataOpt; | dc:Title | dc:Identifier | dc:Language)* )>\n\n    <!ATTLIST dc-metadata%CommonAttributes;%DCNamespaceAttribute;xmlns:oebpackage   %URI;           #FIXED %oebpk.xmlns;>\n\n    <!-- Required elements for <dc-metadata>. -->\n\n    <!ELEMENT dc:Title (#PCDATA)><!ATTLIST dc:Title%CommonAttributes;%DCNamespaceAttribute;>\n\n    <!-- One <dc:Identifier> must specify an 'id' identical to\n         the value of the required <package> 'unique-identifier'\n         attribute. -->\n\n    <!ELEMENT dc:Identifier (#PCDATA)><!ATTLIST dc:Identifier%CommonAttributes;%DCNamespaceAttribute;scheme             NMTOKEN         #IMPLIED>\n\n    <!ELEMENT dc:Language (#PCDATA)><!ATTLIST dc:Language%CommonAttributes;%DCNamespaceAttribute;>\n\n    <!-- Optional elements for <dc-metadata>. -->\n\n    <!ELEMENT dc:Contributor (#PCDATA)><!ATTLIST dc:Contributor%CommonAttributes;%DCNamespaceAttribute;file-as            CDATA           #IMPLIEDrole               NMTOKEN         #IMPLIED>\n\n    <!ELEMENT dc:Coverage (#PCDATA)><!ATTLIST dc:Coverage%CommonAttributes;%DCNamespaceAttribute;>\n\n    <!ELEMENT dc:Creator (#PCDATA)><!ATTLIST dc:Creator%CommonAttributes;%DCNamespaceAttribute;file-as            CDATA           #IMPLIEDrole               NMTOKEN         #IMPLIED>\n\n    <!ELEMENT dc:Date (#PCDATA)><!ATTLIST dc:Date%CommonAttributes;%DCNamespaceAttribute;event              NMTOKEN         #IMPLIED>\n\n    <!ELEMENT dc:Description (#PCDATA)><!ATTLIST dc:Description%CommonAttributes;%DCNamespaceAttribute;>\n\n    <!ELEMENT dc:Format (#PCDATA)><!ATTLIST dc:Format%CommonAttributes;%DCNamespaceAttribute;>\n\n    <!ELEMENT dc:Publisher (#PCDATA)><!ATTLIST dc:Publisher%CommonAttributes;%DCNamespaceAttribute;>\n\n    <!ELEMENT dc:Relation (#PCDATA)><!ATTLIST dc:Relation%CommonAttributes;%DCNamespaceAttribute;>\n\n    <!ELEMENT dc:Rights (#PCDATA)><!ATTLIST dc:Rights%CommonAttributes;%DCNamespaceAttribute;>\n\n    <!ELEMENT dc:Source (#PCDATA)><!ATTLIST dc:Source%CommonAttributes;%DCNamespaceAttribute;>\n\n    <!ELEMENT dc:Subject (#PCDATA)><!ATTLIST dc:Subject%CommonAttributes;%DCNamespaceAttribute;>\n\n    <!ELEMENT dc:Type (#PCDATA)>\n\n    <!ATTLIST dc:Type%CommonAttributes;%DCNamespaceAttribute;>\n\n    <!-- <x-metadata> must contain at least one <meta>. -->\n\n    <!ELEMENT x-metadata (meta+)><!ATTLIST x-metadata %CommonAttributes;>\n\n    <!-- Note that 'content' and 'name' are required attributes\n         for <meta>. -->\n\n    <!ELEMENT meta EMPTY><!ATTLIST meta%CommonAttributes;content            CDATA           #REQUIREDname               NMTOKEN         #REQUIREDscheme             CDATA           #IMPLIED>\n\n    <!-- <manifest> must contain at least one <item>. -->\n\n    <!ELEMENT manifest (item+)><!ATTLIST manifest %CommonAttributes;>\n\n    <!-- Note that 'href', 'id' and 'media-type' are required\n         attributes for <item>. -->\n\n    <!ELEMENT item EMPTY><!ATTLIST item%InternationalAttributes;fallback           IDREF           #IMPLIEDhref               %URI;           #REQUIREDid                 ID              #REQUIREDmedia-type         CDATA           #REQUIRED>\n\n    <!-- <spine> must contain at least one <itemref>. -->\n\n    <!ELEMENT spine (itemref+)><!ATTLIST spine %CommonAttributes;>\n\n    <!-- Note that 'idref' is a required attribute for\n         <itemref>. -->\n\n    <!ELEMENT itemref EMPTY><!ATTLIST itemref%CommonAttributes;idref              IDREF           #REQUIRED>\n\n    <!-- <tours> must contain at least one <tour>. -->\n\n    <!ELEMENT tours (tour+)><!ATTLIST tours %CommonAttributes;>\n\n    <!-- <tour> must contain at least one <site>. Note that\n         'title' is a required attribute for <tour>. -->\n\n    <!ELEMENT tour (site+)><!ATTLIST tour%CommonAttributes;title              CDATA           #REQUIRED>\n\n    <!-- Note that 'href' and 'title' are required attributes\n         for <site>. -->\n\n    <!ELEMENT site EMPTY><!ATTLIST site%CommonAttributes;href               %URI;           #REQUIREDtitle              CDATA           #REQUIRED>\n\n    <!-- <guide> must contain at least one <reference>. -->\n\n    <!ELEMENT guide (reference+)><!ATTLIST guide %CommonAttributes;>\n\n    <!-- Note that 'href', 'title' and 'type' are required\n         attributes for <reference>. -->\n\n    <!ELEMENT reference EMPTY><!ATTLIST reference%CommonAttributes;href               %URI;           #REQUIREDtitle              CDATA           #REQUIREDtype               NMTOKEN         #REQUIRED>\n"
  },
  {
    "path": "modules/book/src/main/resources/dtd/www.daisy.org/z3986/2005/ncx-2005-1.dtd",
    "content": "<!-- NCX 2005-1 DTD  2005-06-26\nfile: ncx-2005-1.dtd                                 \n\n  Authors: Mark Hakkinen, George Kerscher, Tom McLaughlin, James Pritchett, and Michael Moodie\n  Change list:\n  2002-02-12 M. Moodie. Changed content model of navLabel element to eliminate ambiguity.\n  2002-02-27 M. Moodie. Grammatical changes suggested by editor.\n  2004-03-31 J. Pritchett.  Various changes per the 2004 change list:\n            - Changed internal version numbers from 1.1.0 to 1.2.0\n            - Made audio clipBegin/clipEnd mandatory (change #10)\n            - Dropped value attribute from navPoint (change #11)\n            - Replaced lang attribute with xml:lang (change #12)\n            - Added <pageList> and <pageTarget> elements (change #48)\n            - Dropped onFocus and onBlur attributes from navPoint and navTarget (change #49)\n            - Added <img> to content models of docTitle and docAuthor (change #50)\n            - Removed reference to pages in description of navList (change #52)\n            - Added <navInfo> element (change #53)\n            - Added default namespace attribute to description of <ncx> (change #L8)\n            - Removed pageRef and mapRef attributes\n  2004-04-05 J. Pritchett.  Changes after feedback from MM and MG to 2004-03-31 version\n            - Changed internal version numbers from 1.2.0 to 1.1.2 (per MM e-mail of 3/31)\n            - Changed system identifier to use z3986/2004 as path instead of z3986/v100 (per 3/31 con call)\n            - Added class attribute to both pageTarget and pageList (per MG e-mail of 4/1)\n            - Added comment text describing value attribute for pageTarget and navTarget (per MM e-mail of 3/31)\n            - Changed declaration of type attribute on pageTarget to enumerate allowed values \n            - Added playOrder attribute to navPoint, navTarget, and pageTarget (per Lloyd's proposal)\n2004-04-05 T. McLaughlin. In description of smilCustomTest, added id and defaultState are to be copied. \nVersion update to 1.1.3.\n2004-05-14 T. McLaughlin. Reinstated override attribute to be copied also. Added bookStruct attribute \nand enum list to smilCustomTest. Update to 1.1.4.\nRevised, 4/5/2004:  Changed version to 1.1.2 \nRevised, 4/5/2004:  Changed system identifier to use '2004' path \nRevised, 4/5/2004:  TM, Changed version to 1.1.3 \nRevised, 5/14/2004:  TM, Changed version to 1.1.4 \n2004-07-07 M. Moodie Updated version to 1.2.0 everywhere but at top, where version was set to 1.1.5.\n2004-09-15 M. Moodie.  Changed uri to URI throughout.  Set version to 1.1.6.\n2004-09-16 M. Moodie.  Changed version to 1.2.0\n2005-06-26 M. Gylling. Changed pid, sid, ns uri, and filename for Z3986-2005\n            \n  Description:\n                                                  \n  NCX (Navigation Control for XML applications) is a generalized navigation definition DTD for application\nto Digital Talking Books, eBooks, and general web content models.                                                \nThis DTD is an XML application that layers navigation functionality on top of SMIL 2.0  content.                                       \n  \n  The NCX defines a navigation path/model that may be applied upon existing publications,\nwithout modification of the existing publication source, so long as the navigation targets within\nthe source publication can be directly referenced via a URI.                      \n             \nThe following identifiers apply to this DTD:\n  \"-//NISO//DTD ncx 2005-1//EN\"\n  \"http://www.daisy.org/z3986/2005/ncx-2005-1.dtd\"\n-->\n\n    <!-- Basic Entities -->\n\n    <!ENTITY % i18n\"xml:lang    NMTOKEN    #IMPLIED\n  dir    (ltr|rtl)  #IMPLIED\" >\n\n    <!ENTITY % SMILtimeVal  \"CDATA\" ><!ENTITY % URI    \"CDATA\" ><!ENTITY % script  \"CDATA\" >\n\n    <!-- ELEMENTS -->\n\n    <!-- Top Level NCX Container. -->\n    <!-- Revised, 3/31/2004:  Added pageList to content model -->\n    <!ELEMENT ncx (head, docTitle, docAuthor*, navMap, pageList?, navList*)><!-- Revised, 4/5/2004:  Changed version to 1.1.2 -->\n    <!-- Revised 3/29/2004:  Added xmlns -->\n    <!-- Revised, 4/5/2004:  TM, Changed version to 1.1.3 -->\n    <!-- Revised, 5/14/2004:  TM, Changed version to 1.1.4 -->\n    <!-- Revised, 7/7/2004:  MM, Changed version to 1.2.0 -->\n    <!ATTLIST ncxversion     CDATA     #FIXED \"2005-1\"xmlns       %URI;     #FIXED \"http://www.daisy.org/z3986/2005/ncx/\"%i18n;>\n\n    <!-- Document Head - Contains all NCX metadata.\n    -->\n\n    <!ELEMENT head (smilCustomTest | meta)+>\n\n    <!-- 2004-04-05 TM - only id and defaultState are copied -->\n    <!-- 2004-05-14 TM - revert to override copied too; added bookStruct attribute -->\n    <!-- smilCustomTest - Duplicates customTest data found in SMIL files.  Each unique customTest\n    element that appears in one or more SMIL files must have its id, defaultState and override\n    attributes duplicated in a smilCustomTest element in the NCX.  The NCX thus gathers in one\n    place all customTest elements used in the SMIL files, for presentation to the user.\n    -->\n    <!ELEMENT smilCustomTest EMPTY><!ATTLIST smilCustomTestid    ID    #REQUIREDdefaultState  (true|false)   'false'override  (visible|hidden) 'hidden'bookStruct  (PAGE_NUMBER|NOTE|NOTE_REFERENCE|ANNOTATION|LINE_NUMBER|OPTIONAL_SIDEBAR|OPTIONAL_PRODUCER_NOTE)  #IMPLIED>\n\n    <!-- Meta Element - metadata about this NCX -->\n    <!ELEMENT meta EMPTY><!ATTLIST metaname    CDATA    #REQUIREDcontent  CDATA    #REQUIREDscheme  CDATA    #IMPLIED>\n\n    <!-- DocTitle - the title of the document, required and must immediately follow head.\n    -->\n\n    <!-- Revised, 3/31/2004:  Added img to content model -->\n    <!ELEMENT docTitle (text, audio?, img?)><!ATTLIST docTitleid    ID    #IMPLIED%i18n;>\n\n    <!-- DocAuthor - the author of the document, immediately follows docTitle.\n    -->\n\n    <!-- Revised, 3/31/2004:  Added img to content model -->\n    <!ELEMENT docAuthor (text, audio?, img?)><!ATTLIST docAuthorid    ID    #IMPLIED%i18n;>\n\n    <!-- Navigation Structure - container for all of the NCX objects that are part of the\n    hierarchical structure of the document.\n    -->\n\n    <!-- Revised, 3/31/2004:  Added navInfo to content model -->\n    <!ELEMENT navMap (navInfo*, navLabel*, navPoint+)><!ATTLIST navMapid    ID    #IMPLIED>\n\n    <!-- Navigation Point - contains description(s) of target, as well as a pointer to\n    entire content of target.\n    Hierarchy is represented by nesting navPoints.  \"class\" attribute describes the kind\n    of structural unit this object represents (e.g., \"chapter\", \"section\").\n    -->\n    <!ELEMENT navPoint (navLabel+, content, navPoint*)><!-- Revised, 3/29/2004:  Removed onFocus/onBlur -->\n    <!-- Revised, 3/29/2004:  Removed value -->\n    <!-- Revised, 3/31/2004:  Removed pageRef -->\n    <!-- Revised, 4/5/2004:  Added playOrder -->\n    <!ATTLIST navPointid    ID      #REQUIREDclass    CDATA    #IMPLIEDplayOrder CDATA       #REQUIRED>\n\n    <!-- Revised, 3/31/2004:  Added pageList element -->\n    <!-- Page List -  Container for pagination information.\n      -->\n    <!ELEMENT pageList (navInfo*, navLabel*, pageTarget+)><!-- Revised, 4/5/2004:  Added class attribute -->\n    <!ATTLIST pageListid       ID          #IMPLIEDclass    CDATA       #IMPLIED>\n\n    <!-- Revised, 3/31/2004:  Added pageTarget element -->\n    <!-- Revised, 4/5/2004:  Added description of value attribute to comment -->\n    <!-- Page Target -  Container for\n      text, audio, image, and content elements containing navigational\n      information for pages.  The \"value\" attribute is a positive integer representing\n    the numeric value associated with a page. Combination of values of type and\n    value attributes must be unique, when value attribute is present.\n    -->\n    <!ELEMENT pageTarget (navLabel+, content)><!-- Revised, 4/5/2004:  Added class attribute -->\n    <!-- Revised, 4/5/2004:  Changed declaration of type attribute to enumerate values -->\n    <!-- Revised, 4/5/2004:  Added playOrder -->\n    <!ATTLIST pageTargetid       ID          #IMPLIEDvalue    CDATA       #IMPLIEDtype     (front | normal | special)       #REQUIREDclass    CDATA       #IMPLIEDplayOrder CDATA      #REQUIRED>\n\n    <!-- Navigation List - container for distinct, flat sets of navigable elements, e.g.\n    notes, figures, tables, etc.  Essentially a flat version of navMap.  The \"class\" attribute\n    describes the type of object contained in this navList, using dtbook element names, e.g., note.\n    -->\n\n    <!-- Revised, 3/31/2004:  Added navInfo to content model -->\n    <!ELEMENT navList   (navInfo*, navLabel+, navTarget+) ><!ATTLIST navListid    ID    #IMPLIEDclass    CDATA    #IMPLIED>\n\n    <!-- Revised, 4/5/2004:  Added description of value attribute to comment -->\n    <!-- Navigation Target - contains description(s) of target, as well as a pointer to\n    entire content of target.\n    navTargets are the equivalent of navPoints for use in navLists.  \"class\" attribute\n    describes the kind of structure this target represents, using its dtbook element\n    name, e.g., note.  The \"value\" attribute is a positive integer representing the\n    numeric value associated with the navTarget.\n    -->\n\n    <!ELEMENT navTarget  (navLabel+, content) ><!-- Revised, 3/29/2004:  Removed onFocus/onBlur -->\n    <!-- Revised, 3/31/2004:  Removed mapRef -->\n    <!-- Revised, 4/5/2004:  Added playOrder -->\n    <!ATTLIST navTargetid    ID    #REQUIREDclass    CDATA    #IMPLIEDvalue    CDATA    #IMPLIEDplayOrder CDATA       #REQUIRED>\n\n\n    <!-- Revised, 3/31/2004:  Added navInfo element -->\n    <!-- Navigation Information - Contains an informative comment\n      about a navMap, pageList, or navList in various media for presentation to the user.\n    -->\n    <!ELEMENT navInfo (((text, audio?) | audio), img?)><!ATTLIST navInfo%i18n;>\n\n\n    <!-- Navigation Label - Contains a description of a given <navMap>, <navPoint>,\n    <navList>, or <navTarget> in various media for presentation to the user. Can be\n    repeated so descriptions can be provided in multiple languages. -->\n    <!ELEMENT navLabel (((text, audio?) | audio), img?)><!ATTLIST navLabel%i18n;>\n\n\n    <!-- Content Element - pointer into SMIL to beginning of navPoint. -->\n    <!ELEMENT content EMPTY><!ATTLIST contentid    ID    #IMPLIEDsrc    %URI;    #REQUIRED>\n\n    <!-- Text Element - Contains text of docTitle, navPoint heading, navTarget (e.g., page number),\n    or label for navMap or navList. -->\n    <!ELEMENT text (#PCDATA)><!ATTLIST textid    ID        #IMPLIEDclass  CDATA      #IMPLIED>\n\n    <!-- Audio Element - audio clip of navPoint heading. -->\n    <!ELEMENT audio EMPTY><!-- Revised, 3/29/2004:  clipBegin/clipEnd now REQUIRED -->\n    <!ATTLIST audioid    ID        #IMPLIEDclass  CDATA      #IMPLIEDsrc    %URI;      #REQUIREDclipBegin %SMILtimeVal;  #REQUIREDclipEnd  %SMILtimeVal;  #REQUIRED>\n\n    <!-- Image Element - image that may accompany heading. -->\n    <!ELEMENT img EMPTY><!ATTLIST imgid    ID      #IMPLIEDclass  CDATA    #IMPLIEDsrc    %URI;    #REQUIRED>\n"
  },
  {
    "path": "modules/book/src/main/resources/dtd/www.w3.org/TR/ruby/xhtml-ruby-1.mod",
    "content": "<!-- ...................................................................... -->\n    <!-- XHTML Ruby Module .................................................... -->\n    <!-- file: xhtml-ruby-1.mod\n\n         This is XHTML, a reformulation of HTML as a modular XML application.\n         Copyright 1999-2001 W3C (MIT, INRIA, Keio), All Rights Reserved.\n         Revision: $Id: xhtml-ruby-1.mod,v 4.0 2001/04/03 23:14:33 altheim Exp $\n\n         This module is based on the W3C Ruby Annotation Specification:\n\n            http://www.w3.org/TR/ruby\n\n         This DTD module is identified by the PUBLIC and SYSTEM identifiers:\n\n           PUBLIC \"-//W3C//ELEMENTS XHTML Ruby 1.0//EN\"\n           SYSTEM \"http://www.w3.org/TR/ruby/xhtml-ruby-1.mod\"\n\n         ...................................................................... -->\n\n    <!-- Ruby Elements\n\n            ruby, rbc, rtc, rb, rt, rp\n\n         This module declares the elements and their attributes used to\n         support ruby annotation markup.\n    -->\n\n    <!-- declare qualified element type names:\n    -->\n    <!ENTITY % ruby.qname  \"ruby\" ><!ENTITY % rbc.qname  \"rbc\" ><!ENTITY % rtc.qname  \"rtc\" ><!ENTITY % rb.qname  \"rb\" ><!ENTITY % rt.qname  \"rt\" ><!ENTITY % rp.qname  \"rp\" >\n\n    <!-- rp fallback is included by default.\n    -->\n    <!ENTITY % Ruby.fallback \"INCLUDE\" ><!ENTITY % Ruby.fallback.mandatory \"IGNORE\" >\n\n    <!-- Complex ruby is included by default; it may be\n         overridden by other modules to ignore it.\n    -->\n    <!ENTITY % Ruby.complex \"INCLUDE\" >\n\n    <!-- Fragments for the content model of the ruby element -->\n    <![%Ruby.fallback;[<![%Ruby.fallback.mandatory;[<!ENTITY % Ruby.content.simple\"( %rb.qname;, %rp.qname;, %rt.qname;, %rp.qname; )\">]]><!ENTITY % Ruby.content.simple\"( %rb.qname;, ( %rt.qname; | ( %rp.qname;, %rt.qname;, %rp.qname; ) ) )\">]]><!ENTITY % Ruby.content.simple \"( %rb.qname;, %rt.qname; )\" >\n\n    <![%Ruby.complex;[<!ENTITY % Ruby.content.complex\"| ( %rbc.qname;, %rtc.qname;, %rtc.qname;? )\">]]><!ENTITY % Ruby.content.complex \"\" >\n\n    <!-- Content models of the rb and the rt elements are intended to\n         allow other inline-level elements of its parent markup language,\n         but it should not include ruby descendent elements. The following\n         parameter entity %NoRuby.content; can be used to redefine\n         those content models with minimum effort.  It's defined as\n         '( #PCDATA )' by default.\n    -->\n    <!ENTITY % NoRuby.content \"( #PCDATA )\" >\n\n    <!-- one or more digits (NUMBER) -->\n    <!ENTITY % Number.datatype \"CDATA\" >\n\n    <!-- ruby element ...................................... -->\n\n    <!ENTITY % ruby.element  \"INCLUDE\" ><![%ruby.element;[<!ENTITY % ruby.content\"( %Ruby.content.simple; %Ruby.content.complex; )\"><!ELEMENT %ruby.qname;  %ruby.content; ><!-- end of ruby.element -->]]>\n\n    <![%Ruby.complex;[<!-- rbc (ruby base component) element ................. -->\n\n        <!ENTITY % rbc.element  \"INCLUDE\" ><![%rbc.element;[<!ENTITY % rbc.content\"(%rb.qname;)+\"><!ELEMENT %rbc.qname;  %rbc.content; ><!-- end of rbc.element -->]]>\n\n        <!-- rtc (ruby text component) element ................. -->\n\n        <!ENTITY % rtc.element  \"INCLUDE\" ><![%rtc.element;[<!ENTITY % rtc.content\"(%rt.qname;)+\"><!ELEMENT %rtc.qname;  %rtc.content; ><!-- end of rtc.element -->]]>]]>\n\n    <!-- rb (ruby base) element ............................ -->\n\n    <!ENTITY % rb.element  \"INCLUDE\" ><![%rb.element;[<!-- %rb.content; uses %NoRuby.content; as its content model,\n     which is '( #PCDATA )' by default. It may be overridden\n     by other modules to allow other inline-level elements\n     of its parent markup language, but it should not include\n     ruby descendent elements.\n-->\n    <!ENTITY % rb.content \"%NoRuby.content;\" ><!ELEMENT %rb.qname;  %rb.content; ><!-- end of rb.element -->]]>\n\n    <!-- rt (ruby text) element ............................ -->\n\n    <!ENTITY % rt.element  \"INCLUDE\" ><![%rt.element;[<!-- %rt.content; uses %NoRuby.content; as its content model,\n     which is '( #PCDATA )' by default. It may be overridden\n     by other modules to allow other inline-level elements\n     of its parent markup language, but it should not include\n     ruby descendent elements.\n-->\n    <!ENTITY % rt.content \"%NoRuby.content;\" >\n\n    <!ELEMENT %rt.qname;  %rt.content; ><!-- end of rt.element -->]]>\n\n    <!-- rbspan attribute is used for complex ruby only ...... -->\n    <![%Ruby.complex;[<!ENTITY % rt.attlist  \"INCLUDE\" ><![%rt.attlist;[<!ATTLIST %rt.qname;rbspan         %Number.datatype;      \"1\"><!-- end of rt.attlist -->]]>]]>\n\n    <!-- rp (ruby parenthesis) element ..................... -->\n\n    <![%Ruby.fallback;[<!ENTITY % rp.element  \"INCLUDE\" ><![%rp.element;[<!ENTITY % rp.content\"( #PCDATA )\"><!ELEMENT %rp.qname;  %rp.content; ><!-- end of rp.element -->]]>]]>\n\n    <!-- Ruby Common Attributes\n\n         The following optional ATTLIST declarations provide an easy way\n         to define common attributes for ruby elements.  These declarations\n         are ignored by default.\n\n         Ruby elements are intended to have common attributes of its\n         parent markup language.  For example, if a markup language defines\n         common attributes as a parameter entity %attrs;, you may add\n         those attributes by just declaring the following parameter entities\n\n             <!ENTITY % Ruby.common.attlists  \"INCLUDE\" >\n             <!ENTITY % Ruby.common.attrib  \"%attrs;\" >\n\n         before including the Ruby module.\n    -->\n\n    <!ENTITY % Ruby.common.attlists  \"IGNORE\" ><![%Ruby.common.attlists;[<!ENTITY % Ruby.common.attrib  \"\" >\n\n    <!-- common attributes for ruby ........................ -->\n\n    <!ENTITY % Ruby.common.attlist  \"INCLUDE\" ><![%Ruby.common.attlist;[<!ATTLIST %ruby.qname;%Ruby.common.attrib;><!-- end of Ruby.common.attlist -->]]>\n\n    <![%Ruby.complex;[<!-- common attributes for rbc ......................... -->\n\n        <!ENTITY % Rbc.common.attlist  \"INCLUDE\" ><![%Rbc.common.attlist;[<!ATTLIST %rbc.qname;%Ruby.common.attrib;><!-- end of Rbc.common.attlist -->]]>\n\n        <!-- common attributes for rtc ......................... -->\n\n        <!ENTITY % Rtc.common.attlist  \"INCLUDE\" ><![%Rtc.common.attlist;[<!ATTLIST %rtc.qname;%Ruby.common.attrib;><!-- end of Rtc.common.attlist -->]]>]]>\n\n    <!-- common attributes for rb .......................... -->\n\n    <!ENTITY % Rb.common.attlist  \"INCLUDE\" ><![%Rb.common.attlist;[<!ATTLIST %rb.qname;%Ruby.common.attrib;><!-- end of Rb.common.attlist -->]]>\n\n    <!-- common attributes for rt .......................... -->\n\n    <!ENTITY % Rt.common.attlist  \"INCLUDE\" ><![%Rt.common.attlist;[<!ATTLIST %rt.qname;%Ruby.common.attrib;><!-- end of Rt.common.attlist -->]]>\n\n    <![%Ruby.fallback;[<!-- common attributes for rp .......................... -->\n\n        <!ENTITY % Rp.common.attlist  \"INCLUDE\" ><![%Rp.common.attlist;[<!ATTLIST %rp.qname;%Ruby.common.attrib;><!-- end of Rp.common.attlist -->]]>]]>]]>\n\n    <!-- end of xhtml-ruby-1.mod -->\n"
  },
  {
    "path": "modules/book/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-arch-1.mod",
    "content": "<!-- ...................................................................... -->\n    <!-- XHTML Base Architecture Module  ...................................... -->\n    <!-- file: xhtml-arch-1.mod\n\n         This is XHTML, a reformulation of HTML as a modular XML application.\n         Copyright 1998-2005 W3C (MIT, ERCIM, Keio), All Rights Reserved.\n         Revision: $Id: xhtml-arch-1.mod,v 1.1 2010/07/29 13:42:46 bertails Exp $ SMI\n\n         This DTD module is identified by the PUBLIC and SYSTEM identifiers:\n\n           PUBLIC \"-//W3C//ELEMENTS XHTML Base Architecture 1.0//EN\"\n           SYSTEM \"http://www.w3.org/MarkUp/DTD/xhtml-arch-1.mod\"\n\n         Revisions:\n         (none)\n         ....................................................................... -->\n\n    <!-- This optional module includes declarations that enable XHTML to be used\n         as a base architecture according to the 'Architectural Forms Definition\n         Requirements' (Annex A.3, ISO/IEC 10744, 2nd edition). For more information\n         on use of architectural forms, see the HyTime web site at:\n\n             http://www.hytime.org/\n    -->\n\n    <?IS10744 ArcBase xhtml ?>\n\n    <!NOTATION xhtml PUBLIC \"-//W3C//NOTATION AFDR ARCBASE XHTML 1.1//EN\" >\n\n    <!-- Entity declaration for associated Architectural DTD\n    -->\n    <!ENTITY xhtml-arch.dtdPUBLIC \"-//W3C//DTD XHTML Architecture 1.1//EN\"\"xhtml11-arch.dtd\" >\n\n    <?IS10744:arch xhtmlpublic-id       =  \"-//W3C//NOTATION AFDR ARCBASE XHTML 1.1//EN\"dtd-public-id   =  \"-//W3C//DTD XHTML 1.1//EN\"dtd-system-id   =  \"xhtml11.dtd\"doc-elem-form   =  \"html\"form-att        =  \"html\"renamer-att     =  \"htnames\"suppressor-att  =  \"htsupp\"data-ignore-att =  \"htign\"auto            =  \"ArcAuto\"options         =  \"HtModReq HtModOpt\"HtModReq        =  \"Framework Text Hypertext Lists Structure\"HtModOpt        =  \"Standard\"?>\n\n    <!-- end of xhtml-arch-1.mod -->\n"
  },
  {
    "path": "modules/book/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-attribs-1.mod",
    "content": "<!-- ...................................................................... -->\n    <!-- XHTML Common Attributes Module  ...................................... -->\n    <!-- file: xhtml-attribs-1.mod\n\n         This is XHTML, a reformulation of HTML as a modular XML application.\n         Copyright 1998-2005 W3C (MIT, ERCIM, Keio), All Rights Reserved.\n         Revision: $Id: xhtml-attribs-1.mod,v 1.1 2010/07/29 13:42:46 bertails Exp $ SMI\n\n         This DTD module is identified by the PUBLIC and SYSTEM identifiers:\n\n           PUBLIC \"-//W3C//ENTITIES XHTML Common Attributes 1.0//EN\"\n           SYSTEM \"http://www.w3.org/MarkUp/DTD/xhtml-attribs-1.mod\"\n\n         Revisions:\n         (none)\n         ....................................................................... -->\n\n    <!-- Common Attributes\n\n         This module declares many of the common attributes for the XHTML DTD.\n         %NS.decl.attrib; is declared in the XHTML Qname module.\n\n         Note that this file was extended in XHTML Modularization 1.1 to\n         include declarations of \"global\" versions of the attribute collections.\n         The global versions of the attributes are for use on elements in other\n         namespaces.  The global version of \"common\" includes the xmlns declaration\n         for the prefixed version of the xhtml namespace.  If you are only using a\n         specific attribute or an individual attribute collection, you must also\n         include the XHTML.xmlns.attrib.prefixed PE on your elements.\n    -->\n\n    <!ENTITY % id.attrib\"id           ID                       #IMPLIED\">\n\n    <![%XHTML.global.attrs.prefixed;[<!ENTITY % XHTML.global.id.attrib\"%XHTML.prefix;:id           ID        #IMPLIED\">]]>\n\n    <!ENTITY % class.attrib\"class        CDATA                 #IMPLIED\">\n\n    <![%XHTML.global.attrs.prefixed;[<!ENTITY % XHTML.global.class.attrib\"%XHTML.prefix;:class        CDATA                 #IMPLIED\">]]>\n\n    <!ENTITY % title.attrib\"title        %Text.datatype;          #IMPLIED\">\n\n    <![%XHTML.global.attrs.prefixed;[<!ENTITY % XHTML.global.title.attrib\"%XHTML.prefix;:title        %Text.datatype;          #IMPLIED\">]]>\n\n    <!ENTITY % Core.extra.attrib \"\" >\n\n    <!ENTITY % Core.attrib\"%XHTML.xmlns.attrib;\n      %id.attrib;\n      %class.attrib;\n      %title.attrib;\n      xml:space    ( preserve )             #FIXED 'preserve'\n      %Core.extra.attrib;\">\n\n    <!ENTITY % XHTML.global.core.extra.attrib \"\" >\n\n    <![%XHTML.global.attrs.prefixed;[\n\n        <!ENTITY % XHTML.global.core.attrib\"%XHTML.global.id.attrib;\n      %XHTML.global.class.attrib;\n      %XHTML.global.title.attrib;\n      %XHTML.global.core.extra.attrib;\">]]>\n\n    <!ENTITY % XHTML.global.core.attrib \"\" >\n\n\n    <!ENTITY % lang.attrib\"xml:lang     %LanguageCode.datatype;  #IMPLIED\">\n\n    <![%XHTML.bidi;[<!ENTITY % dir.attrib\"dir          ( ltr | rtl )            #IMPLIED\">\n\n        <!ENTITY % I18n.attrib\"%dir.attrib;\n      %lang.attrib;\">\n\n        <![%XHTML.global.attrs.prefixed;[<!ENTITY XHTML.global.i18n.attrib\"%XHTML.prefix;:dir          ( ltr | rtl )            #IMPLIED\n      %lang.attrib;\">]]><!ENTITY XHTML.global.i18n.attrib \"\" >\n\n        ]]><!ENTITY % I18n.attrib\"%lang.attrib;\"><!ENTITY % XHTML.global.i18n.attrib\"%lang.attrib;\">\n\n    <!ENTITY % Common.extra.attrib \"\" ><!ENTITY % XHTML.global.common.extra.attrib \"\" >\n\n    <!-- intrinsic event attributes declared previously\n    -->\n    <!ENTITY % Events.attrib \"\" >\n\n    <!ENTITY % XHTML.global.events.attrib \"\" >\n\n    <!ENTITY % Common.attrib\"%Core.attrib;\n      %I18n.attrib;\n      %Events.attrib;\n      %Common.extra.attrib;\">\n\n    <!ENTITY % XHTML.global.common.attrib\"%XHTML.xmlns.attrib.prefixed;\n      %XHTML.global.core.attrib;\n\t  %XHTML.global.i18n.attrib;\n\t  %XHTML.global.events.attrib;\n\t  %XHTML.global.common.extra.attrib;\">\n\n    <!-- end of xhtml-attribs-1.mod -->\n"
  },
  {
    "path": "modules/book/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-base-1.mod",
    "content": "<!-- ...................................................................... -->\n    <!-- XHTML Base Element Module  ........................................... -->\n    <!-- file: xhtml-base-1.mod\n\n         This is XHTML, a reformulation of HTML as a modular XML application.\n         Copyright 1998-2005 W3C (MIT, ERCIM, Keio), All Rights Reserved.\n         Revision: $Id: xhtml-base-1.mod,v 1.1 2010/07/29 13:42:46 bertails Exp $ SMI\n\n         This DTD module is identified by the PUBLIC and SYSTEM identifiers:\n\n           PUBLIC \"-//W3C//ELEMENTS XHTML Base Element 1.0//EN\"\n           SYSTEM \"http://www.w3.org/MarkUp/DTD/xhtml-base-1.mod\"\n\n         Revisions:\n         (none)\n         ....................................................................... -->\n\n    <!-- Base element\n\n            base\n\n         This module declares the base element type and its attributes,\n         used to define a base URI against which relative URIs in the\n         document will be resolved.\n\n         Note that this module also redeclares the content model for\n         the head element to include the base element.\n    -->\n\n    <!-- base: Document Base URI ........................... -->\n\n    <!ENTITY % base.element  \"INCLUDE\" ><![%base.element;[<!ENTITY % base.content  \"EMPTY\" ><!ENTITY % base.qname  \"base\" ><!ELEMENT %base.qname;  %base.content; ><!-- end of base.element -->]]>\n\n    <!ENTITY % base.attlist  \"INCLUDE\" ><![%base.attlist;[<!ATTLIST %base.qname;%XHTML.xmlns.attrib;href         %URI.datatype;           #REQUIRED><!-- end of base.attlist -->]]>\n\n    <!ENTITY % head.content\"( %HeadOpts.mix;,\n     ( ( %title.qname;, %HeadOpts.mix;, ( %base.qname;, %HeadOpts.mix; )? )\n     | ( %base.qname;, %HeadOpts.mix;, ( %title.qname;, %HeadOpts.mix; ))))\">\n\n    <!-- end of xhtml-base-1.mod -->\n"
  },
  {
    "path": "modules/book/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-bdo-1.mod",
    "content": "<!-- ...................................................................... -->\n    <!-- XHTML BDO Element Module ............................................. -->\n    <!-- file: xhtml-bdo-1.mod\n\n         This is XHTML, a reformulation of HTML as a modular XML application.\n         Copyright 1998-2005 W3C (MIT, ERCIM, Keio), All Rights Reserved.\n         Revision: $Id: xhtml-bdo-1.mod,v 1.1 2010/07/29 13:42:46 bertails Exp $ SMI\n\n         This DTD module is identified by the PUBLIC and SYSTEM identifiers:\n\n           PUBLIC \"-//W3C//ELEMENTS XHTML BDO Element 1.0//EN\"\n           SYSTEM \"http://www.w3.org/MarkUp/DTD/xhtml-bdo-1.mod\"\n\n         Revisions:\n         (none)\n         ....................................................................... -->\n\n    <!-- Bidirectional Override (bdo) Element\n\n         This modules declares the element 'bdo', used to override the\n         Unicode bidirectional algorithm for selected fragments of text.\n\n         DEPENDENCIES:\n         Relies on the conditional section keyword %XHTML.bidi; declared\n         as \"INCLUDE\". Bidirectional text support includes both the bdo\n         element and the 'dir' attribute.\n    -->\n\n    <!ENTITY % bdo.element  \"INCLUDE\" ><![%bdo.element;[<!ENTITY % bdo.content\"( #PCDATA | %Inline.mix; )*\"><!ENTITY % bdo.qname  \"bdo\" ><!ELEMENT %bdo.qname;  %bdo.content; ><!-- end of bdo.element -->]]>\n\n    <!ENTITY % bdo.attlist  \"INCLUDE\" ><![%bdo.attlist;[<!ATTLIST %bdo.qname;%Core.attrib;    %lang.attrib;dir          ( ltr | rtl )            #REQUIRED>]]>\n\n    <!-- end of xhtml-bdo-1.mod -->\n"
  },
  {
    "path": "modules/book/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-blkphras-1.mod",
    "content": "<!-- ...................................................................... -->\n    <!-- XHTML Block Phrasal Module  .......................................... -->\n    <!-- file: xhtml-blkphras-1.mod\n\n         This is XHTML, a reformulation of HTML as a modular XML application.\n         Copyright 1998-2005 W3C (MIT, ERCIM, Keio), All Rights Reserved.\n         Revision: $Id: xhtml-blkphras-1.mod,v 1.1 2010/07/29 13:42:46 bertails Exp $ SMI\n\n         This DTD module is identified by the PUBLIC and SYSTEM identifiers:\n\n           PUBLIC \"-//W3C//ELEMENTS XHTML Block Phrasal 1.0//EN\"\n           SYSTEM \"http://www.w3.org/MarkUp/DTD/xhtml-blkphras-1.mod\"\n\n         Revisions:\n         (none)\n         ....................................................................... -->\n\n    <!-- Block Phrasal\n\n            address, blockquote, pre, h1, h2, h3, h4, h5, h6\n\n         This module declares the elements and their attributes used to\n         support block-level phrasal markup.\n    -->\n\n    <!ENTITY % address.element  \"INCLUDE\" ><![%address.element;[<!ENTITY % address.content\"( #PCDATA | %Inline.mix; )*\" ><!ENTITY % address.qname  \"address\" ><!ELEMENT %address.qname;  %address.content; ><!-- end of address.element -->]]>\n\n    <!ENTITY % address.attlist  \"INCLUDE\" ><![%address.attlist;[<!ATTLIST %address.qname;%Common.attrib;><!-- end of address.attlist -->]]>\n\n    <!ENTITY % blockquote.element  \"INCLUDE\" ><![%blockquote.element;[<!ENTITY % blockquote.content\"( %Block.mix; )*\"><!ENTITY % blockquote.qname  \"blockquote\" ><!ELEMENT %blockquote.qname;  %blockquote.content; ><!-- end of blockquote.element -->]]>\n\n    <!ENTITY % blockquote.attlist  \"INCLUDE\" ><![%blockquote.attlist;[<!ATTLIST %blockquote.qname;%Common.attrib;cite         %URI.datatype;           #IMPLIED><!-- end of blockquote.attlist -->]]>\n\n    <!ENTITY % pre.element  \"INCLUDE\" ><![%pre.element;[<!ENTITY % pre.content\"( #PCDATA\n      | %InlStruct.class;\n      %InlPhras.class;\n      | %tt.qname; | %i.qname; | %b.qname;\n      %I18n.class;\n      %Anchor.class;\n      | %map.qname;\n      %Misc.class;\n      %Inline.extra; )*\"><!ENTITY % pre.qname  \"pre\" ><!ELEMENT %pre.qname;  %pre.content; ><!-- end of pre.element -->]]>\n\n    <!ENTITY % pre.attlist  \"INCLUDE\" ><![%pre.attlist;[<!ATTLIST %pre.qname;%Common.attrib;><!-- end of pre.attlist -->]]>\n\n    <!-- ...................  Heading Elements  ................... -->\n\n    <!ENTITY % Heading.content  \"( #PCDATA | %Inline.mix; )*\" >\n\n    <!ENTITY % h1.element  \"INCLUDE\" ><![%h1.element;[<!ENTITY % h1.qname  \"h1\" ><!ELEMENT %h1.qname;  %Heading.content; ><!-- end of h1.element -->]]>\n\n    <!ENTITY % h1.attlist  \"INCLUDE\" ><![%h1.attlist;[<!ATTLIST %h1.qname;%Common.attrib;><!-- end of h1.attlist -->]]>\n\n    <!ENTITY % h2.element  \"INCLUDE\" ><![%h2.element;[<!ENTITY % h2.qname  \"h2\" ><!ELEMENT %h2.qname;  %Heading.content; ><!-- end of h2.element -->]]>\n\n    <!ENTITY % h2.attlist  \"INCLUDE\" ><![%h2.attlist;[<!ATTLIST %h2.qname;%Common.attrib;><!-- end of h2.attlist -->]]>\n\n    <!ENTITY % h3.element  \"INCLUDE\" ><![%h3.element;[<!ENTITY % h3.qname  \"h3\" ><!ELEMENT %h3.qname;  %Heading.content; ><!-- end of h3.element -->]]>\n\n    <!ENTITY % h3.attlist  \"INCLUDE\" ><![%h3.attlist;[<!ATTLIST %h3.qname;%Common.attrib;><!-- end of h3.attlist -->]]>\n\n    <!ENTITY % h4.element  \"INCLUDE\" ><![%h4.element;[<!ENTITY % h4.qname  \"h4\" ><!ELEMENT %h4.qname;  %Heading.content; ><!-- end of h4.element -->]]>\n\n    <!ENTITY % h4.attlist  \"INCLUDE\" ><![%h4.attlist;[<!ATTLIST %h4.qname;%Common.attrib;><!-- end of h4.attlist -->]]>\n\n    <!ENTITY % h5.element  \"INCLUDE\" ><![%h5.element;[<!ENTITY % h5.qname  \"h5\" ><!ELEMENT %h5.qname;  %Heading.content; ><!-- end of h5.element -->]]>\n\n    <!ENTITY % h5.attlist  \"INCLUDE\" ><![%h5.attlist;[<!ATTLIST %h5.qname;%Common.attrib;><!-- end of h5.attlist -->]]>\n\n    <!ENTITY % h6.element  \"INCLUDE\" ><![%h6.element;[<!ENTITY % h6.qname  \"h6\" ><!ELEMENT %h6.qname;  %Heading.content; ><!-- end of h6.element -->]]>\n\n    <!ENTITY % h6.attlist  \"INCLUDE\" ><![%h6.attlist;[<!ATTLIST %h6.qname;%Common.attrib;><!-- end of h6.attlist -->]]>\n\n    <!-- end of xhtml-blkphras-1.mod -->\n"
  },
  {
    "path": "modules/book/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-blkpres-1.mod",
    "content": "<!-- ...................................................................... -->\n    <!-- XHTML Block Presentation Module  ..................................... -->\n    <!-- file: xhtml-blkpres-1.mod\n\n         This is XHTML, a reformulation of HTML as a modular XML application.\n         Copyright 1998-2005 W3C (MIT, ERCIM, Keio), All Rights Reserved.\n         Revision: $Id: xhtml-blkpres-1.mod,v 1.1 2010/07/29 13:42:46 bertails Exp $ SMI\n\n         This DTD module is identified by the PUBLIC and SYSTEM identifiers:\n\n           PUBLIC \"-//W3C//ELEMENTS XHTML Block Presentation 1.0//EN\"\n           SYSTEM \"http://www.w3.org/MarkUp/DTD/xhtml-blkpres-1.mod\"\n\n         Revisions:\n         (none)\n         ....................................................................... -->\n\n    <!-- Block Presentational Elements\n\n            hr\n\n         This module declares the elements and their attributes used to\n         support block-level presentational markup.\n    -->\n\n    <!ENTITY % hr.element  \"INCLUDE\" ><![%hr.element;[<!ENTITY % hr.content  \"EMPTY\" ><!ENTITY % hr.qname  \"hr\" ><!ELEMENT %hr.qname;  %hr.content; ><!-- end of hr.element -->]]>\n\n    <!ENTITY % hr.attlist  \"INCLUDE\" ><![%hr.attlist;[<!ATTLIST %hr.qname;%Common.attrib;><!-- end of hr.attlist -->]]>\n\n    <!-- end of xhtml-blkpres-1.mod -->\n"
  },
  {
    "path": "modules/book/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-blkstruct-1.mod",
    "content": "<!-- ...................................................................... -->\n    <!-- XHTML Block Structural Module  ....................................... -->\n    <!-- file: xhtml-blkstruct-1.mod\n\n         This is XHTML, a reformulation of HTML as a modular XML application.\n         Copyright 1998-2005 W3C (MIT, ERCIM, Keio), All Rights Reserved.\n         Revision: $Id: xhtml-blkstruct-1.mod,v 1.1 2010/07/29 13:42:46 bertails Exp $ SMI\n\n         This DTD module is identified by the PUBLIC and SYSTEM identifiers:\n\n           PUBLIC \"-//W3C//ELEMENTS XHTML Block Structural 1.0//EN\"\n           SYSTEM \"http://www.w3.org/MarkUp/DTD/xhtml-blkstruct-1.mod\"\n\n         Revisions:\n         (none)\n         ....................................................................... -->\n\n    <!-- Block Structural\n\n            div, p\n\n         This module declares the elements and their attributes used to\n         support block-level structural markup.\n    -->\n\n    <!ENTITY % div.element  \"INCLUDE\" ><![%div.element;[<!ENTITY % div.content\"( #PCDATA | %Flow.mix; )*\"><!ENTITY % div.qname  \"div\" ><!ELEMENT %div.qname;  %div.content; ><!-- end of div.element -->]]>\n\n    <!ENTITY % div.attlist  \"INCLUDE\" ><![%div.attlist;[<!ATTLIST %div.qname;%Common.attrib;><!-- end of div.attlist -->]]>\n\n    <!ENTITY % p.element  \"INCLUDE\" ><![%p.element;[<!ENTITY % p.content\"( #PCDATA | %Inline.mix; )*\" ><!ENTITY % p.qname  \"p\" ><!ELEMENT %p.qname;  %p.content; ><!-- end of p.element -->]]>\n\n    <!ENTITY % p.attlist  \"INCLUDE\" ><![%p.attlist;[<!ATTLIST %p.qname;%Common.attrib;><!-- end of p.attlist -->]]>\n\n    <!-- end of xhtml-blkstruct-1.mod -->\n"
  },
  {
    "path": "modules/book/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-charent-1.mod",
    "content": "<!-- ...................................................................... -->\n    <!-- XHTML Character Entities Module  ......................................... -->\n    <!-- file: xhtml-charent-1.mod\n\n         This is XHTML, a reformulation of HTML as a modular XML application.\n         Copyright 1998-2005 W3C (MIT, ERCIM, Keio), All Rights Reserved.\n         Revision: $Id: xhtml-charent-1.mod,v 1.1 2010/07/29 13:42:46 bertails Exp $ SMI\n\n         This DTD module is identified by the PUBLIC and SYSTEM identifiers:\n\n           PUBLIC \"-//W3C//ENTITIES XHTML Character Entities 1.0//EN\"\n           SYSTEM \"http://www.w3.org/MarkUp/DTD/xhtml-charent-1.mod\"\n\n         Revisions:\n         (none)\n         ....................................................................... -->\n\n    <!-- Character Entities for XHTML\n\n         This module declares the set of character entities for XHTML,\n         including the Latin 1, Symbol and Special character collections.\n    -->\n\n    <!ENTITY % xhtml-lat1PUBLIC \"-//W3C//ENTITIES Latin 1 for XHTML//EN\"\"xhtml-lat1.ent\" >%xhtml-lat1;\n\n    <!ENTITY % xhtml-symbolPUBLIC \"-//W3C//ENTITIES Symbols for XHTML//EN\"\"xhtml-symbol.ent\" >%xhtml-symbol;\n\n    <!ENTITY % xhtml-specialPUBLIC \"-//W3C//ENTITIES Special for XHTML//EN\"\"xhtml-special.ent\" >%xhtml-special;\n\n    <!-- end of xhtml-charent-1.mod -->\n"
  },
  {
    "path": "modules/book/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-csismap-1.mod",
    "content": "<!-- ...................................................................... -->\n    <!-- XHTML Client-side Image Map Module  .................................. -->\n    <!-- file: xhtml-csismap-1.mod\n\n         This is XHTML, a reformulation of HTML as a modular XML application.\n         Copyright 1998-2005 W3C (MIT, ERCIM, Keio), All Rights Reserved.\n         Revision: $Id: xhtml-csismap-1.mod,v 1.1 2010/07/29 13:42:47 bertails Exp $ SMI\n\n         This DTD module is identified by the PUBLIC and SYSTEM identifiers:\n\n           PUBLIC \"-//W3C//ELEMENTS XHTML Client-side Image Maps 1.0//EN\"\n           SYSTEM \"http://www.w3.org/MarkUp/DTD/xhtml-csismap-1.mod\"\n\n         Revisions:\n         (none)\n         ....................................................................... -->\n\n    <!-- Client-side Image Maps\n\n            area, map\n\n         This module declares elements and attributes to support client-side\n         image maps. This requires that the Image Module (or a module\n         declaring the img element type) be included in the DTD.\n\n         These can be placed in the same document or grouped in a\n         separate document, although the latter isn't widely supported\n    -->\n\n    <!ENTITY % area.element  \"INCLUDE\" ><![%area.element;[<!ENTITY % area.content  \"EMPTY\" ><!ENTITY % area.qname  \"area\" ><!ELEMENT %area.qname;  %area.content; ><!-- end of area.element -->]]>\n\n    <!ENTITY % Shape.datatype \"( rect | circle | poly | default )\"><!ENTITY % Coords.datatype \"CDATA\" >\n\n    <!ENTITY % area.attlist  \"INCLUDE\" ><![%area.attlist;[<!ATTLIST %area.qname;%Common.attrib;href         %URI.datatype;           #IMPLIEDshape        %Shape.datatype;         'rect'coords       %Coords.datatype;        #IMPLIEDnohref       ( nohref )               #IMPLIEDalt          %Text.datatype;          #REQUIREDtabindex     %Number.datatype;        #IMPLIEDaccesskey    %Character.datatype;     #IMPLIED><!-- end of area.attlist -->]]>\n\n    <!-- modify anchor attribute definition list\n         to allow for client-side image maps\n    -->\n    <!ATTLIST %a.qname;shape        %Shape.datatype;         'rect'coords       %Coords.datatype;        #IMPLIED>\n\n    <!-- modify img attribute definition list\n         to allow for client-side image maps\n    -->\n    <!ATTLIST %img.qname;usemap       %URIREF.datatype;        #IMPLIED>\n\n    <!-- modify form input attribute definition list\n         to allow for client-side image maps\n    -->\n    <!ATTLIST %input.qname;usemap       %URIREF.datatype;        #IMPLIED>\n\n    <!-- modify object attribute definition list\n         to allow for client-side image maps\n    -->\n    <!ATTLIST %object.qname;usemap       %URIREF.datatype;        #IMPLIED>\n\n    <!-- 'usemap' points to the 'id' attribute of a <map> element,\n         which must be in the same document; support for external\n         document maps was not widely supported in HTML and is\n         eliminated in XHTML.\n\n         It is considered an error for the element pointed to by\n         a usemap URIREF to occur in anything but a <map> element.\n    -->\n\n    <!ENTITY % map.element  \"INCLUDE\" ><![%map.element;[<!ENTITY % map.content\"(( %Block.mix; ) | %area.qname; )+\"><!ENTITY % map.qname  \"map\" ><!ELEMENT %map.qname;  %map.content; ><!-- end of map.element -->]]>\n\n    <!ENTITY % map.attlist  \"INCLUDE\" ><![%map.attlist;[<!ATTLIST %map.qname;%XHTML.xmlns.attrib;id           ID                       #REQUIRED%class.attrib;%title.attrib;%Core.extra.attrib;%I18n.attrib;%Events.attrib;><!-- end of map.attlist -->]]>\n\n    <!-- end of xhtml-csismap-1.mod -->\n"
  },
  {
    "path": "modules/book/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-datatypes-1.mod",
    "content": "<!-- ...................................................................... -->\n    <!-- XHTML Datatypes Module  .............................................. -->\n    <!-- file: xhtml-datatypes-1.mod\n\n         This is XHTML, a reformulation of HTML as a modular XML application.\n         Copyright 1998-2005 W3C (MIT, ERCIM, Keio), All Rights Reserved.\n         Revision: $Id: xhtml-datatypes-1.mod,v 1.1 2010/07/29 13:42:47 bertails Exp $ SMI\n\n         This DTD module is identified by the PUBLIC and SYSTEM identifiers:\n\n           PUBLIC \"-//W3C//ENTITIES XHTML Datatypes 1.0//EN\"\n           SYSTEM \"http://www.w3.org/MarkUp/DTD/xhtml-datatypes-1.mod\"\n\n         Revisions:\n         (none)\n         ....................................................................... -->\n\n    <!-- Datatypes\n\n         defines containers for the following datatypes, many of\n         these imported from other specifications and standards.\n    -->\n\n    <!-- Length defined for cellpadding/cellspacing -->\n\n    <!-- nn for pixels or nn% for percentage length -->\n    <!ENTITY % Length.datatype \"CDATA\" >\n\n    <!-- space-separated list of link types -->\n    <!ENTITY % LinkTypes.datatype \"NMTOKENS\" >\n\n    <!-- single or comma-separated list of media descriptors -->\n    <!ENTITY % MediaDesc.datatype \"CDATA\" >\n\n    <!-- pixel, percentage, or relative -->\n    <!ENTITY % MultiLength.datatype \"CDATA\" >\n\n    <!-- one or more digits (NUMBER) -->\n    <!ENTITY % Number.datatype \"CDATA\" >\n\n    <!-- integer representing length in pixels -->\n    <!ENTITY % Pixels.datatype \"CDATA\" >\n\n    <!-- script expression -->\n    <!ENTITY % Script.datatype \"CDATA\" >\n\n    <!-- textual content -->\n    <!ENTITY % Text.datatype \"CDATA\" >\n\n    <!-- Placeholder Compact URI-related types -->\n    <!ENTITY % CURIE.datatype \"CDATA\" ><!ENTITY % CURIEs.datatype \"CDATA\" ><!ENTITY % SafeCURIE.datatype \"CDATA\" ><!ENTITY % SafeCURIEs.datatype \"CDATA\" ><!ENTITY % URIorSafeCURIE.datatype \"CDATA\" ><!ENTITY % URIorSafeCURIEs.datatype \"CDATA\" >\n\n    <!-- Imported Datatypes ................................ -->\n\n    <!-- a single character from [ISO10646] -->\n    <!ENTITY % Character.datatype \"CDATA\" >\n\n    <!-- a character encoding, as per [RFC2045] -->\n    <!ENTITY % Charset.datatype \"CDATA\" >\n\n    <!-- a space separated list of character encodings, as per [RFC2045] -->\n    <!ENTITY % Charsets.datatype \"CDATA\" >\n\n    <!-- Color specification using color name or sRGB (#RRGGBB) values -->\n    <!ENTITY % Color.datatype \"CDATA\" >\n\n    <!-- media type, as per [RFC2045] -->\n    <!ENTITY % ContentType.datatype \"CDATA\" >\n\n    <!-- comma-separated list of media types, as per [RFC2045] -->\n    <!ENTITY % ContentTypes.datatype \"CDATA\" >\n\n    <!-- date and time information. ISO date format -->\n    <!ENTITY % Datetime.datatype \"CDATA\" >\n\n    <!-- formal public identifier, as per [ISO8879] -->\n    <!ENTITY % FPI.datatype \"CDATA\" >\n\n    <!-- a language code, as per [RFC3066] or its successor -->\n    <!ENTITY % LanguageCode.datatype \"CDATA\" >\n\n    <!-- a comma separated list of language code ranges -->\n    <!ENTITY % LanguageCodes.datatype \"CDATA\" >\n\n    <!-- a qualified name , as per [XMLNS] or its successor -->\n    <!ENTITY % QName.datatype \"CDATA\" ><!ENTITY % QNames.datatype \"CDATA\" >\n\n    <!-- a Uniform Resource Identifier, see [URI] -->\n    <!ENTITY % URI.datatype \"CDATA\" >\n\n    <!-- a space-separated list of Uniform Resource Identifiers, see [URI] -->\n    <!ENTITY % URIs.datatype \"CDATA\" >\n\n    <!-- a relative URI reference consisting of an initial '#' and a fragment ID -->\n    <!ENTITY % URIREF.datatype \"CDATA\" >\n\n    <!-- end of xhtml-datatypes-1.mod -->\n"
  },
  {
    "path": "modules/book/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-datatypes-1.mod.1",
    "content": "<!-- ...................................................................... -->\n<!-- XHTML Datatypes Module  .............................................. -->\n<!-- file: xhtml-datatypes-1.mod\n\n     This is XHTML, a reformulation of HTML as a modular XML application.\n     Copyright 1998-2005 W3C (MIT, ERCIM, Keio), All Rights Reserved.\n     Revision: $Id: xhtml-datatypes-1.mod,v 1.1 2010/07/29 13:42:47 bertails Exp $ SMI\n\n     This DTD module is identified by the PUBLIC and SYSTEM identifiers:\n\n       PUBLIC \"-//W3C//ENTITIES XHTML Datatypes 1.0//EN\"\n       SYSTEM \"http://www.w3.org/MarkUp/DTD/xhtml-datatypes-1.mod\"\n\n     Revisions:\n     (none)\n     ....................................................................... -->\n\n<!-- Datatypes\n\n     defines containers for the following datatypes, many of\n     these imported from other specifications and standards.\n-->\n\n<!-- Length defined for cellpadding/cellspacing -->\n\n<!-- nn for pixels or nn% for percentage length -->\n<!ENTITY % Length.datatype \"CDATA\" >\n\n<!-- space-separated list of link types -->\n<!ENTITY % LinkTypes.datatype \"NMTOKENS\" >\n\n<!-- single or comma-separated list of media descriptors -->\n<!ENTITY % MediaDesc.datatype \"CDATA\" >\n\n<!-- pixel, percentage, or relative -->\n<!ENTITY % MultiLength.datatype \"CDATA\" >\n\n<!-- one or more digits (NUMBER) -->\n<!ENTITY % Number.datatype \"CDATA\" >\n\n<!-- integer representing length in pixels -->\n<!ENTITY % Pixels.datatype \"CDATA\" >\n\n<!-- script expression -->\n<!ENTITY % Script.datatype \"CDATA\" >\n\n<!-- textual content -->\n<!ENTITY % Text.datatype \"CDATA\" >\n\n<!-- Placeholder Compact URI-related types -->\n<!ENTITY % CURIE.datatype \"CDATA\" >\n<!ENTITY % CURIEs.datatype \"CDATA\" >\n<!ENTITY % SafeCURIE.datatype \"CDATA\" >\n<!ENTITY % SafeCURIEs.datatype \"CDATA\" >\n<!ENTITY % URIorSafeCURIE.datatype \"CDATA\" >\n<!ENTITY % URIorSafeCURIEs.datatype \"CDATA\" >\n\n<!-- Imported Datatypes ................................ -->\n\n<!-- a single character from [ISO10646] -->\n<!ENTITY % Character.datatype \"CDATA\" >\n\n<!-- a character encoding, as per [RFC2045] -->\n<!ENTITY % Charset.datatype \"CDATA\" >\n\n<!-- a space separated list of character encodings, as per [RFC2045] -->\n<!ENTITY % Charsets.datatype \"CDATA\" >\n\n<!-- Color specification using color name or sRGB (#RRGGBB) values -->\n<!ENTITY % Color.datatype \"CDATA\" >\n\n<!-- media type, as per [RFC2045] -->\n<!ENTITY % ContentType.datatype \"CDATA\" >\n\n<!-- comma-separated list of media types, as per [RFC2045] -->\n<!ENTITY % ContentTypes.datatype \"CDATA\" >\n\n<!-- date and time information. ISO date format -->\n<!ENTITY % Datetime.datatype \"CDATA\" >\n\n<!-- formal public identifier, as per [ISO8879] -->\n<!ENTITY % FPI.datatype \"CDATA\" >\n\n<!-- a language code, as per [RFC3066] or its successor -->\n<!ENTITY % LanguageCode.datatype \"CDATA\" >\n\n<!-- a comma separated list of language code ranges -->\n<!ENTITY % LanguageCodes.datatype \"CDATA\" >\n\n<!-- a qualified name , as per [XMLNS] or its successor -->\n<!ENTITY % QName.datatype \"CDATA\" >\n<!ENTITY % QNames.datatype \"CDATA\" >\n\n<!-- a Uniform Resource Identifier, see [URI] -->\n<!ENTITY % URI.datatype \"CDATA\" >\n\n<!-- a space-separated list of Uniform Resource Identifiers, see [URI] -->\n<!ENTITY % URIs.datatype \"CDATA\" >\n\n<!-- a relative URI reference consisting of an initial '#' and a fragment ID -->\n<!ENTITY % URIREF.datatype \"CDATA\" >\n\n<!-- end of xhtml-datatypes-1.mod -->\n"
  },
  {
    "path": "modules/book/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-edit-1.mod",
    "content": "<!-- ...................................................................... -->\n    <!-- XHTML Editing Elements Module  ....................................... -->\n    <!-- file: xhtml-edit-1.mod\n\n         This is XHTML, a reformulation of HTML as a modular XML application.\n         Copyright 1998-2005 W3C (MIT, ERCIM, Keio), All Rights Reserved.\n         Revision: $Id: xhtml-edit-1.mod,v 1.1 2010/07/29 13:42:47 bertails Exp $ SMI\n\n         This DTD module is identified by the PUBLIC and SYSTEM identifiers:\n\n           PUBLIC \"-//W3C//ELEMENTS XHTML Editing Markup 1.0//EN\"\n           SYSTEM \"http://www.w3.org/MarkUp/DTD/xhtml-edit-1.mod\"\n\n         Revisions:\n         (none)\n         ....................................................................... -->\n\n    <!-- Editing Elements\n\n            ins, del\n\n         This module declares element types and attributes used to indicate\n         inserted and deleted content while editing a document.\n    -->\n\n    <!-- ins: Inserted Text  ............................... -->\n\n    <!ENTITY % ins.element  \"INCLUDE\" ><![%ins.element;[<!ENTITY % ins.content\"( #PCDATA | %Flow.mix; )*\"><!ENTITY % ins.qname  \"ins\" ><!ELEMENT %ins.qname;  %ins.content; ><!-- end of ins.element -->]]>\n\n    <!ENTITY % ins.attlist  \"INCLUDE\" ><![%ins.attlist;[<!ATTLIST %ins.qname;%Common.attrib;cite         %URI.datatype;           #IMPLIEDdatetime     %Datetime.datatype;      #IMPLIED><!-- end of ins.attlist -->]]>\n\n    <!-- del: Deleted Text  ................................ -->\n\n    <!ENTITY % del.element  \"INCLUDE\" ><![%del.element;[<!ENTITY % del.content\"( #PCDATA | %Flow.mix; )*\"><!ENTITY % del.qname  \"del\" ><!ELEMENT %del.qname;  %del.content; ><!-- end of del.element -->]]>\n\n    <!ENTITY % del.attlist  \"INCLUDE\" ><![%del.attlist;[<!ATTLIST %del.qname;%Common.attrib;cite         %URI.datatype;           #IMPLIEDdatetime     %Datetime.datatype;      #IMPLIED><!-- end of del.attlist -->]]>\n\n    <!-- end of xhtml-edit-1.mod -->\n"
  },
  {
    "path": "modules/book/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-events-1.mod",
    "content": "<!-- ...................................................................... -->\n    <!-- XHTML Intrinsic Events Module  ....................................... -->\n    <!-- file: xhtml-events-1.mod\n\n         This is XHTML, a reformulation of HTML as a modular XML application.\n         Copyright 1998-2005 W3C (MIT, ERCIM, Keio), All Rights Reserved.\n         Revision: $Id: xhtml-events-1.mod,v 1.1 2010/07/29 13:42:47 bertails Exp $ SMI\n\n         This DTD module is identified by the PUBLIC and SYSTEM identifiers:\n\n           PUBLIC \"-//W3C//ENTITIES XHTML Intrinsic Events 1.0//EN\"\n           SYSTEM \"http://www.w3.org/MarkUp/DTD/xhtml-events-1.mod\"\n\n         Revisions:\n         (none)\n         ....................................................................... -->\n\n    <!-- Intrinsic Event Attributes\n\n         These are the event attributes defined in HTML 4,\n         Section 18.2.3 \"Intrinsic Events\". This module must be\n         instantiated prior to the Attributes Module but after\n         the Datatype Module in the Modular Framework module.\n\n        \"Note: Authors of HTML documents are advised that changes\n         are likely to occur in the realm of intrinsic events\n         (e.g., how scripts are bound to events). Research in\n         this realm is carried on by members of the W3C Document\n         Object Model Working Group (see the W3C Web site at\n         http://www.w3.org/ for more information).\"\n    -->\n    <!-- NOTE: Because the ATTLIST declarations in this module occur\n         before their respective ELEMENT declarations in other\n         modules, there may be a dependency on this module that\n         should be considered if any of the parameter entities used\n         for element type names (eg., %a.qname;) are redeclared.\n    -->\n\n    <!ENTITY % Events.attrib\"onclick      %Script.datatype;        #IMPLIED\n      ondblclick   %Script.datatype;        #IMPLIED\n      onmousedown  %Script.datatype;        #IMPLIED\n      onmouseup    %Script.datatype;        #IMPLIED\n      onmouseover  %Script.datatype;        #IMPLIED\n      onmousemove  %Script.datatype;        #IMPLIED\n      onmouseout   %Script.datatype;        #IMPLIED\n      onkeypress   %Script.datatype;        #IMPLIED\n      onkeydown    %Script.datatype;        #IMPLIED\n      onkeyup      %Script.datatype;        #IMPLIED\">\n\n    <![%XHTML.global.attrs.prefixed;[<!ENTITY % XHTML.global.events.attrib\"%XHTML.prefix;:onclick      %Script.datatype;        #IMPLIED\n      %XHTML.prefix;:ondblclick   %Script.datatype;        #IMPLIED\n      %XHTML.prefix;:onmousedown  %Script.datatype;        #IMPLIED\n      %XHTML.prefix;:onmouseup    %Script.datatype;        #IMPLIED\n      %XHTML.prefix;:onmouseover  %Script.datatype;        #IMPLIED\n      %XHTML.prefix;:onmousemove  %Script.datatype;        #IMPLIED\n      %XHTML.prefix;:onmouseout   %Script.datatype;        #IMPLIED\n      %XHTML.prefix;:onkeypress   %Script.datatype;        #IMPLIED\n      %XHTML.prefix;:onkeydown    %Script.datatype;        #IMPLIED\n      %XHTML.prefix;:onkeyup      %Script.datatype;        #IMPLIED\">]]>\n\n    <!-- additional attributes on anchor element\n    -->\n    <!ATTLIST %a.qname;onfocus      %Script.datatype;         #IMPLIEDonblur       %Script.datatype;         #IMPLIED>\n\n    <!-- additional attributes on form element\n    -->\n    <!ATTLIST %form.qname;onsubmit     %Script.datatype;        #IMPLIEDonreset      %Script.datatype;        #IMPLIED>\n\n    <!-- additional attributes on label element\n    -->\n    <!ATTLIST %label.qname;onfocus      %Script.datatype;        #IMPLIEDonblur       %Script.datatype;        #IMPLIED>\n\n    <!-- additional attributes on input element\n    -->\n    <!ATTLIST %input.qname;onfocus      %Script.datatype;        #IMPLIEDonblur       %Script.datatype;        #IMPLIEDonselect     %Script.datatype;        #IMPLIEDonchange     %Script.datatype;        #IMPLIED>\n\n    <!-- additional attributes on select element\n    -->\n    <!ATTLIST %select.qname;onfocus      %Script.datatype;        #IMPLIEDonblur       %Script.datatype;        #IMPLIEDonchange     %Script.datatype;        #IMPLIED>\n\n    <!-- additional attributes on textarea element\n    -->\n    <!ATTLIST %textarea.qname;onfocus      %Script.datatype;        #IMPLIEDonblur       %Script.datatype;        #IMPLIEDonselect     %Script.datatype;        #IMPLIEDonchange     %Script.datatype;        #IMPLIED>\n\n    <!-- additional attributes on button element\n    -->\n    <!ATTLIST %button.qname;onfocus      %Script.datatype;        #IMPLIEDonblur       %Script.datatype;        #IMPLIED>\n\n    <!-- additional attributes on body element\n    -->\n    <!ATTLIST %body.qname;onload       %Script.datatype;        #IMPLIEDonunload     %Script.datatype;        #IMPLIED>\n\n    <!-- additional attributes on area element\n    -->\n    <!ATTLIST %area.qname;onfocus      %Script.datatype;        #IMPLIEDonblur       %Script.datatype;        #IMPLIED>\n\n    <!-- end of xhtml-events-1.mod -->\n"
  },
  {
    "path": "modules/book/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-form-1.mod",
    "content": "<!-- ...................................................................... -->\n    <!-- XHTML Forms Module  .................................................. -->\n    <!-- file: xhtml-form-1.mod\n\n         This is XHTML, a reformulation of HTML as a modular XML application.\n         Copyright 1998-2005 W3C (MIT, ERCIM, Keio), All Rights Reserved.\n         Revision: $Id: xhtml-form-1.mod,v 1.1 2010/07/29 13:42:47 bertails Exp $ SMI\n\n         This DTD module is identified by the PUBLIC and SYSTEM identifiers:\n\n           PUBLIC \"-//W3C//ELEMENTS XHTML Forms 1.0//EN\"\n           SYSTEM \"http://www.w3.org/MarkUp/DTD/xhtml-form-1.mod\"\n\n         Revisions:\n         (none)\n         ....................................................................... -->\n\n    <!-- Forms\n\n            form, label, input, select, optgroup, option,\n            textarea, fieldset, legend, button\n\n         This module declares markup to provide support for online\n         forms, based on the features found in HTML 4 forms.\n    -->\n\n    <!-- declare qualified element type names:\n    -->\n    <!ENTITY % form.qname  \"form\" ><!ENTITY % label.qname  \"label\" ><!ENTITY % input.qname  \"input\" ><!ENTITY % select.qname  \"select\" ><!ENTITY % optgroup.qname  \"optgroup\" ><!ENTITY % option.qname  \"option\" ><!ENTITY % textarea.qname  \"textarea\" ><!ENTITY % fieldset.qname  \"fieldset\" ><!ENTITY % legend.qname  \"legend\" ><!ENTITY % button.qname  \"button\" >\n\n    <!-- %BlkNoForm.mix; includes all non-form block elements,\n         plus %Misc.class;\n    -->\n    <!ENTITY % BlkNoForm.mix\"%Heading.class;\n      | %List.class;\n      | %BlkStruct.class;\n      %BlkPhras.class;\n      %BlkPres.class;\n      %Table.class;\n      %Block.extra;\n      %Misc.class;\">\n\n    <!-- form: Form Element ................................ -->\n\n    <!ENTITY % form.element  \"INCLUDE\" ><![%form.element;[<!ENTITY % form.content\"( %BlkNoForm.mix;\n      | %fieldset.qname; )+\"><!ELEMENT %form.qname;  %form.content; ><!-- end of form.element -->]]>\n\n    <!ENTITY % form.attlist  \"INCLUDE\" ><![%form.attlist;[<!ATTLIST %form.qname;%Common.attrib;action       %URI.datatype;           #REQUIREDmethod       ( get | post )           'get'name         CDATA                    #IMPLIEDenctype      %ContentType.datatype;   'application/x-www-form-urlencoded'accept-charset %Charsets.datatype;    #IMPLIEDaccept       %ContentTypes.datatype;  #IMPLIED><!-- end of form.attlist -->]]>\n\n    <!-- label: Form Field Label Text ...................... -->\n\n    <!-- Each label must not contain more than ONE field\n    -->\n\n    <!ENTITY % label.element  \"INCLUDE\" ><![%label.element;[<!ENTITY % label.content\"( #PCDATA\n      | %input.qname; | %select.qname; | %textarea.qname; | %button.qname;\n      | %InlStruct.class;\n      %InlPhras.class;\n      %I18n.class;\n      %InlPres.class;\n      %Anchor.class;\n      %InlSpecial.class;\n      %Inline.extra;\n      %Misc.class; )*\"><!ELEMENT %label.qname;  %label.content; ><!-- end of label.element -->]]>\n\n    <!ENTITY % label.attlist  \"INCLUDE\" ><![%label.attlist;[<!ATTLIST %label.qname;%Common.attrib;for          IDREF                    #IMPLIEDaccesskey    %Character.datatype;     #IMPLIED><!-- end of label.attlist -->]]>\n\n    <!-- input: Form Control ............................... -->\n\n    <!ENTITY % input.element  \"INCLUDE\" ><![%input.element;[<!ENTITY % input.content  \"EMPTY\" ><!ELEMENT %input.qname;  %input.content; ><!-- end of input.element -->]]>\n\n    <!ENTITY % input.attlist  \"INCLUDE\" ><![%input.attlist;[<!ENTITY % InputType.class\"( text | password | checkbox | radio | submit\n      | reset | file | hidden | image | button )\"><!-- attribute 'name' required for all but submit & reset\n-->\n    <!ATTLIST %input.qname;%Common.attrib;type         %InputType.class;        'text'name         CDATA                    #IMPLIEDvalue        CDATA                    #IMPLIEDchecked      ( checked )              #IMPLIEDdisabled     ( disabled )             #IMPLIEDreadonly     ( readonly )             #IMPLIEDsize         %Number.datatype;        #IMPLIEDmaxlength    %Number.datatype;        #IMPLIEDsrc          %URI.datatype;           #IMPLIEDalt          %Text.datatype;          #IMPLIEDtabindex     %Number.datatype;        #IMPLIEDaccesskey    %Character.datatype;     #IMPLIEDaccept       %ContentTypes.datatype;  #IMPLIED><!-- end of input.attlist -->]]>\n\n    <!-- select: Option Selector ........................... -->\n\n    <!ENTITY % select.element  \"INCLUDE\" ><![%select.element;[<!ENTITY % select.content\"( %optgroup.qname; | %option.qname; )+\"><!ELEMENT %select.qname;  %select.content; ><!-- end of select.element -->]]>\n\n    <!ENTITY % select.attlist  \"INCLUDE\" ><![%select.attlist;[<!ATTLIST %select.qname;%Common.attrib;name         CDATA                    #IMPLIEDsize         %Number.datatype;        #IMPLIEDmultiple     ( multiple )             #IMPLIEDdisabled     ( disabled )             #IMPLIEDtabindex     %Number.datatype;        #IMPLIED><!-- end of select.attlist -->]]>\n\n    <!-- optgroup: Option Group ............................ -->\n\n    <!ENTITY % optgroup.element  \"INCLUDE\" ><![%optgroup.element;[<!ENTITY % optgroup.content  \"( %option.qname; )+\" ><!ELEMENT %optgroup.qname;  %optgroup.content; ><!-- end of optgroup.element -->]]>\n\n    <!ENTITY % optgroup.attlist  \"INCLUDE\" ><![%optgroup.attlist;[<!ATTLIST %optgroup.qname;%Common.attrib;disabled     ( disabled )             #IMPLIEDlabel        %Text.datatype;          #REQUIRED><!-- end of optgroup.attlist -->]]>\n\n    <!-- option: Selectable Choice ......................... -->\n\n    <!ENTITY % option.element  \"INCLUDE\" ><![%option.element;[<!ENTITY % option.content  \"( #PCDATA )\" ><!ELEMENT %option.qname;  %option.content; ><!-- end of option.element -->]]>\n\n    <!ENTITY % option.attlist  \"INCLUDE\" ><![%option.attlist;[<!ATTLIST %option.qname;%Common.attrib;selected     ( selected )             #IMPLIEDdisabled     ( disabled )             #IMPLIEDlabel        %Text.datatype;          #IMPLIEDvalue        CDATA                    #IMPLIED><!-- end of option.attlist -->]]>\n\n    <!-- textarea: Multi-Line Text Field ................... -->\n\n    <!ENTITY % textarea.element  \"INCLUDE\" ><![%textarea.element;[<!ENTITY % textarea.content  \"( #PCDATA )\" ><!ELEMENT %textarea.qname;  %textarea.content; ><!-- end of textarea.element -->]]>\n\n    <!ENTITY % textarea.attlist  \"INCLUDE\" ><![%textarea.attlist;[<!ATTLIST %textarea.qname;%Common.attrib;name         CDATA                    #IMPLIEDrows         %Number.datatype;        #REQUIREDcols         %Number.datatype;        #REQUIREDdisabled     ( disabled )             #IMPLIEDreadonly     ( readonly )             #IMPLIEDtabindex     %Number.datatype;        #IMPLIEDaccesskey    %Character.datatype;     #IMPLIED><!-- end of textarea.attlist -->]]>\n\n    <!-- fieldset: Form Control Group ...................... -->\n\n    <!-- #PCDATA is to solve the mixed content problem,\n         per specification only whitespace is allowed\n    -->\n\n    <!ENTITY % fieldset.element  \"INCLUDE\" ><![%fieldset.element;[<!ENTITY % fieldset.content\"( #PCDATA | %legend.qname; | %Flow.mix; )*\"><!ELEMENT %fieldset.qname;  %fieldset.content; ><!-- end of fieldset.element -->]]>\n\n    <!ENTITY % fieldset.attlist  \"INCLUDE\" ><![%fieldset.attlist;[<!ATTLIST %fieldset.qname;%Common.attrib;><!-- end of fieldset.attlist -->]]>\n\n    <!-- legend: Fieldset Legend ........................... -->\n\n    <!ENTITY % legend.element  \"INCLUDE\" ><![%legend.element;[<!ENTITY % legend.content\"( #PCDATA | %Inline.mix; )*\"><!ELEMENT %legend.qname;  %legend.content; ><!-- end of legend.element -->]]>\n\n    <!ENTITY % legend.attlist  \"INCLUDE\" ><![%legend.attlist;[<!ATTLIST %legend.qname;%Common.attrib;accesskey    %Character.datatype;     #IMPLIED><!-- end of legend.attlist -->]]>\n\n    <!-- button: Push Button ............................... -->\n\n    <!ENTITY % button.element  \"INCLUDE\" ><![%button.element;[<!ENTITY % button.content\"( #PCDATA\n      | %BlkNoForm.mix;\n      | %InlStruct.class;\n      %InlPhras.class;\n      %InlPres.class;\n      %I18n.class;\n      %InlSpecial.class;\n      %Inline.extra; )*\"><!ELEMENT %button.qname;  %button.content; ><!-- end of button.element -->]]>\n\n    <!ENTITY % button.attlist  \"INCLUDE\" ><![%button.attlist;[<!ATTLIST %button.qname;%Common.attrib;name         CDATA                    #IMPLIEDvalue        CDATA                    #IMPLIEDtype         ( button | submit | reset ) 'submit'disabled     ( disabled )             #IMPLIEDtabindex     %Number.datatype;        #IMPLIEDaccesskey    %Character.datatype;     #IMPLIED><!-- end of button.attlist -->]]>\n\n    <!-- end of xhtml-form-1.mod -->\n"
  },
  {
    "path": "modules/book/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-framework-1.mod",
    "content": "<!-- ...................................................................... -->\n    <!-- XHTML Modular Framework Module  ...................................... -->\n    <!-- file: xhtml-framework-1.mod\n\n         This is XHTML, a reformulation of HTML as a modular XML application.\n         Copyright 1998-2005 W3C (MIT, ERCIM, Keio), All Rights Reserved.\n         Revision: $Id: xhtml-framework-1.mod,v 1.1 2010/07/29 13:42:47 bertails Exp $ SMI\n\n         This DTD module is identified by the PUBLIC and SYSTEM identifiers:\n\n           PUBLIC \"-//W3C//ENTITIES XHTML Modular Framework 1.0//EN\"\n           SYSTEM \"http://www.w3.org/MarkUp/DTD/xhtml-framework-1.mod\"\n\n         Revisions:\n         (none)\n         ....................................................................... -->\n\n    <!-- Modular Framework\n\n         This required module instantiates the modules needed\n         to support the XHTML modularization model, including:\n\n            +  datatypes\n            +  namespace-qualified names\n            +  common attributes\n            +  document model\n            +  character entities\n\n         The Intrinsic Events module is ignored by default but\n         occurs in this module because it must be instantiated\n         prior to Attributes but after Datatypes.\n    -->\n\n    <!ENTITY % xhtml-arch.module \"IGNORE\" ><![%xhtml-arch.module;[<!ENTITY % xhtml-arch.modPUBLIC \"-//W3C//ELEMENTS XHTML Base Architecture 1.0//EN\"\"xhtml-arch-1.mod\" >%xhtml-arch.mod;]]>\n\n    <!ENTITY % xhtml-notations.module \"IGNORE\" ><![%xhtml-notations.module;[<!ENTITY % xhtml-notations.modPUBLIC \"-//W3C//NOTATIONS XHTML Notations 1.0//EN\"\"xhtml-notations-1.mod\" >%xhtml-notations.mod;]]>\n\n    <!ENTITY % xhtml-datatypes.module \"INCLUDE\" ><![%xhtml-datatypes.module;[<!ENTITY % xhtml-datatypes.modPUBLIC \"-//W3C//ENTITIES XHTML Datatypes 1.0//EN\"\"xhtml-datatypes-1.mod\" >%xhtml-datatypes.mod;]]>\n\n    <!-- placeholder for XLink support module -->\n    <!ENTITY % xhtml-xlink.mod \"\" >%xhtml-xlink.mod;\n\n    <!ENTITY % xhtml-qname.module \"INCLUDE\" ><![%xhtml-qname.module;[<!ENTITY % xhtml-qname.modPUBLIC \"-//W3C//ENTITIES XHTML Qualified Names 1.0//EN\"\"xhtml-qname-1.mod\" >%xhtml-qname.mod;]]>\n\n    <!ENTITY % xhtml-events.module \"IGNORE\" ><![%xhtml-events.module;[<!ENTITY % xhtml-events.modPUBLIC \"-//W3C//ENTITIES XHTML Intrinsic Events 1.0//EN\"\"xhtml-events-1.mod\" >%xhtml-events.mod;]]>\n\n    <!ENTITY % xhtml-attribs.module \"INCLUDE\" ><![%xhtml-attribs.module;[<!ENTITY % xhtml-attribs.modPUBLIC \"-//W3C//ENTITIES XHTML Common Attributes 1.0//EN\"\"xhtml-attribs-1.mod\" >%xhtml-attribs.mod;]]>\n\n    <!-- placeholder for content model redeclarations -->\n    <!ENTITY % xhtml-model.redecl \"\" >%xhtml-model.redecl;\n\n    <!ENTITY % xhtml-model.module \"INCLUDE\" ><![%xhtml-model.module;[<!-- instantiate the Document Model module declared in the DTD driver\n-->\n    %xhtml-model.mod;]]>\n\n    <!ENTITY % xhtml-charent.module \"INCLUDE\" ><![%xhtml-charent.module;[<!ENTITY % xhtml-charent.modPUBLIC \"-//W3C//ENTITIES XHTML Character Entities 1.0//EN\"\"xhtml-charent-1.mod\" >%xhtml-charent.mod;]]>\n\n    <!-- end of xhtml-framework-1.mod -->\n"
  },
  {
    "path": "modules/book/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-hypertext-1.mod",
    "content": "<!-- ...................................................................... -->\n    <!-- XHTML Hypertext Module  .............................................. -->\n    <!-- file: xhtml-hypertext-1.mod\n\n         This is XHTML, a reformulation of HTML as a modular XML application.\n         Copyright 1998-2005 W3C (MIT, ERCIM, Keio), All Rights Reserved.\n         Revision: $Id: xhtml-hypertext-1.mod,v 1.1 2010/07/29 13:42:47 bertails Exp $ SMI\n\n         This DTD module is identified by the PUBLIC and SYSTEM identifiers:\n\n           PUBLIC \"-//W3C//ELEMENTS XHTML Hypertext 1.0//EN\"\n           SYSTEM \"http://www.w3.org/MarkUp/DTD/xhtml-hypertext-1.mod\"\n\n         Revisions:\n         (none)\n         ....................................................................... -->\n\n    <!-- Hypertext\n\n            a\n\n         This module declares the anchor ('a') element type, which\n         defines the source of a hypertext link. The destination\n         (or link 'target') is identified via its 'id' attribute\n         rather than the 'name' attribute as was used in HTML.\n    -->\n\n    <!-- ............  Anchor Element  ............ -->\n\n    <!ENTITY % a.element  \"INCLUDE\" ><![%a.element;[<!ENTITY % a.content\"( #PCDATA | %InlNoAnchor.mix; )*\"><!ENTITY % a.qname  \"a\" ><!ELEMENT %a.qname;  %a.content; ><!-- end of a.element -->]]>\n\n    <!ENTITY % a.attlist  \"INCLUDE\" ><![%a.attlist;[<!ATTLIST %a.qname;%Common.attrib;href         %URI.datatype;           #IMPLIEDcharset      %Charset.datatype;       #IMPLIEDtype         %ContentType.datatype;   #IMPLIEDhreflang     %LanguageCode.datatype;  #IMPLIEDrel          %LinkTypes.datatype;     #IMPLIEDrev          %LinkTypes.datatype;     #IMPLIEDaccesskey    %Character.datatype;     #IMPLIEDtabindex     %Number.datatype;        #IMPLIED><!-- end of a.attlist -->]]>\n\n    <!-- end of xhtml-hypertext-1.mod -->\n"
  },
  {
    "path": "modules/book/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-image-1.mod",
    "content": "<!-- ...................................................................... -->\n    <!-- XHTML Images Module  ................................................. -->\n    <!-- file: xhtml-image-1.mod\n\n         This is XHTML, a reformulation of HTML as a modular XML application.\n         Copyright 1998-2005 W3C (MIT, ERCIM, Keio), All Rights Reserved.\n         Rovision: $Id: xhtml-image-1.mod,v 1.1 2010/07/29 13:42:47 bertails Exp $ SMI\n\n         This DTD module is identified by the PUBLIC and SYSTEM identifiers:\n\n           PUBLIC \"-//W3C//ELEMENTS XHTML Images 1.0//EN\"\n           SYSTEM \"http://www.w3.org/MarkUp/DTD/xhtml-image-1.mod\"\n\n         Revisions:\n         (none)\n         ....................................................................... -->\n\n    <!-- Images\n\n            img\n\n         This module provides markup to support basic image embedding.\n    -->\n\n    <!-- To avoid problems with text-only UAs as well as to make\n         image content understandable and navigable to users of\n         non-visual UAs, you need to provide a description with\n         the 'alt' attribute, and avoid server-side image maps.\n    -->\n\n    <!ENTITY % img.element  \"INCLUDE\" ><![%img.element;[<!ENTITY % img.content  \"EMPTY\" ><!ENTITY % img.qname  \"img\" ><!ELEMENT %img.qname;  %img.content; ><!-- end of img.element -->]]>\n\n    <!ENTITY % img.attlist  \"INCLUDE\" ><![%img.attlist;[<!ATTLIST %img.qname;%Common.attrib;src          %URI.datatype;           #REQUIREDalt          %Text.datatype;          #REQUIREDlongdesc     %URI.datatype;           #IMPLIEDname         CDATA                    #IMPLIEDheight       %Length.datatype;        #IMPLIEDwidth        %Length.datatype;        #IMPLIED><!-- end of img.attlist -->]]>\n\n    <!-- end of xhtml-image-1.mod -->\n"
  },
  {
    "path": "modules/book/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-inlphras-1.mod",
    "content": "<!-- ...................................................................... -->\n    <!-- XHTML Inline Phrasal Module  ......................................... -->\n    <!-- file: xhtml-inlphras-1.mod\n\n         This is XHTML, a reformulation of HTML as a modular XML application.\n         Copyright 1998-2005 W3C (MIT, ERCIM, Keio), All Rights Reserved.\n         Revision: $Id: xhtml-inlphras-1.mod,v 1.1 2010/07/29 13:42:47 bertails Exp $ SMI\n\n         This DTD module is identified by the PUBLIC and SYSTEM identifiers:\n\n           PUBLIC \"-//W3C//ELEMENTS XHTML Inline Phrasal 1.0//EN\"\n           SYSTEM \"http://www.w3.org/MarkUp/DTD/xhtml-inlphras-1.mod\"\n\n         Revisions:\n         (none)\n         ....................................................................... -->\n\n    <!-- Inline Phrasal\n\n            abbr, acronym, cite, code, dfn, em, kbd, q, samp, strong, var\n\n         This module declares the elements and their attributes used to\n         support inline-level phrasal markup.\n    -->\n\n    <!ENTITY % abbr.element  \"INCLUDE\" ><![%abbr.element;[<!ENTITY % abbr.content\"( #PCDATA | %Inline.mix; )*\"><!ENTITY % abbr.qname  \"abbr\" ><!ELEMENT %abbr.qname;  %abbr.content; ><!-- end of abbr.element -->]]>\n\n    <!ENTITY % abbr.attlist  \"INCLUDE\" ><![%abbr.attlist;[<!ATTLIST %abbr.qname;%Common.attrib;><!-- end of abbr.attlist -->]]>\n\n    <!ENTITY % acronym.element  \"INCLUDE\" ><![%acronym.element;[<!ENTITY % acronym.content\"( #PCDATA | %Inline.mix; )*\"><!ENTITY % acronym.qname  \"acronym\" ><!ELEMENT %acronym.qname;  %acronym.content; ><!-- end of acronym.element -->]]>\n\n    <!ENTITY % acronym.attlist  \"INCLUDE\" ><![%acronym.attlist;[<!ATTLIST %acronym.qname;%Common.attrib;><!-- end of acronym.attlist -->]]>\n\n    <!ENTITY % cite.element  \"INCLUDE\" ><![%cite.element;[<!ENTITY % cite.content\"( #PCDATA | %Inline.mix; )*\"><!ENTITY % cite.qname  \"cite\" ><!ELEMENT %cite.qname;  %cite.content; ><!-- end of cite.element -->]]>\n\n    <!ENTITY % cite.attlist  \"INCLUDE\" ><![%cite.attlist;[<!ATTLIST %cite.qname;%Common.attrib;><!-- end of cite.attlist -->]]>\n\n    <!ENTITY % code.element  \"INCLUDE\" ><![%code.element;[<!ENTITY % code.content\"( #PCDATA | %Inline.mix; )*\"><!ENTITY % code.qname  \"code\" ><!ELEMENT %code.qname;  %code.content; ><!-- end of code.element -->]]>\n\n    <!ENTITY % code.attlist  \"INCLUDE\" ><![%code.attlist;[<!ATTLIST %code.qname;%Common.attrib;><!-- end of code.attlist -->]]>\n\n    <!ENTITY % dfn.element  \"INCLUDE\" ><![%dfn.element;[<!ENTITY % dfn.content\"( #PCDATA | %Inline.mix; )*\"><!ENTITY % dfn.qname  \"dfn\" ><!ELEMENT %dfn.qname;  %dfn.content; ><!-- end of dfn.element -->]]>\n\n    <!ENTITY % dfn.attlist  \"INCLUDE\" ><![%dfn.attlist;[<!ATTLIST %dfn.qname;%Common.attrib;><!-- end of dfn.attlist -->]]>\n\n    <!ENTITY % em.element  \"INCLUDE\" ><![%em.element;[<!ENTITY % em.content\"( #PCDATA | %Inline.mix; )*\"><!ENTITY % em.qname  \"em\" ><!ELEMENT %em.qname;  %em.content; ><!-- end of em.element -->]]>\n\n    <!ENTITY % em.attlist  \"INCLUDE\" ><![%em.attlist;[<!ATTLIST %em.qname;%Common.attrib;><!-- end of em.attlist -->]]>\n\n    <!ENTITY % kbd.element  \"INCLUDE\" ><![%kbd.element;[<!ENTITY % kbd.content\"( #PCDATA | %Inline.mix; )*\"><!ENTITY % kbd.qname  \"kbd\" ><!ELEMENT %kbd.qname;  %kbd.content; ><!-- end of kbd.element -->]]>\n\n    <!ENTITY % kbd.attlist  \"INCLUDE\" ><![%kbd.attlist;[<!ATTLIST %kbd.qname;%Common.attrib;><!-- end of kbd.attlist -->]]>\n\n    <!ENTITY % q.element  \"INCLUDE\" ><![%q.element;[<!ENTITY % q.content\"( #PCDATA | %Inline.mix; )*\"><!ENTITY % q.qname  \"q\" ><!ELEMENT %q.qname;  %q.content; ><!-- end of q.element -->]]>\n\n    <!ENTITY % q.attlist  \"INCLUDE\" ><![%q.attlist;[<!ATTLIST %q.qname;%Common.attrib;cite         %URI.datatype;           #IMPLIED><!-- end of q.attlist -->]]>\n\n    <!ENTITY % samp.element  \"INCLUDE\" ><![%samp.element;[<!ENTITY % samp.content\"( #PCDATA | %Inline.mix; )*\"><!ENTITY % samp.qname  \"samp\" ><!ELEMENT %samp.qname;  %samp.content; ><!-- end of samp.element -->]]>\n\n    <!ENTITY % samp.attlist  \"INCLUDE\" ><![%samp.attlist;[<!ATTLIST %samp.qname;%Common.attrib;><!-- end of samp.attlist -->]]>\n\n    <!ENTITY % strong.element  \"INCLUDE\" ><![%strong.element;[<!ENTITY % strong.content\"( #PCDATA | %Inline.mix; )*\"><!ENTITY % strong.qname  \"strong\" ><!ELEMENT %strong.qname;  %strong.content; ><!-- end of strong.element -->]]>\n\n    <!ENTITY % strong.attlist  \"INCLUDE\" ><![%strong.attlist;[<!ATTLIST %strong.qname;%Common.attrib;><!-- end of strong.attlist -->]]>\n\n    <!ENTITY % var.element  \"INCLUDE\" ><![%var.element;[<!ENTITY % var.content\"( #PCDATA | %Inline.mix; )*\"><!ENTITY % var.qname  \"var\" ><!ELEMENT %var.qname;  %var.content; ><!-- end of var.element -->]]>\n\n    <!ENTITY % var.attlist  \"INCLUDE\" ><![%var.attlist;[<!ATTLIST %var.qname;%Common.attrib;><!-- end of var.attlist -->]]>\n\n    <!-- end of xhtml-inlphras-1.mod -->\n"
  },
  {
    "path": "modules/book/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-inlpres-1.mod",
    "content": "<!-- ...................................................................... -->\n    <!-- XHTML Inline Presentation Module  .................................... -->\n    <!-- file: xhtml-inlpres-1.mod\n\n         This is XHTML, a reformulation of HTML as a modular XML application.\n         Copyright 1998-2005 W3C (MIT, ERCIM, Keio), All Rights Reserved.\n         Revision: $Id: xhtml-inlpres-1.mod,v 1.1 2010/07/29 13:42:47 bertails Exp $ SMI\n\n         This DTD module is identified by the PUBLIC and SYSTEM identifiers:\n\n           PUBLIC \"-//W3C//ELEMENTS XHTML Inline Presentation 1.0//EN\"\n           SYSTEM \"http://www.w3.org/MarkUp/DTD/xhtml-inlpres-1.mod\"\n\n         Revisions:\n         (none)\n         ....................................................................... -->\n\n    <!-- Inline Presentational Elements\n\n            b, big, i, small, sub, sup, tt\n\n         This module declares the elements and their attributes used to\n         support inline-level presentational markup.\n    -->\n\n    <!ENTITY % b.element  \"INCLUDE\" ><![%b.element;[<!ENTITY % b.content\"( #PCDATA | %Inline.mix; )*\"><!ENTITY % b.qname  \"b\" ><!ELEMENT %b.qname;  %b.content; ><!-- end of b.element -->]]>\n\n    <!ENTITY % b.attlist  \"INCLUDE\" ><![%b.attlist;[<!ATTLIST %b.qname;%Common.attrib;><!-- end of b.attlist -->]]>\n\n    <!ENTITY % big.element  \"INCLUDE\" ><![%big.element;[<!ENTITY % big.content\"( #PCDATA | %Inline.mix; )*\"><!ENTITY % big.qname  \"big\" ><!ELEMENT %big.qname;  %big.content; ><!-- end of big.element -->]]>\n\n    <!ENTITY % big.attlist  \"INCLUDE\" ><![%big.attlist;[<!ATTLIST %big.qname;%Common.attrib;><!-- end of big.attlist -->]]>\n\n    <!ENTITY % i.element  \"INCLUDE\" ><![%i.element;[<!ENTITY % i.content\"( #PCDATA | %Inline.mix; )*\"><!ENTITY % i.qname  \"i\" ><!ELEMENT %i.qname;  %i.content; ><!-- end of i.element -->]]>\n\n    <!ENTITY % i.attlist  \"INCLUDE\" ><![%i.attlist;[<!ATTLIST %i.qname;%Common.attrib;><!-- end of i.attlist -->]]>\n\n    <!ENTITY % small.element  \"INCLUDE\" ><![%small.element;[<!ENTITY % small.content\"( #PCDATA | %Inline.mix; )*\"><!ENTITY % small.qname  \"small\" ><!ELEMENT %small.qname;  %small.content; ><!-- end of small.element -->]]>\n\n    <!ENTITY % small.attlist  \"INCLUDE\" ><![%small.attlist;[<!ATTLIST %small.qname;%Common.attrib;><!-- end of small.attlist -->]]>\n\n    <!ENTITY % sub.element  \"INCLUDE\" ><![%sub.element;[<!ENTITY % sub.content\"( #PCDATA | %Inline.mix; )*\"><!ENTITY % sub.qname  \"sub\" ><!ELEMENT %sub.qname;  %sub.content; ><!-- end of sub.element -->]]>\n\n    <!ENTITY % sub.attlist  \"INCLUDE\" ><![%sub.attlist;[<!ATTLIST %sub.qname;%Common.attrib;><!-- end of sub.attlist -->]]>\n\n    <!ENTITY % sup.element  \"INCLUDE\" ><![%sup.element;[<!ENTITY % sup.content\"( #PCDATA | %Inline.mix; )*\"><!ENTITY % sup.qname  \"sup\" ><!ELEMENT %sup.qname;  %sup.content; ><!-- end of sup.element -->]]>\n\n    <!ENTITY % sup.attlist  \"INCLUDE\" ><![%sup.attlist;[<!ATTLIST %sup.qname;%Common.attrib;><!-- end of sup.attlist -->]]>\n\n    <!ENTITY % tt.element  \"INCLUDE\" ><![%tt.element;[<!ENTITY % tt.content\"( #PCDATA | %Inline.mix; )*\"><!ENTITY % tt.qname  \"tt\" ><!ELEMENT %tt.qname;  %tt.content; ><!-- end of tt.element -->]]>\n\n    <!ENTITY % tt.attlist  \"INCLUDE\" ><![%tt.attlist;[<!ATTLIST %tt.qname;%Common.attrib;><!-- end of tt.attlist -->]]>\n\n    <!-- end of xhtml-inlpres-1.mod -->\n"
  },
  {
    "path": "modules/book/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-inlstruct-1.mod",
    "content": "<!-- ...................................................................... -->\n    <!-- XHTML Inline Structural Module  ...................................... -->\n    <!-- file: xhtml-inlstruct-1.mod\n\n         This is XHTML, a reformulation of HTML as a modular XML application.\n         Copyright 1998-2005 W3C (MIT, ERCIM, Keio), All Rights Reserved.\n         Revision: $Id: xhtml-inlstruct-1.mod,v 1.1 2010/07/29 13:42:47 bertails Exp $ SMI\n\n         This DTD module is identified by the PUBLIC and SYSTEM identifiers:\n\n           PUBLIC \"-//W3C//ELEMENTS XHTML Inline Structural 1.0//EN\"\n           SYSTEM \"http://www.w3.org/MarkUp/DTD/xhtml-inlstruct-1.mod\"\n\n         Revisions:\n         (none)\n         ....................................................................... -->\n\n    <!-- Inline Structural\n\n            br, span\n\n         This module declares the elements and their attributes\n         used to support inline-level structural markup.\n    -->\n\n    <!-- br: forced line break ............................. -->\n\n    <!ENTITY % br.element  \"INCLUDE\" ><![%br.element;[\n\n    <!ENTITY % br.content  \"EMPTY\" ><!ENTITY % br.qname  \"br\" ><!ELEMENT %br.qname;  %br.content; >\n\n    <!-- end of br.element -->]]>\n\n    <!ENTITY % br.attlist  \"INCLUDE\" ><![%br.attlist;[<!ATTLIST %br.qname;%Core.attrib;><!-- end of br.attlist -->]]>\n\n    <!-- span: generic inline container .................... -->\n\n    <!ENTITY % span.element  \"INCLUDE\" ><![%span.element;[<!ENTITY % span.content\"( #PCDATA | %Inline.mix; )*\"><!ENTITY % span.qname  \"span\" ><!ELEMENT %span.qname;  %span.content; ><!-- end of span.element -->]]>\n\n    <!ENTITY % span.attlist  \"INCLUDE\" ><![%span.attlist;[<!ATTLIST %span.qname;%Common.attrib;><!-- end of span.attlist -->]]>\n\n    <!-- end of xhtml-inlstruct-1.mod -->\n"
  },
  {
    "path": "modules/book/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-inlstyle-1.mod",
    "content": "<!-- ...................................................................... -->\n    <!-- XHTML Inline Style Module  ........................................... -->\n    <!-- file: xhtml-inlstyle-1.mod\n\n         This is XHTML, a reformulation of HTML as a modular XML application.\n         Copyright 1998-2005 W3C (MIT, ERCIM, Keio), All Rights Reserved.\n         Revision: $Id: xhtml-inlstyle-1.mod,v 1.1 2010/07/29 13:42:47 bertails Exp $\n\n         This DTD module is identified by the PUBLIC and SYSTEM identifiers:\n\n           PUBLIC \"-//W3C//ENTITIES XHTML Inline Style 1.0//EN\"\n           SYSTEM \"http://www.w3.org/MarkUp/DTD/xhtml-inlstyle-1.mod\"\n\n         Revisions:\n         (none)\n         ....................................................................... -->\n\n    <!-- Inline Style\n\n         This module declares the 'style' attribute, used to support inline\n         style markup. This module must be instantiated prior to the XHTML\n         Common Attributes module in order to be included in %Core.attrib;.\n    -->\n\n    <!ENTITY % style.attrib\"style        CDATA                    #IMPLIED\">\n\n\n    <!ENTITY % Core.extra.attrib\"%style.attrib;\">\n\n    <!-- end of xhtml-inlstyle-1.mod -->\n"
  },
  {
    "path": "modules/book/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-lat1.ent",
    "content": "<!-- Portions (C) International Organization for Standardization 1986\n     Permission to copy in any form is granted for use with\n     conforming SGML systems and applications as defined in\n     ISO 8879, provided this notice is included in all copies.\n-->\n    <!-- Character entity set. Typical invocation:\n        <!ENTITY % HTMLlat1 PUBLIC\n           \"-//W3C//ENTITIES Latin 1 for XHTML//EN\"\n           \"http://www.w3.org/TR/xhtml1/DTD/xhtml-lat1.ent\">\n        %HTMLlat1;\n    -->\n\n    <!ENTITY nbsp   \"&#160;\"> <!-- no-break space = non-breaking space,\n                                  U+00A0 ISOnum -->\n    <!ENTITY iexcl  \"&#161;\"> <!-- inverted exclamation mark, U+00A1 ISOnum -->\n    <!ENTITY cent   \"&#162;\"> <!-- cent sign, U+00A2 ISOnum -->\n    <!ENTITY pound  \"&#163;\"> <!-- pound sign, U+00A3 ISOnum -->\n    <!ENTITY curren \"&#164;\"> <!-- currency sign, U+00A4 ISOnum -->\n    <!ENTITY yen    \"&#165;\"> <!-- yen sign = yuan sign, U+00A5 ISOnum -->\n    <!ENTITY brvbar \"&#166;\"> <!-- broken bar = broken vertical bar,\n                                  U+00A6 ISOnum -->\n    <!ENTITY sect   \"&#167;\"> <!-- section sign, U+00A7 ISOnum -->\n    <!ENTITY uml    \"&#168;\"> <!-- diaeresis = spacing diaeresis,\n                                  U+00A8 ISOdia -->\n    <!ENTITY copy   \"&#169;\"> <!-- copyright sign, U+00A9 ISOnum -->\n    <!ENTITY ordf   \"&#170;\"> <!-- feminine ordinal indicator, U+00AA ISOnum -->\n    <!ENTITY laquo  \"&#171;\"> <!-- left-pointing double angle quotation mark\n                                  = left pointing guillemet, U+00AB ISOnum -->\n    <!ENTITY not    \"&#172;\"> <!-- not sign = angled dash,\n                                  U+00AC ISOnum -->\n    <!ENTITY shy    \"&#173;\"> <!-- soft hyphen = discretionary hyphen,\n                                  U+00AD ISOnum -->\n    <!ENTITY reg    \"&#174;\"> <!-- registered sign = registered trade mark sign,\n                                  U+00AE ISOnum -->\n    <!ENTITY macr   \"&#175;\"> <!-- macron = spacing macron = overline\n                                  = APL overbar, U+00AF ISOdia -->\n    <!ENTITY deg    \"&#176;\"> <!-- degree sign, U+00B0 ISOnum -->\n    <!ENTITY plusmn \"&#177;\"> <!-- plus-minus sign = plus-or-minus sign,\n                                  U+00B1 ISOnum -->\n    <!ENTITY sup2   \"&#178;\"> <!-- superscript two = superscript digit two\n                                  = squared, U+00B2 ISOnum -->\n    <!ENTITY sup3   \"&#179;\"> <!-- superscript three = superscript digit three\n                                  = cubed, U+00B3 ISOnum -->\n    <!ENTITY acute  \"&#180;\"> <!-- acute accent = spacing acute,\n                                  U+00B4 ISOdia -->\n    <!ENTITY micro  \"&#181;\"> <!-- micro sign, U+00B5 ISOnum -->\n    <!ENTITY para   \"&#182;\"> <!-- pilcrow sign = paragraph sign,\n                                  U+00B6 ISOnum -->\n    <!ENTITY middot \"&#183;\"> <!-- middle dot = Georgian comma\n                                  = Greek middle dot, U+00B7 ISOnum -->\n    <!ENTITY cedil  \"&#184;\"> <!-- cedilla = spacing cedilla, U+00B8 ISOdia -->\n    <!ENTITY sup1   \"&#185;\"> <!-- superscript one = superscript digit one,\n                                  U+00B9 ISOnum -->\n    <!ENTITY ordm   \"&#186;\"> <!-- masculine ordinal indicator,\n                                  U+00BA ISOnum -->\n    <!ENTITY raquo  \"&#187;\"> <!-- right-pointing double angle quotation mark\n                                  = right pointing guillemet, U+00BB ISOnum -->\n    <!ENTITY frac14 \"&#188;\"> <!-- vulgar fraction one quarter\n                                  = fraction one quarter, U+00BC ISOnum -->\n    <!ENTITY frac12 \"&#189;\"> <!-- vulgar fraction one half\n                                  = fraction one half, U+00BD ISOnum -->\n    <!ENTITY frac34 \"&#190;\"> <!-- vulgar fraction three quarters\n                                  = fraction three quarters, U+00BE ISOnum -->\n    <!ENTITY iquest \"&#191;\"> <!-- inverted question mark\n                                  = turned question mark, U+00BF ISOnum -->\n    <!ENTITY Agrave \"&#192;\"> <!-- latin capital letter A with grave\n                                  = latin capital letter A grave,\n                                  U+00C0 ISOlat1 -->\n    <!ENTITY Aacute \"&#193;\"> <!-- latin capital letter A with acute,\n                                  U+00C1 ISOlat1 -->\n    <!ENTITY Acirc  \"&#194;\"> <!-- latin capital letter A with circumflex,\n                                  U+00C2 ISOlat1 -->\n    <!ENTITY Atilde \"&#195;\"> <!-- latin capital letter A with tilde,\n                                  U+00C3 ISOlat1 -->\n    <!ENTITY Auml   \"&#196;\"> <!-- latin capital letter A with diaeresis,\n                                  U+00C4 ISOlat1 -->\n    <!ENTITY Aring  \"&#197;\"> <!-- latin capital letter A with ring above\n                                  = latin capital letter A ring,\n                                  U+00C5 ISOlat1 -->\n    <!ENTITY AElig  \"&#198;\"> <!-- latin capital letter AE\n                                  = latin capital ligature AE,\n                                  U+00C6 ISOlat1 -->\n    <!ENTITY Ccedil \"&#199;\"> <!-- latin capital letter C with cedilla,\n                                  U+00C7 ISOlat1 -->\n    <!ENTITY Egrave \"&#200;\"> <!-- latin capital letter E with grave,\n                                  U+00C8 ISOlat1 -->\n    <!ENTITY Eacute \"&#201;\"> <!-- latin capital letter E with acute,\n                                  U+00C9 ISOlat1 -->\n    <!ENTITY Ecirc  \"&#202;\"> <!-- latin capital letter E with circumflex,\n                                  U+00CA ISOlat1 -->\n    <!ENTITY Euml   \"&#203;\"> <!-- latin capital letter E with diaeresis,\n                                  U+00CB ISOlat1 -->\n    <!ENTITY Igrave \"&#204;\"> <!-- latin capital letter I with grave,\n                                  U+00CC ISOlat1 -->\n    <!ENTITY Iacute \"&#205;\"> <!-- latin capital letter I with acute,\n                                  U+00CD ISOlat1 -->\n    <!ENTITY Icirc  \"&#206;\"> <!-- latin capital letter I with circumflex,\n                                  U+00CE ISOlat1 -->\n    <!ENTITY Iuml   \"&#207;\"> <!-- latin capital letter I with diaeresis,\n                                  U+00CF ISOlat1 -->\n    <!ENTITY ETH    \"&#208;\"> <!-- latin capital letter ETH, U+00D0 ISOlat1 -->\n    <!ENTITY Ntilde \"&#209;\"> <!-- latin capital letter N with tilde,\n                                  U+00D1 ISOlat1 -->\n    <!ENTITY Ograve \"&#210;\"> <!-- latin capital letter O with grave,\n                                  U+00D2 ISOlat1 -->\n    <!ENTITY Oacute \"&#211;\"> <!-- latin capital letter O with acute,\n                                  U+00D3 ISOlat1 -->\n    <!ENTITY Ocirc  \"&#212;\"> <!-- latin capital letter O with circumflex,\n                                  U+00D4 ISOlat1 -->\n    <!ENTITY Otilde \"&#213;\"> <!-- latin capital letter O with tilde,\n                                  U+00D5 ISOlat1 -->\n    <!ENTITY Ouml   \"&#214;\"> <!-- latin capital letter O with diaeresis,\n                                  U+00D6 ISOlat1 -->\n    <!ENTITY times  \"&#215;\"> <!-- multiplication sign, U+00D7 ISOnum -->\n    <!ENTITY Oslash \"&#216;\"> <!-- latin capital letter O with stroke\n                                  = latin capital letter O slash,\n                                  U+00D8 ISOlat1 -->\n    <!ENTITY Ugrave \"&#217;\"> <!-- latin capital letter U with grave,\n                                  U+00D9 ISOlat1 -->\n    <!ENTITY Uacute \"&#218;\"> <!-- latin capital letter U with acute,\n                                  U+00DA ISOlat1 -->\n    <!ENTITY Ucirc  \"&#219;\"> <!-- latin capital letter U with circumflex,\n                                  U+00DB ISOlat1 -->\n    <!ENTITY Uuml   \"&#220;\"> <!-- latin capital letter U with diaeresis,\n                                  U+00DC ISOlat1 -->\n    <!ENTITY Yacute \"&#221;\"> <!-- latin capital letter Y with acute,\n                                  U+00DD ISOlat1 -->\n    <!ENTITY THORN  \"&#222;\"> <!-- latin capital letter THORN,\n                                  U+00DE ISOlat1 -->\n    <!ENTITY szlig  \"&#223;\"> <!-- latin small letter sharp s = ess-zed,\n                                  U+00DF ISOlat1 -->\n    <!ENTITY agrave \"&#224;\"> <!-- latin small letter a with grave\n                                  = latin small letter a grave,\n                                  U+00E0 ISOlat1 -->\n    <!ENTITY aacute \"&#225;\"> <!-- latin small letter a with acute,\n                                  U+00E1 ISOlat1 -->\n    <!ENTITY acirc  \"&#226;\"> <!-- latin small letter a with circumflex,\n                                  U+00E2 ISOlat1 -->\n    <!ENTITY atilde \"&#227;\"> <!-- latin small letter a with tilde,\n                                  U+00E3 ISOlat1 -->\n    <!ENTITY auml   \"&#228;\"> <!-- latin small letter a with diaeresis,\n                                  U+00E4 ISOlat1 -->\n    <!ENTITY aring  \"&#229;\"> <!-- latin small letter a with ring above\n                                  = latin small letter a ring,\n                                  U+00E5 ISOlat1 -->\n    <!ENTITY aelig  \"&#230;\"> <!-- latin small letter ae\n                                  = latin small ligature ae, U+00E6 ISOlat1 -->\n    <!ENTITY ccedil \"&#231;\"> <!-- latin small letter c with cedilla,\n                                  U+00E7 ISOlat1 -->\n    <!ENTITY egrave \"&#232;\"> <!-- latin small letter e with grave,\n                                  U+00E8 ISOlat1 -->\n    <!ENTITY eacute \"&#233;\"> <!-- latin small letter e with acute,\n                                  U+00E9 ISOlat1 -->\n    <!ENTITY ecirc  \"&#234;\"> <!-- latin small letter e with circumflex,\n                                  U+00EA ISOlat1 -->\n    <!ENTITY euml   \"&#235;\"> <!-- latin small letter e with diaeresis,\n                                  U+00EB ISOlat1 -->\n    <!ENTITY igrave \"&#236;\"> <!-- latin small letter i with grave,\n                                  U+00EC ISOlat1 -->\n    <!ENTITY iacute \"&#237;\"> <!-- latin small letter i with acute,\n                                  U+00ED ISOlat1 -->\n    <!ENTITY icirc  \"&#238;\"> <!-- latin small letter i with circumflex,\n                                  U+00EE ISOlat1 -->\n    <!ENTITY iuml   \"&#239;\"> <!-- latin small letter i with diaeresis,\n                                  U+00EF ISOlat1 -->\n    <!ENTITY eth    \"&#240;\"> <!-- latin small letter eth, U+00F0 ISOlat1 -->\n    <!ENTITY ntilde \"&#241;\"> <!-- latin small letter n with tilde,\n                                  U+00F1 ISOlat1 -->\n    <!ENTITY ograve \"&#242;\"> <!-- latin small letter o with grave,\n                                  U+00F2 ISOlat1 -->\n    <!ENTITY oacute \"&#243;\"> <!-- latin small letter o with acute,\n                                  U+00F3 ISOlat1 -->\n    <!ENTITY ocirc  \"&#244;\"> <!-- latin small letter o with circumflex,\n                                  U+00F4 ISOlat1 -->\n    <!ENTITY otilde \"&#245;\"> <!-- latin small letter o with tilde,\n                                  U+00F5 ISOlat1 -->\n    <!ENTITY ouml   \"&#246;\"> <!-- latin small letter o with diaeresis,\n                                  U+00F6 ISOlat1 -->\n    <!ENTITY divide \"&#247;\"> <!-- division sign, U+00F7 ISOnum -->\n    <!ENTITY oslash \"&#248;\"> <!-- latin small letter o with stroke,\n                                  = latin small letter o slash,\n                                  U+00F8 ISOlat1 -->\n    <!ENTITY ugrave \"&#249;\"> <!-- latin small letter u with grave,\n                                  U+00F9 ISOlat1 -->\n    <!ENTITY uacute \"&#250;\"> <!-- latin small letter u with acute,\n                                  U+00FA ISOlat1 -->\n    <!ENTITY ucirc  \"&#251;\"> <!-- latin small letter u with circumflex,\n                                  U+00FB ISOlat1 -->\n    <!ENTITY uuml   \"&#252;\"> <!-- latin small letter u with diaeresis,\n                                  U+00FC ISOlat1 -->\n    <!ENTITY yacute \"&#253;\"> <!-- latin small letter y with acute,\n                                  U+00FD ISOlat1 -->\n    <!ENTITY thorn  \"&#254;\"> <!-- latin small letter thorn,\n                                  U+00FE ISOlat1 -->\n    <!ENTITY yuml   \"&#255;\"> <!-- latin small letter y with diaeresis,\n                                  U+00FF ISOlat1 -->\n"
  },
  {
    "path": "modules/book/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-link-1.mod",
    "content": "<!-- ...................................................................... -->\n    <!-- XHTML Link Element Module  ........................................... -->\n    <!-- file: xhtml-link-1.mod\n\n         This is XHTML, a reformulation of HTML as a modular XML application.\n         Copyright 1998-2005 W3C (MIT, ERCIM, Keio), All Rights Reserved.\n         Revision: $Id: xhtml-link-1.mod,v 1.1 2010/07/29 13:42:47 bertails Exp $ SMI\n\n         This DTD module is identified by the PUBLIC and SYSTEM identifiers:\n\n           PUBLIC \"-//W3C//ELEMENTS XHTML Link Element 1.0//EN\"\n           SYSTEM \"http://www.w3.org/MarkUp/DTD/xhtml-link-1.mod\"\n\n         Revisions:\n         (none)\n         ....................................................................... -->\n\n    <!-- Link element\n\n            link\n\n         This module declares the link element type and its attributes,\n         which could (in principle) be used to define document-level links\n         to external resources such as:\n\n         a) for document specific toolbars/menus, e.g. start, contents,\n            previous, next, index, end, help\n         b) to link to a separate style sheet (rel=\"stylesheet\")\n         c) to make a link to a script (rel=\"script\")\n         d) by style sheets to control how collections of html nodes are\n            rendered into printed documents\n         e) to make a link to a printable version of this document\n            e.g. a postscript or pdf version (rel=\"alternate\" media=\"print\")\n    -->\n\n    <!-- link: Media-Independent Link ...................... -->\n\n    <!ENTITY % link.element  \"INCLUDE\" ><![%link.element;[<!ENTITY % link.content  \"EMPTY\" ><!ENTITY % link.qname  \"link\" ><!ELEMENT %link.qname;  %link.content; ><!-- end of link.element -->]]>\n\n    <!ENTITY % link.attlist  \"INCLUDE\" ><![%link.attlist;[<!ATTLIST %link.qname;%Common.attrib;charset      %Charset.datatype;       #IMPLIEDhref         %URI.datatype;           #IMPLIEDhreflang     %LanguageCode.datatype;  #IMPLIEDtype         %ContentType.datatype;   #IMPLIEDrel          %LinkTypes.datatype;     #IMPLIEDrev          %LinkTypes.datatype;     #IMPLIEDmedia        %MediaDesc.datatype;     #IMPLIED><!-- end of link.attlist -->]]>\n\n    <!-- end of xhtml-link-1.mod -->\n"
  },
  {
    "path": "modules/book/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-list-1.mod",
    "content": "<!-- ...................................................................... -->\n    <!-- XHTML Lists Module  .................................................. -->\n    <!-- file: xhtml-list-1.mod\n\n         This is XHTML, a reformulation of HTML as a modular XML application.\n         Copyright 1998-2005 W3C (MIT, ERCIM, Keio), All Rights Reserved.\n         Revision: $Id: xhtml-list-1.mod,v 1.1 2010/07/29 13:42:47 bertails Exp $ SMI\n\n         This DTD module is identified by the PUBLIC and SYSTEM identifiers:\n\n           PUBLIC \"-//W3C//ELEMENTS XHTML Lists 1.0//EN\"\n           SYSTEM \"http://www.w3.org/MarkUp/DTD/xhtml-list-1.mod\"\n\n         Revisions:\n         (none)\n         ....................................................................... -->\n\n    <!-- Lists\n\n            dl, dt, dd, ol, ul, li\n\n         This module declares the list-oriented element types\n         and their attributes.\n    -->\n\n    <!ENTITY % dl.qname  \"dl\" ><!ENTITY % dt.qname  \"dt\" ><!ENTITY % dd.qname  \"dd\" ><!ENTITY % ol.qname  \"ol\" ><!ENTITY % ul.qname  \"ul\" ><!ENTITY % li.qname  \"li\" >\n\n    <!-- dl: Definition List ............................... -->\n\n    <!ENTITY % dl.element  \"INCLUDE\" ><![%dl.element;[<!ENTITY % dl.content  \"( %dt.qname; | %dd.qname; )+\" ><!ELEMENT %dl.qname;  %dl.content; ><!-- end of dl.element -->]]>\n\n    <!ENTITY % dl.attlist  \"INCLUDE\" ><![%dl.attlist;[<!ATTLIST %dl.qname;%Common.attrib;><!-- end of dl.attlist -->]]>\n\n    <!-- dt: Definition Term ............................... -->\n\n    <!ENTITY % dt.element  \"INCLUDE\" ><![%dt.element;[<!ENTITY % dt.content\"( #PCDATA | %Inline.mix; )*\"><!ELEMENT %dt.qname;  %dt.content; ><!-- end of dt.element -->]]>\n\n    <!ENTITY % dt.attlist  \"INCLUDE\" ><![%dt.attlist;[<!ATTLIST %dt.qname;%Common.attrib;><!-- end of dt.attlist -->]]>\n\n    <!-- dd: Definition Description ........................ -->\n\n    <!ENTITY % dd.element  \"INCLUDE\" ><![%dd.element;[<!ENTITY % dd.content\"( #PCDATA | %Flow.mix; )*\"><!ELEMENT %dd.qname;  %dd.content; ><!-- end of dd.element -->]]>\n\n    <!ENTITY % dd.attlist  \"INCLUDE\" ><![%dd.attlist;[<!ATTLIST %dd.qname;%Common.attrib;><!-- end of dd.attlist -->]]>\n\n    <!-- ol: Ordered List (numbered styles) ................ -->\n\n    <!ENTITY % ol.element  \"INCLUDE\" ><![%ol.element;[<!ENTITY % ol.content  \"( %li.qname; )+\" ><!ELEMENT %ol.qname;  %ol.content; ><!-- end of ol.element -->]]>\n\n    <!ENTITY % ol.attlist  \"INCLUDE\" ><![%ol.attlist;[<!ATTLIST %ol.qname;%Common.attrib;><!-- end of ol.attlist -->]]>\n\n    <!-- ul: Unordered List (bullet styles) ................ -->\n\n    <!ENTITY % ul.element  \"INCLUDE\" ><![%ul.element;[<!ENTITY % ul.content  \"( %li.qname; )+\" ><!ELEMENT %ul.qname;  %ul.content; ><!-- end of ul.element -->]]>\n\n    <!ENTITY % ul.attlist  \"INCLUDE\" ><![%ul.attlist;[<!ATTLIST %ul.qname;%Common.attrib;><!-- end of ul.attlist -->]]>\n\n    <!-- li: List Item ..................................... -->\n\n    <!ENTITY % li.element  \"INCLUDE\" ><![%li.element;[<!ENTITY % li.content\"( #PCDATA | %Flow.mix; )*\"><!ELEMENT %li.qname;  %li.content; ><!-- end of li.element -->]]>\n\n    <!ENTITY % li.attlist  \"INCLUDE\" ><![%li.attlist;[<!ATTLIST %li.qname;%Common.attrib;><!-- end of li.attlist -->]]>\n\n    <!-- end of xhtml-list-1.mod -->\n"
  },
  {
    "path": "modules/book/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-meta-1.mod",
    "content": "<!-- ...................................................................... -->\n    <!-- XHTML Document Metainformation Module  ............................... -->\n    <!-- file: xhtml-meta-1.mod\n\n         This is XHTML, a reformulation of HTML as a modular XML application.\n         Copyright 1998-2005 W3C (MIT, ERCIM, Keio), All Rights Reserved.\n         Revision: $Id: xhtml-meta-1.mod,v 1.1 2010/07/29 13:42:47 bertails Exp $ SMI\n\n         This DTD module is identified by the PUBLIC and SYSTEM identifiers:\n\n           PUBLIC \"-//W3C//ELEMENTS XHTML Metainformation 1.0//EN\"\n           SYSTEM \"http://www.w3.org/MarkUp/DTD/xhtml-meta-1.mod\"\n\n         Revisions:\n         (none)\n         ....................................................................... -->\n\n    <!-- Meta Information\n\n            meta\n\n         This module declares the meta element type and its attributes,\n         used to provide declarative document metainformation.\n    -->\n\n    <!-- meta: Generic Metainformation ..................... -->\n\n    <!ENTITY % meta.element  \"INCLUDE\" ><![%meta.element;[<!ENTITY % meta.content  \"EMPTY\" ><!ENTITY % meta.qname  \"meta\" ><!ELEMENT %meta.qname;  %meta.content; ><!-- end of meta.element -->]]>\n\n    <!ENTITY % meta.attlist  \"INCLUDE\" ><![%meta.attlist;[<!ATTLIST %meta.qname;%XHTML.xmlns.attrib;%I18n.attrib;http-equiv   NMTOKEN                  #IMPLIEDname         NMTOKEN                  #IMPLIEDcontent      CDATA                    #REQUIREDscheme       CDATA                    #IMPLIED><!-- end of meta.attlist -->]]>\n\n    <!-- end of xhtml-meta-1.mod -->\n"
  },
  {
    "path": "modules/book/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-notations-1.mod",
    "content": "<!-- ...................................................................... -->\n    <!-- XHTML Notations Module  .............................................. -->\n    <!-- file: xhtml-notations-1.mod\n\n         This is XHTML, a reformulation of HTML as a modular XML application.\n         Copyright 1998-2005 W3C (MIT, ERCIM, Keio), All Rights Reserved.\n         Revision: $Id: xhtml-notations-1.mod,v 1.1 2010/07/29 13:42:48 bertails Exp $\n\n         This DTD module is identified by the PUBLIC and SYSTEM identifiers:\n\n           PUBLIC \"-//W3C//NOTATIONS XHTML Notations 1.0//EN\"\n           SYSTEM \"http://www.w3.org/MarkUp/DTD/xhtml-notations-1.mod\"\n\n         Revisions:\n         (none)\n         ....................................................................... -->\n\n    <!-- Notations\n\n         defines the following notations, many of these imported from\n         other specifications and standards. When an existing FPI is\n         known, it is incorporated here.\n    -->\n\n    <!-- XML Notations ..................................... -->\n    <!-- SGML and XML Notations ............................ -->\n\n    <!-- W3C XML 1.0 Recommendation -->\n    <!NOTATION w3c-xmlPUBLIC \"ISO 8879//NOTATION Extensible Markup Language (XML) 1.0//EN\" >\n\n    <!-- XML 1.0 CDATA -->\n    <!NOTATION cdataPUBLIC \"-//W3C//NOTATION XML 1.0: CDATA//EN\" >\n\n    <!-- SGML Formal Public Identifiers -->\n    <!NOTATION fpiPUBLIC \"ISO 8879:1986//NOTATION Formal Public Identifier//EN\" >\n\n    <!-- XHTML Notations ................................... -->\n\n    <!-- Length defined for cellpadding/cellspacing -->\n\n    <!-- nn for pixels or nn% for percentage length -->\n    <!NOTATION lengthPUBLIC \"-//W3C//NOTATION XHTML Datatype: Length//EN\" >\n\n    <!-- space-separated list of link types -->\n    <!NOTATION linkTypesPUBLIC \"-//W3C//NOTATION XHTML Datatype: LinkTypes//EN\" >\n\n    <!-- single or comma-separated list of media descriptors -->\n    <!NOTATION mediaDescPUBLIC \"-//W3C//NOTATION XHTML Datatype: MediaDesc//EN\" >\n\n    <!-- pixel, percentage, or relative -->\n    <!NOTATION multiLengthPUBLIC \"-//W3C//NOTATION XHTML Datatype: MultiLength//EN\" >\n\n    <!-- one or more digits (NUMBER) -->\n    <!NOTATION numberPUBLIC \"-//W3C//NOTATION XHTML Datatype: Number//EN\" >\n\n    <!-- integer representing length in pixels -->\n    <!NOTATION pixelsPUBLIC \"-//W3C//NOTATION XHTML Datatype: Pixels//EN\" >\n\n    <!-- script expression -->\n    <!NOTATION scriptPUBLIC \"-//W3C//NOTATION XHTML Datatype: Script//EN\" >\n\n    <!-- textual content -->\n    <!NOTATION textPUBLIC \"-//W3C//NOTATION XHTML Datatype: Text//EN\" >\n\n    <!-- Imported Notations ................................ -->\n\n    <!-- a single character from [ISO10646] -->\n    <!NOTATION characterPUBLIC \"-//W3C//NOTATION XHTML Datatype: Character//EN\" >\n\n    <!-- a character encoding, as per [RFC2045] -->\n    <!NOTATION charsetPUBLIC \"-//W3C//NOTATION XHTML Datatype: Charset//EN\" >\n\n    <!-- a space separated list of character encodings, as per [RFC2045] -->\n    <!NOTATION charsetsPUBLIC \"-//W3C//NOTATION XHTML Datatype: Charsets//EN\" >\n\n    <!-- media type, as per [RFC2045] -->\n    <!NOTATION contentTypePUBLIC \"-//W3C//NOTATION XHTML Datatype: ContentType//EN\" >\n\n    <!-- comma-separated list of media types, as per [RFC2045] -->\n    <!NOTATION contentTypesPUBLIC \"-//W3C//NOTATION XHTML Datatype: ContentTypes//EN\" >\n\n    <!-- date and time information. ISO date format -->\n    <!NOTATION datetimePUBLIC \"-//W3C//NOTATION XHTML Datatype: Datetime//EN\" >\n\n    <!-- a language code, as per [RFC3066] -->\n    <!NOTATION languageCodePUBLIC \"-//W3C//NOTATION XHTML Datatype: LanguageCode//EN\" >\n\n    <!-- a Uniform Resource Identifier, see [URI] -->\n    <!NOTATION uriPUBLIC \"-//W3C//NOTATION XHTML Datatype: URI//EN\" >\n\n    <!-- a space-separated list of Uniform Resource Identifiers, see [URI] -->\n    <!NOTATION urisPUBLIC \"-//W3C//NOTATION XHTML Datatype: URIs//EN\" >\n\n    <!-- end of xhtml-notations-1.mod -->\n"
  },
  {
    "path": "modules/book/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-object-1.mod",
    "content": "<!-- ...................................................................... -->\n    <!-- XHTML Embedded Object Module  ........................................ -->\n    <!-- file: xhtml-object-1.mod\n\n         This is XHTML, a reformulation of HTML as a modular XML application.\n         Copyright 1998-2005 W3C (MIT, ERCIM, Keio), All Rights Reserved.\n         Revision: $Id: xhtml-object-1.mod,v 1.1 2010/07/29 13:42:48 bertails Exp $ SMI\n\n         This DTD module is identified by the PUBLIC and SYSTEM identifiers:\n\n           PUBLIC \"-//W3C//ELEMENTS XHTML Embedded Object 1.0//EN\"\n           SYSTEM \"http://www.w3.org/MarkUp/DTD/xhtml-object-1.mod\"\n\n         Revisions:\n         (none)\n         ....................................................................... -->\n\n    <!-- Embedded Objects\n\n            object\n\n         This module declares the object element type and its attributes, used\n         to embed external objects as part of XHTML pages. In the document,\n         place param elements prior to other content within the object element.\n\n         Note that use of this module requires instantiation of the Param\n         Element Module.\n    -->\n\n    <!-- object: Generic Embedded Object ................... -->\n\n    <!ENTITY % object.element  \"INCLUDE\" ><![%object.element;[<!ENTITY % object.content\"( #PCDATA | %Flow.mix; | %param.qname; )*\"><!ENTITY % object.qname  \"object\" ><!ELEMENT %object.qname;  %object.content; ><!-- end of object.element -->]]>\n\n    <!ENTITY % object.attlist  \"INCLUDE\" ><![%object.attlist;[<!ATTLIST %object.qname;%Common.attrib;declare      ( declare )              #IMPLIEDclassid      %URI.datatype;           #IMPLIEDcodebase     %URI.datatype;           #IMPLIEDdata         %URI.datatype;           #IMPLIEDtype         %ContentType.datatype;   #IMPLIEDcodetype     %ContentType.datatype;   #IMPLIEDarchive      %URIs.datatype;          #IMPLIEDstandby      %Text.datatype;          #IMPLIEDheight       %Length.datatype;        #IMPLIEDwidth        %Length.datatype;        #IMPLIEDname         CDATA                    #IMPLIEDtabindex     %Number.datatype;        #IMPLIED><!-- end of object.attlist -->]]>\n\n    <!-- end of xhtml-object-1.mod -->\n"
  },
  {
    "path": "modules/book/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-param-1.mod",
    "content": "<!-- ...................................................................... -->\n    <!-- XHTML Param Element Module  ..................................... -->\n    <!-- file: xhtml-param-1.mod\n\n         This is XHTML, a reformulation of HTML as a modular XML application.\n         Copyright 1998-2005 W3C (MIT, ERCIM, Keio), All Rights Reserved.\n         Revision: $Id: xhtml-param-1.mod,v 1.1 2010/07/29 13:42:48 bertails Exp $ SMI\n\n         This DTD module is identified by the PUBLIC and SYSTEM identifiers:\n\n           PUBLIC \"-//W3C//ELEMENTS XHTML Param Element 1.0//EN\"\n           SYSTEM \"http://www.w3.org/MarkUp/DTD/xhtml-param-1.mod\"\n\n         Revisions:\n         (none)\n         ....................................................................... -->\n\n    <!-- Parameters for Java Applets and Embedded Objects\n\n            param\n\n         This module provides declarations for the param element,\n         used to provide named property values for the applet\n         and object elements.\n    -->\n\n    <!-- param: Named Property Value ....................... -->\n\n    <!ENTITY % param.element  \"INCLUDE\" ><![%param.element;[<!ENTITY % param.content  \"EMPTY\" ><!ENTITY % param.qname  \"param\" ><!ELEMENT %param.qname;  %param.content; ><!-- end of param.element -->]]>\n\n    <!ENTITY % param.attlist  \"INCLUDE\" ><![%param.attlist;[<!ATTLIST %param.qname;%XHTML.xmlns.attrib;%id.attrib;name         CDATA                    #REQUIREDvalue        CDATA                    #IMPLIEDvaluetype    ( data | ref | object )  'data'type         %ContentType.datatype;   #IMPLIED><!-- end of param.attlist -->]]>\n\n    <!-- end of xhtml-param-1.mod -->\n"
  },
  {
    "path": "modules/book/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-pres-1.mod",
    "content": "<!-- ...................................................................... -->\n    <!-- XHTML Presentation Module ............................................ -->\n    <!-- file: xhtml-pres-1.mod\n\n         This is XHTML, a reformulation of HTML as a modular XML application.\n         Copyright 1998-2005 W3C (MIT, ERCIM, Keio), All Rights Reserved.\n         Revision: $Id: xhtml-pres-1.mod,v 1.1 2010/07/29 13:42:48 bertails Exp $ SMI\n\n         This DTD module is identified by the PUBLIC and SYSTEM identifiers:\n\n           PUBLIC \"-//W3C//ELEMENTS XHTML Presentation 1.0//EN\"\n           SYSTEM \"http://www.w3.org/MarkUp/DTD/xhtml-pres-1.mod\"\n\n         Revisions:\n         (none)\n         ....................................................................... -->\n\n    <!-- Presentational Elements\n\n         This module defines elements and their attributes for\n         simple presentation-related markup.\n    -->\n\n    <!ENTITY % xhtml-inlpres.module \"INCLUDE\" ><![%xhtml-inlpres.module;[<!ENTITY % xhtml-inlpres.modPUBLIC \"-//W3C//ELEMENTS XHTML Inline Presentation 1.0//EN\"\"xhtml-inlpres-1.mod\" >%xhtml-inlpres.mod;]]>\n\n    <!ENTITY % xhtml-blkpres.module \"INCLUDE\" ><![%xhtml-blkpres.module;[<!ENTITY % xhtml-blkpres.modPUBLIC \"-//W3C//ELEMENTS XHTML Block Presentation 1.0//EN\"\"xhtml-blkpres-1.mod\" >%xhtml-blkpres.mod;]]>\n\n    <!-- end of xhtml-pres-1.mod -->\n"
  },
  {
    "path": "modules/book/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-qname-1.mod",
    "content": "<!-- ....................................................................... -->\n    <!-- XHTML Qname Module  ................................................... -->\n    <!-- file: xhtml-qname-1.mod\n\n         This is XHTML, a reformulation of HTML as a modular XML application.\n         Copyright 1998-2005 W3C (MIT, ERCIM, Keio), All Rights Reserved.\n         Revision: $Id: xhtml-qname-1.mod,v 1.1 2010/07/29 13:42:48 bertails Exp $ SMI\n\n         This DTD module is identified by the PUBLIC and SYSTEM identifiers:\n\n           PUBLIC \"-//W3C//ENTITIES XHTML Qualified Names 1.0//EN\"\n           SYSTEM \"http://www.w3.org/MarkUp/DTD/xhtml-qname-1.mod\"\n\n         Revisions:\n           #2000-10-22: added qname declarations for ruby elements\n         ....................................................................... -->\n\n    <!-- XHTML Qname (Qualified Name) Module\n\n         This module is contained in two parts, labeled Section 'A' and 'B':\n\n           Section A declares parameter entities to support namespace-\n           qualified names, namespace declarations, and name prefixing\n           for XHTML and extensions.\n\n           Section B declares parameter entities used to provide\n           namespace-qualified names for all XHTML element types:\n\n             %applet.qname;   the xmlns-qualified name for <applet>\n             %base.qname;     the xmlns-qualified name for <base>\n             ...\n\n         XHTML extensions would create a module similar to this one.\n         Included in the XHTML distribution is a template module\n         ('template-qname-1.mod') suitable for this purpose.\n    -->\n\n    <!-- Section A: XHTML XML Namespace Framework :::::::::::::::::::: -->\n\n    <!-- 1. Declare a %XHTML.prefixed; conditional section keyword, used\n            to activate namespace prefixing. The default value should\n            inherit '%NS.prefixed;' from the DTD driver, so that unless\n            overridden, the default behaviour follows the overall DTD\n            prefixing scheme.\n    -->\n    <!ENTITY % NS.prefixed \"IGNORE\" ><!ENTITY % XHTML.prefixed \"%NS.prefixed;\" >\n\n    <!-- By default, we always permit XHTML attribute collections to have\n         namespace-qualified prefixes as well.\n    -->\n    <!ENTITY % XHTML.global.attrs.prefixed \"INCLUDE\" ><!-- By default, we allow the XML Schema attributes on the root\n     element.\n-->\n    <!ENTITY % XHTML.xsi.attrs \"INCLUDE\" >\n\n    <!-- 2. Declare a parameter entity (eg., %XHTML.xmlns;) containing\n            the URI reference used to identify the XHTML namespace:\n    -->\n    <!ENTITY % XHTML.xmlns  \"http://www.w3.org/1999/xhtml\" >\n\n    <!-- 3. Declare parameter entities (eg., %XHTML.prefix;) containing\n            the default namespace prefix string(s) to use when prefixing\n            is enabled. This may be overridden in the DTD driver or the\n            internal subset of an document instance. If no default prefix\n            is desired, this may be declared as an empty string.\n\n         NOTE: As specified in [XMLNAMES], the namespace prefix serves\n         as a proxy for the URI reference, and is not in itself significant.\n    -->\n    <!ENTITY % XHTML.prefix  \"xhtml\" >\n\n    <!-- 4. Declare parameter entities (eg., %XHTML.pfx;) containing the\n            colonized prefix(es) (eg., '%XHTML.prefix;:') used when\n            prefixing is active, an empty string when it is not.\n    -->\n    <![%XHTML.prefixed;[<!ENTITY % XHTML.pfx  \"%XHTML.prefix;:\" >]]><!ENTITY % XHTML.pfx  \"\" >\n\n    <!-- declare qualified name extensions here ............ -->\n    <!ENTITY % xhtml-qname-extra.mod \"\" >%xhtml-qname-extra.mod;\n\n    <!-- 5. The parameter entity %XHTML.xmlns.extra.attrib; may be\n            redeclared to contain any non-XHTML namespace declaration\n            attributes for namespaces embedded in XHTML. The default\n            is an empty string.  XLink should be included here if used\n            in the DTD.\n    -->\n    <!ENTITY % XHTML.xmlns.extra.attrib \"\" >\n\n    <!-- The remainder of Section A is only followed in XHTML, not extensions. -->\n\n    <!-- Declare a parameter entity %NS.decl.attrib; containing\n         all XML Namespace declarations used in the DTD, plus the\n         xmlns declaration for XHTML, its form dependent on whether\n         prefixing is active.\n    -->\n    <!ENTITY % XHTML.xmlns.attrib.prefixed\"xmlns:%XHTML.prefix;  %URI.datatype;   #FIXED '%XHTML.xmlns;'\"><![%XHTML.prefixed;[<!ENTITY % NS.decl.attrib\"%XHTML.xmlns.attrib.prefixed;\n      %XHTML.xmlns.extra.attrib;\">]]><!ENTITY % NS.decl.attrib\"%XHTML.xmlns.extra.attrib;\">\n\n    <!-- Declare a parameter entity %XSI.prefix as a prefix to use for XML\n         Schema Instance attributes.\n    -->\n    <!ENTITY % XSI.prefix \"xsi\" >\n\n    <!ENTITY % XSI.xmlns \"http://www.w3.org/2001/XMLSchema-instance\" >\n\n    <!-- Declare a parameter entity %XSI.xmlns.attrib as support for the\n         schemaLocation attribute, since this is legal throughout the DTD.\n    -->\n    <!ENTITY % XSI.xmlns.attrib\"xmlns:%XSI.prefix;  %URI.datatype;   #FIXED '%XSI.xmlns;'\" >\n\n    <!-- This is a placeholder for future XLink support.\n    -->\n    <!ENTITY % XLINK.xmlns.attrib \"\" >\n\n    <!-- This is the attribute for the XML Schema namespace - XHTML\n         Modularization is also expressed in XML Schema, and it needs to\n         be legal to declare the XML Schema namespace and the\n         schemaLocation attribute on the root element of XHTML family\n         documents.\n    -->\n    <![%XHTML.xsi.attrs;[<!ENTITY % XSI.prefix \"xsi\" ><!ENTITY % XSI.pfx \"%XSI.prefix;:\" ><!ENTITY % XSI.xmlns \"http://www.w3.org/2001/XMLSchema-instance\" >\n\n        <!ENTITY % XSI.xmlns.attrib\"xmlns:%XSI.prefix;  %URI.datatype;    #FIXED '%XSI.xmlns;'\">]]><!ENTITY % XSI.prefix \"\" ><!ENTITY % XSI.pfx \"\" ><!ENTITY % XSI.xmlns.attrib \"\" >\n\n\n    <!-- Declare a parameter entity %NS.decl.attrib; containing all\n         XML namespace declaration attributes used by XHTML, including\n         a default xmlns attribute when prefixing is inactive.\n    -->\n    <![%XHTML.prefixed;[<!ENTITY % XHTML.xmlns.attrib\"%NS.decl.attrib;\n      %XSI.xmlns.attrib;\n      %XLINK.xmlns.attrib;\">]]><!ENTITY % XHTML.xmlns.attrib\"xmlns        %URI.datatype;           #FIXED '%XHTML.xmlns;'\n      %NS.decl.attrib;\n      %XSI.xmlns.attrib;\n      %XLINK.xmlns.attrib;\">\n\n    <!-- placeholder for qualified name redeclarations -->\n    <!ENTITY % xhtml-qname.redecl \"\" >%xhtml-qname.redecl;\n\n    <!-- Section B: XHTML Qualified Names ::::::::::::::::::::::::::::: -->\n\n    <!-- 6. This section declares parameter entities used to provide\n            namespace-qualified names for all XHTML element types.\n    -->\n\n    <!-- module:  xhtml-applet-1.mod -->\n    <!ENTITY % applet.qname  \"%XHTML.pfx;applet\" >\n\n    <!-- module:  xhtml-base-1.mod -->\n    <!ENTITY % base.qname    \"%XHTML.pfx;base\" >\n\n    <!-- module:  xhtml-bdo-1.mod -->\n    <!ENTITY % bdo.qname     \"%XHTML.pfx;bdo\" >\n\n    <!-- module:  xhtml-blkphras-1.mod -->\n    <!ENTITY % address.qname \"%XHTML.pfx;address\" ><!ENTITY % blockquote.qname  \"%XHTML.pfx;blockquote\" ><!ENTITY % pre.qname     \"%XHTML.pfx;pre\" ><!ENTITY % h1.qname      \"%XHTML.pfx;h1\" ><!ENTITY % h2.qname      \"%XHTML.pfx;h2\" ><!ENTITY % h3.qname      \"%XHTML.pfx;h3\" ><!ENTITY % h4.qname      \"%XHTML.pfx;h4\" ><!ENTITY % h5.qname      \"%XHTML.pfx;h5\" ><!ENTITY % h6.qname      \"%XHTML.pfx;h6\" >\n\n    <!-- module:  xhtml-blkpres-1.mod -->\n    <!ENTITY % hr.qname      \"%XHTML.pfx;hr\" >\n\n    <!-- module:  xhtml-blkstruct-1.mod -->\n    <!ENTITY % div.qname     \"%XHTML.pfx;div\" ><!ENTITY % p.qname       \"%XHTML.pfx;p\" >\n\n    <!-- module:  xhtml-edit-1.mod -->\n    <!ENTITY % ins.qname     \"%XHTML.pfx;ins\" ><!ENTITY % del.qname     \"%XHTML.pfx;del\" >\n\n    <!-- module:  xhtml-form-1.mod -->\n    <!ENTITY % form.qname    \"%XHTML.pfx;form\" ><!ENTITY % label.qname   \"%XHTML.pfx;label\" ><!ENTITY % input.qname   \"%XHTML.pfx;input\" ><!ENTITY % select.qname  \"%XHTML.pfx;select\" ><!ENTITY % optgroup.qname  \"%XHTML.pfx;optgroup\" ><!ENTITY % option.qname  \"%XHTML.pfx;option\" ><!ENTITY % textarea.qname  \"%XHTML.pfx;textarea\" ><!ENTITY % fieldset.qname  \"%XHTML.pfx;fieldset\" ><!ENTITY % legend.qname  \"%XHTML.pfx;legend\" ><!ENTITY % button.qname  \"%XHTML.pfx;button\" >\n\n    <!-- module:  xhtml-hypertext-1.mod -->\n    <!ENTITY % a.qname       \"%XHTML.pfx;a\" >\n\n    <!-- module:  xhtml-image-1.mod -->\n    <!ENTITY % img.qname     \"%XHTML.pfx;img\" >\n\n    <!-- module:  xhtml-inlphras-1.mod -->\n    <!ENTITY % abbr.qname    \"%XHTML.pfx;abbr\" ><!ENTITY % acronym.qname \"%XHTML.pfx;acronym\" ><!ENTITY % cite.qname    \"%XHTML.pfx;cite\" ><!ENTITY % code.qname    \"%XHTML.pfx;code\" ><!ENTITY % dfn.qname     \"%XHTML.pfx;dfn\" ><!ENTITY % em.qname      \"%XHTML.pfx;em\" ><!ENTITY % kbd.qname     \"%XHTML.pfx;kbd\" ><!ENTITY % q.qname       \"%XHTML.pfx;q\" ><!ENTITY % samp.qname    \"%XHTML.pfx;samp\" ><!ENTITY % strong.qname  \"%XHTML.pfx;strong\" ><!ENTITY % var.qname     \"%XHTML.pfx;var\" >\n\n    <!-- module:  xhtml-inlpres-1.mod -->\n    <!ENTITY % b.qname       \"%XHTML.pfx;b\" ><!ENTITY % big.qname     \"%XHTML.pfx;big\" ><!ENTITY % i.qname       \"%XHTML.pfx;i\" ><!ENTITY % small.qname   \"%XHTML.pfx;small\" ><!ENTITY % sub.qname     \"%XHTML.pfx;sub\" ><!ENTITY % sup.qname     \"%XHTML.pfx;sup\" ><!ENTITY % tt.qname      \"%XHTML.pfx;tt\" >\n\n    <!-- module:  xhtml-inlstruct-1.mod -->\n    <!ENTITY % br.qname      \"%XHTML.pfx;br\" ><!ENTITY % span.qname    \"%XHTML.pfx;span\" >\n\n    <!-- module:  xhtml-ismap-1.mod (also csismap, ssismap) -->\n    <!ENTITY % map.qname     \"%XHTML.pfx;map\" ><!ENTITY % area.qname    \"%XHTML.pfx;area\" >\n\n    <!-- module:  xhtml-link-1.mod -->\n    <!ENTITY % link.qname    \"%XHTML.pfx;link\" >\n\n    <!-- module:  xhtml-list-1.mod -->\n    <!ENTITY % dl.qname      \"%XHTML.pfx;dl\" ><!ENTITY % dt.qname      \"%XHTML.pfx;dt\" ><!ENTITY % dd.qname      \"%XHTML.pfx;dd\" ><!ENTITY % ol.qname      \"%XHTML.pfx;ol\" ><!ENTITY % ul.qname      \"%XHTML.pfx;ul\" ><!ENTITY % li.qname      \"%XHTML.pfx;li\" >\n\n    <!-- module:  xhtml-meta-1.mod -->\n    <!ENTITY % meta.qname    \"%XHTML.pfx;meta\" >\n\n    <!-- module:  xhtml-param-1.mod -->\n    <!ENTITY % param.qname   \"%XHTML.pfx;param\" >\n\n    <!-- module:  xhtml-object-1.mod -->\n    <!ENTITY % object.qname  \"%XHTML.pfx;object\" >\n\n    <!-- module:  xhtml-script-1.mod -->\n    <!ENTITY % script.qname  \"%XHTML.pfx;script\" ><!ENTITY % noscript.qname  \"%XHTML.pfx;noscript\" >\n\n    <!-- module:  xhtml-struct-1.mod -->\n    <!ENTITY % html.qname    \"%XHTML.pfx;html\" ><!ENTITY % head.qname    \"%XHTML.pfx;head\" ><!ENTITY % title.qname   \"%XHTML.pfx;title\" ><!ENTITY % body.qname    \"%XHTML.pfx;body\" >\n\n    <!-- module:  xhtml-style-1.mod -->\n    <!ENTITY % style.qname   \"%XHTML.pfx;style\" >\n\n    <!-- module:  xhtml-table-1.mod -->\n    <!ENTITY % table.qname   \"%XHTML.pfx;table\" ><!ENTITY % caption.qname \"%XHTML.pfx;caption\" ><!ENTITY % thead.qname   \"%XHTML.pfx;thead\" ><!ENTITY % tfoot.qname   \"%XHTML.pfx;tfoot\" ><!ENTITY % tbody.qname   \"%XHTML.pfx;tbody\" ><!ENTITY % colgroup.qname  \"%XHTML.pfx;colgroup\" ><!ENTITY % col.qname     \"%XHTML.pfx;col\" ><!ENTITY % tr.qname      \"%XHTML.pfx;tr\" ><!ENTITY % th.qname      \"%XHTML.pfx;th\" ><!ENTITY % td.qname      \"%XHTML.pfx;td\" >\n\n    <!-- module:  xhtml-ruby-1.mod -->\n\n    <!ENTITY % ruby.qname    \"%XHTML.pfx;ruby\" ><!ENTITY % rbc.qname     \"%XHTML.pfx;rbc\" ><!ENTITY % rtc.qname     \"%XHTML.pfx;rtc\" ><!ENTITY % rb.qname      \"%XHTML.pfx;rb\" ><!ENTITY % rt.qname      \"%XHTML.pfx;rt\" ><!ENTITY % rp.qname      \"%XHTML.pfx;rp\" >\n\n    <!-- Provisional XHTML 2.0 Qualified Names  ...................... -->\n\n    <!-- module:  xhtml-image-2.mod -->\n    <!ENTITY % alt.qname     \"%XHTML.pfx;alt\" >\n\n    <!-- end of xhtml-qname-1.mod -->\n"
  },
  {
    "path": "modules/book/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-script-1.mod",
    "content": "<!-- ...................................................................... -->\n    <!-- XHTML Document Scripting Module  ..................................... -->\n    <!-- file: xhtml-script-1.mod\n\n         This is XHTML, a reformulation of HTML as a modular XML application.\n         Copyright 1998-2005 W3C (MIT, ERCIM, Keio), All Rights Reserved.\n         Revision: $Id: xhtml-script-1.mod,v 1.1 2010/07/29 13:42:48 bertails Exp $ SMI\n\n         This DTD module is identified by the PUBLIC and SYSTEM identifiers:\n\n           PUBLIC \"-//W3C//ELEMENTS XHTML Scripting 1.0//EN\"\n           SYSTEM \"http://www.w3.org/MarkUp/DTD/xhtml-script-1.mod\"\n\n         Revisions:\n         (none)\n         ....................................................................... -->\n\n    <!-- Scripting\n\n            script, noscript\n\n         This module declares element types and attributes used to provide\n         support for executable scripts as well as an alternate content\n         container where scripts are not supported.\n    -->\n\n    <!-- script: Scripting Statement ....................... -->\n\n    <!ENTITY % script.element  \"INCLUDE\" ><![%script.element;[<!ENTITY % script.content  \"( #PCDATA )\" ><!ENTITY % script.qname  \"script\" ><!ELEMENT %script.qname;  %script.content; ><!-- end of script.element -->]]>\n\n    <!ENTITY % script.attlist  \"INCLUDE\" ><![%script.attlist;[<!ATTLIST %script.qname;%XHTML.xmlns.attrib;    %id.attrib;xml:space    ( preserve )             #FIXED 'preserve'charset      %Charset.datatype;       #IMPLIEDtype         %ContentType.datatype;   #REQUIREDsrc          %URI.datatype;           #IMPLIEDdefer        ( defer )                #IMPLIED><!-- end of script.attlist -->]]>\n\n    <!-- noscript: No-Script Alternate Content ............. -->\n\n    <!ENTITY % noscript.element  \"INCLUDE\" ><![%noscript.element;[<!ENTITY % noscript.content\"( %Block.mix; )+\"><!ENTITY % noscript.qname  \"noscript\" ><!ELEMENT %noscript.qname;  %noscript.content; ><!-- end of noscript.element -->]]>\n\n    <!ENTITY % noscript.attlist  \"INCLUDE\" ><![%noscript.attlist;[<!ATTLIST %noscript.qname;%Common.attrib;><!-- end of noscript.attlist -->]]>\n\n    <!-- end of xhtml-script-1.mod -->\n"
  },
  {
    "path": "modules/book/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-special.ent",
    "content": "<!-- Special characters for XHTML -->\n\n    <!-- Character entity set. Typical invocation:\n         <!ENTITY % HTMLspecial PUBLIC\n            \"-//W3C//ENTITIES Special for XHTML//EN\"\n            \"http://www.w3.org/TR/xhtml1/DTD/xhtml-special.ent\">\n         %HTMLspecial;\n    -->\n\n    <!-- Portions (C) International Organization for Standardization 1986:\n         Permission to copy in any form is granted for use with\n         conforming SGML systems and applications as defined in\n         ISO 8879, provided this notice is included in all copies.\n    -->\n\n    <!-- Relevant ISO entity set is given unless names are newly introduced.\n         New names (i.e., not in ISO 8879 list) do not clash with any\n         existing ISO 8879 entity names. ISO 10646 character numbers\n         are given for each character, in hex. values are decimal\n         conversions of the ISO 10646 values and refer to the document\n         character set. Names are Unicode names.\n    -->\n\n    <!-- C0 Controls and Basic Latin -->\n    <!ENTITY quot    \"&#34;\"> <!--  quotation mark, U+0022 ISOnum -->\n    <!ENTITY amp     \"&#38;#38;\"> <!--  ampersand, U+0026 ISOnum -->\n    <!ENTITY lt      \"&#38;#60;\"> <!--  less-than sign, U+003C ISOnum -->\n    <!ENTITY gt      \"&#62;\"> <!--  greater-than sign, U+003E ISOnum -->\n    <!ENTITY apos     \"&#39;\"> <!--  apostrophe = APL quote, U+0027 ISOnum -->\n\n    <!-- Latin Extended-A -->\n    <!ENTITY OElig   \"&#338;\"> <!--  latin capital ligature OE,\n                                    U+0152 ISOlat2 -->\n    <!ENTITY oelig   \"&#339;\"> <!--  latin small ligature oe, U+0153 ISOlat2 -->\n    <!-- ligature is a misnomer, this is a separate character in some languages -->\n    <!ENTITY Scaron  \"&#352;\"> <!--  latin capital letter S with caron,\n                                    U+0160 ISOlat2 -->\n    <!ENTITY scaron  \"&#353;\"> <!--  latin small letter s with caron,\n                                    U+0161 ISOlat2 -->\n    <!ENTITY Yuml    \"&#376;\"> <!--  latin capital letter Y with diaeresis,\n                                    U+0178 ISOlat2 -->\n\n    <!-- Spacing Modifier Letters -->\n    <!ENTITY circ    \"&#710;\"> <!--  modifier letter circumflex accent,\n                                    U+02C6 ISOpub -->\n    <!ENTITY tilde   \"&#732;\"> <!--  small tilde, U+02DC ISOdia -->\n\n    <!-- General Punctuation -->\n    <!ENTITY ensp    \"&#8194;\"> <!-- en space, U+2002 ISOpub -->\n    <!ENTITY emsp    \"&#8195;\"> <!-- em space, U+2003 ISOpub -->\n    <!ENTITY thinsp  \"&#8201;\"> <!-- thin space, U+2009 ISOpub -->\n    <!ENTITY zwnj    \"&#8204;\"> <!-- zero width non-joiner,\n                                    U+200C NEW RFC 2070 -->\n    <!ENTITY zwj     \"&#8205;\"> <!-- zero width joiner, U+200D NEW RFC 2070 -->\n    <!ENTITY lrm     \"&#8206;\"> <!-- left-to-right mark, U+200E NEW RFC 2070 -->\n    <!ENTITY rlm     \"&#8207;\"> <!-- right-to-left mark, U+200F NEW RFC 2070 -->\n    <!ENTITY ndash   \"&#8211;\"> <!-- en dash, U+2013 ISOpub -->\n    <!ENTITY mdash   \"&#8212;\"> <!-- em dash, U+2014 ISOpub -->\n    <!ENTITY lsquo   \"&#8216;\"> <!-- left single quotation mark,\n                                    U+2018 ISOnum -->\n    <!ENTITY rsquo   \"&#8217;\"> <!-- right single quotation mark,\n                                    U+2019 ISOnum -->\n    <!ENTITY sbquo   \"&#8218;\"> <!-- single low-9 quotation mark, U+201A NEW -->\n    <!ENTITY ldquo   \"&#8220;\"> <!-- left double quotation mark,\n                                    U+201C ISOnum -->\n    <!ENTITY rdquo   \"&#8221;\"> <!-- right double quotation mark,\n                                    U+201D ISOnum -->\n    <!ENTITY bdquo   \"&#8222;\"> <!-- double low-9 quotation mark, U+201E NEW -->\n    <!ENTITY dagger  \"&#8224;\"> <!-- dagger, U+2020 ISOpub -->\n    <!ENTITY Dagger  \"&#8225;\"> <!-- double dagger, U+2021 ISOpub -->\n    <!ENTITY permil  \"&#8240;\"> <!-- per mille sign, U+2030 ISOtech -->\n    <!ENTITY lsaquo  \"&#8249;\"> <!-- single left-pointing angle quotation mark,\n                                    U+2039 ISO proposed -->\n    <!-- lsaquo is proposed but not yet ISO standardized -->\n    <!ENTITY rsaquo  \"&#8250;\"> <!-- single right-pointing angle quotation mark,\n                                    U+203A ISO proposed -->\n    <!-- rsaquo is proposed but not yet ISO standardized -->\n\n    <!-- Currency Symbols -->\n    <!ENTITY euro   \"&#8364;\"> <!--  euro sign, U+20AC NEW -->\n"
  },
  {
    "path": "modules/book/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-ssismap-1.mod",
    "content": "<!-- ...................................................................... -->\n    <!-- XHTML Server-side Image Map Module  .................................. -->\n    <!-- file: xhtml-ssismap-1.mod\n\n         This is XHTML, a reformulation of HTML as a modular XML application.\n         Copyright 1998-2005 W3C (MIT, ERCIM, Keio), All Rights Reserved.\n         Revision: $Id: xhtml-ssismap-1.mod,v 1.1 2010/07/29 13:42:48 bertails Exp $ SMI\n\n         This DTD module is identified by the PUBLIC and SYSTEM identifiers:\n\n           PUBLIC \"-//W3C//ELEMENTS XHTML Server-side Image Maps 1.0//EN\"\n           SYSTEM \"http://www.w3.org/MarkUp/DTD/xhtml-ssismap-1.mod\"\n\n         Revisions:\n    #2000-10-22: added declaration for 'ismap' on <input>\n         ....................................................................... -->\n\n    <!-- Server-side Image Maps\n\n         This adds the 'ismap' attribute to the img and input elements\n         to support server-side processing of a user selection.\n    -->\n\n    <!ATTLIST %img.qname;ismap        ( ismap )                #IMPLIED>\n\n    <!ATTLIST %input.qname;ismap        ( ismap )                #IMPLIED>\n\n    <!-- end of xhtml-ssismap-1.mod -->\n"
  },
  {
    "path": "modules/book/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-struct-1.mod",
    "content": "<!-- ...................................................................... -->\n    <!-- XHTML Structure Module  .............................................. -->\n    <!-- file: xhtml-struct-1.mod\n\n         This is XHTML, a reformulation of HTML as a modular XML application.\n         Copyright 1998-2005 W3C (MIT, ERCIM, Keio), All Rights Reserved.\n         Revision: $Id: xhtml-struct-1.mod,v 1.1 2010/07/29 13:42:48 bertails Exp $ SMI\n\n         This DTD module is identified by the PUBLIC and SYSTEM identifiers:\n\n           PUBLIC \"-//W3C//ELEMENTS XHTML Document Structure 1.0//EN\"\n           SYSTEM \"http://www.w3.org/MarkUp/DTD/xhtml-struct-1.mod\"\n\n         Revisions:\n         (none)\n         ....................................................................... -->\n\n    <!-- Document Structure\n\n            title, head, body, html\n\n         The Structure Module defines the major structural elements and\n         their attributes.\n\n         Note that the content model of the head element type is redeclared\n         when the Base Module is included in the DTD.\n\n         The parameter entity containing the XML namespace URI value used\n         for XHTML is '%XHTML.xmlns;', defined in the Qualified Names module.\n    -->\n\n    <!-- title: Document Title ............................. -->\n\n    <!-- The title element is not considered part of the flow of text.\n         It should be displayed, for example as the page header or\n         window title. Exactly one title is required per document.\n    -->\n\n    <!ENTITY % title.element  \"INCLUDE\" ><![%title.element;[<!ENTITY % title.content  \"( #PCDATA )\" ><!ENTITY % title.qname  \"title\" ><!ELEMENT %title.qname;  %title.content; ><!-- end of title.element -->]]>\n\n    <!ENTITY % title.attlist  \"INCLUDE\" ><![%title.attlist;[<!ATTLIST %title.qname;%XHTML.xmlns.attrib;%I18n.attrib;><!-- end of title.attlist -->]]>\n\n    <!-- head: Document Head ............................... -->\n\n    <!ENTITY % head.element  \"INCLUDE\" ><![%head.element;[<!ENTITY % head.content\"( %HeadOpts.mix;, %title.qname;, %HeadOpts.mix; )\"><!ENTITY % head.qname  \"head\" ><!ELEMENT %head.qname;  %head.content; ><!-- end of head.element -->]]>\n\n    <!ENTITY % head.attlist  \"INCLUDE\" ><![%head.attlist;[<!-- reserved for future use with document profiles\n-->\n    <!ENTITY % profile.attrib\"profile      %URIs.datatype;           #IMPLIED\">\n\n    <!ATTLIST %head.qname;%XHTML.xmlns.attrib;%I18n.attrib;%profile.attrib;%id.attrib;><!-- end of head.attlist -->]]>\n\n    <!-- body: Document Body ............................... -->\n\n    <!ENTITY % body.element  \"INCLUDE\" ><![%body.element;[<!ENTITY % body.content\"( %Block.mix; )*\"><!ENTITY % body.qname  \"body\" ><!ELEMENT %body.qname;  %body.content; ><!-- end of body.element -->]]>\n\n    <!ENTITY % body.attlist  \"INCLUDE\" ><![%body.attlist;[<!ATTLIST %body.qname;%Common.attrib;><!-- end of body.attlist -->]]>\n\n    <!-- html: XHTML Document Element ...................... -->\n\n    <!ENTITY % html.element  \"INCLUDE\" ><![%html.element;[<!ENTITY % html.content  \"( %head.qname;, %body.qname; )\" ><!ENTITY % html.qname  \"html\" ><!ELEMENT %html.qname;  %html.content; ><!-- end of html.element -->]]>\n\n    <![%XHTML.xsi.attrs;[<!-- define a parameter for the XSI schemaLocation attribute -->\n        <!ENTITY % XSI.schemaLocation.attrib\"%XSI.pfx;schemaLocation  %URIs.datatype;    #IMPLIED\">]]><!ENTITY % XSI.schemaLocation.attrib \"\">\n\n    <!ENTITY % html.attlist  \"INCLUDE\" ><![%html.attlist;[<!-- version attribute value defined in driver\n-->\n    <!ENTITY % XHTML.version.attrib\"version      CDATA           #FIXED '%XHTML.version;'\">\n\n    <!-- see the Qualified Names module for information\n         on how to extend XHTML using XML namespaces\n    -->\n    <!ATTLIST %html.qname;%XHTML.xmlns.attrib;%XSI.schemaLocation.attrib;%XHTML.version.attrib;%I18n.attrib;%id.attrib;><!-- end of html.attlist -->]]>\n\n    <!-- end of xhtml-struct-1.mod -->\n"
  },
  {
    "path": "modules/book/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-style-1.mod",
    "content": "<!-- ...................................................................... -->\n    <!-- XHTML Document Style Sheet Module  ................................... -->\n    <!-- file: xhtml-style-1.mod\n\n         This is XHTML, a reformulation of HTML as a modular XML application.\n         Copyright 1998-2005 W3C (MIT, ERCIM, Keio), All Rights Reserved.\n         Revision: $Id: xhtml-style-1.mod,v 1.1 2010/07/29 13:42:48 bertails Exp $ SMI\n\n         This DTD module is identified by the PUBLIC and SYSTEM identifiers:\n\n           PUBLIC \"-//W3C//DTD XHTML Style Sheets 1.0//EN\"\n           SYSTEM \"http://www.w3.org/MarkUp/DTD/xhtml-style-1.mod\"\n\n         Revisions:\n         (none)\n         ....................................................................... -->\n\n    <!-- Style Sheets\n\n            style\n\n         This module declares the style element type and its attributes,\n         used to embed style sheet information in the document head element.\n    -->\n\n    <!-- style: Style Sheet Information .................... -->\n\n    <!ENTITY % style.element  \"INCLUDE\" ><![%style.element;[<!ENTITY % style.content  \"( #PCDATA )\" ><!ENTITY % style.qname  \"style\" ><!ELEMENT %style.qname;  %style.content; ><!-- end of style.element -->]]>\n\n    <!ENTITY % style.attlist  \"INCLUDE\" ><![%style.attlist;[<!ATTLIST %style.qname;%XHTML.xmlns.attrib;%id.attrib;%title.attrib;%I18n.attrib;xml:space    ( preserve )             #FIXED 'preserve'type         %ContentType.datatype;   #REQUIREDmedia        %MediaDesc.datatype;     #IMPLIED><!-- end of style.attlist -->]]>\n\n    <!-- end of xhtml-style-1.mod -->\n"
  },
  {
    "path": "modules/book/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-symbol.ent",
    "content": "<!-- Mathematical, Greek and Symbolic characters for XHTML -->\n\n    <!-- Character entity set. Typical invocation:\n         <!ENTITY % HTMLsymbol PUBLIC\n            \"-//W3C//ENTITIES Symbols for XHTML//EN\"\n            \"http://www.w3.org/TR/xhtml1/DTD/xhtml-symbol.ent\">\n         %HTMLsymbol;\n    -->\n\n    <!-- Portions (C) International Organization for Standardization 1986:\n         Permission to copy in any form is granted for use with\n         conforming SGML systems and applications as defined in\n         ISO 8879, provided this notice is included in all copies.\n    -->\n\n    <!-- Relevant ISO entity set is given unless names are newly introduced.\n         New names (i.e., not in ISO 8879 list) do not clash with any\n         existing ISO 8879 entity names. ISO 10646 character numbers\n         are given for each character, in hex. values are decimal\n         conversions of the ISO 10646 values and refer to the document\n         character set. Names are Unicode names.\n    -->\n\n    <!-- Latin Extended-B -->\n    <!ENTITY fnof     \"&#402;\"> <!-- latin small letter f with hook = function\n                                    = florin, U+0192 ISOtech -->\n\n    <!-- Greek -->\n    <!ENTITY Alpha    \"&#913;\"> <!-- greek capital letter alpha, U+0391 -->\n    <!ENTITY Beta     \"&#914;\"> <!-- greek capital letter beta, U+0392 -->\n    <!ENTITY Gamma    \"&#915;\"> <!-- greek capital letter gamma,\n                                    U+0393 ISOgrk3 -->\n    <!ENTITY Delta    \"&#916;\"> <!-- greek capital letter delta,\n                                    U+0394 ISOgrk3 -->\n    <!ENTITY Epsilon  \"&#917;\"> <!-- greek capital letter epsilon, U+0395 -->\n    <!ENTITY Zeta     \"&#918;\"> <!-- greek capital letter zeta, U+0396 -->\n    <!ENTITY Eta      \"&#919;\"> <!-- greek capital letter eta, U+0397 -->\n    <!ENTITY Theta    \"&#920;\"> <!-- greek capital letter theta,\n                                    U+0398 ISOgrk3 -->\n    <!ENTITY Iota     \"&#921;\"> <!-- greek capital letter iota, U+0399 -->\n    <!ENTITY Kappa    \"&#922;\"> <!-- greek capital letter kappa, U+039A -->\n    <!ENTITY Lambda   \"&#923;\"> <!-- greek capital letter lamda,\n                                    U+039B ISOgrk3 -->\n    <!ENTITY Mu       \"&#924;\"> <!-- greek capital letter mu, U+039C -->\n    <!ENTITY Nu       \"&#925;\"> <!-- greek capital letter nu, U+039D -->\n    <!ENTITY Xi       \"&#926;\"> <!-- greek capital letter xi, U+039E ISOgrk3 -->\n    <!ENTITY Omicron  \"&#927;\"> <!-- greek capital letter omicron, U+039F -->\n    <!ENTITY Pi       \"&#928;\"> <!-- greek capital letter pi, U+03A0 ISOgrk3 -->\n    <!ENTITY Rho      \"&#929;\"> <!-- greek capital letter rho, U+03A1 -->\n    <!-- there is no Sigmaf, and no U+03A2 character either -->\n    <!ENTITY Sigma    \"&#931;\"> <!-- greek capital letter sigma,\n                                    U+03A3 ISOgrk3 -->\n    <!ENTITY Tau      \"&#932;\"> <!-- greek capital letter tau, U+03A4 -->\n    <!ENTITY Upsilon  \"&#933;\"> <!-- greek capital letter upsilon,\n                                    U+03A5 ISOgrk3 -->\n    <!ENTITY Phi      \"&#934;\"> <!-- greek capital letter phi,\n                                    U+03A6 ISOgrk3 -->\n    <!ENTITY Chi      \"&#935;\"> <!-- greek capital letter chi, U+03A7 -->\n    <!ENTITY Psi      \"&#936;\"> <!-- greek capital letter psi,\n                                    U+03A8 ISOgrk3 -->\n    <!ENTITY Omega    \"&#937;\"> <!-- greek capital letter omega,\n                                    U+03A9 ISOgrk3 -->\n\n    <!ENTITY alpha    \"&#945;\"> <!-- greek small letter alpha,\n                                    U+03B1 ISOgrk3 -->\n    <!ENTITY beta     \"&#946;\"> <!-- greek small letter beta, U+03B2 ISOgrk3 -->\n    <!ENTITY gamma    \"&#947;\"> <!-- greek small letter gamma,\n                                    U+03B3 ISOgrk3 -->\n    <!ENTITY delta    \"&#948;\"> <!-- greek small letter delta,\n                                    U+03B4 ISOgrk3 -->\n    <!ENTITY epsilon  \"&#949;\"> <!-- greek small letter epsilon,\n                                    U+03B5 ISOgrk3 -->\n    <!ENTITY zeta     \"&#950;\"> <!-- greek small letter zeta, U+03B6 ISOgrk3 -->\n    <!ENTITY eta      \"&#951;\"> <!-- greek small letter eta, U+03B7 ISOgrk3 -->\n    <!ENTITY theta    \"&#952;\"> <!-- greek small letter theta,\n                                    U+03B8 ISOgrk3 -->\n    <!ENTITY iota     \"&#953;\"> <!-- greek small letter iota, U+03B9 ISOgrk3 -->\n    <!ENTITY kappa    \"&#954;\"> <!-- greek small letter kappa,\n                                    U+03BA ISOgrk3 -->\n    <!ENTITY lambda   \"&#955;\"> <!-- greek small letter lamda,\n                                    U+03BB ISOgrk3 -->\n    <!ENTITY mu       \"&#956;\"> <!-- greek small letter mu, U+03BC ISOgrk3 -->\n    <!ENTITY nu       \"&#957;\"> <!-- greek small letter nu, U+03BD ISOgrk3 -->\n    <!ENTITY xi       \"&#958;\"> <!-- greek small letter xi, U+03BE ISOgrk3 -->\n    <!ENTITY omicron  \"&#959;\"> <!-- greek small letter omicron, U+03BF NEW -->\n    <!ENTITY pi       \"&#960;\"> <!-- greek small letter pi, U+03C0 ISOgrk3 -->\n    <!ENTITY rho      \"&#961;\"> <!-- greek small letter rho, U+03C1 ISOgrk3 -->\n    <!ENTITY sigmaf   \"&#962;\"> <!-- greek small letter final sigma,\n                                    U+03C2 ISOgrk3 -->\n    <!ENTITY sigma    \"&#963;\"> <!-- greek small letter sigma,\n                                    U+03C3 ISOgrk3 -->\n    <!ENTITY tau      \"&#964;\"> <!-- greek small letter tau, U+03C4 ISOgrk3 -->\n    <!ENTITY upsilon  \"&#965;\"> <!-- greek small letter upsilon,\n                                    U+03C5 ISOgrk3 -->\n    <!ENTITY phi      \"&#966;\"> <!-- greek small letter phi, U+03C6 ISOgrk3 -->\n    <!ENTITY chi      \"&#967;\"> <!-- greek small letter chi, U+03C7 ISOgrk3 -->\n    <!ENTITY psi      \"&#968;\"> <!-- greek small letter psi, U+03C8 ISOgrk3 -->\n    <!ENTITY omega    \"&#969;\"> <!-- greek small letter omega,\n                                    U+03C9 ISOgrk3 -->\n    <!ENTITY thetasym \"&#977;\"> <!-- greek theta symbol,\n                                    U+03D1 NEW -->\n    <!ENTITY upsih    \"&#978;\"> <!-- greek upsilon with hook symbol,\n                                    U+03D2 NEW -->\n    <!ENTITY piv      \"&#982;\"> <!-- greek pi symbol, U+03D6 ISOgrk3 -->\n\n    <!-- General Punctuation -->\n    <!ENTITY bull     \"&#8226;\"> <!-- bullet = black small circle,\n                                     U+2022 ISOpub  -->\n    <!-- bullet is NOT the same as bullet operator, U+2219 -->\n    <!ENTITY hellip   \"&#8230;\"> <!-- horizontal ellipsis = three dot leader,\n                                     U+2026 ISOpub  -->\n    <!ENTITY prime    \"&#8242;\"> <!-- prime = minutes = feet, U+2032 ISOtech -->\n    <!ENTITY Prime    \"&#8243;\"> <!-- double prime = seconds = inches,\n                                     U+2033 ISOtech -->\n    <!ENTITY oline    \"&#8254;\"> <!-- overline = spacing overscore,\n                                     U+203E NEW -->\n    <!ENTITY frasl    \"&#8260;\"> <!-- fraction slash, U+2044 NEW -->\n\n    <!-- Letterlike Symbols -->\n    <!ENTITY weierp   \"&#8472;\"> <!-- script capital P = power set\n                                     = Weierstrass p, U+2118 ISOamso -->\n    <!ENTITY image    \"&#8465;\"> <!-- black-letter capital I = imaginary part,\n                                     U+2111 ISOamso -->\n    <!ENTITY real     \"&#8476;\"> <!-- black-letter capital R = real part symbol,\n                                     U+211C ISOamso -->\n    <!ENTITY trade    \"&#8482;\"> <!-- trade mark sign, U+2122 ISOnum -->\n    <!ENTITY alefsym  \"&#8501;\"> <!-- alef symbol = first transfinite cardinal,\n                                     U+2135 NEW -->\n    <!-- alef symbol is NOT the same as hebrew letter alef,\n         U+05D0 although the same glyph could be used to depict both characters -->\n\n    <!-- Arrows -->\n    <!ENTITY larr     \"&#8592;\"> <!-- leftwards arrow, U+2190 ISOnum -->\n    <!ENTITY uarr     \"&#8593;\"> <!-- upwards arrow, U+2191 ISOnum-->\n    <!ENTITY rarr     \"&#8594;\"> <!-- rightwards arrow, U+2192 ISOnum -->\n    <!ENTITY darr     \"&#8595;\"> <!-- downwards arrow, U+2193 ISOnum -->\n    <!ENTITY harr     \"&#8596;\"> <!-- left right arrow, U+2194 ISOamsa -->\n    <!ENTITY crarr    \"&#8629;\"> <!-- downwards arrow with corner leftwards\n                                     = carriage return, U+21B5 NEW -->\n    <!ENTITY lArr     \"&#8656;\"> <!-- leftwards double arrow, U+21D0 ISOtech -->\n    <!-- Unicode does not say that lArr is the same as the 'is implied by' arrow\n        but also does not have any other character for that function. So lArr can\n        be used for 'is implied by' as ISOtech suggests -->\n    <!ENTITY uArr     \"&#8657;\"> <!-- upwards double arrow, U+21D1 ISOamsa -->\n    <!ENTITY rArr     \"&#8658;\"> <!-- rightwards double arrow,\n                                     U+21D2 ISOtech -->\n    <!-- Unicode does not say this is the 'implies' character but does not have\n         another character with this function so rArr can be used for 'implies'\n         as ISOtech suggests -->\n    <!ENTITY dArr     \"&#8659;\"> <!-- downwards double arrow, U+21D3 ISOamsa -->\n    <!ENTITY hArr     \"&#8660;\"> <!-- left right double arrow,\n                                     U+21D4 ISOamsa -->\n\n    <!-- Mathematical Operators -->\n    <!ENTITY forall   \"&#8704;\"> <!-- for all, U+2200 ISOtech -->\n    <!ENTITY part     \"&#8706;\"> <!-- partial differential, U+2202 ISOtech  -->\n    <!ENTITY exist    \"&#8707;\"> <!-- there exists, U+2203 ISOtech -->\n    <!ENTITY empty    \"&#8709;\"> <!-- empty set = null set, U+2205 ISOamso -->\n    <!ENTITY nabla    \"&#8711;\"> <!-- nabla = backward difference,\n                                     U+2207 ISOtech -->\n    <!ENTITY isin     \"&#8712;\"> <!-- element of, U+2208 ISOtech -->\n    <!ENTITY notin    \"&#8713;\"> <!-- not an element of, U+2209 ISOtech -->\n    <!ENTITY ni       \"&#8715;\"> <!-- contains as member, U+220B ISOtech -->\n    <!ENTITY prod     \"&#8719;\"> <!-- n-ary product = product sign,\n                                     U+220F ISOamsb -->\n    <!-- prod is NOT the same character as U+03A0 'greek capital letter pi' though\n         the same glyph might be used for both -->\n    <!ENTITY sum      \"&#8721;\"> <!-- n-ary summation, U+2211 ISOamsb -->\n    <!-- sum is NOT the same character as U+03A3 'greek capital letter sigma'\n         though the same glyph might be used for both -->\n    <!ENTITY minus    \"&#8722;\"> <!-- minus sign, U+2212 ISOtech -->\n    <!ENTITY lowast   \"&#8727;\"> <!-- asterisk operator, U+2217 ISOtech -->\n    <!ENTITY radic    \"&#8730;\"> <!-- square root = radical sign,\n                                     U+221A ISOtech -->\n    <!ENTITY prop     \"&#8733;\"> <!-- proportional to, U+221D ISOtech -->\n    <!ENTITY infin    \"&#8734;\"> <!-- infinity, U+221E ISOtech -->\n    <!ENTITY ang      \"&#8736;\"> <!-- angle, U+2220 ISOamso -->\n    <!ENTITY and      \"&#8743;\"> <!-- logical and = wedge, U+2227 ISOtech -->\n    <!ENTITY or       \"&#8744;\"> <!-- logical or = vee, U+2228 ISOtech -->\n    <!ENTITY cap      \"&#8745;\"> <!-- intersection = cap, U+2229 ISOtech -->\n    <!ENTITY cup      \"&#8746;\"> <!-- union = cup, U+222A ISOtech -->\n    <!ENTITY int      \"&#8747;\"> <!-- integral, U+222B ISOtech -->\n    <!ENTITY there4   \"&#8756;\"> <!-- therefore, U+2234 ISOtech -->\n    <!ENTITY sim      \"&#8764;\"> <!-- tilde operator = varies with = similar to,\n                                     U+223C ISOtech -->\n    <!-- tilde operator is NOT the same character as the tilde, U+007E,\n         although the same glyph might be used to represent both  -->\n    <!ENTITY cong     \"&#8773;\"> <!-- approximately equal to, U+2245 ISOtech -->\n    <!ENTITY asymp    \"&#8776;\"> <!-- almost equal to = asymptotic to,\n                                     U+2248 ISOamsr -->\n    <!ENTITY ne       \"&#8800;\"> <!-- not equal to, U+2260 ISOtech -->\n    <!ENTITY equiv    \"&#8801;\"> <!-- identical to, U+2261 ISOtech -->\n    <!ENTITY le       \"&#8804;\"> <!-- less-than or equal to, U+2264 ISOtech -->\n    <!ENTITY ge       \"&#8805;\"> <!-- greater-than or equal to,\n                                     U+2265 ISOtech -->\n    <!ENTITY sub      \"&#8834;\"> <!-- subset of, U+2282 ISOtech -->\n    <!ENTITY sup      \"&#8835;\"> <!-- superset of, U+2283 ISOtech -->\n    <!ENTITY nsub     \"&#8836;\"> <!-- not a subset of, U+2284 ISOamsn -->\n    <!ENTITY sube     \"&#8838;\"> <!-- subset of or equal to, U+2286 ISOtech -->\n    <!ENTITY supe     \"&#8839;\"> <!-- superset of or equal to,\n                                     U+2287 ISOtech -->\n    <!ENTITY oplus    \"&#8853;\"> <!-- circled plus = direct sum,\n                                     U+2295 ISOamsb -->\n    <!ENTITY otimes   \"&#8855;\"> <!-- circled times = vector product,\n                                     U+2297 ISOamsb -->\n    <!ENTITY perp     \"&#8869;\"> <!-- up tack = orthogonal to = perpendicular,\n                                     U+22A5 ISOtech -->\n    <!ENTITY sdot     \"&#8901;\"> <!-- dot operator, U+22C5 ISOamsb -->\n    <!-- dot operator is NOT the same character as U+00B7 middle dot -->\n\n    <!-- Miscellaneous Technical -->\n    <!ENTITY lceil    \"&#8968;\"> <!-- left ceiling = APL upstile,\n                                     U+2308 ISOamsc  -->\n    <!ENTITY rceil    \"&#8969;\"> <!-- right ceiling, U+2309 ISOamsc  -->\n    <!ENTITY lfloor   \"&#8970;\"> <!-- left floor = APL downstile,\n                                     U+230A ISOamsc  -->\n    <!ENTITY rfloor   \"&#8971;\"> <!-- right floor, U+230B ISOamsc  -->\n    <!ENTITY lang     \"&#9001;\"> <!-- left-pointing angle bracket = bra,\n                                     U+2329 ISOtech -->\n    <!-- lang is NOT the same character as U+003C 'less than sign'\n         or U+2039 'single left-pointing angle quotation mark' -->\n    <!ENTITY rang     \"&#9002;\"> <!-- right-pointing angle bracket = ket,\n                                     U+232A ISOtech -->\n    <!-- rang is NOT the same character as U+003E 'greater than sign'\n         or U+203A 'single right-pointing angle quotation mark' -->\n\n    <!-- Geometric Shapes -->\n    <!ENTITY loz      \"&#9674;\"> <!-- lozenge, U+25CA ISOpub -->\n\n    <!-- Miscellaneous Symbols -->\n    <!ENTITY spades   \"&#9824;\"> <!-- black spade suit, U+2660 ISOpub -->\n    <!-- black here seems to mean filled as opposed to hollow -->\n    <!ENTITY clubs    \"&#9827;\"> <!-- black club suit = shamrock,\n                                     U+2663 ISOpub -->\n    <!ENTITY hearts   \"&#9829;\"> <!-- black heart suit = valentine,\n                                     U+2665 ISOpub -->\n    <!ENTITY diams    \"&#9830;\"> <!-- black diamond suit, U+2666 ISOpub -->\n"
  },
  {
    "path": "modules/book/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-symbol.ent.1",
    "content": "<!-- Mathematical, Greek and Symbolic characters for XHTML -->\n\n<!-- Character entity set. Typical invocation:\n     <!ENTITY % HTMLsymbol PUBLIC\n        \"-//W3C//ENTITIES Symbols for XHTML//EN\"\n        \"http://www.w3.org/TR/xhtml1/DTD/xhtml-symbol.ent\">\n     %HTMLsymbol;\n-->\n\n<!-- Portions (C) International Organization for Standardization 1986:\n     Permission to copy in any form is granted for use with\n     conforming SGML systems and applications as defined in\n     ISO 8879, provided this notice is included in all copies.\n-->\n\n<!-- Relevant ISO entity set is given unless names are newly introduced.\n     New names (i.e., not in ISO 8879 list) do not clash with any\n     existing ISO 8879 entity names. ISO 10646 character numbers\n     are given for each character, in hex. values are decimal\n     conversions of the ISO 10646 values and refer to the document\n     character set. Names are Unicode names. \n-->\n\n<!-- Latin Extended-B -->\n<!ENTITY fnof     \"&#402;\"> <!-- latin small letter f with hook = function\n                                    = florin, U+0192 ISOtech -->\n\n<!-- Greek -->\n<!ENTITY Alpha    \"&#913;\"> <!-- greek capital letter alpha, U+0391 -->\n<!ENTITY Beta     \"&#914;\"> <!-- greek capital letter beta, U+0392 -->\n<!ENTITY Gamma    \"&#915;\"> <!-- greek capital letter gamma,\n                                    U+0393 ISOgrk3 -->\n<!ENTITY Delta    \"&#916;\"> <!-- greek capital letter delta,\n                                    U+0394 ISOgrk3 -->\n<!ENTITY Epsilon  \"&#917;\"> <!-- greek capital letter epsilon, U+0395 -->\n<!ENTITY Zeta     \"&#918;\"> <!-- greek capital letter zeta, U+0396 -->\n<!ENTITY Eta      \"&#919;\"> <!-- greek capital letter eta, U+0397 -->\n<!ENTITY Theta    \"&#920;\"> <!-- greek capital letter theta,\n                                    U+0398 ISOgrk3 -->\n<!ENTITY Iota     \"&#921;\"> <!-- greek capital letter iota, U+0399 -->\n<!ENTITY Kappa    \"&#922;\"> <!-- greek capital letter kappa, U+039A -->\n<!ENTITY Lambda   \"&#923;\"> <!-- greek capital letter lamda,\n                                    U+039B ISOgrk3 -->\n<!ENTITY Mu       \"&#924;\"> <!-- greek capital letter mu, U+039C -->\n<!ENTITY Nu       \"&#925;\"> <!-- greek capital letter nu, U+039D -->\n<!ENTITY Xi       \"&#926;\"> <!-- greek capital letter xi, U+039E ISOgrk3 -->\n<!ENTITY Omicron  \"&#927;\"> <!-- greek capital letter omicron, U+039F -->\n<!ENTITY Pi       \"&#928;\"> <!-- greek capital letter pi, U+03A0 ISOgrk3 -->\n<!ENTITY Rho      \"&#929;\"> <!-- greek capital letter rho, U+03A1 -->\n<!-- there is no Sigmaf, and no U+03A2 character either -->\n<!ENTITY Sigma    \"&#931;\"> <!-- greek capital letter sigma,\n                                    U+03A3 ISOgrk3 -->\n<!ENTITY Tau      \"&#932;\"> <!-- greek capital letter tau, U+03A4 -->\n<!ENTITY Upsilon  \"&#933;\"> <!-- greek capital letter upsilon,\n                                    U+03A5 ISOgrk3 -->\n<!ENTITY Phi      \"&#934;\"> <!-- greek capital letter phi,\n                                    U+03A6 ISOgrk3 -->\n<!ENTITY Chi      \"&#935;\"> <!-- greek capital letter chi, U+03A7 -->\n<!ENTITY Psi      \"&#936;\"> <!-- greek capital letter psi,\n                                    U+03A8 ISOgrk3 -->\n<!ENTITY Omega    \"&#937;\"> <!-- greek capital letter omega,\n                                    U+03A9 ISOgrk3 -->\n\n<!ENTITY alpha    \"&#945;\"> <!-- greek small letter alpha,\n                                    U+03B1 ISOgrk3 -->\n<!ENTITY beta     \"&#946;\"> <!-- greek small letter beta, U+03B2 ISOgrk3 -->\n<!ENTITY gamma    \"&#947;\"> <!-- greek small letter gamma,\n                                    U+03B3 ISOgrk3 -->\n<!ENTITY delta    \"&#948;\"> <!-- greek small letter delta,\n                                    U+03B4 ISOgrk3 -->\n<!ENTITY epsilon  \"&#949;\"> <!-- greek small letter epsilon,\n                                    U+03B5 ISOgrk3 -->\n<!ENTITY zeta     \"&#950;\"> <!-- greek small letter zeta, U+03B6 ISOgrk3 -->\n<!ENTITY eta      \"&#951;\"> <!-- greek small letter eta, U+03B7 ISOgrk3 -->\n<!ENTITY theta    \"&#952;\"> <!-- greek small letter theta,\n                                    U+03B8 ISOgrk3 -->\n<!ENTITY iota     \"&#953;\"> <!-- greek small letter iota, U+03B9 ISOgrk3 -->\n<!ENTITY kappa    \"&#954;\"> <!-- greek small letter kappa,\n                                    U+03BA ISOgrk3 -->\n<!ENTITY lambda   \"&#955;\"> <!-- greek small letter lamda,\n                                    U+03BB ISOgrk3 -->\n<!ENTITY mu       \"&#956;\"> <!-- greek small letter mu, U+03BC ISOgrk3 -->\n<!ENTITY nu       \"&#957;\"> <!-- greek small letter nu, U+03BD ISOgrk3 -->\n<!ENTITY xi       \"&#958;\"> <!-- greek small letter xi, U+03BE ISOgrk3 -->\n<!ENTITY omicron  \"&#959;\"> <!-- greek small letter omicron, U+03BF NEW -->\n<!ENTITY pi       \"&#960;\"> <!-- greek small letter pi, U+03C0 ISOgrk3 -->\n<!ENTITY rho      \"&#961;\"> <!-- greek small letter rho, U+03C1 ISOgrk3 -->\n<!ENTITY sigmaf   \"&#962;\"> <!-- greek small letter final sigma,\n                                    U+03C2 ISOgrk3 -->\n<!ENTITY sigma    \"&#963;\"> <!-- greek small letter sigma,\n                                    U+03C3 ISOgrk3 -->\n<!ENTITY tau      \"&#964;\"> <!-- greek small letter tau, U+03C4 ISOgrk3 -->\n<!ENTITY upsilon  \"&#965;\"> <!-- greek small letter upsilon,\n                                    U+03C5 ISOgrk3 -->\n<!ENTITY phi      \"&#966;\"> <!-- greek small letter phi, U+03C6 ISOgrk3 -->\n<!ENTITY chi      \"&#967;\"> <!-- greek small letter chi, U+03C7 ISOgrk3 -->\n<!ENTITY psi      \"&#968;\"> <!-- greek small letter psi, U+03C8 ISOgrk3 -->\n<!ENTITY omega    \"&#969;\"> <!-- greek small letter omega,\n                                    U+03C9 ISOgrk3 -->\n<!ENTITY thetasym \"&#977;\"> <!-- greek theta symbol,\n                                    U+03D1 NEW -->\n<!ENTITY upsih    \"&#978;\"> <!-- greek upsilon with hook symbol,\n                                    U+03D2 NEW -->\n<!ENTITY piv      \"&#982;\"> <!-- greek pi symbol, U+03D6 ISOgrk3 -->\n\n<!-- General Punctuation -->\n<!ENTITY bull     \"&#8226;\"> <!-- bullet = black small circle,\n                                     U+2022 ISOpub  -->\n<!-- bullet is NOT the same as bullet operator, U+2219 -->\n<!ENTITY hellip   \"&#8230;\"> <!-- horizontal ellipsis = three dot leader,\n                                     U+2026 ISOpub  -->\n<!ENTITY prime    \"&#8242;\"> <!-- prime = minutes = feet, U+2032 ISOtech -->\n<!ENTITY Prime    \"&#8243;\"> <!-- double prime = seconds = inches,\n                                     U+2033 ISOtech -->\n<!ENTITY oline    \"&#8254;\"> <!-- overline = spacing overscore,\n                                     U+203E NEW -->\n<!ENTITY frasl    \"&#8260;\"> <!-- fraction slash, U+2044 NEW -->\n\n<!-- Letterlike Symbols -->\n<!ENTITY weierp   \"&#8472;\"> <!-- script capital P = power set\n                                     = Weierstrass p, U+2118 ISOamso -->\n<!ENTITY image    \"&#8465;\"> <!-- black-letter capital I = imaginary part,\n                                     U+2111 ISOamso -->\n<!ENTITY real     \"&#8476;\"> <!-- black-letter capital R = real part symbol,\n                                     U+211C ISOamso -->\n<!ENTITY trade    \"&#8482;\"> <!-- trade mark sign, U+2122 ISOnum -->\n<!ENTITY alefsym  \"&#8501;\"> <!-- alef symbol = first transfinite cardinal,\n                                     U+2135 NEW -->\n<!-- alef symbol is NOT the same as hebrew letter alef,\n     U+05D0 although the same glyph could be used to depict both characters -->\n\n<!-- Arrows -->\n<!ENTITY larr     \"&#8592;\"> <!-- leftwards arrow, U+2190 ISOnum -->\n<!ENTITY uarr     \"&#8593;\"> <!-- upwards arrow, U+2191 ISOnum-->\n<!ENTITY rarr     \"&#8594;\"> <!-- rightwards arrow, U+2192 ISOnum -->\n<!ENTITY darr     \"&#8595;\"> <!-- downwards arrow, U+2193 ISOnum -->\n<!ENTITY harr     \"&#8596;\"> <!-- left right arrow, U+2194 ISOamsa -->\n<!ENTITY crarr    \"&#8629;\"> <!-- downwards arrow with corner leftwards\n                                     = carriage return, U+21B5 NEW -->\n<!ENTITY lArr     \"&#8656;\"> <!-- leftwards double arrow, U+21D0 ISOtech -->\n<!-- Unicode does not say that lArr is the same as the 'is implied by' arrow\n    but also does not have any other character for that function. So lArr can\n    be used for 'is implied by' as ISOtech suggests -->\n<!ENTITY uArr     \"&#8657;\"> <!-- upwards double arrow, U+21D1 ISOamsa -->\n<!ENTITY rArr     \"&#8658;\"> <!-- rightwards double arrow,\n                                     U+21D2 ISOtech -->\n<!-- Unicode does not say this is the 'implies' character but does not have \n     another character with this function so rArr can be used for 'implies'\n     as ISOtech suggests -->\n<!ENTITY dArr     \"&#8659;\"> <!-- downwards double arrow, U+21D3 ISOamsa -->\n<!ENTITY hArr     \"&#8660;\"> <!-- left right double arrow,\n                                     U+21D4 ISOamsa -->\n\n<!-- Mathematical Operators -->\n<!ENTITY forall   \"&#8704;\"> <!-- for all, U+2200 ISOtech -->\n<!ENTITY part     \"&#8706;\"> <!-- partial differential, U+2202 ISOtech  -->\n<!ENTITY exist    \"&#8707;\"> <!-- there exists, U+2203 ISOtech -->\n<!ENTITY empty    \"&#8709;\"> <!-- empty set = null set, U+2205 ISOamso -->\n<!ENTITY nabla    \"&#8711;\"> <!-- nabla = backward difference,\n                                     U+2207 ISOtech -->\n<!ENTITY isin     \"&#8712;\"> <!-- element of, U+2208 ISOtech -->\n<!ENTITY notin    \"&#8713;\"> <!-- not an element of, U+2209 ISOtech -->\n<!ENTITY ni       \"&#8715;\"> <!-- contains as member, U+220B ISOtech -->\n<!ENTITY prod     \"&#8719;\"> <!-- n-ary product = product sign,\n                                     U+220F ISOamsb -->\n<!-- prod is NOT the same character as U+03A0 'greek capital letter pi' though\n     the same glyph might be used for both -->\n<!ENTITY sum      \"&#8721;\"> <!-- n-ary summation, U+2211 ISOamsb -->\n<!-- sum is NOT the same character as U+03A3 'greek capital letter sigma'\n     though the same glyph might be used for both -->\n<!ENTITY minus    \"&#8722;\"> <!-- minus sign, U+2212 ISOtech -->\n<!ENTITY lowast   \"&#8727;\"> <!-- asterisk operator, U+2217 ISOtech -->\n<!ENTITY radic    \"&#8730;\"> <!-- square root = radical sign,\n                                     U+221A ISOtech -->\n<!ENTITY prop     \"&#8733;\"> <!-- proportional to, U+221D ISOtech -->\n<!ENTITY infin    \"&#8734;\"> <!-- infinity, U+221E ISOtech -->\n<!ENTITY ang      \"&#8736;\"> <!-- angle, U+2220 ISOamso -->\n<!ENTITY and      \"&#8743;\"> <!-- logical and = wedge, U+2227 ISOtech -->\n<!ENTITY or       \"&#8744;\"> <!-- logical or = vee, U+2228 ISOtech -->\n<!ENTITY cap      \"&#8745;\"> <!-- intersection = cap, U+2229 ISOtech -->\n<!ENTITY cup      \"&#8746;\"> <!-- union = cup, U+222A ISOtech -->\n<!ENTITY int      \"&#8747;\"> <!-- integral, U+222B ISOtech -->\n<!ENTITY there4   \"&#8756;\"> <!-- therefore, U+2234 ISOtech -->\n<!ENTITY sim      \"&#8764;\"> <!-- tilde operator = varies with = similar to,\n                                     U+223C ISOtech -->\n<!-- tilde operator is NOT the same character as the tilde, U+007E,\n     although the same glyph might be used to represent both  -->\n<!ENTITY cong     \"&#8773;\"> <!-- approximately equal to, U+2245 ISOtech -->\n<!ENTITY asymp    \"&#8776;\"> <!-- almost equal to = asymptotic to,\n                                     U+2248 ISOamsr -->\n<!ENTITY ne       \"&#8800;\"> <!-- not equal to, U+2260 ISOtech -->\n<!ENTITY equiv    \"&#8801;\"> <!-- identical to, U+2261 ISOtech -->\n<!ENTITY le       \"&#8804;\"> <!-- less-than or equal to, U+2264 ISOtech -->\n<!ENTITY ge       \"&#8805;\"> <!-- greater-than or equal to,\n                                     U+2265 ISOtech -->\n<!ENTITY sub      \"&#8834;\"> <!-- subset of, U+2282 ISOtech -->\n<!ENTITY sup      \"&#8835;\"> <!-- superset of, U+2283 ISOtech -->\n<!ENTITY nsub     \"&#8836;\"> <!-- not a subset of, U+2284 ISOamsn -->\n<!ENTITY sube     \"&#8838;\"> <!-- subset of or equal to, U+2286 ISOtech -->\n<!ENTITY supe     \"&#8839;\"> <!-- superset of or equal to,\n                                     U+2287 ISOtech -->\n<!ENTITY oplus    \"&#8853;\"> <!-- circled plus = direct sum,\n                                     U+2295 ISOamsb -->\n<!ENTITY otimes   \"&#8855;\"> <!-- circled times = vector product,\n                                     U+2297 ISOamsb -->\n<!ENTITY perp     \"&#8869;\"> <!-- up tack = orthogonal to = perpendicular,\n                                     U+22A5 ISOtech -->\n<!ENTITY sdot     \"&#8901;\"> <!-- dot operator, U+22C5 ISOamsb -->\n<!-- dot operator is NOT the same character as U+00B7 middle dot -->\n\n<!-- Miscellaneous Technical -->\n<!ENTITY lceil    \"&#8968;\"> <!-- left ceiling = APL upstile,\n                                     U+2308 ISOamsc  -->\n<!ENTITY rceil    \"&#8969;\"> <!-- right ceiling, U+2309 ISOamsc  -->\n<!ENTITY lfloor   \"&#8970;\"> <!-- left floor = APL downstile,\n                                     U+230A ISOamsc  -->\n<!ENTITY rfloor   \"&#8971;\"> <!-- right floor, U+230B ISOamsc  -->\n<!ENTITY lang     \"&#9001;\"> <!-- left-pointing angle bracket = bra,\n                                     U+2329 ISOtech -->\n<!-- lang is NOT the same character as U+003C 'less than sign' \n     or U+2039 'single left-pointing angle quotation mark' -->\n<!ENTITY rang     \"&#9002;\"> <!-- right-pointing angle bracket = ket,\n                                     U+232A ISOtech -->\n<!-- rang is NOT the same character as U+003E 'greater than sign' \n     or U+203A 'single right-pointing angle quotation mark' -->\n\n<!-- Geometric Shapes -->\n<!ENTITY loz      \"&#9674;\"> <!-- lozenge, U+25CA ISOpub -->\n\n<!-- Miscellaneous Symbols -->\n<!ENTITY spades   \"&#9824;\"> <!-- black spade suit, U+2660 ISOpub -->\n<!-- black here seems to mean filled as opposed to hollow -->\n<!ENTITY clubs    \"&#9827;\"> <!-- black club suit = shamrock,\n                                     U+2663 ISOpub -->\n<!ENTITY hearts   \"&#9829;\"> <!-- black heart suit = valentine,\n                                     U+2665 ISOpub -->\n<!ENTITY diams    \"&#9830;\"> <!-- black diamond suit, U+2666 ISOpub -->\n"
  },
  {
    "path": "modules/book/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-table-1.mod",
    "content": "<!-- ...................................................................... -->\n    <!-- XHTML Table Module  .................................................. -->\n    <!-- file: xhtml-table-1.mod\n\n         This is XHTML, a reformulation of HTML as a modular XML application.\n         Copyright 1998-2005 W3C (MIT, ERCIM, Keio), All Rights Reserved.\n         Revision: $Id: xhtml-table-1.mod,v 1.1 2010/07/29 13:42:48 bertails Exp $ SMI\n\n         This DTD module is identified by the PUBLIC and SYSTEM identifiers:\n\n           PUBLIC \"-//W3C//ELEMENTS XHTML Tables 1.0//EN\"\n           SYSTEM \"http://www.w3.org/MarkUp/DTD/xhtml-table-1.mod\"\n\n         Revisions:\n         (none)\n         ....................................................................... -->\n\n    <!-- Tables\n\n            table, caption, thead, tfoot, tbody, colgroup, col, tr, th, td\n\n         This module declares element types and attributes used to provide\n         table markup similar to HTML 4, including features that enable\n         better accessibility for non-visual user agents.\n    -->\n\n    <!-- declare qualified element type names:\n    -->\n    <!ENTITY % table.qname  \"table\" ><!ENTITY % caption.qname  \"caption\" ><!ENTITY % thead.qname  \"thead\" ><!ENTITY % tfoot.qname  \"tfoot\" ><!ENTITY % tbody.qname  \"tbody\" ><!ENTITY % colgroup.qname  \"colgroup\" ><!ENTITY % col.qname  \"col\" ><!ENTITY % tr.qname  \"tr\" ><!ENTITY % th.qname  \"th\" ><!ENTITY % td.qname  \"td\" >\n\n    <!-- The frame attribute specifies which parts of the frame around\n         the table should be rendered. The values are not the same as\n         CALS to avoid a name clash with the valign attribute.\n    -->\n    <!ENTITY % frame.attrib\"frame        ( void\n                   | above\n                   | below\n                   | hsides\n                   | lhs\n                   | rhs\n                   | vsides\n                   | box\n                   | border )               #IMPLIED\">\n\n    <!-- The rules attribute defines which rules to draw between cells:\n\n         If rules is absent then assume:\n\n           \"none\" if border is absent or border=\"0\" otherwise \"all\"\n    -->\n    <!ENTITY % rules.attrib\"rules        ( none\n                   | groups\n                   | rows\n                   | cols\n                   | all )                  #IMPLIED\">\n\n    <!-- horizontal alignment attributes for cell contents\n    -->\n    <!ENTITY % CellHAlign.attrib\"align        ( left\n                   | center\n                   | right\n                   | justify\n                   | char )                 #IMPLIED\n      char         %Character.datatype;     #IMPLIED\n      charoff      %Length.datatype;        #IMPLIED\">\n\n    <!-- vertical alignment attribute for cell contents\n    -->\n    <!ENTITY % CellVAlign.attrib\"valign       ( top\n                   | middle\n                   | bottom\n                   | baseline )             #IMPLIED\">\n\n    <!-- scope is simpler than axes attribute for common tables\n    -->\n    <!ENTITY % scope.attrib\"scope        ( row\n                   | col\n                   | rowgroup\n                   | colgroup )             #IMPLIED\">\n\n    <!-- table: Table Element .............................. -->\n\n    <!ENTITY % table.element  \"INCLUDE\" ><![%table.element;[<!ENTITY % table.content\"( %caption.qname;?, ( %col.qname;* | %colgroup.qname;* ),\n      (( %thead.qname;?, %tfoot.qname;?, %tbody.qname;+ ) | ( %tr.qname;+ )))\"><!ELEMENT %table.qname;  %table.content; ><!-- end of table.element -->]]>\n\n    <!ENTITY % table.attlist  \"INCLUDE\" ><![%table.attlist;[<!ATTLIST %table.qname;%Common.attrib;summary      %Text.datatype;          #IMPLIEDwidth        %Length.datatype;        #IMPLIEDborder       %Pixels.datatype;        #IMPLIED%frame.attrib;%rules.attrib;cellspacing  %Length.datatype;        #IMPLIEDcellpadding  %Length.datatype;        #IMPLIED><!-- end of table.attlist -->]]>\n\n    <!-- caption: Table Caption ............................ -->\n\n    <!ENTITY % caption.element  \"INCLUDE\" ><![%caption.element;[<!ENTITY % caption.content\"( #PCDATA | %Inline.mix; )*\"><!ELEMENT %caption.qname;  %caption.content; ><!-- end of caption.element -->]]>\n\n    <!ENTITY % caption.attlist  \"INCLUDE\" ><![%caption.attlist;[<!ATTLIST %caption.qname;%Common.attrib;><!-- end of caption.attlist -->]]>\n\n    <!-- thead: Table Header ............................... -->\n\n    <!-- Use thead to duplicate headers when breaking table\n         across page boundaries, or for static headers when\n         tbody sections are rendered in scrolling panel.\n    -->\n\n    <!ENTITY % thead.element  \"INCLUDE\" ><![%thead.element;[<!ENTITY % thead.content  \"( %tr.qname; )+\" ><!ELEMENT %thead.qname;  %thead.content; ><!-- end of thead.element -->]]>\n\n    <!ENTITY % thead.attlist  \"INCLUDE\" ><![%thead.attlist;[<!ATTLIST %thead.qname;%Common.attrib;%CellHAlign.attrib;%CellVAlign.attrib;><!-- end of thead.attlist -->]]>\n\n    <!-- tfoot: Table Footer ............................... -->\n\n    <!-- Use tfoot to duplicate footers when breaking table\n         across page boundaries, or for static footers when\n         tbody sections are rendered in scrolling panel.\n    -->\n\n    <!ENTITY % tfoot.element  \"INCLUDE\" ><![%tfoot.element;[<!ENTITY % tfoot.content  \"( %tr.qname; )+\" ><!ELEMENT %tfoot.qname;  %tfoot.content; ><!-- end of tfoot.element -->]]>\n\n    <!ENTITY % tfoot.attlist  \"INCLUDE\" ><![%tfoot.attlist;[<!ATTLIST %tfoot.qname;%Common.attrib;%CellHAlign.attrib;%CellVAlign.attrib;><!-- end of tfoot.attlist -->]]>\n\n    <!-- tbody: Table Body ................................. -->\n\n    <!-- Use multiple tbody sections when rules are needed\n         between groups of table rows.\n    -->\n\n    <!ENTITY % tbody.element  \"INCLUDE\" ><![%tbody.element;[<!ENTITY % tbody.content  \"( %tr.qname; )+\" ><!ELEMENT %tbody.qname;  %tbody.content; ><!-- end of tbody.element -->]]>\n\n    <!ENTITY % tbody.attlist  \"INCLUDE\" ><![%tbody.attlist;[<!ATTLIST %tbody.qname;%Common.attrib;%CellHAlign.attrib;%CellVAlign.attrib;><!-- end of tbody.attlist -->]]>\n\n    <!-- colgroup: Table Column Group ...................... -->\n\n    <!-- colgroup groups a set of col elements. It allows you\n         to group several semantically-related columns together.\n    -->\n\n    <!ENTITY % colgroup.element  \"INCLUDE\" ><![%colgroup.element;[<!ENTITY % colgroup.content  \"( %col.qname; )*\" ><!ELEMENT %colgroup.qname;  %colgroup.content; ><!-- end of colgroup.element -->]]>\n\n    <!ENTITY % colgroup.attlist  \"INCLUDE\" ><![%colgroup.attlist;[<!ATTLIST %colgroup.qname;%Common.attrib;span         %Number.datatype;        '1'width        %MultiLength.datatype;   #IMPLIED%CellHAlign.attrib;%CellVAlign.attrib;><!-- end of colgroup.attlist -->]]>\n\n    <!-- col: Table Column ................................. -->\n\n    <!-- col elements define the alignment properties for\n         cells in one or more columns.\n\n         The width attribute specifies the width of the\n         columns, e.g.\n\n           width=\"64\"        width in screen pixels\n           width=\"0.5*\"      relative width of 0.5\n\n         The span attribute causes the attributes of one\n         col element to apply to more than one column.\n    -->\n\n    <!ENTITY % col.element  \"INCLUDE\" ><![%col.element;[<!ENTITY % col.content  \"EMPTY\" ><!ELEMENT %col.qname;  %col.content; ><!-- end of col.element -->]]>\n\n    <!ENTITY % col.attlist  \"INCLUDE\" ><![%col.attlist;[<!ATTLIST %col.qname;%Common.attrib;span         %Number.datatype;        '1'width        %MultiLength.datatype;   #IMPLIED%CellHAlign.attrib;%CellVAlign.attrib;><!-- end of col.attlist -->]]>\n\n    <!-- tr: Table Row ..................................... -->\n\n    <!ENTITY % tr.element  \"INCLUDE\" ><![%tr.element;[<!ENTITY % tr.content  \"( %th.qname; | %td.qname; )+\" ><!ELEMENT %tr.qname;  %tr.content; ><!-- end of tr.element -->]]>\n\n    <!ENTITY % tr.attlist  \"INCLUDE\" ><![%tr.attlist;[<!ATTLIST %tr.qname;%Common.attrib;%CellHAlign.attrib;%CellVAlign.attrib;><!-- end of tr.attlist -->]]>\n\n    <!-- th: Table Header Cell ............................. -->\n\n    <!-- th is for header cells, td for data,\n         but for cells acting as both use td\n    -->\n\n    <!ENTITY % th.element  \"INCLUDE\" ><![%th.element;[<!ENTITY % th.content\"( #PCDATA | %Flow.mix; )*\"><!ELEMENT %th.qname;  %th.content; ><!-- end of th.element -->]]>\n\n    <!ENTITY % th.attlist  \"INCLUDE\" ><![%th.attlist;[<!ATTLIST %th.qname;%Common.attrib;abbr         %Text.datatype;          #IMPLIEDaxis         CDATA                    #IMPLIEDheaders      IDREFS                   #IMPLIED%scope.attrib;rowspan      %Number.datatype;        '1'colspan      %Number.datatype;        '1'%CellHAlign.attrib;%CellVAlign.attrib;><!-- end of th.attlist -->]]>\n\n    <!-- td: Table Data Cell ............................... -->\n\n    <!ENTITY % td.element  \"INCLUDE\" ><![%td.element;[<!ENTITY % td.content\"( #PCDATA | %Flow.mix; )*\"><!ELEMENT %td.qname;  %td.content; ><!-- end of td.element -->]]>\n\n    <!ENTITY % td.attlist  \"INCLUDE\" ><![%td.attlist;[<!ATTLIST %td.qname;%Common.attrib;abbr         %Text.datatype;          #IMPLIEDaxis         CDATA                    #IMPLIEDheaders      IDREFS                   #IMPLIED%scope.attrib;rowspan      %Number.datatype;        '1'colspan      %Number.datatype;        '1'%CellHAlign.attrib;%CellVAlign.attrib;><!-- end of td.attlist -->]]>\n\n    <!-- end of xhtml-table-1.mod -->\n"
  },
  {
    "path": "modules/book/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml-text-1.mod",
    "content": "<!-- ...................................................................... -->\n    <!-- XHTML Text Module  ................................................... -->\n    <!-- file: xhtml-text-1.mod\n\n         This is XHTML, a reformulation of HTML as a modular XML application.\n         Copyright 1998-2005 W3C (MIT, ERCIM, Keio), All Rights Reserved.\n         Revision: $Id: xhtml-text-1.mod,v 1.1 2010/07/29 13:42:48 bertails Exp $ SMI\n\n         This DTD module is identified by the PUBLIC and SYSTEM identifiers:\n\n           PUBLIC \"-//W3C//ELEMENTS XHTML Text 1.0//EN\"\n           SYSTEM \"http://www.w3.org/MarkUp/DTD/xhtml-text-1.mod\"\n\n         Revisions:\n         (none)\n         ....................................................................... -->\n\n    <!-- Textual Content\n\n         The Text module includes declarations for all core\n         text container elements and their attributes.\n    -->\n\n    <!ENTITY % xhtml-inlstruct.module \"INCLUDE\" ><![%xhtml-inlstruct.module;[<!ENTITY % xhtml-inlstruct.modPUBLIC \"-//W3C//ELEMENTS XHTML Inline Structural 1.0//EN\"\"xhtml-inlstruct-1.mod\" >%xhtml-inlstruct.mod;]]>\n\n    <!ENTITY % xhtml-inlphras.module \"INCLUDE\" ><![%xhtml-inlphras.module;[<!ENTITY % xhtml-inlphras.modPUBLIC \"-//W3C//ELEMENTS XHTML Inline Phrasal 1.0//EN\"\"xhtml-inlphras-1.mod\" >%xhtml-inlphras.mod;]]>\n\n    <!ENTITY % xhtml-blkstruct.module \"INCLUDE\" ><![%xhtml-blkstruct.module;[<!ENTITY % xhtml-blkstruct.modPUBLIC \"-//W3C//ELEMENTS XHTML Block Structural 1.0//EN\"\"xhtml-blkstruct-1.mod\" >%xhtml-blkstruct.mod;]]>\n\n    <!ENTITY % xhtml-blkphras.module \"INCLUDE\" ><![%xhtml-blkphras.module;[<!ENTITY % xhtml-blkphras.modPUBLIC \"-//W3C//ELEMENTS XHTML Block Phrasal 1.0//EN\"\"xhtml-blkphras-1.mod\" >%xhtml-blkphras.mod;]]>\n\n    <!-- end of xhtml-text-1.mod -->\n"
  },
  {
    "path": "modules/book/src/main/resources/dtd/www.w3.org/TR/xhtml-modularization/DTD/xhtml11-model-1.mod",
    "content": "<!-- ....................................................................... -->\n    <!-- XHTML 1.1 Document Model Module  ...................................... -->\n    <!-- file: xhtml11-model-1.mod\n\n         This is XHTML 1.1, a reformulation of HTML as a modular XML application.\n         Copyright 1998-2001 W3C (MIT, INRIA, Keio), All Rights Reserved.\n         Revision: $Id: xhtml11-model-1.mod,v 1.13 2001/05/29 16:37:01 ahby Exp $ SMI\n\n         This DTD module is identified by the PUBLIC and SYSTEM identifiers:\n\n           PUBLIC \"-//W3C//ENTITIES XHTML 1.1 Document Model 1.0//EN\"\n           SYSTEM \"http://www.w3.org/TR/xhtml11/DTD/xhtml11-model-1.mod\"\n\n         Revisions:\n         (none)\n         ....................................................................... -->\n\n    <!-- XHTML 1.1 Document Model\n\n         This module describes the groupings of elements that make up\n         common content models for XHTML elements.\n\n         XHTML has three basic content models:\n\n             %Inline.mix;  character-level elements\n             %Block.mix;   block-like elements, eg., paragraphs and lists\n             %Flow.mix;    any block or inline elements\n\n         Any parameter entities declared in this module may be used\n         to create element content models, but the above three are\n         considered 'global' (insofar as that term applies here).\n\n         The reserved word '#PCDATA' (indicating a text string) is now\n         included explicitly with each element declaration that is\n         declared as mixed content, as XML requires that this token\n         occur first in a content model specification.\n    -->\n    <!-- Extending the Model\n\n         While in some cases this module may need to be rewritten to\n         accommodate changes to the document model, minor extensions\n         may be accomplished by redeclaring any of the three *.extra;\n         parameter entities to contain extension element types as follows:\n\n             %Misc.extra;    whose parent may be any block or\n                             inline element.\n\n             %Inline.extra;  whose parent may be any inline element.\n\n             %Block.extra;   whose parent may be any block element.\n\n         If used, these parameter entities must be an OR-separated\n         list beginning with an OR separator (\"|\"), eg., \"| a | b | c\"\n\n         All block and inline *.class parameter entities not part\n         of the *struct.class classes begin with \"| \" to allow for\n         exclusion from mixes.\n    -->\n\n    <!-- ..............  Optional Elements in head  .................. -->\n\n    <!ENTITY % HeadOpts.mix\"( %script.qname; | %style.qname; | %meta.qname;\n      | %link.qname; | %object.qname; )*\">\n\n    <!-- .................  Miscellaneous Elements  .................. -->\n\n    <!-- ins and del are used to denote editing changes\n    -->\n    <!ENTITY % Edit.class \"| %ins.qname; | %del.qname;\" >\n\n    <!-- script and noscript are used to contain scripts\n         and alternative content\n    -->\n    <!ENTITY % Script.class \"| %script.qname; | %noscript.qname;\" >\n\n    <!ENTITY % Misc.extra \"\" >\n\n    <!-- These elements are neither block nor inline, and can\n         essentially be used anywhere in the document body.\n    -->\n    <!ENTITY % Misc.class\"%Edit.class;\n      %Script.class;\n      %Misc.extra;\">\n\n    <!-- ....................  Inline Elements  ...................... -->\n\n    <!ENTITY % InlStruct.class \"%br.qname; | %span.qname;\" >\n\n    <!ENTITY % InlPhras.class\"| %em.qname; | %strong.qname; | %dfn.qname; | %code.qname;\n      | %samp.qname; | %kbd.qname; | %var.qname; | %cite.qname;\n      | %abbr.qname; | %acronym.qname; | %q.qname;\" >\n\n    <!ENTITY % InlPres.class\"| %tt.qname; | %i.qname; | %b.qname; | %big.qname;\n      | %small.qname; | %sub.qname; | %sup.qname;\" >\n\n    <!ENTITY % I18n.class \"| %bdo.qname;\" >\n\n    <!ENTITY % Anchor.class \"| %a.qname;\" >\n\n    <!ENTITY % InlSpecial.class\"| %img.qname; | %map.qname;\n      | %object.qname;\" >\n\n    <!ENTITY % InlForm.class\"| %input.qname; | %select.qname; | %textarea.qname;\n      | %label.qname; | %button.qname;\" >\n\n    <!ENTITY % Inline.extra \"\" >\n\n    <!ENTITY % Ruby.class \"| %ruby.qname;\" >\n\n    <!-- %Inline.class; includes all inline elements,\n         used as a component in mixes\n    -->\n    <!ENTITY % Inline.class\"%InlStruct.class;\n      %InlPhras.class;\n      %InlPres.class;\n      %I18n.class;\n      %Anchor.class;\n      %InlSpecial.class;\n      %InlForm.class;\n      %Ruby.class;\n      %Inline.extra;\">\n\n    <!-- %InlNoRuby.class; includes all inline elements\n         except ruby, used as a component in mixes\n    -->\n    <!ENTITY % InlNoRuby.class\"%InlStruct.class;\n      %InlPhras.class;\n      %InlPres.class;\n      %I18n.class;\n      %Anchor.class;\n      %InlSpecial.class;\n      %InlForm.class;\n      %Inline.extra;\">\n\n    <!-- %NoRuby.content; includes all inlines except ruby\n    -->\n    <!ENTITY % NoRuby.content\"( #PCDATA\n      | %InlNoRuby.class;\n      %Misc.class; )*\">\n\n    <!-- %InlNoAnchor.class; includes all non-anchor inlines,\n         used as a component in mixes\n    -->\n    <!ENTITY % InlNoAnchor.class\"%InlStruct.class;\n      %InlPhras.class;\n      %InlPres.class;\n      %I18n.class;\n      %InlSpecial.class;\n      %InlForm.class;\n      %Ruby.class;\n      %Inline.extra;\">\n\n    <!-- %InlNoAnchor.mix; includes all non-anchor inlines\n    -->\n    <!ENTITY % InlNoAnchor.mix\"%InlNoAnchor.class;\n      %Misc.class;\">\n\n    <!-- %Inline.mix; includes all inline elements, including %Misc.class;\n    -->\n    <!ENTITY % Inline.mix\"%Inline.class;\n      %Misc.class;\">\n\n    <!-- .....................  Block Elements  ...................... -->\n\n    <!-- In the HTML 4.0 DTD, heading and list elements were included\n         in the %block; parameter entity. The %Heading.class; and\n         %List.class; parameter entities must now be included explicitly\n         on element declarations where desired.\n    -->\n\n    <!ENTITY % Heading.class\"%h1.qname; | %h2.qname; | %h3.qname;\n      | %h4.qname; | %h5.qname; | %h6.qname;\" >\n\n    <!ENTITY % List.class \"%ul.qname; | %ol.qname; | %dl.qname;\" >\n\n    <!ENTITY % Table.class \"| %table.qname;\" >\n\n    <!ENTITY % Form.class  \"| %form.qname;\" >\n\n    <!ENTITY % Fieldset.class  \"| %fieldset.qname;\" >\n\n    <!ENTITY % BlkStruct.class \"%p.qname; | %div.qname;\" >\n\n    <!ENTITY % BlkPhras.class\"| %pre.qname; | %blockquote.qname; | %address.qname;\" >\n\n    <!ENTITY % BlkPres.class \"| %hr.qname;\" >\n\n    <!ENTITY % BlkSpecial.class\"%Table.class;\n      %Form.class;\n      %Fieldset.class;\">\n\n    <!ENTITY % Block.extra \"\" >\n\n    <!-- %Block.class; includes all block elements,\n         used as an component in mixes\n    -->\n    <!ENTITY % Block.class\"%BlkStruct.class;\n      %BlkPhras.class;\n      %BlkPres.class;\n      %BlkSpecial.class;\n      %Block.extra;\">\n\n    <!-- %Block.mix; includes all block elements plus %Misc.class;\n    -->\n    <!ENTITY % Block.mix\"%Heading.class;\n      | %List.class;\n      | %Block.class;\n      %Misc.class;\">\n\n    <!-- ................  All Content Elements  .................. -->\n\n    <!-- %Flow.mix; includes all text content, block and inline\n    -->\n    <!ENTITY % Flow.mix\"%Heading.class;\n      | %List.class;\n      | %Block.class;\n      | %Inline.class;\n      %Misc.class;\">\n\n    <!-- end of xhtml11-model-1.mod -->\n\n\n"
  },
  {
    "path": "modules/book/src/main/resources/dtd/www.w3.org/TR/xhtml1/DTD/xhtml-lat1.ent",
    "content": "<!-- Portions (C) International Organization for Standardization 1986\n     Permission to copy in any form is granted for use with\n     conforming SGML systems and applications as defined in\n     ISO 8879, provided this notice is included in all copies.\n-->\n    <!-- Character entity set. Typical invocation:\n        <!ENTITY % HTMLlat1 PUBLIC\n           \"-//W3C//ENTITIES Latin 1 for XHTML//EN\"\n           \"http://www.w3.org/TR/xhtml1/DTD/xhtml-lat1.ent\">\n        %HTMLlat1;\n    -->\n\n    <!ENTITY nbsp   \"&#160;\"> <!-- no-break space = non-breaking space,\n                                  U+00A0 ISOnum -->\n    <!ENTITY iexcl  \"&#161;\"> <!-- inverted exclamation mark, U+00A1 ISOnum -->\n    <!ENTITY cent   \"&#162;\"> <!-- cent sign, U+00A2 ISOnum -->\n    <!ENTITY pound  \"&#163;\"> <!-- pound sign, U+00A3 ISOnum -->\n    <!ENTITY curren \"&#164;\"> <!-- currency sign, U+00A4 ISOnum -->\n    <!ENTITY yen    \"&#165;\"> <!-- yen sign = yuan sign, U+00A5 ISOnum -->\n    <!ENTITY brvbar \"&#166;\"> <!-- broken bar = broken vertical bar,\n                                  U+00A6 ISOnum -->\n    <!ENTITY sect   \"&#167;\"> <!-- section sign, U+00A7 ISOnum -->\n    <!ENTITY uml    \"&#168;\"> <!-- diaeresis = spacing diaeresis,\n                                  U+00A8 ISOdia -->\n    <!ENTITY copy   \"&#169;\"> <!-- copyright sign, U+00A9 ISOnum -->\n    <!ENTITY ordf   \"&#170;\"> <!-- feminine ordinal indicator, U+00AA ISOnum -->\n    <!ENTITY laquo  \"&#171;\"> <!-- left-pointing double angle quotation mark\n                                  = left pointing guillemet, U+00AB ISOnum -->\n    <!ENTITY not    \"&#172;\"> <!-- not sign = angled dash,\n                                  U+00AC ISOnum -->\n    <!ENTITY shy    \"&#173;\"> <!-- soft hyphen = discretionary hyphen,\n                                  U+00AD ISOnum -->\n    <!ENTITY reg    \"&#174;\"> <!-- registered sign = registered trade mark sign,\n                                  U+00AE ISOnum -->\n    <!ENTITY macr   \"&#175;\"> <!-- macron = spacing macron = overline\n                                  = APL overbar, U+00AF ISOdia -->\n    <!ENTITY deg    \"&#176;\"> <!-- degree sign, U+00B0 ISOnum -->\n    <!ENTITY plusmn \"&#177;\"> <!-- plus-minus sign = plus-or-minus sign,\n                                  U+00B1 ISOnum -->\n    <!ENTITY sup2   \"&#178;\"> <!-- superscript two = superscript digit two\n                                  = squared, U+00B2 ISOnum -->\n    <!ENTITY sup3   \"&#179;\"> <!-- superscript three = superscript digit three\n                                  = cubed, U+00B3 ISOnum -->\n    <!ENTITY acute  \"&#180;\"> <!-- acute accent = spacing acute,\n                                  U+00B4 ISOdia -->\n    <!ENTITY micro  \"&#181;\"> <!-- micro sign, U+00B5 ISOnum -->\n    <!ENTITY para   \"&#182;\"> <!-- pilcrow sign = paragraph sign,\n                                  U+00B6 ISOnum -->\n    <!ENTITY middot \"&#183;\"> <!-- middle dot = Georgian comma\n                                  = Greek middle dot, U+00B7 ISOnum -->\n    <!ENTITY cedil  \"&#184;\"> <!-- cedilla = spacing cedilla, U+00B8 ISOdia -->\n    <!ENTITY sup1   \"&#185;\"> <!-- superscript one = superscript digit one,\n                                  U+00B9 ISOnum -->\n    <!ENTITY ordm   \"&#186;\"> <!-- masculine ordinal indicator,\n                                  U+00BA ISOnum -->\n    <!ENTITY raquo  \"&#187;\"> <!-- right-pointing double angle quotation mark\n                                  = right pointing guillemet, U+00BB ISOnum -->\n    <!ENTITY frac14 \"&#188;\"> <!-- vulgar fraction one quarter\n                                  = fraction one quarter, U+00BC ISOnum -->\n    <!ENTITY frac12 \"&#189;\"> <!-- vulgar fraction one half\n                                  = fraction one half, U+00BD ISOnum -->\n    <!ENTITY frac34 \"&#190;\"> <!-- vulgar fraction three quarters\n                                  = fraction three quarters, U+00BE ISOnum -->\n    <!ENTITY iquest \"&#191;\"> <!-- inverted question mark\n                                  = turned question mark, U+00BF ISOnum -->\n    <!ENTITY Agrave \"&#192;\"> <!-- latin capital letter A with grave\n                                  = latin capital letter A grave,\n                                  U+00C0 ISOlat1 -->\n    <!ENTITY Aacute \"&#193;\"> <!-- latin capital letter A with acute,\n                                  U+00C1 ISOlat1 -->\n    <!ENTITY Acirc  \"&#194;\"> <!-- latin capital letter A with circumflex,\n                                  U+00C2 ISOlat1 -->\n    <!ENTITY Atilde \"&#195;\"> <!-- latin capital letter A with tilde,\n                                  U+00C3 ISOlat1 -->\n    <!ENTITY Auml   \"&#196;\"> <!-- latin capital letter A with diaeresis,\n                                  U+00C4 ISOlat1 -->\n    <!ENTITY Aring  \"&#197;\"> <!-- latin capital letter A with ring above\n                                  = latin capital letter A ring,\n                                  U+00C5 ISOlat1 -->\n    <!ENTITY AElig  \"&#198;\"> <!-- latin capital letter AE\n                                  = latin capital ligature AE,\n                                  U+00C6 ISOlat1 -->\n    <!ENTITY Ccedil \"&#199;\"> <!-- latin capital letter C with cedilla,\n                                  U+00C7 ISOlat1 -->\n    <!ENTITY Egrave \"&#200;\"> <!-- latin capital letter E with grave,\n                                  U+00C8 ISOlat1 -->\n    <!ENTITY Eacute \"&#201;\"> <!-- latin capital letter E with acute,\n                                  U+00C9 ISOlat1 -->\n    <!ENTITY Ecirc  \"&#202;\"> <!-- latin capital letter E with circumflex,\n                                  U+00CA ISOlat1 -->\n    <!ENTITY Euml   \"&#203;\"> <!-- latin capital letter E with diaeresis,\n                                  U+00CB ISOlat1 -->\n    <!ENTITY Igrave \"&#204;\"> <!-- latin capital letter I with grave,\n                                  U+00CC ISOlat1 -->\n    <!ENTITY Iacute \"&#205;\"> <!-- latin capital letter I with acute,\n                                  U+00CD ISOlat1 -->\n    <!ENTITY Icirc  \"&#206;\"> <!-- latin capital letter I with circumflex,\n                                  U+00CE ISOlat1 -->\n    <!ENTITY Iuml   \"&#207;\"> <!-- latin capital letter I with diaeresis,\n                                  U+00CF ISOlat1 -->\n    <!ENTITY ETH    \"&#208;\"> <!-- latin capital letter ETH, U+00D0 ISOlat1 -->\n    <!ENTITY Ntilde \"&#209;\"> <!-- latin capital letter N with tilde,\n                                  U+00D1 ISOlat1 -->\n    <!ENTITY Ograve \"&#210;\"> <!-- latin capital letter O with grave,\n                                  U+00D2 ISOlat1 -->\n    <!ENTITY Oacute \"&#211;\"> <!-- latin capital letter O with acute,\n                                  U+00D3 ISOlat1 -->\n    <!ENTITY Ocirc  \"&#212;\"> <!-- latin capital letter O with circumflex,\n                                  U+00D4 ISOlat1 -->\n    <!ENTITY Otilde \"&#213;\"> <!-- latin capital letter O with tilde,\n                                  U+00D5 ISOlat1 -->\n    <!ENTITY Ouml   \"&#214;\"> <!-- latin capital letter O with diaeresis,\n                                  U+00D6 ISOlat1 -->\n    <!ENTITY times  \"&#215;\"> <!-- multiplication sign, U+00D7 ISOnum -->\n    <!ENTITY Oslash \"&#216;\"> <!-- latin capital letter O with stroke\n                                  = latin capital letter O slash,\n                                  U+00D8 ISOlat1 -->\n    <!ENTITY Ugrave \"&#217;\"> <!-- latin capital letter U with grave,\n                                  U+00D9 ISOlat1 -->\n    <!ENTITY Uacute \"&#218;\"> <!-- latin capital letter U with acute,\n                                  U+00DA ISOlat1 -->\n    <!ENTITY Ucirc  \"&#219;\"> <!-- latin capital letter U with circumflex,\n                                  U+00DB ISOlat1 -->\n    <!ENTITY Uuml   \"&#220;\"> <!-- latin capital letter U with diaeresis,\n                                  U+00DC ISOlat1 -->\n    <!ENTITY Yacute \"&#221;\"> <!-- latin capital letter Y with acute,\n                                  U+00DD ISOlat1 -->\n    <!ENTITY THORN  \"&#222;\"> <!-- latin capital letter THORN,\n                                  U+00DE ISOlat1 -->\n    <!ENTITY szlig  \"&#223;\"> <!-- latin small letter sharp s = ess-zed,\n                                  U+00DF ISOlat1 -->\n    <!ENTITY agrave \"&#224;\"> <!-- latin small letter a with grave\n                                  = latin small letter a grave,\n                                  U+00E0 ISOlat1 -->\n    <!ENTITY aacute \"&#225;\"> <!-- latin small letter a with acute,\n                                  U+00E1 ISOlat1 -->\n    <!ENTITY acirc  \"&#226;\"> <!-- latin small letter a with circumflex,\n                                  U+00E2 ISOlat1 -->\n    <!ENTITY atilde \"&#227;\"> <!-- latin small letter a with tilde,\n                                  U+00E3 ISOlat1 -->\n    <!ENTITY auml   \"&#228;\"> <!-- latin small letter a with diaeresis,\n                                  U+00E4 ISOlat1 -->\n    <!ENTITY aring  \"&#229;\"> <!-- latin small letter a with ring above\n                                  = latin small letter a ring,\n                                  U+00E5 ISOlat1 -->\n    <!ENTITY aelig  \"&#230;\"> <!-- latin small letter ae\n                                  = latin small ligature ae, U+00E6 ISOlat1 -->\n    <!ENTITY ccedil \"&#231;\"> <!-- latin small letter c with cedilla,\n                                  U+00E7 ISOlat1 -->\n    <!ENTITY egrave \"&#232;\"> <!-- latin small letter e with grave,\n                                  U+00E8 ISOlat1 -->\n    <!ENTITY eacute \"&#233;\"> <!-- latin small letter e with acute,\n                                  U+00E9 ISOlat1 -->\n    <!ENTITY ecirc  \"&#234;\"> <!-- latin small letter e with circumflex,\n                                  U+00EA ISOlat1 -->\n    <!ENTITY euml   \"&#235;\"> <!-- latin small letter e with diaeresis,\n                                  U+00EB ISOlat1 -->\n    <!ENTITY igrave \"&#236;\"> <!-- latin small letter i with grave,\n                                  U+00EC ISOlat1 -->\n    <!ENTITY iacute \"&#237;\"> <!-- latin small letter i with acute,\n                                  U+00ED ISOlat1 -->\n    <!ENTITY icirc  \"&#238;\"> <!-- latin small letter i with circumflex,\n                                  U+00EE ISOlat1 -->\n    <!ENTITY iuml   \"&#239;\"> <!-- latin small letter i with diaeresis,\n                                  U+00EF ISOlat1 -->\n    <!ENTITY eth    \"&#240;\"> <!-- latin small letter eth, U+00F0 ISOlat1 -->\n    <!ENTITY ntilde \"&#241;\"> <!-- latin small letter n with tilde,\n                                  U+00F1 ISOlat1 -->\n    <!ENTITY ograve \"&#242;\"> <!-- latin small letter o with grave,\n                                  U+00F2 ISOlat1 -->\n    <!ENTITY oacute \"&#243;\"> <!-- latin small letter o with acute,\n                                  U+00F3 ISOlat1 -->\n    <!ENTITY ocirc  \"&#244;\"> <!-- latin small letter o with circumflex,\n                                  U+00F4 ISOlat1 -->\n    <!ENTITY otilde \"&#245;\"> <!-- latin small letter o with tilde,\n                                  U+00F5 ISOlat1 -->\n    <!ENTITY ouml   \"&#246;\"> <!-- latin small letter o with diaeresis,\n                                  U+00F6 ISOlat1 -->\n    <!ENTITY divide \"&#247;\"> <!-- division sign, U+00F7 ISOnum -->\n    <!ENTITY oslash \"&#248;\"> <!-- latin small letter o with stroke,\n                                  = latin small letter o slash,\n                                  U+00F8 ISOlat1 -->\n    <!ENTITY ugrave \"&#249;\"> <!-- latin small letter u with grave,\n                                  U+00F9 ISOlat1 -->\n    <!ENTITY uacute \"&#250;\"> <!-- latin small letter u with acute,\n                                  U+00FA ISOlat1 -->\n    <!ENTITY ucirc  \"&#251;\"> <!-- latin small letter u with circumflex,\n                                  U+00FB ISOlat1 -->\n    <!ENTITY uuml   \"&#252;\"> <!-- latin small letter u with diaeresis,\n                                  U+00FC ISOlat1 -->\n    <!ENTITY yacute \"&#253;\"> <!-- latin small letter y with acute,\n                                  U+00FD ISOlat1 -->\n    <!ENTITY thorn  \"&#254;\"> <!-- latin small letter thorn,\n                                  U+00FE ISOlat1 -->\n    <!ENTITY yuml   \"&#255;\"> <!-- latin small letter y with diaeresis,\n                                  U+00FF ISOlat1 -->\n"
  },
  {
    "path": "modules/book/src/main/resources/dtd/www.w3.org/TR/xhtml1/DTD/xhtml-special.ent",
    "content": "<!-- Special characters for XHTML -->\n\n    <!-- Character entity set. Typical invocation:\n         <!ENTITY % HTMLspecial PUBLIC\n            \"-//W3C//ENTITIES Special for XHTML//EN\"\n            \"http://www.w3.org/TR/xhtml1/DTD/xhtml-special.ent\">\n         %HTMLspecial;\n    -->\n\n    <!-- Portions (C) International Organization for Standardization 1986:\n         Permission to copy in any form is granted for use with\n         conforming SGML systems and applications as defined in\n         ISO 8879, provided this notice is included in all copies.\n    -->\n\n    <!-- Relevant ISO entity set is given unless names are newly introduced.\n         New names (i.e., not in ISO 8879 list) do not clash with any\n         existing ISO 8879 entity names. ISO 10646 character numbers\n         are given for each character, in hex. values are decimal\n         conversions of the ISO 10646 values and refer to the document\n         character set. Names are Unicode names.\n    -->\n\n    <!-- C0 Controls and Basic Latin -->\n    <!ENTITY quot    \"&#34;\"> <!--  quotation mark, U+0022 ISOnum -->\n    <!ENTITY amp     \"&#38;#38;\"> <!--  ampersand, U+0026 ISOnum -->\n    <!ENTITY lt      \"&#38;#60;\"> <!--  less-than sign, U+003C ISOnum -->\n    <!ENTITY gt      \"&#62;\"> <!--  greater-than sign, U+003E ISOnum -->\n    <!ENTITY apos     \"&#39;\"> <!--  apostrophe = APL quote, U+0027 ISOnum -->\n\n    <!-- Latin Extended-A -->\n    <!ENTITY OElig   \"&#338;\"> <!--  latin capital ligature OE,\n                                    U+0152 ISOlat2 -->\n    <!ENTITY oelig   \"&#339;\"> <!--  latin small ligature oe, U+0153 ISOlat2 -->\n    <!-- ligature is a misnomer, this is a separate character in some languages -->\n    <!ENTITY Scaron  \"&#352;\"> <!--  latin capital letter S with caron,\n                                    U+0160 ISOlat2 -->\n    <!ENTITY scaron  \"&#353;\"> <!--  latin small letter s with caron,\n                                    U+0161 ISOlat2 -->\n    <!ENTITY Yuml    \"&#376;\"> <!--  latin capital letter Y with diaeresis,\n                                    U+0178 ISOlat2 -->\n\n    <!-- Spacing Modifier Letters -->\n    <!ENTITY circ    \"&#710;\"> <!--  modifier letter circumflex accent,\n                                    U+02C6 ISOpub -->\n    <!ENTITY tilde   \"&#732;\"> <!--  small tilde, U+02DC ISOdia -->\n\n    <!-- General Punctuation -->\n    <!ENTITY ensp    \"&#8194;\"> <!-- en space, U+2002 ISOpub -->\n    <!ENTITY emsp    \"&#8195;\"> <!-- em space, U+2003 ISOpub -->\n    <!ENTITY thinsp  \"&#8201;\"> <!-- thin space, U+2009 ISOpub -->\n    <!ENTITY zwnj    \"&#8204;\"> <!-- zero width non-joiner,\n                                    U+200C NEW RFC 2070 -->\n    <!ENTITY zwj     \"&#8205;\"> <!-- zero width joiner, U+200D NEW RFC 2070 -->\n    <!ENTITY lrm     \"&#8206;\"> <!-- left-to-right mark, U+200E NEW RFC 2070 -->\n    <!ENTITY rlm     \"&#8207;\"> <!-- right-to-left mark, U+200F NEW RFC 2070 -->\n    <!ENTITY ndash   \"&#8211;\"> <!-- en dash, U+2013 ISOpub -->\n    <!ENTITY mdash   \"&#8212;\"> <!-- em dash, U+2014 ISOpub -->\n    <!ENTITY lsquo   \"&#8216;\"> <!-- left single quotation mark,\n                                    U+2018 ISOnum -->\n    <!ENTITY rsquo   \"&#8217;\"> <!-- right single quotation mark,\n                                    U+2019 ISOnum -->\n    <!ENTITY sbquo   \"&#8218;\"> <!-- single low-9 quotation mark, U+201A NEW -->\n    <!ENTITY ldquo   \"&#8220;\"> <!-- left double quotation mark,\n                                    U+201C ISOnum -->\n    <!ENTITY rdquo   \"&#8221;\"> <!-- right double quotation mark,\n                                    U+201D ISOnum -->\n    <!ENTITY bdquo   \"&#8222;\"> <!-- double low-9 quotation mark, U+201E NEW -->\n    <!ENTITY dagger  \"&#8224;\"> <!-- dagger, U+2020 ISOpub -->\n    <!ENTITY Dagger  \"&#8225;\"> <!-- double dagger, U+2021 ISOpub -->\n    <!ENTITY permil  \"&#8240;\"> <!-- per mille sign, U+2030 ISOtech -->\n    <!ENTITY lsaquo  \"&#8249;\"> <!-- single left-pointing angle quotation mark,\n                                    U+2039 ISO proposed -->\n    <!-- lsaquo is proposed but not yet ISO standardized -->\n    <!ENTITY rsaquo  \"&#8250;\"> <!-- single right-pointing angle quotation mark,\n                                    U+203A ISO proposed -->\n    <!-- rsaquo is proposed but not yet ISO standardized -->\n\n    <!-- Currency Symbols -->\n    <!ENTITY euro   \"&#8364;\"> <!--  euro sign, U+20AC NEW -->\n"
  },
  {
    "path": "modules/book/src/main/resources/dtd/www.w3.org/TR/xhtml1/DTD/xhtml-symbol.ent",
    "content": "<!-- Mathematical, Greek and Symbolic characters for XHTML -->\n\n    <!-- Character entity set. Typical invocation:\n         <!ENTITY % HTMLsymbol PUBLIC\n            \"-//W3C//ENTITIES Symbols for XHTML//EN\"\n            \"http://www.w3.org/TR/xhtml1/DTD/xhtml-symbol.ent\">\n         %HTMLsymbol;\n    -->\n\n    <!-- Portions (C) International Organization for Standardization 1986:\n         Permission to copy in any form is granted for use with\n         conforming SGML systems and applications as defined in\n         ISO 8879, provided this notice is included in all copies.\n    -->\n\n    <!-- Relevant ISO entity set is given unless names are newly introduced.\n         New names (i.e., not in ISO 8879 list) do not clash with any\n         existing ISO 8879 entity names. ISO 10646 character numbers\n         are given for each character, in hex. values are decimal\n         conversions of the ISO 10646 values and refer to the document\n         character set. Names are Unicode names.\n    -->\n\n    <!-- Latin Extended-B -->\n    <!ENTITY fnof     \"&#402;\"> <!-- latin small letter f with hook = function\n                                    = florin, U+0192 ISOtech -->\n\n    <!-- Greek -->\n    <!ENTITY Alpha    \"&#913;\"> <!-- greek capital letter alpha, U+0391 -->\n    <!ENTITY Beta     \"&#914;\"> <!-- greek capital letter beta, U+0392 -->\n    <!ENTITY Gamma    \"&#915;\"> <!-- greek capital letter gamma,\n                                    U+0393 ISOgrk3 -->\n    <!ENTITY Delta    \"&#916;\"> <!-- greek capital letter delta,\n                                    U+0394 ISOgrk3 -->\n    <!ENTITY Epsilon  \"&#917;\"> <!-- greek capital letter epsilon, U+0395 -->\n    <!ENTITY Zeta     \"&#918;\"> <!-- greek capital letter zeta, U+0396 -->\n    <!ENTITY Eta      \"&#919;\"> <!-- greek capital letter eta, U+0397 -->\n    <!ENTITY Theta    \"&#920;\"> <!-- greek capital letter theta,\n                                    U+0398 ISOgrk3 -->\n    <!ENTITY Iota     \"&#921;\"> <!-- greek capital letter iota, U+0399 -->\n    <!ENTITY Kappa    \"&#922;\"> <!-- greek capital letter kappa, U+039A -->\n    <!ENTITY Lambda   \"&#923;\"> <!-- greek capital letter lamda,\n                                    U+039B ISOgrk3 -->\n    <!ENTITY Mu       \"&#924;\"> <!-- greek capital letter mu, U+039C -->\n    <!ENTITY Nu       \"&#925;\"> <!-- greek capital letter nu, U+039D -->\n    <!ENTITY Xi       \"&#926;\"> <!-- greek capital letter xi, U+039E ISOgrk3 -->\n    <!ENTITY Omicron  \"&#927;\"> <!-- greek capital letter omicron, U+039F -->\n    <!ENTITY Pi       \"&#928;\"> <!-- greek capital letter pi, U+03A0 ISOgrk3 -->\n    <!ENTITY Rho      \"&#929;\"> <!-- greek capital letter rho, U+03A1 -->\n    <!-- there is no Sigmaf, and no U+03A2 character either -->\n    <!ENTITY Sigma    \"&#931;\"> <!-- greek capital letter sigma,\n                                    U+03A3 ISOgrk3 -->\n    <!ENTITY Tau      \"&#932;\"> <!-- greek capital letter tau, U+03A4 -->\n    <!ENTITY Upsilon  \"&#933;\"> <!-- greek capital letter upsilon,\n                                    U+03A5 ISOgrk3 -->\n    <!ENTITY Phi      \"&#934;\"> <!-- greek capital letter phi,\n                                    U+03A6 ISOgrk3 -->\n    <!ENTITY Chi      \"&#935;\"> <!-- greek capital letter chi, U+03A7 -->\n    <!ENTITY Psi      \"&#936;\"> <!-- greek capital letter psi,\n                                    U+03A8 ISOgrk3 -->\n    <!ENTITY Omega    \"&#937;\"> <!-- greek capital letter omega,\n                                    U+03A9 ISOgrk3 -->\n\n    <!ENTITY alpha    \"&#945;\"> <!-- greek small letter alpha,\n                                    U+03B1 ISOgrk3 -->\n    <!ENTITY beta     \"&#946;\"> <!-- greek small letter beta, U+03B2 ISOgrk3 -->\n    <!ENTITY gamma    \"&#947;\"> <!-- greek small letter gamma,\n                                    U+03B3 ISOgrk3 -->\n    <!ENTITY delta    \"&#948;\"> <!-- greek small letter delta,\n                                    U+03B4 ISOgrk3 -->\n    <!ENTITY epsilon  \"&#949;\"> <!-- greek small letter epsilon,\n                                    U+03B5 ISOgrk3 -->\n    <!ENTITY zeta     \"&#950;\"> <!-- greek small letter zeta, U+03B6 ISOgrk3 -->\n    <!ENTITY eta      \"&#951;\"> <!-- greek small letter eta, U+03B7 ISOgrk3 -->\n    <!ENTITY theta    \"&#952;\"> <!-- greek small letter theta,\n                                    U+03B8 ISOgrk3 -->\n    <!ENTITY iota     \"&#953;\"> <!-- greek small letter iota, U+03B9 ISOgrk3 -->\n    <!ENTITY kappa    \"&#954;\"> <!-- greek small letter kappa,\n                                    U+03BA ISOgrk3 -->\n    <!ENTITY lambda   \"&#955;\"> <!-- greek small letter lamda,\n                                    U+03BB ISOgrk3 -->\n    <!ENTITY mu       \"&#956;\"> <!-- greek small letter mu, U+03BC ISOgrk3 -->\n    <!ENTITY nu       \"&#957;\"> <!-- greek small letter nu, U+03BD ISOgrk3 -->\n    <!ENTITY xi       \"&#958;\"> <!-- greek small letter xi, U+03BE ISOgrk3 -->\n    <!ENTITY omicron  \"&#959;\"> <!-- greek small letter omicron, U+03BF NEW -->\n    <!ENTITY pi       \"&#960;\"> <!-- greek small letter pi, U+03C0 ISOgrk3 -->\n    <!ENTITY rho      \"&#961;\"> <!-- greek small letter rho, U+03C1 ISOgrk3 -->\n    <!ENTITY sigmaf   \"&#962;\"> <!-- greek small letter final sigma,\n                                    U+03C2 ISOgrk3 -->\n    <!ENTITY sigma    \"&#963;\"> <!-- greek small letter sigma,\n                                    U+03C3 ISOgrk3 -->\n    <!ENTITY tau      \"&#964;\"> <!-- greek small letter tau, U+03C4 ISOgrk3 -->\n    <!ENTITY upsilon  \"&#965;\"> <!-- greek small letter upsilon,\n                                    U+03C5 ISOgrk3 -->\n    <!ENTITY phi      \"&#966;\"> <!-- greek small letter phi, U+03C6 ISOgrk3 -->\n    <!ENTITY chi      \"&#967;\"> <!-- greek small letter chi, U+03C7 ISOgrk3 -->\n    <!ENTITY psi      \"&#968;\"> <!-- greek small letter psi, U+03C8 ISOgrk3 -->\n    <!ENTITY omega    \"&#969;\"> <!-- greek small letter omega,\n                                    U+03C9 ISOgrk3 -->\n    <!ENTITY thetasym \"&#977;\"> <!-- greek theta symbol,\n                                    U+03D1 NEW -->\n    <!ENTITY upsih    \"&#978;\"> <!-- greek upsilon with hook symbol,\n                                    U+03D2 NEW -->\n    <!ENTITY piv      \"&#982;\"> <!-- greek pi symbol, U+03D6 ISOgrk3 -->\n\n    <!-- General Punctuation -->\n    <!ENTITY bull     \"&#8226;\"> <!-- bullet = black small circle,\n                                     U+2022 ISOpub  -->\n    <!-- bullet is NOT the same as bullet operator, U+2219 -->\n    <!ENTITY hellip   \"&#8230;\"> <!-- horizontal ellipsis = three dot leader,\n                                     U+2026 ISOpub  -->\n    <!ENTITY prime    \"&#8242;\"> <!-- prime = minutes = feet, U+2032 ISOtech -->\n    <!ENTITY Prime    \"&#8243;\"> <!-- double prime = seconds = inches,\n                                     U+2033 ISOtech -->\n    <!ENTITY oline    \"&#8254;\"> <!-- overline = spacing overscore,\n                                     U+203E NEW -->\n    <!ENTITY frasl    \"&#8260;\"> <!-- fraction slash, U+2044 NEW -->\n\n    <!-- Letterlike Symbols -->\n    <!ENTITY weierp   \"&#8472;\"> <!-- script capital P = power set\n                                     = Weierstrass p, U+2118 ISOamso -->\n    <!ENTITY image    \"&#8465;\"> <!-- black-letter capital I = imaginary part,\n                                     U+2111 ISOamso -->\n    <!ENTITY real     \"&#8476;\"> <!-- black-letter capital R = real part symbol,\n                                     U+211C ISOamso -->\n    <!ENTITY trade    \"&#8482;\"> <!-- trade mark sign, U+2122 ISOnum -->\n    <!ENTITY alefsym  \"&#8501;\"> <!-- alef symbol = first transfinite cardinal,\n                                     U+2135 NEW -->\n    <!-- alef symbol is NOT the same as hebrew letter alef,\n         U+05D0 although the same glyph could be used to depict both characters -->\n\n    <!-- Arrows -->\n    <!ENTITY larr     \"&#8592;\"> <!-- leftwards arrow, U+2190 ISOnum -->\n    <!ENTITY uarr     \"&#8593;\"> <!-- upwards arrow, U+2191 ISOnum-->\n    <!ENTITY rarr     \"&#8594;\"> <!-- rightwards arrow, U+2192 ISOnum -->\n    <!ENTITY darr     \"&#8595;\"> <!-- downwards arrow, U+2193 ISOnum -->\n    <!ENTITY harr     \"&#8596;\"> <!-- left right arrow, U+2194 ISOamsa -->\n    <!ENTITY crarr    \"&#8629;\"> <!-- downwards arrow with corner leftwards\n                                     = carriage return, U+21B5 NEW -->\n    <!ENTITY lArr     \"&#8656;\"> <!-- leftwards double arrow, U+21D0 ISOtech -->\n    <!-- Unicode does not say that lArr is the same as the 'is implied by' arrow\n        but also does not have any other character for that function. So lArr can\n        be used for 'is implied by' as ISOtech suggests -->\n    <!ENTITY uArr     \"&#8657;\"> <!-- upwards double arrow, U+21D1 ISOamsa -->\n    <!ENTITY rArr     \"&#8658;\"> <!-- rightwards double arrow,\n                                     U+21D2 ISOtech -->\n    <!-- Unicode does not say this is the 'implies' character but does not have\n         another character with this function so rArr can be used for 'implies'\n         as ISOtech suggests -->\n    <!ENTITY dArr     \"&#8659;\"> <!-- downwards double arrow, U+21D3 ISOamsa -->\n    <!ENTITY hArr     \"&#8660;\"> <!-- left right double arrow,\n                                     U+21D4 ISOamsa -->\n\n    <!-- Mathematical Operators -->\n    <!ENTITY forall   \"&#8704;\"> <!-- for all, U+2200 ISOtech -->\n    <!ENTITY part     \"&#8706;\"> <!-- partial differential, U+2202 ISOtech  -->\n    <!ENTITY exist    \"&#8707;\"> <!-- there exists, U+2203 ISOtech -->\n    <!ENTITY empty    \"&#8709;\"> <!-- empty set = null set, U+2205 ISOamso -->\n    <!ENTITY nabla    \"&#8711;\"> <!-- nabla = backward difference,\n                                     U+2207 ISOtech -->\n    <!ENTITY isin     \"&#8712;\"> <!-- element of, U+2208 ISOtech -->\n    <!ENTITY notin    \"&#8713;\"> <!-- not an element of, U+2209 ISOtech -->\n    <!ENTITY ni       \"&#8715;\"> <!-- contains as member, U+220B ISOtech -->\n    <!ENTITY prod     \"&#8719;\"> <!-- n-ary product = product sign,\n                                     U+220F ISOamsb -->\n    <!-- prod is NOT the same character as U+03A0 'greek capital letter pi' though\n         the same glyph might be used for both -->\n    <!ENTITY sum      \"&#8721;\"> <!-- n-ary summation, U+2211 ISOamsb -->\n    <!-- sum is NOT the same character as U+03A3 'greek capital letter sigma'\n         though the same glyph might be used for both -->\n    <!ENTITY minus    \"&#8722;\"> <!-- minus sign, U+2212 ISOtech -->\n    <!ENTITY lowast   \"&#8727;\"> <!-- asterisk operator, U+2217 ISOtech -->\n    <!ENTITY radic    \"&#8730;\"> <!-- square root = radical sign,\n                                     U+221A ISOtech -->\n    <!ENTITY prop     \"&#8733;\"> <!-- proportional to, U+221D ISOtech -->\n    <!ENTITY infin    \"&#8734;\"> <!-- infinity, U+221E ISOtech -->\n    <!ENTITY ang      \"&#8736;\"> <!-- angle, U+2220 ISOamso -->\n    <!ENTITY and      \"&#8743;\"> <!-- logical and = wedge, U+2227 ISOtech -->\n    <!ENTITY or       \"&#8744;\"> <!-- logical or = vee, U+2228 ISOtech -->\n    <!ENTITY cap      \"&#8745;\"> <!-- intersection = cap, U+2229 ISOtech -->\n    <!ENTITY cup      \"&#8746;\"> <!-- union = cup, U+222A ISOtech -->\n    <!ENTITY int      \"&#8747;\"> <!-- integral, U+222B ISOtech -->\n    <!ENTITY there4   \"&#8756;\"> <!-- therefore, U+2234 ISOtech -->\n    <!ENTITY sim      \"&#8764;\"> <!-- tilde operator = varies with = similar to,\n                                     U+223C ISOtech -->\n    <!-- tilde operator is NOT the same character as the tilde, U+007E,\n         although the same glyph might be used to represent both  -->\n    <!ENTITY cong     \"&#8773;\"> <!-- approximately equal to, U+2245 ISOtech -->\n    <!ENTITY asymp    \"&#8776;\"> <!-- almost equal to = asymptotic to,\n                                     U+2248 ISOamsr -->\n    <!ENTITY ne       \"&#8800;\"> <!-- not equal to, U+2260 ISOtech -->\n    <!ENTITY equiv    \"&#8801;\"> <!-- identical to, U+2261 ISOtech -->\n    <!ENTITY le       \"&#8804;\"> <!-- less-than or equal to, U+2264 ISOtech -->\n    <!ENTITY ge       \"&#8805;\"> <!-- greater-than or equal to,\n                                     U+2265 ISOtech -->\n    <!ENTITY sub      \"&#8834;\"> <!-- subset of, U+2282 ISOtech -->\n    <!ENTITY sup      \"&#8835;\"> <!-- superset of, U+2283 ISOtech -->\n    <!ENTITY nsub     \"&#8836;\"> <!-- not a subset of, U+2284 ISOamsn -->\n    <!ENTITY sube     \"&#8838;\"> <!-- subset of or equal to, U+2286 ISOtech -->\n    <!ENTITY supe     \"&#8839;\"> <!-- superset of or equal to,\n                                     U+2287 ISOtech -->\n    <!ENTITY oplus    \"&#8853;\"> <!-- circled plus = direct sum,\n                                     U+2295 ISOamsb -->\n    <!ENTITY otimes   \"&#8855;\"> <!-- circled times = vector product,\n                                     U+2297 ISOamsb -->\n    <!ENTITY perp     \"&#8869;\"> <!-- up tack = orthogonal to = perpendicular,\n                                     U+22A5 ISOtech -->\n    <!ENTITY sdot     \"&#8901;\"> <!-- dot operator, U+22C5 ISOamsb -->\n    <!-- dot operator is NOT the same character as U+00B7 middle dot -->\n\n    <!-- Miscellaneous Technical -->\n    <!ENTITY lceil    \"&#8968;\"> <!-- left ceiling = APL upstile,\n                                     U+2308 ISOamsc  -->\n    <!ENTITY rceil    \"&#8969;\"> <!-- right ceiling, U+2309 ISOamsc  -->\n    <!ENTITY lfloor   \"&#8970;\"> <!-- left floor = APL downstile,\n                                     U+230A ISOamsc  -->\n    <!ENTITY rfloor   \"&#8971;\"> <!-- right floor, U+230B ISOamsc  -->\n    <!ENTITY lang     \"&#9001;\"> <!-- left-pointing angle bracket = bra,\n                                     U+2329 ISOtech -->\n    <!-- lang is NOT the same character as U+003C 'less than sign'\n         or U+2039 'single left-pointing angle quotation mark' -->\n    <!ENTITY rang     \"&#9002;\"> <!-- right-pointing angle bracket = ket,\n                                     U+232A ISOtech -->\n    <!-- rang is NOT the same character as U+003E 'greater than sign'\n         or U+203A 'single right-pointing angle quotation mark' -->\n\n    <!-- Geometric Shapes -->\n    <!ENTITY loz      \"&#9674;\"> <!-- lozenge, U+25CA ISOpub -->\n\n    <!-- Miscellaneous Symbols -->\n    <!ENTITY spades   \"&#9824;\"> <!-- black spade suit, U+2660 ISOpub -->\n    <!-- black here seems to mean filled as opposed to hollow -->\n    <!ENTITY clubs    \"&#9827;\"> <!-- black club suit = shamrock,\n                                     U+2663 ISOpub -->\n    <!ENTITY hearts   \"&#9829;\"> <!-- black heart suit = valentine,\n                                     U+2665 ISOpub -->\n    <!ENTITY diams    \"&#9830;\"> <!-- black diamond suit, U+2666 ISOpub -->\n"
  },
  {
    "path": "modules/book/src/main/resources/dtd/www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd",
    "content": "<!--\n   Extensible HTML version 1.0 Strict DTD\n\n   This is the same as HTML 4 Strict except for\n   changes due to the differences between XML and SGML.\n\n   Namespace = http://www.w3.org/1999/xhtml\n\n   For further information, see: http://www.w3.org/TR/xhtml1\n\n   Copyright (c) 1998-2002 W3C (MIT, INRIA, Keio),\n   All Rights Reserved. \n\n   This DTD module is identified by the PUBLIC and SYSTEM identifiers:\n\n   PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\"\n   SYSTEM \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\"\n\n   $Revision: 1.1 $\n   $Date: 2002/08/01 13:56:03 $\n\n-->\n\n    <!--================ Character mnemonic entities =========================-->\n\n    <!ENTITY % HTMLlat1 PUBLIC\"-//W3C//ENTITIES Latin 1 for XHTML//EN\"\"xhtml-lat1.ent\">%HTMLlat1;\n\n    <!ENTITY % HTMLsymbol PUBLIC\"-//W3C//ENTITIES Symbols for XHTML//EN\"\"xhtml-symbol.ent\">%HTMLsymbol;\n\n    <!ENTITY % HTMLspecial PUBLIC\"-//W3C//ENTITIES Special for XHTML//EN\"\"xhtml-special.ent\">%HTMLspecial;\n\n    <!--================== Imported Names ====================================-->\n\n    <!ENTITY % ContentType \"CDATA\"><!-- media type, as per [RFC2045] -->\n\n    <!ENTITY % ContentTypes \"CDATA\"><!-- comma-separated list of media types, as per [RFC2045] -->\n\n    <!ENTITY % Charset \"CDATA\"><!-- a character encoding, as per [RFC2045] -->\n\n    <!ENTITY % Charsets \"CDATA\"><!-- a space separated list of character encodings, as per [RFC2045] -->\n\n    <!ENTITY % LanguageCode \"NMTOKEN\"><!-- a language code, as per [RFC3066] -->\n\n    <!ENTITY % Character \"CDATA\"><!-- a single character, as per section 2.2 of [XML] -->\n\n    <!ENTITY % Number \"CDATA\"><!-- one or more digits -->\n\n    <!ENTITY % LinkTypes \"CDATA\"><!-- space-separated list of link types -->\n\n    <!ENTITY % MediaDesc \"CDATA\"><!-- single or comma-separated list of media descriptors -->\n\n    <!ENTITY % URI \"CDATA\"><!-- a Uniform Resource Identifier, see [RFC2396] -->\n\n    <!ENTITY % UriList \"CDATA\"><!-- a space separated list of Uniform Resource Identifiers -->\n\n    <!ENTITY % Datetime \"CDATA\"><!-- date and time information. ISO date format -->\n\n    <!ENTITY % Script \"CDATA\"><!-- script expression -->\n\n    <!ENTITY % StyleSheet \"CDATA\"><!-- style sheet data -->\n\n    <!ENTITY % Text \"CDATA\"><!-- used for titles etc. -->\n\n    <!ENTITY % Length \"CDATA\"><!-- nn for pixels or nn% for percentage length -->\n\n    <!ENTITY % MultiLength \"CDATA\"><!-- pixel, percentage, or relative -->\n\n    <!ENTITY % Pixels \"CDATA\"><!-- integer representing length in pixels -->\n\n    <!-- these are used for image maps -->\n\n    <!ENTITY % Shape \"(rect|circle|poly|default)\">\n\n    <!ENTITY % Coords \"CDATA\"><!-- comma separated list of lengths -->\n\n    <!--=================== Generic Attributes ===============================-->\n\n    <!-- core attributes common to most elements\n      id       document-wide unique id\n      class    space separated list of classes\n      style    associated style info\n      title    advisory title/amplification\n    -->\n    <!ENTITY % coreattrs\"id          ID             #IMPLIED\n  class       CDATA          #IMPLIED\n  style       %StyleSheet;   #IMPLIED\n  title       %Text;         #IMPLIED\">\n\n    <!-- internationalization attributes\n      lang        language code (backwards compatible)\n      xml:lang    language code (as per XML 1.0 spec)\n      dir         direction for weak/neutral text\n    -->\n    <!ENTITY % i18n\"lang        %LanguageCode; #IMPLIED\n  xml:lang    %LanguageCode; #IMPLIED\n  dir         (ltr|rtl)      #IMPLIED\">\n\n    <!-- attributes for common UI events\n      onclick     a pointer button was clicked\n      ondblclick  a pointer button was double clicked\n      onmousedown a pointer button was pressed down\n      onmouseup   a pointer button was released\n      onmousemove a pointer was moved onto the element\n      onmouseout  a pointer was moved away from the element\n      onkeypress  a key was pressed and released\n      onkeydown   a key was pressed down\n      onkeyup     a key was released\n    -->\n    <!ENTITY % events\"onclick     %Script;       #IMPLIED\n  ondblclick  %Script;       #IMPLIED\n  onmousedown %Script;       #IMPLIED\n  onmouseup   %Script;       #IMPLIED\n  onmouseover %Script;       #IMPLIED\n  onmousemove %Script;       #IMPLIED\n  onmouseout  %Script;       #IMPLIED\n  onkeypress  %Script;       #IMPLIED\n  onkeydown   %Script;       #IMPLIED\n  onkeyup     %Script;       #IMPLIED\">\n\n    <!-- attributes for elements that can get the focus\n      accesskey   accessibility key character\n      tabindex    position in tabbing order\n      onfocus     the element got the focus\n      onblur      the element lost the focus\n    -->\n    <!ENTITY % focus\"accesskey   %Character;    #IMPLIED\n  tabindex    %Number;       #IMPLIED\n  onfocus     %Script;       #IMPLIED\n  onblur      %Script;       #IMPLIED\">\n\n    <!ENTITY % attrs \"%coreattrs; %i18n; %events;\">\n\n    <!--=================== Text Elements ====================================-->\n\n    <!ENTITY % special.pre\"br | span | bdo | map\">\n\n\n    <!ENTITY % special\"%special.pre; | object | img \">\n\n    <!ENTITY % fontstyle \"tt | i | b | big | small \">\n\n    <!ENTITY % phrase \"em | strong | dfn | code | q |\n                   samp | kbd | var | cite | abbr | acronym | sub | sup \">\n\n    <!ENTITY % inline.forms \"input | select | textarea | label | button\">\n\n    <!-- these can occur at block or inline level -->\n    <!ENTITY % misc.inline \"ins | del | script\">\n\n    <!-- these can only occur at block level -->\n    <!ENTITY % misc \"noscript | %misc.inline;\">\n\n    <!ENTITY % inline \"a | %special; | %fontstyle; | %phrase; | %inline.forms;\">\n\n    <!-- %Inline; covers inline or \"text-level\" elements -->\n    <!ENTITY % Inline \"(#PCDATA | %inline; | %misc.inline;)*\">\n\n    <!--================== Block level elements ==============================-->\n\n    <!ENTITY % heading \"h1|h2|h3|h4|h5|h6\"><!ENTITY % lists \"ul | ol | dl\"><!ENTITY % blocktext \"pre | hr | blockquote | address\">\n\n    <!ENTITY % block\"p | %heading; | div | %lists; | %blocktext; | fieldset | table\">\n\n    <!ENTITY % Block \"(%block; | form | %misc;)*\">\n\n    <!-- %Flow; mixes block and inline and is used for list items etc. -->\n    <!ENTITY % Flow \"(#PCDATA | %block; | form | %inline; | %misc;)*\">\n\n    <!--================== Content models for exclusions =====================-->\n\n    <!-- a elements use %Inline; excluding a -->\n\n    <!ENTITY % a.content\"(#PCDATA | %special; | %fontstyle; | %phrase; | %inline.forms; | %misc.inline;)*\">\n\n    <!-- pre uses %Inline excluding big, small, sup or sup -->\n\n    <!ENTITY % pre.content\"(#PCDATA | a | %fontstyle; | %phrase; | %special.pre; | %misc.inline;\n      | %inline.forms;)*\">\n\n    <!-- form uses %Block; excluding form -->\n\n    <!ENTITY % form.content \"(%block; | %misc;)*\">\n\n    <!-- button uses %Flow; but excludes a, form and form controls -->\n\n    <!ENTITY % button.content\"(#PCDATA | p | %heading; | div | %lists; | %blocktext; |\n    table | %special; | %fontstyle; | %phrase; | %misc;)*\">\n\n    <!--================ Document Structure ==================================-->\n\n    <!-- the namespace URI designates the document profile -->\n\n    <!ELEMENT html (head, body)><!ATTLIST html%i18n;id          ID             #IMPLIEDxmlns       %URI;          #FIXED 'http://www.w3.org/1999/xhtml'>\n\n    <!--================ Document Head =======================================-->\n\n    <!ENTITY % head.misc \"(script|style|meta|link|object)*\">\n\n    <!-- content model is %head.misc; combined with a single\n         title and an optional base element in any order -->\n\n    <!ELEMENT head (%head.misc;,((title, %head.misc;, (base, %head.misc;)?) |(base, %head.misc;, (title, %head.misc;))))>\n\n    <!ATTLIST head%i18n;id          ID             #IMPLIEDprofile     %URI;          #IMPLIED>\n\n    <!-- The title element is not considered part of the flow of text.\n           It should be displayed, for example as the page header or\n           window title. Exactly one title is required per document.\n        -->\n    <!ELEMENT title (#PCDATA)><!ATTLIST title%i18n;id          ID             #IMPLIED>\n\n    <!-- document base URI -->\n\n    <!ELEMENT base EMPTY><!ATTLIST basehref        %URI;          #REQUIREDid          ID             #IMPLIED>\n\n    <!-- generic metainformation -->\n    <!ELEMENT meta EMPTY><!ATTLIST meta%i18n;id          ID             #IMPLIEDhttp-equiv  CDATA          #IMPLIEDname        CDATA          #IMPLIEDcontent     CDATA          #REQUIREDscheme      CDATA          #IMPLIED>\n\n    <!--\n      Relationship values can be used in principle:\n\n       a) for document specific toolbars/menus when used\n          with the link element in document head e.g.\n            start, contents, previous, next, index, end, help\n       b) to link to a separate style sheet (rel=\"stylesheet\")\n       c) to make a link to a script (rel=\"script\")\n       d) by stylesheets to control how collections of\n          html nodes are rendered into printed documents\n       e) to make a link to a printable version of this document\n          e.g. a PostScript or PDF version (rel=\"alternate\" media=\"print\")\n    -->\n\n    <!ELEMENT link EMPTY><!ATTLIST link%attrs;charset     %Charset;      #IMPLIEDhref        %URI;          #IMPLIEDhreflang    %LanguageCode; #IMPLIEDtype        %ContentType;  #IMPLIEDrel         %LinkTypes;    #IMPLIEDrev         %LinkTypes;    #IMPLIEDmedia       %MediaDesc;    #IMPLIED>\n\n    <!-- style info, which may include CDATA sections -->\n    <!ELEMENT style (#PCDATA)><!ATTLIST style%i18n;id          ID             #IMPLIEDtype        %ContentType;  #REQUIREDmedia       %MediaDesc;    #IMPLIEDtitle       %Text;         #IMPLIEDxml:space   (preserve)     #FIXED 'preserve'>\n\n    <!-- script statements, which may include CDATA sections -->\n    <!ELEMENT script (#PCDATA)><!ATTLIST scriptid          ID             #IMPLIEDcharset     %Charset;      #IMPLIEDtype        %ContentType;  #REQUIREDsrc         %URI;          #IMPLIEDdefer       (defer)        #IMPLIEDxml:space   (preserve)     #FIXED 'preserve'>\n\n    <!-- alternate content container for non script-based rendering -->\n\n    <!ELEMENT noscript %Block;><!ATTLIST noscript%attrs;>\n\n    <!--=================== Document Body ====================================-->\n\n    <!ELEMENT body %Block;><!ATTLIST body%attrs;onload          %Script;   #IMPLIEDonunload        %Script;   #IMPLIED>\n\n    <!ELEMENT div %Flow;>  <!-- generic language/style container -->\n    <!ATTLIST div%attrs;>\n\n    <!--=================== Paragraphs =======================================-->\n\n    <!ELEMENT p %Inline;><!ATTLIST p%attrs;>\n\n    <!--=================== Headings =========================================-->\n\n    <!--\n      There are six levels of headings from h1 (the most important)\n      to h6 (the least important).\n    -->\n\n    <!ELEMENT h1  %Inline;><!ATTLIST h1%attrs;>\n\n    <!ELEMENT h2 %Inline;><!ATTLIST h2%attrs;>\n\n    <!ELEMENT h3 %Inline;><!ATTLIST h3%attrs;>\n\n    <!ELEMENT h4 %Inline;><!ATTLIST h4%attrs;>\n\n    <!ELEMENT h5 %Inline;><!ATTLIST h5%attrs;>\n\n    <!ELEMENT h6 %Inline;><!ATTLIST h6%attrs;>\n\n    <!--=================== Lists ============================================-->\n\n    <!-- Unordered list -->\n\n    <!ELEMENT ul (li)+><!ATTLIST ul%attrs;>\n\n    <!-- Ordered (numbered) list -->\n\n    <!ELEMENT ol (li)+><!ATTLIST ol%attrs;>\n\n    <!-- list item -->\n\n    <!ELEMENT li %Flow;><!ATTLIST li%attrs;>\n\n    <!-- definition lists - dt for term, dd for its definition -->\n\n    <!ELEMENT dl (dt|dd)+><!ATTLIST dl%attrs;>\n\n    <!ELEMENT dt %Inline;><!ATTLIST dt%attrs;>\n\n    <!ELEMENT dd %Flow;><!ATTLIST dd%attrs;>\n\n    <!--=================== Address ==========================================-->\n\n    <!-- information on author -->\n\n    <!ELEMENT address %Inline;><!ATTLIST address%attrs;>\n\n    <!--=================== Horizontal Rule ==================================-->\n\n    <!ELEMENT hr EMPTY><!ATTLIST hr%attrs;>\n\n    <!--=================== Preformatted Text ================================-->\n\n    <!-- content is %Inline; excluding \"img|object|big|small|sub|sup\" -->\n\n    <!ELEMENT pre %pre.content;><!ATTLIST pre%attrs;xml:space (preserve) #FIXED 'preserve'>\n\n    <!--=================== Block-like Quotes ================================-->\n\n    <!ELEMENT blockquote %Block;><!ATTLIST blockquote%attrs;cite        %URI;          #IMPLIED>\n\n    <!--=================== Inserted/Deleted Text ============================-->\n\n    <!--\n      ins/del are allowed in block and inline content, but its\n      inappropriate to include block content within an ins element\n      occurring in inline content.\n    -->\n    <!ELEMENT ins %Flow;><!ATTLIST ins%attrs;cite        %URI;          #IMPLIEDdatetime    %Datetime;     #IMPLIED>\n\n    <!ELEMENT del %Flow;><!ATTLIST del%attrs;cite        %URI;          #IMPLIEDdatetime    %Datetime;     #IMPLIED>\n\n    <!--================== The Anchor Element ================================-->\n\n    <!-- content is %Inline; except that anchors shouldn't be nested -->\n\n    <!ELEMENT a %a.content;><!ATTLIST a%attrs;%focus;charset     %Charset;      #IMPLIEDtype        %ContentType;  #IMPLIEDname        NMTOKEN        #IMPLIEDhref        %URI;          #IMPLIEDhreflang    %LanguageCode; #IMPLIEDrel         %LinkTypes;    #IMPLIEDrev         %LinkTypes;    #IMPLIEDshape       %Shape;        \"rect\"coords      %Coords;       #IMPLIED>\n\n    <!--===================== Inline Elements ================================-->\n\n    <!ELEMENT span %Inline;> <!-- generic language/style container -->\n    <!ATTLIST span%attrs;>\n\n    <!ELEMENT bdo %Inline;>  <!-- I18N BiDi over-ride -->\n    <!ATTLIST bdo%coreattrs;%events;lang        %LanguageCode; #IMPLIEDxml:lang    %LanguageCode; #IMPLIEDdir         (ltr|rtl)      #REQUIRED>\n\n    <!ELEMENT br EMPTY>   <!-- forced line break -->\n    <!ATTLIST br%coreattrs;>\n\n    <!ELEMENT em %Inline;>   <!-- emphasis -->\n    <!ATTLIST em %attrs;>\n\n    <!ELEMENT strong %Inline;>   <!-- strong emphasis -->\n    <!ATTLIST strong %attrs;>\n\n    <!ELEMENT dfn %Inline;>   <!-- definitional -->\n    <!ATTLIST dfn %attrs;>\n\n    <!ELEMENT code %Inline;>   <!-- program code -->\n    <!ATTLIST code %attrs;>\n\n    <!ELEMENT samp %Inline;>   <!-- sample -->\n    <!ATTLIST samp %attrs;>\n\n    <!ELEMENT kbd %Inline;>  <!-- something user would type -->\n    <!ATTLIST kbd %attrs;>\n\n    <!ELEMENT var %Inline;>   <!-- variable -->\n    <!ATTLIST var %attrs;>\n\n    <!ELEMENT cite %Inline;>   <!-- citation -->\n    <!ATTLIST cite %attrs;>\n\n    <!ELEMENT abbr %Inline;>   <!-- abbreviation -->\n    <!ATTLIST abbr %attrs;>\n\n    <!ELEMENT acronym %Inline;>   <!-- acronym -->\n    <!ATTLIST acronym %attrs;>\n\n    <!ELEMENT q %Inline;>   <!-- inlined quote -->\n    <!ATTLIST q%attrs;cite        %URI;          #IMPLIED>\n\n    <!ELEMENT sub %Inline;> <!-- subscript -->\n    <!ATTLIST sub %attrs;>\n\n    <!ELEMENT sup %Inline;> <!-- superscript -->\n    <!ATTLIST sup %attrs;>\n\n    <!ELEMENT tt %Inline;>   <!-- fixed pitch font -->\n    <!ATTLIST tt %attrs;>\n\n    <!ELEMENT i %Inline;>   <!-- italic font -->\n    <!ATTLIST i %attrs;>\n\n    <!ELEMENT b %Inline;>   <!-- bold font -->\n    <!ATTLIST b %attrs;>\n\n    <!ELEMENT big %Inline;>   <!-- bigger font -->\n    <!ATTLIST big %attrs;>\n\n    <!ELEMENT small %Inline;>   <!-- smaller font -->\n    <!ATTLIST small %attrs;>\n\n    <!--==================== Object ======================================-->\n    <!--\n      object is used to embed objects as part of HTML pages.\n      param elements should precede other content. Parameters\n      can also be expressed as attribute/value pairs on the\n      object element itself when brevity is desired.\n    -->\n\n    <!ELEMENT object (#PCDATA | param | %block; | form | %inline; | %misc;)*><!ATTLIST object%attrs;declare     (declare)      #IMPLIEDclassid     %URI;          #IMPLIEDcodebase    %URI;          #IMPLIEDdata        %URI;          #IMPLIEDtype        %ContentType;  #IMPLIEDcodetype    %ContentType;  #IMPLIEDarchive     %UriList;      #IMPLIEDstandby     %Text;         #IMPLIEDheight      %Length;       #IMPLIEDwidth       %Length;       #IMPLIEDusemap      %URI;          #IMPLIEDname        NMTOKEN        #IMPLIEDtabindex    %Number;       #IMPLIED>\n\n    <!--\n      param is used to supply a named property value.\n      In XML it would seem natural to follow RDF and support an\n      abbreviated syntax where the param elements are replaced\n      by attribute value pairs on the object start tag.\n    -->\n    <!ELEMENT param EMPTY><!ATTLIST paramid          ID             #IMPLIEDname        CDATA          #IMPLIEDvalue       CDATA          #IMPLIEDvaluetype   (data|ref|object) \"data\"type        %ContentType;  #IMPLIED>\n\n    <!--=================== Images ===========================================-->\n\n    <!--\n       To avoid accessibility problems for people who aren't\n       able to see the image, you should provide a text\n       description using the alt and longdesc attributes.\n       In addition, avoid the use of server-side image maps.\n       Note that in this DTD there is no name attribute. That\n       is only available in the transitional and frameset DTD.\n    -->\n\n    <!ELEMENT img EMPTY><!ATTLIST img%attrs;src         %URI;          #REQUIREDalt         %Text;         #REQUIREDlongdesc    %URI;          #IMPLIEDheight      %Length;       #IMPLIEDwidth       %Length;       #IMPLIEDusemap      %URI;          #IMPLIEDismap       (ismap)        #IMPLIED>\n\n    <!-- usemap points to a map element which may be in this document\n      or an external document, although the latter is not widely supported -->\n\n    <!--================== Client-side image maps ============================-->\n\n    <!-- These can be placed in the same document or grouped in a\n         separate document although this isn't yet widely supported -->\n\n    <!ELEMENT map ((%block; | form | %misc;)+ | area+)><!ATTLIST map%i18n;%events;id          ID             #REQUIREDclass       CDATA          #IMPLIEDstyle       %StyleSheet;   #IMPLIEDtitle       %Text;         #IMPLIEDname        NMTOKEN        #IMPLIED>\n\n    <!ELEMENT area EMPTY><!ATTLIST area%attrs;%focus;shape       %Shape;        \"rect\"coords      %Coords;       #IMPLIEDhref        %URI;          #IMPLIEDnohref      (nohref)       #IMPLIEDalt         %Text;         #REQUIRED>\n\n    <!--================ Forms ===============================================-->\n    <!ELEMENT form %form.content;>   <!-- forms shouldn't be nested -->\n\n    <!ATTLIST form%attrs;action      %URI;          #REQUIREDmethod      (get|post)     \"get\"enctype     %ContentType;  \"application/x-www-form-urlencoded\"onsubmit    %Script;       #IMPLIEDonreset     %Script;       #IMPLIEDaccept      %ContentTypes; #IMPLIEDaccept-charset %Charsets;  #IMPLIED>\n\n    <!--\n      Each label must not contain more than ONE field\n      Label elements shouldn't be nested.\n    -->\n    <!ELEMENT label %Inline;><!ATTLIST label%attrs;for         IDREF          #IMPLIEDaccesskey   %Character;    #IMPLIEDonfocus     %Script;       #IMPLIEDonblur      %Script;       #IMPLIED>\n\n    <!ENTITY % InputType\"(text | password | checkbox |\n    radio | submit | reset |\n    file | hidden | image | button)\">\n\n    <!-- the name attribute is required for all but submit & reset -->\n\n    <!ELEMENT input EMPTY>     <!-- form control -->\n    <!ATTLIST input%attrs;%focus;type        %InputType;    \"text\"name        CDATA          #IMPLIEDvalue       CDATA          #IMPLIEDchecked     (checked)      #IMPLIEDdisabled    (disabled)     #IMPLIEDreadonly    (readonly)     #IMPLIEDsize        CDATA          #IMPLIEDmaxlength   %Number;       #IMPLIEDsrc         %URI;          #IMPLIEDalt         CDATA          #IMPLIEDusemap      %URI;          #IMPLIEDonselect    %Script;       #IMPLIEDonchange    %Script;       #IMPLIEDaccept      %ContentTypes; #IMPLIED>\n\n    <!ELEMENT select (optgroup|option)+>  <!-- option selector -->\n    <!ATTLIST select%attrs;name        CDATA          #IMPLIEDsize        %Number;       #IMPLIEDmultiple    (multiple)     #IMPLIEDdisabled    (disabled)     #IMPLIEDtabindex    %Number;       #IMPLIEDonfocus     %Script;       #IMPLIEDonblur      %Script;       #IMPLIEDonchange    %Script;       #IMPLIED>\n\n    <!ELEMENT optgroup (option)+>   <!-- option group -->\n    <!ATTLIST optgroup%attrs;disabled    (disabled)     #IMPLIEDlabel       %Text;         #REQUIRED>\n\n    <!ELEMENT option (#PCDATA)>     <!-- selectable choice -->\n    <!ATTLIST option%attrs;selected    (selected)     #IMPLIEDdisabled    (disabled)     #IMPLIEDlabel       %Text;         #IMPLIEDvalue       CDATA          #IMPLIED>\n\n    <!ELEMENT textarea (#PCDATA)>     <!-- multi-line text field -->\n    <!ATTLIST textarea%attrs;%focus;name        CDATA          #IMPLIEDrows        %Number;       #REQUIREDcols        %Number;       #REQUIREDdisabled    (disabled)     #IMPLIEDreadonly    (readonly)     #IMPLIEDonselect    %Script;       #IMPLIEDonchange    %Script;       #IMPLIED>\n\n    <!--\n      The fieldset element is used to group form fields.\n      Only one legend element should occur in the content\n      and if present should only be preceded by whitespace.\n    -->\n    <!ELEMENT fieldset (#PCDATA | legend | %block; | form | %inline; | %misc;)*><!ATTLIST fieldset%attrs;>\n\n    <!ELEMENT legend %Inline;>     <!-- fieldset label -->\n    <!ATTLIST legend%attrs;accesskey   %Character;    #IMPLIED>\n\n    <!--\n     Content is %Flow; excluding a, form and form controls\n    -->\n    <!ELEMENT button %button.content;>  <!-- push button -->\n    <!ATTLIST button%attrs;%focus;name        CDATA          #IMPLIEDvalue       CDATA          #IMPLIEDtype        (button|submit|reset) \"submit\"disabled    (disabled)     #IMPLIED>\n\n    <!--======================= Tables =======================================-->\n\n    <!-- Derived from IETF HTML table standard, see [RFC1942] -->\n\n    <!--\n     The border attribute sets the thickness of the frame around the\n     table. The default units are screen pixels.\n\n     The frame attribute specifies which parts of the frame around\n     the table should be rendered. The values are not the same as\n     CALS to avoid a name clash with the valign attribute.\n    -->\n    <!ENTITY % TFrame \"(void|above|below|hsides|lhs|rhs|vsides|box|border)\">\n\n    <!--\n     The rules attribute defines which rules to draw between cells:\n\n     If rules is absent then assume:\n         \"none\" if border is absent or border=\"0\" otherwise \"all\"\n    -->\n\n    <!ENTITY % TRules \"(none | groups | rows | cols | all)\">\n\n    <!-- horizontal alignment attributes for cell contents\n\n      char        alignment char, e.g. char=':'\n      charoff     offset for alignment char\n    -->\n    <!ENTITY % cellhalign\"align      (left|center|right|justify|char) #IMPLIED\n   char       %Character;    #IMPLIED\n   charoff    %Length;       #IMPLIED\">\n\n    <!-- vertical alignment attributes for cell contents -->\n    <!ENTITY % cellvalign\"valign     (top|middle|bottom|baseline) #IMPLIED\">\n\n    <!ELEMENT table(caption?, (col*|colgroup*), thead?, tfoot?, (tbody+|tr+))><!ELEMENT caption  %Inline;><!ELEMENT thead    (tr)+><!ELEMENT tfoot    (tr)+><!ELEMENT tbody    (tr)+><!ELEMENT colgroup (col)*><!ELEMENT col      EMPTY><!ELEMENT tr       (th|td)+><!ELEMENT th       %Flow;><!ELEMENT td       %Flow;>\n\n    <!ATTLIST table%attrs;summary     %Text;         #IMPLIEDwidth       %Length;       #IMPLIEDborder      %Pixels;       #IMPLIEDframe       %TFrame;       #IMPLIEDrules       %TRules;       #IMPLIEDcellspacing %Length;       #IMPLIEDcellpadding %Length;       #IMPLIED>\n\n    <!ATTLIST caption%attrs;>\n\n    <!--\n    colgroup groups a set of col elements. It allows you to group\n    several semantically related columns together.\n    -->\n    <!ATTLIST colgroup%attrs;span        %Number;       \"1\"width       %MultiLength;  #IMPLIED%cellhalign;%cellvalign;>\n\n    <!--\n     col elements define the alignment properties for cells in\n     one or more columns.\n\n     The width attribute specifies the width of the columns, e.g.\n\n         width=64        width in screen pixels\n         width=0.5*      relative width of 0.5\n\n     The span attribute causes the attributes of one\n     col element to apply to more than one column.\n    -->\n    <!ATTLIST col%attrs;span        %Number;       \"1\"width       %MultiLength;  #IMPLIED%cellhalign;%cellvalign;>\n\n    <!--\n        Use thead to duplicate headers when breaking table\n        across page boundaries, or for static headers when\n        tbody sections are rendered in scrolling panel.\n\n        Use tfoot to duplicate footers when breaking table\n        across page boundaries, or for static footers when\n        tbody sections are rendered in scrolling panel.\n\n        Use multiple tbody sections when rules are needed\n        between groups of table rows.\n    -->\n    <!ATTLIST thead%attrs;%cellhalign;%cellvalign;>\n\n    <!ATTLIST tfoot%attrs;%cellhalign;%cellvalign;>\n\n    <!ATTLIST tbody%attrs;%cellhalign;%cellvalign;>\n\n    <!ATTLIST tr%attrs;%cellhalign;%cellvalign;>\n\n\n    <!-- Scope is simpler than headers attribute for common tables -->\n    <!ENTITY % Scope \"(row|col|rowgroup|colgroup)\">\n\n    <!-- th is for headers, td for data and for cells acting as both -->\n\n    <!ATTLIST th%attrs;abbr        %Text;         #IMPLIEDaxis        CDATA          #IMPLIEDheaders     IDREFS         #IMPLIEDscope       %Scope;        #IMPLIEDrowspan     %Number;       \"1\"colspan     %Number;       \"1\"%cellhalign;%cellvalign;>\n\n    <!ATTLIST td%attrs;abbr        %Text;         #IMPLIEDaxis        CDATA          #IMPLIEDheaders     IDREFS         #IMPLIEDscope       %Scope;        #IMPLIEDrowspan     %Number;       \"1\"colspan     %Number;       \"1\"%cellhalign;%cellvalign;>\n\n"
  },
  {
    "path": "modules/book/src/main/resources/dtd/www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd",
    "content": "<!--\n   Extensible HTML version 1.0 Transitional DTD\n\n   This is the same as HTML 4 Transitional except for\n   changes due to the differences between XML and SGML.\n\n   Namespace = http://www.w3.org/1999/xhtml\n\n   For further information, see: http://www.w3.org/TR/xhtml1\n\n   Copyright (c) 1998-2002 W3C (MIT, INRIA, Keio),\n   All Rights Reserved. \n\n   This DTD module is identified by the PUBLIC and SYSTEM identifiers:\n\n   PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\"\n   SYSTEM \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\"\n\n   $Revision: 1.2 $\n   $Date: 2002/08/01 18:37:55 $\n\n-->\n\n    <!--================ Character mnemonic entities =========================-->\n\n    <!ENTITY % HTMLlat1 PUBLIC\"-//W3C//ENTITIES Latin 1 for XHTML//EN\"\"xhtml-lat1.ent\">%HTMLlat1;\n\n    <!ENTITY % HTMLsymbol PUBLIC\"-//W3C//ENTITIES Symbols for XHTML//EN\"\"xhtml-symbol.ent\">%HTMLsymbol;\n\n    <!ENTITY % HTMLspecial PUBLIC\"-//W3C//ENTITIES Special for XHTML//EN\"\"xhtml-special.ent\">%HTMLspecial;\n\n    <!--================== Imported Names ====================================-->\n\n    <!ENTITY % ContentType \"CDATA\"><!-- media type, as per [RFC2045] -->\n\n    <!ENTITY % ContentTypes \"CDATA\"><!-- comma-separated list of media types, as per [RFC2045] -->\n\n    <!ENTITY % Charset \"CDATA\"><!-- a character encoding, as per [RFC2045] -->\n\n    <!ENTITY % Charsets \"CDATA\"><!-- a space separated list of character encodings, as per [RFC2045] -->\n\n    <!ENTITY % LanguageCode \"NMTOKEN\"><!-- a language code, as per [RFC3066] -->\n\n    <!ENTITY % Character \"CDATA\"><!-- a single character, as per section 2.2 of [XML] -->\n\n    <!ENTITY % Number \"CDATA\"><!-- one or more digits -->\n\n    <!ENTITY % LinkTypes \"CDATA\"><!-- space-separated list of link types -->\n\n    <!ENTITY % MediaDesc \"CDATA\"><!-- single or comma-separated list of media descriptors -->\n\n    <!ENTITY % URI \"CDATA\"><!-- a Uniform Resource Identifier, see [RFC2396] -->\n\n    <!ENTITY % UriList \"CDATA\"><!-- a space separated list of Uniform Resource Identifiers -->\n\n    <!ENTITY % Datetime \"CDATA\"><!-- date and time information. ISO date format -->\n\n    <!ENTITY % Script \"CDATA\"><!-- script expression -->\n\n    <!ENTITY % StyleSheet \"CDATA\"><!-- style sheet data -->\n\n    <!ENTITY % Text \"CDATA\"><!-- used for titles etc. -->\n\n    <!ENTITY % FrameTarget \"NMTOKEN\"><!-- render in this frame -->\n\n    <!ENTITY % Length \"CDATA\"><!-- nn for pixels or nn% for percentage length -->\n\n    <!ENTITY % MultiLength \"CDATA\"><!-- pixel, percentage, or relative -->\n\n    <!ENTITY % Pixels \"CDATA\"><!-- integer representing length in pixels -->\n\n    <!-- these are used for image maps -->\n\n    <!ENTITY % Shape \"(rect|circle|poly|default)\">\n\n    <!ENTITY % Coords \"CDATA\"><!-- comma separated list of lengths -->\n\n    <!-- used for object, applet, img, input and iframe -->\n    <!ENTITY % ImgAlign \"(top|middle|bottom|left|right)\">\n\n    <!-- a color using sRGB: #RRGGBB as Hex values -->\n    <!ENTITY % Color \"CDATA\">\n\n    <!-- There are also 16 widely known color names with their sRGB values:\n\n        Black  = #000000    Green  = #008000\n        Silver = #C0C0C0    Lime   = #00FF00\n        Gray   = #808080    Olive  = #808000\n        White  = #FFFFFF    Yellow = #FFFF00\n        Maroon = #800000    Navy   = #000080\n        Red    = #FF0000    Blue   = #0000FF\n        Purple = #800080    Teal   = #008080\n        Fuchsia= #FF00FF    Aqua   = #00FFFF\n    -->\n\n    <!--=================== Generic Attributes ===============================-->\n\n    <!-- core attributes common to most elements\n      id       document-wide unique id\n      class    space separated list of classes\n      style    associated style info\n      title    advisory title/amplification\n    -->\n    <!ENTITY % coreattrs\"id          ID             #IMPLIED\n  class       CDATA          #IMPLIED\n  style       %StyleSheet;   #IMPLIED\n  title       %Text;         #IMPLIED\">\n\n    <!-- internationalization attributes\n      lang        language code (backwards compatible)\n      xml:lang    language code (as per XML 1.0 spec)\n      dir         direction for weak/neutral text\n    -->\n    <!ENTITY % i18n\"lang        %LanguageCode; #IMPLIED\n  xml:lang    %LanguageCode; #IMPLIED\n  dir         (ltr|rtl)      #IMPLIED\">\n\n    <!-- attributes for common UI events\n      onclick     a pointer button was clicked\n      ondblclick  a pointer button was double clicked\n      onmousedown a pointer button was pressed down\n      onmouseup   a pointer button was released\n      onmousemove a pointer was moved onto the element\n      onmouseout  a pointer was moved away from the element\n      onkeypress  a key was pressed and released\n      onkeydown   a key was pressed down\n      onkeyup     a key was released\n    -->\n    <!ENTITY % events\"onclick     %Script;       #IMPLIED\n  ondblclick  %Script;       #IMPLIED\n  onmousedown %Script;       #IMPLIED\n  onmouseup   %Script;       #IMPLIED\n  onmouseover %Script;       #IMPLIED\n  onmousemove %Script;       #IMPLIED\n  onmouseout  %Script;       #IMPLIED\n  onkeypress  %Script;       #IMPLIED\n  onkeydown   %Script;       #IMPLIED\n  onkeyup     %Script;       #IMPLIED\">\n\n    <!-- attributes for elements that can get the focus\n      accesskey   accessibility key character\n      tabindex    position in tabbing order\n      onfocus     the element got the focus\n      onblur      the element lost the focus\n    -->\n    <!ENTITY % focus\"accesskey   %Character;    #IMPLIED\n  tabindex    %Number;       #IMPLIED\n  onfocus     %Script;       #IMPLIED\n  onblur      %Script;       #IMPLIED\">\n\n    <!ENTITY % attrs \"%coreattrs; %i18n; %events;\">\n\n    <!-- text alignment for p, div, h1-h6. The default is\n         align=\"left\" for ltr headings, \"right\" for rtl -->\n\n    <!ENTITY % TextAlign \"align (left|center|right|justify) #IMPLIED\">\n\n    <!--=================== Text Elements ====================================-->\n\n    <!ENTITY % special.extra\"object | applet | img | map | iframe\">\n\n    <!ENTITY % special.basic    \"br | span | bdo\">\n\n    <!ENTITY % special\"%special.basic; | %special.extra;\">\n\n    <!ENTITY % fontstyle.extra \"big | small | font | basefont\">\n\n    <!ENTITY % fontstyle.basic \"tt | i | b | u\n                      | s | strike \">\n\n    <!ENTITY % fontstyle \"%fontstyle.basic; | %fontstyle.extra;\">\n\n    <!ENTITY % phrase.extra \"sub | sup\"><!ENTITY % phrase.basic \"em | strong | dfn | code | q |\n                   samp | kbd | var | cite | abbr | acronym\">\n\n    <!ENTITY % phrase \"%phrase.basic; | %phrase.extra;\">\n\n    <!ENTITY % inline.forms \"input | select | textarea | label | button\">\n\n    <!-- these can occur at block or inline level -->\n    <!ENTITY % misc.inline \"ins | del | script\">\n\n    <!-- these can only occur at block level -->\n    <!ENTITY % misc \"noscript | %misc.inline;\">\n\n    <!ENTITY % inline \"a | %special; | %fontstyle; | %phrase; | %inline.forms;\">\n\n    <!-- %Inline; covers inline or \"text-level\" elements -->\n    <!ENTITY % Inline \"(#PCDATA | %inline; | %misc.inline;)*\">\n\n    <!--================== Block level elements ==============================-->\n\n    <!ENTITY % heading \"h1|h2|h3|h4|h5|h6\"><!ENTITY % lists \"ul | ol | dl | menu | dir\"><!ENTITY % blocktext \"pre | hr | blockquote | address | center | noframes\">\n\n    <!ENTITY % block\"p | %heading; | div | %lists; | %blocktext; | isindex |fieldset | table\">\n\n    <!-- %Flow; mixes block and inline and is used for list items etc. -->\n    <!ENTITY % Flow \"(#PCDATA | %block; | form | %inline; | %misc;)*\">\n\n    <!--================== Content models for exclusions =====================-->\n\n    <!-- a elements use %Inline; excluding a -->\n\n    <!ENTITY % a.content\"(#PCDATA | %special; | %fontstyle; | %phrase; | %inline.forms; | %misc.inline;)*\">\n\n    <!-- pre uses %Inline excluding img, object, applet, big, small,\n         font, or basefont -->\n\n    <!ENTITY % pre.content\"(#PCDATA | a | %special.basic; | %fontstyle.basic; | %phrase.basic; |\n\t   %inline.forms; | %misc.inline;)*\">\n\n    <!-- form uses %Flow; excluding form -->\n\n    <!ENTITY % form.content \"(#PCDATA | %block; | %inline; | %misc;)*\">\n\n    <!-- button uses %Flow; but excludes a, form, form controls, iframe -->\n\n    <!ENTITY % button.content\"(#PCDATA | p | %heading; | div | %lists; | %blocktext; |\n      table | br | span | bdo | object | applet | img | map |\n      %fontstyle; | %phrase; | %misc;)*\">\n\n    <!--================ Document Structure ==================================-->\n\n    <!-- the namespace URI designates the document profile -->\n\n    <!ELEMENT html (head, body)><!ATTLIST html%i18n;id          ID             #IMPLIEDxmlns       %URI;          #FIXED 'http://www.w3.org/1999/xhtml'>\n\n    <!--================ Document Head =======================================-->\n\n    <!ENTITY % head.misc \"(script|style|meta|link|object|isindex)*\">\n\n    <!-- content model is %head.misc; combined with a single\n         title and an optional base element in any order -->\n\n    <!ELEMENT head (%head.misc;,((title, %head.misc;, (base, %head.misc;)?) |(base, %head.misc;, (title, %head.misc;))))>\n\n    <!ATTLIST head%i18n;id          ID             #IMPLIEDprofile     %URI;          #IMPLIED>\n\n    <!-- The title element is not considered part of the flow of text.\n           It should be displayed, for example as the page header or\n           window title. Exactly one title is required per document.\n        -->\n    <!ELEMENT title (#PCDATA)><!ATTLIST title%i18n;id          ID             #IMPLIED>\n\n    <!-- document base URI -->\n\n    <!ELEMENT base EMPTY><!ATTLIST baseid          ID             #IMPLIEDhref        %URI;          #IMPLIEDtarget      %FrameTarget;  #IMPLIED>\n\n    <!-- generic metainformation -->\n    <!ELEMENT meta EMPTY><!ATTLIST meta%i18n;id          ID             #IMPLIEDhttp-equiv  CDATA          #IMPLIEDname        CDATA          #IMPLIEDcontent     CDATA          #REQUIREDscheme      CDATA          #IMPLIED>\n\n    <!--\n      Relationship values can be used in principle:\n\n       a) for document specific toolbars/menus when used\n          with the link element in document head e.g.\n            start, contents, previous, next, index, end, help\n       b) to link to a separate style sheet (rel=\"stylesheet\")\n       c) to make a link to a script (rel=\"script\")\n       d) by stylesheets to control how collections of\n          html nodes are rendered into printed documents\n       e) to make a link to a printable version of this document\n          e.g. a PostScript or PDF version (rel=\"alternate\" media=\"print\")\n    -->\n\n    <!ELEMENT link EMPTY><!ATTLIST link%attrs;charset     %Charset;      #IMPLIEDhref        %URI;          #IMPLIEDhreflang    %LanguageCode; #IMPLIEDtype        %ContentType;  #IMPLIEDrel         %LinkTypes;    #IMPLIEDrev         %LinkTypes;    #IMPLIEDmedia       %MediaDesc;    #IMPLIEDtarget      %FrameTarget;  #IMPLIED>\n\n    <!-- style info, which may include CDATA sections -->\n    <!ELEMENT style (#PCDATA)><!ATTLIST style%i18n;id          ID             #IMPLIEDtype        %ContentType;  #REQUIREDmedia       %MediaDesc;    #IMPLIEDtitle       %Text;         #IMPLIEDxml:space   (preserve)     #FIXED 'preserve'>\n\n    <!-- script statements, which may include CDATA sections -->\n    <!ELEMENT script (#PCDATA)><!ATTLIST scriptid          ID             #IMPLIEDcharset     %Charset;      #IMPLIEDtype        %ContentType;  #REQUIREDlanguage    CDATA          #IMPLIEDsrc         %URI;          #IMPLIEDdefer       (defer)        #IMPLIEDxml:space   (preserve)     #FIXED 'preserve'>\n\n    <!-- alternate content container for non script-based rendering -->\n\n    <!ELEMENT noscript %Flow;><!ATTLIST noscript%attrs;>\n\n    <!--======================= Frames =======================================-->\n\n    <!-- inline subwindow -->\n\n    <!ELEMENT iframe %Flow;><!ATTLIST iframe%coreattrs;longdesc    %URI;          #IMPLIEDname        NMTOKEN        #IMPLIEDsrc         %URI;          #IMPLIEDframeborder (1|0)          \"1\"marginwidth %Pixels;       #IMPLIEDmarginheight %Pixels;      #IMPLIEDscrolling   (yes|no|auto)  \"auto\"align       %ImgAlign;     #IMPLIEDheight      %Length;       #IMPLIEDwidth       %Length;       #IMPLIED>\n\n    <!-- alternate content container for non frame-based rendering -->\n\n    <!ELEMENT noframes %Flow;><!ATTLIST noframes%attrs;>\n\n    <!--=================== Document Body ====================================-->\n\n    <!ELEMENT body %Flow;><!ATTLIST body%attrs;onload      %Script;       #IMPLIEDonunload    %Script;       #IMPLIEDbackground  %URI;          #IMPLIEDbgcolor     %Color;        #IMPLIEDtext        %Color;        #IMPLIEDlink        %Color;        #IMPLIEDvlink       %Color;        #IMPLIEDalink       %Color;        #IMPLIED>\n\n    <!ELEMENT div %Flow;>  <!-- generic language/style container -->\n    <!ATTLIST div%attrs;%TextAlign;>\n\n    <!--=================== Paragraphs =======================================-->\n\n    <!ELEMENT p %Inline;><!ATTLIST p%attrs;%TextAlign;>\n\n    <!--=================== Headings =========================================-->\n\n    <!--\n      There are six levels of headings from h1 (the most important)\n      to h6 (the least important).\n    -->\n\n    <!ELEMENT h1  %Inline;><!ATTLIST h1%attrs;%TextAlign;>\n\n    <!ELEMENT h2 %Inline;><!ATTLIST h2%attrs;%TextAlign;>\n\n    <!ELEMENT h3 %Inline;><!ATTLIST h3%attrs;%TextAlign;>\n\n    <!ELEMENT h4 %Inline;><!ATTLIST h4%attrs;%TextAlign;>\n\n    <!ELEMENT h5 %Inline;><!ATTLIST h5%attrs;%TextAlign;>\n\n    <!ELEMENT h6 %Inline;><!ATTLIST h6%attrs;%TextAlign;>\n\n    <!--=================== Lists ============================================-->\n\n    <!-- Unordered list bullet styles -->\n\n    <!ENTITY % ULStyle \"(disc|square|circle)\">\n\n    <!-- Unordered list -->\n\n    <!ELEMENT ul (li)+><!ATTLIST ul%attrs;type        %ULStyle;     #IMPLIEDcompact     (compact)     #IMPLIED>\n\n    <!-- Ordered list numbering style\n\n        1   arabic numbers      1, 2, 3, ...\n        a   lower alpha         a, b, c, ...\n        A   upper alpha         A, B, C, ...\n        i   lower roman         i, ii, iii, ...\n        I   upper roman         I, II, III, ...\n\n        The style is applied to the sequence number which by default\n        is reset to 1 for the first list item in an ordered list.\n    -->\n    <!ENTITY % OLStyle \"CDATA\">\n\n    <!-- Ordered (numbered) list -->\n\n    <!ELEMENT ol (li)+><!ATTLIST ol%attrs;type        %OLStyle;      #IMPLIEDcompact     (compact)      #IMPLIEDstart       %Number;       #IMPLIED>\n\n    <!-- single column list (DEPRECATED) -->\n    <!ELEMENT menu (li)+><!ATTLIST menu%attrs;compact     (compact)     #IMPLIED>\n\n    <!-- multiple column list (DEPRECATED) -->\n    <!ELEMENT dir (li)+><!ATTLIST dir%attrs;compact     (compact)     #IMPLIED>\n\n    <!-- LIStyle is constrained to: \"(%ULStyle;|%OLStyle;)\" -->\n    <!ENTITY % LIStyle \"CDATA\">\n\n    <!-- list item -->\n\n    <!ELEMENT li %Flow;><!ATTLIST li%attrs;type        %LIStyle;      #IMPLIEDvalue       %Number;       #IMPLIED>\n\n    <!-- definition lists - dt for term, dd for its definition -->\n\n    <!ELEMENT dl (dt|dd)+><!ATTLIST dl%attrs;compact     (compact)      #IMPLIED>\n\n    <!ELEMENT dt %Inline;><!ATTLIST dt%attrs;>\n\n    <!ELEMENT dd %Flow;><!ATTLIST dd%attrs;>\n\n    <!--=================== Address ==========================================-->\n\n    <!-- information on author -->\n\n    <!ELEMENT address (#PCDATA | %inline; | %misc.inline; | p)*><!ATTLIST address%attrs;>\n\n    <!--=================== Horizontal Rule ==================================-->\n\n    <!ELEMENT hr EMPTY><!ATTLIST hr%attrs;align       (left|center|right) #IMPLIEDnoshade     (noshade)      #IMPLIEDsize        %Pixels;       #IMPLIEDwidth       %Length;       #IMPLIED>\n\n    <!--=================== Preformatted Text ================================-->\n\n    <!-- content is %Inline; excluding\n            \"img|object|applet|big|small|sub|sup|font|basefont\" -->\n\n    <!ELEMENT pre %pre.content;><!ATTLIST pre%attrs;width       %Number;      #IMPLIEDxml:space   (preserve)    #FIXED 'preserve'>\n\n    <!--=================== Block-like Quotes ================================-->\n\n    <!ELEMENT blockquote %Flow;><!ATTLIST blockquote%attrs;cite        %URI;          #IMPLIED>\n\n    <!--=================== Text alignment ===================================-->\n\n    <!-- center content -->\n    <!ELEMENT center %Flow;><!ATTLIST center%attrs;>\n\n    <!--=================== Inserted/Deleted Text ============================-->\n\n    <!--\n      ins/del are allowed in block and inline content, but its\n      inappropriate to include block content within an ins element\n      occurring in inline content.\n    -->\n    <!ELEMENT ins %Flow;><!ATTLIST ins%attrs;cite        %URI;          #IMPLIEDdatetime    %Datetime;     #IMPLIED>\n\n    <!ELEMENT del %Flow;><!ATTLIST del%attrs;cite        %URI;          #IMPLIEDdatetime    %Datetime;     #IMPLIED>\n\n    <!--================== The Anchor Element ================================-->\n\n    <!-- content is %Inline; except that anchors shouldn't be nested -->\n\n    <!ELEMENT a %a.content;><!ATTLIST a%attrs;%focus;charset     %Charset;      #IMPLIEDtype        %ContentType;  #IMPLIEDname        NMTOKEN        #IMPLIEDhref        %URI;          #IMPLIEDhreflang    %LanguageCode; #IMPLIEDrel         %LinkTypes;    #IMPLIEDrev         %LinkTypes;    #IMPLIEDshape       %Shape;        \"rect\"coords      %Coords;       #IMPLIEDtarget      %FrameTarget;  #IMPLIED>\n\n    <!--===================== Inline Elements ================================-->\n\n    <!ELEMENT span %Inline;> <!-- generic language/style container -->\n    <!ATTLIST span%attrs;>\n\n    <!ELEMENT bdo %Inline;>  <!-- I18N BiDi over-ride -->\n    <!ATTLIST bdo%coreattrs;%events;lang        %LanguageCode; #IMPLIEDxml:lang    %LanguageCode; #IMPLIEDdir         (ltr|rtl)      #REQUIRED>\n\n    <!ELEMENT br EMPTY>   <!-- forced line break -->\n    <!ATTLIST br%coreattrs;clear       (left|all|right|none) \"none\">\n\n    <!ELEMENT em %Inline;>   <!-- emphasis -->\n    <!ATTLIST em %attrs;>\n\n    <!ELEMENT strong %Inline;>   <!-- strong emphasis -->\n    <!ATTLIST strong %attrs;>\n\n    <!ELEMENT dfn %Inline;>   <!-- definitional -->\n    <!ATTLIST dfn %attrs;>\n\n    <!ELEMENT code %Inline;>   <!-- program code -->\n    <!ATTLIST code %attrs;>\n\n    <!ELEMENT samp %Inline;>   <!-- sample -->\n    <!ATTLIST samp %attrs;>\n\n    <!ELEMENT kbd %Inline;>  <!-- something user would type -->\n    <!ATTLIST kbd %attrs;>\n\n    <!ELEMENT var %Inline;>   <!-- variable -->\n    <!ATTLIST var %attrs;>\n\n    <!ELEMENT cite %Inline;>   <!-- citation -->\n    <!ATTLIST cite %attrs;>\n\n    <!ELEMENT abbr %Inline;>   <!-- abbreviation -->\n    <!ATTLIST abbr %attrs;>\n\n    <!ELEMENT acronym %Inline;>   <!-- acronym -->\n    <!ATTLIST acronym %attrs;>\n\n    <!ELEMENT q %Inline;>   <!-- inlined quote -->\n    <!ATTLIST q%attrs;cite        %URI;          #IMPLIED>\n\n    <!ELEMENT sub %Inline;> <!-- subscript -->\n    <!ATTLIST sub %attrs;>\n\n    <!ELEMENT sup %Inline;> <!-- superscript -->\n    <!ATTLIST sup %attrs;>\n\n    <!ELEMENT tt %Inline;>   <!-- fixed pitch font -->\n    <!ATTLIST tt %attrs;>\n\n    <!ELEMENT i %Inline;>   <!-- italic font -->\n    <!ATTLIST i %attrs;>\n\n    <!ELEMENT b %Inline;>   <!-- bold font -->\n    <!ATTLIST b %attrs;>\n\n    <!ELEMENT big %Inline;>   <!-- bigger font -->\n    <!ATTLIST big %attrs;>\n\n    <!ELEMENT small %Inline;>   <!-- smaller font -->\n    <!ATTLIST small %attrs;>\n\n    <!ELEMENT u %Inline;>   <!-- underline -->\n    <!ATTLIST u %attrs;>\n\n    <!ELEMENT s %Inline;>   <!-- strike-through -->\n    <!ATTLIST s %attrs;>\n\n    <!ELEMENT strike %Inline;>   <!-- strike-through -->\n    <!ATTLIST strike %attrs;>\n\n    <!ELEMENT basefont EMPTY>  <!-- base font size -->\n    <!ATTLIST basefontid          ID             #IMPLIEDsize        CDATA          #REQUIREDcolor       %Color;        #IMPLIEDface        CDATA          #IMPLIED>\n\n    <!ELEMENT font %Inline;> <!-- local change to font -->\n    <!ATTLIST font%coreattrs;%i18n;size        CDATA          #IMPLIEDcolor       %Color;        #IMPLIEDface        CDATA          #IMPLIED>\n\n    <!--==================== Object ======================================-->\n    <!--\n      object is used to embed objects as part of HTML pages.\n      param elements should precede other content. Parameters\n      can also be expressed as attribute/value pairs on the\n      object element itself when brevity is desired.\n    -->\n\n    <!ELEMENT object (#PCDATA | param | %block; | form | %inline; | %misc;)*><!ATTLIST object%attrs;declare     (declare)      #IMPLIEDclassid     %URI;          #IMPLIEDcodebase    %URI;          #IMPLIEDdata        %URI;          #IMPLIEDtype        %ContentType;  #IMPLIEDcodetype    %ContentType;  #IMPLIEDarchive     %UriList;      #IMPLIEDstandby     %Text;         #IMPLIEDheight      %Length;       #IMPLIEDwidth       %Length;       #IMPLIEDusemap      %URI;          #IMPLIEDname        NMTOKEN        #IMPLIEDtabindex    %Number;       #IMPLIEDalign       %ImgAlign;     #IMPLIEDborder      %Pixels;       #IMPLIEDhspace      %Pixels;       #IMPLIEDvspace      %Pixels;       #IMPLIED>\n\n    <!--\n      param is used to supply a named property value.\n      In XML it would seem natural to follow RDF and support an\n      abbreviated syntax where the param elements are replaced\n      by attribute value pairs on the object start tag.\n    -->\n    <!ELEMENT param EMPTY><!ATTLIST paramid          ID             #IMPLIEDname        CDATA          #REQUIREDvalue       CDATA          #IMPLIEDvaluetype   (data|ref|object) \"data\"type        %ContentType;  #IMPLIED>\n\n    <!--=================== Java applet ==================================-->\n    <!--\n      One of code or object attributes must be present.\n      Place param elements before other content.\n    -->\n    <!ELEMENT applet (#PCDATA | param | %block; | form | %inline; | %misc;)*><!ATTLIST applet%coreattrs;codebase    %URI;          #IMPLIEDarchive     CDATA          #IMPLIEDcode        CDATA          #IMPLIEDobject      CDATA          #IMPLIEDalt         %Text;         #IMPLIEDname        NMTOKEN        #IMPLIEDwidth       %Length;       #REQUIREDheight      %Length;       #REQUIREDalign       %ImgAlign;     #IMPLIEDhspace      %Pixels;       #IMPLIEDvspace      %Pixels;       #IMPLIED>\n\n    <!--=================== Images ===========================================-->\n\n    <!--\n       To avoid accessibility problems for people who aren't\n       able to see the image, you should provide a text\n       description using the alt and longdesc attributes.\n       In addition, avoid the use of server-side image maps.\n    -->\n\n    <!ELEMENT img EMPTY><!ATTLIST img%attrs;src         %URI;          #REQUIREDalt         %Text;         #REQUIREDname        NMTOKEN        #IMPLIEDlongdesc    %URI;          #IMPLIEDheight      %Length;       #IMPLIEDwidth       %Length;       #IMPLIEDusemap      %URI;          #IMPLIEDismap       (ismap)        #IMPLIEDalign       %ImgAlign;     #IMPLIEDborder      %Length;       #IMPLIEDhspace      %Pixels;       #IMPLIEDvspace      %Pixels;       #IMPLIED>\n\n    <!-- usemap points to a map element which may be in this document\n      or an external document, although the latter is not widely supported -->\n\n    <!--================== Client-side image maps ============================-->\n\n    <!-- These can be placed in the same document or grouped in a\n         separate document although this isn't yet widely supported -->\n\n    <!ELEMENT map ((%block; | form | %misc;)+ | area+)><!ATTLIST map%i18n;%events;id          ID             #REQUIREDclass       CDATA          #IMPLIEDstyle       %StyleSheet;   #IMPLIEDtitle       %Text;         #IMPLIEDname        CDATA          #IMPLIED>\n\n    <!ELEMENT area EMPTY><!ATTLIST area%attrs;%focus;shape       %Shape;        \"rect\"coords      %Coords;       #IMPLIEDhref        %URI;          #IMPLIEDnohref      (nohref)       #IMPLIEDalt         %Text;         #REQUIREDtarget      %FrameTarget;  #IMPLIED>\n\n    <!--================ Forms ===============================================-->\n\n    <!ELEMENT form %form.content;>   <!-- forms shouldn't be nested -->\n\n    <!ATTLIST form%attrs;action      %URI;          #REQUIREDmethod      (get|post)     \"get\"name        NMTOKEN        #IMPLIEDenctype     %ContentType;  \"application/x-www-form-urlencoded\"onsubmit    %Script;       #IMPLIEDonreset     %Script;       #IMPLIEDaccept      %ContentTypes; #IMPLIEDaccept-charset %Charsets;  #IMPLIEDtarget      %FrameTarget;  #IMPLIED>\n\n    <!--\n      Each label must not contain more than ONE field\n      Label elements shouldn't be nested.\n    -->\n    <!ELEMENT label %Inline;><!ATTLIST label%attrs;for         IDREF          #IMPLIEDaccesskey   %Character;    #IMPLIEDonfocus     %Script;       #IMPLIEDonblur      %Script;       #IMPLIED>\n\n    <!ENTITY % InputType\"(text | password | checkbox |\n    radio | submit | reset |\n    file | hidden | image | button)\">\n\n    <!-- the name attribute is required for all but submit & reset -->\n\n    <!ELEMENT input EMPTY>     <!-- form control -->\n    <!ATTLIST input%attrs;%focus;type        %InputType;    \"text\"name        CDATA          #IMPLIEDvalue       CDATA          #IMPLIEDchecked     (checked)      #IMPLIEDdisabled    (disabled)     #IMPLIEDreadonly    (readonly)     #IMPLIEDsize        CDATA          #IMPLIEDmaxlength   %Number;       #IMPLIEDsrc         %URI;          #IMPLIEDalt         CDATA          #IMPLIEDusemap      %URI;          #IMPLIEDonselect    %Script;       #IMPLIEDonchange    %Script;       #IMPLIEDaccept      %ContentTypes; #IMPLIEDalign       %ImgAlign;     #IMPLIED>\n\n    <!ELEMENT select (optgroup|option)+>  <!-- option selector -->\n    <!ATTLIST select%attrs;name        CDATA          #IMPLIEDsize        %Number;       #IMPLIEDmultiple    (multiple)     #IMPLIEDdisabled    (disabled)     #IMPLIEDtabindex    %Number;       #IMPLIEDonfocus     %Script;       #IMPLIEDonblur      %Script;       #IMPLIEDonchange    %Script;       #IMPLIED>\n\n    <!ELEMENT optgroup (option)+>   <!-- option group -->\n    <!ATTLIST optgroup%attrs;disabled    (disabled)     #IMPLIEDlabel       %Text;         #REQUIRED>\n\n    <!ELEMENT option (#PCDATA)>     <!-- selectable choice -->\n    <!ATTLIST option%attrs;selected    (selected)     #IMPLIEDdisabled    (disabled)     #IMPLIEDlabel       %Text;         #IMPLIEDvalue       CDATA          #IMPLIED>\n\n    <!ELEMENT textarea (#PCDATA)>     <!-- multi-line text field -->\n    <!ATTLIST textarea%attrs;%focus;name        CDATA          #IMPLIEDrows        %Number;       #REQUIREDcols        %Number;       #REQUIREDdisabled    (disabled)     #IMPLIEDreadonly    (readonly)     #IMPLIEDonselect    %Script;       #IMPLIEDonchange    %Script;       #IMPLIED>\n\n    <!--\n      The fieldset element is used to group form fields.\n      Only one legend element should occur in the content\n      and if present should only be preceded by whitespace.\n    -->\n    <!ELEMENT fieldset (#PCDATA | legend | %block; | form | %inline; | %misc;)*><!ATTLIST fieldset%attrs;>\n\n    <!ENTITY % LAlign \"(top|bottom|left|right)\">\n\n    <!ELEMENT legend %Inline;>     <!-- fieldset label -->\n    <!ATTLIST legend%attrs;accesskey   %Character;    #IMPLIEDalign       %LAlign;       #IMPLIED>\n\n    <!--\n     Content is %Flow; excluding a, form, form controls, iframe\n    -->\n    <!ELEMENT button %button.content;>  <!-- push button -->\n    <!ATTLIST button%attrs;%focus;name        CDATA          #IMPLIEDvalue       CDATA          #IMPLIEDtype        (button|submit|reset) \"submit\"disabled    (disabled)     #IMPLIED>\n\n    <!-- single-line text input control (DEPRECATED) -->\n    <!ELEMENT isindex EMPTY><!ATTLIST isindex%coreattrs;%i18n;prompt      %Text;         #IMPLIED>\n\n    <!--======================= Tables =======================================-->\n\n    <!-- Derived from IETF HTML table standard, see [RFC1942] -->\n\n    <!--\n     The border attribute sets the thickness of the frame around the\n     table. The default units are screen pixels.\n\n     The frame attribute specifies which parts of the frame around\n     the table should be rendered. The values are not the same as\n     CALS to avoid a name clash with the valign attribute.\n    -->\n    <!ENTITY % TFrame \"(void|above|below|hsides|lhs|rhs|vsides|box|border)\">\n\n    <!--\n     The rules attribute defines which rules to draw between cells:\n\n     If rules is absent then assume:\n         \"none\" if border is absent or border=\"0\" otherwise \"all\"\n    -->\n\n    <!ENTITY % TRules \"(none | groups | rows | cols | all)\">\n\n    <!-- horizontal placement of table relative to document -->\n    <!ENTITY % TAlign \"(left|center|right)\">\n\n    <!-- horizontal alignment attributes for cell contents\n\n      char        alignment char, e.g. char=':'\n      charoff     offset for alignment char\n    -->\n    <!ENTITY % cellhalign\"align      (left|center|right|justify|char) #IMPLIED\n   char       %Character;    #IMPLIED\n   charoff    %Length;       #IMPLIED\">\n\n    <!-- vertical alignment attributes for cell contents -->\n    <!ENTITY % cellvalign\"valign     (top|middle|bottom|baseline) #IMPLIED\">\n\n    <!ELEMENT table(caption?, (col*|colgroup*), thead?, tfoot?, (tbody+|tr+))><!ELEMENT caption  %Inline;><!ELEMENT thead    (tr)+><!ELEMENT tfoot    (tr)+><!ELEMENT tbody    (tr)+><!ELEMENT colgroup (col)*><!ELEMENT col      EMPTY><!ELEMENT tr       (th|td)+><!ELEMENT th       %Flow;><!ELEMENT td       %Flow;>\n\n    <!ATTLIST table%attrs;summary     %Text;         #IMPLIEDwidth       %Length;       #IMPLIEDborder      %Pixels;       #IMPLIEDframe       %TFrame;       #IMPLIEDrules       %TRules;       #IMPLIEDcellspacing %Length;       #IMPLIEDcellpadding %Length;       #IMPLIEDalign       %TAlign;       #IMPLIEDbgcolor     %Color;        #IMPLIED>\n\n    <!ENTITY % CAlign \"(top|bottom|left|right)\">\n\n    <!ATTLIST caption%attrs;align       %CAlign;       #IMPLIED>\n\n    <!--\n    colgroup groups a set of col elements. It allows you to group\n    several semantically related columns together.\n    -->\n    <!ATTLIST colgroup%attrs;span        %Number;       \"1\"width       %MultiLength;  #IMPLIED%cellhalign;%cellvalign;>\n\n    <!--\n     col elements define the alignment properties for cells in\n     one or more columns.\n\n     The width attribute specifies the width of the columns, e.g.\n\n         width=64        width in screen pixels\n         width=0.5*      relative width of 0.5\n\n     The span attribute causes the attributes of one\n     col element to apply to more than one column.\n    -->\n    <!ATTLIST col%attrs;span        %Number;       \"1\"width       %MultiLength;  #IMPLIED%cellhalign;%cellvalign;>\n\n    <!--\n        Use thead to duplicate headers when breaking table\n        across page boundaries, or for static headers when\n        tbody sections are rendered in scrolling panel.\n\n        Use tfoot to duplicate footers when breaking table\n        across page boundaries, or for static footers when\n        tbody sections are rendered in scrolling panel.\n\n        Use multiple tbody sections when rules are needed\n        between groups of table rows.\n    -->\n    <!ATTLIST thead%attrs;%cellhalign;%cellvalign;>\n\n    <!ATTLIST tfoot%attrs;%cellhalign;%cellvalign;>\n\n    <!ATTLIST tbody%attrs;%cellhalign;%cellvalign;>\n\n    <!ATTLIST tr%attrs;%cellhalign;%cellvalign;bgcolor     %Color;        #IMPLIED>\n\n    <!-- Scope is simpler than headers attribute for common tables -->\n    <!ENTITY % Scope \"(row|col|rowgroup|colgroup)\">\n\n    <!-- th is for headers, td for data and for cells acting as both -->\n\n    <!ATTLIST th%attrs;abbr        %Text;         #IMPLIEDaxis        CDATA          #IMPLIEDheaders     IDREFS         #IMPLIEDscope       %Scope;        #IMPLIEDrowspan     %Number;       \"1\"colspan     %Number;       \"1\"%cellhalign;%cellvalign;nowrap      (nowrap)       #IMPLIEDbgcolor     %Color;        #IMPLIEDwidth       %Length;       #IMPLIEDheight      %Length;       #IMPLIED>\n\n    <!ATTLIST td%attrs;abbr        %Text;         #IMPLIEDaxis        CDATA          #IMPLIEDheaders     IDREFS         #IMPLIEDscope       %Scope;        #IMPLIEDrowspan     %Number;       \"1\"colspan     %Number;       \"1\"%cellhalign;%cellvalign;nowrap      (nowrap)       #IMPLIEDbgcolor     %Color;        #IMPLIEDwidth       %Length;       #IMPLIEDheight      %Length;       #IMPLIED>\n\n"
  },
  {
    "path": "modules/book/src/main/resources/dtd/www.w3.org/TR/xhtml11/DTD/xhtml11.dtd",
    "content": "<!-- ....................................................................... -->\n    <!-- XHTML 1.1 DTD  ........................................................ -->\n    <!-- file: xhtml11.dtd\n    -->\n\n    <!-- XHTML 1.1 DTD\n\n         This is XHTML, a reformulation of HTML as a modular XML application.\n\n         The Extensible HyperText Markup Language (XHTML)\n         Copyright 1998-2001 World Wide Web Consortium\n            (Massachusetts Institute of Technology, Institut National de\n             Recherche en Informatique et en Automatique, Keio University).\n             All Rights Reserved.\n\n         Permission to use, copy, modify and distribute the XHTML DTD and its\n         accompanying documentation for any purpose and without fee is hereby\n         granted in perpetuity, provided that the above copyright notice and\n         this paragraph appear in all copies.  The copyright holders make no\n         representation about the suitability of the DTD for any purpose.\n\n         It is provided \"as is\" without expressed or implied warranty.\n\n            Author:     Murray M. Altheim <altheim@eng.sun.com>\n            Revision:   $Id: xhtml11.dtd,v 1.21 2001/05/29 16:37:01 ahby Exp $\n\n    -->\n    <!-- This is the driver file for version 1.1 of the XHTML DTD.\n\n         Please use this formal public identifier to identify it:\n\n             \"-//W3C//DTD XHTML 1.1//EN\"\n    -->\n    <!ENTITY % XHTML.version  \"-//W3C//DTD XHTML 1.1//EN\" >\n\n    <!-- Use this URI to identify the default namespace:\n\n             \"http://www.w3.org/1999/xhtml\"\n\n         See the Qualified Names module for information\n         on the use of namespace prefixes in the DTD.\n    -->\n    <!ENTITY % NS.prefixed \"IGNORE\" ><!ENTITY % XHTML.prefix \"\" >\n\n    <!-- Reserved for use with the XLink namespace:\n    -->\n    <!ENTITY % XLINK.xmlns \"\" ><!ENTITY % XLINK.xmlns.attrib \"\" >\n\n    <!-- For example, if you are using XHTML 1.1 directly, use the FPI\n         in the DOCTYPE declaration, with the xmlns attribute on the\n         document element to identify the default namespace:\n\n           <?xml version=\"1.0\"?>\n           <!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.1//EN\" \"xhtml11.dtd\">\n           <html xmlns=\"http://www.w3.org/1999/xhtml\"\n                 xml:lang=\"en\">\n           ...\n           </html>\n\n         Revisions:\n         (none)\n    -->\n\n    <!-- reserved for future use with document profiles -->\n    <!ENTITY % XHTML.profile  \"\" >\n\n    <!-- Bidirectional Text features\n         This feature-test entity is used to declare elements\n         and attributes used for bidirectional text support.\n    -->\n    <!ENTITY % XHTML.bidi  \"INCLUDE\" >\n\n    <?doc type=\"doctype\" role=\"title\" { XHTML 1.1 } ?>\n\n    <!-- ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: -->\n\n    <!-- Pre-Framework Redeclaration placeholder  .................... -->\n    <!-- this serves as a location to insert markup declarations\n         into the DTD prior to the framework declarations.\n    -->\n    <!ENTITY % xhtml-prefw-redecl.module \"IGNORE\" ><![%xhtml-prefw-redecl.module;[%xhtml-prefw-redecl.mod;<!-- end of xhtml-prefw-redecl.module -->]]>\n\n    <!ENTITY % xhtml-events.module \"INCLUDE\" >\n\n    <!-- Inline Style Module  ........................................ -->\n    <!ENTITY % xhtml-inlstyle.module \"INCLUDE\" ><![%xhtml-inlstyle.module;[<!ENTITY % xhtml-inlstyle.modPUBLIC \"-//W3C//ELEMENTS XHTML Inline Style 1.0//EN\"\"http://www.w3.org/TR/xhtml-modularization/DTD/xhtml-inlstyle-1.mod\" >%xhtml-inlstyle.mod;]]>\n\n    <!-- declare Document Model module instantiated in framework\n    -->\n    <!ENTITY % xhtml-model.modPUBLIC \"-//W3C//ENTITIES XHTML 1.1 Document Model 1.0//EN\"\"xhtml11-model-1.mod\" >\n\n    <!-- Modular Framework Module (required) ......................... -->\n    <!ENTITY % xhtml-framework.module \"INCLUDE\" ><![%xhtml-framework.module;[<!ENTITY % xhtml-framework.modPUBLIC \"-//W3C//ENTITIES XHTML Modular Framework 1.0//EN\"\"http://www.w3.org/TR/xhtml-modularization/DTD/xhtml-framework-1.mod\" >%xhtml-framework.mod;]]>\n\n    <!-- Post-Framework Redeclaration placeholder  ................... -->\n    <!-- this serves as a location to insert markup declarations\n         into the DTD following the framework declarations.\n    -->\n    <!ENTITY % xhtml-postfw-redecl.module \"IGNORE\" ><![%xhtml-postfw-redecl.module;[%xhtml-postfw-redecl.mod;<!-- end of xhtml-postfw-redecl.module -->]]>\n\n    <!-- Text Module (Required)  ..................................... -->\n    <!ENTITY % xhtml-text.module \"INCLUDE\" ><![%xhtml-text.module;[<!ENTITY % xhtml-text.modPUBLIC \"-//W3C//ELEMENTS XHTML Text 1.0//EN\"\"http://www.w3.org/TR/xhtml-modularization/DTD/xhtml-text-1.mod\" >%xhtml-text.mod;]]>\n\n    <!-- Hypertext Module (required) ................................. -->\n    <!ENTITY % xhtml-hypertext.module \"INCLUDE\" ><![%xhtml-hypertext.module;[<!ENTITY % xhtml-hypertext.modPUBLIC \"-//W3C//ELEMENTS XHTML Hypertext 1.0//EN\"\"http://www.w3.org/TR/xhtml-modularization/DTD/xhtml-hypertext-1.mod\" >%xhtml-hypertext.mod;]]>\n\n    <!-- Lists Module (required)  .................................... -->\n    <!ENTITY % xhtml-list.module \"INCLUDE\" ><![%xhtml-list.module;[<!ENTITY % xhtml-list.modPUBLIC \"-//W3C//ELEMENTS XHTML Lists 1.0//EN\"\"http://www.w3.org/TR/xhtml-modularization/DTD/xhtml-list-1.mod\" >%xhtml-list.mod;]]>\n\n    <!-- ::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::: -->\n\n    <!-- Edit Module  ................................................ -->\n    <!ENTITY % xhtml-edit.module \"INCLUDE\" ><![%xhtml-edit.module;[<!ENTITY % xhtml-edit.modPUBLIC \"-//W3C//ELEMENTS XHTML Editing Elements 1.0//EN\"\"http://www.w3.org/TR/xhtml-modularization/DTD/xhtml-edit-1.mod\" >%xhtml-edit.mod;]]>\n\n    <!-- BIDI Override Module  ....................................... -->\n    <!ENTITY % xhtml-bdo.module \"%XHTML.bidi;\" ><![%xhtml-bdo.module;[<!ENTITY % xhtml-bdo.modPUBLIC \"-//W3C//ELEMENTS XHTML BIDI Override Element 1.0//EN\"\"http://www.w3.org/TR/xhtml-modularization/DTD/xhtml-bdo-1.mod\" >%xhtml-bdo.mod;]]>\n\n    <!-- Ruby Module  ................................................ -->\n    <!ENTITY % Ruby.common.attlists \"INCLUDE\" ><!ENTITY % Ruby.common.attrib \"%Common.attrib;\" ><!ENTITY % xhtml-ruby.module \"INCLUDE\" ><![%xhtml-ruby.module;[<!ENTITY % xhtml-ruby.modPUBLIC \"-//W3C//ELEMENTS XHTML Ruby 1.0//EN\"\"http://www.w3.org/TR/ruby/xhtml-ruby-1.mod\" >%xhtml-ruby.mod;]]>\n\n    <!-- Presentation Module  ........................................ -->\n    <!ENTITY % xhtml-pres.module \"INCLUDE\" ><![%xhtml-pres.module;[<!ENTITY % xhtml-pres.modPUBLIC \"-//W3C//ELEMENTS XHTML Presentation 1.0//EN\"\"http://www.w3.org/TR/xhtml-modularization/DTD/xhtml-pres-1.mod\" >%xhtml-pres.mod;]]>\n\n    <!-- Link Element Module  ........................................ -->\n    <!ENTITY % xhtml-link.module \"INCLUDE\" ><![%xhtml-link.module;[<!ENTITY % xhtml-link.modPUBLIC \"-//W3C//ELEMENTS XHTML Link Element 1.0//EN\"\"http://www.w3.org/TR/xhtml-modularization/DTD/xhtml-link-1.mod\" >%xhtml-link.mod;]]>\n\n    <!-- Document Metainformation Module  ............................ -->\n    <!ENTITY % xhtml-meta.module \"INCLUDE\" ><![%xhtml-meta.module;[<!ENTITY % xhtml-meta.modPUBLIC \"-//W3C//ELEMENTS XHTML Metainformation 1.0//EN\"\"http://www.w3.org/TR/xhtml-modularization/DTD/xhtml-meta-1.mod\" >%xhtml-meta.mod;]]>\n\n    <!-- Base Element Module  ........................................ -->\n    <!ENTITY % xhtml-base.module \"INCLUDE\" ><![%xhtml-base.module;[<!ENTITY % xhtml-base.modPUBLIC \"-//W3C//ELEMENTS XHTML Base Element 1.0//EN\"\"http://www.w3.org/TR/xhtml-modularization/DTD/xhtml-base-1.mod\" >%xhtml-base.mod;]]>\n\n    <!-- Scripting Module  ........................................... -->\n    <!ENTITY % xhtml-script.module \"INCLUDE\" ><![%xhtml-script.module;[<!ENTITY % xhtml-script.modPUBLIC \"-//W3C//ELEMENTS XHTML Scripting 1.0//EN\"\"http://www.w3.org/TR/xhtml-modularization/DTD/xhtml-script-1.mod\" >%xhtml-script.mod;]]>\n\n    <!-- Style Sheets Module  ......................................... -->\n    <!ENTITY % xhtml-style.module \"INCLUDE\" ><![%xhtml-style.module;[<!ENTITY % xhtml-style.modPUBLIC \"-//W3C//ELEMENTS XHTML Style Sheets 1.0//EN\"\"http://www.w3.org/TR/xhtml-modularization/DTD/xhtml-style-1.mod\" >%xhtml-style.mod;]]>\n\n    <!-- Image Module  ............................................... -->\n    <!ENTITY % xhtml-image.module \"INCLUDE\" ><![%xhtml-image.module;[<!ENTITY % xhtml-image.modPUBLIC \"-//W3C//ELEMENTS XHTML Images 1.0//EN\"\"http://www.w3.org/TR/xhtml-modularization/DTD/xhtml-image-1.mod\" >%xhtml-image.mod;]]>\n\n    <!-- Client-side Image Map Module  ............................... -->\n    <!ENTITY % xhtml-csismap.module \"INCLUDE\" ><![%xhtml-csismap.module;[<!ENTITY % xhtml-csismap.modPUBLIC \"-//W3C//ELEMENTS XHTML Client-side Image Maps 1.0//EN\"\"http://www.w3.org/TR/xhtml-modularization/DTD/xhtml-csismap-1.mod\" >%xhtml-csismap.mod;]]>\n\n    <!-- Server-side Image Map Module  ............................... -->\n    <!ENTITY % xhtml-ssismap.module \"INCLUDE\" ><![%xhtml-ssismap.module;[<!ENTITY % xhtml-ssismap.modPUBLIC \"-//W3C//ELEMENTS XHTML Server-side Image Maps 1.0//EN\"\"http://www.w3.org/TR/xhtml-modularization/DTD/xhtml-ssismap-1.mod\" >%xhtml-ssismap.mod;]]>\n\n    <!-- Param Element Module  ....................................... -->\n    <!ENTITY % xhtml-param.module \"INCLUDE\" ><![%xhtml-param.module;[<!ENTITY % xhtml-param.modPUBLIC \"-//W3C//ELEMENTS XHTML Param Element 1.0//EN\"\"http://www.w3.org/TR/xhtml-modularization/DTD/xhtml-param-1.mod\" >%xhtml-param.mod;]]>\n\n    <!-- Embedded Object Module  ..................................... -->\n    <!ENTITY % xhtml-object.module \"INCLUDE\" ><![%xhtml-object.module;[<!ENTITY % xhtml-object.modPUBLIC \"-//W3C//ELEMENTS XHTML Embedded Object 1.0//EN\"\"http://www.w3.org/TR/xhtml-modularization/DTD/xhtml-object-1.mod\" >%xhtml-object.mod;]]>\n\n    <!-- Tables Module ............................................... -->\n    <!ENTITY % xhtml-table.module \"INCLUDE\" ><![%xhtml-table.module;[<!ENTITY % xhtml-table.modPUBLIC \"-//W3C//ELEMENTS XHTML Tables 1.0//EN\"\"http://www.w3.org/TR/xhtml-modularization/DTD/xhtml-table-1.mod\" >%xhtml-table.mod;]]>\n\n    <!-- Forms Module  ............................................... -->\n    <!ENTITY % xhtml-form.module \"INCLUDE\" ><![%xhtml-form.module;[<!ENTITY % xhtml-form.modPUBLIC \"-//W3C//ELEMENTS XHTML Forms 1.0//EN\"\"http://www.w3.org/TR/xhtml-modularization/DTD/xhtml-form-1.mod\" >%xhtml-form.mod;]]>\n\n    <!-- Legacy Markup ............................................... -->\n    <!ENTITY % xhtml-legacy.module \"IGNORE\" ><![%xhtml-legacy.module;[<!ENTITY % xhtml-legacy.modPUBLIC \"-//W3C//ELEMENTS XHTML Legacy Markup 1.0//EN\"\"http://www.w3.org/TR/xhtml-modularization/DTD/xhtml-legacy-1.mod\" >%xhtml-legacy.mod;]]>\n\n    <!-- Document Structure Module (required)  ....................... -->\n    <!ENTITY % xhtml-struct.module \"INCLUDE\" ><![%xhtml-struct.module;[<!ENTITY % xhtml-struct.modPUBLIC \"-//W3C//ELEMENTS XHTML Document Structure 1.0//EN\"\"http://www.w3.org/TR/xhtml-modularization/DTD/xhtml-struct-1.mod\" >%xhtml-struct.mod;]]>\n\n    <!-- end of XHTML 1.1 DTD  ................................................. -->\n    <!-- ....................................................................... -->\n"
  },
  {
    "path": "modules/book/src/main/resources/log4j.properties",
    "content": "#------------------------------------------------------------------------------\n#\n#  The following properties set the logging levels and log appender.  The\n#  log4j.rootCategory variable defines the default log level and one or more\n#  appenders.  For the console, use 'S'.  For the daily rolling file, use 'R'.\n#  For an HTML formatted log, use 'H'.\n#\n#  To override the default (rootCategory) log level, define a property of the\n#  form (see below for available values):\n#\n#        log4j.logger. =\n#\n#    Available logger names:\n#      TODO\n#\n#    Possible Log Levels:\n#      FATAL, ERROR, WARN, INFO, DEBUG\n#\n#------------------------------------------------------------------------------\nlog4j.rootCategory=INFO, S\n#------------------------------------------------------------------------------\n#\n#  The following properties configure the console (stdout) appender.\n#  See http://logging.apache.org/log4j/docs/api/index.html for details.\n#\n#------------------------------------------------------------------------------\nlog4j.appender.S=org.apache.log4j.ConsoleAppender\nlog4j.appender.S.layout=org.apache.log4j.PatternLayout\nlog4j.appender.S.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} [%p] %l %m%n\n#------------------------------------------------------------------------------\n#\n#  The following properties configure the Daily Rolling File appender.\n#  See http://logging.apache.org/log4j/docs/api/index.html for details.\n#\n#------------------------------------------------------------------------------\nlog4j.appender.R=org.apache.log4j.DailyRollingFileAppender\nlog4j.appender.R.File=logs/epublib.log\nlog4j.appender.R.Append=true\nlog4j.appender.R.DatePattern='.'yyy-MM-dd\nlog4j.appender.R.layout=org.apache.log4j.PatternLayout\nlog4j.appender.R.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %c{1} [%p] %m%n\n#------------------------------------------------------------------------------\n#\n#  The following properties configure the Rolling File appender in HTML.\n#  See http://logging.apache.org/log4j/docs/api/index.html for details.\n#\n#------------------------------------------------------------------------------\nlog4j.appender.H=org.apache.log4j.RollingFileAppender\nlog4j.appender.H.File=logs/epublib_log.html\nlog4j.appender.H.MaxFileSize=100KB\nlog4j.appender.H.Append=false\nlog4j.appender.H.layout=org.apache.log4j.HTMLLayout"
  },
  {
    "path": "modules/rhino/build.gradle",
    "content": "plugins {\n    //d 'com.android.library'\n    //id 'org.jetbrains.kotlin.android'\n    alias libs.plugins.android.library\n    alias libs.plugins.kotlin.android\n}\n\nandroid {\n    compileSdk = compile_sdk_version\n    namespace = 'com.script'\n    kotlin {\n        jvmToolchain {\n            languageVersion.set(JavaLanguageVersion.of(17))\n        }\n    }\n    defaultConfig {\n        minSdk 21\n        targetSdk 36\n\n        consumerProguardFiles \"consumer-rules.pro\"\n    }\n\n    compileOptions {\n        sourceCompatibility JavaVersion.VERSION_17\n        targetCompatibility JavaVersion.VERSION_17\n    }\n    lint {\n        checkDependencies = true\n    }\n    tasks.withType(JavaCompile).tap {\n        configureEach {\n            options.compilerArgs << \"-Xlint:deprecation\"\n        }\n    }\n}\n\ndependencies {\n//    api(fileTree(dir: 'lib', include: ['rhino-1.7.14.jar']))\n    api libs.mozilla.rhino\n\n    implementation(libs.kotlinx.coroutines.core)\n    implementation(libs.okhttp)\n    implementation(libs.androidx.collection)\n\n//    def coroutines_version = '1.7.3'\n//    implementation(\"org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version\")\n}"
  },
  {
    "path": "modules/rhino/consumer-rules.pro",
    "content": "# Add project specific ProGuard rules here.\n# You can control the set of applied configuration files using the\n# proguardFiles setting in build.gradle.\n#\n# For more details, see\n#   http://developer.android.com/guide/developing/tools/proguard.html\n\n# If your project uses WebView with JS, uncomment the following\n# and specify the fully qualified class name to the JavaScript interface\n# class:\n#-keepclassmembers class fqcn.of.javascript.interface.for.webview {\n#   public *;\n#}\n\n# Uncomment this to preserve the line number information for\n# debugging stack traces.\n#-keepattributes SourceFile,LineNumberTable\n\n# If you keep the line number information, uncomment this to\n# hide the original source file name.\n#-renamesourcefileattribute SourceFile\n\n## Rhino\n-keep class\n!org.mozilla.javascript.ast.**,\n!org.mozilla.javascript.xml.**,\n!org.mozilla.javascript.commonjs.**,\n!org.mozilla.javascript.optimizer.**,\n!org.mozilla.javascript.serialize.**,\norg.mozilla.javascript.** { *; }\n\n-dontwarn org.mozilla.javascript.engine.RhinoScriptEngineFactory\n### 以下内容是更新rhino1.7.14.jar后IDE提示添加的\n-dontwarn java.beans.**\n-dontwarn javax.script.**\n### 以下内容是更新rhino1.8.0.jar后IDE提示添加的\n-dontwarn jdk.dynalink.CallSiteDescriptor\n-dontwarn jdk.dynalink.DynamicLinker\n-dontwarn jdk.dynalink.DynamicLinkerFactory\n-dontwarn jdk.dynalink.NamedOperation\n-dontwarn jdk.dynalink.Namespace\n-dontwarn jdk.dynalink.NamespaceOperation\n-dontwarn jdk.dynalink.Operation\n-dontwarn jdk.dynalink.RelinkableCallSite\n-dontwarn jdk.dynalink.StandardNamespace\n-dontwarn jdk.dynalink.StandardOperation\n-dontwarn jdk.dynalink.linker.GuardedInvocation\n-dontwarn jdk.dynalink.linker.GuardingDynamicLinker\n-dontwarn jdk.dynalink.linker.LinkRequest\n-dontwarn jdk.dynalink.linker.LinkerServices\n-dontwarn jdk.dynalink.linker.TypeBasedGuardingDynamicLinker\n-dontwarn jdk.dynalink.linker.support.CompositeTypeBasedGuardingDynamicLinker\n-dontwarn jdk.dynalink.linker.support.Guards\n-dontwarn jdk.dynalink.support.ChainedCallSite\n"
  },
  {
    "path": "modules/rhino/src/main/AndroidManifest.xml",
    "content": "<?xml version=\"1.0\" encoding=\"utf-8\"?>\n<manifest>\n\n</manifest>"
  },
  {
    "path": "modules/rhino/src/main/java/com/script/AbstractScriptEngine.kt",
    "content": "/*\n * Decompiled with CFR 0.152.\n */\npackage com.script\n\nimport com.script.ScriptContext.Companion.ENGINE_SCOPE\nimport com.script.ScriptContext.Companion.GLOBAL_SCOPE\nimport org.mozilla.javascript.Scriptable\nimport java.io.Reader\nimport java.io.StringReader\nimport kotlin.coroutines.CoroutineContext\n\nabstract class AbstractScriptEngine(val bindings: Bindings? = null) : ScriptEngine {\n\n    override var context: ScriptContext = SimpleScriptContext()\n\n    init {\n        bindings?.let {\n            context.setBindings(bindings, ENGINE_SCOPE)\n        }\n    }\n\n    override fun getBindings(scope: Int): Bindings? {\n        if (scope == GLOBAL_SCOPE) {\n            return context.getBindings(GLOBAL_SCOPE)\n        }\n        if (scope == ENGINE_SCOPE) {\n            return context.getBindings(ENGINE_SCOPE)\n        }\n        throw IllegalArgumentException(\"Invalid scope value.\")\n    }\n\n    override fun setBindings(bindings: Bindings?, scope: Int) {\n        when (scope) {\n            GLOBAL_SCOPE -> {\n                context.setBindings(bindings, GLOBAL_SCOPE)\n            }\n\n            ENGINE_SCOPE -> {\n                context.setBindings(bindings, ENGINE_SCOPE)\n            }\n\n            else -> {\n                throw IllegalArgumentException(\"Invalid scope value.\")\n            }\n        }\n    }\n\n    override fun put(key: String, value: Any?) {\n        getBindings(ENGINE_SCOPE)?.put(key, value)\n    }\n\n    override fun get(key: String): Any? {\n        return getBindings(ENGINE_SCOPE)?.get(key)\n    }\n\n    override fun eval(reader: Reader, scope: Scriptable): Any? {\n        return eval(reader, scope, null)\n    }\n\n    override suspend fun evalSuspend(script: String, scope: Scriptable): Any? {\n        return this.evalSuspend(StringReader(script), scope)\n    }\n\n    override fun eval(script: String, scope: Scriptable): Any? {\n        return this.eval(StringReader(script), scope)\n    }\n\n    @Throws(ScriptException::class)\n    override fun eval(reader: Reader, context: ScriptContext): Any? {\n        return this.eval(reader, getRuntimeScope(context))\n    }\n\n    override fun eval(script: String, scope: Scriptable, coroutineContext: CoroutineContext?): Any? {\n        return this.eval(StringReader(script), scope, coroutineContext)\n    }\n\n    @Throws(ScriptException::class)\n    override fun eval(reader: Reader, bindings: Bindings): Any? {\n        return this.eval(reader, getScriptContext(bindings))\n    }\n\n    @Throws(ScriptException::class)\n    override fun eval(script: String, bindings: Bindings): Any? {\n        return this.eval(script, getScriptContext(bindings))\n    }\n\n    @Throws(ScriptException::class)\n    override fun eval(script: String, bindings: ScriptBindings): Any? {\n        return this.eval(script, getRuntimeScope(bindings))\n    }\n\n    @Throws(ScriptException::class)\n    override fun eval(reader: Reader): Any? {\n        return this.eval(reader, context)\n    }\n\n    @Throws(ScriptException::class)\n    override fun eval(script: String): Any? {\n        return this.eval(script, context)\n    }\n\n    @Throws(ScriptException::class)\n    override fun eval(script: String, context: ScriptContext): Any? {\n        return this.eval(StringReader(script), context)\n    }\n\n    override fun getScriptContext(bindings: Bindings): ScriptContext {\n        val ctx = SimpleScriptContext(bindings, context.errorWriter, context.reader, context.writer)\n        val gs = getBindings(GLOBAL_SCOPE)\n        if (gs != null) {\n            ctx.setBindings(gs, GLOBAL_SCOPE)\n        }\n        return ctx\n    }\n}\n"
  },
  {
    "path": "modules/rhino/src/main/java/com/script/Bindings.kt",
    "content": "/*\n * Decompiled with CFR 0.152.\n */\npackage com.script\n\ninterface Bindings : MutableMap<String, Any?> {\n\n    override fun containsKey(key: String): Boolean\n\n    override operator fun get(key: String): Any?\n\n    override fun put(key: String, value: Any?): Any?\n\n    override fun putAll(from: Map<out String, *>)\n\n    override fun remove(key: String): Any?\n\n}"
  },
  {
    "path": "modules/rhino/src/main/java/com/script/Compilable.kt",
    "content": "/*\n * Decompiled with CFR 0.152.\n */\npackage com.script\n\nimport java.io.Reader\n\ninterface Compilable {\n\n    @Throws(ScriptException::class)\n    fun compile(script: Reader): CompiledScript\n\n    @Throws(ScriptException::class)\n    fun compile(script: String): CompiledScript\n}"
  },
  {
    "path": "modules/rhino/src/main/java/com/script/CompiledScript.kt",
    "content": "/*\n * Decompiled with CFR 0.152.\n */\npackage com.script\n\nimport com.script.ScriptContext.Companion.ENGINE_SCOPE\nimport com.script.ScriptContext.Companion.GLOBAL_SCOPE\nimport org.mozilla.javascript.Scriptable\nimport kotlin.coroutines.CoroutineContext\n\nabstract class CompiledScript {\n\n    abstract fun getEngine(): ScriptEngine\n\n    @Throws(ScriptException::class)\n    fun eval(context: ScriptContext): Any? {\n        return eval(getEngine().getRuntimeScope(context))\n    }\n\n    @Throws(ScriptException::class)\n    fun eval(scope: Scriptable): Any? {\n        return eval(scope, null)\n    }\n\n    @Throws(ScriptException::class)\n    abstract fun eval(scope: Scriptable, coroutineContext: CoroutineContext?): Any?\n\n    @Throws(ScriptException::class)\n    abstract suspend fun evalSuspend(scope: Scriptable): Any?\n\n    @Throws(ScriptException::class)\n    fun eval(bindings: Bindings?): Any? {\n        var ctxt = getEngine().context\n        if (bindings != null) {\n            val tempContext = SimpleScriptContext()\n            tempContext.setBindings(bindings, ENGINE_SCOPE)\n            tempContext.setBindings(ctxt.getBindings(GLOBAL_SCOPE), GLOBAL_SCOPE)\n            tempContext.writer = ctxt.writer\n            tempContext.reader = ctxt.reader\n            tempContext.errorWriter = ctxt.errorWriter\n            ctxt = tempContext\n        }\n        return this.eval(ctxt)\n    }\n\n    @Throws(ScriptException::class)\n    fun eval(): Any? {\n        return this.eval(getEngine().context)\n    }\n}"
  },
  {
    "path": "modules/rhino/src/main/java/com/script/Invocable.kt",
    "content": "/*\n * Decompiled with CFR 0.152.\n */\npackage com.script\n\ninterface Invocable {\n    fun <T> getInterface(clazz: Class<T>): T?\n\n    fun <T> getInterface(obj: Any?, paramClass: Class<T>): T?\n\n    @Throws(ScriptException::class, NoSuchMethodException::class)\n    fun invokeFunction(name: String, vararg args: Any): Any?\n\n    @Throws(ScriptException::class, NoSuchMethodException::class)\n    fun invokeMethod(obj: Any?, name: String, vararg args: Any): Any?\n}"
  },
  {
    "path": "modules/rhino/src/main/java/com/script/RhinoContextFactory.kt",
    "content": "package com.script\n\nimport org.mozilla.javascript.ContextFactory\n\nopen class RhinoContextFactory : ContextFactory() {\n\n}"
  },
  {
    "path": "modules/rhino/src/main/java/com/script/ScriptBindings.kt",
    "content": "package com.script\n\nimport org.mozilla.javascript.Context\nimport org.mozilla.javascript.NativeObject\nimport org.mozilla.javascript.ScriptableObject\n\nclass ScriptBindings : NativeObject() {\n\n    companion object {\n        private val topLevelScope: ScriptableObject by lazy {\n            val cx = Context.enter()\n            try {\n                cx.initStandardObjects()\n            } finally {\n                Context.exit()\n            }\n        }\n    }\n\n    init {\n        prototype = topLevelScope\n    }\n\n    operator fun set(key: String, value: Any?) {\n        Context.enter()\n        try {\n            put(key, this, Context.javaToJS(value, this))\n        } finally {\n            Context.exit()\n        }\n    }\n\n    operator fun set(index: Int, value: Any?) {\n        Context.enter()\n        try {\n            put(index, this, Context.javaToJS(value, this))\n        } finally {\n            Context.exit()\n        }\n    }\n\n    fun put(key: String, value: Any?) {\n        set(key, value)\n    }\n\n}\n"
  },
  {
    "path": "modules/rhino/src/main/java/com/script/ScriptBindingsExtensions.kt",
    "content": "package com.script\n\nimport org.mozilla.javascript.Context\n\ninline fun buildScriptBindings(block: (bindings: ScriptBindings) -> Unit): ScriptBindings {\n    val bindings = ScriptBindings()\n    Context.enter()\n    try {\n        block(bindings)\n    } finally {\n        Context.exit()\n    }\n    return bindings\n}\n"
  },
  {
    "path": "modules/rhino/src/main/java/com/script/ScriptContext.kt",
    "content": "/*\n * Decompiled with CFR 0.152.\n */\npackage com.script\n\nimport java.io.Reader\nimport java.io.Writer\n\ninterface ScriptContext {\n\n    var errorWriter: Writer\n\n    var reader: Reader\n\n    val scopes: List<Int>\n\n    var writer: Writer\n\n    fun getAttribute(name: String): Any?\n\n    fun getAttribute(name: String, scope: Int): Any?\n\n    fun getAttributesScope(name: String): Int\n\n    fun getBindings(scope: Int): Bindings?\n\n    fun removeAttribute(name: String, scope: Int): Any?\n\n    fun setAttribute(name: String, value: Any?, scope: Int)\n\n    fun setBindings(bindings: Bindings?, scope: Int)\n\n    companion object {\n        const val ENGINE_SCOPE = 100\n        const val GLOBAL_SCOPE = 200\n    }\n}"
  },
  {
    "path": "modules/rhino/src/main/java/com/script/ScriptEngine.kt",
    "content": "/*\n * Decompiled with CFR 0.152.\n */\npackage com.script\n\nimport org.mozilla.javascript.Scriptable\nimport java.io.Reader\nimport kotlin.coroutines.CoroutineContext\n\ninterface ScriptEngine {\n    var context: ScriptContext\n\n    fun createBindings(): Bindings?\n\n    @Throws(ScriptException::class)\n    fun eval(reader: Reader, scope: Scriptable): Any?\n\n    @Throws(ScriptException::class)\n    fun eval(reader: Reader, scope: Scriptable, coroutineContext: CoroutineContext?): Any?\n\n    @Throws(ScriptException::class)\n    suspend fun evalSuspend(reader: Reader, scope: Scriptable): Any?\n\n    @Throws(ScriptException::class)\n    fun eval(script: String, scope: Scriptable): Any?\n\n    @Throws(ScriptException::class)\n    suspend fun evalSuspend(script: String, scope: Scriptable): Any?\n\n    @Throws(ScriptException::class)\n    fun eval(script: String, scope: Scriptable, coroutineContext: CoroutineContext?): Any?\n\n    @Throws(ScriptException::class)\n    fun eval(reader: Reader): Any?\n\n    @Throws(ScriptException::class)\n    fun eval(reader: Reader, bindings: Bindings): Any?\n\n    @Throws(ScriptException::class)\n    fun eval(reader: Reader, context: ScriptContext): Any?\n\n    @Throws(ScriptException::class)\n    fun eval(script: String): Any?\n\n    @Throws(ScriptException::class)\n    fun eval(script: String, bindings: Bindings): Any?\n\n    @Throws(ScriptException::class)\n    fun eval(script: String, bindings: ScriptBindings): Any?\n\n    @Throws(ScriptException::class)\n    fun eval(script: String, context: ScriptContext): Any?\n\n    fun getRuntimeScope(bindings: ScriptBindings): Scriptable\n\n    fun getRuntimeScope(context: ScriptContext): Scriptable\n\n    fun getScriptContext(bindings: Bindings): ScriptContext\n\n    operator fun get(key: String): Any?\n\n    fun getBindings(scope: Int): Bindings?\n\n    fun put(key: String, value: Any?)\n\n    fun setBindings(bindings: Bindings?, scope: Int)\n\n    companion object {\n        const val FILENAME = \"javax.script.filename\"\n    }\n}"
  },
  {
    "path": "modules/rhino/src/main/java/com/script/ScriptException.kt",
    "content": "/*\n * Decompiled with CFR 0.152.\n */\npackage com.script\n\nclass ScriptException : Exception {\n    var columnNumber: Int\n        private set\n    var fileName: String?\n        private set\n    var lineNumber: Int\n        private set\n\n    constructor(s: String?) : super(s) {\n        fileName = null\n        lineNumber = -1\n        columnNumber = -1\n    }\n\n    constructor(e: Exception?) : super(e) {\n        fileName = null\n        lineNumber = -1\n        columnNumber = -1\n    }\n\n    constructor(message: String?, fileName2: String?, lineNumber2: Int) : super(message) {\n        fileName = fileName2\n        lineNumber = lineNumber2\n        columnNumber = -1\n    }\n\n    constructor(message: String?, fileName2: String?, lineNumber2: Int, columnNumber2: Int) : super(\n        message\n    ) {\n        fileName = fileName2\n        lineNumber = lineNumber2\n        columnNumber = columnNumber2\n    }\n\n    override val message: String\n        get() {\n            val ret = super.message\n            if (fileName == null) {\n                return ret!!\n            }\n            var ret2 = \"$ret in $fileName\"\n            if (lineNumber != -1) {\n                ret2 = \"$ret2 at line number $lineNumber\"\n            }\n            return if (columnNumber != -1) {\n                \"$ret2 at column number $columnNumber\"\n            } else ret2\n        }\n}"
  },
  {
    "path": "modules/rhino/src/main/java/com/script/SimpleBindings.kt",
    "content": "/*\n * Decompiled with CFR 0.152.\n */\npackage com.script\n\nclass SimpleBindings @JvmOverloads constructor(\n    private val map: MutableMap<String, Any?> = HashMap()\n) : Bindings {\n\n    override fun put(key: String, value: Any?): Any? {\n        return map.put(key, value)\n    }\n\n    override fun putAll(from: Map<out String, Any?>) {\n        map.putAll(from)\n    }\n\n    override fun clear() {\n        map.clear()\n    }\n\n    override fun containsKey(key: String): Boolean {\n        return map.containsKey(key)\n    }\n\n    override fun containsValue(value: Any?): Boolean {\n        return map.containsValue(value)\n    }\n\n    override val entries: MutableSet<MutableMap.MutableEntry<String, Any?>>\n        get() = map.entries\n\n    override operator fun get(key: String): Any? {\n        return map[key]\n    }\n\n    override fun isEmpty(): Boolean {\n        return map.isEmpty()\n    }\n\n    override val keys: MutableSet<String>\n        get() = map.keys\n\n    override fun remove(key: String): Any? {\n        return map.remove(key)\n    }\n\n    override val size: Int\n        get() = map.size\n\n    override val values: MutableCollection<Any?>\n        get() = map.values\n\n}"
  },
  {
    "path": "modules/rhino/src/main/java/com/script/SimpleScriptContext.kt",
    "content": "/*\n * Decompiled with CFR 0.152.\n */\npackage com.script\n\nimport com.script.ScriptContext.Companion.ENGINE_SCOPE\nimport com.script.ScriptContext.Companion.GLOBAL_SCOPE\nimport java.io.InputStreamReader\nimport java.io.PrintWriter\nimport java.io.Reader\nimport java.io.Writer\n\nopen class SimpleScriptContext(\n    private var engineScope: Bindings = SimpleBindings(),\n    override var errorWriter: Writer = PrintWriter(System.err, true),\n    override var reader: Reader = InputStreamReader(System.`in`),\n    override var writer: Writer = PrintWriter(System.out, true)\n) : ScriptContext {\n    private var globalScope: Bindings? = null\n\n    override fun setBindings(bindings: Bindings?, scope: Int) {\n        when (scope) {\n            ENGINE_SCOPE -> {\n                if (bindings == null) {\n                    throw NullPointerException(\"Engine scope cannot be null.\")\n                }\n                engineScope = bindings\n                return\n            }\n\n            GLOBAL_SCOPE -> {\n                globalScope = bindings\n                return\n            }\n        }\n        throw IllegalArgumentException(\"Invalid scope value.\")\n    }\n\n    override fun getAttribute(name: String): Any? {\n        return if (engineScope.containsKey(name)) {\n            this.getAttribute(name, ENGINE_SCOPE)\n        } else if (globalScope?.containsKey(name) == true) {\n            this.getAttribute(name, GLOBAL_SCOPE)\n        } else {\n            null\n        }\n    }\n\n    override fun getAttribute(name: String, scope: Int): Any? {\n        when (scope) {\n            ENGINE_SCOPE -> {\n                return engineScope[name]\n            }\n\n            GLOBAL_SCOPE -> {\n                return globalScope?.get(name)\n            }\n        }\n        throw IllegalArgumentException(\"Illegal scope value.\")\n    }\n\n    override fun removeAttribute(name: String, scope: Int): Any? {\n        when (scope) {\n            ENGINE_SCOPE -> {\n                return getBindings(ENGINE_SCOPE)?.remove(name)\n            }\n\n            GLOBAL_SCOPE -> {\n                return getBindings(GLOBAL_SCOPE)?.remove(name)\n            }\n        }\n        throw IllegalArgumentException(\"Illegal scope value.\")\n    }\n\n    override fun setAttribute(name: String, value: Any?, scope: Int) {\n        when (scope) {\n            ENGINE_SCOPE -> engineScope[name] = value\n            GLOBAL_SCOPE -> globalScope?.put(name, value)\n            else -> throw IllegalArgumentException(\"Illegal scope value.\")\n        }\n    }\n\n    override fun getAttributesScope(name: String): Int {\n        return if (engineScope.containsKey(name)) {\n            ENGINE_SCOPE\n        } else if (globalScope?.containsKey(name) == true) {\n            GLOBAL_SCOPE\n        } else {\n            -1\n        }\n    }\n\n    override fun getBindings(scope: Int): Bindings? {\n        if (scope == ENGINE_SCOPE) {\n            return engineScope\n        }\n        if (scope == GLOBAL_SCOPE) {\n            return globalScope\n        }\n        throw IllegalArgumentException(\"Illegal scope value.\")\n    }\n\n    override val scopes: List<Int>\n        get() = Companion.scopes\n\n    companion object {\n        private val scopes = listOf(ENGINE_SCOPE, GLOBAL_SCOPE)\n    }\n}"
  },
  {
    "path": "modules/rhino/src/main/java/com/script/rhino/ClassNameMatcher.kt",
    "content": "package com.script.rhino\n\nimport androidx.collection.LruCache\nimport kotlin.math.min\n\nclass ClassNameMatcher(classNames: List<String>) {\n\n    private val sortedClassNames = classNames.sorted()\n    private val matchCache = LruCache<String, Boolean>(64)\n\n    fun match(className: String): Boolean {\n        matchCache[className]?.let {\n            return it\n        }\n        val match = matchInternal(className)\n        matchCache.put(className, match)\n        return match\n    }\n\n    private fun matchInternal(className: String): Boolean {\n        val index = sortedClassNames.fastBinarySearch { prefix ->\n            comparePrefix(className, prefix)\n        }\n        if (index >= 0) {\n            return true\n        }\n        val prefix = sortedClassNames.getOrNull(-index - 2) ?: return false\n        return className.getOrNull(prefix.length) == '.' && className.startsWith(prefix)\n    }\n\n    private fun comparePrefix(className: String, prefix: String): Int {\n        val len = min(className.length, prefix.length)\n        for (i in 0 ..< len) {\n            val c1 = className[i]\n            val c2 = prefix[i]\n            if (c1 != c2) return c2 - c1\n        }\n        return prefix.length - className.length\n    }\n\n}\n"
  },
  {
    "path": "modules/rhino/src/main/java/com/script/rhino/CollectionExtensions.kt",
    "content": "package com.script.rhino\n\ninline fun <T> List<T>.fastBinarySearch(\n    comparison: (T) -> Int\n): Int {\n    var low = 0\n    var high = lastIndex\n\n    while (low <= high) {\n        val mid = (low + high).ushr(1) // safe from overflows\n        val midVal = get(mid)\n        val cmp = comparison(midVal)\n\n        if (cmp < 0)\n            low = mid + 1\n        else if (cmp > 0)\n            high = mid - 1\n        else\n            return mid // key found\n    }\n    return -(low + 1)  // key not found\n}\n"
  },
  {
    "path": "modules/rhino/src/main/java/com/script/rhino/ExternalScriptable.kt",
    "content": "/*\n * Copyright (c) 2005, 2006, Oracle and/or its affiliates. All rights reserved.\n * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.\n *\n * This code is free software; you can redistribute it and/or modify it\n * under the terms of the GNU General Public License version 2 only, as\n * published by the Free Software Foundation.  Oracle designates this\n * particular file as subject to the \"Classpath\" exception as provided\n * by Oracle in the LICENSE file that accompanied this code.\n *\n * This code is distributed in the hope that it will be useful, but WITHOUT\n * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or\n * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License\n * version 2 for more details (a copy is included in the LICENSE file that\n * accompanied this code).\n *\n * You should have received a copy of the GNU General Public License version\n * 2 along with this work; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.\n *\n * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA\n * or visit www.oracle.com if you need additional information or have any\n * questions.\n */\npackage com.script.rhino\n\nimport com.script.ScriptContext\nimport org.mozilla.javascript.*\nimport org.mozilla.javascript.Function\n\n/**\n * ExternalScriptable is an implementation of Scriptable\n * backed by a JSR 223 ScriptContext instance.\n *\n * @author Mike Grogan\n * @author A. Sundararajan\n * @since 1.6\n */\ninternal class ExternalScriptable @JvmOverloads constructor(\n    val context: ScriptContext,\n    private val indexedProps: MutableMap<Any, Any?> = HashMap()\n) : Scriptable {\n    private var prototype: Scriptable? = null\n    private var parent: Scriptable? = null\n\n    private fun isEmpty(name: String): Boolean {\n        return name == \"\"\n    }\n\n    override fun getClassName(): String {\n        return \"Global\"\n    }\n\n    @Synchronized\n    override fun get(name: String, start: Scriptable): Any? {\n        return if (this.isEmpty(name)) {\n            indexedProps.getOrElse(name) { Scriptable.NOT_FOUND }\n        } else {\n            synchronized(context) {\n                val scope = context.getAttributesScope(name)\n                return if (scope != -1) {\n                    val value = context.getAttribute(name, scope)\n                    Context.javaToJS(value, this)\n                } else {\n                    Scriptable.NOT_FOUND\n                }\n            }\n        }\n    }\n\n    @Synchronized\n    override fun get(index: Int, start: Scriptable): Any? {\n        return indexedProps.getOrElse(index) { Scriptable.NOT_FOUND }\n    }\n\n    @Synchronized\n    override fun has(name: String, start: Scriptable): Boolean {\n        return if (this.isEmpty(name)) {\n            indexedProps.containsKey(name)\n        } else {\n            synchronized(context) { return context.getAttributesScope(name) != -1 }\n        }\n    }\n\n    @Synchronized\n    override fun has(index: Int, start: Scriptable): Boolean {\n        return indexedProps.containsKey(index)\n    }\n\n    override fun put(name: String, start: Scriptable, value: Any?) {\n        if (start === this) {\n            synchronized(this) {\n                if (this.isEmpty(name)) {\n                    indexedProps.put(name, value)\n                } else {\n                    synchronized(context) {\n                        var scope = context.getAttributesScope(name)\n                        if (scope == -1) {\n                            scope = 100\n                        }\n                        context.setAttribute(name, jsToJava(value), scope)\n                    }\n                }\n            }\n        } else {\n            start.put(name, start, value)\n        }\n    }\n\n    override fun put(index: Int, start: Scriptable, value: Any?) {\n        if (start === this) {\n            synchronized(this) { indexedProps.put(index, value) }\n        } else {\n            start.put(index, start, value)\n        }\n    }\n\n    @Synchronized\n    override fun delete(name: String) {\n        if (this.isEmpty(name)) {\n            indexedProps.remove(name)\n        } else {\n            synchronized(context) {\n                val scope = context.getAttributesScope(name)\n                if (scope != -1) {\n                    context.removeAttribute(name, scope)\n                }\n            }\n        }\n    }\n\n    override fun delete(index: Int) {\n        indexedProps.remove(index)\n    }\n\n    override fun getPrototype(): Scriptable? {\n        return prototype\n    }\n\n    override fun setPrototype(prototype: Scriptable?) {\n        this.prototype = prototype\n    }\n\n    override fun getParentScope(): Scriptable? {\n        return parent\n    }\n\n    override fun setParentScope(parent: Scriptable?) {\n        this.parent = parent\n    }\n\n    @Synchronized\n    override fun getIds(): Array<Any> {\n        val keys = allKeys\n        val size = keys.size + indexedProps.size\n        val res = arrayOfNulls<Any>(size)\n        System.arraycopy(keys, 0, res, 0, keys.size)\n        var i = keys.size\n        var index: Any\n        val var5: Iterator<*> = indexedProps.keys.iterator()\n        while (var5.hasNext()) {\n            index = var5.next()!!\n            res[i++] = index\n        }\n        @Suppress(\"UNCHECKED_CAST\")\n        return res as Array<Any>\n    }\n\n    override fun getDefaultValue(typeHint: Class<*>?): Any {\n        for (i in 0..1) {\n            val tryToString: Boolean =\n                if (typeHint == ScriptRuntime.StringClass) {\n                    i == 0\n                } else {\n                    i == 1\n                }\n            var methodName: String\n            var args: Array<Any?>\n            if (tryToString) {\n                methodName = \"toString\"\n                args = ScriptRuntime.emptyArgs\n            } else {\n                methodName = \"valueOf\"\n                args = arrayOfNulls(1)\n                val hint: String = when {\n                    typeHint == null -> {\n                        \"undefined\"\n                    }\n                    typeHint == ScriptRuntime.StringClass -> {\n                        \"string\"\n                    }\n                    typeHint == ScriptRuntime.ScriptableClass -> {\n                        \"object\"\n                    }\n                    typeHint == ScriptRuntime.FunctionClass -> {\n                        \"function\"\n                    }\n                    typeHint != ScriptRuntime.BooleanClass && typeHint != java.lang.Boolean.TYPE -> {\n                        if (typeHint != ScriptRuntime.NumberClass\n                            && typeHint != ScriptRuntime.ByteClass\n                            && typeHint != java.lang.Byte.TYPE\n                            && typeHint != ScriptRuntime.ShortClass\n                            && typeHint != java.lang.Short.TYPE\n                            && typeHint != ScriptRuntime.IntegerClass\n                            && typeHint != Integer.TYPE\n                            && typeHint != ScriptRuntime.FloatClass\n                            && typeHint != java.lang.Float.TYPE\n                            && typeHint != ScriptRuntime.DoubleClass\n                            && typeHint != java.lang.Double.TYPE\n                        ) {\n                            throw Context.reportRuntimeError(\"Invalid JavaScript value of type $typeHint\")\n                        }\n                        \"number\"\n                    }\n                    else -> {\n                        \"boolean\"\n                    }\n                }\n                args[0] = hint\n            }\n            var v = ScriptableObject.getProperty(this, methodName)\n            if (v is Function) {\n                val function = v\n                val cx = Context.enter()\n                v = try {\n                    function.call(cx, function.parentScope, this, args)\n                } finally {\n                    Context.exit()\n                }\n                if (v != null) {\n                    if (v !is Scriptable) {\n                        return v\n                    }\n                    if (typeHint == ScriptRuntime.ScriptableClass || typeHint == ScriptRuntime.FunctionClass) {\n                        return v\n                    }\n                    if (tryToString && v is Wrapper) {\n                        val u = (v as Wrapper).unwrap()\n                        if (u is String) {\n                            return u\n                        }\n                    }\n                }\n            }\n        }\n        val arg = if (typeHint == null) \"undefined\" else typeHint.name\n        throw Context.reportRuntimeError(\"找不到对象的默认值 $arg\")\n    }\n\n    override fun hasInstance(instance: Scriptable): Boolean {\n        var proto = instance.prototype\n        while (proto != null) {\n            if (proto == this) {\n                return true\n            }\n            proto = proto.prototype\n        }\n        return false\n    }\n\n    private val allKeys: Array<String>\n        get() {\n            val list = ArrayList<String>()\n            synchronized(context) {\n                val iterator: Iterator<*> = context.scopes.iterator()\n                while (iterator.hasNext()) {\n                    val scope = iterator.next() as Int\n                    val bindings = context.getBindings(scope)\n                    if (bindings != null) {\n                        list.ensureCapacity(bindings.size)\n                        val iterator1: Iterator<*> = bindings.keys.iterator()\n                        while (iterator1.hasNext()) {\n                            val key = iterator1.next() as String\n                            list.add(key)\n                        }\n                    }\n                }\n            }\n            return list.toTypedArray()\n        }\n\n    private fun jsToJava(jsObj: Any?): Any? {\n        return if (jsObj is Wrapper) {\n            if (jsObj is NativeJavaClass) {\n                jsObj\n            } else {\n                val obj = jsObj.unwrap()\n                if (obj !is Number && obj !is String && obj !is Boolean && obj !is Char) obj else jsObj\n            }\n        } else {\n            jsObj\n        }\n    }\n}"
  },
  {
    "path": "modules/rhino/src/main/java/com/script/rhino/InterfaceImplementor.kt",
    "content": "/*\n * Copyright (c) 2005, 2011, Oracle and/or its affiliates. All rights reserved.\n * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.\n *\n * This code is free software; you can redistribute it and/or modify it\n * under the terms of the GNU General Public License version 2 only, as\n * published by the Free Software Foundation.  Oracle designates this\n * particular file as subject to the \"Classpath\" exception as provided\n * by Oracle in the LICENSE file that accompanied this code.\n *\n * This code is distributed in the hope that it will be useful, but WITHOUT\n * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or\n * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License\n * version 2 for more details (a copy is included in the LICENSE file that\n * accompanied this code).\n *\n * You should have received a copy of the GNU General Public License version\n * 2 along with this work; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.\n *\n * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA\n * or visit www.oracle.com if you need additional information or have any\n * questions.\n */\npackage com.script.rhino\n\nimport com.script.Invocable\nimport com.script.ScriptException\nimport java.lang.reflect.InvocationHandler\nimport java.lang.reflect.Method\nimport java.lang.reflect.Proxy\nimport java.security.AccessControlContext\nimport java.security.AccessController\nimport java.security.PrivilegedExceptionAction\n\n/**\n * java.lang.reflect.Proxy based interface implementor. This is meant\n * to be used to implement Invocable.getInterface.\n *\n * @author Mike Grogan\n * @since 1.6\n */\n@Suppress(\"UNUSED_PARAMETER\")\nopen class InterfaceImplementor(private val engine: Invocable) {\n    @Throws(ScriptException::class)\n    fun <T> getInterface(obj: Any?, clazz: Class<T>?): T? {\n        return if (clazz != null && clazz.isInterface) {\n            if (!isImplemented(obj, clazz)) {\n                null\n            } else {\n                val accContext = AccessController.getContext()\n                clazz.cast(\n                    Proxy.newProxyInstance(\n                        clazz.classLoader,\n                        arrayOf<Class<*>>(clazz),\n                        InterfaceImplementorInvocationHandler(obj, accContext)\n                    )\n                )\n            }\n        } else {\n            throw IllegalArgumentException(\"interface Class expected\")\n        }\n    }\n\n    protected open fun isImplemented(obj: Any?, clazz: Class<*>): Boolean {\n        return true\n    }\n\n    @Throws(ScriptException::class)\n    protected open fun convertResult(method: Method?, res: Any?): Any? {\n        return res\n    }\n\n    @Throws(ScriptException::class)\n    protected fun convertArguments(method: Method?, args: Array<Any>): Array<Any> {\n        return args\n    }\n\n    private inner class InterfaceImplementorInvocationHandler(\n        private val obj: Any?,\n        private val accContext: AccessControlContext\n    ) : InvocationHandler {\n\n        @Throws(Throwable::class)\n        override fun invoke(proxy: Any, method: Method, args: Array<Any>): Any? {\n            val finalArgs = convertArguments(method, args)\n            val result = AccessController.doPrivileged(PrivilegedExceptionAction {\n                if (obj == null) engine.invokeFunction(method.name, *finalArgs)\n                else engine.invokeMethod(obj, method.name, *finalArgs)\n            }, accContext)\n            return convertResult(method, result)\n        }\n\n    }\n}"
  },
  {
    "path": "modules/rhino/src/main/java/com/script/rhino/JSAdapter.kt",
    "content": "/*\n * Copyright (c) 2005, Oracle and/or its affiliates. All rights reserved.\n * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.\n *\n * This code is free software; you can redistribute it and/or modify it\n * under the terms of the GNU General Public License version 2 only, as\n * published by the Free Software Foundation.  Oracle designates this\n * particular file as subject to the \"Classpath\" exception as provided\n * by Oracle in the LICENSE file that accompanied this code.\n *\n * This code is distributed in the hope that it will be useful, but WITHOUT\n * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or\n * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License\n * version 2 for more details (a copy is included in the LICENSE file that\n * accompanied this code).\n *\n * You should have received a copy of the GNU General Public License version\n * 2 along with this work; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.\n *\n * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA\n * or visit www.oracle.com if you need additional information or have any\n * questions.\n */\npackage com.script.rhino\n\nimport org.mozilla.javascript.*\nimport org.mozilla.javascript.Function\n\n/**\n * JSAdapter is java.lang.reflect.Proxy equivalent for JavaScript. JSAdapter\n * calls specially named JavaScript methods on an adaptee object when property\n * access is attempted on it.\n *\n * Example:\n *\n * var y = {\n * __get__    : function (name) { ... }\n * __has__    : function (name) { ... }\n * __put__    : function (name, value) {...}\n * __delete__ : function (name) { ... }\n * __getIds__ : function () { ... }\n * };\n *\n * var x = new JSAdapter(y);\n *\n * x.i;                        // calls y.__get__\n * i in x;                     // calls y.__has__\n * x.p = 10;                   // calls y.__put__\n * delete x.p;                 // calls y.__delete__\n * for (i in x) { print(i); }  // calls y.__getIds__\n *\n * If a special JavaScript method is not found in the adaptee, then JSAdapter\n * forwards the property access to the adaptee itself.\n *\n * JavaScript caller of adapter object is isolated from the fact that\n * the property access/mutation/deletion are really calls to\n * JavaScript methods on adaptee.  Use cases include 'smart'\n * properties, property access tracing/debugging, encaptulation with\n * easy client access - in short JavaScript becomes more \"Self\" like.\n *\n * Note that Rhino already supports special properties like __proto__\n * (to set, get prototype), __parent__ (to set, get parent scope). We\n * follow the same double underscore nameing convention here. Similarly\n * the name JSAdapter is derived from JavaAdapter -- which is a facility\n * to extend, implement Java classes/interfaces by JavaScript.\n *\n * @author A. Sundararajan\n * @since 1.6\n */\n@Suppress(\"unused\", \"UNUSED_PARAMETER\")\nclass JSAdapter private constructor(var adaptee: Scriptable) : Scriptable, Function {\n    private var prototype: Scriptable? = null\n    private var parent: Scriptable? = null\n    private var isPrototype = false\n\n    override fun getClassName(): String {\n        return \"JSAdapter\"\n    }\n\n    override fun get(name: String, start: Scriptable): Any {\n        val func = getAdapteeFunction(GET_PROP)\n        return if (func != null) {\n            this.call(func, arrayOf(name))\n        } else {\n            adaptee[name, adaptee]\n        }\n    }\n\n    override fun get(index: Int, start: Scriptable): Any {\n        val func = getAdapteeFunction(GET_PROP)\n        return if (func != null) {\n            this.call(func, arrayOf(index))\n        } else {\n            adaptee[index, adaptee]\n        }\n    }\n\n    override fun has(name: String, start: Scriptable): Boolean {\n        val func = getAdapteeFunction(HAS_PROP)\n        return if (func != null) {\n            val res = this.call(func, arrayOf(name))\n            Context.toBoolean(res)\n        } else {\n            adaptee.has(name, adaptee)\n        }\n    }\n\n    override fun has(index: Int, start: Scriptable): Boolean {\n        val func = getAdapteeFunction(HAS_PROP)\n        return if (func != null) {\n            val res = this.call(func, arrayOf(index))\n            Context.toBoolean(res)\n        } else {\n            adaptee.has(index, adaptee)\n        }\n    }\n\n    override fun put(name: String, start: Scriptable, value: Any) {\n        if (start === this) {\n            val func = getAdapteeFunction(PUT_PROP)\n            if (func != null) {\n                this.call(func, arrayOf(name, value))\n            } else {\n                adaptee.put(name, adaptee, value)\n            }\n        } else {\n            start.put(name, start, value)\n        }\n    }\n\n    override fun put(index: Int, start: Scriptable, value: Any) {\n        if (start === this) {\n            val func = getAdapteeFunction(PUT_PROP)\n            if (func != null) {\n                this.call(func, arrayOf(index, value))\n            } else {\n                adaptee.put(index, adaptee, value)\n            }\n        } else {\n            start.put(index, start, value)\n        }\n    }\n\n    override fun delete(name: String) {\n        val func = getAdapteeFunction(DEL_PROP)\n        if (func != null) {\n            this.call(func, arrayOf(name))\n        } else {\n            adaptee.delete(name)\n        }\n    }\n\n    override fun delete(index: Int) {\n        val func = getAdapteeFunction(DEL_PROP)\n        if (func != null) {\n            this.call(func, arrayOf(index))\n        } else {\n            adaptee.delete(index)\n        }\n    }\n\n    override fun getPrototype(): Scriptable? {\n        return prototype\n    }\n\n    override fun setPrototype(prototype: Scriptable?) {\n        this.prototype = prototype\n    }\n\n    override fun getParentScope(): Scriptable? {\n        return parent\n    }\n\n    override fun setParentScope(parent: Scriptable?) {\n        this.parent = parent\n    }\n\n    override fun getIds(): Array<Any?> {\n        val func = getAdapteeFunction(GET_PROPIDS)\n        return if (func == null) {\n            adaptee.ids\n        } else {\n            val val1 = this.call(func, arrayOfNulls(0))\n            val res: Array<Any?>\n            when (val1) {\n                is NativeArray -> {\n                    res = arrayOfNulls(val1.length.toInt())\n                    for (index in res.indices) {\n                        res[index] = mapToId(val1[index, val1])\n                    }\n                    res\n                }\n                !is NativeJavaArray -> {\n                    Context.emptyArgs\n                }\n                else -> {\n                    val tmp = val1.unwrap()\n                    if (tmp.javaClass == Array<Any>::class.java) {\n                        val array = tmp as Array<*>\n                        res = arrayOfNulls(array.size)\n                        for (index in array.indices) {\n                            res[index] = mapToId(array[index])\n                        }\n                    } else {\n                        res = Context.emptyArgs\n                    }\n                    res\n                }\n            }\n        }\n    }\n\n    override fun hasInstance(scriptable: Scriptable): Boolean {\n        return if (scriptable is JSAdapter) {\n            true\n        } else {\n            var proto = scriptable.prototype\n            while (proto != null) {\n                if (proto == this) {\n                    return true\n                }\n                proto = proto.prototype\n            }\n            false\n        }\n    }\n\n    override fun getDefaultValue(hint: Class<*>?): Any {\n        return adaptee.getDefaultValue(hint)\n    }\n\n    @Throws(RhinoException::class)\n    override fun call(cx: Context, scope: Scriptable, thisObj: Scriptable, args: Array<Any>): Any {\n        return if (isPrototype) {\n            construct(cx, scope, args)\n        } else {\n            val tmp = adaptee\n            if (tmp is Function) {\n                tmp.call(cx, scope, tmp, args)\n            } else {\n                throw Context.reportRuntimeError(\"TypeError: not a function\")\n            }\n        }\n    }\n\n    @Throws(RhinoException::class)\n    override fun construct(cx: Context, scope: Scriptable, args: Array<Any>): Scriptable {\n        val tmp: Scriptable?\n        return if (isPrototype) {\n            tmp = ScriptableObject.getTopLevelScope(scope)\n            if (args.isNotEmpty()) {\n                JSAdapter(Context.toObject(args[0], tmp))\n            } else {\n                throw Context.reportRuntimeError(\"JSAdapter requires adaptee\")\n            }\n        } else {\n            tmp = adaptee\n            if (tmp is Function) {\n                tmp.construct(cx, scope, args)\n            } else {\n                throw Context.reportRuntimeError(\"TypeError: not a constructor\")\n            }\n        }\n    }\n\n    private fun mapToId(tmp: Any?): Any {\n        return if (tmp is Double) tmp.toInt() else Context.toString(tmp)\n    }\n\n    private fun getAdapteeFunction(name: String): Function? {\n        val o = ScriptableObject.getProperty(adaptee, name)\n        return o as? Function\n    }\n\n    private fun call(func: Function, args: Array<Any?>): Any {\n        val cx = Context.getCurrentContext()\n        val thisObj = adaptee\n        val scope = func.parentScope\n        return try {\n            func.call(cx, scope, thisObj, args)\n        } catch (re: RhinoException) {\n            throw Context.reportRuntimeError(re.message)\n        }\n    }\n\n    companion object {\n        private const val GET_PROP = \"__get__\"\n        private const val HAS_PROP = \"__has__\"\n        private const val PUT_PROP = \"__put__\"\n        private const val DEL_PROP = \"__delete__\"\n        private const val GET_PROPIDS = \"__getIds__\"\n\n        @Throws(RhinoException::class)\n        fun init(cx: Context, scope: Scriptable, sealed: Boolean) {\n            val obj = JSAdapter(cx.newObject(scope))\n            obj.parentScope = scope\n            obj.setPrototype(getFunctionPrototype(scope))\n            obj.isPrototype = true\n            ScriptableObject.defineProperty(scope, \"JSAdapter\", obj, 2)\n        }\n\n        private fun getFunctionPrototype(scope: Scriptable): Scriptable {\n            return ScriptableObject.getFunctionPrototype(scope)\n        }\n    }\n}"
  },
  {
    "path": "modules/rhino/src/main/java/com/script/rhino/JavaAdapter.kt",
    "content": "/*\n * Copyright (c) 2005, 2006, Oracle and/or its affiliates. All rights reserved.\n * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.\n *\n * This code is free software; you can redistribute it and/or modify it\n * under the terms of the GNU General Public License version 2 only, as\n * published by the Free Software Foundation.  Oracle designates this\n * particular file as subject to the \"Classpath\" exception as provided\n * by Oracle in the LICENSE file that accompanied this code.\n *\n * This code is distributed in the hope that it will be useful, but WITHOUT\n * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or\n * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License\n * version 2 for more details (a copy is included in the LICENSE file that\n * accompanied this code).\n *\n * You should have received a copy of the GNU General Public License version\n * 2 along with this work; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.\n *\n * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA\n * or visit www.oracle.com if you need additional information or have any\n * questions.\n */\npackage com.script.rhino\n\nimport com.script.Invocable\nimport org.mozilla.javascript.*\nimport org.mozilla.javascript.Function\n\n/**\n * This class implements Rhino-like JavaAdapter to help implement a Java\n * interface in JavaScript. We support this using Invocable.getInterface.\n * Using this JavaAdapter, script author could write:\n *\n *\n * var r = new java.lang.Runnable() {\n * run: function() { script... }\n * };\n *\n *\n * r.run();\n * new java.lang.Thread(r).start();\n *\n *\n * Note that Rhino's JavaAdapter support allows extending a Java class and/or\n * implementing one or more interfaces. This JavaAdapter implementation does\n * not support these.\n *\n * @author A. Sundararajan\n * @since 1.6\n */\n@Suppress(\"UNUSED_PARAMETER\")\ninternal class JavaAdapter private constructor(private val engine: Invocable) : ScriptableObject(),\n    Function {\n    override fun getClassName(): String {\n        return \"JavaAdapter\"\n    }\n\n    @Throws(RhinoException::class)\n    override fun call(cx: Context, scope: Scriptable, thisObj: Scriptable, args: Array<Any>): Any {\n        return construct(cx, scope, args)\n    }\n\n    @Throws(RhinoException::class)\n    override fun construct(cx: Context, scope: Scriptable, args: Array<Any>): Scriptable {\n        return if (args.size == 2) {\n            var clazz: Class<*>? = null\n            val obj = args[0]\n            if (obj is Wrapper) {\n                val o = obj.unwrap()\n                if (o is Class<*> && o.isInterface) {\n                    clazz = o\n                }\n            } else if (obj is Class<*> && obj.isInterface) {\n                clazz = obj\n            }\n            if (clazz == null) {\n                throw Context.reportRuntimeError(\"JavaAdapter: first arg should be interface Class\")\n            } else {\n                val topLevel = getTopLevelScope(scope)\n                Context.toObject(\n                    engine.getInterface(args[1], clazz),\n                    topLevel\n                )\n            }\n        } else {\n            throw Context.reportRuntimeError(\"JavaAdapter requires two arguments\")\n        }\n    }\n\n    companion object {\n        @JvmStatic\n        @Throws(RhinoException::class)\n        fun init(cx: Context, scope: Scriptable, sealed: Boolean) {\n            val topLevel = scope as RhinoTopLevel\n            val engine: Invocable = topLevel.scriptEngine\n            val obj = JavaAdapter(engine)\n            obj.parentScope = scope\n            obj.prototype = getFunctionPrototype(scope)\n            putProperty(topLevel, \"JavaAdapter\", obj)\n        }\n    }\n}"
  },
  {
    "path": "modules/rhino/src/main/java/com/script/rhino/JavaObjectWrapFactory.kt",
    "content": "package com.script.rhino\n\nimport org.mozilla.javascript.Scriptable\n\nfun interface JavaObjectWrapFactory {\n\n    fun wrap(scope: Scriptable?, javaObject: Any, staticType: Class<*>?): Scriptable\n\n}\n"
  },
  {
    "path": "modules/rhino/src/main/java/com/script/rhino/ProtectedNativeJavaClass.kt",
    "content": "package com.script.rhino\n\nimport org.mozilla.javascript.NativeJavaClass\nimport org.mozilla.javascript.Scriptable\n\nclass ProtectedNativeJavaClass(\n    scope: Scriptable,\n    javaClass: Class<*>,\n    private val protectedName: Set<String> = emptySet()\n) : NativeJavaClass(scope, javaClass) {\n\n    override fun has(\n        name: String,\n        start: Scriptable?\n    ): Boolean {\n        if (protectedName.contains(name)) {\n            return false\n        }\n        return super.has(name, start)\n    }\n\n    override fun get(name: String, start: Scriptable?): Any? {\n        if (protectedName.contains(name)) {\n            return NOT_FOUND\n        }\n        return super.get(name, start)\n    }\n\n    override fun put(\n        name: String,\n        start: Scriptable?,\n        value: Any?\n    ) {\n        if (protectedName.contains(name)) {\n            return\n        }\n        super.put(name, start, value)\n    }\n\n    override fun unwrap(): Any? {\n        return javaObject.toString()\n    }\n\n}\n"
  },
  {
    "path": "modules/rhino/src/main/java/com/script/rhino/ReadOnlyJavaObject.kt",
    "content": "package com.script.rhino\n\nimport org.mozilla.javascript.NativeJavaObject\nimport org.mozilla.javascript.Scriptable\n\nclass ReadOnlyJavaObject(scope: Scriptable?, javaObject: Any, staticType: Class<*>?) :\n    NativeJavaObject(scope, javaObject, staticType) {\n\n    override fun has(name: String, start: Scriptable): Boolean {\n        if (name.length > 3 && name.startsWith(\"set\")) {\n            val name = name.substring(3).replaceFirstChar { it.lowercase() }\n            if (super.has(name, start)) {\n                return false\n            }\n        }\n        return super.has(name, start)\n    }\n\n    override fun get(name: String, start: Scriptable): Any? {\n        if (name.length > 3 && name.startsWith(\"set\")) {\n            val name = name.substring(3).replaceFirstChar { it.lowercase() }\n            if (super.has(name, start)) {\n                return NOT_FOUND\n            }\n        }\n        return super.get(name, start)\n    }\n\n    override fun put(\n        name: String?,\n        start: Scriptable?,\n        value: Any?\n    ) {\n        // do nothing\n    }\n\n    companion object {\n        val factory = JavaObjectWrapFactory { scope, javaObject, staticType ->\n            ReadOnlyJavaObject(scope, javaObject, staticType)\n        }\n    }\n\n}\n"
  },
  {
    "path": "modules/rhino/src/main/java/com/script/rhino/RhinoClassShutter.kt",
    "content": "/*\n * Copyright (c) 2005, Oracle and/or its affiliates. All rights reserved.\n * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.\n *\n * This code is free software; you can redistribute it and/or modify it\n * under the terms of the GNU General Public License version 2 only, as\n * published by the Free Software Foundation.  Oracle designates this\n * particular file as subject to the \"Classpath\" exception as provided\n * by Oracle in the LICENSE file that accompanied this code.\n *\n * This code is distributed in the hope that it will be useful, but WITHOUT\n * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or\n * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License\n * version 2 for more details (a copy is included in the LICENSE file that\n * accompanied this code).\n *\n * You should have received a copy of the GNU General Public License version\n * 2 along with this work; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.\n *\n * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA\n * or visit www.oracle.com if you need additional information or have any\n * questions.\n */\npackage com.script.rhino\n\nimport android.os.Build\nimport org.mozilla.javascript.ClassShutter\nimport org.mozilla.javascript.Context\nimport org.mozilla.javascript.Scriptable\nimport java.io.ObjectInputStream\nimport java.io.ObjectOutputStream\nimport java.lang.reflect.Member\nimport java.nio.file.FileSystem\nimport java.nio.file.Path\nimport java.util.Collections\n\n/**\n * This class prevents script access to certain sensitive classes.\n * Note that this class checks over and above SecurityManager. i.e., although\n * a SecurityManager would pass, class shutter may still prevent access.\n *\n * @author A. Sundararajan\n * @since 1.6\n */\nobject RhinoClassShutter : ClassShutter {\n\n    private val protectedClassNamesMatcher by lazy {\n        listOf(\n            \"java.lang.Class\",\n            \"java.lang.ClassLoader\",\n            \"java.net.URLClassLoader\",\n            \"java.lang.Runtime\",\n            \"java.lang.ProcessBuilder\",\n            \"java.lang.ProcessImpl\",\n            \"java.lang.UNIXProcess\",\n            \"java.io.File\",\n            \"java.io.FileDescriptor\",\n            \"java.io.FileInputStream\",\n            \"java.io.FileOutputStream\",\n            \"java.io.PrintStream\",\n            \"java.io.FileReader\",\n            \"java.io.FileWriter\",\n            \"java.io.PrintWriter\",\n            \"java.io.UnixFileSystem\",\n            \"java.io.RandomAccessFile\",\n            \"java.io.ObjectInputStream\",\n            \"java.io.ObjectOutputStream\",\n            \"java.security.AccessController\",\n            \"java.nio.file.Paths\",\n            \"java.nio.file.Files\",\n            \"java.nio.file.FileSystems\",\n            \"java.util.Formatter\",\n            \"sun.misc.Unsafe\",\n            \"android.content.Intent\",\n            \"android.provider.Settings\",\n            \"android.app.ActivityThread\",\n            \"android.app.AppGlobals\",\n            \"android.os.Looper\",\n            \"android.os.Process\",\n            \"android.os.FileUtils\",\n\n            \"cn.hutool.core.lang.JarClassLoader\",\n            \"cn.hutool.core.lang.Singleton\",\n            \"cn.hutool.core.util.RuntimeUtil\",\n            \"cn.hutool.core.util.ClassLoaderUtil\",\n            \"cn.hutool.core.util.ReflectUtil\",\n            \"cn.hutool.core.util.SerializeUtil\",\n            \"cn.hutool.core.util.ClassUtil\",\n            \"org.mozilla.javascript.DefiningClassLoader\",\n            \"io.legado.app.data.AppDatabase\",\n            \"io.legado.app.data.AppDatabase_Impl\",\n            \"io.legado.app.data.AppDatabaseKt\",\n            \"io.legado.app.utils.ContextExtensionsKt\",\n            \"androidx.core.content.FileProvider\",\n            \"splitties.init.AppCtxKt\",\n            \"okio.JvmSystemFileSystem\",\n            \"okio.JvmFileHandle\",\n            \"okio.NioSystemFileSystem\",\n            \"okio.NioFileSystemFileHandle\",\n            \"okio.Path\",\n\n            \"android.system\",\n            \"android.database\",\n            \"androidx.sqlite.db\",\n            \"androidx.room\",\n            \"cn.hutool.core.io\",\n            \"cn.hutool.core.bean\",\n            \"cn.hutool.core.lang.reflect\",\n            \"dalvik.system\",\n            \"java.nio.file\",\n            \"java.lang.reflect\",\n            \"java.lang.invoke\",\n            \"io.legado.app.data.dao\",\n            \"com.script\",\n            \"org.mozilla\",\n            \"sun\",\n            \"libcore\",\n        ).let { ClassNameMatcher(it) }\n    }\n\n    private val systemClassProtectedName by lazy {\n        Collections.unmodifiableSet(hashSetOf(\"load\", \"loadLibrary\", \"exit\"))\n    }\n\n    private val protectedClasses by lazy {\n        arrayOf(\n            ClassLoader::class.java,\n            Class::class.java,\n            Member::class.java,\n            Context::class.java,\n            ObjectInputStream::class.java,\n            ObjectOutputStream::class.java,\n            okio.FileSystem::class.java,\n            okio.FileHandle::class.java,\n            okio.Path::class.java,\n            android.content.Context::class.java,\n        ) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {\n            arrayOf(FileSystem::class.java, Path::class.java)\n        } else {\n            emptyArray()\n        }\n    }\n\n    fun visibleToScripts(obj: Any): Boolean {\n        when (obj) {\n            is ClassLoader,\n            is Class<*>,\n            is Member,\n            is Context,\n            is ObjectInputStream,\n            is ObjectOutputStream,\n            is okio.FileSystem,\n            is okio.FileHandle,\n            is okio.Path,\n            is android.content.Context -> return false\n        }\n        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {\n            when (obj) {\n                is FileSystem,\n                is Path -> return false\n            }\n        }\n        return visibleToScripts(obj.javaClass.name)\n    }\n\n    fun visibleToScripts(clazz: Class<*>): Boolean {\n        protectedClasses.forEach {\n            if (it.isAssignableFrom(clazz)) {\n                return false\n            }\n        }\n        return true\n    }\n\n    fun wrapJavaClass(scope: Scriptable, javaClass: Class<*>): Scriptable {\n        return when (javaClass) {\n            System::class.java -> {\n                ProtectedNativeJavaClass(scope, javaClass, systemClassProtectedName)\n            }\n\n            else -> ProtectedNativeJavaClass(scope, javaClass)\n        }\n    }\n\n    override fun visibleToScripts(fullClassName: String): Boolean {\n        return !protectedClassNamesMatcher.match(fullClassName)\n    }\n\n}"
  },
  {
    "path": "modules/rhino/src/main/java/com/script/rhino/RhinoCompiledScript.kt",
    "content": "/*\n * Copyright (c) 2005, 2006, Oracle and/or its affiliates. All rights reserved.\n * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.\n *\n * This code is free software; you can redistribute it and/or modify it\n * under the terms of the GNU General Public License version 2 only, as\n * published by the Free Software Foundation.  Oracle designates this\n * particular file as subject to the \"Classpath\" exception as provided\n * by Oracle in the LICENSE file that accompanied this code.\n *\n * This code is distributed in the hope that it will be useful, but WITHOUT\n * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or\n * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License\n * version 2 for more details (a copy is included in the LICENSE file that\n * accompanied this code).\n *\n * You should have received a copy of the GNU General Public License version\n * 2 along with this work; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.\n *\n * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA\n * or visit www.oracle.com if you need additional information or have any\n * questions.\n */\npackage com.script.rhino\n\nimport com.script.CompiledScript\nimport com.script.ScriptEngine\nimport com.script.ScriptException\nimport kotlinx.coroutines.Job\nimport kotlinx.coroutines.asContextElement\nimport kotlinx.coroutines.withContext\nimport org.mozilla.javascript.Context\nimport org.mozilla.javascript.ContinuationPending\nimport org.mozilla.javascript.JavaScriptException\nimport org.mozilla.javascript.RhinoException\nimport org.mozilla.javascript.Script\nimport org.mozilla.javascript.Scriptable\nimport java.io.IOException\nimport kotlin.coroutines.CoroutineContext\n\n/**\n * Represents compiled JavaScript code.\n *\n * @author Mike Grogan\n * @since 1.6\n */\ninternal class RhinoCompiledScript(\n    private val engine: RhinoScriptEngine,\n    private val script: Script\n) : CompiledScript() {\n\n    override fun getEngine(): ScriptEngine {\n        return engine\n    }\n\n    override fun eval(scope: Scriptable, coroutineContext: CoroutineContext?): Any? {\n        val cx = Context.enter() as RhinoContext\n        val previousCoroutineContext = cx.coroutineContext\n        if (coroutineContext != null && coroutineContext[Job] != null) {\n            cx.coroutineContext = coroutineContext\n        }\n        cx.allowScriptRun = true\n        cx.recursiveCount++\n        val result: Any?\n        try {\n            cx.checkRecursive()\n            val ret = script.exec(cx, scope)\n            result = engine.unwrapReturnValue(ret)\n        } catch (re: RhinoException) {\n            val line = if (re.lineNumber() == 0) -1 else re.lineNumber()\n            val msg: String = if (re is JavaScriptException) {\n                re.value.toString()\n            } else {\n                re.toString()\n            }\n            val se = ScriptException(msg, re.sourceName(), line)\n            se.initCause(re)\n            throw se\n        } finally {\n            cx.coroutineContext = previousCoroutineContext\n            cx.allowScriptRun = false\n            cx.recursiveCount--\n            Context.exit()\n        }\n        return result\n    }\n\n    override suspend fun evalSuspend(scope: Scriptable): Any? {\n        val cx = Context.enter() as RhinoContext\n        var ret: Any?\n        withContext(VMBridgeReflect.contextLocal.asContextElement()) {\n            cx.allowScriptRun = true\n            cx.recursiveCount++\n            try {\n                cx.checkRecursive()\n                try {\n                    ret = cx.executeScriptWithContinuations(script, scope)\n                } catch (e: ContinuationPending) {\n                    var pending = e\n                    while (true) {\n                        try {\n                            @Suppress(\"UNCHECKED_CAST\")\n                            val suspendFunction = pending.applicationState as suspend () -> Any?\n                            val functionResult = suspendFunction()\n                            val continuation = pending.continuation\n                            ret = cx.resumeContinuation(continuation, scope, functionResult)\n                            break\n                        } catch (e: ContinuationPending) {\n                            pending = e\n                        }\n                    }\n                }\n            } catch (re: RhinoException) {\n                val line = if (re.lineNumber() == 0) -1 else re.lineNumber()\n                val msg: String = if (re is JavaScriptException) {\n                    re.value.toString()\n                } else {\n                    re.toString()\n                }\n                val se = ScriptException(msg, re.sourceName(), line)\n                se.initCause(re)\n                throw se\n            } catch (var14: IOException) {\n                throw ScriptException(var14)\n            } finally {\n                cx.allowScriptRun = false\n                cx.recursiveCount--\n                Context.exit()\n            }\n        }\n        return engine.unwrapReturnValue(ret)\n    }\n\n}"
  },
  {
    "path": "modules/rhino/src/main/java/com/script/rhino/RhinoContext.kt",
    "content": "package com.script.rhino\n\nimport kotlinx.coroutines.CancellationException\nimport kotlinx.coroutines.ensureActive\nimport org.mozilla.javascript.Context\nimport org.mozilla.javascript.ContextFactory\nimport kotlin.coroutines.CoroutineContext\n\nclass RhinoContext(factory: ContextFactory) : Context(factory) {\n\n    var coroutineContext: CoroutineContext? = null\n    var allowScriptRun = false\n    var recursiveCount = 0\n\n    @Throws(RhinoInterruptError::class)\n    fun ensureActive() {\n        try {\n            coroutineContext?.ensureActive()\n        } catch (e: CancellationException) {\n            throw RhinoInterruptError(e)\n        }\n    }\n\n    @Throws(RhinoRecursionError::class)\n    fun checkRecursive() {\n        if (recursiveCount >= 10) {\n            throw RhinoRecursionError()\n        }\n    }\n\n}\n"
  },
  {
    "path": "modules/rhino/src/main/java/com/script/rhino/RhinoErrors.kt",
    "content": "package com.script.rhino\n\nclass RhinoInterruptError(override val cause: Throwable) : Error()\n\nclass RhinoRecursionError(): Error(\"Maximum recursion depth exceeded.\")\n"
  },
  {
    "path": "modules/rhino/src/main/java/com/script/rhino/RhinoExtensions.kt",
    "content": "package com.script.rhino\n\nimport kotlinx.coroutines.CoroutineScope\nimport kotlinx.coroutines.runBlocking\nimport kotlinx.coroutines.supervisorScope\nimport org.mozilla.javascript.Context\nimport kotlin.contracts.ExperimentalContracts\nimport kotlin.contracts.InvocationKind\nimport kotlin.contracts.contract\nimport kotlin.coroutines.ContinuationInterceptor\nimport kotlin.coroutines.CoroutineContext\nimport kotlin.coroutines.coroutineContext\n\nval rhinoContext: RhinoContext\n    get() = Context.getCurrentContext() as RhinoContext\n\nval rhinoContextOrNull: RhinoContext?\n    get() = Context.getCurrentContext() as? RhinoContext\n\n@Suppress(\"LEAKED_IN_PLACE_LAMBDA\", \"WRONG_INVOCATION_KIND\")\n@OptIn(ExperimentalContracts::class)\ninline fun <T> suspendContinuation(crossinline block: suspend CoroutineScope.() -> T): T {\n    contract {\n        callsInPlace(block, InvocationKind.EXACTLY_ONCE)\n    }\n    val cx = Context.enter()\n    try {\n        val pending = cx.captureContinuation()\n        pending.applicationState = suspend {\n            supervisorScope {\n                block()\n            }\n        }\n        throw pending\n    } catch (e: IllegalStateException) {\n        return runBlocking { block() }\n    } finally {\n        Context.exit()\n    }\n}\n\ninline fun <T> runScriptWithContext(context: CoroutineContext, block: () -> T): T {\n    RhinoScriptEngine\n    val rhinoContext = Context.enter() as RhinoContext\n    val previousCoroutineContext = rhinoContext.coroutineContext\n    rhinoContext.coroutineContext = context.minusKey(ContinuationInterceptor)\n    try {\n        return block()\n    } finally {\n        rhinoContext.coroutineContext = previousCoroutineContext\n        Context.exit()\n    }\n}\n\nsuspend inline fun <T> runScriptWithContext(block: () -> T): T {\n    val rhinoContext = Context.enter() as RhinoContext\n    val previousCoroutineContext = rhinoContext.coroutineContext\n    rhinoContext.coroutineContext = coroutineContext.minusKey(ContinuationInterceptor)\n    try {\n        return block()\n    } finally {\n        rhinoContext.coroutineContext = previousCoroutineContext\n        Context.exit()\n    }\n}\n"
  },
  {
    "path": "modules/rhino/src/main/java/com/script/rhino/RhinoScriptEngine.kt",
    "content": "/*\n * Copyright (c) 2005, 2011, Oracle and/or its affiliates. All rights reserved.\n * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.\n *\n * This code is free software; you can redistribute it and/or modify it\n * under the terms of the GNU General Public License version 2 only, as\n * published by the Free Software Foundation.  Oracle designates this\n * particular file as subject to the \"Classpath\" exception as provided\n * by Oracle in the LICENSE file that accompanied this code.\n *\n * This code is distributed in the hope that it will be useful, but WITHOUT\n * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or\n * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License\n * version 2 for more details (a copy is included in the LICENSE file that\n * accompanied this code).\n *\n * You should have received a copy of the GNU General Public License version\n * 2 along with this work; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.\n *\n * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA\n * or visit www.oracle.com if you need additional information or have any\n * questions.\n */\npackage com.script.rhino\n\nimport com.script.AbstractScriptEngine\nimport com.script.Bindings\nimport com.script.Compilable\nimport com.script.CompiledScript\nimport com.script.Invocable\nimport com.script.ScriptBindings\nimport com.script.ScriptContext\nimport com.script.ScriptException\nimport com.script.SimpleBindings\nimport kotlinx.coroutines.Job\nimport kotlinx.coroutines.asContextElement\nimport kotlinx.coroutines.withContext\nimport org.mozilla.javascript.Callable\nimport org.mozilla.javascript.ConsString\nimport org.mozilla.javascript.Context\nimport org.mozilla.javascript.ContextFactory\nimport org.mozilla.javascript.ContinuationPending\nimport org.mozilla.javascript.Function\nimport org.mozilla.javascript.JavaScriptException\nimport org.mozilla.javascript.RhinoException\nimport org.mozilla.javascript.Scriptable\nimport org.mozilla.javascript.ScriptableObject\nimport org.mozilla.javascript.Undefined\nimport org.mozilla.javascript.Wrapper\nimport java.io.IOException\nimport java.io.Reader\nimport java.io.StringReader\nimport java.lang.reflect.Method\nimport java.security.AccessControlContext\nimport java.security.AccessControlException\nimport java.security.AccessController\nimport java.security.AllPermission\nimport java.security.PrivilegedAction\nimport kotlin.coroutines.CoroutineContext\n\n/**\n * Implementation of `ScriptEngine` using the Mozilla Rhino\n * interpreter.\n *\n * @author Mike Grogan\n * @author A. Sundararajan\n * @since 1.6\n */\n@Suppress(\"MemberVisibilityCanBePrivate\")\nobject RhinoScriptEngine : AbstractScriptEngine(), Invocable, Compilable {\n    var accessContext: AccessControlContext? = null\n    private var topLevel: RhinoTopLevel? = null\n    private val indexedProps: MutableMap<Any, Any?>\n    private val implementor: InterfaceImplementor\n\n    fun eval(js: String, bindingsConfig: ScriptBindings.() -> Unit = {}): Any? {\n        val bindings = ScriptBindings()\n        Context.enter()\n        try {\n            bindings.apply(bindingsConfig)\n        } finally {\n            Context.exit()\n        }\n        return eval(js, bindings)\n    }\n\n    override fun eval(\n        reader: Reader,\n        scope: Scriptable,\n        coroutineContext: CoroutineContext?\n    ): Any? {\n        val cx = Context.enter() as RhinoContext\n        val previousCoroutineContext = cx.coroutineContext\n        if (coroutineContext != null && coroutineContext[Job] != null) {\n            cx.coroutineContext = coroutineContext\n        }\n        cx.allowScriptRun = true\n        cx.recursiveCount++\n        val ret: Any?\n        try {\n            cx.checkRecursive()\n            var filename = this[\"javax.script.filename\"] as? String\n            filename = filename ?: \"<Unknown source>\"\n            ret = cx.evaluateReader(scope, reader, filename, 1, null)\n        } catch (re: RhinoException) {\n            val line = if (re.lineNumber() == 0) -1 else re.lineNumber()\n            val msg: String = if (re is JavaScriptException) {\n                re.value.toString()\n            } else {\n                re.toString()\n            }\n            val se = ScriptException(msg, re.sourceName(), line)\n            se.initCause(re)\n            throw se\n        } catch (var14: IOException) {\n            throw ScriptException(var14)\n        } finally {\n            cx.coroutineContext = previousCoroutineContext\n            cx.allowScriptRun = false\n            cx.recursiveCount--\n            Context.exit()\n        }\n        return unwrapReturnValue(ret)\n    }\n\n    @Throws(ContinuationPending::class)\n    override suspend fun evalSuspend(reader: Reader, scope: Scriptable): Any? {\n        val cx = Context.enter() as RhinoContext\n        var ret: Any?\n        withContext(VMBridgeReflect.contextLocal.asContextElement()) {\n            cx.allowScriptRun = true\n            cx.recursiveCount++\n            try {\n                cx.checkRecursive()\n                var filename = this@RhinoScriptEngine[\"javax.script.filename\"] as? String\n                filename = filename ?: \"<Unknown source>\"\n                val script = cx.compileReader(reader, filename, 1, null)\n                try {\n                    ret = cx.executeScriptWithContinuations(script, scope)\n                } catch (e: ContinuationPending) {\n                    var pending = e\n                    while (true) {\n                        try {\n                            @Suppress(\"UNCHECKED_CAST\")\n                            val suspendFunction = pending.applicationState as suspend () -> Any?\n                            val functionResult = suspendFunction()\n                            val continuation = pending.continuation\n                            ret = cx.resumeContinuation(continuation, scope, functionResult)\n                            break\n                        } catch (e: ContinuationPending) {\n                            pending = e\n                        }\n                    }\n                }\n            } catch (re: RhinoException) {\n                val line = if (re.lineNumber() == 0) -1 else re.lineNumber()\n                val msg: String = if (re is JavaScriptException) {\n                    re.value.toString()\n                } else {\n                    re.toString()\n                }\n                val se = ScriptException(msg, re.sourceName(), line)\n                se.initCause(re)\n                throw se\n            } catch (var14: IOException) {\n                throw ScriptException(var14)\n            } finally {\n                cx.allowScriptRun = false\n                cx.recursiveCount--\n                Context.exit()\n            }\n        }\n        return unwrapReturnValue(ret)\n    }\n\n    override fun createBindings(): Bindings {\n        return SimpleBindings()\n    }\n\n    @Throws(ScriptException::class, NoSuchMethodException::class)\n    override fun invokeFunction(name: String, vararg args: Any): Any? {\n        return this.invoke(null, name, *args)\n    }\n\n    @Throws(ScriptException::class, NoSuchMethodException::class)\n    override fun invokeMethod(obj: Any?, name: String, vararg args: Any): Any? {\n        return if (obj == null) {\n            throw IllegalArgumentException(\"脚本对象不能为空\")\n        } else {\n            this.invoke(obj, name, *args)\n        }\n    }\n\n    @Suppress(\"UNCHECKED_CAST\")\n    @Throws(ScriptException::class, NoSuchMethodException::class)\n    private operator fun invoke(thiz: Any?, name: String?, vararg args: Any?): Any? {\n        var thiz1 = thiz\n        val cx = Context.enter()\n        val var11: Any?\n        try {\n            if (name == null) {\n                throw NullPointerException(\"方法名为空\")\n            }\n            if (thiz1 != null && thiz1 !is Scriptable) {\n                thiz1 = Context.toObject(thiz1, topLevel)\n            }\n            val engineScope = getRuntimeScope(context)\n            val localScope = thiz1 ?: engineScope\n            val obj = ScriptableObject.getProperty(localScope, name) as? Function\n                ?: throw NoSuchMethodException(\"no such method: $name\")\n            var scope = obj.parentScope\n            if (scope == null) {\n                scope = engineScope\n            }\n            val result = obj.call(cx, scope, localScope, wrapArguments(args as? Array<Any?>))\n            var11 = unwrapReturnValue(result)\n        } catch (re: RhinoException) {\n            val line = if (re.lineNumber() == 0) -1 else re.lineNumber()\n            val se = ScriptException(re.toString(), re.sourceName(), line)\n            se.initCause(re)\n            throw se\n        } finally {\n            Context.exit()\n        }\n        return var11\n    }\n\n    override fun <T> getInterface(clazz: Class<T>): T? {\n        return try {\n            implementor.getInterface(null, clazz)\n        } catch (_: ScriptException) {\n            null\n        }\n    }\n\n    override fun <T> getInterface(obj: Any?, paramClass: Class<T>): T? {\n        return if (obj == null) {\n            throw IllegalArgumentException(\"脚本对象不能为空\")\n        } else {\n            try {\n                implementor.getInterface(obj, paramClass)\n            } catch (_: ScriptException) {\n                null\n            }\n        }\n    }\n\n    override fun getRuntimeScope(context: ScriptContext): Scriptable {\n        val newScope: Scriptable = ExternalScriptable(context, indexedProps)\n        val cx = Context.enter()\n        try {\n            newScope.prototype = RhinoTopLevel(cx, this)\n        } finally {\n            Context.exit()\n        }\n        //newScope.put(\"context\", newScope, context)\n        return newScope\n    }\n\n    override fun getRuntimeScope(bindings: ScriptBindings): Scriptable {\n        val cx = Context.enter()\n        try {\n            bindings.prototype = cx.initStandardObjects()\n        } finally {\n            Context.exit()\n        }\n        return bindings\n    }\n\n    @Throws(ScriptException::class)\n    override fun compile(script: String): CompiledScript {\n        return this.compile(StringReader(script) as Reader)\n    }\n\n    @Throws(ScriptException::class)\n    override fun compile(script: Reader): CompiledScript {\n        val cx = Context.enter()\n        val ret: RhinoCompiledScript\n        try {\n            var fileName = this[\"javax.script.filename\"] as? String\n            if (fileName == null) {\n                fileName = \"<Unknown Source>\"\n            }\n            val scr = cx.compileReader(script, fileName, 1, null)\n            ret = RhinoCompiledScript(this, scr)\n        } catch (var9: Exception) {\n            throw ScriptException(var9)\n        } finally {\n            Context.exit()\n        }\n        return ret\n    }\n\n    fun wrapArguments(args: Array<Any?>?): Array<Any?> {\n        return if (args == null) {\n            Context.emptyArgs\n        } else {\n            val res = arrayOfNulls<Any>(args.size)\n            for (i in res.indices) {\n                res[i] = Context.javaToJS(args[i], topLevel)\n            }\n            res\n        }\n    }\n\n    fun unwrapReturnValue(result: Any?): Any? {\n        var result1 = result\n        if (result1 is Wrapper) {\n            result1 = result1.unwrap()\n        }\n        if (result1 is ConsString) {\n            result1 = result1.toString()\n        }\n        return if (result1 is Undefined) null else result1\n    }\n\n    init {\n        ContextFactory.initGlobal(object : ContextFactory() {\n\n            override fun makeContext(): Context {\n                val cx = RhinoContext(this)\n                cx.languageVersion = Context.VERSION_ES6\n                cx.setInterpretedMode(true)\n                cx.setClassShutter(RhinoClassShutter)\n                cx.wrapFactory = RhinoWrapFactory\n                cx.instructionObserverThreshold = 10000\n                cx.maximumInterpreterStackDepth = 1000\n                return cx\n            }\n\n            override fun hasFeature(cx: Context, featureIndex: Int): Boolean {\n                @Suppress(\"UNUSED_EXPRESSION\")\n                return when (featureIndex) {\n                    Context.FEATURE_ENABLE_JAVA_MAP_ACCESS -> true\n                    else -> super.hasFeature(cx, featureIndex)\n                }\n            }\n\n            override fun observeInstructionCount(cx: Context, instructionCount: Int) {\n                if (cx is RhinoContext) {\n                    cx.ensureActive()\n                }\n            }\n\n            override fun doTopCall(\n                callable: Callable,\n                cx: Context,\n                scope: Scriptable,\n                thisObj: Scriptable?,\n                args: Array<Any>\n            ): Any? {\n                var accContext: AccessControlContext? = null\n                val global = ScriptableObject.getTopLevelScope(scope)\n                val globalProto = global.prototype\n                if (globalProto is RhinoTopLevel) {\n                    accContext = globalProto.accessContext\n                }\n                return if (accContext != null) AccessController.doPrivileged(\n                    PrivilegedAction {\n                        superDoTopCall(callable, cx, scope, thisObj, args)\n                    }, accContext\n                ) else superDoTopCall(\n                    callable,\n                    cx,\n                    scope,\n                    thisObj,\n                    args\n                )\n            }\n\n            private fun superDoTopCall(\n                callable: Callable,\n                cx: Context,\n                scope: Scriptable,\n                thisObj: Scriptable?,\n                args: Array<Any>\n            ): Any? {\n                try {\n                    if (cx is RhinoContext) {\n                        if (!cx.allowScriptRun) {\n                            error(\"Not allow run script in unauthorized way.\")\n                        }\n                        cx.ensureActive()\n                    }\n                    return super.doTopCall(callable, cx, scope, thisObj, args)\n                } catch (e: RhinoInterruptError) {\n                    throw e.cause\n                }\n            }\n        })\n\n        if (System.getSecurityManager() != null) {\n            try {\n                AccessController.checkPermission(AllPermission())\n            } catch (_: AccessControlException) {\n                accessContext = AccessController.getContext()\n            }\n        }\n        val cx = Context.enter()\n        try {\n            topLevel = RhinoTopLevel(cx, this)\n        } finally {\n            Context.exit()\n        }\n        indexedProps = HashMap()\n        implementor = object : InterfaceImplementor(this) {\n\n            override fun isImplemented(obj: Any?, clazz: Class<*>): Boolean {\n                var obj1 = obj\n                return try {\n                    if (obj1 != null && obj1 !is Scriptable) {\n                        obj1 = Context.toObject(obj1, topLevel)\n                    }\n                    val engineScope = getRuntimeScope(context)\n                    val localScope = obj1 ?: engineScope\n                    val methods = clazz.methods\n                    val methodsSize = methods.size\n                    for (index in 0 until methodsSize) {\n                        val method = methods[index]\n                        if (method.declaringClass != Any::class.java) {\n                            if (ScriptableObject.getProperty(\n                                    localScope,\n                                    method.name\n                                ) !is Function\n                            ) {\n                                return false\n                            }\n                        }\n                    }\n                    true\n                } finally {\n                    Context.exit()\n                }\n            }\n\n            override fun convertResult(method: Method?, res: Any?): Any? {\n                method ?: return null\n                val desiredType = method.returnType\n                if (desiredType == Void.TYPE) return null\n                return Context.jsToJava(res, desiredType)\n            }\n        }\n    }\n\n}\n"
  },
  {
    "path": "modules/rhino/src/main/java/com/script/rhino/RhinoTopLevel.kt",
    "content": "/*\n * Copyright (c) 2005, 2010, Oracle and/or its affiliates. All rights reserved.\n * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.\n *\n * This code is free software; you can redistribute it and/or modify it\n * under the terms of the GNU General Public License version 2 only, as\n * published by the Free Software Foundation.  Oracle designates this\n * particular file as subject to the \"Classpath\" exception as provided\n * by Oracle in the LICENSE file that accompanied this code.\n *\n * This code is distributed in the hope that it will be useful, but WITHOUT\n * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or\n * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License\n * version 2 for more details (a copy is included in the LICENSE file that\n * accompanied this code).\n *\n * You should have received a copy of the GNU General Public License version\n * 2 along with this work; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.\n *\n * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA\n * or visit www.oracle.com if you need additional information or have any\n * questions.\n */\npackage com.script.rhino\n\nimport com.script.Bindings\nimport com.script.ScriptContext\nimport com.script.SimpleScriptContext\nimport org.mozilla.javascript.Context\nimport org.mozilla.javascript.Function\nimport org.mozilla.javascript.ImporterTopLevel\nimport org.mozilla.javascript.Scriptable\nimport org.mozilla.javascript.Synchronizer\nimport org.mozilla.javascript.Wrapper\nimport java.security.AccessControlContext\n\n/**\n * This class serves as top level scope for Rhino. This class adds\n * 3 top level functions (bindings, scope, sync) and two constructors\n * (JSAdapter, JavaAdapter).\n *\n * @author A. Sundararajan\n * @since 1.6\n */\n@Suppress(\"UNUSED_PARAMETER\", \"unused\")\nclass RhinoTopLevel(cx: Context, val scriptEngine: RhinoScriptEngine) :\n    ImporterTopLevel(cx, System.getSecurityManager() != null) {\n\n    init {\n//        LazilyLoadedCtor(this, \"JSAdapter\", \"com.script.rhino.JSAdapter\", false)\n//        JavaAdapter.init(cx, this, false)\n//        val names = arrayOf(\"bindings\", \"scope\", \"sync\")\n//        defineFunctionProperties(names, RhinoTopLevel::class.java, 2)\n    }\n\n    val accessContext: AccessControlContext?\n        get() = scriptEngine.accessContext\n\n    companion object {\n\n        @JvmStatic\n        fun bindings(\n            cx: Context,\n            thisObj: Scriptable?,\n            args: Array<Any?>,\n            funObj: Function?\n        ): Any {\n            if (args.size == 1) {\n                var arg = args[0]\n                if (arg is Wrapper) {\n                    arg = arg.unwrap()\n                }\n                if (arg is ExternalScriptable) {\n                    val ctx = arg.context\n                    val bind = ctx.getBindings(100)\n                    return Context.javaToJS(bind, getTopLevelScope(thisObj))\n                }\n            }\n            return Context.getUndefinedValue()\n        }\n\n        @JvmStatic\n        fun scope(cx: Context, thisObj: Scriptable?, args: Array<Any?>, funObj: Function?): Any {\n            if (args.size == 1) {\n                var arg = args[0]\n                if (arg is Wrapper) {\n                    arg = arg.unwrap()\n                }\n                if (arg is Bindings) {\n                    val ctx: ScriptContext = SimpleScriptContext()\n                    ctx.setBindings(arg as Bindings?, 100)\n                    val res: Scriptable = ExternalScriptable(ctx)\n                    res.prototype = getObjectPrototype(thisObj)\n                    res.parentScope = getTopLevelScope(thisObj)\n                    return res\n                }\n            }\n            return Context.getUndefinedValue()\n        }\n\n        @JvmStatic\n        fun sync(cx: Context, thisObj: Scriptable?, args: Array<Any?>, funObj: Function?): Any {\n            return if (args.size == 1 && args[0] is Function) {\n                Synchronizer(args[0] as Function?)\n            } else {\n                throw Context.reportRuntimeError(\"wrong argument(s) for sync\")\n            }\n        }\n    }\n}"
  },
  {
    "path": "modules/rhino/src/main/java/com/script/rhino/RhinoWrapFactory.kt",
    "content": "/*\n * Copyright (c) 2005, 2006, Oracle and/or its affiliates. All rights reserved.\n * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.\n *\n * This code is free software; you can redistribute it and/or modify it\n * under the terms of the GNU General Public License version 2 only, as\n * published by the Free Software Foundation.  Oracle designates this\n * particular file as subject to the \"Classpath\" exception as provided\n * by Oracle in the LICENSE file that accompanied this code.\n *\n * This code is distributed in the hope that it will be useful, but WITHOUT\n * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or\n * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License\n * version 2 for more details (a copy is included in the LICENSE file that\n * accompanied this code).\n *\n * You should have received a copy of the GNU General Public License version\n * 2 along with this work; if not, write to the Free Software Foundation,\n * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.\n *\n * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA\n * or visit www.oracle.com if you need additional information or have any\n * questions.\n */\npackage com.script.rhino\n\nimport org.mozilla.javascript.Context\nimport org.mozilla.javascript.NativeJavaPackage\nimport org.mozilla.javascript.ScriptRuntime\nimport org.mozilla.javascript.Scriptable\nimport org.mozilla.javascript.WrapFactory\n\n/**\n * This wrap factory is used for security reasons. JSR 223 script\n * engine interface and JavaScript engine classes are run as bootstrap\n * classes. For example, java.lang.Class.forName method (when called without\n * class loader) uses caller's class loader. This may be exploited by script\n * authors to access classes otherwise not accessible. For example,\n * classes in sun.* namespace are normally not accessible to untrusted\n * code and hence should not be accessible to JavaScript run from\n * untrusted code.\n *\n * @author A. Sundararajan\n * @since 1.6\n */\nobject RhinoWrapFactory : WrapFactory() {\n\n    private val factories = hashMapOf<Class<*>, JavaObjectWrapFactory>()\n\n    override fun wrapAsJavaObject(\n        cx: Context,\n        scope: Scriptable?,\n        javaObject: Any,\n        staticType: Class<*>?\n    ): Scriptable? {\n        if (!RhinoClassShutter.visibleToScripts(javaObject)) {\n            return null\n        }\n        return wrapOrNull(scope, javaObject, staticType)\n            ?: super.wrapAsJavaObject(cx, scope, javaObject, staticType)\n    }\n\n    override fun wrapJavaClass(\n        cx: Context,\n        scope: Scriptable,\n        javaClass: Class<*>\n    ): Scriptable {\n        if (!RhinoClassShutter.visibleToScripts(javaClass)) {\n            @Suppress(\"DEPRECATION\")\n            val pkg = NativeJavaPackage(javaClass.name, null)\n            ScriptRuntime.setObjectProtoAndParent(pkg, scope)\n            return pkg\n        }\n        return RhinoClassShutter.wrapJavaClass(scope, javaClass)\n    }\n\n    private fun wrapOrNull(\n        scope: Scriptable?,\n        javaObject: Any,\n        staticType: Class<*>?\n    ): Scriptable? {\n        return factories[javaObject.javaClass]?.wrap(scope, javaObject, staticType)\n    }\n\n    fun register(clazz: Class<*>, factory: JavaObjectWrapFactory) {\n        if (!factories.contains(clazz)) {\n            factories.put(clazz, factory)\n        }\n    }\n\n}"
  },
  {
    "path": "modules/rhino/src/main/java/com/script/rhino/VMBridgeReflect.kt",
    "content": "package com.script.rhino\n\nimport org.mozilla.javascript.VMBridge\n\nobject VMBridgeReflect {\n\n    val instance: VMBridge by lazy {\n        VMBridge::class.java.getDeclaredField(\"instance\").apply {\n            isAccessible = true\n        }.get(null) as VMBridge\n    }\n\n    val contextLocal: ThreadLocal<Any> by lazy {\n        @Suppress(\"UNCHECKED_CAST\")\n        instance::class.java.getDeclaredField(\"contextLocal\").apply {\n            isAccessible = true\n        }.get(null) as ThreadLocal<Any>\n    }\n\n}\n"
  },
  {
    "path": "modules/web/.browserslistrc",
    "content": "> 1%\nlast 2 versions\nnot dead\n"
  },
  {
    "path": "modules/web/.editorconfig",
    "content": "[*.{js,jsx,mjs,cjs,ts,tsx,mts,cts,vue}]\ncharset = utf-8\nindent_size = 2\nindent_style = space\ninsert_final_newline = true\ntrim_trailing_whitespace = true\n"
  },
  {
    "path": "modules/web/.gitignore",
    "content": "# Logs\nlogs\n*.log\nnpm-debug.log*\nyarn-debug.log*\nyarn-error.log*\npnpm-debug.log*\nlerna-debug.log*\n\nnode_modules\n.DS_Store\ndist\ndist-ssr\ncoverage\n*.local\n\n/cypress/videos/\n/cypress/screenshots/\n\n# Editor directories and files\n.vscode/*\n!.vscode/extensions.json\n.idea\n*.suo\n*.ntvs*\n*.njsproj\n*.sln\n*.sw?\n\n*.tsbuildinfo\n"
  },
  {
    "path": "modules/web/.prettierignore",
    "content": "# d.ts files Generated by unplugin-auto unplugin-vue-components\nauto-imports.d.ts\ncomponents.d.ts"
  },
  {
    "path": "modules/web/.prettierrc.json",
    "content": "\n{\n  \"$schema\": \"https://json.schemastore.org/prettierrc\",\n  \"semi\": false,\n  \"singleQuote\": true,\n  \"arrowParens\": \"avoid\"\n}\n"
  },
  {
    "path": "modules/web/LICENSE",
    "content": "                    GNU GENERAL PUBLIC LICENSE\n                       Version 3, 29 June 2007\n\n Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>\n Everyone is permitted to copy and distribute verbatim copies\n of this license document, but changing it is not allowed.\n\n                            Preamble\n\n  The GNU General Public License is a free, copyleft license for\nsoftware and other kinds of works.\n\n  The licenses for most software and other practical works are designed\nto take away your freedom to share and change the works.  By contrast,\nthe GNU General Public License is intended to guarantee your freedom to\nshare and change all versions of a program--to make sure it remains free\nsoftware for all its users.  We, the Free Software Foundation, use the\nGNU General Public License for most of our software; it applies also to\nany other work released this way by its authors.  You can apply it to\nyour programs, too.\n\n  When we speak of free software, we are referring to freedom, not\nprice.  Our General Public Licenses are designed to make sure that you\nhave the freedom to distribute copies of free software (and charge for\nthem if you wish), that you receive source code or can get it if you\nwant it, that you can change the software or use pieces of it in new\nfree programs, and that you know you can do these things.\n\n  To protect your rights, we need to prevent others from denying you\nthese rights or asking you to surrender the rights.  Therefore, you have\ncertain responsibilities if you distribute copies of the software, or if\nyou modify it: responsibilities to respect the freedom of others.\n\n  For example, if you distribute copies of such a program, whether\ngratis or for a fee, you must pass on to the recipients the same\nfreedoms that you received.  You must make sure that they, too, receive\nor can get the source code.  And you must show them these terms so they\nknow their rights.\n\n  Developers that use the GNU GPL protect your rights with two steps:\n(1) assert copyright on the software, and (2) offer you this License\ngiving you legal permission to copy, distribute and/or modify it.\n\n  For the developers' and authors' protection, the GPL clearly explains\nthat there is no warranty for this free software.  For both users' and\nauthors' sake, the GPL requires that modified versions be marked as\nchanged, so that their problems will not be attributed erroneously to\nauthors of previous versions.\n\n  Some devices are designed to deny users access to install or run\nmodified versions of the software inside them, although the manufacturer\ncan do so.  This is fundamentally incompatible with the aim of\nprotecting users' freedom to change the software.  The systematic\npattern of such abuse occurs in the area of products for individuals to\nuse, which is precisely where it is most unacceptable.  Therefore, we\nhave designed this version of the GPL to prohibit the practice for those\nproducts.  If such problems arise substantially in other domains, we\nstand ready to extend this provision to those domains in future versions\nof the GPL, as needed to protect the freedom of users.\n\n  Finally, every program is threatened constantly by software patents.\nStates should not allow patents to restrict development and use of\nsoftware on general-purpose computers, but in those that do, we wish to\navoid the special danger that patents applied to a free program could\nmake it effectively proprietary.  To prevent this, the GPL assures that\npatents cannot be used to render the program non-free.\n\n  The precise terms and conditions for copying, distribution and\nmodification follow.\n\n                       TERMS AND CONDITIONS\n\n  0. Definitions.\n\n  \"This License\" refers to version 3 of the GNU General Public License.\n\n  \"Copyright\" also means copyright-like laws that apply to other kinds of\nworks, such as semiconductor masks.\n\n  \"The Program\" refers to any copyrightable work licensed under this\nLicense.  Each licensee is addressed as \"you\".  \"Licensees\" and\n\"recipients\" may be individuals or organizations.\n\n  To \"modify\" a work means to copy from or adapt all or part of the work\nin a fashion requiring copyright permission, other than the making of an\nexact copy.  The resulting work is called a \"modified version\" of the\nearlier work or a work \"based on\" the earlier work.\n\n  A \"covered work\" means either the unmodified Program or a work based\non the Program.\n\n  To \"propagate\" a work means to do anything with it that, without\npermission, would make you directly or secondarily liable for\ninfringement under applicable copyright law, except executing it on a\ncomputer or modifying a private copy.  Propagation includes copying,\ndistribution (with or without modification), making available to the\npublic, and in some countries other activities as well.\n\n  To \"convey\" a work means any kind of propagation that enables other\nparties to make or receive copies.  Mere interaction with a user through\na computer network, with no transfer of a copy, is not conveying.\n\n  An interactive user interface displays \"Appropriate Legal Notices\"\nto the extent that it includes a convenient and prominently visible\nfeature that (1) displays an appropriate copyright notice, and (2)\ntells the user that there is no warranty for the work (except to the\nextent that warranties are provided), that licensees may convey the\nwork under this License, and how to view a copy of this License.  If\nthe interface presents a list of user commands or options, such as a\nmenu, a prominent item in the list meets this criterion.\n\n  1. Source Code.\n\n  The \"source code\" for a work means the preferred form of the work\nfor making modifications to it.  \"Object code\" means any non-source\nform of a work.\n\n  A \"Standard Interface\" means an interface that either is an official\nstandard defined by a recognized standards body, or, in the case of\ninterfaces specified for a particular programming language, one that\nis widely used among developers working in that language.\n\n  The \"System Libraries\" of an executable work include anything, other\nthan the work as a whole, that (a) is included in the normal form of\npackaging a Major Component, but which is not part of that Major\nComponent, and (b) serves only to enable use of the work with that\nMajor Component, or to implement a Standard Interface for which an\nimplementation is available to the public in source code form.  A\n\"Major Component\", in this context, means a major essential component\n(kernel, window system, and so on) of the specific operating system\n(if any) on which the executable work runs, or a compiler used to\nproduce the work, or an object code interpreter used to run it.\n\n  The \"Corresponding Source\" for a work in object code form means all\nthe source code needed to generate, install, and (for an executable\nwork) run the object code and to modify the work, including scripts to\ncontrol those activities.  However, it does not include the work's\nSystem Libraries, or general-purpose tools or generally available free\nprograms which are used unmodified in performing those activities but\nwhich are not part of the work.  For example, Corresponding Source\nincludes interface definition files associated with source files for\nthe work, and the source code for shared libraries and dynamically\nlinked subprograms that the work is specifically designed to require,\nsuch as by intimate data communication or control flow between those\nsubprograms and other parts of the work.\n\n  The Corresponding Source need not include anything that users\ncan regenerate automatically from other parts of the Corresponding\nSource.\n\n  The Corresponding Source for a work in source code form is that\nsame work.\n\n  2. Basic Permissions.\n\n  All rights granted under this License are granted for the term of\ncopyright on the Program, and are irrevocable provided the stated\nconditions are met.  This License explicitly affirms your unlimited\npermission to run the unmodified Program.  The output from running a\ncovered work is covered by this License only if the output, given its\ncontent, constitutes a covered work.  This License acknowledges your\nrights of fair use or other equivalent, as provided by copyright law.\n\n  You may make, run and propagate covered works that you do not\nconvey, without conditions so long as your license otherwise remains\nin force.  You may convey covered works to others for the sole purpose\nof having them make modifications exclusively for you, or provide you\nwith facilities for running those works, provided that you comply with\nthe terms of this License in conveying all material for which you do\nnot control copyright.  Those thus making or running the covered works\nfor you must do so exclusively on your behalf, under your direction\nand control, on terms that prohibit them from making any copies of\nyour copyrighted material outside their relationship with you.\n\n  Conveying under any other circumstances is permitted solely under\nthe conditions stated below.  Sublicensing is not allowed; section 10\nmakes it unnecessary.\n\n  3. Protecting Users' Legal Rights From Anti-Circumvention Law.\n\n  No covered work shall be deemed part of an effective technological\nmeasure under any applicable law fulfilling obligations under article\n11 of the WIPO copyright treaty adopted on 20 December 1996, or\nsimilar laws prohibiting or restricting circumvention of such\nmeasures.\n\n  When you convey a covered work, you waive any legal power to forbid\ncircumvention of technological measures to the extent such circumvention\nis effected by exercising rights under this License with respect to\nthe covered work, and you disclaim any intention to limit operation or\nmodification of the work as a means of enforcing, against the work's\nusers, your or third parties' legal rights to forbid circumvention of\ntechnological measures.\n\n  4. Conveying Verbatim Copies.\n\n  You may convey verbatim copies of the Program's source code as you\nreceive it, in any medium, provided that you conspicuously and\nappropriately publish on each copy an appropriate copyright notice;\nkeep intact all notices stating that this License and any\nnon-permissive terms added in accord with section 7 apply to the code;\nkeep intact all notices of the absence of any warranty; and give all\nrecipients a copy of this License along with the Program.\n\n  You may charge any price or no price for each copy that you convey,\nand you may offer support or warranty protection for a fee.\n\n  5. Conveying Modified Source Versions.\n\n  You may convey a work based on the Program, or the modifications to\nproduce it from the Program, in the form of source code under the\nterms of section 4, provided that you also meet all of these conditions:\n\n    a) The work must carry prominent notices stating that you modified\n    it, and giving a relevant date.\n\n    b) The work must carry prominent notices stating that it is\n    released under this License and any conditions added under section\n    7.  This requirement modifies the requirement in section 4 to\n    \"keep intact all notices\".\n\n    c) You must license the entire work, as a whole, under this\n    License to anyone who comes into possession of a copy.  This\n    License will therefore apply, along with any applicable section 7\n    additional terms, to the whole of the work, and all its parts,\n    regardless of how they are packaged.  This License gives no\n    permission to license the work in any other way, but it does not\n    invalidate such permission if you have separately received it.\n\n    d) If the work has interactive user interfaces, each must display\n    Appropriate Legal Notices; however, if the Program has interactive\n    interfaces that do not display Appropriate Legal Notices, your\n    work need not make them do so.\n\n  A compilation of a covered work with other separate and independent\nworks, which are not by their nature extensions of the covered work,\nand which are not combined with it such as to form a larger program,\nin or on a volume of a storage or distribution medium, is called an\n\"aggregate\" if the compilation and its resulting copyright are not\nused to limit the access or legal rights of the compilation's users\nbeyond what the individual works permit.  Inclusion of a covered work\nin an aggregate does not cause this License to apply to the other\nparts of the aggregate.\n\n  6. Conveying Non-Source Forms.\n\n  You may convey a covered work in object code form under the terms\nof sections 4 and 5, provided that you also convey the\nmachine-readable Corresponding Source under the terms of this License,\nin one of these ways:\n\n    a) Convey the object code in, or embodied in, a physical product\n    (including a physical distribution medium), accompanied by the\n    Corresponding Source fixed on a durable physical medium\n    customarily used for software interchange.\n\n    b) Convey the object code in, or embodied in, a physical product\n    (including a physical distribution medium), accompanied by a\n    written offer, valid for at least three years and valid for as\n    long as you offer spare parts or customer support for that product\n    model, to give anyone who possesses the object code either (1) a\n    copy of the Corresponding Source for all the software in the\n    product that is covered by this License, on a durable physical\n    medium customarily used for software interchange, for a price no\n    more than your reasonable cost of physically performing this\n    conveying of source, or (2) access to copy the\n    Corresponding Source from a network server at no charge.\n\n    c) Convey individual copies of the object code with a copy of the\n    written offer to provide the Corresponding Source.  This\n    alternative is allowed only occasionally and noncommercially, and\n    only if you received the object code with such an offer, in accord\n    with subsection 6b.\n\n    d) Convey the object code by offering access from a designated\n    place (gratis or for a charge), and offer equivalent access to the\n    Corresponding Source in the same way through the same place at no\n    further charge.  You need not require recipients to copy the\n    Corresponding Source along with the object code.  If the place to\n    copy the object code is a network server, the Corresponding Source\n    may be on a different server (operated by you or a third party)\n    that supports equivalent copying facilities, provided you maintain\n    clear directions next to the object code saying where to find the\n    Corresponding Source.  Regardless of what server hosts the\n    Corresponding Source, you remain obligated to ensure that it is\n    available for as long as needed to satisfy these requirements.\n\n    e) Convey the object code using peer-to-peer transmission, provided\n    you inform other peers where the object code and Corresponding\n    Source of the work are being offered to the general public at no\n    charge under subsection 6d.\n\n  A separable portion of the object code, whose source code is excluded\nfrom the Corresponding Source as a System Library, need not be\nincluded in conveying the object code work.\n\n  A \"User Product\" is either (1) a \"consumer product\", which means any\ntangible personal property which is normally used for personal, family,\nor household purposes, or (2) anything designed or sold for incorporation\ninto a dwelling.  In determining whether a product is a consumer product,\ndoubtful cases shall be resolved in favor of coverage.  For a particular\nproduct received by a particular user, \"normally used\" refers to a\ntypical or common use of that class of product, regardless of the status\nof the particular user or of the way in which the particular user\nactually uses, or expects or is expected to use, the product.  A product\nis a consumer product regardless of whether the product has substantial\ncommercial, industrial or non-consumer uses, unless such uses represent\nthe only significant mode of use of the product.\n\n  \"Installation Information\" for a User Product means any methods,\nprocedures, authorization keys, or other information required to install\nand execute modified versions of a covered work in that User Product from\na modified version of its Corresponding Source.  The information must\nsuffice to ensure that the continued functioning of the modified object\ncode is in no case prevented or interfered with solely because\nmodification has been made.\n\n  If you convey an object code work under this section in, or with, or\nspecifically for use in, a User Product, and the conveying occurs as\npart of a transaction in which the right of possession and use of the\nUser Product is transferred to the recipient in perpetuity or for a\nfixed term (regardless of how the transaction is characterized), the\nCorresponding Source conveyed under this section must be accompanied\nby the Installation Information.  But this requirement does not apply\nif neither you nor any third party retains the ability to install\nmodified object code on the User Product (for example, the work has\nbeen installed in ROM).\n\n  The requirement to provide Installation Information does not include a\nrequirement to continue to provide support service, warranty, or updates\nfor a work that has been modified or installed by the recipient, or for\nthe User Product in which it has been modified or installed.  Access to a\nnetwork may be denied when the modification itself materially and\nadversely affects the operation of the network or violates the rules and\nprotocols for communication across the network.\n\n  Corresponding Source conveyed, and Installation Information provided,\nin accord with this section must be in a format that is publicly\ndocumented (and with an implementation available to the public in\nsource code form), and must require no special password or key for\nunpacking, reading or copying.\n\n  7. Additional Terms.\n\n  \"Additional permissions\" are terms that supplement the terms of this\nLicense by making exceptions from one or more of its conditions.\nAdditional permissions that are applicable to the entire Program shall\nbe treated as though they were included in this License, to the extent\nthat they are valid under applicable law.  If additional permissions\napply only to part of the Program, that part may be used separately\nunder those permissions, but the entire Program remains governed by\nthis License without regard to the additional permissions.\n\n  When you convey a copy of a covered work, you may at your option\nremove any additional permissions from that copy, or from any part of\nit.  (Additional permissions may be written to require their own\nremoval in certain cases when you modify the work.)  You may place\nadditional permissions on material, added by you to a covered work,\nfor which you have or can give appropriate copyright permission.\n\n  Notwithstanding any other provision of this License, for material you\nadd to a covered work, you may (if authorized by the copyright holders of\nthat material) supplement the terms of this License with terms:\n\n    a) Disclaiming warranty or limiting liability differently from the\n    terms of sections 15 and 16 of this License; or\n\n    b) Requiring preservation of specified reasonable legal notices or\n    author attributions in that material or in the Appropriate Legal\n    Notices displayed by works containing it; or\n\n    c) Prohibiting misrepresentation of the origin of that material, or\n    requiring that modified versions of such material be marked in\n    reasonable ways as different from the original version; or\n\n    d) Limiting the use for publicity purposes of names of licensors or\n    authors of the material; or\n\n    e) Declining to grant rights under trademark law for use of some\n    trade names, trademarks, or service marks; or\n\n    f) Requiring indemnification of licensors and authors of that\n    material by anyone who conveys the material (or modified versions of\n    it) with contractual assumptions of liability to the recipient, for\n    any liability that these contractual assumptions directly impose on\n    those licensors and authors.\n\n  All other non-permissive additional terms are considered \"further\nrestrictions\" within the meaning of section 10.  If the Program as you\nreceived it, or any part of it, contains a notice stating that it is\ngoverned by this License along with a term that is a further\nrestriction, you may remove that term.  If a license document contains\na further restriction but permits relicensing or conveying under this\nLicense, you may add to a covered work material governed by the terms\nof that license document, provided that the further restriction does\nnot survive such relicensing or conveying.\n\n  If you add terms to a covered work in accord with this section, you\nmust place, in the relevant source files, a statement of the\nadditional terms that apply to those files, or a notice indicating\nwhere to find the applicable terms.\n\n  Additional terms, permissive or non-permissive, may be stated in the\nform of a separately written license, or stated as exceptions;\nthe above requirements apply either way.\n\n  8. Termination.\n\n  You may not propagate or modify a covered work except as expressly\nprovided under this License.  Any attempt otherwise to propagate or\nmodify it is void, and will automatically terminate your rights under\nthis License (including any patent licenses granted under the third\nparagraph of section 11).\n\n  However, if you cease all violation of this License, then your\nlicense from a particular copyright holder is reinstated (a)\nprovisionally, unless and until the copyright holder explicitly and\nfinally terminates your license, and (b) permanently, if the copyright\nholder fails to notify you of the violation by some reasonable means\nprior to 60 days after the cessation.\n\n  Moreover, your license from a particular copyright holder is\nreinstated permanently if the copyright holder notifies you of the\nviolation by some reasonable means, this is the first time you have\nreceived notice of violation of this License (for any work) from that\ncopyright holder, and you cure the violation prior to 30 days after\nyour receipt of the notice.\n\n  Termination of your rights under this section does not terminate the\nlicenses of parties who have received copies or rights from you under\nthis License.  If your rights have been terminated and not permanently\nreinstated, you do not qualify to receive new licenses for the same\nmaterial under section 10.\n\n  9. Acceptance Not Required for Having Copies.\n\n  You are not required to accept this License in order to receive or\nrun a copy of the Program.  Ancillary propagation of a covered work\noccurring solely as a consequence of using peer-to-peer transmission\nto receive a copy likewise does not require acceptance.  However,\nnothing other than this License grants you permission to propagate or\nmodify any covered work.  These actions infringe copyright if you do\nnot accept this License.  Therefore, by modifying or propagating a\ncovered work, you indicate your acceptance of this License to do so.\n\n  10. Automatic Licensing of Downstream Recipients.\n\n  Each time you convey a covered work, the recipient automatically\nreceives a license from the original licensors, to run, modify and\npropagate that work, subject to this License.  You are not responsible\nfor enforcing compliance by third parties with this License.\n\n  An \"entity transaction\" is a transaction transferring control of an\norganization, or substantially all assets of one, or subdividing an\norganization, or merging organizations.  If propagation of a covered\nwork results from an entity transaction, each party to that\ntransaction who receives a copy of the work also receives whatever\nlicenses to the work the party's predecessor in interest had or could\ngive under the previous paragraph, plus a right to possession of the\nCorresponding Source of the work from the predecessor in interest, if\nthe predecessor has it or can get it with reasonable efforts.\n\n  You may not impose any further restrictions on the exercise of the\nrights granted or affirmed under this License.  For example, you may\nnot impose a license fee, royalty, or other charge for exercise of\nrights granted under this License, and you may not initiate litigation\n(including a cross-claim or counterclaim in a lawsuit) alleging that\nany patent claim is infringed by making, using, selling, offering for\nsale, or importing the Program or any portion of it.\n\n  11. Patents.\n\n  A \"contributor\" is a copyright holder who authorizes use under this\nLicense of the Program or a work on which the Program is based.  The\nwork thus licensed is called the contributor's \"contributor version\".\n\n  A contributor's \"essential patent claims\" are all patent claims\nowned or controlled by the contributor, whether already acquired or\nhereafter acquired, that would be infringed by some manner, permitted\nby this License, of making, using, or selling its contributor version,\nbut do not include claims that would be infringed only as a\nconsequence of further modification of the contributor version.  For\npurposes of this definition, \"control\" includes the right to grant\npatent sublicenses in a manner consistent with the requirements of\nthis License.\n\n  Each contributor grants you a non-exclusive, worldwide, royalty-free\npatent license under the contributor's essential patent claims, to\nmake, use, sell, offer for sale, import and otherwise run, modify and\npropagate the contents of its contributor version.\n\n  In the following three paragraphs, a \"patent license\" is any express\nagreement or commitment, however denominated, not to enforce a patent\n(such as an express permission to practice a patent or covenant not to\nsue for patent infringement).  To \"grant\" such a patent license to a\nparty means to make such an agreement or commitment not to enforce a\npatent against the party.\n\n  If you convey a covered work, knowingly relying on a patent license,\nand the Corresponding Source of the work is not available for anyone\nto copy, free of charge and under the terms of this License, through a\npublicly available network server or other readily accessible means,\nthen you must either (1) cause the Corresponding Source to be so\navailable, or (2) arrange to deprive yourself of the benefit of the\npatent license for this particular work, or (3) arrange, in a manner\nconsistent with the requirements of this License, to extend the patent\nlicense to downstream recipients.  \"Knowingly relying\" means you have\nactual knowledge that, but for the patent license, your conveying the\ncovered work in a country, or your recipient's use of the covered work\nin a country, would infringe one or more identifiable patents in that\ncountry that you have reason to believe are valid.\n\n  If, pursuant to or in connection with a single transaction or\narrangement, you convey, or propagate by procuring conveyance of, a\ncovered work, and grant a patent license to some of the parties\nreceiving the covered work authorizing them to use, propagate, modify\nor convey a specific copy of the covered work, then the patent license\nyou grant is automatically extended to all recipients of the covered\nwork and works based on it.\n\n  A patent license is \"discriminatory\" if it does not include within\nthe scope of its coverage, prohibits the exercise of, or is\nconditioned on the non-exercise of one or more of the rights that are\nspecifically granted under this License.  You may not convey a covered\nwork if you are a party to an arrangement with a third party that is\nin the business of distributing software, under which you make payment\nto the third party based on the extent of your activity of conveying\nthe work, and under which the third party grants, to any of the\nparties who would receive the covered work from you, a discriminatory\npatent license (a) in connection with copies of the covered work\nconveyed by you (or copies made from those copies), or (b) primarily\nfor and in connection with specific products or compilations that\ncontain the covered work, unless you entered into that arrangement,\nor that patent license was granted, prior to 28 March 2007.\n\n  Nothing in this License shall be construed as excluding or limiting\nany implied license or other defenses to infringement that may\notherwise be available to you under applicable patent law.\n\n  12. No Surrender of Others' Freedom.\n\n  If conditions are imposed on you (whether by court order, agreement or\notherwise) that contradict the conditions of this License, they do not\nexcuse you from the conditions of this License.  If you cannot convey a\ncovered work so as to satisfy simultaneously your obligations under this\nLicense and any other pertinent obligations, then as a consequence you may\nnot convey it at all.  For example, if you agree to terms that obligate you\nto collect a royalty for further conveying from those to whom you convey\nthe Program, the only way you could satisfy both those terms and this\nLicense would be to refrain entirely from conveying the Program.\n\n  13. Use with the GNU Affero General Public License.\n\n  Notwithstanding any other provision of this License, you have\npermission to link or combine any covered work with a work licensed\nunder version 3 of the GNU Affero General Public License into a single\ncombined work, and to convey the resulting work.  The terms of this\nLicense will continue to apply to the part which is the covered work,\nbut the special requirements of the GNU Affero General Public License,\nsection 13, concerning interaction through a network will apply to the\ncombination as such.\n\n  14. Revised Versions of this License.\n\n  The Free Software Foundation may publish revised and/or new versions of\nthe GNU General Public License from time to time.  Such new versions will\nbe similar in spirit to the present version, but may differ in detail to\naddress new problems or concerns.\n\n  Each version is given a distinguishing version number.  If the\nProgram specifies that a certain numbered version of the GNU General\nPublic License \"or any later version\" applies to it, you have the\noption of following the terms and conditions either of that numbered\nversion or of any later version published by the Free Software\nFoundation.  If the Program does not specify a version number of the\nGNU General Public License, you may choose any version ever published\nby the Free Software Foundation.\n\n  If the Program specifies that a proxy can decide which future\nversions of the GNU General Public License can be used, that proxy's\npublic statement of acceptance of a version permanently authorizes you\nto choose that version for the Program.\n\n  Later license versions may give you additional or different\npermissions.  However, no additional obligations are imposed on any\nauthor or copyright holder as a result of your choosing to follow a\nlater version.\n\n  15. Disclaimer of Warranty.\n\n  THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY\nAPPLICABLE LAW.  EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT\nHOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM \"AS IS\" WITHOUT WARRANTY\nOF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,\nTHE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR\nPURPOSE.  THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM\nIS WITH YOU.  SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF\nALL NECESSARY SERVICING, REPAIR OR CORRECTION.\n\n  16. Limitation of Liability.\n\n  IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING\nWILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS\nTHE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY\nGENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE\nUSE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF\nDATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD\nPARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),\nEVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF\nSUCH DAMAGES.\n\n  17. Interpretation of Sections 15 and 16.\n\n  If the disclaimer of warranty and limitation of liability provided\nabove cannot be given local legal effect according to their terms,\nreviewing courts shall apply local law that most closely approximates\nan absolute waiver of all civil liability in connection with the\nProgram, unless a warranty or assumption of liability accompanies a\ncopy of the Program in return for a fee.\n\n                     END OF TERMS AND CONDITIONS\n\n            How to Apply These Terms to Your New Programs\n\n  If you develop a new program, and you want it to be of the greatest\npossible use to the public, the best way to achieve this is to make it\nfree software which everyone can redistribute and change under these terms.\n\n  To do so, attach the following notices to the program.  It is safest\nto attach them to the start of each source file to most effectively\nstate the exclusion of warranty; and each file should have at least\nthe \"copyright\" line and a pointer to where the full notice is found.\n\n    <one line to give the program's name and a brief idea of what it does.>\n    Copyright (C) <year>  <name of author>\n\n    This program is free software: you can redistribute it and/or modify\n    it under the terms of the GNU General Public License as published by\n    the Free Software Foundation, either version 3 of the License, or\n    (at your option) any later version.\n\n    This program is distributed in the hope that it will be useful,\n    but WITHOUT ANY WARRANTY; without even the implied warranty of\n    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the\n    GNU General Public License for more details.\n\n    You should have received a copy of the GNU General Public License\n    along with this program.  If not, see <https://www.gnu.org/licenses/>.\n\nAlso add information on how to contact you by electronic and paper mail.\n\n  If the program does terminal interaction, make it output a short\nnotice like this when it starts in an interactive mode:\n\n    <program>  Copyright (C) <year>  <name of author>\n    This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.\n    This is free software, and you are welcome to redistribute it\n    under certain conditions; type `show c' for details.\n\nThe hypothetical commands `show w' and `show c' should show the appropriate\nparts of the General Public License.  Of course, your program's commands\nmight be different; for a GUI interface, you would use an \"about box\".\n\n  You should also get your employer (if you work as a programmer) or school,\nif any, to sign a \"copyright disclaimer\" for the program, if necessary.\nFor more information on this, and how to apply and follow the GNU GPL, see\n<https://www.gnu.org/licenses/>.\n\n  The GNU General Public License does not permit incorporating your program\ninto proprietary programs.  If your program is a subroutine library, you\nmay consider it more useful to permit linking proprietary applications with\nthe library.  If this is what you want to do, use the GNU Lesser General\nPublic License instead of this License.  But first, please read\n<https://www.gnu.org/licenses/why-not-lgpl.html>.\n"
  },
  {
    "path": "modules/web/README.md",
    "content": "# 阅读web端\n 使用vue3 web书架和web源编辑\n## 路由\n* http://localhost:8080/ 书架\n* http://localhost:8080/#/bookSource 书源编辑\n* http://localhost:8080/#/rssSource 订阅源编辑\n\n## 兼容性\n\n| ![IE](https://cdn.jsdelivr.net/npm/@browser-logos/edge/edge_32x32.png) | ![Firefox](https://cdn.jsdelivr.net/npm/@browser-logos/firefox/firefox_32x32.png) | ![Chrome](https://cdn.jsdelivr.net/npm/@browser-logos/chrome/chrome_32x32.png) | ![Safari](https://cdn.jsdelivr.net/npm/@browser-logos/safari/safari_32x32.png) |\n| ---------------------------------------------------------------------- | --------------------------------------------------------------------------------- | ------------------------------------------------------------------------------ | ------------------------------------------------------------------------------ |\n| Edge ≥ 85                                                              | Firefox ≥ 79                                                                      | Chrome ≥ 85                                                                    | Safari ≥ 14.1                                                                    |\n\n## 开发\n> 需要阅读app提供后端服务\n\n```bash\npnpm dev\n```\n"
  },
  {
    "path": "modules/web/env.d.ts",
    "content": "/// <reference types=\"vite/client\" />\n\ndeclare module \"vue3-virtual-scroll-list\";\n"
  },
  {
    "path": "modules/web/eslint.config.mjs",
    "content": "import pluginVue from 'eslint-plugin-vue'\nimport vueTsEslintConfig from '@vue/eslint-config-typescript'\nimport skipFormatting from '@vue/eslint-config-prettier/skip-formatting'\n\nexport default [\n  {\n    name: 'app/files-to-lint',\n    files: ['**/*.{ts,mts,tsx,vue}'],\n  },\n\n  {\n    name: 'app/files-to-ignore',\n    ignores: ['**/dist/**', '**/dist-ssr/**', '**/coverage/**', \"src/plugins/jump.js\"],\n  },\n\n  ...pluginVue.configs['flat/essential'],\n  ...vueTsEslintConfig(),\n  skipFormatting,\n]\n"
  },
  {
    "path": "modules/web/index.html",
    "content": "<!DOCTYPE html>\r\n<html lang=\"zh\" class=\"\">\r\n    <head>\r\n        <meta charset=\"UTF-8\" />\r\n        <link rel=\"icon\" href=\"/favicon.ico\" />\r\n    <meta name=\"viewport\" content=\"width=device-width,initial-scale=1.0\" />\r\n    </head>\r\n\r\n    <body>\r\n        <div id=\"app\"></div>\r\n        <script type=\"module\" src=\"./src/main.ts\"></script>\r\n    </body>\r\n</html>"
  },
  {
    "path": "modules/web/package.json",
    "content": "{\n  \"name\": \"legado-web\",\n  \"version\": \"1.0.0\",\n  \"private\": true,\n  \"type\": \"module\",\n  \"engines\": {\n    \"node\": \">=20\",\n    \"pnpm\": \">=9\"\n  },\n  \"scripts\": {\n    \"dev\": \"vite\",\n    \"build\": \"run-p type-check \\\"build-only {@}\\\" -- && node ./scripts/sync.js\",\n    \"preview\": \"vite preview\",\n    \"build-only\": \"vite build\",\n    \"type-check\": \"vue-tsc --build --force\",\n    \"lint:fix\": \"eslint . --fix\",\n    \"format\": \"prettier --write src/\"\n  },\n  \"dependencies\": {\n    \"@element-plus/icons-svg\": \"^2.3.1\",\n    \"@element-plus/icons-vue\": \"^2.3.1\",\n    \"@vueuse/core\": \"^11.1.0\",\n    \"@vueuse/shared\": \"^11.1.0\",\n    \"axios\": \"^1.7.7\",\n    \"element-plus\": \"2.8.5\",\n    \"hotkeys-js\": \"^3.13.7\",\n    \"pinia\": \"^2.2.4\",\n    \"vue\": \"^3.5.12\",\n    \"vue-router\": \"^4.4.5\",\n    \"vue3-virtual-scroll-list\": \"^0.2.1\"\n  },\n  \"devDependencies\": {\n    \"sass-embedded\": \"^1.83.1\",\n    \"@tsconfig/node20\": \"^20.1.4\",\n    \"@types/node\": \"^20.16.11\",\n    \"@vitejs/plugin-vue\": \"^5.1.4\",\n    \"@vue/eslint-config-prettier\": \"^10.0.0\",\n    \"@vue/eslint-config-typescript\": \"^14.0.1\",\n    \"@vue/tsconfig\": \"^0.5.1\",\n    \"eslint\": \"^9.12.0\",\n    \"eslint-plugin-vue\": \"^9.29.0\",\n    \"npm-run-all2\": \"^6.2.3\",\n    \"prettier\": \"^3.3.3\",\n    \"typescript\": \"~5.5.4\",\n    \"unplugin-auto-import\": \"^0.18.3\",\n    \"unplugin-icons\": \"^0.19.3\",\n    \"unplugin-vue-components\": \"^0.27.4\",\n    \"vite\": \"^5.4.8\",\n    \"vue-tsc\": \"^2.1.6\"\n  }\n}\n"
  },
  {
    "path": "modules/web/scripts/sync.js",
    "content": "import { URL } from \"node:url\";\nimport fs from \"node:fs\";\nimport process from \"node:process\";\n\nif (!process.env.GITHUB_ENV) {\n  console.log(\"非Github WorkFlows环境，取消文件复制\");\n  process.exit();\n}\nconst LEGADO_ASSETS_WEB_VUE_DIR = new URL(\n  \"../../../app/src/main/assets/web/vue\",\n  import.meta.url,\n);\nconst VUE_DIST_DIR = new URL(\"../dist\", import.meta.url);\n\nconsole.log(\"> delete\", LEGADO_ASSETS_WEB_VUE_DIR.pathname);\n// 删除\nfs.rm(\n  LEGADO_ASSETS_WEB_VUE_DIR,\n  {\n    force: true,\n    recursive: true,\n  },\n  (error) => {\n    if (error) console.log(error);\n    console.log(\"> mkdir\", LEGADO_ASSETS_WEB_VUE_DIR.pathname);\n    fs.mkdir(LEGADO_ASSETS_WEB_VUE_DIR, (error) => {\n      if (error) return console.error(error);\n      console.log(\"> cp dist files\");\n      fs.cp(\n        VUE_DIST_DIR,\n        LEGADO_ASSETS_WEB_VUE_DIR,\n        {\n          recursive: true,\n        },\n        (error) => {\n          if (error) {\n            console.warn(\"> cp error, you may copy files yourshelf\");\n            throw error;\n          }\n          console.log(\"> cp success\");\n        },\n      );\n    });\n  },\n);\n"
  },
  {
    "path": "modules/web/src/App.vue",
    "content": "<template>\n  <router-view></router-view>\n</template>\n"
  },
  {
    "path": "modules/web/src/api/api.ts",
    "content": "/** https://github.com/gedoor/legado/tree/master/app/src/main/java/io/legado/app/api */\n/** https://github.com/gedoor/legado/tree/master/app/src/main/java/io/legado/app/web */\n\nimport type { webReadConfig } from '@/web'\nimport ajax from './axios'\nimport type {\n  BaseBook,\n  Book,\n  BookChapter,\n  BookProgress,\n  SeachBook,\n} from '@/book'\nimport type { Source } from '@/source'\n\nexport type LeagdoApiResponse<T> = {\n  isSuccess: boolean\n  errorMsg: string\n  data: T\n}\n\nexport let legado_http_entry_point = ''\nexport let legado_webSocket_entry_point = ''\n\nlet wsOnError: typeof WebSocket.prototype.onerror = () => {}\nlet wsOnMessage: typeof WebSocket.prototype.onmessage = () => {}\nexport const setWebsocketOnMessage = (callback: typeof wsOnMessage) =>\n  (wsOnMessage = callback)\nexport const setWebsocketOnError = (callback: typeof wsOnError) => {\n  //WebSocket.prototype.onerror = callback\n  wsOnError = callback\n}\n\nexport const setApiEntryPoint = (\n  http_entry_point: string,\n  webSocket_entry_point: string,\n) => {\n  legado_http_entry_point = new URL(http_entry_point).toString()\n  legado_webSocket_entry_point = new URL(webSocket_entry_point).toString()\n  ajax.defaults.baseURL = legado_http_entry_point\n}\n\n// 书架API\n// Http\nconst getReadConfig = async (http_url = legado_http_entry_point) => {\n  const { data } = await ajax.get<LeagdoApiResponse<string>>('getReadConfig', {\n    baseURL: http_url.toString(),\n    timeout: 3000,\n  })\n  if (data.isSuccess) {\n    try {\n      return JSON.parse(data.data) as webReadConfig\n    } catch {}\n  }\n}\nconst saveReadConfig = (config: webReadConfig) =>\n  ajax.post<LeagdoApiResponse<string>>('saveReadConfig', config)\n\n/** @deprecated: 使用`API.saveBookProgressWithBeacon`以确保在页面或者直接关闭的情况下保存进度 */\nconst saveBookProgress = (bookProgress: BookProgress) =>\n  ajax.post('saveBookProgress', bookProgress)\n\n/**主要在直接关闭浏览器情况下可靠发送书籍进度 */\nconst saveBookProgressWithBeacon = (bookProgress: BookProgress) => {\n  if (!bookProgress) return\n  // 常规请求可能会被取消 使用Fetch keep-alive 或者 navigator.sendBeacon\n  navigator.sendBeacon(\n    new URL('saveBookProgress', legado_http_entry_point),\n    JSON.stringify(bookProgress),\n  )\n}\n\nconst getBookShelf = () => ajax.get<LeagdoApiResponse<Book[]>>('getBookshelf')\n\nconst getChapterList = (/** @type {string} */ bookUrl: string) =>\n  ajax.get<LeagdoApiResponse<BookChapter[]>>(\n    'getChapterList?url=' + encodeURIComponent(bookUrl),\n  )\n\nconst getBookContent = (\n  /** @type {string} */ bookUrl: string,\n  /** @type {number} */ chapterIndex: number,\n) =>\n  ajax.get<LeagdoApiResponse<string>>(\n    'getBookContent?url=' +\n      encodeURIComponent(bookUrl) +\n      '&index=' +\n      chapterIndex,\n  )\n\n// webSocket\nconst search = (\n  searchKey: string,\n  onReceive: (data: SeachBook[]) => void,\n  onFinish: () => void,\n) => {\n  const socket = new WebSocket(\n    new URL('searchBook', legado_webSocket_entry_point),\n  )\n  socket.onerror = wsOnError\n\n  socket.onopen = () => {\n    socket.send(`{\"key\":\"${searchKey}\"}`)\n  }\n  socket.onmessage = event => {\n    try {\n      onReceive(JSON.parse(event.data))\n      wsOnMessage?.call(socket, event)\n    } catch {\n      onFinish()\n    }\n  }\n\n  socket.onclose = () => {\n    onFinish()\n  }\n}\n\nconst saveBook = (book: BaseBook) =>\n  ajax.post<LeagdoApiResponse<string>>('saveBook', book)\nconst deleteBook = (book: BaseBook) =>\n  ajax.post<LeagdoApiResponse<string>>('deleteBook', book)\n\nconst isBookSource = /bookSource/i.test(location.href)\n\n// 源编辑API\n// Http\nconst getSources = () =>\n  isBookSource ? ajax.get('getBookSources') : ajax.get('getRssSources')\n\nconst saveSource = (data: Source) =>\n  isBookSource\n    ? ajax.post<LeagdoApiResponse<string>>('saveBookSource', data)\n    : ajax.post<LeagdoApiResponse<string>>('saveRssSource', data)\n\nconst saveSources = (data: Source[]) =>\n  isBookSource\n    ? ajax.post<LeagdoApiResponse<Source[]>>('saveBookSources', data)\n    : ajax.post<LeagdoApiResponse<Source[]>>('saveRssSources', data)\n\nconst deleteSource = (data: Source[]) =>\n  isBookSource\n    ? ajax.post<LeagdoApiResponse<string>>('deleteBookSources', data)\n    : ajax.post<LeagdoApiResponse<string>>('deleteRssSources', data)\n\n// webSocket\nconst debug = (\n  /** @type {string} */ sourceUrl: string,\n  /** @type {string} */ searchKey: string,\n  /** @type {(data: string) => void} */ onReceive: (data: string) => void,\n  /** @type {() => void} */ onFinish: () => void,\n) => {\n  const url = new URL(\n    `${isBookSource ? 'bookSource' : 'rssSource'}Debug`,\n    legado_webSocket_entry_point,\n  )\n\n  const socket = new WebSocket(url)\n  socket.onerror = wsOnError\n  socket.onopen = () => {\n    socket.send(JSON.stringify({ tag: sourceUrl, key: searchKey }))\n  }\n  socket.onmessage = event => {\n    onReceive(event.data)\n    wsOnMessage?.call(socket, event)\n  }\n\n  socket.onclose = () => {\n    onFinish()\n  }\n}\n\n/**\n * 从阅读获取需要特定处理的书籍封面\n * @param {string} coverUrl\n */\nconst getProxyCoverUrl = (coverUrl: string) => {\n  if (coverUrl.startsWith(legado_http_entry_point)) return coverUrl\n  return new URL(\n    'cover?path=' + encodeURIComponent(coverUrl),\n    legado_http_entry_point,\n  ).toString()\n}\n/**\n * 从阅读获取需要特定处理的图片\n * @param {string} bookUrl\n * @param {string} src\n * @param {number|`${number}`} width\n */\nconst getProxyImageUrl = (\n  bookUrl: string,\n  src: string,\n  width: number | `${number}`,\n) => {\n  if (src.startsWith(legado_http_entry_point)) return src\n  return new URL(\n    'image?path=' +\n      encodeURIComponent(src) +\n      '&url=' +\n      encodeURIComponent(bookUrl) +\n      '&width=' +\n      width,\n    legado_http_entry_point,\n  ).toString()\n}\n\nexport default {\n  getReadConfig,\n  saveReadConfig,\n  saveBookProgress,\n  saveBookProgressWithBeacon,\n  getBookShelf,\n  getChapterList,\n  getBookContent,\n  search,\n  saveBook,\n  deleteBook,\n\n  getSources,\n  saveSources,\n  saveSource,\n  deleteSource,\n  debug,\n\n  getProxyCoverUrl,\n  getProxyImageUrl,\n}\n"
  },
  {
    "path": "modules/web/src/api/axios.ts",
    "content": "import axios from 'axios'\n\n/** @type {string} localStorage保存自定义阅读http服务接口的键值 */\nexport const baseURL_localStorage_key = 'remoteUrl'\nconst SECOND = 1000\n\nconst ajax = axios.create({\n  baseURL:\n    import.meta.env.VITE_API ||\n    localStorage.getItem(baseURL_localStorage_key) ||\n    location.origin,\n  timeout: 120 * SECOND,\n})\n\nexport default ajax\n"
  },
  {
    "path": "modules/web/src/api/index.ts",
    "content": "import type { AxiosResponse } from 'axios'\nimport type { LeagdoApiResponse } from './api'\nimport API, {\n  setWebsocketOnError,\n  setApiEntryPoint,\n  legado_http_entry_point,\n  setWebsocketOnMessage,\n} from './api'\nimport ajax from './axios'\nimport { validatorHttpUrl } from '@/utils/utils'\n\nimport { createApp } from 'vue'\nimport App from '@/App.vue'\nimport store, { useConnectionStore } from '@/store'\n\ncreateApp(App).use(store)\nconst connectionStore = useConnectionStore()\n\nconst LeagdoApiResponseKeys: string[] = Array.of('isSuccess', 'errorMsg')\n\nconst notification = ElMessage\n/** Axios.Interceptor: check if resp is LeagaoLeagdoApiResponse*/\nconst responseCheckInterceptor = (resp: AxiosResponse) => {\n  let isLeagdoApiResponse = true\n  try {\n    const data = resp.data\n\n    for (const key of LeagdoApiResponseKeys) {\n      if (!(key in data)) {\n        isLeagdoApiResponse = false\n        LeagdoApiResponseKeys.length = 0\n      }\n    }\n    if ((data as LeagdoApiResponse<unknown>).isSuccess === true) {\n      if (!('data' in data)) {\n        isLeagdoApiResponse = false\n      }\n    }\n  } catch {\n    isLeagdoApiResponse = false\n  }\n  if (isLeagdoApiResponse === false) {\n    notification.warning({ message: '后端返回内容格式错误', grouping: true })\n    throw new Error()\n  }\n  connectionStore.setConnectType('primary')\n  connectionStore.setConnectStatus('已连接 ' + legado_http_entry_point)\n  return resp\n}\n\nconst axiosErrorInterceptor = (err: unknown) => {\n  notification.error({\n    message: '后端连接失败，请检查阅读WEB服务或者设置其它可用链接',\n    grouping: true,\n  })\n  connectionStore.setConnectType('danger')\n  connectionStore.setConnectStatus('连接异常')\n  throw err\n}\n// http全局\najax.interceptors.response.use(responseCheckInterceptor, axiosErrorInterceptor)\n// websocket\nsetWebsocketOnError(axiosErrorInterceptor)\nsetWebsocketOnMessage(() => {\n  connectionStore.setConnectType('primary')\n  connectionStore.setConnectStatus('已连接 ' + legado_http_entry_point)\n})\n/**\n * 按照阅读的默认规则 解析阅读HTTP WebSocket API入口地址\n * @returns [http_url, webSocekt_url]\n */\nexport const parseLeagdoHttpUrlWithDefault = (\n  http_url: string | URL,\n): [string, string] => {\n  let url = new URL(location.origin) //默认当前网址的origin部分\n  if (validatorHttpUrl(http_url)) {\n    url = new URL(http_url)\n  }\n  const { protocol, port } = url\n  // websocket服务端口 为http服务端口 + 1\n  let legado_webSocket_port\n  if (port !== '') {\n    legado_webSocket_port = String(Number(port) + 1)\n  } else {\n    legado_webSocket_port = protocol.startsWith('https:') ? '444' : '81'\n  }\n  // websocket协议是否为加密版本\n  const legado_webSocket_protocol = protocol.startsWith('https:')\n    ? 'wss://'\n    : 'ws://'\n\n  const http_entry_point = url.toString()\n\n  url.protocol = legado_webSocket_protocol\n  url.port = legado_webSocket_port\n  const webSocket_entry_point = url.toString()\n\n  console.info('legado_api_config:')\n  console.table({\n    'http API入口': http_entry_point,\n    'webSocket API入口': webSocket_entry_point,\n  })\n  return [http_entry_point, webSocket_entry_point]\n}\n\n//export const useLeagdoRemoteUrlDialog = () => { }\n\nsetApiEntryPoint(\n  ...parseLeagdoHttpUrlWithDefault(ajax.defaults.baseURL as string),\n)\n\nexport default API\nexport * from './api'\n"
  },
  {
    "path": "modules/web/src/assets/bookshelf.css",
    "content": "body {\n  padding: 0;\n  margin: 0;\n  height: 100vh;\n}\n\n#app {\n  font-family: 'Avenir', Helvetica, Arial, sans-serif;\n  -webkit-font-smoothing: antialiased;\n  -moz-osx-font-smoothing: grayscale;\n  color: #2c3e50;\n  margin: 0;\n  height: 100%;\n}\n"
  },
  {
    "path": "modules/web/src/assets/code.css",
    "content": "code {\n  border-radius: 4px;\n  padding: 0.15rem 0.5rem;\n  background-color: var(--el-fill-color-light);\n  transition:\n    color 0.25s,\n    background-color 0.5s;\n  font-size: 14px;\n}\n"
  },
  {
    "path": "modules/web/src/assets/fonts/iconfont.css",
    "content": "@charset \"UTF-8\";\n@font-face {\n  font-family: 'iconfont';\n  src: url('./iconfont.woff') format('woff');\n}\n"
  },
  {
    "path": "modules/web/src/assets/fonts/popfont.css",
    "content": "@charset \"UTF-8\";\n@font-face {\n  font-family: 'FZZCYSK';\n  src: local('☺'), url('./popfont.ttf');\n  font-style: normal;\n  font-weight: normal;\n}\n"
  },
  {
    "path": "modules/web/src/assets/fonts/shelffont.css",
    "content": "@charset \"UTF-8\";\n@font-face {\n  font-family: 'FZZCYSK';\n  src: local('☺'), url('./shelffont.ttf');\n  font-style: normal;\n  font-weight: normal;\n}\n"
  },
  {
    "path": "modules/web/src/assets/kbd.css",
    "content": "kbd {\n  align-items: center;\n  background: rgba(125, 125, 125, 0.1);\n  border-radius: 3px;\n  border: 0;\n  padding: 4px 5px;\n  font-weight: bold;\n  box-shadow:\n    inset 0 -2px 0 0 #cdcde6,\n    inset 0 0 1px 1px #fff,\n    0 1px 2px 1px rgba(30, 35, 90, 0.4);\n}\n"
  },
  {
    "path": "modules/web/src/assets/sourceeditor.css",
    "content": "@import './kbd.css';\n@import './code.css';\n\nbody {\n  padding: 0;\n  margin: 0;\n}\n\n.el-tabs__header {\n  position: sticky;\n  top: 0px;\n  z-index: 2;\n}\n"
  },
  {
    "path": "modules/web/src/auto-imports.d.ts",
    "content": "/* eslint-disable */\n/* prettier-ignore */\n// @ts-nocheck\n// noinspection JSUnusedGlobalSymbols\n// Generated by unplugin-auto-import\n// biome-ignore lint: disable\nexport {}\ndeclare global {\n  const EffectScope: typeof import('vue')['EffectScope']\n  const ElMessage: typeof import('element-plus/es')['ElMessage']\n  const ElMessageBox: typeof import('element-plus/es')['ElMessageBox']\n  const acceptHMRUpdate: typeof import('pinia')['acceptHMRUpdate']\n  const computed: typeof import('vue')['computed']\n  const createApp: typeof import('vue')['createApp']\n  const createPinia: typeof import('pinia')['createPinia']\n  const customRef: typeof import('vue')['customRef']\n  const defineAsyncComponent: typeof import('vue')['defineAsyncComponent']\n  const defineComponent: typeof import('vue')['defineComponent']\n  const defineStore: typeof import('pinia')['defineStore']\n  const effectScope: typeof import('vue')['effectScope']\n  const getActivePinia: typeof import('pinia')['getActivePinia']\n  const getCurrentInstance: typeof import('vue')['getCurrentInstance']\n  const getCurrentScope: typeof import('vue')['getCurrentScope']\n  const h: typeof import('vue')['h']\n  const inject: typeof import('vue')['inject']\n  const isProxy: typeof import('vue')['isProxy']\n  const isReactive: typeof import('vue')['isReactive']\n  const isReadonly: typeof import('vue')['isReadonly']\n  const isRef: typeof import('vue')['isRef']\n  const mapActions: typeof import('pinia')['mapActions']\n  const mapGetters: typeof import('pinia')['mapGetters']\n  const mapState: typeof import('pinia')['mapState']\n  const mapStores: typeof import('pinia')['mapStores']\n  const mapWritableState: typeof import('pinia')['mapWritableState']\n  const markRaw: typeof import('vue')['markRaw']\n  const nextTick: typeof import('vue')['nextTick']\n  const onActivated: typeof import('vue')['onActivated']\n  const onBeforeMount: typeof import('vue')['onBeforeMount']\n  const onBeforeRouteLeave: typeof import('vue-router')['onBeforeRouteLeave']\n  const onBeforeRouteUpdate: typeof import('vue-router')['onBeforeRouteUpdate']\n  const onBeforeUnmount: typeof import('vue')['onBeforeUnmount']\n  const onBeforeUpdate: typeof import('vue')['onBeforeUpdate']\n  const onDeactivated: typeof import('vue')['onDeactivated']\n  const onErrorCaptured: typeof import('vue')['onErrorCaptured']\n  const onMounted: typeof import('vue')['onMounted']\n  const onRenderTracked: typeof import('vue')['onRenderTracked']\n  const onRenderTriggered: typeof import('vue')['onRenderTriggered']\n  const onScopeDispose: typeof import('vue')['onScopeDispose']\n  const onServerPrefetch: typeof import('vue')['onServerPrefetch']\n  const onUnmounted: typeof import('vue')['onUnmounted']\n  const onUpdated: typeof import('vue')['onUpdated']\n  const onWatcherCleanup: typeof import('vue')['onWatcherCleanup']\n  const provide: typeof import('vue')['provide']\n  const reactive: typeof import('vue')['reactive']\n  const readonly: typeof import('vue')['readonly']\n  const ref: typeof import('vue')['ref']\n  const resolveComponent: typeof import('vue')['resolveComponent']\n  const setActivePinia: typeof import('pinia')['setActivePinia']\n  const setMapStoreSuffix: typeof import('pinia')['setMapStoreSuffix']\n  const shallowReactive: typeof import('vue')['shallowReactive']\n  const shallowReadonly: typeof import('vue')['shallowReadonly']\n  const shallowRef: typeof import('vue')['shallowRef']\n  const store: typeof import('./store/index')['default']\n  const storeToRefs: typeof import('pinia')['storeToRefs']\n  const toRaw: typeof import('vue')['toRaw']\n  const toRef: typeof import('vue')['toRef']\n  const toRefs: typeof import('vue')['toRefs']\n  const toValue: typeof import('vue')['toValue']\n  const triggerRef: typeof import('vue')['triggerRef']\n  const unref: typeof import('vue')['unref']\n  const useAttrs: typeof import('vue')['useAttrs']\n  const useBookStore: typeof import('./store/bookStore')['useBookStore']\n  const useConnectionStore: typeof import('./store/connectionStore')['useConnectionStore']\n  const useCssModule: typeof import('vue')['useCssModule']\n  const useCssVars: typeof import('vue')['useCssVars']\n  const useId: typeof import('vue')['useId']\n  const useLink: typeof import('vue-router')['useLink']\n  const useModel: typeof import('vue')['useModel']\n  const useRoute: typeof import('vue-router')['useRoute']\n  const useRouter: typeof import('vue-router')['useRouter']\n  const useSlots: typeof import('vue')['useSlots']\n  const useSourceStore: typeof import('./store/sourceStore')['useSourceStore']\n  const useTemplateRef: typeof import('vue')['useTemplateRef']\n  const watch: typeof import('vue')['watch']\n  const watchEffect: typeof import('vue')['watchEffect']\n  const watchPostEffect: typeof import('vue')['watchPostEffect']\n  const watchSyncEffect: typeof import('vue')['watchSyncEffect']\n}\n// for type re-export\ndeclare global {\n  // @ts-ignore\n  export type { Component, ComponentPublicInstance, ComputedRef, DirectiveBinding, ExtractDefaultPropTypes, ExtractPropTypes, ExtractPublicPropTypes, InjectionKey, PropType, Ref, MaybeRef, MaybeRefOrGetter, VNode, WritableComputedRef } from 'vue'\n  import('vue')\n}\n"
  },
  {
    "path": "modules/web/src/book.d.ts",
    "content": "/** https://github.com/gedoor/legado/tree/master/app/src/main/java/io/legado/app/data/entities */\nexport type BaseBook = {\n  name: string\n  author: string\n  bookUrl: string\n  kind?: string\n  wordCount?: string\n  variable?: string\n  /** 忽略序列化\n    infoHtml?: string\n    tocHtml?: string\n    */\n}\nexport type Book = BaseBook & {\n  // 目录页Url (toc=table of Contents)\n  tocUrl: string\n  // 书源URL(默认BookType.local)\n  origin: string\n  //书源名称 or 本地书籍文件名\n  originName: string\n  // 分类信息(用户修改)\n  customTag?: string\n  // 封面Url(书源获取)\n  coverUrl?: string\n  // 封面Url(用户修改)\n  customCoverUrl?: string\n  // 简介内容(书源获取)\n  intro?: string\n  // 简介内容(用户修改)\n  customnumberro?: string\n  // 自定义字符集名称(仅适用于本地书籍)\n  charset?: string\n  // 类型详见BookType\n  type: number\n  // 自定义分组索引号\n  group: number\n  // 最新章节标题\n  latestChapterTitle?: string\n  // 最新章节标题更新时间\n  latestChapterTime: number\n  // 最近一次更新书籍信息的时间\n  lastCheckTime: number\n  // 最近一次发现新章节的数量\n  lastCheckCount: number\n  // 书籍目录总数\n  totalChapterNum: number\n  // 当前章节名称\n  durChapterTitle?: string\n  // 当前章节索引\n  durChapterIndex: number\n  // 当前阅读的进度(首行字符的索引位置)\n  durChapterPos: number\n  // 最近一次阅读书籍的时间(打开正文的时间)\n  durChapterTime: number\n  // 刷新书架时更新书籍信息\n  canUpdate: boolean\n  // 手动排序\n  order: number\n  //书源排序\n  originOrder: number\n  //阅读设置\n  readConfig?: ReadConfig\n  //同步时间\n  syncTime: number\n}\nexport type SeachBook = BaseBook & {\n  /** 书源 */\n  origin: string\n  originName: string\n  /** BookType */\n  type: number\n  coverUrl?: string\n  intro?: string\n  latestChapterTitle?: string\n  /** 目录页Url (toc=table of Contents) */\n  tocUrl: string\n  time: number\n  originOrder: number\n  chapterWordCountText?: string\n  chapterWordCount: number0\n  respondTime: number\n}\nexport type BookProgress = Pick<\n  Book,\n  | 'name'\n  | 'author'\n  | 'durChapterIndex'\n  | 'durChapterPos'\n  | 'durChapterTime'\n  | 'durChapterTitle'\n>\n\nexport type BookChapter = {\n  url: string // 章节地址\n  title: string // 章节标题\n  isVolume: boolean // 是否是卷名\n  baseUrl: string // 用来拼接相对url\n  bookUrl: string // 书籍地址\n  index: number // 章节序号\n  isVip: boolean // 是否VIP\n  isPay: boolean // 是否已购买\n  resourceUrl?: string // 音频真实URL\n  tag?: string // 更新时间或其他章节附加信息\n  start?: number // 章节起始位置\n  end?: number // 章节终止位置\n  startFragmentId?: string //EPUB书籍当前章节的fragmentId\n  endFragmentId?: string //EPUB书籍下一章节的fragmentId\n  variable?: string //变量\n}\n"
  },
  {
    "path": "modules/web/src/components/BookItems.vue",
    "content": "<template>\n  <div class=\"books-wrapper\">\n    <div class=\"wrapper\">\n      <div\n        class=\"book\"\n        v-for=\"book in books\"\n        :key=\"book.bookUrl\"\n        @click=\"handleClick(book)\"\n      >\n        <div class=\"cover-img\">\n          <img\n            class=\"cover\"\n            :src=\"getCover(book)\"\n            :key=\"book.coverUrl\"\n            @error.once=\"proxyImage\"\n            alt=\"\"\n            loading=\"lazy\"\n          />\n        </div>\n        <div class=\"info\">\n          <div class=\"name\">{{ book.name }}</div>\n          <div class=\"sub\">\n            <div class=\"author\">\n              {{ book.author }}\n            </div>\n            <div class=\"tags\" v-if=\"isSearch\">\n              <el-tag\n                v-for=\"tag in book.kind?.split(',').slice(0, 2)\"\n                :key=\"tag\"\n              >\n                {{ tag }}\n              </el-tag>\n            </div>\n            <div class=\"update-info\" v-if=\"!isSearch\">\n              <div class=\"dot\">•</div>\n              <div class=\"size\">共{{ (book as Book).totalChapterNum }}章</div>\n              <div class=\"dot\">•</div>\n              <div class=\"date\">\n                {{ dateFormat((book as Book).lastCheckTime) }}\n              </div>\n            </div>\n          </div>\n          <div class=\"intro\" v-if=\"isSearch\">{{ book.intro }}</div>\n\n          <div class=\"dur-chapter\" v-if=\"!isSearch\">\n            已读：{{ (book as Book).durChapterTitle }}\n          </div>\n          <div class=\"last-chapter\">最新：{{ book.latestChapterTitle }}</div>\n        </div>\n      </div>\n    </div>\n  </div>\n</template>\n<script setup lang=\"ts\">\nimport type { Book, SeachBook } from '@/book'\nimport { dateFormat, isLegadoUrl } from '../utils/utils'\nimport API from '@api'\nconst props = defineProps<{\n  books: Array<Book | SeachBook>\n  isSearch: boolean\n}>()\n\nconst emit = defineEmits(['bookClick'])\nconst handleClick = (book: Book | SeachBook) => emit('bookClick', book)\nconst getCover = ({ bookUrl, coverUrl }: Book | SeachBook) => {\n  if (coverUrl === undefined) return API.getProxyCoverUrl(bookUrl)\n  return isLegadoUrl(coverUrl) ? API.getProxyCoverUrl(coverUrl) : coverUrl\n}\nconst proxyImage = (evt: Event) => {\n  const target = evt.target as HTMLImageElement\n  target.src = API.getProxyCoverUrl(target.src)\n}\n\nconst subJustify = computed(() =>\n  props.isSearch ? 'space-between' : 'flex-start',\n)\n</script>\n\n<style lang=\"scss\" scoped>\n.books-wrapper {\n  overflow: auto;\n\n  .wrapper {\n    display: grid;\n    grid-template-columns: repeat(auto-fill, 380px);\n    justify-content: space-around;\n    grid-gap: 10px;\n\n    .book {\n      user-select: none;\n      display: flex;\n      cursor: pointer;\n      margin-bottom: 18px;\n      padding: 24px 24px;\n      width: 360px;\n      flex-direction: row;\n      justify-content: space-around;\n\n      .cover-img {\n        width: 84px;\n        height: 112px;\n\n        .cover {\n          width: 84px;\n          height: 112px;\n        }\n      }\n\n      .info {\n        display: flex;\n        flex-direction: column;\n        justify-content: space-around;\n        align-items: left;\n        height: 112px;\n        margin-left: 20px;\n        flex: 1;\n        overflow: hidden;\n\n        .name {\n          width: fit-content;\n          font-size: 16px;\n          font-weight: 700;\n          color: #33373d;\n        }\n\n        .sub {\n          display: flex;\n          flex-direction: row;\n          align-items: baseline;\n          justify-content: v-bind('subJustify');\n          font-size: 12px;\n          font-weight: 600;\n          color: #6b6b6b;\n          .tags {\n            :deep(.el-tag) {\n              margin-right: 0.5em;\n            }\n          }\n          .update-info {\n            display: flex;\n            .dot {\n              margin: 0 7px;\n            }\n          }\n        }\n\n        .intro,\n        .dur-chapter,\n        .last-chapter {\n          color: #969ba3;\n          font-size: 13px;\n          margin-top: 3px;\n          font-weight: 500;\n          word-wrap: break-word;\n          overflow: hidden;\n          text-overflow: ellipsis;\n          display: -webkit-box;\n          -webkit-box-orient: vertical;\n          -webkit-line-clamp: 1;\n          line-clamp: 1;\n          text-align: left;\n        }\n      }\n    }\n\n    .book:hover {\n      background: rgba(0, 0, 0, 0.1);\n      transition-duration: 0.5s;\n    }\n  }\n\n  .wrapper:last-child {\n    margin-right: auto;\n  }\n}\n\n.books-wrapper::-webkit-scrollbar {\n  width: 0 !important;\n}\n\n@media screen and (max-width: 750px) {\n  .books-wrapper {\n    .wrapper {\n      display: flex;\n      flex-direction: column;\n\n      .book {\n        box-sizing: border-box;\n        width: 100%;\n        margin-bottom: 0;\n        padding: 10px 20px;\n      }\n    }\n  }\n}\n</style>\n"
  },
  {
    "path": "modules/web/src/components/CatalogItem.vue",
    "content": "<template>\n  <div class=\"wrapper\">\n    <div\n      v-for=\"cata in catas\"\n      class=\"cata-text\"\n      :key=\"cata.url\"\n      :class=\"{ selected: isSelected(cata.index) }\"\n      @click=\"gotoChapter(cata)\"\n    >\n      {{ cata.title }}\n    </div>\n  </div>\n</template>\n<script setup lang=\"ts\">\nimport type { BookChapter } from '@/book'\n\nconst props = defineProps<{\n  index: number\n  source: BookChapter | { index: number; catas: BookChapter[] }\n  gotoChapter: (chapter: BookChapter) => void\n  currentChapterIndex: number\n}>()\n\nconst isSelected = (idx: number) => {\n  return idx == props.currentChapterIndex\n}\n\n// PC端 一个虚拟列表中有两个章节\nconst catas = computed(() => {\n  const source = props.source\n  if ('catas' in source) return source.catas\n  return [props.source as BookChapter]\n})\n</script>\n\n<style lang=\"scss\" scoped>\n.selected {\n  color: #eb4259;\n}\n.wrapper {\n  display: flex;\n\n  .cata-text {\n    width: 100%;\n    margin-right: 26px;\n    overflow: hidden;\n    white-space: nowrap;\n    text-overflow: ellipsis;\n  }\n}\n</style>\n"
  },
  {
    "path": "modules/web/src/components/ChapterContent.vue",
    "content": "<template>\n  <div class=\"title\" data-chapterpos=\"0\" ref=\"titleRef\">{{ title }}</div>\n  <div\n    v-for=\"(para, index) in contents\"\n    :key=\"index\"\n    ref=\"paragraphRef\"\n    :data-chapterpos=\"chapterPos[index]\"\n  >\n    <img\n      class=\"full\"\n      v-if=\"/^\\s*<img[^>]*src[^>]+>$/.test(String(para))\"\n      :src=\"getImageSrc(para)\"\n      @error.once=\"proxyImage\"\n      loading=\"lazy\"\n    />\n    <p v-else :style=\"{ fontFamily, fontSize }\" v-html=\"replaceImage(para)\" @error.capture=\"handleImgLoadError\" />\n  </div>\n</template>\n\n<script setup lang=\"ts\">\nimport { isLegadoUrl } from '@/utils/utils'\nimport API from '@api'\nimport jump from '@/plugins/jump'\nimport type { webReadConfig } from '@/web'\n\nconst store = useBookStore()\nconst readWidth = computed(() => store.config.readWidth)\nconst fontSize = computed(() => store.config.fontSize)\nconst bookUrl = computed(() => store.readingBook.bookUrl)\n\nconst props = defineProps<{\n  chapterIndex: number\n  contents: Array<string>\n  title: string\n  spacing: webReadConfig['spacing']\n  fontFamily: string\n  fontSize: string\n}>()\n\nconst imgPattern = /<img[^>]*src=['\"]([^'\"]*(?:['\"][^>]+\\})?)['\"][^>]*>/g\n\nconst replaceImage = (content: string) => {\n  return content.replace(imgPattern, (match, src) => {\n    if (isLegadoUrl(src)) {\n      const proxySrc = API.getProxyImageUrl(\n        bookUrl.value,\n        src,\n        fontSize.value * 2,\n      )\n      return match.replace(src, proxySrc)\n    }\n    return match\n  })\n}\n\nconst getImageSrc = (content: string) => {\n  const imgPattern = /<img[^>]*src=['\"]([^'\"]*(?:['\"][^>]+\\})?)['\"][^>]*>/\n  const src = content.match(imgPattern)![1] //reg tested in template\n  if (isLegadoUrl(src))\n    return API.getProxyImageUrl(\n      bookUrl.value,\n      src,\n      readWidth.value,\n    )\n  return src\n}\nconst proxyImage = (event: Event) => {\n  /* 获取IMG标签原始的src\n    <img src=\"/test\" />\n    假设location.href = http://example.com\n    event.target.src 返回 http://example.com/test\n    (event.target as HTMLImageElement)?.getAttribute(\"src\")  返回/test\n  */\n  const src = (event.target as HTMLImageElement)?.getAttribute(\"src\")\n  if (src != null && src.length > 0) {\n    (event.target as HTMLImageElement).src = API.getProxyImageUrl(\n      bookUrl.value,\n      src,\n      readWidth.value,\n    )\n  }\n}\n\n/**\n * 处理传入的IMG标签错误事件，自动替换图片的代理链接\n */\nconst handleImgLoadError = (event: Event) => {\n  if ((event.target as HTMLElement)?.tagName === \"IMG\") {\n    console.log(\"[ChapterContent]: IMG Load Error, replace src:\",\n      (event.target as HTMLImageElement)?.getAttribute(\"src\"), \"=>\",\n      API.getProxyImageUrl(\n        bookUrl.value,\n        (event.target as HTMLImageElement)?.getAttribute(\"src\") ?? \"\",\n        readWidth.value,\n      )\n    )\n    proxyImage(event)\n  }\n}\n\nconst calculateWordCount = (paragraph: string) => {\n  //内嵌图片文字为1\n  const imagePlaceHolder = ' '\n  return paragraph.replace(imgPattern, imagePlaceHolder).length\n}\nconst chapterPos = computed(() => {\n  let pos = -1\n  return Array.from(props.contents, content => {\n    pos += calculateWordCount(content) + 1 //计算上一段的换行符\n    return pos\n  })\n})\n\nconst titleRef = ref<HTMLElement>()\nconst paragraphRef = ref<HTMLParagraphElement[]>()\nconst scrollToReadedLength = (length: number) => {\n  if (length === 0) return\n  const paragraphIndex = chapterPos.value.findIndex(\n    wordCount => wordCount >= length,\n  )\n  if (paragraphIndex === -1) return\n  nextTick(() => {\n    jump(paragraphRef.value![paragraphIndex], {\n      duration: 0,\n    })\n  })\n}\ndefineExpose({\n  scrollToReadedLength,\n})\nlet intersectionObserver: IntersectionObserver | null = null\nconst emit = defineEmits(['readedLengthChange'])\nonMounted(() => {\n  intersectionObserver = new IntersectionObserver(\n    entries => {\n      for (const { target, isIntersecting } of entries) {\n        if (isIntersecting) {\n          emit(\n            'readedLengthChange',\n            props.chapterIndex,\n            parseInt((target as HTMLElement).dataset.chapterpos as string),\n          )\n        }\n      }\n    },\n    {\n      rootMargin: `0px 0px -${window.innerHeight - 24}px 0px`,\n    },\n  )\n  intersectionObserver.observe(titleRef.value!)\n  paragraphRef.value!.forEach(element => {\n    intersectionObserver!.observe(element)\n  })\n})\n\nonUnmounted(() => {\n  intersectionObserver?.disconnect()\n  intersectionObserver = null\n})\n</script>\n\n<style lang=\"scss\" scoped>\n.title {\n  margin-bottom: 57px;\n  font:\n    24px / 32px PingFangSC-Regular,\n    HelveticaNeue-Light,\n    'Helvetica Neue Light',\n    'Microsoft YaHei',\n    sans-serif;\n}\n\np {\n  display: block;\n  word-wrap: break-word;\n  /*   word-break: break-all; */\n  letter-spacing: calc(v-bind('props.spacing.letter') * 1em);\n  line-height: calc(1 + v-bind('props.spacing.line'));\n  margin: calc(v-bind('props.spacing.paragraph') * 1em) 0;\n\n  :deep(img) {\n    height: 1em;\n  }\n}\n\n.full {\n  display: block;\n  width: 100%;\n}\n</style>\n"
  },
  {
    "path": "modules/web/src/components/PopCatalog.vue",
    "content": "<template>\n  <div\n    :class=\"{ 'cata-wrapper': true, visible: popCataVisible }\"\n    :style=\"popupTheme\"\n  >\n    <div class=\"title\">目录</div>\n    <virtual-list\n      style=\"height: 300px; overflow: auto\"\n      :class=\"{ night: isNight, day: !isNight }\"\n      ref=\"virtualListRef\"\n      data-key=\"index\"\n      wrap-class=\"data-wrapper\"\n      item-class=\"cata\"\n      :data-sources=\"virtualListdata\"\n      :data-component=\"CatalogItem\"\n      :estimate-size=\"40\"\n      :extra-props=\"{ gotoChapter, currentChapterIndex }\"\n    />\n  </div>\n</template>\n\n<script setup lang=\"ts\">\nimport VirtualList from 'vue3-virtual-scroll-list'\nimport settings from '../config/themeConfig'\nimport '../assets/fonts/popfont.css'\nimport CatalogItem from './CatalogItem.vue'\nimport type { BookChapter } from '@/book'\n\nconst store = useBookStore()\n\nconst { catalog, popCataVisible, miniInterface } = storeToRefs(store)\n\n//主题\nconst isNight = computed(() => store.theme)\nconst theme = computed(() => store.theme)\nconst popupTheme = computed(() => {\n  return {\n    background: settings.themes[theme.value].popup,\n  }\n})\n\n//虚拟列表 数据源\nconst virtualListdata = computed(() => {\n  const catalogValue = catalog.value\n  if (miniInterface.value) return catalogValue\n\n  // pc端 virtualListIitem有2个章节\n  const length = Math.ceil(catalogValue.length / 2)\n  const virtualListDataSource = new Array<{\n    index: number\n    catas: BookChapter[]\n  }>(length)\n\n  let i = 0\n  while (i < length) {\n    virtualListDataSource[i] = {\n      index: i,\n      catas: catalogValue.slice(2 * i, 2 * i + 2),\n    }\n    i++\n  }\n  return virtualListDataSource\n})\n\n//打开目录 计算当前章节对应的虚拟列表位置\nconst virtualListRef = ref()\nconst currentChapterIndex = computed({\n  get: () => store.readingBook.chapterIndex,\n  set: value => (store.readingBook.chapterIndex = value),\n})\nconst virtualListIndex = computed(() => {\n  const index = currentChapterIndex.value\n  if (miniInterface.value) return index\n  // pc端 virtualListIitem有2个章节\n  return Math.floor(index / 2)\n})\nonUpdated(() => {\n  // dom更新触发ResizeObserver，更新虚拟列表内部的sizes Map\n  if (!popCataVisible.value) return\n  virtualListRef.value.scrollToIndex(virtualListIndex.value)\n})\n\n// 点击加载对应章节内容\nconst emit = defineEmits(['getContent'])\nconst gotoChapter = (chapter: BookChapter) => {\n  const chapterIndex = catalog.value.indexOf(chapter)\n  currentChapterIndex.value = chapterIndex\n  store.setPopCataVisible(false)\n  store.setContentLoading(true)\n  store.saveBookProgress()\n  emit('getContent', chapterIndex)\n}\n</script>\n\n<style lang=\"scss\" scoped>\n.cata-wrapper {\n  margin: -16px;\n  padding: 18px 0 24px 25px;\n\n  /* background: #ede7da url('../assets/imgs/themes/popup_1.png') repeat; */\n  .title {\n    font-size: 18px;\n    font-weight: 400;\n    font-family: FZZCYSK;\n    margin: 0 0 20px 0;\n    color: #ed4259;\n    width: fit-content;\n    border-bottom: 1px solid #ed4259;\n  }\n  :deep(.data-wrapper) {\n    .cata {\n      /*width: 50%;*/\n      height: 40px;\n      cursor: pointer;\n      font:\n        16px / 40px PingFangSC-Regular,\n        HelveticaNeue-Light,\n        'Helvetica Neue Light',\n        'Microsoft YaHei',\n        sans-serif;\n    }\n  }\n\n  .night {\n    :deep(.cata) {\n      border-bottom: 1px solid #666;\n    }\n  }\n\n  .day {\n    :deep(.cata) {\n      border-bottom: 1px solid #f2f2f2;\n    }\n  }\n}\n</style>\n"
  },
  {
    "path": "modules/web/src/components/ReadSettings.vue",
    "content": "<template>\n  <div\n    class=\"settings-wrapper\"\n    :style=\"popupTheme\"\n    :class=\"{ night: isNight, day: !isNight }\"\n  >\n    <div class=\"settings-title\">设置</div>\n    <div class=\"setting-list\">\n      <ul>\n        <li class=\"theme-list\">\n          <i>阅读主题</i>\n          <span\n            class=\"theme-item\"\n            v-for=\"(themeColor, index) in themeColors\"\n            :key=\"index\"\n            :style=\"themeColor\"\n            ref=\"themes\"\n            @click=\"setTheme(index)\"\n            :class=\"{ selected: theme == index }\"\n            ><em v-if=\"index < 6\" class=\"iconfont\">&#58980;</em\n            ><em v-else class=\"moon-icon\">{{ moonIcon }}</em></span\n          >\n        </li>\n        <li class=\"font-list\">\n          <i>正文字体</i>\n          <span\n            class=\"font-item\"\n            v-for=\"(font, index) in fonts\"\n            :key=\"index\"\n            :class=\"{ selected: selectedFont == index }\"\n            @click=\"setFont(index)\"\n            >{{ font }}</span\n          >\n        </li>\n        <li class=\"font-list\">\n          <i>自定字体</i>\n          <el-tooltip effect=\"dark\" content=\"自定义的字体名称\" placement=\"top\">\n            <input\n              type=\"text\"\n              class=\"font-item font-item-input\"\n              v-model=\"customFontName\"\n              placeholder=\"请输入自定义的字体名称\"\n            />\n          </el-tooltip>\n\n          <el-popover\n            placement=\"top\"\n            width=\"270\"\n            trigger=\"click\"\n            v-model:visible=\"customFontSavePopVisible\"\n          >\n            <p>\n              已经安装在您的设备上的字体请确认输入的字体名称完整无误，或者从网络下载字体。\n            </p>\n            <div style=\"text-align: right; margin: 0\">\n              <el-button\n                size=\"small\"\n                plain\n                @click=\"customFontSavePopVisible = false\"\n                >取消</el-button\n              >\n              <el-button type=\"primary\" size=\"small\" @click=\"setCustomFont()\"\n                >确定</el-button\n              >\n              <el-button type=\"primary\" size=\"small\" @click=\"loadFontFromURL()\"\n                >网络下载</el-button\n              >\n            </div>\n            <template #reference>\n              <span type=\"text\" class=\"font-item\">保存</span>\n            </template>\n          </el-popover>\n        </li>\n        <li class=\"font-size\">\n          <i>字体大小</i>\n          <div class=\"resize\">\n            <span class=\"less\" @click=\"lessFontSize\"\n              ><em class=\"iconfont\">&#58966;</em></span\n            ><b></b> <span class=\"lang\">{{ fontSize }}</span\n            ><b></b>\n            <span class=\"more\" @click=\"moreFontSize\"\n              ><em class=\"iconfont\">&#58976;</em></span\n            >\n          </div>\n        </li>\n        <li class=\"letter-spacing\">\n          <i>字距</i>\n          <div class=\"resize\">\n            <span class=\"less\" @click=\"lessLetterSpacing\"\n              ><em class=\"iconfont\">&#58966;</em></span\n            ><b></b> <span class=\"lang\">{{ spacing.letter.toFixed(2) }}</span\n            ><b></b>\n            <span class=\"more\" @click=\"moreLetterSpacing\"\n              ><em class=\"iconfont\">&#58976;</em></span\n            >\n          </div>\n        </li>\n        <li class=\"line-spacing\">\n          <i>行距</i>\n          <div class=\"resize\">\n            <span class=\"less\" @click=\"lessLineSpacing\"\n              ><em class=\"iconfont\">&#58966;</em></span\n            ><b></b> <span class=\"lang\">{{ spacing.line.toFixed(1) }}</span\n            ><b></b>\n            <span class=\"more\" @click=\"moreLineSpacing\"\n              ><em class=\"iconfont\">&#58976;</em></span\n            >\n          </div>\n        </li>\n        <li class=\"paragraph-spacing\">\n          <i>段距</i>\n          <div class=\"resize\">\n            <div class=\"resize\">\n              <span class=\"less\" @click=\"lessParagraphSpacing\"\n                ><em class=\"iconfont\">&#58966;</em></span\n              ><b></b>\n              <span class=\"lang\">{{ spacing.paragraph.toFixed(1) }}</span\n              ><b></b>\n              <span class=\"more\" @click=\"moreParagraphSpacing\"\n                ><em class=\"iconfont\">&#58976;</em></span\n              >\n            </div>\n          </div>\n        </li>\n        <li class=\"read-width\" v-if=\"!store.miniInterface\">\n          <i>页面宽度</i>\n          <div class=\"resize\">\n            <span class=\"less\" @click=\"lessReadWidth\"\n              ><em class=\"iconfont\">&#58965;</em></span\n            ><b></b> <span class=\"lang\">{{ readWidth }}</span\n            ><b></b>\n            <span class=\"more\" @click=\"moreReadWidth\"\n              ><em class=\"iconfont\">&#58975;</em></span\n            >\n          </div>\n        </li>\n        <li class=\"paragraph-spacing\">\n          <i>翻页速度</i>\n          <div class=\"resize\">\n            <div class=\"resize\">\n              <span class=\"less\" @click=\"lessJumpDuration\">\n                <em class=\"iconfont\">&#xe625;</em>\n              </span>\n              <b></b> <span class=\"lang\">{{ jumpDuration }}</span\n              ><b></b>\n              <span class=\"more\" @click=\"moreJumpDuration\"\n                ><em class=\"iconfont\">&#xe626;</em></span\n              >\n            </div>\n          </div>\n        </li>\n        <li class=\"infinite-loading\">\n          <i>无限加载</i>\n          <span\n            class=\"infinite-loading-item\"\n            :key=\"0\"\n            :class=\"{ selected: infiniteLoading == false }\"\n            @click=\"setInfiniteLoading(false)\"\n            >关闭</span\n          >\n          <span\n            class=\"infinite-loading-item\"\n            :key=\"1\"\n            :class=\"{ selected: infiniteLoading == true }\"\n            @click=\"setInfiniteLoading(true)\"\n            >开启</span\n          >\n        </li>\n      </ul>\n    </div>\n  </div>\n</template>\n\n<script setup lang=\"ts\">\nimport '../assets/fonts/popfont.css'\nimport '../assets/fonts/iconfont.css'\nimport settings from '../config/themeConfig'\nimport API from '@api'\nimport { useDebounceFn } from '@vueuse/shared'\n\nconst store = useBookStore()\nconst saveConfigDebounce = useDebounceFn(\n  () => API.saveReadConfig(store.config),\n  500,\n)\n//阅读界面设置改变时保存同步配置\nwatch(\n  () => store.config,\n  () => {\n    saveConfigDebounce()\n  },\n  {\n    deep: 2, //深度为2\n  },\n)\n\n//主题颜色\nconst theme = computed(() => store.theme)\nconst isNight = computed(() => store.isNight)\nconst moonIcon = computed(() => (theme.value == 6 ? '' : ''))\nconst themeColors = [\n  {\n    background: 'rgba(250, 245, 235, 0.8)',\n  },\n  {\n    background: 'rgba(245, 234, 204, 0.8)',\n  },\n  {\n    background: 'rgba(230, 242, 230, 0.8)',\n  },\n  {\n    background: 'rgba(228, 241, 245, 0.8)',\n  },\n  {\n    background: 'rgba(245, 228, 228, 0.8)',\n  },\n  {\n    background: 'rgba(224, 224, 224, 0.8)',\n  },\n  {\n    background: 'rgba(0, 0, 0, 0.5)',\n  },\n]\nconst popupTheme = computed(() => {\n  return {\n    background: settings.themes[theme.value].popup,\n  }\n})\nconst setTheme = (theme: number) => {\n  store.config.theme = theme\n}\n\n//预置字体\nconst fonts = ref(['雅黑', '宋体', '楷书'])\nconst setFont = (font: number) => {\n  store.config.font = font\n}\nconst selectedFont = computed(() => {\n  return store.config.font\n})\n//自定义字体\nconst customFontName = ref(store.config.customFontName)\nconst customFontSavePopVisible = ref(false)\nconst setCustomFont = () => {\n  customFontSavePopVisible.value = false\n  store.config.font = -1\n  store.config.customFontName = customFontName.value\n}\n// 加载网络字体\nconst loadFontFromURL = () => {\n  customFontSavePopVisible.value = false\n  ElMessageBox.prompt('请输入 字体网络链接', '提示', {\n    confirmButtonText: '确定',\n    cancelButtonText: '取消',\n    inputPattern: /^https?:.+$/,\n    inputErrorMessage: 'url 形式不正确',\n    beforeClose: (action, instance, done) => {\n      if (action === 'confirm') {\n        instance.confirmButtonLoading = true\n        instance.confirmButtonText = '下载中……'\n        // instance.inputValue\n        const url = instance.inputValue\n        if (typeof FontFace !== 'function') {\n          ElMessage.error('浏览器不支持FontFace')\n          return done()\n        }\n        const fontface = new FontFace(customFontName.value, `url(\"${url}\")`)\n        document.fonts.add(fontface)\n        fontface\n          .load()\n          //API.getBookShelf()\n          .then(function () {\n            instance.confirmButtonLoading = false\n            ElMessage.info('字体加载成功！')\n            setCustomFont()\n            done()\n          })\n          .catch(function (error) {\n            instance.confirmButtonLoading = false\n            instance.confirmButtonText = '确定'\n            ElMessage.error('下载失败，请检查您输入的 url')\n            throw error\n          })\n      } else {\n        done()\n      }\n    },\n  })\n}\n\n//字体大小\nconst fontSize = computed(() => {\n  return store.config.fontSize\n})\nconst moreFontSize = () => {\n  if (store.config.fontSize < 48) store.config.fontSize += 2\n}\nconst lessFontSize = () => {\n  if (store.config.fontSize > 12) store.config.fontSize -= 2\n}\n\n//字 行 段落间距\nconst spacing = computed(() => {\n  return store.config.spacing\n})\nconst lessLetterSpacing = () => {\n  store.config.spacing.letter -= 0.01\n}\nconst moreLetterSpacing = () => {\n  store.config.spacing.letter += 0.01\n}\nconst lessLineSpacing = () => {\n  store.config.spacing.line -= 0.1\n}\nconst moreLineSpacing = () => {\n  store.config.spacing.line += 0.1\n}\nconst lessParagraphSpacing = () => {\n  store.config.spacing.paragraph -= 0.1\n}\nconst moreParagraphSpacing = () => {\n  store.config.spacing.paragraph += 0.1\n}\n\n//页面宽度\nconst readWidth = computed(() => {\n  return store.config.readWidth\n})\nconst moreReadWidth = () => {\n  // 此时会截断页面\n  if (store.config.readWidth + 160 + 2 * 68 > window.innerWidth) return\n  store.config.readWidth += 160\n}\nconst lessReadWidth = () => {\n  if (store.config.readWidth > 640) store.config.readWidth -= 160\n}\n\n//翻页速度\nconst jumpDuration = computed(() => {\n  return store.config.jumpDuration\n})\nconst moreJumpDuration = () => {\n  store.config.jumpDuration += 100\n}\nconst lessJumpDuration = () => {\n  if (store.config.jumpDuration === 0) return\n  store.config.jumpDuration -= 100\n}\n\n//无限加载\nconst infiniteLoading = computed(() => {\n  return store.config.infiniteLoading\n})\nconst setInfiniteLoading = (loading: boolean) => {\n  store.config.infiniteLoading = loading\n}\n</script>\n\n<style lang=\"scss\" scoped>\n:deep(.iconfont) {\n  font-family: iconfont;\n  font-style: normal;\n}\n\n:deep(.moon-icon) {\n  font-family: iconfont;\n  font-style: normal;\n}\n\n.settings-wrapper {\n  user-select: none;\n  margin: -13px;\n  /*   width: 478px;\n  height: 350px; */\n  text-align: left;\n  padding: 40px 0 40px 24px;\n  background: #ede7da url('../assets/imgs/themes/popup_1.png') repeat;\n\n  .settings-title {\n    font-size: 18px;\n    line-height: 22px;\n    margin-bottom: 28px;\n    font-family: FZZCYSK;\n    font-weight: 400;\n  }\n\n  .setting-list {\n    max-height: calc(70vh - 50px);\n    overflow: auto;\n\n    ul {\n      list-style: none outside none;\n      margin: 0;\n      padding: 0;\n\n      li {\n        list-style: none outside none;\n\n        i {\n          font:\n            12px / 16px PingFangSC-Regular,\n            '-apple-system',\n            Simsun;\n          display: inline-block;\n          min-width: 48px;\n          margin-right: 16px;\n          vertical-align: middle;\n          color: #666;\n        }\n\n        .theme-item {\n          line-height: 32px;\n          width: 34px;\n          height: 34px;\n          margin-right: 16px;\n          margin-top: 5px;\n          border-radius: 100%;\n          display: inline-block;\n          cursor: pointer;\n          text-align: center;\n          vertical-align: middle;\n\n          .iconfont {\n            display: none;\n          }\n        }\n\n        .selected {\n          color: #ed4259;\n\n          .iconfont {\n            display: inline;\n          }\n        }\n      }\n\n      .font-list,\n      .infinite-loading {\n        margin-top: 28px;\n\n        .font-item,\n        .infinite-loading-item {\n          width: 78px;\n          height: 34px;\n          cursor: pointer;\n          margin-right: 16px;\n          border-radius: 2px;\n          text-align: center;\n          vertical-align: middle;\n          display: inline-block;\n          font:\n            14px / 34px PingFangSC-Regular,\n            HelveticaNeue-Light,\n            'Helvetica Neue Light',\n            'Microsoft YaHei',\n            sans-serif;\n        }\n        .font-item-input {\n          width: 168px;\n          color: #000000;\n        }\n        .selected {\n          color: #ed4259;\n          border: 1px solid #ed4259;\n        }\n\n        .font-item:hover,\n        .infinite-loading-item:hover {\n          border: 1px solid #ed4259;\n          color: #ed4259;\n        }\n      }\n\n      .font-size,\n      .read-width,\n      .letter-spacing,\n      .line-spacing,\n      .paragraph-spacing {\n        margin-top: 28px;\n\n        .resize {\n          display: inline-block;\n          width: 274px;\n          height: 34px;\n          vertical-align: middle;\n          border-radius: 2px;\n\n          span {\n            width: 89px;\n            height: 34px;\n            line-height: 34px;\n            display: inline-block;\n            cursor: pointer;\n            text-align: center;\n            vertical-align: middle;\n\n            em {\n              font-style: normal;\n            }\n          }\n\n          .less:hover,\n          .more:hover {\n            color: #ed4259;\n          }\n\n          .lang {\n            color: #a6a6a6;\n            font-weight: 400;\n            font-family: FZZCYSK;\n          }\n\n          b {\n            display: inline-block;\n            height: 20px;\n            vertical-align: middle;\n          }\n        }\n      }\n    }\n  }\n}\n\n.night {\n  :deep(.theme-item) {\n    border: 1px solid #666;\n  }\n\n  :deep(.selected) {\n    border: 1px solid #666;\n  }\n\n  :deep(.moon-icon) {\n    color: #ed4259;\n  }\n\n  :deep(.font-list),\n  .infinite-loading {\n    .font-item,\n    .infinite-loading-item {\n      border: 1px solid #666;\n      background: rgba(45, 45, 45, 0.5);\n    }\n  }\n\n  :deep(.resize) {\n    border: 1px solid #666;\n    background: rgba(45, 45, 45, 0.5);\n\n    b {\n      border-right: 1px solid #666;\n    }\n  }\n}\n\n.day {\n  :deep(.theme-item) {\n    border: 1px solid #e5e5e5;\n  }\n\n  :deep(.selected) {\n    border: 1px solid #ed4259;\n  }\n\n  :deep(.moon-icon) {\n    display: inline;\n    color: rgba(255, 255, 255, 0.2);\n  }\n\n  :deep(.font-list),\n  .infinite-loading {\n    .font-item,\n    .infinite-loading-item {\n      background: rgba(255, 255, 255, 0.5);\n      border: 1px solid rgba(0, 0, 0, 0.1);\n    }\n  }\n\n  :deep(.resize) {\n    border: 1px solid #e5e5e5;\n    background: rgba(255, 255, 255, 0.5);\n\n    b {\n      border-right: 1px solid #e5e5e5;\n    }\n  }\n}\n\n@media screen and (max-width: 500px) {\n  .settings-wrapper i {\n    display: flex !important;\n    flex-wrap: wrap;\n    padding-bottom: 5px !important;\n  }\n}\n</style>\n"
  },
  {
    "path": "modules/web/src/components/SourceDebug.vue",
    "content": "<template>\n  <el-input\n    v-if=\"isBookSource\"\n    id=\"debug-key\"\n    v-model=\"searchKey\"\n    placeholder=\"搜索书名、作者\"\n    :prefix-icon=\"Search\"\n    style=\"padding-bottom: 4px\"\n    @keydown.enter=\"startDebug\"\n  />\n  <el-input\n    id=\"debug-text\"\n    v-model=\"printDebug\"\n    type=\"textarea\"\n    readonly\n    :rows=\"29\"\n    placeholder=\"这里用于输出调试信息\"\n  />\n</template>\n\n<script setup lang=\"ts\">\nimport API from '@api'\nimport { Search } from '@element-plus/icons-vue'\n\nconst store = useSourceStore()\n\nconst printDebug = ref('')\nconst searchKey = ref('')\n\nwatch(\n  () => store.isDebuging,\n  () => {\n    if (store.isDebuging) startDebug()\n  },\n)\n\nconst appendDebugMsg = (msg: string) => {\n  const debugDom = document.querySelector('#debug-text')\n  debugDom!.scrollTop = debugDom!.scrollHeight\n  printDebug.value += msg + '\\n'\n}\nconst startDebug = async () => {\n  printDebug.value = ''\n  try {\n    await API.saveSource(store.currentSource)\n  } catch (e) {\n    store.debugFinish()\n    throw e\n  }\n  API.debug(\n    store.currentSourceUrl,\n    searchKey.value || store.searchKey,\n    appendDebugMsg,\n    store.debugFinish,\n  )\n}\n\nconst isBookSource = computed(() => {\n  return /bookSource/i.test(window.location.href)\n})\n</script>\n\n<style lang=\"scss\" scoped>\n:deep(#debug-text) {\n  height: calc(100vh - 45px - 36px - 5px);\n}\n</style>\n"
  },
  {
    "path": "modules/web/src/components/SourceHelp.vue",
    "content": "<script setup lang=\"ts\">\nimport { Link } from '@element-plus/icons-vue'\n</script>\n<template>\n  <el-link :icon=\"Link\" href=\"/help/#appHelp\" target=\"_blank\"\n    >APP帮助文档</el-link\n  ><br />\n  <el-link :icon=\"Link\" href=\"/help/#ruleHelp\" target=\"_blank\"\n    >书源制作教程</el-link\n  ><br />\n  <el-link :icon=\"Link\" href=\"/help/#jsHelp\" target=\"_blank\"\n    >js变量和函数</el-link\n  ><br />\n  <el-link :icon=\"Link\" href=\"/help/#xpathHelp\" target=\"_blank\"\n    >xpath语法教程</el-link\n  ><br />\n  <el-link :icon=\"Link\" href=\"/help/#regexHelp\" target=\"_blank\"\n    >正则表达式教程</el-link\n  ><br />\n  <el-link :icon=\"Link\" href=\"/help/#txtTocRuleHelp\" target=\"_blank\"\n    >txt目录正则说明</el-link\n  ><br />\n  <el-link :icon=\"Link\" href=\"/help/#debugHelp\" target=\"_blank\"\n    >书源调试说明</el-link\n  ><br />\n  <el-link :icon=\"Link\" href=\"/help/#httpTTSHelp\" target=\"_blank\"\n    >在线朗读规则</el-link\n  ><br />\n  <el-link :icon=\"Link\" href=\"/help/#webDavBookHelp\" target=\"_blank\">\n    WebDav书籍简明使用教程</el-link\n  ><br />\n  <el-link :icon=\"Link\" href=\"/help/#webDavHelp\" target=\"_blank\">\n    WebDav备份教程</el-link\n  ><br />\n  <el-link :icon=\"Link\" href=\"https://regexr-cn.com/\" target=\"_blank\"\n    >正则表达式在线验证工具</el-link\n  ><br />\n  <div style=\"margin-top: 20px\">\n    <span\n      ><el-text\n        ><code>^$()[]{}.?+*|</code> 这些是Java正则特殊符号,匹配需转义</el-text\n      ></span\n    ><br />\n    <span\n      ><el-text><code>(?s)</code> 前缀表示跨行解析</el-text></span\n    ><br />\n    <span\n      ><el-text><code>(?m)</code> 前缀表示逐行匹配</el-text></span\n    ><br />\n    <span\n      ><el-text><code>(?i)</code> 前缀表示忽略大小写</el-text></span\n    ><br />\n  </div>\n</template>\n\n<style lang=\"scss\" scoped>\n.el-link {\n  padding: 4px;\n}\n.el-text {\n  padding-top: 20px;\n}\n</style>\n"
  },
  {
    "path": "modules/web/src/components/SourceItem.vue",
    "content": "<template>\n  <el-checkbox\n    size=\"large\"\n    border\n    :value=\"sourceUrl\"\n    :class=\"{\n      error: isSaveError,\n      edit: sourceUrl == currentSourceUrl,\n    }\"\n  >\n    {{ getSourceName(source) }}\n    <el-button text :icon=\"Edit\" @click=\"handleSourceClick(source)\" />\n  </el-checkbox>\n</template>\n\n<script setup lang=\"ts\">\nimport { Edit } from '@element-plus/icons-vue'\nimport { getSourceUniqueKey, getSourceName } from '@/utils/souce'\nimport type { Source } from '@/source'\n\nconst props = defineProps<{\n  source: Source\n}>()\n\nconst store = useSourceStore()\n\nconst currentSourceUrl = computed(() => store.currentSourceUrl)\nconst sourceUrl = computed(() => getSourceUniqueKey(props.source))\n\nconst handleSourceClick = (source: Source) => {\n  store.changeCurrentSource(source)\n}\nconst isSaveError = computed(() => {\n  const map = store.savedSourcesMap\n  if (map.size == 0) return false\n  return !map.has(sourceUrl.value)\n})\n</script>\n<style lang=\"scss\" scoped>\n:deep(.el-checkbox__label) {\n  flex: 1;\n  display: flex;\n  justify-content: space-between;\n  align-items: center;\n}\n.error {\n  border-color: var(--el-color-error) !important;\n  color: var(--el-color-error) !important;\n  --el-checkbox-checked-text-color: var(--el-color-error);\n  --el-checkbox-checked-bg-color: var(--el-color-error);\n  --el-checkbox-checked-input-border-color: var(--el-color-error);\n}\n.edit {\n  border-color: var(--el-color-dark) !important;\n}\n</style>\n"
  },
  {
    "path": "modules/web/src/components/SourceJson.vue",
    "content": "<template>\n  <el-input\n    id=\"source-json\"\n    v-model=\"sourceString\"\n    type=\"textarea\"\n    placeholder=\"这里输出序列化的JSON数据,可直接导入'阅读'APP\"\n    :rows=\"30\"\n    @change=\"update\"\n    style=\"margin-bottom: 4px\"\n  ></el-input>\n</template>\n<script setup lang=\"ts\">\nimport { useSourceStore } from '@/store'\n\nconst store = useSourceStore()\nconst sourceString = ref('')\nconst update = async (string: string) => {\n  try {\n    store.changeEditTabSource(JSON.parse(string))\n  } catch {\n    ElMessage({\n      message: '粘贴的源格式错误',\n      type: 'error',\n    })\n  }\n}\n\nwatchEffect(async () => {\n  const source = store.editTabSource\n  if (Object.keys(source).length > 0) {\n    sourceString.value = JSON.stringify(source, null, 4)\n  } else {\n    sourceString.value = ''\n  }\n})\n</script>\n<style lang=\"scss\" scoped>\n:deep(.el-input) {\n  width: 100%;\n}\n:deep(#source-json) {\n  height: calc(100vh - 50px);\n}\n</style>\n"
  },
  {
    "path": "modules/web/src/components/SourceList.vue",
    "content": "<template>\n  <el-input\n    v-model=\"searchKey\"\n    class=\"search\"\n    :prefix-icon=\"Search\"\n    placeholder=\"筛选源\"\n  />\n  <div class=\"tool\">\n    <el-button @click=\"importSourceFile\" :icon=\"Folder\">打开</el-button>\n    <el-button\n      :disabled=\"sourcesFiltered.length === 0\"\n      @click=\"outExport\"\n      :icon=\"Download\"\n    >\n      导出</el-button\n    >\n    <el-button\n      type=\"danger\"\n      :icon=\"Delete\"\n      @click=\"deleteSelectSources\"\n      :disabled=\"sourceSelect.length === 0\"\n      >删除</el-button\n    >\n    <el-button\n      type=\"danger\"\n      :icon=\"Delete\"\n      @click=\"clearAllSources\"\n      :disabled=\"sources.length === 0\"\n      >清空</el-button\n    >\n  </div>\n  <el-checkbox-group id=\"source-list\" v-model=\"sourceUrlSelect\">\n    <virtual-list\n      style=\"height: 100%; overflow-y: auto; overflow-x: hidden\"\n      :data-key=\"(source: Source) => getSourceName(source)\"\n      :data-sources=\"sourcesFiltered\"\n      :data-component=\"SourceItem\"\n      :estimate-size=\"45\"\n    />\n  </el-checkbox-group>\n</template>\n\n<script setup lang=\"ts\">\nimport API from '@api'\nimport { Folder, Delete, Download, Search } from '@element-plus/icons-vue'\nimport {\n  isSourceMatches,\n  getSourceUniqueKey,\n  getSourceName,\n  convertSourcesToMap,\n} from '@utils/souce'\nimport VirtualList from 'vue3-virtual-scroll-list'\nimport SourceItem from './SourceItem.vue'\nimport type { Source } from '@/source'\n\nconst store = useSourceStore()\nconst sourceUrlSelect = ref<string[]>([])\nconst searchKey = ref('')\nconst sources = computed(() => store.sources)\n\n/* 筛选源 */\nconst sourcesFiltered = computed<Source[]>(() => {\n  const key = searchKey.value\n  if (key === '') return sources.value\n  return sources.value.filter(source => isSourceMatches(source, key))\n})\n// 计算当前筛选关键词下的选中源\nconst sourceSelect = computed<Source[]>(() => {\n  const urls = sourceUrlSelect.value\n  if (urls.length == 0) return []\n  const sourcesFilteredMap =\n    searchKey.value == ''\n      ? store.sourcesMap\n      : convertSourcesToMap(sourcesFiltered.value)\n  return urls.reduce((sources, sourceUrl) => {\n    const source = sourcesFilteredMap.get(sourceUrl)\n    if (source) sources.push(source)\n    return sources\n  }, [] as Source[])\n})\n\nconst deleteSelectSources = () => {\n  const sourceSelectValue = sourceSelect.value\n  API.deleteSource(sourceSelectValue).then(({ data }) => {\n    if (!data.isSuccess) return ElMessage.error(data.errorMsg)\n    store.deleteSources(sourceSelectValue)\n    const sourceUrlSelectRawValue = toRaw(sourceUrlSelect.value)\n    sourceSelectValue.forEach(source => {\n      const index = sourceUrlSelectRawValue.indexOf(getSourceUniqueKey(source))\n      if (index > -1) sourceUrlSelectRawValue.splice(index, 1)\n    })\n    sourceUrlSelect.value = sourceUrlSelectRawValue\n  })\n}\nconst clearAllSources = () => {\n  store.clearAllSource()\n  sourceUrlSelect.value = []\n}\n\n//导入本地文件\nconst importSourceFile = () => {\n  const input = document.createElement('input')\n  input.type = 'file'\n  input.accept = '.json,.txt'\n  input.addEventListener('change', () => {\n    const files = input.files\n    if (files === null) {\n      return ElMessage.info('未选择文件')\n    }\n    const reader = new FileReader()\n    reader.readAsText(files[0])\n    reader.onload = () => {\n      try {\n        const jsonData = JSON.parse(reader.result as string)\n        store.saveSources(jsonData)\n      } catch (e: unknown) {\n        ElMessage.error('上传的源格式错误: ' + (e as Error).message)\n      }\n    }\n  })\n  input.click()\n}\n\nconst isBookSource = /bookSource/i.test(window.location.href)\nconst outExport = () => {\n  const exportFile = document.createElement('a')\n  const sources =\n      sourceUrlSelect.value.length === 0\n        ? sourcesFiltered.value\n        : sourceSelect.value,\n    sourceType = isBookSource ? 'BookSource' : 'RssSource'\n\n  exportFile.download = `${sourceType}_${Date()\n    .replace(/.*?\\s(\\d+)\\s(\\d+)\\s(\\d+:\\d+:\\d+).*/, '$2$1$3')\n    .replace(/:/g, '')}.json`\n\n  const myBlob = new Blob([JSON.stringify(sources, null, 4)], {\n    type: 'application/json',\n  })\n  exportFile.href = window.URL.createObjectURL(myBlob)\n  exportFile.click()\n  window.URL.revokeObjectURL(exportFile.href) //avoid memory leak\n}\n</script>\n\n<style lang=\"scss\" scoped>\n.tool {\n  display: flex;\n  margin: 4px 0;\n  justify-content: center;\n}\n\n#source-list {\n  margin-top: 6px;\n  height: calc(100vh - 112px - 7px);\n  :deep(.el-checkbox) {\n    margin-bottom: 4px;\n    width: 100%;\n  }\n}\n</style>\n"
  },
  {
    "path": "modules/web/src/components/SourceTabForm.vue",
    "content": "<template>\n  <el-tabs id=\"source-edit\">\n    <el-tab-pane\n      v-for=\"{ name, children } in Object.values(config)\"\n      :label=\"name\"\n      :key=\"name\"\n    >\n      <el-form label-position=\"right\" label-width=\"auto\">\n        <el-form-item\n          v-for=\"{\n            type,\n            title,\n            namespace,\n            id,\n            array,\n            hint,\n            required = false,\n          } in children\"\n          :label=\"title\"\n          :key=\"title\"\n          :required=\"required\"\n        >\n          <el-input\n            v-if=\"type == 'String' && typeof namespace == 'undefined'\"\n            type=\"textarea\"\n            v-model=\"currentSource[id]\"\n            :placeholder=\"hint\"\n            autosize\n          />\n          <el-input\n            v-if=\"type == 'String' && typeof namespace != 'undefined'\"\n            type=\"textarea\"\n            v-model=\"currentSource[namespace][id]\"\n            :placeholder=\"hint\"\n            autosize\n          />\n\n          <el-switch\n            v-if=\"(type as string) === 'Boolean'\"\n            v-model=\"currentSource[id]\"\n          />\n\n          <el-input-number\n            v-if=\"(type as string) === 'Number'\"\n            v-model=\"currentSource[id]\"\n            :min=\"0\"\n          />\n\n          <el-select\n            v-if=\"(type as string) === 'Array'\"\n            v-model=\"currentSource[id]\"\n          >\n            <el-option\n              v-for=\"(optionName, index) in array\"\n              :value=\"index\"\n              :key=\"optionName\"\n              :label=\"optionName\"\n            />\n          </el-select>\n        </el-form-item>\n      </el-form>\n    </el-tab-pane>\n  </el-tabs>\n</template>\n\n<script setup lang=\"ts\">\nimport type { SourceConfig } from '@/config/sourceConfig'\n\nconst store = useSourceStore()\ndefineProps<{ config: SourceConfig }>()\n\nconst currentSource = computed(() => store.currentSource)\n/* \n修改currentSource的属性 没有直接修改本身\nconst { currentSource } = storeToRefs(store);\n */\n</script>\n\n<style lang=\"scss\" scoped>\n:deep(.el-tab-pane) {\n  height: calc(100vh - 55px);\n  padding-top: 15px;\n  padding-right: 5px;\n  overflow-y: auto;\n}\n:deep(.el-tabs__header) {\n  margin: 0;\n}\n</style>\n"
  },
  {
    "path": "modules/web/src/components/SourceTabTools.vue",
    "content": "<template>\n  <el-tabs v-model=\"current_tab\">\n    <el-tab-pane\n      v-for=\"(tab, index) in tabData\"\n      :key=\"tab[0]\"\n      :name=\"tab[0]\"\n      :label=\"tab[1]\"\n    >\n      <source-json v-if=\"index == 0\" />\n      <source-debug v-if=\"index == 1\" />\n      <source-list v-if=\"index == 2\" />\n      <source-help v-if=\"index == 3\" />\n    </el-tab-pane>\n  </el-tabs>\n</template>\n\n<script setup lang=\"ts\">\nimport { useSourceStore } from '@/store'\n\nconst store = useSourceStore()\n\nconst current_tab = computed({\n  get: () => store.currentTab,\n  set: val => (store.currentTab = val),\n})\n\nconst tabData = ref([\n  ['editTab', '编辑源'],\n  ['editDebug', '调试源'],\n  ['editList', '源列表'],\n  ['editHelp', '帮助信息'],\n])\n</script>\n\n<style lang=\"scss\" scoped>\n:deep(.el-tabs__header) {\n  margin-bottom: 5px;\n}\n</style>\n"
  },
  {
    "path": "modules/web/src/components/ToolBar.vue",
    "content": "<template>\n  <div class=\"menu flex-column-center\">\n    <el-button\n      v-for=\"button in buttons\"\n      size=\"large\"\n      :key=\"button.name\"\n      @click=\"button.action\"\n    >\n      {{ button.name }}\n    </el-button>\n    <el-button size=\"large\" @click=\"() => (hotkeysDialogVisible = true)\"\n      >快捷键</el-button\n    >\n  </div>\n  <el-dialog\n    v-model=\"hotkeysDialogVisible\"\n    :show-close=\"false\"\n    :before-close=\"stopRecordKeyDown\"\n  >\n    <template #header=\"{ titleClass, titleId }\">\n      <div class=\"hotkeys-header flex-space-between\">\n        <div :id=\"titleId\" :class=\"titleClass\">\n          快捷键设置\n          <span v-if=\"recordKeyDowning\">\n            <el-text> / 录入中 </el-text>\n          </span>\n        </div>\n        <el-button\n          :disabled=\"recordKeyDowning\"\n          @click=\"saveHotKeys\"\n          :icon=\"CircleCheckFilled\"\n          >保存</el-button\n        >\n      </div>\n    </template>\n\n    <div class=\"hotkeys-settings flex-column-center\">\n      <div\n        v-for=\"(button, buttonIndex) in buttons\"\n        :key=\"button.name\"\n        class=\"hotkeys-item flex-space-between\"\n      >\n        <span class=\"title\"\n          ><el-text>{{ button.name }}</el-text></span\n        >\n        <div class=\"hotkeys-item__content\">\n          <div v-for=\"(key, hotKeysIndex) in button.hotKeys\" :key=\"key\">\n            <kbd>{{ key }}</kbd>\n            <span v-if=\"hotKeysIndex + 1 < button.hotKeys.length\">\n              <el-text>+</el-text>\n            </span>\n          </div>\n          <span v-if=\"button.hotKeys.length == 0\">未设置</span>\n        </div>\n        <el-button\n          :disabled=\"recordKeyDowning\"\n          text\n          :icon=\"Edit\"\n          @click=\"recordKeyDown(buttonIndex)\"\n          >编辑</el-button\n        >\n      </div>\n    </div>\n  </el-dialog>\n</template>\n\n<script setup lang=\"ts\">\nimport API from '@api'\nimport { CircleCheckFilled, Edit } from '@element-plus/icons-vue'\nimport hotkeys from 'hotkeys-js'\nimport { getSourceName, isInvaildSource, normalizeSource } from '../utils/souce'\n\nconst store = useSourceStore()\nconst pull = () => {\n  const loadingMsg = ElMessage({\n    message: '加载中……',\n    showClose: true,\n    duration: 0,\n  })\n  API.getSources()\n    .then(({ data }) => {\n      if (data.isSuccess) {\n        store.changeTabName('editList')\n        store.saveSources(data.data)\n        ElMessage({\n          message: `成功拉取${data.data.length}条源`,\n          type: 'success',\n        })\n      } else {\n        ElMessage({\n          message: data.errorMsg ?? '后端错误',\n          type: 'error',\n        })\n      }\n    })\n    .finally(() => loadingMsg.close())\n}\n\nconst push = () => {\n  const sources = store.sources\n  store.changeTabName('editList')\n  if (sources.length === 0) {\n    return ElMessage({\n      message: '空空如也',\n      type: 'info',\n    })\n  }\n  ElMessage({\n    message: '正在推送中',\n    type: 'info',\n  })\n  API.saveSources(sources).then(({ data }) => {\n    if (data.isSuccess) {\n      const okData = data.data\n      if (Array.isArray(okData)) {\n        let failMsg = ``\n        if (sources.length > okData.length) {\n          failMsg = '\\n推送失败的源将用红色字体标注!'\n          store.setPushReturnSources(okData)\n        }\n        ElMessage({\n          message: `批量推送源到「阅读3.0APP」\\n共计: ${\n            sources.length\n          } 条\\n成功: ${okData.length} 条\\n失败: ${\n            sources.length - okData.length\n          } 条${failMsg}`,\n          type: 'success',\n        })\n      }\n    } else {\n      ElMessage({\n        message: `批量推送源失败!\\nErrorMsg: ${data.errorMsg}`,\n        type: 'error',\n      })\n    }\n  })\n}\n\nconst conver2Tab = () => {\n  store.changeTabName('editTab')\n  store.changeEditTabSource(store.currentSource)\n}\nconst conver2Source = () => {\n  store.changeCurrentSource(store.editTabSource)\n}\n\nconst undo = () => {\n  store.editHistoryUndo()\n}\n\nconst clearEdit = () => {\n  store.clearEdit()\n  ElMessage({\n    message: '已清除',\n    type: 'success',\n  })\n}\n\nconst redo = () => {\n  store.clearEdit()\n  store.clearAllHistory()\n  ElMessage({\n    message: '已清除所有历史记录',\n    type: 'success',\n  })\n}\n\nconst saveSource = () => {\n  const source = store.currentSource\n  if (isInvaildSource(source)) {\n    normalizeSource(source)\n    API.saveSource(source).then(({ data }) => {\n      const sourceName = getSourceName(source)\n      if (data.isSuccess) {\n        ElMessage({\n          message: `源《${sourceName}》已成功保存到「阅读3.0APP」`,\n          type: 'success',\n        })\n        //save to store\n        store.saveCurrentSource()\n      } else {\n        ElMessage({\n          message: `源《${sourceName}》保存失败!\\nErrorMsg: ${data.errorMsg}`,\n          type: 'error',\n        })\n      }\n    })\n  } else {\n    ElMessage({\n      message: `请检查<必填>项是否全部填写`,\n      type: 'error',\n    })\n  }\n}\n\nconst debug = () => {\n  store.startDebug()\n}\n\nconst buttons = ref<{ name: string; hotKeys: string[]; action: () => void }[]>(\n  Array.of(\n    { name: '⇈推送源', hotKeys: [], action: push },\n    { name: '⇊拉取源', hotKeys: [], action: pull },\n    { name: '⋙生成源', hotKeys: [], action: conver2Tab },\n    { name: '⋘编辑源', hotKeys: [], action: conver2Source },\n    { name: '✗清空表单', hotKeys: [], action: clearEdit },\n    { name: '↶撤销操作', hotKeys: [], action: undo },\n    { name: '↷重做操作', hotKeys: [], action: redo },\n    { name: '⇏调试源', hotKeys: [], action: debug },\n    { name: '✓保存源', hotKeys: [], action: saveSource },\n  ),\n)\nconst hotkeysDialogVisible = ref(true)\n\nconst recordKeyDowning = ref(false)\n\nconst recordKeyDownIndex = ref(-1)\n\nconst stopRecordKeyDown = () => {\n  if (!recordKeyDowning.value) {\n    hotkeysDialogVisible.value = false\n  }\n  recordKeyDowning.value = false\n}\n\nwatch(\n  hotkeysDialogVisible,\n  visibale => {\n    if (!visibale) {\n      hotkeys.unbind('*')\n      readHotkeysConfig()\n      bindHotKeys()\n      return\n    }\n    readHotkeysConfig()\n    hotkeys.unbind()\n    /**监听按键 */\n    hotkeys('*', event => {\n      event.preventDefault()\n      const pressedKeys = hotkeys.getPressedKeyString()\n      if (pressedKeys.length == 1 && pressedKeys[0] == 'esc') {\n        //单独按下esc 不录入\n        return\n      }\n      if (recordKeyDowning.value && recordKeyDownIndex.value > -1)\n        buttons.value[recordKeyDownIndex.value].hotKeys = pressedKeys\n    })\n  },\n  { immediate: true },\n)\n\nconst recordKeyDown = (index: number) => {\n  recordKeyDowning.value = true\n  ElMessage({\n    message: '按ESC键或者点击空白处结束录入',\n    type: 'info',\n  })\n  buttons.value[index].hotKeys = []\n  recordKeyDownIndex.value = index\n}\n\nconst saveHotKeys = () => {\n  const hotKeysConfig: string[][] = []\n  buttons.value.forEach(({ hotKeys }) => {\n    hotKeysConfig.push(hotKeys)\n  })\n  saveHotkeysConfig(hotKeysConfig)\n  hotkeysDialogVisible.value = false\n}\n\nconst bindHotKeys = () => {\n  // hotkeys默认过滤INPUT SELECT TEXTAREA\n  hotkeys.filter = () => true\n  buttons.value.forEach(({ hotKeys, action }) => {\n    if (hotKeys.length == 0) return\n    hotkeys(hotKeys.join('+'), event => {\n      event.preventDefault()\n      action.call(null)\n    })\n  })\n}\nconst saveHotkeysConfig = (config: string[][]) => {\n  localStorage.setItem('legado_web_hotkeys', JSON.stringify(config))\n}\n\n/**\n * 读取快捷键配置\n * @return 是否成功读取配置\n */\nfunction readHotkeysConfig() {\n  try {\n    const localStorageConfig = localStorage.getItem('legado_web_hotkeys')\n    if (localStorageConfig === null) return false\n    const config = JSON.parse(localStorageConfig)\n    if (!Array.isArray(config) || config.length == 0) return false\n    buttons.value.forEach((button, index) => (button.hotKeys = config[index]))\n    return true\n  } catch {\n    ElMessage({ message: '快捷键配置错误', type: 'error' })\n    localStorage.removeItem('legado_web_hotkeys')\n  }\n  return false\n}\n\nonMounted(() => {\n  /**读取热键配置 */\n  if (readHotkeysConfig()) {\n    hotkeysDialogVisible.value = false\n  }\n})\n</script>\n\n<style lang=\"scss\" scoped>\n.flex-space-between {\n  display: flex;\n  justify-content: space-between;\n  align-items: baseline;\n}\n.flex-column-center {\n  display: flex;\n  flex-direction: column;\n  justify-content: center;\n}\n\n.menu > .el-button {\n  margin: 4px;\n  padding: 1em;\n  width: 6em;\n}\n\n.hotkeys-item {\n  .title {\n    width: 5em;\n    display: flex;\n    justify-content: flex-end;\n    margin-right: 1em;\n  }\n  .hotkeys-item__content {\n    display: flex;\n    flex-wrap: wrap;\n    flex: 1;\n    div {\n      margin-bottom: 1em;\n    }\n    span {\n      margin: 0.5em;\n    }\n  }\n}\n</style>\n"
  },
  {
    "path": "modules/web/src/components.d.ts",
    "content": "/* eslint-disable */\n// @ts-nocheck\n// Generated by unplugin-vue-components\n// Read more: https://github.com/vuejs/core/pull/3399\nexport {}\n\n/* prettier-ignore */\ndeclare module 'vue' {\n  export interface GlobalComponents {\n    BookItems: typeof import('./components/BookItems.vue')['default']\n    CatalogItem: typeof import('./components/CatalogItem.vue')['default']\n    ChapterContent: typeof import('./components/ChapterContent.vue')['default']\n    ElButton: typeof import('element-plus/es')['ElButton']\n    ElCheckbox: typeof import('element-plus/es')['ElCheckbox']\n    ElCheckboxGroup: typeof import('element-plus/es')['ElCheckboxGroup']\n    ElDialog: typeof import('element-plus/es')['ElDialog']\n    ElForm: typeof import('element-plus/es')['ElForm']\n    ElFormItem: typeof import('element-plus/es')['ElFormItem']\n    ElInput: typeof import('element-plus/es')['ElInput']\n    ElInputNumber: typeof import('element-plus/es')['ElInputNumber']\n    ElLink: typeof import('element-plus/es')['ElLink']\n    ElOption: typeof import('element-plus/es')['ElOption']\n    ElPopover: typeof import('element-plus/es')['ElPopover']\n    ElSelect: typeof import('element-plus/es')['ElSelect']\n    ElSwitch: typeof import('element-plus/es')['ElSwitch']\n    ElTabPane: typeof import('element-plus/es')['ElTabPane']\n    ElTabs: typeof import('element-plus/es')['ElTabs']\n    ElTag: typeof import('element-plus/es')['ElTag']\n    ElText: typeof import('element-plus/es')['ElText']\n    ElTooltip: typeof import('element-plus/es')['ElTooltip']\n    PopCatalog: typeof import('./components/PopCatalog.vue')['default']\n    ReadSettings: typeof import('./components/ReadSettings.vue')['default']\n    RouterLink: typeof import('vue-router')['RouterLink']\n    RouterView: typeof import('vue-router')['RouterView']\n    SourceDebug: typeof import('./components/SourceDebug.vue')['default']\n    SourceHelp: typeof import('./components/SourceHelp.vue')['default']\n    SourceItem: typeof import('./components/SourceItem.vue')['default']\n    SourceJson: typeof import('./components/SourceJson.vue')['default']\n    SourceList: typeof import('./components/SourceList.vue')['default']\n    SourceTabForm: typeof import('./components/SourceTabForm.vue')['default']\n    SourceTabTools: typeof import('./components/SourceTabTools.vue')['default']\n    ToolBar: typeof import('./components/ToolBar.vue')['default']\n  }\n}\n"
  },
  {
    "path": "modules/web/src/config/bookSourceEditConfig.ts",
    "content": "export default {\n  base: {\n    name: '基础',\n    children: [\n      {\n        title: '源类型',\n        id: 'bookSourceType',\n        type: 'Array',\n        array: ['文本', '音频', '图片', '文件'],\n        required: true,\n      },\n      {\n        title: '源域名',\n        id: 'bookSourceUrl',\n        type: 'String',\n        hint: '通常填写网站主页,例: https://www.qidian.com',\n        required: true,\n      },\n      {\n        title: '源名称',\n        id: 'bookSourceName',\n        type: 'String',\n        hint: '会显示在源列表',\n        required: true,\n      },\n      {\n        title: '源分组',\n        id: 'bookSourceGroup',\n        type: 'String',\n        hint: '描述源的特征信息',\n      },\n      {\n        title: '源注释',\n        id: 'bookSourceComment',\n        type: 'String',\n        hint: '描述源作者和状态',\n      },\n      {\n        title: '登录地址',\n        id: 'loginUrl',\n        type: 'String',\n        hint: '填写网站登录网址,仅在需要登录的源有用',\n      },\n      {\n        title: '登录界面',\n        id: 'loginUi',\n        type: 'String',\n        hint: '自定义登录界面',\n      },\n      {\n        title: '登录检测',\n        id: 'loginCheckJs',\n        type: 'String',\n        hint: '登录检测js',\n      },\n      {\n        title: '封面解密',\n        id: 'coverDecodeJs',\n        type: 'String',\n        hint: '封面解密js',\n      },\n      {\n        title: '链接验证',\n        id: 'bookUrlPattern',\n        type: 'String',\n        hint: '书籍URL正则，当详情页URL与源URL的域名不一致时有效，用于添加网址',\n      },\n      {\n        title: '请求头',\n        id: 'header',\n        type: 'String',\n        hint: '客户端标识',\n      },\n      {\n        title: '变量说明',\n        id: 'variableComment',\n        type: 'String',\n        hint: '书源变量说明',\n      },\n      {\n        title: '并发率',\n        id: 'concurrentRate',\n        type: 'String',\n        hint: '并发率，如1000(访问间隔1000ms)或者1/1000(1000ms内访问1次)',\n      },\n      {\n        title: 'js库',\n        id: 'jsLib',\n        type: 'String',\n        hint: 'js库, 可填写js或者key-value object获取在线js文件',\n      },\n    ],\n  },\n  search: {\n    name: '搜索',\n    children: [\n      {\n        title: '搜索地址',\n        id: 'searchUrl',\n        type: 'String',\n        hint: '[域名可省略]/search.php@kw={{key}}',\n      },\n      {\n        title: '校验文字',\n        namespace: 'ruleSearch',\n        id: 'checkKeyWord',\n        type: 'String',\n        hint: '校验关键字，强烈建议填写',\n      },\n      {\n        title: '列表规则',\n        namespace: 'ruleSearch',\n        id: 'bookList',\n        type: 'String',\n        hint: '选择书籍节点 (规则结果为List<Element>)',\n      },\n      {\n        title: '书名规则',\n        namespace: 'ruleSearch',\n        id: 'name',\n        type: 'String',\n        hint: '选择节点书名 (规则结果为String)',\n      },\n      {\n        title: '作者规则',\n        namespace: 'ruleSearch',\n        id: 'author',\n        type: 'String',\n        hint: '选择节点作者 (规则结果为String)',\n      },\n      {\n        title: '分类规则',\n        namespace: 'ruleSearch',\n        id: 'kind',\n        type: 'String',\n        hint: '选择节点分类信息 (规则结果为String)',\n      },\n      {\n        title: '字数规则',\n        namespace: 'ruleSearch',\n        id: 'wordCount',\n        type: 'String',\n        hint: '选择节点字数信息 (规则结果为String)',\n      },\n      {\n        title: '最新章节',\n        namespace: 'ruleSearch',\n        id: 'lastChapter',\n        type: 'String',\n        hint: '选择节点最新章节 (规则结果为String)',\n      },\n      {\n        title: '简介规则',\n        namespace: 'ruleSearch',\n        id: 'intro',\n        type: 'String',\n        hint: '选择节点书籍简介 (规则结果为String)',\n      },\n      {\n        title: '封面规则',\n        namespace: 'ruleSearch',\n        id: 'coverUrl',\n        type: 'String',\n        hint: '选择节点书籍封面 (规则结果为String类型的url)',\n      },\n      {\n        title: '详情地址',\n        namespace: 'ruleSearch',\n        id: 'bookUrl',\n        type: 'String',\n        hint: '选择书籍详情页网址 (规则结果为String类型的url)',\n      },\n    ],\n  },\n  find: {\n    name: '发现',\n    children: [\n      {\n        title: '发现地址',\n        id: 'exploreUrl',\n        type: 'String',\n        hint: '单个发现格式<name>::<url>或者{url:<url>,title:<name>,style:...}；前者用换行符或者&&连接，后者放在数组内；可用js动态生成',\n      },\n      {\n        title: '列表规则',\n        namespace: 'ruleExplore',\n        id: 'bookList',\n        type: 'String',\n        hint: '选择书籍节点 (规则结果为List<Element>)',\n      },\n      {\n        title: '书名规则',\n        namespace: 'ruleExplore',\n        id: 'name',\n        type: 'String',\n        hint: '选择节点书名 (规则结果为String)',\n      },\n      {\n        title: '作者规则',\n        namespace: 'ruleExplore',\n        id: 'author',\n        type: 'String',\n        hint: '选择节点作者 (规则结果为String)',\n      },\n      {\n        title: '分类规则',\n        namespace: 'ruleExplore',\n        id: 'kind',\n        type: 'String',\n        hint: '选择节点分类信息 (规则结果为String)',\n      },\n      {\n        title: '字数规则',\n        namespace: 'ruleExplore',\n        id: 'wordCount',\n        type: 'String',\n        hint: '选择节点字数信息 (规则结果为String)',\n      },\n      {\n        title: '最新章节',\n        namespace: 'ruleExplore',\n        id: 'lastChapter',\n        type: 'String',\n        hint: '选择节点最新章节 (规则结果为String)',\n      },\n      {\n        title: '简介规则',\n        namespace: 'ruleExplore',\n        id: 'intro',\n        type: 'String',\n        hint: '选择节点书籍简介 (规则结果为String)',\n      },\n      {\n        title: '封面规则',\n        namespace: 'ruleExplore',\n        id: 'coverUrl',\n        type: 'String',\n        hint: '选择节点书籍封面 (规则结果为String类型的url)',\n      },\n      {\n        title: '详情地址',\n        namespace: 'ruleExplore',\n        id: 'bookUrl',\n        type: 'String',\n        hint: '选择书籍详情页网址 (规则结果为String类型的url)',\n      },\n    ],\n  },\n  detail: {\n    name: '详情',\n    children: [\n      {\n        title: '预处理',\n        namespace: 'ruleBookInfo',\n        id: 'init',\n        type: 'String',\n        hint: '用于加速详情信息检索，只支持AllInOne规则',\n      },\n      {\n        title: '书名规则',\n        namespace: 'ruleBookInfo',\n        id: 'name',\n        type: 'String',\n        hint: '选择节点书名 (规则结果为String)',\n      },\n      {\n        title: '作者规则',\n        namespace: 'ruleBookInfo',\n        id: 'author',\n        type: 'String',\n        hint: '选择节点作者 (规则结果为String)',\n      },\n      {\n        title: '分类规则',\n        namespace: 'ruleBookInfo',\n        id: 'kind',\n        type: 'String',\n        hint: '选择节点分类信息 (规则结果为String)',\n      },\n      {\n        title: '字数规则',\n        namespace: 'ruleBookInfo',\n        id: 'wordCount',\n        type: 'String',\n        hint: '选择节点字数信息 (规则结果为String)',\n      },\n      {\n        title: '最新章节',\n        namespace: 'ruleBookInfo',\n        id: 'lastChapter',\n        type: 'String',\n        hint: '选择节点最新章节 (规则结果为String)',\n      },\n      {\n        title: '简介规则',\n        namespace: 'ruleBookInfo',\n        id: 'intro',\n        type: 'String',\n        hint: '选择节点书籍简介 (规则结果为String)',\n      },\n      {\n        title: '封面规则',\n        namespace: 'ruleBookInfo',\n        id: 'coverUrl',\n        type: 'String',\n        hint: '选择节点书籍封面 (规则结果为String类型的url)',\n      },\n      {\n        title: '目录地址',\n        namespace: 'ruleBookInfo',\n        id: 'tocUrl',\n        type: 'String',\n        hint: '选择书籍详情页网址 (规则结果为String类型的url, 与详情页相同时可省略)',\n      },\n      {\n        title: '修改书籍',\n        namespace: 'ruleBookInfo',\n        id: 'canReName',\n        type: 'String',\n        hint: '允许修改书名作者(规则结果为String类型, 默认不允许)',\n      },\n      {\n        title: '下载URL',\n        namespace: 'ruleBookInfo',\n        id: 'downloadUrls',\n        type: 'String',\n        hint: '文件类书源下载地址 (规则结果为String类型的url, 多个链接返回数组)',\n      },\n    ],\n  },\n  directory: {\n    name: '目录',\n    children: [\n      {\n        title: '更新前JS',\n        namespace: 'ruleToc',\n        id: 'preUpdateJs',\n        type: 'String',\n        hint: '更新目录前调用JS 动态更新目录链接',\n      },\n      {\n        title: '列表规则',\n        namespace: 'ruleToc',\n        id: 'chapterList',\n        type: 'String',\n        hint: '选择目录列表的章节节点 (规则结果为List<Element>)',\n      },\n      {\n        title: '章节名称',\n        namespace: 'ruleToc',\n        id: 'chapterName',\n        type: 'String',\n        hint: '选择章节名称 (规则结果为String)',\n      },\n      {\n        title: '章节地址',\n        namespace: 'ruleToc',\n        id: 'chapterUrl',\n        type: 'String',\n        hint: '选择章节链接 (规则结果为String类型的Url)',\n      },\n      {\n        title: '标题处理',\n        namespace: 'ruleToc',\n        id: 'formatJs',\n        type: 'String',\n        hint: '遍历去重后的章节列表的回调，提供index(章节序号从1开始)、title(章节标题)变量，额外提供gInt(初始值0)，返回值作为新的标题',\n      },\n      {\n        title: '卷名标识',\n        namespace: 'ruleToc',\n        id: 'isVolume',\n        type: 'String',\n        hint: '章节名称是否是卷名 (规则结果为Bool)',\n      },\n      {\n        title: '章节信息',\n        namespace: 'ruleToc',\n        id: 'updateTime',\n        type: 'String',\n        hint: '选择章节信息（如更新时间） (规则结果为String)',\n      },\n      {\n        title: '收费标识',\n        namespace: 'ruleToc',\n        id: 'isVip',\n        type: 'String',\n        hint: '章节是否为VIP章节 (规则结果为Bool)',\n      },\n      {\n        title: '购买标识',\n        namespace: 'ruleToc',\n        id: 'isPay',\n        type: 'String',\n        hint: '章节是否为已购买 (规则结果为Bool)',\n      },\n      {\n        title: '翻页规则',\n        namespace: 'ruleToc',\n        id: 'nextTocUrl',\n        type: 'String',\n        hint: '选择目录下一页链接 (规则结果为List<String>或String)',\n      },\n    ],\n  },\n  content: {\n    name: '正文',\n    children: [\n      {\n        title: '正文规则',\n        namespace: 'ruleContent',\n        id: 'content',\n        type: 'String',\n        hint: '选择正文内容 (规则结果为String)',\n      },\n      {\n        title: '标题规则',\n        namespace: 'ruleContent',\n        id: 'title',\n        type: 'String',\n        hint: '获取结果将会覆盖章节标题 (规则结果为String)',\n      },\n      {\n        title: '翻页规则',\n        namespace: 'ruleContent',\n        id: 'nextContentUrl',\n        type: 'String',\n        hint: '选择下一分页(不是下一章)链接 (规则结果为String类型的Url)',\n      },\n      {\n        title: '脚本注入',\n        namespace: 'ruleContent',\n        id: 'webJs',\n        type: 'String',\n        hint: '注入javascript，用于模拟鼠标点击等，必须有返回值，一般为String类型',\n      },\n      {\n        title: '资源正则',\n        namespace: 'ruleContent',\n        id: 'sourceRegex',\n        type: 'String',\n        hint: '匹配资源的url特征，用于嗅探',\n      },\n      {\n        title: '替换规则',\n        namespace: 'ruleContent',\n        id: 'replaceRegex',\n        type: 'String',\n        hint: '多页内容合并后替换，用于正文净化',\n      },\n      {\n        title: '图片样式',\n        namespace: 'ruleContent',\n        id: 'imageStyle',\n        type: 'String',\n        hint: 'FULL:铺满 不填:默认样式',\n      },\n      {\n        title: '图片解密',\n        namespace: 'ruleContent',\n        id: 'imageDecode',\n        type: 'String',\n        hint: '填写JavaScript 返回解密图片的bytes ',\n      },\n      {\n        title: '购买操作',\n        namespace: 'ruleContent',\n        id: 'payAction',\n        type: 'String',\n        hint: '填写JavaScript 返回购买链接或者调用购买接口',\n      },\n    ],\n  },\n  /*\n  review: {\n    name: \"段评\",\n    children: [\n      {\n        title: \"段评URL\",\n        namespace: \"ruleReview\",\nid: \"reviewUrl\",\n        type: \"String\",\n        hint: \"段评URL\",\n      },\n      {\n        title: \"发布头像\",\n        namespace: \"ruleReview\",\nid: \"avatarRule\",\n        type: \"String\",\n        hint: \"段评发布者头像\",\n      },\n      {\n        title: \"段评内容\",\n        namespace: \"ruleReview\",\nid: \"contentRule\",\n        type: \"String\",\n        hint: \"段评内容\",\n      },\n      {\n        title: \"发布时间\",\n        namespace: \"ruleReview\",\nid: \"postTimeRule\",\n        type: \"String\",\n        hint: \"段评发布时间\",\n      },\n      {\n        title: \"回复URL\",\n        namespace: \"ruleReview\",\nid: \"reviewQuoteUrl\",\n        type: \"String\",\n        hint: \"获取段评回复URL\",\n      },\n      {\n        title: \"点赞URL\",\n        namespace: \"ruleReview\",\nid: \"voteUpUrl\",\n        type: \"String\",\n        hint: \"点赞URL\",\n      },\n      {\n        title: \"点踩URL\",\n        namespace: \"ruleReview\",\nid: \"voteDownUrl\",\n        type: \"String\",\n        hint: \"点踩URL\",\n      },\n      {\n        title: \"发送回复\",\n        namespace: \"ruleReview\",\nid: \"postReviewUrl\",\n        type: \"String\",\n        hint: \"发送回复URL\",\n      },\n      {\n        title: \"回复段评\",\n        namespace: \"ruleReview\",\nid: \"postQuoteUrl\",\n        type: \"String\",\n        hint: \"发送回复段评URL\",\n      },\n      {\n        title: \"删除段评\",\n        namespace: \"ruleReview\",\nid: \"deleteUrl\",\n        type: \"String\",\n        hint: \"删除段评URL\",\n      },\n    ],\n  },*/\n  other: {\n    name: '其他',\n    children: [\n      {\n        title: '启用搜索',\n        id: 'enabled',\n        type: 'Boolean',\n      },\n      {\n        title: '启用发现',\n        id: 'enabledExplore',\n        type: 'Boolean',\n      },\n      // {\n      //   title: \"启用段评\",\n      //   id: \"enabledReview\",\n      //   type: \"Boolean\",\n      // },\n      {\n        title: 'CookieJar',\n        id: 'enabledCookieJar',\n        type: 'Boolean',\n      },\n      {\n        title: '搜索权重',\n        id: 'weight',\n        type: 'Number',\n      },\n      {\n        title: '排序编号',\n        id: 'customOrder',\n        type: 'Number',\n      },\n    ],\n  },\n}\n"
  },
  {
    "path": "modules/web/src/config/rssSourceEditConfig.ts",
    "content": "export default {\n  base: {\n    name: '基础',\n    children: [\n      {\n        title: '源域名',\n        id: 'sourceUrl',\n        type: 'String',\n        hint: '通常填写网站主页,例: https://www.qidian.com',\n        required: true,\n      },\n      {\n        title: '图标',\n        id: 'sourceIcon',\n        type: 'String',\n        hint: '填写图片网络链接',\n      },\n      {\n        title: '源名称',\n        id: 'sourceName',\n        type: 'String',\n        hint: '会显示在源列表',\n        required: true,\n      },\n      {\n        title: '源分组',\n        id: 'sourceGroup',\n        type: 'String',\n        hint: '描述源的特征信息',\n      },\n      {\n        title: '源注释',\n        id: 'sourceComment',\n        type: 'String',\n        hint: '描述源作者和状态',\n      },\n      {\n        title: '分类地址',\n        id: 'sortUrl',\n        type: 'String',\n        hint: '名称1::链接1\\n名称2::链接2',\n      },\n      {\n        title: '登录地址',\n        id: 'loginUrl',\n        type: 'String',\n        hint: '填写网站登录网址,仅在需要登录的源有用',\n      },\n      {\n        title: '登录界面',\n        id: 'loginUi',\n        type: 'String',\n        hint: '自定义登录界面',\n      },\n      {\n        title: '登录检测',\n        id: 'loginCheckJs',\n        type: 'String',\n        hint: '登录检测js',\n      },\n      {\n        title: '封面解密',\n        id: 'coverDecodeJs',\n        type: 'String',\n        hint: '封面解密js',\n      },\n      {\n        title: '请求头',\n        id: 'header',\n        type: 'String',\n        hint: '客户端标识',\n      },\n      {\n        title: '变量说明',\n        id: 'variableComment',\n        type: 'String',\n        hint: '源变量说明',\n      },\n      {\n        title: '并发率',\n        id: 'concurrentRate',\n        type: 'String',\n        hint: '并发率',\n      },\n      {\n        title: 'js库',\n        id: 'jsLib',\n        type: 'String',\n        hint: 'js库, 可填写js或者key-value object获取在线js文件',\n      },\n    ],\n  },\n  list: {\n    name: '列表',\n    children: [\n      {\n        title: '列表规则',\n        id: 'ruleArticles',\n        type: 'String',\n        hint: '规则结果为List<Element>',\n      },\n      {\n        title: '翻页规则',\n        id: 'ruleNextPage',\n        type: 'String',\n        hint: '下一页链接 规则结果为List<String>或String',\n      },\n      {\n        title: '标题规则',\n        id: 'ruleTitle',\n        type: 'String',\n        hint: '文章标题 规则结果为String',\n      },\n      {\n        title: '时间规则',\n        id: 'rulePubDate',\n        type: 'String',\n        hint: '文章发布时间 规则结果为String',\n      },\n      {\n        title: '描述规则',\n        id: 'ruleDescription',\n        type: 'String',\n        hint: '文章简要描述 规则结果为String',\n      },\n      {\n        title: '图片规则',\n        id: 'ruleImage',\n        type: 'String',\n        hint: '文章图片链接 规则结果为String',\n      },\n      {\n        title: '链接规则',\n        id: 'ruleLink',\n        type: 'String',\n        hint: '文章链接 规则结果为String',\n      },\n    ],\n  },\n  webView: {\n    name: 'WebView',\n    children: [\n      {\n        title: '内容规则',\n        id: 'ruleContent',\n        type: 'String',\n        hint: '文章正文',\n      },\n      {\n        title: '样式规则',\n        id: 'style',\n        type: 'String',\n        hint: '文章正文样式 填写css',\n      },\n      {\n        title: '注入规则',\n        id: 'injectJs',\n        type: 'String',\n        hint: '注入网页的JavaScript',\n      },\n      {\n        title: '黑名单',\n        id: 'contentBlacklist',\n        type: 'String',\n        hint: 'webView链接加载黑名单，英文逗号隔开',\n      },\n      {\n        title: '白名单',\n        id: 'contentWhitelist',\n        type: 'String',\n        hint: 'webView链接加载白名单，英文逗号隔开',\n      },\n      {\n        title: '链接拦截',\n        id: 'shouldOverrideUrlLoading',\n        type: 'String',\n        hint: '填写js，变量url为当前资源链接，返回true拦截',\n      },\n    ],\n  },\n  other: {\n    name: '其他',\n    children: [\n      {\n        title: '列表样式',\n        id: 'articleStyle',\n        type: 'Array',\n        array: ['默认', '大图', '双列'],\n      },\n      {\n        title: '加载地址',\n        id: 'loadWithBaseUrl',\n        type: 'Boolean',\n      },\n      {\n        title: '启用JS',\n        id: 'enableJs',\n        type: 'Boolean',\n      },\n      {\n        title: '启用',\n        id: 'enabled',\n        type: 'Boolean',\n      },\n      {\n        title: 'Cookie',\n        id: 'enabledCookieJar',\n        type: 'Boolean',\n      },\n      {\n        title: '单URL',\n        id: 'singleUrl',\n        type: 'Boolean',\n      },\n      {\n        title: '排序编号',\n        id: 'customOrder',\n        type: 'Number',\n      },\n    ],\n  },\n}\n"
  },
  {
    "path": "modules/web/src/config/sourceConfig.d.ts",
    "content": "import type { Source } from '@/source'\nimport bookSourceEditConfig from './bookSourceEditConfig'\nimport rssSourceEditConfig from './rssSourceEditConfig'\n\ntype SourceConfigKey =\n  | keyof typeof bookSourceEditConfig\n  | keyof typeof rssSourceEditConfig\ntype SourceConfigRecord = {\n  title: string\n  type: string //\"array\" | \"String\" | \"Boolean\"\n  array?: string[]\n  hint?: string\n  required?: boolean\n  namespace?: Partial<keyof Source>\n  id: Partial<keyof Source>\n}\ntype SourceConfigValue = { name: string; children: SourceConfigRecord[] }\nexport type SourceConfig = Partial<Record<SourceConfigKey, SourceConfigValue>>\n"
  },
  {
    "path": "modules/web/src/config/themeConfig.ts",
    "content": "import body_0 from '../assets/imgs/themes/body_0.png'\nimport content_0 from '../assets/imgs/themes/content_0.png'\nimport popup_0 from '../assets/imgs/themes/popup_0.png'\nimport body_1 from '../assets/imgs/themes/body_1.png'\nimport content_1 from '../assets/imgs/themes/content_1.png'\nimport popup_1 from '../assets/imgs/themes/popup_1.png'\nimport body_2 from '../assets/imgs/themes/body_2.png'\nimport content_2 from '../assets/imgs/themes/content_2.png'\nimport popup_2 from '../assets/imgs/themes/popup_2.png'\nimport body_3 from '../assets/imgs/themes/body_3.png'\nimport content_3 from '../assets/imgs/themes/content_3.png'\nimport popup_3 from '../assets/imgs/themes/popup_3.png'\nimport body_5 from '../assets/imgs/themes/body_5.png'\nimport content_5 from '../assets/imgs/themes/content_5.png'\nimport popup_5 from '../assets/imgs/themes/popup_5.png'\nimport body_6 from '../assets/imgs/themes/body_6.png'\nimport content_6 from '../assets/imgs/themes/content_6.png'\nimport popup_6 from '../assets/imgs/themes/popup_6.png'\nconst settings = {\n  themes: [\n    {\n      body: '#ede7da url(' + body_0 + ') repeat',\n      content: '#ede7da url(' + content_0 + ') repeat',\n      popup: '#ede7da url(' + popup_0 + ') repeat',\n    },\n    {\n      body: '#ede7da url(' + body_1 + ') repeat',\n      content: '#ede7da url(' + content_1 + ') repeat',\n      popup: '#ede7da url(' + popup_1 + ') repeat',\n    },\n    {\n      body: '#ede7da url(' + body_2 + ') repeat',\n      content: '#ede7da url(' + content_2 + ') repeat',\n      popup: '#ede7da url(' + popup_2 + ') repeat',\n    },\n    {\n      body: '#ede7da url(' + body_3 + ') repeat',\n      content: '#ede7da url(' + content_3 + ') repeat',\n      popup: '#ede7da url(' + popup_3 + ') repeat',\n    },\n    {\n      body: '#ebcece repeat',\n      content: '#f5e4e4 repeat',\n      popup: '#faeceb repeat',\n    },\n    {\n      body: '#ede7da url(' + body_5 + ') repeat',\n      content: '#ede7da url(' + content_5 + ') repeat',\n      popup: '#ede7da url(' + popup_5 + ') repeat',\n    },\n    {\n      body: '#ede7da url(' + body_6 + ') repeat',\n      content: '#ede7da url(' + content_6 + ') repeat',\n      popup: '#ede7da url(' + popup_6 + ') repeat',\n    },\n  ],\n  fonts: [\n    'Microsoft YaHei, PingFangSC-Regular, HelveticaNeue-Light, Helvetica Neue Light, sans-serif',\n\n    'PingFangSC-Regular, -apple-system, Simsun',\n\n    'Kaiti',\n  ],\n}\nexport default settings\n"
  },
  {
    "path": "modules/web/src/hooks/loading.css",
    "content": ".el-loading-spinner {\n  font-size: 36px;\n  color: #b5b5b5;\n}\n.el-loading-text {\n  font-weight: 500;\n  color: #b5b5b5 !important;\n}\n"
  },
  {
    "path": "modules/web/src/hooks/loading.ts",
    "content": "import { watch, unref, onUnmounted } from 'vue'\nimport { ElLoading } from 'element-plus'\nimport loadingSvg from '@element-plus/icons-svg/loading.svg?raw'\nimport 'element-plus/theme-chalk/el-loading.css'\nimport './loading.css'\n\nexport const useLoading = (\n  target: MaybeRef<string | HTMLElement | undefined>,\n  text: string,\n  spinner = loadingSvg,\n) => {\n  // loading spinner\n  const isLoading = ref(false)\n  let loadingInstance: ReturnType<typeof ElLoading.service> | null = null\n  const closeLoading = () => (isLoading.value = false)\n  const showLoading = () => (isLoading.value = true)\n  watch(isLoading, loading => {\n    if (!loading) return loadingInstance?.close()\n    loadingInstance = ElLoading.service({\n      target: unref(target),\n      spinner: spinner,\n      text: text,\n      lock: true,\n      background: 'rgba(0, 0, 0, 0)',\n    })\n  })\n\n  const loadingWrapper = (promise: Promise<unknown>) => {\n    if (!(promise instanceof Promise))\n      throw TypeError('loadingWrapper argument must be Promise')\n    showLoading()\n    return promise.finally(closeLoading)\n  }\n\n  onUnmounted(() => {\n    closeLoading()\n  })\n\n  return { isLoading, showLoading, closeLoading, loadingWrapper }\n}\n"
  },
  {
    "path": "modules/web/src/main.ts",
    "content": "import { createApp } from 'vue'\nimport App from './App.vue'\nimport router from '@/router'\nimport store from '@/store'\nimport 'element-plus/theme-chalk/dark/css-vars.css'\n\ncreateApp(App).use(store).use(router).mount('#app')\n// 书架 同步Element PLUS 夜间模式\nwatch(\n  () => useBookStore().isNight,\n  isNight => {\n    if (isNight) {\n      document.documentElement.classList.add('dark')\n    } else {\n      document.documentElement.classList.remove('dark')\n    }\n  },\n)\n\nwindow.addEventListener('vite:preloadError', event => {\n  event.preventDefault()\n})\n"
  },
  {
    "path": "modules/web/src/pages/bookshelf/README.md",
    "content": "# 「阅读3.0」 web 端（已打包进阅读3.0，不能设置IP）\n\n本程序为「阅读3.0」的配套 web 端，需要保证手机和电脑在同一局域网内，然后手机端打开 web 服务。\n\n~~在线地址 http://alanskycn.gitee.io/vip/reader/~~\n\n## 具体实现\n\n使用 Vue3 开发\n\n## 功能特性\n\n- 本地存储阅读记录与设置\n- 阅读主题切换\n- 夜间模式\n- 字号调节\n- 字体调节\n- 阅读宽度调节\n\n## 使用方法\n\n```shell\npnpm install\n#安装项目\npnpm dev\n#开发模式\npnpm build\n#打包\npnpm lint:fix\n#格式化代码\n```\n\n- 调试的时候可以修改.env.development里面的地址连接手机端调试\n"
  },
  {
    "path": "modules/web/src/pages/bookshelf/index.html",
    "content": "<!doctype html>\n<html lang=\"zh\" class=\"\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <link rel=\"icon\" href=\"/favicon.ico\" />\n    <meta name=\"viewport\" content=\"width=device-width,initial-scale=1.0\" />\n  </head>\n\n  <body>\n    <div id=\"app\"></div>\n    <script type=\"module\" src=\"./main.js\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "modules/web/src/pages/bookshelf/main.js",
    "content": "import { createApp } from 'vue'\nimport App from '@/App.vue'\nimport bookRouter from '@/router/bookRouter'\nimport store from '@/store'\nimport 'element-plus/theme-chalk/dark/css-vars.css'\n\ncreateApp(App).use(store).use(bookRouter).mount('#app')\n\n// 同步Element PLUS 夜间模式\nwatch(\n  () => useBookStore().isNight,\n  isNight => {\n    if (isNight) {\n      document.documentElement.classList.add('dark')\n    } else {\n      document.documentElement.classList.remove('dark')\n    }\n  },\n)\n"
  },
  {
    "path": "modules/web/src/pages/source/README.md",
    "content": "# legado_web_editor\n\n## 🚧开发注意\n\n如果你想要调试项目 请修改文件`.env.development`里`VITE_API`为阅读web服务ip\n\n## 路由\n\n/rssSource 订阅源编辑\n/rssSource 书源编辑\n\n## 🎨Project setup\n\n```\npnpm i\n```\n\n### Compiles and hot-reloads for development\n\n```\npnpm dev\n```\n\n### Compiles and minifies for production\n\n```\npnpm build\n```\n\n### Lints and fixes files\n\n```\npnpm lint:fix\n```\n"
  },
  {
    "path": "modules/web/src/pages/source/index.html",
    "content": "<!doctype html>\n<html lang=\"zh\" class=\"\">\n  <head>\n    <meta charset=\"UTF-8\" />\n    <link rel=\"icon\" href=\"/favicon.ico\" />\n    <meta name=\"viewport\" content=\"width=device-width,initial-scale=1.0\" />\n  </head>\n\n  <body>\n    <div id=\"app\"></div>\n    <script type=\"module\" src=\"./main.js\"></script>\n  </body>\n</html>\n"
  },
  {
    "path": "modules/web/src/pages/source/main.js",
    "content": "import { createApp } from 'vue'\nimport App from '@/App.vue'\nimport sourceRouter from '@/router/sourceRouter'\nimport store from '@/store'\nimport 'element-plus/theme-chalk/dark/css-vars.css'\n\ncreateApp(App).use(store).use(sourceRouter).mount('#app')\n"
  },
  {
    "path": "modules/web/src/plugins/jump.d.ts",
    "content": "export {}\nexport type Options = {\n  duration?: number | [(distance: number) => number]\n  offset?: number\n  callback?: () => void // \"undefined\" is a suitable default, and won't be called\n  easing?: (\n    timeElapsed: number,\n    start: number,\n    distance: number,\n    duration: number,\n  ) => number\n  a11y?: boolean\n  container?: HTMLElement | string\n}\nexport default function (\n  target: number | string | HTMLElement,\n  options: Options = {},\n): void\n"
  },
  {
    "path": "modules/web/src/plugins/jump.js",
    "content": "const easeInOutQuad = (t, b, c, d) => {\n  t /= d / 2\n  if (t < 1) return (c / 2) * t * t + b\n  t--\n  return (-c / 2) * (t * (t - 2) - 1) + b\n}\n\nconst jumper = () => {\n  // private variable cache\n  // no variables are created during a jump, preventing memory leaks\n\n  let container // container element to be scrolled       (node)\n  let element // element to scroll to                   (node)\n\n  let start // where scroll starts                    (px)\n  let stop // where scroll stops                     (px)\n\n  let offset // adjustment from the stop position      (px)\n  let easing // easing function                        (function)\n  let a11y // accessibility support flag             (boolean)\n\n  let distance // distance of scroll                     (px)\n  let duration // scroll duration                        (ms)\n\n  let timeStart // time scroll started                    (ms)\n  let timeElapsed // time spent scrolling thus far          (ms)\n\n  let next // next scroll position                   (px)\n\n  let callback // to call when done scrolling            (function)\n\n  // scroll position helper\n\n  function location() {\n    let top = container.scrollTop || container.scrollY || container.pageYOffset\n    top = typeof top === 'undefined' ? 0 : top\n    return top\n  }\n\n  // element offset helper\n\n  function top(element) {\n    const elementTop = element.getBoundingClientRect().top\n    const containerTop = container.getBoundingClientRect\n      ? container.getBoundingClientRect().top\n      : 0\n\n    return elementTop - containerTop + start\n  }\n\n  // scrollTo helper\n\n  function scrollTo(top) {\n    container.scrollTo\n      ? container.scrollTo(0, top) // window\n      : (container.scrollTop = top) // custom container\n  }\n\n  // rAF loop helper\n\n  function loop(timeCurrent) {\n    // store time scroll started, if not started already\n    if (!timeStart) {\n      timeStart = timeCurrent\n    }\n\n    // determine time spent scrolling so far\n    timeElapsed = timeCurrent - timeStart\n\n    // calculate next scroll position\n    next = easing(timeElapsed, start, distance, duration)\n\n    // scroll to it\n    scrollTo(next)\n\n    // check progress\n    timeElapsed < duration\n      ? requestAnimationFrame(loop) // continue scroll loop\n      : done() // scrolling is done\n  }\n\n  // scroll finished helper\n\n  function done() {\n    // account for rAF time rounding inaccuracies\n    scrollTo(start + distance)\n\n    // if scrolling to an element, and accessibility is enabled\n    if (element && a11y) {\n      // add tabindex indicating programmatic focus\n      element.setAttribute('tabindex', '-1')\n\n      // focus the element\n      element.focus()\n    }\n\n    // if it exists, fire the callback\n    if (typeof callback === 'function') {\n      callback()\n    }\n\n    // reset time for next jump\n    timeStart = false\n  }\n\n  // API\n\n  function jump(target, options = {}) {\n    // resolve options, or use defaults\n    duration = options.duration || 1000\n    offset = options.offset || 0\n    callback = options.callback // \"undefined\" is a suitable default, and won't be called\n    easing = options.easing || easeInOutQuad\n    a11y = options.a11y || false\n\n    // resolve container\n    switch (typeof options.container) {\n      case 'object':\n        // we assume container is an HTML element (Node)\n        container = options.container\n        break\n\n      case 'string':\n        container = document.querySelector(options.container)\n        break\n\n      default:\n        container = window\n    }\n\n    // cache starting position\n    start = location()\n\n    // resolve target\n    switch (typeof target) {\n      // scroll from current position\n      case 'number':\n        element = undefined // no element to scroll to\n        a11y = false // make sure accessibility is off\n        stop = start + target\n        break\n\n      // scroll to element (node)\n      // bounding rect is relative to the viewport\n      case 'object':\n        element = target\n        stop = top(element)\n        break\n\n      // scroll to element (selector)\n      // bounding rect is relative to the viewport\n      case 'string':\n        element = document.querySelector(target)\n        stop = top(element)\n        break\n    }\n\n    // resolve scroll distance, accounting for offset\n    distance = stop - start + offset\n\n    // resolve duration\n    switch (typeof options.duration) {\n      // number in ms\n      case 'number':\n        duration = options.duration\n        break\n\n      // function passed the distance of the scroll\n      case 'function':\n        duration = options.duration(distance)\n        break\n    }\n\n    // start the loop\n    requestAnimationFrame(loop)\n  }\n\n  // expose only the jump method\n  return jump\n}\n\n// export singleton\n\nconst singleton = jumper()\n\nexport default singleton\n"
  },
  {
    "path": "modules/web/src/router/bookRouter.ts",
    "content": "import { createWebHashHistory, createRouter } from 'vue-router'\n\nexport const bookRoutes = [\n  {\n    path: '/',\n    name: 'shelf',\n    component: () => import('../views/BookShelf.vue'),\n  },\n  {\n    path: '/chapter',\n    name: 'chapter',\n    component: () => import('../views/BookChapter.vue'),\n  },\n]\n\nconst router = createRouter({\n  // mode: \"history\",\n  history: createWebHashHistory(),\n  routes: bookRoutes,\n})\n\nexport default router\n"
  },
  {
    "path": "modules/web/src/router/index.ts",
    "content": "import { createWebHashHistory, createRouter } from 'vue-router'\nimport { bookRoutes } from './bookRouter'\nimport { sourceRoutes } from './sourceRouter'\n\nconst router = createRouter({\n  //   history: createWebHistory(process.env.BASE_URL),\n  history: createWebHashHistory(),\n  routes: [bookRoutes, sourceRoutes].flat(),\n})\n\nrouter.afterEach(to => {\n  if (to.name == 'shelf') document.title = '书架'\n})\n\nexport default router\n"
  },
  {
    "path": "modules/web/src/router/sourceRouter.ts",
    "content": "import sourceEditor from '../views/SourceEditor.vue'\nimport { createWebHashHistory, createRouter } from 'vue-router'\n\nexport const sourceRoutes = [\n  {\n    path: '/bookSource',\n    name: 'book-home',\n    component: sourceEditor,\n  },\n  {\n    path: '/rssSource',\n    name: 'rss-home',\n    component: sourceEditor,\n  },\n]\n\nconst router = createRouter({\n  //   history: createWebHistory(process.env.BASE_URL),\n  history: createWebHashHistory(),\n  routes: sourceRoutes,\n})\n\nexport default router\n"
  },
  {
    "path": "modules/web/src/source.d.ts",
    "content": "/** https://github.com/gedoor/legado/tree/master/app/src/main/java/io/legado/app/data/entities */\ntype BaseSource = {\n  /**\n   * 并发率\n   */\n  concurrentRate?: string\n  /**\n   * 登录地址\n   */\n  loginUrl?: string\n\n  /**\n   * 登录UI\n   */\n  loginUi?: string\n\n  /**\n   * 请求头\n   */\n  header?: string\n\n  /**\n   * 启用cookieJar\n   */\n  enabledCookieJar?: boolean\n\n  /**\n   * js库\n   */\n  jsLib?: string\n}\ntype BookSoure = BaseSource & {\n  // 地址，包括 http/https\n  bookSourceUrl: string\n  // 名称\n  bookSourceName: string\n  // 分组\n  bookSourceGroup?: string\n  // 类型，0 文本，1 音频, 2 图片, 3 文件（指的是类似知轩藏书只提供下载的网站）\n  bookSourceType: number\n  // 详情页url正则\n  bookUrlPattern?: string\n  // 手动排序编号\n  customOrder: number\n  // 是否启用\n  enabled: boolean\n  // 启用发现\n  enabledExplore: boolean\n  // 登录检测js\n  loginCheckJs?: string\n  // 封面解密js\n  coverDecodeJs?: string\n  // 注释\n  bookSourceComment?: string\n  // 自定义变量说明\n  variableComment?: string\n  // 最后更新时间，用于排序\n  lastUpdateTime: number\n  // 响应时间，用于排序\n  respondTime: number\n  // 智能排序的权重\n  weight: number\n  // 发现url\n  exploreUrl?: string\n  // 发现筛选规则\n  exploreScreen?: string\n  // 发现规则\n  ruleExplore?: ExploreRule\n  // 搜索url\n  searchUrl?: string\n  // 搜索规则\n  ruleSearch?: SearchRule\n  // 书籍信息页规则\n  ruleBookInfo?: BookInfoRule\n  // 目录页规则\n  ruleToc?: TocRule\n  // 正文页规则\n  ruleContent?: ContentRule\n  // 段评规则\n  ruleReview?: ReviewRule\n}\ntype RuleSearch = {\n  checkKeyWord?: string\n  [prop: string]: string\n}\n/* type ExploreRule = {\n    [prop:string]: string\n}\ntype BookInfoRule = {\n    [prop:string]: string\n}\ntype TocRule = {\n    [prop:string]: string\n}\ntype ContentRule = {\n    [prop:string]: string\n}\ntype ReviewRule = {\n    [prop:string]: string\n} */\ntype RssSource = BaseSource & {\n  sourceUrl: string\n  // 名称\n  sourceName: string\n  // 图标\n  sourceIcon: string\n  // 分组\n  sourceGroup?: string\n  // 注释\n  sourceComment?: string\n  // 是否启用\n  enabled: boolean\n  // 自定义变量说明\n  variableComment?: string\n  /**登录检测js**/\n  loginCheckJs?: string\n  /**封面解密js**/\n  coverDecodeJs?: string\n  /**分类Url**/\n  sortUrl?: string\n  /**是否单url源**/\n  singleUrl: boolean\n  /*列表规则*/\n  /**列表样式,0,1,2**/\n  articleStyle: number\n  /**列表规则**/\n  ruleArticles?: string\n  /**下一页规则**/\n  ruleNextPage?: string\n  /**标题规则**/\n  ruleTitle?: string\n  /**发布日期规则**/\n  rulePubDate?: string\n  /*webView规则*/\n  /**描述规则**/\n  ruleDescription?: string\n  /**图片规则**/\n  ruleImage?: string\n  /**链接规则**/\n  ruleLink?: string\n  /**正文规则**/\n  ruleContent?: string\n  /**正文url白名单**/\n  contentWhitelist?: string\n  /**正文url黑名单**/\n  contentBlacklist?: string\n  /**\n   * 跳转url拦截,\n   * js, 返回true拦截,js变量url,可以通过js打开url,比如调用阅读搜索,添加书架等,简化规则写法,不用webView js注入\n   * **/\n  shouldOverrideUrlLoading?: string\n  /**webView样式**/\n  style?: string\n  enableJs: boolean\n  loadWithBaseUrl: boolean\n  /**注入js**/\n  injectJs?: string\n  /*其它规则*/\n  /**最后更新时间，用于排序**/\n  lastUpdateTime: number\n  customOrder: number\n}\ntype Source = BookSoure | RssSource\n\nexport { Source, BookSoure, RssSource }\n"
  },
  {
    "path": "modules/web/src/store/bookStore.ts",
    "content": "import { defineStore } from 'pinia'\nimport API from '@api'\nimport type {\n  BaseBook,\n  Book,\n  BookChapter,\n  BookProgress,\n  SeachBook,\n} from '@/book'\nimport type { webReadConfig } from '@/web'\nimport { ElMessage } from 'element-plus/es'\n\nconst default_config: webReadConfig = {\n  theme: 0,\n  font: 0,\n  fontSize: 18,\n  readWidth: 800,\n  infiniteLoading: false,\n  customFontName: '',\n  jumpDuration: 1000,\n  spacing: {\n    paragraph: 1,\n    line: 0.8,\n    letter: 0,\n  },\n}\nlet webReadConfigLoadedDate: Date | undefined\n\nexport const useBookStore = defineStore('book', {\n  state: () => {\n    return {\n      searchBooks: [] as SeachBook[],\n      shelf: [] as Book[],\n      catalog: [] as BookChapter[],\n      readingBook: { chapterPos: 0, chapterIndex: 0 } as BaseBook & {\n        chapterPos: number\n        chapterIndex: number\n        isSeachBook?: boolean\n      },\n      popCataVisible: false,\n      contentLoading: true,\n      showContent: false,\n      config: default_config,\n      miniInterface: false,\n      readSettingsVisible: false,\n    }\n  },\n  getters: {\n    bookProgress: (state): BookProgress | undefined => {\n      if (state.catalog.length == 0) return\n      const { chapterIndex, chapterPos, name, author } = state.readingBook\n      const title = state.catalog[chapterIndex]?.title\n      if (!title) return\n      return {\n        name,\n        author,\n        durChapterIndex: chapterIndex,\n        durChapterPos: chapterPos,\n        durChapterTime: new Date().getTime(),\n        durChapterTitle: title,\n      }\n    },\n    theme: state => {\n      return state.config.theme\n    },\n    isNight: state => state.config.theme == 6,\n  },\n  actions: {\n    /** 从后端加载书架书籍，优先返回内存缓存 */\n    async loadBookShelf(): Promise<Book[]> {\n      const fetchBookshellf_promise = API.getBookShelf().then(resp => {\n        console.log('API.getBookShelf数据返回')\n        const { isSuccess, data, errorMsg } = resp.data\n        if (isSuccess === true) {\n          if (\n            this.shelf.length !== data.length &&\n            this.shelf.length > 0 &&\n            data.length > 0\n          ) {\n            ElMessage.info(`书架数据已更新`)\n          }\n          this.shelf = data.sort((a, b) => {\n            const x = a['durChapterTime'] || 0\n            const y = b['durChapterTime'] || 0\n            return y - x\n          })\n        } else {\n          if (errorMsg.includes('还没有添加小说') && this.shelf.length > 0) {\n            ElMessage.info('当前书架上的书籍已经被删除')\n            return (this.shelf = [])\n          }\n          ElMessage.error(errorMsg ?? '后端返回格式错误！')\n        }\n        console.log('书架数据已更新')\n        return this.shelf\n      })\n\n      if (this.shelf.length > 0) {\n        // bookshelf data fetched before:do not await\n        console.log('返回缓存书架数据')\n        return this.shelf\n      } else {\n        console.log('从阅读后端获取书架数据...')\n        return await fetchBookshellf_promise\n      }\n    },\n    /** 从后端加载书籍目录，优先返回内存缓存 */\n    async loadWebCatalog(\n      book: typeof this.readingBook,\n    ): Promise<BookChapter[]> {\n      const { bookUrl, name, chapterIndex } = book\n      const fetchChapterList_promise = API.getChapterList(\n        bookUrl as string,\n      ).then(res => {\n        const { isSuccess, data, errorMsg } = res.data\n        if (isSuccess === false) {\n          ElMessage.error(errorMsg)\n          throw new Error()\n        }\n        if (\n          bookUrl === this.readingBook.bookUrl &&\n          data.length !== this.catalog.length &&\n          data.length > 0 &&\n          this.catalog.length > 0\n        ) {\n          ElMessage.info(`书籍${name}: 章节目录已更新`)\n        }\n        this.catalog = data\n        console.log(`书籍${name}: 章节目录已更新`)\n        return this.catalog\n      })\n      if (\n        bookUrl === this.readingBook.bookUrl &&\n        this.catalog.length > 0 &&\n        this.catalog.length - 1 >= chapterIndex\n      ) {\n        console.log(`返回书籍《${name}》 缓存的章节目录`)\n        return this.catalog\n      } else {\n        console.log(`从阅读后端获取书籍《${name}》 章节目录数据...`)\n        return await fetchChapterList_promise\n      }\n    },\n    setPopCataVisible(visible: boolean) {\n      this.popCataVisible = visible\n    },\n    setContentLoading(loading: boolean) {\n      this.contentLoading = loading\n    },\n    setReadingBook(readingBook: typeof this.readingBook) {\n      this.readingBook = readingBook\n    },\n    /** 只从从后端加载一次web阅读配置 */\n    async loadWebConfig() {\n      if (webReadConfigLoadedDate === undefined) {\n        const _config = await API.getReadConfig()\n        webReadConfigLoadedDate = new Date()\n        console.log(\n          `${this.$id}.loadWebConfig: ${webReadConfigLoadedDate.toLocaleString()}成功加载阅读配置`,\n        )\n        return this.setConfig(_config)\n      }\n      console.log(\n        `${this.$id}.loadWebConfig: 已于${webReadConfigLoadedDate.toLocaleString()}成功加载`,\n      )\n    },\n    setConfig(config?: webReadConfig) {\n      this.config = Object.assign({}, this.config, config)\n    },\n    setReadSettingsVisible(visible: boolean) {\n      this.readSettingsVisible = visible\n    },\n    setShowContent(visible: boolean) {\n      this.showContent = visible\n    },\n    setMiniInterface(mini: boolean) {\n      this.miniInterface = mini\n    },\n    async setSearchBooks(books: SeachBook[]) {\n      books.forEach(book => {\n        const isSeachBook = this.shelf.every(\n          item => item.bookUrl !== book.bookUrl,\n        )\n        if (isSeachBook === true) {\n          this.searchBooks.push(book)\n        }\n      })\n    },\n    clearSearchBooks() {\n      this.searchBooks = []\n    },\n    /** 1.保存进度到app 2.修改内存中的数据*/\n    async saveBookProgress() {\n      if (!this.bookProgress) return Promise.resolve()\n      const { bookUrl } = this.readingBook\n      const shelfRaw = toRaw(this.shelf)\n      const findIndex = shelfRaw.findIndex(book => book.bookUrl === bookUrl)\n      if (findIndex > -1) {\n        this.shelf[findIndex] = Object.assign(\n          {},\n          shelfRaw[findIndex],\n          this.bookProgress,\n        )\n      }\n      // 直接关闭浏览器时 http请求可能被取消\n      // return API.saveBookProgress(this.bookProgress)\n      return API.saveBookProgressWithBeacon(this.bookProgress)\n    },\n  },\n})\n"
  },
  {
    "path": "modules/web/src/store/connectionStore.ts",
    "content": "import { defineStore } from 'pinia'\n\nexport const useConnectionStore = defineStore('connection', {\n  state: () => {\n    return {\n      connectStatus: '正在连接后端服务器……',\n      connectType: 'primary' as 'primary' | 'success' | 'danger',\n      newConnect: false,\n    }\n  },\n  actions: {\n    setConnectStatus(connectStatus: string) {\n      if (this.newConnect === true) return\n      this.connectStatus = connectStatus\n    },\n    setConnectType(connectType: 'primary' | 'success' | 'danger') {\n      if (this.newConnect === true) return\n      this.connectType = connectType\n    },\n    setNewConnect(newConnect: boolean) {\n      this.newConnect = newConnect\n    },\n  },\n})\n"
  },
  {
    "path": "modules/web/src/store/index.ts",
    "content": "import { createPinia } from 'pinia'\n\nexport * from './bookStore'\nexport * from './sourceStore'\nexport * from './connectionStore'\nexport default createPinia()\n"
  },
  {
    "path": "modules/web/src/store/sourceStore.ts",
    "content": "import { defineStore } from 'pinia'\nimport {\n  emptyBookSource,\n  emptyRssSource,\n  getSourceUniqueKey,\n  convertSourcesToMap,\n} from '@utils/souce'\nimport type { BookSoure, RssSource, Source } from '@/source'\n\nconst isBookSource = /bookSource/i.test(location.href)\nconst emptySource = isBookSource ? emptyBookSource : emptyRssSource\n\nexport const useSourceStore = defineStore('source', {\n  state: () => {\n    return {\n      bookSources: shallowRef([] as BookSoure[]), // 临时存放所有书源,\n      rssSources: shallowRef([] as RssSource[]), // 临时存放所有订阅源\n      savedSources: [] as Source[], // 批量保存到阅读app成功的源\n      currentSource: JSON.parse(JSON.stringify(emptySource)) as Source, // 当前编辑的源\n      currentTab: localStorage.getItem('tabName') || 'editTab',\n      editTabSource: {} as Source, // 生成序列化的json数据\n      isDebuging: false,\n    }\n  },\n  getters: {\n    sources: (state): Source[] =>\n      isBookSource ? state.bookSources : state.rssSources,\n    sourcesMap: function (): Map<string, Source> {\n      return convertSourcesToMap(this.sources)\n    },\n    savedSourcesMap: (state): Map<string, Source> =>\n      convertSourcesToMap(state.savedSources),\n    currentSourceUrl: state =>\n      isBookSource\n        ? (state.currentSource as BookSoure).bookSourceUrl\n        : (state.currentSource as RssSource).sourceUrl,\n    searchKey: (state): string =>\n      isBookSource\n        ? (state.currentSource as BookSoure)?.ruleSearch?.checkKeyWord || '我的'\n        : '',\n  },\n  actions: {\n    startDebug() {\n      this.currentTab = 'editDebug'\n      this.isDebuging = true\n    },\n    debugFinish() {\n      this.isDebuging = false\n    },\n\n    //拉取源后保存\n    saveSources(data: Source[]) {\n      if (isBookSource) {\n        this.bookSources = markRaw(data) as BookSoure[]\n      } else {\n        this.rssSources = markRaw(data) as RssSource[]\n      }\n    },\n    //批量推送\n    setPushReturnSources(returnSoures: Source[]) {\n      this.savedSources = returnSoures\n    },\n    //删除源\n    deleteSources(data: Source[]) {\n      const sources: Source[] = isBookSource\n        ? this.bookSources\n        : this.rssSources\n      data.forEach(source => {\n        const index = sources.indexOf(source)\n        if (index > -1) sources.splice(index, 1)\n      })\n    },\n    //保存当前编辑源\n    saveCurrentSource() {\n      const source = this.currentSource,\n        map = this.sourcesMap\n      map.set(getSourceUniqueKey(source), JSON.parse(JSON.stringify(source)))\n      this.saveSources(Array.from(map.values()))\n    },\n    // 更改当前编辑的源qq\n    changeCurrentSource(source: Source) {\n      this.currentSource = JSON.parse(JSON.stringify(source))\n    },\n    // update editTab tabName and editTab info\n    changeTabName(tabName: string) {\n      this.currentTab = tabName\n      localStorage.setItem('tabName', tabName)\n    },\n    changeEditTabSource(source: Source) {\n      this.editTabSource = JSON.parse(JSON.stringify(source))\n    },\n    editHistory(history: Source) {\n      let historyObj\n      if (localStorage.getItem('history')) {\n        historyObj = JSON.parse(localStorage.getItem('history')!)\n        historyObj.new.push(history)\n        if (historyObj.new.length > 50) {\n          historyObj.new.shift()\n        }\n        if (historyObj.old.length > 50) {\n          historyObj.old.shift()\n        }\n        localStorage.setItem('history', JSON.stringify(historyObj))\n      } else {\n        const arr = { new: [history], old: [] }\n        localStorage.setItem('history', JSON.stringify(arr))\n      }\n    },\n    editHistoryUndo() {\n      if (localStorage.getItem('history')) {\n        const historyObj = JSON.parse(localStorage.getItem('history')!)\n        historyObj.old.push(this.currentSource)\n        if (historyObj.new.length) {\n          this.currentSource = historyObj.new.pop()\n        }\n        localStorage.setItem('history', JSON.stringify(historyObj))\n      }\n    },\n    clearAllHistory() {\n      localStorage.setItem('history', JSON.stringify({ new: [], old: [] }))\n    },\n    clearEdit() {\n      this.editTabSource = {} as Source\n      this.currentSource = JSON.parse(JSON.stringify(emptySource)) //复制一份新对象\n    },\n\n    // clear all source\n    clearAllSource() {\n      this.bookSources = []\n      this.rssSources = []\n      this.savedSources = []\n    },\n  },\n})\n"
  },
  {
    "path": "modules/web/src/utils/souce.ts",
    "content": "import type { BookSoure, RssSource, Source } from '../source'\nimport { isNullOrBlank } from './utils'\n\nconst isBookSource = (source: Source): source is BookSoure =>\n  'bookSourceName' in source\n\nexport const isInvaildSource: (source: Source) => boolean = source => {\n  if (isBookSource(source)) {\n    return (\n      !isNullOrBlank(source.bookSourceName) &&\n      !isNullOrBlank(source.bookSourceUrl) &&\n      !isNullOrBlank(source.bookSourceType)\n    )\n  }\n  return !isNullOrBlank(source.sourceName) && !isNullOrBlank(source.sourceUrl)\n}\n\nexport const getSourceUniqueKey = (source: Source) =>\n  isBookSource(source) ? source.bookSourceUrl : source.sourceUrl\nexport const getSourceName = (source: Source) =>\n  isBookSource(source) ? source.bookSourceName : source.sourceName\n\nexport const isSourceMatches: (source: Source, searchKey: string) => boolean = (\n  source,\n  searchKey,\n) => {\n  // TODO: 正则和普通字符串识别 识别 * . \\ [ ] <= <! != = ?: () \\d\\w\\s\\...\n  if (isBookSource(source)) {\n    return (\n      (source.bookSourceName.includes(searchKey) ||\n        source.bookSourceUrl.includes(searchKey) ||\n        source.bookSourceGroup?.includes(searchKey) ||\n        source.bookSourceComment?.includes(searchKey)) ??\n      false\n    )\n  }\n  return (\n    (source.sourceName.includes(searchKey) ||\n      source.sourceUrl.includes(searchKey) ||\n      source.sourceGroup?.includes(searchKey) ||\n      source.sourceComment?.includes(searchKey)) ??\n    false\n  )\n}\n\nexport const convertSourcesToMap = (sources: Source[]): Map<string, Source> => {\n  const map = new Map()\n  sources.forEach(source => map.set(getSourceUniqueKey(source), source))\n  return map\n}\n\nexport const normalizeSource = (source: any) => {\n  for (const key in source) {\n    const value = source[key]\n    if (\n      value === '' ||\n      value === null ||\n      (typeof value === 'string' && !value.trim())\n    ) {\n      delete source[key]\n    } else if (value instanceof Object) {\n      normalizeSource(value)\n    }\n  }\n}\n\nexport const emptyBookSource = {\n  ruleSearch: {},\n  ruleBookInfo: {},\n  ruleToc: {},\n  ruleContent: {},\n  // ruleReview: {},\n  ruleExplore: {},\n} as BookSoure\nexport const emptyRssSource = {} as RssSource\n"
  },
  {
    "path": "modules/web/src/utils/utils.ts",
    "content": "import { formatDate } from '@vueuse/shared'\nexport const isNullOrBlank = (string: string | null | undefined | number) =>\n  string == null ||\n  (string as string).length === 0 ||\n  /^\\s+$/.test(string as string)\n\nexport const isLegadoUrl = (/** @type {string} */ url: string) =>\n  /,\\s*\\{/.test(url) ||\n  !(\n    url.startsWith('http') ||\n    url.startsWith('data:') ||\n    url.startsWith('blob:')\n  )\n\n/**\n * 验证输入的URL是否符合阅读后端地址规则\n * @param allowedProtocols 允许的协议，默认`[\"https:\", \"http:\"]`\n */\nexport const validatorHttpUrl = (\n  http_url: string | URL,\n  allowedProtocols: string[] = ['https:', 'http:'],\n) => {\n  try {\n    const url = new URL(http_url)\n    const { protocol } = url\n    if (!allowedProtocols.includes(protocol))\n      throw new Error(\n        `Expected protocol ${allowedProtocols.join('/')}, but ${protocol}`,\n      )\n    return true\n  } catch {\n    return false\n  }\n}\n\nexport const dateFormat = (/** @type {number} */ t: number) => {\n  const time = new Date().getTime()\n  const offset = Math.floor((time - t) / 1000)\n  let str = ''\n\n  if (offset <= 30) {\n    str = '刚刚'\n  } else if (offset < 60) {\n    str = offset + '秒前'\n  } else if (offset < 3600) {\n    str = Math.floor(offset / 60) + '分钟前'\n  } else if (offset < 86400) {\n    str = Math.floor(offset / 3600) + '小时前'\n  } else if (offset < 2592000) {\n    str = Math.floor(offset / 86400) + '天前'\n  } else {\n    str = formatDate(new Date(t), 'YYYY-MM-DD')\n  }\n  return str\n}\n"
  },
  {
    "path": "modules/web/src/views/BookChapter.vue",
    "content": "<template>\n  <div\n    class=\"chapter-wrapper\"\n    :style=\"bodyTheme\"\n    :class=\"{ night: isNight, day: !isNight }\"\n    @click=\"showToolBar = !showToolBar\"\n  >\n    <div class=\"tool-bar\" :style=\"leftBarTheme\">\n      <div class=\"tools\">\n        <el-popover\n          placement=\"right\"\n          :width=\"popupWidth\"\n          trigger=\"click\"\n          :show-arrow=\"false\"\n          v-model:visible=\"popCataVisible\"\n          popper-class=\"pop-cata\"\n        >\n          <PopCatalog @getContent=\"getContent\" class=\"popup\" />\n          <template #reference>\n            <div class=\"tool-icon\" :class=\"{ 'no-point': false }\">\n              <div class=\"iconfont\">&#58905;</div>\n              <div class=\"icon-text\">目录</div>\n            </div>\n          </template>\n        </el-popover>\n        <el-popover\n          placement=\"right\"\n          :width=\"popupWidth\"\n          trigger=\"click\"\n          :show-arrow=\"false\"\n          v-model:visible=\"readSettingsVisible\"\n          popper-class=\"pop-setting\"\n        >\n          <read-settings class=\"popup\" />\n          <template #reference>\n            <div class=\"tool-icon\" :class=\"{ 'no-point': noPoint }\">\n              <div class=\"iconfont\">&#58971;</div>\n              <div class=\"icon-text\">设置</div>\n            </div>\n          </template>\n        </el-popover>\n        <div class=\"tool-icon\" @click=\"toShelf\">\n          <div class=\"iconfont\">&#58892;</div>\n          <div class=\"icon-text\">书架</div>\n        </div>\n        <div class=\"tool-icon\" :class=\"{ 'no-point': noPoint }\" @click=\"toTop\">\n          <div class=\"iconfont\">&#58914;</div>\n          <div class=\"icon-text\">顶部</div>\n        </div>\n        <div\n          class=\"tool-icon\"\n          :class=\"{ 'no-point': noPoint }\"\n          @click=\"toBottom\"\n        >\n          <div class=\"iconfont\">&#58915;</div>\n          <div class=\"icon-text\">底部</div>\n        </div>\n      </div>\n    </div>\n    <div class=\"read-bar\" :style=\"rightBarTheme\">\n      <div class=\"tools\">\n        <div\n          class=\"tool-icon\"\n          :class=\"{ 'no-point': noPoint }\"\n          @click=\"toPreChapter\"\n        >\n          <div class=\"iconfont\">&#58920;</div>\n          <span v-if=\"miniInterface\">上一章</span>\n        </div>\n        <div\n          class=\"tool-icon\"\n          :class=\"{ 'no-point': noPoint }\"\n          @click=\"toNextChapter\"\n        >\n          <span v-if=\"miniInterface\">下一章</span>\n          <div class=\"iconfont\">&#58913;</div>\n        </div>\n      </div>\n    </div>\n    <div class=\"chapter-bar\"></div>\n    <div class=\"chapter\" ref=\"content\" :style=\"chapterTheme\">\n      <div class=\"content\">\n        <div class=\"top-bar\" ref=\"top\"></div>\n        <div\n          v-for=\"data in chapterData\"\n          :key=\"data.index\"\n          :chapterIndex=\"data.index\"\n          ref=\"chapter\"\n        >\n          <chapter-content\n            ref=\"chapterRef\"\n            :chapterIndex=\"data.index\"\n            :contents=\"data.content\"\n            :title=\"data.title\"\n            :spacing=\"store.config.spacing\"\n            :fontSize=\"fontSize\"\n            :fontFamily=\"fontFamily\"\n            @readedLengthChange=\"onReadedLengthChange\"\n            v-if=\"showContent\"\n          />\n        </div>\n        <div class=\"loading\" ref=\"loading\"></div>\n        <div class=\"bottom-bar\" ref=\"bottom\"></div>\n      </div>\n    </div>\n  </div>\n</template>\n\n<script setup lang=\"ts\">\nimport jump from '@/plugins/jump'\nimport settings from '@/config/themeConfig'\nimport API from '@api'\nimport { useLoading } from '@/hooks/loading'\nimport { useThrottleFn } from '@vueuse/shared'\nimport { isNullOrBlank } from '@/utils/utils'\n\nconst content = ref()\n// loading spinner\nconst { isLoading, loadingWrapper } = useLoading(content, '正在获取信息')\nconst store = useBookStore()\n\nconst {\n  catalog,\n  popCataVisible,\n  readSettingsVisible,\n  miniInterface,\n  showContent,\n  bookProgress,\n  theme,\n  isNight,\n} = storeToRefs(store)\n\nconst chapterPos = computed({\n  get: () => store.readingBook.chapterPos,\n  set: value => (store.readingBook.chapterPos = value),\n})\nconst chapterIndex = computed({\n  get: () => store.readingBook.chapterIndex,\n  set: value => (store.readingBook.chapterIndex = value),\n})\nconst isSeachBook = computed({\n  get: () => store.readingBook.isSeachBook,\n  set: value => (store.readingBook.isSeachBook = value),\n})\n\n// 当前阅读书籍readingBook持久化\nwatch(\n  () => store.readingBook,\n  book => {\n    // 保存localStorage\n    // localStorage.setItem(book.bookUrl, JSON.stringify(book));\n    // 最近阅读\n    localStorage.setItem('readingRecent', JSON.stringify(book))\n    //保存 sessionStorage\n    sessionStorage.setItem('chapterIndex', book.chapterIndex.toString())\n    sessionStorage.setItem('chapterPos', book.chapterPos.toString())\n  },\n  { deep: 1 },\n)\n\n// 无限滚动\nconst infiniteLoading = computed(() => store.config.infiniteLoading)\nlet scrollObserver: IntersectionObserver | null\nconst loading = ref()\nwatchEffect(() => {\n  if (!infiniteLoading.value) {\n    scrollObserver?.disconnect()\n  } else {\n    scrollObserver?.observe(loading.value)\n  }\n})\nconst loadMore = () => {\n  const index = chapterData.value.slice(-1)[0].index\n  if (catalog.value.length - 1 > index) {\n    getContent(index + 1, false)\n    store.saveBookProgress() // 保存的是上一章的进度，不是预载的本章进度\n  }\n}\n// IntersectionObserver回调 底部加载\nconst onReachBottom = (entries: IntersectionObserverEntry[]) => {\n  if (isLoading.value) return\n  for (const { isIntersecting } of entries) {\n    if (!isIntersecting) return\n    loadMore()\n  }\n}\n\n// 字体\nconst fontFamily = computed(() => {\n  if (store.config.font >= 0) {\n    return settings.fonts[store.config.font]\n  }\n  return store.config.customFontName\n})\nconst fontSize = computed(() => {\n  return store.config.fontSize + 'px'\n})\n\n// 主题部分\nconst bodyColor = computed(() => settings.themes[theme.value].body)\nconst chapterColor = computed(() => settings.themes[theme.value].content)\nconst popupColor = computed(() => settings.themes[theme.value].popup)\n\nconst readWidth = computed(() => {\n  if (!miniInterface.value) {\n    return store.config.readWidth - 130 + 'px'\n  } else {\n    return window.innerWidth + 'px'\n  }\n})\nconst popupWidth = computed(() => {\n  if (!miniInterface.value) {\n    return store.config.readWidth - 33\n  } else {\n    return window.innerWidth - 33\n  }\n})\nconst bodyTheme = computed(() => {\n  return {\n    background: bodyColor.value,\n  }\n})\nconst chapterTheme = computed(() => {\n  return {\n    background: chapterColor.value,\n    width: readWidth.value,\n  }\n})\nconst showToolBar = ref(false)\nconst leftBarTheme = computed(() => {\n  return {\n    background: popupColor.value,\n    marginLeft: miniInterface.value\n      ? 0\n      : -(store.config.readWidth / 2 + 68) + 'px',\n    display: miniInterface.value && !showToolBar.value ? 'none' : 'block',\n  }\n})\nconst rightBarTheme = computed(() => {\n  return {\n    background: popupColor.value,\n    marginRight: miniInterface.value\n      ? 0\n      : -(store.config.readWidth / 2 + 52) + 'px',\n    display: miniInterface.value && !showToolBar.value ? 'none' : 'block',\n  }\n})\n\n/**\n * pc移动端判断 最大阅读宽度修正\n * 阅读宽度最小为640px 加上工具栏 68px 52px 取较大值 为 776px\n */\nconst onResize = () => {\n  store.setMiniInterface(window.innerWidth < 776)\n  const width = store.config.readWidth /**包含padding */\n  checkPageWidth(width)\n}\n/** 判断阅读宽度是否超出页面或者低于默认值640 */\nconst checkPageWidth = (readWidth: number) => {\n  if (store.miniInterface) return\n  if (readWidth < 640) store.config.readWidth = 640\n  if (readWidth + 2 * 68 > window.innerWidth) store.config.readWidth -= 160\n}\nwatch(\n  () => store.config.readWidth,\n  width => checkPageWidth(width),\n)\n// 顶部底部跳转\nconst top = ref()\nconst bottom = ref()\nconst toTop = () => {\n  jump(top.value)\n}\nconst toBottom = () => {\n  jump(bottom.value)\n}\n\n// 书架路由切换\nconst router = useRouter()\nconst toShelf = () => {\n  router.push('/')\n}\n\n// 获取章节内容\nconst chapterData = ref<{ index: number; content: string[]; title: string }[]>(\n  [],\n)\nconst noPoint = ref(true)\nconst getContent = (index: number, reloadChapter = true, chapterPos = 0) => {\n  if (reloadChapter) {\n    //展示进度条\n    store.setShowContent(false)\n    //强制滚回顶层\n    jump(top.value, { duration: 0 })\n    //从目录，按钮切换章节时保存进度 预加载时不保存\n    saveReadingBookProgressToBrowser(index, chapterPos)\n    chapterData.value = []\n  }\n  const bookUrl = store.readingBook.bookUrl\n  const { title, index: chapterIndex } = catalog.value[index]\n\n  loadingWrapper(\n    API.getBookContent(bookUrl, chapterIndex).then(\n      res => {\n        if (res.data.isSuccess) {\n          const data = res.data.data\n          const content = data.split(/\\n+/)\n          chapterData.value.push({ index, content, title })\n          if (reloadChapter) toChapterPos(chapterPos)\n        } else {\n          ElMessage({ message: res.data.errorMsg, type: 'error' })\n          const content = [res.data.errorMsg]\n          chapterData.value.push({ index, content, title })\n        }\n        store.setContentLoading(true)\n        noPoint.value = false\n        store.setShowContent(true)\n        if (!res.data.isSuccess) {\n          throw res.data\n        }\n      },\n      err => {\n        const content = ['获取章节内容失败！']\n        chapterData.value.push({ index, content, title })\n        store.setShowContent(true)\n        throw err\n      },\n    ),\n  )\n}\n\n// 章节进度跳转和计算\nconst chapter = ref()\nconst chapterRef = ref()\nconst toChapterPos = (pos: number) => {\n  nextTick(() => {\n    if (chapterRef.value.length === 1)\n      chapterRef.value[0].scrollToReadedLength(pos)\n  })\n}\n\n// 60秒保存一次进度\nconst saveBookProgressThrottle = useThrottleFn(\n  () => store.saveBookProgress(),\n  60000,\n)\n\nconst onReadedLengthChange = (index: number, pos: number) => {\n  saveReadingBookProgressToBrowser(index, pos)\n  saveBookProgressThrottle()\n}\n\n// 文档标题\nwatchEffect(() => {\n  document.title = catalog.value[chapterIndex.value]?.title || document.title\n})\n\n// 阅读记录保存浏览器\nconst saveReadingBookProgressToBrowser = (index: number, pos: number) => {\n  // 保存pinia\n  chapterIndex.value = index\n  chapterPos.value = pos\n}\n\n// 进度同步\n// 返回导航变化 同步请求会在获取书架前完成\n\n/**\n * VisibilityChange https://developer.mozilla.org/zh-CN/docs/Web/API/Document/visibilitychange_event\n * 监听关闭页面 切换tab 返回桌面 等操作\n * 注意不用监听点击链接导航变化 不对Safari<14.5兼容处理\n **/\nconst onVisibilityChange = () => {\n  const _bookProgress = bookProgress.value\n  if (document.visibilityState == 'hidden' && _bookProgress) {\n    store.saveBookProgress()\n  }\n}\n// 定时同步\n\n// 章节切换\nconst toNextChapter = () => {\n  store.setContentLoading(true)\n  const index = chapterIndex.value + 1\n  if (typeof catalog.value[index] !== 'undefined') {\n    ElMessage({\n      message: '下一章',\n      type: 'info',\n    })\n    getContent(index)\n    store.saveBookProgress()\n  } else {\n    ElMessage({\n      message: '本章是最后一章',\n      type: 'error',\n    })\n  }\n}\nconst toPreChapter = () => {\n  store.setContentLoading(true)\n  const index = chapterIndex.value - 1\n  if (typeof catalog.value[index] !== 'undefined') {\n    ElMessage({\n      message: '上一章',\n      type: 'info',\n    })\n    getContent(index)\n    store.saveBookProgress()\n  } else {\n    ElMessage({\n      message: '本章是第一章',\n      type: 'error',\n    })\n  }\n}\n\nlet canJump = true\n// 监听方向键\nconst handleKeyPress = (event: KeyboardEvent) => {\n  if (!canJump) return\n  switch (event.key) {\n    case 'ArrowLeft':\n      event.stopPropagation()\n      event.preventDefault()\n      toPreChapter()\n      break\n    case 'ArrowRight':\n      event.stopPropagation()\n      event.preventDefault()\n      toNextChapter()\n      break\n    case 'ArrowUp':\n      event.stopPropagation()\n      event.preventDefault()\n      if (document.documentElement.scrollTop === 0) {\n        ElMessage.warning('已到达页面顶部')\n      } else {\n        canJump = false\n        jump(0 - document.documentElement.clientHeight + 100, {\n          duration: store.config.jumpDuration,\n          callback: () => (canJump = true),\n        })\n      }\n      break\n    case 'ArrowDown':\n      event.stopPropagation()\n      event.preventDefault()\n      if (\n        document.documentElement.clientHeight +\n          document.documentElement.scrollTop ===\n        document.documentElement.scrollHeight\n      ) {\n        ElMessage.warning('已到达页面底部')\n      } else {\n        canJump = false\n        jump(document.documentElement.clientHeight - 100, {\n          duration: store.config.jumpDuration,\n          callback: () => (canJump = true),\n        })\n      }\n      break\n  }\n}\n\n// 阻止默认滚动事件\nconst ignoreKeyPress = (event: KeyboardEvent) => {\n  if (event.key === 'ArrowUp' || event.key === 'ArrowDown') {\n    event.preventDefault()\n    event.stopPropagation()\n  }\n}\n\nonMounted(async () => {\n  await store.loadWebConfig()\n  //获取书籍数据\n  const bookUrl = sessionStorage.getItem('bookUrl')\n  const name = sessionStorage.getItem('bookName')\n  const author = sessionStorage.getItem('bookAuthor')\n  const chapterIndex = Number(sessionStorage.getItem('chapterIndex') || 0)\n  const chapterPos = Number(sessionStorage.getItem('chapterPos') || 0)\n  const isSeachBook = sessionStorage.getItem('isSeachBook') === 'true'\n  if (isNullOrBlank(bookUrl) || isNullOrBlank(name) || author === null) {\n    ElMessage.warning('书籍信息为空，即将自动返回书架页面...')\n    return setTimeout(toShelf, 500)\n  }\n  const book: typeof store.readingBook = {\n    // @ts-expect-error: bookUrl name author is NON_Blank string here\n    bookUrl,\n    // @ts-expect-error: bookUrl name author is NON_Blank string here\n    name,\n    author,\n    chapterIndex,\n    chapterPos,\n    isSeachBook,\n  }\n  onResize()\n  window.addEventListener('resize', onResize)\n  loadingWrapper(\n    store.loadWebCatalog(book).then(chapters => {\n      store.setReadingBook(book)\n      getContent(chapterIndex, true, chapterPos)\n      window.addEventListener('keyup', handleKeyPress)\n      window.addEventListener('keydown', ignoreKeyPress)\n      // 兼容Safari < 14\n      document.addEventListener('visibilitychange', onVisibilityChange)\n      //监听底部加载\n      scrollObserver = new IntersectionObserver(onReachBottom, {\n        rootMargin: '-100% 0% 20% 0%',\n      })\n      if (infiniteLoading.value === true) scrollObserver.observe(loading.value)\n      //第二次点击同一本书 页面标题不会变化\n      document.title = '...'\n      document.title = (name as string) + ' | ' + chapters[chapterIndex].title\n    }),\n  )\n})\n\nonUnmounted(() => {\n  window.removeEventListener('keyup', handleKeyPress)\n  window.removeEventListener('keydown', ignoreKeyPress)\n  window.removeEventListener('resize', onResize)\n  // 兼容Safari < 14\n  document.removeEventListener('visibilitychange', onVisibilityChange)\n  readSettingsVisible.value = false\n  popCataVisible.value = false\n  scrollObserver?.disconnect()\n  scrollObserver = null\n})\n\nconst addToBookShelfConfirm = async () => {\n  const book = store.readingBook\n  // 阅读的是搜索的书籍 并未在书架\n  if (book.isSeachBook === true) {\n    await ElMessageBox.confirm(`是否将《${book.name}》放入书架？`, '放入书架', {\n      confirmButtonText: '确认',\n      cancelButtonText: '否',\n      type: 'info',\n      /*\n        ElMessageBox.confirm默认在触发hashChange事件时自动关闭\n        按下物理返回键时触发hashChange事件\n        使用router.push(\"/\")则不会触发hashChange事件\n        */\n      closeOnHashChange: false,\n    })\n      .then(() => {\n        //选择是，无动作\n        isSeachBook.value = false\n      })\n      .catch(async () => {\n        //选择否，删除书籍\n        await API.deleteBook(book)\n      })\n      .finally(() => sessionStorage.removeItem('isSeachBook'))\n  }\n}\nonBeforeRouteLeave(async (to, from, next) => {\n  console.log('onBeforeRouteLeave')\n  // 弹窗时停止响应按键翻页\n  window.removeEventListener('keyup', handleKeyPress)\n  await addToBookShelfConfirm()\n  next()\n})\n</script>\n\n<style lang=\"scss\" scoped>\n:deep(.pop-setting) {\n  margin-left: 68px;\n  top: 0;\n}\n\n:deep(.pop-cata) {\n  margin-left: 10px;\n}\n\n.chapter-wrapper {\n  padding: 0 4%;\n\n  overflow-x: hidden;\n\n  :deep(.no-point) {\n    pointer-events: none;\n  }\n\n  .tool-bar {\n    position: fixed;\n    top: 0;\n    left: 50%;\n    z-index: 100;\n\n    .tools {\n      display: flex;\n      flex-direction: column;\n\n      .tool-icon {\n        font-size: 18px;\n        width: 58px;\n        height: 48px;\n        text-align: center;\n        padding-top: 12px;\n        cursor: pointer;\n        outline: none;\n\n        .iconfont {\n          font-family: iconfont;\n          width: 16px;\n          height: 16px;\n          font-size: 16px;\n          margin: 0 auto 6px;\n        }\n\n        .icon-text {\n          font-size: 12px;\n        }\n      }\n    }\n  }\n\n  .read-bar {\n    position: fixed;\n    bottom: 0;\n    right: 50%;\n    z-index: 100;\n\n    .tools {\n      display: flex;\n      flex-direction: column;\n\n      .tool-icon {\n        font-size: 18px;\n        width: 42px;\n        height: 31px;\n        padding-top: 12px;\n        text-align: center;\n        align-items: center;\n        cursor: pointer;\n        outline: none;\n        margin-top: -1px;\n\n        .iconfont {\n          font-family: iconfont;\n          width: 16px;\n          height: 16px;\n          font-size: 16px;\n          margin: 0 auto 6px;\n        }\n      }\n    }\n  }\n\n  .chapter {\n    font-family: 'Microsoft YaHei', PingFangSC-Regular, HelveticaNeue-Light,\n      'Helvetica Neue Light', sans-serif;\n    text-align: left;\n    padding: 0 65px;\n    min-height: 100vh;\n    width: 670px;\n    margin: 0 auto;\n\n    .content {\n      font-size: 18px;\n      line-height: 1.8;\n      font-family: 'Microsoft YaHei', PingFangSC-Regular, HelveticaNeue-Light,\n        'Helvetica Neue Light', sans-serif;\n\n      .bottom-bar,\n      .top-bar {\n        height: 64px;\n      }\n    }\n  }\n}\n\n.day {\n  :deep(.popup) {\n    box-shadow:\n      0 2px 4px rgba(0, 0, 0, 0.12),\n      0 0 6px rgba(0, 0, 0, 0.04);\n  }\n\n  :deep(.tool-icon) {\n    border: 1px solid rgba(0, 0, 0, 0.1);\n    margin-top: -1px;\n    color: #000;\n\n    .icon-text {\n      color: rgba(0, 0, 0, 0.4);\n    }\n  }\n\n  :deep(.chapter) {\n    border: 1px solid #d8d8d8;\n    color: #262626;\n  }\n}\n\n.night {\n  :deep(.popup) {\n    box-shadow:\n      0 2px 4px rgba(0, 0, 0, 0.48),\n      0 0 6px rgba(0, 0, 0, 0.16);\n  }\n\n  :deep(.tool-icon) {\n    border: 1px solid #444;\n    margin-top: -1px;\n    color: #666;\n\n    .icon-text {\n      color: #666;\n    }\n  }\n\n  :deep(.chapter) {\n    border: 1px solid #444;\n    color: #666;\n  }\n\n  :deep(.popper__arrow) {\n    background: #666;\n  }\n}\n\n@media screen and (max-width: 776px) {\n  .chapter-wrapper {\n    padding: 0;\n\n    .tool-bar {\n      left: 0;\n      width: 100vw;\n      margin-left: 0 !important;\n\n      .tools {\n        flex-direction: row;\n        justify-content: space-between;\n\n        .tool-icon {\n          border: none;\n        }\n      }\n    }\n\n    .read-bar {\n      right: 0;\n      width: 100vw;\n      margin-right: 0 !important;\n\n      .tools {\n        flex-direction: row;\n        justify-content: space-between;\n        padding: 0 15px;\n\n        .tool-icon {\n          border: none;\n          width: auto;\n\n          .iconfont {\n            display: inline-block;\n          }\n        }\n      }\n    }\n\n    .chapter {\n      width: 100vw !important;\n      padding: 0 20px;\n      box-sizing: border-box;\n    }\n  }\n}\n</style>\n"
  },
  {
    "path": "modules/web/src/views/BookShelf.vue",
    "content": "<template>\n  <div :class=\"{ 'index-wrapper': true, night: isNight, day: !isNight }\">\n    <div class=\"navigation-wrapper\">\n      <div class=\"navigation-title-wrapper\">\n        <div class=\"navigation-title\">阅读</div>\n        <div class=\"navigation-sub-title\">清风不识字，何故乱翻书</div>\n      </div>\n      <div class=\"search-wrapper\">\n        <el-input\n          placeholder=\"搜索书籍，在线书籍自动加入书架\"\n          v-model=\"searchWord\"\n          class=\"search-input\"\n          :prefix-icon=\"SearchIcon\"\n          @keyup.enter=\"searchBook\"\n        >\n        </el-input>\n      </div>\n      <div class=\"bottom-wrapper\">\n        <div class=\"recent-wrapper\">\n          <div class=\"recent-title\">最近阅读</div>\n          <div class=\"reading-recent\">\n            <el-tag\n              :type=\"\n                readingRecent.name == '尚无阅读记录' ? 'warning' : 'primary'\n              \"\n              class=\"recent-book\"\n              size=\"large\"\n              @click=\"\n                toDetail(\n                  readingRecent.bookUrl,\n                  readingRecent.name,\n                  readingRecent.author,\n                  readingRecent.chapterIndex,\n                  readingRecent.chapterPos,\n                  readingRecent.isSeachBook,\n                  true,\n                )\n              \"\n              :class=\"{ 'no-point': readingRecent.bookUrl == '' }\"\n            >\n              {{ readingRecent.name }}\n            </el-tag>\n          </div>\n        </div>\n        <div class=\"setting-wrapper\">\n          <div class=\"setting-title\">基本设定</div>\n          <div class=\"setting-item\">\n            <el-tag\n              :type=\"connectType\"\n              size=\"large\"\n              class=\"setting-connect\"\n              :class=\"{ 'no-point': newConnect }\"\n              @click=\"setLegadoRetmoteUrl\"\n            >\n              {{ connectStatus }}\n            </el-tag>\n          </div>\n        </div>\n      </div>\n      <div class=\"bottom-icons\">\n        <a\n          href=\"https://github.com/gedoor/legado_web_bookshelf\"\n          target=\"_blank\"\n        >\n          <div class=\"bottom-icon\">\n            <img :src=\"githubUrl\" alt=\"\" />\n          </div>\n        </a>\n      </div>\n    </div>\n    <div class=\"shelf-wrapper\" ref=\"shelfWrapper\">\n      <book-items\n        :books=\"books\"\n        @bookClick=\"handleBookClick\"\n        :isSearch=\"isSearching\"\n      ></book-items>\n    </div>\n  </div>\n</template>\n\n<script setup lang=\"ts\">\nimport '@/assets/bookshelf.css'\nimport '@/assets/fonts/shelffont.css'\nimport { useBookStore } from '@/store'\nimport githubUrl from '@/assets/imgs/github.png'\nimport { useLoading } from '@/hooks/loading'\nimport { Search as SearchIcon } from '@element-plus/icons-vue'\nimport { baseURL_localStorage_key } from '@/api/axios'\nimport API, {\n  legado_http_entry_point,\n  parseLeagdoHttpUrlWithDefault,\n  setApiEntryPoint,\n} from '@api'\nimport { validatorHttpUrl } from '@/utils/utils'\nimport type { Book, SeachBook } from '@/book'\nimport type { webReadConfig } from '@/web'\n\nconst store = useBookStore()\nconst isNight = computed(() => store.isNight)\n\n/** shortcuts of `store.setConfig` */\nconst applyReadConfig = (config?: webReadConfig) => {\n  try {\n    if (config !== undefined) store.setConfig(config)\n  } catch {\n    ElMessage.info('阅读界面配置解析错误')\n  }\n}\n\nconst readingRecent = ref<typeof store.readingBook>({\n  name: '尚无阅读记录',\n  author: '',\n  bookUrl: '',\n  chapterIndex: 0,\n  chapterPos: 0,\n  isSeachBook: false,\n})\n\nconst shelfWrapper = ref<HTMLElement>()\n//const shelfWrapper = useTemplateRef<HTMLElement>(\"shelfWrapper\")\nconst { showLoading, closeLoading, loadingWrapper, isLoading } = useLoading(\n  shelfWrapper,\n  '正在获取书籍信息',\n)\n\n// 书架书籍和在线书籍搜索\nconst books = shallowRef<Book[] | SeachBook[]>([])\nconst shelf = computed(() => store.shelf)\nconst searchWord = ref('')\nconst isSearching = ref(false)\nwatchEffect(() => {\n  if (isSearching.value && searchWord.value != '') return\n  isSearching.value = false\n  books.value = []\n  if (searchWord.value == '') {\n    books.value = shelf.value\n    return\n  }\n  books.value = shelf.value.filter(book => {\n    return (\n      book.name.includes(searchWord.value) ||\n      book.author.includes(searchWord.value)\n    )\n  })\n})\n//搜索在线书籍\nconst searchBook = () => {\n  if (searchWord.value == '') return\n  books.value = []\n  store.clearSearchBooks()\n  showLoading()\n  isSearching.value = true\n  API.search(\n    searchWord.value,\n    searcBooks => {\n      if (isLoading) {\n        closeLoading()\n      }\n      try {\n        store.setSearchBooks(searcBooks)\n        books.value = store.searchBooks\n        //store.searchBooks.forEach((item) => books.value.push(item));\n      } catch (e) {\n        ElMessage.error('后端数据错误')\n        throw e\n      }\n    },\n    () => {\n      closeLoading()\n      if (books.value.length == 0) {\n        ElMessage.info('搜索结果为空')\n      }\n    },\n  )\n}\n\n//连接状态\nconst connectionStore = useConnectionStore()\nconst { connectStatus, connectType, newConnect } = storeToRefs(connectionStore)\n\nconst setLegadoRetmoteUrl = () => {\n  ElMessageBox.prompt(\n    '请输入 后端地址 ( 如：http://127.0.0.1:9527 或者通过内网穿透的地址)',\n    '提示',\n    {\n      confirmButtonText: '确定',\n      cancelButtonText: '取消',\n      inputPlaceholder: legado_http_entry_point,\n      inputValidator: value => validatorHttpUrl(value),\n      inputErrorMessage: '输入的格式不对',\n      beforeClose: (action, instance, done) => {\n        if (action === 'confirm') {\n          connectionStore.setNewConnect(true)\n          instance.confirmButtonLoading = true\n          instance.confirmButtonText = '校验中……'\n          // instance.inputValue\n          const url = new URL(instance.inputValue).toString()\n          API.getReadConfig(url)\n            .then(function (config) {\n              connectionStore.setNewConnect(false)\n              applyReadConfig(config)\n              instance.confirmButtonLoading = false\n              store.clearSearchBooks()\n              setApiEntryPoint(...parseLeagdoHttpUrlWithDefault(url))\n              if (url === location.origin) {\n                localStorage.removeItem(baseURL_localStorage_key)\n              } else {\n                localStorage.setItem(baseURL_localStorage_key, url)\n              }\n              store.loadBookShelf()\n              done()\n            })\n            .catch(function (error) {\n              connectionStore.setNewConnect(false)\n              instance.confirmButtonLoading = false\n              instance.confirmButtonText = '确定'\n              throw error\n            })\n        } else {\n          done()\n        }\n      },\n    },\n  )\n}\n\nconst router = useRouter()\nconst handleBookClick = async (book: SeachBook | Book) => {\n  // 判断是否为 searchBook\n  const isSeachBook = 'respondTime' in book\n  if (isSeachBook) {\n    await API.saveBook(book)\n  }\n  const {\n    bookUrl,\n    name,\n    author,\n    // @ts-expect-error: descruct with default value\n    durChapterIndex = 0,\n    // @ts-expect-error: descruct with default value\n    durChapterPos = 0,\n  } = book\n\n  toDetail(bookUrl, name, author, durChapterIndex, durChapterPos, isSeachBook)\n}\nconst toDetail = (\n  bookUrl: string,\n  bookName: string,\n  bookAuthor: string,\n  chapterIndex: number,\n  chapterPos: number,\n  isSeachBook: boolean | undefined = false,\n  fromReadRecentClick = false,\n) => {\n  if (bookName === '尚无阅读记录') return\n  // 最近书籍不再书架上 自动搜索\n  if (\n    fromReadRecentClick &&\n    shelf.value.every(book => book.bookUrl !== bookUrl)\n  ) {\n    searchWord.value = bookName\n    searchBook()\n    return\n  }\n  sessionStorage.setItem('bookUrl', bookUrl)\n  sessionStorage.setItem('bookName', bookName)\n  sessionStorage.setItem('bookAuthor', bookAuthor)\n  sessionStorage.setItem('chapterIndex', String(chapterIndex))\n  sessionStorage.setItem('chapterPos', String(chapterPos))\n  sessionStorage.setItem('isSeachBook', String(isSeachBook))\n  readingRecent.value = {\n    name: bookName,\n    author: bookAuthor,\n    bookUrl,\n    chapterIndex,\n    chapterPos,\n    isSeachBook,\n  }\n  localStorage.setItem('readingRecent', JSON.stringify(readingRecent.value))\n  router.push({\n    path: '/chapter',\n  })\n}\n\nconst loadShelf = async () => {\n  await store.loadWebConfig()\n  await store.saveBookProgress()\n  //确保各种网络情况下同步请求先完成\n  await store.loadBookShelf()\n}\n\nonMounted(() => {\n  //获取最近阅读书籍\n  const readingRecentStr = localStorage.getItem('readingRecent')\n  if (readingRecentStr != null) {\n    readingRecent.value = JSON.parse(readingRecentStr)\n    if (typeof readingRecent.value.chapterIndex == 'undefined') {\n      readingRecent.value.chapterIndex = 0\n    }\n  }\n  console.log('bookshelf mounted')\n  loadingWrapper(loadShelf())\n})\n</script>\n\n<style lang=\"scss\" scoped>\n.index-wrapper {\n  height: 100%;\n  width: 100%;\n  display: flex;\n  flex-direction: row;\n\n  .navigation-wrapper {\n    width: 260px;\n    min-width: 260px;\n    padding: 48px 36px;\n    background-color: #f7f7f7;\n\n    .navigation-title {\n      font-size: 24px;\n      font-weight: 500;\n      font-family: FZZCYSK;\n    }\n\n    .navigation-sub-title {\n      font-size: 16px;\n      font-weight: 300;\n      font-family: FZZCYSK;\n      margin-top: 16px;\n      color: #b1b1b1;\n    }\n\n    .search-wrapper {\n      .search-input {\n        border-radius: 50%;\n        margin-top: 24px;\n\n        :deep(.el-input__wrapper) {\n          border-radius: 50px;\n          border-color: #e3e3e3;\n        }\n      }\n    }\n\n    .bottom-wrapper {\n      display: flex;\n      flex-direction: column;\n    }\n\n    .recent-wrapper {\n      margin-top: 36px;\n\n      .recent-title {\n        font-size: 14px;\n        color: #b1b1b1;\n        font-family: FZZCYSK;\n      }\n\n      .reading-recent {\n        margin: 18px 0;\n\n        .recent-book {\n          font-size: 10px;\n          /*           // font-weight: 400;\n          // margin: 12px 0;\n          // font-weight: 500;\n          // color: #6B7C87; */\n          cursor: pointer;\n          /*           // padding: 6px 18px; */\n        }\n      }\n    }\n\n    .setting-wrapper {\n      margin-top: 36px;\n\n      .setting-title {\n        font-size: 14px;\n        color: #b1b1b1;\n        font-family: FZZCYSK;\n      }\n\n      .no-point {\n        pointer-events: none;\n      }\n\n      .setting-connect {\n        font-size: 8px;\n        margin-top: 16px;\n        /*         // color: #6B7C87; */\n        cursor: pointer;\n      }\n    }\n\n    .bottom-icons {\n      position: fixed;\n      bottom: 0;\n      height: 120px;\n      width: 260px;\n      align-items: center;\n      display: flex;\n      flex-direction: row;\n    }\n  }\n\n  .shelf-wrapper {\n    padding: 48px 48px;\n    width: 100%;\n    display: flex;\n    flex-direction: column;\n    box-sizing: border-box;\n    overflow: hidden;\n  }\n}\n\n@media screen and (max-width: 750px) {\n  .index-wrapper {\n    overflow-x: hidden;\n    flex-direction: column;\n\n    .navigation-wrapper {\n      padding: 20px 24px;\n      box-sizing: border-box;\n      width: 100%;\n\n      .navigation-title-wrapper {\n        white-space: nowrap;\n        display: flex;\n        justify-content: space-between;\n        align-items: flex-end;\n      }\n\n      .bottom-wrapper {\n        flex-direction: row;\n\n        > * {\n          flex-grow: 1;\n          margin-top: 18px;\n\n          .reading-recent,\n          .setting-item {\n            margin-bottom: 0px;\n          }\n        }\n      }\n\n      .bottom-icons {\n        display: none;\n      }\n    }\n\n    .shelf-wrapper {\n      padding: 0;\n      flex-grow: 1;\n\n      :deep(.el-loading-spinner) {\n        display: none;\n      }\n    }\n  }\n}\n\n.night {\n  .navigation-wrapper {\n    background-color: #454545;\n\n    .navigation-title {\n      color: #aeaeae;\n    }\n\n    .search-wrapper {\n      .search-input {\n        .el-input__wrapper {\n          background-color: #454545;\n        }\n\n        .el-input__inner {\n          color: #b1b1b1;\n        }\n      }\n    }\n  }\n\n  :deep(.shelf-wrapper) {\n    background-color: #161819;\n  }\n}\n</style>\n"
  },
  {
    "path": "modules/web/src/views/SourceEditor.vue",
    "content": "<template>\n  <div class=\"editor\">\n    <source-tab-form class=\"left\" :config=\"config\" />\n    <tool-bar />\n    <source-tab-tools class=\"right\" />\n  </div>\n</template>\n<script setup lang=\"ts\">\nimport bookSourceConfig from '@/config/bookSourceEditConfig'\nimport rssSourceConfig from '@/config/rssSourceEditConfig'\nimport '@/assets/sourceeditor.css'\nimport { useDark } from '@vueuse/core'\nimport type { SourceConfig } from '@/config/sourceConfig'\n\nuseDark()\n\nlet config: SourceConfig\n\nif (/bookSource/i.test(location.href)) {\n  config = bookSourceConfig as SourceConfig\n  document.title = '书源管理'\n} else {\n  config = rssSourceConfig as SourceConfig\n  document.title = '订阅源管理'\n}\n</script>\n<style lang=\"scss\" scoped>\n.editor {\n  display: flex;\n  height: 100vh;\n  overflow: hidden;\n  .left {\n    flex: 1;\n    margin-left: 20px;\n  }\n  .right {\n    flex: 1;\n    width: 360px;\n    margin-right: 20px;\n  }\n}\n</style>\n"
  },
  {
    "path": "modules/web/src/web.d.ts",
    "content": "export type webReadConfig = {\n  theme: number\n  font: number\n  fontSize: number\n  readWidth: number\n  infiniteLoading: boolean\n  customFontName: string\n  jumpDuration: number\n  spacing: {\n    paragraph: number\n    line: number\n    letter: number\n  }\n}\n"
  },
  {
    "path": "modules/web/tsconfig.app.json",
    "content": "{\n  \"extends\": \"@vue/tsconfig/tsconfig.dom.json\",\n  \"include\": [\"env.d.ts\", \"src/**/*\", \"src/**/*.vue\"],\n  \"exclude\": [\"src/**/__tests__/*\"],\n  \"compilerOptions\": {\n    \"composite\": true,\n    \"tsBuildInfoFile\": \"./node_modules/.tmp/tsconfig.app.tsbuildinfo\",\n\n    \"baseUrl\": \".\",\n    \"paths\": {\n      \"@/*\": [\"./src/*\"],\n      \"@api\": [\"./src/api\"],\n      \"@utils/*\": [\"./src/utils/*\"]\n    }\n  }\n}\n"
  },
  {
    "path": "modules/web/tsconfig.json",
    "content": "{\n  \"files\": [],\n  \"references\": [\n    {\n      \"path\": \"./tsconfig.node.json\"\n    },\n    {\n      \"path\": \"./tsconfig.app.json\"\n    }\n  ],\n}\n"
  },
  {
    "path": "modules/web/tsconfig.node.json",
    "content": "{\n  \"extends\": \"@tsconfig/node20/tsconfig.json\",\n  \"include\": [\n    \"vite.config.*\",\n    \"vitest.config.*\",\n    \"cypress.config.*\",\n    \"nightwatch.conf.*\",\n    \"playwright.config.*\"\n  ],\n  \"compilerOptions\": {\n    \"composite\": true,\n    \"noEmit\": true,\n    \"tsBuildInfoFile\": \"./node_modules/.tmp/tsconfig.node.tsbuildinfo\",\n\n    \"module\": \"ESNext\",\n    \"moduleResolution\": \"Bundler\",\n    \"types\": [\"node\"]\n  }\n}\n"
  },
  {
    "path": "modules/web/vite.config.ts",
    "content": "import { fileURLToPath, URL } from \"node:url\";\nimport { defineConfig } from \"vite\";\nimport vue from \"@vitejs/plugin-vue\";\nimport Icons from \"unplugin-icons/vite\";\nimport IconsResolver from \"unplugin-icons/resolver\";\nimport AutoImport from \"unplugin-auto-import/vite\";\nimport Components from \"unplugin-vue-components/vite\";\nimport { ElementPlusResolver } from \"unplugin-vue-components/resolvers\";\n\n// https://vitejs.dev/config/\nexport default defineConfig(({ mode }) => {\n  return {\n    plugins: [\n      vue(),\n      AutoImport({\n        imports: [\"vue\", \"vue-router\", \"pinia\"],\n        include: [/\\.[tj]sx?$/, /\\.vue$/, /\\.vue\\?vue/],\n        dirs: [\"src/components\", \"src/store\", \"*.d.ts\"],\n        eslintrc: {\n          //enabled: true,\n        },\n        resolvers: [\n          ElementPlusResolver(),\n          IconsResolver({\n            prefix: \"Icon\",\n          }),\n        ],\n        dts: \"./src/auto-imports.d.ts\",\n      }),\n      Components({\n        resolvers: [\n          ElementPlusResolver(),\n          IconsResolver({\n            enabledCollections: [\"ep\"],\n          }),\n        ],\n        dts: \"./src/components.d.ts\",\n      }),\n      Icons({\n        autoInstall: true,\n      }),\n    ],\n    base: mode === \"development\" ? \"/\" : \"./\",\n    server: {\n      port: 8080,\n    },\n    resolve: {\n      alias: {\n        \"@\": fileURLToPath(new URL(\"./src\", import.meta.url)),\n        \"@api\": fileURLToPath(new URL(\"./src/api\", import.meta.url)),\n        \"@utils\": fileURLToPath(new URL(\"./src/utils/\", import.meta.url)),\n      },\n    },\n    esbuild: {\n      drop: mode === \"development\" ? undefined : [\"console\", \"debugger\"],\n    },\n    build: {\n      rollupOptions: {\n        output: {\n          manualChunks: (id) => {\n            if (id.includes(\"node_modules\")) {\n              return \"vendor\";\n            }\n          },\n        },\n      },\n    },\n    css: {\n      preprocessorOptions: {\n        scss: {\n          api: 'modern-compiler', // or 'modern'\n        },\n      },\n    },\n  }\n});"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"legado\",\n  \"version\": \"1.0.0\",\n  \"devDependencies\": {\n    \"cz-conventional-changelog\": \"^3.1.0\"\n  },\n  \"config\": {\n    \"commitizen\": {\n      \"path\": \"./node_modules/cz-conventional-changelog\"\n    }\n  },\n  \"description\": \"[![Commitizen friendly](https://img.shields.io/badge/commitizen-friendly-brightgreen.svg)](http://commitizen.github.io/cz-cli/)\",\n  \"main\": \"index.js\",\n  \"scripts\": {\n    \"test\": \"echo \\\"Error: no test specified\\\" && exit 1\"\n  },\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/gedoor/legado.git\"\n  },\n  \"keywords\": [],\n  \"author\": \"\",\n  \"license\": \"ISC\",\n  \"bugs\": {\n    \"url\": \"https://github.com/gedoor/legado/issues\"\n  },\n  \"homepage\": \"https://github.com/gedoor/legado#readme\"\n}\n"
  },
  {
    "path": "settings.gradle",
    "content": "pluginManagement {\n    repositories {\n        google {\n            content {\n                includeGroupByRegex(\"com\\\\.android.*\")\n                includeGroupByRegex(\"com\\\\.google.*\")\n                includeGroupByRegex(\"androidx.*\")\n            }\n        }\n        //原仓库\n        gradlePluginPortal()\n        mavenCentral()\n        //镜像仓库,无法连接源仓库自行启用镜像仓库,不要提交修改\n        //maven { url \"https://maven-central-asia.storage-download.googleapis.com/maven2/\" }\n        //maven { url 'https://maven.aliyun.com/repository/google' }\n        //maven { url 'https://maven.aliyun.com/repository/public' }\n        //maven { url 'https://maven.aliyun.com/repository/gradle-plugin' }\n        //maven { url 'https://repo.huaweicloud.com/repository/maven/' }\n    }\n}\n\ndependencyResolutionManagement {\n    repositoriesMode.set(RepositoriesMode.FAIL_ON_PROJECT_REPOS)\n    repositories {\n        //原仓库\n        google {\n            content {\n                includeGroupByRegex(\"com\\\\.android.*\")\n                includeGroupByRegex(\"com\\\\.google.*\")\n                includeGroupByRegex(\"androidx.*\")\n            }\n        }\n        maven {\n            url = 'https://jitpack.io'\n            content {\n                includeGroupByRegex(\"com\\\\.github.*\")\n            }\n        }\n        mavenCentral()\n\n        //镜像仓库,无法连接源仓库自行启用镜像仓库,不要提交修改\n        //maven { url \"https://maven-central-asia.storage-download.googleapis.com/maven2/\" }\n        //maven { url 'https://maven.aliyun.com/repository/google' }\n        //maven { url 'https://maven.aliyun.com/repository/public' }\n        //maven { url 'https://repo.huaweicloud.com/repository/maven/' }\n    }\n}\nrootProject.name = 'legado'\n\ninclude ':app'\ninclude ':modules:book'\ninclude ':modules:rhino'"
  }
]